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@xxxxxxxxx> --- v3: New in the series drivers/gpu/drm/vc4/vc4_crtc.c | 6 +- drivers/gpu/drm/vc4/vc4_drv.h | 3 + drivers/gpu/drm/vc4/vc4_kms.c | 167 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 174 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 08fe8dd..32bdf13 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -1016,7 +1016,11 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data) primary_plane->crtc = crtc; vc4_crtc->channel = vc4_crtc->data->hvs_channel; 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 diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 4288615..ee22a5b 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -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,8 @@ struct vc4_dev { } hangcheck; struct semaphore async_modeset; + + struct drm_private_obj ctm_manager; }; static inline struct vc4_dev * diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c index ba60153..f2eca4d 100644 --- a/drivers/gpu/drm/vc4/vc4_kms.c +++ b/drivers/gpu/drm/vc4/vc4_kms.c @@ -23,6 +23,102 @@ #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) +{ + return to_vc4_ctm_state(drm_atomic_get_private_obj_state(state, manager)); +} + +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; + /* We have zero integer bits so we can only saturate here. */ + if ((in & GENMASK_ULL(62, 32)) > 0) + r |= GENMASK(8, 0); + /* Otherwise take the 9 most important fractional bits. */ + else + r |= (in >> 22) & 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 = vc4_get_ctm_state(state, + &vc4->ctm_manager); + 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 +132,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); @@ -187,9 +285,69 @@ 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 = vc4_get_ctm_state(state, + &vc4->ctm_manager); + struct drm_crtc *crtc; + struct drm_crtc_state *old_crtc_state, *new_crtc_state; + struct drm_color_ctm *ctm; + int i; + + ctm_state->fifo = 0; + for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { + if (new_crtc_state->ctm) { + /* Check userland isn't trying to turn on CTM for more + * than one CRTC at a time. + */ + if (ctm_state->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; + } + + /* fifo is 1-based since 0 disables CTM. */ + ctm_state->fifo = to_vc4_crtc(crtc)->channel + 1; + 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, }; @@ -197,6 +355,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); @@ -224,5 +383,11 @@ int vc4_kms_load(struct drm_device *dev) drm_kms_helper_poll_init(dev); + 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); + return 0; } -- 2.7.4 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel