| /* |
| * Copyright 2017, DornerWorks |
| * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) |
| * Copyright 2020, HENSOLDT Cyber GmbH |
| * |
| * SPDX-License-Identifier: GPL-2.0-only |
| */ |
| |
| #include <platsupport/driver_module.h> |
| #include <platsupport/fdt.h> |
| #include <ethdrivers/gen_config.h> |
| #include <ethdrivers/imx6.h> |
| #include <ethdrivers/raw.h> |
| #include <ethdrivers/helpers.h> |
| #include <ethdrivers/plat/eth_plat.h> |
| #include <string.h> |
| #include <utils/util.h> |
| #include "enet.h" |
| #include "ocotp_ctrl.h" |
| #include "uboot/fec_mxc.h" |
| #include "uboot/miiphy.h" |
| #include "uboot/imx_board.h" |
| #include "uboot/micrel.h" |
| #include "unimplemented.h" |
| |
| #define IRQ_MASK (NETIRQ_RXF | NETIRQ_TXF | NETIRQ_EBERR) |
| #define BUF_SIZE MAX_PKT_SIZE |
| #define DMA_ALIGN 32 |
| |
| struct descriptor { |
| /* NOTE: little endian packing: len before stat */ |
| #if BYTE_ORDER == LITTLE_ENDIAN |
| uint16_t len; |
| uint16_t stat; |
| #elif BYTE_ORDER == BIG_ENDIAN |
| uint16_t stat; |
| uint16_t len; |
| #else |
| #error Could not determine endianess |
| #endif |
| uint32_t phys; |
| }; |
| |
| typedef struct { |
| unsigned int cnt; |
| unsigned int remain; |
| unsigned int tail; |
| unsigned int head; |
| volatile struct descriptor *descr; |
| uintptr_t phys; |
| void **cookies; /* Array with tx/tx size elements of type 'void *' */ |
| } ring_ctx_t; |
| |
| |
| /* we basically extend 'struct eth_driver' here */ |
| typedef struct { |
| struct eth_driver eth_drv; |
| void *mapped_peripheral; |
| irq_id_t irq_id; |
| struct enet *enet; |
| struct phy_device *phy; |
| ring_ctx_t tx; |
| ring_ctx_t rx; |
| unsigned int *tx_lengths; |
| } imx6_eth_driver_t; |
| |
| /* Receive descriptor status */ |
| #define RXD_EMPTY BIT(15) /* Buffer has no data. Waiting for reception. */ |
| #define RXD_OWN0 BIT(14) /* Receive software ownership. R/W by user */ |
| #define RXD_WRAP BIT(13) /* Next buffer is found in ENET_RDSR */ |
| #define RXD_OWN1 BIT(12) /* Receive software ownership. R/W by user */ |
| #define RXD_LAST BIT(11) /* Last buffer in frame. Written by the uDMA. */ |
| #define RXD_MISS BIT( 8) /* Frame does not match MAC (promiscuous mode) */ |
| #define RXD_BROADCAST BIT( 7) /* frame is a broadcast frame */ |
| #define RXD_MULTICAST BIT( 6) /* frame is a multicast frame */ |
| #define RXD_BADLEN BIT( 5) /* Incoming frame was larger than RCR[MAX_FL] */ |
| #define RXD_BADALIGN BIT( 4) /* Frame length does not align to a byte */ |
| #define RXD_CRCERR BIT( 2) /* The frame has a CRC error */ |
| #define RXD_OVERRUN BIT( 1) /* FIFO overrun */ |
| #define RXD_TRUNC BIT( 0) /* Receive frame > TRUNC_FL */ |
| |
| #define RXD_ERROR (RXD_BADLEN | RXD_BADALIGN | RXD_CRCERR |\ |
| RXD_OVERRUN | RXD_TRUNC) |
| |
| /* Transmit descriptor status */ |
| #define TXD_READY BIT(15) /* buffer in use waiting to be transmitted */ |
| #define TXD_OWN0 BIT(14) /* Receive software ownership. R/W by user */ |
| #define TXD_WRAP BIT(13) /* Next buffer is found in ENET_TDSR */ |
| #define TXD_OWN1 BIT(12) /* Receive software ownership. R/W by user */ |
| #define TXD_LAST BIT(11) /* Last buffer in frame. Written by the uDMA. */ |
| #define TXD_ADDCRC BIT(10) /* Append a CRC to the end of the frame */ |
| #define TXD_ADDBADCRC BIT( 9) /* Append a bad CRC to the end of the frame */ |
| |
| static imx6_eth_driver_t *imx6_eth_driver(struct eth_driver *driver) |
| { |
| assert(driver); |
| |
| /* we have simply extended the structure */ |
| assert(driver == driver->eth_data); |
| return (imx6_eth_driver_t *)driver; |
| } |
| |
| struct enet *get_enet_from_driver(struct eth_driver *driver) |
| { |
| assert(driver); |
| |
| imx6_eth_driver_t *dev = imx6_eth_driver(driver); |
| assert(dev); |
| return dev->enet; |
| } |
| |
| static void get_mac(struct eth_driver *driver, uint8_t *mac) |
| { |
| assert(driver); |
| assert(mac); |
| |
| imx6_eth_driver_t *dev = imx6_eth_driver(driver); |
| assert(dev); |
| struct enet *enet = dev->enet; |
| assert(enet); |
| |
| uint64_t mac_u64 = enet_get_mac(enet); |
| /* MAC is big endian u64, 0x0000aabbccddeeff means aa:bb:cc:dd:ee:ff */ |
| for (unsigned int i = 0; i < 6; i++) { |
| mac[5 - i] = (uint8_t)mac_u64; |
| mac_u64 >>= 8; |
| } |
| } |
| |
| static void low_level_init(struct eth_driver *driver, uint8_t *mac, int *mtu) |
| { |
| assert(driver); |
| |
| if (mac) { |
| get_mac(driver, mac); |
| } |
| |
| if (mtu) { |
| *mtu = MAX_PKT_SIZE; |
| } |
| } |
| |
| static void update_ring_slot( |
| ring_ctx_t *ring, |
| unsigned int idx, |
| uintptr_t phys, |
| uint16_t len, |
| uint16_t stat) |
| { |
| volatile struct descriptor *d = &(ring->descr[idx]); |
| d->phys = phys; |
| d->len = len; |
| |
| /* Ensure all writes to the descriptor complete, before we set the flags |
| * that makes hardware aware of this slot. |
| */ |
| __sync_synchronize(); |
| |
| d->stat = stat; |
| } |
| |
| static void fill_rx_bufs(imx6_eth_driver_t *dev) |
| { |
| assert(dev); |
| |
| ring_ctx_t *ring = &(dev->rx); |
| |
| void *cb_cookie = dev->eth_drv.cb_cookie; |
| ethif_raw_allocate_rx_buf cb_alloc = dev->eth_drv.i_cb.allocate_rx_buf; |
| if (!cb_alloc) { |
| /* The function may not be set up (yet), in this case we can't do |
| * anything. If lwip is used, this can be either lwip_allocate_rx_buf() |
| * or lwip_pbuf_allocate_rx_buf() from src/lwip.c |
| */ |
| ZF_LOGW("callback allocate_rx_buf not set, can't allocate %d buffers", |
| ring->remain); |
| } else { |
| __sync_synchronize(); |
| while (ring->remain > 0) { |
| /* request a buffer */ |
| void *cookie = NULL; |
| uintptr_t phys = cb_alloc(cb_cookie, BUF_SIZE, &cookie); |
| if (!phys) { |
| /* There are no buffers left. This can happen if the pool is too |
| * small because CONFIG_LIB_ETHDRIVER_NUM_PREALLOCATED_BUFFERS |
| * is less than CONFIG_LIB_ETHDRIVER_RX_DESC_COUNT. |
| */ |
| break; |
| } |
| uint16_t stat = RXD_EMPTY; |
| int idx = ring->tail; |
| int new_tail = idx + 1; |
| if (new_tail == ring->cnt) { |
| new_tail = 0; |
| stat |= RXD_WRAP; |
| } |
| ring->cookies[idx] = cookie; |
| update_ring_slot(ring, idx, phys, 0, stat); |
| ring->tail = new_tail; |
| /* There is a race condition if add/remove is not synchronized. */ |
| ring->remain--; |
| } |
| __sync_synchronize(); |
| } |
| |
| if (ring->tail != ring->head) { |
| struct enet *enet = dev->enet; |
| assert(enet); |
| if (!enet_rx_enabled(enet)) { |
| enet_rx_enable(enet); |
| } |
| } |
| } |
| |
| static void free_desc_ring(imx6_eth_driver_t *dev) |
| { |
| assert(dev); |
| |
| ps_dma_man_t *dma_man = &(dev->eth_drv.io_ops.dma_manager); |
| assert(dma_man); |
| |
| if (dev->rx.descr) { |
| dma_unpin_free( |
| dma_man, |
| (void *)dev->rx.descr, |
| sizeof(struct descriptor) * dev->rx.cnt); |
| dev->rx.descr = NULL; |
| } |
| |
| if (dev->tx.descr) { |
| dma_unpin_free( |
| dma_man, |
| (void *)dev->tx.descr, |
| sizeof(struct descriptor) * dev->tx.cnt); |
| dev->tx.descr = NULL; |
| } |
| |
| ps_malloc_ops_t *malloc_ops = &(dev->eth_drv.io_ops.malloc_ops); |
| assert(malloc_ops); |
| |
| if (dev->rx.cookies) { |
| ps_free( |
| malloc_ops, |
| sizeof(void *) * dev->rx.cnt, |
| dev->rx.cookies); |
| dev->rx.cookies = NULL; |
| } |
| |
| if (dev->tx.cookies) { |
| ps_free( |
| malloc_ops, |
| sizeof(void *) * dev->tx.cnt, |
| dev->tx.cookies); |
| dev->tx.cookies = NULL; |
| } |
| |
| if (dev->tx_lengths) { |
| ps_free( |
| malloc_ops, |
| sizeof(void *) * dev->tx.cnt, |
| dev->tx_lengths); |
| dev->tx_lengths = NULL; |
| } |
| } |
| |
| static int setup_desc_ring(imx6_eth_driver_t *dev, ring_ctx_t *ring) |
| { |
| assert(dev); |
| assert(ring); |
| |
| /* caller should have zeroed the ring context already. |
| * memset(ring, 0, sizeof(*ring)); |
| */ |
| |
| size_t size = sizeof(struct descriptor) * ring->cnt; |
| |
| /* allocate uncached memory, function will also clean an invalidate cache |
| * to for the area internally to save us from surprises |
| */ |
| ps_dma_man_t *dma_man = &(dev->eth_drv.io_ops.dma_manager); |
| assert(dma_man); |
| dma_addr_t dma = dma_alloc_pin( |
| dma_man, |
| size, |
| 0, // uncached |
| dev->eth_drv.dma_alignment); |
| if (!dma.phys || !dma.virt) { |
| ZF_LOGE("Faild to allocate %d bytes of DMA memory", size); |
| return -1; |
| } |
| |
| ZF_LOGE("dma_alloc_pin: %x -> %p, %d descriptors, %d bytes", |
| dma.phys, dma.virt, ring->cnt, size); |
| |
| /* zero ring */ |
| memset(dma.virt, 0, size); |
| |
| ring->phys = dma.phys; |
| ring->descr = dma.virt; |
| |
| assert(ring->cnt >= 2); |
| ring->descr[ring->cnt - 1].stat = TXD_WRAP; |
| /* 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. |
| */ |
| ring->remain = ring->cnt - 2; |
| |
| ring->tail = 0; |
| ring->head = 0; |
| |
| /* allocate and zero memory for cookies */ |
| ps_malloc_ops_t *malloc_ops = &(dev->eth_drv.io_ops.malloc_ops); |
| assert(malloc_ops); |
| int ret = ps_calloc( |
| malloc_ops, |
| ring->cnt, |
| sizeof(void *), |
| (void **) &ring->cookies); |
| if (ret) { |
| ZF_LOGE("Failed to malloc, code %d", ret); |
| return -1; |
| } |
| |
| /* Ensure operation propagate, so DMA is really set up. */ |
| __sync_synchronize(); |
| |
| return 0; |
| } |
| |
| static void complete_rx(imx6_eth_driver_t *dev) |
| { |
| assert(dev); |
| |
| void *cb_cookie = dev->eth_drv.cb_cookie; |
| ethif_raw_rx_complete cb_complete = dev->eth_drv.i_cb.rx_complete; |
| assert(cb_complete); |
| |
| ring_ctx_t *ring = &(dev->rx); |
| unsigned int head = ring->head; |
| |
| /* Release all descriptors that have data. */ |
| while (head != ring->tail) { |
| |
| /* The NIC hardware can modify the descriptor any time, 'volatile' |
| * prevents the compiler's optimizer from caching values and enforces |
| * every access happen as stated in the code. |
| */ |
| volatile struct descriptor *d = &(ring->descr[head]); |
| |
| /* If the slot is still marked as empty we are done. */ |
| if (d->stat & RXD_EMPTY) { |
| assert(dev->enet); |
| if (!enet_rx_enabled(dev->enet)) { |
| enet_rx_enable(dev->enet); |
| } |
| break; |
| } |
| |
| void *cookie = ring->cookies[head]; |
| /* Go to next buffer, handle roll-over. */ |
| if (++head == ring->cnt) { |
| head = 0; |
| } |
| ring->head = head; |
| |
| /* There is a race condition here if add/remove is not synchronized. */ |
| ring->remain++; |
| |
| /* Tell the driver it can return the DMA buffer to the pool. */ |
| unsigned int len = d->len; |
| cb_complete(cb_cookie, 1, &cookie, &len); |
| } |
| } |
| |
| static void complete_tx(imx6_eth_driver_t *dev) |
| { |
| assert(dev); |
| |
| void *cb_cookie = dev->eth_drv.cb_cookie; |
| ethif_raw_tx_complete cb_complete = dev->eth_drv.i_cb.tx_complete; |
| assert(cb_complete); |
| |
| unsigned int cnt_org; |
| void *cookie; |
| ring_ctx_t *ring = &(dev->tx); |
| unsigned int head = ring->head; |
| unsigned int cnt = 0; |
| |
| while (head != ring->tail) { |
| |
| if (0 == cnt) { |
| cnt = dev->tx_lengths[head]; |
| if ((0 == cnt) || (cnt > dev->tx.cnt)) { |
| /* We are not supposed to read 0 here. */ |
| ZF_LOGE("complete_tx with cnt=%u at head %u", cnt, head); |
| return; |
| } |
| cnt_org = cnt; |
| cookie = ring->cookies[head]; |
| } |
| |
| /* The NIC hardware can modify the descriptor any time, 'volatile' |
| * prevents the compiler's optimizer from caching values and enforces |
| * every access happens as stated in the code. |
| */ |
| volatile struct descriptor *d = &(ring->descr[head]); |
| |
| /* If this buffer was not sent, we can't release any buffer. */ |
| if (d->stat & TXD_READY) { |
| assert(dev->enet); |
| if (!enet_tx_enabled(dev->enet)) { |
| enet_tx_enable(dev->enet); |
| } |
| return; |
| } |
| |
| /* Go to next buffer, handle roll-over. */ |
| if (++head == dev->tx.cnt) { |
| head = 0; |
| } |
| |
| if (0 == --cnt) { |
| ring->head = head; |
| /* race condition if add/remove is not synchronized. */ |
| ring->remain += cnt_org; |
| /* give the buffer back */ |
| cb_complete(cb_cookie, cookie); |
| } |
| |
| } |
| |
| /* The only reason to arrive here is when head equals tails. If cnt is not |
| * zero, then there is some kind of overflow or data corruption. The number |
| * of tx descriptors holding data can't exceed the space in the ring. |
| */ |
| if (0 != cnt) { |
| ZF_LOGE("head at %u reached tail, but cnt=%u", head, cnt); |
| assert(0); |
| } |
| } |
| |
| static void print_state(struct eth_driver *driver) |
| { |
| assert(driver); |
| |
| imx6_eth_driver_t *dev = imx6_eth_driver(driver); |
| struct enet *enet = dev->enet; |
| assert(enet); |
| |
| enet_print_mib(enet); |
| } |
| |
| static void handle_irq(struct eth_driver *driver, int irq) |
| { |
| assert(driver); |
| |
| imx6_eth_driver_t *dev = imx6_eth_driver(driver); |
| assert(dev); |
| struct enet *enet = dev->enet; |
| assert(enet); |
| |
| uint32_t e = enet_clr_events(enet, IRQ_MASK); |
| if (e & NETIRQ_TXF) { |
| complete_tx(dev); |
| } |
| if (e & NETIRQ_RXF) { |
| complete_rx(dev); |
| fill_rx_bufs(dev); |
| } |
| if (e & NETIRQ_EBERR) { |
| ZF_LOGE("Error: System bus/uDMA"); |
| // ethif_print_state(netif_get_eth_driver(netif)); |
| assert(0); |
| while (1); |
| } |
| } |
| |
| /* This is a platsuport IRQ interface IRQ handler wrapper for handle_irq() */ |
| static void eth_irq_handle(void *ctx, ps_irq_acknowledge_fn_t acknowledge_fn, |
| void *ack_data) |
| { |
| if (!ctx) { |
| ZF_LOGE("IRQ handler got ctx=NULL"); |
| assert(0); |
| return; |
| } |
| |
| struct eth_driver *driver = (struct eth_driver *)ctx; |
| |
| /* handle_irq doesn't really expect an IRQ number */ |
| handle_irq(driver, 0); |
| |
| int ret = acknowledge_fn(ack_data); |
| if (ret) { |
| ZF_LOGE("Failed to acknowledge the Ethernet device's IRQ, code %d", ret); |
| } |
| } |
| |
| static void raw_poll(struct eth_driver *driver) |
| { |
| assert(driver); |
| |
| imx6_eth_driver_t *dev = imx6_eth_driver(driver); |
| assert(dev); |
| |
| // TODO: If the interrupts are still enabled, there could be race here. The |
| // caller must ensure this can't happen. |
| complete_rx(dev); |
| complete_tx(dev); |
| fill_rx_bufs(dev); |
| } |
| |
| static int raw_tx(struct eth_driver *driver, unsigned int num, uintptr_t *phys, |
| unsigned int *len, void *cookie) |
| { |
| if (0 == num) { |
| ZF_LOGW("raw_tx() called with num=0"); |
| return ETHIF_TX_ENQUEUED; |
| } |
| |
| assert(driver); |
| |
| imx6_eth_driver_t *dev = imx6_eth_driver(driver); |
| assert(dev); |
| |
| ring_ctx_t *ring = &(dev->tx); |
| |
| /* Ensure we have room */ |
| if (ring->remain < num) { |
| /* not enough room, try to complete some and check again */ |
| complete_tx(dev); |
| unsigned int rem = ring->remain; |
| if (rem < num) { |
| ZF_LOGE("TX queue lacks space, has %d, need %d", rem, num); |
| return ETHIF_TX_FAILED; |
| } |
| } |
| |
| __sync_synchronize(); |
| |
| unsigned int tail = ring->tail; |
| unsigned int tail_new = tail; |
| |
| unsigned int i = num; |
| while (i-- > 0) { |
| uint16_t stat = TXD_READY; |
| if (0 == i) { |
| stat |= TXD_ADDCRC | TXD_LAST; |
| } |
| |
| unsigned int idx = tail_new; |
| if (++tail_new == dev->tx.cnt) { |
| tail_new = 0; |
| stat |= TXD_WRAP; |
| } |
| update_ring_slot(ring, idx, *phys++, *len++, stat); |
| } |
| |
| ring->cookies[tail] = cookie; |
| dev->tx_lengths[tail] = num; |
| ring->tail = tail_new; |
| /* There is a race condition here if add/remove is not synchronized. */ |
| ring->remain -= num; |
| |
| __sync_synchronize(); |
| |
| struct enet *enet = dev->enet; |
| assert(enet); |
| if (!enet_tx_enabled(enet)) { |
| enet_tx_enable(enet); |
| } |
| |
| return ETHIF_TX_ENQUEUED; |
| } |
| |
| static uint64_t obtain_mac(const nic_config_t *nic_config, |
| ps_io_mapper_t *io_mapper) |
| { |
| unsigned int enet_id = nic_config ? nic_config->id : 0; |
| uint64_t cfg_mac = nic_config ? nic_config->mac : 0; |
| unsigned int doForceMac = nic_config && (nic_config->flags & NIC_CONFIG_FORCE_MAC); |
| |
| if (doForceMac && (0 != cfg_mac)) { |
| ZF_LOGI("config: overwriting default MAC"); |
| return cfg_mac; |
| } |
| |
| struct ocotp *ocotp = ocotp_init(io_mapper); |
| if (!ocotp) { |
| ZF_LOGE("Failed to initialize OCOTP to read MAC"); |
| } else { |
| uint64_t ocotp_mac = ocotp_get_mac(ocotp, enet_id); |
| ocotp_free(ocotp, io_mapper); |
| if (0 != ocotp_mac) { |
| ZF_LOGI("taking MAC #%u from OCOTP", enet_id); |
| return ocotp_mac; |
| } |
| |
| #ifdef CONFIG_PLAT_IMX6SX |
| |
| if (1 == enet_id) { |
| ZF_LOGI("IMX6SX: no MAC for enet2 in OCOTP, use enet1 MAC + 1"); |
| ocotp_mac = ocotp_get_mac(ocotp, 0); |
| if (0 != ocotp_mac) { |
| /* The uint64_t is 0x0000<aa><bb><cc><dd><ee><ff> for the MAC |
| * aa:bb:cc:dd:ee:ff, where aa:bb:cc is the OUI and dd:ee:ff is |
| * and ID. Leave OUI as it is and increment the ID by one with |
| * roll over handling. |
| */ |
| uint32_t oui = ocotp_mac >> 24; |
| uint32_t id = ocotp_mac & 0xffffff; |
| return ((uint64_t)oui << 24) | ((id + 1) & 0xffffff); |
| } |
| |
| ZF_LOGI("IMX6SX: no MAC for enet1 in OCOTP"); |
| } |
| |
| #endif /* CONFIG_PLAT_IMX6SX */ |
| |
| } |
| |
| /* no MAC from OCOTP, try using MAC config */ |
| if (0 != cfg_mac) { |
| ZF_LOGI("no MAC in OCOTP, taking MAC from config"); |
| return cfg_mac; |
| } |
| |
| ZF_LOGE("Failed to get MAC from OCOTP or config"); |
| return 0; |
| } |
| |
| static int init_device(imx6_eth_driver_t *dev, const nic_config_t *nic_config) |
| { |
| assert(dev); |
| |
| int ret; |
| |
| uint64_t mac = obtain_mac(nic_config, &(dev->eth_drv.io_ops.io_mapper)); |
| if (0 == mac) { |
| ZF_LOGE("Failed to obtain a MAC"); |
| return -1; |
| } |
| |
| ZF_LOGI("using MAC: %02x:%02x:%02x:%02x:%02x:%02x", |
| (uint8_t)(mac >> 40), |
| (uint8_t)(mac >> 32), |
| (uint8_t)(mac >> 24), |
| (uint8_t)(mac >> 16), |
| (uint8_t)(mac >> 8), |
| (uint8_t)(mac)); |
| |
| ret = setup_desc_ring(dev, &(dev->rx)); |
| if (ret) { |
| ZF_LOGE("Failed to allocate rx_ring, code %d", ret); |
| goto error; |
| } |
| |
| ret = setup_desc_ring(dev, &(dev->tx)); |
| if (ret) { |
| ZF_LOGE("Failed to allocate tx_ring, code %d", ret); |
| goto error; |
| } |
| |
| ps_malloc_ops_t *malloc_ops = &(dev->eth_drv.io_ops.malloc_ops); |
| assert(malloc_ops); |
| ret = ps_calloc( |
| malloc_ops, |
| dev->tx.cnt, |
| sizeof(unsigned int), |
| (void **)&dev->tx_lengths); |
| if (ret) { |
| ZF_LOGE("Failed to malloc, code %d", ret); |
| goto error; |
| } |
| /* ring got allocated, need to free it on error */ |
| |
| unsigned int enet_id = nic_config ? nic_config->id : 0; |
| |
| /* Initialise ethernet pins, also does a PHY reset */ |
| if (0 != enet_id) { |
| ZF_LOGI("skipping IOMUX setup for ENET id=%d", enet_id); |
| } else { |
| ret = setup_iomux_enet(&(dev->eth_drv.io_ops)); |
| if (ret) { |
| ZF_LOGE("Failed to setup IOMUX for ENET id=%d, code %d", |
| enet_id, ret); |
| goto error; |
| } |
| } |
| |
| /* Initialise the RGMII interface, clears and masks all interrupts */ |
| dev->enet = enet_init( |
| dev->mapped_peripheral, |
| dev->tx.phys, |
| dev->rx.phys, |
| BUF_SIZE, |
| mac, |
| &(dev->eth_drv.io_ops)); |
| if (!dev->enet) { |
| ZF_LOGE("Failed to initialize RGMII"); |
| /* currently no way to properly clean up enet */ |
| assert(!"enet cannot be cleaned up"); |
| goto error; |
| } |
| |
| /* Remove CRC (FCS) from ethernet frames when passing it to upper layers, |
| * because the NIC hardware would discard frames with an invalid checksum |
| * anyway by default. Usually, there not much practical gain in keeping it. |
| */ |
| bool do_strip_crc = nic_config && |
| (nic_config->flags & NIC_CONFIG_DROP_FRAME_CRC); |
| ZF_LOGI("config: CRC stripping %s", do_strip_crc ? "ON" : "OFF"); |
| if (do_strip_crc) { |
| enet_crc_strip_enable(dev->enet); |
| } else { |
| enet_crc_strip_disable(dev->enet); |
| } |
| |
| /* Non-Promiscuous mode means that only traffic relevant for us is made |
| * visible by the hardware, everything else is discarded automatically. We |
| * will only see packets addressed to our MAC and broadcast/multicast |
| * packets. This is usually all that is needed unless the upper layer |
| * implements functionality beyond a "normal" application scope, e.g. |
| * switching or monitoring. |
| */ |
| bool do_promiscuous_mode = nic_config && |
| (nic_config->flags & NIC_CONFIG_PROMISCUOUS_MODE); |
| ZF_LOGI("config: promiscuous mode %s", do_promiscuous_mode ? "ON" : "OFF"); |
| if (do_promiscuous_mode) { |
| enet_prom_enable(dev->enet); |
| } else { |
| enet_prom_disable(dev->enet); |
| } |
| |
| /* Initialise the phy library */ |
| miiphy_init(); |
| /* Initialise the phy */ |
| #if defined(CONFIG_PLAT_SABRE) || defined(CONFIG_PLAT_WANDQ) |
| phy_micrel_init(); |
| #elif defined(CONFIG_PLAT_NITROGEN6SX) |
| phy_atheros_init(); |
| #else |
| #error "unsupported board" |
| #endif |
| |
| /* Connect the phy to the ethernet controller */ |
| unsigned int phy_mask = 0xffffffff; |
| if (nic_config && (0 != nic_config->phy_address)) { |
| ZF_LOGI("config: using PHY at address %d", nic_config->phy_address); |
| phy_mask = BIT(nic_config->phy_address); |
| } |
| /* ENET1 has an MDIO interface, for ENET2 we use callbacks */ |
| dev->phy = fec_init( |
| phy_mask, |
| (0 == enet_id) ? dev->enet : NULL, |
| nic_config); |
| if (!dev->phy) { |
| ZF_LOGE("Failed to initialize fec"); |
| goto error; |
| } |
| |
| enet_set_speed( |
| dev->enet, |
| dev->phy->speed, |
| (dev->phy->duplex == DUPLEX_FULL) ? 1 : 0); |
| |
| ZF_LOGI("Link speed: %d Mbps, %s-duplex", |
| dev->phy->speed, |
| (dev->phy->duplex == DUPLEX_FULL) ? "full" : "half"); |
| |
| /* Start the controller, all interrupts are still masked here */ |
| enet_enable(dev->enet); |
| |
| /* This could also enable receiving, so the controller must be enabled. */ |
| fill_rx_bufs(dev); |
| |
| /* Ensure no unused interrupts are pending. */ |
| enet_clr_events(dev->enet, ~((uint32_t)IRQ_MASK)); |
| |
| /* Enable interrupts for the events we care about. This unmask them, some |
| * could already be pending here and trigger immediately. |
| */ |
| enet_enable_events(dev->enet, IRQ_MASK); |
| |
| /* done */ |
| return 0; |
| |
| error: |
| /* ToDo: free dev->phydev if set */ |
| free_desc_ring(dev); |
| return -1; |
| } |
| |
| static int allocate_register_callback(pmem_region_t pmem, unsigned curr_num, |
| size_t num_regs, void *token) |
| { |
| assert(token); |
| imx6_eth_driver_t *dev = (imx6_eth_driver_t *)token; |
| |
| /* we support only one peripheral, ignore others gracefully */ |
| if (curr_num != 0) { |
| ZF_LOGW("Ignoring peripheral register bank #%d at 0x%"PRIx64, |
| curr_num, pmem.base_addr); |
| return 0; |
| } |
| |
| dev->mapped_peripheral = ps_pmem_map( |
| &(dev->eth_drv.io_ops), |
| pmem, |
| false, |
| PS_MEM_NORMAL); |
| if (!dev->mapped_peripheral) { |
| ZF_LOGE("Failed to map the Ethernet device"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int allocate_irq_callback(ps_irq_t irq, unsigned curr_num, |
| size_t num_irqs, void *token) |
| { |
| assert(token); |
| imx6_eth_driver_t *dev = (imx6_eth_driver_t *)token; |
| |
| unsigned target_num = config_set(CONFIG_PLAT_IMX8MQ_EVK) ? 2 : 0; |
| if (curr_num != target_num) { |
| ZF_LOGW("Ignoring interrupt #%d with value %d", curr_num, irq); |
| return 0; |
| } |
| |
| dev->irq_id = ps_irq_register( |
| &(dev->eth_drv.io_ops.irq_ops), |
| irq, |
| eth_irq_handle, |
| dev); |
| if (dev->irq_id < 0) { |
| ZF_LOGE("Failed to register the Ethernet device's IRQ"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| int ethif_imx_init_module(ps_io_ops_t *io_ops, const char *device_path) |
| { |
| int ret; |
| imx6_eth_driver_t *driver = NULL; |
| |
| /* get a configuration if function is implemented */ |
| const nic_config_t *nic_config = NULL; |
| if (get_nic_configuration) { |
| ZF_LOGI("calling get_nic_configuration()"); |
| /* we can get NULL here, if somebody implements this function but does |
| * not give us a config. It's a bit odd, but valid. |
| */ |
| nic_config = get_nic_configuration(); |
| if (nic_config && nic_config->funcs.sync) { |
| ZF_LOGI("Waiting for primary NIC to finish initialization"); |
| ret = nic_config->funcs.sync(); |
| if (ret) { |
| ZF_LOGE("pirmary NIC sync failed, code %d", ret); |
| return -ENODEV; |
| } |
| ZF_LOGI("primary NIC init done, run secondary NIC init"); |
| } |
| } |
| |
| ret = ps_calloc( |
| &io_ops->malloc_ops, |
| 1, |
| sizeof(*driver), |
| (void **) &driver); |
| if (ret) { |
| ZF_LOGE("Failed to allocate memory for the Ethernet driver, code %d", ret); |
| ret = -ENOMEM; |
| goto error; |
| } |
| |
| driver->eth_drv.i_fn = (struct raw_iface_funcs) { |
| .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 |
| }; |
| |
| driver->eth_drv.eth_data = driver; /* use simply extend the structure */ |
| driver->eth_drv.io_ops = *io_ops; |
| driver->eth_drv.dma_alignment = DMA_ALIGN; |
| driver->tx.cnt = CONFIG_LIB_ETHDRIVER_TX_DESC_COUNT; |
| driver->rx.cnt = CONFIG_LIB_ETHDRIVER_RX_DESC_COUNT; |
| |
| ps_fdt_cookie_t *cookie = NULL; |
| ret = ps_fdt_read_path( |
| &io_ops->io_fdt, |
| &io_ops->malloc_ops, |
| device_path, |
| &cookie); |
| if (ret) { |
| ZF_LOGE("Failed to read the path of the Ethernet device, code %d", ret); |
| ret = -ENODEV; |
| goto error; |
| } |
| |
| ret = ps_fdt_walk_registers( |
| &io_ops->io_fdt, |
| cookie, |
| allocate_register_callback, |
| driver); |
| if (ret) { |
| ZF_LOGE("Failed to walk the Ethernet device's registers and allocate them, code %d", ret); |
| ret = -ENODEV; |
| goto error; |
| } |
| |
| ret = ps_fdt_walk_irqs( |
| &io_ops->io_fdt, |
| cookie, |
| allocate_irq_callback, |
| driver); |
| if (ret) { |
| ZF_LOGE("Failed to walk the Ethernet device's IRQs and allocate them, code %d", ret); |
| ret = -ENODEV; |
| goto error; |
| } |
| |
| ret = ps_fdt_cleanup_cookie(&io_ops->malloc_ops, cookie); |
| if (ret) { |
| ZF_LOGE("Failed to free the cookie used to allocate resources, code %d", ret); |
| ret = -ENODEV; |
| goto error; |
| } |
| |
| ret = init_device(driver, nic_config); |
| if (ret) { |
| ZF_LOGE("Failed to initialise the Ethernet driver, code %d", ret); |
| ret = -ENODEV; |
| goto error; |
| } |
| |
| ret = ps_interface_register( |
| &io_ops->interface_registration_ops, |
| PS_ETHERNET_INTERFACE, |
| driver, |
| NULL); |
| if (ret) { |
| ZF_LOGE("Failed to register Ethernet driver interface, code %d", ret); |
| ret = -ENODEV; |
| goto error; |
| } |
| |
| return 0; |
| |
| error: |
| ZF_LOGI("Cleaning up failed driver initialization"); |
| |
| if (driver) { |
| ps_free(&io_ops->malloc_ops, sizeof(*driver), driver); |
| } |
| |
| return ret; |
| |
| } |
| |
| static const char *compatible_strings[] = { |
| /* Other i.MX platforms may also be compatible but the platforms that have |
| * been tested are: |
| * - SABRE Lite (i.MX6Quad) |
| * - Nitrogen6_SoloX (i.MX6SoloX) |
| * - i.MX8MQ Evaluation Kit |
| */ |
| "fsl,imx6q-fec", |
| "fsl,imx6sx-fec", |
| "fsl,imx8mq-fec", |
| NULL |
| }; |
| |
| PS_DRIVER_MODULE_DEFINE(imx_fec, compatible_strings, ethif_imx_init_module); |