diff options
author | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2018-04-23 09:53:31 +0200 |
---|---|---|
committer | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2018-04-23 15:18:44 +0200 |
commit | 031df3914990db0336a0d386fb53558b05de467e (patch) | |
tree | 4661e22f0cdb3f9d06879f0194b77c75f62bac79 /bsps/powerpc/mpc55xxevb/net/smsc9218i.c | |
parent | bsps: Move interrupt controller support to bsps (diff) | |
download | rtems-031df3914990db0336a0d386fb53558b05de467e.tar.bz2 |
bsps: Move legacy network drivers to bsps
This patch is a part of the BSP source reorganization.
Update #3285.
Diffstat (limited to 'bsps/powerpc/mpc55xxevb/net/smsc9218i.c')
-rw-r--r-- | bsps/powerpc/mpc55xxevb/net/smsc9218i.c | 2125 |
1 files changed, 2125 insertions, 0 deletions
diff --git a/bsps/powerpc/mpc55xxevb/net/smsc9218i.c b/bsps/powerpc/mpc55xxevb/net/smsc9218i.c new file mode 100644 index 0000000000..fb0b7aa113 --- /dev/null +++ b/bsps/powerpc/mpc55xxevb/net/smsc9218i.c @@ -0,0 +1,2125 @@ +/** + * @file + * + * @ingroup mpc55xx + * + * @brief SMSC - LAN9218i + */ + +/* + * Copyright (c) 2009-2012 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Obere Lagerstr. 30 + * 82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.org/license/LICENSE. + */ + +#include <rtems.h> + +#include <mpc55xx/regs.h> + +#if defined(RTEMS_NETWORKING) && defined(MPC55XX_HAS_SIU) + +#define __INSIDE_RTEMS_BSD_TCPIP_STACK__ 1 +#define __BSD_VISIBLE 1 + +#include <errno.h> +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <inttypes.h> + +#include <rtems/rtems_bsdnet.h> +#include <rtems/rtems_mii_ioctl.h> + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> + +#include <net/if.h> +#include <net/if_arp.h> +#include <netinet/in.h> +#include <netinet/if_ether.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> + +#include <mpc55xx/edma.h> + +#include <bsp.h> +#include <bsp/irq.h> +#include <bsp/utility.h> +#include <libcpu/powerpc-utility.h> +#include <bsp/smsc9218i.h> + +#if MCLBYTES != 2048 + #warning "unexpected MCLBYTES value" +#endif + +#define ASSERT_SC(sc) assert((sc) == RTEMS_SUCCESSFUL) + +#define SMSC9218I_EVENT_TX RTEMS_EVENT_1 + +#define SMSC9218I_EVENT_TX_START RTEMS_EVENT_2 + +#define SMSC9218I_EVENT_TX_ERROR RTEMS_EVENT_3 + +#define SMSC9218I_EVENT_RX RTEMS_EVENT_4 + +#define SMSC9218I_EVENT_RX_ERROR RTEMS_EVENT_5 + +#define SMSC9218I_EVENT_PHY RTEMS_EVENT_6 + +#define SMSC9218I_EVENT_DMA RTEMS_EVENT_7 + +#define SMSC9218I_EVENT_DMA_ERROR RTEMS_EVENT_8 + +/* Adjust by two bytes for proper IP header alignment */ +#define SMSC9218I_RX_DATA_OFFSET 2 + +#define SMSC9218I_RX_JOBS 32 + +#define SMSC9218I_TX_JOBS 64 + +/* Maximum number of fragments per frame, see manual section 3.11.3.2 */ +#define SMSC9218I_TX_FRAGMENT_MAX 86 + +#if SMSC9218I_TX_JOBS > SMSC9218I_TX_FRAGMENT_MAX + #error "too many TX jobs" +#endif + +#define SMSC9218I_IRQ_CFG_GLOBAL_ENABLE \ + (SMSC9218I_IRQ_CFG_IRQ_EN | SMSC9218I_IRQ_CFG_IRQ_TYPE) + +#define SMSC9218I_IRQ_CFG_GLOBAL_DISABLE SMSC9218I_IRQ_CFG_IRQ_TYPE + +#define SMSC9218I_EDMA_RX_TCD_CDF 0x10004 + +#define SMSC9218I_EDMA_RX_TCD_BMF 0x10003 + +#define SMSC9218I_TCD_BMF_LINK 0x10011 + +#define SMSC9218I_TCD_BMF_LAST 0x10003 + +#define SMSC9218I_TCD_BMF_CLEAR 0x10000 + +#define SMSC9218I_ERROR_INTERRUPTS \ + (SMSC9218I_INT_TXSO \ + | SMSC9218I_INT_RWT \ + | SMSC9218I_INT_RXE \ + | SMSC9218I_INT_TXE) + +#define SMSC9218I_UNLIKELY(x) __builtin_expect((x), 0) + +#ifdef DEBUG + #define SMSC9218I_PRINTF(...) printf(__VA_ARGS__) + #define SMSC9218I_PRINTK(...) printk(__VA_ARGS__) +#else + #define SMSC9218I_PRINTF(...) + #define SMSC9218I_PRINTK(...) +#endif + +typedef enum { + SMSC9218I_NOT_INITIALIZED, + SMSC9218I_CONFIGURED, + SMSC9218I_STARTED, + SMSC9218I_RUNNING +} smsc9218i_state; + +static const char *const state_to_string [] = { + "NOT INITIALIZED", + "CONFIGURED", + "STARTED", + "RUNNING" +}; + +typedef struct { + struct arpcom arpcom; + struct rtems_mdio_info mdio; + smsc9218i_state state; + rtems_id receive_task; + rtems_id transmit_task; + edma_channel_context edma_receive; + edma_channel_context edma_transmit; + unsigned phy_interrupts; + unsigned received_frames; + unsigned receiver_errors; + unsigned receive_interrupts; + unsigned receive_dma_interrupts; + unsigned receive_too_long_errors; + unsigned receive_collision_errors; + unsigned receive_crc_errors; + unsigned receive_dma_errors; + unsigned receive_drop; + unsigned receive_watchdog_timeouts; + unsigned transmitted_frames; + unsigned transmitter_errors; + unsigned transmit_interrupts; + unsigned transmit_dma_interrupts; + unsigned transmit_status_overflows; + unsigned transmit_frame_errors; + unsigned transmit_dma_errors; +} smsc9218i_driver_entry; + +typedef struct { + uint32_t a; + uint32_t b; +} smsc9218i_transmit_command; + +typedef struct { + struct tcd_t command_tcd_table [SMSC9218I_TX_JOBS]; + struct tcd_t data_tcd_table [SMSC9218I_TX_JOBS]; + smsc9218i_transmit_command command_table [SMSC9218I_TX_JOBS]; + struct mbuf *fragment_table [SMSC9218I_TX_JOBS]; + struct mbuf *frame; + struct mbuf *next_fragment; + int empty_index; + int transfer_index; + int transfer_last_index; + int todo_index; + int empty; + int transfer; + int todo; + uint32_t frame_length; + uint32_t command_b; + uint16_t tag; + bool done; + unsigned frame_compact_count; +} smsc9218i_transmit_job_control; + +typedef struct { + struct tcd_t tcd_table [SMSC9218I_RX_JOBS]; + struct mbuf *mbuf_table [SMSC9218I_RX_JOBS]; + int consume; + int done; + int produce; +} smsc9218i_receive_job_control; + +static smsc9218i_receive_job_control smsc_rx_jc __attribute__((aligned (32))); + +static void smsc9218i_transmit_dma_done( + edma_channel_context *ctx, + uint32_t error_status +); + +static void smsc9218i_receive_dma_done( + edma_channel_context *ctx, + uint32_t error_status +); + +static smsc9218i_driver_entry smsc9218i_driver_data = { + .state = SMSC9218I_NOT_INITIALIZED, + .receive_task = RTEMS_ID_NONE, + .transmit_task = RTEMS_ID_NONE, + .edma_receive = { + .edma_tcd = EDMA_TCD_BY_CHANNEL_INDEX(SMSC9218I_EDMA_RX_CHANNEL), + .done = smsc9218i_receive_dma_done + }, + .edma_transmit = { + .edma_tcd = EDMA_TCD_BY_CHANNEL_INDEX(SMSC9218I_EDMA_TX_CHANNEL), + .done = smsc9218i_transmit_dma_done + } +}; + +static rtems_interval smsc9218i_timeout_init(void) +{ + return rtems_clock_get_ticks_since_boot(); +} + +static bool smsc9218i_timeout_not_expired(rtems_interval start) +{ + rtems_interval elapsed = rtems_clock_get_ticks_since_boot() - start; + + return elapsed < rtems_clock_get_ticks_per_second(); +} + +static bool smsc9218i_mac_wait(volatile smsc9218i_registers *regs) +{ + rtems_interval start = smsc9218i_timeout_init(); + bool busy; + + while ( + (busy = (regs->mac_csr_cmd & SMSC9218I_MAC_CSR_CMD_BUSY) != 0) + && smsc9218i_timeout_not_expired(start) + ) { + /* Wait */ + } + + return !busy; +} + +static bool smsc9218i_mac_write( + volatile smsc9218i_registers *regs, + uint32_t address, + uint32_t data +) +{ + bool ok = smsc9218i_mac_wait(regs); + + if (ok) { + regs->mac_csr_data = SMSC9218I_SWAP(data); + regs->mac_csr_cmd = SMSC9218I_MAC_CSR_CMD_BUSY + | SMSC9218I_MAC_CSR_CMD_ADDR(address); + ok = smsc9218i_mac_wait(regs); + } + + return ok; +} + +static uint32_t smsc9218i_mac_read( + volatile smsc9218i_registers *regs, + uint32_t address, + bool *ok_ptr +) +{ + uint32_t mac_csr_data = 0; + bool ok = smsc9218i_mac_wait(regs); + + if (ok) { + regs->mac_csr_cmd = SMSC9218I_MAC_CSR_CMD_BUSY + | SMSC9218I_MAC_CSR_CMD_READ + | SMSC9218I_MAC_CSR_CMD_ADDR(address); + ok = smsc9218i_mac_wait(regs); + + if (ok) { + mac_csr_data = regs->mac_csr_data; + } + } + + if (ok_ptr != NULL) { + *ok_ptr = ok; + } + + return SMSC9218I_SWAP(mac_csr_data); +} + +static bool smsc9218i_phy_wait(volatile smsc9218i_registers *regs) +{ + rtems_interval start = smsc9218i_timeout_init(); + uint32_t mac_mii_acc; + bool busy; + + do { + mac_mii_acc = smsc9218i_mac_read(regs, SMSC9218I_MAC_MII_ACC, NULL); + } while ( + (busy = (mac_mii_acc & SMSC9218I_MAC_MII_ACC_BUSY) != 0) + && smsc9218i_timeout_not_expired(start) + ); + + return !busy; +} + +static bool smsc9218i_phy_write( + volatile smsc9218i_registers *regs, + uint32_t address, + uint32_t data +) +{ + bool ok = smsc9218i_phy_wait(regs); + + if (ok) { + ok = smsc9218i_mac_write(regs, SMSC9218I_MAC_MII_DATA, data); + + if (ok) { + ok = smsc9218i_mac_write( + regs, + SMSC9218I_MAC_MII_ACC, + SMSC9218I_MAC_MII_ACC_PHY_DEFAULT + | SMSC9218I_MAC_MII_ACC_BUSY + | SMSC9218I_MAC_MII_ACC_WRITE + | SMSC9218I_MAC_MII_ACC_ADDR(address) + ); + + if (ok) { + ok = smsc9218i_phy_wait(regs); + } + } + } + + return ok; +} + +static uint32_t smsc9218i_phy_read( + volatile smsc9218i_registers *regs, + uint32_t address +) +{ + smsc9218i_phy_wait(regs); + smsc9218i_mac_write( + regs, + SMSC9218I_MAC_MII_ACC, + SMSC9218I_MAC_MII_ACC_PHY_DEFAULT + | SMSC9218I_MAC_MII_ACC_BUSY + | SMSC9218I_MAC_MII_ACC_ADDR(address) + ); + smsc9218i_phy_wait(regs); + return smsc9218i_mac_read(regs, SMSC9218I_MAC_MII_DATA, NULL); +} + +static bool smsc9218i_enable_promiscous_mode( + volatile smsc9218i_registers *regs, + bool enable +) +{ + bool ok; + uint32_t mac_cr = smsc9218i_mac_read(regs, SMSC9218I_MAC_CR, &ok); + + if (ok) { + uint32_t flags = SMSC9218I_MAC_CR_RXALL | SMSC9218I_MAC_CR_PRMS; + + if (enable) { + mac_cr |= flags; + } else { + mac_cr &= ~flags; + } + + ok = smsc9218i_mac_write(regs, SMSC9218I_MAC_CR, mac_cr); + } + + return ok; +} + +#if defined(DEBUG) +static void smsc9218i_register_dump(volatile smsc9218i_registers *regs) +{ + uint32_t reg = 0; + + reg = regs->id_rev; + printf( + "id_rev: 0x%08" PRIx32 " (ID = 0x%04" PRIx32 + ", revision = 0x%04" PRIx32 ")\n", + SMSC9218I_SWAP(reg), + SMSC9218I_ID_REV_GET_ID(reg), + SMSC9218I_ID_REV_GET_REV(reg) + ); + + reg = regs->irq_cfg; + printf("irq_cfg: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->int_sts; + printf("int_sts: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->int_en; + printf("int_en: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->byte_test; + printf("byte_test: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->fifo_int; + printf("fifo_int: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->rx_cfg; + printf("rx_cfg: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->tx_cfg; + printf("tx_cfg: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->hw_cfg; + printf("hw_cfg: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->rx_dp_ctl; + printf("rx_dp_ctl: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->rx_fifo_inf; + printf( + "rx_fifo_inf: 0x%08" PRIx32 " (status used = %" PRIu32 + ", data used = %" PRIu32 ")\n", + SMSC9218I_SWAP(reg), + SMSC9218I_RX_FIFO_INF_GET_SUSED(reg), + SMSC9218I_RX_FIFO_INF_GET_DUSED(reg) + ); + + reg = regs->tx_fifo_inf; + printf( + "tx_fifo_inf: 0x%08" PRIx32 " (status unused = %" PRIu32 + ", free = %" PRIu32 ")\n", + SMSC9218I_SWAP(reg), + SMSC9218I_TX_FIFO_INF_GET_SUSED(reg), + SMSC9218I_TX_FIFO_INF_GET_FREE(reg) + ); + + reg = regs->pmt_ctrl; + printf("pmt_ctrl: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->gpio_cfg; + printf("gpio_cfg: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->gpt_cfg; + printf("gpt_cfg: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->gpt_cnt; + printf("gpt_cnt: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->word_swap; + printf("word_swap: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->free_run; + printf("free_run: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->rx_drop; + printf("rx_drop: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + reg = regs->afc_cfg; + printf("afc_cfg: 0x%08" PRIx32 "\n", SMSC9218I_SWAP(reg)); + + printf("mac: cr: 0x%08" PRIx32 "\n", smsc9218i_mac_read(regs, SMSC9218I_MAC_CR, NULL)); + printf("mac: addrh: 0x%08" PRIx32 "\n", smsc9218i_mac_read(regs, SMSC9218I_MAC_ADDRH, NULL)); + printf("mac: addrl: 0x%08" PRIx32 "\n", smsc9218i_mac_read(regs, SMSC9218I_MAC_ADDRL, NULL)); + printf("mac: hashh: 0x%08" PRIx32 "\n", smsc9218i_mac_read(regs, SMSC9218I_MAC_HASHH, NULL)); + printf("mac: hashl: 0x%08" PRIx32 "\n", smsc9218i_mac_read(regs, SMSC9218I_MAC_HASHL, NULL)); + printf("mac: mii_acc: 0x%08" PRIx32 "\n", smsc9218i_mac_read(regs, SMSC9218I_MAC_MII_ACC, NULL)); + printf("mac: mii_data: 0x%08" PRIx32 "\n", smsc9218i_mac_read(regs, SMSC9218I_MAC_MII_DATA, NULL)); + printf("mac: flow: 0x%08" PRIx32 "\n", smsc9218i_mac_read(regs, SMSC9218I_MAC_FLOW, NULL)); + printf("mac: vlan1: 0x%08" PRIx32 "\n", smsc9218i_mac_read(regs, SMSC9218I_MAC_VLAN1, NULL)); + printf("mac: vlan2: 0x%08" PRIx32 "\n", smsc9218i_mac_read(regs, SMSC9218I_MAC_VLAN2, NULL)); + printf("mac: wuff: 0x%08" PRIx32 "\n", smsc9218i_mac_read(regs, SMSC9218I_MAC_WUFF, NULL)); + printf("mac: wucsr: 0x%08" PRIx32 "\n", smsc9218i_mac_read(regs, SMSC9218I_MAC_WUCSR, NULL)); + + printf("phy: bcr: 0x%08" PRIx32 "\n", smsc9218i_phy_read(regs, MII_BMCR)); + printf("phy: bsr: 0x%08" PRIx32 "\n", smsc9218i_phy_read(regs, MII_BMSR)); + printf("phy: id1: 0x%08" PRIx32 "\n", smsc9218i_phy_read(regs, MII_PHYIDR1)); + printf("phy: id2: 0x%08" PRIx32 "\n", smsc9218i_phy_read(regs, MII_PHYIDR2)); + printf("phy: anar: 0x%08" PRIx32 "\n", smsc9218i_phy_read(regs, MII_ANAR)); + printf("phy: anlpar: 0x%08" PRIx32 "\n", smsc9218i_phy_read(regs, MII_ANLPAR)); + printf("phy: anexpr: 0x%08" PRIx32 "\n", smsc9218i_phy_read(regs, MII_ANER)); + printf("phy: mcsr: 0x%08" PRIx32 "\n", smsc9218i_phy_read(regs, SMSC9218I_PHY_MCSR)); + printf("phy: spmodes: 0x%08" PRIx32 "\n", smsc9218i_phy_read(regs, SMSC9218I_PHY_SPMODES)); + printf("phy: cisr: 0x%08" PRIx32 "\n", smsc9218i_phy_read(regs, SMSC9218I_PHY_CSIR)); + printf("phy: isr: 0x%08" PRIx32 "\n", smsc9218i_phy_read(regs, SMSC9218I_PHY_ISR)); + printf("phy: imr: 0x%08" PRIx32 "\n", smsc9218i_phy_read(regs, SMSC9218I_PHY_IMR)); + printf("phy: physcsr: 0x%08" PRIx32 "\n", smsc9218i_phy_read(regs, SMSC9218I_PHY_PHYSCSR)); +} +#endif + +static void smsc9218i_flush_tcd(struct tcd_t *tcd) +{ + ppc_data_cache_block_store(tcd); +} + +static uint32_t smsc9218i_align_up(uint32_t val) +{ + return 4U + ((val - 1U) & ~0x3U); +} + +static void smsc9218i_discard_frame( + smsc9218i_driver_entry *e, + volatile smsc9218i_registers *regs, + uint32_t rx_fifo_status, + uint32_t frame_length, + uint32_t data_length +) +{ + /* Update error counters */ + if ((rx_fifo_status & SMSC9218I_RX_STS_ERROR_TOO_LONG) != 0) { + ++e->receive_too_long_errors; + } + if ((rx_fifo_status & SMSC9218I_RX_STS_ERROR_COLLISION) != 0) { + ++e->receive_collision_errors; + } + if ((rx_fifo_status & SMSC9218I_RX_STS_ERROR_CRC) != 0) { + ++e->receive_crc_errors; + } + + /* Discard frame */ + if (frame_length > 16) { + /* Fast forward */ + regs->rx_dp_ctl = SMSC9218I_RX_DP_CTRL_FFWD; + + while ((regs->rx_dp_ctl & SMSC9218I_RX_DP_CTRL_FFWD) != 0) { + /* Wait */ + } + } else { + uint32_t len = data_length / 4; + uint32_t i = 0; + + /* Discard data */ + for (i = 0; i < len; ++i) { + regs->rx_fifo_data; + } + } +} + +static void smsc9218i_setup_receive_dma( + smsc9218i_driver_entry *e, + volatile smsc9218i_registers *regs, + smsc9218i_receive_job_control *jc +) +{ + int c = jc->consume; + int p = jc->produce; + int np = (p + 1) % SMSC9218I_RX_JOBS; + struct tcd_t *first = &jc->tcd_table [p]; + struct tcd_t *last = NULL; + + while (np != c) { + uint32_t rx_fifo_inf = 0; + uint32_t status_used = 0; + + /* Clear FIFO level status */ + regs->int_sts = SMSC9218I_INT_RSFL; + + /* Next FIFO status */ + rx_fifo_inf = regs->rx_fifo_inf; + status_used = SMSC9218I_RX_FIFO_INF_GET_SUSED(rx_fifo_inf); + + if (status_used > 0) { + uint32_t status = regs->rx_fifo_status; + uint32_t frame_length = SMSC9218I_RX_STS_GET_LENGTH(status); + uint32_t data_length = smsc9218i_align_up( + SMSC9218I_RX_DATA_OFFSET + frame_length + ); + bool frame_ok = (status & SMSC9218I_RX_STS_ERROR) == 0; + + if (frame_ok) { + struct mbuf *m = jc->mbuf_table [p]; + int mbuf_length = (int) frame_length - ETHER_HDR_LEN - ETHER_CRC_LEN; + struct tcd_t *current = &jc->tcd_table [p]; + + m->m_len = mbuf_length; + m->m_pkthdr.len = mbuf_length; + + current->NBYTES = data_length; + smsc9218i_flush_tcd(current); + + last = current; + p = np; + np = (p + 1) % SMSC9218I_RX_JOBS; + } else { + smsc9218i_discard_frame(e, regs, status, frame_length, data_length); + } + } else { + break; + } + } + + jc->produce = p; + + if (last != NULL) { + volatile struct tcd_t *channel = e->edma_receive.edma_tcd; + + /* Setup last TCD */ + last->BMF.R = SMSC9218I_TCD_BMF_LAST; + smsc9218i_flush_tcd(last); + ppc_synchronize_data(); + + /* Start eDMA transfer */ + channel->SADDR = first->SADDR; + channel->SDF.R = first->SDF.R; + channel->NBYTES = first->NBYTES; + channel->SLAST = first->SLAST; + channel->DADDR = first->DADDR; + channel->CDF.R = first->CDF.R; + channel->DLAST_SGA = first->DLAST_SGA; + channel->BMF.R = SMSC9218I_TCD_BMF_CLEAR; + channel->BMF.R = first->BMF.R; + } +} + +static void smsc9218i_receive_dma_done( + edma_channel_context *ctx, + uint32_t error_status +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + smsc9218i_driver_entry *e = &smsc9218i_driver_data; + smsc9218i_receive_job_control *jc = &smsc_rx_jc; + + SMSC9218I_PRINTK( + "edma: id = 0x%08x, error status = 0x%08x\n", + channel_entry->id, + error_status + ); + + ++e->receive_dma_interrupts; + if (SMSC9218I_UNLIKELY(error_status != 0)) { + ++e->receive_dma_errors; + } + + sc = rtems_bsdnet_event_send(e->receive_task, SMSC9218I_EVENT_DMA); + ASSERT_SC(sc); + + jc->done = jc->produce; +} + +static void smsc9218i_transmit_dma_done( + edma_channel_context *ctx, + uint32_t error_status +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + smsc9218i_driver_entry *e = &smsc9218i_driver_data; + rtems_event_set event = error_status == 0 ? + SMSC9218I_EVENT_DMA : SMSC9218I_EVENT_DMA_ERROR; + + SMSC9218I_PRINTK( + "edma: id = 0x%08x, error status = 0x%08x\n", + channel_entry->id, + error_status + ); + + ++e->transmit_dma_interrupts; + + sc = rtems_bsdnet_event_send(e->transmit_task, event); + ASSERT_SC(sc); +} + +static void smsc9218i_interrupt_handler(void *arg) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + smsc9218i_driver_entry *e = (smsc9218i_driver_entry *) arg; + volatile smsc9218i_registers *const regs = smsc9218i; + uint32_t int_en = regs->int_en; + uint32_t int_sts = regs->int_sts & int_en; + #ifdef DEBUG + uint32_t irq_cfg = regs->irq_cfg; + #endif + + /* Get interrupt status */ + SMSC9218I_PRINTK( + "interrupt: irq_cfg = 0x%08x, int_sts = 0x%08x, int_en = 0x%08x\n", + SMSC9218I_SWAP(irq_cfg), + SMSC9218I_SWAP(int_sts), + SMSC9218I_SWAP(int_en) + ); + + /* Disable module interrupts */ + regs->irq_cfg = SMSC9218I_IRQ_CFG_GLOBAL_DISABLE; + + /* Clear external interrupt status */ + SIU.EISR.R = 1; + + /* Clear interrupts */ + regs->int_sts = int_sts; + + /* Error interrupts */ + if (SMSC9218I_UNLIKELY((int_sts & SMSC9218I_ERROR_INTERRUPTS) != 0)) { + if ((int_sts & SMSC9218I_INT_TXSO) != 0) { + ++e->transmit_status_overflows; + } + if ((int_sts & SMSC9218I_INT_RWT) != 0) { + ++e->receive_watchdog_timeouts; + } + if ((int_sts & SMSC9218I_INT_RXE) != 0) { + ++e->receiver_errors; + } + if ((int_sts & SMSC9218I_INT_TXE) != 0) { + ++e->transmitter_errors; + } + } + + /* Check receive interrupts */ + if ((int_sts & SMSC9218I_INT_RSFL) != 0) { + int_en &= ~SMSC9218I_INT_RSFL; + ++e->receive_interrupts; + + sc = rtems_bsdnet_event_send(e->receive_task, SMSC9218I_EVENT_RX); + ASSERT_SC(sc); + } + + /* Check PHY interrupts */ + if (SMSC9218I_UNLIKELY((int_sts & SMSC9218I_INT_PHY) != 0)) { + SMSC9218I_PRINTK("interrupt: phy\n"); + int_en &= ~SMSC9218I_INT_PHY; + ++e->phy_interrupts; + sc = rtems_bsdnet_event_send(e->receive_task, SMSC9218I_EVENT_PHY); + ASSERT_SC(sc); + } + + /* Check transmit interrupts */ + if ((int_sts & SMSC9218I_INT_TDFA) != 0) { + SMSC9218I_PRINTK("interrupt: transmit\n"); + int_en &= ~SMSC9218I_INT_TDFA; + ++e->transmit_interrupts; + sc = rtems_bsdnet_event_send(e->transmit_task, SMSC9218I_EVENT_TX); + ASSERT_SC(sc); + } + + /* Update interrupt enable */ + regs->int_en = int_en; + + /* Enable module interrupts */ + regs->irq_cfg = SMSC9218I_IRQ_CFG_GLOBAL_ENABLE; +} + +static void smsc9218i_enable_transmit_interrupts( + volatile smsc9218i_registers *regs +) +{ + rtems_interrupt_level level; + + rtems_interrupt_disable(level); + regs->int_en |= SMSC9218I_INT_TDFA; + rtems_interrupt_enable(level); +} + +static void smsc9218i_enable_phy_interrupts( + volatile smsc9218i_registers *regs +) +{ + rtems_interrupt_level level; + + rtems_interrupt_disable(level); + regs->int_en |= SMSC9218I_INT_PHY; + rtems_interrupt_enable(level); +} + +static void smsc9218i_phy_clear_interrupts( + volatile smsc9218i_registers *regs +) +{ + smsc9218i_phy_read(regs, SMSC9218I_PHY_ISR); +} + +static bool smsc9218i_media_status(smsc9218i_driver_entry *e, int *media) +{ + struct ifnet *ifp = &e->arpcom.ac_if; + + *media = IFM_MAKEWORD(0, 0, 0, SMSC9218I_MAC_MII_ACC_PHY_DEFAULT); + + return (*ifp->if_ioctl)(ifp, SIOCGIFMEDIA, (caddr_t) media) == 0; +} + +static void smsc9218i_media_status_change( + smsc9218i_driver_entry *e, + volatile smsc9218i_registers *regs +) +{ + int media = 0; + bool media_ok = false; + uint32_t mac_cr = 0; + + smsc9218i_phy_clear_interrupts(regs); + smsc9218i_enable_phy_interrupts(regs); + + media_ok = smsc9218i_media_status(e, &media); + mac_cr = smsc9218i_mac_read(regs, SMSC9218I_MAC_CR, NULL); + if (media_ok && (IFM_OPTIONS(media) & IFM_FDX) == 0) { + mac_cr &= ~SMSC9218I_MAC_CR_FDPX; + } else { + mac_cr |= SMSC9218I_MAC_CR_FDPX; + } + smsc9218i_mac_write(regs, SMSC9218I_MAC_CR, mac_cr); +} + +static bool smsc9218i_new_mbuf( + struct ifnet *ifp, + smsc9218i_receive_job_control *jc, + int i, + struct mbuf *old_m +) +{ + bool ok = false; + int wait = old_m != NULL ? M_DONTWAIT : M_WAIT; + struct mbuf *new_m = m_gethdr(wait, MT_DATA); + struct tcd_t *tcd = &jc->tcd_table [i]; + char *data = NULL; + + if (new_m != NULL ) { + new_m->m_pkthdr.rcvif = ifp; + MCLGET(new_m, wait); + + if ((new_m->m_flags & M_EXT) != 0) { + ok = true; + } else { + m_free(new_m); + new_m = old_m; + } + } else { + new_m = old_m; + } + + data = mtod(new_m, char *); + new_m->m_data = data + SMSC9218I_RX_DATA_OFFSET + ETHER_HDR_LEN; + + jc->mbuf_table [i] = new_m; + + tcd->DADDR = (uint32_t) data; + tcd->BMF.R = SMSC9218I_TCD_BMF_LINK; + + /* FIXME: This is maybe a problem in case of a lot of small frames */ + rtems_cache_invalidate_multiple_data_lines( + data, + SMSC9218I_RX_DATA_OFFSET + ETHER_HDR_LEN + ETHERMTU + ETHER_CRC_LEN + ); + + return ok; +} + +static void smsc9218i_init_receive_jobs( + smsc9218i_driver_entry *e, + volatile smsc9218i_registers *regs, + volatile smsc9218i_registers *regs_dma, + smsc9218i_receive_job_control *jc +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + struct ifnet *ifp = &e->arpcom.ac_if; + int i = 0; + + /* Obtain receive eDMA channel */ + sc = mpc55xx_edma_obtain_channel( + &e->edma_receive, + MPC55XX_INTC_DEFAULT_PRIORITY + ); + ASSERT_SC(sc); + + for (i = 0; i < SMSC9218I_RX_JOBS; ++i) { + struct tcd_t *tcd = &jc->tcd_table [i]; + struct tcd_t *next_tcd = &jc->tcd_table [(i + 1) % SMSC9218I_RX_JOBS]; + + tcd->SADDR = (uint32_t) ®s_dma->rx_fifo_data; + tcd->SDF.B.SSIZE = 0x2; + tcd->SDF.B.DSIZE = 0x2; + tcd->CDF.B.CITER = 1; + tcd->CDF.B.DOFF = 4; + tcd->DLAST_SGA = (int32_t) next_tcd; + + smsc9218i_new_mbuf(ifp, jc, i, NULL); + } +} + +static void smsc9218i_ether_input( + smsc9218i_driver_entry *e, + volatile smsc9218i_registers *regs, + smsc9218i_receive_job_control *jc +) +{ + rtems_interrupt_level level; + struct ifnet *ifp = &e->arpcom.ac_if; + int c = jc->consume; + int d = jc->done; + + while (c != d) { + struct mbuf *m = jc->mbuf_table [c]; + struct ether_header *eh = (struct ether_header *) + (mtod(m, char *) - ETHER_HDR_LEN); + + ++e->received_frames; + if (smsc9218i_new_mbuf(ifp, jc, c, m)) { + ether_input(ifp, eh, m); + } + + c = (c + 1) % SMSC9218I_RX_JOBS; + } + + jc->consume = c; + + rtems_interrupt_disable(level); + /* Enabling the receive interrupts while the DMA is active leads to chaos */ + if (c == jc->produce) { + regs->int_en |= SMSC9218I_INT_RSFL; + } + rtems_interrupt_enable(level); +} + +static void smsc9218i_receive_task(void *arg) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_interrupt_level level; + smsc9218i_receive_job_control *jc = &smsc_rx_jc; + smsc9218i_driver_entry *e = (smsc9218i_driver_entry *) arg; + volatile smsc9218i_registers *const regs = smsc9218i; + volatile smsc9218i_registers *const regs_dma = smsc9218i_dma; + uint32_t mac_cr = 0; + + smsc9218i_init_receive_jobs(e, regs, regs_dma, jc); + + /* Configure receiver */ + regs->rx_cfg = SMSC9218I_RX_CFG_END_ALIGN_4 + | SMSC9218I_RX_CFG_DOFF(SMSC9218I_RX_DATA_OFFSET); + + /* Enable MAC receiver */ + mac_cr = smsc9218i_mac_read(regs, SMSC9218I_MAC_CR, NULL); + mac_cr |= SMSC9218I_MAC_CR_RXEN; + smsc9218i_mac_write(regs, SMSC9218I_MAC_CR, mac_cr); + + /* Enable receive interrupts */ + rtems_interrupt_disable(level); + regs->int_en |= SMSC9218I_INT_RSFL; + rtems_interrupt_enable(level); + + /* Enable PHY interrupts */ + smsc9218i_phy_write( + regs, + SMSC9218I_PHY_IMR, + SMSC9218I_PHY_IMR_INT4 | SMSC9218I_PHY_IMR_INT6 + ); + smsc9218i_enable_phy_interrupts(regs); + + while (true) { + rtems_event_set events; + + sc = rtems_bsdnet_event_receive( + SMSC9218I_EVENT_DMA | SMSC9218I_EVENT_PHY | SMSC9218I_EVENT_RX, + RTEMS_EVENT_ANY | RTEMS_WAIT, + RTEMS_NO_TIMEOUT, + &events + ); + ASSERT_SC(sc); + + if ((events & (SMSC9218I_EVENT_RX | SMSC9218I_EVENT_DMA)) != 0) { + smsc9218i_setup_receive_dma(e, regs, jc); + } + + if ((events & SMSC9218I_EVENT_DMA) != 0) { + smsc9218i_ether_input(e, regs, jc); + } + + if ((events & SMSC9218I_EVENT_PHY) != 0) { + smsc9218i_media_status_change(e, regs); + } + } +} + +#if defined(DEBUG) +static void smsc9218i_transmit_job_dump( + smsc9218i_transmit_job_control *jc, + const char *msg +) +{ + char out [SMSC9218I_TX_JOBS + 1]; + int c = 0; + int s = 0; + + out [SMSC9218I_TX_JOBS] = '\0'; + + memset(out, '?', SMSC9218I_TX_JOBS); + + c = jc->todo_index; + s = jc->empty; + while (s > 0) { + out [c] = 'E'; + --s; + c = (c + 1) % SMSC9218I_TX_JOBS; + } + + c = jc->transfer_index; + s = jc->todo; + while (s > 0) { + out [c] = 'T'; + --s; + c = (c + 1) % SMSC9218I_TX_JOBS; + } + + c = jc->empty_index; + s = jc->transfer; + while (s > 0) { + out [c] = 'D'; + --s; + c = (c + 1) % SMSC9218I_TX_JOBS; + } + + printf( + "tx: %s: %02u:%02u:%02u %s\n", + msg, + jc->empty, + jc->todo, + jc->transfer, + out + ); +} +#endif /* defined(DEBUG) */ + +static struct mbuf *smsc9218i_compact_frame( + smsc9218i_transmit_job_control *jc, + uint32_t frame_length +) +{ + struct mbuf *old_m = jc->frame; + struct mbuf *new_m = m_gethdr(M_WAIT, MT_DATA); + char *data = NULL; + + ++jc->frame_compact_count; + + MCLGET(new_m, M_WAIT); + data = mtod(new_m, char *); + + new_m->m_len = (int) frame_length; + new_m->m_pkthdr.len = (int) frame_length; + + while (old_m != NULL) { + size_t len = (size_t) old_m->m_len; + memcpy(data, mtod(old_m, void *), len); + data += len; + old_m = m_free(old_m); + } + + jc->frame = new_m; + + return new_m; +} + +static struct mbuf *smsc9218i_next_transmit_fragment( + struct ifnet *ifp, + smsc9218i_transmit_job_control *jc +) +{ + struct mbuf *m = jc->next_fragment; + + if (m != NULL) { + /* Next fragment is from the current frame */ + jc->next_fragment = m->m_next; + } else { + /* Dequeue first fragment of the next frame */ + IF_DEQUEUE(&ifp->if_snd, m); + + /* Update frame head */ + jc->frame = m; + + if (m != NULL) { + struct mbuf *n = m; + struct mbuf *p = NULL; + uint32_t frame_length = 0; + unsigned fragments = 0; + bool tiny = false; + + /* Calculate frame length and fragment number */ + do { + int len = n->m_len; + + if (len > 0) { + ++fragments; + frame_length += (uint32_t) len; + tiny = tiny || len < 4; + + /* Next fragment */ + p = n; + n = n->m_next; + } else { + /* Discard empty fragment and get next */ + n = m_free(n); + + /* Remove fragment from list */ + if (p != NULL) { + p->m_next = n; + } else { + jc->frame = n; + } + } + } while (n != NULL); + + if (SMSC9218I_UNLIKELY(tiny || fragments > SMSC9218I_TX_JOBS)) { + fragments = 1; + m = smsc9218i_compact_frame(jc, frame_length); + } + + /* Set frame length */ + jc->frame_length = frame_length; + + /* Update next fragment */ + jc->next_fragment = jc->frame->m_next; + + /* Update tag */ + ++jc->tag; + + /* Command B */ + jc->command_b = ((uint32_t) SMSC9218I_TX_B_TAG(jc->tag)) + | SMSC9218I_TX_B_FRAME_LENGTH(jc->frame_length); + + SMSC9218I_PRINTF( + "tx: next frame: tag = %i, frame length = %" PRIu32 + ", fragments = %u\n", + jc->tag, + frame_length, + fragments + ); + } else { + /* The transmit queue is empty, we have no next fragment */ + jc->next_fragment = NULL; + + /* Interface is now inactive */ + ifp->if_flags &= ~IFF_OACTIVE; + + /* Transmit task may wait for events */ + jc->done = true; + + SMSC9218I_PRINTF("tx: inactive\n"); + } + } + + return m; +} + +static void smsc9218i_transmit_create_jobs( + smsc9218i_transmit_job_control *jc, + volatile smsc9218i_registers *const regs, + struct ifnet *ifp +) +{ + int n = jc->empty; + + if (n > 0) { + int c = jc->todo_index; + int i = 0; + + #ifdef DEBUG + smsc9218i_transmit_job_dump(jc, "create"); + #endif + + for (i = 0; i < n; ++i) { + struct mbuf *m = smsc9218i_next_transmit_fragment(ifp, jc); + + if (m != NULL) { + uint32_t first = m == jc->frame ? SMSC9218I_TX_A_FIRST : 0; + uint32_t last = m->m_next == NULL ? SMSC9218I_TX_A_LAST : 0; + uint32_t fragment_length = (uint32_t) m->m_len; + uint32_t fragment_misalign = (uint32_t) (mtod(m, uintptr_t) % 4); + uint32_t data_length = fragment_misalign + fragment_length; + uint32_t data_misalign = data_length % 4; + uint32_t data = (uint32_t) (mtod(m, char *) - fragment_misalign); + + /* Align data length on four byte boundary */ + if (data_misalign > 0) { + data_length += 4 - data_misalign; + } + + /* Cache flush for fragment data */ + rtems_cache_flush_multiple_data_lines( + (const void *) data, + data_length + ); + + /* Remember fragement */ + jc->fragment_table [c] = m; + + /* Command A and B */ + jc->command_table [c].a = SMSC9218I_TX_A_END_ALIGN_4 + | SMSC9218I_TX_A_DOFF(fragment_misalign) + | SMSC9218I_TX_A_FRAGMENT_LENGTH(fragment_length) + | first + | last; + jc->command_table [c].b = jc->command_b; + + /* Data TCD */ + jc->data_tcd_table [c].SADDR = data; + jc->data_tcd_table [c].NBYTES = data_length; + + SMSC9218I_PRINTF("tx: create: index = %u\n", c); + } else { + /* Nothing to do */ + break; + } + + c = (c + 1) % SMSC9218I_TX_JOBS; + } + + /* Increment jobs to do */ + jc->todo += i; + + /* Decrement empty count */ + jc->empty -= i; + + /* Update todo index */ + jc->todo_index = c; + + #ifdef DEBUG + smsc9218i_transmit_job_dump(jc, "create done"); + #endif + } else { + /* Transmit task may wait for events */ + jc->done = true; + } +} + +static void smsc9218i_transmit_do_jobs( + smsc9218i_transmit_job_control *jc, + volatile smsc9218i_registers *const regs, + smsc9218i_driver_entry *e +) +{ + if (jc->transfer == 0) { + uint32_t tx_fifo_inf = regs->tx_fifo_inf; + uint32_t data_free = SMSC9218I_TX_FIFO_INF_GET_FREE(tx_fifo_inf); + int c = jc->transfer_index; + int last_index = c; + int i = 0; + int n = jc->todo; + + #ifdef DEBUG + smsc9218i_transmit_job_dump(jc, "transfer"); + #endif + + for (i = 0; i < n; ++i) { + struct tcd_t *tcd = &jc->data_tcd_table [c]; + uint32_t data_length = tcd->NBYTES + 14; + + if (data_length <= data_free) { + /* Reduce free FIFO space */ + data_free -= data_length; + + /* Index of last TCD */ + last_index = c; + + /* Cache flush for data TCD */ + smsc9218i_flush_tcd(tcd); + } else { + break; + } + + c = (c + 1) % SMSC9218I_TX_JOBS; + } + + if (i > 0) { + volatile struct tcd_t *channel = e->edma_transmit.edma_tcd; + struct tcd_t *start = &jc->command_tcd_table [jc->transfer_index]; + struct tcd_t *last = &jc->data_tcd_table [last_index]; + + /* New transfer index */ + jc->transfer_index = c; + + /* New last transfer index */ + jc->transfer_last_index = last_index; + + /* Set jobs in transfer */ + jc->transfer = i; + + /* Decrement jobs to do */ + jc->todo -= i; + + /* Cache flush for command table */ + rtems_cache_flush_multiple_data_lines( + jc->command_table, + sizeof(jc->command_table) + ); + + /* Enable interrupt for last data TCD */ + last->BMF.R = SMSC9218I_TCD_BMF_LAST; + smsc9218i_flush_tcd(last); + ppc_synchronize_data(); + + /* Start eDMA transfer */ + channel->SADDR = start->SADDR; + channel->SDF.R = start->SDF.R; + channel->NBYTES = start->NBYTES; + channel->SLAST = start->SLAST; + channel->DADDR = start->DADDR; + channel->CDF.R = start->CDF.R; + channel->DLAST_SGA = start->DLAST_SGA; + channel->BMF.R = SMSC9218I_TCD_BMF_CLEAR; + channel->BMF.R = start->BMF.R; + + /* Transmit task may wait for events */ + jc->done = true; + } else if (n > 0) { + /* We need to wait until the FIFO has more free space */ + smsc9218i_enable_transmit_interrupts(regs); + + /* Transmit task may wait for events */ + jc->done = true; + } + + #ifdef DEBUG + smsc9218i_transmit_job_dump(jc, "transfer done"); + #endif + } +} + +static void smsc9218i_transmit_finish_jobs( + smsc9218i_transmit_job_control *jc, + volatile smsc9218i_registers *const regs, + smsc9218i_driver_entry *e, + rtems_event_set events +) +{ + uint32_t tx_fifo_inf = regs->tx_fifo_inf; + uint32_t status_used = SMSC9218I_TX_FIFO_INF_GET_SUSED(tx_fifo_inf); + uint32_t s = 0; + int n = jc->transfer; + + for (s = 0; s < status_used; ++s) { + uint32_t tx_fifo_status = regs->tx_fifo_status; + + if ((tx_fifo_status & SMSC9218I_TX_STS_ERROR) == 0) { + ++e->transmitted_frames; + } else { + ++e->transmit_frame_errors; + } + + SMSC9218I_PRINTF( + "tx: transmit status: tag = %" PRIu32 ", status = 0x%08" PRIx32 "\n", + SMSC9218I_TX_STS_GET_TAG(tx_fifo_status), + SMSC9218I_SWAP(tx_fifo_status) + ); + } + + if ( + (events & (SMSC9218I_EVENT_DMA | SMSC9218I_EVENT_DMA_ERROR)) != 0 + && n > 0 + ) { + int c = jc->empty_index; + int i = 0; + + #ifdef DEBUG + smsc9218i_transmit_job_dump(jc, "finish"); + #endif + + if ((events & SMSC9218I_EVENT_DMA_ERROR) != 0) { + ++e->transmit_dma_errors; + } + + /* Restore last data TCD */ + jc->data_tcd_table [jc->transfer_last_index].BMF.R = + SMSC9218I_TCD_BMF_LINK; + + for (i = 0; i < n; ++i) { + /* Free fragment buffer */ + m_free(jc->fragment_table [c]); + + c = (c + 1) % SMSC9218I_TX_JOBS; + } + + /* Increment empty count */ + jc->empty += n; + + /* New empty index */ + jc->empty_index = jc->transfer_index; + + /* Clear jobs in transfer */ + jc->transfer = 0; + + /* Update empty index */ + jc->empty_index = c; + + #ifdef DEBUG + smsc9218i_transmit_job_dump(jc, "finish done"); + #endif + } +} + +/* FIXME */ +static smsc9218i_transmit_job_control smsc_tx_jc __attribute__((aligned (32))) = { + .frame = NULL, + .next_fragment = NULL, + .frame_length = 0, + .tag = 0, + .command_b = 0, + .done = false, + .empty_index = 0, + .transfer_index = 0, + .todo_index = 0, + .empty = SMSC9218I_TX_JOBS, + .transfer = 0, + .todo = 0 +}; + +static void smsc9218i_transmit_task(void *arg) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_event_set events = 0; + smsc9218i_driver_entry *e = (smsc9218i_driver_entry *) arg; + struct ifnet *ifp = &e->arpcom.ac_if; + volatile smsc9218i_registers *const regs = smsc9218i; + volatile smsc9218i_registers *const regs_dma = smsc9218i_dma; + uint32_t mac_cr = 0; + smsc9218i_transmit_job_control *jc = &smsc_tx_jc; + unsigned i = 0; + + SMSC9218I_PRINTF("%s\n", __func__); + + /* Obtain transmit eDMA channel */ + sc = mpc55xx_edma_obtain_channel( + &e->edma_transmit, + MPC55XX_INTC_DEFAULT_PRIORITY + ); + ASSERT_SC(sc); + + /* Setup transmit eDMA descriptors */ + for (i = 0; i < SMSC9218I_TX_JOBS; ++i) { + unsigned ii = (i + 1) % SMSC9218I_TX_JOBS; + struct tcd_t tcd = EDMA_TCD_DEFAULT; + struct tcd_t *command_tcd = &jc->command_tcd_table [i]; + struct tcd_t *data_tcd = &jc->data_tcd_table [i]; + struct tcd_t *next_command_tcd = &jc->command_tcd_table [ii]; + + tcd.SDF.B.SSIZE = 2; + tcd.SDF.B.SOFF = 4; + tcd.SDF.B.DSIZE = 2; + tcd.CDF.B.CITER = 1; + tcd.BMF.R = SMSC9218I_TCD_BMF_LINK; + + tcd.DADDR = (uint32_t) ®s_dma->tx_fifo_data; + tcd.DLAST_SGA = (int32_t) next_command_tcd; + *data_tcd = tcd; + + tcd.DADDR = (uint32_t) ®s->tx_fifo_data; + tcd.SADDR = (uint32_t) &jc->command_table [i].a; + tcd.NBYTES = 8; + tcd.DLAST_SGA = (int32_t) data_tcd; + *command_tcd = tcd; + } + + /* + * Cache flush for command TCD. The content of the command TCD remains + * invariant. + */ + rtems_cache_flush_multiple_data_lines( + jc->command_tcd_table, + sizeof(jc->command_tcd_table) + ); + + /* Configure transmitter */ + regs->tx_cfg = SMSC9218I_TX_CFG_SAO | SMSC9218I_TX_CFG_ON; + + /* Enable MAC transmitter */ + mac_cr = smsc9218i_mac_read(regs, SMSC9218I_MAC_CR, NULL) | SMSC9218I_MAC_CR_TXEN; + smsc9218i_mac_write(regs, SMSC9218I_MAC_CR, mac_cr); + + /* Main event loop */ + while (true) { + /* Wait for events */ + sc = rtems_bsdnet_event_receive( + SMSC9218I_EVENT_TX + | SMSC9218I_EVENT_TX_START + | SMSC9218I_EVENT_DMA + | SMSC9218I_EVENT_DMA_ERROR, + RTEMS_EVENT_ANY | RTEMS_WAIT, + RTEMS_NO_TIMEOUT, + &events + ); + ASSERT_SC(sc); + + SMSC9218I_PRINTF("tx: wake up: events = 0x%08" PRIx32 "\n", events); + + do { + jc->done = false; + smsc9218i_transmit_finish_jobs(jc, regs, e, events); + smsc9218i_transmit_create_jobs(jc, regs, ifp); + smsc9218i_transmit_do_jobs(jc, regs, e); + } while (!jc->done); + + SMSC9218I_PRINTF("tx: done\n"); + } + + /* Release network semaphore */ + rtems_bsdnet_semaphore_release(); + + /* Terminate self */ + (void) rtems_task_delete(RTEMS_SELF); +} + +#if defined(DEBUG) +static void smsc9218i_test_macros(void) +{ + unsigned i = 0; + unsigned byte_test = 0x87654321U; + unsigned val8 = 0xa5; + unsigned val16 = 0xa55a; + int r = 0; + + r = SMSC9218I_SWAP(SMSC9218I_BYTE_TEST) == byte_test; + printf("[%i] SMSC9218I_SWAP\n", r); + + for (i = 0; i < 32; ++i) { + r = SMSC9218I_SWAP(SMSC9218I_FLAG(i)) == (1U << i); + printf("[%i] flag: %u\n", r, i); + } + + for (i = 0; i < 32; i += 8) { + r = SMSC9218I_SWAP(SMSC9218I_FIELD_8(val8, i)) == (val8 << i); + printf("[%i] field 8: %u\n", r, i); + } + + for (i = 0; i < 32; i += 16) { + r = SMSC9218I_SWAP(SMSC9218I_FIELD_16(val16, i)) == (val16 << i); + printf("[%i] field 16: %u\n", r, i); + } + + for (i = 0; i < 32; i += 8) { + r = SMSC9218I_GET_FIELD_8(SMSC9218I_BYTE_TEST, i) + == ((byte_test >> i) & 0xffU); + printf("[%i] get field 8: %u\n", r, i); + } + + for (i = 0; i < 32; i += 16) { + r = SMSC9218I_GET_FIELD_16(SMSC9218I_BYTE_TEST, i) + == ((byte_test >> i) & 0xffffU); + printf("[%i] get field 16: %u\n", r, i); + } +} +#endif + +static bool smsc9218i_wait_for_eeprom_access( + volatile smsc9218i_registers *regs +) +{ + rtems_interval start = smsc9218i_timeout_init(); + bool busy; + + while ( + (busy = (regs->e2p_cmd & SMSC9218I_E2P_CMD_EPC_BUSY) != 0) + && smsc9218i_timeout_not_expired(start) + ) { + /* Wait */ + } + + return !busy; +} + +static bool smsc9218i_get_mac_address( + volatile smsc9218i_registers *regs, + uint8_t address [6] +) +{ + bool ok = false; + + uint32_t low = smsc9218i_mac_read(regs, SMSC9218I_MAC_ADDRL, &ok); + address [0] = (uint8_t) low; + address [1] = (uint8_t) (low >> 8) & 0xff; + address [2] = (uint8_t) (low >> 16); + address [3] = (uint8_t) (low >> 24); + + if (ok) { + uint32_t high = smsc9218i_mac_read(regs, SMSC9218I_MAC_ADDRH, &ok); + address [4] = (uint8_t) high; + address [5] = (uint8_t) (high >> 8); + } + + return ok; +} + +static bool smsc9218i_set_mac_address( + volatile smsc9218i_registers *regs, + const uint8_t address [6] +) +{ + bool ok = smsc9218i_mac_write( + regs, + SMSC9218I_MAC_ADDRL, + ((uint32_t) address [3] << 24) | ((uint32_t) address [2] << 16) + | ((uint32_t) address [1] << 8) | (uint32_t) address [0] + ); + + if (ok) { + ok = smsc9218i_mac_write( + regs, + SMSC9218I_MAC_ADDRH, + ((uint32_t) address [5] << 8) | (uint32_t) address [4] + ); + } + + return ok; +} + +/* Sometimes the write of the MAC address was not reliable */ +static bool smsc9218i_set_and_verify_mac_address( + volatile smsc9218i_registers *regs, + const uint8_t address [6] +) +{ + bool ok = true; + int i; + + for (i = 0; ok && i < 3; ++i) { + ok = smsc9218i_set_mac_address(regs, address); + + if (ok) { + uint8_t actual_address [6]; + + ok = smsc9218i_get_mac_address(regs, actual_address) + && memcmp(address, actual_address, sizeof(actual_address)) == 0; + } + } + + return ok; +} + +#if defined(DEBUG) +static void smsc9218i_mac_address_dump(volatile smsc9218i_registers *regs) +{ + uint8_t mac_address [6]; + bool ok = smsc9218i_get_mac_address(regs, mac_address); + + if (ok) { + printf( + "MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n", + mac_address [0], + mac_address [1], + mac_address [2], + mac_address [3], + mac_address [4], + mac_address [5] + ); + } else { + printf("cannot read MAC address\n"); + } +} +#endif + +static void smsc9218i_interrupt_init( + smsc9218i_driver_entry *e, + volatile smsc9218i_registers *regs +) +{ +#ifdef SMSC9218I_IRQ_PIN + rtems_status_code sc = RTEMS_SUCCESSFUL; + union SIU_PCR_tag pcr = MPC55XX_ZERO_FLAGS; + union SIU_DIRER_tag direr = MPC55XX_ZERO_FLAGS; + union SIU_DIRSR_tag dirsr = MPC55XX_ZERO_FLAGS; + union SIU_ORER_tag orer = MPC55XX_ZERO_FLAGS; + union SIU_IREER_tag ireer = MPC55XX_ZERO_FLAGS; + union SIU_IFEER_tag ifeer = MPC55XX_ZERO_FLAGS; + union SIU_IDFR_tag idfr = MPC55XX_ZERO_FLAGS; + rtems_interrupt_level level; + + /* Configure IRQ input pin */ + pcr.B.PA = 2; + pcr.B.OBE = 0; + pcr.B.IBE = 1; +#if MPC55XX_CHIP_FAMILY != 551 + pcr.B.DSC = 0; +#endif + pcr.B.ODE = 0; + pcr.B.HYS = 0; + pcr.B.SRC = 0; + pcr.B.WPE = 0; + pcr.B.WPS = 1; + SIU.PCR [SMSC9218I_IRQ_PIN].R = pcr.R; + + /* DMA/Interrupt Request Select */ + rtems_interrupt_disable(level); + dirsr.R = SIU.DIRSR.R; +#if MPC55XX_CHIP_FAMILY != 551 + dirsr.B.DIRS0 = 0; +#endif + SIU.DIRSR.R = dirsr.R; + rtems_interrupt_enable(level); + + /* Overrun Request Enable */ + rtems_interrupt_disable(level); + orer.R = SIU.ORER.R; + orer.B.ORE0 = 0; + SIU.ORER.R = orer.R; + rtems_interrupt_enable(level); + + /* IRQ Rising-Edge Enable */ + rtems_interrupt_disable(level); + ireer.R = SIU.IREER.R; + ireer.B.IREE0 = 0; + SIU.IREER.R = ireer.R; + rtems_interrupt_enable(level); + + /* IRQ Falling-Edge Enable */ + rtems_interrupt_disable(level); + ifeer.R = SIU.IFEER.R; + ifeer.B.IFEE0 = 1; + SIU.IFEER.R = ifeer.R; + rtems_interrupt_enable(level); + + /* IRQ Digital Filter */ + rtems_interrupt_disable(level); + idfr.R = SIU.IDFR.R; + idfr.B.DFL = 0; + SIU.IDFR.R = idfr.R; + rtems_interrupt_enable(level); + + /* Clear external interrupt status */ + SIU.EISR.R = 1; + + /* DMA/Interrupt Request Enable */ + rtems_interrupt_disable(level); + direr.R = SIU.DIRER.R; + direr.B.EIRE0 = 1; + SIU.DIRER.R = direr.R; + rtems_interrupt_enable(level); + + /* Install interrupt handler */ + sc = rtems_interrupt_handler_install( + MPC55XX_IRQ_SIU_EXTERNAL_0, + "SMSC9218i", + RTEMS_INTERRUPT_UNIQUE, + smsc9218i_interrupt_handler, + e + ); + ASSERT_SC(sc); + + /* Enable interrupts and use push-pull driver (active low) */ + regs->irq_cfg = SMSC9218I_IRQ_CFG_GLOBAL_ENABLE; + + /* Enable error interrupts */ + regs->int_en = SMSC9218I_ERROR_INTERRUPTS; +#endif +} + +static void smsc9218i_reset_signal(bool signal) +{ +#ifdef SMSC9218I_RESET_PIN + SIU.GPDO [SMSC9218I_RESET_PIN].R = signal ? 1 : 0; +#endif +} + +static void smsc9218i_reset_signal_init(void) +{ +#ifdef SMSC9218I_RESET_PIN + union SIU_PCR_tag pcr = MPC55XX_ZERO_FLAGS; + + smsc9218i_reset_signal(true); + + pcr.B.PA = 0; + pcr.B.OBE = 1; + pcr.B.IBE = 0; +#if MPC55XX_CHIP_FAMILY != 551 + pcr.B.DSC = 0; +#endif + pcr.B.ODE = 0; + pcr.B.HYS = 0; + pcr.B.SRC = 0; + pcr.B.WPE = 1; + pcr.B.WPS = 1; + + SIU.PCR [SMSC9218I_RESET_PIN].R = pcr.R; +#endif +} + +static bool smsc9218i_hardware_reset(volatile smsc9218i_registers *regs) +{ + rtems_interval start = smsc9218i_timeout_init(); + bool not_ready; + + smsc9218i_reset_signal_init(); + smsc9218i_reset_signal(false); + rtems_bsp_delay(200); + smsc9218i_reset_signal(true); + + while ( + (not_ready = (regs->pmt_ctrl & SMSC9218I_PMT_CTRL_READY) == 0) + && smsc9218i_timeout_not_expired(start) + ) { + /* Wait */ + } + + return !not_ready; +} + +static void smsc9218i_interface_init(void *arg) +{ + smsc9218i_driver_entry *e = (smsc9218i_driver_entry *) arg; + struct ifnet *ifp = &e->arpcom.ac_if; + volatile smsc9218i_registers *const regs = smsc9218i; + bool ok = true; + + SMSC9218I_PRINTF("%s\n", __func__); + + if (e->state == SMSC9218I_CONFIGURED) { + ok = smsc9218i_hardware_reset(regs); + if (ok) { + e->state = SMSC9218I_NOT_INITIALIZED; + } + } + + if (e->state == SMSC9218I_NOT_INITIALIZED) { +#if defined(DEBUG) + smsc9218i_register_dump(regs); +#endif + + /* Set hardware configuration */ + regs->hw_cfg = SMSC9218I_HW_CFG_MBO | SMSC9218I_HW_CFG_TX_FIF_SZ(5); + + ok = smsc9218i_wait_for_eeprom_access(regs); + + if (ok) { + ok = smsc9218i_set_and_verify_mac_address(regs, e->arpcom.ac_enaddr); + + if (ok) { +#if defined(DEBUG) + smsc9218i_mac_address_dump(regs); +#endif + + /* Auto-negotiation advertisment */ + ok = smsc9218i_phy_write( + regs, + MII_ANAR, + ANAR_TX_FD | ANAR_TX | ANAR_10_FD | ANAR_10 | ANAR_CSMA + ); + + if (ok) { +#ifdef SMSC9218I_ENABLE_LED_OUTPUTS + regs->gpio_cfg = SMSC9218I_HW_CFG_LED_1 + | SMSC9218I_HW_CFG_LED_2 + | SMSC9218I_HW_CFG_LED_3; +#endif + + smsc9218i_interrupt_init(e, regs); + + /* Set MAC control */ + ok = smsc9218i_mac_write(regs, SMSC9218I_MAC_CR, SMSC9218I_MAC_CR_FDPX); + if (ok) { + /* Set FIFO interrupts */ + regs->fifo_int = SMSC9218I_FIFO_INT_TDAL(32); + + /* Clear receive drop counter */ + regs->rx_drop; + + /* Start receive task */ + if (e->receive_task == RTEMS_ID_NONE) { + e->receive_task = rtems_bsdnet_newproc( + "ntrx", + 4096, + smsc9218i_receive_task, + e + ); + } + + /* Start transmit task */ + if (e->transmit_task == RTEMS_ID_NONE) { + e->transmit_task = rtems_bsdnet_newproc( + "nttx", + 4096, + smsc9218i_transmit_task, + e + ); + } + + /* Change state */ + if (e->receive_task != RTEMS_ID_NONE + && e->transmit_task != RTEMS_ID_NONE) { + e->state = SMSC9218I_STARTED; + } + } + } + } + } + } + + if (e->state == SMSC9218I_STARTED) { + /* Enable promiscous mode */ + ok = smsc9218i_enable_promiscous_mode( + regs, + (ifp->if_flags & IFF_PROMISC) != 0 + ); + + if (ok) { + /* Set interface to running state */ + ifp->if_flags |= IFF_RUNNING; + + /* Change state */ + e->state = SMSC9218I_RUNNING; + } + } +} + +static int smsc9218i_mdio_read( + int phy, + void *arg, + unsigned phy_reg, + uint32_t *val +) +{ + volatile smsc9218i_registers *const regs = smsc9218i; + + *val = smsc9218i_phy_read(regs, phy_reg); + + return 0; +} + +static int smsc9218i_mdio_write( + int phy, + void *arg, + unsigned phy_reg, + uint32_t data +) +{ + volatile smsc9218i_registers *const regs = smsc9218i; + + smsc9218i_phy_write(regs, phy_reg, data); + + return 0; +} + +static void smsc9218i_interface_stats(smsc9218i_driver_entry *e) +{ + volatile smsc9218i_registers *const regs = smsc9218i; + smsc9218i_transmit_job_control *jc = &smsc_tx_jc; + int media = 0; + bool media_ok = smsc9218i_media_status(e, &media); + + if (media_ok) { + rtems_ifmedia2str(media, NULL, 0); + printf ("\n"); + } else { + printf ("PHY communication error\n"); + } + + e->receive_drop += SMSC9218I_SWAP(regs->rx_drop); + + printf("PHY interrupts: %u\n", e->phy_interrupts); + printf("received frames: %u\n", e->received_frames); + printf("receiver errors: %u\n", e->receiver_errors); + printf("receive interrupts: %u\n", e->receive_interrupts); + printf("receive DMA interrupts: %u\n", e->receive_dma_interrupts); + printf("receive to long errors: %u\n", e->receive_too_long_errors); + printf("receive collision errors: %u\n", e->receive_collision_errors); + printf("receive CRC errors: %u\n", e->receive_crc_errors); + printf("receive DMA errors: %u\n", e->receive_dma_errors); + printf("receive drops: %u\n", e->receive_drop); + printf("receive watchdog timeouts: %u\n", e->receive_watchdog_timeouts); + printf("transmitted frames: %u\n", e->transmitted_frames); + printf("transmitter errors: %u\n", e->transmitter_errors); + printf("transmit interrupts: %u\n", e->transmit_interrupts); + printf("transmit DMA interrupts: %u\n", e->transmit_dma_interrupts); + printf("transmit status overflows: %u\n", e->transmit_status_overflows); + printf("transmit frame errors: %u\n", e->transmit_frame_errors); + printf("transmit DMA errors: %u\n", e->transmit_dma_errors); + printf("frame compact count: %u\n", jc->frame_compact_count); + printf("interface state: %s\n", state_to_string [e->state]); +} + +static void smsc9218i_interface_off(struct ifnet *ifp) +{ + smsc9218i_driver_entry *e = (smsc9218i_driver_entry *) ifp->if_softc; + rtems_status_code sc = RTEMS_SUCCESSFUL; + + sc = rtems_task_suspend(e->receive_task); + ASSERT_SC(sc); + + sc = rtems_task_suspend(e->transmit_task); + ASSERT_SC(sc); + + /* remove interrupt handler */ + sc = rtems_interrupt_handler_remove( + MPC55XX_IRQ_SIU_EXTERNAL_0, + smsc9218i_interrupt_handler, + e + ); + ASSERT_SC(sc); + + mpc55xx_edma_release_channel( + &e->edma_receive + ); + + mpc55xx_edma_release_channel( + &e->edma_transmit + ); + + /* set in reset state */ + smsc9218i_reset_signal(false); +} + +static int smsc9218i_interface_ioctl( + struct ifnet *ifp, + ioctl_command_t command, + caddr_t data +) { + smsc9218i_driver_entry *e = (smsc9218i_driver_entry *) ifp->if_softc; + int rv = 0; + + SMSC9218I_PRINTF("%s\n", __func__); + + switch (command) { + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + rtems_mii_ioctl(&e->mdio, e, command, (int *) data); + break; + case SIOCGIFADDR: + case SIOCSIFADDR: + ether_ioctl(ifp, command, data); + break; + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + /* TODO: init */ + } else { + smsc9218i_interface_off(ifp); + } + break; + case SIO_RTEMS_SHOW_STATS: + smsc9218i_interface_stats(e); + break; + default: + rv = EINVAL; + break; + } + + return rv; +} + +static void smsc9218i_interface_start(struct ifnet *ifp) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + smsc9218i_driver_entry *e = (smsc9218i_driver_entry *) ifp->if_softc; + + /* Interface is now active */ + ifp->if_flags |= IFF_OACTIVE; + + sc = rtems_bsdnet_event_send(e->transmit_task, SMSC9218I_EVENT_TX_START); + ASSERT_SC(sc); +} + +static void smsc9218i_interface_watchdog(struct ifnet *ifp) +{ + SMSC9218I_PRINTF("%s\n", __func__); +} + +static void smsc9218i_attach(struct rtems_bsdnet_ifconfig *config) +{ + smsc9218i_driver_entry *e = &smsc9218i_driver_data; + struct ifnet *ifp = &e->arpcom.ac_if; + char *unit_name = NULL; + int unit_number = rtems_bsdnet_parse_driver_name(config, &unit_name); + + /* Check parameter */ + assert(unit_number == 0); + assert(config->hardware_address != NULL); + assert(e->state == SMSC9218I_NOT_INITIALIZED); + + /* Interrupt number */ + config->irno = MPC55XX_IRQ_SIU_EXTERNAL_0; + + /* Device control */ + config->drv_ctrl = e; + + /* Receive unit number */ + config->rbuf_count = 0; + + /* Transmit unit number */ + config->xbuf_count = 0; + + /* Copy MAC address */ + memcpy(e->arpcom.ac_enaddr, config->hardware_address, ETHER_ADDR_LEN); + + /* Set interface data */ + ifp->if_softc = e; + ifp->if_unit = (short) unit_number; + ifp->if_name = unit_name; + ifp->if_mtu = config->mtu > 0 ? (u_long) config->mtu : ETHERMTU; + ifp->if_init = smsc9218i_interface_init; + ifp->if_ioctl = smsc9218i_interface_ioctl; + ifp->if_start = smsc9218i_interface_start; + ifp->if_output = ether_output; + ifp->if_watchdog = smsc9218i_interface_watchdog; + ifp->if_flags = config->ignore_broadcast ? 0 : IFF_BROADCAST; + ifp->if_snd.ifq_maxlen = ifqmaxlen; + ifp->if_timer = 0; + + /* MDIO */ + e->mdio.mdio_r = smsc9218i_mdio_read; + e->mdio.mdio_w = smsc9218i_mdio_write; + + /* Change status */ + e->state = SMSC9218I_CONFIGURED; + + /* Attach the interface */ + if_attach(ifp); + ether_ifattach(ifp); +} + +int smsc9218i_attach_detach( + struct rtems_bsdnet_ifconfig *config, + int attaching +) { + if (attaching) { + smsc9218i_attach(config); + } else { + /* TODO */ + } + + /* FIXME: Return value */ + return 0; +} + +#endif /* defined(RTEMS_NETWORKING) && defined(MPC55XX_HAS_SIU) */ |