* [PATCH RFC 1/9] drm/bridge: terminate the lines of the bridge state dump
2026-10-08 13:14 [PATCH RFC 0/9] drm/panel: add atomic state Dmitry Baryshkov
@ 2026-10-08 13:14 ` Dmitry Baryshkov
2026-10-08 13:14 ` [PATCH RFC 2/9] drm/bridge: add atomic_print_state callback Dmitry Baryshkov
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-10-08 13:14 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Jessica Zhang,
Konrad Dybcio, Val Packett
Cc: dri-devel, linux-kernel, linux-doc
The bridge state is dumped as a single line: the bus configuration
headers, format codes and flags all run together, followed on the same
line by the state of the next object. drm_printf_indent() does not
terminate the line it prints, and unlike its other users
drm_bridge_atomic_print_priv_state() does not add a newline to its
format strings.
Add the missing newlines.
Fixes: 4a7a146f1cc6 ("drm/bridge: Implement atomic_print_state")
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/drm_bridge.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index e7f9e6d58927..0f37e3ade016 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -565,12 +565,12 @@ drm_bridge_atomic_print_priv_state(struct drm_printer *p,
else
drm_printf(p, "bridge: %ps\n", bridge->funcs);
- drm_printf_indent(p, 1, "input bus configuration:");
- drm_printf_indent(p, 2, "code: %04x", state->input_bus_cfg.format);
- drm_printf_indent(p, 2, "flags: %08x", state->input_bus_cfg.flags);
- drm_printf_indent(p, 1, "output bus configuration:");
- drm_printf_indent(p, 2, "code: %04x", state->output_bus_cfg.format);
- drm_printf_indent(p, 2, "flags: %08x", state->output_bus_cfg.flags);
+ drm_printf_indent(p, 1, "input bus configuration:\n");
+ drm_printf_indent(p, 2, "code: %04x\n", state->input_bus_cfg.format);
+ drm_printf_indent(p, 2, "flags: %08x\n", state->input_bus_cfg.flags);
+ drm_printf_indent(p, 1, "output bus configuration:\n");
+ drm_printf_indent(p, 2, "code: %04x\n", state->output_bus_cfg.format);
+ drm_printf_indent(p, 2, "flags: %08x\n", state->output_bus_cfg.flags);
}
static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = {
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 2/9] drm/bridge: add atomic_print_state callback
2026-10-08 13:14 [PATCH RFC 0/9] drm/panel: add atomic state Dmitry Baryshkov
2026-10-08 13:14 ` [PATCH RFC 1/9] drm/bridge: terminate the lines of the bridge state dump Dmitry Baryshkov
@ 2026-10-08 13:14 ` Dmitry Baryshkov
2026-10-08 13:14 ` [PATCH RFC 3/9] drm/bridge: drop the bridge pointer from drm_bridge_state Dmitry Baryshkov
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-10-08 13:14 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Jessica Zhang,
Konrad Dybcio, Val Packett
Cc: dri-devel, linux-kernel, linux-doc
Bridge drivers can subclass drm_bridge_state, but the state is printed
by the bridge core, which only knows about the bus configuration. The
driver-specific part of the state is thus missing from the state dumps,
making it hard to debug such drivers.
Add an optional atomic_print_state callback letting bridge drivers print
the rest of their state.
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/drm_bridge.c | 3 +++
include/drm/drm_bridge.h | 9 +++++++++
2 files changed, 12 insertions(+)
diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c
index 0f37e3ade016..2b06171b122a 100644
--- a/drivers/gpu/drm/drm_bridge.c
+++ b/drivers/gpu/drm/drm_bridge.c
@@ -571,6 +571,9 @@ drm_bridge_atomic_print_priv_state(struct drm_printer *p,
drm_printf_indent(p, 1, "output bus configuration:\n");
drm_printf_indent(p, 2, "code: %04x\n", state->output_bus_cfg.format);
drm_printf_indent(p, 2, "flags: %08x\n", state->output_bus_cfg.flags);
+
+ if (bridge->funcs->atomic_print_state)
+ bridge->funcs->atomic_print_state(p, state);
}
static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = {
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 1981d24a700d..8b0c72d93d9b 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -312,6 +312,15 @@ struct drm_bridge_funcs {
void (*atomic_destroy_state)(struct drm_bridge *bridge,
struct drm_bridge_state *state);
+ /**
+ * @atomic_print_state:
+ *
+ * If driver subclasses &struct drm_bridge_state, it should implement
+ * this optional hook for printing additional driver specific state.
+ */
+ void (*atomic_print_state)(struct drm_printer *p,
+ const struct drm_bridge_state *state);
+
/**
* @atomic_get_output_bus_fmts:
*
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 3/9] drm/bridge: drop the bridge pointer from drm_bridge_state
2026-10-08 13:14 [PATCH RFC 0/9] drm/panel: add atomic state Dmitry Baryshkov
2026-10-08 13:14 ` [PATCH RFC 1/9] drm/bridge: terminate the lines of the bridge state dump Dmitry Baryshkov
2026-10-08 13:14 ` [PATCH RFC 2/9] drm/bridge: add atomic_print_state callback Dmitry Baryshkov
@ 2026-10-08 13:14 ` Dmitry Baryshkov
2026-10-08 13:14 ` [PATCH RFC 4/9] drm/panel: add atomic state Dmitry Baryshkov
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-10-08 13:14 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Jessica Zhang,
Konrad Dybcio, Val Packett
Cc: dri-devel, linux-kernel, linux-doc
struct drm_bridge_state carries a pointer to the bridge it belongs to.
It was needed when the bridge state was introduced, as the private
object state it is based on had no link to its object back then. Since
commit 97a1f01b3f2f ("drm/atomic: Add atomic_print_state to private
objects") struct drm_private_state has such a link, making the bridge
pointer redundant. Nothing reads it anymore, it is only kept up to date
by the state helpers.
Drop the pointer. The bridge can be obtained from the state with
drm_priv_to_bridge(state->base.obj).
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/drm_atomic_state_helper.c | 2 --
include/drm/drm_atomic.h | 5 -----
2 files changed, 7 deletions(-)
diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
index 406157a3660f..f66937d7044e 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -759,7 +759,6 @@ void __drm_atomic_helper_bridge_duplicate_state(struct drm_bridge *bridge,
{
__drm_atomic_helper_private_obj_duplicate_state(&bridge->base,
&state->base);
- state->bridge = bridge;
}
EXPORT_SYMBOL(__drm_atomic_helper_bridge_duplicate_state);
@@ -822,7 +821,6 @@ void __drm_atomic_helper_bridge_state_init(struct drm_bridge_state *state,
struct drm_bridge *bridge)
{
__drm_atomic_helper_private_obj_create_state(&bridge->base, &state->base);
- state->bridge = bridge;
}
EXPORT_SYMBOL(__drm_atomic_helper_bridge_state_init);
diff --git a/include/drm/drm_atomic.h b/include/drm/drm_atomic.h
index 3ae35b09c0cf..095b9db145b7 100644
--- a/include/drm/drm_atomic.h
+++ b/include/drm/drm_atomic.h
@@ -1381,11 +1381,6 @@ struct drm_bridge_state {
*/
struct drm_private_state base;
- /**
- * @bridge: the bridge this state refers to
- */
- struct drm_bridge *bridge;
-
/**
* @input_bus_cfg: input bus configuration
*/
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 4/9] drm/panel: add atomic state
2026-10-08 13:14 [PATCH RFC 0/9] drm/panel: add atomic state Dmitry Baryshkov
` (2 preceding siblings ...)
2026-10-08 13:14 ` [PATCH RFC 3/9] drm/bridge: drop the bridge pointer from drm_bridge_state Dmitry Baryshkov
@ 2026-10-08 13:14 ` Dmitry Baryshkov
2026-10-08 13:14 ` [PATCH RFC 5/9] drm/panel: add atomic_check callback Dmitry Baryshkov
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-10-08 13:14 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Jessica Zhang,
Konrad Dybcio, Val Packett
Cc: dri-devel, linux-kernel, linux-doc
Panel drivers have no way to track per-commit configuration. A panel
that supports several modes, or that needs different programming
depending on the selected configuration, has to keep that information in
its driver data, where it is neither checked at atomic_check time nor
tied to the commit that is being applied.
Every drm_panel embeds a drm_bridge, and that bridge already carries an
atomic state, but it is the plain drm_bridge_state and the panel driver
never sees it.
Introduce struct drm_panel_state, wrapping drm_bridge_state. As for the
other atomic objects, a panel gets it by implementing the
atomic_create_state, atomic_duplicate_state and atomic_destroy_state
callbacks, either with the default helpers or with its own functions if
it subclasses the state. Add accessors to get the old, new and current
panel state.
Panels not implementing these callbacks keep using the plain bridge
state, nothing changes for them.
The panel state only exists once the embedded bridge has been attached
to an encoder, which is not the case for the display drivers calling
drm_panel_prepare() and its counterparts directly. Make these functions
refuse to handle the panels using atomic state.
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
Documentation/gpu/drm-kms-helpers.rst | 3 +
drivers/gpu/drm/drm_panel.c | 301 +++++++++++++++++++++++++++++++++-
include/drm/drm_panel.h | 95 +++++++++++
3 files changed, 396 insertions(+), 3 deletions(-)
diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
index d64f941d34f5..305c24be7f4b 100644
--- a/Documentation/gpu/drm-kms-helpers.rst
+++ b/Documentation/gpu/drm-kms-helpers.rst
@@ -229,6 +229,9 @@ Panel Helper Reference
.. kernel-doc:: drivers/gpu/drm/drm_panel.c
:doc: drm panel
+.. kernel-doc:: drivers/gpu/drm/drm_panel.c
+ :doc: drm panel atomic state
+
.. kernel-doc:: include/drm/drm_panel.h
:internal:
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index c00529bfb706..481caa294243 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -11,6 +11,7 @@
#include <linux/export.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/slab.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
@@ -38,6 +39,32 @@ static LIST_HEAD(panel_list);
* take look at drm_panel_bridge_add() and devm_drm_panel_bridge_add().
*/
+/**
+ * DOC: drm panel atomic state
+ *
+ * Panels that only support a single configuration can be fully described by
+ * the &drm_panel_funcs.prepare, &drm_panel_funcs.enable,
+ * &drm_panel_funcs.disable and &drm_panel_funcs.unprepare callbacks.
+ *
+ * Panels whose programming depends on the configuration being applied, for
+ * example panels supporting several modes, should track that configuration in
+ * the atomic state instead of the driver data. Such panels get a
+ * &struct drm_panel_state by implementing
+ * &drm_panel_funcs.atomic_create_state,
+ * &drm_panel_funcs.atomic_duplicate_state and
+ * &drm_panel_funcs.atomic_destroy_state, either with the
+ * drm_atomic_helper_panel_create_state(),
+ * drm_atomic_helper_panel_duplicate_state() and
+ * drm_atomic_helper_panel_destroy_state() helpers or with their own functions
+ * if they subclass the state.
+ *
+ * The panel state is the state of the &drm_bridge embedded in the panel. It
+ * only exists once that bridge has been attached to an encoder, so the panels
+ * using atomic state can only be used by display drivers going through the
+ * bridge. Such panel drivers must not call drm_panel_disable() or
+ * drm_panel_unprepare() from their remove or shutdown callbacks either.
+ */
+
static inline struct drm_panel *
drm_bridge_to_panel(const struct drm_bridge *bridge)
{
@@ -216,6 +243,52 @@ static void panel_bridge_debugfs_init(struct drm_bridge *bridge,
panel->funcs->debugfs_init(panel, root);
}
+static struct drm_bridge_state *
+panel_bridge_atomic_create_state(struct drm_bridge *bridge)
+{
+ struct drm_panel *panel = drm_bridge_to_panel(bridge);
+ struct drm_panel_state *panel_state;
+
+ if (!panel->funcs->atomic_create_state)
+ return drm_atomic_helper_bridge_create_state(bridge);
+
+ panel_state = panel->funcs->atomic_create_state(panel);
+ if (IS_ERR(panel_state))
+ return ERR_CAST(panel_state);
+
+ return &panel_state->base;
+}
+
+static struct drm_bridge_state *
+panel_bridge_atomic_duplicate_state(struct drm_bridge *bridge)
+{
+ struct drm_panel *panel = drm_bridge_to_panel(bridge);
+ struct drm_panel_state *panel_state;
+
+ if (!panel->funcs->atomic_duplicate_state)
+ return drm_atomic_helper_bridge_duplicate_state(bridge);
+
+ panel_state = panel->funcs->atomic_duplicate_state(panel);
+ if (!panel_state)
+ return NULL;
+
+ return &panel_state->base;
+}
+
+static void panel_bridge_atomic_destroy_state(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state)
+{
+ struct drm_panel *panel = drm_bridge_to_panel(bridge);
+
+ if (!panel->funcs->atomic_destroy_state) {
+ drm_atomic_helper_bridge_destroy_state(bridge, bridge_state);
+ return;
+ }
+
+ panel->funcs->atomic_destroy_state(panel,
+ drm_bridge_state_to_panel_state(bridge_state));
+}
+
static const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
.attach = panel_bridge_attach,
.detach = panel_bridge_detach,
@@ -224,9 +297,9 @@ static const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
.atomic_disable = panel_bridge_atomic_disable,
.atomic_post_disable = panel_bridge_atomic_post_disable,
.get_modes = panel_bridge_get_modes,
- .atomic_create_state = drm_atomic_helper_bridge_create_state,
- .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
- .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+ .atomic_create_state = panel_bridge_atomic_create_state,
+ .atomic_duplicate_state = panel_bridge_atomic_duplicate_state,
+ .atomic_destroy_state = panel_bridge_atomic_destroy_state,
.atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt,
.debugfs_init = panel_bridge_debugfs_init,
};
@@ -576,6 +649,11 @@ static void drm_panel_init(struct drm_panel *panel, struct device *dev,
if (connector_type == DRM_MODE_CONNECTOR_Unknown)
DRM_WARN("%s: %s: a valid connector type is required!\n", __func__, dev_name(dev));
+ dev_WARN_ONCE(dev,
+ !funcs->atomic_create_state != !funcs->atomic_duplicate_state ||
+ !funcs->atomic_create_state != !funcs->atomic_destroy_state,
+ "Panel state callbacks have to be implemented together\n");
+
INIT_LIST_HEAD(&panel->list);
INIT_LIST_HEAD(&panel->followers);
mutex_init(&panel->follower_lock);
@@ -657,6 +735,9 @@ EXPORT_SYMBOL(devm_drm_panel_add);
* integrated circuitry via a command bus. This function cannot fail (as it is
* called from the pre_enable call chain). There will always be a call to
* drm_panel_disable() afterwards.
+ *
+ * This function can not be used with the panels using atomic state, those are
+ * only supported through the bridge embedded in the panel.
*/
void drm_panel_prepare(struct drm_panel *panel)
{
@@ -666,6 +747,10 @@ void drm_panel_prepare(struct drm_panel *panel)
if (!panel)
return;
+ if (dev_WARN_ONCE(panel->dev, panel->funcs->atomic_create_state,
+ "Atomic panels have to be driven through the bridge\n"))
+ return;
+
if (panel->prepared) {
dev_warn(panel->dev, "Skipping prepare of already prepared panel\n");
return;
@@ -703,6 +788,9 @@ EXPORT_SYMBOL(drm_panel_prepare);
* reset, turn off power supplies, ...). After this function has completed, it
* is usually no longer possible to communicate with the panel until another
* call to drm_panel_prepare().
+ *
+ * This function can not be used with the panels using atomic state, those are
+ * only supported through the bridge embedded in the panel.
*/
void drm_panel_unprepare(struct drm_panel *panel)
{
@@ -712,6 +800,10 @@ void drm_panel_unprepare(struct drm_panel *panel)
if (!panel)
return;
+ if (dev_WARN_ONCE(panel->dev, panel->funcs->atomic_create_state,
+ "Atomic panels have to be driven through the bridge\n"))
+ return;
+
/*
* If you are seeing the warning below it likely means one of two things:
* - Your panel driver incorrectly calls drm_panel_unprepare() in its
@@ -759,6 +851,9 @@ EXPORT_SYMBOL(drm_panel_unprepare);
* this call completes. This function cannot fail (as it is called from the
* enable call chain). There will always be a call to drm_panel_disable()
* afterwards.
+ *
+ * This function can not be used with the panels using atomic state, those are
+ * only supported through the bridge embedded in the panel.
*/
void drm_panel_enable(struct drm_panel *panel)
{
@@ -768,6 +863,10 @@ void drm_panel_enable(struct drm_panel *panel)
if (!panel)
return;
+ if (dev_WARN_ONCE(panel->dev, panel->funcs->atomic_create_state,
+ "Atomic panels have to be driven through the bridge\n"))
+ return;
+
if (panel->enabled) {
dev_warn(panel->dev, "Skipping enable of already enabled panel\n");
return;
@@ -809,6 +908,9 @@ EXPORT_SYMBOL(drm_panel_enable);
* This will typically turn off the panel's backlight or disable the display
* drivers. For smart panels it should still be possible to communicate with
* the integrated circuitry via any command bus after this call.
+ *
+ * This function can not be used with the panels using atomic state, those are
+ * only supported through the bridge embedded in the panel.
*/
void drm_panel_disable(struct drm_panel *panel)
{
@@ -818,6 +920,10 @@ void drm_panel_disable(struct drm_panel *panel)
if (!panel)
return;
+ if (dev_WARN_ONCE(panel->dev, panel->funcs->atomic_create_state,
+ "Atomic panels have to be driven through the bridge\n"))
+ return;
+
/*
* If you are seeing the warning below it likely means one of two things:
* - Your panel driver incorrectly calls drm_panel_disable() in its
@@ -890,6 +996,195 @@ int drm_panel_get_modes(struct drm_panel *panel,
}
EXPORT_SYMBOL(drm_panel_get_modes);
+/**
+ * __drm_atomic_helper_panel_state_init() - Initialize a panel state to its
+ * default
+ * @state: panel state to initialize
+ * @panel: the panel this state refers to
+ *
+ * @state is assumed to be zeroed.
+ *
+ * Initializes the panel state to default values. This is meant to be called
+ * by the panel &drm_panel_funcs.atomic_create_state hook for panels that
+ * subclass the panel state.
+ */
+void __drm_atomic_helper_panel_state_init(struct drm_panel_state *state,
+ struct drm_panel *panel)
+{
+ __drm_atomic_helper_bridge_state_init(&state->base, &panel->bridge);
+}
+EXPORT_SYMBOL(__drm_atomic_helper_panel_state_init);
+
+/**
+ * __drm_atomic_helper_panel_duplicate_state() - Copy atomic panel state
+ * @panel: panel object
+ * @state: atomic panel state
+ *
+ * Copies atomic state from a panel's current state. This is meant to be
+ * called by the panel &drm_panel_funcs.atomic_duplicate_state hook for panels
+ * that subclass the panel state.
+ */
+void __drm_atomic_helper_panel_duplicate_state(struct drm_panel *panel,
+ struct drm_panel_state *state)
+{
+ __drm_atomic_helper_bridge_duplicate_state(&panel->bridge, &state->base);
+}
+EXPORT_SYMBOL(__drm_atomic_helper_panel_duplicate_state);
+
+/**
+ * drm_atomic_helper_panel_create_state() - default
+ * &drm_panel_funcs.atomic_create_state hook for panels
+ * @panel: panel object
+ *
+ * Allocates and initializes a pristine &drm_panel_state. This is meant to be
+ * used by panels that don't subclass the panel state.
+ *
+ * Return:
+ *
+ * Pointer to the new panel state, or an error pointer on failure.
+ */
+struct drm_panel_state *
+drm_atomic_helper_panel_create_state(struct drm_panel *panel)
+{
+ struct drm_panel_state *state;
+
+ state = kzalloc_obj(*state);
+ if (!state)
+ return ERR_PTR(-ENOMEM);
+
+ __drm_atomic_helper_panel_state_init(state, panel);
+
+ return state;
+}
+EXPORT_SYMBOL(drm_atomic_helper_panel_create_state);
+
+/**
+ * drm_atomic_helper_panel_duplicate_state() - default
+ * &drm_panel_funcs.atomic_duplicate_state hook for panels
+ * @panel: panel object
+ *
+ * Allocates a new panel state and initializes it with the current panel state
+ * values. This is meant to be used by panels that don't subclass the panel
+ * state.
+ *
+ * Return:
+ *
+ * Pointer to the new panel state, or NULL on failure.
+ */
+struct drm_panel_state *
+drm_atomic_helper_panel_duplicate_state(struct drm_panel *panel)
+{
+ struct drm_panel_state *state;
+
+ if (drm_WARN_ON(panel->bridge.dev, !panel->bridge.base.state))
+ return NULL;
+
+ state = kzalloc_obj(*state);
+ if (state)
+ __drm_atomic_helper_panel_duplicate_state(panel, state);
+
+ return state;
+}
+EXPORT_SYMBOL(drm_atomic_helper_panel_duplicate_state);
+
+/**
+ * drm_atomic_helper_panel_destroy_state() - default
+ * &drm_panel_funcs.atomic_destroy_state hook for panels
+ * @panel: panel object
+ * @state: panel state to destroy
+ *
+ * Destroys a panel state previously created by
+ * drm_atomic_helper_panel_create_state() or
+ * drm_atomic_helper_panel_duplicate_state(). This is meant to be used by panels
+ * that don't subclass the panel state.
+ */
+void drm_atomic_helper_panel_destroy_state(struct drm_panel *panel,
+ struct drm_panel_state *state)
+{
+ kfree(state);
+}
+EXPORT_SYMBOL(drm_atomic_helper_panel_destroy_state);
+
+/**
+ * drm_atomic_get_old_panel_state() - get old panel state, if it exists
+ * @commit: global atomic state object
+ * @panel: panel to grab
+ *
+ * Return:
+ *
+ * The old panel state for the given panel, or NULL if the panel is not part of
+ * the global atomic state or does not implement the panel state callbacks.
+ */
+struct drm_panel_state *
+drm_atomic_get_old_panel_state(const struct drm_atomic_commit *commit,
+ struct drm_panel *panel)
+{
+ struct drm_bridge_state *bridge_state;
+
+ if (!panel->funcs->atomic_create_state)
+ return NULL;
+
+ bridge_state = drm_atomic_get_old_bridge_state(commit, &panel->bridge);
+ if (!bridge_state)
+ return NULL;
+
+ return drm_bridge_state_to_panel_state(bridge_state);
+}
+EXPORT_SYMBOL(drm_atomic_get_old_panel_state);
+
+/**
+ * drm_atomic_get_new_panel_state() - get new panel state, if it exists
+ * @commit: global atomic state object
+ * @panel: panel to grab
+ *
+ * Return:
+ *
+ * The new panel state for the given panel, or NULL if the panel is not part of
+ * the global atomic state or does not implement the panel state callbacks.
+ */
+struct drm_panel_state *
+drm_atomic_get_new_panel_state(const struct drm_atomic_commit *commit,
+ struct drm_panel *panel)
+{
+ struct drm_bridge_state *bridge_state;
+
+ if (!panel->funcs->atomic_create_state)
+ return NULL;
+
+ bridge_state = drm_atomic_get_new_bridge_state(commit, &panel->bridge);
+ if (!bridge_state)
+ return NULL;
+
+ return drm_bridge_state_to_panel_state(bridge_state);
+}
+EXPORT_SYMBOL(drm_atomic_get_new_panel_state);
+
+/**
+ * drm_panel_get_current_state() - get the current panel state
+ * @panel: panel object
+ *
+ * This function must be called with the modeset lock held.
+ *
+ * Return:
+ *
+ * The current panel state, or NULL if there is none or if the panel does not
+ * implement the panel state callbacks.
+ */
+struct drm_panel_state *drm_panel_get_current_state(struct drm_panel *panel)
+{
+ struct drm_bridge_state *bridge_state;
+
+ if (!panel || !panel->funcs->atomic_create_state)
+ return NULL;
+
+ bridge_state = drm_bridge_get_current_state(&panel->bridge);
+ if (!bridge_state)
+ return NULL;
+
+ return drm_bridge_state_to_panel_state(bridge_state);
+}
+EXPORT_SYMBOL(drm_panel_get_current_state);
+
/**
* drm_panel_get - Acquire a panel reference
* @panel: DRM panel
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h
index 58113ea7d63a..ff63bed934fa 100644
--- a/include/drm/drm_panel.h
+++ b/include/drm/drm_panel.h
@@ -35,6 +35,7 @@
struct backlight_device;
struct dentry;
struct device_node;
+struct drm_atomic_commit;
struct drm_bridge;
struct drm_connector;
struct drm_device;
@@ -44,6 +45,24 @@ struct display_timing;
enum drm_panel_orientation;
+/**
+ * struct drm_panel_state - Atomic panel state object
+ *
+ * Only panels implementing &drm_panel_funcs.atomic_create_state,
+ * &drm_panel_funcs.atomic_duplicate_state and
+ * &drm_panel_funcs.atomic_destroy_state have a panel state. Panel drivers
+ * that need to track additional state can subclass this structure.
+ */
+struct drm_panel_state {
+ /**
+ * @base: inherit from &drm_bridge_state
+ */
+ struct drm_bridge_state base;
+};
+
+#define drm_bridge_state_to_panel_state(bridge_state) \
+ container_of_const(bridge_state, struct drm_panel_state, base)
+
/**
* struct drm_panel_funcs - perform operations on a given panel
*
@@ -148,6 +167,62 @@ struct drm_panel_funcs {
* Allows panels to create panels-specific debugfs files.
*/
void (*debugfs_init)(struct drm_panel *panel, struct dentry *root);
+
+ /**
+ * @atomic_create_state:
+ *
+ * Allocate a pristine, initialized, state for the panel and return
+ * it. This callback must have no side effects: in particular, it must
+ * not affect the hardware state.
+ *
+ * Drivers that subclass &drm_panel_state should use
+ * __drm_atomic_helper_panel_state_init() to initialize the base
+ * structure, the other drivers should use
+ * drm_atomic_helper_panel_create_state().
+ *
+ * This function is optional, panels not implementing it don't have a
+ * panel state. It must be implemented together with
+ * @atomic_duplicate_state and @atomic_destroy_state.
+ *
+ * Return:
+ *
+ * A new, pristine, panel state instance or an error pointer on
+ * failure.
+ */
+ struct drm_panel_state *(*atomic_create_state)(struct drm_panel *panel);
+
+ /**
+ * @atomic_duplicate_state:
+ *
+ * Duplicate the current panel state and return it.
+ *
+ * Drivers that subclass &drm_panel_state should use
+ * __drm_atomic_helper_panel_duplicate_state() to duplicate the base
+ * structure, the other drivers should use
+ * drm_atomic_helper_panel_duplicate_state().
+ *
+ * This function is optional. It must be implemented together with
+ * @atomic_create_state and @atomic_destroy_state.
+ *
+ * Return:
+ *
+ * A valid &drm_panel_state object or NULL if the allocation fails.
+ */
+ struct drm_panel_state *(*atomic_duplicate_state)(struct drm_panel *panel);
+
+ /**
+ * @atomic_destroy_state:
+ *
+ * Destroy a panel state object previously allocated by
+ * @atomic_create_state or @atomic_duplicate_state. Drivers that don't
+ * subclass &drm_panel_state should use
+ * drm_atomic_helper_panel_destroy_state().
+ *
+ * This function is optional. It must be implemented together with
+ * @atomic_create_state and @atomic_duplicate_state.
+ */
+ void (*atomic_destroy_state)(struct drm_panel *panel,
+ struct drm_panel_state *state);
};
struct drm_panel_follower_funcs {
@@ -344,6 +419,26 @@ void drm_panel_disable(struct drm_panel *panel);
int drm_panel_get_modes(struct drm_panel *panel, struct drm_connector *connector);
+void __drm_atomic_helper_panel_state_init(struct drm_panel_state *state,
+ struct drm_panel *panel);
+void __drm_atomic_helper_panel_duplicate_state(struct drm_panel *panel,
+ struct drm_panel_state *state);
+struct drm_panel_state *
+drm_atomic_helper_panel_create_state(struct drm_panel *panel);
+struct drm_panel_state *
+drm_atomic_helper_panel_duplicate_state(struct drm_panel *panel);
+void drm_atomic_helper_panel_destroy_state(struct drm_panel *panel,
+ struct drm_panel_state *state);
+
+struct drm_panel_state *drm_panel_get_current_state(struct drm_panel *panel);
+
+struct drm_panel_state *
+drm_atomic_get_old_panel_state(const struct drm_atomic_commit *commit,
+ struct drm_panel *panel);
+struct drm_panel_state *
+drm_atomic_get_new_panel_state(const struct drm_atomic_commit *commit,
+ struct drm_panel *panel);
+
#if defined(CONFIG_OF) && IS_ENABLED(CONFIG_DRM_PANEL)
struct drm_panel *of_drm_find_panel(const struct device_node *np);
int drm_of_find_panel_or_bridge(const struct device_node *np,
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 5/9] drm/panel: add atomic_check callback
2026-10-08 13:14 [PATCH RFC 0/9] drm/panel: add atomic state Dmitry Baryshkov
` (3 preceding siblings ...)
2026-10-08 13:14 ` [PATCH RFC 4/9] drm/panel: add atomic state Dmitry Baryshkov
@ 2026-10-08 13:14 ` Dmitry Baryshkov
2026-10-08 13:14 ` [PATCH RFC 6/9] drm/panel: factor out the panel follower notifications Dmitry Baryshkov
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-10-08 13:14 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Jessica Zhang,
Konrad Dybcio, Val Packett
Cc: dri-devel, linux-kernel, linux-doc
A panel driver cannot reject a configuration it does not support, nor
compute its state for the configuration that is being requested: none of
its callbacks is called in the check phase of an atomic update.
Add an optional atomic_check callback, called from the atomic_check of
the bridge embedded in the panel. Following the other atomic_check
callbacks it receives the whole commit rather than individual object
states.
Add helpers returning the new CRTC and connector states for a panel, so
that panel drivers don't have to open-code the walk through the encoder.
The callback is only meant for the panels using atomic state, warn about
the panels implementing it without the state callbacks.
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/drm_panel.c | 84 +++++++++++++++++++++++++++++++++++++++++++++
include/drm/drm_panel.h | 30 ++++++++++++++++
2 files changed, 114 insertions(+)
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index 481caa294243..3a154be128a0 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -243,6 +243,19 @@ static void panel_bridge_debugfs_init(struct drm_bridge *bridge,
panel->funcs->debugfs_init(panel, root);
}
+static int panel_bridge_atomic_check(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct drm_panel *panel = drm_bridge_to_panel(bridge);
+
+ if (!panel->funcs->atomic_check)
+ return 0;
+
+ return panel->funcs->atomic_check(panel, crtc_state->state);
+}
+
static struct drm_bridge_state *
panel_bridge_atomic_create_state(struct drm_bridge *bridge)
{
@@ -297,6 +310,7 @@ static const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
.atomic_disable = panel_bridge_atomic_disable,
.atomic_post_disable = panel_bridge_atomic_post_disable,
.get_modes = panel_bridge_get_modes,
+ .atomic_check = panel_bridge_atomic_check,
.atomic_create_state = panel_bridge_atomic_create_state,
.atomic_duplicate_state = panel_bridge_atomic_duplicate_state,
.atomic_destroy_state = panel_bridge_atomic_destroy_state,
@@ -654,6 +668,9 @@ static void drm_panel_init(struct drm_panel *panel, struct device *dev,
!funcs->atomic_create_state != !funcs->atomic_destroy_state,
"Panel state callbacks have to be implemented together\n");
+ dev_WARN_ONCE(dev, !funcs->atomic_create_state && funcs->atomic_check,
+ "Atomic callbacks require the panel state callbacks\n");
+
INIT_LIST_HEAD(&panel->list);
INIT_LIST_HEAD(&panel->followers);
mutex_init(&panel->follower_lock);
@@ -1185,6 +1202,73 @@ struct drm_panel_state *drm_panel_get_current_state(struct drm_panel *panel)
}
EXPORT_SYMBOL(drm_panel_get_current_state);
+/**
+ * drm_atomic_get_new_crtc_state_for_panel() - get new CRTC state for a panel
+ * @commit: global atomic state object
+ * @panel: panel to get the CRTC state for
+ *
+ * This function returns the new state of the CRTC that drives @panel as of
+ * @commit. It is meant to be used from &drm_panel_funcs.atomic_check and the
+ * enable path. In the disable path the panel might not be driven by any CRTC
+ * anymore.
+ *
+ * Return:
+ *
+ * The new CRTC state, or NULL if the panel is not driven by a CRTC that is
+ * part of the global atomic state.
+ */
+struct drm_crtc_state *
+drm_atomic_get_new_crtc_state_for_panel(struct drm_atomic_commit *commit,
+ struct drm_panel *panel)
+{
+ struct drm_encoder *encoder = panel->bridge.encoder;
+ struct drm_crtc *crtc;
+
+ if (!encoder)
+ return NULL;
+
+ crtc = drm_atomic_get_new_crtc_for_encoder(commit, encoder);
+ if (!crtc)
+ return NULL;
+
+ return drm_atomic_get_new_crtc_state(commit, crtc);
+}
+EXPORT_SYMBOL(drm_atomic_get_new_crtc_state_for_panel);
+
+/**
+ * drm_atomic_get_new_connector_state_for_panel() - get new connector state for
+ * a panel
+ * @commit: global atomic state object
+ * @panel: panel to get the connector state for
+ *
+ * This function returns the new state of the connector that @panel is routed
+ * to as of @commit. It is meant to be used from &drm_panel_funcs.atomic_check
+ * and the enable path. In the disable path the panel might not be routed to
+ * any connector anymore.
+ *
+ * Return:
+ *
+ * The new connector state, or NULL if the panel is not routed to a connector
+ * that is part of the global atomic state.
+ */
+struct drm_connector_state *
+drm_atomic_get_new_connector_state_for_panel(const struct drm_atomic_commit *commit,
+ struct drm_panel *panel)
+{
+ struct drm_encoder *encoder = panel->bridge.encoder;
+ struct drm_connector *connector;
+
+ if (!encoder)
+ return NULL;
+
+ connector = drm_atomic_get_new_connector_for_encoder(commit, encoder);
+ if (!connector)
+ return NULL;
+
+ return drm_atomic_get_new_connector_state(commit, connector);
+}
+EXPORT_SYMBOL(drm_atomic_get_new_connector_state_for_panel);
+
/**
* drm_panel_get - Acquire a panel reference
* @panel: DRM panel
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h
index ff63bed934fa..a5879b7b986b 100644
--- a/include/drm/drm_panel.h
+++ b/include/drm/drm_panel.h
@@ -38,6 +38,8 @@ struct device_node;
struct drm_atomic_commit;
struct drm_bridge;
struct drm_connector;
+struct drm_connector_state;
+struct drm_crtc_state;
struct drm_device;
struct drm_panel_follower;
struct drm_panel;
@@ -168,6 +170,28 @@ struct drm_panel_funcs {
*/
void (*debugfs_init)(struct drm_panel *panel, struct dentry *root);
+ /**
+ * @atomic_check:
+ *
+ * Check that the configuration requested in @commit can be handled by
+ * the panel and compute the new panel state. The new panel state can
+ * be retrieved with drm_atomic_get_new_panel_state(), the CRTC and
+ * connector states with drm_atomic_get_new_crtc_state_for_panel() and
+ * drm_atomic_get_new_connector_state_for_panel().
+ *
+ * This function is called in the check phase of an atomic update, it
+ * must not touch the hardware or any persistent driver data.
+ *
+ * This function is optional. It can only be implemented together with
+ * @atomic_create_state.
+ *
+ * Return:
+ *
+ * Zero if the check passed, a negative error code otherwise.
+ */
+ int (*atomic_check)(struct drm_panel *panel,
+ struct drm_atomic_commit *commit);
+
/**
* @atomic_create_state:
*
@@ -438,6 +462,12 @@ drm_atomic_get_old_panel_state(const struct drm_atomic_commit *commit,
struct drm_panel_state *
drm_atomic_get_new_panel_state(const struct drm_atomic_commit *commit,
struct drm_panel *panel);
+struct drm_crtc_state *
+drm_atomic_get_new_crtc_state_for_panel(struct drm_atomic_commit *commit,
+ struct drm_panel *panel);
+struct drm_connector_state *
+drm_atomic_get_new_connector_state_for_panel(const struct drm_atomic_commit *commit,
+ struct drm_panel *panel);
#if defined(CONFIG_OF) && IS_ENABLED(CONFIG_DRM_PANEL)
struct drm_panel *of_drm_find_panel(const struct device_node *np);
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 6/9] drm/panel: factor out the panel follower notifications
2026-10-08 13:14 [PATCH RFC 0/9] drm/panel: add atomic state Dmitry Baryshkov
` (4 preceding siblings ...)
2026-10-08 13:14 ` [PATCH RFC 5/9] drm/panel: add atomic_check callback Dmitry Baryshkov
@ 2026-10-08 13:14 ` Dmitry Baryshkov
2026-10-08 13:14 ` [PATCH RFC 7/9] drm/panel: add atomic variants of the enable and disable callbacks Dmitry Baryshkov
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-10-08 13:14 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Jessica Zhang,
Konrad Dybcio, Val Packett
Cc: dri-devel, linux-kernel, linux-doc
drm_panel_prepare(), drm_panel_unprepare(), drm_panel_enable() and
drm_panel_disable() each open-code the loop notifying the panel
followers. The atomic variants of these functions are going to need the
same notifications, which would duplicate each of the loops.
Move the loops to helpers. No functional change.
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/drm_panel.c | 116 +++++++++++++++++++++++++++++---------------
1 file changed, 76 insertions(+), 40 deletions(-)
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index 3a154be128a0..ed0d16e04eae 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -84,6 +84,78 @@ struct panel_bridge {
u32 connector_type;
};
+static void drm_panel_followers_prepared(struct drm_panel *panel)
+{
+ struct drm_panel_follower *follower;
+ int ret;
+
+ lockdep_assert_held(&panel->follower_lock);
+
+ list_for_each_entry(follower, &panel->followers, list) {
+ if (!follower->funcs->panel_prepared)
+ continue;
+
+ ret = follower->funcs->panel_prepared(follower);
+ if (ret < 0)
+ dev_info(panel->dev, "%ps failed: %d\n",
+ follower->funcs->panel_prepared, ret);
+ }
+}
+
+static void drm_panel_followers_unpreparing(struct drm_panel *panel)
+{
+ struct drm_panel_follower *follower;
+ int ret;
+
+ lockdep_assert_held(&panel->follower_lock);
+
+ list_for_each_entry(follower, &panel->followers, list) {
+ if (!follower->funcs->panel_unpreparing)
+ continue;
+
+ ret = follower->funcs->panel_unpreparing(follower);
+ if (ret < 0)
+ dev_info(panel->dev, "%ps failed: %d\n",
+ follower->funcs->panel_unpreparing, ret);
+ }
+}
+
+static void drm_panel_followers_enabled(struct drm_panel *panel)
+{
+ struct drm_panel_follower *follower;
+ int ret;
+
+ lockdep_assert_held(&panel->follower_lock);
+
+ list_for_each_entry(follower, &panel->followers, list) {
+ if (!follower->funcs->panel_enabled)
+ continue;
+
+ ret = follower->funcs->panel_enabled(follower);
+ if (ret < 0)
+ dev_info(panel->dev, "%ps failed: %d\n",
+ follower->funcs->panel_enabled, ret);
+ }
+}
+
+static void drm_panel_followers_disabling(struct drm_panel *panel)
+{
+ struct drm_panel_follower *follower;
+ int ret;
+
+ lockdep_assert_held(&panel->follower_lock);
+
+ list_for_each_entry(follower, &panel->followers, list) {
+ if (!follower->funcs->panel_disabling)
+ continue;
+
+ ret = follower->funcs->panel_disabling(follower);
+ if (ret < 0)
+ dev_info(panel->dev, "%ps failed: %d\n",
+ follower->funcs->panel_disabling, ret);
+ }
+}
+
static int panel_bridge_connector_get_modes(struct drm_connector *connector)
{
struct drm_panel *panel = drm_connector_to_panel(connector);
@@ -758,7 +830,6 @@ EXPORT_SYMBOL(devm_drm_panel_add);
*/
void drm_panel_prepare(struct drm_panel *panel)
{
- struct drm_panel_follower *follower;
int ret;
if (!panel)
@@ -782,15 +853,7 @@ void drm_panel_prepare(struct drm_panel *panel)
}
panel->prepared = true;
- list_for_each_entry(follower, &panel->followers, list) {
- if (!follower->funcs->panel_prepared)
- continue;
-
- ret = follower->funcs->panel_prepared(follower);
- if (ret < 0)
- dev_info(panel->dev, "%ps failed: %d\n",
- follower->funcs->panel_prepared, ret);
- }
+ drm_panel_followers_prepared(panel);
exit:
mutex_unlock(&panel->follower_lock);
@@ -811,7 +874,6 @@ EXPORT_SYMBOL(drm_panel_prepare);
*/
void drm_panel_unprepare(struct drm_panel *panel)
{
- struct drm_panel_follower *follower;
int ret;
if (!panel)
@@ -837,15 +899,7 @@ void drm_panel_unprepare(struct drm_panel *panel)
mutex_lock(&panel->follower_lock);
- list_for_each_entry(follower, &panel->followers, list) {
- if (!follower->funcs->panel_unpreparing)
- continue;
-
- ret = follower->funcs->panel_unpreparing(follower);
- if (ret < 0)
- dev_info(panel->dev, "%ps failed: %d\n",
- follower->funcs->panel_unpreparing, ret);
- }
+ drm_panel_followers_unpreparing(panel);
if (panel->funcs && panel->funcs->unprepare) {
ret = panel->funcs->unprepare(panel);
@@ -874,7 +928,6 @@ EXPORT_SYMBOL(drm_panel_unprepare);
*/
void drm_panel_enable(struct drm_panel *panel)
{
- struct drm_panel_follower *follower;
int ret;
if (!panel)
@@ -903,15 +956,7 @@ void drm_panel_enable(struct drm_panel *panel)
DRM_DEV_INFO(panel->dev, "failed to enable backlight: %d\n",
ret);
- list_for_each_entry(follower, &panel->followers, list) {
- if (!follower->funcs->panel_enabled)
- continue;
-
- ret = follower->funcs->panel_enabled(follower);
- if (ret < 0)
- dev_info(panel->dev, "%ps failed: %d\n",
- follower->funcs->panel_enabled, ret);
- }
+ drm_panel_followers_enabled(panel);
exit:
mutex_unlock(&panel->follower_lock);
@@ -931,7 +976,6 @@ EXPORT_SYMBOL(drm_panel_enable);
*/
void drm_panel_disable(struct drm_panel *panel)
{
- struct drm_panel_follower *follower;
int ret;
if (!panel)
@@ -957,15 +1001,7 @@ void drm_panel_disable(struct drm_panel *panel)
mutex_lock(&panel->follower_lock);
- list_for_each_entry(follower, &panel->followers, list) {
- if (!follower->funcs->panel_disabling)
- continue;
-
- ret = follower->funcs->panel_disabling(follower);
- if (ret < 0)
- dev_info(panel->dev, "%ps failed: %d\n",
- follower->funcs->panel_disabling, ret);
- }
+ drm_panel_followers_disabling(panel);
ret = backlight_disable(panel->backlight);
if (ret < 0)
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 7/9] drm/panel: add atomic variants of the enable and disable callbacks
2026-10-08 13:14 [PATCH RFC 0/9] drm/panel: add atomic state Dmitry Baryshkov
` (5 preceding siblings ...)
2026-10-08 13:14 ` [PATCH RFC 6/9] drm/panel: factor out the panel follower notifications Dmitry Baryshkov
@ 2026-10-08 13:14 ` Dmitry Baryshkov
2026-10-08 13:14 ` [PATCH RFC 8/9] drm/panel: add atomic_print_state callback Dmitry Baryshkov
2026-10-08 13:14 ` [PATCH RFC 9/9] drm/panel: novatek-nt35950: use the atomic state to select the mode Dmitry Baryshkov
8 siblings, 0 replies; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-10-08 13:14 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Jessica Zhang,
Konrad Dybcio, Val Packett
Cc: dri-devel, linux-kernel, linux-doc
The prepare, enable, disable and unprepare callbacks only receive the
panel. A panel driver that tracks its configuration in the atomic state
cannot tell which state is being applied, so it cannot program the
panel accordingly.
Add atomic_prepare, atomic_enable, atomic_disable and atomic_unprepare
callbacks receiving the commit. They are called by a second set of
functions, used by the bridge embedded in the panel, which fall back to
the legacy callbacks for the panels not implementing the atomic ones.
The callbacks are only meant for the panels using atomic state, which
are never driven through drm_panel_prepare() and its counterparts. Warn
about the panels implementing them without the state callbacks.
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/drm_panel.c | 125 +++++++++++++++++++++++++++++++++++++++++---
include/drm/drm_panel.h | 54 +++++++++++++++++++
2 files changed, 173 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index ed0d16e04eae..a2cd043fe146 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -56,7 +56,10 @@ static LIST_HEAD(panel_list);
* drm_atomic_helper_panel_create_state(),
* drm_atomic_helper_panel_duplicate_state() and
* drm_atomic_helper_panel_destroy_state() helpers or with their own functions
- * if they subclass the state.
+ * if they subclass the state. The state is computed and validated
+ * in &drm_panel_funcs.atomic_check and applied in
+ * &drm_panel_funcs.atomic_prepare, &drm_panel_funcs.atomic_enable,
+ * &drm_panel_funcs.atomic_disable and &drm_panel_funcs.atomic_unprepare.
*
* The panel state is the state of the &drm_bridge embedded in the panel. It
* only exists once that bridge has been attached to an encoder, so the panels
@@ -221,6 +224,112 @@ static void panel_bridge_detach(struct drm_bridge *bridge)
drm_connector_cleanup(connector);
}
+static void drm_panel_atomic_prepare(struct drm_panel *panel,
+ struct drm_atomic_commit *commit)
+{
+ const struct drm_panel_funcs *funcs = panel->funcs;
+ int ret = 0;
+
+ if (panel->prepared) {
+ dev_warn(panel->dev, "Skipping prepare of already prepared panel\n");
+ return;
+ }
+
+ guard(mutex)(&panel->follower_lock);
+
+ if (funcs->atomic_prepare)
+ ret = funcs->atomic_prepare(panel, commit);
+ else if (funcs->prepare)
+ ret = funcs->prepare(panel);
+ if (ret < 0)
+ return;
+ panel->prepared = true;
+
+ drm_panel_followers_prepared(panel);
+}
+
+static void drm_panel_atomic_unprepare(struct drm_panel *panel,
+ struct drm_atomic_commit *commit)
+{
+ const struct drm_panel_funcs *funcs = panel->funcs;
+ int ret = 0;
+
+ if (!panel->prepared) {
+ dev_warn(panel->dev, "Skipping unprepare of already unprepared panel\n");
+ return;
+ }
+
+ guard(mutex)(&panel->follower_lock);
+
+ drm_panel_followers_unpreparing(panel);
+
+ if (funcs->atomic_unprepare)
+ ret = funcs->atomic_unprepare(panel, commit);
+ else if (funcs->unprepare)
+ ret = funcs->unprepare(panel);
+ if (ret < 0)
+ return;
+ panel->prepared = false;
+}
+
+static void drm_panel_atomic_enable(struct drm_panel *panel,
+ struct drm_atomic_commit *commit)
+{
+ const struct drm_panel_funcs *funcs = panel->funcs;
+ int ret = 0;
+
+ if (panel->enabled) {
+ dev_warn(panel->dev, "Skipping enable of already enabled panel\n");
+ return;
+ }
+
+ guard(mutex)(&panel->follower_lock);
+
+ if (funcs->atomic_enable)
+ ret = funcs->atomic_enable(panel, commit);
+ else if (funcs->enable)
+ ret = funcs->enable(panel);
+ if (ret < 0)
+ return;
+ panel->enabled = true;
+
+ ret = backlight_enable(panel->backlight);
+ if (ret < 0)
+ DRM_DEV_INFO(panel->dev, "failed to enable backlight: %d\n",
+ ret);
+
+ drm_panel_followers_enabled(panel);
+}
+
+static void drm_panel_atomic_disable(struct drm_panel *panel,
+ struct drm_atomic_commit *commit)
+{
+ const struct drm_panel_funcs *funcs = panel->funcs;
+ int ret = 0;
+
+ if (!panel->enabled) {
+ dev_warn(panel->dev, "Skipping disable of already disabled panel\n");
+ return;
+ }
+
+ guard(mutex)(&panel->follower_lock);
+
+ drm_panel_followers_disabling(panel);
+
+ ret = backlight_disable(panel->backlight);
+ if (ret < 0)
+ DRM_DEV_INFO(panel->dev, "failed to disable backlight: %d\n",
+ ret);
+
+ if (funcs->atomic_disable)
+ ret = funcs->atomic_disable(panel, commit);
+ else if (funcs->disable)
+ ret = funcs->disable(panel);
+ if (ret < 0)
+ return;
+ panel->enabled = false;
+}
+
static void panel_bridge_atomic_pre_enable(struct drm_bridge *bridge,
struct drm_atomic_commit *atomic_state)
{
@@ -237,7 +346,7 @@ static void panel_bridge_atomic_pre_enable(struct drm_bridge *bridge,
if (old_crtc_state && old_crtc_state->self_refresh_active)
return;
- drm_panel_prepare(panel);
+ drm_panel_atomic_prepare(panel, atomic_state);
}
static void panel_bridge_atomic_enable(struct drm_bridge *bridge,
@@ -256,7 +365,7 @@ static void panel_bridge_atomic_enable(struct drm_bridge *bridge,
if (old_crtc_state && old_crtc_state->self_refresh_active)
return;
- drm_panel_enable(panel);
+ drm_panel_atomic_enable(panel, atomic_state);
}
static void panel_bridge_atomic_disable(struct drm_bridge *bridge,
@@ -275,7 +384,7 @@ static void panel_bridge_atomic_disable(struct drm_bridge *bridge,
if (new_crtc_state && new_crtc_state->self_refresh_active)
return;
- drm_panel_disable(panel);
+ drm_panel_atomic_disable(panel, atomic_state);
}
static void panel_bridge_atomic_post_disable(struct drm_bridge *bridge,
@@ -294,7 +403,7 @@ static void panel_bridge_atomic_post_disable(struct drm_bridge *bridge,
if (new_crtc_state && new_crtc_state->self_refresh_active)
return;
- drm_panel_unprepare(panel);
+ drm_panel_atomic_unprepare(panel, atomic_state);
}
static int panel_bridge_get_modes(struct drm_bridge *bridge,
@@ -740,7 +849,11 @@ static void drm_panel_init(struct drm_panel *panel, struct device *dev,
!funcs->atomic_create_state != !funcs->atomic_destroy_state,
"Panel state callbacks have to be implemented together\n");
- dev_WARN_ONCE(dev, !funcs->atomic_create_state && funcs->atomic_check,
+ dev_WARN_ONCE(dev,
+ !funcs->atomic_create_state &&
+ (funcs->atomic_check || funcs->atomic_prepare ||
+ funcs->atomic_enable || funcs->atomic_disable ||
+ funcs->atomic_unprepare),
"Atomic callbacks require the panel state callbacks\n");
INIT_LIST_HEAD(&panel->list);
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h
index a5879b7b986b..dedd792d2c52 100644
--- a/include/drm/drm_panel.h
+++ b/include/drm/drm_panel.h
@@ -192,6 +192,60 @@ struct drm_panel_funcs {
int (*atomic_check)(struct drm_panel *panel,
struct drm_atomic_commit *commit);
+ /**
+ * @atomic_prepare:
+ *
+ * Atomic variant of @prepare, used instead of it if implemented. The
+ * new panel state can be retrieved from @commit with
+ * drm_atomic_get_new_panel_state().
+ *
+ * Panels using atomic state can only be used through the bridge
+ * embedded in the panel, drm_panel_prepare() and its counterparts
+ * will refuse to handle them.
+ *
+ * This function is optional. It can only be implemented together with
+ * @atomic_create_state.
+ */
+ int (*atomic_prepare)(struct drm_panel *panel,
+ struct drm_atomic_commit *commit);
+
+ /**
+ * @atomic_enable:
+ *
+ * Atomic variant of @enable, used instead of it if implemented. See
+ * @atomic_prepare for details.
+ *
+ * This function is optional.
+ */
+ int (*atomic_enable)(struct drm_panel *panel,
+ struct drm_atomic_commit *commit);
+
+ /**
+ * @atomic_disable:
+ *
+ * Atomic variant of @disable, used instead of it if implemented. See
+ * @atomic_prepare for details.
+ *
+ * The panel might not be routed to a connector or driven by a CRTC
+ * anymore in @commit, the state the panel has been enabled with can
+ * be retrieved with drm_atomic_get_old_panel_state().
+ *
+ * This function is optional.
+ */
+ int (*atomic_disable)(struct drm_panel *panel,
+ struct drm_atomic_commit *commit);
+
+ /**
+ * @atomic_unprepare:
+ *
+ * Atomic variant of @unprepare, used instead of it if implemented.
+ * See @atomic_prepare and @atomic_disable for details.
+ *
+ * This function is optional.
+ */
+ int (*atomic_unprepare)(struct drm_panel *panel,
+ struct drm_atomic_commit *commit);
+
/**
* @atomic_create_state:
*
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 8/9] drm/panel: add atomic_print_state callback
2026-10-08 13:14 [PATCH RFC 0/9] drm/panel: add atomic state Dmitry Baryshkov
` (6 preceding siblings ...)
2026-10-08 13:14 ` [PATCH RFC 7/9] drm/panel: add atomic variants of the enable and disable callbacks Dmitry Baryshkov
@ 2026-10-08 13:14 ` Dmitry Baryshkov
2026-10-08 13:14 ` [PATCH RFC 9/9] drm/panel: novatek-nt35950: use the atomic state to select the mode Dmitry Baryshkov
8 siblings, 0 replies; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-10-08 13:14 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Jessica Zhang,
Konrad Dybcio, Val Packett
Cc: dri-devel, linux-kernel, linux-doc
Panel drivers subclassing drm_panel_state have no way to get their part
of the state into the atomic state dumps, as the state is printed
through the bridge embedded in the panel.
Add an optional atomic_print_state callback, called from the
atomic_print_state callback of the embedded bridge.
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/drm_panel.c | 14 +++++++++++++-
include/drm/drm_panel.h | 11 +++++++++++
2 files changed, 24 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index a2cd043fe146..9be36c5b1037 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -483,6 +483,17 @@ static void panel_bridge_atomic_destroy_state(struct drm_bridge *bridge,
drm_bridge_state_to_panel_state(bridge_state));
}
+static void panel_bridge_atomic_print_state(struct drm_printer *p,
+ const struct drm_bridge_state *bridge_state)
+{
+ struct drm_bridge *bridge = drm_priv_to_bridge(bridge_state->base.obj);
+ struct drm_panel *panel = drm_bridge_to_panel(bridge);
+
+ if (panel->funcs->atomic_print_state)
+ panel->funcs->atomic_print_state(p,
+ drm_bridge_state_to_panel_state(bridge_state));
+}
+
static const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
.attach = panel_bridge_attach,
.detach = panel_bridge_detach,
@@ -495,6 +506,7 @@ static const struct drm_bridge_funcs panel_bridge_bridge_funcs = {
.atomic_create_state = panel_bridge_atomic_create_state,
.atomic_duplicate_state = panel_bridge_atomic_duplicate_state,
.atomic_destroy_state = panel_bridge_atomic_destroy_state,
+ .atomic_print_state = panel_bridge_atomic_print_state,
.atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt,
.debugfs_init = panel_bridge_debugfs_init,
};
@@ -853,7 +865,7 @@ static void drm_panel_init(struct drm_panel *panel, struct device *dev,
!funcs->atomic_create_state &&
(funcs->atomic_check || funcs->atomic_prepare ||
funcs->atomic_enable || funcs->atomic_disable ||
- funcs->atomic_unprepare),
+ funcs->atomic_unprepare || funcs->atomic_print_state),
"Atomic callbacks require the panel state callbacks\n");
INIT_LIST_HEAD(&panel->list);
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h
index dedd792d2c52..20e5b1e5f50d 100644
--- a/include/drm/drm_panel.h
+++ b/include/drm/drm_panel.h
@@ -43,6 +43,7 @@ struct drm_crtc_state;
struct drm_device;
struct drm_panel_follower;
struct drm_panel;
+struct drm_printer;
struct display_timing;
enum drm_panel_orientation;
@@ -301,6 +302,16 @@ struct drm_panel_funcs {
*/
void (*atomic_destroy_state)(struct drm_panel *panel,
struct drm_panel_state *state);
+
+ /**
+ * @atomic_print_state:
+ *
+ * If driver subclasses &struct drm_panel_state, it should implement
+ * this optional hook for printing additional driver specific state.
+ * It can only be implemented together with @atomic_create_state.
+ */
+ void (*atomic_print_state)(struct drm_printer *p,
+ const struct drm_panel_state *state);
};
struct drm_panel_follower_funcs {
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH RFC 9/9] drm/panel: novatek-nt35950: use the atomic state to select the mode
2026-10-08 13:14 [PATCH RFC 0/9] drm/panel: add atomic state Dmitry Baryshkov
` (7 preceding siblings ...)
2026-10-08 13:14 ` [PATCH RFC 8/9] drm/panel: add atomic_print_state callback Dmitry Baryshkov
@ 2026-10-08 13:14 ` Dmitry Baryshkov
8 siblings, 0 replies; 10+ messages in thread
From: Dmitry Baryshkov @ 2026-10-08 13:14 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Luca Ceresoli, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Jessica Zhang,
Konrad Dybcio, Val Packett
Cc: dri-devel, linux-kernel, linux-doc
The compression, scaler and display output settings of the NT35950
depend on the mode being set. As the panel has no access to the
configuration being committed, the driver saves the connector pointer in
get_modes() and then dereferences connector->state->crtc->state from the
prepare() callback. This is done without holding the modeset locks and
it reads the current state of the objects rather than the state being
committed. Moreover, if none of the panel modes match, the driver
silently programs the panel for the first mode instead of rejecting the
configuration.
Subclass the panel state to store the parameters of the selected mode.
Look the mode up in atomic_check(), failing the commit if the panel
doesn't support it, and use the stored parameters in atomic_prepare().
Switch unprepare() to its atomic variant too, so that the driver uses a
single set of callbacks.
Assisted-by: LLM
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
---
drivers/gpu/drm/panel/panel-novatek-nt35950.c | 159 +++++++++++++++++++-------
1 file changed, 117 insertions(+), 42 deletions(-)
diff --git a/drivers/gpu/drm/panel/panel-novatek-nt35950.c b/drivers/gpu/drm/panel/panel-novatek-nt35950.c
index 94aa6489d99f..f5161f11b1f5 100644
--- a/drivers/gpu/drm/panel/panel-novatek-nt35950.c
+++ b/drivers/gpu/drm/panel/panel-novatek-nt35950.c
@@ -11,12 +11,15 @@
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <drm/drm_atomic.h>
#include <drm/drm_connector.h>
#include <drm/drm_crtc.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_modes.h>
#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
#define MCS_CMD_MAUCCTR 0xf0 /* Manufacturer command enable */
#define MCS_PARAM_SCALER_FUNCTION 0x58 /* Scale-up function */
@@ -51,16 +54,19 @@
struct nt35950 {
struct drm_panel panel;
- struct drm_connector *connector;
struct mipi_dsi_device *dsi[2];
struct regulator_bulk_data vregs[NT35950_VREG_MAX];
struct gpio_desc *reset_gpio;
const struct nt35950_panel_desc *desc;
- int cur_mode;
u8 last_page;
};
+struct nt35950_state {
+ struct drm_panel_state base;
+ const struct nt35950_panel_mode *mode_data;
+};
+
struct nt35950_panel_mode {
const struct drm_display_mode mode;
@@ -88,6 +94,9 @@ static inline struct nt35950 *to_nt35950(struct drm_panel *panel)
return container_of(panel, struct nt35950, panel);
}
+#define to_nt35950_state(panel_state) \
+ container_of_const(panel_state, struct nt35950_state, base)
+
static void nt35950_reset(struct nt35950 *nt)
{
gpiod_set_value_cansleep(nt->reset_gpio, 1);
@@ -212,61 +221,37 @@ static void nt35950_inject_black_image(struct mipi_dsi_multi_context *dsi_ctx)
/*
* nt35950_set_dispout - Set Display Output register parameters
- * @nt: Main driver structure
* @dsi_ctx: context for mipi_dsi functions
+ * @mode_data: Parameters of the mode being set
*/
static void nt35950_set_dispout(struct mipi_dsi_multi_context *dsi_ctx,
- struct nt35950 *nt)
+ const struct nt35950_panel_mode *mode_data)
{
u8 cmd_dispout[] = { MCS_PARAM_DISP_OUTPUT_CTRL, 0x00 };
- const struct nt35950_panel_mode *mode_data = nt->desc->mode_data;
- if (mode_data[nt->cur_mode].is_video_mode)
+ if (mode_data->is_video_mode)
cmd_dispout[1] |= MCS_DISP_OUT_VIDEO_MODE;
- if (mode_data[nt->cur_mode].enable_sram)
+ if (mode_data->enable_sram)
cmd_dispout[1] |= MCS_DISP_OUT_SRAM_EN;
mipi_dsi_dcs_write_buffer_multi(dsi_ctx, cmd_dispout,
ARRAY_SIZE(cmd_dispout));
}
-static int nt35950_get_current_mode(struct nt35950 *nt)
-{
- struct drm_connector *connector = nt->connector;
- struct drm_crtc_state *crtc_state;
- int i;
-
- /* Return the default (first) mode if no info available yet */
- if (!connector->state || !connector->state->crtc)
- return 0;
-
- crtc_state = connector->state->crtc->state;
-
- for (i = 0; i < nt->desc->num_modes; i++) {
- if (drm_mode_match(&crtc_state->mode,
- &nt->desc->mode_data[i].mode,
- DRM_MODE_MATCH_TIMINGS | DRM_MODE_MATCH_CLOCK))
- return i;
- }
-
- return 0;
-}
-
-static int nt35950_on(struct nt35950 *nt)
+static int nt35950_on(struct nt35950 *nt,
+ const struct nt35950_panel_mode *mode_data)
{
- const struct nt35950_panel_mode *mode_data = nt->desc->mode_data;
struct mipi_dsi_device *dsi = nt->dsi[0];
struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
- nt->cur_mode = nt35950_get_current_mode(nt);
nt->dsi[0]->mode_flags |= MIPI_DSI_MODE_LPM;
nt->dsi[1]->mode_flags |= MIPI_DSI_MODE_LPM;
nt35950_set_cmd2_page(&dsi_ctx, nt, 0);
- nt35950_set_data_compression(&dsi_ctx, nt, mode_data[nt->cur_mode].compression);
- nt35950_set_scale_mode(&dsi_ctx, mode_data[nt->cur_mode].scaler_mode);
- nt35950_set_scaler(&dsi_ctx, mode_data[nt->cur_mode].scaler_on);
- nt35950_set_dispout(&dsi_ctx, nt);
+ nt35950_set_data_compression(&dsi_ctx, nt, mode_data->compression);
+ nt35950_set_scale_mode(&dsi_ctx, mode_data->scaler_mode);
+ nt35950_set_scaler(&dsi_ctx, mode_data->scaler_on);
+ nt35950_set_dispout(&dsi_ctx, mode_data);
mipi_dsi_dcs_set_tear_on_multi(&dsi_ctx, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
mipi_dsi_dcs_set_tear_scanline_multi(&dsi_ctx, 0);
@@ -354,11 +339,43 @@ static int nt35950_sharp_init_vregs(struct nt35950 *nt, struct device *dev)
return 0;
}
-static int nt35950_prepare(struct drm_panel *panel)
+static int nt35950_atomic_check(struct drm_panel *panel,
+ struct drm_atomic_commit *commit)
+{
+ struct nt35950 *nt = to_nt35950(panel);
+ struct drm_panel_state *panel_state;
+ struct drm_crtc_state *crtc_state;
+ int i;
+
+ panel_state = drm_atomic_get_new_panel_state(commit, panel);
+ crtc_state = drm_atomic_get_new_crtc_state_for_panel(commit, panel);
+ if (!panel_state || !crtc_state)
+ return -EINVAL;
+
+ for (i = 0; i < nt->desc->num_modes; i++) {
+ if (drm_mode_match(&crtc_state->mode,
+ &nt->desc->mode_data[i].mode,
+ DRM_MODE_MATCH_TIMINGS | DRM_MODE_MATCH_CLOCK)) {
+ to_nt35950_state(panel_state)->mode_data =
+ &nt->desc->mode_data[i];
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int nt35950_atomic_prepare(struct drm_panel *panel,
+ struct drm_atomic_commit *commit)
{
struct nt35950 *nt = to_nt35950(panel);
+ struct drm_panel_state *panel_state;
int ret;
+ panel_state = drm_atomic_get_new_panel_state(commit, panel);
+ if (!panel_state || !to_nt35950_state(panel_state)->mode_data)
+ return -EINVAL;
+
ret = regulator_enable(nt->vregs[0].consumer);
if (ret)
return ret;
@@ -380,7 +397,7 @@ static int nt35950_prepare(struct drm_panel *panel)
nt35950_reset(nt);
- ret = nt35950_on(nt);
+ ret = nt35950_on(nt, to_nt35950_state(panel_state)->mode_data);
end:
if (ret < 0) {
@@ -391,7 +408,8 @@ static int nt35950_prepare(struct drm_panel *panel)
return 0;
}
-static int nt35950_unprepare(struct drm_panel *panel)
+static int nt35950_atomic_unprepare(struct drm_panel *panel,
+ struct drm_atomic_commit *commit)
{
struct nt35950 *nt = to_nt35950(panel);
@@ -429,15 +447,72 @@ static int nt35950_get_modes(struct drm_panel *panel,
connector->display_info.bpc = 8;
connector->display_info.height_mm = nt->desc->mode_data[0].mode.height_mm;
connector->display_info.width_mm = nt->desc->mode_data[0].mode.width_mm;
- nt->connector = connector;
return nt->desc->num_modes;
}
+static struct drm_panel_state *
+nt35950_atomic_create_state(struct drm_panel *panel)
+{
+ struct nt35950_state *state;
+
+ state = kzalloc_obj(*state);
+ if (!state)
+ return ERR_PTR(-ENOMEM);
+
+ __drm_atomic_helper_panel_state_init(&state->base, panel);
+
+ return &state->base;
+}
+
+static struct drm_panel_state *
+nt35950_atomic_duplicate_state(struct drm_panel *panel)
+{
+ struct drm_panel_state *old_state = drm_panel_get_current_state(panel);
+ struct nt35950_state *state;
+
+ if (!old_state)
+ return NULL;
+
+ state = kzalloc_obj(*state);
+ if (!state)
+ return NULL;
+
+ __drm_atomic_helper_panel_duplicate_state(panel, &state->base);
+ state->mode_data = to_nt35950_state(old_state)->mode_data;
+
+ return &state->base;
+}
+
+static void nt35950_atomic_destroy_state(struct drm_panel *panel,
+ struct drm_panel_state *state)
+{
+ kfree(to_nt35950_state(state));
+}
+
+static void nt35950_atomic_print_state(struct drm_printer *p,
+ const struct drm_panel_state *state)
+{
+ const struct nt35950_panel_mode *mode_data =
+ to_nt35950_state(state)->mode_data;
+
+ if (!mode_data)
+ return;
+
+ drm_printf_indent(p, 1, "compression: %02x\n", mode_data->compression);
+ drm_printf_indent(p, 1, "scaler: %u, mode %02x\n",
+ mode_data->scaler_on, mode_data->scaler_mode);
+}
+
static const struct drm_panel_funcs nt35950_panel_funcs = {
- .prepare = nt35950_prepare,
- .unprepare = nt35950_unprepare,
+ .atomic_check = nt35950_atomic_check,
+ .atomic_prepare = nt35950_atomic_prepare,
+ .atomic_unprepare = nt35950_atomic_unprepare,
.get_modes = nt35950_get_modes,
+ .atomic_create_state = nt35950_atomic_create_state,
+ .atomic_duplicate_state = nt35950_atomic_duplicate_state,
+ .atomic_destroy_state = nt35950_atomic_destroy_state,
+ .atomic_print_state = nt35950_atomic_print_state,
};
static int nt35950_probe(struct mipi_dsi_device *dsi)
--
2.47.3
^ permalink raw reply [flat|nested] 10+ messages in thread