* Re: [PATCH 3/3] drm: renesas: rz-du: Add support for RZ/G3L LVDS encoder
2026-04-17 17:52 ` [PATCH 3/3] drm: renesas: rz-du: Add support for " Biju
@ 2026-04-19 15:58 ` Dmitry Baryshkov
2026-04-21 9:11 ` Claudiu Beznea
2026-04-21 16:21 ` Biju Das
2026-04-20 7:31 ` Biju Das
2026-04-21 9:12 ` Claudiu Beznea
2 siblings, 2 replies; 15+ messages in thread
From: Dmitry Baryshkov @ 2026-04-19 15:58 UTC (permalink / raw)
To: Biju
Cc: Biju Das, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Philipp Zabel, Geert Uytterhoeven,
Magnus Damm, linux-kernel, dri-devel, linux-renesas-soc,
Prabhakar Mahadev Lad, Tommaso Merciai
On Fri, Apr 17, 2026 at 06:52:30PM +0100, Biju wrote:
> From: Biju Das <biju.das.jz@bp.renesas.com>
>
> Add support for the RZ/G3L LVDS encoder driver. It operates in single-link
> mode with 4 lanes (Data) + 1 lane (Clock) and supports pixel clock rates
> from 25 to 87 MHz. The LVDS module cannot be used at the same time as
> MIPI-DSI. However, LVDS and the DSI interface share a peripheral clock and
> the MIPI_DSI_PRESET_N reset signal. Also, the MIPI_DSI_CMN_RSTB and
> MIPI_DSI_ARESET_N reset signals must be asserted before using the LVDS
> module.
>
> Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> ---
> drivers/gpu/drm/renesas/rz-du/Kconfig | 13 +
> drivers/gpu/drm/renesas/rz-du/Makefile | 1 +
> drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c | 333 ++++++++++++++++++
> drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.h | 22 ++
> .../gpu/drm/renesas/rz-du/rzg3l_lvds_regs.h | 26 ++
> 5 files changed, 395 insertions(+)
> create mode 100644 drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c
> create mode 100644 drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.h
> create mode 100644 drivers/gpu/drm/renesas/rz-du/rzg3l_lvds_regs.h
>
> diff --git a/drivers/gpu/drm/renesas/rz-du/Kconfig b/drivers/gpu/drm/renesas/rz-du/Kconfig
> index 7f2ef7137ae5..cbfc7b6bccb8 100644
> --- a/drivers/gpu/drm/renesas/rz-du/Kconfig
> +++ b/drivers/gpu/drm/renesas/rz-du/Kconfig
> @@ -26,3 +26,16 @@ config DRM_RZG2L_MIPI_DSI
> def_tristate DRM_RZG2L_DU
> depends on DRM_RZG2L_USE_MIPI_DSI
> select DRM_MIPI_DSI
> +
> +config DRM_RZG3L_USE_LVDS
> + bool "RZ/G3L DU LVDS Encoder Support"
> + depends on DRM_BRIDGE && OF
> + default DRM_RZG2L_DU
> + help
> + Enable support for the RZ/G3L Display Unit embedded LVDS encoders.
> +
> +config DRM_RZG3L_LVDS
> + def_tristate DRM_RZG2L_DU
> + depends on DRM_RZG3L_USE_LVDS
> + select DRM_KMS_HELPER
> + select DRM_PANEL
> diff --git a/drivers/gpu/drm/renesas/rz-du/Makefile b/drivers/gpu/drm/renesas/rz-du/Makefile
> index 2987900ea6b6..46decb7ac4f1 100644
> --- a/drivers/gpu/drm/renesas/rz-du/Makefile
> +++ b/drivers/gpu/drm/renesas/rz-du/Makefile
> @@ -8,3 +8,4 @@ rzg2l-du-drm-$(CONFIG_VIDEO_RENESAS_VSP1) += rzg2l_du_vsp.o
> obj-$(CONFIG_DRM_RZG2L_DU) += rzg2l-du-drm.o
>
> obj-$(CONFIG_DRM_RZG2L_MIPI_DSI) += rzg2l_mipi_dsi.o
> +obj-$(CONFIG_DRM_RZG3L_LVDS) += rzg3l_lvds.o
> diff --git a/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c b/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c
> new file mode 100644
> index 000000000000..bedeedbdfada
> --- /dev/null
> +++ b/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c
> @@ -0,0 +1,333 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * RZ/G3L LVDS Encoder Driver
> + *
> + * Copyright (C) 2026 Renesas Electronics Corporation
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/media-bus-format.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_graph.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_panel.h>
> +#include <drm/drm_probe_helper.h>
> +
> +#include "rzg3l_lvds.h"
> +#include "rzg3l_lvds_regs.h"
> +
> +enum rzg3l_lvds_mode {
> + RZG3L_LVDS_MODE_JEIDA = 0,
> + RZG3L_LVDS_MODE_JEIDA_MIRROR = 1,
> + RZG3L_LVDS_MODE_MODE2 = 2,
> + RZG3L_LVDS_MODE_MODE2_MIRROR = 3,
> + RZG3L_LVDS_MODE_VESA = 4,
> + RZG3L_LVDS_MODE_VESA_MIRROR = 5,
> + RZG3L_LVDS_MODE_MODE6 = 6,
> + RZG3L_LVDS_MODE_MODE6_MIRROR = 7,
> +};
> +
> +struct rzg3l_lvds {
> + struct device *dev;
> + struct reset_control *prstc;
> + struct reset_control *lvd_rstc;
> + struct regmap *regmap;
> +
> + struct drm_bridge bridge;
> + struct drm_bridge *next_bridge;
Please use next_bridge from the drm_bridge struct.
> +};
> +
> +#define bridge_to_rzg3l_lvds(b) \
> + container_of(b, struct rzg3l_lvds, bridge)
> +
> +/* -----------------------------------------------------------------------------
> + * Bridge
> + */
> +static void rzg3l_lvds_atomic_enable(struct drm_bridge *bridge,
> + struct drm_atomic_state *state)
> +{
> + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> + const struct drm_bridge_state *bridge_state;
> + int ret;
> + u32 fmt;
> +
> + /* Get the LVDS format from the bridge state. */
> + bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
> + if (!bridge_state) {
> + dev_err(lvds->dev, "failed to get bridge state\n");
> + return;
> + }
> +
> + switch (bridge_state->output_bus_cfg.format) {
> + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
> + fmt = RZG3L_LVDS_MODE_JEIDA;
> + break;
> + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
> + fmt = RZG3L_LVDS_MODE_VESA;
> + break;
> + default:
> + fmt = RZG3L_LVDS_MODE_VESA;
> + dev_warn(lvds->dev, "Unsupported bus fmt 0x%04x\n",
> + bridge_state->output_bus_cfg.format);
> + break;
> + }
> +
> + ret = pm_runtime_resume_and_get(lvds->dev);
If this fails for any reason, the atomic_disable() would still be
called and it will decrement the counter, potentially undeflowing it.
Consider switching to pm_runtime_get_sync(), which suits better here.
> + if (ret < 0) {
> + dev_err(lvds->dev, "pm_runtime_resume_and_get error\n");
> + return;
> + }
> +
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_EN_BGR, LVDS_0_PHY_CH_EN_BGR);
> + usleep_range(20, 25);
> +
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_EN_LDO, LVDS_0_PHY_CH_EN_LDO);
> + usleep_range(10, 15);
> +
> + regmap_write(lvds->regmap, LVDS_CMN, LVDS_CMN_RST_PHY0_SEL);
> + regmap_update_bits(lvds->regmap, LVDS_0_CTL_OFFSET,
> + LVDS_0_CTL_FMT_SEL_MSK,
> + FIELD_PREP(LVDS_0_CTL_FMT_SEL_MSK, fmt));
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_IO_EN_MSK, LVDS_0_PHY_CH_IO_EN);
> + regmap_write(lvds->regmap, LVDS_CMN,
> + LVDS_CMN_RST_PHY0_SEL | LVDS_CMN_PHY_RESET);
> + usleep_range(100, 150);
> +}
> +
> +static void rzg3l_lvds_atomic_disable(struct drm_bridge *bridge,
> + struct drm_atomic_state *state)
> +{
> + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> +
> + regmap_update_bits(lvds->regmap, LVDS_CMN, LVDS_CMN_PHY_RESET, 0);
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_IO_EN_MSK, 0);
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_EN_LDO, 0);
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_EN_BGR, 0);
> +
> + pm_runtime_put(lvds->dev);
> +}
> +
> +static int rzg3l_lvds_attach(struct drm_bridge *bridge,
> + struct drm_encoder *encoder,
> + enum drm_bridge_attach_flags flags)
> +{
> + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> +
> + if (!lvds->next_bridge)
> + return 0;
> +
> + return drm_bridge_attach(encoder, lvds->next_bridge, bridge, flags);
> +}
> +
> +static enum drm_mode_status
> +rzg3l_lvds_bridge_mode_valid(struct drm_bridge *bridge,
> + const struct drm_display_info *info,
> + const struct drm_display_mode *mode)
> +{
> + if (mode->clock > 87000)
> + return MODE_CLOCK_HIGH;
> +
> + if (mode->clock < 25000)
> + return MODE_CLOCK_LOW;
> +
> + return MODE_OK;
> +}
> +
> +bool rzg3l_lvds_is_connected(struct drm_bridge *bridge)
> +{
> + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> +
> + return !!lvds->next_bridge;
> +}
> +EXPORT_SYMBOL_GPL(rzg3l_lvds_is_connected);
How is this going to be used? I don't see the user in the patch. Please
drop the unused API.
> +
> +static const struct drm_bridge_funcs rzg3l_lvds_bridge_ops = {
> + .attach = rzg3l_lvds_attach,
> + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
> + .atomic_reset = drm_atomic_helper_bridge_reset,
> + .atomic_enable = rzg3l_lvds_atomic_enable,
> + .atomic_disable = rzg3l_lvds_atomic_disable,
> + .mode_valid = rzg3l_lvds_bridge_mode_valid,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Power Management
> + */
> +
> +static int rzg3l_lvds_pm_runtime_suspend(struct device *dev)
> +{
> + struct rzg3l_lvds *lvds = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = reset_control_assert(lvds->lvd_rstc);
> + if (ret)
> + return ret;
> +
> + ret = reset_control_assert(lvds->prstc);
> + if (ret)
> + goto err_deassert_lvd_rstc;
> +
> + return 0;
> +
> +err_deassert_lvd_rstc:
> + reset_control_deassert(lvds->lvd_rstc);
> + return ret;
> +}
> +
> +static int rzg3l_lvds_pm_runtime_resume(struct device *dev)
> +{
> + struct rzg3l_lvds *lvds = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = reset_control_deassert(lvds->prstc);
> + if (ret)
> + return ret;
> +
> + return reset_control_deassert(lvds->lvd_rstc);
> + if (ret)
> + goto err_assert_prstc;
> +
> + return 0;
> +
> +err_assert_prstc:
> + reset_control_assert(lvds->prstc);
> + return ret;
> +}
> +
> +static const struct dev_pm_ops rzg3l_lvds_pm_ops = {
> + RUNTIME_PM_OPS(rzg3l_lvds_pm_runtime_suspend,
> + rzg3l_lvds_pm_runtime_resume, NULL)
> + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
> +};
DEFINE_RUNTIME_DEV_PM_OPS()
> +
> +/* -----------------------------------------------------------------------------
> + * Probe & Remove
> + */
> +
> +static int rzg3l_lvds_parse_dt(struct rzg3l_lvds *lvds)
> +{
> + struct device *dev = lvds->dev;
> + struct drm_panel *panel;
> + int ret;
> +
> + ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0,
> + &panel, &lvds->next_bridge);
> + if (ret)
> + return ret;
> +
> + if (panel) {
> + lvds->next_bridge = devm_drm_panel_bridge_add(dev, panel);
> + if (IS_ERR_OR_NULL(lvds->next_bridge))
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int rzg3l_lvds_probe(struct platform_device *pdev)
> +{
> + struct reset_control *rstc, *arstc;
> + struct device *dev = &pdev->dev;
> + struct rzg3l_lvds *lvds;
> + int ret;
> +
> + lvds = devm_drm_bridge_alloc(dev, struct rzg3l_lvds, bridge,
> + &rzg3l_lvds_bridge_ops);
> + if (IS_ERR(lvds))
> + return PTR_ERR(lvds);
> +
> + lvds->dev = dev;
> + lvds->bridge.of_node = pdev->dev.of_node;
> +
> + lvds->regmap = syscon_node_to_regmap(dev->of_node->parent);
> + if (IS_ERR(lvds->regmap))
> + return PTR_ERR(lvds->regmap);
> +
> + rstc = devm_reset_control_get_optional_exclusive(dev, "rst");
> + if (IS_ERR(rstc))
> + return dev_err_probe(dev, PTR_ERR(rstc), "failed to get rst\n");
> +
> + arstc = devm_reset_control_get_optional_exclusive(dev, "arst");
> + if (IS_ERR(arstc))
> + return dev_err_probe(dev, PTR_ERR(arstc),
> + "failed to get arst\n");
> +
> + lvds->prstc = devm_reset_control_get_shared(dev, "prst");
> + if (IS_ERR(lvds->prstc))
> + return dev_err_probe(dev, PTR_ERR(lvds->prstc),
> + "failed to get prst\n");
> +
> + lvds->lvd_rstc = devm_reset_control_get_shared(dev, "lvdrst");
> + if (IS_ERR(lvds->lvd_rstc))
> + return dev_err_probe(dev, PTR_ERR(lvds->lvd_rstc),
> + "failed to get core reset\n");
> +
> + platform_set_drvdata(pdev, lvds);
> +
> + ret = devm_pm_runtime_enable(dev);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to enable Runtime PM\n");
> +
> + ret = rzg3l_lvds_parse_dt(lvds);
> + if (ret < 0)
> + return ret;
> +
> + ret = reset_control_assert(rstc);
> + if (ret < 0)
> + return ret;
> +
> + ret = reset_control_assert(arstc);
> + if (ret < 0)
> + return ret;
> +
> + ret = devm_drm_bridge_add(dev, &lvds->bridge);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "Failed to register drm bridge\n");
> +
> + return ret;
> +}
> +
> +static const struct of_device_id rzg3l_lvds_of_table[] = {
> + { .compatible = "renesas,r9a08g046-lvds" },
> + { /* sentinel */ }
> +};
> +
> +MODULE_DEVICE_TABLE(of, rzg3l_lvds_of_table);
> +
> +static struct platform_driver rzg3l_lvds_platform_driver = {
> + .probe = rzg3l_lvds_probe,
> + .driver = {
> + .name = "rzg3l-lvds",
> + .pm = pm_ptr(&rzg3l_lvds_pm_ops),
> + .of_match_table = rzg3l_lvds_of_table,
> + },
> +};
> +
> +module_platform_driver(rzg3l_lvds_platform_driver);
> +
> +MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>");
> +MODULE_AUTHOR("Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>");
> +MODULE_DESCRIPTION("Renesas RZ/G3L LVDS Encoder Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.h b/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.h
> new file mode 100644
> index 000000000000..2ea3cf4e1cae
> --- /dev/null
> +++ b/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.h
> @@ -0,0 +1,22 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * RZ/G3L LVDS Encoder
> + *
> + * Copyright (C) 2026 Renesas Electronics Corporation
> + *
> + */
> +
> +#ifndef __RZG3L_LVDS_H__
> +#define __RZG3L_LVDS_H__
> +
> +struct drm_bridge;
> +
> +#if IS_ENABLED(CONFIG_DRM_RZG3L_LVDS)
> +bool rzg3l_lvds_is_connected(struct drm_bridge *bridge);
> +#else
> +static inline bool rzg3l_lvds_is_connected(struct drm_bridge *bridge)
> +{
> + return false;
> +}
> +#endif /* CONFIG_DRM_RZG3L_LVDS */
> +#endif /* __RZG3L_LVDS_H__ */
> diff --git a/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds_regs.h b/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds_regs.h
> new file mode 100644
> index 000000000000..281b7648f168
> --- /dev/null
> +++ b/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds_regs.h
> @@ -0,0 +1,26 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * RZ/G3L LVDS Interface Registers Definitions
> + *
> + * Copyright (C) 2026 Renesas Electronics Corporation
> + *
> + */
> +
> +#ifndef __RZG3L_LVDS_REGS_H__
> +#define __RZG3L_LVDS_REGS_H__
> +
> +#define LVDS_CMN 0x00
> +#define LVDS_CMN_RST_PHY0_SEL (1 << 24)
> +#define LVDS_CMN_RST_PHY0_SEL_CH0 (1 << 24)
> +#define LVDS_CMN_PHY_RESET (1 << 0)
> +
> +#define LVDS_0_PHY_OFFSET 0x10
> +#define LVDS_0_PHY_CH_IO_EN_MSK (0x1f)
> +#define LVDS_0_PHY_CH_IO_EN (LVDS_0_PHY_CH_IO_EN_MSK << 0)
> +#define LVDS_0_PHY_CH_EN_BGR BIT(8)
> +#define LVDS_0_PHY_CH_EN_LDO BIT(9)
> +
> +#define LVDS_0_CTL_OFFSET 0x14
> +#define LVDS_0_CTL_FMT_SEL_MSK GENMASK(23, 20)
> +
> +#endif /* __RZG3L_LVDS_REGS_H__ */
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH 3/3] drm: renesas: rz-du: Add support for RZ/G3L LVDS encoder
2026-04-19 15:58 ` Dmitry Baryshkov
@ 2026-04-21 9:11 ` Claudiu Beznea
2026-04-21 11:22 ` Dmitry Baryshkov
2026-04-21 16:21 ` Biju Das
1 sibling, 1 reply; 15+ messages in thread
From: Claudiu Beznea @ 2026-04-21 9:11 UTC (permalink / raw)
To: Dmitry Baryshkov, Biju
Cc: Biju Das, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Philipp Zabel, Geert Uytterhoeven,
Magnus Damm, linux-kernel, dri-devel, linux-renesas-soc,
Prabhakar Mahadev Lad, Tommaso Merciai
Hi,
On 4/19/26 18:58, Dmitry Baryshkov wrote:
> On Fri, Apr 17, 2026 at 06:52:30PM +0100, Biju wrote:
>> From: Biju Das <biju.das.jz@bp.renesas.com>
>>
>> Add support for the RZ/G3L LVDS encoder driver. It operates in single-link
>> mode with 4 lanes (Data) + 1 lane (Clock) and supports pixel clock rates
>> from 25 to 87 MHz. The LVDS module cannot be used at the same time as
>> MIPI-DSI. However, LVDS and the DSI interface share a peripheral clock and
>> the MIPI_DSI_PRESET_N reset signal. Also, the MIPI_DSI_CMN_RSTB and
>> MIPI_DSI_ARESET_N reset signals must be asserted before using the LVDS
>> module.
>>
>> Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
>> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
>> ---
[ ...]
>> +/* -----------------------------------------------------------------------------
>> + * Bridge
>> + */
>> +static void rzg3l_lvds_atomic_enable(struct drm_bridge *bridge,
>> + struct drm_atomic_state *state)
>> +{
>> + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
>> + const struct drm_bridge_state *bridge_state;
>> + int ret;
>> + u32 fmt;
>> +
>> + /* Get the LVDS format from the bridge state. */
>> + bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
>> + if (!bridge_state) {
>> + dev_err(lvds->dev, "failed to get bridge state\n");
>> + return;
>> + }
>> +
>> + switch (bridge_state->output_bus_cfg.format) {
>> + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
>> + fmt = RZG3L_LVDS_MODE_JEIDA;
>> + break;
>> + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
>> + fmt = RZG3L_LVDS_MODE_VESA;
>> + break;
>> + default:
>> + fmt = RZG3L_LVDS_MODE_VESA;
>> + dev_warn(lvds->dev, "Unsupported bus fmt 0x%04x\n",
>> + bridge_state->output_bus_cfg.format);
>> + break;
>> + }
>> +
>> + ret = pm_runtime_resume_and_get(lvds->dev);
>
> If this fails for any reason, the atomic_disable() would still be
> called and it will decrement the counter, potentially undeflowing it.
> Consider switching to pm_runtime_get_sync(), which suits better here.
AFAIK, the clocks of this HW blocks have MSTOP functionality. HW manual of
RZ/G3S [1] (should be the same for RZ/G3L as well) mentions the following in the
chapter 41.2.1. "If the master accesses a module that has the clock stopped and
the MSTOP bit set, a bus error will occur". [1]
MSTOP is set though the clock enable/disable APIs.
The clocks on RZ/G3L are part of clock power domains. If the
pm_runtime_resume_and_get() fails (or any runtime PM resume calls), the clocks
will be off and MSTOP set. In this case, calling atomic_disable() or any API
setting HW registers will lead to sync aborts.
Thank you,
Claudiu
[1]
https://renesasweb.s3.us-west-2.amazonaws.com/prod/docs/X014/X0141882/r01uh1014ej0120-rzg3s.pdf?response-content-disposition=inline%3B%20filename%3D%22r01uh1014ej0120-rzg3s.pdf%22&response-content-type=application%2Fpdf&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA3WXFIBJWSDVTHJX3%2F20260421%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260421T075408Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86700&X-Amz-Signature=435eb49a37a080094aecf291bc3dc2d5b7ee62d3de8075151082e9db6c6acbe7
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH 3/3] drm: renesas: rz-du: Add support for RZ/G3L LVDS encoder
2026-04-21 9:11 ` Claudiu Beznea
@ 2026-04-21 11:22 ` Dmitry Baryshkov
2026-04-22 8:55 ` Claudiu Beznea
0 siblings, 1 reply; 15+ messages in thread
From: Dmitry Baryshkov @ 2026-04-21 11:22 UTC (permalink / raw)
To: Claudiu Beznea
Cc: Biju, Biju Das, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Philipp Zabel,
Geert Uytterhoeven, Magnus Damm, linux-kernel, dri-devel,
linux-renesas-soc, Prabhakar Mahadev Lad, Tommaso Merciai
On Tue, Apr 21, 2026 at 12:11:28PM +0300, Claudiu Beznea wrote:
> Hi,
>
> On 4/19/26 18:58, Dmitry Baryshkov wrote:
> > On Fri, Apr 17, 2026 at 06:52:30PM +0100, Biju wrote:
> > > From: Biju Das <biju.das.jz@bp.renesas.com>
> > >
> > > Add support for the RZ/G3L LVDS encoder driver. It operates in single-link
> > > mode with 4 lanes (Data) + 1 lane (Clock) and supports pixel clock rates
> > > from 25 to 87 MHz. The LVDS module cannot be used at the same time as
> > > MIPI-DSI. However, LVDS and the DSI interface share a peripheral clock and
> > > the MIPI_DSI_PRESET_N reset signal. Also, the MIPI_DSI_CMN_RSTB and
> > > MIPI_DSI_ARESET_N reset signals must be asserted before using the LVDS
> > > module.
> > >
> > > Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> > > Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> > > ---
>
> [ ...]
>
> > > +/* -----------------------------------------------------------------------------
> > > + * Bridge
> > > + */
> > > +static void rzg3l_lvds_atomic_enable(struct drm_bridge *bridge,
> > > + struct drm_atomic_state *state)
> > > +{
> > > + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> > > + const struct drm_bridge_state *bridge_state;
> > > + int ret;
> > > + u32 fmt;
> > > +
> > > + /* Get the LVDS format from the bridge state. */
> > > + bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
> > > + if (!bridge_state) {
> > > + dev_err(lvds->dev, "failed to get bridge state\n");
> > > + return;
> > > + }
> > > +
> > > + switch (bridge_state->output_bus_cfg.format) {
> > > + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
> > > + fmt = RZG3L_LVDS_MODE_JEIDA;
> > > + break;
> > > + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
> > > + fmt = RZG3L_LVDS_MODE_VESA;
> > > + break;
> > > + default:
> > > + fmt = RZG3L_LVDS_MODE_VESA;
> > > + dev_warn(lvds->dev, "Unsupported bus fmt 0x%04x\n",
> > > + bridge_state->output_bus_cfg.format);
> > > + break;
> > > + }
> > > +
> > > + ret = pm_runtime_resume_and_get(lvds->dev);
> >
> > If this fails for any reason, the atomic_disable() would still be
> > called and it will decrement the counter, potentially undeflowing it.
> > Consider switching to pm_runtime_get_sync(), which suits better here.
>
> AFAIK, the clocks of this HW blocks have MSTOP functionality. HW manual of
> RZ/G3S [1] (should be the same for RZ/G3L as well) mentions the following in
> the chapter 41.2.1. "If the master accesses a module that has the clock
> stopped and the MSTOP bit set, a bus error will occur". [1]
> MSTOP is set though the clock enable/disable APIs.
>
> The clocks on RZ/G3L are part of clock power domains. If the
> pm_runtime_resume_and_get() fails (or any runtime PM resume calls), the
> clocks will be off and MSTOP set. In this case, calling atomic_disable() or
> any API setting HW registers will lead to sync aborts.
Then you've identified a bug in the code. The atomic_enable() doesn't
fail, so for each enable there always will be an atomic_disable() call.
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH 3/3] drm: renesas: rz-du: Add support for RZ/G3L LVDS encoder
2026-04-21 11:22 ` Dmitry Baryshkov
@ 2026-04-22 8:55 ` Claudiu Beznea
2026-04-22 9:09 ` Biju Das
2026-04-22 18:45 ` Dmitry Baryshkov
0 siblings, 2 replies; 15+ messages in thread
From: Claudiu Beznea @ 2026-04-22 8:55 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: Biju, Biju Das, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Philipp Zabel,
Geert Uytterhoeven, Magnus Damm, linux-kernel, dri-devel,
linux-renesas-soc, Prabhakar Mahadev Lad, Tommaso Merciai
On 4/21/26 14:22, Dmitry Baryshkov wrote:
> On Tue, Apr 21, 2026 at 12:11:28PM +0300, Claudiu Beznea wrote:
>> Hi,
>>
>> On 4/19/26 18:58, Dmitry Baryshkov wrote:
>>> On Fri, Apr 17, 2026 at 06:52:30PM +0100, Biju wrote:
>>>> From: Biju Das <biju.das.jz@bp.renesas.com>
>>>>
>>>> Add support for the RZ/G3L LVDS encoder driver. It operates in single-link
>>>> mode with 4 lanes (Data) + 1 lane (Clock) and supports pixel clock rates
>>>> from 25 to 87 MHz. The LVDS module cannot be used at the same time as
>>>> MIPI-DSI. However, LVDS and the DSI interface share a peripheral clock and
>>>> the MIPI_DSI_PRESET_N reset signal. Also, the MIPI_DSI_CMN_RSTB and
>>>> MIPI_DSI_ARESET_N reset signals must be asserted before using the LVDS
>>>> module.
>>>>
>>>> Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
>>>> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
>>>> ---
>>
>> [ ...]
>>
>>>> +/* -----------------------------------------------------------------------------
>>>> + * Bridge
>>>> + */
>>>> +static void rzg3l_lvds_atomic_enable(struct drm_bridge *bridge,
>>>> + struct drm_atomic_state *state)
>>>> +{
>>>> + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
>>>> + const struct drm_bridge_state *bridge_state;
>>>> + int ret;
>>>> + u32 fmt;
>>>> +
>>>> + /* Get the LVDS format from the bridge state. */
>>>> + bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
>>>> + if (!bridge_state) {
>>>> + dev_err(lvds->dev, "failed to get bridge state\n");
>>>> + return;
>>>> + }
>>>> +
>>>> + switch (bridge_state->output_bus_cfg.format) {
>>>> + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
>>>> + fmt = RZG3L_LVDS_MODE_JEIDA;
>>>> + break;
>>>> + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
>>>> + fmt = RZG3L_LVDS_MODE_VESA;
>>>> + break;
>>>> + default:
>>>> + fmt = RZG3L_LVDS_MODE_VESA;
>>>> + dev_warn(lvds->dev, "Unsupported bus fmt 0x%04x\n",
>>>> + bridge_state->output_bus_cfg.format);
>>>> + break;
>>>> + }
>>>> +
>>>> + ret = pm_runtime_resume_and_get(lvds->dev);
>>>
>>> If this fails for any reason, the atomic_disable() would still be
>>> called and it will decrement the counter, potentially undeflowing it.
>>> Consider switching to pm_runtime_get_sync(), which suits better here.
>>
>> AFAIK, the clocks of this HW blocks have MSTOP functionality. HW manual of
>> RZ/G3S [1] (should be the same for RZ/G3L as well) mentions the following in
>> the chapter 41.2.1. "If the master accesses a module that has the clock
>> stopped and the MSTOP bit set, a bus error will occur". [1]
>> MSTOP is set though the clock enable/disable APIs.
>>
>> The clocks on RZ/G3L are part of clock power domains. If the
>> pm_runtime_resume_and_get() fails (or any runtime PM resume calls), the
>> clocks will be off and MSTOP set. In this case, calling atomic_disable() or
>> any API setting HW registers will lead to sync aborts.
>
> Then you've identified a bug in the code. The atomic_enable() doesn't
> fail, so for each enable there always will be an atomic_disable() call.
>
Is this something that should be solved by individual drivers providing struct
drm_bridge_funcs to the upper layers or by the subsystem itself?
Accessing HW w/o its power being on (whatever power means here, e.g. clocks,
resets, regulators) seems odd and may lead to critical failures.
On some Renesas SoCs this used to work previously but it is not anymore with the
addition of the so called MSTOP functionality.
Thank you,
Claudiu
^ permalink raw reply [flat|nested] 15+ messages in thread* RE: [PATCH 3/3] drm: renesas: rz-du: Add support for RZ/G3L LVDS encoder
2026-04-22 8:55 ` Claudiu Beznea
@ 2026-04-22 9:09 ` Biju Das
2026-04-22 18:45 ` Dmitry Baryshkov
1 sibling, 0 replies; 15+ messages in thread
From: Biju Das @ 2026-04-22 9:09 UTC (permalink / raw)
To: Claudiu.Beznea, Dmitry Baryshkov
Cc: biju.das.au, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Philipp Zabel, Geert Uytterhoeven,
magnus.damm, linux-kernel, dri-devel, linux-renesas-soc,
Prabhakar Mahadev Lad, Tommaso Merciai
> -----Original Message-----
> From: Claudiu Beznea <claudiu.beznea@tuxon.dev>
> Sent: 22 April 2026 09:56
> Subject: Re: [PATCH 3/3] drm: renesas: rz-du: Add support for RZ/G3L LVDS encoder
>
>
>
> On 4/21/26 14:22, Dmitry Baryshkov wrote:
> > On Tue, Apr 21, 2026 at 12:11:28PM +0300, Claudiu Beznea wrote:
> >> Hi,
> >>
> >> On 4/19/26 18:58, Dmitry Baryshkov wrote:
> >>> On Fri, Apr 17, 2026 at 06:52:30PM +0100, Biju wrote:
> >>>> From: Biju Das <biju.das.jz@bp.renesas.com>
> >>>>
> >>>> Add support for the RZ/G3L LVDS encoder driver. It operates in
> >>>> single-link mode with 4 lanes (Data) + 1 lane (Clock) and supports
> >>>> pixel clock rates from 25 to 87 MHz. The LVDS module cannot be used
> >>>> at the same time as MIPI-DSI. However, LVDS and the DSI interface
> >>>> share a peripheral clock and the MIPI_DSI_PRESET_N reset signal.
> >>>> Also, the MIPI_DSI_CMN_RSTB and MIPI_DSI_ARESET_N reset signals
> >>>> must be asserted before using the LVDS module.
> >>>>
> >>>> Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> >>>> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> >>>> ---
> >>
> >> [ ...]
> >>
> >>>> +/*
> >>>> +------------------------------------------------------------------
> >>>> +-----------
> >>>> + * Bridge
> >>>> + */
> >>>> +static void rzg3l_lvds_atomic_enable(struct drm_bridge *bridge,
> >>>> + struct drm_atomic_state *state) {
> >>>> + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> >>>> + const struct drm_bridge_state *bridge_state;
> >>>> + int ret;
> >>>> + u32 fmt;
> >>>> +
> >>>> + /* Get the LVDS format from the bridge state. */
> >>>> + bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
> >>>> + if (!bridge_state) {
> >>>> + dev_err(lvds->dev, "failed to get bridge state\n");
> >>>> + return;
> >>>> + }
> >>>> +
> >>>> + switch (bridge_state->output_bus_cfg.format) {
> >>>> + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
> >>>> + fmt = RZG3L_LVDS_MODE_JEIDA;
> >>>> + break;
> >>>> + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
> >>>> + fmt = RZG3L_LVDS_MODE_VESA;
> >>>> + break;
> >>>> + default:
> >>>> + fmt = RZG3L_LVDS_MODE_VESA;
> >>>> + dev_warn(lvds->dev, "Unsupported bus fmt 0x%04x\n",
> >>>> + bridge_state->output_bus_cfg.format);
> >>>> + break;
> >>>> + }
> >>>> +
> >>>> + ret = pm_runtime_resume_and_get(lvds->dev);
> >>>
> >>> If this fails for any reason, the atomic_disable() would still be
> >>> called and it will decrement the counter, potentially undeflowing it.
> >>> Consider switching to pm_runtime_get_sync(), which suits better here.
> >>
> >> AFAIK, the clocks of this HW blocks have MSTOP functionality. HW
> >> manual of RZ/G3S [1] (should be the same for RZ/G3L as well) mentions
> >> the following in the chapter 41.2.1. "If the master accesses a module
> >> that has the clock stopped and the MSTOP bit set, a bus error will
> >> occur". [1] MSTOP is set though the clock enable/disable APIs.
> >>
> >> The clocks on RZ/G3L are part of clock power domains. If the
> >> pm_runtime_resume_and_get() fails (or any runtime PM resume calls),
> >> the clocks will be off and MSTOP set. In this case, calling
> >> atomic_disable() or any API setting HW registers will lead to sync aborts.
> >
> > Then you've identified a bug in the code. The atomic_enable() doesn't
> > fail, so for each enable there always will be an atomic_disable() call.
> >
>
> Is this something that should be solved by individual drivers providing struct drm_bridge_funcs to the
> upper layers or by the subsystem itself?
This use case is like system suspend/resume right.
For each system resume call, there will be a system suspend call in future.
Cheers,
Biju
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH 3/3] drm: renesas: rz-du: Add support for RZ/G3L LVDS encoder
2026-04-22 8:55 ` Claudiu Beznea
2026-04-22 9:09 ` Biju Das
@ 2026-04-22 18:45 ` Dmitry Baryshkov
1 sibling, 0 replies; 15+ messages in thread
From: Dmitry Baryshkov @ 2026-04-22 18:45 UTC (permalink / raw)
To: Claudiu Beznea
Cc: Biju, Biju Das, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Philipp Zabel,
Geert Uytterhoeven, Magnus Damm, linux-kernel, dri-devel,
linux-renesas-soc, Prabhakar Mahadev Lad, Tommaso Merciai
On Wed, Apr 22, 2026 at 11:55:37AM +0300, Claudiu Beznea wrote:
>
>
> On 4/21/26 14:22, Dmitry Baryshkov wrote:
> > On Tue, Apr 21, 2026 at 12:11:28PM +0300, Claudiu Beznea wrote:
> > > Hi,
> > >
> > > On 4/19/26 18:58, Dmitry Baryshkov wrote:
> > > > On Fri, Apr 17, 2026 at 06:52:30PM +0100, Biju wrote:
> > > > > From: Biju Das <biju.das.jz@bp.renesas.com>
> > > > >
> > > > > Add support for the RZ/G3L LVDS encoder driver. It operates in single-link
> > > > > mode with 4 lanes (Data) + 1 lane (Clock) and supports pixel clock rates
> > > > > from 25 to 87 MHz. The LVDS module cannot be used at the same time as
> > > > > MIPI-DSI. However, LVDS and the DSI interface share a peripheral clock and
> > > > > the MIPI_DSI_PRESET_N reset signal. Also, the MIPI_DSI_CMN_RSTB and
> > > > > MIPI_DSI_ARESET_N reset signals must be asserted before using the LVDS
> > > > > module.
> > > > >
> > > > > Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> > > > > Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> > > > > ---
> > >
> > > [ ...]
> > >
> > > > > +/* -----------------------------------------------------------------------------
> > > > > + * Bridge
> > > > > + */
> > > > > +static void rzg3l_lvds_atomic_enable(struct drm_bridge *bridge,
> > > > > + struct drm_atomic_state *state)
> > > > > +{
> > > > > + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> > > > > + const struct drm_bridge_state *bridge_state;
> > > > > + int ret;
> > > > > + u32 fmt;
> > > > > +
> > > > > + /* Get the LVDS format from the bridge state. */
> > > > > + bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
> > > > > + if (!bridge_state) {
> > > > > + dev_err(lvds->dev, "failed to get bridge state\n");
> > > > > + return;
> > > > > + }
> > > > > +
> > > > > + switch (bridge_state->output_bus_cfg.format) {
> > > > > + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
> > > > > + fmt = RZG3L_LVDS_MODE_JEIDA;
> > > > > + break;
> > > > > + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
> > > > > + fmt = RZG3L_LVDS_MODE_VESA;
> > > > > + break;
> > > > > + default:
> > > > > + fmt = RZG3L_LVDS_MODE_VESA;
> > > > > + dev_warn(lvds->dev, "Unsupported bus fmt 0x%04x\n",
> > > > > + bridge_state->output_bus_cfg.format);
> > > > > + break;
> > > > > + }
> > > > > +
> > > > > + ret = pm_runtime_resume_and_get(lvds->dev);
> > > >
> > > > If this fails for any reason, the atomic_disable() would still be
> > > > called and it will decrement the counter, potentially undeflowing it.
> > > > Consider switching to pm_runtime_get_sync(), which suits better here.
> > >
> > > AFAIK, the clocks of this HW blocks have MSTOP functionality. HW manual of
> > > RZ/G3S [1] (should be the same for RZ/G3L as well) mentions the following in
> > > the chapter 41.2.1. "If the master accesses a module that has the clock
> > > stopped and the MSTOP bit set, a bus error will occur". [1]
> > > MSTOP is set though the clock enable/disable APIs.
> > >
> > > The clocks on RZ/G3L are part of clock power domains. If the
> > > pm_runtime_resume_and_get() fails (or any runtime PM resume calls), the
> > > clocks will be off and MSTOP set. In this case, calling atomic_disable() or
> > > any API setting HW registers will lead to sync aborts.
> >
> > Then you've identified a bug in the code. The atomic_enable() doesn't
> > fail, so for each enable there always will be an atomic_disable() call.
> >
>
> Is this something that should be solved by individual drivers providing
> struct drm_bridge_funcs to the upper layers or by the subsystem itself?
>
> Accessing HW w/o its power being on (whatever power means here, e.g. clocks,
> resets, regulators) seems odd and may lead to critical failures.
The major problem is that atomic_enable() can't fail for the design
reasons. So, yes, currently this needs to be handled by the driver which
calls pm_resume in the atomic_enable() path.
>
> On some Renesas SoCs this used to work previously but it is not anymore with
> the addition of the so called MSTOP functionality.
>
> Thank you,
> Claudiu
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 15+ messages in thread
* RE: [PATCH 3/3] drm: renesas: rz-du: Add support for RZ/G3L LVDS encoder
2026-04-19 15:58 ` Dmitry Baryshkov
2026-04-21 9:11 ` Claudiu Beznea
@ 2026-04-21 16:21 ` Biju Das
1 sibling, 0 replies; 15+ messages in thread
From: Biju Das @ 2026-04-21 16:21 UTC (permalink / raw)
To: Dmitry Baryshkov, biju.das.au
Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Philipp Zabel, Geert Uytterhoeven,
magnus.damm, linux-kernel, dri-devel, linux-renesas-soc,
Prabhakar Mahadev Lad, Tommaso Merciai
Hi Dmitry Baryshkov,
Thanks for the feedback.
> -----Original Message-----
> From: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
> Sent: 19 April 2026 16:59
> Subject: Re: [PATCH 3/3] drm: renesas: rz-du: Add support for RZ/G3L LVDS encoder
>
> On Fri, Apr 17, 2026 at 06:52:30PM +0100, Biju wrote:
> > From: Biju Das <biju.das.jz@bp.renesas.com>
> >
> > Add support for the RZ/G3L LVDS encoder driver. It operates in
> > single-link mode with 4 lanes (Data) + 1 lane (Clock) and supports
> > pixel clock rates from 25 to 87 MHz. The LVDS module cannot be used at
> > the same time as MIPI-DSI. However, LVDS and the DSI interface share a
> > peripheral clock and the MIPI_DSI_PRESET_N reset signal. Also, the
> > MIPI_DSI_CMN_RSTB and MIPI_DSI_ARESET_N reset signals must be asserted
> > before using the LVDS module.
> >
> > Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> > Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> > ---
> > drivers/gpu/drm/renesas/rz-du/Kconfig | 13 +
> > drivers/gpu/drm/renesas/rz-du/Makefile | 1 +
> > drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c | 333 ++++++++++++++++++
> > drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.h | 22 ++
> > .../gpu/drm/renesas/rz-du/rzg3l_lvds_regs.h | 26 ++
> > 5 files changed, 395 insertions(+)
> > create mode 100644 drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c
> > create mode 100644 drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.h
> > create mode 100644 drivers/gpu/drm/renesas/rz-du/rzg3l_lvds_regs.h
> >
> > diff --git a/drivers/gpu/drm/renesas/rz-du/Kconfig
> > b/drivers/gpu/drm/renesas/rz-du/Kconfig
> > index 7f2ef7137ae5..cbfc7b6bccb8 100644
> > --- a/drivers/gpu/drm/renesas/rz-du/Kconfig
> > +++ b/drivers/gpu/drm/renesas/rz-du/Kconfig
> > @@ -26,3 +26,16 @@ config DRM_RZG2L_MIPI_DSI
> > def_tristate DRM_RZG2L_DU
> > depends on DRM_RZG2L_USE_MIPI_DSI
> > select DRM_MIPI_DSI
> > +
> > +config DRM_RZG3L_USE_LVDS
> > + bool "RZ/G3L DU LVDS Encoder Support"
> > + depends on DRM_BRIDGE && OF
> > + default DRM_RZG2L_DU
> > + help
> > + Enable support for the RZ/G3L Display Unit embedded LVDS encoders.
> > +
> > +config DRM_RZG3L_LVDS
> > + def_tristate DRM_RZG2L_DU
> > + depends on DRM_RZG3L_USE_LVDS
> > + select DRM_KMS_HELPER
> > + select DRM_PANEL
> > diff --git a/drivers/gpu/drm/renesas/rz-du/Makefile
> > b/drivers/gpu/drm/renesas/rz-du/Makefile
> > index 2987900ea6b6..46decb7ac4f1 100644
> > --- a/drivers/gpu/drm/renesas/rz-du/Makefile
> > +++ b/drivers/gpu/drm/renesas/rz-du/Makefile
> > @@ -8,3 +8,4 @@ rzg2l-du-drm-$(CONFIG_VIDEO_RENESAS_VSP1) += rzg2l_du_vsp.o
> > obj-$(CONFIG_DRM_RZG2L_DU) += rzg2l-du-drm.o
> >
> > obj-$(CONFIG_DRM_RZG2L_MIPI_DSI) += rzg2l_mipi_dsi.o
> > +obj-$(CONFIG_DRM_RZG3L_LVDS) += rzg3l_lvds.o
> > diff --git a/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c
> > b/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c
> > new file mode 100644
> > index 000000000000..bedeedbdfada
> > --- /dev/null
> > +++ b/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c
> > @@ -0,0 +1,333 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * RZ/G3L LVDS Encoder Driver
> > + *
> > + * Copyright (C) 2026 Renesas Electronics Corporation */
> > +
> > +#include <linux/bitfield.h>
> > +#include <linux/clk.h>
> > +#include <linux/delay.h>
> > +#include <linux/io.h>
> > +#include <linux/media-bus-format.h>
> > +#include <linux/mfd/syscon.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/of_device.h>
> > +#include <linux/of_graph.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/regmap.h>
> > +#include <linux/reset.h>
> > +
> > +#include <drm/drm_atomic.h>
> > +#include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_bridge.h>
> > +#include <drm/drm_of.h>
> > +#include <drm/drm_panel.h>
> > +#include <drm/drm_probe_helper.h>
> > +
> > +#include "rzg3l_lvds.h"
> > +#include "rzg3l_lvds_regs.h"
> > +
> > +enum rzg3l_lvds_mode {
> > + RZG3L_LVDS_MODE_JEIDA = 0,
> > + RZG3L_LVDS_MODE_JEIDA_MIRROR = 1,
> > + RZG3L_LVDS_MODE_MODE2 = 2,
> > + RZG3L_LVDS_MODE_MODE2_MIRROR = 3,
> > + RZG3L_LVDS_MODE_VESA = 4,
> > + RZG3L_LVDS_MODE_VESA_MIRROR = 5,
> > + RZG3L_LVDS_MODE_MODE6 = 6,
> > + RZG3L_LVDS_MODE_MODE6_MIRROR = 7,
> > +};
> > +
> > +struct rzg3l_lvds {
> > + struct device *dev;
> > + struct reset_control *prstc;
> > + struct reset_control *lvd_rstc;
> > + struct regmap *regmap;
> > +
> > + struct drm_bridge bridge;
> > + struct drm_bridge *next_bridge;
>
> Please use next_bridge from the drm_bridge struct.
OK.
>
> > +};
> > +
> > +#define bridge_to_rzg3l_lvds(b) \
> > + container_of(b, struct rzg3l_lvds, bridge)
> > +
> > +/*
> > +---------------------------------------------------------------------
> > +--------
> > + * Bridge
> > + */
> > +static void rzg3l_lvds_atomic_enable(struct drm_bridge *bridge,
> > + struct drm_atomic_state *state) {
> > + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> > + const struct drm_bridge_state *bridge_state;
> > + int ret;
> > + u32 fmt;
> > +
> > + /* Get the LVDS format from the bridge state. */
> > + bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
> > + if (!bridge_state) {
> > + dev_err(lvds->dev, "failed to get bridge state\n");
> > + return;
> > + }
> > +
> > + switch (bridge_state->output_bus_cfg.format) {
> > + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
> > + fmt = RZG3L_LVDS_MODE_JEIDA;
> > + break;
> > + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
> > + fmt = RZG3L_LVDS_MODE_VESA;
> > + break;
> > + default:
> > + fmt = RZG3L_LVDS_MODE_VESA;
> > + dev_warn(lvds->dev, "Unsupported bus fmt 0x%04x\n",
> > + bridge_state->output_bus_cfg.format);
> > + break;
> > + }
> > +
> > + ret = pm_runtime_resume_and_get(lvds->dev);
>
> If this fails for any reason, the atomic_disable() would still be called and it will decrement the
> counter, potentially undeflowing it.
> Consider switching to pm_runtime_get_sync(), which suits better here.
Agreed.
>
>
> > + if (ret < 0) {
> > + dev_err(lvds->dev, "pm_runtime_resume_and_get error\n");
> > + return;
> > + }
> > +
> > + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> > + LVDS_0_PHY_CH_EN_BGR, LVDS_0_PHY_CH_EN_BGR);
> > + usleep_range(20, 25);
> > +
> > + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> > + LVDS_0_PHY_CH_EN_LDO, LVDS_0_PHY_CH_EN_LDO);
> > + usleep_range(10, 15);
> > +
> > + regmap_write(lvds->regmap, LVDS_CMN, LVDS_CMN_RST_PHY0_SEL);
> > + regmap_update_bits(lvds->regmap, LVDS_0_CTL_OFFSET,
> > + LVDS_0_CTL_FMT_SEL_MSK,
> > + FIELD_PREP(LVDS_0_CTL_FMT_SEL_MSK, fmt));
> > + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> > + LVDS_0_PHY_CH_IO_EN_MSK, LVDS_0_PHY_CH_IO_EN);
> > + regmap_write(lvds->regmap, LVDS_CMN,
> > + LVDS_CMN_RST_PHY0_SEL | LVDS_CMN_PHY_RESET);
> > + usleep_range(100, 150);
> > +}
> > +
> > +static void rzg3l_lvds_atomic_disable(struct drm_bridge *bridge,
> > + struct drm_atomic_state *state) {
> > + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> > +
> > + regmap_update_bits(lvds->regmap, LVDS_CMN, LVDS_CMN_PHY_RESET, 0);
> > + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> > + LVDS_0_PHY_CH_IO_EN_MSK, 0);
> > + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> > + LVDS_0_PHY_CH_EN_LDO, 0);
> > + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> > + LVDS_0_PHY_CH_EN_BGR, 0);
> > +
> > + pm_runtime_put(lvds->dev);
> > +}
> > +
> > +static int rzg3l_lvds_attach(struct drm_bridge *bridge,
> > + struct drm_encoder *encoder,
> > + enum drm_bridge_attach_flags flags) {
> > + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> > +
> > + if (!lvds->next_bridge)
> > + return 0;
> > +
> > + return drm_bridge_attach(encoder, lvds->next_bridge, bridge, flags);
> > +}
> > +
> > +static enum drm_mode_status
> > +rzg3l_lvds_bridge_mode_valid(struct drm_bridge *bridge,
> > + const struct drm_display_info *info,
> > + const struct drm_display_mode *mode) {
> > + if (mode->clock > 87000)
> > + return MODE_CLOCK_HIGH;
> > +
> > + if (mode->clock < 25000)
> > + return MODE_CLOCK_LOW;
> > +
> > + return MODE_OK;
> > +}
> > +
> > +bool rzg3l_lvds_is_connected(struct drm_bridge *bridge) {
> > + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> > +
> > + return !!lvds->next_bridge;
> > +}
> > +EXPORT_SYMBOL_GPL(rzg3l_lvds_is_connected);
>
> How is this going to be used? I don't see the user in the patch. Please drop the unused API.
OK, will drop this patch as it is not required for this platform.
>
> > +
> > +static const struct drm_bridge_funcs rzg3l_lvds_bridge_ops = {
> > + .attach = rzg3l_lvds_attach,
> > + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
> > + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
> > + .atomic_reset = drm_atomic_helper_bridge_reset,
> > + .atomic_enable = rzg3l_lvds_atomic_enable,
> > + .atomic_disable = rzg3l_lvds_atomic_disable,
> > + .mode_valid = rzg3l_lvds_bridge_mode_valid, };
> > +
> > +/*
> > +---------------------------------------------------------------------
> > +--------
> > + * Power Management
> > + */
> > +
> > +static int rzg3l_lvds_pm_runtime_suspend(struct device *dev) {
> > + struct rzg3l_lvds *lvds = dev_get_drvdata(dev);
> > + int ret;
> > +
> > + ret = reset_control_assert(lvds->lvd_rstc);
> > + if (ret)
> > + return ret;
> > +
> > + ret = reset_control_assert(lvds->prstc);
> > + if (ret)
> > + goto err_deassert_lvd_rstc;
> > +
> > + return 0;
> > +
> > +err_deassert_lvd_rstc:
> > + reset_control_deassert(lvds->lvd_rstc);
> > + return ret;
> > +}
> > +
> > +static int rzg3l_lvds_pm_runtime_resume(struct device *dev) {
> > + struct rzg3l_lvds *lvds = dev_get_drvdata(dev);
> > + int ret;
> > +
> > + ret = reset_control_deassert(lvds->prstc);
> > + if (ret)
> > + return ret;
> > +
> > + return reset_control_deassert(lvds->lvd_rstc);
> > + if (ret)
> > + goto err_assert_prstc;
> > +
> > + return 0;
> > +
> > +err_assert_prstc:
> > + reset_control_assert(lvds->prstc);
> > + return ret;
> > +}
> > +
> > +static const struct dev_pm_ops rzg3l_lvds_pm_ops = {
> > + RUNTIME_PM_OPS(rzg3l_lvds_pm_runtime_suspend,
> > + rzg3l_lvds_pm_runtime_resume, NULL)
> > + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> > +pm_runtime_force_resume) };
>
> DEFINE_RUNTIME_DEV_PM_OPS()
OK. Will send v2 with the above changes.
Cheers,
Biju
^ permalink raw reply [flat|nested] 15+ messages in thread
* RE: [PATCH 3/3] drm: renesas: rz-du: Add support for RZ/G3L LVDS encoder
2026-04-17 17:52 ` [PATCH 3/3] drm: renesas: rz-du: Add support for " Biju
2026-04-19 15:58 ` Dmitry Baryshkov
@ 2026-04-20 7:31 ` Biju Das
2026-04-21 9:12 ` Claudiu Beznea
2 siblings, 0 replies; 15+ messages in thread
From: Biju Das @ 2026-04-20 7:31 UTC (permalink / raw)
To: biju.das.au, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Philipp Zabel, Geert Uytterhoeven,
magnus.damm
Cc: linux-kernel, dri-devel, linux-renesas-soc,
Prabhakar Mahadev Lad, biju.das.au, Tommaso Merciai
Hi all,
> -----Original Message-----
> From: Biju <biju.das.au@gmail.com>
> Sent: 17 April 2026 18:53
> Subject: [PATCH 3/3] drm: renesas: rz-du: Add support for RZ/G3L LVDS encoder
>
> From: Biju Das <biju.das.jz@bp.renesas.com>
>
> Add support for the RZ/G3L LVDS encoder driver. It operates in single-link mode with 4 lanes (Data) + 1
> lane (Clock) and supports pixel clock rates from 25 to 87 MHz. The LVDS module cannot be used at the
> same time as MIPI-DSI. However, LVDS and the DSI interface share a peripheral clock and the
> MIPI_DSI_PRESET_N reset signal. Also, the MIPI_DSI_CMN_RSTB and MIPI_DSI_ARESET_N reset signals must be
> asserted before using the LVDS module.
>
> Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> ---
> drivers/gpu/drm/renesas/rz-du/Kconfig | 13 +
> drivers/gpu/drm/renesas/rz-du/Makefile | 1 +
> drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c | 333 ++++++++++++++++++
> drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.h | 22 ++
> .../gpu/drm/renesas/rz-du/rzg3l_lvds_regs.h | 26 ++
> 5 files changed, 395 insertions(+)
> create mode 100644 drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c
> create mode 100644 drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.h
> create mode 100644 drivers/gpu/drm/renesas/rz-du/rzg3l_lvds_regs.h
>
> diff --git a/drivers/gpu/drm/renesas/rz-du/Kconfig b/drivers/gpu/drm/renesas/rz-du/Kconfig
> index 7f2ef7137ae5..cbfc7b6bccb8 100644
> --- a/drivers/gpu/drm/renesas/rz-du/Kconfig
> +++ b/drivers/gpu/drm/renesas/rz-du/Kconfig
> @@ -26,3 +26,16 @@ config DRM_RZG2L_MIPI_DSI
> def_tristate DRM_RZG2L_DU
> depends on DRM_RZG2L_USE_MIPI_DSI
> select DRM_MIPI_DSI
> +
> +config DRM_RZG3L_USE_LVDS
> + bool "RZ/G3L DU LVDS Encoder Support"
> + depends on DRM_BRIDGE && OF
> + default DRM_RZG2L_DU
> + help
> + Enable support for the RZ/G3L Display Unit embedded LVDS encoders.
> +
> +config DRM_RZG3L_LVDS
> + def_tristate DRM_RZG2L_DU
> + depends on DRM_RZG3L_USE_LVDS
> + select DRM_KMS_HELPER
> + select DRM_PANEL
> diff --git a/drivers/gpu/drm/renesas/rz-du/Makefile b/drivers/gpu/drm/renesas/rz-du/Makefile
> index 2987900ea6b6..46decb7ac4f1 100644
> --- a/drivers/gpu/drm/renesas/rz-du/Makefile
> +++ b/drivers/gpu/drm/renesas/rz-du/Makefile
> @@ -8,3 +8,4 @@ rzg2l-du-drm-$(CONFIG_VIDEO_RENESAS_VSP1) += rzg2l_du_vsp.o
> obj-$(CONFIG_DRM_RZG2L_DU) += rzg2l-du-drm.o
>
> obj-$(CONFIG_DRM_RZG2L_MIPI_DSI) += rzg2l_mipi_dsi.o
> +obj-$(CONFIG_DRM_RZG3L_LVDS) += rzg3l_lvds.o
> diff --git a/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c b/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c
> new file mode 100644
> index 000000000000..bedeedbdfada
> --- /dev/null
> +++ b/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c
> @@ -0,0 +1,333 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * RZ/G3L LVDS Encoder Driver
> + *
> + * Copyright (C) 2026 Renesas Electronics Corporation */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/media-bus-format.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_graph.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_panel.h>
> +#include <drm/drm_probe_helper.h>
> +
> +#include "rzg3l_lvds.h"
> +#include "rzg3l_lvds_regs.h"
> +
> +enum rzg3l_lvds_mode {
> + RZG3L_LVDS_MODE_JEIDA = 0,
> + RZG3L_LVDS_MODE_JEIDA_MIRROR = 1,
> + RZG3L_LVDS_MODE_MODE2 = 2,
> + RZG3L_LVDS_MODE_MODE2_MIRROR = 3,
> + RZG3L_LVDS_MODE_VESA = 4,
> + RZG3L_LVDS_MODE_VESA_MIRROR = 5,
> + RZG3L_LVDS_MODE_MODE6 = 6,
> + RZG3L_LVDS_MODE_MODE6_MIRROR = 7,
> +};
> +
> +struct rzg3l_lvds {
> + struct device *dev;
> + struct reset_control *prstc;
> + struct reset_control *lvd_rstc;
> + struct regmap *regmap;
> +
> + struct drm_bridge bridge;
> + struct drm_bridge *next_bridge;
> +};
> +
> +#define bridge_to_rzg3l_lvds(b) \
> + container_of(b, struct rzg3l_lvds, bridge)
> +
> +/*
> +-----------------------------------------------------------------------
> +------
> + * Bridge
> + */
> +static void rzg3l_lvds_atomic_enable(struct drm_bridge *bridge,
> + struct drm_atomic_state *state) {
> + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> + const struct drm_bridge_state *bridge_state;
> + int ret;
> + u32 fmt;
> +
> + /* Get the LVDS format from the bridge state. */
> + bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
> + if (!bridge_state) {
> + dev_err(lvds->dev, "failed to get bridge state\n");
> + return;
> + }
> +
> + switch (bridge_state->output_bus_cfg.format) {
> + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
> + fmt = RZG3L_LVDS_MODE_JEIDA;
> + break;
> + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
> + fmt = RZG3L_LVDS_MODE_VESA;
> + break;
> + default:
> + fmt = RZG3L_LVDS_MODE_VESA;
> + dev_warn(lvds->dev, "Unsupported bus fmt 0x%04x\n",
> + bridge_state->output_bus_cfg.format);
> + break;
> + }
> +
> + ret = pm_runtime_resume_and_get(lvds->dev);
> + if (ret < 0) {
> + dev_err(lvds->dev, "pm_runtime_resume_and_get error\n");
> + return;
> + }
> +
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_EN_BGR, LVDS_0_PHY_CH_EN_BGR);
> + usleep_range(20, 25);
> +
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_EN_LDO, LVDS_0_PHY_CH_EN_LDO);
> + usleep_range(10, 15);
> +
> + regmap_write(lvds->regmap, LVDS_CMN, LVDS_CMN_RST_PHY0_SEL);
> + regmap_update_bits(lvds->regmap, LVDS_0_CTL_OFFSET,
> + LVDS_0_CTL_FMT_SEL_MSK,
> + FIELD_PREP(LVDS_0_CTL_FMT_SEL_MSK, fmt));
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_IO_EN_MSK, LVDS_0_PHY_CH_IO_EN);
> + regmap_write(lvds->regmap, LVDS_CMN,
> + LVDS_CMN_RST_PHY0_SEL | LVDS_CMN_PHY_RESET);
> + usleep_range(100, 150);
> +}
> +
> +static void rzg3l_lvds_atomic_disable(struct drm_bridge *bridge,
> + struct drm_atomic_state *state) {
> + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> +
> + regmap_update_bits(lvds->regmap, LVDS_CMN, LVDS_CMN_PHY_RESET, 0);
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_IO_EN_MSK, 0);
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_EN_LDO, 0);
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_EN_BGR, 0);
> +
> + pm_runtime_put(lvds->dev);
> +}
> +
> +static int rzg3l_lvds_attach(struct drm_bridge *bridge,
> + struct drm_encoder *encoder,
> + enum drm_bridge_attach_flags flags) {
> + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> +
> + if (!lvds->next_bridge)
> + return 0;
> +
> + return drm_bridge_attach(encoder, lvds->next_bridge, bridge, flags); }
> +
> +static enum drm_mode_status
> +rzg3l_lvds_bridge_mode_valid(struct drm_bridge *bridge,
> + const struct drm_display_info *info,
> + const struct drm_display_mode *mode) {
> + if (mode->clock > 87000)
> + return MODE_CLOCK_HIGH;
> +
> + if (mode->clock < 25000)
> + return MODE_CLOCK_LOW;
> +
> + return MODE_OK;
> +}
> +
> +bool rzg3l_lvds_is_connected(struct drm_bridge *bridge) {
> + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> +
> + return !!lvds->next_bridge;
> +}
> +EXPORT_SYMBOL_GPL(rzg3l_lvds_is_connected);
> +
> +static const struct drm_bridge_funcs rzg3l_lvds_bridge_ops = {
> + .attach = rzg3l_lvds_attach,
> + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
> + .atomic_reset = drm_atomic_helper_bridge_reset,
> + .atomic_enable = rzg3l_lvds_atomic_enable,
> + .atomic_disable = rzg3l_lvds_atomic_disable,
> + .mode_valid = rzg3l_lvds_bridge_mode_valid, };
> +
> +/*
> +-----------------------------------------------------------------------
> +------
> + * Power Management
> + */
> +
> +static int rzg3l_lvds_pm_runtime_suspend(struct device *dev) {
> + struct rzg3l_lvds *lvds = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = reset_control_assert(lvds->lvd_rstc);
> + if (ret)
> + return ret;
> +
> + ret = reset_control_assert(lvds->prstc);
> + if (ret)
> + goto err_deassert_lvd_rstc;
> +
> + return 0;
> +
> +err_deassert_lvd_rstc:
> + reset_control_deassert(lvds->lvd_rstc);
> + return ret;
> +}
> +
> +static int rzg3l_lvds_pm_runtime_resume(struct device *dev) {
> + struct rzg3l_lvds *lvds = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = reset_control_deassert(lvds->prstc);
> + if (ret)
> + return ret;
> +
> + return reset_control_deassert(lvds->lvd_rstc);
This should be,
ret = reset_control_deassert(lvds->lvd_rstc);
Cheers,
Biju
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH 3/3] drm: renesas: rz-du: Add support for RZ/G3L LVDS encoder
2026-04-17 17:52 ` [PATCH 3/3] drm: renesas: rz-du: Add support for " Biju
2026-04-19 15:58 ` Dmitry Baryshkov
2026-04-20 7:31 ` Biju Das
@ 2026-04-21 9:12 ` Claudiu Beznea
2 siblings, 0 replies; 15+ messages in thread
From: Claudiu Beznea @ 2026-04-21 9:12 UTC (permalink / raw)
To: Biju, Biju Das, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Philipp Zabel,
Geert Uytterhoeven, Magnus Damm
Cc: linux-kernel, dri-devel, linux-renesas-soc,
Prabhakar Mahadev Lad, Tommaso Merciai
Hi,
On 4/17/26 20:52, Biju wrote:
> From: Biju Das <biju.das.jz@bp.renesas.com>
>
> Add support for the RZ/G3L LVDS encoder driver. It operates in single-link
> mode with 4 lanes (Data) + 1 lane (Clock) and supports pixel clock rates
> from 25 to 87 MHz. The LVDS module cannot be used at the same time as
> MIPI-DSI. However, LVDS and the DSI interface share a peripheral clock and
> the MIPI_DSI_PRESET_N reset signal. Also, the MIPI_DSI_CMN_RSTB and
> MIPI_DSI_ARESET_N reset signals must be asserted before using the LVDS
> module.
>
> Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> ---
> drivers/gpu/drm/renesas/rz-du/Kconfig | 13 +
> drivers/gpu/drm/renesas/rz-du/Makefile | 1 +
> drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c | 333 ++++++++++++++++++
> drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.h | 22 ++
> .../gpu/drm/renesas/rz-du/rzg3l_lvds_regs.h | 26 ++
> 5 files changed, 395 insertions(+)
> create mode 100644 drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c
> create mode 100644 drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.h
> create mode 100644 drivers/gpu/drm/renesas/rz-du/rzg3l_lvds_regs.h
>
> diff --git a/drivers/gpu/drm/renesas/rz-du/Kconfig b/drivers/gpu/drm/renesas/rz-du/Kconfig
> index 7f2ef7137ae5..cbfc7b6bccb8 100644
> --- a/drivers/gpu/drm/renesas/rz-du/Kconfig
> +++ b/drivers/gpu/drm/renesas/rz-du/Kconfig
> @@ -26,3 +26,16 @@ config DRM_RZG2L_MIPI_DSI
> def_tristate DRM_RZG2L_DU
> depends on DRM_RZG2L_USE_MIPI_DSI
> select DRM_MIPI_DSI
> +
> +config DRM_RZG3L_USE_LVDS
> + bool "RZ/G3L DU LVDS Encoder Support"
> + depends on DRM_BRIDGE && OF
> + default DRM_RZG2L_DU
> + help
> + Enable support for the RZ/G3L Display Unit embedded LVDS encoders.
> +
> +config DRM_RZG3L_LVDS
> + def_tristate DRM_RZG2L_DU
> + depends on DRM_RZG3L_USE_LVDS
> + select DRM_KMS_HELPER
> + select DRM_PANEL
> diff --git a/drivers/gpu/drm/renesas/rz-du/Makefile b/drivers/gpu/drm/renesas/rz-du/Makefile
> index 2987900ea6b6..46decb7ac4f1 100644
> --- a/drivers/gpu/drm/renesas/rz-du/Makefile
> +++ b/drivers/gpu/drm/renesas/rz-du/Makefile
> @@ -8,3 +8,4 @@ rzg2l-du-drm-$(CONFIG_VIDEO_RENESAS_VSP1) += rzg2l_du_vsp.o
> obj-$(CONFIG_DRM_RZG2L_DU) += rzg2l-du-drm.o
>
> obj-$(CONFIG_DRM_RZG2L_MIPI_DSI) += rzg2l_mipi_dsi.o
> +obj-$(CONFIG_DRM_RZG3L_LVDS) += rzg3l_lvds.o
> diff --git a/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c b/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c
> new file mode 100644
> index 000000000000..bedeedbdfada
> --- /dev/null
> +++ b/drivers/gpu/drm/renesas/rz-du/rzg3l_lvds.c
> @@ -0,0 +1,333 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * RZ/G3L LVDS Encoder Driver
> + *
> + * Copyright (C) 2026 Renesas Electronics Corporation
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/media-bus-format.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/of_graph.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_panel.h>
> +#include <drm/drm_probe_helper.h>
> +
> +#include "rzg3l_lvds.h"
> +#include "rzg3l_lvds_regs.h"
> +
> +enum rzg3l_lvds_mode {
> + RZG3L_LVDS_MODE_JEIDA = 0,
> + RZG3L_LVDS_MODE_JEIDA_MIRROR = 1,
> + RZG3L_LVDS_MODE_MODE2 = 2,
> + RZG3L_LVDS_MODE_MODE2_MIRROR = 3,
> + RZG3L_LVDS_MODE_VESA = 4,
> + RZG3L_LVDS_MODE_VESA_MIRROR = 5,
> + RZG3L_LVDS_MODE_MODE6 = 6,
> + RZG3L_LVDS_MODE_MODE6_MIRROR = 7,
> +};
> +
> +struct rzg3l_lvds {
> + struct device *dev;
> + struct reset_control *prstc;
> + struct reset_control *lvd_rstc;
> + struct regmap *regmap;
> +
> + struct drm_bridge bridge;
> + struct drm_bridge *next_bridge;
> +};
> +
> +#define bridge_to_rzg3l_lvds(b) \
> + container_of(b, struct rzg3l_lvds, bridge)
> +
> +/* -----------------------------------------------------------------------------
> + * Bridge
> + */
> +static void rzg3l_lvds_atomic_enable(struct drm_bridge *bridge,
> + struct drm_atomic_state *state)
> +{
> + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> + const struct drm_bridge_state *bridge_state;
> + int ret;
> + u32 fmt;
> +
> + /* Get the LVDS format from the bridge state. */
> + bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
> + if (!bridge_state) {
> + dev_err(lvds->dev, "failed to get bridge state\n");
> + return;
> + }
> +
> + switch (bridge_state->output_bus_cfg.format) {
> + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
> + fmt = RZG3L_LVDS_MODE_JEIDA;
> + break;
> + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
> + fmt = RZG3L_LVDS_MODE_VESA;
> + break;
> + default:
> + fmt = RZG3L_LVDS_MODE_VESA;
> + dev_warn(lvds->dev, "Unsupported bus fmt 0x%04x\n",
> + bridge_state->output_bus_cfg.format);
> + break;
> + }
> +
> + ret = pm_runtime_resume_and_get(lvds->dev);
> + if (ret < 0) {
> + dev_err(lvds->dev, "pm_runtime_resume_and_get error\n");
> + return;
> + }
> +
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_EN_BGR, LVDS_0_PHY_CH_EN_BGR);
> + usleep_range(20, 25);
maybe fsleep()? Same for the above usleep_range()
> +
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_EN_LDO, LVDS_0_PHY_CH_EN_LDO);
> + usleep_range(10, 15);
> +
> + regmap_write(lvds->regmap, LVDS_CMN, LVDS_CMN_RST_PHY0_SEL);
> + regmap_update_bits(lvds->regmap, LVDS_0_CTL_OFFSET,
> + LVDS_0_CTL_FMT_SEL_MSK,
> + FIELD_PREP(LVDS_0_CTL_FMT_SEL_MSK, fmt));
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_IO_EN_MSK, LVDS_0_PHY_CH_IO_EN);
> + regmap_write(lvds->regmap, LVDS_CMN,
> + LVDS_CMN_RST_PHY0_SEL | LVDS_CMN_PHY_RESET);
> + usleep_range(100, 150);
> +}
> +
> +static void rzg3l_lvds_atomic_disable(struct drm_bridge *bridge,
> + struct drm_atomic_state *state)
> +{
> + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> +
> + regmap_update_bits(lvds->regmap, LVDS_CMN, LVDS_CMN_PHY_RESET, 0);
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_IO_EN_MSK, 0);
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_EN_LDO, 0);
> + regmap_update_bits(lvds->regmap, LVDS_0_PHY_OFFSET,
> + LVDS_0_PHY_CH_EN_BGR, 0);
> +
> + pm_runtime_put(lvds->dev);
> +}
> +
> +static int rzg3l_lvds_attach(struct drm_bridge *bridge,
> + struct drm_encoder *encoder,
> + enum drm_bridge_attach_flags flags)
> +{
> + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> +
> + if (!lvds->next_bridge)
> + return 0;
> +
> + return drm_bridge_attach(encoder, lvds->next_bridge, bridge, flags);
> +}
> +
> +static enum drm_mode_status
> +rzg3l_lvds_bridge_mode_valid(struct drm_bridge *bridge,
> + const struct drm_display_info *info,
> + const struct drm_display_mode *mode)
> +{
> + if (mode->clock > 87000)
> + return MODE_CLOCK_HIGH;
> +
> + if (mode->clock < 25000)
> + return MODE_CLOCK_LOW;
> +
> + return MODE_OK;
> +}
> +
> +bool rzg3l_lvds_is_connected(struct drm_bridge *bridge)
> +{
> + struct rzg3l_lvds *lvds = bridge_to_rzg3l_lvds(bridge);
> +
> + return !!lvds->next_bridge;
> +}
> +EXPORT_SYMBOL_GPL(rzg3l_lvds_is_connected);
> +
> +static const struct drm_bridge_funcs rzg3l_lvds_bridge_ops = {
> + .attach = rzg3l_lvds_attach,
> + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
> + .atomic_reset = drm_atomic_helper_bridge_reset,
> + .atomic_enable = rzg3l_lvds_atomic_enable,
> + .atomic_disable = rzg3l_lvds_atomic_disable,
> + .mode_valid = rzg3l_lvds_bridge_mode_valid,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Power Management
> + */
> +
> +static int rzg3l_lvds_pm_runtime_suspend(struct device *dev)
> +{
> + struct rzg3l_lvds *lvds = dev_get_drvdata(dev);
You can use:
struct reset_control_bulk_data resets[] = {
{ .rstc = lvds->lvd_rstc },
{ .rstc = lvds->prstc },
};
return reset_control_bulk_assert(ARRAY_SIZE(resets), resets);
> + int ret;
> +
> + ret = reset_control_assert(lvds->lvd_rstc);
> + if (ret)
> + return ret;
> +
> + ret = reset_control_assert(lvds->prstc);
> + if (ret)
> + goto err_deassert_lvd_rstc;
> +
> + return 0;
> +
> +err_deassert_lvd_rstc:
> + reset_control_deassert(lvds->lvd_rstc);
> + return ret;
> +}
> +
> +static int rzg3l_lvds_pm_runtime_resume(struct device *dev)
> +{
> + struct rzg3l_lvds *lvds = dev_get_drvdata(dev);
> + int ret;
Same here.
> +
> + ret = reset_control_deassert(lvds->prstc);
> + if (ret)
> + return ret;
> +
> + return reset_control_deassert(lvds->lvd_rstc);
> + if (ret)
> + goto err_assert_prstc;
> +
> + return 0;
> +
> +err_assert_prstc:
> + reset_control_assert(lvds->prstc);
> + return ret;
> +}
> +
> +static const struct dev_pm_ops rzg3l_lvds_pm_ops = {
> + RUNTIME_PM_OPS(rzg3l_lvds_pm_runtime_suspend,
> + rzg3l_lvds_pm_runtime_resume, NULL)
> + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Probe & Remove
> + */
> +
> +static int rzg3l_lvds_parse_dt(struct rzg3l_lvds *lvds)
> +{
> + struct device *dev = lvds->dev;
> + struct drm_panel *panel;
> + int ret;
> +
> + ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0,
> + &panel, &lvds->next_bridge);
From documentation of this function:
* This function is deprecated and should not be used in new drivers. Use
* devm_drm_of_get_bridge() instead.
devm_of_get_bridged() seems to be doing exactly what rzg3l_lvds_parse_dt() is doing.
Thank you,
Claudiu
^ permalink raw reply [flat|nested] 15+ messages in thread