diff mbox series

[v7,1/5] drm/panel: add basic DP AUX backlight support

Message ID 1624099230-20899-2-git-send-email-rajeevny@codeaurora.org (mailing list archive)
State Superseded
Headers show
Series drm: Support basic DPCD backlight in panel-simple and add a new panel ATNA33XC20 | expand

Commit Message

Rajeev Nandan June 19, 2021, 10:40 a.m. UTC
Some panels support backlight control over DP AUX channel using
VESA's standard backlight control interface.
Using new DRM eDP backlight helpers, add support to create and
register a backlight for those panels in drm_panel to simplify
the panel drivers.

The panel driver with access to "struct drm_dp_aux" can create and
register a backlight device using following code snippet in its
probe() function:

	err = drm_panel_dp_aux_backlight(panel, aux);
	if (err)
		return err;

Then drm_panel will handle backlight_(enable|disable) calls
similar to the case when drm_panel_of_backlight() is used.

Currently, we are not supporting one feature where the source
device can combine the backlight brightness levels set through
DP AUX and the BL_PWM_DIM eDP connector pin. Since it's not
required for the basic backlight controls, it can be added later.

Signed-off-by: Rajeev Nandan <rajeevny@codeaurora.org>
Reviewed-by: Douglas Anderson <dianders@chromium.org>
Reviewed-by: Lyude Paul <lyude@redhat.com>
---

(no changes since v6)

Changes in v5:
- New

Changes in v6:
- Fixed ordering of memory allocation (Douglas)
- Updated word wrapping in a comment (Douglas)

 drivers/gpu/drm/drm_panel.c | 108 ++++++++++++++++++++++++++++++++++++++++++++
 include/drm/drm_panel.h     |  15 ++++--
 2 files changed, 119 insertions(+), 4 deletions(-)

Comments

Rajeev Nandan June 21, 2021, 8:38 a.m. UTC | #1
Hi Sam,

On 20-06-2021 15:01, Sam Ravnborg wrote:
> Hi Rajeev
> 
> On Sat, Jun 19, 2021 at 04:10:26PM +0530, Rajeev Nandan wrote:
>> Some panels support backlight control over DP AUX channel using
>> VESA's standard backlight control interface.
>> Using new DRM eDP backlight helpers, add support to create and
>> register a backlight for those panels in drm_panel to simplify
>> the panel drivers.
>> 
>> The panel driver with access to "struct drm_dp_aux" can create and
>> register a backlight device using following code snippet in its
>> probe() function:
>> 
>> 	err = drm_panel_dp_aux_backlight(panel, aux);
>> 	if (err)
>> 		return err;
> 
> IT very good to have this supported by drm_panel, so we avoid
> bolierplate in various drivers.
> 
>> 
>> Then drm_panel will handle backlight_(enable|disable) calls
>> similar to the case when drm_panel_of_backlight() is used.
>> 
>> Currently, we are not supporting one feature where the source
>> device can combine the backlight brightness levels set through
>> DP AUX and the BL_PWM_DIM eDP connector pin. Since it's not
>> required for the basic backlight controls, it can be added later.
>> 
>> Signed-off-by: Rajeev Nandan <rajeevny@codeaurora.org>
>> Reviewed-by: Douglas Anderson <dianders@chromium.org>
>> Reviewed-by: Lyude Paul <lyude@redhat.com>
>> ---
>> 
>> (no changes since v6)
>> 
>> Changes in v5:
>> - New
>> 
>> Changes in v6:
>> - Fixed ordering of memory allocation (Douglas)
>> - Updated word wrapping in a comment (Douglas)
>> 
>>  drivers/gpu/drm/drm_panel.c | 108 
>> ++++++++++++++++++++++++++++++++++++++++++++
>>  include/drm/drm_panel.h     |  15 ++++--
>>  2 files changed, 119 insertions(+), 4 deletions(-)
>> 
>> diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
>> index f634371..9e65342 100644
>> --- a/drivers/gpu/drm/drm_panel.c
>> +++ b/drivers/gpu/drm/drm_panel.c
>> @@ -26,12 +26,20 @@
>>  #include <linux/module.h>
>> 
>>  #include <drm/drm_crtc.h>
>> +#include <drm/drm_dp_helper.h>
>>  #include <drm/drm_panel.h>
>>  #include <drm/drm_print.h>
>> 
>>  static DEFINE_MUTEX(panel_lock);
>>  static LIST_HEAD(panel_list);
>> 
>> +struct dp_aux_backlight {
>> +	struct backlight_device *base;
>> +	struct drm_dp_aux *aux;
>> +	struct drm_edp_backlight_info info;
>> +	bool enabled;
>> +};
>> +
>>  /**
>>   * DOC: drm panel
>>   *
>> @@ -342,6 +350,106 @@ int drm_panel_of_backlight(struct drm_panel 
>> *panel)
>>  	return 0;
>>  }
>>  EXPORT_SYMBOL(drm_panel_of_backlight);
>> +
>> +static int dp_aux_backlight_update_status(struct backlight_device 
>> *bd)
>> +{
>> +	struct dp_aux_backlight *bl = bl_get_data(bd);
>> +	u16 brightness = backlight_get_brightness(bd);
> backlight_get_brightness() returns an int, so using u16 seems wrong.
> But then drm_edp_backlight_enable() uses u16 for level - so I guess it
> is OK.
> We use unsigned long, int, u16 for brightness. Looks like something one
> could look at one day, but today is not that day.
> 
>> +	int ret = 0;
>> +
>> +	if (brightness > 0) {
> Use backlight_is_blank(bd) here, as this is really what you test for.

The backlight_get_brightness() used above has the backlight_is_blank() 
check and returns brightness 0 when the backlight_is_blank(bd) is true.
So, instead of calling backlight_is_blank(bd), we are checking 
brightness value here.
I took the reference from pwm_backlight_update_status() of the PWM 
backlight driver (drivers/video/backlight/pwm_bl.c)

Yes, we can change this _if_ condition to use backlight_is_blank(bd), as 
this is an inline function, and is more meaningful.
With this, there would be one change in the behavior of 
_backlight_update_status function in the following case:

- Setting brightness=0 when the backlight is not blank:
In the current case setting brightness=0 is disabling the backlight.
In the new case, setting brightness=0 will set the brightness to 0 and 
will do nothing to backlight disable.

I think that should not be a problem?

> 
> I cannot see why you need the extra check on ->enabled?
> Would it be sufficient to check backlight_is_blank() only?

This extra check on bl->enabled flag is added to avoid 
enabling/disabling backlight again if it is already enabled/disabled.
Using this flag way can know the transition between backlight blank and 
un-blank, and decide when to enable/disable the backlight.

> 
>> +		if (!bl->enabled) {
>> +			drm_edp_backlight_enable(bl->aux, &bl->info, brightness);
>> +			bl->enabled = true;
>> +			return 0;
>> +		}
>> +		ret = drm_edp_backlight_set_level(bl->aux, &bl->info, brightness);
>> +	} else {
>> +		if (bl->enabled) {
>> +			drm_edp_backlight_disable(bl->aux, &bl->info);
>> +			bl->enabled = false;
>> +		}
>> +	}
>> +
>> +	return ret;
>> +}
> 
> 	Sam

Thanks,
Rajeev
Doug Anderson June 22, 2021, 6:33 p.m. UTC | #2
Hi,

On Mon, Jun 21, 2021 at 11:38 AM Sam Ravnborg <sam@ravnborg.org> wrote:
>
> > > I cannot see why you need the extra check on ->enabled?
> > > Would it be sufficient to check backlight_is_blank() only?
> >
> > This extra check on bl->enabled flag is added to avoid enabling/disabling
> > backlight again if it is already enabled/disabled.
> > Using this flag way can know the transition between backlight blank and
> > un-blank, and decide when to enable/disable the backlight.
>
> My point is that this should really not be needed, as it would cover up
> for some other bug whaere we try to do something twice that is not
> needed. But I am less certain here so if you think it is needed, keep
> it as is.

I haven't tested this myself, but I believe that it is needed. I don't
think the backlight update_status() function is like an enable/disable
function. I believe it can be called more than one time even while the
backlight is disabled. For instance, you can see that
backlight_update_status() just blindly calls through to update the
status. That function can be called for a number of reasons. Perhaps
Rajeev can put some printouts to confirm but I think that if the
backlight is "blanked" for whatever reason and you write to sysfs and
change the backlight level you'll still get called again even though
the backlight is still "disabled".

-Doug
Rajeev Nandan June 23, 2021, 6:15 a.m. UTC | #3
Hi,

On 23-06-2021 00:03, Doug Anderson wrote:
> Hi,
> 
> On Mon, Jun 21, 2021 at 11:38 AM Sam Ravnborg <sam@ravnborg.org> wrote:
>> 
>> > > I cannot see why you need the extra check on ->enabled?
>> > > Would it be sufficient to check backlight_is_blank() only?
>> >
>> > This extra check on bl->enabled flag is added to avoid enabling/disabling
>> > backlight again if it is already enabled/disabled.
>> > Using this flag way can know the transition between backlight blank and
>> > un-blank, and decide when to enable/disable the backlight.
>> 
>> My point is that this should really not be needed, as it would cover 
>> up
>> for some other bug whaere we try to do something twice that is not
>> needed. But I am less certain here so if you think it is needed, keep
>> it as is.
> 
> I haven't tested this myself, but I believe that it is needed. I don't
> think the backlight update_status() function is like an enable/disable
> function. I believe it can be called more than one time even while the
> backlight is disabled. For instance, you can see that
> backlight_update_status() just blindly calls through to update the
> status. That function can be called for a number of reasons. Perhaps
> Rajeev can put some printouts to confirm but I think that if the
> backlight is "blanked" for whatever reason and you write to sysfs and
> change the backlight level you'll still get called again even though
> the backlight is still "disabled".
> 
Yes, sysfs write will always try to update the backlight even though the 
backlight is "blanked".

The "bl->enabled" check is also required to prevent unnecessary calls to 
drm_edp_backlight_enable() during every backlight level change.

To confirm this, I have added few prints in 
dp_aux_backlight_update_status() function and collected the logs.
(Copying the code here to make the review easy)


static int dp_aux_backlight_update_status(struct backlight_device *bd)
{
         struct dp_aux_backlight *bl = bl_get_data(bd);
         u16 brightness = backlight_get_brightness(bd);
         int ret = 0;

+        pr_err("%s: brightness %d, _is_blank %d, bl->enabled %d\n", 
__func__,
+                brightness, backlight_is_blank(bd), bl->enabled);

         if (!backlight_is_blank(bd)) {
                 if (!bl->enabled) {
+                        pr_err("%s: enabling backlight\n", __func__);
                         drm_edp_backlight_enable(bl->aux, &bl->info, 
brightness);
                         bl->enabled = true;
                         return 0;
                 }
                 ret = drm_edp_backlight_set_level(bl->aux, &bl->info, 
brightness);
         } else {
                 if (bl->enabled) {
+                       pr_err("%s: disabling backlight\n", __func__);
                         drm_edp_backlight_disable(bl->aux, &bl->info);
                         bl->enabled = false;
                 }
         }

         return ret;
}


LOGS
====

During boot
-----------
[    4.752188] dp_aux_backlight_update_status: brightness 102, _is_blank 
0, bl->enabled 0
[    4.760447] dp_aux_backlight_update_status: enabling backlight
[    5.503866] dp_aux_backlight_update_status: brightness 102, _is_blank 
0, bl->enabled 1
[    6.897355] dp_aux_backlight_update_status: brightness 103, _is_blank 
0, bl->enabled 1
[    6.938617] dp_aux_backlight_update_status: brightness 104, _is_blank 
0, bl->enabled 1
[    6.980634] dp_aux_backlight_update_status: brightness 105, _is_blank 
0, bl->enabled 1


Turning Panel OFF
-----------------
localhost ~ # set_power_policy --ac_screen_dim_delay=5 
--ac_screen_off_delay=10
localhost ~ #

[  106.555140] dp_aux_backlight_update_status: brightness 145, _is_blank 
0, bl->enabled 1
...
...
[  111.679407] dp_aux_backlight_update_status: brightness 7, _is_blank 
0, bl->enabled 1
[  111.700302] dp_aux_backlight_update_status: brightness 4, _is_blank 
0, bl->enabled 1
[  111.720805] dp_aux_backlight_update_status: brightness 2, _is_blank 
0, bl->enabled 1
[  111.747486] dp_aux_backlight_update_status: brightness 0, _is_blank 
1, bl->enabled 1
[  111.755580] dp_aux_backlight_update_status: disabling backlight
[  111.792344] dp_aux_backlight_update_status: brightness 0, _is_blank 
1, bl->enabled 0


Changing brightness from sysfs while panel is off
--------------------------------------------------
(it will do nothing)

localhost ~ # echo 100 > 
/sys/class/backlight/dp_aux_backlight/brightness
[  352.754963] dp_aux_backlight_update_status: brightness 0, _is_blank 
1, bl->enabled 0

localhost ~ # echo 200 > 
/sys/class/backlight/dp_aux_backlight/brightness
[  364.708048] dp_aux_backlight_update_status: brightness 0, _is_blank 
1, bl->enabled 0

localhost ~ # echo 0 > /sys/class/backlight/dp_aux_backlight/brightness
[  378.850978] dp_aux_backlight_update_status: brightness 0, _is_blank 
1, bl->enabled 0


Turning Panel ON
----------------
[  553.381745] dp_aux_backlight_update_status: brightness 0, _is_blank 
0, bl->enabled 0
[  553.418133] dp_aux_backlight_update_status: enabling backlight
[  553.426397] dp_aux_backlight_update_status: brightness 159, _is_blank 
0, bl->enabled 1

====



Thanks,
Rajeev
diff mbox series

Patch

diff --git a/drivers/gpu/drm/drm_panel.c b/drivers/gpu/drm/drm_panel.c
index f634371..9e65342 100644
--- a/drivers/gpu/drm/drm_panel.c
+++ b/drivers/gpu/drm/drm_panel.c
@@ -26,12 +26,20 @@ 
 #include <linux/module.h>
 
 #include <drm/drm_crtc.h>
+#include <drm/drm_dp_helper.h>
 #include <drm/drm_panel.h>
 #include <drm/drm_print.h>
 
 static DEFINE_MUTEX(panel_lock);
 static LIST_HEAD(panel_list);
 
+struct dp_aux_backlight {
+	struct backlight_device *base;
+	struct drm_dp_aux *aux;
+	struct drm_edp_backlight_info info;
+	bool enabled;
+};
+
 /**
  * DOC: drm panel
  *
@@ -342,6 +350,106 @@  int drm_panel_of_backlight(struct drm_panel *panel)
 	return 0;
 }
 EXPORT_SYMBOL(drm_panel_of_backlight);
+
+static int dp_aux_backlight_update_status(struct backlight_device *bd)
+{
+	struct dp_aux_backlight *bl = bl_get_data(bd);
+	u16 brightness = backlight_get_brightness(bd);
+	int ret = 0;
+
+	if (brightness > 0) {
+		if (!bl->enabled) {
+			drm_edp_backlight_enable(bl->aux, &bl->info, brightness);
+			bl->enabled = true;
+			return 0;
+		}
+		ret = drm_edp_backlight_set_level(bl->aux, &bl->info, brightness);
+	} else {
+		if (bl->enabled) {
+			drm_edp_backlight_disable(bl->aux, &bl->info);
+			bl->enabled = false;
+		}
+	}
+
+	return ret;
+}
+
+static const struct backlight_ops dp_aux_bl_ops = {
+	.update_status = dp_aux_backlight_update_status,
+};
+
+/**
+ * drm_panel_dp_aux_backlight - create and use DP AUX backlight
+ * @panel: DRM panel
+ * @aux: The DP AUX channel to use
+ *
+ * Use this function to create and handle backlight if your panel
+ * supports backlight control over DP AUX channel using DPCD
+ * registers as per VESA's standard backlight control interface.
+ *
+ * When the panel is enabled backlight will be enabled after a
+ * successful call to &drm_panel_funcs.enable()
+ *
+ * When the panel is disabled backlight will be disabled before the
+ * call to &drm_panel_funcs.disable().
+ *
+ * A typical implementation for a panel driver supporting backlight
+ * control over DP AUX will call this function at probe time.
+ * Backlight will then be handled transparently without requiring
+ * any intervention from the driver.
+ *
+ * drm_panel_dp_aux_backlight() must be called after the call to drm_panel_init().
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int drm_panel_dp_aux_backlight(struct drm_panel *panel, struct drm_dp_aux *aux)
+{
+	struct dp_aux_backlight *bl;
+	struct backlight_properties props = { 0 };
+	u16 current_level;
+	u8 current_mode;
+	u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE];
+	int ret;
+
+	if (!panel || !panel->dev || !aux)
+		return -EINVAL;
+
+	ret = drm_dp_dpcd_read(aux, DP_EDP_DPCD_REV, edp_dpcd,
+			       EDP_DISPLAY_CTL_CAP_SIZE);
+	if (ret < 0)
+		return ret;
+
+	if (!drm_edp_backlight_supported(edp_dpcd)) {
+		DRM_DEV_INFO(panel->dev, "DP AUX backlight is not supported\n");
+		return 0;
+	}
+
+	bl = devm_kzalloc(panel->dev, sizeof(*bl), GFP_KERNEL);
+	if (!bl)
+		return -ENOMEM;
+
+	bl->aux = aux;
+
+	ret = drm_edp_backlight_init(aux, &bl->info, 0, edp_dpcd,
+				     &current_level, &current_mode);
+	if (ret < 0)
+		return ret;
+
+	props.type = BACKLIGHT_RAW;
+	props.brightness = current_level;
+	props.max_brightness = bl->info.max;
+
+	bl->base = devm_backlight_device_register(panel->dev, "dp_aux_backlight",
+						  panel->dev, bl,
+						  &dp_aux_bl_ops, &props);
+	if (IS_ERR(bl->base))
+		return PTR_ERR(bl->base);
+
+	panel->backlight = bl->base;
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_panel_dp_aux_backlight);
 #endif
 
 MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
diff --git a/include/drm/drm_panel.h b/include/drm/drm_panel.h
index 33605c3..3ebfaa6 100644
--- a/include/drm/drm_panel.h
+++ b/include/drm/drm_panel.h
@@ -32,6 +32,7 @@  struct backlight_device;
 struct device_node;
 struct drm_connector;
 struct drm_device;
+struct drm_dp_aux;
 struct drm_panel;
 struct display_timing;
 
@@ -64,8 +65,8 @@  enum drm_panel_orientation;
  * the panel. This is the job of the .unprepare() function.
  *
  * Backlight can be handled automatically if configured using
- * drm_panel_of_backlight(). Then the driver does not need to implement the
- * functionality to enable/disable backlight.
+ * drm_panel_of_backlight() or drm_panel_dp_aux_backlight(). Then the driver
+ * does not need to implement the functionality to enable/disable backlight.
  */
 struct drm_panel_funcs {
 	/**
@@ -144,8 +145,8 @@  struct drm_panel {
 	 * Backlight device, used to turn on backlight after the call
 	 * to enable(), and to turn off backlight before the call to
 	 * disable().
-	 * backlight is set by drm_panel_of_backlight() and drivers
-	 * shall not assign it.
+	 * backlight is set by drm_panel_of_backlight() or
+	 * drm_panel_dp_aux_backlight() and drivers shall not assign it.
 	 */
 	struct backlight_device *backlight;
 
@@ -208,11 +209,17 @@  static inline int of_drm_get_panel_orientation(const struct device_node *np,
 #if IS_ENABLED(CONFIG_DRM_PANEL) && (IS_BUILTIN(CONFIG_BACKLIGHT_CLASS_DEVICE) || \
 	(IS_MODULE(CONFIG_DRM) && IS_MODULE(CONFIG_BACKLIGHT_CLASS_DEVICE)))
 int drm_panel_of_backlight(struct drm_panel *panel);
+int drm_panel_dp_aux_backlight(struct drm_panel *panel, struct drm_dp_aux *aux);
 #else
 static inline int drm_panel_of_backlight(struct drm_panel *panel)
 {
 	return 0;
 }
+static inline int drm_panel_dp_aux_backlight(struct drm_panel *panel,
+					     struct drm_dp_aux *aux)
+{
+	return 0;
+}
 #endif
 
 #endif