nuttx/arch/arm/src/c5471/c5471_ethernet.c
Xiang Xiao 7990f90915 Indent the define statement by two spaces
follow the code style convention

Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2023-05-21 09:52:08 -03:00

2343 lines
66 KiB
C

/****************************************************************************
* arch/arm/src/c5471/c5471_ethernet.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#if defined(CONFIG_NET)
#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <debug.h>
#include <arpa/inet.h>
#include <net/ethernet.h>
#include <nuttx/wdog.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/wqueue.h>
#include <nuttx/net/netdev.h>
#ifdef CONFIG_NET_PKT
# include <nuttx/net/pkt.h>
#endif
#include "chip.h"
#include "arm_internal.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Configuration ************************************************************/
/* If processing is not done at the interrupt level, then work queue support
* is required.
*/
#if !defined(CONFIG_SCHED_WORKQUEUE)
# error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
#endif
/* The low priority work queue is preferred. If it is not enabled, LPWORK
* will be the same as HPWORK.
*
* NOTE: However, the network should NEVER run on the high priority work
* queue! That queue is intended only to service short back end interrupt
* processing that never suspends. Suspending the high priority work queue
* may bring the system to its knees!
*/
#define ETHWORK LPWORK
/* CONFIG_C5471_NET_NINTERFACES determines the number of physical interfaces
* that will be supported.
*/
#ifndef CONFIG_C5471_NET_NINTERFACES
# define CONFIG_C5471_NET_NINTERFACES 1
#endif
/* CONFIG_C5471_NET_STATS will enabled collection of driver statistics.
* Default is disabled.
*/
/* Mode of operation defaults to AUTONEGOTIATION */
#if defined(CONFIG_C5471_AUTONEGOTIATION)
# undef CONFIG_C5471_BASET100
# undef CONFIG_C5471_BASET10
#elif defined(CONFIG_C5471_BASET100)
# undef CONFIG_C5471_AUTONEGOTIATION
# undef CONFIG_C5471_BASET10
#elif defined(CONFIG_C5471_BASET10)
# undef CONFIG_C5471_AUTONEGOTIATION
# undef CONFIG_C5471_BASET100
#else
# define CONFIG_C5471_AUTONEGOTIATION 1
# undef CONFIG_C5471_BASET100
# undef CONFIG_C5471_BASET10
#endif
/* This should be disabled unless you are performing very low level debug */
#undef CONFIG_C5471_NET_DUMPBUFFER
/* Timing values ************************************************************/
/* TX timeout = 1 minute */
#define C5471_TXTIMEOUT (60*CLK_TCK)
/* Ethernet GPIO bit settings ***********************************************/
#define GPIO_CIO_MDIO 0x00004000
#define GPIO_IO_MDCLK 0x00008000
/* Ethernet interface bit settings ******************************************/
/* TX descriptor, word #0 */
#define EIM_TXDESC_OWN_HOST 0x80000000 /* Bit 15: Ownership bit */
#define EIM_TXDESC_OWN_ENET 0x00000000
#define EIM_TXDESC_WRAP_NEXT 0x40000000 /* Bit 14: Descriptor chain wrap */
#define EIM_TXDESC_WRAP_FIRST 0x00000000
#define EIM_TXDESC_FIF 0x20000000 /* Bit 13: First in frame */
#define EIM_TXDESC_LIF 0x10000000 /* Bit 12: Last in frame */
/* Bits 8-11: Retry count status */
#define EIM_TXDESC_INTRE 0x00800000 /* Bit 7: TX_IRQ int enable */
#define EIM_TXDESC_STATUSMASK 0x007f0000 /* Bits 0-6: Status */
#define EIM_TXDESC_RETRYERROR 0x00400000 /* Exceed retry error */
#define EIM_TXDESC_HEARTBEAT 0x00200000 /* Heartbeat (SQE) */
#define EIM_TXDESC_LCOLLISON 0x00100000 /* Late collision error */
#define EIM_TXDESC_COLLISION 0x00080000 /* Collision */
#define EIM_TXDESC_CRCERROR 0x00040000 /* CRC error */
#define EIM_TXDESC_UNDERRUN 0x00020000 /* Underrun error */
#define EIM_TXDESC_LOC 0x00010000 /* Loss of carrier */
/* Packet bytes value used for both TX and RX descriptors */
#define EIM_PACKET_BYTES 0x00000040
/* Count of descriptors */
#define NUM_DESC_TX 32
#define NUM_DESC_RX 64
/* TX descriptor, word #1 */
/* Bit 15: reserved */
#define EIM_TXDESC_PADCRC 0x00004000 /* Bit 14: Enable padding small frames */
/* Bits 11-13: reserved */
#define EIM_TXDESC_BYTEMASK 0x000007ff /* Bits 0-10: Descriptor byte count */
/* RX descriptor, word #0 */
#define EIM_RXDESC_OWN_HOST 0x80000000 /* Bit 15: Ownership bit */
#define EIM_RXDESC_OWN_ENET 0x00000000
#define EIM_RXDESC_WRAP_NEXT 0x40000000 /* Bit 14: Descriptor chain wrap */
#define EIM_RXDESC_WRAP_FIRST 0x00000000
#define EIM_RXDESC_FIF 0x20000000 /* Bit 13: First in frame */
#define EIM_RXDESC_LIF 0x10000000 /* Bit 12: Last in frame */
/* Bits 8-11: reserved */
#define EIM_RXDESC_INTRE 0x00800000 /* Bit 7: RX_IRQ int enable */
#define EIM_RXDESC_STATUSMASK 0x007f0000 /* Bits 0-6: Status */
#define EIM_RXDESC_MISS 0x00400000 /* Miss */
#define EIM_RXDESC_VLAN 0x00200000 /* VLAN */
#define EIM_RXDESC_LFRAME 0x00100000 /* Long frame error */
#define EIM_RXDESC_SFRAME 0x00080000 /* Short frame error */
#define EIM_RXDESC_CRCERROR 0x00040000 /* CRC error */
#define EIM_RXDESC_OVERRUN 0x00020000 /* Overrun error */
#define EIM_RXDESC_ALIGN 0x00010000 /* Non-octect align error */
#define EIM_RXDESC_PADCRC 0x00004000 /* Enable padding for small frames */
/* RX descriptor, word #1 */
/* Bits 11-15: reserved */
#define EIM_RXDESC_BYTEMASK 0x000007ff /* Bits 0-10: Descriptor byte count */
/* EIM_CPU_FILTER bit settings */
/* Bits 5-31: reserved */
#define EIM_FILTER_MACLA 0x00000010 /* Bit 4: Enable logical address+multicast filtering */
#define EIM_FILTER_LOGICAL 0x00000008 /* Bit 3: Enable ENET logical filtering */
#define EIM_FILTER_MULTICAST 0x00000004 /* Bit 2: Enable multicast filtering */
#define EIM_FILTER_BROADCAST 0x00000002 /* Bit 1: Enable broadcast matching */
#define EIM_FILTER_UNICAST 0x00000001 /* Bit 0: Enable dest CPU address matching */
/* EIM_CTRL bit settings */
/* Bits 16-31: Reserved */
#define EIM_CTRL_ESM_EN 0x00008000 /* Bit 15: Ethernet state machine enable */
/* Bits 9-14: reserved */
#define EIM_CTRL_ENET0_EN 0x00000100 /* Bit 8: Enable routing of RX packets CPU->ENET0 */
/* Bit 7: reserved */
#define EIM_CTRL_ENET0_FLW 0x00000040 /* Bit 6: Enable ENET0 flow control RX threshold */
#define EIM_CTRL_RXENET0_EN 0x00000020 /* Bit 5: Enable processing of ENET0 RX queue */
#define EIM_CTRL_TXENET0_EN 0x00000010 /* Bit 4: Enable processing of ENET0 TX queue */
/* Bits 2-3: reserved */
#define EIM_CTRL_RXCPU_EN 0x00000002 /* Bit 1: Enable processing of CPU RX queue */
#define EIM_CTRL_TXCPU_EN 0x00000001 /* Bit 0: Enable processing of CPU TX queue */
/* EIM_STATUS bit settings */
/* Bits 10-31: reserved */
#define EIM_STATUS_CPU_TXLIF 0x00000200 /* Bit 9: Last descriptor of TX packet filled */
#define EIM_STATUS_CPU_RXLIF 0x00000100 /* Bit 8: Last descriptor of RX queue processed */
#define EIM_STATUS_CPU_TX 0x00000080 /* Bit 7: Descriptor filled in TX queue */
#define EIM_STATUS_CPU_RX 0x00000040 /* Bit 6: Descriptor filled in RX queue */
/* Bits 3-5: reserved */
#define EIM_STATUS_ENET0_ERR 0x00000004 /* Bit 2: ENET0 error interrupt */
#define EIM_STATUS_ENET0_TX 0x00000002 /* Bit 1: ENET0 TX interrupt */
#define EIM_STATUS_ENET0_RX 0x00000001 /* Bit 0" ENET0 RX interrupt */
/* EIM_INTEN bit settings */
#define EIM_INTEN_CPU_TXLIF 0x00000200 /* Bit 9: Last descriptor of TX packet filled */
#define EIM_INTEN_CPU_RXLIF 0x00000100 /* Bit 8: Last descriptor of RX queue processed */
#define EIM_INTEN_CPU_TX 0x00000080 /* Bit 7: Descriptor filled in TX queue */
#define EIM_INTEN_CPU_RX 0x00000040 /* Bit 6: Descriptor filled in RX queue */
/* Bits 3-5: reserved */
#define EIM_INTEN_ENET0_ERR 0x00000004 /* Bit 2: ENET0 error interrupt */
#define EIM_INTEN_ENET0_TX 0x00000002 /* Bit 1: ENET0 TX interrupt */
#define EIM_INTEN_ENET0_RX 0x00000001 /* Bit 0: ENET0 RX interrupt */
/* ENET0_ADRMODE_EN bit settings */
#define ENET_ADR_PROMISCUOUS 0x00000008 /* Bit 3: Enable snoop address comparison */
#define ENET_ADR_BROADCAST 0x00000004 /* Bit 2: Enable broadcast address comparison */
#define ENET_ADDR_LCOMPARE 0x00000002 /* Bit 1: Enable logical address comparison */
#define ENET_ADDR_PCOMPARE 0x00000001 /* Bit 0: Enable physical address comparison */
/* ENET0_MODE bit settings */
/* Bits 16-31: reserved */
#define ENET_MODO_FIFO_EN 0x00008000 /* Bit 15: Fifo enable */
/* Bits 8-14: reserved */
#define ENET_MODE_RJCT_SFE 0x00000080 /* Bit 7: Reject short frames during receive */
#define ENET_MODE_DPNET 0x00000040 /* Bit 6: Demand priority networkd vs CSMA/CD */
#define ENET_MODE_MWIDTH 0x00000020 /* Bit 5: Select nibble mode MII port */
#define ENET_MODE_WRAP 0x00000010 /* Bit 4: Internal MAC loopback */
#define ENET_MODE_FDWRAP 0x00000008 /* Bit 3: Full duplex wrap */
#define ENET_MODE_FULLDUPLEX 0x00000004 /* Bit 2: 1:Full duplex */
#define ENET_MODE_HALFDUPLEX 0x00000000 /* 0:Half duplex */
/* Bit 1: reserved */
#define ENET_MODE_ENABLE 0x00000001 /* Bit 0: Port enable */
/* PHY registers */
#define MD_PHY_CONTROL_REG 0x00
#define MD_PHY_MSB_REG 0x02
#define MD_PHY_LSB_REG 0x03
#define MD_PHY_CTRL_STAT_REG 0x17
/* Lucent LU3X31T-T64 transeiver ID */
#define LU3X31_T64_PHYID 0x00437421
/* PHY control register bit settings */
#define MODE_AUTONEG 0x1000
#define MODE_10MBIT_HALFDUP 0x0000
#define MODE_10MBIT_FULLDUP 0x0100
#define MODE_100MBIT_FULLDUP 0x2100
#define MODE_100MBIT_HALFDUP 0x2000
/* Inserts an ARM "nop" instruction */
#define nop() asm(" nop");
/* This is a helper pointer for accessing the contents of Ethernet header */
#define BUF ((struct eth_hdr_s *)priv->c_dev.d_buf)
/****************************************************************************
* Private Types
****************************************************************************/
/* A single packet buffer is used */
static uint8_t g_pktbuf[CONFIG_C5471_NET_NINTERFACES]
[MAX_NETDEV_PKTSIZE + CONFIG_NET_GUARDSIZE];
/* The c5471_driver_s encapsulates all state information for a single c5471
* hardware interface
*/
struct c5471_driver_s
{
bool c_bifup; /* true:ifup false:ifdown */
struct wdog_s c_txtimeout; /* TX timeout timer */
struct work_s c_irqwork; /* For deferring interrupt work to the work queue */
struct work_s c_pollwork; /* For deferring poll work to the work queue */
/* Note: According to the C547x documentation: "The software has to
* maintain two pointers to the current RX-CPU and TX-CPU descriptors.
* At init time, they have to be set to the first descriptors of each
* queue, and they have to be incremented each time a descriptor
* ownership is give to the SWITCH".
*/
volatile uint32_t c_txcpudesc;
volatile uint32_t c_rxcpudesc;
/* Last TX descriptor saved for error handling */
uint32_t c_lastdescstart;
uint32_t c_lastdescend;
/* Shadowed registers */
uint32_t c_eimstatus;
#ifdef CONFIG_C5471_NET_STATS
/* TX statistics */
uint32_t c_txpackets; /* Number of packets sent */
uint32_t c_txmiss; /* Miss */
uint32_t c_txvlan; /* VLAN */
uint32_t c_txlframe; /* Long frame errors */
uint32_t c_txsframe; /* Short frame errors */
uint32_t c_txcrc; /* CRC errors */
uint32_t c_txoverrun; /* Overrun errors */
uint32_t c_txalign; /* Non-octect align errors */
uint32_t c_txtimeouts; /* TX timeouts */
uint32_t c_rxpackets; /* Number of packets received */
uint32_t c_rxretries; /* Exceed retry errors */
uint32_t c_rxheartbeat; /* Heartbeat (SQE) */
uint32_t c_rxlcollision; /* Late collision errors */
uint32_t c_rxcollision; /* Collision */
uint32_t c_rxcrc; /* CRC errors */
uint32_t c_rxunderrun; /* Underrun errors */
uint32_t c_rxloc; /* Loss of carrier */
uint32_t c_rxdropped; /* Packets dropped because of size */
#endif
/* This holds the information visible to the NuttX network */
struct net_driver_s c_dev; /* Interface understood by the network */
};
/****************************************************************************
* Private Data
****************************************************************************/
static struct c5471_driver_s g_c5471[CONFIG_C5471_NET_NINTERFACES];
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Transceiver interface */
static void c5471_mdtxbit (int bit_state);
static int c5471_mdrxbit (void);
static void c5471_mdwrite (int adr, int reg, int data);
static int c5471_mdread (int adr, int reg);
static int c5471_phyinit (void);
/* Support logic */
static inline void c5471_inctxcpu(struct c5471_driver_s *priv);
static inline void c5471_incrxcpu(struct c5471_driver_s *priv);
/* Common TX logic */
static int c5471_transmit(struct c5471_driver_s *priv);
static int c5471_txpoll(struct net_driver_s *dev);
/* Interrupt handling */
#ifdef CONFIG_C5471_NET_STATS
static void c5471_rxstatus(struct c5471_driver_s *priv);
#endif
static void c5471_receive(struct c5471_driver_s *priv);
#ifdef CONFIG_C5471_NET_STATS
static void c5471_txstatus(struct c5471_driver_s *priv);
#endif
static void c5471_txdone(struct c5471_driver_s *priv);
static void c5471_interrupt_work(void *arg);
static int c5471_interrupt(int irq, void *context, void *arg);
/* Watchdog timer expirations */
static void c5471_txtimeout_work(void *arg);
static void c5471_txtimeout_expiry(wdparm_t arg);
/* NuttX callback functions */
static int c5471_ifup(struct net_driver_s *dev);
static int c5471_ifdown(struct net_driver_s *dev);
static void c5471_txavail_work(void *arg);
static int c5471_txavail(struct net_driver_s *dev);
#ifdef CONFIG_NET_MCASTGROUP
static int c5471_addmac(struct net_driver_s *dev, const uint8_t *mac);
static int c5471_rmmac(struct net_driver_s *dev, const uint8_t *mac);
#endif
/* Initialization functions */
static void c5471_eimreset (struct c5471_driver_s *priv);
static void c5471_eimconfig(struct c5471_driver_s *priv);
static void c5471_reset(struct c5471_driver_s *priv);
static void c5471_macassign(struct c5471_driver_s *priv);
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: c5471_dumpbuffer
*
* Description:
* Debug only
*
****************************************************************************/
#ifdef CONFIG_C5471_NET_DUMPBUFFER
static inline void c5471_dumpbuffer(const char *msg, const uint8_t *buffer,
unsigned int nbytes)
{
/* CONFIG_DEBUG_FEATURES, CONFIG_DEBUG_INFO, and CONFIG_DEBUG_NET have to
* be defined or the following does nothing.
*/
ninfodumpbuffer(msg, buffer, nbytes);
}
#else
# define c5471_dumpbuffer(msg, buffer,nbytes)
#endif
/****************************************************************************
* Name: c5471_mdtxbit
*
* Description:
* A helper routine used when serially communicating with the c547X's
* external ethernet transeiver device. GPIO pins are connected to the
* transeiver's MDCLK and MDIO pins and are used to accomplish the serial
* comm.
*
* protocol:
* ___________
* MDCLK ________/ \_
* ________:____
* MDIO <________:____>--------
* :
* ^
* Pin state internalized
*
****************************************************************************/
static void c5471_mdtxbit (int bit_state)
{
/* Note: any non-zero "bit_state" supplied by the caller means we should
* clk a "1" out the MDIO pin.
*/
/* Config MDIO as output pin. */
putreg32((getreg32(GPIO_CIO) & ~GPIO_CIO_MDIO), GPIO_CIO);
/* Select the bit output state */
if (bit_state)
{
/* set MDIO state high. */
putreg32((getreg32(GPIO_IO) | GPIO_CIO_MDIO), GPIO_IO);
}
else
{
/* set MDIO state low. */
putreg32((getreg32(GPIO_IO) & ~GPIO_CIO_MDIO), GPIO_IO);
}
nop();
nop();
nop();
nop();
/* MDCLK rising edge */
putreg32((getreg32(GPIO_IO) | GPIO_IO_MDCLK), GPIO_IO);
nop();
nop();
/* release MDIO */
putreg32((getreg32(GPIO_CIO) | GPIO_CIO_MDIO), GPIO_CIO);
nop();
nop();
/* MDCLK falling edge. */
putreg32((getreg32(GPIO_IO) & ~GPIO_IO_MDCLK), GPIO_IO);
}
/****************************************************************************
* Name: c5471_mdrxbit
*
* Description:
* A helper routine used when serially communicating with the c547X's
* external ethernet transeiver device. GPIO pins are connected to the
* transeiver's MDCLK and MDIO pins and are used to accomplish the serial
* comm.
*
* protocol:
* ___________
* MDCLK ________/ \_
* _______:_____
* MDIO _______:_____>--------
* :
* ^
* pin state sample point
*
****************************************************************************/
static int c5471_mdrxbit (void)
{
register volatile uint32_t bit_state;
/* config MDIO as input pin. */
putreg32((getreg32(GPIO_CIO) | GPIO_CIO_MDIO), GPIO_CIO);
/* Make sure the MDCLK is low */
putreg32((getreg32(GPIO_IO) & ~GPIO_IO_MDCLK), GPIO_IO);
nop();
nop();
nop();
nop();
/* Sample MDIO */
bit_state = getreg32(GPIO_IO) & GPIO_CIO_MDIO;
/* MDCLK rising edge */
putreg32((getreg32(GPIO_IO) | GPIO_IO_MDCLK), GPIO_IO);
nop();
nop();
nop();
nop();
/* MDCLK falling edge. */
putreg32((getreg32(GPIO_IO) & ~GPIO_IO_MDCLK), GPIO_IO); /* MDCLK falling edge */
if (bit_state)
{
return 1;
}
else
{
return OK;
}
}
/****************************************************************************
* Name: c5471_mdwrite
*
* Description:
* A helper routine used when serially communicating with the c547X's
* external ethernet transeiver device. GPIO pins are connected to the
* transeiver's MDCLK and MDIO pins and are used to accomplish the serial
* comm.
*
****************************************************************************/
static void c5471_mdwrite (int adr, int reg, int data)
{
int i;
/* preamble: 11111111111111111111111111111111 */
for (i = 0; i < 32; i++)
{
c5471_mdtxbit(1);
}
/* start of frame: 01 */
c5471_mdtxbit(0);
c5471_mdtxbit(1);
/* operation code: 01 - write */
c5471_mdtxbit(0);
c5471_mdtxbit(1);
/* PHY device address: AAAAA, msb first */
for (i = 0; i < 5; i++)
{
c5471_mdtxbit(adr & 0x10);
adr = adr << 1;
}
/* MII register address: RRRRR, msb first */
for (i = 0; i < 5; i++)
{
c5471_mdtxbit(reg & 0x10);
reg = reg << 1;
}
/* Turnaround time: ZZ */
c5471_mdtxbit(1);
c5471_mdtxbit(0);
/* data: DDDDDDDDDDDDDDDD, msb first */
for (i = 0; i < 16; i++)
{
c5471_mdtxbit(data & 0x8000);
data = data << 1;
}
}
/****************************************************************************
* Name: c5471_mdread
*
* Description:
* A helper routine used when serially communicating with the c547X's
* external ethernet transeiver device. GPIO pins are connected to the
* transeiver's MDCLK and MDIO pins and are used to accomplish the serial
* comm.
*
****************************************************************************/
static int c5471_mdread (int adr, int reg)
{
int i;
int data = 0;
/* preamble: 11111111111111111111111111111111 */
for (i = 0; i < 32; i++)
{
c5471_mdtxbit(1);
}
/* start of frame: 01 */
c5471_mdtxbit(0);
c5471_mdtxbit(1);
/* operation code: 10 - read */
c5471_mdtxbit(1);
c5471_mdtxbit(0);
/* PHY device address: AAAAA, msb first */
for (i = 0; i < 5; i++)
{
c5471_mdtxbit(adr & 0x10);
adr = adr << 1;
}
/* MII register address: RRRRR, msb first */
for (i = 0; i < 5; i++)
{
c5471_mdtxbit(reg & 0x10);
reg = reg << 1;
}
/* turnaround time: ZZ */
c5471_mdrxbit();
c5471_mdrxbit(); /* PHY should drive a 0 */
/* data: DDDDDDDDDDDDDDDD, msb first */
for (i = 0; i < 16; i++)
{
data = data << 1;
data |= c5471_mdrxbit();
}
return data;
}
/****************************************************************************
* Name: c5471_phyinit
*
* Description:
* The c547X EVM board uses a Lucent LU3X31T-T64 transeiver device to
* handle the physical layer (PHY). It's a h/w block that on the one end
* offers a Media Independent Interface (MII) which is connected to the
* Ethernet Interface Module (EIM) internal to the C547x and on the other
* end offers either the 10baseT or 100baseT electrical interface
* connecting to an RJ45 onboard network connector. The PHY transeiver has
* several internal registers allowing host configuration and status
* access. These internal registers are accessible by clocking serial data
* in/out of the MDIO pin of the LU3X31T-T64 chip. For c547X, the MDC and
* the MDIO pins are connected to the C547x GPIO15 and GPIO14 pins
* respectively. Host software twiddles the GPIO pins appropriately to get
* data serially into and out of the chip. This is typically a one time
* operation at boot and normal operation of the transeiver involves
* EIM/Transeiver interaction at the other pins of the transeiver chip and
* doesn't require host intervention at the MDC and MDIO pins.
*
****************************************************************************/
#if defined(CONFIG_C5471_PHY_LU3X31T_T64)
static int c5471_phyinit (void)
{
int phyid;
int status;
/* Next, Setup GPIO pins to talk serially to the Lucent transeiver chip */
/* enable gpio bits 15,14 */
putreg32((getreg32(GPIO_EN) | 0x0000c000), GPIO_EN);
/* config gpio(15); out -> MDCLK */
putreg32((getreg32(GPIO_CIO) & ~0x00008000), GPIO_CIO);
/* config gpio(14); in <- MDIO */
putreg32((getreg32(GPIO_CIO) | 0x00004000), GPIO_CIO);
/* initial pin state; MDCLK = 0 */
putreg32((getreg32(GPIO_IO) & 0x000f3fff), GPIO_IO);
/* Next, request a chip reset */
c5471_mdwrite(0, MD_PHY_CONTROL_REG, 0x8000);
while (c5471_mdread(0, MD_PHY_CONTROL_REG) & 0x8000)
{
/* wait for chip reset to complete */
}
/* Next, Read out the chip ID */
phyid = (c5471_mdread(0, MD_PHY_MSB_REG) << 16) |
c5471_mdread(0, MD_PHY_LSB_REG);
if (phyid != LU3X31_T64_PHYID)
{
nerr("ERROR: Unrecognized PHY ID: %08x\n", phyid);
return -ENODEV;
}
/* Next, Set desired network rate, 10BaseT, 100BaseT, or auto. */
#ifdef CONFIG_C5471_AUTONEGOTIATION
ninfo("Setting PHY Transceiver for Autonegotiation\n");
c5471_mdwrite(0, MD_PHY_CONTROL_REG, MODE_AUTONEG);
#endif
#ifdef CONFIG_C5471_BASET100
ninfo("Setting PHY Transceiver for 100BaseT FullDuplex\n");
c5471_mdwrite(0, MD_PHY_CONTROL_REG, MODE_100MBIT_FULLDUP);
#endif
#ifdef CONFIG_C5471_BASET10
ninfo("Setting PHY Transceiver for 10BaseT FullDuplex\n");
c5471_mdwrite(0, MD_PHY_CONTROL_REG, MODE_10MBIT_FULLDUP);
#endif
status = c5471_mdread(0, MD_PHY_CTRL_STAT_REG);
return status;
}
#elif defined(CONFIG_C5471_PHY_AC101L)
static int c5471_phyinit (void)
{
int phyid;
int status;
/* Next, Setup GPIO pins to talk serially to the Lucent transeiver chip */
putreg32((getreg32(GPIO_EN) | 0x0000c000), GPIO_EN); /* enable gpio bits 15,14 */
putreg32((getreg32(GPIO_CIO) & ~0x00008000), GPIO_CIO); /* config gpio(15); out -> MDCLK */
putreg32((getreg32(GPIO_CIO) | 0x00004000), GPIO_CIO); /* config gpio(14); in <- MDIO */
putreg32((getreg32(GPIO_IO) & 0x000f3fff), GPIO_IO); /* initial pin state; MDCLK = 0 */
return 1;
}
#else
# define c5471_phyinit()
# warning "Assuming no PHY"
#endif
/****************************************************************************
* Name: c5471_inctxcpu
*
* Description:
*
****************************************************************************/
static inline void c5471_inctxcpu(struct c5471_driver_s *priv)
{
if (EIM_TXDESC_WRAP_NEXT & getreg32(priv->c_txcpudesc))
{
/* Loop back around to base of descriptor queue */
priv->c_txcpudesc = getreg32(EIM_CPU_TXBA) + EIM_RAM_START;
}
else
{
priv->c_txcpudesc += 2*sizeof(uint32_t);
}
ninfo("TX CPU desc: %08" PRIx32 "\n", priv->c_txcpudesc);
}
/****************************************************************************
* Name: c5471_incrxcpu
*
* Description:
*
****************************************************************************/
static inline void c5471_incrxcpu(struct c5471_driver_s *priv)
{
if (EIM_RXDESC_WRAP_NEXT & getreg32(priv->c_rxcpudesc))
{
/* Loop back around to base of descriptor queue */
priv->c_rxcpudesc = getreg32(EIM_CPU_RXBA) + EIM_RAM_START;
}
else
{
priv->c_rxcpudesc += 2*sizeof(uint32_t);
}
ninfo("RX CPU desc: %08" PRIx32 "\n", priv->c_rxcpudesc);
}
/****************************************************************************
* Function: c5471_transmit
*
* Description:
* Start hardware transmission. Called either from the txdone interrupt
* handling or from watchdog based polling.
*
* Input Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* OK on success; a negated errno on failure
*
* Assumptions:
*
****************************************************************************/
static int c5471_transmit(struct c5471_driver_s *priv)
{
struct net_driver_s *dev = &priv->c_dev;
volatile uint16_t *packetmem;
uint16_t framelen;
bool bfirstframe;
int nbytes;
int nshorts;
unsigned int i;
unsigned int j;
nbytes = (dev->d_len + 1) & ~1;
j = 0;
bfirstframe = true;
priv->c_lastdescstart = priv->c_rxcpudesc;
ninfo("Packet size: %d RX CPU desc: %08" PRIx32 "\n",
nbytes, priv->c_rxcpudesc);
c5471_dumpbuffer("Transmit packet", dev->d_buf, dev->d_len);
while (nbytes)
{
/* Verify that the hardware is ready to send another packet
* Words #0 and #1 of descriptor
*/
while (EIM_TXDESC_OWN_HOST & getreg32(priv->c_rxcpudesc))
{
/* Loop until the SWITCH lets go of the descriptor giving us access
* rights to submit our new ether frame to it.
*/
}
if (bfirstframe)
{
putreg32((getreg32(priv->c_rxcpudesc) | EIM_RXDESC_FIF),
priv->c_rxcpudesc);
}
else
{
putreg32((getreg32(priv->c_rxcpudesc) & ~EIM_RXDESC_FIF),
priv->c_rxcpudesc);
}
putreg32((getreg32(priv->c_rxcpudesc) & ~EIM_RXDESC_PADCRC),
priv->c_rxcpudesc);
if (bfirstframe)
{
putreg32((getreg32(priv->c_rxcpudesc) | EIM_RXDESC_PADCRC),
priv->c_rxcpudesc);
}
if (nbytes >= EIM_PACKET_BYTES)
{
framelen = EIM_PACKET_BYTES;
}
else
{
framelen = nbytes;
}
/* Submit ether frame bytes to the C5472 Ether Module packet memory
* space. Get the number of 16-bit values to transfer by dividing by
* 2 with round up.
*/
nshorts = (framelen + 1) >> 1;
/* Words #2 and #3 of descriptor */
packetmem = (uint16_t *)getreg32(priv->c_rxcpudesc + sizeof(uint32_t));
for (i = 0; i < nshorts; i++, j++)
{
/* 16-bits at a time. */
packetmem[i] = HTONS(((uint16_t *)dev->d_buf)[j]);
}
putreg32(((getreg32(priv->c_rxcpudesc) & ~EIM_RXDESC_BYTEMASK) |
framelen), priv->c_rxcpudesc);
nbytes -= framelen;
ninfo("Wrote framelen: %d nbytes: %d nshorts: %d\n",
framelen, nbytes, nshorts);
if (0 == nbytes)
{
putreg32((getreg32(priv->c_rxcpudesc) | EIM_RXDESC_LIF),
priv->c_rxcpudesc);
}
else
{
putreg32((getreg32(priv->c_rxcpudesc) & ~EIM_RXDESC_LIF),
priv->c_rxcpudesc);
}
/* We're done with that descriptor; give access rights back to h/w */
putreg32((getreg32(priv->c_rxcpudesc) | EIM_RXDESC_OWN_HOST),
priv->c_rxcpudesc);
/* Next, tell Ether Module that
* those submitted bytes are ready for the wire
*/
putreg32(0x00000001, EIM_CPU_RXREADY);
priv->c_lastdescend = priv->c_rxcpudesc;
/* Advance to the next free descriptor */
c5471_incrxcpu(priv);
bfirstframe = false;
}
/* Packet transferred .. Update statistics */
#ifdef CONFIG_C5471_NET_STATS
priv->c_txpackets++;
#endif
/* Setup the TX timeout watchdog (perhaps restarting the timer) */
wd_start(&priv->c_txtimeout, C5471_TXTIMEOUT,
c5471_txtimeout_expiry, (wdparm_t)priv);
return OK;
}
/****************************************************************************
* Function: c5471_txpoll
*
* Description:
* The transmitter is available, check if the network has any outgoing
* packets ready to send. This is a callback from devif_poll().
* devif_poll() may be called:
*
* 1. When the preceding TX packet send is complete,
* 2. When the preceding TX packet send timesout and the interface is reset
* 3. During normal TX polling
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* OK on success; a negated errno on failure
*
* Assumptions:
*
****************************************************************************/
static int c5471_txpoll(struct net_driver_s *dev)
{
struct c5471_driver_s *priv = (struct c5471_driver_s *)dev->d_private;
/* Send the packet */
c5471_transmit(priv);
/* Check if the ESM has let go of the RX descriptor giving us
* access rights to submit another Ethernet frame.
*/
if ((EIM_TXDESC_OWN_HOST & getreg32(priv->c_rxcpudesc)) != 0)
{
/* No, then return non-zero to terminate the poll */
return 1;
}
/* If zero is returned, the polling will continue until all connections
* have been examined.
*/
return 0;
}
/****************************************************************************
* Function: c5471_rxstatus
*
* Description:
* An interrupt was received indicating that the last RX packet(s) is done
*
* Input Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* None
*
* Assumptions:
*
****************************************************************************/
#ifdef CONFIG_C5471_NET_STATS
static void c5471_rxstatus(struct c5471_driver_s *priv)
{
uint32_t desc = priv->c_txcpudesc;
uint32_t rxstatus;
/* Walk that last packet we just received to collect xmit status bits. */
rxstatus = 0;
for (; ; )
{
if (EIM_TXDESC_OWN_HOST & getreg32(desc))
{
/* The incoming packet queue is empty. */
break;
}
rxstatus |= (getreg32(desc) & EIM_TXDESC_STATUSMASK);
if ((getreg32(desc) & EIM_TXDESC_LIF) != 0)
{
break;
}
/* This packet is made up of several descriptors,
* find next one in chain.
*/
if (EIM_TXDESC_WRAP_NEXT & getreg32(desc))
{
/* Loop back around to base of descriptor queue. */
desc = getreg32(EIM_CPU_TXBA) + EIM_RAM_START;
}
else
{
desc += 2 * sizeof(uint32_t);
}
}
if (rxstatus != 0)
{
if ((rxstatus & EIM_TXDESC_RETRYERROR) != 0)
{
priv->c_rxretries++;
ninfo("c_rxretries: %d\n", priv->c_rxretries);
}
if ((rxstatus & EIM_TXDESC_HEARTBEAT) != 0)
{
priv->c_rxheartbeat++;
ninfo("c_rxheartbeat: %d\n", priv->c_rxheartbeat);
}
if ((rxstatus & EIM_TXDESC_LCOLLISON) != 0)
{
priv->c_rxlcollision++;
ninfo("c_rxlcollision: %d\n", priv->c_rxlcollision);
}
if ((rxstatus & EIM_TXDESC_COLLISION) != 0)
{
priv->c_rxcollision++;
ninfo("c_rxcollision: %d\n", priv->c_rxcollision);
}
if ((rxstatus & EIM_TXDESC_CRCERROR) != 0)
{
priv->c_rxcrc++;
ninfo("c_rxcrc: %d\n", priv->c_rxcrc);
}
if ((rxstatus & EIM_TXDESC_UNDERRUN) != 0)
{
priv->c_rxunderrun++;
ninfo("c_rxunderrun: %d\n", priv->c_rxunderrun);
}
if ((rxstatus & EIM_TXDESC_LOC) != 0)
{
priv->c_rxloc++;
ninfo("c_rxloc: %d\n", priv->c_rxloc);
}
}
}
#endif
/****************************************************************************
* Function: c5471_receive
*
* Description:
* An interrupt was received indicating the availability of a new RX packet
*
* Input Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* None
*
* Assumptions:
*
****************************************************************************/
static void c5471_receive(struct c5471_driver_s *priv)
{
struct net_driver_s *dev = &priv->c_dev;
uint16_t *packetmem;
bool bmore = true;
int packetlen = 0;
int framelen;
int nshorts;
int i;
int j = 0;
/* Walk the newly received packet contained within the EIM and transfer
* its contents to the network buffer. This frees up the memory contained
* within the EIM for additional packets that might be received later from
* the network.
*/
ninfo("Reading TX CPU desc: %08" PRIx32 "\n", priv->c_txcpudesc);
while (bmore)
{
/* Words #0 and #1 of descriptor */
if (EIM_TXDESC_OWN_HOST & getreg32(priv->c_txcpudesc))
{
/* No further packets to receive. */
break;
}
/* Get the size of the frame from words #0 and #1 of the descriptor
* and update the accumulated packet size
*/
framelen = (getreg32(priv->c_txcpudesc) & EIM_TXDESC_BYTEMASK);
packetlen += framelen;
/* Check if the received packet will fit
* within the network packet buffer
*/
if (packetlen < (CONFIG_NET_ETH_PKTSIZE + 4))
{
/* Get the packet memory from words #2 and #3 of descriptor */
packetmem = (uint16_t *)
getreg32(priv->c_txcpudesc + sizeof(uint32_t));
/* Divide by 2 with round up to get the number of 16-bit words. */
nshorts = (framelen + 1) >> 1;
ninfo("Reading framelen: "
"%d packetlen: %d nshorts: %d packetmen: %p\n",
framelen, packetlen, nshorts, packetmem);
for (i = 0 ; i < nshorts; i++, j++)
{
/* Copy the data data from the hardware to priv->c_dev.d_buf
* 16-bits at a time.
*/
((uint16_t *)dev->d_buf)[j] = HTONS(packetmem[i]);
}
}
else
{
ninfo("Discarding framelen: %d packetlen %d\n",
framelen, packetlen);
}
if (getreg32(priv->c_txcpudesc) & EIM_TXDESC_LIF)
{
bmore = false;
}
/* Next, Clear all bits of words0/1 of the emptied descriptor
* except preserve the settings of a select few. Can leave
* descriptor words 2/3 alone.
*/
putreg32((getreg32(priv->c_txcpudesc) &
(EIM_TXDESC_WRAP_NEXT | EIM_TXDESC_INTRE)),
priv->c_txcpudesc);
/* Next, Give ownership of now emptied descriptor back to
* the Ether Module's SWITCH
*/
putreg32((getreg32(priv->c_txcpudesc) | EIM_TXDESC_OWN_HOST),
priv->c_txcpudesc);
/* Advance to the next data buffer */
c5471_inctxcpu(priv);
}
/* Adjust the packet length to remove the CRC bytes
* that the network doesn't care about.
*/
packetlen -= 4;
#ifdef CONFIG_C5471_NET_STATS
/* Increment the count of received packets */
priv->c_rxpackets++;
#endif
/* If we successfully transferred the data into the network buffer,
* then pass it on to the network for processing.
*/
if (packetlen > 0 && packetlen < CONFIG_NET_ETH_PKTSIZE)
{
/* Set amount of data in priv->c_dev.d_len. */
dev->d_len = packetlen;
ninfo("Received packet, packetlen: %d type: %02x\n",
packetlen, NTOHS(BUF->type));
c5471_dumpbuffer("Received packet", dev->d_buf, dev->d_len);
#ifdef CONFIG_NET_PKT
/* When packet sockets are enabled, feed the frame into the tap */
pkt_input(dev);
#endif
/* We only accept IP packets of the configured type and ARP packets */
#ifdef CONFIG_NET_IPv4
if (BUF->type == HTONS(ETHTYPE_IP))
{
ninfo("IPv4 frame\n");
/* Receive an IPv4 packet from the network device */
ipv4_input(dev);
/* If the above function invocation resulted in data that should be
* sent out on the network, d_len field will set to a value > 0.
* Send that data now if ESM has let go of the RX descriptor giving
* us access rights to submit another Ethernet frame.
*/
if (dev->d_len > 0 &&
(EIM_TXDESC_OWN_HOST & getreg32(priv->c_rxcpudesc)) == 0)
{
/* And send the packet */
c5471_transmit(priv);
}
}
else
#endif
#ifdef CONFIG_NET_IPv6
if (BUF->type == HTONS(ETHTYPE_IP6))
{
ninfo("IPv6 frame\n");
/* Give the IPv6 packet to the network layer */
ipv6_input(dev);
/* If the above function invocation resulted in data that should be
* sent out on the network, d_len field will set to a value > 0.
* Send that data now if ESM has let go of the RX descriptor giving
* us access rights to submit another Ethernet frame.
*/
if (dev->d_len > 0 &&
(EIM_TXDESC_OWN_HOST & getreg32(priv->c_rxcpudesc)) == 0)
{
/* And send the packet */
c5471_transmit(priv);
}
}
else
#endif
#ifdef CONFIG_NET_ARP
if (BUF->type == HTONS(ETHTYPE_ARP))
{
arp_input(dev);
/* If the above function invocation resulted in data that should be
* sent out on the network, d_len field will set to a value > 0.
* Send that data now if ESM has let go of the RX descriptor giving
* us access rights to submit another Ethernet frame.
*/
if (dev->d_len > 0 &&
(EIM_TXDESC_OWN_HOST & getreg32(priv->c_rxcpudesc)) == 0)
{
c5471_transmit(priv);
}
}
#endif
}
#ifdef CONFIG_C5471_NET_STATS
else
{
/* Increment the count of dropped packets */
nwarn("WARNING: Too big! packetlen: %d\n", packetlen);
priv->c_rxdropped++;
}
#endif
}
/****************************************************************************
* Function: c5471_txstatus
*
* Description:
* An interrupt was received indicating that the last TX packet(s) is done
*
* Input Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* None
*
* Assumptions:
*
****************************************************************************/
#ifdef CONFIG_C5471_NET_STATS
static void c5471_txstatus(struct c5471_driver_s *priv)
{
uint32_t desc = priv->c_lastdescstart;
uint32_t txstatus;
/* Walk that last packet we just sent to collect xmit status bits. */
txstatus = 0;
if (priv->c_lastdescstart && priv->c_lastdescend)
{
for (; ; )
{
txstatus |= (getreg32(desc) & EIM_RXDESC_STATUSMASK);
if (desc == priv->c_lastdescend)
{
break;
}
/* This packet is made up of several descriptors,
* find next one in chain.
*/
if (EIM_RXDESC_WRAP_NEXT & getreg32(priv->c_rxcpudesc))
{
/* Loop back around to base of descriptor queue. */
desc = getreg32(EIM_CPU_RXBA) + EIM_RAM_START;
}
else
{
desc += 2 * sizeof(uint32_t);
}
}
}
if (txstatus)
{
if ((txstatus & EIM_RXDESC_MISS) != 0)
{
priv->c_txmiss++;
ninfo("c_txmiss: %d\n", priv->c_txmiss);
}
if ((txstatus & EIM_RXDESC_VLAN) != 0)
{
priv->c_txvlan++;
ninfo("c_txvlan: %d\n", priv->c_txvlan);
}
if ((txstatus & EIM_RXDESC_LFRAME) != 0)
{
priv->c_txlframe++;
ninfo("c_txlframe: %d\n", priv->c_txlframe);
}
if ((txstatus & EIM_RXDESC_SFRAME) != 0)
{
priv->c_txsframe++;
ninfo("c_txsframe: %d\n", priv->c_txsframe);
}
if ((txstatus & EIM_RXDESC_CRCERROR) != 0)
{
priv->c_txcrc++;
ninfo("c_txcrc: %d\n", priv->c_txcrc);
}
if ((txstatus & EIM_RXDESC_OVERRUN) != 0)
{
priv->c_txoverrun++;
ninfo("c_txoverrun: %d\n", priv->c_txoverrun);
}
if ((txstatus & EIM_RXDESC_OVERRUN) != 0)
{
priv->c_txalign++;
ninfo("c_txalign: %d\n", priv->c_txalign);
}
}
}
#endif
/****************************************************************************
* Function: c5471_txdone
*
* Description:
* An interrupt was received indicating that the last TX packet(s) is done
*
* Input Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* None
*
* Assumptions:
*
****************************************************************************/
static void c5471_txdone(struct c5471_driver_s *priv)
{
/* If no further xmits are pending, then cancel the TX timeout */
wd_cancel(&priv->c_txtimeout);
/* Then poll the network for new XMIT data */
devif_poll(&priv->c_dev, c5471_txpoll);
}
/****************************************************************************
* Function: c5471_interrupt_work
*
* Description:
* Perform interrupt related work from the worker thread
*
* Input Parameters:
* arg - The argument passed when work_queue() was called.
*
* Returned Value:
* OK on success
*
* Assumptions:
* The network is locked.
*
****************************************************************************/
static void c5471_interrupt_work(void *arg)
{
struct c5471_driver_s *priv = (struct c5471_driver_s *)arg;
/* Process pending Ethernet interrupts */
net_lock();
/* Get and clear interrupt status bits */
priv->c_eimstatus = getreg32(EIM_STATUS);
/* Handle interrupts according to status bit settings */
/* Check if we received an incoming packet, if so, call c5471_receive() */
if ((EIM_STATUS_CPU_TX & priv->c_eimstatus) != 0)
{
/* An incoming packet has been received by the EIM from the network and
* the interrupt associated with EIM's CPU TX queue has been asserted.
* It is the EIM's CPU TX queue that we need to read from to get those
* packets. We use this terminology to stay consistent with the Orion
* documentation.
*/
#ifdef CONFIG_C5471_NET_STATS
/* Check for RX errors */
c5471_rxstatus(priv);
#endif
/* Process the received packet */
c5471_receive(priv);
}
/* Check is a packet transmission just completed.
* If so, call c5471_txdone
*/
if ((EIM_STATUS_CPU_RX & priv->c_eimstatus) != 0)
{
/* An outgoing packet has been processed by the EIM and the interrupt
* associated with EIM's CPU RX queue has been asserted. It is EIM's
* CPU RX queue that we put packets on to send them *out*. TWe use this
* terminology to stay consistent with the Orion documentation.
*/
#ifdef CONFIG_C5471_NET_STATS
/* Check for TX errors */
c5471_txstatus(priv);
#endif
/* Handle the transmission done event */
c5471_txdone(priv);
}
net_unlock();
/* Re-enable Ethernet interrupts */
up_enable_irq(C5471_IRQ_ETHER);
}
/****************************************************************************
* Function: c5471_interrupt
*
* Description:
* Hardware interrupt handler
*
* Input Parameters:
* irq - Number of the IRQ that generated the interrupt
* context - Interrupt register state save info (architecture-specific)
*
* Returned Value:
* OK on success
*
* Assumptions:
*
****************************************************************************/
static int c5471_interrupt(int irq, void *context, void *arg)
{
#if CONFIG_C5471_NET_NINTERFACES == 1
register struct c5471_driver_s *priv = &g_c5471[0];
#else
# error "Additional logic needed to support multiple interfaces"
#endif
/* Disable further Ethernet interrupts. Because Ethernet interrupts are
* also disabled if the TX timeout event occurs, there can be no race
* condition here.
*/
up_disable_irq(C5471_IRQ_ETHER);
/* TODO: Determine if a TX transfer just completed */
{
/* If a TX transfer just completed, then cancel the TX timeout so
* there will be no race condition between any subsequent timeout
* expiration and the deferred interrupt processing.
*/
wd_cancel(&priv->c_txtimeout);
}
/* Schedule to perform the interrupt processing on the worker thread. */
work_queue(ETHWORK, &priv->c_irqwork, c5471_interrupt_work, priv, 0);
return OK;
}
/****************************************************************************
* Function: c5471_txtimeout_work
*
* Description:
* Perform TX timeout related work from the worker thread
*
* Input Parameters:
* arg - The argument passed when work_queue() as called.
*
* Returned Value:
* OK on success
*
* Assumptions:
* The network is locked.
*
****************************************************************************/
static void c5471_txtimeout_work(void *arg)
{
struct c5471_driver_s *priv = (struct c5471_driver_s *)arg;
/* Increment statistics */
net_lock();
#ifdef CONFIG_C5471_NET_STATS
priv->c_txtimeouts++;
ninfo("c_txtimeouts: %d\n", priv->c_txtimeouts);
#endif
/* Then try to restart the hardware */
c5471_ifdown(&priv->c_dev);
c5471_ifup(&priv->c_dev);
/* Then poll the network for new XMIT data */
devif_poll(&priv->c_dev, c5471_txpoll);
net_unlock();
}
/****************************************************************************
* Function: c5471_txtimeout_expiry
*
* Description:
* Our TX watchdog timed out. Called from the timer interrupt handler.
* The last TX never completed. Reset the hardware and start again.
*
* Input Parameters:
* arg - The argument
*
* Returned Value:
* None
*
* Assumptions:
* Global interrupts are disabled by the watchdog logic.
*
****************************************************************************/
static void c5471_txtimeout_expiry(wdparm_t arg)
{
struct c5471_driver_s *priv = (struct c5471_driver_s *)arg;
/* Disable further Ethernet interrupts. This will prevent some race
* conditions with interrupt work. There is still a potential race
* condition with interrupt work that is already queued and in progress.
*/
up_disable_irq(C5471_IRQ_ETHER);
/* Schedule to perform the TX timeout processing on the worker thread,
* canceling any pending IRQ work.
*/
work_queue(ETHWORK, &priv->c_irqwork, c5471_txtimeout_work, priv, 0);
}
/****************************************************************************
* Function: c5471_ifup
*
* Description:
* NuttX Callback: Bring up the Ethernet interface when an IP address is
* provided
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* The user has assigned a MAC to the driver
*
****************************************************************************/
static int c5471_ifup(struct net_driver_s *dev)
{
struct c5471_driver_s *priv = (struct c5471_driver_s *)dev->d_private;
volatile uint32_t clearbits;
ninfo("Bringing up: %d.%d.%d.%d\n",
(int)(dev->d_ipaddr & 0xff),
(int)((dev->d_ipaddr >> 8) & 0xff),
(int)((dev->d_ipaddr >> 16) & 0xff),
(int)(dev->d_ipaddr >> 24));
/* Initialize Ethernet interface */
c5471_reset(priv);
/* Assign the MAC to the device */
c5471_macassign(priv);
/* Clear pending interrupts by reading the EIM status register */
clearbits = getreg32(EIM_STATUS);
UNUSED(clearbits);
/* Enable interrupts going from EIM Module to Interrupt Module. */
putreg32(((getreg32(EIM_INTEN) | EIM_INTEN_CPU_TX | EIM_INTEN_CPU_RX)),
EIM_INTEN);
/* Next, go on-line. According to the C547X documentation the enables have
* to occur in this order to insure proper operation; ESM first then ENET.
*/
putreg32((getreg32(EIM_CTRL) | EIM_CTRL_ESM_EN), EIM_CTRL); /* enable ESM */
putreg32((getreg32(ENET0_MODE) | ENET_MODE_ENABLE), ENET0_MODE); /* enable ENET */
up_mdelay(100);
/* Enable the Ethernet interrupt */
priv->c_bifup = true;
up_enable_irq(C5471_IRQ_ETHER);
return OK;
}
/****************************************************************************
* Function: c5471_ifdown
*
* Description:
* NuttX Callback: Stop the interface.
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* None
*
* Assumptions:
*
****************************************************************************/
static int c5471_ifdown(struct net_driver_s *dev)
{
struct c5471_driver_s *priv = (struct c5471_driver_s *)dev->d_private;
irqstate_t flags;
ninfo("Stopping\n");
/* Disable the Ethernet interrupt */
flags = enter_critical_section();
up_disable_irq(C5471_IRQ_ETHER);
/* Disable interrupts going from EIM Module to Interrupt Module. */
putreg32((getreg32(EIM_INTEN) & ~(EIM_INTEN_CPU_TX | EIM_INTEN_CPU_RX)),
EIM_INTEN);
/* Disable ENET */
putreg32((getreg32(ENET0_MODE) & ~ENET_MODE_ENABLE), ENET0_MODE); /* disable ENET */
/* Disable ESM */
putreg32((getreg32(EIM_CTRL) & ~EIM_CTRL_ESM_EN), EIM_CTRL); /* disable ESM */
/* Cancel the TX timeout timers */
wd_cancel(&priv->c_txtimeout);
/* Reset the device */
priv->c_bifup = false;
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Function: c5471_txavail_work
*
* Description:
* Perform an out-of-cycle poll on the worker thread.
*
* Input Parameters:
* arg - Reference to the NuttX driver state structure (cast to void*)
*
* Returned Value:
* None
*
* Assumptions:
* Called on the higher priority worker thread.
*
****************************************************************************/
static void c5471_txavail_work(void *arg)
{
struct c5471_driver_s *priv = (struct c5471_driver_s *)arg;
ninfo("Polling\n");
/* Ignore the notification if the interface is not yet up */
net_lock();
if (priv->c_bifup)
{
/* Check if the ESM has let go of the RX descriptor giving us access
* rights to submit another Ethernet frame.
*/
if ((EIM_TXDESC_OWN_HOST & getreg32(priv->c_rxcpudesc)) == 0)
{
/* If so, then poll the network for new XMIT data */
devif_poll(&priv->c_dev, c5471_txpoll);
}
}
net_unlock();
}
/****************************************************************************
* Function: c5471_txavail
*
* Description:
* Driver callback invoked when new TX data is available. This is a
* stimulus perform an out-of-cycle poll and, thereby, reduce the TX
* latency.
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* Called in normal user mode
*
****************************************************************************/
static int c5471_txavail(struct net_driver_s *dev)
{
struct c5471_driver_s *priv = (struct c5471_driver_s *)dev->d_private;
/* Is our single work structure available? It may not be if there are
* pending interrupt actions and we will have to ignore the Tx
* availability action.
*/
if (work_available(&priv->c_pollwork))
{
/* Schedule to serialize the poll on the worker thread. */
work_queue(ETHWORK, &priv->c_pollwork, c5471_txavail_work, priv, 0);
}
return OK;
}
/****************************************************************************
* Function: c5471_addmac
*
* Description:
* NuttX Callback: Add the specified MAC address to the hardware multicast
* address filtering
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
* mac - The MAC address to be added
*
* Returned Value:
* None
*
* Assumptions:
*
****************************************************************************/
#ifdef CONFIG_NET_MCASTGROUP
static int c5471_addmac(struct net_driver_s *dev, const uint8_t *mac)
{
struct c5471_driver_s *priv =
(struct c5471_driver_s *)dev->d_private;
/* Add the MAC address to the hardware multicast routing table */
#warning "Multicast MAC support not implemented"
return OK;
}
#endif
/****************************************************************************
* Function: c5471_rmmac
*
* Description:
* NuttX Callback: Remove the specified MAC address from the hardware
* multicast address filtering
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
* mac - The MAC address to be removed
*
* Returned Value:
* None
*
* Assumptions:
*
****************************************************************************/
#ifdef CONFIG_NET_MCASTGROUP
static int c5471_rmmac(struct net_driver_s *dev, const uint8_t *mac)
{
struct c5471_driver_s *priv =
(struct c5471_driver_s *)dev->d_private;
/* Add the MAC address to the hardware multicast routing table */
#warning "Multicast MAC support not implemented"
return OK;
}
#endif
/****************************************************************************
* Name: c5471_eimreset
*
* Description:
* The C547x docs states that a module should generally be reset according
* to the following algorithm:
*
* 1. Put the module in reset.
* 2. Switch on the module clock.
* 3. Wait for eight clock cycles.
* 4. Release the reset.
*
****************************************************************************/
static void c5471_eimreset (struct c5471_driver_s *priv)
{
/* Stop the EIM module clock */
putreg32((getreg32(CLKM) | CLKM_EIM_CLK_STOP), CLKM);
/* Put EIM module in reset */
putreg32((getreg32(CLKM_RESET) & ~CLKM_RESET_EIM), CLKM_RESET);
/* Start the EIM module clock */
putreg32((getreg32(CLKM) & ~CLKM_EIM_CLK_STOP), CLKM);
/* Assert nRESET to reset the board's PHY0/1 chips */
putreg32((CLKM_CTL_RST_EXT_RESET | CLKM_CTL_RST_LEAD_RESET),
CLKM_CTL_RST);
up_mdelay(2);
/* Release the peripheral nRESET signal */
putreg32(CLKM_CTL_RST_LEAD_RESET, CLKM_CTL_RST);
/* Release EIM module reset */
putreg32((getreg32(CLKM_RESET) | CLKM_RESET_EIM), CLKM_RESET);
/* All EIM register should now be in there power-up default states */
priv->c_lastdescstart = 0;
priv->c_lastdescend = 0;
}
/****************************************************************************
* Name: c5471_eimconfig
*
* Description:
* Assumes that all registers are currently in the power-up reset state.
* This routine then modifies that state to provide our specific ethernet
* configuration.
*
****************************************************************************/
static void c5471_eimconfig(struct c5471_driver_s *priv)
{
volatile uint32_t pbuf;
volatile uint32_t desc;
volatile uint32_t val;
int i;
desc = EIM_RAM_START;
pbuf = EIM_RAM_START + 0x6c0;
/* TX ENET 0 */
ninfo("TX ENET0 desc: %08" PRIx32 " pbuf: %08" PRIx32 "\n", desc, pbuf);
putreg32((desc & 0x0000ffff), ENET0_TDBA); /* 16-bit offset address */
for (i = NUM_DESC_TX - 1; i >= 0; i--)
{
if (i == 0)
val = EIM_TXDESC_WRAP_NEXT;
else
val = EIM_TXDESC_WRAP_FIRST;
val |= EIM_TXDESC_OWN_HOST | EIM_TXDESC_INTRE | EIM_TXDESC_PADCRC |
EIM_PACKET_BYTES;
putreg32(val, desc);
desc += sizeof(uint32_t);
putreg32(pbuf, desc);
desc += sizeof(uint32_t);
putreg32(0, pbuf);
pbuf += EIM_PACKET_BYTES;
putreg32(0, pbuf);
pbuf += sizeof(uint32_t); /* Ether Module's "Buffer Usage Word" */
}
/* RX ENET 0 */
ninfo("RX ENET0 desc: %08" PRIx32 " pbuf: %08" PRIx32 "\n", desc, pbuf);
putreg32((desc & 0x0000ffff), ENET0_RDBA); /* 16-bit offset address */
for (i = NUM_DESC_RX - 1; i >= 0; i--)
{
if (i == 0)
val = EIM_RXDESC_WRAP_NEXT;
else
val = EIM_RXDESC_WRAP_FIRST;
val |= EIM_RXDESC_OWN_ENET | EIM_RXDESC_INTRE | EIM_RXDESC_PADCRC |
EIM_PACKET_BYTES;
putreg32(val, desc);
desc += sizeof(uint32_t);
putreg32(pbuf, desc);
desc += sizeof(uint32_t);
putreg32(0, pbuf);
pbuf += EIM_PACKET_BYTES;
putreg32(0, pbuf);
pbuf += sizeof(uint32_t); /* Ether Module's "Buffer Usage Word" */
}
/* TX CPU */
ninfo("TX CPU desc: %08" PRIx32 " pbuf: %08" PRIx32 "\n", desc, pbuf);
priv->c_txcpudesc = desc;
putreg32((desc & 0x0000ffff), EIM_CPU_TXBA); /* 16-bit offset address */
for (i = NUM_DESC_TX - 1; i >= 0; i--)
{
/* Set words 1+2 of the TXDESC */
if (i == 0)
val = EIM_TXDESC_WRAP_NEXT;
else
val = EIM_TXDESC_WRAP_FIRST;
val |= EIM_TXDESC_OWN_HOST | EIM_TXDESC_INTRE | EIM_TXDESC_PADCRC |
EIM_PACKET_BYTES;
putreg32(val, desc);
desc += sizeof(uint32_t);
putreg32(pbuf, desc);
desc += sizeof(uint32_t);
putreg32(0, pbuf);
pbuf += EIM_PACKET_BYTES;
putreg32(0, pbuf);
pbuf += sizeof(uint32_t); /* Ether Module's "Buffer Usage Word" */
}
/* RX CPU */
ninfo("RX CPU desc: %08" PRIx32 " pbuf: %08" PRIx32 "\n", desc, pbuf);
priv->c_rxcpudesc = desc;
putreg32((desc & 0x0000ffff), EIM_CPU_RXBA); /* 16-bit offset address */
for (i = NUM_DESC_RX - 1; i >= 0; i--)
{
/* Set words 1+2 of the RXDESC */
if (i == 0)
val = EIM_RXDESC_WRAP_NEXT;
else
val = EIM_RXDESC_WRAP_FIRST;
val |= EIM_RXDESC_OWN_ENET | EIM_RXDESC_INTRE | EIM_RXDESC_PADCRC |
EIM_PACKET_BYTES;
putreg32(val, desc);
desc += sizeof(uint32_t);
putreg32(pbuf, desc);
desc += sizeof(uint32_t);
putreg32(0, pbuf);
pbuf += EIM_PACKET_BYTES;
putreg32(0, pbuf);
pbuf += sizeof(uint32_t); /* Ether Module's "Buffer Usage Word" */
}
ninfo("END desc: %08" PRIx32 " pbuf: %08" PRIx32 "\n", desc, pbuf);
/* Save the descriptor packet size */
putreg32(EIM_PACKET_BYTES, EIM_BUFSIZE);
/* Set the filter mode */
#if 0
putreg32(EIM_FILTER_UNICAST, EIM_CPU_FILTER);
#elif 0
putreg32(EIM_FILTER_LOGICAL | EIM_FILTER_UNICAST | EIM_FILTER_MULTICAST |
EIM_FILTER_BROADCAST, EIM_CPU_FILTER);
#else
putreg32(EIM_FILTER_UNICAST | EIM_FILTER_MULTICAST | EIM_FILTER_BROADCAST,
EIM_CPU_FILTER);
#endif
/* Disable all Ethernet interrupts */
putreg32(0x00000000, EIM_INTEN);
/* Setup the EIM control register */
#if 1
putreg32(EIM_CTRL_ENET0_EN | EIM_CTRL_RXENET0_EN | EIM_CTRL_TXENET0_EN |
EIM_CTRL_RXCPU_EN | EIM_CTRL_TXCPU_EN, EIM_CTRL);
#else
putreg32(EIM_CTRL_ENET0_EN | EIM_CTRL_ENET0_FLW | EIM_CTRL_RXENET0_EN |
EIM_CTRL_TXENET0_EN | EIM_CTRL_RXCPU_EN | EIM_CTRL_TXCPU_EN,
EIM_CTRL);
#endif
#if 1
putreg32(0x00000000, EIM_MFVHI);
#else
putreg32(0x0000ff00, EIM_MFVHI);
#endif
putreg32(0x00000000, EIM_MFVLO);
putreg32(0x00000000, EIM_MFMHI);
putreg32(0x00000000, EIM_MFMLO);
putreg32(0x00000018, EIM_RXTH);
putreg32(0x00000000, EIM_CPU_RXREADY);
/* Setup the ENET0 mode register */
#if 1
putreg32(ENET_MODE_RJCT_SFE | ENET_MODE_MWIDTH | ENET_MODE_FULLDUPLEX,
ENET0_MODE);
#else
putreg32(ENET_MODE_RJCT_SFE | ENET_MODE_MWIDTH | ENET_MODE_HALFDUPLEX,
ENET0_MODE);
#endif
putreg32(0x00000000, ENET0_BOFFSEED);
putreg32(0x00000000, ENET0_FLWPAUSE);
putreg32(0x00000000, ENET0_FLWCONTROL);
putreg32(0x00000000, ENET0_VTYPE);
#if 0
putreg32(ENET_ADR_BROADCAST | ENET_ADR_PROMISCUOUS, ENET0_ADRMODE_EN);
#else
/* The CPU port is not PROMISCUOUS, it wants a no-promiscuous address
* match yet the SWITCH receives packets from the PROMISCUOUS ENET0
* which routes all packets for filter matching at the CPU port which
* then allows the s/w to see the new incoming packetes that passed
* the filter. Here we are setting the main SWITCH closest the ether
* wire.
*/
putreg32(ENET_ADR_PROMISCUOUS, ENET0_ADRMODE_EN);
#endif
putreg32(0x00000000, ENET0_DRP);
up_mdelay(500);
}
/****************************************************************************
* Name: c5471_reset
*
* Description:
*
****************************************************************************/
static void c5471_reset(struct c5471_driver_s *priv)
{
#if defined(CONFIG_C5471_PHY_LU3X31T_T64)
ninfo("EIM reset\n");
c5471_eimreset(priv);
#endif
ninfo("PHY init\n");
c5471_phyinit();
ninfo("EIM config\n");
c5471_eimconfig(priv);
}
/****************************************************************************
* Name: c5471_macassign
*
* Description:
* Set the mac address of our CPU ether port so that when the SWITCH
* receives packets from the PROMISCUOUS ENET0 it will switch them to the
* CPU port and cause a packet arrival event on the Switch's CPU TX queue
* when an address match occurs. The CPU port is not PROMISCUOUS and wants
* to see only packets specifically addressed to this device.
*
****************************************************************************/
static void c5471_macassign(struct c5471_driver_s *priv)
{
struct net_driver_s *dev = &priv->c_dev;
uint8_t *mptr = dev->d_mac.ether.ether_addr_octet;
register uint32_t tmp;
ninfo("MAC: %0x:%0x:%0x:%0x:%0x:%0x\n",
mptr[0], mptr[1], mptr[2], mptr[3], mptr[4], mptr[5]);
/* Set CPU port MAC address. S/W will only see incoming packets that match
* this destination address.
*/
tmp = (((uint32_t)mptr[0]) << 8) | ((uint32_t)mptr[1]);
putreg32(tmp, EIM_CPU_DAHI);
tmp = (((uint32_t)mptr[2]) << 24) | (((uint32_t)mptr[3]) << 16) |
(((uint32_t)mptr[4]) << 8) | ((uint32_t)mptr[5]);
putreg32(tmp, EIM_CPU_DALO);
#if 0
/* Set the ENET MAC address */
putreg32(getreg32(EIM_CPU_DAHI), ENET0_PARHI);
putreg32(getreg32(EIM_CPU_DALO), ENET0_PARLO);
putreg32(getreg32(EIM_CPU_DAHI), ENET0_LARHI);
putreg32(getreg32(EIM_CPU_DALO), ENET0_LARLO);
#else
/* ENET MAC assignment not needed for its PROMISCUOUS mode */
putreg32(0x00000000, ENET0_PARHI);
putreg32(0x00000000, ENET0_PARLO);
putreg32(0x00000000, ENET0_LARHI);
putreg32(0x00000000, ENET0_LARLO);
#endif
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Function: c5471_initialize
*
* Description:
* Initialize the Ethernet driver
*
* Input Parameters:
* None
*
* Returned Value:
* OK on success; Negated errno on failure.
*
* Assumptions:
*
****************************************************************************/
/* Initialize the DM90x0 chip and driver */
void arm_netinitialize(void)
{
/* Attach the IRQ to the driver */
if (irq_attach(C5471_IRQ_ETHER, c5471_interrupt, NULL))
{
/* We could not attach the ISR to the ISR */
nerr("ERROR: irq_attach() failed\n");
return;
}
/* Initialize the driver structure */
memset(g_c5471, 0,
CONFIG_C5471_NET_NINTERFACES * sizeof(struct c5471_driver_s));
g_c5471[0].c_dev.d_buf = g_pktbuf[0]; /* Single packet buffer */
g_c5471[0].c_dev.d_ifup = c5471_ifup; /* I/F down callback */
g_c5471[0].c_dev.d_ifdown = c5471_ifdown; /* I/F up (new IP address) callback */
g_c5471[0].c_dev.d_txavail = c5471_txavail; /* New TX data callback */
#ifdef CONFIG_NET_MCASTGROUP
g_c5471[0].c_dev.d_addmac = c5471_addmac; /* Add multicast MAC address */
g_c5471[0].c_dev.d_rmmac = c5471_rmmac; /* Remove multicast MAC address */
#endif
g_c5471[0].c_dev.d_private = g_c5471; /* Used to recover private state from dev */
/* Register the device with the OS so that socket IOCTLs can be performed */
netdev_register(&g_c5471[0].c_dev, NET_LL_ETHERNET);
}
#endif /* CONFIG_NET */