diff --git a/CODEOWNERS b/CODEOWNERS index f3d2bb732c9..1db37fb9dd5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -306,6 +306,7 @@ /drivers/ethernet/*smsc91x* @sgrrzhf /drivers/ethernet/*adin2111* @GeorgeCGV /drivers/ethernet/*oa_tc6* @lmajewski +/drivers/ethernet/*lan865x* @lmajewski /drivers/ethernet/phy/ @rlubos @tbursztyka @arvinf @jukkar /drivers/ethernet/phy/*adin2111* @GeorgeCGV /drivers/mdio/ @rlubos @tbursztyka @arvinf diff --git a/drivers/ethernet/CMakeLists.txt b/drivers/ethernet/CMakeLists.txt index 20cc367fe5b..1ae6ef58e82 100644 --- a/drivers/ethernet/CMakeLists.txt +++ b/drivers/ethernet/CMakeLists.txt @@ -34,6 +34,7 @@ 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) +zephyr_library_sources_ifdef(CONFIG_ETH_LAN865X eth_lan865x.c oa_tc6.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 eae83755af0..69b60e09bc2 100644 --- a/drivers/ethernet/Kconfig +++ b/drivers/ethernet/Kconfig @@ -62,6 +62,7 @@ source "drivers/ethernet/Kconfig.smsc91x" source "drivers/ethernet/Kconfig.ivshmem" source "drivers/ethernet/Kconfig.adin2111" source "drivers/ethernet/Kconfig.numaker" +source "drivers/ethernet/Kconfig.lan865x" source "drivers/ethernet/phy/Kconfig" diff --git a/drivers/ethernet/Kconfig.lan865x b/drivers/ethernet/Kconfig.lan865x new file mode 100644 index 00000000000..c3bdc2f9b73 --- /dev/null +++ b/drivers/ethernet/Kconfig.lan865x @@ -0,0 +1,50 @@ +# Copyright (c) 2023 DENX Software Engineering GmbH +# SPDX-License-Identifier: Apache-2.0 + +menuconfig ETH_LAN865X + bool "LAN865X 10BASE-T1S Controller" + default y + depends on DT_HAS_MICROCHIP_LAN865X_ENABLED + select SPI + help + The LAN865X is a low power, 10BASE-T1S transceiver 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 PHY, + the LAN865X enables direct connectivity with a variety of controllers + via a serial peripheral inter-face (SPI). + +if ETH_LAN865X + +config ETH_LAN865X_INIT_PRIORITY + int "LAN865X driver init priority" + default 72 + help + LAN865X device driver initialization priority. + Must be initialized after SPI. + + +config ETH_LAN865X_IRQ_THREAD_STACK_SIZE + int "Stack size for a thread that processes IRQ" + default 512 + help + Size of the stack used for internal thread which is ran to + process raised INT IRQ. + +config ETH_LAN865X_IRQ_THREAD_PRIO + int "Priority for internal incoming packet handler" + default 2 + help + Priority level for internal thread which is ran for LAN + INT IRQ processing. + +config ETH_LAN865X_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. + +endif # ETH_LAN865X diff --git a/drivers/ethernet/eth_lan865x.c b/drivers/ethernet/eth_lan865x.c new file mode 100644 index 00000000000..8107b7d3542 --- /dev/null +++ b/drivers/ethernet/eth_lan865x.c @@ -0,0 +1,585 @@ +/* + * Copyright (c) 2023 DENX Software Engineering GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT microchip_lan865x + +#include +LOG_MODULE_REGISTER(eth_lan865x, CONFIG_ETHERNET_LOG_LEVEL); + +#include +#include + +#include +#include + +#include +#include +#include + +#include "eth_lan865x_priv.h" + +static int lan865x_mac_rxtx_control(const struct device *dev, bool en) +{ + struct lan865x_data *ctx = dev->data; + uint32_t ctl = 0; + + if (en) { + ctl = LAN865x_MAC_NCR_TXEN | LAN865x_MAC_NCR_RXEN; + } + + return oa_tc6_reg_write(ctx->tc6, LAN865x_MAC_NCR, ctl); +} + +static void lan865x_iface_init(struct net_if *iface) +{ + const struct device *dev = net_if_get_device(iface); + struct lan865x_data *ctx = dev->data; + + net_if_set_link_addr(iface, ctx->mac_address, sizeof(ctx->mac_address), + NET_LINK_ETHERNET); + + if (ctx->iface == NULL) { + ctx->iface = iface; + } + + ethernet_init(iface); + + net_eth_carrier_on(iface); + ctx->iface_initialized = true; +} + +static enum ethernet_hw_caps lan865x_port_get_capabilities(const struct device *dev) +{ + ARG_UNUSED(dev); + return ETHERNET_LINK_10BASE_T | ETHERNET_PROMISC_MODE; +} + +static void lan865x_write_macaddress(const struct device *dev); +static int lan865x_set_config(const struct device *dev, enum ethernet_config_type type, + const struct ethernet_config *config) +{ + struct lan865x_data *ctx = dev->data; + int ret = -ENOTSUP; + + if (type == ETHERNET_CONFIG_TYPE_PROMISC_MODE) { + ret = lan865x_mac_rxtx_control(dev, LAN865x_MAC_TXRX_OFF); + if (ret) { + return ret; + } + + ret = oa_tc6_reg_write(ctx->tc6, LAN865x_MAC_NCFGR, + LAN865x_MAC_NCFGR_CAF); + if (ret) { + return ret; + } + + return lan865x_mac_rxtx_control(dev, LAN865x_MAC_TXRX_ON); + } + + if (type == ETHERNET_CONFIG_TYPE_MAC_ADDRESS) { + ret = lan865x_mac_rxtx_control(dev, LAN865x_MAC_TXRX_OFF); + if (ret) { + return ret; + } + + memcpy(ctx->mac_address, config->mac_address.addr, + sizeof(ctx->mac_address)); + + lan865x_write_macaddress(dev); + + net_if_set_link_addr(ctx->iface, ctx->mac_address, + sizeof(ctx->mac_address), + NET_LINK_ETHERNET); + + return lan865x_mac_rxtx_control(dev, LAN865x_MAC_TXRX_ON); + } + + return ret; +} + +static int lan865x_wait_for_reset(const struct device *dev) +{ + struct lan865x_data *ctx = dev->data; + uint8_t i; + + /* Wait for end of LAN865x reset */ + for (i = 0; !ctx->reset && i < LAN865X_RESET_TIMEOUT; i++) { + k_msleep(1); + } + + if (i == LAN865X_RESET_TIMEOUT) { + LOG_ERR("LAN865x reset timeout reached!"); + return -ENODEV; + } + + return 0; +} + +static int lan865x_gpio_reset(const struct device *dev) +{ + const struct lan865x_config *cfg = dev->config; + + /* Perform (GPIO based) HW reset */ + /* assert RESET_N low for 10 µs (5 µs min) */ + gpio_pin_set_dt(&cfg->reset, 1); + k_busy_wait(10U); + /* deassert - end of reset indicated by IRQ_N low */ + gpio_pin_set_dt(&cfg->reset, 0); + + return lan865x_wait_for_reset(dev); +} + +static int lan865x_check_spi(const struct device *dev) +{ + struct lan865x_data *ctx = dev->data; + uint32_t val; + int ret; + + ret = oa_tc6_reg_read(ctx->tc6, LAN865x_DEVID, &val); + if (ret < 0) { + return -ENODEV; + } + + ctx->silicon_rev = val & LAN865X_REV_MASK; + if (ctx->silicon_rev != 1 && ctx->silicon_rev != 2) { + return -ENODEV; + } + + ctx->chip_id = (val >> 4) & 0xFFFF; + if (ctx->chip_id != LAN8650_DEVID && ctx->chip_id != LAN8651_DEVID) { + return -ENODEV; + } + + return ret; +} + +/* Implementation of pseudo code from AN1760 */ +static uint8_t lan865x_read_indirect_reg(const struct device *dev, uint8_t addr, + uint8_t mask) +{ + struct lan865x_data *ctx = dev->data; + uint32_t val; + + oa_tc6_reg_write(ctx->tc6, 0x000400D8, addr); + oa_tc6_reg_write(ctx->tc6, 0x000400DA, 0x02); + + oa_tc6_reg_read(ctx->tc6, 0x000400D9, &val); + + return (uint8_t) val & mask; +} + +static int lan865x_init_chip(const struct device *dev, uint8_t silicon_rev) +{ + struct lan865x_data *ctx = dev->data; + uint8_t value1, value2; + int8_t offset1 = 0, offset2 = 0, ret; + uint16_t value3, value4, value5, value6, value7; + uint16_t cfgparam1, cfgparam2, cfgparam3, cfgparam4, cfgparam5; + uint32_t val; + + ret = lan865x_read_indirect_reg(dev, 0x05, 0x40); + if (ret == 0) { + LOG_ERR("LAN865x error! Please contact microchip support for replacement."); + return -EIO; + } + + value1 = lan865x_read_indirect_reg(dev, 0x04, 0x1F); + if ((value1 & 0x10) != 0) { /* Convert uint8_t to int8_t */ + offset1 = value1 | 0xE0; + if (offset1 < -5) { + LOG_ERR("LAN865x internal error!"); + return -EIO; + } + } else { + offset1 = value1; + } + + value2 = lan865x_read_indirect_reg(dev, 0x08, 0x1F); + if ((value2 & 0x10) != 0) { /* Convert uint8_t to int8_t */ + offset2 = value2 | 0xE0; + } else { + offset2 = value2; + } + + oa_tc6_reg_read(ctx->tc6, 0x00040084, &val); + value3 = (uint16_t)val; + + oa_tc6_reg_read(ctx->tc6, 0x0004008A, &val); + value4 = (uint16_t)val; + + oa_tc6_reg_read(ctx->tc6, 0x000400AD, &val); + value5 = (uint16_t)val; + + oa_tc6_reg_read(ctx->tc6, 0x000400AE, &val); + value6 = (uint8_t)val; + + oa_tc6_reg_read(ctx->tc6, 0x000400AF, &val); + value7 = (uint8_t)val; + + cfgparam1 = (value3 & 0xF) | (((9 + offset1) << 10) | ((14 + offset1) << 4)); + cfgparam2 = (value4 & 0x3FF) | ((40 + offset2) << 10); + cfgparam3 = (value5 & 0xC0C0) | (((5 + offset1) << 8) | (9 + offset1)); + cfgparam4 = (value6 & 0xC0C0) | (((9 + offset1) << 8) | (14 + offset1)); + cfgparam5 = (value7 & 0xC0C0) | (((17 + offset1) << 8) | (22 + offset1)); + + oa_tc6_reg_write(ctx->tc6, 0x00040084, (uint32_t) cfgparam1); + oa_tc6_reg_write(ctx->tc6, 0x0004008A, (uint32_t) cfgparam2); + oa_tc6_reg_write(ctx->tc6, 0x000400AD, (uint32_t) cfgparam3); + oa_tc6_reg_write(ctx->tc6, 0x000400AE, (uint32_t) cfgparam4); + oa_tc6_reg_write(ctx->tc6, 0x000400AF, (uint32_t) cfgparam5); + + return 0; +} +/* Implementation of pseudo code from AN1760 - END */ + +static int lan865x_config_plca(const struct device *dev, uint8_t node_id, + uint8_t node_cnt, uint8_t burst_cnt, uint8_t burst_timer) +{ + struct lan865x_data *ctx = dev->data; + uint32_t val; + + /* Collision Detection */ + oa_tc6_reg_write(ctx->tc6, 0x00040087, 0x0083u); /* COL_DET_CTRL0 */ + + /* T1S Phy Node Id and Max Node Count */ + val = ((uint32_t)node_cnt << 8) | node_id; + oa_tc6_reg_write(ctx->tc6, 0x0004CA02, val); /* PLCA_CONTROL_1_REGISTER */ + + /* PLCA Burst Count and Burst Timer */ + val = ((uint32_t)burst_cnt << 8) | burst_timer; + oa_tc6_reg_write(ctx->tc6, 0x0004CA05, val); /* PLCA_BURST_MODE_REGISTER */ + + /* Enable PLCA */ + oa_tc6_reg_write(ctx->tc6, 0x0004CA01, BIT(15)); /* PLCA_CONTROL_0_REGISTER */ + + return 0; +} + +static void lan865x_write_macaddress(const struct device *dev) +{ + struct lan865x_data *ctx = dev->data; + uint8_t *mac = &ctx->mac_address[0]; + uint32_t val; + + /* SPEC_ADD2_BOTTOM */ + val = (mac[3] << 24) | (mac[2] << 16) | (mac[1] << 8) | mac[0]; + oa_tc6_reg_write(ctx->tc6, LAN865x_MAC_SAB2, val); + + /* SPEC_ADD2_TOP */ + val = (mac[5] << 8) | mac[4]; + oa_tc6_reg_write(ctx->tc6, LAN865x_MAC_SAT2, val); + + /* SPEC_ADD1_BOTTOM */ + val = (mac[5] << 24) | (mac[4] << 16) | (mac[3] << 8) | mac[2]; + oa_tc6_reg_write(ctx->tc6, LAN865x_MAC_SAB1, val); +} + +static int lan865x_default_config(const struct device *dev, uint8_t silicon_rev) +{ + /* Values in the below table are the same for LAN865x rev. B0 and B1 */ + static const oa_mem_map_t lan865x_conf[] = { + { .address = 0x00010000, .value = 0x00000000 }, + { .address = 0x00040091, .value = 0x00009660 }, + { .address = 0x00040081, .value = 0x00000080 }, + { .address = 0x00010077, .value = 0x00000028 }, + { .address = 0x00040043, .value = 0x000000FF }, + { .address = 0x00040044, .value = 0x0000FFFF }, + { .address = 0x00040045, .value = 0x00000000 }, + { .address = 0x00040053, .value = 0x000000FF }, + { .address = 0x00040054, .value = 0x0000FFFF }, + { .address = 0x00040055, .value = 0x00000000 }, + { .address = 0x00040040, .value = 0x00000002 }, + { .address = 0x00040050, .value = 0x00000002 }, + { .address = 0x000400E9, .value = 0x00009E50 }, + { .address = 0x000400F5, .value = 0x00001CF8 }, + { .address = 0x000400F4, .value = 0x0000C020 }, + { .address = 0x000400F8, .value = 0x00009B00 }, + { .address = 0x000400F9, .value = 0x00004E53 }, + { .address = 0x000400B0, .value = 0x00000103 }, + { .address = 0x000400B1, .value = 0x00000910 }, + { .address = 0x000400B2, .value = 0x00001D26 }, + { .address = 0x000400B3, .value = 0x0000002A }, + { .address = 0x000400B4, .value = 0x00000103 }, + { .address = 0x000400B5, .value = 0x0000070D }, + { .address = 0x000400B6, .value = 0x00001720 }, + { .address = 0x000400B7, .value = 0x00000027 }, + { .address = 0x000400B8, .value = 0x00000509 }, + { .address = 0x000400B9, .value = 0x00000E13 }, + { .address = 0x000400BA, .value = 0x00001C25 }, + { .address = 0x000400BB, .value = 0x0000002B }, + { .address = 0x0000000C, .value = 0x00000100 }, + { .address = 0x00040081, .value = 0x000000E0 }, + }; + const struct lan865x_config *cfg = dev->config; + uint8_t i, size = ARRAY_SIZE(lan865x_conf); + struct lan865x_data *ctx = dev->data; + int ret; + + /* Enable protected control RW */ + oa_tc6_set_protected_ctrl(ctx->tc6, true); + + for (i = 0; i < size; i++) { + oa_tc6_reg_write(ctx->tc6, lan865x_conf[i].address, + lan865x_conf[i].value); + } + + if (silicon_rev == 1) { + /* For silicon rev 1 (B0): (bit [3..0] from 0x0A0084 */ + oa_tc6_reg_write(ctx->tc6, 0x000400D0, 0x5F21); + } + + lan865x_write_macaddress(dev); + + ret = lan865x_init_chip(dev, silicon_rev); + if (ret < 0) + return ret; + + if (cfg->plca_enable) { + ret = lan865x_config_plca(dev, cfg->plca_node_id, + cfg->plca_node_count, + cfg->plca_burst_count, + cfg->plca_burst_timer); + if (ret < 0) { + return ret; + } + } + return 0; +} + +static void lan865x_int_callback(const struct device *dev, + struct gpio_callback *cb, + uint32_t pins) +{ + ARG_UNUSED(dev); + ARG_UNUSED(pins); + + struct lan865x_data *ctx = + CONTAINER_OF(cb, struct lan865x_data, gpio_int_callback); + + k_sem_give(&ctx->int_sem); +} + +static void lan865x_read_chunks(const struct device *dev) +{ + const struct lan865x_config *cfg = dev->config; + struct lan865x_data *ctx = dev->data; + struct oa_tc6 *tc6 = ctx->tc6; + struct net_pkt *pkt; + int ret; + + k_sem_take(&ctx->tx_rx_sem, K_FOREVER); + pkt = net_pkt_rx_alloc(K_MSEC(cfg->timeout)); + if (!pkt) { + LOG_ERR("OA RX: Could not allocate packet!"); + k_sem_give(&ctx->tx_rx_sem); + return; + } + + ret = oa_tc6_read_chunks(tc6, pkt); + k_sem_give(&ctx->tx_rx_sem); + if (ret < 0) { + eth_stats_update_errors_rx(ctx->iface); + net_pkt_unref(pkt); + return; + } + + /* Feed buffer frame to IP stack */ + ret = net_recv_data(ctx->iface, pkt); + if (ret < 0) { + LOG_ERR("OA RX: Could not process packet (%d)!", ret); + net_pkt_unref(pkt); + } +} + +static void lan865x_int_thread(const struct device *dev) +{ + struct lan865x_data *ctx = dev->data; + struct oa_tc6 *tc6 = ctx->tc6; + uint32_t sts, val, ftr; + + while (true) { + k_sem_take(&ctx->int_sem, K_FOREVER); + if (!ctx->reset) { + oa_tc6_reg_read(tc6, OA_STATUS0, &sts); + if (sts & OA_STATUS0_RESETC) { + oa_tc6_reg_write(tc6, OA_STATUS0, sts); + lan865x_default_config(dev, ctx->silicon_rev); + oa_tc6_reg_read(tc6, OA_CONFIG0, &val); + oa_tc6_reg_write(tc6, OA_CONFIG0, OA_CONFIG0_SYNC | val); + lan865x_mac_rxtx_control(dev, LAN865x_MAC_TXRX_ON); + + ctx->reset = true; + /* + * According to OA T1S standard - it is mandatory to + * read chunk of data to get the IRQ_N negated (deasserted). + */ + oa_tc6_read_status(tc6, &ftr); + continue; + } + } + + /* + * The IRQ_N is asserted when RCA becomes > 0, so update its value + * before reading chunks. + */ + oa_tc6_update_buf_info(tc6); + + while (tc6->rca > 0) { + lan865x_read_chunks(dev); + } + + if (tc6->exst) { + /* + * Just clear any pending interrupts - data RX will be served + * earlier. + * The RESETC is handled separately as it requires LAN865x device + * configuration. + */ + oa_tc6_reg_read(tc6, OA_STATUS0, &sts); + if (sts != 0) { + oa_tc6_reg_write(tc6, OA_STATUS0, sts); + } + + oa_tc6_reg_read(tc6, OA_STATUS1, &sts); + if (sts != 0) { + oa_tc6_reg_write(tc6, OA_STATUS1, sts); + } + } + } +} + +static int lan865x_init(const struct device *dev) +{ + const struct lan865x_config *cfg = dev->config; + struct lan865x_data *ctx = dev->data; + int ret; + + __ASSERT(cfg->spi.config.frequency <= LAN865X_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; + } + + /* Check SPI communication after reset */ + ret = lan865x_check_spi(dev); + if (ret < 0) { + LOG_ERR("SPI communication not working, %d", ret); + return ret; + } + + /* + * Configure interrupt service routine for LAN865x IRQ + */ + ret = gpio_pin_configure_dt(&cfg->interrupt, GPIO_INPUT); + if (ret < 0) { + LOG_ERR("Failed to configure interrupt GPIO, %d", ret); + return ret; + } + + gpio_init_callback(&(ctx->gpio_int_callback), lan865x_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; + } + + gpio_pin_interrupt_configure_dt(&cfg->interrupt, GPIO_INT_EDGE_TO_ACTIVE); + + /* Start interruption-poll thread */ + ctx->tid_int = + k_thread_create(&ctx->thread, ctx->thread_stack, + CONFIG_ETH_LAN865X_IRQ_THREAD_STACK_SIZE, + (k_thread_entry_t)lan865x_int_thread, + (void *)dev, NULL, NULL, + K_PRIO_COOP(CONFIG_ETH_LAN865X_IRQ_THREAD_PRIO), + 0, K_NO_WAIT); + k_thread_name_set(ctx->tid_int, "lan865x_interrupt"); + + ctx->reset = false; + + /* Perform HW reset - 'rst-gpios' required property set in DT */ + 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; + } + + return lan865x_gpio_reset(dev); +} + +static int lan865x_port_send(const struct device *dev, struct net_pkt *pkt) +{ + struct lan865x_data *ctx = dev->data; + struct oa_tc6 *tc6 = ctx->tc6; + int ret; + + k_sem_take(&ctx->tx_rx_sem, K_FOREVER); + ret = oa_tc6_send_chunks(tc6, pkt); + k_sem_give(&ctx->tx_rx_sem); + if (ret < 0) { + LOG_ERR("TX transmission error, %d", ret); + eth_stats_update_errors_tx(net_pkt_iface(pkt)); + return ret; + } + + return 0; +} + +static const struct ethernet_api lan865x_api_func = { + .iface_api.init = lan865x_iface_init, + .get_capabilities = lan865x_port_get_capabilities, + .set_config = lan865x_set_config, + .send = lan865x_port_send, +}; + +#define LAN865X_DEFINE(inst) \ + static const struct lan865x_config lan865x_config_##inst = { \ + .spi = SPI_DT_SPEC_INST_GET(inst, SPI_WORD_SET(8), 0), \ + .interrupt = GPIO_DT_SPEC_INST_GET(inst, int_gpios), \ + .reset = GPIO_DT_SPEC_INST_GET(inst, rst_gpios), \ + .timeout = CONFIG_ETH_LAN865X_TIMEOUT, \ + .plca_node_id = DT_INST_PROP(inst, plca_node_id), \ + .plca_node_count = DT_INST_PROP(inst, plca_node_count), \ + .plca_burst_count = DT_INST_PROP(inst, plca_burst_count), \ + .plca_burst_timer = DT_INST_PROP(inst, plca_burst_timer), \ + .plca_to_timer = DT_INST_PROP(inst, plca_to_timer), \ + .plca_enable = DT_INST_PROP(inst, plca_enable), \ + }; \ + \ + struct oa_tc6 oa_tc6_##inst = { \ + .cps = 64, \ + .protected = 0, \ + .spi = &lan865x_config_##inst.spi \ + }; \ + static struct lan865x_data lan865x_data_##inst = { \ + .mac_address = DT_INST_PROP(inst, local_mac_address), \ + .tx_rx_sem = \ + Z_SEM_INITIALIZER((lan865x_data_##inst).tx_rx_sem, 1, UINT_MAX), \ + .int_sem = Z_SEM_INITIALIZER((lan865x_data_##inst).int_sem, 0, UINT_MAX), \ + .tc6 = &oa_tc6_##inst \ + }; \ + \ + ETH_NET_DEVICE_DT_INST_DEFINE(inst, lan865x_init, NULL, &lan865x_data_##inst, \ + &lan865x_config_##inst, CONFIG_ETH_INIT_PRIORITY, \ + &lan865x_api_func, NET_ETH_MTU); + +DT_INST_FOREACH_STATUS_OKAY(LAN865X_DEFINE); diff --git a/drivers/ethernet/eth_lan865x_priv.h b/drivers/ethernet/eth_lan865x_priv.h new file mode 100644 index 00000000000..fa4af1a949a --- /dev/null +++ b/drivers/ethernet/eth_lan865x_priv.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 DENX Software Engineering GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef ETH_LAN865X_PRIV_H__ +#define ETH_LAN865X_PRIV_H__ + +#include +#include +#include +#include +#include +#include +#include +#include "oa_tc6.h" + +#define LAN865X_SPI_MAX_FREQUENCY 25000000U +#define LAN865X_HW_BOOT_DELAY_MS 7 +#define LAN8650_DEVID 0x8650 +#define LAN8651_DEVID 0x8651 +#define LAN865X_REV_MASK GENMASK(3, 0) +#define LAN865X_RESET_TIMEOUT 10 + +/* Memory Map Sector (MMS) 1 (0x1) */ +#define LAN865x_MAC_NCR MMS_REG(0x1, 0x000) +#define LAN865x_MAC_NCR_TXEN BIT(3) +#define LAN865x_MAC_NCR_RXEN BIT(2) +#define LAN865x_MAC_NCFGR MMS_REG(0x1, 0x001) +#define LAN865x_MAC_NCFGR_CAF BIT(4) +#define LAN865x_MAC_SAB1 MMS_REG(0x1, 0x022) +#define LAN865x_MAC_SAB2 MMS_REG(0x1, 0x024) +#define LAN865x_MAC_SAT2 MMS_REG(0x1, 0x025) + +#define LAN865x_MAC_TXRX_ON 1 +#define LAN865x_MAC_TXRX_OFF 0 + +/* Memory Map Sector (MMS) 10 (0xA) */ +#define LAN865x_DEVID MMS_REG(0xA, 0x094) + +struct lan865x_config { + struct spi_dt_spec spi; + struct gpio_dt_spec interrupt; + struct gpio_dt_spec reset; + int32_t timeout; + + /* PLCA */ + bool plca_enable : 1; /* 1 - PLCA enable, 0 - CSMA/CD enable */ + uint8_t plca_node_id /* PLCA node id range: 0 to 254 */; + uint8_t plca_node_count; /* PLCA node count range: 1 to 255 */ + uint8_t plca_burst_count; /* PLCA burst count range: 0x0 to 0xFF */ + uint8_t plca_burst_timer; /* PLCA burst timer */ + uint8_t plca_to_timer; /* PLCA TO value */ + + /* MAC */ + bool tx_cut_through_mode; /* 1 - tx cut through, 0 - Store and forward */ + bool rx_cut_through_mode; /* 1 - rx cut through, 0 - Store and forward */ +}; + +struct lan865x_data { + struct net_if *iface; + struct gpio_callback gpio_int_callback; + struct k_sem tx_rx_sem; + struct k_sem int_sem; + struct oa_tc6 *tc6; + uint16_t chip_id; + uint8_t silicon_rev; + uint8_t mac_address[6]; + bool iface_initialized; + bool reset; + + K_KERNEL_STACK_MEMBER(thread_stack, CONFIG_ETH_LAN865X_IRQ_THREAD_STACK_SIZE); + struct k_thread thread; + k_tid_t tid_int; +}; + +#endif /* ETH_LAN865X_PRIV_H__ */ diff --git a/dts/bindings/ethernet/microchip,lan865x.yaml b/dts/bindings/ethernet/microchip,lan865x.yaml new file mode 100644 index 00000000000..699bfca7dc6 --- /dev/null +++ b/dts/bindings/ethernet/microchip,lan865x.yaml @@ -0,0 +1,46 @@ +# Copyright (c) 2023 DENX Software Engineering GmbH +# SPDX-License-Identifier: Apache-2.0 + +description: | + LAN865x standalone 10BASE-T1L Ethernet controller with SPI interface. + +compatible: "microchip,lan865x" + +include: [spi-device.yaml, ethernet-controller.yaml] + +properties: + tx-cut-through-mode: + type: boolean + description: Enable TX cut through mode + rx-cut-through-mode: + type: boolean + description: Enable RX cut through mode + plca-enable: + type: boolean + description: Enable or disable PLCA support + plca-node-id: + type: int + description: Specify the PLCA node ID number + plca-node-count: + type: int + description: Specify the PLCA node count + plca-burst-count: + type: int + description: Specify the PLCA burst count + plca-burst-timer: + type: int + description: Specify the PLCA burst timer value + plca-to-timer: + type: int + description: Specify the PLCA to timer value + int-gpios: + type: phandle-array + required: true + description: | + The interrupt pin of LAN865X is active low. + If connected directly the MCU pin should be configured + as active low. + rst-gpios: + type: phandle-array + required: true + description: The reset pin of LAN865X.