/* * Copyright (c) 2023 PHOENIX CONTACT Electronics GmbH * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(eth_adin2111, CONFIG_ETHERNET_LOG_LEVEL); #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; const struct adin2111_config *adin_cfg = dev->config; bool is_adin2111 = (adin_cfg->id == ADIN2111_MAC); 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) && is_adin2111) { 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) && is_adin2111) { 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) { const struct adin2111_config *cfg = dev->config; bool is_adin2111 = (cfg->id == ADIN2111_MAC); uint8_t mm[6] = {BIT(0), 0U, 0U, 0U, 0U, 0U}; uint32_t rules = ADIN2111_ADDR_APPLY2PORT1 | (is_adin2111 ? ADIN2111_ADDR_APPLY2PORT2 : 0) | 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) { const struct adin2111_config *cfg = dev->config; bool is_adin2111 = (cfg->id == ADIN2111_MAC); uint8_t mac[] = {0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU}; uint32_t rules = ADIN2111_ADDR_APPLY2PORT1 | (is_adin2111 ? ADIN2111_ADDR_APPLY2PORT2 : 0) | 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 || val == ADIN1110_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; bool is_adin2111 = (cfg->id == ADIN2111_MAC); 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 | (is_adin2111 ? ADIN2111_CONFIG2_P1_FWD_UNK2P2 : 0) | (is_adin2111 ? ADIN2111_CONFIG2_P2_FWD_UNK2P1 : 0)); 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 | (is_adin2111 ? ADIN2111_IMASK1_P2_RX_RDY_MASK : 0) | (is_adin2111 ? ADIN2111_IMASK1_P2_PHYINT_MASK : 0)); /* 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), ethernet_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, dev_id, ifaces) \ static uint8_t __aligned(4) adin2111_buffer_##inst[CONFIG_ETH_ADIN2111_BUFFER_SIZE]; \ static const struct adin2111_config adin2111_config_##inst = { \ .id = dev_id, \ .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 = ifaces, \ .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); #define ADIN2111_MAC_INIT(inst) ADIN2111_MAC_INITIALIZE(inst, ADIN2111_MAC, 2) \ /* ports */ \ ADIN2111_PORT_DEVICE_INIT_INSTANCE(inst, 0, 1) \ ADIN2111_PORT_DEVICE_INIT_INSTANCE(inst, 1, 2) #undef DT_DRV_COMPAT #define DT_DRV_COMPAT adi_adin2111 DT_INST_FOREACH_STATUS_OKAY(ADIN2111_MAC_INIT) #define ADIN1110_MAC_INIT(inst) ADIN2111_MAC_INITIALIZE(inst, ADIN1110_MAC, 1) \ /* ports */ \ ADIN2111_PORT_DEVICE_INIT_INSTANCE(inst, 0, 1) #undef DT_DRV_COMPAT #define DT_DRV_COMPAT adi_adin1110 DT_INST_FOREACH_STATUS_OKAY(ADIN1110_MAC_INIT)