From 9536044338d9c341e805e288a58090c49a793638 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Tue, 9 Apr 2019 18:23:41 +0100 Subject: [PATCH] drm: vc4: FKMS reads the EDID from fw, and supports mode setting This extends FKMS to read the EDID from the display, and support requesting a particular mode via KMS. Signed-off-by: Dave Stevenson --- drivers/gpu/drm/vc4/vc4_firmware_kms.c | 334 ++++++++++++++++++--- include/soc/bcm2835/raspberrypi-firmware.h | 2 + 2 files changed, 302 insertions(+), 34 deletions(-) --- a/drivers/gpu/drm/vc4/vc4_firmware_kms.c +++ b/drivers/gpu/drm/vc4/vc4_firmware_kms.c @@ -88,11 +88,60 @@ struct mailbox_blank_display { u32 blank; }; -struct mailbox_get_width_height { +struct mailbox_get_edid { struct rpi_firmware_property_tag_header tag1; - u32 display; - struct rpi_firmware_property_tag_header tag2; - u32 wh[2]; + u32 block; + u32 display_number; + u8 edid[128]; +}; + +struct set_timings { + u8 display; + u8 padding; + u16 video_id_code; + + u32 clock; /* in kHz */ + + u16 hdisplay; + u16 hsync_start; + + u16 hsync_end; + u16 htotal; + + u16 hskew; + u16 vdisplay; + + u16 vsync_start; + u16 vsync_end; + + u16 vtotal; + u16 vscan; + + u16 vrefresh; + u16 padding2; + + u32 flags; +#define TIMINGS_FLAGS_H_SYNC_POS BIT(0) +#define TIMINGS_FLAGS_H_SYNC_NEG 0 +#define TIMINGS_FLAGS_V_SYNC_POS BIT(1) +#define TIMINGS_FLAGS_V_SYNC_NEG 0 + +#define TIMINGS_FLAGS_ASPECT_MASK GENMASK(7, 4) +#define TIMINGS_FLAGS_ASPECT_NONE (0 << 4) +#define TIMINGS_FLAGS_ASPECT_4_3 (1 << 4) +#define TIMINGS_FLAGS_ASPECT_16_9 (2 << 4) +#define TIMINGS_FLAGS_ASPECT_64_27 (3 << 4) +#define TIMINGS_FLAGS_ASPECT_256_135 (4 << 4) + +/* Limited range RGB flag. Not set corresponds to full range. */ +#define TIMINGS_FLAGS_RGB_LIMITED BIT(8) +/* DVI monitor, therefore disable infoframes. Not set corresponds to HDMI. */ +#define TIMINGS_FLAGS_DVI BIT(9) +}; + +struct mailbox_set_mode { + struct rpi_firmware_property_tag_header tag1; + struct set_timings timings; }; static const struct vc_image_format { @@ -186,6 +235,7 @@ struct vc4_crtc { u32 overscan[4]; bool vblank_enabled; u32 display_number; + u32 display_type; }; static inline struct vc4_crtc *to_vc4_crtc(struct drm_crtc *crtc) @@ -195,6 +245,8 @@ static inline struct vc4_crtc *to_vc4_cr struct vc4_fkms_encoder { struct drm_encoder base; + bool hdmi_monitor; + bool rgb_range_selectable; }; static inline struct vc4_fkms_encoder * @@ -212,7 +264,9 @@ struct vc4_fkms_connector { * hook. */ struct drm_encoder *encoder; - u32 display_idx; + struct vc4_dev *vc4_dev; + u32 display_number; + u32 display_type; }; static inline struct vc4_fkms_connector * @@ -221,6 +275,26 @@ to_vc4_fkms_connector(struct drm_connect return container_of(connector, struct vc4_fkms_connector, base); } +static u32 vc4_get_display_type(u32 display_number) +{ + const u32 display_types[] = { + /* The firmware display (DispmanX) IDs map to specific types in + * a fixed manner. + */ + DRM_MODE_ENCODER_DSI, /* MAIN_LCD */ + DRM_MODE_ENCODER_DSI, /* AUX_LCD */ + DRM_MODE_ENCODER_TMDS, /* HDMI0 */ + DRM_MODE_ENCODER_TVDAC, /* VEC */ + DRM_MODE_ENCODER_NONE, /* FORCE_LCD */ + DRM_MODE_ENCODER_NONE, /* FORCE_TV */ + DRM_MODE_ENCODER_NONE, /* FORCE_OTHER */ + DRM_MODE_ENCODER_TMDS, /* HDMI1 */ + DRM_MODE_ENCODER_NONE, /* FORCE_TV2 */ + }; + return display_number > ARRAY_SIZE(display_types) - 1 ? + DRM_MODE_ENCODER_NONE : display_types[display_number]; +} + /* Firmware's structure for making an FB mbox call. */ struct fbinfo_s { u32 xres, yres, xres_virtual, yres_virtual; @@ -255,10 +329,15 @@ static int vc4_plane_set_blank(struct dr .plane_id = vc4_plane->mb.plane.plane_id, } }; + static const char * const plane_types[] = { + "overlay", + "primary", + "cursor" + }; int ret; - DRM_DEBUG_ATOMIC("[PLANE:%d:%s] overlay plane %s", - plane->base.id, plane->name, + DRM_DEBUG_ATOMIC("[PLANE:%d:%s] %s plane %s", + plane->base.id, plane->name, plane_types[plane->type], blank ? "blank" : "unblank"); if (blank) @@ -593,13 +672,102 @@ fail: static void vc4_crtc_mode_set_nofb(struct drm_crtc *crtc) { - /* Everyting is handled in the planes. */ + struct drm_device *dev = crtc->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + struct vc4_fkms_encoder *vc4_encoder = + to_vc4_fkms_encoder(vc4_crtc->encoder); + struct mailbox_set_mode mb = { + .tag1 = { RPI_FIRMWARE_SET_TIMING, + sizeof(struct set_timings), 0}, + }; + union hdmi_infoframe frame; + int ret; + + ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, mode, false); + if (ret < 0) { + DRM_ERROR("couldn't fill AVI infoframe\n"); + return; + } + + DRM_DEBUG_KMS("Setting mode for display num %u mode name %s, clk %d, h(disp %d, start %d, end %d, total %d, skew %d) v(disp %d, start %d, end %d, total %d, scan %d), vrefresh %d, par %u\n", + vc4_crtc->display_number, mode->name, mode->clock, + mode->hdisplay, mode->hsync_start, mode->hsync_end, + mode->htotal, mode->hskew, mode->vdisplay, + mode->vsync_start, mode->vsync_end, mode->vtotal, + mode->vscan, mode->vrefresh, mode->picture_aspect_ratio); + mb.timings.display = vc4_crtc->display_number; + + mb.timings.video_id_code = frame.avi.video_code; + + mb.timings.clock = mode->clock; + mb.timings.hdisplay = mode->hdisplay; + mb.timings.hsync_start = mode->hsync_start; + mb.timings.hsync_end = mode->hsync_end; + mb.timings.htotal = mode->htotal; + mb.timings.hskew = mode->hskew; + mb.timings.vdisplay = mode->vdisplay; + mb.timings.vsync_start = mode->vsync_start; + mb.timings.vsync_end = mode->vsync_end; + mb.timings.vtotal = mode->vtotal; + mb.timings.vscan = mode->vscan; + mb.timings.vrefresh = 0; + mb.timings.flags = 0; + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + mb.timings.flags |= TIMINGS_FLAGS_H_SYNC_POS; + if (mode->flags & DRM_MODE_FLAG_PVSYNC) + mb.timings.flags |= TIMINGS_FLAGS_V_SYNC_POS; + + switch (frame.avi.picture_aspect) { + default: + case HDMI_PICTURE_ASPECT_NONE: + mode->flags |= TIMINGS_FLAGS_ASPECT_NONE; + break; + case HDMI_PICTURE_ASPECT_4_3: + mode->flags |= TIMINGS_FLAGS_ASPECT_4_3; + break; + case HDMI_PICTURE_ASPECT_16_9: + mode->flags |= TIMINGS_FLAGS_ASPECT_16_9; + break; + case HDMI_PICTURE_ASPECT_64_27: + mode->flags |= TIMINGS_FLAGS_ASPECT_64_27; + break; + case HDMI_PICTURE_ASPECT_256_135: + mode->flags |= TIMINGS_FLAGS_ASPECT_256_135; + break; + } + + if (!vc4_encoder->hdmi_monitor) + mb.timings.flags |= TIMINGS_FLAGS_DVI; + else if (drm_default_rgb_quant_range(mode) == + HDMI_QUANTIZATION_RANGE_LIMITED) + mb.timings.flags |= TIMINGS_FLAGS_RGB_LIMITED; + + /* + FIXME: To implement + switch(mode->flag & DRM_MODE_FLAG_3D_MASK) { + case DRM_MODE_FLAG_3D_NONE: + case DRM_MODE_FLAG_3D_FRAME_PACKING: + case DRM_MODE_FLAG_3D_FIELD_ALTERNATIVE: + case DRM_MODE_FLAG_3D_LINE_ALTERNATIVE: + case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_FULL: + case DRM_MODE_FLAG_3D_L_DEPTH: + case DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH: + case DRM_MODE_FLAG_3D_TOP_AND_BOTTOM: + case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF: + } + */ + + ret = rpi_firmware_property_list(vc4->firmware, &mb, sizeof(mb)); } static void vc4_crtc_disable(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { struct drm_plane *plane; + DRM_DEBUG_KMS("[CRTC:%d] vblanks off.\n", + crtc->base.id); drm_crtc_vblank_off(crtc); /* Always turn the planes off on CRTC disable. In DRM, planes @@ -617,6 +785,8 @@ static void vc4_crtc_enable(struct drm_c { struct drm_plane *plane; + DRM_DEBUG_KMS("[CRTC:%d] vblanks on.\n", + crtc->base.id); drm_crtc_vblank_on(crtc); /* Unblank the planes (if they're supposed to be displayed). */ @@ -635,12 +805,20 @@ vc4_crtc_mode_valid(struct drm_crtc *crt return MODE_NO_DBLESCAN; } + /* Limit the pixel clock until we can get dynamic HDMI 2.0 scrambling + * working. + */ + if (mode->clock > 340000) + return MODE_CLOCK_HIGH; + return MODE_OK; } static int vc4_crtc_atomic_check(struct drm_crtc *crtc, struct drm_crtc_state *state) { + DRM_DEBUG_KMS("[CRTC:%d] crtc_atomic_check.\n", + crtc->base.id); return 0; } @@ -650,6 +828,8 @@ static void vc4_crtc_atomic_flush(struct struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); struct drm_device *dev = crtc->dev; + DRM_DEBUG_KMS("[CRTC:%d] crtc_atomic_flush.\n", + crtc->base.id); if (crtc->state->event) { unsigned long flags; @@ -717,6 +897,8 @@ static int vc4_fkms_enable_vblank(struct { struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); + DRM_DEBUG_KMS("[CRTC:%d] enable_vblank.\n", + crtc->base.id); vc4_crtc->vblank_enabled = true; return 0; @@ -726,6 +908,8 @@ static void vc4_fkms_disable_vblank(stru { struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); + DRM_DEBUG_KMS("[CRTC:%d] disable_vblank.\n", + crtc->base.id); vc4_crtc->vblank_enabled = false; } @@ -760,36 +944,92 @@ static const struct of_device_id vc4_fir static enum drm_connector_status vc4_fkms_connector_detect(struct drm_connector *connector, bool force) { + DRM_DEBUG_KMS("connector detect.\n"); return connector_status_connected; } -static int vc4_fkms_connector_get_modes(struct drm_connector *connector) +static int vc4_fkms_get_edid_block(void *data, u8 *buf, unsigned int block, + size_t len) { - struct drm_device *dev = connector->dev; struct vc4_fkms_connector *fkms_connector = - to_vc4_fkms_connector(connector); - struct vc4_dev *vc4 = to_vc4_dev(dev); - struct drm_display_mode *mode; - struct mailbox_get_width_height wh = { - .tag1 = {RPI_FIRMWARE_FRAMEBUFFER_SET_DISPLAY_NUM, 4, 0, }, - .display = fkms_connector->display_idx, - .tag2 = { RPI_FIRMWARE_FRAMEBUFFER_GET_PHYSICAL_WIDTH_HEIGHT, - 8, 0, }, + (struct vc4_fkms_connector *)data; + struct vc4_dev *vc4 = fkms_connector->vc4_dev; + struct mailbox_get_edid mb = { + .tag1 = { RPI_FIRMWARE_GET_EDID_BLOCK_DISPLAY, + 128 + 8, 0 }, + .block = block, + .display_number = fkms_connector->display_number, }; - int ret; + int ret = 0; + + ret = rpi_firmware_property_list(vc4->firmware, &mb, sizeof(mb)); + + if (!ret) + memcpy(buf, mb.edid, len); + + return ret; +} + +static int vc4_fkms_connector_get_modes(struct drm_connector *connector) +{ + struct vc4_fkms_connector *fkms_connector = + to_vc4_fkms_connector(connector); + struct drm_encoder *encoder = fkms_connector->encoder; + struct vc4_fkms_encoder *vc4_encoder = to_vc4_fkms_encoder(encoder); + int ret = 0; + struct edid *edid; + + edid = drm_do_get_edid(connector, vc4_fkms_get_edid_block, + fkms_connector); + + /* FIXME: Can we do CEC? + * cec_s_phys_addr_from_edid(vc4->hdmi->cec_adap, edid); + * if (!edid) + * return -ENODEV; + */ + + vc4_encoder->hdmi_monitor = drm_detect_hdmi_monitor(edid); - ret = rpi_firmware_property_list(vc4->firmware, &wh, sizeof(wh)); + if (edid && edid->input & DRM_EDID_INPUT_DIGITAL) { + vc4_encoder->rgb_range_selectable = + drm_rgb_quant_range_selectable(edid); + } + + drm_connector_update_edid_property(connector, edid); + ret = drm_add_edid_modes(connector, edid); + kfree(edid); + + return ret; +} + +/* FIXME: Read LCD mode from the firmware. This is the DSI panel resolution. */ +static const struct drm_display_mode lcd_mode = { + DRM_MODE("800x480", DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED, + 25979400 / 1000, + 800, 800 + 1, 800 + 1 + 2, 800 + 1 + 2 + 46, 0, + 480, 480 + 7, 480 + 7 + 2, 480 + 7 + 2 + 21, 0, + DRM_MODE_FLAG_INTERLACE) +}; + +static int vc4_fkms_lcd_connector_get_modes(struct drm_connector *connector) +{ + //struct vc4_fkms_connector *fkms_connector = + // to_vc4_fkms_connector(connector); + //struct drm_encoder *encoder = fkms_connector->encoder; + //struct vc4_fkms_encoder *vc4_encoder = to_vc4_fkms_encoder(encoder); + struct drm_display_mode *mode; + //int ret = 0; - if (ret) { - DRM_ERROR("Failed to get screen size: %d (0x%08x 0x%08x)\n", - ret, wh.wh[0], wh.wh[1]); - return 0; + mode = drm_mode_duplicate(connector->dev, + &lcd_mode); + if (!mode) { + DRM_ERROR("Failed to create a new display mode\n"); + return -ENOMEM; } - mode = drm_cvt_mode(dev, wh.wh[0], wh.wh[1], 60 /* vrefresh */, - 0, 0, false); drm_mode_probed_add(connector, mode); + /* We have one mode */ return 1; } @@ -798,11 +1038,14 @@ vc4_fkms_connector_best_encoder(struct d { struct vc4_fkms_connector *fkms_connector = to_vc4_fkms_connector(connector); + DRM_DEBUG_KMS("best_connector.\n"); return fkms_connector->encoder; } static void vc4_fkms_connector_destroy(struct drm_connector *connector) { + DRM_DEBUG_KMS("[CONNECTOR:%d] destroy.\n", + connector->base.id); drm_connector_unregister(connector); drm_connector_cleanup(connector); } @@ -821,14 +1064,22 @@ static const struct drm_connector_helper .best_encoder = vc4_fkms_connector_best_encoder, }; +static const struct drm_connector_helper_funcs vc4_fkms_lcd_conn_helper_funcs = { + .get_modes = vc4_fkms_lcd_connector_get_modes, + .best_encoder = vc4_fkms_connector_best_encoder, +}; + static struct drm_connector * vc4_fkms_connector_init(struct drm_device *dev, struct drm_encoder *encoder, - u32 display_idx) + u32 display_num) { struct drm_connector *connector = NULL; struct vc4_fkms_connector *fkms_connector; + struct vc4_dev *vc4_dev = to_vc4_dev(dev); int ret = 0; + DRM_DEBUG_KMS("connector_init, display_num %u\n", display_num); + fkms_connector = devm_kzalloc(dev->dev, sizeof(*fkms_connector), GFP_KERNEL); if (!fkms_connector) { @@ -838,11 +1089,21 @@ vc4_fkms_connector_init(struct drm_devic connector = &fkms_connector->base; fkms_connector->encoder = encoder; - fkms_connector->display_idx = display_idx; - - drm_connector_init(dev, connector, &vc4_fkms_connector_funcs, - DRM_MODE_CONNECTOR_HDMIA); - drm_connector_helper_add(connector, &vc4_fkms_connector_helper_funcs); + fkms_connector->display_number = display_num; + fkms_connector->display_type = vc4_get_display_type(display_num); + fkms_connector->vc4_dev = vc4_dev; + + if (fkms_connector->display_type == DRM_MODE_ENCODER_DSI) { + drm_connector_init(dev, connector, &vc4_fkms_connector_funcs, + DRM_MODE_CONNECTOR_DSI); + drm_connector_helper_add(connector, + &vc4_fkms_lcd_conn_helper_funcs); + } else { + drm_connector_init(dev, connector, &vc4_fkms_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + drm_connector_helper_add(connector, + &vc4_fkms_connector_helper_funcs); + } connector->polled = (DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT); @@ -863,6 +1124,7 @@ vc4_fkms_connector_init(struct drm_devic static void vc4_fkms_encoder_destroy(struct drm_encoder *encoder) { + DRM_DEBUG_KMS("Encoder_destroy\n"); drm_encoder_cleanup(encoder); } @@ -872,10 +1134,12 @@ static const struct drm_encoder_funcs vc static void vc4_fkms_encoder_enable(struct drm_encoder *encoder) { + DRM_DEBUG_KMS("Encoder_enable\n"); } static void vc4_fkms_encoder_disable(struct drm_encoder *encoder) { + DRM_DEBUG_KMS("Encoder_disable\n"); } static const struct drm_encoder_helper_funcs vc4_fkms_encoder_helper_funcs = { @@ -907,6 +1171,7 @@ static int vc4_fkms_create_screen(struct crtc = &vc4_crtc->base; vc4_crtc->display_number = display_ref; + vc4_crtc->display_type = vc4_get_display_type(display_ref); /* Blank the firmware provided framebuffer */ rpi_firmware_property_list(vc4->firmware, &blank, sizeof(blank)); @@ -950,13 +1215,14 @@ static int vc4_fkms_create_screen(struct return -ENOMEM; vc4_crtc->encoder = &vc4_encoder->base; vc4_encoder->base.possible_crtcs |= drm_crtc_mask(crtc) ; + drm_encoder_init(drm, &vc4_encoder->base, &vc4_fkms_encoder_funcs, - DRM_MODE_ENCODER_TMDS, NULL); + vc4_crtc->display_type, NULL); drm_encoder_helper_add(&vc4_encoder->base, &vc4_fkms_encoder_helper_funcs); vc4_crtc->connector = vc4_fkms_connector_init(drm, &vc4_encoder->base, - display_idx); + display_ref); if (IS_ERR(vc4_crtc->connector)) { ret = PTR_ERR(vc4_crtc->connector); goto err_destroy_encoder; --- a/include/soc/bcm2835/raspberrypi-firmware.h +++ b/include/soc/bcm2835/raspberrypi-firmware.h @@ -78,6 +78,7 @@ enum rpi_firmware_property_tag { RPI_FIRMWARE_GET_DISPMANX_RESOURCE_MEM_HANDLE = 0x00030014, RPI_FIRMWARE_GET_EDID_BLOCK = 0x00030020, RPI_FIRMWARE_GET_CUSTOMER_OTP = 0x00030021, + RPI_FIRMWARE_GET_EDID_BLOCK_DISPLAY = 0x00030023, RPI_FIRMWARE_GET_DOMAIN_STATE = 0x00030030, RPI_FIRMWARE_GET_THROTTLED = 0x00030046, RPI_FIRMWARE_GET_CLOCK_MEASURED = 0x00030047, @@ -150,6 +151,7 @@ enum rpi_firmware_property_tag { RPI_FIRMWARE_VCHIQ_INIT = 0x00048010, RPI_FIRMWARE_SET_PLANE = 0x00048015, + RPI_FIRMWARE_SET_TIMING = 0x00048017, RPI_FIRMWARE_GET_COMMAND_LINE = 0x00050001, RPI_FIRMWARE_GET_DMA_CHANNELS = 0x00060001,