All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v7 0/3] drm: Add support for Cadence MHDP DPI/DP bridge and J721E wrapper.
@ 2020-07-22  7:40 ` Swapnil Jakhade
  0 siblings, 0 replies; 26+ messages in thread
From: Swapnil Jakhade @ 2020-07-22  7:40 UTC (permalink / raw)
  To: airlied, daniel, Laurent.pinchart, robh+dt, a.hajda, narmstrong,
	jonas, jernej.skrabec, dri-devel, devicetree, linux-kernel
  Cc: mparab, sjakhade, yamonkar, tomi.valkeinen, jsarha, nsekhar, praneeth

This patch series adds new DRM bridge driver for Cadence MHDP DPI/DP
bridge. The Cadence Display Port IP is also referred as MHDP (Mobile High
Definition Link, High-Definition Multimedia Interface, Display Port).
Cadence Display Port complies with VESA DisplayPort (DP) and embedded
Display Port (eDP) standards.

The MHDP bridge driver currently implements Single Stream Transport (SST)
mode. It also adds Texas Instruments j721e SoC specific wrapper and adds
the device tree bindings in YAML format.

Some of the features that will be added later on include (but are not
limited to):
- Power Management (PM) support: We will implement the PM functions in
  next stage once there will be a stable driver in upstream
- Converting to connector bridge operations: This is not supported
  currently because of dependency on TIDSS driver which has some
  limitations in supporting new model
- Audio and MST support

The patch series has three patches in the below sequence:
1. 0001-dt-bindings-drm-bridge-Document-Cadence-MHDP-brid.patch
Documents the bindings in yaml format.
2. 0002-drm-bridge-Add-support-for-Cadence-MHDP-DPI-DP-br.patch
This patch adds new DRM bridge driver for Cadence MHDP Display Port.
The patch implements support for single stream transport mode.
3. 0003-drm-bridge-cdns-mhdp-Add-j721e-wrapper.patch
Adds Texas Instruments (TI) j721e wrapper for MHDP. The wrapper configures
MHDP clocks and muxes as required by SoC.

This patch series is dependent on PHY patch series [1] to add new PHY APIs
to get/set PHY attributes which is under review and not merged yet.

[1] https://lkml.org/lkml/2020/7/17/158

Version History:

v7:

In 1/3
- No change

In 2/3
- Switch to atomic versions of bridge operations
- Implement atomic_check() handler to perform all validation checks
- Add struct cdns_mhdp_bridge_state with subclassed bridge state
- Use PHY API[1] to get PHY attributes instead of reading from PHY DT node
- Updated HPD handling and link configuration in IRQ handler
- Add "link_mutex" protecting the access to all the link parameters
- Add support to check and print FW version information
- Add separate function to initialize host parameters to simplify probe
- Use waitqueue instead of manual loop in cdns_mhdp_remove
- Add forward declarations and header files in cdns-mhdp-core.h file
- Use bool instead of single bit values in struct cdns_mhdp_device
- Fix for other minor comments given for v6 patches

In 3/3
- Use of_device_is_compatible() to set compatible string specific values
- Move mhdp_ti_j721e_ops structure to cdns-mhdp-j721e.c
- Remove duplicate Copyright message
- Remove CONFIG_DRM_CDNS_MHDP_J721E check
- Add Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

v6:
- Added minor fixes in YAML file.
- Added Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
  to the YAML patch.
- Removed all the FIXME comments which are invalid in drm driver.
- Reduced the mailbox timeout from 5s to 2s.
- Added Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
  to the 003-drm-mhdp-add-j721e-wrapper patch.
- Added Signed-off all the module authors.
- Fixed the compiler error Reported-by: kbuild test robot <lkp@intel.com>.

v5:
- Added Signed-off-by: Jyri Sarha <jsarha@ti.com> tag to
  the code patches.

v4:
- Added SPDX dual license tag to YAML bindings.
- Corrected indentation of the child node properties.
- Removed the maxItems in the conditional statement.
- Add Reviewed-by: Rob Herring <robh@kernel.org> tag to the
  Document Cadence MHDP bridge bindings patch.
- Renamed the DRM driver executable name from mhdp8546 to cdns-mhdp in
  Makefile.
- Renamed the DRM driver and header file from cdns-mhdp to cdns-mhdp-core.

v3:
- Added if / then clause to validate that the reg length is proper
  based on the value of the compatible property.
- Updated phy property description in YAML to a generic one.
- Renamed num_lanes and max_bit_rate property strings to cdns,num-lanes
  and cdns,max-bit-rate.

v2:
- Use enum in compatible property of YAML file.
- Add reg-names property to YAML file
- Add minItems and maxItems to reg property in YAML.
- Remove cdns_mhdp_link_probe function to remove
  duplication of reading dpcd capabilities.

Swapnil Jakhade (2):
  drm: bridge: Add support for Cadence MHDP DPI/DP bridge
  drm: bridge: cdns-mhdp: Add j721e wrapper

Yuti Amonkar (1):
  dt-bindings: drm/bridge: Document Cadence MHDP bridge bindings

 .../bindings/display/bridge/cdns,mhdp.yaml    |  127 +
 drivers/gpu/drm/bridge/Kconfig                |   24 +
 drivers/gpu/drm/bridge/Makefile               |    7 +
 drivers/gpu/drm/bridge/cdns-mhdp-core.c       | 2508 +++++++++++++++++
 drivers/gpu/drm/bridge/cdns-mhdp-core.h       |  397 +++
 drivers/gpu/drm/bridge/cdns-mhdp-j721e.c      |   75 +
 drivers/gpu/drm/bridge/cdns-mhdp-j721e.h      |   19 +
 7 files changed, 3157 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.c
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.h
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-j721e.c
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-j721e.h

-- 
2.26.1


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

* [PATCH v7 0/3] drm: Add support for Cadence MHDP DPI/DP bridge and J721E wrapper.
@ 2020-07-22  7:40 ` Swapnil Jakhade
  0 siblings, 0 replies; 26+ messages in thread
From: Swapnil Jakhade @ 2020-07-22  7:40 UTC (permalink / raw)
  To: airlied, daniel, Laurent.pinchart, robh+dt, a.hajda, narmstrong,
	jonas, jernej.skrabec, dri-devel, devicetree, linux-kernel
  Cc: mparab, yamonkar, praneeth, nsekhar, jsarha, tomi.valkeinen, sjakhade

This patch series adds new DRM bridge driver for Cadence MHDP DPI/DP
bridge. The Cadence Display Port IP is also referred as MHDP (Mobile High
Definition Link, High-Definition Multimedia Interface, Display Port).
Cadence Display Port complies with VESA DisplayPort (DP) and embedded
Display Port (eDP) standards.

The MHDP bridge driver currently implements Single Stream Transport (SST)
mode. It also adds Texas Instruments j721e SoC specific wrapper and adds
the device tree bindings in YAML format.

Some of the features that will be added later on include (but are not
limited to):
- Power Management (PM) support: We will implement the PM functions in
  next stage once there will be a stable driver in upstream
- Converting to connector bridge operations: This is not supported
  currently because of dependency on TIDSS driver which has some
  limitations in supporting new model
- Audio and MST support

The patch series has three patches in the below sequence:
1. 0001-dt-bindings-drm-bridge-Document-Cadence-MHDP-brid.patch
Documents the bindings in yaml format.
2. 0002-drm-bridge-Add-support-for-Cadence-MHDP-DPI-DP-br.patch
This patch adds new DRM bridge driver for Cadence MHDP Display Port.
The patch implements support for single stream transport mode.
3. 0003-drm-bridge-cdns-mhdp-Add-j721e-wrapper.patch
Adds Texas Instruments (TI) j721e wrapper for MHDP. The wrapper configures
MHDP clocks and muxes as required by SoC.

This patch series is dependent on PHY patch series [1] to add new PHY APIs
to get/set PHY attributes which is under review and not merged yet.

[1] https://lkml.org/lkml/2020/7/17/158

Version History:

v7:

In 1/3
- No change

In 2/3
- Switch to atomic versions of bridge operations
- Implement atomic_check() handler to perform all validation checks
- Add struct cdns_mhdp_bridge_state with subclassed bridge state
- Use PHY API[1] to get PHY attributes instead of reading from PHY DT node
- Updated HPD handling and link configuration in IRQ handler
- Add "link_mutex" protecting the access to all the link parameters
- Add support to check and print FW version information
- Add separate function to initialize host parameters to simplify probe
- Use waitqueue instead of manual loop in cdns_mhdp_remove
- Add forward declarations and header files in cdns-mhdp-core.h file
- Use bool instead of single bit values in struct cdns_mhdp_device
- Fix for other minor comments given for v6 patches

In 3/3
- Use of_device_is_compatible() to set compatible string specific values
- Move mhdp_ti_j721e_ops structure to cdns-mhdp-j721e.c
- Remove duplicate Copyright message
- Remove CONFIG_DRM_CDNS_MHDP_J721E check
- Add Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

v6:
- Added minor fixes in YAML file.
- Added Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
  to the YAML patch.
- Removed all the FIXME comments which are invalid in drm driver.
- Reduced the mailbox timeout from 5s to 2s.
- Added Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
  to the 003-drm-mhdp-add-j721e-wrapper patch.
- Added Signed-off all the module authors.
- Fixed the compiler error Reported-by: kbuild test robot <lkp@intel.com>.

v5:
- Added Signed-off-by: Jyri Sarha <jsarha@ti.com> tag to
  the code patches.

v4:
- Added SPDX dual license tag to YAML bindings.
- Corrected indentation of the child node properties.
- Removed the maxItems in the conditional statement.
- Add Reviewed-by: Rob Herring <robh@kernel.org> tag to the
  Document Cadence MHDP bridge bindings patch.
- Renamed the DRM driver executable name from mhdp8546 to cdns-mhdp in
  Makefile.
- Renamed the DRM driver and header file from cdns-mhdp to cdns-mhdp-core.

v3:
- Added if / then clause to validate that the reg length is proper
  based on the value of the compatible property.
- Updated phy property description in YAML to a generic one.
- Renamed num_lanes and max_bit_rate property strings to cdns,num-lanes
  and cdns,max-bit-rate.

v2:
- Use enum in compatible property of YAML file.
- Add reg-names property to YAML file
- Add minItems and maxItems to reg property in YAML.
- Remove cdns_mhdp_link_probe function to remove
  duplication of reading dpcd capabilities.

Swapnil Jakhade (2):
  drm: bridge: Add support for Cadence MHDP DPI/DP bridge
  drm: bridge: cdns-mhdp: Add j721e wrapper

Yuti Amonkar (1):
  dt-bindings: drm/bridge: Document Cadence MHDP bridge bindings

 .../bindings/display/bridge/cdns,mhdp.yaml    |  127 +
 drivers/gpu/drm/bridge/Kconfig                |   24 +
 drivers/gpu/drm/bridge/Makefile               |    7 +
 drivers/gpu/drm/bridge/cdns-mhdp-core.c       | 2508 +++++++++++++++++
 drivers/gpu/drm/bridge/cdns-mhdp-core.h       |  397 +++
 drivers/gpu/drm/bridge/cdns-mhdp-j721e.c      |   75 +
 drivers/gpu/drm/bridge/cdns-mhdp-j721e.h      |   19 +
 7 files changed, 3157 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.c
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.h
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-j721e.c
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-j721e.h

-- 
2.26.1

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

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

* [PATCH v7 1/3] dt-bindings: drm/bridge: Document Cadence MHDP bridge bindings
  2020-07-22  7:40 ` Swapnil Jakhade
@ 2020-07-22  7:40   ` Swapnil Jakhade
  -1 siblings, 0 replies; 26+ messages in thread
From: Swapnil Jakhade @ 2020-07-22  7:40 UTC (permalink / raw)
  To: airlied, daniel, Laurent.pinchart, robh+dt, a.hajda, narmstrong,
	jonas, jernej.skrabec, dri-devel, devicetree, linux-kernel
  Cc: mparab, sjakhade, yamonkar, tomi.valkeinen, jsarha, nsekhar, praneeth

From: Yuti Amonkar <yamonkar@cadence.com>

Document the bindings used for the Cadence MHDP DPI/DP bridge in
yaml format.

Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
Reviewed-by: Rob Herring <robh@kernel.org>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 .../bindings/display/bridge/cdns,mhdp.yaml    | 127 ++++++++++++++++++
 1 file changed, 127 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml

diff --git a/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml b/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
new file mode 100644
index 000000000000..cdf5760d4ec5
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
@@ -0,0 +1,127 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/display/bridge/cdns,mhdp.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Cadence MHDP bridge
+
+maintainers:
+  - Swapnil Jakhade <sjakhade@cadence.com>
+  - Yuti Amonkar <yamonkar@cadence.com>
+
+properties:
+  compatible:
+    enum:
+      - cdns,mhdp8546
+      - ti,j721e-mhdp8546
+
+  reg:
+    minItems: 1
+    maxItems: 2
+    items:
+      - description:
+          Register block of mhdptx apb registers up to PHY mapped area (AUX_CONFIG_P).
+          The AUX and PMA registers are not part of this range, they are instead
+          included in the associated PHY.
+      - description:
+          Register block for DSS_EDP0_INTG_CFG_VP registers in case of TI J7 SoCs.
+
+  reg-names:
+    minItems: 1
+    maxItems: 2
+    items:
+      - const: mhdptx
+      - const: j721e-intg
+
+  clocks:
+    maxItems: 1
+    description:
+      DP bridge clock, used by the IP to know how to translate a number of
+      clock cycles into a time (which is used to comply with DP standard timings
+      and delays).
+
+  phys:
+    description:
+      phandle to the DisplayPort PHY.
+
+  ports:
+    type: object
+    description:
+      Ports as described in Documentation/devicetree/bindings/graph.txt.
+
+    properties:
+      '#address-cells':
+        const: 1
+
+      '#size-cells':
+        const: 0
+
+      port@0:
+        type: object
+        description:
+          Input port representing the DP bridge input.
+
+      port@1:
+        type: object
+        description:
+          Output port representing the DP bridge output.
+
+    required:
+      - port@0
+      - port@1
+      - '#address-cells'
+      - '#size-cells'
+
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: ti,j721e-mhdp8546
+    then:
+      properties:
+        reg:
+          minItems: 2
+        reg-names:
+          minItems: 2
+
+required:
+  - compatible
+  - clocks
+  - reg
+  - reg-names
+  - phys
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    mhdp: dp-bridge@f0fb000000 {
+        compatible = "cdns,mhdp8546";
+        reg = <0xf0 0xfb000000 0x0 0x1000000>;
+        reg-names = "mhdptx";
+        clocks = <&mhdp_clock>;
+        phys = <&dp_phy>;
+
+        ports {
+              #address-cells = <1>;
+              #size-cells = <0>;
+
+              port@0 {
+                     reg = <0>;
+                     dp_bridge_input: endpoint {
+                        remote-endpoint = <&xxx_dpi_output>;
+                     };
+              };
+
+              port@1 {
+                     reg = <1>;
+                     dp_bridge_output: endpoint {
+                        remote-endpoint = <&xxx_dp_connector_input>;
+                     };
+              };
+        };
+    };
+...
-- 
2.26.1


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

* [PATCH v7 1/3] dt-bindings: drm/bridge: Document Cadence MHDP bridge bindings
@ 2020-07-22  7:40   ` Swapnil Jakhade
  0 siblings, 0 replies; 26+ messages in thread
From: Swapnil Jakhade @ 2020-07-22  7:40 UTC (permalink / raw)
  To: airlied, daniel, Laurent.pinchart, robh+dt, a.hajda, narmstrong,
	jonas, jernej.skrabec, dri-devel, devicetree, linux-kernel
  Cc: mparab, yamonkar, praneeth, nsekhar, jsarha, tomi.valkeinen, sjakhade

From: Yuti Amonkar <yamonkar@cadence.com>

Document the bindings used for the Cadence MHDP DPI/DP bridge in
yaml format.

Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
Reviewed-by: Rob Herring <robh@kernel.org>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 .../bindings/display/bridge/cdns,mhdp.yaml    | 127 ++++++++++++++++++
 1 file changed, 127 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml

diff --git a/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml b/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
new file mode 100644
index 000000000000..cdf5760d4ec5
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
@@ -0,0 +1,127 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/display/bridge/cdns,mhdp.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Cadence MHDP bridge
+
+maintainers:
+  - Swapnil Jakhade <sjakhade@cadence.com>
+  - Yuti Amonkar <yamonkar@cadence.com>
+
+properties:
+  compatible:
+    enum:
+      - cdns,mhdp8546
+      - ti,j721e-mhdp8546
+
+  reg:
+    minItems: 1
+    maxItems: 2
+    items:
+      - description:
+          Register block of mhdptx apb registers up to PHY mapped area (AUX_CONFIG_P).
+          The AUX and PMA registers are not part of this range, they are instead
+          included in the associated PHY.
+      - description:
+          Register block for DSS_EDP0_INTG_CFG_VP registers in case of TI J7 SoCs.
+
+  reg-names:
+    minItems: 1
+    maxItems: 2
+    items:
+      - const: mhdptx
+      - const: j721e-intg
+
+  clocks:
+    maxItems: 1
+    description:
+      DP bridge clock, used by the IP to know how to translate a number of
+      clock cycles into a time (which is used to comply with DP standard timings
+      and delays).
+
+  phys:
+    description:
+      phandle to the DisplayPort PHY.
+
+  ports:
+    type: object
+    description:
+      Ports as described in Documentation/devicetree/bindings/graph.txt.
+
+    properties:
+      '#address-cells':
+        const: 1
+
+      '#size-cells':
+        const: 0
+
+      port@0:
+        type: object
+        description:
+          Input port representing the DP bridge input.
+
+      port@1:
+        type: object
+        description:
+          Output port representing the DP bridge output.
+
+    required:
+      - port@0
+      - port@1
+      - '#address-cells'
+      - '#size-cells'
+
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: ti,j721e-mhdp8546
+    then:
+      properties:
+        reg:
+          minItems: 2
+        reg-names:
+          minItems: 2
+
+required:
+  - compatible
+  - clocks
+  - reg
+  - reg-names
+  - phys
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    mhdp: dp-bridge@f0fb000000 {
+        compatible = "cdns,mhdp8546";
+        reg = <0xf0 0xfb000000 0x0 0x1000000>;
+        reg-names = "mhdptx";
+        clocks = <&mhdp_clock>;
+        phys = <&dp_phy>;
+
+        ports {
+              #address-cells = <1>;
+              #size-cells = <0>;
+
+              port@0 {
+                     reg = <0>;
+                     dp_bridge_input: endpoint {
+                        remote-endpoint = <&xxx_dpi_output>;
+                     };
+              };
+
+              port@1 {
+                     reg = <1>;
+                     dp_bridge_output: endpoint {
+                        remote-endpoint = <&xxx_dp_connector_input>;
+                     };
+              };
+        };
+    };
+...
-- 
2.26.1

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

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

* [PATCH v7 2/3] drm: bridge: Add support for Cadence MHDP DPI/DP bridge
  2020-07-22  7:40 ` Swapnil Jakhade
@ 2020-07-22  7:40   ` Swapnil Jakhade
  -1 siblings, 0 replies; 26+ messages in thread
From: Swapnil Jakhade @ 2020-07-22  7:40 UTC (permalink / raw)
  To: airlied, daniel, Laurent.pinchart, robh+dt, a.hajda, narmstrong,
	jonas, jernej.skrabec, dri-devel, devicetree, linux-kernel
  Cc: mparab, sjakhade, yamonkar, tomi.valkeinen, jsarha, nsekhar, praneeth

Add a new DRM bridge driver for Cadence MHDP DPTX IP used in TI J721e SoC.
MHDP DPTX IP is the component that complies with VESA DisplayPort (DP) and
embedded Display Port (eDP) standards. It integrates uCPU running the
embedded Firmware (FW) interfaced over APB interface.

Basically, it takes a DPI stream as input and outputs it encoded in DP
format. Currently, it supports only SST mode.

Co-developed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Co-developed-by: Jyri Sarha <jsarha@ti.com>
Signed-off-by: Jyri Sarha <jsarha@ti.com>
Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
---
 drivers/gpu/drm/bridge/Kconfig          |   11 +
 drivers/gpu/drm/bridge/Makefile         |    2 +
 drivers/gpu/drm/bridge/cdns-mhdp-core.c | 2493 +++++++++++++++++++++++
 drivers/gpu/drm/bridge/cdns-mhdp-core.h |  396 ++++
 4 files changed, 2902 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.c
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.h

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 43271c21d3fc..6a4c324302a8 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -27,6 +27,17 @@ config DRM_CDNS_DSI
 	  Support Cadence DPI to DSI bridge. This is an internal
 	  bridge and is meant to be directly embedded in a SoC.
 
+config DRM_CDNS_MHDP
+	tristate "Cadence DPI/DP bridge"
+	select DRM_KMS_HELPER
+	select DRM_PANEL_BRIDGE
+	depends on OF
+	help
+	  Support Cadence DPI to DP bridge. This is an internal
+	  bridge and is meant to be directly embedded in a SoC.
+	  It takes a DPI stream as input and outputs it encoded
+	  in DP format.
+
 config DRM_CHRONTEL_CH7033
 	tristate "Chrontel CH7033 Video Encoder"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index d63d4b7e4347..0080a9f80f29 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -1,5 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
+obj-$(CONFIG_DRM_CDNS_MHDP) += cdns-mhdp.o
+cdns-mhdp-objs := cdns-mhdp-core.o
 obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
 obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
 obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.c b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
new file mode 100644
index 000000000000..b16c5503cef1
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
@@ -0,0 +1,2493 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cadence MHDP DP bridge driver.
+ *
+ * Copyright: 2019 Cadence Design Systems, Inc.
+ *
+ * Author: Quentin Schulz <quentin.schulz@free-electrons.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-dp.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#include <asm/unaligned.h>
+
+#include "cdns-mhdp-core.h"
+
+static DECLARE_WAIT_QUEUE_HEAD(fw_load_wq);
+
+static int cdns_mhdp_mailbox_read(struct cdns_mhdp_device *mhdp)
+{
+	int ret, empty;
+
+	WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex));
+
+	ret = readx_poll_timeout(readl, mhdp->regs + CDNS_MAILBOX_EMPTY,
+				 empty, !empty, MAILBOX_RETRY_US,
+				 MAILBOX_TIMEOUT_US);
+	if (ret < 0)
+		return ret;
+
+	return readl(mhdp->regs + CDNS_MAILBOX_RX_DATA) & 0xff;
+}
+
+static int cdns_mhdp_mailbox_write(struct cdns_mhdp_device *mhdp, u8 val)
+{
+	int ret, full;
+
+	WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex));
+
+	ret = readx_poll_timeout(readl, mhdp->regs + CDNS_MAILBOX_FULL,
+				 full, !full, MAILBOX_RETRY_US,
+				 MAILBOX_TIMEOUT_US);
+	if (ret < 0)
+		return ret;
+
+	writel(val, mhdp->regs + CDNS_MAILBOX_TX_DATA);
+
+	return 0;
+}
+
+static int cdns_mhdp_mailbox_validate_receive(struct cdns_mhdp_device *mhdp,
+					      u8 module_id, u8 opcode,
+					      u16 req_size)
+{
+	u32 mbox_size, i;
+	u8 header[4];
+	int ret;
+
+	/* read the header of the message */
+	for (i = 0; i < 4; i++) {
+		ret = cdns_mhdp_mailbox_read(mhdp);
+		if (ret < 0)
+			return ret;
+
+		header[i] = ret;
+	}
+
+	mbox_size = get_unaligned_be16(header + 2);
+
+	if (opcode != header[0] || module_id != header[1] ||
+	    req_size != mbox_size) {
+		/*
+		 * If the message in mailbox is not what we want, we need to
+		 * clear the mailbox by reading its contents.
+		 */
+		for (i = 0; i < mbox_size; i++)
+			if (cdns_mhdp_mailbox_read(mhdp) < 0)
+				break;
+
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int cdns_mhdp_mailbox_read_receive(struct cdns_mhdp_device *mhdp,
+					  u8 *buff, u16 buff_size)
+{
+	u32 i;
+	int ret;
+
+	for (i = 0; i < buff_size; i++) {
+		ret = cdns_mhdp_mailbox_read(mhdp);
+		if (ret < 0)
+			return ret;
+
+		buff[i] = ret;
+	}
+
+	return 0;
+}
+
+static int cdns_mhdp_mailbox_send(struct cdns_mhdp_device *mhdp, u8 module_id,
+				  u8 opcode, u16 size, u8 *message)
+{
+	u8 header[4];
+	int ret, i;
+
+	header[0] = opcode;
+	header[1] = module_id;
+	put_unaligned_be16(size, header + 2);
+
+	for (i = 0; i < 4; i++) {
+		ret = cdns_mhdp_mailbox_write(mhdp, header[i]);
+		if (ret)
+			return ret;
+	}
+
+	for (i = 0; i < size; i++) {
+		ret = cdns_mhdp_mailbox_write(mhdp, message[i]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static
+int cdns_mhdp_reg_read(struct cdns_mhdp_device *mhdp, u32 addr, u32 *value)
+{
+	u8 msg[4], resp[8];
+	int ret;
+
+	put_unaligned_be32(addr, msg);
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_GENERAL,
+				     GENERAL_REGISTER_READ,
+				     sizeof(msg), msg);
+	if (ret)
+		goto err_reg_read;
+
+	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_GENERAL,
+						 GENERAL_REGISTER_READ,
+						 sizeof(resp));
+	if (ret)
+		goto err_reg_read;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, resp, sizeof(resp));
+	if (ret)
+		goto err_reg_read;
+
+	/* Returned address value should be the same as requested */
+	if (memcmp(msg, resp, sizeof(msg))) {
+		ret = -EINVAL;
+		goto err_reg_read;
+	}
+
+	*value = get_unaligned_be32(resp + 4);
+
+err_reg_read:
+	mutex_unlock(&mhdp->mbox_mutex);
+	if (ret) {
+		DRM_DEV_ERROR(mhdp->dev, "Failed to read register.\n");
+		*value = 0;
+	}
+
+	return ret;
+}
+
+static
+int cdns_mhdp_reg_write(struct cdns_mhdp_device *mhdp, u16 addr, u32 val)
+{
+	u8 msg[6];
+	int ret;
+
+	put_unaligned_be16(addr, msg);
+	put_unaligned_be32(val, msg + 2);
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+				     DPTX_WRITE_REGISTER, sizeof(msg), msg);
+
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	return ret;
+}
+
+static
+int cdns_mhdp_reg_write_bit(struct cdns_mhdp_device *mhdp, u16 addr,
+			    u8 start_bit, u8 bits_no, u32 val)
+{
+	u8 field[8];
+	int ret;
+
+	put_unaligned_be16(addr, field);
+	field[2] = start_bit;
+	field[3] = bits_no;
+	put_unaligned_be32(val, field + 4);
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+				     DPTX_WRITE_FIELD, sizeof(field), field);
+
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	return ret;
+}
+
+static
+int cdns_mhdp_dpcd_read(struct cdns_mhdp_device *mhdp,
+			u32 addr, u8 *data, u16 len)
+{
+	u8 msg[5], reg[5];
+	int ret;
+
+	put_unaligned_be16(len, msg);
+	put_unaligned_be24(addr, msg + 2);
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+				     DPTX_READ_DPCD, sizeof(msg), msg);
+	if (ret)
+		goto err_dpcd_read;
+
+	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
+						 DPTX_READ_DPCD,
+						 sizeof(reg) + len);
+	if (ret)
+		goto err_dpcd_read;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
+	if (ret)
+		goto err_dpcd_read;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, data, len);
+
+err_dpcd_read:
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	return ret;
+}
+
+static
+int cdns_mhdp_dpcd_write(struct cdns_mhdp_device *mhdp, u32 addr, u8 value)
+{
+	u8 msg[6], reg[5];
+	int ret;
+
+	put_unaligned_be16(1, msg);
+	put_unaligned_be24(addr, msg + 2);
+	msg[5] = value;
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+				     DPTX_WRITE_DPCD, sizeof(msg), msg);
+	if (ret)
+		goto err_dpcd_write;
+
+	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
+						 DPTX_WRITE_DPCD, sizeof(reg));
+	if (ret)
+		goto err_dpcd_write;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
+	if (ret)
+		goto err_dpcd_write;
+
+	if (addr != get_unaligned_be24(reg + 2))
+		ret = -EINVAL;
+
+err_dpcd_write:
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	if (ret)
+		DRM_DEV_ERROR(mhdp->dev, "dpcd write failed: %d\n", ret);
+	return ret;
+}
+
+static
+int cdns_mhdp_set_firmware_active(struct cdns_mhdp_device *mhdp, bool enable)
+{
+	u8 msg[5];
+	int ret, i;
+
+	msg[0] = GENERAL_MAIN_CONTROL;
+	msg[1] = MB_MODULE_ID_GENERAL;
+	msg[2] = 0;
+	msg[3] = 1;
+	msg[4] = enable ? FW_ACTIVE : FW_STANDBY;
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	for (i = 0; i < sizeof(msg); i++) {
+		ret = cdns_mhdp_mailbox_write(mhdp, msg[i]);
+		if (ret)
+			goto err_set_firmware_active;
+	}
+
+	/* read the firmware state */
+	for (i = 0; i < sizeof(msg); i++)  {
+		ret = cdns_mhdp_mailbox_read(mhdp);
+		if (ret < 0)
+			goto err_set_firmware_active;
+
+		msg[i] = ret;
+	}
+
+	ret = 0;
+
+err_set_firmware_active:
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	if (ret < 0)
+		DRM_DEV_ERROR(mhdp->dev, "set firmware active failed\n");
+	return ret;
+}
+
+static
+int cdns_mhdp_get_hpd_status(struct cdns_mhdp_device *mhdp)
+{
+	u8 status;
+	int ret;
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+				     DPTX_HPD_STATE, 0, NULL);
+	if (ret)
+		goto err_get_hpd;
+
+	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
+						 DPTX_HPD_STATE,
+						 sizeof(status));
+	if (ret)
+		goto err_get_hpd;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, &status, sizeof(status));
+	if (ret)
+		goto err_get_hpd;
+
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	dev_dbg(mhdp->dev, "%s: %d\n", __func__, status);
+
+	return status;
+
+err_get_hpd:
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	DRM_DEV_ERROR(mhdp->dev, "get hpd status failed: %d\n", ret);
+	return ret;
+}
+
+static
+int cdns_mhdp_get_edid_block(void *data, u8 *edid,
+			     unsigned int block, size_t length)
+{
+	struct cdns_mhdp_device *mhdp = data;
+	u8 msg[2], reg[2], i;
+	int ret;
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	for (i = 0; i < 4; i++) {
+		msg[0] = block / 2;
+		msg[1] = block % 2;
+
+		ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+					     DPTX_GET_EDID, sizeof(msg), msg);
+		if (ret)
+			continue;
+
+		ret = cdns_mhdp_mailbox_validate_receive(mhdp,
+							 MB_MODULE_ID_DP_TX,
+							 DPTX_GET_EDID,
+							 sizeof(reg) + length);
+		if (ret)
+			continue;
+
+		ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
+		if (ret)
+			continue;
+
+		ret = cdns_mhdp_mailbox_read_receive(mhdp, edid, length);
+		if (ret)
+			continue;
+
+		if (reg[0] == length && reg[1] == block / 2)
+			break;
+	}
+
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	if (ret)
+		DRM_DEV_ERROR(mhdp->dev, "get block[%d] edid failed: %d\n",
+			      block, ret);
+
+	return ret;
+}
+
+static
+int cdns_mhdp_read_event(struct cdns_mhdp_device *mhdp)
+{
+	u8 event = 0;
+	int ret;
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+				     DPTX_READ_EVENT, 0, NULL);
+	if (ret)
+		goto out;
+
+	ret = cdns_mhdp_mailbox_validate_receive(mhdp,
+						 MB_MODULE_ID_DP_TX,
+						 DPTX_READ_EVENT,
+						 sizeof(event));
+	if (ret < 0)
+		goto out;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, &event,
+					     sizeof(event));
+out:
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(mhdp->dev, "%s: %s%s%s%s\n", __func__,
+		(event & DPTX_READ_EVENT_HPD_TO_HIGH) ? "TO_HIGH " : "",
+		(event & DPTX_READ_EVENT_HPD_TO_LOW) ? "TO_LOW " : "",
+		(event & DPTX_READ_EVENT_HPD_PULSE) ? "PULSE " : "",
+		(event & DPTX_READ_EVENT_HPD_STATE) ? "HPD_STATE " : "");
+
+	return event;
+}
+
+static
+int cdns_mhdp_adjust_lt(struct cdns_mhdp_device *mhdp,
+			u8 nlanes, u16 udelay, u8 *lanes_data, u8 *link_status)
+{
+	u8 payload[7];
+	u8 hdr[5]; /* For DPCD read response header */
+	u32 addr;
+	u8 const nregs = 6; /* Registers 0x202-0x207 */
+	int ret;
+
+	if (nlanes != 4 && nlanes != 2 && nlanes != 1) {
+		DRM_DEV_ERROR(mhdp->dev, "invalid number of lanes: %d\n",
+			      nlanes);
+		ret = -EINVAL;
+		goto err_adjust_lt;
+	}
+
+	payload[0] = nlanes;
+	put_unaligned_be16(udelay, payload + 1);
+	memcpy(payload + 3, lanes_data, nlanes);
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+				     DPTX_ADJUST_LT,
+				     sizeof(payload), payload);
+	if (ret)
+		goto err_adjust_lt;
+
+	/* Yes, read the DPCD read command response */
+	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
+						 DPTX_READ_DPCD,
+						 sizeof(hdr) + nregs);
+	if (ret)
+		goto err_adjust_lt;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, hdr, sizeof(hdr));
+	if (ret)
+		goto err_adjust_lt;
+
+	addr = get_unaligned_be24(hdr + 2);
+	if (addr != DP_LANE0_1_STATUS)
+		goto err_adjust_lt;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, link_status, nregs);
+
+err_adjust_lt:
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	if (ret)
+		DRM_DEV_ERROR(mhdp->dev, "Failed to adjust Link Training.\n");
+
+	return ret;
+}
+
+/**
+ * cdns_mhdp_link_power_up() - power up a DisplayPort link
+ * @aux: DisplayPort AUX channel
+ * @link: pointer to a structure containing the link configuration
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+static
+int cdns_mhdp_link_power_up(struct drm_dp_aux *aux, struct cdns_mhdp_link *link)
+{
+	u8 value;
+	int err;
+
+	/* DP_SET_POWER register is only available on DPCD v1.1 and later */
+	if (link->revision < 0x11)
+		return 0;
+
+	err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value);
+	if (err < 0)
+		return err;
+
+	value &= ~DP_SET_POWER_MASK;
+	value |= DP_SET_POWER_D0;
+
+	err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
+	if (err < 0)
+		return err;
+
+	/*
+	 * According to the DP 1.1 specification, a "Sink Device must exit the
+	 * power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink
+	 * Control Field" (register 0x600).
+	 */
+	usleep_range(1000, 2000);
+
+	return 0;
+}
+
+/**
+ * cdns_mhdp_link_power_down() - power down a DisplayPort link
+ * @aux: DisplayPort AUX channel
+ * @link: pointer to a structure containing the link configuration
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+static
+int cdns_mhdp_link_power_down(struct drm_dp_aux *aux,
+			      struct cdns_mhdp_link *link)
+{
+	u8 value;
+	int err;
+
+	/* DP_SET_POWER register is only available on DPCD v1.1 and later */
+	if (link->revision < 0x11)
+		return 0;
+
+	err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value);
+	if (err < 0)
+		return err;
+
+	value &= ~DP_SET_POWER_MASK;
+	value |= DP_SET_POWER_D3;
+
+	err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/**
+ * cdns_mhdp_link_configure() - configure a DisplayPort link
+ * @aux: DisplayPort AUX channel
+ * @link: pointer to a structure containing the link configuration
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+static
+int cdns_mhdp_link_configure(struct drm_dp_aux *aux,
+			     struct cdns_mhdp_link *link)
+{
+	u8 values[2];
+	int err;
+
+	values[0] = drm_dp_link_rate_to_bw_code(link->rate);
+	values[1] = link->num_lanes;
+
+	if (link->capabilities & DP_LINK_CAP_ENHANCED_FRAMING)
+		values[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
+
+	err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, values, sizeof(values));
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static unsigned int cdns_mhdp_max_link_rate(struct cdns_mhdp_device *mhdp)
+{
+	return min(mhdp->host.link_rate, mhdp->sink.link_rate);
+}
+
+static u8 cdns_mhdp_max_num_lanes(struct cdns_mhdp_device *mhdp)
+{
+	return min(mhdp->sink.lanes_cnt, mhdp->host.lanes_cnt);
+}
+
+static u8 cdns_mhdp_eq_training_pattern_supported(struct cdns_mhdp_device *mhdp)
+{
+	return fls(mhdp->host.pattern_supp & mhdp->sink.pattern_supp);
+}
+
+static bool cdns_mhdp_get_ssc_supported(struct cdns_mhdp_device *mhdp)
+{
+	/* Check if SSC is supported by both sides */
+	return (mhdp->host.ssc) && (mhdp->sink.ssc);
+}
+
+static int cdns_mhdp_check_fw_version(struct cdns_mhdp_device *mhdp)
+{
+	u32 ver_l, ver_h, fw_ver;
+	u32 lib_l_addr, lib_h_addr, lib_ver;
+	u32 major_num, minor_num, revision;
+
+	ver_l = readl(mhdp->regs + CDNS_VER_L);
+	ver_h = readl(mhdp->regs + CDNS_VER_H);
+
+	fw_ver = (ver_h << 8) | ver_l;
+
+	lib_l_addr = readl(mhdp->regs + CDNS_LIB_L_ADDR);
+	lib_h_addr = readl(mhdp->regs + CDNS_LIB_H_ADDR);
+
+	lib_ver = (lib_h_addr << 8) | lib_l_addr;
+
+	if (lib_ver < 33984) {
+		/**
+		 * Older FW versions with major number 1, used to store FW
+		 * version information by storing repository revision number
+		 * in registers. This is for identifying these FW versions.
+		 */
+		major_num = 1;
+		minor_num = 2;
+		if (fw_ver == 26098)
+			revision = 15;
+		else if (lib_ver == 0 && fw_ver == 0)
+			revision = 17;
+		else
+			goto fw_error;
+	} else {
+		/* To identify newer FW versions with major number 2 onwards. */
+		major_num = fw_ver / 10000;
+		minor_num = (fw_ver / 100) % 100;
+		revision = (fw_ver % 10000) % 100;
+	}
+
+	dev_dbg(mhdp->dev, "FW version: v%u.%u.%u\n", major_num, minor_num,
+		revision);
+	return 0;
+
+fw_error:
+	dev_err(mhdp->dev, "Unsupported FW version\n");
+	return -ENODEV;
+}
+
+static int cdns_mhdp_fw_activate(const struct firmware *fw,
+				 struct cdns_mhdp_device *mhdp)
+{
+	unsigned int reg;
+	int ret = 0;
+
+	dev_dbg(mhdp->dev, "%s\n", __func__);
+
+	if (!fw || !fw->data) {
+		dev_err(mhdp->dev, "%s: No firmware.\n", __func__);
+		return -EINVAL;
+	}
+
+	spin_lock(&mhdp->start_lock);
+	if (mhdp->hw_state != MHDP_HW_INACTIVE) {
+		spin_unlock(&mhdp->start_lock);
+		if (mhdp->hw_state != MHDP_HW_STOPPED)
+			dev_err(mhdp->dev, "%s: Bad HW state: %d\n",
+				__func__, mhdp->hw_state);
+		return -EBUSY;
+	}
+	mhdp->hw_state = MHDP_HW_LOADING;
+	spin_unlock(&mhdp->start_lock);
+
+	/* Release uCPU reset and stall it. */
+	writel(CDNS_CPU_STALL, mhdp->regs + CDNS_APB_CTRL);
+
+	memcpy_toio(mhdp->regs + CDNS_MHDP_IMEM, fw->data, fw->size);
+
+	/* Leave debug mode, release stall */
+	writel(0, mhdp->regs + CDNS_APB_CTRL);
+
+	/*
+	 * Wait for the KEEP_ALIVE "message" on the first 8 bits.
+	 * Updated each sched "tick" (~2ms)
+	 */
+	ret = readl_poll_timeout(mhdp->regs + CDNS_KEEP_ALIVE, reg,
+				 reg & CDNS_KEEP_ALIVE_MASK, 500,
+				 CDNS_KEEP_ALIVE_TIMEOUT);
+	if (ret) {
+		dev_err(mhdp->dev,
+			"device didn't give any life sign: reg %d\n", reg);
+		goto error;
+	}
+
+	ret = cdns_mhdp_check_fw_version(mhdp);
+	if (ret)
+		goto error;
+
+	/* Init events to 0 as it's not cleared by FW at boot but on read */
+	readl(mhdp->regs + CDNS_SW_EVENT0);
+	readl(mhdp->regs + CDNS_SW_EVENT1);
+	readl(mhdp->regs + CDNS_SW_EVENT2);
+	readl(mhdp->regs + CDNS_SW_EVENT3);
+
+	/* Activate uCPU */
+	ret = cdns_mhdp_set_firmware_active(mhdp, true);
+	if (ret) {
+		dev_err(mhdp->dev, "%s: Failed to activate FW: %d\n",
+			__func__, ret);
+		goto error;
+	}
+
+	spin_lock(&mhdp->start_lock);
+
+	mhdp->hw_state = MHDP_HW_READY;
+	wake_up(&fw_load_wq);
+	/*
+	 * Here we must keep the lock while enabling the interrupts
+	 * since it would otherwise be possible that interrupt enable
+	 * code is executed after the bridge is detached. The similar
+	 * situation is not possible in attach()/detach() callbacks
+	 * since the hw_state changes from MHDP_HW_READY to
+	 * MHDP_HW_STOPPED happens only due to driver removal when
+	 * bridge should already be detached.
+	 */
+	if (mhdp->bridge_attached)
+		/* enable SW event interrupts */
+		writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
+		       mhdp->regs + CDNS_APB_INT_MASK);
+
+	spin_unlock(&mhdp->start_lock);
+
+	dev_dbg(mhdp->dev, "DP FW activated\n");
+
+	return 0;
+error:
+	spin_lock(&mhdp->start_lock);
+	mhdp->hw_state = MHDP_HW_INACTIVE;
+	spin_unlock(&mhdp->start_lock);
+
+	return ret;
+}
+
+static void cdns_mhdp_fw_cb(const struct firmware *fw, void *context)
+{
+	struct cdns_mhdp_device *mhdp = context;
+	bool bridge_attached;
+	int ret;
+
+	dev_dbg(mhdp->dev, "firmware callback\n");
+
+	ret = cdns_mhdp_fw_activate(fw, mhdp);
+
+	release_firmware(fw);
+
+	if (ret)
+		return;
+
+	/*
+	 *  XXX how to make sure the bridge is still attached when
+	 *      calling drm_kms_helper_hotplug_event() after releasing
+	 *      the lock? We should not hold the spin lock when
+	 *      calling drm_kms_helper_hotplug_event() since it may
+	 *      cause a dead lock. FB-dev console calls detect from the
+	 *      same thread just down the call stack started here.
+	 */
+	spin_lock(&mhdp->start_lock);
+	bridge_attached = mhdp->bridge_attached;
+	spin_unlock(&mhdp->start_lock);
+	if (bridge_attached)
+		drm_kms_helper_hotplug_event(mhdp->bridge.dev);
+}
+
+static int cdns_mhdp_load_firmware(struct cdns_mhdp_device *mhdp)
+{
+	int ret;
+
+	ret = request_firmware_nowait(THIS_MODULE, true, FW_NAME, mhdp->dev,
+				      GFP_KERNEL, mhdp, cdns_mhdp_fw_cb);
+	if (ret) {
+		dev_err(mhdp->dev, "failed to load firmware (%s), ret: %d\n",
+			FW_NAME, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static ssize_t cdns_mhdp_transfer(struct drm_dp_aux *aux,
+				  struct drm_dp_aux_msg *msg)
+{
+	struct cdns_mhdp_device *mhdp = dev_get_drvdata(aux->dev);
+	int ret;
+
+	if (msg->request != DP_AUX_NATIVE_WRITE &&
+	    msg->request != DP_AUX_NATIVE_READ)
+		return -EOPNOTSUPP;
+
+	if (msg->request == DP_AUX_NATIVE_WRITE) {
+		const u8 *buf = msg->buffer;
+		int i;
+
+		for (i = 0; i < msg->size; ++i) {
+			ret = cdns_mhdp_dpcd_write(mhdp,
+						   msg->address + i, buf[i]);
+			if (!ret)
+				continue;
+
+			DRM_DEV_ERROR(mhdp->dev,
+				      "Failed to write DPCD addr %u\n",
+				      msg->address + i);
+
+			return ret;
+		}
+	} else {
+		ret = cdns_mhdp_dpcd_read(mhdp, msg->address,
+					  msg->buffer, msg->size);
+		if (ret) {
+			DRM_DEV_ERROR(mhdp->dev,
+				      "Failed to read DPCD addr %u\n",
+				      msg->address);
+
+			return ret;
+		}
+	}
+
+	return msg->size;
+}
+
+static int cdns_mhdp_link_training_init(struct cdns_mhdp_device *mhdp)
+{
+	u32 reg32;
+	u8 i;
+	union phy_configure_opts phy_cfg;
+	int ret;
+
+	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
+			   DP_TRAINING_PATTERN_DISABLE);
+
+	/* Reset PHY configuration */
+	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1);
+	if (!mhdp->host.scrambler)
+		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_ENHNCD,
+			    mhdp->sink.enhanced & mhdp->host.enhanced);
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_LANE_EN,
+			    CDNS_DP_LANE_EN_LANES(mhdp->link.num_lanes));
+
+	cdns_mhdp_link_configure(&mhdp->aux, &mhdp->link);
+	phy_cfg.dp.link_rate = (mhdp->link.rate / 100);
+	phy_cfg.dp.lanes = (mhdp->link.num_lanes);
+	for (i = 0; i < 4; i++) {
+		phy_cfg.dp.voltage[i] = 0;
+		phy_cfg.dp.pre[i] = 0;
+	}
+	phy_cfg.dp.ssc = cdns_mhdp_get_ssc_supported(mhdp);
+	phy_cfg.dp.set_lanes = true;
+	phy_cfg.dp.set_rate = true;
+	phy_cfg.dp.set_voltages = true;
+	ret = phy_configure(mhdp->phy,  &phy_cfg);
+	if (ret) {
+		dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG,
+			    CDNS_PHY_COMMON_CONFIG |
+			    CDNS_PHY_TRAINING_EN |
+			    CDNS_PHY_TRAINING_TYPE(1) |
+			    CDNS_PHY_SCRAMBLER_BYPASS);
+
+	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
+			   DP_TRAINING_PATTERN_1 | DP_LINK_SCRAMBLING_DISABLE);
+
+	return 0;
+}
+
+static void cdns_mhdp_get_adjust_train(struct cdns_mhdp_device *mhdp,
+				       u8 link_status[DP_LINK_STATUS_SIZE],
+				       u8 lanes_data[CDNS_DP_MAX_NUM_LANES],
+				       union phy_configure_opts *phy_cfg)
+{
+	unsigned int i;
+	u8 adjust, max_pre_emph, max_volt_swing;
+	u8 set_volt, set_pre;
+
+	max_pre_emph = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis)
+			   << DP_TRAIN_PRE_EMPHASIS_SHIFT;
+	max_volt_swing = CDNS_VOLT_SWING(mhdp->host.volt_swing);
+
+	for (i = 0; i < mhdp->link.num_lanes; i++) {
+		/* Check if Voltage swing and pre-emphasis are within limits */
+		adjust = drm_dp_get_adjust_request_voltage(link_status, i);
+		set_volt = min(adjust, max_volt_swing);
+
+		adjust = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
+		set_pre = min(adjust, max_pre_emph)
+			  >> DP_TRAIN_PRE_EMPHASIS_SHIFT;
+
+		/* Voltage swing level and pre-emphasis level combination is
+		 * not allowed: leaving pre-emphasis as-is, and adjusting
+		 * voltage swing.
+		 */
+		if (set_volt + set_pre > 3)
+			set_volt = 3 - set_pre;
+
+		phy_cfg->dp.voltage[i] = set_volt;
+		lanes_data[i] = set_volt;
+
+		if (set_volt == max_volt_swing)
+			lanes_data[i] |= DP_TRAIN_MAX_SWING_REACHED;
+
+		phy_cfg->dp.pre[i] = set_pre;
+		lanes_data[i] |= (set_pre << DP_TRAIN_PRE_EMPHASIS_SHIFT);
+
+		if (set_pre == (max_pre_emph >> DP_TRAIN_PRE_EMPHASIS_SHIFT))
+			lanes_data[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
+	}
+}
+
+static
+void cdns_mhdp_set_adjust_request_voltage(u8 link_status[DP_LINK_STATUS_SIZE],
+					  int lane, u8 volt)
+{
+	int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
+	int s = ((lane & 1) ?
+		 DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT :
+		 DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT);
+	int idx = i - DP_LANE0_1_STATUS;
+
+	link_status[idx] &= ~(DP_ADJUST_VOLTAGE_SWING_LANE0_MASK << s);
+	link_status[idx] |= volt << s;
+}
+
+static
+void cdns_mhdp_set_adjust_request_pre_emphasis(u8 link_status[DP_LINK_STATUS_SIZE],
+					       int lane, u8 pre_emphasis)
+{
+	int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
+	int s = ((lane & 1) ?
+		 DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT :
+		 DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT);
+	int idx = i - DP_LANE0_1_STATUS;
+
+	link_status[idx] &= ~(DP_ADJUST_PRE_EMPHASIS_LANE0_MASK << s);
+	link_status[idx] |= pre_emphasis << s;
+}
+
+static void cdns_mhdp_adjust_requested_eq(struct cdns_mhdp_device *mhdp,
+					  u8 link_status[DP_LINK_STATUS_SIZE])
+{
+	u8 max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis);
+	u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing);
+	unsigned int i;
+	u8 volt, pre;
+
+	for (i = 0; i < mhdp->link.num_lanes; i++) {
+		volt = drm_dp_get_adjust_request_voltage(link_status, i);
+		pre = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
+		if (volt + pre > 3)
+			cdns_mhdp_set_adjust_request_voltage(link_status, i,
+							     3 - pre);
+		if (mhdp->host.volt_swing & CDNS_FORCE_VOLT_SWING)
+			cdns_mhdp_set_adjust_request_voltage(link_status, i,
+							     max_volt);
+		if (mhdp->host.pre_emphasis & CDNS_FORCE_PRE_EMPHASIS)
+			cdns_mhdp_set_adjust_request_pre_emphasis(link_status,
+								  i, max_pre);
+	}
+}
+
+static void cdns_mhdp_print_lt_status(const char *prefix,
+				      struct cdns_mhdp_device *mhdp,
+				      union phy_configure_opts *phy_cfg)
+{
+	int i;
+	char vs_str[8];
+	char pe_str[8];
+	char *vs_p, *pe_p;
+
+	vs_p = vs_str;
+	pe_p = pe_str;
+
+	for (i = 0; i < mhdp->link.num_lanes; i++) {
+		if (i != 0) {
+			*vs_p++ = '/';
+			*pe_p++ = '/';
+		}
+
+		*vs_p++ = '0' + phy_cfg->dp.voltage[i];
+		*pe_p++ = '0' + phy_cfg->dp.pre[i];
+	}
+
+	*vs_p = 0;
+	*pe_p = 0;
+
+	dev_dbg(mhdp->dev, "%s, %u lanes, %u Mbps, vs %s, pe %s\n",
+		prefix,
+		mhdp->link.num_lanes, mhdp->link.rate / 100,
+		vs_str, pe_str);
+}
+
+static bool cdns_mhdp_link_training_channel_eq(struct cdns_mhdp_device *mhdp,
+					       u8 eq_tps,
+					       unsigned int training_interval)
+{
+	u8 lanes_data[CDNS_DP_MAX_NUM_LANES], fail_counter_short = 0;
+	u8 link_status[DP_LINK_STATUS_SIZE];
+	u32 reg32;
+	union phy_configure_opts phy_cfg;
+	int ret;
+	bool r;
+
+	dev_dbg(mhdp->dev, "Starting EQ phase\n");
+
+	/* Enable link training TPS[eq_tps] in PHY */
+	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_EN |
+		CDNS_PHY_TRAINING_TYPE(eq_tps);
+	if (eq_tps != 4)
+		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
+	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
+
+	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
+			   (eq_tps != 4) ? eq_tps | DP_LINK_SCRAMBLING_DISABLE :
+			   CDNS_DP_TRAINING_PATTERN_4);
+
+	drm_dp_dpcd_read_link_status(&mhdp->aux, link_status);
+
+	do {
+		cdns_mhdp_get_adjust_train(mhdp, link_status, lanes_data,
+					   &phy_cfg);
+		phy_cfg.dp.lanes = (mhdp->link.num_lanes);
+		phy_cfg.dp.ssc = cdns_mhdp_get_ssc_supported(mhdp);
+		phy_cfg.dp.set_lanes = false;
+		phy_cfg.dp.set_rate = false;
+		phy_cfg.dp.set_voltages = true;
+		ret = phy_configure(mhdp->phy,  &phy_cfg);
+		if (ret) {
+			dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
+				__func__, ret);
+			goto err;
+		}
+
+		cdns_mhdp_adjust_lt(mhdp, mhdp->link.num_lanes,
+				    training_interval, lanes_data, link_status);
+
+		r = drm_dp_clock_recovery_ok(link_status, mhdp->link.num_lanes);
+		if (!r)
+			goto err;
+
+		if (drm_dp_channel_eq_ok(link_status, mhdp->link.num_lanes)) {
+			cdns_mhdp_print_lt_status("EQ phase ok", mhdp,
+						  &phy_cfg);
+			return true;
+		}
+
+		fail_counter_short++;
+
+		cdns_mhdp_adjust_requested_eq(mhdp, link_status);
+	} while (fail_counter_short < 5);
+
+err:
+	cdns_mhdp_print_lt_status("EQ phase failed", mhdp, &phy_cfg);
+
+	return false;
+}
+
+static void cdns_mhdp_adjust_requested_cr(struct cdns_mhdp_device *mhdp,
+					  u8 link_status[DP_LINK_STATUS_SIZE],
+					  u8 *req_volt, u8 *req_pre)
+{
+	const u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing);
+	const u8 max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis);
+	unsigned int i;
+
+	for (i = 0; i < mhdp->link.num_lanes; i++) {
+		u8 val;
+
+		val = mhdp->host.volt_swing & CDNS_FORCE_VOLT_SWING ?
+		      max_volt : req_volt[i];
+		cdns_mhdp_set_adjust_request_voltage(link_status, i, val);
+
+		val = mhdp->host.pre_emphasis & CDNS_FORCE_PRE_EMPHASIS ?
+		      max_pre : req_pre[i];
+		cdns_mhdp_set_adjust_request_pre_emphasis(link_status, i, val);
+	}
+}
+
+static
+void cdns_mhdp_validate_cr(struct cdns_mhdp_device *mhdp, bool *cr_done,
+			   bool *same_before_adjust, bool *max_swing_reached,
+			   u8 before_cr[DP_LINK_STATUS_SIZE],
+			   u8 after_cr[DP_LINK_STATUS_SIZE], u8 *req_volt,
+			   u8 *req_pre)
+{
+	const u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing);
+	const u8 max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis);
+	bool same_pre, same_volt;
+	unsigned int i;
+	u8 adjust;
+
+	*same_before_adjust = false;
+	*max_swing_reached = false;
+	*cr_done = drm_dp_clock_recovery_ok(after_cr, mhdp->link.num_lanes);
+
+	for (i = 0; i < mhdp->link.num_lanes; i++) {
+		adjust = drm_dp_get_adjust_request_voltage(after_cr, i);
+		req_volt[i] = min(adjust, max_volt);
+
+		adjust = drm_dp_get_adjust_request_pre_emphasis(after_cr, i) >>
+		      DP_TRAIN_PRE_EMPHASIS_SHIFT;
+		req_pre[i] = min(adjust, max_pre);
+
+		same_pre = (before_cr[i] & DP_TRAIN_PRE_EMPHASIS_MASK) ==
+			   req_pre[i] << DP_TRAIN_PRE_EMPHASIS_SHIFT;
+		same_volt = (before_cr[i] & DP_TRAIN_VOLTAGE_SWING_MASK) ==
+			    req_volt[i];
+		if (same_pre && same_volt)
+			*same_before_adjust = true;
+
+		/* 3.1.5.2 in DP Standard v1.4. Table 3-1 */
+		if (!*cr_done && req_volt[i] + req_pre[i] >= 3) {
+			*max_swing_reached = true;
+			return;
+		}
+	}
+}
+
+static bool cdns_mhdp_link_training_cr(struct cdns_mhdp_device *mhdp)
+{
+	u8 lanes_data[CDNS_DP_MAX_NUM_LANES],
+	fail_counter_short = 0, fail_counter_cr_long = 0;
+	u8 link_status[DP_LINK_STATUS_SIZE];
+	bool cr_done;
+	union phy_configure_opts phy_cfg;
+	int ret;
+
+	dev_dbg(mhdp->dev, "Starting CR phase\n");
+
+	ret = cdns_mhdp_link_training_init(mhdp);
+	if (ret)
+		goto err;
+
+	drm_dp_dpcd_read_link_status(&mhdp->aux, link_status);
+
+	do {
+		u8 requested_adjust_volt_swing[CDNS_DP_MAX_NUM_LANES] = {};
+		u8 requested_adjust_pre_emphasis[CDNS_DP_MAX_NUM_LANES] = {};
+		bool same_before_adjust, max_swing_reached;
+
+		cdns_mhdp_get_adjust_train(mhdp, link_status, lanes_data,
+					   &phy_cfg);
+		phy_cfg.dp.lanes = (mhdp->link.num_lanes);
+		phy_cfg.dp.ssc = cdns_mhdp_get_ssc_supported(mhdp);
+		phy_cfg.dp.set_lanes = false;
+		phy_cfg.dp.set_rate = false;
+		phy_cfg.dp.set_voltages = true;
+		ret = phy_configure(mhdp->phy,  &phy_cfg);
+		if (ret) {
+			dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
+				__func__, ret);
+			goto err;
+		}
+
+		cdns_mhdp_adjust_lt(mhdp, mhdp->link.num_lanes, 100,
+				    lanes_data, link_status);
+
+		cdns_mhdp_validate_cr(mhdp, &cr_done, &same_before_adjust,
+				      &max_swing_reached, lanes_data,
+				      link_status,
+				      requested_adjust_volt_swing,
+				      requested_adjust_pre_emphasis);
+
+		if (max_swing_reached) {
+			dev_err(mhdp->dev, "CR: max swing reached\n");
+			goto err;
+		}
+
+		if (cr_done) {
+			cdns_mhdp_print_lt_status("CR phase ok", mhdp,
+						  &phy_cfg);
+			return true;
+		}
+
+		/* Not all CR_DONE bits set */
+		fail_counter_cr_long++;
+
+		if (same_before_adjust) {
+			fail_counter_short++;
+			continue;
+		}
+
+		fail_counter_short = 0;
+		/*
+		 * Voltage swing/pre-emphasis adjust requested
+		 * during CR phase
+		 */
+		cdns_mhdp_adjust_requested_cr(mhdp, link_status,
+					      requested_adjust_volt_swing,
+					      requested_adjust_pre_emphasis);
+	} while (fail_counter_short < 5 && fail_counter_cr_long < 10);
+
+err:
+	cdns_mhdp_print_lt_status("CR phase failed", mhdp, &phy_cfg);
+
+	return false;
+}
+
+static void cdns_mhdp_lower_link_rate(struct cdns_mhdp_link *link)
+{
+	switch (drm_dp_link_rate_to_bw_code(link->rate)) {
+	case DP_LINK_BW_2_7:
+		link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_1_62);
+		break;
+	case DP_LINK_BW_5_4:
+		link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_2_7);
+		break;
+	case DP_LINK_BW_8_1:
+		link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_5_4);
+		break;
+	}
+}
+
+static int cdns_mhdp_link_training(struct cdns_mhdp_device *mhdp,
+				   unsigned int training_interval)
+{
+	u32 reg32;
+	const u8 eq_tps = cdns_mhdp_eq_training_pattern_supported(mhdp);
+	int ret;
+
+	while (1) {
+		if (!cdns_mhdp_link_training_cr(mhdp)) {
+			if (drm_dp_link_rate_to_bw_code(mhdp->link.rate) !=
+			    DP_LINK_BW_1_62) {
+				dev_dbg(mhdp->dev,
+					"Reducing link rate during CR phase\n");
+				cdns_mhdp_lower_link_rate(&mhdp->link);
+
+				continue;
+			} else if (mhdp->link.num_lanes > 1) {
+				dev_dbg(mhdp->dev,
+					"Reducing lanes number during CR phase\n");
+				mhdp->link.num_lanes >>= 1;
+				mhdp->link.rate = cdns_mhdp_max_link_rate(mhdp);
+
+				continue;
+			}
+
+			dev_err(mhdp->dev,
+				"Link training failed during CR phase\n");
+			goto err;
+		}
+
+		if (cdns_mhdp_link_training_channel_eq(mhdp, eq_tps,
+						       training_interval))
+			break;
+
+		if (mhdp->link.num_lanes > 1) {
+			dev_dbg(mhdp->dev,
+				"Reducing lanes number during EQ phase\n");
+			mhdp->link.num_lanes >>= 1;
+
+			continue;
+		} else if (drm_dp_link_rate_to_bw_code(mhdp->link.rate) !=
+			   DP_LINK_BW_1_62) {
+			dev_dbg(mhdp->dev,
+				"Reducing link rate during EQ phase\n");
+			cdns_mhdp_lower_link_rate(&mhdp->link);
+			mhdp->link.num_lanes = cdns_mhdp_max_num_lanes(mhdp);
+
+			continue;
+		}
+
+		dev_err(mhdp->dev, "Link training failed during EQ phase\n");
+		goto err;
+	}
+
+	dev_dbg(mhdp->dev, "Link training ok. Lanes: %u, Rate %u Mbps\n",
+		mhdp->link.num_lanes, mhdp->link.rate / 100);
+
+	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
+			   mhdp->host.scrambler ? 0 :
+			   DP_LINK_SCRAMBLING_DISABLE);
+
+	ret = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &reg32);
+	if (ret < 0) {
+		dev_err(mhdp->dev,
+			"Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n",
+			ret);
+		return ret;
+	}
+	reg32 &= ~GENMASK(1, 0);
+	reg32 |= CDNS_DP_NUM_LANES(mhdp->link.num_lanes);
+	reg32 |= CDNS_DP_WR_FAILING_EDGE_VSYNC;
+	reg32 |= CDNS_DP_FRAMER_EN;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, reg32);
+
+	/* Reset PHY config */
+	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1);
+	if (!mhdp->host.scrambler)
+		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
+	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
+
+	return 0;
+err:
+	/* Reset PHY config */
+	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1);
+	if (!mhdp->host.scrambler)
+		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
+	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
+
+	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
+			   DP_TRAINING_PATTERN_DISABLE);
+
+	return -EIO;
+}
+
+static u32 cdns_mhdp_get_training_interval_us(struct cdns_mhdp_device *mhdp,
+					      u32 interval)
+{
+	if (interval == 0)
+		return 400;
+	if (interval < 5)
+		return 4000 << (interval - 1);
+	dev_err(mhdp->dev,
+		"wrong training interval returned by DPCD: %d\n", interval);
+	return 0;
+}
+
+static void cdns_mhdp_fill_host_caps(struct cdns_mhdp_device *mhdp)
+{
+	unsigned int link_rate;
+	struct phy_attrs attrs;
+
+	/* Get source capabilities based on PHY attributes */
+	phy_get_attrs(mhdp->phy, &attrs);
+
+	mhdp->host.lanes_cnt = attrs.bus_width;
+	if (!mhdp->host.lanes_cnt)
+		mhdp->host.lanes_cnt = 4;
+
+	link_rate = attrs.max_link_rate;
+	if (!link_rate)
+		link_rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_8_1);
+	else
+		/* PHY uses Mb/s, DRM uses tens of kb/s. */
+		link_rate *= 100;
+
+	mhdp->host.link_rate = link_rate;
+	mhdp->host.volt_swing = CDNS_VOLT_SWING(3);
+	mhdp->host.pre_emphasis = CDNS_PRE_EMPHASIS(3);
+	mhdp->host.pattern_supp = CDNS_SUPPORT_TPS(1) |
+				  CDNS_SUPPORT_TPS(2) | CDNS_SUPPORT_TPS(3) |
+				  CDNS_SUPPORT_TPS(4);
+	mhdp->host.lane_mapping = CDNS_LANE_MAPPING_NORMAL;
+	mhdp->host.fast_link = false;
+	mhdp->host.enhanced = true;
+	mhdp->host.scrambler = true;
+	mhdp->host.ssc = false;
+}
+
+static void cdns_mhdp_fill_sink_caps(struct cdns_mhdp_device *mhdp,
+				     u8 dpcd[DP_RECEIVER_CAP_SIZE])
+{
+	mhdp->sink.link_rate = mhdp->link.rate;
+	mhdp->sink.lanes_cnt = mhdp->link.num_lanes;
+	mhdp->sink.enhanced = !!(mhdp->link.capabilities &
+				 DP_LINK_CAP_ENHANCED_FRAMING);
+
+	/* Set SSC support */
+	mhdp->sink.ssc = !!(dpcd[DP_MAX_DOWNSPREAD] &
+				  DP_MAX_DOWNSPREAD_0_5);
+
+	/* Set TPS support */
+	mhdp->sink.pattern_supp = CDNS_SUPPORT_TPS(1) | CDNS_SUPPORT_TPS(2);
+	if (drm_dp_tps3_supported(dpcd))
+		mhdp->sink.pattern_supp |= CDNS_SUPPORT_TPS(3);
+	if (drm_dp_tps4_supported(dpcd))
+		mhdp->sink.pattern_supp |= CDNS_SUPPORT_TPS(4);
+
+	/* Set fast link support */
+	mhdp->sink.fast_link = !!(dpcd[DP_MAX_DOWNSPREAD] &
+				  DP_NO_AUX_HANDSHAKE_LINK_TRAINING);
+}
+
+static int cdns_mhdp_link_up(struct cdns_mhdp_device *mhdp)
+{
+	u32 resp;
+	u8 dpcd[DP_RECEIVER_CAP_SIZE], amp[2];
+	u8 ext_cap_chk = 0;
+	unsigned int addr;
+	int err;
+
+	WARN_ON(!mutex_is_locked(&mhdp->link_mutex));
+
+	drm_dp_dpcd_readb(&mhdp->aux, DP_TRAINING_AUX_RD_INTERVAL,
+			  &ext_cap_chk);
+
+	if (ext_cap_chk & DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT)
+		addr = DP_DP13_DPCD_REV;
+	else
+		addr = DP_DPCD_REV;
+
+	err = drm_dp_dpcd_read(&mhdp->aux, addr, dpcd, DP_RECEIVER_CAP_SIZE);
+	if (err < 0) {
+		dev_err(mhdp->dev, "Failed to read receiver capabilities\n");
+		return err;
+	}
+
+	mhdp->link.revision = dpcd[0];
+	mhdp->link.rate = drm_dp_bw_code_to_link_rate(dpcd[1]);
+	mhdp->link.num_lanes = dpcd[2] & DP_MAX_LANE_COUNT_MASK;
+
+	if (dpcd[2] & DP_ENHANCED_FRAME_CAP)
+		mhdp->link.capabilities |= DP_LINK_CAP_ENHANCED_FRAMING;
+
+	dev_dbg(mhdp->dev, "Set sink device power state via DPCD\n");
+	cdns_mhdp_link_power_up(&mhdp->aux, &mhdp->link);
+
+	cdns_mhdp_fill_sink_caps(mhdp, dpcd);
+
+	mhdp->link.rate = cdns_mhdp_max_link_rate(mhdp);
+	mhdp->link.num_lanes = cdns_mhdp_max_num_lanes(mhdp);
+
+	/* Disable framer for link training */
+	err = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &resp);
+	if (err < 0) {
+		dev_err(mhdp->dev,
+			"Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n",
+			err);
+		return err;
+	}
+
+	resp &= ~CDNS_DP_FRAMER_EN;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, resp);
+
+	/* Spread AMP if required, enable 8b/10b coding */
+	amp[0] = cdns_mhdp_get_ssc_supported(mhdp) ? DP_SPREAD_AMP_0_5 : 0;
+	amp[1] = DP_SET_ANSI_8B10B;
+	drm_dp_dpcd_write(&mhdp->aux, DP_DOWNSPREAD_CTRL, amp, 2);
+
+	if (mhdp->host.fast_link & mhdp->sink.fast_link) {
+		dev_err(mhdp->dev, "fastlink not supported\n");
+		err = -EOPNOTSUPP;
+		goto error;
+	} else {
+		const u32 interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] &
+				     DP_TRAINING_AUX_RD_MASK;
+		const u32 interval_us = cdns_mhdp_get_training_interval_us(mhdp,
+									   interval);
+		if (!interval_us ||
+		    cdns_mhdp_link_training(mhdp, interval_us)) {
+			dev_err(mhdp->dev, "Link training failed. Exiting.\n");
+			err = -EIO;
+			goto error;
+		}
+	}
+
+	mhdp->link_up = true;
+
+	return 0;
+error:
+	return err;
+}
+
+static void cdns_mhdp_link_down(struct cdns_mhdp_device *mhdp)
+{
+	WARN_ON(!mutex_is_locked(&mhdp->link_mutex));
+
+	if (mhdp->plugged)
+		cdns_mhdp_link_power_down(&mhdp->aux, &mhdp->link);
+
+	mhdp->link_up = false;
+}
+
+static int cdns_mhdp_get_modes(struct drm_connector *connector)
+{
+	struct cdns_mhdp_device *mhdp = connector_to_mhdp(connector);
+	struct edid *edid;
+	int num_modes;
+	int ret;
+
+	if (!mhdp->plugged)
+		return 0;
+
+	mutex_lock(&mhdp->link_mutex);
+
+	if (!mhdp->link_up) {
+		ret = cdns_mhdp_link_up(mhdp);
+		if (ret < 0) {
+			mutex_unlock(&mhdp->link_mutex);
+			return 0;
+		}
+	}
+	mutex_unlock(&mhdp->link_mutex);
+
+	edid = drm_do_get_edid(connector, cdns_mhdp_get_edid_block, mhdp);
+	if (!edid) {
+		DRM_DEV_ERROR(mhdp->dev, "Failed to read EDID\n");
+
+		return 0;
+	}
+
+	drm_connector_update_edid_property(connector, edid);
+	num_modes = drm_add_edid_modes(connector, edid);
+	kfree(edid);
+
+	/*
+	 * HACK: Warn about unsupported display formats until we deal
+	 *       with them correctly.
+	 */
+	if (connector->display_info.color_formats &&
+	    !(connector->display_info.color_formats &
+	      mhdp->display_fmt.color_format))
+		dev_warn(mhdp->dev,
+			 "%s: No supported color_format found (0x%08x)\n",
+			__func__, connector->display_info.color_formats);
+
+	if (connector->display_info.bpc &&
+	    connector->display_info.bpc < mhdp->display_fmt.bpc)
+		dev_warn(mhdp->dev, "%s: Display bpc only %d < %d\n",
+			 __func__, connector->display_info.bpc,
+			 mhdp->display_fmt.bpc);
+
+	return num_modes;
+}
+
+static int cdns_mhdp_detect(struct drm_connector *conn,
+			    struct drm_modeset_acquire_ctx *ctx,
+			    bool force)
+{
+	struct cdns_mhdp_device *mhdp = connector_to_mhdp(conn);
+
+	dev_dbg(mhdp->dev, "%s: %d\n", __func__, mhdp->plugged);
+
+	if (mhdp->plugged)
+		return connector_status_connected;
+	else
+		return connector_status_disconnected;
+}
+
+static u32 cdns_mhdp_get_bpp(struct cdns_mhdp_display_fmt *fmt)
+{
+	u32 bpp;
+
+	if (fmt->y_only)
+		return fmt->bpc;
+
+	switch (fmt->color_format) {
+	case DRM_COLOR_FORMAT_RGB444:
+	case DRM_COLOR_FORMAT_YCRCB444:
+		bpp = fmt->bpc * 3;
+		break;
+	case DRM_COLOR_FORMAT_YCRCB422:
+		bpp = fmt->bpc * 2;
+		break;
+	case DRM_COLOR_FORMAT_YCRCB420:
+		bpp = fmt->bpc * 3 / 2;
+		break;
+	default:
+		bpp = fmt->bpc * 3;
+		WARN_ON(1);
+	}
+	return bpp;
+}
+
+static
+bool cdns_mhdp_bandwidth_ok(struct cdns_mhdp_device *mhdp,
+			    const struct drm_display_mode *mode,
+			    int lanes, int rate)
+{
+	u32 max_bw, req_bw, bpp;
+
+	bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt);
+	req_bw = mode->clock * bpp / 8;
+	max_bw = lanes * rate;
+	if (req_bw > max_bw) {
+		dev_dbg(mhdp->dev,
+			"Unsupported Mode: %s, Req BW: %u, Available Max BW:%u\n",
+			mode->name, req_bw, max_bw);
+
+		return false;
+	}
+
+	return true;
+}
+
+static
+enum drm_mode_status cdns_mhdp_mode_valid(struct drm_connector *conn,
+					  struct drm_display_mode *mode)
+{
+	struct cdns_mhdp_device *mhdp = connector_to_mhdp(conn);
+
+	if (!cdns_mhdp_bandwidth_ok(mhdp, mode, mhdp->link.num_lanes,
+				    mhdp->link.rate))
+		return MODE_CLOCK_HIGH;
+
+	return MODE_OK;
+}
+
+static const struct drm_connector_helper_funcs cdns_mhdp_conn_helper_funcs = {
+	.detect_ctx = cdns_mhdp_detect,
+	.get_modes = cdns_mhdp_get_modes,
+	.mode_valid = cdns_mhdp_mode_valid,
+};
+
+static const struct drm_connector_funcs cdns_mhdp_conn_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+	.reset = drm_atomic_helper_connector_reset,
+	.destroy = drm_connector_cleanup,
+};
+
+static int cdns_mhdp_attach(struct drm_bridge *bridge,
+			    enum drm_bridge_attach_flags flags)
+{
+	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
+	u32 bus_format = MEDIA_BUS_FMT_RGB121212_1X36;
+	struct drm_connector *conn = &mhdp->connector;
+	bool hw_ready;
+	int ret;
+
+	dev_dbg(mhdp->dev, "%s\n", __func__);
+
+	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+		DRM_ERROR("Fix bridge driver to make connector optional!");
+		return -EINVAL;
+	}
+
+	if (&mhdp->bridge != bridge)
+		return -ENODEV;
+
+	conn->polled = DRM_CONNECTOR_POLL_HPD;
+
+	ret = drm_connector_init(bridge->dev, conn, &cdns_mhdp_conn_funcs,
+				 DRM_MODE_CONNECTOR_DisplayPort);
+	if (ret) {
+		dev_err(mhdp->dev, "failed to init connector\n");
+		return ret;
+	}
+
+	drm_connector_helper_add(conn, &cdns_mhdp_conn_helper_funcs);
+
+	ret = drm_display_info_set_bus_formats(&conn->display_info,
+					       &bus_format, 1);
+	if (ret)
+		return ret;
+
+	conn->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH;
+
+	ret = drm_connector_attach_encoder(conn, bridge->encoder);
+	if (ret) {
+		dev_err(mhdp->dev, "failed to attach connector to encoder\n");
+		return ret;
+	}
+
+	spin_lock(&mhdp->start_lock);
+
+	mhdp->bridge_attached = true;
+	hw_ready = mhdp->hw_state == MHDP_HW_READY;
+
+	spin_unlock(&mhdp->start_lock);
+
+	if (hw_ready)
+		/* enable SW event interrupts */
+		writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
+		       mhdp->regs + CDNS_APB_INT_MASK);
+
+	return 0;
+}
+
+static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp,
+				      const struct drm_display_mode *mode)
+{
+	unsigned int dp_framer_sp = 0, msa_horizontal_1,
+		msa_vertical_1, bnd_hsync2vsync, hsync2vsync_pol_ctrl,
+		misc0 = 0, misc1 = 0, pxl_repr,
+		front_porch, back_porch, msa_h0, msa_v0, hsync, vsync,
+		dp_vertical_1;
+	u32 bpp, bpc, pxlfmt;
+	u32 framer;
+	u8 stream_id = mhdp->stream_id;
+	int ret;
+
+	pxlfmt = mhdp->display_fmt.color_format;
+	bpc = mhdp->display_fmt.bpc;
+
+	/* If YCBCR supported and stream not SD, use ITU709
+	 * Need to handle ITU version with YCBCR420 when supported
+	 */
+	if ((pxlfmt == DRM_COLOR_FORMAT_YCRCB444 ||
+	     pxlfmt == DRM_COLOR_FORMAT_YCRCB422) && mode->crtc_vdisplay >= 720)
+		misc0 = DP_YCBCR_COEFFICIENTS_ITU709;
+
+	bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt);
+
+	switch (pxlfmt) {
+	case DRM_COLOR_FORMAT_RGB444:
+		pxl_repr = CDNS_DP_FRAMER_RGB << CDNS_DP_FRAMER_PXL_FORMAT;
+		misc0 |= DP_COLOR_FORMAT_RGB;
+		break;
+	case DRM_COLOR_FORMAT_YCRCB444:
+		pxl_repr = CDNS_DP_FRAMER_YCBCR444 << CDNS_DP_FRAMER_PXL_FORMAT;
+		misc0 |= DP_COLOR_FORMAT_YCbCr444 | DP_TEST_DYNAMIC_RANGE_CEA;
+		break;
+	case DRM_COLOR_FORMAT_YCRCB422:
+		pxl_repr = CDNS_DP_FRAMER_YCBCR422 << CDNS_DP_FRAMER_PXL_FORMAT;
+		misc0 |= DP_COLOR_FORMAT_YCbCr422 | DP_TEST_DYNAMIC_RANGE_CEA;
+		break;
+	case DRM_COLOR_FORMAT_YCRCB420:
+		pxl_repr = CDNS_DP_FRAMER_YCBCR420 << CDNS_DP_FRAMER_PXL_FORMAT;
+		break;
+	default:
+		pxl_repr = CDNS_DP_FRAMER_Y_ONLY << CDNS_DP_FRAMER_PXL_FORMAT;
+	}
+
+	switch (bpc) {
+	case 6:
+		misc0 |= DP_TEST_BIT_DEPTH_6;
+		pxl_repr |= CDNS_DP_FRAMER_6_BPC;
+		break;
+	case 8:
+		misc0 |= DP_TEST_BIT_DEPTH_8;
+		pxl_repr |= CDNS_DP_FRAMER_8_BPC;
+		break;
+	case 10:
+		misc0 |= DP_TEST_BIT_DEPTH_10;
+		pxl_repr |= CDNS_DP_FRAMER_10_BPC;
+		break;
+	case 12:
+		misc0 |= DP_TEST_BIT_DEPTH_12;
+		pxl_repr |= CDNS_DP_FRAMER_12_BPC;
+		break;
+	case 16:
+		misc0 |= DP_TEST_BIT_DEPTH_16;
+		pxl_repr |= CDNS_DP_FRAMER_16_BPC;
+		break;
+	}
+
+	bnd_hsync2vsync = CDNS_IP_BYPASS_V_INTERFACE;
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		bnd_hsync2vsync |= CDNS_IP_DET_INTERLACE_FORMAT;
+
+	cdns_mhdp_reg_write(mhdp, CDNS_BND_HSYNC2VSYNC(stream_id),
+			    bnd_hsync2vsync);
+
+	hsync2vsync_pol_ctrl = 0;
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		hsync2vsync_pol_ctrl |= CDNS_H2V_HSYNC_POL_ACTIVE_LOW;
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		hsync2vsync_pol_ctrl |= CDNS_H2V_VSYNC_POL_ACTIVE_LOW;
+	cdns_mhdp_reg_write(mhdp, CDNS_HSYNC2VSYNC_POL_CTRL(stream_id),
+			    hsync2vsync_pol_ctrl);
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_PXL_REPR(stream_id), pxl_repr);
+
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		dp_framer_sp |= CDNS_DP_FRAMER_INTERLACE;
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		dp_framer_sp |= CDNS_DP_FRAMER_HSYNC_POL_LOW;
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		dp_framer_sp |= CDNS_DP_FRAMER_VSYNC_POL_LOW;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_SP(stream_id), dp_framer_sp);
+
+	front_porch = mode->crtc_hsync_start - mode->crtc_hdisplay;
+	back_porch = mode->crtc_htotal - mode->crtc_hsync_end;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRONT_BACK_PORCH(stream_id),
+			    CDNS_DP_FRONT_PORCH(front_porch) |
+			    CDNS_DP_BACK_PORCH(back_porch));
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_BYTE_COUNT(stream_id),
+			    mode->crtc_hdisplay * bpp / 8);
+
+	msa_h0 = mode->crtc_htotal - mode->crtc_hsync_start;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_HORIZONTAL_0(stream_id),
+			    CDNS_DP_MSAH0_H_TOTAL(mode->crtc_htotal) |
+			    CDNS_DP_MSAH0_HSYNC_START(msa_h0));
+
+	hsync = mode->crtc_hsync_end - mode->crtc_hsync_start;
+	msa_horizontal_1 = CDNS_DP_MSAH1_HSYNC_WIDTH(hsync) |
+			   CDNS_DP_MSAH1_HDISP_WIDTH(mode->crtc_hdisplay);
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		msa_horizontal_1 |= CDNS_DP_MSAH1_HSYNC_POL_LOW;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_HORIZONTAL_1(stream_id),
+			    msa_horizontal_1);
+
+	msa_v0 = mode->crtc_vtotal - mode->crtc_vsync_start;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_VERTICAL_0(stream_id),
+			    CDNS_DP_MSAV0_V_TOTAL(mode->crtc_vtotal) |
+			    CDNS_DP_MSAV0_VSYNC_START(msa_v0));
+
+	vsync = mode->crtc_vsync_end - mode->crtc_vsync_start;
+	msa_vertical_1 = CDNS_DP_MSAV1_VSYNC_WIDTH(vsync) |
+			 CDNS_DP_MSAV1_VDISP_WIDTH(mode->crtc_vdisplay);
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		msa_vertical_1 |= CDNS_DP_MSAV1_VSYNC_POL_LOW;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_VERTICAL_1(stream_id),
+			    msa_vertical_1);
+
+	if ((mode->flags & DRM_MODE_FLAG_INTERLACE) &&
+	    mode->crtc_vtotal % 2 == 0)
+		misc1 = DP_TEST_INTERLACED;
+	if (mhdp->display_fmt.y_only)
+		misc1 |= CDNS_DP_TEST_COLOR_FORMAT_RAW_Y_ONLY;
+	/* Use VSC SDP for Y420 */
+	if (pxlfmt == DRM_COLOR_FORMAT_YCRCB420)
+		misc1 = CDNS_DP_TEST_VSC_SDP;
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_MISC(stream_id),
+			    misc0 | (misc1 << 8));
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_HORIZONTAL(stream_id),
+			    CDNS_DP_H_HSYNC_WIDTH(hsync) |
+			    CDNS_DP_H_H_TOTAL(mode->crtc_hdisplay));
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_VERTICAL_0(stream_id),
+			    CDNS_DP_V0_VHEIGHT(mode->crtc_vdisplay) |
+			    CDNS_DP_V0_VSTART(msa_v0));
+
+	dp_vertical_1 = CDNS_DP_V1_VTOTAL(mode->crtc_vtotal);
+	if ((mode->flags & DRM_MODE_FLAG_INTERLACE) &&
+	    mode->crtc_vtotal % 2 == 0)
+		dp_vertical_1 |= CDNS_DP_V1_VTOTAL_EVEN;
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_VERTICAL_1(stream_id), dp_vertical_1);
+
+	cdns_mhdp_reg_write_bit(mhdp, CDNS_DP_VB_ID(stream_id), 2, 1,
+				(mode->flags & DRM_MODE_FLAG_INTERLACE) ?
+				CDNS_DP_VB_ID_INTERLACED : 0);
+
+	ret = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &framer);
+	if (ret < 0) {
+		dev_err(mhdp->dev,
+			"Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n",
+			ret);
+		return;
+	}
+	framer |= CDNS_DP_FRAMER_EN;
+	framer &= ~CDNS_DP_NO_VIDEO_MODE;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, framer);
+}
+
+static void cdns_mhdp_sst_enable(struct cdns_mhdp_device *mhdp,
+				 const struct drm_display_mode *mode,
+				 struct drm_bridge_state *bridge_state)
+{
+	u32 vs, tu_size, line_thresh = 0;
+	struct cdns_mhdp_bridge_state *state;
+
+	state = to_cdns_mhdp_bridge_state(bridge_state);
+
+	vs = state->vs;
+	tu_size = state->tu_size;
+	line_thresh = state->line_thresh;
+
+	mhdp->stream_id = 0;
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_TU,
+			    CDNS_DP_FRAMER_TU_VS(vs) |
+			    CDNS_DP_FRAMER_TU_SIZE(tu_size) |
+			    CDNS_DP_FRAMER_TU_CNT_RST_EN);
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_LINE_THRESH(0),
+			    line_thresh & GENMASK(5, 0));
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_STREAM_CONFIG_2(0),
+			    CDNS_DP_SC2_TU_VS_DIFF((tu_size - vs > 3) ?
+						   0 : tu_size - vs));
+
+	cdns_mhdp_configure_video(mhdp, mode);
+}
+
+static void cdns_mhdp_atomic_enable(struct drm_bridge *bridge,
+				    struct drm_bridge_state *bridge_state)
+{
+	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
+	struct drm_atomic_state *state = bridge_state->base.state;
+	struct cdns_mhdp_bridge_state *mhdp_state;
+	struct drm_crtc_state *crtc_state;
+	struct drm_connector *connector;
+	struct drm_connector_state *conn_state;
+	struct drm_bridge_state *new_state;
+	const struct drm_display_mode *mode;
+	u32 resp;
+	int ret;
+
+	dev_dbg(mhdp->dev, "bridge enable\n");
+
+	mutex_lock(&mhdp->link_mutex);
+
+	if (mhdp->plugged && !mhdp->link_up) {
+		ret = cdns_mhdp_link_up(mhdp);
+		if (ret < 0)
+			goto err_enable;
+	}
+
+	if (mhdp->ops && mhdp->ops->enable)
+		mhdp->ops->enable(mhdp);
+
+	/* Enable VIF clock for stream 0 */
+	ret = cdns_mhdp_reg_read(mhdp, CDNS_DPTX_CAR, &resp);
+	if (ret < 0) {
+		dev_err(mhdp->dev, "Failed to read CDNS_DPTX_CAR %d\n", ret);
+		goto err_enable;
+	}
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_CAR,
+			    resp | CDNS_VIF_CLK_EN | CDNS_VIF_CLK_RSTN);
+
+	connector = drm_atomic_get_new_connector_for_encoder(state,
+							     bridge->encoder);
+	if (WARN_ON(!connector))
+		goto err_enable;
+
+	conn_state = drm_atomic_get_new_connector_state(state, connector);
+	if (WARN_ON(!conn_state))
+		goto err_enable;
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
+	if (WARN_ON(!crtc_state))
+		goto err_enable;
+
+	mode = &crtc_state->adjusted_mode;
+
+	new_state = drm_atomic_get_new_bridge_state(state, bridge);
+	if (WARN_ON(!new_state))
+		goto err_enable;
+
+	cdns_mhdp_sst_enable(mhdp, mode, new_state);
+
+	mhdp_state = to_cdns_mhdp_bridge_state(new_state);
+
+	mhdp_state->current_mode = drm_mode_duplicate(bridge->dev, mode);
+	drm_mode_set_name(mhdp_state->current_mode);
+
+	dev_dbg(mhdp->dev, "%s: Enabling mode %s\n", __func__, mode->name);
+
+	mhdp->bridge_enabled = true;
+
+err_enable:
+	mutex_unlock(&mhdp->link_mutex);
+}
+
+static void cdns_mhdp_atomic_disable(struct drm_bridge *bridge,
+				     struct drm_bridge_state *bridge_state)
+{
+	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
+	u32 resp;
+
+	dev_dbg(mhdp->dev, "%s\n", __func__);
+
+	mutex_lock(&mhdp->link_mutex);
+
+	mhdp->bridge_enabled = false;
+	cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &resp);
+	resp &= ~CDNS_DP_FRAMER_EN;
+	resp |= CDNS_DP_NO_VIDEO_MODE;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, resp);
+
+	cdns_mhdp_link_down(mhdp);
+
+	/* Disable VIF clock for stream 0 */
+	cdns_mhdp_reg_read(mhdp, CDNS_DPTX_CAR, &resp);
+	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_CAR,
+			    resp & ~(CDNS_VIF_CLK_EN | CDNS_VIF_CLK_RSTN));
+
+	if (mhdp->ops && mhdp->ops->disable)
+		mhdp->ops->disable(mhdp);
+
+	mutex_unlock(&mhdp->link_mutex);
+}
+
+static void cdns_mhdp_detach(struct drm_bridge *bridge)
+{
+	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
+
+	dev_dbg(mhdp->dev, "%s\n", __func__);
+
+	spin_lock(&mhdp->start_lock);
+
+	mhdp->bridge_attached = false;
+
+	spin_unlock(&mhdp->start_lock);
+
+	writel(~0, mhdp->regs + CDNS_APB_INT_MASK);
+}
+
+static struct drm_bridge_state *
+cdns_mhdp_bridge_atomic_duplicate_state(struct drm_bridge *bridge)
+{
+	struct cdns_mhdp_bridge_state *state;
+
+	state = kzalloc(sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return NULL;
+
+	__drm_atomic_helper_bridge_duplicate_state(bridge, &state->base);
+
+	return &state->base;
+}
+
+static void
+cdns_mhdp_bridge_atomic_destroy_state(struct drm_bridge *bridge,
+				      struct drm_bridge_state *state)
+{
+	struct cdns_mhdp_bridge_state *cdns_mhdp_state;
+
+	cdns_mhdp_state = to_cdns_mhdp_bridge_state(state);
+
+	kfree(cdns_mhdp_state);
+}
+
+static struct drm_bridge_state *
+cdns_mhdp_bridge_atomic_reset(struct drm_bridge *bridge)
+{
+	struct cdns_mhdp_bridge_state *cdns_mhdp_state;
+
+	cdns_mhdp_state = kzalloc(sizeof(*cdns_mhdp_state), GFP_KERNEL);
+	if (!cdns_mhdp_state)
+		return NULL;
+
+	 __drm_atomic_helper_bridge_reset(bridge, &cdns_mhdp_state->base);
+
+	return &cdns_mhdp_state->base;
+}
+
+static int cdns_mhdp_validate_mode_params(struct cdns_mhdp_device *mhdp,
+					  const struct drm_display_mode *mode,
+					  struct drm_bridge_state *bridge_state)
+{
+	u32 tu_size = 30, line_thresh1, line_thresh2, line_thresh = 0;
+	u32 rate, vs, vs_f, required_bandwidth, available_bandwidth;
+	struct cdns_mhdp_bridge_state *state;
+	u32 bpp, bpc, pxlfmt;
+	int pxlclock, ret;
+
+	state = to_cdns_mhdp_bridge_state(bridge_state);
+	pxlfmt = mhdp->display_fmt.color_format;
+	bpc = mhdp->display_fmt.bpc;
+
+	pxlclock = mode->crtc_clock;
+
+	rate = mhdp->link.rate / 1000;
+
+	bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt);
+
+	if (!cdns_mhdp_bandwidth_ok(mhdp, mode, mhdp->link.num_lanes,
+				    mhdp->link.rate)) {
+		dev_err(mhdp->dev, "%s: Not enough BW for %s (%u lanes at %u Mbps)\n",
+			__func__, mode->name, mhdp->link.num_lanes,
+			mhdp->link.rate / 100);
+		ret = -EINVAL;
+		goto err_tu_size;
+	}
+
+	/* find optimal tu_size */
+	required_bandwidth = pxlclock * bpp / 8;
+	available_bandwidth = mhdp->link.num_lanes * rate;
+	do {
+		tu_size += 2;
+
+		vs_f = tu_size * required_bandwidth / available_bandwidth;
+		vs = vs_f / 1000;
+		vs_f = vs_f % 1000;
+		/* Downspreading is unused currently */
+	} while ((vs == 1 || ((vs_f > 850 || vs_f < 100) && vs_f != 0) ||
+		 tu_size - vs < 2) && tu_size < 64);
+
+	if (vs > 64) {
+		dev_err(mhdp->dev,
+			"%s: No space for framing %s (%u lanes at %u Mbps)\n",
+			__func__, mode->name, mhdp->link.num_lanes,
+			mhdp->link.rate / 100);
+		ret = -EINVAL;
+		goto err_tu_size;
+	}
+
+	line_thresh1 = ((vs + 1) << 5) * 8 / bpp;
+	line_thresh2 = (pxlclock << 5) / 1000 / rate * (vs + 1) - (1 << 5);
+	line_thresh = line_thresh1 - line_thresh2 / mhdp->link.num_lanes;
+	line_thresh = (line_thresh >> 5) + 2;
+
+	state->vs = vs;
+	state->tu_size = tu_size;
+	state->line_thresh = line_thresh;
+
+	return 0;
+
+err_tu_size:
+	return ret;
+}
+
+static int cdns_mhdp_atomic_check(struct drm_bridge *bridge,
+				  struct drm_bridge_state *bridge_state,
+				  struct drm_crtc_state *crtc_state,
+				  struct drm_connector_state *conn_state)
+{
+	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
+	const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+	int ret;
+
+	if (!mhdp->plugged)
+		return 0;
+
+	mutex_lock(&mhdp->link_mutex);
+
+	if (!mhdp->link_up) {
+		ret = cdns_mhdp_link_up(mhdp);
+		if (ret < 0)
+			goto err_check;
+	}
+
+	ret = cdns_mhdp_validate_mode_params(mhdp, mode, bridge_state);
+
+err_check:
+	mutex_unlock(&mhdp->link_mutex);
+	return ret;
+}
+
+static const struct drm_bridge_funcs cdns_mhdp_bridge_funcs = {
+	.atomic_enable = cdns_mhdp_atomic_enable,
+	.atomic_disable = cdns_mhdp_atomic_disable,
+	.atomic_check = cdns_mhdp_atomic_check,
+	.attach = cdns_mhdp_attach,
+	.detach = cdns_mhdp_detach,
+	.atomic_duplicate_state = cdns_mhdp_bridge_atomic_duplicate_state,
+	.atomic_destroy_state = cdns_mhdp_bridge_atomic_destroy_state,
+	.atomic_reset = cdns_mhdp_bridge_atomic_reset,
+};
+
+static bool cdns_mhdp_detect_hpd(struct cdns_mhdp_device *mhdp, bool *hpd_pulse)
+{
+	int hpd_event, hpd_status;
+
+	*hpd_pulse = false;
+
+	hpd_event = cdns_mhdp_read_event(mhdp);
+
+	/* Getting event bits failed, bail out */
+	if (hpd_event < 0) {
+		dev_warn(mhdp->dev, "%s: read event failed: %d\n",
+			 __func__, hpd_event);
+		return false;
+	}
+
+	hpd_status = cdns_mhdp_get_hpd_status(mhdp);
+	if (hpd_status < 0) {
+		dev_warn(mhdp->dev, "%s: get hpd status failed: %d\n",
+			 __func__, hpd_status);
+		return false;
+	}
+
+	if (hpd_event & DPTX_READ_EVENT_HPD_PULSE)
+		*hpd_pulse = true;
+
+	return !!hpd_status;
+}
+
+static void cdns_mhdp_update_link_status(struct cdns_mhdp_device *mhdp)
+{
+	bool hpd_pulse;
+	int ret;
+	struct drm_bridge_state *state;
+	struct cdns_mhdp_bridge_state *cdns_bridge_state;
+	struct drm_connector *conn = &mhdp->connector;
+	struct drm_display_mode *current_mode;
+	bool old_plugged = mhdp->plugged;
+	u8 status[DP_LINK_STATUS_SIZE];
+
+	mhdp->plugged = cdns_mhdp_detect_hpd(mhdp, &hpd_pulse);
+
+	mutex_lock(&mhdp->link_mutex);
+
+	if (!mhdp->plugged) {
+		cdns_mhdp_link_down(mhdp);
+		goto err;
+	}
+
+	/*
+	 * If we get a HPD pulse event and we were and still are connected,
+	 * check the link status. If link status is ok, there's nothing to do
+	 * as we don't handle DP interrupts. If link status is bad, continue
+	 * with full link setup.
+	 */
+	if (hpd_pulse && old_plugged == mhdp->plugged) {
+		ret = drm_dp_dpcd_read_link_status(&mhdp->aux, status);
+
+		/*
+		 * If everything looks fine, just return, as we don't handle
+		 * DP IRQs.
+		 */
+		if (ret > 0 &&
+		    drm_dp_channel_eq_ok(status, mhdp->link.num_lanes) &&
+		    drm_dp_clock_recovery_ok(status, mhdp->link.num_lanes)) {
+			mutex_unlock(&mhdp->link_mutex);
+			return;
+		}
+
+		/* If link is bad, mark link as down so that we do a new LT */
+		mhdp->link_up = false;
+	}
+
+	if (!mhdp->link_up) {
+		ret = cdns_mhdp_link_up(mhdp);
+		if (ret < 0) {
+			mutex_unlock(&mhdp->link_mutex);
+			drm_connector_set_link_status_property(conn,
+							       DRM_MODE_LINK_STATUS_BAD);
+			return;
+		}
+	}
+
+	if (mhdp->bridge_enabled) {
+		state = drm_priv_to_bridge_state(mhdp->bridge.base.state);
+		if (!state)
+			goto err;
+
+		cdns_bridge_state = to_cdns_mhdp_bridge_state(state);
+		if (!cdns_bridge_state)
+			goto err;
+
+		current_mode = cdns_bridge_state->current_mode;
+		if (!current_mode)
+			goto err;
+
+		ret = cdns_mhdp_validate_mode_params(mhdp, current_mode, state);
+		if (ret < 0)
+			goto err;
+
+		dev_dbg(mhdp->dev, "%s: Enabling mode %s\n", __func__,
+			current_mode->name);
+
+		cdns_mhdp_sst_enable(mhdp, current_mode, state);
+	}
+err:
+	mutex_unlock(&mhdp->link_mutex);
+}
+
+static irqreturn_t cdns_mhdp_irq_handler(int irq, void *data)
+{
+	struct cdns_mhdp_device *mhdp = (struct cdns_mhdp_device *)data;
+	u32 apb_stat, sw_ev0;
+	bool bridge_attached;
+
+	apb_stat = readl(mhdp->regs + CDNS_APB_INT_STATUS);
+	sw_ev0 = readl(mhdp->regs + CDNS_SW_EVENT0);
+
+	/*
+	 *  Calling drm_kms_helper_hotplug_event() when not attached
+	 *  to drm device causes an oops because the drm_bridge->dev
+	 *  is NULL. See cdns_mhdp_fw_cb() comments for details about the
+	 *  problems related drm_kms_helper_hotplug_event() call.
+	 */
+	spin_lock(&mhdp->start_lock);
+	bridge_attached = mhdp->bridge_attached;
+	spin_unlock(&mhdp->start_lock);
+
+	if (bridge_attached && (sw_ev0 & CDNS_DPTX_HPD)) {
+		cdns_mhdp_update_link_status(mhdp);
+
+		drm_kms_helper_hotplug_event(mhdp->bridge.dev);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int cdns_mhdp_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct cdns_mhdp_device *mhdp;
+	struct clk *clk;
+	int ret;
+	unsigned long rate;
+	int irq;
+
+	mhdp = devm_kzalloc(dev, sizeof(*mhdp), GFP_KERNEL);
+	if (!mhdp)
+		return -ENOMEM;
+
+	clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(clk)) {
+		dev_err(dev, "couldn't get clk: %ld\n", PTR_ERR(clk));
+		return PTR_ERR(clk);
+	}
+
+	mhdp->clk = clk;
+	mhdp->dev = dev;
+	mutex_init(&mhdp->mbox_mutex);
+	mutex_init(&mhdp->link_mutex);
+	spin_lock_init(&mhdp->start_lock);
+
+	drm_dp_aux_init(&mhdp->aux);
+	mhdp->aux.dev = dev;
+	mhdp->aux.transfer = cdns_mhdp_transfer;
+
+	mhdp->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(mhdp->regs)) {
+		dev_err(dev, "Failed to get memory resource\n");
+		return PTR_ERR(mhdp->regs);
+	}
+
+	mhdp->phy = devm_of_phy_get_by_index(dev, pdev->dev.of_node, 0);
+	if (IS_ERR(mhdp->phy)) {
+		dev_err(dev, "no PHY configured\n");
+		return PTR_ERR(mhdp->phy);
+	}
+
+	platform_set_drvdata(pdev, mhdp);
+
+	mhdp->ops = of_device_get_match_data(dev);
+
+	clk_prepare_enable(clk);
+
+	pm_runtime_enable(dev);
+	ret = pm_runtime_get_sync(dev);
+	if (ret < 0) {
+		dev_err(dev, "pm_runtime_get_sync failed\n");
+		pm_runtime_disable(dev);
+		goto clk_disable;
+	}
+
+	if (mhdp->ops && mhdp->ops->init) {
+		ret = mhdp->ops->init(mhdp);
+		if (ret != 0) {
+			dev_err(dev, "MHDP platform initialization failed: %d\n",
+				ret);
+			goto runtime_put;
+		}
+	}
+
+	rate = clk_get_rate(clk);
+	writel(rate % 1000000, mhdp->regs + CDNS_SW_CLK_L);
+	writel(rate / 1000000, mhdp->regs + CDNS_SW_CLK_H);
+
+	dev_dbg(dev, "func clk rate %lu Hz\n", rate);
+
+	writel(~0, mhdp->regs + CDNS_APB_INT_MASK);
+
+	irq = platform_get_irq(pdev, 0);
+	ret = devm_request_threaded_irq(mhdp->dev, irq, NULL,
+					cdns_mhdp_irq_handler, IRQF_ONESHOT,
+					"mhdp8546", mhdp);
+	if (ret) {
+		dev_err(dev, "cannot install IRQ %d\n", irq);
+		ret = -EIO;
+		goto plat_fini;
+	}
+
+	cdns_mhdp_fill_host_caps(mhdp);
+
+	/* The only currently supported format */
+	mhdp->display_fmt.y_only = false;
+	mhdp->display_fmt.color_format = DRM_COLOR_FORMAT_RGB444;
+	mhdp->display_fmt.bpc = 8;
+
+	mhdp->bridge.of_node = pdev->dev.of_node;
+	mhdp->bridge.funcs = &cdns_mhdp_bridge_funcs;
+
+	ret = phy_init(mhdp->phy);
+	if (ret) {
+		dev_err(mhdp->dev, "Failed to initialize PHY: %d\n", ret);
+		goto plat_fini;
+	}
+
+	ret = cdns_mhdp_load_firmware(mhdp);
+	if (ret)
+		goto phy_exit;
+
+	drm_bridge_add(&mhdp->bridge);
+
+	return 0;
+
+phy_exit:
+	phy_exit(mhdp->phy);
+plat_fini:
+	if (mhdp->ops && mhdp->ops->exit)
+		mhdp->ops->exit(mhdp);
+runtime_put:
+	pm_runtime_put_sync(dev);
+	pm_runtime_disable(dev);
+clk_disable:
+	clk_disable_unprepare(mhdp->clk);
+
+	return ret;
+}
+
+static int cdns_mhdp_remove(struct platform_device *pdev)
+{
+	struct cdns_mhdp_device *mhdp = dev_get_drvdata(&pdev->dev);
+	unsigned long timeout = msecs_to_jiffies(100);
+	bool stop_fw = false;
+	int ret = 0;
+
+	drm_bridge_remove(&mhdp->bridge);
+
+	ret = wait_event_timeout(fw_load_wq, mhdp->hw_state == MHDP_HW_READY,
+				 timeout);
+	if (ret == 0)
+		dev_err(mhdp->dev, "%s: Timeout waiting for fw loading\n",
+			__func__);
+	else
+		stop_fw = true;
+
+	spin_lock(&mhdp->start_lock);
+	mhdp->hw_state = MHDP_HW_STOPPED;
+	spin_unlock(&mhdp->start_lock);
+
+	if (stop_fw) {
+		ret = cdns_mhdp_set_firmware_active(mhdp, false);
+		if (ret)
+			dev_err(mhdp->dev, "%s: De-activate FW failed: %d\n",
+				__func__, ret);
+	}
+
+	phy_exit(mhdp->phy);
+
+	if (mhdp->ops && mhdp->ops->exit)
+		mhdp->ops->exit(mhdp);
+
+	pm_runtime_put_sync(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+
+	clk_disable_unprepare(mhdp->clk);
+
+	return ret;
+}
+
+static const struct of_device_id mhdp_ids[] = {
+	{ .compatible = "cdns,mhdp8546", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mhdp_ids);
+
+static struct platform_driver mhdp_driver = {
+	.driver	= {
+		.name		= "cdns-mhdp",
+		.of_match_table	= of_match_ptr(mhdp_ids),
+	},
+	.probe	= cdns_mhdp_probe,
+	.remove	= cdns_mhdp_remove,
+};
+module_platform_driver(mhdp_driver);
+
+MODULE_FIRMWARE(FW_NAME);
+
+MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
+MODULE_AUTHOR("Swapnil Jakhade <sjakhade@cadence.com>");
+MODULE_DESCRIPTION("Cadence MHDP DP bridge driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cdns-mhdp");
diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.h b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
new file mode 100644
index 000000000000..d1bfcdef4249
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
@@ -0,0 +1,396 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Cadence MHDP DP bridge driver.
+ *
+ * Copyright: 2019 Cadence Design Systems, Inc.
+ *
+ * Author: Quentin Schulz <quentin.schulz@free-electrons.com>
+ *         Swapnil Jakhade <sjakhade@cadence.com>
+ */
+
+#ifndef CDNS_MHDP_CORE_H
+#define CDNS_MHDP_CORE_H
+
+#include <linux/bits.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_dp_helper.h>
+
+struct clk;
+struct device;
+struct phy;
+
+/* Register offsets */
+#define CDNS_APB_CTRL				0x00000
+#define CDNS_CPU_STALL				BIT(3)
+
+#define CDNS_MAILBOX_FULL			0x00008
+#define CDNS_MAILBOX_EMPTY			0x0000c
+#define CDNS_MAILBOX_TX_DATA			0x00010
+#define CDNS_MAILBOX_RX_DATA			0x00014
+#define CDNS_KEEP_ALIVE				0x00018
+#define CDNS_KEEP_ALIVE_MASK			GENMASK(7, 0)
+
+#define CDNS_VER_L				0x0001C
+#define CDNS_VER_H				0x00020
+#define CDNS_LIB_L_ADDR				0x00024
+#define CDNS_LIB_H_ADDR				0x00028
+
+#define CDNS_MB_INT_MASK			0x00034
+#define CDNS_MB_INT_STATUS			0x00038
+
+#define CDNS_SW_CLK_L				0x0003c
+#define CDNS_SW_CLK_H				0x00040
+
+#define CDNS_SW_EVENT0				0x00044
+#define CDNS_DPTX_HPD				BIT(0)
+
+#define CDNS_SW_EVENT1				0x00048
+#define CDNS_SW_EVENT2				0x0004c
+#define CDNS_SW_EVENT3				0x00050
+
+#define CDNS_APB_INT_MASK			0x0006C
+#define CDNS_APB_INT_MASK_MAILBOX_INT		BIT(0)
+#define CDNS_APB_INT_MASK_SW_EVENT_INT		BIT(1)
+
+#define CDNS_APB_INT_STATUS			0x00070
+
+#define CDNS_DPTX_CAR				0x00904
+#define CDNS_VIF_CLK_EN				BIT(0)
+#define CDNS_VIF_CLK_RSTN			BIT(1)
+
+#define CDNS_SOURCE_VIDEO_IF(s)			(0x00b00 + ((s) * 0x20))
+#define CDNS_BND_HSYNC2VSYNC(s)			(CDNS_SOURCE_VIDEO_IF(s) + \
+						 0x00)
+#define CDNS_IP_DTCT_WIN			GENMASK(11, 0)
+#define CDNS_IP_DET_INTERLACE_FORMAT		BIT(12)
+#define CDNS_IP_BYPASS_V_INTERFACE		BIT(13)
+
+#define CDNS_HSYNC2VSYNC_POL_CTRL(s)		(CDNS_SOURCE_VIDEO_IF(s) + \
+						 0x10)
+#define CDNS_H2V_HSYNC_POL_ACTIVE_LOW		BIT(1)
+#define CDNS_H2V_VSYNC_POL_ACTIVE_LOW		BIT(2)
+
+#define CDNS_DPTX_PHY_CONFIG			0x02000
+#define CDNS_PHY_TRAINING_EN			BIT(0)
+#define CDNS_PHY_TRAINING_TYPE(x)		(((x) & GENMASK(3, 0)) << 1)
+#define CDNS_PHY_SCRAMBLER_BYPASS		BIT(5)
+#define CDNS_PHY_ENCODER_BYPASS			BIT(6)
+#define CDNS_PHY_SKEW_BYPASS			BIT(7)
+#define CDNS_PHY_TRAINING_AUTO			BIT(8)
+#define CDNS_PHY_LANE0_SKEW(x)			(((x) & GENMASK(2, 0)) << 9)
+#define CDNS_PHY_LANE1_SKEW(x)			(((x) & GENMASK(2, 0)) << 12)
+#define CDNS_PHY_LANE2_SKEW(x)			(((x) & GENMASK(2, 0)) << 15)
+#define CDNS_PHY_LANE3_SKEW(x)			(((x) & GENMASK(2, 0)) << 18)
+#define CDNS_PHY_COMMON_CONFIG			(CDNS_PHY_LANE1_SKEW(1) | \
+						CDNS_PHY_LANE2_SKEW(2) |  \
+						CDNS_PHY_LANE3_SKEW(3))
+#define CDNS_PHY_10BIT_EN			BIT(21)
+
+#define CDNS_DP_FRAMER_GLOBAL_CONFIG		0x02200
+#define CDNS_DP_NUM_LANES(x)			((x) - 1)
+#define CDNS_DP_MST_EN				BIT(2)
+#define CDNS_DP_FRAMER_EN			BIT(3)
+#define CDNS_DP_RATE_GOVERNOR_EN		BIT(4)
+#define CDNS_DP_NO_VIDEO_MODE			BIT(5)
+#define CDNS_DP_DISABLE_PHY_RST			BIT(6)
+#define CDNS_DP_WR_FAILING_EDGE_VSYNC		BIT(7)
+
+#define CDNS_DP_FRAMER_TU			0x02208
+#define CDNS_DP_FRAMER_TU_SIZE(x)		(((x) & GENMASK(6, 0)) << 8)
+#define CDNS_DP_FRAMER_TU_VS(x)			((x) & GENMASK(5, 0))
+#define CDNS_DP_FRAMER_TU_CNT_RST_EN		BIT(15)
+
+#define CDNS_DP_MTPH_CONTROL			0x02264
+#define CDNS_DP_MTPH_ECF_EN			BIT(0)
+#define CDNS_DP_MTPH_ACT_EN			BIT(1)
+#define CDNS_DP_MTPH_LVP_EN			BIT(2)
+
+#define CDNS_DP_MTPH_STATUS			0x0226C
+#define CDNS_DP_MTPH_ACT_STATUS			BIT(0)
+
+#define CDNS_DP_LANE_EN				0x02300
+#define CDNS_DP_LANE_EN_LANES(x)		GENMASK((x) - 1, 0)
+
+#define CDNS_DP_ENHNCD				0x02304
+
+#define CDNS_DPTX_STREAM(s)			(0x03000 + (s) * 0x80)
+#define CDNS_DP_MSA_HORIZONTAL_0(s)		(CDNS_DPTX_STREAM(s) + 0x00)
+#define CDNS_DP_MSAH0_H_TOTAL(x)		(x)
+#define CDNS_DP_MSAH0_HSYNC_START(x)		((x) << 16)
+
+#define CDNS_DP_MSA_HORIZONTAL_1(s)		(CDNS_DPTX_STREAM(s) + 0x04)
+#define CDNS_DP_MSAH1_HSYNC_WIDTH(x)		(x)
+#define CDNS_DP_MSAH1_HSYNC_POL_LOW		BIT(15)
+#define CDNS_DP_MSAH1_HDISP_WIDTH(x)		((x) << 16)
+
+#define CDNS_DP_MSA_VERTICAL_0(s)		(CDNS_DPTX_STREAM(s) + 0x08)
+#define CDNS_DP_MSAV0_V_TOTAL(x)		(x)
+#define CDNS_DP_MSAV0_VSYNC_START(x)		((x) << 16)
+
+#define CDNS_DP_MSA_VERTICAL_1(s)		(CDNS_DPTX_STREAM(s) + 0x0c)
+#define CDNS_DP_MSAV1_VSYNC_WIDTH(x)		(x)
+#define CDNS_DP_MSAV1_VSYNC_POL_LOW		BIT(15)
+#define CDNS_DP_MSAV1_VDISP_WIDTH(x)		((x) << 16)
+
+#define CDNS_DP_MSA_MISC(s)			(CDNS_DPTX_STREAM(s) + 0x10)
+#define CDNS_DP_STREAM_CONFIG(s)		(CDNS_DPTX_STREAM(s) + 0x14)
+#define CDNS_DP_STREAM_CONFIG_2(s)		(CDNS_DPTX_STREAM(s) + 0x2c)
+#define CDNS_DP_SC2_TU_VS_DIFF(x)		((x) << 8)
+
+#define CDNS_DP_HORIZONTAL(s)			(CDNS_DPTX_STREAM(s) + 0x30)
+#define CDNS_DP_H_HSYNC_WIDTH(x)		(x)
+#define CDNS_DP_H_H_TOTAL(x)			((x) << 16)
+
+#define CDNS_DP_VERTICAL_0(s)			(CDNS_DPTX_STREAM(s) + 0x34)
+#define CDNS_DP_V0_VHEIGHT(x)			(x)
+#define CDNS_DP_V0_VSTART(x)			((x) << 16)
+
+#define CDNS_DP_VERTICAL_1(s)			(CDNS_DPTX_STREAM(s) + 0x38)
+#define CDNS_DP_V1_VTOTAL(x)			(x)
+#define CDNS_DP_V1_VTOTAL_EVEN			BIT(16)
+
+#define CDNS_DP_MST_SLOT_ALLOCATE(s)		(CDNS_DPTX_STREAM(s) + 0x44)
+#define CDNS_DP_S_ALLOC_START_SLOT(x)		(x)
+#define CDNS_DP_S_ALLOC_END_SLOT(x)		((x) << 8)
+
+#define CDNS_DP_RATE_GOVERNING(s)		(CDNS_DPTX_STREAM(s) + 0x48)
+#define CDNS_DP_RG_TARG_AV_SLOTS_Y(x)		(x)
+#define CDNS_DP_RG_TARG_AV_SLOTS_X(x)		((x) << 4)
+#define CDNS_DP_RG_ENABLE			BIT(10)
+
+#define CDNS_DP_FRAMER_PXL_REPR(s)		(CDNS_DPTX_STREAM(s) + 0x4c)
+#define CDNS_DP_FRAMER_6_BPC			BIT(0)
+#define CDNS_DP_FRAMER_8_BPC			BIT(1)
+#define CDNS_DP_FRAMER_10_BPC			BIT(2)
+#define CDNS_DP_FRAMER_12_BPC			BIT(3)
+#define CDNS_DP_FRAMER_16_BPC			BIT(4)
+#define CDNS_DP_FRAMER_PXL_FORMAT		0x8
+#define CDNS_DP_FRAMER_RGB			BIT(0)
+#define CDNS_DP_FRAMER_YCBCR444			BIT(1)
+#define CDNS_DP_FRAMER_YCBCR422			BIT(2)
+#define CDNS_DP_FRAMER_YCBCR420			BIT(3)
+#define CDNS_DP_FRAMER_Y_ONLY			BIT(4)
+
+#define CDNS_DP_FRAMER_SP(s)			(CDNS_DPTX_STREAM(s) + 0x50)
+#define CDNS_DP_FRAMER_VSYNC_POL_LOW		BIT(0)
+#define CDNS_DP_FRAMER_HSYNC_POL_LOW		BIT(1)
+#define CDNS_DP_FRAMER_INTERLACE		BIT(2)
+
+#define CDNS_DP_LINE_THRESH(s)			(CDNS_DPTX_STREAM(s) + 0x64)
+#define CDNS_DP_ACTIVE_LINE_THRESH(x)		(x)
+
+#define CDNS_DP_VB_ID(s)			(CDNS_DPTX_STREAM(s) + 0x68)
+#define CDNS_DP_VB_ID_INTERLACED		BIT(2)
+#define CDNS_DP_VB_ID_COMPRESSED		BIT(6)
+
+#define CDNS_DP_FRONT_BACK_PORCH(s)		(CDNS_DPTX_STREAM(s) + 0x78)
+#define CDNS_DP_BACK_PORCH(x)			(x)
+#define CDNS_DP_FRONT_PORCH(x)			((x) << 16)
+
+#define CDNS_DP_BYTE_COUNT(s)			(CDNS_DPTX_STREAM(s) + 0x7c)
+#define CDNS_DP_BYTE_COUNT_BYTES_IN_CHUNK_SHIFT	16
+
+/* mailbox */
+#define MAILBOX_RETRY_US			1000
+#define MAILBOX_TIMEOUT_US			2000000
+
+#define MB_OPCODE_ID				0
+#define MB_MODULE_ID				1
+#define MB_SIZE_MSB_ID				2
+#define MB_SIZE_LSB_ID				3
+#define MB_DATA_ID				4
+
+#define MB_MODULE_ID_DP_TX			0x01
+#define MB_MODULE_ID_HDCP_TX			0x07
+#define MB_MODULE_ID_HDCP_RX			0x08
+#define MB_MODULE_ID_HDCP_GENERAL		0x09
+#define MB_MODULE_ID_GENERAL			0x0a
+
+/* firmware and opcodes */
+#define FW_NAME					"cadence/mhdp8546.bin"
+#define CDNS_MHDP_IMEM				0x10000
+
+#define GENERAL_MAIN_CONTROL			0x01
+#define GENERAL_TEST_ECHO			0x02
+#define GENERAL_BUS_SETTINGS			0x03
+#define GENERAL_TEST_ACCESS			0x04
+#define GENERAL_REGISTER_READ			0x07
+
+#define DPTX_SET_POWER_MNG			0x00
+#define DPTX_GET_EDID				0x02
+#define DPTX_READ_DPCD				0x03
+#define DPTX_WRITE_DPCD				0x04
+#define DPTX_ENABLE_EVENT			0x05
+#define DPTX_WRITE_REGISTER			0x06
+#define DPTX_READ_REGISTER			0x07
+#define DPTX_WRITE_FIELD			0x08
+#define DPTX_READ_EVENT				0x0a
+#define DPTX_GET_LAST_AUX_STAUS			0x0e
+#define DPTX_HPD_STATE				0x11
+#define DPTX_ADJUST_LT				0x12
+
+#define FW_STANDBY				0
+#define FW_ACTIVE				1
+
+/* HPD */
+#define DPTX_READ_EVENT_HPD_TO_HIGH             BIT(0)
+#define DPTX_READ_EVENT_HPD_TO_LOW              BIT(1)
+#define DPTX_READ_EVENT_HPD_PULSE               BIT(2)
+#define DPTX_READ_EVENT_HPD_STATE               BIT(3)
+
+/* general */
+#define CDNS_DP_TRAINING_PATTERN_4		0x7
+
+#define CDNS_KEEP_ALIVE_TIMEOUT			2000
+
+#define CDNS_VOLT_SWING(x)			((x) & GENMASK(1, 0))
+#define CDNS_FORCE_VOLT_SWING			BIT(2)
+
+#define CDNS_PRE_EMPHASIS(x)			((x) & GENMASK(1, 0))
+#define CDNS_FORCE_PRE_EMPHASIS			BIT(2)
+
+#define CDNS_SUPPORT_TPS(x)			BIT((x) - 1)
+
+#define CDNS_FAST_LINK_TRAINING			BIT(0)
+
+#define CDNS_LANE_MAPPING_TYPE_C_LANE_0(x)	((x) & GENMASK(1, 0))
+#define CDNS_LANE_MAPPING_TYPE_C_LANE_1(x)	((x) & GENMASK(3, 2))
+#define CDNS_LANE_MAPPING_TYPE_C_LANE_2(x)	((x) & GENMASK(5, 4))
+#define CDNS_LANE_MAPPING_TYPE_C_LANE_3(x)	((x) & GENMASK(7, 6))
+#define CDNS_LANE_MAPPING_NORMAL		0xe4
+#define CDNS_LANE_MAPPING_FLIPPED		0x1b
+
+#define CDNS_DP_MAX_NUM_LANES			4
+#define CDNS_DP_TEST_VSC_SDP			BIT(6) /* 1.3+ */
+#define CDNS_DP_TEST_COLOR_FORMAT_RAW_Y_ONLY	BIT(7)
+
+#define CDNS_MHDP_MAX_STREAMS   4
+
+#define DP_LINK_CAP_ENHANCED_FRAMING BIT(0)
+
+struct cdns_mhdp_link {
+	unsigned char revision;
+	unsigned int rate;
+	unsigned int num_lanes;
+	unsigned long capabilities;
+};
+
+struct cdns_mhdp_host {
+	unsigned int link_rate;
+	u8 lanes_cnt;
+	u8 volt_swing;
+	u8 pre_emphasis;
+	u8 pattern_supp;
+	u8 lane_mapping;
+	bool fast_link;
+	bool enhanced;
+	bool scrambler;
+	bool ssc;
+};
+
+struct cdns_mhdp_sink {
+	unsigned int link_rate;
+	u8 lanes_cnt;
+	u8 pattern_supp;
+	bool fast_link;
+	bool enhanced;
+	bool ssc;
+};
+
+struct cdns_mhdp_display_fmt {
+	u32 color_format;
+	u32 bpc;
+	bool y_only;
+};
+
+/*
+ * These enums present MHDP hw initialization state
+ * Legal state transitions are:
+ * MHDP_HW_INACTIVE <-> MHDP_HW_LOADING -> MHDP_HW_READY
+ *        |                                     |
+ *        '----------> MHDP_HW_STOPPED <--------'
+ */
+enum mhdp_hw_state {
+	MHDP_HW_INACTIVE = 0,	/* HW not initialized */
+	MHDP_HW_LOADING,	/* HW initialization in progress */
+	MHDP_HW_READY,		/* HW ready, FW active*/
+	MHDP_HW_STOPPED		/* Driver removal FW to be stopped */
+};
+
+struct cdns_mhdp_device;
+
+struct mhdp_platform_ops {
+	int (*init)(struct cdns_mhdp_device *mhdp);
+	void (*exit)(struct cdns_mhdp_device *mhdp);
+	void (*enable)(struct cdns_mhdp_device *mhdp);
+	void (*disable)(struct cdns_mhdp_device *mhdp);
+};
+
+struct cdns_mhdp_bridge_state {
+	struct drm_bridge_state base;
+	u32 tu_size;
+	u32 vs;
+	u32 line_thresh;
+	struct drm_display_mode *current_mode;
+};
+
+#define to_cdns_mhdp_bridge_state(s) \
+		container_of(s, struct cdns_mhdp_bridge_state, base)
+
+struct cdns_mhdp_device {
+	void __iomem *regs;
+
+	struct device *dev;
+	struct clk *clk;
+	struct phy *phy;
+
+	const struct mhdp_platform_ops *ops;
+
+	/* This is to protect mailbox communications with the firmware */
+	struct mutex mbox_mutex;
+
+	/* "link_mutex" protects the access to all the link parameters
+	 * including the link training process. Link training will be
+	 * invoked both from threaded interrupt handler and from atomic
+	 * callbacks when link_up is not set. So this mutex protects
+	 * flags such as link_up, bridge_enabled, link.num_lanes,
+	 * link.rate etc.
+	 */
+	struct mutex link_mutex;
+
+	struct drm_connector connector;
+	struct drm_bridge bridge;
+
+	struct cdns_mhdp_link link;
+	struct drm_dp_aux aux;
+
+	struct cdns_mhdp_host host;
+	struct cdns_mhdp_sink sink;
+	struct cdns_mhdp_display_fmt display_fmt;
+	s8 stream_id;
+
+	bool link_up;
+	bool plugged;
+
+	/*
+	 * "start_lock" protects the access to bridge_attached and
+	 * hw_state data members that control the delayed firmware
+	 * loading and attaching the bridge. They are accessed from
+	 * both the DRM core and cdns_mhdp_fw_cb(). In most cases just
+	 * protecting the data members is enough, but the irq mask
+	 * setting needs to be protected when enabling the FW.
+	 */
+	spinlock_t start_lock;
+	bool bridge_attached;
+	bool bridge_enabled;
+	enum mhdp_hw_state hw_state;
+};
+
+#define connector_to_mhdp(x) container_of(x, struct cdns_mhdp_device, connector)
+#define bridge_to_mhdp(x) container_of(x, struct cdns_mhdp_device, bridge)
+
+#endif
-- 
2.26.1


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

* [PATCH v7 2/3] drm: bridge: Add support for Cadence MHDP DPI/DP bridge
@ 2020-07-22  7:40   ` Swapnil Jakhade
  0 siblings, 0 replies; 26+ messages in thread
From: Swapnil Jakhade @ 2020-07-22  7:40 UTC (permalink / raw)
  To: airlied, daniel, Laurent.pinchart, robh+dt, a.hajda, narmstrong,
	jonas, jernej.skrabec, dri-devel, devicetree, linux-kernel
  Cc: mparab, yamonkar, praneeth, nsekhar, jsarha, tomi.valkeinen, sjakhade

Add a new DRM bridge driver for Cadence MHDP DPTX IP used in TI J721e SoC.
MHDP DPTX IP is the component that complies with VESA DisplayPort (DP) and
embedded Display Port (eDP) standards. It integrates uCPU running the
embedded Firmware (FW) interfaced over APB interface.

Basically, it takes a DPI stream as input and outputs it encoded in DP
format. Currently, it supports only SST mode.

Co-developed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Co-developed-by: Jyri Sarha <jsarha@ti.com>
Signed-off-by: Jyri Sarha <jsarha@ti.com>
Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
---
 drivers/gpu/drm/bridge/Kconfig          |   11 +
 drivers/gpu/drm/bridge/Makefile         |    2 +
 drivers/gpu/drm/bridge/cdns-mhdp-core.c | 2493 +++++++++++++++++++++++
 drivers/gpu/drm/bridge/cdns-mhdp-core.h |  396 ++++
 4 files changed, 2902 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.c
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.h

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 43271c21d3fc..6a4c324302a8 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -27,6 +27,17 @@ config DRM_CDNS_DSI
 	  Support Cadence DPI to DSI bridge. This is an internal
 	  bridge and is meant to be directly embedded in a SoC.
 
+config DRM_CDNS_MHDP
+	tristate "Cadence DPI/DP bridge"
+	select DRM_KMS_HELPER
+	select DRM_PANEL_BRIDGE
+	depends on OF
+	help
+	  Support Cadence DPI to DP bridge. This is an internal
+	  bridge and is meant to be directly embedded in a SoC.
+	  It takes a DPI stream as input and outputs it encoded
+	  in DP format.
+
 config DRM_CHRONTEL_CH7033
 	tristate "Chrontel CH7033 Video Encoder"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index d63d4b7e4347..0080a9f80f29 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -1,5 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
+obj-$(CONFIG_DRM_CDNS_MHDP) += cdns-mhdp.o
+cdns-mhdp-objs := cdns-mhdp-core.o
 obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
 obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
 obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.c b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
new file mode 100644
index 000000000000..b16c5503cef1
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
@@ -0,0 +1,2493 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cadence MHDP DP bridge driver.
+ *
+ * Copyright: 2019 Cadence Design Systems, Inc.
+ *
+ * Author: Quentin Schulz <quentin.schulz@free-electrons.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/firmware.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-dp.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#include <asm/unaligned.h>
+
+#include "cdns-mhdp-core.h"
+
+static DECLARE_WAIT_QUEUE_HEAD(fw_load_wq);
+
+static int cdns_mhdp_mailbox_read(struct cdns_mhdp_device *mhdp)
+{
+	int ret, empty;
+
+	WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex));
+
+	ret = readx_poll_timeout(readl, mhdp->regs + CDNS_MAILBOX_EMPTY,
+				 empty, !empty, MAILBOX_RETRY_US,
+				 MAILBOX_TIMEOUT_US);
+	if (ret < 0)
+		return ret;
+
+	return readl(mhdp->regs + CDNS_MAILBOX_RX_DATA) & 0xff;
+}
+
+static int cdns_mhdp_mailbox_write(struct cdns_mhdp_device *mhdp, u8 val)
+{
+	int ret, full;
+
+	WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex));
+
+	ret = readx_poll_timeout(readl, mhdp->regs + CDNS_MAILBOX_FULL,
+				 full, !full, MAILBOX_RETRY_US,
+				 MAILBOX_TIMEOUT_US);
+	if (ret < 0)
+		return ret;
+
+	writel(val, mhdp->regs + CDNS_MAILBOX_TX_DATA);
+
+	return 0;
+}
+
+static int cdns_mhdp_mailbox_validate_receive(struct cdns_mhdp_device *mhdp,
+					      u8 module_id, u8 opcode,
+					      u16 req_size)
+{
+	u32 mbox_size, i;
+	u8 header[4];
+	int ret;
+
+	/* read the header of the message */
+	for (i = 0; i < 4; i++) {
+		ret = cdns_mhdp_mailbox_read(mhdp);
+		if (ret < 0)
+			return ret;
+
+		header[i] = ret;
+	}
+
+	mbox_size = get_unaligned_be16(header + 2);
+
+	if (opcode != header[0] || module_id != header[1] ||
+	    req_size != mbox_size) {
+		/*
+		 * If the message in mailbox is not what we want, we need to
+		 * clear the mailbox by reading its contents.
+		 */
+		for (i = 0; i < mbox_size; i++)
+			if (cdns_mhdp_mailbox_read(mhdp) < 0)
+				break;
+
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int cdns_mhdp_mailbox_read_receive(struct cdns_mhdp_device *mhdp,
+					  u8 *buff, u16 buff_size)
+{
+	u32 i;
+	int ret;
+
+	for (i = 0; i < buff_size; i++) {
+		ret = cdns_mhdp_mailbox_read(mhdp);
+		if (ret < 0)
+			return ret;
+
+		buff[i] = ret;
+	}
+
+	return 0;
+}
+
+static int cdns_mhdp_mailbox_send(struct cdns_mhdp_device *mhdp, u8 module_id,
+				  u8 opcode, u16 size, u8 *message)
+{
+	u8 header[4];
+	int ret, i;
+
+	header[0] = opcode;
+	header[1] = module_id;
+	put_unaligned_be16(size, header + 2);
+
+	for (i = 0; i < 4; i++) {
+		ret = cdns_mhdp_mailbox_write(mhdp, header[i]);
+		if (ret)
+			return ret;
+	}
+
+	for (i = 0; i < size; i++) {
+		ret = cdns_mhdp_mailbox_write(mhdp, message[i]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static
+int cdns_mhdp_reg_read(struct cdns_mhdp_device *mhdp, u32 addr, u32 *value)
+{
+	u8 msg[4], resp[8];
+	int ret;
+
+	put_unaligned_be32(addr, msg);
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_GENERAL,
+				     GENERAL_REGISTER_READ,
+				     sizeof(msg), msg);
+	if (ret)
+		goto err_reg_read;
+
+	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_GENERAL,
+						 GENERAL_REGISTER_READ,
+						 sizeof(resp));
+	if (ret)
+		goto err_reg_read;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, resp, sizeof(resp));
+	if (ret)
+		goto err_reg_read;
+
+	/* Returned address value should be the same as requested */
+	if (memcmp(msg, resp, sizeof(msg))) {
+		ret = -EINVAL;
+		goto err_reg_read;
+	}
+
+	*value = get_unaligned_be32(resp + 4);
+
+err_reg_read:
+	mutex_unlock(&mhdp->mbox_mutex);
+	if (ret) {
+		DRM_DEV_ERROR(mhdp->dev, "Failed to read register.\n");
+		*value = 0;
+	}
+
+	return ret;
+}
+
+static
+int cdns_mhdp_reg_write(struct cdns_mhdp_device *mhdp, u16 addr, u32 val)
+{
+	u8 msg[6];
+	int ret;
+
+	put_unaligned_be16(addr, msg);
+	put_unaligned_be32(val, msg + 2);
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+				     DPTX_WRITE_REGISTER, sizeof(msg), msg);
+
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	return ret;
+}
+
+static
+int cdns_mhdp_reg_write_bit(struct cdns_mhdp_device *mhdp, u16 addr,
+			    u8 start_bit, u8 bits_no, u32 val)
+{
+	u8 field[8];
+	int ret;
+
+	put_unaligned_be16(addr, field);
+	field[2] = start_bit;
+	field[3] = bits_no;
+	put_unaligned_be32(val, field + 4);
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+				     DPTX_WRITE_FIELD, sizeof(field), field);
+
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	return ret;
+}
+
+static
+int cdns_mhdp_dpcd_read(struct cdns_mhdp_device *mhdp,
+			u32 addr, u8 *data, u16 len)
+{
+	u8 msg[5], reg[5];
+	int ret;
+
+	put_unaligned_be16(len, msg);
+	put_unaligned_be24(addr, msg + 2);
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+				     DPTX_READ_DPCD, sizeof(msg), msg);
+	if (ret)
+		goto err_dpcd_read;
+
+	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
+						 DPTX_READ_DPCD,
+						 sizeof(reg) + len);
+	if (ret)
+		goto err_dpcd_read;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
+	if (ret)
+		goto err_dpcd_read;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, data, len);
+
+err_dpcd_read:
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	return ret;
+}
+
+static
+int cdns_mhdp_dpcd_write(struct cdns_mhdp_device *mhdp, u32 addr, u8 value)
+{
+	u8 msg[6], reg[5];
+	int ret;
+
+	put_unaligned_be16(1, msg);
+	put_unaligned_be24(addr, msg + 2);
+	msg[5] = value;
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+				     DPTX_WRITE_DPCD, sizeof(msg), msg);
+	if (ret)
+		goto err_dpcd_write;
+
+	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
+						 DPTX_WRITE_DPCD, sizeof(reg));
+	if (ret)
+		goto err_dpcd_write;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
+	if (ret)
+		goto err_dpcd_write;
+
+	if (addr != get_unaligned_be24(reg + 2))
+		ret = -EINVAL;
+
+err_dpcd_write:
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	if (ret)
+		DRM_DEV_ERROR(mhdp->dev, "dpcd write failed: %d\n", ret);
+	return ret;
+}
+
+static
+int cdns_mhdp_set_firmware_active(struct cdns_mhdp_device *mhdp, bool enable)
+{
+	u8 msg[5];
+	int ret, i;
+
+	msg[0] = GENERAL_MAIN_CONTROL;
+	msg[1] = MB_MODULE_ID_GENERAL;
+	msg[2] = 0;
+	msg[3] = 1;
+	msg[4] = enable ? FW_ACTIVE : FW_STANDBY;
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	for (i = 0; i < sizeof(msg); i++) {
+		ret = cdns_mhdp_mailbox_write(mhdp, msg[i]);
+		if (ret)
+			goto err_set_firmware_active;
+	}
+
+	/* read the firmware state */
+	for (i = 0; i < sizeof(msg); i++)  {
+		ret = cdns_mhdp_mailbox_read(mhdp);
+		if (ret < 0)
+			goto err_set_firmware_active;
+
+		msg[i] = ret;
+	}
+
+	ret = 0;
+
+err_set_firmware_active:
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	if (ret < 0)
+		DRM_DEV_ERROR(mhdp->dev, "set firmware active failed\n");
+	return ret;
+}
+
+static
+int cdns_mhdp_get_hpd_status(struct cdns_mhdp_device *mhdp)
+{
+	u8 status;
+	int ret;
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+				     DPTX_HPD_STATE, 0, NULL);
+	if (ret)
+		goto err_get_hpd;
+
+	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
+						 DPTX_HPD_STATE,
+						 sizeof(status));
+	if (ret)
+		goto err_get_hpd;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, &status, sizeof(status));
+	if (ret)
+		goto err_get_hpd;
+
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	dev_dbg(mhdp->dev, "%s: %d\n", __func__, status);
+
+	return status;
+
+err_get_hpd:
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	DRM_DEV_ERROR(mhdp->dev, "get hpd status failed: %d\n", ret);
+	return ret;
+}
+
+static
+int cdns_mhdp_get_edid_block(void *data, u8 *edid,
+			     unsigned int block, size_t length)
+{
+	struct cdns_mhdp_device *mhdp = data;
+	u8 msg[2], reg[2], i;
+	int ret;
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	for (i = 0; i < 4; i++) {
+		msg[0] = block / 2;
+		msg[1] = block % 2;
+
+		ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+					     DPTX_GET_EDID, sizeof(msg), msg);
+		if (ret)
+			continue;
+
+		ret = cdns_mhdp_mailbox_validate_receive(mhdp,
+							 MB_MODULE_ID_DP_TX,
+							 DPTX_GET_EDID,
+							 sizeof(reg) + length);
+		if (ret)
+			continue;
+
+		ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
+		if (ret)
+			continue;
+
+		ret = cdns_mhdp_mailbox_read_receive(mhdp, edid, length);
+		if (ret)
+			continue;
+
+		if (reg[0] == length && reg[1] == block / 2)
+			break;
+	}
+
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	if (ret)
+		DRM_DEV_ERROR(mhdp->dev, "get block[%d] edid failed: %d\n",
+			      block, ret);
+
+	return ret;
+}
+
+static
+int cdns_mhdp_read_event(struct cdns_mhdp_device *mhdp)
+{
+	u8 event = 0;
+	int ret;
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+				     DPTX_READ_EVENT, 0, NULL);
+	if (ret)
+		goto out;
+
+	ret = cdns_mhdp_mailbox_validate_receive(mhdp,
+						 MB_MODULE_ID_DP_TX,
+						 DPTX_READ_EVENT,
+						 sizeof(event));
+	if (ret < 0)
+		goto out;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, &event,
+					     sizeof(event));
+out:
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(mhdp->dev, "%s: %s%s%s%s\n", __func__,
+		(event & DPTX_READ_EVENT_HPD_TO_HIGH) ? "TO_HIGH " : "",
+		(event & DPTX_READ_EVENT_HPD_TO_LOW) ? "TO_LOW " : "",
+		(event & DPTX_READ_EVENT_HPD_PULSE) ? "PULSE " : "",
+		(event & DPTX_READ_EVENT_HPD_STATE) ? "HPD_STATE " : "");
+
+	return event;
+}
+
+static
+int cdns_mhdp_adjust_lt(struct cdns_mhdp_device *mhdp,
+			u8 nlanes, u16 udelay, u8 *lanes_data, u8 *link_status)
+{
+	u8 payload[7];
+	u8 hdr[5]; /* For DPCD read response header */
+	u32 addr;
+	u8 const nregs = 6; /* Registers 0x202-0x207 */
+	int ret;
+
+	if (nlanes != 4 && nlanes != 2 && nlanes != 1) {
+		DRM_DEV_ERROR(mhdp->dev, "invalid number of lanes: %d\n",
+			      nlanes);
+		ret = -EINVAL;
+		goto err_adjust_lt;
+	}
+
+	payload[0] = nlanes;
+	put_unaligned_be16(udelay, payload + 1);
+	memcpy(payload + 3, lanes_data, nlanes);
+
+	mutex_lock(&mhdp->mbox_mutex);
+
+	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+				     DPTX_ADJUST_LT,
+				     sizeof(payload), payload);
+	if (ret)
+		goto err_adjust_lt;
+
+	/* Yes, read the DPCD read command response */
+	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
+						 DPTX_READ_DPCD,
+						 sizeof(hdr) + nregs);
+	if (ret)
+		goto err_adjust_lt;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, hdr, sizeof(hdr));
+	if (ret)
+		goto err_adjust_lt;
+
+	addr = get_unaligned_be24(hdr + 2);
+	if (addr != DP_LANE0_1_STATUS)
+		goto err_adjust_lt;
+
+	ret = cdns_mhdp_mailbox_read_receive(mhdp, link_status, nregs);
+
+err_adjust_lt:
+	mutex_unlock(&mhdp->mbox_mutex);
+
+	if (ret)
+		DRM_DEV_ERROR(mhdp->dev, "Failed to adjust Link Training.\n");
+
+	return ret;
+}
+
+/**
+ * cdns_mhdp_link_power_up() - power up a DisplayPort link
+ * @aux: DisplayPort AUX channel
+ * @link: pointer to a structure containing the link configuration
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+static
+int cdns_mhdp_link_power_up(struct drm_dp_aux *aux, struct cdns_mhdp_link *link)
+{
+	u8 value;
+	int err;
+
+	/* DP_SET_POWER register is only available on DPCD v1.1 and later */
+	if (link->revision < 0x11)
+		return 0;
+
+	err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value);
+	if (err < 0)
+		return err;
+
+	value &= ~DP_SET_POWER_MASK;
+	value |= DP_SET_POWER_D0;
+
+	err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
+	if (err < 0)
+		return err;
+
+	/*
+	 * According to the DP 1.1 specification, a "Sink Device must exit the
+	 * power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink
+	 * Control Field" (register 0x600).
+	 */
+	usleep_range(1000, 2000);
+
+	return 0;
+}
+
+/**
+ * cdns_mhdp_link_power_down() - power down a DisplayPort link
+ * @aux: DisplayPort AUX channel
+ * @link: pointer to a structure containing the link configuration
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+static
+int cdns_mhdp_link_power_down(struct drm_dp_aux *aux,
+			      struct cdns_mhdp_link *link)
+{
+	u8 value;
+	int err;
+
+	/* DP_SET_POWER register is only available on DPCD v1.1 and later */
+	if (link->revision < 0x11)
+		return 0;
+
+	err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value);
+	if (err < 0)
+		return err;
+
+	value &= ~DP_SET_POWER_MASK;
+	value |= DP_SET_POWER_D3;
+
+	err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/**
+ * cdns_mhdp_link_configure() - configure a DisplayPort link
+ * @aux: DisplayPort AUX channel
+ * @link: pointer to a structure containing the link configuration
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+static
+int cdns_mhdp_link_configure(struct drm_dp_aux *aux,
+			     struct cdns_mhdp_link *link)
+{
+	u8 values[2];
+	int err;
+
+	values[0] = drm_dp_link_rate_to_bw_code(link->rate);
+	values[1] = link->num_lanes;
+
+	if (link->capabilities & DP_LINK_CAP_ENHANCED_FRAMING)
+		values[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
+
+	err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, values, sizeof(values));
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static unsigned int cdns_mhdp_max_link_rate(struct cdns_mhdp_device *mhdp)
+{
+	return min(mhdp->host.link_rate, mhdp->sink.link_rate);
+}
+
+static u8 cdns_mhdp_max_num_lanes(struct cdns_mhdp_device *mhdp)
+{
+	return min(mhdp->sink.lanes_cnt, mhdp->host.lanes_cnt);
+}
+
+static u8 cdns_mhdp_eq_training_pattern_supported(struct cdns_mhdp_device *mhdp)
+{
+	return fls(mhdp->host.pattern_supp & mhdp->sink.pattern_supp);
+}
+
+static bool cdns_mhdp_get_ssc_supported(struct cdns_mhdp_device *mhdp)
+{
+	/* Check if SSC is supported by both sides */
+	return (mhdp->host.ssc) && (mhdp->sink.ssc);
+}
+
+static int cdns_mhdp_check_fw_version(struct cdns_mhdp_device *mhdp)
+{
+	u32 ver_l, ver_h, fw_ver;
+	u32 lib_l_addr, lib_h_addr, lib_ver;
+	u32 major_num, minor_num, revision;
+
+	ver_l = readl(mhdp->regs + CDNS_VER_L);
+	ver_h = readl(mhdp->regs + CDNS_VER_H);
+
+	fw_ver = (ver_h << 8) | ver_l;
+
+	lib_l_addr = readl(mhdp->regs + CDNS_LIB_L_ADDR);
+	lib_h_addr = readl(mhdp->regs + CDNS_LIB_H_ADDR);
+
+	lib_ver = (lib_h_addr << 8) | lib_l_addr;
+
+	if (lib_ver < 33984) {
+		/**
+		 * Older FW versions with major number 1, used to store FW
+		 * version information by storing repository revision number
+		 * in registers. This is for identifying these FW versions.
+		 */
+		major_num = 1;
+		minor_num = 2;
+		if (fw_ver == 26098)
+			revision = 15;
+		else if (lib_ver == 0 && fw_ver == 0)
+			revision = 17;
+		else
+			goto fw_error;
+	} else {
+		/* To identify newer FW versions with major number 2 onwards. */
+		major_num = fw_ver / 10000;
+		minor_num = (fw_ver / 100) % 100;
+		revision = (fw_ver % 10000) % 100;
+	}
+
+	dev_dbg(mhdp->dev, "FW version: v%u.%u.%u\n", major_num, minor_num,
+		revision);
+	return 0;
+
+fw_error:
+	dev_err(mhdp->dev, "Unsupported FW version\n");
+	return -ENODEV;
+}
+
+static int cdns_mhdp_fw_activate(const struct firmware *fw,
+				 struct cdns_mhdp_device *mhdp)
+{
+	unsigned int reg;
+	int ret = 0;
+
+	dev_dbg(mhdp->dev, "%s\n", __func__);
+
+	if (!fw || !fw->data) {
+		dev_err(mhdp->dev, "%s: No firmware.\n", __func__);
+		return -EINVAL;
+	}
+
+	spin_lock(&mhdp->start_lock);
+	if (mhdp->hw_state != MHDP_HW_INACTIVE) {
+		spin_unlock(&mhdp->start_lock);
+		if (mhdp->hw_state != MHDP_HW_STOPPED)
+			dev_err(mhdp->dev, "%s: Bad HW state: %d\n",
+				__func__, mhdp->hw_state);
+		return -EBUSY;
+	}
+	mhdp->hw_state = MHDP_HW_LOADING;
+	spin_unlock(&mhdp->start_lock);
+
+	/* Release uCPU reset and stall it. */
+	writel(CDNS_CPU_STALL, mhdp->regs + CDNS_APB_CTRL);
+
+	memcpy_toio(mhdp->regs + CDNS_MHDP_IMEM, fw->data, fw->size);
+
+	/* Leave debug mode, release stall */
+	writel(0, mhdp->regs + CDNS_APB_CTRL);
+
+	/*
+	 * Wait for the KEEP_ALIVE "message" on the first 8 bits.
+	 * Updated each sched "tick" (~2ms)
+	 */
+	ret = readl_poll_timeout(mhdp->regs + CDNS_KEEP_ALIVE, reg,
+				 reg & CDNS_KEEP_ALIVE_MASK, 500,
+				 CDNS_KEEP_ALIVE_TIMEOUT);
+	if (ret) {
+		dev_err(mhdp->dev,
+			"device didn't give any life sign: reg %d\n", reg);
+		goto error;
+	}
+
+	ret = cdns_mhdp_check_fw_version(mhdp);
+	if (ret)
+		goto error;
+
+	/* Init events to 0 as it's not cleared by FW at boot but on read */
+	readl(mhdp->regs + CDNS_SW_EVENT0);
+	readl(mhdp->regs + CDNS_SW_EVENT1);
+	readl(mhdp->regs + CDNS_SW_EVENT2);
+	readl(mhdp->regs + CDNS_SW_EVENT3);
+
+	/* Activate uCPU */
+	ret = cdns_mhdp_set_firmware_active(mhdp, true);
+	if (ret) {
+		dev_err(mhdp->dev, "%s: Failed to activate FW: %d\n",
+			__func__, ret);
+		goto error;
+	}
+
+	spin_lock(&mhdp->start_lock);
+
+	mhdp->hw_state = MHDP_HW_READY;
+	wake_up(&fw_load_wq);
+	/*
+	 * Here we must keep the lock while enabling the interrupts
+	 * since it would otherwise be possible that interrupt enable
+	 * code is executed after the bridge is detached. The similar
+	 * situation is not possible in attach()/detach() callbacks
+	 * since the hw_state changes from MHDP_HW_READY to
+	 * MHDP_HW_STOPPED happens only due to driver removal when
+	 * bridge should already be detached.
+	 */
+	if (mhdp->bridge_attached)
+		/* enable SW event interrupts */
+		writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
+		       mhdp->regs + CDNS_APB_INT_MASK);
+
+	spin_unlock(&mhdp->start_lock);
+
+	dev_dbg(mhdp->dev, "DP FW activated\n");
+
+	return 0;
+error:
+	spin_lock(&mhdp->start_lock);
+	mhdp->hw_state = MHDP_HW_INACTIVE;
+	spin_unlock(&mhdp->start_lock);
+
+	return ret;
+}
+
+static void cdns_mhdp_fw_cb(const struct firmware *fw, void *context)
+{
+	struct cdns_mhdp_device *mhdp = context;
+	bool bridge_attached;
+	int ret;
+
+	dev_dbg(mhdp->dev, "firmware callback\n");
+
+	ret = cdns_mhdp_fw_activate(fw, mhdp);
+
+	release_firmware(fw);
+
+	if (ret)
+		return;
+
+	/*
+	 *  XXX how to make sure the bridge is still attached when
+	 *      calling drm_kms_helper_hotplug_event() after releasing
+	 *      the lock? We should not hold the spin lock when
+	 *      calling drm_kms_helper_hotplug_event() since it may
+	 *      cause a dead lock. FB-dev console calls detect from the
+	 *      same thread just down the call stack started here.
+	 */
+	spin_lock(&mhdp->start_lock);
+	bridge_attached = mhdp->bridge_attached;
+	spin_unlock(&mhdp->start_lock);
+	if (bridge_attached)
+		drm_kms_helper_hotplug_event(mhdp->bridge.dev);
+}
+
+static int cdns_mhdp_load_firmware(struct cdns_mhdp_device *mhdp)
+{
+	int ret;
+
+	ret = request_firmware_nowait(THIS_MODULE, true, FW_NAME, mhdp->dev,
+				      GFP_KERNEL, mhdp, cdns_mhdp_fw_cb);
+	if (ret) {
+		dev_err(mhdp->dev, "failed to load firmware (%s), ret: %d\n",
+			FW_NAME, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static ssize_t cdns_mhdp_transfer(struct drm_dp_aux *aux,
+				  struct drm_dp_aux_msg *msg)
+{
+	struct cdns_mhdp_device *mhdp = dev_get_drvdata(aux->dev);
+	int ret;
+
+	if (msg->request != DP_AUX_NATIVE_WRITE &&
+	    msg->request != DP_AUX_NATIVE_READ)
+		return -EOPNOTSUPP;
+
+	if (msg->request == DP_AUX_NATIVE_WRITE) {
+		const u8 *buf = msg->buffer;
+		int i;
+
+		for (i = 0; i < msg->size; ++i) {
+			ret = cdns_mhdp_dpcd_write(mhdp,
+						   msg->address + i, buf[i]);
+			if (!ret)
+				continue;
+
+			DRM_DEV_ERROR(mhdp->dev,
+				      "Failed to write DPCD addr %u\n",
+				      msg->address + i);
+
+			return ret;
+		}
+	} else {
+		ret = cdns_mhdp_dpcd_read(mhdp, msg->address,
+					  msg->buffer, msg->size);
+		if (ret) {
+			DRM_DEV_ERROR(mhdp->dev,
+				      "Failed to read DPCD addr %u\n",
+				      msg->address);
+
+			return ret;
+		}
+	}
+
+	return msg->size;
+}
+
+static int cdns_mhdp_link_training_init(struct cdns_mhdp_device *mhdp)
+{
+	u32 reg32;
+	u8 i;
+	union phy_configure_opts phy_cfg;
+	int ret;
+
+	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
+			   DP_TRAINING_PATTERN_DISABLE);
+
+	/* Reset PHY configuration */
+	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1);
+	if (!mhdp->host.scrambler)
+		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_ENHNCD,
+			    mhdp->sink.enhanced & mhdp->host.enhanced);
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_LANE_EN,
+			    CDNS_DP_LANE_EN_LANES(mhdp->link.num_lanes));
+
+	cdns_mhdp_link_configure(&mhdp->aux, &mhdp->link);
+	phy_cfg.dp.link_rate = (mhdp->link.rate / 100);
+	phy_cfg.dp.lanes = (mhdp->link.num_lanes);
+	for (i = 0; i < 4; i++) {
+		phy_cfg.dp.voltage[i] = 0;
+		phy_cfg.dp.pre[i] = 0;
+	}
+	phy_cfg.dp.ssc = cdns_mhdp_get_ssc_supported(mhdp);
+	phy_cfg.dp.set_lanes = true;
+	phy_cfg.dp.set_rate = true;
+	phy_cfg.dp.set_voltages = true;
+	ret = phy_configure(mhdp->phy,  &phy_cfg);
+	if (ret) {
+		dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG,
+			    CDNS_PHY_COMMON_CONFIG |
+			    CDNS_PHY_TRAINING_EN |
+			    CDNS_PHY_TRAINING_TYPE(1) |
+			    CDNS_PHY_SCRAMBLER_BYPASS);
+
+	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
+			   DP_TRAINING_PATTERN_1 | DP_LINK_SCRAMBLING_DISABLE);
+
+	return 0;
+}
+
+static void cdns_mhdp_get_adjust_train(struct cdns_mhdp_device *mhdp,
+				       u8 link_status[DP_LINK_STATUS_SIZE],
+				       u8 lanes_data[CDNS_DP_MAX_NUM_LANES],
+				       union phy_configure_opts *phy_cfg)
+{
+	unsigned int i;
+	u8 adjust, max_pre_emph, max_volt_swing;
+	u8 set_volt, set_pre;
+
+	max_pre_emph = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis)
+			   << DP_TRAIN_PRE_EMPHASIS_SHIFT;
+	max_volt_swing = CDNS_VOLT_SWING(mhdp->host.volt_swing);
+
+	for (i = 0; i < mhdp->link.num_lanes; i++) {
+		/* Check if Voltage swing and pre-emphasis are within limits */
+		adjust = drm_dp_get_adjust_request_voltage(link_status, i);
+		set_volt = min(adjust, max_volt_swing);
+
+		adjust = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
+		set_pre = min(adjust, max_pre_emph)
+			  >> DP_TRAIN_PRE_EMPHASIS_SHIFT;
+
+		/* Voltage swing level and pre-emphasis level combination is
+		 * not allowed: leaving pre-emphasis as-is, and adjusting
+		 * voltage swing.
+		 */
+		if (set_volt + set_pre > 3)
+			set_volt = 3 - set_pre;
+
+		phy_cfg->dp.voltage[i] = set_volt;
+		lanes_data[i] = set_volt;
+
+		if (set_volt == max_volt_swing)
+			lanes_data[i] |= DP_TRAIN_MAX_SWING_REACHED;
+
+		phy_cfg->dp.pre[i] = set_pre;
+		lanes_data[i] |= (set_pre << DP_TRAIN_PRE_EMPHASIS_SHIFT);
+
+		if (set_pre == (max_pre_emph >> DP_TRAIN_PRE_EMPHASIS_SHIFT))
+			lanes_data[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
+	}
+}
+
+static
+void cdns_mhdp_set_adjust_request_voltage(u8 link_status[DP_LINK_STATUS_SIZE],
+					  int lane, u8 volt)
+{
+	int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
+	int s = ((lane & 1) ?
+		 DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT :
+		 DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT);
+	int idx = i - DP_LANE0_1_STATUS;
+
+	link_status[idx] &= ~(DP_ADJUST_VOLTAGE_SWING_LANE0_MASK << s);
+	link_status[idx] |= volt << s;
+}
+
+static
+void cdns_mhdp_set_adjust_request_pre_emphasis(u8 link_status[DP_LINK_STATUS_SIZE],
+					       int lane, u8 pre_emphasis)
+{
+	int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
+	int s = ((lane & 1) ?
+		 DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT :
+		 DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT);
+	int idx = i - DP_LANE0_1_STATUS;
+
+	link_status[idx] &= ~(DP_ADJUST_PRE_EMPHASIS_LANE0_MASK << s);
+	link_status[idx] |= pre_emphasis << s;
+}
+
+static void cdns_mhdp_adjust_requested_eq(struct cdns_mhdp_device *mhdp,
+					  u8 link_status[DP_LINK_STATUS_SIZE])
+{
+	u8 max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis);
+	u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing);
+	unsigned int i;
+	u8 volt, pre;
+
+	for (i = 0; i < mhdp->link.num_lanes; i++) {
+		volt = drm_dp_get_adjust_request_voltage(link_status, i);
+		pre = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
+		if (volt + pre > 3)
+			cdns_mhdp_set_adjust_request_voltage(link_status, i,
+							     3 - pre);
+		if (mhdp->host.volt_swing & CDNS_FORCE_VOLT_SWING)
+			cdns_mhdp_set_adjust_request_voltage(link_status, i,
+							     max_volt);
+		if (mhdp->host.pre_emphasis & CDNS_FORCE_PRE_EMPHASIS)
+			cdns_mhdp_set_adjust_request_pre_emphasis(link_status,
+								  i, max_pre);
+	}
+}
+
+static void cdns_mhdp_print_lt_status(const char *prefix,
+				      struct cdns_mhdp_device *mhdp,
+				      union phy_configure_opts *phy_cfg)
+{
+	int i;
+	char vs_str[8];
+	char pe_str[8];
+	char *vs_p, *pe_p;
+
+	vs_p = vs_str;
+	pe_p = pe_str;
+
+	for (i = 0; i < mhdp->link.num_lanes; i++) {
+		if (i != 0) {
+			*vs_p++ = '/';
+			*pe_p++ = '/';
+		}
+
+		*vs_p++ = '0' + phy_cfg->dp.voltage[i];
+		*pe_p++ = '0' + phy_cfg->dp.pre[i];
+	}
+
+	*vs_p = 0;
+	*pe_p = 0;
+
+	dev_dbg(mhdp->dev, "%s, %u lanes, %u Mbps, vs %s, pe %s\n",
+		prefix,
+		mhdp->link.num_lanes, mhdp->link.rate / 100,
+		vs_str, pe_str);
+}
+
+static bool cdns_mhdp_link_training_channel_eq(struct cdns_mhdp_device *mhdp,
+					       u8 eq_tps,
+					       unsigned int training_interval)
+{
+	u8 lanes_data[CDNS_DP_MAX_NUM_LANES], fail_counter_short = 0;
+	u8 link_status[DP_LINK_STATUS_SIZE];
+	u32 reg32;
+	union phy_configure_opts phy_cfg;
+	int ret;
+	bool r;
+
+	dev_dbg(mhdp->dev, "Starting EQ phase\n");
+
+	/* Enable link training TPS[eq_tps] in PHY */
+	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_EN |
+		CDNS_PHY_TRAINING_TYPE(eq_tps);
+	if (eq_tps != 4)
+		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
+	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
+
+	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
+			   (eq_tps != 4) ? eq_tps | DP_LINK_SCRAMBLING_DISABLE :
+			   CDNS_DP_TRAINING_PATTERN_4);
+
+	drm_dp_dpcd_read_link_status(&mhdp->aux, link_status);
+
+	do {
+		cdns_mhdp_get_adjust_train(mhdp, link_status, lanes_data,
+					   &phy_cfg);
+		phy_cfg.dp.lanes = (mhdp->link.num_lanes);
+		phy_cfg.dp.ssc = cdns_mhdp_get_ssc_supported(mhdp);
+		phy_cfg.dp.set_lanes = false;
+		phy_cfg.dp.set_rate = false;
+		phy_cfg.dp.set_voltages = true;
+		ret = phy_configure(mhdp->phy,  &phy_cfg);
+		if (ret) {
+			dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
+				__func__, ret);
+			goto err;
+		}
+
+		cdns_mhdp_adjust_lt(mhdp, mhdp->link.num_lanes,
+				    training_interval, lanes_data, link_status);
+
+		r = drm_dp_clock_recovery_ok(link_status, mhdp->link.num_lanes);
+		if (!r)
+			goto err;
+
+		if (drm_dp_channel_eq_ok(link_status, mhdp->link.num_lanes)) {
+			cdns_mhdp_print_lt_status("EQ phase ok", mhdp,
+						  &phy_cfg);
+			return true;
+		}
+
+		fail_counter_short++;
+
+		cdns_mhdp_adjust_requested_eq(mhdp, link_status);
+	} while (fail_counter_short < 5);
+
+err:
+	cdns_mhdp_print_lt_status("EQ phase failed", mhdp, &phy_cfg);
+
+	return false;
+}
+
+static void cdns_mhdp_adjust_requested_cr(struct cdns_mhdp_device *mhdp,
+					  u8 link_status[DP_LINK_STATUS_SIZE],
+					  u8 *req_volt, u8 *req_pre)
+{
+	const u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing);
+	const u8 max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis);
+	unsigned int i;
+
+	for (i = 0; i < mhdp->link.num_lanes; i++) {
+		u8 val;
+
+		val = mhdp->host.volt_swing & CDNS_FORCE_VOLT_SWING ?
+		      max_volt : req_volt[i];
+		cdns_mhdp_set_adjust_request_voltage(link_status, i, val);
+
+		val = mhdp->host.pre_emphasis & CDNS_FORCE_PRE_EMPHASIS ?
+		      max_pre : req_pre[i];
+		cdns_mhdp_set_adjust_request_pre_emphasis(link_status, i, val);
+	}
+}
+
+static
+void cdns_mhdp_validate_cr(struct cdns_mhdp_device *mhdp, bool *cr_done,
+			   bool *same_before_adjust, bool *max_swing_reached,
+			   u8 before_cr[DP_LINK_STATUS_SIZE],
+			   u8 after_cr[DP_LINK_STATUS_SIZE], u8 *req_volt,
+			   u8 *req_pre)
+{
+	const u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing);
+	const u8 max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis);
+	bool same_pre, same_volt;
+	unsigned int i;
+	u8 adjust;
+
+	*same_before_adjust = false;
+	*max_swing_reached = false;
+	*cr_done = drm_dp_clock_recovery_ok(after_cr, mhdp->link.num_lanes);
+
+	for (i = 0; i < mhdp->link.num_lanes; i++) {
+		adjust = drm_dp_get_adjust_request_voltage(after_cr, i);
+		req_volt[i] = min(adjust, max_volt);
+
+		adjust = drm_dp_get_adjust_request_pre_emphasis(after_cr, i) >>
+		      DP_TRAIN_PRE_EMPHASIS_SHIFT;
+		req_pre[i] = min(adjust, max_pre);
+
+		same_pre = (before_cr[i] & DP_TRAIN_PRE_EMPHASIS_MASK) ==
+			   req_pre[i] << DP_TRAIN_PRE_EMPHASIS_SHIFT;
+		same_volt = (before_cr[i] & DP_TRAIN_VOLTAGE_SWING_MASK) ==
+			    req_volt[i];
+		if (same_pre && same_volt)
+			*same_before_adjust = true;
+
+		/* 3.1.5.2 in DP Standard v1.4. Table 3-1 */
+		if (!*cr_done && req_volt[i] + req_pre[i] >= 3) {
+			*max_swing_reached = true;
+			return;
+		}
+	}
+}
+
+static bool cdns_mhdp_link_training_cr(struct cdns_mhdp_device *mhdp)
+{
+	u8 lanes_data[CDNS_DP_MAX_NUM_LANES],
+	fail_counter_short = 0, fail_counter_cr_long = 0;
+	u8 link_status[DP_LINK_STATUS_SIZE];
+	bool cr_done;
+	union phy_configure_opts phy_cfg;
+	int ret;
+
+	dev_dbg(mhdp->dev, "Starting CR phase\n");
+
+	ret = cdns_mhdp_link_training_init(mhdp);
+	if (ret)
+		goto err;
+
+	drm_dp_dpcd_read_link_status(&mhdp->aux, link_status);
+
+	do {
+		u8 requested_adjust_volt_swing[CDNS_DP_MAX_NUM_LANES] = {};
+		u8 requested_adjust_pre_emphasis[CDNS_DP_MAX_NUM_LANES] = {};
+		bool same_before_adjust, max_swing_reached;
+
+		cdns_mhdp_get_adjust_train(mhdp, link_status, lanes_data,
+					   &phy_cfg);
+		phy_cfg.dp.lanes = (mhdp->link.num_lanes);
+		phy_cfg.dp.ssc = cdns_mhdp_get_ssc_supported(mhdp);
+		phy_cfg.dp.set_lanes = false;
+		phy_cfg.dp.set_rate = false;
+		phy_cfg.dp.set_voltages = true;
+		ret = phy_configure(mhdp->phy,  &phy_cfg);
+		if (ret) {
+			dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
+				__func__, ret);
+			goto err;
+		}
+
+		cdns_mhdp_adjust_lt(mhdp, mhdp->link.num_lanes, 100,
+				    lanes_data, link_status);
+
+		cdns_mhdp_validate_cr(mhdp, &cr_done, &same_before_adjust,
+				      &max_swing_reached, lanes_data,
+				      link_status,
+				      requested_adjust_volt_swing,
+				      requested_adjust_pre_emphasis);
+
+		if (max_swing_reached) {
+			dev_err(mhdp->dev, "CR: max swing reached\n");
+			goto err;
+		}
+
+		if (cr_done) {
+			cdns_mhdp_print_lt_status("CR phase ok", mhdp,
+						  &phy_cfg);
+			return true;
+		}
+
+		/* Not all CR_DONE bits set */
+		fail_counter_cr_long++;
+
+		if (same_before_adjust) {
+			fail_counter_short++;
+			continue;
+		}
+
+		fail_counter_short = 0;
+		/*
+		 * Voltage swing/pre-emphasis adjust requested
+		 * during CR phase
+		 */
+		cdns_mhdp_adjust_requested_cr(mhdp, link_status,
+					      requested_adjust_volt_swing,
+					      requested_adjust_pre_emphasis);
+	} while (fail_counter_short < 5 && fail_counter_cr_long < 10);
+
+err:
+	cdns_mhdp_print_lt_status("CR phase failed", mhdp, &phy_cfg);
+
+	return false;
+}
+
+static void cdns_mhdp_lower_link_rate(struct cdns_mhdp_link *link)
+{
+	switch (drm_dp_link_rate_to_bw_code(link->rate)) {
+	case DP_LINK_BW_2_7:
+		link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_1_62);
+		break;
+	case DP_LINK_BW_5_4:
+		link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_2_7);
+		break;
+	case DP_LINK_BW_8_1:
+		link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_5_4);
+		break;
+	}
+}
+
+static int cdns_mhdp_link_training(struct cdns_mhdp_device *mhdp,
+				   unsigned int training_interval)
+{
+	u32 reg32;
+	const u8 eq_tps = cdns_mhdp_eq_training_pattern_supported(mhdp);
+	int ret;
+
+	while (1) {
+		if (!cdns_mhdp_link_training_cr(mhdp)) {
+			if (drm_dp_link_rate_to_bw_code(mhdp->link.rate) !=
+			    DP_LINK_BW_1_62) {
+				dev_dbg(mhdp->dev,
+					"Reducing link rate during CR phase\n");
+				cdns_mhdp_lower_link_rate(&mhdp->link);
+
+				continue;
+			} else if (mhdp->link.num_lanes > 1) {
+				dev_dbg(mhdp->dev,
+					"Reducing lanes number during CR phase\n");
+				mhdp->link.num_lanes >>= 1;
+				mhdp->link.rate = cdns_mhdp_max_link_rate(mhdp);
+
+				continue;
+			}
+
+			dev_err(mhdp->dev,
+				"Link training failed during CR phase\n");
+			goto err;
+		}
+
+		if (cdns_mhdp_link_training_channel_eq(mhdp, eq_tps,
+						       training_interval))
+			break;
+
+		if (mhdp->link.num_lanes > 1) {
+			dev_dbg(mhdp->dev,
+				"Reducing lanes number during EQ phase\n");
+			mhdp->link.num_lanes >>= 1;
+
+			continue;
+		} else if (drm_dp_link_rate_to_bw_code(mhdp->link.rate) !=
+			   DP_LINK_BW_1_62) {
+			dev_dbg(mhdp->dev,
+				"Reducing link rate during EQ phase\n");
+			cdns_mhdp_lower_link_rate(&mhdp->link);
+			mhdp->link.num_lanes = cdns_mhdp_max_num_lanes(mhdp);
+
+			continue;
+		}
+
+		dev_err(mhdp->dev, "Link training failed during EQ phase\n");
+		goto err;
+	}
+
+	dev_dbg(mhdp->dev, "Link training ok. Lanes: %u, Rate %u Mbps\n",
+		mhdp->link.num_lanes, mhdp->link.rate / 100);
+
+	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
+			   mhdp->host.scrambler ? 0 :
+			   DP_LINK_SCRAMBLING_DISABLE);
+
+	ret = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &reg32);
+	if (ret < 0) {
+		dev_err(mhdp->dev,
+			"Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n",
+			ret);
+		return ret;
+	}
+	reg32 &= ~GENMASK(1, 0);
+	reg32 |= CDNS_DP_NUM_LANES(mhdp->link.num_lanes);
+	reg32 |= CDNS_DP_WR_FAILING_EDGE_VSYNC;
+	reg32 |= CDNS_DP_FRAMER_EN;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, reg32);
+
+	/* Reset PHY config */
+	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1);
+	if (!mhdp->host.scrambler)
+		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
+	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
+
+	return 0;
+err:
+	/* Reset PHY config */
+	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1);
+	if (!mhdp->host.scrambler)
+		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
+	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
+
+	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
+			   DP_TRAINING_PATTERN_DISABLE);
+
+	return -EIO;
+}
+
+static u32 cdns_mhdp_get_training_interval_us(struct cdns_mhdp_device *mhdp,
+					      u32 interval)
+{
+	if (interval == 0)
+		return 400;
+	if (interval < 5)
+		return 4000 << (interval - 1);
+	dev_err(mhdp->dev,
+		"wrong training interval returned by DPCD: %d\n", interval);
+	return 0;
+}
+
+static void cdns_mhdp_fill_host_caps(struct cdns_mhdp_device *mhdp)
+{
+	unsigned int link_rate;
+	struct phy_attrs attrs;
+
+	/* Get source capabilities based on PHY attributes */
+	phy_get_attrs(mhdp->phy, &attrs);
+
+	mhdp->host.lanes_cnt = attrs.bus_width;
+	if (!mhdp->host.lanes_cnt)
+		mhdp->host.lanes_cnt = 4;
+
+	link_rate = attrs.max_link_rate;
+	if (!link_rate)
+		link_rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_8_1);
+	else
+		/* PHY uses Mb/s, DRM uses tens of kb/s. */
+		link_rate *= 100;
+
+	mhdp->host.link_rate = link_rate;
+	mhdp->host.volt_swing = CDNS_VOLT_SWING(3);
+	mhdp->host.pre_emphasis = CDNS_PRE_EMPHASIS(3);
+	mhdp->host.pattern_supp = CDNS_SUPPORT_TPS(1) |
+				  CDNS_SUPPORT_TPS(2) | CDNS_SUPPORT_TPS(3) |
+				  CDNS_SUPPORT_TPS(4);
+	mhdp->host.lane_mapping = CDNS_LANE_MAPPING_NORMAL;
+	mhdp->host.fast_link = false;
+	mhdp->host.enhanced = true;
+	mhdp->host.scrambler = true;
+	mhdp->host.ssc = false;
+}
+
+static void cdns_mhdp_fill_sink_caps(struct cdns_mhdp_device *mhdp,
+				     u8 dpcd[DP_RECEIVER_CAP_SIZE])
+{
+	mhdp->sink.link_rate = mhdp->link.rate;
+	mhdp->sink.lanes_cnt = mhdp->link.num_lanes;
+	mhdp->sink.enhanced = !!(mhdp->link.capabilities &
+				 DP_LINK_CAP_ENHANCED_FRAMING);
+
+	/* Set SSC support */
+	mhdp->sink.ssc = !!(dpcd[DP_MAX_DOWNSPREAD] &
+				  DP_MAX_DOWNSPREAD_0_5);
+
+	/* Set TPS support */
+	mhdp->sink.pattern_supp = CDNS_SUPPORT_TPS(1) | CDNS_SUPPORT_TPS(2);
+	if (drm_dp_tps3_supported(dpcd))
+		mhdp->sink.pattern_supp |= CDNS_SUPPORT_TPS(3);
+	if (drm_dp_tps4_supported(dpcd))
+		mhdp->sink.pattern_supp |= CDNS_SUPPORT_TPS(4);
+
+	/* Set fast link support */
+	mhdp->sink.fast_link = !!(dpcd[DP_MAX_DOWNSPREAD] &
+				  DP_NO_AUX_HANDSHAKE_LINK_TRAINING);
+}
+
+static int cdns_mhdp_link_up(struct cdns_mhdp_device *mhdp)
+{
+	u32 resp;
+	u8 dpcd[DP_RECEIVER_CAP_SIZE], amp[2];
+	u8 ext_cap_chk = 0;
+	unsigned int addr;
+	int err;
+
+	WARN_ON(!mutex_is_locked(&mhdp->link_mutex));
+
+	drm_dp_dpcd_readb(&mhdp->aux, DP_TRAINING_AUX_RD_INTERVAL,
+			  &ext_cap_chk);
+
+	if (ext_cap_chk & DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT)
+		addr = DP_DP13_DPCD_REV;
+	else
+		addr = DP_DPCD_REV;
+
+	err = drm_dp_dpcd_read(&mhdp->aux, addr, dpcd, DP_RECEIVER_CAP_SIZE);
+	if (err < 0) {
+		dev_err(mhdp->dev, "Failed to read receiver capabilities\n");
+		return err;
+	}
+
+	mhdp->link.revision = dpcd[0];
+	mhdp->link.rate = drm_dp_bw_code_to_link_rate(dpcd[1]);
+	mhdp->link.num_lanes = dpcd[2] & DP_MAX_LANE_COUNT_MASK;
+
+	if (dpcd[2] & DP_ENHANCED_FRAME_CAP)
+		mhdp->link.capabilities |= DP_LINK_CAP_ENHANCED_FRAMING;
+
+	dev_dbg(mhdp->dev, "Set sink device power state via DPCD\n");
+	cdns_mhdp_link_power_up(&mhdp->aux, &mhdp->link);
+
+	cdns_mhdp_fill_sink_caps(mhdp, dpcd);
+
+	mhdp->link.rate = cdns_mhdp_max_link_rate(mhdp);
+	mhdp->link.num_lanes = cdns_mhdp_max_num_lanes(mhdp);
+
+	/* Disable framer for link training */
+	err = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &resp);
+	if (err < 0) {
+		dev_err(mhdp->dev,
+			"Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n",
+			err);
+		return err;
+	}
+
+	resp &= ~CDNS_DP_FRAMER_EN;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, resp);
+
+	/* Spread AMP if required, enable 8b/10b coding */
+	amp[0] = cdns_mhdp_get_ssc_supported(mhdp) ? DP_SPREAD_AMP_0_5 : 0;
+	amp[1] = DP_SET_ANSI_8B10B;
+	drm_dp_dpcd_write(&mhdp->aux, DP_DOWNSPREAD_CTRL, amp, 2);
+
+	if (mhdp->host.fast_link & mhdp->sink.fast_link) {
+		dev_err(mhdp->dev, "fastlink not supported\n");
+		err = -EOPNOTSUPP;
+		goto error;
+	} else {
+		const u32 interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] &
+				     DP_TRAINING_AUX_RD_MASK;
+		const u32 interval_us = cdns_mhdp_get_training_interval_us(mhdp,
+									   interval);
+		if (!interval_us ||
+		    cdns_mhdp_link_training(mhdp, interval_us)) {
+			dev_err(mhdp->dev, "Link training failed. Exiting.\n");
+			err = -EIO;
+			goto error;
+		}
+	}
+
+	mhdp->link_up = true;
+
+	return 0;
+error:
+	return err;
+}
+
+static void cdns_mhdp_link_down(struct cdns_mhdp_device *mhdp)
+{
+	WARN_ON(!mutex_is_locked(&mhdp->link_mutex));
+
+	if (mhdp->plugged)
+		cdns_mhdp_link_power_down(&mhdp->aux, &mhdp->link);
+
+	mhdp->link_up = false;
+}
+
+static int cdns_mhdp_get_modes(struct drm_connector *connector)
+{
+	struct cdns_mhdp_device *mhdp = connector_to_mhdp(connector);
+	struct edid *edid;
+	int num_modes;
+	int ret;
+
+	if (!mhdp->plugged)
+		return 0;
+
+	mutex_lock(&mhdp->link_mutex);
+
+	if (!mhdp->link_up) {
+		ret = cdns_mhdp_link_up(mhdp);
+		if (ret < 0) {
+			mutex_unlock(&mhdp->link_mutex);
+			return 0;
+		}
+	}
+	mutex_unlock(&mhdp->link_mutex);
+
+	edid = drm_do_get_edid(connector, cdns_mhdp_get_edid_block, mhdp);
+	if (!edid) {
+		DRM_DEV_ERROR(mhdp->dev, "Failed to read EDID\n");
+
+		return 0;
+	}
+
+	drm_connector_update_edid_property(connector, edid);
+	num_modes = drm_add_edid_modes(connector, edid);
+	kfree(edid);
+
+	/*
+	 * HACK: Warn about unsupported display formats until we deal
+	 *       with them correctly.
+	 */
+	if (connector->display_info.color_formats &&
+	    !(connector->display_info.color_formats &
+	      mhdp->display_fmt.color_format))
+		dev_warn(mhdp->dev,
+			 "%s: No supported color_format found (0x%08x)\n",
+			__func__, connector->display_info.color_formats);
+
+	if (connector->display_info.bpc &&
+	    connector->display_info.bpc < mhdp->display_fmt.bpc)
+		dev_warn(mhdp->dev, "%s: Display bpc only %d < %d\n",
+			 __func__, connector->display_info.bpc,
+			 mhdp->display_fmt.bpc);
+
+	return num_modes;
+}
+
+static int cdns_mhdp_detect(struct drm_connector *conn,
+			    struct drm_modeset_acquire_ctx *ctx,
+			    bool force)
+{
+	struct cdns_mhdp_device *mhdp = connector_to_mhdp(conn);
+
+	dev_dbg(mhdp->dev, "%s: %d\n", __func__, mhdp->plugged);
+
+	if (mhdp->plugged)
+		return connector_status_connected;
+	else
+		return connector_status_disconnected;
+}
+
+static u32 cdns_mhdp_get_bpp(struct cdns_mhdp_display_fmt *fmt)
+{
+	u32 bpp;
+
+	if (fmt->y_only)
+		return fmt->bpc;
+
+	switch (fmt->color_format) {
+	case DRM_COLOR_FORMAT_RGB444:
+	case DRM_COLOR_FORMAT_YCRCB444:
+		bpp = fmt->bpc * 3;
+		break;
+	case DRM_COLOR_FORMAT_YCRCB422:
+		bpp = fmt->bpc * 2;
+		break;
+	case DRM_COLOR_FORMAT_YCRCB420:
+		bpp = fmt->bpc * 3 / 2;
+		break;
+	default:
+		bpp = fmt->bpc * 3;
+		WARN_ON(1);
+	}
+	return bpp;
+}
+
+static
+bool cdns_mhdp_bandwidth_ok(struct cdns_mhdp_device *mhdp,
+			    const struct drm_display_mode *mode,
+			    int lanes, int rate)
+{
+	u32 max_bw, req_bw, bpp;
+
+	bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt);
+	req_bw = mode->clock * bpp / 8;
+	max_bw = lanes * rate;
+	if (req_bw > max_bw) {
+		dev_dbg(mhdp->dev,
+			"Unsupported Mode: %s, Req BW: %u, Available Max BW:%u\n",
+			mode->name, req_bw, max_bw);
+
+		return false;
+	}
+
+	return true;
+}
+
+static
+enum drm_mode_status cdns_mhdp_mode_valid(struct drm_connector *conn,
+					  struct drm_display_mode *mode)
+{
+	struct cdns_mhdp_device *mhdp = connector_to_mhdp(conn);
+
+	if (!cdns_mhdp_bandwidth_ok(mhdp, mode, mhdp->link.num_lanes,
+				    mhdp->link.rate))
+		return MODE_CLOCK_HIGH;
+
+	return MODE_OK;
+}
+
+static const struct drm_connector_helper_funcs cdns_mhdp_conn_helper_funcs = {
+	.detect_ctx = cdns_mhdp_detect,
+	.get_modes = cdns_mhdp_get_modes,
+	.mode_valid = cdns_mhdp_mode_valid,
+};
+
+static const struct drm_connector_funcs cdns_mhdp_conn_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+	.reset = drm_atomic_helper_connector_reset,
+	.destroy = drm_connector_cleanup,
+};
+
+static int cdns_mhdp_attach(struct drm_bridge *bridge,
+			    enum drm_bridge_attach_flags flags)
+{
+	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
+	u32 bus_format = MEDIA_BUS_FMT_RGB121212_1X36;
+	struct drm_connector *conn = &mhdp->connector;
+	bool hw_ready;
+	int ret;
+
+	dev_dbg(mhdp->dev, "%s\n", __func__);
+
+	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+		DRM_ERROR("Fix bridge driver to make connector optional!");
+		return -EINVAL;
+	}
+
+	if (&mhdp->bridge != bridge)
+		return -ENODEV;
+
+	conn->polled = DRM_CONNECTOR_POLL_HPD;
+
+	ret = drm_connector_init(bridge->dev, conn, &cdns_mhdp_conn_funcs,
+				 DRM_MODE_CONNECTOR_DisplayPort);
+	if (ret) {
+		dev_err(mhdp->dev, "failed to init connector\n");
+		return ret;
+	}
+
+	drm_connector_helper_add(conn, &cdns_mhdp_conn_helper_funcs);
+
+	ret = drm_display_info_set_bus_formats(&conn->display_info,
+					       &bus_format, 1);
+	if (ret)
+		return ret;
+
+	conn->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH;
+
+	ret = drm_connector_attach_encoder(conn, bridge->encoder);
+	if (ret) {
+		dev_err(mhdp->dev, "failed to attach connector to encoder\n");
+		return ret;
+	}
+
+	spin_lock(&mhdp->start_lock);
+
+	mhdp->bridge_attached = true;
+	hw_ready = mhdp->hw_state == MHDP_HW_READY;
+
+	spin_unlock(&mhdp->start_lock);
+
+	if (hw_ready)
+		/* enable SW event interrupts */
+		writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
+		       mhdp->regs + CDNS_APB_INT_MASK);
+
+	return 0;
+}
+
+static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp,
+				      const struct drm_display_mode *mode)
+{
+	unsigned int dp_framer_sp = 0, msa_horizontal_1,
+		msa_vertical_1, bnd_hsync2vsync, hsync2vsync_pol_ctrl,
+		misc0 = 0, misc1 = 0, pxl_repr,
+		front_porch, back_porch, msa_h0, msa_v0, hsync, vsync,
+		dp_vertical_1;
+	u32 bpp, bpc, pxlfmt;
+	u32 framer;
+	u8 stream_id = mhdp->stream_id;
+	int ret;
+
+	pxlfmt = mhdp->display_fmt.color_format;
+	bpc = mhdp->display_fmt.bpc;
+
+	/* If YCBCR supported and stream not SD, use ITU709
+	 * Need to handle ITU version with YCBCR420 when supported
+	 */
+	if ((pxlfmt == DRM_COLOR_FORMAT_YCRCB444 ||
+	     pxlfmt == DRM_COLOR_FORMAT_YCRCB422) && mode->crtc_vdisplay >= 720)
+		misc0 = DP_YCBCR_COEFFICIENTS_ITU709;
+
+	bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt);
+
+	switch (pxlfmt) {
+	case DRM_COLOR_FORMAT_RGB444:
+		pxl_repr = CDNS_DP_FRAMER_RGB << CDNS_DP_FRAMER_PXL_FORMAT;
+		misc0 |= DP_COLOR_FORMAT_RGB;
+		break;
+	case DRM_COLOR_FORMAT_YCRCB444:
+		pxl_repr = CDNS_DP_FRAMER_YCBCR444 << CDNS_DP_FRAMER_PXL_FORMAT;
+		misc0 |= DP_COLOR_FORMAT_YCbCr444 | DP_TEST_DYNAMIC_RANGE_CEA;
+		break;
+	case DRM_COLOR_FORMAT_YCRCB422:
+		pxl_repr = CDNS_DP_FRAMER_YCBCR422 << CDNS_DP_FRAMER_PXL_FORMAT;
+		misc0 |= DP_COLOR_FORMAT_YCbCr422 | DP_TEST_DYNAMIC_RANGE_CEA;
+		break;
+	case DRM_COLOR_FORMAT_YCRCB420:
+		pxl_repr = CDNS_DP_FRAMER_YCBCR420 << CDNS_DP_FRAMER_PXL_FORMAT;
+		break;
+	default:
+		pxl_repr = CDNS_DP_FRAMER_Y_ONLY << CDNS_DP_FRAMER_PXL_FORMAT;
+	}
+
+	switch (bpc) {
+	case 6:
+		misc0 |= DP_TEST_BIT_DEPTH_6;
+		pxl_repr |= CDNS_DP_FRAMER_6_BPC;
+		break;
+	case 8:
+		misc0 |= DP_TEST_BIT_DEPTH_8;
+		pxl_repr |= CDNS_DP_FRAMER_8_BPC;
+		break;
+	case 10:
+		misc0 |= DP_TEST_BIT_DEPTH_10;
+		pxl_repr |= CDNS_DP_FRAMER_10_BPC;
+		break;
+	case 12:
+		misc0 |= DP_TEST_BIT_DEPTH_12;
+		pxl_repr |= CDNS_DP_FRAMER_12_BPC;
+		break;
+	case 16:
+		misc0 |= DP_TEST_BIT_DEPTH_16;
+		pxl_repr |= CDNS_DP_FRAMER_16_BPC;
+		break;
+	}
+
+	bnd_hsync2vsync = CDNS_IP_BYPASS_V_INTERFACE;
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		bnd_hsync2vsync |= CDNS_IP_DET_INTERLACE_FORMAT;
+
+	cdns_mhdp_reg_write(mhdp, CDNS_BND_HSYNC2VSYNC(stream_id),
+			    bnd_hsync2vsync);
+
+	hsync2vsync_pol_ctrl = 0;
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		hsync2vsync_pol_ctrl |= CDNS_H2V_HSYNC_POL_ACTIVE_LOW;
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		hsync2vsync_pol_ctrl |= CDNS_H2V_VSYNC_POL_ACTIVE_LOW;
+	cdns_mhdp_reg_write(mhdp, CDNS_HSYNC2VSYNC_POL_CTRL(stream_id),
+			    hsync2vsync_pol_ctrl);
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_PXL_REPR(stream_id), pxl_repr);
+
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		dp_framer_sp |= CDNS_DP_FRAMER_INTERLACE;
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		dp_framer_sp |= CDNS_DP_FRAMER_HSYNC_POL_LOW;
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		dp_framer_sp |= CDNS_DP_FRAMER_VSYNC_POL_LOW;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_SP(stream_id), dp_framer_sp);
+
+	front_porch = mode->crtc_hsync_start - mode->crtc_hdisplay;
+	back_porch = mode->crtc_htotal - mode->crtc_hsync_end;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRONT_BACK_PORCH(stream_id),
+			    CDNS_DP_FRONT_PORCH(front_porch) |
+			    CDNS_DP_BACK_PORCH(back_porch));
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_BYTE_COUNT(stream_id),
+			    mode->crtc_hdisplay * bpp / 8);
+
+	msa_h0 = mode->crtc_htotal - mode->crtc_hsync_start;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_HORIZONTAL_0(stream_id),
+			    CDNS_DP_MSAH0_H_TOTAL(mode->crtc_htotal) |
+			    CDNS_DP_MSAH0_HSYNC_START(msa_h0));
+
+	hsync = mode->crtc_hsync_end - mode->crtc_hsync_start;
+	msa_horizontal_1 = CDNS_DP_MSAH1_HSYNC_WIDTH(hsync) |
+			   CDNS_DP_MSAH1_HDISP_WIDTH(mode->crtc_hdisplay);
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		msa_horizontal_1 |= CDNS_DP_MSAH1_HSYNC_POL_LOW;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_HORIZONTAL_1(stream_id),
+			    msa_horizontal_1);
+
+	msa_v0 = mode->crtc_vtotal - mode->crtc_vsync_start;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_VERTICAL_0(stream_id),
+			    CDNS_DP_MSAV0_V_TOTAL(mode->crtc_vtotal) |
+			    CDNS_DP_MSAV0_VSYNC_START(msa_v0));
+
+	vsync = mode->crtc_vsync_end - mode->crtc_vsync_start;
+	msa_vertical_1 = CDNS_DP_MSAV1_VSYNC_WIDTH(vsync) |
+			 CDNS_DP_MSAV1_VDISP_WIDTH(mode->crtc_vdisplay);
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		msa_vertical_1 |= CDNS_DP_MSAV1_VSYNC_POL_LOW;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_VERTICAL_1(stream_id),
+			    msa_vertical_1);
+
+	if ((mode->flags & DRM_MODE_FLAG_INTERLACE) &&
+	    mode->crtc_vtotal % 2 == 0)
+		misc1 = DP_TEST_INTERLACED;
+	if (mhdp->display_fmt.y_only)
+		misc1 |= CDNS_DP_TEST_COLOR_FORMAT_RAW_Y_ONLY;
+	/* Use VSC SDP for Y420 */
+	if (pxlfmt == DRM_COLOR_FORMAT_YCRCB420)
+		misc1 = CDNS_DP_TEST_VSC_SDP;
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_MISC(stream_id),
+			    misc0 | (misc1 << 8));
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_HORIZONTAL(stream_id),
+			    CDNS_DP_H_HSYNC_WIDTH(hsync) |
+			    CDNS_DP_H_H_TOTAL(mode->crtc_hdisplay));
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_VERTICAL_0(stream_id),
+			    CDNS_DP_V0_VHEIGHT(mode->crtc_vdisplay) |
+			    CDNS_DP_V0_VSTART(msa_v0));
+
+	dp_vertical_1 = CDNS_DP_V1_VTOTAL(mode->crtc_vtotal);
+	if ((mode->flags & DRM_MODE_FLAG_INTERLACE) &&
+	    mode->crtc_vtotal % 2 == 0)
+		dp_vertical_1 |= CDNS_DP_V1_VTOTAL_EVEN;
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_VERTICAL_1(stream_id), dp_vertical_1);
+
+	cdns_mhdp_reg_write_bit(mhdp, CDNS_DP_VB_ID(stream_id), 2, 1,
+				(mode->flags & DRM_MODE_FLAG_INTERLACE) ?
+				CDNS_DP_VB_ID_INTERLACED : 0);
+
+	ret = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &framer);
+	if (ret < 0) {
+		dev_err(mhdp->dev,
+			"Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n",
+			ret);
+		return;
+	}
+	framer |= CDNS_DP_FRAMER_EN;
+	framer &= ~CDNS_DP_NO_VIDEO_MODE;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, framer);
+}
+
+static void cdns_mhdp_sst_enable(struct cdns_mhdp_device *mhdp,
+				 const struct drm_display_mode *mode,
+				 struct drm_bridge_state *bridge_state)
+{
+	u32 vs, tu_size, line_thresh = 0;
+	struct cdns_mhdp_bridge_state *state;
+
+	state = to_cdns_mhdp_bridge_state(bridge_state);
+
+	vs = state->vs;
+	tu_size = state->tu_size;
+	line_thresh = state->line_thresh;
+
+	mhdp->stream_id = 0;
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_TU,
+			    CDNS_DP_FRAMER_TU_VS(vs) |
+			    CDNS_DP_FRAMER_TU_SIZE(tu_size) |
+			    CDNS_DP_FRAMER_TU_CNT_RST_EN);
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_LINE_THRESH(0),
+			    line_thresh & GENMASK(5, 0));
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_STREAM_CONFIG_2(0),
+			    CDNS_DP_SC2_TU_VS_DIFF((tu_size - vs > 3) ?
+						   0 : tu_size - vs));
+
+	cdns_mhdp_configure_video(mhdp, mode);
+}
+
+static void cdns_mhdp_atomic_enable(struct drm_bridge *bridge,
+				    struct drm_bridge_state *bridge_state)
+{
+	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
+	struct drm_atomic_state *state = bridge_state->base.state;
+	struct cdns_mhdp_bridge_state *mhdp_state;
+	struct drm_crtc_state *crtc_state;
+	struct drm_connector *connector;
+	struct drm_connector_state *conn_state;
+	struct drm_bridge_state *new_state;
+	const struct drm_display_mode *mode;
+	u32 resp;
+	int ret;
+
+	dev_dbg(mhdp->dev, "bridge enable\n");
+
+	mutex_lock(&mhdp->link_mutex);
+
+	if (mhdp->plugged && !mhdp->link_up) {
+		ret = cdns_mhdp_link_up(mhdp);
+		if (ret < 0)
+			goto err_enable;
+	}
+
+	if (mhdp->ops && mhdp->ops->enable)
+		mhdp->ops->enable(mhdp);
+
+	/* Enable VIF clock for stream 0 */
+	ret = cdns_mhdp_reg_read(mhdp, CDNS_DPTX_CAR, &resp);
+	if (ret < 0) {
+		dev_err(mhdp->dev, "Failed to read CDNS_DPTX_CAR %d\n", ret);
+		goto err_enable;
+	}
+
+	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_CAR,
+			    resp | CDNS_VIF_CLK_EN | CDNS_VIF_CLK_RSTN);
+
+	connector = drm_atomic_get_new_connector_for_encoder(state,
+							     bridge->encoder);
+	if (WARN_ON(!connector))
+		goto err_enable;
+
+	conn_state = drm_atomic_get_new_connector_state(state, connector);
+	if (WARN_ON(!conn_state))
+		goto err_enable;
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
+	if (WARN_ON(!crtc_state))
+		goto err_enable;
+
+	mode = &crtc_state->adjusted_mode;
+
+	new_state = drm_atomic_get_new_bridge_state(state, bridge);
+	if (WARN_ON(!new_state))
+		goto err_enable;
+
+	cdns_mhdp_sst_enable(mhdp, mode, new_state);
+
+	mhdp_state = to_cdns_mhdp_bridge_state(new_state);
+
+	mhdp_state->current_mode = drm_mode_duplicate(bridge->dev, mode);
+	drm_mode_set_name(mhdp_state->current_mode);
+
+	dev_dbg(mhdp->dev, "%s: Enabling mode %s\n", __func__, mode->name);
+
+	mhdp->bridge_enabled = true;
+
+err_enable:
+	mutex_unlock(&mhdp->link_mutex);
+}
+
+static void cdns_mhdp_atomic_disable(struct drm_bridge *bridge,
+				     struct drm_bridge_state *bridge_state)
+{
+	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
+	u32 resp;
+
+	dev_dbg(mhdp->dev, "%s\n", __func__);
+
+	mutex_lock(&mhdp->link_mutex);
+
+	mhdp->bridge_enabled = false;
+	cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &resp);
+	resp &= ~CDNS_DP_FRAMER_EN;
+	resp |= CDNS_DP_NO_VIDEO_MODE;
+	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, resp);
+
+	cdns_mhdp_link_down(mhdp);
+
+	/* Disable VIF clock for stream 0 */
+	cdns_mhdp_reg_read(mhdp, CDNS_DPTX_CAR, &resp);
+	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_CAR,
+			    resp & ~(CDNS_VIF_CLK_EN | CDNS_VIF_CLK_RSTN));
+
+	if (mhdp->ops && mhdp->ops->disable)
+		mhdp->ops->disable(mhdp);
+
+	mutex_unlock(&mhdp->link_mutex);
+}
+
+static void cdns_mhdp_detach(struct drm_bridge *bridge)
+{
+	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
+
+	dev_dbg(mhdp->dev, "%s\n", __func__);
+
+	spin_lock(&mhdp->start_lock);
+
+	mhdp->bridge_attached = false;
+
+	spin_unlock(&mhdp->start_lock);
+
+	writel(~0, mhdp->regs + CDNS_APB_INT_MASK);
+}
+
+static struct drm_bridge_state *
+cdns_mhdp_bridge_atomic_duplicate_state(struct drm_bridge *bridge)
+{
+	struct cdns_mhdp_bridge_state *state;
+
+	state = kzalloc(sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return NULL;
+
+	__drm_atomic_helper_bridge_duplicate_state(bridge, &state->base);
+
+	return &state->base;
+}
+
+static void
+cdns_mhdp_bridge_atomic_destroy_state(struct drm_bridge *bridge,
+				      struct drm_bridge_state *state)
+{
+	struct cdns_mhdp_bridge_state *cdns_mhdp_state;
+
+	cdns_mhdp_state = to_cdns_mhdp_bridge_state(state);
+
+	kfree(cdns_mhdp_state);
+}
+
+static struct drm_bridge_state *
+cdns_mhdp_bridge_atomic_reset(struct drm_bridge *bridge)
+{
+	struct cdns_mhdp_bridge_state *cdns_mhdp_state;
+
+	cdns_mhdp_state = kzalloc(sizeof(*cdns_mhdp_state), GFP_KERNEL);
+	if (!cdns_mhdp_state)
+		return NULL;
+
+	 __drm_atomic_helper_bridge_reset(bridge, &cdns_mhdp_state->base);
+
+	return &cdns_mhdp_state->base;
+}
+
+static int cdns_mhdp_validate_mode_params(struct cdns_mhdp_device *mhdp,
+					  const struct drm_display_mode *mode,
+					  struct drm_bridge_state *bridge_state)
+{
+	u32 tu_size = 30, line_thresh1, line_thresh2, line_thresh = 0;
+	u32 rate, vs, vs_f, required_bandwidth, available_bandwidth;
+	struct cdns_mhdp_bridge_state *state;
+	u32 bpp, bpc, pxlfmt;
+	int pxlclock, ret;
+
+	state = to_cdns_mhdp_bridge_state(bridge_state);
+	pxlfmt = mhdp->display_fmt.color_format;
+	bpc = mhdp->display_fmt.bpc;
+
+	pxlclock = mode->crtc_clock;
+
+	rate = mhdp->link.rate / 1000;
+
+	bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt);
+
+	if (!cdns_mhdp_bandwidth_ok(mhdp, mode, mhdp->link.num_lanes,
+				    mhdp->link.rate)) {
+		dev_err(mhdp->dev, "%s: Not enough BW for %s (%u lanes at %u Mbps)\n",
+			__func__, mode->name, mhdp->link.num_lanes,
+			mhdp->link.rate / 100);
+		ret = -EINVAL;
+		goto err_tu_size;
+	}
+
+	/* find optimal tu_size */
+	required_bandwidth = pxlclock * bpp / 8;
+	available_bandwidth = mhdp->link.num_lanes * rate;
+	do {
+		tu_size += 2;
+
+		vs_f = tu_size * required_bandwidth / available_bandwidth;
+		vs = vs_f / 1000;
+		vs_f = vs_f % 1000;
+		/* Downspreading is unused currently */
+	} while ((vs == 1 || ((vs_f > 850 || vs_f < 100) && vs_f != 0) ||
+		 tu_size - vs < 2) && tu_size < 64);
+
+	if (vs > 64) {
+		dev_err(mhdp->dev,
+			"%s: No space for framing %s (%u lanes at %u Mbps)\n",
+			__func__, mode->name, mhdp->link.num_lanes,
+			mhdp->link.rate / 100);
+		ret = -EINVAL;
+		goto err_tu_size;
+	}
+
+	line_thresh1 = ((vs + 1) << 5) * 8 / bpp;
+	line_thresh2 = (pxlclock << 5) / 1000 / rate * (vs + 1) - (1 << 5);
+	line_thresh = line_thresh1 - line_thresh2 / mhdp->link.num_lanes;
+	line_thresh = (line_thresh >> 5) + 2;
+
+	state->vs = vs;
+	state->tu_size = tu_size;
+	state->line_thresh = line_thresh;
+
+	return 0;
+
+err_tu_size:
+	return ret;
+}
+
+static int cdns_mhdp_atomic_check(struct drm_bridge *bridge,
+				  struct drm_bridge_state *bridge_state,
+				  struct drm_crtc_state *crtc_state,
+				  struct drm_connector_state *conn_state)
+{
+	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
+	const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+	int ret;
+
+	if (!mhdp->plugged)
+		return 0;
+
+	mutex_lock(&mhdp->link_mutex);
+
+	if (!mhdp->link_up) {
+		ret = cdns_mhdp_link_up(mhdp);
+		if (ret < 0)
+			goto err_check;
+	}
+
+	ret = cdns_mhdp_validate_mode_params(mhdp, mode, bridge_state);
+
+err_check:
+	mutex_unlock(&mhdp->link_mutex);
+	return ret;
+}
+
+static const struct drm_bridge_funcs cdns_mhdp_bridge_funcs = {
+	.atomic_enable = cdns_mhdp_atomic_enable,
+	.atomic_disable = cdns_mhdp_atomic_disable,
+	.atomic_check = cdns_mhdp_atomic_check,
+	.attach = cdns_mhdp_attach,
+	.detach = cdns_mhdp_detach,
+	.atomic_duplicate_state = cdns_mhdp_bridge_atomic_duplicate_state,
+	.atomic_destroy_state = cdns_mhdp_bridge_atomic_destroy_state,
+	.atomic_reset = cdns_mhdp_bridge_atomic_reset,
+};
+
+static bool cdns_mhdp_detect_hpd(struct cdns_mhdp_device *mhdp, bool *hpd_pulse)
+{
+	int hpd_event, hpd_status;
+
+	*hpd_pulse = false;
+
+	hpd_event = cdns_mhdp_read_event(mhdp);
+
+	/* Getting event bits failed, bail out */
+	if (hpd_event < 0) {
+		dev_warn(mhdp->dev, "%s: read event failed: %d\n",
+			 __func__, hpd_event);
+		return false;
+	}
+
+	hpd_status = cdns_mhdp_get_hpd_status(mhdp);
+	if (hpd_status < 0) {
+		dev_warn(mhdp->dev, "%s: get hpd status failed: %d\n",
+			 __func__, hpd_status);
+		return false;
+	}
+
+	if (hpd_event & DPTX_READ_EVENT_HPD_PULSE)
+		*hpd_pulse = true;
+
+	return !!hpd_status;
+}
+
+static void cdns_mhdp_update_link_status(struct cdns_mhdp_device *mhdp)
+{
+	bool hpd_pulse;
+	int ret;
+	struct drm_bridge_state *state;
+	struct cdns_mhdp_bridge_state *cdns_bridge_state;
+	struct drm_connector *conn = &mhdp->connector;
+	struct drm_display_mode *current_mode;
+	bool old_plugged = mhdp->plugged;
+	u8 status[DP_LINK_STATUS_SIZE];
+
+	mhdp->plugged = cdns_mhdp_detect_hpd(mhdp, &hpd_pulse);
+
+	mutex_lock(&mhdp->link_mutex);
+
+	if (!mhdp->plugged) {
+		cdns_mhdp_link_down(mhdp);
+		goto err;
+	}
+
+	/*
+	 * If we get a HPD pulse event and we were and still are connected,
+	 * check the link status. If link status is ok, there's nothing to do
+	 * as we don't handle DP interrupts. If link status is bad, continue
+	 * with full link setup.
+	 */
+	if (hpd_pulse && old_plugged == mhdp->plugged) {
+		ret = drm_dp_dpcd_read_link_status(&mhdp->aux, status);
+
+		/*
+		 * If everything looks fine, just return, as we don't handle
+		 * DP IRQs.
+		 */
+		if (ret > 0 &&
+		    drm_dp_channel_eq_ok(status, mhdp->link.num_lanes) &&
+		    drm_dp_clock_recovery_ok(status, mhdp->link.num_lanes)) {
+			mutex_unlock(&mhdp->link_mutex);
+			return;
+		}
+
+		/* If link is bad, mark link as down so that we do a new LT */
+		mhdp->link_up = false;
+	}
+
+	if (!mhdp->link_up) {
+		ret = cdns_mhdp_link_up(mhdp);
+		if (ret < 0) {
+			mutex_unlock(&mhdp->link_mutex);
+			drm_connector_set_link_status_property(conn,
+							       DRM_MODE_LINK_STATUS_BAD);
+			return;
+		}
+	}
+
+	if (mhdp->bridge_enabled) {
+		state = drm_priv_to_bridge_state(mhdp->bridge.base.state);
+		if (!state)
+			goto err;
+
+		cdns_bridge_state = to_cdns_mhdp_bridge_state(state);
+		if (!cdns_bridge_state)
+			goto err;
+
+		current_mode = cdns_bridge_state->current_mode;
+		if (!current_mode)
+			goto err;
+
+		ret = cdns_mhdp_validate_mode_params(mhdp, current_mode, state);
+		if (ret < 0)
+			goto err;
+
+		dev_dbg(mhdp->dev, "%s: Enabling mode %s\n", __func__,
+			current_mode->name);
+
+		cdns_mhdp_sst_enable(mhdp, current_mode, state);
+	}
+err:
+	mutex_unlock(&mhdp->link_mutex);
+}
+
+static irqreturn_t cdns_mhdp_irq_handler(int irq, void *data)
+{
+	struct cdns_mhdp_device *mhdp = (struct cdns_mhdp_device *)data;
+	u32 apb_stat, sw_ev0;
+	bool bridge_attached;
+
+	apb_stat = readl(mhdp->regs + CDNS_APB_INT_STATUS);
+	sw_ev0 = readl(mhdp->regs + CDNS_SW_EVENT0);
+
+	/*
+	 *  Calling drm_kms_helper_hotplug_event() when not attached
+	 *  to drm device causes an oops because the drm_bridge->dev
+	 *  is NULL. See cdns_mhdp_fw_cb() comments for details about the
+	 *  problems related drm_kms_helper_hotplug_event() call.
+	 */
+	spin_lock(&mhdp->start_lock);
+	bridge_attached = mhdp->bridge_attached;
+	spin_unlock(&mhdp->start_lock);
+
+	if (bridge_attached && (sw_ev0 & CDNS_DPTX_HPD)) {
+		cdns_mhdp_update_link_status(mhdp);
+
+		drm_kms_helper_hotplug_event(mhdp->bridge.dev);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int cdns_mhdp_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct cdns_mhdp_device *mhdp;
+	struct clk *clk;
+	int ret;
+	unsigned long rate;
+	int irq;
+
+	mhdp = devm_kzalloc(dev, sizeof(*mhdp), GFP_KERNEL);
+	if (!mhdp)
+		return -ENOMEM;
+
+	clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(clk)) {
+		dev_err(dev, "couldn't get clk: %ld\n", PTR_ERR(clk));
+		return PTR_ERR(clk);
+	}
+
+	mhdp->clk = clk;
+	mhdp->dev = dev;
+	mutex_init(&mhdp->mbox_mutex);
+	mutex_init(&mhdp->link_mutex);
+	spin_lock_init(&mhdp->start_lock);
+
+	drm_dp_aux_init(&mhdp->aux);
+	mhdp->aux.dev = dev;
+	mhdp->aux.transfer = cdns_mhdp_transfer;
+
+	mhdp->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(mhdp->regs)) {
+		dev_err(dev, "Failed to get memory resource\n");
+		return PTR_ERR(mhdp->regs);
+	}
+
+	mhdp->phy = devm_of_phy_get_by_index(dev, pdev->dev.of_node, 0);
+	if (IS_ERR(mhdp->phy)) {
+		dev_err(dev, "no PHY configured\n");
+		return PTR_ERR(mhdp->phy);
+	}
+
+	platform_set_drvdata(pdev, mhdp);
+
+	mhdp->ops = of_device_get_match_data(dev);
+
+	clk_prepare_enable(clk);
+
+	pm_runtime_enable(dev);
+	ret = pm_runtime_get_sync(dev);
+	if (ret < 0) {
+		dev_err(dev, "pm_runtime_get_sync failed\n");
+		pm_runtime_disable(dev);
+		goto clk_disable;
+	}
+
+	if (mhdp->ops && mhdp->ops->init) {
+		ret = mhdp->ops->init(mhdp);
+		if (ret != 0) {
+			dev_err(dev, "MHDP platform initialization failed: %d\n",
+				ret);
+			goto runtime_put;
+		}
+	}
+
+	rate = clk_get_rate(clk);
+	writel(rate % 1000000, mhdp->regs + CDNS_SW_CLK_L);
+	writel(rate / 1000000, mhdp->regs + CDNS_SW_CLK_H);
+
+	dev_dbg(dev, "func clk rate %lu Hz\n", rate);
+
+	writel(~0, mhdp->regs + CDNS_APB_INT_MASK);
+
+	irq = platform_get_irq(pdev, 0);
+	ret = devm_request_threaded_irq(mhdp->dev, irq, NULL,
+					cdns_mhdp_irq_handler, IRQF_ONESHOT,
+					"mhdp8546", mhdp);
+	if (ret) {
+		dev_err(dev, "cannot install IRQ %d\n", irq);
+		ret = -EIO;
+		goto plat_fini;
+	}
+
+	cdns_mhdp_fill_host_caps(mhdp);
+
+	/* The only currently supported format */
+	mhdp->display_fmt.y_only = false;
+	mhdp->display_fmt.color_format = DRM_COLOR_FORMAT_RGB444;
+	mhdp->display_fmt.bpc = 8;
+
+	mhdp->bridge.of_node = pdev->dev.of_node;
+	mhdp->bridge.funcs = &cdns_mhdp_bridge_funcs;
+
+	ret = phy_init(mhdp->phy);
+	if (ret) {
+		dev_err(mhdp->dev, "Failed to initialize PHY: %d\n", ret);
+		goto plat_fini;
+	}
+
+	ret = cdns_mhdp_load_firmware(mhdp);
+	if (ret)
+		goto phy_exit;
+
+	drm_bridge_add(&mhdp->bridge);
+
+	return 0;
+
+phy_exit:
+	phy_exit(mhdp->phy);
+plat_fini:
+	if (mhdp->ops && mhdp->ops->exit)
+		mhdp->ops->exit(mhdp);
+runtime_put:
+	pm_runtime_put_sync(dev);
+	pm_runtime_disable(dev);
+clk_disable:
+	clk_disable_unprepare(mhdp->clk);
+
+	return ret;
+}
+
+static int cdns_mhdp_remove(struct platform_device *pdev)
+{
+	struct cdns_mhdp_device *mhdp = dev_get_drvdata(&pdev->dev);
+	unsigned long timeout = msecs_to_jiffies(100);
+	bool stop_fw = false;
+	int ret = 0;
+
+	drm_bridge_remove(&mhdp->bridge);
+
+	ret = wait_event_timeout(fw_load_wq, mhdp->hw_state == MHDP_HW_READY,
+				 timeout);
+	if (ret == 0)
+		dev_err(mhdp->dev, "%s: Timeout waiting for fw loading\n",
+			__func__);
+	else
+		stop_fw = true;
+
+	spin_lock(&mhdp->start_lock);
+	mhdp->hw_state = MHDP_HW_STOPPED;
+	spin_unlock(&mhdp->start_lock);
+
+	if (stop_fw) {
+		ret = cdns_mhdp_set_firmware_active(mhdp, false);
+		if (ret)
+			dev_err(mhdp->dev, "%s: De-activate FW failed: %d\n",
+				__func__, ret);
+	}
+
+	phy_exit(mhdp->phy);
+
+	if (mhdp->ops && mhdp->ops->exit)
+		mhdp->ops->exit(mhdp);
+
+	pm_runtime_put_sync(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+
+	clk_disable_unprepare(mhdp->clk);
+
+	return ret;
+}
+
+static const struct of_device_id mhdp_ids[] = {
+	{ .compatible = "cdns,mhdp8546", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mhdp_ids);
+
+static struct platform_driver mhdp_driver = {
+	.driver	= {
+		.name		= "cdns-mhdp",
+		.of_match_table	= of_match_ptr(mhdp_ids),
+	},
+	.probe	= cdns_mhdp_probe,
+	.remove	= cdns_mhdp_remove,
+};
+module_platform_driver(mhdp_driver);
+
+MODULE_FIRMWARE(FW_NAME);
+
+MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
+MODULE_AUTHOR("Swapnil Jakhade <sjakhade@cadence.com>");
+MODULE_DESCRIPTION("Cadence MHDP DP bridge driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cdns-mhdp");
diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.h b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
new file mode 100644
index 000000000000..d1bfcdef4249
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
@@ -0,0 +1,396 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Cadence MHDP DP bridge driver.
+ *
+ * Copyright: 2019 Cadence Design Systems, Inc.
+ *
+ * Author: Quentin Schulz <quentin.schulz@free-electrons.com>
+ *         Swapnil Jakhade <sjakhade@cadence.com>
+ */
+
+#ifndef CDNS_MHDP_CORE_H
+#define CDNS_MHDP_CORE_H
+
+#include <linux/bits.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_dp_helper.h>
+
+struct clk;
+struct device;
+struct phy;
+
+/* Register offsets */
+#define CDNS_APB_CTRL				0x00000
+#define CDNS_CPU_STALL				BIT(3)
+
+#define CDNS_MAILBOX_FULL			0x00008
+#define CDNS_MAILBOX_EMPTY			0x0000c
+#define CDNS_MAILBOX_TX_DATA			0x00010
+#define CDNS_MAILBOX_RX_DATA			0x00014
+#define CDNS_KEEP_ALIVE				0x00018
+#define CDNS_KEEP_ALIVE_MASK			GENMASK(7, 0)
+
+#define CDNS_VER_L				0x0001C
+#define CDNS_VER_H				0x00020
+#define CDNS_LIB_L_ADDR				0x00024
+#define CDNS_LIB_H_ADDR				0x00028
+
+#define CDNS_MB_INT_MASK			0x00034
+#define CDNS_MB_INT_STATUS			0x00038
+
+#define CDNS_SW_CLK_L				0x0003c
+#define CDNS_SW_CLK_H				0x00040
+
+#define CDNS_SW_EVENT0				0x00044
+#define CDNS_DPTX_HPD				BIT(0)
+
+#define CDNS_SW_EVENT1				0x00048
+#define CDNS_SW_EVENT2				0x0004c
+#define CDNS_SW_EVENT3				0x00050
+
+#define CDNS_APB_INT_MASK			0x0006C
+#define CDNS_APB_INT_MASK_MAILBOX_INT		BIT(0)
+#define CDNS_APB_INT_MASK_SW_EVENT_INT		BIT(1)
+
+#define CDNS_APB_INT_STATUS			0x00070
+
+#define CDNS_DPTX_CAR				0x00904
+#define CDNS_VIF_CLK_EN				BIT(0)
+#define CDNS_VIF_CLK_RSTN			BIT(1)
+
+#define CDNS_SOURCE_VIDEO_IF(s)			(0x00b00 + ((s) * 0x20))
+#define CDNS_BND_HSYNC2VSYNC(s)			(CDNS_SOURCE_VIDEO_IF(s) + \
+						 0x00)
+#define CDNS_IP_DTCT_WIN			GENMASK(11, 0)
+#define CDNS_IP_DET_INTERLACE_FORMAT		BIT(12)
+#define CDNS_IP_BYPASS_V_INTERFACE		BIT(13)
+
+#define CDNS_HSYNC2VSYNC_POL_CTRL(s)		(CDNS_SOURCE_VIDEO_IF(s) + \
+						 0x10)
+#define CDNS_H2V_HSYNC_POL_ACTIVE_LOW		BIT(1)
+#define CDNS_H2V_VSYNC_POL_ACTIVE_LOW		BIT(2)
+
+#define CDNS_DPTX_PHY_CONFIG			0x02000
+#define CDNS_PHY_TRAINING_EN			BIT(0)
+#define CDNS_PHY_TRAINING_TYPE(x)		(((x) & GENMASK(3, 0)) << 1)
+#define CDNS_PHY_SCRAMBLER_BYPASS		BIT(5)
+#define CDNS_PHY_ENCODER_BYPASS			BIT(6)
+#define CDNS_PHY_SKEW_BYPASS			BIT(7)
+#define CDNS_PHY_TRAINING_AUTO			BIT(8)
+#define CDNS_PHY_LANE0_SKEW(x)			(((x) & GENMASK(2, 0)) << 9)
+#define CDNS_PHY_LANE1_SKEW(x)			(((x) & GENMASK(2, 0)) << 12)
+#define CDNS_PHY_LANE2_SKEW(x)			(((x) & GENMASK(2, 0)) << 15)
+#define CDNS_PHY_LANE3_SKEW(x)			(((x) & GENMASK(2, 0)) << 18)
+#define CDNS_PHY_COMMON_CONFIG			(CDNS_PHY_LANE1_SKEW(1) | \
+						CDNS_PHY_LANE2_SKEW(2) |  \
+						CDNS_PHY_LANE3_SKEW(3))
+#define CDNS_PHY_10BIT_EN			BIT(21)
+
+#define CDNS_DP_FRAMER_GLOBAL_CONFIG		0x02200
+#define CDNS_DP_NUM_LANES(x)			((x) - 1)
+#define CDNS_DP_MST_EN				BIT(2)
+#define CDNS_DP_FRAMER_EN			BIT(3)
+#define CDNS_DP_RATE_GOVERNOR_EN		BIT(4)
+#define CDNS_DP_NO_VIDEO_MODE			BIT(5)
+#define CDNS_DP_DISABLE_PHY_RST			BIT(6)
+#define CDNS_DP_WR_FAILING_EDGE_VSYNC		BIT(7)
+
+#define CDNS_DP_FRAMER_TU			0x02208
+#define CDNS_DP_FRAMER_TU_SIZE(x)		(((x) & GENMASK(6, 0)) << 8)
+#define CDNS_DP_FRAMER_TU_VS(x)			((x) & GENMASK(5, 0))
+#define CDNS_DP_FRAMER_TU_CNT_RST_EN		BIT(15)
+
+#define CDNS_DP_MTPH_CONTROL			0x02264
+#define CDNS_DP_MTPH_ECF_EN			BIT(0)
+#define CDNS_DP_MTPH_ACT_EN			BIT(1)
+#define CDNS_DP_MTPH_LVP_EN			BIT(2)
+
+#define CDNS_DP_MTPH_STATUS			0x0226C
+#define CDNS_DP_MTPH_ACT_STATUS			BIT(0)
+
+#define CDNS_DP_LANE_EN				0x02300
+#define CDNS_DP_LANE_EN_LANES(x)		GENMASK((x) - 1, 0)
+
+#define CDNS_DP_ENHNCD				0x02304
+
+#define CDNS_DPTX_STREAM(s)			(0x03000 + (s) * 0x80)
+#define CDNS_DP_MSA_HORIZONTAL_0(s)		(CDNS_DPTX_STREAM(s) + 0x00)
+#define CDNS_DP_MSAH0_H_TOTAL(x)		(x)
+#define CDNS_DP_MSAH0_HSYNC_START(x)		((x) << 16)
+
+#define CDNS_DP_MSA_HORIZONTAL_1(s)		(CDNS_DPTX_STREAM(s) + 0x04)
+#define CDNS_DP_MSAH1_HSYNC_WIDTH(x)		(x)
+#define CDNS_DP_MSAH1_HSYNC_POL_LOW		BIT(15)
+#define CDNS_DP_MSAH1_HDISP_WIDTH(x)		((x) << 16)
+
+#define CDNS_DP_MSA_VERTICAL_0(s)		(CDNS_DPTX_STREAM(s) + 0x08)
+#define CDNS_DP_MSAV0_V_TOTAL(x)		(x)
+#define CDNS_DP_MSAV0_VSYNC_START(x)		((x) << 16)
+
+#define CDNS_DP_MSA_VERTICAL_1(s)		(CDNS_DPTX_STREAM(s) + 0x0c)
+#define CDNS_DP_MSAV1_VSYNC_WIDTH(x)		(x)
+#define CDNS_DP_MSAV1_VSYNC_POL_LOW		BIT(15)
+#define CDNS_DP_MSAV1_VDISP_WIDTH(x)		((x) << 16)
+
+#define CDNS_DP_MSA_MISC(s)			(CDNS_DPTX_STREAM(s) + 0x10)
+#define CDNS_DP_STREAM_CONFIG(s)		(CDNS_DPTX_STREAM(s) + 0x14)
+#define CDNS_DP_STREAM_CONFIG_2(s)		(CDNS_DPTX_STREAM(s) + 0x2c)
+#define CDNS_DP_SC2_TU_VS_DIFF(x)		((x) << 8)
+
+#define CDNS_DP_HORIZONTAL(s)			(CDNS_DPTX_STREAM(s) + 0x30)
+#define CDNS_DP_H_HSYNC_WIDTH(x)		(x)
+#define CDNS_DP_H_H_TOTAL(x)			((x) << 16)
+
+#define CDNS_DP_VERTICAL_0(s)			(CDNS_DPTX_STREAM(s) + 0x34)
+#define CDNS_DP_V0_VHEIGHT(x)			(x)
+#define CDNS_DP_V0_VSTART(x)			((x) << 16)
+
+#define CDNS_DP_VERTICAL_1(s)			(CDNS_DPTX_STREAM(s) + 0x38)
+#define CDNS_DP_V1_VTOTAL(x)			(x)
+#define CDNS_DP_V1_VTOTAL_EVEN			BIT(16)
+
+#define CDNS_DP_MST_SLOT_ALLOCATE(s)		(CDNS_DPTX_STREAM(s) + 0x44)
+#define CDNS_DP_S_ALLOC_START_SLOT(x)		(x)
+#define CDNS_DP_S_ALLOC_END_SLOT(x)		((x) << 8)
+
+#define CDNS_DP_RATE_GOVERNING(s)		(CDNS_DPTX_STREAM(s) + 0x48)
+#define CDNS_DP_RG_TARG_AV_SLOTS_Y(x)		(x)
+#define CDNS_DP_RG_TARG_AV_SLOTS_X(x)		((x) << 4)
+#define CDNS_DP_RG_ENABLE			BIT(10)
+
+#define CDNS_DP_FRAMER_PXL_REPR(s)		(CDNS_DPTX_STREAM(s) + 0x4c)
+#define CDNS_DP_FRAMER_6_BPC			BIT(0)
+#define CDNS_DP_FRAMER_8_BPC			BIT(1)
+#define CDNS_DP_FRAMER_10_BPC			BIT(2)
+#define CDNS_DP_FRAMER_12_BPC			BIT(3)
+#define CDNS_DP_FRAMER_16_BPC			BIT(4)
+#define CDNS_DP_FRAMER_PXL_FORMAT		0x8
+#define CDNS_DP_FRAMER_RGB			BIT(0)
+#define CDNS_DP_FRAMER_YCBCR444			BIT(1)
+#define CDNS_DP_FRAMER_YCBCR422			BIT(2)
+#define CDNS_DP_FRAMER_YCBCR420			BIT(3)
+#define CDNS_DP_FRAMER_Y_ONLY			BIT(4)
+
+#define CDNS_DP_FRAMER_SP(s)			(CDNS_DPTX_STREAM(s) + 0x50)
+#define CDNS_DP_FRAMER_VSYNC_POL_LOW		BIT(0)
+#define CDNS_DP_FRAMER_HSYNC_POL_LOW		BIT(1)
+#define CDNS_DP_FRAMER_INTERLACE		BIT(2)
+
+#define CDNS_DP_LINE_THRESH(s)			(CDNS_DPTX_STREAM(s) + 0x64)
+#define CDNS_DP_ACTIVE_LINE_THRESH(x)		(x)
+
+#define CDNS_DP_VB_ID(s)			(CDNS_DPTX_STREAM(s) + 0x68)
+#define CDNS_DP_VB_ID_INTERLACED		BIT(2)
+#define CDNS_DP_VB_ID_COMPRESSED		BIT(6)
+
+#define CDNS_DP_FRONT_BACK_PORCH(s)		(CDNS_DPTX_STREAM(s) + 0x78)
+#define CDNS_DP_BACK_PORCH(x)			(x)
+#define CDNS_DP_FRONT_PORCH(x)			((x) << 16)
+
+#define CDNS_DP_BYTE_COUNT(s)			(CDNS_DPTX_STREAM(s) + 0x7c)
+#define CDNS_DP_BYTE_COUNT_BYTES_IN_CHUNK_SHIFT	16
+
+/* mailbox */
+#define MAILBOX_RETRY_US			1000
+#define MAILBOX_TIMEOUT_US			2000000
+
+#define MB_OPCODE_ID				0
+#define MB_MODULE_ID				1
+#define MB_SIZE_MSB_ID				2
+#define MB_SIZE_LSB_ID				3
+#define MB_DATA_ID				4
+
+#define MB_MODULE_ID_DP_TX			0x01
+#define MB_MODULE_ID_HDCP_TX			0x07
+#define MB_MODULE_ID_HDCP_RX			0x08
+#define MB_MODULE_ID_HDCP_GENERAL		0x09
+#define MB_MODULE_ID_GENERAL			0x0a
+
+/* firmware and opcodes */
+#define FW_NAME					"cadence/mhdp8546.bin"
+#define CDNS_MHDP_IMEM				0x10000
+
+#define GENERAL_MAIN_CONTROL			0x01
+#define GENERAL_TEST_ECHO			0x02
+#define GENERAL_BUS_SETTINGS			0x03
+#define GENERAL_TEST_ACCESS			0x04
+#define GENERAL_REGISTER_READ			0x07
+
+#define DPTX_SET_POWER_MNG			0x00
+#define DPTX_GET_EDID				0x02
+#define DPTX_READ_DPCD				0x03
+#define DPTX_WRITE_DPCD				0x04
+#define DPTX_ENABLE_EVENT			0x05
+#define DPTX_WRITE_REGISTER			0x06
+#define DPTX_READ_REGISTER			0x07
+#define DPTX_WRITE_FIELD			0x08
+#define DPTX_READ_EVENT				0x0a
+#define DPTX_GET_LAST_AUX_STAUS			0x0e
+#define DPTX_HPD_STATE				0x11
+#define DPTX_ADJUST_LT				0x12
+
+#define FW_STANDBY				0
+#define FW_ACTIVE				1
+
+/* HPD */
+#define DPTX_READ_EVENT_HPD_TO_HIGH             BIT(0)
+#define DPTX_READ_EVENT_HPD_TO_LOW              BIT(1)
+#define DPTX_READ_EVENT_HPD_PULSE               BIT(2)
+#define DPTX_READ_EVENT_HPD_STATE               BIT(3)
+
+/* general */
+#define CDNS_DP_TRAINING_PATTERN_4		0x7
+
+#define CDNS_KEEP_ALIVE_TIMEOUT			2000
+
+#define CDNS_VOLT_SWING(x)			((x) & GENMASK(1, 0))
+#define CDNS_FORCE_VOLT_SWING			BIT(2)
+
+#define CDNS_PRE_EMPHASIS(x)			((x) & GENMASK(1, 0))
+#define CDNS_FORCE_PRE_EMPHASIS			BIT(2)
+
+#define CDNS_SUPPORT_TPS(x)			BIT((x) - 1)
+
+#define CDNS_FAST_LINK_TRAINING			BIT(0)
+
+#define CDNS_LANE_MAPPING_TYPE_C_LANE_0(x)	((x) & GENMASK(1, 0))
+#define CDNS_LANE_MAPPING_TYPE_C_LANE_1(x)	((x) & GENMASK(3, 2))
+#define CDNS_LANE_MAPPING_TYPE_C_LANE_2(x)	((x) & GENMASK(5, 4))
+#define CDNS_LANE_MAPPING_TYPE_C_LANE_3(x)	((x) & GENMASK(7, 6))
+#define CDNS_LANE_MAPPING_NORMAL		0xe4
+#define CDNS_LANE_MAPPING_FLIPPED		0x1b
+
+#define CDNS_DP_MAX_NUM_LANES			4
+#define CDNS_DP_TEST_VSC_SDP			BIT(6) /* 1.3+ */
+#define CDNS_DP_TEST_COLOR_FORMAT_RAW_Y_ONLY	BIT(7)
+
+#define CDNS_MHDP_MAX_STREAMS   4
+
+#define DP_LINK_CAP_ENHANCED_FRAMING BIT(0)
+
+struct cdns_mhdp_link {
+	unsigned char revision;
+	unsigned int rate;
+	unsigned int num_lanes;
+	unsigned long capabilities;
+};
+
+struct cdns_mhdp_host {
+	unsigned int link_rate;
+	u8 lanes_cnt;
+	u8 volt_swing;
+	u8 pre_emphasis;
+	u8 pattern_supp;
+	u8 lane_mapping;
+	bool fast_link;
+	bool enhanced;
+	bool scrambler;
+	bool ssc;
+};
+
+struct cdns_mhdp_sink {
+	unsigned int link_rate;
+	u8 lanes_cnt;
+	u8 pattern_supp;
+	bool fast_link;
+	bool enhanced;
+	bool ssc;
+};
+
+struct cdns_mhdp_display_fmt {
+	u32 color_format;
+	u32 bpc;
+	bool y_only;
+};
+
+/*
+ * These enums present MHDP hw initialization state
+ * Legal state transitions are:
+ * MHDP_HW_INACTIVE <-> MHDP_HW_LOADING -> MHDP_HW_READY
+ *        |                                     |
+ *        '----------> MHDP_HW_STOPPED <--------'
+ */
+enum mhdp_hw_state {
+	MHDP_HW_INACTIVE = 0,	/* HW not initialized */
+	MHDP_HW_LOADING,	/* HW initialization in progress */
+	MHDP_HW_READY,		/* HW ready, FW active*/
+	MHDP_HW_STOPPED		/* Driver removal FW to be stopped */
+};
+
+struct cdns_mhdp_device;
+
+struct mhdp_platform_ops {
+	int (*init)(struct cdns_mhdp_device *mhdp);
+	void (*exit)(struct cdns_mhdp_device *mhdp);
+	void (*enable)(struct cdns_mhdp_device *mhdp);
+	void (*disable)(struct cdns_mhdp_device *mhdp);
+};
+
+struct cdns_mhdp_bridge_state {
+	struct drm_bridge_state base;
+	u32 tu_size;
+	u32 vs;
+	u32 line_thresh;
+	struct drm_display_mode *current_mode;
+};
+
+#define to_cdns_mhdp_bridge_state(s) \
+		container_of(s, struct cdns_mhdp_bridge_state, base)
+
+struct cdns_mhdp_device {
+	void __iomem *regs;
+
+	struct device *dev;
+	struct clk *clk;
+	struct phy *phy;
+
+	const struct mhdp_platform_ops *ops;
+
+	/* This is to protect mailbox communications with the firmware */
+	struct mutex mbox_mutex;
+
+	/* "link_mutex" protects the access to all the link parameters
+	 * including the link training process. Link training will be
+	 * invoked both from threaded interrupt handler and from atomic
+	 * callbacks when link_up is not set. So this mutex protects
+	 * flags such as link_up, bridge_enabled, link.num_lanes,
+	 * link.rate etc.
+	 */
+	struct mutex link_mutex;
+
+	struct drm_connector connector;
+	struct drm_bridge bridge;
+
+	struct cdns_mhdp_link link;
+	struct drm_dp_aux aux;
+
+	struct cdns_mhdp_host host;
+	struct cdns_mhdp_sink sink;
+	struct cdns_mhdp_display_fmt display_fmt;
+	s8 stream_id;
+
+	bool link_up;
+	bool plugged;
+
+	/*
+	 * "start_lock" protects the access to bridge_attached and
+	 * hw_state data members that control the delayed firmware
+	 * loading and attaching the bridge. They are accessed from
+	 * both the DRM core and cdns_mhdp_fw_cb(). In most cases just
+	 * protecting the data members is enough, but the irq mask
+	 * setting needs to be protected when enabling the FW.
+	 */
+	spinlock_t start_lock;
+	bool bridge_attached;
+	bool bridge_enabled;
+	enum mhdp_hw_state hw_state;
+};
+
+#define connector_to_mhdp(x) container_of(x, struct cdns_mhdp_device, connector)
+#define bridge_to_mhdp(x) container_of(x, struct cdns_mhdp_device, bridge)
+
+#endif
-- 
2.26.1

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

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

* [PATCH v7 3/3] drm: bridge: cdns-mhdp: Add j721e wrapper
  2020-07-22  7:40 ` Swapnil Jakhade
@ 2020-07-22  7:40   ` Swapnil Jakhade
  -1 siblings, 0 replies; 26+ messages in thread
From: Swapnil Jakhade @ 2020-07-22  7:40 UTC (permalink / raw)
  To: airlied, daniel, Laurent.pinchart, robh+dt, a.hajda, narmstrong,
	jonas, jernej.skrabec, dri-devel, devicetree, linux-kernel
  Cc: mparab, sjakhade, yamonkar, tomi.valkeinen, jsarha, nsekhar, praneeth

Add j721e wrapper for mhdp, which sets up the clock and data muxes.

Signed-off-by: Jyri Sarha <jsarha@ti.com>
Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/bridge/Kconfig           | 13 ++++
 drivers/gpu/drm/bridge/Makefile          |  5 ++
 drivers/gpu/drm/bridge/cdns-mhdp-core.c  | 15 +++++
 drivers/gpu/drm/bridge/cdns-mhdp-core.h  |  1 +
 drivers/gpu/drm/bridge/cdns-mhdp-j721e.c | 75 ++++++++++++++++++++++++
 drivers/gpu/drm/bridge/cdns-mhdp-j721e.h | 19 ++++++
 6 files changed, 128 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-j721e.c
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-j721e.h

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 6a4c324302a8..8c1738653b7e 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -38,6 +38,19 @@ config DRM_CDNS_MHDP
 	  It takes a DPI stream as input and outputs it encoded
 	  in DP format.
 
+if DRM_CDNS_MHDP
+
+config DRM_CDNS_MHDP_J721E
+	depends on ARCH_K3_J721E_SOC
+	bool "J721E Cadence DPI/DP wrapper support"
+	default y
+	help
+	  Support J721E Cadence DPI/DP wrapper. This is a wrapper
+	  which adds support for J721E related platform ops. It
+	  initializes the J721e Display Port and sets up the
+	  clock and data muxes.
+endif
+
 config DRM_CHRONTEL_CH7033
 	tristate "Chrontel CH7033 Video Encoder"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 0080a9f80f29..f81d606e9598 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -2,6 +2,11 @@
 obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
 obj-$(CONFIG_DRM_CDNS_MHDP) += cdns-mhdp.o
 cdns-mhdp-objs := cdns-mhdp-core.o
+
+ifeq ($(CONFIG_DRM_CDNS_MHDP_J721E),y)
+	cdns-mhdp-objs += cdns-mhdp-j721e.o
+endif
+
 obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
 obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
 obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.c b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
index b16c5503cef1..eeb33c54334f 100644
--- a/drivers/gpu/drm/bridge/cdns-mhdp-core.c
+++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
@@ -38,6 +38,8 @@
 
 #include "cdns-mhdp-core.h"
 
+#include "cdns-mhdp-j721e.h"
+
 static DECLARE_WAIT_QUEUE_HEAD(fw_load_wq);
 
 static int cdns_mhdp_mailbox_read(struct cdns_mhdp_device *mhdp)
@@ -1691,6 +1693,16 @@ static int cdns_mhdp_attach(struct drm_bridge *bridge,
 
 	conn->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH;
 
+	if (of_device_is_compatible(mhdp->dev->of_node, "ti,j721e-mhdp8546"))
+	/*
+	 * DP is internal to J7 SoC and we need to use DRIVE_POSEDGE
+	 * in the display controller. This is achieved for the time being
+	 * by defining SAMPLE_NEGEDGE here.
+	 */
+		conn->display_info.bus_flags |=
+					DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE |
+					DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE;
+
 	ret = drm_connector_attach_encoder(conn, bridge->encoder);
 	if (ret) {
 		dev_err(mhdp->dev, "failed to attach connector to encoder\n");
@@ -2470,6 +2482,9 @@ static int cdns_mhdp_remove(struct platform_device *pdev)
 
 static const struct of_device_id mhdp_ids[] = {
 	{ .compatible = "cdns,mhdp8546", },
+#ifdef CONFIG_DRM_CDNS_MHDP_J721E
+	{ .compatible = "ti,j721e-mhdp8546", .data = &mhdp_ti_j721e_ops },
+#endif
 	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, mhdp_ids);
diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.h b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
index d1bfcdef4249..4e176698caca 100644
--- a/drivers/gpu/drm/bridge/cdns-mhdp-core.h
+++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
@@ -343,6 +343,7 @@ struct cdns_mhdp_bridge_state {
 
 struct cdns_mhdp_device {
 	void __iomem *regs;
+	void __iomem *j721e_regs;
 
 	struct device *dev;
 	struct clk *clk;
diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-j721e.c b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.c
new file mode 100644
index 000000000000..7555c217c9a0
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TI j721e Cadence MHDP DP wrapper
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Jyri Sarha <jsarha@ti.com
+ */
+
+#include <linux/io.h>
+#include <linux/platform_device.h>
+
+#include "cdns-mhdp-j721e.h"
+
+#define	REVISION			0x00
+#define	DPTX_IPCFG			0x04
+#define	ECC_MEM_CFG			0x08
+#define	DPTX_DSC_CFG			0x0c
+#define	DPTX_SRC_CFG			0x10
+#define	DPTX_VIF_SECURE_MODE_CFG	0x14
+#define	DPTX_VIF_CONN_STATUS		0x18
+#define	PHY_CLK_STATUS			0x1c
+
+#define DPTX_SRC_AIF_EN			BIT(16)
+#define DPTX_SRC_VIF_3_IN30B		BIT(11)
+#define DPTX_SRC_VIF_2_IN30B		BIT(10)
+#define DPTX_SRC_VIF_1_IN30B		BIT(9)
+#define DPTX_SRC_VIF_0_IN30B		BIT(8)
+#define DPTX_SRC_VIF_3_SEL_DPI5		BIT(7)
+#define DPTX_SRC_VIF_3_SEL_DPI3		0
+#define DPTX_SRC_VIF_2_SEL_DPI4		BIT(6)
+#define DPTX_SRC_VIF_2_SEL_DPI2		0
+#define DPTX_SRC_VIF_1_SEL_DPI3		BIT(5)
+#define DPTX_SRC_VIF_1_SEL_DPI1		0
+#define DPTX_SRC_VIF_0_SEL_DPI2		BIT(4)
+#define DPTX_SRC_VIF_0_SEL_DPI0		0
+#define DPTX_SRC_VIF_3_EN		BIT(3)
+#define DPTX_SRC_VIF_2_EN		BIT(2)
+#define DPTX_SRC_VIF_1_EN		BIT(1)
+#define DPTX_SRC_VIF_0_EN		BIT(0)
+
+/* TODO turn DPTX_IPCFG fw_mem_clk_en at pm_runtime_suspend. */
+
+static int cdns_mhdp_j721e_init(struct cdns_mhdp_device *mhdp)
+{
+	struct platform_device *pdev = to_platform_device(mhdp->dev);
+
+	mhdp->j721e_regs = devm_platform_ioremap_resource(pdev, 1);
+	if (IS_ERR(mhdp->j721e_regs))
+		return PTR_ERR(mhdp->j721e_regs);
+
+	return 0;
+}
+
+static void cdns_mhdp_j721e_enable(struct cdns_mhdp_device *mhdp)
+{
+	/*
+	 * Eneble VIF_0 and select DPI2 as its input. DSS0 DPI0 is connected
+	 * to eDP DPI2. This is the only supported SST configuration on
+	 * J721E.
+	 */
+	writel(DPTX_SRC_VIF_0_EN | DPTX_SRC_VIF_0_SEL_DPI2,
+	       mhdp->j721e_regs + DPTX_SRC_CFG);
+}
+
+static void cdns_mhdp_j721e_disable(struct cdns_mhdp_device *mhdp)
+{
+	/* Put everything to defaults  */
+	writel(0, mhdp->j721e_regs + DPTX_DSC_CFG);
+}
+
+const struct mhdp_platform_ops mhdp_ti_j721e_ops = {
+	.init = cdns_mhdp_j721e_init,
+	.enable = cdns_mhdp_j721e_enable,
+	.disable = cdns_mhdp_j721e_disable,
+};
diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-j721e.h b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.h
new file mode 100644
index 000000000000..f1ff07b82d32
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * TI j721e Cadence MHDP DP wrapper
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Jyri Sarha <jsarha@ti.com
+ */
+
+#ifndef CDNS_MHDP_J721E_H
+#define CDNS_MHDP_J721E_H
+
+#include <linux/platform_device.h>
+#include "cdns-mhdp-core.h"
+
+struct mhdp_platform_ops;
+
+extern const struct mhdp_platform_ops mhdp_ti_j721e_ops;
+
+#endif /* !CDNS_MHDP_J721E_H */
-- 
2.26.1


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

* [PATCH v7 3/3] drm: bridge: cdns-mhdp: Add j721e wrapper
@ 2020-07-22  7:40   ` Swapnil Jakhade
  0 siblings, 0 replies; 26+ messages in thread
From: Swapnil Jakhade @ 2020-07-22  7:40 UTC (permalink / raw)
  To: airlied, daniel, Laurent.pinchart, robh+dt, a.hajda, narmstrong,
	jonas, jernej.skrabec, dri-devel, devicetree, linux-kernel
  Cc: mparab, yamonkar, praneeth, nsekhar, jsarha, tomi.valkeinen, sjakhade

Add j721e wrapper for mhdp, which sets up the clock and data muxes.

Signed-off-by: Jyri Sarha <jsarha@ti.com>
Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/gpu/drm/bridge/Kconfig           | 13 ++++
 drivers/gpu/drm/bridge/Makefile          |  5 ++
 drivers/gpu/drm/bridge/cdns-mhdp-core.c  | 15 +++++
 drivers/gpu/drm/bridge/cdns-mhdp-core.h  |  1 +
 drivers/gpu/drm/bridge/cdns-mhdp-j721e.c | 75 ++++++++++++++++++++++++
 drivers/gpu/drm/bridge/cdns-mhdp-j721e.h | 19 ++++++
 6 files changed, 128 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-j721e.c
 create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-j721e.h

diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 6a4c324302a8..8c1738653b7e 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -38,6 +38,19 @@ config DRM_CDNS_MHDP
 	  It takes a DPI stream as input and outputs it encoded
 	  in DP format.
 
+if DRM_CDNS_MHDP
+
+config DRM_CDNS_MHDP_J721E
+	depends on ARCH_K3_J721E_SOC
+	bool "J721E Cadence DPI/DP wrapper support"
+	default y
+	help
+	  Support J721E Cadence DPI/DP wrapper. This is a wrapper
+	  which adds support for J721E related platform ops. It
+	  initializes the J721e Display Port and sets up the
+	  clock and data muxes.
+endif
+
 config DRM_CHRONTEL_CH7033
 	tristate "Chrontel CH7033 Video Encoder"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index 0080a9f80f29..f81d606e9598 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -2,6 +2,11 @@
 obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
 obj-$(CONFIG_DRM_CDNS_MHDP) += cdns-mhdp.o
 cdns-mhdp-objs := cdns-mhdp-core.o
+
+ifeq ($(CONFIG_DRM_CDNS_MHDP_J721E),y)
+	cdns-mhdp-objs += cdns-mhdp-j721e.o
+endif
+
 obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
 obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
 obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.c b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
index b16c5503cef1..eeb33c54334f 100644
--- a/drivers/gpu/drm/bridge/cdns-mhdp-core.c
+++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
@@ -38,6 +38,8 @@
 
 #include "cdns-mhdp-core.h"
 
+#include "cdns-mhdp-j721e.h"
+
 static DECLARE_WAIT_QUEUE_HEAD(fw_load_wq);
 
 static int cdns_mhdp_mailbox_read(struct cdns_mhdp_device *mhdp)
@@ -1691,6 +1693,16 @@ static int cdns_mhdp_attach(struct drm_bridge *bridge,
 
 	conn->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH;
 
+	if (of_device_is_compatible(mhdp->dev->of_node, "ti,j721e-mhdp8546"))
+	/*
+	 * DP is internal to J7 SoC and we need to use DRIVE_POSEDGE
+	 * in the display controller. This is achieved for the time being
+	 * by defining SAMPLE_NEGEDGE here.
+	 */
+		conn->display_info.bus_flags |=
+					DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE |
+					DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE;
+
 	ret = drm_connector_attach_encoder(conn, bridge->encoder);
 	if (ret) {
 		dev_err(mhdp->dev, "failed to attach connector to encoder\n");
@@ -2470,6 +2482,9 @@ static int cdns_mhdp_remove(struct platform_device *pdev)
 
 static const struct of_device_id mhdp_ids[] = {
 	{ .compatible = "cdns,mhdp8546", },
+#ifdef CONFIG_DRM_CDNS_MHDP_J721E
+	{ .compatible = "ti,j721e-mhdp8546", .data = &mhdp_ti_j721e_ops },
+#endif
 	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, mhdp_ids);
diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.h b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
index d1bfcdef4249..4e176698caca 100644
--- a/drivers/gpu/drm/bridge/cdns-mhdp-core.h
+++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
@@ -343,6 +343,7 @@ struct cdns_mhdp_bridge_state {
 
 struct cdns_mhdp_device {
 	void __iomem *regs;
+	void __iomem *j721e_regs;
 
 	struct device *dev;
 	struct clk *clk;
diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-j721e.c b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.c
new file mode 100644
index 000000000000..7555c217c9a0
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TI j721e Cadence MHDP DP wrapper
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Jyri Sarha <jsarha@ti.com
+ */
+
+#include <linux/io.h>
+#include <linux/platform_device.h>
+
+#include "cdns-mhdp-j721e.h"
+
+#define	REVISION			0x00
+#define	DPTX_IPCFG			0x04
+#define	ECC_MEM_CFG			0x08
+#define	DPTX_DSC_CFG			0x0c
+#define	DPTX_SRC_CFG			0x10
+#define	DPTX_VIF_SECURE_MODE_CFG	0x14
+#define	DPTX_VIF_CONN_STATUS		0x18
+#define	PHY_CLK_STATUS			0x1c
+
+#define DPTX_SRC_AIF_EN			BIT(16)
+#define DPTX_SRC_VIF_3_IN30B		BIT(11)
+#define DPTX_SRC_VIF_2_IN30B		BIT(10)
+#define DPTX_SRC_VIF_1_IN30B		BIT(9)
+#define DPTX_SRC_VIF_0_IN30B		BIT(8)
+#define DPTX_SRC_VIF_3_SEL_DPI5		BIT(7)
+#define DPTX_SRC_VIF_3_SEL_DPI3		0
+#define DPTX_SRC_VIF_2_SEL_DPI4		BIT(6)
+#define DPTX_SRC_VIF_2_SEL_DPI2		0
+#define DPTX_SRC_VIF_1_SEL_DPI3		BIT(5)
+#define DPTX_SRC_VIF_1_SEL_DPI1		0
+#define DPTX_SRC_VIF_0_SEL_DPI2		BIT(4)
+#define DPTX_SRC_VIF_0_SEL_DPI0		0
+#define DPTX_SRC_VIF_3_EN		BIT(3)
+#define DPTX_SRC_VIF_2_EN		BIT(2)
+#define DPTX_SRC_VIF_1_EN		BIT(1)
+#define DPTX_SRC_VIF_0_EN		BIT(0)
+
+/* TODO turn DPTX_IPCFG fw_mem_clk_en at pm_runtime_suspend. */
+
+static int cdns_mhdp_j721e_init(struct cdns_mhdp_device *mhdp)
+{
+	struct platform_device *pdev = to_platform_device(mhdp->dev);
+
+	mhdp->j721e_regs = devm_platform_ioremap_resource(pdev, 1);
+	if (IS_ERR(mhdp->j721e_regs))
+		return PTR_ERR(mhdp->j721e_regs);
+
+	return 0;
+}
+
+static void cdns_mhdp_j721e_enable(struct cdns_mhdp_device *mhdp)
+{
+	/*
+	 * Eneble VIF_0 and select DPI2 as its input. DSS0 DPI0 is connected
+	 * to eDP DPI2. This is the only supported SST configuration on
+	 * J721E.
+	 */
+	writel(DPTX_SRC_VIF_0_EN | DPTX_SRC_VIF_0_SEL_DPI2,
+	       mhdp->j721e_regs + DPTX_SRC_CFG);
+}
+
+static void cdns_mhdp_j721e_disable(struct cdns_mhdp_device *mhdp)
+{
+	/* Put everything to defaults  */
+	writel(0, mhdp->j721e_regs + DPTX_DSC_CFG);
+}
+
+const struct mhdp_platform_ops mhdp_ti_j721e_ops = {
+	.init = cdns_mhdp_j721e_init,
+	.enable = cdns_mhdp_j721e_enable,
+	.disable = cdns_mhdp_j721e_disable,
+};
diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-j721e.h b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.h
new file mode 100644
index 000000000000..f1ff07b82d32
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * TI j721e Cadence MHDP DP wrapper
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Jyri Sarha <jsarha@ti.com
+ */
+
+#ifndef CDNS_MHDP_J721E_H
+#define CDNS_MHDP_J721E_H
+
+#include <linux/platform_device.h>
+#include "cdns-mhdp-core.h"
+
+struct mhdp_platform_ops;
+
+extern const struct mhdp_platform_ops mhdp_ti_j721e_ops;
+
+#endif /* !CDNS_MHDP_J721E_H */
-- 
2.26.1

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

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

* Re: [PATCH v7 2/3] drm: bridge: Add support for Cadence MHDP DPI/DP bridge
  2020-07-22  7:40   ` Swapnil Jakhade
  (?)
@ 2020-07-22 11:43     ` kernel test robot
  -1 siblings, 0 replies; 26+ messages in thread
From: kernel test robot @ 2020-07-22 11:43 UTC (permalink / raw)
  To: Swapnil Jakhade, airlied, daniel, Laurent.pinchart, robh+dt,
	a.hajda, narmstrong, jonas, jernej.skrabec, dri-devel,
	devicetree
  Cc: kbuild-all

[-- Attachment #1: Type: text/plain, Size: 8666 bytes --]

Hi Swapnil,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on robh/for-next]
[also build test WARNING on linus/master v5.8-rc6 next-20200721]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Swapnil-Jakhade/drm-Add-support-for-Cadence-MHDP-DPI-DP-bridge-and-J721E-wrapper/20200722-154322
base:   https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
config: x86_64-allmodconfig (attached as .config)
compiler: gcc-9 (Debian 9.3.0-14) 9.3.0
reproduce (this is a W=1 build):
        # save the attached .config to linux build tree
        make W=1 ARCH=x86_64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_fw_activate':
>> drivers/gpu/drm/bridge/cdns-mhdp-core.c:765:10: warning: conversion from 'long unsigned int' to 'unsigned int' changes value from '18446744073709551613' to '4294967293' [-Woverflow]
     765 |   writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
   In file included from include/linux/bits.h:23,
                    from include/linux/bitops.h:5,
                    from include/linux/kernel.h:12,
                    from include/linux/clk.h:13,
                    from drivers/gpu/drm/bridge/cdns-mhdp-core.c:10:
   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_link_training_init':
   include/linux/bits.h:26:28: warning: comparison of unsigned expression < 0 is always false [-Wtype-limits]
      26 |   __builtin_constant_p((l) > (h)), (l) > (h), 0)))
         |                            ^
   include/linux/build_bug.h:16:62: note: in definition of macro 'BUILD_BUG_ON_ZERO'
      16 | #define BUILD_BUG_ON_ZERO(e) ((int)(sizeof(struct { int:(-!!(e)); })))
         |                                                              ^
   include/linux/bits.h:39:3: note: in expansion of macro 'GENMASK_INPUT_CHECK'
      39 |  (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l))
         |   ^~~~~~~~~~~~~~~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.h:116:35: note: in expansion of macro 'GENMASK'
     116 | #define CDNS_DP_LANE_EN_LANES(x)  GENMASK((x) - 1, 0)
         |                                   ^~~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:888:8: note: in expansion of macro 'CDNS_DP_LANE_EN_LANES'
     888 |        CDNS_DP_LANE_EN_LANES(mhdp->link.num_lanes));
         |        ^~~~~~~~~~~~~~~~~~~~~
   include/linux/bits.h:26:40: warning: comparison of unsigned expression < 0 is always false [-Wtype-limits]
      26 |   __builtin_constant_p((l) > (h)), (l) > (h), 0)))
         |                                        ^
   include/linux/build_bug.h:16:62: note: in definition of macro 'BUILD_BUG_ON_ZERO'
      16 | #define BUILD_BUG_ON_ZERO(e) ((int)(sizeof(struct { int:(-!!(e)); })))
         |                                                              ^
   include/linux/bits.h:39:3: note: in expansion of macro 'GENMASK_INPUT_CHECK'
      39 |  (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l))
         |   ^~~~~~~~~~~~~~~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.h:116:35: note: in expansion of macro 'GENMASK'
     116 | #define CDNS_DP_LANE_EN_LANES(x)  GENMASK((x) - 1, 0)
         |                                   ^~~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:888:8: note: in expansion of macro 'CDNS_DP_LANE_EN_LANES'
     888 |        CDNS_DP_LANE_EN_LANES(mhdp->link.num_lanes));
         |        ^~~~~~~~~~~~~~~~~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_fill_host_caps':
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:1378:2: error: implicit declaration of function 'phy_get_attrs'; did you mean 'vfs_getattr'? [-Werror=implicit-function-declaration]
    1378 |  phy_get_attrs(mhdp->phy, &attrs);
         |  ^~~~~~~~~~~~~
         |  vfs_getattr
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:1384:19: error: 'struct phy_attrs' has no member named 'max_link_rate'
    1384 |  link_rate = attrs.max_link_rate;
         |                   ^
   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_attach':
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:1709:10: warning: conversion from 'long unsigned int' to 'unsigned int' changes value from '18446744073709551613' to '4294967293' [-Woverflow]
    1709 |   writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_validate_mode_params':
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:2077:16: warning: variable 'pxlfmt' set but not used [-Wunused-but-set-variable]
    2077 |  u32 bpp, bpc, pxlfmt;
         |                ^~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:2077:11: warning: variable 'bpc' set but not used [-Wunused-but-set-variable]
    2077 |  u32 bpp, bpc, pxlfmt;
         |           ^~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_irq_handler':
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:2285:6: warning: variable 'apb_stat' set but not used [-Wunused-but-set-variable]
    2285 |  u32 apb_stat, sw_ev0;
         |      ^~~~~~~~
   cc1: some warnings being treated as errors

vim +765 drivers/gpu/drm/bridge/cdns-mhdp-core.c

   686	
   687	static int cdns_mhdp_fw_activate(const struct firmware *fw,
   688					 struct cdns_mhdp_device *mhdp)
   689	{
   690		unsigned int reg;
   691		int ret = 0;
   692	
   693		dev_dbg(mhdp->dev, "%s\n", __func__);
   694	
   695		if (!fw || !fw->data) {
   696			dev_err(mhdp->dev, "%s: No firmware.\n", __func__);
   697			return -EINVAL;
   698		}
   699	
   700		spin_lock(&mhdp->start_lock);
   701		if (mhdp->hw_state != MHDP_HW_INACTIVE) {
   702			spin_unlock(&mhdp->start_lock);
   703			if (mhdp->hw_state != MHDP_HW_STOPPED)
   704				dev_err(mhdp->dev, "%s: Bad HW state: %d\n",
   705					__func__, mhdp->hw_state);
   706			return -EBUSY;
   707		}
   708		mhdp->hw_state = MHDP_HW_LOADING;
   709		spin_unlock(&mhdp->start_lock);
   710	
   711		/* Release uCPU reset and stall it. */
   712		writel(CDNS_CPU_STALL, mhdp->regs + CDNS_APB_CTRL);
   713	
   714		memcpy_toio(mhdp->regs + CDNS_MHDP_IMEM, fw->data, fw->size);
   715	
   716		/* Leave debug mode, release stall */
   717		writel(0, mhdp->regs + CDNS_APB_CTRL);
   718	
   719		/*
   720		 * Wait for the KEEP_ALIVE "message" on the first 8 bits.
   721		 * Updated each sched "tick" (~2ms)
   722		 */
   723		ret = readl_poll_timeout(mhdp->regs + CDNS_KEEP_ALIVE, reg,
   724					 reg & CDNS_KEEP_ALIVE_MASK, 500,
   725					 CDNS_KEEP_ALIVE_TIMEOUT);
   726		if (ret) {
   727			dev_err(mhdp->dev,
   728				"device didn't give any life sign: reg %d\n", reg);
   729			goto error;
   730		}
   731	
   732		ret = cdns_mhdp_check_fw_version(mhdp);
   733		if (ret)
   734			goto error;
   735	
   736		/* Init events to 0 as it's not cleared by FW at boot but on read */
   737		readl(mhdp->regs + CDNS_SW_EVENT0);
   738		readl(mhdp->regs + CDNS_SW_EVENT1);
   739		readl(mhdp->regs + CDNS_SW_EVENT2);
   740		readl(mhdp->regs + CDNS_SW_EVENT3);
   741	
   742		/* Activate uCPU */
   743		ret = cdns_mhdp_set_firmware_active(mhdp, true);
   744		if (ret) {
   745			dev_err(mhdp->dev, "%s: Failed to activate FW: %d\n",
   746				__func__, ret);
   747			goto error;
   748		}
   749	
   750		spin_lock(&mhdp->start_lock);
   751	
   752		mhdp->hw_state = MHDP_HW_READY;
   753		wake_up(&fw_load_wq);
   754		/*
   755		 * Here we must keep the lock while enabling the interrupts
   756		 * since it would otherwise be possible that interrupt enable
   757		 * code is executed after the bridge is detached. The similar
   758		 * situation is not possible in attach()/detach() callbacks
   759		 * since the hw_state changes from MHDP_HW_READY to
   760		 * MHDP_HW_STOPPED happens only due to driver removal when
   761		 * bridge should already be detached.
   762		 */
   763		if (mhdp->bridge_attached)
   764			/* enable SW event interrupts */
 > 765			writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
   766			       mhdp->regs + CDNS_APB_INT_MASK);
   767	
   768		spin_unlock(&mhdp->start_lock);
   769	
   770		dev_dbg(mhdp->dev, "DP FW activated\n");
   771	
   772		return 0;
   773	error:
   774		spin_lock(&mhdp->start_lock);
   775		mhdp->hw_state = MHDP_HW_INACTIVE;
   776		spin_unlock(&mhdp->start_lock);
   777	
   778		return ret;
   779	}
   780	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org

[-- Attachment #2: .config.gz --]
[-- Type: application/gzip, Size: 75856 bytes --]

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

* Re: [PATCH v7 2/3] drm: bridge: Add support for Cadence MHDP DPI/DP bridge
@ 2020-07-22 11:43     ` kernel test robot
  0 siblings, 0 replies; 26+ messages in thread
From: kernel test robot @ 2020-07-22 11:43 UTC (permalink / raw)
  To: Swapnil Jakhade, airlied, daniel, Laurent.pinchart, robh+dt,
	a.hajda, narmstrong, jonas, jernej.skrabec, dri-devel,
	devicetree
  Cc: kbuild-all

[-- Attachment #1: Type: text/plain, Size: 8666 bytes --]

Hi Swapnil,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on robh/for-next]
[also build test WARNING on linus/master v5.8-rc6 next-20200721]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Swapnil-Jakhade/drm-Add-support-for-Cadence-MHDP-DPI-DP-bridge-and-J721E-wrapper/20200722-154322
base:   https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
config: x86_64-allmodconfig (attached as .config)
compiler: gcc-9 (Debian 9.3.0-14) 9.3.0
reproduce (this is a W=1 build):
        # save the attached .config to linux build tree
        make W=1 ARCH=x86_64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_fw_activate':
>> drivers/gpu/drm/bridge/cdns-mhdp-core.c:765:10: warning: conversion from 'long unsigned int' to 'unsigned int' changes value from '18446744073709551613' to '4294967293' [-Woverflow]
     765 |   writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
   In file included from include/linux/bits.h:23,
                    from include/linux/bitops.h:5,
                    from include/linux/kernel.h:12,
                    from include/linux/clk.h:13,
                    from drivers/gpu/drm/bridge/cdns-mhdp-core.c:10:
   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_link_training_init':
   include/linux/bits.h:26:28: warning: comparison of unsigned expression < 0 is always false [-Wtype-limits]
      26 |   __builtin_constant_p((l) > (h)), (l) > (h), 0)))
         |                            ^
   include/linux/build_bug.h:16:62: note: in definition of macro 'BUILD_BUG_ON_ZERO'
      16 | #define BUILD_BUG_ON_ZERO(e) ((int)(sizeof(struct { int:(-!!(e)); })))
         |                                                              ^
   include/linux/bits.h:39:3: note: in expansion of macro 'GENMASK_INPUT_CHECK'
      39 |  (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l))
         |   ^~~~~~~~~~~~~~~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.h:116:35: note: in expansion of macro 'GENMASK'
     116 | #define CDNS_DP_LANE_EN_LANES(x)  GENMASK((x) - 1, 0)
         |                                   ^~~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:888:8: note: in expansion of macro 'CDNS_DP_LANE_EN_LANES'
     888 |        CDNS_DP_LANE_EN_LANES(mhdp->link.num_lanes));
         |        ^~~~~~~~~~~~~~~~~~~~~
   include/linux/bits.h:26:40: warning: comparison of unsigned expression < 0 is always false [-Wtype-limits]
      26 |   __builtin_constant_p((l) > (h)), (l) > (h), 0)))
         |                                        ^
   include/linux/build_bug.h:16:62: note: in definition of macro 'BUILD_BUG_ON_ZERO'
      16 | #define BUILD_BUG_ON_ZERO(e) ((int)(sizeof(struct { int:(-!!(e)); })))
         |                                                              ^
   include/linux/bits.h:39:3: note: in expansion of macro 'GENMASK_INPUT_CHECK'
      39 |  (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l))
         |   ^~~~~~~~~~~~~~~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.h:116:35: note: in expansion of macro 'GENMASK'
     116 | #define CDNS_DP_LANE_EN_LANES(x)  GENMASK((x) - 1, 0)
         |                                   ^~~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:888:8: note: in expansion of macro 'CDNS_DP_LANE_EN_LANES'
     888 |        CDNS_DP_LANE_EN_LANES(mhdp->link.num_lanes));
         |        ^~~~~~~~~~~~~~~~~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_fill_host_caps':
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:1378:2: error: implicit declaration of function 'phy_get_attrs'; did you mean 'vfs_getattr'? [-Werror=implicit-function-declaration]
    1378 |  phy_get_attrs(mhdp->phy, &attrs);
         |  ^~~~~~~~~~~~~
         |  vfs_getattr
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:1384:19: error: 'struct phy_attrs' has no member named 'max_link_rate'
    1384 |  link_rate = attrs.max_link_rate;
         |                   ^
   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_attach':
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:1709:10: warning: conversion from 'long unsigned int' to 'unsigned int' changes value from '18446744073709551613' to '4294967293' [-Woverflow]
    1709 |   writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_validate_mode_params':
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:2077:16: warning: variable 'pxlfmt' set but not used [-Wunused-but-set-variable]
    2077 |  u32 bpp, bpc, pxlfmt;
         |                ^~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:2077:11: warning: variable 'bpc' set but not used [-Wunused-but-set-variable]
    2077 |  u32 bpp, bpc, pxlfmt;
         |           ^~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_irq_handler':
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:2285:6: warning: variable 'apb_stat' set but not used [-Wunused-but-set-variable]
    2285 |  u32 apb_stat, sw_ev0;
         |      ^~~~~~~~
   cc1: some warnings being treated as errors

vim +765 drivers/gpu/drm/bridge/cdns-mhdp-core.c

   686	
   687	static int cdns_mhdp_fw_activate(const struct firmware *fw,
   688					 struct cdns_mhdp_device *mhdp)
   689	{
   690		unsigned int reg;
   691		int ret = 0;
   692	
   693		dev_dbg(mhdp->dev, "%s\n", __func__);
   694	
   695		if (!fw || !fw->data) {
   696			dev_err(mhdp->dev, "%s: No firmware.\n", __func__);
   697			return -EINVAL;
   698		}
   699	
   700		spin_lock(&mhdp->start_lock);
   701		if (mhdp->hw_state != MHDP_HW_INACTIVE) {
   702			spin_unlock(&mhdp->start_lock);
   703			if (mhdp->hw_state != MHDP_HW_STOPPED)
   704				dev_err(mhdp->dev, "%s: Bad HW state: %d\n",
   705					__func__, mhdp->hw_state);
   706			return -EBUSY;
   707		}
   708		mhdp->hw_state = MHDP_HW_LOADING;
   709		spin_unlock(&mhdp->start_lock);
   710	
   711		/* Release uCPU reset and stall it. */
   712		writel(CDNS_CPU_STALL, mhdp->regs + CDNS_APB_CTRL);
   713	
   714		memcpy_toio(mhdp->regs + CDNS_MHDP_IMEM, fw->data, fw->size);
   715	
   716		/* Leave debug mode, release stall */
   717		writel(0, mhdp->regs + CDNS_APB_CTRL);
   718	
   719		/*
   720		 * Wait for the KEEP_ALIVE "message" on the first 8 bits.
   721		 * Updated each sched "tick" (~2ms)
   722		 */
   723		ret = readl_poll_timeout(mhdp->regs + CDNS_KEEP_ALIVE, reg,
   724					 reg & CDNS_KEEP_ALIVE_MASK, 500,
   725					 CDNS_KEEP_ALIVE_TIMEOUT);
   726		if (ret) {
   727			dev_err(mhdp->dev,
   728				"device didn't give any life sign: reg %d\n", reg);
   729			goto error;
   730		}
   731	
   732		ret = cdns_mhdp_check_fw_version(mhdp);
   733		if (ret)
   734			goto error;
   735	
   736		/* Init events to 0 as it's not cleared by FW at boot but on read */
   737		readl(mhdp->regs + CDNS_SW_EVENT0);
   738		readl(mhdp->regs + CDNS_SW_EVENT1);
   739		readl(mhdp->regs + CDNS_SW_EVENT2);
   740		readl(mhdp->regs + CDNS_SW_EVENT3);
   741	
   742		/* Activate uCPU */
   743		ret = cdns_mhdp_set_firmware_active(mhdp, true);
   744		if (ret) {
   745			dev_err(mhdp->dev, "%s: Failed to activate FW: %d\n",
   746				__func__, ret);
   747			goto error;
   748		}
   749	
   750		spin_lock(&mhdp->start_lock);
   751	
   752		mhdp->hw_state = MHDP_HW_READY;
   753		wake_up(&fw_load_wq);
   754		/*
   755		 * Here we must keep the lock while enabling the interrupts
   756		 * since it would otherwise be possible that interrupt enable
   757		 * code is executed after the bridge is detached. The similar
   758		 * situation is not possible in attach()/detach() callbacks
   759		 * since the hw_state changes from MHDP_HW_READY to
   760		 * MHDP_HW_STOPPED happens only due to driver removal when
   761		 * bridge should already be detached.
   762		 */
   763		if (mhdp->bridge_attached)
   764			/* enable SW event interrupts */
 > 765			writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
   766			       mhdp->regs + CDNS_APB_INT_MASK);
   767	
   768		spin_unlock(&mhdp->start_lock);
   769	
   770		dev_dbg(mhdp->dev, "DP FW activated\n");
   771	
   772		return 0;
   773	error:
   774		spin_lock(&mhdp->start_lock);
   775		mhdp->hw_state = MHDP_HW_INACTIVE;
   776		spin_unlock(&mhdp->start_lock);
   777	
   778		return ret;
   779	}
   780	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org

[-- Attachment #2: .config.gz --]
[-- Type: application/gzip, Size: 75856 bytes --]

[-- Attachment #3: Type: text/plain, Size: 160 bytes --]

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

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

* Re: [PATCH v7 2/3] drm: bridge: Add support for Cadence MHDP DPI/DP bridge
@ 2020-07-22 11:43     ` kernel test robot
  0 siblings, 0 replies; 26+ messages in thread
From: kernel test robot @ 2020-07-22 11:43 UTC (permalink / raw)
  To: kbuild-all

[-- Attachment #1: Type: text/plain, Size: 8855 bytes --]

Hi Swapnil,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on robh/for-next]
[also build test WARNING on linus/master v5.8-rc6 next-20200721]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Swapnil-Jakhade/drm-Add-support-for-Cadence-MHDP-DPI-DP-bridge-and-J721E-wrapper/20200722-154322
base:   https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
config: x86_64-allmodconfig (attached as .config)
compiler: gcc-9 (Debian 9.3.0-14) 9.3.0
reproduce (this is a W=1 build):
        # save the attached .config to linux build tree
        make W=1 ARCH=x86_64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_fw_activate':
>> drivers/gpu/drm/bridge/cdns-mhdp-core.c:765:10: warning: conversion from 'long unsigned int' to 'unsigned int' changes value from '18446744073709551613' to '4294967293' [-Woverflow]
     765 |   writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
   In file included from include/linux/bits.h:23,
                    from include/linux/bitops.h:5,
                    from include/linux/kernel.h:12,
                    from include/linux/clk.h:13,
                    from drivers/gpu/drm/bridge/cdns-mhdp-core.c:10:
   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_link_training_init':
   include/linux/bits.h:26:28: warning: comparison of unsigned expression < 0 is always false [-Wtype-limits]
      26 |   __builtin_constant_p((l) > (h)), (l) > (h), 0)))
         |                            ^
   include/linux/build_bug.h:16:62: note: in definition of macro 'BUILD_BUG_ON_ZERO'
      16 | #define BUILD_BUG_ON_ZERO(e) ((int)(sizeof(struct { int:(-!!(e)); })))
         |                                                              ^
   include/linux/bits.h:39:3: note: in expansion of macro 'GENMASK_INPUT_CHECK'
      39 |  (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l))
         |   ^~~~~~~~~~~~~~~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.h:116:35: note: in expansion of macro 'GENMASK'
     116 | #define CDNS_DP_LANE_EN_LANES(x)  GENMASK((x) - 1, 0)
         |                                   ^~~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:888:8: note: in expansion of macro 'CDNS_DP_LANE_EN_LANES'
     888 |        CDNS_DP_LANE_EN_LANES(mhdp->link.num_lanes));
         |        ^~~~~~~~~~~~~~~~~~~~~
   include/linux/bits.h:26:40: warning: comparison of unsigned expression < 0 is always false [-Wtype-limits]
      26 |   __builtin_constant_p((l) > (h)), (l) > (h), 0)))
         |                                        ^
   include/linux/build_bug.h:16:62: note: in definition of macro 'BUILD_BUG_ON_ZERO'
      16 | #define BUILD_BUG_ON_ZERO(e) ((int)(sizeof(struct { int:(-!!(e)); })))
         |                                                              ^
   include/linux/bits.h:39:3: note: in expansion of macro 'GENMASK_INPUT_CHECK'
      39 |  (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l))
         |   ^~~~~~~~~~~~~~~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.h:116:35: note: in expansion of macro 'GENMASK'
     116 | #define CDNS_DP_LANE_EN_LANES(x)  GENMASK((x) - 1, 0)
         |                                   ^~~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:888:8: note: in expansion of macro 'CDNS_DP_LANE_EN_LANES'
     888 |        CDNS_DP_LANE_EN_LANES(mhdp->link.num_lanes));
         |        ^~~~~~~~~~~~~~~~~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_fill_host_caps':
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:1378:2: error: implicit declaration of function 'phy_get_attrs'; did you mean 'vfs_getattr'? [-Werror=implicit-function-declaration]
    1378 |  phy_get_attrs(mhdp->phy, &attrs);
         |  ^~~~~~~~~~~~~
         |  vfs_getattr
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:1384:19: error: 'struct phy_attrs' has no member named 'max_link_rate'
    1384 |  link_rate = attrs.max_link_rate;
         |                   ^
   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_attach':
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:1709:10: warning: conversion from 'long unsigned int' to 'unsigned int' changes value from '18446744073709551613' to '4294967293' [-Woverflow]
    1709 |   writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_validate_mode_params':
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:2077:16: warning: variable 'pxlfmt' set but not used [-Wunused-but-set-variable]
    2077 |  u32 bpp, bpc, pxlfmt;
         |                ^~~~~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:2077:11: warning: variable 'bpc' set but not used [-Wunused-but-set-variable]
    2077 |  u32 bpp, bpc, pxlfmt;
         |           ^~~
   drivers/gpu/drm/bridge/cdns-mhdp-core.c: In function 'cdns_mhdp_irq_handler':
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:2285:6: warning: variable 'apb_stat' set but not used [-Wunused-but-set-variable]
    2285 |  u32 apb_stat, sw_ev0;
         |      ^~~~~~~~
   cc1: some warnings being treated as errors

vim +765 drivers/gpu/drm/bridge/cdns-mhdp-core.c

   686	
   687	static int cdns_mhdp_fw_activate(const struct firmware *fw,
   688					 struct cdns_mhdp_device *mhdp)
   689	{
   690		unsigned int reg;
   691		int ret = 0;
   692	
   693		dev_dbg(mhdp->dev, "%s\n", __func__);
   694	
   695		if (!fw || !fw->data) {
   696			dev_err(mhdp->dev, "%s: No firmware.\n", __func__);
   697			return -EINVAL;
   698		}
   699	
   700		spin_lock(&mhdp->start_lock);
   701		if (mhdp->hw_state != MHDP_HW_INACTIVE) {
   702			spin_unlock(&mhdp->start_lock);
   703			if (mhdp->hw_state != MHDP_HW_STOPPED)
   704				dev_err(mhdp->dev, "%s: Bad HW state: %d\n",
   705					__func__, mhdp->hw_state);
   706			return -EBUSY;
   707		}
   708		mhdp->hw_state = MHDP_HW_LOADING;
   709		spin_unlock(&mhdp->start_lock);
   710	
   711		/* Release uCPU reset and stall it. */
   712		writel(CDNS_CPU_STALL, mhdp->regs + CDNS_APB_CTRL);
   713	
   714		memcpy_toio(mhdp->regs + CDNS_MHDP_IMEM, fw->data, fw->size);
   715	
   716		/* Leave debug mode, release stall */
   717		writel(0, mhdp->regs + CDNS_APB_CTRL);
   718	
   719		/*
   720		 * Wait for the KEEP_ALIVE "message" on the first 8 bits.
   721		 * Updated each sched "tick" (~2ms)
   722		 */
   723		ret = readl_poll_timeout(mhdp->regs + CDNS_KEEP_ALIVE, reg,
   724					 reg & CDNS_KEEP_ALIVE_MASK, 500,
   725					 CDNS_KEEP_ALIVE_TIMEOUT);
   726		if (ret) {
   727			dev_err(mhdp->dev,
   728				"device didn't give any life sign: reg %d\n", reg);
   729			goto error;
   730		}
   731	
   732		ret = cdns_mhdp_check_fw_version(mhdp);
   733		if (ret)
   734			goto error;
   735	
   736		/* Init events to 0 as it's not cleared by FW at boot but on read */
   737		readl(mhdp->regs + CDNS_SW_EVENT0);
   738		readl(mhdp->regs + CDNS_SW_EVENT1);
   739		readl(mhdp->regs + CDNS_SW_EVENT2);
   740		readl(mhdp->regs + CDNS_SW_EVENT3);
   741	
   742		/* Activate uCPU */
   743		ret = cdns_mhdp_set_firmware_active(mhdp, true);
   744		if (ret) {
   745			dev_err(mhdp->dev, "%s: Failed to activate FW: %d\n",
   746				__func__, ret);
   747			goto error;
   748		}
   749	
   750		spin_lock(&mhdp->start_lock);
   751	
   752		mhdp->hw_state = MHDP_HW_READY;
   753		wake_up(&fw_load_wq);
   754		/*
   755		 * Here we must keep the lock while enabling the interrupts
   756		 * since it would otherwise be possible that interrupt enable
   757		 * code is executed after the bridge is detached. The similar
   758		 * situation is not possible in attach()/detach() callbacks
   759		 * since the hw_state changes from MHDP_HW_READY to
   760		 * MHDP_HW_STOPPED happens only due to driver removal when
   761		 * bridge should already be detached.
   762		 */
   763		if (mhdp->bridge_attached)
   764			/* enable SW event interrupts */
 > 765			writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
   766			       mhdp->regs + CDNS_APB_INT_MASK);
   767	
   768		spin_unlock(&mhdp->start_lock);
   769	
   770		dev_dbg(mhdp->dev, "DP FW activated\n");
   771	
   772		return 0;
   773	error:
   774		spin_lock(&mhdp->start_lock);
   775		mhdp->hw_state = MHDP_HW_INACTIVE;
   776		spin_unlock(&mhdp->start_lock);
   777	
   778		return ret;
   779	}
   780	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org

[-- Attachment #2: config.gz --]
[-- Type: application/gzip, Size: 75856 bytes --]

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

* Re: [PATCH v7 2/3] drm: bridge: Add support for Cadence MHDP DPI/DP bridge
  2020-07-22  7:40   ` Swapnil Jakhade
@ 2020-07-22 11:51     ` Laurent Pinchart
  -1 siblings, 0 replies; 26+ messages in thread
From: Laurent Pinchart @ 2020-07-22 11:51 UTC (permalink / raw)
  To: Swapnil Jakhade
  Cc: airlied, daniel, robh+dt, a.hajda, narmstrong, jonas,
	jernej.skrabec, dri-devel, devicetree, linux-kernel, mparab,
	yamonkar, tomi.valkeinen, jsarha, nsekhar, praneeth

Hi Swapnil,

Thank you for the patch.

On Wed, Jul 22, 2020 at 09:40:39AM +0200, Swapnil Jakhade wrote:
> Add a new DRM bridge driver for Cadence MHDP DPTX IP used in TI J721e SoC.
> MHDP DPTX IP is the component that complies with VESA DisplayPort (DP) and
> embedded Display Port (eDP) standards. It integrates uCPU running the
> embedded Firmware (FW) interfaced over APB interface.
> 
> Basically, it takes a DPI stream as input and outputs it encoded in DP
> format. Currently, it supports only SST mode.
> 
> Co-developed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
> Co-developed-by: Jyri Sarha <jsarha@ti.com>
> Signed-off-by: Jyri Sarha <jsarha@ti.com>
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
> Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
> ---
>  drivers/gpu/drm/bridge/Kconfig          |   11 +
>  drivers/gpu/drm/bridge/Makefile         |    2 +
>  drivers/gpu/drm/bridge/cdns-mhdp-core.c | 2493 +++++++++++++++++++++++
>  drivers/gpu/drm/bridge/cdns-mhdp-core.h |  396 ++++
>  4 files changed, 2902 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.c
>  create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.h
> 
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index 43271c21d3fc..6a4c324302a8 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -27,6 +27,17 @@ config DRM_CDNS_DSI
>  	  Support Cadence DPI to DSI bridge. This is an internal
>  	  bridge and is meant to be directly embedded in a SoC.
>  
> +config DRM_CDNS_MHDP
> +	tristate "Cadence DPI/DP bridge"
> +	select DRM_KMS_HELPER
> +	select DRM_PANEL_BRIDGE
> +	depends on OF
> +	help
> +	  Support Cadence DPI to DP bridge. This is an internal
> +	  bridge and is meant to be directly embedded in a SoC.
> +	  It takes a DPI stream as input and outputs it encoded
> +	  in DP format.
> +
>  config DRM_CHRONTEL_CH7033
>  	tristate "Chrontel CH7033 Video Encoder"
>  	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index d63d4b7e4347..0080a9f80f29 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -1,5 +1,7 @@
>  # SPDX-License-Identifier: GPL-2.0
>  obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
> +obj-$(CONFIG_DRM_CDNS_MHDP) += cdns-mhdp.o
> +cdns-mhdp-objs := cdns-mhdp-core.o
>  obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
>  obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
>  obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
> diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.c b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
> new file mode 100644
> index 000000000000..b16c5503cef1
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
> @@ -0,0 +1,2493 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Cadence MHDP DP bridge driver.
> + *
> + * Copyright: 2019 Cadence Design Systems, Inc.
> + *
> + * Author: Quentin Schulz <quentin.schulz@free-electrons.com>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/firmware.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/phy/phy.h>
> +#include <linux/phy/phy-dp.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/wait.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_atomic_state_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_dp_helper.h>
> +#include <drm/drm_modeset_helper_vtables.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_probe_helper.h>
> +
> +#include <asm/unaligned.h>
> +
> +#include "cdns-mhdp-core.h"
> +
> +static DECLARE_WAIT_QUEUE_HEAD(fw_load_wq);
> +
> +static int cdns_mhdp_mailbox_read(struct cdns_mhdp_device *mhdp)
> +{
> +	int ret, empty;
> +
> +	WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex));
> +
> +	ret = readx_poll_timeout(readl, mhdp->regs + CDNS_MAILBOX_EMPTY,
> +				 empty, !empty, MAILBOX_RETRY_US,
> +				 MAILBOX_TIMEOUT_US);
> +	if (ret < 0)
> +		return ret;
> +
> +	return readl(mhdp->regs + CDNS_MAILBOX_RX_DATA) & 0xff;
> +}
> +
> +static int cdns_mhdp_mailbox_write(struct cdns_mhdp_device *mhdp, u8 val)
> +{
> +	int ret, full;
> +
> +	WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex));
> +
> +	ret = readx_poll_timeout(readl, mhdp->regs + CDNS_MAILBOX_FULL,
> +				 full, !full, MAILBOX_RETRY_US,
> +				 MAILBOX_TIMEOUT_US);
> +	if (ret < 0)
> +		return ret;
> +
> +	writel(val, mhdp->regs + CDNS_MAILBOX_TX_DATA);
> +
> +	return 0;
> +}
> +
> +static int cdns_mhdp_mailbox_validate_receive(struct cdns_mhdp_device *mhdp,
> +					      u8 module_id, u8 opcode,
> +					      u16 req_size)
> +{
> +	u32 mbox_size, i;
> +	u8 header[4];
> +	int ret;
> +
> +	/* read the header of the message */
> +	for (i = 0; i < 4; i++) {
> +		ret = cdns_mhdp_mailbox_read(mhdp);
> +		if (ret < 0)
> +			return ret;
> +
> +		header[i] = ret;
> +	}
> +
> +	mbox_size = get_unaligned_be16(header + 2);
> +
> +	if (opcode != header[0] || module_id != header[1] ||
> +	    req_size != mbox_size) {
> +		/*
> +		 * If the message in mailbox is not what we want, we need to
> +		 * clear the mailbox by reading its contents.
> +		 */
> +		for (i = 0; i < mbox_size; i++)
> +			if (cdns_mhdp_mailbox_read(mhdp) < 0)
> +				break;
> +
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int cdns_mhdp_mailbox_read_receive(struct cdns_mhdp_device *mhdp,
> +					  u8 *buff, u16 buff_size)
> +{
> +	u32 i;
> +	int ret;
> +
> +	for (i = 0; i < buff_size; i++) {
> +		ret = cdns_mhdp_mailbox_read(mhdp);
> +		if (ret < 0)
> +			return ret;
> +
> +		buff[i] = ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int cdns_mhdp_mailbox_send(struct cdns_mhdp_device *mhdp, u8 module_id,
> +				  u8 opcode, u16 size, u8 *message)
> +{
> +	u8 header[4];
> +	int ret, i;
> +
> +	header[0] = opcode;
> +	header[1] = module_id;
> +	put_unaligned_be16(size, header + 2);
> +
> +	for (i = 0; i < 4; i++) {
> +		ret = cdns_mhdp_mailbox_write(mhdp, header[i]);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	for (i = 0; i < size; i++) {
> +		ret = cdns_mhdp_mailbox_write(mhdp, message[i]);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static
> +int cdns_mhdp_reg_read(struct cdns_mhdp_device *mhdp, u32 addr, u32 *value)
> +{
> +	u8 msg[4], resp[8];
> +	int ret;
> +
> +	put_unaligned_be32(addr, msg);
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_GENERAL,
> +				     GENERAL_REGISTER_READ,
> +				     sizeof(msg), msg);
> +	if (ret)
> +		goto err_reg_read;
> +
> +	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_GENERAL,
> +						 GENERAL_REGISTER_READ,
> +						 sizeof(resp));
> +	if (ret)
> +		goto err_reg_read;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, resp, sizeof(resp));
> +	if (ret)
> +		goto err_reg_read;
> +
> +	/* Returned address value should be the same as requested */
> +	if (memcmp(msg, resp, sizeof(msg))) {
> +		ret = -EINVAL;
> +		goto err_reg_read;
> +	}
> +
> +	*value = get_unaligned_be32(resp + 4);
> +
> +err_reg_read:
> +	mutex_unlock(&mhdp->mbox_mutex);
> +	if (ret) {
> +		DRM_DEV_ERROR(mhdp->dev, "Failed to read register.\n");
> +		*value = 0;
> +	}
> +
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_reg_write(struct cdns_mhdp_device *mhdp, u16 addr, u32 val)
> +{
> +	u8 msg[6];
> +	int ret;
> +
> +	put_unaligned_be16(addr, msg);
> +	put_unaligned_be32(val, msg + 2);
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +				     DPTX_WRITE_REGISTER, sizeof(msg), msg);
> +
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_reg_write_bit(struct cdns_mhdp_device *mhdp, u16 addr,
> +			    u8 start_bit, u8 bits_no, u32 val)
> +{
> +	u8 field[8];
> +	int ret;
> +
> +	put_unaligned_be16(addr, field);
> +	field[2] = start_bit;
> +	field[3] = bits_no;
> +	put_unaligned_be32(val, field + 4);
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +				     DPTX_WRITE_FIELD, sizeof(field), field);
> +
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_dpcd_read(struct cdns_mhdp_device *mhdp,
> +			u32 addr, u8 *data, u16 len)
> +{
> +	u8 msg[5], reg[5];
> +	int ret;
> +
> +	put_unaligned_be16(len, msg);
> +	put_unaligned_be24(addr, msg + 2);
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +				     DPTX_READ_DPCD, sizeof(msg), msg);
> +	if (ret)
> +		goto err_dpcd_read;
> +
> +	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
> +						 DPTX_READ_DPCD,
> +						 sizeof(reg) + len);
> +	if (ret)
> +		goto err_dpcd_read;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
> +	if (ret)
> +		goto err_dpcd_read;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, data, len);
> +
> +err_dpcd_read:
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_dpcd_write(struct cdns_mhdp_device *mhdp, u32 addr, u8 value)
> +{
> +	u8 msg[6], reg[5];
> +	int ret;
> +
> +	put_unaligned_be16(1, msg);
> +	put_unaligned_be24(addr, msg + 2);
> +	msg[5] = value;
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +				     DPTX_WRITE_DPCD, sizeof(msg), msg);
> +	if (ret)
> +		goto err_dpcd_write;
> +
> +	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
> +						 DPTX_WRITE_DPCD, sizeof(reg));
> +	if (ret)
> +		goto err_dpcd_write;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
> +	if (ret)
> +		goto err_dpcd_write;
> +
> +	if (addr != get_unaligned_be24(reg + 2))
> +		ret = -EINVAL;
> +
> +err_dpcd_write:
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	if (ret)
> +		DRM_DEV_ERROR(mhdp->dev, "dpcd write failed: %d\n", ret);
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_set_firmware_active(struct cdns_mhdp_device *mhdp, bool enable)
> +{
> +	u8 msg[5];
> +	int ret, i;
> +
> +	msg[0] = GENERAL_MAIN_CONTROL;
> +	msg[1] = MB_MODULE_ID_GENERAL;
> +	msg[2] = 0;
> +	msg[3] = 1;
> +	msg[4] = enable ? FW_ACTIVE : FW_STANDBY;
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	for (i = 0; i < sizeof(msg); i++) {
> +		ret = cdns_mhdp_mailbox_write(mhdp, msg[i]);
> +		if (ret)
> +			goto err_set_firmware_active;
> +	}
> +
> +	/* read the firmware state */
> +	for (i = 0; i < sizeof(msg); i++)  {
> +		ret = cdns_mhdp_mailbox_read(mhdp);
> +		if (ret < 0)
> +			goto err_set_firmware_active;
> +
> +		msg[i] = ret;
> +	}
> +
> +	ret = 0;
> +
> +err_set_firmware_active:
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	if (ret < 0)
> +		DRM_DEV_ERROR(mhdp->dev, "set firmware active failed\n");
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_get_hpd_status(struct cdns_mhdp_device *mhdp)
> +{
> +	u8 status;
> +	int ret;
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +				     DPTX_HPD_STATE, 0, NULL);
> +	if (ret)
> +		goto err_get_hpd;
> +
> +	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
> +						 DPTX_HPD_STATE,
> +						 sizeof(status));
> +	if (ret)
> +		goto err_get_hpd;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, &status, sizeof(status));
> +	if (ret)
> +		goto err_get_hpd;
> +
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	dev_dbg(mhdp->dev, "%s: %d\n", __func__, status);
> +
> +	return status;
> +
> +err_get_hpd:
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	DRM_DEV_ERROR(mhdp->dev, "get hpd status failed: %d\n", ret);
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_get_edid_block(void *data, u8 *edid,
> +			     unsigned int block, size_t length)
> +{
> +	struct cdns_mhdp_device *mhdp = data;
> +	u8 msg[2], reg[2], i;
> +	int ret;
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	for (i = 0; i < 4; i++) {
> +		msg[0] = block / 2;
> +		msg[1] = block % 2;
> +
> +		ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +					     DPTX_GET_EDID, sizeof(msg), msg);
> +		if (ret)
> +			continue;
> +
> +		ret = cdns_mhdp_mailbox_validate_receive(mhdp,
> +							 MB_MODULE_ID_DP_TX,
> +							 DPTX_GET_EDID,
> +							 sizeof(reg) + length);
> +		if (ret)
> +			continue;
> +
> +		ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
> +		if (ret)
> +			continue;
> +
> +		ret = cdns_mhdp_mailbox_read_receive(mhdp, edid, length);
> +		if (ret)
> +			continue;
> +
> +		if (reg[0] == length && reg[1] == block / 2)
> +			break;
> +	}
> +
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	if (ret)
> +		DRM_DEV_ERROR(mhdp->dev, "get block[%d] edid failed: %d\n",
> +			      block, ret);
> +
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_read_event(struct cdns_mhdp_device *mhdp)
> +{
> +	u8 event = 0;
> +	int ret;
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +				     DPTX_READ_EVENT, 0, NULL);
> +	if (ret)
> +		goto out;
> +
> +	ret = cdns_mhdp_mailbox_validate_receive(mhdp,
> +						 MB_MODULE_ID_DP_TX,
> +						 DPTX_READ_EVENT,
> +						 sizeof(event));
> +	if (ret < 0)
> +		goto out;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, &event,
> +					     sizeof(event));
> +out:
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	dev_dbg(mhdp->dev, "%s: %s%s%s%s\n", __func__,
> +		(event & DPTX_READ_EVENT_HPD_TO_HIGH) ? "TO_HIGH " : "",
> +		(event & DPTX_READ_EVENT_HPD_TO_LOW) ? "TO_LOW " : "",
> +		(event & DPTX_READ_EVENT_HPD_PULSE) ? "PULSE " : "",
> +		(event & DPTX_READ_EVENT_HPD_STATE) ? "HPD_STATE " : "");
> +
> +	return event;
> +}
> +
> +static
> +int cdns_mhdp_adjust_lt(struct cdns_mhdp_device *mhdp,
> +			u8 nlanes, u16 udelay, u8 *lanes_data, u8 *link_status)
> +{
> +	u8 payload[7];
> +	u8 hdr[5]; /* For DPCD read response header */
> +	u32 addr;
> +	u8 const nregs = 6; /* Registers 0x202-0x207 */
> +	int ret;
> +
> +	if (nlanes != 4 && nlanes != 2 && nlanes != 1) {
> +		DRM_DEV_ERROR(mhdp->dev, "invalid number of lanes: %d\n",
> +			      nlanes);
> +		ret = -EINVAL;
> +		goto err_adjust_lt;
> +	}
> +
> +	payload[0] = nlanes;
> +	put_unaligned_be16(udelay, payload + 1);
> +	memcpy(payload + 3, lanes_data, nlanes);
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +				     DPTX_ADJUST_LT,
> +				     sizeof(payload), payload);
> +	if (ret)
> +		goto err_adjust_lt;
> +
> +	/* Yes, read the DPCD read command response */
> +	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
> +						 DPTX_READ_DPCD,
> +						 sizeof(hdr) + nregs);
> +	if (ret)
> +		goto err_adjust_lt;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, hdr, sizeof(hdr));
> +	if (ret)
> +		goto err_adjust_lt;
> +
> +	addr = get_unaligned_be24(hdr + 2);
> +	if (addr != DP_LANE0_1_STATUS)
> +		goto err_adjust_lt;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, link_status, nregs);
> +
> +err_adjust_lt:
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	if (ret)
> +		DRM_DEV_ERROR(mhdp->dev, "Failed to adjust Link Training.\n");
> +
> +	return ret;
> +}
> +
> +/**
> + * cdns_mhdp_link_power_up() - power up a DisplayPort link
> + * @aux: DisplayPort AUX channel
> + * @link: pointer to a structure containing the link configuration
> + *
> + * Returns 0 on success or a negative error code on failure.
> + */
> +static
> +int cdns_mhdp_link_power_up(struct drm_dp_aux *aux, struct cdns_mhdp_link *link)
> +{
> +	u8 value;
> +	int err;
> +
> +	/* DP_SET_POWER register is only available on DPCD v1.1 and later */
> +	if (link->revision < 0x11)
> +		return 0;
> +
> +	err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value);
> +	if (err < 0)
> +		return err;
> +
> +	value &= ~DP_SET_POWER_MASK;
> +	value |= DP_SET_POWER_D0;
> +
> +	err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
> +	if (err < 0)
> +		return err;
> +
> +	/*
> +	 * According to the DP 1.1 specification, a "Sink Device must exit the
> +	 * power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink
> +	 * Control Field" (register 0x600).
> +	 */
> +	usleep_range(1000, 2000);
> +
> +	return 0;
> +}
> +
> +/**
> + * cdns_mhdp_link_power_down() - power down a DisplayPort link
> + * @aux: DisplayPort AUX channel
> + * @link: pointer to a structure containing the link configuration
> + *
> + * Returns 0 on success or a negative error code on failure.
> + */
> +static
> +int cdns_mhdp_link_power_down(struct drm_dp_aux *aux,
> +			      struct cdns_mhdp_link *link)
> +{
> +	u8 value;
> +	int err;
> +
> +	/* DP_SET_POWER register is only available on DPCD v1.1 and later */
> +	if (link->revision < 0x11)
> +		return 0;
> +
> +	err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value);
> +	if (err < 0)
> +		return err;
> +
> +	value &= ~DP_SET_POWER_MASK;
> +	value |= DP_SET_POWER_D3;
> +
> +	err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
> +	if (err < 0)
> +		return err;
> +
> +	return 0;
> +}
> +
> +/**
> + * cdns_mhdp_link_configure() - configure a DisplayPort link
> + * @aux: DisplayPort AUX channel
> + * @link: pointer to a structure containing the link configuration
> + *
> + * Returns 0 on success or a negative error code on failure.
> + */
> +static
> +int cdns_mhdp_link_configure(struct drm_dp_aux *aux,
> +			     struct cdns_mhdp_link *link)
> +{
> +	u8 values[2];
> +	int err;
> +
> +	values[0] = drm_dp_link_rate_to_bw_code(link->rate);
> +	values[1] = link->num_lanes;
> +
> +	if (link->capabilities & DP_LINK_CAP_ENHANCED_FRAMING)
> +		values[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
> +
> +	err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, values, sizeof(values));
> +	if (err < 0)
> +		return err;
> +
> +	return 0;
> +}
> +
> +static unsigned int cdns_mhdp_max_link_rate(struct cdns_mhdp_device *mhdp)
> +{
> +	return min(mhdp->host.link_rate, mhdp->sink.link_rate);
> +}
> +
> +static u8 cdns_mhdp_max_num_lanes(struct cdns_mhdp_device *mhdp)
> +{
> +	return min(mhdp->sink.lanes_cnt, mhdp->host.lanes_cnt);
> +}
> +
> +static u8 cdns_mhdp_eq_training_pattern_supported(struct cdns_mhdp_device *mhdp)
> +{
> +	return fls(mhdp->host.pattern_supp & mhdp->sink.pattern_supp);
> +}
> +
> +static bool cdns_mhdp_get_ssc_supported(struct cdns_mhdp_device *mhdp)
> +{
> +	/* Check if SSC is supported by both sides */
> +	return (mhdp->host.ssc) && (mhdp->sink.ssc);
> +}
> +
> +static int cdns_mhdp_check_fw_version(struct cdns_mhdp_device *mhdp)
> +{
> +	u32 ver_l, ver_h, fw_ver;
> +	u32 lib_l_addr, lib_h_addr, lib_ver;
> +	u32 major_num, minor_num, revision;
> +
> +	ver_l = readl(mhdp->regs + CDNS_VER_L);
> +	ver_h = readl(mhdp->regs + CDNS_VER_H);
> +
> +	fw_ver = (ver_h << 8) | ver_l;
> +
> +	lib_l_addr = readl(mhdp->regs + CDNS_LIB_L_ADDR);
> +	lib_h_addr = readl(mhdp->regs + CDNS_LIB_H_ADDR);
> +
> +	lib_ver = (lib_h_addr << 8) | lib_l_addr;
> +
> +	if (lib_ver < 33984) {
> +		/**
> +		 * Older FW versions with major number 1, used to store FW
> +		 * version information by storing repository revision number
> +		 * in registers. This is for identifying these FW versions.
> +		 */
> +		major_num = 1;
> +		minor_num = 2;
> +		if (fw_ver == 26098)
> +			revision = 15;
> +		else if (lib_ver == 0 && fw_ver == 0)
> +			revision = 17;
> +		else
> +			goto fw_error;
> +	} else {
> +		/* To identify newer FW versions with major number 2 onwards. */
> +		major_num = fw_ver / 10000;
> +		minor_num = (fw_ver / 100) % 100;
> +		revision = (fw_ver % 10000) % 100;
> +	}
> +
> +	dev_dbg(mhdp->dev, "FW version: v%u.%u.%u\n", major_num, minor_num,
> +		revision);
> +	return 0;
> +
> +fw_error:
> +	dev_err(mhdp->dev, "Unsupported FW version\n");
> +	return -ENODEV;
> +}
> +
> +static int cdns_mhdp_fw_activate(const struct firmware *fw,
> +				 struct cdns_mhdp_device *mhdp)
> +{
> +	unsigned int reg;
> +	int ret = 0;
> +
> +	dev_dbg(mhdp->dev, "%s\n", __func__);
> +
> +	if (!fw || !fw->data) {
> +		dev_err(mhdp->dev, "%s: No firmware.\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	spin_lock(&mhdp->start_lock);
> +	if (mhdp->hw_state != MHDP_HW_INACTIVE) {
> +		spin_unlock(&mhdp->start_lock);
> +		if (mhdp->hw_state != MHDP_HW_STOPPED)
> +			dev_err(mhdp->dev, "%s: Bad HW state: %d\n",
> +				__func__, mhdp->hw_state);
> +		return -EBUSY;
> +	}
> +	mhdp->hw_state = MHDP_HW_LOADING;
> +	spin_unlock(&mhdp->start_lock);
> +
> +	/* Release uCPU reset and stall it. */
> +	writel(CDNS_CPU_STALL, mhdp->regs + CDNS_APB_CTRL);
> +
> +	memcpy_toio(mhdp->regs + CDNS_MHDP_IMEM, fw->data, fw->size);
> +
> +	/* Leave debug mode, release stall */
> +	writel(0, mhdp->regs + CDNS_APB_CTRL);
> +
> +	/*
> +	 * Wait for the KEEP_ALIVE "message" on the first 8 bits.
> +	 * Updated each sched "tick" (~2ms)
> +	 */
> +	ret = readl_poll_timeout(mhdp->regs + CDNS_KEEP_ALIVE, reg,
> +				 reg & CDNS_KEEP_ALIVE_MASK, 500,
> +				 CDNS_KEEP_ALIVE_TIMEOUT);
> +	if (ret) {
> +		dev_err(mhdp->dev,
> +			"device didn't give any life sign: reg %d\n", reg);
> +		goto error;
> +	}
> +
> +	ret = cdns_mhdp_check_fw_version(mhdp);
> +	if (ret)
> +		goto error;
> +
> +	/* Init events to 0 as it's not cleared by FW at boot but on read */
> +	readl(mhdp->regs + CDNS_SW_EVENT0);
> +	readl(mhdp->regs + CDNS_SW_EVENT1);
> +	readl(mhdp->regs + CDNS_SW_EVENT2);
> +	readl(mhdp->regs + CDNS_SW_EVENT3);
> +
> +	/* Activate uCPU */
> +	ret = cdns_mhdp_set_firmware_active(mhdp, true);
> +	if (ret) {
> +		dev_err(mhdp->dev, "%s: Failed to activate FW: %d\n",
> +			__func__, ret);
> +		goto error;
> +	}
> +
> +	spin_lock(&mhdp->start_lock);
> +
> +	mhdp->hw_state = MHDP_HW_READY;
> +	wake_up(&fw_load_wq);
> +	/*
> +	 * Here we must keep the lock while enabling the interrupts
> +	 * since it would otherwise be possible that interrupt enable
> +	 * code is executed after the bridge is detached. The similar
> +	 * situation is not possible in attach()/detach() callbacks
> +	 * since the hw_state changes from MHDP_HW_READY to
> +	 * MHDP_HW_STOPPED happens only due to driver removal when
> +	 * bridge should already be detached.
> +	 */
> +	if (mhdp->bridge_attached)
> +		/* enable SW event interrupts */
> +		writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
> +		       mhdp->regs + CDNS_APB_INT_MASK);
> +
> +	spin_unlock(&mhdp->start_lock);
> +
> +	dev_dbg(mhdp->dev, "DP FW activated\n");
> +
> +	return 0;
> +error:
> +	spin_lock(&mhdp->start_lock);
> +	mhdp->hw_state = MHDP_HW_INACTIVE;
> +	spin_unlock(&mhdp->start_lock);
> +
> +	return ret;
> +}
> +
> +static void cdns_mhdp_fw_cb(const struct firmware *fw, void *context)
> +{
> +	struct cdns_mhdp_device *mhdp = context;
> +	bool bridge_attached;
> +	int ret;
> +
> +	dev_dbg(mhdp->dev, "firmware callback\n");
> +
> +	ret = cdns_mhdp_fw_activate(fw, mhdp);
> +
> +	release_firmware(fw);
> +
> +	if (ret)
> +		return;
> +
> +	/*
> +	 *  XXX how to make sure the bridge is still attached when
> +	 *      calling drm_kms_helper_hotplug_event() after releasing
> +	 *      the lock? We should not hold the spin lock when
> +	 *      calling drm_kms_helper_hotplug_event() since it may
> +	 *      cause a dead lock. FB-dev console calls detect from the
> +	 *      same thread just down the call stack started here.
> +	 */
> +	spin_lock(&mhdp->start_lock);
> +	bridge_attached = mhdp->bridge_attached;
> +	spin_unlock(&mhdp->start_lock);
> +	if (bridge_attached)
> +		drm_kms_helper_hotplug_event(mhdp->bridge.dev);
> +}
> +
> +static int cdns_mhdp_load_firmware(struct cdns_mhdp_device *mhdp)
> +{
> +	int ret;
> +
> +	ret = request_firmware_nowait(THIS_MODULE, true, FW_NAME, mhdp->dev,
> +				      GFP_KERNEL, mhdp, cdns_mhdp_fw_cb);
> +	if (ret) {
> +		dev_err(mhdp->dev, "failed to load firmware (%s), ret: %d\n",
> +			FW_NAME, ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static ssize_t cdns_mhdp_transfer(struct drm_dp_aux *aux,
> +				  struct drm_dp_aux_msg *msg)
> +{
> +	struct cdns_mhdp_device *mhdp = dev_get_drvdata(aux->dev);
> +	int ret;
> +
> +	if (msg->request != DP_AUX_NATIVE_WRITE &&
> +	    msg->request != DP_AUX_NATIVE_READ)
> +		return -EOPNOTSUPP;
> +
> +	if (msg->request == DP_AUX_NATIVE_WRITE) {
> +		const u8 *buf = msg->buffer;
> +		int i;
> +
> +		for (i = 0; i < msg->size; ++i) {
> +			ret = cdns_mhdp_dpcd_write(mhdp,
> +						   msg->address + i, buf[i]);
> +			if (!ret)
> +				continue;
> +
> +			DRM_DEV_ERROR(mhdp->dev,
> +				      "Failed to write DPCD addr %u\n",
> +				      msg->address + i);
> +
> +			return ret;
> +		}
> +	} else {
> +		ret = cdns_mhdp_dpcd_read(mhdp, msg->address,
> +					  msg->buffer, msg->size);
> +		if (ret) {
> +			DRM_DEV_ERROR(mhdp->dev,
> +				      "Failed to read DPCD addr %u\n",
> +				      msg->address);
> +
> +			return ret;
> +		}
> +	}
> +
> +	return msg->size;
> +}
> +
> +static int cdns_mhdp_link_training_init(struct cdns_mhdp_device *mhdp)
> +{
> +	u32 reg32;
> +	u8 i;
> +	union phy_configure_opts phy_cfg;
> +	int ret;
> +
> +	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
> +			   DP_TRAINING_PATTERN_DISABLE);
> +
> +	/* Reset PHY configuration */
> +	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1);
> +	if (!mhdp->host.scrambler)
> +		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_ENHNCD,
> +			    mhdp->sink.enhanced & mhdp->host.enhanced);
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_LANE_EN,
> +			    CDNS_DP_LANE_EN_LANES(mhdp->link.num_lanes));
> +
> +	cdns_mhdp_link_configure(&mhdp->aux, &mhdp->link);
> +	phy_cfg.dp.link_rate = (mhdp->link.rate / 100);
> +	phy_cfg.dp.lanes = (mhdp->link.num_lanes);
> +	for (i = 0; i < 4; i++) {
> +		phy_cfg.dp.voltage[i] = 0;
> +		phy_cfg.dp.pre[i] = 0;
> +	}
> +	phy_cfg.dp.ssc = cdns_mhdp_get_ssc_supported(mhdp);
> +	phy_cfg.dp.set_lanes = true;
> +	phy_cfg.dp.set_rate = true;
> +	phy_cfg.dp.set_voltages = true;
> +	ret = phy_configure(mhdp->phy,  &phy_cfg);
> +	if (ret) {
> +		dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
> +			__func__, ret);
> +		return ret;
> +	}
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG,
> +			    CDNS_PHY_COMMON_CONFIG |
> +			    CDNS_PHY_TRAINING_EN |
> +			    CDNS_PHY_TRAINING_TYPE(1) |
> +			    CDNS_PHY_SCRAMBLER_BYPASS);
> +
> +	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
> +			   DP_TRAINING_PATTERN_1 | DP_LINK_SCRAMBLING_DISABLE);
> +
> +	return 0;
> +}
> +
> +static void cdns_mhdp_get_adjust_train(struct cdns_mhdp_device *mhdp,
> +				       u8 link_status[DP_LINK_STATUS_SIZE],
> +				       u8 lanes_data[CDNS_DP_MAX_NUM_LANES],
> +				       union phy_configure_opts *phy_cfg)
> +{
> +	unsigned int i;
> +	u8 adjust, max_pre_emph, max_volt_swing;
> +	u8 set_volt, set_pre;
> +
> +	max_pre_emph = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis)
> +			   << DP_TRAIN_PRE_EMPHASIS_SHIFT;
> +	max_volt_swing = CDNS_VOLT_SWING(mhdp->host.volt_swing);
> +
> +	for (i = 0; i < mhdp->link.num_lanes; i++) {
> +		/* Check if Voltage swing and pre-emphasis are within limits */
> +		adjust = drm_dp_get_adjust_request_voltage(link_status, i);
> +		set_volt = min(adjust, max_volt_swing);
> +
> +		adjust = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
> +		set_pre = min(adjust, max_pre_emph)
> +			  >> DP_TRAIN_PRE_EMPHASIS_SHIFT;
> +
> +		/* Voltage swing level and pre-emphasis level combination is
> +		 * not allowed: leaving pre-emphasis as-is, and adjusting
> +		 * voltage swing.
> +		 */
> +		if (set_volt + set_pre > 3)
> +			set_volt = 3 - set_pre;
> +
> +		phy_cfg->dp.voltage[i] = set_volt;
> +		lanes_data[i] = set_volt;
> +
> +		if (set_volt == max_volt_swing)
> +			lanes_data[i] |= DP_TRAIN_MAX_SWING_REACHED;
> +
> +		phy_cfg->dp.pre[i] = set_pre;
> +		lanes_data[i] |= (set_pre << DP_TRAIN_PRE_EMPHASIS_SHIFT);
> +
> +		if (set_pre == (max_pre_emph >> DP_TRAIN_PRE_EMPHASIS_SHIFT))
> +			lanes_data[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
> +	}
> +}
> +
> +static
> +void cdns_mhdp_set_adjust_request_voltage(u8 link_status[DP_LINK_STATUS_SIZE],
> +					  int lane, u8 volt)
> +{
> +	int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
> +	int s = ((lane & 1) ?
> +		 DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT :
> +		 DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT);
> +	int idx = i - DP_LANE0_1_STATUS;
> +
> +	link_status[idx] &= ~(DP_ADJUST_VOLTAGE_SWING_LANE0_MASK << s);
> +	link_status[idx] |= volt << s;
> +}
> +
> +static
> +void cdns_mhdp_set_adjust_request_pre_emphasis(u8 link_status[DP_LINK_STATUS_SIZE],
> +					       int lane, u8 pre_emphasis)
> +{
> +	int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
> +	int s = ((lane & 1) ?
> +		 DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT :
> +		 DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT);
> +	int idx = i - DP_LANE0_1_STATUS;
> +
> +	link_status[idx] &= ~(DP_ADJUST_PRE_EMPHASIS_LANE0_MASK << s);
> +	link_status[idx] |= pre_emphasis << s;
> +}
> +
> +static void cdns_mhdp_adjust_requested_eq(struct cdns_mhdp_device *mhdp,
> +					  u8 link_status[DP_LINK_STATUS_SIZE])
> +{
> +	u8 max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis);
> +	u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing);
> +	unsigned int i;
> +	u8 volt, pre;
> +
> +	for (i = 0; i < mhdp->link.num_lanes; i++) {
> +		volt = drm_dp_get_adjust_request_voltage(link_status, i);
> +		pre = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
> +		if (volt + pre > 3)
> +			cdns_mhdp_set_adjust_request_voltage(link_status, i,
> +							     3 - pre);
> +		if (mhdp->host.volt_swing & CDNS_FORCE_VOLT_SWING)
> +			cdns_mhdp_set_adjust_request_voltage(link_status, i,
> +							     max_volt);
> +		if (mhdp->host.pre_emphasis & CDNS_FORCE_PRE_EMPHASIS)
> +			cdns_mhdp_set_adjust_request_pre_emphasis(link_status,
> +								  i, max_pre);
> +	}
> +}
> +
> +static void cdns_mhdp_print_lt_status(const char *prefix,
> +				      struct cdns_mhdp_device *mhdp,
> +				      union phy_configure_opts *phy_cfg)
> +{
> +	int i;
> +	char vs_str[8];
> +	char pe_str[8];
> +	char *vs_p, *pe_p;
> +
> +	vs_p = vs_str;
> +	pe_p = pe_str;
> +
> +	for (i = 0; i < mhdp->link.num_lanes; i++) {
> +		if (i != 0) {
> +			*vs_p++ = '/';
> +			*pe_p++ = '/';
> +		}
> +
> +		*vs_p++ = '0' + phy_cfg->dp.voltage[i];
> +		*pe_p++ = '0' + phy_cfg->dp.pre[i];
> +	}
> +
> +	*vs_p = 0;
> +	*pe_p = 0;
> +
> +	dev_dbg(mhdp->dev, "%s, %u lanes, %u Mbps, vs %s, pe %s\n",
> +		prefix,
> +		mhdp->link.num_lanes, mhdp->link.rate / 100,
> +		vs_str, pe_str);
> +}
> +
> +static bool cdns_mhdp_link_training_channel_eq(struct cdns_mhdp_device *mhdp,
> +					       u8 eq_tps,
> +					       unsigned int training_interval)
> +{
> +	u8 lanes_data[CDNS_DP_MAX_NUM_LANES], fail_counter_short = 0;
> +	u8 link_status[DP_LINK_STATUS_SIZE];
> +	u32 reg32;
> +	union phy_configure_opts phy_cfg;
> +	int ret;
> +	bool r;
> +
> +	dev_dbg(mhdp->dev, "Starting EQ phase\n");
> +
> +	/* Enable link training TPS[eq_tps] in PHY */
> +	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_EN |
> +		CDNS_PHY_TRAINING_TYPE(eq_tps);
> +	if (eq_tps != 4)
> +		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
> +
> +	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
> +			   (eq_tps != 4) ? eq_tps | DP_LINK_SCRAMBLING_DISABLE :
> +			   CDNS_DP_TRAINING_PATTERN_4);
> +
> +	drm_dp_dpcd_read_link_status(&mhdp->aux, link_status);
> +
> +	do {
> +		cdns_mhdp_get_adjust_train(mhdp, link_status, lanes_data,
> +					   &phy_cfg);
> +		phy_cfg.dp.lanes = (mhdp->link.num_lanes);
> +		phy_cfg.dp.ssc = cdns_mhdp_get_ssc_supported(mhdp);
> +		phy_cfg.dp.set_lanes = false;
> +		phy_cfg.dp.set_rate = false;
> +		phy_cfg.dp.set_voltages = true;
> +		ret = phy_configure(mhdp->phy,  &phy_cfg);
> +		if (ret) {
> +			dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
> +				__func__, ret);
> +			goto err;
> +		}
> +
> +		cdns_mhdp_adjust_lt(mhdp, mhdp->link.num_lanes,
> +				    training_interval, lanes_data, link_status);
> +
> +		r = drm_dp_clock_recovery_ok(link_status, mhdp->link.num_lanes);
> +		if (!r)
> +			goto err;
> +
> +		if (drm_dp_channel_eq_ok(link_status, mhdp->link.num_lanes)) {
> +			cdns_mhdp_print_lt_status("EQ phase ok", mhdp,
> +						  &phy_cfg);
> +			return true;
> +		}
> +
> +		fail_counter_short++;
> +
> +		cdns_mhdp_adjust_requested_eq(mhdp, link_status);
> +	} while (fail_counter_short < 5);
> +
> +err:
> +	cdns_mhdp_print_lt_status("EQ phase failed", mhdp, &phy_cfg);
> +
> +	return false;
> +}
> +
> +static void cdns_mhdp_adjust_requested_cr(struct cdns_mhdp_device *mhdp,
> +					  u8 link_status[DP_LINK_STATUS_SIZE],
> +					  u8 *req_volt, u8 *req_pre)
> +{
> +	const u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing);
> +	const u8 max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis);
> +	unsigned int i;
> +
> +	for (i = 0; i < mhdp->link.num_lanes; i++) {
> +		u8 val;
> +
> +		val = mhdp->host.volt_swing & CDNS_FORCE_VOLT_SWING ?
> +		      max_volt : req_volt[i];
> +		cdns_mhdp_set_adjust_request_voltage(link_status, i, val);
> +
> +		val = mhdp->host.pre_emphasis & CDNS_FORCE_PRE_EMPHASIS ?
> +		      max_pre : req_pre[i];
> +		cdns_mhdp_set_adjust_request_pre_emphasis(link_status, i, val);
> +	}
> +}
> +
> +static
> +void cdns_mhdp_validate_cr(struct cdns_mhdp_device *mhdp, bool *cr_done,
> +			   bool *same_before_adjust, bool *max_swing_reached,
> +			   u8 before_cr[DP_LINK_STATUS_SIZE],
> +			   u8 after_cr[DP_LINK_STATUS_SIZE], u8 *req_volt,
> +			   u8 *req_pre)
> +{
> +	const u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing);
> +	const u8 max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis);
> +	bool same_pre, same_volt;
> +	unsigned int i;
> +	u8 adjust;
> +
> +	*same_before_adjust = false;
> +	*max_swing_reached = false;
> +	*cr_done = drm_dp_clock_recovery_ok(after_cr, mhdp->link.num_lanes);
> +
> +	for (i = 0; i < mhdp->link.num_lanes; i++) {
> +		adjust = drm_dp_get_adjust_request_voltage(after_cr, i);
> +		req_volt[i] = min(adjust, max_volt);
> +
> +		adjust = drm_dp_get_adjust_request_pre_emphasis(after_cr, i) >>
> +		      DP_TRAIN_PRE_EMPHASIS_SHIFT;
> +		req_pre[i] = min(adjust, max_pre);
> +
> +		same_pre = (before_cr[i] & DP_TRAIN_PRE_EMPHASIS_MASK) ==
> +			   req_pre[i] << DP_TRAIN_PRE_EMPHASIS_SHIFT;
> +		same_volt = (before_cr[i] & DP_TRAIN_VOLTAGE_SWING_MASK) ==
> +			    req_volt[i];
> +		if (same_pre && same_volt)
> +			*same_before_adjust = true;
> +
> +		/* 3.1.5.2 in DP Standard v1.4. Table 3-1 */
> +		if (!*cr_done && req_volt[i] + req_pre[i] >= 3) {
> +			*max_swing_reached = true;
> +			return;
> +		}
> +	}
> +}
> +
> +static bool cdns_mhdp_link_training_cr(struct cdns_mhdp_device *mhdp)
> +{
> +	u8 lanes_data[CDNS_DP_MAX_NUM_LANES],
> +	fail_counter_short = 0, fail_counter_cr_long = 0;
> +	u8 link_status[DP_LINK_STATUS_SIZE];
> +	bool cr_done;
> +	union phy_configure_opts phy_cfg;
> +	int ret;
> +
> +	dev_dbg(mhdp->dev, "Starting CR phase\n");
> +
> +	ret = cdns_mhdp_link_training_init(mhdp);
> +	if (ret)
> +		goto err;
> +
> +	drm_dp_dpcd_read_link_status(&mhdp->aux, link_status);
> +
> +	do {
> +		u8 requested_adjust_volt_swing[CDNS_DP_MAX_NUM_LANES] = {};
> +		u8 requested_adjust_pre_emphasis[CDNS_DP_MAX_NUM_LANES] = {};
> +		bool same_before_adjust, max_swing_reached;
> +
> +		cdns_mhdp_get_adjust_train(mhdp, link_status, lanes_data,
> +					   &phy_cfg);
> +		phy_cfg.dp.lanes = (mhdp->link.num_lanes);
> +		phy_cfg.dp.ssc = cdns_mhdp_get_ssc_supported(mhdp);
> +		phy_cfg.dp.set_lanes = false;
> +		phy_cfg.dp.set_rate = false;
> +		phy_cfg.dp.set_voltages = true;
> +		ret = phy_configure(mhdp->phy,  &phy_cfg);
> +		if (ret) {
> +			dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
> +				__func__, ret);
> +			goto err;
> +		}
> +
> +		cdns_mhdp_adjust_lt(mhdp, mhdp->link.num_lanes, 100,
> +				    lanes_data, link_status);
> +
> +		cdns_mhdp_validate_cr(mhdp, &cr_done, &same_before_adjust,
> +				      &max_swing_reached, lanes_data,
> +				      link_status,
> +				      requested_adjust_volt_swing,
> +				      requested_adjust_pre_emphasis);
> +
> +		if (max_swing_reached) {
> +			dev_err(mhdp->dev, "CR: max swing reached\n");
> +			goto err;
> +		}
> +
> +		if (cr_done) {
> +			cdns_mhdp_print_lt_status("CR phase ok", mhdp,
> +						  &phy_cfg);
> +			return true;
> +		}
> +
> +		/* Not all CR_DONE bits set */
> +		fail_counter_cr_long++;
> +
> +		if (same_before_adjust) {
> +			fail_counter_short++;
> +			continue;
> +		}
> +
> +		fail_counter_short = 0;
> +		/*
> +		 * Voltage swing/pre-emphasis adjust requested
> +		 * during CR phase
> +		 */
> +		cdns_mhdp_adjust_requested_cr(mhdp, link_status,
> +					      requested_adjust_volt_swing,
> +					      requested_adjust_pre_emphasis);
> +	} while (fail_counter_short < 5 && fail_counter_cr_long < 10);
> +
> +err:
> +	cdns_mhdp_print_lt_status("CR phase failed", mhdp, &phy_cfg);
> +
> +	return false;
> +}
> +
> +static void cdns_mhdp_lower_link_rate(struct cdns_mhdp_link *link)
> +{
> +	switch (drm_dp_link_rate_to_bw_code(link->rate)) {
> +	case DP_LINK_BW_2_7:
> +		link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_1_62);
> +		break;
> +	case DP_LINK_BW_5_4:
> +		link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_2_7);
> +		break;
> +	case DP_LINK_BW_8_1:
> +		link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_5_4);
> +		break;
> +	}
> +}
> +
> +static int cdns_mhdp_link_training(struct cdns_mhdp_device *mhdp,
> +				   unsigned int training_interval)
> +{
> +	u32 reg32;
> +	const u8 eq_tps = cdns_mhdp_eq_training_pattern_supported(mhdp);
> +	int ret;
> +
> +	while (1) {
> +		if (!cdns_mhdp_link_training_cr(mhdp)) {
> +			if (drm_dp_link_rate_to_bw_code(mhdp->link.rate) !=
> +			    DP_LINK_BW_1_62) {
> +				dev_dbg(mhdp->dev,
> +					"Reducing link rate during CR phase\n");
> +				cdns_mhdp_lower_link_rate(&mhdp->link);
> +
> +				continue;
> +			} else if (mhdp->link.num_lanes > 1) {
> +				dev_dbg(mhdp->dev,
> +					"Reducing lanes number during CR phase\n");
> +				mhdp->link.num_lanes >>= 1;
> +				mhdp->link.rate = cdns_mhdp_max_link_rate(mhdp);
> +
> +				continue;
> +			}
> +
> +			dev_err(mhdp->dev,
> +				"Link training failed during CR phase\n");
> +			goto err;
> +		}
> +
> +		if (cdns_mhdp_link_training_channel_eq(mhdp, eq_tps,
> +						       training_interval))
> +			break;
> +
> +		if (mhdp->link.num_lanes > 1) {
> +			dev_dbg(mhdp->dev,
> +				"Reducing lanes number during EQ phase\n");
> +			mhdp->link.num_lanes >>= 1;
> +
> +			continue;
> +		} else if (drm_dp_link_rate_to_bw_code(mhdp->link.rate) !=
> +			   DP_LINK_BW_1_62) {
> +			dev_dbg(mhdp->dev,
> +				"Reducing link rate during EQ phase\n");
> +			cdns_mhdp_lower_link_rate(&mhdp->link);
> +			mhdp->link.num_lanes = cdns_mhdp_max_num_lanes(mhdp);
> +
> +			continue;
> +		}
> +
> +		dev_err(mhdp->dev, "Link training failed during EQ phase\n");
> +		goto err;
> +	}
> +
> +	dev_dbg(mhdp->dev, "Link training ok. Lanes: %u, Rate %u Mbps\n",
> +		mhdp->link.num_lanes, mhdp->link.rate / 100);
> +
> +	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
> +			   mhdp->host.scrambler ? 0 :
> +			   DP_LINK_SCRAMBLING_DISABLE);
> +
> +	ret = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &reg32);
> +	if (ret < 0) {
> +		dev_err(mhdp->dev,
> +			"Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n",
> +			ret);
> +		return ret;
> +	}
> +	reg32 &= ~GENMASK(1, 0);
> +	reg32 |= CDNS_DP_NUM_LANES(mhdp->link.num_lanes);
> +	reg32 |= CDNS_DP_WR_FAILING_EDGE_VSYNC;
> +	reg32 |= CDNS_DP_FRAMER_EN;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, reg32);
> +
> +	/* Reset PHY config */
> +	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1);
> +	if (!mhdp->host.scrambler)
> +		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
> +
> +	return 0;
> +err:
> +	/* Reset PHY config */
> +	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1);
> +	if (!mhdp->host.scrambler)
> +		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
> +
> +	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
> +			   DP_TRAINING_PATTERN_DISABLE);
> +
> +	return -EIO;
> +}
> +
> +static u32 cdns_mhdp_get_training_interval_us(struct cdns_mhdp_device *mhdp,
> +					      u32 interval)
> +{
> +	if (interval == 0)
> +		return 400;
> +	if (interval < 5)
> +		return 4000 << (interval - 1);
> +	dev_err(mhdp->dev,
> +		"wrong training interval returned by DPCD: %d\n", interval);
> +	return 0;
> +}
> +
> +static void cdns_mhdp_fill_host_caps(struct cdns_mhdp_device *mhdp)
> +{
> +	unsigned int link_rate;
> +	struct phy_attrs attrs;
> +
> +	/* Get source capabilities based on PHY attributes */
> +	phy_get_attrs(mhdp->phy, &attrs);
> +
> +	mhdp->host.lanes_cnt = attrs.bus_width;
> +	if (!mhdp->host.lanes_cnt)
> +		mhdp->host.lanes_cnt = 4;
> +
> +	link_rate = attrs.max_link_rate;
> +	if (!link_rate)
> +		link_rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_8_1);
> +	else
> +		/* PHY uses Mb/s, DRM uses tens of kb/s. */
> +		link_rate *= 100;
> +
> +	mhdp->host.link_rate = link_rate;
> +	mhdp->host.volt_swing = CDNS_VOLT_SWING(3);
> +	mhdp->host.pre_emphasis = CDNS_PRE_EMPHASIS(3);
> +	mhdp->host.pattern_supp = CDNS_SUPPORT_TPS(1) |
> +				  CDNS_SUPPORT_TPS(2) | CDNS_SUPPORT_TPS(3) |
> +				  CDNS_SUPPORT_TPS(4);
> +	mhdp->host.lane_mapping = CDNS_LANE_MAPPING_NORMAL;
> +	mhdp->host.fast_link = false;
> +	mhdp->host.enhanced = true;
> +	mhdp->host.scrambler = true;
> +	mhdp->host.ssc = false;
> +}
> +
> +static void cdns_mhdp_fill_sink_caps(struct cdns_mhdp_device *mhdp,
> +				     u8 dpcd[DP_RECEIVER_CAP_SIZE])
> +{
> +	mhdp->sink.link_rate = mhdp->link.rate;
> +	mhdp->sink.lanes_cnt = mhdp->link.num_lanes;
> +	mhdp->sink.enhanced = !!(mhdp->link.capabilities &
> +				 DP_LINK_CAP_ENHANCED_FRAMING);
> +
> +	/* Set SSC support */
> +	mhdp->sink.ssc = !!(dpcd[DP_MAX_DOWNSPREAD] &
> +				  DP_MAX_DOWNSPREAD_0_5);
> +
> +	/* Set TPS support */
> +	mhdp->sink.pattern_supp = CDNS_SUPPORT_TPS(1) | CDNS_SUPPORT_TPS(2);
> +	if (drm_dp_tps3_supported(dpcd))
> +		mhdp->sink.pattern_supp |= CDNS_SUPPORT_TPS(3);
> +	if (drm_dp_tps4_supported(dpcd))
> +		mhdp->sink.pattern_supp |= CDNS_SUPPORT_TPS(4);
> +
> +	/* Set fast link support */
> +	mhdp->sink.fast_link = !!(dpcd[DP_MAX_DOWNSPREAD] &
> +				  DP_NO_AUX_HANDSHAKE_LINK_TRAINING);
> +}
> +
> +static int cdns_mhdp_link_up(struct cdns_mhdp_device *mhdp)
> +{
> +	u32 resp;
> +	u8 dpcd[DP_RECEIVER_CAP_SIZE], amp[2];
> +	u8 ext_cap_chk = 0;
> +	unsigned int addr;
> +	int err;
> +
> +	WARN_ON(!mutex_is_locked(&mhdp->link_mutex));
> +
> +	drm_dp_dpcd_readb(&mhdp->aux, DP_TRAINING_AUX_RD_INTERVAL,
> +			  &ext_cap_chk);
> +
> +	if (ext_cap_chk & DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT)
> +		addr = DP_DP13_DPCD_REV;
> +	else
> +		addr = DP_DPCD_REV;
> +
> +	err = drm_dp_dpcd_read(&mhdp->aux, addr, dpcd, DP_RECEIVER_CAP_SIZE);
> +	if (err < 0) {
> +		dev_err(mhdp->dev, "Failed to read receiver capabilities\n");
> +		return err;
> +	}
> +
> +	mhdp->link.revision = dpcd[0];
> +	mhdp->link.rate = drm_dp_bw_code_to_link_rate(dpcd[1]);
> +	mhdp->link.num_lanes = dpcd[2] & DP_MAX_LANE_COUNT_MASK;
> +
> +	if (dpcd[2] & DP_ENHANCED_FRAME_CAP)
> +		mhdp->link.capabilities |= DP_LINK_CAP_ENHANCED_FRAMING;
> +
> +	dev_dbg(mhdp->dev, "Set sink device power state via DPCD\n");
> +	cdns_mhdp_link_power_up(&mhdp->aux, &mhdp->link);
> +
> +	cdns_mhdp_fill_sink_caps(mhdp, dpcd);
> +
> +	mhdp->link.rate = cdns_mhdp_max_link_rate(mhdp);
> +	mhdp->link.num_lanes = cdns_mhdp_max_num_lanes(mhdp);
> +
> +	/* Disable framer for link training */
> +	err = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &resp);
> +	if (err < 0) {
> +		dev_err(mhdp->dev,
> +			"Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n",
> +			err);
> +		return err;
> +	}
> +
> +	resp &= ~CDNS_DP_FRAMER_EN;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, resp);
> +
> +	/* Spread AMP if required, enable 8b/10b coding */
> +	amp[0] = cdns_mhdp_get_ssc_supported(mhdp) ? DP_SPREAD_AMP_0_5 : 0;
> +	amp[1] = DP_SET_ANSI_8B10B;
> +	drm_dp_dpcd_write(&mhdp->aux, DP_DOWNSPREAD_CTRL, amp, 2);
> +
> +	if (mhdp->host.fast_link & mhdp->sink.fast_link) {
> +		dev_err(mhdp->dev, "fastlink not supported\n");
> +		err = -EOPNOTSUPP;
> +		goto error;
> +	} else {
> +		const u32 interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] &
> +				     DP_TRAINING_AUX_RD_MASK;
> +		const u32 interval_us = cdns_mhdp_get_training_interval_us(mhdp,
> +									   interval);
> +		if (!interval_us ||
> +		    cdns_mhdp_link_training(mhdp, interval_us)) {
> +			dev_err(mhdp->dev, "Link training failed. Exiting.\n");
> +			err = -EIO;
> +			goto error;
> +		}
> +	}
> +
> +	mhdp->link_up = true;
> +
> +	return 0;
> +error:
> +	return err;
> +}
> +
> +static void cdns_mhdp_link_down(struct cdns_mhdp_device *mhdp)
> +{
> +	WARN_ON(!mutex_is_locked(&mhdp->link_mutex));
> +
> +	if (mhdp->plugged)
> +		cdns_mhdp_link_power_down(&mhdp->aux, &mhdp->link);
> +
> +	mhdp->link_up = false;
> +}
> +
> +static int cdns_mhdp_get_modes(struct drm_connector *connector)
> +{
> +	struct cdns_mhdp_device *mhdp = connector_to_mhdp(connector);
> +	struct edid *edid;
> +	int num_modes;
> +	int ret;
> +
> +	if (!mhdp->plugged)
> +		return 0;
> +
> +	mutex_lock(&mhdp->link_mutex);
> +
> +	if (!mhdp->link_up) {
> +		ret = cdns_mhdp_link_up(mhdp);
> +		if (ret < 0) {
> +			mutex_unlock(&mhdp->link_mutex);
> +			return 0;
> +		}
> +	}
> +	mutex_unlock(&mhdp->link_mutex);
> +
> +	edid = drm_do_get_edid(connector, cdns_mhdp_get_edid_block, mhdp);
> +	if (!edid) {
> +		DRM_DEV_ERROR(mhdp->dev, "Failed to read EDID\n");
> +
> +		return 0;
> +	}
> +
> +	drm_connector_update_edid_property(connector, edid);
> +	num_modes = drm_add_edid_modes(connector, edid);
> +	kfree(edid);
> +
> +	/*
> +	 * HACK: Warn about unsupported display formats until we deal
> +	 *       with them correctly.
> +	 */
> +	if (connector->display_info.color_formats &&
> +	    !(connector->display_info.color_formats &
> +	      mhdp->display_fmt.color_format))
> +		dev_warn(mhdp->dev,
> +			 "%s: No supported color_format found (0x%08x)\n",
> +			__func__, connector->display_info.color_formats);
> +
> +	if (connector->display_info.bpc &&
> +	    connector->display_info.bpc < mhdp->display_fmt.bpc)
> +		dev_warn(mhdp->dev, "%s: Display bpc only %d < %d\n",
> +			 __func__, connector->display_info.bpc,
> +			 mhdp->display_fmt.bpc);
> +
> +	return num_modes;
> +}
> +
> +static int cdns_mhdp_detect(struct drm_connector *conn,
> +			    struct drm_modeset_acquire_ctx *ctx,
> +			    bool force)
> +{
> +	struct cdns_mhdp_device *mhdp = connector_to_mhdp(conn);
> +
> +	dev_dbg(mhdp->dev, "%s: %d\n", __func__, mhdp->plugged);
> +
> +	if (mhdp->plugged)
> +		return connector_status_connected;
> +	else
> +		return connector_status_disconnected;
> +}
> +
> +static u32 cdns_mhdp_get_bpp(struct cdns_mhdp_display_fmt *fmt)
> +{
> +	u32 bpp;
> +
> +	if (fmt->y_only)
> +		return fmt->bpc;
> +
> +	switch (fmt->color_format) {
> +	case DRM_COLOR_FORMAT_RGB444:
> +	case DRM_COLOR_FORMAT_YCRCB444:
> +		bpp = fmt->bpc * 3;
> +		break;
> +	case DRM_COLOR_FORMAT_YCRCB422:
> +		bpp = fmt->bpc * 2;
> +		break;
> +	case DRM_COLOR_FORMAT_YCRCB420:
> +		bpp = fmt->bpc * 3 / 2;
> +		break;
> +	default:
> +		bpp = fmt->bpc * 3;
> +		WARN_ON(1);
> +	}
> +	return bpp;
> +}
> +
> +static
> +bool cdns_mhdp_bandwidth_ok(struct cdns_mhdp_device *mhdp,
> +			    const struct drm_display_mode *mode,
> +			    int lanes, int rate)
> +{
> +	u32 max_bw, req_bw, bpp;
> +
> +	bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt);
> +	req_bw = mode->clock * bpp / 8;
> +	max_bw = lanes * rate;
> +	if (req_bw > max_bw) {
> +		dev_dbg(mhdp->dev,
> +			"Unsupported Mode: %s, Req BW: %u, Available Max BW:%u\n",
> +			mode->name, req_bw, max_bw);
> +
> +		return false;
> +	}
> +
> +	return true;
> +}
> +
> +static
> +enum drm_mode_status cdns_mhdp_mode_valid(struct drm_connector *conn,
> +					  struct drm_display_mode *mode)
> +{
> +	struct cdns_mhdp_device *mhdp = connector_to_mhdp(conn);
> +
> +	if (!cdns_mhdp_bandwidth_ok(mhdp, mode, mhdp->link.num_lanes,
> +				    mhdp->link.rate))
> +		return MODE_CLOCK_HIGH;
> +
> +	return MODE_OK;
> +}
> +
> +static const struct drm_connector_helper_funcs cdns_mhdp_conn_helper_funcs = {
> +	.detect_ctx = cdns_mhdp_detect,
> +	.get_modes = cdns_mhdp_get_modes,
> +	.mode_valid = cdns_mhdp_mode_valid,
> +};
> +
> +static const struct drm_connector_funcs cdns_mhdp_conn_funcs = {
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +	.reset = drm_atomic_helper_connector_reset,
> +	.destroy = drm_connector_cleanup,
> +};
> +
> +static int cdns_mhdp_attach(struct drm_bridge *bridge,
> +			    enum drm_bridge_attach_flags flags)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
> +	u32 bus_format = MEDIA_BUS_FMT_RGB121212_1X36;
> +	struct drm_connector *conn = &mhdp->connector;
> +	bool hw_ready;
> +	int ret;
> +
> +	dev_dbg(mhdp->dev, "%s\n", __func__);
> +
> +	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
> +		DRM_ERROR("Fix bridge driver to make connector optional!");
> +		return -EINVAL;
> +	}

For new bridge drivers, the connector needs to be made optional.

> +
> +	if (&mhdp->bridge != bridge)
> +		return -ENODEV;
> +
> +	conn->polled = DRM_CONNECTOR_POLL_HPD;
> +
> +	ret = drm_connector_init(bridge->dev, conn, &cdns_mhdp_conn_funcs,
> +				 DRM_MODE_CONNECTOR_DisplayPort);
> +	if (ret) {
> +		dev_err(mhdp->dev, "failed to init connector\n");
> +		return ret;
> +	}
> +
> +	drm_connector_helper_add(conn, &cdns_mhdp_conn_helper_funcs);
> +
> +	ret = drm_display_info_set_bus_formats(&conn->display_info,
> +					       &bus_format, 1);
> +	if (ret)
> +		return ret;
> +
> +	conn->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH;
> +
> +	ret = drm_connector_attach_encoder(conn, bridge->encoder);
> +	if (ret) {
> +		dev_err(mhdp->dev, "failed to attach connector to encoder\n");
> +		return ret;
> +	}
> +
> +	spin_lock(&mhdp->start_lock);
> +
> +	mhdp->bridge_attached = true;
> +	hw_ready = mhdp->hw_state == MHDP_HW_READY;
> +
> +	spin_unlock(&mhdp->start_lock);
> +
> +	if (hw_ready)
> +		/* enable SW event interrupts */
> +		writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
> +		       mhdp->regs + CDNS_APB_INT_MASK);
> +
> +	return 0;
> +}
> +
> +static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp,
> +				      const struct drm_display_mode *mode)
> +{
> +	unsigned int dp_framer_sp = 0, msa_horizontal_1,
> +		msa_vertical_1, bnd_hsync2vsync, hsync2vsync_pol_ctrl,
> +		misc0 = 0, misc1 = 0, pxl_repr,
> +		front_porch, back_porch, msa_h0, msa_v0, hsync, vsync,
> +		dp_vertical_1;
> +	u32 bpp, bpc, pxlfmt;
> +	u32 framer;
> +	u8 stream_id = mhdp->stream_id;
> +	int ret;
> +
> +	pxlfmt = mhdp->display_fmt.color_format;
> +	bpc = mhdp->display_fmt.bpc;
> +
> +	/* If YCBCR supported and stream not SD, use ITU709
> +	 * Need to handle ITU version with YCBCR420 when supported
> +	 */
> +	if ((pxlfmt == DRM_COLOR_FORMAT_YCRCB444 ||
> +	     pxlfmt == DRM_COLOR_FORMAT_YCRCB422) && mode->crtc_vdisplay >= 720)
> +		misc0 = DP_YCBCR_COEFFICIENTS_ITU709;
> +
> +	bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt);
> +
> +	switch (pxlfmt) {
> +	case DRM_COLOR_FORMAT_RGB444:
> +		pxl_repr = CDNS_DP_FRAMER_RGB << CDNS_DP_FRAMER_PXL_FORMAT;
> +		misc0 |= DP_COLOR_FORMAT_RGB;
> +		break;
> +	case DRM_COLOR_FORMAT_YCRCB444:
> +		pxl_repr = CDNS_DP_FRAMER_YCBCR444 << CDNS_DP_FRAMER_PXL_FORMAT;
> +		misc0 |= DP_COLOR_FORMAT_YCbCr444 | DP_TEST_DYNAMIC_RANGE_CEA;
> +		break;
> +	case DRM_COLOR_FORMAT_YCRCB422:
> +		pxl_repr = CDNS_DP_FRAMER_YCBCR422 << CDNS_DP_FRAMER_PXL_FORMAT;
> +		misc0 |= DP_COLOR_FORMAT_YCbCr422 | DP_TEST_DYNAMIC_RANGE_CEA;
> +		break;
> +	case DRM_COLOR_FORMAT_YCRCB420:
> +		pxl_repr = CDNS_DP_FRAMER_YCBCR420 << CDNS_DP_FRAMER_PXL_FORMAT;
> +		break;
> +	default:
> +		pxl_repr = CDNS_DP_FRAMER_Y_ONLY << CDNS_DP_FRAMER_PXL_FORMAT;
> +	}
> +
> +	switch (bpc) {
> +	case 6:
> +		misc0 |= DP_TEST_BIT_DEPTH_6;
> +		pxl_repr |= CDNS_DP_FRAMER_6_BPC;
> +		break;
> +	case 8:
> +		misc0 |= DP_TEST_BIT_DEPTH_8;
> +		pxl_repr |= CDNS_DP_FRAMER_8_BPC;
> +		break;
> +	case 10:
> +		misc0 |= DP_TEST_BIT_DEPTH_10;
> +		pxl_repr |= CDNS_DP_FRAMER_10_BPC;
> +		break;
> +	case 12:
> +		misc0 |= DP_TEST_BIT_DEPTH_12;
> +		pxl_repr |= CDNS_DP_FRAMER_12_BPC;
> +		break;
> +	case 16:
> +		misc0 |= DP_TEST_BIT_DEPTH_16;
> +		pxl_repr |= CDNS_DP_FRAMER_16_BPC;
> +		break;
> +	}
> +
> +	bnd_hsync2vsync = CDNS_IP_BYPASS_V_INTERFACE;
> +	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
> +		bnd_hsync2vsync |= CDNS_IP_DET_INTERLACE_FORMAT;
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_BND_HSYNC2VSYNC(stream_id),
> +			    bnd_hsync2vsync);
> +
> +	hsync2vsync_pol_ctrl = 0;
> +	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
> +		hsync2vsync_pol_ctrl |= CDNS_H2V_HSYNC_POL_ACTIVE_LOW;
> +	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
> +		hsync2vsync_pol_ctrl |= CDNS_H2V_VSYNC_POL_ACTIVE_LOW;
> +	cdns_mhdp_reg_write(mhdp, CDNS_HSYNC2VSYNC_POL_CTRL(stream_id),
> +			    hsync2vsync_pol_ctrl);
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_PXL_REPR(stream_id), pxl_repr);
> +
> +	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
> +		dp_framer_sp |= CDNS_DP_FRAMER_INTERLACE;
> +	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
> +		dp_framer_sp |= CDNS_DP_FRAMER_HSYNC_POL_LOW;
> +	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
> +		dp_framer_sp |= CDNS_DP_FRAMER_VSYNC_POL_LOW;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_SP(stream_id), dp_framer_sp);
> +
> +	front_porch = mode->crtc_hsync_start - mode->crtc_hdisplay;
> +	back_porch = mode->crtc_htotal - mode->crtc_hsync_end;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRONT_BACK_PORCH(stream_id),
> +			    CDNS_DP_FRONT_PORCH(front_porch) |
> +			    CDNS_DP_BACK_PORCH(back_porch));
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_BYTE_COUNT(stream_id),
> +			    mode->crtc_hdisplay * bpp / 8);
> +
> +	msa_h0 = mode->crtc_htotal - mode->crtc_hsync_start;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_HORIZONTAL_0(stream_id),
> +			    CDNS_DP_MSAH0_H_TOTAL(mode->crtc_htotal) |
> +			    CDNS_DP_MSAH0_HSYNC_START(msa_h0));
> +
> +	hsync = mode->crtc_hsync_end - mode->crtc_hsync_start;
> +	msa_horizontal_1 = CDNS_DP_MSAH1_HSYNC_WIDTH(hsync) |
> +			   CDNS_DP_MSAH1_HDISP_WIDTH(mode->crtc_hdisplay);
> +	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
> +		msa_horizontal_1 |= CDNS_DP_MSAH1_HSYNC_POL_LOW;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_HORIZONTAL_1(stream_id),
> +			    msa_horizontal_1);
> +
> +	msa_v0 = mode->crtc_vtotal - mode->crtc_vsync_start;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_VERTICAL_0(stream_id),
> +			    CDNS_DP_MSAV0_V_TOTAL(mode->crtc_vtotal) |
> +			    CDNS_DP_MSAV0_VSYNC_START(msa_v0));
> +
> +	vsync = mode->crtc_vsync_end - mode->crtc_vsync_start;
> +	msa_vertical_1 = CDNS_DP_MSAV1_VSYNC_WIDTH(vsync) |
> +			 CDNS_DP_MSAV1_VDISP_WIDTH(mode->crtc_vdisplay);
> +	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
> +		msa_vertical_1 |= CDNS_DP_MSAV1_VSYNC_POL_LOW;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_VERTICAL_1(stream_id),
> +			    msa_vertical_1);
> +
> +	if ((mode->flags & DRM_MODE_FLAG_INTERLACE) &&
> +	    mode->crtc_vtotal % 2 == 0)
> +		misc1 = DP_TEST_INTERLACED;
> +	if (mhdp->display_fmt.y_only)
> +		misc1 |= CDNS_DP_TEST_COLOR_FORMAT_RAW_Y_ONLY;
> +	/* Use VSC SDP for Y420 */
> +	if (pxlfmt == DRM_COLOR_FORMAT_YCRCB420)
> +		misc1 = CDNS_DP_TEST_VSC_SDP;
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_MISC(stream_id),
> +			    misc0 | (misc1 << 8));
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_HORIZONTAL(stream_id),
> +			    CDNS_DP_H_HSYNC_WIDTH(hsync) |
> +			    CDNS_DP_H_H_TOTAL(mode->crtc_hdisplay));
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_VERTICAL_0(stream_id),
> +			    CDNS_DP_V0_VHEIGHT(mode->crtc_vdisplay) |
> +			    CDNS_DP_V0_VSTART(msa_v0));
> +
> +	dp_vertical_1 = CDNS_DP_V1_VTOTAL(mode->crtc_vtotal);
> +	if ((mode->flags & DRM_MODE_FLAG_INTERLACE) &&
> +	    mode->crtc_vtotal % 2 == 0)
> +		dp_vertical_1 |= CDNS_DP_V1_VTOTAL_EVEN;
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_VERTICAL_1(stream_id), dp_vertical_1);
> +
> +	cdns_mhdp_reg_write_bit(mhdp, CDNS_DP_VB_ID(stream_id), 2, 1,
> +				(mode->flags & DRM_MODE_FLAG_INTERLACE) ?
> +				CDNS_DP_VB_ID_INTERLACED : 0);
> +
> +	ret = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &framer);
> +	if (ret < 0) {
> +		dev_err(mhdp->dev,
> +			"Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n",
> +			ret);
> +		return;
> +	}
> +	framer |= CDNS_DP_FRAMER_EN;
> +	framer &= ~CDNS_DP_NO_VIDEO_MODE;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, framer);
> +}
> +
> +static void cdns_mhdp_sst_enable(struct cdns_mhdp_device *mhdp,
> +				 const struct drm_display_mode *mode,
> +				 struct drm_bridge_state *bridge_state)
> +{
> +	u32 vs, tu_size, line_thresh = 0;
> +	struct cdns_mhdp_bridge_state *state;
> +
> +	state = to_cdns_mhdp_bridge_state(bridge_state);
> +
> +	vs = state->vs;
> +	tu_size = state->tu_size;
> +	line_thresh = state->line_thresh;
> +
> +	mhdp->stream_id = 0;
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_TU,
> +			    CDNS_DP_FRAMER_TU_VS(vs) |
> +			    CDNS_DP_FRAMER_TU_SIZE(tu_size) |
> +			    CDNS_DP_FRAMER_TU_CNT_RST_EN);
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_LINE_THRESH(0),
> +			    line_thresh & GENMASK(5, 0));
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_STREAM_CONFIG_2(0),
> +			    CDNS_DP_SC2_TU_VS_DIFF((tu_size - vs > 3) ?
> +						   0 : tu_size - vs));
> +
> +	cdns_mhdp_configure_video(mhdp, mode);
> +}
> +
> +static void cdns_mhdp_atomic_enable(struct drm_bridge *bridge,
> +				    struct drm_bridge_state *bridge_state)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
> +	struct drm_atomic_state *state = bridge_state->base.state;
> +	struct cdns_mhdp_bridge_state *mhdp_state;
> +	struct drm_crtc_state *crtc_state;
> +	struct drm_connector *connector;
> +	struct drm_connector_state *conn_state;
> +	struct drm_bridge_state *new_state;
> +	const struct drm_display_mode *mode;
> +	u32 resp;
> +	int ret;
> +
> +	dev_dbg(mhdp->dev, "bridge enable\n");
> +
> +	mutex_lock(&mhdp->link_mutex);
> +
> +	if (mhdp->plugged && !mhdp->link_up) {
> +		ret = cdns_mhdp_link_up(mhdp);
> +		if (ret < 0)
> +			goto err_enable;
> +	}
> +
> +	if (mhdp->ops && mhdp->ops->enable)
> +		mhdp->ops->enable(mhdp);
> +
> +	/* Enable VIF clock for stream 0 */
> +	ret = cdns_mhdp_reg_read(mhdp, CDNS_DPTX_CAR, &resp);
> +	if (ret < 0) {
> +		dev_err(mhdp->dev, "Failed to read CDNS_DPTX_CAR %d\n", ret);
> +		goto err_enable;
> +	}
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_CAR,
> +			    resp | CDNS_VIF_CLK_EN | CDNS_VIF_CLK_RSTN);
> +
> +	connector = drm_atomic_get_new_connector_for_encoder(state,
> +							     bridge->encoder);
> +	if (WARN_ON(!connector))
> +		goto err_enable;
> +
> +	conn_state = drm_atomic_get_new_connector_state(state, connector);
> +	if (WARN_ON(!conn_state))
> +		goto err_enable;
> +
> +	crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
> +	if (WARN_ON(!crtc_state))
> +		goto err_enable;
> +
> +	mode = &crtc_state->adjusted_mode;
> +
> +	new_state = drm_atomic_get_new_bridge_state(state, bridge);
> +	if (WARN_ON(!new_state))
> +		goto err_enable;
> +
> +	cdns_mhdp_sst_enable(mhdp, mode, new_state);
> +
> +	mhdp_state = to_cdns_mhdp_bridge_state(new_state);
> +
> +	mhdp_state->current_mode = drm_mode_duplicate(bridge->dev, mode);
> +	drm_mode_set_name(mhdp_state->current_mode);
> +
> +	dev_dbg(mhdp->dev, "%s: Enabling mode %s\n", __func__, mode->name);
> +
> +	mhdp->bridge_enabled = true;
> +
> +err_enable:
> +	mutex_unlock(&mhdp->link_mutex);
> +}
> +
> +static void cdns_mhdp_atomic_disable(struct drm_bridge *bridge,
> +				     struct drm_bridge_state *bridge_state)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
> +	u32 resp;
> +
> +	dev_dbg(mhdp->dev, "%s\n", __func__);
> +
> +	mutex_lock(&mhdp->link_mutex);
> +
> +	mhdp->bridge_enabled = false;
> +	cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &resp);
> +	resp &= ~CDNS_DP_FRAMER_EN;
> +	resp |= CDNS_DP_NO_VIDEO_MODE;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, resp);
> +
> +	cdns_mhdp_link_down(mhdp);
> +
> +	/* Disable VIF clock for stream 0 */
> +	cdns_mhdp_reg_read(mhdp, CDNS_DPTX_CAR, &resp);
> +	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_CAR,
> +			    resp & ~(CDNS_VIF_CLK_EN | CDNS_VIF_CLK_RSTN));
> +
> +	if (mhdp->ops && mhdp->ops->disable)
> +		mhdp->ops->disable(mhdp);
> +
> +	mutex_unlock(&mhdp->link_mutex);
> +}
> +
> +static void cdns_mhdp_detach(struct drm_bridge *bridge)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
> +
> +	dev_dbg(mhdp->dev, "%s\n", __func__);
> +
> +	spin_lock(&mhdp->start_lock);
> +
> +	mhdp->bridge_attached = false;
> +
> +	spin_unlock(&mhdp->start_lock);
> +
> +	writel(~0, mhdp->regs + CDNS_APB_INT_MASK);
> +}
> +
> +static struct drm_bridge_state *
> +cdns_mhdp_bridge_atomic_duplicate_state(struct drm_bridge *bridge)
> +{
> +	struct cdns_mhdp_bridge_state *state;
> +
> +	state = kzalloc(sizeof(*state), GFP_KERNEL);
> +	if (!state)
> +		return NULL;
> +
> +	__drm_atomic_helper_bridge_duplicate_state(bridge, &state->base);
> +
> +	return &state->base;
> +}
> +
> +static void
> +cdns_mhdp_bridge_atomic_destroy_state(struct drm_bridge *bridge,
> +				      struct drm_bridge_state *state)
> +{
> +	struct cdns_mhdp_bridge_state *cdns_mhdp_state;
> +
> +	cdns_mhdp_state = to_cdns_mhdp_bridge_state(state);
> +
> +	kfree(cdns_mhdp_state);
> +}
> +
> +static struct drm_bridge_state *
> +cdns_mhdp_bridge_atomic_reset(struct drm_bridge *bridge)
> +{
> +	struct cdns_mhdp_bridge_state *cdns_mhdp_state;
> +
> +	cdns_mhdp_state = kzalloc(sizeof(*cdns_mhdp_state), GFP_KERNEL);
> +	if (!cdns_mhdp_state)
> +		return NULL;
> +
> +	 __drm_atomic_helper_bridge_reset(bridge, &cdns_mhdp_state->base);
> +
> +	return &cdns_mhdp_state->base;
> +}
> +
> +static int cdns_mhdp_validate_mode_params(struct cdns_mhdp_device *mhdp,
> +					  const struct drm_display_mode *mode,
> +					  struct drm_bridge_state *bridge_state)
> +{
> +	u32 tu_size = 30, line_thresh1, line_thresh2, line_thresh = 0;
> +	u32 rate, vs, vs_f, required_bandwidth, available_bandwidth;
> +	struct cdns_mhdp_bridge_state *state;
> +	u32 bpp, bpc, pxlfmt;
> +	int pxlclock, ret;
> +
> +	state = to_cdns_mhdp_bridge_state(bridge_state);
> +	pxlfmt = mhdp->display_fmt.color_format;
> +	bpc = mhdp->display_fmt.bpc;
> +
> +	pxlclock = mode->crtc_clock;
> +
> +	rate = mhdp->link.rate / 1000;
> +
> +	bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt);
> +
> +	if (!cdns_mhdp_bandwidth_ok(mhdp, mode, mhdp->link.num_lanes,
> +				    mhdp->link.rate)) {
> +		dev_err(mhdp->dev, "%s: Not enough BW for %s (%u lanes at %u Mbps)\n",
> +			__func__, mode->name, mhdp->link.num_lanes,
> +			mhdp->link.rate / 100);
> +		ret = -EINVAL;
> +		goto err_tu_size;
> +	}
> +
> +	/* find optimal tu_size */
> +	required_bandwidth = pxlclock * bpp / 8;
> +	available_bandwidth = mhdp->link.num_lanes * rate;
> +	do {
> +		tu_size += 2;
> +
> +		vs_f = tu_size * required_bandwidth / available_bandwidth;
> +		vs = vs_f / 1000;
> +		vs_f = vs_f % 1000;
> +		/* Downspreading is unused currently */
> +	} while ((vs == 1 || ((vs_f > 850 || vs_f < 100) && vs_f != 0) ||
> +		 tu_size - vs < 2) && tu_size < 64);
> +
> +	if (vs > 64) {
> +		dev_err(mhdp->dev,
> +			"%s: No space for framing %s (%u lanes at %u Mbps)\n",
> +			__func__, mode->name, mhdp->link.num_lanes,
> +			mhdp->link.rate / 100);
> +		ret = -EINVAL;
> +		goto err_tu_size;
> +	}
> +
> +	line_thresh1 = ((vs + 1) << 5) * 8 / bpp;
> +	line_thresh2 = (pxlclock << 5) / 1000 / rate * (vs + 1) - (1 << 5);
> +	line_thresh = line_thresh1 - line_thresh2 / mhdp->link.num_lanes;
> +	line_thresh = (line_thresh >> 5) + 2;
> +
> +	state->vs = vs;
> +	state->tu_size = tu_size;
> +	state->line_thresh = line_thresh;
> +
> +	return 0;
> +
> +err_tu_size:
> +	return ret;
> +}
> +
> +static int cdns_mhdp_atomic_check(struct drm_bridge *bridge,
> +				  struct drm_bridge_state *bridge_state,
> +				  struct drm_crtc_state *crtc_state,
> +				  struct drm_connector_state *conn_state)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
> +	const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
> +	int ret;
> +
> +	if (!mhdp->plugged)
> +		return 0;
> +
> +	mutex_lock(&mhdp->link_mutex);
> +
> +	if (!mhdp->link_up) {
> +		ret = cdns_mhdp_link_up(mhdp);
> +		if (ret < 0)
> +			goto err_check;
> +	}
> +
> +	ret = cdns_mhdp_validate_mode_params(mhdp, mode, bridge_state);
> +
> +err_check:
> +	mutex_unlock(&mhdp->link_mutex);
> +	return ret;
> +}
> +
> +static const struct drm_bridge_funcs cdns_mhdp_bridge_funcs = {
> +	.atomic_enable = cdns_mhdp_atomic_enable,
> +	.atomic_disable = cdns_mhdp_atomic_disable,
> +	.atomic_check = cdns_mhdp_atomic_check,
> +	.attach = cdns_mhdp_attach,
> +	.detach = cdns_mhdp_detach,
> +	.atomic_duplicate_state = cdns_mhdp_bridge_atomic_duplicate_state,
> +	.atomic_destroy_state = cdns_mhdp_bridge_atomic_destroy_state,
> +	.atomic_reset = cdns_mhdp_bridge_atomic_reset,
> +};
> +
> +static bool cdns_mhdp_detect_hpd(struct cdns_mhdp_device *mhdp, bool *hpd_pulse)
> +{
> +	int hpd_event, hpd_status;
> +
> +	*hpd_pulse = false;
> +
> +	hpd_event = cdns_mhdp_read_event(mhdp);
> +
> +	/* Getting event bits failed, bail out */
> +	if (hpd_event < 0) {
> +		dev_warn(mhdp->dev, "%s: read event failed: %d\n",
> +			 __func__, hpd_event);
> +		return false;
> +	}
> +
> +	hpd_status = cdns_mhdp_get_hpd_status(mhdp);
> +	if (hpd_status < 0) {
> +		dev_warn(mhdp->dev, "%s: get hpd status failed: %d\n",
> +			 __func__, hpd_status);
> +		return false;
> +	}
> +
> +	if (hpd_event & DPTX_READ_EVENT_HPD_PULSE)
> +		*hpd_pulse = true;
> +
> +	return !!hpd_status;
> +}
> +
> +static void cdns_mhdp_update_link_status(struct cdns_mhdp_device *mhdp)
> +{
> +	bool hpd_pulse;
> +	int ret;
> +	struct drm_bridge_state *state;
> +	struct cdns_mhdp_bridge_state *cdns_bridge_state;
> +	struct drm_connector *conn = &mhdp->connector;
> +	struct drm_display_mode *current_mode;
> +	bool old_plugged = mhdp->plugged;
> +	u8 status[DP_LINK_STATUS_SIZE];
> +
> +	mhdp->plugged = cdns_mhdp_detect_hpd(mhdp, &hpd_pulse);
> +
> +	mutex_lock(&mhdp->link_mutex);
> +
> +	if (!mhdp->plugged) {
> +		cdns_mhdp_link_down(mhdp);
> +		goto err;
> +	}
> +
> +	/*
> +	 * If we get a HPD pulse event and we were and still are connected,
> +	 * check the link status. If link status is ok, there's nothing to do
> +	 * as we don't handle DP interrupts. If link status is bad, continue
> +	 * with full link setup.
> +	 */
> +	if (hpd_pulse && old_plugged == mhdp->plugged) {
> +		ret = drm_dp_dpcd_read_link_status(&mhdp->aux, status);
> +
> +		/*
> +		 * If everything looks fine, just return, as we don't handle
> +		 * DP IRQs.
> +		 */
> +		if (ret > 0 &&
> +		    drm_dp_channel_eq_ok(status, mhdp->link.num_lanes) &&
> +		    drm_dp_clock_recovery_ok(status, mhdp->link.num_lanes)) {
> +			mutex_unlock(&mhdp->link_mutex);
> +			return;
> +		}
> +
> +		/* If link is bad, mark link as down so that we do a new LT */
> +		mhdp->link_up = false;
> +	}
> +
> +	if (!mhdp->link_up) {
> +		ret = cdns_mhdp_link_up(mhdp);
> +		if (ret < 0) {
> +			mutex_unlock(&mhdp->link_mutex);
> +			drm_connector_set_link_status_property(conn,
> +							       DRM_MODE_LINK_STATUS_BAD);
> +			return;
> +		}
> +	}
> +
> +	if (mhdp->bridge_enabled) {
> +		state = drm_priv_to_bridge_state(mhdp->bridge.base.state);
> +		if (!state)
> +			goto err;
> +
> +		cdns_bridge_state = to_cdns_mhdp_bridge_state(state);
> +		if (!cdns_bridge_state)
> +			goto err;
> +
> +		current_mode = cdns_bridge_state->current_mode;
> +		if (!current_mode)
> +			goto err;
> +
> +		ret = cdns_mhdp_validate_mode_params(mhdp, current_mode, state);
> +		if (ret < 0)
> +			goto err;
> +
> +		dev_dbg(mhdp->dev, "%s: Enabling mode %s\n", __func__,
> +			current_mode->name);
> +
> +		cdns_mhdp_sst_enable(mhdp, current_mode, state);
> +	}
> +err:
> +	mutex_unlock(&mhdp->link_mutex);
> +}
> +
> +static irqreturn_t cdns_mhdp_irq_handler(int irq, void *data)
> +{
> +	struct cdns_mhdp_device *mhdp = (struct cdns_mhdp_device *)data;
> +	u32 apb_stat, sw_ev0;
> +	bool bridge_attached;
> +
> +	apb_stat = readl(mhdp->regs + CDNS_APB_INT_STATUS);
> +	sw_ev0 = readl(mhdp->regs + CDNS_SW_EVENT0);
> +
> +	/*
> +	 *  Calling drm_kms_helper_hotplug_event() when not attached
> +	 *  to drm device causes an oops because the drm_bridge->dev
> +	 *  is NULL. See cdns_mhdp_fw_cb() comments for details about the
> +	 *  problems related drm_kms_helper_hotplug_event() call.
> +	 */
> +	spin_lock(&mhdp->start_lock);
> +	bridge_attached = mhdp->bridge_attached;
> +	spin_unlock(&mhdp->start_lock);
> +
> +	if (bridge_attached && (sw_ev0 & CDNS_DPTX_HPD)) {
> +		cdns_mhdp_update_link_status(mhdp);
> +
> +		drm_kms_helper_hotplug_event(mhdp->bridge.dev);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int cdns_mhdp_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct cdns_mhdp_device *mhdp;
> +	struct clk *clk;
> +	int ret;
> +	unsigned long rate;
> +	int irq;
> +
> +	mhdp = devm_kzalloc(dev, sizeof(*mhdp), GFP_KERNEL);
> +	if (!mhdp)
> +		return -ENOMEM;
> +
> +	clk = devm_clk_get(dev, NULL);
> +	if (IS_ERR(clk)) {
> +		dev_err(dev, "couldn't get clk: %ld\n", PTR_ERR(clk));
> +		return PTR_ERR(clk);
> +	}
> +
> +	mhdp->clk = clk;
> +	mhdp->dev = dev;
> +	mutex_init(&mhdp->mbox_mutex);
> +	mutex_init(&mhdp->link_mutex);
> +	spin_lock_init(&mhdp->start_lock);
> +
> +	drm_dp_aux_init(&mhdp->aux);
> +	mhdp->aux.dev = dev;
> +	mhdp->aux.transfer = cdns_mhdp_transfer;
> +
> +	mhdp->regs = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(mhdp->regs)) {
> +		dev_err(dev, "Failed to get memory resource\n");
> +		return PTR_ERR(mhdp->regs);
> +	}
> +
> +	mhdp->phy = devm_of_phy_get_by_index(dev, pdev->dev.of_node, 0);
> +	if (IS_ERR(mhdp->phy)) {
> +		dev_err(dev, "no PHY configured\n");
> +		return PTR_ERR(mhdp->phy);
> +	}
> +
> +	platform_set_drvdata(pdev, mhdp);
> +
> +	mhdp->ops = of_device_get_match_data(dev);
> +
> +	clk_prepare_enable(clk);
> +
> +	pm_runtime_enable(dev);
> +	ret = pm_runtime_get_sync(dev);
> +	if (ret < 0) {
> +		dev_err(dev, "pm_runtime_get_sync failed\n");
> +		pm_runtime_disable(dev);
> +		goto clk_disable;
> +	}
> +
> +	if (mhdp->ops && mhdp->ops->init) {
> +		ret = mhdp->ops->init(mhdp);
> +		if (ret != 0) {
> +			dev_err(dev, "MHDP platform initialization failed: %d\n",
> +				ret);
> +			goto runtime_put;
> +		}
> +	}
> +
> +	rate = clk_get_rate(clk);
> +	writel(rate % 1000000, mhdp->regs + CDNS_SW_CLK_L);
> +	writel(rate / 1000000, mhdp->regs + CDNS_SW_CLK_H);
> +
> +	dev_dbg(dev, "func clk rate %lu Hz\n", rate);
> +
> +	writel(~0, mhdp->regs + CDNS_APB_INT_MASK);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	ret = devm_request_threaded_irq(mhdp->dev, irq, NULL,
> +					cdns_mhdp_irq_handler, IRQF_ONESHOT,
> +					"mhdp8546", mhdp);
> +	if (ret) {
> +		dev_err(dev, "cannot install IRQ %d\n", irq);
> +		ret = -EIO;
> +		goto plat_fini;
> +	}
> +
> +	cdns_mhdp_fill_host_caps(mhdp);
> +
> +	/* The only currently supported format */
> +	mhdp->display_fmt.y_only = false;
> +	mhdp->display_fmt.color_format = DRM_COLOR_FORMAT_RGB444;
> +	mhdp->display_fmt.bpc = 8;
> +
> +	mhdp->bridge.of_node = pdev->dev.of_node;
> +	mhdp->bridge.funcs = &cdns_mhdp_bridge_funcs;
> +
> +	ret = phy_init(mhdp->phy);
> +	if (ret) {
> +		dev_err(mhdp->dev, "Failed to initialize PHY: %d\n", ret);
> +		goto plat_fini;
> +	}
> +
> +	ret = cdns_mhdp_load_firmware(mhdp);
> +	if (ret)
> +		goto phy_exit;
> +
> +	drm_bridge_add(&mhdp->bridge);
> +
> +	return 0;
> +
> +phy_exit:
> +	phy_exit(mhdp->phy);
> +plat_fini:
> +	if (mhdp->ops && mhdp->ops->exit)
> +		mhdp->ops->exit(mhdp);
> +runtime_put:
> +	pm_runtime_put_sync(dev);
> +	pm_runtime_disable(dev);
> +clk_disable:
> +	clk_disable_unprepare(mhdp->clk);
> +
> +	return ret;
> +}
> +
> +static int cdns_mhdp_remove(struct platform_device *pdev)
> +{
> +	struct cdns_mhdp_device *mhdp = dev_get_drvdata(&pdev->dev);
> +	unsigned long timeout = msecs_to_jiffies(100);
> +	bool stop_fw = false;
> +	int ret = 0;
> +
> +	drm_bridge_remove(&mhdp->bridge);
> +
> +	ret = wait_event_timeout(fw_load_wq, mhdp->hw_state == MHDP_HW_READY,
> +				 timeout);
> +	if (ret == 0)
> +		dev_err(mhdp->dev, "%s: Timeout waiting for fw loading\n",
> +			__func__);
> +	else
> +		stop_fw = true;
> +
> +	spin_lock(&mhdp->start_lock);
> +	mhdp->hw_state = MHDP_HW_STOPPED;
> +	spin_unlock(&mhdp->start_lock);
> +
> +	if (stop_fw) {
> +		ret = cdns_mhdp_set_firmware_active(mhdp, false);
> +		if (ret)
> +			dev_err(mhdp->dev, "%s: De-activate FW failed: %d\n",
> +				__func__, ret);
> +	}
> +
> +	phy_exit(mhdp->phy);
> +
> +	if (mhdp->ops && mhdp->ops->exit)
> +		mhdp->ops->exit(mhdp);
> +
> +	pm_runtime_put_sync(&pdev->dev);
> +	pm_runtime_disable(&pdev->dev);
> +
> +	clk_disable_unprepare(mhdp->clk);
> +
> +	return ret;
> +}
> +
> +static const struct of_device_id mhdp_ids[] = {
> +	{ .compatible = "cdns,mhdp8546", },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, mhdp_ids);
> +
> +static struct platform_driver mhdp_driver = {
> +	.driver	= {
> +		.name		= "cdns-mhdp",
> +		.of_match_table	= of_match_ptr(mhdp_ids),
> +	},
> +	.probe	= cdns_mhdp_probe,
> +	.remove	= cdns_mhdp_remove,
> +};
> +module_platform_driver(mhdp_driver);
> +
> +MODULE_FIRMWARE(FW_NAME);
> +
> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
> +MODULE_AUTHOR("Swapnil Jakhade <sjakhade@cadence.com>");
> +MODULE_DESCRIPTION("Cadence MHDP DP bridge driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:cdns-mhdp");
> diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.h b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
> new file mode 100644
> index 000000000000..d1bfcdef4249
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
> @@ -0,0 +1,396 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Cadence MHDP DP bridge driver.
> + *
> + * Copyright: 2019 Cadence Design Systems, Inc.
> + *
> + * Author: Quentin Schulz <quentin.schulz@free-electrons.com>
> + *         Swapnil Jakhade <sjakhade@cadence.com>
> + */
> +
> +#ifndef CDNS_MHDP_CORE_H
> +#define CDNS_MHDP_CORE_H
> +
> +#include <linux/bits.h>
> +#include <linux/mutex.h>
> +#include <linux/spinlock.h>
> +
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_dp_helper.h>
> +
> +struct clk;
> +struct device;
> +struct phy;
> +
> +/* Register offsets */
> +#define CDNS_APB_CTRL				0x00000
> +#define CDNS_CPU_STALL				BIT(3)
> +
> +#define CDNS_MAILBOX_FULL			0x00008
> +#define CDNS_MAILBOX_EMPTY			0x0000c
> +#define CDNS_MAILBOX_TX_DATA			0x00010
> +#define CDNS_MAILBOX_RX_DATA			0x00014
> +#define CDNS_KEEP_ALIVE				0x00018
> +#define CDNS_KEEP_ALIVE_MASK			GENMASK(7, 0)
> +
> +#define CDNS_VER_L				0x0001C
> +#define CDNS_VER_H				0x00020
> +#define CDNS_LIB_L_ADDR				0x00024
> +#define CDNS_LIB_H_ADDR				0x00028
> +
> +#define CDNS_MB_INT_MASK			0x00034
> +#define CDNS_MB_INT_STATUS			0x00038
> +
> +#define CDNS_SW_CLK_L				0x0003c
> +#define CDNS_SW_CLK_H				0x00040
> +
> +#define CDNS_SW_EVENT0				0x00044
> +#define CDNS_DPTX_HPD				BIT(0)
> +
> +#define CDNS_SW_EVENT1				0x00048
> +#define CDNS_SW_EVENT2				0x0004c
> +#define CDNS_SW_EVENT3				0x00050
> +
> +#define CDNS_APB_INT_MASK			0x0006C
> +#define CDNS_APB_INT_MASK_MAILBOX_INT		BIT(0)
> +#define CDNS_APB_INT_MASK_SW_EVENT_INT		BIT(1)
> +
> +#define CDNS_APB_INT_STATUS			0x00070
> +
> +#define CDNS_DPTX_CAR				0x00904
> +#define CDNS_VIF_CLK_EN				BIT(0)
> +#define CDNS_VIF_CLK_RSTN			BIT(1)
> +
> +#define CDNS_SOURCE_VIDEO_IF(s)			(0x00b00 + ((s) * 0x20))
> +#define CDNS_BND_HSYNC2VSYNC(s)			(CDNS_SOURCE_VIDEO_IF(s) + \
> +						 0x00)
> +#define CDNS_IP_DTCT_WIN			GENMASK(11, 0)
> +#define CDNS_IP_DET_INTERLACE_FORMAT		BIT(12)
> +#define CDNS_IP_BYPASS_V_INTERFACE		BIT(13)
> +
> +#define CDNS_HSYNC2VSYNC_POL_CTRL(s)		(CDNS_SOURCE_VIDEO_IF(s) + \
> +						 0x10)
> +#define CDNS_H2V_HSYNC_POL_ACTIVE_LOW		BIT(1)
> +#define CDNS_H2V_VSYNC_POL_ACTIVE_LOW		BIT(2)
> +
> +#define CDNS_DPTX_PHY_CONFIG			0x02000
> +#define CDNS_PHY_TRAINING_EN			BIT(0)
> +#define CDNS_PHY_TRAINING_TYPE(x)		(((x) & GENMASK(3, 0)) << 1)
> +#define CDNS_PHY_SCRAMBLER_BYPASS		BIT(5)
> +#define CDNS_PHY_ENCODER_BYPASS			BIT(6)
> +#define CDNS_PHY_SKEW_BYPASS			BIT(7)
> +#define CDNS_PHY_TRAINING_AUTO			BIT(8)
> +#define CDNS_PHY_LANE0_SKEW(x)			(((x) & GENMASK(2, 0)) << 9)
> +#define CDNS_PHY_LANE1_SKEW(x)			(((x) & GENMASK(2, 0)) << 12)
> +#define CDNS_PHY_LANE2_SKEW(x)			(((x) & GENMASK(2, 0)) << 15)
> +#define CDNS_PHY_LANE3_SKEW(x)			(((x) & GENMASK(2, 0)) << 18)
> +#define CDNS_PHY_COMMON_CONFIG			(CDNS_PHY_LANE1_SKEW(1) | \
> +						CDNS_PHY_LANE2_SKEW(2) |  \
> +						CDNS_PHY_LANE3_SKEW(3))
> +#define CDNS_PHY_10BIT_EN			BIT(21)
> +
> +#define CDNS_DP_FRAMER_GLOBAL_CONFIG		0x02200
> +#define CDNS_DP_NUM_LANES(x)			((x) - 1)
> +#define CDNS_DP_MST_EN				BIT(2)
> +#define CDNS_DP_FRAMER_EN			BIT(3)
> +#define CDNS_DP_RATE_GOVERNOR_EN		BIT(4)
> +#define CDNS_DP_NO_VIDEO_MODE			BIT(5)
> +#define CDNS_DP_DISABLE_PHY_RST			BIT(6)
> +#define CDNS_DP_WR_FAILING_EDGE_VSYNC		BIT(7)
> +
> +#define CDNS_DP_FRAMER_TU			0x02208
> +#define CDNS_DP_FRAMER_TU_SIZE(x)		(((x) & GENMASK(6, 0)) << 8)
> +#define CDNS_DP_FRAMER_TU_VS(x)			((x) & GENMASK(5, 0))
> +#define CDNS_DP_FRAMER_TU_CNT_RST_EN		BIT(15)
> +
> +#define CDNS_DP_MTPH_CONTROL			0x02264
> +#define CDNS_DP_MTPH_ECF_EN			BIT(0)
> +#define CDNS_DP_MTPH_ACT_EN			BIT(1)
> +#define CDNS_DP_MTPH_LVP_EN			BIT(2)
> +
> +#define CDNS_DP_MTPH_STATUS			0x0226C
> +#define CDNS_DP_MTPH_ACT_STATUS			BIT(0)
> +
> +#define CDNS_DP_LANE_EN				0x02300
> +#define CDNS_DP_LANE_EN_LANES(x)		GENMASK((x) - 1, 0)
> +
> +#define CDNS_DP_ENHNCD				0x02304
> +
> +#define CDNS_DPTX_STREAM(s)			(0x03000 + (s) * 0x80)
> +#define CDNS_DP_MSA_HORIZONTAL_0(s)		(CDNS_DPTX_STREAM(s) + 0x00)
> +#define CDNS_DP_MSAH0_H_TOTAL(x)		(x)
> +#define CDNS_DP_MSAH0_HSYNC_START(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_HORIZONTAL_1(s)		(CDNS_DPTX_STREAM(s) + 0x04)
> +#define CDNS_DP_MSAH1_HSYNC_WIDTH(x)		(x)
> +#define CDNS_DP_MSAH1_HSYNC_POL_LOW		BIT(15)
> +#define CDNS_DP_MSAH1_HDISP_WIDTH(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_VERTICAL_0(s)		(CDNS_DPTX_STREAM(s) + 0x08)
> +#define CDNS_DP_MSAV0_V_TOTAL(x)		(x)
> +#define CDNS_DP_MSAV0_VSYNC_START(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_VERTICAL_1(s)		(CDNS_DPTX_STREAM(s) + 0x0c)
> +#define CDNS_DP_MSAV1_VSYNC_WIDTH(x)		(x)
> +#define CDNS_DP_MSAV1_VSYNC_POL_LOW		BIT(15)
> +#define CDNS_DP_MSAV1_VDISP_WIDTH(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_MISC(s)			(CDNS_DPTX_STREAM(s) + 0x10)
> +#define CDNS_DP_STREAM_CONFIG(s)		(CDNS_DPTX_STREAM(s) + 0x14)
> +#define CDNS_DP_STREAM_CONFIG_2(s)		(CDNS_DPTX_STREAM(s) + 0x2c)
> +#define CDNS_DP_SC2_TU_VS_DIFF(x)		((x) << 8)
> +
> +#define CDNS_DP_HORIZONTAL(s)			(CDNS_DPTX_STREAM(s) + 0x30)
> +#define CDNS_DP_H_HSYNC_WIDTH(x)		(x)
> +#define CDNS_DP_H_H_TOTAL(x)			((x) << 16)
> +
> +#define CDNS_DP_VERTICAL_0(s)			(CDNS_DPTX_STREAM(s) + 0x34)
> +#define CDNS_DP_V0_VHEIGHT(x)			(x)
> +#define CDNS_DP_V0_VSTART(x)			((x) << 16)
> +
> +#define CDNS_DP_VERTICAL_1(s)			(CDNS_DPTX_STREAM(s) + 0x38)
> +#define CDNS_DP_V1_VTOTAL(x)			(x)
> +#define CDNS_DP_V1_VTOTAL_EVEN			BIT(16)
> +
> +#define CDNS_DP_MST_SLOT_ALLOCATE(s)		(CDNS_DPTX_STREAM(s) + 0x44)
> +#define CDNS_DP_S_ALLOC_START_SLOT(x)		(x)
> +#define CDNS_DP_S_ALLOC_END_SLOT(x)		((x) << 8)
> +
> +#define CDNS_DP_RATE_GOVERNING(s)		(CDNS_DPTX_STREAM(s) + 0x48)
> +#define CDNS_DP_RG_TARG_AV_SLOTS_Y(x)		(x)
> +#define CDNS_DP_RG_TARG_AV_SLOTS_X(x)		((x) << 4)
> +#define CDNS_DP_RG_ENABLE			BIT(10)
> +
> +#define CDNS_DP_FRAMER_PXL_REPR(s)		(CDNS_DPTX_STREAM(s) + 0x4c)
> +#define CDNS_DP_FRAMER_6_BPC			BIT(0)
> +#define CDNS_DP_FRAMER_8_BPC			BIT(1)
> +#define CDNS_DP_FRAMER_10_BPC			BIT(2)
> +#define CDNS_DP_FRAMER_12_BPC			BIT(3)
> +#define CDNS_DP_FRAMER_16_BPC			BIT(4)
> +#define CDNS_DP_FRAMER_PXL_FORMAT		0x8
> +#define CDNS_DP_FRAMER_RGB			BIT(0)
> +#define CDNS_DP_FRAMER_YCBCR444			BIT(1)
> +#define CDNS_DP_FRAMER_YCBCR422			BIT(2)
> +#define CDNS_DP_FRAMER_YCBCR420			BIT(3)
> +#define CDNS_DP_FRAMER_Y_ONLY			BIT(4)
> +
> +#define CDNS_DP_FRAMER_SP(s)			(CDNS_DPTX_STREAM(s) + 0x50)
> +#define CDNS_DP_FRAMER_VSYNC_POL_LOW		BIT(0)
> +#define CDNS_DP_FRAMER_HSYNC_POL_LOW		BIT(1)
> +#define CDNS_DP_FRAMER_INTERLACE		BIT(2)
> +
> +#define CDNS_DP_LINE_THRESH(s)			(CDNS_DPTX_STREAM(s) + 0x64)
> +#define CDNS_DP_ACTIVE_LINE_THRESH(x)		(x)
> +
> +#define CDNS_DP_VB_ID(s)			(CDNS_DPTX_STREAM(s) + 0x68)
> +#define CDNS_DP_VB_ID_INTERLACED		BIT(2)
> +#define CDNS_DP_VB_ID_COMPRESSED		BIT(6)
> +
> +#define CDNS_DP_FRONT_BACK_PORCH(s)		(CDNS_DPTX_STREAM(s) + 0x78)
> +#define CDNS_DP_BACK_PORCH(x)			(x)
> +#define CDNS_DP_FRONT_PORCH(x)			((x) << 16)
> +
> +#define CDNS_DP_BYTE_COUNT(s)			(CDNS_DPTX_STREAM(s) + 0x7c)
> +#define CDNS_DP_BYTE_COUNT_BYTES_IN_CHUNK_SHIFT	16
> +
> +/* mailbox */
> +#define MAILBOX_RETRY_US			1000
> +#define MAILBOX_TIMEOUT_US			2000000
> +
> +#define MB_OPCODE_ID				0
> +#define MB_MODULE_ID				1
> +#define MB_SIZE_MSB_ID				2
> +#define MB_SIZE_LSB_ID				3
> +#define MB_DATA_ID				4
> +
> +#define MB_MODULE_ID_DP_TX			0x01
> +#define MB_MODULE_ID_HDCP_TX			0x07
> +#define MB_MODULE_ID_HDCP_RX			0x08
> +#define MB_MODULE_ID_HDCP_GENERAL		0x09
> +#define MB_MODULE_ID_GENERAL			0x0a
> +
> +/* firmware and opcodes */
> +#define FW_NAME					"cadence/mhdp8546.bin"
> +#define CDNS_MHDP_IMEM				0x10000
> +
> +#define GENERAL_MAIN_CONTROL			0x01
> +#define GENERAL_TEST_ECHO			0x02
> +#define GENERAL_BUS_SETTINGS			0x03
> +#define GENERAL_TEST_ACCESS			0x04
> +#define GENERAL_REGISTER_READ			0x07
> +
> +#define DPTX_SET_POWER_MNG			0x00
> +#define DPTX_GET_EDID				0x02
> +#define DPTX_READ_DPCD				0x03
> +#define DPTX_WRITE_DPCD				0x04
> +#define DPTX_ENABLE_EVENT			0x05
> +#define DPTX_WRITE_REGISTER			0x06
> +#define DPTX_READ_REGISTER			0x07
> +#define DPTX_WRITE_FIELD			0x08
> +#define DPTX_READ_EVENT				0x0a
> +#define DPTX_GET_LAST_AUX_STAUS			0x0e
> +#define DPTX_HPD_STATE				0x11
> +#define DPTX_ADJUST_LT				0x12
> +
> +#define FW_STANDBY				0
> +#define FW_ACTIVE				1
> +
> +/* HPD */
> +#define DPTX_READ_EVENT_HPD_TO_HIGH             BIT(0)
> +#define DPTX_READ_EVENT_HPD_TO_LOW              BIT(1)
> +#define DPTX_READ_EVENT_HPD_PULSE               BIT(2)
> +#define DPTX_READ_EVENT_HPD_STATE               BIT(3)
> +
> +/* general */
> +#define CDNS_DP_TRAINING_PATTERN_4		0x7
> +
> +#define CDNS_KEEP_ALIVE_TIMEOUT			2000
> +
> +#define CDNS_VOLT_SWING(x)			((x) & GENMASK(1, 0))
> +#define CDNS_FORCE_VOLT_SWING			BIT(2)
> +
> +#define CDNS_PRE_EMPHASIS(x)			((x) & GENMASK(1, 0))
> +#define CDNS_FORCE_PRE_EMPHASIS			BIT(2)
> +
> +#define CDNS_SUPPORT_TPS(x)			BIT((x) - 1)
> +
> +#define CDNS_FAST_LINK_TRAINING			BIT(0)
> +
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_0(x)	((x) & GENMASK(1, 0))
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_1(x)	((x) & GENMASK(3, 2))
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_2(x)	((x) & GENMASK(5, 4))
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_3(x)	((x) & GENMASK(7, 6))
> +#define CDNS_LANE_MAPPING_NORMAL		0xe4
> +#define CDNS_LANE_MAPPING_FLIPPED		0x1b
> +
> +#define CDNS_DP_MAX_NUM_LANES			4
> +#define CDNS_DP_TEST_VSC_SDP			BIT(6) /* 1.3+ */
> +#define CDNS_DP_TEST_COLOR_FORMAT_RAW_Y_ONLY	BIT(7)
> +
> +#define CDNS_MHDP_MAX_STREAMS   4
> +
> +#define DP_LINK_CAP_ENHANCED_FRAMING BIT(0)
> +
> +struct cdns_mhdp_link {
> +	unsigned char revision;
> +	unsigned int rate;
> +	unsigned int num_lanes;
> +	unsigned long capabilities;
> +};
> +
> +struct cdns_mhdp_host {
> +	unsigned int link_rate;
> +	u8 lanes_cnt;
> +	u8 volt_swing;
> +	u8 pre_emphasis;
> +	u8 pattern_supp;
> +	u8 lane_mapping;
> +	bool fast_link;
> +	bool enhanced;
> +	bool scrambler;
> +	bool ssc;
> +};
> +
> +struct cdns_mhdp_sink {
> +	unsigned int link_rate;
> +	u8 lanes_cnt;
> +	u8 pattern_supp;
> +	bool fast_link;
> +	bool enhanced;
> +	bool ssc;
> +};
> +
> +struct cdns_mhdp_display_fmt {
> +	u32 color_format;
> +	u32 bpc;
> +	bool y_only;
> +};
> +
> +/*
> + * These enums present MHDP hw initialization state
> + * Legal state transitions are:
> + * MHDP_HW_INACTIVE <-> MHDP_HW_LOADING -> MHDP_HW_READY
> + *        |                                     |
> + *        '----------> MHDP_HW_STOPPED <--------'
> + */
> +enum mhdp_hw_state {
> +	MHDP_HW_INACTIVE = 0,	/* HW not initialized */
> +	MHDP_HW_LOADING,	/* HW initialization in progress */
> +	MHDP_HW_READY,		/* HW ready, FW active*/
> +	MHDP_HW_STOPPED		/* Driver removal FW to be stopped */
> +};
> +
> +struct cdns_mhdp_device;
> +
> +struct mhdp_platform_ops {
> +	int (*init)(struct cdns_mhdp_device *mhdp);
> +	void (*exit)(struct cdns_mhdp_device *mhdp);
> +	void (*enable)(struct cdns_mhdp_device *mhdp);
> +	void (*disable)(struct cdns_mhdp_device *mhdp);
> +};
> +
> +struct cdns_mhdp_bridge_state {
> +	struct drm_bridge_state base;
> +	u32 tu_size;
> +	u32 vs;
> +	u32 line_thresh;
> +	struct drm_display_mode *current_mode;
> +};
> +
> +#define to_cdns_mhdp_bridge_state(s) \
> +		container_of(s, struct cdns_mhdp_bridge_state, base)
> +
> +struct cdns_mhdp_device {
> +	void __iomem *regs;
> +
> +	struct device *dev;
> +	struct clk *clk;
> +	struct phy *phy;
> +
> +	const struct mhdp_platform_ops *ops;
> +
> +	/* This is to protect mailbox communications with the firmware */
> +	struct mutex mbox_mutex;
> +
> +	/* "link_mutex" protects the access to all the link parameters
> +	 * including the link training process. Link training will be
> +	 * invoked both from threaded interrupt handler and from atomic
> +	 * callbacks when link_up is not set. So this mutex protects
> +	 * flags such as link_up, bridge_enabled, link.num_lanes,
> +	 * link.rate etc.
> +	 */
> +	struct mutex link_mutex;
> +
> +	struct drm_connector connector;
> +	struct drm_bridge bridge;
> +
> +	struct cdns_mhdp_link link;
> +	struct drm_dp_aux aux;
> +
> +	struct cdns_mhdp_host host;
> +	struct cdns_mhdp_sink sink;
> +	struct cdns_mhdp_display_fmt display_fmt;
> +	s8 stream_id;
> +
> +	bool link_up;
> +	bool plugged;
> +
> +	/*
> +	 * "start_lock" protects the access to bridge_attached and
> +	 * hw_state data members that control the delayed firmware
> +	 * loading and attaching the bridge. They are accessed from
> +	 * both the DRM core and cdns_mhdp_fw_cb(). In most cases just
> +	 * protecting the data members is enough, but the irq mask
> +	 * setting needs to be protected when enabling the FW.
> +	 */
> +	spinlock_t start_lock;
> +	bool bridge_attached;
> +	bool bridge_enabled;
> +	enum mhdp_hw_state hw_state;
> +};
> +
> +#define connector_to_mhdp(x) container_of(x, struct cdns_mhdp_device, connector)
> +#define bridge_to_mhdp(x) container_of(x, struct cdns_mhdp_device, bridge)
> +
> +#endif

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v7 2/3] drm: bridge: Add support for Cadence MHDP DPI/DP bridge
@ 2020-07-22 11:51     ` Laurent Pinchart
  0 siblings, 0 replies; 26+ messages in thread
From: Laurent Pinchart @ 2020-07-22 11:51 UTC (permalink / raw)
  To: Swapnil Jakhade
  Cc: devicetree, jernej.skrabec, praneeth, yamonkar, jonas, airlied,
	tomi.valkeinen, narmstrong, nsekhar, linux-kernel, dri-devel,
	a.hajda, robh+dt, jsarha, mparab

Hi Swapnil,

Thank you for the patch.

On Wed, Jul 22, 2020 at 09:40:39AM +0200, Swapnil Jakhade wrote:
> Add a new DRM bridge driver for Cadence MHDP DPTX IP used in TI J721e SoC.
> MHDP DPTX IP is the component that complies with VESA DisplayPort (DP) and
> embedded Display Port (eDP) standards. It integrates uCPU running the
> embedded Firmware (FW) interfaced over APB interface.
> 
> Basically, it takes a DPI stream as input and outputs it encoded in DP
> format. Currently, it supports only SST mode.
> 
> Co-developed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
> Co-developed-by: Jyri Sarha <jsarha@ti.com>
> Signed-off-by: Jyri Sarha <jsarha@ti.com>
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
> Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
> ---
>  drivers/gpu/drm/bridge/Kconfig          |   11 +
>  drivers/gpu/drm/bridge/Makefile         |    2 +
>  drivers/gpu/drm/bridge/cdns-mhdp-core.c | 2493 +++++++++++++++++++++++
>  drivers/gpu/drm/bridge/cdns-mhdp-core.h |  396 ++++
>  4 files changed, 2902 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.c
>  create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.h
> 
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index 43271c21d3fc..6a4c324302a8 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -27,6 +27,17 @@ config DRM_CDNS_DSI
>  	  Support Cadence DPI to DSI bridge. This is an internal
>  	  bridge and is meant to be directly embedded in a SoC.
>  
> +config DRM_CDNS_MHDP
> +	tristate "Cadence DPI/DP bridge"
> +	select DRM_KMS_HELPER
> +	select DRM_PANEL_BRIDGE
> +	depends on OF
> +	help
> +	  Support Cadence DPI to DP bridge. This is an internal
> +	  bridge and is meant to be directly embedded in a SoC.
> +	  It takes a DPI stream as input and outputs it encoded
> +	  in DP format.
> +
>  config DRM_CHRONTEL_CH7033
>  	tristate "Chrontel CH7033 Video Encoder"
>  	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index d63d4b7e4347..0080a9f80f29 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -1,5 +1,7 @@
>  # SPDX-License-Identifier: GPL-2.0
>  obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
> +obj-$(CONFIG_DRM_CDNS_MHDP) += cdns-mhdp.o
> +cdns-mhdp-objs := cdns-mhdp-core.o
>  obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
>  obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
>  obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
> diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.c b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
> new file mode 100644
> index 000000000000..b16c5503cef1
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
> @@ -0,0 +1,2493 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Cadence MHDP DP bridge driver.
> + *
> + * Copyright: 2019 Cadence Design Systems, Inc.
> + *
> + * Author: Quentin Schulz <quentin.schulz@free-electrons.com>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/firmware.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/phy/phy.h>
> +#include <linux/phy/phy-dp.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/wait.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_atomic_state_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_dp_helper.h>
> +#include <drm/drm_modeset_helper_vtables.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_probe_helper.h>
> +
> +#include <asm/unaligned.h>
> +
> +#include "cdns-mhdp-core.h"
> +
> +static DECLARE_WAIT_QUEUE_HEAD(fw_load_wq);
> +
> +static int cdns_mhdp_mailbox_read(struct cdns_mhdp_device *mhdp)
> +{
> +	int ret, empty;
> +
> +	WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex));
> +
> +	ret = readx_poll_timeout(readl, mhdp->regs + CDNS_MAILBOX_EMPTY,
> +				 empty, !empty, MAILBOX_RETRY_US,
> +				 MAILBOX_TIMEOUT_US);
> +	if (ret < 0)
> +		return ret;
> +
> +	return readl(mhdp->regs + CDNS_MAILBOX_RX_DATA) & 0xff;
> +}
> +
> +static int cdns_mhdp_mailbox_write(struct cdns_mhdp_device *mhdp, u8 val)
> +{
> +	int ret, full;
> +
> +	WARN_ON(!mutex_is_locked(&mhdp->mbox_mutex));
> +
> +	ret = readx_poll_timeout(readl, mhdp->regs + CDNS_MAILBOX_FULL,
> +				 full, !full, MAILBOX_RETRY_US,
> +				 MAILBOX_TIMEOUT_US);
> +	if (ret < 0)
> +		return ret;
> +
> +	writel(val, mhdp->regs + CDNS_MAILBOX_TX_DATA);
> +
> +	return 0;
> +}
> +
> +static int cdns_mhdp_mailbox_validate_receive(struct cdns_mhdp_device *mhdp,
> +					      u8 module_id, u8 opcode,
> +					      u16 req_size)
> +{
> +	u32 mbox_size, i;
> +	u8 header[4];
> +	int ret;
> +
> +	/* read the header of the message */
> +	for (i = 0; i < 4; i++) {
> +		ret = cdns_mhdp_mailbox_read(mhdp);
> +		if (ret < 0)
> +			return ret;
> +
> +		header[i] = ret;
> +	}
> +
> +	mbox_size = get_unaligned_be16(header + 2);
> +
> +	if (opcode != header[0] || module_id != header[1] ||
> +	    req_size != mbox_size) {
> +		/*
> +		 * If the message in mailbox is not what we want, we need to
> +		 * clear the mailbox by reading its contents.
> +		 */
> +		for (i = 0; i < mbox_size; i++)
> +			if (cdns_mhdp_mailbox_read(mhdp) < 0)
> +				break;
> +
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int cdns_mhdp_mailbox_read_receive(struct cdns_mhdp_device *mhdp,
> +					  u8 *buff, u16 buff_size)
> +{
> +	u32 i;
> +	int ret;
> +
> +	for (i = 0; i < buff_size; i++) {
> +		ret = cdns_mhdp_mailbox_read(mhdp);
> +		if (ret < 0)
> +			return ret;
> +
> +		buff[i] = ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int cdns_mhdp_mailbox_send(struct cdns_mhdp_device *mhdp, u8 module_id,
> +				  u8 opcode, u16 size, u8 *message)
> +{
> +	u8 header[4];
> +	int ret, i;
> +
> +	header[0] = opcode;
> +	header[1] = module_id;
> +	put_unaligned_be16(size, header + 2);
> +
> +	for (i = 0; i < 4; i++) {
> +		ret = cdns_mhdp_mailbox_write(mhdp, header[i]);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	for (i = 0; i < size; i++) {
> +		ret = cdns_mhdp_mailbox_write(mhdp, message[i]);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static
> +int cdns_mhdp_reg_read(struct cdns_mhdp_device *mhdp, u32 addr, u32 *value)
> +{
> +	u8 msg[4], resp[8];
> +	int ret;
> +
> +	put_unaligned_be32(addr, msg);
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_GENERAL,
> +				     GENERAL_REGISTER_READ,
> +				     sizeof(msg), msg);
> +	if (ret)
> +		goto err_reg_read;
> +
> +	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_GENERAL,
> +						 GENERAL_REGISTER_READ,
> +						 sizeof(resp));
> +	if (ret)
> +		goto err_reg_read;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, resp, sizeof(resp));
> +	if (ret)
> +		goto err_reg_read;
> +
> +	/* Returned address value should be the same as requested */
> +	if (memcmp(msg, resp, sizeof(msg))) {
> +		ret = -EINVAL;
> +		goto err_reg_read;
> +	}
> +
> +	*value = get_unaligned_be32(resp + 4);
> +
> +err_reg_read:
> +	mutex_unlock(&mhdp->mbox_mutex);
> +	if (ret) {
> +		DRM_DEV_ERROR(mhdp->dev, "Failed to read register.\n");
> +		*value = 0;
> +	}
> +
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_reg_write(struct cdns_mhdp_device *mhdp, u16 addr, u32 val)
> +{
> +	u8 msg[6];
> +	int ret;
> +
> +	put_unaligned_be16(addr, msg);
> +	put_unaligned_be32(val, msg + 2);
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +				     DPTX_WRITE_REGISTER, sizeof(msg), msg);
> +
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_reg_write_bit(struct cdns_mhdp_device *mhdp, u16 addr,
> +			    u8 start_bit, u8 bits_no, u32 val)
> +{
> +	u8 field[8];
> +	int ret;
> +
> +	put_unaligned_be16(addr, field);
> +	field[2] = start_bit;
> +	field[3] = bits_no;
> +	put_unaligned_be32(val, field + 4);
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +				     DPTX_WRITE_FIELD, sizeof(field), field);
> +
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_dpcd_read(struct cdns_mhdp_device *mhdp,
> +			u32 addr, u8 *data, u16 len)
> +{
> +	u8 msg[5], reg[5];
> +	int ret;
> +
> +	put_unaligned_be16(len, msg);
> +	put_unaligned_be24(addr, msg + 2);
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +				     DPTX_READ_DPCD, sizeof(msg), msg);
> +	if (ret)
> +		goto err_dpcd_read;
> +
> +	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
> +						 DPTX_READ_DPCD,
> +						 sizeof(reg) + len);
> +	if (ret)
> +		goto err_dpcd_read;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
> +	if (ret)
> +		goto err_dpcd_read;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, data, len);
> +
> +err_dpcd_read:
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_dpcd_write(struct cdns_mhdp_device *mhdp, u32 addr, u8 value)
> +{
> +	u8 msg[6], reg[5];
> +	int ret;
> +
> +	put_unaligned_be16(1, msg);
> +	put_unaligned_be24(addr, msg + 2);
> +	msg[5] = value;
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +				     DPTX_WRITE_DPCD, sizeof(msg), msg);
> +	if (ret)
> +		goto err_dpcd_write;
> +
> +	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
> +						 DPTX_WRITE_DPCD, sizeof(reg));
> +	if (ret)
> +		goto err_dpcd_write;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
> +	if (ret)
> +		goto err_dpcd_write;
> +
> +	if (addr != get_unaligned_be24(reg + 2))
> +		ret = -EINVAL;
> +
> +err_dpcd_write:
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	if (ret)
> +		DRM_DEV_ERROR(mhdp->dev, "dpcd write failed: %d\n", ret);
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_set_firmware_active(struct cdns_mhdp_device *mhdp, bool enable)
> +{
> +	u8 msg[5];
> +	int ret, i;
> +
> +	msg[0] = GENERAL_MAIN_CONTROL;
> +	msg[1] = MB_MODULE_ID_GENERAL;
> +	msg[2] = 0;
> +	msg[3] = 1;
> +	msg[4] = enable ? FW_ACTIVE : FW_STANDBY;
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	for (i = 0; i < sizeof(msg); i++) {
> +		ret = cdns_mhdp_mailbox_write(mhdp, msg[i]);
> +		if (ret)
> +			goto err_set_firmware_active;
> +	}
> +
> +	/* read the firmware state */
> +	for (i = 0; i < sizeof(msg); i++)  {
> +		ret = cdns_mhdp_mailbox_read(mhdp);
> +		if (ret < 0)
> +			goto err_set_firmware_active;
> +
> +		msg[i] = ret;
> +	}
> +
> +	ret = 0;
> +
> +err_set_firmware_active:
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	if (ret < 0)
> +		DRM_DEV_ERROR(mhdp->dev, "set firmware active failed\n");
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_get_hpd_status(struct cdns_mhdp_device *mhdp)
> +{
> +	u8 status;
> +	int ret;
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +				     DPTX_HPD_STATE, 0, NULL);
> +	if (ret)
> +		goto err_get_hpd;
> +
> +	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
> +						 DPTX_HPD_STATE,
> +						 sizeof(status));
> +	if (ret)
> +		goto err_get_hpd;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, &status, sizeof(status));
> +	if (ret)
> +		goto err_get_hpd;
> +
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	dev_dbg(mhdp->dev, "%s: %d\n", __func__, status);
> +
> +	return status;
> +
> +err_get_hpd:
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	DRM_DEV_ERROR(mhdp->dev, "get hpd status failed: %d\n", ret);
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_get_edid_block(void *data, u8 *edid,
> +			     unsigned int block, size_t length)
> +{
> +	struct cdns_mhdp_device *mhdp = data;
> +	u8 msg[2], reg[2], i;
> +	int ret;
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	for (i = 0; i < 4; i++) {
> +		msg[0] = block / 2;
> +		msg[1] = block % 2;
> +
> +		ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +					     DPTX_GET_EDID, sizeof(msg), msg);
> +		if (ret)
> +			continue;
> +
> +		ret = cdns_mhdp_mailbox_validate_receive(mhdp,
> +							 MB_MODULE_ID_DP_TX,
> +							 DPTX_GET_EDID,
> +							 sizeof(reg) + length);
> +		if (ret)
> +			continue;
> +
> +		ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
> +		if (ret)
> +			continue;
> +
> +		ret = cdns_mhdp_mailbox_read_receive(mhdp, edid, length);
> +		if (ret)
> +			continue;
> +
> +		if (reg[0] == length && reg[1] == block / 2)
> +			break;
> +	}
> +
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	if (ret)
> +		DRM_DEV_ERROR(mhdp->dev, "get block[%d] edid failed: %d\n",
> +			      block, ret);
> +
> +	return ret;
> +}
> +
> +static
> +int cdns_mhdp_read_event(struct cdns_mhdp_device *mhdp)
> +{
> +	u8 event = 0;
> +	int ret;
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +				     DPTX_READ_EVENT, 0, NULL);
> +	if (ret)
> +		goto out;
> +
> +	ret = cdns_mhdp_mailbox_validate_receive(mhdp,
> +						 MB_MODULE_ID_DP_TX,
> +						 DPTX_READ_EVENT,
> +						 sizeof(event));
> +	if (ret < 0)
> +		goto out;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, &event,
> +					     sizeof(event));
> +out:
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	dev_dbg(mhdp->dev, "%s: %s%s%s%s\n", __func__,
> +		(event & DPTX_READ_EVENT_HPD_TO_HIGH) ? "TO_HIGH " : "",
> +		(event & DPTX_READ_EVENT_HPD_TO_LOW) ? "TO_LOW " : "",
> +		(event & DPTX_READ_EVENT_HPD_PULSE) ? "PULSE " : "",
> +		(event & DPTX_READ_EVENT_HPD_STATE) ? "HPD_STATE " : "");
> +
> +	return event;
> +}
> +
> +static
> +int cdns_mhdp_adjust_lt(struct cdns_mhdp_device *mhdp,
> +			u8 nlanes, u16 udelay, u8 *lanes_data, u8 *link_status)
> +{
> +	u8 payload[7];
> +	u8 hdr[5]; /* For DPCD read response header */
> +	u32 addr;
> +	u8 const nregs = 6; /* Registers 0x202-0x207 */
> +	int ret;
> +
> +	if (nlanes != 4 && nlanes != 2 && nlanes != 1) {
> +		DRM_DEV_ERROR(mhdp->dev, "invalid number of lanes: %d\n",
> +			      nlanes);
> +		ret = -EINVAL;
> +		goto err_adjust_lt;
> +	}
> +
> +	payload[0] = nlanes;
> +	put_unaligned_be16(udelay, payload + 1);
> +	memcpy(payload + 3, lanes_data, nlanes);
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
> +				     DPTX_ADJUST_LT,
> +				     sizeof(payload), payload);
> +	if (ret)
> +		goto err_adjust_lt;
> +
> +	/* Yes, read the DPCD read command response */
> +	ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
> +						 DPTX_READ_DPCD,
> +						 sizeof(hdr) + nregs);
> +	if (ret)
> +		goto err_adjust_lt;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, hdr, sizeof(hdr));
> +	if (ret)
> +		goto err_adjust_lt;
> +
> +	addr = get_unaligned_be24(hdr + 2);
> +	if (addr != DP_LANE0_1_STATUS)
> +		goto err_adjust_lt;
> +
> +	ret = cdns_mhdp_mailbox_read_receive(mhdp, link_status, nregs);
> +
> +err_adjust_lt:
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	if (ret)
> +		DRM_DEV_ERROR(mhdp->dev, "Failed to adjust Link Training.\n");
> +
> +	return ret;
> +}
> +
> +/**
> + * cdns_mhdp_link_power_up() - power up a DisplayPort link
> + * @aux: DisplayPort AUX channel
> + * @link: pointer to a structure containing the link configuration
> + *
> + * Returns 0 on success or a negative error code on failure.
> + */
> +static
> +int cdns_mhdp_link_power_up(struct drm_dp_aux *aux, struct cdns_mhdp_link *link)
> +{
> +	u8 value;
> +	int err;
> +
> +	/* DP_SET_POWER register is only available on DPCD v1.1 and later */
> +	if (link->revision < 0x11)
> +		return 0;
> +
> +	err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value);
> +	if (err < 0)
> +		return err;
> +
> +	value &= ~DP_SET_POWER_MASK;
> +	value |= DP_SET_POWER_D0;
> +
> +	err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
> +	if (err < 0)
> +		return err;
> +
> +	/*
> +	 * According to the DP 1.1 specification, a "Sink Device must exit the
> +	 * power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink
> +	 * Control Field" (register 0x600).
> +	 */
> +	usleep_range(1000, 2000);
> +
> +	return 0;
> +}
> +
> +/**
> + * cdns_mhdp_link_power_down() - power down a DisplayPort link
> + * @aux: DisplayPort AUX channel
> + * @link: pointer to a structure containing the link configuration
> + *
> + * Returns 0 on success or a negative error code on failure.
> + */
> +static
> +int cdns_mhdp_link_power_down(struct drm_dp_aux *aux,
> +			      struct cdns_mhdp_link *link)
> +{
> +	u8 value;
> +	int err;
> +
> +	/* DP_SET_POWER register is only available on DPCD v1.1 and later */
> +	if (link->revision < 0x11)
> +		return 0;
> +
> +	err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value);
> +	if (err < 0)
> +		return err;
> +
> +	value &= ~DP_SET_POWER_MASK;
> +	value |= DP_SET_POWER_D3;
> +
> +	err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value);
> +	if (err < 0)
> +		return err;
> +
> +	return 0;
> +}
> +
> +/**
> + * cdns_mhdp_link_configure() - configure a DisplayPort link
> + * @aux: DisplayPort AUX channel
> + * @link: pointer to a structure containing the link configuration
> + *
> + * Returns 0 on success or a negative error code on failure.
> + */
> +static
> +int cdns_mhdp_link_configure(struct drm_dp_aux *aux,
> +			     struct cdns_mhdp_link *link)
> +{
> +	u8 values[2];
> +	int err;
> +
> +	values[0] = drm_dp_link_rate_to_bw_code(link->rate);
> +	values[1] = link->num_lanes;
> +
> +	if (link->capabilities & DP_LINK_CAP_ENHANCED_FRAMING)
> +		values[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
> +
> +	err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, values, sizeof(values));
> +	if (err < 0)
> +		return err;
> +
> +	return 0;
> +}
> +
> +static unsigned int cdns_mhdp_max_link_rate(struct cdns_mhdp_device *mhdp)
> +{
> +	return min(mhdp->host.link_rate, mhdp->sink.link_rate);
> +}
> +
> +static u8 cdns_mhdp_max_num_lanes(struct cdns_mhdp_device *mhdp)
> +{
> +	return min(mhdp->sink.lanes_cnt, mhdp->host.lanes_cnt);
> +}
> +
> +static u8 cdns_mhdp_eq_training_pattern_supported(struct cdns_mhdp_device *mhdp)
> +{
> +	return fls(mhdp->host.pattern_supp & mhdp->sink.pattern_supp);
> +}
> +
> +static bool cdns_mhdp_get_ssc_supported(struct cdns_mhdp_device *mhdp)
> +{
> +	/* Check if SSC is supported by both sides */
> +	return (mhdp->host.ssc) && (mhdp->sink.ssc);
> +}
> +
> +static int cdns_mhdp_check_fw_version(struct cdns_mhdp_device *mhdp)
> +{
> +	u32 ver_l, ver_h, fw_ver;
> +	u32 lib_l_addr, lib_h_addr, lib_ver;
> +	u32 major_num, minor_num, revision;
> +
> +	ver_l = readl(mhdp->regs + CDNS_VER_L);
> +	ver_h = readl(mhdp->regs + CDNS_VER_H);
> +
> +	fw_ver = (ver_h << 8) | ver_l;
> +
> +	lib_l_addr = readl(mhdp->regs + CDNS_LIB_L_ADDR);
> +	lib_h_addr = readl(mhdp->regs + CDNS_LIB_H_ADDR);
> +
> +	lib_ver = (lib_h_addr << 8) | lib_l_addr;
> +
> +	if (lib_ver < 33984) {
> +		/**
> +		 * Older FW versions with major number 1, used to store FW
> +		 * version information by storing repository revision number
> +		 * in registers. This is for identifying these FW versions.
> +		 */
> +		major_num = 1;
> +		minor_num = 2;
> +		if (fw_ver == 26098)
> +			revision = 15;
> +		else if (lib_ver == 0 && fw_ver == 0)
> +			revision = 17;
> +		else
> +			goto fw_error;
> +	} else {
> +		/* To identify newer FW versions with major number 2 onwards. */
> +		major_num = fw_ver / 10000;
> +		minor_num = (fw_ver / 100) % 100;
> +		revision = (fw_ver % 10000) % 100;
> +	}
> +
> +	dev_dbg(mhdp->dev, "FW version: v%u.%u.%u\n", major_num, minor_num,
> +		revision);
> +	return 0;
> +
> +fw_error:
> +	dev_err(mhdp->dev, "Unsupported FW version\n");
> +	return -ENODEV;
> +}
> +
> +static int cdns_mhdp_fw_activate(const struct firmware *fw,
> +				 struct cdns_mhdp_device *mhdp)
> +{
> +	unsigned int reg;
> +	int ret = 0;
> +
> +	dev_dbg(mhdp->dev, "%s\n", __func__);
> +
> +	if (!fw || !fw->data) {
> +		dev_err(mhdp->dev, "%s: No firmware.\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	spin_lock(&mhdp->start_lock);
> +	if (mhdp->hw_state != MHDP_HW_INACTIVE) {
> +		spin_unlock(&mhdp->start_lock);
> +		if (mhdp->hw_state != MHDP_HW_STOPPED)
> +			dev_err(mhdp->dev, "%s: Bad HW state: %d\n",
> +				__func__, mhdp->hw_state);
> +		return -EBUSY;
> +	}
> +	mhdp->hw_state = MHDP_HW_LOADING;
> +	spin_unlock(&mhdp->start_lock);
> +
> +	/* Release uCPU reset and stall it. */
> +	writel(CDNS_CPU_STALL, mhdp->regs + CDNS_APB_CTRL);
> +
> +	memcpy_toio(mhdp->regs + CDNS_MHDP_IMEM, fw->data, fw->size);
> +
> +	/* Leave debug mode, release stall */
> +	writel(0, mhdp->regs + CDNS_APB_CTRL);
> +
> +	/*
> +	 * Wait for the KEEP_ALIVE "message" on the first 8 bits.
> +	 * Updated each sched "tick" (~2ms)
> +	 */
> +	ret = readl_poll_timeout(mhdp->regs + CDNS_KEEP_ALIVE, reg,
> +				 reg & CDNS_KEEP_ALIVE_MASK, 500,
> +				 CDNS_KEEP_ALIVE_TIMEOUT);
> +	if (ret) {
> +		dev_err(mhdp->dev,
> +			"device didn't give any life sign: reg %d\n", reg);
> +		goto error;
> +	}
> +
> +	ret = cdns_mhdp_check_fw_version(mhdp);
> +	if (ret)
> +		goto error;
> +
> +	/* Init events to 0 as it's not cleared by FW at boot but on read */
> +	readl(mhdp->regs + CDNS_SW_EVENT0);
> +	readl(mhdp->regs + CDNS_SW_EVENT1);
> +	readl(mhdp->regs + CDNS_SW_EVENT2);
> +	readl(mhdp->regs + CDNS_SW_EVENT3);
> +
> +	/* Activate uCPU */
> +	ret = cdns_mhdp_set_firmware_active(mhdp, true);
> +	if (ret) {
> +		dev_err(mhdp->dev, "%s: Failed to activate FW: %d\n",
> +			__func__, ret);
> +		goto error;
> +	}
> +
> +	spin_lock(&mhdp->start_lock);
> +
> +	mhdp->hw_state = MHDP_HW_READY;
> +	wake_up(&fw_load_wq);
> +	/*
> +	 * Here we must keep the lock while enabling the interrupts
> +	 * since it would otherwise be possible that interrupt enable
> +	 * code is executed after the bridge is detached. The similar
> +	 * situation is not possible in attach()/detach() callbacks
> +	 * since the hw_state changes from MHDP_HW_READY to
> +	 * MHDP_HW_STOPPED happens only due to driver removal when
> +	 * bridge should already be detached.
> +	 */
> +	if (mhdp->bridge_attached)
> +		/* enable SW event interrupts */
> +		writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
> +		       mhdp->regs + CDNS_APB_INT_MASK);
> +
> +	spin_unlock(&mhdp->start_lock);
> +
> +	dev_dbg(mhdp->dev, "DP FW activated\n");
> +
> +	return 0;
> +error:
> +	spin_lock(&mhdp->start_lock);
> +	mhdp->hw_state = MHDP_HW_INACTIVE;
> +	spin_unlock(&mhdp->start_lock);
> +
> +	return ret;
> +}
> +
> +static void cdns_mhdp_fw_cb(const struct firmware *fw, void *context)
> +{
> +	struct cdns_mhdp_device *mhdp = context;
> +	bool bridge_attached;
> +	int ret;
> +
> +	dev_dbg(mhdp->dev, "firmware callback\n");
> +
> +	ret = cdns_mhdp_fw_activate(fw, mhdp);
> +
> +	release_firmware(fw);
> +
> +	if (ret)
> +		return;
> +
> +	/*
> +	 *  XXX how to make sure the bridge is still attached when
> +	 *      calling drm_kms_helper_hotplug_event() after releasing
> +	 *      the lock? We should not hold the spin lock when
> +	 *      calling drm_kms_helper_hotplug_event() since it may
> +	 *      cause a dead lock. FB-dev console calls detect from the
> +	 *      same thread just down the call stack started here.
> +	 */
> +	spin_lock(&mhdp->start_lock);
> +	bridge_attached = mhdp->bridge_attached;
> +	spin_unlock(&mhdp->start_lock);
> +	if (bridge_attached)
> +		drm_kms_helper_hotplug_event(mhdp->bridge.dev);
> +}
> +
> +static int cdns_mhdp_load_firmware(struct cdns_mhdp_device *mhdp)
> +{
> +	int ret;
> +
> +	ret = request_firmware_nowait(THIS_MODULE, true, FW_NAME, mhdp->dev,
> +				      GFP_KERNEL, mhdp, cdns_mhdp_fw_cb);
> +	if (ret) {
> +		dev_err(mhdp->dev, "failed to load firmware (%s), ret: %d\n",
> +			FW_NAME, ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static ssize_t cdns_mhdp_transfer(struct drm_dp_aux *aux,
> +				  struct drm_dp_aux_msg *msg)
> +{
> +	struct cdns_mhdp_device *mhdp = dev_get_drvdata(aux->dev);
> +	int ret;
> +
> +	if (msg->request != DP_AUX_NATIVE_WRITE &&
> +	    msg->request != DP_AUX_NATIVE_READ)
> +		return -EOPNOTSUPP;
> +
> +	if (msg->request == DP_AUX_NATIVE_WRITE) {
> +		const u8 *buf = msg->buffer;
> +		int i;
> +
> +		for (i = 0; i < msg->size; ++i) {
> +			ret = cdns_mhdp_dpcd_write(mhdp,
> +						   msg->address + i, buf[i]);
> +			if (!ret)
> +				continue;
> +
> +			DRM_DEV_ERROR(mhdp->dev,
> +				      "Failed to write DPCD addr %u\n",
> +				      msg->address + i);
> +
> +			return ret;
> +		}
> +	} else {
> +		ret = cdns_mhdp_dpcd_read(mhdp, msg->address,
> +					  msg->buffer, msg->size);
> +		if (ret) {
> +			DRM_DEV_ERROR(mhdp->dev,
> +				      "Failed to read DPCD addr %u\n",
> +				      msg->address);
> +
> +			return ret;
> +		}
> +	}
> +
> +	return msg->size;
> +}
> +
> +static int cdns_mhdp_link_training_init(struct cdns_mhdp_device *mhdp)
> +{
> +	u32 reg32;
> +	u8 i;
> +	union phy_configure_opts phy_cfg;
> +	int ret;
> +
> +	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
> +			   DP_TRAINING_PATTERN_DISABLE);
> +
> +	/* Reset PHY configuration */
> +	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1);
> +	if (!mhdp->host.scrambler)
> +		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_ENHNCD,
> +			    mhdp->sink.enhanced & mhdp->host.enhanced);
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_LANE_EN,
> +			    CDNS_DP_LANE_EN_LANES(mhdp->link.num_lanes));
> +
> +	cdns_mhdp_link_configure(&mhdp->aux, &mhdp->link);
> +	phy_cfg.dp.link_rate = (mhdp->link.rate / 100);
> +	phy_cfg.dp.lanes = (mhdp->link.num_lanes);
> +	for (i = 0; i < 4; i++) {
> +		phy_cfg.dp.voltage[i] = 0;
> +		phy_cfg.dp.pre[i] = 0;
> +	}
> +	phy_cfg.dp.ssc = cdns_mhdp_get_ssc_supported(mhdp);
> +	phy_cfg.dp.set_lanes = true;
> +	phy_cfg.dp.set_rate = true;
> +	phy_cfg.dp.set_voltages = true;
> +	ret = phy_configure(mhdp->phy,  &phy_cfg);
> +	if (ret) {
> +		dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
> +			__func__, ret);
> +		return ret;
> +	}
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG,
> +			    CDNS_PHY_COMMON_CONFIG |
> +			    CDNS_PHY_TRAINING_EN |
> +			    CDNS_PHY_TRAINING_TYPE(1) |
> +			    CDNS_PHY_SCRAMBLER_BYPASS);
> +
> +	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
> +			   DP_TRAINING_PATTERN_1 | DP_LINK_SCRAMBLING_DISABLE);
> +
> +	return 0;
> +}
> +
> +static void cdns_mhdp_get_adjust_train(struct cdns_mhdp_device *mhdp,
> +				       u8 link_status[DP_LINK_STATUS_SIZE],
> +				       u8 lanes_data[CDNS_DP_MAX_NUM_LANES],
> +				       union phy_configure_opts *phy_cfg)
> +{
> +	unsigned int i;
> +	u8 adjust, max_pre_emph, max_volt_swing;
> +	u8 set_volt, set_pre;
> +
> +	max_pre_emph = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis)
> +			   << DP_TRAIN_PRE_EMPHASIS_SHIFT;
> +	max_volt_swing = CDNS_VOLT_SWING(mhdp->host.volt_swing);
> +
> +	for (i = 0; i < mhdp->link.num_lanes; i++) {
> +		/* Check if Voltage swing and pre-emphasis are within limits */
> +		adjust = drm_dp_get_adjust_request_voltage(link_status, i);
> +		set_volt = min(adjust, max_volt_swing);
> +
> +		adjust = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
> +		set_pre = min(adjust, max_pre_emph)
> +			  >> DP_TRAIN_PRE_EMPHASIS_SHIFT;
> +
> +		/* Voltage swing level and pre-emphasis level combination is
> +		 * not allowed: leaving pre-emphasis as-is, and adjusting
> +		 * voltage swing.
> +		 */
> +		if (set_volt + set_pre > 3)
> +			set_volt = 3 - set_pre;
> +
> +		phy_cfg->dp.voltage[i] = set_volt;
> +		lanes_data[i] = set_volt;
> +
> +		if (set_volt == max_volt_swing)
> +			lanes_data[i] |= DP_TRAIN_MAX_SWING_REACHED;
> +
> +		phy_cfg->dp.pre[i] = set_pre;
> +		lanes_data[i] |= (set_pre << DP_TRAIN_PRE_EMPHASIS_SHIFT);
> +
> +		if (set_pre == (max_pre_emph >> DP_TRAIN_PRE_EMPHASIS_SHIFT))
> +			lanes_data[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
> +	}
> +}
> +
> +static
> +void cdns_mhdp_set_adjust_request_voltage(u8 link_status[DP_LINK_STATUS_SIZE],
> +					  int lane, u8 volt)
> +{
> +	int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
> +	int s = ((lane & 1) ?
> +		 DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT :
> +		 DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT);
> +	int idx = i - DP_LANE0_1_STATUS;
> +
> +	link_status[idx] &= ~(DP_ADJUST_VOLTAGE_SWING_LANE0_MASK << s);
> +	link_status[idx] |= volt << s;
> +}
> +
> +static
> +void cdns_mhdp_set_adjust_request_pre_emphasis(u8 link_status[DP_LINK_STATUS_SIZE],
> +					       int lane, u8 pre_emphasis)
> +{
> +	int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
> +	int s = ((lane & 1) ?
> +		 DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT :
> +		 DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT);
> +	int idx = i - DP_LANE0_1_STATUS;
> +
> +	link_status[idx] &= ~(DP_ADJUST_PRE_EMPHASIS_LANE0_MASK << s);
> +	link_status[idx] |= pre_emphasis << s;
> +}
> +
> +static void cdns_mhdp_adjust_requested_eq(struct cdns_mhdp_device *mhdp,
> +					  u8 link_status[DP_LINK_STATUS_SIZE])
> +{
> +	u8 max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis);
> +	u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing);
> +	unsigned int i;
> +	u8 volt, pre;
> +
> +	for (i = 0; i < mhdp->link.num_lanes; i++) {
> +		volt = drm_dp_get_adjust_request_voltage(link_status, i);
> +		pre = drm_dp_get_adjust_request_pre_emphasis(link_status, i);
> +		if (volt + pre > 3)
> +			cdns_mhdp_set_adjust_request_voltage(link_status, i,
> +							     3 - pre);
> +		if (mhdp->host.volt_swing & CDNS_FORCE_VOLT_SWING)
> +			cdns_mhdp_set_adjust_request_voltage(link_status, i,
> +							     max_volt);
> +		if (mhdp->host.pre_emphasis & CDNS_FORCE_PRE_EMPHASIS)
> +			cdns_mhdp_set_adjust_request_pre_emphasis(link_status,
> +								  i, max_pre);
> +	}
> +}
> +
> +static void cdns_mhdp_print_lt_status(const char *prefix,
> +				      struct cdns_mhdp_device *mhdp,
> +				      union phy_configure_opts *phy_cfg)
> +{
> +	int i;
> +	char vs_str[8];
> +	char pe_str[8];
> +	char *vs_p, *pe_p;
> +
> +	vs_p = vs_str;
> +	pe_p = pe_str;
> +
> +	for (i = 0; i < mhdp->link.num_lanes; i++) {
> +		if (i != 0) {
> +			*vs_p++ = '/';
> +			*pe_p++ = '/';
> +		}
> +
> +		*vs_p++ = '0' + phy_cfg->dp.voltage[i];
> +		*pe_p++ = '0' + phy_cfg->dp.pre[i];
> +	}
> +
> +	*vs_p = 0;
> +	*pe_p = 0;
> +
> +	dev_dbg(mhdp->dev, "%s, %u lanes, %u Mbps, vs %s, pe %s\n",
> +		prefix,
> +		mhdp->link.num_lanes, mhdp->link.rate / 100,
> +		vs_str, pe_str);
> +}
> +
> +static bool cdns_mhdp_link_training_channel_eq(struct cdns_mhdp_device *mhdp,
> +					       u8 eq_tps,
> +					       unsigned int training_interval)
> +{
> +	u8 lanes_data[CDNS_DP_MAX_NUM_LANES], fail_counter_short = 0;
> +	u8 link_status[DP_LINK_STATUS_SIZE];
> +	u32 reg32;
> +	union phy_configure_opts phy_cfg;
> +	int ret;
> +	bool r;
> +
> +	dev_dbg(mhdp->dev, "Starting EQ phase\n");
> +
> +	/* Enable link training TPS[eq_tps] in PHY */
> +	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_EN |
> +		CDNS_PHY_TRAINING_TYPE(eq_tps);
> +	if (eq_tps != 4)
> +		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
> +
> +	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
> +			   (eq_tps != 4) ? eq_tps | DP_LINK_SCRAMBLING_DISABLE :
> +			   CDNS_DP_TRAINING_PATTERN_4);
> +
> +	drm_dp_dpcd_read_link_status(&mhdp->aux, link_status);
> +
> +	do {
> +		cdns_mhdp_get_adjust_train(mhdp, link_status, lanes_data,
> +					   &phy_cfg);
> +		phy_cfg.dp.lanes = (mhdp->link.num_lanes);
> +		phy_cfg.dp.ssc = cdns_mhdp_get_ssc_supported(mhdp);
> +		phy_cfg.dp.set_lanes = false;
> +		phy_cfg.dp.set_rate = false;
> +		phy_cfg.dp.set_voltages = true;
> +		ret = phy_configure(mhdp->phy,  &phy_cfg);
> +		if (ret) {
> +			dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
> +				__func__, ret);
> +			goto err;
> +		}
> +
> +		cdns_mhdp_adjust_lt(mhdp, mhdp->link.num_lanes,
> +				    training_interval, lanes_data, link_status);
> +
> +		r = drm_dp_clock_recovery_ok(link_status, mhdp->link.num_lanes);
> +		if (!r)
> +			goto err;
> +
> +		if (drm_dp_channel_eq_ok(link_status, mhdp->link.num_lanes)) {
> +			cdns_mhdp_print_lt_status("EQ phase ok", mhdp,
> +						  &phy_cfg);
> +			return true;
> +		}
> +
> +		fail_counter_short++;
> +
> +		cdns_mhdp_adjust_requested_eq(mhdp, link_status);
> +	} while (fail_counter_short < 5);
> +
> +err:
> +	cdns_mhdp_print_lt_status("EQ phase failed", mhdp, &phy_cfg);
> +
> +	return false;
> +}
> +
> +static void cdns_mhdp_adjust_requested_cr(struct cdns_mhdp_device *mhdp,
> +					  u8 link_status[DP_LINK_STATUS_SIZE],
> +					  u8 *req_volt, u8 *req_pre)
> +{
> +	const u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing);
> +	const u8 max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis);
> +	unsigned int i;
> +
> +	for (i = 0; i < mhdp->link.num_lanes; i++) {
> +		u8 val;
> +
> +		val = mhdp->host.volt_swing & CDNS_FORCE_VOLT_SWING ?
> +		      max_volt : req_volt[i];
> +		cdns_mhdp_set_adjust_request_voltage(link_status, i, val);
> +
> +		val = mhdp->host.pre_emphasis & CDNS_FORCE_PRE_EMPHASIS ?
> +		      max_pre : req_pre[i];
> +		cdns_mhdp_set_adjust_request_pre_emphasis(link_status, i, val);
> +	}
> +}
> +
> +static
> +void cdns_mhdp_validate_cr(struct cdns_mhdp_device *mhdp, bool *cr_done,
> +			   bool *same_before_adjust, bool *max_swing_reached,
> +			   u8 before_cr[DP_LINK_STATUS_SIZE],
> +			   u8 after_cr[DP_LINK_STATUS_SIZE], u8 *req_volt,
> +			   u8 *req_pre)
> +{
> +	const u8 max_volt = CDNS_VOLT_SWING(mhdp->host.volt_swing);
> +	const u8 max_pre = CDNS_PRE_EMPHASIS(mhdp->host.pre_emphasis);
> +	bool same_pre, same_volt;
> +	unsigned int i;
> +	u8 adjust;
> +
> +	*same_before_adjust = false;
> +	*max_swing_reached = false;
> +	*cr_done = drm_dp_clock_recovery_ok(after_cr, mhdp->link.num_lanes);
> +
> +	for (i = 0; i < mhdp->link.num_lanes; i++) {
> +		adjust = drm_dp_get_adjust_request_voltage(after_cr, i);
> +		req_volt[i] = min(adjust, max_volt);
> +
> +		adjust = drm_dp_get_adjust_request_pre_emphasis(after_cr, i) >>
> +		      DP_TRAIN_PRE_EMPHASIS_SHIFT;
> +		req_pre[i] = min(adjust, max_pre);
> +
> +		same_pre = (before_cr[i] & DP_TRAIN_PRE_EMPHASIS_MASK) ==
> +			   req_pre[i] << DP_TRAIN_PRE_EMPHASIS_SHIFT;
> +		same_volt = (before_cr[i] & DP_TRAIN_VOLTAGE_SWING_MASK) ==
> +			    req_volt[i];
> +		if (same_pre && same_volt)
> +			*same_before_adjust = true;
> +
> +		/* 3.1.5.2 in DP Standard v1.4. Table 3-1 */
> +		if (!*cr_done && req_volt[i] + req_pre[i] >= 3) {
> +			*max_swing_reached = true;
> +			return;
> +		}
> +	}
> +}
> +
> +static bool cdns_mhdp_link_training_cr(struct cdns_mhdp_device *mhdp)
> +{
> +	u8 lanes_data[CDNS_DP_MAX_NUM_LANES],
> +	fail_counter_short = 0, fail_counter_cr_long = 0;
> +	u8 link_status[DP_LINK_STATUS_SIZE];
> +	bool cr_done;
> +	union phy_configure_opts phy_cfg;
> +	int ret;
> +
> +	dev_dbg(mhdp->dev, "Starting CR phase\n");
> +
> +	ret = cdns_mhdp_link_training_init(mhdp);
> +	if (ret)
> +		goto err;
> +
> +	drm_dp_dpcd_read_link_status(&mhdp->aux, link_status);
> +
> +	do {
> +		u8 requested_adjust_volt_swing[CDNS_DP_MAX_NUM_LANES] = {};
> +		u8 requested_adjust_pre_emphasis[CDNS_DP_MAX_NUM_LANES] = {};
> +		bool same_before_adjust, max_swing_reached;
> +
> +		cdns_mhdp_get_adjust_train(mhdp, link_status, lanes_data,
> +					   &phy_cfg);
> +		phy_cfg.dp.lanes = (mhdp->link.num_lanes);
> +		phy_cfg.dp.ssc = cdns_mhdp_get_ssc_supported(mhdp);
> +		phy_cfg.dp.set_lanes = false;
> +		phy_cfg.dp.set_rate = false;
> +		phy_cfg.dp.set_voltages = true;
> +		ret = phy_configure(mhdp->phy,  &phy_cfg);
> +		if (ret) {
> +			dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
> +				__func__, ret);
> +			goto err;
> +		}
> +
> +		cdns_mhdp_adjust_lt(mhdp, mhdp->link.num_lanes, 100,
> +				    lanes_data, link_status);
> +
> +		cdns_mhdp_validate_cr(mhdp, &cr_done, &same_before_adjust,
> +				      &max_swing_reached, lanes_data,
> +				      link_status,
> +				      requested_adjust_volt_swing,
> +				      requested_adjust_pre_emphasis);
> +
> +		if (max_swing_reached) {
> +			dev_err(mhdp->dev, "CR: max swing reached\n");
> +			goto err;
> +		}
> +
> +		if (cr_done) {
> +			cdns_mhdp_print_lt_status("CR phase ok", mhdp,
> +						  &phy_cfg);
> +			return true;
> +		}
> +
> +		/* Not all CR_DONE bits set */
> +		fail_counter_cr_long++;
> +
> +		if (same_before_adjust) {
> +			fail_counter_short++;
> +			continue;
> +		}
> +
> +		fail_counter_short = 0;
> +		/*
> +		 * Voltage swing/pre-emphasis adjust requested
> +		 * during CR phase
> +		 */
> +		cdns_mhdp_adjust_requested_cr(mhdp, link_status,
> +					      requested_adjust_volt_swing,
> +					      requested_adjust_pre_emphasis);
> +	} while (fail_counter_short < 5 && fail_counter_cr_long < 10);
> +
> +err:
> +	cdns_mhdp_print_lt_status("CR phase failed", mhdp, &phy_cfg);
> +
> +	return false;
> +}
> +
> +static void cdns_mhdp_lower_link_rate(struct cdns_mhdp_link *link)
> +{
> +	switch (drm_dp_link_rate_to_bw_code(link->rate)) {
> +	case DP_LINK_BW_2_7:
> +		link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_1_62);
> +		break;
> +	case DP_LINK_BW_5_4:
> +		link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_2_7);
> +		break;
> +	case DP_LINK_BW_8_1:
> +		link->rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_5_4);
> +		break;
> +	}
> +}
> +
> +static int cdns_mhdp_link_training(struct cdns_mhdp_device *mhdp,
> +				   unsigned int training_interval)
> +{
> +	u32 reg32;
> +	const u8 eq_tps = cdns_mhdp_eq_training_pattern_supported(mhdp);
> +	int ret;
> +
> +	while (1) {
> +		if (!cdns_mhdp_link_training_cr(mhdp)) {
> +			if (drm_dp_link_rate_to_bw_code(mhdp->link.rate) !=
> +			    DP_LINK_BW_1_62) {
> +				dev_dbg(mhdp->dev,
> +					"Reducing link rate during CR phase\n");
> +				cdns_mhdp_lower_link_rate(&mhdp->link);
> +
> +				continue;
> +			} else if (mhdp->link.num_lanes > 1) {
> +				dev_dbg(mhdp->dev,
> +					"Reducing lanes number during CR phase\n");
> +				mhdp->link.num_lanes >>= 1;
> +				mhdp->link.rate = cdns_mhdp_max_link_rate(mhdp);
> +
> +				continue;
> +			}
> +
> +			dev_err(mhdp->dev,
> +				"Link training failed during CR phase\n");
> +			goto err;
> +		}
> +
> +		if (cdns_mhdp_link_training_channel_eq(mhdp, eq_tps,
> +						       training_interval))
> +			break;
> +
> +		if (mhdp->link.num_lanes > 1) {
> +			dev_dbg(mhdp->dev,
> +				"Reducing lanes number during EQ phase\n");
> +			mhdp->link.num_lanes >>= 1;
> +
> +			continue;
> +		} else if (drm_dp_link_rate_to_bw_code(mhdp->link.rate) !=
> +			   DP_LINK_BW_1_62) {
> +			dev_dbg(mhdp->dev,
> +				"Reducing link rate during EQ phase\n");
> +			cdns_mhdp_lower_link_rate(&mhdp->link);
> +			mhdp->link.num_lanes = cdns_mhdp_max_num_lanes(mhdp);
> +
> +			continue;
> +		}
> +
> +		dev_err(mhdp->dev, "Link training failed during EQ phase\n");
> +		goto err;
> +	}
> +
> +	dev_dbg(mhdp->dev, "Link training ok. Lanes: %u, Rate %u Mbps\n",
> +		mhdp->link.num_lanes, mhdp->link.rate / 100);
> +
> +	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
> +			   mhdp->host.scrambler ? 0 :
> +			   DP_LINK_SCRAMBLING_DISABLE);
> +
> +	ret = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &reg32);
> +	if (ret < 0) {
> +		dev_err(mhdp->dev,
> +			"Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n",
> +			ret);
> +		return ret;
> +	}
> +	reg32 &= ~GENMASK(1, 0);
> +	reg32 |= CDNS_DP_NUM_LANES(mhdp->link.num_lanes);
> +	reg32 |= CDNS_DP_WR_FAILING_EDGE_VSYNC;
> +	reg32 |= CDNS_DP_FRAMER_EN;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, reg32);
> +
> +	/* Reset PHY config */
> +	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1);
> +	if (!mhdp->host.scrambler)
> +		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
> +
> +	return 0;
> +err:
> +	/* Reset PHY config */
> +	reg32 = CDNS_PHY_COMMON_CONFIG | CDNS_PHY_TRAINING_TYPE(1);
> +	if (!mhdp->host.scrambler)
> +		reg32 |= CDNS_PHY_SCRAMBLER_BYPASS;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_PHY_CONFIG, reg32);
> +
> +	drm_dp_dpcd_writeb(&mhdp->aux, DP_TRAINING_PATTERN_SET,
> +			   DP_TRAINING_PATTERN_DISABLE);
> +
> +	return -EIO;
> +}
> +
> +static u32 cdns_mhdp_get_training_interval_us(struct cdns_mhdp_device *mhdp,
> +					      u32 interval)
> +{
> +	if (interval == 0)
> +		return 400;
> +	if (interval < 5)
> +		return 4000 << (interval - 1);
> +	dev_err(mhdp->dev,
> +		"wrong training interval returned by DPCD: %d\n", interval);
> +	return 0;
> +}
> +
> +static void cdns_mhdp_fill_host_caps(struct cdns_mhdp_device *mhdp)
> +{
> +	unsigned int link_rate;
> +	struct phy_attrs attrs;
> +
> +	/* Get source capabilities based on PHY attributes */
> +	phy_get_attrs(mhdp->phy, &attrs);
> +
> +	mhdp->host.lanes_cnt = attrs.bus_width;
> +	if (!mhdp->host.lanes_cnt)
> +		mhdp->host.lanes_cnt = 4;
> +
> +	link_rate = attrs.max_link_rate;
> +	if (!link_rate)
> +		link_rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_8_1);
> +	else
> +		/* PHY uses Mb/s, DRM uses tens of kb/s. */
> +		link_rate *= 100;
> +
> +	mhdp->host.link_rate = link_rate;
> +	mhdp->host.volt_swing = CDNS_VOLT_SWING(3);
> +	mhdp->host.pre_emphasis = CDNS_PRE_EMPHASIS(3);
> +	mhdp->host.pattern_supp = CDNS_SUPPORT_TPS(1) |
> +				  CDNS_SUPPORT_TPS(2) | CDNS_SUPPORT_TPS(3) |
> +				  CDNS_SUPPORT_TPS(4);
> +	mhdp->host.lane_mapping = CDNS_LANE_MAPPING_NORMAL;
> +	mhdp->host.fast_link = false;
> +	mhdp->host.enhanced = true;
> +	mhdp->host.scrambler = true;
> +	mhdp->host.ssc = false;
> +}
> +
> +static void cdns_mhdp_fill_sink_caps(struct cdns_mhdp_device *mhdp,
> +				     u8 dpcd[DP_RECEIVER_CAP_SIZE])
> +{
> +	mhdp->sink.link_rate = mhdp->link.rate;
> +	mhdp->sink.lanes_cnt = mhdp->link.num_lanes;
> +	mhdp->sink.enhanced = !!(mhdp->link.capabilities &
> +				 DP_LINK_CAP_ENHANCED_FRAMING);
> +
> +	/* Set SSC support */
> +	mhdp->sink.ssc = !!(dpcd[DP_MAX_DOWNSPREAD] &
> +				  DP_MAX_DOWNSPREAD_0_5);
> +
> +	/* Set TPS support */
> +	mhdp->sink.pattern_supp = CDNS_SUPPORT_TPS(1) | CDNS_SUPPORT_TPS(2);
> +	if (drm_dp_tps3_supported(dpcd))
> +		mhdp->sink.pattern_supp |= CDNS_SUPPORT_TPS(3);
> +	if (drm_dp_tps4_supported(dpcd))
> +		mhdp->sink.pattern_supp |= CDNS_SUPPORT_TPS(4);
> +
> +	/* Set fast link support */
> +	mhdp->sink.fast_link = !!(dpcd[DP_MAX_DOWNSPREAD] &
> +				  DP_NO_AUX_HANDSHAKE_LINK_TRAINING);
> +}
> +
> +static int cdns_mhdp_link_up(struct cdns_mhdp_device *mhdp)
> +{
> +	u32 resp;
> +	u8 dpcd[DP_RECEIVER_CAP_SIZE], amp[2];
> +	u8 ext_cap_chk = 0;
> +	unsigned int addr;
> +	int err;
> +
> +	WARN_ON(!mutex_is_locked(&mhdp->link_mutex));
> +
> +	drm_dp_dpcd_readb(&mhdp->aux, DP_TRAINING_AUX_RD_INTERVAL,
> +			  &ext_cap_chk);
> +
> +	if (ext_cap_chk & DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT)
> +		addr = DP_DP13_DPCD_REV;
> +	else
> +		addr = DP_DPCD_REV;
> +
> +	err = drm_dp_dpcd_read(&mhdp->aux, addr, dpcd, DP_RECEIVER_CAP_SIZE);
> +	if (err < 0) {
> +		dev_err(mhdp->dev, "Failed to read receiver capabilities\n");
> +		return err;
> +	}
> +
> +	mhdp->link.revision = dpcd[0];
> +	mhdp->link.rate = drm_dp_bw_code_to_link_rate(dpcd[1]);
> +	mhdp->link.num_lanes = dpcd[2] & DP_MAX_LANE_COUNT_MASK;
> +
> +	if (dpcd[2] & DP_ENHANCED_FRAME_CAP)
> +		mhdp->link.capabilities |= DP_LINK_CAP_ENHANCED_FRAMING;
> +
> +	dev_dbg(mhdp->dev, "Set sink device power state via DPCD\n");
> +	cdns_mhdp_link_power_up(&mhdp->aux, &mhdp->link);
> +
> +	cdns_mhdp_fill_sink_caps(mhdp, dpcd);
> +
> +	mhdp->link.rate = cdns_mhdp_max_link_rate(mhdp);
> +	mhdp->link.num_lanes = cdns_mhdp_max_num_lanes(mhdp);
> +
> +	/* Disable framer for link training */
> +	err = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &resp);
> +	if (err < 0) {
> +		dev_err(mhdp->dev,
> +			"Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n",
> +			err);
> +		return err;
> +	}
> +
> +	resp &= ~CDNS_DP_FRAMER_EN;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, resp);
> +
> +	/* Spread AMP if required, enable 8b/10b coding */
> +	amp[0] = cdns_mhdp_get_ssc_supported(mhdp) ? DP_SPREAD_AMP_0_5 : 0;
> +	amp[1] = DP_SET_ANSI_8B10B;
> +	drm_dp_dpcd_write(&mhdp->aux, DP_DOWNSPREAD_CTRL, amp, 2);
> +
> +	if (mhdp->host.fast_link & mhdp->sink.fast_link) {
> +		dev_err(mhdp->dev, "fastlink not supported\n");
> +		err = -EOPNOTSUPP;
> +		goto error;
> +	} else {
> +		const u32 interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] &
> +				     DP_TRAINING_AUX_RD_MASK;
> +		const u32 interval_us = cdns_mhdp_get_training_interval_us(mhdp,
> +									   interval);
> +		if (!interval_us ||
> +		    cdns_mhdp_link_training(mhdp, interval_us)) {
> +			dev_err(mhdp->dev, "Link training failed. Exiting.\n");
> +			err = -EIO;
> +			goto error;
> +		}
> +	}
> +
> +	mhdp->link_up = true;
> +
> +	return 0;
> +error:
> +	return err;
> +}
> +
> +static void cdns_mhdp_link_down(struct cdns_mhdp_device *mhdp)
> +{
> +	WARN_ON(!mutex_is_locked(&mhdp->link_mutex));
> +
> +	if (mhdp->plugged)
> +		cdns_mhdp_link_power_down(&mhdp->aux, &mhdp->link);
> +
> +	mhdp->link_up = false;
> +}
> +
> +static int cdns_mhdp_get_modes(struct drm_connector *connector)
> +{
> +	struct cdns_mhdp_device *mhdp = connector_to_mhdp(connector);
> +	struct edid *edid;
> +	int num_modes;
> +	int ret;
> +
> +	if (!mhdp->plugged)
> +		return 0;
> +
> +	mutex_lock(&mhdp->link_mutex);
> +
> +	if (!mhdp->link_up) {
> +		ret = cdns_mhdp_link_up(mhdp);
> +		if (ret < 0) {
> +			mutex_unlock(&mhdp->link_mutex);
> +			return 0;
> +		}
> +	}
> +	mutex_unlock(&mhdp->link_mutex);
> +
> +	edid = drm_do_get_edid(connector, cdns_mhdp_get_edid_block, mhdp);
> +	if (!edid) {
> +		DRM_DEV_ERROR(mhdp->dev, "Failed to read EDID\n");
> +
> +		return 0;
> +	}
> +
> +	drm_connector_update_edid_property(connector, edid);
> +	num_modes = drm_add_edid_modes(connector, edid);
> +	kfree(edid);
> +
> +	/*
> +	 * HACK: Warn about unsupported display formats until we deal
> +	 *       with them correctly.
> +	 */
> +	if (connector->display_info.color_formats &&
> +	    !(connector->display_info.color_formats &
> +	      mhdp->display_fmt.color_format))
> +		dev_warn(mhdp->dev,
> +			 "%s: No supported color_format found (0x%08x)\n",
> +			__func__, connector->display_info.color_formats);
> +
> +	if (connector->display_info.bpc &&
> +	    connector->display_info.bpc < mhdp->display_fmt.bpc)
> +		dev_warn(mhdp->dev, "%s: Display bpc only %d < %d\n",
> +			 __func__, connector->display_info.bpc,
> +			 mhdp->display_fmt.bpc);
> +
> +	return num_modes;
> +}
> +
> +static int cdns_mhdp_detect(struct drm_connector *conn,
> +			    struct drm_modeset_acquire_ctx *ctx,
> +			    bool force)
> +{
> +	struct cdns_mhdp_device *mhdp = connector_to_mhdp(conn);
> +
> +	dev_dbg(mhdp->dev, "%s: %d\n", __func__, mhdp->plugged);
> +
> +	if (mhdp->plugged)
> +		return connector_status_connected;
> +	else
> +		return connector_status_disconnected;
> +}
> +
> +static u32 cdns_mhdp_get_bpp(struct cdns_mhdp_display_fmt *fmt)
> +{
> +	u32 bpp;
> +
> +	if (fmt->y_only)
> +		return fmt->bpc;
> +
> +	switch (fmt->color_format) {
> +	case DRM_COLOR_FORMAT_RGB444:
> +	case DRM_COLOR_FORMAT_YCRCB444:
> +		bpp = fmt->bpc * 3;
> +		break;
> +	case DRM_COLOR_FORMAT_YCRCB422:
> +		bpp = fmt->bpc * 2;
> +		break;
> +	case DRM_COLOR_FORMAT_YCRCB420:
> +		bpp = fmt->bpc * 3 / 2;
> +		break;
> +	default:
> +		bpp = fmt->bpc * 3;
> +		WARN_ON(1);
> +	}
> +	return bpp;
> +}
> +
> +static
> +bool cdns_mhdp_bandwidth_ok(struct cdns_mhdp_device *mhdp,
> +			    const struct drm_display_mode *mode,
> +			    int lanes, int rate)
> +{
> +	u32 max_bw, req_bw, bpp;
> +
> +	bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt);
> +	req_bw = mode->clock * bpp / 8;
> +	max_bw = lanes * rate;
> +	if (req_bw > max_bw) {
> +		dev_dbg(mhdp->dev,
> +			"Unsupported Mode: %s, Req BW: %u, Available Max BW:%u\n",
> +			mode->name, req_bw, max_bw);
> +
> +		return false;
> +	}
> +
> +	return true;
> +}
> +
> +static
> +enum drm_mode_status cdns_mhdp_mode_valid(struct drm_connector *conn,
> +					  struct drm_display_mode *mode)
> +{
> +	struct cdns_mhdp_device *mhdp = connector_to_mhdp(conn);
> +
> +	if (!cdns_mhdp_bandwidth_ok(mhdp, mode, mhdp->link.num_lanes,
> +				    mhdp->link.rate))
> +		return MODE_CLOCK_HIGH;
> +
> +	return MODE_OK;
> +}
> +
> +static const struct drm_connector_helper_funcs cdns_mhdp_conn_helper_funcs = {
> +	.detect_ctx = cdns_mhdp_detect,
> +	.get_modes = cdns_mhdp_get_modes,
> +	.mode_valid = cdns_mhdp_mode_valid,
> +};
> +
> +static const struct drm_connector_funcs cdns_mhdp_conn_funcs = {
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +	.reset = drm_atomic_helper_connector_reset,
> +	.destroy = drm_connector_cleanup,
> +};
> +
> +static int cdns_mhdp_attach(struct drm_bridge *bridge,
> +			    enum drm_bridge_attach_flags flags)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
> +	u32 bus_format = MEDIA_BUS_FMT_RGB121212_1X36;
> +	struct drm_connector *conn = &mhdp->connector;
> +	bool hw_ready;
> +	int ret;
> +
> +	dev_dbg(mhdp->dev, "%s\n", __func__);
> +
> +	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
> +		DRM_ERROR("Fix bridge driver to make connector optional!");
> +		return -EINVAL;
> +	}

For new bridge drivers, the connector needs to be made optional.

> +
> +	if (&mhdp->bridge != bridge)
> +		return -ENODEV;
> +
> +	conn->polled = DRM_CONNECTOR_POLL_HPD;
> +
> +	ret = drm_connector_init(bridge->dev, conn, &cdns_mhdp_conn_funcs,
> +				 DRM_MODE_CONNECTOR_DisplayPort);
> +	if (ret) {
> +		dev_err(mhdp->dev, "failed to init connector\n");
> +		return ret;
> +	}
> +
> +	drm_connector_helper_add(conn, &cdns_mhdp_conn_helper_funcs);
> +
> +	ret = drm_display_info_set_bus_formats(&conn->display_info,
> +					       &bus_format, 1);
> +	if (ret)
> +		return ret;
> +
> +	conn->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH;
> +
> +	ret = drm_connector_attach_encoder(conn, bridge->encoder);
> +	if (ret) {
> +		dev_err(mhdp->dev, "failed to attach connector to encoder\n");
> +		return ret;
> +	}
> +
> +	spin_lock(&mhdp->start_lock);
> +
> +	mhdp->bridge_attached = true;
> +	hw_ready = mhdp->hw_state == MHDP_HW_READY;
> +
> +	spin_unlock(&mhdp->start_lock);
> +
> +	if (hw_ready)
> +		/* enable SW event interrupts */
> +		writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
> +		       mhdp->regs + CDNS_APB_INT_MASK);
> +
> +	return 0;
> +}
> +
> +static void cdns_mhdp_configure_video(struct cdns_mhdp_device *mhdp,
> +				      const struct drm_display_mode *mode)
> +{
> +	unsigned int dp_framer_sp = 0, msa_horizontal_1,
> +		msa_vertical_1, bnd_hsync2vsync, hsync2vsync_pol_ctrl,
> +		misc0 = 0, misc1 = 0, pxl_repr,
> +		front_porch, back_porch, msa_h0, msa_v0, hsync, vsync,
> +		dp_vertical_1;
> +	u32 bpp, bpc, pxlfmt;
> +	u32 framer;
> +	u8 stream_id = mhdp->stream_id;
> +	int ret;
> +
> +	pxlfmt = mhdp->display_fmt.color_format;
> +	bpc = mhdp->display_fmt.bpc;
> +
> +	/* If YCBCR supported and stream not SD, use ITU709
> +	 * Need to handle ITU version with YCBCR420 when supported
> +	 */
> +	if ((pxlfmt == DRM_COLOR_FORMAT_YCRCB444 ||
> +	     pxlfmt == DRM_COLOR_FORMAT_YCRCB422) && mode->crtc_vdisplay >= 720)
> +		misc0 = DP_YCBCR_COEFFICIENTS_ITU709;
> +
> +	bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt);
> +
> +	switch (pxlfmt) {
> +	case DRM_COLOR_FORMAT_RGB444:
> +		pxl_repr = CDNS_DP_FRAMER_RGB << CDNS_DP_FRAMER_PXL_FORMAT;
> +		misc0 |= DP_COLOR_FORMAT_RGB;
> +		break;
> +	case DRM_COLOR_FORMAT_YCRCB444:
> +		pxl_repr = CDNS_DP_FRAMER_YCBCR444 << CDNS_DP_FRAMER_PXL_FORMAT;
> +		misc0 |= DP_COLOR_FORMAT_YCbCr444 | DP_TEST_DYNAMIC_RANGE_CEA;
> +		break;
> +	case DRM_COLOR_FORMAT_YCRCB422:
> +		pxl_repr = CDNS_DP_FRAMER_YCBCR422 << CDNS_DP_FRAMER_PXL_FORMAT;
> +		misc0 |= DP_COLOR_FORMAT_YCbCr422 | DP_TEST_DYNAMIC_RANGE_CEA;
> +		break;
> +	case DRM_COLOR_FORMAT_YCRCB420:
> +		pxl_repr = CDNS_DP_FRAMER_YCBCR420 << CDNS_DP_FRAMER_PXL_FORMAT;
> +		break;
> +	default:
> +		pxl_repr = CDNS_DP_FRAMER_Y_ONLY << CDNS_DP_FRAMER_PXL_FORMAT;
> +	}
> +
> +	switch (bpc) {
> +	case 6:
> +		misc0 |= DP_TEST_BIT_DEPTH_6;
> +		pxl_repr |= CDNS_DP_FRAMER_6_BPC;
> +		break;
> +	case 8:
> +		misc0 |= DP_TEST_BIT_DEPTH_8;
> +		pxl_repr |= CDNS_DP_FRAMER_8_BPC;
> +		break;
> +	case 10:
> +		misc0 |= DP_TEST_BIT_DEPTH_10;
> +		pxl_repr |= CDNS_DP_FRAMER_10_BPC;
> +		break;
> +	case 12:
> +		misc0 |= DP_TEST_BIT_DEPTH_12;
> +		pxl_repr |= CDNS_DP_FRAMER_12_BPC;
> +		break;
> +	case 16:
> +		misc0 |= DP_TEST_BIT_DEPTH_16;
> +		pxl_repr |= CDNS_DP_FRAMER_16_BPC;
> +		break;
> +	}
> +
> +	bnd_hsync2vsync = CDNS_IP_BYPASS_V_INTERFACE;
> +	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
> +		bnd_hsync2vsync |= CDNS_IP_DET_INTERLACE_FORMAT;
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_BND_HSYNC2VSYNC(stream_id),
> +			    bnd_hsync2vsync);
> +
> +	hsync2vsync_pol_ctrl = 0;
> +	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
> +		hsync2vsync_pol_ctrl |= CDNS_H2V_HSYNC_POL_ACTIVE_LOW;
> +	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
> +		hsync2vsync_pol_ctrl |= CDNS_H2V_VSYNC_POL_ACTIVE_LOW;
> +	cdns_mhdp_reg_write(mhdp, CDNS_HSYNC2VSYNC_POL_CTRL(stream_id),
> +			    hsync2vsync_pol_ctrl);
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_PXL_REPR(stream_id), pxl_repr);
> +
> +	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
> +		dp_framer_sp |= CDNS_DP_FRAMER_INTERLACE;
> +	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
> +		dp_framer_sp |= CDNS_DP_FRAMER_HSYNC_POL_LOW;
> +	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
> +		dp_framer_sp |= CDNS_DP_FRAMER_VSYNC_POL_LOW;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_SP(stream_id), dp_framer_sp);
> +
> +	front_porch = mode->crtc_hsync_start - mode->crtc_hdisplay;
> +	back_porch = mode->crtc_htotal - mode->crtc_hsync_end;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRONT_BACK_PORCH(stream_id),
> +			    CDNS_DP_FRONT_PORCH(front_porch) |
> +			    CDNS_DP_BACK_PORCH(back_porch));
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_BYTE_COUNT(stream_id),
> +			    mode->crtc_hdisplay * bpp / 8);
> +
> +	msa_h0 = mode->crtc_htotal - mode->crtc_hsync_start;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_HORIZONTAL_0(stream_id),
> +			    CDNS_DP_MSAH0_H_TOTAL(mode->crtc_htotal) |
> +			    CDNS_DP_MSAH0_HSYNC_START(msa_h0));
> +
> +	hsync = mode->crtc_hsync_end - mode->crtc_hsync_start;
> +	msa_horizontal_1 = CDNS_DP_MSAH1_HSYNC_WIDTH(hsync) |
> +			   CDNS_DP_MSAH1_HDISP_WIDTH(mode->crtc_hdisplay);
> +	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
> +		msa_horizontal_1 |= CDNS_DP_MSAH1_HSYNC_POL_LOW;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_HORIZONTAL_1(stream_id),
> +			    msa_horizontal_1);
> +
> +	msa_v0 = mode->crtc_vtotal - mode->crtc_vsync_start;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_VERTICAL_0(stream_id),
> +			    CDNS_DP_MSAV0_V_TOTAL(mode->crtc_vtotal) |
> +			    CDNS_DP_MSAV0_VSYNC_START(msa_v0));
> +
> +	vsync = mode->crtc_vsync_end - mode->crtc_vsync_start;
> +	msa_vertical_1 = CDNS_DP_MSAV1_VSYNC_WIDTH(vsync) |
> +			 CDNS_DP_MSAV1_VDISP_WIDTH(mode->crtc_vdisplay);
> +	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
> +		msa_vertical_1 |= CDNS_DP_MSAV1_VSYNC_POL_LOW;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_VERTICAL_1(stream_id),
> +			    msa_vertical_1);
> +
> +	if ((mode->flags & DRM_MODE_FLAG_INTERLACE) &&
> +	    mode->crtc_vtotal % 2 == 0)
> +		misc1 = DP_TEST_INTERLACED;
> +	if (mhdp->display_fmt.y_only)
> +		misc1 |= CDNS_DP_TEST_COLOR_FORMAT_RAW_Y_ONLY;
> +	/* Use VSC SDP for Y420 */
> +	if (pxlfmt == DRM_COLOR_FORMAT_YCRCB420)
> +		misc1 = CDNS_DP_TEST_VSC_SDP;
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_MSA_MISC(stream_id),
> +			    misc0 | (misc1 << 8));
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_HORIZONTAL(stream_id),
> +			    CDNS_DP_H_HSYNC_WIDTH(hsync) |
> +			    CDNS_DP_H_H_TOTAL(mode->crtc_hdisplay));
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_VERTICAL_0(stream_id),
> +			    CDNS_DP_V0_VHEIGHT(mode->crtc_vdisplay) |
> +			    CDNS_DP_V0_VSTART(msa_v0));
> +
> +	dp_vertical_1 = CDNS_DP_V1_VTOTAL(mode->crtc_vtotal);
> +	if ((mode->flags & DRM_MODE_FLAG_INTERLACE) &&
> +	    mode->crtc_vtotal % 2 == 0)
> +		dp_vertical_1 |= CDNS_DP_V1_VTOTAL_EVEN;
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_VERTICAL_1(stream_id), dp_vertical_1);
> +
> +	cdns_mhdp_reg_write_bit(mhdp, CDNS_DP_VB_ID(stream_id), 2, 1,
> +				(mode->flags & DRM_MODE_FLAG_INTERLACE) ?
> +				CDNS_DP_VB_ID_INTERLACED : 0);
> +
> +	ret = cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &framer);
> +	if (ret < 0) {
> +		dev_err(mhdp->dev,
> +			"Failed to read CDNS_DP_FRAMER_GLOBAL_CONFIG %d\n",
> +			ret);
> +		return;
> +	}
> +	framer |= CDNS_DP_FRAMER_EN;
> +	framer &= ~CDNS_DP_NO_VIDEO_MODE;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, framer);
> +}
> +
> +static void cdns_mhdp_sst_enable(struct cdns_mhdp_device *mhdp,
> +				 const struct drm_display_mode *mode,
> +				 struct drm_bridge_state *bridge_state)
> +{
> +	u32 vs, tu_size, line_thresh = 0;
> +	struct cdns_mhdp_bridge_state *state;
> +
> +	state = to_cdns_mhdp_bridge_state(bridge_state);
> +
> +	vs = state->vs;
> +	tu_size = state->tu_size;
> +	line_thresh = state->line_thresh;
> +
> +	mhdp->stream_id = 0;
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_TU,
> +			    CDNS_DP_FRAMER_TU_VS(vs) |
> +			    CDNS_DP_FRAMER_TU_SIZE(tu_size) |
> +			    CDNS_DP_FRAMER_TU_CNT_RST_EN);
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_LINE_THRESH(0),
> +			    line_thresh & GENMASK(5, 0));
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_STREAM_CONFIG_2(0),
> +			    CDNS_DP_SC2_TU_VS_DIFF((tu_size - vs > 3) ?
> +						   0 : tu_size - vs));
> +
> +	cdns_mhdp_configure_video(mhdp, mode);
> +}
> +
> +static void cdns_mhdp_atomic_enable(struct drm_bridge *bridge,
> +				    struct drm_bridge_state *bridge_state)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
> +	struct drm_atomic_state *state = bridge_state->base.state;
> +	struct cdns_mhdp_bridge_state *mhdp_state;
> +	struct drm_crtc_state *crtc_state;
> +	struct drm_connector *connector;
> +	struct drm_connector_state *conn_state;
> +	struct drm_bridge_state *new_state;
> +	const struct drm_display_mode *mode;
> +	u32 resp;
> +	int ret;
> +
> +	dev_dbg(mhdp->dev, "bridge enable\n");
> +
> +	mutex_lock(&mhdp->link_mutex);
> +
> +	if (mhdp->plugged && !mhdp->link_up) {
> +		ret = cdns_mhdp_link_up(mhdp);
> +		if (ret < 0)
> +			goto err_enable;
> +	}
> +
> +	if (mhdp->ops && mhdp->ops->enable)
> +		mhdp->ops->enable(mhdp);
> +
> +	/* Enable VIF clock for stream 0 */
> +	ret = cdns_mhdp_reg_read(mhdp, CDNS_DPTX_CAR, &resp);
> +	if (ret < 0) {
> +		dev_err(mhdp->dev, "Failed to read CDNS_DPTX_CAR %d\n", ret);
> +		goto err_enable;
> +	}
> +
> +	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_CAR,
> +			    resp | CDNS_VIF_CLK_EN | CDNS_VIF_CLK_RSTN);
> +
> +	connector = drm_atomic_get_new_connector_for_encoder(state,
> +							     bridge->encoder);
> +	if (WARN_ON(!connector))
> +		goto err_enable;
> +
> +	conn_state = drm_atomic_get_new_connector_state(state, connector);
> +	if (WARN_ON(!conn_state))
> +		goto err_enable;
> +
> +	crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
> +	if (WARN_ON(!crtc_state))
> +		goto err_enable;
> +
> +	mode = &crtc_state->adjusted_mode;
> +
> +	new_state = drm_atomic_get_new_bridge_state(state, bridge);
> +	if (WARN_ON(!new_state))
> +		goto err_enable;
> +
> +	cdns_mhdp_sst_enable(mhdp, mode, new_state);
> +
> +	mhdp_state = to_cdns_mhdp_bridge_state(new_state);
> +
> +	mhdp_state->current_mode = drm_mode_duplicate(bridge->dev, mode);
> +	drm_mode_set_name(mhdp_state->current_mode);
> +
> +	dev_dbg(mhdp->dev, "%s: Enabling mode %s\n", __func__, mode->name);
> +
> +	mhdp->bridge_enabled = true;
> +
> +err_enable:
> +	mutex_unlock(&mhdp->link_mutex);
> +}
> +
> +static void cdns_mhdp_atomic_disable(struct drm_bridge *bridge,
> +				     struct drm_bridge_state *bridge_state)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
> +	u32 resp;
> +
> +	dev_dbg(mhdp->dev, "%s\n", __func__);
> +
> +	mutex_lock(&mhdp->link_mutex);
> +
> +	mhdp->bridge_enabled = false;
> +	cdns_mhdp_reg_read(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, &resp);
> +	resp &= ~CDNS_DP_FRAMER_EN;
> +	resp |= CDNS_DP_NO_VIDEO_MODE;
> +	cdns_mhdp_reg_write(mhdp, CDNS_DP_FRAMER_GLOBAL_CONFIG, resp);
> +
> +	cdns_mhdp_link_down(mhdp);
> +
> +	/* Disable VIF clock for stream 0 */
> +	cdns_mhdp_reg_read(mhdp, CDNS_DPTX_CAR, &resp);
> +	cdns_mhdp_reg_write(mhdp, CDNS_DPTX_CAR,
> +			    resp & ~(CDNS_VIF_CLK_EN | CDNS_VIF_CLK_RSTN));
> +
> +	if (mhdp->ops && mhdp->ops->disable)
> +		mhdp->ops->disable(mhdp);
> +
> +	mutex_unlock(&mhdp->link_mutex);
> +}
> +
> +static void cdns_mhdp_detach(struct drm_bridge *bridge)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
> +
> +	dev_dbg(mhdp->dev, "%s\n", __func__);
> +
> +	spin_lock(&mhdp->start_lock);
> +
> +	mhdp->bridge_attached = false;
> +
> +	spin_unlock(&mhdp->start_lock);
> +
> +	writel(~0, mhdp->regs + CDNS_APB_INT_MASK);
> +}
> +
> +static struct drm_bridge_state *
> +cdns_mhdp_bridge_atomic_duplicate_state(struct drm_bridge *bridge)
> +{
> +	struct cdns_mhdp_bridge_state *state;
> +
> +	state = kzalloc(sizeof(*state), GFP_KERNEL);
> +	if (!state)
> +		return NULL;
> +
> +	__drm_atomic_helper_bridge_duplicate_state(bridge, &state->base);
> +
> +	return &state->base;
> +}
> +
> +static void
> +cdns_mhdp_bridge_atomic_destroy_state(struct drm_bridge *bridge,
> +				      struct drm_bridge_state *state)
> +{
> +	struct cdns_mhdp_bridge_state *cdns_mhdp_state;
> +
> +	cdns_mhdp_state = to_cdns_mhdp_bridge_state(state);
> +
> +	kfree(cdns_mhdp_state);
> +}
> +
> +static struct drm_bridge_state *
> +cdns_mhdp_bridge_atomic_reset(struct drm_bridge *bridge)
> +{
> +	struct cdns_mhdp_bridge_state *cdns_mhdp_state;
> +
> +	cdns_mhdp_state = kzalloc(sizeof(*cdns_mhdp_state), GFP_KERNEL);
> +	if (!cdns_mhdp_state)
> +		return NULL;
> +
> +	 __drm_atomic_helper_bridge_reset(bridge, &cdns_mhdp_state->base);
> +
> +	return &cdns_mhdp_state->base;
> +}
> +
> +static int cdns_mhdp_validate_mode_params(struct cdns_mhdp_device *mhdp,
> +					  const struct drm_display_mode *mode,
> +					  struct drm_bridge_state *bridge_state)
> +{
> +	u32 tu_size = 30, line_thresh1, line_thresh2, line_thresh = 0;
> +	u32 rate, vs, vs_f, required_bandwidth, available_bandwidth;
> +	struct cdns_mhdp_bridge_state *state;
> +	u32 bpp, bpc, pxlfmt;
> +	int pxlclock, ret;
> +
> +	state = to_cdns_mhdp_bridge_state(bridge_state);
> +	pxlfmt = mhdp->display_fmt.color_format;
> +	bpc = mhdp->display_fmt.bpc;
> +
> +	pxlclock = mode->crtc_clock;
> +
> +	rate = mhdp->link.rate / 1000;
> +
> +	bpp = cdns_mhdp_get_bpp(&mhdp->display_fmt);
> +
> +	if (!cdns_mhdp_bandwidth_ok(mhdp, mode, mhdp->link.num_lanes,
> +				    mhdp->link.rate)) {
> +		dev_err(mhdp->dev, "%s: Not enough BW for %s (%u lanes at %u Mbps)\n",
> +			__func__, mode->name, mhdp->link.num_lanes,
> +			mhdp->link.rate / 100);
> +		ret = -EINVAL;
> +		goto err_tu_size;
> +	}
> +
> +	/* find optimal tu_size */
> +	required_bandwidth = pxlclock * bpp / 8;
> +	available_bandwidth = mhdp->link.num_lanes * rate;
> +	do {
> +		tu_size += 2;
> +
> +		vs_f = tu_size * required_bandwidth / available_bandwidth;
> +		vs = vs_f / 1000;
> +		vs_f = vs_f % 1000;
> +		/* Downspreading is unused currently */
> +	} while ((vs == 1 || ((vs_f > 850 || vs_f < 100) && vs_f != 0) ||
> +		 tu_size - vs < 2) && tu_size < 64);
> +
> +	if (vs > 64) {
> +		dev_err(mhdp->dev,
> +			"%s: No space for framing %s (%u lanes at %u Mbps)\n",
> +			__func__, mode->name, mhdp->link.num_lanes,
> +			mhdp->link.rate / 100);
> +		ret = -EINVAL;
> +		goto err_tu_size;
> +	}
> +
> +	line_thresh1 = ((vs + 1) << 5) * 8 / bpp;
> +	line_thresh2 = (pxlclock << 5) / 1000 / rate * (vs + 1) - (1 << 5);
> +	line_thresh = line_thresh1 - line_thresh2 / mhdp->link.num_lanes;
> +	line_thresh = (line_thresh >> 5) + 2;
> +
> +	state->vs = vs;
> +	state->tu_size = tu_size;
> +	state->line_thresh = line_thresh;
> +
> +	return 0;
> +
> +err_tu_size:
> +	return ret;
> +}
> +
> +static int cdns_mhdp_atomic_check(struct drm_bridge *bridge,
> +				  struct drm_bridge_state *bridge_state,
> +				  struct drm_crtc_state *crtc_state,
> +				  struct drm_connector_state *conn_state)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge_to_mhdp(bridge);
> +	const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
> +	int ret;
> +
> +	if (!mhdp->plugged)
> +		return 0;
> +
> +	mutex_lock(&mhdp->link_mutex);
> +
> +	if (!mhdp->link_up) {
> +		ret = cdns_mhdp_link_up(mhdp);
> +		if (ret < 0)
> +			goto err_check;
> +	}
> +
> +	ret = cdns_mhdp_validate_mode_params(mhdp, mode, bridge_state);
> +
> +err_check:
> +	mutex_unlock(&mhdp->link_mutex);
> +	return ret;
> +}
> +
> +static const struct drm_bridge_funcs cdns_mhdp_bridge_funcs = {
> +	.atomic_enable = cdns_mhdp_atomic_enable,
> +	.atomic_disable = cdns_mhdp_atomic_disable,
> +	.atomic_check = cdns_mhdp_atomic_check,
> +	.attach = cdns_mhdp_attach,
> +	.detach = cdns_mhdp_detach,
> +	.atomic_duplicate_state = cdns_mhdp_bridge_atomic_duplicate_state,
> +	.atomic_destroy_state = cdns_mhdp_bridge_atomic_destroy_state,
> +	.atomic_reset = cdns_mhdp_bridge_atomic_reset,
> +};
> +
> +static bool cdns_mhdp_detect_hpd(struct cdns_mhdp_device *mhdp, bool *hpd_pulse)
> +{
> +	int hpd_event, hpd_status;
> +
> +	*hpd_pulse = false;
> +
> +	hpd_event = cdns_mhdp_read_event(mhdp);
> +
> +	/* Getting event bits failed, bail out */
> +	if (hpd_event < 0) {
> +		dev_warn(mhdp->dev, "%s: read event failed: %d\n",
> +			 __func__, hpd_event);
> +		return false;
> +	}
> +
> +	hpd_status = cdns_mhdp_get_hpd_status(mhdp);
> +	if (hpd_status < 0) {
> +		dev_warn(mhdp->dev, "%s: get hpd status failed: %d\n",
> +			 __func__, hpd_status);
> +		return false;
> +	}
> +
> +	if (hpd_event & DPTX_READ_EVENT_HPD_PULSE)
> +		*hpd_pulse = true;
> +
> +	return !!hpd_status;
> +}
> +
> +static void cdns_mhdp_update_link_status(struct cdns_mhdp_device *mhdp)
> +{
> +	bool hpd_pulse;
> +	int ret;
> +	struct drm_bridge_state *state;
> +	struct cdns_mhdp_bridge_state *cdns_bridge_state;
> +	struct drm_connector *conn = &mhdp->connector;
> +	struct drm_display_mode *current_mode;
> +	bool old_plugged = mhdp->plugged;
> +	u8 status[DP_LINK_STATUS_SIZE];
> +
> +	mhdp->plugged = cdns_mhdp_detect_hpd(mhdp, &hpd_pulse);
> +
> +	mutex_lock(&mhdp->link_mutex);
> +
> +	if (!mhdp->plugged) {
> +		cdns_mhdp_link_down(mhdp);
> +		goto err;
> +	}
> +
> +	/*
> +	 * If we get a HPD pulse event and we were and still are connected,
> +	 * check the link status. If link status is ok, there's nothing to do
> +	 * as we don't handle DP interrupts. If link status is bad, continue
> +	 * with full link setup.
> +	 */
> +	if (hpd_pulse && old_plugged == mhdp->plugged) {
> +		ret = drm_dp_dpcd_read_link_status(&mhdp->aux, status);
> +
> +		/*
> +		 * If everything looks fine, just return, as we don't handle
> +		 * DP IRQs.
> +		 */
> +		if (ret > 0 &&
> +		    drm_dp_channel_eq_ok(status, mhdp->link.num_lanes) &&
> +		    drm_dp_clock_recovery_ok(status, mhdp->link.num_lanes)) {
> +			mutex_unlock(&mhdp->link_mutex);
> +			return;
> +		}
> +
> +		/* If link is bad, mark link as down so that we do a new LT */
> +		mhdp->link_up = false;
> +	}
> +
> +	if (!mhdp->link_up) {
> +		ret = cdns_mhdp_link_up(mhdp);
> +		if (ret < 0) {
> +			mutex_unlock(&mhdp->link_mutex);
> +			drm_connector_set_link_status_property(conn,
> +							       DRM_MODE_LINK_STATUS_BAD);
> +			return;
> +		}
> +	}
> +
> +	if (mhdp->bridge_enabled) {
> +		state = drm_priv_to_bridge_state(mhdp->bridge.base.state);
> +		if (!state)
> +			goto err;
> +
> +		cdns_bridge_state = to_cdns_mhdp_bridge_state(state);
> +		if (!cdns_bridge_state)
> +			goto err;
> +
> +		current_mode = cdns_bridge_state->current_mode;
> +		if (!current_mode)
> +			goto err;
> +
> +		ret = cdns_mhdp_validate_mode_params(mhdp, current_mode, state);
> +		if (ret < 0)
> +			goto err;
> +
> +		dev_dbg(mhdp->dev, "%s: Enabling mode %s\n", __func__,
> +			current_mode->name);
> +
> +		cdns_mhdp_sst_enable(mhdp, current_mode, state);
> +	}
> +err:
> +	mutex_unlock(&mhdp->link_mutex);
> +}
> +
> +static irqreturn_t cdns_mhdp_irq_handler(int irq, void *data)
> +{
> +	struct cdns_mhdp_device *mhdp = (struct cdns_mhdp_device *)data;
> +	u32 apb_stat, sw_ev0;
> +	bool bridge_attached;
> +
> +	apb_stat = readl(mhdp->regs + CDNS_APB_INT_STATUS);
> +	sw_ev0 = readl(mhdp->regs + CDNS_SW_EVENT0);
> +
> +	/*
> +	 *  Calling drm_kms_helper_hotplug_event() when not attached
> +	 *  to drm device causes an oops because the drm_bridge->dev
> +	 *  is NULL. See cdns_mhdp_fw_cb() comments for details about the
> +	 *  problems related drm_kms_helper_hotplug_event() call.
> +	 */
> +	spin_lock(&mhdp->start_lock);
> +	bridge_attached = mhdp->bridge_attached;
> +	spin_unlock(&mhdp->start_lock);
> +
> +	if (bridge_attached && (sw_ev0 & CDNS_DPTX_HPD)) {
> +		cdns_mhdp_update_link_status(mhdp);
> +
> +		drm_kms_helper_hotplug_event(mhdp->bridge.dev);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int cdns_mhdp_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct cdns_mhdp_device *mhdp;
> +	struct clk *clk;
> +	int ret;
> +	unsigned long rate;
> +	int irq;
> +
> +	mhdp = devm_kzalloc(dev, sizeof(*mhdp), GFP_KERNEL);
> +	if (!mhdp)
> +		return -ENOMEM;
> +
> +	clk = devm_clk_get(dev, NULL);
> +	if (IS_ERR(clk)) {
> +		dev_err(dev, "couldn't get clk: %ld\n", PTR_ERR(clk));
> +		return PTR_ERR(clk);
> +	}
> +
> +	mhdp->clk = clk;
> +	mhdp->dev = dev;
> +	mutex_init(&mhdp->mbox_mutex);
> +	mutex_init(&mhdp->link_mutex);
> +	spin_lock_init(&mhdp->start_lock);
> +
> +	drm_dp_aux_init(&mhdp->aux);
> +	mhdp->aux.dev = dev;
> +	mhdp->aux.transfer = cdns_mhdp_transfer;
> +
> +	mhdp->regs = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(mhdp->regs)) {
> +		dev_err(dev, "Failed to get memory resource\n");
> +		return PTR_ERR(mhdp->regs);
> +	}
> +
> +	mhdp->phy = devm_of_phy_get_by_index(dev, pdev->dev.of_node, 0);
> +	if (IS_ERR(mhdp->phy)) {
> +		dev_err(dev, "no PHY configured\n");
> +		return PTR_ERR(mhdp->phy);
> +	}
> +
> +	platform_set_drvdata(pdev, mhdp);
> +
> +	mhdp->ops = of_device_get_match_data(dev);
> +
> +	clk_prepare_enable(clk);
> +
> +	pm_runtime_enable(dev);
> +	ret = pm_runtime_get_sync(dev);
> +	if (ret < 0) {
> +		dev_err(dev, "pm_runtime_get_sync failed\n");
> +		pm_runtime_disable(dev);
> +		goto clk_disable;
> +	}
> +
> +	if (mhdp->ops && mhdp->ops->init) {
> +		ret = mhdp->ops->init(mhdp);
> +		if (ret != 0) {
> +			dev_err(dev, "MHDP platform initialization failed: %d\n",
> +				ret);
> +			goto runtime_put;
> +		}
> +	}
> +
> +	rate = clk_get_rate(clk);
> +	writel(rate % 1000000, mhdp->regs + CDNS_SW_CLK_L);
> +	writel(rate / 1000000, mhdp->regs + CDNS_SW_CLK_H);
> +
> +	dev_dbg(dev, "func clk rate %lu Hz\n", rate);
> +
> +	writel(~0, mhdp->regs + CDNS_APB_INT_MASK);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	ret = devm_request_threaded_irq(mhdp->dev, irq, NULL,
> +					cdns_mhdp_irq_handler, IRQF_ONESHOT,
> +					"mhdp8546", mhdp);
> +	if (ret) {
> +		dev_err(dev, "cannot install IRQ %d\n", irq);
> +		ret = -EIO;
> +		goto plat_fini;
> +	}
> +
> +	cdns_mhdp_fill_host_caps(mhdp);
> +
> +	/* The only currently supported format */
> +	mhdp->display_fmt.y_only = false;
> +	mhdp->display_fmt.color_format = DRM_COLOR_FORMAT_RGB444;
> +	mhdp->display_fmt.bpc = 8;
> +
> +	mhdp->bridge.of_node = pdev->dev.of_node;
> +	mhdp->bridge.funcs = &cdns_mhdp_bridge_funcs;
> +
> +	ret = phy_init(mhdp->phy);
> +	if (ret) {
> +		dev_err(mhdp->dev, "Failed to initialize PHY: %d\n", ret);
> +		goto plat_fini;
> +	}
> +
> +	ret = cdns_mhdp_load_firmware(mhdp);
> +	if (ret)
> +		goto phy_exit;
> +
> +	drm_bridge_add(&mhdp->bridge);
> +
> +	return 0;
> +
> +phy_exit:
> +	phy_exit(mhdp->phy);
> +plat_fini:
> +	if (mhdp->ops && mhdp->ops->exit)
> +		mhdp->ops->exit(mhdp);
> +runtime_put:
> +	pm_runtime_put_sync(dev);
> +	pm_runtime_disable(dev);
> +clk_disable:
> +	clk_disable_unprepare(mhdp->clk);
> +
> +	return ret;
> +}
> +
> +static int cdns_mhdp_remove(struct platform_device *pdev)
> +{
> +	struct cdns_mhdp_device *mhdp = dev_get_drvdata(&pdev->dev);
> +	unsigned long timeout = msecs_to_jiffies(100);
> +	bool stop_fw = false;
> +	int ret = 0;
> +
> +	drm_bridge_remove(&mhdp->bridge);
> +
> +	ret = wait_event_timeout(fw_load_wq, mhdp->hw_state == MHDP_HW_READY,
> +				 timeout);
> +	if (ret == 0)
> +		dev_err(mhdp->dev, "%s: Timeout waiting for fw loading\n",
> +			__func__);
> +	else
> +		stop_fw = true;
> +
> +	spin_lock(&mhdp->start_lock);
> +	mhdp->hw_state = MHDP_HW_STOPPED;
> +	spin_unlock(&mhdp->start_lock);
> +
> +	if (stop_fw) {
> +		ret = cdns_mhdp_set_firmware_active(mhdp, false);
> +		if (ret)
> +			dev_err(mhdp->dev, "%s: De-activate FW failed: %d\n",
> +				__func__, ret);
> +	}
> +
> +	phy_exit(mhdp->phy);
> +
> +	if (mhdp->ops && mhdp->ops->exit)
> +		mhdp->ops->exit(mhdp);
> +
> +	pm_runtime_put_sync(&pdev->dev);
> +	pm_runtime_disable(&pdev->dev);
> +
> +	clk_disable_unprepare(mhdp->clk);
> +
> +	return ret;
> +}
> +
> +static const struct of_device_id mhdp_ids[] = {
> +	{ .compatible = "cdns,mhdp8546", },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, mhdp_ids);
> +
> +static struct platform_driver mhdp_driver = {
> +	.driver	= {
> +		.name		= "cdns-mhdp",
> +		.of_match_table	= of_match_ptr(mhdp_ids),
> +	},
> +	.probe	= cdns_mhdp_probe,
> +	.remove	= cdns_mhdp_remove,
> +};
> +module_platform_driver(mhdp_driver);
> +
> +MODULE_FIRMWARE(FW_NAME);
> +
> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
> +MODULE_AUTHOR("Swapnil Jakhade <sjakhade@cadence.com>");
> +MODULE_DESCRIPTION("Cadence MHDP DP bridge driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:cdns-mhdp");
> diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.h b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
> new file mode 100644
> index 000000000000..d1bfcdef4249
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
> @@ -0,0 +1,396 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Cadence MHDP DP bridge driver.
> + *
> + * Copyright: 2019 Cadence Design Systems, Inc.
> + *
> + * Author: Quentin Schulz <quentin.schulz@free-electrons.com>
> + *         Swapnil Jakhade <sjakhade@cadence.com>
> + */
> +
> +#ifndef CDNS_MHDP_CORE_H
> +#define CDNS_MHDP_CORE_H
> +
> +#include <linux/bits.h>
> +#include <linux/mutex.h>
> +#include <linux/spinlock.h>
> +
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_dp_helper.h>
> +
> +struct clk;
> +struct device;
> +struct phy;
> +
> +/* Register offsets */
> +#define CDNS_APB_CTRL				0x00000
> +#define CDNS_CPU_STALL				BIT(3)
> +
> +#define CDNS_MAILBOX_FULL			0x00008
> +#define CDNS_MAILBOX_EMPTY			0x0000c
> +#define CDNS_MAILBOX_TX_DATA			0x00010
> +#define CDNS_MAILBOX_RX_DATA			0x00014
> +#define CDNS_KEEP_ALIVE				0x00018
> +#define CDNS_KEEP_ALIVE_MASK			GENMASK(7, 0)
> +
> +#define CDNS_VER_L				0x0001C
> +#define CDNS_VER_H				0x00020
> +#define CDNS_LIB_L_ADDR				0x00024
> +#define CDNS_LIB_H_ADDR				0x00028
> +
> +#define CDNS_MB_INT_MASK			0x00034
> +#define CDNS_MB_INT_STATUS			0x00038
> +
> +#define CDNS_SW_CLK_L				0x0003c
> +#define CDNS_SW_CLK_H				0x00040
> +
> +#define CDNS_SW_EVENT0				0x00044
> +#define CDNS_DPTX_HPD				BIT(0)
> +
> +#define CDNS_SW_EVENT1				0x00048
> +#define CDNS_SW_EVENT2				0x0004c
> +#define CDNS_SW_EVENT3				0x00050
> +
> +#define CDNS_APB_INT_MASK			0x0006C
> +#define CDNS_APB_INT_MASK_MAILBOX_INT		BIT(0)
> +#define CDNS_APB_INT_MASK_SW_EVENT_INT		BIT(1)
> +
> +#define CDNS_APB_INT_STATUS			0x00070
> +
> +#define CDNS_DPTX_CAR				0x00904
> +#define CDNS_VIF_CLK_EN				BIT(0)
> +#define CDNS_VIF_CLK_RSTN			BIT(1)
> +
> +#define CDNS_SOURCE_VIDEO_IF(s)			(0x00b00 + ((s) * 0x20))
> +#define CDNS_BND_HSYNC2VSYNC(s)			(CDNS_SOURCE_VIDEO_IF(s) + \
> +						 0x00)
> +#define CDNS_IP_DTCT_WIN			GENMASK(11, 0)
> +#define CDNS_IP_DET_INTERLACE_FORMAT		BIT(12)
> +#define CDNS_IP_BYPASS_V_INTERFACE		BIT(13)
> +
> +#define CDNS_HSYNC2VSYNC_POL_CTRL(s)		(CDNS_SOURCE_VIDEO_IF(s) + \
> +						 0x10)
> +#define CDNS_H2V_HSYNC_POL_ACTIVE_LOW		BIT(1)
> +#define CDNS_H2V_VSYNC_POL_ACTIVE_LOW		BIT(2)
> +
> +#define CDNS_DPTX_PHY_CONFIG			0x02000
> +#define CDNS_PHY_TRAINING_EN			BIT(0)
> +#define CDNS_PHY_TRAINING_TYPE(x)		(((x) & GENMASK(3, 0)) << 1)
> +#define CDNS_PHY_SCRAMBLER_BYPASS		BIT(5)
> +#define CDNS_PHY_ENCODER_BYPASS			BIT(6)
> +#define CDNS_PHY_SKEW_BYPASS			BIT(7)
> +#define CDNS_PHY_TRAINING_AUTO			BIT(8)
> +#define CDNS_PHY_LANE0_SKEW(x)			(((x) & GENMASK(2, 0)) << 9)
> +#define CDNS_PHY_LANE1_SKEW(x)			(((x) & GENMASK(2, 0)) << 12)
> +#define CDNS_PHY_LANE2_SKEW(x)			(((x) & GENMASK(2, 0)) << 15)
> +#define CDNS_PHY_LANE3_SKEW(x)			(((x) & GENMASK(2, 0)) << 18)
> +#define CDNS_PHY_COMMON_CONFIG			(CDNS_PHY_LANE1_SKEW(1) | \
> +						CDNS_PHY_LANE2_SKEW(2) |  \
> +						CDNS_PHY_LANE3_SKEW(3))
> +#define CDNS_PHY_10BIT_EN			BIT(21)
> +
> +#define CDNS_DP_FRAMER_GLOBAL_CONFIG		0x02200
> +#define CDNS_DP_NUM_LANES(x)			((x) - 1)
> +#define CDNS_DP_MST_EN				BIT(2)
> +#define CDNS_DP_FRAMER_EN			BIT(3)
> +#define CDNS_DP_RATE_GOVERNOR_EN		BIT(4)
> +#define CDNS_DP_NO_VIDEO_MODE			BIT(5)
> +#define CDNS_DP_DISABLE_PHY_RST			BIT(6)
> +#define CDNS_DP_WR_FAILING_EDGE_VSYNC		BIT(7)
> +
> +#define CDNS_DP_FRAMER_TU			0x02208
> +#define CDNS_DP_FRAMER_TU_SIZE(x)		(((x) & GENMASK(6, 0)) << 8)
> +#define CDNS_DP_FRAMER_TU_VS(x)			((x) & GENMASK(5, 0))
> +#define CDNS_DP_FRAMER_TU_CNT_RST_EN		BIT(15)
> +
> +#define CDNS_DP_MTPH_CONTROL			0x02264
> +#define CDNS_DP_MTPH_ECF_EN			BIT(0)
> +#define CDNS_DP_MTPH_ACT_EN			BIT(1)
> +#define CDNS_DP_MTPH_LVP_EN			BIT(2)
> +
> +#define CDNS_DP_MTPH_STATUS			0x0226C
> +#define CDNS_DP_MTPH_ACT_STATUS			BIT(0)
> +
> +#define CDNS_DP_LANE_EN				0x02300
> +#define CDNS_DP_LANE_EN_LANES(x)		GENMASK((x) - 1, 0)
> +
> +#define CDNS_DP_ENHNCD				0x02304
> +
> +#define CDNS_DPTX_STREAM(s)			(0x03000 + (s) * 0x80)
> +#define CDNS_DP_MSA_HORIZONTAL_0(s)		(CDNS_DPTX_STREAM(s) + 0x00)
> +#define CDNS_DP_MSAH0_H_TOTAL(x)		(x)
> +#define CDNS_DP_MSAH0_HSYNC_START(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_HORIZONTAL_1(s)		(CDNS_DPTX_STREAM(s) + 0x04)
> +#define CDNS_DP_MSAH1_HSYNC_WIDTH(x)		(x)
> +#define CDNS_DP_MSAH1_HSYNC_POL_LOW		BIT(15)
> +#define CDNS_DP_MSAH1_HDISP_WIDTH(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_VERTICAL_0(s)		(CDNS_DPTX_STREAM(s) + 0x08)
> +#define CDNS_DP_MSAV0_V_TOTAL(x)		(x)
> +#define CDNS_DP_MSAV0_VSYNC_START(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_VERTICAL_1(s)		(CDNS_DPTX_STREAM(s) + 0x0c)
> +#define CDNS_DP_MSAV1_VSYNC_WIDTH(x)		(x)
> +#define CDNS_DP_MSAV1_VSYNC_POL_LOW		BIT(15)
> +#define CDNS_DP_MSAV1_VDISP_WIDTH(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_MISC(s)			(CDNS_DPTX_STREAM(s) + 0x10)
> +#define CDNS_DP_STREAM_CONFIG(s)		(CDNS_DPTX_STREAM(s) + 0x14)
> +#define CDNS_DP_STREAM_CONFIG_2(s)		(CDNS_DPTX_STREAM(s) + 0x2c)
> +#define CDNS_DP_SC2_TU_VS_DIFF(x)		((x) << 8)
> +
> +#define CDNS_DP_HORIZONTAL(s)			(CDNS_DPTX_STREAM(s) + 0x30)
> +#define CDNS_DP_H_HSYNC_WIDTH(x)		(x)
> +#define CDNS_DP_H_H_TOTAL(x)			((x) << 16)
> +
> +#define CDNS_DP_VERTICAL_0(s)			(CDNS_DPTX_STREAM(s) + 0x34)
> +#define CDNS_DP_V0_VHEIGHT(x)			(x)
> +#define CDNS_DP_V0_VSTART(x)			((x) << 16)
> +
> +#define CDNS_DP_VERTICAL_1(s)			(CDNS_DPTX_STREAM(s) + 0x38)
> +#define CDNS_DP_V1_VTOTAL(x)			(x)
> +#define CDNS_DP_V1_VTOTAL_EVEN			BIT(16)
> +
> +#define CDNS_DP_MST_SLOT_ALLOCATE(s)		(CDNS_DPTX_STREAM(s) + 0x44)
> +#define CDNS_DP_S_ALLOC_START_SLOT(x)		(x)
> +#define CDNS_DP_S_ALLOC_END_SLOT(x)		((x) << 8)
> +
> +#define CDNS_DP_RATE_GOVERNING(s)		(CDNS_DPTX_STREAM(s) + 0x48)
> +#define CDNS_DP_RG_TARG_AV_SLOTS_Y(x)		(x)
> +#define CDNS_DP_RG_TARG_AV_SLOTS_X(x)		((x) << 4)
> +#define CDNS_DP_RG_ENABLE			BIT(10)
> +
> +#define CDNS_DP_FRAMER_PXL_REPR(s)		(CDNS_DPTX_STREAM(s) + 0x4c)
> +#define CDNS_DP_FRAMER_6_BPC			BIT(0)
> +#define CDNS_DP_FRAMER_8_BPC			BIT(1)
> +#define CDNS_DP_FRAMER_10_BPC			BIT(2)
> +#define CDNS_DP_FRAMER_12_BPC			BIT(3)
> +#define CDNS_DP_FRAMER_16_BPC			BIT(4)
> +#define CDNS_DP_FRAMER_PXL_FORMAT		0x8
> +#define CDNS_DP_FRAMER_RGB			BIT(0)
> +#define CDNS_DP_FRAMER_YCBCR444			BIT(1)
> +#define CDNS_DP_FRAMER_YCBCR422			BIT(2)
> +#define CDNS_DP_FRAMER_YCBCR420			BIT(3)
> +#define CDNS_DP_FRAMER_Y_ONLY			BIT(4)
> +
> +#define CDNS_DP_FRAMER_SP(s)			(CDNS_DPTX_STREAM(s) + 0x50)
> +#define CDNS_DP_FRAMER_VSYNC_POL_LOW		BIT(0)
> +#define CDNS_DP_FRAMER_HSYNC_POL_LOW		BIT(1)
> +#define CDNS_DP_FRAMER_INTERLACE		BIT(2)
> +
> +#define CDNS_DP_LINE_THRESH(s)			(CDNS_DPTX_STREAM(s) + 0x64)
> +#define CDNS_DP_ACTIVE_LINE_THRESH(x)		(x)
> +
> +#define CDNS_DP_VB_ID(s)			(CDNS_DPTX_STREAM(s) + 0x68)
> +#define CDNS_DP_VB_ID_INTERLACED		BIT(2)
> +#define CDNS_DP_VB_ID_COMPRESSED		BIT(6)
> +
> +#define CDNS_DP_FRONT_BACK_PORCH(s)		(CDNS_DPTX_STREAM(s) + 0x78)
> +#define CDNS_DP_BACK_PORCH(x)			(x)
> +#define CDNS_DP_FRONT_PORCH(x)			((x) << 16)
> +
> +#define CDNS_DP_BYTE_COUNT(s)			(CDNS_DPTX_STREAM(s) + 0x7c)
> +#define CDNS_DP_BYTE_COUNT_BYTES_IN_CHUNK_SHIFT	16
> +
> +/* mailbox */
> +#define MAILBOX_RETRY_US			1000
> +#define MAILBOX_TIMEOUT_US			2000000
> +
> +#define MB_OPCODE_ID				0
> +#define MB_MODULE_ID				1
> +#define MB_SIZE_MSB_ID				2
> +#define MB_SIZE_LSB_ID				3
> +#define MB_DATA_ID				4
> +
> +#define MB_MODULE_ID_DP_TX			0x01
> +#define MB_MODULE_ID_HDCP_TX			0x07
> +#define MB_MODULE_ID_HDCP_RX			0x08
> +#define MB_MODULE_ID_HDCP_GENERAL		0x09
> +#define MB_MODULE_ID_GENERAL			0x0a
> +
> +/* firmware and opcodes */
> +#define FW_NAME					"cadence/mhdp8546.bin"
> +#define CDNS_MHDP_IMEM				0x10000
> +
> +#define GENERAL_MAIN_CONTROL			0x01
> +#define GENERAL_TEST_ECHO			0x02
> +#define GENERAL_BUS_SETTINGS			0x03
> +#define GENERAL_TEST_ACCESS			0x04
> +#define GENERAL_REGISTER_READ			0x07
> +
> +#define DPTX_SET_POWER_MNG			0x00
> +#define DPTX_GET_EDID				0x02
> +#define DPTX_READ_DPCD				0x03
> +#define DPTX_WRITE_DPCD				0x04
> +#define DPTX_ENABLE_EVENT			0x05
> +#define DPTX_WRITE_REGISTER			0x06
> +#define DPTX_READ_REGISTER			0x07
> +#define DPTX_WRITE_FIELD			0x08
> +#define DPTX_READ_EVENT				0x0a
> +#define DPTX_GET_LAST_AUX_STAUS			0x0e
> +#define DPTX_HPD_STATE				0x11
> +#define DPTX_ADJUST_LT				0x12
> +
> +#define FW_STANDBY				0
> +#define FW_ACTIVE				1
> +
> +/* HPD */
> +#define DPTX_READ_EVENT_HPD_TO_HIGH             BIT(0)
> +#define DPTX_READ_EVENT_HPD_TO_LOW              BIT(1)
> +#define DPTX_READ_EVENT_HPD_PULSE               BIT(2)
> +#define DPTX_READ_EVENT_HPD_STATE               BIT(3)
> +
> +/* general */
> +#define CDNS_DP_TRAINING_PATTERN_4		0x7
> +
> +#define CDNS_KEEP_ALIVE_TIMEOUT			2000
> +
> +#define CDNS_VOLT_SWING(x)			((x) & GENMASK(1, 0))
> +#define CDNS_FORCE_VOLT_SWING			BIT(2)
> +
> +#define CDNS_PRE_EMPHASIS(x)			((x) & GENMASK(1, 0))
> +#define CDNS_FORCE_PRE_EMPHASIS			BIT(2)
> +
> +#define CDNS_SUPPORT_TPS(x)			BIT((x) - 1)
> +
> +#define CDNS_FAST_LINK_TRAINING			BIT(0)
> +
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_0(x)	((x) & GENMASK(1, 0))
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_1(x)	((x) & GENMASK(3, 2))
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_2(x)	((x) & GENMASK(5, 4))
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_3(x)	((x) & GENMASK(7, 6))
> +#define CDNS_LANE_MAPPING_NORMAL		0xe4
> +#define CDNS_LANE_MAPPING_FLIPPED		0x1b
> +
> +#define CDNS_DP_MAX_NUM_LANES			4
> +#define CDNS_DP_TEST_VSC_SDP			BIT(6) /* 1.3+ */
> +#define CDNS_DP_TEST_COLOR_FORMAT_RAW_Y_ONLY	BIT(7)
> +
> +#define CDNS_MHDP_MAX_STREAMS   4
> +
> +#define DP_LINK_CAP_ENHANCED_FRAMING BIT(0)
> +
> +struct cdns_mhdp_link {
> +	unsigned char revision;
> +	unsigned int rate;
> +	unsigned int num_lanes;
> +	unsigned long capabilities;
> +};
> +
> +struct cdns_mhdp_host {
> +	unsigned int link_rate;
> +	u8 lanes_cnt;
> +	u8 volt_swing;
> +	u8 pre_emphasis;
> +	u8 pattern_supp;
> +	u8 lane_mapping;
> +	bool fast_link;
> +	bool enhanced;
> +	bool scrambler;
> +	bool ssc;
> +};
> +
> +struct cdns_mhdp_sink {
> +	unsigned int link_rate;
> +	u8 lanes_cnt;
> +	u8 pattern_supp;
> +	bool fast_link;
> +	bool enhanced;
> +	bool ssc;
> +};
> +
> +struct cdns_mhdp_display_fmt {
> +	u32 color_format;
> +	u32 bpc;
> +	bool y_only;
> +};
> +
> +/*
> + * These enums present MHDP hw initialization state
> + * Legal state transitions are:
> + * MHDP_HW_INACTIVE <-> MHDP_HW_LOADING -> MHDP_HW_READY
> + *        |                                     |
> + *        '----------> MHDP_HW_STOPPED <--------'
> + */
> +enum mhdp_hw_state {
> +	MHDP_HW_INACTIVE = 0,	/* HW not initialized */
> +	MHDP_HW_LOADING,	/* HW initialization in progress */
> +	MHDP_HW_READY,		/* HW ready, FW active*/
> +	MHDP_HW_STOPPED		/* Driver removal FW to be stopped */
> +};
> +
> +struct cdns_mhdp_device;
> +
> +struct mhdp_platform_ops {
> +	int (*init)(struct cdns_mhdp_device *mhdp);
> +	void (*exit)(struct cdns_mhdp_device *mhdp);
> +	void (*enable)(struct cdns_mhdp_device *mhdp);
> +	void (*disable)(struct cdns_mhdp_device *mhdp);
> +};
> +
> +struct cdns_mhdp_bridge_state {
> +	struct drm_bridge_state base;
> +	u32 tu_size;
> +	u32 vs;
> +	u32 line_thresh;
> +	struct drm_display_mode *current_mode;
> +};
> +
> +#define to_cdns_mhdp_bridge_state(s) \
> +		container_of(s, struct cdns_mhdp_bridge_state, base)
> +
> +struct cdns_mhdp_device {
> +	void __iomem *regs;
> +
> +	struct device *dev;
> +	struct clk *clk;
> +	struct phy *phy;
> +
> +	const struct mhdp_platform_ops *ops;
> +
> +	/* This is to protect mailbox communications with the firmware */
> +	struct mutex mbox_mutex;
> +
> +	/* "link_mutex" protects the access to all the link parameters
> +	 * including the link training process. Link training will be
> +	 * invoked both from threaded interrupt handler and from atomic
> +	 * callbacks when link_up is not set. So this mutex protects
> +	 * flags such as link_up, bridge_enabled, link.num_lanes,
> +	 * link.rate etc.
> +	 */
> +	struct mutex link_mutex;
> +
> +	struct drm_connector connector;
> +	struct drm_bridge bridge;
> +
> +	struct cdns_mhdp_link link;
> +	struct drm_dp_aux aux;
> +
> +	struct cdns_mhdp_host host;
> +	struct cdns_mhdp_sink sink;
> +	struct cdns_mhdp_display_fmt display_fmt;
> +	s8 stream_id;
> +
> +	bool link_up;
> +	bool plugged;
> +
> +	/*
> +	 * "start_lock" protects the access to bridge_attached and
> +	 * hw_state data members that control the delayed firmware
> +	 * loading and attaching the bridge. They are accessed from
> +	 * both the DRM core and cdns_mhdp_fw_cb(). In most cases just
> +	 * protecting the data members is enough, but the irq mask
> +	 * setting needs to be protected when enabling the FW.
> +	 */
> +	spinlock_t start_lock;
> +	bool bridge_attached;
> +	bool bridge_enabled;
> +	enum mhdp_hw_state hw_state;
> +};
> +
> +#define connector_to_mhdp(x) container_of(x, struct cdns_mhdp_device, connector)
> +#define bridge_to_mhdp(x) container_of(x, struct cdns_mhdp_device, bridge)
> +
> +#endif

-- 
Regards,

Laurent Pinchart
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH v7 2/3] drm: bridge: Add support for Cadence MHDP DPI/DP bridge
  2020-07-22  7:40   ` Swapnil Jakhade
  (?)
@ 2020-07-22 21:25     ` kernel test robot
  -1 siblings, 0 replies; 26+ messages in thread
From: kernel test robot @ 2020-07-22 21:25 UTC (permalink / raw)
  To: Swapnil Jakhade, airlied, daniel, Laurent.pinchart, robh+dt,
	a.hajda, narmstrong, jonas, jernej.skrabec, dri-devel,
	devicetree
  Cc: kbuild-all, clang-built-linux

[-- Attachment #1: Type: text/plain, Size: 3719 bytes --]

Hi Swapnil,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on robh/for-next]
[also build test ERROR on linus/master v5.8-rc6 next-20200722]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Swapnil-Jakhade/drm-Add-support-for-Cadence-MHDP-DPI-DP-bridge-and-J721E-wrapper/20200722-154322
base:   https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
config: x86_64-allyesconfig (attached as .config)
compiler: clang version 12.0.0 (https://github.com/llvm/llvm-project 3d9967039d4191b77f939ddc6c6ff4275df620c2)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # install x86_64 cross compiling tool for clang build
        # apt-get install binutils-x86-64-linux-gnu
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross ARCH=x86_64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All error/warnings (new ones prefixed by >>):

>> drivers/gpu/drm/bridge/cdns-mhdp-core.c:765:10: warning: implicit conversion from 'unsigned long' to 'unsigned int' changes value from 18446744073709551613 to 4294967293 [-Wconstant-conversion]
                   writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
                   ~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/gpu/drm/bridge/cdns-mhdp-core.c:1378:2: error: implicit declaration of function 'phy_get_attrs' [-Werror,-Wimplicit-function-declaration]
           phy_get_attrs(mhdp->phy, &attrs);
           ^
>> drivers/gpu/drm/bridge/cdns-mhdp-core.c:1384:20: error: no member named 'max_link_rate' in 'struct phy_attrs'
           link_rate = attrs.max_link_rate;
                       ~~~~~ ^
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:1709:10: warning: implicit conversion from 'unsigned long' to 'unsigned int' changes value from 18446744073709551613 to 4294967293 [-Wconstant-conversion]
                   writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
                   ~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   2 warnings and 2 errors generated.

vim +/phy_get_attrs +1378 drivers/gpu/drm/bridge/cdns-mhdp-core.c

  1371	
  1372	static void cdns_mhdp_fill_host_caps(struct cdns_mhdp_device *mhdp)
  1373	{
  1374		unsigned int link_rate;
  1375		struct phy_attrs attrs;
  1376	
  1377		/* Get source capabilities based on PHY attributes */
> 1378		phy_get_attrs(mhdp->phy, &attrs);
  1379	
  1380		mhdp->host.lanes_cnt = attrs.bus_width;
  1381		if (!mhdp->host.lanes_cnt)
  1382			mhdp->host.lanes_cnt = 4;
  1383	
> 1384		link_rate = attrs.max_link_rate;
  1385		if (!link_rate)
  1386			link_rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_8_1);
  1387		else
  1388			/* PHY uses Mb/s, DRM uses tens of kb/s. */
  1389			link_rate *= 100;
  1390	
  1391		mhdp->host.link_rate = link_rate;
  1392		mhdp->host.volt_swing = CDNS_VOLT_SWING(3);
  1393		mhdp->host.pre_emphasis = CDNS_PRE_EMPHASIS(3);
  1394		mhdp->host.pattern_supp = CDNS_SUPPORT_TPS(1) |
  1395					  CDNS_SUPPORT_TPS(2) | CDNS_SUPPORT_TPS(3) |
  1396					  CDNS_SUPPORT_TPS(4);
  1397		mhdp->host.lane_mapping = CDNS_LANE_MAPPING_NORMAL;
  1398		mhdp->host.fast_link = false;
  1399		mhdp->host.enhanced = true;
  1400		mhdp->host.scrambler = true;
  1401		mhdp->host.ssc = false;
  1402	}
  1403	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org

[-- Attachment #2: .config.gz --]
[-- Type: application/gzip, Size: 75334 bytes --]

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

* Re: [PATCH v7 2/3] drm: bridge: Add support for Cadence MHDP DPI/DP bridge
@ 2020-07-22 21:25     ` kernel test robot
  0 siblings, 0 replies; 26+ messages in thread
From: kernel test robot @ 2020-07-22 21:25 UTC (permalink / raw)
  To: Swapnil Jakhade, airlied, daniel, Laurent.pinchart, robh+dt,
	a.hajda, narmstrong, jonas, jernej.skrabec, dri-devel,
	devicetree
  Cc: clang-built-linux, kbuild-all

[-- Attachment #1: Type: text/plain, Size: 3719 bytes --]

Hi Swapnil,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on robh/for-next]
[also build test ERROR on linus/master v5.8-rc6 next-20200722]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Swapnil-Jakhade/drm-Add-support-for-Cadence-MHDP-DPI-DP-bridge-and-J721E-wrapper/20200722-154322
base:   https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
config: x86_64-allyesconfig (attached as .config)
compiler: clang version 12.0.0 (https://github.com/llvm/llvm-project 3d9967039d4191b77f939ddc6c6ff4275df620c2)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # install x86_64 cross compiling tool for clang build
        # apt-get install binutils-x86-64-linux-gnu
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross ARCH=x86_64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All error/warnings (new ones prefixed by >>):

>> drivers/gpu/drm/bridge/cdns-mhdp-core.c:765:10: warning: implicit conversion from 'unsigned long' to 'unsigned int' changes value from 18446744073709551613 to 4294967293 [-Wconstant-conversion]
                   writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
                   ~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/gpu/drm/bridge/cdns-mhdp-core.c:1378:2: error: implicit declaration of function 'phy_get_attrs' [-Werror,-Wimplicit-function-declaration]
           phy_get_attrs(mhdp->phy, &attrs);
           ^
>> drivers/gpu/drm/bridge/cdns-mhdp-core.c:1384:20: error: no member named 'max_link_rate' in 'struct phy_attrs'
           link_rate = attrs.max_link_rate;
                       ~~~~~ ^
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:1709:10: warning: implicit conversion from 'unsigned long' to 'unsigned int' changes value from 18446744073709551613 to 4294967293 [-Wconstant-conversion]
                   writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
                   ~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   2 warnings and 2 errors generated.

vim +/phy_get_attrs +1378 drivers/gpu/drm/bridge/cdns-mhdp-core.c

  1371	
  1372	static void cdns_mhdp_fill_host_caps(struct cdns_mhdp_device *mhdp)
  1373	{
  1374		unsigned int link_rate;
  1375		struct phy_attrs attrs;
  1376	
  1377		/* Get source capabilities based on PHY attributes */
> 1378		phy_get_attrs(mhdp->phy, &attrs);
  1379	
  1380		mhdp->host.lanes_cnt = attrs.bus_width;
  1381		if (!mhdp->host.lanes_cnt)
  1382			mhdp->host.lanes_cnt = 4;
  1383	
> 1384		link_rate = attrs.max_link_rate;
  1385		if (!link_rate)
  1386			link_rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_8_1);
  1387		else
  1388			/* PHY uses Mb/s, DRM uses tens of kb/s. */
  1389			link_rate *= 100;
  1390	
  1391		mhdp->host.link_rate = link_rate;
  1392		mhdp->host.volt_swing = CDNS_VOLT_SWING(3);
  1393		mhdp->host.pre_emphasis = CDNS_PRE_EMPHASIS(3);
  1394		mhdp->host.pattern_supp = CDNS_SUPPORT_TPS(1) |
  1395					  CDNS_SUPPORT_TPS(2) | CDNS_SUPPORT_TPS(3) |
  1396					  CDNS_SUPPORT_TPS(4);
  1397		mhdp->host.lane_mapping = CDNS_LANE_MAPPING_NORMAL;
  1398		mhdp->host.fast_link = false;
  1399		mhdp->host.enhanced = true;
  1400		mhdp->host.scrambler = true;
  1401		mhdp->host.ssc = false;
  1402	}
  1403	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org

[-- Attachment #2: .config.gz --]
[-- Type: application/gzip, Size: 75334 bytes --]

[-- Attachment #3: Type: text/plain, Size: 160 bytes --]

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

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

* Re: [PATCH v7 2/3] drm: bridge: Add support for Cadence MHDP DPI/DP bridge
@ 2020-07-22 21:25     ` kernel test robot
  0 siblings, 0 replies; 26+ messages in thread
From: kernel test robot @ 2020-07-22 21:25 UTC (permalink / raw)
  To: kbuild-all

[-- Attachment #1: Type: text/plain, Size: 3801 bytes --]

Hi Swapnil,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on robh/for-next]
[also build test ERROR on linus/master v5.8-rc6 next-20200722]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Swapnil-Jakhade/drm-Add-support-for-Cadence-MHDP-DPI-DP-bridge-and-J721E-wrapper/20200722-154322
base:   https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
config: x86_64-allyesconfig (attached as .config)
compiler: clang version 12.0.0 (https://github.com/llvm/llvm-project 3d9967039d4191b77f939ddc6c6ff4275df620c2)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # install x86_64 cross compiling tool for clang build
        # apt-get install binutils-x86-64-linux-gnu
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross ARCH=x86_64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All error/warnings (new ones prefixed by >>):

>> drivers/gpu/drm/bridge/cdns-mhdp-core.c:765:10: warning: implicit conversion from 'unsigned long' to 'unsigned int' changes value from 18446744073709551613 to 4294967293 [-Wconstant-conversion]
                   writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
                   ~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/gpu/drm/bridge/cdns-mhdp-core.c:1378:2: error: implicit declaration of function 'phy_get_attrs' [-Werror,-Wimplicit-function-declaration]
           phy_get_attrs(mhdp->phy, &attrs);
           ^
>> drivers/gpu/drm/bridge/cdns-mhdp-core.c:1384:20: error: no member named 'max_link_rate' in 'struct phy_attrs'
           link_rate = attrs.max_link_rate;
                       ~~~~~ ^
   drivers/gpu/drm/bridge/cdns-mhdp-core.c:1709:10: warning: implicit conversion from 'unsigned long' to 'unsigned int' changes value from 18446744073709551613 to 4294967293 [-Wconstant-conversion]
                   writel(~CDNS_APB_INT_MASK_SW_EVENT_INT,
                   ~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   2 warnings and 2 errors generated.

vim +/phy_get_attrs +1378 drivers/gpu/drm/bridge/cdns-mhdp-core.c

  1371	
  1372	static void cdns_mhdp_fill_host_caps(struct cdns_mhdp_device *mhdp)
  1373	{
  1374		unsigned int link_rate;
  1375		struct phy_attrs attrs;
  1376	
  1377		/* Get source capabilities based on PHY attributes */
> 1378		phy_get_attrs(mhdp->phy, &attrs);
  1379	
  1380		mhdp->host.lanes_cnt = attrs.bus_width;
  1381		if (!mhdp->host.lanes_cnt)
  1382			mhdp->host.lanes_cnt = 4;
  1383	
> 1384		link_rate = attrs.max_link_rate;
  1385		if (!link_rate)
  1386			link_rate = drm_dp_bw_code_to_link_rate(DP_LINK_BW_8_1);
  1387		else
  1388			/* PHY uses Mb/s, DRM uses tens of kb/s. */
  1389			link_rate *= 100;
  1390	
  1391		mhdp->host.link_rate = link_rate;
  1392		mhdp->host.volt_swing = CDNS_VOLT_SWING(3);
  1393		mhdp->host.pre_emphasis = CDNS_PRE_EMPHASIS(3);
  1394		mhdp->host.pattern_supp = CDNS_SUPPORT_TPS(1) |
  1395					  CDNS_SUPPORT_TPS(2) | CDNS_SUPPORT_TPS(3) |
  1396					  CDNS_SUPPORT_TPS(4);
  1397		mhdp->host.lane_mapping = CDNS_LANE_MAPPING_NORMAL;
  1398		mhdp->host.fast_link = false;
  1399		mhdp->host.enhanced = true;
  1400		mhdp->host.scrambler = true;
  1401		mhdp->host.ssc = false;
  1402	}
  1403	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org

[-- Attachment #2: config.gz --]
[-- Type: application/gzip, Size: 75334 bytes --]

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

* Re: [PATCH v7 1/3] dt-bindings: drm/bridge: Document Cadence MHDP bridge bindings
  2020-07-22  7:40   ` Swapnil Jakhade
@ 2020-07-23 15:33     ` Rob Herring
  -1 siblings, 0 replies; 26+ messages in thread
From: Rob Herring @ 2020-07-23 15:33 UTC (permalink / raw)
  To: Swapnil Jakhade
  Cc: airlied, daniel, Laurent.pinchart, a.hajda, narmstrong, jonas,
	jernej.skrabec, dri-devel, devicetree, linux-kernel, mparab,
	yamonkar, tomi.valkeinen, jsarha, nsekhar, praneeth

On Wed, Jul 22, 2020 at 09:40:38AM +0200, Swapnil Jakhade wrote:
> From: Yuti Amonkar <yamonkar@cadence.com>
> 
> Document the bindings used for the Cadence MHDP DPI/DP bridge in
> yaml format.
> 
> Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
> Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
> Reviewed-by: Rob Herring <robh@kernel.org>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  .../bindings/display/bridge/cdns,mhdp.yaml    | 127 ++++++++++++++++++
>  1 file changed, 127 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
> 
> diff --git a/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml b/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
> new file mode 100644
> index 000000000000..cdf5760d4ec5
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
> @@ -0,0 +1,127 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: "http://devicetree.org/schemas/display/bridge/cdns,mhdp.yaml#"
> +$schema: "http://devicetree.org/meta-schemas/core.yaml#"
> +
> +title: Cadence MHDP bridge
> +
> +maintainers:
> +  - Swapnil Jakhade <sjakhade@cadence.com>
> +  - Yuti Amonkar <yamonkar@cadence.com>
> +
> +properties:
> +  compatible:
> +    enum:
> +      - cdns,mhdp8546
> +      - ti,j721e-mhdp8546
> +
> +  reg:
> +    minItems: 1
> +    maxItems: 2
> +    items:
> +      - description:
> +          Register block of mhdptx apb registers up to PHY mapped area (AUX_CONFIG_P).
> +          The AUX and PMA registers are not part of this range, they are instead
> +          included in the associated PHY.
> +      - description:
> +          Register block for DSS_EDP0_INTG_CFG_VP registers in case of TI J7 SoCs.
> +
> +  reg-names:
> +    minItems: 1
> +    maxItems: 2
> +    items:
> +      - const: mhdptx
> +      - const: j721e-intg
> +
> +  clocks:
> +    maxItems: 1
> +    description:
> +      DP bridge clock, used by the IP to know how to translate a number of
> +      clock cycles into a time (which is used to comply with DP standard timings
> +      and delays).
> +
> +  phys:

maxItems: 1

> +    description:
> +      phandle to the DisplayPort PHY.
> +
> +  ports:
> +    type: object
> +    description:
> +      Ports as described in Documentation/devicetree/bindings/graph.txt.
> +
> +    properties:
> +      '#address-cells':
> +        const: 1
> +
> +      '#size-cells':
> +        const: 0
> +
> +      port@0:
> +        type: object
> +        description:
> +          Input port representing the DP bridge input.
> +
> +      port@1:
> +        type: object
> +        description:
> +          Output port representing the DP bridge output.
> +
> +    required:
> +      - port@0
> +      - port@1
> +      - '#address-cells'
> +      - '#size-cells'
> +
> +allOf:
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: ti,j721e-mhdp8546
> +    then:
> +      properties:
> +        reg:
> +          minItems: 2
> +        reg-names:
> +          minItems: 2

else:
  properties:
    reg:
      maxItems: 1
    reg-names:
      maxItems: 1

> +
> +required:
> +  - compatible
> +  - clocks
> +  - reg
> +  - reg-names
> +  - phys
> +  - ports
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    mhdp: dp-bridge@f0fb000000 {
> +        compatible = "cdns,mhdp8546";
> +        reg = <0xf0 0xfb000000 0x0 0x1000000>;
> +        reg-names = "mhdptx";
> +        clocks = <&mhdp_clock>;
> +        phys = <&dp_phy>;
> +
> +        ports {
> +              #address-cells = <1>;
> +              #size-cells = <0>;
> +
> +              port@0 {
> +                     reg = <0>;
> +                     dp_bridge_input: endpoint {
> +                        remote-endpoint = <&xxx_dpi_output>;
> +                     };
> +              };
> +
> +              port@1 {
> +                     reg = <1>;
> +                     dp_bridge_output: endpoint {
> +                        remote-endpoint = <&xxx_dp_connector_input>;
> +                     };
> +              };
> +        };
> +    };
> +...
> -- 
> 2.26.1
> 

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

* Re: [PATCH v7 1/3] dt-bindings: drm/bridge: Document Cadence MHDP bridge bindings
@ 2020-07-23 15:33     ` Rob Herring
  0 siblings, 0 replies; 26+ messages in thread
From: Rob Herring @ 2020-07-23 15:33 UTC (permalink / raw)
  To: Swapnil Jakhade
  Cc: devicetree, jernej.skrabec, praneeth, yamonkar, jonas, airlied,
	nsekhar, narmstrong, jsarha, linux-kernel, dri-devel, a.hajda,
	tomi.valkeinen, Laurent.pinchart, mparab

On Wed, Jul 22, 2020 at 09:40:38AM +0200, Swapnil Jakhade wrote:
> From: Yuti Amonkar <yamonkar@cadence.com>
> 
> Document the bindings used for the Cadence MHDP DPI/DP bridge in
> yaml format.
> 
> Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
> Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
> Reviewed-by: Rob Herring <robh@kernel.org>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  .../bindings/display/bridge/cdns,mhdp.yaml    | 127 ++++++++++++++++++
>  1 file changed, 127 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
> 
> diff --git a/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml b/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
> new file mode 100644
> index 000000000000..cdf5760d4ec5
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
> @@ -0,0 +1,127 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: "http://devicetree.org/schemas/display/bridge/cdns,mhdp.yaml#"
> +$schema: "http://devicetree.org/meta-schemas/core.yaml#"
> +
> +title: Cadence MHDP bridge
> +
> +maintainers:
> +  - Swapnil Jakhade <sjakhade@cadence.com>
> +  - Yuti Amonkar <yamonkar@cadence.com>
> +
> +properties:
> +  compatible:
> +    enum:
> +      - cdns,mhdp8546
> +      - ti,j721e-mhdp8546
> +
> +  reg:
> +    minItems: 1
> +    maxItems: 2
> +    items:
> +      - description:
> +          Register block of mhdptx apb registers up to PHY mapped area (AUX_CONFIG_P).
> +          The AUX and PMA registers are not part of this range, they are instead
> +          included in the associated PHY.
> +      - description:
> +          Register block for DSS_EDP0_INTG_CFG_VP registers in case of TI J7 SoCs.
> +
> +  reg-names:
> +    minItems: 1
> +    maxItems: 2
> +    items:
> +      - const: mhdptx
> +      - const: j721e-intg
> +
> +  clocks:
> +    maxItems: 1
> +    description:
> +      DP bridge clock, used by the IP to know how to translate a number of
> +      clock cycles into a time (which is used to comply with DP standard timings
> +      and delays).
> +
> +  phys:

maxItems: 1

> +    description:
> +      phandle to the DisplayPort PHY.
> +
> +  ports:
> +    type: object
> +    description:
> +      Ports as described in Documentation/devicetree/bindings/graph.txt.
> +
> +    properties:
> +      '#address-cells':
> +        const: 1
> +
> +      '#size-cells':
> +        const: 0
> +
> +      port@0:
> +        type: object
> +        description:
> +          Input port representing the DP bridge input.
> +
> +      port@1:
> +        type: object
> +        description:
> +          Output port representing the DP bridge output.
> +
> +    required:
> +      - port@0
> +      - port@1
> +      - '#address-cells'
> +      - '#size-cells'
> +
> +allOf:
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: ti,j721e-mhdp8546
> +    then:
> +      properties:
> +        reg:
> +          minItems: 2
> +        reg-names:
> +          minItems: 2

else:
  properties:
    reg:
      maxItems: 1
    reg-names:
      maxItems: 1

> +
> +required:
> +  - compatible
> +  - clocks
> +  - reg
> +  - reg-names
> +  - phys
> +  - ports
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    mhdp: dp-bridge@f0fb000000 {
> +        compatible = "cdns,mhdp8546";
> +        reg = <0xf0 0xfb000000 0x0 0x1000000>;
> +        reg-names = "mhdptx";
> +        clocks = <&mhdp_clock>;
> +        phys = <&dp_phy>;
> +
> +        ports {
> +              #address-cells = <1>;
> +              #size-cells = <0>;
> +
> +              port@0 {
> +                     reg = <0>;
> +                     dp_bridge_input: endpoint {
> +                        remote-endpoint = <&xxx_dpi_output>;
> +                     };
> +              };
> +
> +              port@1 {
> +                     reg = <1>;
> +                     dp_bridge_output: endpoint {
> +                        remote-endpoint = <&xxx_dp_connector_input>;
> +                     };
> +              };
> +        };
> +    };
> +...
> -- 
> 2.26.1
> 
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH v7 1/3] dt-bindings: drm/bridge: Document Cadence MHDP bridge bindings
  2020-07-22  7:40   ` Swapnil Jakhade
@ 2020-07-23 15:33     ` Rob Herring
  -1 siblings, 0 replies; 26+ messages in thread
From: Rob Herring @ 2020-07-23 15:33 UTC (permalink / raw)
  To: Swapnil Jakhade
  Cc: a.hajda, airlied, dri-devel, robh+dt, devicetree, nsekhar, jonas,
	jsarha, mparab, jernej.skrabec, daniel, narmstrong,
	Laurent.pinchart, linux-kernel, praneeth, tomi.valkeinen,
	yamonkar

On Wed, 22 Jul 2020 09:40:38 +0200, Swapnil Jakhade wrote:
> From: Yuti Amonkar <yamonkar@cadence.com>
> 
> Document the bindings used for the Cadence MHDP DPI/DP bridge in
> yaml format.
> 
> Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
> Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
> Reviewed-by: Rob Herring <robh@kernel.org>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  .../bindings/display/bridge/cdns,mhdp.yaml    | 127 ++++++++++++++++++
>  1 file changed, 127 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
> 


My bot found errors running 'make dt_binding_check' on your patch:

/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.example.dt.yaml: example-0: dp-bridge@f0fb000000:reg:0: [240, 4211081216, 0, 16777216] is too long


See https://patchwork.ozlabs.org/patch/1333631

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure dt-schema is up to date:

pip3 install git+https://github.com/devicetree-org/dt-schema.git@master --upgrade

Please check and re-submit.


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

* Re: [PATCH v7 1/3] dt-bindings: drm/bridge: Document Cadence MHDP bridge bindings
@ 2020-07-23 15:33     ` Rob Herring
  0 siblings, 0 replies; 26+ messages in thread
From: Rob Herring @ 2020-07-23 15:33 UTC (permalink / raw)
  To: Swapnil Jakhade
  Cc: devicetree, jernej.skrabec, yamonkar, praneeth, jonas, airlied,
	tomi.valkeinen, narmstrong, nsekhar, linux-kernel, dri-devel,
	a.hajda, robh+dt, jsarha, mparab, Laurent.pinchart

On Wed, 22 Jul 2020 09:40:38 +0200, Swapnil Jakhade wrote:
> From: Yuti Amonkar <yamonkar@cadence.com>
> 
> Document the bindings used for the Cadence MHDP DPI/DP bridge in
> yaml format.
> 
> Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
> Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
> Reviewed-by: Rob Herring <robh@kernel.org>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  .../bindings/display/bridge/cdns,mhdp.yaml    | 127 ++++++++++++++++++
>  1 file changed, 127 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
> 


My bot found errors running 'make dt_binding_check' on your patch:

/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.example.dt.yaml: example-0: dp-bridge@f0fb000000:reg:0: [240, 4211081216, 0, 16777216] is too long


See https://patchwork.ozlabs.org/patch/1333631

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure dt-schema is up to date:

pip3 install git+https://github.com/devicetree-org/dt-schema.git@master --upgrade

Please check and re-submit.

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

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

* Re: [PATCH v7 1/3] dt-bindings: drm/bridge: Document Cadence MHDP bridge bindings
  2020-07-22  7:40   ` Swapnil Jakhade
@ 2020-07-26 17:28     ` Sam Ravnborg
  -1 siblings, 0 replies; 26+ messages in thread
From: Sam Ravnborg @ 2020-07-26 17:28 UTC (permalink / raw)
  To: Swapnil Jakhade
  Cc: airlied, daniel, Laurent.pinchart, robh+dt, a.hajda, narmstrong,
	jonas, jernej.skrabec, dri-devel, devicetree, linux-kernel,
	mparab, yamonkar, praneeth, nsekhar, jsarha, tomi.valkeinen

Hi Swapnil.

On Wed, Jul 22, 2020 at 09:40:38AM +0200, Swapnil Jakhade wrote:
> From: Yuti Amonkar <yamonkar@cadence.com>
> 
> Document the bindings used for the Cadence MHDP DPI/DP bridge in
> yaml format.
> 
> Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
> Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
> Reviewed-by: Rob Herring <robh@kernel.org>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  .../bindings/display/bridge/cdns,mhdp.yaml    | 127 ++++++++++++++++++
>  1 file changed, 127 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
> 
> diff --git a/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml b/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
> new file mode 100644
> index 000000000000..cdf5760d4ec5
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
> @@ -0,0 +1,127 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: "http://devicetree.org/schemas/display/bridge/cdns,mhdp.yaml#"
> +$schema: "http://devicetree.org/meta-schemas/core.yaml#"
> +
> +title: Cadence MHDP bridge
> +
> +maintainers:
> +  - Swapnil Jakhade <sjakhade@cadence.com>
> +  - Yuti Amonkar <yamonkar@cadence.com>
> +
> +properties:
> +  compatible:
> +    enum:
> +      - cdns,mhdp8546
> +      - ti,j721e-mhdp8546
> +
> +  reg:
> +    minItems: 1
> +    maxItems: 2
> +    items:
> +      - description:
> +          Register block of mhdptx apb registers up to PHY mapped area (AUX_CONFIG_P).
> +          The AUX and PMA registers are not part of this range, they are instead
> +          included in the associated PHY.
> +      - description:
> +          Register block for DSS_EDP0_INTG_CFG_VP registers in case of TI J7 SoCs.
> +
> +  reg-names:
> +    minItems: 1
> +    maxItems: 2
> +    items:
> +      - const: mhdptx
> +      - const: j721e-intg
> +
> +  clocks:
> +    maxItems: 1
> +    description:
> +      DP bridge clock, used by the IP to know how to translate a number of
> +      clock cycles into a time (which is used to comply with DP standard timings
> +      and delays).
> +
> +  phys:
> +    description:
> +      phandle to the DisplayPort PHY.
> +
> +  ports:
> +    type: object
> +    description:
> +      Ports as described in Documentation/devicetree/bindings/graph.txt.
> +
> +    properties:
> +      '#address-cells':
> +        const: 1
> +
> +      '#size-cells':
> +        const: 0
> +
> +      port@0:
> +        type: object
> +        description:
> +          Input port representing the DP bridge input.
> +
> +      port@1:
> +        type: object
> +        description:
> +          Output port representing the DP bridge output.
> +
> +    required:
> +      - port@0
> +      - port@1
> +      - '#address-cells'
> +      - '#size-cells'
> +
> +allOf:
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: ti,j721e-mhdp8546
> +    then:
> +      properties:
> +        reg:
> +          minItems: 2
> +        reg-names:
> +          minItems: 2
> +
> +required:
> +  - compatible
> +  - clocks
> +  - reg
> +  - reg-names
> +  - phys
> +  - ports
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    mhdp: dp-bridge@f0fb000000 {
> +        compatible = "cdns,mhdp8546";
> +        reg = <0xf0 0xfb000000 0x0 0x1000000>;
> +        reg-names = "mhdptx";
> +        clocks = <&mhdp_clock>;
> +        phys = <&dp_phy>;
> +
> +        ports {

Please be consistent in indent.
We do not aling below '{'.
Keep using 4 spaces for indent for the full example.

	Sam

> +              #address-cells = <1>;
> +              #size-cells = <0>;
> +
> +              port@0 {
> +                     reg = <0>;
> +                     dp_bridge_input: endpoint {
> +                        remote-endpoint = <&xxx_dpi_output>;
> +                     };
> +              };
> +
> +              port@1 {
> +                     reg = <1>;
> +                     dp_bridge_output: endpoint {
> +                        remote-endpoint = <&xxx_dp_connector_input>;
> +                     };
> +              };
> +        };
> +    };
> +...
> -- 
> 2.26.1
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH v7 1/3] dt-bindings: drm/bridge: Document Cadence MHDP bridge bindings
@ 2020-07-26 17:28     ` Sam Ravnborg
  0 siblings, 0 replies; 26+ messages in thread
From: Sam Ravnborg @ 2020-07-26 17:28 UTC (permalink / raw)
  To: Swapnil Jakhade
  Cc: devicetree, jernej.skrabec, praneeth, yamonkar, narmstrong,
	airlied, tomi.valkeinen, jonas, nsekhar, linux-kernel, dri-devel,
	a.hajda, robh+dt, Laurent.pinchart, jsarha, mparab

Hi Swapnil.

On Wed, Jul 22, 2020 at 09:40:38AM +0200, Swapnil Jakhade wrote:
> From: Yuti Amonkar <yamonkar@cadence.com>
> 
> Document the bindings used for the Cadence MHDP DPI/DP bridge in
> yaml format.
> 
> Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
> Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
> Reviewed-by: Rob Herring <robh@kernel.org>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  .../bindings/display/bridge/cdns,mhdp.yaml    | 127 ++++++++++++++++++
>  1 file changed, 127 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
> 
> diff --git a/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml b/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
> new file mode 100644
> index 000000000000..cdf5760d4ec5
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/bridge/cdns,mhdp.yaml
> @@ -0,0 +1,127 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: "http://devicetree.org/schemas/display/bridge/cdns,mhdp.yaml#"
> +$schema: "http://devicetree.org/meta-schemas/core.yaml#"
> +
> +title: Cadence MHDP bridge
> +
> +maintainers:
> +  - Swapnil Jakhade <sjakhade@cadence.com>
> +  - Yuti Amonkar <yamonkar@cadence.com>
> +
> +properties:
> +  compatible:
> +    enum:
> +      - cdns,mhdp8546
> +      - ti,j721e-mhdp8546
> +
> +  reg:
> +    minItems: 1
> +    maxItems: 2
> +    items:
> +      - description:
> +          Register block of mhdptx apb registers up to PHY mapped area (AUX_CONFIG_P).
> +          The AUX and PMA registers are not part of this range, they are instead
> +          included in the associated PHY.
> +      - description:
> +          Register block for DSS_EDP0_INTG_CFG_VP registers in case of TI J7 SoCs.
> +
> +  reg-names:
> +    minItems: 1
> +    maxItems: 2
> +    items:
> +      - const: mhdptx
> +      - const: j721e-intg
> +
> +  clocks:
> +    maxItems: 1
> +    description:
> +      DP bridge clock, used by the IP to know how to translate a number of
> +      clock cycles into a time (which is used to comply with DP standard timings
> +      and delays).
> +
> +  phys:
> +    description:
> +      phandle to the DisplayPort PHY.
> +
> +  ports:
> +    type: object
> +    description:
> +      Ports as described in Documentation/devicetree/bindings/graph.txt.
> +
> +    properties:
> +      '#address-cells':
> +        const: 1
> +
> +      '#size-cells':
> +        const: 0
> +
> +      port@0:
> +        type: object
> +        description:
> +          Input port representing the DP bridge input.
> +
> +      port@1:
> +        type: object
> +        description:
> +          Output port representing the DP bridge output.
> +
> +    required:
> +      - port@0
> +      - port@1
> +      - '#address-cells'
> +      - '#size-cells'
> +
> +allOf:
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: ti,j721e-mhdp8546
> +    then:
> +      properties:
> +        reg:
> +          minItems: 2
> +        reg-names:
> +          minItems: 2
> +
> +required:
> +  - compatible
> +  - clocks
> +  - reg
> +  - reg-names
> +  - phys
> +  - ports
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    mhdp: dp-bridge@f0fb000000 {
> +        compatible = "cdns,mhdp8546";
> +        reg = <0xf0 0xfb000000 0x0 0x1000000>;
> +        reg-names = "mhdptx";
> +        clocks = <&mhdp_clock>;
> +        phys = <&dp_phy>;
> +
> +        ports {

Please be consistent in indent.
We do not aling below '{'.
Keep using 4 spaces for indent for the full example.

	Sam

> +              #address-cells = <1>;
> +              #size-cells = <0>;
> +
> +              port@0 {
> +                     reg = <0>;
> +                     dp_bridge_input: endpoint {
> +                        remote-endpoint = <&xxx_dpi_output>;
> +                     };
> +              };
> +
> +              port@1 {
> +                     reg = <1>;
> +                     dp_bridge_output: endpoint {
> +                        remote-endpoint = <&xxx_dp_connector_input>;
> +                     };
> +              };
> +        };
> +    };
> +...
> -- 
> 2.26.1
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH v7 2/3] drm: bridge: Add support for Cadence MHDP DPI/DP bridge
  2020-07-22  7:40   ` Swapnil Jakhade
@ 2020-07-26 17:44     ` Sam Ravnborg
  -1 siblings, 0 replies; 26+ messages in thread
From: Sam Ravnborg @ 2020-07-26 17:44 UTC (permalink / raw)
  To: Swapnil Jakhade
  Cc: airlied, daniel, Laurent.pinchart, robh+dt, a.hajda, narmstrong,
	jonas, jernej.skrabec, dri-devel, devicetree, linux-kernel,
	mparab, yamonkar, praneeth, nsekhar, jsarha, tomi.valkeinen

Hi Swapnil

Thanks for submitting this driver.

A few highlevel things.

New bridge drivers shall be prepare for use in a chained
bridge driver setup.
This prompts you to add the following:
- Reelvant bridge operations
	I noticed at least get_edid, and detect seems applicable.
- Support the flags argument and only create the connector if requested
  to do so.

Take a look at other bridge drivers that set DRM_BRIDGE_OP_EDID and
DRM_BRIDGE_OP_DETECT for examples.

A few notes follows but I did not look at the full driver.

	Sam

On Wed, Jul 22, 2020 at 09:40:39AM +0200, Swapnil Jakhade wrote:
> Add a new DRM bridge driver for Cadence MHDP DPTX IP used in TI J721e SoC.
> MHDP DPTX IP is the component that complies with VESA DisplayPort (DP) and
> embedded Display Port (eDP) standards. It integrates uCPU running the
> embedded Firmware (FW) interfaced over APB interface.
> 
> Basically, it takes a DPI stream as input and outputs it encoded in DP
> format. Currently, it supports only SST mode.
> 
> Co-developed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
> Co-developed-by: Jyri Sarha <jsarha@ti.com>
> Signed-off-by: Jyri Sarha <jsarha@ti.com>
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
> Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
> ---
>  drivers/gpu/drm/bridge/Kconfig          |   11 +
>  drivers/gpu/drm/bridge/Makefile         |    2 +
>  drivers/gpu/drm/bridge/cdns-mhdp-core.c | 2493 +++++++++++++++++++++++
>  drivers/gpu/drm/bridge/cdns-mhdp-core.h |  396 ++++
>  4 files changed, 2902 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.c
>  create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.h
> 
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index 43271c21d3fc..6a4c324302a8 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -27,6 +27,17 @@ config DRM_CDNS_DSI
>  	  Support Cadence DPI to DSI bridge. This is an internal
>  	  bridge and is meant to be directly embedded in a SoC.
>  
> +config DRM_CDNS_MHDP
> +	tristate "Cadence DPI/DP bridge"
> +	select DRM_KMS_HELPER
> +	select DRM_PANEL_BRIDGE
> +	depends on OF
> +	help
> +	  Support Cadence DPI to DP bridge. This is an internal
> +	  bridge and is meant to be directly embedded in a SoC.
> +	  It takes a DPI stream as input and outputs it encoded
> +	  in DP format.
> +
>  config DRM_CHRONTEL_CH7033
>  	tristate "Chrontel CH7033 Video Encoder"
>  	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index d63d4b7e4347..0080a9f80f29 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -1,5 +1,7 @@
>  # SPDX-License-Identifier: GPL-2.0
>  obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
> +obj-$(CONFIG_DRM_CDNS_MHDP) += cdns-mhdp.o
> +cdns-mhdp-objs := cdns-mhdp-core.o
As the bridge driver is a sinlge file there is no need for the extra
indirection.
If you plan to add extra files later use:
cdns-mhdp-y := cdns-mhdp-core.o

The "-y" syntax is recommended today.

>  obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
>  obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
>  obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
> diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.c b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
> new file mode 100644
> index 000000000000..b16c5503cef1
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
> @@ -0,0 +1,2493 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Cadence MHDP DP bridge driver.
> + *
> + * Copyright: 2019 Cadence Design Systems, Inc.
2020?

> + *
> + * Author: Quentin Schulz <quentin.schulz@free-electrons.com>
Does not match changelog description - several more authors are listed.


> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
> @@ -0,0 +1,396 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Cadence MHDP DP bridge driver.
> + *
> + * Copyright: 2019 Cadence Design Systems, Inc.
> + *
> + * Author: Quentin Schulz <quentin.schulz@free-electrons.com>
> + *         Swapnil Jakhade <sjakhade@cadence.com>
> + */
> +
> +#ifndef CDNS_MHDP_CORE_H
> +#define CDNS_MHDP_CORE_H
> +
> +#include <linux/bits.h>
> +#include <linux/mutex.h>
> +#include <linux/spinlock.h>
> +
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_dp_helper.h>
> +
> +struct clk;
> +struct device;
> +struct phy;
> +
> +/* Register offsets */
> +#define CDNS_APB_CTRL				0x00000
> +#define CDNS_CPU_STALL				BIT(3)
> +
> +#define CDNS_MAILBOX_FULL			0x00008
> +#define CDNS_MAILBOX_EMPTY			0x0000c
> +#define CDNS_MAILBOX_TX_DATA			0x00010
> +#define CDNS_MAILBOX_RX_DATA			0x00014
> +#define CDNS_KEEP_ALIVE				0x00018
> +#define CDNS_KEEP_ALIVE_MASK			GENMASK(7, 0)
> +
> +#define CDNS_VER_L				0x0001C
> +#define CDNS_VER_H				0x00020
> +#define CDNS_LIB_L_ADDR				0x00024
> +#define CDNS_LIB_H_ADDR				0x00028
> +
> +#define CDNS_MB_INT_MASK			0x00034
> +#define CDNS_MB_INT_STATUS			0x00038
> +
> +#define CDNS_SW_CLK_L				0x0003c
> +#define CDNS_SW_CLK_H				0x00040
> +
> +#define CDNS_SW_EVENT0				0x00044
> +#define CDNS_DPTX_HPD				BIT(0)
> +
> +#define CDNS_SW_EVENT1				0x00048
> +#define CDNS_SW_EVENT2				0x0004c
> +#define CDNS_SW_EVENT3				0x00050
> +
> +#define CDNS_APB_INT_MASK			0x0006C
> +#define CDNS_APB_INT_MASK_MAILBOX_INT		BIT(0)
> +#define CDNS_APB_INT_MASK_SW_EVENT_INT		BIT(1)
> +
> +#define CDNS_APB_INT_STATUS			0x00070
> +
> +#define CDNS_DPTX_CAR				0x00904
> +#define CDNS_VIF_CLK_EN				BIT(0)
> +#define CDNS_VIF_CLK_RSTN			BIT(1)
> +
> +#define CDNS_SOURCE_VIDEO_IF(s)			(0x00b00 + ((s) * 0x20))
> +#define CDNS_BND_HSYNC2VSYNC(s)			(CDNS_SOURCE_VIDEO_IF(s) + \
> +						 0x00)
> +#define CDNS_IP_DTCT_WIN			GENMASK(11, 0)
> +#define CDNS_IP_DET_INTERLACE_FORMAT		BIT(12)
> +#define CDNS_IP_BYPASS_V_INTERFACE		BIT(13)
> +
> +#define CDNS_HSYNC2VSYNC_POL_CTRL(s)		(CDNS_SOURCE_VIDEO_IF(s) + \
> +						 0x10)
> +#define CDNS_H2V_HSYNC_POL_ACTIVE_LOW		BIT(1)
> +#define CDNS_H2V_VSYNC_POL_ACTIVE_LOW		BIT(2)
> +
> +#define CDNS_DPTX_PHY_CONFIG			0x02000
> +#define CDNS_PHY_TRAINING_EN			BIT(0)
> +#define CDNS_PHY_TRAINING_TYPE(x)		(((x) & GENMASK(3, 0)) << 1)
> +#define CDNS_PHY_SCRAMBLER_BYPASS		BIT(5)
> +#define CDNS_PHY_ENCODER_BYPASS			BIT(6)
> +#define CDNS_PHY_SKEW_BYPASS			BIT(7)
> +#define CDNS_PHY_TRAINING_AUTO			BIT(8)
> +#define CDNS_PHY_LANE0_SKEW(x)			(((x) & GENMASK(2, 0)) << 9)
> +#define CDNS_PHY_LANE1_SKEW(x)			(((x) & GENMASK(2, 0)) << 12)
> +#define CDNS_PHY_LANE2_SKEW(x)			(((x) & GENMASK(2, 0)) << 15)
> +#define CDNS_PHY_LANE3_SKEW(x)			(((x) & GENMASK(2, 0)) << 18)
> +#define CDNS_PHY_COMMON_CONFIG			(CDNS_PHY_LANE1_SKEW(1) | \
> +						CDNS_PHY_LANE2_SKEW(2) |  \
> +						CDNS_PHY_LANE3_SKEW(3))
> +#define CDNS_PHY_10BIT_EN			BIT(21)
> +
> +#define CDNS_DP_FRAMER_GLOBAL_CONFIG		0x02200
> +#define CDNS_DP_NUM_LANES(x)			((x) - 1)
> +#define CDNS_DP_MST_EN				BIT(2)
> +#define CDNS_DP_FRAMER_EN			BIT(3)
> +#define CDNS_DP_RATE_GOVERNOR_EN		BIT(4)
> +#define CDNS_DP_NO_VIDEO_MODE			BIT(5)
> +#define CDNS_DP_DISABLE_PHY_RST			BIT(6)
> +#define CDNS_DP_WR_FAILING_EDGE_VSYNC		BIT(7)
> +
> +#define CDNS_DP_FRAMER_TU			0x02208
> +#define CDNS_DP_FRAMER_TU_SIZE(x)		(((x) & GENMASK(6, 0)) << 8)
> +#define CDNS_DP_FRAMER_TU_VS(x)			((x) & GENMASK(5, 0))
> +#define CDNS_DP_FRAMER_TU_CNT_RST_EN		BIT(15)
> +
> +#define CDNS_DP_MTPH_CONTROL			0x02264
> +#define CDNS_DP_MTPH_ECF_EN			BIT(0)
> +#define CDNS_DP_MTPH_ACT_EN			BIT(1)
> +#define CDNS_DP_MTPH_LVP_EN			BIT(2)
> +
> +#define CDNS_DP_MTPH_STATUS			0x0226C
> +#define CDNS_DP_MTPH_ACT_STATUS			BIT(0)
> +
> +#define CDNS_DP_LANE_EN				0x02300
> +#define CDNS_DP_LANE_EN_LANES(x)		GENMASK((x) - 1, 0)
> +
> +#define CDNS_DP_ENHNCD				0x02304
> +
> +#define CDNS_DPTX_STREAM(s)			(0x03000 + (s) * 0x80)
> +#define CDNS_DP_MSA_HORIZONTAL_0(s)		(CDNS_DPTX_STREAM(s) + 0x00)
> +#define CDNS_DP_MSAH0_H_TOTAL(x)		(x)
> +#define CDNS_DP_MSAH0_HSYNC_START(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_HORIZONTAL_1(s)		(CDNS_DPTX_STREAM(s) + 0x04)
> +#define CDNS_DP_MSAH1_HSYNC_WIDTH(x)		(x)
> +#define CDNS_DP_MSAH1_HSYNC_POL_LOW		BIT(15)
> +#define CDNS_DP_MSAH1_HDISP_WIDTH(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_VERTICAL_0(s)		(CDNS_DPTX_STREAM(s) + 0x08)
> +#define CDNS_DP_MSAV0_V_TOTAL(x)		(x)
> +#define CDNS_DP_MSAV0_VSYNC_START(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_VERTICAL_1(s)		(CDNS_DPTX_STREAM(s) + 0x0c)
> +#define CDNS_DP_MSAV1_VSYNC_WIDTH(x)		(x)
> +#define CDNS_DP_MSAV1_VSYNC_POL_LOW		BIT(15)
> +#define CDNS_DP_MSAV1_VDISP_WIDTH(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_MISC(s)			(CDNS_DPTX_STREAM(s) + 0x10)
> +#define CDNS_DP_STREAM_CONFIG(s)		(CDNS_DPTX_STREAM(s) + 0x14)
> +#define CDNS_DP_STREAM_CONFIG_2(s)		(CDNS_DPTX_STREAM(s) + 0x2c)
> +#define CDNS_DP_SC2_TU_VS_DIFF(x)		((x) << 8)
> +
> +#define CDNS_DP_HORIZONTAL(s)			(CDNS_DPTX_STREAM(s) + 0x30)
> +#define CDNS_DP_H_HSYNC_WIDTH(x)		(x)
> +#define CDNS_DP_H_H_TOTAL(x)			((x) << 16)
> +
> +#define CDNS_DP_VERTICAL_0(s)			(CDNS_DPTX_STREAM(s) + 0x34)
> +#define CDNS_DP_V0_VHEIGHT(x)			(x)
> +#define CDNS_DP_V0_VSTART(x)			((x) << 16)
> +
> +#define CDNS_DP_VERTICAL_1(s)			(CDNS_DPTX_STREAM(s) + 0x38)
> +#define CDNS_DP_V1_VTOTAL(x)			(x)
> +#define CDNS_DP_V1_VTOTAL_EVEN			BIT(16)
> +
> +#define CDNS_DP_MST_SLOT_ALLOCATE(s)		(CDNS_DPTX_STREAM(s) + 0x44)
> +#define CDNS_DP_S_ALLOC_START_SLOT(x)		(x)
> +#define CDNS_DP_S_ALLOC_END_SLOT(x)		((x) << 8)
> +
> +#define CDNS_DP_RATE_GOVERNING(s)		(CDNS_DPTX_STREAM(s) + 0x48)
> +#define CDNS_DP_RG_TARG_AV_SLOTS_Y(x)		(x)
> +#define CDNS_DP_RG_TARG_AV_SLOTS_X(x)		((x) << 4)
> +#define CDNS_DP_RG_ENABLE			BIT(10)
> +
> +#define CDNS_DP_FRAMER_PXL_REPR(s)		(CDNS_DPTX_STREAM(s) + 0x4c)
> +#define CDNS_DP_FRAMER_6_BPC			BIT(0)
> +#define CDNS_DP_FRAMER_8_BPC			BIT(1)
> +#define CDNS_DP_FRAMER_10_BPC			BIT(2)
> +#define CDNS_DP_FRAMER_12_BPC			BIT(3)
> +#define CDNS_DP_FRAMER_16_BPC			BIT(4)
> +#define CDNS_DP_FRAMER_PXL_FORMAT		0x8
> +#define CDNS_DP_FRAMER_RGB			BIT(0)
> +#define CDNS_DP_FRAMER_YCBCR444			BIT(1)
> +#define CDNS_DP_FRAMER_YCBCR422			BIT(2)
> +#define CDNS_DP_FRAMER_YCBCR420			BIT(3)
> +#define CDNS_DP_FRAMER_Y_ONLY			BIT(4)
> +
> +#define CDNS_DP_FRAMER_SP(s)			(CDNS_DPTX_STREAM(s) + 0x50)
> +#define CDNS_DP_FRAMER_VSYNC_POL_LOW		BIT(0)
> +#define CDNS_DP_FRAMER_HSYNC_POL_LOW		BIT(1)
> +#define CDNS_DP_FRAMER_INTERLACE		BIT(2)
> +
> +#define CDNS_DP_LINE_THRESH(s)			(CDNS_DPTX_STREAM(s) + 0x64)
> +#define CDNS_DP_ACTIVE_LINE_THRESH(x)		(x)
> +
> +#define CDNS_DP_VB_ID(s)			(CDNS_DPTX_STREAM(s) + 0x68)
> +#define CDNS_DP_VB_ID_INTERLACED		BIT(2)
> +#define CDNS_DP_VB_ID_COMPRESSED		BIT(6)
> +
> +#define CDNS_DP_FRONT_BACK_PORCH(s)		(CDNS_DPTX_STREAM(s) + 0x78)
> +#define CDNS_DP_BACK_PORCH(x)			(x)
> +#define CDNS_DP_FRONT_PORCH(x)			((x) << 16)
> +
> +#define CDNS_DP_BYTE_COUNT(s)			(CDNS_DPTX_STREAM(s) + 0x7c)
> +#define CDNS_DP_BYTE_COUNT_BYTES_IN_CHUNK_SHIFT	16
> +
> +/* mailbox */
> +#define MAILBOX_RETRY_US			1000
> +#define MAILBOX_TIMEOUT_US			2000000
> +
> +#define MB_OPCODE_ID				0
> +#define MB_MODULE_ID				1
> +#define MB_SIZE_MSB_ID				2
> +#define MB_SIZE_LSB_ID				3
> +#define MB_DATA_ID				4
> +
> +#define MB_MODULE_ID_DP_TX			0x01
> +#define MB_MODULE_ID_HDCP_TX			0x07
> +#define MB_MODULE_ID_HDCP_RX			0x08
> +#define MB_MODULE_ID_HDCP_GENERAL		0x09
> +#define MB_MODULE_ID_GENERAL			0x0a
> +
> +/* firmware and opcodes */
> +#define FW_NAME					"cadence/mhdp8546.bin"
> +#define CDNS_MHDP_IMEM				0x10000
> +
> +#define GENERAL_MAIN_CONTROL			0x01
> +#define GENERAL_TEST_ECHO			0x02
> +#define GENERAL_BUS_SETTINGS			0x03
> +#define GENERAL_TEST_ACCESS			0x04
> +#define GENERAL_REGISTER_READ			0x07
> +
> +#define DPTX_SET_POWER_MNG			0x00
> +#define DPTX_GET_EDID				0x02
> +#define DPTX_READ_DPCD				0x03
> +#define DPTX_WRITE_DPCD				0x04
> +#define DPTX_ENABLE_EVENT			0x05
> +#define DPTX_WRITE_REGISTER			0x06
> +#define DPTX_READ_REGISTER			0x07
> +#define DPTX_WRITE_FIELD			0x08
> +#define DPTX_READ_EVENT				0x0a
> +#define DPTX_GET_LAST_AUX_STAUS			0x0e
> +#define DPTX_HPD_STATE				0x11
> +#define DPTX_ADJUST_LT				0x12
> +
> +#define FW_STANDBY				0
> +#define FW_ACTIVE				1
> +
> +/* HPD */
> +#define DPTX_READ_EVENT_HPD_TO_HIGH             BIT(0)
> +#define DPTX_READ_EVENT_HPD_TO_LOW              BIT(1)
> +#define DPTX_READ_EVENT_HPD_PULSE               BIT(2)
> +#define DPTX_READ_EVENT_HPD_STATE               BIT(3)
> +
> +/* general */
> +#define CDNS_DP_TRAINING_PATTERN_4		0x7
> +
> +#define CDNS_KEEP_ALIVE_TIMEOUT			2000
> +
> +#define CDNS_VOLT_SWING(x)			((x) & GENMASK(1, 0))
> +#define CDNS_FORCE_VOLT_SWING			BIT(2)
> +
> +#define CDNS_PRE_EMPHASIS(x)			((x) & GENMASK(1, 0))
> +#define CDNS_FORCE_PRE_EMPHASIS			BIT(2)
> +
> +#define CDNS_SUPPORT_TPS(x)			BIT((x) - 1)
> +
> +#define CDNS_FAST_LINK_TRAINING			BIT(0)
> +
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_0(x)	((x) & GENMASK(1, 0))
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_1(x)	((x) & GENMASK(3, 2))
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_2(x)	((x) & GENMASK(5, 4))
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_3(x)	((x) & GENMASK(7, 6))
> +#define CDNS_LANE_MAPPING_NORMAL		0xe4
> +#define CDNS_LANE_MAPPING_FLIPPED		0x1b
> +
> +#define CDNS_DP_MAX_NUM_LANES			4
> +#define CDNS_DP_TEST_VSC_SDP			BIT(6) /* 1.3+ */
> +#define CDNS_DP_TEST_COLOR_FORMAT_RAW_Y_ONLY	BIT(7)
> +
> +#define CDNS_MHDP_MAX_STREAMS   4
> +
> +#define DP_LINK_CAP_ENHANCED_FRAMING BIT(0)
> +
> +struct cdns_mhdp_link {
> +	unsigned char revision;
> +	unsigned int rate;
> +	unsigned int num_lanes;
> +	unsigned long capabilities;
> +};
> +
> +struct cdns_mhdp_host {
> +	unsigned int link_rate;
> +	u8 lanes_cnt;
> +	u8 volt_swing;
> +	u8 pre_emphasis;
> +	u8 pattern_supp;
> +	u8 lane_mapping;
> +	bool fast_link;
> +	bool enhanced;
> +	bool scrambler;
> +	bool ssc;
> +};
> +
> +struct cdns_mhdp_sink {
> +	unsigned int link_rate;
> +	u8 lanes_cnt;
> +	u8 pattern_supp;
> +	bool fast_link;
> +	bool enhanced;
> +	bool ssc;
> +};
> +
> +struct cdns_mhdp_display_fmt {
> +	u32 color_format;
> +	u32 bpc;
> +	bool y_only;
> +};
> +
> +/*
> + * These enums present MHDP hw initialization state
> + * Legal state transitions are:
> + * MHDP_HW_INACTIVE <-> MHDP_HW_LOADING -> MHDP_HW_READY
> + *        |                                     |
> + *        '----------> MHDP_HW_STOPPED <--------'
> + */
> +enum mhdp_hw_state {
> +	MHDP_HW_INACTIVE = 0,	/* HW not initialized */
> +	MHDP_HW_LOADING,	/* HW initialization in progress */
> +	MHDP_HW_READY,		/* HW ready, FW active*/
> +	MHDP_HW_STOPPED		/* Driver removal FW to be stopped */
> +};
> +
> +struct cdns_mhdp_device;
> +
> +struct mhdp_platform_ops {
> +	int (*init)(struct cdns_mhdp_device *mhdp);
> +	void (*exit)(struct cdns_mhdp_device *mhdp);
> +	void (*enable)(struct cdns_mhdp_device *mhdp);
> +	void (*disable)(struct cdns_mhdp_device *mhdp);
> +};
> +
> +struct cdns_mhdp_bridge_state {
> +	struct drm_bridge_state base;
> +	u32 tu_size;
> +	u32 vs;
> +	u32 line_thresh;
> +	struct drm_display_mode *current_mode;
> +};
> +
> +#define to_cdns_mhdp_bridge_state(s) \
> +		container_of(s, struct cdns_mhdp_bridge_state, base)
> +
> +struct cdns_mhdp_device {
> +	void __iomem *regs;
> +
> +	struct device *dev;
> +	struct clk *clk;
> +	struct phy *phy;
> +
> +	const struct mhdp_platform_ops *ops;
> +
> +	/* This is to protect mailbox communications with the firmware */
> +	struct mutex mbox_mutex;
> +
> +	/* "link_mutex" protects the access to all the link parameters
> +	 * including the link training process. Link training will be
> +	 * invoked both from threaded interrupt handler and from atomic
> +	 * callbacks when link_up is not set. So this mutex protects
> +	 * flags such as link_up, bridge_enabled, link.num_lanes,
> +	 * link.rate etc.
> +	 */
> +	struct mutex link_mutex;
> +
> +	struct drm_connector connector;
> +	struct drm_bridge bridge;
> +
> +	struct cdns_mhdp_link link;
> +	struct drm_dp_aux aux;
> +
> +	struct cdns_mhdp_host host;
> +	struct cdns_mhdp_sink sink;
> +	struct cdns_mhdp_display_fmt display_fmt;
> +	s8 stream_id;
Wondering if s8 is correct here. Seems not to be used as such.
But then I did not look to close so it may make total sense.

> +
> +	bool link_up;
> +	bool plugged;
> +
> +	/*
> +	 * "start_lock" protects the access to bridge_attached and
> +	 * hw_state data members that control the delayed firmware
> +	 * loading and attaching the bridge. They are accessed from
> +	 * both the DRM core and cdns_mhdp_fw_cb(). In most cases just
> +	 * protecting the data members is enough, but the irq mask
> +	 * setting needs to be protected when enabling the FW.
> +	 */
> +	spinlock_t start_lock;
> +	bool bridge_attached;
> +	bool bridge_enabled;
> +	enum mhdp_hw_state hw_state;
> +};
> +
> +#define connector_to_mhdp(x) container_of(x, struct cdns_mhdp_device, connector)
> +#define bridge_to_mhdp(x) container_of(x, struct cdns_mhdp_device, bridge)
> +
> +#endif
> -- 
> 2.26.1
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH v7 2/3] drm: bridge: Add support for Cadence MHDP DPI/DP bridge
@ 2020-07-26 17:44     ` Sam Ravnborg
  0 siblings, 0 replies; 26+ messages in thread
From: Sam Ravnborg @ 2020-07-26 17:44 UTC (permalink / raw)
  To: Swapnil Jakhade
  Cc: devicetree, jernej.skrabec, praneeth, yamonkar, narmstrong,
	airlied, tomi.valkeinen, jonas, nsekhar, linux-kernel, dri-devel,
	a.hajda, robh+dt, Laurent.pinchart, jsarha, mparab

Hi Swapnil

Thanks for submitting this driver.

A few highlevel things.

New bridge drivers shall be prepare for use in a chained
bridge driver setup.
This prompts you to add the following:
- Reelvant bridge operations
	I noticed at least get_edid, and detect seems applicable.
- Support the flags argument and only create the connector if requested
  to do so.

Take a look at other bridge drivers that set DRM_BRIDGE_OP_EDID and
DRM_BRIDGE_OP_DETECT for examples.

A few notes follows but I did not look at the full driver.

	Sam

On Wed, Jul 22, 2020 at 09:40:39AM +0200, Swapnil Jakhade wrote:
> Add a new DRM bridge driver for Cadence MHDP DPTX IP used in TI J721e SoC.
> MHDP DPTX IP is the component that complies with VESA DisplayPort (DP) and
> embedded Display Port (eDP) standards. It integrates uCPU running the
> embedded Firmware (FW) interfaced over APB interface.
> 
> Basically, it takes a DPI stream as input and outputs it encoded in DP
> format. Currently, it supports only SST mode.
> 
> Co-developed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
> Co-developed-by: Jyri Sarha <jsarha@ti.com>
> Signed-off-by: Jyri Sarha <jsarha@ti.com>
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
> Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
> ---
>  drivers/gpu/drm/bridge/Kconfig          |   11 +
>  drivers/gpu/drm/bridge/Makefile         |    2 +
>  drivers/gpu/drm/bridge/cdns-mhdp-core.c | 2493 +++++++++++++++++++++++
>  drivers/gpu/drm/bridge/cdns-mhdp-core.h |  396 ++++
>  4 files changed, 2902 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.c
>  create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-core.h
> 
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index 43271c21d3fc..6a4c324302a8 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -27,6 +27,17 @@ config DRM_CDNS_DSI
>  	  Support Cadence DPI to DSI bridge. This is an internal
>  	  bridge and is meant to be directly embedded in a SoC.
>  
> +config DRM_CDNS_MHDP
> +	tristate "Cadence DPI/DP bridge"
> +	select DRM_KMS_HELPER
> +	select DRM_PANEL_BRIDGE
> +	depends on OF
> +	help
> +	  Support Cadence DPI to DP bridge. This is an internal
> +	  bridge and is meant to be directly embedded in a SoC.
> +	  It takes a DPI stream as input and outputs it encoded
> +	  in DP format.
> +
>  config DRM_CHRONTEL_CH7033
>  	tristate "Chrontel CH7033 Video Encoder"
>  	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index d63d4b7e4347..0080a9f80f29 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -1,5 +1,7 @@
>  # SPDX-License-Identifier: GPL-2.0
>  obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
> +obj-$(CONFIG_DRM_CDNS_MHDP) += cdns-mhdp.o
> +cdns-mhdp-objs := cdns-mhdp-core.o
As the bridge driver is a sinlge file there is no need for the extra
indirection.
If you plan to add extra files later use:
cdns-mhdp-y := cdns-mhdp-core.o

The "-y" syntax is recommended today.

>  obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
>  obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
>  obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
> diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.c b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
> new file mode 100644
> index 000000000000..b16c5503cef1
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
> @@ -0,0 +1,2493 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Cadence MHDP DP bridge driver.
> + *
> + * Copyright: 2019 Cadence Design Systems, Inc.
2020?

> + *
> + * Author: Quentin Schulz <quentin.schulz@free-electrons.com>
Does not match changelog description - several more authors are listed.


> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
> @@ -0,0 +1,396 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Cadence MHDP DP bridge driver.
> + *
> + * Copyright: 2019 Cadence Design Systems, Inc.
> + *
> + * Author: Quentin Schulz <quentin.schulz@free-electrons.com>
> + *         Swapnil Jakhade <sjakhade@cadence.com>
> + */
> +
> +#ifndef CDNS_MHDP_CORE_H
> +#define CDNS_MHDP_CORE_H
> +
> +#include <linux/bits.h>
> +#include <linux/mutex.h>
> +#include <linux/spinlock.h>
> +
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_dp_helper.h>
> +
> +struct clk;
> +struct device;
> +struct phy;
> +
> +/* Register offsets */
> +#define CDNS_APB_CTRL				0x00000
> +#define CDNS_CPU_STALL				BIT(3)
> +
> +#define CDNS_MAILBOX_FULL			0x00008
> +#define CDNS_MAILBOX_EMPTY			0x0000c
> +#define CDNS_MAILBOX_TX_DATA			0x00010
> +#define CDNS_MAILBOX_RX_DATA			0x00014
> +#define CDNS_KEEP_ALIVE				0x00018
> +#define CDNS_KEEP_ALIVE_MASK			GENMASK(7, 0)
> +
> +#define CDNS_VER_L				0x0001C
> +#define CDNS_VER_H				0x00020
> +#define CDNS_LIB_L_ADDR				0x00024
> +#define CDNS_LIB_H_ADDR				0x00028
> +
> +#define CDNS_MB_INT_MASK			0x00034
> +#define CDNS_MB_INT_STATUS			0x00038
> +
> +#define CDNS_SW_CLK_L				0x0003c
> +#define CDNS_SW_CLK_H				0x00040
> +
> +#define CDNS_SW_EVENT0				0x00044
> +#define CDNS_DPTX_HPD				BIT(0)
> +
> +#define CDNS_SW_EVENT1				0x00048
> +#define CDNS_SW_EVENT2				0x0004c
> +#define CDNS_SW_EVENT3				0x00050
> +
> +#define CDNS_APB_INT_MASK			0x0006C
> +#define CDNS_APB_INT_MASK_MAILBOX_INT		BIT(0)
> +#define CDNS_APB_INT_MASK_SW_EVENT_INT		BIT(1)
> +
> +#define CDNS_APB_INT_STATUS			0x00070
> +
> +#define CDNS_DPTX_CAR				0x00904
> +#define CDNS_VIF_CLK_EN				BIT(0)
> +#define CDNS_VIF_CLK_RSTN			BIT(1)
> +
> +#define CDNS_SOURCE_VIDEO_IF(s)			(0x00b00 + ((s) * 0x20))
> +#define CDNS_BND_HSYNC2VSYNC(s)			(CDNS_SOURCE_VIDEO_IF(s) + \
> +						 0x00)
> +#define CDNS_IP_DTCT_WIN			GENMASK(11, 0)
> +#define CDNS_IP_DET_INTERLACE_FORMAT		BIT(12)
> +#define CDNS_IP_BYPASS_V_INTERFACE		BIT(13)
> +
> +#define CDNS_HSYNC2VSYNC_POL_CTRL(s)		(CDNS_SOURCE_VIDEO_IF(s) + \
> +						 0x10)
> +#define CDNS_H2V_HSYNC_POL_ACTIVE_LOW		BIT(1)
> +#define CDNS_H2V_VSYNC_POL_ACTIVE_LOW		BIT(2)
> +
> +#define CDNS_DPTX_PHY_CONFIG			0x02000
> +#define CDNS_PHY_TRAINING_EN			BIT(0)
> +#define CDNS_PHY_TRAINING_TYPE(x)		(((x) & GENMASK(3, 0)) << 1)
> +#define CDNS_PHY_SCRAMBLER_BYPASS		BIT(5)
> +#define CDNS_PHY_ENCODER_BYPASS			BIT(6)
> +#define CDNS_PHY_SKEW_BYPASS			BIT(7)
> +#define CDNS_PHY_TRAINING_AUTO			BIT(8)
> +#define CDNS_PHY_LANE0_SKEW(x)			(((x) & GENMASK(2, 0)) << 9)
> +#define CDNS_PHY_LANE1_SKEW(x)			(((x) & GENMASK(2, 0)) << 12)
> +#define CDNS_PHY_LANE2_SKEW(x)			(((x) & GENMASK(2, 0)) << 15)
> +#define CDNS_PHY_LANE3_SKEW(x)			(((x) & GENMASK(2, 0)) << 18)
> +#define CDNS_PHY_COMMON_CONFIG			(CDNS_PHY_LANE1_SKEW(1) | \
> +						CDNS_PHY_LANE2_SKEW(2) |  \
> +						CDNS_PHY_LANE3_SKEW(3))
> +#define CDNS_PHY_10BIT_EN			BIT(21)
> +
> +#define CDNS_DP_FRAMER_GLOBAL_CONFIG		0x02200
> +#define CDNS_DP_NUM_LANES(x)			((x) - 1)
> +#define CDNS_DP_MST_EN				BIT(2)
> +#define CDNS_DP_FRAMER_EN			BIT(3)
> +#define CDNS_DP_RATE_GOVERNOR_EN		BIT(4)
> +#define CDNS_DP_NO_VIDEO_MODE			BIT(5)
> +#define CDNS_DP_DISABLE_PHY_RST			BIT(6)
> +#define CDNS_DP_WR_FAILING_EDGE_VSYNC		BIT(7)
> +
> +#define CDNS_DP_FRAMER_TU			0x02208
> +#define CDNS_DP_FRAMER_TU_SIZE(x)		(((x) & GENMASK(6, 0)) << 8)
> +#define CDNS_DP_FRAMER_TU_VS(x)			((x) & GENMASK(5, 0))
> +#define CDNS_DP_FRAMER_TU_CNT_RST_EN		BIT(15)
> +
> +#define CDNS_DP_MTPH_CONTROL			0x02264
> +#define CDNS_DP_MTPH_ECF_EN			BIT(0)
> +#define CDNS_DP_MTPH_ACT_EN			BIT(1)
> +#define CDNS_DP_MTPH_LVP_EN			BIT(2)
> +
> +#define CDNS_DP_MTPH_STATUS			0x0226C
> +#define CDNS_DP_MTPH_ACT_STATUS			BIT(0)
> +
> +#define CDNS_DP_LANE_EN				0x02300
> +#define CDNS_DP_LANE_EN_LANES(x)		GENMASK((x) - 1, 0)
> +
> +#define CDNS_DP_ENHNCD				0x02304
> +
> +#define CDNS_DPTX_STREAM(s)			(0x03000 + (s) * 0x80)
> +#define CDNS_DP_MSA_HORIZONTAL_0(s)		(CDNS_DPTX_STREAM(s) + 0x00)
> +#define CDNS_DP_MSAH0_H_TOTAL(x)		(x)
> +#define CDNS_DP_MSAH0_HSYNC_START(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_HORIZONTAL_1(s)		(CDNS_DPTX_STREAM(s) + 0x04)
> +#define CDNS_DP_MSAH1_HSYNC_WIDTH(x)		(x)
> +#define CDNS_DP_MSAH1_HSYNC_POL_LOW		BIT(15)
> +#define CDNS_DP_MSAH1_HDISP_WIDTH(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_VERTICAL_0(s)		(CDNS_DPTX_STREAM(s) + 0x08)
> +#define CDNS_DP_MSAV0_V_TOTAL(x)		(x)
> +#define CDNS_DP_MSAV0_VSYNC_START(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_VERTICAL_1(s)		(CDNS_DPTX_STREAM(s) + 0x0c)
> +#define CDNS_DP_MSAV1_VSYNC_WIDTH(x)		(x)
> +#define CDNS_DP_MSAV1_VSYNC_POL_LOW		BIT(15)
> +#define CDNS_DP_MSAV1_VDISP_WIDTH(x)		((x) << 16)
> +
> +#define CDNS_DP_MSA_MISC(s)			(CDNS_DPTX_STREAM(s) + 0x10)
> +#define CDNS_DP_STREAM_CONFIG(s)		(CDNS_DPTX_STREAM(s) + 0x14)
> +#define CDNS_DP_STREAM_CONFIG_2(s)		(CDNS_DPTX_STREAM(s) + 0x2c)
> +#define CDNS_DP_SC2_TU_VS_DIFF(x)		((x) << 8)
> +
> +#define CDNS_DP_HORIZONTAL(s)			(CDNS_DPTX_STREAM(s) + 0x30)
> +#define CDNS_DP_H_HSYNC_WIDTH(x)		(x)
> +#define CDNS_DP_H_H_TOTAL(x)			((x) << 16)
> +
> +#define CDNS_DP_VERTICAL_0(s)			(CDNS_DPTX_STREAM(s) + 0x34)
> +#define CDNS_DP_V0_VHEIGHT(x)			(x)
> +#define CDNS_DP_V0_VSTART(x)			((x) << 16)
> +
> +#define CDNS_DP_VERTICAL_1(s)			(CDNS_DPTX_STREAM(s) + 0x38)
> +#define CDNS_DP_V1_VTOTAL(x)			(x)
> +#define CDNS_DP_V1_VTOTAL_EVEN			BIT(16)
> +
> +#define CDNS_DP_MST_SLOT_ALLOCATE(s)		(CDNS_DPTX_STREAM(s) + 0x44)
> +#define CDNS_DP_S_ALLOC_START_SLOT(x)		(x)
> +#define CDNS_DP_S_ALLOC_END_SLOT(x)		((x) << 8)
> +
> +#define CDNS_DP_RATE_GOVERNING(s)		(CDNS_DPTX_STREAM(s) + 0x48)
> +#define CDNS_DP_RG_TARG_AV_SLOTS_Y(x)		(x)
> +#define CDNS_DP_RG_TARG_AV_SLOTS_X(x)		((x) << 4)
> +#define CDNS_DP_RG_ENABLE			BIT(10)
> +
> +#define CDNS_DP_FRAMER_PXL_REPR(s)		(CDNS_DPTX_STREAM(s) + 0x4c)
> +#define CDNS_DP_FRAMER_6_BPC			BIT(0)
> +#define CDNS_DP_FRAMER_8_BPC			BIT(1)
> +#define CDNS_DP_FRAMER_10_BPC			BIT(2)
> +#define CDNS_DP_FRAMER_12_BPC			BIT(3)
> +#define CDNS_DP_FRAMER_16_BPC			BIT(4)
> +#define CDNS_DP_FRAMER_PXL_FORMAT		0x8
> +#define CDNS_DP_FRAMER_RGB			BIT(0)
> +#define CDNS_DP_FRAMER_YCBCR444			BIT(1)
> +#define CDNS_DP_FRAMER_YCBCR422			BIT(2)
> +#define CDNS_DP_FRAMER_YCBCR420			BIT(3)
> +#define CDNS_DP_FRAMER_Y_ONLY			BIT(4)
> +
> +#define CDNS_DP_FRAMER_SP(s)			(CDNS_DPTX_STREAM(s) + 0x50)
> +#define CDNS_DP_FRAMER_VSYNC_POL_LOW		BIT(0)
> +#define CDNS_DP_FRAMER_HSYNC_POL_LOW		BIT(1)
> +#define CDNS_DP_FRAMER_INTERLACE		BIT(2)
> +
> +#define CDNS_DP_LINE_THRESH(s)			(CDNS_DPTX_STREAM(s) + 0x64)
> +#define CDNS_DP_ACTIVE_LINE_THRESH(x)		(x)
> +
> +#define CDNS_DP_VB_ID(s)			(CDNS_DPTX_STREAM(s) + 0x68)
> +#define CDNS_DP_VB_ID_INTERLACED		BIT(2)
> +#define CDNS_DP_VB_ID_COMPRESSED		BIT(6)
> +
> +#define CDNS_DP_FRONT_BACK_PORCH(s)		(CDNS_DPTX_STREAM(s) + 0x78)
> +#define CDNS_DP_BACK_PORCH(x)			(x)
> +#define CDNS_DP_FRONT_PORCH(x)			((x) << 16)
> +
> +#define CDNS_DP_BYTE_COUNT(s)			(CDNS_DPTX_STREAM(s) + 0x7c)
> +#define CDNS_DP_BYTE_COUNT_BYTES_IN_CHUNK_SHIFT	16
> +
> +/* mailbox */
> +#define MAILBOX_RETRY_US			1000
> +#define MAILBOX_TIMEOUT_US			2000000
> +
> +#define MB_OPCODE_ID				0
> +#define MB_MODULE_ID				1
> +#define MB_SIZE_MSB_ID				2
> +#define MB_SIZE_LSB_ID				3
> +#define MB_DATA_ID				4
> +
> +#define MB_MODULE_ID_DP_TX			0x01
> +#define MB_MODULE_ID_HDCP_TX			0x07
> +#define MB_MODULE_ID_HDCP_RX			0x08
> +#define MB_MODULE_ID_HDCP_GENERAL		0x09
> +#define MB_MODULE_ID_GENERAL			0x0a
> +
> +/* firmware and opcodes */
> +#define FW_NAME					"cadence/mhdp8546.bin"
> +#define CDNS_MHDP_IMEM				0x10000
> +
> +#define GENERAL_MAIN_CONTROL			0x01
> +#define GENERAL_TEST_ECHO			0x02
> +#define GENERAL_BUS_SETTINGS			0x03
> +#define GENERAL_TEST_ACCESS			0x04
> +#define GENERAL_REGISTER_READ			0x07
> +
> +#define DPTX_SET_POWER_MNG			0x00
> +#define DPTX_GET_EDID				0x02
> +#define DPTX_READ_DPCD				0x03
> +#define DPTX_WRITE_DPCD				0x04
> +#define DPTX_ENABLE_EVENT			0x05
> +#define DPTX_WRITE_REGISTER			0x06
> +#define DPTX_READ_REGISTER			0x07
> +#define DPTX_WRITE_FIELD			0x08
> +#define DPTX_READ_EVENT				0x0a
> +#define DPTX_GET_LAST_AUX_STAUS			0x0e
> +#define DPTX_HPD_STATE				0x11
> +#define DPTX_ADJUST_LT				0x12
> +
> +#define FW_STANDBY				0
> +#define FW_ACTIVE				1
> +
> +/* HPD */
> +#define DPTX_READ_EVENT_HPD_TO_HIGH             BIT(0)
> +#define DPTX_READ_EVENT_HPD_TO_LOW              BIT(1)
> +#define DPTX_READ_EVENT_HPD_PULSE               BIT(2)
> +#define DPTX_READ_EVENT_HPD_STATE               BIT(3)
> +
> +/* general */
> +#define CDNS_DP_TRAINING_PATTERN_4		0x7
> +
> +#define CDNS_KEEP_ALIVE_TIMEOUT			2000
> +
> +#define CDNS_VOLT_SWING(x)			((x) & GENMASK(1, 0))
> +#define CDNS_FORCE_VOLT_SWING			BIT(2)
> +
> +#define CDNS_PRE_EMPHASIS(x)			((x) & GENMASK(1, 0))
> +#define CDNS_FORCE_PRE_EMPHASIS			BIT(2)
> +
> +#define CDNS_SUPPORT_TPS(x)			BIT((x) - 1)
> +
> +#define CDNS_FAST_LINK_TRAINING			BIT(0)
> +
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_0(x)	((x) & GENMASK(1, 0))
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_1(x)	((x) & GENMASK(3, 2))
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_2(x)	((x) & GENMASK(5, 4))
> +#define CDNS_LANE_MAPPING_TYPE_C_LANE_3(x)	((x) & GENMASK(7, 6))
> +#define CDNS_LANE_MAPPING_NORMAL		0xe4
> +#define CDNS_LANE_MAPPING_FLIPPED		0x1b
> +
> +#define CDNS_DP_MAX_NUM_LANES			4
> +#define CDNS_DP_TEST_VSC_SDP			BIT(6) /* 1.3+ */
> +#define CDNS_DP_TEST_COLOR_FORMAT_RAW_Y_ONLY	BIT(7)
> +
> +#define CDNS_MHDP_MAX_STREAMS   4
> +
> +#define DP_LINK_CAP_ENHANCED_FRAMING BIT(0)
> +
> +struct cdns_mhdp_link {
> +	unsigned char revision;
> +	unsigned int rate;
> +	unsigned int num_lanes;
> +	unsigned long capabilities;
> +};
> +
> +struct cdns_mhdp_host {
> +	unsigned int link_rate;
> +	u8 lanes_cnt;
> +	u8 volt_swing;
> +	u8 pre_emphasis;
> +	u8 pattern_supp;
> +	u8 lane_mapping;
> +	bool fast_link;
> +	bool enhanced;
> +	bool scrambler;
> +	bool ssc;
> +};
> +
> +struct cdns_mhdp_sink {
> +	unsigned int link_rate;
> +	u8 lanes_cnt;
> +	u8 pattern_supp;
> +	bool fast_link;
> +	bool enhanced;
> +	bool ssc;
> +};
> +
> +struct cdns_mhdp_display_fmt {
> +	u32 color_format;
> +	u32 bpc;
> +	bool y_only;
> +};
> +
> +/*
> + * These enums present MHDP hw initialization state
> + * Legal state transitions are:
> + * MHDP_HW_INACTIVE <-> MHDP_HW_LOADING -> MHDP_HW_READY
> + *        |                                     |
> + *        '----------> MHDP_HW_STOPPED <--------'
> + */
> +enum mhdp_hw_state {
> +	MHDP_HW_INACTIVE = 0,	/* HW not initialized */
> +	MHDP_HW_LOADING,	/* HW initialization in progress */
> +	MHDP_HW_READY,		/* HW ready, FW active*/
> +	MHDP_HW_STOPPED		/* Driver removal FW to be stopped */
> +};
> +
> +struct cdns_mhdp_device;
> +
> +struct mhdp_platform_ops {
> +	int (*init)(struct cdns_mhdp_device *mhdp);
> +	void (*exit)(struct cdns_mhdp_device *mhdp);
> +	void (*enable)(struct cdns_mhdp_device *mhdp);
> +	void (*disable)(struct cdns_mhdp_device *mhdp);
> +};
> +
> +struct cdns_mhdp_bridge_state {
> +	struct drm_bridge_state base;
> +	u32 tu_size;
> +	u32 vs;
> +	u32 line_thresh;
> +	struct drm_display_mode *current_mode;
> +};
> +
> +#define to_cdns_mhdp_bridge_state(s) \
> +		container_of(s, struct cdns_mhdp_bridge_state, base)
> +
> +struct cdns_mhdp_device {
> +	void __iomem *regs;
> +
> +	struct device *dev;
> +	struct clk *clk;
> +	struct phy *phy;
> +
> +	const struct mhdp_platform_ops *ops;
> +
> +	/* This is to protect mailbox communications with the firmware */
> +	struct mutex mbox_mutex;
> +
> +	/* "link_mutex" protects the access to all the link parameters
> +	 * including the link training process. Link training will be
> +	 * invoked both from threaded interrupt handler and from atomic
> +	 * callbacks when link_up is not set. So this mutex protects
> +	 * flags such as link_up, bridge_enabled, link.num_lanes,
> +	 * link.rate etc.
> +	 */
> +	struct mutex link_mutex;
> +
> +	struct drm_connector connector;
> +	struct drm_bridge bridge;
> +
> +	struct cdns_mhdp_link link;
> +	struct drm_dp_aux aux;
> +
> +	struct cdns_mhdp_host host;
> +	struct cdns_mhdp_sink sink;
> +	struct cdns_mhdp_display_fmt display_fmt;
> +	s8 stream_id;
Wondering if s8 is correct here. Seems not to be used as such.
But then I did not look to close so it may make total sense.

> +
> +	bool link_up;
> +	bool plugged;
> +
> +	/*
> +	 * "start_lock" protects the access to bridge_attached and
> +	 * hw_state data members that control the delayed firmware
> +	 * loading and attaching the bridge. They are accessed from
> +	 * both the DRM core and cdns_mhdp_fw_cb(). In most cases just
> +	 * protecting the data members is enough, but the irq mask
> +	 * setting needs to be protected when enabling the FW.
> +	 */
> +	spinlock_t start_lock;
> +	bool bridge_attached;
> +	bool bridge_enabled;
> +	enum mhdp_hw_state hw_state;
> +};
> +
> +#define connector_to_mhdp(x) container_of(x, struct cdns_mhdp_device, connector)
> +#define bridge_to_mhdp(x) container_of(x, struct cdns_mhdp_device, bridge)
> +
> +#endif
> -- 
> 2.26.1
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH v7 3/3] drm: bridge: cdns-mhdp: Add j721e wrapper
  2020-07-22  7:40   ` Swapnil Jakhade
@ 2020-07-26 17:50     ` Sam Ravnborg
  -1 siblings, 0 replies; 26+ messages in thread
From: Sam Ravnborg @ 2020-07-26 17:50 UTC (permalink / raw)
  To: Swapnil Jakhade
  Cc: airlied, daniel, Laurent.pinchart, robh+dt, a.hajda, narmstrong,
	jonas, jernej.skrabec, dri-devel, devicetree, linux-kernel,
	mparab, yamonkar, praneeth, nsekhar, jsarha, tomi.valkeinen

Hi Swapnil

Small nits in the following.

	Sam

On Wed, Jul 22, 2020 at 09:40:40AM +0200, Swapnil Jakhade wrote:
> Add j721e wrapper for mhdp, which sets up the clock and data muxes.
> 
> Signed-off-by: Jyri Sarha <jsarha@ti.com>
> Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
> Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
> Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  drivers/gpu/drm/bridge/Kconfig           | 13 ++++
>  drivers/gpu/drm/bridge/Makefile          |  5 ++
>  drivers/gpu/drm/bridge/cdns-mhdp-core.c  | 15 +++++
>  drivers/gpu/drm/bridge/cdns-mhdp-core.h  |  1 +
>  drivers/gpu/drm/bridge/cdns-mhdp-j721e.c | 75 ++++++++++++++++++++++++
>  drivers/gpu/drm/bridge/cdns-mhdp-j721e.h | 19 ++++++
>  6 files changed, 128 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-j721e.c
>  create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-j721e.h
> 
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index 6a4c324302a8..8c1738653b7e 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -38,6 +38,19 @@ config DRM_CDNS_MHDP
>  	  It takes a DPI stream as input and outputs it encoded
>  	  in DP format.
>  
> +if DRM_CDNS_MHDP
> +
> +config DRM_CDNS_MHDP_J721E
> +	depends on ARCH_K3_J721E_SOC
> +	bool "J721E Cadence DPI/DP wrapper support"
> +	default y
> +	help
> +	  Support J721E Cadence DPI/DP wrapper. This is a wrapper
> +	  which adds support for J721E related platform ops. It
> +	  initializes the J721e Display Port and sets up the
> +	  clock and data muxes.
> +endif
> +
>  config DRM_CHRONTEL_CH7033
>  	tristate "Chrontel CH7033 Video Encoder"
>  	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index 0080a9f80f29..f81d606e9598 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -2,6 +2,11 @@
>  obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
>  obj-$(CONFIG_DRM_CDNS_MHDP) += cdns-mhdp.o
>  cdns-mhdp-objs := cdns-mhdp-core.o
> +
> +ifeq ($(CONFIG_DRM_CDNS_MHDP_J721E),y)
> +	cdns-mhdp-objs += cdns-mhdp-j721e.o
> +endif

Use the idiomatic:
cdns-mhdp-$(CONFIG_DRM_CDNS_MHDP_J721E) += cdns-mhdp-j721e.o


> +
>  obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
>  obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
>  obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
> diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.c b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
> index b16c5503cef1..eeb33c54334f 100644
> --- a/drivers/gpu/drm/bridge/cdns-mhdp-core.c
> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
> @@ -38,6 +38,8 @@
>  
>  #include "cdns-mhdp-core.h"
>  
> +#include "cdns-mhdp-j721e.h"
> +
>  static DECLARE_WAIT_QUEUE_HEAD(fw_load_wq);
>  
>  static int cdns_mhdp_mailbox_read(struct cdns_mhdp_device *mhdp)
> @@ -1691,6 +1693,16 @@ static int cdns_mhdp_attach(struct drm_bridge *bridge,
>  
>  	conn->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH;
>  
> +	if (of_device_is_compatible(mhdp->dev->of_node, "ti,j721e-mhdp8546"))
> +	/*
> +	 * DP is internal to J7 SoC and we need to use DRIVE_POSEDGE
> +	 * in the display controller. This is achieved for the time being
> +	 * by defining SAMPLE_NEGEDGE here.
> +	 */
> +		conn->display_info.bus_flags |=
> +					DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE |
> +					DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE;
> +
>  	ret = drm_connector_attach_encoder(conn, bridge->encoder);
>  	if (ret) {
>  		dev_err(mhdp->dev, "failed to attach connector to encoder\n");
> @@ -2470,6 +2482,9 @@ static int cdns_mhdp_remove(struct platform_device *pdev)
>  
>  static const struct of_device_id mhdp_ids[] = {
>  	{ .compatible = "cdns,mhdp8546", },
> +#ifdef CONFIG_DRM_CDNS_MHDP_J721E
> +	{ .compatible = "ti,j721e-mhdp8546", .data = &mhdp_ti_j721e_ops },
> +#endif
>  	{ /* sentinel */ }
>  };
>  MODULE_DEVICE_TABLE(of, mhdp_ids);
> diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.h b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
> index d1bfcdef4249..4e176698caca 100644
> --- a/drivers/gpu/drm/bridge/cdns-mhdp-core.h
> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
> @@ -343,6 +343,7 @@ struct cdns_mhdp_bridge_state {
>  
>  struct cdns_mhdp_device {
>  	void __iomem *regs;
> +	void __iomem *j721e_regs;
>  
>  	struct device *dev;
>  	struct clk *clk;
> diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-j721e.c b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.c
> new file mode 100644
> index 000000000000..7555c217c9a0
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.c
> @@ -0,0 +1,75 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * TI j721e Cadence MHDP DP wrapper
> + *
> + * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
> + * Author: Jyri Sarha <jsarha@ti.com
> + */
> +
> +#include <linux/io.h>
> +#include <linux/platform_device.h>
> +
> +#include "cdns-mhdp-j721e.h"
> +
> +#define	REVISION			0x00
> +#define	DPTX_IPCFG			0x04
> +#define	ECC_MEM_CFG			0x08
> +#define	DPTX_DSC_CFG			0x0c
> +#define	DPTX_SRC_CFG			0x10
> +#define	DPTX_VIF_SECURE_MODE_CFG	0x14
> +#define	DPTX_VIF_CONN_STATUS		0x18
> +#define	PHY_CLK_STATUS			0x1c
> +
> +#define DPTX_SRC_AIF_EN			BIT(16)
> +#define DPTX_SRC_VIF_3_IN30B		BIT(11)
> +#define DPTX_SRC_VIF_2_IN30B		BIT(10)
> +#define DPTX_SRC_VIF_1_IN30B		BIT(9)
> +#define DPTX_SRC_VIF_0_IN30B		BIT(8)
> +#define DPTX_SRC_VIF_3_SEL_DPI5		BIT(7)
> +#define DPTX_SRC_VIF_3_SEL_DPI3		0
> +#define DPTX_SRC_VIF_2_SEL_DPI4		BIT(6)
> +#define DPTX_SRC_VIF_2_SEL_DPI2		0
> +#define DPTX_SRC_VIF_1_SEL_DPI3		BIT(5)
> +#define DPTX_SRC_VIF_1_SEL_DPI1		0
> +#define DPTX_SRC_VIF_0_SEL_DPI2		BIT(4)
> +#define DPTX_SRC_VIF_0_SEL_DPI0		0
> +#define DPTX_SRC_VIF_3_EN		BIT(3)
> +#define DPTX_SRC_VIF_2_EN		BIT(2)
> +#define DPTX_SRC_VIF_1_EN		BIT(1)
> +#define DPTX_SRC_VIF_0_EN		BIT(0)
> +
> +/* TODO turn DPTX_IPCFG fw_mem_clk_en at pm_runtime_suspend. */
> +
> +static int cdns_mhdp_j721e_init(struct cdns_mhdp_device *mhdp)
> +{
> +	struct platform_device *pdev = to_platform_device(mhdp->dev);
> +
> +	mhdp->j721e_regs = devm_platform_ioremap_resource(pdev, 1);
> +	if (IS_ERR(mhdp->j721e_regs))
> +		return PTR_ERR(mhdp->j721e_regs);

Use PTR_ERR_OR_ZERO() like this:

	return PTR_ERR_OR_ZERO(mhdp->j721e_regs);

> +
> +	return 0;
> +}
> +
> +static void cdns_mhdp_j721e_enable(struct cdns_mhdp_device *mhdp)
> +{
> +	/*
> +	 * Eneble VIF_0 and select DPI2 as its input. DSS0 DPI0 is connected
> +	 * to eDP DPI2. This is the only supported SST configuration on
> +	 * J721E.
> +	 */
> +	writel(DPTX_SRC_VIF_0_EN | DPTX_SRC_VIF_0_SEL_DPI2,
> +	       mhdp->j721e_regs + DPTX_SRC_CFG);
> +}
> +
> +static void cdns_mhdp_j721e_disable(struct cdns_mhdp_device *mhdp)
> +{
> +	/* Put everything to defaults  */
> +	writel(0, mhdp->j721e_regs + DPTX_DSC_CFG);
> +}
> +
> +const struct mhdp_platform_ops mhdp_ti_j721e_ops = {
> +	.init = cdns_mhdp_j721e_init,
> +	.enable = cdns_mhdp_j721e_enable,
> +	.disable = cdns_mhdp_j721e_disable,
> +};
> diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-j721e.h b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.h
> new file mode 100644
> index 000000000000..f1ff07b82d32
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * TI j721e Cadence MHDP DP wrapper
> + *
> + * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
> + * Author: Jyri Sarha <jsarha@ti.com
> + */
> +
> +#ifndef CDNS_MHDP_J721E_H
> +#define CDNS_MHDP_J721E_H
> +
> +#include <linux/platform_device.h>
> +#include "cdns-mhdp-core.h"
> +
> +struct mhdp_platform_ops;
> +
> +extern const struct mhdp_platform_ops mhdp_ti_j721e_ops;
> +
> +#endif /* !CDNS_MHDP_J721E_H */
> -- 
> 2.26.1
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* Re: [PATCH v7 3/3] drm: bridge: cdns-mhdp: Add j721e wrapper
@ 2020-07-26 17:50     ` Sam Ravnborg
  0 siblings, 0 replies; 26+ messages in thread
From: Sam Ravnborg @ 2020-07-26 17:50 UTC (permalink / raw)
  To: Swapnil Jakhade
  Cc: devicetree, jernej.skrabec, praneeth, yamonkar, narmstrong,
	airlied, tomi.valkeinen, jonas, nsekhar, linux-kernel, dri-devel,
	a.hajda, robh+dt, Laurent.pinchart, jsarha, mparab

Hi Swapnil

Small nits in the following.

	Sam

On Wed, Jul 22, 2020 at 09:40:40AM +0200, Swapnil Jakhade wrote:
> Add j721e wrapper for mhdp, which sets up the clock and data muxes.
> 
> Signed-off-by: Jyri Sarha <jsarha@ti.com>
> Signed-off-by: Yuti Amonkar <yamonkar@cadence.com>
> Signed-off-by: Swapnil Jakhade <sjakhade@cadence.com>
> Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  drivers/gpu/drm/bridge/Kconfig           | 13 ++++
>  drivers/gpu/drm/bridge/Makefile          |  5 ++
>  drivers/gpu/drm/bridge/cdns-mhdp-core.c  | 15 +++++
>  drivers/gpu/drm/bridge/cdns-mhdp-core.h  |  1 +
>  drivers/gpu/drm/bridge/cdns-mhdp-j721e.c | 75 ++++++++++++++++++++++++
>  drivers/gpu/drm/bridge/cdns-mhdp-j721e.h | 19 ++++++
>  6 files changed, 128 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-j721e.c
>  create mode 100644 drivers/gpu/drm/bridge/cdns-mhdp-j721e.h
> 
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index 6a4c324302a8..8c1738653b7e 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -38,6 +38,19 @@ config DRM_CDNS_MHDP
>  	  It takes a DPI stream as input and outputs it encoded
>  	  in DP format.
>  
> +if DRM_CDNS_MHDP
> +
> +config DRM_CDNS_MHDP_J721E
> +	depends on ARCH_K3_J721E_SOC
> +	bool "J721E Cadence DPI/DP wrapper support"
> +	default y
> +	help
> +	  Support J721E Cadence DPI/DP wrapper. This is a wrapper
> +	  which adds support for J721E related platform ops. It
> +	  initializes the J721e Display Port and sets up the
> +	  clock and data muxes.
> +endif
> +
>  config DRM_CHRONTEL_CH7033
>  	tristate "Chrontel CH7033 Video Encoder"
>  	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index 0080a9f80f29..f81d606e9598 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -2,6 +2,11 @@
>  obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o
>  obj-$(CONFIG_DRM_CDNS_MHDP) += cdns-mhdp.o
>  cdns-mhdp-objs := cdns-mhdp-core.o
> +
> +ifeq ($(CONFIG_DRM_CDNS_MHDP_J721E),y)
> +	cdns-mhdp-objs += cdns-mhdp-j721e.o
> +endif

Use the idiomatic:
cdns-mhdp-$(CONFIG_DRM_CDNS_MHDP_J721E) += cdns-mhdp-j721e.o


> +
>  obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
>  obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
>  obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
> diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.c b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
> index b16c5503cef1..eeb33c54334f 100644
> --- a/drivers/gpu/drm/bridge/cdns-mhdp-core.c
> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.c
> @@ -38,6 +38,8 @@
>  
>  #include "cdns-mhdp-core.h"
>  
> +#include "cdns-mhdp-j721e.h"
> +
>  static DECLARE_WAIT_QUEUE_HEAD(fw_load_wq);
>  
>  static int cdns_mhdp_mailbox_read(struct cdns_mhdp_device *mhdp)
> @@ -1691,6 +1693,16 @@ static int cdns_mhdp_attach(struct drm_bridge *bridge,
>  
>  	conn->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH;
>  
> +	if (of_device_is_compatible(mhdp->dev->of_node, "ti,j721e-mhdp8546"))
> +	/*
> +	 * DP is internal to J7 SoC and we need to use DRIVE_POSEDGE
> +	 * in the display controller. This is achieved for the time being
> +	 * by defining SAMPLE_NEGEDGE here.
> +	 */
> +		conn->display_info.bus_flags |=
> +					DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE |
> +					DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE;
> +
>  	ret = drm_connector_attach_encoder(conn, bridge->encoder);
>  	if (ret) {
>  		dev_err(mhdp->dev, "failed to attach connector to encoder\n");
> @@ -2470,6 +2482,9 @@ static int cdns_mhdp_remove(struct platform_device *pdev)
>  
>  static const struct of_device_id mhdp_ids[] = {
>  	{ .compatible = "cdns,mhdp8546", },
> +#ifdef CONFIG_DRM_CDNS_MHDP_J721E
> +	{ .compatible = "ti,j721e-mhdp8546", .data = &mhdp_ti_j721e_ops },
> +#endif
>  	{ /* sentinel */ }
>  };
>  MODULE_DEVICE_TABLE(of, mhdp_ids);
> diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-core.h b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
> index d1bfcdef4249..4e176698caca 100644
> --- a/drivers/gpu/drm/bridge/cdns-mhdp-core.h
> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-core.h
> @@ -343,6 +343,7 @@ struct cdns_mhdp_bridge_state {
>  
>  struct cdns_mhdp_device {
>  	void __iomem *regs;
> +	void __iomem *j721e_regs;
>  
>  	struct device *dev;
>  	struct clk *clk;
> diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-j721e.c b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.c
> new file mode 100644
> index 000000000000..7555c217c9a0
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.c
> @@ -0,0 +1,75 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * TI j721e Cadence MHDP DP wrapper
> + *
> + * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
> + * Author: Jyri Sarha <jsarha@ti.com
> + */
> +
> +#include <linux/io.h>
> +#include <linux/platform_device.h>
> +
> +#include "cdns-mhdp-j721e.h"
> +
> +#define	REVISION			0x00
> +#define	DPTX_IPCFG			0x04
> +#define	ECC_MEM_CFG			0x08
> +#define	DPTX_DSC_CFG			0x0c
> +#define	DPTX_SRC_CFG			0x10
> +#define	DPTX_VIF_SECURE_MODE_CFG	0x14
> +#define	DPTX_VIF_CONN_STATUS		0x18
> +#define	PHY_CLK_STATUS			0x1c
> +
> +#define DPTX_SRC_AIF_EN			BIT(16)
> +#define DPTX_SRC_VIF_3_IN30B		BIT(11)
> +#define DPTX_SRC_VIF_2_IN30B		BIT(10)
> +#define DPTX_SRC_VIF_1_IN30B		BIT(9)
> +#define DPTX_SRC_VIF_0_IN30B		BIT(8)
> +#define DPTX_SRC_VIF_3_SEL_DPI5		BIT(7)
> +#define DPTX_SRC_VIF_3_SEL_DPI3		0
> +#define DPTX_SRC_VIF_2_SEL_DPI4		BIT(6)
> +#define DPTX_SRC_VIF_2_SEL_DPI2		0
> +#define DPTX_SRC_VIF_1_SEL_DPI3		BIT(5)
> +#define DPTX_SRC_VIF_1_SEL_DPI1		0
> +#define DPTX_SRC_VIF_0_SEL_DPI2		BIT(4)
> +#define DPTX_SRC_VIF_0_SEL_DPI0		0
> +#define DPTX_SRC_VIF_3_EN		BIT(3)
> +#define DPTX_SRC_VIF_2_EN		BIT(2)
> +#define DPTX_SRC_VIF_1_EN		BIT(1)
> +#define DPTX_SRC_VIF_0_EN		BIT(0)
> +
> +/* TODO turn DPTX_IPCFG fw_mem_clk_en at pm_runtime_suspend. */
> +
> +static int cdns_mhdp_j721e_init(struct cdns_mhdp_device *mhdp)
> +{
> +	struct platform_device *pdev = to_platform_device(mhdp->dev);
> +
> +	mhdp->j721e_regs = devm_platform_ioremap_resource(pdev, 1);
> +	if (IS_ERR(mhdp->j721e_regs))
> +		return PTR_ERR(mhdp->j721e_regs);

Use PTR_ERR_OR_ZERO() like this:

	return PTR_ERR_OR_ZERO(mhdp->j721e_regs);

> +
> +	return 0;
> +}
> +
> +static void cdns_mhdp_j721e_enable(struct cdns_mhdp_device *mhdp)
> +{
> +	/*
> +	 * Eneble VIF_0 and select DPI2 as its input. DSS0 DPI0 is connected
> +	 * to eDP DPI2. This is the only supported SST configuration on
> +	 * J721E.
> +	 */
> +	writel(DPTX_SRC_VIF_0_EN | DPTX_SRC_VIF_0_SEL_DPI2,
> +	       mhdp->j721e_regs + DPTX_SRC_CFG);
> +}
> +
> +static void cdns_mhdp_j721e_disable(struct cdns_mhdp_device *mhdp)
> +{
> +	/* Put everything to defaults  */
> +	writel(0, mhdp->j721e_regs + DPTX_DSC_CFG);
> +}
> +
> +const struct mhdp_platform_ops mhdp_ti_j721e_ops = {
> +	.init = cdns_mhdp_j721e_init,
> +	.enable = cdns_mhdp_j721e_enable,
> +	.disable = cdns_mhdp_j721e_disable,
> +};
> diff --git a/drivers/gpu/drm/bridge/cdns-mhdp-j721e.h b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.h
> new file mode 100644
> index 000000000000..f1ff07b82d32
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/cdns-mhdp-j721e.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * TI j721e Cadence MHDP DP wrapper
> + *
> + * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
> + * Author: Jyri Sarha <jsarha@ti.com
> + */
> +
> +#ifndef CDNS_MHDP_J721E_H
> +#define CDNS_MHDP_J721E_H
> +
> +#include <linux/platform_device.h>
> +#include "cdns-mhdp-core.h"
> +
> +struct mhdp_platform_ops;
> +
> +extern const struct mhdp_platform_ops mhdp_ti_j721e_ops;
> +
> +#endif /* !CDNS_MHDP_J721E_H */
> -- 
> 2.26.1
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

end of thread, other threads:[~2020-07-26 17:50 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-07-22  7:40 [PATCH v7 0/3] drm: Add support for Cadence MHDP DPI/DP bridge and J721E wrapper Swapnil Jakhade
2020-07-22  7:40 ` Swapnil Jakhade
2020-07-22  7:40 ` [PATCH v7 1/3] dt-bindings: drm/bridge: Document Cadence MHDP bridge bindings Swapnil Jakhade
2020-07-22  7:40   ` Swapnil Jakhade
2020-07-23 15:33   ` Rob Herring
2020-07-23 15:33     ` Rob Herring
2020-07-23 15:33   ` Rob Herring
2020-07-23 15:33     ` Rob Herring
2020-07-26 17:28   ` Sam Ravnborg
2020-07-26 17:28     ` Sam Ravnborg
2020-07-22  7:40 ` [PATCH v7 2/3] drm: bridge: Add support for Cadence MHDP DPI/DP bridge Swapnil Jakhade
2020-07-22  7:40   ` Swapnil Jakhade
2020-07-22 11:43   ` kernel test robot
2020-07-22 11:43     ` kernel test robot
2020-07-22 11:43     ` kernel test robot
2020-07-22 11:51   ` Laurent Pinchart
2020-07-22 11:51     ` Laurent Pinchart
2020-07-22 21:25   ` kernel test robot
2020-07-22 21:25     ` kernel test robot
2020-07-22 21:25     ` kernel test robot
2020-07-26 17:44   ` Sam Ravnborg
2020-07-26 17:44     ` Sam Ravnborg
2020-07-22  7:40 ` [PATCH v7 3/3] drm: bridge: cdns-mhdp: Add j721e wrapper Swapnil Jakhade
2020-07-22  7:40   ` Swapnil Jakhade
2020-07-26 17:50   ` Sam Ravnborg
2020-07-26 17:50     ` Sam Ravnborg

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