* [PATCH v2 1/4] drm/rockchip: lvds: propagate bus_flags to the CRTC state
2026-07-24 8:08 [PATCH v2 0/4] drm/rockchip: add RK3568 LVDS support Rok Markovic
@ 2026-07-24 8:08 ` Rok Markovic
2026-07-24 8:19 ` sashiko-bot
2026-07-24 8:08 ` [PATCH v2 2/4] dt-bindings: display: rockchip,lvds: add RK3568 Rok Markovic
` (2 subsequent siblings)
3 siblings, 1 reply; 7+ messages in thread
From: Rok Markovic @ 2026-07-24 8:08 UTC (permalink / raw)
To: Heiko Stuebner, Sandy Huang, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: dri-devel, devicetree, linux-arm-kernel, linux-rockchip,
linux-kernel, Alibek Omarov, Rok Markovic
rockchip_lvds_encoder_atomic_check() sets output_mode and output_type
but never copies the connector's bus_flags into the Rockchip CRTC
state, unlike dw_dp-rockchip.c and dw-mipi-dsi2-rockchip.c which both
do.
panel-lvds parses pixelclk-active from the DT display timing and
publishes it on the connector as DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE.
VOP2 reads that flag back out of the CRTC state to decide
POLFLAG_DCLK_INV. With the flag dropped here, vcstate->bus_flags is
always zero, so a panel that asks to be clocked on the falling edge is
driven on the rising one, and there is no way to express the panel's
requirement from DT at all.
No functional change for the SoCs currently supported by this driver:
rk3288 and px30 pair with the VOP1 driver (rockchip_drm_vop.c), which
never reads bus_flags - only VOP2 consumes it. The flag becomes live
with the RK3568 support added later in this series.
Signed-off-by: Rok Markovic <rok@kanardia.eu>
Assisted-by: Claude:claude-opus-4-8
---
No changes since v1.
drivers/gpu/drm/rockchip/rockchip_lvds.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c
index 75f898a..95fa0a9 100644
--- a/drivers/gpu/drm/rockchip/rockchip_lvds.c
+++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c
@@ -129,7 +129,15 @@ rockchip_lvds_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_connector_state *conn_state)
{
struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
-
+ struct drm_connector *connector = conn_state->connector;
+
+ /*
+ * The VOP derives the pixel clock polarity from this. Without it a
+ * panel that declares pixelclk-active = <0> is clocked on the wrong
+ * edge. panel-lvds fills the connector's bus_flags in from the DT
+ * display timing.
+ */
+ s->bus_flags = connector->display_info.bus_flags;
s->output_mode = ROCKCHIP_OUT_MODE_P888;
s->output_type = DRM_MODE_CONNECTOR_LVDS;
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v2 2/4] dt-bindings: display: rockchip,lvds: add RK3568
2026-07-24 8:08 [PATCH v2 0/4] drm/rockchip: add RK3568 LVDS support Rok Markovic
2026-07-24 8:08 ` [PATCH v2 1/4] drm/rockchip: lvds: propagate bus_flags to the CRTC state Rok Markovic
@ 2026-07-24 8:08 ` Rok Markovic
2026-07-24 8:08 ` [PATCH v2 3/4] drm/rockchip: lvds: add RK3568 support Rok Markovic
2026-07-24 8:08 ` [PATCH v2 4/4] arm64: dts: rockchip: rk356x: add LVDS node Rok Markovic
3 siblings, 0 replies; 7+ messages in thread
From: Rok Markovic @ 2026-07-24 8:08 UTC (permalink / raw)
To: Heiko Stuebner, Sandy Huang, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: dri-devel, devicetree, linux-arm-kernel, linux-rockchip,
linux-kernel, Alibek Omarov, Rok Markovic
The RK3568 LVDS transmitter has no register block of its own: it is
programmed entirely through the GRF and re-uses the MIPI DSI0 D-PHY
(rockchip,rk3568-dsi-dphy) in LVDS mode. It therefore takes the same
shape as px30: phys/phy-names, no reg, and no clock of its own.
Since the block exposes no MMIO, nothing in the LVDS path is clocked
from the DSI controller's APB clock; that gate is held off while LVDS
scans out. It has no analog supplies of its own and no pinctrl either:
the lanes are dedicated D-PHY pins, not muxable GPIOs.
Signed-off-by: Rok Markovic <rok@kanardia.eu>
Assisted-by: Claude:claude-opus-4-8
---
Changes in v2:
- RK3568 now takes no clock (clocks/clock-names disabled, as for
px30): the block has no register space, so nothing consumes the
DSI controller's APB clock.
.../display/rockchip/rockchip,lvds.yaml | 22 +++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,lvds.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,lvds.yaml
index 03b002a..b77d4fa 100644
--- a/Documentation/devicetree/bindings/display/rockchip/rockchip,lvds.yaml
+++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,lvds.yaml
@@ -15,6 +15,7 @@ properties:
enum:
- rockchip,px30-lvds
- rockchip,rk3288-lvds
+ - rockchip,rk3568-lvds
reg:
maxItems: 1
@@ -121,6 +122,27 @@ allOf:
- avdd1v8-supply
- avdd3v3-supply
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: rockchip,rk3568-lvds
+
+ then:
+ properties:
+ reg: false
+ clocks: false
+ clock-names: false
+ avdd1v0-supply: false
+ avdd1v8-supply: false
+ avdd3v3-supply: false
+ pinctrl-names: false
+ pinctrl-0: false
+
+ required:
+ - phys
+ - phy-names
+
additionalProperties: false
examples:
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v2 3/4] drm/rockchip: lvds: add RK3568 support
2026-07-24 8:08 [PATCH v2 0/4] drm/rockchip: add RK3568 LVDS support Rok Markovic
2026-07-24 8:08 ` [PATCH v2 1/4] drm/rockchip: lvds: propagate bus_flags to the CRTC state Rok Markovic
2026-07-24 8:08 ` [PATCH v2 2/4] dt-bindings: display: rockchip,lvds: add RK3568 Rok Markovic
@ 2026-07-24 8:08 ` Rok Markovic
2026-07-24 8:24 ` sashiko-bot
2026-07-24 8:08 ` [PATCH v2 4/4] arm64: dts: rockchip: rk356x: add LVDS node Rok Markovic
3 siblings, 1 reply; 7+ messages in thread
From: Rok Markovic @ 2026-07-24 8:08 UTC (permalink / raw)
To: Heiko Stuebner, Sandy Huang, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: dri-devel, devicetree, linux-arm-kernel, linux-rockchip,
linux-kernel, Alibek Omarov, Rok Markovic
The RK3568 LVDS transmitter has no register block of its own. It is
driven entirely through the GRF and re-uses the MIPI DSI0 D-PHY in
PHY_MODE_LVDS, which phy-rockchip-inno-dsidphy already supports.
Based on Alibek Omarov's earlier posting [1], with the changes below.
Power the D-PHY from the encoder enable path rather than from probe.
phy_power_on() runs the phy driver's whole LVDS bring-up: PLL and
bandgap power-on, a settle, PLL mode select, then a reset pulse of the
serializer and the lane enables. None of that is safe at probe time -
the GRF has not yet switched the block to LVDS mode (LVDS0_MODE_EN is
set from rk3568_lvds_poweron(), i.e. the enable path) and the VOP is
not driving dclk. The serializer is clocked from dclk and latches its
state coming out of that reset, so it comes up dead and stays dead.
The failure is silent: every register in the phy, the GRF and the VOP
reads back exactly as on a working system while the lanes sit at
common mode and never toggle.
Program RK3568_LVDS0_DCLK_INV_SEL from the CRTC state's bus_flags so
the panel's declared pixelclk-active is honoured on the LVDS block as
well as on the VOP pin polarity. Both have to agree with the edge the
panel samples on.
Re-assert RK3568_LVDS0_P2S_EN in rk3568_lvds_poweron().
rk3568_lvds_poweroff() clears MODE_EN and P2S_EN together, so setting
P2S_EN once at probe would leave the parallel-to-serial converter off
after the first disable/enable cycle.
Use regmap_write() rather than regmap_update_bits() for the GRF. These
registers are write-masked - the upper 16 bits select which of the
lower 16 a write may change - so there is nothing to preserve and no
reason to read first. Passing a FIELD_PREP_WM16() value as both the
mask and the value of an update_bits() applies the masking twice and
only works by accident.
Take no clock. The block exposes no MMIO of its own, so there is
nothing for the DSI controller's APB clock to reach; the vendor BSP
scans out LVDS with PCLK_DSITX_0's gate off, and so does this driver.
Between the above, nothing needs programming at probe at all: the GRF
is written entirely from the enable path, so px30_lvds_probe() is left
alone rather than being refactored into a shared phy helper.
Track the enabled state so that a .enable() that bails out unwinds
itself and the .disable() that the atomic helpers still call afterwards
becomes a no-op, rather than underflowing the phy power count and the
runtime-PM count of an encoder that never came up.
[1] https://lore.kernel.org/all/20230119184807.171132-1-a1ba.omarov@gmail.com/
Co-developed-by: Alibek Omarov <a1ba.omarov@gmail.com>
Signed-off-by: Alibek Omarov <a1ba.omarov@gmail.com>
Signed-off-by: Rok Markovic <rok@kanardia.eu>
Assisted-by: Claude:claude-opus-4-8
---
Changes in v2:
- Drop the clock. rk3568_lvds_poweron() no longer enables a pclk;
the block has no MMIO for the DSI APB clock to reach, and the
vendor BSP scans out with PCLK_DSITX_0 gated off.
- Merge rk3568_lvds_grf_config() into rk3568_lvds_poweron(): a
single masked write to VO_CON2 instead of two, and check the
output type before resuming runtime PM.
- Balance the enable/disable teardown so a .enable() that fails
partway does not underflow the phy power and runtime-PM counts
when the atomic helpers then call .disable().
- Use pm_runtime_resume_and_get(), matching the rk3288/px30 paths.
- Drop the in-code comments restating phy_set_mode() internals and
the write-masked GRF convention; the ordering rationale is in the
commit message.
drivers/gpu/drm/rockchip/rockchip_lvds.c | 124 +++++++++++++++++++++++
drivers/gpu/drm/rockchip/rockchip_lvds.h | 10 ++
2 files changed, 134 insertions(+)
diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c
index 95fa0a9..9af21fc 100644
--- a/drivers/gpu/drm/rockchip/rockchip_lvds.c
+++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c
@@ -51,6 +51,7 @@ struct rockchip_lvds {
struct regmap *grf;
struct clk *pclk;
struct phy *dphy;
+ bool enabled;
const struct rockchip_lvds_soc_data *soc_data;
int output; /* rgb lvds or dual lvds output */
int format; /* vesa or jeida format */
@@ -435,6 +436,100 @@ static void px30_lvds_encoder_disable(struct drm_encoder *encoder)
drm_panel_unprepare(lvds->panel);
}
+static int rk3568_lvds_poweron(struct drm_encoder *encoder)
+{
+ struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
+ struct rockchip_crtc_state *s =
+ to_rockchip_crtc_state(encoder->crtc->state);
+ bool negedge = !!(s->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE);
+ int ret;
+
+ if (lvds->output != DISPLAY_OUTPUT_LVDS) {
+ DRM_DEV_ERROR(lvds->dev, "Unsupported display output %d\n",
+ lvds->output);
+ return -EINVAL;
+ }
+
+ ret = pm_runtime_resume_and_get(lvds->dev);
+ if (ret < 0) {
+ DRM_DEV_ERROR(lvds->dev, "failed to get pm runtime: %d\n", ret);
+ return ret;
+ }
+
+ regmap_write(lvds->grf, RK3568_GRF_VO_CON2,
+ RK3568_LVDS0_MODE_EN(1) | RK3568_LVDS0_P2S_EN(1) |
+ RK3568_LVDS0_DCLK_INV_SEL(negedge));
+ regmap_write(lvds->grf, RK3568_GRF_VO_CON0,
+ RK3568_LVDS0_SELECT(lvds->format) |
+ RK3568_LVDS0_MSBSEL(1));
+
+ return 0;
+}
+
+static void rk3568_lvds_poweroff(struct rockchip_lvds *lvds)
+{
+ regmap_write(lvds->grf, RK3568_GRF_VO_CON2,
+ RK3568_LVDS0_MODE_EN(0) | RK3568_LVDS0_P2S_EN(0));
+
+ pm_runtime_put(lvds->dev);
+}
+
+static void rk3568_lvds_encoder_enable(struct drm_encoder *encoder)
+{
+ struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
+ int ret;
+
+ drm_panel_prepare(lvds->panel);
+
+ ret = rk3568_lvds_poweron(encoder);
+ if (ret) {
+ DRM_DEV_ERROR(lvds->dev, "failed to power on LVDS: %d\n", ret);
+ goto err_unprepare;
+ }
+
+ /* The GRF must be in LVDS mode with dclk running before the phy. */
+ ret = phy_set_mode(lvds->dphy, PHY_MODE_LVDS);
+ if (ret) {
+ DRM_DEV_ERROR(lvds->dev, "failed to set phy mode: %d\n", ret);
+ goto err_poweroff;
+ }
+
+ ret = phy_power_on(lvds->dphy);
+ if (ret) {
+ DRM_DEV_ERROR(lvds->dev, "failed to power on phy: %d\n", ret);
+ goto err_poweroff;
+ }
+
+ lvds->enabled = true;
+ drm_panel_enable(lvds->panel);
+ return;
+
+err_poweroff:
+ rk3568_lvds_poweroff(lvds);
+err_unprepare:
+ drm_panel_unprepare(lvds->panel);
+}
+
+static void rk3568_lvds_encoder_disable(struct drm_encoder *encoder)
+{
+ struct rockchip_lvds *lvds = encoder_to_lvds(encoder);
+
+ /*
+ * .enable() cannot report failure, so the atomic helpers call this even
+ * when it bailed out and already unwound itself. Tear down only what an
+ * enable that ran to completion left behind, or the phy power count and
+ * the runtime-PM count of a half-enabled encoder underflow.
+ */
+ if (!lvds->enabled)
+ return;
+
+ drm_panel_disable(lvds->panel);
+ phy_power_off(lvds->dphy);
+ rk3568_lvds_poweroff(lvds);
+ drm_panel_unprepare(lvds->panel);
+ lvds->enabled = false;
+}
+
static const
struct drm_encoder_helper_funcs rk3288_lvds_encoder_helper_funcs = {
.enable = rk3288_lvds_encoder_enable,
@@ -449,6 +544,13 @@ struct drm_encoder_helper_funcs px30_lvds_encoder_helper_funcs = {
.atomic_check = rockchip_lvds_encoder_atomic_check,
};
+static const
+struct drm_encoder_helper_funcs rk3568_lvds_encoder_helper_funcs = {
+ .enable = rk3568_lvds_encoder_enable,
+ .disable = rk3568_lvds_encoder_disable,
+ .atomic_check = rockchip_lvds_encoder_atomic_check,
+};
+
static int rk3288_lvds_probe(struct platform_device *pdev,
struct rockchip_lvds *lvds)
{
@@ -512,6 +614,17 @@ static int px30_lvds_probe(struct platform_device *pdev,
return phy_power_on(lvds->dphy);
}
+static int rk3568_lvds_probe(struct platform_device *pdev,
+ struct rockchip_lvds *lvds)
+{
+ /* The phy is powered on from the encoder enable path, not here. */
+ lvds->dphy = devm_phy_get(&pdev->dev, "dphy");
+ if (IS_ERR(lvds->dphy))
+ return PTR_ERR(lvds->dphy);
+
+ return phy_init(lvds->dphy);
+}
+
static const struct rockchip_lvds_soc_data rk3288_lvds_data = {
.probe = rk3288_lvds_probe,
.helper_funcs = &rk3288_lvds_encoder_helper_funcs,
@@ -522,6 +635,11 @@ static const struct rockchip_lvds_soc_data px30_lvds_data = {
.helper_funcs = &px30_lvds_encoder_helper_funcs,
};
+static const struct rockchip_lvds_soc_data rk3568_lvds_data = {
+ .probe = rk3568_lvds_probe,
+ .helper_funcs = &rk3568_lvds_encoder_helper_funcs,
+};
+
static const struct of_device_id rockchip_lvds_dt_ids[] = {
{
.compatible = "rockchip,rk3288-lvds",
@@ -531,6 +649,10 @@ static const struct of_device_id rockchip_lvds_dt_ids[] = {
.compatible = "rockchip,px30-lvds",
.data = &px30_lvds_data
},
+ {
+ .compatible = "rockchip,rk3568-lvds",
+ .data = &rk3568_lvds_data
+ },
{}
};
MODULE_DEVICE_TABLE(of, rockchip_lvds_dt_ids);
@@ -601,6 +723,8 @@ static int rockchip_lvds_bind(struct device *dev, struct device *master,
encoder = &lvds->encoder.encoder;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
dev->of_node);
+ rockchip_drm_encoder_set_crtc_endpoint_id(&lvds->encoder,
+ dev->of_node, 0, 0);
ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_LVDS);
if (ret < 0) {
diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.h b/drivers/gpu/drm/rockchip/rockchip_lvds.h
index 2d92447..93d3415 100644
--- a/drivers/gpu/drm/rockchip/rockchip_lvds.h
+++ b/drivers/gpu/drm/rockchip/rockchip_lvds.h
@@ -121,4 +121,14 @@
#define PX30_LVDS_P2S_EN(val) FIELD_PREP_WM16(BIT(6), (val))
#define PX30_LVDS_VOP_SEL(val) FIELD_PREP_WM16(BIT(1), (val))
+#define RK3568_GRF_VO_CON0 0x0360
+#define RK3568_LVDS0_SELECT(val) FIELD_PREP_WM16(GENMASK(5, 4), (val))
+#define RK3568_LVDS0_MSBSEL(val) FIELD_PREP_WM16(BIT(3), (val))
+
+#define RK3568_GRF_VO_CON2 0x0368
+#define RK3568_LVDS0_DCLK_INV_SEL(val) FIELD_PREP_WM16(BIT(9), (val))
+#define RK3568_LVDS0_DCLK_DIV2_SEL(val) FIELD_PREP_WM16(BIT(8), (val))
+#define RK3568_LVDS0_MODE_EN(val) FIELD_PREP_WM16(BIT(1), (val))
+#define RK3568_LVDS0_P2S_EN(val) FIELD_PREP_WM16(BIT(0), (val))
+
#endif /* _ROCKCHIP_LVDS_ */
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v2 4/4] arm64: dts: rockchip: rk356x: add LVDS node
2026-07-24 8:08 [PATCH v2 0/4] drm/rockchip: add RK3568 LVDS support Rok Markovic
` (2 preceding siblings ...)
2026-07-24 8:08 ` [PATCH v2 3/4] drm/rockchip: lvds: add RK3568 support Rok Markovic
@ 2026-07-24 8:08 ` Rok Markovic
3 siblings, 0 replies; 7+ messages in thread
From: Rok Markovic @ 2026-07-24 8:08 UTC (permalink / raw)
To: Heiko Stuebner, Sandy Huang, Andy Yan, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: dri-devel, devicetree, linux-arm-kernel, linux-rockchip,
linux-kernel, Alibek Omarov, Rok Markovic
This commit adds the LVDS transmitter node for the RK356x SoC, assigning
its D-PHY, GRF handle and power domain. The block has no register block
and no clock of its own. It is left disabled by default, to be enabled
by specific board files.
Signed-off-by: Rok Markovic <rok@kanardia.eu>
Assisted-by: Claude:claude-opus-4-8
---
Changes in v2:
- Drop the clock from the LVDS node; the block takes none.
arch/arm64/boot/dts/rockchip/rk356x-base.dtsi | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi b/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi
index 64bdd8b..61a932a 100644
--- a/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi
@@ -878,6 +878,29 @@
};
};
+ lvds: lvds {
+ compatible = "rockchip,rk3568-lvds";
+ phys = <&dsi_dphy0>;
+ phy-names = "dphy";
+ power-domains = <&power RK3568_PD_VO>;
+ rockchip,grf = <&grf>;
+ rockchip,output = "lvds";
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ lvds_in: port@0 {
+ reg = <0>;
+ };
+
+ lvds_out: port@1 {
+ reg = <1>;
+ };
+ };
+ };
+
qos_gpu: qos@fe128000 {
compatible = "rockchip,rk3568-qos", "syscon";
reg = <0x0 0xfe128000 0x0 0x20>;
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread