drm/vc4: Add CTM support
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/
This commit is contained in:
parent
22445f0316
commit
766cc6b1f7
|
@ -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).
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -23,6 +23,117 @@
|
|||
#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)
|
||||
|
|
Loading…
Reference in New Issue