From 15bbd57effb26ad3cda932932dfd4a6f142f9098 Mon Sep 17 00:00:00 2001 From: Fuad Hossain Date: Thu, 23 May 2019 18:36:32 -0400 Subject: [PATCH] disp: msm: dp: Improvements to dp mst simulator mode Improve dp mst simulator mode by adding support for up to 8 connectors, the ability to add and remove ports dynamically, and allowing for different EDIDs for each connector. CRs-Fixed: 2459530 Change-Id: I945e3292a7e5150ab7a6bbe0addc4f4f46d58e82 Signed-off-by: Fuad Hossain --- msm/dp/dp_debug.c | 112 ++++++++++++++++++++++++++++++++++ msm/dp/dp_debug.h | 14 +++++ msm/dp/dp_display.c | 6 ++ msm/dp/dp_display.h | 5 +- msm/dp/dp_mst_drm.c | 142 +++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 275 insertions(+), 4 deletions(-) diff --git a/msm/dp/dp_debug.c b/msm/dp/dp_debug.c index 95c09013fedb..9ce359e1a7eb 100644 --- a/msm/dp/dp_debug.c +++ b/msm/dp/dp_debug.c @@ -502,6 +502,90 @@ end: return len; } +static ssize_t dp_debug_write_mst_con_add(struct file *file, + const char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + char buf[SZ_32]; + size_t len = 0; + const int dp_en = BIT(3), hpd_high = BIT(7), hpd_irq = BIT(8); + int vdo = dp_en | hpd_high | hpd_irq; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + /* Leave room for termination char */ + len = min_t(size_t, count, SZ_32 - 1); + if (copy_from_user(buf, user_buff, len)) + goto end; + + debug->dp_debug.mst_hpd_sim = true; + debug->dp_debug.mst_sim_add_con = true; + debug->hpd->simulate_attention(debug->hpd, vdo); +end: + return len; +} + +static ssize_t dp_debug_write_mst_con_remove(struct file *file, + const char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + struct dp_mst_connector *mst_connector; + char buf[SZ_32]; + size_t len = 0; + int con_id = 0; + bool in_list = false; + const int dp_en = BIT(3), hpd_high = BIT(7), hpd_irq = BIT(8); + int vdo = dp_en | hpd_high | hpd_irq; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + /* Leave room for termination char */ + len = min_t(size_t, count, SZ_32 - 1); + if (copy_from_user(buf, user_buff, len)) + goto end; + + buf[len] = '\0'; + + if (sscanf(buf, "%d", &con_id) != 1) { + len = 0; + goto end; + } + + if (!con_id) + goto end; + + /* Verify that the connector id is for a valid mst connector. */ + mutex_lock(&debug->dp_debug.dp_mst_connector_list.lock); + list_for_each_entry(mst_connector, + &debug->dp_debug.dp_mst_connector_list.list, list) { + if (mst_connector->con_id == con_id) { + in_list = true; + break; + } + } + mutex_unlock(&debug->dp_debug.dp_mst_connector_list.lock); + + if (!in_list) { + DRM_ERROR("invalid connector id %u\n", con_id); + goto end; + } + + debug->dp_debug.mst_hpd_sim = true; + debug->dp_debug.mst_sim_remove_con = true; + debug->dp_debug.mst_sim_remove_con_id = con_id; + debug->hpd->simulate_attention(debug->hpd, vdo); +end: + return len; +} + static ssize_t dp_debug_bw_code_write(struct file *file, const char __user *user_buff, size_t count, loff_t *ppos) { @@ -1635,6 +1719,16 @@ static const struct file_operations mst_con_id_fops = { .write = dp_debug_write_mst_con_id, }; +static const struct file_operations mst_con_add_fops = { + .open = simple_open, + .write = dp_debug_write_mst_con_add, +}; + +static const struct file_operations mst_con_remove_fops = { + .open = simple_open, + .write = dp_debug_write_mst_con_remove, +}; + static const struct file_operations hpd_fops = { .open = simple_open, .write = dp_debug_write_hpd, @@ -1787,6 +1881,24 @@ static int dp_debug_init(struct dp_debug *dp_debug) goto error_remove_dir; } + file = debugfs_create_file("mst_con_add", 0644, dir, + debug, &mst_con_add_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DRM_ERROR("[%s] debugfs create mst_con_add failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("mst_con_remove", 0644, dir, + debug, &mst_con_remove_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + DRM_ERROR("[%s] debugfs create mst_con_remove failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + file = debugfs_create_file("hpd", 0644, dir, debug, &hpd_fops); if (IS_ERR_OR_NULL(file)) { diff --git a/msm/dp/dp_debug.h b/msm/dp/dp_debug.h index cb6640f7b275..9243955c74fd 100644 --- a/msm/dp/dp_debug.h +++ b/msm/dp/dp_debug.h @@ -16,13 +16,24 @@ /** * struct dp_debug * @debug_en: specifies whether debug mode enabled + * @sim_mode: specifies whether sim mode enabled + * @psm_enabled: specifies whether psm enabled + * @hdcp_disabled: specifies if hdcp is disabled * @hdcp_wait_sink_sync: used to wait for sink synchronization before HDCP auth + * @aspect_ratio: used to filter out aspect_ratio value * @vdisplay: used to filter out vdisplay value * @hdisplay: used to filter out hdisplay value * @vrefresh: used to filter out vrefresh value * @tpg_state: specifies whether tpg feature is enabled * @max_pclk_khz: max pclk supported * @force_encryption: enable/disable forced encryption for HDCP 2.2 + * @hdcp_status: string holding hdcp status information + * @dp_mst_connector_list: list containing all dp mst connectors + * @mst_hpd_sim: specifies whether simulated hpd enabled + * @mst_sim_add_con: specifies whether new sim connector is to be added + * @mst_sim_remove_con: specifies whether sim connector is to be removed + * @mst_sim_remove_con_id: specifies id of sim connector to be removed + * @mst_port_cnt: number of mst ports to be added during hpd */ struct dp_debug { bool debug_en; @@ -40,6 +51,9 @@ struct dp_debug { char hdcp_status[SZ_128]; struct dp_mst_connector dp_mst_connector_list; bool mst_hpd_sim; + bool mst_sim_add_con; + bool mst_sim_remove_con; + int mst_sim_remove_con_id; u32 mst_port_cnt; u8 *(*get_edid)(struct dp_debug *dp_debug); diff --git a/msm/dp/dp_display.c b/msm/dp/dp_display.c index 369aeda6f171..a2e7042fd9a9 100644 --- a/msm/dp/dp_display.c +++ b/msm/dp/dp_display.c @@ -1026,8 +1026,14 @@ static void dp_display_mst_attention(struct dp_display_private *dp) if (dp->mst.mst_active && dp->mst.cbs.hpd_irq) { hpd_irq.mst_hpd_sim = dp->debug->mst_hpd_sim; + hpd_irq.mst_sim_add_con = dp->debug->mst_sim_add_con; + hpd_irq.mst_sim_remove_con = dp->debug->mst_sim_remove_con; + hpd_irq.mst_sim_remove_con_id = dp->debug->mst_sim_remove_con_id; + hpd_irq.edid = dp->debug->get_edid(dp->debug); dp->mst.cbs.hpd_irq(&dp->dp_display, &hpd_irq); dp->debug->mst_hpd_sim = false; + dp->debug->mst_sim_add_con = false; + dp->debug->mst_sim_remove_con = false; } DP_MST_DEBUG("mst_attention_work. mst_active:%d\n", dp->mst.mst_active); diff --git a/msm/dp/dp_display.h b/msm/dp/dp_display.h index 2597aaf59d60..83e1e99097da 100644 --- a/msm/dp/dp_display.h +++ b/msm/dp/dp_display.h @@ -12,7 +12,7 @@ #include "dp_panel.h" -#define DP_MST_SIM_MAX_PORTS 2 +#define DP_MST_SIM_MAX_PORTS 8 enum dp_drv_state { PM_DEFAULT, @@ -24,6 +24,9 @@ struct dp_mst_hpd_info { bool mst_hpd_sim; u32 mst_port_cnt; u8 *edid; + bool mst_sim_add_con; + bool mst_sim_remove_con; + int mst_sim_remove_con_id; }; struct dp_mst_drm_cbs { diff --git a/msm/dp/dp_mst_drm.c b/msm/dp/dp_mst_drm.c index 4f1cfda73771..e462a44f9039 100644 --- a/msm/dp/dp_mst_drm.c +++ b/msm/dp/dp_mst_drm.c @@ -94,9 +94,16 @@ struct dp_mst_sim_port_data { u8 num_sdp_stream_sinks; }; +struct dp_mst_sim_port_edid { + u8 port_number; + u8 edid[SZ_256]; + bool valid; +}; + struct dp_mst_sim_mode { bool mst_state; struct edid *edid; + struct dp_mst_sim_port_edid port_edids[DP_MST_SIM_MAX_PORTS]; struct work_struct probe_work; const struct drm_dp_mst_topology_cbs *cbs; u32 port_cnt; @@ -156,7 +163,21 @@ static void dp_mst_sim_destroy_port(struct kref *ref) { struct drm_dp_mst_port *port = container_of(ref, struct drm_dp_mst_port, kref); - kfree(port); + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + + if (port->cached_edid) + kfree(port->cached_edid); + + if (port->connector) { + mutex_lock(&mgr->destroy_connector_lock); + kref_get(&port->parent->kref); + list_add(&port->next, &mgr->destroy_connector_list); + mutex_unlock(&mgr->destroy_connector_lock); + schedule_work(&mgr->destroy_connector_work); + return; + } else { + kfree(port); + } } /* DRM DP MST Framework simulator OPs */ @@ -218,7 +239,7 @@ static void dp_mst_sim_link_probe_work(struct work_struct *work) struct dp_mst_sim_mode *sim; struct dp_mst_private *mst; struct dp_mst_sim_port_data port_data; - u8 cnt; + u8 cnt, i; DP_MST_DEBUG("enter\n"); sim = container_of(work, struct dp_mst_sim_mode, probe_work); @@ -233,8 +254,21 @@ static void dp_mst_sim_link_probe_work(struct work_struct *work) port_data.num_sdp_streams = 0; port_data.num_sdp_stream_sinks = 0; + for (i = 0; i < DP_MST_SIM_MAX_PORTS; i++) + sim->port_edids[i].valid = false; + for (cnt = 0; cnt < sim->port_cnt; cnt++) { port_data.port_number = cnt; + + for (i = 0; i < DP_MST_SIM_MAX_PORTS; i++) { + if (sim->port_edids[i].valid) continue; + + sim->port_edids[i].port_number = port_data.port_number; + memcpy(sim->port_edids[i].edid, sim->edid, SZ_256); + sim->port_edids[i].valid = true; + break; + } + dp_mst_sim_add_port(mst, &port_data); } @@ -351,8 +385,19 @@ static struct edid *dp_mst_sim_get_edid(struct drm_connector *connector, { struct dp_mst_private *mst = container_of(mgr, struct dp_mst_private, mst_mgr); + int i; - return drm_edid_duplicate(mst->simulator.edid); + for (i = 0; i < DP_MST_SIM_MAX_PORTS; i++) { + if (mst->simulator.port_edids[i].valid && + mst->simulator.port_edids[i].port_number == + port->port_num) { + return drm_edid_duplicate((struct edid *) + (mst->simulator.port_edids[i].edid)); + } + } + + DRM_ERROR("edid not found for connector %d\n", connector->base.id); + return NULL; } static int dp_mst_sim_topology_mgr_set_mst( @@ -370,6 +415,83 @@ static int dp_mst_sim_topology_mgr_set_mst( return 0; } +static void dp_mst_sim_handle_hpd_irq(void *dp_display, + struct dp_mst_hpd_info *info) +{ + struct dp_display *dp; + struct dp_mst_private *mst; + struct drm_dp_mst_port *port; + struct dp_mst_sim_port_data port_data; + struct drm_dp_mst_branch *mstb; + int i; + bool in_list, port_available; + + dp = dp_display; + mst = dp->dp_mst_prv_info; + + if (info->mst_sim_add_con) { + port_available = false; + for (i = 0; i < DP_MST_SIM_MAX_PORTS; i++) { + if (mst->simulator.port_edids[i].valid) continue; + + port_data.port_number = i; + mst->simulator.port_edids[i].port_number = i; + memcpy(mst->simulator.port_edids[i].edid, info->edid, + SZ_256); + mst->simulator.port_edids[i].valid = true; + port_available = true; + break; + } + + if (!port_available) { + DRM_ERROR("add port failed, limit (%d) reached\n", + DP_MST_SIM_MAX_PORTS); + return; + } + + port_data.input_port = false; + port_data.peer_device_type = DP_PEER_DEVICE_SST_SINK; + port_data.mcs = false; + port_data.ddps = true; + port_data.legacy_device_plug_status = false; + port_data.dpcd_revision = 0; + port_data.num_sdp_streams = 0; + port_data.num_sdp_stream_sinks = 0; + + dp_mst_sim_add_port(mst, &port_data); + } else if (info->mst_sim_remove_con) { + mstb = mst->mst_mgr.mst_primary; + in_list = false; + + mutex_lock(&mst->mst_mgr.lock); + list_for_each_entry(port, + &mstb->ports, next) { + if (port->connector && port->connector->base.id == + info->mst_sim_remove_con_id) { + in_list = true; + list_del(&port->next); + break; + } + } + mutex_unlock(&mst->mst_mgr.lock); + + if (!in_list) { + DRM_ERROR("invalid connector id %d\n", + info->mst_sim_remove_con_id); + return; + } + + for (i = 0; i < DP_MST_SIM_MAX_PORTS; i++) { + if (mst->simulator.port_edids[i].port_number == + port->port_num) { + mst->simulator.port_edids[i].valid = false; + } + } + + kref_put(&port->kref, dp_mst_sim_destroy_port); + } +} + static void _dp_mst_get_vcpi_info( struct drm_dp_mst_topology_mgr *mgr, int vcpi, int *start_slot, int *num_slots) @@ -1857,6 +1979,20 @@ static void dp_mst_display_hpd_irq(void *dp_display, bool handled; if (info->mst_hpd_sim) { + if (info->mst_sim_add_con || info->mst_sim_remove_con) { + dp_mst_sim_handle_hpd_irq(dp_display, info); + + /* + * When removing a connector, hpd_irq -> sim_destroy -> + * destroy_connector_work will be executed in a thread. + * This thread will perform the dp_mst_hotplug at the + * appropriate time. Do not perform hotplug here + * because it may be too early. + */ + if (info->mst_sim_remove_con) + return; + } + dp_mst_hotplug(&mst->mst_mgr); return; }