1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-16 23:05:46 +00:00
Files
esphome/esphome/components/psram/__init__.py

211 lines
6.8 KiB
Python

import logging
import textwrap
import esphome.codegen as cg
from esphome.components.const import CONF_IGNORE_NOT_FOUND
from esphome.components.esp32 import (
CONF_CPU_FREQUENCY,
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES,
VARIANT_ESP32,
add_idf_sdkconfig_option,
get_esp32_variant,
)
from esphome.components.esp32.const import (
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
import esphome.config_validation as cv
from esphome.const import (
CONF_ADVANCED,
CONF_DISABLED,
CONF_FRAMEWORK,
CONF_ID,
CONF_MODE,
CONF_SPEED,
KEY_CORE,
KEY_FRAMEWORK_VERSION,
PLATFORM_ESP32,
)
from esphome.core import CORE
import esphome.final_validate as fv
CODEOWNERS = ["@esphome/core"]
DOMAIN = "psram"
DEPENDENCIES = [PLATFORM_ESP32]
# PSRAM availability tracking for cross-component coordination
KEY_PSRAM_GUARANTEED = "psram_guaranteed"
_LOGGER = logging.getLogger(__name__)
psram_ns = cg.esphome_ns.namespace(DOMAIN)
PsramComponent = psram_ns.class_("PsramComponent", cg.Component)
TYPE_QUAD = "quad"
TYPE_OCTAL = "octal"
TYPE_HEX = "hex"
SDK_MODES = {TYPE_QUAD: "QUAD", TYPE_OCTAL: "OCT", TYPE_HEX: "HEX"}
CONF_ENABLE_ECC = "enable_ecc"
SPIRAM_MODES = {
VARIANT_ESP32: (TYPE_QUAD,),
VARIANT_ESP32S2: (TYPE_QUAD,),
VARIANT_ESP32S3: (TYPE_QUAD, TYPE_OCTAL),
VARIANT_ESP32P4: (TYPE_HEX,),
}
SPIRAM_SPEEDS = {
VARIANT_ESP32: (40, 80, 120),
VARIANT_ESP32S2: (40, 80, 120),
VARIANT_ESP32S3: (40, 80, 120),
VARIANT_ESP32P4: (20, 100, 200),
}
def supported() -> bool:
if not CORE.is_esp32:
return False
variant = get_esp32_variant()
return variant in SPIRAM_MODES
def is_guaranteed() -> bool:
"""Check if PSRAM is guaranteed to be available.
Returns True when PSRAM is configured with both 'disabled: false' and
'ignore_not_found: false', meaning the device will fail to boot if PSRAM
is not found. This ensures safe use of high buffer configurations that
depend on PSRAM.
This function should be called during code generation (to_code phase) by
components that need to know PSRAM availability for configuration decisions.
Returns:
bool: True if PSRAM is guaranteed, False otherwise
"""
return CORE.data.get(KEY_PSRAM_GUARANTEED, False)
def validate_psram_mode(config):
esp32_config = fv.full_config.get()[PLATFORM_ESP32]
if config[CONF_SPEED] == "120MHZ":
if esp32_config[CONF_CPU_FREQUENCY] != "240MHZ":
raise cv.Invalid(
"PSRAM 120MHz requires 240MHz CPU frequency (set in esp32 component)"
)
if config[CONF_MODE] == TYPE_OCTAL:
if (
esp32_config[CONF_FRAMEWORK]
.get(CONF_ADVANCED, {})
.get(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES)
):
_LOGGER.warning(
"120MHz PSRAM in octal mode is an experimental feature - use at your own risk"
)
else:
raise cv.Invalid("PSRAM 120MHz is not supported in octal mode")
if config[CONF_MODE] != TYPE_OCTAL and config[CONF_ENABLE_ECC]:
raise cv.Invalid("ECC is only available in octal mode.")
if config[CONF_MODE] == TYPE_OCTAL:
variant = get_esp32_variant()
if variant != VARIANT_ESP32S3:
raise cv.Invalid(
f"Octal PSRAM is only supported on ESP32-S3, not {variant}"
)
return config
def get_config_schema(config):
variant = get_esp32_variant()
speeds = [f"{s}MHZ" for s in SPIRAM_SPEEDS.get(variant, [])]
if not speeds:
raise cv.Invalid("PSRAM is not supported on this chip")
modes = SPIRAM_MODES[variant]
if CONF_MODE not in config and len(modes) != 1:
raise (
cv.Invalid(
textwrap.dedent(
f"""
{variant} requires PSRAM mode selection; one of {", ".join(modes)}
Selection of the wrong mode for the board will cause a runtime failure to initialise PSRAM
"""
)
)
)
return cv.Schema(
{
cv.GenerateID(): cv.declare_id(PsramComponent),
cv.Optional(CONF_MODE, default=modes[0]): cv.one_of(*modes, lower=True),
cv.Optional(CONF_ENABLE_ECC, default=False): cv.boolean,
cv.Optional(CONF_SPEED, default=speeds[0]): cv.one_of(*speeds, upper=True),
cv.Optional(CONF_DISABLED, default=False): cv.boolean,
cv.Optional(CONF_IGNORE_NOT_FOUND, default=True): cv.boolean,
}
)(config)
CONFIG_SCHEMA = get_config_schema
def _store_psram_guaranteed(config):
"""Store PSRAM guaranteed status in CORE.data for other components.
PSRAM is "guaranteed" when it will fail if not found, ensuring safe use
of high buffer configurations in network/wifi components.
Called during final validation to ensure the flag is available
before any to_code() functions run.
"""
psram_guaranteed = not config[CONF_DISABLED] and not config[CONF_IGNORE_NOT_FOUND]
CORE.data[KEY_PSRAM_GUARANTEED] = psram_guaranteed
return config
FINAL_VALIDATE_SCHEMA = cv.All(validate_psram_mode, _store_psram_guaranteed)
async def to_code(config):
if config[CONF_DISABLED]:
return
if CORE.using_arduino:
cg.add_build_flag("-DBOARD_HAS_PSRAM")
if config[CONF_MODE] == TYPE_OCTAL:
cg.add_platformio_option("board_build.arduino.memory_type", "qio_opi")
add_idf_sdkconfig_option(
f"CONFIG_{get_esp32_variant().upper()}_SPIRAM_SUPPORT", True
)
add_idf_sdkconfig_option("CONFIG_SOC_SPIRAM_SUPPORTED", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM_USE", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM_USE_CAPS_ALLOC", True)
add_idf_sdkconfig_option(
"CONFIG_SPIRAM_IGNORE_NOTFOUND", config[CONF_IGNORE_NOT_FOUND]
)
add_idf_sdkconfig_option(f"CONFIG_SPIRAM_MODE_{SDK_MODES[config[CONF_MODE]]}", True)
# Remove MHz suffix, convert to int
speed = int(config[CONF_SPEED][:-3])
add_idf_sdkconfig_option(f"CONFIG_SPIRAM_SPEED_{speed}M", True)
add_idf_sdkconfig_option("CONFIG_SPIRAM_SPEED", speed)
if config[CONF_MODE] == TYPE_OCTAL and speed == 120:
add_idf_sdkconfig_option("CONFIG_ESPTOOLPY_FLASHFREQ_120M", True)
add_idf_sdkconfig_option("CONFIG_BOOTLOADER_FLASH_DC_AWARE", True)
if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(5, 4, 0):
add_idf_sdkconfig_option(
"CONFIG_SPIRAM_TIMING_TUNING_POINT_VIA_TEMPERATURE_SENSOR", True
)
if config[CONF_ENABLE_ECC]:
add_idf_sdkconfig_option("CONFIG_SPIRAM_ECC_ENABLE", True)
cg.add_define("USE_PSRAM")
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)