From 181499523ac0e9f6e697707fea4be060fe5e838b Mon Sep 17 00:00:00 2001 From: Georgij Cernysiov Date: Fri, 12 May 2023 14:49:53 +0200 Subject: [PATCH 1/4] 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 --- CODEOWNERS | 1 + drivers/ethernet/CMakeLists.txt | 1 + drivers/ethernet/Kconfig | 1 + drivers/ethernet/Kconfig.adin2111 | 67 ++ drivers/ethernet/eth_adin2111.c | 982 ++++++++++++++++++ drivers/ethernet/eth_adin2111_priv.h | 196 ++++ dts/bindings/ethernet/adi,adin2111.yaml | 63 ++ .../zephyr/drivers/ethernet/eth_adin2111.h | 77 ++ 8 files changed, 1388 insertions(+) create mode 100644 drivers/ethernet/Kconfig.adin2111 create mode 100644 drivers/ethernet/eth_adin2111.c create mode 100644 drivers/ethernet/eth_adin2111_priv.h create mode 100644 dts/bindings/ethernet/adi,adin2111.yaml create mode 100644 include/zephyr/drivers/ethernet/eth_adin2111.h diff --git a/CODEOWNERS b/CODEOWNERS index bcbac8a72ffe54..04d02e9f76a96d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -284,6 +284,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 diff --git a/drivers/ethernet/CMakeLists.txt b/drivers/ethernet/CMakeLists.txt index 3314a1d6fa979b..10a5997b3366b3 100644 --- a/drivers/ethernet/CMakeLists.txt +++ b/drivers/ethernet/CMakeLists.txt @@ -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) diff --git a/drivers/ethernet/Kconfig b/drivers/ethernet/Kconfig index 33a85e4a513bf6..8368d468a91287 100644 --- a/drivers/ethernet/Kconfig +++ b/drivers/ethernet/Kconfig @@ -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" diff --git a/drivers/ethernet/Kconfig.adin2111 b/drivers/ethernet/Kconfig.adin2111 new file mode 100644 index 00000000000000..9de89f4362133e --- /dev/null +++ b/drivers/ethernet/Kconfig.adin2111 @@ -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 diff --git a/drivers/ethernet/eth_adin2111.c b/drivers/ethernet/eth_adin2111.c new file mode 100644 index 00000000000000..0ae5c2fadea29f --- /dev/null +++ b/drivers/ethernet/eth_adin2111.c @@ -0,0 +1,982 @@ +/* + * Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(eth_adin2111, CONFIG_ETHERNET_LOG_LEVEL); + +#define DT_DRV_COMPAT adi_adin2111 + +#include +#include +#include + +#if CONFIG_ETH_ADIN2111_SPI_CFG0 +#include +#endif /* CONFIG_ETH_ADIN2111_SPI_CFG0 */ +#include +#include + +#include +#include +#include +#include + +#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) diff --git a/drivers/ethernet/eth_adin2111_priv.h b/drivers/ethernet/eth_adin2111_priv.h new file mode 100644 index 00000000000000..9f702bbd42d9bc --- /dev/null +++ b/drivers/ethernet/eth_adin2111_priv.h @@ -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 +#include +#include +#include +#include +#include +#include + +/* 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__ */ diff --git a/dts/bindings/ethernet/adi,adin2111.yaml b/dts/bindings/ethernet/adi,adin2111.yaml new file mode 100644 index 00000000000000..8c0f9cb20250e9 --- /dev/null +++ b/dts/bindings/ethernet/adi,adin2111.yaml @@ -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. diff --git a/include/zephyr/drivers/ethernet/eth_adin2111.h b/include/zephyr/drivers/ethernet/eth_adin2111.h new file mode 100644 index 00000000000000..3756add8bd3578 --- /dev/null +++ b/include/zephyr/drivers/ethernet/eth_adin2111.h @@ -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 +#include +#include + +#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__ */ From faf1cefdb62322231bc1a3061c0c28d30910a577 Mon Sep 17 00:00:00 2001 From: Georgij Cernysiov Date: Fri, 12 May 2023 14:59:03 +0200 Subject: [PATCH 2/4] drivers: mdio: add adin2111 Adds MDIO driver. Works via exposed ADIN2111 functions. It is possible to access Clause 45 and 22 registers. Due to MDIO API limitation Clause 45 access is done using driver specific MDIO functions. Provides API and functions for PHY driver. Signed-off-by: Georgij Cernysiov --- CODEOWNERS | 1 + drivers/mdio/CMakeLists.txt | 1 + drivers/mdio/Kconfig | 1 + drivers/mdio/Kconfig.adin2111 | 10 + drivers/mdio/mdio_adin2111.c | 207 ++++++++++++++++++++ dts/bindings/mdio/adi,adin2111-mdio.yaml | 19 ++ include/zephyr/drivers/mdio/mdio_adin2111.h | 61 ++++++ 7 files changed, 300 insertions(+) create mode 100644 drivers/mdio/Kconfig.adin2111 create mode 100644 drivers/mdio/mdio_adin2111.c create mode 100644 dts/bindings/mdio/adi,adin2111-mdio.yaml create mode 100644 include/zephyr/drivers/mdio/mdio_adin2111.h diff --git a/CODEOWNERS b/CODEOWNERS index 04d02e9f76a96d..24732085cbd6ed 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -287,6 +287,7 @@ /drivers/ethernet/*adin2111* @GeorgeCGV /drivers/ethernet/phy/ @rlubos @tbursztyka @arvinf /drivers/mdio/ @rlubos @tbursztyka @arvinf +/drivers/mdio/*adin2111* @GeorgeCGV /drivers/flash/ @nashif @de-nordic /drivers/flash/*stm32_qspi* @lmajewski /drivers/flash/*b91* @andy-liu-telink diff --git a/drivers/mdio/CMakeLists.txt b/drivers/mdio/CMakeLists.txt index bb939287e3d498..7fc03a8fc8f320 100644 --- a/drivers/mdio/CMakeLists.txt +++ b/drivers/mdio/CMakeLists.txt @@ -6,3 +6,4 @@ zephyr_library_sources_ifdef(CONFIG_MDIO_SHELL mdio_shell.c) zephyr_library_sources_ifdef(CONFIG_MDIO_ATMEL_SAM mdio_sam.c) zephyr_library_sources_ifdef(CONFIG_MDIO_ESP32 mdio_esp32.c) zephyr_library_sources_ifdef(CONFIG_MDIO_NXP_S32_NETC mdio_nxp_s32_netc.c) +zephyr_library_sources_ifdef(CONFIG_MDIO_ADIN2111 mdio_adin2111.c) diff --git a/drivers/mdio/Kconfig b/drivers/mdio/Kconfig index 4fa8b9dd085fc6..0f2d3b84a845c2 100644 --- a/drivers/mdio/Kconfig +++ b/drivers/mdio/Kconfig @@ -28,6 +28,7 @@ config MDIO_SHELL source "drivers/mdio/Kconfig.esp32" source "drivers/mdio/Kconfig.sam" source "drivers/mdio/Kconfig.nxp_s32" +source "drivers/mdio/Kconfig.adin2111" config MDIO_INIT_PRIORITY int "Init priority" diff --git a/drivers/mdio/Kconfig.adin2111 b/drivers/mdio/Kconfig.adin2111 new file mode 100644 index 00000000000000..778bbbc2544944 --- /dev/null +++ b/drivers/mdio/Kconfig.adin2111 @@ -0,0 +1,10 @@ +# Copyright 2023 PHOENIX CONTACT Electronics GmbH +# SPDX-License-Identifier: Apache-2.0 + +config MDIO_ADIN2111 + bool "NXP S32 NETC External MDIO driver" + default y + depends on DT_HAS_ADI_ADIN2111_MDIO_ENABLED + depends on ETH_ADIN2111 + help + Enable ADIN2111 MDIO driver. diff --git a/drivers/mdio/mdio_adin2111.c b/drivers/mdio/mdio_adin2111.c new file mode 100644 index 00000000000000..39d994c9cc3727 --- /dev/null +++ b/drivers/mdio/mdio_adin2111.c @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(mdio_adin2111, CONFIG_MDIO_LOG_LEVEL); + +#define DT_DRV_COMPAT adi_adin2111_mdio + +#include +#include +#include +#include +#include +#include +#include + +/* MDIO ready check retry delay */ +#define ADIN2111_MDIO_READY_AWAIT_DELAY_POLL_US 5U +/* Number of retries for MDIO ready check */ +#define ADIN2111_MDIO_READY_AWAIT_RETRY_COUNT 10U + +/* MDIO Access Register 1 */ +#define ADIN2111_MDIOACC0 0x20U +/* MDIO Access Register 2 */ +#define ADIN2111_MDIOACC1 0x21U + +/* MDIO MDIOACC Transaction Done */ +#define ADIN211_MDIOACC_MDIO_TRDONE BIT(31) + +struct mdio_adin2111_config { + const struct device *adin; +}; + +static int mdio_adin2111_wait_ready(const struct device *dev, uint16_t reg, + uint32_t *out) +{ + const struct mdio_adin2111_config *const cfg = dev->config; + uint32_t count; + int ret; + + for (count = 0U; count < ADIN2111_MDIO_READY_AWAIT_RETRY_COUNT; ++count) { + ret = eth_adin2111_reg_read(cfg->adin, reg, out); + if (ret >= 0) { + if ((*out) & ADIN211_MDIOACC_MDIO_TRDONE) { + break; + } + ret = -ETIMEDOUT; + } + k_sleep(K_USEC(ADIN2111_MDIO_READY_AWAIT_DELAY_POLL_US)); + } + + return ret; +} + + +int adin2111_mdio_c45_read(const struct device *dev, uint8_t prtad, + uint8_t devad, uint16_t regad, + uint16_t *data) +{ + const struct mdio_adin2111_config *const cfg = dev->config; + uint32_t rdy; + uint32_t cmd; + int ret; + + /* address op */ + cmd = (prtad & 0x1FU) << 21; + cmd |= (devad & 0x1FU) << 16; + cmd |= regad; + + ret = eth_adin2111_reg_write(cfg->adin, ADIN2111_MDIOACC0, cmd); + if (ret < 0) { + return ret; + } + + /* read op */ + cmd = (cmd & ~UINT16_MAX) | (0x3U << 26); + + ret = eth_adin2111_reg_write(cfg->adin, ADIN2111_MDIOACC1, cmd); + if (ret < 0) { + return ret; + } + + ret = mdio_adin2111_wait_ready(dev, ADIN2111_MDIOACC1, &rdy); + if (ret < 0) { + return ret; + } + + /* read out */ + ret = eth_adin2111_reg_read(cfg->adin, ADIN2111_MDIOACC1, &cmd); + + *data = cmd & UINT16_MAX; + + return ret; +} + +int adin2111_mdio_c45_write(const struct device *dev, uint8_t prtad, + uint8_t devad, uint16_t regad, + uint16_t data) +{ + const struct mdio_adin2111_config *const cfg = dev->config; + + uint32_t rdy; + uint32_t cmd; + int ret; + + /* address op */ + cmd = (prtad & 0x1FU) << 21; + cmd |= (devad & 0x1FU) << 16; + cmd |= regad; + + ret = eth_adin2111_reg_write(cfg->adin, ADIN2111_MDIOACC0, cmd); + if (ret < 0) { + return ret; + } + + /* write op */ + cmd |= BIT(26); + cmd = (cmd & ~UINT16_MAX) | data; + + ret = eth_adin2111_reg_write(cfg->adin, ADIN2111_MDIOACC1, cmd); + if (ret < 0) { + return ret; + } + + ret = mdio_adin2111_wait_ready(dev, ADIN2111_MDIOACC1, &rdy); + + return ret; +} + +static int mdio_adin2111_read(const struct device *dev, uint8_t prtad, + uint8_t devad, uint16_t *data) +{ + const struct mdio_adin2111_config *const cfg = dev->config; + uint32_t read; + uint32_t cmd; + int ret; + + cmd = BIT(28); + cmd |= 0x3U << 26; + cmd |= (prtad & 0x1FU) << 21; + cmd |= (devad & 0x1FU) << 16; + + ret = eth_adin2111_reg_write(cfg->adin, ADIN2111_MDIOACC0, cmd); + if (ret >= 0) { + ret = mdio_adin2111_wait_ready(dev, ADIN2111_MDIOACC0, &read); + *data = read & UINT16_MAX; + } + + return ret; +} + +static int mdio_adin2111_write(const struct device *dev, uint8_t prtad, + uint8_t devad, uint16_t data) +{ + const struct mdio_adin2111_config *const cfg = dev->config; + uint32_t cmd; + uint32_t rdy; + int ret; + + cmd = BIT(28); + cmd |= BIT(26); + cmd |= (prtad & 0x1FU) << 21; + cmd |= (devad & 0x1FU) << 16; + cmd |= data; + + ret = eth_adin2111_reg_write(cfg->adin, ADIN2111_MDIOACC0, cmd); + if (ret >= 0) { + ret = mdio_adin2111_wait_ready(dev, ADIN2111_MDIOACC0, &rdy); + } + + return ret; +} + +static void mdio_adin2111_bus_enable(const struct device *dev) +{ + const struct mdio_adin2111_config *const cfg = dev->config; + + eth_adin2111_lock(cfg->adin, K_FOREVER); +} + +static void mdio_adin2111_bus_disable(const struct device *dev) +{ + const struct mdio_adin2111_config *const cfg = dev->config; + + eth_adin2111_unlock(cfg->adin); +} + +static const struct mdio_driver_api mdio_adin2111_api = { + .read = mdio_adin2111_read, + .write = mdio_adin2111_write, + .bus_enable = mdio_adin2111_bus_enable, + .bus_disable = mdio_adin2111_bus_disable +}; + +#define ADIN2111_MDIO_INIT(n) \ + static const struct mdio_adin2111_config mdio_adin2111_config_##n = { \ + .adin = DEVICE_DT_GET(DT_INST_BUS(n)), \ + }; \ + DEVICE_DT_INST_DEFINE(n, NULL, NULL, \ + NULL, &mdio_adin2111_config_##n, \ + POST_KERNEL, CONFIG_MDIO_INIT_PRIORITY, \ + &mdio_adin2111_api); + +DT_INST_FOREACH_STATUS_OKAY(ADIN2111_MDIO_INIT) diff --git a/dts/bindings/mdio/adi,adin2111-mdio.yaml b/dts/bindings/mdio/adi,adin2111-mdio.yaml new file mode 100644 index 00000000000000..ab20d5cb33d093 --- /dev/null +++ b/dts/bindings/mdio/adi,adin2111-mdio.yaml @@ -0,0 +1,19 @@ +# Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH +# SPDX-License-Identifier: Apache-2.0 + +description: ADIN2111 MDIO Driver node + +compatible: "adi,adin2111-mdio" + +include: mdio-controller.yaml + +on-bus: adin2111 + +properties: + "#address-cells": + required: true + const: 1 + + "#size-cells": + required: true + const: 0 diff --git a/include/zephyr/drivers/mdio/mdio_adin2111.h b/include/zephyr/drivers/mdio/mdio_adin2111.h new file mode 100644 index 00000000000000..bf9718614345cc --- /dev/null +++ b/include/zephyr/drivers/mdio/mdio_adin2111.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_MDIO_ADIN2111_H__ +#define ZEPHYR_INCLUDE_DRIVERS_MDIO_ADIN2111_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Read from MDIO Bus using Clause 45 access + * + * @note The caller is responsible for device lock. + * Shall not be called from ISR. + * + * @param[in] dev MDIO device. + * @param[in] prtad Port address. + * @param[in] devad Device address. + * @param[in] regad Register address. + * @param[out] data Pointer to receive read data. + * + * @retval 0 If successful. + * @retval -EIO General input / output error. + * @retval -ETIMEDOUT If transaction timedout on the bus. + * @retval <0 Error, a negative errno code. + */ +int adin2111_mdio_c45_read(const struct device *dev, uint8_t prtad, + uint8_t devad, uint16_t regad, uint16_t *data); + +/** + * @brief Write to MDIO bus using Clause 45 access + * + * @note The caller is responsible for device lock. + * Shall not be called from ISR. + * + * @param[in] dev MDIO device. + * @param[in] prtad Port address. + * @param[in] devad Device address. + * @param[in] regad Register address. + * @param[in] data Data to write. + * + * @retval 0 If successful. + * @retval -EIO General input / output error. + * @retval -ETIMEDOUT If transaction timedout on the bus. + * @retval <0 Error, a negative errno code. + */ +int adin2111_mdio_c45_write(const struct device *dev, uint8_t prtad, + uint8_t devad, uint16_t regad, uint16_t data); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_MDIO_ADIN2111_H__ */ From 7eb27da6748039ef7ae401762c434aea614aae80 Mon Sep 17 00:00:00 2001 From: Georgij Cernysiov Date: Fri, 12 May 2023 15:00:55 +0200 Subject: [PATCH 3/4] drivers: mdio: shell: support adin2111 Adds supprot of ADIN2111 MDIO. The shell allows to access Clause 22 registers. Signed-off-by: Georgij Cernysiov --- drivers/mdio/mdio_shell.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mdio/mdio_shell.c b/drivers/mdio/mdio_shell.c index 6ea87d7017a956..a89cdb770bf086 100644 --- a/drivers/mdio/mdio_shell.c +++ b/drivers/mdio/mdio_shell.c @@ -20,6 +20,8 @@ LOG_MODULE_REGISTER(mdio_shell, CONFIG_LOG_DEFAULT_LEVEL); #define DT_DRV_COMPAT espressif_esp32_mdio #elif DT_HAS_COMPAT_STATUS_OKAY(nxp_s32_netc_emdio) #define DT_DRV_COMPAT nxp_s32_netc_emdio +#elif DT_HAS_COMPAT_STATUS_OKAY(adi_adin2111_mdio) +#define DT_DRV_COMPAT adi_adin2111_mdio #else #error "No known devicetree compatible match for MDIO shell" #endif From 489c53d79833d9f4ad0daa0d63f42a9e7729bda3 Mon Sep 17 00:00:00 2001 From: Georgij Cernysiov Date: Fri, 12 May 2023 15:03:06 +0200 Subject: [PATCH 4/4] drivers: phy: add adin2111 Adds PHY driver. Works via MDIO API and exposed ADIN2111 MDIO Clause 45 functions. Link status detection is triggered by ADIN2111 driver within offloaded IRQ handler. Supports: - LED0, LED1 enable/disable - Fatal HW error detection - AN 2.4V tx mode enable/disable The initialization order is important. PHY 2 must be initialized after PHY1. Therefore, it shall be defined after the 1st one in the devicetree. Signed-off-by: Georgij Cernysiov --- CODEOWNERS | 1 + drivers/ethernet/phy/CMakeLists.txt | 3 +- drivers/ethernet/phy/Kconfig | 11 +- drivers/ethernet/phy/phy_adin2111.c | 510 ++++++++++++++++++++ drivers/ethernet/phy/phy_adin2111_priv.h | 30 ++ dts/bindings/ethernet/adi,adin2111-phy.yaml | 25 + 6 files changed, 578 insertions(+), 2 deletions(-) create mode 100644 drivers/ethernet/phy/phy_adin2111.c create mode 100644 drivers/ethernet/phy/phy_adin2111_priv.h create mode 100644 dts/bindings/ethernet/adi,adin2111-phy.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 24732085cbd6ed..4828c64091d9bd 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -286,6 +286,7 @@ /drivers/ethernet/*smsc91x* @sgrrzhf /drivers/ethernet/*adin2111* @GeorgeCGV /drivers/ethernet/phy/ @rlubos @tbursztyka @arvinf +/drivers/ethernet/phy/*adin2111* @GeorgeCGV /drivers/mdio/ @rlubos @tbursztyka @arvinf /drivers/mdio/*adin2111* @GeorgeCGV /drivers/flash/ @nashif @de-nordic diff --git a/drivers/ethernet/phy/CMakeLists.txt b/drivers/ethernet/phy/CMakeLists.txt index 2592f9d4778e38..ce4cbe3e715f14 100644 --- a/drivers/ethernet/phy/CMakeLists.txt +++ b/drivers/ethernet/phy/CMakeLists.txt @@ -1,3 +1,4 @@ # SPDX-License-Identifier: Apache-2.0 -zephyr_library_sources_ifdef(CONFIG_PHY_GENERIC_MII phy_mii.c) +zephyr_library_sources_ifdef(CONFIG_PHY_GENERIC_MII phy_mii.c) +zephyr_library_sources_ifdef(CONFIG_PHY_ADIN2111 phy_adin2111.c) diff --git a/drivers/ethernet/phy/Kconfig b/drivers/ethernet/phy/Kconfig index 070bd8960cae3e..bd5d9fb48ed680 100644 --- a/drivers/ethernet/phy/Kconfig +++ b/drivers/ethernet/phy/Kconfig @@ -23,12 +23,21 @@ config PHY_INIT_PRIORITY config PHY_GENERIC_MII bool "Generic MII PHY Driver" - default y + default y if !ETH_ADIN2111 depends on MDIO help This is a generic MII PHY interface that communicates with the PHY using the MDIO bus. +config PHY_ADIN2111 + bool "ADIN2111 PHY driver" + default y + depends on DT_HAS_ADI_ADIN2111_PHY_ENABLED + depends on ETH_ADIN2111 + depends on MDIO_ADIN2111 + help + Enable ADIN2111 PHY driver. + config PHY_AUTONEG_TIMEOUT_MS int "Auto-negotiation timeout value in milliseconds" default 4000 diff --git a/drivers/ethernet/phy/phy_adin2111.c b/drivers/ethernet/phy/phy_adin2111.c new file mode 100644 index 00000000000000..843d2d7fc54d76 --- /dev/null +++ b/drivers/ethernet/phy/phy_adin2111.c @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(phy_adin2111, CONFIG_PHY_LOG_LEVEL); + +#define DT_DRV_COMPAT adi_adin2111_phy + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PHYs out of reset check retry delay */ +#define ADIN2111_PHY_AWAIT_DELAY_POLL_US 15U +/* Number of retries for PHYs out of reset check */ +#define ADIN2111_PHY_AWAIT_RETRY_COUNT 200U + +/* PHY's software powerdown check retry delay */ +#define ADIN2111_PHY_SFT_PD_DELAY_POLL_US 15U +/* Number of retries for PHY's software powerdown check */ +#define ADIN2111_PHY_SFT_PD_RETRY_COUNT 200U + +/* PHYs autonegotiation complete timeout */ +#define ADIN2111_AN_COMPLETE_AWAIT_TIMEOUT_MS 3000U + +/* ADIN2111 PHY identifier */ +#define ADIN2111_PHY_ID 0x0283BCA1U + +/* 10BASE-T1L PMA Status Register */ +#define ADIN2111_PHY_PMA_STATUS 0x000108F7U +/* Indicates PHY support of 10BASE-T1L high voltage (2.4V) tx level op mode */ +#define ADIN2111_PHY_PMA_STATUS_B10L_TX_LVL_HI_ABLE BIT(12) + +/* BASE-T1 Autonegotiation Control Register */ +#define ADIN2111_PHY_AN_CONTROL 0x00070200U +/* Autonegotiation Enable */ +#define ADIN2111_PHY_AN_CONTROL_AN_EN BIT(12) +/* Autonegotiation Restart */ +#define ADIN2111_PHY_AN_CONTROL_AN_RESTART BIT(9) + +/* BASE-T1 Autonegotiation Status Register */ +#define ADIN2111_PHY_AN_STATUS 0x00070201U +/* Autonegotiation Complete */ +#define ADIN2111_PHY_AN_STATUS_AN_COMPLETE BIT(5) +/* Link Status */ +#define ADIN2111_PHY_AN_STATUS_AN_LINK_STATUS BIT(2) + +/* 10BASE-T1 Autonegotiation Advertisement Register */ +#define ADIN2111_PHY_AN_ADV_ABILITY_H 0x00070204U +/* Advertise PHY capability of 2.4V tx level op mode */ +#define ADIN2111_PHY_AN_ADV_ABILITY_H_B10L_TX_LVL_HI_ABL BIT(13) +/* Advertise PHY request of 2.4V tx level op mode */ +#define ADIN2111_PHY_AN_ADV_ABILITY_H_B10L_TX_LVL_HI_REQ BIT(12) + +/* System Interrupt Mask Register */ +#define ADIN2111_PHY_CRSM_IRQ_MASK 0x001E0020U +/* System Interrupt Status Register */ +#define ADIN2111_PHY_CRSM_IRQ_STATUS 0x001E0010U +/** + * Mask of reserved interrupts that indicates a fatal error in the system. + * + * There is inconsistency between RM and ADI driver example: + * - RM mask 0x6FFF + * - ADI driver example mask 0x2BFF + * + * The value from the example doesn't include reserved bits 10 and 14. + * The tests show that PHY is still functioning when bit 10 is raised. + * + * Here the value from ADI driver example is used instead of RM. + */ +#define ADIN2111_PHY_CRSM_IRQ_STATUS_FATAL_ERR 0x2BFFU + +/* PHY Subsystem Interrupt Mask Register */ +#define ADIN2111_PHY_SUBSYS_IRQ_MASK 0x001F0021U +/* PHY Subsystem Interrupt Status Register */ +#define ADIN2111_PHY_SUBSYS_IRQ_STATUS 0x001F0011U +/* Link Status Change */ +#define ADIN2111_PHY_SUBSYS_IRQ_STATUS_LINK_STAT_CHNG_LH BIT(1) + +/* Software Power-down Control Register */ +#define ADIN2111_PHY_CRSM_SFT_PD_CNTRL 0x001E8812U +/* System Status Register */ +#define ADIN2111_PHY_CRSM_STAT 0x001E8818U +/* Software Power-down Status */ +#define ADIN2111_CRSM_STAT_CRSM_SFT_PD_RDY BIT(1) + +/* LED Control Register */ +#define ADIN2111_PHY_LED_CNTRL 0x001E8C82U +/* LED 1 Enable */ +#define ADIN2111_PHY_LED_CNTRL_LED1_EN BIT(15) +/* LED 0 Enable */ +#define ADIN2111_PHY_LED_CNTRL_LED0_EN BIT(7) + +struct phy_adin2111_config { + const struct device *mdio; + uint8_t phy_addr; + bool led0_en; + bool led1_en; + bool tx_24v; +}; + +struct phy_adin2111_data { + struct phy_link_state state; + struct k_sem sem; +}; + +static inline int phy_adin2111_c22_read(const struct device *dev, uint16_t reg, + uint16_t *val) +{ + const struct phy_adin2111_config *const cfg = dev->config; + + return mdio_read(cfg->mdio, cfg->phy_addr, reg, val); +} + +static inline int phy_adin2111_c22_write(const struct device *dev, uint16_t reg, + uint16_t val) +{ + const struct phy_adin2111_config *const cfg = dev->config; + + return mdio_write(cfg->mdio, cfg->phy_addr, reg, val); +} + +static inline int phy_adin2111_c45_write(const struct device *dev, uint32_t reg, + uint16_t val) +{ + const struct phy_adin2111_config *cfg = dev->config; + + return adin2111_mdio_c45_write(cfg->mdio, cfg->phy_addr, ((reg >> 16U) & 0x1FU), + (reg & UINT16_MAX), val); +} + +static inline int phy_adin2111_c45_read(const struct device *dev, uint32_t reg, + uint16_t *val) +{ + const struct phy_adin2111_config *cfg = dev->config; + + return adin2111_mdio_c45_read(cfg->mdio, cfg->phy_addr, ((reg >> 16U) & 0x1FU), + (reg & 0xFFFFU), val); +} + +static int phy_adin2111_reg_read(const struct device *dev, uint16_t reg_addr, + uint32_t *data) +{ + const struct phy_adin2111_config *cfg = dev->config; + int ret; + + mdio_bus_enable(cfg->mdio); + + ret = phy_adin2111_c22_read(dev, reg_addr, (uint16_t *) data); + + mdio_bus_disable(cfg->mdio); + + return ret; +} + +static int phy_adin2111_reg_write(const struct device *dev, uint16_t reg_addr, + uint32_t data) +{ + const struct phy_adin2111_config *cfg = dev->config; + int ret; + + mdio_bus_enable(cfg->mdio); + + ret = phy_adin2111_c22_write(dev, reg_addr, (uint16_t) data); + + mdio_bus_disable(cfg->mdio); + + return ret; +} + +static int phy_adin2111_await_phy(const struct device *dev) +{ + int ret; + uint32_t count; + uint16_t val; + + /** + * Port 2 PHY comes out of reset after Port 1 PHY, + * wait until both are out of reset. + * Reading Port 2 PHY registers returns 0s until + * it comes out from reset. + */ + for (count = 0U; count < ADIN2111_PHY_AWAIT_RETRY_COUNT; ++count) { + ret = phy_adin2111_c45_read(dev, ADIN2111_PHY_CRSM_IRQ_MASK, &val); + if (ret >= 0) { + if (val != 0U) { + break; + } + ret = -ETIMEDOUT; + } + k_sleep(K_USEC(ADIN2111_PHY_AWAIT_DELAY_POLL_US)); + } + + return ret; +} + +static int phy_adin2111_an_state_read(const struct device *dev) +{ + struct phy_adin2111_data *const data = dev->data; + uint16_t bmsr; + int ret; + + /* read twice to get actual link status, latch low */ + ret = phy_adin2111_c22_read(dev, MII_BMSR, &bmsr); + if (ret < 0) { + return ret; + } + ret = phy_adin2111_c22_read(dev, MII_BMSR, &bmsr); + if (ret < 0) { + return ret; + } + + data->state.is_up = !!(bmsr & MII_BMSR_LINK_STATUS); + + return 0; +} + +int phy_adin2111_handle_phy_irq(const struct device *dev, + struct phy_link_state *state) +{ + struct phy_adin2111_data *const data = dev->data; + uint16_t subsys_status; + int ret; + + ret = phy_adin2111_c45_read(dev, ADIN2111_PHY_SUBSYS_IRQ_STATUS, + &subsys_status); + if (ret < 0) { + return ret; + } + + if ((subsys_status & ADIN2111_PHY_SUBSYS_IRQ_STATUS_LINK_STAT_CHNG_LH) == 0U) { + /* nothing to process */ + return -EAGAIN; + } + + k_sem_take(&data->sem, K_FOREVER); + + ret = phy_adin2111_an_state_read(dev); + + memcpy(state, &data->state, sizeof(struct phy_link_state)); + + k_sem_give(&data->sem); + + return ret; +} + +static int phy_adin2111_sft_pd(const struct device *dev, bool enter) +{ + int ret; + uint32_t count; + const uint16_t expected = enter ? ADIN2111_CRSM_STAT_CRSM_SFT_PD_RDY : 0U; + uint16_t val; + + ret = phy_adin2111_c45_write(dev, ADIN2111_PHY_CRSM_SFT_PD_CNTRL, + enter ? 1U : 0U); + if (ret < 0) { + return ret; + } + + for (count = 0U; count < ADIN2111_PHY_SFT_PD_RETRY_COUNT; ++count) { + ret = phy_adin2111_c45_read(dev, ADIN2111_PHY_CRSM_STAT, + &val); + if (ret >= 0) { + if ((val & ADIN2111_CRSM_STAT_CRSM_SFT_PD_RDY) == expected) { + break; + } + ret = -ETIMEDOUT; + } + k_sleep(K_USEC(ADIN2111_PHY_SFT_PD_DELAY_POLL_US)); + } + + return ret; +} + +static int phy_adin2111_id(const struct device *dev, uint32_t *phy_id) +{ + uint16_t val; + + if (phy_adin2111_c22_read(dev, MII_PHYID1R, &val) < 0) { + return -EIO; + } + + *phy_id = (val & UINT16_MAX) << 16; + + if (phy_adin2111_c22_read(dev, MII_PHYID2R, &val) < 0) { + return -EIO; + } + + *phy_id |= (val & UINT16_MAX); + + return 0; +} + +static int phy_adin2111_get_link_state(const struct device *dev, + struct phy_link_state *state) +{ + struct phy_adin2111_data *const data = dev->data; + + k_sem_take(&data->sem, K_FOREVER); + + memcpy(state, &data->state, sizeof(struct phy_link_state)); + + k_sem_give(&data->sem); + + return 0; +} + +static int phy_adin2111_cfg_link(const struct device *dev, + enum phy_link_speed adv_speeds) +{ + ARG_UNUSED(dev); + + if (!!(adv_speeds & LINK_FULL_10BASE_T)) { + return 0; + } + + return -ENOTSUP; +} + +static int phy_adin2111_init(const struct device *dev) +{ + const struct phy_adin2111_config *const cfg = dev->config; + struct phy_adin2111_data *const data = dev->data; + uint32_t phy_id; + uint16_t val; + bool tx_24v_supported = false; + int ret; + + data->state.is_up = false; + data->state.speed = LINK_FULL_10BASE_T; + + ret = phy_adin2111_await_phy(dev); + if (ret < 0) { + LOG_ERR("PHY %u didn't come out of reset, %d", + cfg->phy_addr, ret); + return -ENODEV; + } + + ret = phy_adin2111_id(dev, &phy_id); + if (ret < 0) { + LOG_ERR("Failed to read PHY %u ID, %d", + cfg->phy_addr, ret); + return -ENODEV; + } + + if (phy_id != ADIN2111_PHY_ID) { + LOG_ERR("PHY %u unexpected PHY ID %X", cfg->phy_addr, phy_id); + return -EINVAL; + } + + LOG_INF("PHY %u ID %X", cfg->phy_addr, phy_id); + + /* enter software powerdown */ + ret = phy_adin2111_sft_pd(dev, true); + if (ret < 0) { + return ret; + } + + /* disable interrupts */ + ret = phy_adin2111_c45_write(dev, ADIN2111_PHY_CRSM_IRQ_MASK, 0U); + if (ret < 0) { + return ret; + } + + /* enable link status change irq */ + ret = phy_adin2111_c45_write(dev, ADIN2111_PHY_SUBSYS_IRQ_MASK, + ADIN2111_PHY_SUBSYS_IRQ_STATUS_LINK_STAT_CHNG_LH); + if (ret < 0) { + return ret; + } + + /* clear PHY IRQ status before enabling ADIN IRQs */ + ret = phy_adin2111_c45_read(dev, ADIN2111_PHY_CRSM_IRQ_STATUS, &val); + if (ret < 0) { + return ret; + } + + if (val & ADIN2111_PHY_CRSM_IRQ_STATUS_FATAL_ERR) { + LOG_ERR("PHY %u CRSM reports fatal system error", cfg->phy_addr); + return -ENODEV; + } + + ret = phy_adin2111_c45_read(dev, ADIN2111_PHY_SUBSYS_IRQ_STATUS, &val); + if (ret < 0) { + return ret; + } + + if (!cfg->led0_en || !cfg->led1_en) { + ret = phy_adin2111_c45_read(dev, ADIN2111_PHY_LED_CNTRL, &val); + if (ret < 0) { + return ret; + } + if (!cfg->led0_en) { + val &= ~(ADIN2111_PHY_LED_CNTRL_LED0_EN); + } + if (!cfg->led1_en) { + val &= ~(ADIN2111_PHY_LED_CNTRL_LED1_EN); + } + ret = phy_adin2111_c45_write(dev, ADIN2111_PHY_LED_CNTRL, val); + if (ret < 0) { + return ret; + } + } + + /* check 2.4V support */ + ret = phy_adin2111_c45_read(dev, ADIN2111_PHY_PMA_STATUS, &val); + if (ret < 0) { + return ret; + } + + tx_24v_supported = !!(val & ADIN2111_PHY_PMA_STATUS_B10L_TX_LVL_HI_ABLE); + + LOG_INF("PHY %u 2.4V mode %s", cfg->phy_addr, + tx_24v_supported ? "supported" : "not supported"); + + if (!cfg->tx_24v & tx_24v_supported) { + LOG_ERR("PHY %u 2.4V mode supported, but not enabled", + cfg->phy_addr); + } + + /* config 2.4V auto-negotiation */ + ret = phy_adin2111_c45_read(dev, ADIN2111_PHY_AN_ADV_ABILITY_H, + &val); + if (ret < 0) { + return ret; + } + + if (tx_24v_supported) { + val |= ADIN2111_PHY_AN_ADV_ABILITY_H_B10L_TX_LVL_HI_ABL; + } else { + val &= ~ADIN2111_PHY_AN_ADV_ABILITY_H_B10L_TX_LVL_HI_ABL; + } + + if (cfg->tx_24v) { + if (!tx_24v_supported) { + LOG_ERR("PHY %u 2.4V mode enabled, but not supported", + cfg->phy_addr); + return -EINVAL; + } + + val |= ADIN2111_PHY_AN_ADV_ABILITY_H_B10L_TX_LVL_HI_REQ; + } else { + val &= ~ADIN2111_PHY_AN_ADV_ABILITY_H_B10L_TX_LVL_HI_REQ; + } + + ret = phy_adin2111_c45_write(dev, ADIN2111_PHY_AN_ADV_ABILITY_H, + val); + if (ret < 0) { + return ret; + } + + /* enable auto-negotiation */ + ret = phy_adin2111_c45_write(dev, ADIN2111_PHY_AN_CONTROL, + ADIN2111_PHY_AN_CONTROL_AN_EN); + if (ret < 0) { + return ret; + } + + /** + * done, PHY is in software powerdown (SFT PD) + * exit software powerdown, PHY 1 has to exit before PHY 2 + * correct PHY order is expected to be in DTS to guarantee that + */ + return phy_adin2111_sft_pd(dev, false); +} + +static int phy_adin2111_link_cb_set(const struct device *dev, phy_callback_t cb, + void *user_data) +{ + ARG_UNUSED(dev); + ARG_UNUSED(cb); + ARG_UNUSED(user_data); + return -ENOTSUP; +} + +static const struct ethphy_driver_api phy_adin2111_api = { + .get_link = phy_adin2111_get_link_state, + .cfg_link = phy_adin2111_cfg_link, + .link_cb_set = phy_adin2111_link_cb_set, + .read = phy_adin2111_reg_read, + .write = phy_adin2111_reg_write, +}; + +#define ADIN2111_PHY_INITIALIZE(n) \ + static const struct phy_adin2111_config phy_adin2111_config_##n = { \ + .mdio = DEVICE_DT_GET(DT_INST_BUS(n)), \ + .phy_addr = DT_INST_REG_ADDR(n), \ + .led0_en = DT_INST_PROP(n, led0_en), \ + .led1_en = DT_INST_PROP(n, led1_en), \ + .tx_24v = !(DT_INST_PROP(n, disable_tx_mode_24v)), \ + }; \ + static struct phy_adin2111_data phy_adin2111_data_##n = { \ + .sem = Z_SEM_INITIALIZER(phy_adin2111_data_##n.sem, 1, 1), \ + }; \ + DEVICE_DT_INST_DEFINE(n, &phy_adin2111_init, NULL, \ + &phy_adin2111_data_##n, &phy_adin2111_config_##n, \ + POST_KERNEL, CONFIG_PHY_INIT_PRIORITY, \ + &phy_adin2111_api); + +DT_INST_FOREACH_STATUS_OKAY(ADIN2111_PHY_INITIALIZE) diff --git a/drivers/ethernet/phy/phy_adin2111_priv.h b/drivers/ethernet/phy/phy_adin2111_priv.h new file mode 100644 index 00000000000000..dc0c2d0574d8b2 --- /dev/null +++ b/drivers/ethernet/phy/phy_adin2111_priv.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef PHY_ADIN2111_PRIV_H__ +#define PHY_ADIN2111_PRIV_H__ + +#include +#include + +/** + * @brief Handles PHY interrupt. + * + * @note Used internally by the ADIN offloaded ISR handler. + * The caller is responsible for device lock. + * Shall not be called from ISR. + * + * @param[in] dev PHY device. + * @param[out] state Output of the link state. + * + * @retval 0 Successful and link state changed. + * @retval -EAGAIN Successful but link state didn't change. + * @retval <0 MDIO error. + */ +int phy_adin2111_handle_phy_irq(const struct device *dev, + struct phy_link_state *state); + +#endif /* PHY_ADIN2111_PRIV_H__ */ diff --git a/dts/bindings/ethernet/adi,adin2111-phy.yaml b/dts/bindings/ethernet/adi,adin2111-phy.yaml new file mode 100644 index 00000000000000..ca7bb7efc61b38 --- /dev/null +++ b/dts/bindings/ethernet/adi,adin2111-phy.yaml @@ -0,0 +1,25 @@ +# Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH +# SPDX-License-Identifier: Apache-2.0 + +description: ADIN2111 PHY + +compatible: "adi,adin2111-phy" + +include: phy.yaml + +on-bus: mdio + +properties: + reg: + required: true + description: 5-bit physical/port address (PRTAD). + led0-en: + type: boolean + description: Enable LED 0. + led1-en: + type: boolean + description: Enable LED 1. + disable-tx-mode-24v: + type: boolean + description: | + Disables requirement of 2.4V TX operating mode in the AN advertisement.