All these are on top of rc3 (also in my drm-mst-hide-monitor branch).
So after talking to Keith and Daniel in Chicago I decided to give another go at hiding the horror that is 30" 4k dual-panel MST monitors in the kernel.
Also this hw is not going away, if anything it seems to be getting worse with Dell promising a 5k monitor, requiring 2 MST ports.
The first two patches aren't really part of this series, I should probably put them in -fixes anyways. (i.e. ignore them)
So this series, contains the functionality changes required to hide tiled crtc underneath a master crtc, and to keep track of them for modeset and pageflipping and cursors (not finished).
It also allows EDID patching to fake the super-mode, along with hiding the sub-connectors.
Patch 9 is probably the biggest hack here due to lack of atomic modesetting, and how userspace picks crtcs.
Patch 10 is pageflip so hacky as well, and 11 is just bogus cursor handling, I just wanted to see the cursor in some form for now.
Mostly I'm looking for some high-level review of the concepts of this and how much harder it'll make life going forward.
Dave.
From: Dave Airlie airlied@redhat.com
The old code has problems with the Dell MST monitors due to some assumptions I made that weren't true.
I initially thought the Virtual Channel Payload IDs had to be in the DPCD table in ascending order, however it appears that assumption is bogus.
The old code also assumed it was possible to insert a member into the table and it would move other members up, like it does when you remove table entries, however reality has shown this isn't true.
So the new code allocates VCPIs separate from entries in the payload tracking table, and when we remove an entry from the DPCD table, I shuffle the tracking payload entries around in the struct.
This appears to make VT switch more robust (still not perfect) with an MST enabled Dell monitor.
Signed-off-by: Dave Airlie airlied@redhat.com --- drivers/gpu/drm/drm_dp_mst_topology.c | 77 ++++++++++++++++++++++++++--------- include/drm/drm_dp_mst_helper.h | 2 + 2 files changed, 59 insertions(+), 20 deletions(-)
diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c index ac3c273..50926db 100644 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/drm_dp_mst_topology.c @@ -682,7 +682,7 @@ static int build_allocate_payload(struct drm_dp_sideband_msg_tx *msg, int port_n static int drm_dp_mst_assign_payload_id(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_vcpi *vcpi) { - int ret; + int ret, vcpi_ret;
mutex_lock(&mgr->payload_lock); ret = find_first_zero_bit(&mgr->payload_mask, mgr->max_payloads + 1); @@ -692,8 +692,16 @@ static int drm_dp_mst_assign_payload_id(struct drm_dp_mst_topology_mgr *mgr, goto out_unlock; }
+ vcpi_ret = find_first_zero_bit(&mgr->vcpi_mask, mgr->max_payloads + 1); + if (vcpi_ret > mgr->max_payloads) { + ret = -EINVAL; + DRM_DEBUG_KMS("out of vcpi ids %d\n", ret); + goto out_unlock; + } + set_bit(ret, &mgr->payload_mask); - vcpi->vcpi = ret; + set_bit(vcpi_ret, &mgr->vcpi_mask); + vcpi->vcpi = vcpi_ret + 1; mgr->proposed_vcpis[ret - 1] = vcpi; out_unlock: mutex_unlock(&mgr->payload_lock); @@ -701,15 +709,23 @@ out_unlock: }
static void drm_dp_mst_put_payload_id(struct drm_dp_mst_topology_mgr *mgr, - int id) + int vcpi) { - if (id == 0) + int i; + if (vcpi == 0) return;
mutex_lock(&mgr->payload_lock); - DRM_DEBUG_KMS("putting payload %d\n", id); - clear_bit(id, &mgr->payload_mask); - mgr->proposed_vcpis[id - 1] = NULL; + DRM_DEBUG_KMS("putting payload %d\n", vcpi); + clear_bit(vcpi - 1, &mgr->vcpi_mask); + + for (i = 0; i < mgr->max_payloads; i++) { + if (mgr->proposed_vcpis[i]) + if (mgr->proposed_vcpis[i]->vcpi == vcpi) { + mgr->proposed_vcpis[i] = NULL; + clear_bit(i + 1, &mgr->payload_mask); + } + } mutex_unlock(&mgr->payload_lock); }
@@ -1563,7 +1579,7 @@ static int drm_dp_destroy_payload_step1(struct drm_dp_mst_topology_mgr *mgr, }
drm_dp_dpcd_write_payload(mgr, id, payload); - payload->payload_state = 0; + payload->payload_state = DP_PAYLOAD_DELETE_LOCAL; return 0; }
@@ -1590,7 +1606,7 @@ static int drm_dp_destroy_payload_step2(struct drm_dp_mst_topology_mgr *mgr, */ int drm_dp_update_payload_part1(struct drm_dp_mst_topology_mgr *mgr) { - int i; + int i, j; int cur_slots = 1; struct drm_dp_payload req_payload; struct drm_dp_mst_port *port; @@ -1607,26 +1623,46 @@ int drm_dp_update_payload_part1(struct drm_dp_mst_topology_mgr *mgr) port = NULL; req_payload.num_slots = 0; } + + if (mgr->payloads[i].start_slot != req_payload.start_slot) { + mgr->payloads[i].start_slot = req_payload.start_slot; + } /* work out what is required to happen with this payload */ - if (mgr->payloads[i].start_slot != req_payload.start_slot || - mgr->payloads[i].num_slots != req_payload.num_slots) { + if (mgr->payloads[i].num_slots != req_payload.num_slots) {
/* need to push an update for this payload */ if (req_payload.num_slots) { - drm_dp_create_payload_step1(mgr, i + 1, &req_payload); + drm_dp_create_payload_step1(mgr, mgr->proposed_vcpis[i]->vcpi, &req_payload); mgr->payloads[i].num_slots = req_payload.num_slots; } else if (mgr->payloads[i].num_slots) { mgr->payloads[i].num_slots = 0; - drm_dp_destroy_payload_step1(mgr, port, i + 1, &mgr->payloads[i]); + drm_dp_destroy_payload_step1(mgr, port, port->vcpi.vcpi, &mgr->payloads[i]); req_payload.payload_state = mgr->payloads[i].payload_state; - } else - req_payload.payload_state = 0; - - mgr->payloads[i].start_slot = req_payload.start_slot; + mgr->payloads[i].start_slot = 0; + } mgr->payloads[i].payload_state = req_payload.payload_state; } cur_slots += req_payload.num_slots; } + + for (i = 0; i < mgr->max_payloads; i++) { + if (mgr->payloads[i].payload_state == DP_PAYLOAD_DELETE_LOCAL) { + DRM_DEBUG_KMS("removing payload %d\n", i); + for (j = i; j < mgr->max_payloads - 1; j++) { + memcpy(&mgr->payloads[j], &mgr->payloads[j + 1], sizeof(struct drm_dp_payload)); + mgr->proposed_vcpis[j] = mgr->proposed_vcpis[j + 1]; + if (mgr->proposed_vcpis[j] && mgr->proposed_vcpis[j]->num_slots) { + set_bit(j + 1, &mgr->payload_mask); + } else { + clear_bit(j + 1, &mgr->payload_mask); + } + } + memset(&mgr->payloads[mgr->max_payloads - 1], 0, sizeof(struct drm_dp_payload)); + mgr->proposed_vcpis[mgr->max_payloads - 1] = NULL; + clear_bit(mgr->max_payloads, &mgr->payload_mask); + + } + } mutex_unlock(&mgr->payload_lock);
return 0; @@ -1657,9 +1693,9 @@ int drm_dp_update_payload_part2(struct drm_dp_mst_topology_mgr *mgr)
DRM_DEBUG_KMS("payload %d %d\n", i, mgr->payloads[i].payload_state); if (mgr->payloads[i].payload_state == DP_PAYLOAD_LOCAL) { - ret = drm_dp_create_payload_step2(mgr, port, i + 1, &mgr->payloads[i]); + ret = drm_dp_create_payload_step2(mgr, port, mgr->proposed_vcpis[i]->vcpi, &mgr->payloads[i]); } else if (mgr->payloads[i].payload_state == DP_PAYLOAD_DELETE_LOCAL) { - ret = drm_dp_destroy_payload_step2(mgr, i + 1, &mgr->payloads[i]); + ret = drm_dp_destroy_payload_step2(mgr, mgr->proposed_vcpis[i]->vcpi, &mgr->payloads[i]); } if (ret) { mutex_unlock(&mgr->payload_lock); @@ -1861,6 +1897,7 @@ int drm_dp_mst_topology_mgr_set_mst(struct drm_dp_mst_topology_mgr *mgr, bool ms memset(mgr->payloads, 0, mgr->max_payloads * sizeof(struct drm_dp_payload)); mgr->payload_mask = 0; set_bit(0, &mgr->payload_mask); + mgr->vcpi_mask = 0; }
out_unlock: @@ -2474,7 +2511,7 @@ void drm_dp_mst_dump_topology(struct seq_file *m, mutex_unlock(&mgr->lock);
mutex_lock(&mgr->payload_lock); - seq_printf(m, "vcpi: %lx\n", mgr->payload_mask); + seq_printf(m, "vcpi: %lx %lx\n", mgr->payload_mask, mgr->vcpi_mask);
for (i = 0; i < mgr->max_payloads; i++) { if (mgr->proposed_vcpis[i]) { diff --git a/include/drm/drm_dp_mst_helper.h b/include/drm/drm_dp_mst_helper.h index 9b446ad..338fc10 100644 --- a/include/drm/drm_dp_mst_helper.h +++ b/include/drm/drm_dp_mst_helper.h @@ -388,6 +388,7 @@ struct drm_dp_payload { int payload_state; int start_slot; int num_slots; + int vcpi; };
/** @@ -454,6 +455,7 @@ struct drm_dp_mst_topology_mgr { struct drm_dp_vcpi **proposed_vcpis; struct drm_dp_payload *payloads; unsigned long payload_mask; + unsigned long vcpi_mask;
wait_queue_head_t tx_waitq; struct work_struct work;
From: Dave Airlie airlied@redhat.com
Since DP MST has new userspace requirements and any effort at trying to shoehorn things into the something the current userspace can handle are doomed, lets just put this behind a config option.
This also adds a command line arg to switch it on as well.
Distros should probably turn it on when they have a new enough Xorg intel driver.
Signed-off-by: Dave Airlie airlied@redhat.com --- drivers/gpu/drm/i915/Kconfig | 10 ++++++++++ drivers/gpu/drm/i915/i915_drv.h | 1 + drivers/gpu/drm/i915/i915_params.c | 5 +++++ drivers/gpu/drm/i915/intel_dp_mst.c | 3 +++ 4 files changed, 19 insertions(+)
diff --git a/drivers/gpu/drm/i915/Kconfig b/drivers/gpu/drm/i915/Kconfig index 4e39ab3..94a9094 100644 --- a/drivers/gpu/drm/i915/Kconfig +++ b/drivers/gpu/drm/i915/Kconfig @@ -69,3 +69,13 @@ config DRM_I915_PRELIMINARY_HW_SUPPORT option changes the default for that module option.
If in doubt, say "N". + +config DRM_I915_DP_MST + bool "Enable DisplayPort 1.2 MST support by default" + depends on DRM_I915 + default n + help + Choose this option if you want to support DP 1.2 MST and have a new + enough userspace to use it. DP 1.2 MST is required for multi-head + support on many laptop docks when combined with Haswell or later + chipsets. diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index 7a830ea..86759ea 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -2149,6 +2149,7 @@ struct i915_params { bool disable_vtd_wa; int use_mmio_flip; bool mmio_debug; + bool dp_mst; }; extern struct i915_params i915 __read_mostly;
diff --git a/drivers/gpu/drm/i915/i915_params.c b/drivers/gpu/drm/i915/i915_params.c index 7f84dd26..973c773 100644 --- a/drivers/gpu/drm/i915/i915_params.c +++ b/drivers/gpu/drm/i915/i915_params.c @@ -50,6 +50,7 @@ struct i915_params i915 __read_mostly = { .disable_vtd_wa = 0, .use_mmio_flip = 0, .mmio_debug = 0, + .dp_mst = IS_ENABLED(CONFIG_DRM_I915_DP_MST), };
module_param_named(modeset, i915.modeset, int, 0400); @@ -167,3 +168,7 @@ module_param_named(mmio_debug, i915.mmio_debug, bool, 0600); MODULE_PARM_DESC(mmio_debug, "Enable the MMIO debug code (default: false). This may negatively " "affect performance."); + +module_param_named(dp_mst, i915.dp_mst, bool, 0600); +MODULE_PARM_DESC(dp_mst, + "Enable DP MST support (1=enabled, 0=disabled, default depends on CONFIG_DRM_I915_DP_MST."); diff --git a/drivers/gpu/drm/i915/intel_dp_mst.c b/drivers/gpu/drm/i915/intel_dp_mst.c index d9a7a78..116b656 100644 --- a/drivers/gpu/drm/i915/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/intel_dp_mst.c @@ -522,6 +522,9 @@ intel_dp_mst_encoder_init(struct intel_digital_port *intel_dig_port, int conn_ba struct drm_device *dev = intel_dig_port->base.base.dev; int ret;
+ if (i915.dp_mst == false) + return 0; + intel_dp->can_mst = true; intel_dp->mst_mgr.cbs = &mst_cbs;
On Tue, 09 Sep 2014, Dave Airlie airlied@gmail.com wrote:
The permissions should be 0400.
Jani.
From: Dave Airlie airlied@redhat.com
For the monitors with panels we only want EDID once, and they show up on logical ports, which are always connected.
This is required as if we start hiding connector status we won't be able to get the EDID normally later.
Signed-off-by: Dave Airlie airlied@redhat.com --- drivers/gpu/drm/drm_dp_mst_topology.c | 15 +++++++++++++-- drivers/gpu/drm/i915/intel_dp_mst.c | 2 +- include/drm/drm_dp_mst_helper.h | 4 +++- 3 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c index 50926db..234a82c 100644 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/drm_dp_mst_topology.c @@ -1096,6 +1096,9 @@ static void drm_dp_add_port(struct drm_dp_mst_branch *mstb, char proppath[255]; build_mst_prop_path(port, mstb, proppath); port->connector = (*mstb->mgr->cbs->add_connector)(mstb->mgr, port, proppath); + + if (port->port_num >= 8) + port->cached_edid = drm_get_edid(port->connector, &port->aux.ddc); }
/* put reference to this port */ @@ -2149,7 +2152,8 @@ EXPORT_SYMBOL(drm_dp_mst_hpd_irq); * This returns the current connection state for a port. It validates the * port pointer still exists so the caller doesn't require a reference */ -enum drm_connector_status drm_dp_mst_detect_port(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) +enum drm_connector_status drm_dp_mst_detect_port(struct drm_connector *connector, + struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) { enum drm_connector_status status = connector_status_disconnected;
@@ -2168,6 +2172,10 @@ enum drm_connector_status drm_dp_mst_detect_port(struct drm_dp_mst_topology_mgr
case DP_PEER_DEVICE_SST_SINK: status = connector_status_connected; + /* for logical ports - cache the EDID */ + if (port->port_num >= 8 && !port->cached_edid) { + port->cached_edid = drm_get_edid(connector, &port->aux.ddc); + } break; case DP_PEER_DEVICE_DP_LEGACY_CONV: if (port->ldps) @@ -2199,7 +2207,10 @@ struct edid *drm_dp_mst_get_edid(struct drm_connector *connector, struct drm_dp_ if (!port) return NULL;
- edid = drm_get_edid(connector, &port->aux.ddc); + if (port->cached_edid) + edid = drm_edid_duplicate(port->cached_edid); + else + edid = drm_get_edid(connector, &port->aux.ddc); drm_dp_put_port(port); return edid; } diff --git a/drivers/gpu/drm/i915/intel_dp_mst.c b/drivers/gpu/drm/i915/intel_dp_mst.c index 116b656..d92048b 100644 --- a/drivers/gpu/drm/i915/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/intel_dp_mst.c @@ -283,7 +283,7 @@ intel_mst_port_dp_detect(struct drm_connector *connector) struct intel_connector *intel_connector = to_intel_connector(connector); struct intel_dp *intel_dp = intel_connector->mst_port;
- return drm_dp_mst_detect_port(&intel_dp->mst_mgr, intel_connector->port); + return drm_dp_mst_detect_port(connector, &intel_dp->mst_mgr, intel_connector->port); }
static enum drm_connector_status diff --git a/include/drm/drm_dp_mst_helper.h b/include/drm/drm_dp_mst_helper.h index 338fc10..ee6fbad 100644 --- a/include/drm/drm_dp_mst_helper.h +++ b/include/drm/drm_dp_mst_helper.h @@ -92,6 +92,8 @@ struct drm_dp_mst_port { struct drm_dp_vcpi vcpi; struct drm_connector *connector; struct drm_dp_mst_topology_mgr *mgr; + + struct edid *cached_edid; /* for DP logical ports - make tiling work */ };
/** @@ -474,7 +476,7 @@ int drm_dp_mst_topology_mgr_set_mst(struct drm_dp_mst_topology_mgr *mgr, bool ms int drm_dp_mst_hpd_irq(struct drm_dp_mst_topology_mgr *mgr, u8 *esi, bool *handled);
-enum drm_connector_status drm_dp_mst_detect_port(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port); +enum drm_connector_status drm_dp_mst_detect_port(struct drm_connector *connector, struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port);
struct edid *drm_dp_mst_get_edid(struct drm_connector *connector, struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port);
From: Dave Airlie airlied@redhat.com
Just enough to get the tiling info from a Dell 4k monitor.
Signed-off-by: Dave Airlie airlied@redhat.com --- drivers/gpu/drm/Makefile | 2 +- drivers/gpu/drm/drm_displayid.c | 68 ++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/drm_edid.c | 29 ++++++++++++++-- drivers/gpu/drm/i915/intel_modes.c | 3 ++ include/drm/drm_crtc.h | 5 +++ include/drm/drm_displayid.h | 55 ++++++++++++++++++++++++++++++ include/drm/drm_edid.h | 2 ++ 7 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 drivers/gpu/drm/drm_displayid.c create mode 100644 include/drm/drm_displayid.h
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 4a55d59..50eebe1 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -14,7 +14,7 @@ drm-y := drm_auth.o drm_buffer.o drm_bufs.o drm_cache.o \ drm_info.o drm_debugfs.o drm_encoder_slave.o \ drm_trace_points.o drm_global.o drm_prime.o \ drm_rect.o drm_vma_manager.o drm_flip_work.o \ - drm_modeset_lock.o + drm_modeset_lock.o drm_displayid.o
drm-$(CONFIG_COMPAT) += drm_ioc32.o drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o diff --git a/drivers/gpu/drm/drm_displayid.c b/drivers/gpu/drm/drm_displayid.c new file mode 100644 index 0000000..d434da1 --- /dev/null +++ b/drivers/gpu/drm/drm_displayid.c @@ -0,0 +1,68 @@ +/* decode display ID block */ + +/* just enough coding to get tiling blocks from new monitors */ + +#include "drmP.h" +#include "drm_edid.h" +#include "drm_displayid.h" + +int drm_parse_display_id(struct drm_connector *connector, + u8 *displayid, int length, bool is_edid_extension) +{ + /* if this is an EDID extension the first byte will be 0x70 */ + int idx = 0; + struct displayid_hdr *base; + struct displayid_block *block; + u8 csum = 0; + int i; + if (is_edid_extension) + idx = 1; + + base = (struct displayid_hdr *)&displayid[idx]; + + printk("base revision 0x%x, length %d, %d %d\n", + base->rev, base->bytes, base->prod_id, base->ext_count); + + if (base->bytes + 5 > length - idx) + return -EINVAL; + + for (i = idx; i <= base->bytes + 5; i++) { + csum += displayid[i]; + } + if (csum) { + DRM_ERROR("DisplayID checksum invalid, remainder is %d\n", csum); + return -EINVAL; + } + + block = (struct displayid_block *)&displayid[idx + 4]; + printk("block id %d, rev %d, len %d\n", + block->tag, block->rev, block->num_bytes); + + switch (block->tag) { + case DATA_BLOCK_TILED_DISPLAY: { + struct displayid_tiled_block *tile = (struct displayid_tiled_block *)block; + u16 w, h; + u8 tile_v_loc, tile_h_loc; + u8 num_v_tile, num_h_tile; + + w = tile->tile_size[0] | tile->tile_size[1] << 8; + h = tile->tile_size[2] | tile->tile_size[3] << 8; + + num_v_tile = (tile->topo[0] & 0xf) | (tile->topo[2] & 0x30); + num_h_tile = (tile->topo[0] >> 4) | ((tile->topo[2] >> 2) & 0x30); + tile_v_loc = (tile->topo[1] & 0xf) | ((tile->topo[2] & 0x3) << 4); + tile_h_loc = (tile->topo[1] >> 4) | (((tile->topo[2] >> 2) & 0x3) << 4); + + printk("tile cap %d\n", tile->tile_cap); + printk("tile_size %d x %d\n", w, h); + printk("topo num tiles %dx%d, location %dx%d\n", + num_h_tile, num_v_tile, tile_h_loc, tile_v_loc); + printk("vend %c%c%c\n", tile->topology_id[0], tile->topology_id[1], tile->topology_id[2]); + } + break; + default: + printk("unknown displayid tag %d\n", block->tag); + break; + } + return 0; +} diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 1dbf3bc..3d805aa 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -2386,7 +2386,7 @@ add_detailed_modes(struct drm_connector *connector, struct edid *edid, /* * Search EDID for CEA extension block. */ -static u8 *drm_find_cea_extension(struct edid *edid) +static u8 *drm_find_edid_extension(struct edid *edid, int ext_id) { u8 *edid_ext = NULL; int i; @@ -2398,7 +2398,7 @@ static u8 *drm_find_cea_extension(struct edid *edid) /* Find CEA extension */ for (i = 0; i < edid->extensions; i++) { edid_ext = (u8 *)edid + EDID_LENGTH * (i + 1); - if (edid_ext[0] == CEA_EXT) + if (edid_ext[0] == ext_id) break; }
@@ -2408,6 +2408,16 @@ static u8 *drm_find_cea_extension(struct edid *edid) return edid_ext; }
+static u8 *drm_find_cea_extension(struct edid *edid) +{ + return drm_find_edid_extension(edid, CEA_EXT); +} + +static u8 *drm_find_displayid_extension(struct edid *edid) +{ + return drm_find_edid_extension(edid, DISPLAYID_EXT); +} + /* * Calculate the alternate clock for the CEA mode * (60Hz vs. 59.94Hz etc.) @@ -3865,3 +3875,18 @@ drm_hdmi_vendor_infoframe_from_display_mode(struct hdmi_vendor_infoframe *frame, return 0; } EXPORT_SYMBOL(drm_hdmi_vendor_infoframe_from_display_mode); + +void drm_get_displayid(struct drm_connector *connector, + struct i2c_adapter *adapter, struct edid *edid, + bool secondary) +{ + void *displayid = NULL; + displayid = drm_find_displayid_extension(edid); + if (!displayid) { + return; + } + + drm_parse_display_id(connector, displayid, EDID_LENGTH, true); + return; +} +EXPORT_SYMBOL(drm_get_displayid); diff --git a/drivers/gpu/drm/i915/intel_modes.c b/drivers/gpu/drm/i915/intel_modes.c index 0e860f3..35a327e 100644 --- a/drivers/gpu/drm/i915/intel_modes.c +++ b/drivers/gpu/drm/i915/intel_modes.c @@ -65,6 +65,9 @@ int intel_ddc_get_modes(struct drm_connector *connector, if (!edid) return 0;
+ if (edid) { + drm_get_displayid(connector, adapter, edid, true); + } ret = intel_connector_update_modes(connector, edid); kfree(edid);
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index f1105d0..1efc007 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -964,6 +964,9 @@ extern void drm_reinit_primary_mode_group(struct drm_device *dev); extern bool drm_probe_ddc(struct i2c_adapter *adapter); extern struct edid *drm_get_edid(struct drm_connector *connector, struct i2c_adapter *adapter); +extern void drm_get_displayid(struct drm_connector *connector, + struct i2c_adapter *adapter, struct edid *edid, + bool secondary); extern struct edid *drm_edid_duplicate(const struct edid *edid); extern int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid); extern void drm_mode_config_init(struct drm_device *dev); @@ -1106,6 +1109,8 @@ extern void drm_set_preferred_mode(struct drm_connector *connector, extern int drm_edid_header_is_valid(const u8 *raw_edid); extern bool drm_edid_block_valid(u8 *raw_edid, int block, bool print_bad_edid); extern bool drm_edid_is_valid(struct edid *edid); +extern int drm_parse_display_id(struct drm_connector *connector, + u8 *displayid, int length, bool is_edid_extension); struct drm_display_mode *drm_mode_find_dmt(struct drm_device *dev, int hsize, int vsize, int fresh, bool rb); diff --git a/include/drm/drm_displayid.h b/include/drm/drm_displayid.h new file mode 100644 index 0000000..9374f0e --- /dev/null +++ b/include/drm/drm_displayid.h @@ -0,0 +1,55 @@ +#ifndef DRM_DISPLAYID_H +#define DRM_DISPLAYID_H + +#define DATA_BLOCK_PRODUCT_ID 0x00 +#define DATA_BLOCK_DISPLAY_PARAMETERS 0x01 +#define DATA_BLOCK_COLOR_CHARACTERISTICS 0x02 +#define DATA_BLOCK_TYPE_1_DETAILED_TIMING 0x03 +#define DATA_BLOCK_TYPE_2_DETAILED_TIMING 0x04 +#define DATA_BLOCK_TYPE_3_SHORT_TIMING 0x05 +#define DATA_BLOCK_TYPE_4_DMT_TIMING 0x06 +#define DATA_BLOCK_VESA_TIMING 0x07 +#define DATA_BLOCK_CEA_TIMING 0x08 +#define DATA_BLOCK_VIDEO_TIMING_RANGE 0x09 +#define DATA_BLOCK_PRODUCT_SERIAL_NUMBER 0x0a +#define DATA_BLOCK_GP_ASCII_STRING 0x0b +#define DATA_BLOCK_DISPLAY_DEVICE_DATA 0x0c +#define DATA_BLOCK_INTERFACE_POWER_SEQUENCING 0x0d +#define DATA_BLOCK_TRANSFER_CHARACTERISTICS 0x0e +#define DATA_BLOCK_DISPLAY_INTERFACE 0x0f +#define DATA_BLOCK_STEREO_DISPLAY_INTERFACE 0x10 +#define DATA_BLOCK_TILED_DISPLAY 0x12 + +#define DATA_BLOCK_VENDOR_SPECIFIC 0x7f + +#define PRODUCT_TYPE_EXTENSION 0 +#define PRODUCT_TYPE_TEST 1 +#define PRODUCT_TYPE_PANEL 2 +#define PRODUCT_TYPE_MONITOR 3 +#define PRODUCT_TYPE_TV 4 +#define PRODUCT_TYPE_REPEATER 5 +#define PRODUCT_TYPE_DIRECT_DRIVE 6 + +struct displayid_hdr { + u8 rev; + u8 bytes; + u8 prod_id; + u8 ext_count; +} __attribute__((packed)); + +struct displayid_block { + u8 tag; + u8 rev; + u8 num_bytes; +}; + +struct displayid_tiled_block { + struct displayid_block base; + u8 tile_cap; + u8 topo[3]; + u8 tile_size[4]; + u8 tile_pixel_bezel[5]; + u8 topology_id[8]; +} __attribute__((packed)); + +#endif diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h index b96031d..3e87f5a 100644 --- a/include/drm/drm_edid.h +++ b/include/drm/drm_edid.h @@ -27,12 +27,14 @@
#define EDID_LENGTH 128 #define DDC_ADDR 0x50 +#define DDC_ADDR2 0x52 /* E-DDC 1.2 - where DisplayID can hide */
#define CEA_EXT 0x02 #define VTB_EXT 0x10 #define DI_EXT 0x40 #define LS_EXT 0x50 #define MI_EXT 0x60 +#define DISPLAYID_EXT 0x70
struct est_timings { u8 t1;
From: Dave Airlie airlied@redhat.com
This gives us a base identifier to group tiled outputs from.
However after reading about the Dell 5k monitor I expect this is probably too little, and we need some sort of hash table from the monitor EDID serial number.
Signed-off-by: Dave Airlie airlied@redhat.com --- drivers/gpu/drm/drm_dp_mst_topology.c | 30 ++++++++++++++++++++++++++---- include/drm/drm_dp_mst_helper.h | 4 ++++ 2 files changed, 30 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c index 234a82c..5d2a08e 100644 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/drm_dp_mst_topology.c @@ -778,14 +778,14 @@ out: return ret; }
-static struct drm_dp_mst_branch *drm_dp_add_mst_branch_device(u8 lct, u8 *rad) +static struct drm_dp_mst_branch *drm_dp_add_mst_branch_device(u8 lct, u8 *rad, u8 conn_base_id) { struct drm_dp_mst_branch *mstb;
mstb = kzalloc(sizeof(*mstb), GFP_KERNEL); if (!mstb) return NULL; - + mstb->conn_base_id = conn_base_id; mstb->lct = lct; if (lct > 1) memcpy(mstb->rad, rad, lct / 2); @@ -983,7 +983,7 @@ static bool drm_dp_port_setup_pdt(struct drm_dp_mst_port *port) case DP_PEER_DEVICE_MST_BRANCHING: lct = drm_dp_calculate_rad(port, rad);
- port->mstb = drm_dp_add_mst_branch_device(lct, rad); + port->mstb = drm_dp_add_mst_branch_device(lct, rad, 0); port->mstb->mgr = port->mgr; port->mstb->port_parent = port;
@@ -1097,6 +1097,9 @@ static void drm_dp_add_port(struct drm_dp_mst_branch *mstb, build_mst_prop_path(port, mstb, proppath); port->connector = (*mstb->mgr->cbs->add_connector)(mstb->mgr, port, proppath);
+ if (port->mstb) { + port->mstb->conn_base_id = port->connector->base.id; + } if (port->port_num >= 8) port->cached_edid = drm_get_edid(port->connector, &port->aux.ddc); } @@ -1849,7 +1852,7 @@ int drm_dp_mst_topology_mgr_set_mst(struct drm_dp_mst_topology_mgr *mgr, bool ms mgr->avail_slots = mgr->total_slots;
/* add initial branch device at LCT 1 */ - mstb = drm_dp_add_mst_branch_device(1, NULL); + mstb = drm_dp_add_mst_branch_device(1, NULL, mgr->conn_base_id); if (mstb == NULL) { ret = -ENOMEM; goto out_unlock; @@ -2216,6 +2219,25 @@ struct edid *drm_dp_mst_get_edid(struct drm_connector *connector, struct drm_dp_ } EXPORT_SYMBOL(drm_dp_mst_get_edid);
+int drm_dp_mst_get_base_id(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) +{ + int val; + /* we need to search for the port in the mgr in case its gone */ + port = drm_dp_get_validated_port_ref(mgr, port); + if (!port) + return 0; + + if (!port->parent) { + drm_dp_put_port(port); + return 0; + } + + val = port->parent->conn_base_id; + drm_dp_put_port(port); + return val; +} +EXPORT_SYMBOL(drm_dp_mst_get_base_id); /** * drm_dp_find_vcpi_slots() - find slots for this PBN value * @mgr: manager to use diff --git a/include/drm/drm_dp_mst_helper.h b/include/drm/drm_dp_mst_helper.h index ee6fbad..e28d6763 100644 --- a/include/drm/drm_dp_mst_helper.h +++ b/include/drm/drm_dp_mst_helper.h @@ -131,6 +131,8 @@ struct drm_dp_mst_branch { struct drm_dp_sideband_msg_tx *tx_slots[2]; int last_seqno; bool link_address_sent; + + int conn_base_id; };
@@ -480,6 +482,8 @@ enum drm_connector_status drm_dp_mst_detect_port(struct drm_connector *connector
struct edid *drm_dp_mst_get_edid(struct drm_connector *connector, struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port);
+int drm_dp_mst_get_base_id(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port);
int drm_dp_calc_pbn_mode(int clock, int bpp);
From: Dave Airlie airlied@redhat.com
Using the tiling info attempt to set a mode across two crtcs --- drivers/gpu/drm/drm_crtc.c | 100 +++++++++++++++++++++++++++++++++- drivers/gpu/drm/drm_displayid.c | 13 ++++- drivers/gpu/drm/drm_dp_mst_topology.c | 18 +++++- drivers/gpu/drm/drm_edid.c | 2 + drivers/gpu/drm/drm_probe_helper.c | 2 +- drivers/gpu/drm/i915/intel_modes.c | 1 + include/drm/drm_crtc.h | 8 +++ 7 files changed, 140 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 90e7730..99fa259 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -2506,6 +2506,93 @@ int drm_crtc_check_viewport(const struct drm_crtc *crtc, } EXPORT_SYMBOL(drm_crtc_check_viewport);
+/* tiled variants */ +static int drm_mode_setcrtc_tiled(struct drm_mode_set *orig_set) +{ + struct drm_device *dev = orig_set->crtc->dev; + struct drm_mode_set set[2]; + struct drm_crtc *crtc2, *pick_crtc = NULL; + struct drm_connector *connector, *pick_conn[2]; + struct drm_display_mode *cur_mode, *pick_modes[2]; + int ret; + + /* first up we need to find another crtc to use */ + list_for_each_entry(crtc2, &dev->mode_config.crtc_list, head) { + if (crtc2 == orig_set->crtc) + continue; + if (crtc2->enabled) + continue; + pick_crtc = crtc2; + break; + } + + if (pick_crtc == NULL) { + DRM_DEBUG_KMS("unable to located second CRTC for tiling\n"); + return -EINVAL; + } + + pick_conn[0] = orig_set->connectors[0]; + pick_conn[1] = NULL; + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (!connector->has_tile) + continue; + + if (connector == pick_conn[0]) + continue; + + if (connector->tile_group_id != pick_conn[0]->tile_group_id) + continue; + + pick_conn[1] = connector; + } + + DRM_DEBUG_KMS("picked connectors %x and %x from tgid %d\n", pick_conn[0]->base.id, + pick_conn[1]->base.id, pick_conn[0]->tile_group_id); + if (pick_conn[1] == NULL) { + DRM_DEBUG_KMS("unable to located second connector for tiling %d\n", pick_conn[0]->tile_group_id); + return -EINVAL; + } + + pick_modes[0] = pick_modes[1] = NULL; + list_for_each_entry(cur_mode, &pick_conn[0]->modes, head) { + DRM_DEBUG_KMS("trying %d %d\n", cur_mode->hdisplay, cur_mode->vdisplay); + if (cur_mode->hdisplay == pick_conn[1]->tile_h_size + 1 && + cur_mode->vdisplay == pick_conn[1]->tile_v_size + 1) { + pick_modes[0] = pick_modes[1] = cur_mode; + break; + } + } + if (pick_modes[0] == NULL) { + DRM_DEBUG_KMS("unable to locate second mode for tiling %d %d\n", pick_conn[1]->tile_h_size, pick_conn[1]->tile_v_size); + return -EINVAL; + } + + set[0].fb = set[1].fb = orig_set->fb; + + set[0].crtc = orig_set->crtc; + set[1].crtc = pick_crtc; + + set[0].connectors = &pick_conn[0]; + set[0].num_connectors = 1; + + set[1].connectors = &pick_conn[1]; + set[1].num_connectors = 1; + + set[0].x = orig_set->x; + set[0].y = orig_set->y; + set[1].x = orig_set->x + ((pick_conn[1]->tile_h_loc == 1) ? pick_conn[0]->tile_h_size + 1 : 0); + set[1].y = orig_set->y + ((pick_conn[1]->tile_v_loc == 1) ? pick_conn[0]->tile_v_size + 1 : 0); + + /* find a mode to use on each head */ + set[0].mode = pick_modes[0]; + set[1].mode = pick_modes[1]; + + ret = drm_mode_set_config_internal(&set[0]); + + ret = drm_mode_set_config_internal(&set[1]); + + return ret; +} /** * drm_mode_setcrtc - set CRTC configuration * @dev: drm device for the ioctl @@ -2532,6 +2619,7 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data, uint32_t __user *set_connectors_ptr; int ret; int i; + int num_tiles = 1;
if (!drm_core_check_feature(dev, DRIVER_MODESET)) return -EINVAL; @@ -2640,6 +2728,12 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data, connector->base.id, connector->name);
+ if (crtc_req->count_connectors == 1) { + if (connector->has_tile && connector->tile_is_single_monitor) { + if (mode->hdisplay > connector->tile_h_size || mode->vdisplay > connector->tile_v_size) + num_tiles = 2; + } + } connector_set[i] = connector; } } @@ -2651,7 +2745,11 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data, set.connectors = connector_set; set.num_connectors = crtc_req->count_connectors; set.fb = fb; - ret = drm_mode_set_config_internal(&set); + + if (num_tiles > 1) { + ret = drm_mode_setcrtc_tiled(&set); + } else + ret = drm_mode_set_config_internal(&set);
out: if (fb) diff --git a/drivers/gpu/drm/drm_displayid.c b/drivers/gpu/drm/drm_displayid.c index d434da1..63d57a6 100644 --- a/drivers/gpu/drm/drm_displayid.c +++ b/drivers/gpu/drm/drm_displayid.c @@ -53,7 +53,18 @@ int drm_parse_display_id(struct drm_connector *connector, tile_v_loc = (tile->topo[1] & 0xf) | ((tile->topo[2] & 0x3) << 4); tile_h_loc = (tile->topo[1] >> 4) | (((tile->topo[2] >> 2) & 0x3) << 4);
- printk("tile cap %d\n", tile->tile_cap); + connector->has_tile = true; + if (tile->tile_cap & 0x80) + connector->tile_is_single_monitor = true; + + connector->num_h_tile = num_h_tile; + connector->num_v_tile = num_v_tile; + connector->tile_h_loc = tile_h_loc; + connector->tile_v_loc = tile_v_loc; + connector->tile_h_size = w; + connector->tile_v_size = h; + + printk("tile cap 0x%x\n", tile->tile_cap); printk("tile_size %d x %d\n", w, h); printk("topo num tiles %dx%d, location %dx%d\n", num_h_tile, num_v_tile, tile_h_loc, tile_v_loc); diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c index 5d2a08e..1f15d85 100644 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/drm_dp_mst_topology.c @@ -858,6 +858,8 @@ static void drm_dp_destroy_port(struct kref *kref) struct drm_dp_mst_topology_mgr *mgr = port->mgr; if (!port->input) { port->vcpi.num_slots = 0; + + kfree(port->cached_edid); if (port->connector) (*port->mgr->cbs->destroy_connector)(mgr, port->connector); drm_dp_port_teardown_pdt(port, port->pdt); @@ -1100,8 +1102,16 @@ static void drm_dp_add_port(struct drm_dp_mst_branch *mstb, if (port->mstb) { port->mstb->conn_base_id = port->connector->base.id; } - if (port->port_num >= 8) + if (port->port_num >= 8) { port->cached_edid = drm_get_edid(port->connector, &port->aux.ddc); + if (port->cached_edid) { + drm_get_displayid(port->connector, + &port->aux.ddc, + port->cached_edid, + false); + } + + } }
/* put reference to this port */ @@ -2210,10 +2220,16 @@ struct edid *drm_dp_mst_get_edid(struct drm_connector *connector, struct drm_dp_ if (!port) return NULL;
+ if (connector->has_tile && connector->tile_is_single_monitor) { + if (connector->tile_h_loc > 0 || connector->tile_v_loc > 0) { + goto out; + } + } if (port->cached_edid) edid = drm_edid_duplicate(port->cached_edid); else edid = drm_get_edid(connector, &port->aux.ddc); + out: drm_dp_put_port(port); return edid; } diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 3d805aa..94e8a57 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -3881,6 +3881,8 @@ void drm_get_displayid(struct drm_connector *connector, bool secondary) { void *displayid = NULL; + + connector->has_tile = false; displayid = drm_find_displayid_extension(edid); if (!displayid) { return; diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index db7d250..3fa902a 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -139,7 +139,7 @@ static int drm_helper_probe_single_connector_modes_merge_bits(struct drm_connect count = (*connector_funcs->get_modes)(connector); }
- if (count == 0 && connector->status == connector_status_connected) + if (count == 0 && connector->status == connector_status_connected && !connector->has_tile) count = drm_add_modes_noedid(connector, 1024, 768); if (count == 0) goto prune; diff --git a/drivers/gpu/drm/i915/intel_modes.c b/drivers/gpu/drm/i915/intel_modes.c index 35a327e..52948c6 100644 --- a/drivers/gpu/drm/i915/intel_modes.c +++ b/drivers/gpu/drm/i915/intel_modes.c @@ -42,6 +42,7 @@ int intel_connector_update_modes(struct drm_connector *connector, int ret;
drm_mode_connector_update_edid_property(connector, edid); + ret = drm_add_edid_modes(connector, edid); drm_edid_to_eld(connector, edid);
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 1efc007..67c06bd 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -564,6 +564,14 @@ struct drm_connector { unsigned bad_edid_counter;
struct dentry *debugfs_entry; + + /* DisplayID bits */ + bool has_tile; + bool tile_is_single_monitor; + uint32_t tile_group_id; + uint8_t num_h_tile, num_v_tile; + uint8_t tile_h_loc, tile_v_loc; + uint16_t tile_h_size, tile_v_size; };
/**
From: Dave Airlie airlied@redhat.com
This patches the EDID to add the special mode.
TODO make this more generic.
Signed-off-by: Dave Airlie airlied@redhat.com --- drivers/gpu/drm/drm_dp_mst_topology.c | 13 ++++++++++--- drivers/gpu/drm/drm_edid.c | 36 +++++++++++++++++++++++++++++++++++ include/drm/drm_edid.h | 5 ++++- 3 files changed, 50 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c index 1f15d85..08b7140 100644 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/drm_dp_mst_topology.c @@ -31,6 +31,7 @@ #include <drm/drmP.h>
#include <drm/drm_fixed.h> +#include <drm/drm_edid.h>
/** * DOC: dp mst helper @@ -2225,9 +2226,15 @@ struct edid *drm_dp_mst_get_edid(struct drm_connector *connector, struct drm_dp_ goto out; } } - if (port->cached_edid) - edid = drm_edid_duplicate(port->cached_edid); - else + if (port->cached_edid) { + if (connector->has_tile && connector->tile_is_single_monitor) { + edid = drm_patch_edid_detailed_mode(connector->dev, + port->cached_edid, + 3840, 2160, 60); + } else { + edid = drm_edid_duplicate(port->cached_edid); + } + } else edid = drm_get_edid(connector, &port->aux.ddc); out: drm_dp_put_port(port); diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 94e8a57..3ccc2c6 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -3892,3 +3892,39 @@ void drm_get_displayid(struct drm_connector *connector, return; } EXPORT_SYMBOL(drm_get_displayid); + +static void drm_patch_edid_reset_csum(struct edid *edid) +{ + unsigned i, sum = 0; + unsigned char *p = (unsigned char *)edid; + + for (i = 0; i < EDID_LENGTH - 1; i++) + sum += p[i]; + edid->checksum = (0x100 - (sum & 0xff)) & 0xff; +} + +struct edid *drm_patch_edid_detailed_mode(struct drm_device *dev, + struct edid *orig_edid, + int hdisplay, int vdisplay, int vrefresh) +{ + struct edid *edid = drm_edid_duplicate(orig_edid); + struct drm_display_mode *mode = drm_cvt_mode(dev, hdisplay, vdisplay, vrefresh, true, false, false); + + int hblank = mode->htotal - mode->hdisplay; + int vblank = mode->vtotal - mode->vdisplay; + + DRM_DEBUG_KMS("mode->clock is %d, %d\n", mode->clock, cpu_to_le16(mode->clock / 10)); + edid->detailed_timings[1] = edid->detailed_timings[0]; + edid->detailed_timings[0].pixel_clock = cpu_to_le16(mode->clock / 10); + edid->detailed_timings[0].data.pixel_data.hactive_lo = mode->hdisplay & 0xff; + edid->detailed_timings[0].data.pixel_data.hblank_lo = hblank & 0xff; + edid->detailed_timings[0].data.pixel_data.hactive_hblank_hi = (mode->hdisplay >> 4 & 0xf0) | ((hblank >> 8) & 0xf); + edid->detailed_timings[0].data.pixel_data.vactive_lo = mode->vdisplay & 0xff; + edid->detailed_timings[0].data.pixel_data.vblank_lo = vblank & 0xff; + edid->detailed_timings[0].data.pixel_data.vactive_vblank_hi = (mode->vdisplay >> 4 & 0xf0) | ((vblank >> 8) & 0xf); + + drm_patch_edid_reset_csum(edid); + + return edid; +} +EXPORT_SYMBOL(drm_patch_edid_detailed_mode); diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h index 3e87f5a..17eb503 100644 --- a/include/drm/drm_edid.h +++ b/include/drm/drm_edid.h @@ -280,5 +280,8 @@ drm_hdmi_avi_infoframe_from_display_mode(struct hdmi_avi_infoframe *frame, int drm_hdmi_vendor_infoframe_from_display_mode(struct hdmi_vendor_infoframe *frame, const struct drm_display_mode *mode); - +struct drm_device; +struct edid *drm_patch_edid_detailed_mode(struct drm_device *dev, + struct edid *orig_edid, + int hdisplay, int vdisplay, int vrefresh); #endif /* __DRM_EDID_H__ */
From: Dave Airlie airlied@redhat.com
So when userspace asks us to set a mode on a tiled crtc, split it up and find the actual modes and attempt to set them.
Also disable crtcs when no longer in tiled group.
Signed-off-by: Dave Airlie airlied@redhat.com --- drivers/gpu/drm/drm_crtc.c | 51 ++++++++++++++++++++++++++++++++--- drivers/gpu/drm/drm_dp_mst_topology.c | 6 +++++ include/drm/drm_crtc.h | 8 ++++++ 3 files changed, 61 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 99fa259..628f3af 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -764,6 +764,9 @@ int drm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *crtc, crtc->funcs = funcs; crtc->invert_dimensions = false;
+ INIT_LIST_HEAD(&crtc->tile_crtc_list); + crtc->tile_master = NULL; + drm_modeset_lock_all(dev); drm_modeset_lock_init(&crtc->mutex); /* dropped by _unlock_all(): */ @@ -2520,7 +2523,7 @@ static int drm_mode_setcrtc_tiled(struct drm_mode_set *orig_set) list_for_each_entry(crtc2, &dev->mode_config.crtc_list, head) { if (crtc2 == orig_set->crtc) continue; - if (crtc2->enabled) + if (crtc2->enabled && !crtc2->tile_master) continue; pick_crtc = crtc2; break; @@ -2583,14 +2586,26 @@ static int drm_mode_setcrtc_tiled(struct drm_mode_set *orig_set) set[1].x = orig_set->x + ((pick_conn[1]->tile_h_loc == 1) ? pick_conn[0]->tile_h_size + 1 : 0); set[1].y = orig_set->y + ((pick_conn[1]->tile_v_loc == 1) ? pick_conn[0]->tile_v_size + 1 : 0);
+ if (set[1].crtc->tile_master) { + list_del(&set[1].crtc->tile); + set[1].crtc->tile_master = NULL; + } + list_add_tail(&set[1].crtc->tile, &set[0].crtc->tile_crtc_list); + set[1].crtc->tile_master = set[0].crtc; /* find a mode to use on each head */ set[0].mode = pick_modes[0]; set[1].mode = pick_modes[1];
ret = drm_mode_set_config_internal(&set[0]);
- ret = drm_mode_set_config_internal(&set[1]); + if (!ret) { + ret = drm_mode_set_config_internal(&set[1]); + }
+ if (ret) { + set[1].crtc->tile_master = NULL; + list_del(&set[1].crtc->tile); + } return ret; } /** @@ -2637,6 +2652,15 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data, } DRM_DEBUG_KMS("[CRTC:%d]\n", crtc->base.id);
+ if (crtc->tile_master) { + if (crtc_req->mode_valid) + ret = -EBUSY; + else + ret = 0; + DRM_DEBUG_KMS("[CRTC:%d] refused due to tile %d\n", crtc->base.id, ret); + goto out; + } + if (crtc_req->mode_valid) { /* If we have a mode we need a framebuffer. */ /* If we pass -1, set the mode with the currently bound fb */ @@ -2748,9 +2772,25 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data,
if (num_tiles > 1) { ret = drm_mode_setcrtc_tiled(&set); - } else - ret = drm_mode_set_config_internal(&set); + } else { + if (!list_empty(&crtc->tile_crtc_list)) { + struct drm_crtc *tile_crtc, *t; + + list_for_each_entry_safe(tile_crtc, t, &crtc->tile_crtc_list, tile) { + struct drm_mode_set set2; + + tile_crtc->tile_master = NULL; + list_del(&tile_crtc->tile);
+ DRM_DEBUG_KMS("disabling crtc %p due to no longer needing tiling %p\n", tile_crtc, tile_crtc->primary); + memset(&set2, 0, sizeof(struct drm_mode_set)); + set2.crtc = tile_crtc; + set2.fb = NULL; + ret = drm_mode_set_config_internal(&set2); + } + } + ret = drm_mode_set_config_internal(&set); + } out: if (fb) drm_framebuffer_unreference(fb); @@ -4226,6 +4266,9 @@ static int drm_mode_connector_set_obj_prop(struct drm_mode_object *obj, int ret = -EINVAL; struct drm_connector *connector = obj_to_connector(obj);
+ if (connector->has_tile && connector->tile_is_single_monitor && + (connector->tile_h_loc || connector->tile_v_loc)) + return 0; /* Do DPMS ourselves */ if (property == connector->dev->mode_config.dpms_property) { if (connector->funcs->dpms) diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c index 08b7140..ca5eee6 100644 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/drm_dp_mst_topology.c @@ -2190,6 +2190,12 @@ enum drm_connector_status drm_dp_mst_detect_port(struct drm_connector *connector if (port->port_num >= 8 && !port->cached_edid) { port->cached_edid = drm_get_edid(connector, &port->aux.ddc); } + + if (connector->has_tile && connector->tile_group_id == 0) + connector->tile_group_id = port->parent->conn_base_id; + if (connector->has_tile && (connector->tile_h_loc || connector->tile_v_loc)) + status = connector_status_disconnected; + break; case DP_PEER_DEVICE_DP_LEGACY_CONV: if (port->ldps) diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 67c06bd..6041acd 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -375,6 +375,14 @@ struct drm_crtc { void *helper_private;
struct drm_object_properties properties; + + /* crtcs this one is using for tiling */ + struct list_head tile_crtc_list; + + /* tile list entry */ + struct list_head tile; + + struct drm_crtc *tile_master; };
From: Dave Airlie airlied@redhat.com
This is probably not the greatest idea in the world, but if userspace does a modesetting sequences
initial state : crtc 0 -> eDP-1 modeset : crtc 1 -> DP-4 (dual crtc) we have to steal crtc 2 for DP-3 modeset : crtc 2 -> eDP-1
we are kind off stuck, so when we see this, we back up the crtc configuration, proceed with the userspace modeset, then do the second modeset on the released crtc 0.
Signed-off-by: Dave Airlie airlied@redhat.com --- drivers/gpu/drm/drm_crtc.c | 107 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 628f3af..e30518b 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -2509,6 +2509,91 @@ int drm_crtc_check_viewport(const struct drm_crtc *crtc, } EXPORT_SYMBOL(drm_crtc_check_viewport);
+static int drm_mode_get_crtc_set(struct drm_crtc *crtc, struct drm_mode_set *backup_set) +{ + struct drm_device *dev = crtc->dev; + struct drm_display_mode *mode; + struct drm_connector *connector; + int num_connectors = 0; + int i; + + backup_set->crtc = crtc; + backup_set->x = crtc->x; + backup_set->y = crtc->y; + backup_set->fb = crtc->primary->fb; + + mode = drm_mode_create(dev); + if (!mode) { + return -ENOMEM; + } + + *mode = crtc->mode; + backup_set->mode = mode; + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (!connector->encoder) + continue; + if (!connector->encoder->crtc) + continue; + + if (connector->encoder->crtc == crtc) + num_connectors++; + } + + backup_set->connectors = kmalloc(num_connectors * sizeof(struct drm_connector *), GFP_KERNEL); + if (!backup_set->connectors) { + drm_mode_destroy(dev, mode); + return -ENOMEM; + } + + i = 0; + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (!connector->encoder) + continue; + if (!connector->encoder->crtc) + continue; + + if (connector->encoder->crtc == crtc) + backup_set->connectors[i++] = connector; + } + + backup_set->num_connectors = i; + return 0; +} + +static int drm_mode_reset_tiled_crtc(struct drm_mode_set *backup_set, + struct drm_crtc *tile_master) +{ + struct drm_crtc *crtc2, *pick_crtc = NULL; + struct drm_device *dev = backup_set->crtc->dev; + int ret; + /* first up we need to find another crtc to use */ + list_for_each_entry(crtc2, &dev->mode_config.crtc_list, head) { + if (crtc2 == backup_set->crtc) + continue; + if (crtc2->enabled && !crtc2->tile_master) + continue; + pick_crtc = crtc2; + break; + } + + if (!pick_crtc) { + DRM_DEBUG_KMS("unable to find backup crtc\n"); + goto out; + + } + + backup_set->crtc = pick_crtc; + + pick_crtc->tile_master = tile_master; + list_add_tail(&pick_crtc->tile, &tile_master->tile_crtc_list); + + ret = drm_mode_set_config_internal(backup_set); +out: + kfree(backup_set->connectors); + return 0; +} + /* tiled variants */ static int drm_mode_setcrtc_tiled(struct drm_mode_set *orig_set) { @@ -2631,6 +2716,9 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data, struct drm_framebuffer *fb = NULL; struct drm_display_mode *mode = NULL; struct drm_mode_set set; + struct drm_mode_set tile_backup_set; + struct drm_crtc *backup_tile_master = NULL; + bool rework_backup = false; uint32_t __user *set_connectors_ptr; int ret; int i; @@ -2653,12 +2741,17 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data, DRM_DEBUG_KMS("[CRTC:%d]\n", crtc->base.id);
if (crtc->tile_master) { - if (crtc_req->mode_valid) - ret = -EBUSY; - else + if (!crtc_req->mode_valid) { ret = 0; - DRM_DEBUG_KMS("[CRTC:%d] refused due to tile %d\n", crtc->base.id, ret); - goto out; + goto out; + } + + drm_mode_get_crtc_set(crtc, &tile_backup_set); + DRM_DEBUG_KMS("[CRTC:%d] backing up tiling\n", crtc->base.id); + rework_backup = true; + backup_tile_master = crtc->tile_master; + crtc->tile_master = false; + list_del(&crtc->tile); }
if (crtc_req->mode_valid) { @@ -2791,6 +2884,10 @@ int drm_mode_setcrtc(struct drm_device *dev, void *data, } ret = drm_mode_set_config_internal(&set); } + + if (rework_backup) { + drm_mode_reset_tiled_crtc(&tile_backup_set, backup_tile_master); + } out: if (fb) drm_framebuffer_unreference(fb);
From: Dave Airlie airlied@redhat.com
This is OMG bad, but in order to get semantics that don't defeat userspace we have to wait for both flips to complete.
So rewrite the event handling code to make sure we wait for all crtcs in the tile group to complete flipping.
The main problem otherwise is userspace does
add_fb ioctl(PAGE_FLIP, crtc, fb) wait_for_event --> page_flip event rm_fb
This rm_fb will forcefully nuke the framebuffer from all crtcs that are currently running, however that causes bad things to happen like the crtc gets turned off. So we need to wait before sending the event so userspace doesn't do this. Otherwise gnome-shell turns off half the display after 5-10 frames.
Signed-off-by: Dave Airlie airlied@redhat.com --- drivers/gpu/drm/drm_crtc.c | 31 ++++++++++++++++++++++++++++++- drivers/gpu/drm/drm_irq.c | 7 ++++++- drivers/gpu/drm/i915/intel_display.c | 12 ++++++++---- include/drm/drmP.h | 2 ++ 4 files changed, 46 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index e30518b..dd0649a0 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -4771,7 +4771,7 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev, struct drm_pending_vblank_event *e = NULL; unsigned long flags; int ret = -EINVAL; - + int crtc_count = 1; if (page_flip->flags & ~DRM_MODE_PAGE_FLIP_FLAGS || page_flip->reserved != 0) return -EINVAL; @@ -4833,14 +4833,43 @@ int drm_mode_page_flip_ioctl(struct drm_device *dev, e->event.base.type = DRM_EVENT_FLIP_COMPLETE; e->event.base.length = sizeof e->event; e->event.user_data = page_flip->user_data; + e->master = crtc; e->base.event = &e->event.base; e->base.file_priv = file_priv; e->base.destroy = (void (*) (struct drm_pending_event *)) kfree; }
+ if (!list_empty(&crtc->tile_crtc_list)) { + struct drm_crtc *tile; + + list_for_each_entry(tile, &crtc->tile_crtc_list, tile) { + crtc_count++; + } + if (e) + e->crtc_count = crtc_count; + list_for_each_entry(tile, &crtc->tile_crtc_list, tile) { + struct drm_framebuffer *tile_fb = fb; + + drm_framebuffer_reference(fb); + old_fb = tile->primary->fb; + + ret = tile->funcs->page_flip(tile, fb, e, page_flip->flags); + if (ret) { + old_fb = NULL; + } else { + tile_fb = NULL; + } + if (tile_fb) + drm_framebuffer_unreference(tile_fb); + if (old_fb) + drm_framebuffer_unreference(old_fb); + } + } old_fb = crtc->primary->fb; + ret = crtc->funcs->page_flip(crtc, fb, e, page_flip->flags); + if (ret) { if (page_flip->flags & DRM_MODE_PAGE_FLIP_EVENT) { spin_lock_irqsave(&dev->event_lock, flags); diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index 08ba120..9eee6e9 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -783,10 +783,15 @@ static void send_vblank_event(struct drm_device *dev, * Caller must hold event lock. */ void drm_send_vblank_event(struct drm_device *dev, int crtc, - struct drm_pending_vblank_event *e) + struct drm_pending_vblank_event *e) { struct timeval now; unsigned int seq; + + e->crtc_count--; + if (e->crtc_count > 0) + return; + if (crtc >= 0) { seq = drm_vblank_count_and_time(dev, crtc, &now); } else { diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index d074d70..11a6ff3 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -9153,8 +9153,10 @@ static void do_intel_finish_page_flip(struct drm_device *dev,
intel_crtc->unpin_work = NULL;
- if (work->event) - drm_send_vblank_event(dev, intel_crtc->pipe, work->event); + if (work->event) { + struct intel_crtc *tmp = to_intel_crtc(work->event->master); + drm_send_vblank_event(dev, tmp->pipe, work->event); + }
drm_crtc_vblank_put(crtc);
@@ -9795,8 +9797,10 @@ free_work: out_hang: intel_crtc_wait_for_pending_flips(crtc); ret = intel_pipe_set_base(crtc, crtc->x, crtc->y, fb); - if (ret == 0 && event) - drm_send_vblank_event(dev, pipe, event); + if (ret == 0 && event) { + struct intel_crtc *tmp = to_intel_crtc(event->master); + drm_send_vblank_event(dev, tmp->pipe, event); + } } return ret; } diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 1968907..c6005f6 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -982,6 +982,8 @@ struct drm_pending_vblank_event { struct drm_pending_event base; int pipe; struct drm_event_vblank event; + int crtc_count; + struct drm_crtc *master; };
struct drm_vblank_crtc {
From: Dave Airlie airlied@redhat.com
This is going to be a bit of a reference counting nightmare,
Since when we moved from one tile to the other, we need to transfer the framebuffer over between them.
Also only universal is tackled here.
Signed-off-by: Dave Airlie airlied@redhat.com --- drivers/gpu/drm/drm_crtc.c | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index dd0649a0..55c9ad7 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -2919,7 +2919,8 @@ out: */ static int drm_mode_cursor_universal(struct drm_crtc *crtc, struct drm_mode_cursor2 *req, - struct drm_file *file_priv) + struct drm_file *file_priv, + bool draw) { struct drm_device *dev = crtc->dev; struct drm_framebuffer *fb = NULL; @@ -2970,7 +2971,7 @@ static int drm_mode_cursor_universal(struct drm_crtc *crtc, crtc_y = crtc->cursor_y; }
- if (fb) { + if (fb && draw) { crtc_w = fb->width; crtc_h = fb->height; src_w = fb->width << 16; @@ -2981,7 +2982,7 @@ static int drm_mode_cursor_universal(struct drm_crtc *crtc, * setplane_internal will take care of deref'ing either the old or new * framebuffer depending on success. */ - ret = setplane_internal(crtc->cursor, crtc, fb, + ret = setplane_internal(crtc->cursor, crtc, draw ? fb : NULL, crtc_x, crtc_y, crtc_w, crtc_h, 0, 0, src_w, src_h);
@@ -3017,8 +3018,30 @@ static int drm_mode_cursor_common(struct drm_device *dev, * If this crtc has a universal cursor plane, call that plane's update * handler rather than using legacy cursor handlers. */ - if (crtc->cursor) - return drm_mode_cursor_universal(crtc, req, file_priv); + if (crtc->cursor) { + struct drm_mode_cursor2 req2; + struct drm_crtc *tile_crtc; + list_for_each_entry(tile_crtc, &crtc->tile_crtc_list, tile) { + req2 = *req; + + /* TODO broken for > 2 tiles */ + if (req->flags & DRM_MODE_CURSOR_MOVE) { + bool do_move = false; + if (req->x > crtc->mode.hdisplay) { + do_move = true; + req2.x = req->x - crtc->mode.hdisplay; + } + if (req->y > crtc->mode.vdisplay) { + do_move = true; + req2.y = req->y - crtc->mode.vdisplay; + } + if (do_move) + ret = drm_mode_cursor_universal(tile_crtc, &req2, file_priv, true); + } else + ret = drm_mode_cursor_universal(tile_crtc, &req2, file_priv, true); + } + return drm_mode_cursor_universal(crtc, req, file_priv, true); + }
drm_modeset_lock(&crtc->mutex, NULL); if (req->flags & DRM_MODE_CURSOR_BO) {
On Tue, Sep 09, 2014 at 04:28:05PM +1000, Dave Airlie wrote:
There's also dual-channel dsi and similar stuff which is the same. At least on some platforms where you need 2 crtcs to drive both channels.
So thinking about this some more I see piles of issues:
- It obviously looks and works fairly badly without atomic. And it's not just the crtc stealing, but we also need to steal planes. Which isn't a problem on i915, but on SoC platforms there's often just a set of universal planes for all crtcs, and if we also want to fix the dual channel dsi mess with this those matter.
- It will be a lot of work to virtualize the crtcs properly. You ducttape over the pageflip issues, but there's a lot more:
We need to consistently pick the leading crtc for pageflips and timestamps since otherwise the timestamps will be all over the place. Which will upset timing applications and result in video judder.
We need to virtualize all the planes which runs into all the same issues as virtualizing crtcs: We might run out of them when userspace doesn't expect it, or we might not even be able to seamlessly stitch them together over the two crtcs. E.g. older intel chips only had 1 special purpose video overlay shared between all crtcs, so you just can't virtualize that one for 2 crtcs.
There's probably more I've forgotten, but in any case a proper compositor already knows how to deal with all this. Second-guessing the compositor in the kernel is imo not the right approach and will result in major design headaches going forward.
So yeah this approach has a lot of appeal since faking it means existing userspace will keep on working. But I also think the troubles for non-X userspace (which actually wants to use planes properly) isn't worth it. And also for simple X compositors the illusion falls appart quickly with the crtc stealing and timestamp troubles for pageflips/vblanks. So let's look at the differnt issues with the split screen stuff again, and I think most are fairly easy to solve:
- fbdev configuration: We can just move the tiling detection into the fbdev helpers and it should resolve a lot of the troubles since fbdev emulation can explicitly assign crtcs.
- X initial configuration: Decent drivers (sna does it, dunno about the others) already goes to great lengths to faithfully inherit the kernel's setup. Ofc there's stupid xdm which club the driver/kernel right away with a modeset, but imo not trusting the initial setup the kernel has done is a bug. Both from a fastboot perspective and from a "users don't want to spec quirks in 3 billion different config places if kernel cmdline works well enough" pov.
- X compositor real screen size detection: I guess there's no way around to add a new "real screen" rectangles on top of the Xrandr crtc rects. Either the xinerama hack or something new in Xrandr, and then also teaching all compositors about it. Xinerama might fare better since some compositors already use it.
This is obviously the crux since it means we won't have nice 4/5k support from the start until compositors are all updated. But I really don't see a quick way around this.
- Sharing the tile layout detection code: Imo the kernel should expose tile-id, tile-x and tile-y on connectors when they are part of a tiled screen. For dual channel dsi drivers can fill them from DT or vbt or similar. And for dp mst I think your hack is going in the right direction - if we always probe _all_ dp mst slaves on a given master dp port we should be able to fill out the tile properties correctly. Of course that means a bit excessive amounts of reprobing, but that's just a good excuse to finally have decent edid and output state caching in general.
Or we just need to add an atomic probe ioctl which gives you the output state for _all_ connectors at the same time ...
Ok, that's my dump, let's see how much sense it still makes once coffee kicks in ;-)
Cheers, Daniel
dri-devel@lists.freedesktop.org