Skip to content

Adding gamma control to 2712D0 #6839

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: rpi-6.12.y
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions drivers/gpu/drm/vc4/vc4_crtc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1447,6 +1447,27 @@ int __vc4_crtc_init(struct drm_device *drm,
* implemented as private driver state in vc4_kms, not here.
*/
drm_crtc_enable_color_mgmt(crtc, 0, true, crtc->gamma_size);
} else if (vc4->gen == VC4_GEN_6_D) {
/* This is a lie for hvs5 which uses a 16 point PWL, but it
* allows for something smarter than just 16 linearly spaced
* segments. Conversion is done in vc5_hvs_update_gamma_lut.
*/
drm_mode_crtc_set_gamma_size(crtc, 256);
drm_crtc_enable_color_mgmt(crtc, 0, true, crtc->gamma_size);

/* Initialize the gamma PWL entries. Assume 12-bit pipeline,
* evenly spread over full range.
*/
for (i = 0; i < SCALER6D_DSPGAMMA_NUM_POINTS; i++) {
vc4_crtc->pwl_r[i] =
VC6D_HVS_SET_GAMMA_ENTRY(i << 8, i << 12, 1 << 8);
vc4_crtc->pwl_g[i] =
VC6D_HVS_SET_GAMMA_ENTRY(i << 8, i << 12, 1 << 8);
vc4_crtc->pwl_b[i] =
VC6D_HVS_SET_GAMMA_ENTRY(i << 8, i << 12, 1 << 8);
vc4_crtc->pwl_a[i] =
VC6D_HVS_SET_GAMMA_ENTRY(i << 8, i << 12, 1 << 8);
}
}

for (i = 0; i < crtc->gamma_size; i++) {
Expand Down
28 changes: 25 additions & 3 deletions drivers/gpu/drm/vc4/vc4_drv.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <kunit/test-bug.h>

#include "uapi/drm/vc4_drm.h"
#include "vc4_regs.h"

struct drm_device;
struct drm_gem_object;
Expand Down Expand Up @@ -610,6 +611,17 @@
extern const struct vc4_pv_data bcm2712_pv0_data;
extern const struct vc4_pv_data bcm2712_pv1_data;

struct vc6_gamma_entry {
u32 x_c_terms;
u32 grad_term;
};

#define VC6D_HVS_SET_GAMMA_ENTRY(x, c, g) (struct vc6_gamma_entry){ \

Check failure on line 619 in drivers/gpu/drm/vc4/vc4_drv.h

View workflow job for this annotation

GitHub Actions / checkpatch review

ERROR: Macros with complex values should be enclosed in parentheses
.x_c_terms = VC4_SET_FIELD((x), SCALER6D_DSPGAMMA_OFF_X) | \
VC4_SET_FIELD((c), SCALER6D_DSPGAMMA_OFF_C), \
.grad_term = (g) \
}

struct vc4_crtc {
struct drm_crtc base;
struct platform_device *pdev;
Expand All @@ -619,9 +631,19 @@
/* Timestamp at start of vblank irq - unaffected by lock delays. */
ktime_t t_vblank;

u8 lut_r[256];
u8 lut_g[256];
u8 lut_b[256];
union {
struct { /* VC4 gamma LUT */
u8 lut_r[256];
u8 lut_g[256];
u8 lut_b[256];
};
struct { /* VC6_D gamma PWL entries */
struct vc6_gamma_entry pwl_r[SCALER6D_DSPGAMMA_NUM_POINTS];
struct vc6_gamma_entry pwl_g[SCALER6D_DSPGAMMA_NUM_POINTS];
struct vc6_gamma_entry pwl_b[SCALER6D_DSPGAMMA_NUM_POINTS];
struct vc6_gamma_entry pwl_a[SCALER6D_DSPGAMMA_NUM_POINTS];
};
};

struct drm_pending_vblank_event *event;

Expand Down
202 changes: 189 additions & 13 deletions drivers/gpu/drm/vc4/vc4_hvs.c
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@
VC4_REG32(SCALER6D_HISTBIN6),
VC4_REG32(SCALER6D_HISTBIN7),
VC4_REG32(SCALER6D_HVS_ID),
VC4_REG32(SCALER6D_DITHERGAMMA),
};

void vc4_hvs_dump_state(struct vc4_hvs *hvs)
Expand Down Expand Up @@ -400,6 +401,85 @@
return 0;
}

static int vc6_hvs_debugfs_gamma(struct seq_file *m, void *data)
{
struct drm_debugfs_entry *entry = m->private;
struct drm_device *dev = entry->dev;
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct vc4_hvs *hvs = vc4->hvs;
struct drm_printer p = drm_seq_file_printer(m);
unsigned int i, chan;
u32 dispstat, dither_gamma;

for (chan = 0; chan < SCALER_CHANNELS_COUNT; chan++) {
u32 x_c, grad;
u32 offset = SCALER6D_DSPGAMMA_START +
chan * SCALER6D_DSPGAMMA_CHAN_OFFSET;

dispstat = VC4_GET_FIELD(HVS_READ(SCALER6_DISPX_STATUS(chan)),
SCALER6_DISPX_STATUS_MODE);
if (dispstat == SCALER6_DISPX_STATUS_MODE_DISABLED ||
dispstat == SCALER6_DISPX_STATUS_MODE_EOF) {
drm_printf(&p, "HVS channel %u: disabled\n", chan);
continue;
}

dither_gamma = HVS_READ(SCALER6D_DITHERGAMMA);
if (!(dither_gamma & SCALER6D_DITHERGAMMA_GAMMA(chan))) {
drm_printf(&p, "HVS channel %u: Gamma disabled\n", chan);
continue;
}

drm_printf(&p, "HVS channel %u:\n", chan);
drm_printf(&p, " blue:\n");
for (i = 0; i < SCALER6D_DSPGAMMA_NUM_POINTS; i++, offset += 8) {
x_c = HVS_READ(offset);
grad = HVS_READ(offset + 4);
drm_printf(&p, " %08x %08x - x %u, c %u, grad %u\n",
x_c, grad,
VC4_GET_FIELD(x_c, SCALER6D_DSPGAMMA_OFF_X),
VC4_GET_FIELD(x_c, SCALER6D_DSPGAMMA_OFF_C),
grad);
}
drm_printf(&p, " green:\n");
for (i = 0; i < SCALER6D_DSPGAMMA_NUM_POINTS; i++, offset += 8) {
x_c = HVS_READ(offset);
grad = HVS_READ(offset + 4);
drm_printf(&p, " %08x %08x - x %u, c %u, grad %u\n",
x_c, grad,
VC4_GET_FIELD(x_c, SCALER6D_DSPGAMMA_OFF_X),
VC4_GET_FIELD(x_c, SCALER6D_DSPGAMMA_OFF_C),
grad);
}
drm_printf(&p, " red:\n");
for (i = 0; i < SCALER6D_DSPGAMMA_NUM_POINTS; i++, offset += 8) {
x_c = HVS_READ(offset);
grad = HVS_READ(offset + 4);
drm_printf(&p, " %08x %08x - x %u, c %u, grad %u\n",
x_c, grad,
VC4_GET_FIELD(x_c, SCALER6D_DSPGAMMA_OFF_X),
VC4_GET_FIELD(x_c, SCALER6D_DSPGAMMA_OFF_C),
grad);
}

/* Alpha only valid on channel 2 */
if (chan != 2)
continue;

drm_printf(&p, " alpha:\n");
for (i = 0; i < SCALER6D_DSPGAMMA_NUM_POINTS; i++, offset += 8) {
x_c = HVS_READ(offset);
grad = HVS_READ(offset + 4);
drm_printf(&p, " %08x %08x - x %u, c %u, grad %u\n",
x_c, grad,
VC4_GET_FIELD(x_c, SCALER6D_DSPGAMMA_OFF_X),
VC4_GET_FIELD(x_c, SCALER6D_DSPGAMMA_OFF_C),
grad);
}
}
return 0;
}

/* The filter kernel is composed of dwords each containing 3 9-bit
* signed integers packed next to each other.
*/
Expand Down Expand Up @@ -486,13 +566,13 @@
return;

if (hvs->vc4->gen != VC4_GEN_4)
goto exit;

Check failure on line 569 in drivers/gpu/drm/vc4/vc4_hvs.c

View workflow job for this annotation

GitHub Actions / checkpatch review

ERROR: Macros with multiple statements should be enclosed in a do - while loop

Check failure on line 569 in drivers/gpu/drm/vc4/vc4_hvs.c

View workflow job for this annotation

GitHub Actions / checkpatch review

WARNING: macros should not use a trailing semicolon

/* The LUT memory is laid out with each HVS channel in order,
* each of which takes 256 writes for R, 256 for G, then 256
* for B.
*/
HVS_WRITE(SCALER_GAMADDR,

Check failure on line 575 in drivers/gpu/drm/vc4/vc4_hvs.c

View workflow job for this annotation

GitHub Actions / checkpatch review

WARNING: Avoid unnecessary line continuations
SCALER_GAMADDR_AUTOINC |
(vc4_state->assigned_channel * 3 * crtc->gamma_size));

Expand All @@ -500,7 +580,7 @@
HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_r[i]);
for (i = 0; i < crtc->gamma_size; i++)
HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_g[i]);
for (i = 0; i < crtc->gamma_size; i++)

Check failure on line 583 in drivers/gpu/drm/vc4/vc4_hvs.c

View workflow job for this annotation

GitHub Actions / checkpatch review

WARNING: Avoid unnecessary line continuations
HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_b[i]);

exit:
Expand All @@ -524,6 +604,82 @@
vc4_hvs_lut_load(hvs, vc4_crtc);
}

static void vc6_hvs_write_gamma_entry(struct vc4_dev *vc4,
u32 offset,
struct vc6_gamma_entry *gamma)
{
struct vc4_hvs *hvs = vc4->hvs;

HVS_WRITE(offset, gamma->x_c_terms);
HVS_WRITE(offset + 4, gamma->grad_term);
}

static void vc6_hvs_lut_load(struct drm_crtc *crtc)
{
struct drm_device *dev = crtc->dev;
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
u32 i;
u32 offset = SCALER6D_DSPGAMMA_START +
vc4_state->assigned_channel * SCALER6D_DSPGAMMA_CHAN_OFFSET;

for (i = 0; i < SCALER6D_DSPGAMMA_NUM_POINTS; i++, offset += 8)
vc6_hvs_write_gamma_entry(vc4, offset, &vc4_crtc->pwl_b[i]);
for (i = 0; i < SCALER6D_DSPGAMMA_NUM_POINTS; i++, offset += 8)
vc6_hvs_write_gamma_entry(vc4, offset, &vc4_crtc->pwl_g[i]);
for (i = 0; i < SCALER6D_DSPGAMMA_NUM_POINTS; i++, offset += 8)
vc6_hvs_write_gamma_entry(vc4, offset, &vc4_crtc->pwl_r[i]);

if (vc4_state->assigned_channel == 2) {
/* Alpha only valid on channel 2 */
for (i = 0; i < SCALER6D_DSPGAMMA_NUM_POINTS; i++, offset += 8)
vc6_hvs_write_gamma_entry(vc4, offset, &vc4_crtc->pwl_a[i]);
}
}

static void vc6_hvs_update_gamma_lut(struct drm_crtc *crtc)
{
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
struct drm_color_lut *lut = crtc->state->gamma_lut->data;
unsigned int step, i;
u32 start, end;

#define VC6D_HVS_UPDATE_GAMMA_ENTRY_FROM_LUT(pwl, chan) \
start = drm_color_lut_extract(lut[i * step].chan, 12); \
end = drm_color_lut_extract(lut[(i + 1) * step - 1].chan, 12); \
\
/* Negative gradients not permitted by the hardware, so \
* flatten such points out. \
*/ \
if (end < start) \
end = start; \
\
/* Assume 12bit pipeline. \
* X evenly spread over full range (12 bit). \
* C as U12.4 format. \
* Gradient as U4.8 format. \
*/ \
vc4_crtc->pwl[i] = \
VC6D_HVS_SET_GAMMA_ENTRY(i << 8, start << 4, \
((end - start) << 4) / (step - 1))

/* HVS6 has a 16 point piecewise linear function for each colour
* channel (including alpha on channel 2) on each display channel.
*
* Currently take a crude subsample of the gamma LUT, but this could
* be improved to implement curve fitting.
*/
step = crtc->gamma_size / SCALER6D_DSPGAMMA_NUM_POINTS;
for (i = 0; i < SCALER6D_DSPGAMMA_NUM_POINTS; i++) {
VC6D_HVS_UPDATE_GAMMA_ENTRY_FROM_LUT(pwl_r, red);
VC6D_HVS_UPDATE_GAMMA_ENTRY_FROM_LUT(pwl_g, green);
VC6D_HVS_UPDATE_GAMMA_ENTRY_FROM_LUT(pwl_b, blue);
}

vc6_hvs_lut_load(crtc);
}

static void vc4_hvs_irq_enable_eof(struct vc4_hvs *hvs,
unsigned int channel)
{
Expand Down Expand Up @@ -1032,6 +1188,9 @@
VC4_SET_FIELD(mode->vdisplay - 1,
SCALER6_DISPX_CTRL0_LINES));

if (vc4->gen > VC4_GEN_6_D)
vc6_hvs_lut_load(crtc);

drm_dev_exit(idx);

return 0;
Expand Down Expand Up @@ -1271,7 +1430,7 @@
vc4_crtc_send_vblank(crtc);
return;
}

Check failure on line 1433 in drivers/gpu/drm/vc4/vc4_hvs.c

View workflow job for this annotation

GitHub Actions / checkpatch review

WARNING: Missing a blank line after declarations
if (vc4_state->assigned_channel == VC4_HVS_CHANNEL_DISABLED)
goto exit;

Expand Down Expand Up @@ -1346,21 +1505,36 @@
}

if (crtc->state->color_mgmt_changed) {
u32 dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(channel));

WARN_ON_ONCE(vc4->gen > VC4_GEN_5);

if (crtc->state->gamma_lut) {
vc4_hvs_update_gamma_lut(hvs, vc4_crtc);
dispbkgndx |= SCALER_DISPBKGND_GAMMA;
WARN_ON_ONCE(vc4->gen > VC4_GEN_5 && vc4->gen < VC4_GEN_6_C);

if (vc4->gen == VC4_GEN_4) {
u32 dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(channel));
if (crtc->state->gamma_lut) {
vc4_hvs_update_gamma_lut(hvs, vc4_crtc);
dispbkgndx |= SCALER_DISPBKGND_GAMMA;
} else {
/* Unsetting DISPBKGND_GAMMA skips the gamma lut step
* in hardware, which is the same as a linear lut that
* DRM expects us to use in absence of a user lut.
*/
dispbkgndx &= ~SCALER_DISPBKGND_GAMMA;
}
HVS_WRITE(SCALER_DISPBKGNDX(channel), dispbkgndx);
} else {
/* Unsetting DISPBKGND_GAMMA skips the gamma lut step
* in hardware, which is the same as a linear lut that
* DRM expects us to use in absence of a user lut.
*/
dispbkgndx &= ~SCALER_DISPBKGND_GAMMA;
u32 dither_gamma = HVS_READ(SCALER6D_DITHERGAMMA);

if (crtc->state->gamma_lut) {
vc6_hvs_update_gamma_lut(crtc);
dither_gamma |= SCALER6D_DITHERGAMMA_GAMMA(channel);
} else {
/* Unsetting DISPBKGND_GAMMA skips the gamma lut step
* in hardware, which is the same as a linear lut that
* DRM expects us to use in absence of a user lut.
*/
dither_gamma &= ~SCALER6D_DITHERGAMMA_GAMMA(channel);
}
HVS_WRITE(SCALER6D_DITHERGAMMA, dither_gamma);
}
HVS_WRITE(SCALER_DISPBKGNDX(channel), dispbkgndx);
}

if (debug_dump_regs) {
Expand Down Expand Up @@ -1528,6 +1702,8 @@
if (vc4->gen >= VC4_GEN_6_C) {
drm_debugfs_add_file(drm, "hvs_dlists", vc6_hvs_debugfs_dlist, NULL);
drm_debugfs_add_file(drm, "hvs_upm", vc6_hvs_debugfs_upm_allocs, NULL);
if (vc4->gen >= VC4_GEN_6_D)
drm_debugfs_add_file(drm, "gamma", vc6_hvs_debugfs_gamma, NULL);
} else {
drm_debugfs_add_file(drm, "hvs_dlists", vc4_hvs_debugfs_dlist, NULL);
}
Expand Down
26 changes: 26 additions & 0 deletions drivers/gpu/drm/vc4/vc4_regs.h
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,32 @@
#define SCALER6D_QOS2 0x000001a4
#define SCALER6D_PROF2 0x000001a8

#define SCALER6D_DITHERGAMMA 0x00000500
# define SCALER6D_DITHERGAMMA_GAMMA(chan) BIT(7 + chan * 8)
# define SCALER6D_DITHERGAMMA_CSC(chan) BIT(6 + chan * 8)

/* Gamma PWL for each channel. 16 points for each of 4 colour channels (alpha
* only on channel 2). 8 bytes per entry, offsets first, then gradient:
* Y = GRAD * X + C
*
* Values for X and C are left justified, and vary depending on the width of
* the HVS channel:
* 8-bit pipeline: X uses [31:24], C is U8.8 format, and GRAD is U4.8.
* 12-bit pipeline: X uses [31:20], C is U12.4 format, and GRAD is U4.8.
*
* The 3 HVS channels start at 0x400 offsets (ie chan 1 starts at 0x2400, and
* chan 2 at 0x2800).
*/
#define SCALER6D_DSPGAMMA_NUM_POINTS 16
#define SCALER6D_DSPGAMMA_START 0x00008800
#define SCALER6D_DSPGAMMA_CHAN_OFFSET 0x200
# define SCALER6D_DSPGAMMA_OFF_X_MASK VC4_MASK(31, 20)
# define SCALER6D_DSPGAMMA_OFF_X_SHIFT 20
# define SCALER6D_DSPGAMMA_OFF_C_MASK VC4_MASK(15, 0)
# define SCALER6D_DSPGAMMA_OFF_C_SHIFT 0
# define SCALER6D_DSPGAMMA_GRAD_MASK VC4_MASK(11, 0)
# define SCALER6D_DSPGAMMA_GRAD_SHIFT 0

#define SCALER6(x) ((hvs->vc4->gen == VC4_GEN_6_C) ? SCALER6_ ## x : SCALER6D_ ## x)

# define VC4_HDMI_SW_RESET_FORMAT_DETECT BIT(1)
Expand Down
Loading