drivers: ethernet: add adin2111
Adds initial ADIN2111 2-Port 10BASE-T1L (SPE) switch support. Works over SPI. The driver creates 2 interfaces, 1 per port (PHY). Configures multicast and broadcast filters. The same unicast is applied to both ports. Supports: - Link state detection - CRC enable/disable - Ports config set - Ports ETH stats Provides functions for MDIO driver. Signed-off-by: Georgij Cernysiov <geo.cgv@gmail.com>
This commit is contained in:
parent
476a5f57fd
commit
9a15d72b32
|
@ -285,6 +285,7 @@
|
|||
/drivers/ethernet/*w5500* @parthitce
|
||||
/drivers/ethernet/*xlnx_gem* @ibirnbaum
|
||||
/drivers/ethernet/*smsc91x* @sgrrzhf
|
||||
/drivers/ethernet/*adin2111* @GeorgeCGV
|
||||
/drivers/ethernet/phy/ @rlubos @tbursztyka @arvinf
|
||||
/drivers/mdio/ @rlubos @tbursztyka @arvinf
|
||||
/drivers/flash/ @nashif @de-nordic
|
||||
|
|
|
@ -32,6 +32,7 @@ zephyr_library_sources_ifdef(CONFIG_ETH_CYCLONEV eth_cyclonev.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_SLIP_TAP eth_slip_tap.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_ETH_SMSC91X eth_smsc91x.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_ETH_IVSHMEM eth_ivshmem.c eth_ivshmem_queue.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_ETH_ADIN2111 eth_adin2111.c)
|
||||
|
||||
if(CONFIG_ETH_NXP_S32_NETC)
|
||||
zephyr_library_sources(eth_nxp_s32_netc.c)
|
||||
|
|
|
@ -59,6 +59,7 @@ source "drivers/ethernet/Kconfig.cyclonev"
|
|||
source "drivers/ethernet/Kconfig.nxp_s32"
|
||||
source "drivers/ethernet/Kconfig.smsc91x"
|
||||
source "drivers/ethernet/Kconfig.ivshmem"
|
||||
source "drivers/ethernet/Kconfig.adin2111"
|
||||
|
||||
source "drivers/ethernet/phy/Kconfig"
|
||||
|
||||
|
|
67
drivers/ethernet/Kconfig.adin2111
Normal file
67
drivers/ethernet/Kconfig.adin2111
Normal file
|
@ -0,0 +1,67 @@
|
|||
# Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
menuconfig ETH_ADIN2111
|
||||
bool "ADIN2111 2-port 10BASE-T1L Controller"
|
||||
default y
|
||||
depends on DT_HAS_ADI_ADIN2111_ENABLED
|
||||
select SPI
|
||||
select MDIO
|
||||
help
|
||||
The ADIN2111 is a low power, 2-port 10BASE-T1L transceiver
|
||||
designed for industrial Ethernet applications, and is compliant with
|
||||
the IEEE® 802.3cg-2019™ Ethernet standard for long reach, 10
|
||||
Mbps single pair Ethernet (SPE).
|
||||
|
||||
Featuring an integrated media access control (MAC) and a switch,
|
||||
the ADIN2111 enables direct connectivity with a variety of controllers
|
||||
via a serial peripheral inter-face (SPI).
|
||||
|
||||
if ETH_ADIN2111
|
||||
|
||||
config ETH_ADIN2111_INIT_PRIORITY
|
||||
int "ADIN2111 driver init priority"
|
||||
default 72
|
||||
help
|
||||
ADIN2111 device driver initialization priority.
|
||||
Must be initialized after SPI, but before MDIO
|
||||
and ports.
|
||||
|
||||
Both ports use ETH_INIT_PRIORITY initialization priority.
|
||||
|
||||
config ETH_ADIN2111_IRQ_THREAD_STACK_SIZE
|
||||
int "Stack size for a thread that processes ADIN IRQ"
|
||||
default 2048
|
||||
help
|
||||
Size of the stack used for internal thread which is ran to
|
||||
process raised INT IRQ.
|
||||
|
||||
config ETH_ADIN2111_IRQ_THREAD_PRIO
|
||||
int "Priority for internal incoming packet handler"
|
||||
default 2
|
||||
help
|
||||
Priority level for internal thread which is ran for ADIN
|
||||
INT IRQ processing.
|
||||
|
||||
config ETH_ADIN2111_TIMEOUT
|
||||
int "IP buffer timeout"
|
||||
default 100
|
||||
help
|
||||
Given timeout in milliseconds. Maximum amount of time
|
||||
that the driver will wait from the IP stack to get
|
||||
a memory buffer before the Ethernet frame is dropped.
|
||||
|
||||
config ETH_ADIN2111_SPI_CFG0
|
||||
bool "SPI_CFG0"
|
||||
default y
|
||||
help
|
||||
Must be set when ADIN uses 8-bit CRC (Generic SPI)
|
||||
or Protection Mode (OPEN Alliance) on the SPI Host Interface.
|
||||
|
||||
config ETH_ADIN2111_BUFFER_SIZE
|
||||
int "Buffer size in bytes use for frame transmission"
|
||||
default 1524
|
||||
help
|
||||
Transmission and reception buffer size.
|
||||
|
||||
endif # ETH_ADIN2111
|
982
drivers/ethernet/eth_adin2111.c
Normal file
982
drivers/ethernet/eth_adin2111.c
Normal file
|
@ -0,0 +1,982 @@
|
|||
/*
|
||||
* Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(eth_adin2111, CONFIG_ETHERNET_LOG_LEVEL);
|
||||
|
||||
#define DT_DRV_COMPAT adi_adin2111
|
||||
|
||||
#include <zephyr/net/net_pkt.h>
|
||||
#include <zephyr/net/ethernet.h>
|
||||
#include <zephyr/net/phy.h>
|
||||
|
||||
#if CONFIG_ETH_ADIN2111_SPI_CFG0
|
||||
#include <zephyr/sys/crc.h>
|
||||
#endif /* CONFIG_ETH_ADIN2111_SPI_CFG0 */
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <zephyr/net/net_if.h>
|
||||
#include <zephyr/net/ethernet.h>
|
||||
#include <zephyr/net/phy.h>
|
||||
#include <zephyr/drivers/ethernet/eth_adin2111.h>
|
||||
|
||||
#include "phy/phy_adin2111_priv.h"
|
||||
#include "eth_adin2111_priv.h"
|
||||
|
||||
/* SPI Communication check retry delay */
|
||||
#define ADIN2111_DEV_AWAIT_DELAY_POLL_US 100U
|
||||
/* Number of retries SPI Communication check */
|
||||
#define ADIN2111_DEV_AWAIT_RETRY_COUNT 200U
|
||||
|
||||
/* ADIN RESETC check retry delay */
|
||||
#define ADIN2111_RESETC_AWAIT_DELAY_POLL_US 100U
|
||||
/* Number of retries for ADIN RESETC check */
|
||||
#define ADIN2111_RESETC_AWAIT_RETRY_COUNT 200U
|
||||
|
||||
/* Boot delay for clocks stabilisation (maximum 90ms) */
|
||||
#define ADIN2111_HW_BOOT_DELAY_MS 100
|
||||
|
||||
/* MAC Address Rule and DA Filter multicast slot/idx */
|
||||
#define ADIN2111_MULTICAST_ADDR_SLOT 0U
|
||||
/* MAC Address Rule and DA Filter broadcast slot/idx */
|
||||
#define ADIN2111_BROADCAST_ADDR_SLOT 1U
|
||||
/* MAC Address Rule and DA Filter Port 1 slot/idx */
|
||||
#define ADIN2111_UNICAST_P1_ADDR_SLOT 2U
|
||||
/* MAC Address Rule and DA Filter Port 2 slot/idx */
|
||||
#define ADIN2111_UNICAST_P2_ADDR_SLOT 3U
|
||||
|
||||
int eth_adin2111_lock(const struct device *dev, k_timeout_t timeout)
|
||||
{
|
||||
struct adin2111_data *ctx = dev->data;
|
||||
|
||||
return k_mutex_lock(&ctx->lock, timeout);
|
||||
}
|
||||
|
||||
int eth_adin2111_unlock(const struct device *dev)
|
||||
{
|
||||
struct adin2111_data *ctx = dev->data;
|
||||
|
||||
return k_mutex_unlock(&ctx->lock);
|
||||
}
|
||||
|
||||
int eth_adin2111_reg_write(const struct device *dev, const uint16_t reg,
|
||||
const uint32_t val)
|
||||
{
|
||||
const struct adin2111_config *cfg = dev->config;
|
||||
size_t header_size = ADIN2111_WRITE_HEADER_SIZE;
|
||||
size_t data_size = sizeof(uint32_t);
|
||||
#if CONFIG_ETH_ADIN2111_SPI_CFG0
|
||||
uint8_t buf[ADIN2111_REG_WRITE_BUF_SIZE_CRC] = { 0 };
|
||||
#else
|
||||
uint8_t buf[ADIN2111_REG_WRITE_BUF_SIZE] = { 0 };
|
||||
#endif /* CONFIG_ETH_ADIN2111_SPI_CFG0 */
|
||||
|
||||
/* spi header */
|
||||
*(uint16_t *)buf = htons((ADIN2111_WRITE_TXN_CTRL | reg));
|
||||
#if CONFIG_ETH_ADIN2111_SPI_CFG0
|
||||
buf[2] = crc8_ccitt(0, buf, header_size);
|
||||
++header_size;
|
||||
#endif /* CONFIG_ETH_ADIN2111_SPI_CFG0 */
|
||||
|
||||
/* reg */
|
||||
*(uint32_t *)(buf + header_size) = htonl(val);
|
||||
#if CONFIG_ETH_ADIN2111_SPI_CFG0
|
||||
buf[header_size + data_size] = crc8_ccitt(0, &buf[header_size], data_size);
|
||||
++data_size;
|
||||
#endif /* CONFIG_ETH_ADIN2111_SPI_CFG0 */
|
||||
|
||||
const struct spi_buf spi_tx_buf = {
|
||||
.buf = buf,
|
||||
.len = header_size + data_size
|
||||
};
|
||||
const struct spi_buf_set tx = { .buffers = &spi_tx_buf, .count = 1U };
|
||||
|
||||
return spi_write_dt(&cfg->spi, &tx);
|
||||
}
|
||||
|
||||
int eth_adin2111_reg_read(const struct device *dev, const uint16_t reg,
|
||||
uint32_t *val)
|
||||
{
|
||||
const struct adin2111_config *cfg = dev->config;
|
||||
size_t header_len = ADIN2111_READ_HEADER_SIZE;
|
||||
size_t read_len = sizeof(uint32_t);
|
||||
int ret;
|
||||
#if CONFIG_ETH_ADIN2111_SPI_CFG0
|
||||
uint8_t rcv_crc;
|
||||
uint8_t comp_crc;
|
||||
uint8_t buf[ADIN2111_REG_READ_BUF_SIZE_CRC] = { 0 };
|
||||
#else
|
||||
uint8_t buf[ADIN2111_REG_READ_BUF_SIZE] = { 0 };
|
||||
#endif /* CONFIG_ETH_ADIN2111_SPI_CFG0 */
|
||||
|
||||
/* spi header */
|
||||
*(uint16_t *)buf = htons((ADIN2111_READ_TXN_CTRL | reg));
|
||||
#if CONFIG_ETH_ADIN2111_SPI_CFG0
|
||||
buf[2] = crc8_ccitt(0, buf, ADIN2111_SPI_HEADER_SIZE);
|
||||
/* TA */
|
||||
buf[3] = 0U;
|
||||
++header_len;
|
||||
++read_len;
|
||||
#else
|
||||
/* TA */
|
||||
buf[2] = 0U;
|
||||
#endif /* CONFIG_ETH_ADIN2111_SPI_CFG0 */
|
||||
|
||||
const struct spi_buf tx_buf = { .buf = buf, .len = header_len + read_len };
|
||||
const struct spi_buf rx_buf = { .buf = buf, .len = header_len + read_len };
|
||||
const struct spi_buf_set tx = { .buffers = &tx_buf, .count = 1U };
|
||||
const struct spi_buf_set rx = { .buffers = &rx_buf, .count = 1U };
|
||||
|
||||
ret = spi_transceive_dt(&cfg->spi, &tx, &rx);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if CONFIG_ETH_ADIN2111_SPI_CFG0
|
||||
comp_crc = crc8_ccitt(0, &buf[header_len], sizeof(uint32_t));
|
||||
rcv_crc = buf[header_len + sizeof(uint32_t)];
|
||||
|
||||
if (rcv_crc != comp_crc) {
|
||||
/* invalid crc */
|
||||
return -EIO;
|
||||
}
|
||||
#endif /* CONFIG_ETH_ADIN2111_SPI_CFG0 */
|
||||
|
||||
*val = ntohl((*(uint32_t *)(&buf[header_len])));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adin2111_read_fifo(const struct device *dev, const uint8_t port)
|
||||
{
|
||||
const struct adin2111_config *cfg = dev->config;
|
||||
struct adin2111_data *ctx = dev->data;
|
||||
struct net_if *iface;
|
||||
struct net_pkt *pkt;
|
||||
size_t header_len = ADIN2111_READ_HEADER_SIZE;
|
||||
uint16_t fsize_reg = ((port == 0U) ? ADIN2111_P1_RX_FSIZE : ADIN2111_P2_RX_FSIZE);
|
||||
uint16_t rx_reg = ((port == 0U) ? ADIN2111_P1_RX : ADIN2111_P2_RX);
|
||||
uint32_t fsize;
|
||||
uint32_t fsize_real;
|
||||
uint32_t padding_len;
|
||||
#if CONFIG_ETH_ADIN2111_SPI_CFG0
|
||||
uint8_t cmd_buf[ADIN2111_FIFO_READ_CMD_BUF_SIZE_CRC] = { 0 };
|
||||
#else
|
||||
uint8_t cmd_buf[ADIN2111_FIFO_READ_CMD_BUF_SIZE] = { 0 };
|
||||
#endif /* CONFIG_ETH_ADIN2111_SPI_CFG0 */
|
||||
int ret;
|
||||
|
||||
iface = ((struct adin2111_port_data *)ctx->port[port]->data)->iface;
|
||||
|
||||
/* get received frame size in bytes */
|
||||
ret = eth_adin2111_reg_read(dev, fsize_reg, &fsize);
|
||||
if (ret < 0) {
|
||||
eth_stats_update_errors_rx(iface);
|
||||
LOG_ERR("Port %u failed to read RX FSIZE, %d", port, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* burst read must be in multiples of 4 */
|
||||
padding_len = ((fsize % 4) == 0) ? 0U : (ROUND_UP(fsize, 4U) - fsize);
|
||||
/* actual frame length is FSIZE - FRAME HEADER */
|
||||
fsize_real = fsize - ADIN2111_FRAME_HEADER_SIZE;
|
||||
|
||||
/* spi header */
|
||||
*(uint16_t *)cmd_buf = htons((ADIN2111_READ_TXN_CTRL | rx_reg));
|
||||
#if CONFIG_ETH_ADIN2111_SPI_CFG0
|
||||
cmd_buf[2] = crc8_ccitt(0, cmd_buf, ADIN2111_SPI_HEADER_SIZE);
|
||||
/* TA */
|
||||
cmd_buf[3] = 0U;
|
||||
++header_len;
|
||||
#else
|
||||
/* TA */
|
||||
cmd_buf[2] = 0U;
|
||||
#endif /* CONFIG_ETH_ADIN2111_SPI_CFG0 */
|
||||
|
||||
const struct spi_buf tx_buf = { .buf = cmd_buf, .len = sizeof(cmd_buf) };
|
||||
const struct spi_buf rx_buf[3] = {
|
||||
{.buf = NULL, .len = sizeof(cmd_buf) + ADIN2111_FRAME_HEADER_SIZE},
|
||||
{.buf = ctx->buf, .len = fsize_real},
|
||||
{.buf = NULL, .len = padding_len }
|
||||
};
|
||||
const struct spi_buf_set tx = { .buffers = &tx_buf, .count = 1U };
|
||||
const struct spi_buf_set rx = {
|
||||
.buffers = rx_buf,
|
||||
.count = ((padding_len == 0U) ? 2U : 3U)
|
||||
};
|
||||
|
||||
ret = spi_transceive_dt(&cfg->spi, &tx, &rx);
|
||||
if (ret < 0) {
|
||||
eth_stats_update_errors_rx(iface);
|
||||
LOG_ERR("Port %u failed to read RX FIFO, %d", port, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pkt = net_pkt_rx_alloc_with_buffer(iface, fsize_real, AF_UNSPEC, 0,
|
||||
K_MSEC(CONFIG_ETH_ADIN2111_TIMEOUT));
|
||||
if (!pkt) {
|
||||
eth_stats_update_errors_rx(iface);
|
||||
LOG_ERR("Port %u failed to alloc frame RX buffer, %u bytes",
|
||||
port, fsize_real);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = net_pkt_write(pkt, ctx->buf, fsize_real);
|
||||
if (ret < 0) {
|
||||
eth_stats_update_errors_rx(iface);
|
||||
net_pkt_unref(pkt);
|
||||
LOG_ERR("Port %u failed to fill RX frame, %d", port, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = net_recv_data(iface, pkt);
|
||||
if (ret < 0) {
|
||||
eth_stats_update_errors_rx(iface);
|
||||
net_pkt_unref(pkt);
|
||||
LOG_ERR("Port %u failed to enqueue frame to RX queue, %d",
|
||||
port, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
eth_stats_update_bytes_rx(iface, fsize_real);
|
||||
eth_stats_update_pkts_rx(iface);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void adin2111_port_on_phyint(const struct device *dev)
|
||||
{
|
||||
const struct adin2111_port_config *cfg = dev->config;
|
||||
struct adin2111_port_data *data = dev->data;
|
||||
struct phy_link_state state;
|
||||
|
||||
if (phy_adin2111_handle_phy_irq(cfg->phy, &state) < 0) {
|
||||
/* no change or error */
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.is_up) {
|
||||
net_eth_carrier_on(data->iface);
|
||||
} else {
|
||||
net_eth_carrier_off(data->iface);
|
||||
}
|
||||
}
|
||||
|
||||
static void adin2111_offload_thread(const struct device *dev)
|
||||
{
|
||||
struct adin2111_data *ctx = dev->data;
|
||||
uint32_t status0;
|
||||
uint32_t status1;
|
||||
int ret;
|
||||
|
||||
for (;;) {
|
||||
/* await INT */
|
||||
k_sem_take(&ctx->offload_sem, K_FOREVER);
|
||||
|
||||
/* lock device */
|
||||
eth_adin2111_lock(dev, K_FOREVER);
|
||||
|
||||
/* disable interrupts */
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_IMASK0, UINT32_MAX);
|
||||
if (ret < 0) {
|
||||
goto continue_unlock;
|
||||
}
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_IMASK1, UINT32_MAX);
|
||||
if (ret < 0) {
|
||||
goto continue_unlock;
|
||||
}
|
||||
|
||||
/* read interrupts */
|
||||
ret = eth_adin2111_reg_read(dev, ADIN2111_STATUS0, &status0);
|
||||
if (ret < 0) {
|
||||
goto continue_unlock;
|
||||
}
|
||||
ret = eth_adin2111_reg_read(dev, ADIN2111_STATUS1, &status1);
|
||||
if (ret < 0) {
|
||||
goto continue_unlock;
|
||||
}
|
||||
|
||||
#if CONFIG_ETH_ADIN2111_SPI_CFG0
|
||||
if (status0 & ADIN2111_STATUS1_SPI_ERR) {
|
||||
LOG_WRN("Detected TX SPI CRC error");
|
||||
}
|
||||
#endif /* CONFIG_ETH_ADIN2111_SPI_CFG0 */
|
||||
|
||||
/* handle port 1 phy interrupts */
|
||||
if (status0 & ADIN2111_STATUS0_PHYINT) {
|
||||
adin2111_port_on_phyint(ctx->port[0]);
|
||||
}
|
||||
|
||||
/* handle port 2 phy interrupts */
|
||||
if (status1 & ADIN2111_STATUS1_PHYINT) {
|
||||
adin2111_port_on_phyint(ctx->port[1]);
|
||||
}
|
||||
|
||||
/* handle port 1 rx */
|
||||
if (status1 & ADIN2111_STATUS1_P1_RX_RDY) {
|
||||
do {
|
||||
ret = adin2111_read_fifo(dev, 0U);
|
||||
if (ret < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
ret = eth_adin2111_reg_read(dev, ADIN2111_STATUS1, &status1);
|
||||
if (ret < 0) {
|
||||
goto continue_unlock;
|
||||
}
|
||||
} while (!!(status1 & ADIN2111_STATUS1_P1_RX_RDY));
|
||||
}
|
||||
|
||||
/* handle port 2 rx */
|
||||
if (status1 & ADIN2111_STATUS1_P2_RX_RDY) {
|
||||
do {
|
||||
ret = adin2111_read_fifo(dev, 1U);
|
||||
if (ret < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
ret = eth_adin2111_reg_read(dev, ADIN2111_STATUS1, &status1);
|
||||
if (ret < 0) {
|
||||
goto continue_unlock;
|
||||
}
|
||||
} while (!!(status1 & ADIN2111_STATUS1_P2_RX_RDY));
|
||||
}
|
||||
|
||||
continue_unlock:
|
||||
/* clear interrupts */
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_STATUS0, ADIN2111_STATUS0_CLEAR);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to clear STATUS0, %d", ret);
|
||||
}
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_STATUS1, ADIN2111_STATUS1_CLEAR);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to clear STATUS1, %d", ret);
|
||||
}
|
||||
/* enable interrupts */
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_IMASK0, ctx->imask0);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to write IMASK0, %d", ret);
|
||||
}
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_IMASK1, ctx->imask1);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to write IMASK1, %d", ret);
|
||||
}
|
||||
eth_adin2111_unlock(dev);
|
||||
};
|
||||
}
|
||||
|
||||
static void adin2111_int_callback(const struct device *dev,
|
||||
struct gpio_callback *cb,
|
||||
uint32_t pins)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
ARG_UNUSED(pins);
|
||||
|
||||
struct adin2111_data *ctx = CONTAINER_OF(cb, struct adin2111_data, gpio_int_callback);
|
||||
|
||||
k_sem_give(&ctx->offload_sem);
|
||||
}
|
||||
|
||||
static int adin2111_read_tx_space(const struct device *dev, uint32_t *space)
|
||||
{
|
||||
uint32_t val;
|
||||
int ret;
|
||||
|
||||
ret = eth_adin2111_reg_read(dev, ADIN2111_TX_SPACE, &val);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* tx space is a number of halfwords (16-bits), multiply by 2 for bytes */
|
||||
*space = val * 2;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adin2111_port_send(const struct device *dev, struct net_pkt *pkt)
|
||||
{
|
||||
const struct adin2111_port_config *cfg = dev->config;
|
||||
#if defined(CONFIG_NET_STATISTICS_ETHERNET)
|
||||
struct adin2111_port_data *data = dev->data;
|
||||
#endif /* CONFIG_NET_STATISTICS_ETHERNET */
|
||||
const struct device *adin = cfg->adin;
|
||||
struct adin2111_data *ctx = cfg->adin->data;
|
||||
size_t pkt_len = net_pkt_get_len(pkt);
|
||||
size_t header_size = ADIN2111_WRITE_HEADER_SIZE;
|
||||
size_t padded_size;
|
||||
size_t burst_size;
|
||||
uint32_t tx_space;
|
||||
int ret;
|
||||
|
||||
eth_adin2111_lock(adin, K_FOREVER);
|
||||
|
||||
/* query remaining tx fifo space */
|
||||
ret = adin2111_read_tx_space(adin, &tx_space);
|
||||
if (ret < 0) {
|
||||
eth_stats_update_errors_tx(data->iface);
|
||||
LOG_ERR("Failed to read TX FIFO space, %d", ret);
|
||||
goto end_unlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* verify that there is space for the frame
|
||||
* (frame + 2b header + 2b size field)
|
||||
*/
|
||||
if (tx_space <
|
||||
(pkt_len + ADIN2111_FRAME_HEADER_SIZE + ADIN2111_INTERNAL_HEADER_SIZE)) {
|
||||
/* tx buffer is full */
|
||||
eth_stats_update_errors_tx(data->iface);
|
||||
ret = -EBUSY;
|
||||
goto end_unlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* pad to 64 bytes, otherwise MAC/PHY has to do it
|
||||
* internally MAC adds 4 bytes for forward error correction
|
||||
*/
|
||||
if ((pkt_len + ADIN2111_TX_FIFO_BUFFER_MARGIN) < 64) {
|
||||
padded_size = pkt_len
|
||||
+ (64 - (pkt_len + ADIN2111_TX_FIFO_BUFFER_MARGIN))
|
||||
+ ADIN2111_FRAME_HEADER_SIZE;
|
||||
} else {
|
||||
padded_size = pkt_len + ADIN2111_FRAME_HEADER_SIZE;
|
||||
}
|
||||
|
||||
/* prepare burst write (write data must be in multiples of 4) */
|
||||
burst_size = ROUND_UP(padded_size, 4);
|
||||
if ((burst_size + ADIN2111_WRITE_HEADER_SIZE) > CONFIG_ETH_ADIN2111_BUFFER_SIZE) {
|
||||
ret = -ENOMEM;
|
||||
eth_stats_update_errors_tx(data->iface);
|
||||
goto end_unlock;
|
||||
}
|
||||
|
||||
/* prepare tx buffer */
|
||||
memset(ctx->buf, 0, burst_size + ADIN2111_WRITE_HEADER_SIZE);
|
||||
|
||||
/* spi header */
|
||||
*(uint16_t *)ctx->buf = htons(ADIN2111_TXN_CTRL_TX_REG);
|
||||
#if CONFIG_ETH_ADIN2111_SPI_CFG0
|
||||
ctx->buf[2] = crc8_ccitt(0, ctx->buf, header_size);
|
||||
++header_size;
|
||||
#endif /* CONFIG_ETH_ADIN2111_SPI_CFG0 */
|
||||
|
||||
/* frame header */
|
||||
*(uint16_t *)(ctx->buf + header_size) = htons(cfg->port_idx);
|
||||
|
||||
/* read pkt into tx buffer */
|
||||
ret = net_pkt_read(pkt,
|
||||
(ctx->buf + header_size + ADIN2111_FRAME_HEADER_SIZE),
|
||||
pkt_len);
|
||||
if (ret < 0) {
|
||||
eth_stats_update_errors_tx(data->iface);
|
||||
LOG_ERR("Port %u failed to read PKT into TX buffer, %d",
|
||||
cfg->port_idx, ret);
|
||||
goto end_unlock;
|
||||
}
|
||||
|
||||
/* write transmit size */
|
||||
ret = eth_adin2111_reg_write(adin, ADIN2111_TX_FSIZE, padded_size);
|
||||
if (ret < 0) {
|
||||
eth_stats_update_errors_tx(data->iface);
|
||||
LOG_ERR("Port %u write FSIZE failed, %d", cfg->port_idx, ret);
|
||||
goto end_unlock;
|
||||
}
|
||||
|
||||
/* write transaction */
|
||||
const struct spi_buf buf = {
|
||||
.buf = ctx->buf,
|
||||
.len = header_size + burst_size
|
||||
};
|
||||
const struct spi_buf_set tx = { .buffers = &buf, .count = 1U };
|
||||
|
||||
ret = spi_write_dt(&((const struct adin2111_config *) adin->config)->spi,
|
||||
&tx);
|
||||
if (ret < 0) {
|
||||
eth_stats_update_errors_tx(data->iface);
|
||||
LOG_ERR("Port %u frame SPI write failed, %d", cfg->port_idx, ret);
|
||||
goto end_unlock;
|
||||
}
|
||||
|
||||
eth_stats_update_bytes_tx(data->iface, pkt_len);
|
||||
eth_stats_update_pkts_tx(data->iface);
|
||||
|
||||
end_unlock:
|
||||
eth_adin2111_unlock(adin);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adin2111_config_sync(const struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
uint32_t val;
|
||||
|
||||
ret = eth_adin2111_reg_read(dev, ADIN2111_CONFIG0, &val);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
val |= ADIN2111_CONFIG0_SYNC;
|
||||
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_CONFIG0, val);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adin2111_write_filter_address(const struct device *dev,
|
||||
uint8_t *addr, uint8_t *mask,
|
||||
uint32_t rules, uint16_t slot)
|
||||
{
|
||||
uint16_t offset = slot * 2U;
|
||||
int ret;
|
||||
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_ADDR_FILT_UPR + offset,
|
||||
rules | sys_get_be16(&addr[0]));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_ADDR_FILT_LWR + offset,
|
||||
sys_get_be32(&addr[2]));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (offset > 2U) {
|
||||
/* mask filter addresses are limited to 2 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_ADDR_MSK_UPR + offset,
|
||||
sys_get_be16(&mask[0]));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_ADDR_MSK_LWR + offset,
|
||||
sys_get_be32(&mask[2]));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adin2111_filter_multicast(const struct device *dev)
|
||||
{
|
||||
uint8_t mm[6] = {BIT(0), 0U, 0U, 0U, 0U, 0U};
|
||||
uint32_t rules = ADIN2111_ADDR_APPLY2PORT1 |
|
||||
ADIN2111_ADDR_APPLY2PORT2 |
|
||||
ADIN2111_ADDR_TO_HOST |
|
||||
ADIN2111_ADDR_TO_OTHER_PORT;
|
||||
|
||||
return adin2111_write_filter_address(dev, mm, mm, rules,
|
||||
ADIN2111_MULTICAST_ADDR_SLOT);
|
||||
}
|
||||
|
||||
static int adin2111_filter_broadcast(const struct device *dev)
|
||||
{
|
||||
uint8_t mac[] = {0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU};
|
||||
uint32_t rules = ADIN2111_ADDR_APPLY2PORT1 |
|
||||
ADIN2111_ADDR_APPLY2PORT2 |
|
||||
ADIN2111_ADDR_TO_HOST |
|
||||
ADIN2111_ADDR_TO_OTHER_PORT;
|
||||
|
||||
return adin2111_write_filter_address(dev, mac, mac, rules,
|
||||
ADIN2111_BROADCAST_ADDR_SLOT);
|
||||
}
|
||||
|
||||
static int adin2111_filter_unicast(const struct device *dev, uint8_t *addr,
|
||||
uint8_t port_idx)
|
||||
{
|
||||
uint32_t rules = (port_idx == 0 ? ADIN2111_ADDR_APPLY2PORT1
|
||||
: ADIN2111_ADDR_APPLY2PORT2)
|
||||
| ADIN2111_ADDR_TO_HOST;
|
||||
uint16_t slot = (port_idx == 0 ? ADIN2111_UNICAST_P1_ADDR_SLOT
|
||||
: ADIN2111_UNICAST_P2_ADDR_SLOT);
|
||||
|
||||
return adin2111_write_filter_address(dev, addr, NULL, rules, slot);
|
||||
}
|
||||
|
||||
static void adin2111_port_iface_init(struct net_if *iface)
|
||||
{
|
||||
const struct device *dev = net_if_get_device(iface);
|
||||
const struct adin2111_port_config *cfg = dev->config;
|
||||
struct adin2111_port_data *data = dev->data;
|
||||
const struct device *adin = cfg->adin;
|
||||
struct adin2111_data *ctx = adin->data;
|
||||
int ret;
|
||||
|
||||
if (!device_is_ready(adin)) {
|
||||
LOG_ERR("ADIN %s is not ready, can't init port %u iface",
|
||||
cfg->adin->name, cfg->port_idx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!device_is_ready(cfg->phy)) {
|
||||
LOG_ERR("PHY %u is not ready, can't init port %u iface",
|
||||
cfg->phy_addr, cfg->port_idx);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->port[cfg->port_idx] = dev;
|
||||
data->iface = iface;
|
||||
|
||||
ret = adin2111_filter_unicast(adin, data->mac_addr, cfg->port_idx);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Port %u, failed to set unicast filter, %d",
|
||||
cfg->port_idx, ret);
|
||||
return;
|
||||
}
|
||||
net_if_set_link_addr(iface, data->mac_addr, sizeof(data->mac_addr),
|
||||
NET_LINK_ETHERNET);
|
||||
ethernet_init(iface);
|
||||
net_if_carrier_off(iface);
|
||||
|
||||
--ctx->ifaces_left_to_init;
|
||||
|
||||
/* if all ports are initialized */
|
||||
if (ctx->ifaces_left_to_init == 0U) {
|
||||
/* setup rx filters */
|
||||
ret = adin2111_filter_multicast(adin);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Couldn't set multicast filter, %d", ret);
|
||||
return;
|
||||
}
|
||||
ret = adin2111_filter_broadcast(adin);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Couldn't set broadcast filter, %d", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* sync */
|
||||
ret = adin2111_config_sync(adin);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to write CONFIG0 SYNC, %d", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* all ifaces are done, start INT processing */
|
||||
k_thread_create(&ctx->rx_thread, ctx->rx_thread_stack,
|
||||
CONFIG_ETH_ADIN2111_IRQ_THREAD_STACK_SIZE,
|
||||
(k_thread_entry_t)adin2111_offload_thread,
|
||||
(void *)adin, NULL, NULL,
|
||||
CONFIG_ETH_ADIN2111_IRQ_THREAD_PRIO,
|
||||
K_ESSENTIAL, K_NO_WAIT);
|
||||
k_thread_name_set(&ctx->rx_thread, "eth_adin2111_offload");
|
||||
}
|
||||
}
|
||||
|
||||
static enum ethernet_hw_caps adin2111_port_get_capabilities(const struct device *dev)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
return ETHERNET_LINK_10BASE_T
|
||||
#if defined(CONFIG_NET_LLDP)
|
||||
| ETHERNET_LLDP
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
static int adin2111_port_set_config(const struct device *dev,
|
||||
enum ethernet_config_type type,
|
||||
const struct ethernet_config *config)
|
||||
{
|
||||
const struct adin2111_port_config *cfg = dev->config;
|
||||
struct adin2111_port_data *data = dev->data;
|
||||
const struct device *adin = cfg->adin;
|
||||
int ret = -ENOTSUP;
|
||||
|
||||
if (type == ETHERNET_CONFIG_TYPE_MAC_ADDRESS) {
|
||||
ret = adin2111_filter_unicast(adin, data->mac_addr, cfg->port_idx);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
memcpy(data->mac_addr, config->mac_address.addr, sizeof(data->mac_addr));
|
||||
|
||||
net_if_set_link_addr(data->iface, data->mac_addr,
|
||||
sizeof(data->mac_addr),
|
||||
NET_LINK_ETHERNET);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_NET_STATISTICS_ETHERNET)
|
||||
static struct net_stats_eth *adin2111_port_get_stats(const struct device *dev)
|
||||
{
|
||||
struct adin2111_port_data *data = dev->data;
|
||||
|
||||
return &data->stats;
|
||||
}
|
||||
#endif /* CONFIG_NET_STATISTICS_ETHERNET */
|
||||
|
||||
static int adin2111_check_spi(const struct device *dev)
|
||||
{
|
||||
uint32_t count;
|
||||
uint32_t val;
|
||||
int ret;
|
||||
|
||||
/* check SPI communication by reading PHYID */
|
||||
for (count = 0U; count < ADIN2111_DEV_AWAIT_RETRY_COUNT; ++count) {
|
||||
ret = eth_adin2111_reg_read(dev, ADIN2111_PHYID, &val);
|
||||
if (ret >= 0) {
|
||||
if (val == ADIN2111_PHYID_RST_VAL) {
|
||||
break;
|
||||
}
|
||||
ret = -ETIMEDOUT;
|
||||
}
|
||||
k_sleep(K_USEC(ADIN2111_DEV_AWAIT_DELAY_POLL_US));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adin2111_await_device(const struct device *dev)
|
||||
{
|
||||
uint32_t count;
|
||||
uint32_t val;
|
||||
int ret;
|
||||
|
||||
/* await reset complete (RESETC) and clear it */
|
||||
for (count = 0U; count < ADIN2111_RESETC_AWAIT_RETRY_COUNT; ++count) {
|
||||
ret = eth_adin2111_reg_read(dev, ADIN2111_STATUS0, &val);
|
||||
if (ret >= 0) {
|
||||
/* if out of reset */
|
||||
if (val & ADIN2111_STATUS0_RESETC) {
|
||||
/* clear RESETC */
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_STATUS0,
|
||||
ADIN2111_STATUS0_RESETC);
|
||||
if (ret >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ret = -ETIMEDOUT;
|
||||
}
|
||||
k_sleep(K_USEC(ADIN2111_RESETC_AWAIT_DELAY_POLL_US));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adin2111_init(const struct device *dev)
|
||||
{
|
||||
const struct adin2111_config *cfg = dev->config;
|
||||
struct adin2111_data *ctx = dev->data;
|
||||
int ret;
|
||||
uint32_t val;
|
||||
|
||||
__ASSERT(cfg->spi.config.frequency <= ADIN2111_SPI_MAX_FREQUENCY,
|
||||
"SPI frequency exceeds supported maximum\n");
|
||||
|
||||
if (!spi_is_ready_dt(&cfg->spi)) {
|
||||
LOG_ERR("SPI bus %s not ready", cfg->spi.bus->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!gpio_is_ready_dt(&cfg->interrupt)) {
|
||||
LOG_ERR("Interrupt GPIO device %s is not ready",
|
||||
cfg->interrupt.port->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = gpio_pin_configure_dt(&cfg->interrupt, GPIO_INPUT);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to configure interrupt GPIO, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (cfg->reset.port != NULL) {
|
||||
if (!gpio_is_ready_dt(&cfg->reset)) {
|
||||
LOG_ERR("Reset GPIO device %s is not ready",
|
||||
cfg->reset.port->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = gpio_pin_configure_dt(&cfg->reset, GPIO_OUTPUT_INACTIVE);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to configure reset GPIO, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* perform hard reset */
|
||||
/* assert pin low for 16 µs (10 µs min) */
|
||||
gpio_pin_set_dt(&cfg->reset, 1);
|
||||
k_busy_wait(16U);
|
||||
/* deassert and wait for 90 ms (max) for clocks stabilisation */
|
||||
gpio_pin_set_dt(&cfg->reset, 0);
|
||||
k_msleep(ADIN2111_HW_BOOT_DELAY_MS);
|
||||
}
|
||||
|
||||
gpio_init_callback(&(ctx->gpio_int_callback),
|
||||
adin2111_int_callback,
|
||||
BIT(cfg->interrupt.pin));
|
||||
|
||||
ret = gpio_add_callback(cfg->interrupt.port, &ctx->gpio_int_callback);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to add INT callback, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = adin2111_check_spi(dev);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to communicate over SPI, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* perform MACPHY soft reset */
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_RESET, ADIN2111_RESET_SWRESET);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("MACPHY software reset failed, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = adin2111_await_device(dev);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("ADIN did't come out of the reset, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* CONFIG 0 */
|
||||
/* disable Frame Check Sequence validation on the host */
|
||||
/* if that is enabled, then CONFIG_ETH_ADIN2111_SPI_CFG0 must be off */
|
||||
ret = eth_adin2111_reg_read(dev, ADIN2111_CONFIG0, &val);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to read CONFIG0, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* RXCTE must be disabled for Generic SPI */
|
||||
val &= ~ADIN2111_CONFIG0_RXCTE;
|
||||
val &= ~(ADIN2111_CONFIG0_TXCTE | ADIN2111_CONFIG0_TXFCSVE);
|
||||
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_CONFIG0, val);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to write CONFIG0, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* CONFIG 2 */
|
||||
ret = eth_adin2111_reg_read(dev, ADIN2111_CONFIG2, &val);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to read CONFIG2, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if CONFIG_ETH_ADIN2111_SPI_CFG0
|
||||
val |= ADIN2111_CONFIG2_CRC_APPEND;
|
||||
#else
|
||||
val &= ~ADIN2111_CONFIG2_CRC_APPEND;
|
||||
#endif /* CONFIG_ETH_ADIN2111_SPI_CFG0 */
|
||||
|
||||
/* configure forwarding of frames with unknown destination address */
|
||||
/* to the other port. This forwarding is done in hardware. */
|
||||
/* The setting will take effect after the ports */
|
||||
/* are out of software powerdown. */
|
||||
val |= (ADIN2111_CONFIG2_PORT_CUT_THRU_EN |
|
||||
ADIN2111_CONFIG2_P1_FWD_UNK2P2 |
|
||||
ADIN2111_CONFIG2_P2_FWD_UNK2P1);
|
||||
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_CONFIG2, val);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to write CONFIG2, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* configure interrupt masks */
|
||||
ctx->imask0 = ~ADIN2111_IMASK0_PHYINTM;
|
||||
ctx->imask1 = ~(ADIN2111_IMASK1_TX_RDY_MASK |
|
||||
ADIN2111_IMASK1_P1_RX_RDY_MASK |
|
||||
ADIN2111_IMASK1_SPI_ERR_MASK |
|
||||
ADIN2111_IMASK1_P2_RX_RDY_MASK |
|
||||
ADIN2111_IMASK1_P2_PHYINT_MASK);
|
||||
|
||||
/* enable interrupts */
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_IMASK0, ctx->imask0);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to write IMASK0, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
ret = eth_adin2111_reg_write(dev, ADIN2111_IMASK1, ctx->imask1);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to write IMASK1, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = gpio_pin_interrupt_configure_dt(&cfg->interrupt,
|
||||
GPIO_INT_EDGE_TO_ACTIVE);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to enable INT, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct ethernet_api adin2111_port_api = {
|
||||
.iface_api.init = adin2111_port_iface_init,
|
||||
.get_capabilities = adin2111_port_get_capabilities,
|
||||
.set_config = adin2111_port_set_config,
|
||||
.send = adin2111_port_send,
|
||||
#if defined(CONFIG_NET_STATISTICS_ETHERNET)
|
||||
.get_stats = adin2111_port_get_stats,
|
||||
#endif /* CONFIG_NET_STATISTICS_ETHERNET */
|
||||
};
|
||||
|
||||
#define ADIN2111_STR(x) #x
|
||||
#define ADIN2111_XSTR(x) ADIN2111_STR(x)
|
||||
|
||||
#define ADIN2111_MDIO_PHY_BY_ADDR(adin_n, phy_addr) \
|
||||
DEVICE_DT_GET(DT_CHILD(DT_INST_CHILD(adin_n, mdio), phy_##phy_addr))
|
||||
|
||||
#define ADIN2111_PORT_MAC(adin_n, port_n) \
|
||||
DT_PROP(DT_CHILD(DT_DRV_INST(adin_n), port##port_n), local_mac_address)
|
||||
|
||||
#define ADIN2111_PORT_DEVICE_INIT_INSTANCE(parent_n, port_n, phy_n) \
|
||||
static struct adin2111_port_data adin2111_port_data_##port_n = { \
|
||||
.mac_addr = ADIN2111_PORT_MAC(parent_n, phy_n), \
|
||||
}; \
|
||||
static const struct adin2111_port_config adin2111_port_config_##port_n = { \
|
||||
.adin = DEVICE_DT_INST_GET(parent_n), \
|
||||
.phy = ADIN2111_MDIO_PHY_BY_ADDR(parent_n, phy_n), \
|
||||
.port_idx = port_n, \
|
||||
.phy_addr = phy_n, \
|
||||
}; \
|
||||
NET_DEVICE_INIT_INSTANCE(adin2111_port_##port_n, "port_" ADIN2111_XSTR(port_n), port_n, \
|
||||
NULL, NULL, &adin2111_port_data_##port_n, \
|
||||
&adin2111_port_config_##port_n, CONFIG_ETH_INIT_PRIORITY, \
|
||||
&adin2111_port_api, ETHERNET_L2, \
|
||||
NET_L2_GET_CTX_TYPE(ETHERNET_L2), NET_ETH_MTU);
|
||||
|
||||
#define ADIN2111_SPI_OPERATION ((uint16_t)(SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8)))
|
||||
|
||||
#define ADIN2111_MAC_INITIALIZE(inst) \
|
||||
static uint8_t __aligned(4) adin2111_buffer_##inst[CONFIG_ETH_ADIN2111_BUFFER_SIZE]; \
|
||||
static const struct adin2111_config adin2111_config_##inst = { \
|
||||
.spi = SPI_DT_SPEC_INST_GET(inst, ADIN2111_SPI_OPERATION, 1), \
|
||||
.interrupt = GPIO_DT_SPEC_INST_GET(inst, int_gpios), \
|
||||
.reset = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, { 0 }), \
|
||||
}; \
|
||||
static struct adin2111_data adin2111_data_##inst = { \
|
||||
.ifaces_left_to_init = 2U, \
|
||||
.port = {}, \
|
||||
.offload_sem = Z_SEM_INITIALIZER(adin2111_data_##inst.offload_sem, 0, 1), \
|
||||
.lock = Z_MUTEX_INITIALIZER(adin2111_data_##inst.lock), \
|
||||
.buf = adin2111_buffer_##inst, \
|
||||
}; \
|
||||
/* adin */ \
|
||||
DEVICE_DT_DEFINE(DT_DRV_INST(inst), adin2111_init, NULL, \
|
||||
&adin2111_data_##inst, &adin2111_config_##inst, \
|
||||
POST_KERNEL, CONFIG_ETH_ADIN2111_INIT_PRIORITY, \
|
||||
NULL); \
|
||||
/* ports */ \
|
||||
ADIN2111_PORT_DEVICE_INIT_INSTANCE(inst, 0, 1) \
|
||||
ADIN2111_PORT_DEVICE_INIT_INSTANCE(inst, 1, 2)
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(ADIN2111_MAC_INITIALIZE)
|
196
drivers/ethernet/eth_adin2111_priv.h
Normal file
196
drivers/ethernet/eth_adin2111_priv.h
Normal file
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#ifndef ETH_ADIN2111_PRIV_H__
|
||||
#define ETH_ADIN2111_PRIV_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/drivers/spi.h>
|
||||
#include <zephyr/net/net_if.h>
|
||||
#include <ethernet/eth_stats.h>
|
||||
|
||||
/* SPI frequency maximum, based on clock cycle time */
|
||||
#define ADIN2111_SPI_MAX_FREQUENCY 25000000U
|
||||
|
||||
#define ADIN2111_PHYID 0x01U
|
||||
/* PHY Identification Register Reset Value */
|
||||
#define ADIN2111_PHYID_RST_VAL 0x0283BCA1U
|
||||
|
||||
/* Reset Control and Status Register */
|
||||
#define ADIN2111_RESET 0x03U
|
||||
/* MACPHY software reset */
|
||||
#define ADIN2111_RESET_SWRESET BIT(0)
|
||||
|
||||
/* Configuration Register 0 */
|
||||
#define ADIN2111_CONFIG0 0x04U
|
||||
/* Configuration Synchronization */
|
||||
#define ADIN2111_CONFIG0_SYNC BIT(15)
|
||||
/* Transmit Frame Check Sequence Validation Enable */
|
||||
#define ADIN2111_CONFIG0_TXFCSVE BIT(14)
|
||||
/* Transmit Cut Through Enable */
|
||||
#define ADIN2111_CONFIG0_TXCTE BIT(9)
|
||||
/* Receive Cut Through Enable. Must be 0 for Generic SPI */
|
||||
#define ADIN2111_CONFIG0_RXCTE BIT(8)
|
||||
|
||||
/* Configuration Register 2 */
|
||||
#define ADIN2111_CONFIG2 0x06U
|
||||
/* Forward Frames from Port 2 Not Matching a MAC Address to Port 1 */
|
||||
#define ADIN2111_CONFIG2_P2_FWD_UNK2P1 BIT(14)
|
||||
/* Forward Frames from Port 1 Not Matching a MAC Address to Port 2 */
|
||||
#define ADIN2111_CONFIG2_P1_FWD_UNK2P2 BIT(13)
|
||||
/* Enable Cut Through from Port to Port */
|
||||
#define ADIN2111_CONFIG2_PORT_CUT_THRU_EN BIT(11)
|
||||
/* Enable CRC Append */
|
||||
#define ADIN2111_CONFIG2_CRC_APPEND BIT(5)
|
||||
|
||||
/* Status Register 0 */
|
||||
#define ADIN2111_STATUS0 0x08U
|
||||
/* PHY Interrupt for Port 1 */
|
||||
#define ADIN2111_STATUS0_PHYINT BIT(7)
|
||||
/**
|
||||
* Reset Complete.
|
||||
* The bit is set when the MACPHY reset is complete
|
||||
* and ready for configuration.
|
||||
*/
|
||||
#define ADIN2111_STATUS0_RESETC BIT(6)
|
||||
/* Value to completely clear status register 0 */
|
||||
#define ADIN2111_STATUS0_CLEAR 0x1F7FU
|
||||
|
||||
/* Status Register 1 */
|
||||
#define ADIN2111_STATUS1 0x09U
|
||||
/* PHY Interrupt for Port 2 */
|
||||
#define ADIN2111_STATUS1_PHYINT BIT(19)
|
||||
/* Port 2 RX FIFO Contains Data */
|
||||
#define ADIN2111_STATUS1_P2_RX_RDY BIT(17)
|
||||
/* Indicates that a CRC error was detected */
|
||||
#define ADIN2111_STATUS1_SPI_ERR BIT(10)
|
||||
/* Port 1 RX FIFO Contains Data */
|
||||
#define ADIN2111_STATUS1_P1_RX_RDY BIT(4)
|
||||
/* Value to completely clear status register 1 */
|
||||
#define ADIN2111_STATUS1_CLEAR 0xFFF01F08U
|
||||
|
||||
|
||||
/* Interrupt Mask Register 0 */
|
||||
#define ADIN2111_IMASK0 0x0CU
|
||||
/* Physical Layer Interrupt Mask */
|
||||
#define ADIN2111_IMASK0_PHYINTM BIT(7)
|
||||
|
||||
/* Interrupt Mask Register 1 */
|
||||
#define ADIN2111_IMASK1 0x0DU
|
||||
/* Mask Bit for P2_PHYINT */
|
||||
#define ADIN2111_IMASK1_P2_PHYINT_MASK BIT(19)
|
||||
/*!< Mask Bit for P2_RX_RDY. Generic SPI only.*/
|
||||
#define ADIN2111_IMASK1_P2_RX_RDY_MASK BIT(17)
|
||||
/*!< Mask Bit for SPI_ERR. Generic SPI only. */
|
||||
#define ADIN2111_IMASK1_SPI_ERR_MASK BIT(10)
|
||||
/*!< Mask Bit for P1_RX_RDY. Generic SPI only.*/
|
||||
#define ADIN2111_IMASK1_P1_RX_RDY_MASK BIT(4)
|
||||
/*!< Mask Bit for TX_FRM_DONE. Generic SPI only.*/
|
||||
#define ADIN2111_IMASK1_TX_RDY_MASK BIT(4)
|
||||
|
||||
/* MAC Tx Frame Size Register */
|
||||
#define ADIN2111_TX_FSIZE 0x30U
|
||||
/* Tx FIFO Space Register */
|
||||
#define ADIN2111_TX_SPACE 0x32U
|
||||
|
||||
/* MAC Address Rule and DA Filter Upper 16 Bits Registers */
|
||||
#define ADIN2111_ADDR_FILT_UPR 0x50U
|
||||
#define ADIN2111_ADDR_APPLY2PORT2 BIT(31)
|
||||
#define ADIN2111_ADDR_APPLY2PORT1 BIT(30)
|
||||
#define ADIN2111_ADDR_TO_OTHER_PORT BIT(17)
|
||||
#define ADIN2111_ADDR_TO_HOST BIT(16)
|
||||
|
||||
/* MAC Address DA Filter Lower 32 Bits Registers */
|
||||
#define ADIN2111_ADDR_FILT_LWR 0x51U
|
||||
/* Upper 16 Bits of the MAC Address Mask */
|
||||
#define ADIN2111_ADDR_MSK_UPR 0x70U
|
||||
/* Lower 32 Bits of the MAC Address Mask */
|
||||
#define ADIN2111_ADDR_MSK_LWR 0x71U
|
||||
|
||||
/* P1 MAC Rx Frame Size Register */
|
||||
#define ADIN2111_P1_RX_FSIZE 0x90U
|
||||
/* P1 MAC Receive Register */
|
||||
#define ADIN2111_P1_RX 0x91U
|
||||
|
||||
/* P2 MAC Rx Frame Size Register */
|
||||
#define ADIN2111_P2_RX_FSIZE 0xC0U
|
||||
/* P2 MAC Receive Register */
|
||||
#define ADIN2111_P2_RX 0xC1U
|
||||
|
||||
/* SPI header size in bytes */
|
||||
#define ADIN2111_SPI_HEADER_SIZE 2U
|
||||
/* SPI header size for write transaction */
|
||||
#define ADIN2111_WRITE_HEADER_SIZE ADIN2111_SPI_HEADER_SIZE
|
||||
/* SPI header size for read transaction (1 for TA) */
|
||||
#define ADIN2111_READ_HEADER_SIZE (ADIN2111_SPI_HEADER_SIZE + 1U)
|
||||
|
||||
/* SPI register write buffer size without CRC */
|
||||
#define ADIN2111_REG_WRITE_BUF_SIZE (ADIN2111_WRITE_HEADER_SIZE + sizeof(uint32_t))
|
||||
/* SPI register write buffer with appended CRC size (1 for header, 1 for register) */
|
||||
#define ADIN2111_REG_WRITE_BUF_SIZE_CRC (ADIN2111_REG_WRITE_BUF_SIZE + 2U)
|
||||
|
||||
/* SPI register read buffer size with TA without CRC */
|
||||
#define ADIN2111_REG_READ_BUF_SIZE (ADIN2111_READ_HEADER_SIZE + sizeof(uint32_t))
|
||||
/* SPI register read buffer with TA and appended CRC size (1 header, 1 for register) */
|
||||
#define ADIN2111_REG_READ_BUF_SIZE_CRC (ADIN2111_REG_READ_BUF_SIZE + 2U)
|
||||
|
||||
/* SPI read fifo cmd buffer size with TA without CRC */
|
||||
#define ADIN2111_FIFO_READ_CMD_BUF_SIZE (ADIN2111_READ_HEADER_SIZE)
|
||||
/* SPI read fifo cmd buffer with TA and appended CRC size */
|
||||
#define ADIN2111_FIFO_READ_CMD_BUF_SIZE_CRC (ADIN2111_FIFO_READ_CMD_BUF_SIZE + 1U)
|
||||
|
||||
/* SPI Header for writing control transaction in half duplex mode */
|
||||
#define ADIN2111_WRITE_TXN_CTRL 0xA000U
|
||||
/* SPI Header for writing control transaction with MAC TX register (!) in half duplex mode */
|
||||
#define ADIN2111_TXN_CTRL_TX_REG 0xA031U
|
||||
/* SPI Header for reading control transaction in half duplex mode */
|
||||
#define ADIN2111_READ_TXN_CTRL 0x8000U
|
||||
|
||||
/* Frame header size in bytes */
|
||||
#define ADIN2111_FRAME_HEADER_SIZE 2U
|
||||
#define ADIN2111_INTERNAL_HEADER_SIZE 2U
|
||||
/* Number of buffer bytes in TxFIFO to provide frame margin upon writes */
|
||||
#define ADIN2111_TX_FIFO_BUFFER_MARGIN 4U
|
||||
|
||||
struct adin2111_config {
|
||||
struct spi_dt_spec spi;
|
||||
struct gpio_dt_spec interrupt;
|
||||
struct gpio_dt_spec reset;
|
||||
};
|
||||
|
||||
struct adin2111_data {
|
||||
/* Port 0: PHY 1, Port 1: PHY 2 */
|
||||
const struct device *port[2];
|
||||
struct gpio_callback gpio_int_callback;
|
||||
struct k_sem offload_sem;
|
||||
struct k_mutex lock;
|
||||
uint32_t imask0;
|
||||
uint32_t imask1;
|
||||
uint16_t ifaces_left_to_init;
|
||||
uint8_t *buf;
|
||||
|
||||
K_KERNEL_STACK_MEMBER(rx_thread_stack, CONFIG_ETH_ADIN2111_IRQ_THREAD_STACK_SIZE);
|
||||
struct k_thread rx_thread;
|
||||
};
|
||||
|
||||
struct adin2111_port_data {
|
||||
struct net_if *iface;
|
||||
uint8_t mac_addr[6];
|
||||
#if defined(CONFIG_NET_STATISTICS_ETHERNET)
|
||||
struct net_stats_eth stats;
|
||||
#endif /* CONFIG_NET_STATISTICS_ETHERNET */
|
||||
};
|
||||
|
||||
struct adin2111_port_config {
|
||||
const struct device *adin;
|
||||
const struct device *phy;
|
||||
const uint16_t port_idx;
|
||||
const uint16_t phy_addr;
|
||||
};
|
||||
|
||||
#endif /* ETH_ADIN2111_PRIV_H__ */
|
63
dts/bindings/ethernet/adi,adin2111.yaml
Normal file
63
dts/bindings/ethernet/adi,adin2111.yaml
Normal file
|
@ -0,0 +1,63 @@
|
|||
# Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: |
|
||||
ADIN2111 standalone 10BASE-T1L Ethernet controller with SPI interface.
|
||||
|
||||
An example:
|
||||
|
||||
adin2111: adin2111@0 {
|
||||
compatible = "adi,adin2111";
|
||||
reg = <0x0>;
|
||||
spi-max-frequency = <25000000>;
|
||||
int-gpios = <&gpioe 12 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
|
||||
reset-gpios = <&gpioe 8 GPIO_ACTIVE_LOW>;
|
||||
port1 {
|
||||
local-mac-address = [ CA 2F B7 10 23 63 ];
|
||||
};
|
||||
port2 {
|
||||
local-mac-address = [ 3C 82 D4 A2 29 8E ];
|
||||
};
|
||||
mdio: mdio {
|
||||
compatible = "adi,adin2111-mdio";
|
||||
status = "okay";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
phy@1 {
|
||||
reg = <0x1>;
|
||||
compatible = "adi,adin2111-phy";
|
||||
status = "okay";
|
||||
};
|
||||
phy@2 {
|
||||
reg = <0x2>;
|
||||
compatible = "adi,adin2111-phy";
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
compatible: "adi,adin2111"
|
||||
|
||||
include: [spi-device.yaml]
|
||||
|
||||
bus: adin2111
|
||||
|
||||
properties:
|
||||
int-gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: |
|
||||
The interrupt pin of ADIN2111 is active low.
|
||||
If connected directly the MCU pin should be configured
|
||||
as active low.
|
||||
reset-gpios:
|
||||
type: phandle-array
|
||||
description: The reset pin of ADIN2111.
|
||||
|
||||
child-binding:
|
||||
description: Port properties
|
||||
properties:
|
||||
local-mac-address:
|
||||
type: uint8-array
|
||||
description: |
|
||||
Specifies the MAC address that was assigned to the network device.
|
77
include/zephyr/drivers/ethernet/eth_adin2111.h
Normal file
77
include/zephyr/drivers/ethernet/eth_adin2111.h
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_INCLUDE_DRIVERS_ETH_ADIN2111_H__
|
||||
#define ZEPHYR_INCLUDE_DRIVERS_ETH_ADIN2111_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/device.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Locks device access
|
||||
*
|
||||
* @param[in] dev ADIN2111 device.
|
||||
* @param timeout Waiting period to lock the device,
|
||||
* or one of the special values K_NO_WAIT and
|
||||
* K_FOREVER.
|
||||
*
|
||||
* @retval 0 Device locked.
|
||||
* @retval -EBUSY Returned without waiting.
|
||||
* @retval -EAGAIN Waiting period timed out.
|
||||
*/
|
||||
int eth_adin2111_lock(const struct device *dev, k_timeout_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Unlocks device access
|
||||
*
|
||||
* @param[in] dev ADIN2111 device.
|
||||
*
|
||||
* @retval 0 Device unlocked.
|
||||
* @retval -EPERM The current thread does not own the device lock.
|
||||
* @retval -EINVAL The device is not locked.
|
||||
*/
|
||||
int eth_adin2111_unlock(const struct device *dev);
|
||||
|
||||
/**
|
||||
* @brief Writes host MAC interface register over SPI
|
||||
*
|
||||
* @note The caller is responsible for device lock.
|
||||
* Shall not be called from ISR.
|
||||
*
|
||||
* @param[in] dev ADIN2111 device.
|
||||
* @param reg Register address.
|
||||
* @param val Value to write.
|
||||
*
|
||||
* @retval 0 Successful write.
|
||||
* @retval <0 Error, a negative errno code.
|
||||
*/
|
||||
int eth_adin2111_reg_write(const struct device *dev, const uint16_t reg, uint32_t val);
|
||||
|
||||
/**
|
||||
* @brief Reads host MAC interface register over SPI
|
||||
*
|
||||
* @note The caller is responsible for device lock.
|
||||
* Shall not be called from ISR.
|
||||
*
|
||||
* @param[in] dev ADIN2111 device.
|
||||
* @param reg Register address.
|
||||
* @param[out] val Read value output.
|
||||
*
|
||||
* @retval 0 Successful write.
|
||||
* @retval <0 Error, a negative errno code.
|
||||
*/
|
||||
int eth_adin2111_reg_read(const struct device *dev, const uint16_t reg, uint32_t *val);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ZEPHYR_INCLUDE_DRIVERS_ETH_ADIN2111_H__ */
|
Loading…
Reference in a new issue