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

Commit 2868efba 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: hdmi: proper synchronization between hpd and tx power"

parents cca1f412 e7523f8a
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;