drm/vc4: Add CTM support
authorStefan Schake <stschake@gmail.com>
Fri, 20 Apr 2018 12:25:44 +0000 (05:25 -0700)
committerEric Anholt <eric@anholt.net>
Mon, 23 Apr 2018 18:32:55 +0000 (11:32 -0700)
The hardware has a single block for applying a CTM prior to gamma lut.
It can be fed with pixels from one of our CRTC at a time and uses a
matrix with S0.9 scalars. Use private atomic state to reject attempts
from userland to apply CTM for more than one CRTC at a time and reject
matrices with scalars that we can't approximate without integer bits.

Signed-off-by: Stefan Schake <stschake@gmail.com>
Signed-off-by: Eric Anholt <eric@anholt.net>
Reviewed-by: Eric Anholt <eric@anholt.net>
Link: https://patchwork.freedesktop.org/patch/218067/
drivers/gpu/drm/vc4/vc4_crtc.c
drivers/gpu/drm/vc4/vc4_drv.c
drivers/gpu/drm/vc4/vc4_drv.h
drivers/gpu/drm/vc4/vc4_kms.c

index 08fe8dd7d8df5125f8390a7bc2c1782e8cb904dd..83d3b7912fc2bcfe578cebd7f7eb7659bee70be0 100644 (file)
@@ -1018,6 +1018,11 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data)
        drm_mode_crtc_set_gamma_size(crtc, ARRAY_SIZE(vc4_crtc->lut_r));
        drm_crtc_enable_color_mgmt(crtc, 0, false, crtc->gamma_size);
 
+       /* We support CTM, but only for one CRTC at a time. It's therefore
+        * implemented as private driver state in vc4_kms, not here.
+        */
+       drm_crtc_enable_color_mgmt(crtc, 0, true, crtc->gamma_size);
+
        /* Set up some arbitrary number of planes.  We're not limited
         * by a set number of physical registers, just the space in
         * the HVS (16k) and how small an plane can be (28 bytes).
index af9515ae0e0a497fe5dbc03a25e3034bb39ae171..40ddeaafd65f556e8a20b0a5e85935f55eb0da55 100644 (file)
@@ -319,6 +319,7 @@ dev_unref:
 static void vc4_drm_unbind(struct device *dev)
 {
        struct drm_device *drm = dev_get_drvdata(dev);
+       struct vc4_dev *vc4 = to_vc4_dev(drm);
 
        drm_dev_unregister(drm);
 
@@ -326,6 +327,8 @@ static void vc4_drm_unbind(struct device *dev)
 
        drm_mode_config_cleanup(drm);
 
+       drm_atomic_private_obj_fini(&vc4->ctm_manager);
+
        drm_dev_unref(drm);
 }
 
index 4288615b66a208e70354133e52c154a16b98b8ee..22589d39083cb5fc2ce3dff12d844ee3fe4b7b65 100644 (file)
@@ -10,6 +10,7 @@
 #include <drm/drmP.h>
 #include <drm/drm_encoder.h>
 #include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_atomic.h>
 
 #include "uapi/drm/vc4_drm.h"
 
@@ -193,6 +194,9 @@ struct vc4_dev {
        } hangcheck;
 
        struct semaphore async_modeset;
+
+       struct drm_modeset_lock ctm_state_lock;
+       struct drm_private_obj ctm_manager;
 };
 
 static inline struct vc4_dev *
index e791e498a3dd955449bcf36f34f61492d8e8fac3..8a411e5f87768d9c56d5d1207e37be4962d4bbe0 100644 (file)
 #include <drm/drm_fb_cma_helper.h>
 #include <drm/drm_gem_framebuffer_helper.h>
 #include "vc4_drv.h"
+#include "vc4_regs.h"
+
+struct vc4_ctm_state {
+       struct drm_private_state base;
+       struct drm_color_ctm *ctm;
+       int fifo;
+};
+
+static struct vc4_ctm_state *to_vc4_ctm_state(struct drm_private_state *priv)
+{
+       return container_of(priv, struct vc4_ctm_state, base);
+}
+
+static struct vc4_ctm_state *vc4_get_ctm_state(struct drm_atomic_state *state,
+                                              struct drm_private_obj *manager)
+{
+       struct drm_device *dev = state->dev;
+       struct vc4_dev *vc4 = dev->dev_private;
+       struct drm_private_state *priv_state;
+       int ret;
+
+       ret = drm_modeset_lock(&vc4->ctm_state_lock, state->acquire_ctx);
+       if (ret)
+               return ERR_PTR(ret);
+
+       priv_state = drm_atomic_get_private_obj_state(state, manager);
+       if (IS_ERR(priv_state))
+               return ERR_CAST(priv_state);
+
+       return to_vc4_ctm_state(priv_state);
+}
+
+static struct drm_private_state *
+vc4_ctm_duplicate_state(struct drm_private_obj *obj)
+{
+       struct vc4_ctm_state *state;
+
+       state = kmemdup(obj->state, sizeof(*state), GFP_KERNEL);
+       if (!state)
+               return NULL;
+
+       __drm_atomic_helper_private_obj_duplicate_state(obj, &state->base);
+
+       return &state->base;
+}
+
+static void vc4_ctm_destroy_state(struct drm_private_obj *obj,
+                                 struct drm_private_state *state)
+{
+       struct vc4_ctm_state *ctm_state = to_vc4_ctm_state(state);
+
+       kfree(ctm_state);
+}
+
+static const struct drm_private_state_funcs vc4_ctm_state_funcs = {
+       .atomic_duplicate_state = vc4_ctm_duplicate_state,
+       .atomic_destroy_state = vc4_ctm_destroy_state,
+};
+
+/* Converts a DRM S31.32 value to the HW S0.9 format. */
+static u16 vc4_ctm_s31_32_to_s0_9(u64 in)
+{
+       u16 r;
+
+       /* Sign bit. */
+       r = in & BIT_ULL(63) ? BIT(9) : 0;
+
+       if ((in & GENMASK_ULL(62, 32)) > 0) {
+               /* We have zero integer bits so we can only saturate here. */
+               r |= GENMASK(8, 0);
+       } else {
+               /* Otherwise take the 9 most important fractional bits. */
+               r |= (in >> 23) & GENMASK(8, 0);
+       }
+
+       return r;
+}
+
+static void
+vc4_ctm_commit(struct vc4_dev *vc4, struct drm_atomic_state *state)
+{
+       struct vc4_ctm_state *ctm_state = to_vc4_ctm_state(vc4->ctm_manager.state);
+       struct drm_color_ctm *ctm = ctm_state->ctm;
+
+       if (ctm_state->fifo) {
+               HVS_WRITE(SCALER_OLEDCOEF2,
+                         VC4_SET_FIELD(vc4_ctm_s31_32_to_s0_9(ctm->matrix[0]),
+                                       SCALER_OLEDCOEF2_R_TO_R) |
+                         VC4_SET_FIELD(vc4_ctm_s31_32_to_s0_9(ctm->matrix[3]),
+                                       SCALER_OLEDCOEF2_R_TO_G) |
+                         VC4_SET_FIELD(vc4_ctm_s31_32_to_s0_9(ctm->matrix[6]),
+                                       SCALER_OLEDCOEF2_R_TO_B));
+               HVS_WRITE(SCALER_OLEDCOEF1,
+                         VC4_SET_FIELD(vc4_ctm_s31_32_to_s0_9(ctm->matrix[1]),
+                                       SCALER_OLEDCOEF1_G_TO_R) |
+                         VC4_SET_FIELD(vc4_ctm_s31_32_to_s0_9(ctm->matrix[4]),
+                                       SCALER_OLEDCOEF1_G_TO_G) |
+                         VC4_SET_FIELD(vc4_ctm_s31_32_to_s0_9(ctm->matrix[7]),
+                                       SCALER_OLEDCOEF1_G_TO_B));
+               HVS_WRITE(SCALER_OLEDCOEF0,
+                         VC4_SET_FIELD(vc4_ctm_s31_32_to_s0_9(ctm->matrix[2]),
+                                       SCALER_OLEDCOEF0_B_TO_R) |
+                         VC4_SET_FIELD(vc4_ctm_s31_32_to_s0_9(ctm->matrix[5]),
+                                       SCALER_OLEDCOEF0_B_TO_G) |
+                         VC4_SET_FIELD(vc4_ctm_s31_32_to_s0_9(ctm->matrix[8]),
+                                       SCALER_OLEDCOEF0_B_TO_B));
+       }
+
+       HVS_WRITE(SCALER_OLEDOFFS,
+                 VC4_SET_FIELD(ctm_state->fifo, SCALER_OLEDOFFS_DISPFIFO));
+}
 
 static void
 vc4_atomic_complete_commit(struct drm_atomic_state *state)
@@ -36,6 +147,8 @@ vc4_atomic_complete_commit(struct drm_atomic_state *state)
 
        drm_atomic_helper_commit_modeset_disables(dev, state);
 
+       vc4_ctm_commit(vc4, state);
+
        drm_atomic_helper_commit_planes(dev, state, 0);
 
        drm_atomic_helper_commit_modeset_enables(dev, state);
@@ -207,9 +320,89 @@ static struct drm_framebuffer *vc4_fb_create(struct drm_device *dev,
        return drm_gem_fb_create(dev, file_priv, mode_cmd);
 }
 
+/* Our CTM has some peculiar limitations: we can only enable it for one CRTC
+ * at a time and the HW only supports S0.9 scalars. To account for the latter,
+ * we don't allow userland to set a CTM that we have no hope of approximating.
+ */
+static int
+vc4_ctm_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
+{
+       struct vc4_dev *vc4 = to_vc4_dev(dev);
+       struct vc4_ctm_state *ctm_state = NULL;
+       struct drm_crtc *crtc;
+       struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+       struct drm_color_ctm *ctm;
+       int i;
+
+       for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) {
+               /* CTM is being disabled. */
+               if (!new_crtc_state->ctm && old_crtc_state->ctm) {
+                       ctm_state = vc4_get_ctm_state(state, &vc4->ctm_manager);
+                       if (IS_ERR(ctm_state))
+                               return PTR_ERR(ctm_state);
+                       ctm_state->fifo = 0;
+               }
+       }
+
+       for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) {
+               if (new_crtc_state->ctm == old_crtc_state->ctm)
+                       continue;
+
+               if (!ctm_state) {
+                       ctm_state = vc4_get_ctm_state(state, &vc4->ctm_manager);
+                       if (IS_ERR(ctm_state))
+                               return PTR_ERR(ctm_state);
+               }
+
+               /* CTM is being enabled or the matrix changed. */
+               if (new_crtc_state->ctm) {
+                       /* fifo is 1-based since 0 disables CTM. */
+                       int fifo = to_vc4_crtc(crtc)->channel + 1;
+
+                       /* Check userland isn't trying to turn on CTM for more
+                        * than one CRTC at a time.
+                        */
+                       if (ctm_state->fifo && ctm_state->fifo != fifo) {
+                               DRM_DEBUG_DRIVER("Too many CTM configured\n");
+                               return -EINVAL;
+                       }
+
+                       /* Check we can approximate the specified CTM.
+                        * We disallow scalars |c| > 1.0 since the HW has
+                        * no integer bits.
+                        */
+                       ctm = new_crtc_state->ctm->data;
+                       for (i = 0; i < ARRAY_SIZE(ctm->matrix); i++) {
+                               u64 val = ctm->matrix[i];
+
+                               val &= ~BIT_ULL(63);
+                               if (val > BIT_ULL(32))
+                                       return -EINVAL;
+                       }
+
+                       ctm_state->fifo = fifo;
+                       ctm_state->ctm = ctm;
+               }
+       }
+
+       return 0;
+}
+
+static int
+vc4_atomic_check(struct drm_device *dev, struct drm_atomic_state *state)
+{
+       int ret;
+
+       ret = vc4_ctm_atomic_check(dev, state);
+       if (ret < 0)
+               return ret;
+
+       return drm_atomic_helper_check(dev, state);
+}
+
 static const struct drm_mode_config_funcs vc4_mode_funcs = {
        .output_poll_changed = drm_fb_helper_output_poll_changed,
-       .atomic_check = drm_atomic_helper_check,
+       .atomic_check = vc4_atomic_check,
        .atomic_commit = vc4_atomic_commit,
        .fb_create = vc4_fb_create,
 };
@@ -217,6 +410,7 @@ static const struct drm_mode_config_funcs vc4_mode_funcs = {
 int vc4_kms_load(struct drm_device *dev)
 {
        struct vc4_dev *vc4 = to_vc4_dev(dev);
+       struct vc4_ctm_state *ctm_state;
        int ret;
 
        sema_init(&vc4->async_modeset, 1);
@@ -237,6 +431,14 @@ int vc4_kms_load(struct drm_device *dev)
        dev->mode_config.async_page_flip = true;
        dev->mode_config.allow_fb_modifiers = true;
 
+       drm_modeset_lock_init(&vc4->ctm_state_lock);
+
+       ctm_state = kzalloc(sizeof(*ctm_state), GFP_KERNEL);
+       if (!ctm_state)
+               return -ENOMEM;
+       drm_atomic_private_obj_init(&vc4->ctm_manager, &ctm_state->base,
+                                   &vc4_ctm_state_funcs);
+
        drm_mode_config_reset(dev);
 
        if (dev->mode_config.num_connector)