Эх сурвалжийг харах

Merge branch 'drm/next/du' of git://linuxtv.org/pinchartl/media into drm-next

- Convert LVDS support to a drm_bridge driver
- Add DT bindings for the R8A77995 SoC
- Add DT bindings and driver support for the R8A77970 SoC

Note that the LVDS conversion depends on a patch series from Frank Rowand that
will make it upstream through Rob Herring's tree. Frank has provided a stable
branch based on v4.16-rc1 with the patches, and both Rob and I have merged it
into our trees. This should thus generate no conflict when reaching -next.

* 'drm/next/du' of git://linuxtv.org/pinchartl/media:
  dt-bindings: display: renesas: lvds: Document r8a77995 bindings
  dt-bindings: display: renesas: du: Document r8a77995 bindings
  drm: rcar-du: lvds: Add R8A77970 support
  drm: rcar-du: Add R8A77970 support
  dt-bindings: display: renesas: lvds: Document R8A77970 bindings
  dt-bindings: display: renesas: du: Document R8A77970 bindings
  drm: rcar-du: Convert LVDS encoder code to bridge driver
  drm: rcar-du: Fix legacy DT to create LVDS encoder nodes
  dt-bindings: display: renesas: Deprecate LVDS support in the DU bindings
  dt-bindings: display: renesas: Add R-Car LVDS encoder DT bindings
  of: improve reporting invalid overlay target path
  of: convert unittest overlay devicetree source to sugar syntax
  of: Documentation: of_overlay_apply() replaced by of_overlay_fdt_apply()
  of: change overlay apply input data from unflattened to FDT
  x86: devicetree: fix config option around x86_flattree_get_config()
Dave Airlie 7 жил өмнө
parent
commit
2ec360bbc3
49 өөрчлөгдсөн 1884 нэмэгдсэн , 1124 устгасан
  1. 58 0
      Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt
  2. 15 20
      Documentation/devicetree/bindings/display/renesas,du.txt
  3. 2 2
      Documentation/devicetree/overlay-notes.txt
  4. 1 0
      MAINTAINERS
  5. 1 1
      arch/x86/kernel/devicetree.c
  6. 4 2
      drivers/gpu/drm/rcar-du/Kconfig
  7. 7 3
      drivers/gpu/drm/rcar-du/Makefile
  8. 36 6
      drivers/gpu/drm/rcar-du/rcar_du_drv.c
  9. 0 5
      drivers/gpu/drm/rcar-du/rcar_du_drv.h
  10. 15 160
      drivers/gpu/drm/rcar-du/rcar_du_encoder.c
  11. 0 12
      drivers/gpu/drm/rcar-du/rcar_du_encoder.h
  12. 4 10
      drivers/gpu/drm/rcar-du/rcar_du_kms.c
  13. 0 93
      drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c
  14. 0 24
      drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h
  15. 0 238
      drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c
  16. 0 64
      drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h
  17. 322 0
      drivers/gpu/drm/rcar-du/rcar_du_of.c
  18. 20 0
      drivers/gpu/drm/rcar-du/rcar_du_of.h
  19. 76 0
      drivers/gpu/drm/rcar-du/rcar_du_of_lvds_r8a7790.dts
  20. 50 0
      drivers/gpu/drm/rcar-du/rcar_du_of_lvds_r8a7791.dts
  21. 50 0
      drivers/gpu/drm/rcar-du/rcar_du_of_lvds_r8a7793.dts
  22. 50 0
      drivers/gpu/drm/rcar-du/rcar_du_of_lvds_r8a7795.dts
  23. 50 0
      drivers/gpu/drm/rcar-du/rcar_du_of_lvds_r8a7796.dts
  24. 540 0
      drivers/gpu/drm/rcar-du/rcar_lvds.c
  25. 1 0
      drivers/of/Kconfig
  26. 119 15
      drivers/of/overlay.c
  27. 0 6
      drivers/of/resolver.c
  28. 23 5
      drivers/of/unittest-data/Makefile
  29. 44 57
      drivers/of/unittest-data/overlay.dts
  30. 14 0
      drivers/of/unittest-data/overlay_0.dts
  31. 14 0
      drivers/of/unittest-data/overlay_1.dts
  32. 27 0
      drivers/of/unittest-data/overlay_10.dts
  33. 28 0
      drivers/of/unittest-data/overlay_11.dts
  34. 14 0
      drivers/of/unittest-data/overlay_12.dts
  35. 14 0
      drivers/of/unittest-data/overlay_13.dts
  36. 30 0
      drivers/of/unittest-data/overlay_15.dts
  37. 9 0
      drivers/of/unittest-data/overlay_2.dts
  38. 9 0
      drivers/of/unittest-data/overlay_3.dts
  39. 18 0
      drivers/of/unittest-data/overlay_4.dts
  40. 9 0
      drivers/of/unittest-data/overlay_5.dts
  41. 10 0
      drivers/of/unittest-data/overlay_6.dts
  42. 10 0
      drivers/of/unittest-data/overlay_7.dts
  43. 10 0
      drivers/of/unittest-data/overlay_8.dts
  44. 10 0
      drivers/of/unittest-data/overlay_9.dts
  45. 8 15
      drivers/of/unittest-data/overlay_bad_phandle.dts
  46. 9 16
      drivers/of/unittest-data/overlay_bad_symbol.dts
  47. 2 215
      drivers/of/unittest-data/tests-overlay.dtsi
  48. 148 152
      drivers/of/unittest.c
  49. 3 3
      include/linux/of.h

+ 58 - 0
Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt

@@ -0,0 +1,58 @@
+Renesas R-Car LVDS Encoder
+==========================
+
+These DT bindings describe the LVDS encoder embedded in the Renesas R-Car
+Gen2, R-Car Gen3 and RZ/G SoCs.
+
+Required properties:
+
+- compatible : Shall contain one of
+  - "renesas,r8a7743-lvds" for R8A7743 (RZ/G1M) compatible LVDS encoders
+  - "renesas,r8a7790-lvds" for R8A7790 (R-Car H2) compatible LVDS encoders
+  - "renesas,r8a7791-lvds" for R8A7791 (R-Car M2-W) compatible LVDS encoders
+  - "renesas,r8a7793-lvds" for R8A7793 (R-Car M2-N) compatible LVDS encoders
+  - "renesas,r8a7795-lvds" for R8A7795 (R-Car H3) compatible LVDS encoders
+  - "renesas,r8a7796-lvds" for R8A7796 (R-Car M3-W) compatible LVDS encoders
+  - "renesas,r8a77970-lvds" for R8A77970 (R-Car V3M) compatible LVDS encoders
+  - "renesas,r8a77995-lvds" for R8A77995 (R-Car D3) compatible LVDS encoders
+
+- reg: Base address and length for the memory-mapped registers
+- clocks: A phandle + clock-specifier pair for the functional clock
+- resets: A phandle + reset specifier for the module reset
+
+Required nodes:
+
+The LVDS encoder has two video ports. Their connections are modelled using the
+OF graph bindings specified in Documentation/devicetree/bindings/graph.txt.
+
+- Video port 0 corresponds to the parallel RGB input
+- Video port 1 corresponds to the LVDS output
+
+Each port shall have a single endpoint.
+
+
+Example:
+
+	lvds0: lvds@feb90000 {
+		compatible = "renesas,r8a7790-lvds";
+		reg = <0 0xfeb90000 0 0x1c>;
+		clocks = <&cpg CPG_MOD 726>;
+		resets = <&cpg 726>;
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			port@0 {
+				reg = <0>;
+				lvds0_in: endpoint {
+					remote-endpoint = <&du_out_lvds0>;
+				};
+			};
+			port@1 {
+				reg = <1>;
+				lvds0_out: endpoint {
+				};
+			};
+		};
+	};

+ 15 - 20
Documentation/devicetree/bindings/display/renesas,du.txt

@@ -13,13 +13,10 @@ Required Properties:
     - "renesas,du-r8a7794" for R8A7794 (R-Car E2) compatible DU
     - "renesas,du-r8a7795" for R8A7795 (R-Car H3) compatible DU
     - "renesas,du-r8a7796" for R8A7796 (R-Car M3-W) compatible DU
+    - "renesas,du-r8a77970" for R8A77970 (R-Car V3M) compatible DU
+    - "renesas,du-r8a77995" for R8A77995 (R-Car D3) compatible DU
 
-  - reg: A list of base address and length of each memory resource, one for
-    each entry in the reg-names property.
-  - reg-names: Name of the memory resources. The DU requires one memory
-    resource for the DU core (named "du") and one memory resource for each
-    LVDS encoder (named "lvds.x" with "x" being the LVDS controller numerical
-    index).
+  - reg: the memory-mapped I/O registers base address and length
 
   - interrupt-parent: phandle of the parent interrupt controller.
   - interrupts: Interrupt specifiers for the DU interrupts.
@@ -29,14 +26,13 @@ Required Properties:
   - clock-names: Name of the clocks. This property is model-dependent.
     - R8A7779 uses a single functional clock. The clock doesn't need to be
       named.
-    - All other DU instances use one functional clock per channel and one
-      clock per LVDS encoder (if available). The functional clocks must be
-      named "du.x" with "x" being the channel numerical index. The LVDS clocks
-      must be named "lvds.x" with "x" being the LVDS encoder numerical index.
-    - In addition to the functional and encoder clocks, all DU versions also
-      support externally supplied pixel clocks. Those clocks are optional.
-      When supplied they must be named "dclkin.x" with "x" being the input
-      clock numerical index.
+    - All other DU instances use one functional clock per channel The
+      functional clocks must be named "du.x" with "x" being the channel
+      numerical index.
+    - In addition to the functional clocks, all DU versions also support
+      externally supplied pixel clocks. Those clocks are optional. When
+      supplied they must be named "dclkin.x" with "x" being the input clock
+      numerical index.
 
   - vsps: A list of phandle and channel index tuples to the VSPs that handle
     the memory interfaces for the DU channels. The phandle identifies the VSP
@@ -63,15 +59,15 @@ corresponding to each DU output.
  R8A7794 (R-Car E2)   DPAD 0         DPAD 1         -              -
  R8A7795 (R-Car H3)   DPAD 0         HDMI 0         HDMI 1         LVDS 0
  R8A7796 (R-Car M3-W) DPAD 0         HDMI 0         LVDS 0         -
+ R8A77970 (R-Car V3M) DPAD 0         LVDS 0         -              -
+ R8A77995 (R-Car D3)  DPAD 0         LVDS 0         LVDS 1         -
 
 
 Example: R8A7795 (R-Car H3) ES2.0 DU
 
 	du: display@feb00000 {
 		compatible = "renesas,du-r8a7795";
-		reg = <0 0xfeb00000 0 0x80000>,
-		      <0 0xfeb90000 0 0x14>;
-		reg-names = "du", "lvds.0";
+		reg = <0 0xfeb00000 0 0x80000>;
 		interrupts = <GIC_SPI 256 IRQ_TYPE_LEVEL_HIGH>,
 			     <GIC_SPI 268 IRQ_TYPE_LEVEL_HIGH>,
 			     <GIC_SPI 269 IRQ_TYPE_LEVEL_HIGH>,
@@ -79,9 +75,8 @@ Example: R8A7795 (R-Car H3) ES2.0 DU
 		clocks = <&cpg CPG_MOD 724>,
 			 <&cpg CPG_MOD 723>,
 			 <&cpg CPG_MOD 722>,
-			 <&cpg CPG_MOD 721>,
-			 <&cpg CPG_MOD 727>;
-		clock-names = "du.0", "du.1", "du.2", "du.3", "lvds.0";
+			 <&cpg CPG_MOD 721>;
+		clock-names = "du.0", "du.1", "du.2", "du.3";
 		vsps = <&vspd0 0>, <&vspd1 0>, <&vspd2 0>, <&vspd0 1>;
 
 		ports {

+ 2 - 2
Documentation/devicetree/overlay-notes.txt

@@ -87,8 +87,8 @@ Overlay in-kernel API
 
 The API is quite easy to use.
 
-1. Call of_overlay_apply() to create and apply an overlay changeset. The return
-value is an error or a cookie identifying this overlay.
+1. Call of_overlay_fdt_apply() to create and apply an overlay changeset. The
+return value is an error or a cookie identifying this overlay.
 
 2. Call of_overlay_remove() to remove and cleanup the overlay changeset
 previously created via the call to of_overlay_apply(). Removal of an overlay

+ 1 - 0
MAINTAINERS

@@ -4744,6 +4744,7 @@ F:	drivers/gpu/drm/rcar-du/
 F:	drivers/gpu/drm/shmobile/
 F:	include/linux/platform_data/shmob_drm.h
 F:	Documentation/devicetree/bindings/display/bridge/renesas,dw-hdmi.txt
+F:	Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt
 F:	Documentation/devicetree/bindings/display/renesas,du.txt
 
 DRM DRIVERS FOR ROCKCHIP

+ 1 - 1
arch/x86/kernel/devicetree.c

@@ -259,7 +259,7 @@ static void __init dtb_apic_setup(void)
 	dtb_ioapic_setup();
 }
 
-#ifdef CONFIG_OF_FLATTREE
+#ifdef CONFIG_OF_EARLY_FLATTREE
 static void __init x86_flattree_get_config(void)
 {
 	u32 size, map_len;

+ 4 - 2
drivers/gpu/drm/rcar-du/Kconfig

@@ -19,9 +19,11 @@ config DRM_RCAR_DW_HDMI
 	  Enable support for R-Car Gen3 internal HDMI encoder.
 
 config DRM_RCAR_LVDS
-	bool "R-Car DU LVDS Encoder Support"
-	depends on DRM_RCAR_DU
+	tristate "R-Car DU LVDS Encoder Support"
+	depends on DRM && DRM_BRIDGE && OF
 	select DRM_PANEL
+	select OF_FLATTREE
+	select OF_OVERLAY
 	help
 	  Enable support for the R-Car Display Unit embedded LVDS encoders.
 

+ 7 - 3
drivers/gpu/drm/rcar-du/Makefile

@@ -4,12 +4,16 @@ rcar-du-drm-y := rcar_du_crtc.o \
 		 rcar_du_encoder.o \
 		 rcar_du_group.o \
 		 rcar_du_kms.o \
-		 rcar_du_lvdscon.o \
 		 rcar_du_plane.o
 
-rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS)	+= rcar_du_lvdsenc.o
-
+rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS)	+= rcar_du_of.o \
+					   rcar_du_of_lvds_r8a7790.dtb.o \
+					   rcar_du_of_lvds_r8a7791.dtb.o \
+					   rcar_du_of_lvds_r8a7793.dtb.o \
+					   rcar_du_of_lvds_r8a7795.dtb.o \
+					   rcar_du_of_lvds_r8a7796.dtb.o
 rcar-du-drm-$(CONFIG_DRM_RCAR_VSP)	+= rcar_du_vsp.o
 
 obj-$(CONFIG_DRM_RCAR_DU)		+= rcar-du-drm.o
 obj-$(CONFIG_DRM_RCAR_DW_HDMI)		+= rcar_dw_hdmi.o
+obj-$(CONFIG_DRM_RCAR_LVDS)		+= rcar_lvds.o

+ 36 - 6
drivers/gpu/drm/rcar-du/rcar_du_drv.c

@@ -29,6 +29,7 @@
 
 #include "rcar_du_drv.h"
 #include "rcar_du_kms.h"
+#include "rcar_du_of.h"
 #include "rcar_du_regs.h"
 
 /* -----------------------------------------------------------------------------
@@ -74,7 +75,6 @@ static const struct rcar_du_device_info rzg1_du_r8a7745_info = {
 			.port = 1,
 		},
 	},
-	.num_lvds = 0,
 };
 
 static const struct rcar_du_device_info rcar_du_r8a7779_info = {
@@ -95,14 +95,13 @@ static const struct rcar_du_device_info rcar_du_r8a7779_info = {
 			.port = 1,
 		},
 	},
-	.num_lvds = 0,
 };
 
 static const struct rcar_du_device_info rcar_du_r8a7790_info = {
 	.gen = 2,
 	.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
 		  | RCAR_DU_FEATURE_EXT_CTRL_REGS,
-	.quirks = RCAR_DU_QUIRK_ALIGN_128B | RCAR_DU_QUIRK_LVDS_LANES,
+	.quirks = RCAR_DU_QUIRK_ALIGN_128B,
 	.num_crtcs = 3,
 	.routes = {
 		/*
@@ -164,7 +163,6 @@ static const struct rcar_du_device_info rcar_du_r8a7792_info = {
 			.port = 1,
 		},
 	},
-	.num_lvds = 0,
 };
 
 static const struct rcar_du_device_info rcar_du_r8a7794_info = {
@@ -186,7 +184,6 @@ static const struct rcar_du_device_info rcar_du_r8a7794_info = {
 			.port = 1,
 		},
 	},
-	.num_lvds = 0,
 };
 
 static const struct rcar_du_device_info rcar_du_r8a7795_info = {
@@ -249,6 +246,26 @@ static const struct rcar_du_device_info rcar_du_r8a7796_info = {
 	.dpll_ch =  BIT(1),
 };
 
+static const struct rcar_du_device_info rcar_du_r8a77970_info = {
+	.gen = 3,
+	.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
+		  | RCAR_DU_FEATURE_EXT_CTRL_REGS
+		  | RCAR_DU_FEATURE_VSP1_SOURCE,
+	.num_crtcs = 1,
+	.routes = {
+		/* R8A77970 has one RGB output and one LVDS output. */
+		[RCAR_DU_OUTPUT_DPAD0] = {
+			.possible_crtcs = BIT(0),
+			.port = 0,
+		},
+		[RCAR_DU_OUTPUT_LVDS0] = {
+			.possible_crtcs = BIT(0),
+			.port = 1,
+		},
+	},
+	.num_lvds = 1,
+};
+
 static const struct of_device_id rcar_du_of_table[] = {
 	{ .compatible = "renesas,du-r8a7743", .data = &rzg1_du_r8a7743_info },
 	{ .compatible = "renesas,du-r8a7745", .data = &rzg1_du_r8a7745_info },
@@ -260,6 +277,7 @@ static const struct of_device_id rcar_du_of_table[] = {
 	{ .compatible = "renesas,du-r8a7794", .data = &rcar_du_r8a7794_info },
 	{ .compatible = "renesas,du-r8a7795", .data = &rcar_du_r8a7795_info },
 	{ .compatible = "renesas,du-r8a7796", .data = &rcar_du_r8a7796_info },
+	{ .compatible = "renesas,du-r8a77970", .data = &rcar_du_r8a77970_info },
 	{ }
 };
 
@@ -434,7 +452,19 @@ static struct platform_driver rcar_du_platform_driver = {
 	},
 };
 
-module_platform_driver(rcar_du_platform_driver);
+static int __init rcar_du_init(void)
+{
+	rcar_du_of_init(rcar_du_of_table);
+
+	return platform_driver_register(&rcar_du_platform_driver);
+}
+module_init(rcar_du_init);
+
+static void __exit rcar_du_exit(void)
+{
+	platform_driver_unregister(&rcar_du_platform_driver);
+}
+module_exit(rcar_du_exit);
 
 MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
 MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver");

+ 0 - 5
drivers/gpu/drm/rcar-du/rcar_du_drv.h

@@ -26,14 +26,12 @@ struct device;
 struct drm_device;
 struct drm_fbdev_cma;
 struct rcar_du_device;
-struct rcar_du_lvdsenc;
 
 #define RCAR_DU_FEATURE_CRTC_IRQ_CLOCK	(1 << 0)	/* Per-CRTC IRQ and clock */
 #define RCAR_DU_FEATURE_EXT_CTRL_REGS	(1 << 1)	/* Has extended control registers */
 #define RCAR_DU_FEATURE_VSP1_SOURCE	(1 << 2)	/* Has inputs from VSP1 */
 
 #define RCAR_DU_QUIRK_ALIGN_128B	(1 << 0)	/* Align pitches to 128 bytes */
-#define RCAR_DU_QUIRK_LVDS_LANES	(1 << 1)	/* LVDS lanes 1 and 3 inverted */
 
 /*
  * struct rcar_du_output_routing - Output routing specification
@@ -70,7 +68,6 @@ struct rcar_du_device_info {
 
 #define RCAR_DU_MAX_CRTCS		4
 #define RCAR_DU_MAX_GROUPS		DIV_ROUND_UP(RCAR_DU_MAX_CRTCS, 2)
-#define RCAR_DU_MAX_LVDS		2
 #define RCAR_DU_MAX_VSPS		4
 
 struct rcar_du_device {
@@ -96,8 +93,6 @@ struct rcar_du_device {
 
 	unsigned int dpad0_source;
 	unsigned int vspd1_sink;
-
-	struct rcar_du_lvdsenc *lvds[RCAR_DU_MAX_LVDS];
 };
 
 static inline bool rcar_du_has(struct rcar_du_device *rcdu,

+ 15 - 160
drivers/gpu/drm/rcar-du/rcar_du_encoder.c

@@ -21,134 +21,22 @@
 #include "rcar_du_drv.h"
 #include "rcar_du_encoder.h"
 #include "rcar_du_kms.h"
-#include "rcar_du_lvdscon.h"
-#include "rcar_du_lvdsenc.h"
 
 /* -----------------------------------------------------------------------------
  * Encoder
  */
 
-static void rcar_du_encoder_disable(struct drm_encoder *encoder)
-{
-	struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
-
-	if (renc->connector && renc->connector->panel) {
-		drm_panel_disable(renc->connector->panel);
-		drm_panel_unprepare(renc->connector->panel);
-	}
-
-	if (renc->lvds)
-		rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, false);
-}
-
-static void rcar_du_encoder_enable(struct drm_encoder *encoder)
-{
-	struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
-
-	if (renc->lvds)
-		rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, true);
-
-	if (renc->connector && renc->connector->panel) {
-		drm_panel_prepare(renc->connector->panel);
-		drm_panel_enable(renc->connector->panel);
-	}
-}
-
-static int rcar_du_encoder_atomic_check(struct drm_encoder *encoder,
-					struct drm_crtc_state *crtc_state,
-					struct drm_connector_state *conn_state)
-{
-	struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
-	struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
-	const struct drm_display_mode *mode = &crtc_state->mode;
-	struct drm_connector *connector = conn_state->connector;
-	struct drm_device *dev = encoder->dev;
-
-	/*
-	 * Only panel-related encoder types require validation here, everything
-	 * else is handled by the bridge drivers.
-	 */
-	if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) {
-		const struct drm_display_mode *panel_mode;
-
-		if (list_empty(&connector->modes)) {
-			dev_dbg(dev->dev, "encoder: empty modes list\n");
-			return -EINVAL;
-		}
-
-		panel_mode = list_first_entry(&connector->modes,
-					      struct drm_display_mode, head);
-
-		/* We're not allowed to modify the resolution. */
-		if (mode->hdisplay != panel_mode->hdisplay ||
-		    mode->vdisplay != panel_mode->vdisplay)
-			return -EINVAL;
-
-		/*
-		 * The flat panel mode is fixed, just copy it to the adjusted
-		 * mode.
-		 */
-		drm_mode_copy(adjusted_mode, panel_mode);
-	}
-
-	if (renc->lvds)
-		rcar_du_lvdsenc_atomic_check(renc->lvds, adjusted_mode);
-
-	return 0;
-}
-
 static void rcar_du_encoder_mode_set(struct drm_encoder *encoder,
 				     struct drm_crtc_state *crtc_state,
 				     struct drm_connector_state *conn_state)
 {
 	struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
-	struct drm_display_info *info = &conn_state->connector->display_info;
-	enum rcar_lvds_mode mode;
 
 	rcar_du_crtc_route_output(crtc_state->crtc, renc->output);
-
-	if (!renc->lvds) {
-		/*
-		 * The DU driver creates connectors only for the outputs of the
-		 * internal LVDS encoders.
-		 */
-		renc->connector = NULL;
-		return;
-	}
-
-	renc->connector = to_rcar_connector(conn_state->connector);
-
-	if (!info->num_bus_formats || !info->bus_formats) {
-		dev_err(encoder->dev->dev, "no LVDS bus format reported\n");
-		return;
-	}
-
-	switch (info->bus_formats[0]) {
-	case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
-	case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
-		mode = RCAR_LVDS_MODE_JEIDA;
-		break;
-	case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
-		mode = RCAR_LVDS_MODE_VESA;
-		break;
-	default:
-		dev_err(encoder->dev->dev,
-			"unsupported LVDS bus format 0x%04x\n",
-			info->bus_formats[0]);
-		return;
-	}
-
-	if (info->bus_flags & DRM_BUS_FLAG_DATA_LSB_TO_MSB)
-		mode |= RCAR_LVDS_MODE_MIRROR;
-
-	rcar_du_lvdsenc_set_mode(renc->lvds, mode);
 }
 
 static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
 	.atomic_mode_set = rcar_du_encoder_mode_set,
-	.disable = rcar_du_encoder_disable,
-	.enable = rcar_du_encoder_enable,
-	.atomic_check = rcar_du_encoder_atomic_check,
 };
 
 static const struct drm_encoder_funcs encoder_funcs = {
@@ -172,33 +60,14 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
 	renc->output = output;
 	encoder = rcar_encoder_to_drm_encoder(renc);
 
-	switch (output) {
-	case RCAR_DU_OUTPUT_LVDS0:
-		renc->lvds = rcdu->lvds[0];
-		break;
+	dev_dbg(rcdu->dev, "initializing encoder %pOF for output %u\n",
+		enc_node, output);
 
-	case RCAR_DU_OUTPUT_LVDS1:
-		renc->lvds = rcdu->lvds[1];
-		break;
-
-	default:
-		break;
-	}
-
-	if (enc_node) {
-		dev_dbg(rcdu->dev, "initializing encoder %pOF for output %u\n",
-			enc_node, output);
-
-		/* Locate the DRM bridge from the encoder DT node. */
-		bridge = of_drm_find_bridge(enc_node);
-		if (!bridge) {
-			ret = -EPROBE_DEFER;
-			goto done;
-		}
-	} else {
-		dev_dbg(rcdu->dev,
-			"initializing internal encoder for output %u\n",
-			output);
+	/* Locate the DRM bridge from the encoder DT node. */
+	bridge = of_drm_find_bridge(enc_node);
+	if (!bridge) {
+		ret = -EPROBE_DEFER;
+		goto done;
 	}
 
 	ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs,
@@ -208,28 +77,14 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
 
 	drm_encoder_helper_add(encoder, &encoder_helper_funcs);
 
-	if (bridge) {
-		/*
-		 * Attach the bridge to the encoder. The bridge will create the
-		 * connector.
-		 */
-		ret = drm_bridge_attach(encoder, bridge, NULL);
-		if (ret) {
-			drm_encoder_cleanup(encoder);
-			return ret;
-		}
-	} else {
-		/* There's no bridge, create the connector manually. */
-		switch (output) {
-		case RCAR_DU_OUTPUT_LVDS0:
-		case RCAR_DU_OUTPUT_LVDS1:
-			ret = rcar_du_lvds_connector_init(rcdu, renc, con_node);
-			break;
-
-		default:
-			ret = -EINVAL;
-			break;
-		}
+	/*
+	 * Attach the bridge to the encoder. The bridge will create the
+	 * connector.
+	 */
+	ret = drm_bridge_attach(encoder, bridge, NULL);
+	if (ret) {
+		drm_encoder_cleanup(encoder);
+		return ret;
 	}
 
 done:

+ 0 - 12
drivers/gpu/drm/rcar-du/rcar_du_encoder.h

@@ -19,13 +19,10 @@
 
 struct drm_panel;
 struct rcar_du_device;
-struct rcar_du_lvdsenc;
 
 struct rcar_du_encoder {
 	struct drm_encoder base;
 	enum rcar_du_output output;
-	struct rcar_du_connector *connector;
-	struct rcar_du_lvdsenc *lvds;
 };
 
 #define to_rcar_encoder(e) \
@@ -33,15 +30,6 @@ struct rcar_du_encoder {
 
 #define rcar_encoder_to_drm_encoder(e)	(&(e)->base)
 
-struct rcar_du_connector {
-	struct drm_connector connector;
-	struct rcar_du_encoder *encoder;
-	struct drm_panel *panel;
-};
-
-#define to_rcar_connector(c) \
-	container_of(c, struct rcar_du_connector, connector)
-
 int rcar_du_encoder_init(struct rcar_du_device *rcdu,
 			 enum rcar_du_output output,
 			 struct device_node *enc_node,

+ 4 - 10
drivers/gpu/drm/rcar-du/rcar_du_kms.c

@@ -27,7 +27,6 @@
 #include "rcar_du_drv.h"
 #include "rcar_du_encoder.h"
 #include "rcar_du_kms.h"
-#include "rcar_du_lvdsenc.h"
 #include "rcar_du_regs.h"
 #include "rcar_du_vsp.h"
 
@@ -341,11 +340,10 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
 	of_node_put(entity_ep_node);
 
 	if (!encoder) {
-		/*
-		 * If no encoder has been found the entity must be the
-		 * connector.
-		 */
-		connector = entity;
+		dev_warn(rcdu->dev,
+			 "no encoder found for endpoint %pOF, skipping\n",
+			 ep->local_node);
+		return -ENODEV;
 	}
 
 	ret = rcar_du_encoder_init(rcdu, output, encoder, connector);
@@ -595,10 +593,6 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
 	}
 
 	/* Initialize the encoders. */
-	ret = rcar_du_lvdsenc_init(rcdu);
-	if (ret < 0)
-		return ret;
-
 	ret = rcar_du_encoders_init(rcdu);
 	if (ret < 0)
 		return ret;

+ 0 - 93
drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c

@@ -1,93 +0,0 @@
-/*
- * rcar_du_lvdscon.c  --  R-Car Display Unit LVDS Connector
- *
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
- *
- * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- */
-
-#include <drm/drmP.h>
-#include <drm/drm_atomic_helper.h>
-#include <drm/drm_crtc.h>
-#include <drm/drm_crtc_helper.h>
-#include <drm/drm_panel.h>
-
-#include <video/display_timing.h>
-#include <video/of_display_timing.h>
-#include <video/videomode.h>
-
-#include "rcar_du_drv.h"
-#include "rcar_du_encoder.h"
-#include "rcar_du_kms.h"
-#include "rcar_du_lvdscon.h"
-
-static int rcar_du_lvds_connector_get_modes(struct drm_connector *connector)
-{
-	struct rcar_du_connector *rcon = to_rcar_connector(connector);
-
-	return drm_panel_get_modes(rcon->panel);
-}
-
-static const struct drm_connector_helper_funcs connector_helper_funcs = {
-	.get_modes = rcar_du_lvds_connector_get_modes,
-};
-
-static void rcar_du_lvds_connector_destroy(struct drm_connector *connector)
-{
-	struct rcar_du_connector *rcon = to_rcar_connector(connector);
-
-	drm_panel_detach(rcon->panel);
-	drm_connector_cleanup(connector);
-}
-
-static const struct drm_connector_funcs connector_funcs = {
-	.reset = drm_atomic_helper_connector_reset,
-	.fill_modes = drm_helper_probe_single_connector_modes,
-	.destroy = rcar_du_lvds_connector_destroy,
-	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
-	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
-};
-
-int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
-				struct rcar_du_encoder *renc,
-				const struct device_node *np)
-{
-	struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc);
-	struct rcar_du_connector *rcon;
-	struct drm_connector *connector;
-	int ret;
-
-	rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL);
-	if (rcon == NULL)
-		return -ENOMEM;
-
-	connector = &rcon->connector;
-
-	rcon->panel = of_drm_find_panel(np);
-	if (!rcon->panel)
-		return -EPROBE_DEFER;
-
-	ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs,
-				 DRM_MODE_CONNECTOR_LVDS);
-	if (ret < 0)
-		return ret;
-
-	drm_connector_helper_add(connector, &connector_helper_funcs);
-
-	ret = drm_mode_connector_attach_encoder(connector, encoder);
-	if (ret < 0)
-		return ret;
-
-	ret = drm_panel_attach(rcon->panel, connector);
-	if (ret < 0)
-		return ret;
-
-	rcon->encoder = renc;
-
-	return 0;
-}

+ 0 - 24
drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h

@@ -1,24 +0,0 @@
-/*
- * rcar_du_lvdscon.h  --  R-Car Display Unit LVDS Connector
- *
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
- *
- * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- */
-
-#ifndef __RCAR_DU_LVDSCON_H__
-#define __RCAR_DU_LVDSCON_H__
-
-struct rcar_du_device;
-struct rcar_du_encoder;
-
-int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
-				struct rcar_du_encoder *renc,
-				const struct device_node *np);
-
-#endif /* __RCAR_DU_LVDSCON_H__ */

+ 0 - 238
drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c

@@ -1,238 +0,0 @@
-/*
- * rcar_du_lvdsenc.c  --  R-Car Display Unit LVDS Encoder
- *
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
- *
- * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- */
-
-#include <linux/clk.h>
-#include <linux/delay.h>
-#include <linux/io.h>
-#include <linux/platform_device.h>
-#include <linux/slab.h>
-
-#include "rcar_du_drv.h"
-#include "rcar_du_encoder.h"
-#include "rcar_du_lvdsenc.h"
-#include "rcar_lvds_regs.h"
-
-struct rcar_du_lvdsenc {
-	struct rcar_du_device *dev;
-
-	unsigned int index;
-	void __iomem *mmio;
-	struct clk *clock;
-	bool enabled;
-
-	enum rcar_lvds_input input;
-	enum rcar_lvds_mode mode;
-};
-
-static void rcar_lvds_write(struct rcar_du_lvdsenc *lvds, u32 reg, u32 data)
-{
-	iowrite32(data, lvds->mmio + reg);
-}
-
-static u32 rcar_lvds_lvdpllcr_gen2(unsigned int freq)
-{
-	if (freq < 39000)
-		return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
-	else if (freq < 61000)
-		return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
-	else if (freq < 121000)
-		return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
-	else
-		return LVDPLLCR_PLLDLYCNT_150M;
-}
-
-static u32 rcar_lvds_lvdpllcr_gen3(unsigned int freq)
-{
-	if (freq < 42000)
-		return LVDPLLCR_PLLDIVCNT_42M;
-	else if (freq < 85000)
-		return LVDPLLCR_PLLDIVCNT_85M;
-	else if (freq < 128000)
-		return LVDPLLCR_PLLDIVCNT_128M;
-	else
-		return LVDPLLCR_PLLDIVCNT_148M;
-}
-
-static int rcar_du_lvdsenc_start(struct rcar_du_lvdsenc *lvds,
-				 struct rcar_du_crtc *rcrtc)
-{
-	const struct drm_display_mode *mode = &rcrtc->crtc.mode;
-	u32 lvdpllcr;
-	u32 lvdhcr;
-	u32 lvdcr0;
-	int ret;
-
-	if (lvds->enabled)
-		return 0;
-
-	ret = clk_prepare_enable(lvds->clock);
-	if (ret < 0)
-		return ret;
-
-	/*
-	 * Hardcode the channels and control signals routing for now.
-	 *
-	 * HSYNC -> CTRL0
-	 * VSYNC -> CTRL1
-	 * DISP  -> CTRL2
-	 * 0     -> CTRL3
-	 */
-	rcar_lvds_write(lvds, LVDCTRCR, LVDCTRCR_CTR3SEL_ZERO |
-			LVDCTRCR_CTR2SEL_DISP | LVDCTRCR_CTR1SEL_VSYNC |
-			LVDCTRCR_CTR0SEL_HSYNC);
-
-	if (rcar_du_needs(lvds->dev, RCAR_DU_QUIRK_LVDS_LANES))
-		lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 3)
-		       | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 1);
-	else
-		lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 1)
-		       | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 3);
-
-	rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
-
-	/* PLL clock configuration. */
-	if (lvds->dev->info->gen < 3)
-		lvdpllcr = rcar_lvds_lvdpllcr_gen2(mode->clock);
-	else
-		lvdpllcr = rcar_lvds_lvdpllcr_gen3(mode->clock);
-	rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
-
-	/* Set the LVDS mode and select the input. */
-	lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
-	if (rcrtc->index == 2)
-		lvdcr0 |= LVDCR0_DUSEL;
-	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
-
-	/* Turn all the channels on. */
-	rcar_lvds_write(lvds, LVDCR1,
-			LVDCR1_CHSTBY(3) | LVDCR1_CHSTBY(2) |
-			LVDCR1_CHSTBY(1) | LVDCR1_CHSTBY(0) | LVDCR1_CLKSTBY);
-
-	if (lvds->dev->info->gen < 3) {
-		/* Enable LVDS operation and turn the bias circuitry on. */
-		lvdcr0 |= LVDCR0_BEN | LVDCR0_LVEN;
-		rcar_lvds_write(lvds, LVDCR0, lvdcr0);
-	}
-
-	/* Turn the PLL on. */
-	lvdcr0 |= LVDCR0_PLLON;
-	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
-
-	if (lvds->dev->info->gen > 2) {
-		/* Set LVDS normal mode. */
-		lvdcr0 |= LVDCR0_PWD;
-		rcar_lvds_write(lvds, LVDCR0, lvdcr0);
-	}
-
-	/* Wait for the startup delay. */
-	usleep_range(100, 150);
-
-	/* Turn the output on. */
-	lvdcr0 |= LVDCR0_LVRES;
-	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
-
-	lvds->enabled = true;
-
-	return 0;
-}
-
-static void rcar_du_lvdsenc_stop(struct rcar_du_lvdsenc *lvds)
-{
-	if (!lvds->enabled)
-		return;
-
-	rcar_lvds_write(lvds, LVDCR0, 0);
-	rcar_lvds_write(lvds, LVDCR1, 0);
-
-	clk_disable_unprepare(lvds->clock);
-
-	lvds->enabled = false;
-}
-
-int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, struct drm_crtc *crtc,
-			   bool enable)
-{
-	if (!enable) {
-		rcar_du_lvdsenc_stop(lvds);
-		return 0;
-	} else if (crtc) {
-		struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
-		return rcar_du_lvdsenc_start(lvds, rcrtc);
-	} else
-		return -EINVAL;
-}
-
-void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
-				  struct drm_display_mode *mode)
-{
-	/*
-	 * The internal LVDS encoder has a restricted clock frequency operating
-	 * range (31MHz to 148.5MHz). Clamp the clock accordingly.
-	 */
-	mode->clock = clamp(mode->clock, 31000, 148500);
-}
-
-void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
-			      enum rcar_lvds_mode mode)
-{
-	lvds->mode = mode;
-}
-
-static int rcar_du_lvdsenc_get_resources(struct rcar_du_lvdsenc *lvds,
-					 struct platform_device *pdev)
-{
-	struct resource *mem;
-	char name[7];
-
-	sprintf(name, "lvds.%u", lvds->index);
-
-	mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
-	lvds->mmio = devm_ioremap_resource(&pdev->dev, mem);
-	if (IS_ERR(lvds->mmio))
-		return PTR_ERR(lvds->mmio);
-
-	lvds->clock = devm_clk_get(&pdev->dev, name);
-	if (IS_ERR(lvds->clock)) {
-		dev_err(&pdev->dev, "failed to get clock for %s\n", name);
-		return PTR_ERR(lvds->clock);
-	}
-
-	return 0;
-}
-
-int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
-{
-	struct platform_device *pdev = to_platform_device(rcdu->dev);
-	struct rcar_du_lvdsenc *lvds;
-	unsigned int i;
-	int ret;
-
-	for (i = 0; i < rcdu->info->num_lvds; ++i) {
-		lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
-		if (lvds == NULL)
-			return -ENOMEM;
-
-		lvds->dev = rcdu;
-		lvds->index = i;
-		lvds->input = i ? RCAR_LVDS_INPUT_DU1 : RCAR_LVDS_INPUT_DU0;
-		lvds->enabled = false;
-
-		ret = rcar_du_lvdsenc_get_resources(lvds, pdev);
-		if (ret < 0)
-			return ret;
-
-		rcdu->lvds[i] = lvds;
-	}
-
-	return 0;
-}

+ 0 - 64
drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h

@@ -1,64 +0,0 @@
-/*
- * rcar_du_lvdsenc.h  --  R-Car Display Unit LVDS Encoder
- *
- * Copyright (C) 2013-2014 Renesas Electronics Corporation
- *
- * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- */
-
-#ifndef __RCAR_DU_LVDSENC_H__
-#define __RCAR_DU_LVDSENC_H__
-
-#include <linux/io.h>
-#include <linux/module.h>
-
-struct rcar_drm_crtc;
-struct rcar_du_lvdsenc;
-
-enum rcar_lvds_input {
-	RCAR_LVDS_INPUT_DU0,
-	RCAR_LVDS_INPUT_DU1,
-	RCAR_LVDS_INPUT_DU2,
-};
-
-/* Keep in sync with the LVDCR0.LVMD hardware register values. */
-enum rcar_lvds_mode {
-	RCAR_LVDS_MODE_JEIDA = 0,
-	RCAR_LVDS_MODE_MIRROR = 1,
-	RCAR_LVDS_MODE_VESA = 4,
-};
-
-#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
-int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu);
-void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
-			      enum rcar_lvds_mode mode);
-int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds,
-			   struct drm_crtc *crtc, bool enable);
-void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
-				  struct drm_display_mode *mode);
-#else
-static inline int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
-{
-	return 0;
-}
-static inline void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
-					    enum rcar_lvds_mode mode)
-{
-}
-static inline int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds,
-					 struct drm_crtc *crtc, bool enable)
-{
-	return 0;
-}
-static inline void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
-						struct drm_display_mode *mode)
-{
-}
-#endif
-
-#endif /* __RCAR_DU_LVDSENC_H__ */

+ 322 - 0
drivers/gpu/drm/rcar-du/rcar_du_of.c

@@ -0,0 +1,322 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rcar_du_of.c - Legacy DT bindings compatibility
+ *
+ * Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * Based on work from Jyri Sarha <jsarha@ti.com>
+ * Copyright (C) 2015 Texas Instruments
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_fdt.h>
+#include <linux/of_graph.h>
+#include <linux/slab.h>
+
+#include "rcar_du_crtc.h"
+#include "rcar_du_drv.h"
+
+/* -----------------------------------------------------------------------------
+ * Generic Overlay Handling
+ */
+
+struct rcar_du_of_overlay {
+	const char *compatible;
+	void *begin;
+	void *end;
+};
+
+#define RCAR_DU_OF_DTB(type, soc)					\
+	extern char __dtb_rcar_du_of_##type##_##soc##_begin[];		\
+	extern char __dtb_rcar_du_of_##type##_##soc##_end[]
+
+#define RCAR_DU_OF_OVERLAY(type, soc)					\
+	{								\
+		.compatible = "renesas,du-" #soc,			\
+		.begin = __dtb_rcar_du_of_##type##_##soc##_begin,	\
+		.end = __dtb_rcar_du_of_##type##_##soc##_end,		\
+	}
+
+static int __init rcar_du_of_apply_overlay(const struct rcar_du_of_overlay *dtbs,
+					   const char *compatible)
+{
+	const struct rcar_du_of_overlay *dtb = NULL;
+	unsigned int i;
+	int ovcs_id;
+
+	for (i = 0; dtbs[i].compatible; ++i) {
+		if (!strcmp(dtbs[i].compatible, compatible)) {
+			dtb = &dtbs[i];
+			break;
+		}
+	}
+
+	if (!dtb)
+		return -ENODEV;
+
+	ovcs_id = 0;
+	return of_overlay_fdt_apply(dtb->begin, dtb->end - dtb->begin,
+				    &ovcs_id);
+}
+
+static int __init rcar_du_of_add_property(struct of_changeset *ocs,
+					  struct device_node *np,
+					  const char *name, const void *value,
+					  int length)
+{
+	struct property *prop;
+	int ret = -ENOMEM;
+
+	prop = kzalloc(sizeof(*prop), GFP_KERNEL);
+	if (!prop)
+		return -ENOMEM;
+
+	prop->name = kstrdup(name, GFP_KERNEL);
+	if (!prop->name)
+		goto out_err;
+
+	prop->value = kmemdup(value, length, GFP_KERNEL);
+	if (!prop->value)
+		goto out_err;
+
+	of_property_set_flag(prop, OF_DYNAMIC);
+
+	prop->length = length;
+
+	ret = of_changeset_add_property(ocs, np, prop);
+	if (!ret)
+		return 0;
+
+out_err:
+	kfree(prop->value);
+	kfree(prop->name);
+	kfree(prop);
+	return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * LVDS Overlays
+ */
+
+RCAR_DU_OF_DTB(lvds, r8a7790);
+RCAR_DU_OF_DTB(lvds, r8a7791);
+RCAR_DU_OF_DTB(lvds, r8a7793);
+RCAR_DU_OF_DTB(lvds, r8a7795);
+RCAR_DU_OF_DTB(lvds, r8a7796);
+
+static const struct rcar_du_of_overlay rcar_du_lvds_overlays[] __initconst = {
+	RCAR_DU_OF_OVERLAY(lvds, r8a7790),
+	RCAR_DU_OF_OVERLAY(lvds, r8a7791),
+	RCAR_DU_OF_OVERLAY(lvds, r8a7793),
+	RCAR_DU_OF_OVERLAY(lvds, r8a7795),
+	RCAR_DU_OF_OVERLAY(lvds, r8a7796),
+	{ /* Sentinel */ },
+};
+
+static struct of_changeset rcar_du_lvds_changeset;
+
+static void __init rcar_du_of_lvds_patch_one(struct device_node *lvds,
+					     const struct of_phandle_args *clk,
+					     struct device_node *local,
+					     struct device_node *remote)
+{
+	unsigned int psize;
+	unsigned int i;
+	__be32 value[4];
+	int ret;
+
+	/*
+	 * Set the LVDS clocks property. This can't be performed by the overlay
+	 * as the structure of the clock specifier has changed over time, and we
+	 * don't know at compile time which binding version the system we will
+	 * run on uses.
+	 */
+	if (clk->args_count >= ARRAY_SIZE(value) - 1)
+		return;
+
+	of_changeset_init(&rcar_du_lvds_changeset);
+
+	value[0] = cpu_to_be32(clk->np->phandle);
+	for (i = 0; i < clk->args_count; ++i)
+		value[i + 1] = cpu_to_be32(clk->args[i]);
+
+	psize = (clk->args_count + 1) * 4;
+	ret = rcar_du_of_add_property(&rcar_du_lvds_changeset, lvds,
+				      "clocks", value, psize);
+	if (ret < 0)
+		goto done;
+
+	/*
+	 * Insert the node in the OF graph: patch the LVDS ports remote-endpoint
+	 * properties to point to the endpoints of the sibling nodes in the
+	 * graph. This can't be performed by the overlay: on the input side the
+	 * overlay would contain a phandle for the DU LVDS output port that
+	 * would clash with the system DT, and on the output side the connection
+	 * is board-specific.
+	 */
+	value[0] = cpu_to_be32(local->phandle);
+	value[1] = cpu_to_be32(remote->phandle);
+
+	for (i = 0; i < 2; ++i) {
+		struct device_node *endpoint;
+
+		endpoint = of_graph_get_endpoint_by_regs(lvds, i, 0);
+		if (!endpoint) {
+			ret = -EINVAL;
+			goto done;
+		}
+
+		ret = rcar_du_of_add_property(&rcar_du_lvds_changeset,
+					      endpoint, "remote-endpoint",
+					      &value[i], sizeof(value[i]));
+		of_node_put(endpoint);
+		if (ret < 0)
+			goto done;
+	}
+
+	ret = of_changeset_apply(&rcar_du_lvds_changeset);
+
+done:
+	if (ret < 0)
+		of_changeset_destroy(&rcar_du_lvds_changeset);
+}
+
+struct lvds_of_data {
+	struct resource res;
+	struct of_phandle_args clkspec;
+	struct device_node *local;
+	struct device_node *remote;
+};
+
+static void __init rcar_du_of_lvds_patch(const struct of_device_id *of_ids)
+{
+	const struct rcar_du_device_info *info;
+	const struct of_device_id *match;
+	struct lvds_of_data lvds_data[2] = { };
+	struct device_node *lvds_node;
+	struct device_node *soc_node;
+	struct device_node *du_node;
+	char compatible[22];
+	const char *soc_name;
+	unsigned int i;
+	int ret;
+
+	/* Get the DU node and exit if not present or disabled. */
+	du_node = of_find_matching_node_and_match(NULL, of_ids, &match);
+	if (!du_node || !of_device_is_available(du_node)) {
+		of_node_put(du_node);
+		return;
+	}
+
+	info = match->data;
+	soc_node = of_get_parent(du_node);
+
+	if (WARN_ON(info->num_lvds > ARRAY_SIZE(lvds_data)))
+		goto done;
+
+	/*
+	 * Skip if the LVDS nodes already exists.
+	 *
+	 * The nodes are searched based on the compatible string, which we
+	 * construct from the SoC name found in the DU compatible string. As a
+	 * match has been found we know the compatible string matches the
+	 * expected format and can thus skip some of the string manipulation
+	 * normal safety checks.
+	 */
+	soc_name = strchr(match->compatible, '-') + 1;
+	sprintf(compatible, "renesas,%s-lvds", soc_name);
+	lvds_node = of_find_compatible_node(NULL, NULL, compatible);
+	if (lvds_node) {
+		of_node_put(lvds_node);
+		return;
+	}
+
+	/*
+	 * Parse the DU node and store the register specifier, the clock
+	 * specifier and the local and remote endpoint of the LVDS link for
+	 * later use.
+	 */
+	for (i = 0; i < info->num_lvds; ++i) {
+		struct lvds_of_data *lvds = &lvds_data[i];
+		unsigned int port;
+		char name[7];
+		int index;
+
+		sprintf(name, "lvds.%u", i);
+		index = of_property_match_string(du_node, "clock-names", name);
+		if (index < 0)
+			continue;
+
+		ret = of_parse_phandle_with_args(du_node, "clocks",
+						 "#clock-cells", index,
+						 &lvds->clkspec);
+		if (ret < 0)
+			continue;
+
+		port = info->routes[RCAR_DU_OUTPUT_LVDS0 + i].port;
+
+		lvds->local = of_graph_get_endpoint_by_regs(du_node, port, 0);
+		if (!lvds->local)
+			continue;
+
+		lvds->remote = of_graph_get_remote_endpoint(lvds->local);
+		if (!lvds->remote)
+			continue;
+
+		index = of_property_match_string(du_node, "reg-names", name);
+		if (index < 0)
+			continue;
+
+		of_address_to_resource(du_node, index, &lvds->res);
+	}
+
+	/* Parse and apply the overlay. This will resolve phandles. */
+	ret = rcar_du_of_apply_overlay(rcar_du_lvds_overlays,
+				       match->compatible);
+	if (ret < 0)
+		goto done;
+
+	/* Patch the newly created LVDS encoder nodes. */
+	for_each_child_of_node(soc_node, lvds_node) {
+		struct resource res;
+
+		if (!of_device_is_compatible(lvds_node, compatible))
+			continue;
+
+		/* Locate the lvds_data entry based on the resource start. */
+		ret = of_address_to_resource(lvds_node, 0, &res);
+		if (ret < 0)
+			continue;
+
+		for (i = 0; i < ARRAY_SIZE(lvds_data); ++i) {
+			if (lvds_data[i].res.start == res.start)
+				break;
+		}
+
+		if (i == ARRAY_SIZE(lvds_data))
+			continue;
+
+		/* Patch the LVDS encoder. */
+		rcar_du_of_lvds_patch_one(lvds_node, &lvds_data[i].clkspec,
+					  lvds_data[i].local,
+					  lvds_data[i].remote);
+	}
+
+done:
+	for (i = 0; i < info->num_lvds; ++i) {
+		of_node_put(lvds_data[i].clkspec.np);
+		of_node_put(lvds_data[i].local);
+		of_node_put(lvds_data[i].remote);
+	}
+
+	of_node_put(soc_node);
+	of_node_put(du_node);
+}
+
+void __init rcar_du_of_init(const struct of_device_id *of_ids)
+{
+	rcar_du_of_lvds_patch(of_ids);
+}

+ 20 - 0
drivers/gpu/drm/rcar-du/rcar_du_of.h

@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * rcar_du_of.h - Legacy DT bindings compatibility
+ *
+ * Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ */
+#ifndef __RCAR_DU_OF_H__
+#define __RCAR_DU_OF_H__
+
+#include <linux/init.h>
+
+struct of_device_id;
+
+#ifdef CONFIG_DRM_RCAR_LVDS
+void __init rcar_du_of_init(const struct of_device_id *of_ids);
+#else
+static inline void rcar_du_of_init(const struct of_device_id *of_ids) { }
+#endif /* CONFIG_DRM_RCAR_LVDS */
+
+#endif /* __RCAR_DU_OF_H__ */

+ 76 - 0
drivers/gpu/drm/rcar-du/rcar_du_of_lvds_r8a7790.dts

@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rcar_du_of_lvds_r8a7790.dts - Legacy LVDS DT bindings conversion for R8A7790
+ *
+ * Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ */
+
+/dts-v1/;
+/plugin/;
+/ {
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			#address-cells = <2>;
+			#size-cells = <2>;
+
+			lvds@feb90000 {
+				compatible = "renesas,r8a7790-lvds";
+				reg = <0 0xfeb90000 0 0x1c>;
+
+				ports {
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					port@0 {
+						reg = <0>;
+						lvds0_input: endpoint {
+						};
+					};
+					port@1 {
+						reg = <1>;
+						lvds0_out: endpoint {
+						};
+					};
+				};
+			};
+
+			lvds@feb94000 {
+				compatible = "renesas,r8a7790-lvds";
+				reg = <0 0xfeb94000 0 0x1c>;
+
+				ports {
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					port@0 {
+						reg = <0>;
+						lvds1_input: endpoint {
+						};
+					};
+					port@1 {
+						reg = <1>;
+						lvds1_out: endpoint {
+						};
+					};
+				};
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/display@feb00000/ports";
+		__overlay__ {
+			port@1 {
+				endpoint {
+					remote-endpoint = <&lvds0_input>;
+				};
+			};
+			port@2 {
+				endpoint {
+					remote-endpoint = <&lvds1_input>;
+				};
+			};
+		};
+	};
+};

+ 50 - 0
drivers/gpu/drm/rcar-du/rcar_du_of_lvds_r8a7791.dts

@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rcar_du_of_lvds_r8a7791.dts - Legacy LVDS DT bindings conversion for R8A7791
+ *
+ * Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ */
+
+/dts-v1/;
+/plugin/;
+/ {
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			#address-cells = <2>;
+			#size-cells = <2>;
+
+			lvds@feb90000 {
+				compatible = "renesas,r8a7791-lvds";
+				reg = <0 0xfeb90000 0 0x1c>;
+
+				ports {
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					port@0 {
+						reg = <0>;
+						lvds0_input: endpoint {
+						};
+					};
+					port@1 {
+						reg = <1>;
+						lvds0_out: endpoint {
+						};
+					};
+				};
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/display@feb00000/ports";
+		__overlay__ {
+			port@1 {
+				endpoint {
+					remote-endpoint = <&lvds0_input>;
+				};
+			};
+		};
+	};
+};

+ 50 - 0
drivers/gpu/drm/rcar-du/rcar_du_of_lvds_r8a7793.dts

@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rcar_du_of_lvds_r8a7793.dts - Legacy LVDS DT bindings conversion for R8A7793
+ *
+ * Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ */
+
+/dts-v1/;
+/plugin/;
+/ {
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			#address-cells = <2>;
+			#size-cells = <2>;
+
+			lvds@feb90000 {
+				compatible = "renesas,r8a7793-lvds";
+				reg = <0 0xfeb90000 0 0x1c>;
+
+				ports {
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					port@0 {
+						reg = <0>;
+						lvds0_input: endpoint {
+						};
+					};
+					port@1 {
+						reg = <1>;
+						lvds0_out: endpoint {
+						};
+					};
+				};
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/display@feb00000/ports";
+		__overlay__ {
+			port@1 {
+				endpoint {
+					remote-endpoint = <&lvds0_input>;
+				};
+			};
+		};
+	};
+};

+ 50 - 0
drivers/gpu/drm/rcar-du/rcar_du_of_lvds_r8a7795.dts

@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rcar_du_of_lvds_r8a7795.dts - Legacy LVDS DT bindings conversion for R8A7795
+ *
+ * Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ */
+
+/dts-v1/;
+/plugin/;
+/ {
+	fragment@0 {
+		target-path = "/soc";
+		__overlay__ {
+			#address-cells = <2>;
+			#size-cells = <2>;
+
+			lvds@feb90000 {
+				compatible = "renesas,r8a7795-lvds";
+				reg = <0 0xfeb90000 0 0x14>;
+
+				ports {
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					port@0 {
+						reg = <0>;
+						lvds0_input: endpoint {
+						};
+					};
+					port@1 {
+						reg = <1>;
+						lvds0_out: endpoint {
+						};
+					};
+				};
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/soc/display@feb00000/ports";
+		__overlay__ {
+			port@3 {
+				endpoint {
+					remote-endpoint = <&lvds0_input>;
+				};
+			};
+		};
+	};
+};

+ 50 - 0
drivers/gpu/drm/rcar-du/rcar_du_of_lvds_r8a7796.dts

@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rcar_du_of_lvds_r8a7796.dts - Legacy LVDS DT bindings conversion for R8A7796
+ *
+ * Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ */
+
+/dts-v1/;
+/plugin/;
+/ {
+	fragment@0 {
+		target-path = "/soc";
+		__overlay__ {
+			#address-cells = <2>;
+			#size-cells = <2>;
+
+			lvds@feb90000 {
+				compatible = "renesas,r8a7796-lvds";
+				reg = <0 0xfeb90000 0 0x14>;
+
+				ports {
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					port@0 {
+						reg = <0>;
+						lvds0_input: endpoint {
+						};
+					};
+					port@1 {
+						reg = <1>;
+						lvds0_out: endpoint {
+						};
+					};
+				};
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/soc/display@feb00000/ports";
+		__overlay__ {
+			port@3 {
+				endpoint {
+					remote-endpoint = <&lvds0_input>;
+				};
+			};
+		};
+	};
+};

+ 540 - 0
drivers/gpu/drm/rcar-du/rcar_lvds.c

@@ -0,0 +1,540 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rcar_lvds.c  --  R-Car LVDS Encoder
+ *
+ * Copyright (C) 2013-2018 Renesas Electronics Corporation
+ *
+ * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_panel.h>
+
+#include "rcar_lvds_regs.h"
+
+/* Keep in sync with the LVDCR0.LVMD hardware register values. */
+enum rcar_lvds_mode {
+	RCAR_LVDS_MODE_JEIDA = 0,
+	RCAR_LVDS_MODE_MIRROR = 1,
+	RCAR_LVDS_MODE_VESA = 4,
+};
+
+#define RCAR_LVDS_QUIRK_LANES	(1 << 0)	/* LVDS lanes 1 and 3 inverted */
+#define RCAR_LVDS_QUIRK_GEN2_PLLCR (1 << 1)	/* LVDPLLCR has gen2 layout */
+#define RCAR_LVDS_QUIRK_GEN3_LVEN (1 << 2)	/* LVEN bit needs to be set */
+						/* on R8A77970/R8A7799x */
+
+struct rcar_lvds_device_info {
+	unsigned int gen;
+	unsigned int quirks;
+};
+
+struct rcar_lvds {
+	struct device *dev;
+	const struct rcar_lvds_device_info *info;
+
+	struct drm_bridge bridge;
+
+	struct drm_bridge *next_bridge;
+	struct drm_connector connector;
+	struct drm_panel *panel;
+
+	void __iomem *mmio;
+	struct clk *clock;
+	bool enabled;
+
+	struct drm_display_mode display_mode;
+	enum rcar_lvds_mode mode;
+};
+
+#define bridge_to_rcar_lvds(bridge) \
+	container_of(bridge, struct rcar_lvds, bridge)
+
+#define connector_to_rcar_lvds(connector) \
+	container_of(connector, struct rcar_lvds, connector)
+
+static void rcar_lvds_write(struct rcar_lvds *lvds, u32 reg, u32 data)
+{
+	iowrite32(data, lvds->mmio + reg);
+}
+
+/* -----------------------------------------------------------------------------
+ * Connector & Panel
+ */
+
+static int rcar_lvds_connector_get_modes(struct drm_connector *connector)
+{
+	struct rcar_lvds *lvds = connector_to_rcar_lvds(connector);
+
+	return drm_panel_get_modes(lvds->panel);
+}
+
+static int rcar_lvds_connector_atomic_check(struct drm_connector *connector,
+					    struct drm_connector_state *state)
+{
+	struct rcar_lvds *lvds = connector_to_rcar_lvds(connector);
+	const struct drm_display_mode *panel_mode;
+	struct drm_crtc_state *crtc_state;
+
+	if (list_empty(&connector->modes)) {
+		dev_dbg(lvds->dev, "connector: empty modes list\n");
+		return -EINVAL;
+	}
+
+	panel_mode = list_first_entry(&connector->modes,
+				      struct drm_display_mode, head);
+
+	/* We're not allowed to modify the resolution. */
+	crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
+	if (IS_ERR(crtc_state))
+		return PTR_ERR(crtc_state);
+
+	if (crtc_state->mode.hdisplay != panel_mode->hdisplay ||
+	    crtc_state->mode.vdisplay != panel_mode->vdisplay)
+		return -EINVAL;
+
+	/* The flat panel mode is fixed, just copy it to the adjusted mode. */
+	drm_mode_copy(&crtc_state->adjusted_mode, panel_mode);
+
+	return 0;
+}
+
+static const struct drm_connector_helper_funcs rcar_lvds_conn_helper_funcs = {
+	.get_modes = rcar_lvds_connector_get_modes,
+	.atomic_check = rcar_lvds_connector_atomic_check,
+};
+
+static const struct drm_connector_funcs rcar_lvds_conn_funcs = {
+	.reset = drm_atomic_helper_connector_reset,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = drm_connector_cleanup,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+/* -----------------------------------------------------------------------------
+ * Bridge
+ */
+
+static u32 rcar_lvds_lvdpllcr_gen2(unsigned int freq)
+{
+	if (freq < 39000)
+		return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
+	else if (freq < 61000)
+		return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
+	else if (freq < 121000)
+		return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
+	else
+		return LVDPLLCR_PLLDLYCNT_150M;
+}
+
+static u32 rcar_lvds_lvdpllcr_gen3(unsigned int freq)
+{
+	if (freq < 42000)
+		return LVDPLLCR_PLLDIVCNT_42M;
+	else if (freq < 85000)
+		return LVDPLLCR_PLLDIVCNT_85M;
+	else if (freq < 128000)
+		return LVDPLLCR_PLLDIVCNT_128M;
+	else
+		return LVDPLLCR_PLLDIVCNT_148M;
+}
+
+static void rcar_lvds_enable(struct drm_bridge *bridge)
+{
+	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+	const struct drm_display_mode *mode = &lvds->display_mode;
+	/*
+	 * FIXME: We should really retrieve the CRTC through the state, but how
+	 * do we get a state pointer?
+	 */
+	struct drm_crtc *crtc = lvds->bridge.encoder->crtc;
+	u32 lvdpllcr;
+	u32 lvdhcr;
+	u32 lvdcr0;
+	int ret;
+
+	WARN_ON(lvds->enabled);
+
+	ret = clk_prepare_enable(lvds->clock);
+	if (ret < 0)
+		return;
+
+	/*
+	 * Hardcode the channels and control signals routing for now.
+	 *
+	 * HSYNC -> CTRL0
+	 * VSYNC -> CTRL1
+	 * DISP  -> CTRL2
+	 * 0     -> CTRL3
+	 */
+	rcar_lvds_write(lvds, LVDCTRCR, LVDCTRCR_CTR3SEL_ZERO |
+			LVDCTRCR_CTR2SEL_DISP | LVDCTRCR_CTR1SEL_VSYNC |
+			LVDCTRCR_CTR0SEL_HSYNC);
+
+	if (lvds->info->quirks & RCAR_LVDS_QUIRK_LANES)
+		lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 3)
+		       | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 1);
+	else
+		lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 1)
+		       | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 3);
+
+	rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
+
+	/* PLL clock configuration. */
+	if (lvds->info->quirks & RCAR_LVDS_QUIRK_GEN2_PLLCR)
+		lvdpllcr = rcar_lvds_lvdpllcr_gen2(mode->clock);
+	else
+		lvdpllcr = rcar_lvds_lvdpllcr_gen3(mode->clock);
+	rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
+
+	/* Set the LVDS mode and select the input. */
+	lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
+	if (drm_crtc_index(crtc) == 2)
+		lvdcr0 |= LVDCR0_DUSEL;
+	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+
+	/* Turn all the channels on. */
+	rcar_lvds_write(lvds, LVDCR1,
+			LVDCR1_CHSTBY(3) | LVDCR1_CHSTBY(2) |
+			LVDCR1_CHSTBY(1) | LVDCR1_CHSTBY(0) | LVDCR1_CLKSTBY);
+
+	if (lvds->info->gen < 3) {
+		/* Enable LVDS operation and turn the bias circuitry on. */
+		lvdcr0 |= LVDCR0_BEN | LVDCR0_LVEN;
+		rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+	}
+
+	/* Turn the PLL on. */
+	lvdcr0 |= LVDCR0_PLLON;
+	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+
+	if (lvds->info->gen > 2) {
+		/* Set LVDS normal mode. */
+		lvdcr0 |= LVDCR0_PWD;
+		rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+	}
+
+	if (lvds->info->quirks & RCAR_LVDS_QUIRK_GEN3_LVEN) {
+		/* Turn on the LVDS PHY. */
+		lvdcr0 |= LVDCR0_LVEN;
+		rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+	}
+
+	/* Wait for the startup delay. */
+	usleep_range(100, 150);
+
+	/* Turn the output on. */
+	lvdcr0 |= LVDCR0_LVRES;
+	rcar_lvds_write(lvds, LVDCR0, lvdcr0);
+
+	if (lvds->panel) {
+		drm_panel_prepare(lvds->panel);
+		drm_panel_enable(lvds->panel);
+	}
+
+	lvds->enabled = true;
+}
+
+static void rcar_lvds_disable(struct drm_bridge *bridge)
+{
+	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+
+	WARN_ON(!lvds->enabled);
+
+	if (lvds->panel) {
+		drm_panel_disable(lvds->panel);
+		drm_panel_unprepare(lvds->panel);
+	}
+
+	rcar_lvds_write(lvds, LVDCR0, 0);
+	rcar_lvds_write(lvds, LVDCR1, 0);
+
+	clk_disable_unprepare(lvds->clock);
+
+	lvds->enabled = false;
+}
+
+static bool rcar_lvds_mode_fixup(struct drm_bridge *bridge,
+				 const struct drm_display_mode *mode,
+				 struct drm_display_mode *adjusted_mode)
+{
+	/*
+	 * The internal LVDS encoder has a restricted clock frequency operating
+	 * range (31MHz to 148.5MHz). Clamp the clock accordingly.
+	 */
+	adjusted_mode->clock = clamp(adjusted_mode->clock, 31000, 148500);
+
+	return true;
+}
+
+static void rcar_lvds_get_lvds_mode(struct rcar_lvds *lvds)
+{
+	struct drm_display_info *info = &lvds->connector.display_info;
+	enum rcar_lvds_mode mode;
+
+	/*
+	 * There is no API yet to retrieve LVDS mode from a bridge, only panels
+	 * are supported.
+	 */
+	if (!lvds->panel)
+		return;
+
+	if (!info->num_bus_formats || !info->bus_formats) {
+		dev_err(lvds->dev, "no LVDS bus format reported\n");
+		return;
+	}
+
+	switch (info->bus_formats[0]) {
+	case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+	case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+		mode = RCAR_LVDS_MODE_JEIDA;
+		break;
+	case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+		mode = RCAR_LVDS_MODE_VESA;
+		break;
+	default:
+		dev_err(lvds->dev, "unsupported LVDS bus format 0x%04x\n",
+			info->bus_formats[0]);
+		return;
+	}
+
+	if (info->bus_flags & DRM_BUS_FLAG_DATA_LSB_TO_MSB)
+		mode |= RCAR_LVDS_MODE_MIRROR;
+
+	lvds->mode = mode;
+}
+
+static void rcar_lvds_mode_set(struct drm_bridge *bridge,
+			       struct drm_display_mode *mode,
+			       struct drm_display_mode *adjusted_mode)
+{
+	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+
+	WARN_ON(lvds->enabled);
+
+	lvds->display_mode = *adjusted_mode;
+
+	rcar_lvds_get_lvds_mode(lvds);
+}
+
+static int rcar_lvds_attach(struct drm_bridge *bridge)
+{
+	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+	struct drm_connector *connector = &lvds->connector;
+	struct drm_encoder *encoder = bridge->encoder;
+	int ret;
+
+	/* If we have a next bridge just attach it. */
+	if (lvds->next_bridge)
+		return drm_bridge_attach(bridge->encoder, lvds->next_bridge,
+					 bridge);
+
+	/* Otherwise we have a panel, create a connector. */
+	ret = drm_connector_init(bridge->dev, connector, &rcar_lvds_conn_funcs,
+				 DRM_MODE_CONNECTOR_LVDS);
+	if (ret < 0)
+		return ret;
+
+	drm_connector_helper_add(connector, &rcar_lvds_conn_helper_funcs);
+
+	ret = drm_mode_connector_attach_encoder(connector, encoder);
+	if (ret < 0)
+		return ret;
+
+	return drm_panel_attach(lvds->panel, connector);
+}
+
+static void rcar_lvds_detach(struct drm_bridge *bridge)
+{
+	struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
+
+	if (lvds->panel)
+		drm_panel_detach(lvds->panel);
+}
+
+static const struct drm_bridge_funcs rcar_lvds_bridge_ops = {
+	.attach = rcar_lvds_attach,
+	.detach = rcar_lvds_detach,
+	.enable = rcar_lvds_enable,
+	.disable = rcar_lvds_disable,
+	.mode_fixup = rcar_lvds_mode_fixup,
+	.mode_set = rcar_lvds_mode_set,
+};
+
+/* -----------------------------------------------------------------------------
+ * Probe & Remove
+ */
+
+static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
+{
+	struct device_node *local_output = NULL;
+	struct device_node *remote_input = NULL;
+	struct device_node *remote = NULL;
+	struct device_node *node;
+	bool is_bridge = false;
+	int ret = 0;
+
+	local_output = of_graph_get_endpoint_by_regs(lvds->dev->of_node, 1, 0);
+	if (!local_output) {
+		dev_dbg(lvds->dev, "unconnected port@1\n");
+		return -ENODEV;
+	}
+
+	/*
+	 * Locate the connected entity and infer its type from the number of
+	 * endpoints.
+	 */
+	remote = of_graph_get_remote_port_parent(local_output);
+	if (!remote) {
+		dev_dbg(lvds->dev, "unconnected endpoint %pOF\n", local_output);
+		ret = -ENODEV;
+		goto done;
+	}
+
+	if (!of_device_is_available(remote)) {
+		dev_dbg(lvds->dev, "connected entity %pOF is disabled\n",
+			remote);
+		ret = -ENODEV;
+		goto done;
+	}
+
+	remote_input = of_graph_get_remote_endpoint(local_output);
+
+	for_each_endpoint_of_node(remote, node) {
+		if (node != remote_input) {
+			/*
+			 * We've found one endpoint other than the input, this
+			 * must be a bridge.
+			 */
+			is_bridge = true;
+			of_node_put(node);
+			break;
+		}
+	}
+
+	if (is_bridge) {
+		lvds->next_bridge = of_drm_find_bridge(remote);
+		if (!lvds->next_bridge)
+			ret = -EPROBE_DEFER;
+	} else {
+		lvds->panel = of_drm_find_panel(remote);
+		if (!lvds->panel)
+			ret = -EPROBE_DEFER;
+	}
+
+done:
+	of_node_put(local_output);
+	of_node_put(remote_input);
+	of_node_put(remote);
+
+	return ret;
+}
+
+static int rcar_lvds_probe(struct platform_device *pdev)
+{
+	struct rcar_lvds *lvds;
+	struct resource *mem;
+	int ret;
+
+	lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
+	if (lvds == NULL)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, lvds);
+
+	lvds->dev = &pdev->dev;
+	lvds->info = of_device_get_match_data(&pdev->dev);
+	lvds->enabled = false;
+
+	ret = rcar_lvds_parse_dt(lvds);
+	if (ret < 0)
+		return ret;
+
+	lvds->bridge.driver_private = lvds;
+	lvds->bridge.funcs = &rcar_lvds_bridge_ops;
+	lvds->bridge.of_node = pdev->dev.of_node;
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	lvds->mmio = devm_ioremap_resource(&pdev->dev, mem);
+	if (IS_ERR(lvds->mmio))
+		return PTR_ERR(lvds->mmio);
+
+	lvds->clock = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(lvds->clock)) {
+		dev_err(&pdev->dev, "failed to get clock\n");
+		return PTR_ERR(lvds->clock);
+	}
+
+	drm_bridge_add(&lvds->bridge);
+
+	return 0;
+}
+
+static int rcar_lvds_remove(struct platform_device *pdev)
+{
+	struct rcar_lvds *lvds = platform_get_drvdata(pdev);
+
+	drm_bridge_remove(&lvds->bridge);
+
+	return 0;
+}
+
+static const struct rcar_lvds_device_info rcar_lvds_gen2_info = {
+	.gen = 2,
+	.quirks = RCAR_LVDS_QUIRK_GEN2_PLLCR,
+};
+
+static const struct rcar_lvds_device_info rcar_lvds_r8a7790_info = {
+	.gen = 2,
+	.quirks = RCAR_LVDS_QUIRK_GEN2_PLLCR | RCAR_LVDS_QUIRK_LANES,
+};
+
+static const struct rcar_lvds_device_info rcar_lvds_gen3_info = {
+	.gen = 3,
+};
+
+static const struct rcar_lvds_device_info rcar_lvds_r8a77970_info = {
+	.gen = 3,
+	.quirks = RCAR_LVDS_QUIRK_GEN2_PLLCR | RCAR_LVDS_QUIRK_GEN3_LVEN,
+};
+
+static const struct of_device_id rcar_lvds_of_table[] = {
+	{ .compatible = "renesas,r8a7743-lvds", .data = &rcar_lvds_gen2_info },
+	{ .compatible = "renesas,r8a7790-lvds", .data = &rcar_lvds_r8a7790_info },
+	{ .compatible = "renesas,r8a7791-lvds", .data = &rcar_lvds_gen2_info },
+	{ .compatible = "renesas,r8a7793-lvds", .data = &rcar_lvds_gen2_info },
+	{ .compatible = "renesas,r8a7795-lvds", .data = &rcar_lvds_gen3_info },
+	{ .compatible = "renesas,r8a7796-lvds", .data = &rcar_lvds_gen3_info },
+	{ .compatible = "renesas,r8a77970-lvds", .data = &rcar_lvds_r8a77970_info },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(of, rcar_lvds_of_table);
+
+static struct platform_driver rcar_lvds_platform_driver = {
+	.probe		= rcar_lvds_probe,
+	.remove		= rcar_lvds_remove,
+	.driver		= {
+		.name	= "rcar-lvds",
+		.of_match_table = rcar_lvds_of_table,
+	},
+};
+
+module_platform_driver(rcar_lvds_platform_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_DESCRIPTION("Renesas R-Car LVDS Encoder Driver");
+MODULE_LICENSE("GPL");

+ 1 - 0
drivers/of/Kconfig

@@ -92,6 +92,7 @@ config OF_RESOLVE
 config OF_OVERLAY
 	bool "Device Tree overlays"
 	select OF_DYNAMIC
+	select OF_FLATTREE
 	select OF_RESOLVE
 	help
 	  Overlays are a method to dynamically modify part of the kernel's

+ 119 - 15
drivers/of/overlay.c

@@ -12,10 +12,12 @@
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/of_device.h>
+#include <linux/of_fdt.h>
 #include <linux/string.h>
 #include <linux/ctype.h>
 #include <linux/errno.h>
 #include <linux/slab.h>
+#include <linux/libfdt.h>
 #include <linux/err.h>
 #include <linux/idr.h>
 
@@ -33,7 +35,9 @@ struct fragment {
 
 /**
  * struct overlay_changeset
+ * @id:			changeset identifier
  * @ovcs_list:		list on which we are located
+ * @fdt:		FDT that was unflattened to create @overlay_tree
  * @overlay_tree:	expanded device tree that contains the fragment nodes
  * @count:		count of fragment structures
  * @fragments:		fragment nodes in the overlay expanded device tree
@@ -43,6 +47,7 @@ struct fragment {
 struct overlay_changeset {
 	int id;
 	struct list_head ovcs_list;
+	const void *fdt;
 	struct device_node *overlay_tree;
 	int count;
 	struct fragment *fragments;
@@ -483,27 +488,38 @@ static int build_changeset(struct overlay_changeset *ovcs)
  */
 static struct device_node *find_target_node(struct device_node *info_node)
 {
+	struct device_node *node;
 	const char *path;
 	u32 val;
 	int ret;
 
 	ret = of_property_read_u32(info_node, "target", &val);
-	if (!ret)
-		return of_find_node_by_phandle(val);
+	if (!ret) {
+		node = of_find_node_by_phandle(val);
+		if (!node)
+			pr_err("find target, node: %pOF, phandle 0x%x not found\n",
+			       info_node, val);
+		return node;
+	}
 
 	ret = of_property_read_string(info_node, "target-path", &path);
-	if (!ret)
-		return of_find_node_by_path(path);
+	if (!ret) {
+		node =  of_find_node_by_path(path);
+		if (!node)
+			pr_err("find target, node: %pOF, path '%s' not found\n",
+			       info_node, path);
+		return node;
+	}
 
-	pr_err("Failed to find target for node %p (%s)\n",
-		info_node, info_node->name);
+	pr_err("find target, node: %pOF, no target property\n", info_node);
 
 	return NULL;
 }
 
 /**
  * init_overlay_changeset() - initialize overlay changeset from overlay tree
- * @ovcs	Overlay changeset to build
+ * @ovcs:	Overlay changeset to build
+ * @fdt:	the FDT that was unflattened to create @tree
  * @tree:	Contains all the overlay fragments and overlay fixup nodes
  *
  * Initialize @ovcs.  Populate @ovcs->fragments with node information from
@@ -514,7 +530,7 @@ static struct device_node *find_target_node(struct device_node *info_node)
  * detected in @tree, or -ENOSPC if idr_alloc() error.
  */
 static int init_overlay_changeset(struct overlay_changeset *ovcs,
-		struct device_node *tree)
+		const void *fdt, struct device_node *tree)
 {
 	struct device_node *node, *overlay_node;
 	struct fragment *fragment;
@@ -535,6 +551,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
 		pr_debug("%s() tree is not root\n", __func__);
 
 	ovcs->overlay_tree = tree;
+	ovcs->fdt = fdt;
 
 	INIT_LIST_HEAD(&ovcs->ovcs_list);
 
@@ -606,6 +623,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
 	}
 
 	if (!cnt) {
+		pr_err("no fragments or symbols in overlay\n");
 		ret = -EINVAL;
 		goto err_free_fragments;
 	}
@@ -642,11 +660,24 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs)
 	}
 	kfree(ovcs->fragments);
 
+	/*
+	 * TODO
+	 *
+	 * would like to: kfree(ovcs->overlay_tree);
+	 * but can not since drivers may have pointers into this data
+	 *
+	 * would like to: kfree(ovcs->fdt);
+	 * but can not since drivers may have pointers into this data
+	 */
+
 	kfree(ovcs);
 }
 
-/**
+/*
+ * internal documentation
+ *
  * of_overlay_apply() - Create and apply an overlay changeset
+ * @fdt:	the FDT that was unflattened to create @tree
  * @tree:	Expanded overlay device tree
  * @ovcs_id:	Pointer to overlay changeset id
  *
@@ -685,21 +716,29 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs)
  * id is returned to *ovcs_id.
  */
 
-int of_overlay_apply(struct device_node *tree, int *ovcs_id)
+static int of_overlay_apply(const void *fdt, struct device_node *tree,
+		int *ovcs_id)
 {
 	struct overlay_changeset *ovcs;
 	int ret = 0, ret_revert, ret_tmp;
 
-	*ovcs_id = 0;
+	/*
+	 * As of this point, fdt and tree belong to the overlay changeset.
+	 * overlay changeset code is responsible for freeing them.
+	 */
 
 	if (devicetree_corrupt()) {
 		pr_err("devicetree state suspect, refuse to apply overlay\n");
+		kfree(fdt);
+		kfree(tree);
 		ret = -EBUSY;
 		goto out;
 	}
 
 	ovcs = kzalloc(sizeof(*ovcs), GFP_KERNEL);
 	if (!ovcs) {
+		kfree(fdt);
+		kfree(tree);
 		ret = -ENOMEM;
 		goto out;
 	}
@@ -709,12 +748,17 @@ int of_overlay_apply(struct device_node *tree, int *ovcs_id)
 
 	ret = of_resolve_phandles(tree);
 	if (ret)
-		goto err_free_overlay_changeset;
+		goto err_free_tree;
 
-	ret = init_overlay_changeset(ovcs, tree);
+	ret = init_overlay_changeset(ovcs, fdt, tree);
 	if (ret)
-		goto err_free_overlay_changeset;
+		goto err_free_tree;
 
+	/*
+	 * after overlay_notify(), ovcs->overlay_tree related pointers may have
+	 * leaked to drivers, so can not kfree() tree, aka ovcs->overlay_tree;
+	 * and can not free fdt, aka ovcs->fdt
+	 */
 	ret = overlay_notify(ovcs, OF_OVERLAY_PRE_APPLY);
 	if (ret) {
 		pr_err("overlay changeset pre-apply notify error %d\n", ret);
@@ -754,6 +798,10 @@ int of_overlay_apply(struct device_node *tree, int *ovcs_id)
 
 	goto out_unlock;
 
+err_free_tree:
+	kfree(fdt);
+	kfree(tree);
+
 err_free_overlay_changeset:
 	free_overlay_changeset(ovcs);
 
@@ -766,7 +814,63 @@ out:
 
 	return ret;
 }
-EXPORT_SYMBOL_GPL(of_overlay_apply);
+
+int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size,
+			 int *ovcs_id)
+{
+	const void *new_fdt;
+	int ret;
+	u32 size;
+	struct device_node *overlay_root;
+
+	*ovcs_id = 0;
+	ret = 0;
+
+	if (overlay_fdt_size < sizeof(struct fdt_header) ||
+	    fdt_check_header(overlay_fdt)) {
+		pr_err("Invalid overlay_fdt header\n");
+		return -EINVAL;
+	}
+
+	size = fdt_totalsize(overlay_fdt);
+	if (overlay_fdt_size < size)
+		return -EINVAL;
+
+	/*
+	 * Must create permanent copy of FDT because of_fdt_unflatten_tree()
+	 * will create pointers to the passed in FDT in the unflattened tree.
+	 */
+	new_fdt = kmemdup(overlay_fdt, size, GFP_KERNEL);
+	if (!new_fdt)
+		return -ENOMEM;
+
+	of_fdt_unflatten_tree(new_fdt, NULL, &overlay_root);
+	if (!overlay_root) {
+		pr_err("unable to unflatten overlay_fdt\n");
+		ret = -EINVAL;
+		goto out_free_new_fdt;
+	}
+
+	ret = of_overlay_apply(new_fdt, overlay_root, ovcs_id);
+	if (ret < 0) {
+		/*
+		 * new_fdt and overlay_root now belong to the overlay
+		 * changeset.
+		 * overlay changeset code is responsible for freeing them.
+		 */
+		goto out;
+	}
+
+	return 0;
+
+
+out_free_new_fdt:
+	kfree(new_fdt);
+
+out:
+	return ret;
+}
+EXPORT_SYMBOL_GPL(of_overlay_fdt_apply);
 
 /*
  * Find @np in @tree.

+ 0 - 6
drivers/of/resolver.c

@@ -269,17 +269,11 @@ int of_resolve_phandles(struct device_node *overlay)
 		goto out;
 	}
 
-#if 0
-	Temporarily disable check so that old style overlay unittests
-	do not fail when of_resolve_phandles() is moved into
-	of_overlay_apply().
-
 	if (!of_node_check_flag(overlay, OF_DETACHED)) {
 		pr_err("overlay not detached\n");
 		err = -EINVAL;
 		goto out;
 	}
-#endif
 
 	phandle_delta = live_tree_max_phandle() + 1;
 	adjust_overlay_phandles(overlay, phandle_delta);

+ 23 - 5
drivers/of/unittest-data/Makefile

@@ -1,8 +1,22 @@
 # SPDX-License-Identifier: GPL-2.0
-DTC_FLAGS_testcases := -Wno-interrupts_property
 obj-y += testcases.dtb.o
 
 obj-$(CONFIG_OF_OVERLAY) += overlay.dtb.o \
+			    overlay_0.dtb.o \
+			    overlay_1.dtb.o \
+			    overlay_2.dtb.o \
+			    overlay_3.dtb.o \
+			    overlay_4.dtb.o \
+			    overlay_5.dtb.o \
+			    overlay_6.dtb.o \
+			    overlay_7.dtb.o \
+			    overlay_8.dtb.o \
+			    overlay_9.dtb.o \
+			    overlay_10.dtb.o \
+			    overlay_11.dtb.o \
+			    overlay_12.dtb.o \
+			    overlay_13.dtb.o \
+			    overlay_15.dtb.o \
 			    overlay_bad_phandle.dtb.o \
 			    overlay_bad_symbol.dtb.o \
 			    overlay_base.dtb.o
@@ -10,10 +24,14 @@ obj-$(CONFIG_OF_OVERLAY) += overlay.dtb.o \
 targets += $(foreach suffix, dtb dtb.S, $(patsubst %.dtb.o,%.$(suffix),$(obj-y)))
 
 # enable creation of __symbols__ node
-DTC_FLAGS_overlay := -@
-DTC_FLAGS_overlay_bad_phandle := -@
-DTC_FLAGS_overlay_bad_symbol := -@
-DTC_FLAGS_overlay_base := -@
+DTC_FLAGS_overlay += -@
+DTC_FLAGS_overlay_bad_phandle += -@
+DTC_FLAGS_overlay_bad_symbol += -@
+DTC_FLAGS_overlay_base += -@
+DTC_FLAGS_testcases += -@
+
+# suppress warnings about intentional errors
+DTC_FLAGS_testcases += -Wno-interrupts_property
 
 .PRECIOUS: \
 	$(obj)/%.dtb.S \

+ 44 - 57
drivers/of/unittest-data/overlay.dts

@@ -2,76 +2,63 @@
 /dts-v1/;
 /plugin/;
 
-/ {
+&electric_1 {
 
-	fragment@0 {
-		target = <&electric_1>;
+	status = "okay";
 
-		__overlay__ {
-			status = "okay";
-
-			hvac_2: hvac-large-1 {
-				compatible = "ot,hvac-large";
-				heat-range = < 40 75 >;
-				cool-range = < 65 80 >;
-			};
-		};
+	hvac_2: hvac-large-1 {
+		compatible = "ot,hvac-large";
+		heat-range = < 40 75 >;
+		cool-range = < 65 80 >;
 	};
+};
 
-	fragment@1 {
-		target = <&rides_1>;
-
-		__overlay__ {
-			#address-cells = <1>;
-			#size-cells = <1>;
-			status = "okay";
-
-			ride@100 {
-				#address-cells = <1>;
-				#size-cells = <1>;
-
-				track@30 {
-					incline-up = < 48 32 16 >;
-				};
+&rides_1 {
 
-				track@40 {
-					incline-up = < 47 31 15 >;
-				};
-			};
+	#address-cells = <1>;
+	#size-cells = <1>;
+	status = "okay";
 
-			ride_200: ride@200 {
-				#address-cells = <1>;
-				#size-cells = <1>;
-				compatible = "ot,ferris-wheel";
-				reg = < 0x00000200 0x100 >;
-				hvac-provider = < &hvac_2 >;
-				hvac-thermostat = < 27 32 > ;
-				hvac-zones = < 12 5 >;
-				hvac-zone-names = "operator", "snack-bar";
-				spin-controller = < &spin_ctrl_1 3 >;
-				spin-rph = < 30 >;
-				gondolas = < 16 >;
-				gondola-capacity = < 6 >;
+	ride@100 {
+		#address-cells = <1>;
+		#size-cells = <1>;
 
-				ride_200_left: track@10 {
-					reg = < 0x00000010 0x10 >;
-				};
+		track@30 {
+			incline-up = < 48 32 16 >;
+		};
 
-				ride_200_right: track@20 {
-					reg = < 0x00000020 0x10 >;
-				};
-			};
+		track@40 {
+			incline-up = < 47 31 15 >;
 		};
 	};
 
-	fragment@2 {
-		target = <&lights_2>;
+	ride_200: ride@200 {
+		#address-cells = <1>;
+		#size-cells = <1>;
+		compatible = "ot,ferris-wheel";
+		reg = < 0x00000200 0x100 >;
+		hvac-provider = < &hvac_2 >;
+		hvac-thermostat = < 27 32 > ;
+		hvac-zones = < 12 5 >;
+		hvac-zone-names = "operator", "snack-bar";
+		spin-controller = < &spin_ctrl_1 3 >;
+		spin-rph = < 30 >;
+		gondolas = < 16 >;
+		gondola-capacity = < 6 >;
+
+		ride_200_left: track@10 {
+			reg = < 0x00000010 0x10 >;
+		};
 
-		__overlay__ {
-			status = "okay";
-			color = "purple", "white", "red", "green";
-			rate = < 3 256 >;
+		ride_200_right: track@20 {
+			reg = < 0x00000020 0x10 >;
 		};
 	};
+};
+
+&lights_2 {
 
+	status = "okay";
+	color = "purple", "white", "red", "green";
+	rate = < 3 256 >;
 };

+ 14 - 0
drivers/of/unittest-data/overlay_0.dts

@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+/ {
+	/* overlay_0 - enable using absolute target path */
+
+	fragment@0 {
+		target-path = "/testcase-data/overlay-node/test-bus/test-unittest0";
+		__overlay__ {
+			status = "okay";
+		};
+	};
+};

+ 14 - 0
drivers/of/unittest-data/overlay_1.dts

@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+/ {
+	/* overlay_1 - disable using absolute target path */
+
+	fragment@0 {
+		target-path = "/testcase-data/overlay-node/test-bus/test-unittest1";
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+};

+ 27 - 0
drivers/of/unittest-data/overlay_10.dts

@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+/* overlay_10 */
+/* overlays 8, 9, 10, 11 application and removal in bad sequence */
+
+&unittest_test_bus {
+	/* suppress DTC warning */
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	test-unittest10 {
+		compatible = "unittest";
+		status = "okay";
+		reg = <10>;
+
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		test-unittest101 {
+			compatible = "unittest";
+			status = "okay";
+			reg = <1>;
+		};
+	};
+};

+ 28 - 0
drivers/of/unittest-data/overlay_11.dts

@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+/* overlay_11 */
+/* overlays 8, 9, 10, 11 application and removal in bad sequence */
+
+&unittest_test_bus {
+	/* suppress DTC warning */
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	test-unittest11 {
+		compatible = "unittest";
+		status = "okay";
+		reg = <11>;
+
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		test-unittest111 {
+			compatible = "unittest";
+			status = "okay";
+			reg = <1>;
+		};
+
+	};
+};

+ 14 - 0
drivers/of/unittest-data/overlay_12.dts

@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+/ {
+	/* overlay_12 - enable using absolute target path (i2c) */
+
+	fragment@0 {
+		target-path = "/testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest12";
+		__overlay__ {
+			status = "okay";
+		};
+	};
+};

+ 14 - 0
drivers/of/unittest-data/overlay_13.dts

@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+/ {
+	/* overlay_13 - disable using absolute target path (i2c) */
+
+	fragment@0 {
+		target-path = "/testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest13";
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+};

+ 30 - 0
drivers/of/unittest-data/overlay_15.dts

@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+/* overlay_15 - mux overlay */
+
+&unittest_i2c_test_bus {
+	#address-cells = <1>;
+	#size-cells = <0>;
+	test-unittest15 {
+		reg = <11>;
+		compatible = "unittest-i2c-mux";
+		status = "okay";
+
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		i2c@0 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0>;
+
+			test-mux-dev {
+				reg = <32>;
+				compatible = "unittest-i2c-dev";
+				status = "okay";
+			};
+		};
+	};
+};

+ 9 - 0
drivers/of/unittest-data/overlay_2.dts

@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+/* overlay_2 -  enable using label */
+
+&unittest2 {
+	status = "okay";
+};

+ 9 - 0
drivers/of/unittest-data/overlay_3.dts

@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+/* overlay_3 - disable using label */
+
+&unittest3 {
+	status = "disabled";
+};

+ 18 - 0
drivers/of/unittest-data/overlay_4.dts

@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+/* overlay_4 - test insertion of a full node */
+
+&unittest_test_bus {
+
+	/* suppress DTC warning */
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	test-unittest4 {
+		compatible = "unittest";
+		status = "okay";
+		reg = <4>;
+	};
+};

+ 9 - 0
drivers/of/unittest-data/overlay_5.dts

@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+/* overlay_5 - test overlay apply revert */
+
+&unittest5 {
+	status = "okay";
+};

+ 10 - 0
drivers/of/unittest-data/overlay_6.dts

@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+/* overlay_6 */
+/* overlays 6, 7 application and removal in sequence */
+
+&unittest6 {
+	status = "okay";
+};

+ 10 - 0
drivers/of/unittest-data/overlay_7.dts

@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+/* overlay_7 */
+/* overlays 6, 7 application and removal in sequence */
+
+&unittest7 {
+	status = "okay";
+};

+ 10 - 0
drivers/of/unittest-data/overlay_8.dts

@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+/* overlay_8 */
+/* overlays 8, 9, 10, 11 application and removal in bad sequence */
+
+&unittest8 {
+	status = "okay";
+};

+ 10 - 0
drivers/of/unittest-data/overlay_9.dts

@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+/* overlay_9 */
+/* overlays 8, 9, 10, 11 application and removal in bad sequence */
+
+&unittest8 {
+	property-foo = "bar";
+};

+ 8 - 15
drivers/of/unittest-data/overlay_bad_phandle.dts

@@ -2,20 +2,13 @@
 /dts-v1/;
 /plugin/;
 
-/ {
-
-	fragment@0 {
-		target = <&electric_1>;
-
-		__overlay__ {
-
-			// This label should cause an error when the overlay
-			// is applied.  There is already a phandle value
-			// in the base tree for motor-1.
-			spin_ctrl_1_conflict: motor-1 {
-				accelerate = < 3 >;
-				decelerate = < 5 >;
-			};
-		};
+&electric_1 {
+
+	// This label should cause an error when the overlay
+	// is applied.  There is already a phandle value
+	// in the base tree for motor-1.
+	spin_ctrl_1_conflict: motor-1 {
+		accelerate = < 3 >;
+		decelerate = < 5 >;
 	};
 };

+ 9 - 16
drivers/of/unittest-data/overlay_bad_symbol.dts

@@ -2,22 +2,15 @@
 /dts-v1/;
 /plugin/;
 
-/ {
+&electric_1 {
 
-	fragment@0 {
-		target = <&electric_1>;
-
-		__overlay__ {
-
-			// This label should cause an error when the overlay
-			// is applied.  There is already a symbol hvac_1
-			// in the base tree
-			hvac_1: hvac-medium-2 {
-				compatible = "ot,hvac-medium";
-				heat-range = < 50 75 >;
-				cool-range = < 60 80 >;
-			};
-
-		};
+	// This label should cause an error when the overlay
+	// is applied.  There is already a symbol hvac_1
+	// in the base tree
+	hvac_1: hvac-medium-2 {
+		compatible = "ot,hvac-medium";
+		heat-range = < 50 75 >;
+		cool-range = < 60 80 >;
 	};
+
 };

+ 2 - 215
drivers/of/unittest-data/tests-overlay.dtsi

@@ -5,7 +5,7 @@
 		overlay-node {
 
 			/* test bus */
-			unittestbus: test-bus {
+			unittest_test_bus: test-bus {
 				compatible = "simple-bus";
 				#address-cells = <1>;
 				#size-cells = <0>;
@@ -70,7 +70,7 @@
 					reg = <8>;
 				};
 
-				i2c-test-bus {
+				unittest_i2c_test_bus: i2c-test-bus {
 					compatible = "unittest-i2c-bus";
 					status = "okay";
 					reg = <50>;
@@ -113,218 +113,5 @@
 				};
 			};
 		};
-
-		/* test enable using absolute target path */
-		overlay0 {
-			fragment@0 {
-				target-path = "/testcase-data/overlay-node/test-bus/test-unittest0";
-				__overlay__ {
-					status = "okay";
-				};
-			};
-		};
-
-		/* test disable using absolute target path */
-		overlay1 {
-			fragment@0 {
-				target-path = "/testcase-data/overlay-node/test-bus/test-unittest1";
-				__overlay__ {
-					status = "disabled";
-				};
-			};
-		};
-
-		/* test enable using label */
-		overlay2 {
-			fragment@0 {
-				target = <&unittest2>;
-				__overlay__ {
-					status = "okay";
-				};
-			};
-		};
-
-		/* test disable using label */
-		overlay3 {
-			fragment@0 {
-				target = <&unittest3>;
-				__overlay__ {
-					status = "disabled";
-				};
-			};
-		};
-
-		/* test insertion of a full node */
-		overlay4 {
-			fragment@0 {
-				target = <&unittestbus>;
-				__overlay__ {
-
-					/* suppress DTC warning */
-					#address-cells = <1>;
-					#size-cells = <0>;
-
-					test-unittest4 {
-						compatible = "unittest";
-						status = "okay";
-						reg = <4>;
-					};
-				};
-			};
-		};
-
-		/* test overlay apply revert */
-		overlay5 {
-			fragment@0 {
-				target-path = "/testcase-data/overlay-node/test-bus/test-unittest5";
-				__overlay__ {
-					status = "okay";
-				};
-			};
-		};
-
-		/* test overlays application and removal in sequence */
-		overlay6 {
-			fragment@0 {
-				target-path = "/testcase-data/overlay-node/test-bus/test-unittest6";
-				__overlay__ {
-					status = "okay";
-				};
-			};
-		};
-		overlay7 {
-			fragment@0 {
-				target-path = "/testcase-data/overlay-node/test-bus/test-unittest7";
-				__overlay__ {
-					status = "okay";
-				};
-			};
-		};
-
-		/* test overlays application and removal in bad sequence */
-		overlay8 {
-			fragment@0 {
-				target-path = "/testcase-data/overlay-node/test-bus/test-unittest8";
-				__overlay__ {
-					status = "okay";
-				};
-			};
-		};
-		overlay9 {
-			fragment@0 {
-				target-path = "/testcase-data/overlay-node/test-bus/test-unittest8";
-				__overlay__ {
-					property-foo = "bar";
-				};
-			};
-		};
-
-		overlay10 {
-			fragment@0 {
-				target-path = "/testcase-data/overlay-node/test-bus";
-				__overlay__ {
-
-					/* suppress DTC warning */
-					#address-cells = <1>;
-					#size-cells = <0>;
-
-					test-unittest10 {
-						compatible = "unittest";
-						status = "okay";
-						reg = <10>;
-
-						#address-cells = <1>;
-						#size-cells = <0>;
-
-						test-unittest101 {
-							compatible = "unittest";
-							status = "okay";
-							reg = <1>;
-						};
-
-					};
-				};
-			};
-		};
-
-		overlay11 {
-			fragment@0 {
-				target-path = "/testcase-data/overlay-node/test-bus";
-				__overlay__ {
-
-					/* suppress DTC warning */
-					#address-cells = <1>;
-					#size-cells = <0>;
-
-					test-unittest11 {
-						compatible = "unittest";
-						status = "okay";
-						reg = <11>;
-
-						#address-cells = <1>;
-						#size-cells = <0>;
-
-						test-unittest111 {
-							compatible = "unittest";
-							status = "okay";
-							reg = <1>;
-						};
-
-					};
-				};
-			};
-		};
-
-		/* test enable using absolute target path (i2c) */
-		overlay12 {
-			fragment@0 {
-				target-path = "/testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest12";
-				__overlay__ {
-					status = "okay";
-				};
-			};
-		};
-
-		/* test disable using absolute target path (i2c) */
-		overlay13 {
-			fragment@0 {
-				target-path = "/testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest13";
-				__overlay__ {
-					status = "disabled";
-				};
-			};
-		};
-
-		/* test mux overlay */
-		overlay15 {
-			fragment@0 {
-				target-path = "/testcase-data/overlay-node/test-bus/i2c-test-bus";
-				__overlay__ {
-					#address-cells = <1>;
-					#size-cells = <0>;
-					test-unittest15 {
-						reg = <11>;
-						compatible = "unittest-i2c-mux";
-						status = "okay";
-
-						#address-cells = <1>;
-						#size-cells = <0>;
-
-						i2c@0 {
-							#address-cells = <1>;
-							#size-cells = <0>;
-							reg = <0>;
-
-							test-mux-dev {
-								reg = <32>;
-								compatible = "unittest-i2c-dev";
-								status = "okay";
-							};
-						};
-					};
-				};
-			};
-		};
-
 	};
 };

+ 148 - 152
drivers/of/unittest.c

@@ -45,6 +45,8 @@ static struct unittest_results {
 	failed; \
 })
 
+static int __init overlay_data_apply(const char *overlay_name, int *overlay_id);
+
 static void __init of_unittest_find_node_by_name(void)
 {
 	struct device_node *np;
@@ -997,8 +999,7 @@ static int __init unittest_data_add(void)
 	}
 
 	/*
-	 * This lock normally encloses of_overlay_apply() as well as
-	 * of_resolve_phandles().
+	 * This lock normally encloses of_resolve_phandles()
 	 */
 	of_overlay_mutex_lock();
 
@@ -1191,12 +1192,12 @@ static int of_unittest_device_exists(int unittest_nr, enum overlay_type ovtype)
 	return 0;
 }
 
-static const char *overlay_path(int nr)
+static const char *overlay_name_from_nr(int nr)
 {
 	static char buf[256];
 
 	snprintf(buf, sizeof(buf) - 1,
-		"/testcase-data/overlay%d", nr);
+		"overlay_%d", nr);
 	buf[sizeof(buf) - 1] = '\0';
 
 	return buf;
@@ -1263,25 +1264,19 @@ static void of_unittest_destroy_tracked_overlays(void)
 	} while (defers > 0);
 }
 
-static int of_unittest_apply_overlay(int overlay_nr, int unittest_nr,
+static int __init of_unittest_apply_overlay(int overlay_nr, int unittest_nr,
 		int *overlay_id)
 {
 	struct device_node *np = NULL;
+	const char *overlay_name;
 	int ret;
 
-	np = of_find_node_by_path(overlay_path(overlay_nr));
-	if (np == NULL) {
-		unittest(0, "could not find overlay node @\"%s\"\n",
-				overlay_path(overlay_nr));
-		ret = -EINVAL;
-		goto out;
-	}
+	overlay_name = overlay_name_from_nr(overlay_nr);
 
-	*overlay_id = 0;
-	ret = of_overlay_apply(np, overlay_id);
-	if (ret < 0) {
-		unittest(0, "could not create overlay from \"%s\"\n",
-				overlay_path(overlay_nr));
+	ret = overlay_data_apply(overlay_name, overlay_id);
+	if (!ret) {
+		unittest(0, "could not apply overlay \"%s\"\n",
+				overlay_name);
 		goto out;
 	}
 	of_unittest_track_overlay(*overlay_id);
@@ -1295,15 +1290,16 @@ out:
 }
 
 /* apply an overlay while checking before and after states */
-static int of_unittest_apply_overlay_check(int overlay_nr, int unittest_nr,
-		int before, int after, enum overlay_type ovtype)
+static int __init of_unittest_apply_overlay_check(int overlay_nr,
+		int unittest_nr, int before, int after,
+		enum overlay_type ovtype)
 {
 	int ret, ovcs_id;
 
 	/* unittest device must not be in before state */
 	if (of_unittest_device_exists(unittest_nr, ovtype) != before) {
-		unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
-				overlay_path(overlay_nr),
+		unittest(0, "%s with device @\"%s\" %s\n",
+				overlay_name_from_nr(overlay_nr),
 				unittest_path(unittest_nr, ovtype),
 				!before ? "enabled" : "disabled");
 		return -EINVAL;
@@ -1318,8 +1314,8 @@ static int of_unittest_apply_overlay_check(int overlay_nr, int unittest_nr,
 
 	/* unittest device must be to set to after state */
 	if (of_unittest_device_exists(unittest_nr, ovtype) != after) {
-		unittest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n",
-				overlay_path(overlay_nr),
+		unittest(0, "%s failed to create @\"%s\" %s\n",
+				overlay_name_from_nr(overlay_nr),
 				unittest_path(unittest_nr, ovtype),
 				!after ? "enabled" : "disabled");
 		return -EINVAL;
@@ -1329,7 +1325,7 @@ static int of_unittest_apply_overlay_check(int overlay_nr, int unittest_nr,
 }
 
 /* apply an overlay and then revert it while checking before, after states */
-static int of_unittest_apply_revert_overlay_check(int overlay_nr,
+static int __init of_unittest_apply_revert_overlay_check(int overlay_nr,
 		int unittest_nr, int before, int after,
 		enum overlay_type ovtype)
 {
@@ -1337,8 +1333,8 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr,
 
 	/* unittest device must be in before state */
 	if (of_unittest_device_exists(unittest_nr, ovtype) != before) {
-		unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
-				overlay_path(overlay_nr),
+		unittest(0, "%s with device @\"%s\" %s\n",
+				overlay_name_from_nr(overlay_nr),
 				unittest_path(unittest_nr, ovtype),
 				!before ? "enabled" : "disabled");
 		return -EINVAL;
@@ -1354,8 +1350,8 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr,
 
 	/* unittest device must be in after state */
 	if (of_unittest_device_exists(unittest_nr, ovtype) != after) {
-		unittest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n",
-				overlay_path(overlay_nr),
+		unittest(0, "%s failed to create @\"%s\" %s\n",
+				overlay_name_from_nr(overlay_nr),
 				unittest_path(unittest_nr, ovtype),
 				!after ? "enabled" : "disabled");
 		return -EINVAL;
@@ -1363,16 +1359,16 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr,
 
 	ret = of_overlay_remove(&ovcs_id);
 	if (ret != 0) {
-		unittest(0, "overlay @\"%s\" failed to be destroyed @\"%s\"\n",
-				overlay_path(overlay_nr),
+		unittest(0, "%s failed to be destroyed @\"%s\"\n",
+				overlay_name_from_nr(overlay_nr),
 				unittest_path(unittest_nr, ovtype));
 		return ret;
 	}
 
 	/* unittest device must be again in before state */
 	if (of_unittest_device_exists(unittest_nr, PDEV_OVERLAY) != before) {
-		unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
-				overlay_path(overlay_nr),
+		unittest(0, "%s with device @\"%s\" %s\n",
+				overlay_name_from_nr(overlay_nr),
 				unittest_path(unittest_nr, ovtype),
 				!before ? "enabled" : "disabled");
 		return -EINVAL;
@@ -1382,7 +1378,7 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr,
 }
 
 /* test activation of device */
-static void of_unittest_overlay_0(void)
+static void __init of_unittest_overlay_0(void)
 {
 	int ret;
 
@@ -1395,7 +1391,7 @@ static void of_unittest_overlay_0(void)
 }
 
 /* test deactivation of device */
-static void of_unittest_overlay_1(void)
+static void __init of_unittest_overlay_1(void)
 {
 	int ret;
 
@@ -1408,7 +1404,7 @@ static void of_unittest_overlay_1(void)
 }
 
 /* test activation of device */
-static void of_unittest_overlay_2(void)
+static void __init of_unittest_overlay_2(void)
 {
 	int ret;
 
@@ -1421,7 +1417,7 @@ static void of_unittest_overlay_2(void)
 }
 
 /* test deactivation of device */
-static void of_unittest_overlay_3(void)
+static void __init of_unittest_overlay_3(void)
 {
 	int ret;
 
@@ -1434,7 +1430,7 @@ static void of_unittest_overlay_3(void)
 }
 
 /* test activation of a full device node */
-static void of_unittest_overlay_4(void)
+static void __init of_unittest_overlay_4(void)
 {
 	int ret;
 
@@ -1447,7 +1443,7 @@ static void of_unittest_overlay_4(void)
 }
 
 /* test overlay apply/revert sequence */
-static void of_unittest_overlay_5(void)
+static void __init of_unittest_overlay_5(void)
 {
 	int ret;
 
@@ -1460,19 +1456,19 @@ static void of_unittest_overlay_5(void)
 }
 
 /* test overlay application in sequence */
-static void of_unittest_overlay_6(void)
+static void __init of_unittest_overlay_6(void)
 {
-	struct device_node *np;
 	int ret, i, ov_id[2], ovcs_id;
 	int overlay_nr = 6, unittest_nr = 6;
 	int before = 0, after = 1;
+	const char *overlay_name;
 
 	/* unittest device must be in before state */
 	for (i = 0; i < 2; i++) {
 		if (of_unittest_device_exists(unittest_nr + i, PDEV_OVERLAY)
 				!= before) {
-			unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
-					overlay_path(overlay_nr + i),
+			unittest(0, "%s with device @\"%s\" %s\n",
+					overlay_name_from_nr(overlay_nr + i),
 					unittest_path(unittest_nr + i,
 						PDEV_OVERLAY),
 					!before ? "enabled" : "disabled");
@@ -1483,18 +1479,12 @@ static void of_unittest_overlay_6(void)
 	/* apply the overlays */
 	for (i = 0; i < 2; i++) {
 
-		np = of_find_node_by_path(overlay_path(overlay_nr + i));
-		if (np == NULL) {
-			unittest(0, "could not find overlay node @\"%s\"\n",
-					overlay_path(overlay_nr + i));
-			return;
-		}
+		overlay_name = overlay_name_from_nr(overlay_nr + i);
 
-		ovcs_id = 0;
-		ret = of_overlay_apply(np, &ovcs_id);
-		if (ret < 0)  {
-			unittest(0, "could not create overlay from \"%s\"\n",
-					overlay_path(overlay_nr + i));
+		ret = overlay_data_apply(overlay_name, &ovcs_id);
+		if (!ret)  {
+			unittest(0, "could not apply overlay \"%s\"\n",
+					overlay_name);
 			return;
 		}
 		ov_id[i] = ovcs_id;
@@ -1506,7 +1496,7 @@ static void of_unittest_overlay_6(void)
 		if (of_unittest_device_exists(unittest_nr + i, PDEV_OVERLAY)
 				!= after) {
 			unittest(0, "overlay @\"%s\" failed @\"%s\" %s\n",
-					overlay_path(overlay_nr + i),
+					overlay_name_from_nr(overlay_nr + i),
 					unittest_path(unittest_nr + i,
 						PDEV_OVERLAY),
 					!after ? "enabled" : "disabled");
@@ -1518,8 +1508,8 @@ static void of_unittest_overlay_6(void)
 		ovcs_id = ov_id[i];
 		ret = of_overlay_remove(&ovcs_id);
 		if (ret != 0) {
-			unittest(0, "overlay @\"%s\" failed destroy @\"%s\"\n",
-					overlay_path(overlay_nr + i),
+			unittest(0, "%s failed destroy @\"%s\"\n",
+					overlay_name_from_nr(overlay_nr + i),
 					unittest_path(unittest_nr + i,
 						PDEV_OVERLAY));
 			return;
@@ -1531,8 +1521,8 @@ static void of_unittest_overlay_6(void)
 		/* unittest device must be again in before state */
 		if (of_unittest_device_exists(unittest_nr + i, PDEV_OVERLAY)
 				!= before) {
-			unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
-					overlay_path(overlay_nr + i),
+			unittest(0, "%s with device @\"%s\" %s\n",
+					overlay_name_from_nr(overlay_nr + i),
 					unittest_path(unittest_nr + i,
 						PDEV_OVERLAY),
 					!before ? "enabled" : "disabled");
@@ -1544,29 +1534,23 @@ static void of_unittest_overlay_6(void)
 }
 
 /* test overlay application in sequence */
-static void of_unittest_overlay_8(void)
+static void __init of_unittest_overlay_8(void)
 {
-	struct device_node *np;
 	int ret, i, ov_id[2], ovcs_id;
 	int overlay_nr = 8, unittest_nr = 8;
+	const char *overlay_name;
 
 	/* we don't care about device state in this test */
 
 	/* apply the overlays */
 	for (i = 0; i < 2; i++) {
 
-		np = of_find_node_by_path(overlay_path(overlay_nr + i));
-		if (np == NULL) {
-			unittest(0, "could not find overlay node @\"%s\"\n",
-					overlay_path(overlay_nr + i));
-			return;
-		}
+		overlay_name = overlay_name_from_nr(overlay_nr + i);
 
-		ovcs_id = 0;
-		ret = of_overlay_apply(np, &ovcs_id);
+		ret = overlay_data_apply(overlay_name, &ovcs_id);
 		if (ret < 0)  {
-			unittest(0, "could not create overlay from \"%s\"\n",
-					overlay_path(overlay_nr + i));
+			unittest(0, "could not apply overlay \"%s\"\n",
+					overlay_name);
 			return;
 		}
 		ov_id[i] = ovcs_id;
@@ -1577,8 +1561,8 @@ static void of_unittest_overlay_8(void)
 	ovcs_id = ov_id[0];
 	ret = of_overlay_remove(&ovcs_id);
 	if (ret == 0) {
-		unittest(0, "overlay @\"%s\" was destroyed @\"%s\"\n",
-				overlay_path(overlay_nr + 0),
+		unittest(0, "%s was destroyed @\"%s\"\n",
+				overlay_name_from_nr(overlay_nr + 0),
 				unittest_path(unittest_nr,
 					PDEV_OVERLAY));
 		return;
@@ -1589,8 +1573,8 @@ static void of_unittest_overlay_8(void)
 		ovcs_id = ov_id[i];
 		ret = of_overlay_remove(&ovcs_id);
 		if (ret != 0) {
-			unittest(0, "overlay @\"%s\" not destroyed @\"%s\"\n",
-					overlay_path(overlay_nr + i),
+			unittest(0, "%s not destroyed @\"%s\"\n",
+					overlay_name_from_nr(overlay_nr + i),
 					unittest_path(unittest_nr,
 						PDEV_OVERLAY));
 			return;
@@ -1602,7 +1586,7 @@ static void of_unittest_overlay_8(void)
 }
 
 /* test insertion of a bus with parent devices */
-static void of_unittest_overlay_10(void)
+static void __init of_unittest_overlay_10(void)
 {
 	int ret;
 	char *child_path;
@@ -1625,7 +1609,7 @@ static void of_unittest_overlay_10(void)
 }
 
 /* test insertion of a bus with parent devices (and revert) */
-static void of_unittest_overlay_11(void)
+static void __init of_unittest_overlay_11(void)
 {
 	int ret;
 
@@ -1891,7 +1875,7 @@ static void of_unittest_overlay_i2c_cleanup(void)
 	i2c_del_driver(&unittest_i2c_dev_driver);
 }
 
-static void of_unittest_overlay_i2c_12(void)
+static void __init of_unittest_overlay_i2c_12(void)
 {
 	int ret;
 
@@ -1904,7 +1888,7 @@ static void of_unittest_overlay_i2c_12(void)
 }
 
 /* test deactivation of device */
-static void of_unittest_overlay_i2c_13(void)
+static void __init of_unittest_overlay_i2c_13(void)
 {
 	int ret;
 
@@ -1921,7 +1905,7 @@ static void of_unittest_overlay_i2c_14(void)
 {
 }
 
-static void of_unittest_overlay_i2c_15(void)
+static void __init of_unittest_overlay_i2c_15(void)
 {
 	int ret;
 
@@ -2023,23 +2007,38 @@ static inline void __init of_unittest_overlay(void) { }
 	extern uint8_t __dtb_##name##_begin[]; \
 	extern uint8_t __dtb_##name##_end[]
 
-#define OVERLAY_INFO(name, expected) \
-{	.dtb_begin	 = __dtb_##name##_begin, \
-	.dtb_end	 = __dtb_##name##_end, \
-	.expected_result = expected, \
+#define OVERLAY_INFO(overlay_name, expected)             \
+{	.dtb_begin       = __dtb_##overlay_name##_begin, \
+	.dtb_end         = __dtb_##overlay_name##_end,   \
+	.expected_result = expected,                     \
+	.name            = #overlay_name,                \
 }
 
 struct overlay_info {
-	uint8_t		   *dtb_begin;
-	uint8_t		   *dtb_end;
-	void		   *data;
-	struct device_node *np_overlay;
-	int		   expected_result;
-	int		   overlay_id;
+	uint8_t		*dtb_begin;
+	uint8_t		*dtb_end;
+	int		expected_result;
+	int		overlay_id;
+	char		*name;
 };
 
 OVERLAY_INFO_EXTERN(overlay_base);
 OVERLAY_INFO_EXTERN(overlay);
+OVERLAY_INFO_EXTERN(overlay_0);
+OVERLAY_INFO_EXTERN(overlay_1);
+OVERLAY_INFO_EXTERN(overlay_2);
+OVERLAY_INFO_EXTERN(overlay_3);
+OVERLAY_INFO_EXTERN(overlay_4);
+OVERLAY_INFO_EXTERN(overlay_5);
+OVERLAY_INFO_EXTERN(overlay_6);
+OVERLAY_INFO_EXTERN(overlay_7);
+OVERLAY_INFO_EXTERN(overlay_8);
+OVERLAY_INFO_EXTERN(overlay_9);
+OVERLAY_INFO_EXTERN(overlay_10);
+OVERLAY_INFO_EXTERN(overlay_11);
+OVERLAY_INFO_EXTERN(overlay_12);
+OVERLAY_INFO_EXTERN(overlay_13);
+OVERLAY_INFO_EXTERN(overlay_15);
 OVERLAY_INFO_EXTERN(overlay_bad_phandle);
 OVERLAY_INFO_EXTERN(overlay_bad_symbol);
 
@@ -2047,6 +2046,21 @@ OVERLAY_INFO_EXTERN(overlay_bad_symbol);
 static struct overlay_info overlays[] = {
 	OVERLAY_INFO(overlay_base, -9999),
 	OVERLAY_INFO(overlay, 0),
+	OVERLAY_INFO(overlay_0, 0),
+	OVERLAY_INFO(overlay_1, 0),
+	OVERLAY_INFO(overlay_2, 0),
+	OVERLAY_INFO(overlay_3, 0),
+	OVERLAY_INFO(overlay_4, 0),
+	OVERLAY_INFO(overlay_5, 0),
+	OVERLAY_INFO(overlay_6, 0),
+	OVERLAY_INFO(overlay_7, 0),
+	OVERLAY_INFO(overlay_8, 0),
+	OVERLAY_INFO(overlay_9, 0),
+	OVERLAY_INFO(overlay_10, 0),
+	OVERLAY_INFO(overlay_11, 0),
+	OVERLAY_INFO(overlay_12, 0),
+	OVERLAY_INFO(overlay_13, 0),
+	OVERLAY_INFO(overlay_15, 0),
 	OVERLAY_INFO(overlay_bad_phandle, -EINVAL),
 	OVERLAY_INFO(overlay_bad_symbol, -EINVAL),
 	{}
@@ -2077,6 +2091,7 @@ void __init unittest_unflatten_overlay_base(void)
 {
 	struct overlay_info *info;
 	u32 data_size;
+	void *new_fdt;
 	u32 size;
 
 	info = &overlays[0];
@@ -2098,17 +2113,16 @@ void __init unittest_unflatten_overlay_base(void)
 		return;
 	}
 
-	info->data = dt_alloc_memory(size, roundup_pow_of_two(FDT_V17_SIZE));
-	if (!info->data) {
+	new_fdt = dt_alloc_memory(size, roundup_pow_of_two(FDT_V17_SIZE));
+	if (!new_fdt) {
 		pr_err("alloc for dtb 'overlay_base' failed");
 		return;
 	}
 
-	memcpy(info->data, info->dtb_begin, size);
+	memcpy(new_fdt, info->dtb_begin, size);
 
-	__unflatten_device_tree(info->data, NULL, &info->np_overlay,
+	__unflatten_device_tree(new_fdt, NULL, &overlay_base_root,
 				dt_alloc_memory, true);
-	overlay_base_root = info->np_overlay;
 }
 
 /*
@@ -2122,73 +2136,44 @@ void __init unittest_unflatten_overlay_base(void)
  *
  * Return 0 on unexpected error.
  */
-static int __init overlay_data_add(int onum)
+static int __init overlay_data_apply(const char *overlay_name, int *overlay_id)
 {
 	struct overlay_info *info;
+	int found = 0;
 	int k;
 	int ret;
 	u32 size;
-	u32 size_from_header;
 
-	for (k = 0, info = overlays; info; info++, k++) {
-		if (k == onum)
+	for (k = 0, info = overlays; info && info->name; info++, k++) {
+		if (!strcmp(overlay_name, info->name)) {
+			found = 1;
 			break;
+		}
 	}
-	if (onum > k)
+	if (!found) {
+		pr_err("no overlay data for %s\n", overlay_name);
 		return 0;
+	}
 
 	size = info->dtb_end - info->dtb_begin;
 	if (!size) {
-		pr_err("no overlay to attach, %d\n", onum);
+		pr_err("no overlay data for %s\n", overlay_name);
 		ret = 0;
 	}
 
-	size_from_header = fdt_totalsize(info->dtb_begin);
-	if (size_from_header != size) {
-		pr_err("overlay header totalsize != actual size, %d", onum);
-		return 0;
-	}
-
-	/*
-	 * Must create permanent copy of FDT because of_fdt_unflatten_tree()
-	 * will create pointers to the passed in FDT in the EDT.
-	 */
-	info->data = kmemdup(info->dtb_begin, size, GFP_KERNEL);
-	if (!info->data) {
-		pr_err("unable to allocate memory for data, %d\n", onum);
-		return 0;
-	}
-
-	of_fdt_unflatten_tree(info->data, NULL, &info->np_overlay);
-	if (!info->np_overlay) {
-		pr_err("unable to unflatten overlay, %d\n", onum);
-		ret = 0;
-		goto out_free_data;
-	}
-
-	info->overlay_id = 0;
-	ret = of_overlay_apply(info->np_overlay, &info->overlay_id);
-	if (ret < 0) {
-		pr_err("of_overlay_apply() (ret=%d), %d\n", ret, onum);
-		goto out_free_np_overlay;
-	}
-
-	pr_debug("__dtb_overlay_begin applied, overlay id %d\n", ret);
-
-	goto out;
-
-out_free_np_overlay:
-	/*
-	 * info->np_overlay is the unflattened device tree
-	 * It has not been spliced into the live tree.
-	 */
-
-	/* todo: function to free unflattened device tree */
+	ret = of_overlay_fdt_apply(info->dtb_begin, size, &info->overlay_id);
+	if (overlay_id)
+		*overlay_id = info->overlay_id;
+	if (ret < 0)
+		goto out;
 
-out_free_data:
-	kfree(info->data);
+	pr_debug("%s applied\n", overlay_name);
 
 out:
+	if (ret != info->expected_result)
+		pr_err("of_overlay_fdt_apply() expected %d, ret=%d, %s\n",
+		       info->expected_result, ret, overlay_name);
+
 	return (ret == info->expected_result);
 }
 
@@ -2290,18 +2275,29 @@ static __init void of_unittest_overlay_high_level(void)
 		__of_attach_node_sysfs(np);
 
 	if (of_symbols) {
+		struct property *new_prop;
 		for_each_property_of_node(overlay_base_symbols, prop) {
-			ret = __of_add_property(of_symbols, prop);
+
+			new_prop = __of_prop_dup(prop, GFP_KERNEL);
+			if (!new_prop) {
+				unittest(0, "__of_prop_dup() of '%s' from overlay_base node __symbols__",
+					 prop->name);
+				goto err_unlock;
+			}
+			ret = __of_add_property(of_symbols, new_prop);
 			if (ret) {
-				unittest(0,
-					 "duplicate property '%s' in overlay_base node __symbols__",
+				if (!strcmp(new_prop->name, "name")) {
+					/* auto-generated by unflatten */
+					ret = 0;
+					continue;
+				}
+				unittest(0, "duplicate property '%s' in overlay_base node __symbols__",
 					 prop->name);
 				goto err_unlock;
 			}
-			ret = __of_add_property_sysfs(of_symbols, prop);
+			ret = __of_add_property_sysfs(of_symbols, new_prop);
 			if (ret) {
-				unittest(0,
-					 "unable to add property '%s' in overlay_base node __symbols__ to sysfs",
+				unittest(0, "unable to add property '%s' in overlay_base node __symbols__ to sysfs",
 					 prop->name);
 				goto err_unlock;
 			}
@@ -2313,13 +2309,13 @@ static __init void of_unittest_overlay_high_level(void)
 
 	/* now do the normal overlay usage test */
 
-	unittest(overlay_data_add(1),
+	unittest(overlay_data_apply("overlay", NULL),
 		 "Adding overlay 'overlay' failed\n");
 
-	unittest(overlay_data_add(2),
+	unittest(overlay_data_apply("overlay_bad_phandle", NULL),
 		 "Adding overlay 'overlay_bad_phandle' failed\n");
 
-	unittest(overlay_data_add(3),
+	unittest(overlay_data_apply("overlay_bad_symbol", NULL),
 		 "Adding overlay 'overlay_bad_symbol' failed\n");
 
 	return;

+ 3 - 3
include/linux/of.h

@@ -1359,8 +1359,8 @@ struct of_overlay_notify_data {
 
 #ifdef CONFIG_OF_OVERLAY
 
-/* ID based overlays; the API for external users */
-int of_overlay_apply(struct device_node *tree, int *ovcs_id);
+int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size,
+			 int *ovcs_id);
 int of_overlay_remove(int *ovcs_id);
 int of_overlay_remove_all(void);
 
@@ -1369,7 +1369,7 @@ int of_overlay_notifier_unregister(struct notifier_block *nb);
 
 #else
 
-static inline int of_overlay_apply(struct device_node *tree, int *ovcs_id)
+static inline int of_overlay_fdt_apply(void *overlay_fdt, int *ovcs_id)
 {
 	return -ENOTSUPP;
 }