All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCHv6 0/5] Add initial support for slimport anx78xx
@ 2015-12-04  8:35 ` Enric Balletbo i Serra
  0 siblings, 0 replies; 17+ messages in thread
From: Enric Balletbo i Serra @ 2015-12-04  8:35 UTC (permalink / raw)
  To: devicetree, linux-kernel, dri-devel, devel, treding
  Cc: robh+dt, pawel.moll, mark.rutland, ijc+devicetree, galak,
	airlied, gregkh, sjoerd.simons, javier, span, nathan.chung,
	djkurtz, drinkcat, laurent.pinchart, dan.carpenter, jb.tsai,
	cawa.cheng, eddie.huang, cjiao, emil.l.velikov

Hi all,

This is another version of the patch set to introduce the anx7814 transmitter.
Any comments are welcome.

The following series add initial support for the Slimport ANX7814 transmitter, a
ultra-low power Full-HD (1080p60) transmitter designed for portable device.

The driver was originally created and based from the work of Junhua Xia from
Analogix. This driver is a refactor of the original driver and fixes different
coding style lines, and different errors/warnings reported by checkpatch. Also
there were things that I noticed that we need to change like:

 - Convert the numbered GPIO API to the new descriptor based GPIO API.
 - Review the DT binding
 - Add missing MODULE_DEVICE_TABLE(of, ...);
 - Fix Makefiles and Kconfig to build conditionally.
 - Use SIMPLE_DEV_PM_OPS() instead of the deprecated i2c .suspend and
  .resume callbacks.
 - Move to use managed device resources.
 - Remove dead/unused code.
 - And others ...

Changes since last version:
 - Add the revision history within the respective patches (requested by Emil)
 - Add two new patches 01 and 02 as part of the series (requested by Thierry)

Enric Balletbo i Serra (5):
  drm/dp: add DPCD definitions from DP 1.1
  hdmi: added functions for MPEG InfoFrames
  of: Add vendor prefix for Analogix Semiconductor, Inc.
  devicetree: Add new ANX7814 SlimPort transmitter binding.
  drm: bridge: anx78xx: Add anx78xx driver support by analogix.

 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 .../devicetree/bindings/video/bridge/anx7814.txt   |   39 +
 drivers/gpu/drm/bridge/Kconfig                     |    2 +
 drivers/gpu/drm/bridge/Makefile                    |    1 +
 drivers/gpu/drm/bridge/anx78xx/Kconfig             |    5 +
 drivers/gpu/drm/bridge/anx78xx/Makefile            |    4 +
 drivers/gpu/drm/bridge/anx78xx/anx78xx.h           |   44 +
 drivers/gpu/drm/bridge/anx78xx/anx78xx_main.c      |  334 ++
 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.c   | 3210 ++++++++++++++++++++
 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.h   |  110 +
 drivers/gpu/drm/bridge/anx78xx/slimport_tx_reg.h   |  737 +++++
 drivers/video/hdmi.c                               |  156 +
 include/drm/drm_dp_helper.h                        |   10 +
 include/linux/hdmi.h                               |   24 +
 14 files changed, 4677 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/video/bridge/anx7814.txt
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/Kconfig
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/Makefile
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/anx78xx.h
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/anx78xx_main.c
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.c
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.h
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/slimport_tx_reg.h

-- 
2.1.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCHv6 0/5] Add initial support for slimport anx78xx
@ 2015-12-04  8:35 ` Enric Balletbo i Serra
  0 siblings, 0 replies; 17+ messages in thread
From: Enric Balletbo i Serra @ 2015-12-04  8:35 UTC (permalink / raw)
  To: devicetree, linux-kernel, dri-devel, devel, treding
  Cc: mark.rutland, drinkcat, laurent.pinchart, pawel.moll,
	ijc+devicetree, gregkh, emil.l.velikov, cawa.cheng, jb.tsai,
	sjoerd.simons, robh+dt, span, galak, javier, eddie.huang, cjiao,
	dan.carpenter, nathan.chung

Hi all,

This is another version of the patch set to introduce the anx7814 transmitter.
Any comments are welcome.

The following series add initial support for the Slimport ANX7814 transmitter, a
ultra-low power Full-HD (1080p60) transmitter designed for portable device.

The driver was originally created and based from the work of Junhua Xia from
Analogix. This driver is a refactor of the original driver and fixes different
coding style lines, and different errors/warnings reported by checkpatch. Also
there were things that I noticed that we need to change like:

 - Convert the numbered GPIO API to the new descriptor based GPIO API.
 - Review the DT binding
 - Add missing MODULE_DEVICE_TABLE(of, ...);
 - Fix Makefiles and Kconfig to build conditionally.
 - Use SIMPLE_DEV_PM_OPS() instead of the deprecated i2c .suspend and
  .resume callbacks.
 - Move to use managed device resources.
 - Remove dead/unused code.
 - And others ...

Changes since last version:
 - Add the revision history within the respective patches (requested by Emil)
 - Add two new patches 01 and 02 as part of the series (requested by Thierry)

Enric Balletbo i Serra (5):
  drm/dp: add DPCD definitions from DP 1.1
  hdmi: added functions for MPEG InfoFrames
  of: Add vendor prefix for Analogix Semiconductor, Inc.
  devicetree: Add new ANX7814 SlimPort transmitter binding.
  drm: bridge: anx78xx: Add anx78xx driver support by analogix.

 .../devicetree/bindings/vendor-prefixes.txt        |    1 +
 .../devicetree/bindings/video/bridge/anx7814.txt   |   39 +
 drivers/gpu/drm/bridge/Kconfig                     |    2 +
 drivers/gpu/drm/bridge/Makefile                    |    1 +
 drivers/gpu/drm/bridge/anx78xx/Kconfig             |    5 +
 drivers/gpu/drm/bridge/anx78xx/Makefile            |    4 +
 drivers/gpu/drm/bridge/anx78xx/anx78xx.h           |   44 +
 drivers/gpu/drm/bridge/anx78xx/anx78xx_main.c      |  334 ++
 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.c   | 3210 ++++++++++++++++++++
 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.h   |  110 +
 drivers/gpu/drm/bridge/anx78xx/slimport_tx_reg.h   |  737 +++++
 drivers/video/hdmi.c                               |  156 +
 include/drm/drm_dp_helper.h                        |   10 +
 include/linux/hdmi.h                               |   24 +
 14 files changed, 4677 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/video/bridge/anx7814.txt
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/Kconfig
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/Makefile
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/anx78xx.h
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/anx78xx_main.c
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.c
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.h
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/slimport_tx_reg.h

-- 
2.1.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCHv6 1/5] drm/dp: add DPCD definitions from DP 1.1
  2015-12-04  8:35 ` Enric Balletbo i Serra
@ 2015-12-04  8:35   ` Enric Balletbo i Serra
  -1 siblings, 0 replies; 17+ messages in thread
From: Enric Balletbo i Serra @ 2015-12-04  8:35 UTC (permalink / raw)
  To: devicetree, linux-kernel, dri-devel, devel, treding
  Cc: robh+dt, pawel.moll, mark.rutland, ijc+devicetree, galak,
	airlied, gregkh, sjoerd.simons, javier, span, nathan.chung,
	djkurtz, drinkcat, laurent.pinchart, dan.carpenter, jb.tsai,
	cawa.cheng, eddie.huang, cjiao, emil.l.velikov

Add a number of DPCD definitions from DP 1.1

Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
---
 include/drm/drm_dp_helper.h | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/include/drm/drm_dp_helper.h b/include/drm/drm_dp_helper.h
index bb9d0de..9b0c990 100644
--- a/include/drm/drm_dp_helper.h
+++ b/include/drm/drm_dp_helper.h
@@ -73,6 +73,7 @@
 # define DP_ENHANCED_FRAME_CAP		    (1 << 7)
 
 #define DP_MAX_DOWNSPREAD                   0x003
+# define DP_PERCENT_DOWNSPREAD_0_5	    (1 << 0)
 # define DP_NO_AUX_HANDSHAKE_LINK_TRAINING  (1 << 6)
 
 #define DP_NORP                             0x004
@@ -225,6 +226,7 @@
 # define DP_LINK_BW_1_62		    0x06
 # define DP_LINK_BW_2_7			    0x0a
 # define DP_LINK_BW_5_4			    0x14    /* 1.2 */
+# define DP_LINK_BW_6_75		    0x19
 
 #define DP_LANE_COUNT_SET	            0x101
 # define DP_LANE_COUNT_MASK		    0x0f
@@ -354,6 +356,7 @@
 #define DP_PAYLOAD_ALLOCATE_TIME_SLOT_COUNT 0x1c2
 
 #define DP_SINK_COUNT			    0x200
+# define DP_SINK_COUNT_MASK		    0x3f
 /* prior to 1.2 bit 7 was reserved mbz */
 # define DP_GET_SINK_COUNT(x)		    ((((x) & 0x80) >> 1) | ((x) & 0x3f))
 # define DP_SINK_CP_READY		    (1 << 6)
@@ -399,6 +402,10 @@
 # define DP_ADJUST_PRE_EMPHASIS_LANE1_MASK   0xc0
 # define DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT  6
 
+#define DP_SYMBOL_ERROR_COUNT_LANE0	    0x210
+# define DP_ERROR_COUNT_BITS_14_8_MASK	    0x7f
+# define DP_ERROR_COUNT_VALID		    (1 << 7)
+
 #define DP_TEST_REQUEST			    0x218
 # define DP_TEST_LINK_TRAINING		    (1 << 0)
 # define DP_TEST_LINK_VIDEO_PATTERN	    (1 << 1)
@@ -418,6 +425,8 @@
 #define DP_TEST_CRC_G_Y			    0x242
 #define DP_TEST_CRC_B_CB		    0x244
 
+#define DP_PHY_TEST_PATTERN		    0x248 /* DPCD >= 1.1 */
+
 #define DP_TEST_SINK_MISC		    0x246
 # define DP_TEST_CRC_SUPPORTED		    (1 << 5)
 # define DP_TEST_COUNT_MASK		    0xf
@@ -447,6 +456,7 @@
 # define DP_SET_POWER_D0                    0x1
 # define DP_SET_POWER_D3                    0x2
 # define DP_SET_POWER_MASK                  0x3
+# define DP_SET_POWER_12_MASK		    0x7    /* DPCD >= 1.2 */
 
 #define DP_EDP_DPCD_REV			    0x700    /* eDP 1.2 */
 # define DP_EDP_11			    0x00
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCHv6 1/5] drm/dp: add DPCD definitions from DP 1.1
@ 2015-12-04  8:35   ` Enric Balletbo i Serra
  0 siblings, 0 replies; 17+ messages in thread
From: Enric Balletbo i Serra @ 2015-12-04  8:35 UTC (permalink / raw)
  To: devicetree, linux-kernel, dri-devel, devel, treding
  Cc: mark.rutland, drinkcat, laurent.pinchart, pawel.moll,
	ijc+devicetree, gregkh, emil.l.velikov, cawa.cheng, jb.tsai,
	sjoerd.simons, robh+dt, span, galak, javier, eddie.huang, cjiao,
	dan.carpenter, nathan.chung

Add a number of DPCD definitions from DP 1.1

Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
---
 include/drm/drm_dp_helper.h | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/include/drm/drm_dp_helper.h b/include/drm/drm_dp_helper.h
index bb9d0de..9b0c990 100644
--- a/include/drm/drm_dp_helper.h
+++ b/include/drm/drm_dp_helper.h
@@ -73,6 +73,7 @@
 # define DP_ENHANCED_FRAME_CAP		    (1 << 7)
 
 #define DP_MAX_DOWNSPREAD                   0x003
+# define DP_PERCENT_DOWNSPREAD_0_5	    (1 << 0)
 # define DP_NO_AUX_HANDSHAKE_LINK_TRAINING  (1 << 6)
 
 #define DP_NORP                             0x004
@@ -225,6 +226,7 @@
 # define DP_LINK_BW_1_62		    0x06
 # define DP_LINK_BW_2_7			    0x0a
 # define DP_LINK_BW_5_4			    0x14    /* 1.2 */
+# define DP_LINK_BW_6_75		    0x19
 
 #define DP_LANE_COUNT_SET	            0x101
 # define DP_LANE_COUNT_MASK		    0x0f
@@ -354,6 +356,7 @@
 #define DP_PAYLOAD_ALLOCATE_TIME_SLOT_COUNT 0x1c2
 
 #define DP_SINK_COUNT			    0x200
+# define DP_SINK_COUNT_MASK		    0x3f
 /* prior to 1.2 bit 7 was reserved mbz */
 # define DP_GET_SINK_COUNT(x)		    ((((x) & 0x80) >> 1) | ((x) & 0x3f))
 # define DP_SINK_CP_READY		    (1 << 6)
@@ -399,6 +402,10 @@
 # define DP_ADJUST_PRE_EMPHASIS_LANE1_MASK   0xc0
 # define DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT  6
 
+#define DP_SYMBOL_ERROR_COUNT_LANE0	    0x210
+# define DP_ERROR_COUNT_BITS_14_8_MASK	    0x7f
+# define DP_ERROR_COUNT_VALID		    (1 << 7)
+
 #define DP_TEST_REQUEST			    0x218
 # define DP_TEST_LINK_TRAINING		    (1 << 0)
 # define DP_TEST_LINK_VIDEO_PATTERN	    (1 << 1)
@@ -418,6 +425,8 @@
 #define DP_TEST_CRC_G_Y			    0x242
 #define DP_TEST_CRC_B_CB		    0x244
 
+#define DP_PHY_TEST_PATTERN		    0x248 /* DPCD >= 1.1 */
+
 #define DP_TEST_SINK_MISC		    0x246
 # define DP_TEST_CRC_SUPPORTED		    (1 << 5)
 # define DP_TEST_COUNT_MASK		    0xf
@@ -447,6 +456,7 @@
 # define DP_SET_POWER_D0                    0x1
 # define DP_SET_POWER_D3                    0x2
 # define DP_SET_POWER_MASK                  0x3
+# define DP_SET_POWER_12_MASK		    0x7    /* DPCD >= 1.2 */
 
 #define DP_EDP_DPCD_REV			    0x700    /* eDP 1.2 */
 # define DP_EDP_11			    0x00
-- 
2.1.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCHv6 2/5] hdmi: added functions for MPEG InfoFrames
  2015-12-04  8:35 ` Enric Balletbo i Serra
@ 2015-12-04  8:35   ` Enric Balletbo i Serra
  -1 siblings, 0 replies; 17+ messages in thread
From: Enric Balletbo i Serra @ 2015-12-04  8:35 UTC (permalink / raw)
  To: devicetree, linux-kernel, dri-devel, devel, treding
  Cc: robh+dt, pawel.moll, mark.rutland, ijc+devicetree, galak,
	airlied, gregkh, sjoerd.simons, javier, span, nathan.chung,
	djkurtz, drinkcat, laurent.pinchart, dan.carpenter, jb.tsai,
	cawa.cheng, eddie.huang, cjiao, emil.l.velikov

The MPEG Source (MS) InfoFrame is in EIA/CEA-861B. It describes aspects of
the compressed video stream that were used to produce the uncompressed
video.

The patch adds functions to work with MPEG InfoFrames.

Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
---

Changes since last version (requested by Thierry Redding)
 - hdmi_infoframe_pack: Fix missing break
 - hdmi_mpeg_picture_get_name: return NULL instead of "Reserved"
 - hdmi_mpeg_picture_get_name: use more canonical names "I-Frame", "P-Frame", etc
 - hdmi_mpeg_infoframe_unpack: remove braces that aren't needed
 - hdmi_vendor_any_infoframe: s/mpeg/MPEG/

 drivers/video/hdmi.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/hdmi.h |  24 ++++++++
 2 files changed, 180 insertions(+)

diff --git a/drivers/video/hdmi.c b/drivers/video/hdmi.c
index 1626892..47121a6 100644
--- a/drivers/video/hdmi.c
+++ b/drivers/video/hdmi.c
@@ -388,6 +388,81 @@ ssize_t hdmi_vendor_infoframe_pack(struct hdmi_vendor_infoframe *frame,
 }
 EXPORT_SYMBOL(hdmi_vendor_infoframe_pack);
 
+/**
+ * hdmi_mpeg_infoframe_init() - initialize an HDMI MPEG infoframe
+ * @frame: HDMI MPEG infoframe
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+int hdmi_mpeg_infoframe_init(struct hdmi_mpeg_infoframe *frame)
+{
+	memset(frame, 0, sizeof(*frame));
+
+	frame->type = HDMI_INFOFRAME_TYPE_MPEG;
+	frame->version = 1;
+	frame->length = HDMI_MPEG_INFOFRAME_SIZE;
+
+	return 0;
+}
+EXPORT_SYMBOL(hdmi_mpeg_infoframe_init);
+
+/**
+ * hdmi_mpeg_infoframe_pack() - write HDMI MPEG infoframe to binary buffer
+ * @frame: HDMI MPEG infoframe
+ * @buffer: destination buffer
+ * @size: size of buffer
+ *
+ * Packs the information contained in the @frame structure into a binary
+ * representation that can be written into the corresponding controller
+ * registers. Also computes the checksum as required by section 5.3.5 of
+ * the HDMI 1.4 specification.
+ *
+ * Returns the number of bytes packed into the binary buffer or a negative
+ * error code on failure.
+ */
+ssize_t hdmi_mpeg_infoframe_pack(struct hdmi_mpeg_infoframe *frame,
+				 void *buffer, size_t size)
+{
+	u8 *ptr = buffer;
+	size_t length;
+
+	length = HDMI_INFOFRAME_HEADER_SIZE + frame->length;
+
+	if (size < length)
+		return -ENOSPC;
+
+	memset(buffer, 0, size);
+
+	ptr[0] = frame->type;
+	ptr[1] = frame->version;
+	ptr[2] = frame->length;
+	ptr[3] = 0; /* checksum */
+
+	/* start infoframe payload */
+	ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+	/*
+	 * The MPEG Bit Rate is stored as a 32-bit number and is expressed in
+	 * Hertz. MB#0 contains the least significant byte while MB#3 contains
+	 * the most significant byte. If the MPEG Bit Rate is unknown or this
+	 * field doesn’t apply, then all of the bits in Data Bytes 1-4 shall
+	 * be set to 0.
+	 */
+	ptr[0] = frame->bitrate & 0x000000ff;
+	ptr[1] = (frame->bitrate & 0x0000ff00) >> 8;
+	ptr[2] = (frame->bitrate & 0x00ff0000) >> 16;
+	ptr[3] = (frame->bitrate & 0xff000000) >> 24;
+
+	ptr[4] = frame->frame_type;
+	if (frame->repeated)
+		ptr[4] |= BIT(4);
+
+	hdmi_infoframe_set_checksum(buffer, length);
+
+	return length;
+}
+EXPORT_SYMBOL(hdmi_mpeg_infoframe_pack);
+
 /*
  * hdmi_vendor_any_infoframe_pack() - write a vendor infoframe to binary buffer
  */
@@ -435,6 +510,9 @@ hdmi_infoframe_pack(union hdmi_infoframe *frame, void *buffer, size_t size)
 		length = hdmi_vendor_any_infoframe_pack(&frame->vendor,
 							buffer, size);
 		break;
+	case HDMI_INFOFRAME_TYPE_MPEG:
+		length = hdmi_mpeg_infoframe_pack(&frame->mpeg, buffer, size);
+		break;
 	default:
 		WARN(1, "Bad infoframe type %d\n", frame->any.type);
 		length = -EINVAL;
@@ -457,6 +535,8 @@ static const char *hdmi_infoframe_type_get_name(enum hdmi_infoframe_type type)
 		return "Source Product Description (SPD)";
 	case HDMI_INFOFRAME_TYPE_AUDIO:
 		return "Audio";
+	case HDMI_INFOFRAME_TYPE_MPEG:
+		return "MPEG";
 	}
 	return "Reserved";
 }
@@ -899,6 +979,41 @@ static void hdmi_audio_infoframe_log(const char *level,
 			frame->downmix_inhibit ? "Yes" : "No");
 }
 
+static const char *hdmi_mpeg_picture_get_name(enum hdmi_mpeg_frame_type type)
+{
+	switch (type) {
+	case HDMI_MPEG_UNKNOWN_FRAME:
+		return "Unknown";
+	case HDMI_MPEG_I_FRAME:
+		return "Intra-coded picture";
+	case HDMI_MPEG_B_FRAME:
+		return "Bi-predictive picture";
+	case HDMI_MPEG_P_FRAME:
+		return "Predicted picture";
+	}
+	return NULL;
+}
+
+/**
+ * hdmi_mpeg_infoframe_log() - log info of HDMI MPEG infoframe
+ * @level: logging level
+ * @dev: device
+ * @frame: HDMI MPEG infoframe
+ */
+static void hdmi_mpeg_infoframe_log(const char *level,
+				     struct device *dev,
+				     struct hdmi_mpeg_infoframe *frame)
+{
+	hdmi_infoframe_log_header(level, dev,
+				  (struct hdmi_any_infoframe *)frame);
+
+	hdmi_log("    bit rate: %d Hz\n", frame->bitrate);
+	hdmi_log("    frame type: %s\n",
+			hdmi_mpeg_picture_get_name(frame->frame_type));
+	hdmi_log("    repeated frame: %s\n",
+			frame->repeated ? "Yes" : "No");
+}
+
 static const char *
 hdmi_3d_structure_get_name(enum hdmi_3d_structure s3d_struct)
 {
@@ -987,6 +1102,9 @@ void hdmi_infoframe_log(const char *level,
 	case HDMI_INFOFRAME_TYPE_VENDOR:
 		hdmi_vendor_any_infoframe_log(level, dev, &frame->vendor);
 		break;
+	case HDMI_INFOFRAME_TYPE_MPEG:
+		hdmi_mpeg_infoframe_log(level, dev, &frame->mpeg);
+		break;
 	}
 }
 EXPORT_SYMBOL(hdmi_infoframe_log);
@@ -1138,6 +1256,41 @@ static int hdmi_audio_infoframe_unpack(struct hdmi_audio_infoframe *frame,
 }
 
 /**
+ * hdmi_mpeg_infoframe_unpack() - unpack binary buffer to a HDMI MPEG infoframe
+ * @buffer: source buffer
+ * @frame: HDMI MPEG infoframe
+ *
+ * Unpacks the information contained in binary @buffer into a structured
+ * @frame of the HDMI MPEG information frame. Also verifies the checksum as
+ * required by section 5.3.5 of the HDMI 1.4 specification.
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+static int hdmi_mpeg_infoframe_unpack(struct hdmi_mpeg_infoframe *frame,
+				     void *buffer)
+{
+	u8 *ptr = buffer;
+
+	if (ptr[0] != HDMI_INFOFRAME_TYPE_MPEG ||
+	    ptr[1] != 1 ||
+	    ptr[2] != HDMI_MPEG_INFOFRAME_SIZE)
+		return -EINVAL;
+
+	if (hdmi_infoframe_checksum(buffer, HDMI_INFOFRAME_SIZE(MPEG)) != 0)
+		return -EINVAL;
+
+	ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+	frame->bitrate = (ptr[3] << 24) | (ptr[2] << 16) |
+			 (ptr[1] << 8) | ptr[0];
+
+	frame->frame_type = ptr[4] & 0x03;
+	frame->repeated = ptr[4] & BIT(4) ? true : false;
+
+	return 0;
+}
+
+/**
  * hdmi_vendor_infoframe_unpack() - unpack binary buffer to a HDMI vendor infoframe
  * @buffer: source buffer
  * @frame: HDMI Vendor infoframe
@@ -1234,6 +1387,9 @@ int hdmi_infoframe_unpack(union hdmi_infoframe *frame, void *buffer)
 	case HDMI_INFOFRAME_TYPE_VENDOR:
 		ret = hdmi_vendor_any_infoframe_unpack(&frame->vendor, buffer);
 		break;
+	case HDMI_INFOFRAME_TYPE_MPEG:
+		ret = hdmi_mpeg_infoframe_unpack(&frame->mpeg, buffer);
+		break;
 	default:
 		ret = -EINVAL;
 		break;
diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h
index e974420..c033554 100644
--- a/include/linux/hdmi.h
+++ b/include/linux/hdmi.h
@@ -32,11 +32,13 @@ enum hdmi_infoframe_type {
 	HDMI_INFOFRAME_TYPE_AVI = 0x82,
 	HDMI_INFOFRAME_TYPE_SPD = 0x83,
 	HDMI_INFOFRAME_TYPE_AUDIO = 0x84,
+	HDMI_INFOFRAME_TYPE_MPEG = 0x85,
 };
 
 #define HDMI_IEEE_OUI 0x000c03
 #define HDMI_INFOFRAME_HEADER_SIZE  4
 #define HDMI_AVI_INFOFRAME_SIZE    13
+#define HDMI_MPEG_INFOFRAME_SIZE   10
 #define HDMI_SPD_INFOFRAME_SIZE    25
 #define HDMI_AUDIO_INFOFRAME_SIZE  10
 
@@ -297,6 +299,26 @@ int hdmi_vendor_infoframe_init(struct hdmi_vendor_infoframe *frame);
 ssize_t hdmi_vendor_infoframe_pack(struct hdmi_vendor_infoframe *frame,
 				   void *buffer, size_t size);
 
+enum hdmi_mpeg_frame_type {
+	HDMI_MPEG_UNKNOWN_FRAME = 0x00,
+	HDMI_MPEG_I_FRAME = 0x01,
+	HDMI_MPEG_B_FRAME = 0x02,
+	HDMI_MPEG_P_FRAME = 0x03,
+};
+
+struct hdmi_mpeg_infoframe {
+	enum hdmi_infoframe_type type;
+	unsigned char version;
+	unsigned char length;
+	u32 bitrate;
+	enum hdmi_mpeg_frame_type frame_type;
+	bool repeated;
+};
+
+int hdmi_mpeg_infoframe_init(struct hdmi_mpeg_infoframe *frame);
+ssize_t hdmi_mpeg_infoframe_pack(struct hdmi_mpeg_infoframe *frame,
+				   void *buffer, size_t size);
+
 union hdmi_vendor_any_infoframe {
 	struct {
 		enum hdmi_infoframe_type type;
@@ -314,6 +336,7 @@ union hdmi_vendor_any_infoframe {
  * @spd: spd infoframe
  * @vendor: union of all vendor infoframes
  * @audio: audio infoframe
+ * @mpeg: MPEG infoframe
  *
  * This is used by the generic pack function. This works since all infoframes
  * have the same header which also indicates which type of infoframe should be
@@ -325,6 +348,7 @@ union hdmi_infoframe {
 	struct hdmi_spd_infoframe spd;
 	union hdmi_vendor_any_infoframe vendor;
 	struct hdmi_audio_infoframe audio;
+	struct hdmi_mpeg_infoframe mpeg;
 };
 
 ssize_t
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCHv6 2/5] hdmi: added functions for MPEG InfoFrames
@ 2015-12-04  8:35   ` Enric Balletbo i Serra
  0 siblings, 0 replies; 17+ messages in thread
From: Enric Balletbo i Serra @ 2015-12-04  8:35 UTC (permalink / raw)
  To: devicetree, linux-kernel, dri-devel, devel, treding
  Cc: mark.rutland, drinkcat, laurent.pinchart, pawel.moll,
	ijc+devicetree, gregkh, emil.l.velikov, cawa.cheng, jb.tsai,
	sjoerd.simons, robh+dt, span, galak, javier, eddie.huang, cjiao,
	dan.carpenter, nathan.chung

The MPEG Source (MS) InfoFrame is in EIA/CEA-861B. It describes aspects of
the compressed video stream that were used to produce the uncompressed
video.

The patch adds functions to work with MPEG InfoFrames.

Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
---

Changes since last version (requested by Thierry Redding)
 - hdmi_infoframe_pack: Fix missing break
 - hdmi_mpeg_picture_get_name: return NULL instead of "Reserved"
 - hdmi_mpeg_picture_get_name: use more canonical names "I-Frame", "P-Frame", etc
 - hdmi_mpeg_infoframe_unpack: remove braces that aren't needed
 - hdmi_vendor_any_infoframe: s/mpeg/MPEG/

 drivers/video/hdmi.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/hdmi.h |  24 ++++++++
 2 files changed, 180 insertions(+)

diff --git a/drivers/video/hdmi.c b/drivers/video/hdmi.c
index 1626892..47121a6 100644
--- a/drivers/video/hdmi.c
+++ b/drivers/video/hdmi.c
@@ -388,6 +388,81 @@ ssize_t hdmi_vendor_infoframe_pack(struct hdmi_vendor_infoframe *frame,
 }
 EXPORT_SYMBOL(hdmi_vendor_infoframe_pack);
 
+/**
+ * hdmi_mpeg_infoframe_init() - initialize an HDMI MPEG infoframe
+ * @frame: HDMI MPEG infoframe
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+int hdmi_mpeg_infoframe_init(struct hdmi_mpeg_infoframe *frame)
+{
+	memset(frame, 0, sizeof(*frame));
+
+	frame->type = HDMI_INFOFRAME_TYPE_MPEG;
+	frame->version = 1;
+	frame->length = HDMI_MPEG_INFOFRAME_SIZE;
+
+	return 0;
+}
+EXPORT_SYMBOL(hdmi_mpeg_infoframe_init);
+
+/**
+ * hdmi_mpeg_infoframe_pack() - write HDMI MPEG infoframe to binary buffer
+ * @frame: HDMI MPEG infoframe
+ * @buffer: destination buffer
+ * @size: size of buffer
+ *
+ * Packs the information contained in the @frame structure into a binary
+ * representation that can be written into the corresponding controller
+ * registers. Also computes the checksum as required by section 5.3.5 of
+ * the HDMI 1.4 specification.
+ *
+ * Returns the number of bytes packed into the binary buffer or a negative
+ * error code on failure.
+ */
+ssize_t hdmi_mpeg_infoframe_pack(struct hdmi_mpeg_infoframe *frame,
+				 void *buffer, size_t size)
+{
+	u8 *ptr = buffer;
+	size_t length;
+
+	length = HDMI_INFOFRAME_HEADER_SIZE + frame->length;
+
+	if (size < length)
+		return -ENOSPC;
+
+	memset(buffer, 0, size);
+
+	ptr[0] = frame->type;
+	ptr[1] = frame->version;
+	ptr[2] = frame->length;
+	ptr[3] = 0; /* checksum */
+
+	/* start infoframe payload */
+	ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+	/*
+	 * The MPEG Bit Rate is stored as a 32-bit number and is expressed in
+	 * Hertz. MB#0 contains the least significant byte while MB#3 contains
+	 * the most significant byte. If the MPEG Bit Rate is unknown or this
+	 * field doesn’t apply, then all of the bits in Data Bytes 1-4 shall
+	 * be set to 0.
+	 */
+	ptr[0] = frame->bitrate & 0x000000ff;
+	ptr[1] = (frame->bitrate & 0x0000ff00) >> 8;
+	ptr[2] = (frame->bitrate & 0x00ff0000) >> 16;
+	ptr[3] = (frame->bitrate & 0xff000000) >> 24;
+
+	ptr[4] = frame->frame_type;
+	if (frame->repeated)
+		ptr[4] |= BIT(4);
+
+	hdmi_infoframe_set_checksum(buffer, length);
+
+	return length;
+}
+EXPORT_SYMBOL(hdmi_mpeg_infoframe_pack);
+
 /*
  * hdmi_vendor_any_infoframe_pack() - write a vendor infoframe to binary buffer
  */
@@ -435,6 +510,9 @@ hdmi_infoframe_pack(union hdmi_infoframe *frame, void *buffer, size_t size)
 		length = hdmi_vendor_any_infoframe_pack(&frame->vendor,
 							buffer, size);
 		break;
+	case HDMI_INFOFRAME_TYPE_MPEG:
+		length = hdmi_mpeg_infoframe_pack(&frame->mpeg, buffer, size);
+		break;
 	default:
 		WARN(1, "Bad infoframe type %d\n", frame->any.type);
 		length = -EINVAL;
@@ -457,6 +535,8 @@ static const char *hdmi_infoframe_type_get_name(enum hdmi_infoframe_type type)
 		return "Source Product Description (SPD)";
 	case HDMI_INFOFRAME_TYPE_AUDIO:
 		return "Audio";
+	case HDMI_INFOFRAME_TYPE_MPEG:
+		return "MPEG";
 	}
 	return "Reserved";
 }
@@ -899,6 +979,41 @@ static void hdmi_audio_infoframe_log(const char *level,
 			frame->downmix_inhibit ? "Yes" : "No");
 }
 
+static const char *hdmi_mpeg_picture_get_name(enum hdmi_mpeg_frame_type type)
+{
+	switch (type) {
+	case HDMI_MPEG_UNKNOWN_FRAME:
+		return "Unknown";
+	case HDMI_MPEG_I_FRAME:
+		return "Intra-coded picture";
+	case HDMI_MPEG_B_FRAME:
+		return "Bi-predictive picture";
+	case HDMI_MPEG_P_FRAME:
+		return "Predicted picture";
+	}
+	return NULL;
+}
+
+/**
+ * hdmi_mpeg_infoframe_log() - log info of HDMI MPEG infoframe
+ * @level: logging level
+ * @dev: device
+ * @frame: HDMI MPEG infoframe
+ */
+static void hdmi_mpeg_infoframe_log(const char *level,
+				     struct device *dev,
+				     struct hdmi_mpeg_infoframe *frame)
+{
+	hdmi_infoframe_log_header(level, dev,
+				  (struct hdmi_any_infoframe *)frame);
+
+	hdmi_log("    bit rate: %d Hz\n", frame->bitrate);
+	hdmi_log("    frame type: %s\n",
+			hdmi_mpeg_picture_get_name(frame->frame_type));
+	hdmi_log("    repeated frame: %s\n",
+			frame->repeated ? "Yes" : "No");
+}
+
 static const char *
 hdmi_3d_structure_get_name(enum hdmi_3d_structure s3d_struct)
 {
@@ -987,6 +1102,9 @@ void hdmi_infoframe_log(const char *level,
 	case HDMI_INFOFRAME_TYPE_VENDOR:
 		hdmi_vendor_any_infoframe_log(level, dev, &frame->vendor);
 		break;
+	case HDMI_INFOFRAME_TYPE_MPEG:
+		hdmi_mpeg_infoframe_log(level, dev, &frame->mpeg);
+		break;
 	}
 }
 EXPORT_SYMBOL(hdmi_infoframe_log);
@@ -1138,6 +1256,41 @@ static int hdmi_audio_infoframe_unpack(struct hdmi_audio_infoframe *frame,
 }
 
 /**
+ * hdmi_mpeg_infoframe_unpack() - unpack binary buffer to a HDMI MPEG infoframe
+ * @buffer: source buffer
+ * @frame: HDMI MPEG infoframe
+ *
+ * Unpacks the information contained in binary @buffer into a structured
+ * @frame of the HDMI MPEG information frame. Also verifies the checksum as
+ * required by section 5.3.5 of the HDMI 1.4 specification.
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+static int hdmi_mpeg_infoframe_unpack(struct hdmi_mpeg_infoframe *frame,
+				     void *buffer)
+{
+	u8 *ptr = buffer;
+
+	if (ptr[0] != HDMI_INFOFRAME_TYPE_MPEG ||
+	    ptr[1] != 1 ||
+	    ptr[2] != HDMI_MPEG_INFOFRAME_SIZE)
+		return -EINVAL;
+
+	if (hdmi_infoframe_checksum(buffer, HDMI_INFOFRAME_SIZE(MPEG)) != 0)
+		return -EINVAL;
+
+	ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+	frame->bitrate = (ptr[3] << 24) | (ptr[2] << 16) |
+			 (ptr[1] << 8) | ptr[0];
+
+	frame->frame_type = ptr[4] & 0x03;
+	frame->repeated = ptr[4] & BIT(4) ? true : false;
+
+	return 0;
+}
+
+/**
  * hdmi_vendor_infoframe_unpack() - unpack binary buffer to a HDMI vendor infoframe
  * @buffer: source buffer
  * @frame: HDMI Vendor infoframe
@@ -1234,6 +1387,9 @@ int hdmi_infoframe_unpack(union hdmi_infoframe *frame, void *buffer)
 	case HDMI_INFOFRAME_TYPE_VENDOR:
 		ret = hdmi_vendor_any_infoframe_unpack(&frame->vendor, buffer);
 		break;
+	case HDMI_INFOFRAME_TYPE_MPEG:
+		ret = hdmi_mpeg_infoframe_unpack(&frame->mpeg, buffer);
+		break;
 	default:
 		ret = -EINVAL;
 		break;
diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h
index e974420..c033554 100644
--- a/include/linux/hdmi.h
+++ b/include/linux/hdmi.h
@@ -32,11 +32,13 @@ enum hdmi_infoframe_type {
 	HDMI_INFOFRAME_TYPE_AVI = 0x82,
 	HDMI_INFOFRAME_TYPE_SPD = 0x83,
 	HDMI_INFOFRAME_TYPE_AUDIO = 0x84,
+	HDMI_INFOFRAME_TYPE_MPEG = 0x85,
 };
 
 #define HDMI_IEEE_OUI 0x000c03
 #define HDMI_INFOFRAME_HEADER_SIZE  4
 #define HDMI_AVI_INFOFRAME_SIZE    13
+#define HDMI_MPEG_INFOFRAME_SIZE   10
 #define HDMI_SPD_INFOFRAME_SIZE    25
 #define HDMI_AUDIO_INFOFRAME_SIZE  10
 
@@ -297,6 +299,26 @@ int hdmi_vendor_infoframe_init(struct hdmi_vendor_infoframe *frame);
 ssize_t hdmi_vendor_infoframe_pack(struct hdmi_vendor_infoframe *frame,
 				   void *buffer, size_t size);
 
+enum hdmi_mpeg_frame_type {
+	HDMI_MPEG_UNKNOWN_FRAME = 0x00,
+	HDMI_MPEG_I_FRAME = 0x01,
+	HDMI_MPEG_B_FRAME = 0x02,
+	HDMI_MPEG_P_FRAME = 0x03,
+};
+
+struct hdmi_mpeg_infoframe {
+	enum hdmi_infoframe_type type;
+	unsigned char version;
+	unsigned char length;
+	u32 bitrate;
+	enum hdmi_mpeg_frame_type frame_type;
+	bool repeated;
+};
+
+int hdmi_mpeg_infoframe_init(struct hdmi_mpeg_infoframe *frame);
+ssize_t hdmi_mpeg_infoframe_pack(struct hdmi_mpeg_infoframe *frame,
+				   void *buffer, size_t size);
+
 union hdmi_vendor_any_infoframe {
 	struct {
 		enum hdmi_infoframe_type type;
@@ -314,6 +336,7 @@ union hdmi_vendor_any_infoframe {
  * @spd: spd infoframe
  * @vendor: union of all vendor infoframes
  * @audio: audio infoframe
+ * @mpeg: MPEG infoframe
  *
  * This is used by the generic pack function. This works since all infoframes
  * have the same header which also indicates which type of infoframe should be
@@ -325,6 +348,7 @@ union hdmi_infoframe {
 	struct hdmi_spd_infoframe spd;
 	union hdmi_vendor_any_infoframe vendor;
 	struct hdmi_audio_infoframe audio;
+	struct hdmi_mpeg_infoframe mpeg;
 };
 
 ssize_t
-- 
2.1.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCHv6 3/5] of: Add vendor prefix for Analogix Semiconductor, Inc.
  2015-12-04  8:35 ` Enric Balletbo i Serra
                   ` (2 preceding siblings ...)
  (?)
@ 2015-12-04  8:35 ` Enric Balletbo i Serra
  -1 siblings, 0 replies; 17+ messages in thread
From: Enric Balletbo i Serra @ 2015-12-04  8:35 UTC (permalink / raw)
  To: devicetree, linux-kernel, dri-devel, devel, treding
  Cc: robh+dt, pawel.moll, mark.rutland, ijc+devicetree, galak,
	airlied, gregkh, sjoerd.simons, javier, span, nathan.chung,
	djkurtz, drinkcat, laurent.pinchart, dan.carpenter, jb.tsai,
	cawa.cheng, eddie.huang, cjiao, emil.l.velikov

Analogix Semiconductor develops analog and mixed-signal devices for digital
media and communications interconnect applications.

Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Acked-by: Rob Herring <robh@kernel.org>
---

Changes since last version (requested by Rob Herring)
- Fix alphabetical order.

 Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 55df1d4..201d3e1 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -21,6 +21,7 @@ amlogic	Amlogic, Inc.
 ampire	Ampire Co., Ltd.
 ams	AMS AG
 amstaos	AMS-Taos Inc.
+analogix	Analogix Semiconductor, Inc.
 apm	Applied Micro Circuits Corporation (APM)
 aptina	Aptina Imaging
 arasan	Arasan Chip Systems
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCHv6 4/5] devicetree: Add new ANX7814 SlimPort transmitter binding.
  2015-12-04  8:35 ` Enric Balletbo i Serra
@ 2015-12-04  8:35   ` Enric Balletbo i Serra
  -1 siblings, 0 replies; 17+ messages in thread
From: Enric Balletbo i Serra @ 2015-12-04  8:35 UTC (permalink / raw)
  To: devicetree, linux-kernel, dri-devel, devel, treding
  Cc: robh+dt, pawel.moll, mark.rutland, ijc+devicetree, galak,
	airlied, gregkh, sjoerd.simons, javier, span, nathan.chung,
	djkurtz, drinkcat, laurent.pinchart, dan.carpenter, jb.tsai,
	cawa.cheng, eddie.huang, cjiao, emil.l.velikov

The ANX7814 is an ultra-low power Full-HD (1080p60) SlimPort transmitter
designed for portable devices.

You can add support to your board with current binding.

Example:

	anx7814: anx7814@38 {
		compatible = "analogix,anx7814";
		reg = <0x38>;
		interrupt-parent = <&gpio0>
		interrupts = <1 IRQ_TYPE_LEVEL_HIGH>;
		pd-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
		reset-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
		v10-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
		ports {
			anx7814_in: endpoint {
				remote-endpoint = <&hdmi0_out>;
			};
		};
	};

Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
---

Changes since last version (requested by Rob Herring)
- Specify how many ports and how many endpoints for each port
- Simplify to just port (dropping ports)
- For cable det use an interrupt instead (to a gpio controller)

 .../devicetree/bindings/video/bridge/anx7814.txt   | 39 ++++++++++++++++++++++
 1 file changed, 39 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/video/bridge/anx7814.txt

diff --git a/Documentation/devicetree/bindings/video/bridge/anx7814.txt b/Documentation/devicetree/bindings/video/bridge/anx7814.txt
new file mode 100644
index 0000000..4e68789
--- /dev/null
+++ b/Documentation/devicetree/bindings/video/bridge/anx7814.txt
@@ -0,0 +1,39 @@
+Analogix ANX7814 SlimPort (Full-HD Transmitter)
+-----------------------------------------------
+
+The ANX7814 is an ultra-low power Full-HD (1080p60) SlimPort transmitter
+designed for portable devices.
+
+Required properties:
+
+ - compatible		: "analogix,anx7814"
+ - reg			: I2C address of the device
+ - interrupt-parent	: Should be the phandle of the interrupt controller
+			  that services interrupts for this device
+ - interrupts		: Should contain the cable detection interrupt
+ - pd-gpios		: Which GPIO to use for power down
+ - reset-gpios		: Which GPIO to use for reset
+
+Optional properties:
+
+ - v10-gpios		: Which GPIO to use for V10 control.
+ - Video port for HDMI output, using the DT bindings defined in [1].
+
+[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
+
+Example:
+
+	anx7814: anx7814@38 {
+		compatible = "analogix,anx7814";
+		reg = <0x38>;
+		interrupt-parent = <&gpio0>
+		interrupts = <1 IRQ_TYPE_LEVEL_HIGH>;
+		pd-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
+		reset-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
+		v10-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
+		ports {
+			anx7814_in: endpoint {
+				remote-endpoint = <&hdmi0_out>;
+			};
+		};
+	};
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCHv6 4/5] devicetree: Add new ANX7814 SlimPort transmitter binding.
@ 2015-12-04  8:35   ` Enric Balletbo i Serra
  0 siblings, 0 replies; 17+ messages in thread
From: Enric Balletbo i Serra @ 2015-12-04  8:35 UTC (permalink / raw)
  To: devicetree, linux-kernel, dri-devel, devel, treding
  Cc: mark.rutland, drinkcat, laurent.pinchart, pawel.moll,
	ijc+devicetree, gregkh, emil.l.velikov, cawa.cheng, jb.tsai,
	sjoerd.simons, robh+dt, span, galak, javier, eddie.huang, cjiao,
	dan.carpenter, nathan.chung

The ANX7814 is an ultra-low power Full-HD (1080p60) SlimPort transmitter
designed for portable devices.

You can add support to your board with current binding.

Example:

	anx7814: anx7814@38 {
		compatible = "analogix,anx7814";
		reg = <0x38>;
		interrupt-parent = <&gpio0>
		interrupts = <1 IRQ_TYPE_LEVEL_HIGH>;
		pd-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
		reset-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
		v10-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
		ports {
			anx7814_in: endpoint {
				remote-endpoint = <&hdmi0_out>;
			};
		};
	};

Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
---

Changes since last version (requested by Rob Herring)
- Specify how many ports and how many endpoints for each port
- Simplify to just port (dropping ports)
- For cable det use an interrupt instead (to a gpio controller)

 .../devicetree/bindings/video/bridge/anx7814.txt   | 39 ++++++++++++++++++++++
 1 file changed, 39 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/video/bridge/anx7814.txt

diff --git a/Documentation/devicetree/bindings/video/bridge/anx7814.txt b/Documentation/devicetree/bindings/video/bridge/anx7814.txt
new file mode 100644
index 0000000..4e68789
--- /dev/null
+++ b/Documentation/devicetree/bindings/video/bridge/anx7814.txt
@@ -0,0 +1,39 @@
+Analogix ANX7814 SlimPort (Full-HD Transmitter)
+-----------------------------------------------
+
+The ANX7814 is an ultra-low power Full-HD (1080p60) SlimPort transmitter
+designed for portable devices.
+
+Required properties:
+
+ - compatible		: "analogix,anx7814"
+ - reg			: I2C address of the device
+ - interrupt-parent	: Should be the phandle of the interrupt controller
+			  that services interrupts for this device
+ - interrupts		: Should contain the cable detection interrupt
+ - pd-gpios		: Which GPIO to use for power down
+ - reset-gpios		: Which GPIO to use for reset
+
+Optional properties:
+
+ - v10-gpios		: Which GPIO to use for V10 control.
+ - Video port for HDMI output, using the DT bindings defined in [1].
+
+[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
+
+Example:
+
+	anx7814: anx7814@38 {
+		compatible = "analogix,anx7814";
+		reg = <0x38>;
+		interrupt-parent = <&gpio0>
+		interrupts = <1 IRQ_TYPE_LEVEL_HIGH>;
+		pd-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
+		reset-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
+		v10-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
+		ports {
+			anx7814_in: endpoint {
+				remote-endpoint = <&hdmi0_out>;
+			};
+		};
+	};
-- 
2.1.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCHv6 5/5] drm: bridge: anx78xx: Add anx78xx driver support by analogix.
  2015-12-04  8:35 ` Enric Balletbo i Serra
@ 2015-12-04  8:35   ` Enric Balletbo i Serra
  -1 siblings, 0 replies; 17+ messages in thread
From: Enric Balletbo i Serra @ 2015-12-04  8:35 UTC (permalink / raw)
  To: devicetree, linux-kernel, dri-devel, devel, treding
  Cc: robh+dt, pawel.moll, mark.rutland, ijc+devicetree, galak,
	airlied, gregkh, sjoerd.simons, javier, span, nathan.chung,
	djkurtz, drinkcat, laurent.pinchart, dan.carpenter, jb.tsai,
	cawa.cheng, eddie.huang, cjiao, emil.l.velikov

At the moment it only supports ANX7814.

The ANX7814 is an ultra-low power Full-HD (1080p60) SlimPort transmitter
designed for portable devices. The ANX7814 transforms the HDMI output of
an application processor to MyDP or DisplayPort.

The driver supports HDMI to DP pass-through mode and works using external
adapters that converts MyDP or DisplayPort to HDMI or DVI.

Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
---

Changes since last version:
- Fix auto build test ERROR (anx78xx->bridge.of_node = client->dev.of_node)
- Remove more magic numbers and use DP_ defines from hdmi.h
- Use common dp/hdmi defines instead of redefine it.
- Improve a bit the documentation of the driver.
- Improve debug messages.
- Use devm to request the irq.

 drivers/gpu/drm/bridge/Kconfig                   |    2 +
 drivers/gpu/drm/bridge/Makefile                  |    1 +
 drivers/gpu/drm/bridge/anx78xx/Kconfig           |    5 +
 drivers/gpu/drm/bridge/anx78xx/Makefile          |    4 +
 drivers/gpu/drm/bridge/anx78xx/anx78xx.h         |   44 +
 drivers/gpu/drm/bridge/anx78xx/anx78xx_main.c    |  334 +++
 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.c | 3210 ++++++++++++++++++++++
 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.h |  110 +
 drivers/gpu/drm/bridge/anx78xx/slimport_tx_reg.h |  737 +++++
 9 files changed, 4447 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/Kconfig
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/Makefile
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/anx78xx.h
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/anx78xx_main.c
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.c
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.h
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/slimport_tx_reg.h

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 6dddd39..1d92bc1 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -41,4 +41,6 @@ config DRM_PARADE_PS8622
 	---help---
 	  Parade eDP-LVDS bridge chip driver.
 
+source "drivers/gpu/drm/bridge/anx78xx/Kconfig"
+
 endmenu
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index d4e28be..0e9fdb4 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -4,3 +4,4 @@ obj-$(CONFIG_DRM_DW_HDMI) += dw_hdmi.o
 obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw_hdmi-ahb-audio.o
 obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
 obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
+obj-$(CONFIG_DRM_ANX78XX) += anx78xx/
diff --git a/drivers/gpu/drm/bridge/anx78xx/Kconfig b/drivers/gpu/drm/bridge/anx78xx/Kconfig
new file mode 100644
index 0000000..5be362d
--- /dev/null
+++ b/drivers/gpu/drm/bridge/anx78xx/Kconfig
@@ -0,0 +1,5 @@
+config DRM_ANX78XX
+	tristate "Analogix ANX78XX bridge"
+	help
+	  ANX78XX is a HD video transmitter chip over micro-USB
+	  connector for smartphone device.
diff --git a/drivers/gpu/drm/bridge/anx78xx/Makefile b/drivers/gpu/drm/bridge/anx78xx/Makefile
new file mode 100644
index 0000000..a843733
--- /dev/null
+++ b/drivers/gpu/drm/bridge/anx78xx/Makefile
@@ -0,0 +1,4 @@
+obj-${CONFIG_DRM_ANX78XX} :=  anx78xx.o
+
+anx78xx-y += anx78xx_main.o
+anx78xx-y += slimport_tx_drv.o
diff --git a/drivers/gpu/drm/bridge/anx78xx/anx78xx.h b/drivers/gpu/drm/bridge/anx78xx/anx78xx.h
new file mode 100644
index 0000000..6548918
--- /dev/null
+++ b/drivers/gpu/drm/bridge/anx78xx/anx78xx.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright(c) 2015, Analogix Semiconductor. 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 __ANX78xx_H
+#define __ANX78xx_H
+
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/gpio/consumer.h>
+
+#include <drm/drm_crtc.h>
+
+struct anx78xx_platform_data {
+	struct gpio_desc *gpiod_pd;
+	struct gpio_desc *gpiod_reset;
+	struct gpio_desc *gpiod_v10;
+};
+
+struct anx78xx {
+	struct drm_bridge bridge;
+	struct i2c_client *client;
+	struct anx78xx_platform_data *pdata;
+	struct delayed_work work;
+	struct workqueue_struct *workqueue;
+};
+
+void anx78xx_poweron(struct anx78xx *data);
+void anx78xx_poweroff(struct anx78xx *data);
+bool anx78xx_cable_is_detected(struct anx78xx *anx78xx);
+
+#endif
diff --git a/drivers/gpu/drm/bridge/anx78xx/anx78xx_main.c b/drivers/gpu/drm/bridge/anx78xx/anx78xx_main.c
new file mode 100644
index 0000000..0101c23
--- /dev/null
+++ b/drivers/gpu/drm/bridge/anx78xx/anx78xx_main.c
@@ -0,0 +1,334 @@
+/*
+ * Copyright(c) 2015, Analogix Semiconductor. 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.
+ *
+ */
+#include <drm/drm_dp_helper.h>
+
+#include <linux/async.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+#include <linux/types.h>
+
+#include "anx78xx.h"
+#include "slimport_tx_drv.h"
+
+void anx78xx_poweron(struct anx78xx *anx78xx)
+{
+	struct anx78xx_platform_data *pdata = anx78xx->pdata;
+
+	if (pdata->gpiod_v10) {
+		gpiod_set_value_cansleep(pdata->gpiod_v10, 1);
+		usleep_range(1000, 2000);
+	}
+
+	gpiod_set_value_cansleep(pdata->gpiod_reset, 0);
+	usleep_range(1000, 2000);
+
+	gpiod_set_value_cansleep(pdata->gpiod_pd, 0);
+	usleep_range(1000, 2000);
+
+	gpiod_set_value_cansleep(pdata->gpiod_reset, 1);
+}
+
+void anx78xx_poweroff(struct anx78xx *anx78xx)
+{
+	struct anx78xx_platform_data *pdata = anx78xx->pdata;
+
+	if (pdata->gpiod_v10) {
+		gpiod_set_value_cansleep(pdata->gpiod_v10, 0);
+		usleep_range(1000, 2000);
+	}
+
+	gpiod_set_value_cansleep(pdata->gpiod_reset, 0);
+	usleep_range(1000, 2000);
+
+	gpiod_set_value_cansleep(pdata->gpiod_pd, 1);
+	usleep_range(1000, 2000);
+}
+
+static int anx78xx_init_gpio(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	struct anx78xx_platform_data *pdata = anx78xx->pdata;
+
+	/* gpio for chip power down */
+	pdata->gpiod_pd = devm_gpiod_get(dev, "pd", GPIOD_OUT_HIGH);
+	if (IS_ERR(pdata->gpiod_pd)) {
+		dev_err(dev, "unable to claim pd gpio\n");
+		return PTR_ERR(pdata->gpiod_pd);
+	}
+
+	/* gpio for chip reset */
+	pdata->gpiod_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(pdata->gpiod_reset)) {
+		dev_err(dev, "unable to claim reset gpio\n");
+		return PTR_ERR(pdata->gpiod_reset);
+	}
+
+	/* gpio for V10 power control */
+	pdata->gpiod_v10 = devm_gpiod_get_optional(dev, "v10", GPIOD_OUT_LOW);
+	if (IS_ERR(pdata->gpiod_v10)) {
+		dev_err(dev, "unable to claim v10 gpio\n");
+		return PTR_ERR(pdata->gpiod_v10);
+	}
+
+	return 0;
+}
+
+/*
+ * HPD IRQ Event: HPD pulse width greater than 0.25ms but narrower than 2ms
+ * Hot Unplug Event: HPD pulse stays low longer than 2ms.
+ *
+ * AP just monitor HPD pulse high in this irq. If HPD is high, the driver
+ * will power on the chip, and then the driver controls when to power down
+ * the chip, if HPD event is HPD IRQ, the driver deals with IRQ event from
+ * downstream, finally, if HPD event is Hot Plug, the driver power down the
+ * chip.
+ */
+static irqreturn_t anx78xx_cable_isr(int irq, void *data)
+{
+	struct anx78xx *anx78xx = data;
+
+	queue_delayed_work(anx78xx->workqueue, &anx78xx->work, 0);
+
+	return IRQ_HANDLED;
+}
+
+static void anx78xx_work_func(struct work_struct *work)
+{
+	struct anx78xx *anx78xx = container_of(work, struct anx78xx,
+					       work.work);
+
+	if (sp_main_process(anx78xx))
+		queue_delayed_work(anx78xx->workqueue, &anx78xx->work,
+				   msecs_to_jiffies(500));
+	else
+		cancel_delayed_work(&anx78xx->work);
+}
+
+static inline struct anx78xx *bridge_to_anx78xx(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct anx78xx, bridge);
+}
+
+static int anx78xx_bridge_attach(struct drm_bridge *bridge)
+{
+	return 0;
+}
+
+static bool anx78xx_bridge_mode_fixup(struct drm_bridge *bridge,
+				      const struct drm_display_mode *mode,
+				      struct drm_display_mode *adjusted_mode)
+{
+	struct anx78xx *anx78xx = bridge_to_anx78xx(bridge);
+
+	dev_dbg(&anx78xx->client->dev, "mode_fixup %d<%d; %d; %d\n",
+		sp_get_link_bandwidth_from_edid(anx78xx), DP_LINK_BW_5_4,
+		mode->clock, mode->flags & DRM_MODE_FLAG_INTERLACE);
+
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		return false;
+
+	/* Max 720p at 2.7 Ghz, one lane */
+	if (sp_get_link_bandwidth_from_edid(anx78xx) < DP_LINK_BW_5_4 &&
+	    mode->clock > 74250)
+		return false;
+
+	/* Max 1200p at 5.4 Ghz, one lane */
+	if (mode->clock > 154000)
+		return false;
+
+	return true;
+}
+
+static void anx78xx_bridge_disable(struct drm_bridge *bridge) {}
+static void anx78xx_bridge_post_disable(struct drm_bridge *bridge) {}
+static void anx78xx_bridge_mode_set(struct drm_bridge *bridge,
+				    struct drm_display_mode *mode,
+				    struct drm_display_mode *adjusted_mode) {}
+static void anx78xx_bridge_pre_enable(struct drm_bridge *bridge) {}
+static void anx78xx_bridge_enable(struct drm_bridge *bridge) {}
+
+static const struct drm_bridge_funcs anx78xx_bridge_funcs = {
+	.attach		= anx78xx_bridge_attach,
+	.mode_fixup	= anx78xx_bridge_mode_fixup,
+	.disable	= anx78xx_bridge_disable,
+	.post_disable	= anx78xx_bridge_post_disable,
+	.mode_set	= anx78xx_bridge_mode_set,
+	.pre_enable	= anx78xx_bridge_pre_enable,
+	.enable		= anx78xx_bridge_enable,
+};
+
+static int anx78xx_i2c_probe(struct i2c_client *client,
+			     const struct i2c_device_id *id)
+{
+	struct anx78xx *anx78xx;
+	int ret;
+
+	if (!i2c_check_functionality(client->adapter,
+				     I2C_FUNC_SMBUS_I2C_BLOCK)) {
+		dev_err(&client->dev, "i2c bus does not support the device\n");
+		return -ENODEV;
+	}
+
+	anx78xx = devm_kzalloc(&client->dev, sizeof(*anx78xx), GFP_KERNEL);
+	if (!anx78xx)
+		return -ENOMEM;
+
+	anx78xx->pdata = devm_kzalloc(&client->dev,
+				      sizeof(struct anx78xx_platform_data),
+				      GFP_KERNEL);
+	if (!anx78xx->pdata)
+		return -ENOMEM;
+
+#ifdef CONFIG_OF
+	anx78xx->bridge.of_node = client->dev.of_node;
+#endif
+	anx78xx->bridge.funcs = &anx78xx_bridge_funcs;
+	ret = drm_bridge_add(&anx78xx->bridge);
+	if (ret < 0) {
+		dev_err(&client->dev, "add drm bridge failed\n");
+		return ret;
+	}
+
+	anx78xx->client = client;
+
+	i2c_set_clientdata(client, anx78xx);
+
+	ret = anx78xx_init_gpio(anx78xx);
+	if (ret) {
+		dev_err(&client->dev, "failed to initialize gpios\n");
+		goto fail_remove_bridge;
+	}
+
+	INIT_DELAYED_WORK(&anx78xx->work, anx78xx_work_func);
+
+	anx78xx->workqueue = create_singlethread_workqueue("anx78xx_work");
+	if (!anx78xx->workqueue) {
+		dev_err(&client->dev, "failed to create work queue\n");
+		ret = -ENOMEM;
+		goto fail_remove_bridge;
+	}
+
+	ret = sp_system_init(anx78xx);
+	if (ret) {
+		dev_err(&client->dev, "failed to initialize anx78xx\n");
+		goto fail_free_wq;
+	}
+
+	ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+					anx78xx_cable_isr,
+					IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+					"anx78xx-cable-det", anx78xx);
+	if (ret) {
+		dev_err(&client->dev, "failed to request threaded irq\n");
+		goto fail_free_wq;
+	}
+
+	ret = irq_set_irq_wake(client->irq, 1);
+	if (ret) {
+		dev_err(&client->dev, "failed to set irq wake\n");
+		goto fail_free_wq;
+	}
+
+	ret = enable_irq_wake(client->irq);
+	if (ret) {
+		dev_err(&client->dev, "failed to enable irq wake\n");
+		goto fail_free_wq;
+	}
+
+	/* enable driver */
+	queue_delayed_work(anx78xx->workqueue, &anx78xx->work, 0);
+
+	return 0;
+
+fail_free_wq:
+	destroy_workqueue(anx78xx->workqueue);
+fail_remove_bridge:
+	drm_bridge_remove(&anx78xx->bridge);
+	return ret;
+}
+
+static int anx78xx_i2c_remove(struct i2c_client *client)
+{
+	struct anx78xx *anx78xx = i2c_get_clientdata(client);
+
+	destroy_workqueue(anx78xx->workqueue);
+	drm_bridge_remove(&anx78xx->bridge);
+
+	return 0;
+}
+
+static int anx78xx_i2c_suspend(struct device *dev)
+{
+	struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+	struct anx78xx *anx78xx = i2c_get_clientdata(client);
+
+	/* REVISIT: suspend */
+	cancel_delayed_work_sync(&anx78xx->work);
+	flush_workqueue(anx78xx->workqueue);
+	anx78xx_poweroff(anx78xx);
+	sp_variable_init(anx78xx);
+
+	return 0;
+}
+
+static int anx78xx_i2c_resume(struct device *dev)
+{
+	struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+	struct anx78xx *anx78xx = i2c_get_clientdata(client);
+
+	queue_delayed_work(anx78xx->workqueue, &anx78xx->work, 0);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(anx78xx_i2c_pm_ops,
+			anx78xx_i2c_suspend, anx78xx_i2c_resume);
+
+static const struct i2c_device_id anx78xx_id[] = {
+	{"anx7814", 0},
+	{ /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(i2c, anx78xx_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id anx78xx_match_table[] = {
+	{.compatible = "analogix,anx7814",},
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, anx78xx_match_table);
+#endif
+
+static struct i2c_driver anx78xx_driver = {
+	.driver = {
+		   .name = "anx7814",
+		   .pm = &anx78xx_i2c_pm_ops,
+		   .of_match_table = of_match_ptr(anx78xx_match_table),
+		  },
+	.probe = anx78xx_i2c_probe,
+	.remove = anx78xx_i2c_remove,
+	.id_table = anx78xx_id,
+};
+
+module_i2c_driver(anx78xx_driver);
+
+MODULE_DESCRIPTION("Slimport transmitter ANX78XX driver");
+MODULE_AUTHOR("Junhua Xia <jxia@analogixsemi.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("1.1");
diff --git a/drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.c b/drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.c
new file mode 100644
index 0000000..4b77270
--- /dev/null
+++ b/drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.c
@@ -0,0 +1,3210 @@
+/*
+ * Copyright(c) 2015, Analogix Semiconductor. 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.
+ *
+ */
+#include <drm/drm_crtc.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_edid.h>
+
+#include <linux/delay.h>
+#include <linux/hdmi.h>
+#include <linux/types.h>
+
+#include "anx78xx.h"
+#include "slimport_tx_drv.h"
+
+#define XTAL_27M	270
+#define XTAL_CLK	XTAL_27M
+
+struct slimport {
+	bool	hdcp_enabled;	/* HDCP control enable/ disable from AP */
+
+	u8	tx_test_bw;
+	bool	tx_test_lt;
+	bool	tx_test_edid;
+
+	u8	changed_bandwidth;
+
+	bool	need_clean_status;
+
+	u8	hdcp_error_count;
+	u8	hdcp_fail_count;
+	u8	audio_stable_count;	/* Audio stable counter */
+
+	u8	edid_blocks[2 * EDID_LENGTH];
+
+	bool	read_edid_flag;
+	bool	down_sample_en;
+
+	/* Infoframes */
+	union hdmi_infoframe	frame;
+
+	/* Interrupt status registers */
+	u8	common_int[4];
+	u8	dp_int;
+	u8	sp_hdmi_int[7];
+
+	enum sp_tx_state		tx_system_state;
+	enum audio_output_status	tx_ao_state;
+	enum video_output_status	tx_vo_state;
+	enum sp_tx_lt_status		tx_lt_state;
+	enum hdcp_status		hdcp_state;
+	enum repeater_status		repeater_state;
+};
+
+static struct slimport sp;
+
+static const u16 chipid_list[] = {
+	0x7802,
+	0x7806,
+	0x7810,
+	0x7812,
+	0x7814,
+	0x7816,
+	0x7818,
+};
+
+static void sp_hdmi_new_avi_int(struct anx78xx *anx78xx);
+static void sp_hdmi_new_vsi_int(struct anx78xx *anx78xx);
+static void sp_print_system_state(struct anx78xx *anx78xx,
+				  enum sp_tx_state state);
+static void sp_show_information(struct anx78xx *anx78xx);
+
+/**
+ * sp_reg_read: Read a value from a single register.
+ *
+ * @anx78xx: Device to read from.
+ * @addr: Address to read from.
+ * @reg: Register to be read from.
+ * @val: Pointer to store read value.
+ *
+ * A value of zero will be returned on success, a negative errno will
+ * be returned in error cases.
+ */
+static int sp_reg_read(struct anx78xx *anx78xx, u8 addr, u8 offset, u8 *val)
+{
+	int ret;
+	struct i2c_client *client = anx78xx->client;
+
+	client->addr = addr >> 1;
+
+	ret = i2c_smbus_read_byte_data(client, offset);
+	if (ret < 0) {
+		dev_err(&client->dev, "failed to read i2c addr=%x\n", addr);
+		return ret;
+	}
+
+	*val = ret;
+
+	return 0;
+}
+
+/**
+ * sp_reg_write(): Write a value to a single register
+ *
+ * @anx78xx: Device to read from.
+ * @addr: Address to write to.
+ * @offset: Byte interpreted by slave.
+ * @val: Value to be written.
+ *
+ * A value of zero will be returned on success, a negative errno will
+ * be returned in error cases.
+ */
+static int sp_reg_write(struct anx78xx *anx78xx, u8 addr, u8 offset, u8 val)
+{
+	int ret;
+	struct i2c_client *client = anx78xx->client;
+
+	client->addr = addr >> 1;
+
+	ret = i2c_smbus_write_byte_data(client, offset, val);
+	if (ret < 0)
+		dev_err(&client->dev, "failed to write i2c addr=%x\n", addr);
+
+	return ret;
+}
+
+/**
+ * sp_reg_update_bits: Perform a read/modify/write cycle on the register.
+ *
+ * @anx78xx: Device to read from.
+ * @addr: Address to write to.
+ * @offset: Byte interpreted by slave.
+ * @mask: Bitmask to change.
+ * @val: New value for bitmask.
+ *
+ * Returns zero for success, a negative number on error.
+ */
+static int sp_reg_update_bits(struct anx78xx *anx78xx, u8 addr, u8 offset,
+			      u8 mask, u8 val)
+{
+	int ret;
+	u8 orig, tmp;
+
+	ret = sp_reg_read(anx78xx, addr, offset, &orig);
+	if (ret < 0)
+		return ret;
+
+	tmp = orig & ~mask;
+	tmp |= val & mask;
+
+	return sp_reg_write(anx78xx, addr, offset, tmp);
+}
+
+/**
+ * sp_reg_set_bits: Perform a read/write cycle to set bits in register.
+ *
+ * @anx78xx: Device to read from.
+ * @addr: Address to write to.
+ * @offset: Byte interpreted by slave.
+ * @mask: Bitmask to change.
+ *
+ * Returns zero for success, a negative number on error.
+ */
+static inline int sp_reg_set_bits(struct anx78xx *anx78xx, u8 addr,
+				  u8 offset, u8 mask)
+{
+	return sp_reg_update_bits(anx78xx, addr, offset, mask, mask);
+}
+
+/**
+ * sp_reg_clear_bits: Perform a read/write cycle to clear bits in register.
+ *
+ * @anx78xx: Device to read from.
+ * @addr: Address to write to.
+ * @offset: Byte interpreted by slave.
+ * @mask: Bitmask to change.
+ *
+ * Returns zero for success, a negative number on error.
+ */
+static inline int sp_reg_clear_bits(struct anx78xx *anx78xx, u8 addr,
+				    u8 offset, u8 mask)
+{
+	return sp_reg_update_bits(anx78xx, addr, offset, mask, 0);
+}
+
+static inline void sp_video_mute(struct anx78xx *anx78xx, bool enable)
+{
+	if (enable)
+		sp_reg_set_bits(anx78xx, TX_P2, SP_VID_CTRL1_REG,
+				SP_VIDEO_MUTE);
+	else
+		sp_reg_clear_bits(anx78xx, TX_P2, SP_VID_CTRL1_REG,
+				  SP_VIDEO_MUTE);
+}
+
+static inline void sp_hdmi_mute_audio(struct anx78xx *anx78xx, bool enable)
+{
+	if (enable)
+		sp_reg_set_bits(anx78xx, RX_P0, SP_HDMI_MUTE_CTRL_REG,
+				SP_AUD_MUTE);
+	else
+		sp_reg_clear_bits(anx78xx, RX_P0, SP_HDMI_MUTE_CTRL_REG,
+				  SP_AUD_MUTE);
+}
+
+static inline void sp_hdmi_mute_video(struct anx78xx *anx78xx, bool enable)
+{
+	if (enable)
+		sp_reg_set_bits(anx78xx, RX_P0, SP_HDMI_MUTE_CTRL_REG,
+				SP_VID_MUTE);
+	else
+		sp_reg_clear_bits(anx78xx, RX_P0, SP_HDMI_MUTE_CTRL_REG,
+				  SP_VID_MUTE);
+}
+
+static inline void sp_addronly_set(struct anx78xx *anx78xx, bool enable)
+{
+	if (enable)
+		sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+				SP_ADDR_ONLY);
+	else
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+				  SP_ADDR_ONLY);
+}
+
+static inline void sp_set_link_bandwidth(struct anx78xx *anx78xx, u8 bw)
+{
+	sp_reg_write(anx78xx, TX_P0, SP_DP_MAIN_LINK_BW_SET_REG, bw);
+}
+
+static inline u8 sp_get_link_bandwidth(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	sp_reg_read(anx78xx, TX_P0, SP_DP_MAIN_LINK_BW_SET_REG, &val);
+
+	return val & SP_LINK_BW_SET_MASK;
+}
+
+static inline bool sp_get_pll_lock_status(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	sp_reg_read(anx78xx, TX_P0, SP_DP_DEBUG1_REG, &val);
+
+	return (val & SP_DEBUG_PLL_LOCK) != 0;
+}
+
+static inline void sp_gen_m_clk_with_downspreading(struct anx78xx *anx78xx)
+{
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_M_CALCULATION_CTRL_REG,
+			SP_M_GEN_CLK_SEL);
+}
+
+static inline void sp_gen_m_clk_without_downspreading(struct anx78xx *anx78xx)
+{
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_M_CALCULATION_CTRL_REG,
+			  SP_M_GEN_CLK_SEL);
+}
+
+static inline void sp_hdmi_set_hpd(struct anx78xx *anx78xx, bool enable)
+{
+	if (enable)
+		sp_reg_set_bits(anx78xx, TX_P2, SP_VID_CTRL3_REG, SP_HPD_OUT);
+	else
+		sp_reg_clear_bits(anx78xx, TX_P2, SP_VID_CTRL3_REG,
+				  SP_HPD_OUT);
+}
+
+static inline void sp_hdmi_set_termination(struct anx78xx *anx78xx,
+					   bool enable)
+{
+	if (enable)
+		sp_reg_clear_bits(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 7,
+				  SP_PD_RT);
+	else
+		sp_reg_set_bits(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 7,
+				SP_PD_RT);
+}
+
+static inline bool sp_hdcp_repeater_mode(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	sp_reg_read(anx78xx, RX_P1, SP_HDCP_BCAPS_SHADOW_REG, &val);
+
+	return (val & SP_BCAPS_REPEATER);
+}
+
+static inline void sp_clean_hdcp_status(struct anx78xx *anx78xx)
+{
+	sp_reg_write(anx78xx, TX_P0, SP_HDCP_CTRL0_REG,
+		     SP_BKSV_SRM_PASS | SP_KSVLIST_VLD);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_HDCP_CTRL0_REG, SP_RE_AUTH);
+	usleep_range(2000, 4000);
+}
+
+static const u8 dp_tx_output_precise_tune_bits[20] = {
+	0x01, 0x03, 0x07, 0x7f, 0x71, 0x6b, 0x7f,
+	0x73, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00,
+	0x0c, 0x42, 0x1e, 0x3e, 0x72, 0x7e,
+};
+
+static void sp_link_phy_initialization(struct anx78xx *anx78xx)
+{
+	int i;
+
+	/*
+	 * REVISIT : It is writing to a RESERVED bits in Analog Control 0
+	 * register.
+	 */
+	sp_reg_write(anx78xx, TX_P2, SP_ANALOG_CTRL0_REG, 0x02);
+
+	/*
+	 * Write DP TX output emphasis precise tune bits.
+	 */
+	for (i = 0; i < ARRAY_SIZE(dp_tx_output_precise_tune_bits); i++)
+		sp_reg_write(anx78xx, TX_P1, SP_DP_TX_LT_CTRL0_REG + i,
+			     dp_tx_output_precise_tune_bits[i]);
+}
+
+static void sp_set_system_state(struct anx78xx *anx78xx,
+				enum sp_tx_state new_state)
+{
+	u8 val;
+
+	if ((sp.tx_system_state >= STATE_LINK_TRAINING) &&
+	    (new_state < STATE_LINK_TRAINING))
+		sp_reg_set_bits(anx78xx, TX_P0, SP_DP_ANALOG_POWER_DOWN_REG,
+				SP_CH0_PD);
+
+	if (sp.tx_system_state >= STATE_LINK_TRAINING) {
+		if (new_state >= STATE_AUDIO_OUTPUT) {
+			sp_hdmi_mute_audio(anx78xx, true);
+		} else {
+			sp_hdmi_mute_video(anx78xx, true);
+			sp_video_mute(anx78xx, true);
+		}
+	}
+
+	if (!sp_hdcp_repeater_mode(anx78xx)) {
+		if (sp.tx_system_state >= STATE_HDCP_AUTH &&
+		    new_state <= STATE_HDCP_AUTH) {
+			sp_reg_read(anx78xx, TX_P0, SP_HDCP_CTRL0_REG, &val);
+			if (val & ~(SP_KSVLIST_VLD | SP_BKSV_SRM_PASS))
+				sp_clean_hdcp_status(anx78xx);
+		}
+	} else {
+		if (sp.tx_system_state > STATE_LINK_TRAINING &&
+		    new_state <= STATE_LINK_TRAINING) {
+			/* Inform AP to re-auth */
+			sp_hdmi_set_hpd(anx78xx, false);
+			sp_hdmi_set_termination(anx78xx, false);
+			msleep(50);
+		}
+	}
+
+	sp.tx_system_state = new_state;
+	sp.hdcp_state = HDCP_CAPABLE_CHECK;
+	sp.tx_lt_state = LT_INIT;
+	sp.tx_vo_state = VO_WAIT_VIDEO_STABLE;
+	/* Reset audio stable counter */
+	sp.audio_stable_count = 0;
+	sp_print_system_state(anx78xx, sp.tx_system_state);
+}
+
+static inline void sp_reg_hardware_reset(struct anx78xx *anx78xx)
+{
+	sp_reg_set_bits(anx78xx, TX_P2, SP_RESET_CTRL1_REG, SP_HW_RST);
+	sp_variable_init(anx78xx);
+	sp_set_system_state(anx78xx, STATE_SP_INITIALIZED);
+	msleep(500);
+}
+
+static int sp_wait_aux_op_finish(struct anx78xx *anx78xx)
+{
+	u8 errcnt;
+	u8 val;
+	struct device *dev = &anx78xx->client->dev;
+
+	errcnt = 150;
+	while (errcnt--) {
+		sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
+		if (!(val & SP_AUX_EN))
+			break;
+		usleep_range(2000, 4000);
+	}
+
+	if (!errcnt) {
+		dev_err(dev, "aux operate failed!\n");
+		return -1;
+	}
+
+	sp_reg_read(anx78xx, TX_P0, SP_AUX_CH_STATUS_REG, &val);
+	if (val & SP_AUX_STATUS) {
+		dev_err(dev, "wait aux operation status %.2x\n", val);
+		return -1;
+	}
+
+	return 0;
+}
+
+static void sp_print_system_state(struct anx78xx *anx78xx,
+				  enum sp_tx_state state)
+{
+	static const char *const name[] = {
+		[STATE_CABLE_UNPLUGGED]		= "Cable is pulled out",
+		[STATE_WAITING_CABLE_PLUG]	= "Waiting cable plug",
+		[STATE_SP_INITIALIZED]		= "SlimPort initialized",
+		[STATE_SINK_CONNECTION]		= "Sink connection",
+		[STATE_PARSE_EDID]		= "Parse EDID",
+		[STATE_LINK_TRAINING]		= "Link training",
+		[STATE_VIDEO_OUTPUT]		= "Video output",
+		[STATE_HDCP_AUTH]		= "HDCP authentication",
+		[STATE_AUDIO_OUTPUT]		= "Audio output",
+		[STATE_PLAY_BACK]		= "Playback",
+	};
+
+	dev_dbg(&anx78xx->client->dev, "SlimPort state: %s\n",
+		(state < ARRAY_SIZE(name)) ? name[state] : "INVALID");
+}
+
+static void sp_reset_aux(struct anx78xx *anx78xx)
+{
+	sp_reg_set_bits(anx78xx, TX_P2, SP_RESET_CTRL2_REG, SP_AUX_RST);
+	sp_reg_clear_bits(anx78xx, TX_P2, SP_RESET_CTRL2_REG, SP_AUX_RST);
+}
+
+static void sp_write_dpcd_addr(struct anx78xx *anx78xx, unsigned int addr)
+{
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_7_0_REG, addr & 0xff);
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_15_8_REG,
+		     (addr & 0xff00) >> 8);
+
+	/*
+	 * DP AUX CH Address Register #2, only update bits[3:0]
+	 * [7:4] RESERVED
+	 * [3:0] AUX_ADDR[19:16], Register control AUX CH address.
+	 */
+	sp_reg_update_bits(anx78xx, TX_P0, SP_AUX_ADDR_19_16_REG,
+			   SP_AUX_ADDR_19_16_MASK, (addr & 0xf0000) >> 16);
+}
+
+static int sp_dp_read_bytes_from_dpcd(struct anx78xx *anx78xx,
+				      unsigned int addr, u8 count, u8 *buf)
+{
+	u8 val, val1, i;
+
+	/* Buffer size of AUX CH is 16 */
+	if (count > 16)
+		return -1;
+
+	/* Clear AUX CH data buffer */
+	sp_reg_write(anx78xx, TX_P0, SP_BUF_DATA_COUNT_REG, SP_BUF_CLR);
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG,
+		     ((count - 1) << SP_AUX_LENGTH_SHIFT) | 0x09);
+	sp_write_dpcd_addr(anx78xx, addr);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, SP_AUX_EN);
+	usleep_range(2000, 4000);
+
+	if (sp_wait_aux_op_finish(anx78xx)) {
+		sp_reg_read(anx78xx, TX_P2, SP_DP_INT_STATUS_REG, &val);
+		sp_reg_read(anx78xx, TX_P0, SP_DP_DEBUG1_REG, &val1);
+		if (!(val1 & SP_POLLING_EN) || (val & SP_POLLING_ERR))
+			sp_reset_aux(anx78xx);
+		return -1;
+	}
+
+	for (i = 0; i < count; i++) {
+		sp_reg_read(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG + i, &val);
+		buf[i] = val;
+	}
+
+	return 0;
+}
+
+static int sp_dp_write_bytes_to_dpcd(struct anx78xx *anx78xx,
+				     unsigned int addr, u8 count, u8 *buf)
+{
+	int i;
+
+	/* Buffer size of AUX CH is 16 */
+	if (count > 16)
+		return -1;
+
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG,
+		     ((count - 1) << SP_AUX_LENGTH_SHIFT) | 0x08);
+	sp_write_dpcd_addr(anx78xx, addr);
+	for (i = 0; i < count; i++)
+		sp_reg_write(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG + i, buf[i]);
+
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, SP_AUX_EN);
+	return sp_wait_aux_op_finish(anx78xx);
+}
+
+static void sp_block_power_ctrl(struct anx78xx *anx78xx,
+				enum sp_tx_power_block sp_tx_pd_block,
+				bool power)
+{
+	if (power)
+		sp_reg_clear_bits(anx78xx, TX_P2, SP_POWERDOWN_CTRL_REG,
+				  sp_tx_pd_block);
+	else
+		sp_reg_set_bits(anx78xx, TX_P2, SP_POWERDOWN_CTRL_REG,
+				sp_tx_pd_block);
+
+	dev_dbg(&anx78xx->client->dev,
+		"sp_tx_power_on: %.2x\n", sp_tx_pd_block);
+}
+
+void sp_variable_init(struct anx78xx *anx78xx)
+{
+	sp.hdcp_enabled = false;
+
+	sp.tx_system_state = STATE_WAITING_CABLE_PLUG;
+	sp_print_system_state(anx78xx, sp.tx_system_state);
+
+	sp.read_edid_flag = false;
+
+	memset(sp.edid_blocks, 0, sizeof(*sp.edid_blocks));
+
+	sp.tx_lt_state = LT_INIT;
+	sp.hdcp_state = HDCP_CAPABLE_CHECK;
+	sp.repeater_state = HDCP_DONE;
+	sp.tx_vo_state = VO_WAIT_VIDEO_STABLE;
+	sp.tx_ao_state = AO_INIT;
+	sp.changed_bandwidth = DP_LINK_BW_5_4;
+
+	sp.hdcp_error_count = 0;
+	sp.hdcp_fail_count = 0;
+	sp.audio_stable_count = 0;
+
+	sp.tx_test_lt = false;
+	sp.tx_test_bw = 0;
+	sp.tx_test_edid = false;
+}
+
+static void sp_hdmi_tmds_phy_initialization(struct anx78xx *anx78xx)
+{
+	sp_reg_write(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 1, 0x90);
+	sp_reg_write(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 2, 0xa9);
+	sp_reg_write(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 6, 0x92);
+	sp_reg_write(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 7, 0x80);
+	sp_reg_write(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 20, 0xf2);
+}
+
+static void sp_hdmi_initialization(struct anx78xx *anx78xx)
+{
+	sp_reg_write(anx78xx, RX_P0, SP_HDMI_MUTE_CTRL_REG, SP_AUD_MUTE |
+		     SP_VID_MUTE);
+	sp_reg_set_bits(anx78xx, RX_P0, SP_CHIP_CTRL_REG, SP_MAN_HDMI5V_DET |
+			SP_PLLLOCK_CKDT_EN | SP_DIGITAL_CKDT_EN);
+
+	sp_reg_set_bits(anx78xx, RX_P0, SP_SOFTWARE_RESET1_REG,
+			SP_HDCP_MAN_RST | SP_SW_MAN_RST | SP_TMDS_RST |
+			SP_VIDEO_RST);
+	sp_reg_clear_bits(anx78xx, RX_P0, SP_SOFTWARE_RESET1_REG,
+			  SP_HDCP_MAN_RST | SP_SW_MAN_RST | SP_TMDS_RST |
+			  SP_VIDEO_RST);
+
+	/* Sync detect change, GP set mute */
+	sp_reg_set_bits(anx78xx, RX_P0, SP_AUD_EXCEPTION_ENABLE_BASE + 1,
+			BIT(5) | BIT(6));
+	sp_reg_set_bits(anx78xx, RX_P0, SP_AUD_EXCEPTION_ENABLE_BASE + 3,
+			SP_AEC_EN21);
+	sp_reg_set_bits(anx78xx, RX_P0, SP_AUDVID_CTRL_REG, SP_AVC_EN |
+			SP_AAC_OE | SP_AAC_EN);
+
+	sp_reg_clear_bits(anx78xx, RX_P0, SP_SYSTEM_POWER_DOWN1_REG,
+			  SP_PWDN_CTRL);
+
+	sp_reg_set_bits(anx78xx, RX_P0, SP_VID_DATA_RANGE_CTRL_REG,
+			SP_R2Y_INPUT_LIMIT);
+	sp_reg_write(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 22, 0xc4);
+	sp_reg_write(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 23, 0x18);
+
+	/* Enable DDC stretch */
+	sp_reg_write(anx78xx, TX_P0, SP_DP_EXTRA_I2C_DEV_ADDR_REG,
+		     SP_I2C_EXTRA_ADDR);
+
+	sp_hdmi_tmds_phy_initialization(anx78xx);
+	sp_hdmi_set_hpd(anx78xx, false);
+	sp_hdmi_set_termination(anx78xx, false);
+}
+
+static void sp_xtal_clk_sel(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	sp_reg_update_bits(anx78xx, TX_P2, SP_ANALOG_DEBUG2_REG,
+			   SP_XTAL_FRQ | SP_FORCE_SW_OFF_BYPASS,
+			   SP_XTAL_FRQ_27M);
+
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL3_REG,
+		     XTAL_CLK & SP_WAIT_COUNTER_7_0_MASK);
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL4_REG,
+		     ((XTAL_CLK & 0xff00) >> 2) | (XTAL_CLK / 10));
+
+	sp_reg_write(anx78xx, TX_P0, SP_I2C_GEN_10US_TIMER0_REG,
+		     XTAL_CLK & 0xff);
+	sp_reg_write(anx78xx, TX_P0, SP_I2C_GEN_10US_TIMER1_REG,
+		     (XTAL_CLK & 0xff00) >> 8);
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_MISC_CTRL_REG,
+		     XTAL_CLK / 10 - 1);
+
+	sp_reg_read(anx78xx, RX_P0, SP_HDMI_US_TIMER_CTRL_REG, &val);
+	sp_reg_write(anx78xx, RX_P0, SP_HDMI_US_TIMER_CTRL_REG,
+		     (val & SP_MS_TIMER_MARGIN_10_8_MASK) |
+		     ((((XTAL_CLK / 10) >> 1) - 2) << 3));
+}
+
+void sp_tx_initialization(struct anx78xx *anx78xx)
+{
+	/* set terminal resistor to 50 ohm */
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, 0x30);
+	/* enable aux double diff output */
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, 0x08);
+
+	if (!sp_hdcp_repeater_mode(anx78xx)) {
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_HDCP_CTRL_REG,
+				  SP_AUTO_EN | SP_AUTO_START);
+		sp_reg_write(anx78xx, TX_P0, SP_OTP_KEY_PROTECT1_REG,
+			     SP_OTP_PSW1);
+		sp_reg_write(anx78xx, TX_P0, SP_OTP_KEY_PROTECT2_REG,
+			     SP_OTP_PSW2);
+		sp_reg_write(anx78xx, TX_P0, SP_OTP_KEY_PROTECT3_REG,
+			     SP_OTP_PSW3);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_HDCP_KEY_COMMAND_REG,
+				SP_DISABLE_SYNC_HDCP);
+	}
+
+	sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL8_REG, SP_VID_VRES_TH);
+
+	/*
+	 * DP HDCP auto authentication wait timer (when downstream starts to
+	 * auth, DP side will wait for this period then do auth automatically)
+	 */
+	sp_reg_write(anx78xx, TX_P0, SP_HDCP_AUTO_TIMER_REG, 0x00);
+
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_HDCP_CTRL_REG, SP_LINK_POLLING);
+
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_LINK_DEBUG_CTRL_REG,
+			SP_M_VID_DEBUG);
+	sp_reg_set_bits(anx78xx, TX_P2, SP_ANALOG_DEBUG2_REG,
+			SP_POWERON_TIME_1P5MS);
+
+	sp_xtal_clk_sel(anx78xx);
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_DEFER_CTRL_REG,
+		     SP_DEFER_CTRL_EN | 0x0c);
+
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_POLLING_CTRL_REG,
+			SP_AUTO_POLLING_DISABLE);
+	/*
+	 * Short the link integrity check timer to speed up bstatus
+	 * polling for HDCP CTS item 1A-07
+	 */
+	sp_reg_write(anx78xx, TX_P0, SP_HDCP_LINK_CHECK_TIMER_REG, 0x1d);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_MISC_CTRL_REG,
+			SP_EQ_TRAINING_LOOP);
+
+	/* power down the main link by default */
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_ANALOG_POWER_DOWN_REG,
+			SP_CH0_PD);
+
+	sp_reg_write(anx78xx, TX_P2, SP_INT_CTRL_REG, 0x01);
+
+	sp_link_phy_initialization(anx78xx);
+	sp_gen_m_clk_with_downspreading(anx78xx);
+
+	sp.down_sample_en = false;
+}
+
+/*
+ * Check if it is ANALOGIX dongle.
+ */
+static const u8 anx_oui[3] = {0x00, 0x22, 0xb9};
+
+static bool is_anx_dongle(struct anx78xx *anx78xx)
+{
+	u8 buf[3];
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_SINK_OUI, 3, buf);
+	if (!memcmp(buf, anx_oui, 3))
+		return true;
+
+	/* 0x0500~0x0502: BRANCH_IEEE_OUI */
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_BRANCH_OUI, 3, buf);
+	if (!memcmp(buf, anx_oui, 3))
+		return true;
+
+	return false;
+}
+
+static const u8 anx7750[4] = {0x37, 0x37, 0x35, 0x30};
+
+static u8 sp_dp_get_max_rx_bandwidth(struct anx78xx *anx78xx)
+{
+	u8 bandwidth, max_link_rate;
+	u8 buf[4];
+
+	bandwidth = 0;
+	/*
+	 * When ANX dongle is connected, if CHIP_ID=0x7750 the bandwidth is
+	 * 6.75G because ANX7750 DPCD 0x052x is not available.
+	 */
+	if (is_anx_dongle(anx78xx)) {
+		/*
+		 * 00503h - 005FFh: RESERVED for Branch Device vendor-specific
+		 * usage
+		 */
+		sp_dp_read_bytes_from_dpcd(anx78xx, 0x503, 4, buf);
+		if (!memcmp(buf, anx7750, sizeof(anx7750)))
+			bandwidth = DP_LINK_BW_6_75;
+		else
+			sp_dp_read_bytes_from_dpcd(anx78xx, 0x521,
+						   1, &bandwidth);
+	}
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_MAX_LINK_RATE, 1,
+				   &max_link_rate);
+	if (bandwidth < max_link_rate)
+		bandwidth = max_link_rate;
+
+	return bandwidth;
+}
+
+static bool sp_get_dp_connection(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	if (sp_dp_read_bytes_from_dpcd(anx78xx, DP_SINK_COUNT, 1, &val))
+		return false;
+
+	/* FIXME: Shouldn't be 0x3f SINK_COUNT[5:0] */
+	if (!(val & 0x1f))
+		return false;
+
+	if (sp_dp_read_bytes_from_dpcd(anx78xx, DP_NORP, 1, &val))
+		return false;
+
+	if (val & 0x20) {
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_SET_POWER, 1, &val);
+		/*
+		 * Bit 5 = SET_DN_DEVICE_DP_PWR_5V
+		 * Bit 6 = SET_DN_DEVICE_DP_PWR_12V
+		 * Bit 7 = SET_DN_DEVICE_DP_PWR_18V
+		 */
+		val &= 0x1f;
+		val |= 0x20;
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_SET_POWER, 1, &val);
+	}
+
+	return true;
+}
+
+static void sp_enable_video_input(struct anx78xx *anx78xx, bool enable)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_VID_CTRL1_REG, &val);
+	if (enable) {
+		sp_reg_set_bits(anx78xx, TX_P2, SP_VID_CTRL1_REG, SP_VIDEO_EN);
+		dev_dbg(dev, "Slimport video is enabled!\n");
+	} else {
+		sp_reg_clear_bits(anx78xx, TX_P2, SP_VID_CTRL1_REG,
+				  SP_VIDEO_EN);
+		dev_dbg(dev, "Slimport video is disabled!\n");
+	}
+}
+
+static u8 sp_get_edid_bandwidth(u8 *data)
+{
+	u16 pclk;
+
+	pclk = ((u16)data[1] << 8) | ((u16)data[0] & 0xff);
+	if (pclk <= 5300)
+		return DP_LINK_BW_1_62;
+	else if (pclk <= 8900)
+		return DP_LINK_BW_2_7;
+	else if (pclk <= 18000)
+		return DP_LINK_BW_5_4;
+	else
+		return DP_LINK_BW_6_75;
+}
+
+static u8 sp_parse_edid_to_get_bandwidth(struct anx78xx *anx78xx)
+{
+	int i;
+	u8 bandwidth, temp;
+
+	bandwidth = DP_LINK_BW_1_62;
+	for (i = 0; i < 4; i++) {
+		if (sp.edid_blocks[0x36 + 0x12 * i] == 0)
+			break;
+		temp = sp_get_edid_bandwidth(sp.edid_blocks + 0x36 + 0x12 * i);
+		dev_dbg(&anx78xx->client->dev, "bandwidth via EDID : %x\n",
+			temp);
+		if (bandwidth < temp)
+			bandwidth = temp;
+		if (bandwidth >= DP_LINK_BW_6_75)
+			break;
+	}
+
+	return bandwidth;
+}
+
+u8 sp_get_link_bandwidth_from_edid(struct anx78xx *anx78xx)
+{
+	u8 bandwidth, max_bandwidth;
+
+	bandwidth = sp_dp_get_max_rx_bandwidth(anx78xx);
+	max_bandwidth = sp_parse_edid_to_get_bandwidth(anx78xx);
+	if (bandwidth > max_bandwidth)
+		return max_bandwidth;
+
+	return bandwidth;
+}
+
+static int sp_tx_aux_wr(struct anx78xx *anx78xx, u8 offset)
+{
+	sp_reg_write(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG, offset);
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, SP_AUX_EN);
+
+	return sp_wait_aux_op_finish(anx78xx);
+}
+
+static int sp_tx_aux_rd(struct anx78xx *anx78xx, u8 len)
+{
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, len);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, SP_AUX_EN);
+
+	return sp_wait_aux_op_finish(anx78xx);
+}
+
+static int sp_edid_read(struct anx78xx *anx78xx, u8 offset,
+			u8 *buf)
+{
+	u8 data_cnt, errcnt;
+	u8 val, ret, i;
+
+	sp_tx_aux_wr(anx78xx, offset);
+	/* read 16 bytes (MOT = 1) */
+	sp_tx_aux_rd(anx78xx, 0xf0 | DP_AUX_I2C_MOT | DP_AUX_I2C_READ);
+	data_cnt = 0;
+	errcnt = 0;
+
+	while (data_cnt < 16) {
+		sp_reg_read(anx78xx, TX_P0, SP_BUF_DATA_COUNT_REG, &val);
+		val &= SP_BUF_DATA_COUNT_MASK;
+		if (val) {
+			for (i = 0; i < val; i++)
+				sp_reg_read(anx78xx, TX_P0,
+					    SP_DP_BUF_DATA0_REG + i,
+					    &buf[data_cnt + i]);
+			data_cnt += val;
+		} else {
+			if (errcnt++ <= 2)
+				sp_reset_aux(anx78xx);
+			else
+				return -1;
+		}
+		/* read 16 - data_cnt bytes (MOT = 1) */
+		val = DP_AUX_I2C_MOT | DP_AUX_I2C_READ |
+		      ((0x0f - data_cnt) << 4);
+		sp_tx_aux_rd(anx78xx, val);
+	}
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x01);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+			SP_ADDR_ONLY | SP_AUX_EN);
+	ret = sp_wait_aux_op_finish(anx78xx);
+	sp_addronly_set(anx78xx, false);
+
+	return ret;
+}
+
+static void sp_tx_edid_read_initial(struct anx78xx *anx78xx)
+{
+	/* Write AUX CH address 0x00050 */
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_7_0_REG, SP_I2C_EXTRA_ADDR);
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_15_8_REG, 0);
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_AUX_ADDR_19_16_REG,
+			  SP_AUX_ADDR_19_16_MASK);
+}
+
+static int sp_seg_edid_read(struct anx78xx *anx78xx,
+			    u8 segment, u8 offset)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val, errcnt;
+	int i;
+
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_7_0_REG, 0x30);
+
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+			SP_ADDR_ONLY | SP_AUX_EN);
+
+	if (sp_wait_aux_op_finish(anx78xx))
+		return -1;
+
+	sp_reg_write(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG, segment);
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
+
+	sp_reg_update_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+			   SP_ADDR_ONLY | SP_AUX_EN, SP_AUX_EN);
+
+	errcnt = 10;
+	while (errcnt--) {
+		sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
+		if (!(val & SP_AUX_EN))
+			break;
+		usleep_range(1000, 2000);
+	}
+
+	if (!errcnt) {
+		dev_err(dev,
+			"failed to read DP AUX Channel Control Register 2\n");
+		sp_reset_aux(anx78xx);
+		return -1;
+	}
+
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_7_0_REG, SP_I2C_EXTRA_ADDR);
+	sp_tx_aux_wr(anx78xx, offset);
+	/* read 16 bytes (MOT = 1) */
+	sp_tx_aux_rd(anx78xx, 0xf0 | DP_AUX_I2C_MOT | DP_AUX_I2C_READ);
+
+	for (i = 0; i < 16; i++) {
+		errcnt = 10;
+		while (errcnt--) {
+			sp_reg_read(anx78xx, TX_P0, SP_BUF_DATA_COUNT_REG,
+				    &val);
+			if (val & SP_BUF_DATA_COUNT_MASK)
+				break;
+			usleep_range(2000, 4000);
+		}
+
+		if (!errcnt) {
+			dev_err(dev,
+				"failed to read DP Buffer Data Count Register\n");
+			sp_reset_aux(anx78xx);
+			return -1;
+		}
+
+		sp_reg_read(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG + i, &val);
+	}
+
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x01);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+			SP_ADDR_ONLY | SP_AUX_EN);
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+			  SP_ADDR_ONLY);
+	sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
+
+	errcnt = 10;
+	while (errcnt--) {
+		sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
+		if (!(val & SP_AUX_EN))
+			break;
+		usleep_range(1000, 2000);
+	}
+
+	if (!errcnt) {
+		dev_err(dev,
+			"failed to read DP AUX Channel Control Register 2\n");
+		sp_reset_aux(anx78xx);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int sp_edid_block_checksum(const u8 *raw_edid)
+{
+	int i;
+	u8 csum = 0;
+
+	for (i = 0; i < EDID_LENGTH; i++)
+		csum += raw_edid[i];
+
+	return csum;
+}
+
+static int sp_tx_edid_read(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val, last_block, offset = 0;
+	u8 buf[16];
+	int i, j, count;
+
+	sp_tx_edid_read_initial(anx78xx);
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+			SP_AUX_EN | SP_ADDR_ONLY);
+
+	if (sp_wait_aux_op_finish(anx78xx))
+		return -1;
+
+	sp_addronly_set(anx78xx, false);
+
+	/* Read the number of blocks */
+	sp_tx_aux_wr(anx78xx, 0x7e);
+	sp_tx_aux_rd(anx78xx, DP_AUX_I2C_READ);
+	sp_reg_read(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG, &last_block);
+	dev_dbg(dev, "last EDID block is %d\n", last_block);
+
+	/* FIXME: Why not just cap to 3 if the reported value is >3 */
+	if (last_block > 3)
+		last_block = 1;
+
+	/* for every block */
+	for (count = 0; count <= last_block; count++) {
+		switch (count) {
+		case 0:
+		case 1:
+			for (i = 0; i < 8; i++) {
+				offset = (i + count * 8) * 16;
+				if (sp_edid_read(anx78xx, offset, buf))
+					return -1;
+				for (j = 0; j < 16; j++)
+					sp.edid_blocks[offset + j] = buf[j];
+			}
+			break;
+		case 2:
+		case 3:
+			offset = (count == 2) ? 0x00 : 0x80;
+			for (j = 0; j < 8; j++) {
+				if (sp_seg_edid_read(anx78xx, count / 2,
+						     offset))
+					return -1;
+				offset = offset + 0x10;
+			}
+			break;
+		default:
+			break;
+		}
+	}
+
+	sp_reset_aux(anx78xx);
+
+	if (!drm_edid_block_valid(sp.edid_blocks, 0, true, NULL)) {
+		dev_err(dev, "EDID block is invalid\n");
+		return -1;
+	}
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_REQUEST, 1, &val);
+	if (val & DP_TEST_LINK_EDID_READ) {
+		dev_dbg(dev, "EDID test requested\n");
+		val = sp_edid_block_checksum(sp.edid_blocks);
+		dev_dbg(dev, "EDID checksum is %d\n", val);
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_EDID_CHECKSUM, 1,
+					  &val);
+		sp.tx_test_edid = true;
+		val = DP_TEST_EDID_CHECKSUM_WRITE;
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_RESPONSE, 1, &val);
+	}
+
+	return 0;
+}
+
+static bool sp_check_with_pre_edid(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 i;
+	u8 buf[16];
+	bool ret = false;
+
+	sp_tx_edid_read_initial(anx78xx);
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, 0x03);
+
+	if (sp_wait_aux_op_finish(anx78xx))
+		goto return_point;
+
+	sp_addronly_set(anx78xx, false);
+
+	if (sp_edid_read(anx78xx, 0x70, buf))
+		goto return_point;
+
+	for (i = 0; i < 16; i++) {
+		if (sp.edid_blocks[0x70 + i] != buf[i]) {
+			dev_dbg(dev, "%s\n",
+				"different checksum and blocks num\n");
+			goto return_point;
+		}
+	}
+
+	if (sp_edid_read(anx78xx, 0x08, buf))
+		goto return_point;
+
+	for (i = 0; i < 16; i++) {
+		if (sp.edid_blocks[i + 8] != buf[i]) {
+			dev_dbg(dev, "different edid information\n");
+			goto return_point;
+		}
+	}
+
+	ret = true;
+return_point:
+	sp_reset_aux(anx78xx);
+
+	return ret;
+}
+
+static bool sp_edid_process(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 bw, edid_bw, val;
+	int i;
+
+	if (sp.read_edid_flag) {
+		if (!sp_check_with_pre_edid(anx78xx))
+			sp.read_edid_flag = false;
+	} else {
+		if (sp_tx_edid_read(anx78xx)) {
+			dev_err(dev, "EDID corruption!\n");
+			return false;
+		}
+	}
+
+	/* Release the HPD after the OTP loaddown */
+	for (i = 0; i < 10; i++) {
+		sp_reg_read(anx78xx, TX_P0, SP_HDCP_KEY_STATUS_REG, &val);
+		if (val & 0x01)
+			break;
+
+		dev_dbg(dev, "waiting HDCP KEY loaddown\n");
+		usleep_range(1000, 2000);
+	}
+
+	sp_reg_write(anx78xx, RX_P0, SP_INT_MASK_BASE + 1,
+		     SP_HDMI_DVI | SP_CKDT_CHG | SP_SCDT_CHG |
+		     SP_CABLE_PLUG_CHG);
+
+	if (!sp_hdcp_repeater_mode(anx78xx)) {
+		sp_hdmi_set_hpd(anx78xx, true);
+		sp_hdmi_set_termination(anx78xx, true);
+	}
+
+	bw = sp_dp_get_max_rx_bandwidth(anx78xx);
+	dev_dbg(dev, "RX BW %x\n", bw);
+
+	edid_bw = sp_parse_edid_to_get_bandwidth(anx78xx);
+	if (bw <= edid_bw)
+		edid_bw = bw;
+
+	dev_dbg(dev, "set link bw in edid %x\n", edid_bw);
+	sp.changed_bandwidth = edid_bw;
+
+	return true;
+}
+
+static void sp_lvttl_bit_mapping(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+	u8 vid_bit;
+
+	sp_reg_read(anx78xx, RX_P0, SP_VIDEO_STATUS_REG, &val);
+	switch ((val & SP_COLOR_DEPTH_MASK) >> SP_COLOR_DEPTH_SHIFT) {
+	default:
+	case SP_COLOR_DEPTH_MODE_LEGACY:
+		val = SP_IN_BPC_8BIT;
+		vid_bit = 0;
+		break;
+	case SP_COLOR_DEPTH_MODE_24BIT:
+		val = SP_IN_BPC_8BIT;
+		if (sp.frame.avi.colorspace == HDMI_COLORSPACE_YUV422)
+			vid_bit = 5;
+		else
+			vid_bit = 1;
+		break;
+	case SP_COLOR_DEPTH_MODE_30BIT:
+		val = SP_IN_BPC_10BIT;
+		if (sp.frame.avi.colorspace == HDMI_COLORSPACE_YUV422)
+			vid_bit = 6;
+		else
+			vid_bit = 2;
+		/*
+		 * For 10bit video must be set this value to 12bit by
+		 * someone
+		 */
+		if (sp.down_sample_en)
+			vid_bit = 3;
+		break;
+	case SP_COLOR_DEPTH_MODE_36BIT:
+		val = SP_IN_BPC_12BIT;
+		if (sp.frame.avi.colorspace == HDMI_COLORSPACE_YUV422)
+			vid_bit = 6;
+		else
+			vid_bit = 3;
+		break;
+	}
+
+	/*
+	 * For down sample video (12bit, 10bit ---> 8bit),
+	 * this register doesn't change
+	 */
+	if (!sp.down_sample_en)
+		sp_reg_update_bits(anx78xx, TX_P2, SP_VID_CTRL2_REG,
+				   SP_IN_BPC_MASK | SP_IN_COLOR_F_MASK,
+				   (val << SP_IN_BPC_SHIFT) |
+				   sp.frame.avi.colorspace);
+
+	sp_reg_write(anx78xx, TX_P2, SP_BIT_CTRL_SPECIFIC_REG,
+		     SP_ENABLE_BIT_CTRL | vid_bit << SP_BIT_CTRL_SELECT_SHIFT);
+
+	if (sp.tx_test_edid) {
+		/* Set color depth to 6 bpc (18 bpp) for link cts */
+		sp_reg_update_bits(anx78xx, TX_P2, SP_VID_CTRL2_REG,
+				   SP_IN_BPC_MASK, SP_IN_BPC_6BIT);
+		sp.tx_test_edid = false;
+		dev_dbg(dev, "color space is set to 6 bpc (18 bpp)\n");
+	}
+
+	if (sp.frame.avi.colorspace) {
+		/*
+		 * Set video values to default of channel 0, 1 and 2 for HDCP
+		 * embedded "blue screen" when HDCP authentication failed.
+		 */
+		sp_reg_write(anx78xx, TX_P0, SP_HDCP_VID0_BLUE_SCREEN_REG,
+			     0x80);
+		sp_reg_write(anx78xx, TX_P0, SP_HDCP_VID1_BLUE_SCREEN_REG,
+			     0x00);
+		sp_reg_write(anx78xx, TX_P0, SP_HDCP_VID2_BLUE_SCREEN_REG,
+			     0x80);
+	} else {
+		sp_reg_write(anx78xx, TX_P0, SP_HDCP_VID0_BLUE_SCREEN_REG,
+			     0x00);
+		sp_reg_write(anx78xx, TX_P0, SP_HDCP_VID0_BLUE_SCREEN_REG,
+			     0x00);
+		sp_reg_write(anx78xx, TX_P0, SP_HDCP_VID0_BLUE_SCREEN_REG,
+			     0x00);
+	}
+}
+
+static unsigned long sp_pclk_calc(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	unsigned long str_plck;
+	u16 vid_counter;
+	u8 val;
+
+	sp_reg_read(anx78xx, RX_P0, SP_PCLK_HIGHRES_CNT_BASE + 2, &val);
+	vid_counter = val << 8;
+	sp_reg_read(anx78xx, RX_P0, SP_PCLK_HIGHRES_CNT_BASE + 1, &val);
+	vid_counter |= val;
+	str_plck = (vid_counter * XTAL_CLK) >> 12;
+	dev_dbg(dev, "pixel clock is %ld.%ld\n", str_plck / 10, str_plck % 10);
+	return str_plck;
+}
+
+static int sp_tx_bw_lc_sel(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	unsigned long pixel_clk;
+	u8 link, val;
+
+	pixel_clk = sp_pclk_calc(anx78xx);
+
+	sp_reg_read(anx78xx, RX_P0, SP_VIDEO_STATUS_REG, &val);
+	switch ((val & SP_COLOR_DEPTH_MASK) >> SP_COLOR_DEPTH_SHIFT) {
+	case SP_COLOR_DEPTH_MODE_LEGACY:
+	case SP_COLOR_DEPTH_MODE_24BIT:
+	default:
+		break;
+	case SP_COLOR_DEPTH_MODE_30BIT:
+		pixel_clk = (pixel_clk * 5) >> 2;
+		break;
+	case SP_COLOR_DEPTH_MODE_36BIT:
+		pixel_clk = (pixel_clk * 3) >> 1;
+		break;
+	}
+
+	sp.down_sample_en = false;
+	if (pixel_clk <= 530) {
+		link = DP_LINK_BW_1_62;
+	} else if (pixel_clk <= 890) {
+		link = DP_LINK_BW_2_7;
+	} else if (pixel_clk <= 1800) {
+		link = DP_LINK_BW_5_4;
+	} else {
+		link = DP_LINK_BW_6_75;
+		if (pixel_clk > 2240)
+			sp.down_sample_en = true;
+	}
+
+	if (sp_get_link_bandwidth(anx78xx) != link) {
+		sp.changed_bandwidth = link;
+		dev_err(dev,
+			"different bandwidth between sink and video %.2x",
+			link);
+		return -1;
+	}
+	return 0;
+}
+
+static void sp_downspreading_enable(struct anx78xx *anx78xx, bool enable)
+{
+	u8 val;
+
+	sp_reg_read(anx78xx, TX_P0, SP_DP_DOWNSPREADING_CTRL1_REG, &val);
+
+	if (enable) {
+		val |= SP_TX_SSC_DOWNSPREADING;
+		sp_reg_write(anx78xx, TX_P0, SP_DP_DOWNSPREADING_CTRL1_REG,
+			     val);
+
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_DOWNSPREAD_CTRL, 1,
+					   &val);
+		val |= DP_SPREAD_AMP_0_5;
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_DOWNSPREAD_CTRL, 1, &val);
+	} else {
+		val &= ~SP_TX_SSC_DISABLE;
+		sp_reg_write(anx78xx, TX_P0, SP_DP_DOWNSPREADING_CTRL1_REG,
+			     val);
+
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_DOWNSPREAD_CTRL, 1,
+					   &val);
+		val &= ~DP_SPREAD_AMP_0_5;
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_DOWNSPREAD_CTRL, 1, &val);
+	}
+}
+
+static void sp_config_ssc(struct anx78xx *anx78xx, enum sp_ssc_dep sscdep)
+{
+	sp_reg_write(anx78xx, TX_P0, SP_DP_DOWNSPREADING_CTRL1_REG, 0x0);
+	sp_reg_write(anx78xx, TX_P0, SP_DP_DOWNSPREADING_CTRL1_REG, sscdep);
+	sp_downspreading_enable(anx78xx, true);
+}
+
+void sp_dp_enable_enhanced_mode(struct anx78xx *anx78xx, bool enable)
+{
+	if (enable)
+		sp_reg_set_bits(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 4,
+				SP_ENHANCED_MODE);
+	else
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 4,
+				  SP_ENHANCED_MODE);
+}
+
+static void sp_dp_enable_rx_to_enhanced_mode(struct anx78xx *anx78xx,
+					     bool enable)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+
+	if (enable) {
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_LANE_COUNT_SET, 1, &val);
+		val |= DP_ENHANCED_FRAME_CAP;
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_LANE_COUNT_SET, 1, &val);
+		dev_dbg(dev, "rx to enhanced mode enabled\n");
+	} else {
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_LANE_COUNT_SET, 1, &val);
+		val &= ~DP_ENHANCED_FRAME_CAP;
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_LANE_COUNT_SET, 1, &val);
+		dev_dbg(dev, "rx to enhanced mode disabled\n");
+	}
+}
+
+static bool sp_dp_is_enhanced_mode_available(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_MAX_LANE_COUNT, 1, &val);
+
+	return val & DP_ENHANCED_FRAME_CAP;
+}
+
+static void sp_dp_set_enhanced_mode(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	val = sp_dp_is_enhanced_mode_available(anx78xx);
+	sp_dp_enable_rx_to_enhanced_mode(anx78xx, val);
+	sp_dp_enable_enhanced_mode(anx78xx, val);
+}
+
+static u16 sp_link_err_check(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u16 err;
+	u8 buf[2];
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_SYMBOL_ERROR_COUNT_LANE0, 2,
+				   buf);
+	usleep_range(5000, 10000);
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_SYMBOL_ERROR_COUNT_LANE0, 2,
+				   buf);
+
+	if (buf[1] & DP_ERROR_COUNT_VALID) {
+		err = ((buf[1] & DP_ERROR_COUNT_BITS_14_8_MASK) << 8) + buf[0];
+		dev_err(dev, "error of Lane %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+
+static bool sp_lt_finish(struct anx78xx *anx78xx)
+{
+	u8 val;
+	struct device *dev = &anx78xx->client->dev;
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_LANE0_1_STATUS, 1, &val);
+
+	val &= DP_LANE_CR_DONE | DP_LANE_CHANNEL_EQ_DONE |
+	       DP_LANE_SYMBOL_LOCKED;
+	if (val != (DP_LANE_CR_DONE | DP_LANE_CHANNEL_EQ_DONE |
+	    DP_LANE_SYMBOL_LOCKED)) {
+		dev_dbg(dev, "Lane0 status error %.2x\n", val);
+		sp.tx_lt_state = LT_ERROR;
+		return false;
+	}
+
+	/*
+	 * If there is link error, adjust pre-emphasis to check error
+	 * again. If there is no error, keep the setting, otherwise
+	 * use 400mv0db
+	 */
+	if (sp.tx_test_lt) {
+		sp.tx_test_lt = false;
+		sp.tx_lt_state = LT_INIT;
+		return true;
+	}
+
+	if (sp_link_err_check(anx78xx)) {
+		sp_reg_read(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG, &val);
+		if (!(val & SP_MAX_PRE_REACH)) {
+			/* Increase one pre-level */
+			sp_reg_write(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG,
+				     val + 0x08);
+			/*
+			 * If error still exists, return to the link training
+			 * value
+			 */
+			if (sp_link_err_check(anx78xx))
+				sp_reg_write(anx78xx, TX_P0,
+					     SP_DP_LANE0_LT_CTRL_REG, val);
+		}
+	}
+
+	val = sp_get_link_bandwidth(anx78xx);
+	if (val != sp.changed_bandwidth) {
+		dev_dbg(dev, "bandwidth changed, cur:%.2x, per:%.2x\n", val,
+			sp.changed_bandwidth);
+		sp.tx_lt_state = LT_ERROR;
+		return false;
+	}
+
+	dev_dbg(dev, "LT succeed, bandwidth: %.2x", val);
+	sp_reg_read(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG, &val);
+	dev_dbg(dev, "Lane0 set to %.2x\n", val);
+	sp.tx_lt_state = LT_INIT;
+
+	if (sp_hdcp_repeater_mode(anx78xx)) {
+		dev_dbg(dev, "HPD set to 1!\n");
+		sp_hdmi_set_hpd(anx78xx, true);
+		sp_hdmi_set_termination(anx78xx, true);
+	}
+
+	/*
+	 * Under low voltage (DVD10 = 0.97V), some chips cannot output video,
+	 * link down interrupt always happens.
+	 */
+	if (sp_link_err_check(anx78xx) > 200) {
+		dev_dbg(dev, "need to reset Serdes FIFO");
+		sp.tx_lt_state = LT_ERROR;
+	} else {
+		return true;
+	}
+
+	return false;
+}
+
+static bool sp_link_training(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val, version;
+
+	switch (sp.tx_lt_state) {
+	case LT_INIT:
+		sp_block_power_ctrl(anx78xx, SP_TX_PWR_VIDEO, 1);
+		sp_video_mute(anx78xx, true);
+		sp_enable_video_input(anx78xx, false);
+		sp.tx_lt_state = LT_WAIT_PLL_LOCK;
+	/* fallthrough */
+	case LT_WAIT_PLL_LOCK:
+		if (!sp_get_pll_lock_status(anx78xx)) {
+			sp_reg_read(anx78xx, TX_P0, SP_DP_PLL_CTRL_REG,
+				    &val);
+
+			val |= SP_PLL_RST;
+			sp_reg_write(anx78xx, TX_P0, SP_DP_PLL_CTRL_REG,
+				     val);
+
+			val &= ~SP_PLL_RST;
+			sp_reg_write(anx78xx, TX_P0, SP_DP_PLL_CTRL_REG,
+				     val);
+
+			dev_dbg(dev, "PLL not lock!\n");
+			break;
+		} else {
+			sp.tx_lt_state = LT_CHECK_LINK_BW;
+		}
+	/* fallthrough */
+	case LT_CHECK_LINK_BW:
+		val = sp_dp_get_max_rx_bandwidth(anx78xx);
+		if (val < sp.changed_bandwidth) {
+			dev_dbg(dev, "over bandwidth!\n");
+			sp.changed_bandwidth = val;
+			break;
+		} else {
+			sp.tx_lt_state = LT_START;
+		}
+	/* fallthrough */
+	case LT_START:
+		if (sp.tx_test_lt) {
+			sp.changed_bandwidth = sp.tx_test_bw;
+			sp_reg_clear_bits(anx78xx, TX_P2, SP_VID_CTRL2_REG,
+					  0x70);
+		} else {
+			sp_reg_write(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG,
+				     0x00);
+		}
+
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_ANALOG_POWER_DOWN_REG,
+				  SP_CH0_PD);
+
+		sp_config_ssc(anx78xx, SSC_DEP_4000PPM);
+		sp_set_link_bandwidth(anx78xx, sp.changed_bandwidth);
+		sp_dp_set_enhanced_mode(anx78xx);
+
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_DPCD_REV, 1, &version);
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_SET_POWER, 1, &val);
+		if (version >= 0x12)
+			val &= ~DP_SET_POWER_12_MASK;
+		else
+			val &= ~DP_SET_POWER_MASK;
+		val |= DP_SET_POWER_D0;
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_SET_POWER, 1, &val);
+
+		sp_reg_write(anx78xx, TX_P0, SP_DP_LT_CTRL_REG, SP_LT_EN);
+		sp.tx_lt_state = LT_WAITING_FINISH;
+	/* fallthrough */
+	case LT_WAITING_FINISH:
+		/* Waiting interrupt to change training state. */
+		break;
+	case LT_ERROR:
+		sp_reg_set_bits(anx78xx, TX_P2, SP_RESET_CTRL2_REG,
+				SP_SERDES_FIFO_RST);
+		msleep(20);
+		sp_reg_clear_bits(anx78xx, TX_P2, SP_RESET_CTRL2_REG,
+				  SP_SERDES_FIFO_RST);
+		dev_err(dev, "LT ERROR reset SERDES FIFO");
+		sp.tx_lt_state = LT_INIT;
+		break;
+	case LT_FINISH:
+		if (sp_lt_finish(anx78xx))
+			return true;
+		break;
+	default:
+		break;
+	}
+
+	return false;
+}
+
+static bool sp_match_vic_for_bt709(u8 vic)
+{
+	/* Video Identification Code (VIC) for BT709 */
+	return ((vic == 0x04) || (vic == 0x05) || (vic == 0x10) ||
+		(vic == 0x13) || (vic == 0x14) || (vic == 0x1f) ||
+		(vic == 0x20) || (vic == 0x21) || (vic == 0x22) ||
+		(vic == 0x27) || (vic == 0x28) || (vic == 0x29) ||
+		(vic == 0x2e) || (vic == 0x2f) || (vic == 0x3c) ||
+		(vic == 0x3d) || (vic == 0x3e) || (vic == 0x3f) ||
+		(vic == 0x40));
+}
+
+static void sp_set_colorspace(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 colorspace, val;
+
+	if (sp.down_sample_en) {
+		if (sp.frame.avi.colorspace == HDMI_COLORSPACE_YUV422) {
+			dev_dbg(dev, "YCbCr4:2:2 ---> PASS THROUGH.\n");
+			sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL6_REG, 0x00);
+			sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL5_REG, 0x00);
+		} else if (sp.frame.avi.colorspace == HDMI_COLORSPACE_YUV444) {
+			dev_dbg(dev, "YCbCr4:4:4 ---> YCbCr4:2:2\n");
+			sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL6_REG,
+				     SP_VIDEO_PROCESS_EN | SP_UP_SAMPLE);
+			sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL5_REG, 0x00);
+		} else if (sp.frame.avi.colorspace == HDMI_COLORSPACE_RGB) {
+			dev_dbg(dev, "RGB4:4:4 ---> YCbCr4:2:2\n");
+			sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL6_REG,
+				     SP_VIDEO_PROCESS_EN | SP_UP_SAMPLE);
+			sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL5_REG,
+				     SP_CSC_STD_SEL | SP_RANGE_R2Y |
+				     SP_CSPACE_R2Y);
+		}
+		sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL2_REG,
+			     (SP_IN_BPC_8BIT << SP_IN_BPC_SHIFT) | colorspace);
+	} else {
+		sp_reg_read(anx78xx, TX_P2, SP_VID_CTRL2_REG, &colorspace);
+		colorspace &= SP_IN_COLOR_F_MASK;
+
+		/*
+		 * To change the CSC_STD_SEL bit we need to set
+		 * CSPACE_Y2R and CSPACE_ R2Y, otherwise has no
+		 * effect or is undetermined.
+		 */
+		if (colorspace == HDMI_COLORSPACE_RGB) {
+			sp_reg_clear_bits(anx78xx, TX_P2, SP_VID_CTRL5_REG,
+					  SP_RANGE_Y2R | SP_CSPACE_Y2R |
+					  SP_CSC_STD_SEL);
+			sp_reg_clear_bits(anx78xx, TX_P2, SP_VID_CTRL6_REG,
+					  SP_VIDEO_PROCESS_EN | SP_UP_SAMPLE);
+		} else {
+			/*
+			 * Colorimetric format of input video is YCbCr422
+			 * or YCbCr444
+			 */
+			sp_reg_set_bits(anx78xx, TX_P2, SP_VID_CTRL5_REG,
+					SP_RANGE_Y2R | SP_CSPACE_Y2R);
+
+			sp_reg_read(anx78xx, RX_P1,
+				    SP_AVI_INFOFRAME_DATA_BASE + 3,
+				    &val);
+
+			if (sp_match_vic_for_bt709(val))
+				sp_reg_set_bits(anx78xx, TX_P2,
+						SP_VID_CTRL5_REG,
+						SP_CSC_STD_SEL);
+			else	/* Convert based on BT601 */
+				sp_reg_clear_bits(anx78xx, TX_P2,
+						  SP_VID_CTRL5_REG,
+						  SP_CSC_STD_SEL);
+			/*
+			 * Enable 4:2:2 to 4:4:4 up sample when is required
+			 * and enable video process function.
+			 */
+			if (colorspace == HDMI_COLORSPACE_YUV422)
+				sp_reg_set_bits(anx78xx, TX_P2,
+						SP_VID_CTRL6_REG,
+						SP_VIDEO_PROCESS_EN |
+						SP_UP_SAMPLE);
+			else	/* YCBCR444 */
+				sp_reg_update_bits(anx78xx, TX_P2,
+						   SP_VID_CTRL6_REG,
+						   SP_VIDEO_PROCESS_EN |
+						   SP_UP_SAMPLE,
+						   SP_VIDEO_PROCESS_EN);
+		}
+	}
+}
+
+static u8 sp_hdmi_infoframe_checksum(u8 *ptr, int size)
+{
+	u8 csum = 0;
+	int i;
+
+	/* compute checksum */
+	for (i = 0; i < size; i++)
+		csum += ptr[i];
+
+	return 256 - csum;
+}
+
+static void sp_audio_infoframe_init(struct anx78xx *anx78xx)
+{
+	int i;
+	u8 buffer[HDMI_INFOFRAME_SIZE(AUDIO)];
+	u8 *ptr = buffer;
+	struct device *dev = &anx78xx->client->dev;
+
+	ptr[0] = HDMI_INFOFRAME_TYPE_AUDIO;
+	ptr[1] = 1;
+	ptr[2] = HDMI_AUDIO_INFOFRAME_SIZE;
+
+	/* start infoframe paylload */
+	ptr += HDMI_INFOFRAME_HEADER_SIZE;
+	for (i = 0; i < HDMI_AUDIO_INFOFRAME_SIZE; i++)
+		sp_reg_read(anx78xx, RX_P1, SP_AUD_INFOFRAME_DATA_BASE + i,
+			    &ptr[i]);
+
+	ptr[3] = 0;
+	ptr[3] = sp_hdmi_infoframe_checksum(buffer,
+					    HDMI_INFOFRAME_SIZE(AUDIO));
+
+	if (hdmi_infoframe_unpack(&sp.frame, buffer) < 0) {
+		dev_err(dev, "unpack of AUDIO infoframe failed\n");
+		return;
+	}
+}
+
+static void sp_avi_infoframe_init(struct anx78xx *anx78xx)
+{
+	int i;
+	u8 buffer[HDMI_INFOFRAME_SIZE(AVI)];
+	u8 *ptr = buffer;
+	struct device *dev = &anx78xx->client->dev;
+
+	ptr[0] = HDMI_INFOFRAME_TYPE_AVI;
+	ptr[1] = 2;
+	ptr[2] = HDMI_AVI_INFOFRAME_SIZE;
+
+	/* start infoframe payload */
+	ptr += HDMI_INFOFRAME_HEADER_SIZE;
+	for (i = 0; i < HDMI_AVI_INFOFRAME_SIZE; i++)
+		sp_reg_read(anx78xx, RX_P1, SP_AVI_INFOFRAME_DATA_BASE + i,
+			    &ptr[i]);
+
+	ptr[3] = 0;	/* checksum */
+	ptr[3] = sp_hdmi_infoframe_checksum(buffer, HDMI_INFOFRAME_SIZE(AVI));
+
+	if (hdmi_infoframe_unpack(&sp.frame, buffer) < 0) {
+		dev_err(dev, "unpack of AVI infoframe failed\n");
+		return;
+	}
+}
+
+static void sp_mpeg_infoframe_init(struct anx78xx *anx78xx)
+{
+	int i;
+	u8 buffer[HDMI_INFOFRAME_SIZE(MPEG)];
+	u8 *ptr = buffer;
+	struct device *dev = &anx78xx->client->dev;
+
+	ptr[0] = HDMI_INFOFRAME_TYPE_MPEG;
+	ptr[1] = 1;
+	ptr[2] = HDMI_MPEG_INFOFRAME_SIZE;
+
+	/* start infoframe payload */
+	ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+	for (i = 0; i < HDMI_MPEG_INFOFRAME_SIZE; i++)
+		sp_reg_read(anx78xx, RX_P1, SP_MPEG_VS_INFOFRAME_DATA_BASE + i,
+			    &ptr[i]);
+
+	ptr[3] = 0;	/* checksum */
+	ptr[3] = sp_hdmi_infoframe_checksum(buffer, HDMI_INFOFRAME_SIZE(MPEG));
+
+	if (hdmi_infoframe_unpack(&sp.frame, buffer) < 0) {
+		dev_err(dev, "unpack of MPEG infoframe failed\n");
+		return;
+	}
+}
+
+static void sp_vsi_infoframe_init(struct anx78xx *anx78xx)
+{
+	int i;
+	u8 len, buffer[10];
+	u8 *ptr = buffer;
+	struct device *dev = &anx78xx->client->dev;
+
+	ptr[0] = HDMI_INFOFRAME_TYPE_MPEG;
+	ptr[1] = 1;
+
+	/* get infoframe length */
+	sp_reg_read(anx78xx, RX_P1, SP_MPEG_VS_INFOFRAME_LEN_REG,
+		    &len);
+	if (len > 10)
+		return;
+
+	ptr[2] = len;
+
+	/* start infoframe payload */
+	ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+	for (i = 0; i < len; i++)
+		sp_reg_read(anx78xx, RX_P1, SP_MPEG_VS_INFOFRAME_DATA_BASE + i,
+			    &ptr[i]);
+
+	ptr[3] = 0;	/* checksum */
+	ptr[3] = sp_hdmi_infoframe_checksum(buffer,
+					    HDMI_INFOFRAME_HEADER_SIZE + len);
+
+	if (hdmi_infoframe_unpack(&sp.frame, buffer) < 0) {
+		dev_err(dev, "unpack of VSI infoframe failed\n");
+		return;
+	}
+}
+
+static void sp_load_packet(struct anx78xx *anx78xx,
+			   enum hdmi_infoframe_type type)
+{
+	int i;
+	u8 buffer[32];
+	u8 *ptr = buffer;
+
+	memset(buffer, 0, sizeof(*buffer));
+	ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+	switch (type) {
+	case HDMI_INFOFRAME_TYPE_AUDIO:
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_AUD_TYPE_REG,
+			     sp.frame.audio.type);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_AUD_VER_REG,
+			     sp.frame.audio.version);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_AUD_LEN_REG,
+			     sp.frame.audio.length);
+		/* packs the audio information into a binary buffer */
+		hdmi_audio_infoframe_pack(&sp.frame.audio, buffer,
+					  HDMI_INFOFRAME_SIZE(AUDIO));
+		/* start infoframe payload */
+		for (i = 0; i < sp.frame.audio.length; i++)
+			sp_reg_write(anx78xx, TX_P2,
+				     SP_INFOFRAME_AUD_DB0_REG + i, ptr[i]);
+		break;
+	case HDMI_INFOFRAME_TYPE_AVI:
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_AVI_TYPE_REG,
+			     sp.frame.avi.type);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_AVI_VER_REG,
+			     sp.frame.avi.version);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_AVI_LEN_REG,
+			     sp.frame.avi.length);
+		/* packs the AVI information into a binary buffer */
+		hdmi_avi_infoframe_pack(&sp.frame.avi, buffer,
+					HDMI_INFOFRAME_SIZE(AVI));
+		/* start infoframe payload */
+		for (i = 0; i < sp.frame.avi.length; i++)
+			sp_reg_write(anx78xx, TX_P2,
+				     SP_INFOFRAME_AVI_DB0_REG + i, ptr[i]);
+		break;
+	case HDMI_INFOFRAME_TYPE_MPEG:
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_MPEG_TYPE_REG,
+			     sp.frame.mpeg.type);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_MPEG_VER_REG,
+			     sp.frame.mpeg.version);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_MPEG_LEN_REG,
+			     sp.frame.mpeg.length);
+		/* packs the MPEG information into a binary buffer */
+		hdmi_mpeg_infoframe_pack(&sp.frame.mpeg, buffer,
+					 HDMI_INFOFRAME_SIZE(MPEG));
+		/* start infoframe payload */
+		for (i = 0; i < sp.frame.mpeg.length; i++)
+			sp_reg_write(anx78xx, TX_P2,
+				     SP_INFOFRAME_MPEG_DB0_REG + i, ptr[i]);
+		break;
+	case HDMI_INFOFRAME_TYPE_VENDOR:
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_MPEG_TYPE_REG,
+			     sp.frame.vendor.hdmi.type);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_MPEG_VER_REG,
+			     sp.frame.vendor.hdmi.version);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_MPEG_LEN_REG,
+			     sp.frame.vendor.hdmi.length);
+		/* pack the VSI information into a binary buffer */
+		hdmi_vendor_infoframe_pack(&sp.frame.vendor.hdmi, buffer,
+					   HDMI_INFOFRAME_HEADER_SIZE +
+					   sp.frame.vendor.hdmi.length);
+		for (i = 0; i < sp.frame.vendor.hdmi.length; i++)
+			sp_reg_write(anx78xx, TX_P2,
+				     SP_INFOFRAME_MPEG_DB0_REG + i, ptr[i]);
+		break;
+	default:
+		break;
+	}
+}
+
+static void sp_config_packets(struct anx78xx *anx78xx,
+			      enum hdmi_infoframe_type type)
+{
+	switch (type) {
+	case HDMI_INFOFRAME_TYPE_AUDIO:
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				  SP_AUD_IF_EN);
+		sp_load_packet(anx78xx, HDMI_INFOFRAME_TYPE_AUDIO);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_AUD_IF_UP);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_AUD_IF_EN);
+		break;
+	case HDMI_INFOFRAME_TYPE_AVI:
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				  SP_AVI_IF_EN);
+		sp_load_packet(anx78xx, HDMI_INFOFRAME_TYPE_AVI);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_AVI_IF_UD);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_AVI_IF_EN);
+		break;
+	case HDMI_INFOFRAME_TYPE_MPEG:
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				  SP_MPEG_IF_EN);
+		sp_load_packet(anx78xx, HDMI_INFOFRAME_TYPE_MPEG);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_MPEG_IF_UD);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_MPEG_IF_EN);
+		break;
+	case HDMI_INFOFRAME_TYPE_VENDOR:
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				  SP_MPEG_IF_EN);
+		sp_load_packet(anx78xx, HDMI_INFOFRAME_TYPE_VENDOR);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_MPEG_IF_UD);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_MPEG_IF_EN);
+		break;
+	default:
+		break;
+	}
+}
+
+static bool sp_config_video_output(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+
+	switch (sp.tx_vo_state) {
+	default:
+	case VO_WAIT_VIDEO_STABLE:
+		sp_reg_read(anx78xx, RX_P0, SP_SYSTEM_STATUS_REG, &val);
+		if ((val & SP_TMDS_DE_DET) && (val & SP_TMDS_CLOCK_DET)) {
+			if (sp_tx_bw_lc_sel(anx78xx))
+				return false;
+			sp_enable_video_input(anx78xx, false);
+			sp_hdmi_new_avi_int(anx78xx);
+			sp_reg_read(anx78xx, RX_P0,
+				    SP_PACKET_RECEIVING_STATUS_REG, &val);
+			if (val & SP_VSI_RCVD)
+				sp_hdmi_new_vsi_int(anx78xx);
+			sp_enable_video_input(anx78xx, true);
+			sp.tx_vo_state = VO_WAIT_TX_VIDEO_STABLE;
+		} else {
+			dev_dbg(dev, "HDMI input video not stable!\n");
+			break;
+		}
+	/* fallthrough */
+	case VO_WAIT_TX_VIDEO_STABLE:
+		/*
+		 * The flag is write clear and can be latched from last
+		 * status. So the first read and write is to clear the
+		 * previous status.
+		 */
+		sp_reg_read(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, &val);
+		sp_reg_write(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, val);
+
+		sp_reg_read(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, &val);
+		if (val & SP_CHA_STA) {
+			dev_dbg(dev, "stream clock not stable!\n");
+			break;
+		} else {
+			/*
+			 * The flag is write clear and can be latched from
+			 * last status. So the first read and write is to
+			 * clear the previous status.
+			 */
+			sp_reg_read(anx78xx, TX_P0,
+				    SP_DP_SYSTEM_CTRL_BASE + 3,
+				    &val);
+			sp_reg_write(anx78xx, TX_P0,
+				     SP_DP_SYSTEM_CTRL_BASE + 3,
+				     val);
+
+			sp_reg_read(anx78xx, TX_P0,
+				    SP_DP_SYSTEM_CTRL_BASE + 3,
+				    &val);
+			if (val & SP_STRM_VALID) {
+				if (sp.tx_test_lt)
+					sp.tx_test_lt = false;
+				sp.tx_vo_state = VO_FINISH;
+			} else {
+				dev_err(dev, "video stream not valid!\n");
+				break;
+			}
+		}
+	/* fallthrough */
+	case VO_FINISH:
+		sp_block_power_ctrl(anx78xx, SP_TX_PWR_AUDIO, false);
+		sp_hdmi_mute_video(anx78xx, false);
+		sp_video_mute(anx78xx, false);
+		sp_show_information(anx78xx);
+		return true;
+	}
+
+	return false;
+}
+
+static inline void sp_hdcp_encryption_disable(struct anx78xx *anx78xx)
+{
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_HDCP_CTRL0_REG, SP_HDCP_ENC_EN);
+}
+
+static inline void sp_hdcp_encryption_enable(struct anx78xx *anx78xx)
+{
+	sp_reg_set_bits(anx78xx, TX_P0, SP_HDCP_CTRL0_REG, SP_HDCP_ENC_EN);
+}
+
+static void sp_hw_hdcp_enable(struct anx78xx *anx78xx)
+{
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_HDCP_CTRL0_REG,
+			  SP_HDCP_ENC_EN | SP_HARD_AUTH_EN);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_HDCP_CTRL0_REG,
+			SP_HARD_AUTH_EN | SP_BKSV_SRM_PASS |
+			SP_KSVLIST_VLD | SP_HDCP_ENC_EN);
+
+	/*
+	 * Set the wait timing value for R0 checking of HDCP first step
+	 * authentication after write AKSV to receiver. Default value is
+	 * 0x64 (100ms).
+	 */
+	sp_reg_write(anx78xx, TX_P0, SP_HDCP_WAIT_R0_TIME_REG, 0xb0);
+
+	/*
+	 * Set the wait timing value for repeater KSVFIFO ready in HDCP first
+	 * step authentication. Default value is 0x9c (4.2s)
+	 */
+	sp_reg_write(anx78xx, TX_P0, SP_HDCP_RPTR_RDY_WAIT_TIME_REG, 0xc8);
+}
+
+static bool sp_hdcp_process(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+
+	switch (sp.hdcp_state) {
+	case HDCP_CAPABLE_CHECK:
+		sp.hdcp_fail_count = 0;
+		if (is_anx_dongle(anx78xx))
+			sp.hdcp_state = HDCP_WAITING_VID_STB;
+		else
+			sp.hdcp_state = HDCP_HW_ENABLE;
+		if (!sp.hdcp_enabled)
+			sp.hdcp_state = HDCP_NOT_SUPPORTED;
+		if (sp.hdcp_state != HDCP_WAITING_VID_STB)
+			break;
+	/* fallthrough */
+	case HDCP_WAITING_VID_STB:
+		msleep(100);
+		sp.hdcp_state = HDCP_HW_ENABLE;
+	/* fallthrough */
+	case HDCP_HW_ENABLE:
+		sp_video_mute(anx78xx, true);
+		sp_clean_hdcp_status(anx78xx);
+		sp_block_power_ctrl(anx78xx, SP_TX_PWR_HDCP, false);
+		msleep(20);
+		sp_block_power_ctrl(anx78xx, SP_TX_PWR_HDCP, true);
+		sp_reg_write(anx78xx, TX_P2, SP_COMMON_INT_MASK_BASE + 2,
+			     0x01);
+		msleep(50);
+		sp_hw_hdcp_enable(anx78xx);
+		sp.hdcp_state = HDCP_WAITING_FINISH;
+	/* fallthrough */
+	case HDCP_WAITING_FINISH:
+		break;
+	case HDCP_FINISH:
+		sp_hdcp_encryption_enable(anx78xx);
+		sp_hdmi_mute_video(anx78xx, false);
+		sp_video_mute(anx78xx, false);
+		sp.hdcp_state = HDCP_CAPABLE_CHECK;
+		dev_dbg(dev, "HDCP authentication pass\n");
+		return true;
+	case HDCP_FAILED:
+		if (sp.hdcp_fail_count > 5) {
+			sp_reg_hardware_reset(anx78xx);
+			sp.hdcp_state = HDCP_CAPABLE_CHECK;
+			sp.hdcp_fail_count = 0;
+			dev_dbg(dev, "HDCP authentication failed\n");
+		} else {
+			sp.hdcp_fail_count++;
+			sp.hdcp_state = HDCP_WAITING_VID_STB;
+		}
+		break;
+	default:
+	case HDCP_NOT_SUPPORTED:
+		dev_dbg(dev, "sink is not capable HDCP\n");
+		sp_block_power_ctrl(anx78xx, SP_TX_PWR_HDCP, false);
+		sp_video_mute(anx78xx, false);
+		sp.hdcp_state = HDCP_CAPABLE_CHECK;
+		return true;
+	}
+
+	return false;
+}
+
+static void sp_enable_audio_output(struct anx78xx *anx78xx, bool enable)
+{
+	u8 val;
+
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_AUDIO_CTRL_REG, SP_AUD_EN);
+	if (enable) {
+		sp_audio_infoframe_init(anx78xx);
+		sp_config_packets(anx78xx, HDMI_INFOFRAME_TYPE_AUDIO);
+
+		sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
+		if (val & SP_HDMI_AUD_LAYOUT) {
+			sp_reg_read(anx78xx, RX_P1, SP_AUD_INFOFRAME_DATA_BASE,
+				    &val);
+			sp_reg_write(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
+				     (val & 0x07) << 5 | SP_AUDIO_LAYOUT);
+		} else {
+			sp_reg_write(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
+				     SP_I2S_CH_NUM_2 & ~SP_AUDIO_LAYOUT);
+		}
+		sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUDIO_CTRL_REG,
+				SP_AUD_EN);
+	} else {
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				  SP_AUD_IF_EN);
+	}
+}
+
+static int sp_calculate_audio_m_value(struct anx78xx *anx78xx)
+{
+	u8 val;
+	struct device *dev = &anx78xx->client->dev;
+	unsigned long m_aud, ls_clk = 0;
+	unsigned long aud_freq = 0;
+
+	sp_reg_read(anx78xx, RX_P0, SP_AUD_SPDIF_CH_STATUS_BASE + 4, &val);
+
+	switch (val & SP_FS_FREQ_MASK) {
+	case SP_FS_FREQ_44100HZ:
+		aud_freq = 44100;
+		break;
+	case SP_FS_FREQ_48000HZ:
+		aud_freq = 48000;
+		break;
+	case SP_FS_FREQ_32000HZ:
+		aud_freq = 32000;
+		break;
+	case SP_FS_FREQ_88200HZ:
+		aud_freq = 88200;
+		break;
+	case SP_FS_FREQ_96000HZ:
+		aud_freq = 96000;
+		break;
+	case SP_FS_FREQ_176400HZ:
+		aud_freq = 176400;
+		break;
+	case SP_FS_FREQ_192000HZ:
+		aud_freq = 192000;
+		break;
+	default:
+		dev_err(dev, "invalid sampling clock frequency %d\n",
+			val & SP_FS_FREQ_MASK);
+		return -1;
+	}
+
+	switch (sp_get_link_bandwidth(anx78xx)) {
+	case DP_LINK_BW_1_62:
+		ls_clk = 162000;
+		break;
+	case DP_LINK_BW_2_7:
+		ls_clk = 270000;
+		break;
+	case DP_LINK_BW_5_4:
+		ls_clk = 540000;
+		break;
+	case DP_LINK_BW_6_75:
+		ls_clk = 675000;
+		break;
+	default:
+		dev_err(dev, "invalid main link bandwidth setting\n");
+		return -1;
+	}
+
+	dev_dbg(dev, "aud_freq = %ld , LS_CLK = %ld\n", aud_freq, ls_clk);
+
+	m_aud = (((512 * aud_freq) / ls_clk) * 32768) / 1000;
+	sp_reg_write(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL4_REG, m_aud & 0xff);
+	m_aud = m_aud >> 8;
+	sp_reg_write(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL5_REG, m_aud & 0xff);
+	sp_reg_write(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL6_REG, 0x00);
+
+	return 0;
+}
+
+static void sp_config_audio(struct anx78xx *anx78xx)
+{
+	int i;
+	u8 val;
+
+	sp_block_power_ctrl(anx78xx, SP_TX_PWR_AUDIO, true);
+
+	sp_reg_read(anx78xx, TX_P0, SP_DP_MAIN_LINK_BW_SET_REG, &val);
+	if (val & SP_INITIAL_SLIM_M_AUD_SEL)
+		if (sp_calculate_audio_m_value(anx78xx))
+			return;
+
+	sp_reg_clear_bits(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL0_REG,
+			  SP_AUD_INTERFACE_DISABLE);
+
+	sp_reg_set_bits(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL2_REG,
+			SP_M_AUD_ADJUST_ST);
+
+	sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
+	if (val & SP_HDMI_AUD_LAYOUT)
+		sp_reg_set_bits(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
+				SP_I2S_CH_NUM_8 | SP_AUDIO_LAYOUT);
+	else
+		sp_reg_clear_bits(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
+				  SP_I2S_CHANNEL_NUM_MASK | SP_AUDIO_LAYOUT);
+
+	/* transfer audio channel status from HDMI Rx to Slimport Tx */
+	for (i = 1; i <= SP_AUD_CH_STATUS_REG_NUM; i++) {
+		sp_reg_read(anx78xx, RX_P0, SP_AUD_SPDIF_CH_STATUS_BASE + i,
+			    &val);
+		sp_reg_write(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + i,
+			     val);
+	}
+
+	/* enable audio */
+	sp_enable_audio_output(anx78xx, true);
+}
+
+static bool sp_config_audio_output(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	switch (sp.tx_ao_state) {
+	default:
+	case AO_INIT:
+	case AO_CTS_RCV_INT:
+	case AO_AUDIO_RCV_INT:
+		sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
+		if (!val & SP_HDMI_MODE) {
+			sp.tx_ao_state = AO_INIT;
+			return true;
+		}
+		break;
+	case AO_RCV_INT_FINISH:
+		if (sp.audio_stable_count++ > 2) {
+			sp.tx_ao_state = AO_OUTPUT;
+		} else {
+			sp.tx_ao_state = AO_INIT;
+			break;
+		}
+	/* fallthrough */
+	case AO_OUTPUT:
+		sp.audio_stable_count = 0;
+		sp.tx_ao_state = AO_INIT;
+		sp_video_mute(anx78xx, false);
+		sp_hdmi_mute_audio(anx78xx, false);
+		sp_config_audio(anx78xx);
+		return true;
+	}
+
+	return false;
+}
+
+static void sp_initialization(struct anx78xx *anx78xx)
+{
+	sp.read_edid_flag = false;
+
+	/* Power on all modules */
+	sp_reg_write(anx78xx, TX_P2, SP_POWERDOWN_CTRL_REG, 0x00);
+	/* Driver Version */
+	sp_reg_write(anx78xx, TX_P1, SP_FW_VER_REG, FW_VERSION);
+	sp_hdmi_initialization(anx78xx);
+	sp_tx_initialization(anx78xx);
+	msleep(200);
+}
+
+/*
+ * Interrupt receiver function, gets the service interrupts and updates the
+ * status of the interrupts so that correct interrupt service routines can
+ * be called in the SlimPort task handler function.
+ */
+static void sp_int_receiver(struct anx78xx *anx78xx)
+{
+	int i;
+
+	/* Common Interrupt Status Registers */
+	for (i = 0; i < ARRAY_SIZE(sp.common_int); i++) {
+		sp_reg_read(anx78xx, TX_P2, SP_COMMON_INT_STATUS_BASE + 1 + i,
+			    &sp.common_int[i]);
+		sp_reg_write(anx78xx, TX_P2, SP_COMMON_INT_STATUS_BASE + 1 + i,
+			     sp.common_int[i]);
+	}
+
+	/* Display Port Interrupt Status Register */
+	sp_reg_read(anx78xx, TX_P2, SP_DP_INT_STATUS_REG, &sp.dp_int);
+	sp_reg_write(anx78xx, TX_P2, SP_DP_INT_STATUS_REG, sp.dp_int);
+
+	/* Interrupt Status Registers */
+	for (i = 0; i < ARRAY_SIZE(sp.sp_hdmi_int); i++) {
+		sp_reg_read(anx78xx, RX_P0, SP_INT_STATUS1_REG + i,
+			    &sp.sp_hdmi_int[i]);
+		sp_reg_write(anx78xx, RX_P0, SP_INT_STATUS1_REG + i,
+			     sp.sp_hdmi_int[i]);
+	}
+}
+
+static void sp_pll_changed_int_handler(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+
+	if (sp.tx_system_state >= STATE_LINK_TRAINING) {
+		if (!sp_get_pll_lock_status(anx78xx)) {
+			dev_dbg(dev, "PLL not lock!\n");
+			sp_set_system_state(anx78xx, STATE_LINK_TRAINING);
+		}
+	}
+}
+
+static u8 sp_dp_autotest_link_training(struct anx78xx *anx78xx)
+{
+	u8 bandwidth, response;
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_LINK_RATE, 1,
+				   &bandwidth);
+	switch (bandwidth) {
+	case DP_LINK_BW_1_62:
+	case DP_LINK_BW_2_7:
+	case DP_LINK_BW_5_4:
+	case DP_LINK_BW_6_75:
+		sp_set_link_bandwidth(anx78xx, bandwidth);
+		sp.tx_test_bw = bandwidth;
+		break;
+	default:
+		sp_set_link_bandwidth(anx78xx, DP_LINK_BW_6_75);
+		sp.tx_test_bw = DP_LINK_BW_6_75;
+		break;
+	}
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_RESPONSE, 1, &response);
+	response |= DP_TEST_ACK;
+
+	return response;
+}
+
+static u8 sp_autotest_phy_pattern(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 b_sw, response;
+	u8 buf[16];
+	int i;
+
+	sp_dp_autotest_link_training(anx78xx);
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_PHY_TEST_PATTERN, 1, buf);
+	dev_dbg(dev, "DPCD: PHY test pattern = %.2x\n", buf[0]);
+	switch (buf[0]) {
+	case 0:
+		break;
+	case 1:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_TRAINING_PATTERN_SET_REG,
+			     0x04);
+		break;
+	case 2:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_TRAINING_PATTERN_SET_REG,
+			     0x08);
+		break;
+	case 3:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_TRAINING_PATTERN_SET_REG,
+			     0x0c);
+		break;
+	case 4:
+		/* 00249h - 0025Fh: Reserved for test automation extensions */
+		sp_dp_read_bytes_from_dpcd(anx78xx, 0x250, 10,
+					   buf);
+		for (i = 0; i < SP_DP_LT_80BIT_PATTERN_REG_NUM; i++) {
+			sp_reg_write(anx78xx, TX_P1,
+				     SP_DP_LT_80BIT_PATTERN0_REG + i,
+				     buf[0]);
+		}
+		sp_reg_write(anx78xx, TX_P0, SP_DP_TRAINING_PATTERN_SET_REG,
+			     0x30);
+		break;
+	case 5:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_CEP_TRAINING_CTRL0_REG,
+			     0x00);
+		sp_reg_write(anx78xx, TX_P0, SP_DP_CEP_TRAINING_CTRL1_REG,
+			     0x01);
+		sp_reg_write(anx78xx, TX_P0, SP_DP_TRAINING_PATTERN_SET_REG,
+			     0x14);
+		break;
+	}
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_MAX_DOWNSPREAD, 1, buf);
+	dev_dbg(dev, "DPCD: max downspread = %.2x\n", buf[0]);
+	if (buf[0] & DP_PERCENT_DOWNSPREAD_0_5)
+		sp_config_ssc(anx78xx, SSC_DEP_4000PPM);
+	else
+		sp_downspreading_enable(anx78xx, false);
+
+	/* get swing and emphasis adjust request */
+	sp_reg_read(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG, &b_sw);
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_ADJUST_REQUEST_LANE0_1, 1, buf);
+	dev_dbg(dev, "DPCD: 0x00206 = %.2x\n", buf[0]);
+	buf[0] &= DP_ADJUST_VOLTAGE_SWING_LANE0_MASK |
+		  DP_ADJUST_PRE_EMPHASIS_LANE0_MASK;
+	switch (buf[0]) {
+	case 0x00:
+	case 0x01:
+	case 0x02:
+	case 0x03:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG,
+			     (b_sw & ~SP_TX_SW_SET_MASK) | buf[0]);
+		break;
+	case 0x04:
+	case 0x05:
+	case 0x06:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG,
+			     (b_sw & ~SP_TX_SW_SET_MASK) |
+			     (buf[0] + 4));
+		break;
+	case 0x08:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG,
+			     (b_sw & ~SP_TX_SW_SET_MASK) | 0x10);
+		break;
+	case 0x09:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG,
+			     (b_sw & ~SP_TX_SW_SET_MASK) | 0x11);
+		break;
+	case 0x0c:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG,
+			     (b_sw & ~SP_TX_SW_SET_MASK) | 0x18);
+		break;
+	default:
+		break;
+	}
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_RESPONSE, 1, &response);
+	response |= DP_TEST_ACK;
+
+	return response;
+}
+
+static void sp_hpd_irq_process(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+	u8 test_vector;
+	u8 buf[6];
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_SINK_COUNT, 6, buf);
+	dev_dbg(dev, "got hpd irq %x\n", buf[1]);
+
+	if (buf[1] != 0)
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_DEVICE_SERVICE_IRQ_VECTOR,
+					  1, &buf[1]);
+
+	/* HDCP IRQ */
+	if ((buf[1] & DP_CP_IRQ) &&
+	    (sp.hdcp_state > HDCP_WAITING_FINISH ||
+	     sp.tx_system_state >= STATE_HDCP_AUTH)) {
+		sp_dp_read_bytes_from_dpcd(anx78xx, 0x068029, 1,
+					   &val);
+		if (val & 0x04) {
+			if (!sp_hdcp_repeater_mode(anx78xx)) {
+				sp_set_system_state(anx78xx, STATE_HDCP_AUTH);
+				sp_clean_hdcp_status(anx78xx);
+			} else {
+				sp.repeater_state = HDCP_ERROR;
+			}
+			dev_dbg(dev, "CP_IRQ, HDCP sync lost.\n");
+		}
+	}
+
+	/* PHY and Link CTS test */
+	if (buf[1] & DP_AUTOMATED_TEST_REQUEST) {
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_REQUEST, 1,
+					   &test_vector);
+
+		if (test_vector & DP_TEST_LINK_TRAINING) {
+			dev_dbg(dev, "LINK_TRAINING test requested\n");
+			sp.tx_test_lt = true;
+			val = sp_dp_autotest_link_training(anx78xx);
+			sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_RESPONSE, 1,
+						  &val);
+
+			if (sp.tx_system_state >= STATE_LINK_TRAINING) {
+				sp.tx_lt_state = LT_INIT;
+				sp_set_system_state(anx78xx,
+						    STATE_LINK_TRAINING);
+			}
+		}
+
+		if (test_vector & DP_TEST_LINK_VIDEO_PATTERN) {
+			dev_dbg(dev, "VIDEO_PATTERN test requested\n");
+			sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_RESPONSE, 1,
+						   &val);
+			val |= DP_TEST_ACK;
+			sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_RESPONSE, 1,
+						  &val);
+		}
+
+		if (test_vector & DP_TEST_LINK_EDID_READ) {
+			dev_dbg(dev, "EDID test requested\n");
+			if (sp.tx_system_state > STATE_PARSE_EDID)
+				sp_set_system_state(anx78xx, STATE_PARSE_EDID);
+			sp.tx_test_edid = true;
+		}
+
+		if (test_vector & DP_TEST_LINK_PHY_TEST_PATTERN) {
+			dev_dbg(dev, "PHY_PATTERN test requested\n");
+			sp.tx_test_lt = true;
+			val = sp_autotest_phy_pattern(anx78xx);
+			sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_RESPONSE, 1,
+						  &val);
+		}
+	}
+
+	if (sp.tx_system_state > STATE_LINK_TRAINING) {
+		if ((sp.tx_system_state == STATE_HDCP_AUTH) &&
+		    (buf[1] & DP_CP_IRQ)) {
+			dev_dbg(dev, "CP IRQ!\n");
+		} else if (!(buf[4] & DP_INTERLANE_ALIGN_DONE) || ((buf[2] &
+			  (DP_LANE_CR_DONE | DP_LANE_SYMBOL_LOCKED)) !=
+			  (DP_LANE_CR_DONE | DP_LANE_SYMBOL_LOCKED))) {
+			sp_set_system_state(anx78xx, STATE_LINK_TRAINING);
+			dev_dbg(dev, "IRQ: re-LT request!\n");
+			return;
+		}
+
+		dev_dbg(dev, "lane align %x\n", buf[4]);
+		dev_dbg(dev, "lane clock recovery %x\n", buf[2]);
+	}
+}
+
+static void sp_auth_done_int_handler(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 buf[2];
+
+	if (sp_hdcp_repeater_mode(anx78xx)) {
+		sp_reg_read(anx78xx, TX_P0, SP_TX_HDCP_STATUS_REG, &buf[0]);
+		if ((buf[0] & SP_AUTHEN_PASS) &&
+		    (sp.repeater_state == HDCP_DOING))
+			sp.repeater_state = HDCP_DONE;
+		else
+			sp.repeater_state = HDCP_ERROR;
+
+		return;
+	}
+
+	if (sp.hdcp_state > HDCP_HW_ENABLE &&
+	    sp.tx_system_state == STATE_HDCP_AUTH) {
+		sp_reg_read(anx78xx, TX_P0, SP_HDCP_RX_BSTATUS0_REG, &buf[0]);
+		sp_reg_read(anx78xx, TX_P0, SP_HDCP_RX_BSTATUS1_REG, &buf[1]);
+		if ((buf[0] & 0x08) || (buf[1] & 0x80)) {
+			dev_dbg(dev, "max cascade/devs exceeded!\n");
+			sp_hdcp_encryption_disable(anx78xx);
+			sp.hdcp_state = HDCP_FINISH;
+		} else {
+			sp_reg_read(anx78xx, TX_P0, SP_TX_HDCP_STATUS_REG,
+				    buf);
+		}
+
+		if (buf[0] & SP_AUTHEN_PASS) {
+			sp_dp_read_bytes_from_dpcd(anx78xx, 0x06802a, 2, buf);
+			if ((buf[0] & 0x08) || (buf[1] & 0x80)) {
+				dev_dbg(dev, "max cascade/devs exceeded!\n");
+				sp_hdcp_encryption_disable(anx78xx);
+			} else
+				dev_dbg(dev, "%s\n",
+					"authentication pass in Auth Done");
+
+			sp.hdcp_state = HDCP_FINISH;
+		} else {
+			dev_err(dev, "authentication failed in Auth Done\n");
+			sp_video_mute(anx78xx, true);
+			sp_clean_hdcp_status(anx78xx);
+			sp.hdcp_state = HDCP_FAILED;
+		}
+	}
+}
+
+static void sp_lt_done_int_handler(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+
+	if (sp.tx_lt_state == LT_WAITING_FINISH &&
+	    sp.tx_system_state == STATE_LINK_TRAINING) {
+		sp_reg_read(anx78xx, TX_P0, SP_DP_LT_CTRL_REG, &val);
+		if (val & SP_LT_ERROR_TYPE_MASK) {
+			val = (val & SP_LT_ERROR_TYPE_MASK) >> 4;
+			dev_dbg(dev, "LT failed in interrupt %.2x\n",
+				val);
+			sp.tx_lt_state = LT_ERROR;
+		} else {
+			dev_dbg(dev, "LT finish\n");
+			sp.tx_lt_state = LT_FINISH;
+		}
+	}
+}
+
+static void sp_hdmi_clk_det_int(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+
+	dev_dbg(dev, "pixel clock change\n");
+	if (sp.tx_system_state > STATE_VIDEO_OUTPUT) {
+		sp_video_mute(anx78xx, true);
+		sp_enable_audio_output(anx78xx, false);
+		sp_set_system_state(anx78xx, STATE_VIDEO_OUTPUT);
+	}
+}
+
+static void sp_hdmi_dvi_int(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+
+	sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
+	if ((val & SP_HDMI_DET) == SP_DVI_MODE) {
+		dev_dbg(dev, "detected DVI MODE -> mute audio\n");
+		sp_hdmi_mute_audio(anx78xx, true);
+		sp_set_system_state(anx78xx, STATE_LINK_TRAINING);
+	}
+}
+
+static void sp_hdmi_new_avi_int(struct anx78xx *anx78xx)
+{
+	sp_avi_infoframe_init(anx78xx);
+	sp_config_packets(anx78xx, HDMI_INFOFRAME_TYPE_AVI);
+	sp_set_colorspace(anx78xx);
+	sp_lvttl_bit_mapping(anx78xx);
+}
+
+static void sp_hdmi_new_vsi_int(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 v3d_struct;
+
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_3D_VSC_CTRL_REG,
+			  SP_INFO_FRAME_VSC_EN);
+
+	sp_vsi_infoframe_init(anx78xx);
+	sp_config_packets(anx78xx, HDMI_INFOFRAME_TYPE_VENDOR);
+
+	switch (sp.frame.vendor.hdmi.s3d_struct) {
+	case HDMI_3D_STRUCTURE_FRAME_PACKING:
+		v3d_struct = 0x02;
+		break;
+	case HDMI_3D_STRUCTURE_LINE_ALTERNATIVE:
+		v3d_struct = 0x03;
+		break;
+	case HDMI_3D_STRUCTURE_SIDE_BY_SIDE_FULL:
+		v3d_struct = 0x04;
+		break;
+	case HDMI_3D_STRUCTURE_INVALID:
+	default:
+		v3d_struct = 0x00;
+		dev_dbg(dev, "3D structure not supported\n");
+		break;
+	}
+	sp_reg_write(anx78xx, TX_P0, SP_DP_VSC_DB1_REG, v3d_struct);
+
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_3D_VSC_CTRL_REG,
+			SP_INFO_FRAME_VSC_EN);
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+			  SP_SPD_IF_EN);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG, SP_SPD_IF_UD);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG, SP_SPD_IF_EN);
+}
+
+static void sp_hdmi_no_vsi_int(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+
+	sp_reg_read(anx78xx, TX_P0, SP_DP_3D_VSC_CTRL_REG, &val);
+	if (val & SP_INFO_FRAME_VSC_EN) {
+		dev_dbg(dev, "no new VSI is received, disable VSC packet\n");
+		val &= ~SP_INFO_FRAME_VSC_EN;
+		sp_reg_write(anx78xx, TX_P0, SP_DP_3D_VSC_CTRL_REG, val);
+		sp_mpeg_infoframe_init(anx78xx);
+		sp_config_packets(anx78xx, HDMI_INFOFRAME_TYPE_MPEG);
+	}
+}
+
+static inline void sp_hdmi_restart_audio_chk(struct anx78xx *anx78xx)
+{
+	sp_set_system_state(anx78xx, STATE_AUDIO_OUTPUT);
+}
+
+static void sp_hdmi_cts_rcv_int(struct anx78xx *anx78xx)
+{
+	if (sp.tx_ao_state == AO_INIT)
+		sp.tx_ao_state = AO_CTS_RCV_INT;
+	else if (sp.tx_ao_state == AO_AUDIO_RCV_INT)
+		sp.tx_ao_state = AO_RCV_INT_FINISH;
+}
+
+static void sp_hdmi_audio_rcv_int(struct anx78xx *anx78xx)
+{
+	if (sp.tx_ao_state == AO_INIT)
+		sp.tx_ao_state = AO_AUDIO_RCV_INT;
+	else if (sp.tx_ao_state == AO_CTS_RCV_INT)
+		sp.tx_ao_state = AO_RCV_INT_FINISH;
+}
+
+static void sp_hdmi_audio_samplechg_int(struct anx78xx *anx78xx)
+{
+	u16 i;
+	u8 val;
+
+	/* transfer audio channel status from HDMI Rx to Slimport Tx */
+	for (i = 0; i < SP_AUD_CH_STATUS_REG_NUM; i++) {
+		sp_reg_read(anx78xx, RX_P0, SP_AUD_SPDIF_CH_STATUS_BASE + i,
+			    &val);
+		sp_reg_write(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + i,
+			     val);
+	}
+}
+
+static void sp_hdmi_hdcp_error_int(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+
+	if (sp.hdcp_error_count >= 40) {
+		sp.hdcp_error_count = 0;
+		dev_dbg(dev, "lots of HDCP errors occurred!\n");
+		sp_hdmi_mute_audio(anx78xx, true);
+		sp_hdmi_mute_video(anx78xx, true);
+		sp_hdmi_set_hpd(anx78xx, false);
+		usleep_range(10000, 11000);
+		sp_hdmi_set_hpd(anx78xx, true);
+	} else {
+		sp.hdcp_error_count++;
+	}
+}
+
+static void sp_hdmi_new_gcp_int(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	sp_reg_read(anx78xx, RX_P1, SP_GENERAL_CTRL_PACKET_REG, &val);
+	if (val & SP_SET_AVMUTE) {
+		sp_hdmi_mute_video(anx78xx, true);
+		sp_hdmi_mute_audio(anx78xx, true);
+	} else if (val & SP_CLEAR_AVMUTE) {
+		sp_hdmi_mute_video(anx78xx, false);
+		sp_hdmi_mute_audio(anx78xx, false);
+	}
+}
+
+static void sp_hpd_int_handler(struct anx78xx *anx78xx, u8 hpd_source)
+{
+	u8 val;
+	struct device *dev = &anx78xx->client->dev;
+
+	switch (hpd_source) {
+	case SP_HPD_LOST:
+		dev_dbg(dev, "HPD: cable is pulled out\n");
+		sp_hdmi_set_hpd(anx78xx, false);
+		sp_set_system_state(anx78xx, STATE_CABLE_UNPLUGGED);
+		break;
+	case SP_HPD_CHG:
+		dev_dbg(dev, "HPD: hotplug change detected\n");
+		usleep_range(2000, 4000);
+		if (sp.common_int[3] & SP_HPD_IRQ)
+			sp_hpd_irq_process(anx78xx);
+
+		sp_reg_read(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 3, &val);
+		if (val & SP_HPD_STATUS) {
+			if (sp.common_int[3] & SP_HPD_IRQ)
+				sp_hpd_irq_process(anx78xx);
+		} else {
+			sp_reg_read(anx78xx, TX_P0,
+				    SP_DP_SYSTEM_CTRL_BASE + 3,
+				    &val);
+			if (val & SP_HPD_STATUS) {
+				sp_hdmi_set_hpd(anx78xx, false);
+				sp_set_system_state(anx78xx,
+						    STATE_WAITING_CABLE_PLUG);
+			}
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static void sp_system_isr_handler(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+
+	if (sp.common_int[3] & SP_HPD_LOST)
+		sp_hpd_int_handler(anx78xx, SP_HPD_LOST);
+	else if (sp.common_int[3] & SP_HPD_CHG)
+		sp_hpd_int_handler(anx78xx, SP_HPD_CHG);
+
+	if (sp.common_int[0] & SP_PLL_LOCK_CHG)
+		sp_pll_changed_int_handler(anx78xx);
+
+	if (sp.common_int[1] & SP_HDCP_AUTH_DONE)
+		sp_auth_done_int_handler(anx78xx);
+
+	if ((sp.common_int[2] & SP_HDCP_LINK_CHECK_FAIL) &&
+	    !sp_hdcp_repeater_mode(anx78xx)) {
+		sp_set_system_state(anx78xx, STATE_LINK_TRAINING);
+		dev_dbg(dev, "HDCP Sync Lost!\n");
+	}
+
+	if (sp.dp_int & SP_TRAINING_FINISH)
+		sp_lt_done_int_handler(anx78xx);
+
+	if (sp.tx_system_state > STATE_SINK_CONNECTION) {
+		if (sp.sp_hdmi_int[5] & SP_NEW_AVI_PKT)
+			sp_hdmi_new_avi_int(anx78xx);
+	}
+
+	if (sp.tx_system_state > STATE_VIDEO_OUTPUT) {
+		if (sp.sp_hdmi_int[6] & SP_NEW_VS) {
+			sp.sp_hdmi_int[6] &= ~SP_NO_VSI;
+			sp_hdmi_new_vsi_int(anx78xx);
+		}
+		if (sp.sp_hdmi_int[6] & SP_NO_VSI)
+			sp_hdmi_no_vsi_int(anx78xx);
+	}
+
+	if (sp.tx_system_state >= STATE_VIDEO_OUTPUT) {
+		if (sp.sp_hdmi_int[0] & SP_CKDT_CHG)
+			sp_hdmi_clk_det_int(anx78xx);
+
+		if (sp.sp_hdmi_int[0] & SP_SCDT_CHG)
+			dev_dbg(dev, "HDCP Sync Detected\n");
+
+		if (sp.sp_hdmi_int[0] & SP_HDMI_DVI)
+			sp_hdmi_dvi_int(anx78xx);
+
+		if ((sp.sp_hdmi_int[5] & SP_NEW_AUD_PKT) ||
+		    (sp.sp_hdmi_int[2] & SP_AUD_MODE_CHG))
+			sp_hdmi_restart_audio_chk(anx78xx);
+
+		if (sp.sp_hdmi_int[5] & SP_CTS_RCV)
+			sp_hdmi_cts_rcv_int(anx78xx);
+
+		if (sp.sp_hdmi_int[4] & SP_AUDIO_RCV)
+			sp_hdmi_audio_rcv_int(anx78xx);
+
+		if (sp.sp_hdmi_int[1] & SP_HDCP_ERR)
+			sp_hdmi_hdcp_error_int(anx78xx);
+
+		if (sp.sp_hdmi_int[5] & SP_NEW_CP_PKT)
+			sp_hdmi_new_gcp_int(anx78xx);
+
+		if (sp.sp_hdmi_int[1] & SP_AUDIO_SAMPLE_CHG)
+			sp_hdmi_audio_samplechg_int(anx78xx);
+	}
+}
+
+static void sp_show_information(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val, val1;
+	u16 h_res, h_act, v_res, v_act;
+	u16 h_fp, h_sw, h_bp, v_fp, v_sw, v_bp;
+	unsigned long refresh;
+	unsigned long pclk;
+
+	dev_dbg(dev, "\n************* SP Video Information **************\n");
+
+	switch (sp_get_link_bandwidth(anx78xx)) {
+	case DP_LINK_BW_1_62:
+		dev_dbg(dev, "BW = 1.62G\n");
+		break;
+	case DP_LINK_BW_2_7:
+		dev_dbg(dev, "BW = 2.7G\n");
+		break;
+	case DP_LINK_BW_5_4:
+		dev_dbg(dev, "BW = 5.4G\n");
+		break;
+	case DP_LINK_BW_6_75:
+		dev_dbg(dev, "BW = 6.75G\n");
+		break;
+	default:
+		break;
+	}
+
+	pclk = sp_pclk_calc(anx78xx);
+	pclk = pclk / 10;
+
+	sp_reg_read(anx78xx, TX_P2, SP_TOTAL_LINE_STAL_REG, &val);
+	sp_reg_read(anx78xx, TX_P2, SP_TOTAL_LINE_STAH_REG, &val1);
+
+	v_res = val1;
+	v_res = v_res << 8;
+	v_res = v_res + val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_ACT_LINE_STAL_REG, &val);
+	sp_reg_read(anx78xx, TX_P2, SP_ACT_LINE_STAH_REG, &val1);
+
+	v_act = val1;
+	v_act = v_act << 8;
+	v_act = v_act + val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_TOTAL_PIXEL_STAL_REG, &val);
+	sp_reg_read(anx78xx, TX_P2, SP_TOTAL_PIXEL_STAH_REG, &val1);
+
+	h_res = val1;
+	h_res = h_res << 8;
+	h_res = h_res + val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_ACT_PIXEL_STAL_REG, &val);
+	sp_reg_read(anx78xx, TX_P2, SP_ACT_PIXEL_STAH_REG, &val1);
+
+	h_act = val1;
+	h_act = h_act << 8;
+	h_act = h_act + val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_H_F_PORCH_STAL_REG, &val);
+	sp_reg_read(anx78xx, TX_P2, SP_H_F_PORCH_STAH_REG, &val1);
+
+	h_fp = val1;
+	h_fp = h_fp << 8;
+	h_fp = h_fp + val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_H_SYNC_STAL_REG, &val);
+	sp_reg_read(anx78xx, TX_P2, SP_H_SYNC_STAH_REG, &val1);
+
+	h_sw = val1;
+	h_sw = h_sw << 8;
+	h_sw = h_sw + val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_H_B_PORCH_STAL_REG, &val);
+	sp_reg_read(anx78xx, TX_P2, SP_H_B_PORCH_STAH_REG, &val1);
+
+	h_bp = val1;
+	h_bp = h_bp << 8;
+	h_bp = h_bp + val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_V_F_PORCH_STA_REG, &val);
+	v_fp = val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_V_SYNC_STA_REG, &val);
+	v_sw = val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_V_B_PORCH_STA_REG, &val);
+	v_bp = val;
+
+	dev_dbg(dev, "total resolution is %d * %d\n", h_res, v_res);
+
+	dev_dbg(dev, "HF=%d, HSW=%d, HBP=%d\n", h_fp, h_sw, h_bp);
+	dev_dbg(dev, "VF=%d, VSW=%d, VBP=%d\n", v_fp, v_sw, v_bp);
+
+	if (h_res == 0 || v_res == 0) {
+		refresh = 0;
+	} else {
+		refresh = pclk * 1000000;
+		refresh = DIV_ROUND_CLOSEST(refresh, h_res);
+		refresh = DIV_ROUND_CLOSEST(refresh, v_res);
+	}
+
+	dev_dbg(dev, "active resolution is %d * %d @ %ldHz\n", h_act, v_act,
+		refresh);
+
+	sp_reg_read(anx78xx, TX_P0, SP_DP_VIDEO_CTRL_REG, &val);
+
+	val &= SP_COLOR_F_MASK;
+	val >>= SP_COLOR_F_SHIFT;
+	if (val == HDMI_COLORSPACE_RGB)
+		dev_dbg(dev, "colorspace: RGB");
+	else if (val == HDMI_COLORSPACE_YUV422)
+		dev_dbg(dev, "colorspace: YCbCr422");
+	else if (val == HDMI_COLORSPACE_YUV444)
+		dev_dbg(dev, "colorspace: YCbCr444");
+
+	sp_reg_read(anx78xx, TX_P0, SP_DP_VIDEO_CTRL_REG, &val);
+
+	val &= SP_BPC_MASK;
+	val >>= SP_BPC_SHIFT;
+	if (val  == SP_BPC_6BITS)
+		dev_dbg(dev, "6 BPC\n");
+	else if (val == SP_BPC_8BITS)
+		dev_dbg(dev, "8 BPC\n");
+	else if (val == SP_BPC_10BITS)
+		dev_dbg(dev, "10 BPC\n");
+	else if (val == SP_BPC_12BITS)
+		dev_dbg(dev, "12 BPC\n");
+
+	if (is_anx_dongle(anx78xx)) {
+		/*
+		 * 00503h - 005FFh: RESERVED for Branch Device vendor-specific
+		 * usage
+		 */
+		sp_dp_read_bytes_from_dpcd(anx78xx, 0x0523, 1, &val);
+		dev_dbg(dev, "Analogix Dongle FW Ver %.2x\n", val & 0x7f);
+	}
+
+	dev_dbg(dev, "\n**************************************************\n");
+}
+
+static void sp_hdcp_repeater_reauth(struct anx78xx *anx78xx)
+{
+	u8 val, ctrl, status;
+	struct device *dev = &anx78xx->client->dev;
+
+	msleep(50);
+	sp_reg_read(anx78xx, RX_P1, SP_RX_HDCP_STATUS_REG, &val);
+
+	if (val & SP_AUTH_EN) {
+		sp_reg_read(anx78xx, TX_P0, SP_HDCP_CTRL0_REG, &ctrl);
+		if (ctrl & SP_HARD_AUTH_EN) {
+			sp_reg_read(anx78xx, TX_P0, SP_TX_HDCP_STATUS_REG,
+				    &status);
+			if (!(status & SP_AUTHEN_PASS) &&
+			    (status & SP_AUTH_FAIL)) {
+				dev_dbg(dev, "clean HDCP and re-auth\n");
+				sp.repeater_state = HDCP_ERROR;
+			}
+		} else {
+			dev_dbg(dev, "repeater mode, enable HW HDCP\n");
+			sp.repeater_state = HDCP_ERROR;
+		}
+	}
+
+	sp_reg_read(anx78xx, TX_P0, SP_HDCP_CTRL0_REG, &ctrl);
+	sp_reg_read(anx78xx, TX_P0, SP_TX_HDCP_STATUS_REG, &status);
+
+	if ((ctrl == SP_HDCP_FUNCTION_ENABLED) && (status & SP_AUTH_FAIL)) {
+		dev_dbg(dev, "HDCP encryption failure 0x%02x\n", status);
+		sp.repeater_state = HDCP_ERROR;
+	}
+
+	if (sp.repeater_state == HDCP_ERROR) {
+		sp_clean_hdcp_status(anx78xx);
+		msleep(50);
+		/* Clear HDCP AUTH interrupt */
+		sp_reg_set_bits(anx78xx, TX_P2, SP_COMMON_INT_STATUS_BASE + 2,
+				SP_HDCP_AUTH_DONE);
+		sp_hw_hdcp_enable(anx78xx);
+		sp.repeater_state = HDCP_DOING;
+	}
+}
+
+/**
+ * sp_main_process(): SlimPort Main Process
+ *
+ * SlimPort Main Process States:
+ * 0. Cable pulled out.
+ *    - Power down device.
+ * 1. SlimPort plug
+ *    - Power on device
+ * 2. SlimPort initialization
+ *    - Enable the power supply for downstream
+ *    - Power on the register access
+ *    - Initialize the related registers
+ * 3. Sink connection
+ *     - Get the cable type (HDMI, VGA or MyDP)
+ *     - Check the connection with downstream
+ * 4. Read EDID
+ *    - Read partial EDID data to decide whether to re-read entire EDID
+ *    - EDID read
+ *    - Parse EDID to get the video bandwidth
+ * 5. Link training
+ *    - Check the downstream bandwidth
+ *    - Hardware link training
+ * 6. Video output
+ *    - Verify that input video is stable
+ *    - Order by the input video to calculate the bandwidth
+ *    - Set AVI packet, bit-mapping, color depth, etc.
+ * 7. HDCP authentication
+ *    - Verify that HDCP is supported
+ *    - Enable hardware HDCP
+ * 8. Audio output
+ *    - Automatic audio M valu adjustment
+ *    - Configure audio multichannel
+ *    - Set audio packet
+ * 9. Playback
+ *    - The normal system working state
+ *
+ */
+bool sp_main_process(struct anx78xx *anx78xx)
+{
+	/*
+	 * Process the interrupts
+	 */
+	if (sp.tx_system_state > STATE_WAITING_CABLE_PLUG) {
+		/* Interrupt receiver */
+		sp_int_receiver(anx78xx);
+
+		/* Task handler */
+		sp_system_isr_handler(anx78xx);
+
+		/* If device; supports HDCP repeater function re-auth */
+		if (sp_hdcp_repeater_mode(anx78xx))
+			sp_hdcp_repeater_reauth(anx78xx);
+/*
+		if (!sp.hdcp_enabled) {
+			sp_set_system_state(anx78xx, STATE_HDCP_AUTH);
+			sp.hdcp_enabled = true;
+		}
+*/
+	}
+
+	/*
+	 * SlimPort State Process
+	 */
+	switch (sp.tx_system_state) {
+	case STATE_CABLE_UNPLUGGED:
+		sp_variable_init(anx78xx);
+		anx78xx_poweroff(anx78xx);
+		return false;
+	case STATE_WAITING_CABLE_PLUG:
+		sp_variable_init(anx78xx);
+		anx78xx_poweron(anx78xx);
+		sp.tx_system_state = STATE_SP_INITIALIZED;
+		sp_print_system_state(anx78xx, sp.tx_system_state);
+	/* fallthrough */
+	case STATE_SP_INITIALIZED:
+		sp_initialization(anx78xx);
+		sp.tx_system_state = STATE_SINK_CONNECTION;
+		sp_print_system_state(anx78xx, sp.tx_system_state);
+	/* fallthrough */
+	case STATE_SINK_CONNECTION:
+		if (sp_get_dp_connection(anx78xx)) {
+			sp.tx_system_state = STATE_PARSE_EDID;
+			sp_print_system_state(anx78xx, sp.tx_system_state);
+		} else {
+			break;
+		}
+	/* fallthrough */
+	case STATE_PARSE_EDID:
+		if (sp_edid_process(anx78xx)) {
+			sp.tx_system_state = STATE_LINK_TRAINING;
+			sp_print_system_state(anx78xx, sp.tx_system_state);
+		} else {
+			break;
+		}
+	/* fallthrough */
+	case STATE_LINK_TRAINING:
+		if (sp_link_training(anx78xx)) {
+			sp.tx_system_state = STATE_VIDEO_OUTPUT;
+			sp_print_system_state(anx78xx, sp.tx_system_state);
+		} else {
+			break;
+		}
+	/* fallthrough */
+	case STATE_VIDEO_OUTPUT:
+		if (sp_config_video_output(anx78xx)) {
+			sp.tx_system_state = STATE_HDCP_AUTH;
+			sp_print_system_state(anx78xx, sp.tx_system_state);
+		} else {
+			break;
+		}
+	/* fallthrough */
+	case STATE_HDCP_AUTH:
+		if (!sp_hdcp_repeater_mode(anx78xx)) {
+			if (sp_hdcp_process(anx78xx)) {
+				sp.tx_system_state = STATE_AUDIO_OUTPUT;
+				sp_print_system_state(anx78xx,
+						      sp.tx_system_state);
+			} else {
+				break;
+			}
+		} else {
+			sp.tx_system_state = STATE_AUDIO_OUTPUT;
+		}
+	/* fallthrough */
+	case STATE_AUDIO_OUTPUT:
+		if (sp_config_audio_output(anx78xx)) {
+			sp.tx_system_state = STATE_PLAY_BACK;
+			sp_print_system_state(anx78xx, sp.tx_system_state);
+		} else {
+			break;
+		}
+	/* fallthrough */
+	case STATE_PLAY_BACK:
+	default:
+		break;
+	}
+
+	return true;
+}
+
+/**
+ * sp_system_init(): System initialization
+ *
+ * @anx78xx: SlimPort device.
+ *
+ * A value of zero will be returned on success, a negative errno will
+ * be returned in error cases.
+ */
+int sp_system_init(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u16 id;
+	u8 idh = 0, idl = 0;
+	int i;
+
+	sp_variable_init(anx78xx);
+
+	anx78xx_poweron(anx78xx);
+
+	/* check chip id */
+	sp_reg_read(anx78xx, TX_P2, SP_DEVICE_IDL_REG, &idl);
+	sp_reg_read(anx78xx, TX_P2, SP_DEVICE_IDH_REG, &idh);
+	id = idl | (idh << 8);
+
+	for (i = 0; i < ARRAY_SIZE(chipid_list); i++) {
+		if (id == chipid_list[i])
+			return 0;
+	}
+
+	anx78xx_poweroff(anx78xx);
+
+	dev_err(dev, "failed to detect ANX%x\n", id);
+
+	return -ENODEV;
+}
diff --git a/drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.h b/drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.h
new file mode 100644
index 0000000..08eabc9
--- /dev/null
+++ b/drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright(c) 2015, Analogix Semiconductor. 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 __SLIMPORT_TX_DRV_H
+#define __SLIMPORT_TX_DRV_H
+
+#include <linux/hdmi.h>
+
+#include "anx78xx.h"
+#include "slimport_tx_reg.h"
+
+#define FW_VERSION	0x23
+
+enum sp_tx_state {
+	STATE_CABLE_UNPLUGGED,
+	STATE_WAITING_CABLE_PLUG,
+	STATE_SP_INITIALIZED,
+	STATE_SINK_CONNECTION,
+	STATE_PARSE_EDID,
+	STATE_LINK_TRAINING,
+	STATE_VIDEO_OUTPUT,
+	STATE_HDCP_AUTH,
+	STATE_AUDIO_OUTPUT,
+	STATE_PLAY_BACK
+};
+
+enum sp_tx_power_block {
+	SP_TX_PWR_REG = SP_REGISTER_PD,
+	SP_TX_PWR_HDCP = SP_HDCP_PD,
+	SP_TX_PWR_AUDIO = SP_AUDIO_PD,
+	SP_TX_PWR_VIDEO = SP_VIDEO_PD,
+	SP_TX_PWR_LINK = SP_LINK_PD,
+	SP_TX_PWR_TOTAL = SP_TOTAL_PD,
+	SP_TX_PWR_NUMS
+};
+
+enum sp_tx_lt_status {
+	LT_INIT,
+	LT_WAIT_PLL_LOCK,
+	LT_CHECK_LINK_BW,
+	LT_START,
+	LT_WAITING_FINISH,
+	LT_ERROR,
+	LT_FINISH,
+};
+
+enum hdcp_status {
+	HDCP_CAPABLE_CHECK,
+	HDCP_WAITING_VID_STB,
+	HDCP_HW_ENABLE,
+	HDCP_WAITING_FINISH,
+	HDCP_FINISH,
+	HDCP_FAILED,
+	HDCP_NOT_SUPPORTED,
+};
+
+enum repeater_status {
+	HDCP_DONE,
+	HDCP_DOING,
+	HDCP_ERROR,
+};
+
+enum video_output_status {
+	VO_WAIT_VIDEO_STABLE,
+	VO_WAIT_TX_VIDEO_STABLE,
+	VO_CHECK_VIDEO_INFO,
+	VO_FINISH,
+};
+
+enum audio_output_status {
+	AO_INIT,
+	AO_CTS_RCV_INT,
+	AO_AUDIO_RCV_INT,
+	AO_RCV_INT_FINISH,
+	AO_OUTPUT,
+};
+
+enum sp_ssc_dep {
+	SSC_DEP_DISABLE = 0x0,
+	SSC_DEP_500PPM,
+	SSC_DEP_1000PPM,
+	SSC_DEP_1500PPM,
+	SSC_DEP_2000PPM,
+	SSC_DEP_2500PPM,
+	SSC_DEP_3000PPM,
+	SSC_DEP_3500PPM,
+	SSC_DEP_4000PPM,
+	SSC_DEP_4500PPM,
+	SSC_DEP_5000PPM,
+	SSC_DEP_5500PPM,
+	SSC_DEP_6000PPM
+};
+
+bool sp_main_process(struct anx78xx *anx78xx);
+int sp_system_init(struct anx78xx *anx78xx);
+void sp_variable_init(struct anx78xx *anx78xx);
+u8 sp_get_link_bandwidth_from_edid(struct anx78xx *anx78xx);
+
+#endif
diff --git a/drivers/gpu/drm/bridge/anx78xx/slimport_tx_reg.h b/drivers/gpu/drm/bridge/anx78xx/slimport_tx_reg.h
new file mode 100644
index 0000000..c960842
--- /dev/null
+++ b/drivers/gpu/drm/bridge/anx78xx/slimport_tx_reg.h
@@ -0,0 +1,737 @@
+/*
+ * Copyright(c) 2015, Analogix Semiconductor. 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 __SLIMPORT_TX_REG_DEF_H
+#define __SLIMPORT_TX_REG_DEF_H
+
+#define TX_P0				0x70
+#define TX_P1				0x7a
+#define TX_P2				0x72
+
+#define RX_P0				0x7e
+#define RX_P1				0x80
+
+/***************************************************************/
+/* Register definition of device address 0x7e                  */
+/***************************************************************/
+
+/*
+ * System Control and Status
+ */
+
+/* Software Reset Register 1 */
+#define SP_SOFTWARE_RESET1_REG		0x11
+#define SP_VIDEO_RST			BIT(4)
+#define SP_HDCP_MAN_RST			BIT(2)
+#define SP_TMDS_RST			BIT(1)
+#define SP_SW_MAN_RST			BIT(0)
+
+/* System Status Register */
+#define SP_SYSTEM_STATUS_REG		0x14
+#define SP_TMDS_CLOCK_DET		BIT(1)
+#define SP_TMDS_DE_DET			BIT(0)
+
+/* HDMI Status Register */
+#define SP_HDMI_STATUS_REG		0x15
+#define SP_HDMI_AUD_LAYOUT		BIT(3)
+#define SP_HDMI_DET			BIT(0)
+#  define SP_DVI_MODE			0
+#  define SP_HDMI_MODE			1
+
+/* HDMI Mute Control Register */
+#define SP_HDMI_MUTE_CTRL_REG		0x16
+#define SP_AUD_MUTE			BIT(1)
+#define SP_VID_MUTE			BIT(0)
+
+/* System Power Down Register 1 */
+#define SP_SYSTEM_POWER_DOWN1_REG	0x18
+#define SP_PWDN_CTRL			BIT(0)
+
+/*
+ * Audio and Video Auto Control
+ */
+
+/* Auto Audio and Video Control register */
+#define SP_AUDVID_CTRL_REG		0x20
+#define SP_AVC_OE			BIT(7)
+#define SP_AAC_OE			BIT(6)
+#define SP_AVC_EN			BIT(1)
+#define SP_AAC_EN			BIT(0)
+
+/* Audio Exception Enable Registers */
+#define SP_AUD_EXCEPTION_ENABLE_BASE	(0x24 - 1)
+/* Bits for Audio Exception Enable Register 3 */
+#define SP_AEC_EN21			BIT(5)
+
+/*
+ * Interrupt
+ */
+
+/* Interrupt Status Register 1 */
+#define SP_INT_STATUS1_REG		0x31
+/* Bits for Interrupt Status Register 1 */
+#define SP_HDMI_DVI			BIT(7)
+#define SP_CKDT_CHG			BIT(6)
+#define SP_SCDT_CHG			BIT(5)
+#define SP_PCLK_CHG			BIT(4)
+#define SP_PLL_UNLOCK			BIT(3)
+#define SP_CABLE_PLUG_CHG		BIT(2)
+#define SP_SET_MUTE			BIT(1)
+#define SP_SW_INTR			BIT(0)
+/* Bits for Interrupt Status Register 2 */
+#define SP_HDCP_ERR			BIT(5)
+#define SP_AUDIO_SAMPLE_CHG		BIT(0)	/* undocumented */
+/* Bits for Interrupt Status Register 3 */
+#define SP_AUD_MODE_CHG			BIT(0)
+/* Bits for Interrupt Status Register 5 */
+#define SP_AUDIO_RCV			BIT(0)
+/* Bits for Interrupt Status Register 6 */
+#define SP_CTS_RCV			BIT(7)
+#define SP_NEW_AUD_PKT			BIT(4)
+#define SP_NEW_AVI_PKT			BIT(1)
+#define SP_NEW_CP_PKT			BIT(0)
+/* Bits for Interrupt Status Register 7 */
+#define SP_NO_VSI			BIT(7)
+#define SP_NEW_VS			BIT(4)
+
+/* Interrupt Mask Status Registers */
+#define SP_INT_MASK_BASE		(0x41 - 1)
+
+/* HDMI US TIMER Control Register */
+#define SP_HDMI_US_TIMER_CTRL_REG	0x49
+#define SP_MS_TIMER_MARGIN_10_8_MASK	0x07
+
+/*
+ * TMDS Control
+ */
+
+/* TMDS Control Registers */
+#define SP_TMDS_CTRL_BASE		(0x50 - 1)
+/* Bits for TMDS Control Register 7 */
+#define SP_PD_RT			BIT(0)
+
+/*
+ * Video Control
+ */
+
+/* Video Status Register */
+#define SP_VIDEO_STATUS_REG		0x70
+#define SP_COLOR_DEPTH_MASK		0xf0
+#define SP_COLOR_DEPTH_SHIFT		4
+#  define SP_COLOR_DEPTH_MODE_LEGACY	0x00
+#  define SP_COLOR_DEPTH_MODE_24BIT	0x04
+#  define SP_COLOR_DEPTH_MODE_30BIT	0x05
+#  define SP_COLOR_DEPTH_MODE_36BIT	0x06
+#  define SP_COLOR_DEPTH_MODE_48BIT	0x07
+
+/* Video Data Range Control Register */
+#define SP_VID_DATA_RANGE_CTRL_REG	0x83
+#define SP_R2Y_INPUT_LIMIT		BIT(1)
+
+/* Pixel Clock High Resolution Counter Registers */
+#define SP_PCLK_HIGHRES_CNT_BASE	(0x8c - 1)
+
+/*
+ * Audio Control
+ */
+
+/* Number of Audio Channels Status Registers */
+#define SP_AUD_CH_STATUS_REG_NUM	5
+
+/* Audio IN S/PDIF Channel Status Registers */
+#define SP_AUD_SPDIF_CH_STATUS_BASE	0xc7
+
+/* Audio IN S/PDIF Channel Status Register 4 */
+#define SP_FS_FREQ_MASK			0x0f
+#  define SP_FS_FREQ_44100HZ		0x00
+#  define SP_FS_FREQ_48000HZ		0x02
+#  define SP_FS_FREQ_32000HZ		0x03
+#  define SP_FS_FREQ_88200HZ		0x08
+#  define SP_FS_FREQ_96000HZ		0x0a
+#  define SP_FS_FREQ_176400HZ		0x0c
+#  define SP_FS_FREQ_192000HZ		0x0e
+
+/*
+ * Micellaneous Control Block
+ */
+
+/* CHIP Control Register */
+#define SP_CHIP_CTRL_REG		0xe3
+#define SP_MAN_HDMI5V_DET		BIT(3)
+#define SP_PLLLOCK_CKDT_EN		BIT(2)
+#define SP_ANALOG_CKDT_EN		BIT(1)
+#define SP_DIGITAL_CKDT_EN		BIT(0)
+
+/* Packet Receiving Status Register */
+#define SP_PACKET_RECEIVING_STATUS_REG	0xf3
+#define SP_AVI_RCVD			BIT(5)
+#define SP_VSI_RCVD			BIT(1)
+
+/***************************************************************/
+/* Register definition of device address 0x80                  */
+/***************************************************************/
+
+/* HDCP BCAPS Shadow Register */
+#define SP_HDCP_BCAPS_SHADOW_REG	0x2a
+#define SP_BCAPS_REPEATER		BIT(5)
+
+/* HDCP Status Register */
+#define SP_RX_HDCP_STATUS_REG		0x3f
+#define SP_AUTH_EN			BIT(4)
+
+/*
+ * InfoFrame and Control Packet Registers
+ */
+
+/* AVI InfoFrame packet checksum */
+#define SP_AVI_INFOFRAME_CHECKSUM	0xa3
+
+/* AVI InfoFrame Registers */
+#define SP_AVI_INFOFRAME_DATA_BASE	0xa4
+
+#define SP_AVI_COLOR_F_MASK		0x60
+#define SP_AVI_COLOR_F_SHIFT		5
+
+/* Audio InfoFrame Registers */
+#define SP_AUD_INFOFRAME_DATA_BASE	0xc4
+#define SP_AUD_INFOFRAME_LAYOUT_MASK	0x0f
+
+/* MPEG/HDMI Vendor Specific InfoFrame Packet type code */
+#define SP_MPEG_VS_INFOFRAME_TYPE_REG	0xe0
+
+/* MPEG/HDMI Vendor Specific InfoFrame Packet length */
+#define SP_MPEG_VS_INFOFRAME_LEN_REG	0xe2
+
+/* MPEG/HDMI Vendor Specific InfoFrame Packet version number */
+#define SP_MPEG_VS_INFOFRAME_VER_REG	0xe1
+
+/* MPEG/HDMI Vendor Specific InfoFrame Packet content */
+#define SP_MPEG_VS_INFOFRAME_DATA_BASE	0xe4
+
+/* General Control Packet Register */
+#define SP_GENERAL_CTRL_PACKET_REG	0x9f
+#define SP_CLEAR_AVMUTE			BIT(4)
+#define SP_SET_AVMUTE			BIT(0)
+
+/***************************************************************/
+/* Register definition of device address 0x70                  */
+/***************************************************************/
+
+/* HDCP Status Register */
+#define SP_TX_HDCP_STATUS_REG		0x00
+#define SP_AUTH_FAIL			BIT(5)
+#define SP_AUTHEN_PASS			BIT(1)
+
+/* HDCP Control Register 0 */
+#define SP_HDCP_CTRL0_REG		0x01
+#define SP_RX_REPEATER			BIT(6)
+#define SP_RE_AUTH			BIT(5)
+#define SP_SW_AUTH_OK			BIT(4)
+#define SP_HARD_AUTH_EN			BIT(3)
+#define SP_HDCP_ENC_EN			BIT(2)
+#define SP_BKSV_SRM_PASS		BIT(1)
+#define SP_KSVLIST_VLD			BIT(0)
+/* HDCP Function Enabled */
+#define SP_HDCP_FUNCTION_ENABLED	(BIT(0) | BIT(1) | BIT(2) | BIT(3))
+
+/* HDCP Receiver BSTATUS Register 0 */
+#define	SP_HDCP_RX_BSTATUS0_REG		0x1b
+/* HDCP Receiver BSTATUS Register 1 */
+#define	SP_HDCP_RX_BSTATUS1_REG		0x1c
+
+/* HDCP Embedded "Blue Screen" Content Registers */
+#define SP_HDCP_VID0_BLUE_SCREEN_REG	0x2c
+#define SP_HDCP_VID1_BLUE_SCREEN_REG	0x2d
+#define SP_HDCP_VID2_BLUE_SCREEN_REG	0x2e
+
+/* HDCP Wait R0 Timing Register */
+#define SP_HDCP_WAIT_R0_TIME_REG	0x40
+
+/* HDCP Link Integrity Check Timer Register */
+#define SP_HDCP_LINK_CHECK_TIMER_REG	0x41
+
+/* HDCP Repeater Ready Wait Timer Register */
+#define SP_HDCP_RPTR_RDY_WAIT_TIME_REG	0x42
+
+/* HDCP Auto Timer Register */
+#define SP_HDCP_AUTO_TIMER_REG		0x51
+
+/* HDCP Key Status Register */
+#define SP_HDCP_KEY_STATUS_REG		0x5e
+
+/* HDCP Key Command Register */
+#define SP_HDCP_KEY_COMMAND_REG		0x5f
+#define SP_DISABLE_SYNC_HDCP		BIT(2)
+
+/* OTP Memory Key Protection Registers */
+#define SP_OTP_KEY_PROTECT1_REG		0x60
+#define SP_OTP_KEY_PROTECT2_REG		0x61
+#define SP_OTP_KEY_PROTECT3_REG		0x62
+#define SP_OTP_PSW1			0xa2
+#define SP_OTP_PSW2			0x7e
+#define SP_OTP_PSW3			0xc6
+
+/* DP System Control Registers */
+#define SP_DP_SYSTEM_CTRL_BASE		(0x80 - 1)
+/* Bits for DP System Control Register 2 */
+#define SP_CHA_STA			BIT(2)
+/* Bits for DP System Control Register 3 */
+#define SP_HPD_STATUS			BIT(6)
+#define SP_STRM_VALID			BIT(2)
+/* Bits for DP System Control Register 4 */
+#define SP_ENHANCED_MODE		BIT(3)
+
+/* DP Video Control Register */
+#define SP_DP_VIDEO_CTRL_REG		0x84
+#define SP_COLOR_F_MASK			0x06
+#define SP_COLOR_F_SHIFT		1
+#define SP_BPC_MASK			0xe0
+#define SP_BPC_SHIFT			5
+#  define SP_BPC_6BITS			0x00
+#  define SP_BPC_8BITS			0x01
+#  define SP_BPC_10BITS			0x02
+#  define SP_BPC_12BITS			0x03
+
+/* DP Audio Control Register */
+#define SP_DP_AUDIO_CTRL_REG		0x87
+#define SP_AUD_EN			BIT(0)
+
+/* 10us Pulse Generate Timer Registers */
+#define SP_I2C_GEN_10US_TIMER0_REG	0x88
+#define SP_I2C_GEN_10US_TIMER1_REG	0x89
+
+/* Packet Send Control Register */
+#define SP_PACKET_SEND_CTRL_REG		0x90
+#define SP_AUD_IF_UP			BIT(7)
+#define SP_AVI_IF_UD			BIT(6)
+#define SP_MPEG_IF_UD			BIT(5)
+#define SP_SPD_IF_UD			BIT(4)
+#define SP_AUD_IF_EN			BIT(3)
+#define SP_AVI_IF_EN			BIT(2)
+#define SP_MPEG_IF_EN			BIT(1)
+#define SP_SPD_IF_EN			BIT(0)
+
+/* DP HDCP Control Register */
+#define SP_DP_HDCP_CTRL_REG		0x92
+#define SP_AUTO_EN			BIT(7)
+#define SP_AUTO_START			BIT(5)
+#define SP_LINK_POLLING			BIT(1)
+
+/* DP Main Link Bandwidth Setting Register */
+#define SP_DP_MAIN_LINK_BW_SET_REG	0xa0
+#define SP_LINK_BW_SET_MASK		0x1f
+#define SP_INITIAL_SLIM_M_AUD_SEL	BIT(5)
+
+/* DP Training Pattern Set Register */
+#define SP_DP_TRAINING_PATTERN_SET_REG	0xa2
+
+/* DP Lane 0 Link Training Control Register */
+#define SP_DP_LANE0_LT_CTRL_REG		0xa3
+#define SP_TX_SW_SET_MASK		0x1b
+#define SP_MAX_PRE_REACH		BIT(5)
+#define SP_MAX_DRIVE_REACH		BIT(4)
+#define SP_PRE_EMP_LEVEL1		BIT(3)
+#define SP_DRVIE_CURRENT_LEVEL1		BIT(0)
+
+/* DP Link Training Control Register */
+#define SP_DP_LT_CTRL_REG		0xa8
+#define SP_LT_ERROR_TYPE_MASK		0x70
+#  define SP_LT_NO_ERROR		0x00
+#  define SP_LT_AUX_WRITE_ERROR		0x01
+#  define SP_LT_MAX_DRIVE_REACHED	0x02
+#  define SP_LT_WRONG_LANE_COUNT_SET	0x03
+#  define SP_LT_LOOP_SAME_5_TIME	0x04
+#  define SP_LT_CR_FAIL_IN_EQ		0x05
+#  define SP_LT_EQ_LOOP_5_TIME		0x06
+#define SP_LT_EN			BIT(0)
+
+/* DP CEP Training Control Registers */
+#define SP_DP_CEP_TRAINING_CTRL0_REG	0xa9
+#define SP_DP_CEP_TRAINING_CTRL1_REG	0xaa
+
+/* DP Debug Register 1 */
+#define SP_DP_DEBUG1_REG		0xb0
+#define SP_DEBUG_PLL_LOCK		BIT(4)
+#define SP_POLLING_EN			BIT(1)
+
+/* DP Polling Control Register */
+#define SP_DP_POLLING_CTRL_REG		0xb4
+#define SP_AUTO_POLLING_DISABLE		BIT(0)
+
+/* DP Link Debug Control Register */
+#define SP_DP_LINK_DEBUG_CTRL_REG	0xb8
+#define SP_M_VID_DEBUG			BIT(5)
+#define SP_NEW_PRBS7			BIT(4)
+#define SP_INSERT_ER			BIT(1)
+#define SP_PRBS31_EN			BIT(0)
+
+/* AUX Misc control Register */
+#define SP_AUX_MISC_CTRL_REG		0xbf
+
+/* DP PLL control Register */
+#define SP_DP_PLL_CTRL_REG		0xc7
+#define SP_PLL_RST			BIT(6)
+
+/* DP Analog Power Down Register */
+#define SP_DP_ANALOG_POWER_DOWN_REG	0xc8
+#define SP_CH0_PD			BIT(0)
+
+/* DP Misc Control Register */
+#define SP_DP_MISC_CTRL_REG		0xcd
+#define SP_EQ_TRAINING_LOOP		BIT(6)
+
+/* DP Extra I2C Device Address Register */
+#define SP_DP_EXTRA_I2C_DEV_ADDR_REG	0xce
+#define SP_I2C_STRETCH_DISABLE		BIT(7)
+
+#define SP_I2C_EXTRA_ADDR		0x50
+
+/* DP DownSpreading Control Register 1 */
+#define SP_DP_DOWNSPREADING_CTRL1_REG	0xd0
+#define SP_TX_SSC_DISABLE		0xc0
+#define SP_TX_SSC_DOWNSPREADING		BIT(6)
+
+/* DP M Value Calculation Control Register */
+#define SP_DP_M_CALCULATION_CTRL_REG	0xd9
+#define SP_M_GEN_CLK_SEL		BIT(0)
+
+/* AUX Channel Access Status Register */
+#define SP_AUX_CH_STATUS_REG		0xe0
+#define SP_AUX_STATUS			0x0f
+
+/* AUX Channel DEFER Control Register */
+#define SP_AUX_DEFER_CTRL_REG		0xe2
+#define SP_DEFER_CTRL_EN		BIT(7)
+
+/* DP Buffer Data Count Register */
+#define SP_BUF_DATA_COUNT_REG		0xe4
+#define SP_BUF_DATA_COUNT_MASK		0x1f
+#define SP_BUF_CLR			BIT(7)
+
+/* DP AUX Channel Control Register 1 */
+#define SP_DP_AUX_CH_CTRL1_REG		0xe5
+#define SP_AUX_TX_COMM_MASK		0x0f
+#define SP_AUX_LENGTH_MASK		0xf0
+#define SP_AUX_LENGTH_SHIFT		4
+
+/* DP AUX CH Address Register 0 */
+#define SP_AUX_ADDR_7_0_REG		0xe6
+
+/* DP AUX CH Address Register 1 */
+#define SP_AUX_ADDR_15_8_REG		0xe7
+
+/* DP AUX CH Address Register 2 */
+#define SP_AUX_ADDR_19_16_REG		0xe8
+#define SP_AUX_ADDR_19_16_MASK		0x0f
+
+/* DP AUX Channel Control Register 2 */
+#define SP_DP_AUX_CH_CTRL2_REG		0xe9
+#define SP_AUX_SEL_RXCM			BIT(6)
+#define SP_AUX_CHSEL			BIT(3)
+#define SP_AUX_PN_INV			BIT(2)
+#define SP_ADDR_ONLY			BIT(1)
+#define SP_AUX_EN			BIT(0)
+
+/* DP Video Stream Control InfoFrame Register */
+#define SP_DP_3D_VSC_CTRL_REG		0xea
+#define SP_INFO_FRAME_VSC_EN		BIT(0)
+
+/* DP Video Stream Data Byte 1 Register */
+#define SP_DP_VSC_DB1_REG		0xeb
+
+/* DP AUX Channel Control Register 3 */
+#define SP_DP_AUX_CH_CTRL3_REG		0xec
+#define SP_WAIT_COUNTER_7_0_MASK	0xff
+
+/* DP AUX Channel Control Register 4 */
+#define SP_DP_AUX_CH_CTRL4_REG		0xed
+
+/* DP AUX Buffer Data Registers */
+#define SP_DP_BUF_DATA0_REG		0xf0
+
+/***************************************************************/
+/* Register definition of device address 0x72                  */
+/***************************************************************/
+
+/*
+ * Core Register Definitions
+ */
+
+/* Device ID Low Byte Register */
+#define SP_DEVICE_IDL_REG		0x02
+
+/* Device ID High Byte Register */
+#define SP_DEVICE_IDH_REG		0x03
+
+/* Power Down Control Register */
+#define SP_POWERDOWN_CTRL_REG		0x05
+#define SP_REGISTER_PD			BIT(7)
+#define SP_HDCP_PD			BIT(5)
+#define SP_AUDIO_PD			BIT(4)
+#define SP_VIDEO_PD			BIT(3)
+#define SP_LINK_PD			BIT(2)
+#define SP_TOTAL_PD			BIT(1)
+
+/* Reset Control Register 1 */
+#define SP_RESET_CTRL1_REG		0x06
+#define SP_MISC_RST			BIT(7)
+#define SP_VIDCAP_RST			BIT(6)
+#define SP_VIDFIF_RST			BIT(5)
+#define SP_AUDFIF_RST			BIT(4)
+#define SP_AUDCAP_RST			BIT(3)
+#define SP_HDCP_RST			BIT(2)
+#define SP_SW_RST			BIT(1)
+#define SP_HW_RST			BIT(0)
+
+/* Reset Control Register 2 */
+#define SP_RESET_CTRL2_REG		0x07
+#define SP_AUX_RST			BIT(2)
+#define SP_SERDES_FIFO_RST		BIT(1)
+#define SP_I2C_REG_RST			BIT(0)
+
+/* Video Control Register 1 */
+#define SP_VID_CTRL1_REG		0x08
+#define SP_VIDEO_EN			BIT(7)
+#define SP_VIDEO_MUTE			BIT(2)
+#define SP_DE_GEN			BIT(1)
+#define SP_DEMUX			BIT(0)
+
+/* Video Control Register 2 */
+#define SP_VID_CTRL2_REG		0x09
+#define SP_IN_COLOR_F_MASK		0x03
+#define SP_IN_YC_BIT_SEL		BIT(2)
+#define SP_IN_BPC_MASK			0x70
+#define SP_IN_BPC_SHIFT			4
+#  define SP_IN_BPC_12BIT		0x03
+#  define SP_IN_BPC_10BIT		0x02
+#  define SP_IN_BPC_8BIT		0x01
+#  define SP_IN_BPC_6BIT		0x00
+#define SP_IN_D_RANGE			BIT(7)
+
+/* Video Control Register 3 */
+#define SP_VID_CTRL3_REG		0x0a
+#define SP_HPD_OUT			BIT(6)
+
+/* Video Control Register 5 */
+#define SP_VID_CTRL5_REG		0x0c
+#define SP_CSC_STD_SEL			BIT(7)
+#define SP_XVYCC_RNG_LMT		BIT(6)
+#define SP_RANGE_Y2R			BIT(5)
+#define SP_CSPACE_Y2R			BIT(4)
+#define SP_RGB_RNG_LMT			BIT(3)
+#define SP_Y_RNG_LMT			BIT(2)
+#define SP_RANGE_R2Y			BIT(1)
+#define SP_CSPACE_R2Y			BIT(0)
+
+/* Video Control Register 6 */
+#define SP_VID_CTRL6_REG		0x0d
+#define SP_TEST_PATTERN_EN		BIT(7)
+#define SP_VIDEO_PROCESS_EN		BIT(6)
+#define SP_VID_US_MODE			BIT(3)
+#define SP_VID_DS_MODE			BIT(2)
+#define SP_UP_SAMPLE			BIT(1)
+#define SP_DOWN_SAMPLE			BIT(0)
+
+/* Video Control Register 8 */
+#define SP_VID_CTRL8_REG		0x0f
+#define SP_VID_VRES_TH			BIT(0)
+
+/* Total Line Status Low Byte Register */
+#define SP_TOTAL_LINE_STAL_REG		0x24
+
+/* Total Line Status High Byte Register */
+#define SP_TOTAL_LINE_STAH_REG		0x25
+
+/* Active Line Status Low Byte Register */
+#define SP_ACT_LINE_STAL_REG		0x26
+
+/* Active Line Status High Byte Register */
+#define SP_ACT_LINE_STAH_REG		0x27
+
+/* Vertical Front Porch Status Register */
+#define SP_V_F_PORCH_STA_REG		0x28
+
+/* Vertical SYNC Width Status Register */
+#define SP_V_SYNC_STA_REG		0x29
+
+/* Vertical Back Porch Status Register */
+#define SP_V_B_PORCH_STA_REG		0x2a
+
+/* Total Pixel Status Low Byte Register */
+#define SP_TOTAL_PIXEL_STAL_REG		0x2b
+
+/* Total Pixel Status High Byte Register */
+#define SP_TOTAL_PIXEL_STAH_REG		0x2c
+
+/* Active Pixel Status Low Byte Register */
+#define SP_ACT_PIXEL_STAL_REG		0x2d
+
+/* Active Pixel Status High Byte Register */
+#define SP_ACT_PIXEL_STAH_REG		0x2e
+
+/* Horizontal Front Porch Status Low Byte Register */
+#define SP_H_F_PORCH_STAL_REG		0x2f
+
+/* Horizontal Front Porch Statys High Byte Register */
+#define SP_H_F_PORCH_STAH_REG		0x30
+
+/* Horizontal SYNC Width Status Low Byte Register */
+#define SP_H_SYNC_STAL_REG		0x31
+
+/* Horizontal SYNC Width Status High Byte Register */
+#define SP_H_SYNC_STAH_REG		0x32
+
+/* Horizontal Back Porch Status Low Byte Register */
+#define SP_H_B_PORCH_STAL_REG		0x33
+
+/* Horizontal Back Porch Status High Byte Register */
+#define SP_H_B_PORCH_STAH_REG		0x34
+
+/* InfoFrame AVI Packet Type Register */
+#define SP_INFOFRAME_AVI_TYPE_REG	0x70
+
+/* InfoFrame AVI Packet Version Register */
+#define SP_INFOFRAME_AVI_VER_REG	0x71
+
+/* InfoFrame AVI Packet Length Register */
+#define SP_INFOFRAME_AVI_LEN_REG	0x72
+
+/* InfoFrame AVI Packet DB0 Register */
+#define SP_INFOFRAME_AVI_DB0_REG	0x73
+
+/* Bit Control Specific Register */
+#define SP_BIT_CTRL_SPECIFIC_REG	0x80
+#define SP_BIT_CTRL_SELECT_SHIFT	1
+#define SP_ENABLE_BIT_CTRL		BIT(0)
+
+/* InfoFrame Audio Packet Type Register */
+#define SP_INFOFRAME_AUD_TYPE_REG	0x83
+
+/* InfoFrame Audio Packet Version Register */
+#define SP_INFOFRAME_AUD_VER_REG	0x84
+
+/* InfoFrame Audio Packet Length Register */
+#define SP_INFOFRAME_AUD_LEN_REG	0x85
+
+/* InfoFrame Audio Packet DB0 Register */
+#define SP_INFOFRAME_AUD_DB0_REG	0x86
+
+/* InfoFrame MPEG Packet Type Register */
+#define SP_INFOFRAME_MPEG_TYPE_REG	0xb0
+
+/* InfoFrame MPEG Packet Version Register */
+#define SP_INFOFRAME_MPEG_VER_REG	0xb1
+
+/* InfoFrame MPEG Packet Length Register */
+#define SP_INFOFRAME_MPEG_LEN_REG	0xb2
+
+/* InfoFrame MPEG Packet DB0 Register */
+#define SP_INFOFRAME_MPEG_DB0_REG	0xb3
+
+/* Audio Channel Status Registers */
+#define SP_AUD_CH_STATUS_BASE		(0xd0 - 1)
+
+/* Audio Channel Num Register 5 */
+#define SP_I2S_CHANNEL_NUM_MASK		0xe0
+#  define SP_I2S_CH_NUM_1		(0x00 << 5)
+#  define SP_I2S_CH_NUM_2		(0x01 << 5)
+#  define SP_I2S_CH_NUM_3		(0x02 << 5)
+#  define SP_I2S_CH_NUM_4		(0x03 << 5)
+#  define SP_I2S_CH_NUM_5		(0x04 << 5)
+#  define SP_I2S_CH_NUM_6		(0x05 << 5)
+#  define SP_I2S_CH_NUM_7		(0x06 << 5)
+#  define SP_I2S_CH_NUM_8		(0x07 << 5)
+#define SP_EXT_VUCP			BIT(2)
+#define SP_VBIT				BIT(1)
+#define SP_AUDIO_LAYOUT			BIT(0)
+
+/* Analog Debug Register 2 */
+#define SP_ANALOG_DEBUG2_REG		0xdd
+#define SP_FORCE_SW_OFF_BYPASS		0x20
+#define SP_XTAL_FRQ			0x1c
+#  define SP_XTAL_FRQ_19M2		(0x00 << 2)
+#  define SP_XTAL_FRQ_24M		(0x01 << 2)
+#  define SP_XTAL_FRQ_25M		(0x02 << 2)
+#  define SP_XTAL_FRQ_26M		(0x03 << 2)
+#  define SP_XTAL_FRQ_27M		(0x04 << 2)
+#  define SP_XTAL_FRQ_38M4		(0x05 << 2)
+#  define SP_XTAL_FRQ_52M		(0x06 << 2)
+#define SP_POWERON_TIME_1P5MS		0x03
+
+/* Analog Control 0 Register */
+#define SP_ANALOG_CTRL0_REG		0xe1
+
+/* Common Interrupt Status Register 1 */
+#define SP_COMMON_INT_STATUS_BASE	(0xf1 - 1)
+#define SP_PLL_LOCK_CHG			0x40
+
+/* Common Interrupt Status Register 2 */
+#define SP_COMMON_INT_STATUS2		0xf2
+#define SP_HDCP_AUTH_CHG		BIT(1)
+#define SP_HDCP_AUTH_DONE		BIT(0)
+
+#define SP_HDCP_LINK_CHECK_FAIL		BIT(0)
+
+#define SP_HPD_IRQ			BIT(6)
+#define SP_HPD_ESYNC_ERR		BIT(4)
+#define SP_HPD_CHG			BIT(2)
+#define SP_HPD_LOST			BIT(1)
+#define SP_HPD_PLUG			BIT(0)
+
+/* DP Interrupt Status Register */
+#define SP_DP_INT_STATUS_REG		0xf7
+#define SP_TRAINING_FINISH		BIT(5)
+#define SP_POLLING_ERR			BIT(4)
+
+/* Common Interrupt Mask Register */
+#define SP_COMMON_INT_MASK_BASE		(0xf8 - 1)
+
+/* Interrupt Control Register */
+#define SP_INT_CTRL_REG			0xff
+
+/***************************************************************/
+/* Register definition of device address 0x7a                  */
+/***************************************************************/
+
+/* DP TX Link Training Control Register */
+#define SP_DP_TX_LT_CTRL0_REG		0x30
+
+/* PD 1.2 Lint Training 80bit Pattern Register */
+#define SP_DP_LT_80BIT_PATTERN0_REG	0x80
+#define SP_DP_LT_80BIT_PATTERN_REG_NUM	10
+
+/* Audio Interface Control Register 0 */
+#define SP_AUD_INTERFACE_CTRL0_REG	0x5f
+#define SP_AUD_INTERFACE_DISABLE	0x80
+
+/* Audio Interface Control Register 2 */
+#define SP_AUD_INTERFACE_CTRL2_REG	0x60
+#define SP_M_AUD_ADJUST_ST		0x04
+
+/* Audio Interface Control Register 3 */
+#define SP_AUD_INTERFACE_CTRL3_REG	0x62
+
+/* Audio Interface Control Register 4 */
+#define SP_AUD_INTERFACE_CTRL4_REG	0x67
+
+/* Audio Interface Control Register 5 */
+#define SP_AUD_INTERFACE_CTRL5_REG	0x68
+
+/* Audio Interface Control Register 6 */
+#define SP_AUD_INTERFACE_CTRL6_REG	0x69
+
+/* Firmware Version Register */
+#define SP_FW_VER_REG			0xb7
+
+#endif
-- 
2.1.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCHv6 5/5] drm: bridge: anx78xx: Add anx78xx driver support by analogix.
@ 2015-12-04  8:35   ` Enric Balletbo i Serra
  0 siblings, 0 replies; 17+ messages in thread
From: Enric Balletbo i Serra @ 2015-12-04  8:35 UTC (permalink / raw)
  To: devicetree, linux-kernel, dri-devel, devel, treding
  Cc: mark.rutland, drinkcat, laurent.pinchart, pawel.moll,
	ijc+devicetree, gregkh, emil.l.velikov, cawa.cheng, jb.tsai,
	sjoerd.simons, robh+dt, span, galak, javier, eddie.huang, cjiao,
	dan.carpenter, nathan.chung

At the moment it only supports ANX7814.

The ANX7814 is an ultra-low power Full-HD (1080p60) SlimPort transmitter
designed for portable devices. The ANX7814 transforms the HDMI output of
an application processor to MyDP or DisplayPort.

The driver supports HDMI to DP pass-through mode and works using external
adapters that converts MyDP or DisplayPort to HDMI or DVI.

Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
---

Changes since last version:
- Fix auto build test ERROR (anx78xx->bridge.of_node = client->dev.of_node)
- Remove more magic numbers and use DP_ defines from hdmi.h
- Use common dp/hdmi defines instead of redefine it.
- Improve a bit the documentation of the driver.
- Improve debug messages.
- Use devm to request the irq.

 drivers/gpu/drm/bridge/Kconfig                   |    2 +
 drivers/gpu/drm/bridge/Makefile                  |    1 +
 drivers/gpu/drm/bridge/anx78xx/Kconfig           |    5 +
 drivers/gpu/drm/bridge/anx78xx/Makefile          |    4 +
 drivers/gpu/drm/bridge/anx78xx/anx78xx.h         |   44 +
 drivers/gpu/drm/bridge/anx78xx/anx78xx_main.c    |  334 +++
 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.c | 3210 ++++++++++++++++++++++
 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.h |  110 +
 drivers/gpu/drm/bridge/anx78xx/slimport_tx_reg.h |  737 +++++
 9 files changed, 4447 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/Kconfig
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/Makefile
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/anx78xx.h
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/anx78xx_main.c
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.c
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.h
 create mode 100644 drivers/gpu/drm/bridge/anx78xx/slimport_tx_reg.h

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 6dddd39..1d92bc1 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -41,4 +41,6 @@ config DRM_PARADE_PS8622
 	---help---
 	  Parade eDP-LVDS bridge chip driver.
 
+source "drivers/gpu/drm/bridge/anx78xx/Kconfig"
+
 endmenu
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index d4e28be..0e9fdb4 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -4,3 +4,4 @@ obj-$(CONFIG_DRM_DW_HDMI) += dw_hdmi.o
 obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw_hdmi-ahb-audio.o
 obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
 obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
+obj-$(CONFIG_DRM_ANX78XX) += anx78xx/
diff --git a/drivers/gpu/drm/bridge/anx78xx/Kconfig b/drivers/gpu/drm/bridge/anx78xx/Kconfig
new file mode 100644
index 0000000..5be362d
--- /dev/null
+++ b/drivers/gpu/drm/bridge/anx78xx/Kconfig
@@ -0,0 +1,5 @@
+config DRM_ANX78XX
+	tristate "Analogix ANX78XX bridge"
+	help
+	  ANX78XX is a HD video transmitter chip over micro-USB
+	  connector for smartphone device.
diff --git a/drivers/gpu/drm/bridge/anx78xx/Makefile b/drivers/gpu/drm/bridge/anx78xx/Makefile
new file mode 100644
index 0000000..a843733
--- /dev/null
+++ b/drivers/gpu/drm/bridge/anx78xx/Makefile
@@ -0,0 +1,4 @@
+obj-${CONFIG_DRM_ANX78XX} :=  anx78xx.o
+
+anx78xx-y += anx78xx_main.o
+anx78xx-y += slimport_tx_drv.o
diff --git a/drivers/gpu/drm/bridge/anx78xx/anx78xx.h b/drivers/gpu/drm/bridge/anx78xx/anx78xx.h
new file mode 100644
index 0000000..6548918
--- /dev/null
+++ b/drivers/gpu/drm/bridge/anx78xx/anx78xx.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright(c) 2015, Analogix Semiconductor. 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 __ANX78xx_H
+#define __ANX78xx_H
+
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/gpio/consumer.h>
+
+#include <drm/drm_crtc.h>
+
+struct anx78xx_platform_data {
+	struct gpio_desc *gpiod_pd;
+	struct gpio_desc *gpiod_reset;
+	struct gpio_desc *gpiod_v10;
+};
+
+struct anx78xx {
+	struct drm_bridge bridge;
+	struct i2c_client *client;
+	struct anx78xx_platform_data *pdata;
+	struct delayed_work work;
+	struct workqueue_struct *workqueue;
+};
+
+void anx78xx_poweron(struct anx78xx *data);
+void anx78xx_poweroff(struct anx78xx *data);
+bool anx78xx_cable_is_detected(struct anx78xx *anx78xx);
+
+#endif
diff --git a/drivers/gpu/drm/bridge/anx78xx/anx78xx_main.c b/drivers/gpu/drm/bridge/anx78xx/anx78xx_main.c
new file mode 100644
index 0000000..0101c23
--- /dev/null
+++ b/drivers/gpu/drm/bridge/anx78xx/anx78xx_main.c
@@ -0,0 +1,334 @@
+/*
+ * Copyright(c) 2015, Analogix Semiconductor. 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.
+ *
+ */
+#include <drm/drm_dp_helper.h>
+
+#include <linux/async.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+#include <linux/types.h>
+
+#include "anx78xx.h"
+#include "slimport_tx_drv.h"
+
+void anx78xx_poweron(struct anx78xx *anx78xx)
+{
+	struct anx78xx_platform_data *pdata = anx78xx->pdata;
+
+	if (pdata->gpiod_v10) {
+		gpiod_set_value_cansleep(pdata->gpiod_v10, 1);
+		usleep_range(1000, 2000);
+	}
+
+	gpiod_set_value_cansleep(pdata->gpiod_reset, 0);
+	usleep_range(1000, 2000);
+
+	gpiod_set_value_cansleep(pdata->gpiod_pd, 0);
+	usleep_range(1000, 2000);
+
+	gpiod_set_value_cansleep(pdata->gpiod_reset, 1);
+}
+
+void anx78xx_poweroff(struct anx78xx *anx78xx)
+{
+	struct anx78xx_platform_data *pdata = anx78xx->pdata;
+
+	if (pdata->gpiod_v10) {
+		gpiod_set_value_cansleep(pdata->gpiod_v10, 0);
+		usleep_range(1000, 2000);
+	}
+
+	gpiod_set_value_cansleep(pdata->gpiod_reset, 0);
+	usleep_range(1000, 2000);
+
+	gpiod_set_value_cansleep(pdata->gpiod_pd, 1);
+	usleep_range(1000, 2000);
+}
+
+static int anx78xx_init_gpio(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	struct anx78xx_platform_data *pdata = anx78xx->pdata;
+
+	/* gpio for chip power down */
+	pdata->gpiod_pd = devm_gpiod_get(dev, "pd", GPIOD_OUT_HIGH);
+	if (IS_ERR(pdata->gpiod_pd)) {
+		dev_err(dev, "unable to claim pd gpio\n");
+		return PTR_ERR(pdata->gpiod_pd);
+	}
+
+	/* gpio for chip reset */
+	pdata->gpiod_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(pdata->gpiod_reset)) {
+		dev_err(dev, "unable to claim reset gpio\n");
+		return PTR_ERR(pdata->gpiod_reset);
+	}
+
+	/* gpio for V10 power control */
+	pdata->gpiod_v10 = devm_gpiod_get_optional(dev, "v10", GPIOD_OUT_LOW);
+	if (IS_ERR(pdata->gpiod_v10)) {
+		dev_err(dev, "unable to claim v10 gpio\n");
+		return PTR_ERR(pdata->gpiod_v10);
+	}
+
+	return 0;
+}
+
+/*
+ * HPD IRQ Event: HPD pulse width greater than 0.25ms but narrower than 2ms
+ * Hot Unplug Event: HPD pulse stays low longer than 2ms.
+ *
+ * AP just monitor HPD pulse high in this irq. If HPD is high, the driver
+ * will power on the chip, and then the driver controls when to power down
+ * the chip, if HPD event is HPD IRQ, the driver deals with IRQ event from
+ * downstream, finally, if HPD event is Hot Plug, the driver power down the
+ * chip.
+ */
+static irqreturn_t anx78xx_cable_isr(int irq, void *data)
+{
+	struct anx78xx *anx78xx = data;
+
+	queue_delayed_work(anx78xx->workqueue, &anx78xx->work, 0);
+
+	return IRQ_HANDLED;
+}
+
+static void anx78xx_work_func(struct work_struct *work)
+{
+	struct anx78xx *anx78xx = container_of(work, struct anx78xx,
+					       work.work);
+
+	if (sp_main_process(anx78xx))
+		queue_delayed_work(anx78xx->workqueue, &anx78xx->work,
+				   msecs_to_jiffies(500));
+	else
+		cancel_delayed_work(&anx78xx->work);
+}
+
+static inline struct anx78xx *bridge_to_anx78xx(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct anx78xx, bridge);
+}
+
+static int anx78xx_bridge_attach(struct drm_bridge *bridge)
+{
+	return 0;
+}
+
+static bool anx78xx_bridge_mode_fixup(struct drm_bridge *bridge,
+				      const struct drm_display_mode *mode,
+				      struct drm_display_mode *adjusted_mode)
+{
+	struct anx78xx *anx78xx = bridge_to_anx78xx(bridge);
+
+	dev_dbg(&anx78xx->client->dev, "mode_fixup %d<%d; %d; %d\n",
+		sp_get_link_bandwidth_from_edid(anx78xx), DP_LINK_BW_5_4,
+		mode->clock, mode->flags & DRM_MODE_FLAG_INTERLACE);
+
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		return false;
+
+	/* Max 720p at 2.7 Ghz, one lane */
+	if (sp_get_link_bandwidth_from_edid(anx78xx) < DP_LINK_BW_5_4 &&
+	    mode->clock > 74250)
+		return false;
+
+	/* Max 1200p at 5.4 Ghz, one lane */
+	if (mode->clock > 154000)
+		return false;
+
+	return true;
+}
+
+static void anx78xx_bridge_disable(struct drm_bridge *bridge) {}
+static void anx78xx_bridge_post_disable(struct drm_bridge *bridge) {}
+static void anx78xx_bridge_mode_set(struct drm_bridge *bridge,
+				    struct drm_display_mode *mode,
+				    struct drm_display_mode *adjusted_mode) {}
+static void anx78xx_bridge_pre_enable(struct drm_bridge *bridge) {}
+static void anx78xx_bridge_enable(struct drm_bridge *bridge) {}
+
+static const struct drm_bridge_funcs anx78xx_bridge_funcs = {
+	.attach		= anx78xx_bridge_attach,
+	.mode_fixup	= anx78xx_bridge_mode_fixup,
+	.disable	= anx78xx_bridge_disable,
+	.post_disable	= anx78xx_bridge_post_disable,
+	.mode_set	= anx78xx_bridge_mode_set,
+	.pre_enable	= anx78xx_bridge_pre_enable,
+	.enable		= anx78xx_bridge_enable,
+};
+
+static int anx78xx_i2c_probe(struct i2c_client *client,
+			     const struct i2c_device_id *id)
+{
+	struct anx78xx *anx78xx;
+	int ret;
+
+	if (!i2c_check_functionality(client->adapter,
+				     I2C_FUNC_SMBUS_I2C_BLOCK)) {
+		dev_err(&client->dev, "i2c bus does not support the device\n");
+		return -ENODEV;
+	}
+
+	anx78xx = devm_kzalloc(&client->dev, sizeof(*anx78xx), GFP_KERNEL);
+	if (!anx78xx)
+		return -ENOMEM;
+
+	anx78xx->pdata = devm_kzalloc(&client->dev,
+				      sizeof(struct anx78xx_platform_data),
+				      GFP_KERNEL);
+	if (!anx78xx->pdata)
+		return -ENOMEM;
+
+#ifdef CONFIG_OF
+	anx78xx->bridge.of_node = client->dev.of_node;
+#endif
+	anx78xx->bridge.funcs = &anx78xx_bridge_funcs;
+	ret = drm_bridge_add(&anx78xx->bridge);
+	if (ret < 0) {
+		dev_err(&client->dev, "add drm bridge failed\n");
+		return ret;
+	}
+
+	anx78xx->client = client;
+
+	i2c_set_clientdata(client, anx78xx);
+
+	ret = anx78xx_init_gpio(anx78xx);
+	if (ret) {
+		dev_err(&client->dev, "failed to initialize gpios\n");
+		goto fail_remove_bridge;
+	}
+
+	INIT_DELAYED_WORK(&anx78xx->work, anx78xx_work_func);
+
+	anx78xx->workqueue = create_singlethread_workqueue("anx78xx_work");
+	if (!anx78xx->workqueue) {
+		dev_err(&client->dev, "failed to create work queue\n");
+		ret = -ENOMEM;
+		goto fail_remove_bridge;
+	}
+
+	ret = sp_system_init(anx78xx);
+	if (ret) {
+		dev_err(&client->dev, "failed to initialize anx78xx\n");
+		goto fail_free_wq;
+	}
+
+	ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+					anx78xx_cable_isr,
+					IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+					"anx78xx-cable-det", anx78xx);
+	if (ret) {
+		dev_err(&client->dev, "failed to request threaded irq\n");
+		goto fail_free_wq;
+	}
+
+	ret = irq_set_irq_wake(client->irq, 1);
+	if (ret) {
+		dev_err(&client->dev, "failed to set irq wake\n");
+		goto fail_free_wq;
+	}
+
+	ret = enable_irq_wake(client->irq);
+	if (ret) {
+		dev_err(&client->dev, "failed to enable irq wake\n");
+		goto fail_free_wq;
+	}
+
+	/* enable driver */
+	queue_delayed_work(anx78xx->workqueue, &anx78xx->work, 0);
+
+	return 0;
+
+fail_free_wq:
+	destroy_workqueue(anx78xx->workqueue);
+fail_remove_bridge:
+	drm_bridge_remove(&anx78xx->bridge);
+	return ret;
+}
+
+static int anx78xx_i2c_remove(struct i2c_client *client)
+{
+	struct anx78xx *anx78xx = i2c_get_clientdata(client);
+
+	destroy_workqueue(anx78xx->workqueue);
+	drm_bridge_remove(&anx78xx->bridge);
+
+	return 0;
+}
+
+static int anx78xx_i2c_suspend(struct device *dev)
+{
+	struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+	struct anx78xx *anx78xx = i2c_get_clientdata(client);
+
+	/* REVISIT: suspend */
+	cancel_delayed_work_sync(&anx78xx->work);
+	flush_workqueue(anx78xx->workqueue);
+	anx78xx_poweroff(anx78xx);
+	sp_variable_init(anx78xx);
+
+	return 0;
+}
+
+static int anx78xx_i2c_resume(struct device *dev)
+{
+	struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+	struct anx78xx *anx78xx = i2c_get_clientdata(client);
+
+	queue_delayed_work(anx78xx->workqueue, &anx78xx->work, 0);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(anx78xx_i2c_pm_ops,
+			anx78xx_i2c_suspend, anx78xx_i2c_resume);
+
+static const struct i2c_device_id anx78xx_id[] = {
+	{"anx7814", 0},
+	{ /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(i2c, anx78xx_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id anx78xx_match_table[] = {
+	{.compatible = "analogix,anx7814",},
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, anx78xx_match_table);
+#endif
+
+static struct i2c_driver anx78xx_driver = {
+	.driver = {
+		   .name = "anx7814",
+		   .pm = &anx78xx_i2c_pm_ops,
+		   .of_match_table = of_match_ptr(anx78xx_match_table),
+		  },
+	.probe = anx78xx_i2c_probe,
+	.remove = anx78xx_i2c_remove,
+	.id_table = anx78xx_id,
+};
+
+module_i2c_driver(anx78xx_driver);
+
+MODULE_DESCRIPTION("Slimport transmitter ANX78XX driver");
+MODULE_AUTHOR("Junhua Xia <jxia@analogixsemi.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("1.1");
diff --git a/drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.c b/drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.c
new file mode 100644
index 0000000..4b77270
--- /dev/null
+++ b/drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.c
@@ -0,0 +1,3210 @@
+/*
+ * Copyright(c) 2015, Analogix Semiconductor. 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.
+ *
+ */
+#include <drm/drm_crtc.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_edid.h>
+
+#include <linux/delay.h>
+#include <linux/hdmi.h>
+#include <linux/types.h>
+
+#include "anx78xx.h"
+#include "slimport_tx_drv.h"
+
+#define XTAL_27M	270
+#define XTAL_CLK	XTAL_27M
+
+struct slimport {
+	bool	hdcp_enabled;	/* HDCP control enable/ disable from AP */
+
+	u8	tx_test_bw;
+	bool	tx_test_lt;
+	bool	tx_test_edid;
+
+	u8	changed_bandwidth;
+
+	bool	need_clean_status;
+
+	u8	hdcp_error_count;
+	u8	hdcp_fail_count;
+	u8	audio_stable_count;	/* Audio stable counter */
+
+	u8	edid_blocks[2 * EDID_LENGTH];
+
+	bool	read_edid_flag;
+	bool	down_sample_en;
+
+	/* Infoframes */
+	union hdmi_infoframe	frame;
+
+	/* Interrupt status registers */
+	u8	common_int[4];
+	u8	dp_int;
+	u8	sp_hdmi_int[7];
+
+	enum sp_tx_state		tx_system_state;
+	enum audio_output_status	tx_ao_state;
+	enum video_output_status	tx_vo_state;
+	enum sp_tx_lt_status		tx_lt_state;
+	enum hdcp_status		hdcp_state;
+	enum repeater_status		repeater_state;
+};
+
+static struct slimport sp;
+
+static const u16 chipid_list[] = {
+	0x7802,
+	0x7806,
+	0x7810,
+	0x7812,
+	0x7814,
+	0x7816,
+	0x7818,
+};
+
+static void sp_hdmi_new_avi_int(struct anx78xx *anx78xx);
+static void sp_hdmi_new_vsi_int(struct anx78xx *anx78xx);
+static void sp_print_system_state(struct anx78xx *anx78xx,
+				  enum sp_tx_state state);
+static void sp_show_information(struct anx78xx *anx78xx);
+
+/**
+ * sp_reg_read: Read a value from a single register.
+ *
+ * @anx78xx: Device to read from.
+ * @addr: Address to read from.
+ * @reg: Register to be read from.
+ * @val: Pointer to store read value.
+ *
+ * A value of zero will be returned on success, a negative errno will
+ * be returned in error cases.
+ */
+static int sp_reg_read(struct anx78xx *anx78xx, u8 addr, u8 offset, u8 *val)
+{
+	int ret;
+	struct i2c_client *client = anx78xx->client;
+
+	client->addr = addr >> 1;
+
+	ret = i2c_smbus_read_byte_data(client, offset);
+	if (ret < 0) {
+		dev_err(&client->dev, "failed to read i2c addr=%x\n", addr);
+		return ret;
+	}
+
+	*val = ret;
+
+	return 0;
+}
+
+/**
+ * sp_reg_write(): Write a value to a single register
+ *
+ * @anx78xx: Device to read from.
+ * @addr: Address to write to.
+ * @offset: Byte interpreted by slave.
+ * @val: Value to be written.
+ *
+ * A value of zero will be returned on success, a negative errno will
+ * be returned in error cases.
+ */
+static int sp_reg_write(struct anx78xx *anx78xx, u8 addr, u8 offset, u8 val)
+{
+	int ret;
+	struct i2c_client *client = anx78xx->client;
+
+	client->addr = addr >> 1;
+
+	ret = i2c_smbus_write_byte_data(client, offset, val);
+	if (ret < 0)
+		dev_err(&client->dev, "failed to write i2c addr=%x\n", addr);
+
+	return ret;
+}
+
+/**
+ * sp_reg_update_bits: Perform a read/modify/write cycle on the register.
+ *
+ * @anx78xx: Device to read from.
+ * @addr: Address to write to.
+ * @offset: Byte interpreted by slave.
+ * @mask: Bitmask to change.
+ * @val: New value for bitmask.
+ *
+ * Returns zero for success, a negative number on error.
+ */
+static int sp_reg_update_bits(struct anx78xx *anx78xx, u8 addr, u8 offset,
+			      u8 mask, u8 val)
+{
+	int ret;
+	u8 orig, tmp;
+
+	ret = sp_reg_read(anx78xx, addr, offset, &orig);
+	if (ret < 0)
+		return ret;
+
+	tmp = orig & ~mask;
+	tmp |= val & mask;
+
+	return sp_reg_write(anx78xx, addr, offset, tmp);
+}
+
+/**
+ * sp_reg_set_bits: Perform a read/write cycle to set bits in register.
+ *
+ * @anx78xx: Device to read from.
+ * @addr: Address to write to.
+ * @offset: Byte interpreted by slave.
+ * @mask: Bitmask to change.
+ *
+ * Returns zero for success, a negative number on error.
+ */
+static inline int sp_reg_set_bits(struct anx78xx *anx78xx, u8 addr,
+				  u8 offset, u8 mask)
+{
+	return sp_reg_update_bits(anx78xx, addr, offset, mask, mask);
+}
+
+/**
+ * sp_reg_clear_bits: Perform a read/write cycle to clear bits in register.
+ *
+ * @anx78xx: Device to read from.
+ * @addr: Address to write to.
+ * @offset: Byte interpreted by slave.
+ * @mask: Bitmask to change.
+ *
+ * Returns zero for success, a negative number on error.
+ */
+static inline int sp_reg_clear_bits(struct anx78xx *anx78xx, u8 addr,
+				    u8 offset, u8 mask)
+{
+	return sp_reg_update_bits(anx78xx, addr, offset, mask, 0);
+}
+
+static inline void sp_video_mute(struct anx78xx *anx78xx, bool enable)
+{
+	if (enable)
+		sp_reg_set_bits(anx78xx, TX_P2, SP_VID_CTRL1_REG,
+				SP_VIDEO_MUTE);
+	else
+		sp_reg_clear_bits(anx78xx, TX_P2, SP_VID_CTRL1_REG,
+				  SP_VIDEO_MUTE);
+}
+
+static inline void sp_hdmi_mute_audio(struct anx78xx *anx78xx, bool enable)
+{
+	if (enable)
+		sp_reg_set_bits(anx78xx, RX_P0, SP_HDMI_MUTE_CTRL_REG,
+				SP_AUD_MUTE);
+	else
+		sp_reg_clear_bits(anx78xx, RX_P0, SP_HDMI_MUTE_CTRL_REG,
+				  SP_AUD_MUTE);
+}
+
+static inline void sp_hdmi_mute_video(struct anx78xx *anx78xx, bool enable)
+{
+	if (enable)
+		sp_reg_set_bits(anx78xx, RX_P0, SP_HDMI_MUTE_CTRL_REG,
+				SP_VID_MUTE);
+	else
+		sp_reg_clear_bits(anx78xx, RX_P0, SP_HDMI_MUTE_CTRL_REG,
+				  SP_VID_MUTE);
+}
+
+static inline void sp_addronly_set(struct anx78xx *anx78xx, bool enable)
+{
+	if (enable)
+		sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+				SP_ADDR_ONLY);
+	else
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+				  SP_ADDR_ONLY);
+}
+
+static inline void sp_set_link_bandwidth(struct anx78xx *anx78xx, u8 bw)
+{
+	sp_reg_write(anx78xx, TX_P0, SP_DP_MAIN_LINK_BW_SET_REG, bw);
+}
+
+static inline u8 sp_get_link_bandwidth(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	sp_reg_read(anx78xx, TX_P0, SP_DP_MAIN_LINK_BW_SET_REG, &val);
+
+	return val & SP_LINK_BW_SET_MASK;
+}
+
+static inline bool sp_get_pll_lock_status(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	sp_reg_read(anx78xx, TX_P0, SP_DP_DEBUG1_REG, &val);
+
+	return (val & SP_DEBUG_PLL_LOCK) != 0;
+}
+
+static inline void sp_gen_m_clk_with_downspreading(struct anx78xx *anx78xx)
+{
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_M_CALCULATION_CTRL_REG,
+			SP_M_GEN_CLK_SEL);
+}
+
+static inline void sp_gen_m_clk_without_downspreading(struct anx78xx *anx78xx)
+{
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_M_CALCULATION_CTRL_REG,
+			  SP_M_GEN_CLK_SEL);
+}
+
+static inline void sp_hdmi_set_hpd(struct anx78xx *anx78xx, bool enable)
+{
+	if (enable)
+		sp_reg_set_bits(anx78xx, TX_P2, SP_VID_CTRL3_REG, SP_HPD_OUT);
+	else
+		sp_reg_clear_bits(anx78xx, TX_P2, SP_VID_CTRL3_REG,
+				  SP_HPD_OUT);
+}
+
+static inline void sp_hdmi_set_termination(struct anx78xx *anx78xx,
+					   bool enable)
+{
+	if (enable)
+		sp_reg_clear_bits(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 7,
+				  SP_PD_RT);
+	else
+		sp_reg_set_bits(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 7,
+				SP_PD_RT);
+}
+
+static inline bool sp_hdcp_repeater_mode(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	sp_reg_read(anx78xx, RX_P1, SP_HDCP_BCAPS_SHADOW_REG, &val);
+
+	return (val & SP_BCAPS_REPEATER);
+}
+
+static inline void sp_clean_hdcp_status(struct anx78xx *anx78xx)
+{
+	sp_reg_write(anx78xx, TX_P0, SP_HDCP_CTRL0_REG,
+		     SP_BKSV_SRM_PASS | SP_KSVLIST_VLD);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_HDCP_CTRL0_REG, SP_RE_AUTH);
+	usleep_range(2000, 4000);
+}
+
+static const u8 dp_tx_output_precise_tune_bits[20] = {
+	0x01, 0x03, 0x07, 0x7f, 0x71, 0x6b, 0x7f,
+	0x73, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00,
+	0x0c, 0x42, 0x1e, 0x3e, 0x72, 0x7e,
+};
+
+static void sp_link_phy_initialization(struct anx78xx *anx78xx)
+{
+	int i;
+
+	/*
+	 * REVISIT : It is writing to a RESERVED bits in Analog Control 0
+	 * register.
+	 */
+	sp_reg_write(anx78xx, TX_P2, SP_ANALOG_CTRL0_REG, 0x02);
+
+	/*
+	 * Write DP TX output emphasis precise tune bits.
+	 */
+	for (i = 0; i < ARRAY_SIZE(dp_tx_output_precise_tune_bits); i++)
+		sp_reg_write(anx78xx, TX_P1, SP_DP_TX_LT_CTRL0_REG + i,
+			     dp_tx_output_precise_tune_bits[i]);
+}
+
+static void sp_set_system_state(struct anx78xx *anx78xx,
+				enum sp_tx_state new_state)
+{
+	u8 val;
+
+	if ((sp.tx_system_state >= STATE_LINK_TRAINING) &&
+	    (new_state < STATE_LINK_TRAINING))
+		sp_reg_set_bits(anx78xx, TX_P0, SP_DP_ANALOG_POWER_DOWN_REG,
+				SP_CH0_PD);
+
+	if (sp.tx_system_state >= STATE_LINK_TRAINING) {
+		if (new_state >= STATE_AUDIO_OUTPUT) {
+			sp_hdmi_mute_audio(anx78xx, true);
+		} else {
+			sp_hdmi_mute_video(anx78xx, true);
+			sp_video_mute(anx78xx, true);
+		}
+	}
+
+	if (!sp_hdcp_repeater_mode(anx78xx)) {
+		if (sp.tx_system_state >= STATE_HDCP_AUTH &&
+		    new_state <= STATE_HDCP_AUTH) {
+			sp_reg_read(anx78xx, TX_P0, SP_HDCP_CTRL0_REG, &val);
+			if (val & ~(SP_KSVLIST_VLD | SP_BKSV_SRM_PASS))
+				sp_clean_hdcp_status(anx78xx);
+		}
+	} else {
+		if (sp.tx_system_state > STATE_LINK_TRAINING &&
+		    new_state <= STATE_LINK_TRAINING) {
+			/* Inform AP to re-auth */
+			sp_hdmi_set_hpd(anx78xx, false);
+			sp_hdmi_set_termination(anx78xx, false);
+			msleep(50);
+		}
+	}
+
+	sp.tx_system_state = new_state;
+	sp.hdcp_state = HDCP_CAPABLE_CHECK;
+	sp.tx_lt_state = LT_INIT;
+	sp.tx_vo_state = VO_WAIT_VIDEO_STABLE;
+	/* Reset audio stable counter */
+	sp.audio_stable_count = 0;
+	sp_print_system_state(anx78xx, sp.tx_system_state);
+}
+
+static inline void sp_reg_hardware_reset(struct anx78xx *anx78xx)
+{
+	sp_reg_set_bits(anx78xx, TX_P2, SP_RESET_CTRL1_REG, SP_HW_RST);
+	sp_variable_init(anx78xx);
+	sp_set_system_state(anx78xx, STATE_SP_INITIALIZED);
+	msleep(500);
+}
+
+static int sp_wait_aux_op_finish(struct anx78xx *anx78xx)
+{
+	u8 errcnt;
+	u8 val;
+	struct device *dev = &anx78xx->client->dev;
+
+	errcnt = 150;
+	while (errcnt--) {
+		sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
+		if (!(val & SP_AUX_EN))
+			break;
+		usleep_range(2000, 4000);
+	}
+
+	if (!errcnt) {
+		dev_err(dev, "aux operate failed!\n");
+		return -1;
+	}
+
+	sp_reg_read(anx78xx, TX_P0, SP_AUX_CH_STATUS_REG, &val);
+	if (val & SP_AUX_STATUS) {
+		dev_err(dev, "wait aux operation status %.2x\n", val);
+		return -1;
+	}
+
+	return 0;
+}
+
+static void sp_print_system_state(struct anx78xx *anx78xx,
+				  enum sp_tx_state state)
+{
+	static const char *const name[] = {
+		[STATE_CABLE_UNPLUGGED]		= "Cable is pulled out",
+		[STATE_WAITING_CABLE_PLUG]	= "Waiting cable plug",
+		[STATE_SP_INITIALIZED]		= "SlimPort initialized",
+		[STATE_SINK_CONNECTION]		= "Sink connection",
+		[STATE_PARSE_EDID]		= "Parse EDID",
+		[STATE_LINK_TRAINING]		= "Link training",
+		[STATE_VIDEO_OUTPUT]		= "Video output",
+		[STATE_HDCP_AUTH]		= "HDCP authentication",
+		[STATE_AUDIO_OUTPUT]		= "Audio output",
+		[STATE_PLAY_BACK]		= "Playback",
+	};
+
+	dev_dbg(&anx78xx->client->dev, "SlimPort state: %s\n",
+		(state < ARRAY_SIZE(name)) ? name[state] : "INVALID");
+}
+
+static void sp_reset_aux(struct anx78xx *anx78xx)
+{
+	sp_reg_set_bits(anx78xx, TX_P2, SP_RESET_CTRL2_REG, SP_AUX_RST);
+	sp_reg_clear_bits(anx78xx, TX_P2, SP_RESET_CTRL2_REG, SP_AUX_RST);
+}
+
+static void sp_write_dpcd_addr(struct anx78xx *anx78xx, unsigned int addr)
+{
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_7_0_REG, addr & 0xff);
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_15_8_REG,
+		     (addr & 0xff00) >> 8);
+
+	/*
+	 * DP AUX CH Address Register #2, only update bits[3:0]
+	 * [7:4] RESERVED
+	 * [3:0] AUX_ADDR[19:16], Register control AUX CH address.
+	 */
+	sp_reg_update_bits(anx78xx, TX_P0, SP_AUX_ADDR_19_16_REG,
+			   SP_AUX_ADDR_19_16_MASK, (addr & 0xf0000) >> 16);
+}
+
+static int sp_dp_read_bytes_from_dpcd(struct anx78xx *anx78xx,
+				      unsigned int addr, u8 count, u8 *buf)
+{
+	u8 val, val1, i;
+
+	/* Buffer size of AUX CH is 16 */
+	if (count > 16)
+		return -1;
+
+	/* Clear AUX CH data buffer */
+	sp_reg_write(anx78xx, TX_P0, SP_BUF_DATA_COUNT_REG, SP_BUF_CLR);
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG,
+		     ((count - 1) << SP_AUX_LENGTH_SHIFT) | 0x09);
+	sp_write_dpcd_addr(anx78xx, addr);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, SP_AUX_EN);
+	usleep_range(2000, 4000);
+
+	if (sp_wait_aux_op_finish(anx78xx)) {
+		sp_reg_read(anx78xx, TX_P2, SP_DP_INT_STATUS_REG, &val);
+		sp_reg_read(anx78xx, TX_P0, SP_DP_DEBUG1_REG, &val1);
+		if (!(val1 & SP_POLLING_EN) || (val & SP_POLLING_ERR))
+			sp_reset_aux(anx78xx);
+		return -1;
+	}
+
+	for (i = 0; i < count; i++) {
+		sp_reg_read(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG + i, &val);
+		buf[i] = val;
+	}
+
+	return 0;
+}
+
+static int sp_dp_write_bytes_to_dpcd(struct anx78xx *anx78xx,
+				     unsigned int addr, u8 count, u8 *buf)
+{
+	int i;
+
+	/* Buffer size of AUX CH is 16 */
+	if (count > 16)
+		return -1;
+
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG,
+		     ((count - 1) << SP_AUX_LENGTH_SHIFT) | 0x08);
+	sp_write_dpcd_addr(anx78xx, addr);
+	for (i = 0; i < count; i++)
+		sp_reg_write(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG + i, buf[i]);
+
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, SP_AUX_EN);
+	return sp_wait_aux_op_finish(anx78xx);
+}
+
+static void sp_block_power_ctrl(struct anx78xx *anx78xx,
+				enum sp_tx_power_block sp_tx_pd_block,
+				bool power)
+{
+	if (power)
+		sp_reg_clear_bits(anx78xx, TX_P2, SP_POWERDOWN_CTRL_REG,
+				  sp_tx_pd_block);
+	else
+		sp_reg_set_bits(anx78xx, TX_P2, SP_POWERDOWN_CTRL_REG,
+				sp_tx_pd_block);
+
+	dev_dbg(&anx78xx->client->dev,
+		"sp_tx_power_on: %.2x\n", sp_tx_pd_block);
+}
+
+void sp_variable_init(struct anx78xx *anx78xx)
+{
+	sp.hdcp_enabled = false;
+
+	sp.tx_system_state = STATE_WAITING_CABLE_PLUG;
+	sp_print_system_state(anx78xx, sp.tx_system_state);
+
+	sp.read_edid_flag = false;
+
+	memset(sp.edid_blocks, 0, sizeof(*sp.edid_blocks));
+
+	sp.tx_lt_state = LT_INIT;
+	sp.hdcp_state = HDCP_CAPABLE_CHECK;
+	sp.repeater_state = HDCP_DONE;
+	sp.tx_vo_state = VO_WAIT_VIDEO_STABLE;
+	sp.tx_ao_state = AO_INIT;
+	sp.changed_bandwidth = DP_LINK_BW_5_4;
+
+	sp.hdcp_error_count = 0;
+	sp.hdcp_fail_count = 0;
+	sp.audio_stable_count = 0;
+
+	sp.tx_test_lt = false;
+	sp.tx_test_bw = 0;
+	sp.tx_test_edid = false;
+}
+
+static void sp_hdmi_tmds_phy_initialization(struct anx78xx *anx78xx)
+{
+	sp_reg_write(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 1, 0x90);
+	sp_reg_write(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 2, 0xa9);
+	sp_reg_write(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 6, 0x92);
+	sp_reg_write(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 7, 0x80);
+	sp_reg_write(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 20, 0xf2);
+}
+
+static void sp_hdmi_initialization(struct anx78xx *anx78xx)
+{
+	sp_reg_write(anx78xx, RX_P0, SP_HDMI_MUTE_CTRL_REG, SP_AUD_MUTE |
+		     SP_VID_MUTE);
+	sp_reg_set_bits(anx78xx, RX_P0, SP_CHIP_CTRL_REG, SP_MAN_HDMI5V_DET |
+			SP_PLLLOCK_CKDT_EN | SP_DIGITAL_CKDT_EN);
+
+	sp_reg_set_bits(anx78xx, RX_P0, SP_SOFTWARE_RESET1_REG,
+			SP_HDCP_MAN_RST | SP_SW_MAN_RST | SP_TMDS_RST |
+			SP_VIDEO_RST);
+	sp_reg_clear_bits(anx78xx, RX_P0, SP_SOFTWARE_RESET1_REG,
+			  SP_HDCP_MAN_RST | SP_SW_MAN_RST | SP_TMDS_RST |
+			  SP_VIDEO_RST);
+
+	/* Sync detect change, GP set mute */
+	sp_reg_set_bits(anx78xx, RX_P0, SP_AUD_EXCEPTION_ENABLE_BASE + 1,
+			BIT(5) | BIT(6));
+	sp_reg_set_bits(anx78xx, RX_P0, SP_AUD_EXCEPTION_ENABLE_BASE + 3,
+			SP_AEC_EN21);
+	sp_reg_set_bits(anx78xx, RX_P0, SP_AUDVID_CTRL_REG, SP_AVC_EN |
+			SP_AAC_OE | SP_AAC_EN);
+
+	sp_reg_clear_bits(anx78xx, RX_P0, SP_SYSTEM_POWER_DOWN1_REG,
+			  SP_PWDN_CTRL);
+
+	sp_reg_set_bits(anx78xx, RX_P0, SP_VID_DATA_RANGE_CTRL_REG,
+			SP_R2Y_INPUT_LIMIT);
+	sp_reg_write(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 22, 0xc4);
+	sp_reg_write(anx78xx, RX_P0, SP_TMDS_CTRL_BASE + 23, 0x18);
+
+	/* Enable DDC stretch */
+	sp_reg_write(anx78xx, TX_P0, SP_DP_EXTRA_I2C_DEV_ADDR_REG,
+		     SP_I2C_EXTRA_ADDR);
+
+	sp_hdmi_tmds_phy_initialization(anx78xx);
+	sp_hdmi_set_hpd(anx78xx, false);
+	sp_hdmi_set_termination(anx78xx, false);
+}
+
+static void sp_xtal_clk_sel(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	sp_reg_update_bits(anx78xx, TX_P2, SP_ANALOG_DEBUG2_REG,
+			   SP_XTAL_FRQ | SP_FORCE_SW_OFF_BYPASS,
+			   SP_XTAL_FRQ_27M);
+
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL3_REG,
+		     XTAL_CLK & SP_WAIT_COUNTER_7_0_MASK);
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL4_REG,
+		     ((XTAL_CLK & 0xff00) >> 2) | (XTAL_CLK / 10));
+
+	sp_reg_write(anx78xx, TX_P0, SP_I2C_GEN_10US_TIMER0_REG,
+		     XTAL_CLK & 0xff);
+	sp_reg_write(anx78xx, TX_P0, SP_I2C_GEN_10US_TIMER1_REG,
+		     (XTAL_CLK & 0xff00) >> 8);
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_MISC_CTRL_REG,
+		     XTAL_CLK / 10 - 1);
+
+	sp_reg_read(anx78xx, RX_P0, SP_HDMI_US_TIMER_CTRL_REG, &val);
+	sp_reg_write(anx78xx, RX_P0, SP_HDMI_US_TIMER_CTRL_REG,
+		     (val & SP_MS_TIMER_MARGIN_10_8_MASK) |
+		     ((((XTAL_CLK / 10) >> 1) - 2) << 3));
+}
+
+void sp_tx_initialization(struct anx78xx *anx78xx)
+{
+	/* set terminal resistor to 50 ohm */
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, 0x30);
+	/* enable aux double diff output */
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, 0x08);
+
+	if (!sp_hdcp_repeater_mode(anx78xx)) {
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_HDCP_CTRL_REG,
+				  SP_AUTO_EN | SP_AUTO_START);
+		sp_reg_write(anx78xx, TX_P0, SP_OTP_KEY_PROTECT1_REG,
+			     SP_OTP_PSW1);
+		sp_reg_write(anx78xx, TX_P0, SP_OTP_KEY_PROTECT2_REG,
+			     SP_OTP_PSW2);
+		sp_reg_write(anx78xx, TX_P0, SP_OTP_KEY_PROTECT3_REG,
+			     SP_OTP_PSW3);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_HDCP_KEY_COMMAND_REG,
+				SP_DISABLE_SYNC_HDCP);
+	}
+
+	sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL8_REG, SP_VID_VRES_TH);
+
+	/*
+	 * DP HDCP auto authentication wait timer (when downstream starts to
+	 * auth, DP side will wait for this period then do auth automatically)
+	 */
+	sp_reg_write(anx78xx, TX_P0, SP_HDCP_AUTO_TIMER_REG, 0x00);
+
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_HDCP_CTRL_REG, SP_LINK_POLLING);
+
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_LINK_DEBUG_CTRL_REG,
+			SP_M_VID_DEBUG);
+	sp_reg_set_bits(anx78xx, TX_P2, SP_ANALOG_DEBUG2_REG,
+			SP_POWERON_TIME_1P5MS);
+
+	sp_xtal_clk_sel(anx78xx);
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_DEFER_CTRL_REG,
+		     SP_DEFER_CTRL_EN | 0x0c);
+
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_POLLING_CTRL_REG,
+			SP_AUTO_POLLING_DISABLE);
+	/*
+	 * Short the link integrity check timer to speed up bstatus
+	 * polling for HDCP CTS item 1A-07
+	 */
+	sp_reg_write(anx78xx, TX_P0, SP_HDCP_LINK_CHECK_TIMER_REG, 0x1d);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_MISC_CTRL_REG,
+			SP_EQ_TRAINING_LOOP);
+
+	/* power down the main link by default */
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_ANALOG_POWER_DOWN_REG,
+			SP_CH0_PD);
+
+	sp_reg_write(anx78xx, TX_P2, SP_INT_CTRL_REG, 0x01);
+
+	sp_link_phy_initialization(anx78xx);
+	sp_gen_m_clk_with_downspreading(anx78xx);
+
+	sp.down_sample_en = false;
+}
+
+/*
+ * Check if it is ANALOGIX dongle.
+ */
+static const u8 anx_oui[3] = {0x00, 0x22, 0xb9};
+
+static bool is_anx_dongle(struct anx78xx *anx78xx)
+{
+	u8 buf[3];
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_SINK_OUI, 3, buf);
+	if (!memcmp(buf, anx_oui, 3))
+		return true;
+
+	/* 0x0500~0x0502: BRANCH_IEEE_OUI */
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_BRANCH_OUI, 3, buf);
+	if (!memcmp(buf, anx_oui, 3))
+		return true;
+
+	return false;
+}
+
+static const u8 anx7750[4] = {0x37, 0x37, 0x35, 0x30};
+
+static u8 sp_dp_get_max_rx_bandwidth(struct anx78xx *anx78xx)
+{
+	u8 bandwidth, max_link_rate;
+	u8 buf[4];
+
+	bandwidth = 0;
+	/*
+	 * When ANX dongle is connected, if CHIP_ID=0x7750 the bandwidth is
+	 * 6.75G because ANX7750 DPCD 0x052x is not available.
+	 */
+	if (is_anx_dongle(anx78xx)) {
+		/*
+		 * 00503h - 005FFh: RESERVED for Branch Device vendor-specific
+		 * usage
+		 */
+		sp_dp_read_bytes_from_dpcd(anx78xx, 0x503, 4, buf);
+		if (!memcmp(buf, anx7750, sizeof(anx7750)))
+			bandwidth = DP_LINK_BW_6_75;
+		else
+			sp_dp_read_bytes_from_dpcd(anx78xx, 0x521,
+						   1, &bandwidth);
+	}
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_MAX_LINK_RATE, 1,
+				   &max_link_rate);
+	if (bandwidth < max_link_rate)
+		bandwidth = max_link_rate;
+
+	return bandwidth;
+}
+
+static bool sp_get_dp_connection(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	if (sp_dp_read_bytes_from_dpcd(anx78xx, DP_SINK_COUNT, 1, &val))
+		return false;
+
+	/* FIXME: Shouldn't be 0x3f SINK_COUNT[5:0] */
+	if (!(val & 0x1f))
+		return false;
+
+	if (sp_dp_read_bytes_from_dpcd(anx78xx, DP_NORP, 1, &val))
+		return false;
+
+	if (val & 0x20) {
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_SET_POWER, 1, &val);
+		/*
+		 * Bit 5 = SET_DN_DEVICE_DP_PWR_5V
+		 * Bit 6 = SET_DN_DEVICE_DP_PWR_12V
+		 * Bit 7 = SET_DN_DEVICE_DP_PWR_18V
+		 */
+		val &= 0x1f;
+		val |= 0x20;
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_SET_POWER, 1, &val);
+	}
+
+	return true;
+}
+
+static void sp_enable_video_input(struct anx78xx *anx78xx, bool enable)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_VID_CTRL1_REG, &val);
+	if (enable) {
+		sp_reg_set_bits(anx78xx, TX_P2, SP_VID_CTRL1_REG, SP_VIDEO_EN);
+		dev_dbg(dev, "Slimport video is enabled!\n");
+	} else {
+		sp_reg_clear_bits(anx78xx, TX_P2, SP_VID_CTRL1_REG,
+				  SP_VIDEO_EN);
+		dev_dbg(dev, "Slimport video is disabled!\n");
+	}
+}
+
+static u8 sp_get_edid_bandwidth(u8 *data)
+{
+	u16 pclk;
+
+	pclk = ((u16)data[1] << 8) | ((u16)data[0] & 0xff);
+	if (pclk <= 5300)
+		return DP_LINK_BW_1_62;
+	else if (pclk <= 8900)
+		return DP_LINK_BW_2_7;
+	else if (pclk <= 18000)
+		return DP_LINK_BW_5_4;
+	else
+		return DP_LINK_BW_6_75;
+}
+
+static u8 sp_parse_edid_to_get_bandwidth(struct anx78xx *anx78xx)
+{
+	int i;
+	u8 bandwidth, temp;
+
+	bandwidth = DP_LINK_BW_1_62;
+	for (i = 0; i < 4; i++) {
+		if (sp.edid_blocks[0x36 + 0x12 * i] == 0)
+			break;
+		temp = sp_get_edid_bandwidth(sp.edid_blocks + 0x36 + 0x12 * i);
+		dev_dbg(&anx78xx->client->dev, "bandwidth via EDID : %x\n",
+			temp);
+		if (bandwidth < temp)
+			bandwidth = temp;
+		if (bandwidth >= DP_LINK_BW_6_75)
+			break;
+	}
+
+	return bandwidth;
+}
+
+u8 sp_get_link_bandwidth_from_edid(struct anx78xx *anx78xx)
+{
+	u8 bandwidth, max_bandwidth;
+
+	bandwidth = sp_dp_get_max_rx_bandwidth(anx78xx);
+	max_bandwidth = sp_parse_edid_to_get_bandwidth(anx78xx);
+	if (bandwidth > max_bandwidth)
+		return max_bandwidth;
+
+	return bandwidth;
+}
+
+static int sp_tx_aux_wr(struct anx78xx *anx78xx, u8 offset)
+{
+	sp_reg_write(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG, offset);
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, SP_AUX_EN);
+
+	return sp_wait_aux_op_finish(anx78xx);
+}
+
+static int sp_tx_aux_rd(struct anx78xx *anx78xx, u8 len)
+{
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, len);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, SP_AUX_EN);
+
+	return sp_wait_aux_op_finish(anx78xx);
+}
+
+static int sp_edid_read(struct anx78xx *anx78xx, u8 offset,
+			u8 *buf)
+{
+	u8 data_cnt, errcnt;
+	u8 val, ret, i;
+
+	sp_tx_aux_wr(anx78xx, offset);
+	/* read 16 bytes (MOT = 1) */
+	sp_tx_aux_rd(anx78xx, 0xf0 | DP_AUX_I2C_MOT | DP_AUX_I2C_READ);
+	data_cnt = 0;
+	errcnt = 0;
+
+	while (data_cnt < 16) {
+		sp_reg_read(anx78xx, TX_P0, SP_BUF_DATA_COUNT_REG, &val);
+		val &= SP_BUF_DATA_COUNT_MASK;
+		if (val) {
+			for (i = 0; i < val; i++)
+				sp_reg_read(anx78xx, TX_P0,
+					    SP_DP_BUF_DATA0_REG + i,
+					    &buf[data_cnt + i]);
+			data_cnt += val;
+		} else {
+			if (errcnt++ <= 2)
+				sp_reset_aux(anx78xx);
+			else
+				return -1;
+		}
+		/* read 16 - data_cnt bytes (MOT = 1) */
+		val = DP_AUX_I2C_MOT | DP_AUX_I2C_READ |
+		      ((0x0f - data_cnt) << 4);
+		sp_tx_aux_rd(anx78xx, val);
+	}
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x01);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+			SP_ADDR_ONLY | SP_AUX_EN);
+	ret = sp_wait_aux_op_finish(anx78xx);
+	sp_addronly_set(anx78xx, false);
+
+	return ret;
+}
+
+static void sp_tx_edid_read_initial(struct anx78xx *anx78xx)
+{
+	/* Write AUX CH address 0x00050 */
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_7_0_REG, SP_I2C_EXTRA_ADDR);
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_15_8_REG, 0);
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_AUX_ADDR_19_16_REG,
+			  SP_AUX_ADDR_19_16_MASK);
+}
+
+static int sp_seg_edid_read(struct anx78xx *anx78xx,
+			    u8 segment, u8 offset)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val, errcnt;
+	int i;
+
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_7_0_REG, 0x30);
+
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+			SP_ADDR_ONLY | SP_AUX_EN);
+
+	if (sp_wait_aux_op_finish(anx78xx))
+		return -1;
+
+	sp_reg_write(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG, segment);
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
+
+	sp_reg_update_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+			   SP_ADDR_ONLY | SP_AUX_EN, SP_AUX_EN);
+
+	errcnt = 10;
+	while (errcnt--) {
+		sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
+		if (!(val & SP_AUX_EN))
+			break;
+		usleep_range(1000, 2000);
+	}
+
+	if (!errcnt) {
+		dev_err(dev,
+			"failed to read DP AUX Channel Control Register 2\n");
+		sp_reset_aux(anx78xx);
+		return -1;
+	}
+
+	sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_7_0_REG, SP_I2C_EXTRA_ADDR);
+	sp_tx_aux_wr(anx78xx, offset);
+	/* read 16 bytes (MOT = 1) */
+	sp_tx_aux_rd(anx78xx, 0xf0 | DP_AUX_I2C_MOT | DP_AUX_I2C_READ);
+
+	for (i = 0; i < 16; i++) {
+		errcnt = 10;
+		while (errcnt--) {
+			sp_reg_read(anx78xx, TX_P0, SP_BUF_DATA_COUNT_REG,
+				    &val);
+			if (val & SP_BUF_DATA_COUNT_MASK)
+				break;
+			usleep_range(2000, 4000);
+		}
+
+		if (!errcnt) {
+			dev_err(dev,
+				"failed to read DP Buffer Data Count Register\n");
+			sp_reset_aux(anx78xx);
+			return -1;
+		}
+
+		sp_reg_read(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG + i, &val);
+	}
+
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x01);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+			SP_ADDR_ONLY | SP_AUX_EN);
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+			  SP_ADDR_ONLY);
+	sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
+
+	errcnt = 10;
+	while (errcnt--) {
+		sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
+		if (!(val & SP_AUX_EN))
+			break;
+		usleep_range(1000, 2000);
+	}
+
+	if (!errcnt) {
+		dev_err(dev,
+			"failed to read DP AUX Channel Control Register 2\n");
+		sp_reset_aux(anx78xx);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int sp_edid_block_checksum(const u8 *raw_edid)
+{
+	int i;
+	u8 csum = 0;
+
+	for (i = 0; i < EDID_LENGTH; i++)
+		csum += raw_edid[i];
+
+	return csum;
+}
+
+static int sp_tx_edid_read(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val, last_block, offset = 0;
+	u8 buf[16];
+	int i, j, count;
+
+	sp_tx_edid_read_initial(anx78xx);
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
+			SP_AUX_EN | SP_ADDR_ONLY);
+
+	if (sp_wait_aux_op_finish(anx78xx))
+		return -1;
+
+	sp_addronly_set(anx78xx, false);
+
+	/* Read the number of blocks */
+	sp_tx_aux_wr(anx78xx, 0x7e);
+	sp_tx_aux_rd(anx78xx, DP_AUX_I2C_READ);
+	sp_reg_read(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG, &last_block);
+	dev_dbg(dev, "last EDID block is %d\n", last_block);
+
+	/* FIXME: Why not just cap to 3 if the reported value is >3 */
+	if (last_block > 3)
+		last_block = 1;
+
+	/* for every block */
+	for (count = 0; count <= last_block; count++) {
+		switch (count) {
+		case 0:
+		case 1:
+			for (i = 0; i < 8; i++) {
+				offset = (i + count * 8) * 16;
+				if (sp_edid_read(anx78xx, offset, buf))
+					return -1;
+				for (j = 0; j < 16; j++)
+					sp.edid_blocks[offset + j] = buf[j];
+			}
+			break;
+		case 2:
+		case 3:
+			offset = (count == 2) ? 0x00 : 0x80;
+			for (j = 0; j < 8; j++) {
+				if (sp_seg_edid_read(anx78xx, count / 2,
+						     offset))
+					return -1;
+				offset = offset + 0x10;
+			}
+			break;
+		default:
+			break;
+		}
+	}
+
+	sp_reset_aux(anx78xx);
+
+	if (!drm_edid_block_valid(sp.edid_blocks, 0, true, NULL)) {
+		dev_err(dev, "EDID block is invalid\n");
+		return -1;
+	}
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_REQUEST, 1, &val);
+	if (val & DP_TEST_LINK_EDID_READ) {
+		dev_dbg(dev, "EDID test requested\n");
+		val = sp_edid_block_checksum(sp.edid_blocks);
+		dev_dbg(dev, "EDID checksum is %d\n", val);
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_EDID_CHECKSUM, 1,
+					  &val);
+		sp.tx_test_edid = true;
+		val = DP_TEST_EDID_CHECKSUM_WRITE;
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_RESPONSE, 1, &val);
+	}
+
+	return 0;
+}
+
+static bool sp_check_with_pre_edid(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 i;
+	u8 buf[16];
+	bool ret = false;
+
+	sp_tx_edid_read_initial(anx78xx);
+	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, 0x03);
+
+	if (sp_wait_aux_op_finish(anx78xx))
+		goto return_point;
+
+	sp_addronly_set(anx78xx, false);
+
+	if (sp_edid_read(anx78xx, 0x70, buf))
+		goto return_point;
+
+	for (i = 0; i < 16; i++) {
+		if (sp.edid_blocks[0x70 + i] != buf[i]) {
+			dev_dbg(dev, "%s\n",
+				"different checksum and blocks num\n");
+			goto return_point;
+		}
+	}
+
+	if (sp_edid_read(anx78xx, 0x08, buf))
+		goto return_point;
+
+	for (i = 0; i < 16; i++) {
+		if (sp.edid_blocks[i + 8] != buf[i]) {
+			dev_dbg(dev, "different edid information\n");
+			goto return_point;
+		}
+	}
+
+	ret = true;
+return_point:
+	sp_reset_aux(anx78xx);
+
+	return ret;
+}
+
+static bool sp_edid_process(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 bw, edid_bw, val;
+	int i;
+
+	if (sp.read_edid_flag) {
+		if (!sp_check_with_pre_edid(anx78xx))
+			sp.read_edid_flag = false;
+	} else {
+		if (sp_tx_edid_read(anx78xx)) {
+			dev_err(dev, "EDID corruption!\n");
+			return false;
+		}
+	}
+
+	/* Release the HPD after the OTP loaddown */
+	for (i = 0; i < 10; i++) {
+		sp_reg_read(anx78xx, TX_P0, SP_HDCP_KEY_STATUS_REG, &val);
+		if (val & 0x01)
+			break;
+
+		dev_dbg(dev, "waiting HDCP KEY loaddown\n");
+		usleep_range(1000, 2000);
+	}
+
+	sp_reg_write(anx78xx, RX_P0, SP_INT_MASK_BASE + 1,
+		     SP_HDMI_DVI | SP_CKDT_CHG | SP_SCDT_CHG |
+		     SP_CABLE_PLUG_CHG);
+
+	if (!sp_hdcp_repeater_mode(anx78xx)) {
+		sp_hdmi_set_hpd(anx78xx, true);
+		sp_hdmi_set_termination(anx78xx, true);
+	}
+
+	bw = sp_dp_get_max_rx_bandwidth(anx78xx);
+	dev_dbg(dev, "RX BW %x\n", bw);
+
+	edid_bw = sp_parse_edid_to_get_bandwidth(anx78xx);
+	if (bw <= edid_bw)
+		edid_bw = bw;
+
+	dev_dbg(dev, "set link bw in edid %x\n", edid_bw);
+	sp.changed_bandwidth = edid_bw;
+
+	return true;
+}
+
+static void sp_lvttl_bit_mapping(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+	u8 vid_bit;
+
+	sp_reg_read(anx78xx, RX_P0, SP_VIDEO_STATUS_REG, &val);
+	switch ((val & SP_COLOR_DEPTH_MASK) >> SP_COLOR_DEPTH_SHIFT) {
+	default:
+	case SP_COLOR_DEPTH_MODE_LEGACY:
+		val = SP_IN_BPC_8BIT;
+		vid_bit = 0;
+		break;
+	case SP_COLOR_DEPTH_MODE_24BIT:
+		val = SP_IN_BPC_8BIT;
+		if (sp.frame.avi.colorspace == HDMI_COLORSPACE_YUV422)
+			vid_bit = 5;
+		else
+			vid_bit = 1;
+		break;
+	case SP_COLOR_DEPTH_MODE_30BIT:
+		val = SP_IN_BPC_10BIT;
+		if (sp.frame.avi.colorspace == HDMI_COLORSPACE_YUV422)
+			vid_bit = 6;
+		else
+			vid_bit = 2;
+		/*
+		 * For 10bit video must be set this value to 12bit by
+		 * someone
+		 */
+		if (sp.down_sample_en)
+			vid_bit = 3;
+		break;
+	case SP_COLOR_DEPTH_MODE_36BIT:
+		val = SP_IN_BPC_12BIT;
+		if (sp.frame.avi.colorspace == HDMI_COLORSPACE_YUV422)
+			vid_bit = 6;
+		else
+			vid_bit = 3;
+		break;
+	}
+
+	/*
+	 * For down sample video (12bit, 10bit ---> 8bit),
+	 * this register doesn't change
+	 */
+	if (!sp.down_sample_en)
+		sp_reg_update_bits(anx78xx, TX_P2, SP_VID_CTRL2_REG,
+				   SP_IN_BPC_MASK | SP_IN_COLOR_F_MASK,
+				   (val << SP_IN_BPC_SHIFT) |
+				   sp.frame.avi.colorspace);
+
+	sp_reg_write(anx78xx, TX_P2, SP_BIT_CTRL_SPECIFIC_REG,
+		     SP_ENABLE_BIT_CTRL | vid_bit << SP_BIT_CTRL_SELECT_SHIFT);
+
+	if (sp.tx_test_edid) {
+		/* Set color depth to 6 bpc (18 bpp) for link cts */
+		sp_reg_update_bits(anx78xx, TX_P2, SP_VID_CTRL2_REG,
+				   SP_IN_BPC_MASK, SP_IN_BPC_6BIT);
+		sp.tx_test_edid = false;
+		dev_dbg(dev, "color space is set to 6 bpc (18 bpp)\n");
+	}
+
+	if (sp.frame.avi.colorspace) {
+		/*
+		 * Set video values to default of channel 0, 1 and 2 for HDCP
+		 * embedded "blue screen" when HDCP authentication failed.
+		 */
+		sp_reg_write(anx78xx, TX_P0, SP_HDCP_VID0_BLUE_SCREEN_REG,
+			     0x80);
+		sp_reg_write(anx78xx, TX_P0, SP_HDCP_VID1_BLUE_SCREEN_REG,
+			     0x00);
+		sp_reg_write(anx78xx, TX_P0, SP_HDCP_VID2_BLUE_SCREEN_REG,
+			     0x80);
+	} else {
+		sp_reg_write(anx78xx, TX_P0, SP_HDCP_VID0_BLUE_SCREEN_REG,
+			     0x00);
+		sp_reg_write(anx78xx, TX_P0, SP_HDCP_VID0_BLUE_SCREEN_REG,
+			     0x00);
+		sp_reg_write(anx78xx, TX_P0, SP_HDCP_VID0_BLUE_SCREEN_REG,
+			     0x00);
+	}
+}
+
+static unsigned long sp_pclk_calc(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	unsigned long str_plck;
+	u16 vid_counter;
+	u8 val;
+
+	sp_reg_read(anx78xx, RX_P0, SP_PCLK_HIGHRES_CNT_BASE + 2, &val);
+	vid_counter = val << 8;
+	sp_reg_read(anx78xx, RX_P0, SP_PCLK_HIGHRES_CNT_BASE + 1, &val);
+	vid_counter |= val;
+	str_plck = (vid_counter * XTAL_CLK) >> 12;
+	dev_dbg(dev, "pixel clock is %ld.%ld\n", str_plck / 10, str_plck % 10);
+	return str_plck;
+}
+
+static int sp_tx_bw_lc_sel(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	unsigned long pixel_clk;
+	u8 link, val;
+
+	pixel_clk = sp_pclk_calc(anx78xx);
+
+	sp_reg_read(anx78xx, RX_P0, SP_VIDEO_STATUS_REG, &val);
+	switch ((val & SP_COLOR_DEPTH_MASK) >> SP_COLOR_DEPTH_SHIFT) {
+	case SP_COLOR_DEPTH_MODE_LEGACY:
+	case SP_COLOR_DEPTH_MODE_24BIT:
+	default:
+		break;
+	case SP_COLOR_DEPTH_MODE_30BIT:
+		pixel_clk = (pixel_clk * 5) >> 2;
+		break;
+	case SP_COLOR_DEPTH_MODE_36BIT:
+		pixel_clk = (pixel_clk * 3) >> 1;
+		break;
+	}
+
+	sp.down_sample_en = false;
+	if (pixel_clk <= 530) {
+		link = DP_LINK_BW_1_62;
+	} else if (pixel_clk <= 890) {
+		link = DP_LINK_BW_2_7;
+	} else if (pixel_clk <= 1800) {
+		link = DP_LINK_BW_5_4;
+	} else {
+		link = DP_LINK_BW_6_75;
+		if (pixel_clk > 2240)
+			sp.down_sample_en = true;
+	}
+
+	if (sp_get_link_bandwidth(anx78xx) != link) {
+		sp.changed_bandwidth = link;
+		dev_err(dev,
+			"different bandwidth between sink and video %.2x",
+			link);
+		return -1;
+	}
+	return 0;
+}
+
+static void sp_downspreading_enable(struct anx78xx *anx78xx, bool enable)
+{
+	u8 val;
+
+	sp_reg_read(anx78xx, TX_P0, SP_DP_DOWNSPREADING_CTRL1_REG, &val);
+
+	if (enable) {
+		val |= SP_TX_SSC_DOWNSPREADING;
+		sp_reg_write(anx78xx, TX_P0, SP_DP_DOWNSPREADING_CTRL1_REG,
+			     val);
+
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_DOWNSPREAD_CTRL, 1,
+					   &val);
+		val |= DP_SPREAD_AMP_0_5;
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_DOWNSPREAD_CTRL, 1, &val);
+	} else {
+		val &= ~SP_TX_SSC_DISABLE;
+		sp_reg_write(anx78xx, TX_P0, SP_DP_DOWNSPREADING_CTRL1_REG,
+			     val);
+
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_DOWNSPREAD_CTRL, 1,
+					   &val);
+		val &= ~DP_SPREAD_AMP_0_5;
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_DOWNSPREAD_CTRL, 1, &val);
+	}
+}
+
+static void sp_config_ssc(struct anx78xx *anx78xx, enum sp_ssc_dep sscdep)
+{
+	sp_reg_write(anx78xx, TX_P0, SP_DP_DOWNSPREADING_CTRL1_REG, 0x0);
+	sp_reg_write(anx78xx, TX_P0, SP_DP_DOWNSPREADING_CTRL1_REG, sscdep);
+	sp_downspreading_enable(anx78xx, true);
+}
+
+void sp_dp_enable_enhanced_mode(struct anx78xx *anx78xx, bool enable)
+{
+	if (enable)
+		sp_reg_set_bits(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 4,
+				SP_ENHANCED_MODE);
+	else
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 4,
+				  SP_ENHANCED_MODE);
+}
+
+static void sp_dp_enable_rx_to_enhanced_mode(struct anx78xx *anx78xx,
+					     bool enable)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+
+	if (enable) {
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_LANE_COUNT_SET, 1, &val);
+		val |= DP_ENHANCED_FRAME_CAP;
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_LANE_COUNT_SET, 1, &val);
+		dev_dbg(dev, "rx to enhanced mode enabled\n");
+	} else {
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_LANE_COUNT_SET, 1, &val);
+		val &= ~DP_ENHANCED_FRAME_CAP;
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_LANE_COUNT_SET, 1, &val);
+		dev_dbg(dev, "rx to enhanced mode disabled\n");
+	}
+}
+
+static bool sp_dp_is_enhanced_mode_available(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_MAX_LANE_COUNT, 1, &val);
+
+	return val & DP_ENHANCED_FRAME_CAP;
+}
+
+static void sp_dp_set_enhanced_mode(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	val = sp_dp_is_enhanced_mode_available(anx78xx);
+	sp_dp_enable_rx_to_enhanced_mode(anx78xx, val);
+	sp_dp_enable_enhanced_mode(anx78xx, val);
+}
+
+static u16 sp_link_err_check(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u16 err;
+	u8 buf[2];
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_SYMBOL_ERROR_COUNT_LANE0, 2,
+				   buf);
+	usleep_range(5000, 10000);
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_SYMBOL_ERROR_COUNT_LANE0, 2,
+				   buf);
+
+	if (buf[1] & DP_ERROR_COUNT_VALID) {
+		err = ((buf[1] & DP_ERROR_COUNT_BITS_14_8_MASK) << 8) + buf[0];
+		dev_err(dev, "error of Lane %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+
+static bool sp_lt_finish(struct anx78xx *anx78xx)
+{
+	u8 val;
+	struct device *dev = &anx78xx->client->dev;
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_LANE0_1_STATUS, 1, &val);
+
+	val &= DP_LANE_CR_DONE | DP_LANE_CHANNEL_EQ_DONE |
+	       DP_LANE_SYMBOL_LOCKED;
+	if (val != (DP_LANE_CR_DONE | DP_LANE_CHANNEL_EQ_DONE |
+	    DP_LANE_SYMBOL_LOCKED)) {
+		dev_dbg(dev, "Lane0 status error %.2x\n", val);
+		sp.tx_lt_state = LT_ERROR;
+		return false;
+	}
+
+	/*
+	 * If there is link error, adjust pre-emphasis to check error
+	 * again. If there is no error, keep the setting, otherwise
+	 * use 400mv0db
+	 */
+	if (sp.tx_test_lt) {
+		sp.tx_test_lt = false;
+		sp.tx_lt_state = LT_INIT;
+		return true;
+	}
+
+	if (sp_link_err_check(anx78xx)) {
+		sp_reg_read(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG, &val);
+		if (!(val & SP_MAX_PRE_REACH)) {
+			/* Increase one pre-level */
+			sp_reg_write(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG,
+				     val + 0x08);
+			/*
+			 * If error still exists, return to the link training
+			 * value
+			 */
+			if (sp_link_err_check(anx78xx))
+				sp_reg_write(anx78xx, TX_P0,
+					     SP_DP_LANE0_LT_CTRL_REG, val);
+		}
+	}
+
+	val = sp_get_link_bandwidth(anx78xx);
+	if (val != sp.changed_bandwidth) {
+		dev_dbg(dev, "bandwidth changed, cur:%.2x, per:%.2x\n", val,
+			sp.changed_bandwidth);
+		sp.tx_lt_state = LT_ERROR;
+		return false;
+	}
+
+	dev_dbg(dev, "LT succeed, bandwidth: %.2x", val);
+	sp_reg_read(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG, &val);
+	dev_dbg(dev, "Lane0 set to %.2x\n", val);
+	sp.tx_lt_state = LT_INIT;
+
+	if (sp_hdcp_repeater_mode(anx78xx)) {
+		dev_dbg(dev, "HPD set to 1!\n");
+		sp_hdmi_set_hpd(anx78xx, true);
+		sp_hdmi_set_termination(anx78xx, true);
+	}
+
+	/*
+	 * Under low voltage (DVD10 = 0.97V), some chips cannot output video,
+	 * link down interrupt always happens.
+	 */
+	if (sp_link_err_check(anx78xx) > 200) {
+		dev_dbg(dev, "need to reset Serdes FIFO");
+		sp.tx_lt_state = LT_ERROR;
+	} else {
+		return true;
+	}
+
+	return false;
+}
+
+static bool sp_link_training(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val, version;
+
+	switch (sp.tx_lt_state) {
+	case LT_INIT:
+		sp_block_power_ctrl(anx78xx, SP_TX_PWR_VIDEO, 1);
+		sp_video_mute(anx78xx, true);
+		sp_enable_video_input(anx78xx, false);
+		sp.tx_lt_state = LT_WAIT_PLL_LOCK;
+	/* fallthrough */
+	case LT_WAIT_PLL_LOCK:
+		if (!sp_get_pll_lock_status(anx78xx)) {
+			sp_reg_read(anx78xx, TX_P0, SP_DP_PLL_CTRL_REG,
+				    &val);
+
+			val |= SP_PLL_RST;
+			sp_reg_write(anx78xx, TX_P0, SP_DP_PLL_CTRL_REG,
+				     val);
+
+			val &= ~SP_PLL_RST;
+			sp_reg_write(anx78xx, TX_P0, SP_DP_PLL_CTRL_REG,
+				     val);
+
+			dev_dbg(dev, "PLL not lock!\n");
+			break;
+		} else {
+			sp.tx_lt_state = LT_CHECK_LINK_BW;
+		}
+	/* fallthrough */
+	case LT_CHECK_LINK_BW:
+		val = sp_dp_get_max_rx_bandwidth(anx78xx);
+		if (val < sp.changed_bandwidth) {
+			dev_dbg(dev, "over bandwidth!\n");
+			sp.changed_bandwidth = val;
+			break;
+		} else {
+			sp.tx_lt_state = LT_START;
+		}
+	/* fallthrough */
+	case LT_START:
+		if (sp.tx_test_lt) {
+			sp.changed_bandwidth = sp.tx_test_bw;
+			sp_reg_clear_bits(anx78xx, TX_P2, SP_VID_CTRL2_REG,
+					  0x70);
+		} else {
+			sp_reg_write(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG,
+				     0x00);
+		}
+
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_ANALOG_POWER_DOWN_REG,
+				  SP_CH0_PD);
+
+		sp_config_ssc(anx78xx, SSC_DEP_4000PPM);
+		sp_set_link_bandwidth(anx78xx, sp.changed_bandwidth);
+		sp_dp_set_enhanced_mode(anx78xx);
+
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_DPCD_REV, 1, &version);
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_SET_POWER, 1, &val);
+		if (version >= 0x12)
+			val &= ~DP_SET_POWER_12_MASK;
+		else
+			val &= ~DP_SET_POWER_MASK;
+		val |= DP_SET_POWER_D0;
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_SET_POWER, 1, &val);
+
+		sp_reg_write(anx78xx, TX_P0, SP_DP_LT_CTRL_REG, SP_LT_EN);
+		sp.tx_lt_state = LT_WAITING_FINISH;
+	/* fallthrough */
+	case LT_WAITING_FINISH:
+		/* Waiting interrupt to change training state. */
+		break;
+	case LT_ERROR:
+		sp_reg_set_bits(anx78xx, TX_P2, SP_RESET_CTRL2_REG,
+				SP_SERDES_FIFO_RST);
+		msleep(20);
+		sp_reg_clear_bits(anx78xx, TX_P2, SP_RESET_CTRL2_REG,
+				  SP_SERDES_FIFO_RST);
+		dev_err(dev, "LT ERROR reset SERDES FIFO");
+		sp.tx_lt_state = LT_INIT;
+		break;
+	case LT_FINISH:
+		if (sp_lt_finish(anx78xx))
+			return true;
+		break;
+	default:
+		break;
+	}
+
+	return false;
+}
+
+static bool sp_match_vic_for_bt709(u8 vic)
+{
+	/* Video Identification Code (VIC) for BT709 */
+	return ((vic == 0x04) || (vic == 0x05) || (vic == 0x10) ||
+		(vic == 0x13) || (vic == 0x14) || (vic == 0x1f) ||
+		(vic == 0x20) || (vic == 0x21) || (vic == 0x22) ||
+		(vic == 0x27) || (vic == 0x28) || (vic == 0x29) ||
+		(vic == 0x2e) || (vic == 0x2f) || (vic == 0x3c) ||
+		(vic == 0x3d) || (vic == 0x3e) || (vic == 0x3f) ||
+		(vic == 0x40));
+}
+
+static void sp_set_colorspace(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 colorspace, val;
+
+	if (sp.down_sample_en) {
+		if (sp.frame.avi.colorspace == HDMI_COLORSPACE_YUV422) {
+			dev_dbg(dev, "YCbCr4:2:2 ---> PASS THROUGH.\n");
+			sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL6_REG, 0x00);
+			sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL5_REG, 0x00);
+		} else if (sp.frame.avi.colorspace == HDMI_COLORSPACE_YUV444) {
+			dev_dbg(dev, "YCbCr4:4:4 ---> YCbCr4:2:2\n");
+			sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL6_REG,
+				     SP_VIDEO_PROCESS_EN | SP_UP_SAMPLE);
+			sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL5_REG, 0x00);
+		} else if (sp.frame.avi.colorspace == HDMI_COLORSPACE_RGB) {
+			dev_dbg(dev, "RGB4:4:4 ---> YCbCr4:2:2\n");
+			sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL6_REG,
+				     SP_VIDEO_PROCESS_EN | SP_UP_SAMPLE);
+			sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL5_REG,
+				     SP_CSC_STD_SEL | SP_RANGE_R2Y |
+				     SP_CSPACE_R2Y);
+		}
+		sp_reg_write(anx78xx, TX_P2, SP_VID_CTRL2_REG,
+			     (SP_IN_BPC_8BIT << SP_IN_BPC_SHIFT) | colorspace);
+	} else {
+		sp_reg_read(anx78xx, TX_P2, SP_VID_CTRL2_REG, &colorspace);
+		colorspace &= SP_IN_COLOR_F_MASK;
+
+		/*
+		 * To change the CSC_STD_SEL bit we need to set
+		 * CSPACE_Y2R and CSPACE_ R2Y, otherwise has no
+		 * effect or is undetermined.
+		 */
+		if (colorspace == HDMI_COLORSPACE_RGB) {
+			sp_reg_clear_bits(anx78xx, TX_P2, SP_VID_CTRL5_REG,
+					  SP_RANGE_Y2R | SP_CSPACE_Y2R |
+					  SP_CSC_STD_SEL);
+			sp_reg_clear_bits(anx78xx, TX_P2, SP_VID_CTRL6_REG,
+					  SP_VIDEO_PROCESS_EN | SP_UP_SAMPLE);
+		} else {
+			/*
+			 * Colorimetric format of input video is YCbCr422
+			 * or YCbCr444
+			 */
+			sp_reg_set_bits(anx78xx, TX_P2, SP_VID_CTRL5_REG,
+					SP_RANGE_Y2R | SP_CSPACE_Y2R);
+
+			sp_reg_read(anx78xx, RX_P1,
+				    SP_AVI_INFOFRAME_DATA_BASE + 3,
+				    &val);
+
+			if (sp_match_vic_for_bt709(val))
+				sp_reg_set_bits(anx78xx, TX_P2,
+						SP_VID_CTRL5_REG,
+						SP_CSC_STD_SEL);
+			else	/* Convert based on BT601 */
+				sp_reg_clear_bits(anx78xx, TX_P2,
+						  SP_VID_CTRL5_REG,
+						  SP_CSC_STD_SEL);
+			/*
+			 * Enable 4:2:2 to 4:4:4 up sample when is required
+			 * and enable video process function.
+			 */
+			if (colorspace == HDMI_COLORSPACE_YUV422)
+				sp_reg_set_bits(anx78xx, TX_P2,
+						SP_VID_CTRL6_REG,
+						SP_VIDEO_PROCESS_EN |
+						SP_UP_SAMPLE);
+			else	/* YCBCR444 */
+				sp_reg_update_bits(anx78xx, TX_P2,
+						   SP_VID_CTRL6_REG,
+						   SP_VIDEO_PROCESS_EN |
+						   SP_UP_SAMPLE,
+						   SP_VIDEO_PROCESS_EN);
+		}
+	}
+}
+
+static u8 sp_hdmi_infoframe_checksum(u8 *ptr, int size)
+{
+	u8 csum = 0;
+	int i;
+
+	/* compute checksum */
+	for (i = 0; i < size; i++)
+		csum += ptr[i];
+
+	return 256 - csum;
+}
+
+static void sp_audio_infoframe_init(struct anx78xx *anx78xx)
+{
+	int i;
+	u8 buffer[HDMI_INFOFRAME_SIZE(AUDIO)];
+	u8 *ptr = buffer;
+	struct device *dev = &anx78xx->client->dev;
+
+	ptr[0] = HDMI_INFOFRAME_TYPE_AUDIO;
+	ptr[1] = 1;
+	ptr[2] = HDMI_AUDIO_INFOFRAME_SIZE;
+
+	/* start infoframe paylload */
+	ptr += HDMI_INFOFRAME_HEADER_SIZE;
+	for (i = 0; i < HDMI_AUDIO_INFOFRAME_SIZE; i++)
+		sp_reg_read(anx78xx, RX_P1, SP_AUD_INFOFRAME_DATA_BASE + i,
+			    &ptr[i]);
+
+	ptr[3] = 0;
+	ptr[3] = sp_hdmi_infoframe_checksum(buffer,
+					    HDMI_INFOFRAME_SIZE(AUDIO));
+
+	if (hdmi_infoframe_unpack(&sp.frame, buffer) < 0) {
+		dev_err(dev, "unpack of AUDIO infoframe failed\n");
+		return;
+	}
+}
+
+static void sp_avi_infoframe_init(struct anx78xx *anx78xx)
+{
+	int i;
+	u8 buffer[HDMI_INFOFRAME_SIZE(AVI)];
+	u8 *ptr = buffer;
+	struct device *dev = &anx78xx->client->dev;
+
+	ptr[0] = HDMI_INFOFRAME_TYPE_AVI;
+	ptr[1] = 2;
+	ptr[2] = HDMI_AVI_INFOFRAME_SIZE;
+
+	/* start infoframe payload */
+	ptr += HDMI_INFOFRAME_HEADER_SIZE;
+	for (i = 0; i < HDMI_AVI_INFOFRAME_SIZE; i++)
+		sp_reg_read(anx78xx, RX_P1, SP_AVI_INFOFRAME_DATA_BASE + i,
+			    &ptr[i]);
+
+	ptr[3] = 0;	/* checksum */
+	ptr[3] = sp_hdmi_infoframe_checksum(buffer, HDMI_INFOFRAME_SIZE(AVI));
+
+	if (hdmi_infoframe_unpack(&sp.frame, buffer) < 0) {
+		dev_err(dev, "unpack of AVI infoframe failed\n");
+		return;
+	}
+}
+
+static void sp_mpeg_infoframe_init(struct anx78xx *anx78xx)
+{
+	int i;
+	u8 buffer[HDMI_INFOFRAME_SIZE(MPEG)];
+	u8 *ptr = buffer;
+	struct device *dev = &anx78xx->client->dev;
+
+	ptr[0] = HDMI_INFOFRAME_TYPE_MPEG;
+	ptr[1] = 1;
+	ptr[2] = HDMI_MPEG_INFOFRAME_SIZE;
+
+	/* start infoframe payload */
+	ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+	for (i = 0; i < HDMI_MPEG_INFOFRAME_SIZE; i++)
+		sp_reg_read(anx78xx, RX_P1, SP_MPEG_VS_INFOFRAME_DATA_BASE + i,
+			    &ptr[i]);
+
+	ptr[3] = 0;	/* checksum */
+	ptr[3] = sp_hdmi_infoframe_checksum(buffer, HDMI_INFOFRAME_SIZE(MPEG));
+
+	if (hdmi_infoframe_unpack(&sp.frame, buffer) < 0) {
+		dev_err(dev, "unpack of MPEG infoframe failed\n");
+		return;
+	}
+}
+
+static void sp_vsi_infoframe_init(struct anx78xx *anx78xx)
+{
+	int i;
+	u8 len, buffer[10];
+	u8 *ptr = buffer;
+	struct device *dev = &anx78xx->client->dev;
+
+	ptr[0] = HDMI_INFOFRAME_TYPE_MPEG;
+	ptr[1] = 1;
+
+	/* get infoframe length */
+	sp_reg_read(anx78xx, RX_P1, SP_MPEG_VS_INFOFRAME_LEN_REG,
+		    &len);
+	if (len > 10)
+		return;
+
+	ptr[2] = len;
+
+	/* start infoframe payload */
+	ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+	for (i = 0; i < len; i++)
+		sp_reg_read(anx78xx, RX_P1, SP_MPEG_VS_INFOFRAME_DATA_BASE + i,
+			    &ptr[i]);
+
+	ptr[3] = 0;	/* checksum */
+	ptr[3] = sp_hdmi_infoframe_checksum(buffer,
+					    HDMI_INFOFRAME_HEADER_SIZE + len);
+
+	if (hdmi_infoframe_unpack(&sp.frame, buffer) < 0) {
+		dev_err(dev, "unpack of VSI infoframe failed\n");
+		return;
+	}
+}
+
+static void sp_load_packet(struct anx78xx *anx78xx,
+			   enum hdmi_infoframe_type type)
+{
+	int i;
+	u8 buffer[32];
+	u8 *ptr = buffer;
+
+	memset(buffer, 0, sizeof(*buffer));
+	ptr += HDMI_INFOFRAME_HEADER_SIZE;
+
+	switch (type) {
+	case HDMI_INFOFRAME_TYPE_AUDIO:
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_AUD_TYPE_REG,
+			     sp.frame.audio.type);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_AUD_VER_REG,
+			     sp.frame.audio.version);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_AUD_LEN_REG,
+			     sp.frame.audio.length);
+		/* packs the audio information into a binary buffer */
+		hdmi_audio_infoframe_pack(&sp.frame.audio, buffer,
+					  HDMI_INFOFRAME_SIZE(AUDIO));
+		/* start infoframe payload */
+		for (i = 0; i < sp.frame.audio.length; i++)
+			sp_reg_write(anx78xx, TX_P2,
+				     SP_INFOFRAME_AUD_DB0_REG + i, ptr[i]);
+		break;
+	case HDMI_INFOFRAME_TYPE_AVI:
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_AVI_TYPE_REG,
+			     sp.frame.avi.type);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_AVI_VER_REG,
+			     sp.frame.avi.version);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_AVI_LEN_REG,
+			     sp.frame.avi.length);
+		/* packs the AVI information into a binary buffer */
+		hdmi_avi_infoframe_pack(&sp.frame.avi, buffer,
+					HDMI_INFOFRAME_SIZE(AVI));
+		/* start infoframe payload */
+		for (i = 0; i < sp.frame.avi.length; i++)
+			sp_reg_write(anx78xx, TX_P2,
+				     SP_INFOFRAME_AVI_DB0_REG + i, ptr[i]);
+		break;
+	case HDMI_INFOFRAME_TYPE_MPEG:
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_MPEG_TYPE_REG,
+			     sp.frame.mpeg.type);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_MPEG_VER_REG,
+			     sp.frame.mpeg.version);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_MPEG_LEN_REG,
+			     sp.frame.mpeg.length);
+		/* packs the MPEG information into a binary buffer */
+		hdmi_mpeg_infoframe_pack(&sp.frame.mpeg, buffer,
+					 HDMI_INFOFRAME_SIZE(MPEG));
+		/* start infoframe payload */
+		for (i = 0; i < sp.frame.mpeg.length; i++)
+			sp_reg_write(anx78xx, TX_P2,
+				     SP_INFOFRAME_MPEG_DB0_REG + i, ptr[i]);
+		break;
+	case HDMI_INFOFRAME_TYPE_VENDOR:
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_MPEG_TYPE_REG,
+			     sp.frame.vendor.hdmi.type);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_MPEG_VER_REG,
+			     sp.frame.vendor.hdmi.version);
+		sp_reg_write(anx78xx, TX_P2, SP_INFOFRAME_MPEG_LEN_REG,
+			     sp.frame.vendor.hdmi.length);
+		/* pack the VSI information into a binary buffer */
+		hdmi_vendor_infoframe_pack(&sp.frame.vendor.hdmi, buffer,
+					   HDMI_INFOFRAME_HEADER_SIZE +
+					   sp.frame.vendor.hdmi.length);
+		for (i = 0; i < sp.frame.vendor.hdmi.length; i++)
+			sp_reg_write(anx78xx, TX_P2,
+				     SP_INFOFRAME_MPEG_DB0_REG + i, ptr[i]);
+		break;
+	default:
+		break;
+	}
+}
+
+static void sp_config_packets(struct anx78xx *anx78xx,
+			      enum hdmi_infoframe_type type)
+{
+	switch (type) {
+	case HDMI_INFOFRAME_TYPE_AUDIO:
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				  SP_AUD_IF_EN);
+		sp_load_packet(anx78xx, HDMI_INFOFRAME_TYPE_AUDIO);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_AUD_IF_UP);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_AUD_IF_EN);
+		break;
+	case HDMI_INFOFRAME_TYPE_AVI:
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				  SP_AVI_IF_EN);
+		sp_load_packet(anx78xx, HDMI_INFOFRAME_TYPE_AVI);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_AVI_IF_UD);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_AVI_IF_EN);
+		break;
+	case HDMI_INFOFRAME_TYPE_MPEG:
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				  SP_MPEG_IF_EN);
+		sp_load_packet(anx78xx, HDMI_INFOFRAME_TYPE_MPEG);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_MPEG_IF_UD);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_MPEG_IF_EN);
+		break;
+	case HDMI_INFOFRAME_TYPE_VENDOR:
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				  SP_MPEG_IF_EN);
+		sp_load_packet(anx78xx, HDMI_INFOFRAME_TYPE_VENDOR);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_MPEG_IF_UD);
+		sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				SP_MPEG_IF_EN);
+		break;
+	default:
+		break;
+	}
+}
+
+static bool sp_config_video_output(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+
+	switch (sp.tx_vo_state) {
+	default:
+	case VO_WAIT_VIDEO_STABLE:
+		sp_reg_read(anx78xx, RX_P0, SP_SYSTEM_STATUS_REG, &val);
+		if ((val & SP_TMDS_DE_DET) && (val & SP_TMDS_CLOCK_DET)) {
+			if (sp_tx_bw_lc_sel(anx78xx))
+				return false;
+			sp_enable_video_input(anx78xx, false);
+			sp_hdmi_new_avi_int(anx78xx);
+			sp_reg_read(anx78xx, RX_P0,
+				    SP_PACKET_RECEIVING_STATUS_REG, &val);
+			if (val & SP_VSI_RCVD)
+				sp_hdmi_new_vsi_int(anx78xx);
+			sp_enable_video_input(anx78xx, true);
+			sp.tx_vo_state = VO_WAIT_TX_VIDEO_STABLE;
+		} else {
+			dev_dbg(dev, "HDMI input video not stable!\n");
+			break;
+		}
+	/* fallthrough */
+	case VO_WAIT_TX_VIDEO_STABLE:
+		/*
+		 * The flag is write clear and can be latched from last
+		 * status. So the first read and write is to clear the
+		 * previous status.
+		 */
+		sp_reg_read(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, &val);
+		sp_reg_write(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, val);
+
+		sp_reg_read(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, &val);
+		if (val & SP_CHA_STA) {
+			dev_dbg(dev, "stream clock not stable!\n");
+			break;
+		} else {
+			/*
+			 * The flag is write clear and can be latched from
+			 * last status. So the first read and write is to
+			 * clear the previous status.
+			 */
+			sp_reg_read(anx78xx, TX_P0,
+				    SP_DP_SYSTEM_CTRL_BASE + 3,
+				    &val);
+			sp_reg_write(anx78xx, TX_P0,
+				     SP_DP_SYSTEM_CTRL_BASE + 3,
+				     val);
+
+			sp_reg_read(anx78xx, TX_P0,
+				    SP_DP_SYSTEM_CTRL_BASE + 3,
+				    &val);
+			if (val & SP_STRM_VALID) {
+				if (sp.tx_test_lt)
+					sp.tx_test_lt = false;
+				sp.tx_vo_state = VO_FINISH;
+			} else {
+				dev_err(dev, "video stream not valid!\n");
+				break;
+			}
+		}
+	/* fallthrough */
+	case VO_FINISH:
+		sp_block_power_ctrl(anx78xx, SP_TX_PWR_AUDIO, false);
+		sp_hdmi_mute_video(anx78xx, false);
+		sp_video_mute(anx78xx, false);
+		sp_show_information(anx78xx);
+		return true;
+	}
+
+	return false;
+}
+
+static inline void sp_hdcp_encryption_disable(struct anx78xx *anx78xx)
+{
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_HDCP_CTRL0_REG, SP_HDCP_ENC_EN);
+}
+
+static inline void sp_hdcp_encryption_enable(struct anx78xx *anx78xx)
+{
+	sp_reg_set_bits(anx78xx, TX_P0, SP_HDCP_CTRL0_REG, SP_HDCP_ENC_EN);
+}
+
+static void sp_hw_hdcp_enable(struct anx78xx *anx78xx)
+{
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_HDCP_CTRL0_REG,
+			  SP_HDCP_ENC_EN | SP_HARD_AUTH_EN);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_HDCP_CTRL0_REG,
+			SP_HARD_AUTH_EN | SP_BKSV_SRM_PASS |
+			SP_KSVLIST_VLD | SP_HDCP_ENC_EN);
+
+	/*
+	 * Set the wait timing value for R0 checking of HDCP first step
+	 * authentication after write AKSV to receiver. Default value is
+	 * 0x64 (100ms).
+	 */
+	sp_reg_write(anx78xx, TX_P0, SP_HDCP_WAIT_R0_TIME_REG, 0xb0);
+
+	/*
+	 * Set the wait timing value for repeater KSVFIFO ready in HDCP first
+	 * step authentication. Default value is 0x9c (4.2s)
+	 */
+	sp_reg_write(anx78xx, TX_P0, SP_HDCP_RPTR_RDY_WAIT_TIME_REG, 0xc8);
+}
+
+static bool sp_hdcp_process(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+
+	switch (sp.hdcp_state) {
+	case HDCP_CAPABLE_CHECK:
+		sp.hdcp_fail_count = 0;
+		if (is_anx_dongle(anx78xx))
+			sp.hdcp_state = HDCP_WAITING_VID_STB;
+		else
+			sp.hdcp_state = HDCP_HW_ENABLE;
+		if (!sp.hdcp_enabled)
+			sp.hdcp_state = HDCP_NOT_SUPPORTED;
+		if (sp.hdcp_state != HDCP_WAITING_VID_STB)
+			break;
+	/* fallthrough */
+	case HDCP_WAITING_VID_STB:
+		msleep(100);
+		sp.hdcp_state = HDCP_HW_ENABLE;
+	/* fallthrough */
+	case HDCP_HW_ENABLE:
+		sp_video_mute(anx78xx, true);
+		sp_clean_hdcp_status(anx78xx);
+		sp_block_power_ctrl(anx78xx, SP_TX_PWR_HDCP, false);
+		msleep(20);
+		sp_block_power_ctrl(anx78xx, SP_TX_PWR_HDCP, true);
+		sp_reg_write(anx78xx, TX_P2, SP_COMMON_INT_MASK_BASE + 2,
+			     0x01);
+		msleep(50);
+		sp_hw_hdcp_enable(anx78xx);
+		sp.hdcp_state = HDCP_WAITING_FINISH;
+	/* fallthrough */
+	case HDCP_WAITING_FINISH:
+		break;
+	case HDCP_FINISH:
+		sp_hdcp_encryption_enable(anx78xx);
+		sp_hdmi_mute_video(anx78xx, false);
+		sp_video_mute(anx78xx, false);
+		sp.hdcp_state = HDCP_CAPABLE_CHECK;
+		dev_dbg(dev, "HDCP authentication pass\n");
+		return true;
+	case HDCP_FAILED:
+		if (sp.hdcp_fail_count > 5) {
+			sp_reg_hardware_reset(anx78xx);
+			sp.hdcp_state = HDCP_CAPABLE_CHECK;
+			sp.hdcp_fail_count = 0;
+			dev_dbg(dev, "HDCP authentication failed\n");
+		} else {
+			sp.hdcp_fail_count++;
+			sp.hdcp_state = HDCP_WAITING_VID_STB;
+		}
+		break;
+	default:
+	case HDCP_NOT_SUPPORTED:
+		dev_dbg(dev, "sink is not capable HDCP\n");
+		sp_block_power_ctrl(anx78xx, SP_TX_PWR_HDCP, false);
+		sp_video_mute(anx78xx, false);
+		sp.hdcp_state = HDCP_CAPABLE_CHECK;
+		return true;
+	}
+
+	return false;
+}
+
+static void sp_enable_audio_output(struct anx78xx *anx78xx, bool enable)
+{
+	u8 val;
+
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_AUDIO_CTRL_REG, SP_AUD_EN);
+	if (enable) {
+		sp_audio_infoframe_init(anx78xx);
+		sp_config_packets(anx78xx, HDMI_INFOFRAME_TYPE_AUDIO);
+
+		sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
+		if (val & SP_HDMI_AUD_LAYOUT) {
+			sp_reg_read(anx78xx, RX_P1, SP_AUD_INFOFRAME_DATA_BASE,
+				    &val);
+			sp_reg_write(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
+				     (val & 0x07) << 5 | SP_AUDIO_LAYOUT);
+		} else {
+			sp_reg_write(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
+				     SP_I2S_CH_NUM_2 & ~SP_AUDIO_LAYOUT);
+		}
+		sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUDIO_CTRL_REG,
+				SP_AUD_EN);
+	} else {
+		sp_reg_clear_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+				  SP_AUD_IF_EN);
+	}
+}
+
+static int sp_calculate_audio_m_value(struct anx78xx *anx78xx)
+{
+	u8 val;
+	struct device *dev = &anx78xx->client->dev;
+	unsigned long m_aud, ls_clk = 0;
+	unsigned long aud_freq = 0;
+
+	sp_reg_read(anx78xx, RX_P0, SP_AUD_SPDIF_CH_STATUS_BASE + 4, &val);
+
+	switch (val & SP_FS_FREQ_MASK) {
+	case SP_FS_FREQ_44100HZ:
+		aud_freq = 44100;
+		break;
+	case SP_FS_FREQ_48000HZ:
+		aud_freq = 48000;
+		break;
+	case SP_FS_FREQ_32000HZ:
+		aud_freq = 32000;
+		break;
+	case SP_FS_FREQ_88200HZ:
+		aud_freq = 88200;
+		break;
+	case SP_FS_FREQ_96000HZ:
+		aud_freq = 96000;
+		break;
+	case SP_FS_FREQ_176400HZ:
+		aud_freq = 176400;
+		break;
+	case SP_FS_FREQ_192000HZ:
+		aud_freq = 192000;
+		break;
+	default:
+		dev_err(dev, "invalid sampling clock frequency %d\n",
+			val & SP_FS_FREQ_MASK);
+		return -1;
+	}
+
+	switch (sp_get_link_bandwidth(anx78xx)) {
+	case DP_LINK_BW_1_62:
+		ls_clk = 162000;
+		break;
+	case DP_LINK_BW_2_7:
+		ls_clk = 270000;
+		break;
+	case DP_LINK_BW_5_4:
+		ls_clk = 540000;
+		break;
+	case DP_LINK_BW_6_75:
+		ls_clk = 675000;
+		break;
+	default:
+		dev_err(dev, "invalid main link bandwidth setting\n");
+		return -1;
+	}
+
+	dev_dbg(dev, "aud_freq = %ld , LS_CLK = %ld\n", aud_freq, ls_clk);
+
+	m_aud = (((512 * aud_freq) / ls_clk) * 32768) / 1000;
+	sp_reg_write(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL4_REG, m_aud & 0xff);
+	m_aud = m_aud >> 8;
+	sp_reg_write(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL5_REG, m_aud & 0xff);
+	sp_reg_write(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL6_REG, 0x00);
+
+	return 0;
+}
+
+static void sp_config_audio(struct anx78xx *anx78xx)
+{
+	int i;
+	u8 val;
+
+	sp_block_power_ctrl(anx78xx, SP_TX_PWR_AUDIO, true);
+
+	sp_reg_read(anx78xx, TX_P0, SP_DP_MAIN_LINK_BW_SET_REG, &val);
+	if (val & SP_INITIAL_SLIM_M_AUD_SEL)
+		if (sp_calculate_audio_m_value(anx78xx))
+			return;
+
+	sp_reg_clear_bits(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL0_REG,
+			  SP_AUD_INTERFACE_DISABLE);
+
+	sp_reg_set_bits(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL2_REG,
+			SP_M_AUD_ADJUST_ST);
+
+	sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
+	if (val & SP_HDMI_AUD_LAYOUT)
+		sp_reg_set_bits(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
+				SP_I2S_CH_NUM_8 | SP_AUDIO_LAYOUT);
+	else
+		sp_reg_clear_bits(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
+				  SP_I2S_CHANNEL_NUM_MASK | SP_AUDIO_LAYOUT);
+
+	/* transfer audio channel status from HDMI Rx to Slimport Tx */
+	for (i = 1; i <= SP_AUD_CH_STATUS_REG_NUM; i++) {
+		sp_reg_read(anx78xx, RX_P0, SP_AUD_SPDIF_CH_STATUS_BASE + i,
+			    &val);
+		sp_reg_write(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + i,
+			     val);
+	}
+
+	/* enable audio */
+	sp_enable_audio_output(anx78xx, true);
+}
+
+static bool sp_config_audio_output(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	switch (sp.tx_ao_state) {
+	default:
+	case AO_INIT:
+	case AO_CTS_RCV_INT:
+	case AO_AUDIO_RCV_INT:
+		sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
+		if (!val & SP_HDMI_MODE) {
+			sp.tx_ao_state = AO_INIT;
+			return true;
+		}
+		break;
+	case AO_RCV_INT_FINISH:
+		if (sp.audio_stable_count++ > 2) {
+			sp.tx_ao_state = AO_OUTPUT;
+		} else {
+			sp.tx_ao_state = AO_INIT;
+			break;
+		}
+	/* fallthrough */
+	case AO_OUTPUT:
+		sp.audio_stable_count = 0;
+		sp.tx_ao_state = AO_INIT;
+		sp_video_mute(anx78xx, false);
+		sp_hdmi_mute_audio(anx78xx, false);
+		sp_config_audio(anx78xx);
+		return true;
+	}
+
+	return false;
+}
+
+static void sp_initialization(struct anx78xx *anx78xx)
+{
+	sp.read_edid_flag = false;
+
+	/* Power on all modules */
+	sp_reg_write(anx78xx, TX_P2, SP_POWERDOWN_CTRL_REG, 0x00);
+	/* Driver Version */
+	sp_reg_write(anx78xx, TX_P1, SP_FW_VER_REG, FW_VERSION);
+	sp_hdmi_initialization(anx78xx);
+	sp_tx_initialization(anx78xx);
+	msleep(200);
+}
+
+/*
+ * Interrupt receiver function, gets the service interrupts and updates the
+ * status of the interrupts so that correct interrupt service routines can
+ * be called in the SlimPort task handler function.
+ */
+static void sp_int_receiver(struct anx78xx *anx78xx)
+{
+	int i;
+
+	/* Common Interrupt Status Registers */
+	for (i = 0; i < ARRAY_SIZE(sp.common_int); i++) {
+		sp_reg_read(anx78xx, TX_P2, SP_COMMON_INT_STATUS_BASE + 1 + i,
+			    &sp.common_int[i]);
+		sp_reg_write(anx78xx, TX_P2, SP_COMMON_INT_STATUS_BASE + 1 + i,
+			     sp.common_int[i]);
+	}
+
+	/* Display Port Interrupt Status Register */
+	sp_reg_read(anx78xx, TX_P2, SP_DP_INT_STATUS_REG, &sp.dp_int);
+	sp_reg_write(anx78xx, TX_P2, SP_DP_INT_STATUS_REG, sp.dp_int);
+
+	/* Interrupt Status Registers */
+	for (i = 0; i < ARRAY_SIZE(sp.sp_hdmi_int); i++) {
+		sp_reg_read(anx78xx, RX_P0, SP_INT_STATUS1_REG + i,
+			    &sp.sp_hdmi_int[i]);
+		sp_reg_write(anx78xx, RX_P0, SP_INT_STATUS1_REG + i,
+			     sp.sp_hdmi_int[i]);
+	}
+}
+
+static void sp_pll_changed_int_handler(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+
+	if (sp.tx_system_state >= STATE_LINK_TRAINING) {
+		if (!sp_get_pll_lock_status(anx78xx)) {
+			dev_dbg(dev, "PLL not lock!\n");
+			sp_set_system_state(anx78xx, STATE_LINK_TRAINING);
+		}
+	}
+}
+
+static u8 sp_dp_autotest_link_training(struct anx78xx *anx78xx)
+{
+	u8 bandwidth, response;
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_LINK_RATE, 1,
+				   &bandwidth);
+	switch (bandwidth) {
+	case DP_LINK_BW_1_62:
+	case DP_LINK_BW_2_7:
+	case DP_LINK_BW_5_4:
+	case DP_LINK_BW_6_75:
+		sp_set_link_bandwidth(anx78xx, bandwidth);
+		sp.tx_test_bw = bandwidth;
+		break;
+	default:
+		sp_set_link_bandwidth(anx78xx, DP_LINK_BW_6_75);
+		sp.tx_test_bw = DP_LINK_BW_6_75;
+		break;
+	}
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_RESPONSE, 1, &response);
+	response |= DP_TEST_ACK;
+
+	return response;
+}
+
+static u8 sp_autotest_phy_pattern(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 b_sw, response;
+	u8 buf[16];
+	int i;
+
+	sp_dp_autotest_link_training(anx78xx);
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_PHY_TEST_PATTERN, 1, buf);
+	dev_dbg(dev, "DPCD: PHY test pattern = %.2x\n", buf[0]);
+	switch (buf[0]) {
+	case 0:
+		break;
+	case 1:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_TRAINING_PATTERN_SET_REG,
+			     0x04);
+		break;
+	case 2:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_TRAINING_PATTERN_SET_REG,
+			     0x08);
+		break;
+	case 3:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_TRAINING_PATTERN_SET_REG,
+			     0x0c);
+		break;
+	case 4:
+		/* 00249h - 0025Fh: Reserved for test automation extensions */
+		sp_dp_read_bytes_from_dpcd(anx78xx, 0x250, 10,
+					   buf);
+		for (i = 0; i < SP_DP_LT_80BIT_PATTERN_REG_NUM; i++) {
+			sp_reg_write(anx78xx, TX_P1,
+				     SP_DP_LT_80BIT_PATTERN0_REG + i,
+				     buf[0]);
+		}
+		sp_reg_write(anx78xx, TX_P0, SP_DP_TRAINING_PATTERN_SET_REG,
+			     0x30);
+		break;
+	case 5:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_CEP_TRAINING_CTRL0_REG,
+			     0x00);
+		sp_reg_write(anx78xx, TX_P0, SP_DP_CEP_TRAINING_CTRL1_REG,
+			     0x01);
+		sp_reg_write(anx78xx, TX_P0, SP_DP_TRAINING_PATTERN_SET_REG,
+			     0x14);
+		break;
+	}
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_MAX_DOWNSPREAD, 1, buf);
+	dev_dbg(dev, "DPCD: max downspread = %.2x\n", buf[0]);
+	if (buf[0] & DP_PERCENT_DOWNSPREAD_0_5)
+		sp_config_ssc(anx78xx, SSC_DEP_4000PPM);
+	else
+		sp_downspreading_enable(anx78xx, false);
+
+	/* get swing and emphasis adjust request */
+	sp_reg_read(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG, &b_sw);
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_ADJUST_REQUEST_LANE0_1, 1, buf);
+	dev_dbg(dev, "DPCD: 0x00206 = %.2x\n", buf[0]);
+	buf[0] &= DP_ADJUST_VOLTAGE_SWING_LANE0_MASK |
+		  DP_ADJUST_PRE_EMPHASIS_LANE0_MASK;
+	switch (buf[0]) {
+	case 0x00:
+	case 0x01:
+	case 0x02:
+	case 0x03:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG,
+			     (b_sw & ~SP_TX_SW_SET_MASK) | buf[0]);
+		break;
+	case 0x04:
+	case 0x05:
+	case 0x06:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG,
+			     (b_sw & ~SP_TX_SW_SET_MASK) |
+			     (buf[0] + 4));
+		break;
+	case 0x08:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG,
+			     (b_sw & ~SP_TX_SW_SET_MASK) | 0x10);
+		break;
+	case 0x09:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG,
+			     (b_sw & ~SP_TX_SW_SET_MASK) | 0x11);
+		break;
+	case 0x0c:
+		sp_reg_write(anx78xx, TX_P0, SP_DP_LANE0_LT_CTRL_REG,
+			     (b_sw & ~SP_TX_SW_SET_MASK) | 0x18);
+		break;
+	default:
+		break;
+	}
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_RESPONSE, 1, &response);
+	response |= DP_TEST_ACK;
+
+	return response;
+}
+
+static void sp_hpd_irq_process(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+	u8 test_vector;
+	u8 buf[6];
+
+	sp_dp_read_bytes_from_dpcd(anx78xx, DP_SINK_COUNT, 6, buf);
+	dev_dbg(dev, "got hpd irq %x\n", buf[1]);
+
+	if (buf[1] != 0)
+		sp_dp_write_bytes_to_dpcd(anx78xx, DP_DEVICE_SERVICE_IRQ_VECTOR,
+					  1, &buf[1]);
+
+	/* HDCP IRQ */
+	if ((buf[1] & DP_CP_IRQ) &&
+	    (sp.hdcp_state > HDCP_WAITING_FINISH ||
+	     sp.tx_system_state >= STATE_HDCP_AUTH)) {
+		sp_dp_read_bytes_from_dpcd(anx78xx, 0x068029, 1,
+					   &val);
+		if (val & 0x04) {
+			if (!sp_hdcp_repeater_mode(anx78xx)) {
+				sp_set_system_state(anx78xx, STATE_HDCP_AUTH);
+				sp_clean_hdcp_status(anx78xx);
+			} else {
+				sp.repeater_state = HDCP_ERROR;
+			}
+			dev_dbg(dev, "CP_IRQ, HDCP sync lost.\n");
+		}
+	}
+
+	/* PHY and Link CTS test */
+	if (buf[1] & DP_AUTOMATED_TEST_REQUEST) {
+		sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_REQUEST, 1,
+					   &test_vector);
+
+		if (test_vector & DP_TEST_LINK_TRAINING) {
+			dev_dbg(dev, "LINK_TRAINING test requested\n");
+			sp.tx_test_lt = true;
+			val = sp_dp_autotest_link_training(anx78xx);
+			sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_RESPONSE, 1,
+						  &val);
+
+			if (sp.tx_system_state >= STATE_LINK_TRAINING) {
+				sp.tx_lt_state = LT_INIT;
+				sp_set_system_state(anx78xx,
+						    STATE_LINK_TRAINING);
+			}
+		}
+
+		if (test_vector & DP_TEST_LINK_VIDEO_PATTERN) {
+			dev_dbg(dev, "VIDEO_PATTERN test requested\n");
+			sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_RESPONSE, 1,
+						   &val);
+			val |= DP_TEST_ACK;
+			sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_RESPONSE, 1,
+						  &val);
+		}
+
+		if (test_vector & DP_TEST_LINK_EDID_READ) {
+			dev_dbg(dev, "EDID test requested\n");
+			if (sp.tx_system_state > STATE_PARSE_EDID)
+				sp_set_system_state(anx78xx, STATE_PARSE_EDID);
+			sp.tx_test_edid = true;
+		}
+
+		if (test_vector & DP_TEST_LINK_PHY_TEST_PATTERN) {
+			dev_dbg(dev, "PHY_PATTERN test requested\n");
+			sp.tx_test_lt = true;
+			val = sp_autotest_phy_pattern(anx78xx);
+			sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_RESPONSE, 1,
+						  &val);
+		}
+	}
+
+	if (sp.tx_system_state > STATE_LINK_TRAINING) {
+		if ((sp.tx_system_state == STATE_HDCP_AUTH) &&
+		    (buf[1] & DP_CP_IRQ)) {
+			dev_dbg(dev, "CP IRQ!\n");
+		} else if (!(buf[4] & DP_INTERLANE_ALIGN_DONE) || ((buf[2] &
+			  (DP_LANE_CR_DONE | DP_LANE_SYMBOL_LOCKED)) !=
+			  (DP_LANE_CR_DONE | DP_LANE_SYMBOL_LOCKED))) {
+			sp_set_system_state(anx78xx, STATE_LINK_TRAINING);
+			dev_dbg(dev, "IRQ: re-LT request!\n");
+			return;
+		}
+
+		dev_dbg(dev, "lane align %x\n", buf[4]);
+		dev_dbg(dev, "lane clock recovery %x\n", buf[2]);
+	}
+}
+
+static void sp_auth_done_int_handler(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 buf[2];
+
+	if (sp_hdcp_repeater_mode(anx78xx)) {
+		sp_reg_read(anx78xx, TX_P0, SP_TX_HDCP_STATUS_REG, &buf[0]);
+		if ((buf[0] & SP_AUTHEN_PASS) &&
+		    (sp.repeater_state == HDCP_DOING))
+			sp.repeater_state = HDCP_DONE;
+		else
+			sp.repeater_state = HDCP_ERROR;
+
+		return;
+	}
+
+	if (sp.hdcp_state > HDCP_HW_ENABLE &&
+	    sp.tx_system_state == STATE_HDCP_AUTH) {
+		sp_reg_read(anx78xx, TX_P0, SP_HDCP_RX_BSTATUS0_REG, &buf[0]);
+		sp_reg_read(anx78xx, TX_P0, SP_HDCP_RX_BSTATUS1_REG, &buf[1]);
+		if ((buf[0] & 0x08) || (buf[1] & 0x80)) {
+			dev_dbg(dev, "max cascade/devs exceeded!\n");
+			sp_hdcp_encryption_disable(anx78xx);
+			sp.hdcp_state = HDCP_FINISH;
+		} else {
+			sp_reg_read(anx78xx, TX_P0, SP_TX_HDCP_STATUS_REG,
+				    buf);
+		}
+
+		if (buf[0] & SP_AUTHEN_PASS) {
+			sp_dp_read_bytes_from_dpcd(anx78xx, 0x06802a, 2, buf);
+			if ((buf[0] & 0x08) || (buf[1] & 0x80)) {
+				dev_dbg(dev, "max cascade/devs exceeded!\n");
+				sp_hdcp_encryption_disable(anx78xx);
+			} else
+				dev_dbg(dev, "%s\n",
+					"authentication pass in Auth Done");
+
+			sp.hdcp_state = HDCP_FINISH;
+		} else {
+			dev_err(dev, "authentication failed in Auth Done\n");
+			sp_video_mute(anx78xx, true);
+			sp_clean_hdcp_status(anx78xx);
+			sp.hdcp_state = HDCP_FAILED;
+		}
+	}
+}
+
+static void sp_lt_done_int_handler(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+
+	if (sp.tx_lt_state == LT_WAITING_FINISH &&
+	    sp.tx_system_state == STATE_LINK_TRAINING) {
+		sp_reg_read(anx78xx, TX_P0, SP_DP_LT_CTRL_REG, &val);
+		if (val & SP_LT_ERROR_TYPE_MASK) {
+			val = (val & SP_LT_ERROR_TYPE_MASK) >> 4;
+			dev_dbg(dev, "LT failed in interrupt %.2x\n",
+				val);
+			sp.tx_lt_state = LT_ERROR;
+		} else {
+			dev_dbg(dev, "LT finish\n");
+			sp.tx_lt_state = LT_FINISH;
+		}
+	}
+}
+
+static void sp_hdmi_clk_det_int(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+
+	dev_dbg(dev, "pixel clock change\n");
+	if (sp.tx_system_state > STATE_VIDEO_OUTPUT) {
+		sp_video_mute(anx78xx, true);
+		sp_enable_audio_output(anx78xx, false);
+		sp_set_system_state(anx78xx, STATE_VIDEO_OUTPUT);
+	}
+}
+
+static void sp_hdmi_dvi_int(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+
+	sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
+	if ((val & SP_HDMI_DET) == SP_DVI_MODE) {
+		dev_dbg(dev, "detected DVI MODE -> mute audio\n");
+		sp_hdmi_mute_audio(anx78xx, true);
+		sp_set_system_state(anx78xx, STATE_LINK_TRAINING);
+	}
+}
+
+static void sp_hdmi_new_avi_int(struct anx78xx *anx78xx)
+{
+	sp_avi_infoframe_init(anx78xx);
+	sp_config_packets(anx78xx, HDMI_INFOFRAME_TYPE_AVI);
+	sp_set_colorspace(anx78xx);
+	sp_lvttl_bit_mapping(anx78xx);
+}
+
+static void sp_hdmi_new_vsi_int(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 v3d_struct;
+
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_DP_3D_VSC_CTRL_REG,
+			  SP_INFO_FRAME_VSC_EN);
+
+	sp_vsi_infoframe_init(anx78xx);
+	sp_config_packets(anx78xx, HDMI_INFOFRAME_TYPE_VENDOR);
+
+	switch (sp.frame.vendor.hdmi.s3d_struct) {
+	case HDMI_3D_STRUCTURE_FRAME_PACKING:
+		v3d_struct = 0x02;
+		break;
+	case HDMI_3D_STRUCTURE_LINE_ALTERNATIVE:
+		v3d_struct = 0x03;
+		break;
+	case HDMI_3D_STRUCTURE_SIDE_BY_SIDE_FULL:
+		v3d_struct = 0x04;
+		break;
+	case HDMI_3D_STRUCTURE_INVALID:
+	default:
+		v3d_struct = 0x00;
+		dev_dbg(dev, "3D structure not supported\n");
+		break;
+	}
+	sp_reg_write(anx78xx, TX_P0, SP_DP_VSC_DB1_REG, v3d_struct);
+
+	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_3D_VSC_CTRL_REG,
+			SP_INFO_FRAME_VSC_EN);
+	sp_reg_clear_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG,
+			  SP_SPD_IF_EN);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG, SP_SPD_IF_UD);
+	sp_reg_set_bits(anx78xx, TX_P0, SP_PACKET_SEND_CTRL_REG, SP_SPD_IF_EN);
+}
+
+static void sp_hdmi_no_vsi_int(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val;
+
+	sp_reg_read(anx78xx, TX_P0, SP_DP_3D_VSC_CTRL_REG, &val);
+	if (val & SP_INFO_FRAME_VSC_EN) {
+		dev_dbg(dev, "no new VSI is received, disable VSC packet\n");
+		val &= ~SP_INFO_FRAME_VSC_EN;
+		sp_reg_write(anx78xx, TX_P0, SP_DP_3D_VSC_CTRL_REG, val);
+		sp_mpeg_infoframe_init(anx78xx);
+		sp_config_packets(anx78xx, HDMI_INFOFRAME_TYPE_MPEG);
+	}
+}
+
+static inline void sp_hdmi_restart_audio_chk(struct anx78xx *anx78xx)
+{
+	sp_set_system_state(anx78xx, STATE_AUDIO_OUTPUT);
+}
+
+static void sp_hdmi_cts_rcv_int(struct anx78xx *anx78xx)
+{
+	if (sp.tx_ao_state == AO_INIT)
+		sp.tx_ao_state = AO_CTS_RCV_INT;
+	else if (sp.tx_ao_state == AO_AUDIO_RCV_INT)
+		sp.tx_ao_state = AO_RCV_INT_FINISH;
+}
+
+static void sp_hdmi_audio_rcv_int(struct anx78xx *anx78xx)
+{
+	if (sp.tx_ao_state == AO_INIT)
+		sp.tx_ao_state = AO_AUDIO_RCV_INT;
+	else if (sp.tx_ao_state == AO_CTS_RCV_INT)
+		sp.tx_ao_state = AO_RCV_INT_FINISH;
+}
+
+static void sp_hdmi_audio_samplechg_int(struct anx78xx *anx78xx)
+{
+	u16 i;
+	u8 val;
+
+	/* transfer audio channel status from HDMI Rx to Slimport Tx */
+	for (i = 0; i < SP_AUD_CH_STATUS_REG_NUM; i++) {
+		sp_reg_read(anx78xx, RX_P0, SP_AUD_SPDIF_CH_STATUS_BASE + i,
+			    &val);
+		sp_reg_write(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + i,
+			     val);
+	}
+}
+
+static void sp_hdmi_hdcp_error_int(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+
+	if (sp.hdcp_error_count >= 40) {
+		sp.hdcp_error_count = 0;
+		dev_dbg(dev, "lots of HDCP errors occurred!\n");
+		sp_hdmi_mute_audio(anx78xx, true);
+		sp_hdmi_mute_video(anx78xx, true);
+		sp_hdmi_set_hpd(anx78xx, false);
+		usleep_range(10000, 11000);
+		sp_hdmi_set_hpd(anx78xx, true);
+	} else {
+		sp.hdcp_error_count++;
+	}
+}
+
+static void sp_hdmi_new_gcp_int(struct anx78xx *anx78xx)
+{
+	u8 val;
+
+	sp_reg_read(anx78xx, RX_P1, SP_GENERAL_CTRL_PACKET_REG, &val);
+	if (val & SP_SET_AVMUTE) {
+		sp_hdmi_mute_video(anx78xx, true);
+		sp_hdmi_mute_audio(anx78xx, true);
+	} else if (val & SP_CLEAR_AVMUTE) {
+		sp_hdmi_mute_video(anx78xx, false);
+		sp_hdmi_mute_audio(anx78xx, false);
+	}
+}
+
+static void sp_hpd_int_handler(struct anx78xx *anx78xx, u8 hpd_source)
+{
+	u8 val;
+	struct device *dev = &anx78xx->client->dev;
+
+	switch (hpd_source) {
+	case SP_HPD_LOST:
+		dev_dbg(dev, "HPD: cable is pulled out\n");
+		sp_hdmi_set_hpd(anx78xx, false);
+		sp_set_system_state(anx78xx, STATE_CABLE_UNPLUGGED);
+		break;
+	case SP_HPD_CHG:
+		dev_dbg(dev, "HPD: hotplug change detected\n");
+		usleep_range(2000, 4000);
+		if (sp.common_int[3] & SP_HPD_IRQ)
+			sp_hpd_irq_process(anx78xx);
+
+		sp_reg_read(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 3, &val);
+		if (val & SP_HPD_STATUS) {
+			if (sp.common_int[3] & SP_HPD_IRQ)
+				sp_hpd_irq_process(anx78xx);
+		} else {
+			sp_reg_read(anx78xx, TX_P0,
+				    SP_DP_SYSTEM_CTRL_BASE + 3,
+				    &val);
+			if (val & SP_HPD_STATUS) {
+				sp_hdmi_set_hpd(anx78xx, false);
+				sp_set_system_state(anx78xx,
+						    STATE_WAITING_CABLE_PLUG);
+			}
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static void sp_system_isr_handler(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+
+	if (sp.common_int[3] & SP_HPD_LOST)
+		sp_hpd_int_handler(anx78xx, SP_HPD_LOST);
+	else if (sp.common_int[3] & SP_HPD_CHG)
+		sp_hpd_int_handler(anx78xx, SP_HPD_CHG);
+
+	if (sp.common_int[0] & SP_PLL_LOCK_CHG)
+		sp_pll_changed_int_handler(anx78xx);
+
+	if (sp.common_int[1] & SP_HDCP_AUTH_DONE)
+		sp_auth_done_int_handler(anx78xx);
+
+	if ((sp.common_int[2] & SP_HDCP_LINK_CHECK_FAIL) &&
+	    !sp_hdcp_repeater_mode(anx78xx)) {
+		sp_set_system_state(anx78xx, STATE_LINK_TRAINING);
+		dev_dbg(dev, "HDCP Sync Lost!\n");
+	}
+
+	if (sp.dp_int & SP_TRAINING_FINISH)
+		sp_lt_done_int_handler(anx78xx);
+
+	if (sp.tx_system_state > STATE_SINK_CONNECTION) {
+		if (sp.sp_hdmi_int[5] & SP_NEW_AVI_PKT)
+			sp_hdmi_new_avi_int(anx78xx);
+	}
+
+	if (sp.tx_system_state > STATE_VIDEO_OUTPUT) {
+		if (sp.sp_hdmi_int[6] & SP_NEW_VS) {
+			sp.sp_hdmi_int[6] &= ~SP_NO_VSI;
+			sp_hdmi_new_vsi_int(anx78xx);
+		}
+		if (sp.sp_hdmi_int[6] & SP_NO_VSI)
+			sp_hdmi_no_vsi_int(anx78xx);
+	}
+
+	if (sp.tx_system_state >= STATE_VIDEO_OUTPUT) {
+		if (sp.sp_hdmi_int[0] & SP_CKDT_CHG)
+			sp_hdmi_clk_det_int(anx78xx);
+
+		if (sp.sp_hdmi_int[0] & SP_SCDT_CHG)
+			dev_dbg(dev, "HDCP Sync Detected\n");
+
+		if (sp.sp_hdmi_int[0] & SP_HDMI_DVI)
+			sp_hdmi_dvi_int(anx78xx);
+
+		if ((sp.sp_hdmi_int[5] & SP_NEW_AUD_PKT) ||
+		    (sp.sp_hdmi_int[2] & SP_AUD_MODE_CHG))
+			sp_hdmi_restart_audio_chk(anx78xx);
+
+		if (sp.sp_hdmi_int[5] & SP_CTS_RCV)
+			sp_hdmi_cts_rcv_int(anx78xx);
+
+		if (sp.sp_hdmi_int[4] & SP_AUDIO_RCV)
+			sp_hdmi_audio_rcv_int(anx78xx);
+
+		if (sp.sp_hdmi_int[1] & SP_HDCP_ERR)
+			sp_hdmi_hdcp_error_int(anx78xx);
+
+		if (sp.sp_hdmi_int[5] & SP_NEW_CP_PKT)
+			sp_hdmi_new_gcp_int(anx78xx);
+
+		if (sp.sp_hdmi_int[1] & SP_AUDIO_SAMPLE_CHG)
+			sp_hdmi_audio_samplechg_int(anx78xx);
+	}
+}
+
+static void sp_show_information(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u8 val, val1;
+	u16 h_res, h_act, v_res, v_act;
+	u16 h_fp, h_sw, h_bp, v_fp, v_sw, v_bp;
+	unsigned long refresh;
+	unsigned long pclk;
+
+	dev_dbg(dev, "\n************* SP Video Information **************\n");
+
+	switch (sp_get_link_bandwidth(anx78xx)) {
+	case DP_LINK_BW_1_62:
+		dev_dbg(dev, "BW = 1.62G\n");
+		break;
+	case DP_LINK_BW_2_7:
+		dev_dbg(dev, "BW = 2.7G\n");
+		break;
+	case DP_LINK_BW_5_4:
+		dev_dbg(dev, "BW = 5.4G\n");
+		break;
+	case DP_LINK_BW_6_75:
+		dev_dbg(dev, "BW = 6.75G\n");
+		break;
+	default:
+		break;
+	}
+
+	pclk = sp_pclk_calc(anx78xx);
+	pclk = pclk / 10;
+
+	sp_reg_read(anx78xx, TX_P2, SP_TOTAL_LINE_STAL_REG, &val);
+	sp_reg_read(anx78xx, TX_P2, SP_TOTAL_LINE_STAH_REG, &val1);
+
+	v_res = val1;
+	v_res = v_res << 8;
+	v_res = v_res + val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_ACT_LINE_STAL_REG, &val);
+	sp_reg_read(anx78xx, TX_P2, SP_ACT_LINE_STAH_REG, &val1);
+
+	v_act = val1;
+	v_act = v_act << 8;
+	v_act = v_act + val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_TOTAL_PIXEL_STAL_REG, &val);
+	sp_reg_read(anx78xx, TX_P2, SP_TOTAL_PIXEL_STAH_REG, &val1);
+
+	h_res = val1;
+	h_res = h_res << 8;
+	h_res = h_res + val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_ACT_PIXEL_STAL_REG, &val);
+	sp_reg_read(anx78xx, TX_P2, SP_ACT_PIXEL_STAH_REG, &val1);
+
+	h_act = val1;
+	h_act = h_act << 8;
+	h_act = h_act + val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_H_F_PORCH_STAL_REG, &val);
+	sp_reg_read(anx78xx, TX_P2, SP_H_F_PORCH_STAH_REG, &val1);
+
+	h_fp = val1;
+	h_fp = h_fp << 8;
+	h_fp = h_fp + val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_H_SYNC_STAL_REG, &val);
+	sp_reg_read(anx78xx, TX_P2, SP_H_SYNC_STAH_REG, &val1);
+
+	h_sw = val1;
+	h_sw = h_sw << 8;
+	h_sw = h_sw + val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_H_B_PORCH_STAL_REG, &val);
+	sp_reg_read(anx78xx, TX_P2, SP_H_B_PORCH_STAH_REG, &val1);
+
+	h_bp = val1;
+	h_bp = h_bp << 8;
+	h_bp = h_bp + val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_V_F_PORCH_STA_REG, &val);
+	v_fp = val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_V_SYNC_STA_REG, &val);
+	v_sw = val;
+
+	sp_reg_read(anx78xx, TX_P2, SP_V_B_PORCH_STA_REG, &val);
+	v_bp = val;
+
+	dev_dbg(dev, "total resolution is %d * %d\n", h_res, v_res);
+
+	dev_dbg(dev, "HF=%d, HSW=%d, HBP=%d\n", h_fp, h_sw, h_bp);
+	dev_dbg(dev, "VF=%d, VSW=%d, VBP=%d\n", v_fp, v_sw, v_bp);
+
+	if (h_res == 0 || v_res == 0) {
+		refresh = 0;
+	} else {
+		refresh = pclk * 1000000;
+		refresh = DIV_ROUND_CLOSEST(refresh, h_res);
+		refresh = DIV_ROUND_CLOSEST(refresh, v_res);
+	}
+
+	dev_dbg(dev, "active resolution is %d * %d @ %ldHz\n", h_act, v_act,
+		refresh);
+
+	sp_reg_read(anx78xx, TX_P0, SP_DP_VIDEO_CTRL_REG, &val);
+
+	val &= SP_COLOR_F_MASK;
+	val >>= SP_COLOR_F_SHIFT;
+	if (val == HDMI_COLORSPACE_RGB)
+		dev_dbg(dev, "colorspace: RGB");
+	else if (val == HDMI_COLORSPACE_YUV422)
+		dev_dbg(dev, "colorspace: YCbCr422");
+	else if (val == HDMI_COLORSPACE_YUV444)
+		dev_dbg(dev, "colorspace: YCbCr444");
+
+	sp_reg_read(anx78xx, TX_P0, SP_DP_VIDEO_CTRL_REG, &val);
+
+	val &= SP_BPC_MASK;
+	val >>= SP_BPC_SHIFT;
+	if (val  == SP_BPC_6BITS)
+		dev_dbg(dev, "6 BPC\n");
+	else if (val == SP_BPC_8BITS)
+		dev_dbg(dev, "8 BPC\n");
+	else if (val == SP_BPC_10BITS)
+		dev_dbg(dev, "10 BPC\n");
+	else if (val == SP_BPC_12BITS)
+		dev_dbg(dev, "12 BPC\n");
+
+	if (is_anx_dongle(anx78xx)) {
+		/*
+		 * 00503h - 005FFh: RESERVED for Branch Device vendor-specific
+		 * usage
+		 */
+		sp_dp_read_bytes_from_dpcd(anx78xx, 0x0523, 1, &val);
+		dev_dbg(dev, "Analogix Dongle FW Ver %.2x\n", val & 0x7f);
+	}
+
+	dev_dbg(dev, "\n**************************************************\n");
+}
+
+static void sp_hdcp_repeater_reauth(struct anx78xx *anx78xx)
+{
+	u8 val, ctrl, status;
+	struct device *dev = &anx78xx->client->dev;
+
+	msleep(50);
+	sp_reg_read(anx78xx, RX_P1, SP_RX_HDCP_STATUS_REG, &val);
+
+	if (val & SP_AUTH_EN) {
+		sp_reg_read(anx78xx, TX_P0, SP_HDCP_CTRL0_REG, &ctrl);
+		if (ctrl & SP_HARD_AUTH_EN) {
+			sp_reg_read(anx78xx, TX_P0, SP_TX_HDCP_STATUS_REG,
+				    &status);
+			if (!(status & SP_AUTHEN_PASS) &&
+			    (status & SP_AUTH_FAIL)) {
+				dev_dbg(dev, "clean HDCP and re-auth\n");
+				sp.repeater_state = HDCP_ERROR;
+			}
+		} else {
+			dev_dbg(dev, "repeater mode, enable HW HDCP\n");
+			sp.repeater_state = HDCP_ERROR;
+		}
+	}
+
+	sp_reg_read(anx78xx, TX_P0, SP_HDCP_CTRL0_REG, &ctrl);
+	sp_reg_read(anx78xx, TX_P0, SP_TX_HDCP_STATUS_REG, &status);
+
+	if ((ctrl == SP_HDCP_FUNCTION_ENABLED) && (status & SP_AUTH_FAIL)) {
+		dev_dbg(dev, "HDCP encryption failure 0x%02x\n", status);
+		sp.repeater_state = HDCP_ERROR;
+	}
+
+	if (sp.repeater_state == HDCP_ERROR) {
+		sp_clean_hdcp_status(anx78xx);
+		msleep(50);
+		/* Clear HDCP AUTH interrupt */
+		sp_reg_set_bits(anx78xx, TX_P2, SP_COMMON_INT_STATUS_BASE + 2,
+				SP_HDCP_AUTH_DONE);
+		sp_hw_hdcp_enable(anx78xx);
+		sp.repeater_state = HDCP_DOING;
+	}
+}
+
+/**
+ * sp_main_process(): SlimPort Main Process
+ *
+ * SlimPort Main Process States:
+ * 0. Cable pulled out.
+ *    - Power down device.
+ * 1. SlimPort plug
+ *    - Power on device
+ * 2. SlimPort initialization
+ *    - Enable the power supply for downstream
+ *    - Power on the register access
+ *    - Initialize the related registers
+ * 3. Sink connection
+ *     - Get the cable type (HDMI, VGA or MyDP)
+ *     - Check the connection with downstream
+ * 4. Read EDID
+ *    - Read partial EDID data to decide whether to re-read entire EDID
+ *    - EDID read
+ *    - Parse EDID to get the video bandwidth
+ * 5. Link training
+ *    - Check the downstream bandwidth
+ *    - Hardware link training
+ * 6. Video output
+ *    - Verify that input video is stable
+ *    - Order by the input video to calculate the bandwidth
+ *    - Set AVI packet, bit-mapping, color depth, etc.
+ * 7. HDCP authentication
+ *    - Verify that HDCP is supported
+ *    - Enable hardware HDCP
+ * 8. Audio output
+ *    - Automatic audio M valu adjustment
+ *    - Configure audio multichannel
+ *    - Set audio packet
+ * 9. Playback
+ *    - The normal system working state
+ *
+ */
+bool sp_main_process(struct anx78xx *anx78xx)
+{
+	/*
+	 * Process the interrupts
+	 */
+	if (sp.tx_system_state > STATE_WAITING_CABLE_PLUG) {
+		/* Interrupt receiver */
+		sp_int_receiver(anx78xx);
+
+		/* Task handler */
+		sp_system_isr_handler(anx78xx);
+
+		/* If device; supports HDCP repeater function re-auth */
+		if (sp_hdcp_repeater_mode(anx78xx))
+			sp_hdcp_repeater_reauth(anx78xx);
+/*
+		if (!sp.hdcp_enabled) {
+			sp_set_system_state(anx78xx, STATE_HDCP_AUTH);
+			sp.hdcp_enabled = true;
+		}
+*/
+	}
+
+	/*
+	 * SlimPort State Process
+	 */
+	switch (sp.tx_system_state) {
+	case STATE_CABLE_UNPLUGGED:
+		sp_variable_init(anx78xx);
+		anx78xx_poweroff(anx78xx);
+		return false;
+	case STATE_WAITING_CABLE_PLUG:
+		sp_variable_init(anx78xx);
+		anx78xx_poweron(anx78xx);
+		sp.tx_system_state = STATE_SP_INITIALIZED;
+		sp_print_system_state(anx78xx, sp.tx_system_state);
+	/* fallthrough */
+	case STATE_SP_INITIALIZED:
+		sp_initialization(anx78xx);
+		sp.tx_system_state = STATE_SINK_CONNECTION;
+		sp_print_system_state(anx78xx, sp.tx_system_state);
+	/* fallthrough */
+	case STATE_SINK_CONNECTION:
+		if (sp_get_dp_connection(anx78xx)) {
+			sp.tx_system_state = STATE_PARSE_EDID;
+			sp_print_system_state(anx78xx, sp.tx_system_state);
+		} else {
+			break;
+		}
+	/* fallthrough */
+	case STATE_PARSE_EDID:
+		if (sp_edid_process(anx78xx)) {
+			sp.tx_system_state = STATE_LINK_TRAINING;
+			sp_print_system_state(anx78xx, sp.tx_system_state);
+		} else {
+			break;
+		}
+	/* fallthrough */
+	case STATE_LINK_TRAINING:
+		if (sp_link_training(anx78xx)) {
+			sp.tx_system_state = STATE_VIDEO_OUTPUT;
+			sp_print_system_state(anx78xx, sp.tx_system_state);
+		} else {
+			break;
+		}
+	/* fallthrough */
+	case STATE_VIDEO_OUTPUT:
+		if (sp_config_video_output(anx78xx)) {
+			sp.tx_system_state = STATE_HDCP_AUTH;
+			sp_print_system_state(anx78xx, sp.tx_system_state);
+		} else {
+			break;
+		}
+	/* fallthrough */
+	case STATE_HDCP_AUTH:
+		if (!sp_hdcp_repeater_mode(anx78xx)) {
+			if (sp_hdcp_process(anx78xx)) {
+				sp.tx_system_state = STATE_AUDIO_OUTPUT;
+				sp_print_system_state(anx78xx,
+						      sp.tx_system_state);
+			} else {
+				break;
+			}
+		} else {
+			sp.tx_system_state = STATE_AUDIO_OUTPUT;
+		}
+	/* fallthrough */
+	case STATE_AUDIO_OUTPUT:
+		if (sp_config_audio_output(anx78xx)) {
+			sp.tx_system_state = STATE_PLAY_BACK;
+			sp_print_system_state(anx78xx, sp.tx_system_state);
+		} else {
+			break;
+		}
+	/* fallthrough */
+	case STATE_PLAY_BACK:
+	default:
+		break;
+	}
+
+	return true;
+}
+
+/**
+ * sp_system_init(): System initialization
+ *
+ * @anx78xx: SlimPort device.
+ *
+ * A value of zero will be returned on success, a negative errno will
+ * be returned in error cases.
+ */
+int sp_system_init(struct anx78xx *anx78xx)
+{
+	struct device *dev = &anx78xx->client->dev;
+	u16 id;
+	u8 idh = 0, idl = 0;
+	int i;
+
+	sp_variable_init(anx78xx);
+
+	anx78xx_poweron(anx78xx);
+
+	/* check chip id */
+	sp_reg_read(anx78xx, TX_P2, SP_DEVICE_IDL_REG, &idl);
+	sp_reg_read(anx78xx, TX_P2, SP_DEVICE_IDH_REG, &idh);
+	id = idl | (idh << 8);
+
+	for (i = 0; i < ARRAY_SIZE(chipid_list); i++) {
+		if (id == chipid_list[i])
+			return 0;
+	}
+
+	anx78xx_poweroff(anx78xx);
+
+	dev_err(dev, "failed to detect ANX%x\n", id);
+
+	return -ENODEV;
+}
diff --git a/drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.h b/drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.h
new file mode 100644
index 0000000..08eabc9
--- /dev/null
+++ b/drivers/gpu/drm/bridge/anx78xx/slimport_tx_drv.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright(c) 2015, Analogix Semiconductor. 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 __SLIMPORT_TX_DRV_H
+#define __SLIMPORT_TX_DRV_H
+
+#include <linux/hdmi.h>
+
+#include "anx78xx.h"
+#include "slimport_tx_reg.h"
+
+#define FW_VERSION	0x23
+
+enum sp_tx_state {
+	STATE_CABLE_UNPLUGGED,
+	STATE_WAITING_CABLE_PLUG,
+	STATE_SP_INITIALIZED,
+	STATE_SINK_CONNECTION,
+	STATE_PARSE_EDID,
+	STATE_LINK_TRAINING,
+	STATE_VIDEO_OUTPUT,
+	STATE_HDCP_AUTH,
+	STATE_AUDIO_OUTPUT,
+	STATE_PLAY_BACK
+};
+
+enum sp_tx_power_block {
+	SP_TX_PWR_REG = SP_REGISTER_PD,
+	SP_TX_PWR_HDCP = SP_HDCP_PD,
+	SP_TX_PWR_AUDIO = SP_AUDIO_PD,
+	SP_TX_PWR_VIDEO = SP_VIDEO_PD,
+	SP_TX_PWR_LINK = SP_LINK_PD,
+	SP_TX_PWR_TOTAL = SP_TOTAL_PD,
+	SP_TX_PWR_NUMS
+};
+
+enum sp_tx_lt_status {
+	LT_INIT,
+	LT_WAIT_PLL_LOCK,
+	LT_CHECK_LINK_BW,
+	LT_START,
+	LT_WAITING_FINISH,
+	LT_ERROR,
+	LT_FINISH,
+};
+
+enum hdcp_status {
+	HDCP_CAPABLE_CHECK,
+	HDCP_WAITING_VID_STB,
+	HDCP_HW_ENABLE,
+	HDCP_WAITING_FINISH,
+	HDCP_FINISH,
+	HDCP_FAILED,
+	HDCP_NOT_SUPPORTED,
+};
+
+enum repeater_status {
+	HDCP_DONE,
+	HDCP_DOING,
+	HDCP_ERROR,
+};
+
+enum video_output_status {
+	VO_WAIT_VIDEO_STABLE,
+	VO_WAIT_TX_VIDEO_STABLE,
+	VO_CHECK_VIDEO_INFO,
+	VO_FINISH,
+};
+
+enum audio_output_status {
+	AO_INIT,
+	AO_CTS_RCV_INT,
+	AO_AUDIO_RCV_INT,
+	AO_RCV_INT_FINISH,
+	AO_OUTPUT,
+};
+
+enum sp_ssc_dep {
+	SSC_DEP_DISABLE = 0x0,
+	SSC_DEP_500PPM,
+	SSC_DEP_1000PPM,
+	SSC_DEP_1500PPM,
+	SSC_DEP_2000PPM,
+	SSC_DEP_2500PPM,
+	SSC_DEP_3000PPM,
+	SSC_DEP_3500PPM,
+	SSC_DEP_4000PPM,
+	SSC_DEP_4500PPM,
+	SSC_DEP_5000PPM,
+	SSC_DEP_5500PPM,
+	SSC_DEP_6000PPM
+};
+
+bool sp_main_process(struct anx78xx *anx78xx);
+int sp_system_init(struct anx78xx *anx78xx);
+void sp_variable_init(struct anx78xx *anx78xx);
+u8 sp_get_link_bandwidth_from_edid(struct anx78xx *anx78xx);
+
+#endif
diff --git a/drivers/gpu/drm/bridge/anx78xx/slimport_tx_reg.h b/drivers/gpu/drm/bridge/anx78xx/slimport_tx_reg.h
new file mode 100644
index 0000000..c960842
--- /dev/null
+++ b/drivers/gpu/drm/bridge/anx78xx/slimport_tx_reg.h
@@ -0,0 +1,737 @@
+/*
+ * Copyright(c) 2015, Analogix Semiconductor. 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 __SLIMPORT_TX_REG_DEF_H
+#define __SLIMPORT_TX_REG_DEF_H
+
+#define TX_P0				0x70
+#define TX_P1				0x7a
+#define TX_P2				0x72
+
+#define RX_P0				0x7e
+#define RX_P1				0x80
+
+/***************************************************************/
+/* Register definition of device address 0x7e                  */
+/***************************************************************/
+
+/*
+ * System Control and Status
+ */
+
+/* Software Reset Register 1 */
+#define SP_SOFTWARE_RESET1_REG		0x11
+#define SP_VIDEO_RST			BIT(4)
+#define SP_HDCP_MAN_RST			BIT(2)
+#define SP_TMDS_RST			BIT(1)
+#define SP_SW_MAN_RST			BIT(0)
+
+/* System Status Register */
+#define SP_SYSTEM_STATUS_REG		0x14
+#define SP_TMDS_CLOCK_DET		BIT(1)
+#define SP_TMDS_DE_DET			BIT(0)
+
+/* HDMI Status Register */
+#define SP_HDMI_STATUS_REG		0x15
+#define SP_HDMI_AUD_LAYOUT		BIT(3)
+#define SP_HDMI_DET			BIT(0)
+#  define SP_DVI_MODE			0
+#  define SP_HDMI_MODE			1
+
+/* HDMI Mute Control Register */
+#define SP_HDMI_MUTE_CTRL_REG		0x16
+#define SP_AUD_MUTE			BIT(1)
+#define SP_VID_MUTE			BIT(0)
+
+/* System Power Down Register 1 */
+#define SP_SYSTEM_POWER_DOWN1_REG	0x18
+#define SP_PWDN_CTRL			BIT(0)
+
+/*
+ * Audio and Video Auto Control
+ */
+
+/* Auto Audio and Video Control register */
+#define SP_AUDVID_CTRL_REG		0x20
+#define SP_AVC_OE			BIT(7)
+#define SP_AAC_OE			BIT(6)
+#define SP_AVC_EN			BIT(1)
+#define SP_AAC_EN			BIT(0)
+
+/* Audio Exception Enable Registers */
+#define SP_AUD_EXCEPTION_ENABLE_BASE	(0x24 - 1)
+/* Bits for Audio Exception Enable Register 3 */
+#define SP_AEC_EN21			BIT(5)
+
+/*
+ * Interrupt
+ */
+
+/* Interrupt Status Register 1 */
+#define SP_INT_STATUS1_REG		0x31
+/* Bits for Interrupt Status Register 1 */
+#define SP_HDMI_DVI			BIT(7)
+#define SP_CKDT_CHG			BIT(6)
+#define SP_SCDT_CHG			BIT(5)
+#define SP_PCLK_CHG			BIT(4)
+#define SP_PLL_UNLOCK			BIT(3)
+#define SP_CABLE_PLUG_CHG		BIT(2)
+#define SP_SET_MUTE			BIT(1)
+#define SP_SW_INTR			BIT(0)
+/* Bits for Interrupt Status Register 2 */
+#define SP_HDCP_ERR			BIT(5)
+#define SP_AUDIO_SAMPLE_CHG		BIT(0)	/* undocumented */
+/* Bits for Interrupt Status Register 3 */
+#define SP_AUD_MODE_CHG			BIT(0)
+/* Bits for Interrupt Status Register 5 */
+#define SP_AUDIO_RCV			BIT(0)
+/* Bits for Interrupt Status Register 6 */
+#define SP_CTS_RCV			BIT(7)
+#define SP_NEW_AUD_PKT			BIT(4)
+#define SP_NEW_AVI_PKT			BIT(1)
+#define SP_NEW_CP_PKT			BIT(0)
+/* Bits for Interrupt Status Register 7 */
+#define SP_NO_VSI			BIT(7)
+#define SP_NEW_VS			BIT(4)
+
+/* Interrupt Mask Status Registers */
+#define SP_INT_MASK_BASE		(0x41 - 1)
+
+/* HDMI US TIMER Control Register */
+#define SP_HDMI_US_TIMER_CTRL_REG	0x49
+#define SP_MS_TIMER_MARGIN_10_8_MASK	0x07
+
+/*
+ * TMDS Control
+ */
+
+/* TMDS Control Registers */
+#define SP_TMDS_CTRL_BASE		(0x50 - 1)
+/* Bits for TMDS Control Register 7 */
+#define SP_PD_RT			BIT(0)
+
+/*
+ * Video Control
+ */
+
+/* Video Status Register */
+#define SP_VIDEO_STATUS_REG		0x70
+#define SP_COLOR_DEPTH_MASK		0xf0
+#define SP_COLOR_DEPTH_SHIFT		4
+#  define SP_COLOR_DEPTH_MODE_LEGACY	0x00
+#  define SP_COLOR_DEPTH_MODE_24BIT	0x04
+#  define SP_COLOR_DEPTH_MODE_30BIT	0x05
+#  define SP_COLOR_DEPTH_MODE_36BIT	0x06
+#  define SP_COLOR_DEPTH_MODE_48BIT	0x07
+
+/* Video Data Range Control Register */
+#define SP_VID_DATA_RANGE_CTRL_REG	0x83
+#define SP_R2Y_INPUT_LIMIT		BIT(1)
+
+/* Pixel Clock High Resolution Counter Registers */
+#define SP_PCLK_HIGHRES_CNT_BASE	(0x8c - 1)
+
+/*
+ * Audio Control
+ */
+
+/* Number of Audio Channels Status Registers */
+#define SP_AUD_CH_STATUS_REG_NUM	5
+
+/* Audio IN S/PDIF Channel Status Registers */
+#define SP_AUD_SPDIF_CH_STATUS_BASE	0xc7
+
+/* Audio IN S/PDIF Channel Status Register 4 */
+#define SP_FS_FREQ_MASK			0x0f
+#  define SP_FS_FREQ_44100HZ		0x00
+#  define SP_FS_FREQ_48000HZ		0x02
+#  define SP_FS_FREQ_32000HZ		0x03
+#  define SP_FS_FREQ_88200HZ		0x08
+#  define SP_FS_FREQ_96000HZ		0x0a
+#  define SP_FS_FREQ_176400HZ		0x0c
+#  define SP_FS_FREQ_192000HZ		0x0e
+
+/*
+ * Micellaneous Control Block
+ */
+
+/* CHIP Control Register */
+#define SP_CHIP_CTRL_REG		0xe3
+#define SP_MAN_HDMI5V_DET		BIT(3)
+#define SP_PLLLOCK_CKDT_EN		BIT(2)
+#define SP_ANALOG_CKDT_EN		BIT(1)
+#define SP_DIGITAL_CKDT_EN		BIT(0)
+
+/* Packet Receiving Status Register */
+#define SP_PACKET_RECEIVING_STATUS_REG	0xf3
+#define SP_AVI_RCVD			BIT(5)
+#define SP_VSI_RCVD			BIT(1)
+
+/***************************************************************/
+/* Register definition of device address 0x80                  */
+/***************************************************************/
+
+/* HDCP BCAPS Shadow Register */
+#define SP_HDCP_BCAPS_SHADOW_REG	0x2a
+#define SP_BCAPS_REPEATER		BIT(5)
+
+/* HDCP Status Register */
+#define SP_RX_HDCP_STATUS_REG		0x3f
+#define SP_AUTH_EN			BIT(4)
+
+/*
+ * InfoFrame and Control Packet Registers
+ */
+
+/* AVI InfoFrame packet checksum */
+#define SP_AVI_INFOFRAME_CHECKSUM	0xa3
+
+/* AVI InfoFrame Registers */
+#define SP_AVI_INFOFRAME_DATA_BASE	0xa4
+
+#define SP_AVI_COLOR_F_MASK		0x60
+#define SP_AVI_COLOR_F_SHIFT		5
+
+/* Audio InfoFrame Registers */
+#define SP_AUD_INFOFRAME_DATA_BASE	0xc4
+#define SP_AUD_INFOFRAME_LAYOUT_MASK	0x0f
+
+/* MPEG/HDMI Vendor Specific InfoFrame Packet type code */
+#define SP_MPEG_VS_INFOFRAME_TYPE_REG	0xe0
+
+/* MPEG/HDMI Vendor Specific InfoFrame Packet length */
+#define SP_MPEG_VS_INFOFRAME_LEN_REG	0xe2
+
+/* MPEG/HDMI Vendor Specific InfoFrame Packet version number */
+#define SP_MPEG_VS_INFOFRAME_VER_REG	0xe1
+
+/* MPEG/HDMI Vendor Specific InfoFrame Packet content */
+#define SP_MPEG_VS_INFOFRAME_DATA_BASE	0xe4
+
+/* General Control Packet Register */
+#define SP_GENERAL_CTRL_PACKET_REG	0x9f
+#define SP_CLEAR_AVMUTE			BIT(4)
+#define SP_SET_AVMUTE			BIT(0)
+
+/***************************************************************/
+/* Register definition of device address 0x70                  */
+/***************************************************************/
+
+/* HDCP Status Register */
+#define SP_TX_HDCP_STATUS_REG		0x00
+#define SP_AUTH_FAIL			BIT(5)
+#define SP_AUTHEN_PASS			BIT(1)
+
+/* HDCP Control Register 0 */
+#define SP_HDCP_CTRL0_REG		0x01
+#define SP_RX_REPEATER			BIT(6)
+#define SP_RE_AUTH			BIT(5)
+#define SP_SW_AUTH_OK			BIT(4)
+#define SP_HARD_AUTH_EN			BIT(3)
+#define SP_HDCP_ENC_EN			BIT(2)
+#define SP_BKSV_SRM_PASS		BIT(1)
+#define SP_KSVLIST_VLD			BIT(0)
+/* HDCP Function Enabled */
+#define SP_HDCP_FUNCTION_ENABLED	(BIT(0) | BIT(1) | BIT(2) | BIT(3))
+
+/* HDCP Receiver BSTATUS Register 0 */
+#define	SP_HDCP_RX_BSTATUS0_REG		0x1b
+/* HDCP Receiver BSTATUS Register 1 */
+#define	SP_HDCP_RX_BSTATUS1_REG		0x1c
+
+/* HDCP Embedded "Blue Screen" Content Registers */
+#define SP_HDCP_VID0_BLUE_SCREEN_REG	0x2c
+#define SP_HDCP_VID1_BLUE_SCREEN_REG	0x2d
+#define SP_HDCP_VID2_BLUE_SCREEN_REG	0x2e
+
+/* HDCP Wait R0 Timing Register */
+#define SP_HDCP_WAIT_R0_TIME_REG	0x40
+
+/* HDCP Link Integrity Check Timer Register */
+#define SP_HDCP_LINK_CHECK_TIMER_REG	0x41
+
+/* HDCP Repeater Ready Wait Timer Register */
+#define SP_HDCP_RPTR_RDY_WAIT_TIME_REG	0x42
+
+/* HDCP Auto Timer Register */
+#define SP_HDCP_AUTO_TIMER_REG		0x51
+
+/* HDCP Key Status Register */
+#define SP_HDCP_KEY_STATUS_REG		0x5e
+
+/* HDCP Key Command Register */
+#define SP_HDCP_KEY_COMMAND_REG		0x5f
+#define SP_DISABLE_SYNC_HDCP		BIT(2)
+
+/* OTP Memory Key Protection Registers */
+#define SP_OTP_KEY_PROTECT1_REG		0x60
+#define SP_OTP_KEY_PROTECT2_REG		0x61
+#define SP_OTP_KEY_PROTECT3_REG		0x62
+#define SP_OTP_PSW1			0xa2
+#define SP_OTP_PSW2			0x7e
+#define SP_OTP_PSW3			0xc6
+
+/* DP System Control Registers */
+#define SP_DP_SYSTEM_CTRL_BASE		(0x80 - 1)
+/* Bits for DP System Control Register 2 */
+#define SP_CHA_STA			BIT(2)
+/* Bits for DP System Control Register 3 */
+#define SP_HPD_STATUS			BIT(6)
+#define SP_STRM_VALID			BIT(2)
+/* Bits for DP System Control Register 4 */
+#define SP_ENHANCED_MODE		BIT(3)
+
+/* DP Video Control Register */
+#define SP_DP_VIDEO_CTRL_REG		0x84
+#define SP_COLOR_F_MASK			0x06
+#define SP_COLOR_F_SHIFT		1
+#define SP_BPC_MASK			0xe0
+#define SP_BPC_SHIFT			5
+#  define SP_BPC_6BITS			0x00
+#  define SP_BPC_8BITS			0x01
+#  define SP_BPC_10BITS			0x02
+#  define SP_BPC_12BITS			0x03
+
+/* DP Audio Control Register */
+#define SP_DP_AUDIO_CTRL_REG		0x87
+#define SP_AUD_EN			BIT(0)
+
+/* 10us Pulse Generate Timer Registers */
+#define SP_I2C_GEN_10US_TIMER0_REG	0x88
+#define SP_I2C_GEN_10US_TIMER1_REG	0x89
+
+/* Packet Send Control Register */
+#define SP_PACKET_SEND_CTRL_REG		0x90
+#define SP_AUD_IF_UP			BIT(7)
+#define SP_AVI_IF_UD			BIT(6)
+#define SP_MPEG_IF_UD			BIT(5)
+#define SP_SPD_IF_UD			BIT(4)
+#define SP_AUD_IF_EN			BIT(3)
+#define SP_AVI_IF_EN			BIT(2)
+#define SP_MPEG_IF_EN			BIT(1)
+#define SP_SPD_IF_EN			BIT(0)
+
+/* DP HDCP Control Register */
+#define SP_DP_HDCP_CTRL_REG		0x92
+#define SP_AUTO_EN			BIT(7)
+#define SP_AUTO_START			BIT(5)
+#define SP_LINK_POLLING			BIT(1)
+
+/* DP Main Link Bandwidth Setting Register */
+#define SP_DP_MAIN_LINK_BW_SET_REG	0xa0
+#define SP_LINK_BW_SET_MASK		0x1f
+#define SP_INITIAL_SLIM_M_AUD_SEL	BIT(5)
+
+/* DP Training Pattern Set Register */
+#define SP_DP_TRAINING_PATTERN_SET_REG	0xa2
+
+/* DP Lane 0 Link Training Control Register */
+#define SP_DP_LANE0_LT_CTRL_REG		0xa3
+#define SP_TX_SW_SET_MASK		0x1b
+#define SP_MAX_PRE_REACH		BIT(5)
+#define SP_MAX_DRIVE_REACH		BIT(4)
+#define SP_PRE_EMP_LEVEL1		BIT(3)
+#define SP_DRVIE_CURRENT_LEVEL1		BIT(0)
+
+/* DP Link Training Control Register */
+#define SP_DP_LT_CTRL_REG		0xa8
+#define SP_LT_ERROR_TYPE_MASK		0x70
+#  define SP_LT_NO_ERROR		0x00
+#  define SP_LT_AUX_WRITE_ERROR		0x01
+#  define SP_LT_MAX_DRIVE_REACHED	0x02
+#  define SP_LT_WRONG_LANE_COUNT_SET	0x03
+#  define SP_LT_LOOP_SAME_5_TIME	0x04
+#  define SP_LT_CR_FAIL_IN_EQ		0x05
+#  define SP_LT_EQ_LOOP_5_TIME		0x06
+#define SP_LT_EN			BIT(0)
+
+/* DP CEP Training Control Registers */
+#define SP_DP_CEP_TRAINING_CTRL0_REG	0xa9
+#define SP_DP_CEP_TRAINING_CTRL1_REG	0xaa
+
+/* DP Debug Register 1 */
+#define SP_DP_DEBUG1_REG		0xb0
+#define SP_DEBUG_PLL_LOCK		BIT(4)
+#define SP_POLLING_EN			BIT(1)
+
+/* DP Polling Control Register */
+#define SP_DP_POLLING_CTRL_REG		0xb4
+#define SP_AUTO_POLLING_DISABLE		BIT(0)
+
+/* DP Link Debug Control Register */
+#define SP_DP_LINK_DEBUG_CTRL_REG	0xb8
+#define SP_M_VID_DEBUG			BIT(5)
+#define SP_NEW_PRBS7			BIT(4)
+#define SP_INSERT_ER			BIT(1)
+#define SP_PRBS31_EN			BIT(0)
+
+/* AUX Misc control Register */
+#define SP_AUX_MISC_CTRL_REG		0xbf
+
+/* DP PLL control Register */
+#define SP_DP_PLL_CTRL_REG		0xc7
+#define SP_PLL_RST			BIT(6)
+
+/* DP Analog Power Down Register */
+#define SP_DP_ANALOG_POWER_DOWN_REG	0xc8
+#define SP_CH0_PD			BIT(0)
+
+/* DP Misc Control Register */
+#define SP_DP_MISC_CTRL_REG		0xcd
+#define SP_EQ_TRAINING_LOOP		BIT(6)
+
+/* DP Extra I2C Device Address Register */
+#define SP_DP_EXTRA_I2C_DEV_ADDR_REG	0xce
+#define SP_I2C_STRETCH_DISABLE		BIT(7)
+
+#define SP_I2C_EXTRA_ADDR		0x50
+
+/* DP DownSpreading Control Register 1 */
+#define SP_DP_DOWNSPREADING_CTRL1_REG	0xd0
+#define SP_TX_SSC_DISABLE		0xc0
+#define SP_TX_SSC_DOWNSPREADING		BIT(6)
+
+/* DP M Value Calculation Control Register */
+#define SP_DP_M_CALCULATION_CTRL_REG	0xd9
+#define SP_M_GEN_CLK_SEL		BIT(0)
+
+/* AUX Channel Access Status Register */
+#define SP_AUX_CH_STATUS_REG		0xe0
+#define SP_AUX_STATUS			0x0f
+
+/* AUX Channel DEFER Control Register */
+#define SP_AUX_DEFER_CTRL_REG		0xe2
+#define SP_DEFER_CTRL_EN		BIT(7)
+
+/* DP Buffer Data Count Register */
+#define SP_BUF_DATA_COUNT_REG		0xe4
+#define SP_BUF_DATA_COUNT_MASK		0x1f
+#define SP_BUF_CLR			BIT(7)
+
+/* DP AUX Channel Control Register 1 */
+#define SP_DP_AUX_CH_CTRL1_REG		0xe5
+#define SP_AUX_TX_COMM_MASK		0x0f
+#define SP_AUX_LENGTH_MASK		0xf0
+#define SP_AUX_LENGTH_SHIFT		4
+
+/* DP AUX CH Address Register 0 */
+#define SP_AUX_ADDR_7_0_REG		0xe6
+
+/* DP AUX CH Address Register 1 */
+#define SP_AUX_ADDR_15_8_REG		0xe7
+
+/* DP AUX CH Address Register 2 */
+#define SP_AUX_ADDR_19_16_REG		0xe8
+#define SP_AUX_ADDR_19_16_MASK		0x0f
+
+/* DP AUX Channel Control Register 2 */
+#define SP_DP_AUX_CH_CTRL2_REG		0xe9
+#define SP_AUX_SEL_RXCM			BIT(6)
+#define SP_AUX_CHSEL			BIT(3)
+#define SP_AUX_PN_INV			BIT(2)
+#define SP_ADDR_ONLY			BIT(1)
+#define SP_AUX_EN			BIT(0)
+
+/* DP Video Stream Control InfoFrame Register */
+#define SP_DP_3D_VSC_CTRL_REG		0xea
+#define SP_INFO_FRAME_VSC_EN		BIT(0)
+
+/* DP Video Stream Data Byte 1 Register */
+#define SP_DP_VSC_DB1_REG		0xeb
+
+/* DP AUX Channel Control Register 3 */
+#define SP_DP_AUX_CH_CTRL3_REG		0xec
+#define SP_WAIT_COUNTER_7_0_MASK	0xff
+
+/* DP AUX Channel Control Register 4 */
+#define SP_DP_AUX_CH_CTRL4_REG		0xed
+
+/* DP AUX Buffer Data Registers */
+#define SP_DP_BUF_DATA0_REG		0xf0
+
+/***************************************************************/
+/* Register definition of device address 0x72                  */
+/***************************************************************/
+
+/*
+ * Core Register Definitions
+ */
+
+/* Device ID Low Byte Register */
+#define SP_DEVICE_IDL_REG		0x02
+
+/* Device ID High Byte Register */
+#define SP_DEVICE_IDH_REG		0x03
+
+/* Power Down Control Register */
+#define SP_POWERDOWN_CTRL_REG		0x05
+#define SP_REGISTER_PD			BIT(7)
+#define SP_HDCP_PD			BIT(5)
+#define SP_AUDIO_PD			BIT(4)
+#define SP_VIDEO_PD			BIT(3)
+#define SP_LINK_PD			BIT(2)
+#define SP_TOTAL_PD			BIT(1)
+
+/* Reset Control Register 1 */
+#define SP_RESET_CTRL1_REG		0x06
+#define SP_MISC_RST			BIT(7)
+#define SP_VIDCAP_RST			BIT(6)
+#define SP_VIDFIF_RST			BIT(5)
+#define SP_AUDFIF_RST			BIT(4)
+#define SP_AUDCAP_RST			BIT(3)
+#define SP_HDCP_RST			BIT(2)
+#define SP_SW_RST			BIT(1)
+#define SP_HW_RST			BIT(0)
+
+/* Reset Control Register 2 */
+#define SP_RESET_CTRL2_REG		0x07
+#define SP_AUX_RST			BIT(2)
+#define SP_SERDES_FIFO_RST		BIT(1)
+#define SP_I2C_REG_RST			BIT(0)
+
+/* Video Control Register 1 */
+#define SP_VID_CTRL1_REG		0x08
+#define SP_VIDEO_EN			BIT(7)
+#define SP_VIDEO_MUTE			BIT(2)
+#define SP_DE_GEN			BIT(1)
+#define SP_DEMUX			BIT(0)
+
+/* Video Control Register 2 */
+#define SP_VID_CTRL2_REG		0x09
+#define SP_IN_COLOR_F_MASK		0x03
+#define SP_IN_YC_BIT_SEL		BIT(2)
+#define SP_IN_BPC_MASK			0x70
+#define SP_IN_BPC_SHIFT			4
+#  define SP_IN_BPC_12BIT		0x03
+#  define SP_IN_BPC_10BIT		0x02
+#  define SP_IN_BPC_8BIT		0x01
+#  define SP_IN_BPC_6BIT		0x00
+#define SP_IN_D_RANGE			BIT(7)
+
+/* Video Control Register 3 */
+#define SP_VID_CTRL3_REG		0x0a
+#define SP_HPD_OUT			BIT(6)
+
+/* Video Control Register 5 */
+#define SP_VID_CTRL5_REG		0x0c
+#define SP_CSC_STD_SEL			BIT(7)
+#define SP_XVYCC_RNG_LMT		BIT(6)
+#define SP_RANGE_Y2R			BIT(5)
+#define SP_CSPACE_Y2R			BIT(4)
+#define SP_RGB_RNG_LMT			BIT(3)
+#define SP_Y_RNG_LMT			BIT(2)
+#define SP_RANGE_R2Y			BIT(1)
+#define SP_CSPACE_R2Y			BIT(0)
+
+/* Video Control Register 6 */
+#define SP_VID_CTRL6_REG		0x0d
+#define SP_TEST_PATTERN_EN		BIT(7)
+#define SP_VIDEO_PROCESS_EN		BIT(6)
+#define SP_VID_US_MODE			BIT(3)
+#define SP_VID_DS_MODE			BIT(2)
+#define SP_UP_SAMPLE			BIT(1)
+#define SP_DOWN_SAMPLE			BIT(0)
+
+/* Video Control Register 8 */
+#define SP_VID_CTRL8_REG		0x0f
+#define SP_VID_VRES_TH			BIT(0)
+
+/* Total Line Status Low Byte Register */
+#define SP_TOTAL_LINE_STAL_REG		0x24
+
+/* Total Line Status High Byte Register */
+#define SP_TOTAL_LINE_STAH_REG		0x25
+
+/* Active Line Status Low Byte Register */
+#define SP_ACT_LINE_STAL_REG		0x26
+
+/* Active Line Status High Byte Register */
+#define SP_ACT_LINE_STAH_REG		0x27
+
+/* Vertical Front Porch Status Register */
+#define SP_V_F_PORCH_STA_REG		0x28
+
+/* Vertical SYNC Width Status Register */
+#define SP_V_SYNC_STA_REG		0x29
+
+/* Vertical Back Porch Status Register */
+#define SP_V_B_PORCH_STA_REG		0x2a
+
+/* Total Pixel Status Low Byte Register */
+#define SP_TOTAL_PIXEL_STAL_REG		0x2b
+
+/* Total Pixel Status High Byte Register */
+#define SP_TOTAL_PIXEL_STAH_REG		0x2c
+
+/* Active Pixel Status Low Byte Register */
+#define SP_ACT_PIXEL_STAL_REG		0x2d
+
+/* Active Pixel Status High Byte Register */
+#define SP_ACT_PIXEL_STAH_REG		0x2e
+
+/* Horizontal Front Porch Status Low Byte Register */
+#define SP_H_F_PORCH_STAL_REG		0x2f
+
+/* Horizontal Front Porch Statys High Byte Register */
+#define SP_H_F_PORCH_STAH_REG		0x30
+
+/* Horizontal SYNC Width Status Low Byte Register */
+#define SP_H_SYNC_STAL_REG		0x31
+
+/* Horizontal SYNC Width Status High Byte Register */
+#define SP_H_SYNC_STAH_REG		0x32
+
+/* Horizontal Back Porch Status Low Byte Register */
+#define SP_H_B_PORCH_STAL_REG		0x33
+
+/* Horizontal Back Porch Status High Byte Register */
+#define SP_H_B_PORCH_STAH_REG		0x34
+
+/* InfoFrame AVI Packet Type Register */
+#define SP_INFOFRAME_AVI_TYPE_REG	0x70
+
+/* InfoFrame AVI Packet Version Register */
+#define SP_INFOFRAME_AVI_VER_REG	0x71
+
+/* InfoFrame AVI Packet Length Register */
+#define SP_INFOFRAME_AVI_LEN_REG	0x72
+
+/* InfoFrame AVI Packet DB0 Register */
+#define SP_INFOFRAME_AVI_DB0_REG	0x73
+
+/* Bit Control Specific Register */
+#define SP_BIT_CTRL_SPECIFIC_REG	0x80
+#define SP_BIT_CTRL_SELECT_SHIFT	1
+#define SP_ENABLE_BIT_CTRL		BIT(0)
+
+/* InfoFrame Audio Packet Type Register */
+#define SP_INFOFRAME_AUD_TYPE_REG	0x83
+
+/* InfoFrame Audio Packet Version Register */
+#define SP_INFOFRAME_AUD_VER_REG	0x84
+
+/* InfoFrame Audio Packet Length Register */
+#define SP_INFOFRAME_AUD_LEN_REG	0x85
+
+/* InfoFrame Audio Packet DB0 Register */
+#define SP_INFOFRAME_AUD_DB0_REG	0x86
+
+/* InfoFrame MPEG Packet Type Register */
+#define SP_INFOFRAME_MPEG_TYPE_REG	0xb0
+
+/* InfoFrame MPEG Packet Version Register */
+#define SP_INFOFRAME_MPEG_VER_REG	0xb1
+
+/* InfoFrame MPEG Packet Length Register */
+#define SP_INFOFRAME_MPEG_LEN_REG	0xb2
+
+/* InfoFrame MPEG Packet DB0 Register */
+#define SP_INFOFRAME_MPEG_DB0_REG	0xb3
+
+/* Audio Channel Status Registers */
+#define SP_AUD_CH_STATUS_BASE		(0xd0 - 1)
+
+/* Audio Channel Num Register 5 */
+#define SP_I2S_CHANNEL_NUM_MASK		0xe0
+#  define SP_I2S_CH_NUM_1		(0x00 << 5)
+#  define SP_I2S_CH_NUM_2		(0x01 << 5)
+#  define SP_I2S_CH_NUM_3		(0x02 << 5)
+#  define SP_I2S_CH_NUM_4		(0x03 << 5)
+#  define SP_I2S_CH_NUM_5		(0x04 << 5)
+#  define SP_I2S_CH_NUM_6		(0x05 << 5)
+#  define SP_I2S_CH_NUM_7		(0x06 << 5)
+#  define SP_I2S_CH_NUM_8		(0x07 << 5)
+#define SP_EXT_VUCP			BIT(2)
+#define SP_VBIT				BIT(1)
+#define SP_AUDIO_LAYOUT			BIT(0)
+
+/* Analog Debug Register 2 */
+#define SP_ANALOG_DEBUG2_REG		0xdd
+#define SP_FORCE_SW_OFF_BYPASS		0x20
+#define SP_XTAL_FRQ			0x1c
+#  define SP_XTAL_FRQ_19M2		(0x00 << 2)
+#  define SP_XTAL_FRQ_24M		(0x01 << 2)
+#  define SP_XTAL_FRQ_25M		(0x02 << 2)
+#  define SP_XTAL_FRQ_26M		(0x03 << 2)
+#  define SP_XTAL_FRQ_27M		(0x04 << 2)
+#  define SP_XTAL_FRQ_38M4		(0x05 << 2)
+#  define SP_XTAL_FRQ_52M		(0x06 << 2)
+#define SP_POWERON_TIME_1P5MS		0x03
+
+/* Analog Control 0 Register */
+#define SP_ANALOG_CTRL0_REG		0xe1
+
+/* Common Interrupt Status Register 1 */
+#define SP_COMMON_INT_STATUS_BASE	(0xf1 - 1)
+#define SP_PLL_LOCK_CHG			0x40
+
+/* Common Interrupt Status Register 2 */
+#define SP_COMMON_INT_STATUS2		0xf2
+#define SP_HDCP_AUTH_CHG		BIT(1)
+#define SP_HDCP_AUTH_DONE		BIT(0)
+
+#define SP_HDCP_LINK_CHECK_FAIL		BIT(0)
+
+#define SP_HPD_IRQ			BIT(6)
+#define SP_HPD_ESYNC_ERR		BIT(4)
+#define SP_HPD_CHG			BIT(2)
+#define SP_HPD_LOST			BIT(1)
+#define SP_HPD_PLUG			BIT(0)
+
+/* DP Interrupt Status Register */
+#define SP_DP_INT_STATUS_REG		0xf7
+#define SP_TRAINING_FINISH		BIT(5)
+#define SP_POLLING_ERR			BIT(4)
+
+/* Common Interrupt Mask Register */
+#define SP_COMMON_INT_MASK_BASE		(0xf8 - 1)
+
+/* Interrupt Control Register */
+#define SP_INT_CTRL_REG			0xff
+
+/***************************************************************/
+/* Register definition of device address 0x7a                  */
+/***************************************************************/
+
+/* DP TX Link Training Control Register */
+#define SP_DP_TX_LT_CTRL0_REG		0x30
+
+/* PD 1.2 Lint Training 80bit Pattern Register */
+#define SP_DP_LT_80BIT_PATTERN0_REG	0x80
+#define SP_DP_LT_80BIT_PATTERN_REG_NUM	10
+
+/* Audio Interface Control Register 0 */
+#define SP_AUD_INTERFACE_CTRL0_REG	0x5f
+#define SP_AUD_INTERFACE_DISABLE	0x80
+
+/* Audio Interface Control Register 2 */
+#define SP_AUD_INTERFACE_CTRL2_REG	0x60
+#define SP_M_AUD_ADJUST_ST		0x04
+
+/* Audio Interface Control Register 3 */
+#define SP_AUD_INTERFACE_CTRL3_REG	0x62
+
+/* Audio Interface Control Register 4 */
+#define SP_AUD_INTERFACE_CTRL4_REG	0x67
+
+/* Audio Interface Control Register 5 */
+#define SP_AUD_INTERFACE_CTRL5_REG	0x68
+
+/* Audio Interface Control Register 6 */
+#define SP_AUD_INTERFACE_CTRL6_REG	0x69
+
+/* Firmware Version Register */
+#define SP_FW_VER_REG			0xb7
+
+#endif
-- 
2.1.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCHv6 4/5] devicetree: Add new ANX7814 SlimPort transmitter binding.
  2015-12-04  8:35   ` Enric Balletbo i Serra
@ 2015-12-04 16:13     ` Rob Herring
  -1 siblings, 0 replies; 17+ messages in thread
From: Rob Herring @ 2015-12-04 16:13 UTC (permalink / raw)
  To: Enric Balletbo i Serra
  Cc: devicetree, linux-kernel, dri-devel, devel, treding, pawel.moll,
	mark.rutland, ijc+devicetree, galak, airlied, gregkh,
	sjoerd.simons, javier, span, nathan.chung, djkurtz, drinkcat,
	laurent.pinchart, dan.carpenter, jb.tsai, cawa.cheng,
	eddie.huang, cjiao, emil.l.velikov

On Fri, Dec 04, 2015 at 09:35:06AM +0100, Enric Balletbo i Serra wrote:
> The ANX7814 is an ultra-low power Full-HD (1080p60) SlimPort transmitter
> designed for portable devices.
> 
> You can add support to your board with current binding.
> 
> Example:
> 
> 	anx7814: anx7814@38 {
> 		compatible = "analogix,anx7814";
> 		reg = <0x38>;
> 		interrupt-parent = <&gpio0>
> 		interrupts = <1 IRQ_TYPE_LEVEL_HIGH>;
> 		pd-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
> 		reset-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
> 		v10-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
> 		ports {

This and the example below should be just "port" I think. Otherwise:

Acked-by: Rob Herring <robh@kernel.org>


> 			anx7814_in: endpoint {
> 				remote-endpoint = <&hdmi0_out>;
> 			};
> 		};
> 	};
> 
> Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> ---
> 
> Changes since last version (requested by Rob Herring)
> - Specify how many ports and how many endpoints for each port
> - Simplify to just port (dropping ports)
> - For cable det use an interrupt instead (to a gpio controller)
> 
>  .../devicetree/bindings/video/bridge/anx7814.txt   | 39 ++++++++++++++++++++++
>  1 file changed, 39 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/video/bridge/anx7814.txt
> 
> diff --git a/Documentation/devicetree/bindings/video/bridge/anx7814.txt b/Documentation/devicetree/bindings/video/bridge/anx7814.txt
> new file mode 100644
> index 0000000..4e68789
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/video/bridge/anx7814.txt
> @@ -0,0 +1,39 @@
> +Analogix ANX7814 SlimPort (Full-HD Transmitter)
> +-----------------------------------------------
> +
> +The ANX7814 is an ultra-low power Full-HD (1080p60) SlimPort transmitter
> +designed for portable devices.
> +
> +Required properties:
> +
> + - compatible		: "analogix,anx7814"
> + - reg			: I2C address of the device
> + - interrupt-parent	: Should be the phandle of the interrupt controller
> +			  that services interrupts for this device
> + - interrupts		: Should contain the cable detection interrupt
> + - pd-gpios		: Which GPIO to use for power down
> + - reset-gpios		: Which GPIO to use for reset
> +
> +Optional properties:
> +
> + - v10-gpios		: Which GPIO to use for V10 control.
> + - Video port for HDMI output, using the DT bindings defined in [1].
> +
> +[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
> +
> +Example:
> +
> +	anx7814: anx7814@38 {
> +		compatible = "analogix,anx7814";
> +		reg = <0x38>;
> +		interrupt-parent = <&gpio0>
> +		interrupts = <1 IRQ_TYPE_LEVEL_HIGH>;
> +		pd-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
> +		reset-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
> +		v10-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
> +		ports {
> +			anx7814_in: endpoint {
> +				remote-endpoint = <&hdmi0_out>;
> +			};
> +		};
> +	};
> -- 
> 2.1.0
> 

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCHv6 4/5] devicetree: Add new ANX7814 SlimPort transmitter binding.
@ 2015-12-04 16:13     ` Rob Herring
  0 siblings, 0 replies; 17+ messages in thread
From: Rob Herring @ 2015-12-04 16:13 UTC (permalink / raw)
  To: Enric Balletbo i Serra
  Cc: mark.rutland, sjoerd.simons, laurent.pinchart, devel, drinkcat,
	cawa.cheng, nathan.chung, javier, treding, dan.carpenter,
	devicetree, pawel.moll, ijc+devicetree, dri-devel, eddie.huang,
	cjiao, jb.tsai, gregkh, emil.l.velikov, linux-kernel, galak,
	span

On Fri, Dec 04, 2015 at 09:35:06AM +0100, Enric Balletbo i Serra wrote:
> The ANX7814 is an ultra-low power Full-HD (1080p60) SlimPort transmitter
> designed for portable devices.
> 
> You can add support to your board with current binding.
> 
> Example:
> 
> 	anx7814: anx7814@38 {
> 		compatible = "analogix,anx7814";
> 		reg = <0x38>;
> 		interrupt-parent = <&gpio0>
> 		interrupts = <1 IRQ_TYPE_LEVEL_HIGH>;
> 		pd-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
> 		reset-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
> 		v10-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
> 		ports {

This and the example below should be just "port" I think. Otherwise:

Acked-by: Rob Herring <robh@kernel.org>


> 			anx7814_in: endpoint {
> 				remote-endpoint = <&hdmi0_out>;
> 			};
> 		};
> 	};
> 
> Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> ---
> 
> Changes since last version (requested by Rob Herring)
> - Specify how many ports and how many endpoints for each port
> - Simplify to just port (dropping ports)
> - For cable det use an interrupt instead (to a gpio controller)
> 
>  .../devicetree/bindings/video/bridge/anx7814.txt   | 39 ++++++++++++++++++++++
>  1 file changed, 39 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/video/bridge/anx7814.txt
> 
> diff --git a/Documentation/devicetree/bindings/video/bridge/anx7814.txt b/Documentation/devicetree/bindings/video/bridge/anx7814.txt
> new file mode 100644
> index 0000000..4e68789
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/video/bridge/anx7814.txt
> @@ -0,0 +1,39 @@
> +Analogix ANX7814 SlimPort (Full-HD Transmitter)
> +-----------------------------------------------
> +
> +The ANX7814 is an ultra-low power Full-HD (1080p60) SlimPort transmitter
> +designed for portable devices.
> +
> +Required properties:
> +
> + - compatible		: "analogix,anx7814"
> + - reg			: I2C address of the device
> + - interrupt-parent	: Should be the phandle of the interrupt controller
> +			  that services interrupts for this device
> + - interrupts		: Should contain the cable detection interrupt
> + - pd-gpios		: Which GPIO to use for power down
> + - reset-gpios		: Which GPIO to use for reset
> +
> +Optional properties:
> +
> + - v10-gpios		: Which GPIO to use for V10 control.
> + - Video port for HDMI output, using the DT bindings defined in [1].
> +
> +[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
> +
> +Example:
> +
> +	anx7814: anx7814@38 {
> +		compatible = "analogix,anx7814";
> +		reg = <0x38>;
> +		interrupt-parent = <&gpio0>
> +		interrupts = <1 IRQ_TYPE_LEVEL_HIGH>;
> +		pd-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
> +		reset-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
> +		v10-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
> +		ports {
> +			anx7814_in: endpoint {
> +				remote-endpoint = <&hdmi0_out>;
> +			};
> +		};
> +	};
> -- 
> 2.1.0
> 
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCHv6 5/5] drm: bridge: anx78xx: Add anx78xx driver support by analogix.
  2015-12-04  8:35   ` Enric Balletbo i Serra
@ 2015-12-07  8:09     ` Dan Carpenter
  -1 siblings, 0 replies; 17+ messages in thread
From: Dan Carpenter @ 2015-12-07  8:09 UTC (permalink / raw)
  To: Enric Balletbo i Serra
  Cc: devicetree, linux-kernel, dri-devel, devel, treding,
	mark.rutland, drinkcat, laurent.pinchart, pawel.moll,
	ijc+devicetree, airlied, gregkh, emil.l.velikov, cawa.cheng,
	djkurtz, jb.tsai, sjoerd.simons, robh+dt, span, galak, javier,
	eddie.huang, cjiao, nathan.chung

On Fri, Dec 04, 2015 at 09:35:07AM +0100, Enric Balletbo i Serra wrote:
> +static int sp_wait_aux_op_finish(struct anx78xx *anx78xx)
> +{
> +	u8 errcnt;
> +	u8 val;
> +	struct device *dev = &anx78xx->client->dev;
> +
> +	errcnt = 150;
> +	while (errcnt--) {
> +		sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
> +		if (!(val & SP_AUX_EN))
> +			break;
> +		usleep_range(2000, 4000);
> +	}
> +
> +	if (!errcnt) {

This is off by one.  It should be:

	while (--errcnt) {
		...
	}
	if (errcnt == 0)
		return -EWHATEVER;

Or:

	while (errcnt--) {
		...
	}
	if (errcnt == -1)
		return -EWHATEVER;

Also "errcnt" is a bad name, it should be retry_cnt or something (or
maybe it actually is counting errors?).  Also -1 is never a correct
error code, please change all the -1 returns to something better.

> +	/* Buffer size of AUX CH is 16 */
> +	if (count > 16)
> +		return -1;

Just make a define so that you don't need to add comments about why 16
is correct.

	if (count > SIZE_AUX_CH)
		return -EINVAL;

> +	errcnt = 10;
> +	while (errcnt--) {
> +		sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
> +		if (!(val & SP_AUX_EN))
> +			break;
> +		usleep_range(1000, 2000);
> +	}
> +
> +	if (!errcnt) {
> +		dev_err(dev,
> +			"failed to read DP AUX Channel Control Register 2\n");
> +		sp_reset_aux(anx78xx);
> +		return -1;
> +	}

Off by one again.


> +
> +	sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_7_0_REG, SP_I2C_EXTRA_ADDR);
> +	sp_tx_aux_wr(anx78xx, offset);
> +	/* read 16 bytes (MOT = 1) */
> +	sp_tx_aux_rd(anx78xx, 0xf0 | DP_AUX_I2C_MOT | DP_AUX_I2C_READ);
> +
> +	for (i = 0; i < 16; i++) {
> +		errcnt = 10;
> +		while (errcnt--) {
> +			sp_reg_read(anx78xx, TX_P0, SP_BUF_DATA_COUNT_REG,
> +				    &val);
> +			if (val & SP_BUF_DATA_COUNT_MASK)
> +				break;
> +			usleep_range(2000, 4000);
> +		}
> +
> +		if (!errcnt) {
> +			dev_err(dev,
> +				"failed to read DP Buffer Data Count Register\n");
> +			sp_reset_aux(anx78xx);
> +			return -1;
> +		}

And here.

> +	errcnt = 10;
> +	while (errcnt--) {
> +		sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
> +		if (!(val & SP_AUX_EN))
> +			break;
> +		usleep_range(1000, 2000);
> +	}
> +
> +	if (!errcnt) {
> +		dev_err(dev,
> +			"failed to read DP AUX Channel Control Register 2\n");
> +		sp_reset_aux(anx78xx);
> +		return -1;
> +	}

Here.


> +
> +	return 0;
> +}
> +
> +static int sp_edid_block_checksum(const u8 *raw_edid)
> +{
> +	int i;
> +	u8 csum = 0;
> +
> +	for (i = 0; i < EDID_LENGTH; i++)
> +		csum += raw_edid[i];
> +
> +	return csum;
> +}
> +
> +static int sp_tx_edid_read(struct anx78xx *anx78xx)
> +{
> +	struct device *dev = &anx78xx->client->dev;
> +	u8 val, last_block, offset = 0;
> +	u8 buf[16];
> +	int i, j, count;
> +
> +	sp_tx_edid_read_initial(anx78xx);
> +	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
> +	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
> +			SP_AUX_EN | SP_ADDR_ONLY);
> +
> +	if (sp_wait_aux_op_finish(anx78xx))
> +		return -1;
> +
> +	sp_addronly_set(anx78xx, false);
> +
> +	/* Read the number of blocks */
> +	sp_tx_aux_wr(anx78xx, 0x7e);
> +	sp_tx_aux_rd(anx78xx, DP_AUX_I2C_READ);
> +	sp_reg_read(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG, &last_block);
> +	dev_dbg(dev, "last EDID block is %d\n", last_block);
> +
> +	/* FIXME: Why not just cap to 3 if the reported value is >3 */
> +	if (last_block > 3)
> +		last_block = 1;
> +
> +	/* for every block */
> +	for (count = 0; count <= last_block; count++) {
> +		switch (count) {
> +		case 0:
> +		case 1:
> +			for (i = 0; i < 8; i++) {
> +				offset = (i + count * 8) * 16;
> +				if (sp_edid_read(anx78xx, offset, buf))
> +					return -1;
> +				for (j = 0; j < 16; j++)
> +					sp.edid_blocks[offset + j] = buf[j];
> +			}
> +			break;
> +		case 2:
> +		case 3:
> +			offset = (count == 2) ? 0x00 : 0x80;
> +			for (j = 0; j < 8; j++) {
> +				if (sp_seg_edid_read(anx78xx, count / 2,
> +						     offset))
> +					return -1;
> +				offset = offset + 0x10;
> +			}
> +			break;
> +		default:
> +			break;

Is there something which complains if you leave out the default case
statement?  It's not reachable.

> +		}
> +	}
> +
> +	sp_reset_aux(anx78xx);
> +
> +	if (!drm_edid_block_valid(sp.edid_blocks, 0, true, NULL)) {
> +		dev_err(dev, "EDID block is invalid\n");
> +		return -1;
> +	}
> +
> +	sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_REQUEST, 1, &val);
> +	if (val & DP_TEST_LINK_EDID_READ) {
> +		dev_dbg(dev, "EDID test requested\n");
> +		val = sp_edid_block_checksum(sp.edid_blocks);
> +		dev_dbg(dev, "EDID checksum is %d\n", val);
> +		sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_EDID_CHECKSUM, 1,
> +					  &val);
> +		sp.tx_test_edid = true;
> +		val = DP_TEST_EDID_CHECKSUM_WRITE;
> +		sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_RESPONSE, 1, &val);
> +	}
> +
> +	return 0;
> +}
> +
> +static bool sp_check_with_pre_edid(struct anx78xx *anx78xx)
> +{
> +	struct device *dev = &anx78xx->client->dev;
> +	u8 i;
> +	u8 buf[16];
> +	bool ret = false;
> +
> +	sp_tx_edid_read_initial(anx78xx);
> +	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
> +	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, 0x03);
> +
> +	if (sp_wait_aux_op_finish(anx78xx))
> +		goto return_point;
> +
> +	sp_addronly_set(anx78xx, false);
> +
> +	if (sp_edid_read(anx78xx, 0x70, buf))
> +		goto return_point;
> +
> +	for (i = 0; i < 16; i++) {
> +		if (sp.edid_blocks[0x70 + i] != buf[i]) {
> +			dev_dbg(dev, "%s\n",
> +				"different checksum and blocks num\n");
> +			goto return_point;
> +		}
> +	}

Can you just say:

	if (memcmp(&sp.edid_blocks[0x70], buf, 16) != 0) {
		dev_dbg(dev, "different checksum and blocks num\n");
		goto return_point;
	}


> +
> +	if (sp_edid_read(anx78xx, 0x08, buf))
> +		goto return_point;
> +
> +	for (i = 0; i < 16; i++) {
> +		if (sp.edid_blocks[i + 8] != buf[i]) {
> +			dev_dbg(dev, "different edid information\n");
> +			goto return_point;
> +		}
> +	}
> +
> +	ret = true;
> +return_point:
> +	sp_reset_aux(anx78xx);
> +
> +	return ret;
> +}
> +

[ snip ]

> +static bool sp_config_video_output(struct anx78xx *anx78xx)
> +{
> +	struct device *dev = &anx78xx->client->dev;
> +	u8 val;
> +
> +	switch (sp.tx_vo_state) {
> +	default:
> +	case VO_WAIT_VIDEO_STABLE:
> +		sp_reg_read(anx78xx, RX_P0, SP_SYSTEM_STATUS_REG, &val);
> +		if ((val & SP_TMDS_DE_DET) && (val & SP_TMDS_CLOCK_DET)) {
> +			if (sp_tx_bw_lc_sel(anx78xx))
> +				return false;
> +			sp_enable_video_input(anx78xx, false);
> +			sp_hdmi_new_avi_int(anx78xx);
> +			sp_reg_read(anx78xx, RX_P0,
> +				    SP_PACKET_RECEIVING_STATUS_REG, &val);
> +			if (val & SP_VSI_RCVD)
> +				sp_hdmi_new_vsi_int(anx78xx);
> +			sp_enable_video_input(anx78xx, true);
> +			sp.tx_vo_state = VO_WAIT_TX_VIDEO_STABLE;
> +		} else {
> +			dev_dbg(dev, "HDMI input video not stable!\n");
> +			break;
> +		}
> +	/* fallthrough */
> +	case VO_WAIT_TX_VIDEO_STABLE:
> +		/*
> +		 * The flag is write clear and can be latched from last
> +		 * status. So the first read and write is to clear the
> +		 * previous status.
> +		 */
> +		sp_reg_read(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, &val);
> +		sp_reg_write(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, val);
> +
> +		sp_reg_read(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, &val);
> +		if (val & SP_CHA_STA) {
> +			dev_dbg(dev, "stream clock not stable!\n");
> +			break;
> +		} else {


No need for the else statement.  Pull it in one indent level.

> +			/*
> +			 * The flag is write clear and can be latched from
> +			 * last status. So the first read and write is to
> +			 * clear the previous status.
> +			 */
> +			sp_reg_read(anx78xx, TX_P0,
> +				    SP_DP_SYSTEM_CTRL_BASE + 3,
> +				    &val);
> +			sp_reg_write(anx78xx, TX_P0,
> +				     SP_DP_SYSTEM_CTRL_BASE + 3,
> +				     val);
> +
> +			sp_reg_read(anx78xx, TX_P0,
> +				    SP_DP_SYSTEM_CTRL_BASE + 3,
> +				    &val);
> +			if (val & SP_STRM_VALID) {
> +				if (sp.tx_test_lt)
> +					sp.tx_test_lt = false;
> +				sp.tx_vo_state = VO_FINISH;
> +			} else {
> +				dev_err(dev, "video stream not valid!\n");
> +				break;
> +			}
> +		}
> +	/* fallthrough */
> +	case VO_FINISH:
> +		sp_block_power_ctrl(anx78xx, SP_TX_PWR_AUDIO, false);
> +		sp_hdmi_mute_video(anx78xx, false);
> +		sp_video_mute(anx78xx, false);
> +		sp_show_information(anx78xx);
> +		return true;
> +	}
> +
> +	return false;
> +}
> +

[ snip ]

> +static void sp_config_audio(struct anx78xx *anx78xx)
> +{
> +	int i;
> +	u8 val;
> +
> +	sp_block_power_ctrl(anx78xx, SP_TX_PWR_AUDIO, true);
> +
> +	sp_reg_read(anx78xx, TX_P0, SP_DP_MAIN_LINK_BW_SET_REG, &val);
> +	if (val & SP_INITIAL_SLIM_M_AUD_SEL)
> +		if (sp_calculate_audio_m_value(anx78xx))
> +			return;

Combine these:

	if ((val & SP_INITIAL_SLIM_M_AUD_SEL) &&
	    sp_calculate_audio_m_value(anx78xx))
		return;

> +
> +	sp_reg_clear_bits(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL0_REG,
> +			  SP_AUD_INTERFACE_DISABLE);
> +
> +	sp_reg_set_bits(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL2_REG,
> +			SP_M_AUD_ADJUST_ST);
> +
> +	sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
> +	if (val & SP_HDMI_AUD_LAYOUT)
> +		sp_reg_set_bits(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
> +				SP_I2S_CH_NUM_8 | SP_AUDIO_LAYOUT);
> +	else
> +		sp_reg_clear_bits(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
> +				  SP_I2S_CHANNEL_NUM_MASK | SP_AUDIO_LAYOUT);
> +
> +	/* transfer audio channel status from HDMI Rx to Slimport Tx */
> +	for (i = 1; i <= SP_AUD_CH_STATUS_REG_NUM; i++) {
> +		sp_reg_read(anx78xx, RX_P0, SP_AUD_SPDIF_CH_STATUS_BASE + i,
> +			    &val);
> +		sp_reg_write(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + i,
> +			     val);
> +	}

Either this loop is off by one or the loop in sp_hdmi_audio_samplechg_int()
is off by one.  Also just call that function instead of re-implimenting
it here.

> +
> +	/* enable audio */
> +	sp_enable_audio_output(anx78xx, true);
> +}
> +
> +static bool sp_config_audio_output(struct anx78xx *anx78xx)
> +{
> +	u8 val;
> +
> +	switch (sp.tx_ao_state) {
> +	default:
> +	case AO_INIT:
> +	case AO_CTS_RCV_INT:
> +	case AO_AUDIO_RCV_INT:
> +		sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
> +		if (!val & SP_HDMI_MODE) {

This is a precendence error.  It should be:

		if (!(val & SP_HDMI_MODE)) {

> +			sp.tx_ao_state = AO_INIT;
> +			return true;
> +		}
> +		break;

regards,
dan carpenter

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCHv6 5/5] drm: bridge: anx78xx: Add anx78xx driver support by analogix.
@ 2015-12-07  8:09     ` Dan Carpenter
  0 siblings, 0 replies; 17+ messages in thread
From: Dan Carpenter @ 2015-12-07  8:09 UTC (permalink / raw)
  To: Enric Balletbo i Serra
  Cc: mark.rutland, airlied, djkurtz, sjoerd.simons, laurent.pinchart,
	devel, drinkcat, cawa.cheng, nathan.chung, javier, treding,
	devicetree, pawel.moll, ijc+devicetree, robh+dt, dri-devel,
	eddie.huang, cjiao, jb.tsai, gregkh, emil.l.velikov,
	linux-kernel, galak, span

On Fri, Dec 04, 2015 at 09:35:07AM +0100, Enric Balletbo i Serra wrote:
> +static int sp_wait_aux_op_finish(struct anx78xx *anx78xx)
> +{
> +	u8 errcnt;
> +	u8 val;
> +	struct device *dev = &anx78xx->client->dev;
> +
> +	errcnt = 150;
> +	while (errcnt--) {
> +		sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
> +		if (!(val & SP_AUX_EN))
> +			break;
> +		usleep_range(2000, 4000);
> +	}
> +
> +	if (!errcnt) {

This is off by one.  It should be:

	while (--errcnt) {
		...
	}
	if (errcnt == 0)
		return -EWHATEVER;

Or:

	while (errcnt--) {
		...
	}
	if (errcnt == -1)
		return -EWHATEVER;

Also "errcnt" is a bad name, it should be retry_cnt or something (or
maybe it actually is counting errors?).  Also -1 is never a correct
error code, please change all the -1 returns to something better.

> +	/* Buffer size of AUX CH is 16 */
> +	if (count > 16)
> +		return -1;

Just make a define so that you don't need to add comments about why 16
is correct.

	if (count > SIZE_AUX_CH)
		return -EINVAL;

> +	errcnt = 10;
> +	while (errcnt--) {
> +		sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
> +		if (!(val & SP_AUX_EN))
> +			break;
> +		usleep_range(1000, 2000);
> +	}
> +
> +	if (!errcnt) {
> +		dev_err(dev,
> +			"failed to read DP AUX Channel Control Register 2\n");
> +		sp_reset_aux(anx78xx);
> +		return -1;
> +	}

Off by one again.


> +
> +	sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_7_0_REG, SP_I2C_EXTRA_ADDR);
> +	sp_tx_aux_wr(anx78xx, offset);
> +	/* read 16 bytes (MOT = 1) */
> +	sp_tx_aux_rd(anx78xx, 0xf0 | DP_AUX_I2C_MOT | DP_AUX_I2C_READ);
> +
> +	for (i = 0; i < 16; i++) {
> +		errcnt = 10;
> +		while (errcnt--) {
> +			sp_reg_read(anx78xx, TX_P0, SP_BUF_DATA_COUNT_REG,
> +				    &val);
> +			if (val & SP_BUF_DATA_COUNT_MASK)
> +				break;
> +			usleep_range(2000, 4000);
> +		}
> +
> +		if (!errcnt) {
> +			dev_err(dev,
> +				"failed to read DP Buffer Data Count Register\n");
> +			sp_reset_aux(anx78xx);
> +			return -1;
> +		}

And here.

> +	errcnt = 10;
> +	while (errcnt--) {
> +		sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
> +		if (!(val & SP_AUX_EN))
> +			break;
> +		usleep_range(1000, 2000);
> +	}
> +
> +	if (!errcnt) {
> +		dev_err(dev,
> +			"failed to read DP AUX Channel Control Register 2\n");
> +		sp_reset_aux(anx78xx);
> +		return -1;
> +	}

Here.


> +
> +	return 0;
> +}
> +
> +static int sp_edid_block_checksum(const u8 *raw_edid)
> +{
> +	int i;
> +	u8 csum = 0;
> +
> +	for (i = 0; i < EDID_LENGTH; i++)
> +		csum += raw_edid[i];
> +
> +	return csum;
> +}
> +
> +static int sp_tx_edid_read(struct anx78xx *anx78xx)
> +{
> +	struct device *dev = &anx78xx->client->dev;
> +	u8 val, last_block, offset = 0;
> +	u8 buf[16];
> +	int i, j, count;
> +
> +	sp_tx_edid_read_initial(anx78xx);
> +	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
> +	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
> +			SP_AUX_EN | SP_ADDR_ONLY);
> +
> +	if (sp_wait_aux_op_finish(anx78xx))
> +		return -1;
> +
> +	sp_addronly_set(anx78xx, false);
> +
> +	/* Read the number of blocks */
> +	sp_tx_aux_wr(anx78xx, 0x7e);
> +	sp_tx_aux_rd(anx78xx, DP_AUX_I2C_READ);
> +	sp_reg_read(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG, &last_block);
> +	dev_dbg(dev, "last EDID block is %d\n", last_block);
> +
> +	/* FIXME: Why not just cap to 3 if the reported value is >3 */
> +	if (last_block > 3)
> +		last_block = 1;
> +
> +	/* for every block */
> +	for (count = 0; count <= last_block; count++) {
> +		switch (count) {
> +		case 0:
> +		case 1:
> +			for (i = 0; i < 8; i++) {
> +				offset = (i + count * 8) * 16;
> +				if (sp_edid_read(anx78xx, offset, buf))
> +					return -1;
> +				for (j = 0; j < 16; j++)
> +					sp.edid_blocks[offset + j] = buf[j];
> +			}
> +			break;
> +		case 2:
> +		case 3:
> +			offset = (count == 2) ? 0x00 : 0x80;
> +			for (j = 0; j < 8; j++) {
> +				if (sp_seg_edid_read(anx78xx, count / 2,
> +						     offset))
> +					return -1;
> +				offset = offset + 0x10;
> +			}
> +			break;
> +		default:
> +			break;

Is there something which complains if you leave out the default case
statement?  It's not reachable.

> +		}
> +	}
> +
> +	sp_reset_aux(anx78xx);
> +
> +	if (!drm_edid_block_valid(sp.edid_blocks, 0, true, NULL)) {
> +		dev_err(dev, "EDID block is invalid\n");
> +		return -1;
> +	}
> +
> +	sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_REQUEST, 1, &val);
> +	if (val & DP_TEST_LINK_EDID_READ) {
> +		dev_dbg(dev, "EDID test requested\n");
> +		val = sp_edid_block_checksum(sp.edid_blocks);
> +		dev_dbg(dev, "EDID checksum is %d\n", val);
> +		sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_EDID_CHECKSUM, 1,
> +					  &val);
> +		sp.tx_test_edid = true;
> +		val = DP_TEST_EDID_CHECKSUM_WRITE;
> +		sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_RESPONSE, 1, &val);
> +	}
> +
> +	return 0;
> +}
> +
> +static bool sp_check_with_pre_edid(struct anx78xx *anx78xx)
> +{
> +	struct device *dev = &anx78xx->client->dev;
> +	u8 i;
> +	u8 buf[16];
> +	bool ret = false;
> +
> +	sp_tx_edid_read_initial(anx78xx);
> +	sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
> +	sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, 0x03);
> +
> +	if (sp_wait_aux_op_finish(anx78xx))
> +		goto return_point;
> +
> +	sp_addronly_set(anx78xx, false);
> +
> +	if (sp_edid_read(anx78xx, 0x70, buf))
> +		goto return_point;
> +
> +	for (i = 0; i < 16; i++) {
> +		if (sp.edid_blocks[0x70 + i] != buf[i]) {
> +			dev_dbg(dev, "%s\n",
> +				"different checksum and blocks num\n");
> +			goto return_point;
> +		}
> +	}

Can you just say:

	if (memcmp(&sp.edid_blocks[0x70], buf, 16) != 0) {
		dev_dbg(dev, "different checksum and blocks num\n");
		goto return_point;
	}


> +
> +	if (sp_edid_read(anx78xx, 0x08, buf))
> +		goto return_point;
> +
> +	for (i = 0; i < 16; i++) {
> +		if (sp.edid_blocks[i + 8] != buf[i]) {
> +			dev_dbg(dev, "different edid information\n");
> +			goto return_point;
> +		}
> +	}
> +
> +	ret = true;
> +return_point:
> +	sp_reset_aux(anx78xx);
> +
> +	return ret;
> +}
> +

[ snip ]

> +static bool sp_config_video_output(struct anx78xx *anx78xx)
> +{
> +	struct device *dev = &anx78xx->client->dev;
> +	u8 val;
> +
> +	switch (sp.tx_vo_state) {
> +	default:
> +	case VO_WAIT_VIDEO_STABLE:
> +		sp_reg_read(anx78xx, RX_P0, SP_SYSTEM_STATUS_REG, &val);
> +		if ((val & SP_TMDS_DE_DET) && (val & SP_TMDS_CLOCK_DET)) {
> +			if (sp_tx_bw_lc_sel(anx78xx))
> +				return false;
> +			sp_enable_video_input(anx78xx, false);
> +			sp_hdmi_new_avi_int(anx78xx);
> +			sp_reg_read(anx78xx, RX_P0,
> +				    SP_PACKET_RECEIVING_STATUS_REG, &val);
> +			if (val & SP_VSI_RCVD)
> +				sp_hdmi_new_vsi_int(anx78xx);
> +			sp_enable_video_input(anx78xx, true);
> +			sp.tx_vo_state = VO_WAIT_TX_VIDEO_STABLE;
> +		} else {
> +			dev_dbg(dev, "HDMI input video not stable!\n");
> +			break;
> +		}
> +	/* fallthrough */
> +	case VO_WAIT_TX_VIDEO_STABLE:
> +		/*
> +		 * The flag is write clear and can be latched from last
> +		 * status. So the first read and write is to clear the
> +		 * previous status.
> +		 */
> +		sp_reg_read(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, &val);
> +		sp_reg_write(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, val);
> +
> +		sp_reg_read(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, &val);
> +		if (val & SP_CHA_STA) {
> +			dev_dbg(dev, "stream clock not stable!\n");
> +			break;
> +		} else {


No need for the else statement.  Pull it in one indent level.

> +			/*
> +			 * The flag is write clear and can be latched from
> +			 * last status. So the first read and write is to
> +			 * clear the previous status.
> +			 */
> +			sp_reg_read(anx78xx, TX_P0,
> +				    SP_DP_SYSTEM_CTRL_BASE + 3,
> +				    &val);
> +			sp_reg_write(anx78xx, TX_P0,
> +				     SP_DP_SYSTEM_CTRL_BASE + 3,
> +				     val);
> +
> +			sp_reg_read(anx78xx, TX_P0,
> +				    SP_DP_SYSTEM_CTRL_BASE + 3,
> +				    &val);
> +			if (val & SP_STRM_VALID) {
> +				if (sp.tx_test_lt)
> +					sp.tx_test_lt = false;
> +				sp.tx_vo_state = VO_FINISH;
> +			} else {
> +				dev_err(dev, "video stream not valid!\n");
> +				break;
> +			}
> +		}
> +	/* fallthrough */
> +	case VO_FINISH:
> +		sp_block_power_ctrl(anx78xx, SP_TX_PWR_AUDIO, false);
> +		sp_hdmi_mute_video(anx78xx, false);
> +		sp_video_mute(anx78xx, false);
> +		sp_show_information(anx78xx);
> +		return true;
> +	}
> +
> +	return false;
> +}
> +

[ snip ]

> +static void sp_config_audio(struct anx78xx *anx78xx)
> +{
> +	int i;
> +	u8 val;
> +
> +	sp_block_power_ctrl(anx78xx, SP_TX_PWR_AUDIO, true);
> +
> +	sp_reg_read(anx78xx, TX_P0, SP_DP_MAIN_LINK_BW_SET_REG, &val);
> +	if (val & SP_INITIAL_SLIM_M_AUD_SEL)
> +		if (sp_calculate_audio_m_value(anx78xx))
> +			return;

Combine these:

	if ((val & SP_INITIAL_SLIM_M_AUD_SEL) &&
	    sp_calculate_audio_m_value(anx78xx))
		return;

> +
> +	sp_reg_clear_bits(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL0_REG,
> +			  SP_AUD_INTERFACE_DISABLE);
> +
> +	sp_reg_set_bits(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL2_REG,
> +			SP_M_AUD_ADJUST_ST);
> +
> +	sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
> +	if (val & SP_HDMI_AUD_LAYOUT)
> +		sp_reg_set_bits(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
> +				SP_I2S_CH_NUM_8 | SP_AUDIO_LAYOUT);
> +	else
> +		sp_reg_clear_bits(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
> +				  SP_I2S_CHANNEL_NUM_MASK | SP_AUDIO_LAYOUT);
> +
> +	/* transfer audio channel status from HDMI Rx to Slimport Tx */
> +	for (i = 1; i <= SP_AUD_CH_STATUS_REG_NUM; i++) {
> +		sp_reg_read(anx78xx, RX_P0, SP_AUD_SPDIF_CH_STATUS_BASE + i,
> +			    &val);
> +		sp_reg_write(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + i,
> +			     val);
> +	}

Either this loop is off by one or the loop in sp_hdmi_audio_samplechg_int()
is off by one.  Also just call that function instead of re-implimenting
it here.

> +
> +	/* enable audio */
> +	sp_enable_audio_output(anx78xx, true);
> +}
> +
> +static bool sp_config_audio_output(struct anx78xx *anx78xx)
> +{
> +	u8 val;
> +
> +	switch (sp.tx_ao_state) {
> +	default:
> +	case AO_INIT:
> +	case AO_CTS_RCV_INT:
> +	case AO_AUDIO_RCV_INT:
> +		sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
> +		if (!val & SP_HDMI_MODE) {

This is a precendence error.  It should be:

		if (!(val & SP_HDMI_MODE)) {

> +			sp.tx_ao_state = AO_INIT;
> +			return true;
> +		}
> +		break;

regards,
dan carpenter

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCHv6 5/5] drm: bridge: anx78xx: Add anx78xx driver support by analogix.
  2015-12-07  8:09     ` Dan Carpenter
@ 2015-12-09 11:55       ` Enric Balletbo Serra
  -1 siblings, 0 replies; 17+ messages in thread
From: Enric Balletbo Serra @ 2015-12-09 11:55 UTC (permalink / raw)
  To: Dan Carpenter
  Cc: devicetree, linux-kernel, dri-devel, devel, Thierry Reding,
	Mark Rutland, drinkcat, Laurent Pinchart, Pawel Moll,
	Ian Campbell, airlied, Greg Kroah-Hartman, Emil Velikov,
	cawa cheng, Daniel Kurtz, jb.tsai, Sjoerd Simons, Rob Herring,
	span, Kumar Gala, Javier Martinez Canillas, eddie.huang, cjiao,
	Nathan Chung

Hi Dan,

Many thanks for your comments.

2015-12-07 9:09 GMT+01:00 Dan Carpenter <dan.carpenter@oracle.com>:
> On Fri, Dec 04, 2015 at 09:35:07AM +0100, Enric Balletbo i Serra wrote:
>> +static int sp_wait_aux_op_finish(struct anx78xx *anx78xx)
>> +{
>> +     u8 errcnt;
>> +     u8 val;
>> +     struct device *dev = &anx78xx->client->dev;
>> +
>> +     errcnt = 150;
>> +     while (errcnt--) {
>> +             sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
>> +             if (!(val & SP_AUX_EN))
>> +                     break;
>> +             usleep_range(2000, 4000);
>> +     }
>> +
>> +     if (!errcnt) {
>
> This is off by one.  It should be:
>
>         while (--errcnt) {
>                 ...
>         }
>         if (errcnt == 0)
>                 return -EWHATEVER;
>
> Or:
>
>         while (errcnt--) {
>                 ...
>         }
>         if (errcnt == -1)
>                 return -EWHATEVER;
>
> Also "errcnt" is a bad name, it should be retry_cnt or something (or
> maybe it actually is counting errors?).  Also -1 is never a correct
> error code, please change all the -1 returns to something better.
>

Ok, I renamed to retry_cnt and changed all the -1 values to something better.


>> +     /* Buffer size of AUX CH is 16 */
>> +     if (count > 16)
>> +             return -1;
>
> Just make a define so that you don't need to add comments about why 16
> is correct.
>
>         if (count > SIZE_AUX_CH)
>                 return -EINVAL;
>

Added a define

>> +     errcnt = 10;
>> +     while (errcnt--) {
>> +             sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
>> +             if (!(val & SP_AUX_EN))
>> +                     break;
>> +             usleep_range(1000, 2000);
>> +     }
>> +
>> +     if (!errcnt) {
>> +             dev_err(dev,
>> +                     "failed to read DP AUX Channel Control Register 2\n");
>> +             sp_reset_aux(anx78xx);
>> +             return -1;
>> +     }
>
> Off by one again.
>

Ack

>
>> +
>> +     sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_7_0_REG, SP_I2C_EXTRA_ADDR);
>> +     sp_tx_aux_wr(anx78xx, offset);
>> +     /* read 16 bytes (MOT = 1) */
>> +     sp_tx_aux_rd(anx78xx, 0xf0 | DP_AUX_I2C_MOT | DP_AUX_I2C_READ);
>> +
>> +     for (i = 0; i < 16; i++) {
>> +             errcnt = 10;
>> +             while (errcnt--) {
>> +                     sp_reg_read(anx78xx, TX_P0, SP_BUF_DATA_COUNT_REG,
>> +                                 &val);
>> +                     if (val & SP_BUF_DATA_COUNT_MASK)
>> +                             break;
>> +                     usleep_range(2000, 4000);
>> +             }
>> +
>> +             if (!errcnt) {
>> +                     dev_err(dev,
>> +                             "failed to read DP Buffer Data Count Register\n");
>> +                     sp_reset_aux(anx78xx);
>> +                     return -1;
>> +             }
>
> And here.
>

Ack

>> +     errcnt = 10;
>> +     while (errcnt--) {
>> +             sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
>> +             if (!(val & SP_AUX_EN))
>> +                     break;
>> +             usleep_range(1000, 2000);
>> +     }
>> +
>> +     if (!errcnt) {
>> +             dev_err(dev,
>> +                     "failed to read DP AUX Channel Control Register 2\n");
>> +             sp_reset_aux(anx78xx);
>> +             return -1;
>> +     }
>
> Here.
>

Ack

>
>> +
>> +     return 0;
>> +}
>> +
>> +static int sp_edid_block_checksum(const u8 *raw_edid)
>> +{
>> +     int i;
>> +     u8 csum = 0;
>> +
>> +     for (i = 0; i < EDID_LENGTH; i++)
>> +             csum += raw_edid[i];
>> +
>> +     return csum;
>> +}
>> +
>> +static int sp_tx_edid_read(struct anx78xx *anx78xx)
>> +{
>> +     struct device *dev = &anx78xx->client->dev;
>> +     u8 val, last_block, offset = 0;
>> +     u8 buf[16];
>> +     int i, j, count;
>> +
>> +     sp_tx_edid_read_initial(anx78xx);
>> +     sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
>> +     sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
>> +                     SP_AUX_EN | SP_ADDR_ONLY);
>> +
>> +     if (sp_wait_aux_op_finish(anx78xx))
>> +             return -1;
>> +
>> +     sp_addronly_set(anx78xx, false);
>> +
>> +     /* Read the number of blocks */
>> +     sp_tx_aux_wr(anx78xx, 0x7e);
>> +     sp_tx_aux_rd(anx78xx, DP_AUX_I2C_READ);
>> +     sp_reg_read(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG, &last_block);
>> +     dev_dbg(dev, "last EDID block is %d\n", last_block);
>> +
>> +     /* FIXME: Why not just cap to 3 if the reported value is >3 */
>> +     if (last_block > 3)
>> +             last_block = 1;
>> +
>> +     /* for every block */
>> +     for (count = 0; count <= last_block; count++) {
>> +             switch (count) {
>> +             case 0:
>> +             case 1:
>> +                     for (i = 0; i < 8; i++) {
>> +                             offset = (i + count * 8) * 16;
>> +                             if (sp_edid_read(anx78xx, offset, buf))
>> +                                     return -1;
>> +                             for (j = 0; j < 16; j++)
>> +                                     sp.edid_blocks[offset + j] = buf[j];
>> +                     }
>> +                     break;
>> +             case 2:
>> +             case 3:
>> +                     offset = (count == 2) ? 0x00 : 0x80;
>> +                     for (j = 0; j < 8; j++) {
>> +                             if (sp_seg_edid_read(anx78xx, count / 2,
>> +                                                  offset))
>> +                                     return -1;
>> +                             offset = offset + 0x10;
>> +                     }
>> +                     break;
>> +             default:
>> +                     break;
>
> Is there something which complains if you leave out the default case
> statement?  It's not reachable.
>

I left out the default case as is not reachable.

>> +             }
>> +     }
>> +
>> +     sp_reset_aux(anx78xx);
>> +
>> +     if (!drm_edid_block_valid(sp.edid_blocks, 0, true, NULL)) {
>> +             dev_err(dev, "EDID block is invalid\n");
>> +             return -1;
>> +     }
>> +
>> +     sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_REQUEST, 1, &val);
>> +     if (val & DP_TEST_LINK_EDID_READ) {
>> +             dev_dbg(dev, "EDID test requested\n");
>> +             val = sp_edid_block_checksum(sp.edid_blocks);
>> +             dev_dbg(dev, "EDID checksum is %d\n", val);
>> +             sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_EDID_CHECKSUM, 1,
>> +                                       &val);
>> +             sp.tx_test_edid = true;
>> +             val = DP_TEST_EDID_CHECKSUM_WRITE;
>> +             sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_RESPONSE, 1, &val);
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static bool sp_check_with_pre_edid(struct anx78xx *anx78xx)
>> +{
>> +     struct device *dev = &anx78xx->client->dev;
>> +     u8 i;
>> +     u8 buf[16];
>> +     bool ret = false;
>> +
>> +     sp_tx_edid_read_initial(anx78xx);
>> +     sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
>> +     sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, 0x03);
>> +
>> +     if (sp_wait_aux_op_finish(anx78xx))
>> +             goto return_point;
>> +
>> +     sp_addronly_set(anx78xx, false);
>> +
>> +     if (sp_edid_read(anx78xx, 0x70, buf))
>> +             goto return_point;
>> +
>> +     for (i = 0; i < 16; i++) {
>> +             if (sp.edid_blocks[0x70 + i] != buf[i]) {
>> +                     dev_dbg(dev, "%s\n",
>> +                             "different checksum and blocks num\n");
>> +                     goto return_point;
>> +             }
>> +     }
>
> Can you just say:
>
>         if (memcmp(&sp.edid_blocks[0x70], buf, 16) != 0) {
>                 dev_dbg(dev, "different checksum and blocks num\n");
>                 goto return_point;
>         }
>

I will change

>
>> +
>> +     if (sp_edid_read(anx78xx, 0x08, buf))
>> +             goto return_point;
>> +
>> +     for (i = 0; i < 16; i++) {
>> +             if (sp.edid_blocks[i + 8] != buf[i]) {
>> +                     dev_dbg(dev, "different edid information\n");
>> +                     goto return_point;
>> +             }
>> +     }
>> +
>> +     ret = true;
>> +return_point:
>> +     sp_reset_aux(anx78xx);
>> +
>> +     return ret;
>> +}
>> +
>
> [ snip ]
>
>> +static bool sp_config_video_output(struct anx78xx *anx78xx)
>> +{
>> +     struct device *dev = &anx78xx->client->dev;
>> +     u8 val;
>> +
>> +     switch (sp.tx_vo_state) {
>> +     default:
>> +     case VO_WAIT_VIDEO_STABLE:
>> +             sp_reg_read(anx78xx, RX_P0, SP_SYSTEM_STATUS_REG, &val);
>> +             if ((val & SP_TMDS_DE_DET) && (val & SP_TMDS_CLOCK_DET)) {
>> +                     if (sp_tx_bw_lc_sel(anx78xx))
>> +                             return false;
>> +                     sp_enable_video_input(anx78xx, false);
>> +                     sp_hdmi_new_avi_int(anx78xx);
>> +                     sp_reg_read(anx78xx, RX_P0,
>> +                                 SP_PACKET_RECEIVING_STATUS_REG, &val);
>> +                     if (val & SP_VSI_RCVD)
>> +                             sp_hdmi_new_vsi_int(anx78xx);
>> +                     sp_enable_video_input(anx78xx, true);
>> +                     sp.tx_vo_state = VO_WAIT_TX_VIDEO_STABLE;
>> +             } else {
>> +                     dev_dbg(dev, "HDMI input video not stable!\n");
>> +                     break;
>> +             }
>> +     /* fallthrough */
>> +     case VO_WAIT_TX_VIDEO_STABLE:
>> +             /*
>> +              * The flag is write clear and can be latched from last
>> +              * status. So the first read and write is to clear the
>> +              * previous status.
>> +              */
>> +             sp_reg_read(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, &val);
>> +             sp_reg_write(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, val);
>> +
>> +             sp_reg_read(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, &val);
>> +             if (val & SP_CHA_STA) {
>> +                     dev_dbg(dev, "stream clock not stable!\n");
>> +                     break;
>> +             } else {
>
>
> No need for the else statement.  Pull it in one indent level.
>

Ack

>> +                     /*
>> +                      * The flag is write clear and can be latched from
>> +                      * last status. So the first read and write is to
>> +                      * clear the previous status.
>> +                      */
>> +                     sp_reg_read(anx78xx, TX_P0,
>> +                                 SP_DP_SYSTEM_CTRL_BASE + 3,
>> +                                 &val);
>> +                     sp_reg_write(anx78xx, TX_P0,
>> +                                  SP_DP_SYSTEM_CTRL_BASE + 3,
>> +                                  val);
>> +
>> +                     sp_reg_read(anx78xx, TX_P0,
>> +                                 SP_DP_SYSTEM_CTRL_BASE + 3,
>> +                                 &val);
>> +                     if (val & SP_STRM_VALID) {
>> +                             if (sp.tx_test_lt)
>> +                                     sp.tx_test_lt = false;
>> +                             sp.tx_vo_state = VO_FINISH;
>> +                     } else {
>> +                             dev_err(dev, "video stream not valid!\n");
>> +                             break;
>> +                     }
>> +             }
>> +     /* fallthrough */
>> +     case VO_FINISH:
>> +             sp_block_power_ctrl(anx78xx, SP_TX_PWR_AUDIO, false);
>> +             sp_hdmi_mute_video(anx78xx, false);
>> +             sp_video_mute(anx78xx, false);
>> +             sp_show_information(anx78xx);
>> +             return true;
>> +     }
Changed
>> +
>> +     return false;
>> +}
>> +
>
> [ snip ]
>
>> +static void sp_config_audio(struct anx78xx *anx78xx)
>> +{
>> +     int i;
>> +     u8 val;
>> +
>> +     sp_block_power_ctrl(anx78xx, SP_TX_PWR_AUDIO, true);
>> +
>> +     sp_reg_read(anx78xx, TX_P0, SP_DP_MAIN_LINK_BW_SET_REG, &val);
>> +     if (val & SP_INITIAL_SLIM_M_AUD_SEL)
>> +             if (sp_calculate_audio_m_value(anx78xx))
>> +                     return;
>
> Combine these:
>
>         if ((val & SP_INITIAL_SLIM_M_AUD_SEL) &&
>             sp_calculate_audio_m_value(anx78xx))
>                 return;
>

Ok

>> +
>> +     sp_reg_clear_bits(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL0_REG,
>> +                       SP_AUD_INTERFACE_DISABLE);
>> +
>> +     sp_reg_set_bits(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL2_REG,
>> +                     SP_M_AUD_ADJUST_ST);
>> +
>> +     sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
>> +     if (val & SP_HDMI_AUD_LAYOUT)
>> +             sp_reg_set_bits(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
>> +                             SP_I2S_CH_NUM_8 | SP_AUDIO_LAYOUT);
>> +     else
>> +             sp_reg_clear_bits(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
>> +                               SP_I2S_CHANNEL_NUM_MASK | SP_AUDIO_LAYOUT);
>> +
>> +     /* transfer audio channel status from HDMI Rx to Slimport Tx */
>> +     for (i = 1; i <= SP_AUD_CH_STATUS_REG_NUM; i++) {
>> +             sp_reg_read(anx78xx, RX_P0, SP_AUD_SPDIF_CH_STATUS_BASE + i,
>> +                         &val);
>> +             sp_reg_write(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + i,
>> +                          val);
>> +     }
>
> Either this loop is off by one or the loop in sp_hdmi_audio_samplechg_int()
> is off by one.  Also just call that function instead of re-implimenting
> it here.
>

Right, I'll fix that

>> +
>> +     /* enable audio */
>> +     sp_enable_audio_output(anx78xx, true);
>> +}
>> +
>> +static bool sp_config_audio_output(struct anx78xx *anx78xx)
>> +{
>> +     u8 val;
>> +
>> +     switch (sp.tx_ao_state) {
>> +     default:
>> +     case AO_INIT:
>> +     case AO_CTS_RCV_INT:
>> +     case AO_AUDIO_RCV_INT:
>> +             sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
>> +             if (!val & SP_HDMI_MODE) {
>
> This is a precendence error.  It should be:
>

Ack

>                 if (!(val & SP_HDMI_MODE)) {
>
>> +                     sp.tx_ao_state = AO_INIT;
>> +                     return true;
>> +             }
>> +             break;
>
> regards,
> dan carpenter

I'll wait a bit more to see if anyone else does more comments and then
I'll send another version with the changes you suggested. Thanks.

Regards,
   Enric

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCHv6 5/5] drm: bridge: anx78xx: Add anx78xx driver support by analogix.
@ 2015-12-09 11:55       ` Enric Balletbo Serra
  0 siblings, 0 replies; 17+ messages in thread
From: Enric Balletbo Serra @ 2015-12-09 11:55 UTC (permalink / raw)
  To: Dan Carpenter
  Cc: Mark Rutland, Sjoerd Simons, Laurent Pinchart, devel, drinkcat,
	cawa cheng, Nathan Chung, Javier Martinez Canillas,
	Thierry Reding, devicetree, Pawel Moll, Ian Campbell,
	Rob Herring, dri-devel, eddie.huang, cjiao, jb.tsai,
	Greg Kroah-Hartman, Emil Velikov, linux-kernel, Kumar Gala, span

Hi Dan,

Many thanks for your comments.

2015-12-07 9:09 GMT+01:00 Dan Carpenter <dan.carpenter@oracle.com>:
> On Fri, Dec 04, 2015 at 09:35:07AM +0100, Enric Balletbo i Serra wrote:
>> +static int sp_wait_aux_op_finish(struct anx78xx *anx78xx)
>> +{
>> +     u8 errcnt;
>> +     u8 val;
>> +     struct device *dev = &anx78xx->client->dev;
>> +
>> +     errcnt = 150;
>> +     while (errcnt--) {
>> +             sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
>> +             if (!(val & SP_AUX_EN))
>> +                     break;
>> +             usleep_range(2000, 4000);
>> +     }
>> +
>> +     if (!errcnt) {
>
> This is off by one.  It should be:
>
>         while (--errcnt) {
>                 ...
>         }
>         if (errcnt == 0)
>                 return -EWHATEVER;
>
> Or:
>
>         while (errcnt--) {
>                 ...
>         }
>         if (errcnt == -1)
>                 return -EWHATEVER;
>
> Also "errcnt" is a bad name, it should be retry_cnt or something (or
> maybe it actually is counting errors?).  Also -1 is never a correct
> error code, please change all the -1 returns to something better.
>

Ok, I renamed to retry_cnt and changed all the -1 values to something better.


>> +     /* Buffer size of AUX CH is 16 */
>> +     if (count > 16)
>> +             return -1;
>
> Just make a define so that you don't need to add comments about why 16
> is correct.
>
>         if (count > SIZE_AUX_CH)
>                 return -EINVAL;
>

Added a define

>> +     errcnt = 10;
>> +     while (errcnt--) {
>> +             sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
>> +             if (!(val & SP_AUX_EN))
>> +                     break;
>> +             usleep_range(1000, 2000);
>> +     }
>> +
>> +     if (!errcnt) {
>> +             dev_err(dev,
>> +                     "failed to read DP AUX Channel Control Register 2\n");
>> +             sp_reset_aux(anx78xx);
>> +             return -1;
>> +     }
>
> Off by one again.
>

Ack

>
>> +
>> +     sp_reg_write(anx78xx, TX_P0, SP_AUX_ADDR_7_0_REG, SP_I2C_EXTRA_ADDR);
>> +     sp_tx_aux_wr(anx78xx, offset);
>> +     /* read 16 bytes (MOT = 1) */
>> +     sp_tx_aux_rd(anx78xx, 0xf0 | DP_AUX_I2C_MOT | DP_AUX_I2C_READ);
>> +
>> +     for (i = 0; i < 16; i++) {
>> +             errcnt = 10;
>> +             while (errcnt--) {
>> +                     sp_reg_read(anx78xx, TX_P0, SP_BUF_DATA_COUNT_REG,
>> +                                 &val);
>> +                     if (val & SP_BUF_DATA_COUNT_MASK)
>> +                             break;
>> +                     usleep_range(2000, 4000);
>> +             }
>> +
>> +             if (!errcnt) {
>> +                     dev_err(dev,
>> +                             "failed to read DP Buffer Data Count Register\n");
>> +                     sp_reset_aux(anx78xx);
>> +                     return -1;
>> +             }
>
> And here.
>

Ack

>> +     errcnt = 10;
>> +     while (errcnt--) {
>> +             sp_reg_read(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, &val);
>> +             if (!(val & SP_AUX_EN))
>> +                     break;
>> +             usleep_range(1000, 2000);
>> +     }
>> +
>> +     if (!errcnt) {
>> +             dev_err(dev,
>> +                     "failed to read DP AUX Channel Control Register 2\n");
>> +             sp_reset_aux(anx78xx);
>> +             return -1;
>> +     }
>
> Here.
>

Ack

>
>> +
>> +     return 0;
>> +}
>> +
>> +static int sp_edid_block_checksum(const u8 *raw_edid)
>> +{
>> +     int i;
>> +     u8 csum = 0;
>> +
>> +     for (i = 0; i < EDID_LENGTH; i++)
>> +             csum += raw_edid[i];
>> +
>> +     return csum;
>> +}
>> +
>> +static int sp_tx_edid_read(struct anx78xx *anx78xx)
>> +{
>> +     struct device *dev = &anx78xx->client->dev;
>> +     u8 val, last_block, offset = 0;
>> +     u8 buf[16];
>> +     int i, j, count;
>> +
>> +     sp_tx_edid_read_initial(anx78xx);
>> +     sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
>> +     sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG,
>> +                     SP_AUX_EN | SP_ADDR_ONLY);
>> +
>> +     if (sp_wait_aux_op_finish(anx78xx))
>> +             return -1;
>> +
>> +     sp_addronly_set(anx78xx, false);
>> +
>> +     /* Read the number of blocks */
>> +     sp_tx_aux_wr(anx78xx, 0x7e);
>> +     sp_tx_aux_rd(anx78xx, DP_AUX_I2C_READ);
>> +     sp_reg_read(anx78xx, TX_P0, SP_DP_BUF_DATA0_REG, &last_block);
>> +     dev_dbg(dev, "last EDID block is %d\n", last_block);
>> +
>> +     /* FIXME: Why not just cap to 3 if the reported value is >3 */
>> +     if (last_block > 3)
>> +             last_block = 1;
>> +
>> +     /* for every block */
>> +     for (count = 0; count <= last_block; count++) {
>> +             switch (count) {
>> +             case 0:
>> +             case 1:
>> +                     for (i = 0; i < 8; i++) {
>> +                             offset = (i + count * 8) * 16;
>> +                             if (sp_edid_read(anx78xx, offset, buf))
>> +                                     return -1;
>> +                             for (j = 0; j < 16; j++)
>> +                                     sp.edid_blocks[offset + j] = buf[j];
>> +                     }
>> +                     break;
>> +             case 2:
>> +             case 3:
>> +                     offset = (count == 2) ? 0x00 : 0x80;
>> +                     for (j = 0; j < 8; j++) {
>> +                             if (sp_seg_edid_read(anx78xx, count / 2,
>> +                                                  offset))
>> +                                     return -1;
>> +                             offset = offset + 0x10;
>> +                     }
>> +                     break;
>> +             default:
>> +                     break;
>
> Is there something which complains if you leave out the default case
> statement?  It's not reachable.
>

I left out the default case as is not reachable.

>> +             }
>> +     }
>> +
>> +     sp_reset_aux(anx78xx);
>> +
>> +     if (!drm_edid_block_valid(sp.edid_blocks, 0, true, NULL)) {
>> +             dev_err(dev, "EDID block is invalid\n");
>> +             return -1;
>> +     }
>> +
>> +     sp_dp_read_bytes_from_dpcd(anx78xx, DP_TEST_REQUEST, 1, &val);
>> +     if (val & DP_TEST_LINK_EDID_READ) {
>> +             dev_dbg(dev, "EDID test requested\n");
>> +             val = sp_edid_block_checksum(sp.edid_blocks);
>> +             dev_dbg(dev, "EDID checksum is %d\n", val);
>> +             sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_EDID_CHECKSUM, 1,
>> +                                       &val);
>> +             sp.tx_test_edid = true;
>> +             val = DP_TEST_EDID_CHECKSUM_WRITE;
>> +             sp_dp_write_bytes_to_dpcd(anx78xx, DP_TEST_RESPONSE, 1, &val);
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static bool sp_check_with_pre_edid(struct anx78xx *anx78xx)
>> +{
>> +     struct device *dev = &anx78xx->client->dev;
>> +     u8 i;
>> +     u8 buf[16];
>> +     bool ret = false;
>> +
>> +     sp_tx_edid_read_initial(anx78xx);
>> +     sp_reg_write(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL1_REG, 0x04);
>> +     sp_reg_set_bits(anx78xx, TX_P0, SP_DP_AUX_CH_CTRL2_REG, 0x03);
>> +
>> +     if (sp_wait_aux_op_finish(anx78xx))
>> +             goto return_point;
>> +
>> +     sp_addronly_set(anx78xx, false);
>> +
>> +     if (sp_edid_read(anx78xx, 0x70, buf))
>> +             goto return_point;
>> +
>> +     for (i = 0; i < 16; i++) {
>> +             if (sp.edid_blocks[0x70 + i] != buf[i]) {
>> +                     dev_dbg(dev, "%s\n",
>> +                             "different checksum and blocks num\n");
>> +                     goto return_point;
>> +             }
>> +     }
>
> Can you just say:
>
>         if (memcmp(&sp.edid_blocks[0x70], buf, 16) != 0) {
>                 dev_dbg(dev, "different checksum and blocks num\n");
>                 goto return_point;
>         }
>

I will change

>
>> +
>> +     if (sp_edid_read(anx78xx, 0x08, buf))
>> +             goto return_point;
>> +
>> +     for (i = 0; i < 16; i++) {
>> +             if (sp.edid_blocks[i + 8] != buf[i]) {
>> +                     dev_dbg(dev, "different edid information\n");
>> +                     goto return_point;
>> +             }
>> +     }
>> +
>> +     ret = true;
>> +return_point:
>> +     sp_reset_aux(anx78xx);
>> +
>> +     return ret;
>> +}
>> +
>
> [ snip ]
>
>> +static bool sp_config_video_output(struct anx78xx *anx78xx)
>> +{
>> +     struct device *dev = &anx78xx->client->dev;
>> +     u8 val;
>> +
>> +     switch (sp.tx_vo_state) {
>> +     default:
>> +     case VO_WAIT_VIDEO_STABLE:
>> +             sp_reg_read(anx78xx, RX_P0, SP_SYSTEM_STATUS_REG, &val);
>> +             if ((val & SP_TMDS_DE_DET) && (val & SP_TMDS_CLOCK_DET)) {
>> +                     if (sp_tx_bw_lc_sel(anx78xx))
>> +                             return false;
>> +                     sp_enable_video_input(anx78xx, false);
>> +                     sp_hdmi_new_avi_int(anx78xx);
>> +                     sp_reg_read(anx78xx, RX_P0,
>> +                                 SP_PACKET_RECEIVING_STATUS_REG, &val);
>> +                     if (val & SP_VSI_RCVD)
>> +                             sp_hdmi_new_vsi_int(anx78xx);
>> +                     sp_enable_video_input(anx78xx, true);
>> +                     sp.tx_vo_state = VO_WAIT_TX_VIDEO_STABLE;
>> +             } else {
>> +                     dev_dbg(dev, "HDMI input video not stable!\n");
>> +                     break;
>> +             }
>> +     /* fallthrough */
>> +     case VO_WAIT_TX_VIDEO_STABLE:
>> +             /*
>> +              * The flag is write clear and can be latched from last
>> +              * status. So the first read and write is to clear the
>> +              * previous status.
>> +              */
>> +             sp_reg_read(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, &val);
>> +             sp_reg_write(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, val);
>> +
>> +             sp_reg_read(anx78xx, TX_P0, SP_DP_SYSTEM_CTRL_BASE + 2, &val);
>> +             if (val & SP_CHA_STA) {
>> +                     dev_dbg(dev, "stream clock not stable!\n");
>> +                     break;
>> +             } else {
>
>
> No need for the else statement.  Pull it in one indent level.
>

Ack

>> +                     /*
>> +                      * The flag is write clear and can be latched from
>> +                      * last status. So the first read and write is to
>> +                      * clear the previous status.
>> +                      */
>> +                     sp_reg_read(anx78xx, TX_P0,
>> +                                 SP_DP_SYSTEM_CTRL_BASE + 3,
>> +                                 &val);
>> +                     sp_reg_write(anx78xx, TX_P0,
>> +                                  SP_DP_SYSTEM_CTRL_BASE + 3,
>> +                                  val);
>> +
>> +                     sp_reg_read(anx78xx, TX_P0,
>> +                                 SP_DP_SYSTEM_CTRL_BASE + 3,
>> +                                 &val);
>> +                     if (val & SP_STRM_VALID) {
>> +                             if (sp.tx_test_lt)
>> +                                     sp.tx_test_lt = false;
>> +                             sp.tx_vo_state = VO_FINISH;
>> +                     } else {
>> +                             dev_err(dev, "video stream not valid!\n");
>> +                             break;
>> +                     }
>> +             }
>> +     /* fallthrough */
>> +     case VO_FINISH:
>> +             sp_block_power_ctrl(anx78xx, SP_TX_PWR_AUDIO, false);
>> +             sp_hdmi_mute_video(anx78xx, false);
>> +             sp_video_mute(anx78xx, false);
>> +             sp_show_information(anx78xx);
>> +             return true;
>> +     }
Changed
>> +
>> +     return false;
>> +}
>> +
>
> [ snip ]
>
>> +static void sp_config_audio(struct anx78xx *anx78xx)
>> +{
>> +     int i;
>> +     u8 val;
>> +
>> +     sp_block_power_ctrl(anx78xx, SP_TX_PWR_AUDIO, true);
>> +
>> +     sp_reg_read(anx78xx, TX_P0, SP_DP_MAIN_LINK_BW_SET_REG, &val);
>> +     if (val & SP_INITIAL_SLIM_M_AUD_SEL)
>> +             if (sp_calculate_audio_m_value(anx78xx))
>> +                     return;
>
> Combine these:
>
>         if ((val & SP_INITIAL_SLIM_M_AUD_SEL) &&
>             sp_calculate_audio_m_value(anx78xx))
>                 return;
>

Ok

>> +
>> +     sp_reg_clear_bits(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL0_REG,
>> +                       SP_AUD_INTERFACE_DISABLE);
>> +
>> +     sp_reg_set_bits(anx78xx, TX_P1, SP_AUD_INTERFACE_CTRL2_REG,
>> +                     SP_M_AUD_ADJUST_ST);
>> +
>> +     sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
>> +     if (val & SP_HDMI_AUD_LAYOUT)
>> +             sp_reg_set_bits(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
>> +                             SP_I2S_CH_NUM_8 | SP_AUDIO_LAYOUT);
>> +     else
>> +             sp_reg_clear_bits(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + 5,
>> +                               SP_I2S_CHANNEL_NUM_MASK | SP_AUDIO_LAYOUT);
>> +
>> +     /* transfer audio channel status from HDMI Rx to Slimport Tx */
>> +     for (i = 1; i <= SP_AUD_CH_STATUS_REG_NUM; i++) {
>> +             sp_reg_read(anx78xx, RX_P0, SP_AUD_SPDIF_CH_STATUS_BASE + i,
>> +                         &val);
>> +             sp_reg_write(anx78xx, TX_P2, SP_AUD_CH_STATUS_BASE + i,
>> +                          val);
>> +     }
>
> Either this loop is off by one or the loop in sp_hdmi_audio_samplechg_int()
> is off by one.  Also just call that function instead of re-implimenting
> it here.
>

Right, I'll fix that

>> +
>> +     /* enable audio */
>> +     sp_enable_audio_output(anx78xx, true);
>> +}
>> +
>> +static bool sp_config_audio_output(struct anx78xx *anx78xx)
>> +{
>> +     u8 val;
>> +
>> +     switch (sp.tx_ao_state) {
>> +     default:
>> +     case AO_INIT:
>> +     case AO_CTS_RCV_INT:
>> +     case AO_AUDIO_RCV_INT:
>> +             sp_reg_read(anx78xx, RX_P0, SP_HDMI_STATUS_REG, &val);
>> +             if (!val & SP_HDMI_MODE) {
>
> This is a precendence error.  It should be:
>

Ack

>                 if (!(val & SP_HDMI_MODE)) {
>
>> +                     sp.tx_ao_state = AO_INIT;
>> +                     return true;
>> +             }
>> +             break;
>
> regards,
> dan carpenter

I'll wait a bit more to see if anyone else does more comments and then
I'll send another version with the changes you suggested. Thanks.

Regards,
   Enric
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2015-12-09 11:55 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-12-04  8:35 [PATCHv6 0/5] Add initial support for slimport anx78xx Enric Balletbo i Serra
2015-12-04  8:35 ` Enric Balletbo i Serra
2015-12-04  8:35 ` [PATCHv6 1/5] drm/dp: add DPCD definitions from DP 1.1 Enric Balletbo i Serra
2015-12-04  8:35   ` Enric Balletbo i Serra
2015-12-04  8:35 ` [PATCHv6 2/5] hdmi: added functions for MPEG InfoFrames Enric Balletbo i Serra
2015-12-04  8:35   ` Enric Balletbo i Serra
2015-12-04  8:35 ` [PATCHv6 3/5] of: Add vendor prefix for Analogix Semiconductor, Inc Enric Balletbo i Serra
2015-12-04  8:35 ` [PATCHv6 4/5] devicetree: Add new ANX7814 SlimPort transmitter binding Enric Balletbo i Serra
2015-12-04  8:35   ` Enric Balletbo i Serra
2015-12-04 16:13   ` Rob Herring
2015-12-04 16:13     ` Rob Herring
2015-12-04  8:35 ` [PATCHv6 5/5] drm: bridge: anx78xx: Add anx78xx driver support by analogix Enric Balletbo i Serra
2015-12-04  8:35   ` Enric Balletbo i Serra
2015-12-07  8:09   ` Dan Carpenter
2015-12-07  8:09     ` Dan Carpenter
2015-12-09 11:55     ` Enric Balletbo Serra
2015-12-09 11:55       ` Enric Balletbo Serra

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.