forked from Imagelibrary/rtems
2125 lines
52 KiB
C
2125 lines
52 KiB
C
/**
|
|
* @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)
|
|
|
|
#include <machine/rtems-bsd-kernel-space.h>
|
|
|
|
#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 */
|
|
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) */
|