Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e66fc6f5 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "msm: mdss: dp: fix HDCP 1.x state transitions"

parents ddc8181a 058c973e
Loading
Loading
Loading
Loading
+50 −2
Original line number Diff line number Diff line
@@ -27,7 +27,46 @@ Required properties
- qcom,aux-en-gpio:			Specifies the aux-channel enable gpio.
- qcom,aux-sel-gpio:			Specifies the aux-channel select gpio.
- qcom,usbplug-cc-gpio:			Specifies the usbplug orientation gpio.
- qcom,aux-cfg-settings:		An array that specifies the DP AUX configuration settings.
- qcom,aux-cfg0-settings:		Specifies the DP AUX configuration 0 settings. The first
					entry in this array corresponds to the register offset
					within DP AUX, while the remaining entries indicate the
					programmable values.
- qcom,aux-cfg1-settings:		Specifies the DP AUX configuration 1 settings. The first
					entry in this array corresponds to the register offset
					within DP AUX, while the remaining entries indicate the
					programmable values.
- qcom,aux-cfg2-settings:		Specifies the DP AUX configuration 2 settings. The first
					entry in this array corresponds to the register offset
					within DP AUX, while the remaining entries indicate the
					programmable values.
- qcom,aux-cfg3-settings:		Specifies the DP AUX configuration 3 settings. The first
					entry in this array corresponds to the register offset
					within DP AUX, while the remaining entries indicate the
					programmable values.
- qcom,aux-cfg4-settings:		Specifies the DP AUX configuration 4 settings. The first
					entry in this array corresponds to the register offset
					within DP AUX, while the remaining entries indicate the
					programmable values.
- qcom,aux-cfg5-settings:		Specifies the DP AUX configuration 5 settings. The first
					entry in this array corresponds to the register offset
					within DP AUX, while the remaining entries indicate the
					programmable values.
- qcom,aux-cfg6-settings:		Specifies the DP AUX configuration 6 settings. The first
					entry in this array corresponds to the register offset
					within DP AUX, while the remaining entries indicate the
					programmable values.
- qcom,aux-cfg7-settings:		Specifies the DP AUX configuration 7 settings. The first
					entry in this array corresponds to the register offset
					within DP AUX, while the remaining entries indicate the
					programmable values.
- qcom,aux-cfg8-settings:		Specifies the DP AUX configuration 8 settings. The first
					entry in this array corresponds to the register offset
					within DP AUX, while the remaining entries indicate the
					programmable values.
- qcom,aux-cfg9-settings:		Specifies the DP AUX configuration 9 settings. The first
					entry in this array corresponds to the register offset
					within DP AUX, while the remaining entries indicate the
					programmable values.

Optional properties:
- qcom,<type>-supply-entries:		A node that lists the elements of the supply used by the
@@ -87,7 +126,16 @@ Example:
			"core_aux_clk", "core_cfg_ahb_clk", "ctrl_link_clk",
			"ctrl_link_iface_clk", "ctrl_crypto_clk", "ctrl_pixel_clk";

		qcom,aux-cfg-settings = [00 13 00 10 0a 26 0a 03 8b 03];
		qcom,aux-cfg0-settings = [1c 00];
		qcom,aux-cfg1-settings = [20 13 23 1d];
		qcom,aux-cfg2-settings = [24 00];
		qcom,aux-cfg3-settings = [28 00];
		qcom,aux-cfg4-settings = [2c 0a];
		qcom,aux-cfg5-settings = [30 26];
		qcom,aux-cfg6-settings = [34 0a];
		qcom,aux-cfg7-settings = [38 03];
		qcom,aux-cfg8-settings = [3c bb];
		qcom,aux-cfg9-settings = [40 03];
		qcom,logical2physical-lane-map = [02 03 01 00];
		qcom,phy-register-offset = <0x4>;
		qcom,max-pclk-frequency-khz = <593470>;
+10 −1
Original line number Diff line number Diff line
@@ -502,7 +502,16 @@

		qcom,msm_ext_disp = <&msm_ext_disp>;

		qcom,aux-cfg-settings = [00 13 00 10 0a 26 0a 03 8b 03];
		qcom,aux-cfg0-settings = [1c 00];
		qcom,aux-cfg1-settings = [20 13 23 1d];
		qcom,aux-cfg2-settings = [24 00];
		qcom,aux-cfg3-settings = [28 00];
		qcom,aux-cfg4-settings = [2c 0a];
		qcom,aux-cfg5-settings = [30 26];
		qcom,aux-cfg6-settings = [34 0a];
		qcom,aux-cfg7-settings = [38 03];
		qcom,aux-cfg8-settings = [3c bb];
		qcom,aux-cfg9-settings = [40 03];
		qcom,logical2physical-lane-map = [02 03 01 00];

		qcom,core-supply-entries {
+10 −1
Original line number Diff line number Diff line
@@ -505,7 +505,16 @@

		qcom,msm_ext_disp = <&msm_ext_disp>;

		qcom,aux-cfg-settings = [00 13 00 00 0a 28 0a 03 b7 03];
		qcom,aux-cfg0-settings = [20 00];
		qcom,aux-cfg1-settings = [24 13 23 1d];
		qcom,aux-cfg2-settings = [28 00];
		qcom,aux-cfg3-settings = [2c 00];
		qcom,aux-cfg4-settings = [30 0a];
		qcom,aux-cfg5-settings = [34 28];
		qcom,aux-cfg6-settings = [38 0a];
		qcom,aux-cfg7-settings = [3c 03];
		qcom,aux-cfg8-settings = [40 b7];
		qcom,aux-cfg9-settings = [44 03];
		qcom,logical2physical-lane-map = [00 01 02 03];
		qcom,phy-register-offset = <0x4>;
		qcom,max-pclk-frequency-khz = <300000>;
+294 −81
Original line number Diff line number Diff line
@@ -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 };
@@ -133,22 +138,77 @@ static int mdss_dp_is_clk_prefix(const char *clk_prefix, const char *clk_name)
	return !strncmp(clk_name, clk_prefix, strlen(clk_prefix));
}

static int mdss_dp_parse_prop(struct platform_device *pdev,
			struct mdss_dp_drv_pdata *dp_drv)
static void mdss_dp_reset_phy_config_indices(struct mdss_dp_drv_pdata *dp)
{
	int len = 0, i = 0, rc = 0;
	int i = 0;

	for (i = 0; i < PHY_AUX_CFG_MAX; i++)
		dp->aux_cfg[i].current_index = 0;
}

static void mdss_dp_phy_aux_cfg_reset(struct mdss_dp_drv_pdata *dp)
{
	int i = 0;

	for (i = 0; i < PHY_AUX_CFG_MAX; i++)
		dp->aux_cfg[i] = (const struct mdss_dp_phy_cfg){ 0 };
}

static int mdss_dp_parse_aux_cfg(struct platform_device *pdev,
			struct mdss_dp_drv_pdata *dp)
{
	int len = 0, i = 0, j = 0, config_count = 0;
	const char *data;
	int const minimum_config_count = 1;

	data = of_get_property(pdev->dev.of_node,
		"qcom,aux-cfg-settings", &len);
	if ((!data) || (len != AUX_CFG_LEN)) {
		pr_err("%s:%d, Unable to read DP AUX CFG settings",
			__func__, __LINE__);
	for (i = 0; i < PHY_AUX_CFG_MAX; i++) {
		const char *property = mdss_dp_get_phy_aux_config_property(i);

		data = of_get_property(pdev->dev.of_node, property, &len);
		if (!data) {
			pr_err("Unable to read %s\n", property);
			goto error;
		}

		config_count = len - 1;
		if ((config_count < minimum_config_count) ||
			(config_count > MDSS_DP_MAX_PHY_CFG_VALUE_CNT)) {
			pr_err("Invalid config count (%d) configs for %s\n",
					config_count, property);
			goto error;
		}

		dp->aux_cfg[i].offset = data[0];
		dp->aux_cfg[i].cfg_cnt = config_count;
		pr_debug("%s offset=0x%x, cfg_cnt=%d\n",
				property,
				dp->aux_cfg[i].offset,
				dp->aux_cfg[i].cfg_cnt);
		for (j = 1; j < len; j++) {
			dp->aux_cfg[i].lut[j - 1] = data[j];
			pr_debug("%s lut[%d]=0x%x\n",
					property,
					i,
					dp->aux_cfg[i].lut[j - 1]);
		}
	}

	return 0;

error:
	mdss_dp_phy_aux_cfg_reset(dp);
	return -EINVAL;
}

	for (i = 0; i < len; i++)
		dp_drv->aux_cfg[i] = data[i];
static int mdss_dp_parse_prop(struct platform_device *pdev,
			struct mdss_dp_drv_pdata *dp_drv)
{
	int len = 0, i = 0, rc = 0;
	const char *data;

	rc = mdss_dp_parse_aux_cfg(pdev, dp_drv);
	if (rc)
		return rc;

	data = of_get_property(pdev->dev.of_node,
		"qcom,logical2physical-lane-map", &len);
@@ -958,6 +1018,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;
@@ -1230,12 +1296,6 @@ static int dp_init_panel_info(struct mdss_dp_drv_pdata *dp_drv, u32 vic)
	return 0;
} /* dp_init_panel_info */

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);
}

/**
 * mdss_dp_get_lane_mapping() - returns lane mapping based on given orientation
 * @orientation: usb plug orientation
@@ -1621,15 +1681,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) {
	/*
		 * Acknowledge the connection event if link training has already
		 * been done. This will unblock the external display thread and
		 * allow the driver to progress. For example, in the case of
		 * video test pattern requests, to send the test response and
		 * start transmitting the test pattern.
	 * 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
	 */
		mdss_dp_ack_state(dp_drv, true);
	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;
	}
@@ -1647,6 +1711,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) {
@@ -1664,10 +1745,16 @@ static int mdss_dp_off_irq(struct mdss_dp_drv_pdata *dp_drv)
	/* Make sure DP mainlink and audio engines are disabled */
	wmb();

	mdss_dp_ack_state(dp_drv, false);
	/*
	 * 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);

	complete_all(&dp_drv->irq_comp);
	pr_debug("end\n");

	return 0;
@@ -1694,11 +1781,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_ack_state(dp_drv, false);
	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");

@@ -1737,8 +1824,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,
@@ -1763,7 +1851,8 @@ static int mdss_dp_send_video_notification(
		goto end;
	}

	flags |= MSM_EXT_DISP_HPD_VIDEO;
	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,
@@ -1873,14 +1962,16 @@ static int mdss_dp_host_init(struct mdss_panel_data *pdata)
	mdss_dp_ctrl_reset(&dp_drv->ctrl_io);
	mdss_dp_phy_reset(&dp_drv->ctrl_io);
	mdss_dp_aux_reset(&dp_drv->ctrl_io);
	mdss_dp_aux_set_limits(&dp_drv->ctrl_io);

	mdss_dp_aux_ctrl(&dp_drv->ctrl_io, true);

	pr_debug("Ctrl_hw_rev =0x%x, phy hw_rev =0x%x\n",
	       mdss_dp_get_ctrl_hw_version(&dp_drv->ctrl_io),
	       mdss_dp_get_phy_hw_version(&dp_drv->phy_io));

	mdss_dp_phy_aux_setup(&dp_drv->phy_io, dp_drv->aux_cfg,
			dp_drv->phy_reg_offset);
	mdss_dp_reset_phy_config_indices(dp_drv);
	mdss_dp_phy_aux_setup(dp_drv);

	mdss_dp_irq_enable(dp_drv);
	dp_drv->dp_initialized = true;
@@ -1948,10 +2039,12 @@ static int mdss_dp_host_deinit(struct mdss_dp_drv_pdata *dp)
static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp,
	enum notification_status status)
{
	const int irq_comp_timeout = HZ * 2;
	int ret = 0;
	bool notify = false;
	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),
@@ -1964,39 +2057,40 @@ static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp,
	case NOTIFY_CONNECT_IRQ_HPD:
		if (dp->hpd_notification_status != NOTIFY_DISCONNECT_IRQ_HPD)
			goto invalid_request;
		/* Follow the same programming as for NOTIFY_CONNECT */
		mdss_dp_host_init(&dp->panel_data);
		mdss_dp_send_video_notification(dp, true);
		notify = true;
		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;
		mdss_dp_host_init(&dp->panel_data);
		mdss_dp_send_video_notification(dp, true);
		notify = true;
		connect = true;
		break;
	case NOTIFY_DISCONNECT:
		mdss_dp_send_audio_notification(dp, false);
		mdss_dp_send_video_notification(dp, false);
		/*
		 * Userspace triggers a disconnect event on boot up, this must
		 * not be processed as there was no previously connected sink
		 * device.
		 */
		if (dp->hpd_notification_status == NOTIFY_UNKNOWN)
			goto invalid_request;
		if (dp->hpd_notification_status == NOTIFY_DISCONNECT_IRQ_HPD) {
			/*
			 * user modules already turned off. Need to explicitly
			 * turn off DP core here.
			 */
			mdss_dp_off_hpd(dp);
		} else {
			notify = true;
			connect = false;
		}
		break;
	case NOTIFY_DISCONNECT_IRQ_HPD:
		if (dp->hpd_notification_status == NOTIFY_DISCONNECT)
			goto invalid_request;

		mdss_dp_send_audio_notification(dp, false);
		mdss_dp_send_video_notification(dp, false);
		if (!IS_ERR_VALUE(ret) && ret) {
			reinit_completion(&dp->irq_comp);
			ret = wait_for_completion_timeout(&dp->irq_comp,
					irq_comp_timeout);
			if (ret <= 0) {
				pr_warn("irq_comp timed out\n");
				ret = -EINVAL;
			} else {
				ret = 0;
			}
		}
		notify = true;
		connect = false;
		break;
	default:
		pr_err("Invalid notification status = %d\n", status);
@@ -2004,7 +2098,7 @@ static int mdss_dp_notify_clients(struct mdss_dp_drv_pdata *dp,
		break;
	}

	goto end;
	goto notify;

invalid_request:
	pr_err("Invalid request %s --> %s\n",
@@ -2013,15 +2107,34 @@ invalid_request:
		mdss_dp_notification_status_to_string(status));
	ret = -EINVAL;

end:
notify:
	if (ret || !notify) {
		pr_debug("not sending notification\n");
		goto end;
	}

	atomic_set(&dp->notification_pending, 1);
	if (connect) {
		mdss_dp_host_init(&dp->panel_data);
		ret = mdss_dp_send_video_notification(dp, true);
	} else {
		mdss_dp_send_audio_notification(dp, false);
		ret = mdss_dp_send_video_notification(dp, false);
	}

	if (!ret) {
		pr_debug("Successfully sent notification %s --> %s\n",
			mdss_dp_notification_status_to_string(
				dp->hpd_notification_status),
			mdss_dp_notification_status_to_string(status));
		dp->hpd_notification_status = status;
	} else {
		pr_err("%s Notification failed\n",
			mdss_dp_notification_status_to_string(status));
		atomic_set(&dp->notification_pending, 0);
	}

end:
	dp->hpd_notification_status = status;
	mutex_unlock(&dp->pd_msg_mutex);
	return ret;
}
@@ -2031,9 +2144,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);
@@ -2046,8 +2156,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");
@@ -2058,14 +2185,18 @@ static int mdss_dp_process_hpd_high(struct mdss_dp_drv_pdata *dp)
	hdmi_edid_set_max_pclk_rate(dp->panel_data.panel_info.edid_data,
		min(dp->max_pclk_khz, max_pclk_khz));

	if (dp->dpcd_read_required) {
		pr_debug("reading DPCD with updated AUX config\n");
		mdss_dp_dpcd_cap_read(dp);
		dp->dpcd_read_required = false;
	}

	ret = hdmi_edid_parser(dp->panel_data.panel_info.edid_data);
	if (ret) {
		pr_err("edid parse failed, setting default resolution\n");
		goto notify;
	}

	dp->sink_info_read = true;

notify:
	if (ret) {
		/* set failsafe parameters */
@@ -2092,7 +2223,6 @@ notify:
end:
	pr_debug("end\n");
	return ret;

}

static int mdss_dp_check_params(struct mdss_dp_drv_pdata *dp, void *arg)
@@ -2814,8 +2944,6 @@ static void mdss_dp_mainlink_push_idle(struct mdss_panel_data *pdata)
	/* wait until link training is completed */
	mutex_lock(&dp_drv->train_mutex);

	mdss_dp_aux_set_sink_power_state(dp_drv, SINK_POWER_OFF);

	reinit_completion(&dp_drv->idle_comp);
	mdss_dp_state_ctrl(&dp_drv->ctrl_io, ST_PUSH_IDLE);
	if (!wait_for_completion_timeout(&dp_drv->idle_comp,
@@ -2905,28 +3033,33 @@ static int mdss_dp_event_handler(struct mdss_panel_data *pdata,

	switch (event) {
	case MDSS_EVENT_UNBLANK:
		mdss_dp_ack_state(dp, true);
		rc = mdss_dp_on(pdata);
		break;
	case MDSS_EVENT_PANEL_ON:
		mdss_dp_update_hdcp_info(dp);

		if (dp_is_hdcp_enabled(dp)) {
			cancel_delayed_work(&dp->hdcp_cb_work);
			cancel_delayed_work_sync(&dp->hdcp_cb_work);

			dp->hdcp_status = HDCP_STATE_AUTHENTICATING;
			queue_delayed_work(dp->workq,
				&dp->hdcp_cb_work, HZ / 2);
		}
		break;
	case MDSS_EVENT_POST_PANEL_ON:
		atomic_set(&dp->notification_pending, 0);
		complete_all(&dp->notification_comp);
		break;
	case MDSS_EVENT_PANEL_OFF:
		rc = mdss_dp_off(pdata);
		atomic_set(&dp->notification_pending, 0);
		complete_all(&dp->notification_comp);
		break;
	case MDSS_EVENT_BLANK:
		if (dp_is_hdcp_enabled(dp)) {
			dp->hdcp_status = HDCP_STATE_INACTIVE;

			cancel_delayed_work(&dp->hdcp_cb_work);
			cancel_delayed_work_sync(&dp->hdcp_cb_work);
			if (dp->hdcp.ops->off)
				dp->hdcp.ops->off(dp->hdcp.data);
		}
@@ -3083,6 +3216,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);
}

@@ -3209,6 +3343,7 @@ irqreturn_t dp_isr(int irq, void *ptr)
	spin_lock(&dp->lock);
	isr1 = dp_read(base + DP_INTR_STATUS);
	isr2 = dp_read(base + DP_INTR_STATUS2);
	pr_debug("isr1=0x%08x, isr2=0x%08x\n", isr1, isr2);

	mask1 = isr1 & dp->mask1;

@@ -3425,6 +3560,17 @@ static inline void mdss_dp_link_maintenance(struct mdss_dp_drv_pdata *dp,
	if (mdss_dp_notify_clients(dp, NOTIFY_DISCONNECT_IRQ_HPD))
		return;

	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;
		}
	}

	mdss_dp_on_irq(dp, lt_needed);
}

@@ -3576,7 +3722,7 @@ static int mdss_dp_process_audio_pattern_request(struct mdss_dp_drv_pdata *dp)
		return -EINVAL;

	if (dp_is_hdcp_enabled(dp) && dp->hdcp.ops->off) {
		cancel_delayed_work(&dp->hdcp_cb_work);
		cancel_delayed_work_sync(&dp->hdcp_cb_work);
		dp->hdcp.ops->off(dp->hdcp.data);
	}

@@ -3622,10 +3768,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)
@@ -3723,19 +3905,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;

@@ -3748,7 +3930,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;
@@ -3842,11 +4023,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);
			cancel_delayed_work_sync(&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");

@@ -3874,6 +4065,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) {
@@ -3895,6 +4091,7 @@ static void mdss_dp_handle_attention(struct mdss_dp_drv_pdata *dp)

		pr_debug("processing item %d in the list\n", ++i);

		reinit_completion(&dp->notification_comp);
		mutex_lock(&dp->attention_lock);
		node = list_first_entry(&dp->attention_head,
				struct mdss_dp_attention_node, list);
@@ -3909,6 +4106,21 @@ static void mdss_dp_handle_attention(struct mdss_dp_drv_pdata *dp)
		mdss_dp_usbpd_ext_dp_status(&dp->alt_mode.dp_status);
		mdss_dp_process_attention(dp);

		if (atomic_read(&dp->notification_pending)) {
			pr_debug("waiting for the attention event to finish\n");
			/*
			 * This wait is intentionally implemented without a
			 * timeout since this is happens only in possible error
			 * conditions e.g. if the display framework does not
			 * power off/on the DisplayPort device in time. Other
			 * events might already be queued from the sink at this
			 * point and they cannot be processed until the power
			 * off/on is complete otherwise we might have problems
			 * with interleaving of these events e.g. un-clocked
			 * register access.
			 */
			wait_for_completion(&dp->notification_comp);
		}
		pr_debug("done processing item %d in the list\n", i);
	};

@@ -4080,8 +4292,9 @@ static int mdss_dp_probe(struct platform_device *pdev)

	dp_drv->inited = true;
	dp_drv->hpd_irq_on = false;
	atomic_set(&dp_drv->notification_pending, 0);
	mdss_dp_reset_test_data(dp_drv);
	init_completion(&dp_drv->irq_comp);
	init_completion(&dp_drv->notification_comp);
	dp_drv->suspend_vic = HDMI_VFRMT_UNKNOWN;

	pr_debug("done\n");
Loading