From c4097ec6a960b124a395bcd66692fb300e0f33df Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Fri, 17 Oct 2025 14:18:38 +0200 Subject: [PATCH 01/22] ARM: dts: Add stm32mp257f-dk-u-boot.dtsi Add U-Boot support for stm32mp257f-dk board. Signed-off-by: Patrice Chotard Reviewed-by: Patrick Delaunay --- arch/arm/dts/stm32mp257f-dk-u-boot.dtsi | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 arch/arm/dts/stm32mp257f-dk-u-boot.dtsi diff --git a/arch/arm/dts/stm32mp257f-dk-u-boot.dtsi b/arch/arm/dts/stm32mp257f-dk-u-boot.dtsi new file mode 100644 index 00000000000..fe3fe9c5166 --- /dev/null +++ b/arch/arm/dts/stm32mp257f-dk-u-boot.dtsi @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later OR BSD-3-Clause +/* + * Copyright (C) STMicroelectronics 2025 - All Rights Reserved + */ + +#include "stm32mp25-u-boot.dtsi" + +/ { + config { + u-boot,mmc-env-partition = "u-boot-env"; + }; +}; + +&usart2 { + bootph-all; +}; + +&usart2_pins_a { + bootph-all; + pins1 { + bootph-all; + }; + pins2 { + bootph-all; + }; +}; From 125b3c8b6da463e23a08100100b6d8e60f38fc32 Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Fri, 17 Oct 2025 14:18:39 +0200 Subject: [PATCH 02/22] ARM: dts: Fix "arm, smc-id" value for stm32mp23-u-boot.dtsi OP-TEE "arm,smc-id" is equal to 0xbc000000 but kernel DT has been upstream with an incorrect value. Fix it temporarily until kernel DT is fixed. Signed-off-by: Patrice Chotard Reviewed-by: Patrick Delaunay --- arch/arm/dts/stm32mp23-u-boot.dtsi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/dts/stm32mp23-u-boot.dtsi b/arch/arm/dts/stm32mp23-u-boot.dtsi index 872a8739c54..5a9436dd193 100644 --- a/arch/arm/dts/stm32mp23-u-boot.dtsi +++ b/arch/arm/dts/stm32mp23-u-boot.dtsi @@ -37,6 +37,11 @@ soc@0 { bootph-all; }; + + /* temporary until kernel DT update */ + watchdog { + arm,smc-id = <0xbc000000>; + }; }; &bsec { From 64b115d8f188ece7bcb25ff3e3e4a8d1d164287a Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Fri, 17 Oct 2025 14:18:40 +0200 Subject: [PATCH 03/22] ARM: dts: Fix "arm, smc-id" value for stm32mp25-u-boot.dtsi OP-TEE "arm,smc-id" is equal to 0xbc000000 but kernel DT has been upstream with an incorrect value. Fix it temporarily until kernel DT is fixed. Signed-off-by: Patrice Chotard Reviewed-by: Patrick Delaunay --- arch/arm/dts/stm32mp25-u-boot.dtsi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/dts/stm32mp25-u-boot.dtsi b/arch/arm/dts/stm32mp25-u-boot.dtsi index d9aeeb6d510..ee82a0289e2 100644 --- a/arch/arm/dts/stm32mp25-u-boot.dtsi +++ b/arch/arm/dts/stm32mp25-u-boot.dtsi @@ -39,6 +39,11 @@ soc@0 { bootph-all; }; + + /* temporary until kernel DT update */ + watchdog { + arm,smc-id = <0xbc000000>; + }; }; &bsec { From 3192a7eb0e6e9080ec26d7aa222857981b4132e5 Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Fri, 17 Oct 2025 14:18:41 +0200 Subject: [PATCH 04/22] ARM: dts: Add txbyteclk clock in stm32mp235f-dk-u-boot.dtsi Add txbyteclk to avoid error during clock registration. Signed-off-by: Patrice Chotard Reviewed-by: Patrick Delaunay --- arch/arm/dts/stm32mp235f-dk-u-boot.dtsi | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/arm/dts/stm32mp235f-dk-u-boot.dtsi b/arch/arm/dts/stm32mp235f-dk-u-boot.dtsi index 1bc77874050..84279c4712a 100644 --- a/arch/arm/dts/stm32mp235f-dk-u-boot.dtsi +++ b/arch/arm/dts/stm32mp235f-dk-u-boot.dtsi @@ -10,6 +10,14 @@ u-boot,boot-led = "led-blue"; u-boot,mmc-env-partition = "u-boot-env"; }; + + clocks { + txbyteclk { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <0>; + }; + }; }; &usart2 { From 87b0ab0365dc2bf884752bf86654367e11835fda Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Fri, 17 Oct 2025 14:18:42 +0200 Subject: [PATCH 05/22] stm32mp: Add stm32mp23 support for syscon driver Add "st,stm32mp23-syscfg" compatible. Fixes: fdd30ee308a2 ("ARM: stm32mp: Add STM32MP23 support") Signed-off-by: Patrice Chotard Reviewed-by: Patrick Delaunay --- arch/arm/mach-stm32mp/syscon.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/mach-stm32mp/syscon.c b/arch/arm/mach-stm32mp/syscon.c index 8bcbd979340..b00897e87ec 100644 --- a/arch/arm/mach-stm32mp/syscon.c +++ b/arch/arm/mach-stm32mp/syscon.c @@ -10,6 +10,7 @@ static const struct udevice_id stm32mp_syscon_ids[] = { { .compatible = "st,stm32mp157-syscfg", .data = STM32MP_SYSCON_SYSCFG }, + { .compatible = "st,stm32mp23-syscfg", .data = STM32MP_SYSCON_SYSCFG}, { .compatible = "st,stm32mp25-syscfg", .data = STM32MP_SYSCON_SYSCFG}, { } }; From 58d718b111be8864553f0ac05072b0e04006173f Mon Sep 17 00:00:00 2001 From: Olivier Moysan Date: Fri, 14 Nov 2025 17:08:52 +0100 Subject: [PATCH 06/22] adc: stm32mp13: add support of adc to stm32mp13 Add support of STM32 ADCs to STM32MP13x. This patch introduces stm32_adc_regspec structure, as this is already done in kernel driver, to manage smartly the differences in register set between STMP32MP15 and STM32MP13 ADCs. Signed-off-by: Olivier Moysan Signed-off-by: Patrice Chotard Reviewed-by: Patrick Delaunay --- drivers/adc/stm32-adc-core.c | 1 + drivers/adc/stm32-adc.c | 92 +++++++++++++++++++++++++++++------- 2 files changed, 77 insertions(+), 16 deletions(-) diff --git a/drivers/adc/stm32-adc-core.c b/drivers/adc/stm32-adc-core.c index af340b8b273..3446e34fa46 100644 --- a/drivers/adc/stm32-adc-core.c +++ b/drivers/adc/stm32-adc-core.c @@ -200,6 +200,7 @@ err_aclk_disable: static const struct udevice_id stm32_adc_core_ids[] = { { .compatible = "st,stm32h7-adc-core" }, { .compatible = "st,stm32mp1-adc-core" }, + { .compatible = "st,stm32mp13-adc-core" }, {} }; diff --git a/drivers/adc/stm32-adc.c b/drivers/adc/stm32-adc.c index d50f00f1233..b11f771b71c 100644 --- a/drivers/adc/stm32-adc.c +++ b/drivers/adc/stm32-adc.c @@ -49,29 +49,68 @@ /* STM32H7_ADC_SQR1 - bit fields */ #define STM32H7_SQ1_SHIFT 6 +/* STM32H7_ADC_DIFSEL - bit fields */ +#define STM32H7_DIFSEL_SHIFT 0 +#define STM32H7_DIFSEL_MASK GENMASK(19, 0) + /* BOOST bit must be set on STM32H7 when ADC clock is above 20MHz */ #define STM32H7_BOOST_CLKRATE 20000000UL +/* STM32MP13 - Registers for each ADC instance */ +#define STM32MP13_ADC_DIFSEL 0xB0 + +/* STM32MP13_ADC_CFGR specific bit fields */ +#define STM32MP13_DMAEN BIT(0) +#define STM32MP13_DMACFG BIT(1) + +/* STM32MP13_ADC_DIFSEL - bit fields */ +#define STM32MP13_DIFSEL_SHIFT 0 +#define STM32MP13_DIFSEL_MASK GENMASK(18, 0) + #define STM32_ADC_CH_MAX 20 /* max number of channels */ #define STM32_ADC_TIMEOUT_US 100000 -struct stm32_adc_cfg { - unsigned int max_channels; - unsigned int num_bits; - bool has_vregready; -}; - struct stm32_adc { void __iomem *regs; int active_channel; const struct stm32_adc_cfg *cfg; }; +struct stm32_adc_regs { + int reg; + int mask; + int shift; +}; + +struct stm32_adc_regspec { + const struct stm32_adc_regs difsel; +}; + +struct stm32_adc_cfg { + const struct stm32_adc_regspec *regs; + unsigned int max_channels; + unsigned int num_bits; + bool has_vregready; + bool has_boostmode; + bool has_linearcal; + bool has_presel; +}; + +static const struct stm32_adc_regspec stm32h7_adc_regspec = { + .difsel = { STM32H7_ADC_DIFSEL, STM32H7_DIFSEL_MASK }, +}; + +static const struct stm32_adc_regspec stm32mp13_adc_regspec = { + .difsel = { STM32MP13_ADC_DIFSEL, STM32MP13_DIFSEL_MASK }, +}; + static void stm32_adc_enter_pwr_down(struct udevice *dev) { struct stm32_adc *adc = dev_get_priv(dev); - clrbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_BOOST); + if (adc->cfg->has_boostmode) + clrbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_BOOST); + /* Setting DEEPPWD disables ADC vreg and clears ADVREGEN */ setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_DEEPPWD); } @@ -90,8 +129,7 @@ static int stm32_adc_exit_pwr_down(struct udevice *dev) /* Exit deep power down, then enable ADC voltage regulator */ clrbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_DEEPPWD); setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADVREGEN); - - if (common->rate > STM32H7_BOOST_CLKRATE) + if (adc->cfg->has_boostmode && common->rate > STM32H7_BOOST_CLKRATE) setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_BOOST); /* Wait for startup time */ @@ -134,7 +172,7 @@ static int stm32_adc_start_channel(struct udevice *dev, int channel) return ret; /* Only use single ended channels */ - writel(0, adc->regs + STM32H7_ADC_DIFSEL); + clrbits_le32(adc->regs + adc->cfg->regs->difsel.reg, adc->cfg->regs->difsel.mask); /* Enable ADC, Poll for ADRDY to be set (after adc startup time) */ setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADEN); @@ -147,7 +185,8 @@ static int stm32_adc_start_channel(struct udevice *dev, int channel) } /* Preselect channels */ - writel(uc_pdata->channel_mask, adc->regs + STM32H7_ADC_PCSEL); + if (adc->cfg->has_presel) + writel(uc_pdata->channel_mask, adc->regs + STM32H7_ADC_PCSEL); /* Set sampling time to max value by default */ writel(0xffffffff, adc->regs + STM32H7_ADC_SMPR1); @@ -156,9 +195,11 @@ static int stm32_adc_start_channel(struct udevice *dev, int channel) /* Program regular sequence: chan in SQ1 & len = 0 for one channel */ writel(channel << STM32H7_SQ1_SHIFT, adc->regs + STM32H7_ADC_SQR1); - /* Trigger detection disabled (conversion can be launched in SW) */ - clrbits_le32(adc->regs + STM32H7_ADC_CFGR, STM32H7_EXTEN | - STM32H7_DMNGT); + /* + * Trigger detection disabled (conversion can be launched in SW) + * STM32H7_DMNGT is equivalent to STM32MP13_DMAEN & STM32MP13_DMACFG + */ + clrbits_le32(adc->regs + STM32H7_ADC_CFGR, STM32H7_EXTEN | STM32H7_DMNGT); adc->active_channel = channel; return 0; @@ -206,7 +247,7 @@ static int stm32_adc_selfcalib(struct udevice *dev) { struct stm32_adc *adc = dev_get_priv(dev); int ret; - u32 val; + u32 val, mask; /* * Select calibration mode: @@ -231,7 +272,10 @@ static int stm32_adc_selfcalib(struct udevice *dev) * - Linearity calibration (needs to be done only once for single/diff) * will run simultaneously with offset calibration. */ - setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADCALDIF | STM32H7_ADCALLIN); + mask = STM32H7_ADCALDIF; + if (adc->cfg->has_linearcal) + mask |= STM32H7_ADCALLIN; + setbits_le32(adc->regs + STM32H7_ADC_CR, mask); /* Start calibration, then wait for completion */ setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADCAL); @@ -394,14 +438,28 @@ static const struct adc_ops stm32_adc_ops = { }; static const struct stm32_adc_cfg stm32h7_adc_cfg = { + .regs = &stm32h7_adc_regspec, .num_bits = 16, .max_channels = STM32_ADC_CH_MAX, + .has_boostmode = true, + .has_linearcal = true, + .has_presel = true, }; static const struct stm32_adc_cfg stm32mp1_adc_cfg = { + .regs = &stm32h7_adc_regspec, .num_bits = 16, .max_channels = STM32_ADC_CH_MAX, .has_vregready = true, + .has_boostmode = true, + .has_linearcal = true, + .has_presel = true, +}; + +static const struct stm32_adc_cfg stm32mp13_adc_cfg = { + .regs = &stm32mp13_adc_regspec, + .num_bits = 12, + .max_channels = STM32_ADC_CH_MAX - 1, }; static const struct udevice_id stm32_adc_ids[] = { @@ -409,6 +467,8 @@ static const struct udevice_id stm32_adc_ids[] = { .data = (ulong)&stm32h7_adc_cfg }, { .compatible = "st,stm32mp1-adc", .data = (ulong)&stm32mp1_adc_cfg }, + { .compatible = "st,stm32mp13-adc", + .data = (ulong)&stm32mp13_adc_cfg }, {} }; From 371baac357c58aebd0a96d3061c97910d4f1da64 Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Fri, 14 Nov 2025 17:08:53 +0100 Subject: [PATCH 07/22] configs: stm32: Enable ADC support for stm32mp13_defconfig Enable STM_ADC and CM_ADC for stm32mp13_defconfig Signed-off-by: Patrice Chotard Reviewed-by: Patrick Delaunay --- configs/stm32mp13_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configs/stm32mp13_defconfig b/configs/stm32mp13_defconfig index 3283e910219..8ef465ac222 100644 --- a/configs/stm32mp13_defconfig +++ b/configs/stm32mp13_defconfig @@ -29,6 +29,7 @@ CONFIG_CMD_NVEDIT_EFI=y CONFIG_CMD_MEMINFO=y CONFIG_CMD_MEMTEST=y CONFIG_CMD_UNZIP=y +CONFIG_CMD_ADC=y CONFIG_CMD_CLK=y CONFIG_CMD_FUSE=y CONFIG_CMD_GPIO=y @@ -56,6 +57,7 @@ CONFIG_ENV_REDUNDANT=y CONFIG_ENV_RELOC_GD_ENV_ADDR=y CONFIG_ENV_MMC_DEVICE_INDEX=-1 CONFIG_ENV_MMC_USE_DT=y +CONFIG_STM32_ADC=y CONFIG_SYS_64BIT_LBA=y CONFIG_BUTTON=y CONFIG_BUTTON_GPIO=y From 75defb7fbf9e10a7ddd817cbbcbabdd2a89805b3 Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Fri, 14 Nov 2025 17:08:54 +0100 Subject: [PATCH 08/22] ARM: dts: Add st, adc_usb_pd property for stm32mp135-dk-u-boot Add st,adc_usb_pd property in /config node for stm32mp135-dk-u-boot. This needed to check board USB power delivery. Signed-off-by: Patrice Chotard Reviewed-by: Patrick Delaunay --- arch/arm/dts/stm32mp135f-dk-u-boot.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/dts/stm32mp135f-dk-u-boot.dtsi b/arch/arm/dts/stm32mp135f-dk-u-boot.dtsi index f004e9840a2..326a8f8736e 100644 --- a/arch/arm/dts/stm32mp135f-dk-u-boot.dtsi +++ b/arch/arm/dts/stm32mp135f-dk-u-boot.dtsi @@ -14,6 +14,7 @@ u-boot,boot-led = "led-blue"; u-boot,error-led = "led-red"; u-boot,mmc-env-partition = "u-boot-env"; + st,adc_usb_pd = <&adc1 6>, <&adc1 12>; }; gpio-keys { From 1a7ea0280eefd0c0c01dfab8e48e1909b81ba2de Mon Sep 17 00:00:00 2001 From: Raphael Gallais-Pou Date: Thu, 4 Sep 2025 14:53:05 +0200 Subject: [PATCH 09/22] ofnode: support panel-timings in ofnode_decode_display_timing The "Display Timings" in panel-common.yaml can be provided by 2 properties - panel-timing: when display panels are restricted to a single resolution the "panel-timing" node expresses the required timings. - display-timings: several resolutions with different timings are supported with several timing subnode of "display-timings" node This patch update the parsing function to handle this 2 possibility when index = 0. Reviewed-by: Patrice Chotard Reviewed-by: Yannick Fertre Signed-off-by: Raphael Gallais-Pou --- drivers/core/ofnode.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/drivers/core/ofnode.c b/drivers/core/ofnode.c index 071d998a0a5..cf1cf8abfbe 100644 --- a/drivers/core/ofnode.c +++ b/drivers/core/ofnode.c @@ -1221,13 +1221,16 @@ int ofnode_decode_display_timing(ofnode parent, int index, int ret = 0; timings = ofnode_find_subnode(parent, "display-timings"); - if (!ofnode_valid(timings)) - return -EINVAL; - - i = 0; - ofnode_for_each_subnode(node, timings) { - if (i++ == index) - break; + if (ofnode_valid(timings)) { + i = 0; + ofnode_for_each_subnode(node, timings) { + if (i++ == index) + break; + } + } else { + if (index != 0) + return -EINVAL; + node = ofnode_find_subnode(parent, "panel-timing"); } if (!ofnode_valid(node)) From a58312b3c2108382543cf4e95f1262716a68c511 Mon Sep 17 00:00:00 2001 From: Raphael Gallais-Pou Date: Thu, 4 Sep 2025 14:53:06 +0200 Subject: [PATCH 10/22] video: simple_panel: add support for "panel-lvds" display Add the compatible "panel-lvds" for simple-panel driver in U-Boot. In Linux this compatible is managed by the driver drivers/gpu/drm/panel/panel-lvds.c but in U-Boot the specific LVDS features (bus_format/bus_flags) are not supported. Reviewed-by: Patrice Chotard Reviewed-by: Yannick Fertre Signed-off-by: Raphael Gallais-Pou --- drivers/video/simple_panel.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/video/simple_panel.c b/drivers/video/simple_panel.c index b6c5b058b2e..0f23df701bc 100644 --- a/drivers/video/simple_panel.c +++ b/drivers/video/simple_panel.c @@ -191,6 +191,7 @@ static const struct mipi_dsi_panel_plat panasonic_vvx10f004b00 = { static const struct udevice_id simple_panel_ids[] = { { .compatible = "simple-panel" }, + { .compatible = "panel-lvds" }, { .compatible = "auo,b133xtn01" }, { .compatible = "auo,b116xw03" }, { .compatible = "auo,b133htn01" }, From 16cfbbbe2b513f3c93474ca247b955a6ac281f1a Mon Sep 17 00:00:00 2001 From: Raphael Gallais-Pou Date: Thu, 4 Sep 2025 14:53:07 +0200 Subject: [PATCH 11/22] video: stm32: STM32 driver support for LVDS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LVDS Display Interface Transmitter handles the LVDS protocol: it maps the pixels received from the upstream Pixel-DMA (LTDC) onto the LVDS PHY. The LVDS controller driver supports the following high-level features: • FDP-Link-I and OpenLDI (v0.95) protocols • Single-Link or Dual-Link operation • Single-Display or Double-Display (with the same content duplicated on both) • Flexible Bit-Mapping, including JEIDA and VESA • RGB888 or RGB666 output • Synchronous design, with one input pixel per clock cycle • No resolution limitation. Acked-by: Yannick Fertre Signed-off-by: Raphael Gallais-Pou --- MAINTAINERS | 1 + doc/board/st/st-dt.rst | 1 + drivers/video/stm32/Kconfig | 9 + drivers/video/stm32/Makefile | 1 + drivers/video/stm32/stm32_lvds.c | 693 +++++++++++++++++++++++++++++++ 5 files changed, 705 insertions(+) create mode 100644 drivers/video/stm32/stm32_lvds.c diff --git a/MAINTAINERS b/MAINTAINERS index 60772a494a3..b43dae882b3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -734,6 +734,7 @@ F: drivers/spi/stm32_ospi.c F: drivers/spi/stm32_qspi.c F: drivers/spi/stm32_spi.c F: drivers/video/stm32/stm32_ltdc.c +F: drivers/video/stm32/stm32_lvds.c F: drivers/watchdog/stm32mp_wdt.c F: include/dt-bindings/clock/stm32fx-clock.h F: include/dt-bindings/clock/stm32mp* diff --git a/doc/board/st/st-dt.rst b/doc/board/st/st-dt.rst index 2a285c81807..28cada97e72 100644 --- a/doc/board/st/st-dt.rst +++ b/doc/board/st/st-dt.rst @@ -25,6 +25,7 @@ kernel binding directory = Documentation/devicetree/bindings/ * display - display/st,stm32-dsi.yaml - display/st,stm32-ltdc.yaml + - display/st,stm32mp25-lvds.yaml * gpio - pinctrl/st,stm32-pinctrl.yaml * hwlock diff --git a/drivers/video/stm32/Kconfig b/drivers/video/stm32/Kconfig index c354c402c28..4cb8a841caf 100644 --- a/drivers/video/stm32/Kconfig +++ b/drivers/video/stm32/Kconfig @@ -23,6 +23,15 @@ config VIDEO_STM32_DSI This option enables support DSI internal bridge which can be used on devices which have DSI devices connected. +config VIDEO_STM32_LVDS + bool "Enable STM32 LVDS video support" + depends on VIDEO_STM32 + select VIDEO_BRIDGE + select VIDEO_DW_MIPI_DSI + help + This enables Low Voltage Differential Signaling (LVDS) display + support. + config VIDEO_STM32_MAX_XRES int "Maximum horizontal resolution (for memory allocation purposes)" depends on VIDEO_STM32 diff --git a/drivers/video/stm32/Makefile b/drivers/video/stm32/Makefile index f8b42d1a4d1..059d9000c1d 100644 --- a/drivers/video/stm32/Makefile +++ b/drivers/video/stm32/Makefile @@ -7,3 +7,4 @@ obj-${CONFIG_VIDEO_STM32} = stm32_ltdc.o obj-${CONFIG_VIDEO_STM32_DSI} += stm32_dsi.o +obj-${CONFIG_VIDEO_STM32_LVDS} += stm32_lvds.o diff --git a/drivers/video/stm32/stm32_lvds.c b/drivers/video/stm32/stm32_lvds.c new file mode 100644 index 00000000000..bf1393c9e87 --- /dev/null +++ b/drivers/video/stm32/stm32_lvds.c @@ -0,0 +1,693 @@ +// SPDX-License-Identifier: GPL-2.0-or-later OR BSD-3-Clause +/* + * Copyright (C) 2025 STMicroelectronics - All Rights Reserved + * Author(s): Raphaël Gallais-Pou for STMicroelectronics. + * + * This Low Voltage Differential Signal controller driver is based on the Linux Kernel driver from + * drivers/gpu/drm/stm/ltdc.c + */ + +#define LOG_CATEGORY UCLASS_VIDEO_BRIDGE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* LVDS Host registers */ +#define LVDS_CR 0x0000 /* configuration register */ +#define LVDS_DMLCR0 0x0004 /* data mapping lsb configuration register 0 */ +#define LVDS_DMMCR0 0x0008 /* data mapping msb configuration register 0 */ +#define LVDS_DMLCR1 0x000C /* data mapping lsb configuration register 1 */ +#define LVDS_DMMCR1 0x0010 /* data mapping msb configuration register 1 */ +#define LVDS_DMLCR2 0x0014 /* data mapping lsb configuration register 2 */ +#define LVDS_DMMCR2 0x0018 /* data mapping msb configuration register 2 */ +#define LVDS_DMLCR3 0x001C /* data mapping lsb configuration register 3 */ +#define LVDS_DMMCR3 0x0020 /* data mapping msb configuration register 3 */ +#define LVDS_DMLCR4 0x0024 /* data mapping lsb configuration register 4 */ +#define LVDS_DMMCR4 0x0028 /* data mapping msb configuration register 4 */ +#define LVDS_DMLCR(id) (LVDS_DMLCR0 + 8U * (id)) +#define LVDS_DMMCR(id) (LVDS_DMMCR0 + 8U * (id)) +#define LVDS_CDL1CR 0x002C /* channel distrib link 1 configuration register */ +#define LVDS_CDL2CR 0x0030 /* channel distrib link 2 configuration register */ + +#define CDL1CR_DEFAULT 0x4321 +#define CDL2CR_DEFAULT 0x59876 + +/* LVDS Host registers */ +#define LVDS_PHY_MASTER 0x0 +#define LVDS_PHY_SLAVE 0x100 + +/* phy parameter can only be one of those two above */ +#define LVDS_PXGCR(phy) ((phy) + 0x1000) /* Global Control Register */ +#define LVDS_PXCMCR1(phy) ((phy) + 0x100C) /* Current Mode Control Register 1 */ +#define LVDS_PXCMCR2(phy) ((phy) + 0x1010) /* Current Mode Control Register 2 */ +#define LVDS_PXSCR(phy) ((phy) + 0x1020) /* Serial Control Register */ +#define LVDS_PXBCR1(phy) ((phy) + 0x102C) /* Bias Control Register 1 */ +#define LVDS_PXBCR2(phy) ((phy) + 0x1030) /* Bias Control Register 2 */ +#define LVDS_PXBCR3(phy) ((phy) + 0x1034) /* Bias Control Register 3 */ +#define LVDS_PXMPLCR(phy) ((phy) + 0x1064) /* Monitor PLL Lock Control Register */ +#define LVDS_PXDCR(phy) ((phy) + 0x1084) /* Debug Control Register */ +#define LVDS_PXSSR1(phy) ((phy) + 0x1088) /* Spare Status Register 1 */ +#define LVDS_PXCFGCR(phy) ((phy) + 0x10A0) /* Configuration Control Register */ +#define LVDS_PXPLLCR1(phy) ((phy) + 0x10C0) /* PLL_MODE 1 Control Register */ +#define LVDS_PXPLLCR2(phy) ((phy) + 0x10C4) /* PLL_MODE 2 Control Register */ +#define LVDS_PXPLLSR(phy) ((phy) + 0x10C8) /* PLL Status Register */ +#define LVDS_PXPLLSDCR1(phy) ((phy) + 0x10CC) /* PLL_SD_1 Control Register */ +#define LVDS_PXPLLSDCR2(phy) ((phy) + 0x10D0) /* PLL_SD_2 Control Register */ +#define LVDS_PXPLLTWGCR1(phy) ((phy) + 0x10D4) /* PLL_TWG_1 Control Register */ +#define LVDS_PXPLLTWGCR2(phy) ((phy) + 0x10D8) /* PLL_TWG_2 Control Register */ +#define LVDS_PXPLLCPCR(phy) ((phy) + 0x10E0) /* PLL_CP Control Register */ +#define LVDS_PXPLLTESTCR(phy) ((phy) + 0x10E8) /* PLL_TEST Control Register */ + +/* LVDS Wrapper registers */ +#define LVDS_WCLKCR 0x11B0 /* Wrapper clock control register */ +#define LVDS_HWCFGR 0x1FF0 /* HW configuration register */ +#define LVDS_VERR 0x1FF4 /* Version register */ +#define LVDS_IPIDR 0x1FF8 /* Identification register */ +#define LVDS_SIDR 0x1FFC /* Size Identification register */ + +#define CR_LVDSEN BIT(0) /* LVDS PHY Enable */ +#define CR_HSPOL BIT(1) /* HS Polarity (horizontal sync) */ +#define CR_VSPOL BIT(2) /* VS Polarity (vertical sync) */ +#define CR_DEPOL BIT(3) /* DE Polarity (data enable) */ +#define CR_CI BIT(4) /* Control Internal (software controlled bit) */ +#define CR_LKMOD BIT(5) /* Link Mode, for both Links */ +#define CR_LKPHA BIT(6) /* Link Phase, for both Links */ +#define CR_LK1POL GENMASK(20, 16) /* Link-1 output Polarity */ +#define CR_LK2POL GENMASK(25, 21) /* Link-2 output Polarity */ + +#define DMMCRX_MAP0 GENMASK(4, 0) +#define DMMCRX_MAP1 GENMASK(9, 5) +#define DMMCRX_MAP2 GENMASK(14, 10) +#define DMMCRX_MAP3 GENMASK(19, 15) +#define DMLCRX_MAP4 GENMASK(4, 0) +#define DMLCRX_MAP5 GENMASK(9, 5) +#define DMLCRX_MAP6 GENMASK(14, 10) + +#define CDLCRX_DISTR0 GENMASK(3, 0) +#define CDLCRX_DISTR1 GENMASK(7, 4) +#define CDLCRX_DISTR2 GENMASK(11, 8) +#define CDLCRX_DISTR3 GENMASK(15, 12) +#define CDLCRX_DISTR4 GENMASK(19, 16) + +#define FREF_INDEX 0 +#define NDIV_INDEX 1 +#define FPFD_INDEX 2 +#define MDIV_INDEX 3 +#define FVCO_INDEX 4 +#define BDIV_INDEX 5 +#define FBIT_INDEX 6 +#define FLS_INDEX 7 +#define FDP_INDEX 8 + +#define PXGCR_BIT_CLK_OUT BIT(0) +#define PXGCR_LS_CLK_OUT BIT(4) +#define PXGCR_DP_CLK_OUT BIT(8) +#define PXGCR_RSTZ BIT(24) +#define PXGCR_DIV_RSTN BIT(25) + +#define PXCMCR1_CM_EN_DL (BIT(28) | BIT(20) | BIT(12) | BIT(4)) +#define PXCMCR2_CM_EN_DL4 BIT(4) +#define PXSCR_SER_DATA_OK BIT(16) +#define PXBCR1_EN_BIAS_DL (BIT(16) | BIT(12) | BIT(8) | BIT(4) | BIT(0)) +#define PXBCR2_BIAS_EN BIT(28) +#define PXBCR3_VM_EN_DL (BIT(16) | BIT(12) | BIT(8) | BIT(4) | BIT(0)) +#define PXDCR_POWER_OK BIT(12) +#define PXCFGCR_EN_DIG_DL GENMASK(4, 0) + +#define PXPLLCR1_PLL_EN BIT(0) +#define PxPLLCR1_SD_EN BIT(1) +#define PXPLLCR1_TWG_EN BIT(2) +#define PXPLLCR1_PLL_DIVIDERS_EN BIT(8) +#define PXPLLCR2_NDIV GENMASK(25, 16) +#define PXPLLCR2_BDIV GENMASK(9, 0) +#define PXPLLSR_PLL_LOCK BIT(0) +#define PXPLLSDCR1_MDIV GENMASK(9, 0) +#define PXPLLCPCR_CPCTRL_DEFAULT 0x1 +#define PXPLLTESTCR_PLL_TEST_CLK_EN BIT(0) +#define PXPLLTESTCR_PLL_TDIV_EN BIT(8) +#define PXPLLTESTCR_TDIV GENMASK(25, 16) +#define PXPLLTESTCR_TDIV_VALUE 70 + +#define WCLKCR_SLV_CLKPIX_SEL BIT(0) +#define WCLKCR_SRCSEL BIT(8) + +/* Sleep & timeout for pll lock/unlock */ +#define SLEEP_US 1000 +#define TIMEOUT_US 20000000 + +#define PHY_SLV_OFS 0x100 + +/* PLL parameters */ +#define NDIV_MIN 2 +#define NDIV_MAX 6 +#define BDIV_MIN 2 +#define BDIV_MAX 6 +#define MDIV_MIN 1 +#define MDIV_MAX 1023 + +struct stm32_lvds_plat { + void __iomem *base; + struct udevice *panel; + struct reset_ctl rst; + struct clk pclk; + struct clk refclk; +}; + +struct stm32_lvds_priv { + struct display_timing timings; + u32 refclk_rate; + int dual_link; + int bus_format; +}; + +/* + * enum lvds_pixels_order - Pixel order of an LVDS connection + * @LVDS_DUAL_LINK_EVEN_ODD_PIXELS: Even pixels are expected to be generated + * from the first port, odd pixels from the second port + * @LVDS_DUAL_LINK_ODD_EVEN_PIXELS: Odd pixels are expected to be generated + * from the first port, even pixels from the second port + */ +enum lvds_pixels_order { + LVDS_DUAL_LINK_EVEN_ODD_PIXELS = BIT(0), + LVDS_DUAL_LINK_ODD_EVEN_PIXELS = BIT(1), +}; + +enum lvds_pixel { + PIX_R_0 = 0x00, + PIX_R_1 = 0x01, + PIX_R_2 = 0x02, + PIX_R_3 = 0x03, + PIX_R_4 = 0x04, + PIX_R_5 = 0x05, + PIX_R_6 = 0x06, + PIX_R_7 = 0x07, + PIX_G_0 = 0x08, + PIX_G_1 = 0x09, + PIX_G_2 = 0x0A, + PIX_G_3 = 0x0B, + PIX_G_4 = 0x0C, + PIX_G_5 = 0x0D, + PIX_G_6 = 0x0E, + PIX_G_7 = 0x0F, + PIX_B_0 = 0x10, + PIX_B_1 = 0x11, + PIX_B_2 = 0x12, + PIX_B_3 = 0x13, + PIX_B_4 = 0x14, + PIX_B_5 = 0x15, + PIX_B_6 = 0x16, + PIX_B_7 = 0x17, + PIX_H_S = 0x18, + PIX_V_S = 0x19, + PIX_D_E = 0x1A, + PIX_C_E = 0x1B, + PIX_C_I = 0x1C, + PIX_TOG = 0x1D, + PIX_ONE = 0x1E, + PIX_ZER = 0x1F, +}; + +/* + * Expected JEIDA-RGB888 data to be sent in LSB format + * bit6 ............................bit0 + */ +const enum lvds_pixel lvds_bitmap_jeida_rgb888[5][7] = { + { PIX_ONE, PIX_ONE, PIX_ZER, PIX_ZER, PIX_ZER, PIX_ONE, PIX_ONE }, + { PIX_G_2, PIX_R_7, PIX_R_6, PIX_R_5, PIX_R_4, PIX_R_3, PIX_R_2 }, + { PIX_B_3, PIX_B_2, PIX_G_7, PIX_G_6, PIX_G_5, PIX_G_4, PIX_G_3 }, + { PIX_D_E, PIX_V_S, PIX_H_S, PIX_B_7, PIX_B_6, PIX_B_5, PIX_B_4 }, + { PIX_C_E, PIX_B_1, PIX_B_0, PIX_G_1, PIX_G_0, PIX_R_1, PIX_R_0 } +}; + +/* + * Expected VESA-RGB888 data to be sent in LSB format + * bit6 ............................bit0 + */ +const enum lvds_pixel lvds_bitmap_vesa_rgb888[5][7] = { + { PIX_ONE, PIX_ONE, PIX_ZER, PIX_ZER, PIX_ZER, PIX_ONE, PIX_ONE }, + { PIX_G_0, PIX_R_5, PIX_R_4, PIX_R_3, PIX_R_2, PIX_R_1, PIX_R_0 }, + { PIX_B_1, PIX_B_0, PIX_G_5, PIX_G_4, PIX_G_3, PIX_G_2, PIX_G_1 }, + { PIX_D_E, PIX_V_S, PIX_H_S, PIX_B_5, PIX_B_4, PIX_B_3, PIX_B_2 }, + { PIX_C_E, PIX_B_7, PIX_B_6, PIX_G_7, PIX_G_6, PIX_R_7, PIX_R_6 } +}; + +static inline void lvds_writel(void __iomem *base, u32 reg, u32 val) +{ + writel(val, base + reg); +} + +static inline u32 lvds_readl(void __iomem *base, u32 reg) +{ + return readl(base + reg); +} + +static inline void lvds_set(void __iomem *base, u32 reg, u32 mask) +{ + lvds_writel(base, reg, lvds_readl(base, reg) | mask); +} + +static inline void lvds_clear(void __iomem *base, u32 reg, u32 mask) +{ + lvds_writel(base, reg, lvds_readl(base, reg) & ~mask); +} + +static u32 pll_get_clkout_khz(u32 clkin_khz, u32 bdiv, u32 mdiv, u32 ndiv) +{ + int divisor = ndiv * bdiv; + + /* Prevents from division by 0 */ + if (!divisor) + return 0; + + return clkin_khz * mdiv / divisor; +} + +static int lvds_pll_get_params(u32 clkin_khz, u32 clkout_khz, + u32 *bdiv, u32 *mdiv, u32 *ndiv) +{ + u32 i, o, n; + u32 delta, best_delta; /* all in khz */ + + /* Early checks preventing division by 0 & odd results */ + if (clkin_khz == 0 || clkout_khz == 0) + return -EINVAL; + + best_delta = 1000000; /* big started value (1000000khz) */ + + for (i = NDIV_MIN; i <= NDIV_MAX; i++) { + for (o = BDIV_MIN; o <= BDIV_MAX; o++) { + n = DIV_ROUND_CLOSEST(i * o * clkout_khz, clkin_khz); + /* Check ndiv according to vco range */ + if (n < MDIV_MIN || n > MDIV_MAX) + continue; + /* Check if new delta is better & saves parameters */ + delta = abs(pll_get_clkout_khz(clkin_khz, i, n, o) - clkout_khz); + if (delta < best_delta) { + *ndiv = i; + *mdiv = n; + *bdiv = o; + best_delta = delta; + } + /* fast return in case of "perfect result" */ + if (!delta) + return 0; + } + } + + return 0; +} + +static int stm32_lvds_pll_enable(struct udevice *dev, + int phy) +{ + struct stm32_lvds_plat *plat = dev_get_plat(dev); + struct stm32_lvds_priv *priv = dev_get_priv(dev); + struct display_timing timings = priv->timings; + u32 pll_in_khz, bdiv = 0, mdiv = 0, ndiv = 0; + int ret, val, multiplier; + + /* Release PHY from reset */ + lvds_set(plat->base, LVDS_PXGCR(phy), PXGCR_DIV_RSTN | PXGCR_RSTZ); + + /* lvds_pll_config */ + /* Set PLL Slv & Mst configs and timings */ + pll_in_khz = priv->refclk_rate / 1000; + + if (priv->dual_link) + multiplier = 2; + else + multiplier = 1; + + ret = lvds_pll_get_params(pll_in_khz, timings.pixelclock.typ * 7 / 1000 / multiplier, + &bdiv, &mdiv, &ndiv); + if (ret) + return ret; + + /* Set PLL parameters */ + lvds_writel(plat->base, LVDS_PXPLLCR2(phy), (ndiv << 16) | bdiv); + lvds_writel(plat->base, LVDS_PXPLLSDCR1(phy), mdiv); + lvds_writel(plat->base, LVDS_PXPLLTESTCR(phy), PXPLLTESTCR_TDIV_VALUE << 16); + + /* Disable TWG and SD: for now, PLL just need to be in integer mode */ + lvds_clear(plat->base, LVDS_PXPLLCR1(phy), PXPLLCR1_TWG_EN | PxPLLCR1_SD_EN); + + /* Power up bias and PLL dividers */ + lvds_set(plat->base, LVDS_PXDCR(phy), PXDCR_POWER_OK); + + lvds_set(plat->base, LVDS_PXCMCR1(phy), PXCMCR1_CM_EN_DL); + lvds_set(plat->base, LVDS_PXCMCR2(phy), PXCMCR2_CM_EN_DL4); + + lvds_set(plat->base, LVDS_PXPLLCPCR(phy), PXPLLCPCR_CPCTRL_DEFAULT); + lvds_set(plat->base, LVDS_PXBCR3(phy), PXBCR3_VM_EN_DL); + lvds_set(plat->base, LVDS_PXBCR1(phy), PXBCR1_EN_BIAS_DL); + lvds_set(plat->base, LVDS_PXCFGCR(phy), PXCFGCR_EN_DIG_DL); + + /* lvds_pll_enable */ + /* PLL lock timing control for the monitor unmask after startup (pll_en) */ + /* Adjust the value so that the masking window is opened at start-up */ + /* MST_MON_PLL_LOCK_UNMASK_TUNE */ + lvds_writel(plat->base, LVDS_PXMPLCR(phy), (0x200 - 0x160) << 16); + + lvds_writel(plat->base, LVDS_PXBCR2(phy), PXBCR2_BIAS_EN); + + lvds_set(plat->base, LVDS_PXGCR(phy), + PXGCR_DP_CLK_OUT | PXGCR_LS_CLK_OUT | PXGCR_BIT_CLK_OUT); + + lvds_set(plat->base, LVDS_PXPLLTESTCR(phy), PXPLLTESTCR_PLL_TDIV_EN); + lvds_set(plat->base, LVDS_PXPLLCR1(phy), PXPLLCR1_PLL_DIVIDERS_EN); + lvds_set(plat->base, LVDS_PXSCR(phy), PXSCR_SER_DATA_OK); + + /* Enable the LVDS PLL & wait for its lock */ + lvds_set(plat->base, LVDS_PXPLLCR1(phy), PXPLLCR1_PLL_EN); + ret = readl_poll_sleep_timeout(plat->base + LVDS_PXPLLSR(phy), + val, val & PXPLLSR_PLL_LOCK, SLEEP_US, TIMEOUT_US); + if (ret) + return ret; + + /* Select MST PHY clock as pixel clock for the LDITX instead of FREF */ + /* WCLKCR_SLV_CLKPIX_SEL is for dual link */ + lvds_writel(plat->base, LVDS_WCLKCR, WCLKCR_SLV_CLKPIX_SEL); + + lvds_set(plat->base, LVDS_PXPLLTESTCR(phy), PXPLLTESTCR_PLL_TEST_CLK_EN); + + return 0; +} + +static int stm32_lvds_enable(struct udevice *dev) +{ + struct stm32_lvds_plat *plat = dev_get_plat(dev); + struct stm32_lvds_priv *priv = dev_get_priv(dev); + struct display_timing timings = priv->timings; + u32 lvds_cdl1cr = 0; + u32 lvds_cdl2cr = 0; + u32 lvds_dmlcr = 0; + u32 lvds_dmmcr = 0; + u32 lvds_cr = 0; + int i; + + lvds_clear(plat->base, LVDS_CDL1CR, CDLCRX_DISTR0 | CDLCRX_DISTR1 | CDLCRX_DISTR2 + | CDLCRX_DISTR3 | CDLCRX_DISTR4); + lvds_clear(plat->base, LVDS_CDL2CR, CDLCRX_DISTR0 | CDLCRX_DISTR1 | CDLCRX_DISTR2 + | CDLCRX_DISTR3 | CDLCRX_DISTR4); + + /* Set channel distribution */ + lvds_cr &= ~CR_LKMOD; + lvds_cdl1cr = CDL1CR_DEFAULT; + + if (priv->dual_link) { + lvds_cr |= CR_LKMOD; + lvds_cdl2cr = CDL2CR_DEFAULT; + } + + /* Set signal polarity */ + if (timings.flags & DISPLAY_FLAGS_DE_LOW) + lvds_cr |= CR_DEPOL; + + if (timings.flags & DISPLAY_FLAGS_HSYNC_LOW) + lvds_cr |= CR_HSPOL; + + if (timings.flags & DISPLAY_FLAGS_VSYNC_LOW) + lvds_cr |= CR_VSPOL; + + /* Set link phase */ + switch (priv->dual_link) { + case LVDS_DUAL_LINK_EVEN_ODD_PIXELS: /* LKPHA = 0 */ + lvds_cr &= ~CR_LKPHA; + break; + case LVDS_DUAL_LINK_ODD_EVEN_PIXELS: /* LKPHA = 1 */ + lvds_cr |= CR_LKPHA; + break; + default: + dev_dbg(dev, "No phase precised, setting default\n"); + lvds_cr &= ~CR_LKPHA; + break; + } + + /* Set Data Mapping */ + switch (priv->bus_format) { + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: /* VESA-RGB888 */ + for (i = 0; i < 5; i++) { + lvds_dmlcr = ((lvds_bitmap_vesa_rgb888[i][0]) + + (lvds_bitmap_vesa_rgb888[i][1] << 5) + + (lvds_bitmap_vesa_rgb888[i][2] << 10) + + (lvds_bitmap_vesa_rgb888[i][3] << 15)); + lvds_dmmcr = ((lvds_bitmap_vesa_rgb888[i][4]) + + (lvds_bitmap_vesa_rgb888[i][5] << 5) + + (lvds_bitmap_vesa_rgb888[i][6] << 10)); + + /* Write registers at the end of computations */ + lvds_writel(plat->base, LVDS_DMLCR(i), lvds_dmlcr); + lvds_writel(plat->base, LVDS_DMMCR(i), lvds_dmmcr); + } + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: /* JEIDA-RGB888 */ + for (i = 0; i < 5; i++) { + lvds_dmlcr = ((lvds_bitmap_jeida_rgb888[i][0]) + + (lvds_bitmap_jeida_rgb888[i][1] << 5) + + (lvds_bitmap_jeida_rgb888[i][2] << 10) + + (lvds_bitmap_jeida_rgb888[i][3] << 15)); + lvds_dmmcr = ((lvds_bitmap_jeida_rgb888[i][4]) + + (lvds_bitmap_jeida_rgb888[i][5] << 5) + + (lvds_bitmap_jeida_rgb888[i][6] << 10)); + + /* Write registers at the end of computations */ + lvds_writel(plat->base, LVDS_DMLCR(i), lvds_dmlcr); + lvds_writel(plat->base, LVDS_DMMCR(i), lvds_dmmcr); + } + break; + default: + dev_dbg(dev, "Unsupported LVDS bus format 0x%04x\n", priv->bus_format); + } + + /* Turn the output on */ + lvds_cr |= CR_LVDSEN; + + /* Commit config to registers */ + lvds_set(plat->base, LVDS_CR, lvds_cr); + lvds_writel(plat->base, LVDS_CDL1CR, lvds_cdl1cr); + lvds_writel(plat->base, LVDS_CDL2CR, lvds_cdl2cr); + + return 0; +} + +static int stm32_lvds_attach(struct udevice *dev) +{ + struct stm32_lvds_plat *plat = dev_get_plat(dev); + struct stm32_lvds_priv *priv = dev_get_priv(dev); + int ret; + + ret = panel_get_display_timing(plat->panel, &priv->timings); + if (ret) { + ret = ofnode_decode_display_timing(dev_ofnode(plat->panel), + 0, &priv->timings); + if (ret) { + dev_err(dev, "decode display timing error %d\n", ret); + return ret; + } + } + + ret = stm32_lvds_enable(dev); + + return ret; +} + +static int stm32_lvds_set_backlight(struct udevice *dev, int percent) +{ + struct stm32_lvds_plat *plat = dev_get_plat(dev); + int ret; + + ret = panel_enable_backlight(plat->panel); + if (ret) { + dev_err(dev, "panel %s enable backlight error %d\n", + plat->panel->name, ret); + } + + return ret; +} + +static int lvds_handle_pixel_order(struct stm32_lvds_plat *plat) +{ + ofnode parent, panel_port0, panel_port1; + bool even_pixels, odd_pixels; + int port0, port1; + + /* + * In case we are operating in single link, + * there is only one port linked to the LVDS. + * Check whether we are in this case and exit if yes. + */ + parent = ofnode_find_subnode(dev_ofnode(plat->panel), "ports"); + if (!ofnode_valid(parent)) + return 0; + + panel_port0 = ofnode_first_subnode(parent); + if (!ofnode_valid(panel_port0)) + return -EPIPE; + + even_pixels = ofnode_read_bool(panel_port0, "dual-lvds-even-pixels"); + odd_pixels = ofnode_read_bool(panel_port0, "dual-lvds-odd-pixels"); + if (even_pixels && odd_pixels) + return -EINVAL; + + port0 = even_pixels ? LVDS_DUAL_LINK_EVEN_ODD_PIXELS : + LVDS_DUAL_LINK_ODD_EVEN_PIXELS; + + panel_port1 = ofnode_next_subnode(panel_port0); + if (!ofnode_valid(panel_port1)) + return -EPIPE; + + even_pixels = ofnode_read_bool(panel_port1, "dual-lvds-even-pixels"); + odd_pixels = ofnode_read_bool(panel_port1, "dual-lvds-odd-pixels"); + if (even_pixels && odd_pixels) + return -EINVAL; + + port1 = even_pixels ? LVDS_DUAL_LINK_EVEN_ODD_PIXELS : + LVDS_DUAL_LINK_ODD_EVEN_PIXELS; + + /* + * A valid dual-LVDS bus is found when one port is marked with + * "dual-lvds-even-pixels", and the other port is marked with + * "dual-lvds-odd-pixels", bail out if the markers are not right. + */ + if (port0 + port1 != LVDS_DUAL_LINK_EVEN_ODD_PIXELS + LVDS_DUAL_LINK_ODD_EVEN_PIXELS) + return -EINVAL; + + return port0; +} + +static int stm32_lvds_of_to_plat(struct udevice *dev) +{ + struct stm32_lvds_plat *plat = dev_get_plat(dev); + struct stm32_lvds_priv *priv = dev_get_priv(dev); + const char *data_mapping; + int ret; + + plat->base = dev_read_addr_ptr(dev); + if ((fdt_addr_t)plat->base == FDT_ADDR_T_NONE) { + dev_err(dev, "Unable to read LVDS base address\n"); + return -EINVAL; + } + + ret = clk_get_by_name(dev, "pclk", &plat->pclk); + if (ret) { + dev_err(dev, "Unable to get peripheral clock: %d\n", ret); + return ret; + } + + ret = clk_get_by_name(dev, "ref", &plat->refclk); + if (ret) { + dev_err(dev, "Unable to get reference clock: %d\n", ret); + return ret; + } + + ret = reset_get_by_index(dev, 0, &plat->rst); + if (ret) { + dev_err(dev, "Failed to get LVDS reset: %d\n", ret); + return ret; + } + + ret = uclass_get_device_by_driver(UCLASS_PANEL, + DM_DRIVER_GET(simple_panel), &plat->panel); + if (ret) { + dev_err(dev, "panel device error %d\n", ret); + return ret; + } + + ret = panel_get_display_timing(plat->panel, &priv->timings); + if (ret) { + ret = ofnode_decode_display_timing(dev_ofnode(plat->panel), + 0, &priv->timings); + if (ret) { + dev_err(dev, "decode display timing error %d\n", ret); + return ret; + } + } + + data_mapping = ofnode_read_string(dev_ofnode(plat->panel), "data-mapping"); + if (!strcmp(data_mapping, "vesa-24")) + priv->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG; + else if (!strcmp(data_mapping, "jeida-24")) + priv->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA; + else + priv->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG; + + return 0; +} + +static int stm32_lvds_probe(struct udevice *dev) +{ + struct stm32_lvds_plat *plat = dev_get_plat(dev); + struct stm32_lvds_priv *priv = dev_get_priv(dev); + int ret; + + ret = clk_enable(&plat->pclk); + if (ret) { + dev_err(dev, "Failed to enable peripheral clock: %d\n", ret); + return ret; + } + + ret = clk_enable(&plat->refclk); + if (ret) { + dev_err(dev, "Failed to enable reference clock: %d\n", ret); + goto err_clk; + } + + priv->refclk_rate = (unsigned int)clk_get_rate(&plat->refclk); + + reset_deassert(&plat->rst); + + /* Handle dual link config */ + priv->dual_link = lvds_handle_pixel_order(plat); + if (priv->dual_link < 0) + goto err_rst; + + if (priv->dual_link > 0) { + ret = stm32_lvds_pll_enable(dev, LVDS_PHY_SLAVE); + if (ret) + goto err_rst; + } + + ret = stm32_lvds_pll_enable(dev, LVDS_PHY_MASTER); + if (ret) + goto err_rst; + + return 0; + +err_rst: + clk_disable(&plat->refclk); +err_clk: + clk_disable(&plat->pclk); + + return ret; +} + +static const struct video_bridge_ops stm32_lvds_ops = { + .attach = stm32_lvds_attach, + .set_backlight = stm32_lvds_set_backlight, +}; + +static const struct udevice_id stm32_lvds_ids[] = { + {.compatible = "st,stm32mp25-lvds"}, + {} +}; + +U_BOOT_DRIVER(stm32_lvds) = { + .name = "stm32-display-lvds", + .id = UCLASS_VIDEO_BRIDGE, + .of_match = stm32_lvds_ids, + .ops = &stm32_lvds_ops, + .of_to_plat = stm32_lvds_of_to_plat, + .probe = stm32_lvds_probe, + .plat_auto = sizeof(struct stm32_lvds_plat), + .priv_auto = sizeof(struct stm32_lvds_priv), +}; From b3316f6053feb3be1a731536c250942606447ea9 Mon Sep 17 00:00:00 2001 From: Raphael Gallais-Pou Date: Thu, 4 Sep 2025 14:53:08 +0200 Subject: [PATCH 12/22] video: stm32: ltdc: support new hardware version for STM32MP25 SoC STM32MP2 SoCs feature a new version of the LTDC IP. This new version features a bus clock, as well as a 150MHz pad frequency. Add its compatible to the list of device to probe and handle quirks. The new hardware version features a bus clock. Reviewed-by: Patrice Chotard Acked-by: Yannick Fertre Signed-off-by: Raphael Gallais-Pou --- drivers/video/stm32/stm32_ltdc.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/video/stm32/stm32_ltdc.c b/drivers/video/stm32/stm32_ltdc.c index 0a062c8939d..efe9a00996e 100644 --- a/drivers/video/stm32/stm32_ltdc.c +++ b/drivers/video/stm32/stm32_ltdc.c @@ -262,6 +262,7 @@ static const u32 layer_regs_a2[] = { #define HWVER_10300 0x010300 #define HWVER_20101 0x020101 #define HWVER_40100 0x040100 +#define HWVER_40101 0x040101 enum stm32_ltdc_pix_fmt { PF_ARGB8888 = 0, /* ARGB [32 bits] */ @@ -529,7 +530,7 @@ static int stm32_ltdc_probe(struct udevice *dev) struct udevice *bridge = NULL; struct udevice *panel = NULL; struct display_timing timings; - struct clk pclk; + struct clk pclk, bclk; struct reset_ctl rst; ulong rate; int ret; @@ -540,7 +541,21 @@ static int stm32_ltdc_probe(struct udevice *dev) return -EINVAL; } - ret = clk_get_by_index(dev, 0, &pclk); + ret = clk_get_by_name(dev, "bus", &bclk); + if (ret) { + if (ret != -ENODATA) { + dev_err(dev, "bus clock get error %d\n", ret); + return ret; + } + } else { + ret = clk_enable(&bclk); + if (ret) { + dev_err(dev, "bus clock enable error %d\n", ret); + return ret; + } + } + + ret = clk_get_by_name(dev, "lcd", &pclk); if (ret) { dev_err(dev, "peripheral clock get error %d\n", ret); return ret; @@ -566,6 +581,7 @@ static int stm32_ltdc_probe(struct udevice *dev) priv->pix_fmt_hw = pix_fmt_a1; break; case HWVER_40100: + case HWVER_40101: priv->layer_regs = layer_regs_a2; priv->pix_fmt_hw = pix_fmt_a2; break; @@ -688,6 +704,8 @@ static int stm32_ltdc_bind(struct udevice *dev) static const struct udevice_id stm32_ltdc_ids[] = { { .compatible = "st,stm32-ltdc" }, + { .compatible = "st,stm32mp251-ltdc" }, + { .compatible = "st,stm32mp255-ltdc" }, { } }; From 35be94614733e500bc00b8297ffdecf50aea6d03 Mon Sep 17 00:00:00 2001 From: Raphael Gallais-Pou Date: Thu, 4 Sep 2025 14:53:09 +0200 Subject: [PATCH 13/22] video: stm32: ltdc: properly search the first available panel Initially there was only one DSI bridge with one panel attached to this device. This explained the call to uclass_first_device_err(UCLASS_PANEL, ...) which worked fine at the time. Now that multiple bridges and panels, with different technologies, can be plugged onto the board this way to get the panel device is outdated. The lookup is done is two steps. First we circle through the UCLASS_VIDEO_BRIDGE, and once we get one, we search through its endpoints until we get a UCLASS_PANEL device available. Acked-by: Yannick Fertre Signed-off-by: Raphael Gallais-Pou Reviewed-by: Patrice Chotard --- drivers/video/stm32/stm32_ltdc.c | 136 ++++++++++++++++++++++++++++--- 1 file changed, 125 insertions(+), 11 deletions(-) diff --git a/drivers/video/stm32/stm32_ltdc.c b/drivers/video/stm32/stm32_ltdc.c index efe9a00996e..834bfb625d2 100644 --- a/drivers/video/stm32/stm32_ltdc.c +++ b/drivers/video/stm32/stm32_ltdc.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -495,6 +496,101 @@ static void stm32_ltdc_set_layer1(struct stm32_ltdc_priv *priv, ulong fb_addr) setbits_le32(priv->regs + LTDC_L1CR, LXCR_LEN); } +static int stm32_ltdc_get_remote_device(struct udevice *dev, ofnode ep_node, + enum uclass_id id, struct udevice **remote_dev) +{ + u32 remote_phandle; + ofnode remote; + int ret = 0; + + ret = ofnode_read_u32(ep_node, "remote-endpoint", &remote_phandle); + if (ret) { + dev_err(dev, "%s(%s): Could not find remote-endpoint property\n", + __func__, dev_read_name(dev)); + return ret; + } + + remote = ofnode_get_by_phandle(remote_phandle); + if (!ofnode_valid(remote)) + return -EINVAL; + + while (ofnode_valid(remote)) { + remote = ofnode_get_parent(remote); + if (!ofnode_valid(remote)) { + dev_dbg(dev, "%s(%s): no uclass_id %d for remote-endpoint\n", + __func__, dev_read_name(dev), id); + continue; + } + + ret = uclass_find_device_by_ofnode(id, remote, remote_dev); + if (*remote_dev && !ret) { + ret = uclass_get_device_by_ofnode(id, remote, remote_dev); + if (ret) + dev_dbg(dev, "%s(%s): failed to get remote device %s\n", + __func__, dev_read_name(dev), dev_read_name(*remote_dev)); + break; + } + }; + + return ret; +} + +static int stm32_ltdc_get_panel(struct udevice *dev, struct udevice **panel) +{ + ofnode ep_node, node, ports; + int ret = 0; + + if (!dev) + return -EINVAL; + + ports = ofnode_find_subnode(dev_ofnode(dev), "ports"); + if (!ofnode_valid(ports)) { + dev_err(dev, "Remote bridge subnode\n"); + return ret; + } + + for (node = ofnode_first_subnode(ports); + ofnode_valid(node); + node = dev_read_next_subnode(node)) { + ep_node = ofnode_first_subnode(node); + if (!ofnode_valid(ep_node)) + continue; + + ret = stm32_ltdc_get_remote_device(dev, ep_node, UCLASS_PANEL, panel); + } + + /* Sanity check, we can get out of the loop without having a clean ofnode */ + if (!(*panel)) + ret = -EINVAL; + else + if (!ofnode_valid(dev_ofnode(*panel))) + ret = -EINVAL; + + return ret; +} + +static int stm32_ltdc_display_init(struct udevice *dev, ofnode *ep_node, + struct udevice **panel, struct udevice **bridge) +{ + int ret; + + if (*panel) + return -EINVAL; + + if (IS_ENABLED(CONFIG_VIDEO_BRIDGE)) { + ret = stm32_ltdc_get_remote_device(dev, *ep_node, UCLASS_VIDEO_BRIDGE, bridge); + if (ret) + return ret; + + ret = stm32_ltdc_get_panel(*bridge, panel); + } else { + /* no bridge, search a panel from display controller node */ + ret = stm32_ltdc_get_remote_device(dev, *ep_node, UCLASS_PANEL, panel); + } + + return ret; +} + #if IS_ENABLED(CONFIG_TARGET_STM32F469_DISCOVERY) static int stm32_ltdc_alloc_fb(struct udevice *dev) { @@ -532,6 +628,7 @@ static int stm32_ltdc_probe(struct udevice *dev) struct display_timing timings; struct clk pclk, bclk; struct reset_ctl rst; + ofnode node, port; ulong rate; int ret; @@ -568,7 +665,7 @@ static int stm32_ltdc_probe(struct udevice *dev) } priv->hw_version = readl(priv->regs + LTDC_IDR); - debug("%s: LTDC hardware 0x%x\n", __func__, priv->hw_version); + dev_dbg(dev, "%s: LTDC hardware 0x%x\n", __func__, priv->hw_version); switch (priv->hw_version) { case HWVER_10200: @@ -589,13 +686,35 @@ static int stm32_ltdc_probe(struct udevice *dev) return -ENODEV; } - ret = uclass_first_device_err(UCLASS_PANEL, &panel); - if (ret) { - if (ret != -ENODEV) - dev_err(dev, "panel device error %d\n", ret); - return ret; + /* + * Try all the ports until one working. + * + * This is done in two times. First is checks for the + * UCLASS_VIDEO_BRIDGE available, and then for this bridge + * it scans for a UCLASS_PANEL. + */ + + port = dev_read_subnode(dev, "port"); + if (!ofnode_valid(port)) { + dev_err(dev, "%s(%s): 'port' subnode not found\n", + __func__, dev_read_name(dev)); + return -EINVAL; } + for (node = ofnode_first_subnode(port); + ofnode_valid(node); + node = dev_read_next_subnode(node)) { + ret = stm32_ltdc_display_init(dev, &node, &panel, &bridge); + if (ret) + dev_dbg(dev, "Device failed ret=%d\n", ret); + else + break; + } + + /* Sanity check */ + if (ret) + return ret; + ret = panel_get_display_timing(panel, &timings); if (ret) { ret = ofnode_decode_display_timing(dev_ofnode(panel), @@ -624,11 +743,6 @@ static int stm32_ltdc_probe(struct udevice *dev) reset_deassert(&rst); if (IS_ENABLED(CONFIG_VIDEO_BRIDGE)) { - ret = uclass_get_device(UCLASS_VIDEO_BRIDGE, 0, &bridge); - if (ret) - dev_dbg(dev, - "No video bridge, or no backlight on bridge\n"); - if (bridge) { ret = video_bridge_attach(bridge); if (ret) { From 8876396fc7b4c95f295bb65cefcf3763b6a313ca Mon Sep 17 00:00:00 2001 From: Raphael Gallais-Pou Date: Thu, 4 Sep 2025 14:53:11 +0200 Subject: [PATCH 14/22] configs: stm32mp25: enable LVDS display support Compile VIDEO_STM32 and VIDEO_STM32_LVDS by default. Reviewed-by: Patrice Chotard Signed-off-by: Raphael Gallais-Pou Acked-by: Yannick Fertre --- configs/stm32mp25_defconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/configs/stm32mp25_defconfig b/configs/stm32mp25_defconfig index a5ee02a09b4..61cee2f354e 100644 --- a/configs/stm32mp25_defconfig +++ b/configs/stm32mp25_defconfig @@ -91,6 +91,9 @@ CONFIG_SPI=y CONFIG_DM_SPI=y CONFIG_STM32_OSPI=y # CONFIG_OPTEE_TA_AVB is not set +CONFIG_VIDEO=y +CONFIG_VIDEO_STM32=y +CONFIG_VIDEO_STM32_LVDS=y CONFIG_WDT=y CONFIG_WDT_STM32MP=y CONFIG_WDT_ARM_SMC=y From 16da0356414a789dd910d4597ebc80b968aae03c Mon Sep 17 00:00:00 2001 From: Asadeds Date: Tue, 28 Oct 2025 11:09:26 +0530 Subject: [PATCH 15/22] arm: stm32mp25: add ethernet support for stm32mp255 series Add missing CPU_STM32MP255* cases in get_eth_nb() so that U-Boot correctly reports 2 Ethernet interfaces on stm32mp255 devices. This fixes the "ethernet not found" error during boot. Signed-off-by: Md Asadullah --- arch/arm/mach-stm32mp/stm32mp2/stm32mp25x.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/arm/mach-stm32mp/stm32mp2/stm32mp25x.c b/arch/arm/mach-stm32mp/stm32mp2/stm32mp25x.c index 12b43ea5cdf..bf1f3d3c5a7 100644 --- a/arch/arm/mach-stm32mp/stm32mp2/stm32mp25x.c +++ b/arch/arm/mach-stm32mp/stm32mp2/stm32mp25x.c @@ -77,6 +77,14 @@ int get_eth_nb(void) case CPU_STM32MP257Axx: nb_eth = 5; /* dual ETH with TSN support */ break; + case CPU_STM32MP255Fxx: + fallthrough; + case CPU_STM32MP255Dxx: + fallthrough; + case CPU_STM32MP255Cxx: + fallthrough; + case CPU_STM32MP255Axx: + fallthrough; case CPU_STM32MP253Fxx: fallthrough; case CPU_STM32MP253Dxx: From 9853527d6c1d18d3ce592a8754c1f8f8bddfef18 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 23 Oct 2025 23:46:05 +0200 Subject: [PATCH 16/22] ARM: dts: stm32: Fix STM32MP15xx DHSOM boot breakage due to ETZPC Switch etzpc bus to simple-bus to prevent breakage on non-TFA systems. This fixes boot of every STM32MP15xx DHSOM device. Fixes: ad3cdc677dda ("ARM: stm32mp: add ETZPC system bus driver for STM32MP1") Signed-off-by: Marek Vasut Reviewed-by: Patrice Chotard --- arch/arm/dts/stm32mp15xx-dhsom-u-boot.dtsi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm/dts/stm32mp15xx-dhsom-u-boot.dtsi b/arch/arm/dts/stm32mp15xx-dhsom-u-boot.dtsi index 386c605c07f..ed2629f379a 100644 --- a/arch/arm/dts/stm32mp15xx-dhsom-u-boot.dtsi +++ b/arch/arm/dts/stm32mp15xx-dhsom-u-boot.dtsi @@ -51,3 +51,7 @@ }; }; }; + +&etzpc { + compatible = "simple-bus"; +}; From 68430f102d11da3184dd3783b7ef72fc7b1a8f57 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 23 Oct 2025 23:47:25 +0200 Subject: [PATCH 17/22] ARM: dts: stm32: Keep the reg11 and reg18 regulators always enabled on STM32MP13xx DHCOR Do not disable reg11 and reg18, disabling these regulators causes the SoC to hang. Signed-off-by: Marek Vasut Reviewed-by: Patrice Chotard --- arch/arm/dts/stm32mp13xx-dhcor-u-boot.dtsi | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/arm/dts/stm32mp13xx-dhcor-u-boot.dtsi b/arch/arm/dts/stm32mp13xx-dhcor-u-boot.dtsi index 699ba15d6ea..5ca0258e3ff 100644 --- a/arch/arm/dts/stm32mp13xx-dhcor-u-boot.dtsi +++ b/arch/arm/dts/stm32mp13xx-dhcor-u-boot.dtsi @@ -186,6 +186,14 @@ }; #endif +®11 { + regulator-always-on; +}; + +®18 { + regulator-always-on; +}; + &sdmmc1 { status = "disabled"; }; From f65ca70193c1e7426303075c046277864a00896b Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 23 Oct 2025 23:47:57 +0200 Subject: [PATCH 18/22] ARM: dts: stm32: Introduce DH STM32MP13x target Split the DH STM32MP13x based boards from ST STM32MP13x target, this way the DH board specific code can be reused for STM32MP13x DHSOM. Signed-off-by: Marek Vasut Reviewed-by: Patrice Chotard --- arch/arm/mach-stm32mp/Kconfig.13x | 12 ++++++++++ board/dhelectronics/dh_stm32mp1/Kconfig | 23 +++++++++++++++++++ board/dhelectronics/dh_stm32mp1/Makefile | 3 ++- board/dhelectronics/dh_stm32mp1/board.c | 28 ++++++++++++++++++++++++ configs/stm32mp13_dhcor_defconfig | 2 +- 5 files changed, 66 insertions(+), 2 deletions(-) diff --git a/arch/arm/mach-stm32mp/Kconfig.13x b/arch/arm/mach-stm32mp/Kconfig.13x index 6a45c4e4132..e9697e34f95 100644 --- a/arch/arm/mach-stm32mp/Kconfig.13x +++ b/arch/arm/mach-stm32mp/Kconfig.13x @@ -17,6 +17,17 @@ config TARGET_ST_STM32MP13X managed by board/st/stm32mp1. The difference between board are managed with devicetree +config TARGET_DH_STM32MP13X + bool "DH electronics STM32MP13x boards" + imply BOOTSTAGE + imply CMD_BOOTSTAGE + imply CMD_CLS if CMD_BMP + imply DISABLE_CONSOLE + imply PRE_CONSOLE_BUFFER + imply SILENT_CONSOLE + help + Target the DH DHSBC development kit with STM32MP13x SoM. + endchoice config TEXT_BASE @@ -50,6 +61,7 @@ config DEBUG_UART_CLOCK default 64000000 endif +source "board/dhelectronics/dh_stm32mp1/Kconfig" source "board/st/stm32mp1/Kconfig" endif diff --git a/board/dhelectronics/dh_stm32mp1/Kconfig b/board/dhelectronics/dh_stm32mp1/Kconfig index dc707c2753f..05cb97b61e6 100644 --- a/board/dhelectronics/dh_stm32mp1/Kconfig +++ b/board/dhelectronics/dh_stm32mp1/Kconfig @@ -20,3 +20,26 @@ config ENV_OFFSET_REDUND source "board/st/common/Kconfig" endif + +if TARGET_DH_STM32MP13X + +config SYS_BOARD + default "dh_stm32mp1" + +config SYS_VENDOR + default "dhelectronics" + +config SYS_CONFIG_NAME + default "stm32mp13_st_common" + +config ENV_SECT_SIZE + default 0x10000 if ENV_IS_IN_SPI_FLASH + +config ENV_OFFSET + default 0x3E0000 if ENV_IS_IN_SPI_FLASH + +config ENV_OFFSET_REDUND + default 0x3F0000 if ENV_IS_IN_SPI_FLASH + +source "board/st/common/Kconfig" +endif diff --git a/board/dhelectronics/dh_stm32mp1/Makefile b/board/dhelectronics/dh_stm32mp1/Makefile index 30db1dee807..ffd221ac224 100644 --- a/board/dhelectronics/dh_stm32mp1/Makefile +++ b/board/dhelectronics/dh_stm32mp1/Makefile @@ -3,6 +3,7 @@ # Copyright (C) 2018, STMicroelectronics - All Rights Reserved # -obj-y += ../../st/common/stpmic1.o board.o +obj-$(CONFIG_PMIC_STPMIC1) += ../../st/common/stpmic1.o +obj-y += board.o obj-$(CONFIG_SET_DFU_ALT_INFO) += ../../st/common/stm32mp_dfu.o diff --git a/board/dhelectronics/dh_stm32mp1/board.c b/board/dhelectronics/dh_stm32mp1/board.c index d98b2c6e809..a9b1a0f2c34 100644 --- a/board/dhelectronics/dh_stm32mp1/board.c +++ b/board/dhelectronics/dh_stm32mp1/board.c @@ -692,6 +692,34 @@ void board_quiesce_devices(void) #endif } +#ifdef CONFIG_TARGET_DH_STM32MP13X +enum env_location env_get_location(enum env_operation op, int prio) +{ + u32 bootmode = get_bootmode(); + + if (prio) + return ENVL_UNKNOWN; + + switch (bootmode & TAMP_BOOT_DEVICE_MASK) { + case BOOT_FLASH_SD: + case BOOT_FLASH_EMMC: + if (CONFIG_IS_ENABLED(ENV_IS_IN_MMC)) + return ENVL_MMC; + else + return ENVL_NOWHERE; + + case BOOT_FLASH_NOR: + if (CONFIG_IS_ENABLED(ENV_IS_IN_SPI_FLASH)) + return ENVL_SPI_FLASH; + else + return ENVL_NOWHERE; + + default: + return ENVL_NOWHERE; + } +} +#endif + static void dh_stm32_ks8851_fixup(void *blob) { struct gpio_desc ks8851intrn; diff --git a/configs/stm32mp13_dhcor_defconfig b/configs/stm32mp13_dhcor_defconfig index c21416459e6..f751deb801f 100644 --- a/configs/stm32mp13_dhcor_defconfig +++ b/configs/stm32mp13_dhcor_defconfig @@ -8,7 +8,7 @@ CONFIG_ENV_OFFSET=0x3E0000 CONFIG_DEFAULT_DEVICE_TREE="st/stm32mp135f-dhcor-dhsbc" CONFIG_STM32MP13X=y CONFIG_DDR_CACHEABLE_SIZE=0x8000000 -CONFIG_TARGET_ST_STM32MP13X=y +CONFIG_TARGET_DH_STM32MP13X=y CONFIG_ENV_OFFSET_REDUND=0x3F0000 CONFIG_STM32MP15_PWR=y CONFIG_ARMV7_NONSEC=y From da9e7637df05c62d59fcfdb2c72bfd92bd7da92b Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 31 Oct 2025 05:16:09 +0100 Subject: [PATCH 19/22] ARM: stm32: Add missing build of ST DFU virt code on DH STM32MP1 DHSOM Commit 6d91f0a3a14d ("board: st: common: cleanup dfu support") split the vendor-specific DFU implementation into two files, but failed to update other non-ST boards. This did not lead to noticeable breakage with plain simple dfu-util, but it makes the ST proprietary programmer CLI tool end in an infinite loop. Update the Makefile accordingly to allow even that kind of tooling to work. Fixes: 6d91f0a3a14d ("board: st: common: cleanup dfu support") Signed-off-by: Marek Vasut Reviewed-by: Patrice Chotard --- board/dhelectronics/dh_stm32mp1/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/board/dhelectronics/dh_stm32mp1/Makefile b/board/dhelectronics/dh_stm32mp1/Makefile index ffd221ac224..2f4a301d1a0 100644 --- a/board/dhelectronics/dh_stm32mp1/Makefile +++ b/board/dhelectronics/dh_stm32mp1/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_PMIC_STPMIC1) += ../../st/common/stpmic1.o obj-y += board.o obj-$(CONFIG_SET_DFU_ALT_INFO) += ../../st/common/stm32mp_dfu.o +obj-$(CONFIG_$(PHASE_)DFU_VIRT) += ../../st/common/stm32mp_dfu_virt.o From c5c5d8a4f847e22f6c6b22e6d4ff9abfd2509b00 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 23 Oct 2025 23:48:24 +0200 Subject: [PATCH 20/22] board: dhelectronics: Move dh_add_item_number_and_serial_to_env() to common code Move dh_add_item_number_and_serial_to_env() to common code, so it can be used by both STM32MP13xx and iMX8MP DHSOM. No functional change. Signed-off-by: Marek Vasut Reviewed-by: Patrice Chotard --- board/dhelectronics/common/dh_common.c | 37 +++++++++++++++++++ board/dhelectronics/common/dh_common.h | 9 +++++ .../dh_imx8mp/imx8mp_dhcom_pdk2.c | 37 ------------------- 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/board/dhelectronics/common/dh_common.c b/board/dhelectronics/common/dh_common.c index 6101ecc7ebc..e7ee23aa8ce 100644 --- a/board/dhelectronics/common/dh_common.c +++ b/board/dhelectronics/common/dh_common.c @@ -246,3 +246,40 @@ __weak int dh_setup_mac_address(struct eeprom_id_page *eip) printf("%s: Unable to set mac address!\n", __func__); return -ENXIO; } + +void dh_add_item_number_and_serial_to_env(struct eeprom_id_page *eip) +{ + char *item_number_env; + char item_number[8]; /* String with 7 characters + string termination */ + char *serial_env; + char serial[10]; /* String with 9 characters + string termination */ + int ret; + + ret = dh_get_value_from_eeprom_buffer(DH_ITEM_NUMBER, item_number, sizeof(item_number), + eip); + if (ret) { + printf("%s: Unable to get DHSOM item number from EEPROM ID page! ret = %d\n", + __func__, ret); + } else { + item_number_env = env_get("dh_som_item_number"); + if (!item_number_env) + env_set("dh_som_item_number", item_number); + else if (strcmp(item_number_env, item_number)) + printf("Warning: Environment dh_som_item_number differs from EEPROM ID page value (%s != %s)\n", + item_number_env, item_number); + } + + ret = dh_get_value_from_eeprom_buffer(DH_SERIAL_NUMBER, serial, sizeof(serial), + eip); + if (ret) { + printf("%s: Unable to get DHSOM serial number from EEPROM ID page! ret = %d\n", + __func__, ret); + } else { + serial_env = env_get("dh_som_serial_number"); + if (!serial_env) + env_set("dh_som_serial_number", serial); + else if (strcmp(serial_env, serial)) + printf("Warning: Environment dh_som_serial_number differs from EEPROM ID page value (%s != %s)\n", + serial_env, serial); + } +} diff --git a/board/dhelectronics/common/dh_common.h b/board/dhelectronics/common/dh_common.h index c4693c60618..b4f31bdb88e 100644 --- a/board/dhelectronics/common/dh_common.h +++ b/board/dhelectronics/common/dh_common.h @@ -107,7 +107,16 @@ int dh_get_value_from_eeprom_buffer(enum eip_request_values request, u8 *data, i /* * dh_setup_mac_address - Try to get MAC address from various locations and write it to env + * @eip: ID EEPROM buffer * * Return: 0 if OK, other value on error */ int dh_setup_mac_address(struct eeprom_id_page *eip); + +/* + * dh_add_item_number_and_serial_to_env - Try to get DH IDs from WLP write them to env + * @eip: ID EEPROM buffer + * + * Return: 0 if OK, other value on error + */ +void dh_add_item_number_and_serial_to_env(struct eeprom_id_page *eip); diff --git a/board/dhelectronics/dh_imx8mp/imx8mp_dhcom_pdk2.c b/board/dhelectronics/dh_imx8mp/imx8mp_dhcom_pdk2.c index 3a890c5920c..5c35a5bf447 100644 --- a/board/dhelectronics/dh_imx8mp/imx8mp_dhcom_pdk2.c +++ b/board/dhelectronics/dh_imx8mp/imx8mp_dhcom_pdk2.c @@ -116,43 +116,6 @@ int dh_setup_mac_address(struct eeprom_id_page *eip) return ret; } -void dh_add_item_number_and_serial_to_env(struct eeprom_id_page *eip) -{ - char *item_number_env; - char item_number[8]; /* String with 7 characters + string termination */ - char *serial_env; - char serial[10]; /* String with 9 characters + string termination */ - int ret; - - ret = dh_get_value_from_eeprom_buffer(DH_ITEM_NUMBER, item_number, sizeof(item_number), - eip); - if (ret) { - printf("%s: Unable to get DHSOM item number from EEPROM ID page! ret = %d\n", - __func__, ret); - } else { - item_number_env = env_get("dh_som_item_number"); - if (!item_number_env) - env_set("dh_som_item_number", item_number); - else if (strcmp(item_number_env, item_number)) - printf("Warning: Environment dh_som_item_number differs from EEPROM ID page value (%s != %s)\n", - item_number_env, item_number); - } - - ret = dh_get_value_from_eeprom_buffer(DH_SERIAL_NUMBER, serial, sizeof(serial), - eip); - if (ret) { - printf("%s: Unable to get DHSOM serial number from EEPROM ID page! ret = %d\n", - __func__, ret); - } else { - serial_env = env_get("dh_som_serial_number"); - if (!serial_env) - env_set("dh_som_serial_number", serial); - else if (strcmp(serial_env, serial)) - printf("Warning: Environment dh_som_serial_number differs from EEPROM ID page value (%s != %s)\n", - serial_env, serial); - } -} - int board_late_init(void) { u8 eeprom_buffer[DH_EEPROM_ID_PAGE_MAX_SIZE] = { 0 }; From f37f0dc8e9b56c1566d654eed3ab169e3d7ab955 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 23 Oct 2025 23:48:25 +0200 Subject: [PATCH 21/22] ARM: stm32: Read values from M24256 write-lockable page on STM32MP13xx DHCOR The STM32MP13xx DHCOR SoM is populated with M24256 EEPROM that contains an additional write-lockable page called ID page, which is populated with a structure containing ethernet MAC addresses, DH item number and DH serial number. Read out the MAC address from the WL page between higher priority SoC fuses and lower priority plain EEPROM storage area. Read out the DH item and serial numbers and set environment variables accordingly. Signed-off-by: Marek Vasut Reviewed-by: Patrice Chotard --- arch/arm/dts/stm32mp13xx-dhcor-u-boot.dtsi | 1 + board/dhelectronics/dh_stm32mp1/board.c | 51 ++++++++++++++++++---- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/arch/arm/dts/stm32mp13xx-dhcor-u-boot.dtsi b/arch/arm/dts/stm32mp13xx-dhcor-u-boot.dtsi index 5ca0258e3ff..bedb7c600d5 100644 --- a/arch/arm/dts/stm32mp13xx-dhcor-u-boot.dtsi +++ b/arch/arm/dts/stm32mp13xx-dhcor-u-boot.dtsi @@ -10,6 +10,7 @@ / { aliases { eeprom0 = &eeprom0; + eeprom0wl = &eeprom0wl; }; config { diff --git a/board/dhelectronics/dh_stm32mp1/board.c b/board/dhelectronics/dh_stm32mp1/board.c index a9b1a0f2c34..065d2f338c2 100644 --- a/board/dhelectronics/dh_stm32mp1/board.c +++ b/board/dhelectronics/dh_stm32mp1/board.c @@ -119,7 +119,7 @@ static bool dh_stm32_mac_is_in_ks8851(void) return false; } -static int dh_stm32_setup_ethaddr(void) +static int dh_stm32_setup_ethaddr(struct eeprom_id_page *eip) { unsigned char enetaddr[6]; @@ -129,13 +129,19 @@ static int dh_stm32_setup_ethaddr(void) if (dh_get_mac_is_enabled("ethernet0")) return 0; + if (!dh_get_value_from_eeprom_buffer(DH_MAC0, enetaddr, sizeof(enetaddr), eip)) + goto out; + if (!dh_get_mac_from_eeprom(enetaddr, "eeprom0")) - return eth_env_set_enetaddr("ethaddr", enetaddr); + goto out; return -ENXIO; + +out: + return eth_env_set_enetaddr("ethaddr", enetaddr); } -static int dh_stm32_setup_eth1addr(void) +static int dh_stm32_setup_eth1addr(struct eeprom_id_page *eip) { unsigned char enetaddr[6]; @@ -148,20 +154,47 @@ static int dh_stm32_setup_eth1addr(void) if (dh_stm32_mac_is_in_ks8851()) return 0; - if (!dh_get_mac_from_eeprom(enetaddr, "eeprom0")) { - enetaddr[5]++; - return eth_env_set_enetaddr("eth1addr", enetaddr); - } + if (!dh_get_value_from_eeprom_buffer(DH_MAC1, enetaddr, sizeof(enetaddr), eip)) + goto out; + + if (!dh_get_mac_from_eeprom(enetaddr, "eeprom0")) + goto increment_out; return -ENXIO; + +increment_out: + enetaddr[5]++; + +out: + return eth_env_set_enetaddr("eth1addr", enetaddr); } int setup_mac_address(void) { - if (dh_stm32_setup_ethaddr()) + u8 eeprom_buffer[DH_EEPROM_ID_PAGE_MAX_SIZE] = { 0 }; + struct eeprom_id_page *eip = (struct eeprom_id_page *)eeprom_buffer; + int ret; + + ret = dh_read_eeprom_id_page(eeprom_buffer, "eeprom0wl"); + if (ret) { + /* + * The EEPROM ID page is available on SoM rev. 200 and greater. + * For SoM rev. 100 the return value will be -ENODEV. Suppress + * the error message for that, because the absence cannot be + * treated as an error. + */ + if (ret != -ENODEV) + printf("%s: Cannot read valid data from EEPROM ID page! ret = %d\n", + __func__, ret); + eip = NULL; + } else { + dh_add_item_number_and_serial_to_env(eip); + } + + if (dh_stm32_setup_ethaddr(eip)) log_err("%s: Unable to setup ethaddr!\n", __func__); - if (dh_stm32_setup_eth1addr()) + if (dh_stm32_setup_eth1addr(eip)) log_err("%s: Unable to setup eth1addr!\n", __func__); return 0; From 96b66742a99ac22f812536ff89efda90f7f09d8c Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 23 Oct 2025 23:48:26 +0200 Subject: [PATCH 22/22] ARM: stm32: Add MAC address readout from fuses on DH STM32MP1 DHSOM Add support for reading out the MAC address from SoC fuses on DH STM32MP1 DHSOM. The DH STM32MP1 DHSOM may contain external ethernet MACs, which benefit from the MAC address stored in SoC fuses as well, handle those consistently. This however means the architecture setup_mac_address() cannot be used and instead a simpler local fuse read out is implemented in the board file. Signed-off-by: Marek Vasut Reviewed-by: Patrice Chotard --- board/dhelectronics/dh_stm32mp1/board.c | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/board/dhelectronics/dh_stm32mp1/board.c b/board/dhelectronics/dh_stm32mp1/board.c index 065d2f338c2..c18f1911fe4 100644 --- a/board/dhelectronics/dh_stm32mp1/board.c +++ b/board/dhelectronics/dh_stm32mp1/board.c @@ -119,6 +119,29 @@ static bool dh_stm32_mac_is_in_ks8851(void) return false; } +static int dh_stm32_get_mac_from_fuse(unsigned char *enetaddr, int index) +{ + struct udevice *dev; + u8 otp[12]; + int ret; + + ret = uclass_get_device_by_driver(UCLASS_MISC, + DM_DRIVER_GET(stm32mp_bsec), + &dev); + if (ret) + return ret; + + ret = misc_read(dev, STM32_BSEC_SHADOW(BSEC_OTP_MAC), otp, sizeof(otp)); + if (ret < 0) + return ret; + + memcpy(enetaddr, otp + ARP_HLEN * index, ARP_HLEN); + if (!is_valid_ethaddr(enetaddr)) + return -EINVAL; + + return 0; +} + static int dh_stm32_setup_ethaddr(struct eeprom_id_page *eip) { unsigned char enetaddr[6]; @@ -129,6 +152,9 @@ static int dh_stm32_setup_ethaddr(struct eeprom_id_page *eip) if (dh_get_mac_is_enabled("ethernet0")) return 0; + if (!dh_stm32_get_mac_from_fuse(enetaddr, 0)) + goto out; + if (!dh_get_value_from_eeprom_buffer(DH_MAC0, enetaddr, sizeof(enetaddr), eip)) goto out; @@ -154,6 +180,9 @@ static int dh_stm32_setup_eth1addr(struct eeprom_id_page *eip) if (dh_stm32_mac_is_in_ks8851()) return 0; + if (!dh_stm32_get_mac_from_fuse(enetaddr, 1)) + goto out; + if (!dh_get_value_from_eeprom_buffer(DH_MAC1, enetaddr, sizeof(enetaddr), eip)) goto out;