Skip to contentSkip to conversation

Synopsys DesignWare HDMI Video Capture Controller + PHY

PATCH Superseded Jose Abreu 3 participants 9 messages Dec 7, 2017

Subsystems (2)

v9 of 10 Superseded Latest is v10 →

lore
Share & tools
lore

Jose Abreu

author
#0 lore raw

v9 cover Synopsys DesignWare HDMI Video Capture Controller + PHY

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

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

Version 9 implements the work developed by Sakari Ailus regarding subdevices pipelines registration.

This series was tested in a FPGA platform using an embedded platform called ARC AXS101.

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

Cc Joao Pinto <[email protected]>
Cc Mauro Carvalho Chehab <[email protected]>
Cc Hans Verkuil <[email protected]>
Cc Sylwester Nawrocki <[email protected]>
Cc Sakari Ailus <[email protected]>
Cc Rob Herring <[email protected]>
Cc Mark Rutland <[email protected]>
 .../devicetree/bindings/media/snps,dw-hdmi-rx.txt  |   58 +
 MAINTAINERS                                        |    7 +
 drivers/media/platform/Kconfig                     |    2 +
 drivers/media/platform/Makefile                    |    2 +
 drivers/media/platform/dwc/Kconfig                 |   23 +
 drivers/media/platform/dwc/Makefile                |    2 +
 drivers/media/platform/dwc/dw-hdmi-phy-e405.c      |  844 +++++++++
 drivers/media/platform/dwc/dw-hdmi-phy-e405.h      |   63 +
 drivers/media/platform/dwc/dw-hdmi-rx.c            | 1834 ++++++++++++++++++++
 drivers/media/platform/dwc/dw-hdmi-rx.h            |  441 +++++
 include/media/dwc/dw-hdmi-phy-pdata.h              |  128 ++
 include/media/dwc/dw-hdmi-rx-pdata.h               |   70 +
 12 files changed, 3474 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.txt
 create mode 100644 drivers/media/platform/dwc/Kconfig
 create mode 100644 drivers/media/platform/dwc/Makefile
 create mode 100644 drivers/media/platform/dwc/dw-hdmi-phy-e405.c
 create mode 100644 drivers/media/platform/dwc/dw-hdmi-phy-e405.h
 create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.c
 create mode 100644 drivers/media/platform/dwc/dw-hdmi-rx.h
 create mode 100644 include/media/dwc/dw-hdmi-phy-pdata.h
 create mode 100644 include/media/dwc/dw-hdmi-rx-pdata.h
Signature · 1 line
1.9.1
author
#1 lore raw

PATCH v9 1/4 dt-bindings: media: Document Synopsys DesignWare HDMI RX

Document the bindings for the Synopsys DesignWare HDMI RX.

Cc Joao Pinto <[email protected]>
Cc Rob Herring <[email protected]>
Cc Mark Rutland <[email protected]>
Cc Mauro Carvalho Chehab <[email protected]>
Cc Hans Verkuil <[email protected]>
Cc Sylwester Nawrocki <[email protected]>

---
Changes from v7:
- Remove SoC specific bindings (Rob)
Changes from v6:
- Document which properties are required/optional (Sylwester)
- Drop compatible string for SoC (Sylwester)
- Reword edid-phandle property (Sylwester)
- Typo fixes (Sylwester)
Changes from v4:
- Use "cfg" instead of "cfg-clk" (Rob)
- Change node names (Rob)
Changes from v3:
- Document the new DT bindings suggested by Sylwester
Changes from v2:
- Document edid-phandle property

---
 .../devicetree/bindings/media/snps,dw-hdmi-rx.txt  | 58 ++++++++++++++++++++++
 1 file changed, 58 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.txt
Documentation/devicetree/bindings/media/snps,dw-hdmi-rx.txt new file +58 -0
new file mode 100644
index 0000000..1dc09c6
@@ -0,0 +1,58 @@
+Synopsys DesignWare HDMI RX Decoder
+===================================
+
+This document defines device tree properties for the Synopsys DesignWare HDMI
+RX Decoder (DWC HDMI RX).
+
+The properties bellow belong to the Synopsys DesignWare HDMI RX Decoder node.
+
+Required properties:
+
+- compatible: Shall be "snps,dw-hdmi-rx".
+- reg: Memory mapped base address and length of the DWC HDMI RX registers.
+- interrupts: Reference to the DWC HDMI RX interrupt and the HDMI 5V sense
+interrupt.
+- clocks: Reference to the config clock.
+- clock-names: Shall be "cfg".
+- #address-cells: Shall be 1.
+- #size-cells: Shall be 0.
+
+Optional properties:
+
+- edid-phandle: Reference to the EDID handler block; if this property is not
+specified it is assumed that EDID is handled by device described by parent
+node of the HDMI RX node. You should not specify this property if your HDMI RX
+controller does not have CEC.
+
+You also have to create a subnode for the PHY device. PHY node properties are
+as follows.
+
+Required properties:
+
+- compatible: Shall be "snps,dw-hdmi-phy-e405".
+- reg: Shall be the JTAG address of the PHY.
+- clocks: Reference to the config clock.
+- clock-names: Shall be "cfg".
+
+Example:
+
+hdmi_rx: hdmi-rx@0 {
+	compatible = "snps,dw-hdmi-rx";
+	reg = <0x0 0x10000>;
+	interrupts = <1 2>;
+	edid-phandle = <&dw_hdmi_edid>;
+
+	clocks = <&dw_hdmi_refclk>;
+	clock-names = "cfg";
+
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	hdmi-phy@fc {
+		compatible = "snps,dw-hdmi-phy-e405";
+		reg = <0xfc>;
+
+		clocks = <&dw_hdmi_refclk>;
+		clock-names = "cfg";
+	};
+};
Signature · 1 line
1.9.1
author
#2 lore raw

PATCH v9 2/4 MAINTAINERS: Add entry for Synopsys DesignWare HDMI drivers

In reply to Jose Abreu

Add an entry for Synopsys DesignWare HDMI Receivers drivers and phys.

---
 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)
MAINTAINERS +7 -0
index 7a52a66..a1675bc 100644
@@ -13108,6 +13108,13 @@ L:	[email protected]
 S:	Supported
 F:	drivers/net/ethernet/synopsys/
 
+SYNOPSYS DESIGNWARE HDMI RECEIVERS AND PHY DRIVERS
+M:	Jose Abreu <[email protected]>
+L:	[email protected]
+S:	Maintained
+F:	drivers/media/platform/dwc/*
+F:	include/media/dwc/*
+
 SYNOPSYS DESIGNWARE I2C DRIVER
 M:	Jarkko Nikula <[email protected]>
 R:	Andy Shevchenko <[email protected]>
Signature · 1 line
1.9.1
author
#3 lore raw

PATCH v9 3/4 platform: Add Synopsys DesignWare HDMI RX PHY e405 Driver

In reply to Jose Abreu

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

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

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

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

Cc Joao Pinto <[email protected]>
Cc Mauro Carvalho Chehab <[email protected]>
Cc Hans Verkuil <[email protected]>
Cc Sylwester Nawrocki <[email protected]>

---
Changes from v4:
- Use usleep_range (Sylwester)
- Remove some comments (Sylwester)
- Parse phy version from of_device_id (Sylwester)
- Use "cfg" instead of "cfg-clk" (Sylwester, Rob)
- Change some messages to dev_dbg (Sylwester)
Changes from v3:
- Use v4l2 async API (Sylwester)
- Use clock API (Sylwester)
- Add compatible string (Sylwester)
Changes from RFC:
- Remove a bunch of functions that can be collapsed into a single config() function
- Add comments for the callbacks and structures (Hans)

---
 drivers/media/platform/Kconfig                |   2 +
 drivers/media/platform/Makefile               |   2 +
 drivers/media/platform/dwc/Kconfig            |   8 +
 drivers/media/platform/dwc/Makefile           |   1 +
 drivers/media/platform/dwc/dw-hdmi-phy-e405.c | 844 ++++++++++++++++++++++++++
 drivers/media/platform/dwc/dw-hdmi-phy-e405.h |  63 ++
 include/media/dwc/dw-hdmi-phy-pdata.h         | 128 ++++
 7 files changed, 1048 insertions(+)
 create mode 100644 drivers/media/platform/dwc/Kconfig
 create mode 100644 drivers/media/platform/dwc/Makefile
 create mode 100644 drivers/media/platform/dwc/dw-hdmi-phy-e405.c
 create mode 100644 drivers/media/platform/dwc/dw-hdmi-phy-e405.h
 create mode 100644 include/media/dwc/dw-hdmi-phy-pdata.h
drivers/media/platform/Kconfig +2 -0
index fd0c998..0187cbd 100644
@@ -33,6 +33,8 @@ source "drivers/media/platform/omap/Kconfig"
 
 source "drivers/media/platform/blackfin/Kconfig"
 
+source "drivers/media/platform/dwc/Kconfig"
+
 config VIDEO_SH_VOU
 	tristate "SuperH VOU video output driver"
 	depends on MEDIA_CAMERA_SUPPORT
drivers/media/platform/Makefile +2 -0
index 003b0bb..2e879c6 100644
@@ -97,3 +97,5 @@ obj-$(CONFIG_VIDEO_QCOM_CAMSS)		+= qcom/camss-8x16/
 obj-$(CONFIG_VIDEO_QCOM_VENUS)		+= qcom/venus/
 
 obj-y					+= meson/
+
+obj-y					+= dwc/
drivers/media/platform/dwc/Kconfig new file +8 -0
new file mode 100644
index 0000000..361d38d
@@ -0,0 +1,8 @@
+config VIDEO_DWC_HDMI_PHY_E405
+	tristate "Synopsys Designware HDMI RX PHY e405 driver"
+	depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+	help
+	  Support for Synopsys Designware HDMI RX PHY. Version is e405.
+
+	  To compile this driver as a module, choose M here. The module
+	  will be called dw-hdmi-phy-e405.
drivers/media/platform/dwc/Makefile new file +1 -0
new file mode 100644
index 0000000..fc3b62c
@@ -0,0 +1 @@
+obj-$(CONFIG_VIDEO_DWC_HDMI_PHY_E405) += dw-hdmi-phy-e405.o
drivers/media/platform/dwc/dw-hdmi-phy-e405.c new file +844 -0

844 lines not on this page · raw message

drivers/media/platform/dwc/dw-hdmi-phy-e405.h new file +63 -0

63 lines not on this page · raw message

include/media/dwc/dw-hdmi-phy-pdata.h new file +128 -0

128 lines not on this page · raw message

Signature · 1 line
1.9.1
author
#4 lore raw

PATCH v9 4/4 platform: Add Synopsys DesignWare HDMI RX Controller Driver

In reply to Jose Abreu

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

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

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

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

Cc Joao Pinto <[email protected]>
Cc Mauro Carvalho Chehab <[email protected]>
Cc Hans Verkuil <[email protected]>
Cc Sylwester Nawrocki <[email protected]>
Cc Sakari Ailus <[email protected]>

---
Changes from v8:
- Incorporate Sakari's work on ASYNC subdevs
Changes from v6:
- edid-phandle now also looks for parent node (Sylwester)
- Fix kbuild build warnings
Changes from v5:
- Removed HDCP 1.4 support (Hans)
- Removed some CEC debug messages (Hans)
- Add s_dv_timings callback (Hans)
- Add V4L2_CID_DV_RX_POWER_PRESENT ctrl (Hans)
Changes from v4:
- Add flag V4L2_SUBDEV_FL_HAS_DEVNODE (Sylwester)
- Remove some comments and change some messages to dev_dbg (Sylwester)
- Use v4l2_async_subnotifier_register() (Sylwester)
Changes from v3:
- Use v4l2 async API (Sylwester)
- Do not block waiting for phy
- Do not use busy waiting delays (Sylwester)
- Simplify dw_hdmi_power_on (Sylwester)
- Use clock API (Sylwester)
- Use compatible string (Sylwester)
- Minor fixes (Sylwester)
Changes from v2:
- Address review comments from Hans regarding CEC
- Use CEC notifier
- Enable SCDC
Changes from v1:
- Add support for CEC
- Correct typo errors
- Correctly detect interlaced video modes
- Correct VIC parsing
Changes from RFC:
- Add support for HDCP 1.4
- Fixup HDMI_VIC not being parsed (Hans)
- Send source change signal when powering off (Hans)
- Add a "wait stable delay"
- Detect interlaced video modes (Hans)
- Restrain g/s_register from reading/writing to HDCP regs (Hans)

---
 drivers/media/platform/dwc/Kconfig      |   15 +
 drivers/media/platform/dwc/Makefile     |    1 +
 drivers/media/platform/dwc/dw-hdmi-rx.c | 1834 +++++++++++++++++++++++++++++++
 drivers/media/platform/dwc/dw-hdmi-rx.h |  441 ++++++++
 include/media/dwc/dw-hdmi-rx-pdata.h    |   70 ++
 5 files changed, 2361 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
drivers/media/platform/dwc/Kconfig +15 -0
index 361d38d..3ddccde 100644
@@ -6,3 +6,18 @@ 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.
+
+config VIDEO_DWC_HDMI_RX_CEC
+	bool
+	depends on VIDEO_DWC_HDMI_RX
+	select CEC_CORE
+	select CEC_NOTIFIER
drivers/media/platform/dwc/Makefile +1 -0
index fc3b62c..cd04ca9 100644
@@ -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
drivers/media/platform/dwc/dw-hdmi-rx.c new file +1834 -0

1,834 lines not on this page · raw message

drivers/media/platform/dwc/dw-hdmi-rx.h new file +441 -0

441 lines not on this page · raw message

include/media/dwc/dw-hdmi-rx-pdata.h new file +70 -0

70 lines not on this page · raw message

Signature · 1 line
1.9.1
#5 lore raw

Re: [PATCH v9 4/4] [media] platform: Add Synopsys DesignWare HDMI RX Controller Driver

Hi Jose,

Some (small) comments below:

Quoted 86 lines · Jose Abreu

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

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

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

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

Cc Joao Pinto <[email protected]>
Cc Mauro Carvalho Chehab <[email protected]>
Cc Hans Verkuil <[email protected]>
Cc Sylwester Nawrocki <[email protected]>
Cc Sakari Ailus <[email protected]>

---
Changes from v8:
- Incorporate Sakari's work on ASYNC subdevs
Changes from v6:
- edid-phandle now also looks for parent node (Sylwester)
- Fix kbuild build warnings
Changes from v5:
- Removed HDCP 1.4 support (Hans)
- Removed some CEC debug messages (Hans)
- Add s_dv_timings callback (Hans)
- Add V4L2_CID_DV_RX_POWER_PRESENT ctrl (Hans)
Changes from v4:
- Add flag V4L2_SUBDEV_FL_HAS_DEVNODE (Sylwester)
- Remove some comments and change some messages to dev_dbg (Sylwester)
- Use v4l2_async_subnotifier_register() (Sylwester)
Changes from v3:
- Use v4l2 async API (Sylwester)
- Do not block waiting for phy
- Do not use busy waiting delays (Sylwester)
- Simplify dw_hdmi_power_on (Sylwester)
- Use clock API (Sylwester)
- Use compatible string (Sylwester)
- Minor fixes (Sylwester)
Changes from v2:
- Address review comments from Hans regarding CEC
- Use CEC notifier
- Enable SCDC
Changes from v1:
- Add support for CEC
- Correct typo errors
- Correctly detect interlaced video modes
- Correct VIC parsing
Changes from RFC:
- Add support for HDCP 1.4
- Fixup HDMI_VIC not being parsed (Hans)
- Send source change signal when powering off (Hans)
- Add a "wait stable delay"
- Detect interlaced video modes (Hans)
- Restrain g/s_register from reading/writing to HDCP regs (Hans)

---
 drivers/media/platform/dwc/Kconfig      |   15 +
 drivers/media/platform/dwc/Makefile     |    1 +
 drivers/media/platform/dwc/dw-hdmi-rx.c | 1834 +++++++++++++++++++++++++++++++
 drivers/media/platform/dwc/dw-hdmi-rx.h |  441 ++++++++
 include/media/dwc/dw-hdmi-rx-pdata.h    |   70 ++
 5 files changed, 2361 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

<snip>

Quoted 11 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +11 -0
+static void dw_hdmi_cec_tx_raw_status(struct dw_hdmi_dev *dw_dev, u32 stat)
+{
+	if (hdmi_readl(dw_dev, HDMI_CEC_CTRL) & HDMI_CEC_CTRL_SEND_MASK) {
+		dev_dbg(dw_dev->dev, "%s: tx is busy\n", __func__);
+		return;
+	}
+
+	if (stat & HDMI_AUD_CEC_ISTS_ARBLST) {
+		cec_transmit_attempt_done(dw_dev->cec_adap,
+				CEC_TX_STATUS_ARB_LOST);
+		return;

drivers/media/platform/dwc/dw-hdmi-rx.c line 630

Quoted 10 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +10 -0
+	}
+
+	if (stat & HDMI_AUD_CEC_ISTS_NACK) {
+		cec_transmit_attempt_done(dw_dev->cec_adap, CEC_TX_STATUS_NACK);
+		return;
+	}
+
+	if (stat & HDMI_AUD_CEC_ISTS_ERROR_INIT) {
+		dev_dbg(dw_dev->dev, "%s: got initiator error\n", __func__);
+		cec_transmit_attempt_done(dw_dev->cec_adap, CEC_TX_STATUS_ERROR);

There is no separate 'low drive' interrupt? Do you know what happens if a low drive is received during a transmit?

FYI: I've been working on error injection support for my cec-gpio driver, allowing me to test all these nasty little corner cases. And that includes Arbitration Lost. It's available here:

https://git.linuxtv.org/hverkuil/media_tree.git/log/?h=cec-error-inj

It works like a charm with my Rpi3.

Quoted 569 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +569 -0
+		return;
+	}
+
+	if (stat & HDMI_AUD_CEC_ISTS_DONE) {
+		cec_transmit_attempt_done(dw_dev->cec_adap, CEC_TX_STATUS_OK);
+		return;
+	}
+}
+
+static void dw_hdmi_cec_received_msg(struct dw_hdmi_dev *dw_dev)
+{
+	struct cec_msg msg;
+	u8 i;
+
+	msg.len = hdmi_readl(dw_dev, HDMI_CEC_RX_CNT);
+	if (!msg.len || msg.len > HDMI_CEC_RX_DATA_MAX)
+		return; /* it's an invalid/non-existent message */
+
+	for (i = 0; i < msg.len; i++)
+		msg.msg[i] = hdmi_readl(dw_dev, HDMI_CEC_RX_DATA(i));
+
+	hdmi_writel(dw_dev, 0x0, HDMI_CEC_LOCK);
+	cec_received_msg(dw_dev->cec_adap, &msg);
+}
+
+static int dw_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
+{
+	struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
+
+	if (enable) {
+		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_L);
+		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_H);
+		hdmi_writel(dw_dev, 0x0, HDMI_CEC_LOCK);
+		dw_hdmi_cec_clear_ints(dw_dev);
+		dw_hdmi_cec_enable_ints(dw_dev);
+	} else {
+		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_L);
+		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_H);
+		dw_hdmi_cec_disable_ints(dw_dev);
+		dw_hdmi_cec_clear_ints(dw_dev);
+	}
+
+	return 0;
+}
+
+static int dw_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
+{
+	struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
+	u32 tmp;
+
+	if (addr == CEC_LOG_ADDR_INVALID) {
+		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_L);
+		hdmi_writel(dw_dev, 0x0, HDMI_CEC_ADDR_H);
+		return 0;
+	}
+
+	if (addr >= 8) {
+		tmp = hdmi_readl(dw_dev, HDMI_CEC_ADDR_H);
+		tmp |= BIT(addr - 8);
+		hdmi_writel(dw_dev, tmp, HDMI_CEC_ADDR_H);
+	} else {
+		tmp = hdmi_readl(dw_dev, HDMI_CEC_ADDR_L);
+		tmp |= BIT(addr);
+		hdmi_writel(dw_dev, tmp, HDMI_CEC_ADDR_L);
+	}
+
+	return 0;
+}
+
+static int dw_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
+		u32 signal_free_time, struct cec_msg *msg)
+{
+	struct dw_hdmi_dev *dw_dev = cec_get_drvdata(adap);
+	u8 len = msg->len;
+	u32 reg;
+	int i;
+
+	if (hdmi_readl(dw_dev, HDMI_CEC_CTRL) & HDMI_CEC_CTRL_SEND_MASK) {
+		dev_err(dw_dev->dev, "%s: tx is busy\n", __func__);
+		return -EBUSY;
+	}
+
+	for (i = 0; i < len; i++)
+		hdmi_writel(dw_dev, msg->msg[i], HDMI_CEC_TX_DATA(i));
+
+	switch (signal_free_time) {
+	case CEC_SIGNAL_FREE_TIME_RETRY:
+		reg = 0x0;
+		break;
+	case CEC_SIGNAL_FREE_TIME_NEXT_XFER:
+		reg = 0x2;
+		break;
+	case CEC_SIGNAL_FREE_TIME_NEW_INITIATOR:
+	default:
+		reg = 0x1;
+		break;
+	}
+
+	hdmi_writel(dw_dev, len, HDMI_CEC_TX_CNT);
+	hdmi_mask_writel(dw_dev, reg, HDMI_CEC_CTRL,
+			HDMI_CEC_CTRL_FRAME_TYP_OFFSET,
+			HDMI_CEC_CTRL_FRAME_TYP_MASK);
+	hdmi_mask_writel(dw_dev, 0x1, HDMI_CEC_CTRL,
+			HDMI_CEC_CTRL_SEND_OFFSET,
+			HDMI_CEC_CTRL_SEND_MASK);
+	return 0;
+}
+
+static const struct cec_adap_ops dw_hdmi_cec_adap_ops = {
+	.adap_enable = dw_hdmi_cec_adap_enable,
+	.adap_log_addr = dw_hdmi_cec_adap_log_addr,
+	.adap_transmit = dw_hdmi_cec_adap_transmit,
+};
+
+static void dw_hdmi_cec_irq_handler(struct dw_hdmi_dev *dw_dev)
+{
+	u32 cec_ists = dw_hdmi_get_int_val(dw_dev, HDMI_AUD_CEC_ISTS,
+			HDMI_AUD_CEC_IEN);
+
+	dw_hdmi_cec_clear_ints(dw_dev);
+
+	if (cec_ists) {
+		dw_hdmi_cec_tx_raw_status(dw_dev, cec_ists);
+		if (cec_ists & HDMI_AUD_CEC_ISTS_EOM)
+			dw_hdmi_cec_received_msg(dw_dev);
+	}
+}
+#endif
+
+static u8 dw_hdmi_get_curr_vic(struct dw_hdmi_dev *dw_dev, bool *is_hdmi_vic)
+{
+	u8 vic = hdmi_mask_readl(dw_dev, HDMI_PDEC_AVI_PB,
+			HDMI_PDEC_AVI_PB_VID_IDENT_CODE_OFFSET,
+			HDMI_PDEC_AVI_PB_VID_IDENT_CODE_MASK) & 0xff;
+
+	if (!vic) {
+		vic = hdmi_mask_readl(dw_dev, HDMI_PDEC_VSI_PAYLOAD0,
+				HDMI_PDEC_VSI_PAYLOAD0_HDMI_VIC_OFFSET,
+				HDMI_PDEC_VSI_PAYLOAD0_HDMI_VIC_MASK) & 0xff;
+		if (is_hdmi_vic)
+			*is_hdmi_vic = true;
+	} else {
+		if (is_hdmi_vic)
+			*is_hdmi_vic = false;
+	}
+
+	return vic;
+}
+
+static u64 dw_hdmi_get_pixelclk(struct dw_hdmi_dev *dw_dev)
+{
+	u32 rate = hdmi_mask_readl(dw_dev, HDMI_CKM_RESULT,
+			HDMI_CKM_RESULT_CLKRATE_OFFSET,
+			HDMI_CKM_RESULT_CLKRATE_MASK);
+	u32 evaltime = hdmi_mask_readl(dw_dev, HDMI_CKM_EVLTM,
+			HDMI_CKM_EVLTM_EVAL_TIME_OFFSET,
+			HDMI_CKM_EVLTM_EVAL_TIME_MASK);
+	u64 tmp = (u64)rate * (u64)dw_dev->cfg_clk * 1000000;
+
+	do_div(tmp, evaltime);
+	return tmp;
+}
+
+static u32 dw_hdmi_get_colordepth(struct dw_hdmi_dev *dw_dev)
+{
+	u32 dcm = hdmi_mask_readl(dw_dev, HDMI_STS,
+			HDMI_STS_DCM_CURRENT_MODE_OFFSET,
+			HDMI_STS_DCM_CURRENT_MODE_MASK);
+
+	switch (dcm) {
+	case 0x4:
+		return 24;
+	case 0x5:
+		return 30;
+	case 0x6:
+		return 36;
+	case 0x7:
+		return 48;
+	default:
+		return 24;
+	}
+}
+
+static void dw_hdmi_set_input(struct dw_hdmi_dev *dw_dev, u32 input)
+{
+	hdmi_mask_writel(dw_dev, input, HDMI_PHY_CTRL,
+			HDMI_PHY_CTRL_PORTSELECT_OFFSET,
+			HDMI_PHY_CTRL_PORTSELECT_MASK);
+}
+
+static void dw_hdmi_enable_hpd(struct dw_hdmi_dev *dw_dev, u32 input_mask)
+{
+	hdmi_mask_writel(dw_dev, input_mask, 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 void dw_hdmi_enable_scdc(struct dw_hdmi_dev *dw_dev)
+{
+	hdmi_mask_writel(dw_dev, 0x1, HDMI_SCDC_CONFIG,
+			HDMI_SCDC_CONFIG_POWERPROVIDED_OFFSET,
+			HDMI_SCDC_CONFIG_POWERPROVIDED_MASK);
+}
+
+static void dw_hdmi_disable_scdc(struct dw_hdmi_dev *dw_dev)
+{
+	hdmi_mask_writel(dw_dev, 0x0, HDMI_SCDC_CONFIG,
+			HDMI_SCDC_CONFIG_POWERPROVIDED_OFFSET,
+			HDMI_SCDC_CONFIG_POWERPROVIDED_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_EQUALIZER);
+			break;
+		case HDMI_STATE_EQUALIZER:
+			eqret = dw_hdmi_phy_eq_init(dw_dev, 5,
+					dw_dev->phy_eq_force);
+			ret = dw_hdmi_wait_phy_lock_poll(dw_dev);
+
+			/* Do not force equalizer */
+			dw_dev->phy_eq_force = false;
+
+			if (ret || eqret) {
+				if (ret || eqret == -ETIMEDOUT) {
+					/* No TMDSVALID signal:
+					 * 	- force equalizer */
+					dw_dev->phy_eq_force = true;
+				}
+				break;
+			}
+
+			dw_hdmi_set_state(dw_dev, HDMI_STATE_VIDEO_UNSTABLE);
+			break;
+		case HDMI_STATE_VIDEO_UNSTABLE:
+			dw_hdmi_reset_datapath(dw_dev);
+			dw_hdmi_wait_video_stable(dw_dev);
+			dw_hdmi_clear_ints(dw_dev);
+			dw_hdmi_enable_ints(dw_dev);
+			dw_hdmi_set_state(dw_dev, HDMI_STATE_POWER_ON);
+			break;
+		case HDMI_STATE_POWER_ON:
+			break;
+		default:
+			dev_err(dw_dev->dev, "%s called with state (%d)\n",
+					__func__, dw_dev->state);
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (dw_dev->state == HDMI_STATE_POWER_ON) {
+			dev_info(dw_dev->dev, "HDMI-RX configured\n");
+			dw_hdmi_event_source_change(dw_dev);
+			return 0;
+		}
+	}
+
+out:
+	dw_hdmi_set_state(dw_dev, HDMI_STATE_POWER_OFF);
+	return ret;
+}
+
+static void 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);
+}
+
+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;
+}
+
+static void dw_hdmi_work_handler(struct work_struct *work)
+{
+	struct dw_hdmi_dev *dw_dev = container_of(work, struct dw_hdmi_dev, work);
+
+	__dw_hdmi_power_on(dw_dev, dw_dev->configured_input);
+}
+
+static int dw_hdmi_power_on(struct dw_hdmi_dev *dw_dev, u32 input)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dw_dev->lock, flags);
+	if (dw_dev->pending_config) {
+		spin_unlock_irqrestore(&dw_dev->lock, flags);
+		return 0;
+	}
+
+	INIT_WORK(&dw_dev->work, dw_hdmi_work_handler);
+	dw_dev->configured_input = input;
+	dw_dev->pending_config = true;
+	queue_work(dw_dev->wq, &dw_dev->work);
+	spin_unlock_irqrestore(&dw_dev->lock, flags);
+	return 0;
+}
+
+static void dw_hdmi_power_off(struct dw_hdmi_dev *dw_dev)
+{
+	unsigned long flags;
+
+	dw_dev->force_off = true;
+	flush_workqueue(dw_dev->wq);
+	dw_dev->force_off = false;
+
+	spin_lock_irqsave(&dw_dev->lock, flags);
+	dw_dev->pending_config = false;
+	dw_dev->state = HDMI_STATE_POWER_OFF;
+	spin_unlock_irqrestore(&dw_dev->lock, flags);
+
+	/* Reset variables */
+	dw_dev->phy_eq_force = true;
+
+	/* Send source change event to userspace */
+	dw_hdmi_event_source_change(dw_dev);
+}
+
+static 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);
+	}
+
+#if IS_ENABLED(CONFIG_VIDEO_DWC_HDMI_RX_CEC)
+	dw_hdmi_cec_irq_handler(dw_dev);
+#endif
+
+	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;
+	u32 stat = 0;
+	int i;
+
+	if (!has_signal(dw_dev, old_input)) {
+		dw_hdmi_disable_ints(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);
+		stat |= on << i;
+
+		if (is_off(dw_dev) && on && !pending_config) {
+			dw_hdmi_power_on(dw_dev, i);
+			dw_hdmi_set_input(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);
+
+	if (stat) {
+		/*
+		 * If there are any connected ports enable the HPD and the SCDC
+		 * for these ports.
+		 */
+		dw_hdmi_enable_scdc(dw_dev);
+		dw_hdmi_enable_hpd(dw_dev, stat);
+	} else {
+		/*
+		 * If there are no connected ports disable whole HPD and SCDC
+		 * also.
+		 */
+		dw_hdmi_disable_hpd(dw_dev);
+		dw_hdmi_disable_scdc(dw_dev);
+	}
+
+	v4l2_ctrl_s_ctrl(dw_dev->detect_tx_5v_ctrl, stat);
+	dev_dbg(dw_dev->dev, "%s: stat=0x%x\n", __func__, stat);
+}
+
+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_s_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__);
+	if (!v4l2_valid_dv_timings(timings, &dw_hdmi_timings_cap, NULL, NULL))
+		return -EINVAL;
+	if (v4l2_match_dv_timings(timings, &dw_dev->timings, 0, false))
+		return 0;
+
+	dw_dev->timings = *timings;
+	return 0;
+}
+
+static int dw_hdmi_g_dv_timings(struct v4l2_subdev *sd,
+		struct v4l2_dv_timings *timings)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+
+	*timings = dw_dev->timings;
+	return 0;
+}
+
+static int dw_hdmi_query_dv_timings(struct v4l2_subdev *sd,
+		struct v4l2_dv_timings *timings)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+	struct v4l2_bt_timings *bt = &timings->bt;
+	bool is_hdmi_vic;
+	u32 htot, hofs;
+	u32 vtot;
+	u8 vic;
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+
+	memset(timings, 0, sizeof(*timings));
+
+	timings->type = V4L2_DV_BT_656_1120;
+	bt->width = hdmi_readl(dw_dev, HDMI_MD_HACT_PX);
+	bt->height = hdmi_readl(dw_dev, HDMI_MD_VAL);
+	bt->interlaced = hdmi_readl(dw_dev, HDMI_MD_STS) & HDMI_MD_STS_ILACE;
+
+	if (hdmi_readl(dw_dev, HDMI_ISTS) & HDMI_ISTS_VS_POL_ADJ)
+		bt->polarities |= V4L2_DV_VSYNC_POS_POL;
+	if (hdmi_readl(dw_dev, HDMI_ISTS) & HDMI_ISTS_HS_POL_ADJ)
+		bt->polarities |= V4L2_DV_HSYNC_POS_POL;
+
+	bt->pixelclock = dw_hdmi_get_pixelclk(dw_dev);
+
+	/* HTOT = HACT + HFRONT + HSYNC + HBACK */
+	htot = hdmi_mask_readl(dw_dev, HDMI_MD_HT1,
+			HDMI_MD_HT1_HTOT_PIX_OFFSET,
+			HDMI_MD_HT1_HTOT_PIX_MASK);
+	/* HOFS = HSYNC + HBACK */
+	hofs = hdmi_mask_readl(dw_dev, HDMI_MD_HT1,
+			HDMI_MD_HT1_HOFS_PIX_OFFSET,
+			HDMI_MD_HT1_HOFS_PIX_MASK);
+
+	bt->hfrontporch = htot - hofs - bt->width;
+	bt->hsync = hdmi_mask_readl(dw_dev, HDMI_MD_HT0,
+			HDMI_MD_HT0_HS_CLK_OFFSET,
+			HDMI_MD_HT0_HS_CLK_MASK);
+	bt->hbackporch = hofs - bt->hsync;
+
+	/* VTOT = VACT + VFRONT + VSYNC + VBACK */
+	vtot = hdmi_readl(dw_dev, HDMI_MD_VTL);
+
+	hdmi_mask_writel(dw_dev, 0x1, HDMI_MD_VCTRL,
+			HDMI_MD_VCTRL_V_OFFS_LIN_MODE_OFFSET,

drivers/media/platform/dwc/dw-hdmi-rx.c line 1209

Quoted 10 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +10 -0
+			HDMI_MD_VCTRL_V_OFFS_LIN_MODE_MASK);
+	msleep(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);
+	msleep(50);
+	bt->vbackporch = hdmi_readl(dw_dev, HDMI_MD_VOL);
+	bt->vfrontporch = vtot - bt->height - bt->vsync - bt->vbackporch;

For interlaced formats the bt->il_* fields should also be filled in.

Quoted 4 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +4 -0
+	bt->standards = V4L2_DV_BT_STD_CEA861;
+
+	vic = dw_hdmi_get_curr_vic(dw_dev, &is_hdmi_vic);
+	if (vic) {

drivers/media/platform/dwc/dw-hdmi-rx.c line 1223

Quoted 10 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +10 -0
+		if (is_hdmi_vic) {
+			bt->flags |= V4L2_DV_FL_HAS_HDMI_VIC;
+			bt->hdmi_vic = vic;
+			bt->cea861_vic = 0;
+		} else {
+			bt->flags |= V4L2_DV_FL_HAS_CEA861_VIC;
+			bt->hdmi_vic = 0;
+			bt->cea861_vic = vic;
+		}
+	}

You can simplify this. We have this function in v4l2-dv-timings.c:
v4l2_find_dv_timings_cea861_vic(). If you read a CEA861 vic code, then you can call it to find the corresponding timings and just return that.

I thought I made a v4l2_find_dv_timings_hdmi_vic as well, but apparently I didn't. It's trivial to add it, though.

Quoted 19 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +19 -0
+
+	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,

drivers/media/platform/dwc/dw-hdmi-rx.c line 1252

Quoted 10 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +10 -0
+		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;

Were interlaced formats tested? (Apologies if I have asked this before)

Interlaced is tricky and my recommendation is to only add support for it to a driver if you have been able to test it.

I see that dw_hdmi_timings_cap only supports progressive, so I conclude that this hasn't been tested. It's better to just fix field to NONE in that case.

Quoted 191 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +191 -0
+	else
+		format->field = V4L2_FIELD_NONE;
+
+	return 0;
+}
+
+static int dw_hdmi_get_fmt(struct v4l2_subdev *sd,
+		struct v4l2_subdev_pad_config *cfg,
+		struct v4l2_subdev_format *format)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+	return dw_hdmi_fill_format(dw_dev, &format->format);
+}
+
+static int dw_hdmi_set_fmt(struct v4l2_subdev *sd,
+		struct v4l2_subdev_pad_config *cfg,
+		struct v4l2_subdev_format *format)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+
+	if (format->format.code != dw_dev->mbus_code) {
+		dev_dbg(dw_dev->dev, "invalid format\n");
+		return -EINVAL;
+	}
+
+	return dw_hdmi_get_fmt(sd, cfg, format);
+}
+
+static int dw_hdmi_dv_timings_cap(struct v4l2_subdev *sd,
+		struct v4l2_dv_timings_cap *cap)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+	unsigned int pad = cap->pad;
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+
+	*cap = dw_hdmi_timings_cap;
+	cap->pad = pad;
+	return 0;
+}
+
+static int dw_hdmi_enum_dv_timings(struct v4l2_subdev *sd,
+		struct v4l2_enum_dv_timings *timings)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+	return v4l2_enum_dv_timings_cap(timings, &dw_hdmi_timings_cap,
+			NULL, NULL);
+}
+
+static int dw_hdmi_log_status(struct v4l2_subdev *sd)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+	struct v4l2_dv_timings timings;
+
+	v4l2_info(sd, "--- Chip configuration ---\n");
+	v4l2_info(sd, "cfg_clk=%dMHz\n", dw_dev->cfg_clk);
+	v4l2_info(sd, "phy_drv=%s, phy_jtag_addr=0x%x\n",
+			dw_dev->phy_sd ? "present" : "not present",
+			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, 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);
+
+	v4l2_ctrl_subdev_log_status(sd);
+	return 0;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static void dw_hdmi_invalid_register(struct dw_hdmi_dev *dw_dev, u64 reg)
+{
+	dev_err(dw_dev->dev, "register 0x%llx not supported\n", reg);
+	dev_err(dw_dev->dev, "0x0000-0x7fff: Main controller map\n");
+	dev_err(dw_dev->dev, "0x8000-0x80ff: PHY map\n");
+}
+
+static bool dw_hdmi_is_reserved_register(struct dw_hdmi_dev *dw_dev, u32 reg)
+{
+	/*
+	 * NOTE: Some of the HDCP registers are write only. This means that
+	 * a read from these registers will never return and can block the bus
+	 * in some architectures. Disable the read to these registers and also
+	 * disable the write as a safety measure because userspace should not
+	 * be able to set HDCP registers.
+	 */
+	if (reg >= HDMI_HDCP_CTRL && reg <= HDMI_HDCP_STS)
+		return true;
+	if (reg == HDMI_HDCP22_CONTROL)
+		return true;
+	if (reg == HDMI_HDCP22_STATUS)
+		return true;
+	return false;
+}
+
+static int dw_hdmi_g_register(struct v4l2_subdev *sd,
+		struct v4l2_dbg_register *reg)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	switch (reg->reg >> 15) {
+	case 0: /* Controller core read */
+		if (dw_hdmi_is_reserved_register(dw_dev, reg->reg & 0x7fff))
+			return -EINVAL;
+
+		reg->size = 4;
+		reg->val = hdmi_readl(dw_dev, reg->reg & 0x7fff);
+		return 0;
+	case 1: /* PHY read */
+		if ((reg->reg & ~0xff) != BIT(15))
+			break;
+
+		reg->size = 2;
+		reg->val = dw_hdmi_phy_read(dw_dev, reg->reg & 0xff);
+		return 0;
+	default:
+		break;
+	}
+
+	dw_hdmi_invalid_register(dw_dev, reg->reg);
+	return 0;
+}
+
+static int dw_hdmi_s_register(struct v4l2_subdev *sd,
+		const struct v4l2_dbg_register *reg)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	switch (reg->reg >> 15) {
+	case 0: /* Controller core write */
+		if (dw_hdmi_is_reserved_register(dw_dev, reg->reg & 0x7fff))
+			return -EINVAL;
+
+		hdmi_writel(dw_dev, reg->val & GENMASK(31,0), reg->reg & 0x7fff);
+		return 0;
+	case 1: /* PHY write */
+		if ((reg->reg & ~0xff) != BIT(15))
+			break;
+		dw_hdmi_phy_write(dw_dev, reg->val & 0xffff, reg->reg & 0xff);
+		return 0;
+	default:
+		break;
+	}
+
+	dw_hdmi_invalid_register(dw_dev, reg->reg);
+	return 0;
+}
+#endif
+
+static int dw_hdmi_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
+		struct v4l2_event_subscription *sub)
+{
+	switch (sub->type) {
+	case V4L2_EVENT_SOURCE_CHANGE:
+		return v4l2_src_change_event_subdev_subscribe(sd, fh, sub);
+	default:
+		return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub);
+	}
+}
+
+static int dw_hdmi_registered(struct v4l2_subdev *sd)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+	int ret;
+
+	ret = cec_register_adapter(dw_dev->cec_adap, dw_dev->dev);
+	if (ret) {
+		dev_err(dw_dev->dev, "failed to register CEC adapter\n");

drivers/media/platform/dwc/dw-hdmi-rx.c line 1453

Quoted 10 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +10 -0
+		cec_delete_adapter(dw_dev->cec_adap);
+		return ret;
+	}
+
+	cec_register_cec_notifier(dw_dev->cec_adap, dw_dev->cec_notifier);
+	dw_dev->registered = true;
+
+	return 0;
+	/*return v4l2_async_subdev_notifier_register(&dw_dev->sd,
+			&dw_dev->v4l2_notifier);*/

Comment can be dropped, I guess.

Quoted 281 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +281 -0
+}
+
+static void dw_hdmi_unregistered(struct v4l2_subdev *sd)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+
+	cec_unregister_adapter(dw_dev->cec_adap);
+	cec_notifier_put(dw_dev->cec_notifier);
+	v4l2_async_notifier_unregister(&dw_dev->v4l2_notifier);
+}
+
+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,
+	.s_dv_timings = dw_hdmi_s_dv_timings,
+	.g_dv_timings = dw_hdmi_g_dv_timings,
+	.query_dv_timings = dw_hdmi_query_dv_timings,
+};
+
+static const struct v4l2_subdev_pad_ops dw_hdmi_sd_pad_ops = {
+	.enum_mbus_code = dw_hdmi_enum_mbus_code,
+	.get_fmt = dw_hdmi_get_fmt,
+	.set_fmt = dw_hdmi_set_fmt,
+	.dv_timings_cap = dw_hdmi_dv_timings_cap,
+	.enum_dv_timings = dw_hdmi_enum_dv_timings,
+};
+
+static const struct v4l2_subdev_ops dw_hdmi_sd_ops = {
+	.core = &dw_hdmi_sd_core_ops,
+	.video = &dw_hdmi_sd_video_ops,
+	.pad = &dw_hdmi_sd_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops dw_hdmi_internal_ops = {
+	.registered = dw_hdmi_registered,
+	.unregistered = dw_hdmi_unregistered,
+};
+
+static int dw_hdmi_v4l2_notify_bound(struct v4l2_async_notifier *notifier,
+		struct v4l2_subdev *subdev, struct v4l2_async_subdev *asd)
+{
+	struct dw_hdmi_dev *dw_dev = notifier_to_dw_dev(notifier);
+
+	if (dw_dev->phy_async_sd.match.fwnode.fwnode == dev_fwnode(subdev->dev)) {
+		dev_dbg(dw_dev->dev, "found new subdev '%s'\n", subdev->name);
+		dw_dev->phy_sd = subdev;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static void dw_hdmi_v4l2_notify_unbind(struct v4l2_async_notifier *notifier,
+		struct v4l2_subdev *subdev, struct v4l2_async_subdev *asd)
+{
+	struct dw_hdmi_dev *dw_dev = notifier_to_dw_dev(notifier);
+
+	if (dw_dev->phy_sd == subdev) {
+		dev_dbg(dw_dev->dev, "unbinding '%s'\n", subdev->name);
+		dw_dev->phy_sd = NULL;
+	}
+}
+
+static const struct v4l2_async_notifier_operations dw_hdmi_notifier_ops = {
+	.bound = dw_hdmi_v4l2_notify_bound,
+	.unbind = dw_hdmi_v4l2_notify_unbind,
+};
+
+static int dw_hdmi_v4l2_init_notifier(struct dw_hdmi_dev *dw_dev)
+{
+	struct v4l2_async_subdev **subdevs = NULL;
+	struct device_node *child = NULL;
+
+	subdevs = devm_kzalloc(dw_dev->dev, sizeof(*subdevs), GFP_KERNEL);
+	if (!subdevs)
+		return -ENOMEM;
+
+	child = dw_hdmi_get_phy_of_node(dw_dev, NULL);
+	if (!child)
+		return -EINVAL;
+
+	dw_dev->phy_async_sd.match.fwnode.fwnode = of_fwnode_handle(child);
+	dw_dev->phy_async_sd.match_type = V4L2_ASYNC_MATCH_FWNODE;
+
+	subdevs[0] = &dw_dev->phy_async_sd;
+	dw_dev->v4l2_notifier.num_subdevs = 1;
+	dw_dev->v4l2_notifier.subdevs = subdevs;
+	dw_dev->v4l2_notifier.ops = &dw_hdmi_notifier_ops;
+	return 0;
+}
+
+static int dw_hdmi_parse_notifier(struct dw_hdmi_dev *dw_dev)
+{
+#if IS_ENABLED(CONFIG_VIDEO_DWC_HDMI_RX_CEC)
+	struct device_node *notifier, *np = dw_dev->of_node;
+
+	/* Notifier device parsing */
+	notifier = of_parse_phandle(np, "edid-phandle", 0);
+	if (!notifier && dw_dev->dev->parent)
+		notifier = dw_dev->dev->parent->of_node;
+
+	if (!notifier) {
+		dev_err(dw_dev->dev, "missing edid-phandle in DT\n");
+		return -EINVAL;
+	}
+
+	dw_dev->notifier_pdev = of_find_device_by_node(notifier);
+	if (!dw_dev->notifier_pdev)
+		return -EPROBE_DEFER;
+
+	return 0;
+#else
+	return 0;
+#endif
+}
+
+static int dw_hdmi_parse_dt(struct dw_hdmi_dev *dw_dev)
+{
+	struct device_node *phy_node, *np = dw_dev->of_node;
+	u32 tmp;
+	int ret;
+
+	if (!np) {
+		dev_err(dw_dev->dev, "missing DT node\n");
+		return -EINVAL;
+	}
+
+	/* PHY properties parsing */
+	phy_node = dw_hdmi_get_phy_of_node(dw_dev, NULL);
+	of_property_read_u32(phy_node, "reg", &tmp);
+
+	dw_dev->phy_jtag_addr = tmp & 0xff;
+	if (!dw_dev->phy_jtag_addr) {
+		dev_err(dw_dev->dev, "missing phy jtag address in DT\n");
+		return -EINVAL;
+	}
+
+	/* Get config clock value */
+	dw_dev->clk = devm_clk_get(dw_dev->dev, "cfg");
+	if (IS_ERR(dw_dev->clk)) {
+		dev_err(dw_dev->dev, "failed to get cfg clock\n");
+		return PTR_ERR(dw_dev->clk);
+	}
+
+	ret = clk_prepare_enable(dw_dev->clk);
+	if (ret) {
+		dev_err(dw_dev->dev, "failed to enable cfg clock\n");
+		return ret;
+	}
+
+	dw_dev->cfg_clk = clk_get_rate(dw_dev->clk) / 1000000U;
+	if (!dw_dev->cfg_clk) {
+		dev_err(dw_dev->dev, "invalid cfg clock frequency\n");
+		ret = -EINVAL;
+		goto err_clk;
+	}
+
+	ret = dw_hdmi_parse_notifier(dw_dev);
+	if (ret)
+		goto err_clk;
+
+	return 0;
+
+err_clk:
+	clk_disable_unprepare(dw_dev->clk);
+	return ret;
+}
+
+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 v4l2_ctrl_handler *hdl;
+	struct dw_hdmi_dev *dw_dev;
+	struct v4l2_subdev *sd;
+	struct resource *res;
+	int ret, irq;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	dw_dev = devm_kzalloc(dev, sizeof(*dw_dev), GFP_KERNEL);
+	if (!dw_dev)
+		return -ENOMEM;
+
+	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;
+	spin_lock_init(&dw_dev->lock);
+
+	ret = dw_hdmi_parse_dt(dw_dev);
+	if (ret)
+		return ret;
+
+	/* Deferred work */
+	dw_dev->wq = create_singlethread_workqueue(DW_HDMI_RX_DRVNAME);
+	if (!dw_dev->wq) {
+		dev_err(dev, "failed to create workqueue\n");
+		return -ENOMEM;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	dw_dev->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(dw_dev->regs)) {
+		dev_err(dev, "failed to remap resource\n");
+		ret = PTR_ERR(dw_dev->regs);
+		goto err_wq;
+	}
+
+	/* Disable HPD as soon as posssible */
+	dw_hdmi_disable_hpd(dw_dev);
+	/* Prevent HDCP from tampering video */
+	dw_hdmi_config_hdcp(dw_dev);
+
+	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;
+
+	/* V4L2 initialization */
+	sd = &dw_dev->sd;
+	v4l2_subdev_init(sd, &dw_hdmi_sd_ops);
+	strlcpy(sd->name, dev_name(dev), sizeof(sd->name));
+	sd->dev = dev;
+	sd->internal_ops = &dw_hdmi_internal_ops;
+	sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+	/* Control handlers */
+	hdl = &dw_dev->hdl;
+	v4l2_ctrl_handler_init(hdl, 1);
+	dw_dev->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL,
+			V4L2_CID_DV_RX_POWER_PRESENT, 0, BIT(4) - 1, 0, 0);
+
+	sd->ctrl_handler = hdl;
+	if (hdl->error) {
+		ret = hdl->error;
+		goto err_hdl;
+	}
+
+	/* Wait for ctrl handler register before requesting 5v interrupt */
+	irq = platform_get_irq(pdev, 1);
+	if (irq < 0) {
+		ret = irq;
+		goto err_hdl;
+	}
+
+	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_hdl;
+
+	/* Notifier for subdev binding */
+	ret = dw_hdmi_v4l2_init_notifier(dw_dev);
+	if (ret) {
+		dev_err(dev, "failed to init v4l2 notifier\n");
+		goto err_hdl;
+	}
+

drivers/media/platform/dwc/dw-hdmi-rx.c line 1744

Quoted 10 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +10 -0
+	/* PHY loading */
+	ret = dw_hdmi_phy_init(dw_dev);
+	if (ret)
+		goto err_hdl;
+
+	/* CEC */
+#if IS_ENABLED(CONFIG_VIDEO_DWC_HDMI_RX_CEC)
+	dw_dev->cec_adap = cec_allocate_adapter(&dw_hdmi_cec_adap_ops,
+			dw_dev, dev_name(dev), CEC_CAP_TRANSMIT |
+			CEC_CAP_LOG_ADDRS | CEC_CAP_RC | CEC_CAP_PASSTHROUGH,

Use CEC_CAP_DEFAULTS instead of specifying these caps separately.

Quoted 604 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +90 -0
+			HDMI_CEC_MAX_LOG_ADDRS);
+	ret = PTR_ERR_OR_ZERO(dw_dev->cec_adap);
+	if (ret) {
+		dev_err(dev, "failed to allocate CEC adapter\n");
+		goto err_cec;
+	}
+
+	dw_dev->cec_notifier = cec_notifier_get(&dw_dev->notifier_pdev->dev);
+	if (!dw_dev->cec_notifier) {
+		dev_err(dev, "failed to allocate CEC notifier\n");
+		ret = -ENOMEM;
+		goto err_cec;
+	}
+
+	dev_info(dev, "CEC is enabled\n");
+#else
+	dev_info(dev, "CEC is disabled\n");
+#endif
+
+	ret = v4l2_async_register_subdev(sd);
+	if (ret) {
+		dev_err(dev, "failed to register subdev\n");
+		goto err_cec;
+	}
+
+	ret = v4l2_async_subdev_notifier_register(sd, &dw_dev->v4l2_notifier);
+	if (ret) {
+		dev_err(dev, "failed to register notifier\n");
+		goto err_subdev;
+	}
+
+	/* Fill initial format settings */
+	dw_dev->timings = timings_def;
+	dw_dev->mbus_code = MEDIA_BUS_FMT_BGR888_1X24;
+
+	dev_set_drvdata(dev, sd);
+	dw_dev->state = HDMI_STATE_POWER_OFF;
+	dw_hdmi_detect_tx_5v(dw_dev);
+	dev_dbg(dev, "driver probed\n");
+	return 0;
+
+err_subdev:
+	v4l2_async_unregister_subdev(sd);
+err_cec:
+	cec_delete_adapter(dw_dev->cec_adap);
+	dw_hdmi_phy_exit(dw_dev);
+err_hdl:
+	v4l2_ctrl_handler_free(hdl);
+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);
+
+	dw_hdmi_disable_ints(dw_dev);
+	dw_hdmi_disable_hpd(dw_dev);
+	dw_hdmi_disable_scdc(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);
+	dw_hdmi_phy_exit(dw_dev);
+	v4l2_async_notifier_unregister(&dw_dev->v4l2_notifier);
+	v4l2_async_unregister_subdev(sd);
+	v4l2_ctrl_handler_free(sd->ctrl_handler);
+	clk_disable_unprepare(dw_dev->clk);
+	dev_dbg(dev, "driver removed\n");
+	return 0;
+}
+
+static const struct of_device_id dw_hdmi_rx_id[] = {
+	{ .compatible = "snps,dw-hdmi-rx" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, dw_hdmi_rx_id);
+
+static struct platform_driver dw_hdmi_rx_driver = {
+	.probe = dw_hdmi_rx_probe,
+	.remove = dw_hdmi_rx_remove,
+	.driver = {
+		.name = DW_HDMI_RX_DRVNAME,
+		.of_match_table = dw_hdmi_rx_id,
+	}
+};
+module_platform_driver(dw_hdmi_rx_driver);
drivers/media/platform/dwc/dw-hdmi-rx.h new file +441 -0
new file mode 100644
index 0000000..14ec5a6
@@ -0,0 +1,441 @@
+/*
+ * Synopsys Designware HDMI Receiver controller driver
+ *
+ * This Synopsys dw-hdmi-rx software and associated documentation
+ * (hereinafter the "Software") is an unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing between
+ * Synopsys and you. The Software IS NOT an item of Licensed Software or a
+ * Licensed Product under any End User Software License Agreement or
+ * Agreement for Licensed Products with Synopsys or any supplement thereto.
+ * Synopsys is a registered trademark of Synopsys, Inc. Other names included
+ * in the SOFTWARE may be the trademarks of their respective owners.
+ *
+ * The contents of this file are dual-licensed; you may select either version 2
+ * of the GNU General Public License (“GPL”) or the MIT license (“MIT”).
+ *
+ * Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS"  WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE
+ * ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __DW_HDMI_RX_H__
+#define __DW_HDMI_RX_H__
+
+#include <linux/bitops.h>
+
+/* id_hdmi Registers */
+#define HDMI_SETUP_CTRL				0x0000
+#define HDMI_PLL_LCK_STS			0x0030
+#define HDMI_CKM_EVLTM				0x0094
+#define HDMI_CKM_RESULT				0x009c
+#define HDMI_STS				0x00bc
+
+/* id_hdcp_1_4 Registers */
+#define HDMI_HDCP_CTRL				0x00c0
+#define HDMI_HDCP_SETTINGS			0x00c4
+#define HDMI_HDCP_SEED				0x00c8
+#define HDMI_HDCP_BKSV1				0x00cc
+#define HDMI_HDCP_BKSV0				0x00d0
+#define HDMI_HDCP_KIDX				0x00d4
+#define HDMI_HDCP_KEY1				0x00d8
+#define HDMI_HDCP_KEY0				0x00dc
+#define HDMI_HDCP_DBG				0x00e0
+#define HDMI_HDCP_AKSV1				0x00e4
+#define HDMI_HDCP_AKSV0				0x00e8
+#define HDMI_HDCP_AN1				0x00ec
+#define HDMI_HDCP_AN0				0x00f0
+#define HDMI_HDCP_EESS_WOO			0x00f4
+#define HDMI_HDCP_I2C_TIMEOUT			0x00f8
+#define HDMI_HDCP_STS				0x00fc
+
+/* id_mode_detection Registers */
+#define HDMI_MD_HT0				0x0148
+#define HDMI_MD_HT1				0x014c
+#define HDMI_MD_HACT_PX				0x0150
+#define HDMI_MD_VCTRL				0x0158
+#define HDMI_MD_VOL				0x0164
+#define HDMI_MD_VAL				0x0168
+#define HDMI_MD_VTL				0x0170
+#define HDMI_MD_STS				0x0180
+
+/* id_phy_configuration Registers */
+#define HDMI_PHY_CTRL				0x02c0
+#define HDMI_PHY_JTAG_CONF			0x02ec
+#define HDMI_PHY_JTAG_TAP_TCLK			0x02f0
+#define HDMI_PHY_JTAG_TAP_IN			0x02f4
+#define HDMI_PHY_JTAG_TAP_OUT			0x02f8
+#define HDMI_PHY_JTAG_ADDR			0x02fc
+
+/* id_packet_decoder Registers */
+#define HDMI_PDEC_STS				0x0360
+#define HDMI_PDEC_VSI_PAYLOAD0			0x0368
+#define HDMI_PDEC_AVI_PB			0x03a4
+
+/* id_hdmi_2_0 Registers */
+#define HDMI_SCDC_CONFIG			0x0808
+#define HDMI_HDCP22_CONTROL			0x081c
+#define HDMI_HDCP22_STATUS			0x08fc
+
+/* id_audio_and_cec_interrupt Registers */
+#define HDMI_AUD_CEC_IEN_CLR			0x0f90
+#define HDMI_AUD_CEC_IEN_SET			0x0f94
+#define HDMI_AUD_CEC_ISTS			0x0f98
+#define HDMI_AUD_CEC_IEN			0x0f9c
+#define HDMI_AUD_CEC_ICLR			0x0fa0
+#define HDMI_AUD_CEC_ISET			0x0fa4
+
+/* id_mode_detection_interrupt Registers */
+#define HDMI_MD_IEN_CLR				0x0fc0
+#define HDMI_MD_IEN_SET				0x0fc4
+#define HDMI_MD_ISTS				0x0fc8
+#define HDMI_MD_IEN				0x0fcc
+#define HDMI_MD_ICLR				0x0fd0
+#define HDMI_MD_ISET				0x0fd4
+
+/* id_hdmi_interrupt Registers */
+#define HDMI_IEN_CLR				0x0fd8
+#define HDMI_IEN_SET				0x0fdc
+#define HDMI_ISTS				0x0fe0
+#define HDMI_IEN				0x0fe4
+#define HDMI_ICLR				0x0fe8
+#define HDMI_ISET				0x0fec
+
+/* id_dmi Registers */
+#define HDMI_DMI_SW_RST				0x0ff0
+
+/* id_cec Registers */
+#define HDMI_CEC_CTRL				0x1f00
+#define HDMI_CEC_MASK				0x1f08
+#define HDMI_CEC_ADDR_L				0x1f14
+#define HDMI_CEC_ADDR_H				0x1f18
+#define HDMI_CEC_TX_CNT				0x1f1c
+#define HDMI_CEC_RX_CNT				0x1f20
+#define HDMI_CEC_TX_DATA(i)			(0x1f40 + ((i) * 4))
+#define HDMI_CEC_TX_DATA_MAX			16
+#define HDMI_CEC_RX_DATA(i)			(0x1f80 + ((i) * 4))
+#define HDMI_CEC_RX_DATA_MAX			16
+#define HDMI_CEC_LOCK				0x1fc0
+#define HDMI_CEC_WAKEUPCTRL			0x1fc4
+
+/* id_cbus Registers */
+#define HDMI_CBUSIOCTRL				0x3020
+
+enum {
+	/* SETUP_CTRL field values */
+	HDMI_SETUP_CTRL_HOT_PLUG_DETECT_INPUT_X_MASK = GENMASK(27,24),
+	HDMI_SETUP_CTRL_HOT_PLUG_DETECT_INPUT_X_OFFSET = 24,
+	HDMI_SETUP_CTRL_HDMIBUS_RESET_OVR_EN_MASK = BIT(21),
+	HDMI_SETUP_CTRL_HDMIBUS_RESET_OVR_EN_OFFSET = 21,
+	HDMI_SETUP_CTRL_BUS_RESET_OVR_MASK = BIT(20),
+	HDMI_SETUP_CTRL_BUS_RESET_OVR_OFFSET = 20,
+	HDMI_SETUP_CTRL_HDMI_RESET_OVR_MASK = BIT(19),
+	HDMI_SETUP_CTRL_HDMI_RESET_OVR_OFFSET = 19,
+	HDMI_SETUP_CTRL_PON_RESET_OVR_MASK = BIT(18),
+	HDMI_SETUP_CTRL_PON_RESET_OVR_OFFSET = 18,
+	HDMI_SETUP_CTRL_RESET_OVR_MASK = BIT(17),
+	HDMI_SETUP_CTRL_RESET_OVR_OFFSET = 17,
+	HDMI_SETUP_CTRL_RESET_OVR_EN_MASK = BIT(16),
+	HDMI_SETUP_CTRL_RESET_OVR_EN_OFFSET = 16,
+	HDMI_SETUP_CTRL_EQ_OSM_OVR_MASK = BIT(15),
+	HDMI_SETUP_CTRL_EQ_OSM_OVR_OFFSET = 15,
+	HDMI_SETUP_CTRL_EQ_OSM_OVR_EN_MASK = BIT(14),
+	HDMI_SETUP_CTRL_EQ_OSM_OVR_EN_OFFSET = 14,
+	HDMI_SETUP_CTRL_NOWAIT_ACTIVITY_MASK = BIT(13),
+	HDMI_SETUP_CTRL_NOWAIT_ACTIVITY_OFFSET = 13,
+	HDMI_SETUP_CTRL_EQ_CAL_TIME_MASK = GENMASK(12,7),
+	HDMI_SETUP_CTRL_EQ_CAL_TIME_OFFSET = 7,
+	HDMI_SETUP_CTRL_USE_PLL_LOCK_MASK = BIT(6),
+	HDMI_SETUP_CTRL_USE_PLL_LOCK_OFFSET = 6,
+	HDMI_SETUP_CTRL_FORCE_STATE_MASK = BIT(5),
+	HDMI_SETUP_CTRL_FORCE_STATE_OFFSET = 5,
+	HDMI_SETUP_CTRL_TARGET_STATE_MASK = GENMASK(4,1),
+	HDMI_SETUP_CTRL_TARGET_STATE_OFFSET = 1,
+	HDMI_SETUP_CTRL_HOT_PLUG_DETECT_MASK = BIT(0),
+	HDMI_SETUP_CTRL_HOT_PLUG_DETECT_OFFSET = 0,
+	/* PLL_LCK_STS field values */
+	HDMI_PLL_LCK_STS_PLL_LOCKED = BIT(0),
+	/* CKM_EVLTM field values */
+	HDMI_CKM_EVLTM_LOCK_HYST_MASK = GENMASK(21,20),
+	HDMI_CKM_EVLTM_LOCK_HYST_OFFSET = 20,
+	HDMI_CKM_EVLTM_CLK_HYST_MASK = GENMASK(18,16),
+	HDMI_CKM_EVLTM_CLK_HYST_OFFSET = 16,
+	HDMI_CKM_EVLTM_EVAL_TIME_MASK = GENMASK(15,4),
+	HDMI_CKM_EVLTM_EVAL_TIME_OFFSET = 4,
+	HDMI_CKM_EVLTM_CLK_MEAS_INPUT_SRC_MASK = BIT(0),
+	HDMI_CKM_EVLTM_CLK_MEAS_INPUT_SRC_OFFSET = 0,
+	/* CKM_RESULT field values */
+	HDMI_CKM_RESULT_CLOCK_IN_RANGE = BIT(17),
+	HDMI_CKM_RESULT_FREQ_LOCKED = BIT(16),
+	HDMI_CKM_RESULT_CLKRATE_MASK = GENMASK(15,0),
+	HDMI_CKM_RESULT_CLKRATE_OFFSET = 0,
+	/* STS field values */
+	HDMI_STS_DCM_CURRENT_MODE_MASK = GENMASK(31,28),
+	HDMI_STS_DCM_CURRENT_MODE_OFFSET = 28,
+	HDMI_STS_DCM_LAST_PIXEL_PHASE_STS_MASK = GENMASK(27,24),
+	HDMI_STS_DCM_LAST_PIXEL_PHASE_STS_OFFSET = 24,
+	HDMI_STS_DCM_PHASE_DIFF_CNT_MASK = GENMASK(23,16),
+	HDMI_STS_DCM_PH_DIFF_CNT_OVERFL = BIT(15),
+	HDMI_STS_DCM_GCP_ZERO_FIELDS_PASS = BIT(14),
+	HDMI_STS_CTL3_STS = BIT(13),
+	HDMI_STS_CTL2_STS = BIT(12),
+	HDMI_STS_CTL1_STS = BIT(11),
+	HDMI_STS_CTL0_STS = BIT(10),
+	HDMI_STS_VS_POL_ADJ_STS = BIT(9),
+	HDMI_STS_HS_POL_ADJ_STS = BIT(8),
+	HDMI_STS_RES_OVERLOAD_STS = BIT(7),
+	HDMI_STS_DCM_CURRENT_PP_MASK = GENMASK(3,0),
+	HDMI_STS_DCM_CURRENT_PP_OFFSET = 0,
+	/* HDCP_CTRL field values */
+	HDMI_HDCP_CTRL_ENDISLOCK_MASK = BIT(25),
+	HDMI_HDCP_CTRL_ENDISLOCK_OFFSET = 25,
+	HDMI_HDCP_CTRL_ENABLE_MASK = BIT(24),
+	HDMI_HDCP_CTRL_ENABLE_OFFSET = 24,
+	HDMI_HDCP_CTRL_FREEZE_HDCP_FSM_MASK = BIT(21),
+	HDMI_HDCP_CTRL_FREEZE_HDCP_FSM_OFFSET = 21,
+	HDMI_HDCP_CTRL_FREEZE_HDCP_STATE_MASK = GENMASK(20,15),
+	HDMI_HDCP_CTRL_FREEZE_HDCP_STATE_OFFSET = 15,
+	HDMI_HDCP_CTRL_VID_DE_MASK = BIT(14),
+	HDMI_HDCP_CTRL_VID_DE_OFFSET = 14,
+	HDMI_HDCP_CTRL_SEL_AVMUTE_MASK = GENMASK(11,10),
+	HDMI_HDCP_CTRL_SEL_AVMUTE_OFFSET = 10,
+	HDMI_HDCP_CTRL_CTL_MASK = GENMASK(9,8),
+	HDMI_HDCP_CTRL_CTL_OFFSET = 8,
+	HDMI_HDCP_CTRL_RI_RATE_MASK = GENMASK(7,6),
+	HDMI_HDCP_CTRL_RI_RATE_OFFSET = 6,
+	HDMI_HDCP_CTRL_HDMI_MODE_ENABLE_MASK = BIT(2),
+	HDMI_HDCP_CTRL_HDMI_MODE_ENABLE_OFFSET = 2,
+	HDMI_HDCP_CTRL_KEY_DECRYPT_ENABLE_MASK = BIT(1),
+	HDMI_HDCP_CTRL_KEY_DECRYPT_ENABLE_OFFSET = 1,
+	HDMI_HDCP_CTRL_ENC_EN_MASK = BIT(0),
+	HDMI_HDCP_CTRL_ENC_EN_OFFSET = 0,
+	/* HDCP_SEED field values */
+	HDMI_HDCP_SEED_KEY_DECRYPT_SEED_MASK = GENMASK(15,0),
+	HDMI_HDCP_SEED_KEY_DECRYPT_SEED_OFFSET = 0,
+	/* HDCP_STS field values */
+	HDMI_HDCP_STS_ENC_STATE = BIT(9),
+	HDMI_HDCP_STS_AUTH_START = BIT(8),
+	HDMI_HDCP_STS_KEY_WR_OK = BIT(0),
+	/* MD_HT0 field values */
+	HDMI_MD_HT0_HTOT32_CLK_MASK = GENMASK(31,16),
+	HDMI_MD_HT0_HTOT32_CLK_OFFSET = 16,
+	HDMI_MD_HT0_HS_CLK_MASK = GENMASK(15,0),
+	HDMI_MD_HT0_HS_CLK_OFFSET = 0,
+	/* MD_HT1 field values */
+	HDMI_MD_HT1_HTOT_PIX_MASK = GENMASK(31,16),
+	HDMI_MD_HT1_HTOT_PIX_OFFSET = 16,
+	HDMI_MD_HT1_HOFS_PIX_MASK = GENMASK(15,0),
+	HDMI_MD_HT1_HOFS_PIX_OFFSET = 0,
+	/* MD_VCTRL field values */
+	HDMI_MD_VCTRL_V_OFFS_LIN_MODE_MASK = BIT(4),
+	HDMI_MD_VCTRL_V_OFFS_LIN_MODE_OFFSET = 4,
+	HDMI_MD_VCTRL_V_EDGE_MASK = BIT(1),
+	HDMI_MD_VCTRL_V_EDGE_OFFSET = 1,
+	HDMI_MD_VCTRL_V_MODE_MASK = BIT(0),
+	HDMI_MD_VCTRL_V_MODE_OFFSET = 0,
+	/* MD_STS field values */
+	HDMI_MD_STS_ILACE = BIT(3),
+	HDMI_MD_STS_DE_ACTIVITY = BIT(2),
+	HDMI_MD_STS_VS_ACT = BIT(1),
+	HDMI_MD_STS_HS_ACT = BIT(0),
+	/* PHY_CTRL field values */
+	HDMI_PHY_CTRL_SVSRETMODEZ_MASK = BIT(6),
+	HDMI_PHY_CTRL_SVSRETMODEZ_OFFSET = 6,
+	HDMI_PHY_CTRL_CFGCLKFREQ_MASK = GENMASK(5,4),
+	HDMI_PHY_CTRL_CFGCLKFREQ_OFFSET = 4,
+	HDMI_PHY_CTRL_PORTSELECT_MASK = GENMASK(3,2),
+	HDMI_PHY_CTRL_PORTSELECT_OFFSET = 2,
+	HDMI_PHY_CTRL_PDDQ_MASK = BIT(1),
+	HDMI_PHY_CTRL_PDDQ_OFFSET = 1,
+	HDMI_PHY_CTRL_RESET_MASK = BIT(0),
+	HDMI_PHY_CTRL_RESET_OFFSET = 0,
+	/* PHY_JTAG_TAP_IN field values */
+	HDMI_PHY_JTAG_TAP_IN_TMS = BIT(4),
+	HDMI_PHY_JTAG_TAP_IN_TDI = BIT(0),
+	/* PDEC_STS field values */
+	HDMI_PDEC_STS_DRM_CKS_CHG = BIT(31),
+	HDMI_PDEC_STS_DRM_RCV = BIT(30),
+	HDMI_PDEC_STS_NTSCVBI_CKS_CHG = BIT(29),
+	HDMI_PDEC_STS_DVIDET = BIT(28),
+	HDMI_PDEC_STS_VSI_CKS_CHG = BIT(27),
+	HDMI_PDEC_STS_GMD_CKS_CHG = BIT(26),
+	HDMI_PDEC_STS_AIF_CKS_CHG = BIT(25),
+	HDMI_PDEC_STS_AVI_CKS_CHG = BIT(24),
+	HDMI_PDEC_STS_ACR_N_CHG = BIT(23),
+	HDMI_PDEC_STS_ACR_CTS_CHG = BIT(22),
+	HDMI_PDEC_STS_GCP_AV_MUTE_CHG = BIT(21),
+	HDMI_PDEC_STS_GMD_RCV = BIT(20),
+	HDMI_PDEC_STS_AIF_RCV = BIT(19),
+	HDMI_PDEC_STS_AVI_RCV = BIT(18),
+	HDMI_PDEC_STS_ACR_RCV = BIT(17),
+	HDMI_PDEC_STS_GCP_RCV = BIT(16),
+	HDMI_PDEC_STS_VSI_RCV = BIT(15),
+	HDMI_PDEC_STS_AMP_RCV = BIT(14),
+	HDMI_PDEC_STS_NTSCVBI_RCV = BIT(13),
+	HDMI_PDEC_STS_OBA_LAYOUT = BIT(12),
+	HDMI_PDEC_STS_AUDS_LAYOUT = BIT(11),
+	HDMI_PDEC_STS_PD_FIFO_NEW_ENTRY = BIT(8),
+	HDMI_PDEC_STS_PD_FIFO_OVERFL = BIT(4),
+	HDMI_PDEC_STS_PD_FIFO_UNDERFL = BIT(3),
+	HDMI_PDEC_STS_PD_FIFO_TH_START_PASS = BIT(2),
+	HDMI_PDEC_STS_PD_FIFO_TH_MAX_PASS = BIT(1),
+	HDMI_PDEC_STS_PD_FIFO_TH_MIN_PASS = BIT(0),
+	/* PDEC_VSI_PAYLOAD0 field values */
+	HDMI_PDEC_VSI_PAYLOAD0_HDMI_VIC_MASK = GENMASK(15,8),
+	HDMI_PDEC_VSI_PAYLOAD0_HDMI_VIC_OFFSET = 8,
+	/* PDEC_AVI_PB field values */
+	HDMI_PDEC_AVI_PB_VID_IDENT_CODE_MASK = GENMASK(31,24),
+	HDMI_PDEC_AVI_PB_VID_IDENT_CODE_OFFSET = 24,
+	HDMI_PDEC_AVI_PB_IT_CONTENT = BIT(23),
+	HDMI_PDEC_AVI_PB_EXT_COLORIMETRY_MASK = GENMASK(22,20),
+	HDMI_PDEC_AVI_PB_EXT_COLORIMETRY_OFFSET = 20,
+	HDMI_PDEC_AVI_PB_RGB_QUANT_RANGE_MASK = GENMASK(19,18),
+	HDMI_PDEC_AVI_PB_RGB_QUANT_RANGE_OFFSET = 18,
+	HDMI_PDEC_AVI_PB_NON_UNIF_SCALE_MASK = GENMASK(17,16),
+	HDMI_PDEC_AVI_PB_NON_UNIF_SCALE_OFFSET = 16,
+	HDMI_PDEC_AVI_PB_COLORIMETRY_MASK = GENMASK(15,14),
+	HDMI_PDEC_AVI_PB_COLORIMETRY_OFFSET = 14,
+	HDMI_PDEC_AVI_PB_PIC_ASPECT_RAT_MASK = GENMASK(13,12),
+	HDMI_PDEC_AVI_PB_PIC_ASPECT_RAT_OFFSET = 12,
+	HDMI_PDEC_AVI_PB_ACT_ASPECT_RAT_MASK = GENMASK(11,8),
+	HDMI_PDEC_AVI_PB_ACT_ASPECT_RAT_OFFSET = 8,
+	HDMI_PDEC_AVI_PB_VIDEO_FORMAT_MASK = GENMASK(7,5),
+	HDMI_PDEC_AVI_PB_VIDEO_FORMAT_OFFSET = 5,
+	HDMI_PDEC_AVI_PB_ACT_INFO_PRESENT = BIT(4),
+	HDMI_PDEC_AVI_PB_BAR_INFO_VALID_MASK = GENMASK(3,2),
+	HDMI_PDEC_AVI_PB_BAR_INFO_VALID_OFFSET = 2,
+	HDMI_PDEC_AVI_PB_SCAN_INFO_MASK = GENMASK(1,0),
+	HDMI_PDEC_AVI_PB_SCAN_INFO_OFFSET = 0,
+	/* SCDC_CONFIG field values */
+	HDMI_SCDC_CONFIG_HPDLOW_MASK = BIT(1),
+	HDMI_SCDC_CONFIG_HPDLOW_OFFSET = 1,
+	HDMI_SCDC_CONFIG_POWERPROVIDED_MASK = BIT(0),
+	HDMI_SCDC_CONFIG_POWERPROVIDED_OFFSET = 0,
+	/* HDCP22_CONTROL field values */
+	HDMI_HDCP22_CONTROL_CD_OVR_VAL_MASK = GENMASK(23,20),
+	HDMI_HDCP22_CONTROL_CD_OVR_VAL_OFFSET = 20,
+	HDMI_HDCP22_CONTROL_CD_OVR_EN_MASK = BIT(16),
+	HDMI_HDCP22_CONTROL_CD_OVR_EN_OFFSET = 16,
+	HDMI_HDCP22_CONTROL_HPD_MASK = BIT(12),
+	HDMI_HDCP22_CONTROL_HPD_OFFSET = 12,
+	HDMI_HDCP22_CONTROL_PKT_ERR_OVR_VAL_MASK = BIT(9),
+	HDMI_HDCP22_CONTROL_PKT_ERR_OVR_VAL_OFFSET= 9,
+	HDMI_HDCP22_CONTROL_PKT_ERR_OVR_EN_MASK = BIT(8),
+	HDMI_HDCP22_CONTROL_PKT_ERR_OVR_EN_OFFSET = 8,
+	HDMI_HDCP22_CONTROL_AVMUTE_OVR_VAL_MASK = BIT(5),
+	HDMI_HDCP22_CONTROL_AVMUTE_OVR_VAL_OFFSET = 5,
+	HDMI_HDCP22_CONTROL_AVMUTE_OVR_EN_MASK = BIT(4),
+	HDMI_HDCP22_CONTROL_AVMUTE_OVR_EN_OFFSET = 4,
+	HDMI_HDCP22_CONTROL_OVR_VAL_MASK = BIT(2),
+	HDMI_HDCP22_CONTROL_OVR_VAL_OFFSET = 2,
+	HDMI_HDCP22_CONTROL_OVR_EN_MASK = BIT(1),
+	HDMI_HDCP22_CONTROL_OVR_EN_OFFSET = 1,
+	HDMI_HDCP22_CONTROL_SWITCH_LCK_MASK = BIT(0),
+	HDMI_HDCP22_CONTROL_SWITCH_LCK_OFFSET = 0,
+	/* AUD_CEC_ISTS field values */
+	HDMI_AUD_CEC_ISTS_WAKEUPCTRL = BIT(22),
+	HDMI_AUD_CEC_ISTS_ERROR_FOLL = BIT(21),
+	HDMI_AUD_CEC_ISTS_ERROR_INIT = BIT(20),
+	HDMI_AUD_CEC_ISTS_ARBLST = BIT(19),
+	HDMI_AUD_CEC_ISTS_NACK = BIT(18),
+	HDMI_AUD_CEC_ISTS_EOM = BIT(17),
+	HDMI_AUD_CEC_ISTS_DONE = BIT(16),
+	HDMI_AUD_CEC_ISTS_SCK_STABLE = BIT(1),
+	HDMI_AUD_CEC_ISTS_CTSN_CNT = BIT(0),
+	/* MD_ISTS field values */
+	HDMI_MD_ISTS_VOFS_LIN = BIT(11),
+	HDMI_MD_ISTS_VTOT_LIN = BIT(10),
+	HDMI_MD_ISTS_VACT_LIN = BIT(9),
+	HDMI_MD_ISTS_VS_CLK = BIT(8),
+	HDMI_MD_ISTS_VTOT_CLK = BIT(7),
+	HDMI_MD_ISTS_HACT_PIX = BIT(6),
+	HDMI_MD_ISTS_HS_CLK = BIT(5),
+	HDMI_MD_ISTS_HTOT32_CLK = BIT(4),
+	HDMI_MD_ISTS_ILACE = BIT(3),
+	HDMI_MD_ISTS_DE_ACTIVITY = BIT(2),
+	HDMI_MD_ISTS_VS_ACT = BIT(1),
+	HDMI_MD_ISTS_HS_ACT = BIT(0),
+	/* ISTS field values */
+	HDMI_ISTS_I2CMP_ARBLOST = BIT(30),
+	HDMI_ISTS_I2CMPNACK = BIT(29),
+	HDMI_ISTS_I2CMPDONE = BIT(28),
+	HDMI_ISTS_VS_THR_REACHED = BIT(27),
+	HDMI_ISTS_VSYNC_ACT_EDGE = BIT(26),
+	HDMI_ISTS_AKSV_RCV = BIT(25),
+	HDMI_ISTS_PLL_CLOCK_GATED = BIT(24),
+	HDMI_ISTS_DESER_MISAL = BIT(23),
+	HDMI_ISTS_CDSENSE_CHG = BIT(22),
+	HDMI_ISTS_CEAVID_EMPTY = BIT(21),
+	HDMI_ISTS_CEAVID_FULL = BIT(20),
+	HDMI_ISTS_SCDCTMDSCFGCHANGE = BIT(19),
+	HDMI_ISTS_SCDCSCSTATUSCHANGE = BIT(18),
+	HDMI_ISTS_SCDCCFGCHANGE = BIT(17),
+	HDMI_ISTS_DCM_CURRENT_MODE_CHG = BIT(16),
+	HDMI_ISTS_DCM_PH_DIFF_CNT_OVERFL = BIT(15),
+	HDMI_ISTS_DCM_GCP_ZERO_FIELDS_PASS = BIT(14),
+	HDMI_ISTS_CTL3_CHANGE = BIT(13),
+	HDMI_ISTS_CTL2_CHANGE = BIT(12),
+	HDMI_ISTS_CTL1_CHANGE = BIT(11),
+	HDMI_ISTS_CTL0_CHANGE = BIT(10),
+	HDMI_ISTS_VS_POL_ADJ = BIT(9),
+	HDMI_ISTS_HS_POL_ADJ = BIT(8),
+	HDMI_ISTS_RES_OVERLOAD = BIT(7),
+	HDMI_ISTS_CLK_CHANGE = BIT(6),
+	HDMI_ISTS_PLL_LCK_CHG = BIT(5),
+	HDMI_ISTS_EQGAIN_DONE = BIT(4),
+	HDMI_ISTS_OFFSCAL_DONE = BIT(3),
+	HDMI_ISTS_RESCAL_DONE = BIT(2),
+	HDMI_ISTS_ACT_CHANGE = BIT(1),
+	HDMI_ISTS_STATE_REACHED = BIT(0),
+	/* DMI_SW_RST field values */
+	HDMI_DMI_SW_RST_TMDS = BIT(16),
+	HDMI_DMI_SW_RST_HDCP = BIT(8),
+	HDMI_DMI_SW_RST_VID = BIT(7),
+	HDMI_DMI_SW_RST_PIXEL = BIT(6),
+	HDMI_DMI_SW_RST_CEC = BIT(5),
+	HDMI_DMI_SW_RST_AUD = BIT(4),
+	HDMI_DMI_SW_RST_BUS = BIT(3),
+	HDMI_DMI_SW_RST_HDMI = BIT(2),
+	HDMI_DMI_SW_RST_MODET = BIT(1),
+	HDMI_DMI_SW_RST_MAIN = BIT(0),
+	/* CEC_CTRL field values */
+	HDMI_CEC_CTRL_STANDBY_MASK = BIT(4),
+	HDMI_CEC_CTRL_STANDBY_OFFSET = 4,
+	HDMI_CEC_CTRL_BC_NACK_MASK = BIT(3),
+	HDMI_CEC_CTRL_BC_NACK_OFFSET = 3,
+	HDMI_CEC_CTRL_FRAME_TYP_MASK = GENMASK(2,1),
+	HDMI_CEC_CTRL_FRAME_TYP_OFFSET = 1,
+	HDMI_CEC_CTRL_SEND_MASK = BIT(0),
+	HDMI_CEC_CTRL_SEND_OFFSET = 0,
+	/* CEC_MASK field values */
+	HDMI_CEC_MASK_WAKEUP_MASK = BIT(6),
+	HDMI_CEC_MASK_WAKEUP_OFFSET = 6,
+	HDMI_CEC_MASK_ERROR_FLOW_MASK = BIT(5),
+	HDMI_CEC_MASK_ERROR_FLOW_OFFSET = 5,
+	HDMI_CEC_MASK_ERROR_INITITATOR_MASK = BIT(4),
+	HDMI_CEC_MASK_ERROR_INITITATOR_OFFSET = 4,
+	HDMI_CEC_MASK_ARB_LOST_MASK = BIT(3),
+	HDMI_CEC_MASK_ARB_LOST_OFFSET = 3,
+	HDMI_CEC_MASK_NACK_MASK = BIT(2),
+	HDMI_CEC_MASK_NACK_OFFSET = 2,
+	HDMI_CEC_MASK_EOM_MASK = BIT(1),
+	HDMI_CEC_MASK_EOM_OFFSET = 1,
+	HDMI_CEC_MASK_DONE_MASK = BIT(0),
+	HDMI_CEC_MASK_DONE_OFFSET = 0,
+	/* CBUSIOCTRL field values */
+	HDMI_CBUSIOCTRL_DATAPATH_CBUSZ_MASK = BIT(24),
+	HDMI_CBUSIOCTRL_DATAPATH_CBUSZ_OFFSET = 24,
+	HDMI_CBUSIOCTRL_SVSRETMODEZ_MASK = BIT(16),
+	HDMI_CBUSIOCTRL_SVSRETMODEZ_OFFSET = 16,
+	HDMI_CBUSIOCTRL_PDDQ_MASK = BIT(8),
+	HDMI_CBUSIOCTRL_PDDQ_OFFSET = 8,
+	HDMI_CBUSIOCTRL_RESET_MASK = BIT(0),
+	HDMI_CBUSIOCTRL_RESET_OFFSET = 0,
+};
+
+#endif /* __DW_HDMI_RX_H__ */
include/media/dwc/dw-hdmi-rx-pdata.h new file +60 -0
new file mode 100644
index 0000000..41d5c8d
@@ -0,0 +1,70 @@
+/*
+ * 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 - Platform Data configuration for HDMI receiver.
+ *
+ * @dw_5v_status: 5v status callback. Shall return the status of the given
+ * input, i.e. shall be true if a cable is connected to the specified input.
+ *
+ * @dw_5v_clear: 5v clear callback. Shall clear the interrupt associated with
+ * the 5v sense controller.
+ *
+ * @dw_5v_arg: Argument to be used with the 5v sense callbacks.
+ *
+ * @dw_zcal_reset: Impedance calibration reset callback. Shall be called when
+ * the impedance calibration needs to be restarted. This is used by phy driver
+ * only.
+ *
+ * @dw_zcal_done: Impedance calibration status callback. Shall return true if
+ * the impedance calibration procedure has ended. This is used by phy driver
+ * only.
+ *
+ * @dw_zcal_arg: Argument to be used with the ZCAL calibration callbacks.
+ */
+struct dw_hdmi_rx_pdata {
+	/* 5V sense interface */

include/media/dwc/dw-hdmi-rx-pdata.h line 70

Quoted 10 lines
include/media/dwc/dw-hdmi-rx-pdata.h new file +10 -0
+	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__ */

Regards,

Hans

author
#6 lore raw

Re: [PATCH v9 4/4] [media] platform: Add Synopsys DesignWare HDMI RX Controller Driver

Hi Hans,

Quoted 3 lines · Hans Verkuil

Hi Jose,

Some (small) comments below:

Thanks for the review!

Quoted 105 lines · Jose Abreu
Quoted 86 lines

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

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

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

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

Cc Joao Pinto <[email protected]>
Cc Mauro Carvalho Chehab <[email protected]>
Cc Hans Verkuil <[email protected]>
Cc Sylwester Nawrocki <[email protected]>
Cc Sakari Ailus <[email protected]>

---
Changes from v8:
- Incorporate Sakari's work on ASYNC subdevs
Changes from v6:
- edid-phandle now also looks for parent node (Sylwester)
- Fix kbuild build warnings
Changes from v5:
- Removed HDCP 1.4 support (Hans)
- Removed some CEC debug messages (Hans)
- Add s_dv_timings callback (Hans)
- Add V4L2_CID_DV_RX_POWER_PRESENT ctrl (Hans)
Changes from v4:
- Add flag V4L2_SUBDEV_FL_HAS_DEVNODE (Sylwester)
- Remove some comments and change some messages to dev_dbg (Sylwester)
- Use v4l2_async_subnotifier_register() (Sylwester)
Changes from v3:
- Use v4l2 async API (Sylwester)
- Do not block waiting for phy
- Do not use busy waiting delays (Sylwester)
- Simplify dw_hdmi_power_on (Sylwester)
- Use clock API (Sylwester)
- Use compatible string (Sylwester)
- Minor fixes (Sylwester)
Changes from v2:
- Address review comments from Hans regarding CEC
- Use CEC notifier
- Enable SCDC
Changes from v1:
- Add support for CEC
- Correct typo errors
- Correctly detect interlaced video modes
- Correct VIC parsing
Changes from RFC:
- Add support for HDCP 1.4
- Fixup HDMI_VIC not being parsed (Hans)
- Send source change signal when powering off (Hans)
- Add a "wait stable delay"
- Detect interlaced video modes (Hans)
- Restrain g/s_register from reading/writing to HDCP regs (Hans)

---
 drivers/media/platform/dwc/Kconfig      |   15 +
 drivers/media/platform/dwc/Makefile     |    1 +
 drivers/media/platform/dwc/dw-hdmi-rx.c | 1834 +++++++++++++++++++++++++++++++
 drivers/media/platform/dwc/dw-hdmi-rx.h |  441 ++++++++
 include/media/dwc/dw-hdmi-rx-pdata.h    |   70 ++
 5 files changed, 2361 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

<snip>

Quoted 15 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +15 -0
+static void dw_hdmi_cec_tx_raw_status(struct dw_hdmi_dev *dw_dev, u32 stat)
+{
+	if (hdmi_readl(dw_dev, HDMI_CEC_CTRL) & HDMI_CEC_CTRL_SEND_MASK) {
+		dev_dbg(dw_dev->dev, "%s: tx is busy\n", __func__);
+		return;
+	}
+
+	if (stat & HDMI_AUD_CEC_ISTS_ARBLST) {
+		cec_transmit_attempt_done(dw_dev->cec_adap,
+				CEC_TX_STATUS_ARB_LOST);
+		return;
+	}
+
+	if (stat & HDMI_AUD_CEC_ISTS_NACK) {
+		cec_transmit_attempt_done(dw_dev->cec_adap, CEC_TX_STATUS_NACK);
Quoted 8 lines

drivers/media/platform/dwc/dw-hdmi-rx.c line 630

Quoted 6 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +6 -0
+		return;
+	}
+
+	if (stat & HDMI_AUD_CEC_ISTS_ERROR_INIT) {
+		dev_dbg(dw_dev->dev, "%s: got initiator error\n", __func__);
+		cec_transmit_attempt_done(dw_dev->cec_adap, CEC_TX_STATUS_ERROR);

There is no separate 'low drive' interrupt? Do you know what happens if a low drive is received during a transmit?

I think it launches this interrupt, i.e. Initiator Error.

Quoted 8 lines

FYI: I've been working on error injection support for my cec-gpio driver, allowing me to test all these nasty little corner cases. And that includes Arbitration Lost. It's available here:

https://urldefense.proofpoint.com/v2/url?u=https-3A__git.linuxtv.org_hverkuil_media-5Ftree.git_log_-3Fh-3Dcec-2Derror-2Dinj&d=DwIDaQ&c=DPL6_X_6JkXFx7AXWqB0tg&r=WHDsc6kcWAl4i96Vm5hJ_19IJiuxx_p_Rzo2g-uHDKw&m=lM9aIhXqx9F8VZg9CADcx_qJbK_B2NTyHn8ZNIEpAEo&s=x0PASA3DhAHKXPrpj2G31D5xOFu0ERI3ewWb-yyTLMg&e=

It works like a charm with my Rpi3.

Nice! But for this I would need to have access to the physical pin for cec, right? I don't have that ...

Quoted 51 lines
Quoted 51 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +50 -0
+
+static int dw_hdmi_query_dv_timings(struct v4l2_subdev *sd,
+		struct v4l2_dv_timings *timings)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+	struct v4l2_bt_timings *bt = &timings->bt;
+	bool is_hdmi_vic;
+	u32 htot, hofs;
+	u32 vtot;
+	u8 vic;
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+
+	memset(timings, 0, sizeof(*timings));
+
+	timings->type = V4L2_DV_BT_656_1120;
+	bt->width = hdmi_readl(dw_dev, HDMI_MD_HACT_PX);
+	bt->height = hdmi_readl(dw_dev, HDMI_MD_VAL);
+	bt->interlaced = hdmi_readl(dw_dev, HDMI_MD_STS) & HDMI_MD_STS_ILACE;
+
+	if (hdmi_readl(dw_dev, HDMI_ISTS) & HDMI_ISTS_VS_POL_ADJ)
+		bt->polarities |= V4L2_DV_VSYNC_POS_POL;
+	if (hdmi_readl(dw_dev, HDMI_ISTS) & HDMI_ISTS_HS_POL_ADJ)
+		bt->polarities |= V4L2_DV_HSYNC_POS_POL;
+
+	bt->pixelclock = dw_hdmi_get_pixelclk(dw_dev);
+
+	/* HTOT = HACT + HFRONT + HSYNC + HBACK */
+	htot = hdmi_mask_readl(dw_dev, HDMI_MD_HT1,
+			HDMI_MD_HT1_HTOT_PIX_OFFSET,
+			HDMI_MD_HT1_HTOT_PIX_MASK);
+	/* HOFS = HSYNC + HBACK */
+	hofs = hdmi_mask_readl(dw_dev, HDMI_MD_HT1,
+			HDMI_MD_HT1_HOFS_PIX_OFFSET,
+			HDMI_MD_HT1_HOFS_PIX_MASK);
+
+	bt->hfrontporch = htot - hofs - bt->width;
+	bt->hsync = hdmi_mask_readl(dw_dev, HDMI_MD_HT0,
+			HDMI_MD_HT0_HS_CLK_OFFSET,
+			HDMI_MD_HT0_HS_CLK_MASK);
+	bt->hbackporch = hofs - bt->hsync;
+
+	/* VTOT = VACT + VFRONT + VSYNC + VBACK */
+	vtot = hdmi_readl(dw_dev, HDMI_MD_VTL);
+
+	hdmi_mask_writel(dw_dev, 0x1, HDMI_MD_VCTRL,
+			HDMI_MD_VCTRL_V_OFFS_LIN_MODE_OFFSET,
+			HDMI_MD_VCTRL_V_OFFS_LIN_MODE_MASK);
+	msleep(50);
+	bt->vsync = hdmi_readl(dw_dev, HDMI_MD_VOL);
Quoted 8 lines

drivers/media/platform/dwc/dw-hdmi-rx.c line 1209

Quoted 7 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +7 -0
+
+	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);
+	msleep(50);
+	bt->vbackporch = hdmi_readl(dw_dev, HDMI_MD_VOL);
+	bt->vfrontporch = vtot - bt->height - bt->vsync - bt->vbackporch;

For interlaced formats the bt->il_* fields should also be filled in.

See [1].

Quoted 20 lines
Quoted 5 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +4 -0
+	bt->standards = V4L2_DV_BT_STD_CEA861;
+
+	vic = dw_hdmi_get_curr_vic(dw_dev, &is_hdmi_vic);
+	if (vic) {

drivers/media/platform/dwc/dw-hdmi-rx.c line 1223

Quoted 10 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +10 -0
+		if (is_hdmi_vic) {
+			bt->flags |= V4L2_DV_FL_HAS_HDMI_VIC;
+			bt->hdmi_vic = vic;
+			bt->cea861_vic = 0;
+		} else {
+			bt->flags |= V4L2_DV_FL_HAS_CEA861_VIC;
+			bt->hdmi_vic = 0;
+			bt->cea861_vic = vic;
+		}
+	}

You can simplify this. We have this function in v4l2-dv-timings.c:
v4l2_find_dv_timings_cea861_vic(). If you read a CEA861 vic code, then you can call it to find the corresponding timings and just return that.

Quoted 2 lines

I thought I made a v4l2_find_dv_timings_hdmi_vic as well, but apparently I didn't. It's trivial to add it, though.

Ok, but how will then we handle 59.94 vs 60Hz video modes? Because the vic remains the same in these modes.

Quoted 32 lines
Quoted 20 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +19 -0
+
+	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,

drivers/media/platform/dwc/dw-hdmi-rx.c line 1252

Quoted 10 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +10 -0
+		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;

Were interlaced formats tested? (Apologies if I have asked this before)

Quoted 6 lines

Interlaced is tricky and my recommendation is to only add support for it to a driver if you have been able to test it.

I see that dw_hdmi_timings_cap only supports progressive, so I conclude that this hasn't been tested. It's better to just fix field to NONE in that case.

[1] Ok, but then I will still need to fill bt->il_* fields ?

Quoted 12 lines
Quoted 12 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +12 -0
+
+static int dw_hdmi_registered(struct v4l2_subdev *sd)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+	int ret;
+
+	ret = cec_register_adapter(dw_dev->cec_adap, dw_dev->dev);
+	if (ret) {
+		dev_err(dw_dev->dev, "failed to register CEC adapter\n");
+		cec_delete_adapter(dw_dev->cec_adap);
+		return ret;
+	}
Quoted 8 lines

drivers/media/platform/dwc/dw-hdmi-rx.c line 1453

Quoted 7 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +7 -0
+
+	cec_register_cec_notifier(dw_dev->cec_adap, dw_dev->cec_notifier);
+	dw_dev->registered = true;
+
+	return 0;
+	/*return v4l2_async_subdev_notifier_register(&dw_dev->sd,
+			&dw_dev->v4l2_notifier);*/

Comment can be dropped, I guess.

Yeah, leftovers from the previous version...

Quoted 108 lines
Quoted 108 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +107 -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 v4l2_ctrl_handler *hdl;
+	struct dw_hdmi_dev *dw_dev;
+	struct v4l2_subdev *sd;
+	struct resource *res;
+	int ret, irq;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	dw_dev = devm_kzalloc(dev, sizeof(*dw_dev), GFP_KERNEL);
+	if (!dw_dev)
+		return -ENOMEM;
+
+	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;
+	spin_lock_init(&dw_dev->lock);
+
+	ret = dw_hdmi_parse_dt(dw_dev);
+	if (ret)
+		return ret;
+
+	/* Deferred work */
+	dw_dev->wq = create_singlethread_workqueue(DW_HDMI_RX_DRVNAME);
+	if (!dw_dev->wq) {
+		dev_err(dev, "failed to create workqueue\n");
+		return -ENOMEM;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	dw_dev->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(dw_dev->regs)) {
+		dev_err(dev, "failed to remap resource\n");
+		ret = PTR_ERR(dw_dev->regs);
+		goto err_wq;
+	}
+
+	/* Disable HPD as soon as posssible */
+	dw_hdmi_disable_hpd(dw_dev);
+	/* Prevent HDCP from tampering video */
+	dw_hdmi_config_hdcp(dw_dev);
+
+	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;
+
+	/* V4L2 initialization */
+	sd = &dw_dev->sd;
+	v4l2_subdev_init(sd, &dw_hdmi_sd_ops);
+	strlcpy(sd->name, dev_name(dev), sizeof(sd->name));
+	sd->dev = dev;
+	sd->internal_ops = &dw_hdmi_internal_ops;
+	sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+	/* Control handlers */
+	hdl = &dw_dev->hdl;
+	v4l2_ctrl_handler_init(hdl, 1);
+	dw_dev->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL,
+			V4L2_CID_DV_RX_POWER_PRESENT, 0, BIT(4) - 1, 0, 0);
+
+	sd->ctrl_handler = hdl;
+	if (hdl->error) {
+		ret = hdl->error;
+		goto err_hdl;
+	}
+
+	/* Wait for ctrl handler register before requesting 5v interrupt */
+	irq = platform_get_irq(pdev, 1);
+	if (irq < 0) {
+		ret = irq;
+		goto err_hdl;
+	}
+
+	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_hdl;
+
+	/* Notifier for subdev binding */
+	ret = dw_hdmi_v4l2_init_notifier(dw_dev);
+	if (ret) {
+		dev_err(dev, "failed to init v4l2 notifier\n");
+		goto err_hdl;
+	}
+
+	/* PHY loading */
+	ret = dw_hdmi_phy_init(dw_dev);
+	if (ret)
Quoted 8 lines

drivers/media/platform/dwc/dw-hdmi-rx.c line 1744

Quoted 7 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +7 -0
+		goto err_hdl;
+
+	/* CEC */
+#if IS_ENABLED(CONFIG_VIDEO_DWC_HDMI_RX_CEC)
+	dw_dev->cec_adap = cec_allocate_adapter(&dw_hdmi_cec_adap_ops,
+			dw_dev, dev_name(dev), CEC_CAP_TRANSMIT |
+			CEC_CAP_LOG_ADDRS | CEC_CAP_RC | CEC_CAP_PASSTHROUGH,

Use CEC_CAP_DEFAULTS instead of specifying these caps separately.

Ok.

Thanks and Best Regards,
Jose Miguel Abreu

Quoted 4 lines

Regards,

Hans

#7 lore raw

Re: [PATCH v9 4/4] [media] platform: Add Synopsys DesignWare HDMI RX Controller Driver

Quoted 123 lines · Jose Abreu

Hi Hans,

Quoted 3 lines · Hans Verkuil

Hi Jose,

Some (small) comments below:

Thanks for the review!

Quoted 105 lines · Jose Abreu
Quoted 86 lines

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

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

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

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

Cc Joao Pinto <[email protected]>
Cc Mauro Carvalho Chehab <[email protected]>
Cc Hans Verkuil <[email protected]>
Cc Sylwester Nawrocki <[email protected]>
Cc Sakari Ailus <[email protected]>

---
Changes from v8:
- Incorporate Sakari's work on ASYNC subdevs
Changes from v6:
- edid-phandle now also looks for parent node (Sylwester)
- Fix kbuild build warnings
Changes from v5:
- Removed HDCP 1.4 support (Hans)
- Removed some CEC debug messages (Hans)
- Add s_dv_timings callback (Hans)
- Add V4L2_CID_DV_RX_POWER_PRESENT ctrl (Hans)
Changes from v4:
- Add flag V4L2_SUBDEV_FL_HAS_DEVNODE (Sylwester)
- Remove some comments and change some messages to dev_dbg (Sylwester)
- Use v4l2_async_subnotifier_register() (Sylwester)
Changes from v3:
- Use v4l2 async API (Sylwester)
- Do not block waiting for phy
- Do not use busy waiting delays (Sylwester)
- Simplify dw_hdmi_power_on (Sylwester)
- Use clock API (Sylwester)
- Use compatible string (Sylwester)
- Minor fixes (Sylwester)
Changes from v2:
- Address review comments from Hans regarding CEC
- Use CEC notifier
- Enable SCDC
Changes from v1:
- Add support for CEC
- Correct typo errors
- Correctly detect interlaced video modes
- Correct VIC parsing
Changes from RFC:
- Add support for HDCP 1.4
- Fixup HDMI_VIC not being parsed (Hans)
- Send source change signal when powering off (Hans)
- Add a "wait stable delay"
- Detect interlaced video modes (Hans)
- Restrain g/s_register from reading/writing to HDCP regs (Hans)

---
 drivers/media/platform/dwc/Kconfig      |   15 +
 drivers/media/platform/dwc/Makefile     |    1 +
 drivers/media/platform/dwc/dw-hdmi-rx.c | 1834 +++++++++++++++++++++++++++++++
 drivers/media/platform/dwc/dw-hdmi-rx.h |  441 ++++++++
 include/media/dwc/dw-hdmi-rx-pdata.h    |   70 ++
 5 files changed, 2361 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

<snip>

Quoted 15 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +15 -0
+static void dw_hdmi_cec_tx_raw_status(struct dw_hdmi_dev *dw_dev, u32 stat)
+{
+	if (hdmi_readl(dw_dev, HDMI_CEC_CTRL) & HDMI_CEC_CTRL_SEND_MASK) {
+		dev_dbg(dw_dev->dev, "%s: tx is busy\n", __func__);
+		return;
+	}
+
+	if (stat & HDMI_AUD_CEC_ISTS_ARBLST) {
+		cec_transmit_attempt_done(dw_dev->cec_adap,
+				CEC_TX_STATUS_ARB_LOST);
+		return;
+	}
+
+	if (stat & HDMI_AUD_CEC_ISTS_NACK) {
+		cec_transmit_attempt_done(dw_dev->cec_adap, CEC_TX_STATUS_NACK);
Quoted 8 lines

drivers/media/platform/dwc/dw-hdmi-rx.c line 630

Quoted 6 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +6 -0
+		return;
+	}
+
+	if (stat & HDMI_AUD_CEC_ISTS_ERROR_INIT) {
+		dev_dbg(dw_dev->dev, "%s: got initiator error\n", __func__);
+		cec_transmit_attempt_done(dw_dev->cec_adap, CEC_TX_STATUS_ERROR);

There is no separate 'low drive' interrupt? Do you know what happens if a low drive is received during a transmit?

Quoted 1 line

I think it launches this interrupt, i.e. Initiator Error.

That would make sense. Can you replace STATUS_ERROR with LOW_DRIVE?

Quoted 6 lines
Quoted 6 lines

FYI: I've been working on error injection support for my cec-gpio driver, allowing me to test all these nasty little corner cases. And that includes Arbitration Lost. It's available here:

Quoted 6 lines
Quoted 3 lines

https://urldefense.proofpoint.com/v2/url?u=https-3A__git.linuxtv.org_hverkuil_media-5Ftree.git_log_-3Fh-3Dcec-2Derror-2Dinj&d=DwIDaQ&c=DPL6_X_6JkXFx7AXWqB0tg&r=WHDsc6kcWAl4i96Vm5hJ_19IJiuxx_p_Rzo2g-uHDKw&m=lM9aIhXqx9F8VZg9CADcx_qJbK_B2NTyHn8ZNIEpAEo&s=x0PASA3DhAHKXPrpj2G31D5xOFu0ERI3ewWb-yyTLMg&e=

It works like a charm with my Rpi3.

Nice! But for this I would need to have access to the physical pin for cec, right? I don't have that ...

I've set up my Rpi3 like described in the "Making a CEC debugger" section
here: https://hverkuil.home.xs4all.nl/cec-status.txt

For around $80 you have a really nice CEC debugger. It doesn't have to be a Raspberry Pi, any SBC which can give you a pull-up GPIO pin works. Just hook it up in the dts.

My plan is to eventually have scripts that can test all these weird conditions automatically. Especially hard-to-reproduce corner cases like low-drive conditions, lost arbitration, spurious pulses, pulses that are too long or too short, or messages longer than 16 bytes.

Quoted 83 lines
Quoted 52 lines
Quoted 52 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +50 -0
+
+static int dw_hdmi_query_dv_timings(struct v4l2_subdev *sd,
+		struct v4l2_dv_timings *timings)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+	struct v4l2_bt_timings *bt = &timings->bt;
+	bool is_hdmi_vic;
+	u32 htot, hofs;
+	u32 vtot;
+	u8 vic;
+
+	dev_dbg(dw_dev->dev, "%s\n", __func__);
+
+	memset(timings, 0, sizeof(*timings));
+
+	timings->type = V4L2_DV_BT_656_1120;
+	bt->width = hdmi_readl(dw_dev, HDMI_MD_HACT_PX);
+	bt->height = hdmi_readl(dw_dev, HDMI_MD_VAL);
+	bt->interlaced = hdmi_readl(dw_dev, HDMI_MD_STS) & HDMI_MD_STS_ILACE;
+
+	if (hdmi_readl(dw_dev, HDMI_ISTS) & HDMI_ISTS_VS_POL_ADJ)
+		bt->polarities |= V4L2_DV_VSYNC_POS_POL;
+	if (hdmi_readl(dw_dev, HDMI_ISTS) & HDMI_ISTS_HS_POL_ADJ)
+		bt->polarities |= V4L2_DV_HSYNC_POS_POL;
+
+	bt->pixelclock = dw_hdmi_get_pixelclk(dw_dev);
+
+	/* HTOT = HACT + HFRONT + HSYNC + HBACK */
+	htot = hdmi_mask_readl(dw_dev, HDMI_MD_HT1,
+			HDMI_MD_HT1_HTOT_PIX_OFFSET,
+			HDMI_MD_HT1_HTOT_PIX_MASK);
+	/* HOFS = HSYNC + HBACK */
+	hofs = hdmi_mask_readl(dw_dev, HDMI_MD_HT1,
+			HDMI_MD_HT1_HOFS_PIX_OFFSET,
+			HDMI_MD_HT1_HOFS_PIX_MASK);
+
+	bt->hfrontporch = htot - hofs - bt->width;
+	bt->hsync = hdmi_mask_readl(dw_dev, HDMI_MD_HT0,
+			HDMI_MD_HT0_HS_CLK_OFFSET,
+			HDMI_MD_HT0_HS_CLK_MASK);
+	bt->hbackporch = hofs - bt->hsync;
+
+	/* VTOT = VACT + VFRONT + VSYNC + VBACK */
+	vtot = hdmi_readl(dw_dev, HDMI_MD_VTL);
+
+	hdmi_mask_writel(dw_dev, 0x1, HDMI_MD_VCTRL,
+			HDMI_MD_VCTRL_V_OFFS_LIN_MODE_OFFSET,
+			HDMI_MD_VCTRL_V_OFFS_LIN_MODE_MASK);
+	msleep(50);
+	bt->vsync = hdmi_readl(dw_dev, HDMI_MD_VOL);
Quoted 8 lines

drivers/media/platform/dwc/dw-hdmi-rx.c line 1209

Quoted 7 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +7 -0
+
+	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);
+	msleep(50);
+	bt->vbackporch = hdmi_readl(dw_dev, HDMI_MD_VOL);
+	bt->vfrontporch = vtot - bt->height - bt->vsync - bt->vbackporch;

For interlaced formats the bt->il_* fields should also be filled in.

See [1].

Quoted 20 lines
Quoted 5 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +4 -0
+	bt->standards = V4L2_DV_BT_STD_CEA861;
+
+	vic = dw_hdmi_get_curr_vic(dw_dev, &is_hdmi_vic);
+	if (vic) {

drivers/media/platform/dwc/dw-hdmi-rx.c line 1223

Quoted 10 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +10 -0
+		if (is_hdmi_vic) {
+			bt->flags |= V4L2_DV_FL_HAS_HDMI_VIC;
+			bt->hdmi_vic = vic;
+			bt->cea861_vic = 0;
+		} else {
+			bt->flags |= V4L2_DV_FL_HAS_CEA861_VIC;
+			bt->hdmi_vic = 0;
+			bt->cea861_vic = vic;
+		}
+	}

You can simplify this. We have this function in v4l2-dv-timings.c:
v4l2_find_dv_timings_cea861_vic(). If you read a CEA861 vic code, then you can call it to find the corresponding timings and just return that.

Quoted 5 lines
Quoted 2 lines

I thought I made a v4l2_find_dv_timings_hdmi_vic as well, but apparently I didn't. It's trivial to add it, though.

Ok, but how will then we handle 59.94 vs 60Hz video modes? Because the vic remains the same in these modes.

Ah, yes. Ignore this for now, once this driver is merged we need to take another look at this. We discussed 59.94 vs 60 in the past, and we should revisit that. But let's get this in first.

Quoted 33 lines
Quoted 33 lines
Quoted 21 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +19 -0
+
+	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,

drivers/media/platform/dwc/dw-hdmi-rx.c line 1252

Quoted 10 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +10 -0
+		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;

Were interlaced formats tested? (Apologies if I have asked this before)

Quoted 8 lines
Quoted 6 lines

Interlaced is tricky and my recommendation is to only add support for it to a driver if you have been able to test it.

I see that dw_hdmi_timings_cap only supports progressive, so I conclude that this hasn't been tested. It's better to just fix field to NONE in that case.

[1] Ok, but then I will still need to fill bt->il_* fields ?

If you have that information, then you should do that, yes. It's still a good idea to return precise information here.

Quoted 141 lines
Quoted 13 lines
Quoted 13 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +12 -0
+
+static int dw_hdmi_registered(struct v4l2_subdev *sd)
+{
+	struct dw_hdmi_dev *dw_dev = to_dw_dev(sd);
+	int ret;
+
+	ret = cec_register_adapter(dw_dev->cec_adap, dw_dev->dev);
+	if (ret) {
+		dev_err(dw_dev->dev, "failed to register CEC adapter\n");
+		cec_delete_adapter(dw_dev->cec_adap);
+		return ret;
+	}
Quoted 8 lines

drivers/media/platform/dwc/dw-hdmi-rx.c line 1453

Quoted 7 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +7 -0
+
+	cec_register_cec_notifier(dw_dev->cec_adap, dw_dev->cec_notifier);
+	dw_dev->registered = true;
+
+	return 0;
+	/*return v4l2_async_subdev_notifier_register(&dw_dev->sd,
+			&dw_dev->v4l2_notifier);*/

Comment can be dropped, I guess.

Yeah, leftovers from the previous version...

Quoted 108 lines
Quoted 108 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +107 -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 v4l2_ctrl_handler *hdl;
+	struct dw_hdmi_dev *dw_dev;
+	struct v4l2_subdev *sd;
+	struct resource *res;
+	int ret, irq;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	dw_dev = devm_kzalloc(dev, sizeof(*dw_dev), GFP_KERNEL);
+	if (!dw_dev)
+		return -ENOMEM;
+
+	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;
+	spin_lock_init(&dw_dev->lock);
+
+	ret = dw_hdmi_parse_dt(dw_dev);
+	if (ret)
+		return ret;
+
+	/* Deferred work */
+	dw_dev->wq = create_singlethread_workqueue(DW_HDMI_RX_DRVNAME);
+	if (!dw_dev->wq) {
+		dev_err(dev, "failed to create workqueue\n");
+		return -ENOMEM;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	dw_dev->regs = devm_ioremap_resource(dev, res);
+	if (IS_ERR(dw_dev->regs)) {
+		dev_err(dev, "failed to remap resource\n");
+		ret = PTR_ERR(dw_dev->regs);
+		goto err_wq;
+	}
+
+	/* Disable HPD as soon as posssible */
+	dw_hdmi_disable_hpd(dw_dev);
+	/* Prevent HDCP from tampering video */
+	dw_hdmi_config_hdcp(dw_dev);
+
+	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;
+
+	/* V4L2 initialization */
+	sd = &dw_dev->sd;
+	v4l2_subdev_init(sd, &dw_hdmi_sd_ops);
+	strlcpy(sd->name, dev_name(dev), sizeof(sd->name));
+	sd->dev = dev;
+	sd->internal_ops = &dw_hdmi_internal_ops;
+	sd->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+	/* Control handlers */
+	hdl = &dw_dev->hdl;
+	v4l2_ctrl_handler_init(hdl, 1);
+	dw_dev->detect_tx_5v_ctrl = v4l2_ctrl_new_std(hdl, NULL,
+			V4L2_CID_DV_RX_POWER_PRESENT, 0, BIT(4) - 1, 0, 0);
+
+	sd->ctrl_handler = hdl;
+	if (hdl->error) {
+		ret = hdl->error;
+		goto err_hdl;
+	}
+
+	/* Wait for ctrl handler register before requesting 5v interrupt */
+	irq = platform_get_irq(pdev, 1);
+	if (irq < 0) {
+		ret = irq;
+		goto err_hdl;
+	}
+
+	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_hdl;
+
+	/* Notifier for subdev binding */
+	ret = dw_hdmi_v4l2_init_notifier(dw_dev);
+	if (ret) {
+		dev_err(dev, "failed to init v4l2 notifier\n");
+		goto err_hdl;
+	}
+
+	/* PHY loading */
+	ret = dw_hdmi_phy_init(dw_dev);
+	if (ret)
Quoted 8 lines

drivers/media/platform/dwc/dw-hdmi-rx.c line 1744

Quoted 7 lines
drivers/media/platform/dwc/dw-hdmi-rx.c new file +7 -0
+		goto err_hdl;
+
+	/* CEC */
+#if IS_ENABLED(CONFIG_VIDEO_DWC_HDMI_RX_CEC)
+	dw_dev->cec_adap = cec_allocate_adapter(&dw_hdmi_cec_adap_ops,
+			dw_dev, dev_name(dev), CEC_CAP_TRANSMIT |
+			CEC_CAP_LOG_ADDRS | CEC_CAP_RC | CEC_CAP_PASSTHROUGH,

Use CEC_CAP_DEFAULTS instead of specifying these caps separately.

Quoted 4 lines

Ok.

Thanks and Best Regards,
Jose Miguel Abreu

Regards,

Hans

#8 lore raw

Re: [PATCH v9 4/4] [media] platform: Add Synopsys DesignWare HDMI RX Controller Driver

In reply to Hans Verkuil

Jose,

Quoted 41 lines · Hans Verkuil

Hi Jose,

Some (small) comments below:

Quoted 36 lines · Jose Abreu

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

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

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

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

Cc Joao Pinto <[email protected]>
Cc Mauro Carvalho Chehab <[email protected]>
Cc Hans Verkuil <[email protected]>
Cc Sylwester Nawrocki <[email protected]>
Cc Sakari Ailus <[email protected]>

[]

Quoted 20 lines
Quoted 20 lines
drivers/media/platform/dwc/dw-hdmi-rx.h new file +17 -0
@@ -0,0 +1,441 @@
+/*
+ * 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.
+ *
Quoted 8 lines

drivers/media/platform/dwc/dw-hdmi-rx.h line 25

Quoted 8 lines
drivers/media/platform/dwc/dw-hdmi-rx.h new file +8 -0
+ * 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.
+ */

My heart bleeds when I see such a long legalese crap obstructing your otherwise fine code contributions.

Would you be kind enough to consider using the new SPDX ids as documented by Thomas and commented and agreed by Linus, Greg and other maintainers?
This has the same effect, but is much more concise and readable.

This could come out this way in this example:

Quoted 7 lines

drivers/media/platform/dwc/dw-hdmi-rx.h line 25

Quoted 7 lines
drivers/media/platform/dwc/dw-hdmi-rx.h new file +7 -0
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Synopsys Designware HDMI Receiver controller driver
+ *
+ * Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
+ *
+ */

Or even better using C++ /// style comments if this works for your code. These are preferred by Linus for such things (and requested for .c files SPDX ids vs. .h includes):

Quoted 3 lines

drivers/media/platform/dwc/dw-hdmi-rx.h line 35

Quoted 3 lines
drivers/media/platform/dwc/dw-hdmi-rx.h new file +3 -0
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+// Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
+// Synopsys Designware HDMI Receiver controller driver

Think of it this way: unless you are a legalese lover or a lawyer-who-codes and confused the kernel with a licensing contract draft, you now have improved the signal/noise ratio of your code quite nicely by removing about 20 lines of comments.

And if someone ever prints your code, you will have saved a tree or two and be a good earth citizen.
And even better, you can now grep your code for licenses, unambiguously.

If you could do this that would be really nice: we already have tagged ~13K files with SPDX and they are now in Linus's tree. We still have roughly 60K files to do... so every little help that would avoid piling up more work for us with new and innovative legalese boilerplate would really be much appreciated.

Extra bonus: you can also do this for all your past, present and future contributions.... and spread the good word at your company so that everyone does the same: with this small thing you will earn at least 10 or more extra good karma points and my eternal gratitude.

Thank you for your kind consideration, your friendly kernel licensing janitor!

Signature · 2 lines
Cordially
Philippe Ombredanne