Forward connector luminance operations through panel and bridge connectors.
Allow bridges that own brightness control to expose luminance operations,
and route panel bridges to the panel backlight backend.

Attach the property only when a usable brightness backend is present and
CONFIG_DRM_LUMINANCE is enabled.

Signed-off-by: Mario Limonciello <[email protected]>
---
 drivers/gpu/drm/bridge/panel.c                | 39 +++++++++++
 .../gpu/drm/display/drm_bridge_connector.c    | 65 +++++++++++++++++++
 drivers/gpu/drm/drm_panel.c                   | 55 ++++++++++++++++
 include/drm/drm_bridge.h                      | 26 ++++++++
 include/drm/drm_panel.h                       |  5 ++
 5 files changed, 190 insertions(+)

diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
index 02388a3de626e..e447b959cf407 100644
--- a/drivers/gpu/drm/bridge/panel.c
+++ b/drivers/gpu/drm/bridge/panel.c
@@ -45,9 +45,29 @@ static int panel_bridge_connector_get_modes(struct 
drm_connector *connector)
        return drm_panel_get_modes(panel_bridge->panel, connector);
 }
 
+static void
+panel_bridge_connector_set_luminance(const struct drm_connector_state 
*conn_state)
+{
+       struct panel_bridge *panel_bridge =
+               drm_connector_to_panel_bridge(conn_state->connector);
+
+       drm_panel_set_luminance(panel_bridge->panel, conn_state->luminance);
+}
+
+static void
+panel_bridge_connector_disable_luminance(const struct drm_connector_state 
*conn_state)
+{
+       struct panel_bridge *panel_bridge =
+               drm_connector_to_panel_bridge(conn_state->connector);
+
+       drm_panel_disable_luminance(panel_bridge->panel);
+}
+
 static const struct drm_connector_helper_funcs
 panel_bridge_connector_helper_funcs = {
        .get_modes = panel_bridge_connector_get_modes,
+       .set_luminance = panel_bridge_connector_set_luminance,
+       .disable_luminance = panel_bridge_connector_disable_luminance,
 };
 
 static const struct drm_connector_funcs panel_bridge_connector_funcs = {
@@ -82,6 +102,10 @@ static int panel_bridge_attach(struct drm_bridge *bridge,
 
        drm_panel_bridge_set_orientation(connector, bridge);
 
+       ret = drm_panel_attach_luminance_property(panel_bridge->panel, 
connector);
+       if (ret)
+               return ret;
+
        drm_connector_attach_encoder(&panel_bridge->connector,
                                          encoder);
 
@@ -233,6 +257,21 @@ bool drm_bridge_is_panel(const struct drm_bridge *bridge)
 }
 EXPORT_SYMBOL(drm_bridge_is_panel);
 
+/**
+ * drm_panel_bridge_to_panel - return the panel wrapped by a panel bridge
+ * @bridge: panel bridge
+ *
+ * Return: the wrapped panel, or %NULL if @bridge isn't a panel bridge
+ */
+struct drm_panel *drm_panel_bridge_to_panel(struct drm_bridge *bridge)
+{
+       if (!drm_bridge_is_panel(bridge))
+               return NULL;
+
+       return drm_bridge_to_panel_bridge(bridge)->panel;
+}
+EXPORT_SYMBOL(drm_panel_bridge_to_panel);
+
 /**
  * drm_panel_bridge_add - Creates a &drm_bridge and &drm_connector that
  * just calls the appropriate functions from &drm_panel.
diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c 
b/drivers/gpu/drm/display/drm_bridge_connector.c
index 707d7143e475f..99c83b1acbe5a 100644
--- a/drivers/gpu/drm/display/drm_bridge_connector.c
+++ b/drivers/gpu/drm/display/drm_bridge_connector.c
@@ -18,6 +18,7 @@
 #include <drm/drm_edid.h>
 #include <drm/drm_managed.h>
 #include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_panel.h>
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
 #include <drm/display/drm_hdcp_helper.h>
@@ -123,6 +124,18 @@ struct drm_bridge_connector {
         * DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER).
         */
        struct drm_bridge *bridge_hdmi_cec;
+       /**
+        * @bridge_luminance:
+        *
+        * The last bridge in the chain that controls panel luminance.
+        */
+       struct drm_bridge *bridge_luminance;
+       /**
+        * @panel:
+        *
+        * The panel at the end of the bridge chain, if any.
+        */
+       struct drm_panel *panel;
 
        /**
         * @hdmi_funcs:
@@ -420,6 +433,30 @@ static int drm_bridge_connector_atomic_check(struct 
drm_connector *connector,
        return 0;
 }
 
+static void
+drm_bridge_connector_set_luminance(const struct drm_connector_state 
*conn_state)
+{
+       struct drm_bridge_connector *bridge_connector =
+               to_drm_bridge_connector(conn_state->connector);
+
+       drm_panel_set_luminance(bridge_connector->panel, conn_state->luminance);
+       if (bridge_connector->bridge_luminance)
+               bridge_connector->bridge_luminance->funcs->set_luminance
+                       (bridge_connector->bridge_luminance, 
conn_state->luminance);
+}
+
+static void
+drm_bridge_connector_disable_luminance(const struct drm_connector_state 
*conn_state)
+{
+       struct drm_bridge_connector *bridge_connector =
+               to_drm_bridge_connector(conn_state->connector);
+
+       drm_panel_disable_luminance(bridge_connector->panel);
+       if (bridge_connector->bridge_luminance)
+               bridge_connector->bridge_luminance->funcs->set_luminance
+                       (bridge_connector->bridge_luminance, 0);
+}
+
 static const struct drm_connector_helper_funcs 
drm_bridge_connector_helper_funcs = {
        .get_modes = drm_bridge_connector_get_modes,
        .detect_ctx = drm_bridge_connector_detect_ctx,
@@ -428,6 +465,8 @@ static const struct drm_connector_helper_funcs 
drm_bridge_connector_helper_funcs
        .enable_hpd = drm_bridge_connector_enable_hpd,
        .disable_hpd = drm_bridge_connector_disable_hpd,
        .atomic_check = drm_bridge_connector_atomic_check,
+       .set_luminance = drm_bridge_connector_set_luminance,
+       .disable_luminance = drm_bridge_connector_disable_luminance,
 };
 
 static enum drm_mode_status
@@ -834,6 +873,7 @@ static void drm_bridge_connector_put_bridges(struct 
drm_device *dev, void *data)
        drm_bridge_put(bridge_connector->bridge_hdmi_audio);
        drm_bridge_put(bridge_connector->bridge_dp_audio);
        drm_bridge_put(bridge_connector->bridge_hdmi_cec);
+       drm_bridge_put(bridge_connector->bridge_luminance);
 }
 
 /**
@@ -917,6 +957,13 @@ struct drm_connector *drm_bridge_connector_init(struct 
drm_device *drm,
                        drm_bridge_put(bridge_connector->bridge_detect);
                        bridge_connector->bridge_detect = 
drm_bridge_get(bridge);
                }
+               if (bridge->ops & DRM_BRIDGE_OP_LUMINANCE) {
+                       if (!bridge->funcs->set_luminance)
+                               return ERR_PTR(-EINVAL);
+
+                       drm_bridge_put(bridge_connector->bridge_luminance);
+                       bridge_connector->bridge_luminance = 
drm_bridge_get(bridge);
+               }
                if (bridge->ops & DRM_BRIDGE_OP_HDMI) {
                        if (bridge_connector->bridge_hdmi)
                                return ERR_PTR(-EBUSY);
@@ -1143,6 +1190,24 @@ struct drm_connector *drm_bridge_connector_init(struct 
drm_device *drm,
        if (panel_bridge)
                drm_panel_bridge_set_orientation(connector, panel_bridge);
 
+       if (panel_bridge) {
+               bridge_connector->panel = 
drm_panel_bridge_to_panel(panel_bridge);
+               ret = 
drm_panel_attach_luminance_property(bridge_connector->panel,
+                                                         connector);
+               if (ret)
+                       return ERR_PTR(ret);
+       }
+
+       if (bridge_connector->bridge_luminance && 
!connector->luminance_property) {
+               struct drm_bridge *bridge = bridge_connector->bridge_luminance;
+
+               ret = drm_connector_attach_luminance_property(connector, 0,
+                                                             
bridge->luminance_max,
+                                                            bridge->luminance);
+               if (ret)
+                       return ERR_PTR(ret);
+       }
+
        if (support_hdcp && IS_REACHABLE(CONFIG_DRM_DISPLAY_HELPER) &&
            IS_ENABLED(CONFIG_DRM_DISPLAY_HDCP_HELPER))
                drm_connector_attach_content_protection_property(connector, 
true);
diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index f8f6082e637fd..25355748009a4 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -341,6 +341,61 @@ void drm_panel_disable(struct drm_panel *panel)
 }
 EXPORT_SYMBOL(drm_panel_disable);
 
+/**
+ * drm_panel_attach_luminance_property - expose a panel backlight through DRM
+ * @panel: panel whose backlight is controlled
+ * @connector: connector on which to expose the property
+ *
+ * The backlight class device remains the internal hardware backend. When the
+ * luminance UAPI is enabled, userspace controls that backend through the DRM
+ * connector property instead.
+ *
+ * Return: 0 on success or a negative error code
+ */
+int drm_panel_attach_luminance_property(struct drm_panel *panel,
+                                       struct drm_connector *connector)
+{
+       struct backlight_device *backlight;
+
+       if (!IS_ENABLED(CONFIG_DRM_LUMINANCE) || !panel)
+               return 0;
+
+       backlight = panel->backlight;
+       if (!backlight)
+               return 0;
+
+       return drm_connector_attach_luminance_property(connector, 0,
+                                       backlight->props.max_brightness,
+                                       backlight->props.brightness);
+}
+EXPORT_SYMBOL(drm_panel_attach_luminance_property);
+
+/**
+ * drm_panel_set_luminance - set a panel backlight's relative luminance
+ * @panel: panel whose backlight is controlled
+ * @luminance: relative luminance level
+ */
+void drm_panel_set_luminance(struct drm_panel *panel, unsigned int luminance)
+{
+       if (!panel || !panel->backlight)
+               return;
+
+       panel->backlight->props.brightness = luminance;
+       backlight_update_status(panel->backlight);
+}
+EXPORT_SYMBOL(drm_panel_set_luminance);
+
+/**
+ * drm_panel_disable_luminance - turn off a panel backlight
+ * @panel: panel whose backlight is controlled
+ */
+void drm_panel_disable_luminance(struct drm_panel *panel)
+{
+       if (panel)
+               backlight_disable(panel->backlight);
+}
+EXPORT_SYMBOL(drm_panel_disable_luminance);
+
 /**
  * drm_panel_get_modes - probe the available display modes of a panel
  * @panel: DRM panel
diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h
index 77b3ffe692304..56511d4ded55a 100644
--- a/include/drm/drm_bridge.h
+++ b/include/drm/drm_bridge.h
@@ -62,6 +62,15 @@ enum drm_bridge_attach_flags {
  * struct drm_bridge_funcs - drm_bridge control functions
  */
 struct drm_bridge_funcs {
+       /**
+        * @set_luminance:
+        *
+        * Apply a relative luminance level to a bridge that controls panel
+        * emission. Bridges implementing this callback shall set
+        * @DRM_BRIDGE_OP_LUMINANCE.
+        */
+       void (*set_luminance)(struct drm_bridge *bridge, unsigned int 
luminance);
+
        /**
         * @attach:
         *
@@ -1021,6 +1030,11 @@ enum drm_bridge_ops {
         * &drm_bridge_funcs->hdmi_clear_spd_infoframe callbacks.
         */
        DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME = BIT(10),
+
+       /**
+        * @DRM_BRIDGE_OP_LUMINANCE: The bridge controls panel luminance.
+        */
+       DRM_BRIDGE_OP_LUMINANCE = BIT(11),
 };
 
 /**
@@ -1071,6 +1085,12 @@ struct drm_bridge {
        void *driver_private;
        /** @ops: bitmask of operations supported by the bridge */
        enum drm_bridge_ops ops;
+
+       /** @luminance_max: Maximum relative luminance level. */
+       unsigned int luminance_max;
+
+       /** @luminance: Current relative luminance level. */
+       unsigned int luminance;
        /**
         * @type: Type of the connection at the bridge output
         * (DRM_MODE_CONNECTOR_*). For bridges at the end of this chain this
@@ -1530,6 +1550,7 @@ struct drm_bridge *drm_panel_bridge_add_typed(struct 
drm_panel *panel,
 void drm_panel_bridge_remove(struct drm_bridge *bridge);
 int drm_panel_bridge_set_orientation(struct drm_connector *connector,
                                     struct drm_bridge *bridge);
+struct drm_panel *drm_panel_bridge_to_panel(struct drm_bridge *bridge);
 struct drm_bridge *devm_drm_panel_bridge_add(struct device *dev,
                                             struct drm_panel *panel);
 struct drm_bridge *devm_drm_panel_bridge_add_typed(struct device *dev,
@@ -1549,6 +1570,11 @@ static inline int 
drm_panel_bridge_set_orientation(struct drm_connector *connect
 {
        return -EINVAL;
 }
+
+static inline struct drm_panel *drm_panel_bridge_to_panel(struct drm_bridge 
*bridge)
+{
+       return NULL;
+}
 #endif
 
 #if defined(CONFIG_OF) && defined(CONFIG_DRM_PANEL_BRIDGE)
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h
index b87323443f49c..dfd225ba05c8d 100644
--- a/include/drm/drm_panel.h
+++ b/include/drm/drm_panel.h
@@ -333,6 +333,11 @@ void drm_panel_unprepare(struct drm_panel *panel);
 void drm_panel_enable(struct drm_panel *panel);
 void drm_panel_disable(struct drm_panel *panel);
 
+int drm_panel_attach_luminance_property(struct drm_panel *panel,
+                                       struct drm_connector *connector);
+void drm_panel_set_luminance(struct drm_panel *panel, unsigned int luminance);
+void drm_panel_disable_luminance(struct drm_panel *panel);
+
 int drm_panel_get_modes(struct drm_panel *panel, struct drm_connector 
*connector);
 
 #if defined(CONFIG_OF) && defined(CONFIG_DRM_PANEL)
-- 
2.43.0

Reply via email to