From c4f131e6ee3f04c94845515f23d07257ae2216e9 Mon Sep 17 00:00:00 2001 From: Vladimir Chistyakov Date: Mon, 1 Mar 2021 10:39:23 +0700 Subject: [PATCH] esp_eth: Add a KSZ8851SNL SPI Ethernet driver Implement the PHY and MAC layers in the driver similar to the W5500 driver. Update Kconfig, CMakeLists.txt, and component.mk to incorporate the changes. Resolves: #6542 Merges https://github.com/espressif/esp-idf/pull/6636 Closes https://github.com/espressif/esp-idf/issues/6542 --- components/esp_eth/CMakeLists.txt | 5 + components/esp_eth/Kconfig | 7 + components/esp_eth/component.mk | 4 + components/esp_eth/include/esp_eth_mac.h | 33 + components/esp_eth/include/esp_eth_phy.h | 13 + .../esp_eth/src/esp_eth_mac_ksz8851snl.c | 716 ++++++++++++++++++ .../esp_eth/src/esp_eth_phy_ksz8851snl.c | 255 +++++++ components/esp_eth/src/ksz8851.h | 390 ++++++++++ 8 files changed, 1423 insertions(+) create mode 100644 components/esp_eth/src/esp_eth_mac_ksz8851snl.c create mode 100644 components/esp_eth/src/esp_eth_phy_ksz8851snl.c create mode 100644 components/esp_eth/src/ksz8851.h diff --git a/components/esp_eth/CMakeLists.txt b/components/esp_eth/CMakeLists.txt index 43375d2346..2d348158de 100644 --- a/components/esp_eth/CMakeLists.txt +++ b/components/esp_eth/CMakeLists.txt @@ -37,6 +37,11 @@ if(CONFIG_ETH_ENABLED) "src/esp_eth_phy_w5500.c") endif() + if(CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL) + list(APPEND srcs "src/esp_eth_mac_ksz8851snl.c" + "src/esp_eth_phy_ksz8851snl.c") + endif() + if(CONFIG_ETH_USE_OPENETH) list(APPEND srcs "src/esp_eth_mac_openeth.c") endif() diff --git a/components/esp_eth/Kconfig b/components/esp_eth/Kconfig index cba7514fd1..78f597ab0e 100644 --- a/components/esp_eth/Kconfig +++ b/components/esp_eth/Kconfig @@ -139,6 +139,13 @@ menu "Ethernet" However the driver in ESP-IDF only enables the RAW MAC mode, making it compatible with the software TCP/IP stack. Say yes to enable W5500 driver. + + config ETH_SPI_ETHERNET_KSZ8851SNL + bool "Use KSZ8851SNL" + help + The KSZ8851SNL is a single-chip Fast Ethernet controller consisting of + a 10/100 physical layer transceiver (PHY), a MAC, and a Serial Peripheral Interface (SPI). + Select this to enable KSZ8851SNL driver. endif # ETH_USE_SPI_ETHERNET menuconfig ETH_USE_OPENETH diff --git a/components/esp_eth/component.mk b/components/esp_eth/component.mk index acfd5a33ce..4650d5f5c4 100644 --- a/components/esp_eth/component.mk +++ b/components/esp_eth/component.mk @@ -16,6 +16,10 @@ ifndef CONFIG_ETH_SPI_ETHERNET_W5500 COMPONENT_OBJEXCLUDE += src/esp_eth_mac_w5500.o src/esp_eth_phy_w5500.o endif +ifndef CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL + COMPONENT_OBJEXCLUDE += src/esp_eth_mac_ksz8851snl.o src/esp_eth_phy_ksz8851snl.o +endif + ifndef CONFIG_ETH_USE_OPENETH COMPONENT_OBJEXCLUDE += src/esp_eth_mac_openeth.o endif diff --git a/components/esp_eth/include/esp_eth_mac.h b/components/esp_eth/include/esp_eth_mac.h index 7543fdd2d2..042c369fc6 100644 --- a/components/esp_eth/include/esp_eth_mac.h +++ b/components/esp_eth/include/esp_eth_mac.h @@ -396,6 +396,39 @@ typedef struct { esp_eth_mac_t *esp_eth_mac_new_w5500(const eth_w5500_config_t *w5500_config, const eth_mac_config_t *mac_config); #endif // CONFIG_ETH_SPI_ETHERNET_W5500 +#if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL +/** + * @brief KSZ8851SNL specific configuration + * + */ +typedef struct { + void *spi_hdl; /*!< Handle of SPI device driver */ + int int_gpio_num; /*!< Interrupt GPIO number */ +} eth_ksz8851snl_config_t; + +/** + * @brief Default KSZ8851SNL specific configuration + * + */ +#define ETH_KSZ8851SNL_DEFAULT_CONFIG(spi_device) \ + { \ + .spi_hdl = spi_device, \ + .int_gpio_num = 14, \ + } + +/** +* @brief Create KSZ8851SNL Ethernet MAC instance +* +* @param ksz8851snl_config: KSZ8851SNL specific configuration +* @param mac_config: Ethernet MAC configuration +* +* @return +* - instance: create MAC instance successfully +* - NULL: create MAC instance failed because some error occurred +*/ +esp_eth_mac_t *esp_eth_mac_new_ksz8851snl(const eth_ksz8851snl_config_t *ksz8851snl_config, const eth_mac_config_t *mac_config); +#endif // CONFIG_ETH_SPI_ETHERNET_KSZ8851 + #if CONFIG_ETH_USE_OPENETH /** * @brief Create OpenCores Ethernet MAC instance diff --git a/components/esp_eth/include/esp_eth_phy.h b/components/esp_eth/include/esp_eth_phy.h index 9dbc2ef9cd..c9d32b367c 100644 --- a/components/esp_eth/include/esp_eth_phy.h +++ b/components/esp_eth/include/esp_eth_phy.h @@ -300,6 +300,19 @@ esp_eth_phy_t *esp_eth_phy_new_dm9051(const eth_phy_config_t *config); */ esp_eth_phy_t *esp_eth_phy_new_w5500(const eth_phy_config_t *config); #endif + +#if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL +/** +* @brief Create a PHY instance of KSZ8851SNL +* +* @param[in] config: configuration of PHY +* +* @return +* - instance: create PHY instance successfully +* - NULL: create PHY instance failed because some error occurred +*/ +esp_eth_phy_t *esp_eth_phy_new_ksz8851snl(const eth_phy_config_t *config); +#endif #ifdef __cplusplus } #endif diff --git a/components/esp_eth/src/esp_eth_mac_ksz8851snl.c b/components/esp_eth/src/esp_eth_mac_ksz8851snl.c new file mode 100644 index 0000000000..6f8b588946 --- /dev/null +++ b/components/esp_eth/src/esp_eth_mac_ksz8851snl.c @@ -0,0 +1,716 @@ +// Copyright (c) 2021 Vladimir Chistyakov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#include "ksz8851.h" + +#include "driver/gpio.h" +#include "esp_log.h" +#include "esp_rom_gpio.h" + +#include + +static const char *TAG = "ksz8851snl-mac"; + +#define MAC_CHECK(a, str, goto_tag, ret_value, ...) \ + do { \ + if (!(a)) { \ + ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + ret = ret_value; \ + goto goto_tag; \ + } \ + } while (0) + +typedef enum { + KSZ8851_SPI_COMMAND_READ_REG = 0x0U, + KSZ8851_SPI_COMMAND_WRITE_REG = 0x1U, + KSZ8851_SPI_COMMAND_READ_FIFO = 0x2U, + KSZ8851_SPI_COMMAND_WRITE_FIFO = 0x3U, +} ksz8851_spi_commands_t; + +static const unsigned KSZ8851_SPI_COMMAND_BITS = 2U; +static const unsigned KSZ8851_SPI_ADDR_SHIFT = 2U; +static const unsigned KSZ8851_SPI_BYTE_MASK_SHIFT = 8U + KSZ8851_SPI_ADDR_SHIFT; +static const unsigned KSZ8851_SPI_LOCK_TIMEOUT_MS = 500U; + +typedef enum { + KSZ8851_QMU_PACKET_LENGTH = 2000U, + KSZ8851_QMU_PACKET_PADDING = 16U, +} ksz8851_qmu_packet_size_t; + +static const uint16_t RXDTTR_VALUE = 0x03E8U; +static const uint16_t RXDBCTR_VALUE = 0x1000U; +static const uint16_t RXFCT_VALUE = 0X0001U; + +IRAM_ATTR static void ksz8851_isr_handler(void *arg) +{ + emac_ksz8851snl_t *emac = (emac_ksz8851snl_t *)arg; + BaseType_t high_task_wakeup = pdFALSE; + vTaskNotifyGiveFromISR(emac->rx_task_hdl, &high_task_wakeup); + if (high_task_wakeup != pdFALSE) { + portYIELD_FROM_ISR(); + } +} + +static inline bool ksz8851_mutex_lock(emac_ksz8851snl_t *emac) +{ + return xSemaphoreTakeRecursive(emac->spi_lock, pdMS_TO_TICKS(KSZ8851_SPI_LOCK_TIMEOUT_MS)) == pdTRUE; +} + +static inline bool ksz8851_mutex_unlock(emac_ksz8851snl_t *emac) +{ + return xSemaphoreGiveRecursive(emac->spi_lock) == pdTRUE; +} + +static esp_err_t ksz8851_read_reg(emac_ksz8851snl_t *emac, uint32_t address, uint16_t *value) +{ + esp_err_t ret = ESP_OK; + MAC_CHECK(value != NULL, "out pointer must not be null", err, ESP_ERR_INVALID_ARG); + MAC_CHECK((address & ~KSZ8851_VALID_ADDRESS_MASK) == 0U, "address is out of bounds", err, ESP_ERR_INVALID_ARG); + + const unsigned data_size = 16U; // NOTE(v.chistyakov): bits + // NOTE(v.chistyakov): select upper or lower word inside a dword + const unsigned byte_mask = 0x3U << (KSZ8851_SPI_BYTE_MASK_SHIFT + (address & 0x2U)); + address <<= KSZ8851_SPI_ADDR_SHIFT; + + spi_transaction_ext_t trans = { + .base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY | SPI_TRANS_USE_RXDATA, + .base.cmd = KSZ8851_SPI_COMMAND_READ_REG, + .base.addr = address | byte_mask, + .base.length = data_size, + .command_bits = KSZ8851_SPI_COMMAND_BITS, + .address_bits = 16 - KSZ8851_SPI_COMMAND_BITS, + }; + if (ksz8851_mutex_lock(emac)) { + if (spi_device_polling_transmit(emac->spi_hdl, &trans.base) != ESP_OK) { + ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__); + ret = ESP_FAIL; + } + ksz8851_mutex_unlock(emac); + memcpy(value, trans.base.rx_data, data_size >> 3U); + ESP_LOGV(TAG, "reading reg 0x%02x == 0x%04x", address, *value); + } else { + ret = ESP_ERR_TIMEOUT; + } +err: + return ret; +} + +static esp_err_t ksz8851_write_reg(emac_ksz8851snl_t *emac, uint32_t address, uint16_t value) +{ + esp_err_t ret = ESP_OK; + MAC_CHECK((address & ~KSZ8851_VALID_ADDRESS_MASK) == 0U, "address is out of bounds", err, ESP_ERR_INVALID_ARG); + ESP_LOGV(TAG, "writing reg 0x%02x = 0x%04x", address, value); + + const unsigned data_size = 16U; // NOTE(v.chistyakov): bits + // NOTE(v.chistyakov): select upper or lower word inside a dword + const unsigned byte_mask = 0x3U << (KSZ8851_SPI_BYTE_MASK_SHIFT + (address & 0x2U)); + address <<= KSZ8851_SPI_ADDR_SHIFT; + + spi_transaction_ext_t trans = { + .base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY | SPI_TRANS_USE_TXDATA, + .base.cmd = KSZ8851_SPI_COMMAND_WRITE_REG, + .base.addr = address | byte_mask, + .base.length = data_size, + .command_bits = KSZ8851_SPI_COMMAND_BITS, + .address_bits = 16 - KSZ8851_SPI_COMMAND_BITS, + }; + + memcpy(trans.base.tx_data, &value, data_size >> 3U); + if (ksz8851_mutex_lock(emac)) { + if (spi_device_polling_transmit(emac->spi_hdl, &trans.base) != ESP_OK) { + ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__); + ret = ESP_FAIL; + } + ksz8851_mutex_unlock(emac); + } else { + ret = ESP_ERR_TIMEOUT; + } +err: + return ret; +} + +static esp_err_t ksz8851_set_bits(emac_ksz8851snl_t *emac, uint32_t address, uint16_t value) +{ + esp_err_t ret = ESP_OK; + uint16_t old; + MAC_CHECK(ksz8851_read_reg(emac, address, &old) == ESP_OK, "failed to read reg 0x%x", err, ESP_FAIL, address); + old |= value; + MAC_CHECK(ksz8851_write_reg(emac, address, old) == ESP_OK, "failed to write reg 0x%x", err, ESP_FAIL, address); +err: + return ret; +} + +static esp_err_t ksz8851_clear_bits(emac_ksz8851snl_t *emac, uint32_t address, uint16_t value) +{ + esp_err_t ret = ESP_OK; + uint16_t old; + MAC_CHECK(ksz8851_read_reg(emac, address, &old) == ESP_OK, "failed to read reg 0x%x", err, ESP_FAIL, address); + old &= ~value; + MAC_CHECK(ksz8851_write_reg(emac, address, old) == ESP_OK, "failed to write reg 0x%x", err, ESP_FAIL, address); +err: + return ret; +} + +static esp_err_t emac_ksz8851_set_mediator(esp_eth_mac_t *mac, esp_eth_mediator_t *eth) +{ + esp_err_t ret = ESP_OK; + MAC_CHECK(eth, "mediator can not be null", err, ESP_ERR_INVALID_ARG); + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + emac->eth = eth; + return ESP_OK; +err: + return ret; +} + +static esp_err_t emac_ksz8851_init(esp_eth_mac_t *mac) +{ + esp_err_t ret = ESP_OK; + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + esp_eth_mediator_t *eth = emac->eth; + esp_rom_gpio_pad_select_gpio(emac->int_gpio_num); + gpio_set_direction(emac->int_gpio_num, GPIO_MODE_INPUT); + gpio_set_pull_mode(emac->int_gpio_num, GPIO_PULLUP_ONLY); + gpio_set_intr_type(emac->int_gpio_num, GPIO_INTR_NEGEDGE); // NOTE(v.chistyakov): active low + gpio_intr_enable(emac->int_gpio_num); + gpio_isr_handler_add(emac->int_gpio_num, ksz8851_isr_handler, emac); + MAC_CHECK(eth->on_state_changed(eth, ETH_STATE_LLINIT, NULL) == ESP_OK, "lowlevel init failed", err, ESP_FAIL); + // NOTE(v.chistyakov): soft reset + { + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_PMECR, PMECR_WAKEUP_TO_NORMAL) == ESP_OK, "PMECR write failed", err, + ESP_FAIL); + + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_GRR, GRR_GLOBAL_SOFT_RESET) == ESP_OK, "GRR write failed", err, ESP_FAIL); + vTaskDelay(pdMS_TO_TICKS(emac->sw_reset_timeout_ms)); + + MAC_CHECK(ksz8851_clear_bits(emac, KSZ8851_GRR, GRR_GLOBAL_SOFT_RESET) == ESP_OK, "GRR write failed", err, + ESP_FAIL); + vTaskDelay(pdMS_TO_TICKS(emac->sw_reset_timeout_ms)); + } + // NOTE(v.chistyakov): verify chip id + { + uint16_t id; + MAC_CHECK(ksz8851_read_reg(emac, KSZ8851_CIDER, &id) == ESP_OK, "CIDER read failed", err, ESP_FAIL); + uint8_t family_id = (id & CIDER_FAMILY_ID_MASK) >> CIDER_FAMILY_ID_SHIFT; + uint8_t chip_id = (id & CIDER_CHIP_ID_MASK) >> CIDER_CHIP_ID_SHIFT; + uint8_t revision = (id & CIDER_REVISION_ID_MASK) >> CIDER_REVISION_ID_SHIFT; + ESP_LOGI(TAG, "Family ID = 0x%x\t Chip ID = 0x%x\t Revision ID = 0x%x", family_id, chip_id, revision); + MAC_CHECK(family_id == CIDER_KSZ8851SNL_FAMILY_ID, "wrong family id", err, ESP_FAIL); + MAC_CHECK(chip_id == CIDER_KSZ8851SNL_CHIP_ID, "wrong chip id", err, ESP_FAIL); + } + // NOTE(v.chistyakov): set default values + { + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_TXFDPR, TXFDPR_TXFPAI) == ESP_OK, "TXFDPR write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_TXCR, + TXCR_TXFCE | TXCR_TXPE | TXCR_TXCE | TXCR_TCGICMP | TXCR_TCGIP | TXCR_TCGTCP) == ESP_OK, + "TXCR write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_RXFDPR, RXFDPR_RXFPAI) == ESP_OK, "RXFDPR write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_RXFCTR, RXFCT_VALUE) == ESP_OK, "RXFCTR write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_RXDTTR, RXDTTR_VALUE) == ESP_OK, "RXDTTR write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_RXDBCTR, RXDBCTR_VALUE) == ESP_OK, "RXDBCTR write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_RXCR1, + RXCR1_RXUDPFCC | RXCR1_RXTCPFCC | RXCR1_RXIPFCC | RXCR1_RXPAFMA | RXCR1_RXFCE | + RXCR1_RXBE | RXCR1_RXUE) == ESP_OK, + "RXCR1 write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_RXCR2, + (4 << RXCR2_SRDBL_SHIFT) | RXCR2_IUFFP | RXCR2_RXIUFCEZ | RXCR2_UDPLFE | + RXCR2_RXICMPFCC) == ESP_OK, + "RXCR2 write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_RXQCR, RXQCR_RXIPHTOE | RXQCR_RXFCTE | RXQCR_ADRFE) == ESP_OK, + "RXQCR write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_clear_bits(emac, KSZ8851_P1CR, P1CR_FORCE_DUPLEX) == ESP_OK, "P1CR write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_P1CR, P1CR_RESTART_AN) == ESP_OK, "P1CR write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_ISR, ISR_ALL) == ESP_OK, "ISR write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_IER, + IER_LCIE | IER_TXIE | IER_RXIE | IER_LDIE | IER_SPIBEIE | IER_RXOIE) == ESP_OK, + "IER write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_TXQCR, TXQCR_AETFE) == ESP_OK, "TXQCR write failed", err, ESP_FAIL); + } + ESP_LOGD(TAG, "MAC initialized"); + return ESP_OK; +err: + ESP_LOGD(TAG, "MAC initialization failed"); + gpio_isr_handler_remove(emac->int_gpio_num); + gpio_reset_pin(emac->int_gpio_num); + eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL); + return ret; +} + +static esp_err_t emac_ksz8851_deinit(esp_eth_mac_t *mac) +{ + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + esp_eth_mediator_t *eth = emac->eth; + mac->stop(mac); + gpio_isr_handler_remove(emac->int_gpio_num); + gpio_reset_pin(emac->int_gpio_num); + eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL); + ESP_LOGD(TAG, "MAC deinitialized"); + return ESP_OK; +} + +static esp_err_t emac_ksz8851_start(esp_eth_mac_t *mac) +{ + esp_err_t ret = ESP_OK; + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_TXCR, TXCR_TXE) == ESP_OK, "TXCR write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_RXCR1, RXCR1_RXE) == ESP_OK, "RXCR1 write failed", err, ESP_FAIL); + ESP_LOGD(TAG, "MAC started"); +err: + return ret; +} + +static esp_err_t emac_ksz8851_stop(esp_eth_mac_t *mac) +{ + esp_err_t ret = ESP_OK; + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + MAC_CHECK(ksz8851_clear_bits(emac, KSZ8851_TXCR, TXCR_TXE) == ESP_OK, "TXCR write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_clear_bits(emac, KSZ8851_RXCR1, RXCR1_RXE) == ESP_OK, "RXCR1 write failed", err, ESP_FAIL); + ESP_LOGD(TAG, "MAC stopped"); +err: + return ret; +} + +static esp_err_t emac_ksz8851snl_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32_t length) +{ + static unsigned s_frame_id = 0U; + + ESP_LOGV(TAG, "transmitting frame of size %u", length); + esp_err_t ret = ESP_OK; + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + if (!ksz8851_mutex_lock(emac)) { + return ESP_ERR_TIMEOUT; + } + + MAC_CHECK(length <= KSZ8851_QMU_PACKET_LENGTH, "packet is too big", err, ESP_ERR_INVALID_ARG); + // NOTE(v.chistyakov): 4 bytes header + length aligned to 4 bytes + unsigned transmit_length = 4U + ((length + 3U) & ~0x3U); + + uint16_t free_space; + MAC_CHECK(ksz8851_read_reg(emac, KSZ8851_TXMIR, &free_space) == ESP_OK, "TXMIR read failed", err, ESP_FAIL); + MAC_CHECK(transmit_length <= free_space, "TXQ free space (%d) < send length (%d)", err, ESP_FAIL, free_space, + transmit_length); + + emac->tx_buffer[0] = ++s_frame_id & TXSR_TXFID_MASK; + emac->tx_buffer[1] = 0x80U; + emac->tx_buffer[2] = length & 0xFFU; + emac->tx_buffer[3] = (length >> 8U) & 0xFFU; + memcpy(emac->tx_buffer + 4U, buf, length); + + spi_transaction_ext_t trans = { + .base.flags = SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_CMD, + .base.cmd = KSZ8851_SPI_COMMAND_WRITE_FIFO, + .base.length = transmit_length * 8U, // NOTE(v.chistyakov): bits + .base.tx_buffer = emac->tx_buffer, + .command_bits = 2U, + .address_bits = 6U, + }; + + uint16_t ier; + MAC_CHECK(ksz8851_read_reg(emac, KSZ8851_IER, &ier) == ESP_OK, "IER read failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_write_reg(emac, KSZ8851_IER, 0) == ESP_OK, "IER write failed", err, ESP_FAIL); + + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_RXQCR, RXQCR_SDA) == ESP_OK, "RXQCR write failed", err, ESP_FAIL); + if (spi_device_polling_transmit(emac->spi_hdl, &trans.base) != ESP_OK) { + ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__); + ret = ESP_FAIL; + } + MAC_CHECK(ksz8851_clear_bits(emac, KSZ8851_RXQCR, RXQCR_SDA) == ESP_OK, "RXQCR write failed", err, ESP_FAIL); + + MAC_CHECK(ksz8851_write_reg(emac, KSZ8851_IER, ier) == ESP_OK, "IER write failed", err, ESP_FAIL); +err: + ksz8851_mutex_unlock(emac); + return ret; +} + +static esp_err_t emac_ksz8851_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length) +{ + esp_err_t ret = ESP_OK; + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + if (!ksz8851_mutex_lock(emac)) { + return ESP_ERR_TIMEOUT; + } + + MAC_CHECK(buf, "receive buffer can not be null", err, ESP_ERR_INVALID_ARG); + MAC_CHECK(length, "receive buffer length can not be null", err, ESP_ERR_INVALID_ARG); + MAC_CHECK(*length > 0U, "receive buffer length must be greater than zero", err, ESP_ERR_INVALID_ARG); + + uint16_t header_status; + MAC_CHECK(ksz8851_read_reg(emac, KSZ8851_RXFHSR, &header_status) == ESP_OK, "RXFHSR read failed", err, ESP_FAIL); + + uint16_t byte_count; + MAC_CHECK(ksz8851_read_reg(emac, KSZ8851_RXFHBCR, &byte_count) == ESP_OK, "RXFHBCR read failed", err, ESP_FAIL); + byte_count &= RXFHBCR_RXBC_MASK; + + // NOTE(v.chistyakov): do not include 2 bytes padding at the beginning and 4 bytes CRC at the end + const unsigned frame_size = byte_count - 6U; + MAC_CHECK(frame_size <= *length, "frame size is greater than length", err, ESP_FAIL); + + if (header_status & RXFHSR_RXFV) { + // NOTE(v.chistyakov): 4 dummy + 4 header + alignment + const unsigned receive_size = 8U + ((byte_count + 3U) & ~0x3U); + spi_transaction_ext_t trans = { + .base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY, + .base.cmd = KSZ8851_SPI_COMMAND_READ_FIFO, + .base.length = receive_size * 8U, // NOTE(v.chistyakov): bits + .base.rx_buffer = emac->rx_buffer, + .command_bits = 2U, + .address_bits = 6U, + }; + + MAC_CHECK(ksz8851_clear_bits(emac, KSZ8851_RXFDPR, RXFDPR_RXFP_MASK) == ESP_OK, "RXFDPR write failed", err, + ESP_FAIL); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_RXQCR, RXQCR_SDA) == ESP_OK, "RXQCR write failed", err, ESP_FAIL); + if (spi_device_polling_transmit(emac->spi_hdl, &trans.base) != ESP_OK) { + ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__); + ret = ESP_FAIL; + } + MAC_CHECK(ksz8851_clear_bits(emac, KSZ8851_RXQCR, RXQCR_SDA) == ESP_OK, "RXQCR write failed", err, ESP_FAIL); + + // NOTE(v.chistyakov): skip 4 dummy, 4 header, 2 padding + memcpy(buf, emac->rx_buffer + 10U, frame_size); + *length = frame_size; + ESP_LOGV(TAG, "received frame of size %u", frame_size); + } else if (header_status & (RXFHSR_RXCE | RXFHSR_RXRF | RXFHSR_RXFTL | RXFHSR_RXMR | RXFHSR_RXUDPFCS | RXFHSR_RXTCPFCS | + RXFHSR_RXIPFCS | RXFHSR_RXICMPFCS)) { + // NOTE(v.chistyakov): RRXEF is a self-clearing bit + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_RXQCR, RXQCR_RRXEF) == ESP_OK, "RXQCR write failed", err, ESP_FAIL); + *length = 0U; + } +err: + ksz8851_mutex_unlock(emac); + return ret; +} + +static esp_err_t emac_ksz8851_read_phy_reg(esp_eth_mac_t *mac, uint32_t phy_addr, uint32_t phy_reg, uint32_t *reg_value) +{ + esp_err_t ret = ESP_OK; + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + MAC_CHECK(reg_value, "reg_value can not be null", err, ESP_ERR_INVALID_ARG); + uint16_t tmp_val; + MAC_CHECK(ksz8851_read_reg(emac, phy_reg, &tmp_val) == ESP_OK, "read PHY register failed", err, ESP_FAIL); + *reg_value = tmp_val; +err: + return ret; +} + +static esp_err_t emac_ksz8851_write_phy_reg(esp_eth_mac_t *mac, uint32_t phy_addr, uint32_t phy_reg, uint32_t reg_value) +{ + esp_err_t ret = ESP_OK; + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + MAC_CHECK(ksz8851_write_reg(emac, phy_reg, (uint16_t)reg_value) == ESP_OK, "write PHY register failed", err, + ESP_FAIL); +err: + return ret; +} + +static esp_err_t emac_ksz8851_set_addr(esp_eth_mac_t *mac, uint8_t *addr) +{ + esp_err_t ret = ESP_OK; + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + MAC_CHECK(addr, "addr can not be null", err, ESP_ERR_INVALID_ARG); + uint16_t MARL = addr[5] | ((uint16_t)(addr[4]) << 8); + uint16_t MARM = addr[3] | ((uint16_t)(addr[2]) << 8); + uint16_t MARH = addr[1] | ((uint16_t)(addr[0]) << 8); + MAC_CHECK(ksz8851_write_reg(emac, KSZ8851_MARL, MARL) == ESP_OK, "MARL write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_write_reg(emac, KSZ8851_MARM, MARM) == ESP_OK, "MARM write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_write_reg(emac, KSZ8851_MARH, MARH) == ESP_OK, "MARH write failed", err, ESP_FAIL); + ESP_LOGD(TAG, "set MAC address to %02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], + addr[5]); +err: + return ret; +} + +static esp_err_t emac_ksz8851_get_addr(esp_eth_mac_t *mac, uint8_t *addr) +{ + esp_err_t ret = ESP_OK; + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + MAC_CHECK(addr, "addr can not be null", err, ESP_ERR_INVALID_ARG); + uint16_t MARL, MARM, MARH; + MAC_CHECK(ksz8851_read_reg(emac, KSZ8851_MARL, &MARL) == ESP_OK, "MARL read failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_read_reg(emac, KSZ8851_MARM, &MARM) == ESP_OK, "MARM read failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_read_reg(emac, KSZ8851_MARH, &MARH) == ESP_OK, "MARH read failed", err, ESP_FAIL); + addr[0] = (MARH >> 8) & 0xFF; + addr[1] = MARH & 0xFF; + addr[2] = (MARM >> 8) & 0xFF; + addr[3] = MARM & 0xFF; + addr[4] = (MARL >> 8) & 0xFF; + addr[5] = MARL & 0xFF; +err: + return ret; +} + +static esp_err_t emac_ksz8851_set_speed(esp_eth_mac_t *mac, eth_speed_t speed) +{ + esp_err_t ret = ESP_OK; + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + switch (speed) { + case ETH_SPEED_100M: + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_P1CR, P1CR_FORCE_SPEED) == ESP_OK, "P1CR write failed", err, ESP_FAIL); + ESP_LOGD(TAG, "set speed to 100M"); + break; + case ETH_SPEED_10M: + MAC_CHECK(ksz8851_clear_bits(emac, KSZ8851_P1CR, P1CR_FORCE_SPEED) == ESP_OK, "P1CR write failed", err, ESP_FAIL); + ESP_LOGD(TAG, "set speed to 10M"); + break; + default: MAC_CHECK(false, "unknown speed", err, ESP_ERR_INVALID_ARG); break; + } +err: + return ret; +} + +static esp_err_t emac_ksz8851_set_duplex(esp_eth_mac_t *mac, eth_duplex_t duplex) +{ + esp_err_t ret = ESP_OK; + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + switch (duplex) { + case ETH_DUPLEX_FULL: + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_P1CR, P1CR_FORCE_DUPLEX) == ESP_OK, "P1CR write failed", err, ESP_FAIL); + ESP_LOGD(TAG, "set duplex to full"); + break; + case ETH_DUPLEX_HALF: + MAC_CHECK(ksz8851_clear_bits(emac, KSZ8851_P1CR, P1CR_FORCE_DUPLEX) == ESP_OK, "P1CR write failed", err, ESP_FAIL); + ESP_LOGD(TAG, "set duplex to half"); + break; + default: MAC_CHECK(false, "unknown duplex", err, ESP_ERR_INVALID_ARG); break; + } +err: + return ret; +} + +static esp_err_t emac_ksz8851_set_link(esp_eth_mac_t *mac, eth_link_t link) +{ + esp_err_t ret = ESP_OK; + switch (link) { + case ETH_LINK_UP: + MAC_CHECK(mac->start(mac) == ESP_OK, "ksz8851 start failed", err, ESP_FAIL); + ESP_LOGD(TAG, "link is up"); + break; + case ETH_LINK_DOWN: + MAC_CHECK(mac->stop(mac) == ESP_OK, "ksz8851 stop failed", err, ESP_FAIL); + ESP_LOGD(TAG, "link is down"); + break; + default: MAC_CHECK(false, "unknown link status", err, ESP_ERR_INVALID_ARG); break; + } +err: + return ret; +} + +static esp_err_t emac_ksz8851_set_promiscuous(esp_eth_mac_t *mac, bool enable) +{ + esp_err_t ret = ESP_OK; + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + uint16_t rxcr1; + MAC_CHECK(ksz8851_read_reg(emac, KSZ8851_RXCR1, &rxcr1) == ESP_OK, "RXCR1 read failed", err, ESP_FAIL); + if (enable) { + // NOTE(v.chistyakov): set promiscuous mode + ESP_LOGD(TAG, "setting promiscuous mode"); + rxcr1 |= RXCR1_RXINVF | RXCR1_RXAE; + rxcr1 &= ~(RXCR1_RXPAFMA | RXCR1_RXMAFMA); + } else { + // NOTE(v.chistyakov): set hash perfect (default) + ESP_LOGD(TAG, "setting hash perfect mode"); + rxcr1 |= RXCR1_RXPAFMA; + rxcr1 &= ~(RXCR1_RXINVF | RXCR1_RXAE | RXCR1_RXMAFMA); + } + MAC_CHECK(ksz8851_write_reg(emac, KSZ8851_RXCR1, rxcr1) == ESP_OK, "RXCR1 write failed", err, ESP_FAIL); +err: + return ret; +} + +static esp_err_t emac_ksz8851_enable_flow_ctrl(esp_eth_mac_t *mac, bool enable) +{ + esp_err_t ret = ESP_OK; + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + if (enable) { + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_TXCR, TXCR_TXFCE) == ESP_OK, "TXCR write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_set_bits(emac, KSZ8851_RXCR1, RXCR1_RXFCE) == ESP_OK, "RXCR write failed", err, ESP_FAIL); + ESP_LOGD(TAG, "flow control enabled"); + } else { + MAC_CHECK(ksz8851_clear_bits(emac, KSZ8851_TXCR, TXCR_TXFCE) == ESP_OK, "TXCR write failed", err, ESP_FAIL); + MAC_CHECK(ksz8851_clear_bits(emac, KSZ8851_RXCR1, RXCR1_RXFCE) == ESP_OK, "RXCR write failed", err, ESP_FAIL); + ESP_LOGD(TAG, "flow control disabled"); + } +err: + return ret; +} + +static esp_err_t emac_ksz8851_set_peer_pause_ability(esp_eth_mac_t *mac, uint32_t ability) +{ + // NOTE(v.chistyakov): peer's pause ability is determined with auto-negotiation + return ESP_ERR_NOT_SUPPORTED; +} + +static esp_err_t emac_ksz8851_del(esp_eth_mac_t *mac) +{ + emac_ksz8851snl_t *emac = __containerof(mac, emac_ksz8851snl_t, parent); + vTaskDelete(emac->rx_task_hdl); + vSemaphoreDelete(emac->spi_lock); + heap_caps_free(emac->rx_buffer); + heap_caps_free(emac->tx_buffer); + free(emac); + return ESP_OK; +} + +static void emac_ksz8851snl_task(void *arg) +{ + emac_ksz8851snl_t *emac = (emac_ksz8851snl_t *)arg; + while (1) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + uint16_t interrupt_status; + ksz8851_read_reg(emac, KSZ8851_ISR, &interrupt_status); + ksz8851_write_reg(emac, KSZ8851_ISR, ISR_ALL); + + if (interrupt_status & ISR_LCIS) { + ESP_LOGD(TAG, "Link Change Interrupt"); + } + if (interrupt_status & ISR_TXIS) { + ESP_LOGD(TAG, "TX Interrupt"); + } + if (interrupt_status & ISR_RXOIS) { + ESP_LOGD(TAG, "RX Overrun Interrupt"); + } + if (interrupt_status & ISR_TXPSIS) { + ESP_LOGD(TAG, "TX Process Stopped Interrupt"); + } + if (interrupt_status & ISR_RXPSIS) { + ESP_LOGD(TAG, "RX Process Stopped Interrupt"); + } + if (interrupt_status & ISR_TXSAIS) { + ESP_LOGD(TAG, "TX Space Available Interrupt"); + } + if (interrupt_status & ISR_RXWFDIS) { + ESP_LOGD(TAG, "RX Wakeup Frame Detect Interrupt"); + } + if (interrupt_status & ISR_RXMPDIS) { + ESP_LOGD(TAG, "RX Magic Packet Detect Interrupt"); + } + if (interrupt_status & ISR_LDIS) { + ESP_LOGD(TAG, "Linkup Detect Interrupt"); + ksz8851_set_bits(emac, KSZ8851_PMECR, PMECR_WAKEUP_LINK); + } + if (interrupt_status & ISR_EDIS) { + ESP_LOGD(TAG, "Energy Detect Interrupt"); + } + if (interrupt_status & ISR_SPIBEIS) { + ESP_LOGD(TAG, "SPI Bus Error Interrupt"); + } + if (interrupt_status & ISR_RXIS) { + ESP_LOGD(TAG, "RX Interrupt"); + + uint16_t ier; + ksz8851_read_reg(emac, KSZ8851_IER, &ier); + ksz8851_write_reg(emac, KSZ8851_IER, 0); + + uint16_t frame_count = 0; + ksz8851_read_reg(emac, KSZ8851_RXFCTR, &frame_count); + frame_count = (frame_count & RXFCTR_RXFC_MASK) >> RXFCTR_RXFC_SHIFT; + + while (frame_count--) { + uint32_t length = ETH_MAX_PACKET_SIZE; + uint8_t *packet = malloc(ETH_MAX_PACKET_SIZE); + if (!packet) { + continue; + } + + if (emac->parent.receive(&emac->parent, packet, &length) == ESP_OK && length) { + emac->eth->stack_input(emac->eth, packet, length); + // NOTE(v.chistyakov): the packet is freed in the upper layers + } else { + free(packet); + ksz8851_clear_bits(emac, KSZ8851_RXCR1, RXCR1_RXE); + ksz8851_set_bits(emac, KSZ8851_RXCR1, RXCR1_FRXQ); + ksz8851_clear_bits(emac, KSZ8851_RXCR1, RXCR1_FRXQ); + ksz8851_set_bits(emac, KSZ8851_RXCR1, RXCR1_RXE); + } + } + ksz8851_write_reg(emac, KSZ8851_IER, ier); + } + } + vTaskDelete(NULL); +} + +esp_eth_mac_t *esp_eth_mac_new_ksz8851snl(const eth_ksz8851snl_config_t *ksz8851snl_config, + const eth_mac_config_t *mac_config) +{ + esp_eth_mac_t *ret = NULL; + emac_ksz8851snl_t *emac = NULL; + + MAC_CHECK(ksz8851snl_config && mac_config, "arguments can not be null", err, NULL); + MAC_CHECK(ksz8851snl_config->int_gpio_num >= 0, "invalid interrupt gpio number", err, NULL); + + emac = calloc(1, sizeof(emac_ksz8851snl_t)); + MAC_CHECK(emac, "no mem for MAC instance", err, NULL); + + emac->sw_reset_timeout_ms = mac_config->sw_reset_timeout_ms; + emac->int_gpio_num = ksz8851snl_config->int_gpio_num; + emac->spi_hdl = ksz8851snl_config->spi_hdl; + emac->parent.set_mediator = emac_ksz8851_set_mediator; + emac->parent.init = emac_ksz8851_init; + emac->parent.deinit = emac_ksz8851_deinit; + emac->parent.start = emac_ksz8851_start; + emac->parent.stop = emac_ksz8851_stop; + emac->parent.transmit = emac_ksz8851snl_transmit; + emac->parent.receive = emac_ksz8851_receive; + emac->parent.read_phy_reg = emac_ksz8851_read_phy_reg; + emac->parent.write_phy_reg = emac_ksz8851_write_phy_reg; + emac->parent.set_addr = emac_ksz8851_set_addr; + emac->parent.get_addr = emac_ksz8851_get_addr; + emac->parent.set_speed = emac_ksz8851_set_speed; + emac->parent.set_duplex = emac_ksz8851_set_duplex; + emac->parent.set_link = emac_ksz8851_set_link; + emac->parent.set_promiscuous = emac_ksz8851_set_promiscuous; + emac->parent.enable_flow_ctrl = emac_ksz8851_enable_flow_ctrl; + emac->parent.set_peer_pause_ability = emac_ksz8851_set_peer_pause_ability; + emac->parent.del = emac_ksz8851_del; + emac->spi_lock = xSemaphoreCreateRecursiveMutex(); + MAC_CHECK(emac->spi_lock, "create lock failed", err, NULL); + emac->rx_buffer = NULL; + emac->tx_buffer = NULL; + emac->rx_buffer = heap_caps_malloc(KSZ8851_QMU_PACKET_LENGTH + KSZ8851_QMU_PACKET_PADDING, MALLOC_CAP_DMA); + emac->tx_buffer = heap_caps_malloc(KSZ8851_QMU_PACKET_LENGTH + KSZ8851_QMU_PACKET_PADDING, MALLOC_CAP_DMA); + MAC_CHECK(emac->rx_buffer, "RX buffer allocation failed", err, NULL); + MAC_CHECK(emac->tx_buffer, "TX buffer allocation failed", err, NULL); + + BaseType_t core_num = tskNO_AFFINITY; + if (mac_config->flags & ETH_MAC_FLAG_PIN_TO_CORE) { + core_num = cpu_hal_get_core_id(); + } + BaseType_t xReturned = xTaskCreatePinnedToCore(emac_ksz8851snl_task, "ksz8851snl_tsk", mac_config->rx_task_stack_size, + emac, mac_config->rx_task_prio, &emac->rx_task_hdl, core_num); + MAC_CHECK(xReturned == pdPASS, "create ksz8851 task failed", err, NULL); + return &(emac->parent); + +err: + if (emac) { + if (emac->rx_task_hdl) { + vTaskDelete(emac->rx_task_hdl); + } + if (emac->spi_lock) { + vSemaphoreDelete(emac->spi_lock); + } + if (emac->spi_lock) { + vSemaphoreDelete(emac->spi_lock); + } + // NOTE(v.chistyakov): safe to call with NULL + heap_caps_free(emac->rx_buffer); + heap_caps_free(emac->tx_buffer); + free(emac); + } + return ret; +} diff --git a/components/esp_eth/src/esp_eth_phy_ksz8851snl.c b/components/esp_eth/src/esp_eth_phy_ksz8851snl.c new file mode 100644 index 0000000000..c8db737227 --- /dev/null +++ b/components/esp_eth/src/esp_eth_phy_ksz8851snl.c @@ -0,0 +1,255 @@ +// Copyright (c) 2021 Vladimir Chistyakov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#include "ksz8851.h" + +#include "driver/gpio.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_rom_gpio.h" + +#include +#include + +static const char *TAG = "ksz8851snl-phy"; +#define PHY_CHECK(a, str, goto_tag, ...) \ + do { \ + if (!(a)) { \ + ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + goto goto_tag; \ + } \ + } while (0) + +static esp_err_t ksz8851_update_link_duplex_speed(phy_ksz8851snl_t *ksz8851) +{ + esp_eth_mediator_t *eth = ksz8851->eth; + eth_speed_t speed = ETH_SPEED_10M; + eth_duplex_t duplex = ETH_DUPLEX_HALF; + uint32_t status; + + PHY_CHECK(eth->phy_reg_read(eth, ksz8851->addr, KSZ8851_P1SR, &status) == ESP_OK, "P1SR read failed", err); + eth_link_t link = (status & P1SR_LINK_GOOD) ? ETH_LINK_UP : ETH_LINK_DOWN; + if (ksz8851->link_status != link) { + if (link == ETH_LINK_UP) { + if (status & P1SR_OPERATION_SPEED) { + speed = ETH_SPEED_100M; + ESP_LOGD(TAG, "speed 100M"); + } else { + speed = ETH_SPEED_10M; + ESP_LOGD(TAG, "speed 10M"); + } + if (status & P1SR_OPERATION_DUPLEX) { + duplex = ETH_DUPLEX_FULL; + ESP_LOGD(TAG, "duplex full"); + } else { + duplex = ETH_DUPLEX_HALF; + ESP_LOGD(TAG, "duplex half"); + } + PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_SPEED, (void *)speed) == ESP_OK, "change speed failed", err); + PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_DUPLEX, (void *)duplex) == ESP_OK, "change duplex failed", err); + } + PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_LINK, (void *)link) == ESP_OK, "change link failed", err); + ksz8851->link_status = link; + } + return ESP_OK; +err: + return ESP_FAIL; +} + +static esp_err_t phy_ksz8851_set_mediator(esp_eth_phy_t *phy, esp_eth_mediator_t *eth) +{ + PHY_CHECK(eth, "mediator can not be null", err); + phy_ksz8851snl_t *ksz8851 = __containerof(phy, phy_ksz8851snl_t, parent); + ksz8851->eth = eth; + return ESP_OK; +err: + return ESP_ERR_INVALID_ARG; +} + +static esp_err_t phy_ksz8851_reset(esp_eth_phy_t *phy) +{ + phy_ksz8851snl_t *ksz8851 = __containerof(phy, phy_ksz8851snl_t, parent); + ksz8851->link_status = ETH_LINK_DOWN; + esp_eth_mediator_t *eth = ksz8851->eth; + ESP_LOGD(TAG, "soft reset"); + // NOTE(v.chistyakov): PHY_RESET bit is self-clearing + PHY_CHECK(eth->phy_reg_write(eth, ksz8851->addr, KSZ8851_PHYRR, PHYRR_PHY_RESET) == ESP_OK, "PHYRR write failed", + err); + vTaskDelay(pdMS_TO_TICKS(ksz8851->reset_timeout_ms)); + return ESP_OK; +err: + return ESP_FAIL; +} + +static esp_err_t phy_ksz8851_reset_hw(esp_eth_phy_t *phy) +{ + phy_ksz8851snl_t *ksz8851 = __containerof(phy, phy_ksz8851snl_t, parent); + // NOTE(v.chistyakov): set reset_gpio_num to a negative value can skip hardware reset phy chip + if (ksz8851->reset_gpio_num >= 0) { + ESP_LOGD(TAG, "hard reset"); + esp_rom_gpio_pad_select_gpio(ksz8851->reset_gpio_num); + gpio_set_direction(ksz8851->reset_gpio_num, GPIO_MODE_OUTPUT); + gpio_set_level(ksz8851->reset_gpio_num, 0); + esp_rom_delay_us(ksz8851->reset_timeout_ms * 1000); + gpio_set_level(ksz8851->reset_gpio_num, 1); + } + return ESP_OK; +} + +static esp_err_t phy_ksz8851_pwrctl(esp_eth_phy_t *phy, bool enable) +{ + phy_ksz8851snl_t *ksz8851 = __containerof(phy, phy_ksz8851snl_t, parent); + esp_eth_mediator_t *eth = ksz8851->eth; + if (enable) { + ESP_LOGD(TAG, "normal mode"); + PHY_CHECK(eth->phy_reg_write(eth, ksz8851->addr, KSZ8851_PMECR, PMECR_PME_MODE_POWER_SAVING) == ESP_OK, + "PMECR write failed", err); + } else { + ESP_LOGD(TAG, "power saving mode"); + PHY_CHECK(eth->phy_reg_write(eth, ksz8851->addr, KSZ8851_PMECR, PMECR_PME_MODE_NORMAL) == ESP_OK, + "PMECR write failed", err); + } + return ESP_OK; +err: + return ESP_FAIL; +} + +static esp_err_t phy_ksz8851_init(esp_eth_phy_t *phy) +{ + ESP_LOGD(TAG, "initializing PHY"); + PHY_CHECK(phy_ksz8851_pwrctl(phy, true) == ESP_OK, "power control failed", err); + PHY_CHECK(phy_ksz8851_reset(phy) == ESP_OK, "reset failed", err); + return ESP_OK; +err: + return ESP_FAIL; +} + +static esp_err_t phy_ksz8851_deinit(esp_eth_phy_t *phy) +{ + ESP_LOGD(TAG, "deinitializing PHY"); + PHY_CHECK(phy_ksz8851_pwrctl(phy, false) == ESP_OK, "power control failed", err); + return ESP_OK; +err: + return ESP_FAIL; +} + +static esp_err_t phy_ksz8851_negotiate(esp_eth_phy_t *phy) +{ + phy_ksz8851snl_t *ksz8851 = __containerof(phy, phy_ksz8851snl_t, parent); + esp_eth_mediator_t *eth = ksz8851->eth; + ESP_LOGD(TAG, "restart negotiation"); + + uint32_t control; + PHY_CHECK(eth->phy_reg_read(eth, ksz8851->addr, KSZ8851_P1CR, &control) == ESP_OK, "P1CR read failed", err); + PHY_CHECK(eth->phy_reg_write(eth, ksz8851->addr, KSZ8851_P1CR, control | P1CR_RESTART_AN) == ESP_OK, + "P1CR write failed", err); + + vTaskDelay(pdMS_TO_TICKS(ksz8851->autonego_timeout_ms)); + uint32_t status; + PHY_CHECK(eth->phy_reg_read(eth, ksz8851->addr, KSZ8851_P1SR, &status) == ESP_OK, "P1SR read failed", err); + PHY_CHECK(status & P1SR_AN_DONE, "auto-negotiation failed", err); + PHY_CHECK(eth->phy_reg_write(eth, ksz8851->addr, KSZ8851_P1CR, control) == ESP_OK, "P1CR write failed", err); + + PHY_CHECK(ksz8851_update_link_duplex_speed(ksz8851) == ESP_OK, "update link duplex speed failed", err); + ESP_LOGD(TAG, "negotiation succeded"); + return ESP_OK; +err: + ESP_LOGD(TAG, "negotiation failed"); + return ESP_FAIL; +} + +static esp_err_t phy_ksz8851_get_link(esp_eth_phy_t *phy) +{ + phy_ksz8851snl_t *ksz8851 = __containerof(phy, phy_ksz8851snl_t, parent); + return ksz8851_update_link_duplex_speed(ksz8851); +} + +static esp_err_t phy_ksz8851_set_addr(esp_eth_phy_t *phy, uint32_t addr) +{ + phy_ksz8851snl_t *ksz8851 = __containerof(phy, phy_ksz8851snl_t, parent); + ksz8851->addr = addr; + ESP_LOGD(TAG, "setting PHY addr to %u", addr); + return ESP_OK; +} + +static esp_err_t phy_ksz8851_get_addr(esp_eth_phy_t *phy, uint32_t *addr) +{ + PHY_CHECK(addr, "addr can not be null", err); + phy_ksz8851snl_t *ksz8851 = __containerof(phy, phy_ksz8851snl_t, parent); + *addr = ksz8851->addr; + return ESP_OK; +err: + return ESP_ERR_INVALID_ARG; +} + +static esp_err_t phy_ksz8851_advertise_pause_ability(esp_eth_phy_t *phy, uint32_t ability) +{ + phy_ksz8851snl_t *ksz8851 = __containerof(phy, phy_ksz8851snl_t, parent); + esp_eth_mediator_t *eth = ksz8851->eth; + + uint32_t anar; + PHY_CHECK(eth->phy_reg_read(eth, ksz8851->addr, KSZ8851_P1ANAR, &anar) == ESP_OK, "P1ANAR read failed", err); + if (ability) { + PHY_CHECK(eth->phy_reg_write(eth, ksz8851->addr, KSZ8851_P1ANAR, anar | P1ANAR_PAUSE) == ESP_OK, + "P1ANAR write failed", err); + ESP_LOGD(TAG, "start advertising pause ability"); + } else { + PHY_CHECK(eth->phy_reg_write(eth, ksz8851->addr, KSZ8851_P1ANAR, anar & ~P1ANAR_PAUSE) == ESP_OK, + "P1ANAR write failed", err); + ESP_LOGD(TAG, "stop advertising pause ability"); + } + return ESP_OK; +err: + return ESP_FAIL; +} + +static esp_err_t phy_ksz8851_del(esp_eth_phy_t *phy) +{ + ESP_LOGD(TAG, "deleting PHY"); + phy_ksz8851snl_t *ksz8851 = __containerof(phy, phy_ksz8851snl_t, parent); + free(ksz8851); + return ESP_OK; +} + +esp_eth_phy_t *esp_eth_phy_new_ksz8851snl(const eth_phy_config_t *config) +{ + PHY_CHECK(config, "config can not be null", err); + phy_ksz8851snl_t *ksz8851 = calloc(1, sizeof(phy_ksz8851snl_t)); + PHY_CHECK(ksz8851, "no mem for PHY instance", err); + ksz8851->addr = config->phy_addr; + ksz8851->reset_timeout_ms = config->reset_timeout_ms; + ksz8851->reset_gpio_num = config->reset_gpio_num; + ksz8851->link_status = ETH_LINK_DOWN; + ksz8851->autonego_timeout_ms = config->autonego_timeout_ms; + ksz8851->parent.set_mediator = phy_ksz8851_set_mediator; + ksz8851->parent.reset = phy_ksz8851_reset; + ksz8851->parent.reset_hw = phy_ksz8851_reset_hw; + ksz8851->parent.init = phy_ksz8851_init; + ksz8851->parent.deinit = phy_ksz8851_deinit; + ksz8851->parent.negotiate = phy_ksz8851_negotiate; + ksz8851->parent.get_link = phy_ksz8851_get_link; + ksz8851->parent.pwrctl = phy_ksz8851_pwrctl; + ksz8851->parent.set_addr = phy_ksz8851_set_addr; + ksz8851->parent.get_addr = phy_ksz8851_get_addr; + ksz8851->parent.advertise_pause_ability = phy_ksz8851_advertise_pause_ability; + ksz8851->parent.del = phy_ksz8851_del; + return &(ksz8851->parent); +err: + return NULL; +} diff --git a/components/esp_eth/src/ksz8851.h b/components/esp_eth/src/ksz8851.h new file mode 100644 index 0000000000..4cb86acd4b --- /dev/null +++ b/components/esp_eth/src/ksz8851.h @@ -0,0 +1,390 @@ +// Copyright (c) 2021 Vladimir Chistyakov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#pragma once +#include "driver/spi_master.h" +#include "esp_eth.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" + +#include + +typedef enum { + KSZ8851_CCR = 0x08, ///< Chip Configuration Register + KSZ8851_MARL = 0x10, ///< Host MAC Address Register Low + KSZ8851_MARM = 0x12, ///< Host MAC Address Register Middle + KSZ8851_MARH = 0x14, ///< Host MAC Address Register High + KSZ8851_OBCR = 0x20, ///< On-Chip Bus Control Register + KSZ8851_EEPCR = 0x22, ///< EEPROM Control Register + KSZ8851_MBIR = 0x24, ///< Memory Built-In Self-Test (BIST) Info Register + KSZ8851_GRR = 0x26, ///< Global Reset Register + KSZ8851_WFCR = 0x2A, ///< Wakeup Frame Control Register + KSZ8851_WF0CRC0 = 0x30, ///< Wakeup Frame 0 CRC0 Register (lower 16 bits) + KSZ8851_WF0CRC1 = 0x32, ///< Wakeup Frame 0 CRC1 Register (upper 16 bits) + KSZ8851_WF0BM0 = 0x34, ///< Wakeup Frame 0 Byte Mask 0 Register (0-15) + KSZ8851_WF0BM1 = 0x36, ///< Wakeup Frame 0 Byte Mask 1 Register (16-31) + KSZ8851_WF0BM2 = 0x38, ///< Wakeup Frame 0 Byte Mask 2 Register (32-47) + KSZ8851_WF0BM3 = 0x3A, ///< Wakeup Frame 0 Byte Mask 3 Register (48-63) + KSZ8851_WF1CRC0 = 0x40, ///< Wakeup Frame 1 CRC0 Register (lower 16 bits) + KSZ8851_WF1CRC1 = 0x42, ///< Wakeup Frame 1 CRC1 Register (upper 16 bits) + KSZ8851_WF1BM0 = 0x44, ///< Wakeup Frame 1 Byte Mask 0 Register (0-15) + KSZ8851_WF1BM1 = 0x46, ///< Wakeup Frame 1 Byte Mask 1 Register (16-31) + KSZ8851_WF1BM2 = 0x48, ///< Wakeup Frame 1 Byte Mask 2 Register (32-47) + KSZ8851_WF1BM3 = 0x4A, ///< Wakeup Frame 1 Byte Mask 3 Register (48-63) + KSZ8851_WF2CRC0 = 0x50, ///< Wakeup Frame 2 CRC0 Register (lower 16 bits) + KSZ8851_WF2CRC1 = 0x52, ///< Wakeup Frame 2 CRC1 Register (upper 16 bits) + KSZ8851_WF2BM0 = 0x54, ///< Wakeup Frame 2 Byte Mask 0 Register (0-15) + KSZ8851_WF2BM1 = 0x56, ///< Wakeup Frame 2 Byte Mask 1 Register (16-31) + KSZ8851_WF2BM2 = 0x58, ///< Wakeup Frame 2 Byte Mask 2 Register (32-47) + KSZ8851_WF2BM3 = 0x5A, ///< Wakeup Frame 2 Byte Mask 3 Register (48-63) + KSZ8851_WF3CRC0 = 0x60, ///< Wakeup Frame 3 CRC0 Register (lower 16 bits) + KSZ8851_WF3CRC1 = 0x62, ///< Wakeup Frame 3 CRC1 Register (upper 16 bits) + KSZ8851_WF3BM0 = 0x64, ///< Wakeup Frame 3 Byte Mask 0 Register (0-15) + KSZ8851_WF3BM1 = 0x66, ///< Wakeup Frame 3 Byte Mask 1 Register (16-31) + KSZ8851_WF3BM2 = 0x68, ///< Wakeup Frame 3 Byte Mask 2 Register (32-47) + KSZ8851_WF3BM3 = 0x6A, ///< Wakeup Frame 3 Byte Mask 3 Register (48-63) + KSZ8851_TXCR = 0x70, ///< Transmit Control Register + KSZ8851_TXSR = 0x72, ///< Transmit Status Register + KSZ8851_RXCR1 = 0x74, ///< Receive Control Register 1 + KSZ8851_RXCR2 = 0x76, ///< Receive Control Register 2 + KSZ8851_TXMIR = 0x78, ///< TXQ Memory Information Register + KSZ8851_RXFHSR = 0x7C, ///< Receive Frame Header Status Register + KSZ8851_RXFHBCR = 0x7E, ///< Receive Frame Header Byte Count Register + KSZ8851_TXQCR = 0x80, ///< TXQ Command Register + KSZ8851_RXQCR = 0x82, ///< RXQ Command Register + KSZ8851_TXFDPR = 0x84, ///< TX Frame Data Pointer Register + KSZ8851_RXFDPR = 0x86, ///< RX Frame Data Pointer Register + KSZ8851_RXDTTR = 0x8C, ///< RX Duration Timer Threshold Register + KSZ8851_RXDBCTR = 0x8E, ///< RX Data Byte Count Threshold Register + KSZ8851_IER = 0x90, ///< Interrupt Enable Register + KSZ8851_ISR = 0x92, ///< Interrupt Status Register + KSZ8851_RXFCTR = 0x9C, ///< RX Frame Count & Threshold Register + KSZ8851_TXNTFSR = 0x9E, ///< TX Next Total Frames Size Register + KSZ8851_MAHTR0 = 0xA0, ///< MAC Address Hash Table Register 0 + KSZ8851_MAHTR1 = 0xA2, ///< MAC Address Hash Table Register 1 + KSZ8851_MAHTR2 = 0xA4, ///< MAC Address Hash Table Register 2 + KSZ8851_MAHTR3 = 0xA6, ///< MAC Address Hash Table Register 3 + KSZ8851_FCLWR = 0xB0, ///< Flow Control Low Watermark Register + KSZ8851_FCHWR = 0xB2, ///< Flow Control High Watermark Register + KSZ8851_FCOWR = 0xB4, ///< Flow Control Overrun Watermark Register + KSZ8851_CIDER = 0xC0, ///< Chip ID and Enable Register + KSZ8851_CGCR = 0xC6, ///< Chip Global Control Register + KSZ8851_IACR = 0xC8, ///< Indirect Access Control Register + KSZ8851_IADLR = 0xD0, ///< Indirect Access Data Low Register + KSZ8851_IADHR = 0xD2, ///< Indirect Access Data High Register + KSZ8851_PMECR = 0xD4, ///< Power Management Event Control Register + KSZ8851_GSWUTR = 0xD6, ///< Go-Sleep & Wake-Up Time Register + KSZ8851_PHYRR = 0xD8, ///< PHY Reset Register + KSZ8851_P1MBCR = 0xE4, ///< PHY 1 MII-Register Basic Control Register + KSZ8851_P1MBSR = 0xE6, ///< PHY 1 MII-Register Basic Status Register + KSZ8851_PHY1ILR = 0xE8, ///< PHY 1 PHY ID Low Register + KSZ8851_PHY1IHR = 0xEA, ///< PHY 1 PHY ID High Register + KSZ8851_P1ANAR = 0xEC, ///< PHY 1 Auto-Negotiation Advertisement Register + KSZ8851_P1ANLPR = 0xEE, ///< PHY 1 Auto-Negotiation Link Partner Ability Register + KSZ8851_P1SCLMD = 0xF4, ///< Port 1 PHY Special Control/Status, LinkMD + KSZ8851_P1CR = 0xF6, ///< Port 1 Control Register + KSZ8851_P1SR = 0xF8, ///< Port 1 Status Register + KSZ8851_VALID_ADDRESS_MASK = 0xFE, ///< All register addresses are under this mask +} ksz8851_registers_t; + +typedef enum { + CCR_EEPROM_PRESENCE = 0x0200U, ///< RO EEPROM presence + CCR_SPI_BUS_MODE = 0x0100U, ///< RO SPI bus mode + CCR_32PIN_CHIP_PACKAGE = 0x0001U, ///< RO 32-Pin Chip Package + + OBCR_OUTPUT_PIN_DRIVE_STRENGTH = 0x0040U, ///< RW Output Pin Drive Strength: 8mA (0) or 16mA (1) + OBCR_ONCHIP_BUS_CLOCK_SELECTION = 0X0004U, ///< RW On-Chip Bus Clock Selection: 125MHz (0) + OBCR_ONCHIP_BUS_CLOCK_DIVIDE_BY_1 = 0x0000U, ///< RW On-Chip Bus Clock Divider Selection + OBCR_ONCHIP_BUS_CLCOK_DIVIDE_BY_2 = 0x0001U, ///< Rw On-Chip Bus Clock Divider Selection + OBCR_ONCHIP_BUS_CLCOK_DIVIDE_BY_3 = 0x0002U, ///< RW On-Chip Bus Clock Divider Selection + + EEPCR_EESRWA = 0x0020U, ///< RW EEPROM Software Read (0) or Write (1) Access + EEPCR_EESA = 0x0010U, ///< RW EEPROM Software Access + EEPCR_EESB = 0x0008U, ///< RO EEPROM Data receive + EEPCR_EECB2 = 0x0004U, ///< RW EEPROM Data transmit + EEPCR_EECB1 = 0x0002U, ///< RW EEPROM Serial clock + EEPCR_EECB0 = 0x0001U, ///< RW EEPROM Chip select + + MBIR_TXMBF = 0x1000U, ///< RO TX Memory BIST Test Finish + MBIR_TXMBFA = 0x0800U, ///< RO TX Memory BIST Test Fail + MBIR_TXMBFC_SHIFT = 8U, ///< RO TX Memory BIST Test Fail Count Shift + MBIR_TXMBFC_MASK = 0x7 << MBIR_TXMBFC_SHIFT, ///< RO TX Memory BIST Test Fail Count Mask + MBIR_RXMBF = 0x0010U, ///< RO RX Memory Bist Finish + MBIR_RXMBFA = 0x0008U, ///< RO RX Memory Bist Fail + MBIR_RXMBFC = 0x7U, ///< RO RX Memory BIST Test Fail Count + + GRR_QMU_MODULE_SOFT_RESET = 0x0002U, ///< RW QMU Module Soft Reset + GRR_GLOBAL_SOFT_RESET = 0x0001U, ///< Rw Global Soft Reset + + WFCR_MPRXE = 0x0080U, ///< RW Magic Packet RX Enable + WFCR_WF3E = 0x0008U, ///< RW Wake up Frame 3 Enable + WFCR_WF2E = 0x0004U, ///< RW Wake up Frame 2 Enable + WFCR_WF1E = 0x0002U, ///< RW Wake up Frame 1 Enable + WFCR_WF0E = 0x0001U, ///< RW Wake up Frame 0 Enable + + TXCR_TCGICMP = 0x0100U, ///< RW Transmit Checksum Generation for ICMP + TXCR_TCGTCP = 0x0040U, ///< RW Transmit Checksum Generation for TCP + TXCR_TCGIP = 0x0020U, ///< RW Transmit Checksum Generation for IP + TXCR_FTXQ = 0x0010U, ///< RW Flush Transmit Queue + TXCR_TXFCE = 0x0008U, ///< RW Transmit Flow Control Enable + TXCR_TXPE = 0x0004U, ///< RW Transmit Padding Enable + TXCR_TXCE = 0x0002U, ///< RW Transmit CRC Enable + TXCR_TXE = 0x0001U, ///< RW Transmit Enable + + TXSR_TXLC = 0x2000U, ///< RO Transmit Late Collision + TXSR_TXMC = 0x1000U, ///< RO Transmit Maximum Collision + TXSR_TXFID_MASK = 0x003FU, ///< RO Transmit Frame ID Mask + + RXCR1_FRXQ = 0x8000U, ///< RW Flush Receive Queue + RXCR1_RXUDPFCC = 0x4000U, ///< RW Receive UDP Frame Checksum Check Enable + RXCR1_RXTCPFCC = 0x2000U, ///< RW Receive TCP Frame Checksum Check Enable + RXCR1_RXIPFCC = 0x1000U, ///< RW Receive IP Frame Checksum Check Enable + RXCR1_RXPAFMA = 0x0800U, ///< RW Receive Physical Address Filtering with MAC Address Enable + RXCR1_RXFCE = 0x0400U, ///< RW Receive Flow Control Enable + RXCR1_RXEFE = 0x0200U, ///< RW Receive Error Frame Enable + RXCR1_RXMAFMA = 0x0100U, ///< RW Receive Multicast Address Filtering with MAC Address Enable + RXCR1_RXBE = 0x0080U, ///< RW Receive Broadcast Enable + RXCR1_RXME = 0x0040U, ///< RW Receive Multicast Enable + RXCR1_RXUE = 0x0020U, ///< RW Receive Unicast Enable + RXCR1_RXAE = 0x0010U, ///< RW Receive All Enable + RXCR1_RXINVF = 0x0002U, ///< RW Receive Inverse Filtering + RXCR1_RXE = 0x0001U, ///< RW Receive Enable + + RXCR2_SRDBL_SHIFT = 5U, ///< WO SPI Receive Data Burst Length: 4/8/16/32/frame (0-4) + RXCR2_IUFFP = 0x0010U, ///< RW IPv4/IPv6/UDP Fragment Frame Pass + RXCR2_RXIUFCEZ = 0x0008U, ///< RW Receive IPv4/IPv6/UDP Frame Checksum Equal Zero + RXCR2_UDPLFE = 0x0004U, ///< RW Lite Frame Enable + RXCR2_RXICMPFCC = 0x0002U, ///< RW Receive ICMP Frame Checksum Check Enable + RXCR2_RXSAF = 0x0001U, ///< RW Receive Source Address Filtering + + TXMIR_TXMA_MASK = 0x1FFFU, ///< RO Transmit Memory Available Mask + + RXFHSR_RXFV = 0x8000U, ///< RO Receive Frame Valid + RXFHSR_RXICMPFCS = 0x2000U, ///< RO Receive ICMP Frame Checksum Status + RXFHSR_RXIPFCS = 0x1000U, ///< RO Receive IP Frame Checksum Status + RXFHSR_RXTCPFCS = 0x0800U, ///< RO Receive TCP Frame Checksum Status + RXFHSR_RXUDPFCS = 0x0400U, ///< RO Receive UDP Frame Checksum Status + RXFHSR_RXBF = 0x0080U, ///< RO Receive Broadcast Frame + RXFHSR_RXMF = 0x0040U, ///< RO Receive Multicast Frame + RXFHSR_RXUF = 0x0020U, ///< RO Receive Unicast Frame + RXFHSR_RXMR = 0x0010U, ///< RO Receive MII Error + RXFHSR_RXFT = 0x0008U, ///< RO Receive Frame Type + RXFHSR_RXFTL = 0x0004U, ///< RO Receive Frame Too Long + RXFHSR_RXRF = 0x0002U, ///< RO Receive Runt Frame + RXFHSR_RXCE = 0x0001U, ///< RO Receive CRC Error + + RXFHBCR_RXBC_MASK = 0x0FFFU, ///< RO Receive Byte Count Mask + + TXQCR_AETFE = 0x0004U, ///< RW Auto-Enqueue TXQ Frame Enable + TXQCR_TXQMAM = 0x0002U, ///< RW TXQ Memory Available Monitor + TXQCR_METFE = 0x0001U, ///< RW (SC) Manual Enqueue TXQ Frame Enable + + RXQCR_RXDTTS = 0x1000U, ///< RO RX Duration Timer Threshold Status + RXQCR_RXDBCTS = 0x0800U, ///< RO RX Data Byte Count Threshold Status + RXQCR_RXFCTS = 0x0400U, ///< RO RX Frame Count Threshold Status + RXQCR_RXIPHTOE = 0x0200U, ///< RW RX IP Header Two-Byte Offset Enable + RXQCR_RXDTTE = 0x0080U, ///< RW RX Duration Timer Threshold Enable + RXQCR_RXDBCTE = 0x0040U, ///< RW RX Data Byte Count Threshold Enable + RXQCR_RXFCTE = 0x0020U, ///< RW RX Frame Count Threshold Enable + RXQCR_ADRFE = 0x0010U, ///< RW Auto-Dequeue RXQ Frame Enable + RXQCR_SDA = 0x0008U, ///< WO Start DMA Access + RXQCR_RRXEF = 0x0001U, ///< RW Release RX Error Frame + + TXFDPR_TXFPAI = 0x4000U, ///< RW TX Frame Data Pointer Auto Increment + TXFDPR_TXFP_MASK = 0x07FFU, ///< RO TX Frame Pointer Mask + + RXFDPR_RXFPAI = 0x4000U, ///< RW RX Frame Pointer Auto Increment + RXFDPR_RXFP_MASK = 0x07FFU, ///< WO RX Frame Pointer Mask + + IER_LCIE = 0x8000U, ///< RW Link Change Interrupt Enable + IER_TXIE = 0x4000U, ///< RW Transmit Interrupt Enable + IER_RXIE = 0x2000U, ///< RW Receive Interrupt Enable + IER_RXOIE = 0x0800U, ///< RW Receive Overrun Interrupt Enable + IER_TXPSIE = 0x0200U, ///< RW Transmit Process Stopped Interrupt Enable + IER_RXPSIE = 0x0100U, ///< RW Receive Process Stopped Interrupt Enable + IER_TXSAIE = 0x0040U, ///< RW Transmit Space Available Interrupt Enable + IER_RXWFDIE = 0x0020U, ///< RW Receive Wake-up Frame Detect Interrupt Enable + IER_RXMPDIE = 0x0010U, ///< RW Receive Magic Packet Detect Interrupt Enable + IER_LDIE = 0x0008U, ///< RW Linkup Detect Interrupt Enable + IER_EDIE = 0x0004U, ///< RW Energy Detect Interrupt Enable + IER_SPIBEIE = 0x0002U, ///< RW SPI Bus Error Interrupt Enable + IER_DEDIE = 0x0001U, ///< RW Delay Energy Detect Interrupt Enable + + ISR_LCIS = 0x8000U, ///< RO (W1C) Link Change Interrupt Status + ISR_TXIS = 0x4000U, ///< RO (W1C) Transmit Interrupt Status + ISR_RXIS = 0x2000U, ///< RO (W1C) Receive Interrupt Status + ISR_RXOIS = 0x0800U, ///< RO (W1C) Receive Overrun Interrupt Status + ISR_TXPSIS = 0x0200U, ///< RO (W1C) Transmit Process Stopped Interrupt Status + ISR_RXPSIS = 0x0100U, ///< RO (W1C) Receive Process Stopped Interrupt Status + ISR_TXSAIS = 0x0040U, ///< RO (W1C) Transmit Space Available Interrupt Status + ISR_RXWFDIS = 0x0020U, ///< RO (W1C) Receive Wakeup Frame Detect Interrupt Status + ISR_RXMPDIS = 0x0010U, ///< RO (W1C) Receive Magic Packet Detect Interrupt Status + ISR_LDIS = 0x0008U, ///< RO (W1C) Linkup Detect Interrupt Status + ISR_EDIS = 0x0004U, ///< RO (W1C) Energy Detect Interrupt Status + ISR_SPIBEIS = 0x0002U, ///< RO (W1C) SPI Bus Error Interrupt Status + ISR_ALL = 0xFFFFU, ///< WO Clear register value + + RXFCTR_RXFC_SHIFT = 8U, ///< RO RX Frame Count Shift + RXFCTR_RXFC_MASK = 0xFF << RXFCTR_RXFC_SHIFT, ///< RO RX Frame Count Mask + RXFCTR_RXFCT_MASK = 0xFFU, ///< RW Receive Frame Count Threshold + + FCLWR_MASK = 0x0FFFU, ///< RW Flow Control Low Watermark Configuration Mask + FCHWR_MASK = 0x0FFFU, ///< RW Flow Control High Watermark Configuration Mask + FCOWR_MASK = 0x0FFFU, ///< RW Flow Control Overrun Watermark Configuration Mask + + CIDER_KSZ8851SNL_FAMILY_ID = 0x88U, ///< KSZ8851SNL Family ID + CIDER_KSZ8851SNL_CHIP_ID = 0x7U, ///< KSZ8851SNL Chip ID + CIDER_FAMILY_ID_SHIFT = 8U, ///< RO Family ID Shift + CIDER_FAMILY_ID_MASK = 0xFF << CIDER_FAMILY_ID_SHIFT, ///< RO Family ID Mask + CIDER_CHIP_ID_SHIFT = 4U, ///< RO Chip ID Shift + CIDER_CHIP_ID_MASK = 0xF << CIDER_CHIP_ID_SHIFT, ///< RO Chip ID Mask + CIDER_REVISION_ID_SHIFT = 1U, ///< RO Revision ID Shift + CIDER_REVISION_ID_MASK = 0x7 << CIDER_REVISION_ID_SHIFT, ///< RO Revision ID Mask + + CGCR_LEDSEL0 = 0x0200U, ///< RW PHY LED Mode: 0 - 100BT + LINK/ACTU, 1 - ACT + LINK + + IACR_READ_ENABLE = 0x1000U, ///< RW Read Enable + IACR_MIB_COUNTER_SELECT = 0x0C00U, ///< RW Table Select + IACR_INDIRECT_ADDRESS_MASK = 0x001FU, ///< RW Indirect Address Mask + + PMECR_PME_DELAY_ENABLE = 0x4000U, ///< RW PME Delay Enable + PMECR_PME_OUTPUT_POLARITY = 0x1000U, ///< RW PME Output Polarity + PMECR_WUP_FRAME_EN = 0x0800U, ///< RW Wake-on-LAN to PME Output Enable receive wake-up frame + PMECR_MAGIC_PACKET = 0x0400U, ///< RW Wake-on-LAN to PME Output Enable receive magic packet + PMECR_LINK_CHANGE_TO_UP = 0x0200U, ///< RW Wake-on-LAN to PME Output Enable link change to up + PMECR_SIGNAL_ENERGY_DETECTED = 0x0100U, ///< RW Wake-on-LAN to PME Output Enable energy detected + PMECR_AUTO_WAKEUP_ENABLE = 0x0080U, ///< RW Auto Wake-Up Enable + PMECR_WAKEUP_TO_NORMAL = 0x0040U, ///< RW Wake-Up to Normal Operation Mode + PMECR_WAKEUP_FRAME_EVENT = 0x0020U, ///< RO (W1C) Wake-Up Event Indication wakeup frame event detected + PMECR_WAKEUP_MAGIC_PACKET = 0x0010U, ///< RO (W1C) Wake-Up Event Indication magic packet event detected + PMECR_WAKEUP_LINK = 0x0008U, ///< RO (W1C) Wake-Up Event Indication link up event detected + PMECR_WAKEUP_ENERGY = 0x0004U, ///< RO (W1C) Wake-Up Event Indication energy event detected + PMECR_PME_MODE_MASK = 0x0003U, ///< RW Power Management Mode Mask + PMECR_PME_MODE_NORMAL = 0x0000U, ///< RW Normal Operation Mode + PMECR_PME_MODE_ENERGY_DETECT = 0x0001U, ///< RW Energy Detect Mode + PMECR_PME_MODE_SOFT_POWER_DOWN = 0x0002U, ///< RW Soft Power Down Mode + PMECR_PME_MODE_POWER_SAVING = 0x0003U, ///< RW Power Saving Mode + + GSWUTR_WAKE_UP_TIME_SHIFT = 8U, ///< RW Wake-up Time Shift + GSWUTR_WAKE_UP_TIME_MASK = 0xFF << GSWUTR_WAKE_UP_TIME_SHIFT, ///< RW Wake-up Time Mask + GSWUTR_GO_SLEEP_TIME_MASK = 0x0003U, ///< RW Go-sleep Time Mask + + PHYRR_PHY_RESET = 0x0001U, ///< WO (SC) PHY Reset Bit + + P1MBCR_LOCAL_LOOPBACK = 0x4000U, ///< RW Local (far-end) loopback (llb) + P1MBCR_FORCE100 = 0x2000U, ///< RW Force 100 + P1MBCR_AN_ENABLE = 0x1000U, ///< RW AN Enable + P1MBCR_RESTART_AN = 0x0200U, ///< RW Restart AN + P1MBCR_FORCE_FULL_DUPLEX = 0x0100U, ///< RW Force Full-Duplex + P1MBCR_HP_MDIX = 0x0020U, ///< RW HP Auto MDI-X mode + P1MBCR_FORCE_MDIX = 0x0010U, ///< RWForce MDI-X + P1MBCR_DISABLE_MDIX = 0x0008U, ///< RW Disable MDI-X + P1MBCR_DISABLE_TRANSMIT = 0x0002U, ///< RW Disable Transmit + P1MBCR_DISABLE_LED = 0x0001U, ///< RW Disable LED + + P1MBSR_T4_CAPABLE = 0x8000U, ///< RO T4 Capable + P1MBSR_100_FULL_CAPABLE = 0x4000U, ///< RO 100 Full Capable + P1MBSR_100_HALF_CAPABLE = 0x2000U, ///< RO 100 Half Capable + P1MBSR_10_FULL_CAPABLE = 0x1000U, ///< RO 10 Full Capable + P1MBSR_10_HALF_CAPABLE = 0x0800U, ///< RO 10 Half Capable + P1MBSR_PREAMBLE_SUPPRESSED = 0x0040U, ///< RO Preamble suppressed (not supported) + P1MBSR_AN_COMPLETE = 0x0020U, ///< RO AN Complete + P1MBSR_AN_CAPABLE = 0x0008U, ///< RO AN Capable + P1MBSR_LINK_STATUS = 0x0004U, ///< RO Link Status + P1MBSR_JABBER_TEST = 0x0002U, ///< RO Jabber test (not supported) + P1MBSR_EXTENDED_CAPABLE = 0x0001U, ///< RO Extended Capable + + P1ANAR_NEXT_PAGE = 0x8000U, ///< RO Next page (not supported) + P1ANAR_REMOTE_FAULT = 0x2000U, ///< RO Remote fault (not supported) + P1ANAR_PAUSE = 0x0400U, ///< RW Pause (flow control capability) + P1ANAR_ADV_100_FULL = 0x0100U, ///< RW Adv 100 Full + P1ANAR_ADV_100_HALF = 0x0080U, ///< RW Adv 100 Half + P1ANAR_ADV_10_FULL = 0x0040U, ///< RW Adv 10 Full + P1ANAR_ADV_10_HALF = 0x0020U, ///< RW Adv 10 Half + + P1ANLPR_NEXT_PAGE = 0x8000U, ///< RO Next page (not supported) + P1ANLPR_LP_ACK = 0x4000U, ///< RO LP ACK (not suppported) + P1ANLPR_REMOTE_FAULT = 0x2000U, ///< RO Remote fault (not supported) + P1ANLPR_PAUSE = 0x0400U, ///< RO Pause + P1ANLPR_ADV_100_FULL = 0x0100U, ///< RO Adv 100 Full + P1ANLPR_ADV_100_HALF = 0x0080U, ///< RO Adv 100 Half + P1ANLPR_ADV_10_FULL = 0x0040U, ///< RO Adv 10 Full + P1ANLPR_ADV_10_HALF = 0x0020U, ///< RO Adv 10 Half + + P1SCLMD_VCT_RESULT_SHIFT = 13U, ///< RO VCT result Shift + P1SCLMD_VCT_RESULT_MASK = 0x3 << P1SCLMD_VCT_RESULT_SHIFT, ///< RO VCT result Mask + P1SCLMD_VCT_RESULT_NORMAL = 0x0U, ///< RO VCT result normal condition + P1SCLMD_VCT_RESULT_OPEN = 0x1U, ///< RO VCT result open cable condition + P1SCLMD_VCT_RESULT_SHORT = 0x2U, ///< RO VCT result short cable condition + P1SCLMD_VCT_RESULT_TEST_FAILED = 0x3U, ///< RO VCT result test failed + P1SCLMD_VCT_ENABLE = 0x1000U, ///< RW (SC) VCT Enable + P1SCLMD_FORCE_LINK = 0x0800U, ///< RW Force link + P1SCLMD_REMOTE_LOOPBACK = 0x0200U, ///< RW Remote (Near-end) loopback (rlb) + P1SCLMD_VCT_FAULT_COUNT_MASK = 0x1FU, ///< RO Distance to the fault * 0.4m + + P1CR_LED_OFF = 0x8000U, ///< RW Turn off all of the port 1 LEDs + P1CR_TXIDS = 0x4000U, ///< RW Disable the port’s transmitter. + P1CR_RESTART_AN = 0x2000U, ///< RW Restart AN + P1CR_DISABLE_AUTO_MDI_MDIX = 0x0400U, ///< RW Disable auto MDI/MDI-X + P1CR_FORCE_MDIX = 0x0200U, ///< RW Force MDI-X + P1CR_AUTO_NEGOTIATION_ENABLE = 0x0080U, ///< RW Auto Negotiation Enable + P1CR_FORCE_SPEED = 0x0040U, ///< RW Force Speed 100 + P1CR_FORCE_DUPLEX = 0x0020U, ///< RW Force Full Duplex + P1CR_ADVERTISED_FLOW_CONTROL_CAPABILITY = 0x0010U, ///< RW Advertise flow control capability + P1CR_ADVERTISED_100BT_FULL_DUPLEX_CAPABILITY = 0x0008U, ///< RW Advertise 100BT full-duplex capability + P1CR_ADVERTISED_100BT_HALF_DUPLEX_CAPABILITY = 0x0004U, ///< RW Advertise 100BT half-duplex capability + P1CR_ADVERTISED_10BT_FULL_DUPLEX_CAPABILITY = 0x0002U, ///< RW Advertise 10BT full-duplex capability + P1CR_ADVERTISED_10BT_HALF_DUPLEX_CAPABILITY = 0x0001U, ///< RW Advertise 10BT half-duplex capability + + P1SR_HP_MDIX = 0x8000U, ///< RW HP Auto MDI-X mode + P1SR_POLARITY_REVERSE = 0x2000U, ///< RO Polarity Reverse + P1SR_OPERATION_SPEED = 0x0400U, ///< RO Operation Speed 100 + P1SR_OPERATION_DUPLEX = 0x0200U, ///< RO Operation Duplex Full + P1SR_MDIX_STATUS = 0x0080U, ///< RO MDI status + P1SR_AN_DONE = 0x0040U, ///< RO AN Done + P1SR_LINK_GOOD = 0x0020U, ///< RO Link Good + P1SR_PARTNER_FLOW_CONTROL_CAPABILITY = 0x0010U, ///< RO Partner flow control capability + P1SR_PARTNER_100BT_FULL_DUPLEX_CAPABILITY = 0x0008U, ///< RO Partner 100BT full-duplex capability + P1SR_PARTNER_100BT_HALF_DUPLEX_CAPABILITY = 0x0004U, ///< RO Partner 100BT half-duplex capability + P1SR_PARTNER_10BT_FULL_DUPLEX_CAPABILITY = 0x0002U, ///< RO Partner 10BT full-duplex capability + P1SR_PARTNER_10BT_HALF_DUPLEX_CAPABILITY = 0x0001U, ///< RO Partner 10BT half-duplex capability +} ksz8851_register_bits_t; + +typedef struct { + esp_eth_mac_t parent; + esp_eth_mediator_t *eth; + spi_device_handle_t spi_hdl; + SemaphoreHandle_t spi_lock; + TaskHandle_t rx_task_hdl; + uint32_t sw_reset_timeout_ms; + int int_gpio_num; + uint8_t *rx_buffer; + uint8_t *tx_buffer; +} emac_ksz8851snl_t; + +typedef struct { + esp_eth_phy_t parent; + esp_eth_mediator_t *eth; + int32_t addr; + uint32_t reset_timeout_ms; + uint32_t autonego_timeout_ms; + eth_link_t link_status; + int reset_gpio_num; +} phy_ksz8851snl_t;