drm: mali-dp: enable gamma support
authorMihail Atanassov <mihail.atanassov@arm.com>
Wed, 1 Feb 2017 14:48:50 +0000 (14:48 +0000)
committerLiviu Dudau <Liviu.Dudau@arm.com>
Mon, 24 Apr 2017 09:45:34 +0000 (10:45 +0100)
Add gamma via the DRM GAMMA_LUT/GAMMA_LUT_SIZE CRTC
properties. The expected LUT size is 4096 in order
to produce as accurate a set of segments as possible.

This version uses only the green channel's gamma curve
to set the hardware curve on DP550/650. For the sake of
simplicity, it uses the same table of coefficients for
all 3 curves on DP500.

Signed-off-by: Mihail Atanassov <mihail.atanassov@arm.com>
Signed-off-by: Liviu Dudau <liviu.dudau@arm.com>
drivers/gpu/drm/arm/malidp_crtc.c
drivers/gpu/drm/arm/malidp_drv.c
drivers/gpu/drm/arm/malidp_drv.h
drivers/gpu/drm/arm/malidp_hw.c
drivers/gpu/drm/arm/malidp_hw.h
drivers/gpu/drm/arm/malidp_regs.h

index ec1396f..27ab094 100644 (file)
@@ -93,6 +93,108 @@ static void malidp_crtc_disable(struct drm_crtc *crtc)
        }
 }
 
+static const struct gamma_curve_segment {
+       u16 start;
+       u16 end;
+} segments[MALIDP_COEFFTAB_NUM_COEFFS] = {
+       /* sector 0 */
+       {    0,    0 }, {    1,    1 }, {    2,    2 }, {    3,    3 },
+       {    4,    4 }, {    5,    5 }, {    6,    6 }, {    7,    7 },
+       {    8,    8 }, {    9,    9 }, {   10,   10 }, {   11,   11 },
+       {   12,   12 }, {   13,   13 }, {   14,   14 }, {   15,   15 },
+       /* sector 1 */
+       {   16,   19 }, {   20,   23 }, {   24,   27 }, {   28,   31 },
+       /* sector 2 */
+       {   32,   39 }, {   40,   47 }, {   48,   55 }, {   56,   63 },
+       /* sector 3 */
+       {   64,   79 }, {   80,   95 }, {   96,  111 }, {  112,  127 },
+       /* sector 4 */
+       {  128,  159 }, {  160,  191 }, {  192,  223 }, {  224,  255 },
+       /* sector 5 */
+       {  256,  319 }, {  320,  383 }, {  384,  447 }, {  448,  511 },
+       /* sector 6 */
+       {  512,  639 }, {  640,  767 }, {  768,  895 }, {  896, 1023 },
+       { 1024, 1151 }, { 1152, 1279 }, { 1280, 1407 }, { 1408, 1535 },
+       { 1536, 1663 }, { 1664, 1791 }, { 1792, 1919 }, { 1920, 2047 },
+       { 2048, 2175 }, { 2176, 2303 }, { 2304, 2431 }, { 2432, 2559 },
+       { 2560, 2687 }, { 2688, 2815 }, { 2816, 2943 }, { 2944, 3071 },
+       { 3072, 3199 }, { 3200, 3327 }, { 3328, 3455 }, { 3456, 3583 },
+       { 3584, 3711 }, { 3712, 3839 }, { 3840, 3967 }, { 3968, 4095 },
+};
+
+#define DE_COEFTAB_DATA(a, b) ((((a) & 0xfff) << 16) | (((b) & 0xfff)))
+
+static void malidp_generate_gamma_table(struct drm_property_blob *lut_blob,
+                                       u32 coeffs[MALIDP_COEFFTAB_NUM_COEFFS])
+{
+       struct drm_color_lut *lut = (struct drm_color_lut *)lut_blob->data;
+       int i;
+
+       for (i = 0; i < MALIDP_COEFFTAB_NUM_COEFFS; ++i) {
+               u32 a, b, delta_in, out_start, out_end;
+
+               delta_in = segments[i].end - segments[i].start;
+               /* DP has 12-bit internal precision for its LUTs. */
+               out_start = drm_color_lut_extract(lut[segments[i].start].green,
+                                                 12);
+               out_end = drm_color_lut_extract(lut[segments[i].end].green, 12);
+               a = (delta_in == 0) ? 0 : ((out_end - out_start) * 256) / delta_in;
+               b = out_start;
+               coeffs[i] = DE_COEFTAB_DATA(a, b);
+       }
+}
+
+/*
+ * Check if there is a new gamma LUT and if it is of an acceptable size. Also,
+ * reject any LUTs that use distinct red, green, and blue curves.
+ */
+static int malidp_crtc_atomic_check_gamma(struct drm_crtc *crtc,
+                                         struct drm_crtc_state *state)
+{
+       struct malidp_crtc_state *mc = to_malidp_crtc_state(state);
+       struct drm_color_lut *lut;
+       size_t lut_size;
+       int i;
+
+       if (!state->color_mgmt_changed || !state->gamma_lut)
+               return 0;
+
+       if (crtc->state->gamma_lut &&
+           (crtc->state->gamma_lut->base.id == state->gamma_lut->base.id))
+               return 0;
+
+       if (state->gamma_lut->length % sizeof(struct drm_color_lut))
+               return -EINVAL;
+
+       lut_size = state->gamma_lut->length / sizeof(struct drm_color_lut);
+       if (lut_size != MALIDP_GAMMA_LUT_SIZE)
+               return -EINVAL;
+
+       lut = (struct drm_color_lut *)state->gamma_lut->data;
+       for (i = 0; i < lut_size; ++i)
+               if (!((lut[i].red == lut[i].green) &&
+                     (lut[i].red == lut[i].blue)))
+                       return -EINVAL;
+
+       if (!state->mode_changed) {
+               int ret;
+
+               state->mode_changed = true;
+               /*
+                * Kerneldoc for drm_atomic_helper_check_modeset mandates that
+                * it be invoked when the driver sets ->mode_changed. Since
+                * changing the gamma LUT doesn't depend on any external
+                * resources, it is safe to call it only once.
+                */
+               ret = drm_atomic_helper_check_modeset(crtc->dev, state->state);
+               if (ret)
+                       return ret;
+       }
+
+       malidp_generate_gamma_table(state->gamma_lut, mc->gamma_coeffs);
+       return 0;
+}
+
 static int malidp_crtc_atomic_check(struct drm_crtc *crtc,
                                    struct drm_crtc_state *state)
 {
@@ -168,7 +270,7 @@ static int malidp_crtc_atomic_check(struct drm_crtc *crtc,
                }
        }
 
-       return 0;
+       return malidp_crtc_atomic_check_gamma(crtc, state);
 }
 
 static const struct drm_crtc_helper_funcs malidp_crtc_helper_funcs = {
@@ -180,16 +282,19 @@ static const struct drm_crtc_helper_funcs malidp_crtc_helper_funcs = {
 
 static struct drm_crtc_state *malidp_crtc_duplicate_state(struct drm_crtc *crtc)
 {
-       struct malidp_crtc_state *state;
+       struct malidp_crtc_state *state, *old_state;
 
        if (WARN_ON(!crtc->state))
                return NULL;
 
+       old_state = to_malidp_crtc_state(crtc->state);
        state = kmalloc(sizeof(*state), GFP_KERNEL);
        if (!state)
                return NULL;
 
        __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);
+       memcpy(state->gamma_coeffs, old_state->gamma_coeffs,
+              sizeof(state->gamma_coeffs));
 
        return &state->base;
 }
@@ -244,6 +349,7 @@ static void malidp_crtc_disable_vblank(struct drm_crtc *crtc)
 }
 
 static const struct drm_crtc_funcs malidp_crtc_funcs = {
+       .gamma_set = drm_atomic_helper_legacy_gamma_set,
        .destroy = drm_crtc_cleanup,
        .set_config = drm_atomic_helper_set_config,
        .page_flip = drm_atomic_helper_page_flip,
@@ -281,11 +387,15 @@ int malidp_crtc_init(struct drm_device *drm)
 
        ret = drm_crtc_init_with_planes(drm, &malidp->crtc, primary, NULL,
                                        &malidp_crtc_funcs, NULL);
+       if (ret)
+               goto crtc_cleanup_planes;
 
-       if (!ret) {
-               drm_crtc_helper_add(&malidp->crtc, &malidp_crtc_helper_funcs);
-               return 0;
-       }
+       drm_crtc_helper_add(&malidp->crtc, &malidp_crtc_helper_funcs);
+       drm_mode_crtc_set_gamma_size(&malidp->crtc, MALIDP_GAMMA_LUT_SIZE);
+       /* No inverse-gamma and color adjustments yet. */
+       drm_crtc_enable_color_mgmt(&malidp->crtc, 0, false, MALIDP_GAMMA_LUT_SIZE);
+
+       return 0;
 
 crtc_cleanup_planes:
        malidp_de_planes_destroy(drm);
index e954c22..1eb6bbc 100644 (file)
 
 #define MALIDP_CONF_VALID_TIMEOUT      250
 
+static void malidp_write_gamma_table(struct malidp_hw_device *hwdev,
+                                    u32 data[MALIDP_COEFFTAB_NUM_COEFFS])
+{
+       int i;
+       /* Update all channels with a single gamma curve. */
+       const u32 gamma_write_mask = GENMASK(18, 16);
+       /*
+        * Always write an entire table, so the address field in
+        * DE_COEFFTAB_ADDR is 0 and we can use the gamma_write_mask bitmask
+        * directly.
+        */
+       malidp_hw_write(hwdev, gamma_write_mask,
+                       hwdev->map.coeffs_base + MALIDP_COEF_TABLE_ADDR);
+       for (i = 0; i < MALIDP_COEFFTAB_NUM_COEFFS; ++i)
+               malidp_hw_write(hwdev, data[i],
+                               hwdev->map.coeffs_base +
+                               MALIDP_COEF_TABLE_DATA);
+}
+
+static void malidp_atomic_commit_update_gamma(struct drm_crtc *crtc,
+                                             struct drm_crtc_state *old_state)
+{
+       struct malidp_drm *malidp = crtc_to_malidp_device(crtc);
+       struct malidp_hw_device *hwdev = malidp->dev;
+
+       if (!crtc->state->color_mgmt_changed)
+               return;
+
+       if (!crtc->state->gamma_lut) {
+               malidp_hw_clearbits(hwdev,
+                                   MALIDP_DISP_FUNC_GAMMA,
+                                   MALIDP_DE_DISPLAY_FUNC);
+       } else {
+               struct malidp_crtc_state *mc =
+                       to_malidp_crtc_state(crtc->state);
+
+               if (!old_state->gamma_lut || (crtc->state->gamma_lut->base.id !=
+                                             old_state->gamma_lut->base.id))
+                       malidp_write_gamma_table(hwdev, mc->gamma_coeffs);
+
+               malidp_hw_setbits(hwdev, MALIDP_DISP_FUNC_GAMMA,
+                                 MALIDP_DE_DISPLAY_FUNC);
+       }
+}
+
 /*
  * set the "config valid" bit and wait until the hardware acts on it
  */
@@ -92,11 +137,17 @@ static void malidp_atomic_commit_hw_done(struct drm_atomic_state *state)
 static void malidp_atomic_commit_tail(struct drm_atomic_state *state)
 {
        struct drm_device *drm = state->dev;
+       struct drm_crtc *crtc;
+       struct drm_crtc_state *old_crtc_state;
+       int i;
 
        pm_runtime_get_sync(drm->dev);
 
        drm_atomic_helper_commit_modeset_disables(drm, state);
 
+       for_each_crtc_in_state(state, crtc, old_crtc_state, i)
+               malidp_atomic_commit_update_gamma(crtc, old_crtc_state);
+
        drm_atomic_helper_commit_planes(drm, state, 0);
 
        drm_atomic_helper_commit_modeset_enables(drm, state);
index bdc2620..f5abd8b 100644 (file)
@@ -50,6 +50,7 @@ struct malidp_plane_state {
 
 struct malidp_crtc_state {
        struct drm_crtc_state base;
+       u32 gamma_coeffs[MALIDP_COEFFTAB_NUM_COEFFS];
 };
 
 #define to_malidp_crtc_state(x) container_of(x, struct malidp_crtc_state, base)
index 9f55130..b8025b7 100644 (file)
@@ -415,6 +415,7 @@ static int malidp650_query_hw(struct malidp_hw_device *hwdev)
 const struct malidp_hw_device malidp_device[MALIDP_MAX_DEVICES] = {
        [MALIDP_500] = {
                .map = {
+                       .coeffs_base = MALIDP500_COEFFS_BASE,
                        .se_base = MALIDP500_SE_BASE,
                        .dc_base = MALIDP500_DC_BASE,
                        .out_depth_base = MALIDP500_OUTPUT_DEPTH,
@@ -451,6 +452,7 @@ const struct malidp_hw_device malidp_device[MALIDP_MAX_DEVICES] = {
        },
        [MALIDP_550] = {
                .map = {
+                       .coeffs_base = MALIDP550_COEFFS_BASE,
                        .se_base = MALIDP550_SE_BASE,
                        .dc_base = MALIDP550_DC_BASE,
                        .out_depth_base = MALIDP550_DE_OUTPUT_DEPTH,
@@ -485,6 +487,7 @@ const struct malidp_hw_device malidp_device[MALIDP_MAX_DEVICES] = {
        },
        [MALIDP_650] = {
                .map = {
+                       .coeffs_base = MALIDP550_COEFFS_BASE,
                        .se_base = MALIDP550_SE_BASE,
                        .dc_base = MALIDP550_DC_BASE,
                        .out_depth_base = MALIDP550_DE_OUTPUT_DEPTH,
index ea2dbae..c2df6b6 100644 (file)
@@ -67,6 +67,8 @@ struct malidp_layer {
 struct malidp_hw_regmap {
        /* address offset of the DE register bank */
        /* is always 0x0000 */
+       /* address offset of the DE coefficients registers */
+       const u16 coeffs_base;
        /* address offset of the SE registers bank */
        const u16 se_base;
        /* address offset of the DC registers bank */
@@ -257,4 +259,8 @@ static inline bool malidp_hw_pitch_valid(struct malidp_hw_device *hwdev,
 #define MALIDP_BGND_COLOR_G            0x000
 #define MALIDP_BGND_COLOR_B            0x000
 
+#define MALIDP_COEFFTAB_NUM_COEFFS     64
+
+#define MALIDP_GAMMA_LUT_SIZE          4096
+
 #endif  /* __MALIDP_HW_H__ */
index b816067..28252c5 100644 (file)
@@ -63,6 +63,7 @@
 
 /* bit masks that are common between products */
 #define   MALIDP_CFG_VALID             (1 << 0)
+#define   MALIDP_DISP_FUNC_GAMMA       (1 << 0)
 #define   MALIDP_DISP_FUNC_ILACED      (1 << 8)
 
 /* register offsets for IRQ management */
 
 #define MALIDP_PRODUCT_ID(__core_id) ((u32)(__core_id) >> 16)
 
+/* register offsets relative to MALIDP5x0_COEFFS_BASE */
+#define MALIDP_COLOR_ADJ_COEF          0x00000
+#define MALIDP_COEF_TABLE_ADDR         0x00030
+#define MALIDP_COEF_TABLE_DATA         0x00034
+
 /* register offsets and bits specific to DP500 */
 #define MALIDP500_ADDR_SPACE_SIZE      0x01000
 #define MALIDP500_DC_BASE              0x00000
 #define MALIDP500_COLOR_ADJ_COEF       0x00078
 #define MALIDP500_COEF_TABLE_ADDR      0x000a8
 #define MALIDP500_COEF_TABLE_DATA      0x000ac
+
+/*
+ * The YUV2RGB coefficients on the DP500 are not in the video layer's register
+ * block. They belong in a separate block above the layer's registers, hence
+ * the negative offset.
+ */
+#define MALIDP500_LV_YUV2RGB           ((s16)(-0xB8))
+/*
+ * To match DP550/650, the start of the coeffs registers is
+ * at COLORADJ_COEFF0 instead of at YUV_RGB_COEF1.
+ */
+#define MALIDP500_COEFFS_BASE          0x00078
 #define MALIDP500_DE_LV_BASE           0x00100
 #define MALIDP500_DE_LV_PTR_BASE       0x00124
 #define MALIDP500_DE_LG1_BASE          0x00200
 #define MALIDP550_DE_DISP_SIDEBAND     0x00040
 #define MALIDP550_DE_BGND_COLOR                0x00044
 #define MALIDP550_DE_OUTPUT_DEPTH      0x0004c
-#define MALIDP550_DE_COLOR_COEF                0x00050
-#define MALIDP550_DE_COEF_TABLE_ADDR   0x00080
-#define MALIDP550_DE_COEF_TABLE_DATA   0x00084
+#define MALIDP550_COEFFS_BASE          0x00050
 #define MALIDP550_DE_LV1_BASE          0x00100
 #define MALIDP550_DE_LV1_PTR_BASE      0x00124
 #define MALIDP550_DE_LV2_BASE          0x00200