ESPHome 2026.9.0
Loading...
Searching...
No Matches
w5500_custom_spi.cpp
Go to the documentation of this file.
1#include "w5500_custom_spi.h"
2
3#if defined(USE_ESP32) && defined(USE_ETHERNET_W5500)
4
5#include <driver/spi_master.h>
6#include <freertos/FreeRTOS.h>
7#include <freertos/semphr.h>
8#include <cstring>
9
10namespace esphome::ethernet {
11
12namespace {
13
14// Context returned by init() and handed back to read/write/deinit. There is one W5500 per device, so a
15// single static instance replaces a heap allocation that could fail. It is always clear when init() runs:
16// esp_eth_mac_new_w5500() calls deinit() on every failure after init() succeeded, and nothing else
17// uninstalls the driver
18struct W5500CustomSpiContext {
19 spi_device_handle_t handle;
20 SemaphoreHandle_t lock;
21};
22// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - intentional mutable state
23W5500CustomSpiContext w5500_context{};
24
25// Transfers up to the ESP32 SPI hardware FIFO size (64 bytes) stay on the polling path; larger
26// transfers (the frame payloads) use the blocking, DMA-backed transmit.
27constexpr uint32_t W5500_SPI_BULK_THRESHOLD = 64;
28constexpr uint32_t W5500_SPI_LOCK_TIMEOUT_MS = 50;
29
30void *w5500_custom_spi_init(const void *spi_config) {
31 const auto *config = static_cast<const eth_w5500_config_t *>(spi_config);
32 auto *ctx = &w5500_context;
33 // The W5500 SPI frame carries the 16-bit address in the command phase and the 8-bit control
34 // byte in the address phase; mirror what the stock driver configures.
35 spi_device_interface_config_t devcfg = *config->spi_devcfg;
36 devcfg.command_bits = 16;
37 devcfg.address_bits = 8;
38 if (spi_bus_add_device(config->spi_host_id, &devcfg, &ctx->handle) != ESP_OK) {
39 ctx->handle = nullptr;
40 return nullptr;
41 }
42 ctx->lock = xSemaphoreCreateMutex();
43 if (ctx->lock == nullptr) {
44 spi_bus_remove_device(ctx->handle);
45 ctx->handle = nullptr;
46 return nullptr;
47 }
48 return ctx;
49}
50
51esp_err_t w5500_custom_spi_deinit(void *spi_ctx) {
52 auto *ctx = static_cast<W5500CustomSpiContext *>(spi_ctx);
53 spi_bus_remove_device(ctx->handle);
54 vSemaphoreDelete(ctx->lock);
55 *ctx = {};
56 return ESP_OK;
57}
58
59// Runs one transaction under the device lock, choosing the polling vs blocking transmit by size.
60// Bulk payloads (> FIFO size) block so the calling task sleeps while DMA runs; small register
61// accesses stay on the cheaper polling path. Used by both read and write.
62esp_err_t w5500_custom_spi_transfer(W5500CustomSpiContext *ctx, spi_transaction_t *trans, uint32_t len) {
63 if (xSemaphoreTake(ctx->lock, pdMS_TO_TICKS(W5500_SPI_LOCK_TIMEOUT_MS)) != pdTRUE) {
64 return ESP_ERR_TIMEOUT;
65 }
66 esp_err_t ret;
67 if (len > W5500_SPI_BULK_THRESHOLD) {
68 ret = spi_device_transmit(ctx->handle, trans);
69 } else {
70 ret = spi_device_polling_transmit(ctx->handle, trans);
71 }
72 xSemaphoreGive(ctx->lock);
73 return ret;
74}
75
76esp_err_t w5500_custom_spi_write(void *spi_ctx, uint32_t cmd, uint32_t addr, const void *data, uint32_t len) {
77 auto *ctx = static_cast<W5500CustomSpiContext *>(spi_ctx);
78 spi_transaction_t trans = {};
79 trans.cmd = static_cast<uint16_t>(cmd);
80 trans.addr = addr;
81 trans.length = 8 * len;
82 trans.tx_buffer = data;
83 return w5500_custom_spi_transfer(ctx, &trans, len);
84}
85
86esp_err_t w5500_custom_spi_read(void *spi_ctx, uint32_t cmd, uint32_t addr, void *data, uint32_t len) {
87 auto *ctx = static_cast<W5500CustomSpiContext *>(spi_ctx);
88 spi_transaction_t trans = {};
89 // Reads of <= 4 bytes use the transaction's inline RX buffer to avoid 4-byte boundary
90 // overwrites of adjacent registers (same guard the stock driver uses).
91 const bool use_rxdata = len <= 4;
92 trans.flags = use_rxdata ? SPI_TRANS_USE_RXDATA : 0;
93 trans.cmd = static_cast<uint16_t>(cmd);
94 trans.addr = addr;
95 trans.length = 8 * len;
96 trans.rx_buffer = data;
97 esp_err_t ret = w5500_custom_spi_transfer(ctx, &trans, len);
98 if (use_rxdata && (ret == ESP_OK)) {
99 memcpy(data, trans.rx_data, len);
100 }
101 return ret;
102}
103
104} // namespace
105
106void install_w5500_async_spi(eth_w5500_config_t &config) {
107 // Point the custom driver's config at the W5500 config itself; init() reads spi_host_id and
108 // spi_devcfg back out of it. The self-reference is valid because both the config and the
109 // spi_devcfg it points at outlive the esp_eth_mac_new_w5500() call that runs init().
110 config.custom_spi_driver.config = &config;
111 config.custom_spi_driver.init = w5500_custom_spi_init;
112 config.custom_spi_driver.deinit = w5500_custom_spi_deinit;
113 config.custom_spi_driver.read = w5500_custom_spi_read;
114 config.custom_spi_driver.write = w5500_custom_spi_write;
115}
116
117} // namespace esphome::ethernet
118
119#endif // USE_ESP32 && USE_ETHERNET_W5500
int ret
void install_w5500_async_spi(eth_w5500_config_t &config)
std::span< const uint8_t > data
const void size_t len
Definition hal.h:64
static void uint32_t
SemaphoreHandle_t lock
spi_device_handle_t handle