linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/4] Synopsys Designware HDMI Video Capture Controller + PHY
@ 2017-06-16 10:07 Jose Abreu
  2017-06-16 10:07 ` [PATCH v2 1/4] [media] platform: Add Synopsys Designware HDMI RX PHY e405 Driver Jose Abreu
                   ` (3 more replies)
  0 siblings, 4 replies; 10+ messages in thread
From: Jose Abreu @ 2017-06-16 10:07 UTC (permalink / raw)
  To: linux-media, linux-kernel, devicetree
  Cc: Jose Abreu, Carlos Palminha, Mauro Carvalho Chehab, Hans Verkuil,
	Rob Herring, Mark Rutland

The Synopsys Designware HDMI RX controller is an HDMI receiver controller that
is responsible to process digital data that comes from a phy. The final result
is a stream of raw video data that can then be connected to a video DMA, for
example, and transfered into RAM so that it can be displayed.

The controller + phy available in this series natively support all HDMI 1.4 and
HDMI 2.0 modes, including deep color. Although, the driver is quite in its
initial stage and unfortunatelly only non deep color modes are supported. Also,
audio is not yet supported in the driver (the controller has several audio
output interfaces).

Version 2 is just a respin of the previous version with some minor bug fixes in
the controller driver and I also added CEC support.

This series was tested in a FPGA platform.

Jose Abreu (4):
  [media] platform: Add Synopsys Designware HDMI RX PHY e405 Driver
  [media] platform: Add Synopsys Designware HDMI RX Controller Driver
  MAINTAINERS: Add entry for Synopsys Designware HDMI drivers
  dt-bindings: media: Document Synopsys Designware HDMI RX

Cc: Carlos Palminha <palminha@synopsys.com>
Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
Cc: Hans Verkuil <hans.verkuil@cisco.com>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>

 .../devicetree/bindings/media/snps,dw-hdmi-rx.txt  |   41 +
 MAINTAINERS                                        |    7 +
 drivers/media/platform/Kconfig                     |    2 +
 drivers/media/platform/Makefile                    |    2 +
 drivers/media/platform/dwc/Kconfig                 |   18 +
 drivers/media/platform/dwc/Makefile                |    2 +
 drivers/media/platform/dwc/dw-hdmi-phy-e405.c      |  783 +++++++++
 drivers/media/platform/dwc/dw-hdmi-phy-e405.h      |   63 +
 drivers/media/platform/dwc/dw-hdmi-rx.c            | 1764 ++++++++++++++++++++
 drivers/media/platform/dwc/dw-hdmi-rx.h            |  435 +++++
 include/media/dwc/dw-hdmi-phy-pdata.h              |  131 ++
 include/media/dwc/dw-hdmi-rx-pdata.h               |   63 +
 12 files changed, 3311 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.txt
 create mode 100644 drivers/media/platform/dwc/Kconfig
 create mode 100644 drivers/media/platform/dwc/Makefile
 create mode 100644 drivers/media/platform/dwc/dw-hdmi-phy-e405.c
 create mode 100644 drivers/media/platform/dwc/dw-hdmi-phy-e405.h
 create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.c
 create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.h
 create mode 100644 include/media/dwc/dw-hdmi-phy-pdata.h
 create mode 100644 include/media/dwc/dw-hdmi-rx-pdata.h

-- 
1.9.1

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

* [PATCH v2 1/4] [media] platform: Add Synopsys Designware HDMI RX PHY e405 Driver
  2017-06-16 10:07 [PATCH v2 0/4] Synopsys Designware HDMI Video Capture Controller + PHY Jose Abreu
@ 2017-06-16 10:07 ` Jose Abreu
  2017-06-16 10:07 ` [PATCH v2 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver Jose Abreu
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 10+ messages in thread
From: Jose Abreu @ 2017-06-16 10:07 UTC (permalink / raw)
  To: linux-media, linux-kernel, devicetree
  Cc: Jose Abreu, Carlos Palminha, Mauro Carvalho Chehab, Hans Verkuil

This adds support for the Synopsys Designware HDMI RX PHY e405. This
phy receives and decodes HDMI video that is delivered to a controller.

Main features included in this driver are:
	- Equalizer algorithm that chooses the phy best settings
	according to the detected HDMI cable characteristics.
	- Support for scrambling
	- Support for HDMI 2.0 modes up to 6G (HDMI 4k@60Hz).

The driver was implemented as a standalone V4L2 subdevice and the
phy interface with the controller was implemented using V4L2 ioctls. I
do not know if this is the best option but it is not possible to use the
existing API functions directly as we need specific functions that will
be called by the controller at specific configuration stages.

There is also a bidirectional communication between controller and phy:
The phy must provide functions that the controller will call (i.e.
configuration functions) and the controller must provide read/write
callbacks, as well as other specific functions.

Signed-off-by: Jose Abreu <joabreu@synopsys.com>
Cc: Carlos Palminha <palminha@synopsys.com>
Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
Cc: Hans Verkuil <hans.verkuil@cisco.com>

Changes from RFC:
	- Remove a bunch of functions that can be collapsed into
	a single config() function
	- Add comments for the callbacks and structures (Hans)

Signed-off-by: Jose Abreu <joabreu@synopsys.com>
---
 drivers/media/platform/Kconfig                |   2 +
 drivers/media/platform/Makefile               |   2 +
 drivers/media/platform/dwc/Kconfig            |   8 +
 drivers/media/platform/dwc/Makefile           |   1 +
 drivers/media/platform/dwc/dw-hdmi-phy-e405.c | 783 ++++++++++++++++++++++++++
 drivers/media/platform/dwc/dw-hdmi-phy-e405.h |  63 +++
 include/media/dwc/dw-hdmi-phy-pdata.h         | 131 +++++
 7 files changed, 990 insertions(+)
 create mode 100644 drivers/media/platform/dwc/Kconfig
 create mode 100644 drivers/media/platform/dwc/Makefile
 create mode 100644 drivers/media/platform/dwc/dw-hdmi-phy-e405.c
 create mode 100644 drivers/media/platform/dwc/dw-hdmi-phy-e405.h
 create mode 100644 include/media/dwc/dw-hdmi-phy-pdata.h

diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 288d3b0..8d83803 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -33,6 +33,8 @@ source "drivers/media/platform/omap/Kconfig"
 
 source "drivers/media/platform/blackfin/Kconfig"
 
+source "drivers/media/platform/dwc/Kconfig"
+
 config VIDEO_SH_VOU
 	tristate "SuperH VOU video output driver"
 	depends on MEDIA_CAMERA_SUPPORT
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index c3588d5..4910241 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -79,3 +79,5 @@ obj-$(CONFIG_VIDEO_MEDIATEK_VCODEC)	+= mtk-vcodec/
 obj-$(CONFIG_VIDEO_MEDIATEK_MDP)	+= mtk-mdp/
 
 obj-$(CONFIG_VIDEO_MEDIATEK_JPEG)	+= mtk-jpeg/
+
+obj-y	+= dwc/
diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
new file mode 100644
index 0000000..361d38d
--- /dev/null
+++ b/drivers/media/platform/dwc/Kconfig
@@ -0,0 +1,8 @@
+config VIDEO_DWC_HDMI_PHY_E405
+	tristate "Synopsys Designware HDMI RX PHY e405 driver"
+	depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+	help
+	  Support for Synopsys Designware HDMI RX PHY. Version is e405.
+
+	  To compile this driver as a module, choose M here. The module
+	  will be called dw-hdmi-phy-e405.
diff --git a/drivers/media/platform/dwc/Makefile b/drivers/media/platform/dwc/Makefile
new file mode 100644
index 0000000..fc3b62c
--- /dev/null
+++ b/drivers/media/platform/dwc/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_VIDEO_DWC_HDMI_PHY_E405) += dw-hdmi-phy-e405.o
diff --git a/drivers/media/platform/dwc/dw-hdmi-phy-e405.c b/drivers/media/platform/dwc/dw-hdmi-phy-e405.c
new file mode 100644
index 0000000..b6a5b4b
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-hdmi-phy-e405.c
@@ -0,0 +1,783 @@
+/*
+ * Synopsys Designware HDMI PHY E405 driver
+ *
+ * This Synopsys dw-phy-e405 software and associated documentation
+ * (hereinafter the "Software") is an unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing between
+ * Synopsys and you. The Software IS NOT an item of Licensed Software or a
+ * Licensed Product under any End User Software License Agreement or
+ * Agreement for Licensed Products with Synopsys or any supplement thereto.
+ * Synopsys is a registered trademark of Synopsys, Inc. Other names included
+ * in the SOFTWARE may be the trademarks of their respective owners.
+ *
+ * The contents of this file are dual-licensed; you may select either version 2
+ * of the GNU General Public License (“GPL”) or the MIT license (“MIT”).
+ *
+ * Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
+ *
+ * THIS 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 THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <media/v4l2-subdev.h>
+#include <media/dwc/dw-hdmi-phy-pdata.h>
+#include "dw-hdmi-phy-e405.h"
+
+MODULE_AUTHOR("Jose Abreu <joabreu@synopsys.com>");
+MODULE_DESCRIPTION("Designware HDMI PHY e405 driver");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_ALIAS("platform:" DW_PHY_E405_DRVNAME);
+
+#define PHY_EQ_WAIT_TIME_START			3
+#define PHY_EQ_SLEEP_TIME_CDR			30
+#define PHY_EQ_SLEEP_TIME_ACQ			1
+#define PHY_EQ_BOUNDSPREAD			20
+#define PHY_EQ_MIN_ACQ_STABLE			3
+#define PHY_EQ_ACC_LIMIT			360
+#define PHY_EQ_ACC_MIN_LIMIT			0
+#define PHY_EQ_MAX_SETTING			13
+#define PHY_EQ_SHORT_CABLE_SETTING		4
+#define PHY_EQ_ERROR_CABLE_SETTING		4
+#define PHY_EQ_MIN_SLOPE			50
+#define PHY_EQ_AVG_ACQ				5
+#define PHY_EQ_MINMAX_NTRIES			3
+#define PHY_EQ_EQUALIZED_COUNTER_VAL		512
+#define PHY_EQ_EQUALIZED_COUNTER_VAL_HDMI20	512
+#define PHY_EQ_MINMAX_MAXDIFF			4
+#define PHY_EQ_MINMAX_MAXDIFF_HDMI20		2
+#define PHY_EQ_FATBIT_MASK			0x0000
+#define PHY_EQ_FATBIT_MASK_4K			0x0c03
+#define PHY_EQ_FATBIT_MASK_HDMI20		0x0e03
+
+struct dw_phy_eq_ch {
+	u16 best_long_setting;
+	u8 valid_long_setting;
+	u16 best_short_setting;
+	u8 valid_short_setting;
+	u16 best_setting;
+	u16 acc;
+	u16 acq;
+	u16 last_acq;
+	u16 upper_bound_acq;
+	u16 lower_bound_acq;
+	u16 out_bound_acq;
+	u16 read_acq;
+};
+
+static const struct dw_phy_mpll_config {
+	u16 addr;
+	u16 val;
+} dw_phy_e405_mpll_cfg[] = {
+	{ 0x27, 0x1B94 },
+	{ 0x28, 0x16D2 },
+	{ 0x29, 0x12D9 },
+	{ 0x2A, 0x3249 },
+	{ 0x2B, 0x3653 },
+	{ 0x2C, 0x3436 },
+	{ 0x2D, 0x124D },
+	{ 0x2E, 0x0001 },
+	{ 0xCE, 0x0505 },
+	{ 0xCF, 0x0505 },
+	{ 0xD0, 0x0000 },
+	{ 0x00, 0x0000 },
+};
+
+struct dw_phy_dev {
+	struct device *dev;
+	struct dw_phy_pdata *config;
+	bool phy_enabled;
+	struct v4l2_subdev sd;
+	u16 mpll_status;
+	unsigned char color_depth;
+	bool hdmi2;
+	bool scrambling;
+	struct mutex lock;
+};
+
+static inline struct dw_phy_dev *to_dw_dev(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct dw_phy_dev, sd);
+}
+
+static void phy_write(struct dw_phy_dev *dw_dev, u16 val, u16 addr)
+{
+	void *arg = dw_dev->config->funcs_arg;
+
+	dw_dev->config->funcs->write(arg, val, addr);
+}
+
+static u16 phy_read(struct dw_phy_dev *dw_dev, u16 addr)
+{
+	void *arg = dw_dev->config->funcs_arg;
+
+	return dw_dev->config->funcs->read(arg, addr);
+}
+
+static void phy_reset(struct dw_phy_dev *dw_dev, int enable)
+{
+	void *arg = dw_dev->config->funcs_arg;
+
+	dw_dev->config->funcs->reset(arg, enable);
+}
+
+static void phy_pddq(struct dw_phy_dev *dw_dev, int enable)
+{
+	void *arg = dw_dev->config->funcs_arg;
+
+	dw_dev->config->funcs->pddq(arg, enable);
+}
+
+static void phy_svsmode(struct dw_phy_dev *dw_dev, int enable)
+{
+	void *arg = dw_dev->config->funcs_arg;
+
+	dw_dev->config->funcs->svsmode(arg, enable);
+}
+
+static void phy_zcal_reset(struct dw_phy_dev *dw_dev)
+{
+	void *arg = dw_dev->config->funcs_arg;
+
+	dw_dev->config->funcs->zcal_reset(arg);
+}
+
+static bool phy_zcal_done(struct dw_phy_dev *dw_dev)
+{
+	void *arg = dw_dev->config->funcs_arg;
+
+	return dw_dev->config->funcs->zcal_done(arg);
+}
+
+static bool phy_tmds_valid(struct dw_phy_dev *dw_dev)
+{
+	void *arg = dw_dev->config->funcs_arg;
+
+	return dw_dev->config->funcs->tmds_valid(arg);
+}
+
+static int dw_phy_eq_test(struct dw_phy_dev *dw_dev,
+		u16 *fat_bit_mask, int *min_max_length)
+{
+	u16 main_fsm_status, val;
+	int i;
+
+	for (i = 0; i < PHY_EQ_WAIT_TIME_START; i++) {
+		main_fsm_status = phy_read(dw_dev, PHY_MAINFSM_STATUS1);
+		if (main_fsm_status & 0x100)
+			break;
+		mdelay(PHY_EQ_SLEEP_TIME_CDR);
+	}
+
+	if (i == PHY_EQ_WAIT_TIME_START) {
+		dev_err(dw_dev->dev, "phy start conditions not achieved\n");
+		return -ETIMEDOUT;
+	}
+
+	if (main_fsm_status & 0x400) {
+		dev_err(dw_dev->dev, "invalid pll rate\n");
+		return -EINVAL;
+	}
+
+	val = (phy_read(dw_dev, PHY_CDR_CTRL_CNT) & 0x300) >> 8;
+	if (val == 0x1) {
+		/* HDMI 2.0 */
+		*fat_bit_mask = PHY_EQ_FATBIT_MASK_HDMI20;
+		*min_max_length = PHY_EQ_MINMAX_MAXDIFF_HDMI20;
+		dev_dbg(dw_dev->dev, "[EQUALIZER] using HDMI 2.0 values\n");
+	} else if (!(main_fsm_status & 0x600)) {
+		/* HDMI 1.4 (pll rate = 0) */
+		*fat_bit_mask = PHY_EQ_FATBIT_MASK_4K;
+		*min_max_length = PHY_EQ_MINMAX_MAXDIFF;
+		dev_dbg(dw_dev->dev, "[EQUALIZER] using HDMI 1.4@4k values\n");
+	} else {
+		/* HDMI 1.4 */
+		*fat_bit_mask = PHY_EQ_FATBIT_MASK;
+		*min_max_length = PHY_EQ_MINMAX_MAXDIFF;
+		dev_dbg(dw_dev->dev, "[EQUALIZER] using HDMI 1.4 values\n");
+	}
+
+	return 0;
+}
+
+static void dw_phy_eq_default(struct dw_phy_dev *dw_dev)
+{
+	phy_write(dw_dev, 0x08A8, PHY_CH0_EQ_CTRL1);
+	phy_write(dw_dev, 0x0020, PHY_CH0_EQ_CTRL2);
+	phy_write(dw_dev, 0x08A8, PHY_CH1_EQ_CTRL1);
+	phy_write(dw_dev, 0x0020, PHY_CH1_EQ_CTRL2);
+	phy_write(dw_dev, 0x08A8, PHY_CH2_EQ_CTRL1);
+	phy_write(dw_dev, 0x0020, PHY_CH2_EQ_CTRL2);
+}
+
+static void dw_phy_eq_single(struct dw_phy_dev *dw_dev)
+{
+	phy_write(dw_dev, 0x0211, PHY_CH0_EQ_CTRL1);
+	phy_write(dw_dev, 0x0211, PHY_CH1_EQ_CTRL1);
+	phy_write(dw_dev, 0x0211, PHY_CH2_EQ_CTRL1);
+}
+
+static void dw_phy_eq_equal_setting(struct dw_phy_dev *dw_dev,
+		u16 lock_vector)
+{
+	phy_write(dw_dev, lock_vector, PHY_CH0_EQ_CTRL4);
+	phy_write(dw_dev, 0x0024, PHY_CH0_EQ_CTRL2);
+	phy_write(dw_dev, 0x0026, PHY_CH0_EQ_CTRL2);
+	phy_read(dw_dev, PHY_CH0_EQ_STATUS2);
+	phy_write(dw_dev, lock_vector, PHY_CH1_EQ_CTRL4);
+	phy_write(dw_dev, 0x0024, PHY_CH1_EQ_CTRL2);
+	phy_write(dw_dev, 0x0026, PHY_CH1_EQ_CTRL2);
+	phy_read(dw_dev, PHY_CH1_EQ_STATUS2);
+	phy_write(dw_dev, lock_vector, PHY_CH2_EQ_CTRL4);
+	phy_write(dw_dev, 0x0024, PHY_CH2_EQ_CTRL2);
+	phy_write(dw_dev, 0x0026, PHY_CH2_EQ_CTRL2);
+	phy_read(dw_dev, PHY_CH2_EQ_STATUS2);
+}
+
+static void dw_phy_eq_equal_setting_ch0(struct dw_phy_dev *dw_dev,
+		u16 lock_vector)
+{
+	phy_write(dw_dev, lock_vector, PHY_CH0_EQ_CTRL4);
+	phy_write(dw_dev, 0x0024, PHY_CH0_EQ_CTRL2);
+	phy_write(dw_dev, 0x0026, PHY_CH0_EQ_CTRL2);
+	phy_read(dw_dev, PHY_CH0_EQ_STATUS2);
+}
+
+static void dw_phy_eq_equal_setting_ch1(struct dw_phy_dev *dw_dev,
+		u16 lock_vector)
+{
+	phy_write(dw_dev, lock_vector, PHY_CH1_EQ_CTRL4);
+	phy_write(dw_dev, 0x0024, PHY_CH1_EQ_CTRL2);
+	phy_write(dw_dev, 0x0026, PHY_CH1_EQ_CTRL2);
+	phy_read(dw_dev, PHY_CH1_EQ_STATUS2);
+}
+
+static void dw_phy_eq_equal_setting_ch2(struct dw_phy_dev *dw_dev,
+		u16 lock_vector)
+{
+	phy_write(dw_dev, lock_vector, PHY_CH2_EQ_CTRL4);
+	phy_write(dw_dev, 0x0024, PHY_CH2_EQ_CTRL2);
+	phy_write(dw_dev, 0x0026, PHY_CH2_EQ_CTRL2);
+	phy_read(dw_dev, PHY_CH2_EQ_STATUS2);
+}
+
+static void dw_phy_eq_auto_calib(struct dw_phy_dev *dw_dev)
+{
+	phy_write(dw_dev, 0x1809, PHY_MAINFSM_CTRL);
+	phy_write(dw_dev, 0x1819, PHY_MAINFSM_CTRL);
+	phy_write(dw_dev, 0x1809, PHY_MAINFSM_CTRL);
+}
+
+static void dw_phy_eq_init_vars(struct dw_phy_eq_ch *ch)
+{
+	ch->acc = 0;
+	ch->acq = 0;
+	ch->last_acq = 0;
+	ch->valid_long_setting = 0;
+	ch->valid_short_setting = 0;
+	ch->best_setting = PHY_EQ_SHORT_CABLE_SETTING;
+}
+
+static bool dw_phy_eq_acquire_early_cnt(struct dw_phy_dev *dw_dev,
+		u16 setting, u16 acq, struct dw_phy_eq_ch *ch0,
+		struct dw_phy_eq_ch *ch1, struct dw_phy_eq_ch *ch2)
+{
+	u16 lock_vector = 0x1;
+	int i;
+
+	lock_vector <<= setting;
+	ch0->out_bound_acq = 0;
+	ch1->out_bound_acq = 0;
+	ch2->out_bound_acq = 0;
+	ch0->acq = 0;
+	ch1->acq = 0;
+	ch2->acq = 0;
+
+	dw_phy_eq_equal_setting(dw_dev, lock_vector);
+	dw_phy_eq_auto_calib(dw_dev);
+
+	mdelay(PHY_EQ_SLEEP_TIME_CDR);
+	if (!phy_tmds_valid(dw_dev))
+		dev_dbg(dw_dev->dev, "TMDS is NOT valid\n");
+
+	ch0->read_acq = phy_read(dw_dev, PHY_CH0_EQ_STATUS3);
+	ch1->read_acq = phy_read(dw_dev, PHY_CH1_EQ_STATUS3);
+	ch2->read_acq = phy_read(dw_dev, PHY_CH2_EQ_STATUS3);
+
+	ch0->acq += ch0->read_acq;
+	ch1->acq += ch1->read_acq;
+	ch2->acq += ch2->read_acq;
+
+	ch0->upper_bound_acq = ch0->read_acq + PHY_EQ_BOUNDSPREAD;
+	ch0->lower_bound_acq = ch0->read_acq - PHY_EQ_BOUNDSPREAD;
+	ch1->upper_bound_acq = ch1->read_acq + PHY_EQ_BOUNDSPREAD;
+	ch1->lower_bound_acq = ch1->read_acq - PHY_EQ_BOUNDSPREAD;
+	ch2->upper_bound_acq = ch2->read_acq + PHY_EQ_BOUNDSPREAD;
+	ch2->lower_bound_acq = ch2->read_acq - PHY_EQ_BOUNDSPREAD;
+
+	for (i = 1; i < acq; i++) {
+		dw_phy_eq_auto_calib(dw_dev);
+		mdelay(PHY_EQ_SLEEP_TIME_ACQ);
+
+		if ((ch0->read_acq > ch0->upper_bound_acq) ||
+				(ch0->read_acq < ch0->lower_bound_acq))
+			ch0->out_bound_acq++;
+		if ((ch1->read_acq > ch1->upper_bound_acq) ||
+				(ch1->read_acq < ch1->lower_bound_acq))
+			ch1->out_bound_acq++;
+		if ((ch2->read_acq > ch2->upper_bound_acq) ||
+				(ch2->read_acq < ch1->lower_bound_acq))
+			ch2->out_bound_acq++;
+
+		if (i == PHY_EQ_MIN_ACQ_STABLE) {
+			if ((ch0->out_bound_acq == 0) &&
+					(ch1->out_bound_acq == 0) &&
+					(ch2->out_bound_acq == 0)) {
+				acq = 3;
+				break;
+			}
+		}
+
+		ch0->read_acq = phy_read(dw_dev, PHY_CH0_EQ_STATUS3);
+		ch1->read_acq = phy_read(dw_dev, PHY_CH1_EQ_STATUS3);
+		ch2->read_acq = phy_read(dw_dev, PHY_CH2_EQ_STATUS3);
+
+		ch0->acq += ch0->read_acq;
+		ch1->acq += ch1->read_acq;
+		ch2->acq += ch2->read_acq;
+	}
+
+	ch0->acq = ch0->acq / acq;
+	ch1->acq = ch1->acq / acq;
+	ch2->acq = ch2->acq / acq;
+
+	return true;
+}
+
+static int dw_phy_eq_test_type(u16 setting, bool tmds_valid,
+		struct dw_phy_eq_ch *ch)
+{
+	u16 step_slope = 0;
+
+	if ((ch->acq < ch->last_acq) && tmds_valid) {
+		/* Long cable equalization */
+		ch->acc += ch->last_acq - ch->acq;
+		if ((ch->valid_long_setting == 0) && (ch->acq < 512) &&
+				(ch->acc > 0)) {
+			ch->best_long_setting = setting;
+			ch->valid_long_setting = 1;
+		}
+		step_slope = ch->last_acq - ch->acq;
+	}
+
+	if (tmds_valid && (ch->valid_short_setting == 0)) {
+		/* Short cable equalization */
+		if ((setting < PHY_EQ_SHORT_CABLE_SETTING) &&
+				(ch->acq < PHY_EQ_EQUALIZED_COUNTER_VAL)) {
+			ch->best_short_setting= setting;
+			ch->valid_short_setting = 1;
+		}
+
+		if (setting == PHY_EQ_SHORT_CABLE_SETTING) {
+			ch->best_short_setting = PHY_EQ_SHORT_CABLE_SETTING;
+			ch->valid_short_setting = 1;
+		}
+	}
+
+	if (ch->valid_long_setting && (ch->acc > PHY_EQ_ACC_LIMIT)) {
+		ch->best_setting = ch->best_long_setting;
+		return 1;
+	}
+
+	if ((setting == PHY_EQ_MAX_SETTING) && (ch->acc < PHY_EQ_ACC_LIMIT) &&
+			ch->valid_short_setting) {
+		ch->best_setting = ch->best_short_setting;
+		return 2;
+	}
+
+	if ((setting == PHY_EQ_MAX_SETTING) && tmds_valid &&
+			(ch->acc > PHY_EQ_ACC_LIMIT) &&
+			(step_slope > PHY_EQ_MIN_SLOPE)) {
+		ch->best_setting = PHY_EQ_MAX_SETTING;
+		return 3;
+	}
+
+	if (setting == PHY_EQ_MAX_SETTING) {
+		ch->best_setting = PHY_EQ_ERROR_CABLE_SETTING;
+		return 255;
+	}
+
+	return 0;
+}
+
+static bool dw_phy_eq_setting_finder(struct dw_phy_dev *dw_dev, u16 acq,
+		struct dw_phy_eq_ch *ch0, struct dw_phy_eq_ch *ch1,
+		struct dw_phy_eq_ch *ch2)
+{
+	u16 act = 0;
+	int ret_ch0 = 0, ret_ch1 = 0, ret_ch2 = 0;
+	bool tmds_valid = false;
+
+	dw_phy_eq_init_vars(ch0);
+	dw_phy_eq_init_vars(ch1);
+	dw_phy_eq_init_vars(ch2);
+
+	tmds_valid = dw_phy_eq_acquire_early_cnt(dw_dev, act, acq,
+			ch0, ch1, ch2);
+
+	while ((ret_ch0 == 0) || (ret_ch1 == 0) || (ret_ch2 == 0)) {
+		act++;
+
+		ch0->last_acq = ch0->acq;
+		ch1->last_acq = ch1->acq;
+		ch2->last_acq = ch2->acq;
+
+		tmds_valid = dw_phy_eq_acquire_early_cnt(dw_dev, act, acq,
+				ch0, ch1, ch2);
+
+		if (!ret_ch0)
+			ret_ch0 = dw_phy_eq_test_type(act, tmds_valid, ch0);
+		if (!ret_ch1)
+			ret_ch1 = dw_phy_eq_test_type(act, tmds_valid, ch1);
+		if (!ret_ch2)
+			ret_ch2 = dw_phy_eq_test_type(act, tmds_valid, ch2);
+	}
+
+	if ((ret_ch0 == 255) || (ret_ch1 == 255) || (ret_ch2 == 255))
+		return false;
+	return true;
+}
+
+static bool dw_phy_eq_maxvsmin(u16 ch0_setting, u16 ch1_setting,
+		u16 ch2_setting, u16 min_max_length)
+{
+	u16 min = ch0_setting, max = ch0_setting;
+
+	if (ch1_setting > max)
+		max = ch1_setting;
+	if (ch2_setting > max)
+		max = ch2_setting;
+	if (ch1_setting < min)
+		min = ch1_setting;
+	if (ch2_setting < min)
+		min = ch2_setting;
+
+	if ((max - min) > min_max_length)
+		return false;
+	return true;
+}
+
+static int dw_phy_eq_init(struct dw_phy_dev *dw_dev, u16 acq, bool force)
+{
+	struct dw_phy_pdata *phy = dw_dev->config;
+	struct dw_phy_eq_ch ch0, ch1, ch2;
+	u16 fat_bit_mask, lock_vector = 0x1;
+	int min_max_length, i, ret = 0;
+	u16 mpll_status;
+
+	if (phy->version < 401)
+		return ret;
+	if (!dw_dev->phy_enabled)
+		return -EINVAL;
+
+	mpll_status = phy_read(dw_dev, PHY_CLK_MPLL_STATUS);
+	if (mpll_status == dw_dev->mpll_status && !force)
+		return ret;
+	dw_dev->mpll_status = mpll_status;
+
+	phy_write(dw_dev, 0x00, PHY_MAINFSM_OVR2);
+	phy_write(dw_dev, 0x00, PHY_CH0_EQ_CTRL3);
+	phy_write(dw_dev, 0x00, PHY_CH1_EQ_CTRL3);
+	phy_write(dw_dev, 0x00, PHY_CH2_EQ_CTRL3);
+
+	ret = dw_phy_eq_test(dw_dev, &fat_bit_mask, &min_max_length);
+	if (ret) {
+		if (ret == -EINVAL) /* Means equalizer is not needed */
+			ret = 0;
+
+		/* Do not change values if we don't have clock */
+		if (ret != -ETIMEDOUT) {
+			dw_phy_eq_default(dw_dev);
+			phy_pddq(dw_dev, 1);
+			phy_pddq(dw_dev, 0);
+		}
+	} else {
+		dw_phy_eq_single(dw_dev);
+		dw_phy_eq_equal_setting(dw_dev, 0x0001);
+		phy_write(dw_dev, fat_bit_mask, PHY_CH0_EQ_CTRL6);
+		phy_write(dw_dev, fat_bit_mask, PHY_CH1_EQ_CTRL6);
+		phy_write(dw_dev, fat_bit_mask, PHY_CH2_EQ_CTRL6);
+
+		for (i = 0; i < PHY_EQ_MINMAX_NTRIES; i++) {
+			if (dw_phy_eq_setting_finder(dw_dev, acq,
+						&ch0, &ch1, &ch2)) {
+				if (dw_phy_eq_maxvsmin(ch0.best_setting,
+							ch1.best_setting,
+							ch2.best_setting,
+							min_max_length))
+					break;
+			}
+
+			ch0.best_setting = PHY_EQ_ERROR_CABLE_SETTING;
+			ch1.best_setting = PHY_EQ_ERROR_CABLE_SETTING;
+			ch2.best_setting = PHY_EQ_ERROR_CABLE_SETTING;
+		}
+
+		dev_dbg(dw_dev->dev, "equalizer settings: "
+				"ch0=0x%x, ch1=0x%x, ch1=0x%x\n",
+				ch0.best_setting, ch1.best_setting,
+				ch2.best_setting);
+
+		if (i == PHY_EQ_MINMAX_NTRIES)
+			ret = -EINVAL;
+
+		lock_vector = 0x1;
+		lock_vector <<= ch0.best_setting;
+		dw_phy_eq_equal_setting_ch0(dw_dev, lock_vector);
+
+		lock_vector = 0x1;
+		lock_vector <<= ch1.best_setting;
+		dw_phy_eq_equal_setting_ch1(dw_dev, lock_vector);
+
+		lock_vector = 0x1;
+		lock_vector <<= ch2.best_setting;
+		dw_phy_eq_equal_setting_ch2(dw_dev, lock_vector);
+
+		phy_pddq(dw_dev, 1);
+		phy_pddq(dw_dev, 0);
+	}
+
+	return ret;
+}
+
+static int dw_phy_config(struct dw_phy_dev *dw_dev, unsigned char color_depth,
+		bool hdmi2, bool scrambling)
+{
+	struct device *dev = dw_dev->dev;
+	struct dw_phy_pdata *phy = dw_dev->config;
+	const struct dw_phy_mpll_config *mpll_cfg = dw_phy_e405_mpll_cfg;
+	bool zcal_done;
+	u16 val, res_idx;
+	int timeout = 100;
+
+	dev_dbg(dev, "%s: color_depth=%d, hdmi2=%d, scrambling=%d\n",
+			__func__, color_depth, hdmi2, scrambling);
+
+	switch (color_depth) {
+	case 8:
+		res_idx = 0x0;
+		break;
+	case 10:
+		res_idx = 0x1;
+		break;
+	case 12:
+		res_idx = 0x2;
+		break;
+	case 16:
+		res_idx = 0x3;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	phy_reset(dw_dev, 1);
+	phy_pddq(dw_dev, 1);
+	phy_svsmode(dw_dev, 1);
+
+	phy_zcal_reset(dw_dev);
+	do {
+		udelay(1000);
+		zcal_done = phy_zcal_done(dw_dev);
+	} while (!zcal_done && timeout--);
+
+	if (!zcal_done) {
+		dev_err(dw_dev->dev, "Zcal calibration failed\n");
+		return -ETIMEDOUT;
+	}
+
+	phy_reset(dw_dev, 0);
+
+	/* CMU */
+	val = (0x08 << 10) | (0x01 << 9);
+	val |= (phy->cfg_clk * 4) & GENMASK(8, 0);
+	phy_write(dw_dev, val, PHY_CMU_CONFIG);
+
+	/* Color Depth and enable fast switching */
+	val = phy_read(dw_dev, PHY_SYSTEM_CONFIG);
+	val = (val & ~0x60) | (res_idx << 5) | BIT(11);
+	phy_write(dw_dev, val, PHY_SYSTEM_CONFIG);
+
+	/* MPLL */
+	for (; mpll_cfg->addr != 0x0; mpll_cfg++)
+		phy_write(dw_dev, mpll_cfg->val, mpll_cfg->addr);
+
+	/* Operation for data rates between 3.4Gbps and 6Gbps */
+	val = phy_read(dw_dev, PHY_CDR_CTRL_CNT);
+	if (hdmi2)
+		val |= BIT(8);
+	else
+		val &= ~BIT(8);
+	phy_write(dw_dev, val, PHY_CDR_CTRL_CNT);
+
+	/* Scrambling */
+	val = phy_read(dw_dev, PHY_OVL_PROT_CTRL);
+	if (scrambling)
+		val |= GENMASK(7,6);
+	else
+		val &= ~GENMASK(7,6);
+	phy_write(dw_dev, val, PHY_OVL_PROT_CTRL);
+
+	/* Enable phy */
+	phy_pddq(dw_dev, 0);
+
+	dw_dev->color_depth = color_depth;
+	dw_dev->hdmi2 = hdmi2;
+	dw_dev->scrambling = scrambling;
+	return 0;
+}
+
+static int dw_phy_enable(struct dw_phy_dev *dw_dev, unsigned char color_depth,
+		bool hdmi2, bool scrambling)
+{
+	int ret;
+
+	if (dw_dev->phy_enabled &&
+	    dw_dev->color_depth == color_depth &&
+	    dw_dev->hdmi2 == hdmi2 &&
+	    dw_dev->scrambling == scrambling)
+		return 0;
+
+	ret = dw_phy_config(dw_dev, color_depth, hdmi2, scrambling);
+	if (ret)
+		return ret;
+
+	phy_reset(dw_dev, 0);
+	phy_pddq(dw_dev, 0);
+	dw_dev->phy_enabled = true;
+	return 0;
+}
+
+static void dw_phy_disable(struct dw_phy_dev *dw_dev)
+{
+	if (!dw_dev->phy_enabled)
+		return;
+
+	phy_reset(dw_dev, 1);
+	phy_pddq(dw_dev, 1);
+	phy_svsmode(dw_dev, 0);
+	dw_dev->mpll_status = 0xFFFF;
+	dw_dev->phy_enabled = false;
+}
+
+static long dw_phy_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
+{
+	struct dw_phy_dev *dw_dev = to_dw_dev(sd);
+	struct dw_phy_config_command *ccmd;
+	struct dw_phy_eq_command *ecmd;
+	int ret = 0;
+
+	dev_dbg(dw_dev->dev, "%s: cmd=%d\n", __func__, cmd);
+
+	mutex_lock(&dw_dev->lock);
+	switch (cmd) {
+	case DW_PHY_IOCTL_EQ_INIT:
+		ecmd = (struct dw_phy_eq_command *)arg; 
+		ecmd->result = dw_phy_eq_init(dw_dev, ecmd->nacq, ecmd->force);
+		break;
+	case DW_PHY_IOCTL_CONFIG:
+		ccmd = (struct dw_phy_config_command *)arg;
+		ccmd->result = dw_phy_enable(dw_dev, ccmd->color_depth,
+				ccmd->hdmi2, ccmd->scrambling);
+		break;
+	default:
+		ret = -ENOIOCTLCMD;
+		break;
+	}
+	mutex_unlock(&dw_dev->lock);
+
+	return ret;
+}
+
+static int dw_phy_s_power(struct v4l2_subdev *sd, int on)
+{
+	struct dw_phy_dev *dw_dev = to_dw_dev(sd);
+
+	dev_dbg(dw_dev->dev, "%s: on=%d\n", __func__, on);
+
+	mutex_lock(&dw_dev->lock);
+	if (!on)
+		dw_phy_disable(dw_dev);
+	mutex_unlock(&dw_dev->lock);
+	return 0;
+}
+
+static const struct v4l2_subdev_core_ops dw_phy_core_ops = {
+	.ioctl = dw_phy_ioctl,
+	.s_power = dw_phy_s_power,
+};
+
+static const struct v4l2_subdev_ops dw_phy_sd_ops = {
+	.core = &dw_phy_core_ops,
+};
+
+static int dw_phy_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct dw_phy_dev *dw_dev;
+	struct dw_phy_pdata *pdata = pdev->dev.platform_data;
+	struct v4l2_subdev *sd;
+
+	dev_dbg(dev, "probe start\n");
+
+	/* Resource allocation */
+	dw_dev = devm_kzalloc(dev, sizeof(*dw_dev), GFP_KERNEL);
+	if (!dw_dev)
+		return -ENOMEM;
+
+	/* Resource initialization */
+	if (!pdata)
+		return -EINVAL;
+
+	dw_dev->dev = dev;
+	dw_dev->config = pdata;
+	mutex_init(&dw_dev->lock);
+
+	/* V4L2 initialization */
+	sd = &dw_dev->sd;
+	v4l2_subdev_init(sd, &dw_phy_sd_ops);
+	strlcpy(sd->name, dev_name(dev), sizeof(sd->name));
+
+	/* Force phy disabling */
+	dw_dev->phy_enabled = true;
+	dw_phy_disable(dw_dev);
+
+	/* All done */
+	dev_set_drvdata(dev, sd);
+	dev_info(dev, "driver probed\n");
+	return 0;
+}
+
+static int dw_phy_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+
+	dev_info(dev, "driver removed\n");
+	return 0;
+}
+
+static struct platform_driver dw_phy_e405_driver = {
+	.probe = dw_phy_probe,
+	.remove = dw_phy_remove,
+	.driver = {
+		.name = DW_PHY_E405_DRVNAME,
+	}
+};
+module_platform_driver(dw_phy_e405_driver);
diff --git a/drivers/media/platform/dwc/dw-hdmi-phy-e405.h b/drivers/media/platform/dwc/dw-hdmi-phy-e405.h
new file mode 100644
index 0000000..3e6c8da
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-hdmi-phy-e405.h
@@ -0,0 +1,63 @@
+/*
+ * Synopsys Designware HDMI PHY E405 driver
+ *
+ * This Synopsys dw-phy-e405 software and associated documentation
+ * (hereinafter the "Software") is an unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing between
+ * Synopsys and you. The Software IS NOT an item of Licensed Software or a
+ * Licensed Product under any End User Software License Agreement or
+ * Agreement for Licensed Products with Synopsys or any supplement thereto.
+ * Synopsys is a registered trademark of Synopsys, Inc. Other names included
+ * in the SOFTWARE may be the trademarks of their respective owners.
+ *
+ * The contents of this file are dual-licensed; you may select either version 2
+ * of the GNU General Public License (“GPL”) or the MIT license (“MIT”).
+ *
+ * Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
+ *
+ * THIS 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 THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __DW_HDMI_PHY_E405_H__
+#define __DW_HDMI_PHY_E405_H__
+
+#define PHY_CMU_CONFIG				0x02
+#define PHY_SYSTEM_CONFIG			0x03
+#define PHY_MAINFSM_CTRL			0x05
+#define PHY_MAINFSM_OVR2			0x08
+#define PHY_MAINFSM_STATUS1			0x09
+#define PHY_OVL_PROT_CTRL			0x0D
+#define PHY_CDR_CTRL_CNT			0x0E
+#define PHY_CLK_MPLL_STATUS			0x2F
+#define PHY_CH0_EQ_CTRL1			0x32
+#define PHY_CH0_EQ_CTRL2			0x33
+#define PHY_CH0_EQ_STATUS			0x34
+#define PHY_CH0_EQ_CTRL3			0x3E
+#define PHY_CH0_EQ_CTRL4			0x3F
+#define PHY_CH0_EQ_STATUS2			0x40
+#define PHY_CH0_EQ_STATUS3			0x42
+#define PHY_CH0_EQ_CTRL6			0x43
+#define PHY_CH1_EQ_CTRL1			0x52
+#define PHY_CH1_EQ_CTRL2			0x53
+#define PHY_CH1_EQ_STATUS			0x54
+#define PHY_CH1_EQ_CTRL3			0x5E
+#define PHY_CH1_EQ_CTRL4			0x5F
+#define PHY_CH1_EQ_STATUS2			0x60
+#define PHY_CH1_EQ_STATUS3			0x62
+#define PHY_CH1_EQ_CTRL6			0x63
+#define PHY_CH2_EQ_CTRL1			0x72
+#define PHY_CH2_EQ_CTRL2			0x73
+#define PHY_CH2_EQ_STATUS			0x74
+#define PHY_CH2_EQ_CTRL3			0x7E
+#define PHY_CH2_EQ_CTRL4			0x7F
+#define PHY_CH2_EQ_STATUS2			0x80
+#define PHY_CH2_EQ_STATUS3			0x82
+#define PHY_CH2_EQ_CTRL6			0x83
+
+#endif /* __DW_HDMI_PHY_E405_H__ */
diff --git a/include/media/dwc/dw-hdmi-phy-pdata.h b/include/media/dwc/dw-hdmi-phy-pdata.h
new file mode 100644
index 0000000..1be1cc3
--- /dev/null
+++ b/include/media/dwc/dw-hdmi-phy-pdata.h
@@ -0,0 +1,131 @@
+/*
+ * Synopsys Designware HDMI PHY platform data
+ *
+ * This Synopsys dw-hdmi-phy software and associated documentation
+ * (hereinafter the "Software") is an unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing between
+ * Synopsys and you. The Software IS NOT an item of Licensed Software or a
+ * Licensed Product under any End User Software License Agreement or
+ * Agreement for Licensed Products with Synopsys or any supplement thereto.
+ * Synopsys is a registered trademark of Synopsys, Inc. Other names included
+ * in the SOFTWARE may be the trademarks of their respective owners.
+ *
+ * The contents of this file are dual-licensed; you may select either version 2
+ * of the GNU General Public License (“GPL”) or the MIT license (“MIT”).
+ *
+ * Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
+ *
+ * THIS 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 THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __DW_HDMI_PHY_PDATA_H__
+#define __DW_HDMI_PHY_PDATA_H__
+
+#define DW_PHY_E405_DRVNAME	"dw-hdmi-phy-e405"
+
+#define DW_PHY_IOCTL_EQ_INIT		_IOW('R',1,int)
+#define DW_PHY_IOCTL_CONFIG		_IOW('R',2,int)
+
+/**
+ * struct dw_phy_eq_command - Command arguments for HDMI PHY equalizer
+ * 	algorithm.
+ *
+ * @nacq: Number of acquisitions to get.
+ *
+ * @force: Force equalizer algorithm even if the MPLL status didn't change
+ * from previous run.
+ *
+ * @result: Result from the equalizer algorithm. Shall be zero if equalizer
+ * ran with success or didn't run because the video mode does not need
+ * equalizer (for low clock values).
+ */
+struct dw_phy_eq_command {
+	u16 nacq;
+	bool force;
+	int result;
+};
+
+/**
+ * struct dw_phy_config_command - Command arguments for HDMI PHY configuration
+ * function.
+ *
+ * @color_depth: Color depth of the video mode being received.
+ *
+ * @hdmi2: Must be set to true if the video mode being received has a data
+ * rate above 3.4Gbps (generally HDMI 2.0 video modes, like 4k@60Hz).
+ *
+ * @scrambling: Must be set to true if scrambling is currently enabled.
+ * Scrambling is enabled by source when SCDC scrambling_en field is set to 1.
+ */
+struct dw_phy_config_command {
+	unsigned char color_depth;
+	bool hdmi2;
+	bool scrambling;
+	int result;
+};
+
+/**
+ * struct dw_phy_funcs - Set of callbacks used to communicate between phy
+ * 	and hdmi controller. Controller must correctly fill these callbacks
+ * 	before probbing the phy driver.
+ *
+ * @write: write callback. Write value 'val' into address 'addr' of phy.
+ *
+ * @read: read callback. Read address 'addr' and return the value.
+ *
+ * @reset: reset callback. Activate phy reset. Active high.
+ *
+ * @pddq: pddq callback. Activate phy configuration mode. Active high.
+ *
+ * @svsmode: svsmode callback. Activate phy retention mode. Active low.
+ *
+ * @zcal_reset: zcal reset callback. Restart the impedance calibration
+ * procedure. Active high. This is only used in prototyping and not in real
+ * ASIC. Callback shall be empty (but non NULL) in ASIC cases.
+ *
+ * @zcal_done: zcal done callback. Return the current status of impedance
+ * calibration procedure. This is only used in prototyping and not in real
+ * ASIC. Shall return always true in ASIC cases.
+ *
+ * @tmds_valid: TMDS valid callback. Return the current status of TMDS signal
+ * that comes from phy and feeds controller. This is read from a controller
+ * register.
+ */
+struct dw_phy_funcs {
+	void (*write) (void *arg, u16 val, u16 addr);
+	u16 (*read) (void *arg, u16 addr);
+	void (*reset) (void *arg, int enable);
+	void (*pddq) (void *arg, int enable);
+	void (*svsmode) (void *arg, int enable);
+	void (*zcal_reset) (void *arg);
+	bool (*zcal_done) (void *arg);
+	bool (*tmds_valid) (void *arg);
+};
+
+/**
+ * struct dw_phy_pdata - Platform data definition for Synopsys HDMI PHY.
+ *
+ * @version: phy version number.
+ *
+ * @cfg_clk: value of cfg_clk that is fed to the phy.
+ *
+ * @funcs: set of callbacks that must be correctly filled and supplied to phy.
+ * See @dw_phy_funcs.
+ *
+ * @funcs_arg: parameter that is supplied to callbacks along with the function
+ * parameters.
+ */
+struct dw_phy_pdata {
+	unsigned int version;
+	unsigned int cfg_clk;
+	const struct dw_phy_funcs *funcs;
+	void *funcs_arg;
+};
+
+#endif /* __DW_HDMI_PHY_PDATA_H__ */
-- 
1.9.1

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

* [PATCH v2 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver
  2017-06-16 10:07 [PATCH v2 0/4] Synopsys Designware HDMI Video Capture Controller + PHY Jose Abreu
  2017-06-16 10:07 ` [PATCH v2 1/4] [media] platform: Add Synopsys Designware HDMI RX PHY e405 Driver Jose Abreu
@ 2017-06-16 10:07 ` Jose Abreu
  2017-06-16 12:38   ` Hans Verkuil
  2017-06-16 10:07 ` [PATCH v2 3/4] MAINTAINERS: Add entry for Synopsys Designware HDMI drivers Jose Abreu
  2017-06-16 10:07 ` [PATCH v2 4/4] dt-bindings: media: Document Synopsys Designware HDMI RX Jose Abreu
  3 siblings, 1 reply; 10+ messages in thread
From: Jose Abreu @ 2017-06-16 10:07 UTC (permalink / raw)
  To: linux-media, linux-kernel, devicetree
  Cc: Jose Abreu, Carlos Palminha, Mauro Carvalho Chehab, Hans Verkuil

This is an initial submission for the Synopsys Designware HDMI RX
Controller Driver. This driver interacts with a phy driver so that
a communication between them is created and a video pipeline is
configured.

The controller + phy pipeline can then be integrated into a fully
featured system that can be able to receive video up to 4k@60Hz
with deep color 48bit RGB, depending on the platform. Although,
this initial version does not yet handle deep color modes.

This driver was implemented as a standard V4L2 subdevice and its
main features are:
	- Internal state machine that reconfigures phy until the
	video is not stable
	- JTAG communication with phy
	- Inter-module communication with phy driver
	- Debug write/read ioctls

Some notes:
	- RX sense controller (cable connection/disconnection) must
	be handled by the platform wrapper as this is not integrated
	into the controller RTL
	- The same goes for EDID ROM's
	- ZCAL calibration is needed only in FPGA platforms, in ASIC
	this is not needed
	- The state machine is not an ideal solution as it creates a
	kthread but it is needed because some sources might not be
	very stable at sending the video (i.e. we must react
	accordingly).

Signed-off-by: Jose Abreu <joabreu@synopsys.com>
Cc: Carlos Palminha <palminha@synopsys.com>
Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
Cc: Hans Verkuil <hans.verkuil@cisco.com>

Changes from v1:
	- Added support for CEC
	- Correct typo errors
	- Correctly detect interlaced video modes
	- Correct VIC parsing
Changes from RFC:
	- Added support for HDCP 1.4
	- Fixup HDMI_VIC not being parsed (Hans)
	- Send source change signal when powering off (Hans)
	- Added a "wait stable delay"
	- Detect interlaced video modes (Hans)
	- Restrain g/s_register from reading/writing to HDCP regs (Hans)

Signed-off-by: Jose Abreu <joabreu@synopsys.com>
---
 drivers/media/platform/dwc/Kconfig      |   10 +
 drivers/media/platform/dwc/Makefile     |    1 +
 drivers/media/platform/dwc/dw-hdmi-rx.c | 1764 +++++++++++++++++++++++++++++++
 drivers/media/platform/dwc/dw-hdmi-rx.h |  435 ++++++++
 include/media/dwc/dw-hdmi-rx-pdata.h    |   63 ++
 5 files changed, 2273 insertions(+)
 create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.c
 create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.h
 create mode 100644 include/media/dwc/dw-hdmi-rx-pdata.h

diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
index 361d38d..aba18af 100644
--- a/drivers/media/platform/dwc/Kconfig
+++ b/drivers/media/platform/dwc/Kconfig
@@ -6,3 +6,13 @@ config VIDEO_DWC_HDMI_PHY_E405
 
 	  To compile this driver as a module, choose M here. The module
 	  will be called dw-hdmi-phy-e405.
+
+config VIDEO_DWC_HDMI_RX
+	tristate "Synopsys Designware HDMI Receiver driver"
+	depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+	select CEC_CORE
+	help
+	  Support for Synopsys Designware HDMI RX controller.
+
+	  To compile this driver as a module, choose M here. The module
+	  will be called dw-hdmi-rx.
diff --git a/drivers/media/platform/dwc/Makefile b/drivers/media/platform/dwc/Makefile
index fc3b62c..cd04ca9 100644
--- a/drivers/media/platform/dwc/Makefile
+++ b/drivers/media/platform/dwc/Makefile
@@ -1 +1,2 @@
 obj-$(CONFIG_VIDEO_DWC_HDMI_PHY_E405) += dw-hdmi-phy-e405.o
+obj-$(CONFIG_VIDEO_DWC_HDMI_RX) += dw-hdmi-rx.o
diff --git a/drivers/media/platform/dwc/dw-hdmi-rx.c b/drivers/media/platform/dwc/dw-hdmi-rx.c
new file mode 100644
index 0000000..3b3c096
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-hdmi-rx.c
@@ -0,0 +1,1764 @@
+/*
+ * Synopsys Designware HDMI Receiver controller driver
+ *
+ * This Synopsys dw-hdmi-rx software and associated documentation
+ * (hereinafter the "Software") is an unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing between
+ * Synopsys and you. The Software IS NOT an item of Licensed Software or a
+ * Licensed Product under any End User Software License Agreement or
+ * Agreement for Licensed Products with Synopsys or any supplement thereto.
+ * Synopsys is a registered trademark of Synopsys, Inc. Other names included
+ * in the SOFTWARE may be the trademarks of their respective owners.
+ *
+ * The contents of this file are dual-licensed; you may select either version 2
+ * of the GNU General Public License (“GPL”) or the MIT license (“MIT”).
+ *
+ * Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
+ *
+ * THIS 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 THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/v4l2-dv-timings.h>
+#include <linux/workqueue.h>
+#include <media/cec.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-dv-timings.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-subdev.h>
+#include <media/dwc/dw-hdmi-phy-pdata.h>
+#include <media/dwc/dw-hdmi-rx-pdata.h>
+#include "dw-hdmi-rx.h"
+
+#define HDMI_DEFAULT_TIMING		V4L2_DV_BT_CEA_640X480P59_94
+#define HDMI_CEC_MAX_LOG_ADDRS		CEC_MAX_LOG_ADDRS
+
+MODULE_AUTHOR("Carlos Palminha <palminha@synopsys.com>");
+MODULE_AUTHOR("Jose Abreu <joabreu@synopsys.com>");
+MODULE_DESCRIPTION("Designware HDMI Receiver driver");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_ALIAS("platform:" DW_HDMI_RX_DRVNAME);
+
+static const struct v4l2_dv_timings_cap dw_hdmi_timings_cap = {
+	.type = V4L2_DV_BT_656_1120,
+	.reserved = { 0 },
+	V4L2_INIT_BT_TIMINGS(
+			640, 4096,		/* min/max width */
+			480, 4455,		/* min/max height */
+			20000000, 600000000,	/* min/max pixelclock */
+			V4L2_DV_BT_STD_CEA861,	/* standards */
+			/* capabilities */
+			V4L2_DV_BT_CAP_PROGRESSIVE
+	)
+};
+
+static const struct v4l2_event dw_hdmi_event_fmt = {
+	.type = V4L2_EVENT_SOURCE_CHANGE,
+	.u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
+};
+
+enum dw_hdmi_state {
+	HDMI_STATE_NO_INIT = 0,
+	HDMI_STATE_POWER_OFF,
+	HDMI_STATE_PHY_CONFIG,
+	HDMI_STATE_CONTROLLER_CONFIG,
+	HDMI_STATE_EQUALIZER,
+	HDMI_STATE_VIDEO_UNSTABLE,
+	HDMI_STATE_POWER_ON,
+};
+
+struct dw_hdmi_dev {
+	struct dw_hdmi_rx_pdata *config;
+	struct workqueue_struct *wq;
+	enum dw_hdmi_state state;
+	bool registered;
+	bool pending_config;
+	bool force_off;
+	spinlock_t lock;
+	void __iomem *regs;
+	struct device_node *of_node;
+	struct v4l2_subdev sd;
+	struct v4l2_dv_timings timings;
+	struct v4l2_device v4l2_dev;
+	struct dw_phy_pdata phy_config;
+	struct platform_device *phy_pdev;
+	struct v4l2_subdev *phy_sd;
+	bool phy_eq_force;
+	u8 phy_jtag_addr;
+	const char *phy_drv;
+	struct device *dev;
+	u32 mbus_code;
+	unsigned int selected_input;
+	unsigned int configured_input;
+	u32 cfg_clk;
+	struct cec_adapter *cec_adap;
+	u8 cec_addr[HDMI_CEC_MAX_LOG_ADDRS];
+	u32 cec_valid_addrs;
+	bool cec_enabled_adap;
+};
+
+static const char *get_state_name(enum dw_hdmi_state state)
+{
+	switch (state) {
+	case HDMI_STATE_NO_INIT:
+		return "NO_INIT";
+	case HDMI_STATE_POWER_OFF:
+		return "POWER_OFF";
+	case HDMI_STATE_PHY_CONFIG:
+		return "PHY_CONFIG";
+	case HDMI_STATE_CONTROLLER_CONFIG:
+		return "CONTROLLER_CONFIG";
+	case HDMI_STATE_EQUALIZER:
+		return "EQUALIZER";
+	case HDMI_STATE_VIDEO_UNSTABLE:
+		return "VIDEO_UNSTABLE";
+	case HDMI_STATE_POWER_ON:
+		return "POWER_ON";
+	default:
+		return "UNKNOWN";
+	}
+}
+
+static inline void dw_hdmi_set_state(struct dw_hdmi_dev *dw_dev,
+		enum dw_hdmi_state new_state)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dw_dev->lock, flags);
+	dev_dbg(dw_dev->dev, "old_state=%s, new_state=%s\n",
+			get_state_name(dw_dev->state),
+			get_state_name(new_state));
+	dw_dev->state = new_state;
+	spin_unlock_irqrestore(&dw_dev->lock, flags);
+}
+
+static inline struct dw_hdmi_dev *to_dw_dev(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct dw_hdmi_dev, sd);
+}
+
+static inline void hdmi_writel(struct dw_hdmi_dev *dw_dev, u32 val, int reg)
+{
+	writel(val, dw_dev->regs + reg);
+}
+
+static inline u32 hdmi_readl(struct dw_hdmi_dev *dw_dev, int reg)
+{
+	return readl(dw_dev->regs + reg);
+}
+
+static void hdmi_modl(struct dw_hdmi_dev *dw_dev, u32 data, u32 mask, int reg)
+{
+	u32 val = hdmi_readl(dw_dev, reg) & ~mask;
+
+	val |= data & mask;
+	hdmi_writel(dw_dev, val, reg);
+}
+
+static void hdmi_mask_writel(struct dw_hdmi_dev *dw_dev, u32 data, int reg,
+		u32 shift, u32 mask)
+{
+	hdmi_modl(dw_dev, data << shift, mask, reg);
+}
+
+static u32 hdmi_mask_readl(struct dw_hdmi_dev *dw_dev, int reg, u32 shift,
+		u32 mask)
+{
+	return (hdmi_readl(dw_dev, reg) & mask) >> shift;
+}
+
+static bool dw_hdmi_5v_status(struct dw_hdmi_dev *dw_dev, int input)
+{
+	void __iomem *arg = dw_dev->config->dw_5v_arg;
+
+	if (dw_dev->config->dw_5v_status)
+		return dw_dev->config->dw_5v_status(arg, input);
+	return false;
+}
+
+static void dw_hdmi_5v_clear(struct dw_hdmi_dev *dw_dev)
+{
+	void __iomem *arg = dw_dev->config->dw_5v_arg;
+
+	if (dw_dev->config->dw_5v_clear)
+		dw_dev->config->dw_5v_clear(arg);
+}
+
+static inline bool is_off(struct dw_hdmi_dev *dw_dev)
+{
+	return dw_dev->state <= HDMI_STATE_POWER_OFF;
+}
+
+static bool is_hdcp14_key_write_allowed(struct dw_hdmi_dev *dw_dev)
+{
+	return hdmi_readl(dw_dev, HDMI_HDCP_STS) & HDMI_HDCP_STS_KEY_WR_OK;
+}
+
+static bool has_signal(struct dw_hdmi_dev *dw_dev, unsigned int input)
+{
+	return dw_hdmi_5v_status(dw_dev, input);
+}
+
+#define HDMI_JTAG_TAP_ADDR_CMD		0
+#define HDMI_JTAG_TAP_WRITE_CMD		1
+#define HDMI_JTAG_TAP_READ_CMD		3
+
+static void hdmi_phy_jtag_send_pulse(struct dw_hdmi_dev *dw_dev, u8 tms, u8 tdi)
+{
+	u8 val;
+
+	val = tms ? HDMI_PHY_JTAG_TAP_IN_TMS : 0;
+	val |= tdi ? HDMI_PHY_JTAG_TAP_IN_TDI : 0;
+
+	hdmi_writel(dw_dev, 0, HDMI_PHY_JTAG_TAP_TCLK);
+	hdmi_writel(dw_dev, val, HDMI_PHY_JTAG_TAP_IN);
+	hdmi_writel(dw_dev, 1, HDMI_PHY_JTAG_TAP_TCLK);
+}
+
+static void hdmi_phy_jtag_shift_dr(struct dw_hdmi_dev *dw_dev)
+{
+	hdmi_phy_jtag_send_pulse(dw_dev, 1, 0);
+	hdmi_phy_jtag_send_pulse(dw_dev, 0, 0);
+	hdmi_phy_jtag_send_pulse(dw_dev, 0, 0);
+}
+
+static void hdmi_phy_jtag_shift_ir(struct dw_hdmi_dev *dw_dev)
+{
+	hdmi_phy_jtag_send_pulse(dw_dev, 1, 0);
+	hdmi_phy_jtag_send_pulse(dw_dev, 1, 0);
+	hdmi_phy_jtag_send_pulse(dw_dev, 0, 0);
+	hdmi_phy_jtag_send_pulse(dw_dev, 0, 0);
+}
+
+static u16 hdmi_phy_jtag_send(struct dw_hdmi_dev *dw_dev, u8 cmd, u16 val)
+{
+	u32 in = (cmd << 16) | val;
+	u16 out = 0;
+	int i;
+
+	for (i = 0; i < 16; i++) {
+		hdmi_phy_jtag_send_pulse(dw_dev, 0, in & 0x1);
+		out |= (hdmi_readl(dw_dev, HDMI_PHY_JTAG_TAP_OUT) & 0x1) << i;
+		in >>= 1;
+	}
+
+	hdmi_phy_jtag_send_pulse(dw_dev, 0, in & 0x1);
+	in >>= 1;
+	hdmi_phy_jtag_send_pulse(dw_dev, 1, in & 0x1);
+
+	out |= (hdmi_readl(dw_dev, HDMI_PHY_JTAG_TAP_OUT) & 0x1) << ++i;
+	return out;
+}
+
+static void hdmi_phy_jtag_idle(struct dw_hdmi_dev *dw_dev)
+{
+	hdmi_phy_jtag_send_pulse(dw_dev, 1, 0);
+	hdmi_phy_jtag_send_pulse(dw_dev, 0, 0);
+}
+
+static void hdmi_phy_jtag_init(struct dw_hdmi_dev *dw_dev, u8 addr)
+{
+	int i;
+
+	hdmi_writel(dw_dev, addr, HDMI_PHY_JTAG_ADDR);
+	/* reset */
+	hdmi_writel(dw_dev, 0x10, HDMI_PHY_JTAG_TAP_IN);
+	hdmi_writel(dw_dev, 0x0, HDMI_PHY_JTAG_CONF);
+	hdmi_writel(dw_dev, 0x1, HDMI_PHY_JTAG_CONF);
+	hdmi_phy_jtag_send_pulse(dw_dev, 0, 0);
+	/* soft reset */
+	for (i = 0; i < 5; i++)
+		hdmi_phy_jtag_send_pulse(dw_dev, 1, 0);
+	hdmi_phy_jtag_send_pulse(dw_dev, 0, 0);
+	/* set slave address */
+	hdmi_phy_jtag_shift_ir(dw_dev);
+	for (i = 0; i < 7; i++) {
+		hdmi_phy_jtag_send_pulse(dw_dev, 0, addr & 0x1);
+		addr >>= 1;
+	}
+	hdmi_phy_jtag_send_pulse(dw_dev, 1, addr & 0x1);
+	hdmi_phy_jtag_idle(dw_dev);
+}
+
+static void hdmi_phy_jtag_write(struct dw_hdmi_dev *dw_dev, u16 val, u16 addr)
+{
+	hdmi_phy_jtag_shift_dr(dw_dev);
+	hdmi_phy_jtag_send(dw_dev, HDMI_JTAG_TAP_ADDR_CMD, addr << 8);
+	hdmi_phy_jtag_idle(dw_dev);
+	hdmi_phy_jtag_shift_dr(dw_dev);
+	hdmi_phy_jtag_send(dw_dev, HDMI_JTAG_TAP_WRITE_CMD, val);
+	hdmi_phy_jtag_idle(dw_dev);
+}
+
+static u16 hdmi_phy_jtag_read(struct dw_hdmi_dev *dw_dev, u16 addr)
+{
+	u16 val;
+
+	hdmi_phy_jtag_shift_dr(dw_dev);
+	hdmi_phy_jtag_send(dw_dev, HDMI_JTAG_TAP_ADDR_CMD, addr << 8);
+	hdmi_phy_jtag_idle(dw_dev);
+	hdmi_phy_jtag_shift_dr(dw_dev);
+	val = hdmi_phy_jtag_send(dw_dev, HDMI_JTAG_TAP_READ_CMD, 0xFFFF);
+	hdmi_phy_jtag_idle(dw_dev);
+	
+	return val;
+}
+
+static void dw_hdmi_phy_write(void *arg, u16 val, u16 addr)
+{
+	struct dw_hdmi_dev *dw_dev = arg;
+	u16 rval;
+
+	hdmi_phy_jtag_init(dw_dev, dw_dev->phy_jtag_addr);
+	hdmi_phy_jtag_write(dw_dev, val, addr);
+	rval = hdmi_phy_jtag_read(dw_dev, addr);
+
+	if (rval != val) {
+		dev_err(dw_dev->dev,
+			"JTAG read-back failed: expected=0x%x, got=0x%x\n",
+			val, rval);
+	}
+}
+
+static u16 dw_hdmi_phy_read(void *arg, u16 addr)
+{
+	struct dw_hdmi_dev *dw_dev = arg;
+
+	hdmi_phy_jtag_init(dw_dev, dw_dev->phy_jtag_addr);
+	return hdmi_phy_jtag_read(dw_dev, addr);
+}
+
+static void dw_hdmi_phy_reset(void *arg, int enable)
+{
+	struct dw_hdmi_dev *dw_dev = arg;
+
+	hdmi_mask_writel(dw_dev, enable, HDMI_PHY_CTRL,
+			HDMI_PHY_CTRL_RESET_OFFSET,
+			HDMI_PHY_CTRL_RESET_MASK);
+}
+
+static void dw_hdmi_phy_pddq(void *arg, int enable)
+{
+	struct dw_hdmi_dev *dw_dev = arg;
+
+	hdmi_mask_writel(dw_dev, enable, HDMI_PHY_CTRL,
+			HDMI_PHY_CTRL_PDDQ_OFFSET,
+			HDMI_PHY_CTRL_PDDQ_MASK);
+}
+
+static void dw_hdmi_phy_svsmode(void *arg, int enable)
+{
+	struct dw_hdmi_dev *dw_dev = arg;
+
+	hdmi_mask_writel(dw_dev, enable, HDMI_PHY_CTRL,
+			HDMI_PHY_CTRL_SVSRETMODEZ_OFFSET,
+			HDMI_PHY_CTRL_SVSRETMODEZ_MASK);
+}
+
+static void dw_hdmi_zcal_reset(void *arg)
+{
+	struct dw_hdmi_dev *dw_dev = arg;
+
+	if (dw_dev->config->dw_zcal_reset)
+		dw_dev->config->dw_zcal_reset(dw_dev->config->dw_zcal_arg);
+}
+
+static bool dw_hdmi_zcal_done(void *arg)
+{
+	struct dw_hdmi_dev *dw_dev = arg;
+
+	if (dw_dev->config->dw_zcal_done)
+		return dw_dev->config->dw_zcal_done(dw_dev->config->dw_zcal_arg);
+	return true;
+}
+
+static bool dw_hdmi_tmds_valid(void *arg)
+{
+	struct dw_hdmi_dev *dw_dev = arg;
+
+	return hdmi_readl(dw_dev, HDMI_PLL_LCK_STS) & HDMI_PLL_LCK_STS_PLL_LOCKED;
+}
+
+static const struct dw_phy_funcs dw_hdmi_phy_funcs = {
+	.write = dw_hdmi_phy_write,
+	.read = dw_hdmi_phy_read,
+	.reset = dw_hdmi_phy_reset,
+	.pddq = dw_hdmi_phy_pddq,
+	.svsmode = dw_hdmi_phy_svsmode,
+	.zcal_reset = dw_hdmi_zcal_reset,
+	.zcal_done = dw_hdmi_zcal_done,
+	.tmds_valid = dw_hdmi_tmds_valid,
+};
+
+static int dw_hdmi_phy_init(struct dw_hdmi_dev *dw_dev)
+{
+	struct dw_phy_pdata *phy = &dw_dev->phy_config;
+	struct platform_device_info pdevinfo;
+
+	memset(&pdevinfo, 0, sizeof(pdevinfo));
+
+	phy->funcs = &dw_hdmi_phy_funcs;
+	phy->funcs_arg = dw_dev;
+
+	pdevinfo.parent = dw_dev->dev;
+	pdevinfo.id = PLATFORM_DEVID_NONE;
+	pdevinfo.name = dw_dev->phy_drv;
+	pdevinfo.data = phy;
+	pdevinfo.size_data = sizeof(*phy);
+	pdevinfo.dma_mask = DMA_BIT_MASK(32);
+
+	request_module(pdevinfo.name);
+
+	dw_dev->phy_pdev = platform_device_register_full(&pdevinfo);
+	if (IS_ERR(dw_dev->phy_pdev)) {
+		dev_err(dw_dev->dev, "failed to register phy device\n");
+		return PTR_ERR(dw_dev->phy_pdev);
+	}
+
+	if (!dw_dev->phy_pdev->dev.driver) {
+		dev_err(dw_dev->dev, "failed to initialize phy driver\n");
+		goto err;
+	}
+
+	if (!try_module_get(dw_dev->phy_pdev->dev.driver->owner)) {
+		dev_err(dw_dev->dev, "failed to get phy module\n");
+		goto err;
+	}
+
+	dw_dev->phy_sd = dev_get_drvdata(&dw_dev->phy_pdev->dev);
+	if (!dw_dev->phy_sd) {
+		dev_err(dw_dev->dev, "failed to get phy subdev\n");
+		goto err_put;
+	}
+
+	if (v4l2_device_register_subdev(&dw_dev->v4l2_dev, dw_dev->phy_sd)) {
+		dev_err(dw_dev->dev, "failed to register phy subdev\n");
+		goto err_put;
+	}
+
+	module_put(dw_dev->phy_pdev->dev.driver->owner);
+	return 0;
+
+err_put:
+	module_put(dw_dev->phy_pdev->dev.driver->owner);
+err:
+	platform_device_unregister(dw_dev->phy_pdev);
+	return -EINVAL;
+}
+
+static void dw_hdmi_phy_exit(struct dw_hdmi_dev *dw_dev)
+{
+	if (!IS_ERR(dw_dev->phy_pdev))
+		platform_device_unregister(dw_dev->phy_pdev);
+}
+
+static int dw_hdmi_phy_eq_init(struct dw_hdmi_dev *dw_dev, u16 acq, bool force)
+{
+	struct dw_phy_eq_command cmd = {
+		.result = 0,
+		.nacq = acq,
+		.force = force,
+	};
+	int ret;
+
+	ret = v4l2_subdev_call(dw_dev->phy_sd, core, ioctl,
+			DW_PHY_IOCTL_EQ_INIT, &cmd);
+	if (ret)
+		return ret;
+	return cmd.result;
+}
+
+static int dw_hdmi_phy_config(struct dw_hdmi_dev *dw_dev,
+		unsigned char color_depth, bool hdmi2, bool scrambling)
+{
+	struct dw_phy_config_command cmd = {
+		.result = 0,
+		.color_depth = color_depth,
+		.hdmi2 = hdmi2,
+		.scrambling = scrambling,
+	};
+	int ret;
+
+	if (dw_dev->phy_config.version >= 401) {
+		hdmi_mask_writel(dw_dev, 0x1, HDMI_CBUSIOCTRL,
+				HDMI_CBUSIOCTRL_DATAPATH_CBUSZ_OFFSET,
+				HDMI_CBUSIOCTRL_DATAPATH_CBUSZ_MASK);
+		hdmi_mask_writel(dw_dev, 0x1, HDMI_CBUSIOCTRL,
+				HDMI_CBUSIOCTRL_SVSRETMODEZ_OFFSET,
+				HDMI_CBUSIOCTRL_SVSRETMODEZ_MASK);
+		hdmi_mask_writel(dw_dev, 0x1, HDMI_CBUSIOCTRL,
+				HDMI_CBUSIOCTRL_PDDQ_OFFSET,
+				HDMI_CBUSIOCTRL_PDDQ_MASK);
+		hdmi_mask_writel(dw_dev, 0x1, HDMI_CBUSIOCTRL,
+				HDMI_CBUSIOCTRL_RESET_OFFSET,
+				HDMI_CBUSIOCTRL_RESET_MASK);
+	}
+
+	ret = v4l2_subdev_call(dw_dev->phy_sd, core, ioctl,
+			DW_PHY_IOCTL_CONFIG, &cmd);
+	if (ret)
+		return ret;
+	return cmd.result;
+}
+
+static void dw_hdmi_phy_s_power(struct dw_hdmi_dev *dw_dev, bool on)
+{
+	v4l2_subdev_call(dw_dev->phy_sd, core, s_power, on);
+}
+
+static void dw_hdmi_event_source_change(struct dw_hdmi_dev *dw_dev)
+{
+	if (dw_dev->registered)
+		v4l2_subdev_notify_event(&dw_dev->sd, &dw_hdmi_event_fmt);
+}
+
+static int dw_hdmi_wait_phy_lock_poll(struct dw_hdmi_dev *dw_dev)
+{
+	int timeout = 10;
+
+	while (!dw_hdmi_tmds_valid(dw_dev) && timeout-- && !dw_dev->force_off)
+		usleep_range(5000, 10000);
+
+	if (!dw_hdmi_tmds_valid(dw_dev))
+		return -ETIMEDOUT;
+	return 0;
+}
+
+static void dw_hdmi_reset_datapath(struct dw_hdmi_dev *dw_dev)
+{
+	u32 val = HDMI_DMI_SW_RST_TMDS |
+		HDMI_DMI_SW_RST_HDCP |
+		HDMI_DMI_SW_RST_VID |
+		HDMI_DMI_SW_RST_PIXEL |
+		HDMI_DMI_SW_RST_CEC |
+		HDMI_DMI_SW_RST_AUD |
+		HDMI_DMI_SW_RST_BUS |
+		HDMI_DMI_SW_RST_HDMI |
+		HDMI_DMI_SW_RST_MODET;
+
+	hdmi_writel(dw_dev, val, HDMI_DMI_SW_RST);
+}
+
+static void dw_hdmi_wait_video_stable(struct dw_hdmi_dev *dw_dev)
+{
+	/*
+	 * Empiric value. Video should be stable way longer before the
+	 * end of this sleep time. Though, we can have some video change
+	 * interrupts before the video is stable so filter them by sleeping.
+	 */
+	msleep(200);
+}
+
+static void dw_hdmi_enable_ints(struct dw_hdmi_dev *dw_dev)
+{
+	hdmi_writel(dw_dev, HDMI_ISTS_CLK_CHANGE | HDMI_ISTS_PLL_LCK_CHG,
+			HDMI_IEN_SET);
+	hdmi_writel(dw_dev, (~0x0) & (~HDMI_MD_ISTS_VOFS_LIN), HDMI_MD_IEN_SET);
+}
+
+static void dw_hdmi_disable_ints(struct dw_hdmi_dev *dw_dev)
+{
+	hdmi_writel(dw_dev, ~0x0, HDMI_IEN_CLR);
+	hdmi_writel(dw_dev, ~0x0, HDMI_MD_IEN_CLR);
+}
+
+static void dw_hdmi_clear_ints(struct dw_hdmi_dev *dw_dev)
+{
+	hdmi_writel(dw_dev, ~0x0, HDMI_ICLR);
+	hdmi_writel(dw_dev, ~0x0, HDMI_MD_ICLR);
+}
+
+static void dw_hdmi_cec_enable_ints(struct dw_hdmi_dev *dw_dev)
+{
+	u32 mask = HDMI_AUD_CEC_ISTS_DONE | HDMI_AUD_CEC_ISTS_EOM |
+		HDMI_AUD_CEC_ISTS_NACK | HDMI_AUD_CEC_ISTS_ARBLST |
+		HDMI_AUD_CEC_ISTS_ERROR_INIT | HDMI_AUD_CEC_ISTS_ERROR_FOLL;
+
+	hdmi_writel(dw_dev, mask, HDMI_AUD_CEC_IEN_SET);
+	hdmi_writel(dw_dev, 0x0, HDMI_CEC_MASK);
+}
+
+static void dw_hdmi_cec_disable_ints(struct dw_hdmi_dev *dw_dev)
+{
+	hdmi_writel(dw_dev, ~0x0, HDMI_AUD_CEC_IEN_CLR);
+	hdmi_writel(dw_dev, ~0x0, HDMI_CEC_MASK);
+}
+
+static void dw_hdmi_cec_clear_ints(struct dw_hdmi_dev *dw_dev)
+{
+	hdmi_writel(dw_dev, ~0x0, HDMI_AUD_CEC_ICLR);
+}
+
+static void dw_hdmi_cec_tx_raw_status(struct dw_hdmi_dev *dw_dev, u32 stat)
+{
+	u32 error_mask = HDMI_AUD_CEC_ISTS_NACK |
+		HDMI_AUD_CEC_ISTS_ERROR_INIT;
+
+	if (hdmi_readl(dw_dev, HDMI_CEC_CTRL) & HDMI_CEC_CTRL_SEND_MASK) {
+		dev_dbg(dw_dev->dev, "%s: tx is busy\n", __func__);
+		return;
+	}
+
+	if (stat & HDMI_AUD_CEC_ISTS_ARBLST) {
+		dev_dbg(dw_dev->dev, "%s: arbitration lost\n", __func__);
+		cec_transmit_done(dw_dev->cec_adap, CEC_TX_STATUS_ARB_LOST,
+				1, 0, 0, 0);
+		return;
+	}
+
+	if (stat & HDMI_AUD_CEC_ISTS_DONE) {
+		dev_dbg(dw_dev->dev, "%s: transmission done\n", __func__);
+		cec_transmit_done(dw_dev->cec_adap, CEC_TX_STATUS_OK, 0, 0, 0, 0);
+		return;
+	}
+
+	if (stat & error_mask) {
+		/* If we reached here we have an error */
+		u8 status = 0, nack_cnt = 0, error_cnt = 0;
+
+		dev_dbg(dw_dev->dev, "%s: error found (stat=0x%x)\n", __func__,
+				stat);
+
+		if (stat & HDMI_AUD_CEC_ISTS_NACK) {
+			status |= CEC_TX_STATUS_NACK;
+			nack_cnt++;
+		}
+
+		if (stat & HDMI_AUD_CEC_ISTS_ERROR_INIT) {
+			status |= CEC_TX_STATUS_ERROR;
+			error_cnt++;
+		}
+
+		cec_transmit_done(dw_dev->cec_adap, status, 0, nack_cnt, 0,
+				error_cnt);
+		return;
+	}
+}
+
+static void dw_hdmi_cec_received_msg(struct dw_hdmi_dev *dw_dev)
+{
+	struct cec_msg msg;
+	u8 i;
+
+	msg.len = hdmi_readl(dw_dev, HDMI_CEC_RX_CNT);
+	if (!msg.len || msg.len > HDMI_CEC_RX_DATA_MAX)
+		return; /* its an invalid/non-existent message */
+
+	for (i = 0; i < msg.len; i++)
+		msg.msg[i] = hdmi_readl(dw_dev, HDMI_CEC_RX_DATA(i));
+
+	hdmi_writel(dw_dev, 0x0, HDMI_CEC_LOCK);
+	cec_received_msg(dw_dev->cec_adap, &msg);
+}
+
+static int dw_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
+{
+	struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
+
+	if (!dw_dev->cec_enabled_adap && enable) {
+		hdmi_writel(dw_dev, 0x0, HDMI_CEC_LOCK);
+		dw_hdmi_cec_enable_ints(dw_dev);
+	} else if (dw_dev->cec_enabled_adap && !enable) {
+		dw_hdmi_cec_disable_ints(dw_dev);
+		dw_dev->cec_valid_addrs = 0;
+	}
+
+	dw_dev->cec_enabled_adap = enable;
+	return 0;
+}
+
+static int dw_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
+{
+	struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
+	int i, free_idx = HDMI_CEC_MAX_LOG_ADDRS;
+	u32 tmp;
+
+	if (!dw_dev->cec_enabled_adap)
+		return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO;
+
+	if (addr == CEC_LOG_ADDR_INVALID) {
+		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_L);
+		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_H);
+		dw_dev->cec_valid_addrs = 0;
+		return 0;
+	}
+
+	for (i = 0; i < HDMI_CEC_MAX_LOG_ADDRS; i++) {
+		bool is_valid = dw_dev->cec_valid_addrs & (1 << i);
+
+		if (free_idx == HDMI_CEC_MAX_LOG_ADDRS && !is_valid)
+			free_idx = i;
+		if (is_valid && dw_dev->cec_addr[i] == addr)
+			return 0;
+	}
+
+	if (i == HDMI_CEC_MAX_LOG_ADDRS) {
+		i = free_idx;
+		if (i == HDMI_CEC_MAX_LOG_ADDRS)
+			return -ENXIO;
+	}
+
+	dw_dev->cec_addr[i] = addr;
+	dw_dev->cec_valid_addrs |= 1 << i;
+
+	if (i >= 8) {
+		tmp = hdmi_readl(dw_dev, HDMI_CEC_ADDR_H);
+		tmp |= BIT(i - 8);
+		hdmi_writel(dw_dev, tmp, HDMI_CEC_ADDR_H);
+	} else {
+		tmp = hdmi_readl(dw_dev, HDMI_CEC_ADDR_L);
+		tmp |= BIT(i);
+		hdmi_writel(dw_dev, tmp, HDMI_CEC_ADDR_L);
+	}
+
+	return 0;
+}
+
+static int dw_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
+		u32 signal_free_time, struct cec_msg *msg)
+{
+	struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
+	u8 len = msg->len;
+	u32 reg;
+	int i;
+
+	if (len > HDMI_CEC_TX_DATA_MAX) {
+		dev_err(dw_dev->dev, "%s: invalid len (%d)\n", __func__, len);
+		return -EINVAL;
+	}
+
+	if (hdmi_readl(dw_dev, HDMI_CEC_CTRL) & HDMI_CEC_CTRL_SEND_MASK) {
+		dev_err(dw_dev->dev, "%s: tx is busy\n", __func__);
+		return -EBUSY;
+	}
+
+	for (i = 0; i < HDMI_CEC_TX_DATA_MAX; i++)
+		hdmi_writel(dw_dev, 0x0, HDMI_CEC_TX_DATA(i));
+	for (i = 0; i < len; i++)
+		hdmi_writel(dw_dev, msg->msg[i], HDMI_CEC_TX_DATA(i));
+
+	switch (signal_free_time) {
+	case 3:
+		reg = 0x0;
+		break;
+	case 7:
+		reg = 0x2;
+		break;
+	case 5:
+	default:
+		reg = 0x1;
+		break;
+	}
+
+	hdmi_writel(dw_dev, len, HDMI_CEC_TX_CNT);
+	hdmi_mask_writel(dw_dev, reg, HDMI_CEC_CTRL,
+			HDMI_CEC_CTRL_FRAME_TYP_OFFSET,
+			HDMI_CEC_CTRL_FRAME_TYP_MASK);
+	hdmi_mask_writel(dw_dev, 0x1, HDMI_CEC_CTRL,
+			HDMI_CEC_CTRL_SEND_OFFSET,
+			HDMI_CEC_CTRL_SEND_MASK);
+	return 0;
+}
+
+static const struct cec_adap_ops dw_hdmi_cec_adap_ops = {
+	.adap_enable = dw_hdmi_cec_adap_enable,
+	.adap_log_addr = dw_hdmi_cec_adap_log_addr,
+	.adap_transmit = dw_hdmi_cec_adap_transmit,
+};
+
+static u8 dw_hdmi_get_curr_vic(struct dw_hdmi_dev *dw_dev, bool *is_hdmi_vic)
+{
+	u8 vic = hdmi_mask_readl(dw_dev, HDMI_PDEC_AVI_PB,
+			HDMI_PDEC_AVI_PB_VID_IDENT_CODE_OFFSET,
+			HDMI_PDEC_AVI_PB_VID_IDENT_CODE_MASK) & 0xff;
+
+	if (!vic) {
+		vic = hdmi_mask_readl(dw_dev, HDMI_PDEC_VSI_PAYLOAD0,
+				HDMI_PDEC_VSI_PAYLOAD0_HDMI_VIC_OFFSET,
+				HDMI_PDEC_VSI_PAYLOAD0_HDMI_VIC_MASK) & 0xff;
+		if (is_hdmi_vic)
+			*is_hdmi_vic = true;
+	} else {
+		if (is_hdmi_vic)
+			*is_hdmi_vic = false;
+	}
+
+	return vic;
+}
+
+static u64 dw_hdmi_get_pixelclk(struct dw_hdmi_dev *dw_dev)
+{
+	u32 rate = hdmi_mask_readl(dw_dev, HDMI_CKM_RESULT,
+			HDMI_CKM_RESULT_CLKRATE_OFFSET,
+			HDMI_CKM_RESULT_CLKRATE_MASK);
+	u32 evaltime = hdmi_mask_readl(dw_dev, HDMI_CKM_EVLTM,
+			HDMI_CKM_EVLTM_EVAL_TIME_OFFSET,
+			HDMI_CKM_EVLTM_EVAL_TIME_MASK);
+	u64 tmp = (u64)rate * (u64)dw_dev->cfg_clk * 1000000;
+
+	do_div(tmp, evaltime);
+	return tmp;
+}
+
+static u32 dw_hdmi_get_colordepth(struct dw_hdmi_dev *dw_dev)
+{
+	u32 dcm = hdmi_mask_readl(dw_dev, HDMI_STS,
+			HDMI_STS_DCM_CURRENT_MODE_OFFSET,
+			HDMI_STS_DCM_CURRENT_MODE_MASK);
+
+	switch (dcm) {
+	case 0x4:
+		return 24;
+	case 0x5:
+		return 30;
+	case 0x6:
+		return 36;
+	case 0x7:
+		return 48;
+	default:
+		return 24;
+	}
+}
+
+static void dw_hdmi_set_input(struct dw_hdmi_dev *dw_dev, u32 input)
+{
+	hdmi_mask_writel(dw_dev, input, HDMI_PHY_CTRL,
+			HDMI_PHY_CTRL_PORTSELECT_OFFSET,
+			HDMI_PHY_CTRL_PORTSELECT_MASK);
+	dw_dev->configured_input = input;
+}
+
+static void dw_hdmi_enable_hpd(struct dw_hdmi_dev *dw_dev, u32 input)
+{
+	switch (input) {
+	case 0:
+	case 1:
+	case 2:
+	case 3:
+		break;
+	default:
+		return;
+	}
+
+	hdmi_mask_writel(dw_dev, BIT(input), HDMI_SETUP_CTRL,
+			HDMI_SETUP_CTRL_HOT_PLUG_DETECT_INPUT_X_OFFSET,
+			HDMI_SETUP_CTRL_HOT_PLUG_DETECT_INPUT_X_MASK);
+	hdmi_mask_writel(dw_dev, 0x1, HDMI_SETUP_CTRL,
+			HDMI_SETUP_CTRL_HOT_PLUG_DETECT_OFFSET,
+			HDMI_SETUP_CTRL_HOT_PLUG_DETECT_MASK);
+}
+
+static void dw_hdmi_disable_hpd(struct dw_hdmi_dev *dw_dev)
+{
+	hdmi_mask_writel(dw_dev, 0x0, HDMI_SETUP_CTRL,
+			HDMI_SETUP_CTRL_HOT_PLUG_DETECT_INPUT_X_OFFSET,
+			HDMI_SETUP_CTRL_HOT_PLUG_DETECT_INPUT_X_MASK);
+	hdmi_mask_writel(dw_dev, 0x0, HDMI_SETUP_CTRL,
+			HDMI_SETUP_CTRL_HOT_PLUG_DETECT_OFFSET,
+			HDMI_SETUP_CTRL_HOT_PLUG_DETECT_MASK);
+}
+
+static int dw_hdmi_config(struct dw_hdmi_dev *dw_dev, u32 input)
+{
+	int eqret, ret = 0;
+
+	while (1) {
+		/* Give up silently if we are forcing off */
+		if (dw_dev->force_off) {
+			ret = 0;
+			goto out;
+		}
+		/* Give up silently if input has disconnected */
+		if (!has_signal(dw_dev, input)) {
+			ret = 0;
+			goto out;
+		}
+
+		switch (dw_dev->state) {
+		case HDMI_STATE_POWER_OFF:
+			dw_hdmi_disable_ints(dw_dev);
+			dw_hdmi_set_state(dw_dev, HDMI_STATE_PHY_CONFIG);
+			break;
+		case HDMI_STATE_PHY_CONFIG:
+			dw_hdmi_phy_s_power(dw_dev, true);
+			dw_hdmi_phy_config(dw_dev, 8, false, false);
+			dw_hdmi_set_state(dw_dev, HDMI_STATE_CONTROLLER_CONFIG);
+			break;
+		case HDMI_STATE_CONTROLLER_CONFIG:
+			dw_hdmi_set_input(dw_dev, input);
+			dw_hdmi_enable_hpd(dw_dev, input);
+			dw_hdmi_set_state(dw_dev, HDMI_STATE_EQUALIZER);
+			break;
+		case HDMI_STATE_EQUALIZER:
+			eqret = dw_hdmi_phy_eq_init(dw_dev, 5,
+					dw_dev->phy_eq_force);
+			ret = dw_hdmi_wait_phy_lock_poll(dw_dev);
+
+			/* Do not force equalizer */
+			dw_dev->phy_eq_force = false;
+
+			if (ret || eqret) {
+				if (ret || eqret == -ETIMEDOUT) {
+					/* No TMDSVALID signal:
+					 * 	- force equalizer */
+					dw_dev->phy_eq_force = true;
+				}
+				break;
+			}
+
+			dw_hdmi_set_state(dw_dev, HDMI_STATE_VIDEO_UNSTABLE);
+			break;
+		case HDMI_STATE_VIDEO_UNSTABLE:
+			dw_hdmi_reset_datapath(dw_dev);
+			dw_hdmi_wait_video_stable(dw_dev);
+			dw_hdmi_clear_ints(dw_dev);
+			dw_hdmi_enable_ints(dw_dev);
+			dw_hdmi_set_state(dw_dev, HDMI_STATE_POWER_ON);
+			break;
+		case HDMI_STATE_POWER_ON:
+			break;
+		default:
+			dev_err(dw_dev->dev, "%s called with state (%d)\n",
+					__func__, dw_dev->state);
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (dw_dev->state == HDMI_STATE_POWER_ON) {
+			dev_info(dw_dev->dev, "HDMI-RX configured\n");
+			dw_hdmi_event_source_change(dw_dev);
+			return 0;
+		}
+	}
+
+out:
+	dw_hdmi_set_state(dw_dev, HDMI_STATE_POWER_OFF);
+	return ret;
+}
+
+static int dw_hdmi_config_hdcp(struct dw_hdmi_dev *dw_dev)
+{
+	struct dw_hdmi_hdcp14_key *keys = &dw_dev->config->hdcp14_keys;
+	int i, j, key_write_tries = 5;
+
+	/* TODO: HDCP 2.2 is not implemented in SW for now, just bypass it */
+	hdmi_mask_writel(dw_dev, 0x0, HDMI_HDCP22_CONTROL,
+			HDMI_HDCP22_CONTROL_OVR_VAL_OFFSET,
+			HDMI_HDCP22_CONTROL_OVR_VAL_MASK);
+	hdmi_mask_writel(dw_dev, 0x1, HDMI_HDCP22_CONTROL,
+			HDMI_HDCP22_CONTROL_OVR_EN_OFFSET,
+			HDMI_HDCP22_CONTROL_OVR_EN_MASK);
+
+	if (!keys->keys_valid) {
+		dev_warn(dw_dev->dev, "[HDCP 1.4] no valid keys provided\n");
+		return 0;
+	}
+
+	hdmi_mask_writel(dw_dev, 0x0, HDMI_HDCP_CTRL,
+			HDMI_HDCP_CTRL_ENABLE_OFFSET,
+			HDMI_HDCP_CTRL_ENABLE_MASK);
+	hdmi_mask_writel(dw_dev, 0x1, HDMI_HDCP_CTRL,
+			HDMI_HDCP_CTRL_KEY_DECRYPT_ENABLE_OFFSET,
+			HDMI_HDCP_CTRL_KEY_DECRYPT_ENABLE_MASK);
+
+	hdmi_writel(dw_dev, keys->seed, HDMI_HDCP_SEED);
+
+	for (i = 0; i < DW_HDMI_HDCP14_KEYS_SIZE; i += 2) {
+		for (j = 0; j < key_write_tries; j++) {
+			if (is_hdcp14_key_write_allowed(dw_dev))
+				break;
+			mdelay(10);
+		}
+
+		if (j == key_write_tries)
+			return -ETIMEDOUT;
+
+		hdmi_writel(dw_dev, keys->keys[i], HDMI_HDCP_KEY1);
+		hdmi_writel(dw_dev, keys->keys[i + 1], HDMI_HDCP_KEY0);
+	}
+
+	hdmi_writel(dw_dev, keys->bksv[0], HDMI_HDCP_BKSV1);
+	hdmi_writel(dw_dev, keys->bksv[1], HDMI_HDCP_BKSV0);
+
+	hdmi_mask_writel(dw_dev, 0x1, HDMI_HDCP_CTRL,
+			HDMI_HDCP_CTRL_ENABLE_OFFSET,
+			HDMI_HDCP_CTRL_ENABLE_MASK);
+	return 0;
+}
+
+static int __dw_hdmi_power_on(struct dw_hdmi_dev *dw_dev, u32 input)
+{
+	unsigned long flags;
+	int ret;
+
+	ret = dw_hdmi_config(dw_dev, input);
+
+	spin_lock_irqsave(&dw_dev->lock, flags);
+	dw_dev->pending_config = false;
+	spin_unlock_irqrestore(&dw_dev->lock, flags);
+
+	return ret;
+}
+
+struct dw_hdmi_work_data {
+	struct dw_hdmi_dev *dw_dev;
+	struct work_struct work;
+	u32 input;
+};
+
+static void dw_hdmi_work_handler(struct work_struct *work)
+{
+	struct dw_hdmi_work_data *data = container_of(work,
+			struct dw_hdmi_work_data, work);
+
+	__dw_hdmi_power_on(data->dw_dev, data->input);
+	devm_kfree(data->dw_dev->dev, data);
+}
+
+static int dw_hdmi_power_on(struct dw_hdmi_dev *dw_dev, u32 input)
+{
+	struct dw_hdmi_work_data *data;
+	unsigned long flags;
+
+	data = devm_kzalloc(dw_dev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	INIT_WORK(&data->work, dw_hdmi_work_handler);
+	data->dw_dev = dw_dev;
+	data->input = input;
+
+	spin_lock_irqsave(&dw_dev->lock, flags);
+	if (dw_dev->pending_config) {
+		devm_kfree(dw_dev->dev, data);
+		spin_unlock_irqrestore(&dw_dev->lock, flags);
+		return 0;
+	}
+
+	queue_work(dw_dev->wq, &data->work);
+	dw_dev->pending_config = true;
+	spin_unlock_irqrestore(&dw_dev->lock, flags);
+	return 0;
+}
+
+static void dw_hdmi_power_off(struct dw_hdmi_dev *dw_dev)
+{
+	unsigned long flags;
+
+	dw_dev->force_off = true;
+	flush_workqueue(dw_dev->wq);
+	dw_dev->force_off = false;
+
+	spin_lock_irqsave(&dw_dev->lock, flags);
+	dw_dev->pending_config = false;
+	dw_dev->state = HDMI_STATE_POWER_OFF;
+	spin_unlock_irqrestore(&dw_dev->lock, flags);
+
+	/* Reset variables */
+	dw_dev->phy_eq_force = true;
+
+	/* Send source change event to userspace */
+	dw_hdmi_event_source_change(dw_dev);
+}
+
+static u32 dw_hdmi_get_int_val(struct dw_hdmi_dev *dw_dev, u32 ists, u32 ien)
+{
+	return hdmi_readl(dw_dev, ists) & hdmi_readl(dw_dev, ien);
+}
+
+static irqreturn_t dw_hdmi_irq_handler(int irq, void *dev_data)
+{
+	struct dw_hdmi_dev *dw_dev = dev_data;
+	u32 hdmi_ists = dw_hdmi_get_int_val(dw_dev, HDMI_ISTS, HDMI_IEN);
+	u32 md_ists = dw_hdmi_get_int_val(dw_dev, HDMI_MD_ISTS, HDMI_MD_IEN);
+	u32 cec_ists = dw_hdmi_get_int_val(dw_dev, HDMI_AUD_CEC_ISTS,
+			HDMI_AUD_CEC_IEN);
+
+	dw_hdmi_clear_ints(dw_dev);
+	dw_hdmi_cec_clear_ints(dw_dev);
+
+	if ((hdmi_ists & HDMI_ISTS_CLK_CHANGE) ||
+	    (hdmi_ists & HDMI_ISTS_PLL_LCK_CHG) || md_ists) {
+		dw_hdmi_power_off(dw_dev);
+		if (has_signal(dw_dev, dw_dev->configured_input))
+			dw_hdmi_power_on(dw_dev, dw_dev->configured_input);
+	}
+
+	if (cec_ists) {
+		dw_hdmi_cec_tx_raw_status(dw_dev, cec_ists);
+		if (cec_ists & HDMI_AUD_CEC_ISTS_EOM)
+			dw_hdmi_cec_received_msg(dw_dev);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void dw_hdmi_detect_tx_5v(struct dw_hdmi_dev *dw_dev)
+{
+	unsigned int input_count = 4; /* TODO: Get from DT node this value */
+	unsigned int old_input = dw_dev->configured_input;
+	unsigned int new_input = old_input;
+	bool pending_config = false, current_on = true;
+	int i;
+
+	if (!has_signal(dw_dev, old_input)) {
+		dw_hdmi_disable_ints(dw_dev);
+		dw_hdmi_disable_hpd(dw_dev);
+		dw_hdmi_power_off(dw_dev);
+		current_on = false;
+	}
+
+	for (i = 0; i < input_count; i++) {
+		bool on = has_signal(dw_dev, i);
+
+		if (is_off(dw_dev) && on && !pending_config) {
+			dw_hdmi_power_on(dw_dev, i);
+			new_input = i;
+			pending_config = true;
+		}
+	}
+
+	if ((new_input == old_input) && !pending_config && !current_on)
+		dw_hdmi_phy_s_power(dw_dev, false);
+
+	dev_dbg(dw_dev->dev, "%s: %d%d%d%d\n", __func__,
+			dw_hdmi_5v_status(dw_dev, 0),
+			dw_hdmi_5v_status(dw_dev, 1),
+			dw_hdmi_5v_status(dw_dev, 2),
+			dw_hdmi_5v_status(dw_dev, 3));
+}
+
+static irqreturn_t dw_hdmi_5v_irq_handler(int irq, void *dev_data)
+{
+	struct dw_hdmi_dev *dw_dev = dev_data;
+
+	dw_hdmi_detect_tx_5v(dw_dev);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t dw_hdmi_5v_hard_irq_handler(int irq, void *dev_data)
+{
+	struct dw_hdmi_dev *dw_dev = dev_data;
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+	dw_hdmi_5v_clear(dw_dev);
+	return IRQ_WAKE_THREAD;
+}
+
+static int dw_hdmi_s_routing(struct v4l2_subdev *sd, u32 input, u32 output,
+		u32 config)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	if (!has_signal(dw_dev, input))
+		return -EINVAL;
+
+	dw_dev->selected_input = input;
+	if (input == dw_dev->configured_input)
+		return 0;
+
+	dw_hdmi_power_off(dw_dev);
+	return dw_hdmi_power_on(dw_dev, input);
+}
+
+static int dw_hdmi_g_input_status(struct v4l2_subdev *sd, u32 *status)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	*status = 0;
+	if (!has_signal(dw_dev, dw_dev->selected_input))
+		*status |= V4L2_IN_ST_NO_POWER;
+	if (is_off(dw_dev))
+		*status |= V4L2_IN_ST_NO_SIGNAL;
+
+	dev_dbg(dw_dev->dev, "%s: status=0x%x\n", __func__, *status);
+	return 0;
+}
+
+static int dw_hdmi_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *parm)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+
+	/* TODO: Use helper to compute timeperframe */
+	parm->parm.capture.timeperframe.numerator = 1;
+	parm->parm.capture.timeperframe.denominator = 60;
+	return 0;
+}
+
+static int dw_hdmi_g_dv_timings(struct v4l2_subdev *sd,
+		struct v4l2_dv_timings *timings)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+
+	*timings = dw_dev->timings;
+	return 0;
+}
+
+static int dw_hdmi_query_dv_timings(struct v4l2_subdev *sd,
+		struct v4l2_dv_timings *timings)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+	struct v4l2_bt_timings *bt = &timings->bt;
+	bool is_hdmi_vic;
+	u32 htot, hofs;
+	u32 vtot;
+	u8 vic;
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+
+	memset(timings, 0, sizeof(*timings));
+
+	timings->type = V4L2_DV_BT_656_1120;
+	bt->width = hdmi_readl(dw_dev, HDMI_MD_HACT_PX);
+	bt->height = hdmi_readl(dw_dev, HDMI_MD_VAL);
+	bt->interlaced = hdmi_readl(dw_dev, HDMI_MD_STS) & HDMI_MD_STS_ILACE;
+
+	if (hdmi_readl(dw_dev, HDMI_ISTS) & HDMI_ISTS_VS_POL_ADJ)
+		bt->polarities |= V4L2_DV_VSYNC_POS_POL;
+	if (hdmi_readl(dw_dev, HDMI_ISTS) & HDMI_ISTS_HS_POL_ADJ)
+		bt->polarities |= V4L2_DV_HSYNC_POS_POL;
+
+	bt->pixelclock = dw_hdmi_get_pixelclk(dw_dev);
+
+	/* HTOT = HACT + HFRONT + HSYNC + HBACK */
+	htot = hdmi_mask_readl(dw_dev, HDMI_MD_HT1,
+			HDMI_MD_HT1_HTOT_PIX_OFFSET,
+			HDMI_MD_HT1_HTOT_PIX_MASK);
+	/* HOFS = HSYNC + HBACK */
+	hofs = hdmi_mask_readl(dw_dev, HDMI_MD_HT1,
+			HDMI_MD_HT1_HOFS_PIX_OFFSET,
+			HDMI_MD_HT1_HOFS_PIX_MASK);
+
+	bt->hfrontporch = htot - hofs - bt->width;
+	bt->hsync = hdmi_mask_readl(dw_dev, HDMI_MD_HT0,
+			HDMI_MD_HT0_HS_CLK_OFFSET,
+			HDMI_MD_HT0_HS_CLK_MASK);
+	bt->hbackporch = hofs - bt->hsync;
+
+	/* VTOT = VACT + VFRONT + VSYNC + VBACK */
+	vtot = hdmi_readl(dw_dev, HDMI_MD_VTL);
+
+	hdmi_mask_writel(dw_dev, 0x1, HDMI_MD_VCTRL,
+			HDMI_MD_VCTRL_V_OFFS_LIN_MODE_OFFSET,
+			HDMI_MD_VCTRL_V_OFFS_LIN_MODE_MASK);
+	mdelay(50);
+	bt->vsync = hdmi_readl(dw_dev, HDMI_MD_VOL);
+
+	hdmi_mask_writel(dw_dev, 0x0, HDMI_MD_VCTRL,
+			HDMI_MD_VCTRL_V_OFFS_LIN_MODE_OFFSET,
+			HDMI_MD_VCTRL_V_OFFS_LIN_MODE_MASK);
+	mdelay(50);
+	bt->vbackporch = hdmi_readl(dw_dev, HDMI_MD_VOL);
+	bt->vfrontporch = vtot - bt->height - bt->vsync - bt->vbackporch;
+	bt->standards = V4L2_DV_BT_STD_CEA861;
+
+	vic = dw_hdmi_get_curr_vic(dw_dev, &is_hdmi_vic);
+	if (vic) {
+		if (is_hdmi_vic) {
+			bt->flags |= V4L2_DV_FL_HAS_HDMI_VIC;
+			bt->hdmi_vic = vic;
+			bt->cea861_vic = 0;
+		} else {
+			bt->flags |= V4L2_DV_FL_HAS_CEA861_VIC;
+			bt->hdmi_vic = 0;
+			bt->cea861_vic = vic;
+		}
+	}
+
+	return 0;
+}
+
+static int dw_hdmi_enum_mbus_code(struct v4l2_subdev *sd,
+		struct v4l2_subdev_pad_config *cfg,
+		struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+	if (code->index != 0)
+		return -EINVAL;
+
+	code->code = dw_dev->mbus_code;
+	return 0;
+}
+
+static int dw_hdmi_fill_format(struct dw_hdmi_dev *dw_dev,
+		struct v4l2_mbus_framefmt *format)
+{
+	memset(format, 0, sizeof(*format));
+
+	format->width = dw_dev->timings.bt.width;
+	format->height = dw_dev->timings.bt.height;
+	format->colorspace = V4L2_COLORSPACE_SRGB;
+	format->code = dw_dev->mbus_code;
+	if (dw_dev->timings.bt.interlaced)
+		format->field = V4L2_FIELD_ALTERNATE;
+	else
+		format->field = V4L2_FIELD_NONE;
+
+	return 0;
+}
+
+static int dw_hdmi_get_fmt(struct v4l2_subdev *sd,
+		struct v4l2_subdev_pad_config *cfg,
+		struct v4l2_subdev_format *format)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+	return dw_hdmi_fill_format(dw_dev, &format->format);
+}
+
+static int dw_hdmi_set_fmt(struct v4l2_subdev *sd,
+		struct v4l2_subdev_pad_config *cfg,
+		struct v4l2_subdev_format *format)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+
+	if (format->format.code != dw_dev->mbus_code) {
+		dev_dbg(dw_dev->dev, "invalid format\n");
+		return -EINVAL;
+	}
+
+	return dw_hdmi_get_fmt(sd, cfg, format);
+}
+
+static int dw_hdmi_dv_timings_cap(struct v4l2_subdev *sd,
+		struct v4l2_dv_timings_cap *cap)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+	unsigned int pad = cap->pad;
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+
+	*cap = dw_hdmi_timings_cap;
+	cap->pad = pad;
+	return 0;
+}
+
+static int dw_hdmi_enum_dv_timings(struct v4l2_subdev *sd,
+		struct v4l2_enum_dv_timings *timings)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+	return v4l2_enum_dv_timings_cap(timings, &dw_hdmi_timings_cap,
+			NULL, NULL);
+}
+
+static int dw_hdmi_log_status(struct v4l2_subdev *sd)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+	struct v4l2_dv_timings timings;
+
+	v4l2_info(sd, "--- Chip configuration ---\n");
+	v4l2_info(sd, "cfg_clk=%dMHz\n", dw_dev->cfg_clk);
+	v4l2_info(sd, "phy_drv=%s, phy_jtag_addr=0x%x\n", dw_dev->phy_drv,
+			dw_dev->phy_jtag_addr);
+
+	v4l2_info(sd, "--- Chip status ---\n");
+	v4l2_info(sd, "selected_input=%d: signal=%d\n", dw_dev->selected_input,
+			has_signal(dw_dev, dw_dev->selected_input));
+	v4l2_info(sd, "configured_input=%d: signal=%d\n",
+			dw_dev->configured_input,
+			has_signal(dw_dev, dw_dev->configured_input));
+
+	v4l2_info(sd, "--- CEC status ---\n");
+	if (dw_dev->cec_enabled_adap) {
+		int i;
+
+		for (i = 0; i < HDMI_CEC_MAX_LOG_ADDRS; i++) {
+			bool is_valid = dw_dev->cec_valid_addrs & (1 << i);
+
+			if (is_valid)
+				v4l2_info(sd, "cec_logical_address=0x%x\n",
+						dw_dev->cec_addr[i]);
+		}
+	}
+
+	v4l2_info(sd, "--- Video status ---\n");
+	v4l2_info(sd, "type=%s, color_depth=%dbits",
+			hdmi_readl(dw_dev, HDMI_PDEC_STS) &
+			HDMI_PDEC_STS_DVIDET ? "dvi" : "hdmi",
+			dw_hdmi_get_colordepth(dw_dev));
+
+	v4l2_info(sd, "--- Video timings ---\n");
+	if (dw_hdmi_query_dv_timings(sd, &timings))
+		v4l2_info(sd, "No video detected\n");
+	else
+		v4l2_print_dv_timings(sd->name, "Detected format: ",
+				&timings, true);
+	v4l2_print_dv_timings(sd->name, "Configured format: ",
+			&dw_dev->timings, true);
+	return 0;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static void dw_hdmi_invalid_register(struct dw_hdmi_dev *dw_dev, u64 reg)
+{
+	dev_err(dw_dev->dev, "register 0x%llx not supported\n", reg);
+	dev_err(dw_dev->dev, "0x0000-0x7fff: Main controller map\n");
+	dev_err(dw_dev->dev, "0x8000-0x80ff: PHY map\n");
+}
+
+static bool dw_hdmi_is_reserved_register(struct dw_hdmi_dev *dw_dev, u32 reg)
+{
+	if (reg >= HDMI_HDCP_CTRL && reg <= HDMI_HDCP_STS)
+		return true;
+	if (reg == HDMI_HDCP22_CONTROL)
+		return true;
+	if (reg == HDMI_HDCP22_STATUS)
+		return true;
+	return false;
+}
+
+static int dw_hdmi_g_register(struct v4l2_subdev *sd,
+		struct v4l2_dbg_register *reg)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	switch (reg->reg >> 15) {
+	case 0: /* Controller core read */
+		if (dw_hdmi_is_reserved_register(dw_dev, reg->reg & 0x7fff))
+			return -EINVAL;
+
+		reg->size = 4;
+		reg->val = hdmi_readl(dw_dev, reg->reg & 0x7fff);
+		return 0;
+	case 1: /* PHY read */
+		if ((reg->reg & ~0xff) != BIT(15))
+			break;
+
+		reg->size = 2;
+		reg->val = dw_hdmi_phy_read(dw_dev, reg->reg & 0xff);
+		return 0;
+	default:
+		break;
+	}
+
+	dw_hdmi_invalid_register(dw_dev, reg->reg);
+	return 0;
+}
+
+static int dw_hdmi_s_register(struct v4l2_subdev *sd,
+		const struct v4l2_dbg_register *reg)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	switch (reg->reg >> 15) {
+	case 0: /* Controller core write */
+		if (dw_hdmi_is_reserved_register(dw_dev, reg->reg & 0x7fff))
+			return -EINVAL;
+
+		hdmi_writel(dw_dev, reg->val & GENMASK(31,0), reg->reg & 0x7fff);
+		return 0;
+	case 1: /* PHY write */
+		if ((reg->reg & ~0xff) != BIT(15))
+			break;
+		dw_hdmi_phy_write(dw_dev, reg->val & 0xffff, reg->reg & 0xff);
+		return 0;
+	default:
+		break;
+	}
+
+	dw_hdmi_invalid_register(dw_dev, reg->reg);
+	return 0;
+}
+#endif
+
+static int dw_hdmi_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
+		struct v4l2_event_subscription *sub)
+{
+	switch (sub->type) {
+	case V4L2_EVENT_SOURCE_CHANGE:
+		return v4l2_src_change_event_subdev_subscribe(sd, fh, sub);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int dw_hdmi_registered(struct v4l2_subdev *sd)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+	int ret;
+
+	ret = cec_register_adapter(dw_dev->cec_adap, dw_dev->dev);
+	if (ret) {
+		dev_err(dw_dev->dev, "failed to register CEC adapter\n");
+		cec_delete_adapter(dw_dev->cec_adap);
+	}
+
+	dw_dev->registered = true;
+	return ret;
+}
+
+static void dw_hdmi_unregistered(struct v4l2_subdev *sd)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	cec_unregister_adapter(dw_dev->cec_adap);
+}
+
+static const struct v4l2_subdev_core_ops dw_hdmi_sd_core_ops = {
+	.log_status = dw_hdmi_log_status,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.g_register = dw_hdmi_g_register,
+	.s_register = dw_hdmi_s_register,
+#endif
+	.subscribe_event = dw_hdmi_subscribe_event,
+};
+
+static const struct v4l2_subdev_video_ops dw_hdmi_sd_video_ops = {
+	.s_routing = dw_hdmi_s_routing,
+	.g_input_status = dw_hdmi_g_input_status,
+	.g_parm = dw_hdmi_g_parm,
+	.g_dv_timings = dw_hdmi_g_dv_timings,
+	.query_dv_timings = dw_hdmi_query_dv_timings,
+};
+
+static const struct v4l2_subdev_pad_ops dw_hdmi_sd_pad_ops = {
+	.enum_mbus_code = dw_hdmi_enum_mbus_code,
+	.get_fmt = dw_hdmi_get_fmt,
+	.set_fmt = dw_hdmi_set_fmt,
+	.dv_timings_cap = dw_hdmi_dv_timings_cap,
+	.enum_dv_timings = dw_hdmi_enum_dv_timings,
+};
+
+static const struct v4l2_subdev_ops dw_hdmi_sd_ops = {
+	.core = &dw_hdmi_sd_core_ops,
+	.video = &dw_hdmi_sd_video_ops,
+	.pad = &dw_hdmi_sd_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops dw_hdmi_internal_ops = {
+	.registered = dw_hdmi_registered,
+	.unregistered = dw_hdmi_unregistered,
+};
+
+static int dw_hdmi_parse_dt(struct dw_hdmi_dev *dw_dev)
+{
+	struct dw_phy_pdata *phy = &dw_dev->phy_config;
+	struct device_node *np = dw_dev->of_node;
+
+	if (!np) {
+		dev_err(dw_dev->dev, "missing DT node\n");
+		return -EINVAL;
+	}
+
+	/* PHY properties parsing */
+	of_property_read_u8(np, "snps,hdmi-phy-jtag-addr",
+			&dw_dev->phy_jtag_addr);
+	if (!dw_dev->phy_jtag_addr) {
+		dev_err(dw_dev->dev, "missing hdmi-phy-jtag-addr in DT\n");
+		return -EINVAL;
+	}
+
+	of_property_read_u32(np, "snps,hdmi-phy-version", &phy->version);
+	if (!phy->version) {
+		dev_err(dw_dev->dev, "missing hdmi-phy-version in DT\n");
+		return -EINVAL;
+	}
+
+	of_property_read_u32(np, "snps,hdmi-phy-cfg-clk", &phy->cfg_clk);
+	if (!phy->cfg_clk) {
+		dev_err(dw_dev->dev, "missing hdmi-phy-cfg-clk in DT\n");
+		return -EINVAL;
+	}
+
+	if (of_property_read_string_index(np, "snps,hdmi-phy-driver", 0,
+				&dw_dev->phy_drv) < 0) {
+		dev_err(dw_dev->dev, "missing hdmi-phy-driver in DT\n");
+		return -EINVAL;
+	}
+
+	/* Controller properties parsing */
+	of_property_read_u32(np, "snps,hdmi-ctl-cfg-clk", &dw_dev->cfg_clk);
+	if (!dw_dev->cfg_clk) {
+		dev_err(dw_dev->dev, "missing hdmi-ctl-cfg-clk in DT\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int dw_hdmi_rx_probe(struct platform_device *pdev)
+{
+	const struct v4l2_dv_timings timings_def = HDMI_DEFAULT_TIMING;
+	struct dw_hdmi_rx_pdata *pdata = pdev->dev.platform_data;
+	struct device *dev = &pdev->dev;
+	struct dw_hdmi_dev *dw_dev;
+	struct v4l2_subdev *sd;
+	struct resource *res;
+	int ret, irq;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	/* Resource allocation */
+	dw_dev = devm_kzalloc(dev, sizeof(*dw_dev), GFP_KERNEL);
+	if (!dw_dev)
+		return -ENOMEM;
+
+	/* Resource initialization */
+	if (!pdata) {
+		dev_err(dev, "missing platform data\n");
+		return -EINVAL;
+	}
+
+	dw_dev->dev = dev;
+	dw_dev->config = pdata;
+	dw_dev->state = HDMI_STATE_NO_INIT;
+	dw_dev->of_node = dev->of_node ? dev->of_node : dev->parent->of_node;
+	spin_lock_init(&dw_dev->lock);
+
+	/* Device Tree parsing */
+	ret = dw_hdmi_parse_dt(dw_dev);
+	if (ret)
+		return ret;
+
+	/* Deferred work */
+	dw_dev->wq = create_workqueue(DW_HDMI_RX_DRVNAME);
+	if (!dw_dev->wq)
+		return -ENOMEM;
+
+	/* Registers mapping */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		ret = -ENXIO;
+		goto err_wq;
+	}
+
+	dw_dev->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(dw_dev->regs)) {
+		ret = PTR_ERR(dw_dev->regs);
+		goto err_wq;
+	}
+
+	/* Disable HPD as soon as posssible */
+	dw_hdmi_disable_hpd(dw_dev);
+
+	/* Configure HDCP */
+	ret = dw_hdmi_config_hdcp(dw_dev);
+	if (ret)
+		goto err_wq;
+
+	/* Interrupts mapping */
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		ret = irq;
+		goto err_wq;
+	}
+
+	ret = devm_request_threaded_irq(dev, irq, NULL, dw_hdmi_irq_handler,
+			IRQF_ONESHOT, DW_HDMI_RX_DRVNAME, dw_dev);
+	if (ret)
+		goto err_wq;
+
+	irq = platform_get_irq(pdev, 1);
+	if (irq < 0) {
+		ret = irq;
+		goto err_wq;
+	}
+
+	ret = devm_request_threaded_irq(dev, irq, dw_hdmi_5v_hard_irq_handler,
+			dw_hdmi_5v_irq_handler, IRQF_ONESHOT,
+			DW_HDMI_RX_DRVNAME "-5v-handler", dw_dev);
+	if (ret)
+		goto err_wq;
+
+	/* V4L2 initialization */
+	sd = &dw_dev->sd;
+	v4l2_subdev_init(sd, &dw_hdmi_sd_ops);
+	strlcpy(sd->name, DW_HDMI_RX_DRVNAME, sizeof(sd->name));
+	sd->internal_ops = &dw_hdmi_internal_ops;
+	sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS;
+
+	/* V4L2 device for phy subdev */
+	strlcpy(dw_dev->v4l2_dev.name, DW_HDMI_RX_DRVNAME,
+			sizeof(dw_dev->v4l2_dev.name));
+	ret = v4l2_device_register(NULL, &dw_dev->v4l2_dev);
+	if (ret) {
+		dev_err(dev, "failed to register v4l2 device\n");
+		goto err_wq;
+	}
+
+	/* Phy loading */
+	ret = dw_hdmi_phy_init(dw_dev);
+	if (ret)
+		goto err_v4l2_dev;
+
+	/* CEC */
+	dw_dev->cec_adap = cec_allocate_adapter(&dw_hdmi_cec_adap_ops,
+			dw_dev, dev_name(dev), CEC_CAP_TRANSMIT |
+			CEC_CAP_PHYS_ADDR | CEC_CAP_LOG_ADDRS,
+			HDMI_CEC_MAX_LOG_ADDRS);
+	ret = PTR_ERR_OR_ZERO(dw_dev->cec_adap);
+	if (ret) {
+		dev_err(dev, "failed to allocate CEC adapter\n");
+		goto err_phy;
+	}
+
+	/* Fill initial format settings */
+	dw_dev->timings = timings_def;
+	dw_dev->mbus_code = MEDIA_BUS_FMT_BGR888_1X24;
+
+	/* All done */
+	dev_set_drvdata(dev, sd);
+	dw_dev->state = HDMI_STATE_POWER_OFF;
+	dw_hdmi_detect_tx_5v(dw_dev);
+	dev_info(dev, "driver probed\n");
+	return 0;
+
+err_phy:
+	dw_hdmi_phy_exit(dw_dev);
+err_v4l2_dev:
+	v4l2_device_unregister(&dw_dev->v4l2_dev);
+err_wq:
+	destroy_workqueue(dw_dev->wq);
+	return ret;
+}
+
+static int dw_hdmi_rx_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct v4l2_subdev *sd = dev_get_drvdata(dev);
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	dw_hdmi_disable_ints(dw_dev);
+	dw_hdmi_disable_hpd(dw_dev);
+	dw_hdmi_power_off(dw_dev);
+	dw_hdmi_phy_s_power(dw_dev, false);
+	flush_workqueue(dw_dev->wq);
+	destroy_workqueue(dw_dev->wq);
+	v4l2_device_unregister(&dw_dev->v4l2_dev);
+	dw_hdmi_phy_exit(dw_dev);
+	dev_info(dev, "driver removed\n");
+	return 0;
+}
+
+static struct platform_driver dw_hdmi_rx_driver = {
+	.probe = dw_hdmi_rx_probe,
+	.remove = dw_hdmi_rx_remove,
+	.driver = {
+		.name = DW_HDMI_RX_DRVNAME,
+	}
+};
+module_platform_driver(dw_hdmi_rx_driver);
diff --git a/drivers/media/platform/dwc/dw-hdmi-rx.h b/drivers/media/platform/dwc/dw-hdmi-rx.h
new file mode 100644
index 0000000..c7358a9a
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-hdmi-rx.h
@@ -0,0 +1,435 @@
+/*
+ * Synopsys Designware HDMI Receiver controller driver
+ *
+ * This Synopsys dw-hdmi-rx software and associated documentation
+ * (hereinafter the "Software") is an unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing between
+ * Synopsys and you. The Software IS NOT an item of Licensed Software or a
+ * Licensed Product under any End User Software License Agreement or
+ * Agreement for Licensed Products with Synopsys or any supplement thereto.
+ * Synopsys is a registered trademark of Synopsys, Inc. Other names included
+ * in the SOFTWARE may be the trademarks of their respective owners.
+ *
+ * The contents of this file are dual-licensed; you may select either version 2
+ * of the GNU General Public License (“GPL”) or the MIT license (“MIT”).
+ *
+ * Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
+ *
+ * THIS 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 THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __DW_HDMI_RX_H__
+#define __DW_HDMI_RX_H__
+
+#include <linux/bitops.h>
+
+/* id_hdmi Registers */
+#define HDMI_SETUP_CTRL				0x0000
+#define HDMI_PLL_LCK_STS			0x0030
+#define HDMI_CKM_EVLTM				0x0094
+#define HDMI_CKM_RESULT				0x009c
+#define HDMI_STS				0x00bc
+
+/* id_hdcp_1_4 Registers */
+#define HDMI_HDCP_CTRL				0x00c0
+#define HDMI_HDCP_SETTINGS			0x00c4
+#define HDMI_HDCP_SEED				0x00c8
+#define HDMI_HDCP_BKSV1				0x00cc
+#define HDMI_HDCP_BKSV0				0x00d0
+#define HDMI_HDCP_KIDX				0x00d4
+#define HDMI_HDCP_KEY1				0x00d8
+#define HDMI_HDCP_KEY0				0x00dc
+#define HDMI_HDCP_DBG				0x00e0
+#define HDMI_HDCP_AKSV1				0x00e4
+#define HDMI_HDCP_AKSV0				0x00e8
+#define HDMI_HDCP_AN1				0x00ec
+#define HDMI_HDCP_AN0				0x00f0
+#define HDMI_HDCP_EESS_WOO			0x00f4
+#define HDMI_HDCP_I2C_TIMEOUT			0x00f8
+#define HDMI_HDCP_STS				0x00fc
+
+/* id_mode_detection Registers */
+#define HDMI_MD_HT0				0x0148
+#define HDMI_MD_HT1				0x014c
+#define HDMI_MD_HACT_PX				0x0150
+#define HDMI_MD_VCTRL				0x0158
+#define HDMI_MD_VOL				0x0164
+#define HDMI_MD_VAL				0x0168
+#define HDMI_MD_VTL				0x0170
+#define HDMI_MD_STS				0x0180
+
+/* id_phy_configuration Registers */
+#define HDMI_PHY_CTRL				0x02c0
+#define HDMI_PHY_JTAG_CONF			0x02ec
+#define HDMI_PHY_JTAG_TAP_TCLK			0x02f0
+#define HDMI_PHY_JTAG_TAP_IN			0x02f4
+#define HDMI_PHY_JTAG_TAP_OUT			0x02f8
+#define HDMI_PHY_JTAG_ADDR			0x02fc
+
+/* id_packet_decoder Registers */
+#define HDMI_PDEC_STS				0x0360
+#define HDMI_PDEC_VSI_PAYLOAD0			0x0368
+#define HDMI_PDEC_AVI_PB			0x03a4
+
+/* id_hdmi_2_0 Registers */
+#define HDMI_HDCP22_CONTROL			0x081c
+#define HDMI_HDCP22_STATUS			0x08fc
+
+/* id_audio_and_cec_interrupt Registers */
+#define HDMI_AUD_CEC_IEN_CLR			0x0f90
+#define HDMI_AUD_CEC_IEN_SET			0x0f94
+#define HDMI_AUD_CEC_ISTS			0x0f98
+#define HDMI_AUD_CEC_IEN			0x0f9c
+#define HDMI_AUD_CEC_ICLR			0x0fa0
+#define HDMI_AUD_CEC_ISET			0x0fa4
+
+/* id_mode_detection_interrupt Registers */
+#define HDMI_MD_IEN_CLR				0x0fc0
+#define HDMI_MD_IEN_SET				0x0fc4
+#define HDMI_MD_ISTS				0x0fc8
+#define HDMI_MD_IEN				0x0fcc
+#define HDMI_MD_ICLR				0x0fd0
+#define HDMI_MD_ISET				0x0fd4
+
+/* id_hdmi_interrupt Registers */
+#define HDMI_IEN_CLR				0x0fd8
+#define HDMI_IEN_SET				0x0fdc
+#define HDMI_ISTS				0x0fe0
+#define HDMI_IEN				0x0fe4
+#define HDMI_ICLR				0x0fe8
+#define HDMI_ISET				0x0fec
+
+/* id_dmi Registers */
+#define HDMI_DMI_SW_RST				0x0ff0
+
+/* id_cec Registers */
+#define HDMI_CEC_CTRL				0x1f00
+#define HDMI_CEC_MASK				0x1f08
+#define HDMI_CEC_ADDR_L				0x1f14
+#define HDMI_CEC_ADDR_H				0x1f18
+#define HDMI_CEC_TX_CNT				0x1f1c
+#define HDMI_CEC_RX_CNT				0x1f20
+#define HDMI_CEC_TX_DATA(i)			(0x1f40 + ((i) * 4))
+#define HDMI_CEC_TX_DATA_MAX			16
+#define HDMI_CEC_RX_DATA(i)			(0x1f80 + ((i) * 4))
+#define HDMI_CEC_RX_DATA_MAX			16
+#define HDMI_CEC_LOCK				0x1fc0
+#define HDMI_CEC_WAKEUPCTRL			0x1fc4
+
+/* id_cbus Registers */
+#define HDMI_CBUSIOCTRL				0x3020
+
+enum {
+	/* SETUP_CTRL field values */
+	HDMI_SETUP_CTRL_HOT_PLUG_DETECT_INPUT_X_MASK = GENMASK(27,24),
+	HDMI_SETUP_CTRL_HOT_PLUG_DETECT_INPUT_X_OFFSET = 24,
+	HDMI_SETUP_CTRL_HDMIBUS_RESET_OVR_EN_MASK = BIT(21),
+	HDMI_SETUP_CTRL_HDMIBUS_RESET_OVR_EN_OFFSET = 21,
+	HDMI_SETUP_CTRL_BUS_RESET_OVR_MASK = BIT(20),
+	HDMI_SETUP_CTRL_BUS_RESET_OVR_OFFSET = 20,
+	HDMI_SETUP_CTRL_HDMI_RESET_OVR_MASK = BIT(19),
+	HDMI_SETUP_CTRL_HDMI_RESET_OVR_OFFSET = 19,
+	HDMI_SETUP_CTRL_PON_RESET_OVR_MASK = BIT(18),
+	HDMI_SETUP_CTRL_PON_RESET_OVR_OFFSET = 18,
+	HDMI_SETUP_CTRL_RESET_OVR_MASK = BIT(17),
+	HDMI_SETUP_CTRL_RESET_OVR_OFFSET = 17,
+	HDMI_SETUP_CTRL_RESET_OVR_EN_MASK = BIT(16),
+	HDMI_SETUP_CTRL_RESET_OVR_EN_OFFSET = 16,
+	HDMI_SETUP_CTRL_EQ_OSM_OVR_MASK = BIT(15),
+	HDMI_SETUP_CTRL_EQ_OSM_OVR_OFFSET = 15,
+	HDMI_SETUP_CTRL_EQ_OSM_OVR_EN_MASK = BIT(14),
+	HDMI_SETUP_CTRL_EQ_OSM_OVR_EN_OFFSET = 14,
+	HDMI_SETUP_CTRL_NOWAIT_ACTIVITY_MASK = BIT(13),
+	HDMI_SETUP_CTRL_NOWAIT_ACTIVITY_OFFSET = 13,
+	HDMI_SETUP_CTRL_EQ_CAL_TIME_MASK = GENMASK(12,7),
+	HDMI_SETUP_CTRL_EQ_CAL_TIME_OFFSET = 7,
+	HDMI_SETUP_CTRL_USE_PLL_LOCK_MASK = BIT(6),
+	HDMI_SETUP_CTRL_USE_PLL_LOCK_OFFSET = 6,
+	HDMI_SETUP_CTRL_FORCE_STATE_MASK = BIT(5),
+	HDMI_SETUP_CTRL_FORCE_STATE_OFFSET = 5,
+	HDMI_SETUP_CTRL_TARGET_STATE_MASK = GENMASK(4,1),
+	HDMI_SETUP_CTRL_TARGET_STATE_OFFSET = 1,
+	HDMI_SETUP_CTRL_HOT_PLUG_DETECT_MASK = BIT(0),
+	HDMI_SETUP_CTRL_HOT_PLUG_DETECT_OFFSET = 0,
+	/* PLL_LCK_STS field values */
+	HDMI_PLL_LCK_STS_PLL_LOCKED = BIT(0),
+	/* CKM_EVLTM field values */
+	HDMI_CKM_EVLTM_LOCK_HYST_MASK = GENMASK(21,20),
+	HDMI_CKM_EVLTM_LOCK_HYST_OFFSET = 20,
+	HDMI_CKM_EVLTM_CLK_HYST_MASK = GENMASK(18,16),
+	HDMI_CKM_EVLTM_CLK_HYST_OFFSET = 16,
+	HDMI_CKM_EVLTM_EVAL_TIME_MASK = GENMASK(15,4),
+	HDMI_CKM_EVLTM_EVAL_TIME_OFFSET = 4,
+	HDMI_CKM_EVLTM_CLK_MEAS_INPUT_SRC_MASK = BIT(0),
+	HDMI_CKM_EVLTM_CLK_MEAS_INPUT_SRC_OFFSET = 0,
+	/* CKM_RESULT field values */
+	HDMI_CKM_RESULT_CLOCK_IN_RANGE = BIT(17),
+	HDMI_CKM_RESULT_FREQ_LOCKED = BIT(16),
+	HDMI_CKM_RESULT_CLKRATE_MASK = GENMASK(15,0),
+	HDMI_CKM_RESULT_CLKRATE_OFFSET = 0,
+	/* STS field values */
+	HDMI_STS_DCM_CURRENT_MODE_MASK = GENMASK(31,28),
+	HDMI_STS_DCM_CURRENT_MODE_OFFSET = 28,
+	HDMI_STS_DCM_LAST_PIXEL_PHASE_STS_MASK = GENMASK(27,24),
+	HDMI_STS_DCM_LAST_PIXEL_PHASE_STS_OFFSET = 24,
+	HDMI_STS_DCM_PHASE_DIFF_CNT_MASK = GENMASK(23,16),
+	HDMI_STS_DCM_PH_DIFF_CNT_OVERFL = BIT(15),
+	HDMI_STS_DCM_GCP_ZERO_FIELDS_PASS = BIT(14),
+	HDMI_STS_CTL3_STS = BIT(13),
+	HDMI_STS_CTL2_STS = BIT(12),
+	HDMI_STS_CTL1_STS = BIT(11),
+	HDMI_STS_CTL0_STS = BIT(10),
+	HDMI_STS_VS_POL_ADJ_STS = BIT(9),
+	HDMI_STS_HS_POL_ADJ_STS = BIT(8),
+	HDMI_STS_RES_OVERLOAD_STS = BIT(7),
+	HDMI_STS_DCM_CURRENT_PP_MASK = GENMASK(3,0),
+	HDMI_STS_DCM_CURRENT_PP_OFFSET = 0,
+	/* HDCP_CTRL field values */
+	HDMI_HDCP_CTRL_ENDISLOCK_MASK = BIT(25),
+	HDMI_HDCP_CTRL_ENDISLOCK_OFFSET = 25,
+	HDMI_HDCP_CTRL_ENABLE_MASK = BIT(24),
+	HDMI_HDCP_CTRL_ENABLE_OFFSET = 24,
+	HDMI_HDCP_CTRL_FREEZE_HDCP_FSM_MASK = BIT(21),
+	HDMI_HDCP_CTRL_FREEZE_HDCP_FSM_OFFSET = 21,
+	HDMI_HDCP_CTRL_FREEZE_HDCP_STATE_MASK = GENMASK(20,15),
+	HDMI_HDCP_CTRL_FREEZE_HDCP_STATE_OFFSET = 15,
+	HDMI_HDCP_CTRL_VID_DE_MASK = BIT(14),
+	HDMI_HDCP_CTRL_VID_DE_OFFSET = 14,
+	HDMI_HDCP_CTRL_SEL_AVMUTE_MASK = GENMASK(11,10),
+	HDMI_HDCP_CTRL_SEL_AVMUTE_OFFSET = 10,
+	HDMI_HDCP_CTRL_CTL_MASK = GENMASK(9,8),
+	HDMI_HDCP_CTRL_CTL_OFFSET = 8,
+	HDMI_HDCP_CTRL_RI_RATE_MASK = GENMASK(7,6),
+	HDMI_HDCP_CTRL_RI_RATE_OFFSET = 6,
+	HDMI_HDCP_CTRL_HDMI_MODE_ENABLE_MASK = BIT(2),
+	HDMI_HDCP_CTRL_HDMI_MODE_ENABLE_OFFSET = 2,
+	HDMI_HDCP_CTRL_KEY_DECRYPT_ENABLE_MASK = BIT(1),
+	HDMI_HDCP_CTRL_KEY_DECRYPT_ENABLE_OFFSET = 1,
+	HDMI_HDCP_CTRL_ENC_EN_MASK = BIT(0),
+	HDMI_HDCP_CTRL_ENC_EN_OFFSET = 0,
+	/* HDCP_SEED field values */
+	HDMI_HDCP_SEED_KEY_DECRYPT_SEED_MASK = GENMASK(15,0),
+	HDMI_HDCP_SEED_KEY_DECRYPT_SEED_OFFSET = 0,
+	/* HDCP_STS field values */
+	HDMI_HDCP_STS_ENC_STATE = BIT(9),
+	HDMI_HDCP_STS_AUTH_START = BIT(8),
+	HDMI_HDCP_STS_KEY_WR_OK = BIT(0),
+	/* MD_HT0 field values */
+	HDMI_MD_HT0_HTOT32_CLK_MASK = GENMASK(31,16),
+	HDMI_MD_HT0_HTOT32_CLK_OFFSET = 16,
+	HDMI_MD_HT0_HS_CLK_MASK = GENMASK(15,0),
+	HDMI_MD_HT0_HS_CLK_OFFSET = 0,
+	/* MD_HT1 field values */
+	HDMI_MD_HT1_HTOT_PIX_MASK = GENMASK(31,16),
+	HDMI_MD_HT1_HTOT_PIX_OFFSET = 16,
+	HDMI_MD_HT1_HOFS_PIX_MASK = GENMASK(15,0),
+	HDMI_MD_HT1_HOFS_PIX_OFFSET = 0,
+	/* MD_VCTRL field values */
+	HDMI_MD_VCTRL_V_OFFS_LIN_MODE_MASK = BIT(4),
+	HDMI_MD_VCTRL_V_OFFS_LIN_MODE_OFFSET = 4,
+	HDMI_MD_VCTRL_V_EDGE_MASK = BIT(1),
+	HDMI_MD_VCTRL_V_EDGE_OFFSET = 1,
+	HDMI_MD_VCTRL_V_MODE_MASK = BIT(0),
+	HDMI_MD_VCTRL_V_MODE_OFFSET = 0,
+	/* MD_STS field values */
+	HDMI_MD_STS_ILACE = BIT(3),
+	HDMI_MD_STS_DE_ACTIVITY = BIT(2),
+	HDMI_MD_STS_VS_ACT = BIT(1),
+	HDMI_MD_STS_HS_ACT = BIT(0),
+	/* PHY_CTRL field values */
+	HDMI_PHY_CTRL_SVSRETMODEZ_MASK = BIT(6),
+	HDMI_PHY_CTRL_SVSRETMODEZ_OFFSET = 6,
+	HDMI_PHY_CTRL_CFGCLKFREQ_MASK = GENMASK(5,4),
+	HDMI_PHY_CTRL_CFGCLKFREQ_OFFSET = 4,
+	HDMI_PHY_CTRL_PORTSELECT_MASK = GENMASK(3,2),
+	HDMI_PHY_CTRL_PORTSELECT_OFFSET = 2,
+	HDMI_PHY_CTRL_PDDQ_MASK = BIT(1),
+	HDMI_PHY_CTRL_PDDQ_OFFSET = 1,
+	HDMI_PHY_CTRL_RESET_MASK = BIT(0),
+	HDMI_PHY_CTRL_RESET_OFFSET = 0,
+	/* PHY_JTAG_TAP_IN field values */
+	HDMI_PHY_JTAG_TAP_IN_TMS = BIT(4),
+	HDMI_PHY_JTAG_TAP_IN_TDI = BIT(0),
+	/* PDEC_STS field values */
+	HDMI_PDEC_STS_DRM_CKS_CHG = BIT(31),
+	HDMI_PDEC_STS_DRM_RCV = BIT(30),
+	HDMI_PDEC_STS_NTSCVBI_CKS_CHG = BIT(29),
+	HDMI_PDEC_STS_DVIDET = BIT(28),
+	HDMI_PDEC_STS_VSI_CKS_CHG = BIT(27),
+	HDMI_PDEC_STS_GMD_CKS_CHG = BIT(26),
+	HDMI_PDEC_STS_AIF_CKS_CHG = BIT(25),
+	HDMI_PDEC_STS_AVI_CKS_CHG = BIT(24),
+	HDMI_PDEC_STS_ACR_N_CHG = BIT(23),
+	HDMI_PDEC_STS_ACR_CTS_CHG = BIT(22),
+	HDMI_PDEC_STS_GCP_AV_MUTE_CHG = BIT(21),
+	HDMI_PDEC_STS_GMD_RCV = BIT(20),
+	HDMI_PDEC_STS_AIF_RCV = BIT(19),
+	HDMI_PDEC_STS_AVI_RCV = BIT(18),
+	HDMI_PDEC_STS_ACR_RCV = BIT(17),
+	HDMI_PDEC_STS_GCP_RCV = BIT(16),
+	HDMI_PDEC_STS_VSI_RCV = BIT(15),
+	HDMI_PDEC_STS_AMP_RCV = BIT(14),
+	HDMI_PDEC_STS_NTSCVBI_RCV = BIT(13),
+	HDMI_PDEC_STS_OBA_LAYOUT = BIT(12),
+	HDMI_PDEC_STS_AUDS_LAYOUT = BIT(11),
+	HDMI_PDEC_STS_PD_FIFO_NEW_ENTRY = BIT(8),
+	HDMI_PDEC_STS_PD_FIFO_OVERFL = BIT(4),
+	HDMI_PDEC_STS_PD_FIFO_UNDERFL = BIT(3),
+	HDMI_PDEC_STS_PD_FIFO_TH_START_PASS = BIT(2),
+	HDMI_PDEC_STS_PD_FIFO_TH_MAX_PASS = BIT(1),
+	HDMI_PDEC_STS_PD_FIFO_TH_MIN_PASS = BIT(0),
+	/* PDEC_VSI_PAYLOAD0 field values */
+	HDMI_PDEC_VSI_PAYLOAD0_HDMI_VIC_MASK = GENMASK(15,8),
+	HDMI_PDEC_VSI_PAYLOAD0_HDMI_VIC_OFFSET = 8,
+	/* PDEC_AVI_PB field values */
+	HDMI_PDEC_AVI_PB_VID_IDENT_CODE_MASK = GENMASK(31,24),
+	HDMI_PDEC_AVI_PB_VID_IDENT_CODE_OFFSET = 24,
+	HDMI_PDEC_AVI_PB_IT_CONTENT = BIT(23),
+	HDMI_PDEC_AVI_PB_EXT_COLORIMETRY_MASK = GENMASK(22,20),
+	HDMI_PDEC_AVI_PB_EXT_COLORIMETRY_OFFSET = 20,
+	HDMI_PDEC_AVI_PB_RGB_QUANT_RANGE_MASK = GENMASK(19,18),
+	HDMI_PDEC_AVI_PB_RGB_QUANT_RANGE_OFFSET = 18,
+	HDMI_PDEC_AVI_PB_NON_UNIF_SCALE_MASK = GENMASK(17,16),
+	HDMI_PDEC_AVI_PB_NON_UNIF_SCALE_OFFSET = 16,
+	HDMI_PDEC_AVI_PB_COLORIMETRY_MASK = GENMASK(15,14),
+	HDMI_PDEC_AVI_PB_COLORIMETRY_OFFSET = 14,
+	HDMI_PDEC_AVI_PB_PIC_ASPECT_RAT_MASK = GENMASK(13,12),
+	HDMI_PDEC_AVI_PB_PIC_ASPECT_RAT_OFFSET = 12,
+	HDMI_PDEC_AVI_PB_ACT_ASPECT_RAT_MASK = GENMASK(11,8),
+	HDMI_PDEC_AVI_PB_ACT_ASPECT_RAT_OFFSET = 8,
+	HDMI_PDEC_AVI_PB_VIDEO_FORMAT_MASK = GENMASK(7,5),
+	HDMI_PDEC_AVI_PB_VIDEO_FORMAT_OFFSET = 5,
+	HDMI_PDEC_AVI_PB_ACT_INFO_PRESENT = BIT(4),
+	HDMI_PDEC_AVI_PB_BAR_INFO_VALID_MASK = GENMASK(3,2),
+	HDMI_PDEC_AVI_PB_BAR_INFO_VALID_OFFSET = 2,
+	HDMI_PDEC_AVI_PB_SCAN_INFO_MASK = GENMASK(1,0),
+	HDMI_PDEC_AVI_PB_SCAN_INFO_OFFSET = 0,
+	/* HDCP22_CONTROL field values */
+	HDMI_HDCP22_CONTROL_CD_OVR_VAL_MASK = GENMASK(23,20),
+	HDMI_HDCP22_CONTROL_CD_OVR_VAL_OFFSET = 20,
+	HDMI_HDCP22_CONTROL_CD_OVR_EN_MASK = BIT(16),
+	HDMI_HDCP22_CONTROL_CD_OVR_EN_OFFSET = 16,
+	HDMI_HDCP22_CONTROL_HPD_MASK = BIT(12),
+	HDMI_HDCP22_CONTROL_HPD_OFFSET = 12,
+	HDMI_HDCP22_CONTROL_PKT_ERR_OVR_VAL_MASK = BIT(9),
+	HDMI_HDCP22_CONTROL_PKT_ERR_OVR_VAL_OFFSET= 9,
+	HDMI_HDCP22_CONTROL_PKT_ERR_OVR_EN_MASK = BIT(8),
+	HDMI_HDCP22_CONTROL_PKT_ERR_OVR_EN_OFFSET = 8,
+	HDMI_HDCP22_CONTROL_AVMUTE_OVR_VAL_MASK = BIT(5),
+	HDMI_HDCP22_CONTROL_AVMUTE_OVR_VAL_OFFSET = 5,
+	HDMI_HDCP22_CONTROL_AVMUTE_OVR_EN_MASK = BIT(4),
+	HDMI_HDCP22_CONTROL_AVMUTE_OVR_EN_OFFSET = 4,
+	HDMI_HDCP22_CONTROL_OVR_VAL_MASK = BIT(2),
+	HDMI_HDCP22_CONTROL_OVR_VAL_OFFSET = 2,
+	HDMI_HDCP22_CONTROL_OVR_EN_MASK = BIT(1),
+	HDMI_HDCP22_CONTROL_OVR_EN_OFFSET = 1,
+	HDMI_HDCP22_CONTROL_SWITCH_LCK_MASK = BIT(0),
+	HDMI_HDCP22_CONTROL_SWITCH_LCK_OFFSET = 0,
+	/* AUD_CEC_ISTS field values */
+	HDMI_AUD_CEC_ISTS_WAKEUPCTRL = BIT(22),
+	HDMI_AUD_CEC_ISTS_ERROR_FOLL = BIT(21),
+	HDMI_AUD_CEC_ISTS_ERROR_INIT = BIT(20),
+	HDMI_AUD_CEC_ISTS_ARBLST = BIT(19),
+	HDMI_AUD_CEC_ISTS_NACK = BIT(18),
+	HDMI_AUD_CEC_ISTS_EOM = BIT(17),
+	HDMI_AUD_CEC_ISTS_DONE = BIT(16),
+	HDMI_AUD_CEC_ISTS_SCK_STABLE = BIT(1),
+	HDMI_AUD_CEC_ISTS_CTSN_CNT = BIT(0),
+	/* MD_ISTS field values */
+	HDMI_MD_ISTS_VOFS_LIN = BIT(11),
+	HDMI_MD_ISTS_VTOT_LIN = BIT(10),
+	HDMI_MD_ISTS_VACT_LIN = BIT(9),
+	HDMI_MD_ISTS_VS_CLK = BIT(8),
+	HDMI_MD_ISTS_VTOT_CLK = BIT(7),
+	HDMI_MD_ISTS_HACT_PIX = BIT(6),
+	HDMI_MD_ISTS_HS_CLK = BIT(5),
+	HDMI_MD_ISTS_HTOT32_CLK = BIT(4),
+	HDMI_MD_ISTS_ILACE = BIT(3),
+	HDMI_MD_ISTS_DE_ACTIVITY = BIT(2),
+	HDMI_MD_ISTS_VS_ACT = BIT(1),
+	HDMI_MD_ISTS_HS_ACT = BIT(0),
+	/* ISTS field values */
+	HDMI_ISTS_I2CMP_ARBLOST = BIT(30),
+	HDMI_ISTS_I2CMPNACK = BIT(29),
+	HDMI_ISTS_I2CMPDONE = BIT(28),
+	HDMI_ISTS_VS_THR_REACHED = BIT(27),
+	HDMI_ISTS_VSYNC_ACT_EDGE = BIT(26),
+	HDMI_ISTS_AKSV_RCV = BIT(25),
+	HDMI_ISTS_PLL_CLOCK_GATED = BIT(24),
+	HDMI_ISTS_DESER_MISAL = BIT(23),
+	HDMI_ISTS_CDSENSE_CHG = BIT(22),
+	HDMI_ISTS_CEAVID_EMPTY = BIT(21),
+	HDMI_ISTS_CEAVID_FULL = BIT(20),
+	HDMI_ISTS_SCDCTMDSCFGCHANGE = BIT(19),
+	HDMI_ISTS_SCDCSCSTATUSCHANGE = BIT(18),
+	HDMI_ISTS_SCDCCFGCHANGE = BIT(17),
+	HDMI_ISTS_DCM_CURRENT_MODE_CHG = BIT(16),
+	HDMI_ISTS_DCM_PH_DIFF_CNT_OVERFL = BIT(15),
+	HDMI_ISTS_DCM_GCP_ZERO_FIELDS_PASS = BIT(14),
+	HDMI_ISTS_CTL3_CHANGE = BIT(13),
+	HDMI_ISTS_CTL2_CHANGE = BIT(12),
+	HDMI_ISTS_CTL1_CHANGE = BIT(11),
+	HDMI_ISTS_CTL0_CHANGE = BIT(10),
+	HDMI_ISTS_VS_POL_ADJ = BIT(9),
+	HDMI_ISTS_HS_POL_ADJ = BIT(8),
+	HDMI_ISTS_RES_OVERLOAD = BIT(7),
+	HDMI_ISTS_CLK_CHANGE = BIT(6),
+	HDMI_ISTS_PLL_LCK_CHG = BIT(5),
+	HDMI_ISTS_EQGAIN_DONE = BIT(4),
+	HDMI_ISTS_OFFSCAL_DONE = BIT(3),
+	HDMI_ISTS_RESCAL_DONE = BIT(2),
+	HDMI_ISTS_ACT_CHANGE = BIT(1),
+	HDMI_ISTS_STATE_REACHED = BIT(0),
+	/* DMI_SW_RST field values */
+	HDMI_DMI_SW_RST_TMDS = BIT(16),
+	HDMI_DMI_SW_RST_HDCP = BIT(8),
+	HDMI_DMI_SW_RST_VID = BIT(7),
+	HDMI_DMI_SW_RST_PIXEL = BIT(6),
+	HDMI_DMI_SW_RST_CEC = BIT(5),
+	HDMI_DMI_SW_RST_AUD = BIT(4),
+	HDMI_DMI_SW_RST_BUS = BIT(3),
+	HDMI_DMI_SW_RST_HDMI = BIT(2),
+	HDMI_DMI_SW_RST_MODET = BIT(1),
+	HDMI_DMI_SW_RST_MAIN = BIT(0),
+	/* CEC_CTRL field values */
+	HDMI_CEC_CTRL_STANDBY_MASK = BIT(4),
+	HDMI_CEC_CTRL_STANDBY_OFFSET = 4,
+	HDMI_CEC_CTRL_BC_NACK_MASK = BIT(3),
+	HDMI_CEC_CTRL_BC_NACK_OFFSET = 3,
+	HDMI_CEC_CTRL_FRAME_TYP_MASK = GENMASK(2,1),
+	HDMI_CEC_CTRL_FRAME_TYP_OFFSET = 1,
+	HDMI_CEC_CTRL_SEND_MASK = BIT(0),
+	HDMI_CEC_CTRL_SEND_OFFSET = 0,
+	/* CEC_MASK field values */
+	HDMI_CEC_MASK_WAKEUP_MASK = BIT(6),
+	HDMI_CEC_MASK_WAKEUP_OFFSET = 6,
+	HDMI_CEC_MASK_ERROR_FLOW_MASK = BIT(5),
+	HDMI_CEC_MASK_ERROR_FLOW_OFFSET = 5,
+	HDMI_CEC_MASK_ERROR_INITITATOR_MASK = BIT(4),
+	HDMI_CEC_MASK_ERROR_INITITATOR_OFFSET = 4,
+	HDMI_CEC_MASK_ARB_LOST_MASK = BIT(3),
+	HDMI_CEC_MASK_ARB_LOST_OFFSET = 3,
+	HDMI_CEC_MASK_NACK_MASK = BIT(2),
+	HDMI_CEC_MASK_NACK_OFFSET = 2,
+	HDMI_CEC_MASK_EOM_MASK = BIT(1),
+	HDMI_CEC_MASK_EOM_OFFSET = 1,
+	HDMI_CEC_MASK_DONE_MASK = BIT(0),
+	HDMI_CEC_MASK_DONE_OFFSET = 0,
+	/* CBUSIOCTRL field values */
+	HDMI_CBUSIOCTRL_DATAPATH_CBUSZ_MASK = BIT(24),
+	HDMI_CBUSIOCTRL_DATAPATH_CBUSZ_OFFSET = 24,
+	HDMI_CBUSIOCTRL_SVSRETMODEZ_MASK = BIT(16),
+	HDMI_CBUSIOCTRL_SVSRETMODEZ_OFFSET = 16,
+	HDMI_CBUSIOCTRL_PDDQ_MASK = BIT(8),
+	HDMI_CBUSIOCTRL_PDDQ_OFFSET = 8,
+	HDMI_CBUSIOCTRL_RESET_MASK = BIT(0),
+	HDMI_CBUSIOCTRL_RESET_OFFSET = 0,
+};
+
+#endif /* __DW_HDMI_RX_H__ */
diff --git a/include/media/dwc/dw-hdmi-rx-pdata.h b/include/media/dwc/dw-hdmi-rx-pdata.h
new file mode 100644
index 0000000..ff8554d
--- /dev/null
+++ b/include/media/dwc/dw-hdmi-rx-pdata.h
@@ -0,0 +1,63 @@
+/*
+ * Synopsys Designware HDMI Receiver controller platform data
+ *
+ * This Synopsys dw-hdmi-rx software and associated documentation
+ * (hereinafter the "Software") is an unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing between
+ * Synopsys and you. The Software IS NOT an item of Licensed Software or a
+ * Licensed Product under any End User Software License Agreement or
+ * Agreement for Licensed Products with Synopsys or any supplement thereto.
+ * Synopsys is a registered trademark of Synopsys, Inc. Other names included
+ * in the SOFTWARE may be the trademarks of their respective owners.
+ *
+ * The contents of this file are dual-licensed; you may select either version 2
+ * of the GNU General Public License (“GPL”) or the MIT license (“MIT”).
+ *
+ * Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
+ *
+ * THIS 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 THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __DW_HDMI_RX_PDATA_H__
+#define __DW_HDMI_RX_PDATA_H__
+
+#define DW_HDMI_RX_DRVNAME			"dw-hdmi-rx"
+
+/* Notify events */
+#define DW_HDMI_NOTIFY_IS_OFF		1
+#define DW_HDMI_NOTIFY_INPUT_CHANGED	2
+#define DW_HDMI_NOTIFY_AUDIO_CHANGED	3
+#define DW_HDMI_NOTIFY_IS_STABLE	4
+
+/* HDCP 1.4 */
+#define DW_HDMI_HDCP14_BKSV_SIZE	2
+#define DW_HDMI_HDCP14_KEYS_SIZE	(2 * 40)
+
+struct dw_hdmi_hdcp14_key {
+	u32 seed;
+	u32 bksv[DW_HDMI_HDCP14_BKSV_SIZE];
+	u32 keys[DW_HDMI_HDCP14_KEYS_SIZE];
+	bool keys_valid;
+};
+
+struct dw_hdmi_rx_pdata {
+	/* Controller configuration */
+	unsigned int iref_clk; /* MHz */
+	struct dw_hdmi_hdcp14_key hdcp14_keys;
+	/* 5V sense interface */
+	bool (*dw_5v_status)(void __iomem *regs, int input);
+	void (*dw_5v_clear)(void __iomem *regs);
+	void __iomem *dw_5v_arg;
+	/* Zcal interface */
+	void (*dw_zcal_reset)(void __iomem *regs);
+	bool (*dw_zcal_done)(void __iomem *regs);
+	void __iomem *dw_zcal_arg;
+};
+
+#endif /* __DW_HDMI_RX_PDATA_H__ */
-- 
1.9.1

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

* [PATCH v2 3/4] MAINTAINERS: Add entry for Synopsys Designware HDMI drivers
  2017-06-16 10:07 [PATCH v2 0/4] Synopsys Designware HDMI Video Capture Controller + PHY Jose Abreu
  2017-06-16 10:07 ` [PATCH v2 1/4] [media] platform: Add Synopsys Designware HDMI RX PHY e405 Driver Jose Abreu
  2017-06-16 10:07 ` [PATCH v2 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver Jose Abreu
@ 2017-06-16 10:07 ` Jose Abreu
  2017-06-16 10:07 ` [PATCH v2 4/4] dt-bindings: media: Document Synopsys Designware HDMI RX Jose Abreu
  3 siblings, 0 replies; 10+ messages in thread
From: Jose Abreu @ 2017-06-16 10:07 UTC (permalink / raw)
  To: linux-media, linux-kernel, devicetree; +Cc: Jose Abreu, Carlos Palminha

Add a entry for Synopsys Designware HDMI Receivers drivers
and phys.

Signed-off-by: Jose Abreu <joabreu@synopsys.com>
Cc: Carlos Palminha <palminha@synopsys.com>
---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 053c3bd..e798040 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11294,6 +11294,13 @@ L:	netdev@vger.kernel.org
 S:	Supported
 F:	drivers/net/ethernet/synopsys/
 
+SYNOPSYS DESIGNWARE HDMI RECEIVERS AND PHY DRIVERS
+M:	Jose Abreu <joabreu@synopsys.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	drivers/media/platform/dwc/*
+F:	include/media/dwc/*
+
 SYNOPSYS DESIGNWARE I2C DRIVER
 M:	Jarkko Nikula <jarkko.nikula@linux.intel.com>
 R:	Andy Shevchenko <andriy.shevchenko@linux.intel.com>
-- 
1.9.1

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

* [PATCH v2 4/4] dt-bindings: media: Document Synopsys Designware HDMI RX
  2017-06-16 10:07 [PATCH v2 0/4] Synopsys Designware HDMI Video Capture Controller + PHY Jose Abreu
                   ` (2 preceding siblings ...)
  2017-06-16 10:07 ` [PATCH v2 3/4] MAINTAINERS: Add entry for Synopsys Designware HDMI drivers Jose Abreu
@ 2017-06-16 10:07 ` Jose Abreu
  3 siblings, 0 replies; 10+ messages in thread
From: Jose Abreu @ 2017-06-16 10:07 UTC (permalink / raw)
  To: linux-media, linux-kernel, devicetree
  Cc: Jose Abreu, Carlos Palminha, Rob Herring, Mark Rutland,
	Mauro Carvalho Chehab

Document the bindings for the Synopsys Designware HDMI RX.

Signed-off-by: Jose Abreu <joabreu@synopsys.com>
Cc: Carlos Palminha <palminha@synopsys.com>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
---
 .../devicetree/bindings/media/snps,dw-hdmi-rx.txt  | 41 ++++++++++++++++++++++
 1 file changed, 41 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.txt

diff --git a/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.txt b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.txt
new file mode 100644
index 0000000..f69eee5
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.txt
@@ -0,0 +1,41 @@
+Synopsys DesignWare HDMI RX Decoder
+===================================
+
+This document defines device tree properties for the Synopsys DesignWare HDMI
+RX Decoder (DWC HDMI RX). It doesn't constitute a device tree binding
+specification by itself but is meant to be referenced by platform-specific
+device tree bindings.
+
+When referenced from platform device tree bindings the properties defined in
+this document are defined as follows.
+
+- reg: Memory mapped base address and length of the DWC HDMI RX registers.
+
+- interrupts: Reference to the DWC HDMI RX interrupt and 5v sense interrupt.
+
+- snps,hdmi-phy-jtag-addr: JTAG address of the phy to be used.
+
+- snps,hdmi-phy-version: Version of the phy to be used.
+
+- snps,hdmi-phy-cfg-clk: Value of the cfg clk that is delivered to phy.
+
+- snps,hdmi-phy-driver: *Exact* name of the phy driver to be used.
+
+- snps,hdmi-ctl-cfg-clk: Value of the cfg clk that is delivered to the
+controller.
+
+A sample binding is now provided. The compatible string is for a wrapper driver
+which then instantiates the Synopsys Designware HDMI RX decoder driver.
+
+Example:
+
+dw_hdmi_wrapper: dw-hdmi-wrapper@0 {
+	compatible = "snps,dw-hdmi-rx-wrapper";
+	reg = <0x0 0x10000>; /* controller regbank */
+	interrupts = <1 3>; /* controller interrupt + 5v sense interrupt */
+	snps,hdmi-phy-driver = "dw-hdmi-phy-e405";
+	snps,hdmi-phy-version = <405>;
+	snps,hdmi-phy-cfg-clk = <25>; /* MHz */
+	snps,hdmi-ctl-cfg-clk = <25>; /* MHz */
+	snps,hdmi-phy-jtag-addr = /bits/ 8 <0xfc>;
+};
-- 
1.9.1

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

* Re: [PATCH v2 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver
  2017-06-16 10:07 ` [PATCH v2 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver Jose Abreu
@ 2017-06-16 12:38   ` Hans Verkuil
  2017-06-16 13:21     ` Jose Abreu
  0 siblings, 1 reply; 10+ messages in thread
From: Hans Verkuil @ 2017-06-16 12:38 UTC (permalink / raw)
  To: Jose Abreu, linux-media, linux-kernel, devicetree
  Cc: Carlos Palminha, Mauro Carvalho Chehab, Hans Verkuil

Hi Jose,

Just a quick review of the new CEC code:

On 06/16/17 12:07, Jose Abreu wrote:
> This is an initial submission for the Synopsys Designware HDMI RX
> Controller Driver. This driver interacts with a phy driver so that
> a communication between them is created and a video pipeline is
> configured.
> 
> The controller + phy pipeline can then be integrated into a fully
> featured system that can be able to receive video up to 4k@60Hz
> with deep color 48bit RGB, depending on the platform. Although,
> this initial version does not yet handle deep color modes.
> 
> This driver was implemented as a standard V4L2 subdevice and its
> main features are:
> 	- Internal state machine that reconfigures phy until the
> 	video is not stable
> 	- JTAG communication with phy
> 	- Inter-module communication with phy driver
> 	- Debug write/read ioctls
> 
> Some notes:
> 	- RX sense controller (cable connection/disconnection) must
> 	be handled by the platform wrapper as this is not integrated
> 	into the controller RTL
> 	- The same goes for EDID ROM's
> 	- ZCAL calibration is needed only in FPGA platforms, in ASIC
> 	this is not needed
> 	- The state machine is not an ideal solution as it creates a
> 	kthread but it is needed because some sources might not be
> 	very stable at sending the video (i.e. we must react
> 	accordingly).
> 
> Signed-off-by: Jose Abreu <joabreu@synopsys.com>
> Cc: Carlos Palminha <palminha@synopsys.com>
> Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
> Cc: Hans Verkuil <hans.verkuil@cisco.com>
> 
> Changes from v1:
> 	- Added support for CEC
> 	- Correct typo errors
> 	- Correctly detect interlaced video modes
> 	- Correct VIC parsing
> Changes from RFC:
> 	- Added support for HDCP 1.4
> 	- Fixup HDMI_VIC not being parsed (Hans)
> 	- Send source change signal when powering off (Hans)
> 	- Added a "wait stable delay"
> 	- Detect interlaced video modes (Hans)
> 	- Restrain g/s_register from reading/writing to HDCP regs (Hans)
> 
> Signed-off-by: Jose Abreu <joabreu@synopsys.com>
> ---
>  drivers/media/platform/dwc/Kconfig      |   10 +
>  drivers/media/platform/dwc/Makefile     |    1 +
>  drivers/media/platform/dwc/dw-hdmi-rx.c | 1764 +++++++++++++++++++++++++++++++
>  drivers/media/platform/dwc/dw-hdmi-rx.h |  435 ++++++++
>  include/media/dwc/dw-hdmi-rx-pdata.h    |   63 ++
>  5 files changed, 2273 insertions(+)
>  create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.c
>  create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.h
>  create mode 100644 include/media/dwc/dw-hdmi-rx-pdata.h
> diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
> index 361d38d..aba18af 100644
> --- a/drivers/media/platform/dwc/Kconfig
> +++ b/drivers/media/platform/dwc/Kconfig
> @@ -6,3 +6,13 @@ config VIDEO_DWC_HDMI_PHY_E405
>  
>  	  To compile this driver as a module, choose M here. The module
>  	  will be called dw-hdmi-phy-e405.
> +
> +config VIDEO_DWC_HDMI_RX
> +	tristate "Synopsys Designware HDMI Receiver driver"
> +	depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
> +	select CEC_CORE
> +	help
> +	  Support for Synopsys Designware HDMI RX controller.
> +
> +	  To compile this driver as a module, choose M here. The module
> +	  will be called dw-hdmi-rx.

Add a separate config VIDEO_DWC_HDMI_RX_CEC to explicitly enable
CEC. CEC is an optional component of HDMI and supporting CEC should
be something you select in the kernel config.

<snip>

General question: is this CEC IP the same IP as is used in the HDMI
transmitter? If so, then it is not a good idea to duplicate the
code.

If they are different, then that should be noted in the source.

> 
> +static void dw_hdmi_cec_enable_ints(struct dw_hdmi_dev *dw_dev)
> +{
> +	u32 mask = HDMI_AUD_CEC_ISTS_DONE | HDMI_AUD_CEC_ISTS_EOM |
> +		HDMI_AUD_CEC_ISTS_NACK | HDMI_AUD_CEC_ISTS_ARBLST |
> +		HDMI_AUD_CEC_ISTS_ERROR_INIT | HDMI_AUD_CEC_ISTS_ERROR_FOLL;
> +
> +	hdmi_writel(dw_dev, mask, HDMI_AUD_CEC_IEN_SET);
> +	hdmi_writel(dw_dev, 0x0, HDMI_CEC_MASK);
> +}
> +
> +static void dw_hdmi_cec_disable_ints(struct dw_hdmi_dev *dw_dev)
> +{
> +	hdmi_writel(dw_dev, ~0x0, HDMI_AUD_CEC_IEN_CLR);
> +	hdmi_writel(dw_dev, ~0x0, HDMI_CEC_MASK);
> +}
> +
> +static void dw_hdmi_cec_clear_ints(struct dw_hdmi_dev *dw_dev)
> +{
> +	hdmi_writel(dw_dev, ~0x0, HDMI_AUD_CEC_ICLR);
> +}
> +
> +static void dw_hdmi_cec_tx_raw_status(struct dw_hdmi_dev *dw_dev, u32 stat)
> +{
> +	u32 error_mask = HDMI_AUD_CEC_ISTS_NACK |
> +		HDMI_AUD_CEC_ISTS_ERROR_INIT;
> +
> +	if (hdmi_readl(dw_dev, HDMI_CEC_CTRL) & HDMI_CEC_CTRL_SEND_MASK) {
> +		dev_dbg(dw_dev->dev, "%s: tx is busy\n", __func__);
> +		return;
> +	}
> +
> +	if (stat & HDMI_AUD_CEC_ISTS_ARBLST) {
> +		dev_dbg(dw_dev->dev, "%s: arbitration lost\n", __func__);
> +		cec_transmit_done(dw_dev->cec_adap, CEC_TX_STATUS_ARB_LOST,
> +				1, 0, 0, 0);
> +		return;
> +	}
> +
> +	if (stat & HDMI_AUD_CEC_ISTS_DONE) {
> +		dev_dbg(dw_dev->dev, "%s: transmission done\n", __func__);
> +		cec_transmit_done(dw_dev->cec_adap, CEC_TX_STATUS_OK, 0, 0, 0, 0);
> +		return;
> +	}
> +
> +	if (stat & error_mask) {
> +		/* If we reached here we have an error */
> +		u8 status = 0, nack_cnt = 0, error_cnt = 0;
> +
> +		dev_dbg(dw_dev->dev, "%s: error found (stat=0x%x)\n", __func__,
> +				stat);
> +
> +		if (stat & HDMI_AUD_CEC_ISTS_NACK) {
> +			status |= CEC_TX_STATUS_NACK;
> +			nack_cnt++;
> +		}
> +
> +		if (stat & HDMI_AUD_CEC_ISTS_ERROR_INIT) {
> +			status |= CEC_TX_STATUS_ERROR;
> +			error_cnt++;
> +		}

What does it mean if you have both NACK and ERROR_INIT flags set? Is that
an error or a NACK? You have to chose here which one you pick when calling
cec_transmit_done.

> +
> +		cec_transmit_done(dw_dev->cec_adap, status, 0, nack_cnt, 0,
> +				error_cnt);
> +		return;
> +	}
> +}
> +
> +static void dw_hdmi_cec_received_msg(struct dw_hdmi_dev *dw_dev)
> +{
> +	struct cec_msg msg;
> +	u8 i;
> +
> +	msg.len = hdmi_readl(dw_dev, HDMI_CEC_RX_CNT);
> +	if (!msg.len || msg.len > HDMI_CEC_RX_DATA_MAX)
> +		return; /* its an invalid/non-existent message */

it's

> +
> +	for (i = 0; i < msg.len; i++)
> +		msg.msg[i] = hdmi_readl(dw_dev, HDMI_CEC_RX_DATA(i));
> +
> +	hdmi_writel(dw_dev, 0x0, HDMI_CEC_LOCK);
> +	cec_received_msg(dw_dev->cec_adap, &msg);
> +}
> +
> +static int dw_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
> +{
> +	struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
> +
> +	if (!dw_dev->cec_enabled_adap && enable) {
> +		hdmi_writel(dw_dev, 0x0, HDMI_CEC_LOCK);
> +		dw_hdmi_cec_enable_ints(dw_dev);
> +	} else if (dw_dev->cec_enabled_adap && !enable) {
> +		dw_hdmi_cec_disable_ints(dw_dev);
> +		dw_dev->cec_valid_addrs = 0;
> +	}
> +
> +	dw_dev->cec_enabled_adap = enable;
> +	return 0;
> +}
> +
> +static int dw_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
> +{
> +	struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
> +	int i, free_idx = HDMI_CEC_MAX_LOG_ADDRS;
> +	u32 tmp;
> +
> +	if (!dw_dev->cec_enabled_adap)
> +		return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO;

This can't be called unless the adapter is enabled. So this check
can be removed.

> +
> +	if (addr == CEC_LOG_ADDR_INVALID) {
> +		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_L);
> +		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_H);
> +		dw_dev->cec_valid_addrs = 0;
> +		return 0;
> +	}
> +
> +	for (i = 0; i < HDMI_CEC_MAX_LOG_ADDRS; i++) {
> +		bool is_valid = dw_dev->cec_valid_addrs & (1 << i);
> +
> +		if (free_idx == HDMI_CEC_MAX_LOG_ADDRS && !is_valid)
> +			free_idx = i;
> +		if (is_valid && dw_dev->cec_addr[i] == addr)
> +			return 0;
> +	}
> +
> +	if (i == HDMI_CEC_MAX_LOG_ADDRS) {
> +		i = free_idx;
> +		if (i == HDMI_CEC_MAX_LOG_ADDRS)
> +			return -ENXIO;
> +	}
> +
> +	dw_dev->cec_addr[i] = addr;
> +	dw_dev->cec_valid_addrs |= 1 << i;
> +
> +	if (i >= 8) {
> +		tmp = hdmi_readl(dw_dev, HDMI_CEC_ADDR_H);
> +		tmp |= BIT(i - 8);
> +		hdmi_writel(dw_dev, tmp, HDMI_CEC_ADDR_H);
> +	} else {
> +		tmp = hdmi_readl(dw_dev, HDMI_CEC_ADDR_L);
> +		tmp |= BIT(i);
> +		hdmi_writel(dw_dev, tmp, HDMI_CEC_ADDR_L);
> +	}

This seems overengineered. HDMI_CEC_ADDR_H/L is just a bitmask, so just set the
bit corresponding to addr. I'm not sure what the point of cec_addr[] and cec_valid_addrs
is.

> +
> +	return 0;
> +}
> +
> +static int dw_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
> +		u32 signal_free_time, struct cec_msg *msg)
> +{
> +	struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
> +	u8 len = msg->len;
> +	u32 reg;
> +	int i;
> +
> +	if (len > HDMI_CEC_TX_DATA_MAX) {
> +		dev_err(dw_dev->dev, "%s: invalid len (%d)\n", __func__, len);
> +		return -EINVAL;
> +	}

Can't happen.

> +
> +	if (hdmi_readl(dw_dev, HDMI_CEC_CTRL) & HDMI_CEC_CTRL_SEND_MASK) {
> +		dev_err(dw_dev->dev, "%s: tx is busy\n", __func__);
> +		return -EBUSY;

This should never happen, but it doesn't hurt to check.

> +	}
> +
> +	for (i = 0; i < HDMI_CEC_TX_DATA_MAX; i++)
> +		hdmi_writel(dw_dev, 0x0, HDMI_CEC_TX_DATA(i));

Why clear this? Is it necessary?

> +	for (i = 0; i < len; i++)
> +		hdmi_writel(dw_dev, msg->msg[i], HDMI_CEC_TX_DATA(i));
> +
> +	switch (signal_free_time) {
> +	case 3:
> +		reg = 0x0;
> +		break;
> +	case 7:
> +		reg = 0x2;
> +		break;
> +	case 5:

Use the defines in media/cec.h instead of hardcoding these values.

> +	default:
> +		reg = 0x1;
> +		break;
> +	}
> +
> +	hdmi_writel(dw_dev, len, HDMI_CEC_TX_CNT);
> +	hdmi_mask_writel(dw_dev, reg, HDMI_CEC_CTRL,
> +			HDMI_CEC_CTRL_FRAME_TYP_OFFSET,
> +			HDMI_CEC_CTRL_FRAME_TYP_MASK);
> +	hdmi_mask_writel(dw_dev, 0x1, HDMI_CEC_CTRL,
> +			HDMI_CEC_CTRL_SEND_OFFSET,
> +			HDMI_CEC_CTRL_SEND_MASK);
> +	return 0;
> +}
> +
> +static const struct cec_adap_ops dw_hdmi_cec_adap_ops = {
> +	.adap_enable = dw_hdmi_cec_adap_enable,
> +	.adap_log_addr = dw_hdmi_cec_adap_log_addr,
> +	.adap_transmit = dw_hdmi_cec_adap_transmit,
> +};

<snip>

> +static int dw_hdmi_log_status(struct v4l2_subdev *sd)
> +{
> +	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
> +	struct v4l2_dv_timings timings;
> +
> +	v4l2_info(sd, "--- Chip configuration ---\n");
> +	v4l2_info(sd, "cfg_clk=%dMHz\n", dw_dev->cfg_clk);
> +	v4l2_info(sd, "phy_drv=%s, phy_jtag_addr=0x%x\n", dw_dev->phy_drv,
> +			dw_dev->phy_jtag_addr);
> +
> +	v4l2_info(sd, "--- Chip status ---\n");
> +	v4l2_info(sd, "selected_input=%d: signal=%d\n", dw_dev->selected_input,
> +			has_signal(dw_dev, dw_dev->selected_input));
> +	v4l2_info(sd, "configured_input=%d: signal=%d\n",
> +			dw_dev->configured_input,
> +			has_signal(dw_dev, dw_dev->configured_input));
> +
> +	v4l2_info(sd, "--- CEC status ---\n");
> +	if (dw_dev->cec_enabled_adap) {
> +		int i;
> +
> +		for (i = 0; i < HDMI_CEC_MAX_LOG_ADDRS; i++) {
> +			bool is_valid = dw_dev->cec_valid_addrs & (1 << i);
> +
> +			if (is_valid)
> +				v4l2_info(sd, "cec_logical_address=0x%x\n",
> +						dw_dev->cec_addr[i]);
> +		}
> +	}

I'm not sure how useful this is. The same information is available through
/sys/kernel/debug/cec/cecX/status.

And just running cec-ctl gives it as well.

Up to you, though.

> +
> +	v4l2_info(sd, "--- Video status ---\n");
> +	v4l2_info(sd, "type=%s, color_depth=%dbits",
> +			hdmi_readl(dw_dev, HDMI_PDEC_STS) &
> +			HDMI_PDEC_STS_DVIDET ? "dvi" : "hdmi",
> +			dw_hdmi_get_colordepth(dw_dev));
> +
> +	v4l2_info(sd, "--- Video timings ---\n");
> +	if (dw_hdmi_query_dv_timings(sd, &timings))
> +		v4l2_info(sd, "No video detected\n");
> +	else
> +		v4l2_print_dv_timings(sd->name, "Detected format: ",
> +				&timings, true);
> +	v4l2_print_dv_timings(sd->name, "Configured format: ",
> +			&dw_dev->timings, true);
> +	return 0;
> +}
> +

<snip>

> +	/* CEC */
> +	dw_dev->cec_adap = cec_allocate_adapter(&dw_hdmi_cec_adap_ops,
> +			dw_dev, dev_name(dev), CEC_CAP_TRANSMIT |
> +			CEC_CAP_PHYS_ADDR | CEC_CAP_LOG_ADDRS,

Add CEC_CAP_RC and CEC_CAP_PASSTHROUGH.

I'm not sure about CEC_CAP_PHYS_ADDR. The problem here is that this driver
doesn't handle the EDID, but without that it doesn't know what physical
address to use.

I wonder if the cec-notifier can be used for this, possibly with adaptations.
Relying on users to set the physical address is a last resort since it is very
painful to do so. cec-notifier was specifically designed to solve this.

> +			HDMI_CEC_MAX_LOG_ADDRS);
> +	ret = PTR_ERR_OR_ZERO(dw_dev->cec_adap);
> +	if (ret) {
> +		dev_err(dev, "failed to allocate CEC adapter\n");
> +		goto err_phy;
> +	}

Regards,

	Hans

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

* Re: [PATCH v2 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver
  2017-06-16 12:38   ` Hans Verkuil
@ 2017-06-16 13:21     ` Jose Abreu
  2017-06-16 13:44       ` Hans Verkuil
  0 siblings, 1 reply; 10+ messages in thread
From: Jose Abreu @ 2017-06-16 13:21 UTC (permalink / raw)
  To: Hans Verkuil, Jose Abreu, linux-media, linux-kernel, devicetree
  Cc: Carlos Palminha, Mauro Carvalho Chehab, Hans Verkuil

Hi Hans,


On 16-06-2017 13:38, Hans Verkuil wrote:
> Hi Jose,
>
> Just a quick review of the new CEC code:

Thanks!

>
> On 06/16/17 12:07, Jose Abreu wrote:
>> This is an initial submission for the Synopsys Designware HDMI RX
>> Controller Driver. This driver interacts with a phy driver so that
>> a communication between them is created and a video pipeline is
>> configured.
>>
>> The controller + phy pipeline can then be integrated into a fully
>> featured system that can be able to receive video up to 4k@60Hz
>> with deep color 48bit RGB, depending on the platform. Although,
>> this initial version does not yet handle deep color modes.
>>
>> This driver was implemented as a standard V4L2 subdevice and its
>> main features are:
>> 	- Internal state machine that reconfigures phy until the
>> 	video is not stable
>> 	- JTAG communication with phy
>> 	- Inter-module communication with phy driver
>> 	- Debug write/read ioctls
>>
>> Some notes:
>> 	- RX sense controller (cable connection/disconnection) must
>> 	be handled by the platform wrapper as this is not integrated
>> 	into the controller RTL
>> 	- The same goes for EDID ROM's
>> 	- ZCAL calibration is needed only in FPGA platforms, in ASIC
>> 	this is not needed
>> 	- The state machine is not an ideal solution as it creates a
>> 	kthread but it is needed because some sources might not be
>> 	very stable at sending the video (i.e. we must react
>> 	accordingly).
>>
>> Signed-off-by: Jose Abreu <joabreu@synopsys.com>
>> Cc: Carlos Palminha <palminha@synopsys.com>
>> Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
>> Cc: Hans Verkuil <hans.verkuil@cisco.com>
>>
>> Changes from v1:
>> 	- Added support for CEC
>> 	- Correct typo errors
>> 	- Correctly detect interlaced video modes
>> 	- Correct VIC parsing
>> Changes from RFC:
>> 	- Added support for HDCP 1.4
>> 	- Fixup HDMI_VIC not being parsed (Hans)
>> 	- Send source change signal when powering off (Hans)
>> 	- Added a "wait stable delay"
>> 	- Detect interlaced video modes (Hans)
>> 	- Restrain g/s_register from reading/writing to HDCP regs (Hans)
>>
>> Signed-off-by: Jose Abreu <joabreu@synopsys.com>
>> ---
>>  drivers/media/platform/dwc/Kconfig      |   10 +
>>  drivers/media/platform/dwc/Makefile     |    1 +
>>  drivers/media/platform/dwc/dw-hdmi-rx.c | 1764 +++++++++++++++++++++++++++++++
>>  drivers/media/platform/dwc/dw-hdmi-rx.h |  435 ++++++++
>>  include/media/dwc/dw-hdmi-rx-pdata.h    |   63 ++
>>  5 files changed, 2273 insertions(+)
>>  create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.c
>>  create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.h
>>  create mode 100644 include/media/dwc/dw-hdmi-rx-pdata.h
>> diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
>> index 361d38d..aba18af 100644
>> --- a/drivers/media/platform/dwc/Kconfig
>> +++ b/drivers/media/platform/dwc/Kconfig
>> @@ -6,3 +6,13 @@ config VIDEO_DWC_HDMI_PHY_E405
>>  
>>  	  To compile this driver as a module, choose M here. The module
>>  	  will be called dw-hdmi-phy-e405.
>> +
>> +config VIDEO_DWC_HDMI_RX
>> +	tristate "Synopsys Designware HDMI Receiver driver"
>> +	depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
>> +	select CEC_CORE
>> +	help
>> +	  Support for Synopsys Designware HDMI RX controller.
>> +
>> +	  To compile this driver as a module, choose M here. The module
>> +	  will be called dw-hdmi-rx.
> Add a separate config VIDEO_DWC_HDMI_RX_CEC to explicitly enable
> CEC. CEC is an optional component of HDMI and supporting CEC should
> be something you select in the kernel config.

Ok, will add, but see bellow.

>
> <snip>
>
> General question: is this CEC IP the same IP as is used in the HDMI
> transmitter? If so, then it is not a good idea to duplicate the
> code.
>
> If they are different, then that should be noted in the source.

Well, at regbank they are :) I mean, CEC is not a separate IP,
its directly integrated in the controller so they probably just
reused the block in TX and in RX. CEC is optional in the
controller configuration (as well as many other functionalities).
Unfortunately there is no register in this controller that can be
used to tell the configuration that was used (i.e. tell if CEC
interface is present). I could read registers and check against
default values, but that doesn't seem very nice. Either way I
think the CEC Kconfig bool in this should be hidden from users
and just let the wrapper driver activate if needed, because the
wrapper driver will probably know if the platform has cec support
or not. Or we could add a node in the DT.

>
>> +static void dw_hdmi_cec_enable_ints(struct dw_hdmi_dev *dw_dev)
>> +{
>> +	u32 mask = HDMI_AUD_CEC_ISTS_DONE | HDMI_AUD_CEC_ISTS_EOM |
>> +		HDMI_AUD_CEC_ISTS_NACK | HDMI_AUD_CEC_ISTS_ARBLST |
>> +		HDMI_AUD_CEC_ISTS_ERROR_INIT | HDMI_AUD_CEC_ISTS_ERROR_FOLL;
>> +
>> +	hdmi_writel(dw_dev, mask, HDMI_AUD_CEC_IEN_SET);
>> +	hdmi_writel(dw_dev, 0x0, HDMI_CEC_MASK);
>> +}
>> +
>> +static void dw_hdmi_cec_disable_ints(struct dw_hdmi_dev *dw_dev)
>> +{
>> +	hdmi_writel(dw_dev, ~0x0, HDMI_AUD_CEC_IEN_CLR);
>> +	hdmi_writel(dw_dev, ~0x0, HDMI_CEC_MASK);
>> +}
>> +
>> +static void dw_hdmi_cec_clear_ints(struct dw_hdmi_dev *dw_dev)
>> +{
>> +	hdmi_writel(dw_dev, ~0x0, HDMI_AUD_CEC_ICLR);
>> +}
>> +
>> +static void dw_hdmi_cec_tx_raw_status(struct dw_hdmi_dev *dw_dev, u32 stat)
>> +{
>> +	u32 error_mask = HDMI_AUD_CEC_ISTS_NACK |
>> +		HDMI_AUD_CEC_ISTS_ERROR_INIT;
>> +
>> +	if (hdmi_readl(dw_dev, HDMI_CEC_CTRL) & HDMI_CEC_CTRL_SEND_MASK) {
>> +		dev_dbg(dw_dev->dev, "%s: tx is busy\n", __func__);
>> +		return;
>> +	}
>> +
>> +	if (stat & HDMI_AUD_CEC_ISTS_ARBLST) {
>> +		dev_dbg(dw_dev->dev, "%s: arbitration lost\n", __func__);
>> +		cec_transmit_done(dw_dev->cec_adap, CEC_TX_STATUS_ARB_LOST,
>> +				1, 0, 0, 0);
>> +		return;
>> +	}
>> +
>> +	if (stat & HDMI_AUD_CEC_ISTS_DONE) {
>> +		dev_dbg(dw_dev->dev, "%s: transmission done\n", __func__);
>> +		cec_transmit_done(dw_dev->cec_adap, CEC_TX_STATUS_OK, 0, 0, 0, 0);
>> +		return;
>> +	}
>> +
>> +	if (stat & error_mask) {
>> +		/* If we reached here we have an error */
>> +		u8 status = 0, nack_cnt = 0, error_cnt = 0;
>> +
>> +		dev_dbg(dw_dev->dev, "%s: error found (stat=0x%x)\n", __func__,
>> +				stat);
>> +
>> +		if (stat & HDMI_AUD_CEC_ISTS_NACK) {
>> +			status |= CEC_TX_STATUS_NACK;
>> +			nack_cnt++;
>> +		}
>> +
>> +		if (stat & HDMI_AUD_CEC_ISTS_ERROR_INIT) {
>> +			status |= CEC_TX_STATUS_ERROR;
>> +			error_cnt++;
>> +		}
> What does it mean if you have both NACK and ERROR_INIT flags set? Is that
> an error or a NACK? You have to chose here which one you pick when calling
> cec_transmit_done.

I don't know if these can happen simultaneously. I remember I
could provoke NACK's but I never got ERROR_INIT.

>
>> +
>> +		cec_transmit_done(dw_dev->cec_adap, status, 0, nack_cnt, 0,
>> +				error_cnt);
>> +		return;
>> +	}
>> +}
>> +
>> +static void dw_hdmi_cec_received_msg(struct dw_hdmi_dev *dw_dev)
>> +{
>> +	struct cec_msg msg;
>> +	u8 i;
>> +
>> +	msg.len = hdmi_readl(dw_dev, HDMI_CEC_RX_CNT);
>> +	if (!msg.len || msg.len > HDMI_CEC_RX_DATA_MAX)
>> +		return; /* its an invalid/non-existent message */
> it's
>
>> +
>> +	for (i = 0; i < msg.len; i++)
>> +		msg.msg[i] = hdmi_readl(dw_dev, HDMI_CEC_RX_DATA(i));
>> +
>> +	hdmi_writel(dw_dev, 0x0, HDMI_CEC_LOCK);
>> +	cec_received_msg(dw_dev->cec_adap, &msg);
>> +}
>> +
>> +static int dw_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
>> +{
>> +	struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
>> +
>> +	if (!dw_dev->cec_enabled_adap && enable) {
>> +		hdmi_writel(dw_dev, 0x0, HDMI_CEC_LOCK);
>> +		dw_hdmi_cec_enable_ints(dw_dev);
>> +	} else if (dw_dev->cec_enabled_adap && !enable) {
>> +		dw_hdmi_cec_disable_ints(dw_dev);
>> +		dw_dev->cec_valid_addrs = 0;
>> +	}
>> +
>> +	dw_dev->cec_enabled_adap = enable;
>> +	return 0;
>> +}
>> +
>> +static int dw_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
>> +{
>> +	struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
>> +	int i, free_idx = HDMI_CEC_MAX_LOG_ADDRS;
>> +	u32 tmp;
>> +
>> +	if (!dw_dev->cec_enabled_adap)
>> +		return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO;
> This can't be called unless the adapter is enabled. So this check
> can be removed.
>
>> +
>> +	if (addr == CEC_LOG_ADDR_INVALID) {
>> +		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_L);
>> +		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_H);
>> +		dw_dev->cec_valid_addrs = 0;
>> +		return 0;
>> +	}
>> +
>> +	for (i = 0; i < HDMI_CEC_MAX_LOG_ADDRS; i++) {
>> +		bool is_valid = dw_dev->cec_valid_addrs & (1 << i);
>> +
>> +		if (free_idx == HDMI_CEC_MAX_LOG_ADDRS && !is_valid)
>> +			free_idx = i;
>> +		if (is_valid && dw_dev->cec_addr[i] == addr)
>> +			return 0;
>> +	}
>> +
>> +	if (i == HDMI_CEC_MAX_LOG_ADDRS) {
>> +		i = free_idx;
>> +		if (i == HDMI_CEC_MAX_LOG_ADDRS)
>> +			return -ENXIO;
>> +	}
>> +
>> +	dw_dev->cec_addr[i] = addr;
>> +	dw_dev->cec_valid_addrs |= 1 << i;
>> +
>> +	if (i >= 8) {
>> +		tmp = hdmi_readl(dw_dev, HDMI_CEC_ADDR_H);
>> +		tmp |= BIT(i - 8);
>> +		hdmi_writel(dw_dev, tmp, HDMI_CEC_ADDR_H);
>> +	} else {
>> +		tmp = hdmi_readl(dw_dev, HDMI_CEC_ADDR_L);
>> +		tmp |= BIT(i);
>> +		hdmi_writel(dw_dev, tmp, HDMI_CEC_ADDR_L);
>> +	}
> This seems overengineered. HDMI_CEC_ADDR_H/L is just a bitmask, so just set the
> bit corresponding to addr. I'm not sure what the point of cec_addr[] and cec_valid_addrs
> is.

You mean I can only select between these:

ADDR_L:
Logical address 0 - Device TV
Logical address 1 - Recording Device 1
Logical address 2 - Recording Device 2
Logical address 3 - Tuner 1
Logical address 4 - Playback Device 1
Logical address 5 - Audio System
Logical address 6 - Tuner 2
Logical address 7 - Tuner 3

ADDR_H:
Logical address 8 - Playback Device 2
Logical address 9 - Playback Device 3
Logical address 10 - Tuner 4
Logical address 11 - Playback Device 3
Logical address 12 - Reserved
Logical address 13 - Reserved
Logical address 14 - Free use
Logical address 15 - Unregistered (as initiator address), Broadcast
(as destination address)

?

But I still need to know if I got exhausted of logical addresses,
right? (And now I realized this code is not respecting the
logical addresses allocation :/)

>
>> +
>> +	return 0;
>> +}
>> +
>> +static int dw_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
>> +		u32 signal_free_time, struct cec_msg *msg)
>> +{
>> +	struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
>> +	u8 len = msg->len;
>> +	u32 reg;
>> +	int i;
>> +
>> +	if (len > HDMI_CEC_TX_DATA_MAX) {
>> +		dev_err(dw_dev->dev, "%s: invalid len (%d)\n", __func__, len);
>> +		return -EINVAL;
>> +	}
> Can't happen.
>
>> +
>> +	if (hdmi_readl(dw_dev, HDMI_CEC_CTRL) & HDMI_CEC_CTRL_SEND_MASK) {
>> +		dev_err(dw_dev->dev, "%s: tx is busy\n", __func__);
>> +		return -EBUSY;
> This should never happen, but it doesn't hurt to check.
>
>> +	}
>> +
>> +	for (i = 0; i < HDMI_CEC_TX_DATA_MAX; i++)
>> +		hdmi_writel(dw_dev, 0x0, HDMI_CEC_TX_DATA(i));
> Why clear this? Is it necessary?
>
>> +	for (i = 0; i < len; i++)
>> +		hdmi_writel(dw_dev, msg->msg[i], HDMI_CEC_TX_DATA(i));
>> +
>> +	switch (signal_free_time) {
>> +	case 3:
>> +		reg = 0x0;
>> +		break;
>> +	case 7:
>> +		reg = 0x2;
>> +		break;
>> +	case 5:
> Use the defines in media/cec.h instead of hardcoding these values.
>
>> +	default:
>> +		reg = 0x1;
>> +		break;
>> +	}
>> +
>> +	hdmi_writel(dw_dev, len, HDMI_CEC_TX_CNT);
>> +	hdmi_mask_writel(dw_dev, reg, HDMI_CEC_CTRL,
>> +			HDMI_CEC_CTRL_FRAME_TYP_OFFSET,
>> +			HDMI_CEC_CTRL_FRAME_TYP_MASK);
>> +	hdmi_mask_writel(dw_dev, 0x1, HDMI_CEC_CTRL,
>> +			HDMI_CEC_CTRL_SEND_OFFSET,
>> +			HDMI_CEC_CTRL_SEND_MASK);
>> +	return 0;
>> +}
>> +
>> +static const struct cec_adap_ops dw_hdmi_cec_adap_ops = {
>> +	.adap_enable = dw_hdmi_cec_adap_enable,
>> +	.adap_log_addr = dw_hdmi_cec_adap_log_addr,
>> +	.adap_transmit = dw_hdmi_cec_adap_transmit,
>> +};
> <snip>
>
>> +static int dw_hdmi_log_status(struct v4l2_subdev *sd)
>> +{
>> +	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>> +	struct v4l2_dv_timings timings;
>> +
>> +	v4l2_info(sd, "--- Chip configuration ---\n");
>> +	v4l2_info(sd, "cfg_clk=%dMHz\n", dw_dev->cfg_clk);
>> +	v4l2_info(sd, "phy_drv=%s, phy_jtag_addr=0x%x\n", dw_dev->phy_drv,
>> +			dw_dev->phy_jtag_addr);
>> +
>> +	v4l2_info(sd, "--- Chip status ---\n");
>> +	v4l2_info(sd, "selected_input=%d: signal=%d\n", dw_dev->selected_input,
>> +			has_signal(dw_dev, dw_dev->selected_input));
>> +	v4l2_info(sd, "configured_input=%d: signal=%d\n",
>> +			dw_dev->configured_input,
>> +			has_signal(dw_dev, dw_dev->configured_input));
>> +
>> +	v4l2_info(sd, "--- CEC status ---\n");
>> +	if (dw_dev->cec_enabled_adap) {
>> +		int i;
>> +
>> +		for (i = 0; i < HDMI_CEC_MAX_LOG_ADDRS; i++) {
>> +			bool is_valid = dw_dev->cec_valid_addrs & (1 << i);
>> +
>> +			if (is_valid)
>> +				v4l2_info(sd, "cec_logical_address=0x%x\n",
>> +						dw_dev->cec_addr[i]);
>> +		}
>> +	}
> I'm not sure how useful this is. The same information is available through
> /sys/kernel/debug/cec/cecX/status.
>
> And just running cec-ctl gives it as well.
>
> Up to you, though.
>
>> +
>> +	v4l2_info(sd, "--- Video status ---\n");
>> +	v4l2_info(sd, "type=%s, color_depth=%dbits",
>> +			hdmi_readl(dw_dev, HDMI_PDEC_STS) &
>> +			HDMI_PDEC_STS_DVIDET ? "dvi" : "hdmi",
>> +			dw_hdmi_get_colordepth(dw_dev));
>> +
>> +	v4l2_info(sd, "--- Video timings ---\n");
>> +	if (dw_hdmi_query_dv_timings(sd, &timings))
>> +		v4l2_info(sd, "No video detected\n");
>> +	else
>> +		v4l2_print_dv_timings(sd->name, "Detected format: ",
>> +				&timings, true);
>> +	v4l2_print_dv_timings(sd->name, "Configured format: ",
>> +			&dw_dev->timings, true);
>> +	return 0;
>> +}
>> +
> <snip>
>
>> +	/* CEC */
>> +	dw_dev->cec_adap = cec_allocate_adapter(&dw_hdmi_cec_adap_ops,
>> +			dw_dev, dev_name(dev), CEC_CAP_TRANSMIT |
>> +			CEC_CAP_PHYS_ADDR | CEC_CAP_LOG_ADDRS,
> Add CEC_CAP_RC and CEC_CAP_PASSTHROUGH.
>
> I'm not sure about CEC_CAP_PHYS_ADDR. The problem here is that this driver
> doesn't handle the EDID, but without that it doesn't know what physical
> address to use.
>
> I wonder if the cec-notifier can be used for this, possibly with adaptations.
> Relying on users to set the physical address is a last resort since it is very
> painful to do so. cec-notifier was specifically designed to solve this.

Yes, EDID ROM is not integrated into the controller so I can't
add the code. How exactly can I use cec-notifier here?

Best regards,
Jose Miguel Abreu

>
>> +			HDMI_CEC_MAX_LOG_ADDRS);
>> +	ret = PTR_ERR_OR_ZERO(dw_dev->cec_adap);
>> +	if (ret) {
>> +		dev_err(dev, "failed to allocate CEC adapter\n");
>> +		goto err_phy;
>> +	}
> Regards,
>
> 	Hans

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

* Re: [PATCH v2 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver
  2017-06-16 13:21     ` Jose Abreu
@ 2017-06-16 13:44       ` Hans Verkuil
  2017-06-16 15:52         ` Jose Abreu
  0 siblings, 1 reply; 10+ messages in thread
From: Hans Verkuil @ 2017-06-16 13:44 UTC (permalink / raw)
  To: Jose Abreu, linux-media, linux-kernel, devicetree
  Cc: Carlos Palminha, Mauro Carvalho Chehab, Hans Verkuil

On 06/16/17 15:21, Jose Abreu wrote:
> Hi Hans,
> 
> 
> On 16-06-2017 13:38, Hans Verkuil wrote:
>> Hi Jose,
>>
>> Just a quick review of the new CEC code:
> 
> Thanks!
> 
>>
>> On 06/16/17 12:07, Jose Abreu wrote:
>>> This is an initial submission for the Synopsys Designware HDMI RX
>>> Controller Driver. This driver interacts with a phy driver so that
>>> a communication between them is created and a video pipeline is
>>> configured.
>>>
>>> The controller + phy pipeline can then be integrated into a fully
>>> featured system that can be able to receive video up to 4k@60Hz
>>> with deep color 48bit RGB, depending on the platform. Although,
>>> this initial version does not yet handle deep color modes.
>>>
>>> This driver was implemented as a standard V4L2 subdevice and its
>>> main features are:
>>> 	- Internal state machine that reconfigures phy until the
>>> 	video is not stable
>>> 	- JTAG communication with phy
>>> 	- Inter-module communication with phy driver
>>> 	- Debug write/read ioctls
>>>
>>> Some notes:
>>> 	- RX sense controller (cable connection/disconnection) must
>>> 	be handled by the platform wrapper as this is not integrated
>>> 	into the controller RTL
>>> 	- The same goes for EDID ROM's
>>> 	- ZCAL calibration is needed only in FPGA platforms, in ASIC
>>> 	this is not needed
>>> 	- The state machine is not an ideal solution as it creates a
>>> 	kthread but it is needed because some sources might not be
>>> 	very stable at sending the video (i.e. we must react
>>> 	accordingly).
>>>
>>> Signed-off-by: Jose Abreu <joabreu@synopsys.com>
>>> Cc: Carlos Palminha <palminha@synopsys.com>
>>> Cc: Mauro Carvalho Chehab <mchehab@kernel.org>
>>> Cc: Hans Verkuil <hans.verkuil@cisco.com>
>>>
>>> Changes from v1:
>>> 	- Added support for CEC
>>> 	- Correct typo errors
>>> 	- Correctly detect interlaced video modes
>>> 	- Correct VIC parsing
>>> Changes from RFC:
>>> 	- Added support for HDCP 1.4
>>> 	- Fixup HDMI_VIC not being parsed (Hans)
>>> 	- Send source change signal when powering off (Hans)
>>> 	- Added a "wait stable delay"
>>> 	- Detect interlaced video modes (Hans)
>>> 	- Restrain g/s_register from reading/writing to HDCP regs (Hans)
>>>
>>> Signed-off-by: Jose Abreu <joabreu@synopsys.com>
>>> ---
>>>  drivers/media/platform/dwc/Kconfig      |   10 +
>>>  drivers/media/platform/dwc/Makefile     |    1 +
>>>  drivers/media/platform/dwc/dw-hdmi-rx.c | 1764 +++++++++++++++++++++++++++++++
>>>  drivers/media/platform/dwc/dw-hdmi-rx.h |  435 ++++++++
>>>  include/media/dwc/dw-hdmi-rx-pdata.h    |   63 ++
>>>  5 files changed, 2273 insertions(+)
>>>  create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.c
>>>  create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.h
>>>  create mode 100644 include/media/dwc/dw-hdmi-rx-pdata.h
>>> diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
>>> index 361d38d..aba18af 100644
>>> --- a/drivers/media/platform/dwc/Kconfig
>>> +++ b/drivers/media/platform/dwc/Kconfig
>>> @@ -6,3 +6,13 @@ config VIDEO_DWC_HDMI_PHY_E405
>>>  
>>>  	  To compile this driver as a module, choose M here. The module
>>>  	  will be called dw-hdmi-phy-e405.
>>> +
>>> +config VIDEO_DWC_HDMI_RX
>>> +	tristate "Synopsys Designware HDMI Receiver driver"
>>> +	depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
>>> +	select CEC_CORE
>>> +	help
>>> +	  Support for Synopsys Designware HDMI RX controller.
>>> +
>>> +	  To compile this driver as a module, choose M here. The module
>>> +	  will be called dw-hdmi-rx.
>> Add a separate config VIDEO_DWC_HDMI_RX_CEC to explicitly enable
>> CEC. CEC is an optional component of HDMI and supporting CEC should
>> be something you select in the kernel config.
> 
> Ok, will add, but see bellow.
> 
>>
>> <snip>
>>
>> General question: is this CEC IP the same IP as is used in the HDMI
>> transmitter? If so, then it is not a good idea to duplicate the
>> code.
>>
>> If they are different, then that should be noted in the source.
> 
> Well, at regbank they are :) I mean, CEC is not a separate IP,
> its directly integrated in the controller so they probably just
> reused the block in TX and in RX.

I compared the registers of this driver with that of Russell's CEC
driver, and esp. the interrupt handling part is quite different.

Since that's really the meat of the CEC code, I agree that it is
OK to keep this separate.

Perhaps a comment about these differences might be useful.

> CEC is optional in the
> controller configuration (as well as many other functionalities).
> Unfortunately there is no register in this controller that can be
> used to tell the configuration that was used (i.e. tell if CEC
> interface is present). I could read registers and check against
> default values, but that doesn't seem very nice. Either way I
> think the CEC Kconfig bool in this should be hidden from users
> and just let the wrapper driver activate if needed, because the
> wrapper driver will probably know if the platform has cec support
> or not. Or we could add a node in the DT.

As long as the CEC part can be disabled if it is not needed. It
avoid having to load the cec.ko module.

> 
>>
>>> +static void dw_hdmi_cec_enable_ints(struct dw_hdmi_dev *dw_dev)
>>> +{
>>> +	u32 mask = HDMI_AUD_CEC_ISTS_DONE | HDMI_AUD_CEC_ISTS_EOM |
>>> +		HDMI_AUD_CEC_ISTS_NACK | HDMI_AUD_CEC_ISTS_ARBLST |
>>> +		HDMI_AUD_CEC_ISTS_ERROR_INIT | HDMI_AUD_CEC_ISTS_ERROR_FOLL;
>>> +
>>> +	hdmi_writel(dw_dev, mask, HDMI_AUD_CEC_IEN_SET);
>>> +	hdmi_writel(dw_dev, 0x0, HDMI_CEC_MASK);
>>> +}
>>> +
>>> +static void dw_hdmi_cec_disable_ints(struct dw_hdmi_dev *dw_dev)
>>> +{
>>> +	hdmi_writel(dw_dev, ~0x0, HDMI_AUD_CEC_IEN_CLR);
>>> +	hdmi_writel(dw_dev, ~0x0, HDMI_CEC_MASK);
>>> +}
>>> +
>>> +static void dw_hdmi_cec_clear_ints(struct dw_hdmi_dev *dw_dev)
>>> +{
>>> +	hdmi_writel(dw_dev, ~0x0, HDMI_AUD_CEC_ICLR);
>>> +}
>>> +
>>> +static void dw_hdmi_cec_tx_raw_status(struct dw_hdmi_dev *dw_dev, u32 stat)
>>> +{
>>> +	u32 error_mask = HDMI_AUD_CEC_ISTS_NACK |
>>> +		HDMI_AUD_CEC_ISTS_ERROR_INIT;
>>> +
>>> +	if (hdmi_readl(dw_dev, HDMI_CEC_CTRL) & HDMI_CEC_CTRL_SEND_MASK) {
>>> +		dev_dbg(dw_dev->dev, "%s: tx is busy\n", __func__);
>>> +		return;
>>> +	}
>>> +
>>> +	if (stat & HDMI_AUD_CEC_ISTS_ARBLST) {
>>> +		dev_dbg(dw_dev->dev, "%s: arbitration lost\n", __func__);
>>> +		cec_transmit_done(dw_dev->cec_adap, CEC_TX_STATUS_ARB_LOST,
>>> +				1, 0, 0, 0);
>>> +		return;
>>> +	}
>>> +
>>> +	if (stat & HDMI_AUD_CEC_ISTS_DONE) {
>>> +		dev_dbg(dw_dev->dev, "%s: transmission done\n", __func__);
>>> +		cec_transmit_done(dw_dev->cec_adap, CEC_TX_STATUS_OK, 0, 0, 0, 0);
>>> +		return;
>>> +	}
>>> +
>>> +	if (stat & error_mask) {
>>> +		/* If we reached here we have an error */
>>> +		u8 status = 0, nack_cnt = 0, error_cnt = 0;
>>> +
>>> +		dev_dbg(dw_dev->dev, "%s: error found (stat=0x%x)\n", __func__,
>>> +				stat);
>>> +
>>> +		if (stat & HDMI_AUD_CEC_ISTS_NACK) {
>>> +			status |= CEC_TX_STATUS_NACK;
>>> +			nack_cnt++;
>>> +		}
>>> +
>>> +		if (stat & HDMI_AUD_CEC_ISTS_ERROR_INIT) {
>>> +			status |= CEC_TX_STATUS_ERROR;
>>> +			error_cnt++;
>>> +		}
>> What does it mean if you have both NACK and ERROR_INIT flags set? Is that
>> an error or a NACK? You have to chose here which one you pick when calling
>> cec_transmit_done.
> 
> I don't know if these can happen simultaneously. I remember I
> could provoke NACK's but I never got ERROR_INIT.

I would put an else before the ERROR_INIT if. The combination makes no sense
and it would confuse the CEC framework.

> 
>>
>>> +
>>> +		cec_transmit_done(dw_dev->cec_adap, status, 0, nack_cnt, 0,
>>> +				error_cnt);
>>> +		return;
>>> +	}
>>> +}
>>> +
>>> +static void dw_hdmi_cec_received_msg(struct dw_hdmi_dev *dw_dev)
>>> +{
>>> +	struct cec_msg msg;
>>> +	u8 i;
>>> +
>>> +	msg.len = hdmi_readl(dw_dev, HDMI_CEC_RX_CNT);
>>> +	if (!msg.len || msg.len > HDMI_CEC_RX_DATA_MAX)
>>> +		return; /* its an invalid/non-existent message */
>> it's
>>
>>> +
>>> +	for (i = 0; i < msg.len; i++)
>>> +		msg.msg[i] = hdmi_readl(dw_dev, HDMI_CEC_RX_DATA(i));
>>> +
>>> +	hdmi_writel(dw_dev, 0x0, HDMI_CEC_LOCK);
>>> +	cec_received_msg(dw_dev->cec_adap, &msg);
>>> +}
>>> +
>>> +static int dw_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
>>> +{
>>> +	struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
>>> +
>>> +	if (!dw_dev->cec_enabled_adap && enable) {
>>> +		hdmi_writel(dw_dev, 0x0, HDMI_CEC_LOCK);
>>> +		dw_hdmi_cec_enable_ints(dw_dev);
>>> +	} else if (dw_dev->cec_enabled_adap && !enable) {
>>> +		dw_hdmi_cec_disable_ints(dw_dev);
>>> +		dw_dev->cec_valid_addrs = 0;
>>> +	}
>>> +
>>> +	dw_dev->cec_enabled_adap = enable;
>>> +	return 0;
>>> +}
>>> +
>>> +static int dw_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
>>> +{
>>> +	struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
>>> +	int i, free_idx = HDMI_CEC_MAX_LOG_ADDRS;
>>> +	u32 tmp;
>>> +
>>> +	if (!dw_dev->cec_enabled_adap)
>>> +		return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO;
>> This can't be called unless the adapter is enabled. So this check
>> can be removed.
>>
>>> +
>>> +	if (addr == CEC_LOG_ADDR_INVALID) {
>>> +		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_L);
>>> +		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_H);
>>> +		dw_dev->cec_valid_addrs = 0;
>>> +		return 0;
>>> +	}
>>> +
>>> +	for (i = 0; i < HDMI_CEC_MAX_LOG_ADDRS; i++) {
>>> +		bool is_valid = dw_dev->cec_valid_addrs & (1 << i);
>>> +
>>> +		if (free_idx == HDMI_CEC_MAX_LOG_ADDRS && !is_valid)
>>> +			free_idx = i;
>>> +		if (is_valid && dw_dev->cec_addr[i] == addr)
>>> +			return 0;
>>> +	}
>>> +
>>> +	if (i == HDMI_CEC_MAX_LOG_ADDRS) {
>>> +		i = free_idx;
>>> +		if (i == HDMI_CEC_MAX_LOG_ADDRS)
>>> +			return -ENXIO;
>>> +	}
>>> +
>>> +	dw_dev->cec_addr[i] = addr;
>>> +	dw_dev->cec_valid_addrs |= 1 << i;
>>> +
>>> +	if (i >= 8) {
>>> +		tmp = hdmi_readl(dw_dev, HDMI_CEC_ADDR_H);
>>> +		tmp |= BIT(i - 8);
>>> +		hdmi_writel(dw_dev, tmp, HDMI_CEC_ADDR_H);
>>> +	} else {
>>> +		tmp = hdmi_readl(dw_dev, HDMI_CEC_ADDR_L);
>>> +		tmp |= BIT(i);
>>> +		hdmi_writel(dw_dev, tmp, HDMI_CEC_ADDR_L);
>>> +	}
>> This seems overengineered. HDMI_CEC_ADDR_H/L is just a bitmask, so just set the
>> bit corresponding to addr. I'm not sure what the point of cec_addr[] and cec_valid_addrs
>> is.
> 
> You mean I can only select between these:
> 
> ADDR_L:
> Logical address 0 - Device TV
> Logical address 1 - Recording Device 1
> Logical address 2 - Recording Device 2
> Logical address 3 - Tuner 1
> Logical address 4 - Playback Device 1
> Logical address 5 - Audio System
> Logical address 6 - Tuner 2
> Logical address 7 - Tuner 3
> 
> ADDR_H:
> Logical address 8 - Playback Device 2
> Logical address 9 - Playback Device 3
> Logical address 10 - Tuner 4
> Logical address 11 - Playback Device 3
> Logical address 12 - Reserved
> Logical address 13 - Reserved
> Logical address 14 - Free use
> Logical address 15 - Unregistered (as initiator address), Broadcast
> (as destination address)
> 
> ?
> 
> But I still need to know if I got exhausted of logical addresses,
> right? (And now I realized this code is not respecting the
> logical addresses allocation :/)

You tell the framework about the maximum number of LAs that you
support simultaneously when you call cec_allocate_adapter. The framework
will take care of checking that that max is never exceeded. No need
to check again.

You're asked here to either clear the logical address mask (when INVALID
is passed in) or to activate the given logical address. So:

	if (addr == CEC_LOG_ADDR_INVALID) {
		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_L);
		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_H);
		return 0;
	}

	if (i >= 8) {
		tmp = hdmi_readl(dw_dev, HDMI_CEC_ADDR_H);
		tmp |= BIT(i - 8);
		hdmi_writel(dw_dev, tmp, HDMI_CEC_ADDR_H);
	} else {
		tmp = hdmi_readl(dw_dev, HDMI_CEC_ADDR_L);
		tmp |= BIT(i);
		hdmi_writel(dw_dev, tmp, HDMI_CEC_ADDR_L);
	}

That's all you need to do.

> 
>>
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int dw_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
>>> +		u32 signal_free_time, struct cec_msg *msg)
>>> +{
>>> +	struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
>>> +	u8 len = msg->len;
>>> +	u32 reg;
>>> +	int i;
>>> +
>>> +	if (len > HDMI_CEC_TX_DATA_MAX) {
>>> +		dev_err(dw_dev->dev, "%s: invalid len (%d)\n", __func__, len);
>>> +		return -EINVAL;
>>> +	}
>> Can't happen.
>>
>>> +
>>> +	if (hdmi_readl(dw_dev, HDMI_CEC_CTRL) & HDMI_CEC_CTRL_SEND_MASK) {
>>> +		dev_err(dw_dev->dev, "%s: tx is busy\n", __func__);
>>> +		return -EBUSY;
>> This should never happen, but it doesn't hurt to check.
>>
>>> +	}
>>> +
>>> +	for (i = 0; i < HDMI_CEC_TX_DATA_MAX; i++)
>>> +		hdmi_writel(dw_dev, 0x0, HDMI_CEC_TX_DATA(i));
>> Why clear this? Is it necessary?
>>
>>> +	for (i = 0; i < len; i++)
>>> +		hdmi_writel(dw_dev, msg->msg[i], HDMI_CEC_TX_DATA(i));
>>> +
>>> +	switch (signal_free_time) {
>>> +	case 3:
>>> +		reg = 0x0;
>>> +		break;
>>> +	case 7:
>>> +		reg = 0x2;
>>> +		break;
>>> +	case 5:
>> Use the defines in media/cec.h instead of hardcoding these values.
>>
>>> +	default:
>>> +		reg = 0x1;
>>> +		break;
>>> +	}
>>> +
>>> +	hdmi_writel(dw_dev, len, HDMI_CEC_TX_CNT);
>>> +	hdmi_mask_writel(dw_dev, reg, HDMI_CEC_CTRL,
>>> +			HDMI_CEC_CTRL_FRAME_TYP_OFFSET,
>>> +			HDMI_CEC_CTRL_FRAME_TYP_MASK);
>>> +	hdmi_mask_writel(dw_dev, 0x1, HDMI_CEC_CTRL,
>>> +			HDMI_CEC_CTRL_SEND_OFFSET,
>>> +			HDMI_CEC_CTRL_SEND_MASK);
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct cec_adap_ops dw_hdmi_cec_adap_ops = {
>>> +	.adap_enable = dw_hdmi_cec_adap_enable,
>>> +	.adap_log_addr = dw_hdmi_cec_adap_log_addr,
>>> +	.adap_transmit = dw_hdmi_cec_adap_transmit,
>>> +};
>> <snip>
>>
>>> +static int dw_hdmi_log_status(struct v4l2_subdev *sd)
>>> +{
>>> +	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
>>> +	struct v4l2_dv_timings timings;
>>> +
>>> +	v4l2_info(sd, "--- Chip configuration ---\n");
>>> +	v4l2_info(sd, "cfg_clk=%dMHz\n", dw_dev->cfg_clk);
>>> +	v4l2_info(sd, "phy_drv=%s, phy_jtag_addr=0x%x\n", dw_dev->phy_drv,
>>> +			dw_dev->phy_jtag_addr);
>>> +
>>> +	v4l2_info(sd, "--- Chip status ---\n");
>>> +	v4l2_info(sd, "selected_input=%d: signal=%d\n", dw_dev->selected_input,
>>> +			has_signal(dw_dev, dw_dev->selected_input));
>>> +	v4l2_info(sd, "configured_input=%d: signal=%d\n",
>>> +			dw_dev->configured_input,
>>> +			has_signal(dw_dev, dw_dev->configured_input));
>>> +
>>> +	v4l2_info(sd, "--- CEC status ---\n");
>>> +	if (dw_dev->cec_enabled_adap) {
>>> +		int i;
>>> +
>>> +		for (i = 0; i < HDMI_CEC_MAX_LOG_ADDRS; i++) {
>>> +			bool is_valid = dw_dev->cec_valid_addrs & (1 << i);
>>> +
>>> +			if (is_valid)
>>> +				v4l2_info(sd, "cec_logical_address=0x%x\n",
>>> +						dw_dev->cec_addr[i]);
>>> +		}
>>> +	}
>> I'm not sure how useful this is. The same information is available through
>> /sys/kernel/debug/cec/cecX/status.
>>
>> And just running cec-ctl gives it as well.
>>
>> Up to you, though.
>>
>>> +
>>> +	v4l2_info(sd, "--- Video status ---\n");
>>> +	v4l2_info(sd, "type=%s, color_depth=%dbits",
>>> +			hdmi_readl(dw_dev, HDMI_PDEC_STS) &
>>> +			HDMI_PDEC_STS_DVIDET ? "dvi" : "hdmi",
>>> +			dw_hdmi_get_colordepth(dw_dev));
>>> +
>>> +	v4l2_info(sd, "--- Video timings ---\n");
>>> +	if (dw_hdmi_query_dv_timings(sd, &timings))
>>> +		v4l2_info(sd, "No video detected\n");
>>> +	else
>>> +		v4l2_print_dv_timings(sd->name, "Detected format: ",
>>> +				&timings, true);
>>> +	v4l2_print_dv_timings(sd->name, "Configured format: ",
>>> +			&dw_dev->timings, true);
>>> +	return 0;
>>> +}
>>> +
>> <snip>
>>
>>> +	/* CEC */
>>> +	dw_dev->cec_adap = cec_allocate_adapter(&dw_hdmi_cec_adap_ops,
>>> +			dw_dev, dev_name(dev), CEC_CAP_TRANSMIT |
>>> +			CEC_CAP_PHYS_ADDR | CEC_CAP_LOG_ADDRS,
>> Add CEC_CAP_RC and CEC_CAP_PASSTHROUGH.
>>
>> I'm not sure about CEC_CAP_PHYS_ADDR. The problem here is that this driver
>> doesn't handle the EDID, but without that it doesn't know what physical
>> address to use.
>>
>> I wonder if the cec-notifier can be used for this, possibly with adaptations.
>> Relying on users to set the physical address is a last resort since it is very
>> painful to do so. cec-notifier was specifically designed to solve this.
> 
> Yes, EDID ROM is not integrated into the controller so I can't
> add the code. How exactly can I use cec-notifier here?

drivers/media/platform/sti/cec/stih-cec.c is a good example. The notifier is
called by drivers/gpu/drm/sti/sti_hdmi.c.

Regards,

	Hans

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

* Re: [PATCH v2 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver
  2017-06-16 13:44       ` Hans Verkuil
@ 2017-06-16 15:52         ` Jose Abreu
  2017-06-16 15:59           ` Hans Verkuil
  0 siblings, 1 reply; 10+ messages in thread
From: Jose Abreu @ 2017-06-16 15:52 UTC (permalink / raw)
  To: Hans Verkuil, Jose Abreu, linux-media, linux-kernel, devicetree
  Cc: Carlos Palminha, Mauro Carvalho Chehab, Hans Verkuil

Hi Hans,


On 16-06-2017 14:44, Hans Verkuil wrote:
>
>>> <snip>
>>>
>>>> +	/* CEC */
>>>> +	dw_dev->cec_adap = cec_allocate_adapter(&dw_hdmi_cec_adap_ops,
>>>> +			dw_dev, dev_name(dev), CEC_CAP_TRANSMIT |
>>>> +			CEC_CAP_PHYS_ADDR | CEC_CAP_LOG_ADDRS,
>>> Add CEC_CAP_RC and CEC_CAP_PASSTHROUGH.
>>>
>>> I'm not sure about CEC_CAP_PHYS_ADDR. The problem here is that this driver
>>> doesn't handle the EDID, but without that it doesn't know what physical
>>> address to use.
>>>
>>> I wonder if the cec-notifier can be used for this, possibly with adaptations.
>>> Relying on users to set the physical address is a last resort since it is very
>>> painful to do so. cec-notifier was specifically designed to solve this.
>> Yes, EDID ROM is not integrated into the controller so I can't
>> add the code. How exactly can I use cec-notifier here?
> drivers/media/platform/sti/cec/stih-cec.c is a good example. The notifier is
> called by drivers/gpu/drm/sti/sti_hdmi.c.

Done! Implemented and working :) I'm wondering if you want me to
wait some more time for other comments or just send the v3 now? I
also added support for SCDC read request (its a matter of
activating a bit).

BTW, I used the DT node name "hdmi-phandle" but I don't know if
it is the best because it can cause confusion about the
hdmi-phandle that you documented in media/cec.txt

Best regards,
Jose Miguel Abreu

>
> Regards,
>
> 	Hans
>

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

* Re: [PATCH v2 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver
  2017-06-16 15:52         ` Jose Abreu
@ 2017-06-16 15:59           ` Hans Verkuil
  0 siblings, 0 replies; 10+ messages in thread
From: Hans Verkuil @ 2017-06-16 15:59 UTC (permalink / raw)
  To: Jose Abreu, linux-media, linux-kernel, devicetree
  Cc: Carlos Palminha, Mauro Carvalho Chehab, Hans Verkuil

On 06/16/2017 05:52 PM, Jose Abreu wrote:
> Hi Hans,
> 
> 
> On 16-06-2017 14:44, Hans Verkuil wrote:
>>
>>>> <snip>
>>>>
>>>>> +	/* CEC */
>>>>> +	dw_dev->cec_adap = cec_allocate_adapter(&dw_hdmi_cec_adap_ops,
>>>>> +			dw_dev, dev_name(dev), CEC_CAP_TRANSMIT |
>>>>> +			CEC_CAP_PHYS_ADDR | CEC_CAP_LOG_ADDRS,
>>>> Add CEC_CAP_RC and CEC_CAP_PASSTHROUGH.
>>>>
>>>> I'm not sure about CEC_CAP_PHYS_ADDR. The problem here is that this driver
>>>> doesn't handle the EDID, but without that it doesn't know what physical
>>>> address to use.
>>>>
>>>> I wonder if the cec-notifier can be used for this, possibly with adaptations.
>>>> Relying on users to set the physical address is a last resort since it is very
>>>> painful to do so. cec-notifier was specifically designed to solve this.
>>> Yes, EDID ROM is not integrated into the controller so I can't
>>> add the code. How exactly can I use cec-notifier here?
>> drivers/media/platform/sti/cec/stih-cec.c is a good example. The notifier is
>> called by drivers/gpu/drm/sti/sti_hdmi.c.
> 
> Done! Implemented and working :) I'm wondering if you want me to
> wait some more time for other comments or just send the v3 now? I
> also added support for SCDC read request (its a matter of
> activating a bit).

Well, if it is ready, then just send a v3!

> BTW, I used the DT node name "hdmi-phandle" but I don't know if
> it is the best because it can cause confusion about the
> hdmi-phandle that you documented in media/cec.txt

I had the same thought. It doesn't really fit for CEC adapters on
HDMI receivers.

Perhaps edid-phandle? Or something else?

Regards,

	Hans

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

end of thread, other threads:[~2017-06-16 15:59 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-06-16 10:07 [PATCH v2 0/4] Synopsys Designware HDMI Video Capture Controller + PHY Jose Abreu
2017-06-16 10:07 ` [PATCH v2 1/4] [media] platform: Add Synopsys Designware HDMI RX PHY e405 Driver Jose Abreu
2017-06-16 10:07 ` [PATCH v2 2/4] [media] platform: Add Synopsys Designware HDMI RX Controller Driver Jose Abreu
2017-06-16 12:38   ` Hans Verkuil
2017-06-16 13:21     ` Jose Abreu
2017-06-16 13:44       ` Hans Verkuil
2017-06-16 15:52         ` Jose Abreu
2017-06-16 15:59           ` Hans Verkuil
2017-06-16 10:07 ` [PATCH v2 3/4] MAINTAINERS: Add entry for Synopsys Designware HDMI drivers Jose Abreu
2017-06-16 10:07 ` [PATCH v2 4/4] dt-bindings: media: Document Synopsys Designware HDMI RX Jose Abreu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).