diff --git a/CODEOWNERS b/CODEOWNERS index bcbac8a72ffe54..4828c64091d9bd 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -284,8 +284,11 @@ /drivers/ethernet/*w5500* @parthitce /drivers/ethernet/*xlnx_gem* @ibirnbaum /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 /drivers/flash/*stm32_qspi* @lmajewski /drivers/flash/*b91* @andy-liu-telink 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/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/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/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 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. 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/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/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__ */ 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__ */