Fixes a purple bar on the left side of the screen with my Dell 2408WFP. It will also be required for supporting the double-clocked video modes.
Signed-off-by: Eric Anholt eric@anholt.net --- drivers/gpu/drm/vc4/vc4_hdmi.c | 136 +++++++++++++++++++++++++++++++++++++++-- drivers/gpu/drm/vc4/vc4_regs.h | 5 ++ 2 files changed, 136 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index d94108ca961d..d6b54b905bee 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -62,6 +62,8 @@ struct vc4_hdmi { struct vc4_hdmi_encoder { struct vc4_encoder base; bool hdmi_monitor; + bool limited_rgb_range; + bool rgb_range_selectable; };
static inline struct vc4_hdmi_encoder * @@ -205,6 +207,12 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector) return -ENODEV;
vc4_encoder->hdmi_monitor = drm_detect_hdmi_monitor(edid); + + if (edid && edid->input & DRM_EDID_INPUT_DIGITAL) { + vc4_encoder->rgb_range_selectable = + drm_rgb_quant_range_selectable(edid); + } + drm_mode_connector_update_edid_property(connector, edid); ret = drm_add_edid_modes(connector, edid);
@@ -272,6 +280,117 @@ static const struct drm_encoder_funcs vc4_hdmi_encoder_funcs = { .destroy = vc4_hdmi_encoder_destroy, };
+static int vc4_hdmi_stop_packet(struct drm_encoder *encoder, + enum hdmi_infoframe_type type) +{ + struct drm_device *dev = encoder->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); + u32 packet_id = type - 0x80; + + HDMI_WRITE(VC4_HDMI_RAM_PACKET_CONFIG, + HDMI_READ(VC4_HDMI_RAM_PACKET_CONFIG) & ~BIT(packet_id)); + + return wait_for(!(HDMI_READ(VC4_HDMI_RAM_PACKET_STATUS) & + BIT(packet_id)), 100); +} + +static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, + union hdmi_infoframe *frame) +{ + struct drm_device *dev = encoder->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); + u32 packet_id = frame->any.type - 0x80; + u32 packet_reg = VC4_HDMI_GCP_0 + VC4_HDMI_PACKET_STRIDE * packet_id; + uint8_t buffer[VC4_HDMI_PACKET_STRIDE]; + ssize_t len, i; + int ret; + + WARN_ONCE(!(HDMI_READ(VC4_HDMI_RAM_PACKET_CONFIG) & + VC4_HDMI_RAM_PACKET_ENABLE), + "Packet RAM has to be on to store the packet."); + + len = hdmi_infoframe_pack(frame, buffer, sizeof(buffer)); + if (len < 0) + return; + + ret = vc4_hdmi_stop_packet(encoder, frame->any.type); + if (ret) { + DRM_ERROR("Failed to wait for infoframe to go idle: %d\n", ret); + return; + } + + for (i = 0; i < len; i += 7) { + HDMI_WRITE(packet_reg, + buffer[i + 0] << 0 | + buffer[i + 1] << 8 | + buffer[i + 2] << 16); + packet_reg += 4; + + HDMI_WRITE(packet_reg, + buffer[i + 3] << 0 | + buffer[i + 4] << 8 | + buffer[i + 5] << 16 | + buffer[i + 6] << 24); + packet_reg += 4; + } + + HDMI_WRITE(VC4_HDMI_RAM_PACKET_CONFIG, + HDMI_READ(VC4_HDMI_RAM_PACKET_CONFIG) | BIT(packet_id)); + ret = wait_for((HDMI_READ(VC4_HDMI_RAM_PACKET_STATUS) & + BIT(packet_id)), 100); + if (ret) + DRM_ERROR("Failed to wait for infoframe to start: %d\n", ret); +} + +static void vc4_hdmi_set_avi_infoframe(struct drm_encoder *encoder) +{ + struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder); + struct drm_crtc *crtc = encoder->crtc; + const struct drm_display_mode *mode = &crtc->state->adjusted_mode; + union hdmi_infoframe frame; + int ret; + + ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, mode); + if (ret < 0) { + DRM_ERROR("couldn't fill AVI infoframe\n"); + return; + } + + if (vc4_encoder->rgb_range_selectable) { + if (vc4_encoder->limited_rgb_range) { + frame.avi.quantization_range = + HDMI_QUANTIZATION_RANGE_LIMITED; + } else { + frame.avi.quantization_range = + HDMI_QUANTIZATION_RANGE_FULL; + } + } + + vc4_hdmi_write_infoframe(encoder, &frame); +} + +static void vc4_hdmi_set_spd_infoframe(struct drm_encoder *encoder) +{ + union hdmi_infoframe frame; + int ret; + + ret = hdmi_spd_infoframe_init(&frame.spd, "Broadcom", "Videocore"); + if (ret < 0) { + DRM_ERROR("couldn't fill SPD infoframe\n"); + return; + } + + frame.spd.sdi = HDMI_SPD_SDI_PC; + + vc4_hdmi_write_infoframe(encoder, &frame); +} + +static void vc4_hdmi_set_infoframes(struct drm_encoder *encoder) +{ + vc4_hdmi_set_avi_infoframe(encoder); + vc4_hdmi_set_spd_infoframe(encoder); +} + static void vc4_hdmi_encoder_mode_set(struct drm_encoder *encoder, struct drm_display_mode *unadjusted_mode, struct drm_display_mode *mode) @@ -340,8 +459,9 @@ static void vc4_hdmi_encoder_mode_set(struct drm_encoder *encoder,
if (vc4_encoder->hdmi_monitor && drm_match_cea_mode(mode) > 1) { /* CEA VICs other than #1 requre limited range RGB - * output. Apply a colorspace conversion to squash - * 0-255 down to 16-235. The matrix here is: + * output unless overridden by an AVI infoframe. + * Apply a colorspace conversion to squash 0-255 down + * to 16-235. The matrix here is: * * [ 0 0 0.8594 16] * [ 0 0.8594 0 16] @@ -359,6 +479,9 @@ static void vc4_hdmi_encoder_mode_set(struct drm_encoder *encoder, HD_WRITE(VC4_HD_CSC_24_23, (0x100 << 16) | 0x000); HD_WRITE(VC4_HD_CSC_32_31, (0x000 << 16) | 0x6e0); HD_WRITE(VC4_HD_CSC_34_33, (0x100 << 16) | 0x000); + vc4_encoder->limited_rgb_range = true; + } else { + vc4_encoder->limited_rgb_range = false; }
/* The RGB order applies even when CSC is disabled. */ @@ -377,6 +500,8 @@ static void vc4_hdmi_encoder_disable(struct drm_encoder *encoder) struct drm_device *dev = encoder->dev; struct vc4_dev *vc4 = to_vc4_dev(dev);
+ HDMI_WRITE(VC4_HDMI_RAM_PACKET_CONFIG, 0); + HDMI_WRITE(VC4_HDMI_TX_PHY_RESET_CTL, 0xf << 16); HD_WRITE(VC4_HD_VID_CTL, HD_READ(VC4_HD_VID_CTL) & ~VC4_HD_VID_CTL_ENABLE); @@ -429,9 +554,10 @@ static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder) HDMI_READ(VC4_HDMI_SCHEDULER_CONTROL) | VC4_HDMI_SCHEDULER_CONTROL_VERT_ALWAYS_KEEPOUT);
- /* XXX: Set HDMI_RAM_PACKET_CONFIG (1 << 16) and set - * up the infoframe. - */ + HDMI_WRITE(VC4_HDMI_RAM_PACKET_CONFIG, + VC4_HDMI_RAM_PACKET_ENABLE); + + vc4_hdmi_set_infoframes(encoder);
drift = HDMI_READ(VC4_HDMI_FIFO_CTL); drift &= VC4_HDMI_FIFO_VALID_WRITE_MASK; diff --git a/drivers/gpu/drm/vc4/vc4_regs.h b/drivers/gpu/drm/vc4/vc4_regs.h index c5a423ead86f..0b868aafa8db 100644 --- a/drivers/gpu/drm/vc4/vc4_regs.h +++ b/drivers/gpu/drm/vc4/vc4_regs.h @@ -441,6 +441,8 @@ #define VC4_HDMI_RAM_PACKET_CONFIG 0x0a0 # define VC4_HDMI_RAM_PACKET_ENABLE BIT(16)
+#define VC4_HDMI_RAM_PACKET_STATUS 0x0a4 + #define VC4_HDMI_HORZA 0x0c4 # define VC4_HDMI_HORZA_VPOS BIT(14) # define VC4_HDMI_HORZA_HPOS BIT(13) @@ -502,6 +504,9 @@
#define VC4_HDMI_TX_PHY_RESET_CTL 0x2c0
+#define VC4_HDMI_GCP_0 0x400 +#define VC4_HDMI_PACKET_STRIDE 0x24 + #define VC4_HD_M_CTL 0x00c # define VC4_HD_M_REGISTER_FILE_STANDBY (3 << 6) # define VC4_HD_M_RAM_STANDBY (3 << 4)
Now that we have infoframes to report the pixel repeat flag, we can start using it. Fixes locking the 720x480i and 720x576i modes on my Dell 2408WFP. Like the 1920x1080i case, they don't fit properly on the screen, though.
Signed-off-by: Eric Anholt eric@anholt.net --- drivers/gpu/drm/vc4/vc4_crtc.c | 17 +++++++++++------ drivers/gpu/drm/vc4/vc4_hdmi.c | 16 +++++++++++----- drivers/gpu/drm/vc4/vc4_regs.h | 2 ++ 3 files changed, 24 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 83cafea03eff..7f08d681a74b 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -378,6 +378,7 @@ static void vc4_crtc_mode_set_nofb(struct drm_crtc *crtc) struct drm_crtc_state *state = crtc->state; struct drm_display_mode *mode = &state->adjusted_mode; bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE; + u32 pixel_rep = (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1; u32 format = PV_CONTROL_FORMAT_24; bool debug_dump_regs = false; int clock_select = vc4_get_clock_select(crtc); @@ -393,14 +394,17 @@ static void vc4_crtc_mode_set_nofb(struct drm_crtc *crtc) CRTC_WRITE(PV_CONTROL, 0);
CRTC_WRITE(PV_HORZA, - VC4_SET_FIELD(mode->htotal - mode->hsync_end, + VC4_SET_FIELD((mode->htotal - + mode->hsync_end) * pixel_rep, PV_HORZA_HBP) | - VC4_SET_FIELD(mode->hsync_end - mode->hsync_start, + VC4_SET_FIELD((mode->hsync_end - + mode->hsync_start) * pixel_rep, PV_HORZA_HSYNC)); CRTC_WRITE(PV_HORZB, - VC4_SET_FIELD(mode->hsync_start - mode->hdisplay, + VC4_SET_FIELD((mode->hsync_start - + mode->hdisplay) * pixel_rep, PV_HORZB_HFP) | - VC4_SET_FIELD(mode->hdisplay, PV_HORZB_HACTIVE)); + VC4_SET_FIELD(mode->hdisplay * pixel_rep, PV_HORZB_HACTIVE));
CRTC_WRITE(PV_VERTA, VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end, @@ -434,20 +438,21 @@ static void vc4_crtc_mode_set_nofb(struct drm_crtc *crtc) CRTC_WRITE(PV_V_CONTROL, PV_VCONTROL_CONTINUOUS | PV_VCONTROL_INTERLACE | - VC4_SET_FIELD(mode->htotal / 2, + VC4_SET_FIELD(mode->htotal * pixel_rep / 2, PV_VCONTROL_ODD_DELAY)); CRTC_WRITE(PV_VSYNCD_EVEN, 0); } else { CRTC_WRITE(PV_V_CONTROL, PV_VCONTROL_CONTINUOUS); }
- CRTC_WRITE(PV_HACT_ACT, mode->hdisplay); + CRTC_WRITE(PV_HACT_ACT, mode->hdisplay * pixel_rep);
CRTC_WRITE(PV_CONTROL, VC4_SET_FIELD(format, PV_CONTROL_FORMAT) | VC4_SET_FIELD(vc4_get_fifo_full_level(format), PV_CONTROL_FIFO_LEVEL) | + VC4_SET_FIELD(pixel_rep - 1, PV_CONTROL_PIXEL_REP) | PV_CONTROL_CLR_AT_START | PV_CONTROL_TRIGGER_UNDERFLOW | PV_CONTROL_WAIT_HSTART | diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index d6b54b905bee..c4cb2e26de32 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -402,6 +402,7 @@ static void vc4_hdmi_encoder_mode_set(struct drm_encoder *encoder, bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC; bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE; + u32 pixel_rep = (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1; u32 verta = (VC4_SET_FIELD(mode->crtc_vsync_end - mode->crtc_vsync_start, VC4_HDMI_VERTA_VSP) | VC4_SET_FIELD(mode->crtc_vsync_start - mode->crtc_vdisplay, @@ -424,7 +425,8 @@ static void vc4_hdmi_encoder_mode_set(struct drm_encoder *encoder,
HD_WRITE(VC4_HD_VID_CTL, 0);
- clk_set_rate(vc4->hdmi->pixel_clock, mode->clock * 1000); + clk_set_rate(vc4->hdmi->pixel_clock, mode->clock * 1000 * + ((mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1));
HDMI_WRITE(VC4_HDMI_SCHEDULER_CONTROL, HDMI_READ(VC4_HDMI_SCHEDULER_CONTROL) | @@ -434,14 +436,18 @@ static void vc4_hdmi_encoder_mode_set(struct drm_encoder *encoder, HDMI_WRITE(VC4_HDMI_HORZA, (vsync_pos ? VC4_HDMI_HORZA_VPOS : 0) | (hsync_pos ? VC4_HDMI_HORZA_HPOS : 0) | - VC4_SET_FIELD(mode->hdisplay, VC4_HDMI_HORZA_HAP)); + VC4_SET_FIELD(mode->hdisplay * pixel_rep, + VC4_HDMI_HORZA_HAP));
HDMI_WRITE(VC4_HDMI_HORZB, - VC4_SET_FIELD(mode->htotal - mode->hsync_end, + VC4_SET_FIELD((mode->htotal - + mode->hsync_end) * pixel_rep, VC4_HDMI_HORZB_HBP) | - VC4_SET_FIELD(mode->hsync_end - mode->hsync_start, + VC4_SET_FIELD((mode->hsync_end - + mode->hsync_start) * pixel_rep, VC4_HDMI_HORZB_HSP) | - VC4_SET_FIELD(mode->hsync_start - mode->hdisplay, + VC4_SET_FIELD((mode->hsync_start - + mode->hdisplay) * pixel_rep, VC4_HDMI_HORZB_HFP));
HDMI_WRITE(VC4_HDMI_VERTA0, verta); diff --git a/drivers/gpu/drm/vc4/vc4_regs.h b/drivers/gpu/drm/vc4/vc4_regs.h index 0b868aafa8db..1aa44c2db556 100644 --- a/drivers/gpu/drm/vc4/vc4_regs.h +++ b/drivers/gpu/drm/vc4/vc4_regs.h @@ -175,6 +175,8 @@ # define PV_CONTROL_CLR_AT_START BIT(14) # define PV_CONTROL_TRIGGER_UNDERFLOW BIT(13) # define PV_CONTROL_WAIT_HSTART BIT(12) +# define PV_CONTROL_PIXEL_REP_MASK VC4_MASK(5, 4) +# define PV_CONTROL_PIXEL_REP_SHIFT 4 # define PV_CONTROL_CLK_SELECT_DSI_VEC 0 # define PV_CONTROL_CLK_SELECT_DPI_SMI_HDMI 1 # define PV_CONTROL_CLK_SELECT_MASK VC4_MASK(3, 2)
dri-devel@lists.freedesktop.org