summaryrefslogtreecommitdiff
path: root/drivers/video/fbdev
diff options
context:
space:
mode:
authorAravind Venkateswaran <aravindh@codeaurora.org>2017-04-07 15:19:46 -0700
committerGerrit - the friendly Code Review server <code-review@localhost>2017-04-13 18:19:17 -0700
commit61ac76cfad61263cf881f89f8908911f6d1eb6c9 (patch)
tree3f57ff87b55f448af5ee506490613d96ed8c30a2 /drivers/video/fbdev
parenta2de6652c1d80c43c9eeffdeb0472cdaf195f665 (diff)
msm: mdss: dp: fix handling of branch devices
A branch device usually notifies a change in downstream connections using the HPD IRQ pulse. Handle this by checking for a change in downstream sink count and appropriately handling EDID reads. It is also possible that the branch device may not have any local EDID. In such cases, when the downstream sink count is zero, do not read EDID. CRs-Fixed: 1112711 Change-Id: I230560c995d7c3b395e37aef5483e5468e1d1dec Signed-off-by: Aravind Venkateswaran <aravindh@codeaurora.org>
Diffstat (limited to 'drivers/video/fbdev')
-rw-r--r--drivers/video/fbdev/msm/mdss_dp.c150
-rw-r--r--drivers/video/fbdev/msm/mdss_dp.h7
-rw-r--r--drivers/video/fbdev/msm/mdss_dp_aux.c44
3 files changed, 166 insertions, 35 deletions
diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c
index bcca85b3cb2c..f067ce07c6db 100644
--- a/drivers/video/fbdev/msm/mdss_dp.c
+++ b/drivers/video/fbdev/msm/mdss_dp.c
@@ -69,6 +69,11 @@ static int mdss_dp_process_phy_test_pattern_request(
static int mdss_dp_send_audio_notification(
struct mdss_dp_drv_pdata *dp, int val);
+static inline void mdss_dp_reset_sink_count(struct mdss_dp_drv_pdata *dp)
+{
+ memset(&dp->sink_count, 0, sizeof(dp->sink_count));
+}
+
static inline void mdss_dp_reset_test_data(struct mdss_dp_drv_pdata *dp)
{
dp->test_data = (const struct dpcd_test_request){ 0 };
@@ -958,6 +963,12 @@ void mdss_dp_config_ctrl(struct mdss_dp_drv_pdata *dp)
mdss_dp_configuration_ctrl(&dp->ctrl_io, data);
}
+static inline void mdss_dp_ack_state(struct mdss_dp_drv_pdata *dp, int val)
+{
+ if (dp && dp->ext_audio_data.intf_ops.notify)
+ dp->ext_audio_data.intf_ops.notify(dp->ext_pdev, val);
+}
+
static int mdss_dp_wait4video_ready(struct mdss_dp_drv_pdata *dp_drv)
{
int ret = 0;
@@ -1615,7 +1626,19 @@ int mdss_dp_on(struct mdss_panel_data *pdata)
dp_drv = container_of(pdata, struct mdss_dp_drv_pdata,
panel_data);
- if (dp_drv->power_on) {
+ /*
+ * If the link already active, then nothing needs to be done here.
+ * However, it is possible that the the power_on flag could be
+ * set to true but we would still need to initialize the DP host.
+ * An example of this use-case is when a multiport dongle is connected
+ * and subsequently the downstream sink is disconnected. This would
+ * only go through the IRQ HPD path where we tear down the link but
+ * the power_on flag remains set to true. When the downstream sink
+ * is subsequently connected again, we need to re-initialize DP
+ * host
+ */
+ if (dp_drv->power_on &&
+ (dp_drv->new_vic && (dp_drv->new_vic == dp_drv->vic))) {
pr_debug("Link already setup, return\n");
return 0;
}
@@ -1633,6 +1656,23 @@ int mdss_dp_on(struct mdss_panel_data *pdata)
return mdss_dp_on_hpd(dp_drv);
}
+static bool mdss_dp_is_ds_bridge(struct mdss_dp_drv_pdata *dp)
+{
+ return dp->dpcd.downstream_port.dfp_present;
+}
+
+static bool mdss_dp_is_ds_bridge_sink_count_zero(struct mdss_dp_drv_pdata *dp)
+{
+ return (mdss_dp_is_ds_bridge(dp) &&
+ (dp->sink_count.count == 0));
+}
+
+static bool mdss_dp_is_ds_bridge_no_local_edid(struct mdss_dp_drv_pdata *dp)
+{
+ return (mdss_dp_is_ds_bridge_sink_count_zero(dp) &&
+ !(dp->dpcd.flags & DPCD_PORT_0_EDID_PRESENTED));
+}
+
static int mdss_dp_off_irq(struct mdss_dp_drv_pdata *dp_drv)
{
if (!dp_drv->power_on) {
@@ -1650,6 +1690,14 @@ static int mdss_dp_off_irq(struct mdss_dp_drv_pdata *dp_drv)
/* Make sure DP mainlink and audio engines are disabled */
wmb();
+ /*
+ * If downstream device is a brige which no longer has any
+ * downstream devices connected to it, then we should reset
+ * the current panel info
+ */
+ if (mdss_dp_is_ds_bridge_sink_count_zero(dp_drv))
+ dp_init_panel_info(dp_drv, HDMI_VFRMT_UNKNOWN);
+
mutex_unlock(&dp_drv->train_mutex);
pr_debug("end\n");
@@ -1678,10 +1726,11 @@ static int mdss_dp_off_hpd(struct mdss_dp_drv_pdata *dp_drv)
mdss_dp_host_deinit(dp_drv);
dp_drv->power_on = false;
- dp_drv->sink_info_read = false;
dp_init_panel_info(dp_drv, HDMI_VFRMT_UNKNOWN);
mdss_dp_reset_test_data(dp_drv);
+ mdss_dp_reset_sink_count(dp_drv);
+ dp_drv->prev_sink_count = dp_drv->sink_count;
mutex_unlock(&dp_drv->train_mutex);
pr_debug("DP off done\n");
@@ -1720,8 +1769,9 @@ static int mdss_dp_send_audio_notification(
if (mdss_dp_sink_audio_supp(dp) || dp->audio_test_req) {
dp->audio_test_req = false;
- pr_debug("sending audio notification\n");
flags |= MSM_EXT_DISP_HPD_AUDIO;
+ pr_debug("sending audio notification = %d, flags = %d\n", val,
+ flags);
if (dp->ext_audio_data.intf_ops.hpd)
ret = dp->ext_audio_data.intf_ops.hpd(dp->ext_pdev,
@@ -1747,6 +1797,7 @@ static int mdss_dp_send_video_notification(
}
flags |= MSM_EXT_DISP_HPD_ASYNC_VIDEO;
+ pr_debug("sending video notification = %d, flags = %d\n", val, flags);
if (dp->ext_audio_data.intf_ops.hpd)
ret = dp->ext_audio_data.intf_ops.hpd(dp->ext_pdev,
@@ -1936,6 +1987,7 @@ static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp,
bool connect;
mutex_lock(&dp->pd_msg_mutex);
+ pr_debug("beginning notification\n");
if (status == dp->hpd_notification_status) {
pr_debug("No change in status %s --> %s\n",
mdss_dp_notification_status_to_string(status),
@@ -1952,9 +2004,7 @@ static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp,
connect = true;
break;
case NOTIFY_CONNECT:
- if ((dp->hpd_notification_status == NOTIFY_CONNECT_IRQ_HPD) ||
- (dp->hpd_notification_status ==
- NOTIFY_DISCONNECT_IRQ_HPD))
+ if (dp->hpd_notification_status == NOTIFY_CONNECT_IRQ_HPD)
goto invalid_request;
notify = true;
connect = true;
@@ -2037,9 +2087,6 @@ static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp)
int ret;
u32 max_pclk_khz;
- if (dp->sink_info_read)
- return 0;
-
pr_debug("start\n");
ret = mdss_dp_dpcd_cap_read(dp);
@@ -2052,8 +2099,25 @@ static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp)
*/
pr_err("dpcd read failed, set failsafe parameters\n");
mdss_dp_set_default_link_parameters(dp);
+ goto read_edid;
}
+ /*
+ * When connected to a multiport adaptor which does not have a
+ * local EDID present, do not attempt to read the EDID.
+ * When connected to a multiport adaptor with no downstream device
+ * connected to it, do not attempt to read the EDID. It is possible
+ * that the adaptor may advertise the presence of local EDID, but it
+ * is not guaranteed to work.
+ */
+ if (mdss_dp_is_ds_bridge_sink_count_zero(dp)) {
+ if (mdss_dp_is_ds_bridge_no_local_edid(dp))
+ pr_debug("No local EDID present on DS branch device\n");
+ pr_info("no downstream devices, skip client notification\n");
+ goto end;
+ }
+
+read_edid:
ret = mdss_dp_edid_read(dp);
if (ret) {
pr_err("edid read error, setting default resolution\n");
@@ -2070,8 +2134,6 @@ static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp)
goto notify;
}
- dp->sink_info_read = true;
-
notify:
if (ret) {
/* set failsafe parameters */
@@ -2098,7 +2160,6 @@ notify:
end:
pr_debug("end\n");
return ret;
-
}
static int mdss_dp_check_params(struct mdss_dp_drv_pdata *dp, void *arg)
@@ -3094,6 +3155,7 @@ static int mdss_retrieve_dp_ctrl_resources(struct platform_device *pdev,
static void mdss_dp_video_ready(struct mdss_dp_drv_pdata *dp)
{
pr_debug("dp_video_ready\n");
+ mdss_dp_ack_state(dp, true);
complete(&dp->video_comp);
}
@@ -3644,10 +3706,46 @@ static int mdss_dp_process_audio_pattern_request(struct mdss_dp_drv_pdata *dp)
static int mdss_dp_process_downstream_port_status_change(
struct mdss_dp_drv_pdata *dp)
{
- if (!mdss_dp_is_downstream_port_status_changed(dp))
+ bool ds_status_changed = false;
+
+ if (mdss_dp_is_downstream_port_status_changed(dp)) {
+ pr_debug("downstream port status changed\n");
+ ds_status_changed = true;
+ }
+
+ /*
+ * Ideally sink should update the downstream port status changed
+ * whenever it updates the downstream sink count. However, it is
+ * possible that only the sink count is updated without setting
+ * the downstream port status changed bit.
+ */
+ if (dp->sink_count.count != dp->prev_sink_count.count) {
+ pr_debug("downstream sink count changed from %d --> %d\n",
+ dp->prev_sink_count.count, dp->sink_count.count);
+ ds_status_changed = true;
+ }
+
+ if (!ds_status_changed)
return -EINVAL;
- return mdss_dp_edid_read(dp);
+ mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT_IRQ_HPD);
+ if (atomic_read(&dp->notification_pending)) {
+ int ret;
+
+ pr_debug("waiting for the disconnect to finish\n");
+ ret = wait_for_completion_timeout(&dp->notification_comp, HZ);
+ if (ret <= 0) {
+ pr_warn("NOTIFY_DISCONNECT_IRQ_HPD timed out\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ if (mdss_dp_is_ds_bridge_sink_count_zero(dp)) {
+ pr_debug("sink count is zero, nothing to do\n");
+ return 0;
+ }
+
+ return mdss_dp_process_hpd_high(dp);
}
static bool mdss_dp_video_pattern_test_lt_needed(struct mdss_dp_drv_pdata *dp)
@@ -3745,19 +3843,19 @@ static int mdss_dp_process_hpd_irq_high(struct mdss_dp_drv_pdata *dp)
mdss_dp_aux_parse_sink_status_field(dp);
- ret = mdss_dp_process_link_training_request(dp);
+ ret = mdss_dp_process_downstream_port_status_change(dp);
if (!ret)
goto exit;
- ret = mdss_dp_process_phy_test_pattern_request(dp);
+ ret = mdss_dp_process_link_training_request(dp);
if (!ret)
goto exit;
- ret = mdss_dp_process_link_status_update(dp);
+ ret = mdss_dp_process_phy_test_pattern_request(dp);
if (!ret)
goto exit;
- ret = mdss_dp_process_downstream_port_status_change(dp);
+ ret = mdss_dp_process_link_status_update(dp);
if (!ret)
goto exit;
@@ -3770,7 +3868,6 @@ static int mdss_dp_process_hpd_irq_high(struct mdss_dp_drv_pdata *dp)
goto exit;
pr_debug("done\n");
-
exit:
dp->hpd_irq_on = false;
return ret;
@@ -3864,11 +3961,21 @@ static void mdss_dp_process_attention(struct mdss_dp_drv_pdata *dp_drv)
if (!dp_drv->alt_mode.dp_status.hpd_high) {
pr_debug("Attention: HPD low\n");
+ if (!dp_drv->power_on) {
+ pr_debug("HPD already low\n");
+ return;
+ }
+
if (dp_is_hdcp_enabled(dp_drv) && dp_drv->hdcp.ops->off) {
cancel_delayed_work(&dp_drv->hdcp_cb_work);
dp_drv->hdcp.ops->off(dp_drv->hdcp.data);
}
+ /*
+ * Reset the sink count before nofifying clients since HPD Low
+ * indicates that the downstream device has been disconnected.
+ */
+ mdss_dp_reset_sink_count(dp_drv);
mdss_dp_notify_clients(dp_drv, NOTIFY_DISCONNECT);
pr_debug("Attention: Notified clients\n");
@@ -3896,6 +4003,11 @@ static void mdss_dp_process_attention(struct mdss_dp_drv_pdata *dp_drv)
pr_debug("Attention: HPD high\n");
+ if (dp_drv->power_on) {
+ pr_debug("HPD high processed already\n");
+ return;
+ }
+
dp_drv->alt_mode.current_state |= DP_STATUS_DONE;
if (dp_drv->alt_mode.current_state & DP_CONFIGURE_DONE) {
diff --git a/drivers/video/fbdev/msm/mdss_dp.h b/drivers/video/fbdev/msm/mdss_dp.h
index 2c10791faff4..b01516847a7e 100644
--- a/drivers/video/fbdev/msm/mdss_dp.h
+++ b/drivers/video/fbdev/msm/mdss_dp.h
@@ -186,6 +186,7 @@ struct dp_alt_mode {
#define DPCD_MAX_DOWNSPREAD_0_5 BIT(2)
#define DPCD_NO_AUX_HANDSHAKE BIT(3)
#define DPCD_PORT_0_EDID_PRESENTED BIT(4)
+#define DPCD_PORT_1_EDID_PRESENTED BIT(5)
/* event */
#define EV_EDP_AUX_SETUP BIT(0)
@@ -239,6 +240,8 @@ struct downstream_port_config {
bool oui_support;
};
+#define DP_MAX_DS_PORT_COUNT 2
+
struct dpcd_cap {
char major;
char minor;
@@ -249,7 +252,7 @@ struct dpcd_cap {
char enhanced_frame;
u32 max_link_rate; /* 162, 270 and 540 Mb, divided by 10 */
u32 flags;
- u32 rx_port0_buf_size;
+ u32 rx_port_buf_size[DP_MAX_DS_PORT_COUNT];
u32 training_read_interval;/* us */
struct downstream_port_config downstream_port;
};
@@ -449,7 +452,6 @@ struct mdss_dp_drv_pdata {
bool core_clks_on;
bool link_clks_on;
bool power_on;
- bool sink_info_read;
u32 suspend_vic;
bool hpd;
bool psm_enabled;
@@ -563,6 +565,7 @@ struct mdss_dp_drv_pdata {
struct dpcd_test_request test_data;
struct dpcd_sink_count sink_count;
+ struct dpcd_sink_count prev_sink_count;
struct list_head attention_head;
};
diff --git a/drivers/video/fbdev/msm/mdss_dp_aux.c b/drivers/video/fbdev/msm/mdss_dp_aux.c
index 8566b1d6985a..17831fde9673 100644
--- a/drivers/video/fbdev/msm/mdss_dp_aux.c
+++ b/drivers/video/fbdev/msm/mdss_dp_aux.c
@@ -750,6 +750,8 @@ int mdss_dp_edid_read(struct mdss_dp_drv_pdata *dp)
return ret;
}
+ memset(dp->edid_buf, 0, dp->edid_buf_size);
+
/**
* Parse the test request vector to see whether there is a
* TEST_EDID_READ test request.
@@ -834,6 +836,10 @@ int mdss_dp_dpcd_cap_read(struct mdss_dp_drv_pdata *ep)
struct dpcd_cap *cap;
struct edp_buf *rp;
int rlen;
+ int i;
+
+ cap = &ep->dpcd;
+ memset(cap, 0, sizeof(*cap));
rlen = dp_aux_read_buf(ep, 0, len, 0);
if (rlen <= 0) {
@@ -848,11 +854,8 @@ int mdss_dp_dpcd_cap_read(struct mdss_dp_drv_pdata *ep)
}
rp = &ep->rxp;
- cap = &ep->dpcd;
bp = rp->data;
- memset(cap, 0, sizeof(*cap));
-
data = *bp++; /* byte 0 */
cap->major = (data >> 4) & 0x0f;
cap->minor = data & 0x0f;
@@ -909,6 +912,11 @@ int mdss_dp_dpcd_cap_read(struct mdss_dp_drv_pdata *ep)
data = *bp++; /* Byte 7: DOWN_STREAM_PORT_COUNT */
cap->downstream_port.dfp_count = data & 0x7;
+ if (cap->downstream_port.dfp_count > DP_MAX_DS_PORT_COUNT) {
+ pr_debug("DS port count %d greater that max (%d) supported\n",
+ cap->downstream_port.dfp_count, DP_MAX_DS_PORT_COUNT);
+ cap->downstream_port.dfp_count = DP_MAX_DS_PORT_COUNT;
+ }
cap->downstream_port.msa_timing_par_ignored = data & BIT(6);
cap->downstream_port.oui_support = data & BIT(7);
pr_debug("dfp_count = %d, msa_timing_par_ignored = %d\n",
@@ -916,17 +924,23 @@ int mdss_dp_dpcd_cap_read(struct mdss_dp_drv_pdata *ep)
cap->downstream_port.msa_timing_par_ignored);
pr_debug("oui_support = %d\n", cap->downstream_port.oui_support);
- data = *bp++; /* byte 8 */
- if (data & BIT(1)) {
- cap->flags |= DPCD_PORT_0_EDID_PRESENTED;
- pr_debug("edid presented\n");
- }
-
- data = *bp++; /* byte 9 */
- cap->rx_port0_buf_size = (data + 1) * 32;
- pr_debug("lane_buf_size=%d\n", cap->rx_port0_buf_size);
+ for (i = 0; i < DP_MAX_DS_PORT_COUNT; i++) {
+ data = *bp++; /* byte 8 + i*2 */
+ pr_debug("parsing capabilities for DS port %d\n", i);
+ if (data & BIT(1)) {
+ if (i == 0)
+ cap->flags |= DPCD_PORT_0_EDID_PRESENTED;
+ else
+ cap->flags |= DPCD_PORT_1_EDID_PRESENTED;
+ pr_debug("local edid present\n");
+ } else {
+ pr_debug("local edid absent\n");
+ }
- bp += 2; /* skip 10, 11 port1 capability */
+ data = *bp++; /* byte 9 + i*2 */
+ cap->rx_port_buf_size[i] = (data + 1) * 32;
+ pr_debug("lane_buf_size=%d\n", cap->rx_port_buf_size[i]);
+ }
data = *bp++; /* byte 12 */
cap->i2c_speed_ctrl = data;
@@ -1258,6 +1272,8 @@ static void dp_sink_parse_sink_count(struct mdss_dp_drv_pdata *ep)
int const param_len = 0x1;
int const sink_count_addr = 0x200;
+ ep->prev_sink_count = ep->sink_count;
+
rlen = dp_aux_read_buf(ep, sink_count_addr, param_len, 0);
if (rlen < param_len) {
pr_err("failed to read sink count\n");
@@ -2363,8 +2379,8 @@ clear:
void mdss_dp_aux_parse_sink_status_field(struct mdss_dp_drv_pdata *ep)
{
dp_sink_parse_sink_count(ep);
- dp_sink_parse_test_request(ep);
mdss_dp_aux_link_status_read(ep, 6);
+ dp_sink_parse_test_request(ep);
}
int mdss_dp_dpcd_status_read(struct mdss_dp_drv_pdata *ep)