You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
929 lines
26 KiB
929 lines
26 KiB
/* ENC28J60 Stand-alone Ethernet Controller with SPI |
|
* |
|
* Copyright (c) 2016 Intel Corporation |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT microchip_enc28j60 |
|
|
|
#define LOG_MODULE_NAME eth_enc28j60 |
|
#define LOG_LEVEL CONFIG_ETHERNET_LOG_LEVEL |
|
|
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(LOG_MODULE_NAME); |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/device.h> |
|
#include <string.h> |
|
#include <errno.h> |
|
#include <zephyr/drivers/gpio.h> |
|
#include <zephyr/drivers/spi.h> |
|
#include <zephyr/net/net_pkt.h> |
|
#include <zephyr/net/net_if.h> |
|
#include <zephyr/net/ethernet.h> |
|
#include <ethernet/eth_stats.h> |
|
|
|
#include "eth_enc28j60_priv.h" |
|
#include "eth.h" |
|
|
|
#define D10D24S 11 |
|
|
|
static int eth_enc28j60_soft_reset(const struct device *dev) |
|
{ |
|
const struct eth_enc28j60_config *config = dev->config; |
|
uint8_t buf[2] = { ENC28J60_SPI_SC, 0xFF }; |
|
const struct spi_buf tx_buf = { |
|
.buf = buf, |
|
.len = 1, |
|
}; |
|
const struct spi_buf_set tx = { |
|
.buffers = &tx_buf, |
|
.count = 1 |
|
}; |
|
|
|
return spi_write_dt(&config->spi, &tx); |
|
} |
|
|
|
static void eth_enc28j60_set_bank(const struct device *dev, uint16_t reg_addr) |
|
{ |
|
const struct eth_enc28j60_config *config = dev->config; |
|
uint8_t buf[2]; |
|
const struct spi_buf tx_buf = { |
|
.buf = buf, |
|
.len = 2 |
|
}; |
|
const struct spi_buf rx_buf = { |
|
.buf = buf, |
|
.len = 2 |
|
}; |
|
const struct spi_buf_set tx = { |
|
.buffers = &tx_buf, |
|
.count = 1 |
|
}; |
|
const struct spi_buf_set rx = { |
|
.buffers = &rx_buf, |
|
.count = 1 |
|
}; |
|
|
|
buf[0] = ENC28J60_SPI_RCR | ENC28J60_REG_ECON1; |
|
buf[1] = 0x0; |
|
|
|
if (!spi_transceive_dt(&config->spi, &tx, &rx)) { |
|
buf[0] = ENC28J60_SPI_WCR | ENC28J60_REG_ECON1; |
|
buf[1] = (buf[1] & 0xFC) | ((reg_addr >> 8) & 0x03); |
|
|
|
spi_write_dt(&config->spi, &tx); |
|
} else { |
|
LOG_DBG("%s: Failure while setting bank to 0x%04x", dev->name, reg_addr); |
|
} |
|
} |
|
|
|
static void eth_enc28j60_write_reg(const struct device *dev, |
|
uint16_t reg_addr, |
|
uint8_t value) |
|
{ |
|
const struct eth_enc28j60_config *config = dev->config; |
|
uint8_t buf[2]; |
|
const struct spi_buf tx_buf = { |
|
.buf = buf, |
|
.len = 2 |
|
}; |
|
const struct spi_buf_set tx = { |
|
.buffers = &tx_buf, |
|
.count = 1 |
|
}; |
|
|
|
buf[0] = ENC28J60_SPI_WCR | (reg_addr & 0xFF); |
|
buf[1] = value; |
|
|
|
spi_write_dt(&config->spi, &tx); |
|
} |
|
|
|
static void eth_enc28j60_read_reg(const struct device *dev, uint16_t reg_addr, |
|
uint8_t *value) |
|
{ |
|
const struct eth_enc28j60_config *config = dev->config; |
|
uint8_t buf[3]; |
|
const struct spi_buf tx_buf = { |
|
.buf = buf, |
|
.len = 2 |
|
}; |
|
const struct spi_buf_set tx = { |
|
.buffers = &tx_buf, |
|
.count = 1 |
|
}; |
|
struct spi_buf rx_buf = { |
|
.buf = buf, |
|
}; |
|
const struct spi_buf_set rx = { |
|
.buffers = &rx_buf, |
|
.count = 1 |
|
}; |
|
uint8_t rx_size = 2U; |
|
|
|
if (reg_addr & 0xF000) { |
|
rx_size = 3U; |
|
} |
|
|
|
rx_buf.len = rx_size; |
|
|
|
buf[0] = ENC28J60_SPI_RCR | (reg_addr & 0xFF); |
|
buf[1] = 0x0; |
|
|
|
if (!spi_transceive_dt(&config->spi, &tx, &rx)) { |
|
*value = buf[rx_size - 1]; |
|
} else { |
|
LOG_DBG("%s: Failure while reading register 0x%04x", dev->name, reg_addr); |
|
*value = 0U; |
|
} |
|
} |
|
|
|
static void eth_enc28j60_set_eth_reg(const struct device *dev, |
|
uint16_t reg_addr, |
|
uint8_t value) |
|
{ |
|
const struct eth_enc28j60_config *config = dev->config; |
|
uint8_t buf[2]; |
|
const struct spi_buf tx_buf = { |
|
.buf = buf, |
|
.len = 2 |
|
}; |
|
const struct spi_buf_set tx = { |
|
.buffers = &tx_buf, |
|
.count = 1 |
|
}; |
|
|
|
buf[0] = ENC28J60_SPI_BFS | (reg_addr & 0xFF); |
|
buf[1] = value; |
|
|
|
spi_write_dt(&config->spi, &tx); |
|
} |
|
|
|
|
|
static void eth_enc28j60_clear_eth_reg(const struct device *dev, |
|
uint16_t reg_addr, |
|
uint8_t value) |
|
{ |
|
const struct eth_enc28j60_config *config = dev->config; |
|
uint8_t buf[2]; |
|
const struct spi_buf tx_buf = { |
|
.buf = buf, |
|
.len = 2 |
|
}; |
|
const struct spi_buf_set tx = { |
|
.buffers = &tx_buf, |
|
.count = 1 |
|
}; |
|
|
|
buf[0] = ENC28J60_SPI_BFC | (reg_addr & 0xFF); |
|
buf[1] = value; |
|
|
|
spi_write_dt(&config->spi, &tx); |
|
} |
|
|
|
static void eth_enc28j60_write_mem(const struct device *dev, |
|
uint8_t *data_buffer, |
|
uint16_t buf_len) |
|
{ |
|
const struct eth_enc28j60_config *config = dev->config; |
|
uint8_t buf[1] = { ENC28J60_SPI_WBM }; |
|
struct spi_buf tx_buf[2] = { |
|
{ |
|
.buf = buf, |
|
.len = 1 |
|
}, |
|
}; |
|
const struct spi_buf_set tx = { |
|
.buffers = tx_buf, |
|
.count = 2 |
|
}; |
|
uint16_t num_segments; |
|
uint16_t num_remaining; |
|
int i; |
|
|
|
num_segments = buf_len / MAX_BUFFER_LENGTH; |
|
num_remaining = buf_len - MAX_BUFFER_LENGTH * num_segments; |
|
|
|
for (i = 0; i < num_segments; i++, data_buffer += MAX_BUFFER_LENGTH) { |
|
tx_buf[1].buf = data_buffer; |
|
tx_buf[1].len = MAX_BUFFER_LENGTH; |
|
|
|
if (spi_write_dt(&config->spi, &tx)) { |
|
LOG_ERR("%s: Failed to write memory", dev->name); |
|
return; |
|
} |
|
} |
|
|
|
if (num_remaining > 0) { |
|
tx_buf[1].buf = data_buffer; |
|
tx_buf[1].len = num_remaining; |
|
|
|
if (spi_write_dt(&config->spi, &tx)) { |
|
LOG_ERR("%s: Failed to write memory", dev->name); |
|
} |
|
} |
|
} |
|
|
|
static void eth_enc28j60_read_mem(const struct device *dev, |
|
uint8_t *data_buffer, |
|
uint16_t buf_len) |
|
{ |
|
const struct eth_enc28j60_config *config = dev->config; |
|
uint8_t buf[1] = { ENC28J60_SPI_RBM }; |
|
const struct spi_buf tx_buf = { |
|
.buf = buf, |
|
.len = 1 |
|
}; |
|
const struct spi_buf_set tx = { |
|
.buffers = &tx_buf, |
|
.count = 1 |
|
}; |
|
struct spi_buf rx_buf[2] = { |
|
{ |
|
.buf = NULL, |
|
.len = 1 |
|
}, |
|
}; |
|
const struct spi_buf_set rx = { |
|
.buffers = rx_buf, |
|
.count = 2 |
|
}; |
|
uint16_t num_segments; |
|
uint16_t num_remaining; |
|
int i; |
|
|
|
num_segments = buf_len / MAX_BUFFER_LENGTH; |
|
num_remaining = buf_len - MAX_BUFFER_LENGTH * num_segments; |
|
|
|
for (i = 0; i < num_segments; i++, data_buffer += MAX_BUFFER_LENGTH) { |
|
|
|
rx_buf[1].buf = data_buffer; |
|
rx_buf[1].len = MAX_BUFFER_LENGTH; |
|
|
|
if (spi_transceive_dt(&config->spi, &tx, &rx)) { |
|
LOG_ERR("%s: Failed to read memory", dev->name); |
|
return; |
|
} |
|
} |
|
|
|
if (num_remaining > 0) { |
|
rx_buf[1].buf = data_buffer; |
|
rx_buf[1].len = num_remaining; |
|
|
|
if (spi_transceive_dt(&config->spi, &tx, &rx)) { |
|
LOG_ERR("%s: Failed to read memory", dev->name); |
|
} |
|
} |
|
} |
|
|
|
static void eth_enc28j60_write_phy(const struct device *dev, |
|
uint16_t reg_addr, |
|
int16_t data) |
|
{ |
|
uint8_t data_mistat; |
|
|
|
eth_enc28j60_set_bank(dev, ENC28J60_REG_MIREGADR); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MIREGADR, reg_addr); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MIWRL, data & 0xFF); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MIWRH, data >> 8); |
|
eth_enc28j60_set_bank(dev, ENC28J60_REG_MISTAT); |
|
|
|
do { |
|
/* wait 10.24 useconds */ |
|
k_busy_wait(D10D24S); |
|
eth_enc28j60_read_reg(dev, ENC28J60_REG_MISTAT, |
|
&data_mistat); |
|
} while ((data_mistat & ENC28J60_BIT_MISTAT_BUSY)); |
|
} |
|
|
|
static void eth_enc28j60_read_phy(const struct device *dev, |
|
uint16_t reg_addr, |
|
int16_t *data) |
|
{ |
|
uint8_t data_mistat; |
|
uint8_t lsb; |
|
uint8_t msb; |
|
|
|
eth_enc28j60_set_bank(dev, ENC28J60_REG_MIREGADR); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MIREGADR, reg_addr); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MICMD, |
|
ENC28J60_BIT_MICMD_MIIRD); |
|
eth_enc28j60_set_bank(dev, ENC28J60_REG_MISTAT); |
|
|
|
do { |
|
/* wait 10.24 useconds */ |
|
k_busy_wait(D10D24S); |
|
eth_enc28j60_read_reg(dev, ENC28J60_REG_MISTAT, |
|
&data_mistat); |
|
} while ((data_mistat & ENC28J60_BIT_MISTAT_BUSY)); |
|
|
|
eth_enc28j60_set_bank(dev, ENC28J60_REG_MIREGADR); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MICMD, 0x0); |
|
eth_enc28j60_read_reg(dev, ENC28J60_REG_MIRDL, &lsb); |
|
eth_enc28j60_read_reg(dev, ENC28J60_REG_MIRDH, &msb); |
|
|
|
*data = (msb << 8) | lsb; |
|
} |
|
|
|
static void eth_enc28j60_gpio_callback(const struct device *dev, |
|
struct gpio_callback *cb, |
|
uint32_t pins) |
|
{ |
|
struct eth_enc28j60_runtime *context = |
|
CONTAINER_OF(cb, struct eth_enc28j60_runtime, gpio_cb); |
|
|
|
k_sem_give(&context->int_sem); |
|
} |
|
|
|
static int eth_enc28j60_init_buffers(const struct device *dev) |
|
{ |
|
uint8_t data_estat; |
|
const struct eth_enc28j60_config *config = dev->config; |
|
|
|
/* Reception buffers initialization */ |
|
eth_enc28j60_set_bank(dev, ENC28J60_REG_ERXSTL); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ERXSTL, |
|
ENC28J60_RXSTART & 0xFF); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ERXSTH, |
|
ENC28J60_RXSTART >> 8); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ERXRDPTL, |
|
ENC28J60_RXSTART & 0xFF); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ERXRDPTH, |
|
ENC28J60_RXSTART >> 8); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ERXNDL, |
|
ENC28J60_RXEND & 0xFF); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ERXNDH, |
|
ENC28J60_RXEND >> 8); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ETXSTL, |
|
ENC28J60_TXSTART & 0xFF); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ETXSTH, |
|
ENC28J60_TXSTART >> 8); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ETXNDL, |
|
ENC28J60_TXEND & 0xFF); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ETXNDH, |
|
ENC28J60_TXEND >> 8); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ERDPTL, |
|
ENC28J60_RXSTART & 0xFF); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ERDPTH, |
|
ENC28J60_RXSTART >> 8); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_EWRPTL, |
|
ENC28J60_TXSTART & 0xFF); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_EWRPTH, |
|
ENC28J60_TXSTART >> 8); |
|
|
|
eth_enc28j60_set_bank(dev, ENC28J60_REG_ERXFCON); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ERXFCON, |
|
config->hw_rx_filter); |
|
|
|
/* Waiting for OST */ |
|
/* 32 bits for this timer should be fine, rollover not an issue with initialisation */ |
|
uint32_t start_wait = (uint32_t) k_uptime_get(); |
|
do { |
|
/* If the CLK isn't ready don't wait forever */ |
|
if ((k_uptime_get_32() - start_wait) > CONFIG_ETH_ENC28J60_CLKRDY_INIT_WAIT_MS) { |
|
LOG_ERR("OST wait timed out"); |
|
return -ETIMEDOUT; |
|
} |
|
/* wait 10.24 useconds */ |
|
k_busy_wait(D10D24S); |
|
eth_enc28j60_read_reg(dev, ENC28J60_REG_ESTAT, &data_estat); |
|
} while (!(data_estat & ENC28J60_BIT_ESTAT_CLKRDY)); |
|
|
|
return 0; |
|
} |
|
|
|
static void eth_enc28j60_init_mac(const struct device *dev) |
|
{ |
|
const struct eth_enc28j60_config *config = dev->config; |
|
struct eth_enc28j60_runtime *context = dev->data; |
|
uint8_t data_macon; |
|
|
|
eth_enc28j60_set_bank(dev, ENC28J60_REG_MACON1); |
|
|
|
/* Set MARXEN to enable MAC to receive frames */ |
|
eth_enc28j60_read_reg(dev, ENC28J60_REG_MACON1, &data_macon); |
|
data_macon |= ENC28J60_BIT_MACON1_MARXEN | ENC28J60_BIT_MACON1_RXPAUS |
|
| ENC28J60_BIT_MACON1_TXPAUS; |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MACON1, data_macon); |
|
|
|
data_macon = ENC28J60_MAC_CONFIG; |
|
|
|
if (config->full_duplex) { |
|
data_macon |= ENC28J60_BIT_MACON3_FULDPX; |
|
} |
|
|
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MACON3, data_macon); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MAIPGL, ENC28J60_MAC_NBBIPGL); |
|
|
|
if (config->full_duplex) { |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MAIPGH, |
|
ENC28J60_MAC_NBBIPGH); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MABBIPG, |
|
ENC28J60_MAC_BBIPG_FD); |
|
} else { |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MABBIPG, |
|
ENC28J60_MAC_BBIPG_HD); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MACON4, 1 << 6); |
|
} |
|
|
|
/* Configure MAC address */ |
|
eth_enc28j60_set_bank(dev, ENC28J60_REG_MAADR1); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MAADR6, |
|
context->mac_address[5]); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MAADR5, |
|
context->mac_address[4]); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MAADR4, |
|
context->mac_address[3]); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MAADR3, |
|
context->mac_address[2]); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MAADR2, |
|
context->mac_address[1]); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_MAADR1, |
|
context->mac_address[0]); |
|
} |
|
|
|
static void eth_enc28j60_init_phy(const struct device *dev) |
|
{ |
|
const struct eth_enc28j60_config *config = dev->config; |
|
|
|
if (config->full_duplex) { |
|
eth_enc28j60_write_phy(dev, ENC28J60_PHY_PHCON1, |
|
ENC28J60_BIT_PHCON1_PDPXMD); |
|
eth_enc28j60_write_phy(dev, ENC28J60_PHY_PHCON2, 0x0); |
|
} else { |
|
eth_enc28j60_write_phy(dev, ENC28J60_PHY_PHCON1, 0x0); |
|
eth_enc28j60_write_phy(dev, ENC28J60_PHY_PHCON2, |
|
ENC28J60_BIT_PHCON2_HDLDIS); |
|
} |
|
} |
|
|
|
static struct net_if *get_iface(struct eth_enc28j60_runtime *ctx) |
|
{ |
|
return ctx->iface; |
|
} |
|
|
|
static int eth_enc28j60_tx(const struct device *dev, struct net_pkt *pkt) |
|
{ |
|
struct eth_enc28j60_runtime *context = dev->data; |
|
uint16_t tx_bufaddr = ENC28J60_TXSTART; |
|
uint16_t len = net_pkt_get_len(pkt); |
|
uint8_t per_packet_control; |
|
uint16_t tx_bufaddr_end; |
|
struct net_buf *frag; |
|
uint8_t tx_end; |
|
|
|
LOG_DBG("%s: pkt %p (len %u)", dev->name, pkt, len); |
|
|
|
k_sem_take(&context->tx_rx_sem, K_FOREVER); |
|
|
|
/* Latest errata sheet: DS80349C |
|
* always reset transmit logic (Errata Issue 12) |
|
* the Microchip TCP/IP stack implementation used to first check |
|
* whether TXERIF is set and only then reset the transmit logic |
|
* but this has been changed in later versions; possibly they |
|
* have a reason for this; they don't mention this in the errata |
|
* sheet |
|
*/ |
|
eth_enc28j60_set_eth_reg(dev, ENC28J60_REG_ECON1, |
|
ENC28J60_BIT_ECON1_TXRST); |
|
eth_enc28j60_clear_eth_reg(dev, ENC28J60_REG_ECON1, |
|
ENC28J60_BIT_ECON1_TXRST); |
|
|
|
/* Write the buffer content into the transmission buffer */ |
|
eth_enc28j60_set_bank(dev, ENC28J60_REG_ETXSTL); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_EWRPTL, tx_bufaddr & 0xFF); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_EWRPTH, tx_bufaddr >> 8); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ETXSTL, tx_bufaddr & 0xFF); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ETXSTH, tx_bufaddr >> 8); |
|
|
|
/* Write the data into the buffer */ |
|
per_packet_control = ENC28J60_PPCTL_BYTE; |
|
eth_enc28j60_write_mem(dev, &per_packet_control, 1); |
|
|
|
for (frag = pkt->frags; frag; frag = frag->frags) { |
|
eth_enc28j60_write_mem(dev, frag->data, frag->len); |
|
} |
|
|
|
tx_bufaddr_end = tx_bufaddr + len; |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ETXNDL, |
|
tx_bufaddr_end & 0xFF); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ETXNDH, tx_bufaddr_end >> 8); |
|
|
|
/* Signal ENC28J60 to send the buffer */ |
|
eth_enc28j60_set_eth_reg(dev, ENC28J60_REG_ECON1, |
|
ENC28J60_BIT_ECON1_TXRTS); |
|
|
|
do { |
|
/* wait 10.24 useconds */ |
|
k_busy_wait(D10D24S); |
|
eth_enc28j60_read_reg(dev, ENC28J60_REG_EIR, &tx_end); |
|
tx_end &= ENC28J60_BIT_EIR_TXIF; |
|
} while (!tx_end); |
|
|
|
|
|
eth_enc28j60_read_reg(dev, ENC28J60_REG_ESTAT, &tx_end); |
|
|
|
k_sem_give(&context->tx_rx_sem); |
|
|
|
if (tx_end & ENC28J60_BIT_ESTAT_TXABRT) { |
|
LOG_ERR("%s: TX failed!", dev->name); |
|
|
|
/* 12.1.3 "TRANSMIT ERROR INTERRUPT FLAG (TXERIF)" states: |
|
* |
|
* "After determining the problem and solution, the |
|
* host controller should clear the LATECOL (if set) and |
|
* TXABRT bits so that future aborts can be detected |
|
* accurately." |
|
*/ |
|
eth_enc28j60_clear_eth_reg(dev, ENC28J60_REG_ESTAT, |
|
ENC28J60_BIT_ESTAT_TXABRT |
|
| ENC28J60_BIT_ESTAT_LATECOL); |
|
|
|
return -EIO; |
|
} |
|
|
|
LOG_DBG("%s: Tx successful", dev->name); |
|
|
|
return 0; |
|
} |
|
|
|
static void enc28j60_read_packet(const struct device *dev, uint16_t frm_len) |
|
{ |
|
const struct eth_enc28j60_config *config = dev->config; |
|
struct eth_enc28j60_runtime *context = dev->data; |
|
struct net_buf *pkt_buf; |
|
struct net_pkt *pkt; |
|
uint16_t lengthfr; |
|
uint8_t dummy[4]; |
|
|
|
/* Get the frame from the buffer */ |
|
pkt = net_pkt_rx_alloc_with_buffer(get_iface(context), frm_len, |
|
AF_UNSPEC, 0, K_MSEC(config->timeout)); |
|
if (!pkt) { |
|
LOG_ERR("%s: Could not allocate rx buffer", dev->name); |
|
eth_stats_update_errors_rx(get_iface(context)); |
|
return; |
|
} |
|
|
|
pkt_buf = pkt->buffer; |
|
lengthfr = frm_len; |
|
|
|
do { |
|
size_t frag_len; |
|
uint8_t *data_ptr; |
|
size_t spi_frame_len; |
|
|
|
data_ptr = pkt_buf->data; |
|
|
|
/* Review the space available for the new frag */ |
|
frag_len = net_buf_tailroom(pkt_buf); |
|
|
|
if (frm_len > frag_len) { |
|
spi_frame_len = frag_len; |
|
} else { |
|
spi_frame_len = frm_len; |
|
} |
|
|
|
eth_enc28j60_read_mem(dev, data_ptr, spi_frame_len); |
|
|
|
net_buf_add(pkt_buf, spi_frame_len); |
|
|
|
/* One fragment has been written via SPI */ |
|
frm_len -= spi_frame_len; |
|
pkt_buf = pkt_buf->frags; |
|
} while (frm_len > 0); |
|
|
|
/* Let's pop the useless CRC */ |
|
eth_enc28j60_read_mem(dev, dummy, 4); |
|
|
|
/* Pops one padding byte from spi circular buffer |
|
* introduced by the device when the frame length is odd |
|
*/ |
|
if (lengthfr & 0x01) { |
|
eth_enc28j60_read_mem(dev, dummy, 1); |
|
} |
|
|
|
net_pkt_set_iface(pkt, context->iface); |
|
|
|
/* Feed buffer frame to IP stack */ |
|
LOG_DBG("%s: Received packet of length %u", dev->name, lengthfr); |
|
if (net_recv_data(net_pkt_iface(pkt), pkt) < 0) { |
|
net_pkt_unref(pkt); |
|
} |
|
} |
|
|
|
static int eth_enc28j60_rx(const struct device *dev) |
|
{ |
|
struct eth_enc28j60_runtime *context = dev->data; |
|
uint8_t counter; |
|
|
|
/* Errata 6. The Receive Packet Pending Interrupt Flag (EIR.PKTIF) |
|
* does not reliably/accurately report the status of pending packet. |
|
* Use EPKTCNT register instead. |
|
*/ |
|
eth_enc28j60_set_bank(dev, ENC28J60_REG_EPKTCNT); |
|
eth_enc28j60_read_reg(dev, ENC28J60_REG_EPKTCNT, &counter); |
|
if (!counter) { |
|
return 0; |
|
} |
|
|
|
k_sem_take(&context->tx_rx_sem, K_FOREVER); |
|
|
|
do { |
|
uint16_t frm_len = 0U; |
|
uint8_t info[RSV_SIZE]; |
|
uint16_t next_packet; |
|
uint8_t rdptl = 0U; |
|
uint8_t rdpth = 0U; |
|
|
|
/* remove read fifo address to packet header address */ |
|
eth_enc28j60_set_bank(dev, ENC28J60_REG_ERXRDPTL); |
|
eth_enc28j60_read_reg(dev, ENC28J60_REG_ERXRDPTL, &rdptl); |
|
eth_enc28j60_read_reg(dev, ENC28J60_REG_ERXRDPTH, &rdpth); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ERDPTL, rdptl); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ERDPTH, rdpth); |
|
|
|
/* Read address for next packet */ |
|
eth_enc28j60_read_mem(dev, info, 2); |
|
next_packet = info[0] | (uint16_t)info[1] << 8; |
|
|
|
/* Errata 14. Even values in ERXRDPT |
|
* may corrupt receive buffer. |
|
No need adjust next packet |
|
if (next_packet == 0) { |
|
next_packet = ENC28J60_RXEND; |
|
} else if (!(next_packet & 0x01)) { |
|
next_packet--; |
|
}*/ |
|
|
|
/* Read reception status vector */ |
|
eth_enc28j60_read_mem(dev, info, 4); |
|
|
|
/* Get the frame length from the rx status vector, |
|
* minus CRC size at the end which is always present |
|
*/ |
|
frm_len = sys_get_le16(info) - 4; |
|
|
|
enc28j60_read_packet(dev, frm_len); |
|
|
|
/* Free buffer memory and decrement rx counter */ |
|
eth_enc28j60_set_bank(dev, ENC28J60_REG_ERXRDPTL); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ERXRDPTL, |
|
next_packet & 0xFF); |
|
eth_enc28j60_write_reg(dev, ENC28J60_REG_ERXRDPTH, |
|
next_packet >> 8); |
|
eth_enc28j60_set_eth_reg(dev, ENC28J60_REG_ECON2, |
|
ENC28J60_BIT_ECON2_PKTDEC); |
|
|
|
/* Check if there are frames to clean from the buffer */ |
|
eth_enc28j60_set_bank(dev, ENC28J60_REG_EPKTCNT); |
|
eth_enc28j60_read_reg(dev, ENC28J60_REG_EPKTCNT, &counter); |
|
} while (counter); |
|
|
|
k_sem_give(&context->tx_rx_sem); |
|
|
|
/* Clear a potential Receive Error Interrupt Flag bit (RX buffer full). |
|
* PKTIF was automatically cleared in eth_enc28j60_rx() when EPKTCNT |
|
* reached zero, so no need to clear it. |
|
*/ |
|
eth_enc28j60_clear_eth_reg(dev, ENC28J60_REG_EIR, |
|
ENC28J60_BIT_EIR_RXERIF); |
|
|
|
return 0; |
|
} |
|
|
|
static void eth_enc28j60_rx_thread(void *p1, void *p2, void *p3) |
|
{ |
|
ARG_UNUSED(p2); |
|
ARG_UNUSED(p3); |
|
|
|
const struct device *dev = p1; |
|
struct eth_enc28j60_runtime *context = dev->data; |
|
uint8_t int_stat; |
|
|
|
while (true) { |
|
k_sem_take(&context->int_sem, K_FOREVER); |
|
|
|
/* Disable interrupts during processing, otherwise there's a small race |
|
* window where we can miss one! |
|
*/ |
|
eth_enc28j60_clear_eth_reg(dev, ENC28J60_REG_EIE, ENC28J60_BIT_EIE_INTIE); |
|
|
|
eth_enc28j60_read_reg(dev, ENC28J60_REG_EIR, &int_stat); |
|
if (int_stat & ENC28J60_BIT_EIR_LINKIF) { |
|
uint16_t phir; |
|
uint16_t phstat2; |
|
/* Clear link change interrupt flag by PHIR reg read */ |
|
eth_enc28j60_read_phy(dev, ENC28J60_PHY_PHIR, &phir); |
|
eth_enc28j60_read_phy(dev, ENC28J60_PHY_PHSTAT2, &phstat2); |
|
if (phstat2 & ENC28J60_BIT_PHSTAT2_LSTAT) { |
|
LOG_INF("%s: Link up", dev->name); |
|
/* We may have been interrupted before L2 init complete |
|
* If so flag that the carrier should be set on in init |
|
*/ |
|
if (context->iface_initialized) { |
|
net_eth_carrier_on(context->iface); |
|
} else { |
|
context->iface_carrier_on_init = true; |
|
} |
|
} else { |
|
LOG_INF("%s: Link down", dev->name); |
|
|
|
if (context->iface_initialized) { |
|
net_eth_carrier_off(context->iface); |
|
} |
|
} |
|
} |
|
|
|
/* We cannot rely on the PKTIF flag because of errata 6. Call |
|
* eth_enc28j60_rx() unconditionally. It will check EPKTCNT instead. |
|
*/ |
|
eth_enc28j60_rx(dev); |
|
|
|
/* Now that the IRQ line was released, enable interrupts back */ |
|
eth_enc28j60_set_eth_reg(dev, ENC28J60_REG_EIE, ENC28J60_BIT_EIE_INTIE); |
|
} |
|
} |
|
|
|
static enum ethernet_hw_caps eth_enc28j60_get_capabilities(const struct device *dev) |
|
{ |
|
ARG_UNUSED(dev); |
|
|
|
return ETHERNET_LINK_10BASE |
|
#if defined(CONFIG_NET_VLAN) |
|
| ETHERNET_HW_VLAN |
|
#endif |
|
; |
|
} |
|
|
|
static int eth_enc28j60_set_config(const struct device *dev, |
|
enum ethernet_config_type type, |
|
const struct ethernet_config *config) |
|
{ |
|
struct eth_enc28j60_runtime *context = dev->data; |
|
|
|
/* Compile time check that the memcpy below won't overflow */ |
|
BUILD_ASSERT(sizeof(context->mac_address) <= sizeof(config->mac_address.addr), |
|
"ENC28j60 Runtime MAC address buffer too small"); |
|
|
|
if (type == ETHERNET_CONFIG_TYPE_MAC_ADDRESS) { |
|
memcpy(context->mac_address, config->mac_address.addr, |
|
sizeof(config->mac_address.addr)); |
|
eth_enc28j60_init_mac(dev); |
|
|
|
if (context->iface != NULL) { |
|
net_if_set_link_addr(context->iface, context->mac_address, |
|
sizeof(context->mac_address), |
|
NET_LINK_ETHERNET); |
|
} |
|
|
|
LOG_INF("Set cfg - MAC %02x:%02x:%02x:%02x:%02x:%02x", |
|
context->mac_address[0], context->mac_address[1], |
|
context->mac_address[2], context->mac_address[3], |
|
context->mac_address[4], context->mac_address[5]); |
|
|
|
return 0; |
|
} |
|
|
|
/* Only mac address config supported */ |
|
return -ENOTSUP; |
|
} |
|
|
|
static void eth_enc28j60_iface_init(struct net_if *iface) |
|
{ |
|
const struct device *dev = net_if_get_device(iface); |
|
struct eth_enc28j60_runtime *context = dev->data; |
|
|
|
net_if_set_link_addr(iface, context->mac_address, |
|
sizeof(context->mac_address), |
|
NET_LINK_ETHERNET); |
|
|
|
if (context->iface == NULL) { |
|
context->iface = iface; |
|
} |
|
|
|
ethernet_init(iface); |
|
|
|
/* The device may have already interrupted us to flag link UP */ |
|
if (context->iface_carrier_on_init) { |
|
net_if_carrier_on(iface); |
|
} else { |
|
net_if_carrier_off(iface); |
|
} |
|
context->iface_initialized = true; |
|
} |
|
|
|
static const struct ethernet_api api_funcs = { |
|
.iface_api.init = eth_enc28j60_iface_init, |
|
.set_config = eth_enc28j60_set_config, |
|
.get_capabilities = eth_enc28j60_get_capabilities, |
|
.send = eth_enc28j60_tx, |
|
}; |
|
|
|
static int eth_enc28j60_init(const struct device *dev) |
|
{ |
|
const struct eth_enc28j60_config *config = dev->config; |
|
struct eth_enc28j60_runtime *context = dev->data; |
|
|
|
/* SPI config */ |
|
if (!spi_is_ready_dt(&config->spi)) { |
|
LOG_ERR("%s: SPI master port %s not ready", dev->name, config->spi.bus->name); |
|
return -EINVAL; |
|
} |
|
|
|
/* Initialize GPIO */ |
|
if (!gpio_is_ready_dt(&config->interrupt)) { |
|
LOG_ERR("%s: GPIO port %s not ready", dev->name, config->interrupt.port->name); |
|
return -EINVAL; |
|
} |
|
|
|
if (gpio_pin_configure_dt(&config->interrupt, GPIO_INPUT)) { |
|
LOG_ERR("%s: Unable to configure GPIO pin %u", dev->name, config->interrupt.pin); |
|
return -EINVAL; |
|
} |
|
|
|
gpio_init_callback(&(context->gpio_cb), eth_enc28j60_gpio_callback, |
|
BIT(config->interrupt.pin)); |
|
|
|
if (gpio_add_callback(config->interrupt.port, &(context->gpio_cb))) { |
|
return -EINVAL; |
|
} |
|
|
|
gpio_pin_interrupt_configure_dt(&config->interrupt, |
|
GPIO_INT_EDGE_TO_ACTIVE); |
|
|
|
if (eth_enc28j60_soft_reset(dev)) { |
|
LOG_ERR("%s: Soft-reset failed", dev->name); |
|
return -EIO; |
|
} |
|
|
|
/* Errata B7/1 */ |
|
k_busy_wait(D10D24S); |
|
|
|
/* Apply a random MAC address if requested in DT */ |
|
if (config->random_mac) { |
|
gen_random_mac(context->mac_address, MICROCHIP_OUI_B0, MICROCHIP_OUI_B1, |
|
MICROCHIP_OUI_B2); |
|
LOG_INF("Random MAC Addr %02x:%02x:%02x:%02x:%02x:%02x", context->mac_address[0], |
|
context->mac_address[1], context->mac_address[2], context->mac_address[3], |
|
context->mac_address[4], context->mac_address[5]); |
|
} else { |
|
/* Assign octets not previously taken from devicetree */ |
|
context->mac_address[0] = MICROCHIP_OUI_B0; |
|
context->mac_address[1] = MICROCHIP_OUI_B1; |
|
context->mac_address[2] = MICROCHIP_OUI_B2; |
|
} |
|
|
|
if (eth_enc28j60_init_buffers(dev)) { |
|
return -ETIMEDOUT; |
|
} |
|
eth_enc28j60_init_mac(dev); |
|
eth_enc28j60_init_phy(dev); |
|
|
|
/* Enable interruptions */ |
|
eth_enc28j60_set_eth_reg(dev, ENC28J60_REG_EIE, ENC28J60_BIT_EIE_INTIE); |
|
eth_enc28j60_set_eth_reg(dev, ENC28J60_REG_EIE, ENC28J60_BIT_EIE_PKTIE); |
|
eth_enc28j60_set_eth_reg(dev, ENC28J60_REG_EIE, ENC28J60_BIT_EIE_LINKIE); |
|
eth_enc28j60_write_phy(dev, ENC28J60_PHY_PHIE, ENC28J60_BIT_PHIE_PGEIE | |
|
ENC28J60_BIT_PHIE_PLNKIE); |
|
|
|
/* Enable Reception */ |
|
eth_enc28j60_set_eth_reg(dev, ENC28J60_REG_ECON1, |
|
ENC28J60_BIT_ECON1_RXEN); |
|
|
|
/* Start interruption-poll thread */ |
|
k_thread_create(&context->thread, context->thread_stack, |
|
CONFIG_ETH_ENC28J60_RX_THREAD_STACK_SIZE, |
|
eth_enc28j60_rx_thread, |
|
(void *)dev, NULL, NULL, |
|
K_PRIO_COOP(CONFIG_ETH_ENC28J60_RX_THREAD_PRIO), |
|
0, K_NO_WAIT); |
|
|
|
LOG_INF("%s: Initialized", dev->name); |
|
|
|
return 0; |
|
} |
|
|
|
#define ENC28J60_DEFINE(inst) \ |
|
static struct eth_enc28j60_runtime eth_enc28j60_runtime_##inst = { \ |
|
.mac_address = DT_INST_PROP(inst, local_mac_address), \ |
|
.tx_rx_sem = \ |
|
Z_SEM_INITIALIZER((eth_enc28j60_runtime_##inst).tx_rx_sem, 1, UINT_MAX), \ |
|
.int_sem = Z_SEM_INITIALIZER((eth_enc28j60_runtime_##inst).int_sem, 0, UINT_MAX), \ |
|
}; \ |
|
\ |
|
static const struct eth_enc28j60_config eth_enc28j60_config_##inst = { \ |
|
.spi = SPI_DT_SPEC_INST_GET(inst, SPI_WORD_SET(8), 0), \ |
|
.interrupt = GPIO_DT_SPEC_INST_GET(inst, int_gpios), \ |
|
.full_duplex = DT_INST_PROP(0, full_duplex), \ |
|
.timeout = CONFIG_ETH_ENC28J60_TIMEOUT, \ |
|
.hw_rx_filter = DT_INST_PROP_OR(inst, hw_rx_filter, ENC28J60_RECEIVE_FILTERS), \ |
|
.random_mac = DT_INST_PROP(inst, zephyr_random_mac_address), \ |
|
}; \ |
|
\ |
|
ETH_NET_DEVICE_DT_INST_DEFINE(inst, eth_enc28j60_init, NULL, ð_enc28j60_runtime_##inst, \ |
|
ð_enc28j60_config_##inst, CONFIG_ETH_INIT_PRIORITY, \ |
|
&api_funcs, NET_ETH_MTU); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(ENC28J60_DEFINE);
|
|
|