All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFCv3 PATCH 0/8] V4L2: add missing pieces to support HDMI et al and add adv7604/ad9389b drivers
@ 2012-08-10 11:21 Hans Verkuil
  2012-08-10 11:21 ` [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort Hans Verkuil
  0 siblings, 1 reply; 13+ messages in thread
From: Hans Verkuil @ 2012-08-10 11:21 UTC (permalink / raw)
  To: linux-media
  Cc: marbugge, Soby Mathew, mats.randgaard, manjunath.hadli,
	Tomasz Stanislawski, Mauro Carvalho Chehab, Scott Jiang,
	dri-devel

Hi all,

This is the third version of this patch series. The second version can be
found here: http://www.spinics.net/lists/linux-media/msg50413.html

I made a pull request based on that and got some feedback:

http://patchwork.linuxtv.org/patch/13442/

The feedback has been incorporated in this third version.

One suggestion I got was to run this by the video devs as well so that they
can take a look at the V4L2 EDID API, just in case I missed something, so
that's why this is being cross-posted to the dri-devel mailinglist.

Note that the EDID API at the moment is only meant to pass the EDID to userspace
and vice versa. There is no parsing at the moment. If we ever need parsing in
V4L2 (and I'm sure we will) then we will of course use shared EDID parsing code.

The second patch documents the new V4L2 API additions. So that's a good one
to review when it comes to the API.

Regards,

	Hans


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

* [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort.
  2012-08-10 11:21 [RFCv3 PATCH 0/8] V4L2: add missing pieces to support HDMI et al and add adv7604/ad9389b drivers Hans Verkuil
@ 2012-08-10 11:21 ` Hans Verkuil
  2012-08-10 11:21   ` [RFCv3 PATCH 2/8] V4L2 spec: document the new DV controls and ioctls Hans Verkuil
                     ` (7 more replies)
  0 siblings, 8 replies; 13+ messages in thread
From: Hans Verkuil @ 2012-08-10 11:21 UTC (permalink / raw)
  To: linux-media
  Cc: marbugge, Soby Mathew, mats.randgaard, manjunath.hadli,
	Tomasz Stanislawski, Mauro Carvalho Chehab, Scott Jiang,
	dri-devel

These new controls and two new ioctls make it possible to properly support
VGA, DVI-A/D/I, HDMI and DisplayPort connectors. All these controls and the
ioctls are all at the sub-device level. They are meant for V4L2 bridge/platform
drivers or to be accessed on embedded systems through /dev/v4l-subdev* device
nodes.

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
---
 include/linux/v4l2-subdev.h |   10 ++++++++++
 include/linux/videodev2.h   |   23 +++++++++++++++++++++++
 2 files changed, 33 insertions(+)

diff --git a/include/linux/v4l2-subdev.h b/include/linux/v4l2-subdev.h
index 8c57ee9..a426a78 100644
--- a/include/linux/v4l2-subdev.h
+++ b/include/linux/v4l2-subdev.h
@@ -148,6 +148,14 @@ struct v4l2_subdev_selection {
 	__u32 reserved[8];
 };
 
+struct v4l2_subdev_edid {
+	__u32 pad;
+	__u32 start_block;
+	__u32 blocks;
+	__u32 reserved[5];
+	__u8 __user *edid;
+};
+
 #define VIDIOC_SUBDEV_G_FMT	_IOWR('V',  4, struct v4l2_subdev_format)
 #define VIDIOC_SUBDEV_S_FMT	_IOWR('V',  5, struct v4l2_subdev_format)
 #define VIDIOC_SUBDEV_G_FRAME_INTERVAL \
@@ -166,5 +174,7 @@ struct v4l2_subdev_selection {
 	_IOWR('V', 61, struct v4l2_subdev_selection)
 #define VIDIOC_SUBDEV_S_SELECTION \
 	_IOWR('V', 62, struct v4l2_subdev_selection)
+#define VIDIOC_SUBDEV_G_EDID	_IOWR('V', 63, struct v4l2_subdev_edid)
+#define VIDIOC_SUBDEV_S_EDID	_IOWR('V', 64, struct v4l2_subdev_edid)
 
 #endif
diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h
index 7a147c8..91939a7 100644
--- a/include/linux/videodev2.h
+++ b/include/linux/videodev2.h
@@ -1250,6 +1250,7 @@ struct v4l2_ext_controls {
 #define V4L2_CTRL_CLASS_JPEG 0x009d0000		/* JPEG-compression controls */
 #define V4L2_CTRL_CLASS_IMAGE_SOURCE 0x009e0000	/* Image source controls */
 #define V4L2_CTRL_CLASS_IMAGE_PROC 0x009f0000	/* Image processing controls */
+#define V4L2_CTRL_CLASS_DV 0x00a00000		/* Digital Video controls */
 
 #define V4L2_CTRL_ID_MASK      	  (0x0fffffff)
 #define V4L2_CTRL_ID2CLASS(id)    ((id) & 0x0fff0000UL)
@@ -1993,6 +1994,28 @@ enum v4l2_jpeg_chroma_subsampling {
 #define V4L2_CID_LINK_FREQ			(V4L2_CID_IMAGE_PROC_CLASS_BASE + 1)
 #define V4L2_CID_PIXEL_RATE			(V4L2_CID_IMAGE_PROC_CLASS_BASE + 2)
 
+/*  DV-class control IDs defined by V4L2 */
+#define V4L2_CID_DV_CLASS_BASE			(V4L2_CTRL_CLASS_DV | 0x900)
+#define V4L2_CID_DV_CLASS			(V4L2_CTRL_CLASS_DV | 1)
+
+#define	V4L2_CID_DV_TX_HOTPLUG			(V4L2_CID_DV_CLASS_BASE + 1)
+#define	V4L2_CID_DV_TX_RXSENSE			(V4L2_CID_DV_CLASS_BASE + 2)
+#define	V4L2_CID_DV_TX_EDID_PRESENT		(V4L2_CID_DV_CLASS_BASE + 3)
+#define	V4L2_CID_DV_TX_MODE			(V4L2_CID_DV_CLASS_BASE + 4)
+enum v4l2_dv_tx_mode {
+	V4L2_DV_TX_MODE_DVI_D	= 0,
+	V4L2_DV_TX_MODE_HDMI	= 1,
+};
+#define V4L2_CID_DV_TX_RGB_RANGE		(V4L2_CID_DV_CLASS_BASE + 5)
+enum v4l2_dv_rgb_range {
+	V4L2_DV_RGB_RANGE_AUTO	  = 0,
+	V4L2_DV_RGB_RANGE_LIMITED = 1,
+	V4L2_DV_RGB_RANGE_FULL	  = 2,
+};
+
+#define	V4L2_CID_DV_RX_POWER_PRESENT		(V4L2_CID_DV_CLASS_BASE + 100)
+#define V4L2_CID_DV_RX_RGB_RANGE		(V4L2_CID_DV_CLASS_BASE + 101)
+
 /*
  *	T U N I N G
  */
-- 
1.7.10.4


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

* [RFCv3 PATCH 2/8] V4L2 spec: document the new DV controls and ioctls.
  2012-08-10 11:21 ` [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort Hans Verkuil
@ 2012-08-10 11:21   ` Hans Verkuil
  2012-08-10 11:21   ` [RFCv3 PATCH 3/8] v4l2-subdev: add support for the new edid ioctls Hans Verkuil
                     ` (6 subsequent siblings)
  7 siblings, 0 replies; 13+ messages in thread
From: Hans Verkuil @ 2012-08-10 11:21 UTC (permalink / raw)
  To: linux-media
  Cc: marbugge, Soby Mathew, mats.randgaard, manjunath.hadli,
	Tomasz Stanislawski, Mauro Carvalho Chehab, Scott Jiang,
	dri-devel

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
---
 Documentation/DocBook/media/v4l/biblio.xml   |   40 +++++++
 Documentation/DocBook/media/v4l/controls.xml |  161 ++++++++++++++++++++++++++
 Documentation/DocBook/media/v4l/v4l2.xml     |    1 +
 3 files changed, 202 insertions(+)

diff --git a/Documentation/DocBook/media/v4l/biblio.xml b/Documentation/DocBook/media/v4l/biblio.xml
index 1078e45..860626a 100644
--- a/Documentation/DocBook/media/v4l/biblio.xml
+++ b/Documentation/DocBook/media/v4l/biblio.xml
@@ -226,4 +226,44 @@ in the frequency range from 87,5 to 108,0 MHz</title>
       <title>VESA and Industry Standards and Guidelines for Computer Display Monitor Timing (DMT)</title>
     </biblioentry>
 
+    <biblioentry id="vesaedid">
+      <abbrev>EDID</abbrev>
+      <authorgroup>
+	<corpauthor>Video Electronics Standards Association
+(<ulink url="http://www.vesa.org">http://www.vesa.org</ulink>)</corpauthor>
+      </authorgroup>
+      <title>VESA Enhanced Extended Display Identification Data Standard</title>
+      <subtitle>Release A, Revision 2</subtitle>
+    </biblioentry>
+
+    <biblioentry id="hdcp">
+      <abbrev>HDCP</abbrev>
+      <authorgroup>
+	<corpauthor>Digital Content Protection LLC
+(<ulink url="http://www.digital-cp.com">http://www.digital-cp.com</ulink>)</corpauthor>
+      </authorgroup>
+      <title>High-bandwidth Digital Content Protection System</title>
+      <subtitle>Revision 1.3</subtitle>
+    </biblioentry>
+
+    <biblioentry id="hdmi">
+      <abbrev>HDMI</abbrev>
+      <authorgroup>
+	<corpauthor>HDMI Licensing LLC
+(<ulink url="http://www.hdmi.org">http://www.hdmi.org</ulink>)</corpauthor>
+      </authorgroup>
+      <title>High-Definition Multimedia Interface</title>
+      <subtitle>Specification Version 1.4a</subtitle>
+    </biblioentry>
+
+    <biblioentry id="dp">
+      <abbrev>DP</abbrev>
+      <authorgroup>
+	<corpauthor>Video Electronics Standards Association
+(<ulink url="http://www.vesa.org">http://www.vesa.org</ulink>)</corpauthor>
+      </authorgroup>
+      <title>VESA DisplayPort Standard</title>
+      <subtitle>Version 1, Revision 2</subtitle>
+    </biblioentry>
+
   </bibliography>
diff --git a/Documentation/DocBook/media/v4l/controls.xml b/Documentation/DocBook/media/v4l/controls.xml
index b0964fb..1b0b95e 100644
--- a/Documentation/DocBook/media/v4l/controls.xml
+++ b/Documentation/DocBook/media/v4l/controls.xml
@@ -4274,4 +4274,165 @@ interface and may change in the future.</para>
       </table>
 
     </section>
+
+    <section id="dv-controls">
+      <title>Digital Video Control Reference</title>
+
+      <note>
+	<title>Experimental</title>
+
+	<para>This is an <link
+	linkend="experimental">experimental</link> interface and may
+	change in the future.</para>
+      </note>
+
+      <para>
+	The Digital Video control class is intended to control receivers
+	and transmitters for <ulink url="http://en.wikipedia.org/wiki/Vga">VGA</ulink>,
+	<ulink url="http://en.wikipedia.org/wiki/Digital_Visual_Interface">DVI</ulink>
+	(Digital Visual Interface), HDMI (<xref linkend="hdmi" />) and DisplayPort (<xref linkend="dp" />).
+	These controls are generally expected to be private to the receiver or transmitter
+	subdevice that implements them, so they are only exposed on the
+	<filename>/dev/v4l-subdev*</filename> device node.
+      </para>
+
+      <para>Note that these devices can have multiple input or output pads which are
+      hooked up to e.g. HDMI connectors. Even though the subdevice will receive or
+      transmit video from/to only one of those pads, the other pads can still be
+      active when it comes to EDID (Extended Display Identification Data,
+      <xref linkend="vesaedid" />) and HDCP (High-bandwidth Digital Content
+      Protection System, <xref linkend="hdcp" />) processing, allowing the device
+      to do the fairly slow EDID/HDCP handling in advance. This allows for quick
+      switching between connectors.</para>
+
+      <para>These pads appear in several of the controls in this section as
+      bitmasks, one bit for each pad. Bit 0 corresponds to pad 0, bit 1 to pad 1,
+      etc. The maximum value of the control is the set of valid pads.</para>
+
+      <table pgwide="1" frame="none" id="dv-control-id">
+      <title>Digital Video Control IDs</title>
+
+      <tgroup cols="4">
+	<colspec colname="c1" colwidth="1*" />
+	<colspec colname="c2" colwidth="6*" />
+	<colspec colname="c3" colwidth="2*" />
+	<colspec colname="c4" colwidth="6*" />
+	<spanspec namest="c1" nameend="c2" spanname="id" />
+	<spanspec namest="c2" nameend="c4" spanname="descr" />
+	<thead>
+	  <row>
+	    <entry spanname="id" align="left">ID</entry>
+	    <entry align="left">Type</entry>
+	  </row><row rowsep="1"><entry spanname="descr" align="left">Description</entry>
+	  </row>
+	</thead>
+	<tbody valign="top">
+	  <row><entry></entry></row>
+	  <row>
+	    <entry spanname="id"><constant>V4L2_CID_DV_CLASS</constant></entry>
+	    <entry>class</entry>
+	  </row>
+	  <row>
+	    <entry spanname="descr">The Digital Video class descriptor.</entry>
+	  </row>
+	  <row>
+	    <entry spanname="id"><constant>V4L2_CID_DV_TX_HOTPLUG</constant></entry>
+	    <entry>bitmask</entry>
+	  </row>
+	  <row>
+	    <entry spanname="descr">Many connectors have a hotplug pin which is high
+	    if EDID information is available from the source. This control shows the
+	    state of the hotplug pin as seen by the transmitter.
+	    Each bit corresponds to an output pad on the transmitter. If an output pad
+	    does not have an associated hotplug pin, then the bit for that pad will be 0.
+	    This read-only control is applicable to DVI-D, HDMI and DisplayPort connectors.
+	    </entry>
+	  </row>
+	  <row>
+	    <entry spanname="id"><constant>V4L2_CID_DV_TX_RXSENSE</constant></entry>
+	    <entry>bitmask</entry>
+	  </row>
+	  <row>
+	    <entry spanname="descr">Rx Sense is the detection of pull-ups on the TMDS
+            clock lines. This normally means that the sink has left/entered standby (i.e.
+	    the transmitter can sense that the receiver is ready to receive video).
+	    Each bit corresponds to an output pad on the transmitter. If an output pad
+	    does not have an associated Rx Sense, then the bit for that pad will be 0.
+	    This read-only control is applicable to DVI-D and HDMI devices.
+	    </entry>
+	  </row>
+	  <row>
+	    <entry spanname="id"><constant>V4L2_CID_DV_TX_EDID_PRESENT</constant></entry>
+	    <entry>bitmask</entry>
+	  </row>
+	  <row>
+	    <entry spanname="descr">When the transmitter sees the hotplug signal from the
+	    receiver it will attempt to read the EDID. If set, then the transmitter has read
+	    at least the first block (= 128 bytes).
+	    Each bit corresponds to an output pad on the transmitter. If an output pad
+	    does not support EDIDs, then the bit for that pad will be 0.
+	    This read-only control is applicable to VGA, DVI-A/D, HDMI and DisplayPort connectors.
+	    </entry>
+	  </row>
+	  <row>
+	    <entry spanname="id"><constant>V4L2_CID_DV_TX_MODE</constant></entry>
+	    <entry id="v4l2-dv-tx-mode">enum v4l2_dv_tx_mode</entry>
+	  </row>
+	  <row>
+	    <entry spanname="descr">HDMI transmitters can transmit in DVI-D mode (just video)
+	    or in HDMI mode (video + audio + auxiliary data). This control selects which mode
+	    to use: V4L2_DV_TX_MODE_DVI_D or V4L2_DV_TX_MODE_HDMI.
+	    This control is applicable to HDMI connectors.
+	    </entry>
+	  </row>
+	  <row>
+	    <entry spanname="id"><constant>V4L2_CID_DV_TX_RGB_RANGE</constant></entry>
+	    <entry id="v4l2-dv-rgb-range">enum v4l2_dv_rgb_range</entry>
+	  </row>
+	  <row>
+	    <entry spanname="descr">Select the quantization range for RGB output. V4L2_DV_RANGE_AUTO
+	    follows the RGB quantization range specified in the standard for the video interface
+	    (ie. <xref linkend="cea861" /> for HDMI). V4L2_DV_RANGE_LIMITED and V4L2_DV_RANGE_FULL override the standard
+	    to be compatible with sinks that have not implemented the standard correctly
+	    (unfortunately quite common for HDMI and DVI-D). Full range allows all possible values to be
+	    used whereas limited range sets the range to (16 &lt;&lt; (N-8)) - (235 &lt;&lt; (N-8))
+	    where N is the number of bits per component.
+	    This control is applicable to VGA, DVI-A/D, HDMI and DisplayPort connectors.
+	    </entry>
+	  </row>
+	  <row>
+	    <entry spanname="id"><constant>V4L2_CID_DV_RX_POWER_PRESENT</constant></entry>
+	    <entry>bitmask</entry>
+	  </row>
+	  <row>
+	    <entry spanname="descr">Detects whether the receiver receives power from the source
+	    (e.g. HDMI carries 5V on one of the pins). This is often used to power an eeprom
+	    which contains EDID information, such that the source can read the EDID even if
+	    the sink is in standby/power off.
+	    Each bit corresponds to an input pad on the transmitter. If an input pad
+	    cannot detect whether power is present, then the bit for that pad will be 0.
+	    This read-only control is applicable to DVI-D, HDMI and DisplayPort connectors.
+	    </entry>
+	  </row>
+	  <row>
+	    <entry spanname="id"><constant>V4L2_CID_DV_RX_RGB_RANGE</constant></entry>
+	    <entry>enum v4l2_dv_rgb_range</entry>
+	  </row>
+	  <row>
+	    <entry spanname="descr">Select the quantization range for RGB input. V4L2_DV_RANGE_AUTO
+	    follows the RGB quantization range specified in the standard for the video interface
+	    (ie. <xref linkend="cea861" /> for HDMI). V4L2_DV_RANGE_LIMITED and V4L2_DV_RANGE_FULL override the standard
+	    to be compatible with sources that have not implemented the standard correctly
+	    (unfortunately quite common for HDMI and DVI-D). Full range allows all possible values to be
+	    used whereas limited range sets the range to (16 &lt;&lt; (N-8)) - (235 &lt;&lt; (N-8))
+	    where N is the number of bits per component.
+	    This control is applicable to VGA, DVI-A/D, HDMI and DisplayPort connectors.
+	    </entry>
+	  </row>
+	  <row><entry></entry></row>
+	</tbody>
+      </tgroup>
+      </table>
+
+    </section>
 </section>
diff --git a/Documentation/DocBook/media/v4l/v4l2.xml b/Documentation/DocBook/media/v4l/v4l2.xml
index eee6908..b251c4b 100644
--- a/Documentation/DocBook/media/v4l/v4l2.xml
+++ b/Documentation/DocBook/media/v4l/v4l2.xml
@@ -581,6 +581,7 @@ and discussions on the V4L mailing list.</revremark>
     &sub-subdev-enum-frame-size;
     &sub-subdev-enum-mbus-code;
     &sub-subdev-g-crop;
+    &sub-subdev-g-edid;
     &sub-subdev-g-fmt;
     &sub-subdev-g-frame-interval;
     &sub-subdev-g-selection;
-- 
1.7.10.4


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

* [RFCv3 PATCH 3/8] v4l2-subdev: add support for the new edid ioctls.
  2012-08-10 11:21 ` [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort Hans Verkuil
  2012-08-10 11:21   ` [RFCv3 PATCH 2/8] V4L2 spec: document the new DV controls and ioctls Hans Verkuil
@ 2012-08-10 11:21   ` Hans Verkuil
  2012-08-16 17:55     ` Soby Mathew
  2012-08-10 11:21   ` [RFCv3 PATCH 4/8] v4l2-ctrls.c: add support for the new DV controls Hans Verkuil
                     ` (5 subsequent siblings)
  7 siblings, 1 reply; 13+ messages in thread
From: Hans Verkuil @ 2012-08-10 11:21 UTC (permalink / raw)
  To: linux-media
  Cc: marbugge, Soby Mathew, mats.randgaard, manjunath.hadli,
	Tomasz Stanislawski, Mauro Carvalho Chehab, Scott Jiang,
	dri-devel

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
---
 drivers/media/video/v4l2-compat-ioctl32.c |   57 +++++++++++++++++++++++++++++
 drivers/media/video/v4l2-ioctl.c          |   13 +++++++
 drivers/media/video/v4l2-subdev.c         |    6 +++
 include/media/v4l2-subdev.h               |    2 +
 4 files changed, 78 insertions(+)

diff --git a/drivers/media/video/v4l2-compat-ioctl32.c b/drivers/media/video/v4l2-compat-ioctl32.c
index 9ebd5c5..e843705 100644
--- a/drivers/media/video/v4l2-compat-ioctl32.c
+++ b/drivers/media/video/v4l2-compat-ioctl32.c
@@ -16,6 +16,7 @@
 #include <linux/compat.h>
 #include <linux/module.h>
 #include <linux/videodev2.h>
+#include <linux/v4l2-subdev.h>
 #include <media/v4l2-dev.h>
 #include <media/v4l2-ioctl.h>
 
@@ -729,6 +730,44 @@ static int put_v4l2_event32(struct v4l2_event *kp, struct v4l2_event32 __user *u
 	return 0;
 }
 
+struct v4l2_subdev_edid32 {
+	__u32 pad;
+	__u32 start_block;
+	__u32 blocks;
+	__u32 reserved[5];
+	compat_caddr_t edid;
+};
+
+static int get_v4l2_subdev_edid32(struct v4l2_subdev_edid *kp, struct v4l2_subdev_edid32 __user *up)
+{
+	u32 tmp;
+
+	if (!access_ok(VERIFY_READ, up, sizeof(struct v4l2_subdev_edid32)) ||
+		get_user(kp->pad, &up->pad) ||
+		get_user(kp->start_block, &up->start_block) ||
+		get_user(kp->blocks, &up->blocks) ||
+		get_user(tmp, &up->edid) ||
+		copy_from_user(kp->reserved, up->reserved, sizeof(kp->reserved)))
+			return -EFAULT;
+	kp->edid = compat_ptr(tmp);
+	return 0;
+}
+
+static int put_v4l2_subdev_edid32(struct v4l2_subdev_edid *kp, struct v4l2_subdev_edid32 __user *up)
+{
+	u32 tmp = (u32)((unsigned long)kp->edid);
+
+	if (!access_ok(VERIFY_WRITE, up, sizeof(struct v4l2_subdev_edid32)) ||
+		put_user(kp->pad, &up->pad) ||
+		put_user(kp->start_block, &up->start_block) ||
+		put_user(kp->blocks, &up->blocks) ||
+		put_user(tmp, &up->edid) ||
+		copy_to_user(kp->reserved, up->reserved, sizeof(kp->reserved)))
+			return -EFAULT;
+	return 0;
+}
+
+
 #define VIDIOC_G_FMT32		_IOWR('V',  4, struct v4l2_format32)
 #define VIDIOC_S_FMT32		_IOWR('V',  5, struct v4l2_format32)
 #define VIDIOC_QUERYBUF32	_IOWR('V',  9, struct v4l2_buffer32)
@@ -738,6 +777,8 @@ static int put_v4l2_event32(struct v4l2_event *kp, struct v4l2_event32 __user *u
 #define VIDIOC_DQBUF32		_IOWR('V', 17, struct v4l2_buffer32)
 #define VIDIOC_ENUMSTD32	_IOWR('V', 25, struct v4l2_standard32)
 #define VIDIOC_ENUMINPUT32	_IOWR('V', 26, struct v4l2_input32)
+#define VIDIOC_SUBDEV_G_EDID32	_IOWR('V', 63, struct v4l2_subdev_edid32)
+#define VIDIOC_SUBDEV_S_EDID32	_IOWR('V', 64, struct v4l2_subdev_edid32)
 #define VIDIOC_TRY_FMT32      	_IOWR('V', 64, struct v4l2_format32)
 #define VIDIOC_G_EXT_CTRLS32    _IOWR('V', 71, struct v4l2_ext_controls32)
 #define VIDIOC_S_EXT_CTRLS32    _IOWR('V', 72, struct v4l2_ext_controls32)
@@ -765,6 +806,7 @@ static long do_video_ioctl(struct file *file, unsigned int cmd, unsigned long ar
 		struct v4l2_ext_controls v2ecs;
 		struct v4l2_event v2ev;
 		struct v4l2_create_buffers v2crt;
+		struct v4l2_subdev_edid v2edid;
 		unsigned long vx;
 		int vi;
 	} karg;
@@ -797,6 +839,8 @@ static long do_video_ioctl(struct file *file, unsigned int cmd, unsigned long ar
 	case VIDIOC_S_OUTPUT32: cmd = VIDIOC_S_OUTPUT; break;
 	case VIDIOC_CREATE_BUFS32: cmd = VIDIOC_CREATE_BUFS; break;
 	case VIDIOC_PREPARE_BUF32: cmd = VIDIOC_PREPARE_BUF; break;
+	case VIDIOC_SUBDEV_G_EDID32: cmd = VIDIOC_SUBDEV_G_EDID; break;
+	case VIDIOC_SUBDEV_S_EDID32: cmd = VIDIOC_SUBDEV_S_EDID; break;
 	}
 
 	switch (cmd) {
@@ -814,6 +858,12 @@ static long do_video_ioctl(struct file *file, unsigned int cmd, unsigned long ar
 		compatible_arg = 0;
 		break;
 
+	case VIDIOC_SUBDEV_G_EDID:
+	case VIDIOC_SUBDEV_S_EDID:
+		err = get_v4l2_subdev_edid32(&karg.v2edid, up);
+		compatible_arg = 0;
+		break;
+
 	case VIDIOC_G_FMT:
 	case VIDIOC_S_FMT:
 	case VIDIOC_TRY_FMT:
@@ -906,6 +956,11 @@ static long do_video_ioctl(struct file *file, unsigned int cmd, unsigned long ar
 		err = put_v4l2_event32(&karg.v2ev, up);
 		break;
 
+	case VIDIOC_SUBDEV_G_EDID:
+	case VIDIOC_SUBDEV_S_EDID:
+		err = put_v4l2_subdev_edid32(&karg.v2edid, up);
+		break;
+
 	case VIDIOC_G_FMT:
 	case VIDIOC_S_FMT:
 	case VIDIOC_TRY_FMT:
@@ -1026,6 +1081,8 @@ long v4l2_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg)
 	case VIDIOC_QUERY_DV_TIMINGS:
 	case VIDIOC_DV_TIMINGS_CAP:
 	case VIDIOC_ENUM_FREQ_BANDS:
+	case VIDIOC_SUBDEV_G_EDID32:
+	case VIDIOC_SUBDEV_S_EDID32:
 		ret = do_video_ioctl(file, cmd, arg);
 		break;
 
diff --git a/drivers/media/video/v4l2-ioctl.c b/drivers/media/video/v4l2-ioctl.c
index c3b7b5f..1400f98 100644
--- a/drivers/media/video/v4l2-ioctl.c
+++ b/drivers/media/video/v4l2-ioctl.c
@@ -2185,6 +2185,19 @@ static int check_array_args(unsigned int cmd, void *parg, size_t *array_size,
 		break;
 	}
 
+	case VIDIOC_SUBDEV_G_EDID:
+	case VIDIOC_SUBDEV_S_EDID: {
+		struct v4l2_subdev_edid *edid = parg;
+
+		if (edid->blocks) {
+			*user_ptr = (void __user *)edid->edid;
+			*kernel_ptr = (void *)&edid->edid;
+			*array_size = edid->blocks * 128;
+			ret = 1;
+		}
+		break;
+	}
+
 	case VIDIOC_S_EXT_CTRLS:
 	case VIDIOC_G_EXT_CTRLS:
 	case VIDIOC_TRY_EXT_CTRLS: {
diff --git a/drivers/media/video/v4l2-subdev.c b/drivers/media/video/v4l2-subdev.c
index 9182f81..dced41c 100644
--- a/drivers/media/video/v4l2-subdev.c
+++ b/drivers/media/video/v4l2-subdev.c
@@ -348,6 +348,12 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg)
 		return v4l2_subdev_call(
 			sd, pad, set_selection, subdev_fh, sel);
 	}
+
+	case VIDIOC_SUBDEV_G_EDID:
+		return v4l2_subdev_call(sd, pad, get_edid, arg);
+
+	case VIDIOC_SUBDEV_S_EDID:
+		return v4l2_subdev_call(sd, pad, set_edid, arg);
 #endif
 	default:
 		return v4l2_subdev_call(sd, core, ioctl, cmd, arg);
diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
index c35a354..74c578f 100644
--- a/include/media/v4l2-subdev.h
+++ b/include/media/v4l2-subdev.h
@@ -476,6 +476,8 @@ struct v4l2_subdev_pad_ops {
 			     struct v4l2_subdev_selection *sel);
 	int (*set_selection)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
 			     struct v4l2_subdev_selection *sel);
+	int (*get_edid)(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid);
+	int (*set_edid)(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid);
 #ifdef CONFIG_MEDIA_CONTROLLER
 	int (*link_validate)(struct v4l2_subdev *sd, struct media_link *link,
 			     struct v4l2_subdev_format *source_fmt,
-- 
1.7.10.4


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

* [RFCv3 PATCH 4/8] v4l2-ctrls.c: add support for the new DV controls.
  2012-08-10 11:21 ` [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort Hans Verkuil
  2012-08-10 11:21   ` [RFCv3 PATCH 2/8] V4L2 spec: document the new DV controls and ioctls Hans Verkuil
  2012-08-10 11:21   ` [RFCv3 PATCH 3/8] v4l2-subdev: add support for the new edid ioctls Hans Verkuil
@ 2012-08-10 11:21   ` Hans Verkuil
  2012-08-10 11:21   ` [RFCv3 PATCH 5/8] v4l2-common: add v4l_match_dv_timings Hans Verkuil
                     ` (4 subsequent siblings)
  7 siblings, 0 replies; 13+ messages in thread
From: Hans Verkuil @ 2012-08-10 11:21 UTC (permalink / raw)
  To: linux-media
  Cc: marbugge, Soby Mathew, mats.randgaard, manjunath.hadli,
	Tomasz Stanislawski, Mauro Carvalho Chehab, Scott Jiang,
	dri-devel

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
---
 drivers/media/video/v4l2-ctrls.c |   39 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/drivers/media/video/v4l2-ctrls.c b/drivers/media/video/v4l2-ctrls.c
index b6a2ee7..6a34c30 100644
--- a/drivers/media/video/v4l2-ctrls.c
+++ b/drivers/media/video/v4l2-ctrls.c
@@ -425,6 +425,18 @@ const char * const *v4l2_ctrl_get_menu(u32 id)
 		"Gray",
 		NULL,
 	};
+	static const char * const dv_tx_mode[] = {
+		"DVI-D",
+		"HDMI",
+		NULL,
+	};
+	static const char * const dv_rgb_range[] = {
+		"Automatic",
+		"RGB limited range (16-235)",
+		"RGB full range (0-255)",
+		NULL,
+	};
+
 
 	switch (id) {
 	case V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ:
@@ -502,6 +514,11 @@ const char * const *v4l2_ctrl_get_menu(u32 id)
 		return mpeg4_profile;
 	case V4L2_CID_JPEG_CHROMA_SUBSAMPLING:
 		return jpeg_chroma_subsampling;
+	case V4L2_CID_DV_TX_MODE:
+		return dv_tx_mode;
+	case V4L2_CID_DV_TX_RGB_RANGE:
+	case V4L2_CID_DV_RX_RGB_RANGE:
+		return dv_rgb_range;
 
 	default:
 		return NULL;
@@ -733,6 +750,16 @@ const char *v4l2_ctrl_get_name(u32 id)
 	case V4L2_CID_LINK_FREQ:		return "Link Frequency";
 	case V4L2_CID_PIXEL_RATE:		return "Pixel Rate";
 
+	/* DV controls */
+	case V4L2_CID_DV_CLASS:			return "Digital Video Controls";
+	case V4L2_CID_DV_TX_HOTPLUG:		return "Hotplug Present";
+	case V4L2_CID_DV_TX_RXSENSE:		return "RxSense Present";
+	case V4L2_CID_DV_TX_EDID_PRESENT:	return "EDID Present";
+	case V4L2_CID_DV_TX_MODE:		return "Transmit Mode";
+	case V4L2_CID_DV_TX_RGB_RANGE:		return "Tx RGB Quantization Range";
+	case V4L2_CID_DV_RX_POWER_PRESENT:	return "Power Present";
+	case V4L2_CID_DV_RX_RGB_RANGE:		return "Rx RGB Quantization Range";
+
 	default:
 		return NULL;
 	}
@@ -832,6 +859,9 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
 	case V4L2_CID_ISO_SENSITIVITY_AUTO:
 	case V4L2_CID_EXPOSURE_METERING:
 	case V4L2_CID_SCENE_MODE:
+	case V4L2_CID_DV_TX_MODE:
+	case V4L2_CID_DV_TX_RGB_RANGE:
+	case V4L2_CID_DV_RX_RGB_RANGE:
 		*type = V4L2_CTRL_TYPE_MENU;
 		break;
 	case V4L2_CID_LINK_FREQ:
@@ -853,6 +883,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
 	case V4L2_CID_JPEG_CLASS:
 	case V4L2_CID_IMAGE_SOURCE_CLASS:
 	case V4L2_CID_IMAGE_PROC_CLASS:
+	case V4L2_CID_DV_CLASS:
 		*type = V4L2_CTRL_TYPE_CTRL_CLASS;
 		/* You can neither read not write these */
 		*flags |= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_WRITE_ONLY;
@@ -869,6 +900,10 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
 	case V4L2_CID_JPEG_ACTIVE_MARKER:
 	case V4L2_CID_3A_LOCK:
 	case V4L2_CID_AUTO_FOCUS_STATUS:
+	case V4L2_CID_DV_TX_HOTPLUG:
+	case V4L2_CID_DV_TX_RXSENSE:
+	case V4L2_CID_DV_TX_EDID_PRESENT:
+	case V4L2_CID_DV_RX_POWER_PRESENT:
 		*type = V4L2_CTRL_TYPE_BITMASK;
 		break;
 	case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE:
@@ -933,6 +968,10 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
 	case V4L2_CID_FLASH_STROBE_STATUS:
 	case V4L2_CID_AUTO_FOCUS_STATUS:
 	case V4L2_CID_FLASH_READY:
+	case V4L2_CID_DV_TX_HOTPLUG:
+	case V4L2_CID_DV_TX_RXSENSE:
+	case V4L2_CID_DV_TX_EDID_PRESENT:
+	case V4L2_CID_DV_RX_POWER_PRESENT:
 		*flags |= V4L2_CTRL_FLAG_READ_ONLY;
 		break;
 	}
-- 
1.7.10.4


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

* [RFCv3 PATCH 5/8] v4l2-common: add v4l_match_dv_timings.
  2012-08-10 11:21 ` [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort Hans Verkuil
                     ` (2 preceding siblings ...)
  2012-08-10 11:21   ` [RFCv3 PATCH 4/8] v4l2-ctrls.c: add support for the new DV controls Hans Verkuil
@ 2012-08-10 11:21   ` Hans Verkuil
  2012-08-10 11:21   ` [RFCv3 PATCH 6/8] v4l2-common: add CVT and GTF detection functions Hans Verkuil
                     ` (3 subsequent siblings)
  7 siblings, 0 replies; 13+ messages in thread
From: Hans Verkuil @ 2012-08-10 11:21 UTC (permalink / raw)
  To: linux-media
  Cc: marbugge, Soby Mathew, mats.randgaard, manjunath.hadli,
	Tomasz Stanislawski, Mauro Carvalho Chehab, Scott Jiang,
	dri-devel

Add the v4l_match_dv_timings function that can be used to compare two
v4l2_dv_timings structs.

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
---
 drivers/media/video/v4l2-common.c |   33 +++++++++++++++++++++++++++++++++
 include/media/v4l2-common.h       |    4 ++++
 2 files changed, 37 insertions(+)

diff --git a/drivers/media/video/v4l2-common.c b/drivers/media/video/v4l2-common.c
index 1baec83..38da47c 100644
--- a/drivers/media/video/v4l2-common.c
+++ b/drivers/media/video/v4l2-common.c
@@ -597,6 +597,39 @@ int v4l_fill_dv_preset_info(u32 preset, struct v4l2_dv_enum_preset *info)
 }
 EXPORT_SYMBOL_GPL(v4l_fill_dv_preset_info);
 
+/**
+ * v4l_match_dv_timings - check if two timings match
+ * @t1 - compare this v4l2_dv_timings struct...
+ * @t2 - with this struct.
+ * @pclock_delta - the allowed pixelclock deviation.
+ *
+ * Compare t1 with t2 with a given margin of error for the pixelclock.
+ */
+bool v4l_match_dv_timings(const struct v4l2_dv_timings *t1,
+			  const struct v4l2_dv_timings *t2,
+			  unsigned pclock_delta)
+{
+	if (t1->type != t2->type || t1->type != V4L2_DV_BT_656_1120)
+		return false;
+	if (t1->bt.width == t2->bt.width &&
+	    t1->bt.height == t2->bt.height &&
+	    t1->bt.interlaced == t2->bt.interlaced &&
+	    t1->bt.polarities == t2->bt.polarities &&
+	    t1->bt.pixelclock >= t2->bt.pixelclock - pclock_delta &&
+	    t1->bt.pixelclock <= t2->bt.pixelclock + pclock_delta &&
+	    t1->bt.hfrontporch == t2->bt.hfrontporch &&
+	    t1->bt.vfrontporch == t2->bt.vfrontporch &&
+	    t1->bt.vsync == t2->bt.vsync &&
+	    t1->bt.vbackporch == t2->bt.vbackporch &&
+	    (!t1->bt.interlaced ||
+		(t1->bt.il_vfrontporch == t2->bt.il_vfrontporch &&
+		 t1->bt.il_vsync == t2->bt.il_vsync &&
+		 t1->bt.il_vbackporch == t2->bt.il_vbackporch)))
+		return true;
+	return false;
+}
+EXPORT_SYMBOL_GPL(v4l_match_dv_timings);
+
 const struct v4l2_frmsize_discrete *v4l2_find_nearest_format(
 		const struct v4l2_discrete_probe *probe,
 		s32 width, s32 height)
diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h
index a298ec4..b43b968 100644
--- a/include/media/v4l2-common.h
+++ b/include/media/v4l2-common.h
@@ -212,4 +212,8 @@ const struct v4l2_frmsize_discrete *v4l2_find_nearest_format(
 		const struct v4l2_discrete_probe *probe,
 		s32 width, s32 height);
 
+bool v4l_match_dv_timings(const struct v4l2_dv_timings *t1,
+			  const struct v4l2_dv_timings *t2,
+			  unsigned pclock_delta);
+
 #endif /* V4L2_COMMON_H_ */
-- 
1.7.10.4


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

* [RFCv3 PATCH 6/8] v4l2-common: add CVT and GTF detection functions.
  2012-08-10 11:21 ` [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort Hans Verkuil
                     ` (3 preceding siblings ...)
  2012-08-10 11:21   ` [RFCv3 PATCH 5/8] v4l2-common: add v4l_match_dv_timings Hans Verkuil
@ 2012-08-10 11:21   ` Hans Verkuil
  2012-08-10 11:21   ` [RFCv3 PATCH 7/8] adv7604: driver for the Analog Devices ADV7604 video decoder Hans Verkuil
                     ` (2 subsequent siblings)
  7 siblings, 0 replies; 13+ messages in thread
From: Hans Verkuil @ 2012-08-10 11:21 UTC (permalink / raw)
  To: linux-media
  Cc: marbugge, Soby Mathew, mats.randgaard, manjunath.hadli,
	Tomasz Stanislawski, Mauro Carvalho Chehab, Scott Jiang,
	dri-devel

These two helper functions detect whether the analog video timings detected
by the video receiver match the VESA CVT or GTF standards.

They basically do the inverse of the CVT and GTF modeline calculations.

This patch also adds a helper function that will determine the aspect ratio
based on the provided EDID values. This aspect ratio can be given to the GTF
helper function.

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
---
 drivers/media/video/v4l2-common.c |  325 +++++++++++++++++++++++++++++++++++++
 include/media/v4l2-common.h       |    9 +
 2 files changed, 334 insertions(+)

diff --git a/drivers/media/video/v4l2-common.c b/drivers/media/video/v4l2-common.c
index 38da47c..33e57d8 100644
--- a/drivers/media/video/v4l2-common.c
+++ b/drivers/media/video/v4l2-common.c
@@ -630,6 +630,331 @@ bool v4l_match_dv_timings(const struct v4l2_dv_timings *t1,
 }
 EXPORT_SYMBOL_GPL(v4l_match_dv_timings);
 
+/*
+ * CVT defines
+ * Based on Coordinated Video Timings Standard
+ * version 1.1 September 10, 2003
+ */
+
+#define CVT_PXL_CLK_GRAN	250000	/* pixel clock granularity */
+
+/* Normal blanking */
+#define CVT_MIN_V_BPORCH	7	/* lines */
+#define CVT_MIN_V_PORCH_RND	3	/* lines */
+#define CVT_MIN_VSYNC_BP	550	/* min time of vsync + back porch (us) */
+
+/* Normal blanking for CVT uses GTF to calculate horizontal blanking */
+#define CVT_CELL_GRAN		8	/* character cell granularity */
+#define CVT_M			600	/* blanking formula gradient */
+#define CVT_C			40	/* blanking formula offset */
+#define CVT_K			128	/* blanking formula scaling factor */
+#define CVT_J			20	/* blanking formula scaling factor */
+#define CVT_C_PRIME (((CVT_C - CVT_J) * CVT_K / 256) + CVT_J)
+#define CVT_M_PRIME (CVT_K * CVT_M / 256)
+
+/* Reduced Blanking */
+#define CVT_RB_MIN_V_BPORCH    7       /* lines  */
+#define CVT_RB_V_FPORCH        3       /* lines  */
+#define CVT_RB_MIN_V_BLANK   460     /* us     */
+#define CVT_RB_H_SYNC         32       /* pixels */
+#define CVT_RB_H_BPORCH       80       /* pixels */
+#define CVT_RB_H_BLANK       160       /* pixels */
+
+/** v4l2_detect_cvt - detect if the given timings follow the CVT standard
+ * @frame_height - the total height of the frame (including blanking) in lines.
+ * @hfreq - the horizontal frequency in Hz.
+ * @vsync - the height of the vertical sync in lines.
+ * @polarities - the horizontal and vertical polarities (same as struct
+ *		v4l2_bt_timings polarities).
+ * @fmt - the resulting timings.
+ *
+ * This function will attempt to detect if the given values correspond to a
+ * valid CVT format. If so, then it will return true, and fmt will be filled
+ * in with the found CVT timings.
+ */
+bool v4l2_detect_cvt(unsigned frame_height, unsigned hfreq, unsigned vsync,
+		u32 polarities, struct v4l2_dv_timings *fmt)
+{
+	int  v_fp, v_bp, h_fp, h_bp, hsync;
+	int  frame_width, image_height, image_width;
+	bool reduced_blanking;
+	unsigned pix_clk;
+
+	if (vsync < 4 || vsync > 7)
+		return false;
+
+	if (polarities == V4L2_DV_VSYNC_POS_POL)
+		reduced_blanking = false;
+	else if (polarities == V4L2_DV_HSYNC_POS_POL)
+		reduced_blanking = true;
+	else
+		return false;
+
+	/* Vertical */
+	if (reduced_blanking) {
+		v_fp = CVT_RB_V_FPORCH;
+		v_bp = (CVT_RB_MIN_V_BLANK * hfreq + 999999) / 1000000;
+		v_bp -= vsync + v_fp;
+
+		if (v_bp < CVT_RB_MIN_V_BPORCH)
+			v_bp = CVT_RB_MIN_V_BPORCH;
+	} else {
+		v_fp = CVT_MIN_V_PORCH_RND;
+		v_bp = (CVT_MIN_VSYNC_BP * hfreq + 999999) / 1000000 - vsync;
+
+		if (v_bp < CVT_MIN_V_BPORCH)
+			v_bp = CVT_MIN_V_BPORCH;
+	}
+	image_height = (frame_height - v_fp - vsync - v_bp + 1) & ~0x1;
+
+	/* Aspect ratio based on vsync */
+	switch (vsync) {
+	case 4:
+		image_width = (image_height * 4) / 3;
+		break;
+	case 5:
+		image_width = (image_height * 16) / 9;
+		break;
+	case 6:
+		image_width = (image_height * 16) / 10;
+		break;
+	case 7:
+		/* special case */
+		if (image_height == 1024)
+			image_width = (image_height * 5) / 4;
+		else if (image_height == 768)
+			image_width = (image_height * 15) / 9;
+		else
+			return false;
+		break;
+	default:
+		return false;
+	}
+
+	image_width = image_width & ~7;
+
+	/* Horizontal */
+	if (reduced_blanking) {
+		pix_clk = (image_width + CVT_RB_H_BLANK) * hfreq;
+		pix_clk = (pix_clk / CVT_PXL_CLK_GRAN) * CVT_PXL_CLK_GRAN;
+
+		h_bp = CVT_RB_H_BPORCH;
+		hsync = CVT_RB_H_SYNC;
+		h_fp = CVT_RB_H_BLANK - h_bp - hsync;
+
+		frame_width = image_width + CVT_RB_H_BLANK;
+	} else {
+		int h_blank;
+		unsigned ideal_duty_cycle = CVT_C_PRIME - (CVT_M_PRIME * 1000) / hfreq;
+
+		h_blank = (image_width * ideal_duty_cycle + (100 - ideal_duty_cycle) / 2) /
+						(100 - ideal_duty_cycle);
+		h_blank = h_blank - h_blank % (2 * CVT_CELL_GRAN);
+
+		if (h_blank * 100 / image_width < 20) {
+			h_blank = image_width / 5;
+			h_blank = (h_blank + 0x7) & ~0x7;
+		}
+
+		pix_clk = (image_width + h_blank) * hfreq;
+		pix_clk = (pix_clk / CVT_PXL_CLK_GRAN) * CVT_PXL_CLK_GRAN;
+
+		h_bp = h_blank / 2;
+		frame_width = image_width + h_blank;
+
+		hsync = (frame_width * 8 + 50) / 100;
+		hsync = hsync - hsync % CVT_CELL_GRAN;
+		h_fp = h_blank - hsync - h_bp;
+	}
+
+	fmt->bt.polarities = polarities;
+	fmt->bt.width = image_width;
+	fmt->bt.height = image_height;
+	fmt->bt.hfrontporch = h_fp;
+	fmt->bt.vfrontporch = v_fp;
+	fmt->bt.hsync = hsync;
+	fmt->bt.vsync = vsync;
+	fmt->bt.hbackporch = frame_width - image_width - h_fp - hsync;
+	fmt->bt.vbackporch = frame_height - image_height - v_fp - vsync;
+	fmt->bt.pixelclock = pix_clk;
+	fmt->bt.standards = V4L2_DV_BT_STD_CVT;
+	if (reduced_blanking)
+		fmt->bt.flags |= V4L2_DV_FL_REDUCED_BLANKING;
+	return true;
+}
+EXPORT_SYMBOL_GPL(v4l2_detect_cvt);
+
+/*
+ * GTF defines
+ * Based on Generalized Timing Formula Standard
+ * Version 1.1 September 2, 1999
+ */
+
+#define GTF_PXL_CLK_GRAN	250000	/* pixel clock granularity */
+
+#define GTF_MIN_VSYNC_BP	550	/* min time of vsync + back porch (us) */
+#define GTF_V_FP		1	/* vertical front porch (lines) */
+#define GTF_CELL_GRAN		8	/* character cell granularity */
+
+/* Default */
+#define GTF_D_M			600	/* blanking formula gradient */
+#define GTF_D_C			40	/* blanking formula offset */
+#define GTF_D_K			128	/* blanking formula scaling factor */
+#define GTF_D_J			20	/* blanking formula scaling factor */
+#define GTF_D_C_PRIME ((((GTF_D_C - GTF_D_J) * GTF_D_K) / 256) + GTF_D_J)
+#define GTF_D_M_PRIME ((GTF_D_K * GTF_D_M) / 256)
+
+/* Secondary */
+#define GTF_S_M			3600	/* blanking formula gradient */
+#define GTF_S_C			40	/* blanking formula offset */
+#define GTF_S_K			128	/* blanking formula scaling factor */
+#define GTF_S_J			35	/* blanking formula scaling factor */
+#define GTF_S_C_PRIME ((((GTF_S_C - GTF_S_J) * GTF_S_K) / 256) + GTF_S_J)
+#define GTF_S_M_PRIME ((GTF_S_K * GTF_S_M) / 256)
+
+/** v4l2_detect_gtf - detect if the given timings follow the GTF standard
+ * @frame_height - the total height of the frame (including blanking) in lines.
+ * @hfreq - the horizontal frequency in Hz.
+ * @vsync - the height of the vertical sync in lines.
+ * @polarities - the horizontal and vertical polarities (same as struct
+ *		v4l2_bt_timings polarities).
+ * @aspect - preferred aspect ratio. GTF has no method of determining the
+ *		aspect ratio in order to derive the image width from the
+ *		image height, so it has to be passed explicitly. Usually
+ *		the native screen aspect ratio is used for this. If it
+ *		is not filled in correctly, then 16:9 will be assumed.
+ * @fmt - the resulting timings.
+ *
+ * This function will attempt to detect if the given values correspond to a
+ * valid GTF format. If so, then it will return true, and fmt will be filled
+ * in with the found GTF timings.
+ */
+bool v4l2_detect_gtf(unsigned frame_height,
+		unsigned hfreq,
+		unsigned vsync,
+		u32 polarities,
+		struct v4l2_fract aspect,
+		struct v4l2_dv_timings *fmt)
+{
+	int pix_clk;
+	int  v_fp, v_bp, h_fp, h_bp, hsync;
+	int frame_width, image_height, image_width;
+	bool default_gtf;
+	int h_blank;
+
+	if (vsync != 3)
+		return false;
+
+	if (polarities == V4L2_DV_VSYNC_POS_POL)
+		default_gtf = true;
+	else if (polarities == V4L2_DV_HSYNC_POS_POL)
+		default_gtf = false;
+	else
+		return false;
+
+	/* Vertical */
+	v_fp = GTF_V_FP;
+	v_bp = (GTF_MIN_VSYNC_BP * hfreq + 999999) / 1000000 - vsync;
+	image_height = (frame_height - v_fp - vsync - v_bp + 1) & ~0x1;
+
+	if (aspect.numerator == 0 || aspect.denominator == 0) {
+		aspect.numerator = 16;
+		aspect.denominator = 9;
+	}
+	image_width = ((image_height * aspect.numerator) / aspect.denominator);
+
+	/* Horizontal */
+	if (default_gtf)
+		h_blank = ((image_width * GTF_D_C_PRIME * hfreq) -
+					(image_width * GTF_D_M_PRIME * 1000) +
+			(hfreq * (100 - GTF_D_C_PRIME) + GTF_D_M_PRIME * 1000) / 2) /
+			(hfreq * (100 - GTF_D_C_PRIME) + GTF_D_M_PRIME * 1000);
+	else
+		h_blank = ((image_width * GTF_S_C_PRIME * hfreq) -
+					(image_width * GTF_S_M_PRIME * 1000) +
+			(hfreq * (100 - GTF_S_C_PRIME) + GTF_S_M_PRIME * 1000) / 2) /
+			(hfreq * (100 - GTF_S_C_PRIME) + GTF_S_M_PRIME * 1000);
+
+	h_blank = h_blank - h_blank % (2 * GTF_CELL_GRAN);
+	frame_width = image_width + h_blank;
+
+	pix_clk = (image_width + h_blank) * hfreq;
+	pix_clk = pix_clk / GTF_PXL_CLK_GRAN * GTF_PXL_CLK_GRAN;
+
+	hsync = (frame_width * 8 + 50) / 100;
+	hsync = hsync - hsync % GTF_CELL_GRAN;
+
+	h_fp = h_blank / 2 - hsync;
+	h_bp = h_blank / 2;
+
+	fmt->bt.polarities = polarities;
+	fmt->bt.width = image_width;
+	fmt->bt.height = image_height;
+	fmt->bt.hfrontporch = h_fp;
+	fmt->bt.vfrontporch = v_fp;
+	fmt->bt.hsync = hsync;
+	fmt->bt.vsync = vsync;
+	fmt->bt.hbackporch = frame_width - image_width - h_fp - hsync;
+	fmt->bt.vbackporch = frame_height - image_height - v_fp - vsync;
+	fmt->bt.pixelclock = pix_clk;
+	fmt->bt.standards = V4L2_DV_BT_STD_GTF;
+	if (!default_gtf)
+		fmt->bt.flags |= V4L2_DV_FL_REDUCED_BLANKING;
+	return true;
+}
+EXPORT_SYMBOL_GPL(v4l2_detect_gtf);
+
+/** v4l2_calc_aspect_ratio - calculate the aspect ratio based on bytes
+ *	0x15 and 0x16 from the EDID.
+ * @hor_landscape - byte 0x15 from the EDID.
+ * @vert_portrait - byte 0x16 from the EDID.
+ *
+ * Determines the aspect ratio from the EDID.
+ * See VESA Enhanced EDID standard, release A, rev 2, section 3.6.2:
+ * "Horizontal and Vertical Screen Size or Aspect Ratio"
+ */
+struct v4l2_fract v4l2_calc_aspect_ratio(u8 hor_landscape, u8 vert_portrait)
+{
+	struct v4l2_fract aspect = { 16, 9 };
+	u32 tmp;
+	u8 ratio;
+
+	/* Nothing filled in, fallback to 16:9 */
+	if (!hor_landscape && !vert_portrait)
+		return aspect;
+	/* Both filled in, so they are interpreted as the screen size in cm */
+	if (hor_landscape && vert_portrait) {
+		aspect.numerator = hor_landscape;
+		aspect.denominator = vert_portrait;
+		return aspect;
+	}
+	/* Only one is filled in, so interpret them as a ratio:
+	   (val + 99) / 100 */
+	ratio = hor_landscape | vert_portrait;
+	/* Change some rounded values into the exact aspect ratio */
+	if (ratio == 79) {
+		aspect.numerator = 16;
+		aspect.denominator = 9;
+	} else if (ratio == 34) {
+		aspect.numerator = 4;
+		aspect.numerator = 3;
+	} else if (ratio == 68) {
+		aspect.numerator = 15;
+		aspect.numerator = 9;
+	} else {
+		aspect.numerator = hor_landscape + 99;
+		aspect.denominator = 100;
+	}
+	if (hor_landscape)
+		return aspect;
+	/* The aspect ratio is for portrait, so swap numerator and denominator */
+	tmp = aspect.denominator;
+	aspect.denominator = aspect.numerator;
+	aspect.numerator = tmp;
+	return aspect;
+}
+EXPORT_SYMBOL_GPL(v4l2_calc_aspect_ratio);
+
 const struct v4l2_frmsize_discrete *v4l2_find_nearest_format(
 		const struct v4l2_discrete_probe *probe,
 		s32 width, s32 height)
diff --git a/include/media/v4l2-common.h b/include/media/v4l2-common.h
index b43b968..6df9554 100644
--- a/include/media/v4l2-common.h
+++ b/include/media/v4l2-common.h
@@ -216,4 +216,13 @@ bool v4l_match_dv_timings(const struct v4l2_dv_timings *t1,
 			  const struct v4l2_dv_timings *t2,
 			  unsigned pclock_delta);
 
+bool v4l2_detect_cvt(unsigned frame_height, unsigned hfreq, unsigned vsync,
+		u32 polarities, struct v4l2_dv_timings *fmt);
+
+bool v4l2_detect_gtf(unsigned frame_height, unsigned hfreq, unsigned vsync,
+		u32 polarities, struct v4l2_fract aspect,
+		struct v4l2_dv_timings *fmt);
+
+struct v4l2_fract v4l2_calc_aspect_ratio(u8 hor_landscape, u8 vert_portrait);
+
 #endif /* V4L2_COMMON_H_ */
-- 
1.7.10.4


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

* [RFCv3 PATCH 7/8] adv7604: driver for the Analog Devices ADV7604 video decoder.
  2012-08-10 11:21 ` [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort Hans Verkuil
                     ` (4 preceding siblings ...)
  2012-08-10 11:21   ` [RFCv3 PATCH 6/8] v4l2-common: add CVT and GTF detection functions Hans Verkuil
@ 2012-08-10 11:21   ` Hans Verkuil
  2012-08-10 11:21   ` [RFCv3 PATCH 8/8] ad9389b: driver for the Analog Devices AD9389B video encoder Hans Verkuil
  2012-08-13 11:48   ` [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort Soby Mathew
  7 siblings, 0 replies; 13+ messages in thread
From: Hans Verkuil @ 2012-08-10 11:21 UTC (permalink / raw)
  To: linux-media
  Cc: marbugge, Soby Mathew, mats.randgaard, manjunath.hadli,
	Tomasz Stanislawski, Mauro Carvalho Chehab, Scott Jiang,
	dri-devel

Initial version of this driver.

The full datasheets are available from the Analog Devices website:

  http://ez.analog.com/docs/DOC-1545

Not all features of the receiver are supported by this driver for various
reasons. Most notably:

- No CEC support (the CEC API needs a lot more discussion)
- Only port A of the four HDMI input ports is implemented (our hardware only
  uses that port)
- No HDCP repeater support (we don't use that either)

And since there are some 600-odd pages of datasheet for this single device,
I'm sure that there are many more things missing, but this driver does work
well for our hardware.

Note that I am using the register addresses instead of register names: the
datasheet containing the register descriptions is organized by register
address. Using names would make the datasheet lookup very hard. An attempt
was made to try and document what is being done when registers are used
instead.

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
---
 drivers/media/video/Kconfig     |   12 +
 drivers/media/video/Makefile    |    1 +
 drivers/media/video/adv7604.c   | 1959 +++++++++++++++++++++++++++++++++++++++
 include/media/adv7604.h         |  153 +++
 include/media/v4l2-chip-ident.h |    3 +
 5 files changed, 2128 insertions(+)
 create mode 100644 drivers/media/video/adv7604.c
 create mode 100644 include/media/adv7604.h

diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig
index d5df1fd..6d92d2d 100644
--- a/drivers/media/video/Kconfig
+++ b/drivers/media/video/Kconfig
@@ -284,6 +284,18 @@ config VIDEO_ADV7183
 	  To compile this driver as a module, choose M here: the
 	  module will be called adv7183.
 
+config VIDEO_ADV7604
+	tristate "Analog Devices ADV7604 decoder"
+	depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API
+	---help---
+	  Support for the Analog Devices ADV7604 video decoder.
+
+	  This is a Analog Devices Component/Graphics Digitizer
+	  with 4:1 Multiplexed HDMI Receiver.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called adv7604.
+
 config VIDEO_BT819
 	tristate "BT819A VideoStream decoder"
 	depends on VIDEO_V4L2 && I2C
diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile
index b7ada61..133dd9b 100644
--- a/drivers/media/video/Makefile
+++ b/drivers/media/video/Makefile
@@ -46,6 +46,7 @@ obj-$(CONFIG_VIDEO_ADV7180) += adv7180.o
 obj-$(CONFIG_VIDEO_ADV7183) += adv7183.o
 obj-$(CONFIG_VIDEO_ADV7343) += adv7343.o
 obj-$(CONFIG_VIDEO_ADV7393) += adv7393.o
+obj-$(CONFIG_VIDEO_ADV7604) += adv7604.o
 obj-$(CONFIG_VIDEO_VPX3220) += vpx3220.o
 obj-$(CONFIG_VIDEO_VS6624)  += vs6624.o
 obj-$(CONFIG_VIDEO_BT819) += bt819.o
diff --git a/drivers/media/video/adv7604.c b/drivers/media/video/adv7604.c
new file mode 100644
index 0000000..109bc9b
--- /dev/null
+++ b/drivers/media/video/adv7604.c
@@ -0,0 +1,1959 @@
+/*
+ * adv7604 - Analog Devices ADV7604 video decoder driver
+ *
+ * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+/*
+ * References (c = chapter, p = page):
+ * REF_01 - Analog devices, ADV7604, Register Settings Recommendations,
+ *		Revision 2.5, June 2010
+ * REF_02 - Analog devices, Register map documentation, Documentation of
+ *		the register maps, Software manual, Rev. F, June 2010
+ * REF_03 - Analog devices, ADV7604, Hardware Manual, Rev. F, August 2010
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/videodev2.h>
+#include <linux/workqueue.h>
+#include <linux/v4l2-dv-timings.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-chip-ident.h>
+#include <media/adv7604.h>
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "debug level (0-2)");
+
+MODULE_DESCRIPTION("Analog Devices ADV7604 video decoder driver");
+MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>");
+MODULE_AUTHOR("Mats Randgaard <mats.randgaard@cisco.com>");
+MODULE_LICENSE("GPL");
+
+/* ADV7604 system clock frequency */
+#define ADV7604_fsc (28636360)
+
+#define DIGITAL_INPUT ((state->prim_mode == ADV7604_PRIM_MODE_HDMI_COMP) || \
+			(state->prim_mode == ADV7604_PRIM_MODE_HDMI_GR))
+
+/*
+ **********************************************************************
+ *
+ *  Arrays with configuration parameters for the ADV7604
+ *
+ **********************************************************************
+ */
+struct adv7604_state {
+	struct adv7604_platform_data pdata;
+	struct v4l2_subdev sd;
+	struct media_pad pad;
+	struct v4l2_ctrl_handler hdl;
+	enum adv7604_prim_mode prim_mode;
+	struct v4l2_dv_timings timings;
+	u8 edid[256];
+	unsigned edid_blocks;
+	struct v4l2_fract aspect_ratio;
+	u32 rgb_quantization_range;
+	struct workqueue_struct *work_queues;
+	struct delayed_work delayed_work_enable_hotplug;
+	bool connector_hdmi;
+
+	/* i2c clients */
+	struct i2c_client *i2c_avlink;
+	struct i2c_client *i2c_cec;
+	struct i2c_client *i2c_infoframe;
+	struct i2c_client *i2c_esdp;
+	struct i2c_client *i2c_dpp;
+	struct i2c_client *i2c_afe;
+	struct i2c_client *i2c_repeater;
+	struct i2c_client *i2c_edid;
+	struct i2c_client *i2c_hdmi;
+	struct i2c_client *i2c_test;
+	struct i2c_client *i2c_cp;
+	struct i2c_client *i2c_vdp;
+
+	/* controls */
+	struct v4l2_ctrl *detect_tx_5v_ctrl;
+	struct v4l2_ctrl *analog_sampling_phase_ctrl;
+	struct v4l2_ctrl *free_run_color_manual_ctrl;
+	struct v4l2_ctrl *free_run_color_ctrl;
+	struct v4l2_ctrl *rgb_quantization_range_ctrl;
+};
+
+/* Supported CEA and DMT timings */
+static const struct v4l2_dv_timings adv7604_timings[] = {
+	V4L2_DV_BT_CEA_720X480P59_94,
+	V4L2_DV_BT_CEA_720X576P50,
+	V4L2_DV_BT_CEA_1280X720P24,
+	V4L2_DV_BT_CEA_1280X720P25,
+	V4L2_DV_BT_CEA_1280X720P30,
+	V4L2_DV_BT_CEA_1280X720P50,
+	V4L2_DV_BT_CEA_1280X720P60,
+	V4L2_DV_BT_CEA_1920X1080P24,
+	V4L2_DV_BT_CEA_1920X1080P25,
+	V4L2_DV_BT_CEA_1920X1080P30,
+	V4L2_DV_BT_CEA_1920X1080P50,
+	V4L2_DV_BT_CEA_1920X1080P60,
+
+	V4L2_DV_BT_DMT_640X350P85,
+	V4L2_DV_BT_DMT_640X400P85,
+	V4L2_DV_BT_DMT_720X400P85,
+	V4L2_DV_BT_DMT_640X480P60,
+	V4L2_DV_BT_DMT_640X480P72,
+	V4L2_DV_BT_DMT_640X480P75,
+	V4L2_DV_BT_DMT_640X480P85,
+	V4L2_DV_BT_DMT_800X600P56,
+	V4L2_DV_BT_DMT_800X600P60,
+	V4L2_DV_BT_DMT_800X600P72,
+	V4L2_DV_BT_DMT_800X600P75,
+	V4L2_DV_BT_DMT_800X600P85,
+	V4L2_DV_BT_DMT_848X480P60,
+	V4L2_DV_BT_DMT_1024X768P60,
+	V4L2_DV_BT_DMT_1024X768P70,
+	V4L2_DV_BT_DMT_1024X768P75,
+	V4L2_DV_BT_DMT_1024X768P85,
+	V4L2_DV_BT_DMT_1152X864P75,
+	V4L2_DV_BT_DMT_1280X768P60_RB,
+	V4L2_DV_BT_DMT_1280X768P60,
+	V4L2_DV_BT_DMT_1280X768P75,
+	V4L2_DV_BT_DMT_1280X768P85,
+	V4L2_DV_BT_DMT_1280X800P60_RB,
+	V4L2_DV_BT_DMT_1280X800P60,
+	V4L2_DV_BT_DMT_1280X800P75,
+	V4L2_DV_BT_DMT_1280X800P85,
+	V4L2_DV_BT_DMT_1280X960P60,
+	V4L2_DV_BT_DMT_1280X960P85,
+	V4L2_DV_BT_DMT_1280X1024P60,
+	V4L2_DV_BT_DMT_1280X1024P75,
+	V4L2_DV_BT_DMT_1280X1024P85,
+	V4L2_DV_BT_DMT_1360X768P60,
+	V4L2_DV_BT_DMT_1400X1050P60_RB,
+	V4L2_DV_BT_DMT_1400X1050P60,
+	V4L2_DV_BT_DMT_1400X1050P75,
+	V4L2_DV_BT_DMT_1400X1050P85,
+	V4L2_DV_BT_DMT_1440X900P60_RB,
+	V4L2_DV_BT_DMT_1440X900P60,
+	V4L2_DV_BT_DMT_1600X1200P60,
+	V4L2_DV_BT_DMT_1680X1050P60_RB,
+	V4L2_DV_BT_DMT_1680X1050P60,
+	V4L2_DV_BT_DMT_1792X1344P60,
+	V4L2_DV_BT_DMT_1856X1392P60,
+	V4L2_DV_BT_DMT_1920X1200P60_RB,
+	V4L2_DV_BT_DMT_1366X768P60,
+	V4L2_DV_BT_DMT_1920X1080P60,
+	{ },
+};
+
+/* ----------------------------------------------------------------------- */
+
+static inline struct adv7604_state *to_state(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct adv7604_state, sd);
+}
+
+static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl)
+{
+	return &container_of(ctrl->handler, struct adv7604_state, hdl)->sd;
+}
+
+static inline unsigned hblanking(const struct v4l2_bt_timings *t)
+{
+	return t->hfrontporch + t->hsync + t->hbackporch;
+}
+
+static inline unsigned htotal(const struct v4l2_bt_timings *t)
+{
+	return t->width + t->hfrontporch + t->hsync + t->hbackporch;
+}
+
+static inline unsigned vblanking(const struct v4l2_bt_timings *t)
+{
+	return t->vfrontporch + t->vsync + t->vbackporch;
+}
+
+static inline unsigned vtotal(const struct v4l2_bt_timings *t)
+{
+	return t->height + t->vfrontporch + t->vsync + t->vbackporch;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static s32 adv_smbus_read_byte_data_check(struct i2c_client *client,
+		u8 command, bool check)
+{
+	union i2c_smbus_data data;
+
+	if (!i2c_smbus_xfer(client->adapter, client->addr, client->flags,
+			I2C_SMBUS_READ, command,
+			I2C_SMBUS_BYTE_DATA, &data))
+		return data.byte;
+	if (check)
+		v4l_err(client, "error reading %02x, %02x\n",
+				client->addr, command);
+	return -EIO;
+}
+
+static s32 adv_smbus_read_byte_data(struct i2c_client *client, u8 command)
+{
+	return adv_smbus_read_byte_data_check(client, command, true);
+}
+
+static s32 adv_smbus_write_byte_data(struct i2c_client *client,
+					u8 command, u8 value)
+{
+	union i2c_smbus_data data;
+	int err;
+	int i;
+
+	data.byte = value;
+	for (i = 0; i < 3; i++) {
+		err = i2c_smbus_xfer(client->adapter, client->addr,
+				client->flags,
+				I2C_SMBUS_WRITE, command,
+				I2C_SMBUS_BYTE_DATA, &data);
+		if (!err)
+			break;
+	}
+	if (err < 0)
+		v4l_err(client, "error writing %02x, %02x, %02x\n",
+				client->addr, command, value);
+	return err;
+}
+
+static s32 adv_smbus_write_i2c_block_data(struct i2c_client *client,
+	       u8 command, unsigned length, const u8 *values)
+{
+	union i2c_smbus_data data;
+
+	if (length > I2C_SMBUS_BLOCK_MAX)
+		length = I2C_SMBUS_BLOCK_MAX;
+	data.block[0] = length;
+	memcpy(data.block + 1, values, length);
+	return i2c_smbus_xfer(client->adapter, client->addr, client->flags,
+			      I2C_SMBUS_WRITE, command,
+			      I2C_SMBUS_I2C_BLOCK_DATA, &data);
+}
+
+/* ----------------------------------------------------------------------- */
+
+static inline int io_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	return adv_smbus_read_byte_data(client, reg);
+}
+
+static inline int io_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	return adv_smbus_write_byte_data(client, reg, val);
+}
+
+static inline int io_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val)
+{
+	return io_write(sd, reg, (io_read(sd, reg) & mask) | val);
+}
+
+static inline int avlink_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_avlink, reg);
+}
+
+static inline int avlink_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_avlink, reg, val);
+}
+
+static inline int cec_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_cec, reg);
+}
+
+static inline int cec_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_cec, reg, val);
+}
+
+static inline int cec_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val)
+{
+	return cec_write(sd, reg, (cec_read(sd, reg) & mask) | val);
+}
+
+static inline int infoframe_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_infoframe, reg);
+}
+
+static inline int infoframe_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_infoframe, reg, val);
+}
+
+static inline int esdp_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_esdp, reg);
+}
+
+static inline int esdp_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_esdp, reg, val);
+}
+
+static inline int dpp_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_dpp, reg);
+}
+
+static inline int dpp_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_dpp, reg, val);
+}
+
+static inline int afe_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_afe, reg);
+}
+
+static inline int afe_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_afe, reg, val);
+}
+
+static inline int rep_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_repeater, reg);
+}
+
+static inline int rep_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_repeater, reg, val);
+}
+
+static inline int rep_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val)
+{
+	return rep_write(sd, reg, (rep_read(sd, reg) & mask) | val);
+}
+
+static inline int edid_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_edid, reg);
+}
+
+static inline int edid_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_edid, reg, val);
+}
+
+static inline int edid_read_block(struct v4l2_subdev *sd, unsigned len, u8 *val)
+{
+	struct adv7604_state *state = to_state(sd);
+	struct i2c_client *client = state->i2c_edid;
+	u8 msgbuf0[1] = { 0 };
+	u8 msgbuf1[256];
+	struct i2c_msg msg[2] = { { client->addr, 0, 1, msgbuf0 },
+				  { client->addr, 0 | I2C_M_RD, len, msgbuf1 }
+				};
+
+	if (i2c_transfer(client->adapter, msg, 2) < 0)
+		return -EIO;
+	memcpy(val, msgbuf1, len);
+	return 0;
+}
+
+static void adv7604_delayed_work_enable_hotplug(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct adv7604_state *state = container_of(dwork, struct adv7604_state,
+						delayed_work_enable_hotplug);
+	struct v4l2_subdev *sd = &state->sd;
+
+	v4l2_dbg(2, debug, sd, "%s: enable hotplug\n", __func__);
+
+	v4l2_subdev_notify(sd, ADV7604_HOTPLUG, (void *)1);
+}
+
+static inline int edid_write_block(struct v4l2_subdev *sd,
+					unsigned len, const u8 *val)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct adv7604_state *state = to_state(sd);
+	int err = 0;
+	int i;
+
+	v4l2_dbg(2, debug, sd, "%s: write EDID block (%d byte)\n", __func__, len);
+
+	v4l2_subdev_notify(sd, ADV7604_HOTPLUG, (void *)0);
+
+	/* Disables I2C access to internal EDID ram from DDC port */
+	rep_write_and_or(sd, 0x77, 0xf0, 0x0);
+
+	for (i = 0; !err && i < len; i += I2C_SMBUS_BLOCK_MAX)
+		err = adv_smbus_write_i2c_block_data(state->i2c_edid, i,
+				I2C_SMBUS_BLOCK_MAX, val + i);
+	if (err)
+		return err;
+
+	/* adv7604 calculates the checksums and enables I2C access to internal
+	   EDID ram from DDC port. */
+	rep_write_and_or(sd, 0x77, 0xf0, 0x1);
+
+	for (i = 0; i < 1000; i++) {
+		if (rep_read(sd, 0x7d) & 1)
+			break;
+		mdelay(1);
+	}
+	if (i == 1000) {
+		v4l_err(client, "error enabling edid\n");
+		return -EIO;
+	}
+
+	/* enable hotplug after 100 ms */
+	queue_delayed_work(state->work_queues,
+			&state->delayed_work_enable_hotplug, HZ / 10);
+	return 0;
+}
+
+static inline int hdmi_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_hdmi, reg);
+}
+
+static inline int hdmi_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_hdmi, reg, val);
+}
+
+static inline int test_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_test, reg);
+}
+
+static inline int test_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_test, reg, val);
+}
+
+static inline int cp_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_cp, reg);
+}
+
+static inline int cp_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_cp, reg, val);
+}
+
+static inline int cp_write_and_or(struct v4l2_subdev *sd, u8 reg, u8 mask, u8 val)
+{
+	return cp_write(sd, reg, (cp_read(sd, reg) & mask) | val);
+}
+
+static inline int vdp_read(struct v4l2_subdev *sd, u8 reg)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_read_byte_data(state->i2c_vdp, reg);
+}
+
+static inline int vdp_write(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	return adv_smbus_write_byte_data(state->i2c_vdp, reg, val);
+}
+
+/* ----------------------------------------------------------------------- */
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static void adv7604_inv_register(struct v4l2_subdev *sd)
+{
+	v4l2_info(sd, "0x000-0x0ff: IO Map\n");
+	v4l2_info(sd, "0x100-0x1ff: AVLink Map\n");
+	v4l2_info(sd, "0x200-0x2ff: CEC Map\n");
+	v4l2_info(sd, "0x300-0x3ff: InfoFrame Map\n");
+	v4l2_info(sd, "0x400-0x4ff: ESDP Map\n");
+	v4l2_info(sd, "0x500-0x5ff: DPP Map\n");
+	v4l2_info(sd, "0x600-0x6ff: AFE Map\n");
+	v4l2_info(sd, "0x700-0x7ff: Repeater Map\n");
+	v4l2_info(sd, "0x800-0x8ff: EDID Map\n");
+	v4l2_info(sd, "0x900-0x9ff: HDMI Map\n");
+	v4l2_info(sd, "0xa00-0xaff: Test Map\n");
+	v4l2_info(sd, "0xb00-0xbff: CP Map\n");
+	v4l2_info(sd, "0xc00-0xcff: VDP Map\n");
+}
+
+static int adv7604_g_register(struct v4l2_subdev *sd,
+					struct v4l2_dbg_register *reg)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	if (!v4l2_chip_match_i2c_client(client, &reg->match))
+		return -EINVAL;
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+	reg->size = 1;
+	switch (reg->reg >> 8) {
+	case 0:
+		reg->val = io_read(sd, reg->reg & 0xff);
+		break;
+	case 1:
+		reg->val = avlink_read(sd, reg->reg & 0xff);
+		break;
+	case 2:
+		reg->val = cec_read(sd, reg->reg & 0xff);
+		break;
+	case 3:
+		reg->val = infoframe_read(sd, reg->reg & 0xff);
+		break;
+	case 4:
+		reg->val = esdp_read(sd, reg->reg & 0xff);
+		break;
+	case 5:
+		reg->val = dpp_read(sd, reg->reg & 0xff);
+		break;
+	case 6:
+		reg->val = afe_read(sd, reg->reg & 0xff);
+		break;
+	case 7:
+		reg->val = rep_read(sd, reg->reg & 0xff);
+		break;
+	case 8:
+		reg->val = edid_read(sd, reg->reg & 0xff);
+		break;
+	case 9:
+		reg->val = hdmi_read(sd, reg->reg & 0xff);
+		break;
+	case 0xa:
+		reg->val = test_read(sd, reg->reg & 0xff);
+		break;
+	case 0xb:
+		reg->val = cp_read(sd, reg->reg & 0xff);
+		break;
+	case 0xc:
+		reg->val = vdp_read(sd, reg->reg & 0xff);
+		break;
+	default:
+		v4l2_info(sd, "Register %03llx not supported\n", reg->reg);
+		adv7604_inv_register(sd);
+		break;
+	}
+	return 0;
+}
+
+static int adv7604_s_register(struct v4l2_subdev *sd,
+					struct v4l2_dbg_register *reg)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	if (!v4l2_chip_match_i2c_client(client, &reg->match))
+		return -EINVAL;
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+	switch (reg->reg >> 8) {
+	case 0:
+		io_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 1:
+		avlink_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 2:
+		cec_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 3:
+		infoframe_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 4:
+		esdp_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 5:
+		dpp_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 6:
+		afe_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 7:
+		rep_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 8:
+		edid_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 9:
+		hdmi_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 0xa:
+		test_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 0xb:
+		cp_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	case 0xc:
+		vdp_write(sd, reg->reg & 0xff, reg->val & 0xff);
+		break;
+	default:
+		v4l2_info(sd, "Register %03llx not supported\n", reg->reg);
+		adv7604_inv_register(sd);
+		break;
+	}
+	return 0;
+}
+#endif
+
+static int adv7604_s_detect_tx_5v_ctrl(struct v4l2_subdev *sd)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	/* port A only */
+	return v4l2_ctrl_s_ctrl(state->detect_tx_5v_ctrl,
+				((io_read(sd, 0x6f) & 0x10) >> 4));
+}
+
+static void configure_free_run(struct v4l2_subdev *sd, const struct v4l2_bt_timings *timings)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	u32 width = htotal(timings);
+	u32 height = vtotal(timings);
+	u16 ch1_fr_ll = (((u32)timings->pixelclock / 100) > 0) ?
+		((width * (ADV7604_fsc / 100)) / ((u32)timings->pixelclock / 100)) : 0;
+
+	v4l2_dbg(2, debug, sd, "%s\n", __func__);
+
+	cp_write(sd, 0x8f, (ch1_fr_ll >> 8) & 0x7);	/* CH1_FR_LL */
+	cp_write(sd, 0x90, ch1_fr_ll & 0xff);		/* CH1_FR_LL */
+	cp_write(sd, 0xab, (height >> 4) & 0xff); /* CP_LCOUNT_MAX */
+	cp_write(sd, 0xac, (height & 0x0f) << 4); /* CP_LCOUNT_MAX */
+	/* TODO support interlaced */
+	cp_write(sd, 0x91, 0x10);	/* INTERLACED */
+
+	/* Should only be set in auto-graphics mode [REF_02 p. 91-92] */
+	if ((io_read(sd, 0x00) == 0x07) && (io_read(sd, 0x01) == 0x02)) {
+		u16 cp_start_sav, cp_start_eav, cp_start_vbi, cp_end_vbi;
+		const u8 pll[2] = {
+			(0xc0 | ((width >> 8) & 0x1f)),
+			(width & 0xff)
+		};
+
+		/* setup PLL_DIV_MAN_EN and PLL_DIV_RATIO */
+		/* IO-map reg. 0x16 and 0x17 should be written in sequence */
+		if (adv_smbus_write_i2c_block_data(client, 0x16, 2, pll)) {
+			v4l2_err(sd, "writing to reg 0x16 and 0x17 failed\n");
+			return;
+		}
+
+		/* active video - horizontal timing */
+		cp_start_sav = timings->hsync + timings->hbackporch - 4;
+		cp_start_eav = width - timings->hfrontporch;
+		cp_write(sd, 0xa2, (cp_start_sav >> 4) & 0xff);
+		cp_write(sd, 0xa3, ((cp_start_sav & 0x0f) << 4) | ((cp_start_eav >> 8) & 0x0f));
+		cp_write(sd, 0xa4, cp_start_eav & 0xff);
+
+		/* active video - vertical timing */
+		cp_start_vbi = height - timings->vfrontporch;
+		cp_end_vbi = timings->vsync + timings->vbackporch;
+		cp_write(sd, 0xa5, (cp_start_vbi >> 4) & 0xff);
+		cp_write(sd, 0xa6, ((cp_start_vbi & 0xf) << 4) | ((cp_end_vbi >> 8) & 0xf));
+		cp_write(sd, 0xa7, cp_end_vbi & 0xff);
+	} else {
+		/* reset to default values */
+		io_write(sd, 0x16, 0x43);
+		io_write(sd, 0x17, 0x5a);
+		cp_write(sd, 0xa2, 0x00);
+		cp_write(sd, 0xa3, 0x00);
+		cp_write(sd, 0xa4, 0x00);
+		cp_write(sd, 0xa5, 0x00);
+		cp_write(sd, 0xa6, 0x00);
+		cp_write(sd, 0xa7, 0x00);
+	}
+}
+
+
+static void set_rgb_quantization_range(struct v4l2_subdev *sd)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	switch (state->rgb_quantization_range) {
+	case V4L2_DV_RGB_RANGE_AUTO:
+		/* automatic */
+		if ((hdmi_read(sd, 0x05) & 0x80) ||
+				(state->prim_mode == ADV7604_PRIM_MODE_COMP) ||
+				(state->prim_mode == ADV7604_PRIM_MODE_RGB)) {
+			/* receiving HDMI or analog signal */
+			io_write_and_or(sd, 0x02, 0x0f, 0xf0);
+		} else {
+			/* receiving DVI-D signal */
+
+			/* ADV7604 selects RGB limited range regardless of
+			   input format (CE/IT) in automatic mode */
+			if (state->timings.bt.standards & V4L2_DV_BT_STD_CEA861) {
+				/* RGB limited range (16-235) */
+				io_write_and_or(sd, 0x02, 0x0f, 0x00);
+
+			} else {
+				/* RGB full range (0-255) */
+				io_write_and_or(sd, 0x02, 0x0f, 0x10);
+			}
+		}
+		break;
+	case V4L2_DV_RGB_RANGE_LIMITED:
+		/* RGB limited range (16-235) */
+		io_write_and_or(sd, 0x02, 0x0f, 0x00);
+		break;
+	case V4L2_DV_RGB_RANGE_FULL:
+		/* RGB full range (0-255) */
+		io_write_and_or(sd, 0x02, 0x0f, 0x10);
+		break;
+	}
+}
+
+
+static int adv7604_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct v4l2_subdev *sd = to_sd(ctrl);
+	struct adv7604_state *state = to_state(sd);
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		cp_write(sd, 0x3c, ctrl->val);
+		return 0;
+	case V4L2_CID_CONTRAST:
+		cp_write(sd, 0x3a, ctrl->val);
+		return 0;
+	case V4L2_CID_SATURATION:
+		cp_write(sd, 0x3b, ctrl->val);
+		return 0;
+	case V4L2_CID_HUE:
+		cp_write(sd, 0x3d, ctrl->val);
+		return 0;
+	case  V4L2_CID_DV_RX_RGB_RANGE:
+		state->rgb_quantization_range = ctrl->val;
+		set_rgb_quantization_range(sd);
+		return 0;
+	case V4L2_CID_ADV_RX_ANALOG_SAMPLING_PHASE:
+		/* Set the analog sampling phase. This is needed to find the
+		   best sampling phase for analog video: an application or
+		   driver has to try a number of phases and analyze the picture
+		   quality before settling on the best performing phase. */
+		afe_write(sd, 0xc8, ctrl->val);
+		return 0;
+	case V4L2_CID_ADV_RX_FREE_RUN_COLOR_MANUAL:
+		/* Use the default blue color for free running mode,
+		   or supply your own. */
+		cp_write_and_or(sd, 0xbf, ~0x04, (ctrl->val << 2));
+		return 0;
+	case V4L2_CID_ADV_RX_FREE_RUN_COLOR:
+		cp_write(sd, 0xc0, (ctrl->val & 0xff0000) >> 16);
+		cp_write(sd, 0xc1, (ctrl->val & 0x00ff00) >> 8);
+		cp_write(sd, 0xc2, (u8)(ctrl->val & 0x0000ff));
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int adv7604_g_chip_ident(struct v4l2_subdev *sd,
+					struct v4l2_dbg_chip_ident *chip)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_ADV7604, 0);
+}
+
+/* ----------------------------------------------------------------------- */
+
+static inline bool no_power(struct v4l2_subdev *sd)
+{
+	/* Entire chip or CP powered off */
+	return io_read(sd, 0x0c) & 0x24;
+}
+
+static inline bool no_signal_tmds(struct v4l2_subdev *sd)
+{
+	/* TODO port B, C and D */
+	return !(io_read(sd, 0x6a) & 0x10);
+}
+
+static inline bool no_lock_tmds(struct v4l2_subdev *sd)
+{
+	return (io_read(sd, 0x6a) & 0xe0) != 0xe0;
+}
+
+static inline bool no_lock_sspd(struct v4l2_subdev *sd)
+{
+	/* TODO channel 2 */
+	return ((cp_read(sd, 0xb5) & 0xd0) != 0xd0);
+}
+
+static inline bool no_lock_stdi(struct v4l2_subdev *sd)
+{
+	/* TODO channel 2 */
+	return !(cp_read(sd, 0xb1) & 0x80);
+}
+
+static inline bool no_signal(struct v4l2_subdev *sd)
+{
+	struct adv7604_state *state = to_state(sd);
+	bool ret;
+
+	ret = no_power(sd);
+
+	ret |= no_lock_stdi(sd);
+	ret |= no_lock_sspd(sd);
+
+	if (DIGITAL_INPUT) {
+		ret |= no_lock_tmds(sd);
+		ret |= no_signal_tmds(sd);
+	}
+
+	return ret;
+}
+
+static inline bool no_lock_cp(struct v4l2_subdev *sd)
+{
+	/* CP has detected a non standard number of lines on the incoming
+	   video compared to what it is configured to receive by s_dv_timings */
+	return io_read(sd, 0x12) & 0x01;
+}
+
+static int adv7604_g_input_status(struct v4l2_subdev *sd, u32 *status)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	*status = 0;
+	*status |= no_power(sd) ? V4L2_IN_ST_NO_POWER : 0;
+	*status |= no_signal(sd) ? V4L2_IN_ST_NO_SIGNAL : 0;
+	if (no_lock_cp(sd))
+		*status |= DIGITAL_INPUT ? V4L2_IN_ST_NO_SYNC : V4L2_IN_ST_NO_H_LOCK;
+
+	v4l2_dbg(1, debug, sd, "%s: status = 0x%x\n", __func__, *status);
+
+	return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static void adv7604_print_timings(struct v4l2_subdev *sd,
+	struct v4l2_dv_timings *timings, const char *txt, bool detailed)
+{
+	struct v4l2_bt_timings *bt = &timings->bt;
+	u32 htot, vtot;
+
+	if (timings->type != V4L2_DV_BT_656_1120)
+		return;
+
+	htot = htotal(bt);
+	vtot = vtotal(bt);
+
+	v4l2_info(sd, "%s %dx%d%s%d (%dx%d)",
+			txt, bt->width, bt->height, bt->interlaced ? "i" : "p",
+			(htot * vtot) > 0 ? ((u32)bt->pixelclock /
+				(htot * vtot)) : 0,
+			htot, vtot);
+
+	if (detailed) {
+		v4l2_info(sd, "    horizontal: fp = %d, %ssync = %d, bp = %d\n",
+				bt->hfrontporch,
+				(bt->polarities & V4L2_DV_HSYNC_POS_POL) ? "+" : "-",
+				bt->hsync, bt->hbackporch);
+		v4l2_info(sd, "    vertical: fp = %d, %ssync = %d, bp = %d\n",
+				bt->vfrontporch,
+				(bt->polarities & V4L2_DV_VSYNC_POS_POL) ? "+" : "-",
+				bt->vsync, bt->vbackporch);
+		v4l2_info(sd, "    pixelclock: %lld, flags: 0x%x, standards: 0x%x\n",
+				bt->pixelclock, bt->flags, bt->standards);
+	}
+}
+
+struct stdi_readback {
+	u16 bl, lcf, lcvs;
+	u8 hs_pol, vs_pol;
+	bool interlaced;
+};
+
+static int stdi2dv_timings(struct v4l2_subdev *sd,
+		struct stdi_readback *stdi,
+		struct v4l2_dv_timings *timings)
+{
+	struct adv7604_state *state = to_state(sd);
+	u32 hfreq = (ADV7604_fsc * 8) / stdi->bl;
+	u32 pix_clk;
+	int i;
+
+	for (i = 0; adv7604_timings[i].bt.height; i++) {
+		if (vtotal(&adv7604_timings[i].bt) != stdi->lcf + 1)
+			continue;
+		if (adv7604_timings[i].bt.vsync != stdi->lcvs)
+			continue;
+
+		pix_clk = hfreq * htotal(&adv7604_timings[i].bt);
+
+		if ((pix_clk < adv7604_timings[i].bt.pixelclock + 1000000) &&
+		    (pix_clk > adv7604_timings[i].bt.pixelclock - 1000000)) {
+			*timings = adv7604_timings[i];
+			return 0;
+		}
+	}
+
+	if (v4l2_detect_cvt(stdi->lcf + 1, hfreq, stdi->lcvs,
+			(stdi->hs_pol == '+' ? V4L2_DV_HSYNC_POS_POL : 0) |
+			(stdi->vs_pol == '+' ? V4L2_DV_VSYNC_POS_POL : 0),
+			timings))
+		return 0;
+	if (v4l2_detect_gtf(stdi->lcf + 1, hfreq, stdi->lcvs,
+			(stdi->hs_pol == '+' ? V4L2_DV_HSYNC_POS_POL : 0) |
+			(stdi->vs_pol == '+' ? V4L2_DV_VSYNC_POS_POL : 0),
+			state->aspect_ratio, timings))
+		return 0;
+
+	v4l2_dbg(2, debug, sd, "%s: No format candidate found for lcf=%d, bl = %d\n",
+			__func__, stdi->lcf, stdi->bl);
+	return -1;
+}
+
+static int read_stdi(struct v4l2_subdev *sd, struct stdi_readback *stdi)
+{
+	if (no_lock_stdi(sd) || no_lock_sspd(sd)) {
+		v4l2_dbg(2, debug, sd, "%s: STDI and/or SSPD not locked\n", __func__);
+		return -1;
+	}
+
+	/* read STDI */
+	stdi->bl = ((cp_read(sd, 0xb1) & 0x3f) << 8) | cp_read(sd, 0xb2);
+	stdi->lcf = ((cp_read(sd, 0xb3) & 0x7) << 8) | cp_read(sd, 0xb4);
+	stdi->lcvs = cp_read(sd, 0xb3) >> 3;
+	stdi->interlaced = io_read(sd, 0x12) & 0x10;
+
+	/* read SSPD */
+	if ((cp_read(sd, 0xb5) & 0x03) == 0x01) {
+		stdi->hs_pol = ((cp_read(sd, 0xb5) & 0x10) ?
+				((cp_read(sd, 0xb5) & 0x08) ? '+' : '-') : 'x');
+		stdi->vs_pol = ((cp_read(sd, 0xb5) & 0x40) ?
+				((cp_read(sd, 0xb5) & 0x20) ? '+' : '-') : 'x');
+	} else {
+		stdi->hs_pol = 'x';
+		stdi->vs_pol = 'x';
+	}
+
+	if (no_lock_stdi(sd) || no_lock_sspd(sd)) {
+		v4l2_dbg(2, debug, sd,
+			"%s: signal lost during readout of STDI/SSPD\n", __func__);
+		return -1;
+	}
+
+	if (stdi->lcf < 239 || stdi->bl < 8 || stdi->bl == 0x3fff) {
+		v4l2_dbg(2, debug, sd, "%s: invalid signal\n", __func__);
+		memset(stdi, 0, sizeof(struct stdi_readback));
+		return -1;
+	}
+
+	v4l2_dbg(2, debug, sd,
+		"%s: lcf (frame height - 1) = %d, bl = %d, lcvs (vsync) = %d, %chsync, %cvsync, %s\n",
+		__func__, stdi->lcf, stdi->bl, stdi->lcvs,
+		stdi->hs_pol, stdi->vs_pol,
+		stdi->interlaced ? "interlaced" : "progressive");
+
+	return 0;
+}
+
+static int adv7604_enum_dv_timings(struct v4l2_subdev *sd,
+			struct v4l2_enum_dv_timings *timings)
+{
+	if (timings->index >= ARRAY_SIZE(adv7604_timings) - 1)
+		return -EINVAL;
+	memset(timings->reserved, 0, sizeof(timings->reserved));
+	timings->timings = adv7604_timings[timings->index];
+	return 0;
+}
+
+static int adv7604_dv_timings_cap(struct v4l2_subdev *sd,
+			struct v4l2_dv_timings_cap *cap)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	cap->type = V4L2_DV_BT_656_1120;
+	cap->bt.max_width = 1920;
+	cap->bt.max_height = 1200;
+	cap->bt.min_pixelclock = 27000000;
+	if (DIGITAL_INPUT)
+		cap->bt.max_pixelclock = 225000000;
+	else
+		cap->bt.max_pixelclock = 170000000;
+	cap->bt.standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
+			 V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT;
+	cap->bt.capabilities = V4L2_DV_BT_CAP_PROGRESSIVE |
+		V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM;
+	return 0;
+}
+
+/* Fill the optional fields .standards and .flags in struct v4l2_dv_timings
+   if the format is listed in adv7604_timings[] */
+static void adv7604_fill_optional_dv_timings_fields(struct v4l2_subdev *sd,
+		struct v4l2_dv_timings *timings)
+{
+	struct adv7604_state *state = to_state(sd);
+	int i;
+
+	for (i = 0; adv7604_timings[i].bt.width; i++) {
+		if (v4l_match_dv_timings(timings, &adv7604_timings[i],
+					DIGITAL_INPUT ? 250000 : 1000000)) {
+			*timings = adv7604_timings[i];
+			break;
+		}
+	}
+}
+
+static int adv7604_query_dv_timings(struct v4l2_subdev *sd,
+			struct v4l2_dv_timings *timings)
+{
+	struct adv7604_state *state = to_state(sd);
+	struct v4l2_bt_timings *bt = &timings->bt;
+	struct stdi_readback stdi;
+
+	if (!timings)
+		return -EINVAL;
+
+	memset(timings, 0, sizeof(struct v4l2_dv_timings));
+
+	if (no_signal(sd)) {
+		v4l2_dbg(1, debug, sd, "%s: no valid signal\n", __func__);
+		return -ENOLINK;
+	}
+
+	/* read STDI */
+	if (read_stdi(sd, &stdi)) {
+		v4l2_dbg(1, debug, sd, "%s: STDI/SSPD not locked\n", __func__);
+		return -ENOLINK;
+	}
+	bt->interlaced = stdi.interlaced ?
+		V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE;
+
+	if (DIGITAL_INPUT) {
+		timings->type = V4L2_DV_BT_656_1120;
+
+		bt->width = (hdmi_read(sd, 0x07) & 0x0f) * 256 + hdmi_read(sd, 0x08);
+		bt->height = (hdmi_read(sd, 0x09) & 0x0f) * 256 + hdmi_read(sd, 0x0a);
+		bt->pixelclock = (hdmi_read(sd, 0x06) * 1000000) +
+			((hdmi_read(sd, 0x3b) & 0x30) >> 4) * 250000;
+		bt->hfrontporch = (hdmi_read(sd, 0x20) & 0x03) * 256 +
+			hdmi_read(sd, 0x21);
+		bt->hsync = (hdmi_read(sd, 0x22) & 0x03) * 256 +
+			hdmi_read(sd, 0x23);
+		bt->hbackporch = (hdmi_read(sd, 0x24) & 0x03) * 256 +
+			hdmi_read(sd, 0x25);
+		bt->vfrontporch = ((hdmi_read(sd, 0x2a) & 0x1f) * 256 +
+			hdmi_read(sd, 0x2b)) / 2;
+		bt->vsync = ((hdmi_read(sd, 0x2e) & 0x1f) * 256 +
+			hdmi_read(sd, 0x2f)) / 2;
+		bt->vbackporch = ((hdmi_read(sd, 0x32) & 0x1f) * 256 +
+			hdmi_read(sd, 0x33)) / 2;
+		bt->polarities = ((hdmi_read(sd, 0x05) & 0x10) ? V4L2_DV_VSYNC_POS_POL : 0) |
+			((hdmi_read(sd, 0x05) & 0x20) ? V4L2_DV_HSYNC_POS_POL : 0);
+		if (bt->interlaced == V4L2_DV_INTERLACED) {
+			bt->height += (hdmi_read(sd, 0x0b) & 0x0f) * 256 +
+					hdmi_read(sd, 0x0c);
+			bt->il_vfrontporch = ((hdmi_read(sd, 0x2c) & 0x1f) * 256 +
+					hdmi_read(sd, 0x2d)) / 2;
+			bt->il_vsync = ((hdmi_read(sd, 0x30) & 0x1f) * 256 +
+					hdmi_read(sd, 0x31)) / 2;
+			bt->vbackporch = ((hdmi_read(sd, 0x34) & 0x1f) * 256 +
+					hdmi_read(sd, 0x35)) / 2;
+		}
+		adv7604_fill_optional_dv_timings_fields(sd, timings);
+	} else {
+		/* find format
+		 * Since LCVS values are inaccurate (REF_03, page 275-276),
+		 * stdi2dv_timings() is called with lcvs +-1 if the first attempt fails.
+		 */
+		if (!stdi2dv_timings(sd, &stdi, timings))
+			goto found;
+		stdi.lcvs += 1;
+		v4l2_dbg(1, debug, sd, "%s: lcvs + 1 = %d\n", __func__, stdi.lcvs);
+		if (!stdi2dv_timings(sd, &stdi, timings))
+			goto found;
+		stdi.lcvs -= 2;
+		v4l2_dbg(1, debug, sd, "%s: lcvs - 1 = %d\n", __func__, stdi.lcvs);
+		if (stdi2dv_timings(sd, &stdi, timings)) {
+			v4l2_dbg(1, debug, sd, "%s: format not supported\n", __func__);
+			return -ERANGE;
+		}
+	}
+found:
+
+	if (no_signal(sd)) {
+		v4l2_dbg(1, debug, sd, "%s: signal lost during readout\n", __func__);
+		memset(timings, 0, sizeof(struct v4l2_dv_timings));
+		return -ENOLINK;
+	}
+
+	if ((!DIGITAL_INPUT && bt->pixelclock > 170000000) ||
+			(DIGITAL_INPUT && bt->pixelclock > 225000000)) {
+		v4l2_dbg(1, debug, sd, "%s: pixelclock out of range %d\n",
+				__func__, (u32)bt->pixelclock);
+		return -ERANGE;
+	}
+
+	if (debug > 1)
+		adv7604_print_timings(sd, timings,
+				"adv7604_query_dv_timings:", true);
+
+	return 0;
+}
+
+static int adv7604_s_dv_timings(struct v4l2_subdev *sd,
+		struct v4l2_dv_timings *timings)
+{
+	struct adv7604_state *state = to_state(sd);
+	struct v4l2_bt_timings *bt;
+
+	if (!timings)
+		return -EINVAL;
+
+	bt = &timings->bt;
+
+	if ((!DIGITAL_INPUT && bt->pixelclock > 170000000) ||
+			(DIGITAL_INPUT && bt->pixelclock > 225000000)) {
+		v4l2_dbg(1, debug, sd, "%s: pixelclock out of range %d\n",
+				__func__, (u32)bt->pixelclock);
+		return -ERANGE;
+	}
+	adv7604_fill_optional_dv_timings_fields(sd, timings);
+
+	state->timings = *timings;
+
+	/* freerun */
+	configure_free_run(sd, bt);
+
+	set_rgb_quantization_range(sd);
+
+
+	if (debug > 1)
+		adv7604_print_timings(sd, timings,
+				"adv7604_s_dv_timings:", true);
+	return 0;
+}
+
+static int adv7604_g_dv_timings(struct v4l2_subdev *sd,
+		struct v4l2_dv_timings *timings)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	*timings = state->timings;
+	return 0;
+}
+
+static void enable_input(struct v4l2_subdev *sd, enum adv7604_prim_mode prim_mode)
+{
+	switch (prim_mode) {
+	case ADV7604_PRIM_MODE_COMP:
+	case ADV7604_PRIM_MODE_RGB:
+		/* enable */
+		io_write(sd, 0x15, 0xb0);   /* Disable Tristate of Pins (no audio) */
+		break;
+	case ADV7604_PRIM_MODE_HDMI_COMP:
+	case ADV7604_PRIM_MODE_HDMI_GR:
+		/* enable */
+		hdmi_write(sd, 0x1a, 0x0a); /* Unmute audio */
+		hdmi_write(sd, 0x01, 0x00); /* Enable HDMI clock terminators */
+		io_write(sd, 0x15, 0xa0);   /* Disable Tristate of Pins */
+		break;
+	default:
+		v4l2_err(sd, "%s: reserved primary mode 0x%0x\n",
+				__func__, prim_mode);
+		break;
+	}
+}
+
+static void disable_input(struct v4l2_subdev *sd)
+{
+	/* disable */
+	io_write(sd, 0x15, 0xbe);   /* Tristate all outputs from video core */
+	hdmi_write(sd, 0x1a, 0x1a); /* Mute audio */
+	hdmi_write(sd, 0x01, 0x78); /* Disable HDMI clock terminators */
+}
+
+static void select_input(struct v4l2_subdev *sd, enum adv7604_prim_mode prim_mode)
+{
+	switch (prim_mode) {
+	case ADV7604_PRIM_MODE_COMP:
+	case ADV7604_PRIM_MODE_RGB:
+		/* set mode and select free run resolution */
+		io_write(sd, 0x00, 0x07); /* video std */
+		io_write(sd, 0x01, 0x02); /* prim mode */
+		/* enable embedded syncs for auto graphics mode */
+		cp_write_and_or(sd, 0x81, 0xef, 0x10);
+
+		/* reset ADI recommended settings for HDMI: */
+		/* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 4. */
+		hdmi_write(sd, 0x0d, 0x04); /* HDMI filter optimization */
+		hdmi_write(sd, 0x3d, 0x00); /* DDC bus active pull-up control */
+		hdmi_write(sd, 0x3e, 0x74); /* TMDS PLL optimization */
+		hdmi_write(sd, 0x4e, 0x3b); /* TMDS PLL optimization */
+		hdmi_write(sd, 0x57, 0x74); /* TMDS PLL optimization */
+		hdmi_write(sd, 0x58, 0x63); /* TMDS PLL optimization */
+		hdmi_write(sd, 0x8d, 0x18); /* equaliser */
+		hdmi_write(sd, 0x8e, 0x34); /* equaliser */
+		hdmi_write(sd, 0x93, 0x88); /* equaliser */
+		hdmi_write(sd, 0x94, 0x2e); /* equaliser */
+		hdmi_write(sd, 0x96, 0x00); /* enable automatic EQ changing */
+
+		afe_write(sd, 0x00, 0x08); /* power up ADC */
+		afe_write(sd, 0x01, 0x06); /* power up Analog Front End */
+		afe_write(sd, 0xc8, 0x00); /* phase control */
+
+		/* set ADI recommended settings for digitizer */
+		/* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 17. */
+		afe_write(sd, 0x12, 0x7b); /* ADC noise shaping filter controls */
+		afe_write(sd, 0x0c, 0x1f); /* CP core gain controls */
+		cp_write(sd, 0x3e, 0x04); /* CP core pre-gain control */
+		cp_write(sd, 0xc3, 0x39); /* CP coast control. Graphics mode */
+		cp_write(sd, 0x40, 0x5c); /* CP core pre-gain control. Graphics mode */
+		break;
+
+	case ADV7604_PRIM_MODE_HDMI_COMP:
+	case ADV7604_PRIM_MODE_HDMI_GR:
+		/* set mode and select free run resolution */
+		/* video std */
+		io_write(sd, 0x00,
+			(prim_mode == ADV7604_PRIM_MODE_HDMI_GR) ? 0x02 : 0x1e);
+		io_write(sd, 0x01, prim_mode); /* prim mode */
+		/* disable embedded syncs for auto graphics mode */
+		cp_write_and_or(sd, 0x81, 0xef, 0x00);
+
+		/* set ADI recommended settings for HDMI: */
+		/* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 4. */
+		hdmi_write(sd, 0x0d, 0x84); /* HDMI filter optimization */
+		hdmi_write(sd, 0x3d, 0x10); /* DDC bus active pull-up control */
+		hdmi_write(sd, 0x3e, 0x39); /* TMDS PLL optimization */
+		hdmi_write(sd, 0x4e, 0x3b); /* TMDS PLL optimization */
+		hdmi_write(sd, 0x57, 0xb6); /* TMDS PLL optimization */
+		hdmi_write(sd, 0x58, 0x03); /* TMDS PLL optimization */
+		hdmi_write(sd, 0x8d, 0x18); /* equaliser */
+		hdmi_write(sd, 0x8e, 0x34); /* equaliser */
+		hdmi_write(sd, 0x93, 0x8b); /* equaliser */
+		hdmi_write(sd, 0x94, 0x2d); /* equaliser */
+		hdmi_write(sd, 0x96, 0x01); /* enable automatic EQ changing */
+
+		afe_write(sd, 0x00, 0xff); /* power down ADC */
+		afe_write(sd, 0x01, 0xfe); /* power down Analog Front End */
+		afe_write(sd, 0xc8, 0x40); /* phase control */
+
+		/* reset ADI recommended settings for digitizer */
+		/* "ADV7604 Register Settings Recommendations (rev. 2.5, June 2010)" p. 17. */
+		afe_write(sd, 0x12, 0xfb); /* ADC noise shaping filter controls */
+		afe_write(sd, 0x0c, 0x0d); /* CP core gain controls */
+		cp_write(sd, 0x3e, 0x00); /* CP core pre-gain control */
+		cp_write(sd, 0xc3, 0x39); /* CP coast control. Graphics mode */
+		cp_write(sd, 0x40, 0x80); /* CP core pre-gain control. Graphics mode */
+
+		break;
+	default:
+		v4l2_err(sd, "%s: reserved primary mode 0x%0x\n", __func__, prim_mode);
+		break;
+	}
+}
+
+static int adv7604_s_routing(struct v4l2_subdev *sd,
+		u32 input, u32 output, u32 config)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	v4l2_dbg(2, debug, sd, "%s: input %d", __func__, input);
+
+	switch (input) {
+	case 0:
+		/* TODO select HDMI_COMP or HDMI_GR */
+		state->prim_mode = ADV7604_PRIM_MODE_HDMI_COMP;
+		break;
+	case 1:
+		state->prim_mode = ADV7604_PRIM_MODE_RGB;
+		break;
+	case 2:
+		state->prim_mode = ADV7604_PRIM_MODE_COMP;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	disable_input(sd);
+
+	select_input(sd, state->prim_mode);
+
+	enable_input(sd, state->prim_mode);
+
+	return 0;
+}
+
+static int adv7604_enum_mbus_fmt(struct v4l2_subdev *sd, unsigned int index,
+			     enum v4l2_mbus_pixelcode *code)
+{
+	if (index)
+		return -EINVAL;
+	/* Good enough for now */
+	*code = V4L2_MBUS_FMT_FIXED;
+	return 0;
+}
+
+static int adv7604_g_mbus_fmt(struct v4l2_subdev *sd,
+		struct v4l2_mbus_framefmt *fmt)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	fmt->width = state->timings.bt.width;
+	fmt->height = state->timings.bt.height;
+	fmt->code = V4L2_MBUS_FMT_FIXED;
+	fmt->field = V4L2_FIELD_NONE;
+	if (state->timings.bt.standards & V4L2_DV_BT_STD_CEA861) {
+		fmt->colorspace = (state->timings.bt.height <= 576) ?
+			V4L2_COLORSPACE_SMPTE170M : V4L2_COLORSPACE_REC709;
+	}
+	return 0;
+}
+
+static int adv7604_isr(struct v4l2_subdev *sd, u32 status, bool *handled)
+{
+	struct adv7604_state *state = to_state(sd);
+	u8 fmt_change, fmt_change_digital, tx_5v;
+
+	/* format change */
+	fmt_change = io_read(sd, 0x43) & 0x98;
+	if (fmt_change)
+		io_write(sd, 0x44, fmt_change);
+	fmt_change_digital = DIGITAL_INPUT ? (io_read(sd, 0x6b) & 0xc0) : 0;
+	if (fmt_change_digital)
+		io_write(sd, 0x6c, fmt_change_digital);
+	if (fmt_change || fmt_change_digital) {
+		v4l2_dbg(1, debug, sd,
+			"%s: ADV7604_FMT_CHANGE, fmt_change = 0x%x, fmt_change_digital = 0x%x\n",
+			__func__, fmt_change, fmt_change_digital);
+		v4l2_subdev_notify(sd, ADV7604_FMT_CHANGE, NULL);
+		if (handled)
+			*handled = true;
+	}
+	/* tx 5v detect */
+	tx_5v = io_read(sd, 0x70) & 0x10;
+	if (tx_5v) {
+		v4l2_dbg(1, debug, sd, "%s: tx_5v: 0x%x\n", __func__, tx_5v);
+		io_write(sd, 0x71, tx_5v);
+		adv7604_s_detect_tx_5v_ctrl(sd);
+		if (handled)
+			*handled = true;
+	}
+	return 0;
+}
+
+static int adv7604_get_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid)
+{
+	struct adv7604_state *state = to_state(sd);
+
+	if (edid->pad != 0)
+		return -EINVAL;
+	if (edid->blocks == 0)
+		return -EINVAL;
+	if (edid->start_block >= state->edid_blocks)
+		return -EINVAL;
+	if (edid->start_block + edid->blocks > state->edid_blocks)
+		edid->blocks = state->edid_blocks - edid->start_block;
+	if (!edid->edid)
+		return -EINVAL;
+	memcpy(edid->edid + edid->start_block * 128,
+	       state->edid + edid->start_block * 128,
+	       edid->blocks * 128);
+	return 0;
+}
+
+static int adv7604_set_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid)
+{
+	struct adv7604_state *state = to_state(sd);
+	int err;
+
+	if (edid->pad != 0)
+		return -EINVAL;
+	if (edid->start_block != 0)
+		return -EINVAL;
+	if (edid->blocks == 0) {
+		/* Pull down the hotplug pin */
+		v4l2_subdev_notify(sd, ADV7604_HOTPLUG, (void *)0);
+		/* Disables I2C access to internal EDID ram from DDC port */
+		rep_write_and_or(sd, 0x77, 0xf0, 0x0);
+		state->edid_blocks = 0;
+		/* Fall back to a 16:9 aspect ratio */
+		state->aspect_ratio.numerator = 16;
+		state->aspect_ratio.denominator = 9;
+		return 0;
+	}
+	if (edid->blocks > 2)
+		return -E2BIG;
+	if (!edid->edid)
+		return -EINVAL;
+	memcpy(state->edid, edid->edid, 128 * edid->blocks);
+	state->edid_blocks = edid->blocks;
+	state->aspect_ratio = v4l2_calc_aspect_ratio(edid->edid[0x15],
+			edid->edid[0x16]);
+	err = edid_write_block(sd, 128 * edid->blocks, state->edid);
+	if (err < 0)
+		v4l2_err(sd, "error %d writing edid\n", err);
+	return err;
+}
+
+/*********** avi info frame CEA-861-E **************/
+
+static void print_avi_infoframe(struct v4l2_subdev *sd)
+{
+	int i;
+	u8 buf[14];
+	u8 avi_len;
+	u8 avi_ver;
+
+	if (!(hdmi_read(sd, 0x05) & 0x80)) {
+		v4l2_info(sd, "receive DVI-D signal (AVI infoframe not supported)\n");
+		return;
+	}
+	if (!(io_read(sd, 0x60) & 0x01)) {
+		v4l2_info(sd, "AVI infoframe not received\n");
+		return;
+	}
+
+	if (io_read(sd, 0x83) & 0x01) {
+		v4l2_info(sd, "AVI infoframe checksum error has occurred earlier\n");
+		io_write(sd, 0x85, 0x01); /* clear AVI_INF_CKS_ERR_RAW */
+		if (io_read(sd, 0x83) & 0x01) {
+			v4l2_info(sd, "AVI infoframe checksum error still present\n");
+			io_write(sd, 0x85, 0x01); /* clear AVI_INF_CKS_ERR_RAW */
+		}
+	}
+
+	avi_len = infoframe_read(sd, 0xe2);
+	avi_ver = infoframe_read(sd, 0xe1);
+	v4l2_info(sd, "AVI infoframe version %d (%d byte)\n",
+			avi_ver, avi_len);
+
+	if (avi_ver != 0x02)
+		return;
+
+	for (i = 0; i < 14; i++)
+		buf[i] = infoframe_read(sd, i);
+
+	v4l2_info(sd,
+		"\t%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
+		buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
+		buf[8], buf[9], buf[10], buf[11], buf[12], buf[13]);
+}
+
+static int adv7604_log_status(struct v4l2_subdev *sd)
+{
+	struct adv7604_state *state = to_state(sd);
+	struct v4l2_dv_timings timings;
+	struct stdi_readback stdi;
+	u8 reg_io_0x02 = io_read(sd, 0x02);
+
+	char *csc_coeff_sel_rb[16] = {
+		"bypassed", "YPbPr601 -> RGB", "reserved", "YPbPr709 -> RGB",
+		"reserved", "RGB -> YPbPr601", "reserved", "RGB -> YPbPr709",
+		"reserved", "YPbPr709 -> YPbPr601", "YPbPr601 -> YPbPr709",
+		"reserved", "reserved", "reserved", "reserved", "manual"
+	};
+	char *input_color_space_txt[16] = {
+		"RGB limited range (16-235)", "RGB full range (0-255)",
+		"YCbCr Bt.601 (16-235)", "YCbCr Bt.709 (16-235)",
+		"XvYCC Bt.601", "XvYCC Bt.709",
+		"YCbCr Bt.601 (0-255)", "YCbCr Bt.709 (0-255)",
+		"invalid", "invalid", "invalid", "invalid", "invalid",
+		"invalid", "invalid", "automatic"
+	};
+	char *rgb_quantization_range_txt[] = {
+		"Automatic",
+		"RGB limited range (16-235)",
+		"RGB full range (0-255)",
+	};
+
+	v4l2_info(sd, "-----Chip status-----\n");
+	v4l2_info(sd, "Chip power: %s\n", no_power(sd) ? "off" : "on");
+	v4l2_info(sd, "Connector type: %s\n", state->connector_hdmi ?
+			"HDMI" : (DIGITAL_INPUT ? "DVI-D" : "DVI-A"));
+	v4l2_info(sd, "EDID: %s\n", ((rep_read(sd, 0x7d) & 0x01) &&
+			(rep_read(sd, 0x77) & 0x01)) ? "enabled" : "disabled ");
+	v4l2_info(sd, "CEC: %s\n", !!(cec_read(sd, 0x2a) & 0x01) ?
+			"enabled" : "disabled");
+
+	v4l2_info(sd, "-----Signal status-----\n");
+	v4l2_info(sd, "Cable detected (+5V power): %s\n",
+			(io_read(sd, 0x6f) & 0x10) ? "true" : "false");
+	v4l2_info(sd, "TMDS signal detected: %s\n",
+			no_signal_tmds(sd) ? "false" : "true");
+	v4l2_info(sd, "TMDS signal locked: %s\n",
+			no_lock_tmds(sd) ? "false" : "true");
+	v4l2_info(sd, "SSPD locked: %s\n", no_lock_sspd(sd) ? "false" : "true");
+	v4l2_info(sd, "STDI locked: %s\n", no_lock_stdi(sd) ? "false" : "true");
+	v4l2_info(sd, "CP locked: %s\n", no_lock_cp(sd) ? "false" : "true");
+	v4l2_info(sd, "CP free run: %s\n",
+			(!!(cp_read(sd, 0xff) & 0x10) ? "on" : "off"));
+	v4l2_info(sd, "Prim-mode = 0x%x, video std = 0x%x\n",
+			io_read(sd, 0x01) & 0x0f, io_read(sd, 0x00) & 0x3f);
+
+	v4l2_info(sd, "-----Video Timings-----\n");
+	if (read_stdi(sd, &stdi))
+		v4l2_info(sd, "STDI: not locked\n");
+	else
+		v4l2_info(sd, "STDI: lcf (frame height - 1) = %d, bl = %d, lcvs (vsync) = %d, %s, %chsync, %cvsync\n",
+				stdi.lcf, stdi.bl, stdi.lcvs,
+				stdi.interlaced ? "interlaced" : "progressive",
+				stdi.hs_pol, stdi.vs_pol);
+	if (adv7604_query_dv_timings(sd, &timings))
+		v4l2_info(sd, "No video detected\n");
+	else
+		adv7604_print_timings(sd, &timings, "Detected format:", true);
+	adv7604_print_timings(sd, &state->timings, "Configured format:", true);
+
+	v4l2_info(sd, "-----Color space-----\n");
+	v4l2_info(sd, "RGB quantization range ctrl: %s\n",
+			rgb_quantization_range_txt[state->rgb_quantization_range]);
+	v4l2_info(sd, "Input color space: %s\n",
+			input_color_space_txt[reg_io_0x02 >> 4]);
+	v4l2_info(sd, "Output color space: %s %s, saturator %s\n",
+			(reg_io_0x02 & 0x02) ? "RGB" : "YCbCr",
+			(reg_io_0x02 & 0x04) ? "(16-235)" : "(0-255)",
+			((reg_io_0x02 & 0x04) ^ (reg_io_0x02 & 0x01)) ?
+					"enabled" : "disabled");
+	v4l2_info(sd, "Color space conversion: %s\n",
+			csc_coeff_sel_rb[cp_read(sd, 0xfc) >> 4]);
+
+	/* Digital video */
+	if (DIGITAL_INPUT) {
+		v4l2_info(sd, "-----HDMI status-----\n");
+		v4l2_info(sd, "HDCP encrypted content: %s\n",
+				hdmi_read(sd, 0x05) & 0x40 ? "true" : "false");
+
+		print_avi_infoframe(sd);
+	}
+
+	return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static const struct v4l2_ctrl_ops adv7604_ctrl_ops = {
+	.s_ctrl = adv7604_s_ctrl,
+};
+
+static const struct v4l2_subdev_core_ops adv7604_core_ops = {
+	.log_status = adv7604_log_status,
+	.g_ext_ctrls = v4l2_subdev_g_ext_ctrls,
+	.try_ext_ctrls = v4l2_subdev_try_ext_ctrls,
+	.s_ext_ctrls = v4l2_subdev_s_ext_ctrls,
+	.g_ctrl = v4l2_subdev_g_ctrl,
+	.s_ctrl = v4l2_subdev_s_ctrl,
+	.queryctrl = v4l2_subdev_queryctrl,
+	.querymenu = v4l2_subdev_querymenu,
+	.g_chip_ident = adv7604_g_chip_ident,
+	.interrupt_service_routine = adv7604_isr,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.g_register = adv7604_g_register,
+	.s_register = adv7604_s_register,
+#endif
+};
+
+static const struct v4l2_subdev_video_ops adv7604_video_ops = {
+	.s_routing = adv7604_s_routing,
+	.g_input_status = adv7604_g_input_status,
+	.s_dv_timings = adv7604_s_dv_timings,
+	.g_dv_timings = adv7604_g_dv_timings,
+	.query_dv_timings = adv7604_query_dv_timings,
+	.enum_dv_timings = adv7604_enum_dv_timings,
+	.dv_timings_cap = adv7604_dv_timings_cap,
+	.enum_mbus_fmt = adv7604_enum_mbus_fmt,
+	.g_mbus_fmt = adv7604_g_mbus_fmt,
+	.try_mbus_fmt = adv7604_g_mbus_fmt,
+	.s_mbus_fmt = adv7604_g_mbus_fmt,
+};
+
+static const struct v4l2_subdev_pad_ops adv7604_pad_ops = {
+	.get_edid = adv7604_get_edid,
+	.set_edid = adv7604_set_edid,
+};
+
+static const struct v4l2_subdev_ops adv7604_ops = {
+	.core = &adv7604_core_ops,
+	.video = &adv7604_video_ops,
+	.pad = &adv7604_pad_ops,
+};
+
+/* -------------------------- custom ctrls ---------------------------------- */
+
+static const struct v4l2_ctrl_config adv7604_ctrl_analog_sampling_phase = {
+	.ops = &adv7604_ctrl_ops,
+	.id = V4L2_CID_ADV_RX_ANALOG_SAMPLING_PHASE,
+	.name = "Analog Sampling Phase",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.min = 0,
+	.max = 0x1f,
+	.step = 1,
+	.def = 0,
+};
+
+static const struct v4l2_ctrl_config adv7604_ctrl_free_run_color_manual = {
+	.ops = &adv7604_ctrl_ops,
+	.id = V4L2_CID_ADV_RX_FREE_RUN_COLOR_MANUAL,
+	.name = "Free Running Color, Manual",
+	.type = V4L2_CTRL_TYPE_BOOLEAN,
+	.min = false,
+	.max = true,
+	.step = 1,
+	.def = false,
+};
+
+static const struct v4l2_ctrl_config adv7604_ctrl_free_run_color = {
+	.ops = &adv7604_ctrl_ops,
+	.id = V4L2_CID_ADV_RX_FREE_RUN_COLOR,
+	.name = "Free Running Color",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.min = 0x0,
+	.max = 0xffffff,
+	.step = 0x1,
+	.def = 0x0,
+};
+
+/* ----------------------------------------------------------------------- */
+
+static int adv7604_core_init(struct v4l2_subdev *sd)
+{
+	struct adv7604_state *state = to_state(sd);
+	struct adv7604_platform_data *pdata = &state->pdata;
+
+	hdmi_write(sd, 0x48,
+		(pdata->disable_pwrdnb ? 0x80 : 0) |
+		(pdata->disable_cable_det_rst ? 0x40 : 0));
+
+	disable_input(sd);
+
+	/* power */
+	io_write(sd, 0x0c, 0x42);   /* Power up part and power down VDP */
+	io_write(sd, 0x0b, 0x44);   /* Power down ESDP block */
+	cp_write(sd, 0xcf, 0x01);   /* Power down macrovision */
+
+	/* video format */
+	io_write_and_or(sd, 0x02, 0xf0,
+			pdata->alt_gamma << 3 |
+			pdata->op_656_range << 2 |
+			pdata->rgb_out << 1 |
+			pdata->alt_data_sat << 0);
+	io_write(sd, 0x03, pdata->op_format_sel);
+	io_write_and_or(sd, 0x04, 0x1f, pdata->op_ch_sel << 5);
+	io_write_and_or(sd, 0x05, 0xf0, pdata->blank_data << 3 |
+					pdata->insert_av_codes << 2 |
+					pdata->replicate_av_codes << 1 |
+					pdata->invert_cbcr << 0);
+
+	/* TODO from platform data */
+	cp_write(sd, 0x69, 0x30);   /* Enable CP CSC */
+	io_write(sd, 0x06, 0xa6);   /* positive VS and HS */
+	io_write(sd, 0x14, 0x7f);   /* Drive strength adjusted to max */
+	cp_write(sd, 0xba, (pdata->hdmi_free_run_mode << 1) | 0x01); /* HDMI free run */
+	cp_write(sd, 0xf3, 0xdc); /* Low threshold to enter/exit free run mode */
+	cp_write(sd, 0xf9, 0x23); /*  STDI ch. 1 - LCVS change threshold -
+				      ADI recommended setting [REF_01 c. 2.3.3] */
+	cp_write(sd, 0x45, 0x23); /*  STDI ch. 2 - LCVS change threshold -
+				      ADI recommended setting [REF_01 c. 2.3.3] */
+	cp_write(sd, 0xc9, 0x2d); /* use prim_mode and vid_std as free run resolution
+				     for digital formats */
+
+	/* TODO from platform data */
+	afe_write(sd, 0xb5, 0x01);  /* Setting MCLK to 256Fs */
+
+	afe_write(sd, 0x02, pdata->ain_sel); /* Select analog input muxing mode */
+	io_write_and_or(sd, 0x30, ~(1 << 4), pdata->output_bus_lsb_to_msb << 4);
+
+	state->prim_mode = pdata->prim_mode;
+	select_input(sd, pdata->prim_mode);
+
+	enable_input(sd, pdata->prim_mode);
+
+	/* interrupts */
+	io_write(sd, 0x40, 0xc2); /* Configure INT1 */
+	io_write(sd, 0x41, 0xd7); /* STDI irq for any change, disable INT2 */
+	io_write(sd, 0x46, 0x98); /* Enable SSPD, STDI and CP unlocked interrupts */
+	io_write(sd, 0x6e, 0xc0); /* Enable V_LOCKED and DE_REGEN_LCK interrupts */
+	io_write(sd, 0x73, 0x10); /* Enable CABLE_DET_A_ST (+5v) interrupt */
+
+	return v4l2_ctrl_handler_setup(sd->ctrl_handler);
+}
+
+static void adv7604_unregister_clients(struct adv7604_state *state)
+{
+	if (state->i2c_avlink)
+		i2c_unregister_device(state->i2c_avlink);
+	if (state->i2c_cec)
+		i2c_unregister_device(state->i2c_cec);
+	if (state->i2c_infoframe)
+		i2c_unregister_device(state->i2c_infoframe);
+	if (state->i2c_esdp)
+		i2c_unregister_device(state->i2c_esdp);
+	if (state->i2c_dpp)
+		i2c_unregister_device(state->i2c_dpp);
+	if (state->i2c_afe)
+		i2c_unregister_device(state->i2c_afe);
+	if (state->i2c_repeater)
+		i2c_unregister_device(state->i2c_repeater);
+	if (state->i2c_edid)
+		i2c_unregister_device(state->i2c_edid);
+	if (state->i2c_hdmi)
+		i2c_unregister_device(state->i2c_hdmi);
+	if (state->i2c_test)
+		i2c_unregister_device(state->i2c_test);
+	if (state->i2c_cp)
+		i2c_unregister_device(state->i2c_cp);
+	if (state->i2c_vdp)
+		i2c_unregister_device(state->i2c_vdp);
+}
+
+static struct i2c_client *adv7604_dummy_client(struct v4l2_subdev *sd,
+							u8 addr, u8 io_reg)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	if (addr)
+		io_write(sd, io_reg, addr << 1);
+	return i2c_new_dummy(client->adapter, io_read(sd, io_reg) >> 1);
+}
+
+static int adv7604_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	struct adv7604_state *state;
+	struct adv7604_platform_data *pdata = client->dev.platform_data;
+	struct v4l2_ctrl_handler *hdl;
+	struct v4l2_subdev *sd;
+	int err;
+
+	/* Check if the adapter supports the needed features */
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -EIO;
+	v4l_dbg(1, debug, client, "detecting adv7604 client on address 0x%x\n",
+			client->addr << 1);
+
+	state = kzalloc(sizeof(struct adv7604_state), GFP_KERNEL);
+	if (!state) {
+		v4l_err(client, "Could not allocate adv7604_state memory!\n");
+		return -ENOMEM;
+	}
+
+	/* platform data */
+	if (!pdata) {
+		v4l_err(client, "No platform data!\n");
+		err = -ENODEV;
+		goto err_state;
+	}
+	memcpy(&state->pdata, pdata, sizeof(state->pdata));
+
+	sd = &state->sd;
+	v4l2_i2c_subdev_init(sd, client, &adv7604_ops);
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	state->connector_hdmi = pdata->connector_hdmi;
+
+	/* i2c access to adv7604? */
+	if (adv_smbus_read_byte_data_check(client, 0xfb, false) != 0x68) {
+		v4l2_info(sd, "not an adv7604 on address 0x%x\n",
+				client->addr << 1);
+		err = -ENODEV;
+		goto err_state;
+	}
+
+	/* control handlers */
+	hdl = &state->hdl;
+	v4l2_ctrl_handler_init(hdl, 9);
+
+	v4l2_ctrl_new_std(hdl, &adv7604_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, -128, 127, 1, 0);
+	v4l2_ctrl_new_std(hdl, &adv7604_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 255, 1, 128);
+	v4l2_ctrl_new_std(hdl, &adv7604_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 255, 1, 128);
+	v4l2_ctrl_new_std(hdl, &adv7604_ctrl_ops,
+			V4L2_CID_HUE, 0, 128, 1, 0);
+
+	/* private controls */
+	state->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL,
+			V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0);
+	state->detect_tx_5v_ctrl->is_private = true;
+	state->rgb_quantization_range_ctrl =
+		v4l2_ctrl_new_std_menu(hdl, &adv7604_ctrl_ops,
+			V4L2_CID_DV_RX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL,
+			0, V4L2_DV_RGB_RANGE_AUTO);
+	state->rgb_quantization_range_ctrl->is_private = true;
+
+	/* custom controls */
+	state->analog_sampling_phase_ctrl =
+		v4l2_ctrl_new_custom(hdl, &adv7604_ctrl_analog_sampling_phase, NULL);
+	state->analog_sampling_phase_ctrl->is_private = true;
+	state->free_run_color_manual_ctrl =
+		v4l2_ctrl_new_custom(hdl, &adv7604_ctrl_free_run_color_manual, NULL);
+	state->free_run_color_manual_ctrl->is_private = true;
+	state->free_run_color_ctrl =
+		v4l2_ctrl_new_custom(hdl, &adv7604_ctrl_free_run_color, NULL);
+	state->free_run_color_ctrl->is_private = true;
+
+	sd->ctrl_handler = hdl;
+	if (hdl->error) {
+		err = hdl->error;
+		goto err_hdl;
+	}
+	if (adv7604_s_detect_tx_5v_ctrl(sd)) {
+		err = -ENODEV;
+		goto err_hdl;
+	}
+
+	state->i2c_avlink = adv7604_dummy_client(sd, pdata->i2c_avlink, 0xf3);
+	state->i2c_cec = adv7604_dummy_client(sd, pdata->i2c_cec, 0xf4);
+	state->i2c_infoframe = adv7604_dummy_client(sd, pdata->i2c_infoframe, 0xf5);
+	state->i2c_esdp = adv7604_dummy_client(sd, pdata->i2c_esdp, 0xf6);
+	state->i2c_dpp = adv7604_dummy_client(sd, pdata->i2c_dpp, 0xf7);
+	state->i2c_afe = adv7604_dummy_client(sd, pdata->i2c_afe, 0xf8);
+	state->i2c_repeater = adv7604_dummy_client(sd, pdata->i2c_repeater, 0xf9);
+	state->i2c_edid = adv7604_dummy_client(sd, pdata->i2c_edid, 0xfa);
+	state->i2c_hdmi = adv7604_dummy_client(sd, pdata->i2c_hdmi, 0xfb);
+	state->i2c_test = adv7604_dummy_client(sd, pdata->i2c_test, 0xfc);
+	state->i2c_cp = adv7604_dummy_client(sd, pdata->i2c_cp, 0xfd);
+	state->i2c_vdp = adv7604_dummy_client(sd, pdata->i2c_vdp, 0xfe);
+	if (!state->i2c_avlink || !state->i2c_cec || !state->i2c_infoframe ||
+	    !state->i2c_esdp || !state->i2c_dpp || !state->i2c_afe ||
+	    !state->i2c_repeater || !state->i2c_edid || !state->i2c_hdmi ||
+	    !state->i2c_test || !state->i2c_cp || !state->i2c_vdp) {
+		err = -ENOMEM;
+		v4l2_err(sd, "failed to create all i2c clients\n");
+		goto err_i2c;
+	}
+
+	/* work queues */
+	state->work_queues = create_singlethread_workqueue(client->name);
+	if (!state->work_queues) {
+		v4l2_err(sd, "Could not create work queue\n");
+		err = -ENOMEM;
+		goto err_i2c;
+	}
+
+	INIT_DELAYED_WORK(&state->delayed_work_enable_hotplug,
+			adv7604_delayed_work_enable_hotplug);
+
+	state->pad.flags = MEDIA_PAD_FL_SOURCE;
+	err = media_entity_init(&sd->entity, 1, &state->pad, 0);
+	if (err)
+		goto err_work_queues;
+
+	err = adv7604_core_init(sd);
+	if (err)
+		goto err_entity;
+	v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name,
+			client->addr << 1, client->adapter->name);
+	return 0;
+
+err_entity:
+	media_entity_cleanup(&sd->entity);
+err_work_queues:
+	cancel_delayed_work(&state->delayed_work_enable_hotplug);
+	destroy_workqueue(state->work_queues);
+err_i2c:
+	adv7604_unregister_clients(state);
+err_hdl:
+	v4l2_ctrl_handler_free(hdl);
+err_state:
+	kfree(state);
+	return err;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static int adv7604_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct adv7604_state *state = to_state(sd);
+
+	cancel_delayed_work(&state->delayed_work_enable_hotplug);
+	destroy_workqueue(state->work_queues);
+	v4l2_device_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	adv7604_unregister_clients(to_state(sd));
+	v4l2_ctrl_handler_free(sd->ctrl_handler);
+	kfree(to_state(sd));
+	return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static struct i2c_device_id adv7604_id[] = {
+	{ "adv7604", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, adv7604_id);
+
+static struct i2c_driver adv7604_driver = {
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "adv7604",
+	},
+	.probe = adv7604_probe,
+	.remove = adv7604_remove,
+	.id_table = adv7604_id,
+};
+
+module_i2c_driver(adv7604_driver);
diff --git a/include/media/adv7604.h b/include/media/adv7604.h
new file mode 100644
index 0000000..171b957
--- /dev/null
+++ b/include/media/adv7604.h
@@ -0,0 +1,153 @@
+/*
+ * adv7604 - Analog Devices ADV7604 video decoder driver
+ *
+ * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+#ifndef _ADV7604_
+#define _ADV7604_
+
+/* Analog input muxing modes (AFE register 0x02, [2:0]) */
+enum adv7604_ain_sel {
+	ADV7604_AIN1_2_3_NC_SYNC_1_2 = 0,
+	ADV7604_AIN4_5_6_NC_SYNC_2_1 = 1,
+	ADV7604_AIN7_8_9_NC_SYNC_3_1 = 2,
+	ADV7604_AIN10_11_12_NC_SYNC_4_1 = 3,
+	ADV7604_AIN9_4_5_6_SYNC_2_1 = 4,
+};
+
+/* Bus rotation and reordering (IO register 0x04, [7:5]) */
+enum adv7604_op_ch_sel {
+	ADV7604_OP_CH_SEL_GBR = 0,
+	ADV7604_OP_CH_SEL_GRB = 1,
+	ADV7604_OP_CH_SEL_BGR = 2,
+	ADV7604_OP_CH_SEL_RGB = 3,
+	ADV7604_OP_CH_SEL_BRG = 4,
+	ADV7604_OP_CH_SEL_RBG = 5,
+};
+
+/* Primary mode (IO register 0x01, [3:0]) */
+enum adv7604_prim_mode {
+	ADV7604_PRIM_MODE_COMP = 1,
+	ADV7604_PRIM_MODE_RGB = 2,
+	ADV7604_PRIM_MODE_HDMI_COMP = 5,
+	ADV7604_PRIM_MODE_HDMI_GR = 6,
+};
+
+/* Input Color Space (IO register 0x02, [7:4]) */
+enum adv7604_inp_color_space {
+	ADV7604_INP_COLOR_SPACE_LIM_RGB = 0,
+	ADV7604_INP_COLOR_SPACE_FULL_RGB = 1,
+	ADV7604_INP_COLOR_SPACE_LIM_YCbCr_601 = 2,
+	ADV7604_INP_COLOR_SPACE_LIM_YCbCr_709 = 3,
+	ADV7604_INP_COLOR_SPACE_XVYCC_601 = 4,
+	ADV7604_INP_COLOR_SPACE_XVYCC_709 = 5,
+	ADV7604_INP_COLOR_SPACE_FULL_YCbCr_601 = 6,
+	ADV7604_INP_COLOR_SPACE_FULL_YCbCr_709 = 7,
+	ADV7604_INP_COLOR_SPACE_AUTO = 0xf,
+};
+
+/* Select output format (IO register 0x03, [7:0]) */
+enum adv7604_op_format_sel {
+	ADV7604_OP_FORMAT_SEL_SDR_ITU656_8 = 0x00,
+	ADV7604_OP_FORMAT_SEL_SDR_ITU656_10 = 0x01,
+	ADV7604_OP_FORMAT_SEL_SDR_ITU656_12_MODE0 = 0x02,
+	ADV7604_OP_FORMAT_SEL_SDR_ITU656_12_MODE1 = 0x06,
+	ADV7604_OP_FORMAT_SEL_SDR_ITU656_12_MODE2 = 0x0a,
+	ADV7604_OP_FORMAT_SEL_DDR_422_8 = 0x20,
+	ADV7604_OP_FORMAT_SEL_DDR_422_10 = 0x21,
+	ADV7604_OP_FORMAT_SEL_DDR_422_12_MODE0 = 0x22,
+	ADV7604_OP_FORMAT_SEL_DDR_422_12_MODE1 = 0x23,
+	ADV7604_OP_FORMAT_SEL_DDR_422_12_MODE2 = 0x24,
+	ADV7604_OP_FORMAT_SEL_SDR_444_24 = 0x40,
+	ADV7604_OP_FORMAT_SEL_SDR_444_30 = 0x41,
+	ADV7604_OP_FORMAT_SEL_SDR_444_36_MODE0 = 0x42,
+	ADV7604_OP_FORMAT_SEL_DDR_444_24 = 0x60,
+	ADV7604_OP_FORMAT_SEL_DDR_444_30 = 0x61,
+	ADV7604_OP_FORMAT_SEL_DDR_444_36 = 0x62,
+	ADV7604_OP_FORMAT_SEL_SDR_ITU656_16 = 0x80,
+	ADV7604_OP_FORMAT_SEL_SDR_ITU656_20 = 0x81,
+	ADV7604_OP_FORMAT_SEL_SDR_ITU656_24_MODE0 = 0x82,
+	ADV7604_OP_FORMAT_SEL_SDR_ITU656_24_MODE1 = 0x86,
+	ADV7604_OP_FORMAT_SEL_SDR_ITU656_24_MODE2 = 0x8a,
+};
+
+/* Platform dependent definition */
+struct adv7604_platform_data {
+	/* connector - HDMI or DVI? */
+	unsigned connector_hdmi:1;
+
+	/* DIS_PWRDNB: 1 if the PWRDNB pin is unused and unconnected */
+	unsigned disable_pwrdnb:1;
+
+	/* DIS_CABLE_DET_RST: 1 if the 5V pins are unused and unconnected */
+	unsigned disable_cable_det_rst:1;
+
+	/* Analog input muxing mode */
+	enum adv7604_ain_sel ain_sel;
+
+	/* Bus rotation and reordering */
+	enum adv7604_op_ch_sel op_ch_sel;
+
+	/* Primary mode */
+	enum adv7604_prim_mode prim_mode;
+
+	/* Select output format */
+	enum adv7604_op_format_sel op_format_sel;
+
+	/* IO register 0x02 */
+	unsigned alt_gamma:1;
+	unsigned op_656_range:1;
+	unsigned rgb_out:1;
+	unsigned alt_data_sat:1;
+
+	/* IO register 0x05 */
+	unsigned blank_data:1;
+	unsigned insert_av_codes:1;
+	unsigned replicate_av_codes:1;
+	unsigned invert_cbcr:1;
+
+	/* IO register 0x30 */
+	unsigned output_bus_lsb_to_msb:1;
+
+	/* Free run */
+	unsigned hdmi_free_run_mode;
+
+	/* i2c addresses: 0 == use default */
+	u8 i2c_avlink;
+	u8 i2c_cec;
+	u8 i2c_infoframe;
+	u8 i2c_esdp;
+	u8 i2c_dpp;
+	u8 i2c_afe;
+	u8 i2c_repeater;
+	u8 i2c_edid;
+	u8 i2c_hdmi;
+	u8 i2c_test;
+	u8 i2c_cp;
+	u8 i2c_vdp;
+};
+
+#define V4L2_CID_ADV_RX_ANALOG_SAMPLING_PHASE	(V4L2_CID_DV_CLASS_BASE + 0x1000)
+#define V4L2_CID_ADV_RX_FREE_RUN_COLOR_MANUAL	(V4L2_CID_DV_CLASS_BASE + 0x1001)
+#define V4L2_CID_ADV_RX_FREE_RUN_COLOR		(V4L2_CID_DV_CLASS_BASE + 0x1002)
+
+/* notify events */
+#define ADV7604_HOTPLUG		1
+#define ADV7604_FMT_CHANGE	2
+
+#endif
diff --git a/include/media/v4l2-chip-ident.h b/include/media/v4l2-chip-ident.h
index 58f914a..6adb360e 100644
--- a/include/media/v4l2-chip-ident.h
+++ b/include/media/v4l2-chip-ident.h
@@ -183,6 +183,9 @@ enum {
 	/* module adv7393: just ident 7393 */
 	V4L2_IDENT_ADV7393 = 7393,
 
+	/* module adv7604: just ident 7604 */
+	V4L2_IDENT_ADV7604 = 7604,
+
 	/* module saa7706h: just ident 7706 */
 	V4L2_IDENT_SAA7706H = 7706,
 
-- 
1.7.10.4


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

* [RFCv3 PATCH 8/8] ad9389b: driver for the Analog Devices AD9389B video encoder.
  2012-08-10 11:21 ` [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort Hans Verkuil
                     ` (5 preceding siblings ...)
  2012-08-10 11:21   ` [RFCv3 PATCH 7/8] adv7604: driver for the Analog Devices ADV7604 video decoder Hans Verkuil
@ 2012-08-10 11:21   ` Hans Verkuil
  2012-08-13 11:48   ` [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort Soby Mathew
  7 siblings, 0 replies; 13+ messages in thread
From: Hans Verkuil @ 2012-08-10 11:21 UTC (permalink / raw)
  To: linux-media
  Cc: marbugge, Soby Mathew, mats.randgaard, manjunath.hadli,
	Tomasz Stanislawski, Mauro Carvalho Chehab, Scott Jiang,
	dri-devel

Initial version of this driver.

The full datasheets are available from the Analog Devices website:

  http://ez.analog.com/docs/DOC-1741

Not all features of the receiver are supported by this driver for various
reasons. Most notably:

- No CEC support (the CEC API needs a lot more discussion)
- No HDCP repeater support (we don't use that either)

I'm sure that there are more things missing, but this driver does work
well for our hardware.

Note that I am using the register addresses instead of register names: the
datasheet containing the register descriptions is organized by register
address. Using names would make the datasheet lookup very hard. An attempt
was made to try and document what is being done when registers are used
instead.

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
---
 drivers/media/video/Kconfig     |   11 +
 drivers/media/video/Makefile    |    1 +
 drivers/media/video/ad9389b.c   | 1328 +++++++++++++++++++++++++++++++++++++++
 include/media/ad9389b.h         |   49 ++
 include/media/v4l2-chip-ident.h |    3 +
 5 files changed, 1392 insertions(+)
 create mode 100644 drivers/media/video/ad9389b.c
 create mode 100644 include/media/ad9389b.h

diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig
index 6d92d2d..f2b623d 100644
--- a/drivers/media/video/Kconfig
+++ b/drivers/media/video/Kconfig
@@ -483,6 +483,17 @@ config VIDEO_ADV7393
 	  To compile this driver as a module, choose M here: the
 	  module will be called adv7393.
 
+config VIDEO_AD9389B
+	tristate "Analog Devices AD9389B encoder"
+	depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API
+	---help---
+	  Support for the Analog Devices AD9389B video encoder.
+
+	  This is a Analog Devices HDMI transmitter.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ad9389b.
+
 config VIDEO_AK881X
 	tristate "AK8813/AK8814 video encoders"
 	depends on I2C
diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile
index 133dd9b..7cb5cef 100644
--- a/drivers/media/video/Makefile
+++ b/drivers/media/video/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_VIDEO_ADV7183) += adv7183.o
 obj-$(CONFIG_VIDEO_ADV7343) += adv7343.o
 obj-$(CONFIG_VIDEO_ADV7393) += adv7393.o
 obj-$(CONFIG_VIDEO_ADV7604) += adv7604.o
+obj-$(CONFIG_VIDEO_AD9389B) += ad9389b.o
 obj-$(CONFIG_VIDEO_VPX3220) += vpx3220.o
 obj-$(CONFIG_VIDEO_VS6624)  += vs6624.o
 obj-$(CONFIG_VIDEO_BT819) += bt819.o
diff --git a/drivers/media/video/ad9389b.c b/drivers/media/video/ad9389b.c
new file mode 100644
index 0000000..c2886b6
--- /dev/null
+++ b/drivers/media/video/ad9389b.c
@@ -0,0 +1,1328 @@
+/*
+ * Analog Devices AD9389B/AD9889B video encoder driver
+ *
+ * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*
+ * References (c = chapter, p = page):
+ * REF_01 - Analog Devices, Programming Guide, AD9889B/AD9389B,
+ * HDMI Transitter, Rev. A, October 2010
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/videodev2.h>
+#include <linux/workqueue.h>
+#include <linux/v4l2-dv-timings.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-chip-ident.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/ad9389b.h>
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "debug level (0-2)");
+
+MODULE_DESCRIPTION("Analog Devices AD9389B/AD9889B video encoder driver");
+MODULE_AUTHOR("Hans Verkuil <hans.verkuil@cisco.com>");
+MODULE_AUTHOR("Martin Bugge <marbugge@cisco.com>");
+MODULE_LICENSE("GPL");
+
+#define MASK_AD9389B_EDID_RDY_INT   0x04
+#define MASK_AD9389B_MSEN_INT       0x40
+#define MASK_AD9389B_HPD_INT        0x80
+
+#define MASK_AD9389B_HPD_DETECT     0x40
+#define MASK_AD9389B_MSEN_DETECT    0x20
+#define MASK_AD9389B_EDID_RDY       0x10
+
+#define EDID_MAX_RETRIES (8)
+#define EDID_DELAY 250
+#define EDID_MAX_SEGM 8
+
+/*
+**********************************************************************
+*
+*  Arrays with configuration parameters for the AD9389B
+*
+**********************************************************************
+*/
+
+struct i2c_reg_value {
+	u8 reg;
+	u8 value;
+};
+
+struct ad9389b_state_edid {
+	/* total number of blocks */
+	u32 blocks;
+	/* Number of segments read */
+	u32 segments;
+	u8 data[EDID_MAX_SEGM * 256];
+	/* Number of EDID read retries left */
+	unsigned read_retries;
+};
+
+struct ad9389b_state {
+	struct ad9389b_platform_data pdata;
+	struct v4l2_subdev sd;
+	struct media_pad pad;
+	struct v4l2_ctrl_handler hdl;
+	int chip_revision;
+	/* Is the ad9389b powered on? */
+	bool power_on;
+	/* Did we receive hotplug and rx-sense signals? */
+	bool have_monitor;
+	/* timings from s_dv_timings */
+	struct v4l2_dv_timings dv_timings;
+	/* controls */
+	struct v4l2_ctrl *hdmi_mode_ctrl;
+	struct v4l2_ctrl *hotplug_ctrl;
+	struct v4l2_ctrl *rx_sense_ctrl;
+	struct v4l2_ctrl *have_edid0_ctrl;
+	struct v4l2_ctrl *rgb_quantization_range_ctrl;
+	struct i2c_client *edid_i2c_client;
+	struct ad9389b_state_edid edid;
+	/* Running counter of the number of detected EDIDs (for debugging) */
+	unsigned edid_detect_counter;
+	struct workqueue_struct *work_queue;
+	struct delayed_work edid_handler; /* work entry */
+};
+
+static void ad9389b_check_monitor_present_status(struct v4l2_subdev *sd);
+static bool ad9389b_check_edid_status(struct v4l2_subdev *sd);
+static void ad9389b_setup(struct v4l2_subdev *sd);
+static int ad9389b_s_i2s_clock_freq(struct v4l2_subdev *sd, u32 freq);
+static int ad9389b_s_clock_freq(struct v4l2_subdev *sd, u32 freq);
+
+static inline struct ad9389b_state *get_ad9389b_state(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct ad9389b_state, sd);
+}
+
+static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl)
+{
+	return &container_of(ctrl->handler, struct ad9389b_state, hdl)->sd;
+}
+
+/* ------------------------ I2C ----------------------------------------------- */
+
+static int ad9389b_rd(struct v4l2_subdev *sd, u8 reg)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	return i2c_smbus_read_byte_data(client, reg);
+}
+
+static int ad9389b_wr(struct v4l2_subdev *sd, u8 reg, u8 val)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	int ret;
+	int i;
+
+	for (i = 0; i < 3; i++) {
+		ret = i2c_smbus_write_byte_data(client, reg, val);
+		if (ret == 0)
+			return 0;
+	}
+	v4l2_err(sd, "I2C Write Problem\n");
+	return ret;
+}
+
+/* To set specific bits in the register, a clear-mask is given (to be AND-ed),
+   and then the value-mask (to be OR-ed). */
+static inline void ad9389b_wr_and_or(struct v4l2_subdev *sd, u8 reg,
+						u8 clr_mask, u8 val_mask)
+{
+	ad9389b_wr(sd, reg, (ad9389b_rd(sd, reg) & clr_mask) | val_mask);
+}
+
+static void ad9389b_edid_rd(struct v4l2_subdev *sd, u16 len, u8 *buf)
+{
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+	int i;
+
+	v4l2_dbg(1, debug, sd, "%s:\n", __func__);
+
+	for (i = 0; i < len; i++)
+		buf[i] = i2c_smbus_read_byte_data(state->edid_i2c_client, i);
+}
+
+static inline bool ad9389b_have_hotplug(struct v4l2_subdev *sd)
+{
+	return ad9389b_rd(sd, 0x42) & MASK_AD9389B_HPD_DETECT;
+}
+
+static inline bool ad9389b_have_rx_sense(struct v4l2_subdev *sd)
+{
+	return ad9389b_rd(sd, 0x42) & MASK_AD9389B_MSEN_DETECT;
+}
+
+static void ad9389b_csc_conversion_mode(struct v4l2_subdev *sd, u8 mode)
+{
+	ad9389b_wr_and_or(sd, 0x17, 0xe7, (mode & 0x3)<<3);
+	ad9389b_wr_and_or(sd, 0x18, 0x9f, (mode & 0x3)<<5);
+}
+
+static void ad9389b_csc_coeff(struct v4l2_subdev *sd,
+			      u16 A1, u16 A2, u16 A3, u16 A4,
+			      u16 B1, u16 B2, u16 B3, u16 B4,
+			      u16 C1, u16 C2, u16 C3, u16 C4)
+{
+	/* A */
+	ad9389b_wr_and_or(sd, 0x18, 0xe0, A1>>8);
+	ad9389b_wr(sd, 0x19, A1);
+	ad9389b_wr_and_or(sd, 0x1A, 0xe0, A2>>8);
+	ad9389b_wr(sd, 0x1B, A2);
+	ad9389b_wr_and_or(sd, 0x1c, 0xe0, A3>>8);
+	ad9389b_wr(sd, 0x1d, A3);
+	ad9389b_wr_and_or(sd, 0x1e, 0xe0, A4>>8);
+	ad9389b_wr(sd, 0x1f, A4);
+
+	/* B */
+	ad9389b_wr_and_or(sd, 0x20, 0xe0, B1>>8);
+	ad9389b_wr(sd, 0x21, B1);
+	ad9389b_wr_and_or(sd, 0x22, 0xe0, B2>>8);
+	ad9389b_wr(sd, 0x23, B2);
+	ad9389b_wr_and_or(sd, 0x24, 0xe0, B3>>8);
+	ad9389b_wr(sd, 0x25, B3);
+	ad9389b_wr_and_or(sd, 0x26, 0xe0, B4>>8);
+	ad9389b_wr(sd, 0x27, B4);
+
+	/* C */
+	ad9389b_wr_and_or(sd, 0x28, 0xe0, C1>>8);
+	ad9389b_wr(sd, 0x29, C1);
+	ad9389b_wr_and_or(sd, 0x2A, 0xe0, C2>>8);
+	ad9389b_wr(sd, 0x2B, C2);
+	ad9389b_wr_and_or(sd, 0x2C, 0xe0, C3>>8);
+	ad9389b_wr(sd, 0x2D, C3);
+	ad9389b_wr_and_or(sd, 0x2E, 0xe0, C4>>8);
+	ad9389b_wr(sd, 0x2F, C4);
+}
+
+static void ad9389b_csc_rgb_full2limit(struct v4l2_subdev *sd, bool enable)
+{
+	if (enable) {
+		u8 csc_mode = 0;
+
+		ad9389b_csc_conversion_mode(sd, csc_mode);
+		ad9389b_csc_coeff(sd,
+				  4096-564, 0, 0, 256,
+				  0, 4096-564, 0, 256,
+				  0, 0, 4096-564, 256);
+		/* enable CSC */
+		ad9389b_wr_and_or(sd, 0x3b, 0xfe, 0x1);
+		/* AVI infoframe: Limited range RGB (16-235) */
+		ad9389b_wr_and_or(sd, 0xcd, 0xf9, 0x02);
+	} else {
+		/* disable CSC */
+		ad9389b_wr_and_or(sd, 0x3b, 0xfe, 0x0);
+		/* AVI infoframe: Full range RGB (0-255) */
+		ad9389b_wr_and_or(sd, 0xcd, 0xf9, 0x04);
+	}
+}
+
+static void ad9389b_set_IT_content_AVI_InfoFrame(struct v4l2_subdev *sd)
+{
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+
+	if (state->dv_timings.bt.standards & V4L2_DV_BT_STD_CEA861) {
+		/* CEA format, not IT  */
+		ad9389b_wr_and_or(sd, 0xcd, 0xbf, 0x00);
+	} else {
+		/* IT format */
+		ad9389b_wr_and_or(sd, 0xcd, 0xbf, 0x40);
+	}
+}
+
+static int ad9389b_set_rgb_quantization_mode(struct v4l2_subdev *sd, struct v4l2_ctrl *ctrl)
+{
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+
+	switch (ctrl->val) {
+	case V4L2_DV_RGB_RANGE_AUTO:
+		/* automatic */
+		if (state->dv_timings.bt.standards & V4L2_DV_BT_STD_CEA861) {
+			/* cea format, RGB limited range (16-235) */
+			ad9389b_csc_rgb_full2limit(sd, true);
+		} else {
+			/* not cea format, RGB full range (0-255) */
+			ad9389b_csc_rgb_full2limit(sd, false);
+		}
+		break;
+	case V4L2_DV_RGB_RANGE_LIMITED:
+		/* RGB limited range (16-235) */
+		ad9389b_csc_rgb_full2limit(sd, true);
+		break;
+	case V4L2_DV_RGB_RANGE_FULL:
+		/* RGB full range (0-255) */
+		ad9389b_csc_rgb_full2limit(sd, false);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void ad9389b_set_manual_pll_gear(struct v4l2_subdev *sd, u32 pixelclock)
+{
+	u8 gear;
+
+	/* Workaround for TMDS PLL problem
+	 * The TMDS PLL in AD9389b change gear when the chip is heated above a
+	 * certain temperature. The output is disabled when the PLL change gear
+	 * so the monitor has to lock on the signal again. A workaround for
+	 * this is to use the manual PLL gears. This is a solution from Analog
+	 * Devices that is not documented in the datasheets.
+	 * 0x98 [7] = enable manual gearing. 0x98 [6:4] = gear
+	 *
+	 * The pixel frequency ranges are based on readout of the gear the
+	 * automatic gearing selects for different pixel clocks
+	 * (read from 0x9e [3:1]).
+	 */
+
+	if (pixelclock > 140000000)
+		gear = 0xc0; /* 4th gear */
+	else if (pixelclock > 117000000)
+		gear = 0xb0; /* 3rd gear */
+	else if (pixelclock > 87000000)
+		gear = 0xa0; /* 2nd gear */
+	else if (pixelclock > 60000000)
+		gear = 0x90; /* 1st gear */
+	else
+		gear = 0x80; /* 0th gear */
+
+	ad9389b_wr_and_or(sd, 0x98, 0x0f, gear);
+}
+
+/* ------------------------------ CTRL OPS ------------------------------ */
+
+static int ad9389b_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct v4l2_subdev *sd = to_sd(ctrl);
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+
+	v4l2_dbg(1, debug, sd,
+		"%s: ctrl id: %d, ctrl->val %d\n", __func__, ctrl->id, ctrl->val);
+
+	if (state->hdmi_mode_ctrl == ctrl) {
+		/* Set HDMI or DVI-D */
+		ad9389b_wr_and_or(sd, 0xaf, 0xfd,
+				ctrl->val == V4L2_DV_TX_MODE_HDMI ? 0x02 : 0x00);
+		return 0;
+	}
+	if (state->rgb_quantization_range_ctrl == ctrl)
+		return ad9389b_set_rgb_quantization_mode(sd, ctrl);
+	return -EINVAL;
+}
+
+static const struct v4l2_ctrl_ops ad9389b_ctrl_ops = {
+	.s_ctrl = ad9389b_s_ctrl,
+};
+
+/* ---------------------------- CORE OPS ------------------------------------------- */
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int ad9389b_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	if (!v4l2_chip_match_i2c_client(client, &reg->match))
+		return -EINVAL;
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+	reg->val = ad9389b_rd(sd, reg->reg & 0xff);
+	reg->size = 1;
+	return 0;
+}
+
+static int ad9389b_s_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	if (!v4l2_chip_match_i2c_client(client, &reg->match))
+		return -EINVAL;
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+	ad9389b_wr(sd, reg->reg & 0xff, reg->val & 0xff);
+	return 0;
+}
+#endif
+
+static int ad9389b_g_chip_ident(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_AD9389B, 0);
+}
+
+static int ad9389b_log_status(struct v4l2_subdev *sd)
+{
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+	struct ad9389b_state_edid *edid = &state->edid;
+
+	static const char * const states[] = {
+		"in reset",
+		"reading EDID",
+		"idle",
+		"initializing HDCP",
+		"HDCP enabled",
+		"initializing HDCP repeater",
+		"6", "7", "8", "9", "A", "B", "C", "D", "E", "F"
+	};
+	static const char * const errors[] = {
+		"no error",
+		"bad receiver BKSV",
+		"Ri mismatch",
+		"Pj mismatch",
+		"i2c error",
+		"timed out",
+		"max repeater cascade exceeded",
+		"hash check failed",
+		"too many devices",
+		"9", "A", "B", "C", "D", "E", "F"
+	};
+
+	u8 manual_gear;
+
+	v4l2_info(sd, "chip revision %d\n", state->chip_revision);
+	v4l2_info(sd, "power %s\n", state->power_on ? "on" : "off");
+	v4l2_info(sd, "%s hotplug, %s Rx Sense, %s EDID (%d block(s))\n",
+			(ad9389b_rd(sd, 0x42) & MASK_AD9389B_HPD_DETECT) ?
+							"detected" : "no",
+			(ad9389b_rd(sd, 0x42) & MASK_AD9389B_MSEN_DETECT) ?
+							"detected" : "no",
+			edid->segments ? "found" : "no", edid->blocks);
+	if (state->have_monitor) {
+		v4l2_info(sd, "%s output %s\n",
+				  (ad9389b_rd(sd, 0xaf) & 0x02) ?
+				  "HDMI" : "DVI-D",
+				  (ad9389b_rd(sd, 0xa1) & 0x3c) ?
+				  "disabled" : "enabled");
+	}
+	v4l2_info(sd, "ad9389b: %s\n", (ad9389b_rd(sd, 0xb8) & 0x40) ?
+					"encrypted" : "no encryption");
+	v4l2_info(sd, "state: %s, error: %s, detect count: %u, msk/irq: %02x/%02x\n",
+			states[ad9389b_rd(sd, 0xc8) & 0xf],
+			errors[ad9389b_rd(sd, 0xc8) >> 4],
+			state->edid_detect_counter,
+			ad9389b_rd(sd, 0x94), ad9389b_rd(sd, 0x96));
+	manual_gear = ad9389b_rd(sd, 0x98) & 0x80;
+	v4l2_info(sd, "ad9389b: RGB quantization: %s range\n",
+			ad9389b_rd(sd, 0x3b) & 0x01 ? "limited" : "full");
+	v4l2_info(sd, "ad9389b: %s gear %d\n",
+		  manual_gear ? "manual" : "automatic",
+		  manual_gear ? ((ad9389b_rd(sd, 0x98) & 0x70) >> 4) :
+				((ad9389b_rd(sd, 0x9e) & 0x0e) >> 1));
+	if (state->have_monitor) {
+		if (ad9389b_rd(sd, 0xaf) & 0x02) {
+			/* HDMI only */
+			u8 manual_cts = ad9389b_rd(sd, 0x0a) & 0x80;
+			u32 N = (ad9389b_rd(sd, 0x01) & 0xf) << 16 |
+				 ad9389b_rd(sd, 0x02) << 8 |
+				 ad9389b_rd(sd, 0x03);
+			u8 vic_detect = ad9389b_rd(sd, 0x3e) >> 2;
+			u8 vic_sent = ad9389b_rd(sd, 0x3d) & 0x3f;
+			u32 CTS;
+
+			if (manual_cts)
+				CTS = (ad9389b_rd(sd, 0x07) & 0xf) << 16 |
+				       ad9389b_rd(sd, 0x08) << 8 |
+				       ad9389b_rd(sd, 0x09);
+			else
+				CTS = (ad9389b_rd(sd, 0x04) & 0xf) << 16 |
+				       ad9389b_rd(sd, 0x05) << 8 |
+				       ad9389b_rd(sd, 0x06);
+			N = (ad9389b_rd(sd, 0x01) & 0xf) << 16 |
+			     ad9389b_rd(sd, 0x02) << 8 |
+			     ad9389b_rd(sd, 0x03);
+
+			v4l2_info(sd, "ad9389b: CTS %s mode: N %d, CTS %d\n",
+				manual_cts ? "manual" : "automatic", N, CTS);
+
+			v4l2_info(sd, "ad9389b: VIC: detected %d, sent %d\n",
+				vic_detect, vic_sent);
+		}
+	}
+	if (state->dv_timings.type == V4L2_DV_BT_656_1120) {
+		struct v4l2_bt_timings *bt = bt = &state->dv_timings.bt;
+		u32 frame_width = bt->width + bt->hfrontporch +
+			bt->hsync + bt->hbackporch;
+		u32 frame_height = bt->height + bt->vfrontporch +
+			bt->vsync + bt->vbackporch;
+		u32 frame_size = frame_width * frame_height;
+
+		v4l2_info(sd, "timings: %ux%u%s%u (%ux%u). Pix freq. = %u Hz. Polarities = 0x%x\n",
+			bt->width, bt->height, bt->interlaced ? "i" : "p",
+			frame_size > 0 ?  (unsigned)bt->pixelclock / frame_size : 0,
+			frame_width, frame_height,
+			(unsigned)bt->pixelclock, bt->polarities);
+	} else {
+		v4l2_info(sd, "no timings set\n");
+	}
+	return 0;
+}
+
+/* Power up/down ad9389b */
+static int ad9389b_s_power(struct v4l2_subdev *sd, int on)
+{
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+	struct ad9389b_platform_data *pdata = &state->pdata;
+	const int retries = 20;
+	int i;
+
+	v4l2_dbg(1, debug, sd, "%s: power %s\n", __func__, on ? "on" : "off");
+
+	state->power_on = on;
+
+	if (!on) {
+		/* Power down */
+		ad9389b_wr_and_or(sd, 0x41, 0xbf, 0x40);
+		return true;
+	}
+
+	/* Power up */
+	/* The ad9389b does not always come up immediately.
+	   Retry multiple times. */
+	for (i = 0; i < retries; i++) {
+		ad9389b_wr_and_or(sd, 0x41, 0xbf, 0x0);
+		if ((ad9389b_rd(sd, 0x41) & 0x40) == 0)
+			break;
+		ad9389b_wr_and_or(sd, 0x41, 0xbf, 0x40);
+		msleep(10);
+	}
+	if (i == retries) {
+		v4l2_dbg(1, debug, sd, "failed to powerup the ad9389b\n");
+		ad9389b_s_power(sd, 0);
+		return false;
+	}
+	if (i > 1)
+		v4l2_dbg(1, debug, sd,
+			"needed %d retries to powerup the ad9389b\n", i);
+
+	/* Select chip: AD9389B */
+	ad9389b_wr_and_or(sd, 0xba, 0xef, 0x10);
+
+	/* Reserved registers that must be set according to REF_01 p. 11*/
+	ad9389b_wr_and_or(sd, 0x98, 0xf0, 0x07);
+	ad9389b_wr(sd, 0x9c, 0x38);
+	ad9389b_wr_and_or(sd, 0x9d, 0xfc, 0x01);
+
+	/* Differential output drive strength */
+	if (pdata->diff_data_drive_strength > 0)
+		ad9389b_wr(sd, 0xa2, pdata->diff_data_drive_strength);
+	else
+		ad9389b_wr(sd, 0xa2, 0x87);
+
+	if (pdata->diff_clk_drive_strength > 0)
+		ad9389b_wr(sd, 0xa3, pdata->diff_clk_drive_strength);
+	else
+		ad9389b_wr(sd, 0xa3, 0x87);
+
+	ad9389b_wr(sd, 0x0a, 0x01);
+	ad9389b_wr(sd, 0xbb, 0xff);
+
+	/* Set number of attempts to read the EDID */
+	ad9389b_wr(sd, 0xc9, 0xf);
+	return true;
+}
+
+/* Enable interrupts */
+static void ad9389b_set_isr(struct v4l2_subdev *sd, bool enable)
+{
+	u8 irqs = MASK_AD9389B_HPD_INT | MASK_AD9389B_MSEN_INT;
+	u8 irqs_rd;
+	int retries = 100;
+
+	/* The datasheet says that the EDID ready interrupt should be
+	   disabled if there is no hotplug. */
+	if (!enable)
+		irqs = 0;
+	else if (ad9389b_have_hotplug(sd))
+		irqs |= MASK_AD9389B_EDID_RDY_INT;
+
+	/*
+	 * This i2c write can fail (approx. 1 in 1000 writes). But it
+	 * is essential that this register is correct, so retry it
+	 * multiple times.
+	 *
+	 * Note that the i2c write does not report an error, but the readback
+	 * clearly shows the wrong value.
+	 */
+	do {
+		ad9389b_wr(sd, 0x94, irqs);
+		irqs_rd = ad9389b_rd(sd, 0x94);
+	} while (retries-- && irqs_rd != irqs);
+
+	if (irqs_rd != irqs)
+		v4l2_err(sd, "Could not set interrupts: hw failure?\n");
+}
+
+/* Interrupt handler */
+static int ad9389b_isr(struct v4l2_subdev *sd, u32 status, bool *handled)
+{
+	u8 irq_status;
+
+	/* disable interrupts to prevent a race condition */
+	ad9389b_set_isr(sd, false);
+	irq_status = ad9389b_rd(sd, 0x96);
+	/* clear detected interrupts */
+	ad9389b_wr(sd, 0x96, irq_status);
+
+	if (irq_status & (MASK_AD9389B_HPD_INT | MASK_AD9389B_MSEN_INT))
+		ad9389b_check_monitor_present_status(sd);
+	if (irq_status & MASK_AD9389B_EDID_RDY_INT)
+		ad9389b_check_edid_status(sd);
+
+	/* enable interrupts */
+	ad9389b_set_isr(sd, true);
+	*handled = true;
+	return 0;
+}
+
+static const struct v4l2_subdev_core_ops ad9389b_core_ops = {
+	.log_status = ad9389b_log_status,
+	.g_chip_ident = ad9389b_g_chip_ident,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.g_register = ad9389b_g_register,
+	.s_register = ad9389b_s_register,
+#endif
+	.s_power = ad9389b_s_power,
+	.interrupt_service_routine = ad9389b_isr,
+};
+
+/* ------------------------------ PAD OPS ------------------------------ */
+
+static int ad9389b_get_edid(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid)
+{
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+
+	if (edid->pad != 0)
+		return -EINVAL;
+	if (edid->blocks == 0 || edid->blocks > 256)
+		return -EINVAL;
+	if (!edid->edid)
+		return -EINVAL;
+	if (!state->edid.segments) {
+		v4l2_dbg(1, debug, sd, "EDID segment 0 not found\n");
+		return -ENODATA;
+	}
+	if (edid->start_block >= state->edid.segments * 2)
+		return -E2BIG;
+	if (edid->blocks + edid->start_block >= state->edid.segments * 2)
+		edid->blocks = state->edid.segments * 2 - edid->start_block;
+	memcpy(edid->edid, &state->edid.data[edid->start_block * 128],
+				128 * edid->blocks);
+	return 0;
+}
+
+static const struct v4l2_subdev_pad_ops ad9389b_pad_ops = {
+	.get_edid = ad9389b_get_edid,
+};
+
+/* ------------------------------ VIDEO OPS ------------------------------ */
+
+/* Enable/disable ad9389b output */
+static int ad9389b_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+
+	v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__, (enable ? "en" : "dis"));
+
+	ad9389b_wr_and_or(sd, 0xa1, ~0x3c, (enable ? 0 : 0x3c));
+	if (enable) {
+		ad9389b_check_monitor_present_status(sd);
+	} else {
+		ad9389b_s_power(sd, 0);
+		state->have_monitor = false;
+	}
+	return 0;
+}
+
+static const struct v4l2_dv_timings ad9389b_timings[] = {
+	V4L2_DV_BT_CEA_720X480P59_94,
+	V4L2_DV_BT_CEA_720X576P50,
+	V4L2_DV_BT_CEA_1280X720P24,
+	V4L2_DV_BT_CEA_1280X720P25,
+	V4L2_DV_BT_CEA_1280X720P30,
+	V4L2_DV_BT_CEA_1280X720P50,
+	V4L2_DV_BT_CEA_1280X720P60,
+	V4L2_DV_BT_CEA_1920X1080P24,
+	V4L2_DV_BT_CEA_1920X1080P25,
+	V4L2_DV_BT_CEA_1920X1080P30,
+	V4L2_DV_BT_CEA_1920X1080P50,
+	V4L2_DV_BT_CEA_1920X1080P60,
+
+	V4L2_DV_BT_DMT_640X350P85,
+	V4L2_DV_BT_DMT_640X400P85,
+	V4L2_DV_BT_DMT_720X400P85,
+	V4L2_DV_BT_DMT_640X480P60,
+	V4L2_DV_BT_DMT_640X480P72,
+	V4L2_DV_BT_DMT_640X480P75,
+	V4L2_DV_BT_DMT_640X480P85,
+	V4L2_DV_BT_DMT_800X600P56,
+	V4L2_DV_BT_DMT_800X600P60,
+	V4L2_DV_BT_DMT_800X600P72,
+	V4L2_DV_BT_DMT_800X600P75,
+	V4L2_DV_BT_DMT_800X600P85,
+	V4L2_DV_BT_DMT_848X480P60,
+	V4L2_DV_BT_DMT_1024X768P60,
+	V4L2_DV_BT_DMT_1024X768P70,
+	V4L2_DV_BT_DMT_1024X768P75,
+	V4L2_DV_BT_DMT_1024X768P85,
+	V4L2_DV_BT_DMT_1152X864P75,
+	V4L2_DV_BT_DMT_1280X768P60_RB,
+	V4L2_DV_BT_DMT_1280X768P60,
+	V4L2_DV_BT_DMT_1280X768P75,
+	V4L2_DV_BT_DMT_1280X768P85,
+	V4L2_DV_BT_DMT_1280X800P60_RB,
+	V4L2_DV_BT_DMT_1280X800P60,
+	V4L2_DV_BT_DMT_1280X800P75,
+	V4L2_DV_BT_DMT_1280X800P85,
+	V4L2_DV_BT_DMT_1280X960P60,
+	V4L2_DV_BT_DMT_1280X960P85,
+	V4L2_DV_BT_DMT_1280X1024P60,
+	V4L2_DV_BT_DMT_1280X1024P75,
+	V4L2_DV_BT_DMT_1280X1024P85,
+	V4L2_DV_BT_DMT_1360X768P60,
+	V4L2_DV_BT_DMT_1400X1050P60_RB,
+	V4L2_DV_BT_DMT_1400X1050P60,
+	V4L2_DV_BT_DMT_1400X1050P75,
+	V4L2_DV_BT_DMT_1400X1050P85,
+	V4L2_DV_BT_DMT_1440X900P60_RB,
+	V4L2_DV_BT_DMT_1440X900P60,
+	V4L2_DV_BT_DMT_1600X1200P60,
+	V4L2_DV_BT_DMT_1680X1050P60_RB,
+	V4L2_DV_BT_DMT_1680X1050P60,
+	V4L2_DV_BT_DMT_1792X1344P60,
+	V4L2_DV_BT_DMT_1856X1392P60,
+	V4L2_DV_BT_DMT_1920X1200P60_RB,
+	V4L2_DV_BT_DMT_1366X768P60,
+	V4L2_DV_BT_DMT_1920X1080P60,
+	{},
+};
+
+static int ad9389b_s_dv_timings(struct v4l2_subdev *sd,
+				struct v4l2_dv_timings *timings)
+{
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+	int i;
+
+	v4l2_dbg(1, debug, sd, "%s:\n", __func__);
+
+	/* quick sanity check */
+	if (timings->type != V4L2_DV_BT_656_1120)
+		return -EINVAL;
+
+	if (timings->bt.interlaced)
+		return -EINVAL;
+	if (timings->bt.pixelclock < 27000000 ||
+	    timings->bt.pixelclock > 170000000)
+		return -EINVAL;
+
+	/* Fill the optional fields .standards and .flags in struct v4l2_dv_timings
+	   if the format is listed in ad9389b_timings[] */
+	for (i = 0; ad9389b_timings[i].bt.width; i++) {
+		if (v4l_match_dv_timings(timings, &ad9389b_timings[i], 0)) {
+			*timings = ad9389b_timings[i];
+			break;
+		}
+	}
+
+	timings->bt.flags &= ~V4L2_DV_FL_REDUCED_FPS;
+
+	/* save timings */
+	state->dv_timings = *timings;
+
+	/* update quantization range based on new dv_timings */
+	ad9389b_set_rgb_quantization_mode(sd, state->rgb_quantization_range_ctrl);
+
+	/* update PLL gear based on new dv_timings */
+	if (state->pdata.tmds_pll_gear == AD9389B_TMDS_PLL_GEAR_SEMI_AUTOMATIC)
+		ad9389b_set_manual_pll_gear(sd, (u32)timings->bt.pixelclock);
+
+	/* update AVI infoframe */
+	ad9389b_set_IT_content_AVI_InfoFrame(sd);
+
+	return 0;
+}
+
+static int ad9389b_g_dv_timings(struct v4l2_subdev *sd,
+				struct v4l2_dv_timings *timings)
+{
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+
+	v4l2_dbg(1, debug, sd, "%s:\n", __func__);
+
+	if (!timings)
+		return -EINVAL;
+
+	*timings = state->dv_timings;
+
+	return 0;
+}
+
+static int ad9389b_enum_dv_timings(struct v4l2_subdev *sd,
+			struct v4l2_enum_dv_timings *timings)
+{
+	if (timings->index >= ARRAY_SIZE(ad9389b_timings))
+		return -EINVAL;
+
+	memset(timings->reserved, 0, sizeof(timings->reserved));
+	timings->timings = ad9389b_timings[timings->index];
+	return 0;
+}
+
+static int ad9389b_dv_timings_cap(struct v4l2_subdev *sd,
+			struct v4l2_dv_timings_cap *cap)
+{
+	cap->type = V4L2_DV_BT_656_1120;
+	cap->bt.max_width = 1920;
+	cap->bt.max_height = 1200;
+	cap->bt.min_pixelclock = 27000000;
+	cap->bt.max_pixelclock = 170000000;
+	cap->bt.standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
+			 V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT;
+	cap->bt.capabilities = V4L2_DV_BT_CAP_PROGRESSIVE |
+		V4L2_DV_BT_CAP_REDUCED_BLANKING | V4L2_DV_BT_CAP_CUSTOM;
+	return 0;
+}
+
+static const struct v4l2_subdev_video_ops ad9389b_video_ops = {
+	.s_stream = ad9389b_s_stream,
+	.s_dv_timings = ad9389b_s_dv_timings,
+	.g_dv_timings = ad9389b_g_dv_timings,
+	.enum_dv_timings = ad9389b_enum_dv_timings,
+	.dv_timings_cap = ad9389b_dv_timings_cap,
+};
+
+static int ad9389b_s_audio_stream(struct v4l2_subdev *sd, int enable)
+{
+	v4l2_dbg(1, debug, sd, "%s: %sable\n", __func__, (enable ? "en" : "dis"));
+
+	if (enable)
+		ad9389b_wr_and_or(sd, 0x45, 0x3f, 0x80);
+	else
+		ad9389b_wr_and_or(sd, 0x45, 0x3f, 0x40);
+
+	return 0;
+}
+
+static int ad9389b_s_clock_freq(struct v4l2_subdev *sd, u32 freq)
+{
+	u32 N;
+
+	switch (freq) {
+	case 32000: N = 4096; break;
+	case 44100: N = 6272; break;
+	case 48000: N = 6144; break;
+	case 88200: N = 12544; break;
+	case 96000: N = 12288; break;
+	case 176400: N = 25088; break;
+	case 192000: N = 24576; break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Set N (used with CTS to regenerate the audio clock) */
+	ad9389b_wr(sd, 0x01, (N >> 16) & 0xf);
+	ad9389b_wr(sd, 0x02, (N >> 8) & 0xff);
+	ad9389b_wr(sd, 0x03, N & 0xff);
+
+	return 0;
+}
+
+static int ad9389b_s_i2s_clock_freq(struct v4l2_subdev *sd, u32 freq)
+{
+	u32 i2s_sf;
+
+	switch (freq) {
+	case 32000: i2s_sf = 0x30; break;
+	case 44100: i2s_sf = 0x00; break;
+	case 48000: i2s_sf = 0x20; break;
+	case 88200: i2s_sf = 0x80; break;
+	case 96000: i2s_sf = 0xa0; break;
+	case 176400: i2s_sf = 0xc0; break;
+	case 192000: i2s_sf = 0xe0; break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Set sampling frequency for I2S audio to 48 kHz */
+	ad9389b_wr_and_or(sd, 0x15, 0xf, i2s_sf);
+
+	return 0;
+}
+
+static int ad9389b_s_routing(struct v4l2_subdev *sd, u32 input, u32 output, u32 config)
+{
+	/* TODO based on input/output/config */
+	/* TODO See datasheet "Programmers guide" p. 39-40 */
+
+	/* Only 2 channels in use for application */
+	ad9389b_wr_and_or(sd, 0x50, 0x1f, 0x20);
+	/* Speaker mapping */
+	ad9389b_wr(sd, 0x51, 0x00);
+
+	/* TODO Where should this be placed? */
+	/* 16 bit audio word length */
+	ad9389b_wr_and_or(sd, 0x14, 0xf0, 0x02);
+
+	return 0;
+}
+
+static const struct v4l2_subdev_audio_ops ad9389b_audio_ops = {
+	.s_stream = ad9389b_s_audio_stream,
+	.s_clock_freq = ad9389b_s_clock_freq,
+	.s_i2s_clock_freq = ad9389b_s_i2s_clock_freq,
+	.s_routing = ad9389b_s_routing,
+};
+
+/* --------------------- SUBDEV OPS --------------------------------------- */
+
+static const struct v4l2_subdev_ops ad9389b_ops = {
+	.core  = &ad9389b_core_ops,
+	.video = &ad9389b_video_ops,
+	.audio = &ad9389b_audio_ops,
+	.pad = &ad9389b_pad_ops,
+};
+
+/* ----------------------------------------------------------------------- */
+static void ad9389b_dbg_dump_edid(int lvl, int debug, struct v4l2_subdev *sd,
+							int segment, u8 *buf)
+{
+	int i, j;
+
+	if (debug < lvl)
+		return;
+
+	v4l2_dbg(lvl, debug, sd, "edid segment %d\n", segment);
+	for (i = 0; i < 256; i += 16) {
+		u8 b[128];
+		u8 *bp = b;
+
+		if (i == 128)
+			v4l2_dbg(lvl, debug, sd, "\n");
+		for (j = i; j < i + 16; j++) {
+			sprintf(bp, "0x%02x, ", buf[j]);
+			bp += 6;
+		}
+		bp[0] = '\0';
+		v4l2_dbg(lvl, debug, sd, "%s\n", b);
+	}
+}
+
+static void ad9389b_edid_handler(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct ad9389b_state *state = container_of(dwork,
+			struct ad9389b_state, edid_handler);
+	struct v4l2_subdev *sd = &state->sd;
+	struct ad9389b_edid_detect ed;
+
+	v4l2_dbg(1, debug, sd, "%s:\n", __func__);
+
+	if (ad9389b_check_edid_status(sd)) {
+		/* Return if we received the EDID. */
+		return;
+	}
+
+	if (ad9389b_have_hotplug(sd)) {
+		/* We must retry reading the EDID several times, it is possible
+		 * that initially the EDID couldn't be read due to i2c errors
+		 * (DVI connectors are particularly prone to this problem). */
+		if (state->edid.read_retries) {
+			state->edid.read_retries--;
+			/* EDID read failed, trigger a retry */
+			ad9389b_wr(sd, 0xc9, 0xf);
+			queue_delayed_work(state->work_queue,
+					&state->edid_handler, EDID_DELAY);
+			return;
+		}
+	}
+
+	/* We failed to read the EDID, so send an event for this. */
+	ed.present = false;
+	ed.segment = ad9389b_rd(sd, 0xc4);
+	v4l2_subdev_notify(sd, AD9389B_EDID_DETECT, (void *)&ed);
+	v4l2_dbg(1, debug, sd, "%s: no edid found\n", __func__);
+}
+
+static void ad9389b_audio_setup(struct v4l2_subdev *sd)
+{
+	v4l2_dbg(1, debug, sd, "%s\n", __func__);
+
+	ad9389b_s_i2s_clock_freq(sd, 48000);
+	ad9389b_s_clock_freq(sd, 48000);
+	ad9389b_s_routing(sd, 0, 0, 0);
+}
+
+/* Initial setup of AD9389b */
+
+/* Configure hdmi transmitter. */
+static void ad9389b_setup(struct v4l2_subdev *sd)
+{
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+
+	v4l2_dbg(1, debug, sd, "%s\n", __func__);
+
+	/* Input format: RGB 4:4:4 */
+	ad9389b_wr_and_or(sd, 0x15, 0xf1, 0x0);
+	/* Output format: RGB 4:4:4 */
+	ad9389b_wr_and_or(sd, 0x16, 0x3f, 0x0);
+	/* CSC fixed point: +/-2, 1st order interpolation 4:2:2 -> 4:4:4 up
+	   conversion, Aspect ratio: 16:9 */
+	ad9389b_wr_and_or(sd, 0x17, 0xe1, 0x0e);
+	/* Disable pixel repetition and CSC */
+	ad9389b_wr_and_or(sd, 0x3b, 0x9e, 0x0);
+	/* Output format: RGB 4:4:4, Active Format Information is valid. */
+	ad9389b_wr_and_or(sd, 0x45, 0xc7, 0x08);
+	/* Underscanned */
+	ad9389b_wr_and_or(sd, 0x46, 0x3f, 0x80);
+	/* Setup video format */
+	ad9389b_wr(sd, 0x3c, 0x0);
+	/* Active format aspect ratio: same as picure. */
+	ad9389b_wr(sd, 0x47, 0x80);
+	/* No encryption */
+	ad9389b_wr_and_or(sd, 0xaf, 0xef, 0x0);
+	/* Positive clk edge capture for input video clock */
+	ad9389b_wr_and_or(sd, 0xba, 0x1f, 0x60);
+
+	ad9389b_audio_setup(sd);
+
+	v4l2_ctrl_handler_setup(&state->hdl);
+
+	ad9389b_set_IT_content_AVI_InfoFrame(sd);
+}
+
+static void ad9389b_notify_monitor_detect(struct v4l2_subdev *sd)
+{
+	struct ad9389b_monitor_detect mdt;
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+
+	mdt.present = state->have_monitor;
+	v4l2_subdev_notify(sd, AD9389B_MONITOR_DETECT, (void *)&mdt);
+}
+
+static void ad9389b_check_monitor_present_status(struct v4l2_subdev *sd)
+{
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+	/* read hotplug and rx-sense state */
+	u8 status = ad9389b_rd(sd, 0x42);
+
+	v4l2_dbg(1, debug, sd, "%s: status: 0x%x%s%s\n",
+			 __func__,
+			 status,
+			 status & MASK_AD9389B_HPD_DETECT ? ", hotplug" : "",
+			 status & MASK_AD9389B_MSEN_DETECT ? ", rx-sense" : "");
+
+	if ((status & MASK_AD9389B_HPD_DETECT) &&
+	    ((status & MASK_AD9389B_MSEN_DETECT) || state->edid.segments)) {
+		v4l2_dbg(1, debug, sd,
+				"%s: hotplug and (rx-sense or edid)\n", __func__);
+		if (!state->have_monitor) {
+			v4l2_dbg(1, debug, sd, "%s: monitor detected\n", __func__);
+			state->have_monitor = true;
+			ad9389b_set_isr(sd, true);
+			if (!ad9389b_s_power(sd, true)) {
+				v4l2_dbg(1, debug, sd,
+					"%s: monitor detected, powerup failed\n", __func__);
+				return;
+			}
+			ad9389b_setup(sd);
+			ad9389b_notify_monitor_detect(sd);
+			state->edid.read_retries = EDID_MAX_RETRIES;
+			queue_delayed_work(state->work_queue,
+					&state->edid_handler, EDID_DELAY);
+		}
+	} else if (status & MASK_AD9389B_HPD_DETECT) {
+		v4l2_dbg(1, debug, sd, "%s: hotplug detected\n", __func__);
+		state->edid.read_retries = EDID_MAX_RETRIES;
+		queue_delayed_work(state->work_queue,
+				&state->edid_handler, EDID_DELAY);
+	} else if (!(status & MASK_AD9389B_HPD_DETECT)) {
+		v4l2_dbg(1, debug, sd, "%s: hotplug not detected\n", __func__);
+		if (state->have_monitor) {
+			v4l2_dbg(1, debug, sd, "%s: monitor not detected\n", __func__);
+			state->have_monitor = false;
+			ad9389b_notify_monitor_detect(sd);
+		}
+		ad9389b_s_power(sd, false);
+		memset(&state->edid, 0, sizeof(struct ad9389b_state_edid));
+	}
+
+	/* update read only ctrls */
+	v4l2_ctrl_s_ctrl(state->hotplug_ctrl, ad9389b_have_hotplug(sd) ? 0x1 : 0x0);
+	v4l2_ctrl_s_ctrl(state->rx_sense_ctrl, ad9389b_have_rx_sense(sd) ? 0x1 : 0x0);
+	v4l2_ctrl_s_ctrl(state->have_edid0_ctrl, state->edid.segments ? 0x1 : 0x0);
+}
+
+static bool edid_block_verify_crc(u8 *edid_block)
+{
+	int i;
+	u8 sum = 0;
+
+	for (i = 0; i < 127; i++)
+		sum += *(edid_block + i);
+	return ((255 - sum + 1) == edid_block[127]);
+}
+
+static bool edid_segment_verify_crc(struct v4l2_subdev *sd, u32 segment)
+{
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+	u32 blocks = state->edid.blocks;
+	u8 *data = state->edid.data;
+
+	if (edid_block_verify_crc(&data[segment * 256])) {
+		if ((segment + 1) * 2 <= blocks)
+			return edid_block_verify_crc(&data[segment * 256 + 128]);
+		return true;
+	}
+	return false;
+}
+
+static bool ad9389b_check_edid_status(struct v4l2_subdev *sd)
+{
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+	struct ad9389b_edid_detect ed;
+	int segment;
+	u8 edidRdy = ad9389b_rd(sd, 0xc5);
+
+	v4l2_dbg(1, debug, sd, "%s: edid ready (retries: %d)\n",
+			 __func__, EDID_MAX_RETRIES - state->edid.read_retries);
+
+	if (!(edidRdy & MASK_AD9389B_EDID_RDY))
+		return false;
+
+	segment = ad9389b_rd(sd, 0xc4);
+	if (segment >= EDID_MAX_SEGM) {
+		v4l2_err(sd, "edid segment number too big\n");
+		return false;
+	}
+	v4l2_dbg(1, debug, sd, "%s: got segment %d\n", __func__, segment);
+	ad9389b_edid_rd(sd, 256, &state->edid.data[segment * 256]);
+	ad9389b_dbg_dump_edid(2, debug, sd, segment,
+			&state->edid.data[segment * 256]);
+	if (segment == 0) {
+		state->edid.blocks = state->edid.data[0x7e] + 1;
+		v4l2_dbg(1, debug, sd, "%s: %d blocks in total\n",
+				__func__, state->edid.blocks);
+	}
+	if (!edid_segment_verify_crc(sd, segment)) {
+		/* edid crc error, force reread of edid segment */
+		ad9389b_s_power(sd, false);
+		ad9389b_s_power(sd, true);
+		return false;
+	}
+	/* one more segment read ok */
+	state->edid.segments = segment + 1;
+	if (((state->edid.data[0x7e] >> 1) + 1) > state->edid.segments) {
+		/* Request next EDID segment */
+		v4l2_dbg(1, debug, sd, "%s: request segment %d\n",
+				__func__, state->edid.segments);
+		ad9389b_wr(sd, 0xc9, 0xf);
+		ad9389b_wr(sd, 0xc4, state->edid.segments);
+		state->edid.read_retries = EDID_MAX_RETRIES;
+		queue_delayed_work(state->work_queue,
+				&state->edid_handler, EDID_DELAY);
+		return false;
+	}
+
+	/* report when we have all segments but report only for segment 0 */
+	ed.present = true;
+	ed.segment = 0;
+	v4l2_subdev_notify(sd, AD9389B_EDID_DETECT, (void *)&ed);
+	state->edid_detect_counter++;
+	v4l2_ctrl_s_ctrl(state->have_edid0_ctrl, state->edid.segments ? 0x1 : 0x0);
+	return ed.present;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static void ad9389b_init_setup(struct v4l2_subdev *sd)
+{
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+	struct ad9389b_state_edid *edid = &state->edid;
+
+	v4l2_dbg(1, debug, sd, "%s\n", __func__);
+
+	/* clear all interrupts */
+	ad9389b_wr(sd, 0x96, 0xff);
+
+	memset(edid, 0, sizeof(struct ad9389b_state_edid));
+	state->have_monitor = false;
+	ad9389b_set_isr(sd, false);
+}
+
+static int ad9389b_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	const struct v4l2_dv_timings dv1080p60 = V4L2_DV_BT_CEA_1920X1080P60;
+	struct ad9389b_state *state;
+	struct ad9389b_platform_data *pdata = client->dev.platform_data;
+	struct v4l2_ctrl_handler *hdl;
+	struct v4l2_subdev *sd;
+	int err = -EIO;
+
+	/* Check if the adapter supports the needed features */
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -EIO;
+
+	v4l_dbg(1, debug, client, "detecting ad9389b client on address 0x%x\n",
+			client->addr << 1);
+
+	state = kzalloc(sizeof(struct ad9389b_state), GFP_KERNEL);
+	if (!state)
+		return -ENOMEM;
+
+	/* Platform data */
+	if (pdata == NULL) {
+		v4l_err(client, "No platform data!\n");
+		err = -ENODEV;
+		goto err_free;
+	}
+	memcpy(&state->pdata, pdata, sizeof(state->pdata));
+
+	sd = &state->sd;
+	v4l2_i2c_subdev_init(sd, client, &ad9389b_ops);
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+	hdl = &state->hdl;
+	v4l2_ctrl_handler_init(hdl, 5);
+
+	/* private controls */
+
+	state->hdmi_mode_ctrl = v4l2_ctrl_new_std_menu(hdl, &ad9389b_ctrl_ops,
+			V4L2_CID_DV_TX_MODE, V4L2_DV_TX_MODE_HDMI,
+			0, V4L2_DV_TX_MODE_DVI_D);
+	state->hdmi_mode_ctrl->is_private = true;
+	state->hotplug_ctrl = v4l2_ctrl_new_std(hdl, NULL,
+			V4L2_CID_DV_TX_HOTPLUG, 0, 1, 0, 0);
+	state->hotplug_ctrl->is_private = true;
+	state->rx_sense_ctrl = v4l2_ctrl_new_std(hdl, NULL,
+			V4L2_CID_DV_TX_RXSENSE, 0, 1, 0, 0);
+	state->rx_sense_ctrl->is_private = true;
+	state->have_edid0_ctrl = v4l2_ctrl_new_std(hdl, NULL,
+			V4L2_CID_DV_TX_EDID_PRESENT, 0, 1, 0, 0);
+	state->have_edid0_ctrl->is_private = true;
+	state->rgb_quantization_range_ctrl =
+		v4l2_ctrl_new_std_menu(hdl, &ad9389b_ctrl_ops,
+			V4L2_CID_DV_TX_RGB_RANGE, V4L2_DV_RGB_RANGE_FULL,
+			0, V4L2_DV_RGB_RANGE_AUTO);
+	state->rgb_quantization_range_ctrl->is_private = true;
+	sd->ctrl_handler = hdl;
+	if (hdl->error) {
+		err = hdl->error;
+
+		goto err_hdl;
+	}
+
+	state->pad.flags = MEDIA_PAD_FL_SINK;
+	err = media_entity_init(&sd->entity, 1, &state->pad, 0);
+	if (err)
+		goto err_hdl;
+
+	state->chip_revision = ad9389b_rd(sd, 0x0);
+	if (state->chip_revision != 2) {
+		v4l2_err(sd, "chip_revision %d != 2\n", state->chip_revision);
+		err = -EIO;
+		goto err_entity;
+	}
+	v4l2_dbg(1, debug, sd, "reg 0x41 0x%x, chip version (reg 0x00) 0x%x\n",
+			ad9389b_rd(sd, 0x41), state->chip_revision);
+
+	state->edid_i2c_client = i2c_new_dummy(client->adapter, (0x7e>>1));
+	if (state->edid_i2c_client == NULL) {
+		v4l2_err(sd, "failed to register edid i2c client\n");
+		goto err_entity;
+	}
+
+	state->work_queue = create_singlethread_workqueue(sd->name);
+	if (state->work_queue == NULL) {
+		v4l2_err(sd, "could not create workqueue\n");
+		goto err_unreg;
+	}
+
+	INIT_DELAYED_WORK(&state->edid_handler, ad9389b_edid_handler);
+	state->dv_timings = dv1080p60;
+
+	ad9389b_init_setup(sd);
+	ad9389b_set_isr(sd, true);
+
+	v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name,
+			  client->addr << 1, client->adapter->name);
+	return 0;
+
+err_unreg:
+	i2c_unregister_device(state->edid_i2c_client);
+err_entity:
+	media_entity_cleanup(&sd->entity);
+err_hdl:
+	v4l2_ctrl_handler_free(&state->hdl);
+err_free:
+	kfree(state);
+	return err;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static int ad9389b_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ad9389b_state *state = get_ad9389b_state(sd);
+
+	state->chip_revision = -1;
+
+	v4l2_dbg(1, debug, sd, "%s removed @ 0x%x (%s)\n", client->name,
+		 client->addr << 1, client->adapter->name);
+
+	ad9389b_s_stream(sd, false);
+	ad9389b_s_audio_stream(sd, false);
+	ad9389b_init_setup(sd);
+	cancel_delayed_work(&state->edid_handler);
+	i2c_unregister_device(state->edid_i2c_client);
+	destroy_workqueue(state->work_queue);
+	v4l2_device_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	v4l2_ctrl_handler_free(sd->ctrl_handler);
+	kfree(get_ad9389b_state(sd));
+	return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static struct i2c_device_id ad9389b_id[] = {
+	{ "ad9389b", V4L2_IDENT_AD9389B },
+	{ "ad9889b", V4L2_IDENT_AD9389B },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ad9389b_id);
+
+static struct i2c_driver ad9389b_driver = {
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "ad9389b",
+	},
+	.probe = ad9389b_probe,
+	.remove = ad9389b_remove,
+	.id_table = ad9389b_id,
+};
+
+module_i2c_driver(ad9389b_driver);
diff --git a/include/media/ad9389b.h b/include/media/ad9389b.h
new file mode 100644
index 0000000..5ba9af8
--- /dev/null
+++ b/include/media/ad9389b.h
@@ -0,0 +1,49 @@
+/*
+ * Analog Devices AD9389B/AD9889B video encoder driver header
+ *
+ * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef AD9389B_H
+#define AD9389B_H
+
+enum ad9389b_tmds_pll_gear {
+	AD9389B_TMDS_PLL_GEAR_AUTOMATIC,
+	AD9389B_TMDS_PLL_GEAR_SEMI_AUTOMATIC,
+};
+
+/* Platform dependent definitions */
+struct ad9389b_platform_data {
+	enum ad9389b_tmds_pll_gear tmds_pll_gear ;
+	/* Differential Data/Clock Output Drive Strength (reg. 0xa2/0xa3) */
+	u8 diff_data_drive_strength;
+	u8 diff_clk_drive_strength;
+};
+
+/* notify events */
+#define AD9389B_MONITOR_DETECT 0
+#define AD9389B_EDID_DETECT 1
+
+struct ad9389b_monitor_detect {
+	int present;
+};
+
+struct ad9389b_edid_detect {
+	int present;
+	int segment;
+};
+
+#endif
diff --git a/include/media/v4l2-chip-ident.h b/include/media/v4l2-chip-ident.h
index 6adb360e..4ee125b 100644
--- a/include/media/v4l2-chip-ident.h
+++ b/include/media/v4l2-chip-ident.h
@@ -215,6 +215,9 @@ enum {
 	V4L2_IDENT_CX23888_AV = 8881, /* Integrated A/V decoder */
 	V4L2_IDENT_CX23888_IR = 8882, /* Integrated infrared controller */
 
+	/* module ad9389b: just ident 9389 */
+	V4L2_IDENT_AD9389B = 9389,
+
 	/* module tda9840: just ident 9840 */
 	V4L2_IDENT_TDA9840 = 9840,
 
-- 
1.7.10.4


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

* Re: [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort.
  2012-08-10 11:21 ` [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort Hans Verkuil
                     ` (6 preceding siblings ...)
  2012-08-10 11:21   ` [RFCv3 PATCH 8/8] ad9389b: driver for the Analog Devices AD9389B video encoder Hans Verkuil
@ 2012-08-13 11:48   ` Soby Mathew
  2012-08-13 12:15     ` Hans Verkuil
  7 siblings, 1 reply; 13+ messages in thread
From: Soby Mathew @ 2012-08-13 11:48 UTC (permalink / raw)
  To: Hans Verkuil
  Cc: linux-media, marbugge, Soby Mathew, mats.randgaard,
	manjunath.hadli, Tomasz Stanislawski, Mauro Carvalho Chehab,
	Scott Jiang, dri-devel

Hi Hans,
   The patch seems to cover most of the requirement.  I find 2 gaps:

> +/*  DV-class control IDs defined by V4L2 */
> +#define V4L2_CID_DV_CLASS_BASE                 (V4L2_CTRL_CLASS_DV | 0x900)
> +#define V4L2_CID_DV_CLASS                      (V4L2_CTRL_CLASS_DV | 1)
> +
> +#define        V4L2_CID_DV_TX_HOTPLUG                  (V4L2_CID_DV_CLASS_BASE + 1)
> +#define        V4L2_CID_DV_TX_RXSENSE                  (V4L2_CID_DV_CLASS_BASE + 2)
> +#define        V4L2_CID_DV_TX_EDID_PRESENT             (V4L2_CID_DV_CLASS_BASE + 3)
> +#define        V4L2_CID_DV_TX_MODE                     (V4L2_CID_DV_CLASS_BASE + 4)
> +enum v4l2_dv_tx_mode {
> +       V4L2_DV_TX_MODE_DVI_D   = 0,
> +       V4L2_DV_TX_MODE_HDMI    = 1,
> +};


Even at the receiver side the DVI/HDMI mode need to be detected. So
probably a control for the RX mode is needed.


> +#define V4L2_CID_DV_TX_RGB_RANGE               (V4L2_CID_DV_CLASS_BASE + 5)
> +enum v4l2_dv_rgb_range {
> +       V4L2_DV_RGB_RANGE_AUTO    = 0,
> +       V4L2_DV_RGB_RANGE_LIMITED = 1,
> +       V4L2_DV_RGB_RANGE_FULL    = 2,
> +};
> +
> +#define        V4L2_CID_DV_RX_POWER_PRESENT            (V4L2_CID_DV_CLASS_BASE + 100)
> +#define V4L2_CID_DV_RX_RGB_RANGE               (V4L2_CID_DV_CLASS_BASE + 101)
> +

Similarly, some sources can support YC mode of transmission instaed of
RGB. To control the YC Quantization Range, we can define  control
V4L2_CID_DV_TX_YC_RANGE

Similar control would be needed at the Rx side also like
V4L2_CID_DV_RX_YC_RANGE.


Best Regards
Soby Mathew

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

* Re: [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort.
  2012-08-13 11:48   ` [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort Soby Mathew
@ 2012-08-13 12:15     ` Hans Verkuil
  0 siblings, 0 replies; 13+ messages in thread
From: Hans Verkuil @ 2012-08-13 12:15 UTC (permalink / raw)
  To: Soby Mathew
  Cc: Hans Verkuil, linux-media, marbugge, Soby Mathew, mats.randgaard,
	manjunath.hadli, Tomasz Stanislawski, Mauro Carvalho Chehab,
	Scott Jiang, dri-devel

On Mon August 13 2012 13:48:28 Soby Mathew wrote:
> Hi Hans,
>    The patch seems to cover most of the requirement.  I find 2 gaps:
> 
> > +/*  DV-class control IDs defined by V4L2 */
> > +#define V4L2_CID_DV_CLASS_BASE                 (V4L2_CTRL_CLASS_DV | 0x900)
> > +#define V4L2_CID_DV_CLASS                      (V4L2_CTRL_CLASS_DV | 1)
> > +
> > +#define        V4L2_CID_DV_TX_HOTPLUG                  (V4L2_CID_DV_CLASS_BASE + 1)
> > +#define        V4L2_CID_DV_TX_RXSENSE                  (V4L2_CID_DV_CLASS_BASE + 2)
> > +#define        V4L2_CID_DV_TX_EDID_PRESENT             (V4L2_CID_DV_CLASS_BASE + 3)
> > +#define        V4L2_CID_DV_TX_MODE                     (V4L2_CID_DV_CLASS_BASE + 4)
> > +enum v4l2_dv_tx_mode {
> > +       V4L2_DV_TX_MODE_DVI_D   = 0,
> > +       V4L2_DV_TX_MODE_HDMI    = 1,
> > +};
> 
> 
> Even at the receiver side the DVI/HDMI mode need to be detected. So
> probably a control for the RX mode is needed.

We left that part out for the moment: we (Cisco) do not need that at the
moment, and for this first version I wanted to concentrate only on those
parts that were absolutely necessary.

Once it's merged it is much easier to add additional functionality like
that.

> 
> 
> > +#define V4L2_CID_DV_TX_RGB_RANGE               (V4L2_CID_DV_CLASS_BASE + 5)
> > +enum v4l2_dv_rgb_range {
> > +       V4L2_DV_RGB_RANGE_AUTO    = 0,
> > +       V4L2_DV_RGB_RANGE_LIMITED = 1,
> > +       V4L2_DV_RGB_RANGE_FULL    = 2,
> > +};
> > +
> > +#define        V4L2_CID_DV_RX_POWER_PRESENT            (V4L2_CID_DV_CLASS_BASE + 100)
> > +#define V4L2_CID_DV_RX_RGB_RANGE               (V4L2_CID_DV_CLASS_BASE + 101)
> > +
> 
> Similarly, some sources can support YC mode of transmission instaed of
> RGB. To control the YC Quantization Range, we can define  control
> V4L2_CID_DV_TX_YC_RANGE
> 
> Similar control would be needed at the Rx side also like
> V4L2_CID_DV_RX_YC_RANGE.

The question is if you actually need this control for the YC range. For RGB
it is necessary because you cannot rely on the autodetect: too many devices
do not handle that correctly, so you have to be able to override.

For YC the situation seems to be much better and we (Cisco) haven't seen the
need yet to be able to override the automatic YC range setup.

If there is a clear situation where this is necessary, then it can be added.

Regards,

	Hans

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

* Re: [RFCv3 PATCH 3/8] v4l2-subdev: add support for the new edid ioctls.
  2012-08-10 11:21   ` [RFCv3 PATCH 3/8] v4l2-subdev: add support for the new edid ioctls Hans Verkuil
@ 2012-08-16 17:55     ` Soby Mathew
  2012-08-17  7:42       ` Soby Mathew
  0 siblings, 1 reply; 13+ messages in thread
From: Soby Mathew @ 2012-08-16 17:55 UTC (permalink / raw)
  To: Hans Verkuil
  Cc: linux-media, marbugge, Soby Mathew, mats.randgaard,
	manjunath.hadli, Tomasz Stanislawski, Mauro Carvalho Chehab,
	Scott Jiang, dri-devel

Hi Hans,
   For EDID update, it is recommended that the HPD line be toggled
after the EDID update is completed. So for the driver to detect the
EDID write is complete, probably a field mentioning the EDID write
completed would be good, so that the HPD toggling can be done by the
driver.

Best Regards
Soby Mathew

On 8/10/12, Hans Verkuil <hans.verkuil@cisco.com> wrote:
> Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
> ---
>  drivers/media/video/v4l2-compat-ioctl32.c |   57
> +++++++++++++++++++++++++++++
>  drivers/media/video/v4l2-ioctl.c          |   13 +++++++
>  drivers/media/video/v4l2-subdev.c         |    6 +++
>  include/media/v4l2-subdev.h               |    2 +
>  4 files changed, 78 insertions(+)
>
> diff --git a/drivers/media/video/v4l2-compat-ioctl32.c
> b/drivers/media/video/v4l2-compat-ioctl32.c
> index 9ebd5c5..e843705 100644
> --- a/drivers/media/video/v4l2-compat-ioctl32.c
> +++ b/drivers/media/video/v4l2-compat-ioctl32.c
> @@ -16,6 +16,7 @@
>  #include <linux/compat.h>
>  #include <linux/module.h>
>  #include <linux/videodev2.h>
> +#include <linux/v4l2-subdev.h>
>  #include <media/v4l2-dev.h>
>  #include <media/v4l2-ioctl.h>
>
> @@ -729,6 +730,44 @@ static int put_v4l2_event32(struct v4l2_event *kp,
> struct v4l2_event32 __user *u
>  	return 0;
>  }
>
> +struct v4l2_subdev_edid32 {
> +	__u32 pad;
> +	__u32 start_block;
> +	__u32 blocks;
> +	__u32 reserved[5];
> +	compat_caddr_t edid;
> +};
> +
> +static int get_v4l2_subdev_edid32(struct v4l2_subdev_edid *kp, struct
> v4l2_subdev_edid32 __user *up)
> +{
> +	u32 tmp;
> +
> +	if (!access_ok(VERIFY_READ, up, sizeof(struct v4l2_subdev_edid32)) ||
> +		get_user(kp->pad, &up->pad) ||
> +		get_user(kp->start_block, &up->start_block) ||
> +		get_user(kp->blocks, &up->blocks) ||
> +		get_user(tmp, &up->edid) ||
> +		copy_from_user(kp->reserved, up->reserved, sizeof(kp->reserved)))
> +			return -EFAULT;
> +	kp->edid = compat_ptr(tmp);
> +	return 0;
> +}
> +
> +static int put_v4l2_subdev_edid32(struct v4l2_subdev_edid *kp, struct
> v4l2_subdev_edid32 __user *up)
> +{
> +	u32 tmp = (u32)((unsigned long)kp->edid);
> +
> +	if (!access_ok(VERIFY_WRITE, up, sizeof(struct v4l2_subdev_edid32)) ||
> +		put_user(kp->pad, &up->pad) ||
> +		put_user(kp->start_block, &up->start_block) ||
> +		put_user(kp->blocks, &up->blocks) ||
> +		put_user(tmp, &up->edid) ||
> +		copy_to_user(kp->reserved, up->reserved, sizeof(kp->reserved)))
> +			return -EFAULT;
> +	return 0;
> +}
> +
> +
>  #define VIDIOC_G_FMT32		_IOWR('V',  4, struct v4l2_format32)
>  #define VIDIOC_S_FMT32		_IOWR('V',  5, struct v4l2_format32)
>  #define VIDIOC_QUERYBUF32	_IOWR('V',  9, struct v4l2_buffer32)
> @@ -738,6 +777,8 @@ static int put_v4l2_event32(struct v4l2_event *kp,
> struct v4l2_event32 __user *u
>  #define VIDIOC_DQBUF32		_IOWR('V', 17, struct v4l2_buffer32)
>  #define VIDIOC_ENUMSTD32	_IOWR('V', 25, struct v4l2_standard32)
>  #define VIDIOC_ENUMINPUT32	_IOWR('V', 26, struct v4l2_input32)
> +#define VIDIOC_SUBDEV_G_EDID32	_IOWR('V', 63, struct v4l2_subdev_edid32)
> +#define VIDIOC_SUBDEV_S_EDID32	_IOWR('V', 64, struct v4l2_subdev_edid32)
>  #define VIDIOC_TRY_FMT32      	_IOWR('V', 64, struct v4l2_format32)
>  #define VIDIOC_G_EXT_CTRLS32    _IOWR('V', 71, struct v4l2_ext_controls32)
>  #define VIDIOC_S_EXT_CTRLS32    _IOWR('V', 72, struct v4l2_ext_controls32)
> @@ -765,6 +806,7 @@ static long do_video_ioctl(struct file *file, unsigned
> int cmd, unsigned long ar
>  		struct v4l2_ext_controls v2ecs;
>  		struct v4l2_event v2ev;
>  		struct v4l2_create_buffers v2crt;
> +		struct v4l2_subdev_edid v2edid;
>  		unsigned long vx;
>  		int vi;
>  	} karg;
> @@ -797,6 +839,8 @@ static long do_video_ioctl(struct file *file, unsigned
> int cmd, unsigned long ar
>  	case VIDIOC_S_OUTPUT32: cmd = VIDIOC_S_OUTPUT; break;
>  	case VIDIOC_CREATE_BUFS32: cmd = VIDIOC_CREATE_BUFS; break;
>  	case VIDIOC_PREPARE_BUF32: cmd = VIDIOC_PREPARE_BUF; break;
> +	case VIDIOC_SUBDEV_G_EDID32: cmd = VIDIOC_SUBDEV_G_EDID; break;
> +	case VIDIOC_SUBDEV_S_EDID32: cmd = VIDIOC_SUBDEV_S_EDID; break;
>  	}
>
>  	switch (cmd) {
> @@ -814,6 +858,12 @@ static long do_video_ioctl(struct file *file, unsigned
> int cmd, unsigned long ar
>  		compatible_arg = 0;
>  		break;
>
> +	case VIDIOC_SUBDEV_G_EDID:
> +	case VIDIOC_SUBDEV_S_EDID:
> +		err = get_v4l2_subdev_edid32(&karg.v2edid, up);
> +		compatible_arg = 0;
> +		break;
> +
>  	case VIDIOC_G_FMT:
>  	case VIDIOC_S_FMT:
>  	case VIDIOC_TRY_FMT:
> @@ -906,6 +956,11 @@ static long do_video_ioctl(struct file *file, unsigned
> int cmd, unsigned long ar
>  		err = put_v4l2_event32(&karg.v2ev, up);
>  		break;
>
> +	case VIDIOC_SUBDEV_G_EDID:
> +	case VIDIOC_SUBDEV_S_EDID:
> +		err = put_v4l2_subdev_edid32(&karg.v2edid, up);
> +		break;
> +
>  	case VIDIOC_G_FMT:
>  	case VIDIOC_S_FMT:
>  	case VIDIOC_TRY_FMT:
> @@ -1026,6 +1081,8 @@ long v4l2_compat_ioctl32(struct file *file, unsigned
> int cmd, unsigned long arg)
>  	case VIDIOC_QUERY_DV_TIMINGS:
>  	case VIDIOC_DV_TIMINGS_CAP:
>  	case VIDIOC_ENUM_FREQ_BANDS:
> +	case VIDIOC_SUBDEV_G_EDID32:
> +	case VIDIOC_SUBDEV_S_EDID32:
>  		ret = do_video_ioctl(file, cmd, arg);
>  		break;
>
> diff --git a/drivers/media/video/v4l2-ioctl.c
> b/drivers/media/video/v4l2-ioctl.c
> index c3b7b5f..1400f98 100644
> --- a/drivers/media/video/v4l2-ioctl.c
> +++ b/drivers/media/video/v4l2-ioctl.c
> @@ -2185,6 +2185,19 @@ static int check_array_args(unsigned int cmd, void
> *parg, size_t *array_size,
>  		break;
>  	}
>
> +	case VIDIOC_SUBDEV_G_EDID:
> +	case VIDIOC_SUBDEV_S_EDID: {
> +		struct v4l2_subdev_edid *edid = parg;
> +
> +		if (edid->blocks) {
> +			*user_ptr = (void __user *)edid->edid;
> +			*kernel_ptr = (void *)&edid->edid;
> +			*array_size = edid->blocks * 128;
> +			ret = 1;
> +		}
> +		break;
> +	}
> +
>  	case VIDIOC_S_EXT_CTRLS:
>  	case VIDIOC_G_EXT_CTRLS:
>  	case VIDIOC_TRY_EXT_CTRLS: {
> diff --git a/drivers/media/video/v4l2-subdev.c
> b/drivers/media/video/v4l2-subdev.c
> index 9182f81..dced41c 100644
> --- a/drivers/media/video/v4l2-subdev.c
> +++ b/drivers/media/video/v4l2-subdev.c
> @@ -348,6 +348,12 @@ static long subdev_do_ioctl(struct file *file, unsigned
> int cmd, void *arg)
>  		return v4l2_subdev_call(
>  			sd, pad, set_selection, subdev_fh, sel);
>  	}
> +
> +	case VIDIOC_SUBDEV_G_EDID:
> +		return v4l2_subdev_call(sd, pad, get_edid, arg);
> +
> +	case VIDIOC_SUBDEV_S_EDID:
> +		return v4l2_subdev_call(sd, pad, set_edid, arg);
>  #endif
>  	default:
>  		return v4l2_subdev_call(sd, core, ioctl, cmd, arg);
> diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
> index c35a354..74c578f 100644
> --- a/include/media/v4l2-subdev.h
> +++ b/include/media/v4l2-subdev.h
> @@ -476,6 +476,8 @@ struct v4l2_subdev_pad_ops {
>  			     struct v4l2_subdev_selection *sel);
>  	int (*set_selection)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh,
>  			     struct v4l2_subdev_selection *sel);
> +	int (*get_edid)(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid);
> +	int (*set_edid)(struct v4l2_subdev *sd, struct v4l2_subdev_edid *edid);
>  #ifdef CONFIG_MEDIA_CONTROLLER
>  	int (*link_validate)(struct v4l2_subdev *sd, struct media_link *link,
>  			     struct v4l2_subdev_format *source_fmt,
> --
> 1.7.10.4
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-media" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

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

* Re: [RFCv3 PATCH 3/8] v4l2-subdev: add support for the new edid ioctls.
  2012-08-16 17:55     ` Soby Mathew
@ 2012-08-17  7:42       ` Soby Mathew
  0 siblings, 0 replies; 13+ messages in thread
From: Soby Mathew @ 2012-08-17  7:42 UTC (permalink / raw)
  To: Hans Verkuil
  Cc: linux-media, marbugge, Soby Mathew, mats.randgaard,
	manjunath.hadli, Tomasz Stanislawski, Mauro Carvalho Chehab,
	Scott Jiang, dri-devel

Hi Hans
 I didnt catch this sentence in the documentation of the API "It is
not possible to set part of an EDID, it is always all or nothing." .
Guess, I have to read the documentation thoroughly before making
assumptions. It makes my question irrelevant.

Best Regards
Soby Mathew



On Thu, Aug 16, 2012 at 11:25 PM, Soby Mathew <soby.linuxtv@gmail.com> wrote:
> Hi Hans,
>    For EDID update, it is recommended that the HPD line be toggled
> after the EDID update is completed. So for the driver to detect the
> EDID write is complete, probably a field mentioning the EDID write
> completed would be good, so that the HPD toggling can be done by the
> driver.
>
> Best Regards
> Soby Mathew
>
<clipped>

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

end of thread, other threads:[~2012-08-17  7:42 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-08-10 11:21 [RFCv3 PATCH 0/8] V4L2: add missing pieces to support HDMI et al and add adv7604/ad9389b drivers Hans Verkuil
2012-08-10 11:21 ` [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort Hans Verkuil
2012-08-10 11:21   ` [RFCv3 PATCH 2/8] V4L2 spec: document the new DV controls and ioctls Hans Verkuil
2012-08-10 11:21   ` [RFCv3 PATCH 3/8] v4l2-subdev: add support for the new edid ioctls Hans Verkuil
2012-08-16 17:55     ` Soby Mathew
2012-08-17  7:42       ` Soby Mathew
2012-08-10 11:21   ` [RFCv3 PATCH 4/8] v4l2-ctrls.c: add support for the new DV controls Hans Verkuil
2012-08-10 11:21   ` [RFCv3 PATCH 5/8] v4l2-common: add v4l_match_dv_timings Hans Verkuil
2012-08-10 11:21   ` [RFCv3 PATCH 6/8] v4l2-common: add CVT and GTF detection functions Hans Verkuil
2012-08-10 11:21   ` [RFCv3 PATCH 7/8] adv7604: driver for the Analog Devices ADV7604 video decoder Hans Verkuil
2012-08-10 11:21   ` [RFCv3 PATCH 8/8] ad9389b: driver for the Analog Devices AD9389B video encoder Hans Verkuil
2012-08-13 11:48   ` [RFCv3 PATCH 1/8] v4l2 core: add the missing pieces to support DVI/HDMI/DisplayPort Soby Mathew
2012-08-13 12:15     ` Hans Verkuil

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.