2453 lines
64 KiB
C
2453 lines
64 KiB
C
/****************************************************************************
|
|
* drivers/net/lan9250.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>
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <debug.h>
|
|
#include <errno.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/wdog.h>
|
|
#include <nuttx/wqueue.h>
|
|
#include <nuttx/clock.h>
|
|
#include <nuttx/net/net.h>
|
|
#include <nuttx/net/ip.h>
|
|
#include <nuttx/net/netdev.h>
|
|
#include <nuttx/net/lan9250.h>
|
|
|
|
#ifdef CONFIG_NET_PKT
|
|
# include <nuttx/net/pkt.h>
|
|
#endif
|
|
|
|
#ifdef CONFIG_LAN9250_SPI
|
|
# include <nuttx/spi/spi.h>
|
|
#else
|
|
# include <nuttx/spi/qspi.h>
|
|
#endif
|
|
|
|
#include "lan9250.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
/* Configuration ************************************************************/
|
|
|
|
/* LAN9250 SPI clock */
|
|
|
|
#define LAN9250_FREQUENCY CONFIG_LAN9250_FREQUENCY
|
|
|
|
/* LAN9250 read and write command */
|
|
|
|
#ifdef CONFIG_LAN9250_SPI
|
|
# if LAN9250_FREQUENCY > (30 * 1000 * 1000)
|
|
# define LAN9250_SPI_READ LAN9250_SPI_CMD_FREAD
|
|
# else
|
|
# define LAN9250_SPI_READ LAN9250_SPI_CMD_READ
|
|
# endif
|
|
|
|
# define LAN9250_SPI_WRITE LAN9250_SPI_CMD_WRITE
|
|
#else
|
|
# define LAN9250_SPI_READ LAN9250_QSPI_CMD_READ
|
|
# define LAN9250_SPI_WRITE LAN9250_QSPI_CMD_WRITE
|
|
|
|
# define LAN9250_SPI_ENABLE_SQI LAN9250_SPI_CMD_ENABLE_SQI
|
|
#endif
|
|
|
|
/**
|
|
* LAN9250 interrupt trigger source:
|
|
*
|
|
* - PHY for checking link up or down
|
|
* - TX data FIFO available
|
|
* - RX data FIFO reaches programmed level
|
|
*/
|
|
|
|
#define LAN9250_INT_SOURCE (IER_PHY | IER_TDFA | IER_RSFL)
|
|
|
|
/* 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 LAN9250_WORK LPWORK
|
|
|
|
/* Timing *******************************************************************/
|
|
|
|
/* Reset timeout in second */
|
|
|
|
#define LAN9250_RESET_TIMEOUT (5)
|
|
|
|
/* TX timeout = 1 minute */
|
|
|
|
#define LAN9250_TX_TIMEOUT (60 * CLK_TCK)
|
|
|
|
/* Poll timeout */
|
|
|
|
#define LAN9250_POLL_TIMEOUT MSEC2TICK(50)
|
|
|
|
/* Read/Write MAC register timeout in second */
|
|
|
|
#define LAN9250_MAC_TIMEOUT 2
|
|
|
|
/* Read/Write PHY register timeout in second */
|
|
|
|
#define LAN9250_PHY_TIMEOUT 2
|
|
|
|
/* Read/Write PHY register timeout in millisecond */
|
|
|
|
#define LAN9250_SQI_TIMEOUT 20
|
|
|
|
/* Packet Memory ************************************************************/
|
|
|
|
/* Misc. Helper Macros ******************************************************/
|
|
|
|
#define LAN9250_ALIGN(v) (((v) + 3) & (~3))
|
|
|
|
/* Packet buffer size with 4-CRC */
|
|
|
|
#define LAN9250_PKTBUF_SIZE LAN9250_ALIGN(MAX_NETDEV_PKTSIZE + \
|
|
CONFIG_NET_GUARDSIZE + \
|
|
4)
|
|
|
|
/* This is a helper pointer for accessing the contents of Ethernet header */
|
|
|
|
#define BUF ((struct eth_hdr_s *)priv->dev.d_buf)
|
|
|
|
/* Debug ********************************************************************/
|
|
|
|
#ifdef CONFIG_LAN9250_REGDEBUG
|
|
# define lan9250_setreg_dump(a, v) \
|
|
syslog(LOG_DEBUG, "LAN9250 REG: 0x%04x<-0x%08x\n", a, v)
|
|
# define lan9250_getreg_dump(a, v) \
|
|
syslog(LOG_DEBUG, "LAN9250 REG: 0x%04x->0x%08x\n", a, v)
|
|
# define lan9250_setmacreg_dump(a, v) \
|
|
syslog(LOG_DEBUG, "LAN9250 MAC: 0x%02x<-0x%08x\n", a, v)
|
|
# define lan9250_getmacreg_dump(a, v) \
|
|
syslog(LOG_DEBUG, "LAN9250 MAC: 0x%02x->0x%08x\n", a, v)
|
|
# define lan9250_setphyreg_dump(a, v) \
|
|
syslog(LOG_DEBUG, "LAN9250 PHY: 0x%02x<-0x%08x\n", a, v)
|
|
# define lan9250_getphyreg_dump(a, v) \
|
|
syslog(LOG_DEBUG, "LAN9250 PHY: 0x%02x->0x%08x\n", a, v)
|
|
# define lan9250_buffer_dump(c, b, s) \
|
|
syslog(LOG_DEBUG, "LAN9250 BUF: cmd: %04x buffer: %p length: %d\n", c, b, s)
|
|
#else
|
|
# define lan9250_setreg_dump(a, v)
|
|
# define lan9250_getreg_dump(a, v)
|
|
# define lan9250_setmacreg_dump(a, v)
|
|
# define lan9250_getmacreg_dump(a, v)
|
|
# define lan9250_setphyreg_dump(a, v)
|
|
# define lan9250_getphyreg_dump(a, v)
|
|
# define lan9250_buffer_dump(c, b, s)
|
|
#endif
|
|
|
|
/* CONFIG_LAN9250_DUMPPACKET will dump the contents of each packet. */
|
|
|
|
#ifdef CONFIG_LAN9250_DUMPPACKET
|
|
# define lan9250_dump_buf(m, a, n) lib_dumpbuffer(m, a, n)
|
|
#else
|
|
# define lan9250_dump_buf(m, a, n)
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* The lan9250_driver_s encapsulates all state information for a single
|
|
* hardware interface
|
|
*/
|
|
|
|
struct lan9250_driver_s
|
|
{
|
|
/* Low-level MCU-specific support */
|
|
|
|
const struct lan9250_lower_s *lower;
|
|
|
|
/* This is the contained (Q)SPI driver instance */
|
|
|
|
#ifdef CONFIG_LAN9250_SPI
|
|
struct spi_dev_s *spi;
|
|
#else
|
|
bool sqi_mode;
|
|
struct qspi_dev_s *qspi;
|
|
#endif
|
|
|
|
bool tx_available; /* TX is available */
|
|
|
|
/* Read/Write buffer for SPI transmission */
|
|
|
|
uint32_t pktbuf[LAN9250_PKTBUF_SIZE / 4];
|
|
|
|
struct wdog_s txtout_timer; /* TX timeout timer */
|
|
struct work_s txtout_work; /* Tx timeout work */
|
|
struct work_s txpoll_work; /* TX poll work */
|
|
struct work_s irq_work; /* Interrupt work */
|
|
|
|
/* This holds the information visible to the NuttX network */
|
|
|
|
struct net_driver_s dev; /* Interface understood by the network */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
/* Driver status structure */
|
|
|
|
static struct lan9250_driver_s g_lan9250;
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Low-level SPI helpers */
|
|
|
|
static inline void lan9250_config_spi(FAR struct lan9250_driver_s *priv);
|
|
static void lan9250_lock_spi(FAR struct lan9250_driver_s *priv);
|
|
static inline void lan9250_unlock_spi(FAR struct lan9250_driver_s *priv);
|
|
|
|
/* SPI control register access */
|
|
|
|
static uint32_t lan9250_get_reg(FAR struct lan9250_driver_s *priv,
|
|
uint16_t address);
|
|
static void lan9250_set_reg(FAR struct lan9250_driver_s *priv,
|
|
uint16_t address, uint32_t data);
|
|
static void lan9250_wait_ready(FAR struct lan9250_driver_s *priv,
|
|
uint16_t address, uint32_t mask,
|
|
uint32_t expected, uint32_t timeout);
|
|
|
|
/* MAC register access */
|
|
|
|
static uint32_t lan9250_get_macreg(FAR struct lan9250_driver_s *priv,
|
|
uint8_t address);
|
|
static void lan9250_set_macreg(FAR struct lan9250_driver_s *priv,
|
|
uint8_t address, uint32_t data);
|
|
static void lan9250_wait_mac_ready(FAR struct lan9250_driver_s *priv,
|
|
uint8_t address, uint32_t mask,
|
|
uint32_t expected, uint32_t timeout);
|
|
|
|
/* PHY register access */
|
|
|
|
static uint16_t lan9250_get_phyreg(FAR struct lan9250_driver_s *priv,
|
|
uint8_t phyaddr);
|
|
static void lan9250_set_phyreg(FAR struct lan9250_driver_s *priv,
|
|
uint8_t phyaddr, uint16_t phydata);
|
|
|
|
/* SPI buffer transfers */
|
|
|
|
static void lan9250_recv_buffer(FAR struct lan9250_driver_s *priv,
|
|
FAR uint8_t *buffer, size_t buflen);
|
|
static inline void lan9250_send_buffer(FAR struct lan9250_driver_s *priv,
|
|
FAR const uint8_t *buffer,
|
|
size_t buflen);
|
|
|
|
/* Misc handling */
|
|
|
|
#ifdef CONFIG_LAN9250_SQI
|
|
static void lan9250_enable_sqi(FAR struct lan9250_driver_s *priv);
|
|
#endif
|
|
static inline void lan9250_sw_reset(FAR struct lan9250_driver_s *priv);
|
|
static void lan9250_set_txavailabe(FAR struct lan9250_driver_s *priv,
|
|
bool enable);
|
|
static int lan9250_reset(FAR struct lan9250_driver_s *priv);
|
|
static void lan9250_set_macaddr(FAR struct lan9250_driver_s *priv);
|
|
|
|
/* Common TX logic */
|
|
|
|
static int lan9250_transmit(FAR struct lan9250_driver_s *priv);
|
|
static int lan9250_txpoll(FAR struct net_driver_s *dev);
|
|
|
|
/* Interrupt handling */
|
|
|
|
static void lan9250_netdev_rx(FAR struct lan9250_driver_s *priv);
|
|
static void lan9250_phy_isr(FAR struct lan9250_driver_s *priv);
|
|
static void lan9250_txavailable_isr(FAR struct lan9250_driver_s *priv);
|
|
static void lan9250_rxdone_isr(FAR struct lan9250_driver_s *priv);
|
|
static int lan9250_interrupt(int irq, FAR void *context, FAR void *arg);
|
|
|
|
/* Watchdog timer expirations */
|
|
|
|
static void lan9250_txtout_timercb(wdparm_t arg);
|
|
|
|
/* Driver worker */
|
|
|
|
static void lan9250_txavail_work(FAR void *arg);
|
|
static void lan9250_txtout_worker(FAR void *arg);
|
|
static void lan9250_int_worker(FAR void *arg);
|
|
|
|
/* NuttX callback functions */
|
|
|
|
static int lan9250_ifup(FAR struct net_driver_s *dev);
|
|
static int lan9250_ifdown(FAR struct net_driver_s *dev);
|
|
static int lan9250_txavail(FAR struct net_driver_s *dev);
|
|
#ifdef CONFIG_NET_MCASTGROUP
|
|
static int lan9250_addmac(FAR struct net_driver_s *dev,
|
|
FAR const uint8_t *mac);
|
|
static int lan9250_rmmac(FAR struct net_driver_s *dev,
|
|
FAR const uint8_t *mac);
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_config_spi
|
|
*
|
|
* Description:
|
|
* Configure the SPI for use with the LAN9250
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void lan9250_config_spi(FAR struct lan9250_driver_s *priv)
|
|
{
|
|
#ifdef CONFIG_LAN9250_SPI
|
|
FAR struct spi_dev_s *spi = priv->spi;
|
|
|
|
/* Configure SPI for the LAN9250. */
|
|
|
|
SPI_SETMODE(spi, CONFIG_LAN9250_SPIMODE);
|
|
SPI_SETBITS(spi, 8);
|
|
SPI_HWFEATURES(spi, 0);
|
|
SPI_SETFREQUENCY(spi, LAN9250_FREQUENCY);
|
|
#else
|
|
FAR struct qspi_dev_s *qspi = priv->qspi;
|
|
|
|
/* Configure QSPI for the LAN9250. */
|
|
|
|
QSPI_SETMODE(qspi, CONFIG_LAN9250_SPIMODE);
|
|
QSPI_SETBITS(qspi, 8);
|
|
QSPI_HWFEATURES(qspi, 0);
|
|
QSPI_SETFREQUENCY(qspi, LAN9250_FREQUENCY);
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_lock_spi
|
|
*
|
|
* Description:
|
|
* Select the SPI, locking it and re-configuring it if necessary.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_lock_spi(FAR struct lan9250_driver_s *priv)
|
|
{
|
|
#ifdef CONFIG_LAN9250_SPI
|
|
|
|
/* Lock the SPI bus in case there are multiple devices or tasks competing
|
|
* for the SPI bus.
|
|
*/
|
|
|
|
SPI_LOCK(priv->spi, true);
|
|
#else
|
|
|
|
/* Lock the QSPI bus in case there are multiple devices or tasks competing
|
|
* for the QSPI bus.
|
|
*/
|
|
|
|
QSPI_LOCK(priv->qspi, true);
|
|
#endif
|
|
|
|
/* Now make sure that the (Q)SPI bus is configured for the LAN9250 (it
|
|
* might have gotten configured for a different device while unlocked)
|
|
*/
|
|
|
|
#ifndef CONFIG_LAN9250_SPI_EXCLUSIVE
|
|
lan9250_config_spi(priv);
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_unlock_spi
|
|
*
|
|
* Description:
|
|
* De-select the SPI
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void lan9250_unlock_spi(FAR struct lan9250_driver_s *priv)
|
|
{
|
|
#ifdef CONFIG_LAN9250_SPI
|
|
|
|
/* Relinquish the lock on the bus. */
|
|
|
|
SPI_LOCK(priv->spi, false);
|
|
#else
|
|
|
|
/* Relinquish the lock on the bus. */
|
|
|
|
QSPI_LOCK(priv->qspi, false);
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_get_reg
|
|
*
|
|
* Description:
|
|
* Read a global register value.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
* address - Register address
|
|
*
|
|
* Returned Value:
|
|
* The value read from the register.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint32_t lan9250_get_reg(FAR struct lan9250_driver_s *priv,
|
|
uint16_t address)
|
|
{
|
|
#ifdef CONFIG_LAN9250_SPI
|
|
# if LAN9250_SPI_READ == LAN9250_SPI_CMD_FREAD
|
|
uint8_t cmd_buffer[4];
|
|
# else
|
|
uint8_t cmd_buffer[3];
|
|
# endif
|
|
uint32_t regval;
|
|
|
|
cmd_buffer[0] = LAN9250_SPI_READ;
|
|
cmd_buffer[1] = (uint8_t)(address >> 8);
|
|
cmd_buffer[2] = (uint8_t)(address >> 0);
|
|
# if LAN9250_SPI_READ == LAN9250_SPI_CMD_FREAD
|
|
cmd_buffer[3] = 0xff; /* SPI read dummy */
|
|
# endif
|
|
|
|
/* Select LAN9250 chip */
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_ETHERNET(0), true);
|
|
|
|
/* Send the read command and register address */
|
|
|
|
SPI_SNDBLOCK(priv->spi, cmd_buffer, sizeof(cmd_buffer));
|
|
|
|
/* Receive register value, total 4 bytes */
|
|
|
|
SPI_RECVBLOCK(priv->spi, ®val, 4);
|
|
|
|
/* De-select LAN9250 chip */
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_ETHERNET(0), false);
|
|
#else
|
|
struct qspi_cmdinfo_s cmdinfo;
|
|
uint32_t regval;
|
|
uint32_t buffer[2];
|
|
int ret;
|
|
|
|
cmdinfo.cmd = LAN9250_SPI_READ;
|
|
cmdinfo.addr = address;
|
|
cmdinfo.addrlen = sizeof(address);
|
|
cmdinfo.buffer = buffer;
|
|
cmdinfo.buflen = sizeof(buffer); /* 4 dummy bytes */
|
|
cmdinfo.flags = QSPICMD_ADDRESS | QSPICMD_READDATA;
|
|
if (priv->sqi_mode)
|
|
{
|
|
cmdinfo.flags |= QSPICMD_IQUAD;
|
|
}
|
|
|
|
ret = QSPI_COMMAND(priv->qspi, &cmdinfo);
|
|
DEBUGASSERT(ret == 0);
|
|
|
|
regval = buffer[1];
|
|
#endif
|
|
|
|
lan9250_getreg_dump(address, regval);
|
|
return regval;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_set_reg
|
|
*
|
|
* Description:
|
|
* Write to a global register.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
* address - Register address
|
|
* data - The data to send
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_set_reg(FAR struct lan9250_driver_s *priv,
|
|
uint16_t address,
|
|
uint32_t data)
|
|
{
|
|
#ifdef CONFIG_LAN9250_SPI
|
|
uint8_t cmd_buffer[3];
|
|
|
|
DEBUGASSERT(priv && priv->spi);
|
|
|
|
cmd_buffer[0] = LAN9250_SPI_WRITE;
|
|
cmd_buffer[1] = (uint8_t)(address >> 8);
|
|
cmd_buffer[2] = (uint8_t)(address >> 0);
|
|
|
|
/* Select LAN9250 chip */
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_ETHERNET(0), true);
|
|
|
|
/* Send the read command and register address */
|
|
|
|
SPI_SNDBLOCK(priv->spi, cmd_buffer, sizeof(cmd_buffer));
|
|
|
|
/* Receive register value, total 4 bytes */
|
|
|
|
SPI_SNDBLOCK(priv->spi, &data, 4);
|
|
|
|
/* De-select LAN9250 chip. */
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_ETHERNET(0), false);
|
|
#else
|
|
struct qspi_cmdinfo_s cmdinfo;
|
|
int ret;
|
|
|
|
cmdinfo.cmd = LAN9250_SPI_WRITE;
|
|
cmdinfo.addr = address;
|
|
cmdinfo.addrlen = sizeof(address);
|
|
cmdinfo.buffer = &data;
|
|
cmdinfo.buflen = sizeof(data);
|
|
cmdinfo.flags = QSPICMD_ADDRESS | QSPICMD_WRITEDATA;
|
|
if (priv->sqi_mode)
|
|
{
|
|
cmdinfo.flags |= QSPICMD_IQUAD;
|
|
}
|
|
|
|
ret = QSPI_COMMAND(priv->qspi, &cmdinfo);
|
|
DEBUGASSERT(ret == 0);
|
|
#endif
|
|
|
|
lan9250_setreg_dump(address, data);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_wait_ready
|
|
*
|
|
* Description:
|
|
* Wait until LAN9250 is ready.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
* address - Register address
|
|
* mask - Register value mask
|
|
* expected - Expected register value
|
|
* second - Wait timeout in second
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_wait_ready(FAR struct lan9250_driver_s *priv,
|
|
uint16_t address, uint32_t mask,
|
|
uint32_t expected, uint32_t second)
|
|
{
|
|
clock_t tout_ticks = clock_systime_ticks() + SEC2TICK(second);
|
|
bool timeout = false;
|
|
|
|
while (1)
|
|
{
|
|
if ((lan9250_get_reg(priv, address) & mask) == expected)
|
|
{
|
|
break;
|
|
}
|
|
else if (clock_systime_ticks() > tout_ticks)
|
|
{
|
|
timeout = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (timeout)
|
|
{
|
|
nerr("ERROR: wait register:0x%02x, mask:0x%08x, expected:0x%08x\n",
|
|
address, mask, expected);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_get_macreg
|
|
*
|
|
* Description:
|
|
* Read a MAC register value.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
* address - MAC register address
|
|
*
|
|
* Returned Value:
|
|
* The value read from the MAC register.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint32_t lan9250_get_macreg(FAR struct lan9250_driver_s *priv,
|
|
uint8_t address)
|
|
{
|
|
uint32_t regval = address | HMCSRICR_CSRB | HMCSRICR_RNW;
|
|
|
|
/* Wait for MAC to be ready and send reading register command */
|
|
|
|
lan9250_wait_ready(priv, LAN9250_HMCSRICR, HMCSRICR_CSRB, 0,
|
|
LAN9250_MAC_TIMEOUT);
|
|
lan9250_set_reg(priv, LAN9250_HMCSRICR, regval);
|
|
|
|
/* Wait for MAC to be ready and read register value */
|
|
|
|
lan9250_wait_ready(priv, LAN9250_HMCSRICR, HMCSRICR_CSRB, 0,
|
|
LAN9250_MAC_TIMEOUT);
|
|
regval = lan9250_get_reg(priv, LAN9250_HMCSRIDR);
|
|
|
|
lan9250_getmacreg_dump(address, regval);
|
|
return regval;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_set_macreg
|
|
*
|
|
* Description:
|
|
* Write to a MAC register.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
* cmd - MAC register address
|
|
* data - The data to send
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_set_macreg(FAR struct lan9250_driver_s *priv,
|
|
uint8_t address, uint32_t data)
|
|
{
|
|
uint32_t regval = address | HMCSRICR_CSRB;
|
|
|
|
lan9250_setmacreg_dump(address, data);
|
|
|
|
/* Wait for MAC to be ready and send writing register command and data */
|
|
|
|
lan9250_wait_ready(priv, LAN9250_HMCSRICR, HMCSRICR_CSRB, 0,
|
|
LAN9250_MAC_TIMEOUT);
|
|
lan9250_set_reg(priv, LAN9250_HMCSRIDR, data);
|
|
lan9250_set_reg(priv, LAN9250_HMCSRICR, regval);
|
|
|
|
/* Wait until writing MAC is done */
|
|
|
|
lan9250_wait_ready(priv, LAN9250_HMCSRICR, HMCSRICR_CSRB, 0,
|
|
LAN9250_MAC_TIMEOUT);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_wait_mac_ready
|
|
*
|
|
* Description:
|
|
* Wait until LAN9250 MAC is ready
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
* address - MAC register address
|
|
* mask - MAC register value mask
|
|
* expected - Expected MAC register value
|
|
* timeout - Wait timeout in second
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_wait_mac_ready(FAR struct lan9250_driver_s *priv,
|
|
uint8_t address, uint32_t mask,
|
|
uint32_t expected, uint32_t second)
|
|
{
|
|
clock_t tout_ticks = clock_systime_ticks() + SEC2TICK(second);
|
|
bool timeout = false;
|
|
|
|
while (1)
|
|
{
|
|
if ((lan9250_get_macreg(priv, address) & mask) == expected)
|
|
{
|
|
break;
|
|
}
|
|
else if (clock_systime_ticks() > tout_ticks)
|
|
{
|
|
timeout = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (timeout)
|
|
{
|
|
nerr("ERROR: wait MAC register:0x%02x, mask:0x%08x, expected:0x%08x\n",
|
|
address, mask, expected);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_get_phyreg
|
|
*
|
|
* Description:
|
|
* Read 16-bits of PHY data.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
* address - The PHY register address
|
|
*
|
|
* Returned Value:
|
|
* 16-bit value read from the PHY
|
|
*
|
|
****************************************************************************/
|
|
|
|
static uint16_t lan9250_get_phyreg(FAR struct lan9250_driver_s *priv,
|
|
uint8_t address)
|
|
{
|
|
uint32_t regval = (1 << HMACMIIAR_PHYA_S) |
|
|
(address << HMACMIIAR_MIIRX_S);
|
|
|
|
/* Wait PHY to be ready and send reading register command */
|
|
|
|
lan9250_wait_mac_ready(priv, LAN9250_HMACMIIAR, HMACMIIAR_MIIB, 0,
|
|
LAN9250_PHY_TIMEOUT);
|
|
lan9250_set_macreg(priv, LAN9250_HMACMIIAR, regval);
|
|
|
|
/* Wait PHY to be ready and read register value */
|
|
|
|
lan9250_wait_mac_ready(priv, LAN9250_HMACMIIAR, HMACMIIAR_MIIB, 0,
|
|
LAN9250_PHY_TIMEOUT);
|
|
regval = lan9250_get_macreg(priv, LAN9250_HMACMIIDR);
|
|
|
|
lan9250_getphyreg_dump(address, regval);
|
|
|
|
return regval;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_set_phyreg
|
|
*
|
|
* Description:
|
|
* Write 16-bits of PHY data.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
* address - The PHY register address
|
|
* data - 16-bit data to write to the PHY
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_set_phyreg(FAR struct lan9250_driver_s *priv,
|
|
uint8_t address,
|
|
uint16_t data)
|
|
{
|
|
uint32_t regval = (1 << HMACMIIAR_PHYA_S) |
|
|
(address << HMACMIIAR_MIIRX_S) |
|
|
HMACMIIAR_MIIW;
|
|
|
|
DEBUGASSERT((address & (HMACMIIAR_MIIRX_M >> HMACMIIAR_MIIRX_S))
|
|
== address);
|
|
|
|
lan9250_setphyreg_dump(address, data);
|
|
|
|
/* Wait PHY to be ready and send writing register command and data */
|
|
|
|
lan9250_wait_mac_ready(priv, LAN9250_HMACMIIAR, HMACMIIAR_MIIB, 0,
|
|
LAN9250_PHY_TIMEOUT);
|
|
lan9250_set_macreg(priv, LAN9250_HMACMIIDR, data);
|
|
lan9250_set_macreg(priv, LAN9250_HMACMIIAR, regval);
|
|
|
|
/* Wait PHY until writing is done */
|
|
|
|
lan9250_wait_mac_ready(priv, LAN9250_HMACMIIAR, HMACMIIAR_MIIB, 0,
|
|
LAN9250_PHY_TIMEOUT);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_recv_buffer
|
|
*
|
|
* Description:
|
|
* Read a buffer of data.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
* buffer - A pointer to the buffer to read into
|
|
* buflen - The number of bytes to read
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_recv_buffer(FAR struct lan9250_driver_s *priv,
|
|
FAR uint8_t *buffer,
|
|
size_t buflen)
|
|
{
|
|
#ifdef CONFIG_LAN9250_SPI
|
|
# if LAN9250_SPI_READ == LAN9250_SPI_CMD_FREAD
|
|
uint8_t cmd_buffer[4];
|
|
# else
|
|
uint8_t cmd_buffer[3];
|
|
# endif
|
|
|
|
DEBUGASSERT(priv && priv->spi);
|
|
DEBUGASSERT(buflen <= sizeof(priv->pktbuf));
|
|
|
|
/* Read dummy data */
|
|
|
|
lan9250_get_reg(priv, LAN9250_RXDFR);
|
|
|
|
/* Read valid buffer data */
|
|
|
|
cmd_buffer[0] = LAN9250_SPI_READ;
|
|
cmd_buffer[1] = (uint8_t)(LAN9250_RXDFR >> 8);
|
|
cmd_buffer[2] = (uint8_t)(LAN9250_RXDFR >> 0);
|
|
# if LAN9250_SPI_READ == LAN9250_SPI_CMD_FREAD
|
|
cmd_buffer[3] = 0xff; /* SPI read dummy */
|
|
# endif
|
|
|
|
/* Select LAN9250 chip */
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_ETHERNET(0), true);
|
|
|
|
/* Send the read command and register address */
|
|
|
|
SPI_SNDBLOCK(priv->spi, cmd_buffer, sizeof(cmd_buffer));
|
|
SPI_RECVBLOCK(priv->spi, buffer, buflen);
|
|
|
|
/* De-select LAN9250 chip */
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_ETHERNET(0), false);
|
|
#else
|
|
struct qspi_meminfo_s meminfo;
|
|
int ret;
|
|
|
|
meminfo.cmd = LAN9250_SPI_READ;
|
|
meminfo.addr = LAN9250_RXDFR;
|
|
meminfo.addrlen = sizeof(uint16_t);
|
|
meminfo.buffer = buffer;
|
|
meminfo.buflen = buflen;
|
|
meminfo.dummies = 16; /* 8 SPI dummy clock and 4 dummy RX bytes */
|
|
meminfo.flags = QSPIMEM_READ;
|
|
if (priv->sqi_mode)
|
|
{
|
|
meminfo.flags |= QSPIMEM_IQUAD;
|
|
}
|
|
|
|
ret = QSPI_MEMORY(priv->qspi, &meminfo);
|
|
DEBUGASSERT(ret == 0);
|
|
#endif /* CONFIG_LAN9250_SPI */
|
|
|
|
lan9250_buffer_dump(LAN9250_RXDFR, buffer, buflen);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_send_buffer
|
|
*
|
|
* Description:
|
|
* Write a buffer of data.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
* buffer - A pointer to the buffer to write from
|
|
* buflen - The number of bytes to write
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void lan9250_send_buffer(FAR struct lan9250_driver_s *priv,
|
|
FAR const uint8_t *buffer,
|
|
size_t buflen)
|
|
{
|
|
#ifdef CONFIG_LAN9250_SPI
|
|
uint8_t cmd_buffer[3];
|
|
uint32_t regval;
|
|
#else
|
|
struct qspi_meminfo_s meminfo;
|
|
uint32_t regval;
|
|
int ret;
|
|
#endif
|
|
|
|
lan9250_buffer_dump(LAN9250_TXDFR, buffer, buflen);
|
|
|
|
/**
|
|
* LAN9250 SPI write command A fields:
|
|
*
|
|
* - TX packet length 4 bytes align
|
|
* - First frame
|
|
* - Last frame
|
|
*/
|
|
|
|
regval = SPI_CMD_WRITE_A_4BA | SPI_CMD_WRITE_A_FS |
|
|
SPI_CMD_WRITE_A_LS | buflen;
|
|
lan9250_set_reg(priv, LAN9250_TXDFR, regval);
|
|
|
|
/**
|
|
* LAN9250 SPI write command B fields:
|
|
*
|
|
* - Packet TAG
|
|
*/
|
|
|
|
regval = SPI_CMD_WRITE_B_PT | buflen;
|
|
lan9250_set_reg(priv, LAN9250_TXDFR, regval);
|
|
|
|
#ifdef CONFIG_LAN9250_SPI
|
|
|
|
/* Read valid buffer data */
|
|
|
|
cmd_buffer[0] = LAN9250_SPI_WRITE;
|
|
cmd_buffer[1] = (uint8_t)(LAN9250_TXDFR >> 8);
|
|
cmd_buffer[2] = (uint8_t)(LAN9250_TXDFR >> 0);
|
|
|
|
/* Select LAN9250 chip */
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_ETHERNET(0), true);
|
|
|
|
/* Send the read command and register address, total 3 bytes */
|
|
|
|
SPI_SNDBLOCK(priv->spi, cmd_buffer, 3);
|
|
|
|
/* Write data into FIFO with 4-byte align */
|
|
|
|
SPI_SNDBLOCK(priv->spi, buffer, LAN9250_ALIGN(buflen));
|
|
|
|
/* De-select LAN9250 chip */
|
|
|
|
SPI_SELECT(priv->spi, SPIDEV_ETHERNET(0), false);
|
|
#else
|
|
meminfo.cmd = LAN9250_SPI_WRITE;
|
|
meminfo.addr = LAN9250_TXDFR;
|
|
meminfo.addrlen = sizeof(uint16_t);
|
|
meminfo.buffer = (void *)buffer;
|
|
meminfo.buflen = LAN9250_ALIGN(buflen);
|
|
meminfo.dummies = 0;
|
|
meminfo.flags = QSPIMEM_WRITE;
|
|
if (priv->sqi_mode)
|
|
{
|
|
meminfo.flags |= QSPIMEM_IQUAD;
|
|
}
|
|
|
|
ret = QSPI_MEMORY(priv->qspi, &meminfo);
|
|
DEBUGASSERT(ret == 0);
|
|
#endif /* CONFIG_LAN9250_SPI */
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_enable_sqi
|
|
*
|
|
* Description:
|
|
* Send a command to LAN9250 to enable SQI mode.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_LAN9250_SQI
|
|
static void lan9250_enable_sqi(FAR struct lan9250_driver_s *priv)
|
|
{
|
|
clock_t tout_ticks = clock_systime_ticks() +
|
|
MSEC2TICK(LAN9250_SQI_TIMEOUT);
|
|
struct qspi_cmdinfo_s cmdinfo;
|
|
int ret;
|
|
|
|
cmdinfo.cmd = LAN9250_SPI_ENABLE_SQI;
|
|
cmdinfo.addrlen = 0;
|
|
cmdinfo.buflen = 0;
|
|
cmdinfo.flags = 0;
|
|
|
|
ret = QSPI_COMMAND(priv->qspi, &cmdinfo);
|
|
DEBUGASSERT(ret == 0);
|
|
|
|
/* USE SQI mode interface */
|
|
|
|
priv->sqi_mode = true;
|
|
|
|
/* Check read register value in SQI mode */
|
|
|
|
while (1)
|
|
{
|
|
if ((lan9250_get_reg(priv, LAN9250_BOTR) & BOTR_MASK) == BOTR_VAL)
|
|
{
|
|
break;
|
|
}
|
|
else if (clock_systime_ticks() > tout_ticks)
|
|
{
|
|
/* Check timeout and continue to use SPI mode */
|
|
|
|
priv->sqi_mode = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_sw_reset
|
|
*
|
|
* Description:
|
|
* Send a command to reset LAN9250.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void lan9250_sw_reset(FAR struct lan9250_driver_s *priv)
|
|
{
|
|
/* Wait until LAN9250 SPI bus is ready */
|
|
|
|
lan9250_wait_ready(priv, LAN9250_BOTR, BOTR_MASK,
|
|
BOTR_VAL, LAN9250_RESET_TIMEOUT);
|
|
|
|
/**
|
|
* Send a command to reset:
|
|
*
|
|
* - Digital controller
|
|
* - HMAC
|
|
* - PHY
|
|
*/
|
|
|
|
lan9250_set_reg(priv, LAN9250_RSTCR, RSTCR_DR | RSTCR_PHYR | RSTCR_HMACR);
|
|
|
|
/* Wait some time for hardware ready */
|
|
|
|
lan9250_wait_ready(priv, LAN9250_HWCFGR, HWCFGR_READY,
|
|
HWCFGR_READY, LAN9250_RESET_TIMEOUT);
|
|
|
|
#ifdef CONFIG_LAN9250_SQI
|
|
|
|
/* Enable SQI mode */
|
|
|
|
lan9250_enable_sqi(priv);
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_set_txavailabe
|
|
*
|
|
* Description:
|
|
* Enable or disable TX data FIFO available interrupt.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
* enable - true: enable this interrupt, false: disable this interrupt
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_set_txavailabe(FAR struct lan9250_driver_s *priv,
|
|
bool enable)
|
|
{
|
|
uint32_t regval;
|
|
|
|
if (enable)
|
|
{
|
|
/**
|
|
* Configure FIFO level interrupt:
|
|
*
|
|
* - TX data available level: Ethernet maximum packet size, transform
|
|
* this value to be block number with 64-byte align
|
|
* - TX status level: 255, use maximum value so that this will not be
|
|
* actually used by this driver
|
|
* - RX status level: 0, so that RX interrupt can trigger once
|
|
* receiving one packet
|
|
*/
|
|
|
|
regval = (((LAN9250_PKTBUF_SIZE + 63) / 64) << FLIR_FITXDAL_S) |
|
|
FLIR_FITXSL_M;
|
|
|
|
/* Mark TX data FIFO is unavailable */
|
|
|
|
priv->tx_available = false;
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* Configure FIFO level interrupt:
|
|
*
|
|
* - TX data available level: 255, so that no interrupt triggers
|
|
* - TX status level: 255, use maximum value so that this will not be
|
|
* actually used by this driver
|
|
* - RX status level: 0, so that RX interrupt can trigger once
|
|
* receiving one packet
|
|
*/
|
|
|
|
regval = FLIR_FITXDAL_M | FLIR_FITXSL_M;
|
|
|
|
/* Mark TX data FIFO is available */
|
|
|
|
priv->tx_available = true;
|
|
}
|
|
|
|
lan9250_set_reg(priv, LAN9250_FLIR, regval);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_reset
|
|
*
|
|
* Description:
|
|
* Stop, reset, re-initialize, and restart the LAN9250. This is done
|
|
* initially, on ifup, and after a TX timeout.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lan9250_reset(FAR struct lan9250_driver_s *priv)
|
|
{
|
|
uint32_t regval;
|
|
|
|
nwarn("WARNING: Reset\n");
|
|
|
|
/* Configure SPI for the LAN9250 */
|
|
|
|
lan9250_config_spi(priv);
|
|
|
|
/* reset the LAN9250 by sending command */
|
|
|
|
lan9250_sw_reset(priv);
|
|
|
|
/* Read LAN9250 hardware ID */
|
|
|
|
regval = lan9250_get_reg(priv, LAN9250_CIARR);
|
|
if ((regval & CIARR_CID_M) != CIARR_CID_V)
|
|
{
|
|
nerr("ERROR: Bad Rev ID: %08x\n", regval);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ninfo("Rev ID: %08x\n", regval & CIARR_CREV_M);
|
|
|
|
/**
|
|
* Configure TX FIFO size mode to be 8:
|
|
*
|
|
* - TX data FIFO size: 7680
|
|
* - RX data FIFO size: 7680
|
|
* - TX status FIFO size: 512
|
|
* - RX status FIFO size: 512
|
|
*/
|
|
|
|
regval = HWCFGR_MBO | (8 << HWCFGR_TXFS_S);
|
|
lan9250_set_reg(priv, LAN9250_HWCFGR, regval);
|
|
|
|
/**
|
|
* Configure MAC automatic flow control:
|
|
*
|
|
* - Automatic flow control high level: 110
|
|
* - Automatic flow control low level: 55
|
|
* - Backpressure duration: 4
|
|
* - Flow control on any frame
|
|
*/
|
|
|
|
regval = (110 << HMAFCCFGR_AFCHL_S) |
|
|
(55 << HMAFCCFGR_AFCLL_S) |
|
|
(4 << HMAFCCFGR_BPD_S) |
|
|
HMAFCCFGR_FCOAF;
|
|
lan9250_set_reg(priv, LAN9250_HMAFCCFGR, regval);
|
|
|
|
/**
|
|
* Configure host MAC flow control:
|
|
*
|
|
* - Pause time: 15
|
|
* - Flow control
|
|
*/
|
|
|
|
regval = (0xf << HMACFCR_PT_S) | HMACFCR_FLE;
|
|
lan9250_set_macreg(priv, LAN9250_HMACFCR, regval);
|
|
|
|
/**
|
|
* Configure interrupt:
|
|
*
|
|
* - Interrupt De-assertion interval: 10
|
|
* - Interrupt output to pin
|
|
* - Interrupt pin active output low
|
|
* - Interrupt pin push-pull driver
|
|
*/
|
|
|
|
regval = (10 << ICFGR_IDAI_S) |
|
|
ICFGR_IRQE | ICFGR_IRQBT;
|
|
lan9250_set_reg(priv, LAN9250_ICFGR, regval);
|
|
|
|
/**
|
|
* Configure interrupt trigger source, please refer to macro
|
|
* LAN9250_INT_SOURCE.
|
|
*/
|
|
|
|
lan9250_set_reg(priv, LAN9250_IER, LAN9250_INT_SOURCE);
|
|
|
|
/* Disable TX data FIFO available interrupt */
|
|
|
|
lan9250_set_txavailabe(priv, false);
|
|
|
|
/**
|
|
* Configure RX:
|
|
*
|
|
* - RX DMA counter: Ethernet maximum packet size
|
|
* - RX data offset: 4, so that need read dummy before reading data
|
|
*/
|
|
|
|
regval = (LAN9250_PKTBUF_SIZE << RXCFGR_RXDMAC_S) | (4 << RXCFGR_RXDO_S);
|
|
lan9250_set_reg(priv, LAN9250_RXCFGR, regval);
|
|
|
|
/**
|
|
* Configure remote power management:
|
|
*
|
|
* - Auto wakeup
|
|
* - Disable 1588 clock
|
|
* - Disable 1588 timestamp unit clock
|
|
* - Energy-detect
|
|
* - Wake on
|
|
* - PME pin push-pull driver
|
|
* - Clear wakeon
|
|
* - PME active high
|
|
* - PME pin
|
|
*/
|
|
|
|
regval = PMCR_PMWU | PMCR_1588CLKD | PMCR_1588TSUCLKD |
|
|
PMCR_EDE | PMCR_WOE | PMCR_PMEBT |
|
|
PMCR_WOLS | PMCR_PMEP | PMCR_PMEE;
|
|
lan9250_set_reg(priv, LAN9250_PMCR, regval);
|
|
|
|
/**
|
|
* Configure PHY basic control:
|
|
*
|
|
* - Auto-Negotiation for speed(10 or 100Mbsp) and direction
|
|
* (half or full duplex)
|
|
*/
|
|
|
|
lan9250_set_phyreg(priv, LAN9250_PHYBCR, PHYBCR_ANE);
|
|
|
|
/**
|
|
* Configure PHY auto-negotiation advertisement capability:
|
|
*
|
|
* - Asymmetric pause
|
|
* - Symmetric pause
|
|
* - 100Base-X full deplex if !CONFIG_LAN9250_HALFDUPPLEX
|
|
* - 100Base-X half deplex
|
|
* - 10Base-X full deplex if !CONFIG_LAN9250_HALFDUPPLEX
|
|
* - 10Base-X half deplex
|
|
* - Select IEEE802.3
|
|
*/
|
|
|
|
regval = PHYANAR_AP | PHYANAR_SP | PHYANAR_100BXHD |
|
|
PHYANAR_10BXHD | PHYANAR_SF;
|
|
#ifndef CONFIG_LAN9250_HALFDUPPLEX
|
|
regval |= PHYANAR_100BXFD | PHYANAR_10BXFD;
|
|
#endif
|
|
lan9250_set_phyreg(priv, LAN9250_PHYANAR, regval);
|
|
|
|
/**
|
|
* Configure PHY special mode:
|
|
*
|
|
* - PHY mode = 111b, enable all capable and auto-nagotiation
|
|
* - PHY address = 1, default value is fixed to 1 by manufacturer
|
|
*/
|
|
|
|
regval = PHYSMR_PM_M | 1;
|
|
lan9250_set_phyreg(priv, LAN9250_PHYSMR, regval);
|
|
|
|
/**
|
|
* Configure PHY special control or status indication:
|
|
*
|
|
* - Port auto-MDIX determined by bits 14 and 13
|
|
* - Auto-MDIX
|
|
* - Disable SQE tests
|
|
*/
|
|
|
|
regval = PHYSCOSIR_AMDIXC | PHYSCOSIR_AMDIXE | PHYSCOSIR_SQETD;
|
|
lan9250_set_phyreg(priv, LAN9250_PHYSCOSIR, regval);
|
|
|
|
/**
|
|
* Configure PHY interrupt source:
|
|
*
|
|
* - Link up
|
|
* - Link down
|
|
*/
|
|
|
|
regval = PHYIER_LU | PHYIER_LD;
|
|
lan9250_set_phyreg(priv, LAN9250_PHYIER, regval);
|
|
|
|
/**
|
|
* Configure special control or status:
|
|
*
|
|
* - Fixed to write 0000010b to reserved filed
|
|
*/
|
|
|
|
lan9250_set_phyreg(priv, LAN9250_PHYSCOSR, PHYSCOSR_RD);
|
|
|
|
/* Clear interrupt status */
|
|
|
|
lan9250_set_reg(priv, LAN9250_ISR, UINT32_MAX);
|
|
|
|
/**
|
|
* Configure HMAC control:
|
|
*
|
|
* - Automaticaly strip the pad field on incoming packets
|
|
* - TX enable
|
|
* - RX enable
|
|
* - Full duplex mode if !CONFIG_LAN9250_HALFDUPPLEX
|
|
*/
|
|
|
|
regval = HMACCR_APS | HMACCR_TXE | HMACCR_RXE;
|
|
#ifndef CONFIG_LAN9250_HALFDUPPLEX
|
|
regval |= HMACCR_FDM;
|
|
#endif
|
|
lan9250_set_macreg(priv, LAN9250_HMACCR, regval);
|
|
|
|
/** Configure TX:
|
|
*
|
|
* - TX enable
|
|
*/
|
|
|
|
lan9250_set_reg(priv, LAN9250_TXCFGR, TXCFGR_TXE);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_set_macaddr
|
|
*
|
|
* Description:
|
|
* Set the MAC address to the configured value. This is done after ifup
|
|
* or after a TX timeout. Note that this means that the interface must
|
|
* be down before configuring the MAC addr.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_set_macaddr(FAR struct lan9250_driver_s *priv)
|
|
{
|
|
uint32_t high_addr;
|
|
uint32_t low_addr;
|
|
FAR uint8_t *mac = priv->dev.d_mac.ether.ether_addr_octet;
|
|
|
|
high_addr = (((uint32_t)mac[5] << 8) | ((uint32_t)mac[4] << 0));
|
|
low_addr = (((uint32_t)mac[3] << 24) | ((uint32_t)mac[2] << 16) |
|
|
((uint32_t)mac[1] << 8) | ((uint32_t)mac[0] << 0));
|
|
|
|
lan9250_set_macreg(priv, LAN9250_HMACAHR, high_addr);
|
|
lan9250_set_macreg(priv, LAN9250_HMACALR, low_addr);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_transmit
|
|
*
|
|
* Description:
|
|
* Start hardware transmission. Called either from:
|
|
*
|
|
* - pkif interrupt when an application responds to the receipt of data
|
|
* by trying to send something, or
|
|
* - From watchdog based polling.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; a negated errno on failure
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lan9250_transmit(FAR struct lan9250_driver_s *priv)
|
|
{
|
|
uint32_t regval;
|
|
uint16_t free_size;
|
|
uint8_t status_size;
|
|
|
|
regval = lan9250_get_reg(priv, LAN9250_TXFIR);
|
|
status_size = (regval & TXFIR_TXSFUS_M) >> TXFIR_TXSFUS_S;
|
|
free_size = regval & TXFIR_TXDFFS_M;
|
|
|
|
ninfo("availabe status size:%d, free space size:%d\n",
|
|
status_size, free_size);
|
|
|
|
/* Clear TX status FIFO if it is no empty by reading data */
|
|
|
|
for (int i = 0; i < status_size; i++)
|
|
{
|
|
lan9250_get_reg(priv, LAN9250_TXSFR);
|
|
}
|
|
|
|
if (free_size > priv->dev.d_len)
|
|
{
|
|
/* Increment statistics */
|
|
|
|
ninfo("Sending packet, pktlen: %d\n", priv->dev.d_len);
|
|
NETDEV_TXPACKETS(&priv->dev);
|
|
|
|
/* Send the packet: address=priv->dev.d_buf, length=priv->dev.d_len */
|
|
|
|
lan9250_dump_buf("Transmit Packet", priv->dev.d_buf,
|
|
priv->dev.d_len);
|
|
|
|
lan9250_send_buffer(priv, priv->dev.d_buf, priv->dev.d_len);
|
|
|
|
if ((free_size - priv->dev.d_len) < LAN9250_PKTBUF_SIZE)
|
|
{
|
|
/* Enable TX data FIFO available interrupt */
|
|
|
|
lan9250_set_txavailabe(priv, true);
|
|
|
|
/**
|
|
* Enable the TX timeout watchdog (perhaps restarting the timer)
|
|
* when free data space is not enough.
|
|
*/
|
|
|
|
wd_start(&priv->txtout_timer, LAN9250_TX_TIMEOUT,
|
|
lan9250_txtout_timercb, (wdparm_t)priv);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* This condition is not supported. */
|
|
|
|
DEBUGASSERT(0);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_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
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lan9250_txpoll(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct lan9250_driver_s *priv =
|
|
(FAR struct lan9250_driver_s *)dev->d_private;
|
|
|
|
/* Send the packet */
|
|
|
|
lan9250_transmit(priv);
|
|
|
|
/* Stop the poll now if free FIFO buffer is not enough */
|
|
|
|
return priv->tx_available ? OK : -EBUSY;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_txavail_work
|
|
*
|
|
* Description:
|
|
* Perform an out-of-cycle poll on the worker thread.
|
|
*
|
|
* Parameters:
|
|
* dev - The reference to the driver structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_txavail_work(FAR void *arg)
|
|
{
|
|
FAR struct lan9250_driver_s *priv = (FAR struct lan9250_driver_s *)arg;
|
|
FAR struct net_driver_s *dev = &priv->dev;
|
|
|
|
/* Lock the network and serialize driver operations if necessary.
|
|
* NOTE: Serialization is only required in the case where the driver work
|
|
* is performed on an LP worker thread and where more than one LP worker
|
|
* thread has been configured.
|
|
*/
|
|
|
|
net_lock();
|
|
lan9250_lock_spi(priv);
|
|
|
|
/* Ignore the notification if the interface is not yet up */
|
|
|
|
if (IFF_IS_UP(dev->d_flags))
|
|
{
|
|
/* Check if there is room in the hardware to hold another outgoing
|
|
* packet.
|
|
*/
|
|
|
|
/* If so, then poll the network for new XMIT data */
|
|
|
|
devif_poll(dev, lan9250_txpoll);
|
|
}
|
|
|
|
/* Release lock on the SPI bus and the network */
|
|
|
|
lan9250_unlock_spi(priv);
|
|
net_unlock();
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_phy_isr
|
|
*
|
|
* Description:
|
|
* Process LAN9250 PHY interrupt.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_phy_isr(FAR struct lan9250_driver_s *priv)
|
|
{
|
|
uint16_t regval;
|
|
|
|
regval = lan9250_get_phyreg(priv, LAN9250_PHYISFR);
|
|
if (regval & PHYISFR_LU)
|
|
{
|
|
ninfo("Link up\n");
|
|
|
|
IFF_SET_UP(priv->dev.d_flags);
|
|
}
|
|
else if (regval & PHYISFR_LD)
|
|
{
|
|
ninfo("Link down\n");
|
|
|
|
IFF_CLR_UP(priv->dev.d_flags);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_txavailable_isr
|
|
*
|
|
* Description:
|
|
* TX data FIFO available interrupt function.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_txavailable_isr(FAR struct lan9250_driver_s *priv)
|
|
{
|
|
/* Update statistics */
|
|
|
|
NETDEV_TXDONE(&priv->dev);
|
|
|
|
/* Disable TX data FIFO available interrupt */
|
|
|
|
lan9250_set_txavailabe(priv, false);
|
|
|
|
/* If no further xmits are pending, then cancel the TX timeout */
|
|
|
|
wd_cancel(&priv->txtout_timer);
|
|
|
|
/* Then poll the network for new XMIT data */
|
|
|
|
devif_poll(&priv->dev, lan9250_txpoll);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_netdev_rx
|
|
*
|
|
* Description:
|
|
* Give the newly received packet to the network.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_netdev_rx(FAR struct lan9250_driver_s *priv)
|
|
{
|
|
#ifdef CONFIG_NET_PKT
|
|
/* When packet sockets are enabled, feed the frame into the tap */
|
|
|
|
pkt_input(&priv->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");
|
|
NETDEV_RXIPV4(&priv->dev);
|
|
|
|
/* Receive an IPv4 packet from the network device */
|
|
|
|
ipv4_input(&priv->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.
|
|
*/
|
|
|
|
if (priv->dev.d_len > 0)
|
|
{
|
|
/* And send the packet */
|
|
|
|
lan9250_transmit(priv);
|
|
}
|
|
}
|
|
else
|
|
#endif /* CONFIG_NET_IPv4 */
|
|
#ifdef CONFIG_NET_IPv6
|
|
if (BUF->type == HTONS(ETHTYPE_IP6))
|
|
{
|
|
ninfo("IPv6 frame\n");
|
|
NETDEV_RXIPV6(&priv->dev);
|
|
|
|
/* Give the IPv6 packet to the network layer */
|
|
|
|
ipv6_input(&priv->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.
|
|
*/
|
|
|
|
if (priv->dev.d_len > 0)
|
|
{
|
|
/* And send the packet */
|
|
|
|
lan9250_transmit(priv);
|
|
}
|
|
}
|
|
else
|
|
#endif /* CONFIG_NET_IPv6 */
|
|
#ifdef CONFIG_NET_ARP
|
|
if (BUF->type == HTONS(ETHTYPE_ARP))
|
|
{
|
|
ninfo("ARP packet received (%02x)\n", BUF->type);
|
|
NETDEV_RXARP(&priv->dev);
|
|
|
|
arp_input(&priv->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.
|
|
*/
|
|
|
|
if (priv->dev.d_len > 0)
|
|
{
|
|
lan9250_transmit(priv);
|
|
}
|
|
}
|
|
else
|
|
#endif /* CONFIG_NET_ARP */
|
|
{
|
|
nwarn("WARNING: Unsupported packet type dropped (%02x)\n",
|
|
HTONS(BUF->type));
|
|
NETDEV_RXDROPPED(&priv->dev);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_rxdone_isr
|
|
*
|
|
* 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
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_rxdone_isr(FAR struct lan9250_driver_s *priv)
|
|
{
|
|
uint8_t pktcnt;
|
|
uint16_t pktlen;
|
|
uint32_t regval;
|
|
|
|
/* Update statistics */
|
|
|
|
NETDEV_RXPACKETS(&priv->dev);
|
|
|
|
/* Check valiad packet count */
|
|
|
|
regval = lan9250_get_reg(priv, LAN9250_RXFIR);
|
|
pktcnt = (regval & RXFIR_RXSFUS_M) >> RXFIR_RXSFUS_S;
|
|
|
|
ninfo("pktcnt:%d\n", pktcnt);
|
|
|
|
while (pktcnt--)
|
|
{
|
|
/* Check valiad packet data size */
|
|
|
|
regval = lan9250_get_reg(priv, LAN9250_RXSFR);
|
|
pktlen = (regval & RXSFF_PL_M) >> RXSFF_PL_S;
|
|
|
|
ninfo("pktlen:%d\n", pktlen);
|
|
|
|
if (pktlen)
|
|
{
|
|
/**
|
|
* Copy the data from the receive buffer to priv->dev.d_buf.
|
|
* ERDPT should be correctly positioned from the last call to
|
|
* end_rdbuffer (above).
|
|
*/
|
|
|
|
lan9250_recv_buffer(priv, priv->dev.d_buf, pktlen);
|
|
|
|
/**
|
|
* Save the packet length (without the 4 byte CRC)
|
|
* in priv->dev.d_len.
|
|
*/
|
|
|
|
priv->dev.d_len = pktlen - 4;
|
|
|
|
lan9250_dump_buf("Received Packet", priv->dev.d_buf,
|
|
priv->dev.d_len);
|
|
|
|
/* Dispatch the packet to the network */
|
|
|
|
lan9250_netdev_rx(priv);
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_int_worker
|
|
*
|
|
* Description:
|
|
* Perform interrupt handling logic outside of the interrupt handler (on
|
|
* the work queue thread).
|
|
*
|
|
* Input Parameters:
|
|
* arg - The reference to the driver structure (case to void*)
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_int_worker(FAR void *arg)
|
|
{
|
|
FAR struct lan9250_driver_s *priv = (FAR struct lan9250_driver_s *)arg;
|
|
uint32_t regval;
|
|
|
|
DEBUGASSERT(priv);
|
|
|
|
/* Get exclusive access to both the network and the SPI bus. */
|
|
|
|
net_lock();
|
|
lan9250_lock_spi(priv);
|
|
|
|
/**
|
|
* There is no infinite loop check... if there are always pending
|
|
* interrupts, we are just broken.
|
|
*/
|
|
|
|
regval = lan9250_get_reg(priv, LAN9250_ISR);
|
|
if ((regval & LAN9250_INT_SOURCE) != 0)
|
|
{
|
|
/* Handle interrupts according to interrupt resource bit
|
|
* settings.
|
|
*/
|
|
|
|
ninfo("Interrupt status: %08x\n", regval);
|
|
|
|
#if LAN9250_INT_SOURCE & IER_SW
|
|
if ((regval & ISR_SW) != 0)
|
|
{
|
|
ninfo("\tSoftware\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_RAEDY
|
|
if ((regval & ISR_READY) != 0)
|
|
{
|
|
ninfo("\tDevice ready\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_1588
|
|
if ((regval & ISR_1588) != 0)
|
|
{
|
|
ninfo("\t1588\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_PHY
|
|
if ((regval & ISR_PHY) != 0)
|
|
{
|
|
ninfo("\tPHY\n");
|
|
|
|
lan9250_phy_isr(priv);
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_TXSTOP
|
|
if ((regval & ISR_TXS) != 0)
|
|
{
|
|
ninfo("\tTX stop\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_RXSTOP
|
|
if ((regval & ISR_RXS) != 0)
|
|
{
|
|
ninfo("\tRX stop\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_RXDFH
|
|
if ((regval & ISR_RXDFH) != 0)
|
|
{
|
|
ninfo("\tRX dropped frame count halfway\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_TXIOC
|
|
if ((regval & ISR_TXIOC) != 0)
|
|
{
|
|
ninfo("\tTX IOC\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_RXD
|
|
if ((regval & ISR_RXD) != 0)
|
|
{
|
|
ninfo("\tRX DMA\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_GPT
|
|
if ((regval & ISR_GPT) != 0)
|
|
{
|
|
ninfo("\tGP-timer\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_PME
|
|
if ((regval & ISR_PME) != 0)
|
|
{
|
|
ninfo("\tPower management\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_TXSO
|
|
if ((regval & ISR_TXSO) != 0)
|
|
{
|
|
ninfo("\tTX FIFO status overflow\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_RWT
|
|
if ((regval & ISR_RWT) != 0)
|
|
{
|
|
ninfo("\tRX watchdog timeout\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_RXE
|
|
if ((regval & ISR_RXE) != 0)
|
|
{
|
|
ninfo("\tRX error\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_TXE
|
|
if ((regval & ISR_TXE) != 0)
|
|
{
|
|
ninfo("\tTX error\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_GPIO
|
|
if ((regval & ISR_GPIO) != 0)
|
|
{
|
|
ninfo("\tISR_GPIO event\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_TDFO
|
|
if ((regval & ISR_TDFO) != 0)
|
|
{
|
|
ninfo("\tTX data FIFO overrun\n");
|
|
}
|
|
#endif
|
|
|
|
if ((regval & ISR_TDFA) != 0)
|
|
{
|
|
ninfo("\tTX data FIFO available\n");
|
|
|
|
lan9250_txavailable_isr(priv);
|
|
}
|
|
|
|
#if LAN9250_INT_SOURCE & IER_TSFF
|
|
if ((regval & ISR_TSFF) != 0)
|
|
{
|
|
ninfo("\tTX status FIFO full\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_TSFL
|
|
if ((regval & ISR_TSFL) != 0)
|
|
{
|
|
ninfo("\tTX status FIFO level\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_RXDF
|
|
if ((regval & ISR_RXDF) != 0)
|
|
{
|
|
ninfo("\tRX dropped frame\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_RSFF
|
|
if ((regval & ISR_RSFF) != 0)
|
|
{
|
|
ninfo("\tRX status FIFO full\n");
|
|
}
|
|
#endif
|
|
|
|
#if LAN9250_INT_SOURCE & IER_RSFL
|
|
if ((regval & ISR_RSFL) != 0)
|
|
{
|
|
ninfo("\tRX status FIFO level\n");
|
|
|
|
lan9250_rxdone_isr(priv);
|
|
}
|
|
#endif
|
|
|
|
lan9250_set_reg(priv, LAN9250_ISR, regval);
|
|
}
|
|
|
|
/* Release lock on the SPI bus and the network */
|
|
|
|
lan9250_unlock_spi(priv);
|
|
net_unlock();
|
|
|
|
/**
|
|
* Enable ISR_GPIO interrupts after unlocking net so that application
|
|
* could have chance to process Ethernet packet and free iob.
|
|
*/
|
|
|
|
priv->lower->enable(priv->lower);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_interrupt
|
|
*
|
|
* Description:
|
|
* Hardware interrupt handler
|
|
*
|
|
* Input Parameters:
|
|
* irq - Number of the IRQ that generated the interrupt
|
|
* context - Interrupt register state save info (architecture-specific)
|
|
* arg - Interrupt private data
|
|
*
|
|
* Returned Value:
|
|
* OK on success
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lan9250_interrupt(int irq, FAR void *context, FAR void *arg)
|
|
{
|
|
FAR struct lan9250_driver_s *priv;
|
|
|
|
DEBUGASSERT(arg != NULL);
|
|
priv = (FAR struct lan9250_driver_s *)arg;
|
|
|
|
/* In complex environments, we cannot do SPI transfers from the interrupt
|
|
* handler because semaphores are probably used to lock the SPI bus. In
|
|
* this case, we will defer processing to the worker thread. This is also
|
|
* much kinder in the use of system resources and is, therefore, probably
|
|
* a good thing to do in any event.
|
|
*/
|
|
|
|
DEBUGASSERT(work_available(&priv->irq_work));
|
|
|
|
/* Notice that further ISR_GPIO interrupts are disabled until the work is
|
|
* actually performed. This is to prevent overrun of the worker thread.
|
|
* Interrupts are re-enabled in lan9250_int_worker() when the work is done.
|
|
*/
|
|
|
|
priv->lower->disable(priv->lower);
|
|
return work_queue(LAN9250_WORK, &priv->irq_work, lan9250_int_worker,
|
|
(void *)priv, 0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_txtout_worker
|
|
*
|
|
* Description:
|
|
* Our TX watchdog timed out. This is the worker thread continuation of
|
|
* the watchdog timer interrupt. Reset the hardware and start again.
|
|
*
|
|
* Input Parameters:
|
|
* arg - The reference to the driver structure (case to void*)
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_txtout_worker(FAR void *arg)
|
|
{
|
|
FAR struct lan9250_driver_s *priv = (FAR struct lan9250_driver_s *)arg;
|
|
int ret;
|
|
|
|
nerr("ERROR: Tx timeout\n");
|
|
DEBUGASSERT(priv);
|
|
|
|
/* Get exclusive access to the network */
|
|
|
|
net_lock();
|
|
|
|
/* Increment statistics and dump debug info */
|
|
|
|
NETDEV_TXTIMEOUTS(&priv->dev);
|
|
|
|
/* Then reset the hardware: Take the interface down, then bring it
|
|
* back up
|
|
*/
|
|
|
|
ret = lan9250_ifdown(&priv->dev);
|
|
DEBUGASSERT(ret == OK);
|
|
ret = lan9250_ifup(&priv->dev);
|
|
DEBUGASSERT(ret == OK);
|
|
UNUSED(ret);
|
|
|
|
/* Then poll the network for new XMIT data */
|
|
|
|
devif_poll(&priv->dev, lan9250_txpoll);
|
|
|
|
/* Release lock on the network */
|
|
|
|
net_unlock();
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_txtout_timercb
|
|
*
|
|
* Description:
|
|
* Our TX watchdog timed out. Called from the timer interrupt handler.
|
|
* The last TX never completed. Perform work on the worker thread.
|
|
*
|
|
* Input Parameters:
|
|
* arg - The argument
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void lan9250_txtout_timercb(wdparm_t arg)
|
|
{
|
|
FAR struct lan9250_driver_s *priv = (FAR struct lan9250_driver_s *)arg;
|
|
int ret;
|
|
|
|
/* In complex environments, we cannot do SPI transfers from the timeout
|
|
* handler because semaphores are probably used to lock the SPI bus. In
|
|
* this case, we will defer processing to the worker thread. This is also
|
|
* much kinder in the use of system resources and is, therefore, probably
|
|
* a good thing to do in any event.
|
|
*/
|
|
|
|
DEBUGASSERT(priv && work_available(&priv->txtout_work));
|
|
|
|
/* Notice that Tx timeout watchdog is not active so further Tx timeouts
|
|
* can occur until we restart the Tx timeout watchdog.
|
|
*/
|
|
|
|
ret = work_queue(LAN9250_WORK, &priv->txtout_work,
|
|
lan9250_txtout_worker, priv, 0);
|
|
DEBUGASSERT(ret == OK);
|
|
UNUSED(ret);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_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:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lan9250_ifup(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct lan9250_driver_s *priv =
|
|
(FAR struct lan9250_driver_s *)dev->d_private;
|
|
int ret;
|
|
#ifdef CONFIG_LAN9250_REGDEBUG
|
|
uint32_t mac_addr[2];
|
|
#endif
|
|
|
|
ninfo("Bringing up: %u.%u.%u.%u\n",
|
|
ip4_addr1(dev->d_ipaddr), ip4_addr2(dev->d_ipaddr),
|
|
ip4_addr3(dev->d_ipaddr), ip4_addr4(dev->d_ipaddr));
|
|
|
|
/* Lock the SPI bus so that we have exclusive access */
|
|
|
|
lan9250_lock_spi(priv);
|
|
|
|
/* Initialize Ethernet interface, set the MAC address, and make sure that
|
|
* the LAN9250 is not in power save mode.
|
|
*/
|
|
|
|
ret = lan9250_reset(priv);
|
|
if (ret == OK)
|
|
{
|
|
lan9250_set_macaddr(priv);
|
|
|
|
/* Enable interrupts at the LAN9250. Interrupts are still disabled
|
|
* at the interrupt controller.
|
|
*/
|
|
|
|
/* Enable the receiver */
|
|
|
|
/* Mark the interface up and enable the Ethernet interrupt at the
|
|
* controller
|
|
*/
|
|
|
|
priv->lower->enable(priv->lower);
|
|
|
|
/* Read MAC address here in debug mode */
|
|
|
|
#ifdef CONFIG_LAN9250_REGDEBUG
|
|
mac_addr[0] = lan9250_get_macreg(priv, LAN9250_HMACALR);
|
|
mac_addr[1] = lan9250_get_macreg(priv, LAN9250_HMACAHR);
|
|
|
|
ninfo("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
(uint8_t)(mac_addr[1] >> 0), (uint8_t)(mac_addr[1] >> 8),
|
|
(uint8_t)(mac_addr[0] >> 24), (uint8_t)(mac_addr[0] >> 16),
|
|
(uint8_t)(mac_addr[0] >> 8), (uint8_t)(mac_addr[0] >> 0));
|
|
#endif
|
|
}
|
|
|
|
/* Un-lock the SPI bus */
|
|
|
|
lan9250_unlock_spi(priv);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_ifdown
|
|
*
|
|
* Description:
|
|
* NuttX Callback: Stop the interface.
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lan9250_ifdown(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct lan9250_driver_s *priv =
|
|
(FAR struct lan9250_driver_s *)dev->d_private;
|
|
irqstate_t flags;
|
|
|
|
ninfo("Taking down: %u.%u.%u.%u\n",
|
|
ip4_addr1(dev->d_ipaddr), ip4_addr2(dev->d_ipaddr),
|
|
ip4_addr3(dev->d_ipaddr), ip4_addr4(dev->d_ipaddr));
|
|
|
|
/* Lock the SPI bus so that we have exclusive access */
|
|
|
|
lan9250_lock_spi(priv);
|
|
|
|
/* Reset the device and leave in the power save state */
|
|
|
|
lan9250_sw_reset(priv);
|
|
|
|
/* Disable the Ethernet interrupt */
|
|
|
|
flags = enter_critical_section();
|
|
priv->lower->disable(priv->lower);
|
|
|
|
/* Cancel the TX timeout timers */
|
|
|
|
wd_cancel(&priv->txtout_timer);
|
|
|
|
IFF_CLR_UP(priv->dev.d_flags);
|
|
leave_critical_section(flags);
|
|
|
|
/* Un-lock the SPI bus */
|
|
|
|
lan9250_unlock_spi(priv);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_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:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int lan9250_txavail(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct lan9250_driver_s *priv =
|
|
(FAR struct lan9250_driver_s *)dev->d_private;
|
|
irqstate_t flags;
|
|
|
|
/**
|
|
* Lock the SPI bus so that we have exclusive access for but SPI and
|
|
* LAN9250 SPI private data.
|
|
*/
|
|
|
|
lan9250_lock_spi(priv);
|
|
|
|
flags = enter_critical_section();
|
|
|
|
/**
|
|
* Since SPI is locked, so interrupt work must not really process when
|
|
* CPU run here, so:
|
|
*
|
|
* - priv->tx_available = true, TX data FIFO is available and its related
|
|
* interrupt is disable.
|
|
* - priv->tx_available = false, TX data FIFO is unavailable and its
|
|
* related interrupt is enable, so when this interrupt triggers,
|
|
* function "devif_poll" will poll the network for new XMIT data.
|
|
*/
|
|
|
|
if (IFF_IS_UP(dev->d_flags) &&
|
|
priv->tx_available &&
|
|
work_available(&priv->txpoll_work))
|
|
{
|
|
/* Schedule to serialize the poll on the worker thread. */
|
|
|
|
work_queue(LAN9250_WORK, &priv->txpoll_work,
|
|
lan9250_txavail_work, priv, 0);
|
|
}
|
|
|
|
/* Un-lock the SPI bus */
|
|
|
|
leave_critical_section(flags);
|
|
lan9250_unlock_spi(priv);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_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:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NET_MCASTGROUP
|
|
static int lan9250_addmac(FAR struct net_driver_s *dev,
|
|
FAR const uint8_t *mac)
|
|
{
|
|
/* #warning "Multicast MAC support not implemented" */
|
|
|
|
return -ENOSYS;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_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:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NET_MCASTGROUP
|
|
static int lan9250_rmmac(FAR struct net_driver_s *dev,
|
|
FAR const uint8_t *mac)
|
|
{
|
|
/* #warning "Multicast MAC support not implemented" */
|
|
|
|
return -ENOSYS;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: lan9250_initialize
|
|
*
|
|
* Description:
|
|
* Initialize the LAN9250 Ethernet driver.
|
|
*
|
|
* Input Parameters:
|
|
* spi - A reference to the platform's SPI driver for the LAN9250 when
|
|
* enable CONFIG_LAN9250_SPI
|
|
* qspi - A reference to the platform's SQI driver for the LAN9250 when
|
|
* enable CONFIG_LAN9250_SQI
|
|
* lower - The MCU-specific interrupt used to control low-level MCU
|
|
* functions (i.e., LAN9250 GPIO interrupts).
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int lan9250_initialize(
|
|
#ifdef CONFIG_LAN9250_SPI
|
|
FAR struct spi_dev_s *spi,
|
|
#else
|
|
FAR struct qspi_dev_s *qspi,
|
|
#endif
|
|
FAR const struct lan9250_lower_s *lower)
|
|
{
|
|
FAR struct lan9250_driver_s *priv = &g_lan9250;
|
|
FAR struct net_driver_s *dev = &priv->dev;
|
|
|
|
/* Initialize the driver structure */
|
|
|
|
memset(priv, 0, sizeof(struct lan9250_driver_s));
|
|
|
|
dev->d_buf = (uint8_t *)priv->pktbuf;
|
|
dev->d_ifup = lan9250_ifup;
|
|
dev->d_ifdown = lan9250_ifdown;
|
|
dev->d_txavail = lan9250_txavail;
|
|
#ifdef CONFIG_NET_MCASTGROUP
|
|
dev->d_addmac = lan9250_addmac;
|
|
dev->d_rmmac = lan9250_rmmac;
|
|
#endif
|
|
dev->d_private = priv;
|
|
|
|
#ifdef CONFIG_LAN9250_SPI
|
|
priv->spi = spi;
|
|
#else
|
|
priv->qspi = qspi;
|
|
#endif
|
|
|
|
priv->lower = lower;
|
|
|
|
/* Attach the interrupt to the driver (but don't enable it yet) */
|
|
|
|
if (lower->attach(lower, lan9250_interrupt, priv) < 0)
|
|
{
|
|
/* We could not attach the ISR to the interrupt */
|
|
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/* Try to read MAC address from low-level function. */
|
|
|
|
if (lower->getmac)
|
|
{
|
|
lower->getmac(lower, priv->dev.d_mac.ether.ether_addr_octet);
|
|
}
|
|
|
|
/* Register the device with the OS so that socket IOCTLs can be performed */
|
|
|
|
return netdev_register(dev, NET_LL_ETHERNET);
|
|
}
|