Message ID | 20201002192210.19967-3-l.stelmach@samsung.com (mailing list archive) |
---|---|
State | Not Applicable |
Headers | show |
Series | AX88796C SPI Ethernet Adapter | expand |
> +static u32 ax88796c_get_link(struct net_device *ndev) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + > + mutex_lock(&ax_local->spi_lock); > + > + phy_read_status(ndev->phydev); > + > + mutex_unlock(&ax_local->spi_lock); Why do you take this mutux before calling phy_read_status()? The phylib core will not be taking this mutex when it calls into the PHY driver. This applies to all the calls you have with phy_ There should not be any need to call phy_read_status(). phylib will do this once per second, or after any interrupt from the PHY. so just use phydev->link > +static void > +ax88796c_get_regs(struct net_device *ndev, struct ethtool_regs *regs, void *_p) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + u16 *p = _p; > + int offset, i; > + > + memset(p, 0, AX88796C_REGDUMP_LEN); > + > + 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++; > + } > + > + for (i = 0; i < AX88796C_PHY_REGDUMP_LEN / 2; i++) { > + *p = phy_read(ax_local->phydev, i); > + p++; Depending on the PHY, that can be dangerous. phylib could be busy doing things with the PHY. It could be looking at a different page for example. miitool(1) can give you the same functionally without the MAC driver doing anything, other than forwarding the IOCTL call on. > +int ax88796c_mdio_read(struct mii_bus *mdiobus, int phy_id, int loc) > +{ > + struct ax88796c_device *ax_local = mdiobus->priv; > + int ret; > + > + 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) > + return -EBUSY; Return whatever read_poll_timeout() returned. It is probably -ETIMEDOUT, but it could also be -EIO for example. > +ax88796c_mdio_write(struct mii_bus *mdiobus, int phy_id, int loc, u16 val) > +{ > + struct ax88796c_device *ax_local = mdiobus->priv; > + int ret; > + > + 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); > + if (ret) > + return -EIO; > + > + if (loc == MII_ADVERTISE) { > + AX_WRITE(&ax_local->ax_spi, (BMCR_FULLDPLX | BMCR_ANRESTART | > + BMCR_ANENABLE | BMCR_SPEED100), P2_MDIODR); > + AX_WRITE(&ax_local->ax_spi, (MDIOCR_RADDR(MII_BMCR) | > + MDIOCR_FADDR(phy_id) | MDIOCR_WRITE), > + P2_MDIOCR); > What is this doing? > + ret = read_poll_timeout(AX_READ, ret, > + ((ret & MDIOCR_VALID) != 0), 0, > + jiffies_to_usecs(HZ / 100), false, > + &ax_local->ax_spi, P2_MDIOCR); > + if (ret) > + return -EIO; > + } > + > + return 0; > +} > +static char *no_regs_list = "80018001,e1918001,8001a001,fc0d0000"; > +unsigned long ax88796c_no_regs_mask[AX88796C_REGDUMP_LEN / (sizeof(unsigned long) * 8)]; > + > +module_param(comp, int, 0444); > +MODULE_PARM_DESC(comp, "0=Non-Compression Mode, 1=Compression Mode"); > + > +module_param(msg_enable, int, 0444); > +MODULE_PARM_DESC(msg_enable, "Message mask (see linux/netdevice.h for bitmap)"); No module parameters allowed, not in netdev. > +static int ax88796c_reload_eeprom(struct ax88796c_device *ax_local) > +{ > + int ret; > + > + AX_WRITE(&ax_local->ax_spi, EECR_RELOAD, P3_EECR); > + > + ret = read_poll_timeout(AX_READ, ret, > + (ret & PSR_DEV_READY), > + 0, jiffies_to_usecs(2 * HZ / 1000), false, > + &ax_local->ax_spi, P0_PSR); > + if (ret) { > + dev_err(&ax_local->spi->dev, > + "timeout waiting for reload eeprom\n"); > + return -1; return ret not EINVAL which is -1 > +static int ax88796c_set_mac_address(struct net_device *ndev, void *p) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + struct sockaddr *addr = p; > + > + if (!is_valid_ether_addr(addr->sa_data)) > + return -EADDRNOTAVAIL; It would be better to just use eth_mac_addr(). > +static int > +ax88796c_check_free_pages(struct ax88796c_device *ax_local, u8 need_pages) > +{ > + u8 free_pages; > + u16 tmp; > + > + free_pages = AX_READ(&ax_local->ax_spi, P0_TFBFCR) & TX_FREEBUF_MASK; > + if (free_pages < need_pages) { > + /* schedule free page interrupt */ > + tmp = AX_READ(&ax_local->ax_spi, P0_TFBFCR) > + & TFBFCR_SCHE_FREE_PAGE; > + AX_WRITE(&ax_local->ax_spi, tmp | TFBFCR_TX_PAGE_SET | > + TFBFCR_SET_FREE_PAGE(need_pages), > + P0_TFBFCR); > + return -ENOMEM; > + } > + > + return 0; > +} > + > +static struct sk_buff * > +ax88796c_tx_fixup(struct net_device *ndev, struct sk_buff_head *q) > +{ > + if (netif_msg_pktdata(ax_local)) { > + char pfx[IFNAMSIZ + 7]; > + > + snprintf(pfx, sizeof(pfx), "%s: ", ndev->name); > + > + netdev_info(ndev, "TX packet len %d, total len %d, seq %d\n", > + pkt_len, tx_skb->len, seq_num); > + > + netdev_info(ndev, " SPI Header:\n"); > + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, > + tx_skb->data, 4, 0); > + > + netdev_info(ndev, " TX SOP:\n"); > + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, > + tx_skb->data + 4, TX_OVERHEAD, 0); > + > + netdev_info(ndev, " TX packet:\n"); > + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, > + tx_skb->data + 4 + TX_OVERHEAD, > + tx_skb->len - TX_EOP_SIZE - 4 - TX_OVERHEAD, 0); > + > + netdev_info(ndev, " TX EOP:\n"); > + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, > + tx_skb->data + tx_skb->len - 4, 4, 0); > + } I expect others are going to ask you to remove this. > +static void ax88796c_handle_link_change(struct net_device *ndev) > +{ > + if (net_ratelimit()) > + phy_print_status(ndev->phydev); > +} > + > +void ax88796c_phy_init(struct ax88796c_device *ax_local) > +{ > + /* Enable PHY auto-polling */ > + AX_WRITE(&ax_local->ax_spi, > + PCR_PHYID(0x10) | PCR_POLL_EN | > + PCR_POLL_FLOWCTRL | PCR_POLL_BMCR, P2_PCR); Auto-polling of the PHY is generally a bad idea. The hardware is not going to respect the phydev->lock mutex, for example. Disable this, and add a proper ax88796c_handle_link_change(). > +static int > +ax88796c_open(struct net_device *ndev) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + int ret; > + unsigned long irq_flag = IRQF_SHARED; > + > + mutex_lock(&ax_local->spi_lock); > + > + ret = ax88796c_soft_reset(ax_local); > + if (ret < 0) > + return -ENODEV; > + > + ret = request_irq(ndev->irq, ax88796c_interrupt, > + irq_flag, ndev->name, ndev); Maybe look at using request_threaded_irq(). You can then remove your work queue, and do the work in the thread_fn. > + if (ret) { > + netdev_err(ndev, "unable to get IRQ %d (errno=%d).\n", > + ndev->irq, ret); > + return -ENXIO; return ret; In general, never change a return code unless you have a really good reason why. And if you do have a reason, document it. > +static int > +ax88796c_close(struct net_device *ndev) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + > + netif_stop_queue(ndev); > + > + free_irq(ndev->irq, ndev); > + > + phy_stop(ndev->phydev); > + > + mutex_lock(&ax_local->spi_lock); > + > + AX_WRITE(&ax_local->ax_spi, IMR_MASKALL, P0_IMR); > + ax88796c_free_skb_queue(&ax_local->tx_wait_q); > + > + ax88796c_soft_reset(ax_local); > + > + mutex_unlock(&ax_local->spi_lock); > + netif_carrier_off(ndev); phy_stop() will do that for you. > +static int ax88796c_probe(struct spi_device *spi) > +{ > + ax_local->mdiobus->priv = ax_local; > + ax_local->mdiobus->read = ax88796c_mdio_read; > + ax_local->mdiobus->write = ax88796c_mdio_write; > + ax_local->mdiobus->name = "ax88976c-mdiobus"; > + ax_local->mdiobus->phy_mask = ~(1 << 0x10); BIT(0x10); > + > + ret = devm_register_netdev(&spi->dev, ndev); > + if (ret) { > + dev_err(&spi->dev, "failed to register a network device\n"); > + destroy_workqueue(ax_local->ax_work_queue); > + goto err; > + } The device is not live. If this is being used for NFS root, the kernel will start using it. So what sort of mess will it get into, if there is no PHY yet? Nothing important should happen after register_netdev(). > + > + ax_local->phydev = phy_find_first(ax_local->mdiobus); > + if (!ax_local->phydev) { > + dev_err(&spi->dev, "no PHY found\n"); > + ret = -ENODEV; > + goto err; > + } > + > + ax_local->phydev->irq = PHY_IGNORE_INTERRUPT; > + phy_connect_direct(ax_local->ndev, ax_local->phydev, > + ax88796c_handle_link_change, > + PHY_INTERFACE_MODE_MII); > + > + netif_info(ax_local, probe, ndev, "%s %s registered\n", > + dev_driver_string(&spi->dev), > + dev_name(&spi->dev)); > + phy_attached_info(ax_local->phydev); > + > + ret = 0; > +err: > + return ret; > +} > + > +static int ax88796c_remove(struct spi_device *spi) > +{ > + struct ax88796c_device *ax_local = dev_get_drvdata(&spi->dev); > + struct net_device *ndev = ax_local->ndev; You might want to disconnect the PHY. > + > + netif_info(ax_local, probe, ndev, "removing network device %s %s\n", > + dev_driver_string(&spi->dev), > + dev_name(&spi->dev)); > + > + destroy_workqueue(ax_local->ax_work_queue); > + > + return 0; > +} > + > +static const struct of_device_id ax88796c_dt_ids[] = { > + { .compatible = "asix,ax88796c" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ax88796c_dt_ids); > + > +static const struct spi_device_id asix_id[] = { > + { "ax88796c", 0 }, > + { } > +}; > +MODULE_DEVICE_TABLE(spi, asix_id); > + > +static struct spi_driver ax88796c_spi_driver = { > + .driver = { > + .name = DRV_NAME, > +#ifdef CONFIG_USE_OF > + .of_match_table = of_match_ptr(ax88796c_dt_ids), > +#endif I don't think you need the #ifdef. > +#ifndef _AX88796C_MAIN_H > +#define _AX88796C_MAIN_H > + > +#include <linux/netdevice.h> > +#include <linux/mii.h> > + > +#include "ax88796c_spi.h" > + > +/* These identify the driver base version and may not be removed. */ > +#define DRV_NAME "ax88796c" > +#define ADP_NAME "ASIX AX88796C SPI Ethernet Adapter" > +#define DRV_VERSION "1.2.0" DRV_VERSION are pretty pointless. Not sure you use it anyway. Please remove. > + unsigned long capabilities; > + #define AX_CAP_DMA 1 > + #define AX_CAP_COMP 2 > + #define AX_CAP_BIDIR 4 BIT(0), BIT(1), BIT(2)... > +struct skb_data; > + > +struct skb_data { > + enum skb_state state; > + struct net_device *ndev; > + struct sk_buff *skb; > + size_t len; > + dma_addr_t phy_addr; > +}; A forward definition, followed by the real definition? > + #define FER_IPALM (1 << 0) > + #define FER_DCRC (1 << 1) > + #define FER_RH3M (1 << 2) > + #define FER_HEADERSWAP (1 << 7) > + #define FER_WSWAP (1 << 8) > + #define FER_BSWAP (1 << 9) > + #define FER_INTHI (1 << 10) > + #define FER_INTLO (0 << 10) > + #define FER_IRQ_PULL (1 << 11) > + #define FER_RXEN (1 << 14) > + #define FER_TXEN (1 << 15) Isn't checkpatch giving warnings and suggesting BIT? Andrew
On 02.10.2020 21:22, Łukasz Stelmach wrote: > 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. > > [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: Łukasz Stelmach <l.stelmach@samsung.com> > --- > MAINTAINERS | 6 + > drivers/net/ethernet/Kconfig | 1 + > drivers/net/ethernet/Makefile | 1 + > drivers/net/ethernet/asix/Kconfig | 21 + > drivers/net/ethernet/asix/Makefile | 6 + > drivers/net/ethernet/asix/ax88796c_ioctl.c | 241 +++++ > drivers/net/ethernet/asix/ax88796c_ioctl.h | 27 + > drivers/net/ethernet/asix/ax88796c_main.c | 1041 ++++++++++++++++++++ > drivers/net/ethernet/asix/ax88796c_main.h | 568 +++++++++++ > drivers/net/ethernet/asix/ax88796c_spi.c | 111 +++ > drivers/net/ethernet/asix/ax88796c_spi.h | 69 ++ > 11 files changed, 2092 insertions(+) > create mode 100644 drivers/net/ethernet/asix/Kconfig > create mode 100644 drivers/net/ethernet/asix/Makefile > create mode 100644 drivers/net/ethernet/asix/ax88796c_ioctl.c > create mode 100644 drivers/net/ethernet/asix/ax88796c_ioctl.h > create mode 100644 drivers/net/ethernet/asix/ax88796c_main.c > create mode 100644 drivers/net/ethernet/asix/ax88796c_main.h > create mode 100644 drivers/net/ethernet/asix/ax88796c_spi.c > create mode 100644 drivers/net/ethernet/asix/ax88796c_spi.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index deaafb617361..654eb0127479 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -2822,6 +2822,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,ax99706c-spi.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) > diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig > index de50e8b9e656..f3b218e45ea5 100644 > --- a/drivers/net/ethernet/Kconfig > +++ b/drivers/net/ethernet/Kconfig > @@ -32,6 +32,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/aurora/Kconfig" > source "drivers/net/ethernet/broadcom/Kconfig" > diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile > index f8f38dcb5f8a..9eb368d93607 100644 > --- a/drivers/net/ethernet/Makefile > +++ b/drivers/net/ethernet/Makefile > @@ -18,6 +18,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_AURORA) += aurora/ > obj-$(CONFIG_NET_VENDOR_CADENCE) += cadence/ > diff --git a/drivers/net/ethernet/asix/Kconfig b/drivers/net/ethernet/asix/Kconfig > new file mode 100644 > index 000000000000..7caa45607450 > --- /dev/null > +++ b/drivers/net/ethernet/asix/Kconfig > @@ -0,0 +1,21 @@ > +# > +# 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" > + depends on SPI > + depends on GPIOLIB > + help > + Say Y here if you intend to use ASIX AX88796C attached in SPI mode. > + > +endif # NET_VENDOR_ASIX > diff --git a/drivers/net/ethernet/asix/Makefile b/drivers/net/ethernet/asix/Makefile > new file mode 100644 > index 000000000000..0bfbbb042634 > --- /dev/null > +++ b/drivers/net/ethernet/asix/Makefile > @@ -0,0 +1,6 @@ > +# > +# Makefile for the Asix network device drivers. > +# > + > +obj-$(CONFIG_SPI_AX88796C) += ax88796c.o > +ax88796c-y := ax88796c_main.o ax88796c_ioctl.o ax88796c_spi.o > diff --git a/drivers/net/ethernet/asix/ax88796c_ioctl.c b/drivers/net/ethernet/asix/ax88796c_ioctl.c > new file mode 100644 > index 000000000000..b3e3ac96790d > --- /dev/null > +++ b/drivers/net/ethernet/asix/ax88796c_ioctl.c > @@ -0,0 +1,241 @@ > +// 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 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_link(struct net_device *ndev) Why not using ethtool_op_get_link ? > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + > + mutex_lock(&ax_local->spi_lock); > + > + phy_read_status(ndev->phydev); > + > + mutex_unlock(&ax_local->spi_lock); > + > + return ndev->phydev->link; > +} > + > +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 int > +ax88796c_get_link_ksettings(struct net_device *ndev, > + struct ethtool_link_ksettings *cmd) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + int ret; > + > + mutex_lock(&ax_local->spi_lock); > + > + ret = phy_ethtool_get_link_ksettings(ndev, cmd); > + > + mutex_unlock(&ax_local->spi_lock); > + > + return ret; > +} > + > +static int > +ax88796c_set_link_ksettings(struct net_device *ndev, > + const struct ethtool_link_ksettings *cmd) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + int ret; > + > + mutex_lock(&ax_local->spi_lock); > + > + ret = phy_ethtool_set_link_ksettings(ndev, cmd); > + > + mutex_unlock(&ax_local->spi_lock); > + > + return ret; > +} > + > +static int ax88796c_nway_reset(struct net_device *ndev) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + int ret; > + > + mutex_lock(&ax_local->spi_lock); > + > + ret = phy_ethtool_nway_reset(ndev); > + > + mutex_unlock(&ax_local->spi_lock); > + > + return ret; > +} > + > +static u32 ax88796c_ethtool_getmsglevel(struct net_device *ndev) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + > + return ax_local->msg_enable; > +} > + > +static void ax88796c_ethtool_setmsglevel(struct net_device *ndev, u32 level) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + > + ax_local->msg_enable = level; > +} > + > +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); > + u16 *p = _p; > + int offset, i; > + > + memset(p, 0, AX88796C_REGDUMP_LEN); > + > + 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++; > + } > + > + for (i = 0; i < AX88796C_PHY_REGDUMP_LEN / 2; i++) { > + *p = phy_read(ax_local->phydev, i); > + p++; > + } > +} > + > +int ax88796c_mdio_read(struct mii_bus *mdiobus, int phy_id, int loc) > +{ > + struct ax88796c_device *ax_local = mdiobus->priv; > + int ret; > + > + 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) > + return -EBUSY; > + > + return AX_READ(&ax_local->ax_spi, P2_MDIODR); > +} > + > +int > +ax88796c_mdio_write(struct mii_bus *mdiobus, int phy_id, int loc, u16 val) > +{ > + struct ax88796c_device *ax_local = mdiobus->priv; > + int ret; > + > + 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); > + if (ret) > + return -EIO; > + > + if (loc == MII_ADVERTISE) { > + AX_WRITE(&ax_local->ax_spi, (BMCR_FULLDPLX | BMCR_ANRESTART | > + BMCR_ANENABLE | BMCR_SPEED100), P2_MDIODR); This looks very hacky. Why do you do this? What if the user wants to switch to fixed mode? > + AX_WRITE(&ax_local->ax_spi, (MDIOCR_RADDR(MII_BMCR) | > + 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); > + if (ret) > + return -EIO; > + } > + > + return 0; > +} > + > +void ax88796c_set_csums(struct ax88796c_device *ax_local) > +{ > + struct net_device *ndev = ax_local->ndev; > + > + if (ndev->features & NETIF_F_RXCSUM) { > + AX_WRITE(&ax_local->ax_spi, COERCR0_DEFAULT, P4_COERCR0); > + AX_WRITE(&ax_local->ax_spi, COERCR1_DEFAULT, P4_COERCR1); > + } else { > + AX_WRITE(&ax_local->ax_spi, 0, P4_COERCR0); > + AX_WRITE(&ax_local->ax_spi, 0, P4_COERCR1); > + } > + > + if (ndev->features & NETIF_F_HW_CSUM) { > + AX_WRITE(&ax_local->ax_spi, COETCR0_DEFAULT, P4_COETCR0); > + AX_WRITE(&ax_local->ax_spi, COETCR1_TXPPPE, P4_COETCR1); > + } else { > + AX_WRITE(&ax_local->ax_spi, 0, P4_COETCR0); > + AX_WRITE(&ax_local->ax_spi, 0, P4_COETCR1); > + } > +} > + > +const struct ethtool_ops ax88796c_ethtool_ops = { > + .get_drvinfo = ax88796c_get_drvinfo, > + .get_link = ax88796c_get_link, > + .get_msglevel = ax88796c_get_msglevel, > + .set_msglevel = ax88796c_set_msglevel, > + .get_link_ksettings = ax88796c_get_link_ksettings, > + .set_link_ksettings = ax88796c_set_link_ksettings, > + .nway_reset = ax88796c_nway_reset, > + .get_msglevel = ax88796c_ethtool_getmsglevel, > + .set_msglevel = ax88796c_ethtool_setmsglevel, > + .get_regs_len = ax88796c_get_regs_len, > + .get_regs = ax88796c_get_regs, > +}; > + > +int ax88796c_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + int ret; > + > + mutex_lock(&ax_local->spi_lock); > + > + ret = phy_mii_ioctl(ndev->phydev, ifr, cmd); > + > + mutex_unlock(&ax_local->spi_lock); > + > + return ret; > +} > diff --git a/drivers/net/ethernet/asix/ax88796c_ioctl.h b/drivers/net/ethernet/asix/ax88796c_ioctl.h > new file mode 100644 > index 000000000000..d478981bf995 > --- /dev/null > +++ b/drivers/net/ethernet/asix/ax88796c_ioctl.h > @@ -0,0 +1,27 @@ > +/* 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); > +void ax88796c_set_csums(struct ax88796c_device *ax_local); > +int ax88796c_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd); > + > +#endif > diff --git a/drivers/net/ethernet/asix/ax88796c_main.c b/drivers/net/ethernet/asix/ax88796c_main.c > new file mode 100644 > index 000000000000..2148ea01362a > --- /dev/null > +++ b/drivers/net/ethernet/asix/ax88796c_main.c > @@ -0,0 +1,1041 @@ > +// 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 "ax88796c_main.h" > +#include "ax88796c_ioctl.h" > + > +#include <linux/bitmap.h> > +#include <linux/etherdevice.h> > +#include <linux/iopoll.h> > +#include <linux/mdio.h> > +#include <linux/module.h> > +#include <linux/netdevice.h> > +#include <linux/of.h> > +#include <linux/phy.h> > +#include <linux/spi/spi.h> > + > +static int comp; > +static int msg_enable = NETIF_MSG_PROBE | > + NETIF_MSG_LINK | > + /* NETIF_MSG_TIMER | */ > + /* NETIF_MSG_IFDOWN | */ > + /* NETIF_MSG_IFUP | */ > + NETIF_MSG_RX_ERR | > + NETIF_MSG_TX_ERR | > + /* NETIF_MSG_TX_QUEUED | */ > + /* NETIF_MSG_INTR | */ > + /* NETIF_MSG_TX_DONE | */ > + /* NETIF_MSG_RX_STATUS | */ > + /* NETIF_MSG_PKTDATA | */ > + /* NETIF_MSG_HW | */ > + /* NETIF_MSG_WOL | */ > + 0; > + > +static char *no_regs_list = "80018001,e1918001,8001a001,fc0d0000"; > +unsigned long ax88796c_no_regs_mask[AX88796C_REGDUMP_LEN / (sizeof(unsigned long) * 8)]; > + > +module_param(comp, int, 0444); > +MODULE_PARM_DESC(comp, "0=Non-Compression Mode, 1=Compression Mode"); > + > +module_param(msg_enable, int, 0444); > +MODULE_PARM_DESC(msg_enable, "Message mask (see linux/netdevice.h for bitmap)"); > + > +static int ax88796c_soft_reset(struct ax88796c_device *ax_local) > +{ > + u16 temp; > + int ret; > + > + AX_WRITE(&ax_local->ax_spi, PSR_RESET, P0_PSR); > + AX_WRITE(&ax_local->ax_spi, PSR_RESET_CLR, P0_PSR); > + > + ret = read_poll_timeout(AX_READ, ret, > + (ret & PSR_DEV_READY), > + 0, jiffies_to_usecs(160 * HZ / 1000), false, > + &ax_local->ax_spi, P0_PSR); > + if (ret) > + return -1; > + > + temp = AX_READ(&ax_local->ax_spi, P4_SPICR); > + if (ax_local->capabilities & AX_CAP_COMP) { > + AX_WRITE(&ax_local->ax_spi, > + (temp | SPICR_RCEN | SPICR_QCEN), P4_SPICR); > + ax_local->ax_spi.comp = 1; > + } else { > + AX_WRITE(&ax_local->ax_spi, > + (temp & ~(SPICR_RCEN | SPICR_QCEN)), P4_SPICR); > + ax_local->ax_spi.comp = 0; > + } > + > + return 0; > +} > + > +static int ax88796c_reload_eeprom(struct ax88796c_device *ax_local) > +{ > + int ret; > + > + AX_WRITE(&ax_local->ax_spi, EECR_RELOAD, P3_EECR); > + > + ret = read_poll_timeout(AX_READ, ret, > + (ret & PSR_DEV_READY), > + 0, jiffies_to_usecs(2 * HZ / 1000), false, > + &ax_local->ax_spi, P0_PSR); > + if (ret) { > + dev_err(&ax_local->spi->dev, > + "timeout waiting for reload eeprom\n"); > + return -1; > + } > + > + return 0; > +} > + > +static void ax88796c_set_hw_multicast(struct net_device *ndev) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + u16 rx_ctl = RXCR_AB; > + int mc_count = netdev_mc_count(ndev); > + > + memset(ax_local->multi_filter, 0, AX_MCAST_FILTER_SIZE); > + > + if (ndev->flags & IFF_PROMISC) { > + rx_ctl |= RXCR_PRO; > + > + } else if (ndev->flags & IFF_ALLMULTI || mc_count > AX_MAX_MCAST) { > + rx_ctl |= RXCR_AMALL; > + > + } else if (mc_count == 0) { > + /* just broadcast and directed */ > + } else { > + u32 crc_bits; > + int i; > + struct netdev_hw_addr *ha; > + > + netdev_for_each_mc_addr(ha, ndev) { > + crc_bits = ether_crc(ETH_ALEN, ha->addr); > + ax_local->multi_filter[crc_bits >> 29] |= > + (1 << ((crc_bits >> 26) & 7)); > + } > + > + for (i = 0; i < 4; i++) { > + AX_WRITE(&ax_local->ax_spi, > + ((ax_local->multi_filter[i * 2 + 1] << 8) | > + ax_local->multi_filter[i * 2]), P3_MFAR(i)); > + } > + } > + > + AX_WRITE(&ax_local->ax_spi, rx_ctl, P2_RXCR); > +} > + > +static void ax88796c_set_mac_addr(struct net_device *ndev) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + > + AX_WRITE(&ax_local->ax_spi, ((u16)(ndev->dev_addr[4] << 8) | > + (u16)ndev->dev_addr[5]), P3_MACASR0); > + AX_WRITE(&ax_local->ax_spi, ((u16)(ndev->dev_addr[2] << 8) | > + (u16)ndev->dev_addr[3]), P3_MACASR1); > + AX_WRITE(&ax_local->ax_spi, ((u16)(ndev->dev_addr[0] << 8) | > + (u16)ndev->dev_addr[1]), P3_MACASR2); > +} > + > +static int ax88796c_set_mac_address(struct net_device *ndev, void *p) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + struct sockaddr *addr = p; > + > + if (!is_valid_ether_addr(addr->sa_data)) > + return -EADDRNOTAVAIL; > + > + memcpy(ndev->dev_addr, addr->sa_data, ndev->addr_len); > + > + mutex_lock(&ax_local->spi_lock); > + > + ax88796c_set_mac_addr(ndev); > + > + mutex_unlock(&ax_local->spi_lock); > + > + return 0; > +} > + > +static void ax88796c_load_mac_addr(struct net_device *ndev) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + u16 temp; > + > + /* Try the device tree first */ > + if (!eth_platform_get_mac_address(&ax_local->spi->dev, ndev->dev_addr) && > + is_valid_ether_addr(ndev->dev_addr)) { > + if (netif_msg_probe(ax_local)) > + dev_info(&ax_local->spi->dev, > + "MAC address read from device tree\n"); > + return; > + } > + > + /* Read the MAC address from AX88796C */ > + temp = AX_READ(&ax_local->ax_spi, P3_MACASR0); > + ndev->dev_addr[5] = (u8)temp; > + ndev->dev_addr[4] = (u8)(temp >> 8); > + > + temp = AX_READ(&ax_local->ax_spi, P3_MACASR1); > + ndev->dev_addr[3] = (u8)temp; > + ndev->dev_addr[2] = (u8)(temp >> 8); > + > + temp = AX_READ(&ax_local->ax_spi, P3_MACASR2); > + ndev->dev_addr[1] = (u8)temp; > + ndev->dev_addr[0] = (u8)(temp >> 8); > + > + if (is_valid_ether_addr(ndev->dev_addr)) { > + if (netif_msg_probe(ax_local)) > + dev_info(&ax_local->spi->dev, > + "MAC address read from ASIX chip\n"); > + return; > + } > + > + /* Use random address if none found */ > + if (netif_msg_probe(ax_local)) > + dev_info(&ax_local->spi->dev, "Use random MAC address\n"); > + eth_hw_addr_random(ndev); > +} > + > +static void ax88796c_proc_tx_hdr(struct tx_pkt_info *info, u8 ip_summed) > +{ > + u16 pkt_len_bar = (~info->pkt_len & TX_HDR_SOP_PKTLENBAR); > + > + /* Prepare SOP header */ > + info->sop.flags_len = info->pkt_len | > + ((ip_summed == CHECKSUM_NONE) || > + (ip_summed == CHECKSUM_UNNECESSARY) ? TX_HDR_SOP_DICF : 0); > + > + info->sop.seq_lenbar = ((info->seq_num << 11) & TX_HDR_SOP_SEQNUM) > + | pkt_len_bar; > + cpu_to_be16s(&info->sop.flags_len); > + cpu_to_be16s(&info->sop.seq_lenbar); > + > + /* Prepare Segment header */ > + info->seg.flags_seqnum_seglen = TX_HDR_SEG_FS | TX_HDR_SEG_LS > + | info->pkt_len; > + > + info->seg.eo_so_seglenbar = pkt_len_bar; > + > + cpu_to_be16s(&info->seg.flags_seqnum_seglen); > + cpu_to_be16s(&info->seg.eo_so_seglenbar); > + > + /* Prepare EOP header */ > + info->eop.seq_len = ((info->seq_num << 11) & > + TX_HDR_EOP_SEQNUM) | info->pkt_len; > + info->eop.seqbar_lenbar = ((~info->seq_num << 11) & > + TX_HDR_EOP_SEQNUMBAR) | pkt_len_bar; > + > + cpu_to_be16s(&info->eop.seq_len); > + cpu_to_be16s(&info->eop.seqbar_lenbar); > +} > + > +static int > +ax88796c_check_free_pages(struct ax88796c_device *ax_local, u8 need_pages) > +{ > + u8 free_pages; > + u16 tmp; > + > + free_pages = AX_READ(&ax_local->ax_spi, P0_TFBFCR) & TX_FREEBUF_MASK; > + if (free_pages < need_pages) { > + /* schedule free page interrupt */ > + tmp = AX_READ(&ax_local->ax_spi, P0_TFBFCR) > + & TFBFCR_SCHE_FREE_PAGE; > + AX_WRITE(&ax_local->ax_spi, tmp | TFBFCR_TX_PAGE_SET | > + TFBFCR_SET_FREE_PAGE(need_pages), > + P0_TFBFCR); > + return -ENOMEM; > + } > + > + return 0; > +} > + > +static struct sk_buff * > +ax88796c_tx_fixup(struct net_device *ndev, struct sk_buff_head *q) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + struct sk_buff *skb, *tx_skb; > + struct tx_pkt_info *info; > + struct skb_data *entry; > + int headroom; > + int tailroom; > + u8 need_pages; > + u16 tol_len, pkt_len; > + u8 padlen, seq_num; > + u8 spi_len = ax_local->ax_spi.comp ? 1 : 4; > + > + if (skb_queue_empty(q)) > + return NULL; > + > + skb = skb_peek(q); > + pkt_len = skb->len; > + need_pages = (pkt_len + TX_OVERHEAD + 127) >> 7; > + if (ax88796c_check_free_pages(ax_local, need_pages) != 0) > + return NULL; > + > + headroom = skb_headroom(skb); > + tailroom = skb_tailroom(skb); > + padlen = ((pkt_len + 3) & 0x7FC) - pkt_len; > + tol_len = ((pkt_len + 3) & 0x7FC) + > + TX_OVERHEAD + TX_EOP_SIZE + spi_len; > + seq_num = ++ax_local->seq_num & 0x1F; > + > + info = (struct tx_pkt_info *)skb->cb; > + info->pkt_len = pkt_len; > + > + if ((!skb_cloned(skb)) && > + (headroom >= (TX_OVERHEAD + spi_len)) && > + (tailroom >= (padlen + TX_EOP_SIZE))) { > + info->seq_num = seq_num; > + ax88796c_proc_tx_hdr(info, skb->ip_summed); > + > + /* SOP and SEG header */ > + memcpy(skb_push(skb, TX_OVERHEAD), &info->sop, TX_OVERHEAD); > + > + /* Write SPI TXQ header */ > + memcpy(skb_push(skb, spi_len), tx_cmd_buf, spi_len); > + > + /* Make 32-bit alignment */ > + skb_put(skb, padlen); > + > + /* EOP header */ > + memcpy(skb_put(skb, TX_EOP_SIZE), &info->eop, TX_EOP_SIZE); > + > + tx_skb = skb; > + skb_unlink(skb, q); > + } else { > + tx_skb = alloc_skb(tol_len, GFP_KERNEL); Are you sure GFP_KERNEL is appropriate here and you don't have to use GFP_ATOMIC? > + if (!tx_skb) > + return NULL; > + > + /* Write SPI TXQ header */ > + memcpy(skb_put(tx_skb, spi_len), tx_cmd_buf, spi_len); > + > + info->seq_num = seq_num; > + ax88796c_proc_tx_hdr(info, skb->ip_summed); > + > + /* SOP and SEG header */ > + memcpy(skb_put(tx_skb, TX_OVERHEAD), > + &info->sop, TX_OVERHEAD); > + > + /* Packet */ > + memcpy(skb_put(tx_skb, ((pkt_len + 3) & 0xFFFC)), > + skb->data, pkt_len); > + > + /* EOP header */ > + memcpy(skb_put(tx_skb, TX_EOP_SIZE), > + &info->eop, TX_EOP_SIZE); > + > + skb_unlink(skb, q); > + dev_kfree_skb(skb); > + } > + > + entry = (struct skb_data *)tx_skb->cb; > + memset(entry, 0, sizeof(*entry)); > + entry->len = pkt_len; > + > + if (netif_msg_pktdata(ax_local)) { > + char pfx[IFNAMSIZ + 7]; > + > + snprintf(pfx, sizeof(pfx), "%s: ", ndev->name); > + > + netdev_info(ndev, "TX packet len %d, total len %d, seq %d\n", > + pkt_len, tx_skb->len, seq_num); > + > + netdev_info(ndev, " SPI Header:\n"); > + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, > + tx_skb->data, 4, 0); > + > + netdev_info(ndev, " TX SOP:\n"); > + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, > + tx_skb->data + 4, TX_OVERHEAD, 0); > + > + netdev_info(ndev, " TX packet:\n"); > + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, > + tx_skb->data + 4 + TX_OVERHEAD, > + tx_skb->len - TX_EOP_SIZE - 4 - TX_OVERHEAD, 0); > + > + netdev_info(ndev, " TX EOP:\n"); > + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, > + tx_skb->data + tx_skb->len - 4, 4, 0); > + } > + > + return tx_skb; > +} > + > +static int ax88796c_hard_xmit(struct ax88796c_device *ax_local) > +{ > + struct sk_buff *tx_skb; > + struct skb_data *entry; > + > + tx_skb = ax88796c_tx_fixup(ax_local->ndev, &ax_local->tx_wait_q); > + > + if (!tx_skb) > + return 0; > + > + entry = (struct skb_data *)tx_skb->cb; > + > + AX_WRITE(&ax_local->ax_spi, > + (TSNR_TXB_START | TSNR_PKT_CNT(1)), P0_TSNR); > + > + axspi_write_txq(&ax_local->ax_spi, tx_skb->data, tx_skb->len); > + > + if (((AX_READ(&ax_local->ax_spi, P0_TSNR) & TXNR_TXB_IDLE) == 0) || > + ((ISR_TXERR & AX_READ(&ax_local->ax_spi, P0_ISR)) != 0)) { > + /* Ack tx error int */ > + AX_WRITE(&ax_local->ax_spi, ISR_TXERR, P0_ISR); > + > + ax_local->stats.tx_dropped++; > + > + netif_err(ax_local, tx_err, ax_local->ndev, > + "TX FIFO error, re-initialize the TX bridge\n"); > + > + /* Reinitial tx bridge */ > + AX_WRITE(&ax_local->ax_spi, TXNR_TXB_REINIT | > + AX_READ(&ax_local->ax_spi, P0_TSNR), P0_TSNR); > + ax_local->seq_num = 0; > + } else { > + ax_local->stats.tx_packets++; > + ax_local->stats.tx_bytes += entry->len; > + } > + > + entry->state = tx_done; > + dev_kfree_skb(tx_skb); > + > + return 1; > +} > + > +static int > +ax88796c_start_xmit(struct sk_buff *skb, struct net_device *ndev) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + > + skb_queue_tail(&ax_local->tx_wait_q, skb); > + if (skb_queue_len(&ax_local->tx_wait_q) > TX_QUEUE_HIGH_WATER) { > + netif_err(ax_local, tx_queued, ndev, > + "Too much TX packets in queue %d\n", > + skb_queue_len(&ax_local->tx_wait_q)); > + > + netif_stop_queue(ndev); > + } > + > + set_bit(EVENT_TX, &ax_local->flags); > + queue_work(ax_local->ax_work_queue, &ax_local->ax_work); > + > + return NETDEV_TX_OK; > +} > + > +static void > +ax88796c_skb_return(struct ax88796c_device *ax_local, struct sk_buff *skb, > + struct rx_header *rxhdr) > +{ > + struct net_device *ndev = ax_local->ndev; > + int status; > + > + do { > + if (!(ndev->features & NETIF_F_RXCSUM)) > + break; > + > + /* checksum error bit is set */ > + if ((rxhdr->flags & RX_HDR3_L3_ERR) || > + (rxhdr->flags & RX_HDR3_L4_ERR)) > + break; > + > + /* Other types may be indicated by more than one bit. */ > + if ((rxhdr->flags & RX_HDR3_L4_TYPE_TCP) || > + (rxhdr->flags & RX_HDR3_L4_TYPE_UDP)) > + skb->ip_summed = CHECKSUM_UNNECESSARY; > + } while (0); Why this do {} while(0) construct? If you want to separate this code, then put it into its own function. > + > + ax_local->stats.rx_packets++; > + ax_local->stats.rx_bytes += skb->len; > + skb->dev = ndev; > + > + skb->truesize = skb->len + sizeof(struct sk_buff); > + skb->protocol = eth_type_trans(skb, ax_local->ndev); > + > + netif_info(ax_local, rx_status, ndev, "< rx, len %zu, type 0x%x\n", > + skb->len + sizeof(struct ethhdr), skb->protocol); > + > + status = netif_rx(skb); > + if (status != NET_RX_SUCCESS) > + netif_info(ax_local, rx_err, ndev, > + "netif_rx status %d\n", status); > +} > + > +static void > +ax88796c_rx_fixup(struct ax88796c_device *ax_local, struct sk_buff *rx_skb) > +{ > + struct rx_header *rxhdr = (struct rx_header *)rx_skb->data; > + struct net_device *ndev = ax_local->ndev; > + u16 len; > + > + be16_to_cpus(&rxhdr->flags_len); > + be16_to_cpus(&rxhdr->seq_lenbar); > + be16_to_cpus(&rxhdr->flags); > + > + if ((((short)rxhdr->flags_len) & RX_HDR1_PKT_LEN) != > + (~((short)rxhdr->seq_lenbar) & 0x7FF)) { > + netif_err(ax_local, rx_err, ndev, "Header error\n"); > + > + ax_local->stats.rx_frame_errors++; > + kfree_skb(rx_skb); > + return; > + } > + > + if ((rxhdr->flags_len & RX_HDR1_MII_ERR) || > + (rxhdr->flags_len & RX_HDR1_CRC_ERR)) { > + netif_err(ax_local, rx_err, ndev, "CRC or MII error\n"); > + > + ax_local->stats.rx_crc_errors++; > + kfree_skb(rx_skb); > + return; > + } > + > + len = rxhdr->flags_len & RX_HDR1_PKT_LEN; > + if (netif_msg_pktdata(ax_local)) { > + char pfx[IFNAMSIZ + 7]; > + > + snprintf(pfx, sizeof(pfx), "%s: ", ndev->name); > + netdev_info(ndev, "RX data, total len %d, packet len %d\n", > + rx_skb->len, len); > + > + netdev_info(ndev, " Dump RX packet header:"); > + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, > + rx_skb->data, sizeof(*rxhdr), 0); > + > + netdev_info(ndev, " Dump RX packet:"); > + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, > + rx_skb->data + sizeof(*rxhdr), len, 0); > + } > + > + skb_pull(rx_skb, sizeof(*rxhdr)); > + __pskb_trim(rx_skb, len); > + > + return ax88796c_skb_return(ax_local, rx_skb, rxhdr); > +} > + > +static int ax88796c_receive(struct net_device *ndev) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + struct sk_buff *skb; > + struct skb_data *entry; > + u16 w_count, pkt_len; > + u8 pkt_cnt; > + > + /* check rx packet and total word count */ > + AX_WRITE(&ax_local->ax_spi, AX_READ(&ax_local->ax_spi, P0_RTWCR) > + | RTWCR_RX_LATCH, P0_RTWCR); > + > + pkt_cnt = AX_READ(&ax_local->ax_spi, P0_RXBCR2) & RXBCR2_PKT_MASK; > + if (!pkt_cnt) > + return 0; > + > + pkt_len = AX_READ(&ax_local->ax_spi, P0_RCPHR) & 0x7FF; > + > + w_count = ((pkt_len + 6 + 3) & 0xFFFC) >> 1; > + > + skb = alloc_skb((w_count * 2), GFP_KERNEL); Also here, sure you don't have to use GFP_ATOMIC? > + if (!skb) { > + AX_WRITE(&ax_local->ax_spi, RXBCR1_RXB_DISCARD, P0_RXBCR1); > + return 0; > + } > + entry = (struct skb_data *)skb->cb; > + > + AX_WRITE(&ax_local->ax_spi, RXBCR1_RXB_START | w_count, P0_RXBCR1); > + > + axspi_read_rxq(&ax_local->ax_spi, > + skb_put(skb, w_count * 2), skb->len); > + > + /* Check if rx bridge is idle */ > + if ((AX_READ(&ax_local->ax_spi, P0_RXBCR2) & RXBCR2_RXB_IDLE) == 0) { > + netif_err(ax_local, rx_err, ndev, > + "Rx Bridge is not idle\n"); > + AX_WRITE(&ax_local->ax_spi, RXBCR2_RXB_REINIT, P0_RXBCR2); > + > + entry->state = rx_err; > + } else { > + entry->state = rx_done; > + } > + > + AX_WRITE(&ax_local->ax_spi, ISR_RXPKT, P0_ISR); > + > + ax88796c_rx_fixup(ax_local, skb); > + > + return 1; > +} > + > +static int ax88796c_process_isr(struct ax88796c_device *ax_local) > +{ > + u16 isr; > + u8 done = 0; > + struct net_device *ndev = ax_local->ndev; > + > + isr = AX_READ(&ax_local->ax_spi, P0_ISR); > + AX_WRITE(&ax_local->ax_spi, isr, P0_ISR); > + > + netif_dbg(ax_local, intr, ndev, " ISR 0x%04x\n", isr); > + > + if (isr & ISR_TXERR) { > + netif_dbg(ax_local, intr, ndev, " TXERR interrupt\n"); > + AX_WRITE(&ax_local->ax_spi, TXNR_TXB_REINIT, P0_TSNR); > + ax_local->seq_num = 0x1f; > + } > + > + if (isr & ISR_TXPAGES) { > + netif_dbg(ax_local, intr, ndev, " TXPAGES interrupt\n"); > + set_bit(EVENT_TX, &ax_local->flags); > + } > + > + if (isr & ISR_LINK) { > + netif_dbg(ax_local, intr, ndev, " Link change interrupt\n"); > + phy_mac_interrupt(ax_local->ndev->phydev); > + } > + > + if (isr & ISR_RXPKT) { > + netif_dbg(ax_local, intr, ndev, " RX interrupt\n"); > + done = ax88796c_receive(ax_local->ndev); > + } > + > + return done; > +} > + > +static irqreturn_t ax88796c_interrupt(int irq, void *dev_instance) > +{ > + struct net_device *ndev = dev_instance; > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + > + if (!ndev) { > + pr_err("irq %d for unknown device.\n", irq); > + return IRQ_RETVAL(0); > + } > + > + disable_irq_nosync(irq); > + > + netif_dbg(ax_local, intr, ndev, "Interrupt occurred\n"); > + > + set_bit(EVENT_INTR, &ax_local->flags); > + queue_work(ax_local->ax_work_queue, &ax_local->ax_work); > + Why not simpy using a threaded interrupt? And in general: Why don't you use NAPI in the driver? > + return IRQ_HANDLED; > +} > + > +static void ax88796c_work(struct work_struct *work) > +{ > + struct ax88796c_device *ax_local = > + container_of(work, struct ax88796c_device, ax_work); > + > + mutex_lock(&ax_local->spi_lock); > + > + if (test_bit(EVENT_SET_MULTI, &ax_local->flags)) { > + ax88796c_set_hw_multicast(ax_local->ndev); > + clear_bit(EVENT_SET_MULTI, &ax_local->flags); > + } > + > + if (test_bit(EVENT_INTR, &ax_local->flags)) { > + AX_WRITE(&ax_local->ax_spi, IMR_MASKALL, P0_IMR); > + > + while (1) { > + if (!ax88796c_process_isr(ax_local)) > + break; > + } > + > + clear_bit(EVENT_INTR, &ax_local->flags); > + > + AX_WRITE(&ax_local->ax_spi, IMR_DEFAULT, P0_IMR); > + > + enable_irq(ax_local->ndev->irq); > + } > + > + if (test_bit(EVENT_TX, &ax_local->flags)) { > + while (skb_queue_len(&ax_local->tx_wait_q)) { > + if (!ax88796c_hard_xmit(ax_local)) > + break; > + } > + > + clear_bit(EVENT_TX, &ax_local->flags); > + > + if (netif_queue_stopped(ax_local->ndev) && > + (skb_queue_len(&ax_local->tx_wait_q) < TX_QUEUE_LOW_WATER)) > + netif_wake_queue(ax_local->ndev); > + } > + > + mutex_unlock(&ax_local->spi_lock); > +} > + > +static struct net_device_stats *ax88796c_get_stats(struct net_device *ndev) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + > + return &ax_local->stats; > +} > + > +static void ax88796c_handle_link_change(struct net_device *ndev) > +{ > + if (net_ratelimit()) > + phy_print_status(ndev->phydev); > +} > + > +void ax88796c_phy_init(struct ax88796c_device *ax_local) > +{ > + /* Enable PHY auto-polling */ > + AX_WRITE(&ax_local->ax_spi, > + PCR_PHYID(0x10) | PCR_POLL_EN | > + PCR_POLL_FLOWCTRL | PCR_POLL_BMCR, P2_PCR); > +} > + > +static int > +ax88796c_open(struct net_device *ndev) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + int ret; > + unsigned long irq_flag = IRQF_SHARED; > + > + mutex_lock(&ax_local->spi_lock); > + > + ret = ax88796c_soft_reset(ax_local); > + if (ret < 0) > + return -ENODEV; > + > + ret = request_irq(ndev->irq, ax88796c_interrupt, > + irq_flag, ndev->name, ndev); > + if (ret) { > + netdev_err(ndev, "unable to get IRQ %d (errno=%d).\n", > + ndev->irq, ret); > + return -ENXIO; > + } > + > + ax_local->seq_num = 0x1f; > + > + ax88796c_set_mac_addr(ndev); > + ax88796c_set_csums(ax_local); > + > + /* Disable stuffing packet */ > + AX_WRITE(&ax_local->ax_spi, > + AX_READ(&ax_local->ax_spi, P1_RXBSPCR) > + & ~RXBSPCR_STUF_ENABLE, P1_RXBSPCR); > + > + /* Enable RX packet process */ > + AX_WRITE(&ax_local->ax_spi, RPPER_RXEN, P1_RPPER); > + > + AX_WRITE(&ax_local->ax_spi, AX_READ(&ax_local->ax_spi, P0_FER) > + | FER_RXEN | FER_TXEN | FER_BSWAP | FER_IRQ_PULL, P0_FER); > + > + /* Setup LED mode */ > + AX_WRITE(&ax_local->ax_spi, > + (LCR_LED0_EN | LCR_LED0_DUPLEX | LCR_LED1_EN | > + LCR_LED1_100MODE), P2_LCR0); > + AX_WRITE(&ax_local->ax_spi, > + (AX_READ(&ax_local->ax_spi, P2_LCR1) & LCR_LED2_MASK) | > + LCR_LED2_EN | LCR_LED2_LINK, P2_LCR1); > + > + ax88796c_phy_init(ax_local); > + > + phy_start(ax_local->ndev->phydev); > + > + netif_start_queue(ndev); > + > + AX_WRITE(&ax_local->ax_spi, IMR_DEFAULT, P0_IMR); > + > + spi_message_init(&ax_local->ax_spi.rx_msg); > + > + mutex_unlock(&ax_local->spi_lock); > + > + return 0; > +} > + > +static void ax88796c_free_skb_queue(struct sk_buff_head *q) > +{ > + struct sk_buff *skb; > + > + while (q->qlen) { > + skb = skb_dequeue(q); > + kfree_skb(skb); > + } > +} > + > +static int > +ax88796c_close(struct net_device *ndev) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + > + netif_stop_queue(ndev); > + > + free_irq(ndev->irq, ndev); This looks racy. I think e.g. you can still get e.g. rx interrupts. > + > + phy_stop(ndev->phydev); > + > + mutex_lock(&ax_local->spi_lock); > + > + AX_WRITE(&ax_local->ax_spi, IMR_MASKALL, P0_IMR); > + ax88796c_free_skb_queue(&ax_local->tx_wait_q); > + > + ax88796c_soft_reset(ax_local); > + > + mutex_unlock(&ax_local->spi_lock); > + netif_carrier_off(ndev); > + > + return 0; > +} > + > +static int > +ax88796c_set_features(struct net_device *ndev, netdev_features_t features) > +{ > + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > + netdev_features_t changed = features ^ ndev->features; > + > + if (!(changed & (NETIF_F_RXCSUM | NETIF_F_HW_CSUM))) > + return 0; > + > + ndev->features = features; > + > + if (changed & (NETIF_F_RXCSUM | NETIF_F_HW_CSUM)) > + ax88796c_set_csums(ax_local); > + > + return 0; > +} > + > +static const struct net_device_ops ax88796c_netdev_ops = { > + .ndo_open = ax88796c_open, > + .ndo_stop = ax88796c_close, > + .ndo_start_xmit = ax88796c_start_xmit, > + .ndo_get_stats = ax88796c_get_stats, > + .ndo_do_ioctl = ax88796c_ioctl, > + .ndo_set_mac_address = ax88796c_set_mac_address, > + .ndo_set_features = ax88796c_set_features, > +}; > + > +static int ax88796c_hard_reset(struct ax88796c_device *ax_local) > +{ > + struct device *dev = (struct device *)&ax_local->spi->dev; > + struct gpio_desc *reset_gpio; > + > + /* reset info */ > + reset_gpio = gpiod_get(dev, "reset", 0); > + if (IS_ERR(reset_gpio)) { > + dev_err(dev, "Could not get 'reset' GPIO: %ld", PTR_ERR(reset_gpio)); > + return PTR_ERR(reset_gpio); > + } > + > + /* set reset */ > + gpiod_direction_output(reset_gpio, 1); > + msleep(100); > + gpiod_direction_output(reset_gpio, 0); > + gpiod_put(reset_gpio); > + msleep(20); > + > + return 0; > +} > + > +static int ax88796c_probe(struct spi_device *spi) > +{ > + struct net_device *ndev; > + struct ax88796c_device *ax_local; > + int ret; > + u16 temp; > + > + ndev = devm_alloc_etherdev(&spi->dev, sizeof(*ax_local)); > + if (!ndev) { > + dev_err(&spi->dev, "AX88796C SPI: Could not allocate ethernet device\n"); > + return -ENOMEM; > + } > + SET_NETDEV_DEV(ndev, &spi->dev); > + > + ax_local = to_ax88796c_device(ndev); > + memset(ax_local, 0, sizeof(*ax_local)); > + > + dev_set_drvdata(&spi->dev, ax_local); > + ax_local->spi = spi; > + ax_local->ax_spi.spi = spi; > + > + ax_local->ndev = ndev; > + ax_local->capabilities |= comp ? AX_CAP_COMP : 0; > + ax_local->msg_enable = msg_enable; > + > + ax_local->mdiobus = devm_mdiobus_alloc(&spi->dev); > + if (!ax_local->mdiobus) { > + dev_err(&spi->dev, "Could not allocate MDIO bus\n"); > + return -ENOMEM; > + } > + > + ax_local->mdiobus->priv = ax_local; > + ax_local->mdiobus->read = ax88796c_mdio_read; > + ax_local->mdiobus->write = ax88796c_mdio_write; > + ax_local->mdiobus->name = "ax88976c-mdiobus"; > + ax_local->mdiobus->phy_mask = ~(1 << 0x10); > + ax_local->mdiobus->parent = &spi->dev; > + > + snprintf(ax_local->mdiobus->id, ARRAY_SIZE(ax_local->mdiobus->id), > + "ax88796c-spi-%s.%u", dev_name(&spi->dev), spi->chip_select); > + > + ret = devm_mdiobus_register(&spi->dev, ax_local->mdiobus); > + if (ret < 0) { > + dev_err(&spi->dev, "Could not register MDIO bus\n"); > + return ret; > + } > + > + if (netif_msg_probe(ax_local)) { > + dev_info(&spi->dev, "AX88796C-SPI Configuration:\n"); > + dev_info(&spi->dev, " Compression : %s\n", > + ax_local->capabilities & AX_CAP_COMP ? "ON" : "OFF"); > + } > + > + ndev->irq = spi->irq; > + ndev->netdev_ops = &ax88796c_netdev_ops; > + ndev->ethtool_ops = &ax88796c_ethtool_ops; > + ndev->hw_features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM; > + ndev->features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM; > + ndev->hard_header_len += (TX_OVERHEAD + 4); > + > + /* ax88796c gpio reset */ > + ax88796c_hard_reset(ax_local); > + > + /* Reset AX88796C */ > + ret = ax88796c_soft_reset(ax_local); > + if (ret < 0) { > + ret = -ENODEV; > + goto err; > + } > + /* Check board revision */ > + temp = AX_READ(&ax_local->ax_spi, P2_CRIR); > + if ((temp & 0xF) != 0x0) { > + dev_err(&spi->dev, "spi read failed: %d\n", temp); > + ret = -ENODEV; > + goto err; > + } > + > + temp = AX_READ(&ax_local->ax_spi, P0_BOR); > + if (temp == 0x1234) { > + ax_local->plat_endian = PLAT_LITTLE_ENDIAN; > + } else { > + AX_WRITE(&ax_local->ax_spi, 0xFFFF, P0_BOR); > + ax_local->plat_endian = PLAT_BIG_ENDIAN; > + } > + > + /*Reload EEPROM*/ > + ax88796c_reload_eeprom(ax_local); > + > + ax88796c_load_mac_addr(ndev); > + > + if (netif_msg_probe(ax_local)) > + dev_info(&spi->dev, > + "irq %d, MAC addr %02X:%02X:%02X:%02X:%02X:%02X\n", > + ndev->irq, > + ndev->dev_addr[0], ndev->dev_addr[1], > + ndev->dev_addr[2], ndev->dev_addr[3], > + ndev->dev_addr[4], ndev->dev_addr[5]); > + > + /* Disable power saving */ > + AX_WRITE(&ax_local->ax_spi, (AX_READ(&ax_local->ax_spi, P0_PSCR) > + & PSCR_PS_MASK) | PSCR_PS_D0, P0_PSCR); > + > + INIT_WORK(&ax_local->ax_work, ax88796c_work); > + > + ax_local->ax_work_queue = > + create_singlethread_workqueue("ax88796c_work"); > + > + mutex_init(&ax_local->spi_lock); > + > + skb_queue_head_init(&ax_local->tx_wait_q); > + > + ret = devm_register_netdev(&spi->dev, ndev); > + if (ret) { > + dev_err(&spi->dev, "failed to register a network device\n"); > + destroy_workqueue(ax_local->ax_work_queue); > + goto err; > + } > + > + ax_local->phydev = phy_find_first(ax_local->mdiobus); > + if (!ax_local->phydev) { > + dev_err(&spi->dev, "no PHY found\n"); > + ret = -ENODEV; > + goto err; > + } > + > + ax_local->phydev->irq = PHY_IGNORE_INTERRUPT; > + phy_connect_direct(ax_local->ndev, ax_local->phydev, > + ax88796c_handle_link_change, > + PHY_INTERFACE_MODE_MII); > + > + netif_info(ax_local, probe, ndev, "%s %s registered\n", > + dev_driver_string(&spi->dev), > + dev_name(&spi->dev)); > + phy_attached_info(ax_local->phydev); Does the integrated PHY provide a proper PHY ID? Is there a PHY driver for it or do you rely on the genphy driver? > + > + ret = 0; > +err: > + return ret; > +} > + > +static int ax88796c_remove(struct spi_device *spi) > +{ > + struct ax88796c_device *ax_local = dev_get_drvdata(&spi->dev); > + struct net_device *ndev = ax_local->ndev; > + > + netif_info(ax_local, probe, ndev, "removing network device %s %s\n", > + dev_driver_string(&spi->dev), > + dev_name(&spi->dev)); > + > + destroy_workqueue(ax_local->ax_work_queue); > + > + return 0; > +} > + > +static const struct of_device_id ax88796c_dt_ids[] = { > + { .compatible = "asix,ax88796c" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, ax88796c_dt_ids); > + > +static const struct spi_device_id asix_id[] = { > + { "ax88796c", 0 }, > + { } > +}; > +MODULE_DEVICE_TABLE(spi, asix_id); > + > +static struct spi_driver ax88796c_spi_driver = { > + .driver = { > + .name = DRV_NAME, > +#ifdef CONFIG_USE_OF > + .of_match_table = of_match_ptr(ax88796c_dt_ids), > +#endif > + }, > + .probe = ax88796c_probe, > + .remove = ax88796c_remove, > + .id_table = asix_id, > +}; > + > +static __init int ax88796c_spi_init(void) > +{ > + int ret; > + > + bitmap_zero(ax88796c_no_regs_mask, AX88796C_REGDUMP_LEN); > + ret = bitmap_parse(no_regs_list, 35, > + ax88796c_no_regs_mask, AX88796C_REGDUMP_LEN); > + if (ret) { > + bitmap_fill(ax88796c_no_regs_mask, AX88796C_REGDUMP_LEN); > + pr_err("Invalid bitmap description, masking all registers\n"); > + } > + > + return spi_register_driver(&ax88796c_spi_driver); > +} > + > +static __exit void ax88796c_spi_exit(void) > +{ > + spi_unregister_driver(&ax88796c_spi_driver); > +} > + > +module_init(ax88796c_spi_init); > +module_exit(ax88796c_spi_exit); > + > +MODULE_AUTHOR("ASIX"); > +MODULE_DESCRIPTION("ASIX AX88796C SPI Ethernet driver"); > +MODULE_LICENSE("GPL"); > + > +/* ax88796c_phy */ > diff --git a/drivers/net/ethernet/asix/ax88796c_main.h b/drivers/net/ethernet/asix/ax88796c_main.h > new file mode 100644 > index 000000000000..428833978fbf > --- /dev/null > +++ b/drivers/net/ethernet/asix/ax88796c_main.h > @@ -0,0 +1,568 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2010 ASIX Electronics Corporation > + * Copyright (c) 2020 Samsung Electronics > + * > + * ASIX AX88796C SPI Fast Ethernet Linux driver > + */ > + > +#ifndef _AX88796C_MAIN_H > +#define _AX88796C_MAIN_H > + > +#include <linux/netdevice.h> > +#include <linux/mii.h> > + > +#include "ax88796c_spi.h" > + > +/* These identify the driver base version and may not be removed. */ > +#define DRV_NAME "ax88796c" > +#define ADP_NAME "ASIX AX88796C SPI Ethernet Adapter" > +#define DRV_VERSION "1.2.0" > + An benefit in using a separate driver version? Typically the kernel version is sufficient. > +#define TX_QUEUE_HIGH_WATER 45 /* Tx queue high water mark */ > +#define TX_QUEUE_LOW_WATER 20 /* Tx queue low water mark */ > + > +#define AX88796C_REGDUMP_LEN 256 > +#define AX88796C_PHY_REGDUMP_LEN 12 > + > +#define TX_OVERHEAD 8 > +#define TX_EOP_SIZE 4 > + > +#define AX_MCAST_FILTER_SIZE 8 > +#define AX_MAX_MCAST 64 > +#define AX_MAX_CLK 80000000 > +#define TX_HDR_SOP_DICF 0x8000 > +#define TX_HDR_SOP_CPHI 0x4000 > +#define TX_HDR_SOP_INT 0x2000 > +#define TX_HDR_SOP_MDEQ 0x1000 > +#define TX_HDR_SOP_PKTLEN 0x07FF > +#define TX_HDR_SOP_SEQNUM 0xF800 > +#define TX_HDR_SOP_PKTLENBAR 0x07FF > + > +#define TX_HDR_SEG_FS 0x8000 > +#define TX_HDR_SEG_LS 0x4000 > +#define TX_HDR_SEG_SEGNUM 0x3800 > +#define TX_HDR_SEG_SEGLEN 0x0700 > +#define TX_HDR_SEG_EOFST 0xC000 > +#define TX_HDR_SEG_SOFST 0x3800 > +#define TX_HDR_SEG_SEGLENBAR 0x07FF > + > +#define TX_HDR_EOP_SEQNUM 0xF800 > +#define TX_HDR_EOP_PKTLEN 0x07FF > +#define TX_HDR_EOP_SEQNUMBAR 0xF800 > +#define TX_HDR_EOP_PKTLENBAR 0x07FF > + > +/* Rx header fields mask */ > +#define RX_HDR1_MCBC 0x8000 > +#define RX_HDR1_STUFF_PKT 0x4000 > +#define RX_HDR1_MII_ERR 0x2000 > +#define RX_HDR1_CRC_ERR 0x1000 > +#define RX_HDR1_PKT_LEN 0x07FF > + > +#define RX_HDR2_SEQ_NUM 0xF800 > +#define RX_HDR2_PKT_LEN_BAR 0x7FFF > + > +#define RX_HDR3_PE 0x8000 > +#define RX_HDR3_L3_TYPE_IPV4V6 0x6000 > +#define RX_HDR3_L3_TYPE_IP 0x4000 > +#define RX_HDR3_L3_TYPE_IPV6 0x2000 > +#define RX_HDR3_L4_TYPE_ICMPV6 0x1400 > +#define RX_HDR3_L4_TYPE_TCP 0x1000 > +#define RX_HDR3_L4_TYPE_IGMP 0x0c00 > +#define RX_HDR3_L4_TYPE_ICMP 0x0800 > +#define RX_HDR3_L4_TYPE_UDP 0x0400 > +#define RX_HDR3_L3_ERR 0x0200 > +#define RX_HDR3_L4_ERR 0x0100 > +#define RX_HDR3_PRIORITY(x) ((x) << 4) > +#define RX_HDR3_STRIP 0x0008 > +#define RX_HDR3_VLAN_ID 0x0007 > + > +enum watchdog_state { > + chk_link = 0, > + chk_cable, > + ax_nop, > +}; > + > +struct ax88796c_device { > + struct resource *addr_res; /* resources found */ > + struct resource *addr_req; /* resources requested */ > + struct resource *irq_res; > + > + struct spi_device *spi; > + struct net_device *ndev; > + struct net_device_stats stats; > + > + struct timer_list watchdog; > + enum watchdog_state w_state; > + size_t w_ticks; > + > + struct work_struct ax_work; > + struct workqueue_struct *ax_work_queue; > + struct tasklet_struct bh; > + > + struct mutex spi_lock; /* device access */ > + > + struct sk_buff_head tx_wait_q; > + > + struct axspi_data ax_spi; > + > + struct mii_bus *mdiobus; > + struct phy_device *phydev; > + > + int msg_enable; > + > + u16 seq_num; > + > + u8 multi_filter[AX_MCAST_FILTER_SIZE]; > + > + unsigned long capabilities; > + #define AX_CAP_DMA 1 > + #define AX_CAP_COMP 2 > + #define AX_CAP_BIDIR 4 > + > + u8 plat_endian; > + #define PLAT_LITTLE_ENDIAN 0 > + #define PLAT_BIG_ENDIAN 1 > + > + unsigned long flags; > + #define EVENT_INTR 1 > + #define EVENT_TX 2 > + #define EVENT_SET_MULTI 4 > + > +}; > + > +#define to_ax88796c_device(ndev) ((struct ax88796c_device *)netdev_priv(ndev)) > + Is this helper really needed? in The places I've seen you can assign the void* pointer returned netdev_priv(). > +enum skb_state { > + illegal = 0, > + tx_done, > + rx_done, > + rx_err, > +}; > + > +struct skb_data; > + > +struct skb_data { > + enum skb_state state; > + struct net_device *ndev; > + struct sk_buff *skb; > + size_t len; > + dma_addr_t phy_addr; > +}; > + > +/* A88796C register definition */ > + /* Definition of PAGE0 */ > +#define P0_PSR (0x00) > + #define PSR_DEV_READY (1 << 7) > + #define PSR_RESET (0 << 15) > + #define PSR_RESET_CLR (1 << 15) > +#define P0_BOR (0x02) > +#define P0_FER (0x04) > + #define FER_IPALM (1 << 0) > + #define FER_DCRC (1 << 1) > + #define FER_RH3M (1 << 2) > + #define FER_HEADERSWAP (1 << 7) > + #define FER_WSWAP (1 << 8) > + #define FER_BSWAP (1 << 9) > + #define FER_INTHI (1 << 10) > + #define FER_INTLO (0 << 10) > + #define FER_IRQ_PULL (1 << 11) > + #define FER_RXEN (1 << 14) > + #define FER_TXEN (1 << 15) > +#define P0_ISR (0x06) > + #define ISR_RXPKT (1 << 0) > + #define ISR_MDQ (1 << 4) > + #define ISR_TXT (1 << 5) > + #define ISR_TXPAGES (1 << 6) > + #define ISR_TXERR (1 << 8) > + #define ISR_LINK (1 << 9) > +#define P0_IMR (0x08) > + #define IMR_RXPKT (1 << 0) > + #define IMR_MDQ (1 << 4) > + #define IMR_TXT (1 << 5) > + #define IMR_TXPAGES (1 << 6) > + #define IMR_TXERR (1 << 8) > + #define IMR_LINK (1 << 9) > + #define IMR_MASKALL (0xFFFF) > + #define IMR_DEFAULT (IMR_TXERR) > +#define P0_WFCR (0x0A) > + #define WFCR_PMEIND (1 << 0) /* PME indication */ > + #define WFCR_PMETYPE (1 << 1) /* PME I/O type */ > + #define WFCR_PMEPOL (1 << 2) /* PME polarity */ > + #define WFCR_PMERST (1 << 3) /* Reset PME */ > + #define WFCR_SLEEP (1 << 4) /* Enable sleep mode */ > + #define WFCR_WAKEUP (1 << 5) /* Enable wakeup mode */ > + #define WFCR_WAITEVENT (1 << 6) /* Reserved */ > + #define WFCR_CLRWAKE (1 << 7) /* Clear wakeup */ > + #define WFCR_LINKCH (1 << 8) /* Enable link change */ > + #define WFCR_MAGICP (1 << 9) /* Enable magic packet */ > + #define WFCR_WAKEF (1 << 10) /* Enable wakeup frame */ > + #define WFCR_PMEEN (1 << 11) /* Enable PME pin */ > + #define WFCR_LINKCHS (1 << 12) /* Link change status */ > + #define WFCR_MAGICPS (1 << 13) /* Magic packet status */ > + #define WFCR_WAKEFS (1 << 14) /* Wakeup frame status */ > + #define WFCR_PMES (1 << 15) /* PME pin status */ > +#define P0_PSCR (0x0C) > + #define PSCR_PS_MASK (0xFFF0) > + #define PSCR_PS_D0 (0) > + #define PSCR_PS_D1 (1 << 0) > + #define PSCR_PS_D2 (1 << 1) > + #define PSCR_FPS (1 << 3) /* Enable fiber mode PS */ > + #define PSCR_SWPS (1 << 4) /* Enable software */ > + /* PS control */ > + #define PSCR_WOLPS (1 << 5) /* Enable WOL PS */ > + #define PSCR_SWWOL (1 << 6) /* Enable software select */ > + /* WOL PS */ > + #define PSCR_PHYOSC (1 << 7) /* Internal PHY OSC control */ > + #define PSCR_FOFEF (1 << 8) /* Force PHY generate FEF */ > + #define PSCR_FOF (1 << 9) /* Force PHY in fiber mode */ > + #define PSCR_PHYPD (1 << 10) /* PHY power down. */ > + /* Active high */ > + #define PSCR_PHYRST (1 << 11) /* PHY reset signal. */ > + /* Active low */ > + #define PSCR_PHYCSIL (1 << 12) /* PHY cable energy detect */ > + #define PSCR_PHYCOFF (1 << 13) /* PHY cable off */ > + #define PSCR_PHYLINK (1 << 14) /* PHY link status */ > + #define PSCR_EEPOK (1 << 15) /* EEPROM load complete */ > +#define P0_MACCR (0x0E) > + #define MACCR_RXFC_ENABLE (1 << 3) > + #define MACCR_RXFC_MASK 0xFFF7 > + #define MACCR_TXFC_ENABLE (1 << 4) > + #define MACCR_TXFC_MASK 0xFFEF > + #define MACCR_PSI (1 << 6) /* Software Cable-Off */ > + /* Power Saving Interrupt */ > + #define MACCR_PF (1 << 7) > + #define MACCR_PMM_BITS 8 > + #define MACCR_PMM_MASK (0x1F00) > + #define MACCR_PMM_RESET (1 << 8) > + #define MACCR_PMM_WAIT (2 << 8) > + #define MACCR_PMM_READY (3 << 8) > + #define MACCR_PMM_D1 (4 << 8) > + #define MACCR_PMM_D2 (5 << 8) > + #define MACCR_PMM_WAKE (7 << 8) > + #define MACCR_PMM_D1_WAKE (8 << 8) > + #define MACCR_PMM_D2_WAKE (9 << 8) > + #define MACCR_PMM_SLEEP (10 << 8) > + #define MACCR_PMM_PHY_RESET (11 << 8) > + #define MACCR_PMM_SOFT_D1 (16 << 8) > + #define MACCR_PMM_SOFT_D2 (17 << 8) > +#define P0_TFBFCR (0x10) > + #define TFBFCR_SCHE_FREE_PAGE 0xE07F > + #define TFBFCR_FREE_PAGE_BITS 0x07 > + #define TFBFCR_FREE_PAGE_LATCH (1 << 6) > + #define TFBFCR_SET_FREE_PAGE(x) (((x) & 0x3F) << TFBFCR_FREE_PAGE_BITS) > + #define TFBFCR_TX_PAGE_SET (1 << 13) > + #define TFBFCR_MANU_ENTX (1 << 15) > + #define TX_FREEBUF_MASK 0x003F > + #define TX_DPTSTART 0x4000 > + > +#define P0_TSNR (0x12) > + #define TXNR_TXB_ERR (1 << 5) > + #define TXNR_TXB_IDLE (1 << 6) > + #define TSNR_PKT_CNT(x) (((x) & 0x3F) << 8) > + #define TXNR_TXB_REINIT (1 << 14) > + #define TSNR_TXB_START (1 << 15) > +#define P0_RTDPR (0x14) > +#define P0_RXBCR1 (0x16) > + #define RXBCR1_RXB_DISCARD (1 << 14) > + #define RXBCR1_RXB_START (1 << 15) > +#define P0_RXBCR2 (0x18) > + #define RXBCR2_PKT_MASK (0xFF) > + #define RXBCR2_RXPC_MASK (0x7F) > + #define RXBCR2_RXB_READY (1 << 13) > + #define RXBCR2_RXB_IDLE (1 << 14) > + #define RXBCR2_RXB_REINIT (1 << 15) > +#define P0_RTWCR (0x1A) > + #define RTWCR_RXWC_MASK (0x3FFF) > + #define RTWCR_RX_LATCH (1 << 15) > +#define P0_RCPHR (0x1C) > + > + /* Definition of PAGE1 */ > +#define P1_RPPER (0x22) > + #define RPPER_RXEN (1 << 0) > +#define P1_MRCR (0x28) > +#define P1_MDR (0x2A) > +#define P1_RMPR (0x2C) > +#define P1_TMPR (0x2E) > +#define P1_RXBSPCR (0x30) > + #define RXBSPCR_STUF_WORD_CNT(x) (((x) & 0x7000) >> 12) > + #define RXBSPCR_STUF_ENABLE (1 << 15) > +#define P1_MCR (0x32) > + #define MCR_SBP (1 << 8) > + #define MCR_SM (1 << 9) > + #define MCR_CRCENLAN (1 << 11) > + #define MCR_STP (1 << 12) > + /* Definition of PAGE2 */ > +#define P2_CIR (0x42) > +#define P2_PCR (0x44) > + #define PCR_POLL_EN (1 << 0) > + #define PCR_POLL_FLOWCTRL (1 << 1) > + #define PCR_POLL_BMCR (1 << 2) > + #define PCR_PHYID(x) ((x) << 8) > +#define P2_PHYSR (0x46) > +#define P2_MDIODR (0x48) > +#define P2_MDIOCR (0x4A) > + #define MDIOCR_RADDR(x) ((x) & 0x1F) > + #define MDIOCR_FADDR(x) (((x) & 0x1F) << 8) > + #define MDIOCR_VALID (1 << 13) > + #define MDIOCR_READ (1 << 14) > + #define MDIOCR_WRITE (1 << 15) > +#define P2_LCR0 (0x4C) > + #define LCR_LED0_EN (1 << 0) > + #define LCR_LED0_100MODE (1 << 1) > + #define LCR_LED0_DUPLEX (1 << 2) > + #define LCR_LED0_LINK (1 << 3) > + #define LCR_LED0_ACT (1 << 4) > + #define LCR_LED0_COL (1 << 5) > + #define LCR_LED0_10MODE (1 << 6) > + #define LCR_LED0_DUPCOL (1 << 7) > + #define LCR_LED1_EN (1 << 8) > + #define LCR_LED1_100MODE (1 << 9) > + #define LCR_LED1_DUPLEX (1 << 10) > + #define LCR_LED1_LINK (1 << 11) > + #define LCR_LED1_ACT (1 << 12) > + #define LCR_LED1_COL (1 << 13) > + #define LCR_LED1_10MODE (1 << 14) > + #define LCR_LED1_DUPCOL (1 << 15) > +#define P2_LCR1 (0x4E) > + #define LCR_LED2_MASK (0xFF00) > + #define LCR_LED2_EN (1 << 0) > + #define LCR_LED2_100MODE (1 << 1) > + #define LCR_LED2_DUPLEX (1 << 2) > + #define LCR_LED2_LINK (1 << 3) > + #define LCR_LED2_ACT (1 << 4) > + #define LCR_LED2_COL (1 << 5) > + #define LCR_LED2_10MODE (1 << 6) > + #define LCR_LED2_DUPCOL (1 << 7) > +#define P2_IPGCR (0x50) > +#define P2_CRIR (0x52) > +#define P2_FLHWCR (0x54) > +#define P2_RXCR (0x56) > + #define RXCR_PRO (1 << 0) > + #define RXCR_AMALL (1 << 1) > + #define RXCR_SEP (1 << 2) > + #define RXCR_AB (1 << 3) > + #define RXCR_AM (1 << 4) > + #define RXCR_AP (1 << 5) > + #define RXCR_ARP (1 << 6) > +#define P2_JLCR (0x58) > +#define P2_MPLR (0x5C) > + > + /* Definition of PAGE3 */ > +#define P3_MACASR0 (0x62) > + #define P3_MACASR(x) (P3_MACASR0 + 2 * (x)) > + #define MACASR_LOWBYTE_MASK 0x00FF > + #define MACASR_HIGH_BITS 0x08 > +#define P3_MACASR1 (0x64) > +#define P3_MACASR2 (0x66) > +#define P3_MFAR01 (0x68) > +#define P3_MFAR_BASE (0x68) > + #define P3_MFAR(x) (P3_MFAR_BASE + 2 * (x)) > + > +#define P3_MFAR23 (0x6A) > +#define P3_MFAR45 (0x6C) > +#define P3_MFAR67 (0x6E) > +#define P3_VID0FR (0x70) > +#define P3_VID1FR (0x72) > +#define P3_EECSR (0x74) > +#define P3_EEDR (0x76) > +#define P3_EECR (0x78) > + #define EECR_ADDR_MASK (0x00FF) > + #define EECR_READ_ACT (1 << 8) > + #define EECR_WRITE_ACT (1 << 9) > + #define EECR_WRITE_DISABLE (1 << 10) > + #define EECR_WRITE_ENABLE (1 << 11) > + #define EECR_EE_READY (1 << 13) > + #define EECR_RELOAD (1 << 14) > + #define EECR_RESET (1 << 15) > +#define P3_TPCR (0x7A) > + #define TPCR_PATT_MASK (0xFF) > + #define TPCR_RAND_PKT_EN (1 << 14) > + #define TPCR_FIXED_PKT_EN (1 << 15) > +#define P3_TPLR (0x7C) > + /* Definition of PAGE4 */ > +#define P4_SPICR (0x8A) > + #define SPICR_RCEN (1 << 0) > + #define SPICR_QCEN (1 << 1) > + #define SPICR_RBRE (1 << 3) > + #define SPICR_PMM (1 << 4) > + #define SPICR_LOOPBACK (1 << 8) > + #define SPICR_CORE_RES_CLR (1 << 10) > + #define SPICR_SPI_RES_CLR (1 << 11) > +#define P4_SPIISMR (0x8C) > + > +#define P4_COERCR0 (0x92) > + #define COERCR0_RXIPCE (1 << 0) > + #define COERCR0_RXIPVE (1 << 1) > + #define COERCR0_RXV6PE (1 << 2) > + #define COERCR0_RXTCPE (1 << 3) > + #define COERCR0_RXUDPE (1 << 4) > + #define COERCR0_RXICMP (1 << 5) > + #define COERCR0_RXIGMP (1 << 6) > + #define COERCR0_RXICV6 (1 << 7) > + > + #define COERCR0_RXTCPV6 (1 << 8) > + #define COERCR0_RXUDPV6 (1 << 9) > + #define COERCR0_RXICMV6 (1 << 10) > + #define COERCR0_RXIGMV6 (1 << 11) > + #define COERCR0_RXICV6V6 (1 << 12) > + > + #define COERCR0_DEFAULT (COERCR0_RXIPCE | COERCR0_RXV6PE | \ > + COERCR0_RXTCPE | COERCR0_RXUDPE | \ > + COERCR0_RXTCPV6 | COERCR0_RXUDPV6) > +#define P4_COERCR1 (0x94) > + #define COERCR1_IPCEDP (1 << 0) > + #define COERCR1_IPVEDP (1 << 1) > + #define COERCR1_V6VEDP (1 << 2) > + #define COERCR1_TCPEDP (1 << 3) > + #define COERCR1_UDPEDP (1 << 4) > + #define COERCR1_ICMPDP (1 << 5) > + #define COERCR1_IGMPDP (1 << 6) > + #define COERCR1_ICV6DP (1 << 7) > + #define COERCR1_RX64TE (1 << 8) > + #define COERCR1_RXPPPE (1 << 9) > + #define COERCR1_TCP6DP (1 << 10) > + #define COERCR1_UDP6DP (1 << 11) > + #define COERCR1_IC6DP (1 << 12) > + #define COERCR1_IG6DP (1 << 13) > + #define COERCR1_ICV66DP (1 << 14) > + #define COERCR1_RPCE (1 << 15) > + > + #define COERCR1_DEFAULT (COERCR1_RXPPPE) > + > +#define P4_COETCR0 (0x96) > + #define COETCR0_TXIP (1 << 0) > + #define COETCR0_TXTCP (1 << 1) > + #define COETCR0_TXUDP (1 << 2) > + #define COETCR0_TXICMP (1 << 3) > + #define COETCR0_TXIGMP (1 << 4) > + #define COETCR0_TXICV6 (1 << 5) > + #define COETCR0_TXTCPV6 (1 << 8) > + #define COETCR0_TXUDPV6 (1 << 9) > + #define COETCR0_TXICMV6 (1 << 10) > + #define COETCR0_TXIGMV6 (1 << 11) > + #define COETCR0_TXICV6V6 (1 << 12) > + > + #define COETCR0_DEFAULT (COETCR0_TXIP | COETCR0_TXTCP | \ > + COETCR0_TXUDP | COETCR0_TXTCPV6 | \ > + COETCR0_TXUDPV6) > +#define P4_COETCR1 (0x98) > + #define COETCR1_TX64TE (1 << 0) > + #define COETCR1_TXPPPE (1 << 1) > + > +#define P4_COECEDR (0x9A) > +#define P4_L2CECR (0x9C) > + > + /* Definition of PAGE5 */ > +#define P5_WFTR (0xA2) > + #define WFTR_2MS (0x01) > + #define WFTR_4MS (0x02) > + #define WFTR_8MS (0x03) > + #define WFTR_16MS (0x04) > + #define WFTR_32MS (0x05) > + #define WFTR_64MS (0x06) > + #define WFTR_128MS (0x07) > + #define WFTR_256MS (0x08) > + #define WFTR_512MS (0x09) > + #define WFTR_1024MS (0x0A) > + #define WFTR_2048MS (0x0B) > + #define WFTR_4096MS (0x0C) > + #define WFTR_8192MS (0x0D) > + #define WFTR_16384MS (0x0E) > + #define WFTR_32768MS (0x0F) > +#define P5_WFCCR (0xA4) > +#define P5_WFCR03 (0xA6) > + #define WFCR03_F0_EN (1 << 0) > + #define WFCR03_F1_EN (1 << 4) > + #define WFCR03_F2_EN (1 << 8) > + #define WFCR03_F3_EN (1 << 12) > +#define P5_WFCR47 (0xA8) > + #define WFCR47_F4_EN (1 << 0) > + #define WFCR47_F5_EN (1 << 4) > + #define WFCR47_F6_EN (1 << 8) > + #define WFCR47_F7_EN (1 << 12) > +#define P5_WF0BMR0 (0xAA) > +#define P5_WF0BMR1 (0xAC) > +#define P5_WF0CR (0xAE) > +#define P5_WF0OBR (0xB0) > +#define P5_WF1BMR0 (0xB2) > +#define P5_WF1BMR1 (0xB4) > +#define P5_WF1CR (0xB6) > +#define P5_WF1OBR (0xB8) > +#define P5_WF2BMR0 (0xBA) > +#define P5_WF2BMR1 (0xBC) > + > + /* Definition of PAGE6 */ > +#define P6_WF2CR (0xC2) > +#define P6_WF2OBR (0xC4) > +#define P6_WF3BMR0 (0xC6) > +#define P6_WF3BMR1 (0xC8) > +#define P6_WF3CR (0xCA) > +#define P6_WF3OBR (0xCC) > +#define P6_WF4BMR0 (0xCE) > +#define P6_WF4BMR1 (0xD0) > +#define P6_WF4CR (0xD2) > +#define P6_WF4OBR (0xD4) > +#define P6_WF5BMR0 (0xD6) > +#define P6_WF5BMR1 (0xD8) > +#define P6_WF5CR (0xDA) > +#define P6_WF5OBR (0xDC) > + > +/* Definition of PAGE7 */ > +#define P7_WF6BMR0 (0xE2) > +#define P7_WF6BMR1 (0xE4) > +#define P7_WF6CR (0xE6) > +#define P7_WF6OBR (0xE8) > +#define P7_WF7BMR0 (0xEA) > +#define P7_WF7BMR1 (0xEC) > +#define P7_WF7CR (0xEE) > +#define P7_WF7OBR (0xF0) > +#define P7_WFR01 (0xF2) > +#define P7_WFR23 (0xF4) > +#define P7_WFR45 (0xF6) > +#define P7_WFR67 (0xF8) > +#define P7_WFPC0 (0xFA) > +#define P7_WFPC1 (0xFC) > + > +/* Tx headers structure */ > +struct tx_sop_header { > + /* bit 15-11: flags, bit 10-0: packet length */ > + u16 flags_len; > + /* bit 15-11: sequence number, bit 11-0: packet length bar */ > + u16 seq_lenbar; > +} __packed; > + > +struct tx_segment_header { > + /* bit 15-14: flags, bit 13-11: segment number */ > + /* bit 10-0: segment length */ > + u16 flags_seqnum_seglen; > + /* bit 15-14: end offset, bit 13-11: start offset */ > + /* bit 10-0: segment length bar */ > + u16 eo_so_seglenbar; > +} __packed; > + > +struct tx_eop_header { > + /* bit 15-11: sequence number, bit 10-0: packet length */ > + u16 seq_len; > + /* bit 15-11: sequence number bar, bit 10-0: packet length bar */ > + u16 seqbar_lenbar; > +} __packed; > + > +struct tx_pkt_info { > + struct tx_sop_header sop; > + struct tx_segment_header seg; > + struct tx_eop_header eop; > + u16 pkt_len; > + u16 seq_num; > +} __packed; > + > +/* Rx headers structure */ > +struct rx_header { > + u16 flags_len; > + u16 seq_lenbar; > + u16 flags; > +} __packed; > + > +extern unsigned long ax88796c_no_regs_mask[]; > + > +#endif /* #ifndef _AX88796C_MAIN_H */ > diff --git a/drivers/net/ethernet/asix/ax88796c_spi.c b/drivers/net/ethernet/asix/ax88796c_spi.c > new file mode 100644 > index 000000000000..1a20bbeb4dc1 > --- /dev/null > +++ b/drivers/net/ethernet/asix/ax88796c_spi.c > @@ -0,0 +1,111 @@ > +// 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" > + > +/* driver bus management functions */ > +int axspi_wakeup(const struct axspi_data *ax_spi) > +{ > + u8 tx_buf; > + int ret; > + > + tx_buf = AX_SPICMD_EXIT_PWD; /* OP */ > + ret = spi_write(ax_spi->spi, &tx_buf, 1); > + if (ret) > + dev_err(&ax_spi->spi->dev, "%s() failed: ret = %d\n", __func__, ret); > + return ret; > +} > + > +int axspi_read_status(const struct axspi_data *ax_spi, struct spi_status *status) > +{ > + u8 tx_buf; > + int ret; > + > + /* OP */ > + tx_buf = AX_SPICMD_READ_STATUS; > + ret = spi_write_then_read(ax_spi->spi, &tx_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, 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(const struct axspi_data *ax_spi, u8 reg) > +{ > + u8 tx_buf[4]; > + u16 rx_buf = 0; > + int ret; > + int len = ax_spi->comp ? 3 : 4; > + > + tx_buf[0] = 0x03; /* OP code read register */ > + tx_buf[1] = reg; /* register address */ > + tx_buf[2] = 0xFF; /* dumy cycle */ > + tx_buf[3] = 0xFF; /* dumy cycle */ > + ret = spi_write_then_read(ax_spi->spi, tx_buf, len, (u8 *)&rx_buf, 2); > + if (ret) > + dev_err(&ax_spi->spi->dev, "%s() failed: ret = %d\n", __func__, ret); > + else > + le16_to_cpus(&rx_buf); > + > + return rx_buf; > +} > + > +int axspi_write_reg(const struct axspi_data *ax_spi, u8 reg, u16 value) > +{ > + u8 tx_buf[4]; > + int ret; > + > + tx_buf[0] = AX_SPICMD_WRITE_REG; /* OP code read register */ > + tx_buf[1] = reg; /* register address */ > + tx_buf[2] = value; > + tx_buf[3] = value >> 8; > + > + ret = spi_write(ax_spi->spi, tx_buf, 4); > + if (ret) > + dev_err(&ax_spi->spi->dev, "%s() failed: ret = %d\n", __func__, ret); > + return ret; > +} > + > diff --git a/drivers/net/ethernet/asix/ax88796c_spi.h b/drivers/net/ethernet/asix/ax88796c_spi.h > new file mode 100644 > index 000000000000..7a49205c2cfb > --- /dev/null > +++ b/drivers/net/ethernet/asix/ax88796c_spi.h > @@ -0,0 +1,69 @@ > +/* 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 > + > +static const u8 rx_cmd_buf[5] = {AX_SPICMD_READ_RXQ, 0xFF, 0xFF, 0xFF, 0xFF}; > +static const u8 tx_cmd_buf[4] = {AX_SPICMD_WRITE_TXQ, 0xFF, 0xFF, 0xFF}; > + > +struct axspi_data { > + struct spi_device *spi; > + struct spi_message rx_msg; > + struct spi_transfer spi_rx_xfer[2]; > + u8 cmd_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(const struct axspi_data *ax_spi, u8 reg); > +int axspi_write_reg(const struct axspi_data *ax_spi, u8 reg, u16 value); > +int axspi_read_status(const struct axspi_data *ax_spi, struct spi_status *status); > +int axspi_wakeup(const struct axspi_data *ax_spi); > + > +static inline u16 AX_READ(const struct axspi_data *ax_spi, u8 offset) > +{ > + return axspi_read_reg(ax_spi, offset); > +} > + > +static inline int AX_WRITE(const struct axspi_data *ax_spi, u16 value, u8 offset) > +{ > + return axspi_write_reg(ax_spi, offset, value); > +} > + > +static inline int AX_READ_STATUS(const 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 > + >
It was <2020-10-03 sob 14:59>, when Heiner Kallweit wrote: > On 02.10.2020 21:22, Łukasz Stelmach wrote: >> 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. >> >> [1] >> https://protect2.fireeye.com/v1/url?k=f34a6c6f-ae99377b-f34be720-0cc47a31ce4e-31468d469d6422a1&q=1&e=9cb90086-9be8-4db6-8a58-c5447926b709&u=https%3A%2F%2Fwww.asix.com.tw%2Fproducts.php%3Fop%3DpItemdetail%26PItemID%3D104%3B65%3B86%26PLine%3D65 >> [2] >> https://protect2.fireeye.com/v1/url?k=67412e7e-3a92756a-6740a531-0cc47a31ce4e-4192baccfff43dec&q=1&e=9cb90086-9be8-4db6-8a58-c5447926b709&u=https%3A%2F%2Fgit.tizen.org%2Fcgit%2Fprofile%2Fcommon%2Fplatform%2Fkernel%2Flinux-3.10-artik%2F >> >> The other ax88796 driver is for NE2000 compatible AX88796L chip. These >> chips are not compatible. Hence, two separate drivers are required. >> >> Signed-off-by: Łukasz Stelmach <l.stelmach@samsung.com> >> --- >> MAINTAINERS | 6 + >> drivers/net/ethernet/Kconfig | 1 + >> drivers/net/ethernet/Makefile | 1 + >> drivers/net/ethernet/asix/Kconfig | 21 + >> drivers/net/ethernet/asix/Makefile | 6 + >> drivers/net/ethernet/asix/ax88796c_ioctl.c | 241 +++++ >> drivers/net/ethernet/asix/ax88796c_ioctl.h | 27 + >> drivers/net/ethernet/asix/ax88796c_main.c | 1041 ++++++++++++++++++++ >> drivers/net/ethernet/asix/ax88796c_main.h | 568 +++++++++++ >> drivers/net/ethernet/asix/ax88796c_spi.c | 111 +++ >> drivers/net/ethernet/asix/ax88796c_spi.h | 69 ++ >> 11 files changed, 2092 insertions(+) >> create mode 100644 drivers/net/ethernet/asix/Kconfig >> create mode 100644 drivers/net/ethernet/asix/Makefile >> create mode 100644 drivers/net/ethernet/asix/ax88796c_ioctl.c >> create mode 100644 drivers/net/ethernet/asix/ax88796c_ioctl.h >> create mode 100644 drivers/net/ethernet/asix/ax88796c_main.c >> create mode 100644 drivers/net/ethernet/asix/ax88796c_main.h >> create mode 100644 drivers/net/ethernet/asix/ax88796c_spi.c >> create mode 100644 drivers/net/ethernet/asix/ax88796c_spi.h >> >> diff --git a/MAINTAINERS b/MAINTAINERS >> index deaafb617361..654eb0127479 100644 >> --- a/MAINTAINERS >> +++ b/MAINTAINERS >> @@ -2822,6 +2822,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,ax99706c-spi.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) >> diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig >> index de50e8b9e656..f3b218e45ea5 100644 >> --- a/drivers/net/ethernet/Kconfig >> +++ b/drivers/net/ethernet/Kconfig >> @@ -32,6 +32,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/aurora/Kconfig" >> source "drivers/net/ethernet/broadcom/Kconfig" >> diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile >> index f8f38dcb5f8a..9eb368d93607 100644 >> --- a/drivers/net/ethernet/Makefile >> +++ b/drivers/net/ethernet/Makefile >> @@ -18,6 +18,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_AURORA) += aurora/ >> obj-$(CONFIG_NET_VENDOR_CADENCE) += cadence/ >> diff --git a/drivers/net/ethernet/asix/Kconfig b/drivers/net/ethernet/asix/Kconfig >> new file mode 100644 >> index 000000000000..7caa45607450 >> --- /dev/null >> +++ b/drivers/net/ethernet/asix/Kconfig >> @@ -0,0 +1,21 @@ >> +# >> +# 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" >> + depends on SPI >> + depends on GPIOLIB >> + help >> + Say Y here if you intend to use ASIX AX88796C attached in SPI mode. >> + >> +endif # NET_VENDOR_ASIX >> diff --git a/drivers/net/ethernet/asix/Makefile b/drivers/net/ethernet/asix/Makefile >> new file mode 100644 >> index 000000000000..0bfbbb042634 >> --- /dev/null >> +++ b/drivers/net/ethernet/asix/Makefile >> @@ -0,0 +1,6 @@ >> +# >> +# Makefile for the Asix network device drivers. >> +# >> + >> +obj-$(CONFIG_SPI_AX88796C) += ax88796c.o >> +ax88796c-y := ax88796c_main.o ax88796c_ioctl.o ax88796c_spi.o >> diff --git a/drivers/net/ethernet/asix/ax88796c_ioctl.c b/drivers/net/ethernet/asix/ax88796c_ioctl.c >> new file mode 100644 >> index 000000000000..b3e3ac96790d >> --- /dev/null >> +++ b/drivers/net/ethernet/asix/ax88796c_ioctl.c >> @@ -0,0 +1,241 @@ >> +// 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 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_link(struct net_device *ndev) > > Why not using ethtool_op_get_link ? > For no particualr reason other than lack of knowledge. It seems OK to use it and it works fine. >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + >> + mutex_lock(&ax_local->spi_lock); >> + >> + phy_read_status(ndev->phydev); >> + >> + mutex_unlock(&ax_local->spi_lock); >> + >> + return ndev->phydev->link; >> +} >> + >> +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 int >> +ax88796c_get_link_ksettings(struct net_device *ndev, >> + struct ethtool_link_ksettings *cmd) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + int ret; >> + >> + mutex_lock(&ax_local->spi_lock); >> + >> + ret = phy_ethtool_get_link_ksettings(ndev, cmd); >> + >> + mutex_unlock(&ax_local->spi_lock); >> + >> + return ret; >> +} >> + >> +static int >> +ax88796c_set_link_ksettings(struct net_device *ndev, >> + const struct ethtool_link_ksettings *cmd) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + int ret; >> + >> + mutex_lock(&ax_local->spi_lock); >> + >> + ret = phy_ethtool_set_link_ksettings(ndev, cmd); >> + >> + mutex_unlock(&ax_local->spi_lock); >> + >> + return ret; >> +} >> + >> +static int ax88796c_nway_reset(struct net_device *ndev) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + int ret; >> + >> + mutex_lock(&ax_local->spi_lock); >> + >> + ret = phy_ethtool_nway_reset(ndev); >> + >> + mutex_unlock(&ax_local->spi_lock); >> + >> + return ret; >> +} >> + >> +static u32 ax88796c_ethtool_getmsglevel(struct net_device *ndev) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + >> + return ax_local->msg_enable; >> +} >> + >> +static void ax88796c_ethtool_setmsglevel(struct net_device *ndev, u32 level) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + >> + ax_local->msg_enable = level; >> +} >> + >> +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); >> + u16 *p = _p; >> + int offset, i; >> + >> + memset(p, 0, AX88796C_REGDUMP_LEN); >> + >> + 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++; >> + } >> + >> + for (i = 0; i < AX88796C_PHY_REGDUMP_LEN / 2; i++) { >> + *p = phy_read(ax_local->phydev, i); >> + p++; >> + } >> +} >> + >> +int ax88796c_mdio_read(struct mii_bus *mdiobus, int phy_id, int loc) >> +{ >> + struct ax88796c_device *ax_local = mdiobus->priv; >> + int ret; >> + >> + 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) >> + return -EBUSY; >> + >> + return AX_READ(&ax_local->ax_spi, P2_MDIODR); >> +} >> + >> +int >> +ax88796c_mdio_write(struct mii_bus *mdiobus, int phy_id, int loc, u16 val) >> +{ >> + struct ax88796c_device *ax_local = mdiobus->priv; >> + int ret; >> + >> + 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); >> + if (ret) >> + return -EIO; >> + >> + if (loc == MII_ADVERTISE) { >> + AX_WRITE(&ax_local->ax_spi, (BMCR_FULLDPLX | BMCR_ANRESTART | >> + BMCR_ANENABLE | BMCR_SPEED100), P2_MDIODR); > > This looks very hacky. Why do you do this? As I wrote Andy Lunn: I will honestly say, I am not sure. Apparently it is not required and I am willing to remove it. It could be of some use when the driver didn't use phylib. > What if the user wants to switch to fixed mode? For the record, it doesn't look like it prevents a fixed mode. This code runs when user changes the list of advertised modes which means they want it to be used for autonegotiation so why not turn it on. However, since it doesn't appear other drivers behave like this I will remove this code. >> + AX_WRITE(&ax_local->ax_spi, (MDIOCR_RADDR(MII_BMCR) | >> + 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); >> + if (ret) >> + return -EIO; >> + } >> + >> + return 0; >> +} >> + >> +void ax88796c_set_csums(struct ax88796c_device *ax_local) >> +{ >> + struct net_device *ndev = ax_local->ndev; >> + >> + if (ndev->features & NETIF_F_RXCSUM) { >> + AX_WRITE(&ax_local->ax_spi, COERCR0_DEFAULT, P4_COERCR0); >> + AX_WRITE(&ax_local->ax_spi, COERCR1_DEFAULT, P4_COERCR1); >> + } else { >> + AX_WRITE(&ax_local->ax_spi, 0, P4_COERCR0); >> + AX_WRITE(&ax_local->ax_spi, 0, P4_COERCR1); >> + } >> + >> + if (ndev->features & NETIF_F_HW_CSUM) { >> + AX_WRITE(&ax_local->ax_spi, COETCR0_DEFAULT, P4_COETCR0); >> + AX_WRITE(&ax_local->ax_spi, COETCR1_TXPPPE, P4_COETCR1); >> + } else { >> + AX_WRITE(&ax_local->ax_spi, 0, P4_COETCR0); >> + AX_WRITE(&ax_local->ax_spi, 0, P4_COETCR1); >> + } >> +} >> + >> +const struct ethtool_ops ax88796c_ethtool_ops = { >> + .get_drvinfo = ax88796c_get_drvinfo, >> + .get_link = ax88796c_get_link, >> + .get_msglevel = ax88796c_get_msglevel, >> + .set_msglevel = ax88796c_set_msglevel, >> + .get_link_ksettings = ax88796c_get_link_ksettings, >> + .set_link_ksettings = ax88796c_set_link_ksettings, >> + .nway_reset = ax88796c_nway_reset, >> + .get_msglevel = ax88796c_ethtool_getmsglevel, >> + .set_msglevel = ax88796c_ethtool_setmsglevel, >> + .get_regs_len = ax88796c_get_regs_len, >> + .get_regs = ax88796c_get_regs, >> +}; >> + >> +int ax88796c_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + int ret; >> + >> + mutex_lock(&ax_local->spi_lock); >> + >> + ret = phy_mii_ioctl(ndev->phydev, ifr, cmd); >> + >> + mutex_unlock(&ax_local->spi_lock); >> + >> + return ret; >> +} >> diff --git a/drivers/net/ethernet/asix/ax88796c_ioctl.h b/drivers/net/ethernet/asix/ax88796c_ioctl.h >> new file mode 100644 >> index 000000000000..d478981bf995 >> --- /dev/null >> +++ b/drivers/net/ethernet/asix/ax88796c_ioctl.h >> @@ -0,0 +1,27 @@ >> +/* 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); >> +void ax88796c_set_csums(struct ax88796c_device *ax_local); >> +int ax88796c_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd); >> + >> +#endif >> diff --git a/drivers/net/ethernet/asix/ax88796c_main.c b/drivers/net/ethernet/asix/ax88796c_main.c >> new file mode 100644 >> index 000000000000..2148ea01362a >> --- /dev/null >> +++ b/drivers/net/ethernet/asix/ax88796c_main.c >> @@ -0,0 +1,1041 @@ >> +// 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 "ax88796c_main.h" >> +#include "ax88796c_ioctl.h" >> + >> +#include <linux/bitmap.h> >> +#include <linux/etherdevice.h> >> +#include <linux/iopoll.h> >> +#include <linux/mdio.h> >> +#include <linux/module.h> >> +#include <linux/netdevice.h> >> +#include <linux/of.h> >> +#include <linux/phy.h> >> +#include <linux/spi/spi.h> >> + >> +static int comp; >> +static int msg_enable = NETIF_MSG_PROBE | >> + NETIF_MSG_LINK | >> + /* NETIF_MSG_TIMER | */ >> + /* NETIF_MSG_IFDOWN | */ >> + /* NETIF_MSG_IFUP | */ >> + NETIF_MSG_RX_ERR | >> + NETIF_MSG_TX_ERR | >> + /* NETIF_MSG_TX_QUEUED | */ >> + /* NETIF_MSG_INTR | */ >> + /* NETIF_MSG_TX_DONE | */ >> + /* NETIF_MSG_RX_STATUS | */ >> + /* NETIF_MSG_PKTDATA | */ >> + /* NETIF_MSG_HW | */ >> + /* NETIF_MSG_WOL | */ >> + 0; >> + >> +static char *no_regs_list = "80018001,e1918001,8001a001,fc0d0000"; >> +unsigned long ax88796c_no_regs_mask[AX88796C_REGDUMP_LEN / (sizeof(unsigned long) * 8)]; >> + >> +module_param(comp, int, 0444); >> +MODULE_PARM_DESC(comp, "0=Non-Compression Mode, 1=Compression Mode"); >> + >> +module_param(msg_enable, int, 0444); >> +MODULE_PARM_DESC(msg_enable, "Message mask (see linux/netdevice.h for bitmap)"); >> + >> +static int ax88796c_soft_reset(struct ax88796c_device *ax_local) >> +{ >> + u16 temp; >> + int ret; >> + >> + AX_WRITE(&ax_local->ax_spi, PSR_RESET, P0_PSR); >> + AX_WRITE(&ax_local->ax_spi, PSR_RESET_CLR, P0_PSR); >> + >> + ret = read_poll_timeout(AX_READ, ret, >> + (ret & PSR_DEV_READY), >> + 0, jiffies_to_usecs(160 * HZ / 1000), false, >> + &ax_local->ax_spi, P0_PSR); >> + if (ret) >> + return -1; >> + >> + temp = AX_READ(&ax_local->ax_spi, P4_SPICR); >> + if (ax_local->capabilities & AX_CAP_COMP) { >> + AX_WRITE(&ax_local->ax_spi, >> + (temp | SPICR_RCEN | SPICR_QCEN), P4_SPICR); >> + ax_local->ax_spi.comp = 1; >> + } else { >> + AX_WRITE(&ax_local->ax_spi, >> + (temp & ~(SPICR_RCEN | SPICR_QCEN)), P4_SPICR); >> + ax_local->ax_spi.comp = 0; >> + } >> + >> + return 0; >> +} >> + >> +static int ax88796c_reload_eeprom(struct ax88796c_device *ax_local) >> +{ >> + int ret; >> + >> + AX_WRITE(&ax_local->ax_spi, EECR_RELOAD, P3_EECR); >> + >> + ret = read_poll_timeout(AX_READ, ret, >> + (ret & PSR_DEV_READY), >> + 0, jiffies_to_usecs(2 * HZ / 1000), false, >> + &ax_local->ax_spi, P0_PSR); >> + if (ret) { >> + dev_err(&ax_local->spi->dev, >> + "timeout waiting for reload eeprom\n"); >> + return -1; >> + } >> + >> + return 0; >> +} >> + >> +static void ax88796c_set_hw_multicast(struct net_device *ndev) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + u16 rx_ctl = RXCR_AB; >> + int mc_count = netdev_mc_count(ndev); >> + >> + memset(ax_local->multi_filter, 0, AX_MCAST_FILTER_SIZE); >> + >> + if (ndev->flags & IFF_PROMISC) { >> + rx_ctl |= RXCR_PRO; >> + >> + } else if (ndev->flags & IFF_ALLMULTI || mc_count > AX_MAX_MCAST) { >> + rx_ctl |= RXCR_AMALL; >> + >> + } else if (mc_count == 0) { >> + /* just broadcast and directed */ >> + } else { >> + u32 crc_bits; >> + int i; >> + struct netdev_hw_addr *ha; >> + >> + netdev_for_each_mc_addr(ha, ndev) { >> + crc_bits = ether_crc(ETH_ALEN, ha->addr); >> + ax_local->multi_filter[crc_bits >> 29] |= >> + (1 << ((crc_bits >> 26) & 7)); >> + } >> + >> + for (i = 0; i < 4; i++) { >> + AX_WRITE(&ax_local->ax_spi, >> + ((ax_local->multi_filter[i * 2 + 1] << 8) | >> + ax_local->multi_filter[i * 2]), P3_MFAR(i)); >> + } >> + } >> + >> + AX_WRITE(&ax_local->ax_spi, rx_ctl, P2_RXCR); >> +} >> + >> +static void ax88796c_set_mac_addr(struct net_device *ndev) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + >> + AX_WRITE(&ax_local->ax_spi, ((u16)(ndev->dev_addr[4] << 8) | >> + (u16)ndev->dev_addr[5]), P3_MACASR0); >> + AX_WRITE(&ax_local->ax_spi, ((u16)(ndev->dev_addr[2] << 8) | >> + (u16)ndev->dev_addr[3]), P3_MACASR1); >> + AX_WRITE(&ax_local->ax_spi, ((u16)(ndev->dev_addr[0] << 8) | >> + (u16)ndev->dev_addr[1]), P3_MACASR2); >> +} >> + >> +static int ax88796c_set_mac_address(struct net_device *ndev, void *p) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + struct sockaddr *addr = p; >> + >> + if (!is_valid_ether_addr(addr->sa_data)) >> + return -EADDRNOTAVAIL; >> + >> + memcpy(ndev->dev_addr, addr->sa_data, ndev->addr_len); >> + >> + mutex_lock(&ax_local->spi_lock); >> + >> + ax88796c_set_mac_addr(ndev); >> + >> + mutex_unlock(&ax_local->spi_lock); >> + >> + return 0; >> +} >> + >> +static void ax88796c_load_mac_addr(struct net_device *ndev) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + u16 temp; >> + >> + /* Try the device tree first */ >> + if (!eth_platform_get_mac_address(&ax_local->spi->dev, ndev->dev_addr) && >> + is_valid_ether_addr(ndev->dev_addr)) { >> + if (netif_msg_probe(ax_local)) >> + dev_info(&ax_local->spi->dev, >> + "MAC address read from device tree\n"); >> + return; >> + } >> + >> + /* Read the MAC address from AX88796C */ >> + temp = AX_READ(&ax_local->ax_spi, P3_MACASR0); >> + ndev->dev_addr[5] = (u8)temp; >> + ndev->dev_addr[4] = (u8)(temp >> 8); >> + >> + temp = AX_READ(&ax_local->ax_spi, P3_MACASR1); >> + ndev->dev_addr[3] = (u8)temp; >> + ndev->dev_addr[2] = (u8)(temp >> 8); >> + >> + temp = AX_READ(&ax_local->ax_spi, P3_MACASR2); >> + ndev->dev_addr[1] = (u8)temp; >> + ndev->dev_addr[0] = (u8)(temp >> 8); >> + >> + if (is_valid_ether_addr(ndev->dev_addr)) { >> + if (netif_msg_probe(ax_local)) >> + dev_info(&ax_local->spi->dev, >> + "MAC address read from ASIX chip\n"); >> + return; >> + } >> + >> + /* Use random address if none found */ >> + if (netif_msg_probe(ax_local)) >> + dev_info(&ax_local->spi->dev, "Use random MAC address\n"); >> + eth_hw_addr_random(ndev); >> +} >> + >> +static void ax88796c_proc_tx_hdr(struct tx_pkt_info *info, u8 ip_summed) >> +{ >> + u16 pkt_len_bar = (~info->pkt_len & TX_HDR_SOP_PKTLENBAR); >> + >> + /* Prepare SOP header */ >> + info->sop.flags_len = info->pkt_len | >> + ((ip_summed == CHECKSUM_NONE) || >> + (ip_summed == CHECKSUM_UNNECESSARY) ? TX_HDR_SOP_DICF : 0); >> + >> + info->sop.seq_lenbar = ((info->seq_num << 11) & TX_HDR_SOP_SEQNUM) >> + | pkt_len_bar; >> + cpu_to_be16s(&info->sop.flags_len); >> + cpu_to_be16s(&info->sop.seq_lenbar); >> + >> + /* Prepare Segment header */ >> + info->seg.flags_seqnum_seglen = TX_HDR_SEG_FS | TX_HDR_SEG_LS >> + | info->pkt_len; >> + >> + info->seg.eo_so_seglenbar = pkt_len_bar; >> + >> + cpu_to_be16s(&info->seg.flags_seqnum_seglen); >> + cpu_to_be16s(&info->seg.eo_so_seglenbar); >> + >> + /* Prepare EOP header */ >> + info->eop.seq_len = ((info->seq_num << 11) & >> + TX_HDR_EOP_SEQNUM) | info->pkt_len; >> + info->eop.seqbar_lenbar = ((~info->seq_num << 11) & >> + TX_HDR_EOP_SEQNUMBAR) | pkt_len_bar; >> + >> + cpu_to_be16s(&info->eop.seq_len); >> + cpu_to_be16s(&info->eop.seqbar_lenbar); >> +} >> + >> +static int >> +ax88796c_check_free_pages(struct ax88796c_device *ax_local, u8 need_pages) >> +{ >> + u8 free_pages; >> + u16 tmp; >> + >> + free_pages = AX_READ(&ax_local->ax_spi, P0_TFBFCR) & TX_FREEBUF_MASK; >> + if (free_pages < need_pages) { >> + /* schedule free page interrupt */ >> + tmp = AX_READ(&ax_local->ax_spi, P0_TFBFCR) >> + & TFBFCR_SCHE_FREE_PAGE; >> + AX_WRITE(&ax_local->ax_spi, tmp | TFBFCR_TX_PAGE_SET | >> + TFBFCR_SET_FREE_PAGE(need_pages), >> + P0_TFBFCR); >> + return -ENOMEM; >> + } >> + >> + return 0; >> +} >> + >> +static struct sk_buff * >> +ax88796c_tx_fixup(struct net_device *ndev, struct sk_buff_head *q) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + struct sk_buff *skb, *tx_skb; >> + struct tx_pkt_info *info; >> + struct skb_data *entry; >> + int headroom; >> + int tailroom; >> + u8 need_pages; >> + u16 tol_len, pkt_len; >> + u8 padlen, seq_num; >> + u8 spi_len = ax_local->ax_spi.comp ? 1 : 4; >> + >> + if (skb_queue_empty(q)) >> + return NULL; >> + >> + skb = skb_peek(q); >> + pkt_len = skb->len; >> + need_pages = (pkt_len + TX_OVERHEAD + 127) >> 7; >> + if (ax88796c_check_free_pages(ax_local, need_pages) != 0) >> + return NULL; >> + >> + headroom = skb_headroom(skb); >> + tailroom = skb_tailroom(skb); >> + padlen = ((pkt_len + 3) & 0x7FC) - pkt_len; >> + tol_len = ((pkt_len + 3) & 0x7FC) + >> + TX_OVERHEAD + TX_EOP_SIZE + spi_len; >> + seq_num = ++ax_local->seq_num & 0x1F; >> + >> + info = (struct tx_pkt_info *)skb->cb; >> + info->pkt_len = pkt_len; >> + >> + if ((!skb_cloned(skb)) && >> + (headroom >= (TX_OVERHEAD + spi_len)) && >> + (tailroom >= (padlen + TX_EOP_SIZE))) { >> + info->seq_num = seq_num; >> + ax88796c_proc_tx_hdr(info, skb->ip_summed); >> + >> + /* SOP and SEG header */ >> + memcpy(skb_push(skb, TX_OVERHEAD), &info->sop, TX_OVERHEAD); >> + >> + /* Write SPI TXQ header */ >> + memcpy(skb_push(skb, spi_len), tx_cmd_buf, spi_len); >> + >> + /* Make 32-bit alignment */ >> + skb_put(skb, padlen); >> + >> + /* EOP header */ >> + memcpy(skb_put(skb, TX_EOP_SIZE), &info->eop, TX_EOP_SIZE); >> + >> + tx_skb = skb; >> + skb_unlink(skb, q); >> + } else { >> + tx_skb = alloc_skb(tol_len, GFP_KERNEL); > > Are you sure GFP_KERNEL is appropriate here and you don't have > to use GFP_ATOMIC? > Well… It's not called from the ISR but rather from a work queue. >> + if (!tx_skb) >> + return NULL; >> + >> + /* Write SPI TXQ header */ >> + memcpy(skb_put(tx_skb, spi_len), tx_cmd_buf, spi_len); >> + >> + info->seq_num = seq_num; >> + ax88796c_proc_tx_hdr(info, skb->ip_summed); >> + >> + /* SOP and SEG header */ >> + memcpy(skb_put(tx_skb, TX_OVERHEAD), >> + &info->sop, TX_OVERHEAD); >> + >> + /* Packet */ >> + memcpy(skb_put(tx_skb, ((pkt_len + 3) & 0xFFFC)), >> + skb->data, pkt_len); >> + >> + /* EOP header */ >> + memcpy(skb_put(tx_skb, TX_EOP_SIZE), >> + &info->eop, TX_EOP_SIZE); >> + >> + skb_unlink(skb, q); >> + dev_kfree_skb(skb); >> + } >> + >> + entry = (struct skb_data *)tx_skb->cb; >> + memset(entry, 0, sizeof(*entry)); >> + entry->len = pkt_len; >> + >> + if (netif_msg_pktdata(ax_local)) { >> + char pfx[IFNAMSIZ + 7]; >> + >> + snprintf(pfx, sizeof(pfx), "%s: ", ndev->name); >> + >> + netdev_info(ndev, "TX packet len %d, total len %d, seq %d\n", >> + pkt_len, tx_skb->len, seq_num); >> + >> + netdev_info(ndev, " SPI Header:\n"); >> + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, >> + tx_skb->data, 4, 0); >> + >> + netdev_info(ndev, " TX SOP:\n"); >> + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, >> + tx_skb->data + 4, TX_OVERHEAD, 0); >> + >> + netdev_info(ndev, " TX packet:\n"); >> + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, >> + tx_skb->data + 4 + TX_OVERHEAD, >> + tx_skb->len - TX_EOP_SIZE - 4 - TX_OVERHEAD, 0); >> + >> + netdev_info(ndev, " TX EOP:\n"); >> + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, >> + tx_skb->data + tx_skb->len - 4, 4, 0); >> + } >> + >> + return tx_skb; >> +} >> + >> +static int ax88796c_hard_xmit(struct ax88796c_device *ax_local) >> +{ >> + struct sk_buff *tx_skb; >> + struct skb_data *entry; >> + >> + tx_skb = ax88796c_tx_fixup(ax_local->ndev, &ax_local->tx_wait_q); >> + >> + if (!tx_skb) >> + return 0; >> + >> + entry = (struct skb_data *)tx_skb->cb; >> + >> + AX_WRITE(&ax_local->ax_spi, >> + (TSNR_TXB_START | TSNR_PKT_CNT(1)), P0_TSNR); >> + >> + axspi_write_txq(&ax_local->ax_spi, tx_skb->data, tx_skb->len); >> + >> + if (((AX_READ(&ax_local->ax_spi, P0_TSNR) & TXNR_TXB_IDLE) == 0) || >> + ((ISR_TXERR & AX_READ(&ax_local->ax_spi, P0_ISR)) != 0)) { >> + /* Ack tx error int */ >> + AX_WRITE(&ax_local->ax_spi, ISR_TXERR, P0_ISR); >> + >> + ax_local->stats.tx_dropped++; >> + >> + netif_err(ax_local, tx_err, ax_local->ndev, >> + "TX FIFO error, re-initialize the TX bridge\n"); >> + >> + /* Reinitial tx bridge */ >> + AX_WRITE(&ax_local->ax_spi, TXNR_TXB_REINIT | >> + AX_READ(&ax_local->ax_spi, P0_TSNR), P0_TSNR); >> + ax_local->seq_num = 0; >> + } else { >> + ax_local->stats.tx_packets++; >> + ax_local->stats.tx_bytes += entry->len; >> + } >> + >> + entry->state = tx_done; >> + dev_kfree_skb(tx_skb); >> + >> + return 1; >> +} >> + >> +static int >> +ax88796c_start_xmit(struct sk_buff *skb, struct net_device *ndev) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + >> + skb_queue_tail(&ax_local->tx_wait_q, skb); >> + if (skb_queue_len(&ax_local->tx_wait_q) > TX_QUEUE_HIGH_WATER) { >> + netif_err(ax_local, tx_queued, ndev, >> + "Too much TX packets in queue %d\n", >> + skb_queue_len(&ax_local->tx_wait_q)); >> + >> + netif_stop_queue(ndev); >> + } >> + >> + set_bit(EVENT_TX, &ax_local->flags); >> + queue_work(ax_local->ax_work_queue, &ax_local->ax_work); >> + >> + return NETDEV_TX_OK; >> +} >> + >> +static void >> +ax88796c_skb_return(struct ax88796c_device *ax_local, struct sk_buff *skb, >> + struct rx_header *rxhdr) >> +{ >> + struct net_device *ndev = ax_local->ndev; >> + int status; >> + >> + do { >> + if (!(ndev->features & NETIF_F_RXCSUM)) >> + break; >> + >> + /* checksum error bit is set */ >> + if ((rxhdr->flags & RX_HDR3_L3_ERR) || >> + (rxhdr->flags & RX_HDR3_L4_ERR)) >> + break; >> + >> + /* Other types may be indicated by more than one bit. */ >> + if ((rxhdr->flags & RX_HDR3_L4_TYPE_TCP) || >> + (rxhdr->flags & RX_HDR3_L4_TYPE_UDP)) >> + skb->ip_summed = CHECKSUM_UNNECESSARY; >> + } while (0); > > Why this do {} while(0) construct? If you want to separate this code, > then put it into its own function. > >> + >> + ax_local->stats.rx_packets++; >> + ax_local->stats.rx_bytes += skb->len; >> + skb->dev = ndev; >> + >> + skb->truesize = skb->len + sizeof(struct sk_buff); >> + skb->protocol = eth_type_trans(skb, ax_local->ndev); >> + >> + netif_info(ax_local, rx_status, ndev, "< rx, len %zu, type 0x%x\n", >> + skb->len + sizeof(struct ethhdr), skb->protocol); >> + >> + status = netif_rx(skb); >> + if (status != NET_RX_SUCCESS) >> + netif_info(ax_local, rx_err, ndev, >> + "netif_rx status %d\n", status); >> +} >> + >> +static void >> +ax88796c_rx_fixup(struct ax88796c_device *ax_local, struct sk_buff *rx_skb) >> +{ >> + struct rx_header *rxhdr = (struct rx_header *)rx_skb->data; >> + struct net_device *ndev = ax_local->ndev; >> + u16 len; >> + >> + be16_to_cpus(&rxhdr->flags_len); >> + be16_to_cpus(&rxhdr->seq_lenbar); >> + be16_to_cpus(&rxhdr->flags); >> + >> + if ((((short)rxhdr->flags_len) & RX_HDR1_PKT_LEN) != >> + (~((short)rxhdr->seq_lenbar) & 0x7FF)) { >> + netif_err(ax_local, rx_err, ndev, "Header error\n"); >> + >> + ax_local->stats.rx_frame_errors++; >> + kfree_skb(rx_skb); >> + return; >> + } >> + >> + if ((rxhdr->flags_len & RX_HDR1_MII_ERR) || >> + (rxhdr->flags_len & RX_HDR1_CRC_ERR)) { >> + netif_err(ax_local, rx_err, ndev, "CRC or MII error\n"); >> + >> + ax_local->stats.rx_crc_errors++; >> + kfree_skb(rx_skb); >> + return; >> + } >> + >> + len = rxhdr->flags_len & RX_HDR1_PKT_LEN; >> + if (netif_msg_pktdata(ax_local)) { >> + char pfx[IFNAMSIZ + 7]; >> + >> + snprintf(pfx, sizeof(pfx), "%s: ", ndev->name); >> + netdev_info(ndev, "RX data, total len %d, packet len %d\n", >> + rx_skb->len, len); >> + >> + netdev_info(ndev, " Dump RX packet header:"); >> + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, >> + rx_skb->data, sizeof(*rxhdr), 0); >> + >> + netdev_info(ndev, " Dump RX packet:"); >> + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, >> + rx_skb->data + sizeof(*rxhdr), len, 0); >> + } >> + >> + skb_pull(rx_skb, sizeof(*rxhdr)); >> + __pskb_trim(rx_skb, len); >> + >> + return ax88796c_skb_return(ax_local, rx_skb, rxhdr); >> +} >> + >> +static int ax88796c_receive(struct net_device *ndev) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + struct sk_buff *skb; >> + struct skb_data *entry; >> + u16 w_count, pkt_len; >> + u8 pkt_cnt; >> + >> + /* check rx packet and total word count */ >> + AX_WRITE(&ax_local->ax_spi, AX_READ(&ax_local->ax_spi, P0_RTWCR) >> + | RTWCR_RX_LATCH, P0_RTWCR); >> + >> + pkt_cnt = AX_READ(&ax_local->ax_spi, P0_RXBCR2) & RXBCR2_PKT_MASK; >> + if (!pkt_cnt) >> + return 0; >> + >> + pkt_len = AX_READ(&ax_local->ax_spi, P0_RCPHR) & 0x7FF; >> + >> + w_count = ((pkt_len + 6 + 3) & 0xFFFC) >> 1; >> + >> + skb = alloc_skb((w_count * 2), GFP_KERNEL); > > Also here, sure you don't have to use GFP_ATOMIC? > This one, indeed, is inside ISR. Done. >> + if (!skb) { >> + AX_WRITE(&ax_local->ax_spi, RXBCR1_RXB_DISCARD, P0_RXBCR1); >> + return 0; >> + } >> + entry = (struct skb_data *)skb->cb; >> + >> + AX_WRITE(&ax_local->ax_spi, RXBCR1_RXB_START | w_count, P0_RXBCR1); >> + >> + axspi_read_rxq(&ax_local->ax_spi, >> + skb_put(skb, w_count * 2), skb->len); >> + >> + /* Check if rx bridge is idle */ >> + if ((AX_READ(&ax_local->ax_spi, P0_RXBCR2) & RXBCR2_RXB_IDLE) == 0) { >> + netif_err(ax_local, rx_err, ndev, >> + "Rx Bridge is not idle\n"); >> + AX_WRITE(&ax_local->ax_spi, RXBCR2_RXB_REINIT, P0_RXBCR2); >> + >> + entry->state = rx_err; >> + } else { >> + entry->state = rx_done; >> + } >> + >> + AX_WRITE(&ax_local->ax_spi, ISR_RXPKT, P0_ISR); >> + >> + ax88796c_rx_fixup(ax_local, skb); >> + >> + return 1; >> +} >> + >> +static int ax88796c_process_isr(struct ax88796c_device *ax_local) >> +{ >> + u16 isr; >> + u8 done = 0; >> + struct net_device *ndev = ax_local->ndev; >> + >> + isr = AX_READ(&ax_local->ax_spi, P0_ISR); >> + AX_WRITE(&ax_local->ax_spi, isr, P0_ISR); >> + >> + netif_dbg(ax_local, intr, ndev, " ISR 0x%04x\n", isr); >> + >> + if (isr & ISR_TXERR) { >> + netif_dbg(ax_local, intr, ndev, " TXERR interrupt\n"); >> + AX_WRITE(&ax_local->ax_spi, TXNR_TXB_REINIT, P0_TSNR); >> + ax_local->seq_num = 0x1f; >> + } >> + >> + if (isr & ISR_TXPAGES) { >> + netif_dbg(ax_local, intr, ndev, " TXPAGES interrupt\n"); >> + set_bit(EVENT_TX, &ax_local->flags); >> + } >> + >> + if (isr & ISR_LINK) { >> + netif_dbg(ax_local, intr, ndev, " Link change interrupt\n"); >> + phy_mac_interrupt(ax_local->ndev->phydev); >> + } >> + >> + if (isr & ISR_RXPKT) { >> + netif_dbg(ax_local, intr, ndev, " RX interrupt\n"); >> + done = ax88796c_receive(ax_local->ndev); >> + } >> + >> + return done; >> +} >> + >> +static irqreturn_t ax88796c_interrupt(int irq, void *dev_instance) >> +{ >> + struct net_device *ndev = dev_instance; >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + >> + if (!ndev) { >> + pr_err("irq %d for unknown device.\n", irq); >> + return IRQ_RETVAL(0); >> + } >> + >> + disable_irq_nosync(irq); >> + >> + netif_dbg(ax_local, intr, ndev, "Interrupt occurred\n"); >> + >> + set_bit(EVENT_INTR, &ax_local->flags); >> + queue_work(ax_local->ax_work_queue, &ax_local->ax_work); >> + > > Why not simpy using a threaded interrupt? > And in general: Why don't you use NAPI in the driver? > As I mention in the commit description this is a driver from a vendor kernel. I am porting it to the mainline. I am willing to make it meet standards and expectation, but my primary goal was to make it work reliably without too many changes. If I understand correctly, using threaded interrupt and getting rid(?) of the work queue means I need to rework the transmitting code too. I'll be more than happy, if I can avoid it. >> + return IRQ_HANDLED; >> +} >> + >> +static void ax88796c_work(struct work_struct *work) >> +{ >> + struct ax88796c_device *ax_local = >> + container_of(work, struct ax88796c_device, ax_work); >> + >> + mutex_lock(&ax_local->spi_lock); >> + >> + if (test_bit(EVENT_SET_MULTI, &ax_local->flags)) { >> + ax88796c_set_hw_multicast(ax_local->ndev); >> + clear_bit(EVENT_SET_MULTI, &ax_local->flags); >> + } >> + >> + if (test_bit(EVENT_INTR, &ax_local->flags)) { >> + AX_WRITE(&ax_local->ax_spi, IMR_MASKALL, P0_IMR); >> + >> + while (1) { >> + if (!ax88796c_process_isr(ax_local)) >> + break; >> + } >> + >> + clear_bit(EVENT_INTR, &ax_local->flags); >> + >> + AX_WRITE(&ax_local->ax_spi, IMR_DEFAULT, P0_IMR); >> + >> + enable_irq(ax_local->ndev->irq); >> + } >> + >> + if (test_bit(EVENT_TX, &ax_local->flags)) { >> + while (skb_queue_len(&ax_local->tx_wait_q)) { >> + if (!ax88796c_hard_xmit(ax_local)) >> + break; >> + } >> + >> + clear_bit(EVENT_TX, &ax_local->flags); >> + >> + if (netif_queue_stopped(ax_local->ndev) && >> + (skb_queue_len(&ax_local->tx_wait_q) < TX_QUEUE_LOW_WATER)) >> + netif_wake_queue(ax_local->ndev); >> + } >> + >> + mutex_unlock(&ax_local->spi_lock); >> +} >> + >> +static struct net_device_stats *ax88796c_get_stats(struct net_device *ndev) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + >> + return &ax_local->stats; >> +} >> + >> +static void ax88796c_handle_link_change(struct net_device *ndev) >> +{ >> + if (net_ratelimit()) >> + phy_print_status(ndev->phydev); >> +} >> + >> +void ax88796c_phy_init(struct ax88796c_device *ax_local) >> +{ >> + /* Enable PHY auto-polling */ >> + AX_WRITE(&ax_local->ax_spi, >> + PCR_PHYID(0x10) | PCR_POLL_EN | >> + PCR_POLL_FLOWCTRL | PCR_POLL_BMCR, P2_PCR); >> +} >> + >> +static int >> +ax88796c_open(struct net_device *ndev) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + int ret; >> + unsigned long irq_flag = IRQF_SHARED; >> + >> + mutex_lock(&ax_local->spi_lock); >> + >> + ret = ax88796c_soft_reset(ax_local); >> + if (ret < 0) >> + return -ENODEV; >> + >> + ret = request_irq(ndev->irq, ax88796c_interrupt, >> + irq_flag, ndev->name, ndev); >> + if (ret) { >> + netdev_err(ndev, "unable to get IRQ %d (errno=%d).\n", >> + ndev->irq, ret); >> + return -ENXIO; >> + } >> + >> + ax_local->seq_num = 0x1f; >> + >> + ax88796c_set_mac_addr(ndev); >> + ax88796c_set_csums(ax_local); >> + >> + /* Disable stuffing packet */ >> + AX_WRITE(&ax_local->ax_spi, >> + AX_READ(&ax_local->ax_spi, P1_RXBSPCR) >> + & ~RXBSPCR_STUF_ENABLE, P1_RXBSPCR); >> + >> + /* Enable RX packet process */ >> + AX_WRITE(&ax_local->ax_spi, RPPER_RXEN, P1_RPPER); >> + >> + AX_WRITE(&ax_local->ax_spi, AX_READ(&ax_local->ax_spi, P0_FER) >> + | FER_RXEN | FER_TXEN | FER_BSWAP | FER_IRQ_PULL, P0_FER); >> + >> + /* Setup LED mode */ >> + AX_WRITE(&ax_local->ax_spi, >> + (LCR_LED0_EN | LCR_LED0_DUPLEX | LCR_LED1_EN | >> + LCR_LED1_100MODE), P2_LCR0); >> + AX_WRITE(&ax_local->ax_spi, >> + (AX_READ(&ax_local->ax_spi, P2_LCR1) & LCR_LED2_MASK) | >> + LCR_LED2_EN | LCR_LED2_LINK, P2_LCR1); >> + >> + ax88796c_phy_init(ax_local); >> + >> + phy_start(ax_local->ndev->phydev); >> + >> + netif_start_queue(ndev); >> + >> + AX_WRITE(&ax_local->ax_spi, IMR_DEFAULT, P0_IMR); >> + >> + spi_message_init(&ax_local->ax_spi.rx_msg); >> + >> + mutex_unlock(&ax_local->spi_lock); >> + >> + return 0; >> +} >> + >> +static void ax88796c_free_skb_queue(struct sk_buff_head *q) >> +{ >> + struct sk_buff *skb; >> + >> + while (q->qlen) { >> + skb = skb_dequeue(q); >> + kfree_skb(skb); >> + } >> +} >> + >> +static int >> +ax88796c_close(struct net_device *ndev) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + >> + netif_stop_queue(ndev); >> + >> + free_irq(ndev->irq, ndev); > > This looks racy. I think e.g. you can still get e.g. rx interrupts. > Done. >> + >> + phy_stop(ndev->phydev); >> + >> + mutex_lock(&ax_local->spi_lock); >> + >> + AX_WRITE(&ax_local->ax_spi, IMR_MASKALL, P0_IMR); >> + ax88796c_free_skb_queue(&ax_local->tx_wait_q); >> + >> + ax88796c_soft_reset(ax_local); >> + >> + mutex_unlock(&ax_local->spi_lock); >> + netif_carrier_off(ndev); >> + >> + return 0; >> +} >> + >> +static int >> +ax88796c_set_features(struct net_device *ndev, netdev_features_t features) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + netdev_features_t changed = features ^ ndev->features; >> + >> + if (!(changed & (NETIF_F_RXCSUM | NETIF_F_HW_CSUM))) >> + return 0; >> + >> + ndev->features = features; >> + >> + if (changed & (NETIF_F_RXCSUM | NETIF_F_HW_CSUM)) >> + ax88796c_set_csums(ax_local); >> + >> + return 0; >> +} >> + >> +static const struct net_device_ops ax88796c_netdev_ops = { >> + .ndo_open = ax88796c_open, >> + .ndo_stop = ax88796c_close, >> + .ndo_start_xmit = ax88796c_start_xmit, >> + .ndo_get_stats = ax88796c_get_stats, >> + .ndo_do_ioctl = ax88796c_ioctl, >> + .ndo_set_mac_address = ax88796c_set_mac_address, >> + .ndo_set_features = ax88796c_set_features, >> +}; >> + >> +static int ax88796c_hard_reset(struct ax88796c_device *ax_local) >> +{ >> + struct device *dev = (struct device *)&ax_local->spi->dev; >> + struct gpio_desc *reset_gpio; >> + >> + /* reset info */ >> + reset_gpio = gpiod_get(dev, "reset", 0); >> + if (IS_ERR(reset_gpio)) { >> + dev_err(dev, "Could not get 'reset' GPIO: %ld", PTR_ERR(reset_gpio)); >> + return PTR_ERR(reset_gpio); >> + } >> + >> + /* set reset */ >> + gpiod_direction_output(reset_gpio, 1); >> + msleep(100); >> + gpiod_direction_output(reset_gpio, 0); >> + gpiod_put(reset_gpio); >> + msleep(20); >> + >> + return 0; >> +} >> + >> +static int ax88796c_probe(struct spi_device *spi) >> +{ >> + struct net_device *ndev; >> + struct ax88796c_device *ax_local; >> + int ret; >> + u16 temp; >> + >> + ndev = devm_alloc_etherdev(&spi->dev, sizeof(*ax_local)); >> + if (!ndev) { >> + dev_err(&spi->dev, "AX88796C SPI: Could not allocate ethernet device\n"); >> + return -ENOMEM; >> + } >> + SET_NETDEV_DEV(ndev, &spi->dev); >> + >> + ax_local = to_ax88796c_device(ndev); >> + memset(ax_local, 0, sizeof(*ax_local)); >> + >> + dev_set_drvdata(&spi->dev, ax_local); >> + ax_local->spi = spi; >> + ax_local->ax_spi.spi = spi; >> + >> + ax_local->ndev = ndev; >> + ax_local->capabilities |= comp ? AX_CAP_COMP : 0; >> + ax_local->msg_enable = msg_enable; >> + >> + ax_local->mdiobus = devm_mdiobus_alloc(&spi->dev); >> + if (!ax_local->mdiobus) { >> + dev_err(&spi->dev, "Could not allocate MDIO bus\n"); >> + return -ENOMEM; >> + } >> + >> + ax_local->mdiobus->priv = ax_local; >> + ax_local->mdiobus->read = ax88796c_mdio_read; >> + ax_local->mdiobus->write = ax88796c_mdio_write; >> + ax_local->mdiobus->name = "ax88976c-mdiobus"; >> + ax_local->mdiobus->phy_mask = ~(1 << 0x10); >> + ax_local->mdiobus->parent = &spi->dev; >> + >> + snprintf(ax_local->mdiobus->id, ARRAY_SIZE(ax_local->mdiobus->id), >> + "ax88796c-spi-%s.%u", dev_name(&spi->dev), spi->chip_select); >> + >> + ret = devm_mdiobus_register(&spi->dev, ax_local->mdiobus); >> + if (ret < 0) { >> + dev_err(&spi->dev, "Could not register MDIO bus\n"); >> + return ret; >> + } >> + >> + if (netif_msg_probe(ax_local)) { >> + dev_info(&spi->dev, "AX88796C-SPI Configuration:\n"); >> + dev_info(&spi->dev, " Compression : %s\n", >> + ax_local->capabilities & AX_CAP_COMP ? "ON" : "OFF"); >> + } >> + >> + ndev->irq = spi->irq; >> + ndev->netdev_ops = &ax88796c_netdev_ops; >> + ndev->ethtool_ops = &ax88796c_ethtool_ops; >> + ndev->hw_features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM; >> + ndev->features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM; >> + ndev->hard_header_len += (TX_OVERHEAD + 4); >> + >> + /* ax88796c gpio reset */ >> + ax88796c_hard_reset(ax_local); >> + >> + /* Reset AX88796C */ >> + ret = ax88796c_soft_reset(ax_local); >> + if (ret < 0) { >> + ret = -ENODEV; >> + goto err; >> + } >> + /* Check board revision */ >> + temp = AX_READ(&ax_local->ax_spi, P2_CRIR); >> + if ((temp & 0xF) != 0x0) { >> + dev_err(&spi->dev, "spi read failed: %d\n", temp); >> + ret = -ENODEV; >> + goto err; >> + } >> + >> + temp = AX_READ(&ax_local->ax_spi, P0_BOR); >> + if (temp == 0x1234) { >> + ax_local->plat_endian = PLAT_LITTLE_ENDIAN; >> + } else { >> + AX_WRITE(&ax_local->ax_spi, 0xFFFF, P0_BOR); >> + ax_local->plat_endian = PLAT_BIG_ENDIAN; >> + } >> + >> + /*Reload EEPROM*/ >> + ax88796c_reload_eeprom(ax_local); >> + >> + ax88796c_load_mac_addr(ndev); >> + >> + if (netif_msg_probe(ax_local)) >> + dev_info(&spi->dev, >> + "irq %d, MAC addr %02X:%02X:%02X:%02X:%02X:%02X\n", >> + ndev->irq, >> + ndev->dev_addr[0], ndev->dev_addr[1], >> + ndev->dev_addr[2], ndev->dev_addr[3], >> + ndev->dev_addr[4], ndev->dev_addr[5]); >> + >> + /* Disable power saving */ >> + AX_WRITE(&ax_local->ax_spi, (AX_READ(&ax_local->ax_spi, P0_PSCR) >> + & PSCR_PS_MASK) | PSCR_PS_D0, P0_PSCR); >> + >> + INIT_WORK(&ax_local->ax_work, ax88796c_work); >> + >> + ax_local->ax_work_queue = >> + create_singlethread_workqueue("ax88796c_work"); >> + >> + mutex_init(&ax_local->spi_lock); >> + >> + skb_queue_head_init(&ax_local->tx_wait_q); >> + >> + ret = devm_register_netdev(&spi->dev, ndev); >> + if (ret) { >> + dev_err(&spi->dev, "failed to register a network device\n"); >> + destroy_workqueue(ax_local->ax_work_queue); >> + goto err; >> + } >> + >> + ax_local->phydev = phy_find_first(ax_local->mdiobus); >> + if (!ax_local->phydev) { >> + dev_err(&spi->dev, "no PHY found\n"); >> + ret = -ENODEV; >> + goto err; >> + } >> + >> + ax_local->phydev->irq = PHY_IGNORE_INTERRUPT; >> + phy_connect_direct(ax_local->ndev, ax_local->phydev, >> + ax88796c_handle_link_change, >> + PHY_INTERFACE_MODE_MII); >> + >> + netif_info(ax_local, probe, ndev, "%s %s registered\n", >> + dev_driver_string(&spi->dev), >> + dev_name(&spi->dev)); >> + phy_attached_info(ax_local->phydev); > > Does the integrated PHY provide a proper PHY ID? Yes. > > Is there a PHY driver for it or do you rely on the genphy driver? > genphy >> + >> + ret = 0; >> +err: >> + return ret; >> +} >> + >> +static int ax88796c_remove(struct spi_device *spi) >> +{ >> + struct ax88796c_device *ax_local = dev_get_drvdata(&spi->dev); >> + struct net_device *ndev = ax_local->ndev; >> + >> + netif_info(ax_local, probe, ndev, "removing network device %s %s\n", >> + dev_driver_string(&spi->dev), >> + dev_name(&spi->dev)); >> + >> + destroy_workqueue(ax_local->ax_work_queue); >> + >> + return 0; >> +} >> + >> +static const struct of_device_id ax88796c_dt_ids[] = { >> + { .compatible = "asix,ax88796c" }, >> + {}, >> +}; >> +MODULE_DEVICE_TABLE(of, ax88796c_dt_ids); >> + >> +static const struct spi_device_id asix_id[] = { >> + { "ax88796c", 0 }, >> + { } >> +}; >> +MODULE_DEVICE_TABLE(spi, asix_id); >> + >> +static struct spi_driver ax88796c_spi_driver = { >> + .driver = { >> + .name = DRV_NAME, >> +#ifdef CONFIG_USE_OF >> + .of_match_table = of_match_ptr(ax88796c_dt_ids), >> +#endif >> + }, >> + .probe = ax88796c_probe, >> + .remove = ax88796c_remove, >> + .id_table = asix_id, >> +}; >> + >> +static __init int ax88796c_spi_init(void) >> +{ >> + int ret; >> + >> + bitmap_zero(ax88796c_no_regs_mask, AX88796C_REGDUMP_LEN); >> + ret = bitmap_parse(no_regs_list, 35, >> + ax88796c_no_regs_mask, AX88796C_REGDUMP_LEN); >> + if (ret) { >> + bitmap_fill(ax88796c_no_regs_mask, AX88796C_REGDUMP_LEN); >> + pr_err("Invalid bitmap description, masking all registers\n"); >> + } >> + >> + return spi_register_driver(&ax88796c_spi_driver); >> +} >> + >> +static __exit void ax88796c_spi_exit(void) >> +{ >> + spi_unregister_driver(&ax88796c_spi_driver); >> +} >> + >> +module_init(ax88796c_spi_init); >> +module_exit(ax88796c_spi_exit); >> + >> +MODULE_AUTHOR("ASIX"); >> +MODULE_DESCRIPTION("ASIX AX88796C SPI Ethernet driver"); >> +MODULE_LICENSE("GPL"); >> + >> +/* ax88796c_phy */ >> diff --git a/drivers/net/ethernet/asix/ax88796c_main.h b/drivers/net/ethernet/asix/ax88796c_main.h >> new file mode 100644 >> index 000000000000..428833978fbf >> --- /dev/null >> +++ b/drivers/net/ethernet/asix/ax88796c_main.h >> @@ -0,0 +1,568 @@ >> +/* SPDX-License-Identifier: GPL-2.0-only */ >> +/* >> + * Copyright (c) 2010 ASIX Electronics Corporation >> + * Copyright (c) 2020 Samsung Electronics >> + * >> + * ASIX AX88796C SPI Fast Ethernet Linux driver >> + */ >> + >> +#ifndef _AX88796C_MAIN_H >> +#define _AX88796C_MAIN_H >> + >> +#include <linux/netdevice.h> >> +#include <linux/mii.h> >> + >> +#include "ax88796c_spi.h" >> + >> +/* These identify the driver base version and may not be removed. */ >> +#define DRV_NAME "ax88796c" >> +#define ADP_NAME "ASIX AX88796C SPI Ethernet Adapter" >> +#define DRV_VERSION "1.2.0" >> + > An benefit in using a separate driver version? Typically the > kernel version is sufficient. > Removed. >> +#define TX_QUEUE_HIGH_WATER 45 /* Tx queue high water mark */ >> +#define TX_QUEUE_LOW_WATER 20 /* Tx queue low water mark */ >> + >> +#define AX88796C_REGDUMP_LEN 256 >> +#define AX88796C_PHY_REGDUMP_LEN 12 >> + >> +#define TX_OVERHEAD 8 >> +#define TX_EOP_SIZE 4 >> + >> +#define AX_MCAST_FILTER_SIZE 8 >> +#define AX_MAX_MCAST 64 >> +#define AX_MAX_CLK 80000000 >> +#define TX_HDR_SOP_DICF 0x8000 >> +#define TX_HDR_SOP_CPHI 0x4000 >> +#define TX_HDR_SOP_INT 0x2000 >> +#define TX_HDR_SOP_MDEQ 0x1000 >> +#define TX_HDR_SOP_PKTLEN 0x07FF >> +#define TX_HDR_SOP_SEQNUM 0xF800 >> +#define TX_HDR_SOP_PKTLENBAR 0x07FF >> + >> +#define TX_HDR_SEG_FS 0x8000 >> +#define TX_HDR_SEG_LS 0x4000 >> +#define TX_HDR_SEG_SEGNUM 0x3800 >> +#define TX_HDR_SEG_SEGLEN 0x0700 >> +#define TX_HDR_SEG_EOFST 0xC000 >> +#define TX_HDR_SEG_SOFST 0x3800 >> +#define TX_HDR_SEG_SEGLENBAR 0x07FF >> + >> +#define TX_HDR_EOP_SEQNUM 0xF800 >> +#define TX_HDR_EOP_PKTLEN 0x07FF >> +#define TX_HDR_EOP_SEQNUMBAR 0xF800 >> +#define TX_HDR_EOP_PKTLENBAR 0x07FF >> + >> +/* Rx header fields mask */ >> +#define RX_HDR1_MCBC 0x8000 >> +#define RX_HDR1_STUFF_PKT 0x4000 >> +#define RX_HDR1_MII_ERR 0x2000 >> +#define RX_HDR1_CRC_ERR 0x1000 >> +#define RX_HDR1_PKT_LEN 0x07FF >> + >> +#define RX_HDR2_SEQ_NUM 0xF800 >> +#define RX_HDR2_PKT_LEN_BAR 0x7FFF >> + >> +#define RX_HDR3_PE 0x8000 >> +#define RX_HDR3_L3_TYPE_IPV4V6 0x6000 >> +#define RX_HDR3_L3_TYPE_IP 0x4000 >> +#define RX_HDR3_L3_TYPE_IPV6 0x2000 >> +#define RX_HDR3_L4_TYPE_ICMPV6 0x1400 >> +#define RX_HDR3_L4_TYPE_TCP 0x1000 >> +#define RX_HDR3_L4_TYPE_IGMP 0x0c00 >> +#define RX_HDR3_L4_TYPE_ICMP 0x0800 >> +#define RX_HDR3_L4_TYPE_UDP 0x0400 >> +#define RX_HDR3_L3_ERR 0x0200 >> +#define RX_HDR3_L4_ERR 0x0100 >> +#define RX_HDR3_PRIORITY(x) ((x) << 4) >> +#define RX_HDR3_STRIP 0x0008 >> +#define RX_HDR3_VLAN_ID 0x0007 >> + >> +enum watchdog_state { >> + chk_link = 0, >> + chk_cable, >> + ax_nop, >> +}; >> + >> +struct ax88796c_device { >> + struct resource *addr_res; /* resources found */ >> + struct resource *addr_req; /* resources requested */ >> + struct resource *irq_res; >> + >> + struct spi_device *spi; >> + struct net_device *ndev; >> + struct net_device_stats stats; >> + >> + struct timer_list watchdog; >> + enum watchdog_state w_state; >> + size_t w_ticks; >> + >> + struct work_struct ax_work; >> + struct workqueue_struct *ax_work_queue; >> + struct tasklet_struct bh; >> + >> + struct mutex spi_lock; /* device access */ >> + >> + struct sk_buff_head tx_wait_q; >> + >> + struct axspi_data ax_spi; >> + >> + struct mii_bus *mdiobus; >> + struct phy_device *phydev; >> + >> + int msg_enable; >> + >> + u16 seq_num; >> + >> + u8 multi_filter[AX_MCAST_FILTER_SIZE]; >> + >> + unsigned long capabilities; >> + #define AX_CAP_DMA 1 >> + #define AX_CAP_COMP 2 >> + #define AX_CAP_BIDIR 4 >> + >> + u8 plat_endian; >> + #define PLAT_LITTLE_ENDIAN 0 >> + #define PLAT_BIG_ENDIAN 1 >> + >> + unsigned long flags; >> + #define EVENT_INTR 1 >> + #define EVENT_TX 2 >> + #define EVENT_SET_MULTI 4 >> + >> +}; >> + >> +#define to_ax88796c_device(ndev) ((struct ax88796c_device *)netdev_priv(ndev)) >> + > Is this helper really needed? in The places I've seen you can assign > the void* pointer returned netdev_priv(). > As I said, it was not my code. I am porting it and this has worked. It doesn't look that bad IMHO. Do you think I should change it? Thanks for the feedback.
It was <2020-10-02 pią 22:36>, when Andrew Lunn wrote: >> +static u32 ax88796c_get_link(struct net_device *ndev) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + >> + mutex_lock(&ax_local->spi_lock); >> + >> + phy_read_status(ndev->phydev); >> + >> + mutex_unlock(&ax_local->spi_lock); > > Why do you take this mutux before calling phy_read_status()? The > phylib core will not be taking this mutex when it calls into the PHY > driver. This applies to all the calls you have with phy_ > I need to review the use of this mutex. Thanks for spotting. > There should not be any need to call phy_read_status(). phylib will do > this once per second, or after any interrupt from the PHY. so just use > > phydev->link > Using ethtool_op_get_link() >> +static void >> +ax88796c_get_regs(struct net_device *ndev, struct ethtool_regs *regs, void *_p) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + u16 *p = _p; >> + int offset, i; >> + >> + memset(p, 0, AX88796C_REGDUMP_LEN); >> + >> + 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++; >> + } >> + >> + for (i = 0; i < AX88796C_PHY_REGDUMP_LEN / 2; i++) { >> + *p = phy_read(ax_local->phydev, i); >> + p++; > > Depending on the PHY, that can be dangerous. This is a built-in generic PHY. The chip has no lines to attach any other external one. > phylib could be busy doing things with the PHY. It could be looking at How does phylib prevent concurrent access to a PHY? > a different page for example. Different page? > miitool(1) can give you the same functionally without the MAC driver > doing anything, other than forwarding the IOCTL call on. No, I am afraid mii-tool is not able to dump registers. I am not insisting on dumping PHY registeres but I think it is nice to have them. Intel drivers do it. >> +int ax88796c_mdio_read(struct mii_bus *mdiobus, int phy_id, int loc) >> +{ >> + struct ax88796c_device *ax_local = mdiobus->priv; >> + int ret; >> + >> + 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) >> + return -EBUSY; > > Return whatever read_poll_timeout() returned. It is probably > -ETIMEDOUT, but it could also be -EIO for example. Indeed it is -ETIMEDOUT. Returning ret. >> +ax88796c_mdio_write(struct mii_bus *mdiobus, int phy_id, int loc, u16 val) >> +{ >> + struct ax88796c_device *ax_local = mdiobus->priv; >> + int ret; >> + >> + 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); >> + if (ret) >> + return -EIO; >> + >> + if (loc == MII_ADVERTISE) { >> + AX_WRITE(&ax_local->ax_spi, (BMCR_FULLDPLX | BMCR_ANRESTART | >> + BMCR_ANENABLE | BMCR_SPEED100), P2_MDIODR); >> + AX_WRITE(&ax_local->ax_spi, (MDIOCR_RADDR(MII_BMCR) | >> + MDIOCR_FADDR(phy_id) | MDIOCR_WRITE), >> + P2_MDIOCR); >> > > What is this doing? > Well… it turns autonegotiation when changing advertised link modes. But this is obvious. As to why this code is here, I will honestly say — I am not sure (Reminder: this is a vendor driver I am porting, I am more than happy to receive any comments, thank you). Apparently it is not required and I am willing to remove it. It could be of some use when the driver didn't use phylib. >> + ret = read_poll_timeout(AX_READ, ret, >> + ((ret & MDIOCR_VALID) != 0), 0, >> + jiffies_to_usecs(HZ / 100), false, >> + &ax_local->ax_spi, P2_MDIOCR); >> + if (ret) >> + return -EIO; >> + } >> + >> + return 0; >> +} > >> +static char *no_regs_list = "80018001,e1918001,8001a001,fc0d0000"; >> +unsigned long ax88796c_no_regs_mask[AX88796C_REGDUMP_LEN / (sizeof(unsigned long) * 8)]; >> + >> +module_param(comp, int, 0444); >> +MODULE_PARM_DESC(comp, "0=Non-Compression Mode, 1=Compression Mode"); >> + >> +module_param(msg_enable, int, 0444); >> +MODULE_PARM_DESC(msg_enable, "Message mask (see linux/netdevice.h for bitmap)"); > > No module parameters allowed, not in netdev. > >> +static int ax88796c_reload_eeprom(struct ax88796c_device *ax_local) >> +{ >> + int ret; >> + >> + AX_WRITE(&ax_local->ax_spi, EECR_RELOAD, P3_EECR); >> + >> + ret = read_poll_timeout(AX_READ, ret, >> + (ret & PSR_DEV_READY), >> + 0, jiffies_to_usecs(2 * HZ / 1000), false, >> + &ax_local->ax_spi, P0_PSR); >> + if (ret) { >> + dev_err(&ax_local->spi->dev, >> + "timeout waiting for reload eeprom\n"); >> + return -1; > > return ret not EINVAL which is -1 > Done. >> +static int ax88796c_set_mac_address(struct net_device *ndev, void *p) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + struct sockaddr *addr = p; >> + >> + if (!is_valid_ether_addr(addr->sa_data)) >> + return -EADDRNOTAVAIL; > > It would be better to just use eth_mac_addr(). > Done. >> +static int >> +ax88796c_check_free_pages(struct ax88796c_device *ax_local, u8 need_pages) >> +{ >> + u8 free_pages; >> + u16 tmp; >> + >> + free_pages = AX_READ(&ax_local->ax_spi, P0_TFBFCR) & TX_FREEBUF_MASK; >> + if (free_pages < need_pages) { >> + /* schedule free page interrupt */ >> + tmp = AX_READ(&ax_local->ax_spi, P0_TFBFCR) >> + & TFBFCR_SCHE_FREE_PAGE; >> + AX_WRITE(&ax_local->ax_spi, tmp | TFBFCR_TX_PAGE_SET | >> + TFBFCR_SET_FREE_PAGE(need_pages), >> + P0_TFBFCR); >> + return -ENOMEM; >> + } >> + >> + return 0; >> +} >> + >> +static struct sk_buff * >> +ax88796c_tx_fixup(struct net_device *ndev, struct sk_buff_head *q) >> +{ >> + if (netif_msg_pktdata(ax_local)) { >> + char pfx[IFNAMSIZ + 7]; >> + >> + snprintf(pfx, sizeof(pfx), "%s: ", ndev->name); >> + >> + netdev_info(ndev, "TX packet len %d, total len %d, seq %d\n", >> + pkt_len, tx_skb->len, seq_num); >> + >> + netdev_info(ndev, " SPI Header:\n"); >> + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, >> + tx_skb->data, 4, 0); >> + >> + netdev_info(ndev, " TX SOP:\n"); >> + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, >> + tx_skb->data + 4, TX_OVERHEAD, 0); >> + >> + netdev_info(ndev, " TX packet:\n"); >> + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, >> + tx_skb->data + 4 + TX_OVERHEAD, >> + tx_skb->len - TX_EOP_SIZE - 4 - TX_OVERHEAD, 0); >> + >> + netdev_info(ndev, " TX EOP:\n"); >> + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, >> + tx_skb->data + tx_skb->len - 4, 4, 0); >> + } > > I expect others are going to ask you to remove this. > You mean dumping packets? I will if they do. What is pktdata flag for then? >> +static void ax88796c_handle_link_change(struct net_device *ndev) >> +{ >> + if (net_ratelimit()) >> + phy_print_status(ndev->phydev); >> +} >> + >> +void ax88796c_phy_init(struct ax88796c_device *ax_local) >> +{ >> + /* Enable PHY auto-polling */ >> + AX_WRITE(&ax_local->ax_spi, >> + PCR_PHYID(0x10) | PCR_POLL_EN | >> + PCR_POLL_FLOWCTRL | PCR_POLL_BMCR, P2_PCR); > > Auto-polling of the PHY is generally a bad idea. The hardware is not > going to respect the phydev->lock mutex, for example. Disable this, > and add a proper ax88796c_handle_link_change(). > Done. >> +static int >> +ax88796c_open(struct net_device *ndev) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + int ret; >> + unsigned long irq_flag = IRQF_SHARED; >> + >> + mutex_lock(&ax_local->spi_lock); >> + >> + ret = ax88796c_soft_reset(ax_local); >> + if (ret < 0) >> + return -ENODEV; >> + >> + ret = request_irq(ndev->irq, ax88796c_interrupt, >> + irq_flag, ndev->name, ndev); > > Maybe look at using request_threaded_irq(). You can then remove your > work queue, and do the work in the thread_fn. > There is other work beeing done in the work queue too. >> + if (ret) { >> + netdev_err(ndev, "unable to get IRQ %d (errno=%d).\n", >> + ndev->irq, ret); >> + return -ENXIO; > > return ret; > > In general, never change a return code unless you have a really good > reason why. And if you do have a reason, document it. > OK, Done. >> +static int >> +ax88796c_close(struct net_device *ndev) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + >> + netif_stop_queue(ndev); >> + >> + free_irq(ndev->irq, ndev); >> + >> + phy_stop(ndev->phydev); >> + >> + mutex_lock(&ax_local->spi_lock); >> + >> + AX_WRITE(&ax_local->ax_spi, IMR_MASKALL, P0_IMR); >> + ax88796c_free_skb_queue(&ax_local->tx_wait_q); >> + >> + ax88796c_soft_reset(ax_local); >> + >> + mutex_unlock(&ax_local->spi_lock); >> + netif_carrier_off(ndev); > > phy_stop() will do that for you. > Removed. >> +static int ax88796c_probe(struct spi_device *spi) >> +{ > >> + ax_local->mdiobus->priv = ax_local; >> + ax_local->mdiobus->read = ax88796c_mdio_read; >> + ax_local->mdiobus->write = ax88796c_mdio_write; >> + ax_local->mdiobus->name = "ax88976c-mdiobus"; >> + ax_local->mdiobus->phy_mask = ~(1 << 0x10); > > BIT(0x10); > Done. >> + >> + ret = devm_register_netdev(&spi->dev, ndev); >> + if (ret) { >> + dev_err(&spi->dev, "failed to register a network device\n"); >> + destroy_workqueue(ax_local->ax_work_queue); >> + goto err; >> + } > > The device is not live. If this is being used for NFS root, the kernel > will start using it. So what sort of mess will it get into, if there > is no PHY yet? Nothing important should happen after register_netdev(). > But, with an unregistered network device ndev_owner in phy_attach_direct() is NULL. Thus, phy_connect_direct() below fails. --8<---------------cut here---------------start------------->8--- 1332 if (dev) 1333 ndev_owner = dev->dev.parent->driver->owner; 1334 if (ndev_owner != bus->owner && !try_module_get(bus->owner)) { 1335 phydev_err(phydev, "failed to get the bus module\n"); 1336 return -EIO; 1337 } --8<---------------cut here---------------end--------------->8--- >> + >> + ax_local->phydev = phy_find_first(ax_local->mdiobus); >> + if (!ax_local->phydev) { >> + dev_err(&spi->dev, "no PHY found\n"); >> + ret = -ENODEV; >> + goto err; >> + } >> + >> + ax_local->phydev->irq = PHY_IGNORE_INTERRUPT; >> + phy_connect_direct(ax_local->ndev, ax_local->phydev, >> + ax88796c_handle_link_change, >> + PHY_INTERFACE_MODE_MII); >> + >> + netif_info(ax_local, probe, ndev, "%s %s registered\n", >> + dev_driver_string(&spi->dev), >> + dev_name(&spi->dev)); >> + phy_attached_info(ax_local->phydev); >> + >> + ret = 0; >> +err: >> + return ret; >> +} >> + >> +static int ax88796c_remove(struct spi_device *spi) >> +{ >> + struct ax88796c_device *ax_local = dev_get_drvdata(&spi->dev); >> + struct net_device *ndev = ax_local->ndev; > > You might want to disconnect the PHY. > I do (-; Done. >> + >> + netif_info(ax_local, probe, ndev, "removing network device %s %s\n", >> + dev_driver_string(&spi->dev), >> + dev_name(&spi->dev)); >> + >> + destroy_workqueue(ax_local->ax_work_queue); >> + >> + return 0; >> +} >> + >> +static const struct of_device_id ax88796c_dt_ids[] = { >> + { .compatible = "asix,ax88796c" }, >> + {}, >> +}; >> +MODULE_DEVICE_TABLE(of, ax88796c_dt_ids); >> + >> +static const struct spi_device_id asix_id[] = { >> + { "ax88796c", 0 }, >> + { } >> +}; >> +MODULE_DEVICE_TABLE(spi, asix_id); >> + >> +static struct spi_driver ax88796c_spi_driver = { >> + .driver = { >> + .name = DRV_NAME, >> +#ifdef CONFIG_USE_OF >> + .of_match_table = of_match_ptr(ax88796c_dt_ids), >> +#endif > > I don't think you need the #ifdef. > Indeed, it appears to be an uncommon practice to use it in this context. Done. >> +#ifndef _AX88796C_MAIN_H >> +#define _AX88796C_MAIN_H >> + >> +#include <linux/netdevice.h> >> +#include <linux/mii.h> >> + >> +#include "ax88796c_spi.h" >> + >> +/* These identify the driver base version and may not be removed. */ >> +#define DRV_NAME "ax88796c" >> +#define ADP_NAME "ASIX AX88796C SPI Ethernet Adapter" >> +#define DRV_VERSION "1.2.0" > > DRV_VERSION are pretty pointless. Not sure you use it anyway. Please > remove. > Done. >> + unsigned long capabilities; >> + #define AX_CAP_DMA 1 >> + #define AX_CAP_COMP 2 >> + #define AX_CAP_BIDIR 4 > > BIT(0), BIT(1), BIT(2)... > No problem. Do you have any recommendation how to express this #define PSR_RESET (0 << 15) I know it equals 0, but shows explicitly the bit number. >> +struct skb_data; >> + >> +struct skb_data { >> + enum skb_state state; >> + struct net_device *ndev; >> + struct sk_buff *skb; >> + size_t len; >> + dma_addr_t phy_addr; >> +}; > > A forward definition, followed by the real definition? > There must have been something in between. Done. >> + #define FER_IPALM (1 << 0) >> + #define FER_DCRC (1 << 1) >> + #define FER_RH3M (1 << 2) >> + #define FER_HEADERSWAP (1 << 7) >> + #define FER_WSWAP (1 << 8) >> + #define FER_BSWAP (1 << 9) >> + #define FER_INTHI (1 << 10) >> + #define FER_INTLO (0 << 10) >> + #define FER_IRQ_PULL (1 << 11) >> + #define FER_RXEN (1 << 14) >> + #define FER_TXEN (1 << 15) > > Isn't checkpatch giving warnings and suggesting BIT? Not exactly. It gives green CHECK messages, which I decided to ignore. Apparently a wrong move. Thanks for the feedback.
> >> +static void > >> +ax88796c_get_regs(struct net_device *ndev, struct ethtool_regs *regs, void *_p) > >> +{ > >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); > >> + u16 *p = _p; > >> + int offset, i; > >> + > >> + memset(p, 0, AX88796C_REGDUMP_LEN); > >> + > >> + 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++; > >> + } > >> + > >> + for (i = 0; i < AX88796C_PHY_REGDUMP_LEN / 2; i++) { > >> + *p = phy_read(ax_local->phydev, i); > >> + p++; > > > > Depending on the PHY, that can be dangerous. > > This is a built-in generic PHY. The chip has no lines to attach any > other external one. > > > phylib could be busy doing things with the PHY. It could be looking at > > How does phylib prevent concurrent access to a PHY? phydev->lock. All access to the PHY should go through the phylib, which will take the lock before calling into the driver. > > a different page for example. > > Different page? I was talking about the general case. A number of PHYs have more than 32 registers. So they implement pages to give access to more registers. For that to work, you need to ensure you don't have concurrent access. > > miitool(1) can give you the same functionally without the MAC driver > > doing anything, other than forwarding the IOCTL call on. > > No, I am afraid mii-tool is not able to dump registers. It should be able to. sudo mii-tool -vv eth0 Using SIOCGMIIPHY=0x8947 eth0: negotiated 1000baseT-FD flow-control, link ok registers for MII PHY 0: 1040 79ed 001c c800 0de1 c5e1 006d 0000 0000 0200 0800 0000 0000 0000 0000 2000 0000 0000 ffff 0000 0000 0400 0f00 0f00 318b 0053 31ec 8012 bf1f 0000 0000 0000 product info: vendor 00:07:32, model 0 rev 0 basic mode: autonegotiation enabled basic status: autonegotiation complete, link ok capabilities: 1000baseT-FD 100baseTx-FD 100baseTx-HD 10baseT-FD 10baseT-HD advertising: 1000baseT-FD 100baseTx-FD 100baseTx-HD 10baseT-FD 10baseT-HD flow-control link partner: 1000baseT-FD 100baseTx-FD 100baseTx-HD 10baseT-FD 10baseT-HD flow-control > >> +ax88796c_mdio_write(struct mii_bus *mdiobus, int phy_id, int loc, u16 val) > >> +{ > >> + struct ax88796c_device *ax_local = mdiobus->priv; > >> + int ret; > >> + > >> + 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); > >> + if (ret) > >> + return -EIO; > >> + > >> + if (loc == MII_ADVERTISE) { > >> + AX_WRITE(&ax_local->ax_spi, (BMCR_FULLDPLX | BMCR_ANRESTART | > >> + BMCR_ANENABLE | BMCR_SPEED100), P2_MDIODR); > >> + AX_WRITE(&ax_local->ax_spi, (MDIOCR_RADDR(MII_BMCR) | > >> + MDIOCR_FADDR(phy_id) | MDIOCR_WRITE), > >> + P2_MDIOCR); > >> > > > > What is this doing? > > > > Well… it turns autonegotiation when changing advertised link modes. But > this is obvious. As to why this code is here, I will honestly say — I am > not sure (Reminder: this is a vendor driver I am porting, I am more than > happy to receive any comments, thank you). Apparently it is not required > and I am willing to remove it. Please do remove it. > >> + > >> + ret = devm_register_netdev(&spi->dev, ndev); > >> + if (ret) { > >> + dev_err(&spi->dev, "failed to register a network device\n"); > >> + destroy_workqueue(ax_local->ax_work_queue); > >> + goto err; > >> + } > > > > The device is not live. If this is being used for NFS root, the kernel > > will start using it. So what sort of mess will it get into, if there > > is no PHY yet? Nothing important should happen after register_netdev(). > > > > But, with an unregistered network device ndev_owner in > phy_attach_direct() is NULL. Thus, phy_connect_direct() below fails. > > --8<---------------cut here---------------start------------->8--- > 1332 if (dev) > 1333 ndev_owner = dev->dev.parent->driver->owner; > 1334 if (ndev_owner != bus->owner && !try_module_get(bus->owner)) { > 1335 phydev_err(phydev, "failed to get the bus module\n"); > 1336 return -EIO; > 1337 } > --8<---------------cut here---------------end--------------->8--- Which is probably why most drivers actually attach the PHY in open() and detach it in close(). It can be done in probe, just look around for a driver which does and copy it. > No problem. Do you have any recommendation how to express this > > #define PSR_RESET (0 << 15) > I know it equals 0, but shows explicitly the bit number. Yes, that is useful for documentation. How about: #define PSR_NOT_RESET BIT(15) And then turn the logic around. Andrew
It was <2020-10-16 pią 20:01>, when Andrew Lunn wrote: >> >> +static void >> >> +ax88796c_get_regs(struct net_device *ndev, struct ethtool_regs *regs, void *_p) >> >> +{ >> >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> >> + u16 *p = _p; >> >> + int offset, i; >> >> + >> >> + memset(p, 0, AX88796C_REGDUMP_LEN); >> >> + >> >> + 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++; >> >> + } >> >> + >> >> + for (i = 0; i < AX88796C_PHY_REGDUMP_LEN / 2; i++) { >> >> + *p = phy_read(ax_local->phydev, i); >> >> + p++; >> > >> > Depending on the PHY, that can be dangerous. >> >> This is a built-in generic PHY. The chip has no lines to attach any >> other external one. >> >> > phylib could be busy doing things with the PHY. It could be looking at >> >> How does phylib prevent concurrent access to a PHY? > > phydev->lock. All access to the PHY should go through the phylib, > which will take the lock before calling into the driver. > >> > a different page for example. >> >> Different page? > > I was talking about the general case. A number of PHYs have more than > 32 registers. So they implement pages to give access to more > registers. For that to work, you need to ensure you don't have > concurrent access. > It is not the case, this one has got only 7 and one only needs to make sure the transaction in ax88796c_mdio_read() is not messed up. >> > miitool(1) can give you the same functionally without the MAC driver >> > doing anything, other than forwarding the IOCTL call on. >> >> No, I am afraid mii-tool is not able to dump registers. > > It should be able to. > > sudo mii-tool -vv eth0 > Using SIOCGMIIPHY=0x8947 > eth0: negotiated 1000baseT-FD flow-control, link ok > registers for MII PHY 0: > 1040 79ed 001c c800 0de1 c5e1 006d 0000 > 0000 0200 0800 0000 0000 0000 0000 2000 > 0000 0000 ffff 0000 0000 0400 0f00 0f00 > 318b 0053 31ec 8012 bf1f 0000 0000 0000 > product info: vendor 00:07:32, model 0 rev 0 > basic mode: autonegotiation enabled > basic status: autonegotiation complete, link ok > capabilities: 1000baseT-FD 100baseTx-FD 100baseTx-HD 10baseT-FD 10baseT-HD > advertising: 1000baseT-FD 100baseTx-FD 100baseTx-HD 10baseT-FD 10baseT-HD flow-control > link partner: 1000baseT-FD 100baseTx-FD 100baseTx-HD 10baseT-FD 10baseT-HD flow-control Indeed. However, mii-tool(1) simply calls ioctl(SIOCGMIIREG) number of times. Is looping in the userspace any better than in kernel? Anyway, thanks for pointing out possible problems, I will make sure to avoid them. >> >> +ax88796c_mdio_write(struct mii_bus *mdiobus, int phy_id, int loc, u16 val) >> >> +{ >> >> + struct ax88796c_device *ax_local = mdiobus->priv; >> >> + int ret; >> >> + >> >> + 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); >> >> + if (ret) >> >> + return -EIO; >> >> + >> >> + if (loc == MII_ADVERTISE) { >> >> + AX_WRITE(&ax_local->ax_spi, (BMCR_FULLDPLX | BMCR_ANRESTART | >> >> + BMCR_ANENABLE | BMCR_SPEED100), P2_MDIODR); >> >> + AX_WRITE(&ax_local->ax_spi, (MDIOCR_RADDR(MII_BMCR) | >> >> + MDIOCR_FADDR(phy_id) | MDIOCR_WRITE), >> >> + P2_MDIOCR); >> >> >> > >> > What is this doing? >> > >> >> Well… it turns autonegotiation when changing advertised link modes. But >> this is obvious. As to why this code is here, I will honestly say — I am >> not sure (Reminder: this is a vendor driver I am porting, I am more than >> happy to receive any comments, thank you). Apparently it is not required >> and I am willing to remove it. > > Please do remove it. > Done. >> >> + >> >> + ret = devm_register_netdev(&spi->dev, ndev); >> >> + if (ret) { >> >> + dev_err(&spi->dev, "failed to register a network device\n"); >> >> + destroy_workqueue(ax_local->ax_work_queue); >> >> + goto err; >> >> + } >> > >> > The device is not live. If this is being used for NFS root, the kernel >> > will start using it. So what sort of mess will it get into, if there >> > is no PHY yet? Nothing important should happen after register_netdev(). >> > >> >> But, with an unregistered network device ndev_owner in >> phy_attach_direct() is NULL. Thus, phy_connect_direct() below fails. >> >> --8<---------------cut here---------------start------------->8--- >> 1332 if (dev) >> 1333 ndev_owner = dev->dev.parent->driver->owner; >> 1334 if (ndev_owner != bus->owner && !try_module_get(bus->owner)) { >> 1335 phydev_err(phydev, "failed to get the bus module\n"); >> 1336 return -EIO; >> 1337 } >> --8<---------------cut here---------------end--------------->8--- > > Which is probably why most drivers actually attach the PHY in open() > and detach it in close(). > > It can be done in probe, just look around for a driver which does and > copy it. > I will. Thanks for the info. >> No problem. Do you have any recommendation how to express this >> >> #define PSR_RESET (0 << 15) >> >> I know it equals 0, but shows explicitly the bit number. > > Yes, that is useful for documentation. How about: > > #define PSR_NOT_RESET BIT(15) > > And then turn the logic around. OK. This is an idea. I'll look around some more.
It was <2020-10-02 pią 22:36>, when Andrew Lunn wrote: >> +static int >> +ax88796c_open(struct net_device *ndev) >> +{ >> + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); >> + int ret; >> + unsigned long irq_flag = IRQF_SHARED; >> + >> + mutex_lock(&ax_local->spi_lock); >> + >> + ret = ax88796c_soft_reset(ax_local); >> + if (ret < 0) >> + return -ENODEV; >> + >> + ret = request_irq(ndev->irq, ax88796c_interrupt, >> + irq_flag, ndev->name, ndev); > > Maybe look at using request_threaded_irq(). You can then remove your > work queue, and do the work in the thread_fn. I looked and I looked and I didn't see how I could reasonably manage asynchronous start_xmit calls, the work queue also supports at the moment. Asynchronous start_xmit is important because data are transmitted via SPI which isn't very fast and it seems better not to block userspace processes that are sending data. Having both irq and xmit in the same place doesn't seem bad. Do you have any recommendations?
> I looked and I looked and I didn't see how I could reasonably manage > asynchronous start_xmit calls, the work queue also supports at the > moment. O.K, keep it, since it has other uses. If it was just for interrupt handling, threaded IRQs could of simplified the code. But it looks like you cannot make that simplification. Andrew
diff --git a/MAINTAINERS b/MAINTAINERS index deaafb617361..654eb0127479 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2822,6 +2822,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,ax99706c-spi.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) diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index de50e8b9e656..f3b218e45ea5 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -32,6 +32,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/aurora/Kconfig" source "drivers/net/ethernet/broadcom/Kconfig" diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index f8f38dcb5f8a..9eb368d93607 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -18,6 +18,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_AURORA) += aurora/ obj-$(CONFIG_NET_VENDOR_CADENCE) += cadence/ diff --git a/drivers/net/ethernet/asix/Kconfig b/drivers/net/ethernet/asix/Kconfig new file mode 100644 index 000000000000..7caa45607450 --- /dev/null +++ b/drivers/net/ethernet/asix/Kconfig @@ -0,0 +1,21 @@ +# +# 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" + depends on SPI + depends on GPIOLIB + help + Say Y here if you intend to use ASIX AX88796C attached in SPI mode. + +endif # NET_VENDOR_ASIX diff --git a/drivers/net/ethernet/asix/Makefile b/drivers/net/ethernet/asix/Makefile new file mode 100644 index 000000000000..0bfbbb042634 --- /dev/null +++ b/drivers/net/ethernet/asix/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the Asix network device drivers. +# + +obj-$(CONFIG_SPI_AX88796C) += ax88796c.o +ax88796c-y := ax88796c_main.o ax88796c_ioctl.o ax88796c_spi.o diff --git a/drivers/net/ethernet/asix/ax88796c_ioctl.c b/drivers/net/ethernet/asix/ax88796c_ioctl.c new file mode 100644 index 000000000000..b3e3ac96790d --- /dev/null +++ b/drivers/net/ethernet/asix/ax88796c_ioctl.c @@ -0,0 +1,241 @@ +// 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 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_link(struct net_device *ndev) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + + mutex_lock(&ax_local->spi_lock); + + phy_read_status(ndev->phydev); + + mutex_unlock(&ax_local->spi_lock); + + return ndev->phydev->link; +} + +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 int +ax88796c_get_link_ksettings(struct net_device *ndev, + struct ethtool_link_ksettings *cmd) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + int ret; + + mutex_lock(&ax_local->spi_lock); + + ret = phy_ethtool_get_link_ksettings(ndev, cmd); + + mutex_unlock(&ax_local->spi_lock); + + return ret; +} + +static int +ax88796c_set_link_ksettings(struct net_device *ndev, + const struct ethtool_link_ksettings *cmd) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + int ret; + + mutex_lock(&ax_local->spi_lock); + + ret = phy_ethtool_set_link_ksettings(ndev, cmd); + + mutex_unlock(&ax_local->spi_lock); + + return ret; +} + +static int ax88796c_nway_reset(struct net_device *ndev) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + int ret; + + mutex_lock(&ax_local->spi_lock); + + ret = phy_ethtool_nway_reset(ndev); + + mutex_unlock(&ax_local->spi_lock); + + return ret; +} + +static u32 ax88796c_ethtool_getmsglevel(struct net_device *ndev) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + + return ax_local->msg_enable; +} + +static void ax88796c_ethtool_setmsglevel(struct net_device *ndev, u32 level) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + + ax_local->msg_enable = level; +} + +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); + u16 *p = _p; + int offset, i; + + memset(p, 0, AX88796C_REGDUMP_LEN); + + 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++; + } + + for (i = 0; i < AX88796C_PHY_REGDUMP_LEN / 2; i++) { + *p = phy_read(ax_local->phydev, i); + p++; + } +} + +int ax88796c_mdio_read(struct mii_bus *mdiobus, int phy_id, int loc) +{ + struct ax88796c_device *ax_local = mdiobus->priv; + int ret; + + 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) + return -EBUSY; + + return AX_READ(&ax_local->ax_spi, P2_MDIODR); +} + +int +ax88796c_mdio_write(struct mii_bus *mdiobus, int phy_id, int loc, u16 val) +{ + struct ax88796c_device *ax_local = mdiobus->priv; + int ret; + + 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); + if (ret) + return -EIO; + + if (loc == MII_ADVERTISE) { + AX_WRITE(&ax_local->ax_spi, (BMCR_FULLDPLX | BMCR_ANRESTART | + BMCR_ANENABLE | BMCR_SPEED100), P2_MDIODR); + AX_WRITE(&ax_local->ax_spi, (MDIOCR_RADDR(MII_BMCR) | + 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); + if (ret) + return -EIO; + } + + return 0; +} + +void ax88796c_set_csums(struct ax88796c_device *ax_local) +{ + struct net_device *ndev = ax_local->ndev; + + if (ndev->features & NETIF_F_RXCSUM) { + AX_WRITE(&ax_local->ax_spi, COERCR0_DEFAULT, P4_COERCR0); + AX_WRITE(&ax_local->ax_spi, COERCR1_DEFAULT, P4_COERCR1); + } else { + AX_WRITE(&ax_local->ax_spi, 0, P4_COERCR0); + AX_WRITE(&ax_local->ax_spi, 0, P4_COERCR1); + } + + if (ndev->features & NETIF_F_HW_CSUM) { + AX_WRITE(&ax_local->ax_spi, COETCR0_DEFAULT, P4_COETCR0); + AX_WRITE(&ax_local->ax_spi, COETCR1_TXPPPE, P4_COETCR1); + } else { + AX_WRITE(&ax_local->ax_spi, 0, P4_COETCR0); + AX_WRITE(&ax_local->ax_spi, 0, P4_COETCR1); + } +} + +const struct ethtool_ops ax88796c_ethtool_ops = { + .get_drvinfo = ax88796c_get_drvinfo, + .get_link = ax88796c_get_link, + .get_msglevel = ax88796c_get_msglevel, + .set_msglevel = ax88796c_set_msglevel, + .get_link_ksettings = ax88796c_get_link_ksettings, + .set_link_ksettings = ax88796c_set_link_ksettings, + .nway_reset = ax88796c_nway_reset, + .get_msglevel = ax88796c_ethtool_getmsglevel, + .set_msglevel = ax88796c_ethtool_setmsglevel, + .get_regs_len = ax88796c_get_regs_len, + .get_regs = ax88796c_get_regs, +}; + +int ax88796c_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + int ret; + + mutex_lock(&ax_local->spi_lock); + + ret = phy_mii_ioctl(ndev->phydev, ifr, cmd); + + mutex_unlock(&ax_local->spi_lock); + + return ret; +} diff --git a/drivers/net/ethernet/asix/ax88796c_ioctl.h b/drivers/net/ethernet/asix/ax88796c_ioctl.h new file mode 100644 index 000000000000..d478981bf995 --- /dev/null +++ b/drivers/net/ethernet/asix/ax88796c_ioctl.h @@ -0,0 +1,27 @@ +/* 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); +void ax88796c_set_csums(struct ax88796c_device *ax_local); +int ax88796c_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd); + +#endif diff --git a/drivers/net/ethernet/asix/ax88796c_main.c b/drivers/net/ethernet/asix/ax88796c_main.c new file mode 100644 index 000000000000..2148ea01362a --- /dev/null +++ b/drivers/net/ethernet/asix/ax88796c_main.c @@ -0,0 +1,1041 @@ +// 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 "ax88796c_main.h" +#include "ax88796c_ioctl.h" + +#include <linux/bitmap.h> +#include <linux/etherdevice.h> +#include <linux/iopoll.h> +#include <linux/mdio.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/of.h> +#include <linux/phy.h> +#include <linux/spi/spi.h> + +static int comp; +static int msg_enable = NETIF_MSG_PROBE | + NETIF_MSG_LINK | + /* NETIF_MSG_TIMER | */ + /* NETIF_MSG_IFDOWN | */ + /* NETIF_MSG_IFUP | */ + NETIF_MSG_RX_ERR | + NETIF_MSG_TX_ERR | + /* NETIF_MSG_TX_QUEUED | */ + /* NETIF_MSG_INTR | */ + /* NETIF_MSG_TX_DONE | */ + /* NETIF_MSG_RX_STATUS | */ + /* NETIF_MSG_PKTDATA | */ + /* NETIF_MSG_HW | */ + /* NETIF_MSG_WOL | */ + 0; + +static char *no_regs_list = "80018001,e1918001,8001a001,fc0d0000"; +unsigned long ax88796c_no_regs_mask[AX88796C_REGDUMP_LEN / (sizeof(unsigned long) * 8)]; + +module_param(comp, int, 0444); +MODULE_PARM_DESC(comp, "0=Non-Compression Mode, 1=Compression Mode"); + +module_param(msg_enable, int, 0444); +MODULE_PARM_DESC(msg_enable, "Message mask (see linux/netdevice.h for bitmap)"); + +static int ax88796c_soft_reset(struct ax88796c_device *ax_local) +{ + u16 temp; + int ret; + + AX_WRITE(&ax_local->ax_spi, PSR_RESET, P0_PSR); + AX_WRITE(&ax_local->ax_spi, PSR_RESET_CLR, P0_PSR); + + ret = read_poll_timeout(AX_READ, ret, + (ret & PSR_DEV_READY), + 0, jiffies_to_usecs(160 * HZ / 1000), false, + &ax_local->ax_spi, P0_PSR); + if (ret) + return -1; + + temp = AX_READ(&ax_local->ax_spi, P4_SPICR); + if (ax_local->capabilities & AX_CAP_COMP) { + AX_WRITE(&ax_local->ax_spi, + (temp | SPICR_RCEN | SPICR_QCEN), P4_SPICR); + ax_local->ax_spi.comp = 1; + } else { + AX_WRITE(&ax_local->ax_spi, + (temp & ~(SPICR_RCEN | SPICR_QCEN)), P4_SPICR); + ax_local->ax_spi.comp = 0; + } + + return 0; +} + +static int ax88796c_reload_eeprom(struct ax88796c_device *ax_local) +{ + int ret; + + AX_WRITE(&ax_local->ax_spi, EECR_RELOAD, P3_EECR); + + ret = read_poll_timeout(AX_READ, ret, + (ret & PSR_DEV_READY), + 0, jiffies_to_usecs(2 * HZ / 1000), false, + &ax_local->ax_spi, P0_PSR); + if (ret) { + dev_err(&ax_local->spi->dev, + "timeout waiting for reload eeprom\n"); + return -1; + } + + return 0; +} + +static void ax88796c_set_hw_multicast(struct net_device *ndev) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + u16 rx_ctl = RXCR_AB; + int mc_count = netdev_mc_count(ndev); + + memset(ax_local->multi_filter, 0, AX_MCAST_FILTER_SIZE); + + if (ndev->flags & IFF_PROMISC) { + rx_ctl |= RXCR_PRO; + + } else if (ndev->flags & IFF_ALLMULTI || mc_count > AX_MAX_MCAST) { + rx_ctl |= RXCR_AMALL; + + } else if (mc_count == 0) { + /* just broadcast and directed */ + } else { + u32 crc_bits; + int i; + struct netdev_hw_addr *ha; + + netdev_for_each_mc_addr(ha, ndev) { + crc_bits = ether_crc(ETH_ALEN, ha->addr); + ax_local->multi_filter[crc_bits >> 29] |= + (1 << ((crc_bits >> 26) & 7)); + } + + for (i = 0; i < 4; i++) { + AX_WRITE(&ax_local->ax_spi, + ((ax_local->multi_filter[i * 2 + 1] << 8) | + ax_local->multi_filter[i * 2]), P3_MFAR(i)); + } + } + + AX_WRITE(&ax_local->ax_spi, rx_ctl, P2_RXCR); +} + +static void ax88796c_set_mac_addr(struct net_device *ndev) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + + AX_WRITE(&ax_local->ax_spi, ((u16)(ndev->dev_addr[4] << 8) | + (u16)ndev->dev_addr[5]), P3_MACASR0); + AX_WRITE(&ax_local->ax_spi, ((u16)(ndev->dev_addr[2] << 8) | + (u16)ndev->dev_addr[3]), P3_MACASR1); + AX_WRITE(&ax_local->ax_spi, ((u16)(ndev->dev_addr[0] << 8) | + (u16)ndev->dev_addr[1]), P3_MACASR2); +} + +static int ax88796c_set_mac_address(struct net_device *ndev, void *p) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + struct sockaddr *addr = p; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + + memcpy(ndev->dev_addr, addr->sa_data, ndev->addr_len); + + mutex_lock(&ax_local->spi_lock); + + ax88796c_set_mac_addr(ndev); + + mutex_unlock(&ax_local->spi_lock); + + return 0; +} + +static void ax88796c_load_mac_addr(struct net_device *ndev) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + u16 temp; + + /* Try the device tree first */ + if (!eth_platform_get_mac_address(&ax_local->spi->dev, ndev->dev_addr) && + is_valid_ether_addr(ndev->dev_addr)) { + if (netif_msg_probe(ax_local)) + dev_info(&ax_local->spi->dev, + "MAC address read from device tree\n"); + return; + } + + /* Read the MAC address from AX88796C */ + temp = AX_READ(&ax_local->ax_spi, P3_MACASR0); + ndev->dev_addr[5] = (u8)temp; + ndev->dev_addr[4] = (u8)(temp >> 8); + + temp = AX_READ(&ax_local->ax_spi, P3_MACASR1); + ndev->dev_addr[3] = (u8)temp; + ndev->dev_addr[2] = (u8)(temp >> 8); + + temp = AX_READ(&ax_local->ax_spi, P3_MACASR2); + ndev->dev_addr[1] = (u8)temp; + ndev->dev_addr[0] = (u8)(temp >> 8); + + if (is_valid_ether_addr(ndev->dev_addr)) { + if (netif_msg_probe(ax_local)) + dev_info(&ax_local->spi->dev, + "MAC address read from ASIX chip\n"); + return; + } + + /* Use random address if none found */ + if (netif_msg_probe(ax_local)) + dev_info(&ax_local->spi->dev, "Use random MAC address\n"); + eth_hw_addr_random(ndev); +} + +static void ax88796c_proc_tx_hdr(struct tx_pkt_info *info, u8 ip_summed) +{ + u16 pkt_len_bar = (~info->pkt_len & TX_HDR_SOP_PKTLENBAR); + + /* Prepare SOP header */ + info->sop.flags_len = info->pkt_len | + ((ip_summed == CHECKSUM_NONE) || + (ip_summed == CHECKSUM_UNNECESSARY) ? TX_HDR_SOP_DICF : 0); + + info->sop.seq_lenbar = ((info->seq_num << 11) & TX_HDR_SOP_SEQNUM) + | pkt_len_bar; + cpu_to_be16s(&info->sop.flags_len); + cpu_to_be16s(&info->sop.seq_lenbar); + + /* Prepare Segment header */ + info->seg.flags_seqnum_seglen = TX_HDR_SEG_FS | TX_HDR_SEG_LS + | info->pkt_len; + + info->seg.eo_so_seglenbar = pkt_len_bar; + + cpu_to_be16s(&info->seg.flags_seqnum_seglen); + cpu_to_be16s(&info->seg.eo_so_seglenbar); + + /* Prepare EOP header */ + info->eop.seq_len = ((info->seq_num << 11) & + TX_HDR_EOP_SEQNUM) | info->pkt_len; + info->eop.seqbar_lenbar = ((~info->seq_num << 11) & + TX_HDR_EOP_SEQNUMBAR) | pkt_len_bar; + + cpu_to_be16s(&info->eop.seq_len); + cpu_to_be16s(&info->eop.seqbar_lenbar); +} + +static int +ax88796c_check_free_pages(struct ax88796c_device *ax_local, u8 need_pages) +{ + u8 free_pages; + u16 tmp; + + free_pages = AX_READ(&ax_local->ax_spi, P0_TFBFCR) & TX_FREEBUF_MASK; + if (free_pages < need_pages) { + /* schedule free page interrupt */ + tmp = AX_READ(&ax_local->ax_spi, P0_TFBFCR) + & TFBFCR_SCHE_FREE_PAGE; + AX_WRITE(&ax_local->ax_spi, tmp | TFBFCR_TX_PAGE_SET | + TFBFCR_SET_FREE_PAGE(need_pages), + P0_TFBFCR); + return -ENOMEM; + } + + return 0; +} + +static struct sk_buff * +ax88796c_tx_fixup(struct net_device *ndev, struct sk_buff_head *q) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + struct sk_buff *skb, *tx_skb; + struct tx_pkt_info *info; + struct skb_data *entry; + int headroom; + int tailroom; + u8 need_pages; + u16 tol_len, pkt_len; + u8 padlen, seq_num; + u8 spi_len = ax_local->ax_spi.comp ? 1 : 4; + + if (skb_queue_empty(q)) + return NULL; + + skb = skb_peek(q); + pkt_len = skb->len; + need_pages = (pkt_len + TX_OVERHEAD + 127) >> 7; + if (ax88796c_check_free_pages(ax_local, need_pages) != 0) + return NULL; + + headroom = skb_headroom(skb); + tailroom = skb_tailroom(skb); + padlen = ((pkt_len + 3) & 0x7FC) - pkt_len; + tol_len = ((pkt_len + 3) & 0x7FC) + + TX_OVERHEAD + TX_EOP_SIZE + spi_len; + seq_num = ++ax_local->seq_num & 0x1F; + + info = (struct tx_pkt_info *)skb->cb; + info->pkt_len = pkt_len; + + if ((!skb_cloned(skb)) && + (headroom >= (TX_OVERHEAD + spi_len)) && + (tailroom >= (padlen + TX_EOP_SIZE))) { + info->seq_num = seq_num; + ax88796c_proc_tx_hdr(info, skb->ip_summed); + + /* SOP and SEG header */ + memcpy(skb_push(skb, TX_OVERHEAD), &info->sop, TX_OVERHEAD); + + /* Write SPI TXQ header */ + memcpy(skb_push(skb, spi_len), tx_cmd_buf, spi_len); + + /* Make 32-bit alignment */ + skb_put(skb, padlen); + + /* EOP header */ + memcpy(skb_put(skb, TX_EOP_SIZE), &info->eop, TX_EOP_SIZE); + + tx_skb = skb; + skb_unlink(skb, q); + } else { + tx_skb = alloc_skb(tol_len, GFP_KERNEL); + if (!tx_skb) + return NULL; + + /* Write SPI TXQ header */ + memcpy(skb_put(tx_skb, spi_len), tx_cmd_buf, spi_len); + + info->seq_num = seq_num; + ax88796c_proc_tx_hdr(info, skb->ip_summed); + + /* SOP and SEG header */ + memcpy(skb_put(tx_skb, TX_OVERHEAD), + &info->sop, TX_OVERHEAD); + + /* Packet */ + memcpy(skb_put(tx_skb, ((pkt_len + 3) & 0xFFFC)), + skb->data, pkt_len); + + /* EOP header */ + memcpy(skb_put(tx_skb, TX_EOP_SIZE), + &info->eop, TX_EOP_SIZE); + + skb_unlink(skb, q); + dev_kfree_skb(skb); + } + + entry = (struct skb_data *)tx_skb->cb; + memset(entry, 0, sizeof(*entry)); + entry->len = pkt_len; + + if (netif_msg_pktdata(ax_local)) { + char pfx[IFNAMSIZ + 7]; + + snprintf(pfx, sizeof(pfx), "%s: ", ndev->name); + + netdev_info(ndev, "TX packet len %d, total len %d, seq %d\n", + pkt_len, tx_skb->len, seq_num); + + netdev_info(ndev, " SPI Header:\n"); + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, + tx_skb->data, 4, 0); + + netdev_info(ndev, " TX SOP:\n"); + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, + tx_skb->data + 4, TX_OVERHEAD, 0); + + netdev_info(ndev, " TX packet:\n"); + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, + tx_skb->data + 4 + TX_OVERHEAD, + tx_skb->len - TX_EOP_SIZE - 4 - TX_OVERHEAD, 0); + + netdev_info(ndev, " TX EOP:\n"); + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, + tx_skb->data + tx_skb->len - 4, 4, 0); + } + + return tx_skb; +} + +static int ax88796c_hard_xmit(struct ax88796c_device *ax_local) +{ + struct sk_buff *tx_skb; + struct skb_data *entry; + + tx_skb = ax88796c_tx_fixup(ax_local->ndev, &ax_local->tx_wait_q); + + if (!tx_skb) + return 0; + + entry = (struct skb_data *)tx_skb->cb; + + AX_WRITE(&ax_local->ax_spi, + (TSNR_TXB_START | TSNR_PKT_CNT(1)), P0_TSNR); + + axspi_write_txq(&ax_local->ax_spi, tx_skb->data, tx_skb->len); + + if (((AX_READ(&ax_local->ax_spi, P0_TSNR) & TXNR_TXB_IDLE) == 0) || + ((ISR_TXERR & AX_READ(&ax_local->ax_spi, P0_ISR)) != 0)) { + /* Ack tx error int */ + AX_WRITE(&ax_local->ax_spi, ISR_TXERR, P0_ISR); + + ax_local->stats.tx_dropped++; + + netif_err(ax_local, tx_err, ax_local->ndev, + "TX FIFO error, re-initialize the TX bridge\n"); + + /* Reinitial tx bridge */ + AX_WRITE(&ax_local->ax_spi, TXNR_TXB_REINIT | + AX_READ(&ax_local->ax_spi, P0_TSNR), P0_TSNR); + ax_local->seq_num = 0; + } else { + ax_local->stats.tx_packets++; + ax_local->stats.tx_bytes += entry->len; + } + + entry->state = tx_done; + dev_kfree_skb(tx_skb); + + return 1; +} + +static int +ax88796c_start_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + + skb_queue_tail(&ax_local->tx_wait_q, skb); + if (skb_queue_len(&ax_local->tx_wait_q) > TX_QUEUE_HIGH_WATER) { + netif_err(ax_local, tx_queued, ndev, + "Too much TX packets in queue %d\n", + skb_queue_len(&ax_local->tx_wait_q)); + + netif_stop_queue(ndev); + } + + set_bit(EVENT_TX, &ax_local->flags); + queue_work(ax_local->ax_work_queue, &ax_local->ax_work); + + return NETDEV_TX_OK; +} + +static void +ax88796c_skb_return(struct ax88796c_device *ax_local, struct sk_buff *skb, + struct rx_header *rxhdr) +{ + struct net_device *ndev = ax_local->ndev; + int status; + + do { + if (!(ndev->features & NETIF_F_RXCSUM)) + break; + + /* checksum error bit is set */ + if ((rxhdr->flags & RX_HDR3_L3_ERR) || + (rxhdr->flags & RX_HDR3_L4_ERR)) + break; + + /* Other types may be indicated by more than one bit. */ + if ((rxhdr->flags & RX_HDR3_L4_TYPE_TCP) || + (rxhdr->flags & RX_HDR3_L4_TYPE_UDP)) + skb->ip_summed = CHECKSUM_UNNECESSARY; + } while (0); + + ax_local->stats.rx_packets++; + ax_local->stats.rx_bytes += skb->len; + skb->dev = ndev; + + skb->truesize = skb->len + sizeof(struct sk_buff); + skb->protocol = eth_type_trans(skb, ax_local->ndev); + + netif_info(ax_local, rx_status, ndev, "< rx, len %zu, type 0x%x\n", + skb->len + sizeof(struct ethhdr), skb->protocol); + + status = netif_rx(skb); + if (status != NET_RX_SUCCESS) + netif_info(ax_local, rx_err, ndev, + "netif_rx status %d\n", status); +} + +static void +ax88796c_rx_fixup(struct ax88796c_device *ax_local, struct sk_buff *rx_skb) +{ + struct rx_header *rxhdr = (struct rx_header *)rx_skb->data; + struct net_device *ndev = ax_local->ndev; + u16 len; + + be16_to_cpus(&rxhdr->flags_len); + be16_to_cpus(&rxhdr->seq_lenbar); + be16_to_cpus(&rxhdr->flags); + + if ((((short)rxhdr->flags_len) & RX_HDR1_PKT_LEN) != + (~((short)rxhdr->seq_lenbar) & 0x7FF)) { + netif_err(ax_local, rx_err, ndev, "Header error\n"); + + ax_local->stats.rx_frame_errors++; + kfree_skb(rx_skb); + return; + } + + if ((rxhdr->flags_len & RX_HDR1_MII_ERR) || + (rxhdr->flags_len & RX_HDR1_CRC_ERR)) { + netif_err(ax_local, rx_err, ndev, "CRC or MII error\n"); + + ax_local->stats.rx_crc_errors++; + kfree_skb(rx_skb); + return; + } + + len = rxhdr->flags_len & RX_HDR1_PKT_LEN; + if (netif_msg_pktdata(ax_local)) { + char pfx[IFNAMSIZ + 7]; + + snprintf(pfx, sizeof(pfx), "%s: ", ndev->name); + netdev_info(ndev, "RX data, total len %d, packet len %d\n", + rx_skb->len, len); + + netdev_info(ndev, " Dump RX packet header:"); + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, + rx_skb->data, sizeof(*rxhdr), 0); + + netdev_info(ndev, " Dump RX packet:"); + print_hex_dump(KERN_INFO, pfx, DUMP_PREFIX_OFFSET, 16, 1, + rx_skb->data + sizeof(*rxhdr), len, 0); + } + + skb_pull(rx_skb, sizeof(*rxhdr)); + __pskb_trim(rx_skb, len); + + return ax88796c_skb_return(ax_local, rx_skb, rxhdr); +} + +static int ax88796c_receive(struct net_device *ndev) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + struct sk_buff *skb; + struct skb_data *entry; + u16 w_count, pkt_len; + u8 pkt_cnt; + + /* check rx packet and total word count */ + AX_WRITE(&ax_local->ax_spi, AX_READ(&ax_local->ax_spi, P0_RTWCR) + | RTWCR_RX_LATCH, P0_RTWCR); + + pkt_cnt = AX_READ(&ax_local->ax_spi, P0_RXBCR2) & RXBCR2_PKT_MASK; + if (!pkt_cnt) + return 0; + + pkt_len = AX_READ(&ax_local->ax_spi, P0_RCPHR) & 0x7FF; + + w_count = ((pkt_len + 6 + 3) & 0xFFFC) >> 1; + + skb = alloc_skb((w_count * 2), GFP_KERNEL); + if (!skb) { + AX_WRITE(&ax_local->ax_spi, RXBCR1_RXB_DISCARD, P0_RXBCR1); + return 0; + } + entry = (struct skb_data *)skb->cb; + + AX_WRITE(&ax_local->ax_spi, RXBCR1_RXB_START | w_count, P0_RXBCR1); + + axspi_read_rxq(&ax_local->ax_spi, + skb_put(skb, w_count * 2), skb->len); + + /* Check if rx bridge is idle */ + if ((AX_READ(&ax_local->ax_spi, P0_RXBCR2) & RXBCR2_RXB_IDLE) == 0) { + netif_err(ax_local, rx_err, ndev, + "Rx Bridge is not idle\n"); + AX_WRITE(&ax_local->ax_spi, RXBCR2_RXB_REINIT, P0_RXBCR2); + + entry->state = rx_err; + } else { + entry->state = rx_done; + } + + AX_WRITE(&ax_local->ax_spi, ISR_RXPKT, P0_ISR); + + ax88796c_rx_fixup(ax_local, skb); + + return 1; +} + +static int ax88796c_process_isr(struct ax88796c_device *ax_local) +{ + u16 isr; + u8 done = 0; + struct net_device *ndev = ax_local->ndev; + + isr = AX_READ(&ax_local->ax_spi, P0_ISR); + AX_WRITE(&ax_local->ax_spi, isr, P0_ISR); + + netif_dbg(ax_local, intr, ndev, " ISR 0x%04x\n", isr); + + if (isr & ISR_TXERR) { + netif_dbg(ax_local, intr, ndev, " TXERR interrupt\n"); + AX_WRITE(&ax_local->ax_spi, TXNR_TXB_REINIT, P0_TSNR); + ax_local->seq_num = 0x1f; + } + + if (isr & ISR_TXPAGES) { + netif_dbg(ax_local, intr, ndev, " TXPAGES interrupt\n"); + set_bit(EVENT_TX, &ax_local->flags); + } + + if (isr & ISR_LINK) { + netif_dbg(ax_local, intr, ndev, " Link change interrupt\n"); + phy_mac_interrupt(ax_local->ndev->phydev); + } + + if (isr & ISR_RXPKT) { + netif_dbg(ax_local, intr, ndev, " RX interrupt\n"); + done = ax88796c_receive(ax_local->ndev); + } + + return done; +} + +static irqreturn_t ax88796c_interrupt(int irq, void *dev_instance) +{ + struct net_device *ndev = dev_instance; + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + + if (!ndev) { + pr_err("irq %d for unknown device.\n", irq); + return IRQ_RETVAL(0); + } + + disable_irq_nosync(irq); + + netif_dbg(ax_local, intr, ndev, "Interrupt occurred\n"); + + set_bit(EVENT_INTR, &ax_local->flags); + queue_work(ax_local->ax_work_queue, &ax_local->ax_work); + + return IRQ_HANDLED; +} + +static void ax88796c_work(struct work_struct *work) +{ + struct ax88796c_device *ax_local = + container_of(work, struct ax88796c_device, ax_work); + + mutex_lock(&ax_local->spi_lock); + + if (test_bit(EVENT_SET_MULTI, &ax_local->flags)) { + ax88796c_set_hw_multicast(ax_local->ndev); + clear_bit(EVENT_SET_MULTI, &ax_local->flags); + } + + if (test_bit(EVENT_INTR, &ax_local->flags)) { + AX_WRITE(&ax_local->ax_spi, IMR_MASKALL, P0_IMR); + + while (1) { + if (!ax88796c_process_isr(ax_local)) + break; + } + + clear_bit(EVENT_INTR, &ax_local->flags); + + AX_WRITE(&ax_local->ax_spi, IMR_DEFAULT, P0_IMR); + + enable_irq(ax_local->ndev->irq); + } + + if (test_bit(EVENT_TX, &ax_local->flags)) { + while (skb_queue_len(&ax_local->tx_wait_q)) { + if (!ax88796c_hard_xmit(ax_local)) + break; + } + + clear_bit(EVENT_TX, &ax_local->flags); + + if (netif_queue_stopped(ax_local->ndev) && + (skb_queue_len(&ax_local->tx_wait_q) < TX_QUEUE_LOW_WATER)) + netif_wake_queue(ax_local->ndev); + } + + mutex_unlock(&ax_local->spi_lock); +} + +static struct net_device_stats *ax88796c_get_stats(struct net_device *ndev) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + + return &ax_local->stats; +} + +static void ax88796c_handle_link_change(struct net_device *ndev) +{ + if (net_ratelimit()) + phy_print_status(ndev->phydev); +} + +void ax88796c_phy_init(struct ax88796c_device *ax_local) +{ + /* Enable PHY auto-polling */ + AX_WRITE(&ax_local->ax_spi, + PCR_PHYID(0x10) | PCR_POLL_EN | + PCR_POLL_FLOWCTRL | PCR_POLL_BMCR, P2_PCR); +} + +static int +ax88796c_open(struct net_device *ndev) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + int ret; + unsigned long irq_flag = IRQF_SHARED; + + mutex_lock(&ax_local->spi_lock); + + ret = ax88796c_soft_reset(ax_local); + if (ret < 0) + return -ENODEV; + + ret = request_irq(ndev->irq, ax88796c_interrupt, + irq_flag, ndev->name, ndev); + if (ret) { + netdev_err(ndev, "unable to get IRQ %d (errno=%d).\n", + ndev->irq, ret); + return -ENXIO; + } + + ax_local->seq_num = 0x1f; + + ax88796c_set_mac_addr(ndev); + ax88796c_set_csums(ax_local); + + /* Disable stuffing packet */ + AX_WRITE(&ax_local->ax_spi, + AX_READ(&ax_local->ax_spi, P1_RXBSPCR) + & ~RXBSPCR_STUF_ENABLE, P1_RXBSPCR); + + /* Enable RX packet process */ + AX_WRITE(&ax_local->ax_spi, RPPER_RXEN, P1_RPPER); + + AX_WRITE(&ax_local->ax_spi, AX_READ(&ax_local->ax_spi, P0_FER) + | FER_RXEN | FER_TXEN | FER_BSWAP | FER_IRQ_PULL, P0_FER); + + /* Setup LED mode */ + AX_WRITE(&ax_local->ax_spi, + (LCR_LED0_EN | LCR_LED0_DUPLEX | LCR_LED1_EN | + LCR_LED1_100MODE), P2_LCR0); + AX_WRITE(&ax_local->ax_spi, + (AX_READ(&ax_local->ax_spi, P2_LCR1) & LCR_LED2_MASK) | + LCR_LED2_EN | LCR_LED2_LINK, P2_LCR1); + + ax88796c_phy_init(ax_local); + + phy_start(ax_local->ndev->phydev); + + netif_start_queue(ndev); + + AX_WRITE(&ax_local->ax_spi, IMR_DEFAULT, P0_IMR); + + spi_message_init(&ax_local->ax_spi.rx_msg); + + mutex_unlock(&ax_local->spi_lock); + + return 0; +} + +static void ax88796c_free_skb_queue(struct sk_buff_head *q) +{ + struct sk_buff *skb; + + while (q->qlen) { + skb = skb_dequeue(q); + kfree_skb(skb); + } +} + +static int +ax88796c_close(struct net_device *ndev) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + + netif_stop_queue(ndev); + + free_irq(ndev->irq, ndev); + + phy_stop(ndev->phydev); + + mutex_lock(&ax_local->spi_lock); + + AX_WRITE(&ax_local->ax_spi, IMR_MASKALL, P0_IMR); + ax88796c_free_skb_queue(&ax_local->tx_wait_q); + + ax88796c_soft_reset(ax_local); + + mutex_unlock(&ax_local->spi_lock); + netif_carrier_off(ndev); + + return 0; +} + +static int +ax88796c_set_features(struct net_device *ndev, netdev_features_t features) +{ + struct ax88796c_device *ax_local = to_ax88796c_device(ndev); + netdev_features_t changed = features ^ ndev->features; + + if (!(changed & (NETIF_F_RXCSUM | NETIF_F_HW_CSUM))) + return 0; + + ndev->features = features; + + if (changed & (NETIF_F_RXCSUM | NETIF_F_HW_CSUM)) + ax88796c_set_csums(ax_local); + + return 0; +} + +static const struct net_device_ops ax88796c_netdev_ops = { + .ndo_open = ax88796c_open, + .ndo_stop = ax88796c_close, + .ndo_start_xmit = ax88796c_start_xmit, + .ndo_get_stats = ax88796c_get_stats, + .ndo_do_ioctl = ax88796c_ioctl, + .ndo_set_mac_address = ax88796c_set_mac_address, + .ndo_set_features = ax88796c_set_features, +}; + +static int ax88796c_hard_reset(struct ax88796c_device *ax_local) +{ + struct device *dev = (struct device *)&ax_local->spi->dev; + struct gpio_desc *reset_gpio; + + /* reset info */ + reset_gpio = gpiod_get(dev, "reset", 0); + if (IS_ERR(reset_gpio)) { + dev_err(dev, "Could not get 'reset' GPIO: %ld", PTR_ERR(reset_gpio)); + return PTR_ERR(reset_gpio); + } + + /* set reset */ + gpiod_direction_output(reset_gpio, 1); + msleep(100); + gpiod_direction_output(reset_gpio, 0); + gpiod_put(reset_gpio); + msleep(20); + + return 0; +} + +static int ax88796c_probe(struct spi_device *spi) +{ + struct net_device *ndev; + struct ax88796c_device *ax_local; + int ret; + u16 temp; + + ndev = devm_alloc_etherdev(&spi->dev, sizeof(*ax_local)); + if (!ndev) { + dev_err(&spi->dev, "AX88796C SPI: Could not allocate ethernet device\n"); + return -ENOMEM; + } + SET_NETDEV_DEV(ndev, &spi->dev); + + ax_local = to_ax88796c_device(ndev); + memset(ax_local, 0, sizeof(*ax_local)); + + dev_set_drvdata(&spi->dev, ax_local); + ax_local->spi = spi; + ax_local->ax_spi.spi = spi; + + ax_local->ndev = ndev; + ax_local->capabilities |= comp ? AX_CAP_COMP : 0; + ax_local->msg_enable = msg_enable; + + ax_local->mdiobus = devm_mdiobus_alloc(&spi->dev); + if (!ax_local->mdiobus) { + dev_err(&spi->dev, "Could not allocate MDIO bus\n"); + return -ENOMEM; + } + + ax_local->mdiobus->priv = ax_local; + ax_local->mdiobus->read = ax88796c_mdio_read; + ax_local->mdiobus->write = ax88796c_mdio_write; + ax_local->mdiobus->name = "ax88976c-mdiobus"; + ax_local->mdiobus->phy_mask = ~(1 << 0x10); + ax_local->mdiobus->parent = &spi->dev; + + snprintf(ax_local->mdiobus->id, ARRAY_SIZE(ax_local->mdiobus->id), + "ax88796c-spi-%s.%u", dev_name(&spi->dev), spi->chip_select); + + ret = devm_mdiobus_register(&spi->dev, ax_local->mdiobus); + if (ret < 0) { + dev_err(&spi->dev, "Could not register MDIO bus\n"); + return ret; + } + + if (netif_msg_probe(ax_local)) { + dev_info(&spi->dev, "AX88796C-SPI Configuration:\n"); + dev_info(&spi->dev, " Compression : %s\n", + ax_local->capabilities & AX_CAP_COMP ? "ON" : "OFF"); + } + + ndev->irq = spi->irq; + ndev->netdev_ops = &ax88796c_netdev_ops; + ndev->ethtool_ops = &ax88796c_ethtool_ops; + ndev->hw_features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM; + ndev->features |= NETIF_F_HW_CSUM | NETIF_F_RXCSUM; + ndev->hard_header_len += (TX_OVERHEAD + 4); + + /* ax88796c gpio reset */ + ax88796c_hard_reset(ax_local); + + /* Reset AX88796C */ + ret = ax88796c_soft_reset(ax_local); + if (ret < 0) { + ret = -ENODEV; + goto err; + } + /* Check board revision */ + temp = AX_READ(&ax_local->ax_spi, P2_CRIR); + if ((temp & 0xF) != 0x0) { + dev_err(&spi->dev, "spi read failed: %d\n", temp); + ret = -ENODEV; + goto err; + } + + temp = AX_READ(&ax_local->ax_spi, P0_BOR); + if (temp == 0x1234) { + ax_local->plat_endian = PLAT_LITTLE_ENDIAN; + } else { + AX_WRITE(&ax_local->ax_spi, 0xFFFF, P0_BOR); + ax_local->plat_endian = PLAT_BIG_ENDIAN; + } + + /*Reload EEPROM*/ + ax88796c_reload_eeprom(ax_local); + + ax88796c_load_mac_addr(ndev); + + if (netif_msg_probe(ax_local)) + dev_info(&spi->dev, + "irq %d, MAC addr %02X:%02X:%02X:%02X:%02X:%02X\n", + ndev->irq, + ndev->dev_addr[0], ndev->dev_addr[1], + ndev->dev_addr[2], ndev->dev_addr[3], + ndev->dev_addr[4], ndev->dev_addr[5]); + + /* Disable power saving */ + AX_WRITE(&ax_local->ax_spi, (AX_READ(&ax_local->ax_spi, P0_PSCR) + & PSCR_PS_MASK) | PSCR_PS_D0, P0_PSCR); + + INIT_WORK(&ax_local->ax_work, ax88796c_work); + + ax_local->ax_work_queue = + create_singlethread_workqueue("ax88796c_work"); + + mutex_init(&ax_local->spi_lock); + + skb_queue_head_init(&ax_local->tx_wait_q); + + ret = devm_register_netdev(&spi->dev, ndev); + if (ret) { + dev_err(&spi->dev, "failed to register a network device\n"); + destroy_workqueue(ax_local->ax_work_queue); + goto err; + } + + ax_local->phydev = phy_find_first(ax_local->mdiobus); + if (!ax_local->phydev) { + dev_err(&spi->dev, "no PHY found\n"); + ret = -ENODEV; + goto err; + } + + ax_local->phydev->irq = PHY_IGNORE_INTERRUPT; + phy_connect_direct(ax_local->ndev, ax_local->phydev, + ax88796c_handle_link_change, + PHY_INTERFACE_MODE_MII); + + netif_info(ax_local, probe, ndev, "%s %s registered\n", + dev_driver_string(&spi->dev), + dev_name(&spi->dev)); + phy_attached_info(ax_local->phydev); + + ret = 0; +err: + return ret; +} + +static int ax88796c_remove(struct spi_device *spi) +{ + struct ax88796c_device *ax_local = dev_get_drvdata(&spi->dev); + struct net_device *ndev = ax_local->ndev; + + netif_info(ax_local, probe, ndev, "removing network device %s %s\n", + dev_driver_string(&spi->dev), + dev_name(&spi->dev)); + + destroy_workqueue(ax_local->ax_work_queue); + + return 0; +} + +static const struct of_device_id ax88796c_dt_ids[] = { + { .compatible = "asix,ax88796c" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ax88796c_dt_ids); + +static const struct spi_device_id asix_id[] = { + { "ax88796c", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, asix_id); + +static struct spi_driver ax88796c_spi_driver = { + .driver = { + .name = DRV_NAME, +#ifdef CONFIG_USE_OF + .of_match_table = of_match_ptr(ax88796c_dt_ids), +#endif + }, + .probe = ax88796c_probe, + .remove = ax88796c_remove, + .id_table = asix_id, +}; + +static __init int ax88796c_spi_init(void) +{ + int ret; + + bitmap_zero(ax88796c_no_regs_mask, AX88796C_REGDUMP_LEN); + ret = bitmap_parse(no_regs_list, 35, + ax88796c_no_regs_mask, AX88796C_REGDUMP_LEN); + if (ret) { + bitmap_fill(ax88796c_no_regs_mask, AX88796C_REGDUMP_LEN); + pr_err("Invalid bitmap description, masking all registers\n"); + } + + return spi_register_driver(&ax88796c_spi_driver); +} + +static __exit void ax88796c_spi_exit(void) +{ + spi_unregister_driver(&ax88796c_spi_driver); +} + +module_init(ax88796c_spi_init); +module_exit(ax88796c_spi_exit); + +MODULE_AUTHOR("ASIX"); +MODULE_DESCRIPTION("ASIX AX88796C SPI Ethernet driver"); +MODULE_LICENSE("GPL"); + +/* ax88796c_phy */ diff --git a/drivers/net/ethernet/asix/ax88796c_main.h b/drivers/net/ethernet/asix/ax88796c_main.h new file mode 100644 index 000000000000..428833978fbf --- /dev/null +++ b/drivers/net/ethernet/asix/ax88796c_main.h @@ -0,0 +1,568 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2010 ASIX Electronics Corporation + * Copyright (c) 2020 Samsung Electronics + * + * ASIX AX88796C SPI Fast Ethernet Linux driver + */ + +#ifndef _AX88796C_MAIN_H +#define _AX88796C_MAIN_H + +#include <linux/netdevice.h> +#include <linux/mii.h> + +#include "ax88796c_spi.h" + +/* These identify the driver base version and may not be removed. */ +#define DRV_NAME "ax88796c" +#define ADP_NAME "ASIX AX88796C SPI Ethernet Adapter" +#define DRV_VERSION "1.2.0" + +#define TX_QUEUE_HIGH_WATER 45 /* Tx queue high water mark */ +#define TX_QUEUE_LOW_WATER 20 /* Tx queue low water mark */ + +#define AX88796C_REGDUMP_LEN 256 +#define AX88796C_PHY_REGDUMP_LEN 12 + +#define TX_OVERHEAD 8 +#define TX_EOP_SIZE 4 + +#define AX_MCAST_FILTER_SIZE 8 +#define AX_MAX_MCAST 64 +#define AX_MAX_CLK 80000000 +#define TX_HDR_SOP_DICF 0x8000 +#define TX_HDR_SOP_CPHI 0x4000 +#define TX_HDR_SOP_INT 0x2000 +#define TX_HDR_SOP_MDEQ 0x1000 +#define TX_HDR_SOP_PKTLEN 0x07FF +#define TX_HDR_SOP_SEQNUM 0xF800 +#define TX_HDR_SOP_PKTLENBAR 0x07FF + +#define TX_HDR_SEG_FS 0x8000 +#define TX_HDR_SEG_LS 0x4000 +#define TX_HDR_SEG_SEGNUM 0x3800 +#define TX_HDR_SEG_SEGLEN 0x0700 +#define TX_HDR_SEG_EOFST 0xC000 +#define TX_HDR_SEG_SOFST 0x3800 +#define TX_HDR_SEG_SEGLENBAR 0x07FF + +#define TX_HDR_EOP_SEQNUM 0xF800 +#define TX_HDR_EOP_PKTLEN 0x07FF +#define TX_HDR_EOP_SEQNUMBAR 0xF800 +#define TX_HDR_EOP_PKTLENBAR 0x07FF + +/* Rx header fields mask */ +#define RX_HDR1_MCBC 0x8000 +#define RX_HDR1_STUFF_PKT 0x4000 +#define RX_HDR1_MII_ERR 0x2000 +#define RX_HDR1_CRC_ERR 0x1000 +#define RX_HDR1_PKT_LEN 0x07FF + +#define RX_HDR2_SEQ_NUM 0xF800 +#define RX_HDR2_PKT_LEN_BAR 0x7FFF + +#define RX_HDR3_PE 0x8000 +#define RX_HDR3_L3_TYPE_IPV4V6 0x6000 +#define RX_HDR3_L3_TYPE_IP 0x4000 +#define RX_HDR3_L3_TYPE_IPV6 0x2000 +#define RX_HDR3_L4_TYPE_ICMPV6 0x1400 +#define RX_HDR3_L4_TYPE_TCP 0x1000 +#define RX_HDR3_L4_TYPE_IGMP 0x0c00 +#define RX_HDR3_L4_TYPE_ICMP 0x0800 +#define RX_HDR3_L4_TYPE_UDP 0x0400 +#define RX_HDR3_L3_ERR 0x0200 +#define RX_HDR3_L4_ERR 0x0100 +#define RX_HDR3_PRIORITY(x) ((x) << 4) +#define RX_HDR3_STRIP 0x0008 +#define RX_HDR3_VLAN_ID 0x0007 + +enum watchdog_state { + chk_link = 0, + chk_cable, + ax_nop, +}; + +struct ax88796c_device { + struct resource *addr_res; /* resources found */ + struct resource *addr_req; /* resources requested */ + struct resource *irq_res; + + struct spi_device *spi; + struct net_device *ndev; + struct net_device_stats stats; + + struct timer_list watchdog; + enum watchdog_state w_state; + size_t w_ticks; + + struct work_struct ax_work; + struct workqueue_struct *ax_work_queue; + struct tasklet_struct bh; + + struct mutex spi_lock; /* device access */ + + struct sk_buff_head tx_wait_q; + + struct axspi_data ax_spi; + + struct mii_bus *mdiobus; + struct phy_device *phydev; + + int msg_enable; + + u16 seq_num; + + u8 multi_filter[AX_MCAST_FILTER_SIZE]; + + unsigned long capabilities; + #define AX_CAP_DMA 1 + #define AX_CAP_COMP 2 + #define AX_CAP_BIDIR 4 + + u8 plat_endian; + #define PLAT_LITTLE_ENDIAN 0 + #define PLAT_BIG_ENDIAN 1 + + unsigned long flags; + #define EVENT_INTR 1 + #define EVENT_TX 2 + #define EVENT_SET_MULTI 4 + +}; + +#define to_ax88796c_device(ndev) ((struct ax88796c_device *)netdev_priv(ndev)) + +enum skb_state { + illegal = 0, + tx_done, + rx_done, + rx_err, +}; + +struct skb_data; + +struct skb_data { + enum skb_state state; + struct net_device *ndev; + struct sk_buff *skb; + size_t len; + dma_addr_t phy_addr; +}; + +/* A88796C register definition */ + /* Definition of PAGE0 */ +#define P0_PSR (0x00) + #define PSR_DEV_READY (1 << 7) + #define PSR_RESET (0 << 15) + #define PSR_RESET_CLR (1 << 15) +#define P0_BOR (0x02) +#define P0_FER (0x04) + #define FER_IPALM (1 << 0) + #define FER_DCRC (1 << 1) + #define FER_RH3M (1 << 2) + #define FER_HEADERSWAP (1 << 7) + #define FER_WSWAP (1 << 8) + #define FER_BSWAP (1 << 9) + #define FER_INTHI (1 << 10) + #define FER_INTLO (0 << 10) + #define FER_IRQ_PULL (1 << 11) + #define FER_RXEN (1 << 14) + #define FER_TXEN (1 << 15) +#define P0_ISR (0x06) + #define ISR_RXPKT (1 << 0) + #define ISR_MDQ (1 << 4) + #define ISR_TXT (1 << 5) + #define ISR_TXPAGES (1 << 6) + #define ISR_TXERR (1 << 8) + #define ISR_LINK (1 << 9) +#define P0_IMR (0x08) + #define IMR_RXPKT (1 << 0) + #define IMR_MDQ (1 << 4) + #define IMR_TXT (1 << 5) + #define IMR_TXPAGES (1 << 6) + #define IMR_TXERR (1 << 8) + #define IMR_LINK (1 << 9) + #define IMR_MASKALL (0xFFFF) + #define IMR_DEFAULT (IMR_TXERR) +#define P0_WFCR (0x0A) + #define WFCR_PMEIND (1 << 0) /* PME indication */ + #define WFCR_PMETYPE (1 << 1) /* PME I/O type */ + #define WFCR_PMEPOL (1 << 2) /* PME polarity */ + #define WFCR_PMERST (1 << 3) /* Reset PME */ + #define WFCR_SLEEP (1 << 4) /* Enable sleep mode */ + #define WFCR_WAKEUP (1 << 5) /* Enable wakeup mode */ + #define WFCR_WAITEVENT (1 << 6) /* Reserved */ + #define WFCR_CLRWAKE (1 << 7) /* Clear wakeup */ + #define WFCR_LINKCH (1 << 8) /* Enable link change */ + #define WFCR_MAGICP (1 << 9) /* Enable magic packet */ + #define WFCR_WAKEF (1 << 10) /* Enable wakeup frame */ + #define WFCR_PMEEN (1 << 11) /* Enable PME pin */ + #define WFCR_LINKCHS (1 << 12) /* Link change status */ + #define WFCR_MAGICPS (1 << 13) /* Magic packet status */ + #define WFCR_WAKEFS (1 << 14) /* Wakeup frame status */ + #define WFCR_PMES (1 << 15) /* PME pin status */ +#define P0_PSCR (0x0C) + #define PSCR_PS_MASK (0xFFF0) + #define PSCR_PS_D0 (0) + #define PSCR_PS_D1 (1 << 0) + #define PSCR_PS_D2 (1 << 1) + #define PSCR_FPS (1 << 3) /* Enable fiber mode PS */ + #define PSCR_SWPS (1 << 4) /* Enable software */ + /* PS control */ + #define PSCR_WOLPS (1 << 5) /* Enable WOL PS */ + #define PSCR_SWWOL (1 << 6) /* Enable software select */ + /* WOL PS */ + #define PSCR_PHYOSC (1 << 7) /* Internal PHY OSC control */ + #define PSCR_FOFEF (1 << 8) /* Force PHY generate FEF */ + #define PSCR_FOF (1 << 9) /* Force PHY in fiber mode */ + #define PSCR_PHYPD (1 << 10) /* PHY power down. */ + /* Active high */ + #define PSCR_PHYRST (1 << 11) /* PHY reset signal. */ + /* Active low */ + #define PSCR_PHYCSIL (1 << 12) /* PHY cable energy detect */ + #define PSCR_PHYCOFF (1 << 13) /* PHY cable off */ + #define PSCR_PHYLINK (1 << 14) /* PHY link status */ + #define PSCR_EEPOK (1 << 15) /* EEPROM load complete */ +#define P0_MACCR (0x0E) + #define MACCR_RXFC_ENABLE (1 << 3) + #define MACCR_RXFC_MASK 0xFFF7 + #define MACCR_TXFC_ENABLE (1 << 4) + #define MACCR_TXFC_MASK 0xFFEF + #define MACCR_PSI (1 << 6) /* Software Cable-Off */ + /* Power Saving Interrupt */ + #define MACCR_PF (1 << 7) + #define MACCR_PMM_BITS 8 + #define MACCR_PMM_MASK (0x1F00) + #define MACCR_PMM_RESET (1 << 8) + #define MACCR_PMM_WAIT (2 << 8) + #define MACCR_PMM_READY (3 << 8) + #define MACCR_PMM_D1 (4 << 8) + #define MACCR_PMM_D2 (5 << 8) + #define MACCR_PMM_WAKE (7 << 8) + #define MACCR_PMM_D1_WAKE (8 << 8) + #define MACCR_PMM_D2_WAKE (9 << 8) + #define MACCR_PMM_SLEEP (10 << 8) + #define MACCR_PMM_PHY_RESET (11 << 8) + #define MACCR_PMM_SOFT_D1 (16 << 8) + #define MACCR_PMM_SOFT_D2 (17 << 8) +#define P0_TFBFCR (0x10) + #define TFBFCR_SCHE_FREE_PAGE 0xE07F + #define TFBFCR_FREE_PAGE_BITS 0x07 + #define TFBFCR_FREE_PAGE_LATCH (1 << 6) + #define TFBFCR_SET_FREE_PAGE(x) (((x) & 0x3F) << TFBFCR_FREE_PAGE_BITS) + #define TFBFCR_TX_PAGE_SET (1 << 13) + #define TFBFCR_MANU_ENTX (1 << 15) + #define TX_FREEBUF_MASK 0x003F + #define TX_DPTSTART 0x4000 + +#define P0_TSNR (0x12) + #define TXNR_TXB_ERR (1 << 5) + #define TXNR_TXB_IDLE (1 << 6) + #define TSNR_PKT_CNT(x) (((x) & 0x3F) << 8) + #define TXNR_TXB_REINIT (1 << 14) + #define TSNR_TXB_START (1 << 15) +#define P0_RTDPR (0x14) +#define P0_RXBCR1 (0x16) + #define RXBCR1_RXB_DISCARD (1 << 14) + #define RXBCR1_RXB_START (1 << 15) +#define P0_RXBCR2 (0x18) + #define RXBCR2_PKT_MASK (0xFF) + #define RXBCR2_RXPC_MASK (0x7F) + #define RXBCR2_RXB_READY (1 << 13) + #define RXBCR2_RXB_IDLE (1 << 14) + #define RXBCR2_RXB_REINIT (1 << 15) +#define P0_RTWCR (0x1A) + #define RTWCR_RXWC_MASK (0x3FFF) + #define RTWCR_RX_LATCH (1 << 15) +#define P0_RCPHR (0x1C) + + /* Definition of PAGE1 */ +#define P1_RPPER (0x22) + #define RPPER_RXEN (1 << 0) +#define P1_MRCR (0x28) +#define P1_MDR (0x2A) +#define P1_RMPR (0x2C) +#define P1_TMPR (0x2E) +#define P1_RXBSPCR (0x30) + #define RXBSPCR_STUF_WORD_CNT(x) (((x) & 0x7000) >> 12) + #define RXBSPCR_STUF_ENABLE (1 << 15) +#define P1_MCR (0x32) + #define MCR_SBP (1 << 8) + #define MCR_SM (1 << 9) + #define MCR_CRCENLAN (1 << 11) + #define MCR_STP (1 << 12) + /* Definition of PAGE2 */ +#define P2_CIR (0x42) +#define P2_PCR (0x44) + #define PCR_POLL_EN (1 << 0) + #define PCR_POLL_FLOWCTRL (1 << 1) + #define PCR_POLL_BMCR (1 << 2) + #define PCR_PHYID(x) ((x) << 8) +#define P2_PHYSR (0x46) +#define P2_MDIODR (0x48) +#define P2_MDIOCR (0x4A) + #define MDIOCR_RADDR(x) ((x) & 0x1F) + #define MDIOCR_FADDR(x) (((x) & 0x1F) << 8) + #define MDIOCR_VALID (1 << 13) + #define MDIOCR_READ (1 << 14) + #define MDIOCR_WRITE (1 << 15) +#define P2_LCR0 (0x4C) + #define LCR_LED0_EN (1 << 0) + #define LCR_LED0_100MODE (1 << 1) + #define LCR_LED0_DUPLEX (1 << 2) + #define LCR_LED0_LINK (1 << 3) + #define LCR_LED0_ACT (1 << 4) + #define LCR_LED0_COL (1 << 5) + #define LCR_LED0_10MODE (1 << 6) + #define LCR_LED0_DUPCOL (1 << 7) + #define LCR_LED1_EN (1 << 8) + #define LCR_LED1_100MODE (1 << 9) + #define LCR_LED1_DUPLEX (1 << 10) + #define LCR_LED1_LINK (1 << 11) + #define LCR_LED1_ACT (1 << 12) + #define LCR_LED1_COL (1 << 13) + #define LCR_LED1_10MODE (1 << 14) + #define LCR_LED1_DUPCOL (1 << 15) +#define P2_LCR1 (0x4E) + #define LCR_LED2_MASK (0xFF00) + #define LCR_LED2_EN (1 << 0) + #define LCR_LED2_100MODE (1 << 1) + #define LCR_LED2_DUPLEX (1 << 2) + #define LCR_LED2_LINK (1 << 3) + #define LCR_LED2_ACT (1 << 4) + #define LCR_LED2_COL (1 << 5) + #define LCR_LED2_10MODE (1 << 6) + #define LCR_LED2_DUPCOL (1 << 7) +#define P2_IPGCR (0x50) +#define P2_CRIR (0x52) +#define P2_FLHWCR (0x54) +#define P2_RXCR (0x56) + #define RXCR_PRO (1 << 0) + #define RXCR_AMALL (1 << 1) + #define RXCR_SEP (1 << 2) + #define RXCR_AB (1 << 3) + #define RXCR_AM (1 << 4) + #define RXCR_AP (1 << 5) + #define RXCR_ARP (1 << 6) +#define P2_JLCR (0x58) +#define P2_MPLR (0x5C) + + /* Definition of PAGE3 */ +#define P3_MACASR0 (0x62) + #define P3_MACASR(x) (P3_MACASR0 + 2 * (x)) + #define MACASR_LOWBYTE_MASK 0x00FF + #define MACASR_HIGH_BITS 0x08 +#define P3_MACASR1 (0x64) +#define P3_MACASR2 (0x66) +#define P3_MFAR01 (0x68) +#define P3_MFAR_BASE (0x68) + #define P3_MFAR(x) (P3_MFAR_BASE + 2 * (x)) + +#define P3_MFAR23 (0x6A) +#define P3_MFAR45 (0x6C) +#define P3_MFAR67 (0x6E) +#define P3_VID0FR (0x70) +#define P3_VID1FR (0x72) +#define P3_EECSR (0x74) +#define P3_EEDR (0x76) +#define P3_EECR (0x78) + #define EECR_ADDR_MASK (0x00FF) + #define EECR_READ_ACT (1 << 8) + #define EECR_WRITE_ACT (1 << 9) + #define EECR_WRITE_DISABLE (1 << 10) + #define EECR_WRITE_ENABLE (1 << 11) + #define EECR_EE_READY (1 << 13) + #define EECR_RELOAD (1 << 14) + #define EECR_RESET (1 << 15) +#define P3_TPCR (0x7A) + #define TPCR_PATT_MASK (0xFF) + #define TPCR_RAND_PKT_EN (1 << 14) + #define TPCR_FIXED_PKT_EN (1 << 15) +#define P3_TPLR (0x7C) + /* Definition of PAGE4 */ +#define P4_SPICR (0x8A) + #define SPICR_RCEN (1 << 0) + #define SPICR_QCEN (1 << 1) + #define SPICR_RBRE (1 << 3) + #define SPICR_PMM (1 << 4) + #define SPICR_LOOPBACK (1 << 8) + #define SPICR_CORE_RES_CLR (1 << 10) + #define SPICR_SPI_RES_CLR (1 << 11) +#define P4_SPIISMR (0x8C) + +#define P4_COERCR0 (0x92) + #define COERCR0_RXIPCE (1 << 0) + #define COERCR0_RXIPVE (1 << 1) + #define COERCR0_RXV6PE (1 << 2) + #define COERCR0_RXTCPE (1 << 3) + #define COERCR0_RXUDPE (1 << 4) + #define COERCR0_RXICMP (1 << 5) + #define COERCR0_RXIGMP (1 << 6) + #define COERCR0_RXICV6 (1 << 7) + + #define COERCR0_RXTCPV6 (1 << 8) + #define COERCR0_RXUDPV6 (1 << 9) + #define COERCR0_RXICMV6 (1 << 10) + #define COERCR0_RXIGMV6 (1 << 11) + #define COERCR0_RXICV6V6 (1 << 12) + + #define COERCR0_DEFAULT (COERCR0_RXIPCE | COERCR0_RXV6PE | \ + COERCR0_RXTCPE | COERCR0_RXUDPE | \ + COERCR0_RXTCPV6 | COERCR0_RXUDPV6) +#define P4_COERCR1 (0x94) + #define COERCR1_IPCEDP (1 << 0) + #define COERCR1_IPVEDP (1 << 1) + #define COERCR1_V6VEDP (1 << 2) + #define COERCR1_TCPEDP (1 << 3) + #define COERCR1_UDPEDP (1 << 4) + #define COERCR1_ICMPDP (1 << 5) + #define COERCR1_IGMPDP (1 << 6) + #define COERCR1_ICV6DP (1 << 7) + #define COERCR1_RX64TE (1 << 8) + #define COERCR1_RXPPPE (1 << 9) + #define COERCR1_TCP6DP (1 << 10) + #define COERCR1_UDP6DP (1 << 11) + #define COERCR1_IC6DP (1 << 12) + #define COERCR1_IG6DP (1 << 13) + #define COERCR1_ICV66DP (1 << 14) + #define COERCR1_RPCE (1 << 15) + + #define COERCR1_DEFAULT (COERCR1_RXPPPE) + +#define P4_COETCR0 (0x96) + #define COETCR0_TXIP (1 << 0) + #define COETCR0_TXTCP (1 << 1) + #define COETCR0_TXUDP (1 << 2) + #define COETCR0_TXICMP (1 << 3) + #define COETCR0_TXIGMP (1 << 4) + #define COETCR0_TXICV6 (1 << 5) + #define COETCR0_TXTCPV6 (1 << 8) + #define COETCR0_TXUDPV6 (1 << 9) + #define COETCR0_TXICMV6 (1 << 10) + #define COETCR0_TXIGMV6 (1 << 11) + #define COETCR0_TXICV6V6 (1 << 12) + + #define COETCR0_DEFAULT (COETCR0_TXIP | COETCR0_TXTCP | \ + COETCR0_TXUDP | COETCR0_TXTCPV6 | \ + COETCR0_TXUDPV6) +#define P4_COETCR1 (0x98) + #define COETCR1_TX64TE (1 << 0) + #define COETCR1_TXPPPE (1 << 1) + +#define P4_COECEDR (0x9A) +#define P4_L2CECR (0x9C) + + /* Definition of PAGE5 */ +#define P5_WFTR (0xA2) + #define WFTR_2MS (0x01) + #define WFTR_4MS (0x02) + #define WFTR_8MS (0x03) + #define WFTR_16MS (0x04) + #define WFTR_32MS (0x05) + #define WFTR_64MS (0x06) + #define WFTR_128MS (0x07) + #define WFTR_256MS (0x08) + #define WFTR_512MS (0x09) + #define WFTR_1024MS (0x0A) + #define WFTR_2048MS (0x0B) + #define WFTR_4096MS (0x0C) + #define WFTR_8192MS (0x0D) + #define WFTR_16384MS (0x0E) + #define WFTR_32768MS (0x0F) +#define P5_WFCCR (0xA4) +#define P5_WFCR03 (0xA6) + #define WFCR03_F0_EN (1 << 0) + #define WFCR03_F1_EN (1 << 4) + #define WFCR03_F2_EN (1 << 8) + #define WFCR03_F3_EN (1 << 12) +#define P5_WFCR47 (0xA8) + #define WFCR47_F4_EN (1 << 0) + #define WFCR47_F5_EN (1 << 4) + #define WFCR47_F6_EN (1 << 8) + #define WFCR47_F7_EN (1 << 12) +#define P5_WF0BMR0 (0xAA) +#define P5_WF0BMR1 (0xAC) +#define P5_WF0CR (0xAE) +#define P5_WF0OBR (0xB0) +#define P5_WF1BMR0 (0xB2) +#define P5_WF1BMR1 (0xB4) +#define P5_WF1CR (0xB6) +#define P5_WF1OBR (0xB8) +#define P5_WF2BMR0 (0xBA) +#define P5_WF2BMR1 (0xBC) + + /* Definition of PAGE6 */ +#define P6_WF2CR (0xC2) +#define P6_WF2OBR (0xC4) +#define P6_WF3BMR0 (0xC6) +#define P6_WF3BMR1 (0xC8) +#define P6_WF3CR (0xCA) +#define P6_WF3OBR (0xCC) +#define P6_WF4BMR0 (0xCE) +#define P6_WF4BMR1 (0xD0) +#define P6_WF4CR (0xD2) +#define P6_WF4OBR (0xD4) +#define P6_WF5BMR0 (0xD6) +#define P6_WF5BMR1 (0xD8) +#define P6_WF5CR (0xDA) +#define P6_WF5OBR (0xDC) + +/* Definition of PAGE7 */ +#define P7_WF6BMR0 (0xE2) +#define P7_WF6BMR1 (0xE4) +#define P7_WF6CR (0xE6) +#define P7_WF6OBR (0xE8) +#define P7_WF7BMR0 (0xEA) +#define P7_WF7BMR1 (0xEC) +#define P7_WF7CR (0xEE) +#define P7_WF7OBR (0xF0) +#define P7_WFR01 (0xF2) +#define P7_WFR23 (0xF4) +#define P7_WFR45 (0xF6) +#define P7_WFR67 (0xF8) +#define P7_WFPC0 (0xFA) +#define P7_WFPC1 (0xFC) + +/* Tx headers structure */ +struct tx_sop_header { + /* bit 15-11: flags, bit 10-0: packet length */ + u16 flags_len; + /* bit 15-11: sequence number, bit 11-0: packet length bar */ + u16 seq_lenbar; +} __packed; + +struct tx_segment_header { + /* bit 15-14: flags, bit 13-11: segment number */ + /* bit 10-0: segment length */ + u16 flags_seqnum_seglen; + /* bit 15-14: end offset, bit 13-11: start offset */ + /* bit 10-0: segment length bar */ + u16 eo_so_seglenbar; +} __packed; + +struct tx_eop_header { + /* bit 15-11: sequence number, bit 10-0: packet length */ + u16 seq_len; + /* bit 15-11: sequence number bar, bit 10-0: packet length bar */ + u16 seqbar_lenbar; +} __packed; + +struct tx_pkt_info { + struct tx_sop_header sop; + struct tx_segment_header seg; + struct tx_eop_header eop; + u16 pkt_len; + u16 seq_num; +} __packed; + +/* Rx headers structure */ +struct rx_header { + u16 flags_len; + u16 seq_lenbar; + u16 flags; +} __packed; + +extern unsigned long ax88796c_no_regs_mask[]; + +#endif /* #ifndef _AX88796C_MAIN_H */ diff --git a/drivers/net/ethernet/asix/ax88796c_spi.c b/drivers/net/ethernet/asix/ax88796c_spi.c new file mode 100644 index 000000000000..1a20bbeb4dc1 --- /dev/null +++ b/drivers/net/ethernet/asix/ax88796c_spi.c @@ -0,0 +1,111 @@ +// 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" + +/* driver bus management functions */ +int axspi_wakeup(const struct axspi_data *ax_spi) +{ + u8 tx_buf; + int ret; + + tx_buf = AX_SPICMD_EXIT_PWD; /* OP */ + ret = spi_write(ax_spi->spi, &tx_buf, 1); + if (ret) + dev_err(&ax_spi->spi->dev, "%s() failed: ret = %d\n", __func__, ret); + return ret; +} + +int axspi_read_status(const struct axspi_data *ax_spi, struct spi_status *status) +{ + u8 tx_buf; + int ret; + + /* OP */ + tx_buf = AX_SPICMD_READ_STATUS; + ret = spi_write_then_read(ax_spi->spi, &tx_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, 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(const struct axspi_data *ax_spi, u8 reg) +{ + u8 tx_buf[4]; + u16 rx_buf = 0; + int ret; + int len = ax_spi->comp ? 3 : 4; + + tx_buf[0] = 0x03; /* OP code read register */ + tx_buf[1] = reg; /* register address */ + tx_buf[2] = 0xFF; /* dumy cycle */ + tx_buf[3] = 0xFF; /* dumy cycle */ + ret = spi_write_then_read(ax_spi->spi, tx_buf, len, (u8 *)&rx_buf, 2); + if (ret) + dev_err(&ax_spi->spi->dev, "%s() failed: ret = %d\n", __func__, ret); + else + le16_to_cpus(&rx_buf); + + return rx_buf; +} + +int axspi_write_reg(const struct axspi_data *ax_spi, u8 reg, u16 value) +{ + u8 tx_buf[4]; + int ret; + + tx_buf[0] = AX_SPICMD_WRITE_REG; /* OP code read register */ + tx_buf[1] = reg; /* register address */ + tx_buf[2] = value; + tx_buf[3] = value >> 8; + + ret = spi_write(ax_spi->spi, tx_buf, 4); + if (ret) + dev_err(&ax_spi->spi->dev, "%s() failed: ret = %d\n", __func__, ret); + return ret; +} + diff --git a/drivers/net/ethernet/asix/ax88796c_spi.h b/drivers/net/ethernet/asix/ax88796c_spi.h new file mode 100644 index 000000000000..7a49205c2cfb --- /dev/null +++ b/drivers/net/ethernet/asix/ax88796c_spi.h @@ -0,0 +1,69 @@ +/* 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 + +static const u8 rx_cmd_buf[5] = {AX_SPICMD_READ_RXQ, 0xFF, 0xFF, 0xFF, 0xFF}; +static const u8 tx_cmd_buf[4] = {AX_SPICMD_WRITE_TXQ, 0xFF, 0xFF, 0xFF}; + +struct axspi_data { + struct spi_device *spi; + struct spi_message rx_msg; + struct spi_transfer spi_rx_xfer[2]; + u8 cmd_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(const struct axspi_data *ax_spi, u8 reg); +int axspi_write_reg(const struct axspi_data *ax_spi, u8 reg, u16 value); +int axspi_read_status(const struct axspi_data *ax_spi, struct spi_status *status); +int axspi_wakeup(const struct axspi_data *ax_spi); + +static inline u16 AX_READ(const struct axspi_data *ax_spi, u8 offset) +{ + return axspi_read_reg(ax_spi, offset); +} + +static inline int AX_WRITE(const struct axspi_data *ax_spi, u16 value, u8 offset) +{ + return axspi_write_reg(ax_spi, offset, value); +} + +static inline int AX_READ_STATUS(const 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 +
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. [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: Łukasz Stelmach <l.stelmach@samsung.com> --- MAINTAINERS | 6 + drivers/net/ethernet/Kconfig | 1 + drivers/net/ethernet/Makefile | 1 + drivers/net/ethernet/asix/Kconfig | 21 + drivers/net/ethernet/asix/Makefile | 6 + drivers/net/ethernet/asix/ax88796c_ioctl.c | 241 +++++ drivers/net/ethernet/asix/ax88796c_ioctl.h | 27 + drivers/net/ethernet/asix/ax88796c_main.c | 1041 ++++++++++++++++++++ drivers/net/ethernet/asix/ax88796c_main.h | 568 +++++++++++ drivers/net/ethernet/asix/ax88796c_spi.c | 111 +++ drivers/net/ethernet/asix/ax88796c_spi.h | 69 ++ 11 files changed, 2092 insertions(+) create mode 100644 drivers/net/ethernet/asix/Kconfig create mode 100644 drivers/net/ethernet/asix/Makefile create mode 100644 drivers/net/ethernet/asix/ax88796c_ioctl.c create mode 100644 drivers/net/ethernet/asix/ax88796c_ioctl.h create mode 100644 drivers/net/ethernet/asix/ax88796c_main.c create mode 100644 drivers/net/ethernet/asix/ax88796c_main.h create mode 100644 drivers/net/ethernet/asix/ax88796c_spi.c create mode 100644 drivers/net/ethernet/asix/ax88796c_spi.h