diff --git a/drivers/ethernet/CMakeLists.txt b/drivers/ethernet/CMakeLists.txt index 51265b2643..988ce7705d 100644 --- a/drivers/ethernet/CMakeLists.txt +++ b/drivers/ethernet/CMakeLists.txt @@ -7,6 +7,11 @@ zephyr_sources_ifdef(CONFIG_ETH_SAM_GMAC phy_sam_gmac.c ) +zephyr_sources_ifdef(CONFIG_ETH_GECKO + eth_gecko.c + phy_gecko.c + ) + zephyr_sources_ifdef(CONFIG_ETH_STELLARIS eth_stellaris.c) zephyr_sources_ifdef(CONFIG_ETH_E1000 eth_e1000.c) zephyr_sources_ifdef(CONFIG_ETH_ENC28J60 eth_enc28j60.c) diff --git a/drivers/ethernet/Kconfig b/drivers/ethernet/Kconfig index 4dba466a36..8cd6c7b58e 100644 --- a/drivers/ethernet/Kconfig +++ b/drivers/ethernet/Kconfig @@ -31,5 +31,6 @@ source "drivers/ethernet/Kconfig.smsc911x" source "drivers/ethernet/Kconfig.native_posix" source "drivers/ethernet/Kconfig.stellaris" source "drivers/ethernet/Kconfig.liteeth" +source "drivers/ethernet/Kconfig.gecko" endmenu # "Ethernet Drivers" diff --git a/drivers/ethernet/Kconfig.gecko b/drivers/ethernet/Kconfig.gecko new file mode 100644 index 0000000000..71f096150e --- /dev/null +++ b/drivers/ethernet/Kconfig.gecko @@ -0,0 +1,96 @@ +# Copyright (c) 2019 Interay Solutions B.V. +# Copyright (c) 2019 Oane Kingma +# SPDX-License-Identifier: Apache-2.0 + +menuconfig ETH_GECKO + bool "SiLabs Gecko Ethernet driver" + help + Enable Ethernet driver for Silicon Labs Gecko chips. + +if ETH_GECKO + +config ETH_GECKO_NAME + string "Device name" + default "ETH_0" + help + Device name allows user to obtain a handle to the device object + required by all driver API functions. Device name has to be unique. + +config ETH_GECKO_IRQ_PRI + int "Interrupt priority" + default 0 + help + IRQ priority of Ethernet device + +config ETH_GECKO_RX_THREAD_STACK_SIZE + int "RX thread stack size" + default 1500 + help + RX thread stack size + +config ETH_GECKO_RX_THREAD_PRIO + int "RX thread priority" + default 2 + help + RX thread priority + +choice ETH_GECKO_MAC_SELECT + prompt "MAC address" + help + Choose how to configure MAC address. + +config ETH_GECKO_RANDOM_MAC + bool "Random MAC address" + help + Generate a random MAC address dynamically. + +config ETH_GECKO_MAC_MANUAL + bool "Manual" + help + Assign an arbitrary MAC address. + +endchoice # ETH_GECKO_MAC_SELECT + +if ETH_GECKO_MAC_MANUAL + +config ETH_GECKO_MAC0 + hex "MAC Address Byte 0" + default 0 + range 0 0xff + +config ETH_GECKO_MAC1 + hex "MAC Address Byte 1" + default 0 + range 0 0xff + +config ETH_GECKO_MAC2 + hex "MAC Address Byte 2" + default 0 + range 0 0xff + +config ETH_GECKO_MAC3 + hex "MAC Address Byte 3" + default 0 + range 0 0xff + +config ETH_GECKO_MAC4 + hex "MAC Address Byte 4" + default 0 + range 0 0xff + +config ETH_GECKO_MAC5 + hex "MAC Address Byte 5" + default 0 + range 0 0xff + +endif # ETH_GECKO_MAC_MANUAL + +config ETH_GECKO_CARRIER_CHECK_RX_IDLE_TIMEOUT_MS + int "Carrier check timeout period (ms)" + default 500 + range 100 30000 + help + Set the RX idle timeout period in milliseconds after which the + PHY's carrier status is re-evaluated. + +endif # ETH_GECKO diff --git a/drivers/ethernet/eth_gecko.c b/drivers/ethernet/eth_gecko.c new file mode 100644 index 0000000000..b9b4a4f5e1 --- /dev/null +++ b/drivers/ethernet/eth_gecko.c @@ -0,0 +1,676 @@ +/* + * Copyright (c) 2019 Interay Solutions B.V. + * Copyright (c) 2019 Oane Kingma + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Silicon Labs EFM32 Giant Gecko 11 Ethernet driver. + * Limitations: + * - no link monitoring through PHY interrupt + */ + +#include +LOG_MODULE_REGISTER(eth_gecko, CONFIG_ETHERNET_LOG_LEVEL); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phy_gecko.h" +#include "eth_gecko_priv.h" + + +static u8_t dma_tx_buffer[ETH_TX_BUF_COUNT][ETH_TX_BUF_SIZE] +__aligned(ETH_BUF_ALIGNMENT); +static u8_t dma_rx_buffer[ETH_RX_BUF_COUNT][ETH_RX_BUF_SIZE] +__aligned(ETH_BUF_ALIGNMENT); +static struct eth_buf_desc dma_tx_desc_tab[ETH_TX_BUF_COUNT] +__aligned(ETH_DESC_ALIGNMENT); +static struct eth_buf_desc dma_rx_desc_tab[ETH_RX_BUF_COUNT] +__aligned(ETH_DESC_ALIGNMENT); +static u32_t tx_buf_idx; +static u32_t rx_buf_idx; + + +static void link_configure(ETH_TypeDef *eth, u32_t flags) +{ + u32_t val; + + __ASSERT_NO_MSG(eth != NULL); + + /* Disable receiver & transmitter */ + eth->NETWORKCTRL &= ~(ETH_NETWORKCTRL_ENBTX | ETH_NETWORKCTRL_ENBRX); + + /* Set duplex mode and speed */ + val = eth->NETWORKCFG; + val &= ~(_ETH_NETWORKCFG_FULLDUPLEX_MASK | _ETH_NETWORKCFG_SPEED_MASK); + val |= flags & + (_ETH_NETWORKCFG_FULLDUPLEX_MASK | _ETH_NETWORKCFG_SPEED_MASK); + eth->NETWORKCFG = val; + + /* Enable transmitter and receiver */ + eth->NETWORKCTRL |= (ETH_NETWORKCTRL_ENBTX | ETH_NETWORKCTRL_ENBRX); +} + +static void eth_init_tx_buf_desc(void) +{ + u32_t address; + int i; + + /* Initialize TX buffer descriptors */ + for (i = 0; i < ETH_TX_BUF_COUNT; i++) { + address = (u32_t) dma_tx_buffer[i]; + dma_tx_desc_tab[i].address = address; + dma_tx_desc_tab[i].status = ETH_TX_USED; + } + + /* Mark last descriptor entry with wrap flag */ + dma_tx_desc_tab[i - 1].status |= ETH_TX_WRAP; + tx_buf_idx = 0; +} + +static void eth_init_rx_buf_desc(void) +{ + u32_t address; + int i; + + for (i = 0; i < ETH_RX_BUF_COUNT; i++) { + address = (u32_t) dma_rx_buffer[i]; + dma_rx_desc_tab[i].address = address & ETH_RX_ADDRESS; + dma_rx_desc_tab[i].status = 0; + } + + /* Mark last descriptor entry with wrap flag */ + dma_rx_desc_tab[i - 1].address |= ETH_RX_WRAP; + rx_buf_idx = 0; +} + +static void rx_error_handler(ETH_TypeDef *eth) +{ + __ASSERT_NO_MSG(eth != NULL); + + /* Stop reception */ + ETH_RX_DISABLE(eth); + + /* Reset RX buffer descriptor list */ + eth_init_rx_buf_desc(); + eth->RXQPTR = (u32_t)dma_rx_desc_tab; + + /* Restart reception */ + ETH_RX_ENABLE(eth); +} + +static struct net_pkt *frame_get(struct device *dev) +{ + struct eth_gecko_dev_data *const dev_data = DEV_DATA(dev); + const struct eth_gecko_dev_cfg *const cfg = DEV_CFG(dev); + ETH_TypeDef *eth = cfg->regs; + struct net_pkt *rx_frame = NULL; + u16_t frag_len, total_len; + u32_t sofIdx, eofIdx; + u32_t i, j; + + __ASSERT_NO_MSG(dev != NULL); + __ASSERT_NO_MSG(dev_data != NULL); + __ASSERT_NO_MSG(cfg != NULL); + + /* Preset indeces and total frame length */ + sofIdx = UINT32_MAX; + eofIdx = UINT32_MAX; + total_len = 0; + + /* Check if a full frame is received (SOF/EOF present) + * and determine total length of frame + */ + for (i = 0; i < ETH_RX_BUF_COUNT; i++) { + j = (i + rx_buf_idx); + if (j >= ETH_RX_BUF_COUNT) { + j -= ETH_RX_BUF_COUNT; + } + + /* Verify it is an ETH owned buffer */ + if (!(dma_rx_desc_tab[j].address & ETH_RX_OWNERSHIP)) { + /* No more ETH owned buffers to process */ + break; + } + + /* Check for SOF */ + if (dma_rx_desc_tab[j].status & ETH_RX_SOF) { + sofIdx = j; + } + + if (sofIdx != UINT32_MAX) { + total_len += (dma_rx_desc_tab[j].status & + ETH_RX_LENGTH); + + /* Check for EOF */ + if (dma_rx_desc_tab[j].status & ETH_RX_EOF) { + eofIdx = j; + break; + } + } + } + + LOG_DBG("sof/eof: %u/%u, rx_buf_idx: %u, len: %u", sofIdx, eofIdx, + rx_buf_idx, total_len); + + /* Verify we found a full frame */ + if (eofIdx != UINT32_MAX) { + /* Allocate room for full frame */ + rx_frame = net_pkt_rx_alloc_with_buffer(dev_data->iface, + total_len, AF_UNSPEC, 0, K_NO_WAIT); + if (!rx_frame) { + LOG_ERR("Failed to obtain RX buffer"); + ETH_RX_DISABLE(eth); + eth_init_rx_buf_desc(); + eth->RXQPTR = (u32_t)dma_rx_desc_tab; + ETH_RX_ENABLE(eth); + return rx_frame; + } + + /* Copy frame (fragments)*/ + j = sofIdx; + while (total_len) { + frag_len = MIN(total_len, ETH_RX_BUF_SIZE); + LOG_DBG("frag: %u, fraglen: %u, rx_buf_idx: %u", j, + frag_len, rx_buf_idx); + if (net_pkt_write(rx_frame, &dma_rx_buffer[j], + frag_len) < 0) { + LOG_ERR("Failed to append RX buffer"); + dma_rx_desc_tab[j].address &= + ~ETH_RX_OWNERSHIP; + net_pkt_unref(rx_frame); + rx_frame = NULL; + break; + } + + dma_rx_desc_tab[j].address &= ~ETH_RX_OWNERSHIP; + + total_len -= frag_len; + if (++j >= ETH_RX_BUF_COUNT) { + j -= ETH_RX_BUF_COUNT; + } + + if (++rx_buf_idx >= ETH_RX_BUF_COUNT) { + rx_buf_idx -= ETH_RX_BUF_COUNT; + } + } + } + + return rx_frame; +} + +static void eth_rx(struct device *dev) +{ + struct eth_gecko_dev_data *const dev_data = DEV_DATA(dev); + struct net_pkt *rx_frame; + int res = 0; + + __ASSERT_NO_MSG(dev != NULL); + __ASSERT_NO_MSG(dev_data != NULL); + + /* Iterate across (possibly multiple) frames */ + rx_frame = frame_get(dev); + while (rx_frame) { + /* All data for this frame received */ + res = net_recv_data(dev_data->iface, rx_frame); + if (res < 0) { + LOG_ERR("Failed to enqueue frame into RX queue: %d", + res); + eth_stats_update_errors_rx(dev_data->iface); + net_pkt_unref(rx_frame); + } + + /* Check if more frames are received */ + rx_frame = frame_get(dev); + } +} + +static int eth_tx(struct device *dev, struct net_pkt *pkt) +{ + struct eth_gecko_dev_data *const dev_data = DEV_DATA(dev); + const struct eth_gecko_dev_cfg *const cfg = DEV_CFG(dev); + ETH_TypeDef *eth = cfg->regs; + u16_t total_len; + u8_t *dma_buffer; + int res = 0; + + __ASSERT_NO_MSG(dev != NULL); + __ASSERT_NO_MSG(dev_data != NULL); + __ASSERT_NO_MSG(cfg != NULL); + + __ASSERT(pkt, "Buf pointer is NULL"); + __ASSERT(pkt->frags, "Frame data missing"); + + /* Determine length of frame */ + total_len = net_pkt_get_len(pkt); + if (total_len > ETH_TX_BUF_SIZE) { + LOG_ERR("PKT to big"); + res = -EIO; + goto error; + } + + if (k_sem_take(&dev_data->tx_sem, K_MSEC(100)) != 0) { + LOG_ERR("TX process did not complete within 100ms"); + res = -EIO; + goto error; + } + + /* Make sure current buffer is available for writing */ + if (!(dma_tx_desc_tab[tx_buf_idx].status & ETH_TX_USED)) { + LOG_ERR("Buffer already in use"); + res = -EIO; + goto error; + } + + dma_buffer = (u8_t *)dma_tx_desc_tab[tx_buf_idx].address; + if (net_pkt_read(pkt, dma_buffer, total_len)) { + LOG_ERR("Failed to read packet into buffer"); + res = -EIO; + goto error; + } + + if (tx_buf_idx < (ETH_TX_BUF_COUNT - 1)) { + dma_tx_desc_tab[tx_buf_idx].status = + (total_len & ETH_TX_LENGTH) | ETH_TX_LAST; + tx_buf_idx++; + } else { + dma_tx_desc_tab[tx_buf_idx].status = + (total_len & ETH_TX_LENGTH) | (ETH_TX_LAST | + ETH_TX_WRAP); + tx_buf_idx = 0; + } + + /* Kick off transmission */ + eth->NETWORKCTRL |= ETH_NETWORKCTRL_TXSTRT; + +error: + return res; +} + +static void rx_thread(void *arg1, void *unused1, void *unused2) +{ + struct device *dev = (struct device *)arg1; + struct eth_gecko_dev_data *const dev_data = DEV_DATA(dev); + const struct eth_gecko_dev_cfg *const cfg = DEV_CFG(dev); + int res; + + __ASSERT_NO_MSG(arg1 != NULL); + ARG_UNUSED(unused1); + ARG_UNUSED(unused2); + __ASSERT_NO_MSG(dev_data != NULL); + __ASSERT_NO_MSG(cfg != NULL); + + while (1) { + res = k_sem_take(&dev_data->rx_sem, K_MSEC( + CONFIG_ETH_GECKO_CARRIER_CHECK_RX_IDLE_TIMEOUT_MS)); + if (res == 0) { + if (dev_data->link_up != true) { + dev_data->link_up = true; + net_eth_carrier_on(dev_data->iface); + } + + /* Process received data */ + eth_rx(dev); + } else if (res == -EAGAIN) { + if (phy_gecko_is_linked(&cfg->phy)) { + if (dev_data->link_up != true) { + dev_data->link_up = true; + net_eth_carrier_on(dev_data->iface); + } + } else { + if (dev_data->link_up != false) { + dev_data->link_up = false; + net_eth_carrier_off(dev_data->iface); + } + } + } + } +} + +static void eth_isr(void *arg) +{ + struct device *dev = (struct device *)arg; + struct eth_gecko_dev_data *const dev_data = DEV_DATA(dev); + const struct eth_gecko_dev_cfg *const cfg = DEV_CFG(dev); + ETH_TypeDef *eth = cfg->regs; + u32_t int_clr = 0; + u32_t int_stat = eth->IFCR; + u32_t tx_irq_mask = (ETH_IENS_TXCMPLT | ETH_IENS_TXUNDERRUN | + ETH_IENS_RTRYLMTORLATECOL | + ETH_IENS_TXUSEDBITREAD | + ETH_IENS_AMBAERR); + u32_t rx_irq_mask = (ETH_IENS_RXCMPLT | ETH_IENS_RXUSEDBITREAD); + + __ASSERT_NO_MSG(arg != NULL); + __ASSERT_NO_MSG(dev_data != NULL); + __ASSERT_NO_MSG(cfg != NULL); + + /* Receive handling */ + if (int_stat & rx_irq_mask) { + if (int_stat & ETH_IENS_RXCMPLT) { + /* Receive complete */ + k_sem_give(&dev_data->rx_sem); + } else { + /* Receive error */ + LOG_DBG("RX Error"); + rx_error_handler(eth); + } + + int_clr |= rx_irq_mask; + } + + /* Transmit handling */ + if (int_stat & tx_irq_mask) { + if (int_stat & ETH_IENS_TXCMPLT) { + /* Transmit complete */ + } else { + /* Transmit error: no actual handling, the current + * buffer is no longer used and we release the + * semaphore which signals the user thread to + * start TX of a new packet + */ + } + + int_clr |= tx_irq_mask; + + /* Signal TX thread we're ready to start transmission */ + k_sem_give(&dev_data->tx_sem); + } + + /* Clear interrupts */ + eth->IFCR = int_clr; +} + +static void eth_init_clocks(struct device *dev) +{ + __ASSERT_NO_MSG(dev != NULL); + + CMU_ClockEnable(cmuClock_HFPER, true); + CMU_ClockEnable(cmuClock_ETH, true); +} + +static void eth_init_pins(struct device *dev) +{ + const struct eth_gecko_dev_cfg *const cfg = DEV_CFG(dev); + ETH_TypeDef *eth = cfg->regs; + u32_t idx; + + __ASSERT_NO_MSG(dev != NULL); + __ASSERT_NO_MSG(cfg != NULL); + + eth->ROUTELOC1 = 0; + eth->ROUTEPEN = 0; + +#if defined(DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII) + for (idx = 0; idx < ARRAY_SIZE(cfg->pin_list->rmii); idx++) + soc_gpio_configure(&cfg->pin_list->rmii[idx]); + + eth->ROUTELOC1 |= (DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII << + _ETH_ROUTELOC1_RMIILOC_SHIFT); + eth->ROUTEPEN |= ETH_ROUTEPEN_RMIIPEN; +#endif + +#if defined(DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_MDIO) + for (idx = 0; idx < ARRAY_SIZE(cfg->pin_list->mdio); idx++) + soc_gpio_configure(&cfg->pin_list->mdio[idx]); + + eth->ROUTELOC1 |= (DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_MDIO << + _ETH_ROUTELOC1_MDIOLOC_SHIFT); + eth->ROUTEPEN |= ETH_ROUTEPEN_MDIOPEN; +#endif + +} + +static int eth_init(struct device *dev) +{ + const struct eth_gecko_dev_cfg *const cfg = DEV_CFG(dev); + ETH_TypeDef *eth = cfg->regs; + + __ASSERT_NO_MSG(dev != NULL); + __ASSERT_NO_MSG(cfg != NULL); + + /* Enable clocks */ + eth_init_clocks(dev); + + /* Connect pins to peripheral */ + eth_init_pins(dev); + +#if defined(DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII) + /* Enable global clock and RMII operation */ + eth->CTRL = ETH_CTRL_GBLCLKEN | ETH_CTRL_MIISEL_RMII; +#endif + + /* Connect and enable IRQ */ + cfg->config_func(); + + LOG_INF("Device %s initialized", DEV_NAME(dev)); + + return 0; +} + +#if defined(CONFIG_ETH_GECKO_RANDOM_MAC) +static void generate_random_mac(u8_t mac_addr[6]) +{ + u32_t entropy; + + entropy = sys_rand32_get(); + + /* SiLabs' OUI */ + mac_addr[0] = SILABS_OUI_B0; + mac_addr[1] = SILABS_OUI_B1; + mac_addr[2] = SILABS_OUI_B2; + + mac_addr[3] = entropy >> 0; + mac_addr[4] = entropy >> 8; + mac_addr[5] = entropy >> 16; + + /* Set MAC address locally administered, unicast (LAA) */ + mac_addr[0] |= 0x02; +} +#endif + +static void generate_mac(u8_t mac_addr[6]) +{ +#if defined(CONFIG_ETH_GECKO_RANDOM_MAC) + generate_random_mac(mac_addr); +#endif +} + +static void eth_iface_init(struct net_if *iface) +{ + struct device *const dev = net_if_get_device(iface); + struct eth_gecko_dev_data *const dev_data = DEV_DATA(dev); + const struct eth_gecko_dev_cfg *const cfg = DEV_CFG(dev); + ETH_TypeDef *eth = cfg->regs; + u32_t link_status; + int result; + + __ASSERT_NO_MSG(iface != NULL); + __ASSERT_NO_MSG(dev != NULL); + __ASSERT_NO_MSG(dev_data != NULL); + __ASSERT_NO_MSG(cfg != NULL); + + LOG_DBG("eth_initialize"); + + dev_data->iface = iface; + dev_data->link_up = false; + ethernet_init(iface); + + net_if_flag_set(iface, NET_IF_NO_AUTO_START); + + /* Generate MAC address, possibly used for filtering */ + generate_mac(dev_data->mac_addr); + + /* Set link address */ + LOG_DBG("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]); + + net_if_set_link_addr(iface, dev_data->mac_addr, + sizeof(dev_data->mac_addr), NET_LINK_ETHERNET); + + /* Disable transmit and receive circuits */ + eth->NETWORKCTRL = 0; + eth->NETWORKCFG = 0; + + /* Filtering MAC addresses */ + eth->SPECADDR1BOTTOM = + (dev_data->mac_addr[0] << 0) | + (dev_data->mac_addr[1] << 8) | + (dev_data->mac_addr[2] << 16) | + (dev_data->mac_addr[3] << 24); + eth->SPECADDR1TOP = + (dev_data->mac_addr[4] << 0) | + (dev_data->mac_addr[5] << 8); + + eth->SPECADDR2BOTTOM = 0; + eth->SPECADDR3BOTTOM = 0; + eth->SPECADDR4BOTTOM = 0; + + /* Initialise hash table */ + eth->HASHBOTTOM = 0; + eth->HASHTOP = 0; + + /* Initialise DMA buffers */ + eth_init_tx_buf_desc(); + eth_init_rx_buf_desc(); + + /* Point to locations of TX/RX DMA descriptor lists */ + eth->TXQPTR = (u32_t)dma_tx_desc_tab; + eth->RXQPTR = (u32_t)dma_rx_desc_tab; + + /* DMA RX size configuration */ + eth->DMACFG = (eth->DMACFG & ~_ETH_DMACFG_RXBUFSIZE_MASK) | + ((ETH_RX_BUF_SIZE / 64) << _ETH_DMACFG_RXBUFSIZE_SHIFT); + + /* Clear status/interrupt registers */ + eth->IFCR |= _ETH_IFCR_MASK; + eth->TXSTATUS = ETH_TXSTATUS_TXUNDERRUN | ETH_TXSTATUS_TXCMPLT | + ETH_TXSTATUS_AMBAERR | ETH_TXSTATUS_TXGO | + ETH_TXSTATUS_RETRYLMTEXCD | ETH_TXSTATUS_COLOCCRD | + ETH_TXSTATUS_USEDBITREAD; + eth->RXSTATUS = ETH_RXSTATUS_RESPNOTOK | ETH_RXSTATUS_RXOVERRUN | + ETH_RXSTATUS_FRMRX | ETH_RXSTATUS_BUFFNOTAVAIL; + + /* Enable interrupts */ + eth->IENS = ETH_IENS_RXCMPLT | + ETH_IENS_RXUSEDBITREAD | + ETH_IENS_TXCMPLT | + ETH_IENS_TXUNDERRUN | + ETH_IENS_RTRYLMTORLATECOL | + ETH_IENS_TXUSEDBITREAD | + ETH_IENS_AMBAERR; + + /* Additional DMA configuration */ + eth->DMACFG |= _ETH_DMACFG_AMBABRSTLEN_MASK | + ETH_DMACFG_FRCDISCARDONERR | + ETH_DMACFG_TXPBUFTCPEN; + eth->DMACFG &= ~ETH_DMACFG_HDRDATASPLITEN; + + /* Set network configuration */ + eth->NETWORKCFG |= ETH_NETWORKCFG_FCSREMOVE | + ETH_NETWORKCFG_UNICASTHASHEN | + ETH_NETWORKCFG_MULTICASTHASHEN | + ETH_NETWORKCFG_RX1536BYTEFRAMES | + ETH_NETWORKCFG_RXCHKSUMOFFLOADEN; + + /* Setup PHY management port */ + eth->NETWORKCFG |= (4 << _ETH_NETWORKCFG_MDCCLKDIV_SHIFT) & + _ETH_NETWORKCFG_MDCCLKDIV_MASK; + eth->NETWORKCTRL |= ETH_NETWORKCTRL_MANPORTEN; + + /* Initialise PHY */ + result = phy_gecko_init(&cfg->phy); + if (result < 0) { + LOG_ERR("ETH PHY Initialization Error"); + return; + } + + /* PHY auto-negotiate link parameters */ + result = phy_gecko_auto_negotiate(&cfg->phy, &link_status); + if (result < 0) { + LOG_ERR("ETH PHY auto-negotiate sequence failed"); + return; + } + + /* Initialise TX/RX semaphores */ + k_sem_init(&dev_data->tx_sem, 1, ETH_TX_BUF_COUNT); + k_sem_init(&dev_data->rx_sem, 0, UINT_MAX); + + /* Start interruption-poll thread */ + k_thread_create(&dev_data->rx_thread, dev_data->rx_thread_stack, + K_THREAD_STACK_SIZEOF(dev_data->rx_thread_stack), + rx_thread, (void *) dev, NULL, NULL, + K_PRIO_COOP(CONFIG_ETH_GECKO_RX_THREAD_PRIO), + 0, K_NO_WAIT); + + /* Set up link parameters and enable receiver/transmitter */ + link_configure(eth, link_status); +} + +static enum ethernet_hw_caps eth_gecko_get_capabilities(struct device *dev) +{ + ARG_UNUSED(dev); + + return ETHERNET_LINK_10BASE_T | ETHERNET_LINK_100BASE_T; +} + +static const struct ethernet_api eth_api = { + .iface_api.init = eth_iface_init, + .get_capabilities = eth_gecko_get_capabilities, + .send = eth_tx, +}; + +static struct device DEVICE_NAME_GET(eth_gecko); + +static void eth0_irq_config(void) +{ + IRQ_CONNECT(DT_INST_0_SILABS_GECKO_ETHERNET_IRQ_0, + DT_INST_0_SILABS_GECKO_ETHERNET_IRQ_0_PRIORITY, eth_isr, + DEVICE_GET(eth_gecko), 0); + irq_enable(DT_INST_0_SILABS_GECKO_ETHERNET_IRQ_0); +} + +static const struct eth_gecko_pin_list pins_eth0 = { + .mdio = PIN_LIST_PHY, + .rmii = PIN_LIST_RMII +}; + +static const struct eth_gecko_dev_cfg eth0_config = { + .regs = (ETH_TypeDef *) + DT_INST_0_SILABS_GECKO_ETHERNET_BASE_ADDRESS, + .pin_list = &pins_eth0, + .pin_list_size = ARRAY_SIZE(pins_eth0.mdio) + + ARRAY_SIZE(pins_eth0.rmii), + .config_func = eth0_irq_config, + .phy = { (ETH_TypeDef *) + DT_INST_0_SILABS_GECKO_ETHERNET_BASE_ADDRESS, + DT_INST_0_SILABS_GECKO_ETHERNET_PHY_ADDRESS }, +}; + +static struct eth_gecko_dev_data eth0_data = { +#ifdef CONFIG_ETH_GECKO_MAC_MANUAL + .mac_addr = { + CONFIG_ETH_GECKO_MAC0, + CONFIG_ETH_GECKO_MAC1, + CONFIG_ETH_GECKO_MAC2, + CONFIG_ETH_GECKO_MAC3, + CONFIG_ETH_GECKO_MAC4, + CONFIG_ETH_GECKO_MAC5, + }, +#endif +}; + +ETH_NET_DEVICE_INIT(eth_gecko, CONFIG_ETH_GECKO_NAME, eth_init, + ð0_data, ð0_config, CONFIG_ETH_INIT_PRIORITY, + ð_api, ETH_GECKO_MTU); diff --git a/drivers/ethernet/eth_gecko_priv.h b/drivers/ethernet/eth_gecko_priv.h new file mode 100644 index 0000000000..011dd1f1f4 --- /dev/null +++ b/drivers/ethernet/eth_gecko_priv.h @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2019 Interay Solutions B.V. + * Copyright (c) 2019 Oane Kingma + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_ETHERNET_ETH_GECKO_PRIV_H_ +#define ZEPHYR_DRIVERS_ETHERNET_ETH_GECKO_PRIV_H_ + +#include +#include + +#define ETH_GECKO_MTU NET_ETH_MTU + +#define SILABS_OUI_B0 0x00 +#define SILABS_OUI_B1 0x0B +#define SILABS_OUI_B2 0x57 + +#define ETH_TX_BUF_SIZE 1536 +#define ETH_TX_BUF_COUNT 2 +#define ETH_RX_BUF_SIZE 128 +#define ETH_RX_BUF_COUNT 32 + +#define ETH_BUF_ALIGNMENT 16 + +#define ETH_DESC_ALIGNMENT 4 + +#define ETH_TX_USED BIT(31) +#define ETH_TX_WRAP BIT(30) +#define ETH_TX_ERROR BIT(29) +#define ETH_TX_UNDERRUN BIT(28) +#define ETH_TX_EXHAUSTED BIT(27) +#define ETH_TX_NO_CRC BIT(16) +#define ETH_TX_LAST BIT(15) +#define ETH_TX_LENGTH (2048-1) + +#define ETH_RX_ADDRESS ~(ETH_DESC_ALIGNMENT-1) +#define ETH_RX_WRAP BIT(1) +#define ETH_RX_OWNERSHIP BIT(0) +#define ETH_RX_BROADCAST BIT(31) +#define ETH_RX_MULTICAST_HASH BIT(30) +#define ETH_RX_UNICAST_HASH BIT(29) +#define ETH_RX_EXT_ADDR BIT(28) +#define ETH_RX_SAR1 BIT(26) +#define ETH_RX_SAR2 BIT(25) +#define ETH_RX_SAR3 BIT(24) +#define ETH_RX_SAR4 BIT(23) +#define ETH_RX_TYPE_ID BIT(22) +#define ETH_RX_VLAN_TAG BIT(21) +#define ETH_RX_PRIORITY_TAG BIT(20) +#define ETH_RX_VLAN_PRIORITY (0x7UL<<17) +#define ETH_RX_CFI BIT(16) +#define ETH_RX_EOF BIT(15) +#define ETH_RX_SOF BIT(14) +#define ETH_RX_OFFSET (0x3UL<<12) +#define ETH_RX_LENGTH (4096-1) + +#define ETH_RX_ENABLE(base) (base->NETWORKCTRL |= ETH_NETWORKCTRL_ENBRX) +#define ETH_RX_DISABLE(base) (base->NETWORKCTRL &= ~ETH_NETWORKCTRL_ENBRX) + +#define ETH_TX_ENABLE(base) (base->NETWORKCTRL |= ETH_NETWORKCTRL_ENBTX) +#define ETH_TX_DISABLE(base) (base->NETWORKCTRL &= ~ETH_NETWORKCTRL_ENBTX) + +struct eth_buf_desc { + u32_t address; + u32_t status; +}; + +struct eth_gecko_pin_list { + struct soc_gpio_pin mdio[2]; + struct soc_gpio_pin rmii[7]; +}; + +/* Device constant configuration parameters */ +struct eth_gecko_dev_cfg { + ETH_TypeDef *regs; + const struct eth_gecko_pin_list *pin_list; + u32_t pin_list_size; + void (*config_func)(void); + struct phy_gecko_dev phy; +}; + +/* Device run time data */ +struct eth_gecko_dev_data { + struct net_if *iface; + u8_t mac_addr[6]; + struct k_sem tx_sem; + struct k_sem rx_sem; + + K_THREAD_STACK_MEMBER(rx_thread_stack, + CONFIG_ETH_GECKO_RX_THREAD_STACK_SIZE); + struct k_thread rx_thread; + bool link_up; +}; + +#define DEV_NAME(dev) ((dev)->config->name) +#define DEV_CFG(dev) \ + ((struct eth_gecko_dev_cfg *)(dev)->config->config_info) +#define DEV_DATA(dev) \ + ((struct eth_gecko_dev_data *)(dev)->driver_data) + +/* PHY Management pins */ +#define PIN_PHY_MDC {DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_PHY_MDC_1, \ + DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_PHY_MDC_2, gpioModePushPull,\ + 0} +#define PIN_PHY_MDIO {DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_PHY_MDIO_1, \ + DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_PHY_MDIO_2, gpioModePushPull,\ + 0} + +#define PIN_LIST_PHY {PIN_PHY_MDC, PIN_PHY_MDIO} + +/* RMII pins excluding reference clock, handled by board.c */ +#define PIN_RMII_CRSDV {DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_CRS_DV_1,\ + DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_CRS_DV_2, gpioModeInput, 0} + +#define PIN_RMII_TXD0 {DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_TXD0_1,\ + DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_TXD0_2, gpioModePushPull, 0} + +#define PIN_RMII_TXD1 {DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_TXD1_1,\ + DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_TXD1_2, gpioModePushPull, 0} + +#define PIN_RMII_TX_EN {DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_TX_EN_1,\ + DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_TX_EN_2, gpioModePushPull, 0} + +#define PIN_RMII_RXD0 {DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_RXD0_1,\ + DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_RXD0_2, gpioModeInput, 0} + +#define PIN_RMII_RXD1 {DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_RXD1_1,\ + DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_RXD1_2, gpioModeInput, 0} + +#define PIN_RMII_RX_ER {DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_RX_ER_1,\ + DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_RX_ER_2, gpioModeInput, 0} + +#define PIN_LIST_RMII {PIN_RMII_CRSDV, PIN_RMII_TXD0, PIN_RMII_TXD1, \ + PIN_RMII_TX_EN, PIN_RMII_RXD0, PIN_RMII_RXD1, PIN_RMII_RX_ER} + +/* RMII reference clock is not included in RMII pin set + * #define PIN_RMII_REFCLK {DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_REFCLK_1,\ + * DT_INST_0_SILABS_GECKO_ETHERNET_LOCATION_RMII_REFCLK_2, gpioModePushPull, 0} + */ + +#endif /* ZEPHYR_DRIVERS_ETHERNET_ETH_GECKO_PRIV_H_ */ diff --git a/drivers/ethernet/phy_gecko.c b/drivers/ethernet/phy_gecko.c new file mode 100644 index 0000000000..b9fd4b00b8 --- /dev/null +++ b/drivers/ethernet/phy_gecko.c @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2019 Interay Solutions B.V. + * Copyright (c) 2019 Oane Kingma + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* SiLabs Giant Gecko GG11 Ethernet PHY driver. */ + +#include +#include +#include +#include "phy_gecko.h" + +#include +LOG_MODULE_REGISTER(eth_gecko_phy, CONFIG_ETHERNET_LOG_LEVEL); + +/* Maximum time to establish a link through auto-negotiation for + * 10BASE-T, 100BASE-TX is 3.7s, to add an extra margin the timeout + * is set at 4s. + */ +#define PHY_AUTONEG_TIMEOUT_MS 4000 + +/* Enable MDIO serial bus between MAC and PHY. */ +static void mdio_bus_enable(ETH_TypeDef *eth) +{ + eth->NETWORKCTRL |= ETH_NETWORKCTRL_MANPORTEN; +} + +/* Enable MDIO serial bus between MAC and PHY. */ +static void mdio_bus_disable(ETH_TypeDef *eth) +{ + eth->NETWORKCTRL &= ~ETH_NETWORKCTRL_MANPORTEN; +} + +/* Wait PHY operation complete. */ +static int mdio_bus_wait(ETH_TypeDef *eth) +{ + u32_t retries = 100U; /* will wait up to 1 s */ + + while (!(eth->NETWORKSTATUS & ETH_NETWORKSTATUS_MANDONE)) { + if (retries-- == 0U) { + LOG_ERR("timeout"); + return -ETIMEDOUT; + } + + k_sleep(10); + } + + return 0; +} + +/* Send command to PHY over MDIO serial bus */ +static int mdio_bus_send(ETH_TypeDef *eth, u8_t phy_addr, u8_t reg_addr, + u8_t rw, u16_t data) +{ + int retval; + + /* Write PHY management register */ + eth->PHYMNGMNT = ETH_PHYMNGMNT_WRITE0_DEFAULT + | ETH_PHYMNGMNT_WRITE1 + | ((rw ? 0x02 : 0x01) << _ETH_PHYMNGMNT_OPERATION_SHIFT) + | ((phy_addr << _ETH_PHYMNGMNT_PHYADDR_SHIFT) + & _ETH_PHYMNGMNT_PHYADDR_MASK) + | ((reg_addr << _ETH_PHYMNGMNT_REGADDR_SHIFT) + & _ETH_PHYMNGMNT_REGADDR_MASK) + | (0x2 << _ETH_PHYMNGMNT_WRITE10_SHIFT) + | (data & _ETH_PHYMNGMNT_PHYRWDATA_MASK); + + /* Wait until PHY is ready */ + retval = mdio_bus_wait(eth); + if (retval < 0) { + return retval; + } + + return 0; +} + +/* Read PHY register. */ +static int phy_read(const struct phy_gecko_dev *phy, u8_t reg_addr, + u32_t *value) +{ + ETH_TypeDef *const eth = phy->regs; + u8_t phy_addr = phy->address; + int retval; + + retval = mdio_bus_send(eth, phy_addr, reg_addr, 1, 0); + if (retval < 0) { + return retval; + } + + /* Read data */ + *value = eth->PHYMNGMNT & _ETH_PHYMNGMNT_PHYRWDATA_MASK; + + return 0; +} + +/* Write PHY register. */ +static int phy_write(const struct phy_gecko_dev *phy, u8_t reg_addr, + u32_t value) +{ + ETH_TypeDef *const eth = phy->regs; + u8_t phy_addr = phy->address; + + return mdio_bus_send(eth, phy_addr, reg_addr, 0, value); +} + +/* Issue a PHY soft reset. */ +static int phy_soft_reset(const struct phy_gecko_dev *phy) +{ + u32_t phy_reg; + u32_t retries = 12U; + int retval; + + /* Issue a soft reset */ + retval = phy_write(phy, MII_BMCR, MII_BMCR_RESET); + if (retval < 0) { + return retval; + } + + /* Wait up to 0.6s for the reset sequence to finish. According to + * IEEE 802.3, Section 2, Subsection 22.2.4.1.1 a PHY reset may take + * up to 0.5 s. + */ + do { + if (retries-- == 0U) { + return -ETIMEDOUT; + } + + k_sleep(50); + + retval = phy_read(phy, MII_BMCR, &phy_reg); + if (retval < 0) { + return retval; + } + } while (phy_reg & MII_BMCR_RESET); + + return 0; +} + +int phy_gecko_init(const struct phy_gecko_dev *phy) +{ + ETH_TypeDef *const eth = phy->regs; + int phy_id; + + mdio_bus_enable(eth); + + LOG_INF("Soft Reset of ETH PHY"); + phy_soft_reset(phy); + + /* Verify that the PHY device is responding */ + phy_id = phy_gecko_id_get(phy); + if (phy_id == 0xFFFFFFFF) { + LOG_ERR("Unable to detect a valid PHY"); + return -1; + } + + LOG_INF("PHYID: 0x%X at addr: %d", phy_id, phy->address); + + mdio_bus_disable(eth); + + return 0; +} + +u32_t phy_gecko_id_get(const struct phy_gecko_dev *phy) +{ + ETH_TypeDef *const eth = phy->regs; + u32_t phy_reg; + u32_t phy_id; + + mdio_bus_enable(eth); + + if (phy_read(phy, MII_PHYID1R, &phy_reg) < 0) { + return 0xFFFFFFFF; + } + + phy_id = (phy_reg & 0xFFFF) << 16; + + if (phy_read(phy, MII_PHYID2R, &phy_reg) < 0) { + return 0xFFFFFFFF; + } + + phy_id |= (phy_reg & 0xFFFF); + + mdio_bus_disable(eth); + + return phy_id; +} + +int phy_gecko_auto_negotiate(const struct phy_gecko_dev *phy, + u32_t *status) +{ + ETH_TypeDef *const eth = phy->regs; + u32_t val; + u32_t ability_adv; + u32_t ability_rcvd; + u32_t retries = PHY_AUTONEG_TIMEOUT_MS / 100; + int retval; + + mdio_bus_enable(eth); + + LOG_DBG("Starting ETH PHY auto-negotiate sequence"); + + /* Read PHY default advertising parameters */ + retval = phy_read(phy, MII_ANAR, &ability_adv); + if (retval < 0) { + goto auto_negotiate_exit; + } + + /* Configure and start auto-negotiation process */ + retval = phy_read(phy, MII_BMCR, &val); + if (retval < 0) { + goto auto_negotiate_exit; + } + + val |= MII_BMCR_AUTONEG_ENABLE | MII_BMCR_AUTONEG_RESTART; + val &= ~MII_BMCR_ISOLATE; /* Don't isolate the PHY */ + + retval = phy_write(phy, MII_BMCR, val); + if (retval < 0) { + goto auto_negotiate_exit; + } + + /* Wait for the auto-negotiation process to complete */ + do { + if (retries-- == 0U) { + retval = -ETIMEDOUT; + goto auto_negotiate_exit; + } + + k_sleep(100); + + retval = phy_read(phy, MII_BMSR, &val); + if (retval < 0) { + goto auto_negotiate_exit; + } + } while (!(val & MII_BMSR_AUTONEG_COMPLETE)); + + LOG_DBG("PHY auto-negotiate sequence completed"); + + /* Read abilities of the remote device */ + retval = phy_read(phy, MII_ANLPAR, &ability_rcvd); + if (retval < 0) { + goto auto_negotiate_exit; + } + + /* Determine the best possible mode of operation */ + if ((ability_adv & ability_rcvd) & MII_ADVERTISE_100_FULL) { + *status = ETH_NETWORKCFG_FULLDUPLEX | ETH_NETWORKCFG_SPEED; + } else if ((ability_adv & ability_rcvd) & MII_ADVERTISE_100_HALF) { + *status = ETH_NETWORKCFG_SPEED; + } else if ((ability_adv & ability_rcvd) & MII_ADVERTISE_10_FULL) { + *status = ETH_NETWORKCFG_FULLDUPLEX; + } else { + *status = 0; + } + + LOG_INF("common abilities: speed %s Mb, %s duplex", + *status & ETH_NETWORKCFG_SPEED ? "100" : "10", + *status & ETH_NETWORKCFG_FULLDUPLEX ? "full" : "half"); + +auto_negotiate_exit: + mdio_bus_disable(eth); + return retval; +} + +bool phy_gecko_is_linked(const struct phy_gecko_dev *phy) +{ + ETH_TypeDef *const eth = phy->regs; + u32_t phy_reg; + bool phy_linked = false; + + mdio_bus_enable(eth); + + if (phy_read(phy, MII_BMSR, &phy_reg) < 0) { + return phy_linked; + } + + phy_linked = (phy_reg & MII_BMSR_LINK_STATUS); + + mdio_bus_disable(eth); + + return phy_linked; +} diff --git a/drivers/ethernet/phy_gecko.h b/drivers/ethernet/phy_gecko.h new file mode 100644 index 0000000000..554f0c7acb --- /dev/null +++ b/drivers/ethernet/phy_gecko.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Interay Solutions B.V. + * Copyright (c) 2019 Oane Kingma + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_ETHERNET_PHY_GECKO_H_ +#define ZEPHYR_DRIVERS_ETHERNET_PHY_GECKO_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct phy_gecko_dev { + ETH_TypeDef *regs; + u8_t address; +}; + +/** + * @brief Initialize Ethernet PHY device. + * + * @param phy PHY instance + * @return 0 on success or a negative error value on failure + */ +int phy_gecko_init(const struct phy_gecko_dev *phy); + +/** + * @brief Auto-negotiate and configure link parameters. + * + * @param phy PHY instance + * @param status link parameters common to remote and local PHY + * @return 0 on success or a negative error value on failure + */ +int phy_gecko_auto_negotiate(const struct phy_gecko_dev *phy, + u32_t *status); +/** + * @brief Get PHY ID value. + * + * @param phy PHY instance + * @return PHY ID value or 0xFFFFFFFF on failure + */ +u32_t phy_gecko_id_get(const struct phy_gecko_dev *phy); + +/** + * @brief Get PHY linked status. + * + * @param phy PHY instance + * @return PHY linked status + */ +bool phy_gecko_is_linked(const struct phy_gecko_dev *phy); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_ETHERNET_PHY_GECKO_H_ */ diff --git a/dts/bindings/ethernet/silabs,gecko-ethernet.yaml b/dts/bindings/ethernet/silabs,gecko-ethernet.yaml new file mode 100644 index 0000000000..40cb71d21a --- /dev/null +++ b/dts/bindings/ethernet/silabs,gecko-ethernet.yaml @@ -0,0 +1,101 @@ +# Copyright (c) 2019 Interay Solutions B.V. +# Copyright (c) 2019 Oane Kingma +# SPDX-License-Identifier: Apache-2.0 + +description: SiLabs Gecko Ethernet + +compatible: "silabs,gecko-ethernet" + +include: ethernet.yaml + +properties: + reg: + required: true + description: mmio register space + + interrupts: + required: true + description: required interrupts + + # PHY address + phy-address: + type: int + required: true + description: address of the PHY on the MDIO bus + + # RMII interface location + location-rmii: + type: int + required: true + description: location of RMII pins, configuration defined as + + # PHY management interface location + location-mdio: + type: int + required: true + description: location of MDC and MDIO pins, configuration defined as + + # PHY management pins + location-phy_mdc: + type: array + required: true + description: PHY MDC individual pin configuration defined as + + location-phy_mdio: + type: array + required: true + description: PHY MDIO individual pin configuration defined as + + # RMII interface pins + location-rmii_refclk: + type: array + required: true + description: Reference clock individual pin configuration defined as + + location-rmii_crs_dv: + type: array + required: true + description: Receive data valid individual pin configuration defined as + + location-rmii_txd0: + type: array + required: true + description: Transmit data 0 individual pin configuration defined as + + location-rmii_txd1: + type: array + required: true + description: Transmit data 1 individual pin configuration defined as + + location-rmii_tx_en: + type: array + required: true + description: Transmit enable individual pin configuration defined as + + location-rmii_rxd0: + type: array + required: true + description: Receive data 0 individual pin configuration defined as + + location-rmii_rxd1: + type: array + required: true + description: Receive data 1 individual pin configuration defined as + + location-rmii_rx_er: + type: array + required: true + description: Receive error individual pin configuration defined as + + # PHY control pins + location-phy_pwr_enable: + type: array + description: PHY power enable individual pin configuration defined as + + location-phy_reset: + type: array + description: PHY reset individual pin configuration defined as + + location-phy_interrupt: + type: array + description: PHY interrupt individual pin configuration defined as