[PATCH v3 3/7] video: stm32: STM32 driver support for LVDS

Patrice CHOTARD patrice.chotard at foss.st.com
Fri Nov 14 17:48:52 CET 2025



On 10/30/25 08:42, Yannick FERTRE wrote:
> Hi Raphael,
> 
> Thanks for the patch.
> 
> Acked-by: Yannick Fertre<yannick.fertre at foss.st.com>
> 
> Le 04/09/2025 à 14:53, Raphael Gallais-Pou a écrit :
>> 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 <yannick.fertre at foss.st.com>
>> Signed-off-by: Raphael Gallais-Pou <raphael.gallais-pou at 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 | 693 +++++++++++++++++++++++++++++++++++++++
>>   5 files changed, 705 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..bf1393c9e8724ed099aeed63344ebdc0004507b6
>> --- /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 <raphael.gallais-pou at 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    */
>> +#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),
>> +};
>>
Applied to u-boot-stm32/master

Thanks
Patrice


More information about the U-Boot mailing list