diff --git a/arch/riscv/configs/capltd_cva6_cheri_genesys2_defconfig b/arch/riscv/configs/capltd_cva6_cheri_genesys2_defconfig new file mode 100644 index 00000000000000..5de439d5063b27 --- /dev/null +++ b/arch/riscv/configs/capltd_cva6_cheri_genesys2_defconfig @@ -0,0 +1,39 @@ +CONFIG_DEFAULT_HOSTNAME="cva6-cheri" +CONFIG_BLK_DEV_INITRD=y +CONFIG_HZ_100=y +CONFIG_CMDLINE="earlycon console=ttyS0,115200n8" +# CONFIG_GCC_PLUGINS is not set +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +# CONFIG_WIRELESS is not set +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_NETDEVICES=y +CONFIG_LOWRISC_DIGILENT_100MHZ=y +CONFIG_MDIO_BITBANG=y +# CONFIG_WLAN is not set +# CONFIG_INPUT_KEYBOARD is not set +# CONFIG_INPUT_MOUSE is not set +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_OF_PLATFORM=y +# CONFIG_HW_RANDOM is not set +CONFIG_SPI=y +CONFIG_SPI_XILINX=y +CONFIG_GPIOLIB=y +CONFIG_GPIO_XILINX=y +CONFIG_POWER_RESET=y +CONFIG_POWER_RESET_GPIO_RESTART=y +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_PANIC=y +# CONFIG_IOMMU_SUPPORT is not set +CONFIG_TMPFS=y +CONFIG_PRINTK_TIME=y +CONFIG_STRIP_ASM_SYMS=y +CONFIG_DEBUG_SECTION_MISMATCH=y diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig index aead145dd91d12..a7843dd9f8bd83 100644 --- a/drivers/net/ethernet/Kconfig +++ b/drivers/net/ethernet/Kconfig @@ -120,6 +120,7 @@ config LANTIQ_XRX200 source "drivers/net/ethernet/adi/Kconfig" source "drivers/net/ethernet/litex/Kconfig" +source "drivers/net/ethernet/lowrisc/Kconfig" source "drivers/net/ethernet/marvell/Kconfig" source "drivers/net/ethernet/mediatek/Kconfig" source "drivers/net/ethernet/mellanox/Kconfig" diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile index 998dd628b202ce..6813a2f2513cd2 100644 --- a/drivers/net/ethernet/Makefile +++ b/drivers/net/ethernet/Makefile @@ -57,6 +57,7 @@ obj-$(CONFIG_KORINA) += korina.o obj-$(CONFIG_LANTIQ_ETOP) += lantiq_etop.o obj-$(CONFIG_LANTIQ_XRX200) += lantiq_xrx200.o obj-$(CONFIG_NET_VENDOR_LITEX) += litex/ +obj-$(CONFIG_NET_VENDOR_LOWRISC) += lowrisc/ obj-$(CONFIG_NET_VENDOR_MARVELL) += marvell/ obj-$(CONFIG_NET_VENDOR_MEDIATEK) += mediatek/ obj-$(CONFIG_NET_VENDOR_MELLANOX) += mellanox/ diff --git a/drivers/net/ethernet/lowrisc/Kconfig b/drivers/net/ethernet/lowrisc/Kconfig new file mode 100644 index 00000000000000..ab95f70ab9d811 --- /dev/null +++ b/drivers/net/ethernet/lowrisc/Kconfig @@ -0,0 +1,26 @@ +# +# Xilink device configuration +# + +config NET_VENDOR_LOWRISC + bool "Lowrisc devices" + default y + depends on RISCV + help + If you have a network (Ethernet) card belonging to this class, say Y. + + Note that the answer to this question doesn't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about Lowrisc devices. If you say Y, you will be asked + for your specific card in the following questions. + +if NET_VENDOR_LOWRISC + +config LOWRISC_DIGILENT_100MHZ + tristate "Lowrisc 100MHz Ethernet Nexys4_DDR support" + depends on RISCV + select PHYLIB + help + This driver supports the 100MHz Ethernet for Nexys4_DDR Digilent boards from Lowrisc. + +endif # NET_VENDOR_LOWRISC diff --git a/drivers/net/ethernet/lowrisc/Makefile b/drivers/net/ethernet/lowrisc/Makefile new file mode 100644 index 00000000000000..4eb3adee161d5f --- /dev/null +++ b/drivers/net/ethernet/lowrisc/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the Lowrisc network device driver. +# + +obj-$(CONFIG_LOWRISC_DIGILENT_100MHZ) += lowrisc_100MHz.o +CFLAGS_lowrisc_100MHz.o := -DDEBUG diff --git a/drivers/net/ethernet/lowrisc/lowrisc_100MHz.c b/drivers/net/ethernet/lowrisc/lowrisc_100MHz.c new file mode 100644 index 00000000000000..3dfb874b1f75cf --- /dev/null +++ b/drivers/net/ethernet/lowrisc/lowrisc_100MHz.c @@ -0,0 +1,800 @@ +/* + * Lowrisc Ether100MHz Linux driver for the Lowrisc Ethernet 100MHz device. + * + * This is an experimental driver which is based on the original emac_lite + * driver from John Williams . + * + * 2007 - 2013 (c) Xilinx, Inc. + * PHY control portions copyright (C) 2015 Microchip Technology + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lowrisc_100MHz.h" + +#define DRIVER_AUTHOR "WOOJUNG HUH " +#define DRIVER_DESC "Microchip LAN8720 PHY driver" +#define DRIVER_NAME "lowrisc-eth" + +/* General Ethernet Definitions */ +#define XEL_ARP_PACKET_SIZE 28 /* Max ARP packet size */ +#define XEL_HEADER_IP_LENGTH_OFFSET 16 /* IP Length Offset */ + +#define TX_TIMEOUT (60 * HZ) /* Tx timeout is 60 seconds. */ + +/** + * struct net_local - Our private per device data + * @ndev: instance of the network device + * @mii_bus: pointer to the MII bus + */ +struct net_local { + struct mdiobb_ctrl ctrl; /* must be first for bitbang driver to work */ + void __iomem *ioaddr; + struct net_device *ndev; + u32 msg_enable; + + struct mii_bus *mii_bus; + int last_duplex; + int last_carrier; + + /* Spinlock */ + struct mutex lock; + uint32_t last_mdio_gpio; + int irq; + + struct napi_struct napi; +}; + +static inline void eth_write(struct net_local *priv, size_t addr, int data) +{ + volatile uint64_t *eth_base = (volatile uint64_t *)(priv->ioaddr); + eth_base[addr >> 3] = data; +} + +static inline void eth_copyout(struct net_local *priv, uint8_t *data, int len) +{ + int i, rnd = ((len - 1) | 7) + 1; + volatile uint64_t *eth_base = (volatile uint64_t *)(priv->ioaddr); + if (!(((size_t)data) & 7)) { + uint64_t *ptr = (uint64_t *)data; + for (i = 0; i < rnd / 8; i++) + eth_base[TXBUFF_OFFSET / 8 + i] = ptr[i]; + } else // We can't unfortunately rely on the skb being word aligned + { + uint64_t notptr; + for (i = 0; i < rnd / 8; i++) { + memcpy(¬ptr, data + (i << 3), sizeof(uint64_t)); + eth_base[TXBUFF_OFFSET / 8 + i] = notptr; + } + } +} + +static inline int eth_read(struct net_local *priv, size_t addr) +{ + volatile uint64_t *eth_base = (volatile uint64_t *)(priv->ioaddr); + return eth_base[addr >> 3]; +} + +static inline void eth_copyin(struct net_local *priv, uint8_t *data, int len, + int start) +{ + int i, rnd = ((len - 1) | 7) + 1; + volatile uint64_t *eth_base = (volatile uint64_t *)(priv->ioaddr); + if (!(((size_t)data) & 7)) { + uint64_t *ptr = (uint64_t *)data; + for (i = 0; i < rnd / 8; i++) + ptr[i] = eth_base[start + i]; + } else // We can't unfortunately rely on the skb being word aligned + { + for (i = 0; i < rnd / 8; i++) { + uint64_t notptr = eth_base[start + i]; + memcpy(data + (i << 3), ¬ptr, sizeof(uint64_t)); + } + } +} + +static inline void eth_enable_irq(struct net_local *priv) +{ + volatile uint64_t *eth_base = (volatile uint64_t *)(priv->ioaddr); + eth_base[MACHI_OFFSET >> 3] |= MACHI_IRQ_EN; + mmiowb(); +} + +static inline void eth_disable_irq(struct net_local *priv) +{ + volatile uint64_t *eth_base = (volatile uint64_t *)(priv->ioaddr); + eth_base[MACHI_OFFSET >> 3] &= ~MACHI_IRQ_EN; + mmiowb(); +} + +/** + * lowrisc_update_address - Update the MAC address in the device + * @drvdata: Pointer to the Ether100MHz device private data + * @address_ptr:Pointer to the MAC address (MAC address is a 48-bit value) + * + * Tx must be idle and Rx should be idle for deterministic results. + * It is recommended that this function should be called after the + * initialization and before transmission of any packets from the device. + * The MAC address can be programmed using any of the two transmit + * buffers (if configured). + */ + +static void lowrisc_update_address(struct net_local *priv, + const u8 *address_ptr) +{ + uint32_t macaddr_lo, macaddr_hi; + memcpy(&macaddr_lo, address_ptr + 2, sizeof(uint32_t)); + memcpy(&macaddr_hi, address_ptr + 0, sizeof(uint16_t)); + mutex_lock(&priv->lock); + eth_write(priv, MACLO_OFFSET, htonl(macaddr_lo)); + eth_write(priv, MACHI_OFFSET, htons(macaddr_hi)); + eth_write(priv, RFCS_OFFSET, 8); /* use 8 buffers */ + mutex_unlock(&priv->lock); +} + +/** + * lowrisc_read_mac_address - Read the MAC address in the device + * @drvdata: Pointer to the Ether100MHz device private data + * @address_ptr:Pointer to the 6-byte buffer to receive the MAC address (MAC address is a 48-bit value) + * + * In lowrisc the starting value is programmed by the boot loader according to DIP switch [15:12] + */ + +static void lowrisc_read_mac_address(struct net_local *priv, u8 *address_ptr) +{ + uint32_t macaddr_hi, macaddr_lo; + mutex_lock(&priv->lock); + macaddr_hi = ntohs(eth_read(priv, MACHI_OFFSET) & MACHI_MACADDR_MASK); + macaddr_lo = ntohl(eth_read(priv, MACLO_OFFSET)); + mutex_unlock(&priv->lock); + memcpy(address_ptr + 2, &macaddr_lo, sizeof(uint32_t)); + memcpy(address_ptr + 0, &macaddr_hi, sizeof(uint16_t)); +} + +/** + * lowrisc_set_mac_address - Set the MAC address for this device + * @dev: Pointer to the network device instance + * @addr: Void pointer to the sockaddr structure + * + * This function copies the HW address from the sockaddr strucutre to the + * net_device structure and updates the address in HW. + * + * Return: Error if the net device is busy or 0 if the addr is set + * successfully + */ +static int lowrisc_set_mac_address(struct net_device *ndev, void *address) +{ + struct net_local *priv = netdev_priv(ndev); + struct sockaddr *addr = address; + + if (netif_running(ndev)) + return -EBUSY; + + eth_hw_addr_set(ndev, addr->sa_data); + lowrisc_update_address(priv, ndev->dev_addr); + return 0; +} + +/** + * lowrisc_tx_timeout - Callback for Tx Timeout + * @dev: Pointer to the network device + * + * This function is called when Tx time out occurs for Ether100MHz device. + */ +static void lowrisc_tx_timeout(struct net_device *ndev, unsigned int txqueue) +{ + struct net_local *priv = netdev_priv(ndev); + + dev_err(&priv->ndev->dev, "Exceeded transmit timeout of %lu ms\n", + TX_TIMEOUT * 1000UL / HZ); + + ndev->stats.tx_errors++; + + /* Reset the device */ + mutex_lock(&priv->lock); + + /* Shouldn't really be necessary, but shouldn't hurt */ + netif_stop_queue(ndev); + + /* To exclude tx timeout */ + netif_trans_update(ndev); /* prevent tx timeout */ + + /* We're all ready to go. Start the queue */ + netif_wake_queue(ndev); + mutex_unlock(&priv->lock); +} + +/** + * lowrisc_close - Close the network device + * @dev: Pointer to the network device + * + * This function stops the Tx queue, disables interrupts and frees the IRQ for + * the Ether100MHz device. + * It also disconnects the phy device associated with the Ether100MHz device. + */ +static int lowrisc_close(struct net_device *ndev) +{ + struct net_local *priv = netdev_priv(ndev); + + BUG_ON(!ndev->phydev); + + netif_stop_queue(ndev); + napi_disable(&priv->napi); + mutex_lock(&priv->lock); + eth_disable_irq(priv); + mutex_unlock(&priv->lock); + + printk("Close device, free interrupt\n"); + free_irq(priv->irq, priv); + phy_disconnect(ndev->phydev); + + mdiobus_unregister(priv->mii_bus); + free_mdio_bitbang(priv->mii_bus); + + return 0; +} + +/** + * lowrisc_remove_ndev - Free the network device + * @ndev: Pointer to the network device to be freed + * + * This function un maps the IO region of the Ether100MHz device and frees the net + * device. + */ +static void lowrisc_remove_ndev(struct net_device *ndev) +{ + if (ndev) { + free_netdev(ndev); + } +} + +static void lowrisc_phy_adjust_link(struct net_device *ndev) +{ + struct net_local *priv = netdev_priv(ndev); + struct phy_device *phy_dev = ndev->phydev; + int carrier; + + if (phy_dev->duplex != priv->last_duplex) { + if (phy_dev->duplex) { + netif_dbg(priv, link, priv->ndev, "full duplex mode\n"); + } else { + netif_dbg(priv, link, priv->ndev, "half duplex mode\n"); + } + + priv->last_duplex = phy_dev->duplex; + } + + carrier = netif_carrier_ok(ndev); + if (carrier != priv->last_carrier) { + if (carrier) + netif_dbg(priv, link, priv->ndev, "carrier OK\n"); + else + netif_dbg(priv, link, priv->ndev, "no carrier\n"); + priv->last_carrier = carrier; + } +} + +static int lowrisc_mii_probe(struct net_device *ndev) +{ + struct net_local *priv = netdev_priv(ndev); + struct phy_device *phydev = NULL; + + BUG_ON(ndev->phydev); + + phydev = phy_find_first(priv->mii_bus); + if (!phydev) { + netdev_err(ndev, "no PHY found in range address 0..%d\n", + PHY_MAX_ADDR - 1); + return -ENODEV; + } + + phy_connect_direct(ndev, phydev, lowrisc_phy_adjust_link, PHY_INTERFACE_MODE_MII); + + if (IS_ERR(phydev)) { + netdev_err(ndev, "Could not attach to PHY\n"); + return PTR_ERR(phydev); + } + + /* mask with MAC supported features */ + linkmode_copy(phydev->advertising, phydev->supported); + + phy_attached_info(phydev); + + priv->last_duplex = -1; + priv->last_carrier = -1; + + return 0; +} + +static void mdio_dir(struct mdiobb_ctrl *ctrl, int dir) +{ + struct net_local *priv = (struct net_local *) + ctrl; /* struct mdiobb_ctrl must be first in net_local for bitbang driver to work */ + volatile uint64_t *eth_base = (volatile uint64_t *)(priv->ioaddr); + if (dir) + priv->last_mdio_gpio |= MDIOCTRL_MDIOOEN_MASK; // output driving + else + priv->last_mdio_gpio &= + ~MDIOCTRL_MDIOOEN_MASK; // input receiving + eth_base[MDIOCTRL_OFFSET >> 3] = priv->last_mdio_gpio; + mmiowb(); +} + +static int mdio_get(struct mdiobb_ctrl *ctrl) +{ + int retval; + struct net_local *priv = (struct net_local *) + ctrl; /* struct mdiobb_ctrl must be first in net_local for bitbang driver to work */ + volatile uint64_t *eth_base = (volatile uint64_t *)(priv->ioaddr); + retval = eth_base[MDIOCTRL_OFFSET >> 3]; + return retval & MDIOCTRL_MDIOIN_MASK ? 1 : 0; +} + +static void mdio_set(struct mdiobb_ctrl *ctrl, int what) +{ + struct net_local *priv = (struct net_local *) + ctrl; /* struct mdiobb_ctrl must be first in net_local for bitbang driver to work */ + volatile uint64_t *eth_base = (volatile uint64_t *)(priv->ioaddr); + if (what) + priv->last_mdio_gpio |= MDIOCTRL_MDIOOUT_MASK; + else + priv->last_mdio_gpio &= ~MDIOCTRL_MDIOOUT_MASK; + eth_base[MDIOCTRL_OFFSET >> 3] = priv->last_mdio_gpio; + mmiowb(); +} + +static void mdc_set(struct mdiobb_ctrl *ctrl, int what) +{ + struct net_local *priv = (struct net_local *) + ctrl; /* struct mdiobb_ctrl must be first in net_local for bitbang driver to work */ + volatile uint64_t *eth_base = (volatile uint64_t *)(priv->ioaddr); + if (what) + priv->last_mdio_gpio |= MDIOCTRL_MDIOCLK_MASK; + else + priv->last_mdio_gpio &= ~MDIOCTRL_MDIOCLK_MASK; + eth_base[MDIOCTRL_OFFSET >> 3] = priv->last_mdio_gpio; + mmiowb(); +} + +static struct mdiobb_ops mdio_gpio_ops = { + .owner = THIS_MODULE, + .set_mdc = mdc_set, + .set_mdio_dir = mdio_dir, + .set_mdio_data = mdio_set, + .get_mdio_data = mdio_get, +}; + +static int lowrisc_mii_init(struct net_device *ndev) +{ + struct mii_bus *new_bus; + struct net_local *priv = netdev_priv(ndev); + int err = -ENXIO; + + priv->ctrl.ops = &mdio_gpio_ops; + new_bus = alloc_mdio_bitbang(&(priv->ctrl)); + + if (!new_bus) { + err = -ENOMEM; + goto err_out_1; + } + snprintf(new_bus->id, MII_BUS_ID_SIZE, "lowrisc-0"); + new_bus->name = "GPIO Bitbanged LowRISC", + + new_bus->phy_mask = ~(1 << 1); + new_bus->phy_ignore_ta_mask = 0; + + mutex_init(&(new_bus->mdio_lock)); + + priv->mii_bus = new_bus; + priv->mii_bus->priv = priv; + + /* Mask all PHYs except ID 1 (internal) */ + priv->mii_bus->phy_mask = ~(1 << 1); + + if (mdiobus_register(priv->mii_bus)) { + netif_warn(priv, probe, priv->ndev, + "Error registering mii bus\n"); + goto err_out_free_bus_2; + } + + if (lowrisc_mii_probe(ndev) < 0) { + netif_warn(priv, probe, priv->ndev, "Error probing mii bus\n"); + goto err_out_unregister_bus_3; + } + + return 0; + +err_out_unregister_bus_3: + mdiobus_unregister(priv->mii_bus); +err_out_free_bus_2: + mdiobus_free(priv->mii_bus); +err_out_1: + return err; +} +/**********************/ +/* Interrupt Handlers */ +/**********************/ + +/** + * lowrisc_ether_isr - Interrupt handler for frames received + * @priv: Pointer to the struct net_local + * + * This function allocates memory for a socket buffer, fills it with data + * received and hands it over to the TCP/IP stack. + */ + +static int lowrisc_ether_poll(struct napi_struct *napiptr, int budget) +{ + int rsr, buf, rx_count = 0; + struct net_local *priv = container_of(napiptr, struct net_local, napi); + struct net_device *ndev = priv->ndev; + rsr = eth_read(priv, RSR_OFFSET); + buf = rsr & RSR_RECV_FIRST_MASK; + /* Check if there is Rx Data available */ + while ((rsr & RSR_RECV_DONE_MASK) && (rx_count < budget)) { + int len = eth_read(priv, RPLR_OFFSET + ((buf & 7) << 3)) - + 4; /* discard FCS bytes ?? */ + if ((len >= 60) && (len <= ETH_FRAME_LEN + ETH_FCS_LEN)) { + int rnd = ((len - 1) | 7) + + 1; /* round to a multiple of 8 */ + struct sk_buff *skb = napi_alloc_skb( + &priv->napi, rnd); // drop surplus packets + if (unlikely(!skb)) { + /* Couldn't get memory, we carry on regardless and drop if necessary */ + ndev->stats.rx_dropped++; + } else { + int start = + RXBUFF_OFFSET / 8 + ((buf & 7) << 8); + skb_put(skb, + len); /* Tell the skb how much data we got */ + + eth_copyin(priv, skb->data, len, start); + skb->protocol = eth_type_trans(skb, ndev); + netif_receive_skb(skb); + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += len; + ++rx_count; + } + } else + ndev->stats.rx_errors++; + /* acknowledge, even if an error occurs, to reset irq */ + eth_write(priv, RSR_OFFSET, ++buf); + rsr = eth_read(priv, RSR_OFFSET); + } + + if (rsr & RSR_RECV_DONE_MASK) { + return 1; + } else { + napi_complete_done(&priv->napi, rx_count); + eth_enable_irq(priv); + return 0; + } +} + +static irqreturn_t lowrisc_ether_isr(int irq, void *priv_id) +{ + int rsr = 0; + struct net_local *priv = priv_id; + struct net_device *ndev = priv->ndev; + if (napi_schedule_prep(&priv->napi)) { + eth_disable_irq(priv); + __napi_schedule(&priv->napi); + } else /* we are in denial of service mode */ + do { + int buf = rsr & RSR_RECV_FIRST_MASK; + ndev->stats.rx_dropped++; + eth_write(priv, RSR_OFFSET, ++buf); + rsr = eth_read(priv, RSR_OFFSET); + } while (rsr & RSR_RECV_DONE_MASK); + return IRQ_HANDLED; +} + +static int lowrisc_get_regs_len(struct net_device __always_unused *netdev) +{ +#define LOWRISC_REGS_LEN 64 /* overestimate */ + return LOWRISC_REGS_LEN * sizeof(u32); +} + +static void lowrisc_get_regs(struct net_device *ndev, struct ethtool_regs *regs, + void *p) +{ + struct net_local *priv = netdev_priv(ndev); + struct phy_device *phy = ndev->phydev; + + u32 *regs_buff = p; + int i; + + memset(p, 0, LOWRISC_REGS_LEN * sizeof(u32)); + + regs->version = 0; + mutex_lock(&priv->lock); + for (i = 0; i < LOWRISC_REGS_LEN; i++) { + if (i >= 32) + regs_buff[i] = + eth_read(priv, MACLO_OFFSET + ((i - 32) << 3)); + else { + regs_buff[i] = phy_read(phy, i); + } + } + mutex_unlock(&priv->lock); +} + +static const struct ethtool_ops lowrisc_ethtool_ops = { + .get_regs_len = lowrisc_get_regs_len, + .get_regs = lowrisc_get_regs, + .get_link_ksettings = phy_ethtool_get_link_ksettings, + .set_link_ksettings = phy_ethtool_set_link_ksettings +}; + +/** + * lowrisc_open - Open the network device + * @dev: Pointer to the network device + * + * This function sets the MAC address, requests an IRQ and enables interrupts + * for the Ether100MHz device and starts the Tx queue. + * It also connects to the phy device, if MDIO is included in Ether100MHz device. + */ + +static int lowrisc_open(struct net_device *ndev) +{ + int retval = 0; + struct net_local *priv = netdev_priv(ndev); + ndev->ethtool_ops = &lowrisc_ethtool_ops; + + /* Set the MAC address each time opened */ + lowrisc_update_address(priv, ndev->dev_addr); + + retval = lowrisc_mii_init(ndev); + if (retval) { + dev_err(&ndev->dev, "Failed to initialize Phy\n"); + retval = -ENODEV; + return retval; + } + phy_start(ndev->phydev); + + /* Grab the IRQ */ + printk("Open device, request interrupt %d\n", priv->irq); + retval = request_irq(priv->irq, lowrisc_ether_isr, 0, ndev->name, priv); + if (retval) { + dev_err(&priv->ndev->dev, "Could not allocate interrupt %d\n", + priv->irq); + phy_disconnect(ndev->phydev); + return retval; + } + + lowrisc_update_address(priv, ndev->dev_addr); + + /* We're ready to go */ + napi_enable(&priv->napi); + netif_start_queue(ndev); + + /* enable the irq */ + eth_enable_irq(priv); + return 0; +} + +/** + * lowrisc_send - Transmit a frame + * @orig_skb: Pointer to the socket buffer to be transmitted + * @dev: Pointer to the network device + * + * This function checks if the Tx buffer of the Ether100MHz device is free to send + * data. If so, it fills the Tx buffer with data from socket buffer data, + * updates the stats and frees the socket buffer. + * Return: 0, always. + */ +static netdev_tx_t lowrisc_send(struct sk_buff *new_skb, + struct net_device *ndev) +{ + struct net_local *priv = netdev_priv(ndev); + unsigned int len = new_skb->len; + int rslt; + if (mutex_is_locked(&priv->lock)) + return NETDEV_TX_BUSY; + rslt = eth_read(priv, TPLR_OFFSET); + if (rslt & TPLR_BUSY_MASK) + return NETDEV_TX_BUSY; + eth_copyout(priv, new_skb->data, len); + eth_write(priv, TPLR_OFFSET, len); + + skb_tx_timestamp(new_skb); + + ndev->stats.tx_bytes += len; + ndev->stats.tx_packets++; + dev_consume_skb_any(new_skb); + + return NETDEV_TX_OK; +} + +static int lowrisc_mii_ioctl(struct net_device *netdev, struct ifreq *ifr, + int cmd) +{ + struct phy_device *phy = netdev->phydev; + struct mii_ioctl_data *data = if_mii(ifr); + + switch (cmd) { + case SIOCGMIIPHY: + data->phy_id = 1; + break; + case SIOCGMIIREG: + data->val_out = phy_read(phy, data->reg_num); + break; + case SIOCSMIIREG: + phy_write(phy, data->reg_num, data->val_in); + break; + default: + return -EOPNOTSUPP; + } + return 0; +} + +static struct net_device_stats *lowrisc_get_stats(struct net_device *ndev) +{ + return &ndev->stats; +} + +static void lowrisc_set_rx_mode(struct net_device *ndev) +{ + struct net_local *priv = netdev_priv(ndev); + volatile uint64_t *eth_base = (volatile uint64_t *)(priv->ioaddr); + + if (ndev->flags & IFF_PROMISC) { + /* Set promiscuous. */ + eth_base[MACHI_OFFSET >> 3] |= MACHI_ALLPKTS_MASK; + } else { + eth_base[MACHI_OFFSET >> 3] &= ~MACHI_ALLPKTS_MASK; + } +} +static struct net_device_ops lowrisc_netdev_ops = { + .ndo_open = lowrisc_open, + .ndo_stop = lowrisc_close, + .ndo_start_xmit = lowrisc_send, + .ndo_get_stats = lowrisc_get_stats, + .ndo_set_rx_mode = lowrisc_set_rx_mode, + .ndo_set_mac_address = lowrisc_set_mac_address, + .ndo_tx_timeout = lowrisc_tx_timeout, + .ndo_do_ioctl = lowrisc_mii_ioctl, +}; + +/** + * lowrisc_of_probe - Probe method for the Ether100MHz device. + * @ofdev: Pointer to OF device structure + * @match: Pointer to the structure used for matching a device + * + * This function probes for the Ether100MHz device in the device tree. + * It initializes the driver data structure and the hardware, sets the MAC + * address and registers the network device. + * It also registers a mii_bus for the Ether100MHz device, if MDIO is included + * in the device. + * + * Return: 0, if the driver is bound to the Ether100MHz device, or + * a negative error if there is failure. + */ +static int lowrisc_100MHz_probe(struct platform_device *ofdev) +{ + struct net_device *ndev = NULL; + struct net_local *priv = NULL; + struct device *dev = &ofdev->dev; + struct resource *lowrisc_ethernet; + unsigned char mac_address[7]; + int rc = 0; + + /* Create an ethernet device instance */ + ndev = devm_alloc_etherdev(dev, sizeof(struct net_local)); + if (!ndev) + return -ENOMEM; + + dev_set_drvdata(dev, ndev); + SET_NETDEV_DEV(ndev, &ofdev->dev); + + priv = netdev_priv(ndev); + priv->ndev = ndev; + priv->ioaddr = devm_platform_get_and_ioremap_resource(ofdev, 0, &lowrisc_ethernet); + mutex_init(&priv->lock); + + ndev->netdev_ops = &lowrisc_netdev_ops; + ndev->flags &= ~IFF_MULTICAST; + ndev->watchdog_timeo = TX_TIMEOUT; + netif_napi_add_weight(ndev, &priv->napi, lowrisc_ether_poll, 64); + + printk("lowrisc-digilent-ethernet: Lowrisc ethernet platform (%llX-%llX) mapped to %lx\n", + lowrisc_ethernet[0].start, lowrisc_ethernet[0].end, + (size_t)(priv->ioaddr)); + + priv->irq = platform_get_irq(ofdev, 0); + + /* get the MAC address set by the boot loader */ + lowrisc_read_mac_address(priv, mac_address); + eth_hw_addr_set(ndev, mac_address); + dev_info(dev, "MAC Address: %pM\n", mac_address); + + /* Set the MAC address in the Ether100MHz device */ + lowrisc_update_address(priv, ndev->dev_addr); + + /* Finally, register the device */ + rc = register_netdev(ndev); + if (rc) { + dev_err(dev, "Cannot register network device, aborting\n"); + goto error; + } + + dev_info(dev, "Lowrisc Ether100MHz registered\n"); + + return 0; + +error: + lowrisc_remove_ndev(ndev); + return rc; +} + +/* Match table for OF platform binding */ +static const struct of_device_id lowrisc_100MHz_of_match[] = { + { .compatible = DRIVER_NAME }, + { /* end of list */ }, +}; +MODULE_DEVICE_TABLE(of, lowrisc_100MHz_of_match); + +static void lowrisc_100MHz_free(struct platform_device *of_dev) +{ + struct resource *iomem = + platform_get_resource(of_dev, IORESOURCE_MEM, 0); + release_mem_region(iomem->start, resource_size(iomem)); +} + +static void lowrisc_100MHz_unregister(struct platform_device *of_dev) +{ + lowrisc_100MHz_free(of_dev); +} + +static struct platform_driver lowrisc_100MHz_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = lowrisc_100MHz_of_match, + }, + .probe = lowrisc_100MHz_probe, + .remove = lowrisc_100MHz_unregister, +}; + +module_platform_driver(lowrisc_100MHz_driver); + +MODULE_AUTHOR("Jonathan Kimmitt"); +MODULE_DESCRIPTION("Lowrisc Ethernet 100MHz driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/lowrisc/lowrisc_100MHz.h b/drivers/net/ethernet/lowrisc/lowrisc_100MHz.h new file mode 100644 index 00000000000000..9d227c10dd36c5 --- /dev/null +++ b/drivers/net/ethernet/lowrisc/lowrisc_100MHz.h @@ -0,0 +1,55 @@ +// See LICENSE for license details. + +#ifndef ETH_HEADER_H +#define ETH_HEADER_H + +/* Register offsets for the LowRISC Ethernet Core */ + +/* Register offsets (in bytes) for the LowRISC Core */ +#define TXBUFF_OFFSET 0x1000 /* Transmit Buffer */ + +#define MACLO_OFFSET 0x0800 /* MAC address low 32-bits */ +#define MACHI_OFFSET 0x0808 /* MAC address high 16-bits and MAC ctrl */ +#define TPLR_OFFSET 0x0810 /* Tx packet length */ +#define TFCS_OFFSET 0x0818 /* Tx frame check sequence register */ +#define MDIOCTRL_OFFSET 0x0820 /* MDIO Control Register */ +#define RFCS_OFFSET 0x0828 /* Rx frame check sequence register(read) and last register(write) */ +#define RSR_OFFSET 0x0830 /* Rx status and reset register */ +#define RBAD_OFFSET 0x0838 /* Rx bad frame and bad fcs register arrays */ +#define RPLR_OFFSET 0x0840 /* Rx packet length register array */ + +#define RXBUFF_OFFSET 0x4000 /* Receive Buffer */ + +/* MAC Ctrl Register (MACHI) Bit Masks */ +#define MACHI_MACADDR_MASK 0x0000FFFF /* MAC high 16-bits mask */ +#define MACHI_COOKED_MASK 0x00010000 /* obsolete flag */ +#define MACHI_LOOPBACK_MASK 0x00020000 /* Rx loopback packets */ +#define MACHI_ALLPKTS_MASK 0x00400000 /* Rx all packets (promiscuous mode) */ +#define MACHI_IRQ_EN 0x00800000 /* Rx packet interrupt enable */ + +/* MDIO Control Register Bit Masks */ +#define MDIOCTRL_MDIOCLK_MASK 0x00000001 /* MDIO Clock Mask */ +#define MDIOCTRL_MDIOOUT_MASK 0x00000002 /* MDIO Output Mask */ +#define MDIOCTRL_MDIOOEN_MASK 0x00000004 /* MDIO Output Enable Mask, 3-state enable, high=input, low=output */ +#define MDIOCTRL_MDIORST_MASK 0x00000008 /* MDIO Input Mask */ +#define MDIOCTRL_MDIOIN_MASK 0x00000008 /* MDIO Input Mask */ + +/* Transmit Status Register (TPLR) Bit Masks */ +#define TPLR_FRAME_ADDR_MASK 0x0FFF0000 /* Tx frame address */ +#define TPLR_PACKET_LEN_MASK 0x00000FFF /* Tx packet length */ +#define TPLR_BUSY_MASK 0x80000000 /* Tx busy mask */ + +/* Receive Status Register (RSR) */ +#define RSR_RECV_FIRST_MASK 0x0000000F /* first available buffer (static) */ +#define RSR_RECV_NEXT_MASK 0x000000F0 /* current rx buffer (volatile) */ +#define RSR_RECV_LAST_MASK 0x00000F00 /* last available rx buffer (static) */ +#define RSR_RECV_DONE_MASK 0x00001000 /* Rx complete */ +#define RSR_RECV_IRQ_MASK 0x00002000 /* Rx irq bit */ + +/* General Ethernet Definitions */ +#define HEADER_OFFSET 12 /* Offset to length field */ +#define HEADER_SHIFT 16 /* Shift value for length */ +#define ARP_PACKET_SIZE 28 /* Max ARP packet size */ +#define HEADER_IP_LENGTH_OFFSET 16 /* IP Length Offset */ + +#endif