mbox series

[v4,0/4] drm: Support basic DPCD backlight in panel-simple and add a new panel ATNA33XC20

Message ID 1621927831-29471-1-git-send-email-rajeevny@codeaurora.org
Headers show
Series drm: Support basic DPCD backlight in panel-simple and add a new panel ATNA33XC20 | expand

Message

Rajeev Nandan May 25, 2021, 7:30 a.m. UTC
This series adds the support for the eDP panel that needs the backlight
controlling over the DP AUX channel using DPCD registers of the panel
as per the VESA's standard.

This series also adds support for the Samsung eDP AMOLED panel that
needs DP AUX to control the backlight, and introduces new delays in the
@panel_desc.delay to support this panel.

This patch series depends on the following two series:
- Doug's series [1], exposed the DP AUX channel to the panel-simple.
- Lyude's series [2], introduced new drm helper functions for DPCD
  backlight.

This series is the logical successor to the series [3].

Changes in v1:
- Created dpcd backlight helper with very basic functionality, added
  backlight registration in the ti-sn65dsi86 bridge driver.

Changes in v2:
- Created a new DisplayPort aux backlight driver and moved the code from
  drm_dp_aux_backlight.c (v1) to the new driver.

Changes in v3:
- Fixed module compilation (kernel test bot).

Changes in v4:
- Added basic DPCD backlight support in panel-simple.
- Added support for a new Samsung panel ATNA33XC20 that needs DPCD
  backlight controlling and has a requirement of delays between enable
  GPIO and regulator.

[1] https://lore.kernel.org/dri-devel/20210525000159.3384921-1-dianders@chromium.org/
[2] https://lore.kernel.org/dri-devel/20210514181504.565252-1-lyude@redhat.com/
[3] https://lore.kernel.org/dri-devel/1619416756-3533-1-git-send-email-rajeevny@codeaurora.org/

Rajeev Nandan (4):
  drm/panel-simple: Add basic DPCD backlight support
  drm/panel-simple: Support for delays between GPIO & regulator
  dt-bindings: display: simple: Add Samsung ATNA33XC20
  drm/panel-simple: Add Samsung ATNA33XC20

 .../bindings/display/panel/panel-simple.yaml       |   2 +
 drivers/gpu/drm/panel/panel-simple.c               | 156 ++++++++++++++++++++-
 2 files changed, 155 insertions(+), 3 deletions(-)

Comments

Doug Anderson May 25, 2021, 5:18 p.m. UTC | #1
Hi,

On Tue, May 25, 2021 at 12:31 AM Rajeev Nandan <rajeevny@codeaurora.org> wrote:
>
> @@ -171,6 +172,19 @@ struct panel_desc {
>
>         /** @connector_type: LVDS, eDP, DSI, DPI, etc. */
>         int connector_type;
> +
> +       /**
> +        * @uses_dpcd_backlight: Panel supports eDP dpcd backlight control.
> +        *
> +        * Set true, if the panel supports backlight control over eDP AUX channel
> +        * using DPCD registers as per VESA's standard.
> +        */
> +       bool uses_dpcd_backlight;
> +};
> +
> +struct edp_backlight {
> +       struct backlight_device *dev;

Can you pick a name other than "dev". In my mind "dev" means you've
got a "struct device" or a "struct device *".


> +       struct drm_edp_backlight_info info;
>  };
>
>  struct panel_simple {
> @@ -194,6 +208,8 @@ struct panel_simple {
>
>         struct edid *edid;
>
> +       struct edp_backlight *edp_bl;
> +

I don't think you need to add this pointer. See below for details, but
basically the backlight device should be in base.backlight. Any code
that needs the containing structure can use the standard
"container_of" syntax.


>         struct drm_display_mode override_mode;
>
>         enum drm_panel_orientation orientation;
> @@ -330,10 +346,14 @@ static void panel_simple_wait(ktime_t start_ktime, unsigned int min_ms)
>  static int panel_simple_disable(struct drm_panel *panel)
>  {
>         struct panel_simple *p = to_panel_simple(panel);
> +       struct edp_backlight *bl = p->edp_bl;
>
>         if (!p->enabled)
>                 return 0;
>
> +       if (p->desc->uses_dpcd_backlight && bl)
> +               drm_edp_backlight_disable(p->aux, &bl->info);
> +

It feels like this shouldn't be needed. I would have expected that
your backlight should be in 'panel->backlight'. Then
drm_panel_enable() will call backlight_enable() on your backlight
automatically after calling the panel's enable function.


>         if (p->desc->delay.disable)
>                 msleep(p->desc->delay.disable);
>
> @@ -496,6 +516,7 @@ static int panel_simple_prepare(struct drm_panel *panel)
>  static int panel_simple_enable(struct drm_panel *panel)
>  {
>         struct panel_simple *p = to_panel_simple(panel);
> +       struct edp_backlight *bl = p->edp_bl;
>
>         if (p->enabled)
>                 return 0;
> @@ -505,6 +526,10 @@ static int panel_simple_enable(struct drm_panel *panel)
>
>         panel_simple_wait(p->prepared_time, p->desc->delay.prepare_to_enable);
>
> +       if (p->desc->uses_dpcd_backlight && bl)
> +               drm_edp_backlight_enable(p->aux, &bl->info,
> +                                        bl->dev->props.brightness);
> +

Similar to disable, this shouldn't be needed.


>         p->enabled = true;
>
>         return 0;
> @@ -565,6 +590,59 @@ static const struct drm_panel_funcs panel_simple_funcs = {
>         .get_timings = panel_simple_get_timings,
>  };
>
> +static int edp_backlight_update_status(struct backlight_device *bd)
> +{
> +       struct panel_simple *p = bl_get_data(bd);
> +       struct edp_backlight *bl = p->edp_bl;
> +
> +       if (!p->enabled)
> +               return 0;
> +
> +       return drm_edp_backlight_set_level(p->aux, &bl->info, bd->props.brightness);

I notice that the "nouveau" driver grabs a whole pile of locks around
this. Do we need some of those? I guess perhaps checking "p->enabled"
isn't so valid without holding some of those locks.

Actually, I guess you probably can't look at "p->enabled" anyway if
this gets moved out of panel-simple as I'm suggesting.

...but do you even need something like this check? Shouldn't it be
handled by the fact that drm_panel will handle enabling/disabling the
backlight at the right times?


> +}
> +
> +static const struct backlight_ops edp_backlight_ops = {
> +       .update_status = edp_backlight_update_status,
> +};
> +
> +static int edp_backlight_register(struct device *dev, struct panel_simple *panel)
> +{
> +       struct edp_backlight *bl;
> +       struct backlight_properties props = { 0 };
> +       u16 current_level;
> +       u8 current_mode;
> +       u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE];
> +       int ret;
> +
> +       bl = devm_kzalloc(dev, sizeof(*bl), GFP_KERNEL);
> +       if (!bl)
> +               return -ENOMEM;
> +
> +       ret = drm_dp_dpcd_read(panel->aux, DP_EDP_DPCD_REV, edp_dpcd,
> +                              EDP_DISPLAY_CTL_CAP_SIZE);
> +       if (ret < 0)
> +               return ret;
> +
> +       ret = drm_edp_backlight_init(panel->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->dev = devm_backlight_device_register(dev, "edp_backlight",
> +                                               dev, panel,
> +                                               &edp_backlight_ops, &props);
> +       if (IS_ERR(bl->dev))
> +               return PTR_ERR(bl->dev);
> +
> +       panel->edp_bl = bl;
> +
> +       return 0;
> +}
> +

I expect there to be quite a bit of pushback to putting this directly
into panel-simple. How about if you move edp_backlight_register() into
drm_panel.c, parallel to drm_panel_of_backlight(). Maybe you'd call it
drm_panel_dp_aux_backlight() to make it look symmetric?

If you do that then the amount of code / complexity being added to
"simple" panel is quite small. I think it would just come down to
adding the boolean flag and the patch to probe that you have below.

Actually, now that I think about it, you could maybe even get by
_without_ the boolean flag? I think you could use these rules
(untested!):

1. Call drm_panel_of_backlight() always, just like we do today. If a
backlight was specified in the device tree then we should use it.

2. If no backlight was specified in the device tree then, I believe,
drm_panel_of_backlight() will return with no errors but will have
panel->backlight set to NULL.

3. If there was no backlight specified in the device tree and you have
the DP AUX channel and drm_edp_backlight_supported() then create a DP
AUX backlight.

The one feature that wouldn't be supported by the above would be
"DP_EDP_BACKLIGHT_AUX_PWM_PRODUCT_CAP". Presumably that's fine. If
someone later wants to figure out how to solve that then they can.


>  static struct panel_desc panel_dpi;
>
>  static int panel_dpi_probe(struct device *dev,
> @@ -796,9 +874,24 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc,
>
>         drm_panel_init(&panel->base, dev, &panel_simple_funcs, connector_type);
>
> -       err = drm_panel_of_backlight(&panel->base);
> -       if (err)
> -               goto disable_pm_runtime;
> +       if (panel->desc->uses_dpcd_backlight) {
> +               if (!panel->aux) {
> +                       dev_err(dev, "edp backlight needs DP aux\n");
> +                       err = -EINVAL;
> +                       goto disable_pm_runtime;
> +               }
> +
> +               err = edp_backlight_register(dev, panel);
> +               if (err) {
> +                       dev_err(dev, "failed to register edp backlight %d\n", err);
> +                       goto disable_pm_runtime;
> +               }
> +
> +       } else {

nit: get rid of the blank line above the "} else {"


> +               err = drm_panel_of_backlight(&panel->base);
> +               if (err)
> +                       goto disable_pm_runtime;
> +       }

See above where I'm suggesting some different logic. Specifically:
always try the drm_panel_of_backlight() call and then fallback to the
AUX backlight if "panel->base.backlight" is NULL and "panel->aux" is
not NULL.

-Doug
Rajeev Nandan May 27, 2021, 12:21 p.m. UTC | #2
Hi,

On 25-05-2021 22:48, Doug Anderson wrote:
> Hi,
> 
> On Tue, May 25, 2021 at 12:31 AM Rajeev Nandan 
> <rajeevny@codeaurora.org> wrote:
>> 
>> @@ -171,6 +172,19 @@ struct panel_desc {
>> 
>>         /** @connector_type: LVDS, eDP, DSI, DPI, etc. */
>>         int connector_type;
>> +
>> +       /**
>> +        * @uses_dpcd_backlight: Panel supports eDP dpcd backlight 
>> control.
>> +        *
>> +        * Set true, if the panel supports backlight control over eDP 
>> AUX channel
>> +        * using DPCD registers as per VESA's standard.
>> +        */
>> +       bool uses_dpcd_backlight;
>> +};
>> +
>> +struct edp_backlight {
>> +       struct backlight_device *dev;
> 
> Can you pick a name other than "dev". In my mind "dev" means you've
> got a "struct device" or a "struct device *".

In the backlight.h "bd" is used for "struct backlight_device". I can use 
"bd"?

> 
> 
>> +       struct drm_edp_backlight_info info;
>>  };
>> 
>>  struct panel_simple {
>> @@ -194,6 +208,8 @@ struct panel_simple {
>> 
>>         struct edid *edid;
>> 
>> +       struct edp_backlight *edp_bl;
>> +
> 
> I don't think you need to add this pointer. See below for details, but
> basically the backlight device should be in base.backlight. Any code
> that needs the containing structure can use the standard
> "container_of" syntax.
> 

The documentation of the "struct drm_panel -> backlight" mentions
"backlight is set by drm_panel_of_backlight() and drivers shall not 
assign it."
That's why I was not sure if I should touch that part. Because of this, 
I added
backlight enable/disable calls inside panel_simple_disable/enable().

> 
>>         struct drm_display_mode override_mode;
>> 
>>         enum drm_panel_orientation orientation;
>> @@ -330,10 +346,14 @@ static void panel_simple_wait(ktime_t 
>> start_ktime, unsigned int min_ms)
>>  static int panel_simple_disable(struct drm_panel *panel)
>>  {
>>         struct panel_simple *p = to_panel_simple(panel);
>> +       struct edp_backlight *bl = p->edp_bl;
>> 
>>         if (!p->enabled)
>>                 return 0;
>> 
>> +       if (p->desc->uses_dpcd_backlight && bl)
>> +               drm_edp_backlight_disable(p->aux, &bl->info);
>> +
> 
> It feels like this shouldn't be needed. I would have expected that
> your backlight should be in 'panel->backlight'. Then
> drm_panel_enable() will call backlight_enable() on your backlight
> automatically after calling the panel's enable function.

Yes, this is not needed if the backlight is part of panel->backlight.

> 
> 
>>         if (p->desc->delay.disable)
>>                 msleep(p->desc->delay.disable);
>> 
>> @@ -496,6 +516,7 @@ static int panel_simple_prepare(struct drm_panel 
>> *panel)
>>  static int panel_simple_enable(struct drm_panel *panel)
>>  {
>>         struct panel_simple *p = to_panel_simple(panel);
>> +       struct edp_backlight *bl = p->edp_bl;
>> 
>>         if (p->enabled)
>>                 return 0;
>> @@ -505,6 +526,10 @@ static int panel_simple_enable(struct drm_panel 
>> *panel)
>> 
>>         panel_simple_wait(p->prepared_time, 
>> p->desc->delay.prepare_to_enable);
>> 
>> +       if (p->desc->uses_dpcd_backlight && bl)
>> +               drm_edp_backlight_enable(p->aux, &bl->info,
>> +                                        bl->dev->props.brightness);
>> +
> 
> Similar to disable, this shouldn't be needed.

Will remove this too.

> 
> 
>>         p->enabled = true;
>> 
>>         return 0;
>> @@ -565,6 +590,59 @@ static const struct drm_panel_funcs 
>> panel_simple_funcs = {
>>         .get_timings = panel_simple_get_timings,
>>  };
>> 
>> +static int edp_backlight_update_status(struct backlight_device *bd)
>> +{
>> +       struct panel_simple *p = bl_get_data(bd);
>> +       struct edp_backlight *bl = p->edp_bl;
>> +
>> +       if (!p->enabled)
>> +               return 0;
>> +
>> +       return drm_edp_backlight_set_level(p->aux, &bl->info, 
>> bd->props.brightness);
> 
> I notice that the "nouveau" driver grabs a whole pile of locks around
> this. Do we need some of those? I guess perhaps checking "p->enabled"
> isn't so valid without holding some of those locks.
> 
> Actually, I guess you probably can't look at "p->enabled" anyway if
> this gets moved out of panel-simple as I'm suggesting.
> 
> ...but do you even need something like this check? Shouldn't it be
> handled by the fact that drm_panel will handle enabling/disabling the
> backlight at the right times?
> 

The idea behind this check was to avoid the backlight update operation
(avoid DP aux access) when the panel is disabled. In case, if someone 
sets the
brightness from the sysfs when the panel is off. I should have used
backlight_get_brightness() or backlight_is_blank().

As we are moving this function out of the panel-simple, and going to use
panel->backlight, I will remove this check.

> 
>> +}
>> +
>> +static const struct backlight_ops edp_backlight_ops = {
>> +       .update_status = edp_backlight_update_status,
>> +};
>> +
>> +static int edp_backlight_register(struct device *dev, struct 
>> panel_simple *panel)
>> +{
>> +       struct edp_backlight *bl;
>> +       struct backlight_properties props = { 0 };
>> +       u16 current_level;
>> +       u8 current_mode;
>> +       u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE];
>> +       int ret;
>> +
>> +       bl = devm_kzalloc(dev, sizeof(*bl), GFP_KERNEL);
>> +       if (!bl)
>> +               return -ENOMEM;
>> +
>> +       ret = drm_dp_dpcd_read(panel->aux, DP_EDP_DPCD_REV, edp_dpcd,
>> +                              EDP_DISPLAY_CTL_CAP_SIZE);
>> +       if (ret < 0)
>> +               return ret;
>> +
>> +       ret = drm_edp_backlight_init(panel->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->dev = devm_backlight_device_register(dev, "edp_backlight",
>> +                                               dev, panel,
>> +                                               &edp_backlight_ops, 
>> &props);
>> +       if (IS_ERR(bl->dev))
>> +               return PTR_ERR(bl->dev);
>> +
>> +       panel->edp_bl = bl;
>> +
>> +       return 0;
>> +}
>> +
> 
> I expect there to be quite a bit of pushback to putting this directly
> into panel-simple. How about if you move edp_backlight_register() into
> drm_panel.c, parallel to drm_panel_of_backlight(). Maybe you'd call it
> drm_panel_dp_aux_backlight() to make it look symmetric?
> 
> If you do that then the amount of code / complexity being added to
> "simple" panel is quite small. I think it would just come down to
> adding the boolean flag and the patch to probe that you have below.
> 
> Actually, now that I think about it, you could maybe even get by
> _without_ the boolean flag? I think you could use these rules
> (untested!):
> 
> 1. Call drm_panel_of_backlight() always, just like we do today. If a
> backlight was specified in the device tree then we should use it.
> 
> 2. If no backlight was specified in the device tree then, I believe,
> drm_panel_of_backlight() will return with no errors but will have
> panel->backlight set to NULL.
> 
> 3. If there was no backlight specified in the device tree and you have
> the DP AUX channel and drm_edp_backlight_supported() then create a DP
> AUX backlight.
> 
> The one feature that wouldn't be supported by the above would be
> "DP_EDP_BACKLIGHT_AUX_PWM_PRODUCT_CAP". Presumably that's fine. If
> someone later wants to figure out how to solve that then they can.
> 

This looks perfect. I will make the changes.

> 
>>  static struct panel_desc panel_dpi;
>> 
>>  static int panel_dpi_probe(struct device *dev,
>> @@ -796,9 +874,24 @@ static int panel_simple_probe(struct device *dev, 
>> const struct panel_desc *desc,
>> 
>>         drm_panel_init(&panel->base, dev, &panel_simple_funcs, 
>> connector_type);
>> 
>> -       err = drm_panel_of_backlight(&panel->base);
>> -       if (err)
>> -               goto disable_pm_runtime;
>> +       if (panel->desc->uses_dpcd_backlight) {
>> +               if (!panel->aux) {
>> +                       dev_err(dev, "edp backlight needs DP aux\n");
>> +                       err = -EINVAL;
>> +                       goto disable_pm_runtime;
>> +               }
>> +
>> +               err = edp_backlight_register(dev, panel);
>> +               if (err) {
>> +                       dev_err(dev, "failed to register edp backlight 
>> %d\n", err);
>> +                       goto disable_pm_runtime;
>> +               }
>> +
>> +       } else {
> 
> nit: get rid of the blank line above the "} else {"
Oops! I will fix this.

> 
> 
>> +               err = drm_panel_of_backlight(&panel->base);
>> +               if (err)
>> +                       goto disable_pm_runtime;
>> +       }
> 
> See above where I'm suggesting some different logic. Specifically:
> always try the drm_panel_of_backlight() call and then fallback to the
> AUX backlight if "panel->base.backlight" is NULL and "panel->aux" is
> not NULL.

What I understood:
1. Create a new API drm_panel_dp_aux_backlight() in drm_panel.c
1.1. Register DP AUX backlight if "struct drm_dp_aux" is given and
     drm_edp_backlight_supported()
2. Create a call back function for backlight ".update_status()" inside 
drm_panel.c ?
   This function should also handle the backlight enable/disable 
operations.
3. Use the suggested rules to call drm_panel_dp_aux_backlight() as a 
fallback, if
    no backlight is specified in the DT.
4. Remove the @uses_dpcd_backlight flag from panel_desc as this should 
be auto-detected.

Thanks, for the review.

-Rajeev
Doug Anderson May 27, 2021, 9:41 p.m. UTC | #3
Hi,

On Thu, May 27, 2021 at 5:21 AM <rajeevny@codeaurora.org> wrote:
>
> >> @@ -171,6 +172,19 @@ struct panel_desc {
> >>
> >>         /** @connector_type: LVDS, eDP, DSI, DPI, etc. */
> >>         int connector_type;
> >> +
> >> +       /**
> >> +        * @uses_dpcd_backlight: Panel supports eDP dpcd backlight
> >> control.
> >> +        *
> >> +        * Set true, if the panel supports backlight control over eDP
> >> AUX channel
> >> +        * using DPCD registers as per VESA's standard.
> >> +        */
> >> +       bool uses_dpcd_backlight;
> >> +};
> >> +
> >> +struct edp_backlight {
> >> +       struct backlight_device *dev;
> >
> > Can you pick a name other than "dev". In my mind "dev" means you've
> > got a "struct device" or a "struct device *".
>
> In the backlight.h "bd" is used for "struct backlight_device". I can use
> "bd"?

That would be OK w/ me since it's not "dev". In theory you could also
call it "base" like panel-simple does with the base class drm_panel,
but I'll leave that up to you. It's mostly that in my brain "dev" is
reserved for "struct device" but otherwise I'm pretty flexible.


> >> +       struct drm_edp_backlight_info info;
> >>  };
> >>
> >>  struct panel_simple {
> >> @@ -194,6 +208,8 @@ struct panel_simple {
> >>
> >>         struct edid *edid;
> >>
> >> +       struct edp_backlight *edp_bl;
> >> +
> >
> > I don't think you need to add this pointer. See below for details, but
> > basically the backlight device should be in base.backlight. Any code
> > that needs the containing structure can use the standard
> > "container_of" syntax.
> >
>
> The documentation of the "struct drm_panel -> backlight" mentions
> "backlight is set by drm_panel_of_backlight() and drivers shall not
> assign it."
> That's why I was not sure if I should touch that part. Because of this,
> I added
> backlight enable/disable calls inside panel_simple_disable/enable().

Fair enough. In my opinion (subject to being overridden by the adults
in the room), if you move your backlight code into drm_panel.c and
call it drm_panel_dp_aux_backlight() then it's fair game to use. This
basically means that it's no longer a "driver" assigning it since it's
being done in drm_panel.c. ;-) Obviously you'd want to update the
comment, too...


> >> +               err = drm_panel_of_backlight(&panel->base);
> >> +               if (err)
> >> +                       goto disable_pm_runtime;
> >> +       }
> >
> > See above where I'm suggesting some different logic. Specifically:
> > always try the drm_panel_of_backlight() call and then fallback to the
> > AUX backlight if "panel->base.backlight" is NULL and "panel->aux" is
> > not NULL.
>
> What I understood:
> 1. Create a new API drm_panel_dp_aux_backlight() in drm_panel.c
> 1.1. Register DP AUX backlight if "struct drm_dp_aux" is given and
>      drm_edp_backlight_supported()
> 2. Create a call back function for backlight ".update_status()" inside
> drm_panel.c ?
>    This function should also handle the backlight enable/disable
> operations.
> 3. Use the suggested rules to call drm_panel_dp_aux_backlight() as a
> fallback, if
>     no backlight is specified in the DT.
> 4. Remove the @uses_dpcd_backlight flag from panel_desc as this should
> be auto-detected.

This sounds about right to me.

As per all of my reviews in the DRM subsystem, this is all just my
opinion and if someone more senior in DRM contradicts me then, of
course, you might have to change directions. Hopefully that doesn't
happen but it's always good to give warning...

-Doug