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

Commit e7523f8a authored by Ajay Singh Parmar's avatar Ajay Singh Parmar
Browse files

msm: mdss: hdmi: proper synchronization between hpd and tx power



Currently, HDMI driver has HPD (Hot Plug Detect) work to handle HPD
interrupts and power off work to handle the Tx power off. Power off
work waits for HPD work to finish if in progress. In case of suspend,
this can lead to a dead lock or power off work might starve for long
time HPD work may take some time to finish resulting in suspend or next
resume to timeout.

Power off work was added to unblock the user thread as it used to take
a long time waiting for audio engine to finish. That part of the code
has been moved to HPD work. Now power off can be synchronous with the
call as now it doesn't account for the delay. This removes the dependency
on other work.

Also, use a separate mutex for power on/off. The existing mutex is being
used in large number of places which are not related. During suspend/resume
use of mutex in places like sysfs read/write might result in longer hold of
the mutex resulting in power off delay which eventually may timeout the
suspend or resume.

Change-Id: Ie1e461f6cb7f90d440bfd203b935cd7dea069b5e
Signed-off-by: default avatarAjay Singh Parmar <aparmar@codeaurora.org>
parent 388cfdf9
Loading
Loading
Loading
Loading
+79 −68
Original line number Diff line number Diff line
@@ -730,9 +730,13 @@ static ssize_t hdmi_tx_sysfs_wta_hpd(struct device *dev,

		rc = hdmi_tx_sysfs_enable_hpd(hdmi_ctrl, false);

		mutex_lock(&hdmi_ctrl->power_mutex);
		if (hdmi_ctrl->panel_power_on && hdmi_ctrl->hpd_state) {
			mutex_unlock(&hdmi_ctrl->power_mutex);
			hdmi_tx_set_audio_switch_node(hdmi_ctrl, 0);
			hdmi_tx_wait_for_audio_engine(hdmi_ctrl);
		} else {
			mutex_unlock(&hdmi_ctrl->power_mutex);
		}

		hdmi_tx_send_cable_notification(hdmi_ctrl, 0);
@@ -1413,17 +1417,6 @@ static void hdmi_tx_hpd_int_work(struct work_struct *work)
		hdmi_ctrl->hpd_state ? "CONNECT" : "DISCONNECT");

	if (hdmi_ctrl->hpd_state) {
		/*
		 * If a down stream device or bridge chip is attached to hdmi
		 * Tx core output, it is likely that it might be powering the
		 * hpd module ON/OFF on cable connect/disconnect as it would
		 * have its own mechanism of detecting cable. Flush power off
		 * work is needed in case there is any race condidtion between
		 * power off and on during fast cable plug in/out.
		 */
		if (hdmi_ctrl->ds_registered)
			flush_work(&hdmi_ctrl->power_off_work);

		if (hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_DDC_PM, true)) {
			DEV_ERR("%s: Failed to enable ddc power\n", __func__);
			return;
@@ -1446,8 +1439,8 @@ static void hdmi_tx_hpd_int_work(struct work_struct *work)
		hdmi_tx_send_cable_notification(hdmi_ctrl, false);
	}

	if (!completion_done(&hdmi_ctrl->hpd_done))
		complete_all(&hdmi_ctrl->hpd_done);
	if (!completion_done(&hdmi_ctrl->hpd_int_done))
		complete_all(&hdmi_ctrl->hpd_int_done);
} /* hdmi_tx_hpd_int_work */

static int hdmi_tx_check_capability(struct hdmi_tx_ctrl *hdmi_ctrl)
@@ -2552,7 +2545,9 @@ static int hdmi_tx_audio_info_setup(struct platform_device *pdev,
		return -ENODEV;
	}

	mutex_lock(&hdmi_ctrl->power_mutex);
	if (!hdmi_tx_is_dvi_mode(hdmi_ctrl) && hdmi_ctrl->panel_power_on) {
		mutex_unlock(&hdmi_ctrl->power_mutex);
		/* Map given sample rate to Enum */
		if (sample_rate == 32000)
			sample_rate = AUDIO_SAMPLE_RATE_32KHZ;
@@ -2580,6 +2575,7 @@ static int hdmi_tx_audio_info_setup(struct platform_device *pdev,
			DEV_ERR("%s: hdmi_tx_audio_iframe_setup failed.rc=%d\n",
				__func__, rc);
	} else {
		mutex_unlock(&hdmi_ctrl->power_mutex);
		rc = -EPERM;
	}

@@ -2848,21 +2844,27 @@ static void hdmi_tx_hpd_polarity_setup(struct hdmi_tx_ctrl *hdmi_ctrl,
	}
} /* hdmi_tx_hpd_polarity_setup */

static void hdmi_tx_power_off_work(struct work_struct *work)
static int hdmi_tx_power_off(struct mdss_panel_data *panel_data)
{
	struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
	struct dss_io_data *io = NULL;
	struct hdmi_tx_ctrl *hdmi_ctrl =
		hdmi_tx_get_drvdata_from_panel_data(panel_data);

	hdmi_ctrl = container_of(work, struct hdmi_tx_ctrl, power_off_work);
	if (!hdmi_ctrl) {
		DEV_DBG("%s: invalid input\n", __func__);
		return;
	mutex_lock(&hdmi_ctrl->power_mutex);
	if (!hdmi_ctrl ||
		(!panel_data->panel_info.cont_splash_enabled &&
		!hdmi_ctrl->panel_power_on)) {
		mutex_unlock(&hdmi_ctrl->power_mutex);
		DEV_ERR("%s: invalid input\n", __func__);
		return -EINVAL;
	} else {
		mutex_unlock(&hdmi_ctrl->power_mutex);
	}

	io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
	if (!io->base) {
		DEV_ERR("%s: Core io is not initialized\n", __func__);
		return;
		return -EINVAL;
	}

	if (!hdmi_tx_is_dvi_mode(hdmi_ctrl))
@@ -2874,41 +2876,20 @@ static void hdmi_tx_power_off_work(struct work_struct *work)

	hdmi_tx_core_off(hdmi_ctrl);

	mutex_lock(&hdmi_ctrl->power_mutex);
	hdmi_ctrl->panel_power_on = false;
	mutex_unlock(&hdmi_ctrl->power_mutex);

	if (hdmi_ctrl->hpd_off_pending) {
		hdmi_tx_hpd_off(hdmi_ctrl);
		hdmi_ctrl->hpd_off_pending = false;
	}

	mutex_lock(&hdmi_ctrl->mutex);
	hdmi_ctrl->panel_power_on = false;
	mutex_unlock(&hdmi_ctrl->mutex);

	DEV_INFO("%s: HDMI Core: OFF\n", __func__);

	if (hdmi_ctrl->hdmi_tx_hpd_done)
		hdmi_ctrl->hdmi_tx_hpd_done(
			hdmi_ctrl->downstream_data);
} /* hdmi_tx_power_off_work */

static int hdmi_tx_power_off(struct mdss_panel_data *panel_data)
{
	struct hdmi_tx_ctrl *hdmi_ctrl =
		hdmi_tx_get_drvdata_from_panel_data(panel_data);

	if (!hdmi_ctrl ||
		(!panel_data->panel_info.cont_splash_enabled &&
		!hdmi_ctrl->panel_power_on)) {
		DEV_ERR("%s: invalid input\n", __func__);
		return -EINVAL;
	}

	/*
	 * Queue work item to handle power down sequence.
	 * This is needed since we need to wait for the audio engine
	 * to shutdown first before we shutdown the HDMI core.
	 */
	DEV_DBG("%s: Queuing work to power off HDMI core\n", __func__);
	queue_work(hdmi_ctrl->workq, &hdmi_ctrl->power_off_work);
	DEV_INFO("%s: HDMI Core: OFF\n", __func__);

	return 0;
} /* hdmi_tx_power_off */
@@ -2941,9 +2922,6 @@ static int hdmi_tx_power_on(struct mdss_panel_data *panel_data)
	panel_info = &panel_data->panel_info;
	hdmi_ctrl->hdcp_feature_on = hdcp_feature_on;

	/* If a power down is already underway, wait for it to finish */
	flush_work(&hdmi_ctrl->power_off_work);

	res_changed = hdmi_tx_set_video_fmt(hdmi_ctrl, panel_info);

	DEV_DBG("%s: %dx%d%s\n", __func__,
@@ -2955,7 +2933,9 @@ static int hdmi_tx_power_on(struct mdss_panel_data *panel_data)
		panel_data->panel_info.cont_splash_enabled = false;

		if (res_changed == RESOLUTION_UNCHANGED) {
			mutex_lock(&hdmi_ctrl->power_mutex);
			hdmi_ctrl->panel_power_on = true;
			mutex_unlock(&hdmi_ctrl->power_mutex);

			hdmi_cec_config(
				hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC]);
@@ -2973,9 +2953,9 @@ static int hdmi_tx_power_on(struct mdss_panel_data *panel_data)
		return rc;
	}

	mutex_lock(&hdmi_ctrl->mutex);
	mutex_lock(&hdmi_ctrl->power_mutex);
	hdmi_ctrl->panel_power_on = true;
	mutex_unlock(&hdmi_ctrl->mutex);
	mutex_unlock(&hdmi_ctrl->power_mutex);

	hdmi_cec_config(hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC]);

@@ -3027,6 +3007,7 @@ static void hdmi_tx_hpd_off(struct hdmi_tx_ctrl *hdmi_ctrl)
	}

	/* finish the ongoing hpd work if any */
	if (!hdmi_ctrl->panel_suspend)
		flush_work(&hdmi_ctrl->hpd_int_work);

	/* Turn off HPD interrupts */
@@ -3053,6 +3034,10 @@ static void hdmi_tx_hpd_off(struct hdmi_tx_ctrl *hdmi_ctrl)
	spin_unlock_irqrestore(&hdmi_ctrl->hpd_state_lock, flags);

	hdmi_ctrl->hpd_initialized = false;

	if (!completion_done(&hdmi_ctrl->hpd_off_done))
		complete_all(&hdmi_ctrl->hpd_off_done);

	DEV_DBG("%s: HPD is now OFF\n", __func__);
} /* hdmi_tx_hpd_off */

@@ -3125,16 +3110,29 @@ static int hdmi_tx_sysfs_enable_hpd(struct hdmi_tx_ctrl *hdmi_ctrl, int on)

	DEV_INFO("%s: %d\n", __func__, on);
	if (on) {
		if (hdmi_ctrl->hpd_off_pending) {
			u32 timeout;

			INIT_COMPLETION(hdmi_ctrl->hpd_off_done);
			timeout = wait_for_completion_timeout(
				&hdmi_ctrl->hpd_off_done, HZ);

			if (!timeout)
				DEV_ERR("%s: hpd off still pending\n",
					__func__);
		}
		rc = hdmi_tx_hpd_on(hdmi_ctrl);
	} else {
		/* If power down is already underway, wait for it to finish */
		flush_work(&hdmi_ctrl->power_off_work);

		if (!hdmi_ctrl->panel_power_on)
		mutex_lock(&hdmi_ctrl->power_mutex);
		if (!hdmi_ctrl->panel_power_on &&
			!hdmi_ctrl->hpd_off_pending) {
			mutex_unlock(&hdmi_ctrl->power_mutex);
			hdmi_tx_hpd_off(hdmi_ctrl);
		else
		} else {
			mutex_unlock(&hdmi_ctrl->power_mutex);
			hdmi_ctrl->hpd_off_pending = true;
		}
	}

	return rc;
} /* hdmi_tx_sysfs_enable_hpd */
@@ -3206,6 +3204,14 @@ static irqreturn_t hdmi_tx_isr(int irq, void *data)
		 * new hpd interrupt.
		 */
		DSS_REG_W(io, HDMI_HPD_INT_CTRL, BIT(0));

		/*
		 * If suspend has already triggered, don't start the hpd work
		 * to avoid a possible deadlock during suspend where hpd off
		 * waits for hpd interrupt to finish. Suspend thread will
		 * eventually reset the HPD module.
		 */
		if (!hdmi_ctrl->panel_suspend)
			queue_work(hdmi_ctrl->workq, &hdmi_ctrl->hpd_int_work);
	}

@@ -3250,6 +3256,7 @@ static void hdmi_tx_dev_deinit(struct hdmi_tx_ctrl *hdmi_ctrl)
	if (hdmi_ctrl->workq)
		destroy_workqueue(hdmi_ctrl->workq);
	mutex_destroy(&hdmi_ctrl->lut_lock);
	mutex_destroy(&hdmi_ctrl->power_mutex);
	mutex_destroy(&hdmi_ctrl->cable_notify_mutex);
	mutex_destroy(&hdmi_ctrl->mutex);

@@ -3280,6 +3287,7 @@ static int hdmi_tx_dev_init(struct hdmi_tx_ctrl *hdmi_ctrl)
	mutex_init(&hdmi_ctrl->mutex);
	mutex_init(&hdmi_ctrl->lut_lock);
	mutex_init(&hdmi_ctrl->cable_notify_mutex);
	mutex_init(&hdmi_ctrl->power_mutex);

	INIT_LIST_HEAD(&hdmi_ctrl->cable_notify_handlers);

@@ -3299,11 +3307,11 @@ static int hdmi_tx_dev_init(struct hdmi_tx_ctrl *hdmi_ctrl)
	hdmi_ctrl->hpd_state = false;
	hdmi_ctrl->hpd_initialized = false;
	hdmi_ctrl->hpd_off_pending = false;
	init_completion(&hdmi_ctrl->hpd_done);
	init_completion(&hdmi_ctrl->hpd_int_done);
	init_completion(&hdmi_ctrl->hpd_off_done);

	INIT_WORK(&hdmi_ctrl->hpd_int_work, hdmi_tx_hpd_int_work);
	INIT_WORK(&hdmi_ctrl->cable_notify_work, hdmi_tx_cable_notify_work);
	INIT_WORK(&hdmi_ctrl->power_off_work, hdmi_tx_power_off_work);

	spin_lock_init(&hdmi_ctrl->hpd_state_lock);

@@ -3398,7 +3406,7 @@ static int hdmi_tx_panel_event_handler(struct mdss_panel_data *panel_data,
		}

		if (hdmi_ctrl->pdata.primary) {
			INIT_COMPLETION(hdmi_ctrl->hpd_done);
			INIT_COMPLETION(hdmi_ctrl->hpd_int_done);
			rc = hdmi_tx_sysfs_enable_hpd(hdmi_ctrl, true);
			if (rc) {
				DEV_ERR("%s: hpd_enable failed. rc=%d\n",
@@ -3432,12 +3440,8 @@ static int hdmi_tx_panel_event_handler(struct mdss_panel_data *panel_data,
		break;

	case MDSS_EVENT_RESUME:
		/* If a suspend is already underway, wait for it to finish */
		if (hdmi_ctrl->panel_suspend && hdmi_ctrl->panel_power_on)
			flush_work(&hdmi_ctrl->power_off_work);

		if (hdmi_ctrl->hpd_feature_on) {
			INIT_COMPLETION(hdmi_ctrl->hpd_done);
			INIT_COMPLETION(hdmi_ctrl->hpd_int_done);

			rc = hdmi_tx_hpd_on(hdmi_ctrl);
			if (rc)
@@ -3452,7 +3456,7 @@ static int hdmi_tx_panel_event_handler(struct mdss_panel_data *panel_data,
			hdmi_ctrl->panel_suspend = false;

			timeout = wait_for_completion_timeout(
				&hdmi_ctrl->hpd_done, HZ/10);
				&hdmi_ctrl->hpd_int_done, HZ/10);
			if (!timeout && !hdmi_ctrl->hpd_state) {
				DEV_INFO("%s: cable removed during suspend\n",
					__func__);
@@ -3482,12 +3486,16 @@ static int hdmi_tx_panel_event_handler(struct mdss_panel_data *panel_data,
		break;

	case MDSS_EVENT_SUSPEND:
		if (!hdmi_ctrl->panel_power_on) {
		mutex_lock(&hdmi_ctrl->power_mutex);
		if (!hdmi_ctrl->panel_power_on &&
			!hdmi_ctrl->hpd_off_pending) {
			mutex_unlock(&hdmi_ctrl->power_mutex);
			if (hdmi_ctrl->hpd_feature_on)
				hdmi_tx_hpd_off(hdmi_ctrl);

			hdmi_ctrl->panel_suspend = false;
		} else {
			mutex_unlock(&hdmi_ctrl->power_mutex);
			hdmi_ctrl->hpd_off_pending = true;
			hdmi_ctrl->panel_suspend = true;
		}
@@ -3508,13 +3516,16 @@ static int hdmi_tx_panel_event_handler(struct mdss_panel_data *panel_data,
		break;

	case MDSS_EVENT_PANEL_OFF:
		mutex_lock(&hdmi_ctrl->power_mutex);
		if (hdmi_ctrl->panel_power_on) {
			mutex_unlock(&hdmi_ctrl->power_mutex);
			hdmi_tx_config_avmute(hdmi_ctrl, 1);
			rc = hdmi_tx_power_off(panel_data);
			if (rc)
				DEV_ERR("%s: hdmi_tx_power_off failed.rc=%d\n",
					__func__, rc);
		} else {
			mutex_unlock(&hdmi_ctrl->power_mutex);
			DEV_DBG("%s: hdmi is already powered off\n", __func__);
		}

+4 −3
Original line number Diff line number Diff line
/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
/* Copyright (c) 2010-2015, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -71,6 +71,7 @@ struct hdmi_tx_ctrl {

	struct mutex mutex;
	struct mutex lut_lock;
	struct mutex power_mutex;
	struct mutex cable_notify_mutex;
	struct list_head cable_notify_handlers;
	struct kobject *kobj;
@@ -94,10 +95,10 @@ struct hdmi_tx_ctrl {
	u8  mhl_hpd_on;

	struct hdmi_util_ds_data ds_data;
	struct completion hpd_done;
	struct completion hpd_int_done;
	struct completion hpd_off_done;
	struct work_struct hpd_int_work;

	struct work_struct power_off_work;
	struct work_struct cable_notify_work;

	bool hdcp_feature_on;