blob: c1fe7791ee6a9d7975b48a7433e4a11088f03ccf [file] [log] [blame]
/*
* Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: GPL-2.0-only
*/
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <platsupport/driver_module.h>
#include <platsupport/fdt.h>
#include <ethdrivers/gen_config.h>
#include <ethdrivers/odroidc2.h>
#include <ethdrivers/raw.h>
#include <ethdrivers/helpers.h>
#include <utils/util.h>
#include "uboot/common.h"
#include "uboot/net.h"
#include "uboot/miiphy.h"
#include "io.h"
#include "uboot/netdev.h"
#include "unimplemented.h"
#include "uboot/designware.h"
#define BUF_SIZE 2048
#define MAC_LEN 6
#define DMA_ALIGN 64
struct descriptor {
uint32_t txrx_status;
uint32_t dmamac_cntl;
uint32_t dmamac_addr;
uint32_t dmamac_next;
};
struct odroidc2_eth_data {
struct eth_device *eth_dev;
uint8_t mac[MAC_LEN];
uintptr_t tx_ring_phys;
uintptr_t rx_ring_phys;
volatile struct descriptor *tx_ring;
volatile struct descriptor *rx_ring;
unsigned int rx_size;
unsigned int tx_size;
void **rx_cookies; /* Array of rx_size elements of type 'void *' */
void **tx_cookies; /* Array of tx_size elements of type 'void *' */
unsigned int rx_remain;
unsigned int tx_remain;
unsigned int *tx_lengths;
/* Indexes used to keep track of the head and tail of the descriptor queues */
unsigned int rdt, rdh, tdt, tdh;
};
static bool enabled = false;
static void low_level_init(struct eth_driver *driver, uint8_t *mac, int *mtu)
{
struct odroidc2_eth_data *dev = (struct odroidc2_eth_data *)driver->eth_data;
memcpy(mac, dev->mac, MAC_LEN);
*mtu = MAX_PKT_SIZE;
}
static void fill_rx_bufs(struct eth_driver *driver)
{
struct odroidc2_eth_data *dev = (struct odroidc2_eth_data *)driver->eth_data;
__sync_synchronize();
while (dev->rx_remain > 0) {
/* request a buffer */
void *cookie = NULL;
int next_rdt = (dev->rdt + 1) % dev->rx_size;
// This fn ptr is either lwip_allocate_rx_buf or lwip_pbuf_allocate_rx_buf (in src/lwip.c)
uintptr_t phys = driver->i_cb.allocate_rx_buf ? driver->i_cb.allocate_rx_buf(driver->cb_cookie, BUF_SIZE, &cookie) : 0;
if (!phys) {
// NOTE: This condition could happen if
// CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS < CONFIG_LIB_ETHDRIVER_RX_DESC_COUNT
break;
}
dev->rx_cookies[dev->rdt] = cookie;
dev->rx_ring[dev->rdt].dmamac_addr = phys;
dev->rx_ring[dev->rdt].dmamac_cntl = (MAC_MAX_FRAME_SZ & DESC_RXCTRL_SIZE1MASK) | DESC_RXCTRL_RXCHAIN;
dev->rx_ring[dev->rdt].txrx_status = DESC_RXSTS_OWNBYDMA;
__sync_synchronize();
dev->rdt = next_rdt;
dev->rx_remain--;
}
__sync_synchronize();
/* NOTE Maybe check if receiving isn't enabled? */
if (enabled) {
designware_start_receive(dev->eth_dev);
}
}
static void free_desc_ring(struct odroidc2_eth_data *dev, ps_dma_man_t *dma_man)
{
if (dev->rx_ring) {
dma_unpin_free(dma_man, (void *)dev->rx_ring, sizeof(struct descriptor) * dev->rx_size);
dev->rx_ring = NULL;
}
if (dev->tx_ring) {
dma_unpin_free(dma_man, (void *)dev->tx_ring, sizeof(struct descriptor) * dev->tx_size);
dev->tx_ring = NULL;
}
if (dev->rx_cookies) {
free(dev->rx_cookies);
dev->rx_cookies = NULL;
}
if (dev->tx_cookies) {
free(dev->tx_cookies);
dev->tx_cookies = NULL;
}
if (dev->tx_lengths) {
free(dev->tx_lengths);
dev->tx_lengths = NULL;
}
}
static int initialize_desc_ring(struct odroidc2_eth_data *dev, ps_dma_man_t *dma_man)
{
dma_addr_t rx_ring = dma_alloc_pin(dma_man, sizeof(struct descriptor) * dev->rx_size, 0, DMA_ALIGN);
if (!rx_ring.phys) {
LOG_ERROR("Failed to allocate rx_ring");
return -1;
}
dev->rx_ring = rx_ring.virt;
dev->rx_ring_phys = rx_ring.phys;
dma_addr_t tx_ring = dma_alloc_pin(dma_man, sizeof(struct descriptor) * dev->tx_size, 0, DMA_ALIGN);
if (!tx_ring.phys) {
LOG_ERROR("Failed to allocate tx_ring");
free_desc_ring(dev, dma_man);
return -1;
}
ps_dma_cache_clean_invalidate(dma_man, rx_ring.virt, sizeof(struct descriptor) * dev->rx_size);
ps_dma_cache_clean_invalidate(dma_man, tx_ring.virt, sizeof(struct descriptor) * dev->tx_size);
dev->rx_cookies = malloc(sizeof(void *) * dev->rx_size);
dev->tx_cookies = malloc(sizeof(void *) * dev->tx_size);
dev->tx_lengths = malloc(sizeof(unsigned int) * dev->tx_size);
if (!dev->rx_cookies || !dev->tx_cookies || !dev->tx_lengths) {
if (dev->rx_cookies) {
free(dev->rx_cookies);
}
if (dev->tx_cookies) {
free(dev->tx_cookies);
}
if (dev->tx_lengths) {
free(dev->tx_lengths);
}
LOG_ERROR("Failed to malloc");
free_desc_ring(dev, dma_man);
return -1;
}
dev->tx_ring = tx_ring.virt;
dev->tx_ring_phys = tx_ring.phys;
/* Remaining needs to be 2 less than size as we cannot actually enqueue size many descriptors,
* since then the head and tail pointers would be equal, indicating empty. */
dev->rx_remain = dev->rx_size - 2;
dev->tx_remain = dev->tx_size - 2;
dev->rdt = dev->rdh = dev->tdt = dev->tdh = 0;
uintptr_t next_phys = dev->tx_ring_phys;
for (unsigned int i = 0; i < dev->tx_size; i++) {
next_phys += sizeof(struct descriptor);
dev->tx_ring[i].dmamac_addr = 0;
if (i == (dev->tx_size - 1)) {
dev->tx_ring[i].dmamac_next = (uint32_t)(dev->tx_ring_phys & 0xFFFFFFFF);
} else {
dev->tx_ring[i].dmamac_next = (uint32_t)(next_phys & 0xFFFFFFFF);
}
dev->tx_ring[i].dmamac_cntl = DESC_TXCTRL_TXCHAIN;
dev->tx_ring[i].txrx_status = 0;
}
next_phys = dev->rx_ring_phys;
for (unsigned int i = 0; i < dev->rx_size; i++) {
next_phys += sizeof(struct descriptor);
dev->rx_ring[i].dmamac_addr = 0;
if (i == (dev->rx_size - 1)) {
dev->rx_ring[i].dmamac_next = (uint32_t)(dev->rx_ring_phys & 0xFFFFFFFF);
} else {
dev->rx_ring[i].dmamac_next = (uint32_t)(next_phys & 0xFFFFFFFF);
}
dev->rx_ring[i].dmamac_cntl = (MAC_MAX_FRAME_SZ & DESC_RXCTRL_SIZE1MASK) | DESC_RXCTRL_RXCHAIN;
dev->rx_ring[i].txrx_status = 0;
}
__sync_synchronize();
return 0;
}
static void complete_rx(struct eth_driver *eth_driver)
{
struct odroidc2_eth_data *dev = (struct odroidc2_eth_data *)eth_driver->eth_data;
unsigned int rdt = dev->rdt;
while (dev->rdh != rdt) {
unsigned int status = dev->rx_ring[dev->rdh].txrx_status;
/* Ensure no memory references get ordered before we checked the descriptor was written back */
__sync_synchronize();
if (status & DESC_RXSTS_OWNBYDMA) {
/* not complete yet */
break;
}
void *cookie = dev->rx_cookies[dev->rdh];
unsigned int len = (status & DESC_RXSTS_FRMLENMSK) >> DESC_RXSTS_FRMLENSHFT;
/* update rdh */
dev->rdh = (dev->rdh + 1) % dev->rx_size;
dev->rx_remain++;
/* Give the buffers back */
eth_driver->i_cb.rx_complete(eth_driver->cb_cookie, 1, &cookie, &len);
}
/* NOTE Maybe re-enable the Ethernet device for RX if there are still descriptors? */
}
static void complete_tx(struct eth_driver *driver)
{
struct odroidc2_eth_data *dev = (struct odroidc2_eth_data *)driver->eth_data;
while (dev->tdh != dev->tdt) {
unsigned int i;
for (i = 0; i < dev->tx_lengths[dev->tdh]; i++) {
if (dev->tx_ring[dev->tdh + i].txrx_status & DESC_TXSTS_OWNBYDMA) {
/* Not yet complete */
return;
}
}
/* do not let memory loads happen before our checking of the descriptor write back */
__sync_synchronize();
/* increase where we believe tdh to be */
void *cookie = dev->tx_cookies[dev->tdh];
dev->tx_remain += dev->tx_lengths[dev->tdh];
dev->tdh = (dev->tdh + dev->tx_lengths[dev->tdh]) % dev->tx_size;
/* give the buffer back */
driver->i_cb.tx_complete(driver->cb_cookie, cookie);
}
/* NOTE Maybe re-enable the Ethernet device for TX if there are still descriptors? */
}
static void print_state(struct eth_driver *eth_driver)
{
ZF_LOGE("Not implemented");
}
static void handle_irq(struct eth_driver *driver, int irq)
{
struct odroidc2_eth_data *eth_data = (struct odroidc2_eth_data *)driver->eth_data;
uint32_t status = 0;
designware_interrupt_status(eth_data->eth_dev, &status);
designware_ack(eth_data->eth_dev, status);
if (status & DMA_INTR_ENA_TIE) {
complete_tx(driver);
}
if (status & DMA_INTR_ENA_RIE) {
complete_rx(driver);
fill_rx_bufs(driver);
}
if (status & DMA_INTR_ABNORMAL) {
ZF_LOGE("Ethernet device crashed with an abnormal interrupt, reporting...");
if (status & DMA_INTR_ENA_FBE) {
ZF_LOGE(" Ethernet device fatal bus error");
}
if (status & DMA_INTR_ENA_UNE) {
ZF_LOGE(" Ethernet device TX underflow");
}
ZF_LOGF("Done.");
}
}
/* This is a platsuport IRQ interface IRQ handler wrapper for handle_irq() */
static void eth_irq_handle(void *data, ps_irq_acknowledge_fn_t acknowledge_fn, void *ack_data)
{
ZF_LOGF_IF(data == NULL, "Passed in NULL for the data");
struct eth_driver *driver = data;
/* handle_irq doesn't really expect an IRQ number */
handle_irq(driver, 0);
int error = acknowledge_fn(ack_data);
if (error) {
LOG_ERROR("Failed to acknowledge the Ethernet device's IRQ");
}
}
static void raw_poll(struct eth_driver *driver)
{
complete_rx(driver);
complete_tx(driver);
fill_rx_bufs(driver);
}
static int raw_tx(struct eth_driver *driver, unsigned int num, uintptr_t *phys, unsigned int *len, void *cookie)
{
struct odroidc2_eth_data *dev = (struct odroidc2_eth_data *)driver->eth_data;
/* Ensure we have room */
if (dev->tx_remain < num) {
/* try and complete some */
complete_tx(driver);
if (dev->tx_remain < num) {
return ETHIF_TX_FAILED;
}
}
unsigned int i;
__sync_synchronize();
for (i = 0; i < num; i++) {
unsigned int ring = (dev->tdt + i) % dev->tx_size;
dev->tx_ring[ring].dmamac_addr = phys[i];
dev->tx_ring[ring].dmamac_cntl = DESC_TXCTRL_TXCHAIN;
dev->tx_ring[ring].dmamac_cntl |= (len[i] << DESC_TXCTRL_SIZE1SHFT) & DESC_TXCTRL_SIZE1MASK;
if (i == 0) {
dev->tx_ring[ring].dmamac_cntl |= DESC_TXCTRL_TXFIRST;
}
if (i == (num - 1)) {
dev->tx_ring[ring].dmamac_cntl |= DESC_TXCTRL_TXLAST;
}
dev->tx_ring[ring].dmamac_cntl |= DESC_TXCTRL_TXINT;
dev->tx_ring[ring].txrx_status = DESC_TXSTS_OWNBYDMA;
}
dev->tx_cookies[dev->tdt] = cookie;
dev->tx_lengths[dev->tdt] = num;
dev->tdt = (dev->tdt + num) % dev->tx_size;
dev->tx_remain -= num;
__sync_synchronize();
/* NOTE Maybe check if it's in the middle of sending? */
designware_start_send(dev->eth_dev);
return ETHIF_TX_ENQUEUED;
}
static void get_mac(struct eth_driver *driver, uint8_t *mac)
{
struct odroidc2_eth_data *eth_data = (struct odroidc2_eth_data *) driver->eth_data;
memcpy(mac, eth_data->mac, MAC_LEN);
}
static struct raw_iface_funcs iface_fns = {
.raw_handleIRQ = handle_irq,
.print_state = print_state,
.low_level_init = low_level_init,
.raw_tx = raw_tx,
.raw_poll = raw_poll,
.get_mac = get_mac
};
int ethif_odroidc2_init(struct eth_driver *eth_driver, ps_io_ops_t io_ops, void *config)
{
int err;
struct odroidc2_eth_data *eth_data = NULL;
struct eth_device *eth_dev;
if (config == NULL) {
ZF_LOGE("Cannot get platform info; passed in config pointer NULL");
goto error;
}
struct arm_eth_plat_config *plat_config = (struct arm_eth_plat_config *)config;
unsigned long base_addr = (unsigned long)((uintptr_t) plat_config->buffer_addr);
eth_data = (struct odroidc2_eth_data *)malloc(sizeof(struct odroidc2_eth_data));
if (eth_data == NULL) {
ZF_LOGE("Failed to allocate eth data struct");
goto error;
}
eth_data->tx_size = CONFIG_LIB_ETHDRIVER_TX_DESC_COUNT;
eth_data->rx_size = CONFIG_LIB_ETHDRIVER_RX_DESC_COUNT;
eth_driver->eth_data = eth_data;
eth_driver->dma_alignment = DMA_ALIGN;
eth_driver->i_fn = iface_fns;
err = initialize_desc_ring(eth_data, &io_ops.dma_manager);
if (err) {
LOG_ERROR("Failed to allocate descriptor rings");
goto error;
}
/* Initialise the MII abstraction layer */
miiphy_init();
/* Initialise the actual Realtek PHY */
phy_init();
/* Do some more PHY init, setup structures */
eth_dev = malloc(sizeof(*eth_dev));
if (NULL == eth_dev) {
ZF_LOGE("Failed to allocate eth_dev structure");
goto error;
}
int ret = designware_initialize(base_addr, 0, eth_dev);
if (ret != 0) {
ZF_LOGE("Failed: designware_initialize.");
goto error;
}
eth_data->eth_dev = eth_dev;
/* We must read the MAC address out of the device after the register
* addresses have been initialized (designware_initialize), but before
* initializing the hardware (uboot_eth_dev.init), because initializing
* the hardware involves a soft reset which will wipe the MAC address
* which was configured by u-boot */
ret = designware_read_hwaddr(eth_dev, eth_data->mac);
if (ret != 0) {
ZF_LOGE("Failed: designware_read_hwaddr.");
goto error;
}
/* NOTE: This line determines what our MAC address will be, it is
* internally programmed into the device during the next init step*/
memcpy(eth_dev->enetaddr, eth_data->mac, 6);
/* Bring up the interface */
ret = designware_startup(eth_dev);
if (ret != 0) {
ZF_LOGE("Failed: designware_startup");
goto error;
}
ret = designware_write_descriptors(eth_dev, eth_data->tx_ring_phys, eth_data->rx_ring_phys);
if (ret != 0) {
ZF_LOGE("Failed: designware_write_descriptors");
goto error;
}
fill_rx_bufs(eth_driver);
enabled = true;
/* Last step, enable it */
ret = designware_enable(eth_dev);
if (ret != 0) {
ZF_LOGE("Failed: designware_enable");
}
return 0;
error:
if (eth_data) {
free(eth_data);
}
if (eth_dev) {
free(eth_dev);
}
free_desc_ring(eth_data, &io_ops.dma_manager);
return -1;
}
int ethif_odroidc2_init_module(ps_io_ops_t *io_ops, const char *device_path)
{
struct arm_eth_plat_config plat_config;
struct eth_driver *eth_driver;
int error = ps_calloc(&io_ops->malloc_ops, 1, sizeof(*eth_driver), (void **) &eth_driver);
if (error) {
ZF_LOGE("Failed to allocate memory for the Ethernet driver");
return -ENOMEM;
}
ps_fdt_cookie_t *cookie = NULL;
error = ps_fdt_read_path(&io_ops->io_fdt, &io_ops->malloc_ops, device_path, &cookie);
if (error) {
ZF_LOGE("Failed to read the path of the Ethernet driver");
return -ENODEV;
}
void *mapped_addr = ps_fdt_index_map_register(io_ops, cookie, 0, NULL);
if (mapped_addr == NULL) {
ZF_LOGE("Failed to map the registers");
return -ENODEV;
}
irq_id_t eth_irq_id = ps_fdt_index_register_irq(io_ops, cookie, 0, eth_irq_handle, eth_driver);
if (eth_irq_id < 0) {
ZF_LOGE("Failed to register the Ethernet's IRQ");
return -ENODEV;
}
plat_config.buffer_addr = mapped_addr;
plat_config.prom_mode = 1;
error = ethif_odroidc2_init(eth_driver, *io_ops, &plat_config);
if (error) {
ZF_LOGE("Failed to initialise the Ethernet driver");
return -ENODEV;
}
return ps_interface_register(&io_ops->interface_registration_ops, PS_ETHERNET_INTERFACE, eth_driver, NULL);
}
static const char *compatible_strings[] = {
"amlogic,meson-gx-dwmac",
"amlogic,meson-gxbb-dwmac",
"snps,dwmac",
NULL
};
PS_DRIVER_MODULE_DEFINE(odroidc2_ethernet, compatible_strings, ethif_odroidc2_init_module);