diff mbox

[3/5] drm: introduce pipe color correction properties

Message ID 1456397929-27406-4-git-send-email-lionel.g.landwerlin@intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Lionel Landwerlin Feb. 25, 2016, 10:58 a.m. UTC
Patch based on a previous series by Shashank Sharma.

This introduces optional properties to enable color correction at the
pipe level. It relies on 3 transformations applied to every pixels
displayed. First a lookup into a degamma table, then a multiplication
of the rgb components by a 3x3 matrix and finally another lookup into
a gamma table.

The following properties can be added to a pipe :
  - DEGAMMA_LUT : blob containing degamma LUT
  - DEGAMMA_LUT_SIZE : number of elements in DEGAMMA_LUT
  - CTM : transformation matrix applied after the degamma LUT
  - GAMMA_LUT : blob containing gamma LUT
  - GAMMA_LUT_SIZE : number of elements in GAMMA_LUT

DEGAMMA_LUT_SIZE and GAMMA_LUT_SIZE are read only properties, set by
the driver to tell userspace applications what sizes should be the
lookup tables in DEGAMMA_LUT and GAMMA_LUT.

A helper is also provided so legacy gamma correction is redirected
through these new properties.

v2: Register LUT size properties as range

v3: Fix round in drm_color_lut_get_value() helper
    More docs on how degamma/gamma properties are used

v4: Update contributors

v5: Rename CTM_MATRIX property to CTM (Doh!)
    Add legacy gamma_set atomic helper
    Describe CTM/LUT acronyms in the kernel doc

Signed-off-by: Shashank Sharma <shashank.sharma@intel.com>
Signed-off-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com>
Signed-off-by: Kumar, Kiran S <kiran.s.kumar@intel.com>
Signed-off-by: Kausal Malladi <kausalmalladi@gmail.com>
Reviewed-by: Matt Roper <matthew.d.roper@intel.com>
---
 Documentation/DocBook/gpu.tmpl      |  59 ++++++++++++++++++++-
 drivers/gpu/drm/drm_atomic.c        |  86 +++++++++++++++++++++++++++++-
 drivers/gpu/drm/drm_atomic_helper.c | 103 ++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/drm_crtc.c          |  35 ++++++++++++
 drivers/gpu/drm/drm_crtc_helper.c   |  33 ++++++++++++
 include/drm/drm_atomic_helper.h     |   3 ++
 include/drm/drm_crtc.h              |  46 +++++++++++++++-
 include/drm/drm_crtc_helper.h       |   3 ++
 include/uapi/drm/drm_mode.h         |  15 ++++++
 9 files changed, 378 insertions(+), 5 deletions(-)

Comments

Emil Velikov Feb. 26, 2016, 12:36 a.m. UTC | #1
Hi Lionel,

A bunch of suggestions - feel free to take or ignore them :-)

On 25 February 2016 at 10:58, Lionel Landwerlin
<lionel.g.landwerlin@intel.com> wrote:
> Patch based on a previous series by Shashank Sharma.
>
> This introduces optional properties to enable color correction at the
> pipe level. It relies on 3 transformations applied to every pixels
> displayed. First a lookup into a degamma table, then a multiplication
> of the rgb components by a 3x3 matrix and finally another lookup into
> a gamma table.
>
> The following properties can be added to a pipe :
>   - DEGAMMA_LUT : blob containing degamma LUT
>   - DEGAMMA_LUT_SIZE : number of elements in DEGAMMA_LUT
>   - CTM : transformation matrix applied after the degamma LUT
>   - GAMMA_LUT : blob containing gamma LUT
>   - GAMMA_LUT_SIZE : number of elements in GAMMA_LUT
>
> DEGAMMA_LUT_SIZE and GAMMA_LUT_SIZE are read only properties, set by
> the driver to tell userspace applications what sizes should be the
> lookup tables in DEGAMMA_LUT and GAMMA_LUT.
>
> A helper is also provided so legacy gamma correction is redirected
> through these new properties.
>
> v2: Register LUT size properties as range
>
> v3: Fix round in drm_color_lut_get_value() helper
>     More docs on how degamma/gamma properties are used
>
> v4: Update contributors
>
> v5: Rename CTM_MATRIX property to CTM (Doh!)
>     Add legacy gamma_set atomic helper
>     Describe CTM/LUT acronyms in the kernel doc
>
> Signed-off-by: Shashank Sharma <shashank.sharma@intel.com>
> Signed-off-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com>
> Signed-off-by: Kumar, Kiran S <kiran.s.kumar@intel.com>
> Signed-off-by: Kausal Malladi <kausalmalladi@gmail.com>
The above should be kept in the order of which people worked on them.

> Reviewed-by: Matt Roper <matthew.d.roper@intel.com>

> --- a/drivers/gpu/drm/drm_atomic.c
> +++ b/drivers/gpu/drm/drm_atomic.c

> @@ -376,6 +377,57 @@ int drm_atomic_set_mode_prop_for_crtc(struct drm_crtc_state *state,
>  EXPORT_SYMBOL(drm_atomic_set_mode_prop_for_crtc);
>
>  /**
> + * drm_atomic_replace_property_blob - replace a blob property
> + * @blob: a pointer to the member blob to be replaced
> + * @new_blob: the new blob to replace with
> + * @expected_size: the expected size of the new blob
> + * @replaced: whether the blob has been replaced
> + *
> + * RETURNS:
> + * Zero on success, error code on failure
> + */
> +static int
> +drm_atomic_replace_property_blob(struct drm_property_blob **blob,
> +                                struct drm_property_blob *new_blob,
> +                                bool *replaced)
"Replaced" here and though the rest of the patch is used as "changed".
Worth naming it that way ?

> +{
> +       struct drm_property_blob *old_blob = *blob;
> +
> +       if (old_blob == new_blob)
> +               return 0;
> +
> +       if (old_blob)
> +               drm_property_unreference_blob(old_blob);
> +       if (new_blob)
> +               drm_property_reference_blob(new_blob);
> +       *blob = new_blob;
> +       *replaced = true;
> +
> +       return 0;
The function always succeeds - drop the return value ?

> +}
> +
> +static int
> +drm_atomic_replace_property_blob_from_id(struct drm_crtc *crtc,
> +                                        struct drm_property_blob **blob,
> +                                        uint64_t blob_id,
> +                                        ssize_t expected_size,
> +                                        bool *replaced)
> +{
> +       struct drm_device *dev = crtc->dev;
> +       struct drm_property_blob *new_blob = NULL;
> +
> +       if (blob_id != 0) {
> +               new_blob = drm_property_lookup_blob(dev, blob_id);
> +               if (new_blob == NULL)
> +                       return -EINVAL;
> +               if (expected_size > 0 && expected_size != new_blob->length)
> +                       return -EINVAL;
> +       }
> +
Having a look at drm_atomic_set_mode_prop_for_crtc() I think I can
spot a bug - it shouldn't drop/unref the old blob in case of an error.
A case you handle nicely here. Perhaps it's worth using the
drm_atomic_replace_property_blob() in there ?


> @@ -397,6 +449,7 @@ int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
>  {
>         struct drm_device *dev = crtc->dev;
>         struct drm_mode_config *config = &dev->mode_config;
> +       bool replaced = false;
>         int ret;
>
>         if (property == config->prop_active)
> @@ -407,8 +460,31 @@ int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
>                 ret = drm_atomic_set_mode_prop_for_crtc(state, mode);
>                 drm_property_unreference_blob(mode);
>                 return ret;
> -       }
> -       else if (crtc->funcs->atomic_set_property)
> +       } else if (property == config->degamma_lut_property) {
> +               ret = drm_atomic_replace_property_blob_from_id(crtc,
> +                                       &state->degamma_lut,
> +                                       val,
> +                                       -1,
> +                                       &replaced);
> +               state->color_mgmt_changed = replaced;
> +               return ret;

> +       } else if (property == config->gamma_lut_property) {
> +               ret = drm_atomic_replace_property_blob_from_id(crtc,
> +                                       &state->gamma_lut,
> +                                       val,
> +                                       -1,
Wondering if these "-1" shouldn't be derived/replaced with the
contents of the respective _size properly ?


> @@ -444,6 +520,12 @@ drm_atomic_crtc_get_property(struct drm_crtc *crtc,
>                 *val = state->active;
>         else if (property == config->prop_mode_id)
>                 *val = (state->mode_blob) ? state->mode_blob->base.id : 0;
> +       else if (property == config->degamma_lut_property)
> +               *val = (state->degamma_lut) ? state->degamma_lut->base.id : 0;
> +       else if (property == config->ctm_property)
> +               *val = (state->ctm) ? state->ctm->base.id : 0;
> +       else if (property == config->gamma_lut_property)
> +               *val = (state->gamma_lut) ? state->gamma_lut->base.id : 0;
>         else if (crtc->funcs->atomic_get_property)
>                 return crtc->funcs->atomic_get_property(crtc, state, property, val);
>         else
> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> index 4da4f2a..7ab8040 100644
> --- a/drivers/gpu/drm/drm_atomic_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_helper.c

> @@ -2557,6 +2564,9 @@ void __drm_atomic_helper_crtc_destroy_state(struct drm_crtc *crtc,
>                                             struct drm_crtc_state *state)
>  {
>         drm_property_unreference_blob(state->mode_blob);
> +       drm_property_unreference_blob(state->degamma_lut);
> +       drm_property_unreference_blob(state->ctm);
> +       drm_property_unreference_blob(state->gamma_lut);
Might want to keep the dtor in reverse order comparing to the ctor -
duplicate_state()


> @@ -2870,3 +2880,96 @@ void drm_atomic_helper_connector_destroy_state(struct drm_connector *connector,
>         kfree(state);
>  }
>  EXPORT_SYMBOL(drm_atomic_helper_connector_destroy_state);
> +
> +/**
> + * drm_atomic_helper_legacy_gamma_set - set the legacy gamma correction table
> + * @crtc: CRTC object
> + * @red: red correction table
> + * @green: green correction table
> + * @blue: green correction table
> + * @start:
> + * @size: size of the tables
> + *
> + * Implements support for legacy gamma correction table for drivers
> + * that support color management through the DEGAMMA_LUT/GAMMA_LUT
> + * properties.
> + */
> +void drm_atomic_helper_legacy_gamma_set(struct drm_crtc *crtc,
> +                                       u16 *red, u16 *green, u16 *blue,
> +                                       uint32_t start, uint32_t size)
> +{
> +       struct drm_device *dev = crtc->dev;
> +       struct drm_mode_config *config = &dev->mode_config;
> +       struct drm_atomic_state *state;
> +       struct drm_crtc_state *crtc_state;
> +       struct drm_property_blob *blob = NULL;
> +       struct drm_color_lut *blob_data;
> +       int i, ret = 0;
> +
> +       state = drm_atomic_state_alloc(crtc->dev);
> +       if (!state)
> +               return;
> +
> +       blob = drm_property_create_blob(dev,
> +                                       sizeof(struct drm_color_lut) * size,
> +                                       NULL);
> +
To keep the bringup/teardown simpler (and complete):
Move create_blob() before to state_alloc() and null check blob
immediately. One would need to add unref_blob() when state_alloc()
fails.

> +       state->acquire_ctx = crtc->dev->mode_config.acquire_ctx;
> +retry:
> +       crtc_state = drm_atomic_get_crtc_state(state, crtc);
> +       if (IS_ERR(crtc_state)) {
> +               ret = PTR_ERR(crtc_state);
> +               goto fail;
> +       }
> +
> +       /* Reset DEGAMMA_LUT and CTM properties. */
> +       ret = drm_atomic_crtc_set_property(crtc, crtc_state,
> +                       config->degamma_lut_property, 0);
> +       if (ret)
> +               goto fail;
Add new blank line please.

> +       ret = drm_atomic_crtc_set_property(crtc, crtc_state,
> +                       config->ctm_property, 0);
> +       if (ret)
> +               goto fail;
> +
> +       /* Set GAMMA_LUT with legacy values. */
> +       if (blob == NULL) {
> +               ret = -ENOMEM;
> +               goto fail;
> +       }
> +
> +       blob_data = (struct drm_color_lut *) blob->data;
> +       for (i = 0; i < size; i++) {
> +               blob_data[i].red = red[i];
> +               blob_data[i].green = green[i];
> +               blob_data[i].blue = blue[i];
> +       }
> +
Move this loop after create_blob()

> +       ret = drm_atomic_crtc_set_property(crtc, crtc_state,
> +                       config->gamma_lut_property, blob->base.id);
> +       if (ret)
> +               goto fail;
> +
> +       ret = drm_atomic_commit(state);
> +       if (ret != 0)
Please check in a consistent way. Currently we have ret != 0 vs ret
and foo == NULL vs !foo.

> +               goto fail;
> +
> +       drm_property_unreference_blob(blob);
> +
> +       /* Driver takes ownership of state on successful commit. */
Move the comment before unreference_blob(), so that it's closer to
atomic_commit() ?


> --- a/drivers/gpu/drm/drm_crtc.c
> +++ b/drivers/gpu/drm/drm_crtc.c
> @@ -1554,6 +1554,41 @@ static int drm_mode_create_standard_properties(struct drm_device *dev)
>                 return -ENOMEM;
>         dev->mode_config.prop_mode_id = prop;
>
> +       prop = drm_property_create(dev,
> +                       DRM_MODE_PROP_BLOB,
> +                       "DEGAMMA_LUT", 0);

Just wondering -  don't we want this and the remaining properties to
be atomic only ? I doubt we have userspace that [will be updated to]
handle these, yet lacks atomic.


> --- a/drivers/gpu/drm/drm_crtc_helper.c
> +++ b/drivers/gpu/drm/drm_crtc_helper.c
> @@ -1075,3 +1075,36 @@ int drm_helper_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
>         return drm_plane_helper_commit(plane, plane_state, old_fb);
>  }
>  EXPORT_SYMBOL(drm_helper_crtc_mode_set_base);
> +
> +/**
> + * drm_helper_crtc_enable_color_mgmt - enable color management properties
> + * @crtc: DRM CRTC
> + * @degamma_lut_size: the size of the degamma lut (before CSC)
> + * @gamma_lut_size: the size of the gamma lut (after CSC)
> + *
> + * This function lets the driver enable the color correction properties on a
> + * CRTC. This includes 3 degamma, csc and gamma properties that userspace can
> + * set and 2 size properties to inform the userspace of the lut sizes.
> + */
> +void drm_helper_crtc_enable_color_mgmt(struct drm_crtc *crtc,
> +                                      int degamma_lut_size,
> +                                      int gamma_lut_size)
> +{
> +       struct drm_device *dev = crtc->dev;
> +       struct drm_mode_config *config = &dev->mode_config;
> +
> +       drm_object_attach_property(&crtc->base,
> +                                  config->degamma_lut_property, 0);
> +       drm_object_attach_property(&crtc->base,
> +                                  config->ctm_property, 0);
> +       drm_object_attach_property(&crtc->base,
> +                                  config->gamma_lut_property, 0);
> +
> +       drm_object_attach_property(&crtc->base,
> +                                  config->degamma_lut_size_property,
> +                                  degamma_lut_size);
> +       drm_object_attach_property(&crtc->base,
> +                                  config->gamma_lut_size_property,
> +                                  gamma_lut_size);
Wondering if we cannot have these listed like elsewhere in the patch.
I.e. have the _size property just after its respective counterpart.

Regards,
Emil
Matt Roper Feb. 26, 2016, 1:04 a.m. UTC | #2
On Fri, Feb 26, 2016 at 12:36:12AM +0000, Emil Velikov wrote:
...
> > --- a/drivers/gpu/drm/drm_crtc.c
> > +++ b/drivers/gpu/drm/drm_crtc.c
> > @@ -1554,6 +1554,41 @@ static int drm_mode_create_standard_properties(struct drm_device *dev)
> >                 return -ENOMEM;
> >         dev->mode_config.prop_mode_id = prop;
> >
> > +       prop = drm_property_create(dev,
> > +                       DRM_MODE_PROP_BLOB,
> > +                       "DEGAMMA_LUT", 0);
> 
> Just wondering -  don't we want this and the remaining properties to
> be atomic only ? I doubt we have userspace that [will be updated to]
> handle these, yet lacks atomic.

I asked this on a previous version of the series as well since I thought
I remembered Daniel Vetter indicating that the goal was to have new
capabilities going forward should require atomic (even if the properties
could still technically work okay via the legacy property interface).
Daniel Stone felt it was probably fine to still allow it via legacy
though:

  https://lists.freedesktop.org/archives/intel-gfx/2016-January/086120.html

I personally don't have a strong feeling either way, but we should
probably just make sure everyone is on the same page.  If we decide as a
community that we *do* want the atomic requirement going forward, maybe
we can add a note about that to the kerneldoc or something so we
remember in the future.


Matt
Lionel Landwerlin Feb. 26, 2016, 3:43 p.m. UTC | #3
On 26/02/16 00:36, Emil Velikov wrote:
> Hi Lionel,
>
> A bunch of suggestions - feel free to take or ignore them :-)
>
> On 25 February 2016 at 10:58, Lionel Landwerlin
> <lionel.g.landwerlin@intel.com> wrote:
>> Patch based on a previous series by Shashank Sharma.
>>
>> This introduces optional properties to enable color correction at the
>> pipe level. It relies on 3 transformations applied to every pixels
>> displayed. First a lookup into a degamma table, then a multiplication
>> of the rgb components by a 3x3 matrix and finally another lookup into
>> a gamma table.
>>
>> The following properties can be added to a pipe :
>>    - DEGAMMA_LUT : blob containing degamma LUT
>>    - DEGAMMA_LUT_SIZE : number of elements in DEGAMMA_LUT
>>    - CTM : transformation matrix applied after the degamma LUT
>>    - GAMMA_LUT : blob containing gamma LUT
>>    - GAMMA_LUT_SIZE : number of elements in GAMMA_LUT
>>
>> DEGAMMA_LUT_SIZE and GAMMA_LUT_SIZE are read only properties, set by
>> the driver to tell userspace applications what sizes should be the
>> lookup tables in DEGAMMA_LUT and GAMMA_LUT.
>>
>> A helper is also provided so legacy gamma correction is redirected
>> through these new properties.
>>
>> v2: Register LUT size properties as range
>>
>> v3: Fix round in drm_color_lut_get_value() helper
>>      More docs on how degamma/gamma properties are used
>>
>> v4: Update contributors
>>
>> v5: Rename CTM_MATRIX property to CTM (Doh!)
>>      Add legacy gamma_set atomic helper
>>      Describe CTM/LUT acronyms in the kernel doc
>>
>> Signed-off-by: Shashank Sharma <shashank.sharma@intel.com>
>> Signed-off-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com>
>> Signed-off-by: Kumar, Kiran S <kiran.s.kumar@intel.com>
>> Signed-off-by: Kausal Malladi <kausalmalladi@gmail.com>
> The above should be kept in the order of which people worked on them.
>
>> Reviewed-by: Matt Roper <matthew.d.roper@intel.com>
>> --- a/drivers/gpu/drm/drm_atomic.c
>> +++ b/drivers/gpu/drm/drm_atomic.c
>> @@ -376,6 +377,57 @@ int drm_atomic_set_mode_prop_for_crtc(struct drm_crtc_state *state,
>>   EXPORT_SYMBOL(drm_atomic_set_mode_prop_for_crtc);
>>
>>   /**
>> + * drm_atomic_replace_property_blob - replace a blob property
>> + * @blob: a pointer to the member blob to be replaced
>> + * @new_blob: the new blob to replace with
>> + * @expected_size: the expected size of the new blob
>> + * @replaced: whether the blob has been replaced
>> + *
>> + * RETURNS:
>> + * Zero on success, error code on failure
>> + */
>> +static int
>> +drm_atomic_replace_property_blob(struct drm_property_blob **blob,
>> +                                struct drm_property_blob *new_blob,
>> +                                bool *replaced)
> "Replaced" here and though the rest of the patch is used as "changed".
> Worth naming it that way ?
I think the former describes the action, the later the state.

>
>> +{
>> +       struct drm_property_blob *old_blob = *blob;
>> +
>> +       if (old_blob == new_blob)
>> +               return 0;
>> +
>> +       if (old_blob)
>> +               drm_property_unreference_blob(old_blob);
>> +       if (new_blob)
>> +               drm_property_reference_blob(new_blob);
>> +       *blob = new_blob;
>> +       *replaced = true;
>> +
>> +       return 0;
> The function always succeeds - drop the return value ?
Well spotted, dropping.

>> +}
>> +
>> +static int
>> +drm_atomic_replace_property_blob_from_id(struct drm_crtc *crtc,
>> +                                        struct drm_property_blob **blob,
>> +                                        uint64_t blob_id,
>> +                                        ssize_t expected_size,
>> +                                        bool *replaced)
>> +{
>> +       struct drm_device *dev = crtc->dev;
>> +       struct drm_property_blob *new_blob = NULL;
>> +
>> +       if (blob_id != 0) {
>> +               new_blob = drm_property_lookup_blob(dev, blob_id);
>> +               if (new_blob == NULL)
>> +                       return -EINVAL;
>> +               if (expected_size > 0 && expected_size != new_blob->length)
>> +                       return -EINVAL;
>> +       }
>> +
> Having a look at drm_atomic_set_mode_prop_for_crtc() I think I can
> spot a bug - it shouldn't drop/unref the old blob in case of an error.
> A case you handle nicely here. Perhaps it's worth using the
> drm_atomic_replace_property_blob() in there ?

I'm not sure it matters as the drm_crtc_state you're set properties on 
will be discarded if there is an error.
The current drm_crtc_state that has been applied onto the hardware 
should be untouched.

>
>> @@ -397,6 +449,7 @@ int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
>>   {
>>          struct drm_device *dev = crtc->dev;
>>          struct drm_mode_config *config = &dev->mode_config;
>> +       bool replaced = false;
>>          int ret;
>>
>>          if (property == config->prop_active)
>> @@ -407,8 +460,31 @@ int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
>>                  ret = drm_atomic_set_mode_prop_for_crtc(state, mode);
>>                  drm_property_unreference_blob(mode);
>>                  return ret;
>> -       }
>> -       else if (crtc->funcs->atomic_set_property)
>> +       } else if (property == config->degamma_lut_property) {
>> +               ret = drm_atomic_replace_property_blob_from_id(crtc,
>> +                                       &state->degamma_lut,
>> +                                       val,
>> +                                       -1,
>> +                                       &replaced);
>> +               state->color_mgmt_changed = replaced;
>> +               return ret;
>> +       } else if (property == config->gamma_lut_property) {
>> +               ret = drm_atomic_replace_property_blob_from_id(crtc,
>> +                                       &state->gamma_lut,
>> +                                       val,
>> +                                       -1,
> Wondering if these "-1" shouldn't be derived/replaced with the
> contents of the respective _size properly ?

This is because we accept more than one size of degamma/gamma LUT 
(legacy -> 256 elements, new LUT -> (de)gamma_lut_size elements).
It's up for the driver the check the size and raise an error in its 
atomic_check() vfunc.

>
>
>> @@ -444,6 +520,12 @@ drm_atomic_crtc_get_property(struct drm_crtc *crtc,
>>                  *val = state->active;
>>          else if (property == config->prop_mode_id)
>>                  *val = (state->mode_blob) ? state->mode_blob->base.id : 0;
>> +       else if (property == config->degamma_lut_property)
>> +               *val = (state->degamma_lut) ? state->degamma_lut->base.id : 0;
>> +       else if (property == config->ctm_property)
>> +               *val = (state->ctm) ? state->ctm->base.id : 0;
>> +       else if (property == config->gamma_lut_property)
>> +               *val = (state->gamma_lut) ? state->gamma_lut->base.id : 0;
>>          else if (crtc->funcs->atomic_get_property)
>>                  return crtc->funcs->atomic_get_property(crtc, state, property, val);
>>          else
>> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
>> index 4da4f2a..7ab8040 100644
>> --- a/drivers/gpu/drm/drm_atomic_helper.c
>> +++ b/drivers/gpu/drm/drm_atomic_helper.c
>> @@ -2557,6 +2564,9 @@ void __drm_atomic_helper_crtc_destroy_state(struct drm_crtc *crtc,
>>                                              struct drm_crtc_state *state)
>>   {
>>          drm_property_unreference_blob(state->mode_blob);
>> +       drm_property_unreference_blob(state->degamma_lut);
>> +       drm_property_unreference_blob(state->ctm);
>> +       drm_property_unreference_blob(state->gamma_lut);
> Might want to keep the dtor in reverse order comparing to the ctor -
> duplicate_state()
>
>
>> @@ -2870,3 +2880,96 @@ void drm_atomic_helper_connector_destroy_state(struct drm_connector *connector,
>>          kfree(state);
>>   }
>>   EXPORT_SYMBOL(drm_atomic_helper_connector_destroy_state);
>> +
>> +/**
>> + * drm_atomic_helper_legacy_gamma_set - set the legacy gamma correction table
>> + * @crtc: CRTC object
>> + * @red: red correction table
>> + * @green: green correction table
>> + * @blue: green correction table
>> + * @start:
>> + * @size: size of the tables
>> + *
>> + * Implements support for legacy gamma correction table for drivers
>> + * that support color management through the DEGAMMA_LUT/GAMMA_LUT
>> + * properties.
>> + */
>> +void drm_atomic_helper_legacy_gamma_set(struct drm_crtc *crtc,
>> +                                       u16 *red, u16 *green, u16 *blue,
>> +                                       uint32_t start, uint32_t size)
>> +{
>> +       struct drm_device *dev = crtc->dev;
>> +       struct drm_mode_config *config = &dev->mode_config;
>> +       struct drm_atomic_state *state;
>> +       struct drm_crtc_state *crtc_state;
>> +       struct drm_property_blob *blob = NULL;
>> +       struct drm_color_lut *blob_data;
>> +       int i, ret = 0;
>> +
>> +       state = drm_atomic_state_alloc(crtc->dev);
>> +       if (!state)
>> +               return;
>> +
>> +       blob = drm_property_create_blob(dev,
>> +                                       sizeof(struct drm_color_lut) * size,
>> +                                       NULL);
>> +
> To keep the bringup/teardown simpler (and complete):
> Move create_blob() before to state_alloc() and null check blob
> immediately. One would need to add unref_blob() when state_alloc()
> fails.
Moving the if (blob == NULL) right after the blob allocation to make it 
simpler.

What about completeness? Is there something inherently wrong here?
>
>> +       state->acquire_ctx = crtc->dev->mode_config.acquire_ctx;
>> +retry:
>> +       crtc_state = drm_atomic_get_crtc_state(state, crtc);
>> +       if (IS_ERR(crtc_state)) {
>> +               ret = PTR_ERR(crtc_state);
>> +               goto fail;
>> +       }
>> +
>> +       /* Reset DEGAMMA_LUT and CTM properties. */
>> +       ret = drm_atomic_crtc_set_property(crtc, crtc_state,
>> +                       config->degamma_lut_property, 0);
>> +       if (ret)
>> +               goto fail;
> Add new blank line please.

Sure.
>
>> +       ret = drm_atomic_crtc_set_property(crtc, crtc_state,
>> +                       config->ctm_property, 0);
>> +       if (ret)
>> +               goto fail;
>> +
>> +       /* Set GAMMA_LUT with legacy values. */
>> +       if (blob == NULL) {
>> +               ret = -ENOMEM;
>> +               goto fail;
>> +       }
>> +
>> +       blob_data = (struct drm_color_lut *) blob->data;
>> +       for (i = 0; i < size; i++) {
>> +               blob_data[i].red = red[i];
>> +               blob_data[i].green = green[i];
>> +               blob_data[i].blue = blue[i];
>> +       }
>> +
> Move this loop after create_blob()
Thanks, indeed no need to refill it in case of retry.

>
>> +       ret = drm_atomic_crtc_set_property(crtc, crtc_state,
>> +                       config->gamma_lut_property, blob->base.id);
>> +       if (ret)
>> +               goto fail;
>> +
>> +       ret = drm_atomic_commit(state);
>> +       if (ret != 0)
> Please check in a consistent way. Currently we have ret != 0 vs ret
> and foo == NULL vs !foo.

Sure.

>
>> +               goto fail;
>> +
>> +       drm_property_unreference_blob(blob);
>> +
>> +       /* Driver takes ownership of state on successful commit. */
> Move the comment before unreference_blob(), so that it's closer to
> atomic_commit() ?

Sure.
>
>> --- a/drivers/gpu/drm/drm_crtc.c
>> +++ b/drivers/gpu/drm/drm_crtc.c
>> @@ -1554,6 +1554,41 @@ static int drm_mode_create_standard_properties(struct drm_device *dev)
>>                  return -ENOMEM;
>>          dev->mode_config.prop_mode_id = prop;
>>
>> +       prop = drm_property_create(dev,
>> +                       DRM_MODE_PROP_BLOB,
>> +                       "DEGAMMA_LUT", 0);
> Just wondering -  don't we want this and the remaining properties to
> be atomic only ? I doubt we have userspace that [will be updated to]
> handle these, yet lacks atomic.
This was pointed out by Matt already. Here is Daniel Stone's response :
https://lists.freedesktop.org/archives/intel-gfx/2016-January/086120.html

I think it's fine to have these properties not atomic because it's not 
really something you update very often (maybe just when starting your UI).
That's actually how we would like to use them in ChromiumOS as a first 
step, until eventually ChromiumOS switches to atomic.

>
>
>> --- a/drivers/gpu/drm/drm_crtc_helper.c
>> +++ b/drivers/gpu/drm/drm_crtc_helper.c
>> @@ -1075,3 +1075,36 @@ int drm_helper_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
>>          return drm_plane_helper_commit(plane, plane_state, old_fb);
>>   }
>>   EXPORT_SYMBOL(drm_helper_crtc_mode_set_base);
>> +
>> +/**
>> + * drm_helper_crtc_enable_color_mgmt - enable color management properties
>> + * @crtc: DRM CRTC
>> + * @degamma_lut_size: the size of the degamma lut (before CSC)
>> + * @gamma_lut_size: the size of the gamma lut (after CSC)
>> + *
>> + * This function lets the driver enable the color correction properties on a
>> + * CRTC. This includes 3 degamma, csc and gamma properties that userspace can
>> + * set and 2 size properties to inform the userspace of the lut sizes.
>> + */
>> +void drm_helper_crtc_enable_color_mgmt(struct drm_crtc *crtc,
>> +                                      int degamma_lut_size,
>> +                                      int gamma_lut_size)
>> +{
>> +       struct drm_device *dev = crtc->dev;
>> +       struct drm_mode_config *config = &dev->mode_config;
>> +
>> +       drm_object_attach_property(&crtc->base,
>> +                                  config->degamma_lut_property, 0);
>> +       drm_object_attach_property(&crtc->base,
>> +                                  config->ctm_property, 0);
>> +       drm_object_attach_property(&crtc->base,
>> +                                  config->gamma_lut_property, 0);
>> +
>> +       drm_object_attach_property(&crtc->base,
>> +                                  config->degamma_lut_size_property,
>> +                                  degamma_lut_size);
>> +       drm_object_attach_property(&crtc->base,
>> +                                  config->gamma_lut_size_property,
>> +                                  gamma_lut_size);
> Wondering if we cannot have these listed like elsewhere in the patch.
> I.e. have the _size property just after its respective counterpart.
>
> Regards,
> Emil
>
Emil Velikov Feb. 27, 2016, 12:35 a.m. UTC | #4
On 26 February 2016 at 15:43, Lionel Landwerlin
<lionel.g.landwerlin@intel.com> wrote:
> On 26/02/16 00:36, Emil Velikov wrote:
>>
>> Hi Lionel,
>>
>> A bunch of suggestions - feel free to take or ignore them :-)
>>
>> On 25 February 2016 at 10:58, Lionel Landwerlin
>> <lionel.g.landwerlin@intel.com> wrote:

> I'm not sure it matters as the drm_crtc_state you're set properties on will
> be discarded if there is an error.
> The current drm_crtc_state that has been applied onto the hardware should be
> untouched.
>
That's the thing - the current drm_crts_state (mode_blob) is being
discarded, as opposed to the newly setup one. Although I could have
misunderstood something.


>
> This is because we accept more than one size of degamma/gamma LUT (legacy ->
> 256 elements, new LUT -> (de)gamma_lut_size elements).
> It's up for the driver the check the size and raise an error in its
> atomic_check() vfunc.
>
Ahh yes the legacy vs atomic difference.


>
> Moving the if (blob == NULL) right after the blob allocation to make it
> simpler.
>
> What about completeness? Is there something inherently wrong here?
The suggestion to reorder things is mostly to keep the setup/teardown
order in reverse order - create_blob, create_state and drop_state,
drop_blob. As you've noticed, I'm kind of a sucker for those :-) There
are no inter-dependencies that would require it here so it's not
required.

>>
>>
>>> +       state->acquire_ctx = crtc->dev->mode_config.acquire_ctx;
>>> +retry:
>>> +       crtc_state = drm_atomic_get_crtc_state(state, crtc);
>>> +       if (IS_ERR(crtc_state)) {
>>> +               ret = PTR_ERR(crtc_state);
>>> +               goto fail;
>>> +       }
>>> +
>>> +       /* Reset DEGAMMA_LUT and CTM properties. */
>>> +       ret = drm_atomic_crtc_set_property(crtc, crtc_state,
>>> +                       config->degamma_lut_property, 0);
>>> +       if (ret)
>>> +               goto fail;
>>
>> Add new blank line please.
>
>
> Sure.
>>
>>
>>> +       ret = drm_atomic_crtc_set_property(crtc, crtc_state,
>>> +                       config->ctm_property, 0);
>>> +       if (ret)
>>> +               goto fail;
>>> +
>>> +       /* Set GAMMA_LUT with legacy values. */
>>> +       if (blob == NULL) {
>>> +               ret = -ENOMEM;
>>> +               goto fail;
>>> +       }
>>> +
>>> +       blob_data = (struct drm_color_lut *) blob->data;
>>> +       for (i = 0; i < size; i++) {
>>> +               blob_data[i].red = red[i];
>>> +               blob_data[i].green = green[i];
>>> +               blob_data[i].blue = blue[i];
>>> +       }
>>> +
>>
>> Move this loop after create_blob()
>
> Thanks, indeed no need to refill it in case of retry.
>
>>
>>> +       ret = drm_atomic_crtc_set_property(crtc, crtc_state,
>>> +                       config->gamma_lut_property, blob->base.id);
>>> +       if (ret)
>>> +               goto fail;
>>> +
>>> +       ret = drm_atomic_commit(state);
>>> +       if (ret != 0)
>>
>> Please check in a consistent way. Currently we have ret != 0 vs ret
>> and foo == NULL vs !foo.
>
>
> Sure.
>
>>
>>> +               goto fail;
>>> +
>>> +       drm_property_unreference_blob(blob);
>>> +
>>> +       /* Driver takes ownership of state on successful commit. */
>>
>> Move the comment before unreference_blob(), so that it's closer to
>> atomic_commit() ?
>
>
> Sure.
>>
>>
>>> --- a/drivers/gpu/drm/drm_crtc.c
>>> +++ b/drivers/gpu/drm/drm_crtc.c
>>> @@ -1554,6 +1554,41 @@ static int
>>> drm_mode_create_standard_properties(struct drm_device *dev)
>>>                  return -ENOMEM;
>>>          dev->mode_config.prop_mode_id = prop;
>>>
>>> +       prop = drm_property_create(dev,
>>> +                       DRM_MODE_PROP_BLOB,
>>> +                       "DEGAMMA_LUT", 0);
>>
>> Just wondering -  don't we want this and the remaining properties to
>> be atomic only ? I doubt we have userspace that [will be updated to]
>> handle these, yet lacks atomic.
>
> This was pointed out by Matt already. Here is Daniel Stone's response :
> https://lists.freedesktop.org/archives/intel-gfx/2016-January/086120.html
>
> I think it's fine to have these properties not atomic because it's not
> really something you update very often (maybe just when starting your UI).
> That's actually how we would like to use them in ChromiumOS as a first step,
> until eventually ChromiumOS switches to atomic.
>
It wasn't a question of "can it be used" but more of "would it make
sense to not switch to atomics once we're here". As is, userspace will
need to have two slightly different code paths. Put the question "how
to deal with if compositor crashes and (re)applying gamma/etc.
multiple times" by Daniel Vettel, on top of it all and things get
extra messy. In both usespace in kernel.

My line of thought is - if there is a high demand for
non-atomic(legacy) degamma/etc. one can easily add it. On the other
hand, once it lands one cannot remove the code from the kernel, ever.

It's up-to you guys really. Just thought I mentioned it.

-Emil
diff mbox

Patch

diff --git a/Documentation/DocBook/gpu.tmpl b/Documentation/DocBook/gpu.tmpl
index fe6b36a..1692c4d 100644
--- a/Documentation/DocBook/gpu.tmpl
+++ b/Documentation/DocBook/gpu.tmpl
@@ -1816,7 +1816,7 @@  void intel_crt_init(struct drm_device *dev)
 	<td valign="top" >Description/Restrictions</td>
 	</tr>
 	<tr>
-	<td rowspan="37" valign="top" >DRM</td>
+	<td rowspan="42" valign="top" >DRM</td>
 	<td valign="top" >Generic</td>
 	<td valign="top" >“rotation”</td>
 	<td valign="top" >BITMASK</td>
@@ -2068,7 +2068,7 @@  void intel_crt_init(struct drm_device *dev)
 	<td valign="top" >property to suggest an Y offset for a connector</td>
 	</tr>
 	<tr>
-	<td rowspan="3" valign="top" >Optional</td>
+	<td rowspan="8" valign="top" >Optional</td>
 	<td valign="top" >“scaling mode”</td>
 	<td valign="top" >ENUM</td>
 	<td valign="top" >{ "None", "Full", "Center", "Full aspect" }</td>
@@ -2092,6 +2092,61 @@  void intel_crt_init(struct drm_device *dev)
 	<td valign="top" >TBD</td>
 	</tr>
 	<tr>
+	<td valign="top" >“DEGAMMA_LUT”</td>
+	<td valign="top" >BLOB</td>
+	<td valign="top" >0</td>
+	<td valign="top" >CRTC</td>
+	<td valign="top" >DRM property to set the degamma lookup table
+		(LUT) mapping pixel data from the framebuffer before it is
+		given to the transformation matrix. The data is an interpreted
+		as an array of struct drm_color_lut elements. Hardware might
+		choose not to use the full precision of the LUT elements nor
+		use all the elements of the LUT (for example the hardware
+		might choose to interpolate between LUT[0] and LUT[4]). </td>
+	</tr>
+	<tr>
+	<td valign="top" >“DEGAMMA_LUT_SIZE”</td>
+	<td valign="top" >RANGE | IMMUTABLE</td>
+	<td valign="top" >Min=0, Max=UINT_MAX</td>
+	<td valign="top" >CRTC</td>
+	<td valign="top" >DRM property to gives the size of the lookup
+		table to be set on the DEGAMMA_LUT property (the size depends
+		on the underlying hardware).</td>
+	</tr>
+	<tr>
+	<td valign="top" >“CTM”</td>
+	<td valign="top" >BLOB</td>
+	<td valign="top" >0</td>
+	<td valign="top" >CRTC</td>
+	<td valign="top" >DRM property to set the current
+		transformation matrix (CTM) apply to pixel data after the
+		lookup through the degamma LUT and before the lookup through
+		the gamma LUT. The data is an interpreted as a struct
+		drm_color_ctm.</td>
+	</tr>
+	<tr>
+	<td valign="top" >“GAMMA_LUT”</td>
+	<td valign="top" >BLOB</td>
+	<td valign="top" >0</td>
+	<td valign="top" >CRTC</td>
+	<td valign="top" >DRM property to set the gamma lookup table
+		(LUT) mapping pixel data after to the transformation matrix to
+		data sent to the connector. The data is an interpreted as an
+		array of struct drm_color_lut elements. Hardware might choose
+		not to use the full precision of the LUT elements nor use all
+		the elements of the LUT (for example the hardware might choose
+		to interpolate between LUT[0] and LUT[4]).</td>
+	</tr>
+	<tr>
+	<td valign="top" >“GAMMA_LUT_SIZE”</td>
+	<td valign="top" >RANGE | IMMUTABLE</td>
+	<td valign="top" >Min=0, Max=UINT_MAX</td>
+	<td valign="top" >CRTC</td>
+	<td valign="top" >DRM property to gives the size of the lookup
+		table to be set on the GAMMA_LUT property (the size depends on
+		the underlying hardware).</td>
+	</tr>
+	<tr>
 	<td rowspan="20" valign="top" >i915</td>
 	<td rowspan="2" valign="top" >Generic</td>
 	<td valign="top" >"Broadcast RGB"</td>
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 092620c..b767a4f 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -28,6 +28,7 @@ 
 
 #include <drm/drmP.h>
 #include <drm/drm_atomic.h>
+#include <drm/drm_mode.h>
 #include <drm/drm_plane_helper.h>
 
 /**
@@ -376,6 +377,57 @@  int drm_atomic_set_mode_prop_for_crtc(struct drm_crtc_state *state,
 EXPORT_SYMBOL(drm_atomic_set_mode_prop_for_crtc);
 
 /**
+ * drm_atomic_replace_property_blob - replace a blob property
+ * @blob: a pointer to the member blob to be replaced
+ * @new_blob: the new blob to replace with
+ * @expected_size: the expected size of the new blob
+ * @replaced: whether the blob has been replaced
+ *
+ * RETURNS:
+ * Zero on success, error code on failure
+ */
+static int
+drm_atomic_replace_property_blob(struct drm_property_blob **blob,
+				 struct drm_property_blob *new_blob,
+				 bool *replaced)
+{
+	struct drm_property_blob *old_blob = *blob;
+
+	if (old_blob == new_blob)
+		return 0;
+
+	if (old_blob)
+		drm_property_unreference_blob(old_blob);
+	if (new_blob)
+		drm_property_reference_blob(new_blob);
+	*blob = new_blob;
+	*replaced = true;
+
+	return 0;
+}
+
+static int
+drm_atomic_replace_property_blob_from_id(struct drm_crtc *crtc,
+					 struct drm_property_blob **blob,
+					 uint64_t blob_id,
+					 ssize_t expected_size,
+					 bool *replaced)
+{
+	struct drm_device *dev = crtc->dev;
+	struct drm_property_blob *new_blob = NULL;
+
+	if (blob_id != 0) {
+		new_blob = drm_property_lookup_blob(dev, blob_id);
+		if (new_blob == NULL)
+			return -EINVAL;
+		if (expected_size > 0 && expected_size != new_blob->length)
+			return -EINVAL;
+	}
+
+	return drm_atomic_replace_property_blob(blob, new_blob, replaced);
+}
+
+/**
  * drm_atomic_crtc_set_property - set property on CRTC
  * @crtc: the drm CRTC to set a property on
  * @state: the state object to update with the new property value
@@ -397,6 +449,7 @@  int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
 {
 	struct drm_device *dev = crtc->dev;
 	struct drm_mode_config *config = &dev->mode_config;
+	bool replaced = false;
 	int ret;
 
 	if (property == config->prop_active)
@@ -407,8 +460,31 @@  int drm_atomic_crtc_set_property(struct drm_crtc *crtc,
 		ret = drm_atomic_set_mode_prop_for_crtc(state, mode);
 		drm_property_unreference_blob(mode);
 		return ret;
-	}
-	else if (crtc->funcs->atomic_set_property)
+	} else if (property == config->degamma_lut_property) {
+		ret = drm_atomic_replace_property_blob_from_id(crtc,
+					&state->degamma_lut,
+					val,
+					-1,
+					&replaced);
+		state->color_mgmt_changed = replaced;
+		return ret;
+	} else if (property == config->ctm_property) {
+		ret = drm_atomic_replace_property_blob_from_id(crtc,
+					&state->ctm,
+					val,
+					sizeof(struct drm_color_ctm),
+					&replaced);
+		state->color_mgmt_changed = replaced;
+		return ret;
+	} else if (property == config->gamma_lut_property) {
+		ret = drm_atomic_replace_property_blob_from_id(crtc,
+					&state->gamma_lut,
+					val,
+					-1,
+					&replaced);
+		state->color_mgmt_changed = replaced;
+		return ret;
+	} else if (crtc->funcs->atomic_set_property)
 		return crtc->funcs->atomic_set_property(crtc, state, property, val);
 	else
 		return -EINVAL;
@@ -444,6 +520,12 @@  drm_atomic_crtc_get_property(struct drm_crtc *crtc,
 		*val = state->active;
 	else if (property == config->prop_mode_id)
 		*val = (state->mode_blob) ? state->mode_blob->base.id : 0;
+	else if (property == config->degamma_lut_property)
+		*val = (state->degamma_lut) ? state->degamma_lut->base.id : 0;
+	else if (property == config->ctm_property)
+		*val = (state->ctm) ? state->ctm->base.id : 0;
+	else if (property == config->gamma_lut_property)
+		*val = (state->gamma_lut) ? state->gamma_lut->base.id : 0;
 	else if (crtc->funcs->atomic_get_property)
 		return crtc->funcs->atomic_get_property(crtc, state, property, val);
 	else
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 4da4f2a..7ab8040 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -2513,10 +2513,17 @@  void __drm_atomic_helper_crtc_duplicate_state(struct drm_crtc *crtc,
 
 	if (state->mode_blob)
 		drm_property_reference_blob(state->mode_blob);
+	if (state->degamma_lut)
+		drm_property_reference_blob(state->degamma_lut);
+	if (state->ctm)
+		drm_property_reference_blob(state->ctm);
+	if (state->gamma_lut)
+		drm_property_reference_blob(state->gamma_lut);
 	state->mode_changed = false;
 	state->active_changed = false;
 	state->planes_changed = false;
 	state->connectors_changed = false;
+	state->color_mgmt_changed = false;
 	state->event = NULL;
 }
 EXPORT_SYMBOL(__drm_atomic_helper_crtc_duplicate_state);
@@ -2557,6 +2564,9 @@  void __drm_atomic_helper_crtc_destroy_state(struct drm_crtc *crtc,
 					    struct drm_crtc_state *state)
 {
 	drm_property_unreference_blob(state->mode_blob);
+	drm_property_unreference_blob(state->degamma_lut);
+	drm_property_unreference_blob(state->ctm);
+	drm_property_unreference_blob(state->gamma_lut);
 }
 EXPORT_SYMBOL(__drm_atomic_helper_crtc_destroy_state);
 
@@ -2870,3 +2880,96 @@  void drm_atomic_helper_connector_destroy_state(struct drm_connector *connector,
 	kfree(state);
 }
 EXPORT_SYMBOL(drm_atomic_helper_connector_destroy_state);
+
+/**
+ * drm_atomic_helper_legacy_gamma_set - set the legacy gamma correction table
+ * @crtc: CRTC object
+ * @red: red correction table
+ * @green: green correction table
+ * @blue: green correction table
+ * @start:
+ * @size: size of the tables
+ *
+ * Implements support for legacy gamma correction table for drivers
+ * that support color management through the DEGAMMA_LUT/GAMMA_LUT
+ * properties.
+ */
+void drm_atomic_helper_legacy_gamma_set(struct drm_crtc *crtc,
+					u16 *red, u16 *green, u16 *blue,
+					uint32_t start, uint32_t size)
+{
+	struct drm_device *dev = crtc->dev;
+	struct drm_mode_config *config = &dev->mode_config;
+	struct drm_atomic_state *state;
+	struct drm_crtc_state *crtc_state;
+	struct drm_property_blob *blob = NULL;
+	struct drm_color_lut *blob_data;
+	int i, ret = 0;
+
+	state = drm_atomic_state_alloc(crtc->dev);
+	if (!state)
+		return;
+
+	blob = drm_property_create_blob(dev,
+					sizeof(struct drm_color_lut) * size,
+					NULL);
+
+	state->acquire_ctx = crtc->dev->mode_config.acquire_ctx;
+retry:
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (IS_ERR(crtc_state)) {
+		ret = PTR_ERR(crtc_state);
+		goto fail;
+	}
+
+	/* Reset DEGAMMA_LUT and CTM properties. */
+	ret = drm_atomic_crtc_set_property(crtc, crtc_state,
+			config->degamma_lut_property, 0);
+	if (ret)
+		goto fail;
+	ret = drm_atomic_crtc_set_property(crtc, crtc_state,
+			config->ctm_property, 0);
+	if (ret)
+		goto fail;
+
+	/* Set GAMMA_LUT with legacy values. */
+	if (blob == NULL) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	blob_data = (struct drm_color_lut *) blob->data;
+	for (i = 0; i < size; i++) {
+		blob_data[i].red = red[i];
+		blob_data[i].green = green[i];
+		blob_data[i].blue = blue[i];
+	}
+
+	ret = drm_atomic_crtc_set_property(crtc, crtc_state,
+			config->gamma_lut_property, blob->base.id);
+	if (ret)
+		goto fail;
+
+	ret = drm_atomic_commit(state);
+	if (ret != 0)
+		goto fail;
+
+	drm_property_unreference_blob(blob);
+
+	/* Driver takes ownership of state on successful commit. */
+	return;
+fail:
+	if (ret == -EDEADLK)
+		goto backoff;
+
+	drm_atomic_state_free(state);
+	drm_property_unreference_blob(blob);
+
+	return;
+backoff:
+	drm_atomic_state_clear(state);
+	drm_atomic_legacy_backoff(state);
+
+	goto retry;
+}
+EXPORT_SYMBOL(drm_atomic_helper_legacy_gamma_set);
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c
index 8451400..50066d1 100644
--- a/drivers/gpu/drm/drm_crtc.c
+++ b/drivers/gpu/drm/drm_crtc.c
@@ -1554,6 +1554,41 @@  static int drm_mode_create_standard_properties(struct drm_device *dev)
 		return -ENOMEM;
 	dev->mode_config.prop_mode_id = prop;
 
+	prop = drm_property_create(dev,
+			DRM_MODE_PROP_BLOB,
+			"DEGAMMA_LUT", 0);
+	if (!prop)
+		return -ENOMEM;
+	dev->mode_config.degamma_lut_property = prop;
+
+	prop = drm_property_create_range(dev,
+			DRM_MODE_PROP_IMMUTABLE,
+			"DEGAMMA_LUT_SIZE", 0, UINT_MAX);
+	if (!prop)
+		return -ENOMEM;
+	dev->mode_config.degamma_lut_size_property = prop;
+
+	prop = drm_property_create(dev,
+			DRM_MODE_PROP_BLOB,
+			"CTM", 0);
+	if (!prop)
+		return -ENOMEM;
+	dev->mode_config.ctm_property = prop;
+
+	prop = drm_property_create(dev,
+			DRM_MODE_PROP_BLOB,
+			"GAMMA_LUT", 0);
+	if (!prop)
+		return -ENOMEM;
+	dev->mode_config.gamma_lut_property = prop;
+
+	prop = drm_property_create_range(dev,
+			DRM_MODE_PROP_IMMUTABLE,
+			"GAMMA_LUT_SIZE", 0, UINT_MAX);
+	if (!prop)
+		return -ENOMEM;
+	dev->mode_config.gamma_lut_size_property = prop;
+
 	return 0;
 }
 
diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c
index 7539eea..79555d2 100644
--- a/drivers/gpu/drm/drm_crtc_helper.c
+++ b/drivers/gpu/drm/drm_crtc_helper.c
@@ -1075,3 +1075,36 @@  int drm_helper_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
 	return drm_plane_helper_commit(plane, plane_state, old_fb);
 }
 EXPORT_SYMBOL(drm_helper_crtc_mode_set_base);
+
+/**
+ * drm_helper_crtc_enable_color_mgmt - enable color management properties
+ * @crtc: DRM CRTC
+ * @degamma_lut_size: the size of the degamma lut (before CSC)
+ * @gamma_lut_size: the size of the gamma lut (after CSC)
+ *
+ * This function lets the driver enable the color correction properties on a
+ * CRTC. This includes 3 degamma, csc and gamma properties that userspace can
+ * set and 2 size properties to inform the userspace of the lut sizes.
+ */
+void drm_helper_crtc_enable_color_mgmt(struct drm_crtc *crtc,
+				       int degamma_lut_size,
+				       int gamma_lut_size)
+{
+	struct drm_device *dev = crtc->dev;
+	struct drm_mode_config *config = &dev->mode_config;
+
+	drm_object_attach_property(&crtc->base,
+				   config->degamma_lut_property, 0);
+	drm_object_attach_property(&crtc->base,
+				   config->ctm_property, 0);
+	drm_object_attach_property(&crtc->base,
+				   config->gamma_lut_property, 0);
+
+	drm_object_attach_property(&crtc->base,
+				   config->degamma_lut_size_property,
+				   degamma_lut_size);
+	drm_object_attach_property(&crtc->base,
+				   config->gamma_lut_size_property,
+				   gamma_lut_size);
+}
+EXPORT_SYMBOL(drm_helper_crtc_enable_color_mgmt);
diff --git a/include/drm/drm_atomic_helper.h b/include/drm/drm_atomic_helper.h
index fe5efad..9054598c 100644
--- a/include/drm/drm_atomic_helper.h
+++ b/include/drm/drm_atomic_helper.h
@@ -146,6 +146,9 @@  __drm_atomic_helper_connector_destroy_state(struct drm_connector *connector,
 					    struct drm_connector_state *state);
 void drm_atomic_helper_connector_destroy_state(struct drm_connector *connector,
 					  struct drm_connector_state *state);
+void drm_atomic_helper_legacy_gamma_set(struct drm_crtc *crtc,
+					u16 *red, u16 *green, u16 *blue,
+					uint32_t start, uint32_t size);
 
 /**
  * drm_atomic_crtc_for_each_plane - iterate over planes currently attached to CRTC
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index 7fad193..f618803 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -305,6 +305,8 @@  struct drm_plane_helper_funcs;
  * @mode_changed: crtc_state->mode or crtc_state->enable has been changed
  * @active_changed: crtc_state->active has been toggled.
  * @connectors_changed: connectors to this crtc have been updated
+ * @color_mgmt_changed: color management properties have changed (degamma or
+ *	gamma LUT or CSC matrix)
  * @plane_mask: bitmask of (1 << drm_plane_index(plane)) of attached planes
  * @connector_mask: bitmask of (1 << drm_connector_index(connector)) of attached connectors
  * @encoder_mask: bitmask of (1 << drm_encoder_index(encoder)) of attached encoders
@@ -312,6 +314,11 @@  struct drm_plane_helper_funcs;
  * 	update to ensure framebuffer cleanup isn't done too early
  * @adjusted_mode: for use by helpers and drivers to compute adjusted mode timings
  * @mode: current mode timings
+ * @degamma_lut: Lookup table for converting framebuffer pixel data
+ *	before apply the conversion matrix
+ * @ctm: Transformation matrix
+ * @gamma_lut: Lookup table for converting pixel data after the
+ *	conversion matrix
  * @event: optional pointer to a DRM event to signal upon completion of the
  * 	state update
  * @state: backpointer to global drm_atomic_state
@@ -333,6 +340,7 @@  struct drm_crtc_state {
 	bool mode_changed : 1;
 	bool active_changed : 1;
 	bool connectors_changed : 1;
+	bool color_mgmt_changed : 1;
 
 	/* attached planes bitmask:
 	 * WARNING: transitional helpers do not maintain plane_mask so
@@ -355,6 +363,11 @@  struct drm_crtc_state {
 	/* blob property to expose current mode to atomic userspace */
 	struct drm_property_blob *mode_blob;
 
+	/* blob property to expose color management to userspace */
+	struct drm_property_blob *degamma_lut;
+	struct drm_property_blob *ctm;
+	struct drm_property_blob *gamma_lut;
+
 	struct drm_pending_vblank_event *event;
 
 	struct drm_atomic_state *state;
@@ -757,7 +770,7 @@  struct drm_crtc {
 	int x, y;
 	const struct drm_crtc_funcs *funcs;
 
-	/* CRTC gamma size for reporting to userspace */
+	/* Legacy FB CRTC gamma size for reporting to userspace */
 	uint32_t gamma_size;
 	uint16_t *gamma_store;
 
@@ -2026,6 +2039,15 @@  struct drm_mode_config_funcs {
  * @property_blob_list: list of all the blob property objects
  * @blob_lock: mutex for blob property allocation and management
  * @*_property: core property tracking
+ * @degamma_lut_property: LUT used to convert the framebuffer's colors to linear
+ *	gamma
+ * @degamma_lut_size_property: size of the degamma LUT as supported by the
+ *	driver (read-only)
+ * @ctm_property: Matrix used to convert colors after the lookup in the
+ *	degamma LUT
+ * @gamma_lut_property: LUT used to convert the colors, after the CSC matrix, to
+ *	the gamma space of the connected screen (read-only)
+ * @gamma_lut_size_property: size of the gamma LUT as supported by the driver
  * @preferred_depth: preferred RBG pixel depth, used by fb helpers
  * @prefer_shadow: hint to userspace to prefer shadow-fb rendering
  * @async_page_flip: does this device support async flips on the primary plane?
@@ -2128,6 +2150,13 @@  struct drm_mode_config {
 	struct drm_property *aspect_ratio_property;
 	struct drm_property *dirty_info_property;
 
+	/* Optional color correction properties */
+	struct drm_property *degamma_lut_property;
+	struct drm_property *degamma_lut_size_property;
+	struct drm_property *ctm_property;
+	struct drm_property *gamma_lut_property;
+	struct drm_property *gamma_lut_size_property;
+
 	/* properties for virtual machine layout */
 	struct drm_property *suggested_x_property;
 	struct drm_property *suggested_y_property;
@@ -2554,6 +2583,21 @@  static inline struct drm_property *drm_property_find(struct drm_device *dev,
 	return mo ? obj_to_property(mo) : NULL;
 }
 
+/*
+ * Extract a degamma/gamma LUT value provided by user and round it to the
+ * precision supported by the hardware.
+ */
+static inline uint32_t drm_color_lut_extract(uint32_t user_input,
+					     uint32_t bit_precision)
+{
+	uint32_t val = user_input + (1 << (16 - bit_precision - 1));
+	uint32_t max = 0xffff >> (16 - bit_precision);
+
+	val >>= 16 - bit_precision;
+
+	return clamp_val(val, 0, max);
+}
+
 /* Plane list iterator for legacy (overlay only) planes. */
 #define drm_for_each_legacy_plane(plane, dev) \
 	list_for_each_entry(plane, &(dev)->mode_config.plane_list, head) \
diff --git a/include/drm/drm_crtc_helper.h b/include/drm/drm_crtc_helper.h
index 4b37afa..97fa894 100644
--- a/include/drm/drm_crtc_helper.h
+++ b/include/drm/drm_crtc_helper.h
@@ -48,6 +48,9 @@  extern bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
 				     struct drm_display_mode *mode,
 				     int x, int y,
 				     struct drm_framebuffer *old_fb);
+extern void drm_helper_crtc_enable_color_mgmt(struct drm_crtc *crtc,
+					      int degamma_lut_size,
+					      int gamma_lut_size);
 extern bool drm_helper_crtc_in_use(struct drm_crtc *crtc);
 extern bool drm_helper_encoder_in_use(struct drm_encoder *encoder);
 
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
index 50adb46..c021743 100644
--- a/include/uapi/drm/drm_mode.h
+++ b/include/uapi/drm/drm_mode.h
@@ -487,6 +487,21 @@  struct drm_mode_crtc_lut {
 	__u64 blue;
 };
 
+struct drm_color_ctm {
+	/* Conversion matrix in S31.32 format. */
+	__s64 matrix[9];
+};
+
+struct drm_color_lut {
+	/*
+	 * Data is U0.16 fixed point format.
+	 */
+	__u16 red;
+	__u16 green;
+	__u16 blue;
+	__u16 reserved;
+};
+
 #define DRM_MODE_PAGE_FLIP_EVENT 0x01
 #define DRM_MODE_PAGE_FLIP_ASYNC 0x02
 #define DRM_MODE_PAGE_FLIP_FLAGS (DRM_MODE_PAGE_FLIP_EVENT|DRM_MODE_PAGE_FLIP_ASYNC)