diff mbox

[RFC,2/2,media] platform: Add Synopsys Designware HDMI RX Controller Driver

Message ID a0c0a46aa86abf87da4f6b1742114fbfc40a3963.1492767176.git.joabreu@synopsys.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jose Abreu April 21, 2017, 9:53 a.m. UTC
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. Althoug,
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>
Cc: linux-kernel@vger.kernel.org
Cc: linux-media@vger.kernel.org
---
 drivers/media/platform/dwc/Kconfig      |    9 +
 drivers/media/platform/dwc/Makefile     |    1 +
 drivers/media/platform/dwc/dw-hdmi-rx.c | 1396 +++++++++++++++++++++++++++++++
 drivers/media/platform/dwc/dw-hdmi-rx.h |  313 +++++++
 include/media/dwc/dw-hdmi-rx-pdata.h    |   50 ++
 5 files changed, 1769 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

Comments

Hans Verkuil May 22, 2017, 10:36 a.m. UTC | #1
On 04/21/2017 11:53 AM, 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. Althoug,

Typo: 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

What is ZCAL?

> 	- 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).

I can guarantee that sources can be unstable, based on years of painful experience
with HDMI receivers :-)

> 
> 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>
> Cc: linux-kernel@vger.kernel.org
> Cc: linux-media@vger.kernel.org
> ---
>  drivers/media/platform/dwc/Kconfig      |    9 +
>  drivers/media/platform/dwc/Makefile     |    1 +
>  drivers/media/platform/dwc/dw-hdmi-rx.c | 1396 +++++++++++++++++++++++++++++++
>  drivers/media/platform/dwc/dw-hdmi-rx.h |  313 +++++++
>  include/media/dwc/dw-hdmi-rx-pdata.h    |   50 ++
>  5 files changed, 1769 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..11efa23 100644
> --- a/drivers/media/platform/dwc/Kconfig
> +++ b/drivers/media/platform/dwc/Kconfig
> @@ -6,3 +6,12 @@ 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
> +	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..6bfbc86
> --- /dev/null
> +++ b/drivers/media/platform/dwc/dw-hdmi-rx.c
> @@ -0,0 +1,1396 @@
> +/*
> + * 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/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
> +
> +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, 594000000,	/* 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 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;
> +};
> +
> +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 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_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 res,
> +		bool hdmi2, bool scrambling)
> +{
> +	struct dw_phy_command cmd = {
> +		.result = 0,
> +		.res = res,
> +		.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 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_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_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 u8 dw_hdmi_get_curr_vic(struct dw_hdmi_dev *dw_dev)
> +{
> +	return 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;
> +}
> +
> +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 0;
> +	}
> +}
> +
> +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);
> +}
> +
> +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_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");
> +			v4l2_subdev_notify_event(&dw_dev->sd,
> +					&dw_hdmi_event_fmt);

Whenever you lose the signal, you also have to send this event. It informs userspace
when there is a stable signal, or when that stable signal disappears.

> +			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)
> +{
> +	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);
> +	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;
> +}
> +
> +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);
> +
> +	dw_hdmi_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);
> +	}
> +
> +	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__);
> +
> +	memset(timings, 0, sizeof(*timings));

This is pointless since *timings is overwritten by the next assignment.

> +	*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;
> +	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 = 0;

There is no interlaced support? Most receivers can at least detect it.

> +
> +	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);

Can this be rounded up to a value above 594 MHz? In the timings cap that
is the max frequency, but you probably need to allow for a bit of margin there
in case you measure e.g. 594050000 Hz.

> +
> +	/* 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);
> +	if (vic) {
> +		bt->flags |= V4L2_DV_FL_HAS_CEA861_VIC;
> +		bt->cea861_vic = vic;

You also need to handle the HDMI VIC case for 4kp30 among others.

> +	}
> +
> +	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 bool dw_hdmi_check_dv_timings(const struct v4l2_dv_timings *t, void *hdl)
> +{
> +	return true;
> +}
> +
> +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,
> +			dw_hdmi_check_dv_timings, NULL);

dw_hdmi_check_dv_timings can just be NULL here. No need to provide it if
it always returns true.

> +}
> +
> +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, "--- Video status ---\n");
> +	v4l2_info(sd, "type=%s\n, 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 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 */
> +		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 */
> +		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;
> +	}

Be careful here: if you support HDCP, then you typically don't want to allow
userspace to touch any HDCP-related registers through this API.

> +
> +	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 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 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->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;
> +
> +	/* 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_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);

<snip>

Regards,

	Hans
Jose Abreu May 23, 2017, 4:38 p.m. UTC | #2
Hi Hans,


Thanks for the review!

On 22-05-2017 11:36, Hans Verkuil wrote:
> On 04/21/2017 11:53 AM, 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. Althoug,
> Typo: 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
> What is ZCAL?

ZCAL is impedance calibration. Its is needed in our test boards.
 
>
>> 	- 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).
> I can guarantee that sources can be unstable, based on years of painful experience
> with HDMI receivers :-)
>
>> 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>
>> Cc: linux-kernel@vger.kernel.org
>> Cc: linux-media@vger.kernel.org
>> ---
>>  drivers/media/platform/dwc/Kconfig      |    9 +
>>  drivers/media/platform/dwc/Makefile     |    1 +
>>  drivers/media/platform/dwc/dw-hdmi-rx.c | 1396 +++++++++++++++++++++++++++++++
>>  drivers/media/platform/dwc/dw-hdmi-rx.h |  313 +++++++
>>  include/media/dwc/dw-hdmi-rx-pdata.h    |   50 ++
>>  5 files changed, 1769 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..11efa23 100644
>> --- a/drivers/media/platform/dwc/Kconfig
>> +++ b/drivers/media/platform/dwc/Kconfig
>> @@ -6,3 +6,12 @@ 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
>> +	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..6bfbc86
>> --- /dev/null
>> +++ b/drivers/media/platform/dwc/dw-hdmi-rx.c
>> @@ -0,0 +1,1396 @@
>> +/*
>> + * 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/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
>> +
>> +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, 594000000,	/* 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 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;
>> +};
>> +
>> +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 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_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 res,
>> +		bool hdmi2, bool scrambling)
>> +{
>> +	struct dw_phy_command cmd = {
>> +		.result = 0,
>> +		.res = res,
>> +		.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 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_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_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 u8 dw_hdmi_get_curr_vic(struct dw_hdmi_dev *dw_dev)
>> +{
>> +	return 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;
>> +}
>> +
>> +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 0;
>> +	}
>> +}
>> +
>> +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);
>> +}
>> +
>> +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_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");
>> +			v4l2_subdev_notify_event(&dw_dev->sd,
>> +					&dw_hdmi_event_fmt);
> Whenever you lose the signal, you also have to send this event. It informs userspace
> when there is a stable signal, or when that stable signal disappears.

Ok.

>
>> +			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)
>> +{
>> +	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);
>> +	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;
>> +}
>> +
>> +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);
>> +
>> +	dw_hdmi_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);
>> +	}
>> +
>> +	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__);
>> +
>> +	memset(timings, 0, sizeof(*timings));
> This is pointless since *timings is overwritten by the next assignment.
>
>> +	*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;
>> +	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 = 0;
> There is no interlaced support? Most receivers can at least detect it.

The controller supports interlaced, unfortunately there is no way
I can test it, so we chose not to add it in the driver.

>
>> +
>> +	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);
> Can this be rounded up to a value above 594 MHz? In the timings cap that
> is the max frequency, but you probably need to allow for a bit of margin there
> in case you measure e.g. 594050000 Hz.

Hmm, yeah, probably it can. Actually the timings cap may not be
correct because we support deep color in 4k, so freq will be >
594MHz.

>
>> +
>> +	/* 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);
>> +	if (vic) {
>> +		bt->flags |= V4L2_DV_FL_HAS_CEA861_VIC;
>> +		bt->cea861_vic = vic;
> You also need to handle the HDMI VIC case for 4kp30 among others.

Ok.

>
>> +	}
>> +
>> +	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 bool dw_hdmi_check_dv_timings(const struct v4l2_dv_timings *t, void *hdl)
>> +{
>> +	return true;
>> +}
>> +
>> +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,
>> +			dw_hdmi_check_dv_timings, NULL);
> dw_hdmi_check_dv_timings can just be NULL here. No need to provide it if
> it always returns true.

Ok.

>
>> +}
>> +
>> +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, "--- Video status ---\n");
>> +	v4l2_info(sd, "type=%s\n, 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 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 */
>> +		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 */
>> +		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;
>> +	}
> Be careful here: if you support HDCP, then you typically don't want to allow
> userspace to touch any HDCP-related registers through this API.

Yeah, HDCP is supported but still not implemented. Still, the
only thing the user will be able to change will be bksv because
keys can not be read, they are write only. I will add a check though.

Best regards,
Jose Miguel Abreu

>
>> +
>> +	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 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 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->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;
>> +
>> +	/* 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_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);
> <snip>
>
> Regards,
>
> 	Hans
Hans Verkuil May 23, 2017, 4:48 p.m. UTC | #3
On 05/23/2017 06:38 PM, Jose Abreu wrote:
> Hi Hans,
> 
> 
> Thanks for the review!
> 
> On 22-05-2017 11:36, Hans Verkuil wrote:
>> On 04/21/2017 11:53 AM, Jose Abreu wrote:

<snip>

>>> +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;
>>> +	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 = 0;
>> There is no interlaced support? Most receivers can at least detect it.
> 
> The controller supports interlaced, unfortunately there is no way
> I can test it, so we chose not to add it in the driver.

But isn't there a register that tells you if the source was interlaced?
Almost all HDMI receiver drivers can detect this, even though they don't
actually allow interlaced formats to be used. It is disabled in the
DV timings capabilities.

My problem with this code is that it doesn't tell the caller that the
signal is interlaced. This can lead to problems where it is incorrectly
interpreted as progressive.

> 
>>
>>> +
>>> +	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);
>> Can this be rounded up to a value above 594 MHz? In the timings cap that
>> is the max frequency, but you probably need to allow for a bit of margin there
>> in case you measure e.g. 594050000 Hz.
> 
> Hmm, yeah, probably it can. Actually the timings cap may not be
> correct because we support deep color in 4k, so freq will be >
> 594MHz.

No, since this is the pixel clock, and the number of pixels remains the same,
even when using deep color.

It is increasingly likely that this driver might be the first to support
deep color, so it is very possible that some API changes may have to be made.

I always wanted to add support for this, but I never had the chance.

>>> +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 */
>>> +		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;
>>> +	}
>> Be careful here: if you support HDCP, then you typically don't want to allow
>> userspace to touch any HDCP-related registers through this API.
> 
> Yeah, HDCP is supported but still not implemented. Still, the
> only thing the user will be able to change will be bksv because
> keys can not be read, they are write only. I will add a check though.

Let me know if/when you want to add actual HDCP support. I have some old patches
for HDCP that we (Cisco) made a long time ago.

Regards,

	Hans
diff mbox

Patch

diff --git a/drivers/media/platform/dwc/Kconfig b/drivers/media/platform/dwc/Kconfig
index 361d38d..11efa23 100644
--- a/drivers/media/platform/dwc/Kconfig
+++ b/drivers/media/platform/dwc/Kconfig
@@ -6,3 +6,12 @@  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
+	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..6bfbc86
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-hdmi-rx.c
@@ -0,0 +1,1396 @@ 
+/*
+ * 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/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
+
+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, 594000000,	/* 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 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;
+};
+
+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 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_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 res,
+		bool hdmi2, bool scrambling)
+{
+	struct dw_phy_command cmd = {
+		.result = 0,
+		.res = res,
+		.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 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_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_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 u8 dw_hdmi_get_curr_vic(struct dw_hdmi_dev *dw_dev)
+{
+	return 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;
+}
+
+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 0;
+	}
+}
+
+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);
+}
+
+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_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");
+			v4l2_subdev_notify_event(&dw_dev->sd,
+					&dw_hdmi_event_fmt);
+			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)
+{
+	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);
+	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;
+}
+
+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);
+
+	dw_hdmi_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);
+	}
+
+	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__);
+
+	memset(timings, 0, sizeof(*timings));
+	*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;
+	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 = 0;
+
+	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);
+	if (vic) {
+		bt->flags |= V4L2_DV_FL_HAS_CEA861_VIC;
+		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 bool dw_hdmi_check_dv_timings(const struct v4l2_dv_timings *t, void *hdl)
+{
+	return true;
+}
+
+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,
+			dw_hdmi_check_dv_timings, 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, "--- Video status ---\n");
+	v4l2_info(sd, "type=%s\n, 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 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 */
+		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 */
+		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 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 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->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;
+
+	/* 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_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..1a3bda4
--- /dev/null
+++ b/drivers/media/platform/dwc/dw-hdmi-rx.h
@@ -0,0 +1,313 @@ 
+/*
+ * 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_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_AVI_PB			0x03a4
+
+/* id_hdmi_2_0 Registers */
+#define HDMI_HDCP22_CONTROL			0x081c
+
+/* 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_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,
+	/* 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_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,
+	/* 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),
+	/* 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..755d680
--- /dev/null
+++ b/include/media/dwc/dw-hdmi-rx-pdata.h
@@ -0,0 +1,50 @@ 
+/*
+ * 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
+
+struct dw_hdmi_rx_pdata {
+	unsigned int iref_clk; /* MHz */
+	/* 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__ */