msm: mhl: hdmi: MHL-HDMI handshake implementation

By default, HDMI driver adds entries for
all the formats that it supports into an internal table.
However, when MHL Tx is present, the max format
supported should be limited based on the maximum bandwidth
supported by the MHL Tx. We implement a handshake between
HDMI and MHL drivers to exchange callback function which
returns the status of HDMI's TMDS lines that is used by MHL
driver before sending RAP messages.

Change-Id: I53b6541307f54309fd53f0062d9be3ef1e15c495
Signed-off-by: Manoj Rao <manojraj@codeaurora.org>
diff --git a/Documentation/devicetree/bindings/fb/msm-hdmi-tx.txt b/Documentation/devicetree/bindings/fb/msm-hdmi-tx.txt
index a30d1d6..a2b66f7 100644
--- a/Documentation/devicetree/bindings/fb/msm-hdmi-tx.txt
+++ b/Documentation/devicetree/bindings/fb/msm-hdmi-tx.txt
@@ -43,7 +43,7 @@
 - compatible : "msm-hdmi-audio-codec-rx";
 
 Example:
-	qcom,hdmi_tx@fd922100 {
+	mdss_hdmi_tx: qcom,hdmi_tx@fd922100 {
 		cell-index = <0>;
 		compatible = "qcom,hdmi-tx";
 		reg =	<0xfd922100 0x35C>,
diff --git a/Documentation/devicetree/bindings/i2c/sii8334-i2c.txt b/Documentation/devicetree/bindings/i2c/sii8334-i2c.txt
index ed45192..27a2149 100644
--- a/Documentation/devicetree/bindings/i2c/sii8334-i2c.txt
+++ b/Documentation/devicetree/bindings/i2c/sii8334-i2c.txt
@@ -7,6 +7,7 @@
 - mhl-pwr-gpio: MHL power gpio required for power rails
 - mhl-rst-gpio: MHL reset gpio going into sii8334 for toggling reset pin
 - <supply-name>-supply: phandle to the regulator device tree node.
+- qcom,hdmi-tx-map: phandle to the hdmi tx device tree node.
 
 Example:
 	i2c@f9967000 {
@@ -22,5 +23,6 @@
 			avcc_12-supply = <&pm8941_l2>;
 			smps3a-supply = <&pm8941_s3>;
 			vdda-supply = <&pm8941_l12>;
+			qcom,hdmi-tx-map = <&mdss_hdmi_tx>;
 		};
 	};
diff --git a/drivers/video/msm/mdss/mdss_hdmi_edid.c b/drivers/video/msm/mdss/mdss_hdmi_edid.c
index 1aae22e..08be337 100644
--- a/drivers/video/msm/mdss/mdss_hdmi_edid.c
+++ b/drivers/video/msm/mdss/mdss_hdmi_edid.c
@@ -240,6 +240,8 @@
 	if (edid_ctrl->sink_data.num_of_elements) {
 		u32 *video_mode = edid_ctrl->sink_data.disp_mode_list;
 		for (i = 0; i < edid_ctrl->sink_data.num_of_elements; ++i) {
+			if (!hdmi_get_supported_mode(*video_mode))
+				continue;
 			if (ret > 0)
 				ret += snprintf(buf+ret, PAGE_SIZE-ret, ",%d",
 					*video_mode++ + 1);
diff --git a/drivers/video/msm/mdss/mdss_hdmi_mhl.h b/drivers/video/msm/mdss/mdss_hdmi_mhl.h
new file mode 100644
index 0000000..8fef63e
--- /dev/null
+++ b/drivers/video/msm/mdss/mdss_hdmi_mhl.h
@@ -0,0 +1,27 @@
+/* Copyright (c) 2013, 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
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __MDSS_HDMI_MHL_H__
+#define __MDSS_HDMI_MHL_H__
+
+#include <linux/platform_device.h>
+
+struct msm_hdmi_mhl_ops {
+	u8 (*tmds_enabled)(struct platform_device *pdev);
+	int (*set_mhl_max_pclk)(struct platform_device *pdev, u32 max_val);
+};
+
+int msm_hdmi_register_mhl(struct platform_device *pdev,
+			  struct msm_hdmi_mhl_ops *ops);
+
+#endif /* __MDSS_HDMI_MHL_H__ */
diff --git a/drivers/video/msm/mdss/mdss_hdmi_tx.c b/drivers/video/msm/mdss/mdss_hdmi_tx.c
index b6dec99..5404000 100644
--- a/drivers/video/msm/mdss/mdss_hdmi_tx.c
+++ b/drivers/video/msm/mdss/mdss_hdmi_tx.c
@@ -30,6 +30,7 @@
 #include "mdss_hdmi_hdcp.h"
 #include "mdss.h"
 #include "mdss_panel.h"
+#include "mdss_hdmi_mhl.h"
 
 #define DRV_NAME "hdmi-tx"
 #define COMPATIBLE_NAME "qcom,hdmi-tx"
@@ -629,6 +630,30 @@
 	hdmi_set_supported_mode(HDMI_VFRMT_4096x2160p24_16_9);
 } /* hdmi_tx_setup_video_mode_lut */
 
+/* Table tuned to indicate video formats supported by the MHL Tx */
+/* Valid pclk rates (Mhz): 25.2, 27, 27.03, 74.25 */
+static void hdmi_tx_setup_mhl_video_mode_lut(struct hdmi_tx_ctrl *hdmi_ctrl)
+{
+	u32 i;
+	struct hdmi_disp_mode_timing_type *temp_timing;
+
+	if (!hdmi_ctrl->mhl_max_pclk) {
+		DEV_WARN("%s: mhl max pclk not set!\n", __func__);
+		return;
+	}
+	DEV_DBG("%s: max mode set to [%u]\n",
+		__func__, hdmi_ctrl->mhl_max_pclk);
+	for (i = 0; i < HDMI_VFRMT_MAX; i++) {
+		temp_timing =
+		(struct hdmi_disp_mode_timing_type *)hdmi_get_supported_mode(i);
+		if (!temp_timing)
+			continue;
+		/* formats that exceed max mhl line clk bw */
+		if (temp_timing->pixel_freq > hdmi_ctrl->mhl_max_pclk)
+			hdmi_del_supported_mode(i);
+	}
+} /* hdmi_tx_setup_mhl_video_mode_lut */
+
 static int hdmi_tx_read_sink_info(struct hdmi_tx_ctrl *hdmi_ctrl)
 {
 	int status;
@@ -1758,12 +1783,65 @@
 		hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID], blk);
 } /* hdmi_tx_get_audio_edid_blk */
 
+static u8 hdmi_tx_tmds_enabled(struct platform_device *pdev)
+{
+	struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev);
+
+	if (!hdmi_ctrl) {
+		DEV_ERR("%s: invalid input\n", __func__);
+		return -ENODEV;
+	}
+
+	/* status of tmds */
+	return (hdmi_ctrl->timing_gen_on == true);
+}
+
+static int hdmi_tx_set_mhl_max_pclk(struct platform_device *pdev, u32 max_val)
+{
+	struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
+
+	hdmi_ctrl = platform_get_drvdata(pdev);
+
+	if (!hdmi_ctrl) {
+		DEV_ERR("%s: invalid input\n", __func__);
+		return -ENODEV;
+	}
+	if (max_val) {
+		hdmi_ctrl->mhl_max_pclk = max_val;
+		hdmi_tx_setup_mhl_video_mode_lut(hdmi_ctrl);
+	} else {
+		DEV_ERR("%s: invalid max pclk val\n", __func__);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+int msm_hdmi_register_mhl(struct platform_device *pdev,
+		struct msm_hdmi_mhl_ops *ops)
+{
+	struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev);
+
+	if (!hdmi_ctrl) {
+		DEV_ERR("%s: invalid pdev\n", __func__);
+		return -ENODEV;
+	}
+
+	if (!ops) {
+		DEV_ERR("%s: invalid ops\n", __func__);
+		return -EINVAL;
+	}
+
+	ops->tmds_enabled = hdmi_tx_tmds_enabled;
+	ops->set_mhl_max_pclk = hdmi_tx_set_mhl_max_pclk;
+	return 0;
+}
+
 int msm_hdmi_register_audio_codec(struct platform_device *pdev,
 	struct msm_hdmi_audio_codec_ops *ops)
 {
 	struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev);
 
-	if (!hdmi_ctrl) {
+	if (!hdmi_ctrl || !ops) {
 		DEV_ERR("%s: invalid input\n", __func__);
 		return -ENODEV;
 	}
@@ -2436,6 +2514,7 @@
 				DEV_ERR("%s: hdcp auth failed. rc=%d\n",
 					__func__, rc);
 		}
+		hdmi_ctrl->timing_gen_on = true;
 		break;
 
 	case MDSS_EVENT_SUSPEND:
@@ -2463,6 +2542,7 @@
 		break;
 
 	case MDSS_EVENT_TIMEGEN_OFF:
+		hdmi_ctrl->timing_gen_on = false;
 		break;
 
 	case MDSS_EVENT_CLOSE:
diff --git a/drivers/video/msm/mdss/mdss_hdmi_tx.h b/drivers/video/msm/mdss/mdss_hdmi_tx.h
index ba5ee5b..06ae427 100644
--- a/drivers/video/msm/mdss/mdss_hdmi_tx.h
+++ b/drivers/video/msm/mdss/mdss_hdmi_tx.h
@@ -58,6 +58,8 @@
 	u32 hpd_off_pending;
 	u32 hpd_feature_on;
 	u32 hpd_initialized;
+	u8  timing_gen_on;
+	u32 mhl_max_pclk;
 	struct completion hpd_done;
 	struct work_struct hpd_int_work;
 
diff --git a/drivers/video/msm/mdss/mdss_hdmi_util.c b/drivers/video/msm/mdss/mdss_hdmi_util.c
index 13e2c9b..07c2336 100644
--- a/drivers/video/msm/mdss/mdss_hdmi_util.c
+++ b/drivers/video/msm/mdss/mdss_hdmi_util.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2010-2013, 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
@@ -34,6 +34,16 @@
 	}
 } /* hdmi_init_supported_video_timings */
 
+void hdmi_del_supported_mode(u32 mode)
+{
+	struct hdmi_disp_mode_timing_type *ret = NULL;
+	DEV_DBG("%s: removing %s\n", __func__,
+		 hdmi_get_video_fmt_2string(mode));
+	ret = &hdmi_supported_video_mode_lut[mode];
+	if (ret != NULL && ret->supported)
+		ret->supported = false;
+}
+
 const struct hdmi_disp_mode_timing_type *hdmi_get_supported_mode(u32 mode)
 {
 	const struct hdmi_disp_mode_timing_type *ret = NULL;
diff --git a/drivers/video/msm/mdss/mdss_hdmi_util.h b/drivers/video/msm/mdss/mdss_hdmi_util.h
index d621616..914aac1 100644
--- a/drivers/video/msm/mdss/mdss_hdmi_util.h
+++ b/drivers/video/msm/mdss/mdss_hdmi_util.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2010-2013, 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
@@ -430,6 +430,7 @@
 int hdmi_get_video_id_code(struct hdmi_disp_mode_timing_type *timing_in);
 const struct hdmi_disp_mode_timing_type *hdmi_get_supported_mode(u32 mode);
 void hdmi_set_supported_mode(u32 mode);
+void hdmi_del_supported_mode(u32 mode);
 const char *hdmi_get_video_fmt_2string(u32 format);
 ssize_t hdmi_get_video_3d_fmt_2string(u32 format, char *buf);
 
diff --git a/drivers/video/msm/mdss/mhl_msc.c b/drivers/video/msm/mdss/mhl_msc.c
index 3d3fff9..add65ac 100644
--- a/drivers/video/msm/mdss/mhl_msc.c
+++ b/drivers/video/msm/mdss/mhl_msc.c
@@ -16,6 +16,7 @@
 #include <linux/vmalloc.h>
 #include <linux/input.h>
 #include "mhl_msc.h"
+#include "mdss_hdmi_mhl.h"
 
 static struct mhl_tx_ctrl *mhl_ctrl;
 static DEFINE_MUTEX(msc_send_workqueue_mutex);
@@ -39,6 +40,18 @@
 	"Reserved        ",
 };
 
+static bool mhl_check_tmds_enabled(struct mhl_tx_ctrl *mhl_ctrl)
+{
+	if (mhl_ctrl && mhl_ctrl->hdmi_mhl_ops) {
+		struct msm_hdmi_mhl_ops *ops = mhl_ctrl->hdmi_mhl_ops;
+		struct platform_device *pdev = mhl_ctrl->pdata->hdmi_pdev;
+		return (ops->tmds_enabled(pdev) == true);
+	} else {
+		pr_err("%s: invalid input\n", __func__);
+		return false;
+	}
+}
+
 static void mhl_print_devcap(u8 offset, u8 devcap)
 {
 	switch (offset) {
@@ -398,10 +411,12 @@
 static int mhl_rap_recv(struct mhl_tx_ctrl *mhl_ctrl, u8 action_code)
 {
 	u8 error_code;
+	bool tmds_en;
 
 	switch (action_code) {
 	case MHL_RAP_POLL:
-		if (mhl_ctrl->tmds_enabled())
+		tmds_en = mhl_check_tmds_enabled(mhl_ctrl);
+		if (tmds_en)
 			error_code = MHL_RAPK_NO_ERROR;
 		else
 			error_code = MHL_RAPK_UNSUPPORTED_ACTION_CODE;
@@ -513,6 +528,8 @@
 int mhl_msc_recv_write_stat(struct mhl_tx_ctrl *mhl_ctrl,
 			    u8 offset, u8 value)
 {
+	bool tmds_en;
+
 	if (offset >= 2)
 		return -EFAULT;
 
@@ -543,10 +560,11 @@
 		 * changed and PATH ENABLED
 		 * bit set
 		 */
+		tmds_en = mhl_check_tmds_enabled(mhl_ctrl);
 		if ((value ^ mhl_ctrl->path_en_state)
 		    & MHL_STATUS_PATH_ENABLED) {
 			if (value & MHL_STATUS_PATH_ENABLED) {
-				if (mhl_ctrl->tmds_enabled() &&
+				if (tmds_en &&
 				    (mhl_ctrl->devcap[offset] &
 				     MHL_FEATURE_RAP_SUPPORT)) {
 					mhl_msc_send_msc_msg(
diff --git a/drivers/video/msm/mdss/mhl_sii8334.c b/drivers/video/msm/mdss/mhl_sii8334.c
index 4d6af15..30dd471 100644
--- a/drivers/video/msm/mdss/mhl_sii8334.c
+++ b/drivers/video/msm/mdss/mhl_sii8334.c
@@ -30,6 +30,7 @@
 #include "mdss_panel.h"
 #include "mdss_io_util.h"
 #include "mhl_msc.h"
+#include "mdss_hdmi_mhl.h"
 
 #define MHL_DRIVER_NAME "sii8334"
 #define COMPATIBLE_NAME "qcom,mhl-sii8334"
@@ -193,8 +194,6 @@
 static void mhl_init_reg_settings(struct mhl_tx_ctrl *mhl_ctrl,
 				  bool mhl_disc_en);
 
-static uint8_t store_tmds_state;
-
 int mhl_i2c_reg_read(struct i2c_client *client,
 			    uint8_t slave_addr_index, uint8_t reg_offset)
 {
@@ -239,6 +238,8 @@
 	int i, rc = 0;
 	struct device_node *of_node = NULL;
 	struct dss_gpio *temp_gpio = NULL;
+	struct platform_device *hdmi_pdev = NULL;
+	struct device_node *hdmi_tx_node = NULL;
 	int dt_gpio;
 	i = 0;
 
@@ -316,6 +317,23 @@
 		 temp_gpio->gpio);
 	pdata->gpios[MHL_TX_INTR_GPIO] = temp_gpio;
 
+	/* parse phandle for hdmi tx */
+	hdmi_tx_node = of_parse_phandle(of_node, "qcom,hdmi-tx-map", 0);
+	if (!hdmi_tx_node) {
+		pr_err("%s: can't find hdmi phandle\n", __func__);
+		goto error;
+	}
+
+	hdmi_pdev = of_find_device_by_node(hdmi_tx_node);
+	if (!hdmi_pdev) {
+		pr_err("%s: can't find the device by node\n", __func__);
+		goto error;
+	}
+	pr_debug("%s: hdmi_pdev [0X%x] to pdata->pdev\n",
+	       __func__, (unsigned int)hdmi_pdev);
+
+	pdata->hdmi_pdev = hdmi_pdev;
+
 	return 0;
 error:
 	pr_err("%s: ret due to err\n", __func__);
@@ -719,10 +737,6 @@
 	}
 }
 
-uint8_t check_tmds_enabled(void)
-{
-	return store_tmds_state;
-}
 
 void mhl_tmds_ctrl(struct mhl_tx_ctrl *mhl_ctrl, uint8_t on)
 {
@@ -730,16 +744,8 @@
 	if (on) {
 		MHL_SII_REG_NAME_MOD(REG_TMDS_CCTRL, BIT4, BIT4);
 		mhl_drive_hpd(mhl_ctrl, HPD_UP);
-		/*
-		 * store the state to be used
-		 * before responding to RAP msgs
-		 * this needs to be obtained from
-		 * hdmi driver
-		 */
-		store_tmds_state = 1;
 	} else {
 		MHL_SII_REG_NAME_MOD(REG_TMDS_CCTRL, BIT4, 0x00);
-		store_tmds_state = 0;
 	}
 }
 
@@ -1007,7 +1013,10 @@
 		 */
 		cbus_stat = MHL_SII_CBUS_RD(0x0D);
 		if (BIT6 & cbus_stat)
-			mhl_tmds_ctrl(mhl_ctrl, TMDS_ENABLE);
+			mhl_drive_hpd(mhl_ctrl, HPD_UP);
+		else
+			mhl_drive_hpd(mhl_ctrl, HPD_DOWN);
+
 	}
 }
 
@@ -1636,6 +1645,7 @@
 	struct mhl_tx_platform_data *pdata = NULL;
 	struct mhl_tx_ctrl *mhl_ctrl;
 	struct usb_ext_notification *mhl_info = NULL;
+	struct msm_hdmi_mhl_ops *hdmi_mhl_ops = NULL;
 
 	mhl_ctrl = devm_kzalloc(&client->dev, sizeof(*mhl_ctrl), GFP_KERNEL);
 	if (!mhl_ctrl) {
@@ -1784,25 +1794,63 @@
 		goto failed_probe;
 	}
 
+	hdmi_mhl_ops = devm_kzalloc(&client->dev,
+				    sizeof(struct msm_hdmi_mhl_ops),
+				    GFP_KERNEL);
+	if (!hdmi_mhl_ops) {
+		pr_err("%s: alloc hdmi mhl ops failed\n", __func__);
+		rc = -ENOMEM;
+		goto failed_probe_pwr;
+	}
+
 	pr_debug("%s: i2c client addr is [%x]\n", __func__, client->addr);
+	if (mhl_ctrl->pdata->hdmi_pdev) {
+		rc = msm_hdmi_register_mhl(mhl_ctrl->pdata->hdmi_pdev,
+					   hdmi_mhl_ops);
+		if (rc) {
+			pr_err("%s: register with hdmi failed\n", __func__);
+			rc = -EPROBE_DEFER;
+			goto failed_probe_pwr;
+		}
+	}
+
+	if (!hdmi_mhl_ops || !hdmi_mhl_ops->tmds_enabled ||
+	    !hdmi_mhl_ops->set_mhl_max_pclk) {
+		pr_err("%s: func ptr is NULL\n", __func__);
+		rc = -EINVAL;
+		goto failed_probe_pwr;
+	}
+	mhl_ctrl->hdmi_mhl_ops = hdmi_mhl_ops;
+
+	rc = hdmi_mhl_ops->set_mhl_max_pclk(
+		mhl_ctrl->pdata->hdmi_pdev, MAX_MHL_PCLK);
+	if (rc) {
+		pr_err("%s: can't set max mhl pclk\n", __func__);
+		goto failed_probe_pwr;
+	}
 
 	mhl_info = devm_kzalloc(&client->dev, sizeof(*mhl_info), GFP_KERNEL);
 	if (!mhl_info) {
 		pr_err("%s: alloc mhl info failed\n", __func__);
-		goto failed_probe;
+		rc = -ENOMEM;
+		goto failed_probe_pwr;
 	}
 
 	mhl_info->ctxt = mhl_ctrl;
 	mhl_info->notify = mhl_sii_device_discovery;
 	if (msm_register_usb_ext_notification(mhl_info)) {
 		pr_err("%s: register for usb notifcn failed\n", __func__);
-		goto failed_probe;
+		rc = -EPROBE_DEFER;
+		goto failed_probe_pwr;
 	}
 	mhl_ctrl->mhl_info = mhl_info;
 	mhl_register_msc(mhl_ctrl);
-	mhl_ctrl->tmds_enabled = check_tmds_enabled;
 	return 0;
+
+failed_probe_pwr:
+	power_supply_unregister(&mhl_ctrl->mhl_psy);
 failed_probe:
+	free_irq(mhl_ctrl->i2c_handle->irq, mhl_ctrl);
 	mhl_gpio_config(mhl_ctrl, 0);
 	mhl_vreg_config(mhl_ctrl, 0);
 	/* do not deep-free */
@@ -1814,6 +1862,9 @@
 failed_no_mem:
 	if (mhl_ctrl)
 		devm_kfree(&client->dev, mhl_ctrl);
+	mhl_info = NULL;
+	pdata = NULL;
+	mhl_ctrl = NULL;
 	pr_err("%s: PROBE FAILED, rc=%d\n", __func__, rc);
 	return rc;
 }
diff --git a/include/linux/mhl_8334.h b/include/linux/mhl_8334.h
index 75e6546..d6f8356 100644
--- a/include/linux/mhl_8334.h
+++ b/include/linux/mhl_8334.h
@@ -50,6 +50,8 @@
 	u8 data[MHL_SCRATCHPAD_SIZE];
 };
 
+/* MHL 8334 supports a max HD pixel clk of 75 MHz */
+#define MAX_MHL_PCLK 75000
 
 /* USB driver interface  */
 
@@ -124,6 +126,7 @@
 	struct dss_gpio *gpios[MHL_TX_MAX_GPIO];
 	struct dss_vreg *vregs[MHL_TX_MAX_VREG];
 	int irq;
+	struct platform_device *hdmi_pdev;
 };
 
 struct mhl_tx_ctrl {
@@ -144,7 +147,7 @@
 	uint8_t devcap[16];
 	uint8_t devcap_state;
 	uint8_t path_en_state;
-	uint8_t (*tmds_enabled)(void);
+	void *hdmi_mhl_ops;
 	struct work_struct mhl_msc_send_work;
 	struct list_head list_cmd;
 	struct input_dev *input;