On 8/20/25 18:17, Raphael Gallais-Pou wrote:
> 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.
> 
> Signed-off-by: Raphael Gallais-Pou <raphael.gallais-...@foss.st.com>
> ---
>  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 | 673 
> +++++++++++++++++++++++++++++++++++++++
>  5 files changed, 685 insertions(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 
> 3fb163aa1db1dba249e60b40ab3e785db4fde5d3..604ba4ca04c1079b14cd8f4c1fff4c12da0e278d
>  100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -728,6 +728,7 @@ F:        drivers/serial/serial_stm32.*
>  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 
> 2a285c81807ee26924948d8c2497f11dfe45b6e6..28cada97e72da711408a1aaaa928ad3faa51162e
>  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 
> c354c402c288b4c004bd2b5cc74a4d00beef2773..4cb8a841caf51aaba5115b9fcddb967f73a3d9b4
>  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 
> f8b42d1a4d126a31aa11ce820e829b133ca3bb2d..059d9000c1d7762d06bff36d5408143a34848227
>  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 
> 0000000000000000000000000000000000000000..1e3e740122dfee0c947819532da64f0d99327bde
> --- /dev/null
> +++ b/drivers/video/stm32/stm32_lvds.c
> @@ -0,0 +1,673 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2025 STMicroelectronics - All Rights Reserved
> + * Author(s): Raphaël Gallais-Pou <raphael.gallais-...@foss.st.com> 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 <clk.h>
> +#include <dm.h>
> +#include <log.h>
> +#include <media_bus_format.h>
> +#include <panel.h>
> +#include <reset.h>
> +#include <video.h>
> +#include <video_bridge.h>
> +#include <asm/io.h>
> +#include <dm/device_compat.h>
> +#include <dm/ofnode.h>
> +#include <linux/iopoll.h>
> +
> +/* 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   */

All define must be in uppercase, you are mixing lower/upper case which is not 
encouraged.
Same remark below .

> +#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 PHY_GCR_BIT_CLK_OUT  BIT(0)
> +#define PHY_GCR_LS_CLK_OUT   BIT(4)
> +#define PHY_GCR_DP_CLK_OUT   BIT(8)
> +#define PHY_GCR_RSTZ         BIT(24)
> +#define PHY_GCR_DIV_RSTN     BIT(25)
> +
> +#define PHY_PxPLLTESTCR_TDIV GENMASK(25, 16)
> +#define PHY_PxPLLCR2_NDIV    GENMASK(25, 16)
> +#define PHY_PxPLLCR2_BDIV    GENMASK(9, 0)
> +#define PHY_PxPLLSDCR1_MDIV  GENMASK(9, 0)
> +
> +#define PLL_EN               BIT(0)
> +#define PLL_LOCK     BIT(0)
> +#define CM_EN_DL     (BIT(28) | BIT(20) | BIT(12) | BIT(4))
> +#define CM_EN_DL4    BIT(4)
> +#define VM_EN_DL     (BIT(16) | BIT(12) | BIT(8) | BIT(4) | BIT(0))
> +#define EN_BIAS_DL   (BIT(16) | BIT(12) | BIT(8) | BIT(4) | BIT(0))
> +#define EN_DIG_DL    GENMASK(4, 0)
> +#define BIAS_EN              BIT(28)
> +#define POWER_OK     BIT(12)
> +
> +#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
> +
> +struct stm32_lvds {
> +     void __iomem *base;
> +     struct udevice *panel;
> +     u32 refclk;
> +     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(struct stm32_lvds *lvds, u32 reg, u32 val)
> +{
> +     writel(val, lvds->base + reg);
> +}
> +
> +static inline u32 lvds_readl(struct stm32_lvds *lvds, u32 reg)
> +{
> +     return readl(lvds->base + reg);
> +}
> +
> +static inline void lvds_set(struct stm32_lvds *lvds, u32 reg, u32 mask)
> +{
> +     lvds_writel(lvds, reg, lvds_readl(lvds, reg) | mask);
> +}
> +
> +static inline void lvds_clear(struct stm32_lvds *lvds, u32 reg, u32 mask)
> +{
> +     lvds_writel(lvds, reg, lvds_readl(lvds, reg) & ~mask);
> +}
> +
> +/* Integer mode */
> +#define EN_SD                0
> +#define EN_TWG               0
> +#define DOWN_SPREAD  0
> +#define TEST_DIV     70

Put these defines with the others, above first function. 

> +
> +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;
> +}
> +
> +#define NDIV_MIN     2
> +#define NDIV_MAX     6
> +#define BDIV_MIN     2
> +#define BDIV_MAX     6
> +#define MDIV_MIN     1
> +#define MDIV_MAX     1023

ditto

> +
> +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)

clkin_khz and clkout_khz are unsigned, so can't be negative. 

if (clkin_khz = 0 || clkout_khz = 0)  should be engouh 

> +             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 stm32_lvds *lvds,
> +                              struct display_timing *timings,
> +                              int phy)
> +{
> +     u32 pll_in_khz, bdiv = 0, mdiv = 0, ndiv = 0;
> +     int ret, val, multiplier;
> +
> +     /* Release PHY from reset */
> +     lvds_set(lvds, LVDS_PxGCR(phy), PHY_GCR_DIV_RSTN | PHY_GCR_RSTZ);
> +
> +     /* lvds_pll_config */
> +     /* Set PLL Slv & Mst configs and timings */
> +     pll_in_khz = lvds->refclk / 1000;
> +
> +     if (lvds->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(lvds, LVDS_PxPLLCR2(phy), (ndiv << 16) | bdiv);
> +     lvds_writel(lvds, LVDS_PxPLLSDCR1(phy), mdiv);
> +     lvds_writel(lvds, LVDS_PxPLLTESTCR(phy), TEST_DIV << 16);
> +
> +     /* Disable TWG and SD: for now, PLL just need to be in integer mode */
> +     lvds_clear(lvds, LVDS_PxPLLCR1(phy), EN_TWG | EN_SD);
> +
> +     /* Power up bias and PLL dividers */
> +     lvds_set(lvds, LVDS_PxDCR(phy), POWER_OK);
> +
> +     lvds_set(lvds, LVDS_PxCMCR1(phy), CM_EN_DL);
> +     lvds_set(lvds, LVDS_PxCMCR2(phy), CM_EN_DL4);
> +
> +     lvds_set(lvds, LVDS_PxPLLCPCR(phy), 0x1);

Add a define to replace 0x1, for example #defince 
PLL_CHARGE_PUMP_CONTROL_DEFAULT 0x1 ?

> +     lvds_set(lvds, LVDS_PxBCR3(phy), VM_EN_DL);
> +     lvds_set(lvds, LVDS_PxBCR1(phy), EN_BIAS_DL);
> +     lvds_set(lvds, LVDS_PxCFGCR(phy), 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(lvds, LVDS_PxMPLCR(phy), (0x200 - 0x160) << 16);

What are 0x200 and 0x160 ? add a define with a meaningful name for each of 
these value.

> +
> +     lvds_writel(lvds, LVDS_PxBCR2(phy), BIAS_EN);
> +
> +     lvds_set(lvds, LVDS_PxGCR(phy),
> +              PHY_GCR_DP_CLK_OUT | PHY_GCR_LS_CLK_OUT | PHY_GCR_BIT_CLK_OUT);
> +
> +     /* TODO hardcoded values for now */
> +     lvds_set(lvds, LVDS_PxPLLTESTCR(phy), BIT(8) /* PLL_TEST_DIV_EN */);

Why hardcoded value for now ? simply add a define PLL_TEST_DIV_EN BIT(8) ?

> +     lvds_set(lvds, LVDS_PxPLLCR1(phy), BIT(8) /* PLL_DIVIDERS_ENABLE */);

ditto

> +
> +     lvds_set(lvds, LVDS_PxSCR(phy), BIT(16) /* SER_DATA_OK */);

ditto

> +
> +     /* Enable the LVDS PLL & wait for its lock */
> +     lvds_set(lvds, LVDS_PxPLLCR1(phy), PLL_EN);
> +     ret = readl_poll_sleep_timeout(lvds->base + LVDS_PxPLLSR(phy),
> +                                    val, val & 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(lvds, LVDS_WCLKCR, WCLKCR_SLV_CLKPIX_SEL);
> +
> +     lvds_set(lvds, LVDS_PxPLLTESTCR(phy), BIT(0));

add #define PLL_TEST_CLK_EN BIT(0) instead ?

lvds_set(lvds, LVDS_PxPLLTESTCR(phy), PLL_TEST_CLK_EN);


> +
> +     return 0;
> +}
> +
> +static int stm32_lvds_enable(struct udevice *dev,
> +                          const struct display_timing *timings)
> +{
> +     struct stm32_lvds *lvds = dev_get_priv(dev);
> +     u32 lvds_cdl1cr = 0;
> +     u32 lvds_cdl2cr = 0;
> +     u32 lvds_dmlcr = 0;
> +     u32 lvds_dmmcr = 0;
> +     u32 lvds_cr = 0;
> +     int i;
> +
> +     lvds_clear(lvds, LVDS_CDL1CR, CDLCRx_DISTR0 | CDLCRx_DISTR1 | 
> CDLCRx_DISTR2
> +                                     | CDLCRx_DISTR3 | CDLCRx_DISTR4);
> +     lvds_clear(lvds, LVDS_CDL2CR, CDLCRx_DISTR0 | CDLCRx_DISTR1 | 
> CDLCRx_DISTR2
> +                                     | CDLCRx_DISTR3 | CDLCRx_DISTR4);
> +
> +     /* Set channel distribution */
> +     lvds_cr &= ~CR_LKMOD;
> +     lvds_cdl1cr = CDL1CR_DEFAULT;
> +
> +     if (lvds->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 (lvds->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 (lvds->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(lvds, LVDS_DMLCR(i), lvds_dmlcr);
> +                     lvds_writel(lvds, 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(lvds, LVDS_DMLCR(i), lvds_dmlcr);
> +                     lvds_writel(lvds, LVDS_DMMCR(i), lvds_dmmcr);
> +             }
> +             break;
> +     default:
> +             dev_dbg(dev, "Unsupported LVDS bus format 0x%04x\n", 
> lvds->bus_format);
> +     }
> +
> +     /* Turn the output on */
> +     lvds_cr |= CR_LVDSEN;
> +
> +     /* Commit config to registers */
> +     lvds_set(lvds, LVDS_CR, lvds_cr);
> +     lvds_writel(lvds, LVDS_CDL1CR, lvds_cdl1cr);
> +     lvds_writel(lvds, LVDS_CDL2CR, lvds_cdl2cr);
> +
> +     return 0;
> +}
> +
> +static int stm32_lvds_attach(struct udevice *dev)
> +{
> +     struct stm32_lvds *lvds = dev_get_priv(dev);
> +     struct display_timing timings;
> +     int ret;
> +
> +     ret = panel_get_display_timing(lvds->panel, &timings);
> +     if (ret) {
> +             ret = ofnode_decode_display_timing(dev_ofnode(lvds->panel),
> +                                                0, &timings);
> +             if (ret) {
> +                     dev_err(dev, "decode display timing error %d\n", ret);
> +                     return ret;
> +             }
> +     }
> +
> +     ret = stm32_lvds_enable(dev, &timings);
> +
> +     return ret;
> +}
> +
> +static int stm32_lvds_set_backlight(struct udevice *dev, int percent)
> +{
> +     struct stm32_lvds *lvds = dev_get_priv(dev);
> +     int ret;
> +
> +     ret = panel_enable_backlight(lvds->panel);
> +     if (ret) {
> +             dev_err(dev, "panel %s enable backlight error %d\n",
> +                     lvds->panel->name, ret);
> +     }
> +
> +     return ret;
> +}
> +
> +static int lvds_handle_pixel_order(struct stm32_lvds *lvds)
> +{
> +     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(lvds->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_probe(struct udevice *dev)
> +{
> +     struct stm32_lvds *priv = dev_get_priv(dev);
> +     struct display_timing timings;
> +     struct reset_ctl rst;
> +     struct clk pclk, refclk;
> +     const char *data_mapping;
> +     int ret;
> +
> +     priv->base = dev_read_addr_ptr(dev);
> +     if ((fdt_addr_t)priv->base == FDT_ADDR_T_NONE) {
> +             dev_err(dev, "Unable to read LVDS base address\n");
> +             return -EINVAL;
> +     }
> +

Device tree resources reading must be located in an of_to_plat() function, not 
in probe().
See for example, see ./drivers/spi/stm32_spi.c

For reference : 
https://docs.u-boot.org/en/stable/develop/driver-model/design.html#reading-ofdata



> +     ret = clk_get_by_name(dev, "pclk", &pclk);
> +     if (ret) {
> +             dev_err(dev, "Unable to get peripheral clock: %d\n", ret);
> +             return ret;

ditto

> +     }
> +
> +     ret = clk_enable(&pclk);
> +     if (ret) {
> +             dev_err(dev, "Failed to enable peripheral clock: %d\n", ret);
> +             return ret;
> +     }
> +
> +     ret = clk_get_by_name(dev, "ref", &refclk);
> +     if (ret) {
> +             dev_err(dev, "Unable to get reference clock: %d\n", ret);
> +             goto err_clk;
> +     }

ditto

> +
> +     ret = clk_enable(&refclk);
> +     if (ret) {
> +             dev_err(dev, "Failed to enable reference clock: %d\n", ret);
> +             goto err_clk;
> +     }
> +
> +     priv->refclk = (unsigned int)clk_get_rate(&refclk);
> +
> +     ret = reset_get_by_index(dev, 0, &rst);
> +     if (ret) {
> +             dev_err(dev, "Failed to get LVDS reset: %d\n", ret);
> +             goto err_rst;
> +     }
> +

ditto

> +     reset_deassert(&rst);
> +
> +     ret = uclass_get_device_by_driver(UCLASS_PANEL,
> +                                       DM_DRIVER_GET(simple_panel), 
> &priv->panel);
> +     if (ret) {
> +             dev_err(dev, "panel device error %d\n", ret);
> +             goto err_rst;
> +     }

ditto
> +
> +     ret = panel_get_display_timing(priv->panel, &timings);
> +     if (ret) {
> +             ret = ofnode_decode_display_timing(dev_ofnode(priv->panel),
> +                                                0, &timings);
> +             if (ret) {
> +                     dev_err(dev, "decode display timing error %d\n", ret);
> +                     goto err_rst;
> +             }
> +     }
> +

ditto 

> +     data_mapping = ofnode_read_string(dev_ofnode(priv->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;

ditto

> +
> +     /* Handle dual link config */
> +     priv->dual_link = lvds_handle_pixel_order(priv);
> +     if (priv->dual_link < 0)
> +             goto err_rst;
> +
> +     if (priv->dual_link > 0) {
> +             ret = stm32_lvds_pll_enable(priv, &timings, LVDS_PHY_SLAVE);
> +             if (ret)
> +                     goto err_rst;
> +     }
> +
> +     ret = stm32_lvds_pll_enable(priv, &timings, LVDS_PHY_MASTER);
> +     if (ret)
> +             goto err_rst;
> +
> +     return 0;
> +
> +err_rst:
> +     clk_disable(&refclk);
> +err_clk:
> +     clk_disable(&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,
> +     .probe          = stm32_lvds_probe,
> +     .priv_auto      = sizeof(struct stm32_lvds),
> +};
> 

Reply via email to