Commit 2ec360bb authored by Dave Airlie's avatar Dave Airlie

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()
parents f073d78e 77f59f89
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 {
};
};
};
};
...@@ -13,13 +13,10 @@ Required Properties: ...@@ -13,13 +13,10 @@ Required Properties:
- "renesas,du-r8a7794" for R8A7794 (R-Car E2) compatible DU - "renesas,du-r8a7794" for R8A7794 (R-Car E2) compatible DU
- "renesas,du-r8a7795" for R8A7795 (R-Car H3) 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-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 - reg: the memory-mapped I/O registers base address and length
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).
- interrupt-parent: phandle of the parent interrupt controller. - interrupt-parent: phandle of the parent interrupt controller.
- interrupts: Interrupt specifiers for the DU interrupts. - interrupts: Interrupt specifiers for the DU interrupts.
...@@ -29,14 +26,13 @@ Required Properties: ...@@ -29,14 +26,13 @@ Required Properties:
- clock-names: Name of the clocks. This property is model-dependent. - clock-names: Name of the clocks. This property is model-dependent.
- R8A7779 uses a single functional clock. The clock doesn't need to be - R8A7779 uses a single functional clock. The clock doesn't need to be
named. named.
- All other DU instances use one functional clock per channel and one - All other DU instances use one functional clock per channel The
clock per LVDS encoder (if available). The functional clocks must be functional clocks must be named "du.x" with "x" being the channel
named "du.x" with "x" being the channel numerical index. The LVDS clocks numerical index.
must be named "lvds.x" with "x" being the LVDS encoder numerical index. - In addition to the functional clocks, all DU versions also support
- In addition to the functional and encoder clocks, all DU versions also externally supplied pixel clocks. Those clocks are optional. When
support externally supplied pixel clocks. Those clocks are optional. supplied they must be named "dclkin.x" with "x" being the input clock
When supplied they must be named "dclkin.x" with "x" being the input numerical index.
clock numerical index.
- vsps: A list of phandle and channel index tuples to the VSPs that handle - 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 the memory interfaces for the DU channels. The phandle identifies the VSP
...@@ -63,15 +59,15 @@ corresponding to each DU output. ...@@ -63,15 +59,15 @@ corresponding to each DU output.
R8A7794 (R-Car E2) DPAD 0 DPAD 1 - - R8A7794 (R-Car E2) DPAD 0 DPAD 1 - -
R8A7795 (R-Car H3) DPAD 0 HDMI 0 HDMI 1 LVDS 0 R8A7795 (R-Car H3) DPAD 0 HDMI 0 HDMI 1 LVDS 0
R8A7796 (R-Car M3-W) DPAD 0 HDMI 0 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 Example: R8A7795 (R-Car H3) ES2.0 DU
du: display@feb00000 { du: display@feb00000 {
compatible = "renesas,du-r8a7795"; compatible = "renesas,du-r8a7795";
reg = <0 0xfeb00000 0 0x80000>, reg = <0 0xfeb00000 0 0x80000>;
<0 0xfeb90000 0 0x14>;
reg-names = "du", "lvds.0";
interrupts = <GIC_SPI 256 IRQ_TYPE_LEVEL_HIGH>, interrupts = <GIC_SPI 256 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 268 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 268 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 269 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 269 IRQ_TYPE_LEVEL_HIGH>,
...@@ -79,9 +75,8 @@ Example: R8A7795 (R-Car H3) ES2.0 DU ...@@ -79,9 +75,8 @@ Example: R8A7795 (R-Car H3) ES2.0 DU
clocks = <&cpg CPG_MOD 724>, clocks = <&cpg CPG_MOD 724>,
<&cpg CPG_MOD 723>, <&cpg CPG_MOD 723>,
<&cpg CPG_MOD 722>, <&cpg CPG_MOD 722>,
<&cpg CPG_MOD 721>, <&cpg CPG_MOD 721>;
<&cpg CPG_MOD 727>; clock-names = "du.0", "du.1", "du.2", "du.3";
clock-names = "du.0", "du.1", "du.2", "du.3", "lvds.0";
vsps = <&vspd0 0>, <&vspd1 0>, <&vspd2 0>, <&vspd0 1>; vsps = <&vspd0 0>, <&vspd1 0>, <&vspd2 0>, <&vspd0 1>;
ports { ports {
......
...@@ -87,8 +87,8 @@ Overlay in-kernel API ...@@ -87,8 +87,8 @@ Overlay in-kernel API
The API is quite easy to use. The API is quite easy to use.
1. Call of_overlay_apply() to create and apply an overlay changeset. The return 1. Call of_overlay_fdt_apply() to create and apply an overlay changeset. The
value is an error or a cookie identifying this overlay. return value is an error or a cookie identifying this overlay.
2. Call of_overlay_remove() to remove and cleanup the overlay changeset 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 previously created via the call to of_overlay_apply(). Removal of an overlay
......
...@@ -4744,6 +4744,7 @@ F: drivers/gpu/drm/rcar-du/ ...@@ -4744,6 +4744,7 @@ F: drivers/gpu/drm/rcar-du/
F: drivers/gpu/drm/shmobile/ F: drivers/gpu/drm/shmobile/
F: include/linux/platform_data/shmob_drm.h 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,dw-hdmi.txt
F: Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt
F: Documentation/devicetree/bindings/display/renesas,du.txt F: Documentation/devicetree/bindings/display/renesas,du.txt
DRM DRIVERS FOR ROCKCHIP DRM DRIVERS FOR ROCKCHIP
......
...@@ -259,7 +259,7 @@ static void __init dtb_apic_setup(void) ...@@ -259,7 +259,7 @@ static void __init dtb_apic_setup(void)
dtb_ioapic_setup(); dtb_ioapic_setup();
} }
#ifdef CONFIG_OF_FLATTREE #ifdef CONFIG_OF_EARLY_FLATTREE
static void __init x86_flattree_get_config(void) static void __init x86_flattree_get_config(void)
{ {
u32 size, map_len; u32 size, map_len;
......
...@@ -19,9 +19,11 @@ config DRM_RCAR_DW_HDMI ...@@ -19,9 +19,11 @@ config DRM_RCAR_DW_HDMI
Enable support for R-Car Gen3 internal HDMI encoder. Enable support for R-Car Gen3 internal HDMI encoder.
config DRM_RCAR_LVDS config DRM_RCAR_LVDS
bool "R-Car DU LVDS Encoder Support" tristate "R-Car DU LVDS Encoder Support"
depends on DRM_RCAR_DU depends on DRM && DRM_BRIDGE && OF
select DRM_PANEL select DRM_PANEL
select OF_FLATTREE
select OF_OVERLAY
help help
Enable support for the R-Car Display Unit embedded LVDS encoders. Enable support for the R-Car Display Unit embedded LVDS encoders.
......
...@@ -4,12 +4,16 @@ rcar-du-drm-y := rcar_du_crtc.o \ ...@@ -4,12 +4,16 @@ rcar-du-drm-y := rcar_du_crtc.o \
rcar_du_encoder.o \ rcar_du_encoder.o \
rcar_du_group.o \ rcar_du_group.o \
rcar_du_kms.o \ rcar_du_kms.o \
rcar_du_lvdscon.o \
rcar_du_plane.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 rcar-du-drm-$(CONFIG_DRM_RCAR_VSP) += rcar_du_vsp.o
obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o
obj-$(CONFIG_DRM_RCAR_DW_HDMI) += rcar_dw_hdmi.o obj-$(CONFIG_DRM_RCAR_DW_HDMI) += rcar_dw_hdmi.o
obj-$(CONFIG_DRM_RCAR_LVDS) += rcar_lvds.o
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "rcar_du_drv.h" #include "rcar_du_drv.h"
#include "rcar_du_kms.h" #include "rcar_du_kms.h"
#include "rcar_du_of.h"
#include "rcar_du_regs.h" #include "rcar_du_regs.h"
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
...@@ -74,7 +75,6 @@ static const struct rcar_du_device_info rzg1_du_r8a7745_info = { ...@@ -74,7 +75,6 @@ static const struct rcar_du_device_info rzg1_du_r8a7745_info = {
.port = 1, .port = 1,
}, },
}, },
.num_lvds = 0,
}; };
static const struct rcar_du_device_info rcar_du_r8a7779_info = { 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 = { ...@@ -95,14 +95,13 @@ static const struct rcar_du_device_info rcar_du_r8a7779_info = {
.port = 1, .port = 1,
}, },
}, },
.num_lvds = 0,
}; };
static const struct rcar_du_device_info rcar_du_r8a7790_info = { static const struct rcar_du_device_info rcar_du_r8a7790_info = {
.gen = 2, .gen = 2,
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_EXT_CTRL_REGS, | 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, .num_crtcs = 3,
.routes = { .routes = {
/* /*
...@@ -164,7 +163,6 @@ static const struct rcar_du_device_info rcar_du_r8a7792_info = { ...@@ -164,7 +163,6 @@ static const struct rcar_du_device_info rcar_du_r8a7792_info = {
.port = 1, .port = 1,
}, },
}, },
.num_lvds = 0,
}; };
static const struct rcar_du_device_info rcar_du_r8a7794_info = { 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 = { ...@@ -186,7 +184,6 @@ static const struct rcar_du_device_info rcar_du_r8a7794_info = {
.port = 1, .port = 1,
}, },
}, },
.num_lvds = 0,
}; };
static const struct rcar_du_device_info rcar_du_r8a7795_info = { 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 = { ...@@ -249,6 +246,26 @@ static const struct rcar_du_device_info rcar_du_r8a7796_info = {
.dpll_ch = BIT(1), .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[] = { static const struct of_device_id rcar_du_of_table[] = {
{ .compatible = "renesas,du-r8a7743", .data = &rzg1_du_r8a7743_info }, { .compatible = "renesas,du-r8a7743", .data = &rzg1_du_r8a7743_info },
{ .compatible = "renesas,du-r8a7745", .data = &rzg1_du_r8a7745_info }, { .compatible = "renesas,du-r8a7745", .data = &rzg1_du_r8a7745_info },
...@@ -260,6 +277,7 @@ static const struct of_device_id rcar_du_of_table[] = { ...@@ -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-r8a7794", .data = &rcar_du_r8a7794_info },
{ .compatible = "renesas,du-r8a7795", .data = &rcar_du_r8a7795_info }, { .compatible = "renesas,du-r8a7795", .data = &rcar_du_r8a7795_info },
{ .compatible = "renesas,du-r8a7796", .data = &rcar_du_r8a7796_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 = { ...@@ -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_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver"); MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver");
......
...@@ -26,14 +26,12 @@ struct device; ...@@ -26,14 +26,12 @@ struct device;
struct drm_device; struct drm_device;
struct drm_fbdev_cma; struct drm_fbdev_cma;
struct rcar_du_device; 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_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_EXT_CTRL_REGS (1 << 1) /* Has extended control registers */
#define RCAR_DU_FEATURE_VSP1_SOURCE (1 << 2) /* Has inputs from VSP1 */ #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_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 * struct rcar_du_output_routing - Output routing specification
...@@ -70,7 +68,6 @@ struct rcar_du_device_info { ...@@ -70,7 +68,6 @@ struct rcar_du_device_info {
#define RCAR_DU_MAX_CRTCS 4 #define RCAR_DU_MAX_CRTCS 4
#define RCAR_DU_MAX_GROUPS DIV_ROUND_UP(RCAR_DU_MAX_CRTCS, 2) #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 #define RCAR_DU_MAX_VSPS 4
struct rcar_du_device { struct rcar_du_device {
...@@ -96,8 +93,6 @@ struct rcar_du_device { ...@@ -96,8 +93,6 @@ struct rcar_du_device {
unsigned int dpad0_source; unsigned int dpad0_source;
unsigned int vspd1_sink; unsigned int vspd1_sink;
struct rcar_du_lvdsenc *lvds[RCAR_DU_MAX_LVDS];
}; };
static inline bool rcar_du_has(struct rcar_du_device *rcdu, static inline bool rcar_du_has(struct rcar_du_device *rcdu,
......
...@@ -21,134 +21,22 @@ ...@@ -21,134 +21,22 @@
#include "rcar_du_drv.h" #include "rcar_du_drv.h"
#include "rcar_du_encoder.h" #include "rcar_du_encoder.h"
#include "rcar_du_kms.h" #include "rcar_du_kms.h"
#include "rcar_du_lvdscon.h"
#include "rcar_du_lvdsenc.h"
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* Encoder * 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, static void rcar_du_encoder_mode_set(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state, struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state) struct drm_connector_state *conn_state)
{ {
struct rcar_du_encoder *renc = to_rcar_encoder(encoder); 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); 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 = { static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
.atomic_mode_set = rcar_du_encoder_mode_set, .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 = { static const struct drm_encoder_funcs encoder_funcs = {
...@@ -172,20 +60,6 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu, ...@@ -172,20 +60,6 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
renc->output = output; renc->output = output;
encoder = rcar_encoder_to_drm_encoder(renc); encoder = rcar_encoder_to_drm_encoder(renc);
switch (output) {
case RCAR_DU_OUTPUT_LVDS0:
renc->lvds = rcdu->lvds[0];
break;
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", dev_dbg(rcdu->dev, "initializing encoder %pOF for output %u\n",
enc_node, output); enc_node, output);
...@@ -195,11 +69,6 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu, ...@@ -195,11 +69,6 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
ret = -EPROBE_DEFER; ret = -EPROBE_DEFER;
goto done; goto done;
} }
} else {
dev_dbg(rcdu->dev,
"initializing internal encoder for output %u\n",
output);
}
ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs, ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs,
DRM_MODE_ENCODER_NONE, NULL); DRM_MODE_ENCODER_NONE, NULL);
...@@ -208,7 +77,6 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu, ...@@ -208,7 +77,6 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
drm_encoder_helper_add(encoder, &encoder_helper_funcs); drm_encoder_helper_add(encoder, &encoder_helper_funcs);
if (bridge) {
/* /*
* Attach the bridge to the encoder. The bridge will create the * Attach the bridge to the encoder. The bridge will create the
* connector. * connector.
...@@ -218,19 +86,6 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu, ...@@ -218,19 +86,6 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
drm_encoder_cleanup(encoder); drm_encoder_cleanup(encoder);
return ret; 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;
}
}
done: done:
if (ret < 0) { if (ret < 0) {
......
...@@ -19,13 +19,10 @@ ...@@ -19,13 +19,10 @@
struct drm_panel; struct drm_panel;
struct rcar_du_device; struct rcar_du_device;
struct rcar_du_lvdsenc;
struct rcar_du_encoder { struct rcar_du_encoder {
struct drm_encoder base; struct drm_encoder base;
enum rcar_du_output output; enum rcar_du_output output;
struct rcar_du_connector *connector;
struct rcar_du_lvdsenc *lvds;
}; };
#define to_rcar_encoder(e) \ #define to_rcar_encoder(e) \
...@@ -33,15 +30,6 @@ struct rcar_du_encoder { ...@@ -33,15 +30,6 @@ struct rcar_du_encoder {
#define rcar_encoder_to_drm_encoder(e) (&(e)->base) #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, int rcar_du_encoder_init(struct rcar_du_device *rcdu,
enum rcar_du_output output, enum rcar_du_output output,
struct device_node *enc_node, struct device_node *enc_node,
......
...@@ -27,7 +27,6 @@ ...@@ -27,7 +27,6 @@
#include "rcar_du_drv.h" #include "rcar_du_drv.h"
#include "rcar_du_encoder.h" #include "rcar_du_encoder.h"
#include "rcar_du_kms.h" #include "rcar_du_kms.h"
#include "rcar_du_lvdsenc.h"
#include "rcar_du_regs.h" #include "rcar_du_regs.h"
#include "rcar_du_vsp.h" #include "rcar_du_vsp.h"
...@@ -341,11 +340,10 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu, ...@@ -341,11 +340,10 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
of_node_put(entity_ep_node); of_node_put(entity_ep_node);
if (!encoder) { if (!encoder) {
/* dev_warn(rcdu->dev,
* If no encoder has been found the entity must be the "no encoder found for endpoint %pOF, skipping\n",
* connector. ep->local_node);
*/ return -ENODEV;
connector = entity;
} }
ret = rcar_du_encoder_init(rcdu, output, encoder, connector); ret = rcar_du_encoder_init(rcdu, output, encoder, connector);
...@@ -595,10 +593,6 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu) ...@@ -595,10 +593,6 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
} }
/* Initialize the encoders. */ /* Initialize the encoders. */
ret = rcar_du_lvdsenc_init(rcdu);
if (ret < 0)
return ret;
ret = rcar_du_encoders_init(rcdu); ret = rcar_du_encoders_init(rcdu);
if (ret < 0) if (ret < 0)
return ret; return ret;
......
/*
* 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;
}
/*
* 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__ */
/*
* 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;
}
/*
* 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__ */
// 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);
}
/* 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__ */
// 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>;
};
};
};
};
};
// 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>;
};
};
};
};
};
// 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>;
};
};
};
};
};
// 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>;
};
};
};
};
};
// 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>;
};
};
};
};
};
// 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");
...@@ -92,6 +92,7 @@ config OF_RESOLVE ...@@ -92,6 +92,7 @@ config OF_RESOLVE
config OF_OVERLAY config OF_OVERLAY
bool "Device Tree overlays" bool "Device Tree overlays"
select OF_DYNAMIC select OF_DYNAMIC
select OF_FLATTREE
select OF_RESOLVE select OF_RESOLVE
help help
Overlays are a method to dynamically modify part of the kernel's Overlays are a method to dynamically modify part of the kernel's
......
...@@ -12,10 +12,12 @@ ...@@ -12,10 +12,12 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_device.h> #include <linux/of_device.h>
#include <linux/of_fdt.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/ctype.h> #include <linux/ctype.h>
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/libfdt.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/idr.h> #include <linux/idr.h>
...@@ -33,7 +35,9 @@ struct fragment { ...@@ -33,7 +35,9 @@ struct fragment {
/** /**
* struct overlay_changeset * struct overlay_changeset
* @id: changeset identifier
* @ovcs_list: list on which we are located * @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 * @overlay_tree: expanded device tree that contains the fragment nodes
* @count: count of fragment structures * @count: count of fragment structures
* @fragments: fragment nodes in the overlay expanded device tree * @fragments: fragment nodes in the overlay expanded device tree
...@@ -43,6 +47,7 @@ struct fragment { ...@@ -43,6 +47,7 @@ struct fragment {
struct overlay_changeset { struct overlay_changeset {
int id; int id;
struct list_head ovcs_list; struct list_head ovcs_list;
const void *fdt;
struct device_node *overlay_tree; struct device_node *overlay_tree;
int count; int count;
struct fragment *fragments; struct fragment *fragments;
...@@ -483,27 +488,38 @@ static int build_changeset(struct overlay_changeset *ovcs) ...@@ -483,27 +488,38 @@ static int build_changeset(struct overlay_changeset *ovcs)
*/ */
static struct device_node *find_target_node(struct device_node *info_node) static struct device_node *find_target_node(struct device_node *info_node)
{ {
struct device_node *node;
const char *path; const char *path;
u32 val; u32 val;
int ret; int ret;
ret = of_property_read_u32(info_node, "target", &val); ret = of_property_read_u32(info_node, "target", &val);
if (!ret) if (!ret) {
return of_find_node_by_phandle(val); 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); ret = of_property_read_string(info_node, "target-path", &path);
if (!ret) if (!ret) {
return of_find_node_by_path(path); 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", pr_err("find target, node: %pOF, no target property\n", info_node);
info_node, info_node->name);
return NULL; return NULL;
} }
/** /**
* init_overlay_changeset() - initialize overlay changeset from overlay tree * 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 * @tree: Contains all the overlay fragments and overlay fixup nodes
* *
* Initialize @ovcs. Populate @ovcs->fragments with node information from * 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) ...@@ -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. * detected in @tree, or -ENOSPC if idr_alloc() error.
*/ */
static int init_overlay_changeset(struct overlay_changeset *ovcs, 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 device_node *node, *overlay_node;
struct fragment *fragment; struct fragment *fragment;
...@@ -535,6 +551,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs, ...@@ -535,6 +551,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
pr_debug("%s() tree is not root\n", __func__); pr_debug("%s() tree is not root\n", __func__);
ovcs->overlay_tree = tree; ovcs->overlay_tree = tree;
ovcs->fdt = fdt;
INIT_LIST_HEAD(&ovcs->ovcs_list); INIT_LIST_HEAD(&ovcs->ovcs_list);
...@@ -606,6 +623,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs, ...@@ -606,6 +623,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
} }
if (!cnt) { if (!cnt) {
pr_err("no fragments or symbols in overlay\n");
ret = -EINVAL; ret = -EINVAL;
goto err_free_fragments; goto err_free_fragments;
} }
...@@ -642,11 +660,24 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs) ...@@ -642,11 +660,24 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs)
} }
kfree(ovcs->fragments); 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); kfree(ovcs);
} }
/** /*
* internal documentation
*
* of_overlay_apply() - Create and apply an overlay changeset * of_overlay_apply() - Create and apply an overlay changeset
* @fdt: the FDT that was unflattened to create @tree
* @tree: Expanded overlay device tree * @tree: Expanded overlay device tree
* @ovcs_id: Pointer to overlay changeset id * @ovcs_id: Pointer to overlay changeset id
* *
...@@ -685,21 +716,29 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs) ...@@ -685,21 +716,29 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs)
* id is returned to *ovcs_id. * 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; struct overlay_changeset *ovcs;
int ret = 0, ret_revert, ret_tmp; 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()) { if (devicetree_corrupt()) {
pr_err("devicetree state suspect, refuse to apply overlay\n"); pr_err("devicetree state suspect, refuse to apply overlay\n");
kfree(fdt);
kfree(tree);
ret = -EBUSY; ret = -EBUSY;
goto out; goto out;
} }
ovcs = kzalloc(sizeof(*ovcs), GFP_KERNEL); ovcs = kzalloc(sizeof(*ovcs), GFP_KERNEL);
if (!ovcs) { if (!ovcs) {
kfree(fdt);
kfree(tree);
ret = -ENOMEM; ret = -ENOMEM;
goto out; goto out;
} }
...@@ -709,12 +748,17 @@ int of_overlay_apply(struct device_node *tree, int *ovcs_id) ...@@ -709,12 +748,17 @@ int of_overlay_apply(struct device_node *tree, int *ovcs_id)
ret = of_resolve_phandles(tree); ret = of_resolve_phandles(tree);
if (ret) 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) 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); ret = overlay_notify(ovcs, OF_OVERLAY_PRE_APPLY);
if (ret) { if (ret) {
pr_err("overlay changeset pre-apply notify error %d\n", 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) ...@@ -754,6 +798,10 @@ int of_overlay_apply(struct device_node *tree, int *ovcs_id)
goto out_unlock; goto out_unlock;
err_free_tree:
kfree(fdt);
kfree(tree);
err_free_overlay_changeset: err_free_overlay_changeset:
free_overlay_changeset(ovcs); free_overlay_changeset(ovcs);
...@@ -766,7 +814,63 @@ int of_overlay_apply(struct device_node *tree, int *ovcs_id) ...@@ -766,7 +814,63 @@ int of_overlay_apply(struct device_node *tree, int *ovcs_id)
return ret; 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. * Find @np in @tree.
......
...@@ -269,17 +269,11 @@ int of_resolve_phandles(struct device_node *overlay) ...@@ -269,17 +269,11 @@ int of_resolve_phandles(struct device_node *overlay)
goto out; 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)) { if (!of_node_check_flag(overlay, OF_DETACHED)) {
pr_err("overlay not detached\n"); pr_err("overlay not detached\n");
err = -EINVAL; err = -EINVAL;
goto out; goto out;
} }
#endif
phandle_delta = live_tree_max_phandle() + 1; phandle_delta = live_tree_max_phandle() + 1;
adjust_overlay_phandles(overlay, phandle_delta); adjust_overlay_phandles(overlay, phandle_delta);
......
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
DTC_FLAGS_testcases := -Wno-interrupts_property
obj-y += testcases.dtb.o obj-y += testcases.dtb.o
obj-$(CONFIG_OF_OVERLAY) += overlay.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_phandle.dtb.o \
overlay_bad_symbol.dtb.o \ overlay_bad_symbol.dtb.o \
overlay_base.dtb.o overlay_base.dtb.o
...@@ -10,10 +24,14 @@ obj-$(CONFIG_OF_OVERLAY) += overlay.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))) targets += $(foreach suffix, dtb dtb.S, $(patsubst %.dtb.o,%.$(suffix),$(obj-y)))
# enable creation of __symbols__ node # enable creation of __symbols__ node
DTC_FLAGS_overlay := -@ DTC_FLAGS_overlay += -@
DTC_FLAGS_overlay_bad_phandle := -@ DTC_FLAGS_overlay_bad_phandle += -@
DTC_FLAGS_overlay_bad_symbol := -@ DTC_FLAGS_overlay_bad_symbol += -@
DTC_FLAGS_overlay_base := -@ DTC_FLAGS_overlay_base += -@
DTC_FLAGS_testcases += -@
# suppress warnings about intentional errors
DTC_FLAGS_testcases += -Wno-interrupts_property
.PRECIOUS: \ .PRECIOUS: \
$(obj)/%.dtb.S \ $(obj)/%.dtb.S \
......
...@@ -2,12 +2,8 @@ ...@@ -2,12 +2,8 @@
/dts-v1/; /dts-v1/;
/plugin/; /plugin/;
/ { &electric_1 {
fragment@0 {
target = <&electric_1>;
__overlay__ {
status = "okay"; status = "okay";
hvac_2: hvac-large-1 { hvac_2: hvac-large-1 {
...@@ -15,13 +11,10 @@ hvac_2: hvac-large-1 { ...@@ -15,13 +11,10 @@ hvac_2: hvac-large-1 {
heat-range = < 40 75 >; heat-range = < 40 75 >;
cool-range = < 65 80 >; cool-range = < 65 80 >;
}; };
}; };
};
fragment@1 { &rides_1 {
target = <&rides_1>;
__overlay__ {
#address-cells = <1>; #address-cells = <1>;
#size-cells = <1>; #size-cells = <1>;
status = "okay"; status = "okay";
...@@ -61,17 +54,11 @@ ride_200_right: track@20 { ...@@ -61,17 +54,11 @@ ride_200_right: track@20 {
reg = < 0x00000020 0x10 >; reg = < 0x00000020 0x10 >;
}; };
}; };
}; };
};
fragment@2 { &lights_2 {
target = <&lights_2>;
__overlay__ {
status = "okay"; status = "okay";
color = "purple", "white", "red", "green"; color = "purple", "white", "red", "green";
rate = < 3 256 >; rate = < 3 256 >;
};
};
}; };
// 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";
};
};
};
// 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";
};
};
};
// 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>;
};
};
};
// 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>;
};
};
};
// 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";
};
};
};
// 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";
};
};
};
// 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";
};
};
};
};
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_2 - enable using label */
&unittest2 {
status = "okay";
};
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_3 - disable using label */
&unittest3 {
status = "disabled";
};
// 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>;
};
};
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_5 - test overlay apply revert */
&unittest5 {
status = "okay";
};
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_6 */
/* overlays 6, 7 application and removal in sequence */
&unittest6 {
status = "okay";
};
// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
/plugin/;
/* overlay_7 */
/* overlays 6, 7 application and removal in sequence */
&unittest7 {
status = "okay";
};
// 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";
};
// 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";
};
...@@ -2,12 +2,7 @@ ...@@ -2,12 +2,7 @@
/dts-v1/; /dts-v1/;
/plugin/; /plugin/;
/ { &electric_1 {
fragment@0 {
target = <&electric_1>;
__overlay__ {
// This label should cause an error when the overlay // This label should cause an error when the overlay
// is applied. There is already a phandle value // is applied. There is already a phandle value
...@@ -16,6 +11,4 @@ spin_ctrl_1_conflict: motor-1 { ...@@ -16,6 +11,4 @@ spin_ctrl_1_conflict: motor-1 {
accelerate = < 3 >; accelerate = < 3 >;
decelerate = < 5 >; decelerate = < 5 >;
}; };
};
};
}; };
...@@ -2,12 +2,7 @@ ...@@ -2,12 +2,7 @@
/dts-v1/; /dts-v1/;
/plugin/; /plugin/;
/ { &electric_1 {
fragment@0 {
target = <&electric_1>;
__overlay__ {
// This label should cause an error when the overlay // This label should cause an error when the overlay
// is applied. There is already a symbol hvac_1 // is applied. There is already a symbol hvac_1
...@@ -18,6 +13,4 @@ hvac_1: hvac-medium-2 { ...@@ -18,6 +13,4 @@ hvac_1: hvac-medium-2 {
cool-range = < 60 80 >; cool-range = < 60 80 >;
}; };
};
};
}; };
...@@ -5,7 +5,7 @@ testcase-data { ...@@ -5,7 +5,7 @@ testcase-data {
overlay-node { overlay-node {
/* test bus */ /* test bus */
unittestbus: test-bus { unittest_test_bus: test-bus {
compatible = "simple-bus"; compatible = "simple-bus";
#address-cells = <1>; #address-cells = <1>;
#size-cells = <0>; #size-cells = <0>;
...@@ -70,7 +70,7 @@ unittest8: test-unittest8 { ...@@ -70,7 +70,7 @@ unittest8: test-unittest8 {
reg = <8>; reg = <8>;
}; };
i2c-test-bus { unittest_i2c_test_bus: i2c-test-bus {
compatible = "unittest-i2c-bus"; compatible = "unittest-i2c-bus";
status = "okay"; status = "okay";
reg = <50>; reg = <50>;
...@@ -113,218 +113,5 @@ test-mux-dev { ...@@ -113,218 +113,5 @@ test-mux-dev {
}; };
}; };
}; };
/* 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";
};
};
};
};
};
};
}; };
}; };
...@@ -45,6 +45,8 @@ static struct unittest_results { ...@@ -45,6 +45,8 @@ static struct unittest_results {
failed; \ failed; \
}) })
static int __init overlay_data_apply(const char *overlay_name, int *overlay_id);
static void __init of_unittest_find_node_by_name(void) static void __init of_unittest_find_node_by_name(void)
{ {
struct device_node *np; struct device_node *np;
...@@ -997,8 +999,7 @@ static int __init unittest_data_add(void) ...@@ -997,8 +999,7 @@ static int __init unittest_data_add(void)
} }
/* /*
* This lock normally encloses of_overlay_apply() as well as * This lock normally encloses of_resolve_phandles()
* of_resolve_phandles().
*/ */
of_overlay_mutex_lock(); of_overlay_mutex_lock();
...@@ -1191,12 +1192,12 @@ static int of_unittest_device_exists(int unittest_nr, enum overlay_type ovtype) ...@@ -1191,12 +1192,12 @@ static int of_unittest_device_exists(int unittest_nr, enum overlay_type ovtype)
return 0; return 0;
} }
static const char *overlay_path(int nr) static const char *overlay_name_from_nr(int nr)
{ {
static char buf[256]; static char buf[256];
snprintf(buf, sizeof(buf) - 1, snprintf(buf, sizeof(buf) - 1,
"/testcase-data/overlay%d", nr); "overlay_%d", nr);
buf[sizeof(buf) - 1] = '\0'; buf[sizeof(buf) - 1] = '\0';
return buf; return buf;
...@@ -1263,25 +1264,19 @@ static void of_unittest_destroy_tracked_overlays(void) ...@@ -1263,25 +1264,19 @@ static void of_unittest_destroy_tracked_overlays(void)
} while (defers > 0); } 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) int *overlay_id)
{ {
struct device_node *np = NULL; struct device_node *np = NULL;
const char *overlay_name;
int ret; int ret;
np = of_find_node_by_path(overlay_path(overlay_nr)); overlay_name = overlay_name_from_nr(overlay_nr);
if (np == NULL) {
unittest(0, "could not find overlay node @\"%s\"\n",
overlay_path(overlay_nr));
ret = -EINVAL;
goto out;
}
*overlay_id = 0; ret = overlay_data_apply(overlay_name, overlay_id);
ret = of_overlay_apply(np, overlay_id); if (!ret) {
if (ret < 0) { unittest(0, "could not apply overlay \"%s\"\n",
unittest(0, "could not create overlay from \"%s\"\n", overlay_name);
overlay_path(overlay_nr));
goto out; goto out;
} }
of_unittest_track_overlay(*overlay_id); of_unittest_track_overlay(*overlay_id);
...@@ -1295,15 +1290,16 @@ static int of_unittest_apply_overlay(int overlay_nr, int unittest_nr, ...@@ -1295,15 +1290,16 @@ static int of_unittest_apply_overlay(int overlay_nr, int unittest_nr,
} }
/* apply an overlay while checking before and after states */ /* apply an overlay while checking before and after states */
static int of_unittest_apply_overlay_check(int overlay_nr, int unittest_nr, static int __init of_unittest_apply_overlay_check(int overlay_nr,
int before, int after, enum overlay_type ovtype) int unittest_nr, int before, int after,
enum overlay_type ovtype)
{ {
int ret, ovcs_id; int ret, ovcs_id;
/* unittest device must not be in before state */ /* unittest device must not be in before state */
if (of_unittest_device_exists(unittest_nr, ovtype) != before) { if (of_unittest_device_exists(unittest_nr, ovtype) != before) {
unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n", unittest(0, "%s with device @\"%s\" %s\n",
overlay_path(overlay_nr), overlay_name_from_nr(overlay_nr),
unittest_path(unittest_nr, ovtype), unittest_path(unittest_nr, ovtype),
!before ? "enabled" : "disabled"); !before ? "enabled" : "disabled");
return -EINVAL; return -EINVAL;
...@@ -1318,8 +1314,8 @@ static int of_unittest_apply_overlay_check(int overlay_nr, int unittest_nr, ...@@ -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 */ /* unittest device must be to set to after state */
if (of_unittest_device_exists(unittest_nr, ovtype) != after) { if (of_unittest_device_exists(unittest_nr, ovtype) != after) {
unittest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n", unittest(0, "%s failed to create @\"%s\" %s\n",
overlay_path(overlay_nr), overlay_name_from_nr(overlay_nr),
unittest_path(unittest_nr, ovtype), unittest_path(unittest_nr, ovtype),
!after ? "enabled" : "disabled"); !after ? "enabled" : "disabled");
return -EINVAL; return -EINVAL;
...@@ -1329,7 +1325,7 @@ static int of_unittest_apply_overlay_check(int overlay_nr, int unittest_nr, ...@@ -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 */ /* 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, int unittest_nr, int before, int after,
enum overlay_type ovtype) enum overlay_type ovtype)
{ {
...@@ -1337,8 +1333,8 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr, ...@@ -1337,8 +1333,8 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr,
/* unittest device must be in before state */ /* unittest device must be in before state */
if (of_unittest_device_exists(unittest_nr, ovtype) != before) { if (of_unittest_device_exists(unittest_nr, ovtype) != before) {
unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n", unittest(0, "%s with device @\"%s\" %s\n",
overlay_path(overlay_nr), overlay_name_from_nr(overlay_nr),
unittest_path(unittest_nr, ovtype), unittest_path(unittest_nr, ovtype),
!before ? "enabled" : "disabled"); !before ? "enabled" : "disabled");
return -EINVAL; return -EINVAL;
...@@ -1354,8 +1350,8 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr, ...@@ -1354,8 +1350,8 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr,
/* unittest device must be in after state */ /* unittest device must be in after state */
if (of_unittest_device_exists(unittest_nr, ovtype) != after) { if (of_unittest_device_exists(unittest_nr, ovtype) != after) {
unittest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n", unittest(0, "%s failed to create @\"%s\" %s\n",
overlay_path(overlay_nr), overlay_name_from_nr(overlay_nr),
unittest_path(unittest_nr, ovtype), unittest_path(unittest_nr, ovtype),
!after ? "enabled" : "disabled"); !after ? "enabled" : "disabled");
return -EINVAL; return -EINVAL;
...@@ -1363,16 +1359,16 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr, ...@@ -1363,16 +1359,16 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr,
ret = of_overlay_remove(&ovcs_id); ret = of_overlay_remove(&ovcs_id);
if (ret != 0) { if (ret != 0) {
unittest(0, "overlay @\"%s\" failed to be destroyed @\"%s\"\n", unittest(0, "%s failed to be destroyed @\"%s\"\n",
overlay_path(overlay_nr), overlay_name_from_nr(overlay_nr),
unittest_path(unittest_nr, ovtype)); unittest_path(unittest_nr, ovtype));
return ret; return ret;
} }
/* unittest device must be again in before state */ /* unittest device must be again in before state */
if (of_unittest_device_exists(unittest_nr, PDEV_OVERLAY) != before) { if (of_unittest_device_exists(unittest_nr, PDEV_OVERLAY) != before) {
unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n", unittest(0, "%s with device @\"%s\" %s\n",
overlay_path(overlay_nr), overlay_name_from_nr(overlay_nr),
unittest_path(unittest_nr, ovtype), unittest_path(unittest_nr, ovtype),
!before ? "enabled" : "disabled"); !before ? "enabled" : "disabled");
return -EINVAL; return -EINVAL;
...@@ -1382,7 +1378,7 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr, ...@@ -1382,7 +1378,7 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr,
} }
/* test activation of device */ /* test activation of device */
static void of_unittest_overlay_0(void) static void __init of_unittest_overlay_0(void)
{ {
int ret; int ret;
...@@ -1395,7 +1391,7 @@ static void of_unittest_overlay_0(void) ...@@ -1395,7 +1391,7 @@ static void of_unittest_overlay_0(void)
} }
/* test deactivation of device */ /* test deactivation of device */
static void of_unittest_overlay_1(void) static void __init of_unittest_overlay_1(void)
{ {
int ret; int ret;
...@@ -1408,7 +1404,7 @@ static void of_unittest_overlay_1(void) ...@@ -1408,7 +1404,7 @@ static void of_unittest_overlay_1(void)
} }
/* test activation of device */ /* test activation of device */
static void of_unittest_overlay_2(void) static void __init of_unittest_overlay_2(void)
{ {
int ret; int ret;
...@@ -1421,7 +1417,7 @@ static void of_unittest_overlay_2(void) ...@@ -1421,7 +1417,7 @@ static void of_unittest_overlay_2(void)
} }
/* test deactivation of device */ /* test deactivation of device */
static void of_unittest_overlay_3(void) static void __init of_unittest_overlay_3(void)
{ {
int ret; int ret;
...@@ -1434,7 +1430,7 @@ static void of_unittest_overlay_3(void) ...@@ -1434,7 +1430,7 @@ static void of_unittest_overlay_3(void)
} }
/* test activation of a full device node */ /* test activation of a full device node */
static void of_unittest_overlay_4(void) static void __init of_unittest_overlay_4(void)
{ {
int ret; int ret;
...@@ -1447,7 +1443,7 @@ static void of_unittest_overlay_4(void) ...@@ -1447,7 +1443,7 @@ static void of_unittest_overlay_4(void)
} }
/* test overlay apply/revert sequence */ /* test overlay apply/revert sequence */
static void of_unittest_overlay_5(void) static void __init of_unittest_overlay_5(void)
{ {
int ret; int ret;
...@@ -1460,19 +1456,19 @@ static void of_unittest_overlay_5(void) ...@@ -1460,19 +1456,19 @@ static void of_unittest_overlay_5(void)
} }
/* test overlay application in sequence */ /* 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 ret, i, ov_id[2], ovcs_id;
int overlay_nr = 6, unittest_nr = 6; int overlay_nr = 6, unittest_nr = 6;
int before = 0, after = 1; int before = 0, after = 1;
const char *overlay_name;
/* unittest device must be in before state */ /* unittest device must be in before state */
for (i = 0; i < 2; i++) { for (i = 0; i < 2; i++) {
if (of_unittest_device_exists(unittest_nr + i, PDEV_OVERLAY) if (of_unittest_device_exists(unittest_nr + i, PDEV_OVERLAY)
!= before) { != before) {
unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n", unittest(0, "%s with device @\"%s\" %s\n",
overlay_path(overlay_nr + i), overlay_name_from_nr(overlay_nr + i),
unittest_path(unittest_nr + i, unittest_path(unittest_nr + i,
PDEV_OVERLAY), PDEV_OVERLAY),
!before ? "enabled" : "disabled"); !before ? "enabled" : "disabled");
...@@ -1483,18 +1479,12 @@ static void of_unittest_overlay_6(void) ...@@ -1483,18 +1479,12 @@ static void of_unittest_overlay_6(void)
/* apply the overlays */ /* apply the overlays */
for (i = 0; i < 2; i++) { for (i = 0; i < 2; i++) {
np = of_find_node_by_path(overlay_path(overlay_nr + i)); overlay_name = overlay_name_from_nr(overlay_nr + i);
if (np == NULL) {
unittest(0, "could not find overlay node @\"%s\"\n",
overlay_path(overlay_nr + i));
return;
}
ovcs_id = 0; ret = overlay_data_apply(overlay_name, &ovcs_id);
ret = of_overlay_apply(np, &ovcs_id); if (!ret) {
if (ret < 0) { unittest(0, "could not apply overlay \"%s\"\n",
unittest(0, "could not create overlay from \"%s\"\n", overlay_name);
overlay_path(overlay_nr + i));
return; return;
} }
ov_id[i] = ovcs_id; ov_id[i] = ovcs_id;
...@@ -1506,7 +1496,7 @@ static void of_unittest_overlay_6(void) ...@@ -1506,7 +1496,7 @@ static void of_unittest_overlay_6(void)
if (of_unittest_device_exists(unittest_nr + i, PDEV_OVERLAY) if (of_unittest_device_exists(unittest_nr + i, PDEV_OVERLAY)
!= after) { != after) {
unittest(0, "overlay @\"%s\" failed @\"%s\" %s\n", 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, unittest_path(unittest_nr + i,
PDEV_OVERLAY), PDEV_OVERLAY),
!after ? "enabled" : "disabled"); !after ? "enabled" : "disabled");
...@@ -1518,8 +1508,8 @@ static void of_unittest_overlay_6(void) ...@@ -1518,8 +1508,8 @@ static void of_unittest_overlay_6(void)
ovcs_id = ov_id[i]; ovcs_id = ov_id[i];
ret = of_overlay_remove(&ovcs_id); ret = of_overlay_remove(&ovcs_id);
if (ret != 0) { if (ret != 0) {
unittest(0, "overlay @\"%s\" failed destroy @\"%s\"\n", unittest(0, "%s failed destroy @\"%s\"\n",
overlay_path(overlay_nr + i), overlay_name_from_nr(overlay_nr + i),
unittest_path(unittest_nr + i, unittest_path(unittest_nr + i,
PDEV_OVERLAY)); PDEV_OVERLAY));
return; return;
...@@ -1531,8 +1521,8 @@ static void of_unittest_overlay_6(void) ...@@ -1531,8 +1521,8 @@ static void of_unittest_overlay_6(void)
/* unittest device must be again in before state */ /* unittest device must be again in before state */
if (of_unittest_device_exists(unittest_nr + i, PDEV_OVERLAY) if (of_unittest_device_exists(unittest_nr + i, PDEV_OVERLAY)
!= before) { != before) {
unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n", unittest(0, "%s with device @\"%s\" %s\n",
overlay_path(overlay_nr + i), overlay_name_from_nr(overlay_nr + i),
unittest_path(unittest_nr + i, unittest_path(unittest_nr + i,
PDEV_OVERLAY), PDEV_OVERLAY),
!before ? "enabled" : "disabled"); !before ? "enabled" : "disabled");
...@@ -1544,29 +1534,23 @@ static void of_unittest_overlay_6(void) ...@@ -1544,29 +1534,23 @@ static void of_unittest_overlay_6(void)
} }
/* test overlay application in sequence */ /* 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 ret, i, ov_id[2], ovcs_id;
int overlay_nr = 8, unittest_nr = 8; int overlay_nr = 8, unittest_nr = 8;
const char *overlay_name;
/* we don't care about device state in this test */ /* we don't care about device state in this test */
/* apply the overlays */ /* apply the overlays */
for (i = 0; i < 2; i++) { for (i = 0; i < 2; i++) {
np = of_find_node_by_path(overlay_path(overlay_nr + i)); overlay_name = overlay_name_from_nr(overlay_nr + i);
if (np == NULL) {
unittest(0, "could not find overlay node @\"%s\"\n",
overlay_path(overlay_nr + i));
return;
}
ovcs_id = 0; ret = overlay_data_apply(overlay_name, &ovcs_id);
ret = of_overlay_apply(np, &ovcs_id);
if (ret < 0) { if (ret < 0) {
unittest(0, "could not create overlay from \"%s\"\n", unittest(0, "could not apply overlay \"%s\"\n",
overlay_path(overlay_nr + i)); overlay_name);
return; return;
} }
ov_id[i] = ovcs_id; ov_id[i] = ovcs_id;
...@@ -1577,8 +1561,8 @@ static void of_unittest_overlay_8(void) ...@@ -1577,8 +1561,8 @@ static void of_unittest_overlay_8(void)
ovcs_id = ov_id[0]; ovcs_id = ov_id[0];
ret = of_overlay_remove(&ovcs_id); ret = of_overlay_remove(&ovcs_id);
if (ret == 0) { if (ret == 0) {
unittest(0, "overlay @\"%s\" was destroyed @\"%s\"\n", unittest(0, "%s was destroyed @\"%s\"\n",
overlay_path(overlay_nr + 0), overlay_name_from_nr(overlay_nr + 0),
unittest_path(unittest_nr, unittest_path(unittest_nr,
PDEV_OVERLAY)); PDEV_OVERLAY));
return; return;
...@@ -1589,8 +1573,8 @@ static void of_unittest_overlay_8(void) ...@@ -1589,8 +1573,8 @@ static void of_unittest_overlay_8(void)
ovcs_id = ov_id[i]; ovcs_id = ov_id[i];
ret = of_overlay_remove(&ovcs_id); ret = of_overlay_remove(&ovcs_id);
if (ret != 0) { if (ret != 0) {
unittest(0, "overlay @\"%s\" not destroyed @\"%s\"\n", unittest(0, "%s not destroyed @\"%s\"\n",
overlay_path(overlay_nr + i), overlay_name_from_nr(overlay_nr + i),
unittest_path(unittest_nr, unittest_path(unittest_nr,
PDEV_OVERLAY)); PDEV_OVERLAY));
return; return;
...@@ -1602,7 +1586,7 @@ static void of_unittest_overlay_8(void) ...@@ -1602,7 +1586,7 @@ static void of_unittest_overlay_8(void)
} }
/* test insertion of a bus with parent devices */ /* 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; int ret;
char *child_path; char *child_path;
...@@ -1625,7 +1609,7 @@ static void of_unittest_overlay_10(void) ...@@ -1625,7 +1609,7 @@ static void of_unittest_overlay_10(void)
} }
/* test insertion of a bus with parent devices (and revert) */ /* 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; int ret;
...@@ -1891,7 +1875,7 @@ static void of_unittest_overlay_i2c_cleanup(void) ...@@ -1891,7 +1875,7 @@ static void of_unittest_overlay_i2c_cleanup(void)
i2c_del_driver(&unittest_i2c_dev_driver); 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; int ret;
...@@ -1904,7 +1888,7 @@ static void of_unittest_overlay_i2c_12(void) ...@@ -1904,7 +1888,7 @@ static void of_unittest_overlay_i2c_12(void)
} }
/* test deactivation of device */ /* test deactivation of device */
static void of_unittest_overlay_i2c_13(void) static void __init of_unittest_overlay_i2c_13(void)
{ {
int ret; int ret;
...@@ -1921,7 +1905,7 @@ static void of_unittest_overlay_i2c_14(void) ...@@ -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; int ret;
...@@ -2023,23 +2007,38 @@ static inline void __init of_unittest_overlay(void) { } ...@@ -2023,23 +2007,38 @@ static inline void __init of_unittest_overlay(void) { }
extern uint8_t __dtb_##name##_begin[]; \ extern uint8_t __dtb_##name##_begin[]; \
extern uint8_t __dtb_##name##_end[] extern uint8_t __dtb_##name##_end[]
#define OVERLAY_INFO(name, expected) \ #define OVERLAY_INFO(overlay_name, expected) \
{ .dtb_begin = __dtb_##name##_begin, \ { .dtb_begin = __dtb_##overlay_name##_begin, \
.dtb_end = __dtb_##name##_end, \ .dtb_end = __dtb_##overlay_name##_end, \
.expected_result = expected, \ .expected_result = expected, \
.name = #overlay_name, \
} }
struct overlay_info { struct overlay_info {
uint8_t *dtb_begin; uint8_t *dtb_begin;
uint8_t *dtb_end; uint8_t *dtb_end;
void *data;
struct device_node *np_overlay;
int expected_result; int expected_result;
int overlay_id; int overlay_id;
char *name;
}; };
OVERLAY_INFO_EXTERN(overlay_base); OVERLAY_INFO_EXTERN(overlay_base);
OVERLAY_INFO_EXTERN(overlay); 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_phandle);
OVERLAY_INFO_EXTERN(overlay_bad_symbol); OVERLAY_INFO_EXTERN(overlay_bad_symbol);
...@@ -2047,6 +2046,21 @@ OVERLAY_INFO_EXTERN(overlay_bad_symbol); ...@@ -2047,6 +2046,21 @@ OVERLAY_INFO_EXTERN(overlay_bad_symbol);
static struct overlay_info overlays[] = { static struct overlay_info overlays[] = {
OVERLAY_INFO(overlay_base, -9999), OVERLAY_INFO(overlay_base, -9999),
OVERLAY_INFO(overlay, 0), 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_phandle, -EINVAL),
OVERLAY_INFO(overlay_bad_symbol, -EINVAL), OVERLAY_INFO(overlay_bad_symbol, -EINVAL),
{} {}
...@@ -2077,6 +2091,7 @@ void __init unittest_unflatten_overlay_base(void) ...@@ -2077,6 +2091,7 @@ void __init unittest_unflatten_overlay_base(void)
{ {
struct overlay_info *info; struct overlay_info *info;
u32 data_size; u32 data_size;
void *new_fdt;
u32 size; u32 size;
info = &overlays[0]; info = &overlays[0];
...@@ -2098,17 +2113,16 @@ void __init unittest_unflatten_overlay_base(void) ...@@ -2098,17 +2113,16 @@ void __init unittest_unflatten_overlay_base(void)
return; return;
} }
info->data = dt_alloc_memory(size, roundup_pow_of_two(FDT_V17_SIZE)); new_fdt = dt_alloc_memory(size, roundup_pow_of_two(FDT_V17_SIZE));
if (!info->data) { if (!new_fdt) {
pr_err("alloc for dtb 'overlay_base' failed"); pr_err("alloc for dtb 'overlay_base' failed");
return; 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); dt_alloc_memory, true);
overlay_base_root = info->np_overlay;
} }
/* /*
...@@ -2122,73 +2136,44 @@ void __init unittest_unflatten_overlay_base(void) ...@@ -2122,73 +2136,44 @@ void __init unittest_unflatten_overlay_base(void)
* *
* Return 0 on unexpected error. * 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; struct overlay_info *info;
int found = 0;
int k; int k;
int ret; int ret;
u32 size; u32 size;
u32 size_from_header;
for (k = 0, info = overlays; info; info++, k++) { for (k = 0, info = overlays; info && info->name; info++, k++) {
if (k == onum) if (!strcmp(overlay_name, info->name)) {
found = 1;
break; break;
} }
if (onum > k)
return 0;
size = info->dtb_end - info->dtb_begin;
if (!size) {
pr_err("no overlay to attach, %d\n", onum);
ret = 0;
} }
if (!found) {
size_from_header = fdt_totalsize(info->dtb_begin); pr_err("no overlay data for %s\n", overlay_name);
if (size_from_header != size) {
pr_err("overlay header totalsize != actual size, %d", onum);
return 0; return 0;
} }
/* size = info->dtb_end - info->dtb_begin;
* Must create permanent copy of FDT because of_fdt_unflatten_tree() if (!size) {
* will create pointers to the passed in FDT in the EDT. pr_err("no overlay data for %s\n", overlay_name);
*/
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; 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); 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; goto out;
out_free_np_overlay: pr_debug("%s applied\n", overlay_name);
/*
* info->np_overlay is the unflattened device tree
* It has not been spliced into the live tree.
*/
/* todo: function to free unflattened device tree */
out_free_data:
kfree(info->data);
out: 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); return (ret == info->expected_result);
} }
...@@ -2290,18 +2275,29 @@ static __init void of_unittest_overlay_high_level(void) ...@@ -2290,18 +2275,29 @@ static __init void of_unittest_overlay_high_level(void)
__of_attach_node_sysfs(np); __of_attach_node_sysfs(np);
if (of_symbols) { if (of_symbols) {
struct property *new_prop;
for_each_property_of_node(overlay_base_symbols, 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) { if (ret) {
unittest(0, if (!strcmp(new_prop->name, "name")) {
"duplicate property '%s' in overlay_base node __symbols__", /* auto-generated by unflatten */
ret = 0;
continue;
}
unittest(0, "duplicate property '%s' in overlay_base node __symbols__",
prop->name); prop->name);
goto err_unlock; goto err_unlock;
} }
ret = __of_add_property_sysfs(of_symbols, prop); ret = __of_add_property_sysfs(of_symbols, new_prop);
if (ret) { if (ret) {
unittest(0, unittest(0, "unable to add property '%s' in overlay_base node __symbols__ to sysfs",
"unable to add property '%s' in overlay_base node __symbols__ to sysfs",
prop->name); prop->name);
goto err_unlock; goto err_unlock;
} }
...@@ -2313,13 +2309,13 @@ static __init void of_unittest_overlay_high_level(void) ...@@ -2313,13 +2309,13 @@ static __init void of_unittest_overlay_high_level(void)
/* now do the normal overlay usage test */ /* now do the normal overlay usage test */
unittest(overlay_data_add(1), unittest(overlay_data_apply("overlay", NULL),
"Adding overlay 'overlay' failed\n"); "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"); "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"); "Adding overlay 'overlay_bad_symbol' failed\n");
return; return;
......
...@@ -1359,8 +1359,8 @@ struct of_overlay_notify_data { ...@@ -1359,8 +1359,8 @@ struct of_overlay_notify_data {
#ifdef CONFIG_OF_OVERLAY #ifdef CONFIG_OF_OVERLAY
/* ID based overlays; the API for external users */ int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size,
int of_overlay_apply(struct device_node *tree, int *ovcs_id); int *ovcs_id);
int of_overlay_remove(int *ovcs_id); int of_overlay_remove(int *ovcs_id);
int of_overlay_remove_all(void); int of_overlay_remove_all(void);
...@@ -1369,7 +1369,7 @@ int of_overlay_notifier_unregister(struct notifier_block *nb); ...@@ -1369,7 +1369,7 @@ int of_overlay_notifier_unregister(struct notifier_block *nb);
#else #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; return -ENOTSUPP;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment