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 <fhossain@codeaurora.org>
This commit is contained in:
@@ -502,6 +502,90 @@ end:
|
|||||||
return len;
|
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,
|
static ssize_t dp_debug_bw_code_write(struct file *file,
|
||||||
const char __user *user_buff, size_t count, loff_t *ppos)
|
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,
|
.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 = {
|
static const struct file_operations hpd_fops = {
|
||||||
.open = simple_open,
|
.open = simple_open,
|
||||||
.write = dp_debug_write_hpd,
|
.write = dp_debug_write_hpd,
|
||||||
@@ -1787,6 +1881,24 @@ static int dp_debug_init(struct dp_debug *dp_debug)
|
|||||||
goto error_remove_dir;
|
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,
|
file = debugfs_create_file("hpd", 0644, dir,
|
||||||
debug, &hpd_fops);
|
debug, &hpd_fops);
|
||||||
if (IS_ERR_OR_NULL(file)) {
|
if (IS_ERR_OR_NULL(file)) {
|
||||||
|
@@ -16,13 +16,24 @@
|
|||||||
/**
|
/**
|
||||||
* struct dp_debug
|
* struct dp_debug
|
||||||
* @debug_en: specifies whether debug mode enabled
|
* @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
|
* @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
|
* @vdisplay: used to filter out vdisplay value
|
||||||
* @hdisplay: used to filter out hdisplay value
|
* @hdisplay: used to filter out hdisplay value
|
||||||
* @vrefresh: used to filter out vrefresh value
|
* @vrefresh: used to filter out vrefresh value
|
||||||
* @tpg_state: specifies whether tpg feature is enabled
|
* @tpg_state: specifies whether tpg feature is enabled
|
||||||
* @max_pclk_khz: max pclk supported
|
* @max_pclk_khz: max pclk supported
|
||||||
* @force_encryption: enable/disable forced encryption for HDCP 2.2
|
* @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 {
|
struct dp_debug {
|
||||||
bool debug_en;
|
bool debug_en;
|
||||||
@@ -40,6 +51,9 @@ struct dp_debug {
|
|||||||
char hdcp_status[SZ_128];
|
char hdcp_status[SZ_128];
|
||||||
struct dp_mst_connector dp_mst_connector_list;
|
struct dp_mst_connector dp_mst_connector_list;
|
||||||
bool mst_hpd_sim;
|
bool mst_hpd_sim;
|
||||||
|
bool mst_sim_add_con;
|
||||||
|
bool mst_sim_remove_con;
|
||||||
|
int mst_sim_remove_con_id;
|
||||||
u32 mst_port_cnt;
|
u32 mst_port_cnt;
|
||||||
|
|
||||||
u8 *(*get_edid)(struct dp_debug *dp_debug);
|
u8 *(*get_edid)(struct dp_debug *dp_debug);
|
||||||
|
@@ -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) {
|
if (dp->mst.mst_active && dp->mst.cbs.hpd_irq) {
|
||||||
hpd_irq.mst_hpd_sim = dp->debug->mst_hpd_sim;
|
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->mst.cbs.hpd_irq(&dp->dp_display, &hpd_irq);
|
||||||
dp->debug->mst_hpd_sim = false;
|
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);
|
DP_MST_DEBUG("mst_attention_work. mst_active:%d\n", dp->mst.mst_active);
|
||||||
|
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
#include "dp_panel.h"
|
#include "dp_panel.h"
|
||||||
|
|
||||||
#define DP_MST_SIM_MAX_PORTS 2
|
#define DP_MST_SIM_MAX_PORTS 8
|
||||||
|
|
||||||
enum dp_drv_state {
|
enum dp_drv_state {
|
||||||
PM_DEFAULT,
|
PM_DEFAULT,
|
||||||
@@ -24,6 +24,9 @@ struct dp_mst_hpd_info {
|
|||||||
bool mst_hpd_sim;
|
bool mst_hpd_sim;
|
||||||
u32 mst_port_cnt;
|
u32 mst_port_cnt;
|
||||||
u8 *edid;
|
u8 *edid;
|
||||||
|
bool mst_sim_add_con;
|
||||||
|
bool mst_sim_remove_con;
|
||||||
|
int mst_sim_remove_con_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct dp_mst_drm_cbs {
|
struct dp_mst_drm_cbs {
|
||||||
|
@@ -94,9 +94,16 @@ struct dp_mst_sim_port_data {
|
|||||||
u8 num_sdp_stream_sinks;
|
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 {
|
struct dp_mst_sim_mode {
|
||||||
bool mst_state;
|
bool mst_state;
|
||||||
struct edid *edid;
|
struct edid *edid;
|
||||||
|
struct dp_mst_sim_port_edid port_edids[DP_MST_SIM_MAX_PORTS];
|
||||||
struct work_struct probe_work;
|
struct work_struct probe_work;
|
||||||
const struct drm_dp_mst_topology_cbs *cbs;
|
const struct drm_dp_mst_topology_cbs *cbs;
|
||||||
u32 port_cnt;
|
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 *port = container_of(ref,
|
||||||
struct drm_dp_mst_port, kref);
|
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 */
|
/* 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_sim_mode *sim;
|
||||||
struct dp_mst_private *mst;
|
struct dp_mst_private *mst;
|
||||||
struct dp_mst_sim_port_data port_data;
|
struct dp_mst_sim_port_data port_data;
|
||||||
u8 cnt;
|
u8 cnt, i;
|
||||||
|
|
||||||
DP_MST_DEBUG("enter\n");
|
DP_MST_DEBUG("enter\n");
|
||||||
sim = container_of(work, struct dp_mst_sim_mode, probe_work);
|
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_streams = 0;
|
||||||
port_data.num_sdp_stream_sinks = 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++) {
|
for (cnt = 0; cnt < sim->port_cnt; cnt++) {
|
||||||
port_data.port_number = 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);
|
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 = container_of(mgr,
|
||||||
struct dp_mst_private, mst_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(
|
static int dp_mst_sim_topology_mgr_set_mst(
|
||||||
@@ -370,6 +415,83 @@ static int dp_mst_sim_topology_mgr_set_mst(
|
|||||||
return 0;
|
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(
|
static void _dp_mst_get_vcpi_info(
|
||||||
struct drm_dp_mst_topology_mgr *mgr,
|
struct drm_dp_mst_topology_mgr *mgr,
|
||||||
int vcpi, int *start_slot, int *num_slots)
|
int vcpi, int *start_slot, int *num_slots)
|
||||||
@@ -1857,6 +1979,20 @@ static void dp_mst_display_hpd_irq(void *dp_display,
|
|||||||
bool handled;
|
bool handled;
|
||||||
|
|
||||||
if (info->mst_hpd_sim) {
|
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);
|
dp_mst_hotplug(&mst->mst_mgr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user