mirror of
https://github.com/esphome/esphome.git
synced 2026-09-30 16:30:22 +00:00
[ethernet] Add KSZ8851SNL SPI ethernet support (#19883)
This commit is contained in:
@@ -138,6 +138,7 @@ ETHERNET_TYPES = {
|
||||
"GENERIC": EthernetType.ETHERNET_TYPE_GENERIC,
|
||||
"YT8531": EthernetType.ETHERNET_TYPE_YT8531,
|
||||
"CH390": EthernetType.ETHERNET_TYPE_CH390,
|
||||
"KSZ8851SNL": EthernetType.ETHERNET_TYPE_KSZ8851SNL,
|
||||
}
|
||||
|
||||
# PHY types that need compile-time defines for conditional compilation
|
||||
@@ -160,6 +161,7 @@ _PHY_TYPE_TO_DEFINE = {
|
||||
"GENERIC": "USE_ETHERNET_GENERIC",
|
||||
"YT8531": "USE_ETHERNET_YT8531",
|
||||
"CH390": "USE_ETHERNET_CH390",
|
||||
"KSZ8851SNL": "USE_ETHERNET_KSZ8851SNL",
|
||||
}
|
||||
|
||||
|
||||
@@ -184,13 +186,14 @@ _IDF6_ETHERNET_COMPONENTS: dict[str, IDFRegistryComponent] = {
|
||||
"ENC28J60": IDFRegistryComponent("espressif/enc28j60", "1.0.1"),
|
||||
"LAN8670": IDFRegistryComponent("espressif/lan867x", "2.0.0"),
|
||||
"CH390": IDFRegistryComponent("espressif/ch390", "0.3.0"),
|
||||
"KSZ8851SNL": IDFRegistryComponent("espressif/ksz8851snl", "1.2.0"),
|
||||
}
|
||||
|
||||
# These types are always external IDF components (never built-in to ESP-IDF)
|
||||
_ALWAYS_EXTERNAL_IDF_COMPONENTS = {"LAN8670", "ENC28J60", "CH390"}
|
||||
|
||||
# ESP32-only SPI ethernet types (W5100 is RP2040-only, no ESP-IDF driver)
|
||||
SPI_ETHERNET_TYPES = {"W5500", "DM9051", "ENC28J60", "CH390"}
|
||||
SPI_ETHERNET_TYPES = {"W5500", "DM9051", "ENC28J60", "CH390", "KSZ8851SNL"}
|
||||
# RP2-supported ethernet types (SPI and PIO QSPI). Applies to the whole
|
||||
# RP2 family (RP2040 and RP2350); the chip-specific W5100 caveat in the
|
||||
# comment above is about ESP-IDF driver coverage, not the RP2 platform.
|
||||
@@ -534,6 +537,12 @@ SPI_SCHEMA_ENC28J60 = _spi_schema(default_clock="20MHz", max_clock=int(20e6))
|
||||
# https://www.wch-ic.com/downloads/CH390DS1_PDF.html
|
||||
SPI_SCHEMA_CH390 = _spi_schema(max_clock=int(72e6))
|
||||
|
||||
# The KSZ8851SNL rates fSCLK at 40 MHz maximum, so the shared 80 MHz ceiling is
|
||||
# out of spec while the 26.67 MHz default is not. KSZ8851SNL/SNLI datasheet
|
||||
# DS00002381C, table 7-1:
|
||||
# https://ww1.microchip.com/downloads/aemDocuments/documents/UNG/ProductDocuments/DataSheets/KSZ8851SNL-Single-Port-Ethernet-Controller-with-SPI-DS00002381C.pdf
|
||||
SPI_SCHEMA_KSZ8851SNL = _spi_schema(max_clock=int(40e6))
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.typed_schema(
|
||||
{
|
||||
@@ -549,6 +558,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
"OPENETH": cv.All(BASE_SCHEMA, cv.only_on([Platform.ESP32])),
|
||||
"DM9051": SPI_SCHEMA,
|
||||
"CH390": SPI_SCHEMA_CH390,
|
||||
"KSZ8851SNL": SPI_SCHEMA_KSZ8851SNL,
|
||||
"ENC28J60": SPI_SCHEMA_ENC28J60,
|
||||
"W6100": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2])),
|
||||
"W6300": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2])),
|
||||
|
||||
@@ -92,6 +92,7 @@ enum EthernetType : uint8_t {
|
||||
ETHERNET_TYPE_GENERIC,
|
||||
ETHERNET_TYPE_YT8531,
|
||||
ETHERNET_TYPE_CH390,
|
||||
ETHERNET_TYPE_KSZ8851SNL,
|
||||
};
|
||||
|
||||
struct ManualIP {
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
#include "esp_eth_mac_dm9051.h"
|
||||
#include "esp_eth_phy_dm9051.h"
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_KSZ8851SNL
|
||||
#include "esp_eth_mac_ksz8851snl.h"
|
||||
#include "esp_eth_phy_ksz8851snl.h"
|
||||
#endif
|
||||
#endif // ESP_IDF_VERSION >= 6.0.0
|
||||
|
||||
// LAN867x header exists on all IDF versions (external component since IDF 5.3)
|
||||
@@ -264,6 +268,8 @@ void EthernetComponent::ethernet_lazy_init_() {
|
||||
eth_enc28j60_config_t enc28j60_config = ETH_ENC28J60_DEFAULT_CONFIG(host, &devcfg);
|
||||
#elif defined(USE_ETHERNET_CH390)
|
||||
eth_ch390_config_t ch390_config = ETH_CH390_DEFAULT_CONFIG(host, &devcfg);
|
||||
#elif defined(USE_ETHERNET_KSZ8851SNL)
|
||||
eth_ksz8851snl_config_t ksz8851snl_config = ETH_KSZ8851SNL_DEFAULT_CONFIG(host, &devcfg);
|
||||
#endif
|
||||
|
||||
#if defined(USE_ETHERNET_W5500)
|
||||
@@ -290,6 +296,11 @@ void EthernetComponent::ethernet_lazy_init_() {
|
||||
#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
|
||||
ch390_config.poll_period_ms = this->polling_interval_;
|
||||
#endif
|
||||
#elif defined(USE_ETHERNET_KSZ8851SNL)
|
||||
ksz8851snl_config.int_gpio_num = this->interrupt_pin_;
|
||||
#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
|
||||
ksz8851snl_config.poll_period_ms = this->polling_interval_;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
phy_config.phy_addr = this->phy_addr_spi_;
|
||||
@@ -420,6 +431,12 @@ void EthernetComponent::ethernet_lazy_init_() {
|
||||
this->phy_ = esp_eth_phy_new_ch390(&phy_config);
|
||||
break;
|
||||
}
|
||||
#elif defined(USE_ETHERNET_KSZ8851SNL)
|
||||
case ETHERNET_TYPE_KSZ8851SNL: {
|
||||
mac = esp_eth_mac_new_ksz8851snl(&ksz8851snl_config, &mac_config);
|
||||
this->phy_ = esp_eth_phy_new_ksz8851snl(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
default: {
|
||||
@@ -593,6 +610,10 @@ void EthernetComponent::dump_config() {
|
||||
case ETHERNET_TYPE_CH390:
|
||||
eth_type = "CH390";
|
||||
break;
|
||||
#elif defined(USE_ETHERNET_KSZ8851SNL)
|
||||
case ETHERNET_TYPE_KSZ8851SNL:
|
||||
eth_type = "KSZ8851SNL";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_OPENETH
|
||||
case ETHERNET_TYPE_OPENETH:
|
||||
|
||||
@@ -473,8 +473,10 @@
|
||||
#define USE_ETHERNET_W6300
|
||||
#define USE_ETHERNET_DM9051
|
||||
#define USE_ETHERNET_CH390
|
||||
#define USE_ETHERNET_KSZ8851SNL
|
||||
#define CONFIG_ETH_SPI_ETHERNET_W5500 1
|
||||
#define CONFIG_ETH_SPI_ETHERNET_DM9051 1
|
||||
#define CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL 1
|
||||
#define CONFIG_ETH_USE_ESP32_EMAC 1
|
||||
#define USE_ETHERNET_MANUAL_IP
|
||||
#define USE_ETHERNET_IP_STATE_LISTENERS
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
ethernet:
|
||||
id: eth_component
|
||||
type: KSZ8851SNL
|
||||
clk_pin: GPIO18
|
||||
mosi_pin: GPIO23
|
||||
miso_pin: GPIO19
|
||||
cs_pin: GPIO5
|
||||
interrupt_pin: GPIO36
|
||||
reset_pin: GPIO22
|
||||
clock_speed: 20MHz
|
||||
@@ -0,0 +1,18 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
version: 6.0.0
|
||||
|
||||
ethernet:
|
||||
id: eth_component
|
||||
type: KSZ8851SNL
|
||||
clk_pin: GPIO18
|
||||
mosi_pin: GPIO23
|
||||
miso_pin: GPIO19
|
||||
cs_pin: GPIO5
|
||||
interrupt_pin: GPIO36
|
||||
reset_pin: GPIO22
|
||||
clock_speed: 20MHz
|
||||
@@ -0,0 +1,105 @@
|
||||
"""Tests for the KSZ8851SNL SPI ethernet type."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from voluptuous import Invalid
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.esp32 import (
|
||||
KEY_BOARD,
|
||||
KEY_IDF_VERSION,
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
from esphome.components.esp32.const import (
|
||||
KEY_COMPONENTS,
|
||||
KEY_ESP32,
|
||||
KEY_REF,
|
||||
KEY_SDKCONFIG_OPTIONS,
|
||||
)
|
||||
from esphome.components.ethernet import CONF_CLOCK_SPEED, CONFIG_SCHEMA
|
||||
from esphome.const import PlatformFramework
|
||||
from esphome.core import CORE
|
||||
|
||||
from ..types import SetCoreConfigCallable
|
||||
|
||||
_BASE_CONFIG = {
|
||||
"type": "KSZ8851SNL",
|
||||
"clk_pin": 47,
|
||||
"mosi_pin": 48,
|
||||
"miso_pin": 14,
|
||||
"cs_pin": 21,
|
||||
}
|
||||
|
||||
_SDKCONFIG_OPTION = "CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL"
|
||||
_IDF_COMPONENT = "espressif/ksz8851snl"
|
||||
|
||||
|
||||
def _sdkconfig() -> dict[str, object]:
|
||||
return CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]
|
||||
|
||||
|
||||
def _idf_components() -> dict[str, dict[str, str | None]]:
|
||||
return CORE.data[KEY_ESP32][KEY_COMPONENTS]
|
||||
|
||||
|
||||
def test_codegen_on_idf5_wires_the_spi_driver(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""On IDF 5.x the driver is built into esp_eth and enabled by its Kconfig option."""
|
||||
main_cpp = generate_main(component_config_path("ksz8851snl_idf5.yaml"))
|
||||
|
||||
assert "eth_component->set_type(ethernet::ETHERNET_TYPE_KSZ8851SNL);" in main_cpp
|
||||
assert "eth_component->set_cs_pin(5);" in main_cpp
|
||||
assert "eth_component->set_interrupt_pin(36);" in main_cpp
|
||||
assert "USE_ETHERNET_KSZ8851SNL" in {d.name for d in CORE.defines}
|
||||
assert _sdkconfig()[_SDKCONFIG_OPTION] is True
|
||||
# Built into IDF 5.x, so the registry component must not be pulled in.
|
||||
assert _IDF_COMPONENT not in _idf_components()
|
||||
|
||||
|
||||
def test_codegen_on_idf6_pulls_the_registry_component(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""IDF 6.0 dropped the built-in driver, so the managed component is required."""
|
||||
generate_main(component_config_path("ksz8851snl_idf6.yaml"))
|
||||
|
||||
assert _idf_components()[_IDF_COMPONENT][KEY_REF] == "1.2.0"
|
||||
# The Kconfig option no longer exists on IDF 6.0.
|
||||
assert _SDKCONFIG_OPTION not in _sdkconfig()
|
||||
|
||||
|
||||
def _set_esp32_s3(set_core_config: SetCoreConfigCallable) -> None:
|
||||
set_core_config(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
platform_data={
|
||||
KEY_BOARD: "esp32-s3-devkitc-1",
|
||||
KEY_VARIANT: VARIANT_ESP32S3,
|
||||
KEY_IDF_VERSION: cv.Version(5, 3, 2),
|
||||
},
|
||||
)
|
||||
# _validate derives use_address from the node name, which has no default here.
|
||||
CORE.name = "ksz8851snl-test"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("clock_speed", ["26.67MHz", "40MHz"])
|
||||
def test_accepts_clock_speed_up_to_the_datasheet_maximum(
|
||||
set_core_config: SetCoreConfigCallable, clock_speed: str
|
||||
) -> None:
|
||||
"""The datasheet rates fSCLK to 40MHz, so the whole range must be accepted."""
|
||||
_set_esp32_s3(set_core_config)
|
||||
config = CONFIG_SCHEMA({**_BASE_CONFIG, CONF_CLOCK_SPEED: clock_speed})
|
||||
assert config[CONF_CLOCK_SPEED] == cv.frequency(clock_speed)
|
||||
|
||||
|
||||
def test_rejects_clock_speed_above_the_datasheet_maximum(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
"""The shared 80MHz ceiling is out of spec for this part."""
|
||||
_set_esp32_s3(set_core_config)
|
||||
with pytest.raises(Invalid, match="value must be at most 40000000"):
|
||||
CONFIG_SCHEMA({**_BASE_CONFIG, CONF_CLOCK_SPEED: "80MHz"})
|
||||
@@ -0,0 +1,19 @@
|
||||
ethernet:
|
||||
type: KSZ8851SNL
|
||||
clk_pin: 19
|
||||
mosi_pin: 21
|
||||
miso_pin: 23
|
||||
cs_pin: 18
|
||||
interrupt_pin: 36
|
||||
reset_pin: 22
|
||||
clock_speed: 10Mhz
|
||||
manual_ip:
|
||||
static_ip: 192.168.178.56
|
||||
gateway: 192.168.178.1
|
||||
subnet: 255.255.255.0
|
||||
domain: .local
|
||||
mac_address: "02:AA:BB:CC:DD:01"
|
||||
on_connect:
|
||||
- logger.log: "Ethernet connected!"
|
||||
on_disconnect:
|
||||
- logger.log: "Ethernet disconnected!"
|
||||
@@ -0,0 +1,2 @@
|
||||
packages:
|
||||
ethernet: !include common-ksz8851snl.yaml
|
||||
Reference in New Issue
Block a user