diff --git a/boards/microchip/sam/sam_e54_xpro/Kconfig.defconfig b/boards/microchip/sam/sam_e54_xpro/Kconfig.defconfig new file mode 100644 index 0000000000000..a599a8711fd32 --- /dev/null +++ b/boards/microchip/sam/sam_e54_xpro/Kconfig.defconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +if NETWORKING + +config NET_L2_ETHERNET + default y + +endif # NETWORKING diff --git a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro-pinctrl.dtsi b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro-pinctrl.dtsi index dcd24d59b034d..db37cf39b7839 100644 --- a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro-pinctrl.dtsi +++ b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro-pinctrl.dtsi @@ -19,4 +19,22 @@ pinmux = ; }; }; + gmac_rmii: gmac_rmii { + group1 { + pinmux = , + , + , + , + , + , + , + ; + }; + }; + mdio_default: mdio_default { + group1 { + pinmux = , + ; + }; + }; }; diff --git a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.dts b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.dts index 68d2da2770066..f38ac068468f1 100644 --- a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.dts +++ b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.dts @@ -192,10 +192,34 @@ channels = <6>; }; +&gmac { + status = "okay"; + compatible = "microchip,gmac-g1-eth"; + + pinctrl-0 = <&gmac_rmii>; + pinctrl-names = "default"; + + phy-handle = <&phy>; +}; + +&mdio { + status = "okay"; + compatible = "microchip,gmac-g1-mdio"; + + pinctrl-0 = <&mdio_default>; + pinctrl-names = "default"; + + phy: ethernet-phy@0 { + compatible = "ethernet-phy"; + status = "okay"; + reg = <0>; + }; +}; + &portb { status = "okay"; }; &portc { status = "okay"; -}; +}; \ No newline at end of file diff --git a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml index cf53c605ac087..fed8b4dbb0731 100644 --- a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml +++ b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml @@ -19,4 +19,5 @@ supported: - reset - shell - uart + - netif:eth vendor: microchip diff --git a/drivers/ethernet/CMakeLists.txt b/drivers/ethernet/CMakeLists.txt index 12b9142850b33..72582f0754f3c 100644 --- a/drivers/ethernet/CMakeLists.txt +++ b/drivers/ethernet/CMakeLists.txt @@ -30,6 +30,7 @@ zephyr_library_sources_ifdef(CONFIG_ETH_LITEX_LITEETH eth_litex_liteeth.c) zephyr_library_sources_ifdef(CONFIG_ETH_SMSC911X eth_smsc911x.c) zephyr_library_sources_ifdef(CONFIG_ETH_STELLARIS eth_stellaris.c) zephyr_library_sources_ifdef(CONFIG_ETH_W5500 eth_w5500.c) +zephyr_library_sources_ifdef(CONFIG_ETH_MCHP_GMAC_G1 eth_mchp_gmac_g1.c) zephyr_library_sources_ifdef(CONFIG_ETH_SAM_GMAC eth_sam_gmac.c) zephyr_library_sources_ifdef(CONFIG_ETH_CYCLONEV eth_cyclonev.c) zephyr_library_sources_ifdef(CONFIG_SLIP_TAP eth_slip_tap.c) diff --git a/drivers/ethernet/Kconfig b/drivers/ethernet/Kconfig index 15913fd1a8ba7..78bc97ed6e535 100644 --- a/drivers/ethernet/Kconfig +++ b/drivers/ethernet/Kconfig @@ -54,6 +54,7 @@ source "drivers/ethernet/Kconfig.enc424j600" source "drivers/ethernet/Kconfig.esp32" source "drivers/ethernet/Kconfig.e1000" source "drivers/ethernet/Kconfig.sam_gmac" +source "drivers/ethernet/Kconfig.mchp" source "drivers/ethernet/Kconfig.stm32_hal" source "drivers/ethernet/Kconfig.dwmac" source "drivers/ethernet/Kconfig.smsc911x" diff --git a/drivers/ethernet/Kconfig.mchp b/drivers/ethernet/Kconfig.mchp new file mode 100644 index 0000000000000..2a7eff0c50977 --- /dev/null +++ b/drivers/ethernet/Kconfig.mchp @@ -0,0 +1,129 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +config ETH_MCHP_GMAC_G1 + bool "Microchip Ethernet driver" + default y + depends on NET_BUF_FIXED_DATA_SIZE + depends on DT_HAS_MICROCHIP_GMAC_G1_ETH_ENABLED + select NOCACHE_MEMORY if ARCH_HAS_NOCACHE_MEMORY_SUPPORT + select MDIO + select ETH_DSA_SUPPORT + select PINCTRL + help + Enable Microchip MCU Family Ethernet driver. + +# Define menu symbol for configuring ethernet features +menuconfig ETH_MCHP_GMAC + bool "Microchip GMAC Ethernet driver" + default y + depends on ETH_MCHP_GMAC_G1 + help + Enable support for Microchip clock controller driver. + +if ETH_MCHP_GMAC + +# Workaround for not being able to have commas in macro arguments +DT_ETH_MCHP_PATH := $(dt_nodelabel_path,gmac) + +# Just for readability, to keep the following lines shorter. +DT_ETH_MCHP_NQ := $(dt_node_int_prop_int,$(DT_ETH_MCHP_PATH),num-queues) + +config ETH_MCHP_QUEUES + int "Number of active hardware TX and RX queues" + default 1 + range 1 $(DT_ETH_MCHP_NQ) if SOC_SERIES_SAME70 || \ + SOC_SERIES_SAMV71 || \ + SOC_SERIES_SAM4E || \ + SOC_SERIES_SAME54 + help + Select the number of hardware queues used by the driver. Packets will be + routed to appropriate queues based on their priority. + +config ETH_MCHP_FORCE_QUEUE + bool "Force all traffic to be routed through a specific queue" + depends on ETH_MCHP_QUEUES > 1 + depends on NET_TC_RX_COUNT < 5 + help + This option is meant to be used only for debugging. Use it to force all + traffic to be routed through a specific hardware queue. With this enabled + it is easier to verify whether the chosen hardware queue actually works. + This works only if there are four or fewer RX traffic classes enabled, as + the SAM GMAC hardware supports screening up to four traffic classes. + +config ETH_MCHP_FORCED_QUEUE + int "Queue to force the packets to" + depends on ETH_MCHP_FORCE_QUEUE + default 0 + range 0 1 if ETH_MCHP_QUEUES = 2 + range 0 2 if ETH_MCHP_QUEUES = 3 + range 0 3 if ETH_MCHP_QUEUES = 4 + range 0 4 if ETH_MCHP_QUEUES = 5 + range 0 5 if ETH_MCHP_QUEUES = 6 + help + Which queue to force the routing to. This affects both the TX and RX queues + setup. + +config ETH_MCHP_BUF_RX_COUNT + int "Network RX buffers preallocated by the SAM ETH driver" + default 12 + help + Number of network buffers that will be permanently allocated by the + Ethernet driver. These buffers are used in receive path. They are + preallocated by the driver and made available to the GMAC module to be + filled in with incoming data. Their number has to be large enough to fit + at least one complete Ethernet frame. SAM ETH driver will always allocate + that amount of buffers for itself thus reducing the NET_BUF_RX_COUNT + which is a total amount of RX data buffers used by the whole networking + stack. One has to ensure that NET_PKT_RX_COUNT is large enough to + fit at least two Ethernet frames: one being received by the GMAC module + and the other being processed by the higher layer networking stack. + +config ETH_MCHP_MAC_I2C_EEPROM + default y + select I2C + bool "Read from an I2C EEPROM" + help + Read MAC address from an I2C EEPROM. + +if ETH_MCHP_MAC_I2C_EEPROM + +config ETH_MCHP_MAC_I2C_INT_ADDRESS + hex "I2C EEPROM internal address" + default 0x9A + range 0 0xffffffff + help + Internal address of the EEPROM chip where the MAC address is stored. + Chips with 1 to 4 byte internal address size are supported. Address + size has to be configured in a separate Kconfig option. + +config ETH_MCHP_MAC_I2C_INT_ADDRESS_SIZE + int "I2C EEPROM internal address size" + default 1 + range 1 4 + help + Size (in bytes) of the internal EEPROM address. + +endif # ETH_MCHP_MAC_I2C_EEPROM + +config PTP_CLOCK_SAM_GMAC + bool "SAM GMAC PTP clock driver support" + default y + depends on PTP_CLOCK + help + Enable SAM GMAC PTP Clock support. + +config ETH_MCHP_RX_THREAD_STACK_SIZE + int "MCHP RX Work thread stack size" + default 1600 + help + RX thread stack size in bytes. + +config ETH_MCHP_RX_THREAD_PRIORITY + int "MCHP RX work thread priority" + default 2 + help + Ethernet Driver handles RX in workqueue thread. + This options sets the priority of that thread. + +endif # ETH_MCHP_GMAC diff --git a/drivers/ethernet/eth_mchp_gmac_g1.c b/drivers/ethernet/eth_mchp_gmac_g1.c new file mode 100644 index 0000000000000..918681ee0b455 --- /dev/null +++ b/drivers/ethernet/eth_mchp_gmac_g1.c @@ -0,0 +1,1727 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file eth_mchp_gmac_g1.c + * @brief Eth GMAC driver for Microchip devices. + * + * This file provides the implementation of eth GMAC functions + * for Microchip-based systems. + */ + +#define LOG_MODULE_NAME eth_mchp_gmac_g1 +#define LOG_LEVEL CONFIG_ETHERNET_LOG_LEVEL + +#include +LOG_MODULE_REGISTER(LOG_MODULE_NAME); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "eth.h" + +/** + * @brief Define compatible string for device tree. + */ +#define DT_DRV_COMPAT microchip_gmac_g1_eth + +#include + +#define MCHP_OUI_B0 0x00 +#define MCHP_OUI_B1 0x04 +#define MCHP_OUI_B2 0xA3 + +#define GMAC_MTU NET_ETH_MTU + +#define GMAC_FRAME_SIZE_MAX (GMAC_MTU + 18) + +/** Memory alignment of the RX/TX Buffer Descriptor List */ +#define GMAC_DESC_ALIGNMENT 4 + +/** Total number of queues supported by GMAC hardware module */ +#define GMAC_QUEUE_NUM DT_INST_PROP(0, num_queues) +#define GMAC_PRIORITY_QUEUE_NUM (GMAC_QUEUE_NUM - 1) + +#if (GMAC_PRIORITY_QUEUE_NUM >= 1) +BUILD_ASSERT(ARRAY_SIZE(GMAC->GMAC_TBQBAPQ) + 1 == GMAC_QUEUE_NUM, + "GMAC_QUEUE_NUM doesn't match soc header"); +#endif + +/** Number of priority queues used */ +#define GMAC_ACTIVE_QUEUE_NUM (CONFIG_ETH_MCHP_QUEUES) +#define GMAC_ACTIVE_PRIORITY_QUEUE_NUM (GMAC_ACTIVE_QUEUE_NUM - 1) + +/** RX descriptors count for main queue */ +#define MAIN_QUEUE_RX_DESC_COUNT (CONFIG_ETH_MCHP_BUF_RX_COUNT + 1) + +/** TX descriptors count for main queue */ +#define MAIN_QUEUE_TX_DESC_COUNT (CONFIG_NET_BUF_TX_COUNT + 1) + +#define PRIORITY_QUEUE1_RX_DESC_COUNT 1 +#define PRIORITY_QUEUE1_TX_DESC_COUNT 1 + +/* + * Receive buffer descriptor bit field definitions + */ + +/** Buffer ownership, needs to be 0 for the GMAC to write data to the buffer */ +#define GMAC_RXW0_OWNERSHIP (0x1u << 0) +/** Last descriptor in the receive buffer descriptor list */ +#define GMAC_RXW0_WRAP (0x1u << 1) +/** Address of beginning of buffer */ +#define GMAC_RXW0_ADDR (0x3FFFFFFFu << 2) + +/** Receive frame length including FCS */ +#define GMAC_RXW1_LEN (0x1FFFu << 0) +/** FCS status */ +#define GMAC_RXW1_FCS_STATUS (0x1u << 13) +/** Start of frame */ +#define GMAC_RXW1_SOF (0x1u << 14) +/** End of frame */ +#define GMAC_RXW1_EOF (0x1u << 15) +/** Canonical Format Indicator */ +#define GMAC_RXW1_CFI (0x1u << 16) +/** VLAN priority (if VLAN detected) */ +#define GMAC_RXW1_VLANPRIORITY (0x7u << 17) +/** Priority tag detected */ +#define GMAC_RXW1_PRIORITYDETECTED (0x1u << 20) +/** VLAN tag detected */ +#define GMAC_RXW1_VLANDETECTED (0x1u << 21) +/** Type ID match */ +#define GMAC_RXW1_TYPEIDMATCH (0x3u << 22) +/** Type ID register match found */ +#define GMAC_RXW1_TYPEIDFOUND (0x1u << 24) +/** Specific Address Register match */ +#define GMAC_RXW1_ADDRMATCH (0x3u << 25) +/** Specific Address Register match found */ +#define GMAC_RXW1_ADDRFOUND (0x1u << 27) +/** Unicast hash match */ +#define GMAC_RXW1_UNIHASHMATCH (0x1u << 29) +/** Multicast hash match */ +#define GMAC_RXW1_MULTIHASHMATCH (0x1u << 30) +/** Global all ones broadcast address detected */ +#define GMAC_RXW1_BROADCASTDETECTED (0x1u << 31) + +/* + * Transmit buffer descriptor bit field definitions + */ + +/** Transmit buffer length */ +#define GMAC_TXW1_LEN (0x3FFFu << 0) +/** Last buffer in the current frame */ +#define GMAC_TXW1_LASTBUFFER (0x1u << 15) +/** No CRC */ +#define GMAC_TXW1_NOCRC (0x1u << 16) +/** Transmit IP/TCP/UDP checksum generation offload errors */ +#define GMAC_TXW1_CHKSUMERR (0x7u << 20) +/** Late collision, transmit error detected */ +#define GMAC_TXW1_LATECOLERR (0x1u << 26) +/** Transmit frame corruption due to AHB error */ +#define GMAC_TXW1_TRANSERR (0x1u << 27) +/** Retry limit exceeded, transmit error detected */ +#define GMAC_TXW1_RETRYEXC (0x1u << 29) +/** Last descriptor in Transmit Descriptor list */ +#define GMAC_TXW1_WRAP (0x1u << 30) +/** Buffer used, must be 0 for the GMAC to read data to the transmit buffer */ +#define GMAC_TXW1_USED (0x1u << 31) + +/* + * Interrupt Status/Enable/Disable/Mask register bit field definitions + */ + +#define GMAC_INT_RX_ERR_BITS (GMAC_IER_RXUBR_Msk | GMAC_IER_ROVR_Msk) + +#define GMAC_INT_TX_ERR_BITS (GMAC_IER_TUR_Msk | GMAC_IER_RLEX_Msk | GMAC_IER_TFC_Msk) + +#define GMAC_INT_EN_FLAGS \ + (GMAC_IER_RCOMP_Msk | GMAC_INT_RX_ERR_BITS | GMAC_IER_TCOMP_Msk | GMAC_INT_TX_ERR_BITS | \ + GMAC_IER_HRESP_Msk) + +/** GMAC Priority Queues DMA flags */ +#define GMAC_DMA_QUEUE_FLAGS (0) + +/** List of GMAC queues */ +enum queue_idx { + GMAC_QUE_0, /** Main queue */ +}; + +#if (DT_INST_PROP(0, max_frame_size) == 1518) +/* Maximum frame length is 1518 bytes */ +#define GMAC_MAX_FRAME_SIZE 0 +#elif (DT_INST_PROP(0, max_frame_size) == 1536) +/* Enable Max Frame Size of 1536 */ +#define GMAC_MAX_FRAME_SIZE GMAC_NCFGR_MAXFS +#elif (DT_INST_PROP(0, max_frame_size) == 10240) +/* Jumbo Frame Enable */ +#define GMAC_MAX_FRAME_SIZE GMAC_NCFGR_JFRAME +#else +#error "GMAC_MAX_FRAME_SIZE is invalid, fix it at device tree." +#endif + +#define ETH_MCHP_ESUCCESS 0 + +#define ETH_MCHP_SHIFT_1_BYTE 8 +#define ETH_MCHP_SHIFT_2_BYTE 16 +#define ETH_MCHP_SHIFT_3_BYTE 24 + +#define ETH_MCHP_RX_PKT_WAIT_TIMEOUT_MS 500 + +/** Receive/transmit buffer descriptor */ +struct gmac_desc { + /* buffer descriptor address word */ + uint32_t w0; + + /* buffer descriptor status word */ + uint32_t w1; +}; + +/** Ring list of receive/transmit buffer descriptors */ +struct gmac_desc_list { + /* pointer to buffer descriptor */ + struct gmac_desc *buf; + + /* buffer length */ + uint16_t len; + + /* first element of the buffer descriptor list */ + uint16_t head; + + /* last element of the buffer descriptor list */ + uint16_t tail; +}; + +/* Device run time data */ +/** + * @brief Queue related for the Eth device. + * + * This structure contains the run queue related for the Eth device. + */ +struct gmac_queue { + /* Rx descriptor list */ + struct gmac_desc_list rx_desc_list; + + /* Tx descriptor list */ + struct gmac_desc_list tx_desc_list; + + /* Transmit Semaphore */ + struct k_sem tx_sem; + + /* Fragment list associated with a frame */ + struct net_buf **rx_frag_list; + + /* Number of RX frames dropped by the driver */ + volatile uint32_t err_rx_frames_dropped; + + /* Number of times receive queue was flushed */ + volatile uint32_t err_rx_flushed_count; + + /* Number of times transmit queue was flushed */ + volatile uint32_t err_tx_flushed_count; + + /* Queue Index */ + enum queue_idx que_idx; +}; + +typedef struct gmac_work_data { + /* processing rx packets in worker thread */ + struct k_work rx_work; + + /* Pointer to Rx Queue */ + struct gmac_queue *queue; +} gmac_work_data_t; + +/** + * @brief Run time data structure for the Eth peripheral. + * + * This structure contains the run time parameters for the Eth + * device, including statistics. + */ +typedef struct gmac_dev_data { + /* net iface structure has to be the first field */ + struct net_if *iface; + + /* MAC Address */ + uint8_t mac_addr[6]; + + /* Link Status */ + bool link_up; + + /* semaphore for rx packet event */ + struct k_sem rx_int_sem; + + /* processing rx packets in thread */ + K_KERNEL_STACK_MEMBER(rx_thread_stack, CONFIG_ETH_MCHP_RX_THREAD_STACK_SIZE); + + /* processing rx packets in worker thread */ + struct k_thread rx_thread; + + /* Pointer to Rx Queue */ + struct gmac_queue *rx_queue; + + /* queue list */ + struct gmac_queue queue_list[GMAC_QUEUE_NUM]; + +#if defined(CONFIG_NET_STATISTICS_ETHERNET) + /* eth frame statistics */ + struct net_stats_eth stats; +#endif /* CONFIG_NET_STATISTICS_ETHERNET */ +} gmac_dev_data_t; + +/** + * @brief Clock configuration structure for the ETH. + * + * This structure contains the clock configuration parameters for the ETH + * device. + */ +typedef struct gmac_clock { + /* Clock driver */ + const struct device *clock_dev; + + /* Main APB clock subsystem. */ + clock_control_subsys_t mclk_apb_sys; + + /* Main AHB clock subsystem. */ + clock_control_subsys_t mclk_ahb_sys; +} gmac_clock_t; + +/* Device constant configuration parameters */ + +/** + * @brief device configuration structure for the ETH device. + * + * This structure contains the Device constant configuration parameters + * for the ETH device. + */ +typedef struct gmac_dev_config { + /* GMAC register */ + gmac_registers_t *const regs; + + /* Pin Control structure */ + const struct pinctrl_dev_config *pcfg; + + /* Configuration function pointer */ + void (*config_func)(void); + + /* Pointer to Phy device */ + const struct device *phy_dev; + + /* clock device configuration */ + gmac_clock_t eth_clock; +} gmac_dev_config_t; + +#define ETH_MCHP_CLOCK_DEFN(n) \ + .eth_clock.clock_dev = DEVICE_DT_GET(DT_NODELABEL(clock)), \ + .eth_clock.mclk_apb_sys = (void *)DT_INST_CLOCKS_CELL_BY_NAME(n, mclk_apb, subsystem), \ + .eth_clock.mclk_ahb_sys = (void *)DT_INST_CLOCKS_CELL_BY_NAME(n, mclk_ahb, subsystem) + +/* + * Verify Kconfig configuration + */ +#if CONFIG_NET_BUF_DATA_SIZE * CONFIG_ETH_MCHP_BUF_RX_COUNT < GMAC_FRAME_SIZE_MAX +#error CONFIG_NET_BUF_DATA_SIZE * CONFIG_ETH_SAM_GMAC_BUF_RX_COUNT is \ + not large enough to hold a full frame +#endif + +#if CONFIG_NET_BUF_DATA_SIZE * (CONFIG_NET_BUF_RX_COUNT - CONFIG_ETH_MCHP_BUF_RX_COUNT) < \ + GMAC_FRAME_SIZE_MAX +#error (CONFIG_NET_BUF_RX_COUNT - CONFIG_ETH_SAM_GMAC_BUF_RX_COUNT) * \ + CONFIG_NET_BUF_DATA_SIZE are not large enough to hold a full frame +#endif + +#if CONFIG_NET_BUF_DATA_SIZE & 0x3F +#pragma message "CONFIG_NET_BUF_DATA_SIZE should be a multiple of 64 bytes " \ + "due to the granularity of RX DMA" +#endif + +#if (CONFIG_ETH_MCHP_BUF_RX_COUNT + 1) * GMAC_ACTIVE_QUEUE_NUM > CONFIG_NET_BUF_RX_COUNT +#error Not enough RX buffers to allocate descriptors for each HW queue +#endif + +BUILD_ASSERT(DT_INST_ENUM_IDX(0, phy_connection_type) <= 1, "Invalid PHY connection"); + +/** RX descriptors list */ +static struct gmac_desc rx_desc_que0[MAIN_QUEUE_RX_DESC_COUNT] __nocache + __aligned(GMAC_DESC_ALIGNMENT); + +/** TX descriptors list */ +static struct gmac_desc tx_desc_que0[MAIN_QUEUE_TX_DESC_COUNT] __nocache + __aligned(GMAC_DESC_ALIGNMENT); + +/** RX buffer accounting list */ +static struct net_buf *rx_frag_list_que0[MAIN_QUEUE_RX_DESC_COUNT]; + +#define MODULO_INC(val, max) \ + { \ + val = (++val < max) ? val : 0; \ + } + +/** + * @brief Free pre-reserved RX buffers. + * + * This function is used to free pre-reserved RX buffers. + * + * @param rx_frag_list Pointer to the fragment list. + * @param len No of fragments in the list. + */ +static void gmac_free_rx_bufs(struct net_buf **rx_frag_list, uint16_t len) +{ + for (int i = 0; i < len; i++) { + if (rx_frag_list[i] != NULL) { + net_buf_unref(rx_frag_list[i]); + rx_frag_list[i] = NULL; + } + } +} + +/** + * @brief Initialize RX descriptor list. + * + * This function is used to Initialize RX descriptor list + * + * @param gmac_regs Pointer to gmac registers. + * @param queue Pointer to gmac queue structure. + * + * @return 0 if successful, -ENOBUFS if no buffers available. + */ +static int gmac_rx_descriptors_init(gmac_registers_t *gmac_regs, + struct gmac_queue *queue) +{ + struct gmac_desc_list *rx_desc_list = &queue->rx_desc_list; + struct net_buf **rx_frag_list = queue->rx_frag_list; + struct net_buf *rx_buf; + uint8_t *rx_buf_addr; + int result = ETH_MCHP_ESUCCESS; + + __ASSERT_NO_MSG(rx_frag_list); + + rx_desc_list->tail = 0U; + + for (int i = 0; i < rx_desc_list->len; i++) { + rx_buf = net_pkt_get_reserve_rx_data(CONFIG_NET_BUF_DATA_SIZE, K_NO_WAIT); + if (rx_buf == NULL) { + gmac_free_rx_bufs(rx_frag_list, rx_desc_list->len); + LOG_ERR("Failed to reserve data net buffers"); + result = -ENOBUFS; + break; + } + + rx_frag_list[i] = rx_buf; + + rx_buf_addr = rx_buf->data; + __ASSERT(!((uint32_t)rx_buf_addr & ~GMAC_RXW0_ADDR), + "Misaligned RX buffer address"); + __ASSERT(rx_buf->size == CONFIG_NET_BUF_DATA_SIZE, + "Incorrect length of RX data buffer"); + + /* Give ownership to GMAC and remove the wrap bit */ + rx_desc_list->buf[i].w0 = (uint32_t)rx_buf_addr & GMAC_RXW0_ADDR; + rx_desc_list->buf[i].w1 = 0U; + } + + /* Set the wrap bit on the last descriptor */ + rx_desc_list->buf[rx_desc_list->len - 1U].w0 |= GMAC_RXW0_WRAP; + + /* Set Receive Buffer Queue Pointer Register */ + gmac_regs->GMAC_RBQB = (uint32_t)queue->rx_desc_list.buf; + + return result; +} + +/** + * @brief Initialize TX descriptor list. + * + * This function is used to Initialize TX descriptor list. + * + * @param gmac_regs Pointer to gmac registers. + * @param queue Pointer to gmac queue structure. + */ +static void gmac_tx_descriptors_init(gmac_registers_t *gmac_regs, + struct gmac_queue *queue) +{ + struct gmac_desc_list *tx_desc_list = &queue->tx_desc_list; + + tx_desc_list->head = 0U; + tx_desc_list->tail = 0U; + + for (int i = 0; i < tx_desc_list->len; i++) { + tx_desc_list->buf[i].w0 = 0U; + tx_desc_list->buf[i].w1 = GMAC_TXW1_USED; + } + + /* Set the wrap bit on the last descriptor */ + tx_desc_list->buf[tx_desc_list->len - 1U].w1 |= GMAC_TXW1_WRAP; + + /* Set Transmit Buffer Queue Pointer Register */ + gmac_regs->GMAC_TBQB = (uint32_t)queue->tx_desc_list.buf; +} + +/** + * @brief Sets the receive buffer queue pointer in the appropriate register. + * + * This function is used to set the receive buffer queue pointer in + * the appropriate register. + * + * @param gmac Pointer to gmac registers. + * @param queue Pointer to gmac queue structure. + */ +static void gmac_set_receive_buf_queue_pointer(gmac_registers_t *gmac, + struct gmac_queue *queue) +{ + gmac->GMAC_RBQB = (uint32_t)queue->rx_desc_list.buf; +} + +/** + * @brief Sets the receive buffer queue pointer in the appropriate register. + * + * This function is used to set the receive buffer queue pointer in + * the appropriate register. + * + * @param gmac_regs Pointer to gmac registers. + * @param queue Pointer to gmac queue structure. + * + * @return 0 if successful, -ENOBUFS if no buffers available. + */ +static int gmac_queue_init(gmac_registers_t *gmac_regs, struct gmac_queue *queue) +{ + int result; + + __ASSERT_NO_MSG(queue->rx_desc_list.len > 0); + __ASSERT_NO_MSG(queue->tx_desc_list.len > 0); + __ASSERT(!((uint32_t)queue->rx_desc_list.buf & ~GMAC_RBQB_ADDR_Msk), + "RX descriptors have to be word aligned"); + __ASSERT(!((uint32_t)queue->tx_desc_list.buf & ~GMAC_TBQB_ADDR_Msk), + "TX descriptors have to be word aligned"); + + /* Setup descriptor lists */ + result = gmac_rx_descriptors_init(gmac_regs, queue); + if (result < 0) { + return result; + } + + gmac_tx_descriptors_init(gmac_regs, queue); + + /* Initialize TX semaphore. This semaphore is used to wait until the TX + * data has been sent. + */ + k_sem_init(&queue->tx_sem, 0, 1); + + /* Configure GMAC DMA transfer */ + gmac_regs->GMAC_DCFGR = + + /* Receive Buffer Size (defined in multiples of 64 bytes) */ + GMAC_DCFGR_DRBS(CONFIG_NET_BUF_DATA_SIZE >> 6) | +#if defined(GMAC_DCFGR_RXBMS) + /* Use full receive buffer size on parts where this is selectable */ + GMAC_DCFGR_RXBMS(3) | +#endif + /* Attempt to use INCR4 AHB bursts (Default) */ + GMAC_DCFGR_FBLDO_INCR4 | + /* DMA Queue Flags */ + GMAC_DMA_QUEUE_FLAGS; + + /* Setup RX/TX completion and error interrupts */ + gmac_regs->GMAC_IER = GMAC_INT_EN_FLAGS; + + queue->err_rx_frames_dropped = 0U; + queue->err_rx_flushed_count = 0U; + queue->err_tx_flushed_count = 0U; + + result = ETH_MCHP_ESUCCESS; + + LOG_INF("Queue %d activated", queue->que_idx); + + return result; +} + +/** + * @brief Set MAC Address for frame filtering logic. + * + * This function is used to set MAC Address for frame filtering logic. + * + * @param gmac Pointer to gmac registers. + * @param index Index. + * @param mac_addr MAC Address. + */ +static void gmac_mac_addr_set(gmac_registers_t *gmac, uint8_t index, uint8_t mac_addr[6]) +{ + uint32_t bottom_addr = 0; + uint32_t top_addr = 0; + + __ASSERT(index < 4, "index has to be in the range 0..3"); + + bottom_addr = (mac_addr[3] << ETH_MCHP_SHIFT_3_BYTE) | + (mac_addr[2] << ETH_MCHP_SHIFT_2_BYTE) | + (mac_addr[1] << ETH_MCHP_SHIFT_1_BYTE) | (mac_addr[0]); + gmac->SA[index].GMAC_SAB = GMAC_SAB_ADDR(bottom_addr); + + top_addr = (mac_addr[5] << ETH_MCHP_SHIFT_1_BYTE) | (mac_addr[4]); + gmac->SA[index].GMAC_SAT = GMAC_SAT_ADDR(top_addr); +} + +/** + * @brief Process successfully sent packets. + * + * This function is used to process successfully sent packets. + * + * @param gmac Pointer to gmac registers. + * @param queue Pointer to gmac queue structure. + */ +static void gmac_tx_completed(gmac_registers_t *gmac, struct gmac_queue *queue) +{ + k_sem_give(&queue->tx_sem); +} + +/** + * @brief Reset TX queue when errors are detected. + * + * This function is used to reset TX queue when errors are detected. + * + * @param gmac Pointer to register structure. + * @param queue Pointer to gmac queue structure. + */ +static void gmac_tx_error_handler(gmac_registers_t *gmac, struct gmac_queue *queue) +{ + queue->err_tx_flushed_count++; + + /* Stop transmission, clean transmit pipeline and control registers */ + gmac->GMAC_NCR &= ~GMAC_NCR_TXEN_Msk; + + gmac_tx_descriptors_init(gmac, queue); + + /* Reinitialize TX mutex */ + k_sem_give(&queue->tx_sem); + + /* Restart transmission */ + gmac->GMAC_NCR |= GMAC_NCR_TXEN_Msk; +} + +/** + * @brief Clean RX queue, any received data still stored in the buffers is abandoned. + * + * This function is used to clean RX queue, any received data still stored + * in the buffers is abandoned. + * + * @param gmac Pointer to gmac registers. + * @param queue Pointer to gmac queue structure. + */ +static void gmac_rx_error_handler(gmac_registers_t *gmac, struct gmac_queue *queue) +{ + queue->err_rx_flushed_count++; + + /* Stop reception */ + gmac->GMAC_NCR &= ~GMAC_NCR_RXEN_Msk; + + queue->rx_desc_list.tail = 0U; + + /* clean RX queue */ + for (int i = 0; i < queue->rx_desc_list.len; i++) { + queue->rx_desc_list.buf[i].w1 = 0U; + queue->rx_desc_list.buf[i].w0 &= ~GMAC_RXW0_OWNERSHIP; + } + + gmac_set_receive_buf_queue_pointer(gmac, queue); + + /* Restart reception */ + gmac->GMAC_NCR |= GMAC_NCR_RXEN_Msk; +} + +/** + * @brief Initialize the registers, interrupts. + * + * This function is used to Initialize and configure the registers, + * and interrupts. + * + * @param gmac Pointer to gmac registers. + * + * @return divisor if successful, -ENOTSUP if not supprted, -EINVAL if invalid phy connection type. + */ +static int gmac_init(gmac_registers_t *gmac) +{ + uint32_t gmac_ncfgr_val; + uint32_t val; + + /* Initialize GMAC driver */ + gmac_ncfgr_val = GMAC_NCFGR_MTIHEN_Msk /* Multicast Hash Enable */ + | GMAC_NCFGR_LFERD_Msk /* Length Field Error Frame Discard */ + | GMAC_NCFGR_RFCS_Msk /* Remove Frame Check Sequence */ + | GMAC_NCFGR_RXCOEN_Msk /* Receive Checksum Offload Enable */ + | GMAC_MAX_FRAME_SIZE; + + /* Set Network Control Register to its default value, clear stats. */ + gmac->GMAC_NCR = GMAC_NCR_CLRSTAT_Msk | GMAC_NCR_MPE_Msk; + + /* Dsiable tx and rx */ + gmac->GMAC_NCR &= ~GMAC_NCR_TXEN_Msk; + gmac->GMAC_NCR &= ~GMAC_NCR_RXEN_Msk; + + /* Disable all interrupts */ + gmac->GMAC_IDR = UINT32_MAX; + /* Clear all interrupts */ + (void)gmac->GMAC_ISR; + + /* Setup Hash Registers - enable reception of all + * multicast frames when GMAC_NCFGR_MTIHEN is set. + */ + gmac->GMAC_HRB = UINT32_MAX; + gmac->GMAC_HRT = UINT32_MAX; + + /* Clear Status resgiters for Rx & Tx */ + gmac->GMAC_TSR = GMAC_TSR_RESETVALUE; + gmac->GMAC_RSR = GMAC_RSR_RESETVALUE; + + /* Setup Network Configuration Register */ + val = gmac->GMAC_NCFGR; + val |= gmac_ncfgr_val; + gmac->GMAC_NCFGR = val; + + /* Default (RMII) is defined at microchip,gmac-g1-eth.yaml file */ + switch (DT_INST_ENUM_IDX(0, phy_connection_type)) { + case 0: /* mii */ + gmac->GMAC_UR = 0x1; + break; + case 1: /* rmii */ + gmac->GMAC_UR = 0x0; + break; + default: + /* Build assert at top of file should catch this case */ + LOG_ERR("The phy connection type is invalid"); + + return -EINVAL; + break; + } + + return ETH_MCHP_ESUCCESS; +} + +/** + * @brief Initialize the registers, interrupts. + * + * This function is used to Initialize and configure the registers, + * and interrupts. + * + * @param gmac Pointer to gmac registers. + * @param full_duplex if full duplex or not. + * @param speed_100M if speed is 100M or not. + */ +static void gmac_link_configure(gmac_registers_t *gmac, bool full_duplex, bool speed_100M) +{ + uint32_t val; + + val = gmac->GMAC_NCFGR; + + val &= ~(GMAC_NCFGR_FD_Msk | GMAC_NCFGR_SPD_Msk); + val |= (full_duplex) ? GMAC_NCFGR_FD_Msk : 0; + val |= (speed_100M) ? GMAC_NCFGR_SPD_Msk : 0; + + gmac->GMAC_NCFGR = val; + + gmac->GMAC_NCR |= (GMAC_NCR_RXEN_Msk | GMAC_NCR_TXEN_Msk); +} + +/** + * @brief Set the register to start the transmission. + * + * This function is used to set the register to start the transmission. + * + * @param gmac Pointer to gmac registers. + */ +static void gmac_tx(gmac_registers_t *gmac) +{ + /* Start transmission */ + gmac->GMAC_NCR |= GMAC_NCR_TSTART_Msk; +} + +/** + * @brief ISR to handle the interrupt. + * + * This ISR is used to handle interrupt related to reception of frame, or + * completion of transmission of a frame. + * + * @param gmac Pointer to gmac registers. + * @param queue Pointer to gmac queue structure. + */ +static void gmac_queue0_isr(gmac_registers_t *gmac, struct gmac_queue *queue) +{ + struct gmac_desc_list *rx_desc_list; + struct gmac_desc_list *tx_desc_list; + struct gmac_desc *tail_desc; + uint32_t isr; + + /* Interrupt Status Register is cleared on read */ + isr = gmac->GMAC_ISR; + LOG_DBG("GMAC_ISR=0x%08x", isr); + + rx_desc_list = &queue->rx_desc_list; + tx_desc_list = &queue->tx_desc_list; + + /* Check if packet received */ + if ((isr & GMAC_INT_RX_ERR_BITS) != 0) { + gmac_rx_error_handler(gmac, queue); + } else if (isr & GMAC_ISR_RCOMP_Msk) { + gmac_dev_data_t *dev_data = + CONTAINER_OF(queue, gmac_dev_data_t, queue_list[queue->que_idx]); + + tail_desc = &rx_desc_list->buf[rx_desc_list->tail]; + + LOG_DBG("rx.w1=0x%08x, tail=%d", tail_desc->w1, rx_desc_list->tail); + dev_data->rx_queue = queue; + k_sem_give(&dev_data->rx_int_sem); + } + + /* Check if TX packet completion */ + if ((isr & GMAC_INT_TX_ERR_BITS) != 0) { + gmac_tx_error_handler(gmac, queue); + } else if (isr & GMAC_ISR_TCOMP_Msk) { + gmac_tx_completed(gmac, queue); + } + + if ((isr & GMAC_IER_HRESP_Msk) != 0) { + LOG_DBG("IER HRESP"); + } +} + +#if DT_INST_NODE_HAS_PROP(0, mac_eeprom) +/** + * @brief Read MAC address from EEPROM + * + * This function is used to read MAC address from EEPROM. + * + * @param mac_addr buffer for the read mac address. + */ +static void gmac_get_mac_addr_from_i2c_eeprom(uint8_t mac_addr[6]) +{ + uint32_t iaddr = CONFIG_ETH_MCHP_MAC_I2C_INT_ADDRESS; + int ret; + const struct i2c_dt_spec i2c = I2C_DT_SPEC_GET(DT_INST_PHANDLE(0, mac_eeprom)); + + /* Check if the I2C bus is ready */ + if (device_is_ready(i2c.bus) != true) { + LOG_ERR("I2C Bus is not ready"); + return; + } + + /* Read the MAc address */ + ret = i2c_write_read_dt(&i2c, &iaddr, CONFIG_ETH_MCHP_MAC_I2C_INT_ADDRESS_SIZE, + mac_addr, 6); + if (ret != 0) { + LOG_ERR("I2C: failed to read MAC addr"); + return; + } + + return; +} +#endif + +/** + * @brief Generate MAC Address for frame filtering logic. + * + * This function is used to get MAC Address for frame filtering logic. + * + * @param gmac Pointer to gmac registers. + * @param index Index. + * @param mac_addr MAC Address. + */ +static void gmac_mac_addr_generate(gmac_registers_t *gmac, uint8_t index, uint8_t mac_addr[6]) +{ +#if DT_INST_NODE_HAS_PROP(0, mac_eeprom) + gmac_get_mac_addr_from_i2c_eeprom(mac_addr); +#elif DT_INST_PROP(0, zephyr_random_mac_address) + gen_random_mac(mac_addr, MCHP_OUI_B0, MCHP_OUI_B1, MCHP_OUI_B2); +#endif +} + +/** + * @brief Set MAC address + * + * This function is used to set the MAC address either by reading it from EEPROM, + * or generating it. + * + * @param gmac Pointer to gmac registers. + * @param mac_addr buffer for the read mac address. + */ +static void gmac_generate_set_mac(gmac_registers_t *gmac, uint8_t mac_addr[6]) +{ + /* Generate/ Get MAC Address for frame filtering logic */ + gmac_mac_addr_generate(gmac, 0, mac_addr); + + /* Set MAC Address for frame filtering logic */ + gmac_mac_addr_set(gmac, 0, mac_addr); +} + +#if defined(CONFIG_NET_STATISTICS_ETHERNET) +/** + * @brief Get Eth MAC stats + * + * This function is used to get the statistics related to Eth MAC device. + * + * @param gmac Pointer to gmac registers. + * @param eth_stats Pointer to structure in which to fill in the stats data. + */ +static void gmac_get_stats(gmac_registers_t *gmac, struct net_stats_eth *eth_stats) +{ + uint32_t err_ae = gmac->GMAC_AE; /* Alignment Error */ + uint32_t err_ofr = gmac->GMAC_OFR; /* Over Length Frames */ + uint32_t err_fcs = gmac->GMAC_FCSE; /* FCS Error */ + uint32_t err_scf = gmac->GMAC_SCF; /* Single Collision */ + uint32_t err_mcf = gmac->GMAC_MCF; /* Multiple Collision */ + uint32_t err_ecf = gmac->GMAC_EC; /* Excess Collision */ + + /* + * No ned to update the bytes rx/tx; bcast rx/tx; pkt rx/tx + * as it is being updated in the net subsystem via the + * eth_stats_update_* APIs in eth_stats.h + */ + + eth_stats->collisions += err_scf + err_mcf + err_ecf; + + eth_stats->csum.rx_csum_offload_errors += gmac->GMAC_FCSE; + eth_stats->csum.rx_csum_offload_good = 0; + + eth_stats->error_details.rx_align_errors += err_ae; + eth_stats->error_details.rx_long_length_errors += err_ofr; + eth_stats->error_details.rx_crc_errors += err_fcs; + eth_stats->error_details.rx_length_errors += err_ofr; + + eth_stats->errors.rx += gmac->GMAC_UCE + gmac->GMAC_TCE + gmac->GMAC_IHCE + gmac->GMAC_ROE + + gmac->GMAC_RRE + err_ae + gmac->GMAC_RSE + gmac->GMAC_LFFE + + err_fcs + gmac->GMAC_JR + err_ofr + gmac->GMAC_UFR; + + eth_stats->errors.tx += err_scf + err_mcf + err_ecf + gmac->GMAC_TUR + gmac->GMAC_CSE; + + eth_stats->multicast.rx += gmac->GMAC_MFR; + eth_stats->multicast.tx += gmac->GMAC_MFT; + + /* eth_stats->flow_control */ + + /* eth_stats->hw_timestamp.rx_hwtstamp_cleared */ + + eth_stats->tx_dropped = 0; + eth_stats->tx_restart_queue = 0; + eth_stats->tx_timeout_count = 0; + eth_stats->unknown_protocol = 0; + +#ifdef CONFIG_NET_STATISTICS_ETHERNET_VENDOR + eth_stats->vendor.key = NULL; + eth_stats->vendor.value = 0; +#endif /* CONFIG_NET_STATISTICS_ETHERNET_VENDOR */ +} +#endif /* CONFIG_NET_STATISTICS_ETHERNET */ + +/** + * @brief Get the net interface handle associated with the Eth MAC driver + * + * This function is used to get the net interface handle associated with + * the Eth MAC to pass the received packet to it. + * + * @param ctx data context of the Eth MAC driver. + * + * @return net_if* Pointer to the net interface structure + */ +static struct net_if *gmac_get_iface(gmac_dev_data_t *ctx) +{ + return ctx->iface; +} + +/** + * @brief Get the frame from the Rx Queue + * + * This function is used to get the frame received from the network from + * the queue. + * + * @param queue Pointer to the queue onwhich the fram has been received. + * + * @return net_pkt* Pointer to the buffer containing the complete frame. + */ +static struct net_pkt *gmac_frame_get(struct gmac_queue *queue) +{ + struct gmac_desc_list *rx_desc_list = &queue->rx_desc_list; + struct gmac_desc *rx_desc; + struct net_buf **rx_frag_list = queue->rx_frag_list; + struct net_pkt *rx_frame; + bool frame_is_complete; + struct net_buf *frag; + struct net_buf *new_frag; + struct net_buf *last_frag = NULL; + uint8_t *frag_data; + uint32_t frag_len; + uint32_t frame_len = 0U; + uint16_t tail; + uint8_t wrap; + + /* Check if there exists a complete frame in RX descriptor list */ + tail = rx_desc_list->tail; + rx_desc = &rx_desc_list->buf[tail]; + frame_is_complete = false; + while (((rx_desc->w0 & GMAC_RXW0_OWNERSHIP) != 0) && (frame_is_complete == false)) { + + if (((uint8_t *)(rx_desc->w0 & GMAC_RXW0_ADDR)) == 0) { + rx_desc->w0 &= ~GMAC_RXW0_OWNERSHIP; + break; + } + frame_is_complete = (bool)(rx_desc->w1 & GMAC_RXW1_EOF); + MODULO_INC(tail, rx_desc_list->len); + rx_desc = &rx_desc_list->buf[tail]; + } + /* Frame which is not complete can be dropped by GMAC. Do not process + * it, even partially. + */ + if (frame_is_complete == false) { + return NULL; + } + + /* Process a frame */ + tail = rx_desc_list->tail; + rx_desc = &rx_desc_list->buf[tail]; + frame_is_complete = false; + + if ((rx_desc->w1 & GMAC_RXW1_SOF) != 0) { + rx_frame = net_pkt_rx_alloc(K_NO_WAIT); + } else { + /* TODO: Don't assume first RX fragment will have SOF (Start of frame) + * bit set. If SOF bit is missing recover gracefully by dropping + * invalid frame. + */ + return NULL; + } + + while (((rx_desc->w0 & GMAC_RXW0_OWNERSHIP) != 0) && (frame_is_complete == false)) { + frag = rx_frag_list[tail]; + frag_data = (uint8_t *)(rx_desc->w0 & GMAC_RXW0_ADDR); + + __ASSERT(frag->data == frag_data, "RX descriptor and buffer list desynchronized"); + + frame_is_complete = (bool)(rx_desc->w1 & GMAC_RXW1_EOF); + if (frame_is_complete == true) { + frag_len = (rx_desc->w1 & GMAC_RXW1_LEN) - frame_len; + } else { + frag_len = CONFIG_NET_BUF_DATA_SIZE; + } + + frame_len += frag_len; + + /* Link frame fragments only if RX net buffer is valid */ + if (rx_frame != NULL) { + /* Get a new data net buffer from the buffer pool */ + new_frag = net_pkt_get_frag(rx_frame, CONFIG_NET_BUF_DATA_SIZE, K_NO_WAIT); + if (new_frag == NULL) { + queue->err_rx_frames_dropped++; + net_pkt_unref(rx_frame); + rx_frame = NULL; + } else { + net_buf_add(frag, frag_len); + if (!last_frag) { + net_pkt_frag_insert(rx_frame, frag); + } else { + net_buf_frag_insert(last_frag, frag); + } + last_frag = frag; + frag = new_frag; + rx_frag_list[tail] = frag; + } + } + + /* Update buffer descriptor status word */ + rx_desc->w1 = 0U; + + /* Guarantee that status word is written before the address + * word to avoid race condition. + */ + barrier_dmem_fence_full(); + + /* Update buffer descriptor address word */ + wrap = (tail == (rx_desc_list->len - 1U) ? GMAC_RXW0_WRAP : 0); + rx_desc->w0 = ((uint32_t)frag->data & GMAC_RXW0_ADDR) | wrap; + + MODULO_INC(tail, rx_desc_list->len); + rx_desc = &rx_desc_list->buf[tail]; + } + + rx_desc_list->tail = tail; + LOG_DBG("Frame complete: rx=%p, tail=%d", rx_frame, tail); + __ASSERT_NO_MSG(frame_is_complete); + + return rx_frame; +} + +/** + * @brief Get the frame from the Rx Queue and pass it onto the net interface. + * + * This function is used to get the frame received from the network from + * the queue, and pass it on to the net interface layer. + * + * @param queue Pointer to the queue onwhich the fram has been received. + */ +static void gmac_rx(struct gmac_queue *queue) +{ + gmac_dev_data_t *dev_data = + CONTAINER_OF(queue, gmac_dev_data_t, queue_list[queue->que_idx]); + struct net_pkt *rx_frame; + + /* More than one frame could have been received by GMAC, get all + * complete frames stored in the GMAC RX descriptor list. + */ + rx_frame = gmac_frame_get(queue); + if (rx_frame == NULL) { +#if defined(CONFIG_NET_STATISTICS_ETHERNET) + dev_data->stats.error_details.rx_buf_alloc_failed++; +#endif + } + + while (rx_frame) { + LOG_DBG("ETH rx"); + + if (net_recv_data(gmac_get_iface(dev_data), rx_frame) < 0) { +#if defined(CONFIG_NET_STATISTICS_ETHERNET) + dev_data->stats.error_details.rx_frame_errors++; +#endif + net_pkt_unref(rx_frame); + } + + rx_frame = gmac_frame_get(queue); + } +} + +static void gmac_rx_thread(void *arg1, void *unused1, void *unused2) +{ + int res; + struct device *dev = (struct device *)arg1; + + gmac_dev_data_t *dev_data = dev->data; + + while (1) { + res = k_sem_take(&dev_data->rx_int_sem, K_MSEC(ETH_MCHP_RX_PKT_WAIT_TIMEOUT_MS)); + if (res == 0) { + /* semaphore taken, update link status and receive packets */ + + struct gmac_queue *queue = dev_data->rx_queue; + + gmac_rx(queue); + } + } +} + +/** + * @brief Start the Eth MAC device. + * + * This function is used to start the Eth Interface. + * + * @param dev Pointer to the Eth MAC device structure. + * + * @return 0 on starting the Eth MAC device. + */ +static int eth_mchp_start(const struct device *dev) +{ + gmac_dev_data_t *const dev_data = dev->data; + + /* Do not start the interface until PHY link is up */ + if (dev_data->link_up == false) { + net_if_carrier_off(dev_data->iface); + } else { + net_eth_carrier_on(dev_data->iface); + } + + return 0; +} + +/** + * @brief Stop the Eth MAC device. + * + * This function is used to stop the Eth Interface. + * + * @param dev Pointer to the Eth MAC device structure. + * + * @return 0 on stopping the Eth MAC device. + */ +static int eth_mchp_stop(const struct device *dev) +{ + gmac_dev_data_t *const dev_data = dev->data; + + net_eth_carrier_off(dev_data->iface); + return 0; +} + +/** + * @brief Find the Tx Queue based on the priority of the packet + * + * This function is used to find the Tx Queue based on the priority of + * the packet. + * + * @param priority packet piority. + * + * @return Queue id of the queue to which this packet should enqueued. + */ +#if !defined(CONFIG_ETH_SAM_GMAC_FORCE_QUEUE) && \ + ((GMAC_ACTIVE_QUEUE_NUM != NET_TC_TX_COUNT) || \ + ((NET_TC_TX_COUNT != NET_TC_RX_COUNT) && defined(CONFIG_NET_VLAN))) +static int eth_mchp_priority2queue(enum net_priority priority) +{ + static const uint8_t queue_priority_map[] = { +#if GMAC_ACTIVE_QUEUE_NUM == 1 + 0, 0, 0, 0, 0, + 0, 0, 0 +#endif + }; + + return queue_priority_map[priority]; +} +#endif + +/** + * @brief Transmit the frame to network + * + * This function is used to queeu the frame coming in from the net interface + * to th etx queue to eventually transmit the frame to the network. + * the packet. + * + * @param dev Pointer to the Eth MAC device structure. + * @param dev Pointer to the net packet which was received from the net interface. + * + * @return 0 if the frame was successfully transmitted, -EIO in error case. + */ +static int eth_mchp_send(const struct device *dev, struct net_pkt *pkt) +{ + const gmac_dev_config_t *const cfg = dev->config; + gmac_dev_data_t *const dev_data = dev->data; + gmac_registers_t *const gmac_regs = cfg->regs; + struct gmac_queue *queue; + struct gmac_desc_list *tx_desc_list; + struct gmac_desc *tx_desc; + struct gmac_desc *tx_first_desc; + struct net_buf *frag; + uint8_t *frag_data; + uint16_t frag_len; + uint32_t err_tx_flushed_count_at_entry; + uint8_t pkt_prio; + uint32_t pkt_len = 0; + + pkt_len = net_pkt_get_len(pkt); + + if (pkt == NULL) { +#if defined(CONFIG_NET_STATISTICS_ETHERNET) + dev_data->stats.errors.tx++; +#endif + return -EIO; + } + + if (pkt->frags == NULL) { +#if defined(CONFIG_NET_STATISTICS_ETHERNET) + dev_data->stats.errors.tx++; +#endif + return -EIO; + } + + LOG_DBG("ETH tx"); + + /* Decide which queue should be used */ + pkt_prio = net_pkt_priority(pkt); + +#if defined(CONFIG_ETH_SAM_GMAC_FORCE_QUEUE) + /* Route eveything to the forced queue */ + queue = &dev_data->queue_list[CONFIG_ETH_SAM_GMAC_FORCED_QUEUE]; + +#elif GMAC_ACTIVE_QUEUE_NUM == CONFIG_NET_TC_TX_COUNT + /* Prefer to chose queue based on its traffic class */ + queue = &dev_data->queue_list[net_tx_priority2tc(pkt_prio)]; + +#else + /* If that's not possible due to config - use builtin mapping */ + queue = &dev_data->queue_list[eth_mchp_priority2queue(pkt_prio)]; +#endif + + tx_desc_list = &queue->tx_desc_list; + err_tx_flushed_count_at_entry = queue->err_tx_flushed_count; + + frag = pkt->frags; + + /* Keep reference to the descriptor */ + tx_first_desc = &tx_desc_list->buf[tx_desc_list->head]; + + while (frag != NULL) { + + frag_data = frag->data; + frag_len = frag->len; + + tx_desc = &tx_desc_list->buf[tx_desc_list->head]; + + /* Update buffer descriptor address word */ + tx_desc->w0 = (uint32_t)frag_data; + + /* Update buffer descriptor status word (clear used bit except + * for the first frag). + */ + tx_desc->w1 = + (frag_len & GMAC_TXW1_LEN) | (!frag->frags ? GMAC_TXW1_LASTBUFFER : 0) | + (tx_desc_list->head == (tx_desc_list->len - 1U) ? GMAC_TXW1_WRAP : 0) | + (tx_desc == tx_first_desc ? GMAC_TXW1_USED : 0); + + /* Update descriptor position */ + MODULO_INC(tx_desc_list->head, tx_desc_list->len); + + /* Continue with the rest of fragments (only data) */ + frag = frag->frags; + } + + /* Ensure the descriptor following the last one is marked as used */ + tx_desc_list->buf[tx_desc_list->head].w1 = GMAC_TXW1_USED; + + /* Guarantee that all the fragments have been written before removing + * the used bit to avoid race condition. + */ + barrier_dmem_fence_full(); + + /* Remove the used bit of the first fragment to allow the controller + * to process it and the following fragments. + */ + tx_first_desc->w1 &= ~GMAC_TXW1_USED; + + /* Guarantee that the first fragment got its bit removed before starting + * sending packets to avoid packets getting stuck. + */ + barrier_dmem_fence_full(); + + /* Start transmission */ + gmac_tx(gmac_regs); + + /* Wait until the packet is sent */ + k_sem_take(&queue->tx_sem, K_FOREVER); + + /* Check if transmit successful or not */ + if (queue->err_tx_flushed_count != err_tx_flushed_count_at_entry) { +#if defined(CONFIG_NET_STATISTICS_ETHERNET) + dev_data->stats.errors.tx++; +#endif + return -EIO; + } + return 0; +} + +/** + * @brief ISR when either packet is received or transmission is completed + * + * This is an ISR which is called when either packet is received on Queue 0 + * or transmission of the previous frame has completed. + * + * @param dev Pointer to the Eth MAC device structure. + */ +static void eth_mchp_queue0_isr(const struct device *dev) +{ + const gmac_dev_config_t *const cfg = dev->config; + gmac_dev_data_t *const dev_data = dev->data; + gmac_registers_t *const gmac_regs = cfg->regs; + struct gmac_queue *queue = &dev_data->queue_list[0]; + + gmac_queue0_isr(gmac_regs, queue); +} + +/** + * @brief Eth MAC Device Initialization + * + * This function is used to initialize the Eth MAC devices etting the + * pin control state, and ensuring the Eth MAC device is ready. + * + * @param dev Pointer to the Eth MAC device structure. + * + * @return 0 on success, negative error code on failure. + */ +static int eth_mchp_initialize(const struct device *dev) +{ + const gmac_dev_config_t *const cfg = dev->config; + gmac_dev_data_t *const dev_data = dev->data; + + int retval; + + cfg->config_func(); + + /* Enable clocks */ + retval = clock_control_on( + ((const gmac_dev_config_t *)(dev->config))->eth_clock.clock_dev, + (((gmac_dev_config_t *)(dev->config))->eth_clock.mclk_apb_sys)); + if ((retval != 0) && (retval != -EALREADY)) { + LOG_ERR("Failed to enable the MCLK APB for Ethernet: %d", retval); + return retval; + } + + retval = clock_control_on( + ((const gmac_dev_config_t *)(dev->config))->eth_clock.clock_dev, + (((gmac_dev_config_t *)(dev->config))->eth_clock.mclk_ahb_sys)); + if ((retval != 0) && (retval != -EALREADY)) { + LOG_ERR("Failed to enable the MCLK AHB for Ethernet: %d", retval); + return retval; + } + + /* Connect pins to the peripheral */ + retval = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); + if (retval != 0) { + LOG_ERR("pinctrl_apply_state() Failed for Ethernet driver: %d", retval); + return retval; + } + + k_sem_init(&dev_data->rx_int_sem, 0, K_SEM_MAX_LIMIT); + + return retval; +} + +/** + * @brief Eth MAC Interface link state change callback + * + * This function is called when there is a change in link state of the Eth Phy + * to let the Eth MAC driver know about it. This callback is registered with + * the Eth Phy during initialization. + * + * @param pdev Pointer to the Eth Phy device structure. + * @param state Pointer to the link state structure. + * @param user_data Pointer to the Eth MAC device structure. + */ +static void eth_mchp_phy_link_state_changed(const struct device *pdev, struct phy_link_state *state, + void *user_data) +{ + const struct device *dev = (const struct device *)user_data; + gmac_dev_data_t *const dev_data = dev->data; + const gmac_dev_config_t *const cfg = dev->config; + gmac_registers_t *const gmac_regs = cfg->regs; + bool is_up; + + is_up = state->is_up; + + if ((is_up == true) && (dev_data->link_up == false)) { + LOG_INF("Link up"); + + /* Announce link up status */ + dev_data->link_up = true; + net_eth_carrier_on(dev_data->iface); + + /* Set up link */ + gmac_link_configure(gmac_regs, PHY_LINK_IS_FULL_DUPLEX(state->speed), + PHY_LINK_IS_SPEED_100M(state->speed)); + } else if ((is_up == false) && (dev_data->link_up == true)) { + LOG_INF("Link down"); + + /* Announce link down status */ + dev_data->link_up = false; + net_eth_carrier_off(dev_data->iface); + } +} + +/** + * @brief Getting the Phy Device handle + * + * This function is called for getting the phy device handle associated with + * this Eth MAc interface. + * + * @param dev Pointer to the Eth MAC device structure. + * + * @return Pointer to the Phy Device structure. + */ +static const struct device *eth_mchp_get_phy(const struct device *dev) +{ + const gmac_dev_config_t *const cfg = dev->config; + + return cfg->phy_dev; +} + +/** + * @brief Initializing the Eth Interface + * + * This function is used to initialize the ethernet interface by configure + * and enabling the clocks, setting the appropriate registers, initializing + * rx and tx queues associated with the interface, and setting and regsitering + * the MAC address with the upper layer. + * + * @param iface Pointer to the net interface structure. + */ +static void eth_mchp_iface_init(struct net_if *iface) +{ + const struct device *dev = net_if_get_device(iface); + gmac_dev_data_t *const dev_data = dev->data; + const gmac_dev_config_t *const cfg = dev->config; + gmac_registers_t *const gmac_regs = cfg->regs; + static bool init_done; + int i; + int result; + + if (dev_data->iface == NULL) { + dev_data->iface = iface; + } + + ethernet_init(iface); + + /* The rest of initialization should only be done once */ + if (init_done == true) { + return; + } + + result = gmac_init(gmac_regs); + if (result < 0) { + LOG_ERR("Unable to initialize ETH driver"); + return; + } + + /* Set the MAC address */ + gmac_generate_set_mac(gmac_regs, dev_data->mac_addr); + + LOG_INF("MAC: %02x:%02x:%02x:%02x:%02x:%02x", dev_data->mac_addr[0], + dev_data->mac_addr[1], dev_data->mac_addr[2], dev_data->mac_addr[3], + dev_data->mac_addr[4], dev_data->mac_addr[5]); + + /* Register Ethernet MAC Address with the upper layer */ + net_if_set_link_addr(iface, dev_data->mac_addr, sizeof(dev_data->mac_addr), + NET_LINK_ETHERNET); + + /* Initialize GMAC queues */ + for (i = GMAC_QUE_0; i < GMAC_QUEUE_NUM; i++) { + result = gmac_queue_init(gmac_regs, &dev_data->queue_list[i]); + if (result < 0) { + LOG_ERR("Unable to initialize ETH queue%d", i); + return; + } + } + + if (result < 0) { + return; + } + + if (device_is_ready(cfg->phy_dev) == true) { + phy_link_callback_set(cfg->phy_dev, ð_mchp_phy_link_state_changed, + (void *)dev); + + } else { + LOG_ERR("PHY device not ready"); + } + + /* Start interruption-poll thread */ + k_thread_create(&dev_data->rx_thread, dev_data->rx_thread_stack, + K_KERNEL_STACK_SIZEOF(dev_data->rx_thread_stack), + gmac_rx_thread, (void *)dev, NULL, NULL, + IS_ENABLED(CONFIG_NET_TC_THREAD_PREEMPTIVE) + ? K_PRIO_PREEMPT(CONFIG_ETH_MCHP_RX_THREAD_PRIORITY) + : K_PRIO_COOP(CONFIG_ETH_MCHP_RX_THREAD_PRIORITY), + 0, K_NO_WAIT); + + k_thread_name_set(&dev_data->rx_thread, "mchp_eth_rx"); + + init_done = true; + + return; +} + +#if defined(CONFIG_NET_STATISTICS_ETHERNET) +/** + * @brief Get the statistics related to Ethernet interface. + * + * This function is used to get the statistics related to Ethernet interface. + * + * @param dev Pointer to the Eth MAC device structure. + * + * @return net_stats_eth* Pointer to the ethernet statistics structure. + */ +static struct net_stats_eth *eth_mchp_get_stats(const struct device *dev) +{ + gmac_dev_data_t *dev_data = dev->data; + const gmac_dev_config_t *const cfg = dev->config; + gmac_registers_t *const gmac_regs = cfg->regs; + + gmac_get_stats(gmac_regs, &dev_data->stats); + + return &dev_data->stats; +} +#endif + +/** + * @brief Get the Eth MAC device capabilities. + * + * This function is used to get the Eth MAC device capabilities. + * + * @param dev Pointer to the Eth MAC device structure. + * + * @return set of device capabilities. + */ +static enum ethernet_hw_caps eth_mchp_get_capabilities(const struct device *dev) +{ + ARG_UNUSED(dev); + + return ETHERNET_LINK_10BASE | +#if defined(CONFIG_NET_VLAN) + ETHERNET_HW_VLAN | +#endif +#if GMAC_ACTIVE_PRIORITY_QUEUE_NUM >= 1 + ETHERNET_PRIORITY_QUEUES | ETHERNET_QAV | +#endif + ETHERNET_LINK_100BASE; +} + +/** + * @brief Set the hardware specific configuration for the Eternet interface. + * + * This function is used to set the hardware specific configuration for + * the Eternet interface. + * + * @param dev Pointer to the Eth MAC device structure. + * @param type specific param being set like the MAC address. + * @param config Pointer to the Eth configuration structure to get the + * corresponding config to be set. + * + * @return 0 if successful, -ENOTSUP if type not supported. + */ +static int eth_mchp_set_config(const struct device *dev, enum ethernet_config_type type, + const struct ethernet_config *config) +{ + int result = 0; + + switch (type) { + case ETHERNET_CONFIG_TYPE_MAC_ADDRESS: { + gmac_dev_data_t *const dev_data = dev->data; + const gmac_dev_config_t *const cfg = dev->config; + gmac_registers_t *const gmac_regs = cfg->regs; + + memcpy(dev_data->mac_addr, config->mac_address.addr, sizeof(dev_data->mac_addr)); + + /* Set MAC Address for frame filtering logic */ + gmac_mac_addr_set(gmac_regs, 0, dev_data->mac_addr); + + LOG_INF("%s MAC set to %02x:%02x:%02x:%02x:%02x:%02x", dev->name, + dev_data->mac_addr[0], dev_data->mac_addr[1], dev_data->mac_addr[2], + dev_data->mac_addr[3], dev_data->mac_addr[4], dev_data->mac_addr[5]); + + /* Register Ethernet MAC Address with the upper layer */ + net_if_set_link_addr(dev_data->iface, dev_data->mac_addr, + sizeof(dev_data->mac_addr), NET_LINK_ETHERNET); + break; + } + default: + result = -ENOTSUP; + break; + } + + return result; +} + +/** + * @brief Get the hardware specific configuration for the Eternet interface. + * + * This function is used to get the hardware specific configuration for + * the Eternet interface. + * + * @param dev Pointer to the Eth MAC device structure. + * @param type specific param being get like the number of queues. + * @param config Pointer to the Eth configuration structure to get the + * corresponding config to be set. + * + * @return 0 if successful, -ENOTSUP if type not supported. + */ +static int eth_mchp_get_config(const struct device *dev, enum ethernet_config_type type, + struct ethernet_config *config) +{ + const gmac_dev_config_t *const cfg = dev->config; + gmac_registers_t *const gmac_regs = cfg->regs; + uint32_t val; + uint32_t retval = 0; + + switch (type) { + case ETHERNET_CONFIG_TYPE_RX_CHECKSUM_SUPPORT: + { + val = gmac_regs->GMAC_NCFGR; + config->chksum_support = (val & GMAC_NCFGR_RXCOEN_Msk) ? (ETHERNET_CHECKSUM_SUPPORT_IPV4_HEADER | ETHERNET_CHECKSUM_SUPPORT_TCP | ETHERNET_CHECKSUM_SUPPORT_UDP) : 0; + break; + } + + default: + retval = -ENOTSUP; + break; + } + + return retval; +} + +/** + * @brief Eth MAC device API structure for Microchip Eth MAC driver. + * + * This structure defines the function pointers for Eth MAC operations, + * including sending/ receiving frames and optional statistics functions. + */ +static const struct ethernet_api eth_mchp_api = { + /* interface initialization function */ + .iface_api.init = eth_mchp_iface_init, + + /* eth interface start function */ + .start = eth_mchp_start, + + /* eth interface stop function */ + .stop = eth_mchp_stop, + +#if defined(CONFIG_NET_STATISTICS_ETHERNET) + /* get interface stats function */ + .get_stats = eth_mchp_get_stats, +#endif + + /* get device capabilities function */ + .get_capabilities = eth_mchp_get_capabilities, + + /* eth hw config set function */ + .set_config = eth_mchp_set_config, + + /* eth hw config get function */ + .get_config = eth_mchp_get_config, + + /* phy interface get function */ + .get_phy = eth_mchp_get_phy, + + /* eth frame transmit function */ + .send = eth_mchp_send, +}; + +/** + * @brief Macro to configure and enable Eth MAC IRQ + */ +static void eth0_irq_config(void) +{ + IRQ_CONNECT(DT_INST_IRQ_BY_NAME(0, gmac, irq), DT_INST_IRQ_BY_NAME(0, gmac, priority), + eth_mchp_queue0_isr, DEVICE_DT_INST_GET(0), 0); + irq_enable(DT_INST_IRQ_BY_NAME(0, gmac, irq)); +} + +PINCTRL_DT_INST_DEFINE(0); + +static const gmac_dev_config_t eth0_config = { + .regs = (gmac_registers_t *)DT_INST_REG_ADDR(0), + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0), + .config_func = eth0_irq_config, + ETH_MCHP_CLOCK_DEFN(0), + .phy_dev = DEVICE_DT_GET(DT_INST_PHANDLE(0, phy_handle)), +}; + +static gmac_dev_data_t eth0_data = { +#if NODE_HAS_VALID_MAC_ADDR(DT_DRV_INST(0)) + .mac_addr = DT_INST_PROP(0, local_mac_address), +#endif + .queue_list = {{ + .que_idx = GMAC_QUE_0, + /* clang-format off */ + .rx_desc_list = { + .buf = rx_desc_que0, + .len = ARRAY_SIZE(rx_desc_que0), + }, + .tx_desc_list = { + .buf = tx_desc_que0, + .len = ARRAY_SIZE(tx_desc_que0), + }, + /* clang-format on */ + .rx_frag_list = rx_frag_list_que0, + }}, +}; + +/** + * @brief Macro to initialize Eth MAC device + * + * @param n Instance number + */ +ETH_NET_DEVICE_DT_INST_DEFINE(0, eth_mchp_initialize, NULL, ð0_data, ð0_config, + CONFIG_ETH_INIT_PRIORITY, ð_mchp_api, GMAC_MTU); diff --git a/drivers/mdio/CMakeLists.txt b/drivers/mdio/CMakeLists.txt index ee772e79f8072..3f676c14b7c92 100644 --- a/drivers/mdio/CMakeLists.txt +++ b/drivers/mdio/CMakeLists.txt @@ -4,6 +4,7 @@ zephyr_library() 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_MCHP_GMAC_G1 mdio_mchp_gmac_g1.c) zephyr_library_sources_ifdef(CONFIG_MDIO_ESP32 mdio_esp32.c) zephyr_library_sources_ifdef(CONFIG_MDIO_LITEX_LITEETH mdio_litex_liteeth.c) zephyr_library_sources_ifdef(CONFIG_MDIO_NXP_IMX_NETC mdio_nxp_imx_netc.c) diff --git a/drivers/mdio/Kconfig b/drivers/mdio/Kconfig index 8b842ac6a8eda..2d97c234d294a 100644 --- a/drivers/mdio/Kconfig +++ b/drivers/mdio/Kconfig @@ -26,6 +26,7 @@ config MDIO_SHELL # overridden (by defining symbols in multiple locations) source "drivers/mdio/Kconfig.esp32" source "drivers/mdio/Kconfig.sam" +source "drivers/mdio/Kconfig.mchp" source "drivers/mdio/Kconfig.nxp_imx_netc" source "drivers/mdio/Kconfig.nxp_s32_netc" source "drivers/mdio/Kconfig.nxp_s32_gmac" diff --git a/drivers/mdio/Kconfig.mchp b/drivers/mdio/Kconfig.mchp new file mode 100644 index 0000000000000..c6266b806e483 --- /dev/null +++ b/drivers/mdio/Kconfig.mchp @@ -0,0 +1,9 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +config MDIO_MCHP_GMAC_G1 + bool "Microchip MDIO driver" + depends on ETH_MCHP_GMAC + default y + help + Enable Microchip MCU Family MDIO driver. diff --git a/drivers/mdio/mdio_mchp_gmac_g1.c b/drivers/mdio/mdio_mchp_gmac_g1.c new file mode 100644 index 0000000000000..5963d8524afeb --- /dev/null +++ b/drivers/mdio/mdio_mchp_gmac_g1.c @@ -0,0 +1,511 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file mdio_mchp_gmac_g1.c + * @brief MDIO driver for Microchip devices. + * + * This file provides the implementation of mdio functions + * for Microchip-based systems. + */ + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(mdio_mchp_gmac_g1, CONFIG_MDIO_LOG_LEVEL); + +/* Define compatible string */ +#define DT_DRV_COMPAT microchip_gmac_g1_mdio + +#define MDIO_MCHP_CLOCK_RATE_20MHZ (20000000U) +#define MDIO_MCHP_CLOCK_RATE_40MHZ (40000000U) +#define MDIO_MCHP_CLOCK_RATE_80MHZ (80000000U) +#define MDIO_MCHP_CLOCK_RATE_120MHZ (120000000U) +#define MDIO_MCHP_CLOCK_RATE_160MHZ (160000000U) +#define MDIO_MCHP_CLOCK_RATE_240MHZ (240000000U) + +/** + * @brief Define the HAL configuration for MDIO. + * + * This macro sets up the HAL configuration for the MDIO peripheral + * + * @param n Instance number. + */ +#define HAL_MCHP_MDIO_CONFIG(n) .regs = (gmac_registers_t *)DT_INST_REG_ADDR(n), + +#define MDIO_MCHP_ESUCCESS 0 + +#define MDIO_MCHP_OP_TIMEOUT 50 + +/* Do the peripheral clock related configuration */ + +/** + * @brief Clock configuration structure for the MDIO. + * + * This structure contains the clock configuration parameters for the MDIO + * peripheral. + */ +typedef struct mdio_clock { + /* Clock driver */ + const struct device *clock_dev; + + /* Main APB clock subsystem. */ + clock_control_subsys_t mclk_apb_sys; + + /* Main AHB clock subsystem. */ + clock_control_subsys_t mclk_ahb_sys; +} mdio_clock_t; + +/** + * @brief Run time data structure for the MDIO device. + * + * This structure contains the run time parameters for the MDIO device. + */ +struct mdio_dev_data { + /* Semaphore to access registers */ + struct k_sem sem; +}; + +/** + * @brief device configuration structure for the MDIO device. + * + * This structure contains the Device constant configuration parameters + * for the MDIO device. + */ +typedef struct mdio_dev_config { + /* Pin control structure */ + const struct pinctrl_dev_config *pcfg; + + /* GMAC register */ + gmac_registers_t *const regs; + + /* clock structure */ + mdio_clock_t mdio_clock; +} mdio_dev_config_t; + +/* clang-format off */ +#define MDIO_MCHP_CLOCK_DEFN(n) \ + .mdio_clock.clock_dev = DEVICE_DT_GET(DT_NODELABEL(clock)), \ + .mdio_clock.mclk_apb_sys = (void *)DT_INST_CLOCKS_CELL_BY_NAME(n, mclk_apb, subsystem), \ + .mdio_clock.mclk_ahb_sys = (void *)DT_INST_CLOCKS_CELL_BY_NAME(n, mclk_ahb, subsystem) +/* clang-format on */ + +/** + * @brief Register configuration structure + * + * This structure contains the configuration parameters for + * reading/ writing onto the MDIO bus. + */ +typedef struct mdio_config_transfer { + /* Operation - read/ write */ + enum mdio_opcode op; + + /* Data to be written */ + uint16_t data_in; + + /* BUffer for data to be read */ + uint16_t *data_out; + + /* Port Address */ + uint8_t prtad; + + /* Register Address */ + uint8_t regad; + + /* Using clause c34 or not */ + bool c45; +} mdio_config_transfer_t; + +/** + * @brief Read/ Write to MDIO bus + * + * This function is used to read/ write to MDIO bus. + * + * @param regs Pointer to gmac registers. + * @param cfg Pointer to config related to the read/ write operation. + * + * @return 0 if successful else -ETIMEDOUT if the request timedout. + */ +static inline int mdio_transfer(gmac_registers_t *regs, mdio_config_transfer_t *cfg) +{ + int timeout = MDIO_MCHP_OP_TIMEOUT; + + /* Evaluate the register value to be set */ + uint32_t reg_val = (cfg->c45 ? 0U : GMAC_MAN_CLTTO_Msk) | GMAC_MAN_OP(cfg->op) | + GMAC_MAN_WTN(0x02) | GMAC_MAN_PHYA(cfg->prtad) | + GMAC_MAN_REGA(cfg->regad) | GMAC_MAN_DATA(cfg->data_in); + + /* Set the value in the register */ + regs->GMAC_MAN = reg_val; + + /* Wait until done */ + while ((regs->GMAC_NSR & GMAC_NSR_IDLE_Msk) == 0) { + if (timeout-- == 0U) { + LOG_ERR("transfer timedout"); + return -ETIMEDOUT; + } + + k_sleep(K_MSEC(5)); + } + + /* Copy the value in case of read operation */ + if ((cfg->data_out) != NULL) { + *(cfg->data_out) = regs->GMAC_MAN & GMAC_MAN_DATA_Msk; + } + + return MDIO_MCHP_ESUCCESS; +} + +/** + * @brief Read from register + * + * This function is used to read from MII register. + * + * @param dev Pointer to the MDIO device structure. + * @param prtad Port Address. + * @param regad Register Address. + * @param data Pointer to the buffer to read the value. + * + * @return 0 if successful else -ETIMEDOUT if the request timedout. + */ +static int mdio_mchp_read(const struct device *dev, uint8_t prtad, uint8_t regad, uint16_t *data) +{ + int ret = MDIO_MCHP_ESUCCESS; + struct mdio_dev_data *const mdio_data = dev->data; + const struct mdio_dev_config *const cfg = dev->config; + mdio_config_transfer_t cfg_xfer; + + /* Take Semaphore */ + k_sem_take(&mdio_data->sem, K_FOREVER); + + cfg_xfer.prtad = prtad; + cfg_xfer.regad = regad; + cfg_xfer.op = MDIO_OP_C22_READ; + cfg_xfer.c45 = false; + cfg_xfer.data_in = 0; + cfg_xfer.data_out = data; + + /* Calling HAL API to read the data */ + ret = mdio_transfer(cfg->regs, &cfg_xfer); + + /* Release Semaphore */ + k_sem_give(&mdio_data->sem); + return ret; +} + +/** + * @brief Write to register + * + * This function is used to write to MII register. + * + * @param dev Pointer to the MDIO device structure. + * @param prtad Port Address. + * @param regad Register Address. + * @param data Buffer to write the value from. + * + * @return 0 if successful else -ETIMEDOUT if the request timedout. + */ +static int mdio_mchp_write(const struct device *dev, uint8_t prtad, uint8_t regad, uint16_t data) +{ + int ret = MDIO_MCHP_ESUCCESS; + struct mdio_dev_data *const mdio_data = dev->data; + const struct mdio_dev_config *const cfg = dev->config; + mdio_config_transfer_t cfg_xfer; + + /* Take Semaphore */ + k_sem_take(&mdio_data->sem, K_FOREVER); + + cfg_xfer.prtad = prtad; + cfg_xfer.regad = regad; + cfg_xfer.op = MDIO_OP_C22_WRITE; + cfg_xfer.c45 = false; + cfg_xfer.data_in = data; + cfg_xfer.data_out = NULL; + + /* Calling HAL API to write the data */ + ret = mdio_transfer(cfg->regs, &cfg_xfer); + + /* Release Semaphore */ + k_sem_give(&mdio_data->sem); + return ret; +} + +/** + * @brief Read from MDIO bus using clause 45 access + * + * This function is used to read from MDIO bus using clause 45 access. + * + * @param dev Pointer to the MDIO device structure. + * @param prtad Port Address. + * @param devad Device Address. + * @param regad Register Address. + * @param data Pointer to the buffer to read the value. + * + * @return 0 if successful else -ETIMEDOUT if the request timedout. + */ +static int mdio_mchp_read_c45(const struct device *dev, uint8_t prtad, uint8_t devad, + uint16_t regad, uint16_t *data) +{ + int err = MDIO_MCHP_ESUCCESS; + struct mdio_dev_data *const mdio_data = dev->data; + const struct mdio_dev_config *const cfg = dev->config; + mdio_config_transfer_t cfg_xfer; + + /* Take Semaphore */ + k_sem_take(&mdio_data->sem, K_FOREVER); + + cfg_xfer.prtad = prtad; + cfg_xfer.regad = devad; + cfg_xfer.op = MDIO_OP_C45_ADDRESS; + cfg_xfer.c45 = true; + cfg_xfer.data_in = regad; + cfg_xfer.data_out = NULL; + + /* Calling HAL API to write address from which to read */ + err = mdio_transfer(cfg->regs, &cfg_xfer); + if (err == MDIO_MCHP_ESUCCESS) { + cfg_xfer.prtad = prtad; + cfg_xfer.regad = devad; + cfg_xfer.op = MDIO_OP_C45_READ; + cfg_xfer.c45 = true; + cfg_xfer.data_in = 0; + cfg_xfer.data_out = data; + + /* Calling HAL API to read the data */ + err = mdio_transfer(cfg->regs, &cfg_xfer); + } + + /* Release Semaphore */ + k_sem_give(&mdio_data->sem); + return err; +} + +/** + * @brief Write to MDIO bus using clause 45 access + * + * This function is used to write to MDIO bus using clause 45 access. + * + * @param dev Pointer to the MDIO device structure. + * @param prtad Port Address. + * @param devad Device Address. + * @param regad Register Address. + * @param data Buffer to write the value to. + * + * @return 0 if successful else -ETIMEDOUT if the request timedout. + */ +static int mdio_mchp_write_c45(const struct device *dev, uint8_t prtad, uint8_t devad, + uint16_t regad, uint16_t data) +{ + int err = MDIO_MCHP_ESUCCESS; + struct mdio_dev_data *const mdio_data = dev->data; + const struct mdio_dev_config *const cfg = dev->config; + mdio_config_transfer_t cfg_xfer; + + /* Take Semaphore */ + k_sem_take(&mdio_data->sem, K_FOREVER); + + cfg_xfer.prtad = prtad; + cfg_xfer.regad = devad; + cfg_xfer.op = MDIO_OP_C45_ADDRESS; + cfg_xfer.c45 = true; + cfg_xfer.data_in = regad; + cfg_xfer.data_out = NULL; + + /* Calling HAL API to write address from which to read */ + err = mdio_transfer(cfg->regs, &cfg_xfer); + if (err == MDIO_MCHP_ESUCCESS) { + cfg_xfer.prtad = prtad; + cfg_xfer.regad = devad; + cfg_xfer.op = MDIO_OP_C45_READ; + cfg_xfer.c45 = true; + cfg_xfer.data_in = data; + cfg_xfer.data_out = NULL; + + /* Calling HAL API to write the data */ + err = mdio_transfer(cfg->regs, &cfg_xfer); + } + + /* Release Semaphore */ + k_sem_give(&mdio_data->sem); + return err; +} + +/** + * @brief Enable MDIO bus + * + * This function is used to enable MDIO bus. + * + * @param dev Pointer to the MDIO device structure. + */ +static void mdio_mchp_bus_enable(const struct device *dev) +{ + const struct mdio_dev_config *const cfg = dev->config; + + cfg->regs->GMAC_NCR |= GMAC_NCR_MPE_Msk; +} + +/** + * @brief Disable MDIO bus + * + * This function is used to disable MDIO bus. + * + * @param dev Pointer to the MDIO device structure. + */ +static void mdio_mchp_bus_disable(const struct device *dev) +{ + const struct mdio_dev_config *const cfg = dev->config; + + cfg->regs->GMAC_NCR &= ~GMAC_NCR_MPE_Msk; +} + +/** + * @brief Set MCK to MDC clock divisor. + * + * This function is used to set MCK to MDC clock divisor. Also, according to + * 802.3 MDC should be less then 2.5 MHz + * + * @param mck valid frequency. + * + * @return divisor if successful, -ENOTSUP if not supprted. + */ +static inline int mdio_get_mck_clock_divisor(uint32_t mck) +{ + uint32_t mck_divisor; + + if (mck <= MDIO_MCHP_CLOCK_RATE_20MHZ) { + mck_divisor = GMAC_NCFGR_CLK_MCK8; + } else if (mck <= MDIO_MCHP_CLOCK_RATE_40MHZ) { + mck_divisor = GMAC_NCFGR_CLK_MCK16; + } else if (mck <= MDIO_MCHP_CLOCK_RATE_80MHZ) { + mck_divisor = GMAC_NCFGR_CLK_MCK32; + } else if (mck <= MDIO_MCHP_CLOCK_RATE_120MHZ) { + mck_divisor = GMAC_NCFGR_CLK_MCK48; + } else if (mck <= MDIO_MCHP_CLOCK_RATE_160MHZ) { + mck_divisor = GMAC_NCFGR_CLK_MCK64; + } else if (mck <= MDIO_MCHP_CLOCK_RATE_240MHZ) { + mck_divisor = GMAC_NCFGR_CLK_MCK96; + } else { + LOG_ERR("No valid MDC clock"); + mck_divisor = -ENOTSUP; + } + + LOG_INF("mck %d mck_divisor = 0x%x", mck, mck_divisor); + + return mck_divisor; +} + +/** + * @brief MDIO Device Initialization + * + * This function is used to initialize the MDIO device, setting the + * pin control state, and enabling the clock. + * + * @param dev Pointer to the Eth MAC device structure. + * + * @return 0 on success, negative error code on failure. + */ +static int mdio_mchp_initialize(const struct device *dev) +{ + const struct mdio_dev_config *const cfg = dev->config; + struct mdio_dev_data *const data = dev->data; + int retval = MDIO_MCHP_ESUCCESS; + uint32_t clk_freq_hz = 0; + int mck_divisor; + + do { + /* Initialize the Semaphore */ + k_sem_init(&data->sem, 1, 1); + + /* Enable clocks */ + retval = clock_control_on( + ((const mdio_dev_config_t *)(dev->config))->mdio_clock.clock_dev, + (((mdio_dev_config_t *)(dev->config))->mdio_clock.mclk_apb_sys)); + if ((retval != 0) && (retval != -EALREADY)) { + LOG_ERR("Failed to enable the MCLK APB for Mdio: %d", retval); + break; + } + + retval = clock_control_on( + ((const mdio_dev_config_t *)(dev->config))->mdio_clock.clock_dev, + (((mdio_dev_config_t *)(dev->config))->mdio_clock.mclk_ahb_sys)); + if ((retval != 0) && (retval != -EALREADY)) { + LOG_ERR("Failed to enable the MCLK AHB for Mdio: %d", retval); + break; + } + + /* Get Clock frequency */ + retval = clock_control_get_rate( + ((const mdio_dev_config_t *)(dev->config))->mdio_clock.clock_dev, + (((mdio_dev_config_t *)(dev->config))->mdio_clock.mclk_apb_sys), + &clk_freq_hz); + if (retval < 0) { + LOG_ERR("ETH_MCHP_GET_CLOCK_FREQ Failed"); + } + + mck_divisor = mdio_get_mck_clock_divisor(clk_freq_hz); + if (mck_divisor < 0) { + retval = mck_divisor; + break; + } + + /* Setup Network Configuration Register */ + cfg->regs->GMAC_NCFGR = mck_divisor; + /* Connect pins to the peripheral */ + retval = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); + if (retval != 0) { + LOG_ERR("pinctrl_apply_state() Failed for Mdio driver: %d", retval); + break; + } + } while (0); + + return retval; +} + +/** + * @brief MDIO device API structure for Microchip MDIO driver. + * + * This structure defines the function pointers for MDIO operations, + * including reading/ writing to bus, and enabling/ disabling of the bus. + */ +static DEVICE_API(mdio, mdio_mchp_driver_api) = { + /* read function */ + .read = mdio_mchp_read, + + /* write function */ + .write = mdio_mchp_write, + + /* read c45 function */ + .read_c45 = mdio_mchp_read_c45, + + /* write c45 function */ + .write_c45 = mdio_mchp_write_c45, + + /* enable bus function */ + .bus_enable = mdio_mchp_bus_enable, + + /* disable bus function */ + .bus_disable = mdio_mchp_bus_disable, +}; + +#define MDIO_MCHP_CONFIG(n) \ + static const struct mdio_dev_config mdio_dev_config_##n = { \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ + .regs = (gmac_registers_t *)DT_INST_REG_ADDR(n), \ + MDIO_MCHP_CLOCK_DEFN(n)}; + +#define MDIO_MCHP_DEVICE(n) \ + PINCTRL_DT_INST_DEFINE(n); \ + MDIO_MCHP_CONFIG(n); \ + static struct mdio_dev_data mdio_dev_data##n; \ + DEVICE_DT_INST_DEFINE(n, &mdio_mchp_initialize, NULL, &mdio_dev_data##n, \ + &mdio_dev_config_##n, POST_KERNEL, CONFIG_MDIO_INIT_PRIORITY, \ + &mdio_mchp_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(MDIO_MCHP_DEVICE) diff --git a/dts/arm/microchip/sam/sam_d5x_e5x/atsame54/atsame54p20a.dtsi b/dts/arm/microchip/sam/sam_d5x_e5x/atsame54/atsame54p20a.dtsi index f65e3775b5329..074c491a747e4 100644 --- a/dts/arm/microchip/sam/sam_d5x_e5x/atsame54/atsame54p20a.dtsi +++ b/dts/arm/microchip/sam/sam_d5x_e5x/atsame54/atsame54p20a.dtsi @@ -7,3 +7,4 @@ #include #include #include +#include diff --git a/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi b/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi index 9e2a23568565c..67e7a5eca04b1 100644 --- a/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi +++ b/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi @@ -238,4 +238,4 @@ &nvic { arm,num-irq-priority-bits = <3>; -}; +}; \ No newline at end of file diff --git a/dts/arm/microchip/sam/sam_d5x_e5x/common/same53.dtsi b/dts/arm/microchip/sam/sam_d5x_e5x/common/same53.dtsi new file mode 100644 index 0000000000000..f6d53fe2bffe0 --- /dev/null +++ b/dts/arm/microchip/sam/sam_d5x_e5x/common/same53.dtsi @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + soc { + gmac: ethernet@42000800 { + compatible = "microchip,gmac-g1-eth"; + reg = <0x42000800 0x400>; + interrupts = <84 0>; + interrupt-names = "gmac"; + num-queues = <1>; + local-mac-address = [00 00 00 00 00 00]; + status = "disabled"; + clocks = <&mclkperiph CLOCK_MCHP_MCLKPERIPH_ID_AHB_GMAC>, + <&mclkperiph CLOCK_MCHP_MCLKPERIPH_ID_APBC_GMAC>; + clock-names = "mclk-ahb", "mclk-apb"; + }; + mdio: mdio@42000800 { + compatible = "microchip,gmac-g1-mdio"; + reg = <0x42000800 0x400>; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&mclkperiph CLOCK_MCHP_MCLKPERIPH_ID_AHB_GMAC>, + <&mclkperiph CLOCK_MCHP_MCLKPERIPH_ID_APBC_GMAC>; + clock-names = "mclk-ahb", "mclk-apb"; + }; + }; +}; diff --git a/dts/arm/microchip/sam/sam_d5x_e5x/common/same54.dtsi b/dts/arm/microchip/sam/sam_d5x_e5x/common/same54.dtsi new file mode 100644 index 0000000000000..86be18110f24c --- /dev/null +++ b/dts/arm/microchip/sam/sam_d5x_e5x/common/same54.dtsi @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + soc { + /* todo: Define CAN node */ + gmac: ethernet@42000800 { + compatible = "microchip,gmac-g1-eth"; + reg = <0x42000800 0x400>; + interrupts = <84 0>; + interrupt-names = "gmac"; + num-queues = <1>; + local-mac-address = [00 00 00 00 00 00]; + status = "disabled"; + clocks = <&mclkperiph CLOCK_MCHP_MCLKPERIPH_ID_AHB_GMAC>, + <&mclkperiph CLOCK_MCHP_MCLKPERIPH_ID_APBC_GMAC>; + clock-names = "mclk-ahb", "mclk-apb"; + }; + mdio: mdio@42000800 { + compatible = "microchip,gmac-g1-mdio"; + reg = <0x42000800 0x400>; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&mclkperiph CLOCK_MCHP_MCLKPERIPH_ID_AHB_GMAC>, + <&mclkperiph CLOCK_MCHP_MCLKPERIPH_ID_APBC_GMAC>; + clock-names = "mclk-ahb", "mclk-apb"; + }; + }; +}; diff --git a/dts/bindings/ethernet/microchip,gmac-g1-eth.yaml b/dts/bindings/ethernet/microchip,gmac-g1-eth.yaml new file mode 100644 index 0000000000000..b2f1687bc14fc --- /dev/null +++ b/dts/bindings/ethernet/microchip,gmac-g1-eth.yaml @@ -0,0 +1,54 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: Microchip GMAC Ethernet binding + +compatible: "microchip,gmac-g1-eth" + +include: + - name: ethernet-controller.yaml + - name: pinctrl-device.yaml + +properties: + reg: + required: true + + phy-handle: + required: true + + num-queues: + type: int + required: true + description: | + Number of hardware TX and RX queues. + + max-frame-size: + type: int + default: 1518 + description: | + Maximum ethernet frame size. The current ethernet frame sizes + supported by hardware are 1518, 1536 and 10240 (jumbo frames). This + means that normally gmac will reject any frame above max-frame-size + value. The default value is 1518, which represents an usual + IEEE 802.3 ethernet frame: + + Ethernet Frame [ 14 MAC HEADER | 1500 MTU | 4 FCS ] = 1518 bytes + + When using value 1536 it is possible extend ethernet MAC HEADER up + to 32 bytes. The hardware have support to jumbo frames and it can be + enabled by selecting the value 10240. + + max-speed: + type: int + default: 100 + description: | + This specifies maximum speed in Mbit/s supported by the device. The + gmac driver supports 10Mbit/s and 100Mbit/s. Using 100, as default + value, enables driver to configure 10 and 100Mbit/s speeds. + + mac-eeprom: + type: phandle + description: phandle to I2C eeprom device node. + + phy-connection-type: + default: "rmii" diff --git a/dts/bindings/mdio/microchip,gmac-g1-mdio.yaml b/dts/bindings/mdio/microchip,gmac-g1-mdio.yaml new file mode 100644 index 0000000000000..2750b5425e4c2 --- /dev/null +++ b/dts/bindings/mdio/microchip,gmac-g1-mdio.yaml @@ -0,0 +1,14 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: Microchip GMAC MDIO binding + +compatible: "microchip,gmac-g1-mdio" + +include: + - name: mdio-controller.yaml + - name: pinctrl-device.yaml + +properties: + clocks: + type: phandle-array diff --git a/dts/bindings/mtd/microchip,24mac402.yaml b/dts/bindings/mtd/microchip,24mac402.yaml new file mode 100644 index 0000000000000..7fcaee1b9cf0d --- /dev/null +++ b/dts/bindings/mtd/microchip,24mac402.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: Microchip AT24 (or compatible) I2C EEPROM + +compatible: "microchip,at24mac402" + +include: [i2c-device.yaml]