/** * @file * * @ingroup RTEMSBSPsPowerPCMPC55XX * * @brief SMSC - LAN9218i */ /* * Copyright (c) 2009-2012 embedded brains GmbH. All rights reserved. * * embedded brains GmbH * Obere Lagerstr. 30 * 82178 Puchheim * Germany * * * 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 #include #if defined(RTEMS_NETWORKING) && defined(MPC55XX_HAS_SIU) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 */ rtems_task_exit(); } #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) */