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()hifive-unleashed-5.1
commit
2ec360bbc3
|
@ -0,0 +1,58 @@
|
|||
Renesas R-Car LVDS Encoder
|
||||
==========================
|
||||
|
||||
These DT bindings describe the LVDS encoder embedded in the Renesas R-Car
|
||||
Gen2, R-Car Gen3 and RZ/G SoCs.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : Shall contain one of
|
||||
- "renesas,r8a7743-lvds" for R8A7743 (RZ/G1M) compatible LVDS encoders
|
||||
- "renesas,r8a7790-lvds" for R8A7790 (R-Car H2) compatible LVDS encoders
|
||||
- "renesas,r8a7791-lvds" for R8A7791 (R-Car M2-W) compatible LVDS encoders
|
||||
- "renesas,r8a7793-lvds" for R8A7793 (R-Car M2-N) compatible LVDS encoders
|
||||
- "renesas,r8a7795-lvds" for R8A7795 (R-Car H3) compatible LVDS encoders
|
||||
- "renesas,r8a7796-lvds" for R8A7796 (R-Car M3-W) compatible LVDS encoders
|
||||
- "renesas,r8a77970-lvds" for R8A77970 (R-Car V3M) compatible LVDS encoders
|
||||
- "renesas,r8a77995-lvds" for R8A77995 (R-Car D3) compatible LVDS encoders
|
||||
|
||||
- reg: Base address and length for the memory-mapped registers
|
||||
- clocks: A phandle + clock-specifier pair for the functional clock
|
||||
- resets: A phandle + reset specifier for the module reset
|
||||
|
||||
Required nodes:
|
||||
|
||||
The LVDS encoder has two video ports. Their connections are modelled using the
|
||||
OF graph bindings specified in Documentation/devicetree/bindings/graph.txt.
|
||||
|
||||
- Video port 0 corresponds to the parallel RGB input
|
||||
- Video port 1 corresponds to the LVDS output
|
||||
|
||||
Each port shall have a single endpoint.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
lvds0: lvds@feb90000 {
|
||||
compatible = "renesas,r8a7790-lvds";
|
||||
reg = <0 0xfeb90000 0 0x1c>;
|
||||
clocks = <&cpg CPG_MOD 726>;
|
||||
resets = <&cpg 726>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
lvds0_in: endpoint {
|
||||
remote-endpoint = <&du_out_lvds0>;
|
||||
};
|
||||
};
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
lvds0_out: endpoint {
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -13,13 +13,10 @@ Required Properties:
|
|||
- "renesas,du-r8a7794" for R8A7794 (R-Car E2) compatible DU
|
||||
- "renesas,du-r8a7795" for R8A7795 (R-Car H3) compatible DU
|
||||
- "renesas,du-r8a7796" for R8A7796 (R-Car M3-W) compatible DU
|
||||
- "renesas,du-r8a77970" for R8A77970 (R-Car V3M) compatible DU
|
||||
- "renesas,du-r8a77995" for R8A77995 (R-Car D3) compatible DU
|
||||
|
||||
- reg: A list of base address and length of each memory resource, one for
|
||||
each entry in the reg-names property.
|
||||
- reg-names: Name of the memory resources. The DU requires one memory
|
||||
resource for the DU core (named "du") and one memory resource for each
|
||||
LVDS encoder (named "lvds.x" with "x" being the LVDS controller numerical
|
||||
index).
|
||||
- reg: the memory-mapped I/O registers base address and length
|
||||
|
||||
- interrupt-parent: phandle of the parent interrupt controller.
|
||||
- interrupts: Interrupt specifiers for the DU interrupts.
|
||||
|
@ -29,14 +26,13 @@ Required Properties:
|
|||
- clock-names: Name of the clocks. This property is model-dependent.
|
||||
- R8A7779 uses a single functional clock. The clock doesn't need to be
|
||||
named.
|
||||
- All other DU instances use one functional clock per channel and one
|
||||
clock per LVDS encoder (if available). The functional clocks must be
|
||||
named "du.x" with "x" being the channel numerical index. The LVDS clocks
|
||||
must be named "lvds.x" with "x" being the LVDS encoder numerical index.
|
||||
- In addition to the functional and encoder clocks, all DU versions also
|
||||
support externally supplied pixel clocks. Those clocks are optional.
|
||||
When supplied they must be named "dclkin.x" with "x" being the input
|
||||
clock numerical index.
|
||||
- All other DU instances use one functional clock per channel The
|
||||
functional clocks must be named "du.x" with "x" being the channel
|
||||
numerical index.
|
||||
- In addition to the functional clocks, all DU versions also support
|
||||
externally supplied pixel clocks. Those clocks are optional. When
|
||||
supplied they must be named "dclkin.x" with "x" being the input clock
|
||||
numerical index.
|
||||
|
||||
- vsps: A list of phandle and channel index tuples to the VSPs that handle
|
||||
the memory interfaces for the DU channels. The phandle identifies the VSP
|
||||
|
@ -63,15 +59,15 @@ corresponding to each DU output.
|
|||
R8A7794 (R-Car E2) DPAD 0 DPAD 1 - -
|
||||
R8A7795 (R-Car H3) DPAD 0 HDMI 0 HDMI 1 LVDS 0
|
||||
R8A7796 (R-Car M3-W) DPAD 0 HDMI 0 LVDS 0 -
|
||||
R8A77970 (R-Car V3M) DPAD 0 LVDS 0 - -
|
||||
R8A77995 (R-Car D3) DPAD 0 LVDS 0 LVDS 1 -
|
||||
|
||||
|
||||
Example: R8A7795 (R-Car H3) ES2.0 DU
|
||||
|
||||
du: display@feb00000 {
|
||||
compatible = "renesas,du-r8a7795";
|
||||
reg = <0 0xfeb00000 0 0x80000>,
|
||||
<0 0xfeb90000 0 0x14>;
|
||||
reg-names = "du", "lvds.0";
|
||||
reg = <0 0xfeb00000 0 0x80000>;
|
||||
interrupts = <GIC_SPI 256 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 268 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 269 IRQ_TYPE_LEVEL_HIGH>,
|
||||
|
@ -79,9 +75,8 @@ Example: R8A7795 (R-Car H3) ES2.0 DU
|
|||
clocks = <&cpg CPG_MOD 724>,
|
||||
<&cpg CPG_MOD 723>,
|
||||
<&cpg CPG_MOD 722>,
|
||||
<&cpg CPG_MOD 721>,
|
||||
<&cpg CPG_MOD 727>;
|
||||
clock-names = "du.0", "du.1", "du.2", "du.3", "lvds.0";
|
||||
<&cpg CPG_MOD 721>;
|
||||
clock-names = "du.0", "du.1", "du.2", "du.3";
|
||||
vsps = <&vspd0 0>, <&vspd1 0>, <&vspd2 0>, <&vspd0 1>;
|
||||
|
||||
ports {
|
||||
|
|
|
@ -87,8 +87,8 @@ Overlay in-kernel API
|
|||
|
||||
The API is quite easy to use.
|
||||
|
||||
1. Call of_overlay_apply() to create and apply an overlay changeset. The return
|
||||
value is an error or a cookie identifying this overlay.
|
||||
1. Call of_overlay_fdt_apply() to create and apply an overlay changeset. The
|
||||
return value is an error or a cookie identifying this overlay.
|
||||
|
||||
2. Call of_overlay_remove() to remove and cleanup the overlay changeset
|
||||
previously created via the call to of_overlay_apply(). Removal of an overlay
|
||||
|
|
|
@ -4744,6 +4744,7 @@ F: drivers/gpu/drm/rcar-du/
|
|||
F: drivers/gpu/drm/shmobile/
|
||||
F: include/linux/platform_data/shmob_drm.h
|
||||
F: Documentation/devicetree/bindings/display/bridge/renesas,dw-hdmi.txt
|
||||
F: Documentation/devicetree/bindings/display/bridge/renesas,lvds.txt
|
||||
F: Documentation/devicetree/bindings/display/renesas,du.txt
|
||||
|
||||
DRM DRIVERS FOR ROCKCHIP
|
||||
|
|
|
@ -259,7 +259,7 @@ static void __init dtb_apic_setup(void)
|
|||
dtb_ioapic_setup();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF_FLATTREE
|
||||
#ifdef CONFIG_OF_EARLY_FLATTREE
|
||||
static void __init x86_flattree_get_config(void)
|
||||
{
|
||||
u32 size, map_len;
|
||||
|
|
|
@ -19,9 +19,11 @@ config DRM_RCAR_DW_HDMI
|
|||
Enable support for R-Car Gen3 internal HDMI encoder.
|
||||
|
||||
config DRM_RCAR_LVDS
|
||||
bool "R-Car DU LVDS Encoder Support"
|
||||
depends on DRM_RCAR_DU
|
||||
tristate "R-Car DU LVDS Encoder Support"
|
||||
depends on DRM && DRM_BRIDGE && OF
|
||||
select DRM_PANEL
|
||||
select OF_FLATTREE
|
||||
select OF_OVERLAY
|
||||
help
|
||||
Enable support for the R-Car Display Unit embedded LVDS encoders.
|
||||
|
||||
|
|
|
@ -4,12 +4,16 @@ rcar-du-drm-y := rcar_du_crtc.o \
|
|||
rcar_du_encoder.o \
|
||||
rcar_du_group.o \
|
||||
rcar_du_kms.o \
|
||||
rcar_du_lvdscon.o \
|
||||
rcar_du_plane.o
|
||||
|
||||
rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.o
|
||||
|
||||
rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_of.o \
|
||||
rcar_du_of_lvds_r8a7790.dtb.o \
|
||||
rcar_du_of_lvds_r8a7791.dtb.o \
|
||||
rcar_du_of_lvds_r8a7793.dtb.o \
|
||||
rcar_du_of_lvds_r8a7795.dtb.o \
|
||||
rcar_du_of_lvds_r8a7796.dtb.o
|
||||
rcar-du-drm-$(CONFIG_DRM_RCAR_VSP) += rcar_du_vsp.o
|
||||
|
||||
obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o
|
||||
obj-$(CONFIG_DRM_RCAR_DW_HDMI) += rcar_dw_hdmi.o
|
||||
obj-$(CONFIG_DRM_RCAR_LVDS) += rcar_lvds.o
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
#include "rcar_du_drv.h"
|
||||
#include "rcar_du_kms.h"
|
||||
#include "rcar_du_of.h"
|
||||
#include "rcar_du_regs.h"
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
|
@ -74,7 +75,6 @@ static const struct rcar_du_device_info rzg1_du_r8a7745_info = {
|
|||
.port = 1,
|
||||
},
|
||||
},
|
||||
.num_lvds = 0,
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a7779_info = {
|
||||
|
@ -95,14 +95,13 @@ static const struct rcar_du_device_info rcar_du_r8a7779_info = {
|
|||
.port = 1,
|
||||
},
|
||||
},
|
||||
.num_lvds = 0,
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a7790_info = {
|
||||
.gen = 2,
|
||||
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
|
||||
| RCAR_DU_FEATURE_EXT_CTRL_REGS,
|
||||
.quirks = RCAR_DU_QUIRK_ALIGN_128B | RCAR_DU_QUIRK_LVDS_LANES,
|
||||
.quirks = RCAR_DU_QUIRK_ALIGN_128B,
|
||||
.num_crtcs = 3,
|
||||
.routes = {
|
||||
/*
|
||||
|
@ -164,7 +163,6 @@ static const struct rcar_du_device_info rcar_du_r8a7792_info = {
|
|||
.port = 1,
|
||||
},
|
||||
},
|
||||
.num_lvds = 0,
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a7794_info = {
|
||||
|
@ -186,7 +184,6 @@ static const struct rcar_du_device_info rcar_du_r8a7794_info = {
|
|||
.port = 1,
|
||||
},
|
||||
},
|
||||
.num_lvds = 0,
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a7795_info = {
|
||||
|
@ -249,6 +246,26 @@ static const struct rcar_du_device_info rcar_du_r8a7796_info = {
|
|||
.dpll_ch = BIT(1),
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a77970_info = {
|
||||
.gen = 3,
|
||||
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
|
||||
| RCAR_DU_FEATURE_EXT_CTRL_REGS
|
||||
| RCAR_DU_FEATURE_VSP1_SOURCE,
|
||||
.num_crtcs = 1,
|
||||
.routes = {
|
||||
/* R8A77970 has one RGB output and one LVDS output. */
|
||||
[RCAR_DU_OUTPUT_DPAD0] = {
|
||||
.possible_crtcs = BIT(0),
|
||||
.port = 0,
|
||||
},
|
||||
[RCAR_DU_OUTPUT_LVDS0] = {
|
||||
.possible_crtcs = BIT(0),
|
||||
.port = 1,
|
||||
},
|
||||
},
|
||||
.num_lvds = 1,
|
||||
};
|
||||
|
||||
static const struct of_device_id rcar_du_of_table[] = {
|
||||
{ .compatible = "renesas,du-r8a7743", .data = &rzg1_du_r8a7743_info },
|
||||
{ .compatible = "renesas,du-r8a7745", .data = &rzg1_du_r8a7745_info },
|
||||
|
@ -260,6 +277,7 @@ static const struct of_device_id rcar_du_of_table[] = {
|
|||
{ .compatible = "renesas,du-r8a7794", .data = &rcar_du_r8a7794_info },
|
||||
{ .compatible = "renesas,du-r8a7795", .data = &rcar_du_r8a7795_info },
|
||||
{ .compatible = "renesas,du-r8a7796", .data = &rcar_du_r8a7796_info },
|
||||
{ .compatible = "renesas,du-r8a77970", .data = &rcar_du_r8a77970_info },
|
||||
{ }
|
||||
};
|
||||
|
||||
|
@ -434,7 +452,19 @@ static struct platform_driver rcar_du_platform_driver = {
|
|||
},
|
||||
};
|
||||
|
||||
module_platform_driver(rcar_du_platform_driver);
|
||||
static int __init rcar_du_init(void)
|
||||
{
|
||||
rcar_du_of_init(rcar_du_of_table);
|
||||
|
||||
return platform_driver_register(&rcar_du_platform_driver);
|
||||
}
|
||||
module_init(rcar_du_init);
|
||||
|
||||
static void __exit rcar_du_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&rcar_du_platform_driver);
|
||||
}
|
||||
module_exit(rcar_du_exit);
|
||||
|
||||
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
|
||||
MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver");
|
||||
|
|
|
@ -26,14 +26,12 @@ struct device;
|
|||
struct drm_device;
|
||||
struct drm_fbdev_cma;
|
||||
struct rcar_du_device;
|
||||
struct rcar_du_lvdsenc;
|
||||
|
||||
#define RCAR_DU_FEATURE_CRTC_IRQ_CLOCK (1 << 0) /* Per-CRTC IRQ and clock */
|
||||
#define RCAR_DU_FEATURE_EXT_CTRL_REGS (1 << 1) /* Has extended control registers */
|
||||
#define RCAR_DU_FEATURE_VSP1_SOURCE (1 << 2) /* Has inputs from VSP1 */
|
||||
|
||||
#define RCAR_DU_QUIRK_ALIGN_128B (1 << 0) /* Align pitches to 128 bytes */
|
||||
#define RCAR_DU_QUIRK_LVDS_LANES (1 << 1) /* LVDS lanes 1 and 3 inverted */
|
||||
|
||||
/*
|
||||
* struct rcar_du_output_routing - Output routing specification
|
||||
|
@ -70,7 +68,6 @@ struct rcar_du_device_info {
|
|||
|
||||
#define RCAR_DU_MAX_CRTCS 4
|
||||
#define RCAR_DU_MAX_GROUPS DIV_ROUND_UP(RCAR_DU_MAX_CRTCS, 2)
|
||||
#define RCAR_DU_MAX_LVDS 2
|
||||
#define RCAR_DU_MAX_VSPS 4
|
||||
|
||||
struct rcar_du_device {
|
||||
|
@ -96,8 +93,6 @@ struct rcar_du_device {
|
|||
|
||||
unsigned int dpad0_source;
|
||||
unsigned int vspd1_sink;
|
||||
|
||||
struct rcar_du_lvdsenc *lvds[RCAR_DU_MAX_LVDS];
|
||||
};
|
||||
|
||||
static inline bool rcar_du_has(struct rcar_du_device *rcdu,
|
||||
|
|
|
@ -21,134 +21,22 @@
|
|||
#include "rcar_du_drv.h"
|
||||
#include "rcar_du_encoder.h"
|
||||
#include "rcar_du_kms.h"
|
||||
#include "rcar_du_lvdscon.h"
|
||||
#include "rcar_du_lvdsenc.h"
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Encoder
|
||||
*/
|
||||
|
||||
static void rcar_du_encoder_disable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
|
||||
|
||||
if (renc->connector && renc->connector->panel) {
|
||||
drm_panel_disable(renc->connector->panel);
|
||||
drm_panel_unprepare(renc->connector->panel);
|
||||
}
|
||||
|
||||
if (renc->lvds)
|
||||
rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, false);
|
||||
}
|
||||
|
||||
static void rcar_du_encoder_enable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
|
||||
|
||||
if (renc->lvds)
|
||||
rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, true);
|
||||
|
||||
if (renc->connector && renc->connector->panel) {
|
||||
drm_panel_prepare(renc->connector->panel);
|
||||
drm_panel_enable(renc->connector->panel);
|
||||
}
|
||||
}
|
||||
|
||||
static int rcar_du_encoder_atomic_check(struct drm_encoder *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
|
||||
struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
|
||||
const struct drm_display_mode *mode = &crtc_state->mode;
|
||||
struct drm_connector *connector = conn_state->connector;
|
||||
struct drm_device *dev = encoder->dev;
|
||||
|
||||
/*
|
||||
* Only panel-related encoder types require validation here, everything
|
||||
* else is handled by the bridge drivers.
|
||||
*/
|
||||
if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) {
|
||||
const struct drm_display_mode *panel_mode;
|
||||
|
||||
if (list_empty(&connector->modes)) {
|
||||
dev_dbg(dev->dev, "encoder: empty modes list\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
panel_mode = list_first_entry(&connector->modes,
|
||||
struct drm_display_mode, head);
|
||||
|
||||
/* We're not allowed to modify the resolution. */
|
||||
if (mode->hdisplay != panel_mode->hdisplay ||
|
||||
mode->vdisplay != panel_mode->vdisplay)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* The flat panel mode is fixed, just copy it to the adjusted
|
||||
* mode.
|
||||
*/
|
||||
drm_mode_copy(adjusted_mode, panel_mode);
|
||||
}
|
||||
|
||||
if (renc->lvds)
|
||||
rcar_du_lvdsenc_atomic_check(renc->lvds, adjusted_mode);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rcar_du_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
|
||||
struct drm_display_info *info = &conn_state->connector->display_info;
|
||||
enum rcar_lvds_mode mode;
|
||||
|
||||
rcar_du_crtc_route_output(crtc_state->crtc, renc->output);
|
||||
|
||||
if (!renc->lvds) {
|
||||
/*
|
||||
* The DU driver creates connectors only for the outputs of the
|
||||
* internal LVDS encoders.
|
||||
*/
|
||||
renc->connector = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
renc->connector = to_rcar_connector(conn_state->connector);
|
||||
|
||||
if (!info->num_bus_formats || !info->bus_formats) {
|
||||
dev_err(encoder->dev->dev, "no LVDS bus format reported\n");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (info->bus_formats[0]) {
|
||||
case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
|
||||
case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
|
||||
mode = RCAR_LVDS_MODE_JEIDA;
|
||||
break;
|
||||
case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
|
||||
mode = RCAR_LVDS_MODE_VESA;
|
||||
break;
|
||||
default:
|
||||
dev_err(encoder->dev->dev,
|
||||
"unsupported LVDS bus format 0x%04x\n",
|
||||
info->bus_formats[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->bus_flags & DRM_BUS_FLAG_DATA_LSB_TO_MSB)
|
||||
mode |= RCAR_LVDS_MODE_MIRROR;
|
||||
|
||||
rcar_du_lvdsenc_set_mode(renc->lvds, mode);
|
||||
}
|
||||
|
||||
static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
|
||||
.atomic_mode_set = rcar_du_encoder_mode_set,
|
||||
.disable = rcar_du_encoder_disable,
|
||||
.enable = rcar_du_encoder_enable,
|
||||
.atomic_check = rcar_du_encoder_atomic_check,
|
||||
};
|
||||
|
||||
static const struct drm_encoder_funcs encoder_funcs = {
|
||||
|
@ -172,33 +60,14 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
|
|||
renc->output = output;
|
||||
encoder = rcar_encoder_to_drm_encoder(renc);
|
||||
|
||||
switch (output) {
|
||||
case RCAR_DU_OUTPUT_LVDS0:
|
||||
renc->lvds = rcdu->lvds[0];
|
||||
break;
|
||||
dev_dbg(rcdu->dev, "initializing encoder %pOF for output %u\n",
|
||||
enc_node, output);
|
||||
|
||||
case RCAR_DU_OUTPUT_LVDS1:
|
||||
renc->lvds = rcdu->lvds[1];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (enc_node) {
|
||||
dev_dbg(rcdu->dev, "initializing encoder %pOF for output %u\n",
|
||||
enc_node, output);
|
||||
|
||||
/* Locate the DRM bridge from the encoder DT node. */
|
||||
bridge = of_drm_find_bridge(enc_node);
|
||||
if (!bridge) {
|
||||
ret = -EPROBE_DEFER;
|
||||
goto done;
|
||||
}
|
||||
} else {
|
||||
dev_dbg(rcdu->dev,
|
||||
"initializing internal encoder for output %u\n",
|
||||
output);
|
||||
/* Locate the DRM bridge from the encoder DT node. */
|
||||
bridge = of_drm_find_bridge(enc_node);
|
||||
if (!bridge) {
|
||||
ret = -EPROBE_DEFER;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs,
|
||||
|
@ -208,28 +77,14 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
|
|||
|
||||
drm_encoder_helper_add(encoder, &encoder_helper_funcs);
|
||||
|
||||
if (bridge) {
|
||||
/*
|
||||
* Attach the bridge to the encoder. The bridge will create the
|
||||
* connector.
|
||||
*/
|
||||
ret = drm_bridge_attach(encoder, bridge, NULL);
|
||||
if (ret) {
|
||||
drm_encoder_cleanup(encoder);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
/* There's no bridge, create the connector manually. */
|
||||
switch (output) {
|
||||
case RCAR_DU_OUTPUT_LVDS0:
|
||||
case RCAR_DU_OUTPUT_LVDS1:
|
||||
ret = rcar_du_lvds_connector_init(rcdu, renc, con_node);
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* Attach the bridge to the encoder. The bridge will create the
|
||||
* connector.
|
||||
*/
|
||||
ret = drm_bridge_attach(encoder, bridge, NULL);
|
||||
if (ret) {
|
||||
drm_encoder_cleanup(encoder);
|
||||
return ret;
|
||||
}
|
||||
|
||||
done:
|
||||
|
|
|
@ -19,13 +19,10 @@
|
|||
|
||||
struct drm_panel;
|
||||
struct rcar_du_device;
|
||||
struct rcar_du_lvdsenc;
|
||||
|
||||
struct rcar_du_encoder {
|
||||
struct drm_encoder base;
|
||||
enum rcar_du_output output;
|
||||
struct rcar_du_connector *connector;
|
||||
struct rcar_du_lvdsenc *lvds;
|
||||
};
|
||||
|
||||
#define to_rcar_encoder(e) \
|
||||
|
@ -33,15 +30,6 @@ struct rcar_du_encoder {
|
|||
|
||||
#define rcar_encoder_to_drm_encoder(e) (&(e)->base)
|
||||
|
||||
struct rcar_du_connector {
|
||||
struct drm_connector connector;
|
||||
struct rcar_du_encoder *encoder;
|
||||
struct drm_panel *panel;
|
||||
};
|
||||
|
||||
#define to_rcar_connector(c) \
|
||||
container_of(c, struct rcar_du_connector, connector)
|
||||
|
||||
int rcar_du_encoder_init(struct rcar_du_device *rcdu,
|
||||
enum rcar_du_output output,
|
||||
struct device_node *enc_node,
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
#include "rcar_du_drv.h"
|
||||
#include "rcar_du_encoder.h"
|
||||
#include "rcar_du_kms.h"
|
||||
#include "rcar_du_lvdsenc.h"
|
||||
#include "rcar_du_regs.h"
|
||||
#include "rcar_du_vsp.h"
|
||||
|
||||
|
@ -341,11 +340,10 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
|
|||
of_node_put(entity_ep_node);
|
||||
|
||||
if (!encoder) {
|
||||
/*
|
||||
* If no encoder has been found the entity must be the
|
||||
* connector.
|
||||
*/
|
||||
connector = entity;
|
||||
dev_warn(rcdu->dev,
|
||||
"no encoder found for endpoint %pOF, skipping\n",
|
||||
ep->local_node);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = rcar_du_encoder_init(rcdu, output, encoder, connector);
|
||||
|
@ -595,10 +593,6 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
|
|||
}
|
||||
|
||||
/* Initialize the encoders. */
|
||||
ret = rcar_du_lvdsenc_init(rcdu);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = rcar_du_encoders_init(rcdu);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
* rcar_du_lvdscon.c -- R-Car Display Unit LVDS Connector
|
||||
*
|
||||
* Copyright (C) 2013-2014 Renesas Electronics Corporation
|
||||
*
|
||||
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
#include <video/display_timing.h>
|
||||
#include <video/of_display_timing.h>
|
||||
#include <video/videomode.h>
|
||||
|
||||
#include "rcar_du_drv.h"
|
||||
#include "rcar_du_encoder.h"
|
||||
#include "rcar_du_kms.h"
|
||||
#include "rcar_du_lvdscon.h"
|
||||
|
||||
static int rcar_du_lvds_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct rcar_du_connector *rcon = to_rcar_connector(connector);
|
||||
|
||||
return drm_panel_get_modes(rcon->panel);
|
||||
}
|
||||
|
||||
static const struct drm_connector_helper_funcs connector_helper_funcs = {
|
||||
.get_modes = rcar_du_lvds_connector_get_modes,
|
||||
};
|
||||
|
||||
static void rcar_du_lvds_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
struct rcar_du_connector *rcon = to_rcar_connector(connector);
|
||||
|
||||
drm_panel_detach(rcon->panel);
|
||||
drm_connector_cleanup(connector);
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs connector_funcs = {
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = rcar_du_lvds_connector_destroy,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
|
||||
struct rcar_du_encoder *renc,
|
||||
const struct device_node *np)
|
||||
{
|
||||
struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc);
|
||||
struct rcar_du_connector *rcon;
|
||||
struct drm_connector *connector;
|
||||
int ret;
|
||||
|
||||
rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL);
|
||||
if (rcon == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
connector = &rcon->connector;
|
||||
|
||||
rcon->panel = of_drm_find_panel(np);
|
||||
if (!rcon->panel)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs,
|
||||
DRM_MODE_CONNECTOR_LVDS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
drm_connector_helper_add(connector, &connector_helper_funcs);
|
||||
|
||||
ret = drm_mode_connector_attach_encoder(connector, encoder);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = drm_panel_attach(rcon->panel, connector);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
rcon->encoder = renc;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* rcar_du_lvdscon.h -- R-Car Display Unit LVDS Connector
|
||||
*
|
||||
* Copyright (C) 2013-2014 Renesas Electronics Corporation
|
||||
*
|
||||
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef __RCAR_DU_LVDSCON_H__
|
||||
#define __RCAR_DU_LVDSCON_H__
|
||||
|
||||
struct rcar_du_device;
|
||||
struct rcar_du_encoder;
|
||||
|
||||
int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
|
||||
struct rcar_du_encoder *renc,
|
||||
const struct device_node *np);
|
||||
|
||||
#endif /* __RCAR_DU_LVDSCON_H__ */
|
|
@ -1,238 +0,0 @@
|
|||
/*
|
||||
* rcar_du_lvdsenc.c -- R-Car Display Unit LVDS Encoder
|
||||
*
|
||||
* Copyright (C) 2013-2014 Renesas Electronics Corporation
|
||||
*
|
||||
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "rcar_du_drv.h"
|
||||
#include "rcar_du_encoder.h"
|
||||
#include "rcar_du_lvdsenc.h"
|
||||
#include "rcar_lvds_regs.h"
|
||||
|
||||
struct rcar_du_lvdsenc {
|
||||
struct rcar_du_device *dev;
|
||||
|
||||
unsigned int index;
|
||||
void __iomem *mmio;
|
||||
struct clk *clock;
|
||||
bool enabled;
|
||||
|
||||
enum rcar_lvds_input input;
|
||||
enum rcar_lvds_mode mode;
|
||||
};
|
||||
|
||||
static void rcar_lvds_write(struct rcar_du_lvdsenc *lvds, u32 reg, u32 data)
|
||||
{
|
||||
iowrite32(data, lvds->mmio + reg);
|
||||
}
|
||||
|
||||
static u32 rcar_lvds_lvdpllcr_gen2(unsigned int freq)
|
||||
{
|
||||
if (freq < 39000)
|
||||
return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
|
||||
else if (freq < 61000)
|
||||
return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
|
||||
else if (freq < 121000)
|
||||
return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
|
||||
else
|
||||
return LVDPLLCR_PLLDLYCNT_150M;
|
||||
}
|
||||
|
||||
static u32 rcar_lvds_lvdpllcr_gen3(unsigned int freq)
|
||||
{
|
||||
if (freq < 42000)
|
||||
return LVDPLLCR_PLLDIVCNT_42M;
|
||||
else if (freq < 85000)
|
||||
return LVDPLLCR_PLLDIVCNT_85M;
|
||||
else if (freq < 128000)
|
||||
return LVDPLLCR_PLLDIVCNT_128M;
|
||||
else
|
||||
return LVDPLLCR_PLLDIVCNT_148M;
|
||||
}
|
||||
|
||||
static int rcar_du_lvdsenc_start(struct rcar_du_lvdsenc *lvds,
|
||||
struct rcar_du_crtc *rcrtc)
|
||||
{
|
||||
const struct drm_display_mode *mode = &rcrtc->crtc.mode;
|
||||
u32 lvdpllcr;
|
||||
u32 lvdhcr;
|
||||
u32 lvdcr0;
|
||||
int ret;
|
||||
|
||||
if (lvds->enabled)
|
||||
return 0;
|
||||
|
||||
ret = clk_prepare_enable(lvds->clock);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Hardcode the channels and control signals routing for now.
|
||||
*
|
||||
* HSYNC -> CTRL0
|
||||
* VSYNC -> CTRL1
|
||||
* DISP -> CTRL2
|
||||
* 0 -> CTRL3
|
||||
*/
|
||||
rcar_lvds_write(lvds, LVDCTRCR, LVDCTRCR_CTR3SEL_ZERO |
|
||||
LVDCTRCR_CTR2SEL_DISP | LVDCTRCR_CTR1SEL_VSYNC |
|
||||
LVDCTRCR_CTR0SEL_HSYNC);
|
||||
|
||||
if (rcar_du_needs(lvds->dev, RCAR_DU_QUIRK_LVDS_LANES))
|
||||
lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 3)
|
||||
| LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 1);
|
||||
else
|
||||
lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 1)
|
||||
| LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 3);
|
||||
|
||||
rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
|
||||
|
||||
/* PLL clock configuration. */
|
||||
if (lvds->dev->info->gen < 3)
|
||||
lvdpllcr = rcar_lvds_lvdpllcr_gen2(mode->clock);
|
||||
else
|
||||
lvdpllcr = rcar_lvds_lvdpllcr_gen3(mode->clock);
|
||||
rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
|
||||
|
||||
/* Set the LVDS mode and select the input. */
|
||||
lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
|
||||
if (rcrtc->index == 2)
|
||||
lvdcr0 |= LVDCR0_DUSEL;
|
||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
|
||||
/* Turn all the channels on. */
|
||||
rcar_lvds_write(lvds, LVDCR1,
|
||||
LVDCR1_CHSTBY(3) | LVDCR1_CHSTBY(2) |
|
||||
LVDCR1_CHSTBY(1) | LVDCR1_CHSTBY(0) | LVDCR1_CLKSTBY);
|
||||
|
||||
if (lvds->dev->info->gen < 3) {
|
||||
/* Enable LVDS operation and turn the bias circuitry on. */
|
||||
lvdcr0 |= LVDCR0_BEN | LVDCR0_LVEN;
|
||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
}
|
||||
|
||||
/* Turn the PLL on. */
|
||||
lvdcr0 |= LVDCR0_PLLON;
|
||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
|
||||
if (lvds->dev->info->gen > 2) {
|
||||
/* Set LVDS normal mode. */
|
||||
lvdcr0 |= LVDCR0_PWD;
|
||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
}
|
||||
|
||||
/* Wait for the startup delay. */
|
||||
usleep_range(100, 150);
|
||||
|
||||
/* Turn the output on. */
|
||||
lvdcr0 |= LVDCR0_LVRES;
|
||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
|
||||
lvds->enabled = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rcar_du_lvdsenc_stop(struct rcar_du_lvdsenc *lvds)
|
||||
{
|
||||
if (!lvds->enabled)
|
||||
return;
|
||||
|
||||
rcar_lvds_write(lvds, LVDCR0, 0);
|
||||
rcar_lvds_write(lvds, LVDCR1, 0);
|
||||
|
||||
clk_disable_unprepare(lvds->clock);
|
||||
|
||||
lvds->enabled = false;
|
||||
}
|
||||
|
||||
int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, struct drm_crtc *crtc,
|
||||
bool enable)
|
||||
{
|
||||
if (!enable) {
|
||||
rcar_du_lvdsenc_stop(lvds);
|
||||
return 0;
|
||||
} else if (crtc) {
|
||||
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
|
||||
return rcar_du_lvdsenc_start(lvds, rcrtc);
|
||||
} else
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
/*
|
||||
* The internal LVDS encoder has a restricted clock frequency operating
|
||||
* range (31MHz to 148.5MHz). Clamp the clock accordingly.
|
||||
*/
|
||||
mode->clock = clamp(mode->clock, 31000, 148500);
|
||||
}
|
||||
|
||||
void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
|
||||
enum rcar_lvds_mode mode)
|
||||
{
|
||||
lvds->mode = mode;
|
||||
}
|
||||
|
||||
static int rcar_du_lvdsenc_get_resources(struct rcar_du_lvdsenc *lvds,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
struct resource *mem;
|
||||
char name[7];
|
||||
|
||||
sprintf(name, "lvds.%u", lvds->index);
|
||||
|
||||
mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
|
||||
lvds->mmio = devm_ioremap_resource(&pdev->dev, mem);
|
||||
if (IS_ERR(lvds->mmio))
|
||||
return PTR_ERR(lvds->mmio);
|
||||
|
||||
lvds->clock = devm_clk_get(&pdev->dev, name);
|
||||
if (IS_ERR(lvds->clock)) {
|
||||
dev_err(&pdev->dev, "failed to get clock for %s\n", name);
|
||||
return PTR_ERR(lvds->clock);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(rcdu->dev);
|
||||
struct rcar_du_lvdsenc *lvds;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
for (i = 0; i < rcdu->info->num_lvds; ++i) {
|
||||
lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
|
||||
if (lvds == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
lvds->dev = rcdu;
|
||||
lvds->index = i;
|
||||
lvds->input = i ? RCAR_LVDS_INPUT_DU1 : RCAR_LVDS_INPUT_DU0;
|
||||
lvds->enabled = false;
|
||||
|
||||
ret = rcar_du_lvdsenc_get_resources(lvds, pdev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
rcdu->lvds[i] = lvds;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* rcar_du_lvdsenc.h -- R-Car Display Unit LVDS Encoder
|
||||
*
|
||||
* Copyright (C) 2013-2014 Renesas Electronics Corporation
|
||||
*
|
||||
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef __RCAR_DU_LVDSENC_H__
|
||||
#define __RCAR_DU_LVDSENC_H__
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
struct rcar_drm_crtc;
|
||||
struct rcar_du_lvdsenc;
|
||||
|
||||
enum rcar_lvds_input {
|
||||
RCAR_LVDS_INPUT_DU0,
|
||||
RCAR_LVDS_INPUT_DU1,
|
||||
RCAR_LVDS_INPUT_DU2,
|
||||
};
|
||||
|
||||
/* Keep in sync with the LVDCR0.LVMD hardware register values. */
|
||||
enum rcar_lvds_mode {
|
||||
RCAR_LVDS_MODE_JEIDA = 0,
|
||||
RCAR_LVDS_MODE_MIRROR = 1,
|
||||
RCAR_LVDS_MODE_VESA = 4,
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
|
||||
int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu);
|
||||
void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
|
||||
enum rcar_lvds_mode mode);
|
||||
int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds,
|
||||
struct drm_crtc *crtc, bool enable);
|
||||
void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
|
||||
struct drm_display_mode *mode);
|
||||
#else
|
||||
static inline int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
|
||||
enum rcar_lvds_mode mode)
|
||||
{
|
||||
}
|
||||
static inline int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds,
|
||||
struct drm_crtc *crtc, bool enable)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __RCAR_DU_LVDSENC_H__ */
|
|
@ -0,0 +1,322 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* rcar_du_of.c - Legacy DT bindings compatibility
|
||||
*
|
||||
* Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*
|
||||
* Based on work from Jyri Sarha <jsarha@ti.com>
|
||||
* Copyright (C) 2015 Texas Instruments
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_fdt.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "rcar_du_crtc.h"
|
||||
#include "rcar_du_drv.h"
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Generic Overlay Handling
|
||||
*/
|
||||
|
||||
struct rcar_du_of_overlay {
|
||||
const char *compatible;
|
||||
void *begin;
|
||||
void *end;
|
||||
};
|
||||
|
||||
#define RCAR_DU_OF_DTB(type, soc) \
|
||||
extern char __dtb_rcar_du_of_##type##_##soc##_begin[]; \
|
||||
extern char __dtb_rcar_du_of_##type##_##soc##_end[]
|
||||
|
||||
#define RCAR_DU_OF_OVERLAY(type, soc) \
|
||||
{ \
|
||||
.compatible = "renesas,du-" #soc, \
|
||||
.begin = __dtb_rcar_du_of_##type##_##soc##_begin, \
|
||||
.end = __dtb_rcar_du_of_##type##_##soc##_end, \
|
||||
}
|
||||
|
||||
static int __init rcar_du_of_apply_overlay(const struct rcar_du_of_overlay *dtbs,
|
||||
const char *compatible)
|
||||
{
|
||||
const struct rcar_du_of_overlay *dtb = NULL;
|
||||
unsigned int i;
|
||||
int ovcs_id;
|
||||
|
||||
for (i = 0; dtbs[i].compatible; ++i) {
|
||||
if (!strcmp(dtbs[i].compatible, compatible)) {
|
||||
dtb = &dtbs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dtb)
|
||||
return -ENODEV;
|
||||
|
||||
ovcs_id = 0;
|
||||
return of_overlay_fdt_apply(dtb->begin, dtb->end - dtb->begin,
|
||||
&ovcs_id);
|
||||
}
|
||||
|
||||
static int __init rcar_du_of_add_property(struct of_changeset *ocs,
|
||||
struct device_node *np,
|
||||
const char *name, const void *value,
|
||||
int length)
|
||||
{
|
||||
struct property *prop;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
prop = kzalloc(sizeof(*prop), GFP_KERNEL);
|
||||
if (!prop)
|
||||
return -ENOMEM;
|
||||
|
||||
prop->name = kstrdup(name, GFP_KERNEL);
|
||||
if (!prop->name)
|
||||
goto out_err;
|
||||
|
||||
prop->value = kmemdup(value, length, GFP_KERNEL);
|
||||
if (!prop->value)
|
||||
goto out_err;
|
||||
|
||||
of_property_set_flag(prop, OF_DYNAMIC);
|
||||
|
||||
prop->length = length;
|
||||
|
||||
ret = of_changeset_add_property(ocs, np, prop);
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
out_err:
|
||||
kfree(prop->value);
|
||||
kfree(prop->name);
|
||||
kfree(prop);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* LVDS Overlays
|
||||
*/
|
||||
|
||||
RCAR_DU_OF_DTB(lvds, r8a7790);
|
||||
RCAR_DU_OF_DTB(lvds, r8a7791);
|
||||
RCAR_DU_OF_DTB(lvds, r8a7793);
|
||||
RCAR_DU_OF_DTB(lvds, r8a7795);
|
||||
RCAR_DU_OF_DTB(lvds, r8a7796);
|
||||
|
||||
static const struct rcar_du_of_overlay rcar_du_lvds_overlays[] __initconst = {
|
||||
RCAR_DU_OF_OVERLAY(lvds, r8a7790),
|
||||
RCAR_DU_OF_OVERLAY(lvds, r8a7791),
|
||||
RCAR_DU_OF_OVERLAY(lvds, r8a7793),
|
||||
RCAR_DU_OF_OVERLAY(lvds, r8a7795),
|
||||
RCAR_DU_OF_OVERLAY(lvds, r8a7796),
|
||||
{ /* Sentinel */ },
|
||||
};
|
||||
|
||||
static struct of_changeset rcar_du_lvds_changeset;
|
||||
|
||||
static void __init rcar_du_of_lvds_patch_one(struct device_node *lvds,
|
||||
const struct of_phandle_args *clk,
|
||||
struct device_node *local,
|
||||
struct device_node *remote)
|
||||
{
|
||||
unsigned int psize;
|
||||
unsigned int i;
|
||||
__be32 value[4];
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Set the LVDS clocks property. This can't be performed by the overlay
|
||||
* as the structure of the clock specifier has changed over time, and we
|
||||
* don't know at compile time which binding version the system we will
|
||||
* run on uses.
|
||||
*/
|
||||
if (clk->args_count >= ARRAY_SIZE(value) - 1)
|
||||
return;
|
||||
|
||||
of_changeset_init(&rcar_du_lvds_changeset);
|
||||
|
||||
value[0] = cpu_to_be32(clk->np->phandle);
|
||||
for (i = 0; i < clk->args_count; ++i)
|
||||
value[i + 1] = cpu_to_be32(clk->args[i]);
|
||||
|
||||
psize = (clk->args_count + 1) * 4;
|
||||
ret = rcar_du_of_add_property(&rcar_du_lvds_changeset, lvds,
|
||||
"clocks", value, psize);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
/*
|
||||
* Insert the node in the OF graph: patch the LVDS ports remote-endpoint
|
||||
* properties to point to the endpoints of the sibling nodes in the
|
||||
* graph. This can't be performed by the overlay: on the input side the
|
||||
* overlay would contain a phandle for the DU LVDS output port that
|
||||
* would clash with the system DT, and on the output side the connection
|
||||
* is board-specific.
|
||||
*/
|
||||
value[0] = cpu_to_be32(local->phandle);
|
||||
value[1] = cpu_to_be32(remote->phandle);
|
||||
|
||||
for (i = 0; i < 2; ++i) {
|
||||
struct device_node *endpoint;
|
||||
|
||||
endpoint = of_graph_get_endpoint_by_regs(lvds, i, 0);
|
||||
if (!endpoint) {
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = rcar_du_of_add_property(&rcar_du_lvds_changeset,
|
||||
endpoint, "remote-endpoint",
|
||||
&value[i], sizeof(value[i]));
|
||||
of_node_put(endpoint);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = of_changeset_apply(&rcar_du_lvds_changeset);
|
||||
|
||||
done:
|
||||
if (ret < 0)
|
||||
of_changeset_destroy(&rcar_du_lvds_changeset);
|
||||
}
|
||||
|
||||
struct lvds_of_data {
|
||||
struct resource res;
|
||||
struct of_phandle_args clkspec;
|
||||
struct device_node *local;
|
||||
struct device_node *remote;
|
||||
};
|
||||
|
||||
static void __init rcar_du_of_lvds_patch(const struct of_device_id *of_ids)
|
||||
{
|
||||
const struct rcar_du_device_info *info;
|
||||
const struct of_device_id *match;
|
||||
struct lvds_of_data lvds_data[2] = { };
|
||||
struct device_node *lvds_node;
|
||||
struct device_node *soc_node;
|
||||
struct device_node *du_node;
|
||||
char compatible[22];
|
||||
const char *soc_name;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
/* Get the DU node and exit if not present or disabled. */
|
||||
du_node = of_find_matching_node_and_match(NULL, of_ids, &match);
|
||||
if (!du_node || !of_device_is_available(du_node)) {
|
||||
of_node_put(du_node);
|
||||
return;
|
||||
}
|
||||
|
||||
info = match->data;
|
||||
soc_node = of_get_parent(du_node);
|
||||
|
||||
if (WARN_ON(info->num_lvds > ARRAY_SIZE(lvds_data)))
|
||||
goto done;
|
||||
|
||||
/*
|
||||
* Skip if the LVDS nodes already exists.
|
||||
*
|
||||
* The nodes are searched based on the compatible string, which we
|
||||
* construct from the SoC name found in the DU compatible string. As a
|
||||
* match has been found we know the compatible string matches the
|
||||
* expected format and can thus skip some of the string manipulation
|
||||
* normal safety checks.
|
||||
*/
|
||||
soc_name = strchr(match->compatible, '-') + 1;
|
||||
sprintf(compatible, "renesas,%s-lvds", soc_name);
|
||||
lvds_node = of_find_compatible_node(NULL, NULL, compatible);
|
||||
if (lvds_node) {
|
||||
of_node_put(lvds_node);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the DU node and store the register specifier, the clock
|
||||
* specifier and the local and remote endpoint of the LVDS link for
|
||||
* later use.
|
||||
*/
|
||||
for (i = 0; i < info->num_lvds; ++i) {
|
||||
struct lvds_of_data *lvds = &lvds_data[i];
|
||||
unsigned int port;
|
||||
char name[7];
|
||||
int index;
|
||||
|
||||
sprintf(name, "lvds.%u", i);
|
||||
index = of_property_match_string(du_node, "clock-names", name);
|
||||
if (index < 0)
|
||||
continue;
|
||||
|
||||
ret = of_parse_phandle_with_args(du_node, "clocks",
|
||||
"#clock-cells", index,
|
||||
&lvds->clkspec);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
port = info->routes[RCAR_DU_OUTPUT_LVDS0 + i].port;
|
||||
|
||||
lvds->local = of_graph_get_endpoint_by_regs(du_node, port, 0);
|
||||
if (!lvds->local)
|
||||
continue;
|
||||
|
||||
lvds->remote = of_graph_get_remote_endpoint(lvds->local);
|
||||
if (!lvds->remote)
|
||||
continue;
|
||||
|
||||
index = of_property_match_string(du_node, "reg-names", name);
|
||||
if (index < 0)
|
||||
continue;
|
||||
|
||||
of_address_to_resource(du_node, index, &lvds->res);
|
||||
}
|
||||
|
||||
/* Parse and apply the overlay. This will resolve phandles. */
|
||||
ret = rcar_du_of_apply_overlay(rcar_du_lvds_overlays,
|
||||
match->compatible);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
|
||||
/* Patch the newly created LVDS encoder nodes. */
|
||||
for_each_child_of_node(soc_node, lvds_node) {
|
||||
struct resource res;
|
||||
|
||||
if (!of_device_is_compatible(lvds_node, compatible))
|
||||
continue;
|
||||
|
||||
/* Locate the lvds_data entry based on the resource start. */
|
||||
ret = of_address_to_resource(lvds_node, 0, &res);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(lvds_data); ++i) {
|
||||
if (lvds_data[i].res.start == res.start)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == ARRAY_SIZE(lvds_data))
|
||||
continue;
|
||||
|
||||
/* Patch the LVDS encoder. */
|
||||
rcar_du_of_lvds_patch_one(lvds_node, &lvds_data[i].clkspec,
|
||||
lvds_data[i].local,
|
||||
lvds_data[i].remote);
|
||||
}
|
||||
|
||||
done:
|
||||
for (i = 0; i < info->num_lvds; ++i) {
|
||||
of_node_put(lvds_data[i].clkspec.np);
|
||||
of_node_put(lvds_data[i].local);
|
||||
of_node_put(lvds_data[i].remote);
|
||||
}
|
||||
|
||||
of_node_put(soc_node);
|
||||
of_node_put(du_node);
|
||||
}
|
||||
|
||||
void __init rcar_du_of_init(const struct of_device_id *of_ids)
|
||||
{
|
||||
rcar_du_of_lvds_patch(of_ids);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* rcar_du_of.h - Legacy DT bindings compatibility
|
||||
*
|
||||
* Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
#ifndef __RCAR_DU_OF_H__
|
||||
#define __RCAR_DU_OF_H__
|
||||
|
||||
#include <linux/init.h>
|
||||
|
||||
struct of_device_id;
|
||||
|
||||
#ifdef CONFIG_DRM_RCAR_LVDS
|
||||
void __init rcar_du_of_init(const struct of_device_id *of_ids);
|
||||
#else
|
||||
static inline void rcar_du_of_init(const struct of_device_id *of_ids) { }
|
||||
#endif /* CONFIG_DRM_RCAR_LVDS */
|
||||
|
||||
#endif /* __RCAR_DU_OF_H__ */
|
|
@ -0,0 +1,76 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* rcar_du_of_lvds_r8a7790.dts - Legacy LVDS DT bindings conversion for R8A7790
|
||||
*
|
||||
* Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
/ {
|
||||
fragment@0 {
|
||||
target-path = "/";
|
||||
__overlay__ {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
|
||||
lvds@feb90000 {
|
||||
compatible = "renesas,r8a7790-lvds";
|
||||
reg = <0 0xfeb90000 0 0x1c>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
lvds0_input: endpoint {
|
||||
};
|
||||
};
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
lvds0_out: endpoint {
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
lvds@feb94000 {
|
||||
compatible = "renesas,r8a7790-lvds";
|
||||
reg = <0 0xfeb94000 0 0x1c>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
lvds1_input: endpoint {
|
||||
};
|
||||
};
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
lvds1_out: endpoint {
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fragment@1 {
|
||||
target-path = "/display@feb00000/ports";
|
||||
__overlay__ {
|
||||
port@1 {
|
||||
endpoint {
|
||||
remote-endpoint = <&lvds0_input>;
|
||||
};
|
||||
};
|
||||
port@2 {
|
||||
endpoint {
|
||||
remote-endpoint = <&lvds1_input>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,50 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* rcar_du_of_lvds_r8a7791.dts - Legacy LVDS DT bindings conversion for R8A7791
|
||||
*
|
||||
* Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
/ {
|
||||
fragment@0 {
|
||||
target-path = "/";
|
||||
__overlay__ {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
|
||||
lvds@feb90000 {
|
||||
compatible = "renesas,r8a7791-lvds";
|
||||
reg = <0 0xfeb90000 0 0x1c>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
lvds0_input: endpoint {
|
||||
};
|
||||
};
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
lvds0_out: endpoint {
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fragment@1 {
|
||||
target-path = "/display@feb00000/ports";
|
||||
__overlay__ {
|
||||
port@1 {
|
||||
endpoint {
|
||||
remote-endpoint = <&lvds0_input>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,50 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* rcar_du_of_lvds_r8a7793.dts - Legacy LVDS DT bindings conversion for R8A7793
|
||||
*
|
||||
* Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
/ {
|
||||
fragment@0 {
|
||||
target-path = "/";
|
||||
__overlay__ {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
|
||||
lvds@feb90000 {
|
||||
compatible = "renesas,r8a7793-lvds";
|
||||
reg = <0 0xfeb90000 0 0x1c>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
lvds0_input: endpoint {
|
||||
};
|
||||
};
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
lvds0_out: endpoint {
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fragment@1 {
|
||||
target-path = "/display@feb00000/ports";
|
||||
__overlay__ {
|
||||
port@1 {
|
||||
endpoint {
|
||||
remote-endpoint = <&lvds0_input>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,50 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* rcar_du_of_lvds_r8a7795.dts - Legacy LVDS DT bindings conversion for R8A7795
|
||||
*
|
||||
* Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
/ {
|
||||
fragment@0 {
|
||||
target-path = "/soc";
|
||||
__overlay__ {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
|
||||
lvds@feb90000 {
|
||||
compatible = "renesas,r8a7795-lvds";
|
||||
reg = <0 0xfeb90000 0 0x14>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
lvds0_input: endpoint {
|
||||
};
|
||||
};
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
lvds0_out: endpoint {
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fragment@1 {
|
||||
target-path = "/soc/display@feb00000/ports";
|
||||
__overlay__ {
|
||||
port@3 {
|
||||
endpoint {
|
||||
remote-endpoint = <&lvds0_input>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,50 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* rcar_du_of_lvds_r8a7796.dts - Legacy LVDS DT bindings conversion for R8A7796
|
||||
*
|
||||
* Copyright (C) 2018 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
/ {
|
||||
fragment@0 {
|
||||
target-path = "/soc";
|
||||
__overlay__ {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
|
||||
lvds@feb90000 {
|
||||
compatible = "renesas,r8a7796-lvds";
|
||||
reg = <0 0xfeb90000 0 0x14>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
lvds0_input: endpoint {
|
||||
};
|
||||
};
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
lvds0_out: endpoint {
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fragment@1 {
|
||||
target-path = "/soc/display@feb00000/ports";
|
||||
__overlay__ {
|
||||
port@3 {
|
||||
endpoint {
|
||||
remote-endpoint = <&lvds0_input>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,540 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* rcar_lvds.c -- R-Car LVDS Encoder
|
||||
*
|
||||
* Copyright (C) 2013-2018 Renesas Electronics Corporation
|
||||
*
|
||||
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_bridge.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
#include "rcar_lvds_regs.h"
|
||||
|
||||
/* Keep in sync with the LVDCR0.LVMD hardware register values. */
|
||||
enum rcar_lvds_mode {
|
||||
RCAR_LVDS_MODE_JEIDA = 0,
|
||||
RCAR_LVDS_MODE_MIRROR = 1,
|
||||
RCAR_LVDS_MODE_VESA = 4,
|
||||
};
|
||||
|
||||
#define RCAR_LVDS_QUIRK_LANES (1 << 0) /* LVDS lanes 1 and 3 inverted */
|
||||
#define RCAR_LVDS_QUIRK_GEN2_PLLCR (1 << 1) /* LVDPLLCR has gen2 layout */
|
||||
#define RCAR_LVDS_QUIRK_GEN3_LVEN (1 << 2) /* LVEN bit needs to be set */
|
||||
/* on R8A77970/R8A7799x */
|
||||
|
||||
struct rcar_lvds_device_info {
|
||||
unsigned int gen;
|
||||
unsigned int quirks;
|
||||
};
|
||||
|
||||
struct rcar_lvds {
|
||||
struct device *dev;
|
||||
const struct rcar_lvds_device_info *info;
|
||||
|
||||
struct drm_bridge bridge;
|
||||
|
||||
struct drm_bridge *next_bridge;
|
||||
struct drm_connector connector;
|
||||
struct drm_panel *panel;
|
||||
|
||||
void __iomem *mmio;
|
||||
struct clk *clock;
|
||||
bool enabled;
|
||||
|
||||
struct drm_display_mode display_mode;
|
||||
enum rcar_lvds_mode mode;
|
||||
};
|
||||
|
||||
#define bridge_to_rcar_lvds(bridge) \
|
||||
container_of(bridge, struct rcar_lvds, bridge)
|
||||
|
||||
#define connector_to_rcar_lvds(connector) \
|
||||
container_of(connector, struct rcar_lvds, connector)
|
||||
|
||||
static void rcar_lvds_write(struct rcar_lvds *lvds, u32 reg, u32 data)
|
||||
{
|
||||
iowrite32(data, lvds->mmio + reg);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Connector & Panel
|
||||
*/
|
||||
|
||||
static int rcar_lvds_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct rcar_lvds *lvds = connector_to_rcar_lvds(connector);
|
||||
|
||||
return drm_panel_get_modes(lvds->panel);
|
||||
}
|
||||
|
||||
static int rcar_lvds_connector_atomic_check(struct drm_connector *connector,
|
||||
struct drm_connector_state *state)
|
||||
{
|
||||
struct rcar_lvds *lvds = connector_to_rcar_lvds(connector);
|
||||
const struct drm_display_mode *panel_mode;
|
||||
struct drm_crtc_state *crtc_state;
|
||||
|
||||
if (list_empty(&connector->modes)) {
|
||||
dev_dbg(lvds->dev, "connector: empty modes list\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
panel_mode = list_first_entry(&connector->modes,
|
||||
struct drm_display_mode, head);
|
||||
|
||||
/* We're not allowed to modify the resolution. */
|
||||
crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
|
||||
if (IS_ERR(crtc_state))
|
||||
return PTR_ERR(crtc_state);
|
||||
|
||||
if (crtc_state->mode.hdisplay != panel_mode->hdisplay ||
|
||||
crtc_state->mode.vdisplay != panel_mode->vdisplay)
|
||||
return -EINVAL;
|
||||
|
||||
/* The flat panel mode is fixed, just copy it to the adjusted mode. */
|
||||
drm_mode_copy(&crtc_state->adjusted_mode, panel_mode);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct drm_connector_helper_funcs rcar_lvds_conn_helper_funcs = {
|
||||
.get_modes = rcar_lvds_connector_get_modes,
|
||||
.atomic_check = rcar_lvds_connector_atomic_check,
|
||||
};
|
||||
|
||||
static const struct drm_connector_funcs rcar_lvds_conn_funcs = {
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = drm_connector_cleanup,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Bridge
|
||||
*/
|
||||
|
||||
static u32 rcar_lvds_lvdpllcr_gen2(unsigned int freq)
|
||||
{
|
||||
if (freq < 39000)
|
||||
return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
|
||||
else if (freq < 61000)
|
||||
return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
|
||||
else if (freq < 121000)
|
||||
return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
|
||||
else
|
||||
return LVDPLLCR_PLLDLYCNT_150M;
|
||||
}
|
||||
|
||||
static u32 rcar_lvds_lvdpllcr_gen3(unsigned int freq)
|
||||
{
|
||||
if (freq < 42000)
|
||||
return LVDPLLCR_PLLDIVCNT_42M;
|
||||
else if (freq < 85000)
|
||||
return LVDPLLCR_PLLDIVCNT_85M;
|
||||
else if (freq < 128000)
|
||||
return LVDPLLCR_PLLDIVCNT_128M;
|
||||
else
|
||||
return LVDPLLCR_PLLDIVCNT_148M;
|
||||
}
|
||||
|
||||
static void rcar_lvds_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
|
||||
const struct drm_display_mode *mode = &lvds->display_mode;
|
||||
/*
|
||||
* FIXME: We should really retrieve the CRTC through the state, but how
|
||||
* do we get a state pointer?
|
||||
*/
|
||||
struct drm_crtc *crtc = lvds->bridge.encoder->crtc;
|
||||
u32 lvdpllcr;
|
||||
u32 lvdhcr;
|
||||
u32 lvdcr0;
|
||||
int ret;
|
||||
|
||||
WARN_ON(lvds->enabled);
|
||||
|
||||
ret = clk_prepare_enable(lvds->clock);
|
||||
if (ret < 0)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Hardcode the channels and control signals routing for now.
|
||||
*
|
||||
* HSYNC -> CTRL0
|
||||
* VSYNC -> CTRL1
|
||||
* DISP -> CTRL2
|
||||
* 0 -> CTRL3
|
||||
*/
|
||||
rcar_lvds_write(lvds, LVDCTRCR, LVDCTRCR_CTR3SEL_ZERO |
|
||||
LVDCTRCR_CTR2SEL_DISP | LVDCTRCR_CTR1SEL_VSYNC |
|
||||
LVDCTRCR_CTR0SEL_HSYNC);
|
||||
|
||||
if (lvds->info->quirks & RCAR_LVDS_QUIRK_LANES)
|
||||
lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 3)
|
||||
| LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 1);
|
||||
else
|
||||
lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 1)
|
||||
| LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 3);
|
||||
|
||||
rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
|
||||
|
||||
/* PLL clock configuration. */
|
||||
if (lvds->info->quirks & RCAR_LVDS_QUIRK_GEN2_PLLCR)
|
||||
lvdpllcr = rcar_lvds_lvdpllcr_gen2(mode->clock);
|
||||
else
|
||||
lvdpllcr = rcar_lvds_lvdpllcr_gen3(mode->clock);
|
||||
rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
|
||||
|
||||
/* Set the LVDS mode and select the input. */
|
||||
lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
|
||||
if (drm_crtc_index(crtc) == 2)
|
||||
lvdcr0 |= LVDCR0_DUSEL;
|
||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
|
||||
/* Turn all the channels on. */
|
||||
rcar_lvds_write(lvds, LVDCR1,
|
||||
LVDCR1_CHSTBY(3) | LVDCR1_CHSTBY(2) |
|
||||
LVDCR1_CHSTBY(1) | LVDCR1_CHSTBY(0) | LVDCR1_CLKSTBY);
|
||||
|
||||
if (lvds->info->gen < 3) {
|
||||
/* Enable LVDS operation and turn the bias circuitry on. */
|
||||
lvdcr0 |= LVDCR0_BEN | LVDCR0_LVEN;
|
||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
}
|
||||
|
||||
/* Turn the PLL on. */
|
||||
lvdcr0 |= LVDCR0_PLLON;
|
||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
|
||||
if (lvds->info->gen > 2) {
|
||||
/* Set LVDS normal mode. */
|
||||
lvdcr0 |= LVDCR0_PWD;
|
||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
}
|
||||
|
||||
if (lvds->info->quirks & RCAR_LVDS_QUIRK_GEN3_LVEN) {
|
||||
/* Turn on the LVDS PHY. */
|
||||
lvdcr0 |= LVDCR0_LVEN;
|
||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
}
|
||||
|
||||
/* Wait for the startup delay. */
|
||||
usleep_range(100, 150);
|
||||
|
||||
/* Turn the output on. */
|
||||
lvdcr0 |= LVDCR0_LVRES;
|
||||
rcar_lvds_write(lvds, LVDCR0, lvdcr0);
|
||||
|
||||
if (lvds->panel) {
|
||||
drm_panel_prepare(lvds->panel);
|
||||
drm_panel_enable(lvds->panel);
|
||||
}
|
||||
|
||||
lvds->enabled = true;
|
||||
}
|
||||
|
||||
static void rcar_lvds_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
|
||||
|
||||
WARN_ON(!lvds->enabled);
|
||||
|
||||
if (lvds->panel) {
|
||||
drm_panel_disable(lvds->panel);
|
||||
drm_panel_unprepare(lvds->panel);
|
||||
}
|
||||
|
||||
rcar_lvds_write(lvds, LVDCR0, 0);
|
||||
rcar_lvds_write(lvds, LVDCR1, 0);
|
||||
|
||||
clk_disable_unprepare(lvds->clock);
|
||||
|
||||
lvds->enabled = false;
|
||||
}
|
||||
|
||||
static bool rcar_lvds_mode_fixup(struct drm_bridge *bridge,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
/*
|
||||
* The internal LVDS encoder has a restricted clock frequency operating
|
||||
* range (31MHz to 148.5MHz). Clamp the clock accordingly.
|
||||
*/
|
||||
adjusted_mode->clock = clamp(adjusted_mode->clock, 31000, 148500);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void rcar_lvds_get_lvds_mode(struct rcar_lvds *lvds)
|
||||
{
|
||||
struct drm_display_info *info = &lvds->connector.display_info;
|
||||
enum rcar_lvds_mode mode;
|
||||
|
||||
/*
|
||||
* There is no API yet to retrieve LVDS mode from a bridge, only panels
|
||||
* are supported.
|
||||
*/
|
||||
if (!lvds->panel)
|
||||
return;
|
||||
|
||||
if (!info->num_bus_formats || !info->bus_formats) {
|
||||
dev_err(lvds->dev, "no LVDS bus format reported\n");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (info->bus_formats[0]) {
|
||||
case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
|
||||
case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
|
||||
mode = RCAR_LVDS_MODE_JEIDA;
|
||||
break;
|
||||
case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
|
||||
mode = RCAR_LVDS_MODE_VESA;
|
||||
break;
|
||||
default:
|
||||
dev_err(lvds->dev, "unsupported LVDS bus format 0x%04x\n",
|
||||
info->bus_formats[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->bus_flags & DRM_BUS_FLAG_DATA_LSB_TO_MSB)
|
||||
mode |= RCAR_LVDS_MODE_MIRROR;
|
||||
|
||||
lvds->mode = mode;
|
||||
}
|
||||
|
||||
static void rcar_lvds_mode_set(struct drm_bridge *bridge,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
|
||||
|
||||
WARN_ON(lvds->enabled);
|
||||
|
||||
lvds->display_mode = *adjusted_mode;
|
||||
|
||||
rcar_lvds_get_lvds_mode(lvds);
|
||||
}
|
||||
|
||||
static int rcar_lvds_attach(struct drm_bridge *bridge)
|
||||
{
|
||||
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
|
||||
struct drm_connector *connector = &lvds->connector;
|
||||
struct drm_encoder *encoder = bridge->encoder;
|
||||
int ret;
|
||||
|
||||
/* If we have a next bridge just attach it. */
|
||||
if (lvds->next_bridge)
|
||||
return drm_bridge_attach(bridge->encoder, lvds->next_bridge,
|
||||
bridge);
|
||||
|
||||
/* Otherwise we have a panel, create a connector. */
|
||||
ret = drm_connector_init(bridge->dev, connector, &rcar_lvds_conn_funcs,
|
||||
DRM_MODE_CONNECTOR_LVDS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
drm_connector_helper_add(connector, &rcar_lvds_conn_helper_funcs);
|
||||
|
||||
ret = drm_mode_connector_attach_encoder(connector, encoder);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return drm_panel_attach(lvds->panel, connector);
|
||||
}
|
||||
|
||||
static void rcar_lvds_detach(struct drm_bridge *bridge)
|
||||
{
|
||||
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
|
||||
|
||||
if (lvds->panel)
|
||||
drm_panel_detach(lvds->panel);
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs rcar_lvds_bridge_ops = {
|
||||
.attach = rcar_lvds_attach,
|
||||
.detach = rcar_lvds_detach,
|
||||
.enable = rcar_lvds_enable,
|
||||
.disable = rcar_lvds_disable,
|
||||
.mode_fixup = rcar_lvds_mode_fixup,
|
||||
.mode_set = rcar_lvds_mode_set,
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Probe & Remove
|
||||
*/
|
||||
|
||||
static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
|
||||
{
|
||||
struct device_node *local_output = NULL;
|
||||
struct device_node *remote_input = NULL;
|
||||
struct device_node *remote = NULL;
|
||||
struct device_node *node;
|
||||
bool is_bridge = false;
|
||||
int ret = 0;
|
||||
|
||||
local_output = of_graph_get_endpoint_by_regs(lvds->dev->of_node, 1, 0);
|
||||
if (!local_output) {
|
||||
dev_dbg(lvds->dev, "unconnected port@1\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/*
|
||||
* Locate the connected entity and infer its type from the number of
|
||||
* endpoints.
|
||||
*/
|
||||
remote = of_graph_get_remote_port_parent(local_output);
|
||||
if (!remote) {
|
||||
dev_dbg(lvds->dev, "unconnected endpoint %pOF\n", local_output);
|
||||
ret = -ENODEV;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!of_device_is_available(remote)) {
|
||||
dev_dbg(lvds->dev, "connected entity %pOF is disabled\n",
|
||||
remote);
|
||||
ret = -ENODEV;
|
||||
goto done;
|
||||
}
|
||||
|
||||
remote_input = of_graph_get_remote_endpoint(local_output);
|
||||
|
||||
for_each_endpoint_of_node(remote, node) {
|
||||
if (node != remote_input) {
|
||||
/*
|
||||
* We've found one endpoint other than the input, this
|
||||
* must be a bridge.
|
||||
*/
|
||||
is_bridge = true;
|
||||
of_node_put(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_bridge) {
|
||||
lvds->next_bridge = of_drm_find_bridge(remote);
|
||||
if (!lvds->next_bridge)
|
||||
ret = -EPROBE_DEFER;
|
||||
} else {
|
||||
lvds->panel = of_drm_find_panel(remote);
|
||||
if (!lvds->panel)
|
||||
ret = -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
done:
|
||||
of_node_put(local_output);
|
||||
of_node_put(remote_input);
|
||||
of_node_put(remote);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rcar_lvds_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rcar_lvds *lvds;
|
||||
struct resource *mem;
|
||||
int ret;
|
||||
|
||||
lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
|
||||
if (lvds == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, lvds);
|
||||
|
||||
lvds->dev = &pdev->dev;
|
||||
lvds->info = of_device_get_match_data(&pdev->dev);
|
||||
lvds->enabled = false;
|
||||
|
||||
ret = rcar_lvds_parse_dt(lvds);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
lvds->bridge.driver_private = lvds;
|
||||
lvds->bridge.funcs = &rcar_lvds_bridge_ops;
|
||||
lvds->bridge.of_node = pdev->dev.of_node;
|
||||
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
lvds->mmio = devm_ioremap_resource(&pdev->dev, mem);
|
||||
if (IS_ERR(lvds->mmio))
|
||||
return PTR_ERR(lvds->mmio);
|
||||
|
||||
lvds->clock = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(lvds->clock)) {
|
||||
dev_err(&pdev->dev, "failed to get clock\n");
|
||||
return PTR_ERR(lvds->clock);
|
||||
}
|
||||
|
||||
drm_bridge_add(&lvds->bridge);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rcar_lvds_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rcar_lvds *lvds = platform_get_drvdata(pdev);
|
||||
|
||||
drm_bridge_remove(&lvds->bridge);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rcar_lvds_device_info rcar_lvds_gen2_info = {
|
||||
.gen = 2,
|
||||
.quirks = RCAR_LVDS_QUIRK_GEN2_PLLCR,
|
||||
};
|
||||
|
||||
static const struct rcar_lvds_device_info rcar_lvds_r8a7790_info = {
|
||||
.gen = 2,
|
||||
.quirks = RCAR_LVDS_QUIRK_GEN2_PLLCR | RCAR_LVDS_QUIRK_LANES,
|
||||
};
|
||||
|
||||
static const struct rcar_lvds_device_info rcar_lvds_gen3_info = {
|
||||
.gen = 3,
|
||||
};
|
||||
|
||||
static const struct rcar_lvds_device_info rcar_lvds_r8a77970_info = {
|
||||
.gen = 3,
|
||||
.quirks = RCAR_LVDS_QUIRK_GEN2_PLLCR | RCAR_LVDS_QUIRK_GEN3_LVEN,
|
||||
};
|
||||
|
||||
static const struct of_device_id rcar_lvds_of_table[] = {
|
||||
{ .compatible = "renesas,r8a7743-lvds", .data = &rcar_lvds_gen2_info },
|
||||
{ .compatible = "renesas,r8a7790-lvds", .data = &rcar_lvds_r8a7790_info },
|
||||
{ .compatible = "renesas,r8a7791-lvds", .data = &rcar_lvds_gen2_info },
|
||||
{ .compatible = "renesas,r8a7793-lvds", .data = &rcar_lvds_gen2_info },
|
||||
{ .compatible = "renesas,r8a7795-lvds", .data = &rcar_lvds_gen3_info },
|
||||
{ .compatible = "renesas,r8a7796-lvds", .data = &rcar_lvds_gen3_info },
|
||||
{ .compatible = "renesas,r8a77970-lvds", .data = &rcar_lvds_r8a77970_info },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, rcar_lvds_of_table);
|
||||
|
||||
static struct platform_driver rcar_lvds_platform_driver = {
|
||||
.probe = rcar_lvds_probe,
|
||||
.remove = rcar_lvds_remove,
|
||||
.driver = {
|
||||
.name = "rcar-lvds",
|
||||
.of_match_table = rcar_lvds_of_table,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(rcar_lvds_platform_driver);
|
||||
|
||||
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
|
||||
MODULE_DESCRIPTION("Renesas R-Car LVDS Encoder Driver");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -92,6 +92,7 @@ config OF_RESOLVE
|
|||
config OF_OVERLAY
|
||||
bool "Device Tree overlays"
|
||||
select OF_DYNAMIC
|
||||
select OF_FLATTREE
|
||||
select OF_RESOLVE
|
||||
help
|
||||
Overlays are a method to dynamically modify part of the kernel's
|
||||
|
|
|
@ -12,10 +12,12 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_fdt.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/libfdt.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/idr.h>
|
||||
|
||||
|
@ -33,7 +35,9 @@ struct fragment {
|
|||
|
||||
/**
|
||||
* struct overlay_changeset
|
||||
* @id: changeset identifier
|
||||
* @ovcs_list: list on which we are located
|
||||
* @fdt: FDT that was unflattened to create @overlay_tree
|
||||
* @overlay_tree: expanded device tree that contains the fragment nodes
|
||||
* @count: count of fragment structures
|
||||
* @fragments: fragment nodes in the overlay expanded device tree
|
||||
|
@ -43,6 +47,7 @@ struct fragment {
|
|||
struct overlay_changeset {
|
||||
int id;
|
||||
struct list_head ovcs_list;
|
||||
const void *fdt;
|
||||
struct device_node *overlay_tree;
|
||||
int count;
|
||||
struct fragment *fragments;
|
||||
|
@ -483,27 +488,38 @@ static int build_changeset(struct overlay_changeset *ovcs)
|
|||
*/
|
||||
static struct device_node *find_target_node(struct device_node *info_node)
|
||||
{
|
||||
struct device_node *node;
|
||||
const char *path;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_u32(info_node, "target", &val);
|
||||
if (!ret)
|
||||
return of_find_node_by_phandle(val);
|
||||
if (!ret) {
|
||||
node = of_find_node_by_phandle(val);
|
||||
if (!node)
|
||||
pr_err("find target, node: %pOF, phandle 0x%x not found\n",
|
||||
info_node, val);
|
||||
return node;
|
||||
}
|
||||
|
||||
ret = of_property_read_string(info_node, "target-path", &path);
|
||||
if (!ret)
|
||||
return of_find_node_by_path(path);
|
||||
if (!ret) {
|
||||
node = of_find_node_by_path(path);
|
||||
if (!node)
|
||||
pr_err("find target, node: %pOF, path '%s' not found\n",
|
||||
info_node, path);
|
||||
return node;
|
||||
}
|
||||
|
||||
pr_err("Failed to find target for node %p (%s)\n",
|
||||
info_node, info_node->name);
|
||||
pr_err("find target, node: %pOF, no target property\n", info_node);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* init_overlay_changeset() - initialize overlay changeset from overlay tree
|
||||
* @ovcs Overlay changeset to build
|
||||
* @ovcs: Overlay changeset to build
|
||||
* @fdt: the FDT that was unflattened to create @tree
|
||||
* @tree: Contains all the overlay fragments and overlay fixup nodes
|
||||
*
|
||||
* Initialize @ovcs. Populate @ovcs->fragments with node information from
|
||||
|
@ -514,7 +530,7 @@ static struct device_node *find_target_node(struct device_node *info_node)
|
|||
* detected in @tree, or -ENOSPC if idr_alloc() error.
|
||||
*/
|
||||
static int init_overlay_changeset(struct overlay_changeset *ovcs,
|
||||
struct device_node *tree)
|
||||
const void *fdt, struct device_node *tree)
|
||||
{
|
||||
struct device_node *node, *overlay_node;
|
||||
struct fragment *fragment;
|
||||
|
@ -535,6 +551,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
|
|||
pr_debug("%s() tree is not root\n", __func__);
|
||||
|
||||
ovcs->overlay_tree = tree;
|
||||
ovcs->fdt = fdt;
|
||||
|
||||
INIT_LIST_HEAD(&ovcs->ovcs_list);
|
||||
|
||||
|
@ -606,6 +623,7 @@ static int init_overlay_changeset(struct overlay_changeset *ovcs,
|
|||
}
|
||||
|
||||
if (!cnt) {
|
||||
pr_err("no fragments or symbols in overlay\n");
|
||||
ret = -EINVAL;
|
||||
goto err_free_fragments;
|
||||
}
|
||||
|
@ -642,11 +660,24 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs)
|
|||
}
|
||||
kfree(ovcs->fragments);
|
||||
|
||||
/*
|
||||
* TODO
|
||||
*
|
||||
* would like to: kfree(ovcs->overlay_tree);
|
||||
* but can not since drivers may have pointers into this data
|
||||
*
|
||||
* would like to: kfree(ovcs->fdt);
|
||||
* but can not since drivers may have pointers into this data
|
||||
*/
|
||||
|
||||
kfree(ovcs);
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* internal documentation
|
||||
*
|
||||
* of_overlay_apply() - Create and apply an overlay changeset
|
||||
* @fdt: the FDT that was unflattened to create @tree
|
||||
* @tree: Expanded overlay device tree
|
||||
* @ovcs_id: Pointer to overlay changeset id
|
||||
*
|
||||
|
@ -685,21 +716,29 @@ static void free_overlay_changeset(struct overlay_changeset *ovcs)
|
|||
* id is returned to *ovcs_id.
|
||||
*/
|
||||
|
||||
int of_overlay_apply(struct device_node *tree, int *ovcs_id)
|
||||
static int of_overlay_apply(const void *fdt, struct device_node *tree,
|
||||
int *ovcs_id)
|
||||
{
|
||||
struct overlay_changeset *ovcs;
|
||||
int ret = 0, ret_revert, ret_tmp;
|
||||
|
||||
*ovcs_id = 0;
|
||||
/*
|
||||
* As of this point, fdt and tree belong to the overlay changeset.
|
||||
* overlay changeset code is responsible for freeing them.
|
||||
*/
|
||||
|
||||
if (devicetree_corrupt()) {
|
||||
pr_err("devicetree state suspect, refuse to apply overlay\n");
|
||||
kfree(fdt);
|
||||
kfree(tree);
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ovcs = kzalloc(sizeof(*ovcs), GFP_KERNEL);
|
||||
if (!ovcs) {
|
||||
kfree(fdt);
|
||||
kfree(tree);
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
@ -709,12 +748,17 @@ int of_overlay_apply(struct device_node *tree, int *ovcs_id)
|
|||
|
||||
ret = of_resolve_phandles(tree);
|
||||
if (ret)
|
||||
goto err_free_overlay_changeset;
|
||||
goto err_free_tree;
|
||||
|
||||
ret = init_overlay_changeset(ovcs, tree);
|
||||
ret = init_overlay_changeset(ovcs, fdt, tree);
|
||||
if (ret)
|
||||
goto err_free_overlay_changeset;
|
||||
goto err_free_tree;
|
||||
|
||||
/*
|
||||
* after overlay_notify(), ovcs->overlay_tree related pointers may have
|
||||
* leaked to drivers, so can not kfree() tree, aka ovcs->overlay_tree;
|
||||
* and can not free fdt, aka ovcs->fdt
|
||||
*/
|
||||
ret = overlay_notify(ovcs, OF_OVERLAY_PRE_APPLY);
|
||||
if (ret) {
|
||||
pr_err("overlay changeset pre-apply notify error %d\n", ret);
|
||||
|
@ -754,6 +798,10 @@ int of_overlay_apply(struct device_node *tree, int *ovcs_id)
|
|||
|
||||
goto out_unlock;
|
||||
|
||||
err_free_tree:
|
||||
kfree(fdt);
|
||||
kfree(tree);
|
||||
|
||||
err_free_overlay_changeset:
|
||||
free_overlay_changeset(ovcs);
|
||||
|
||||
|
@ -766,7 +814,63 @@ out:
|
|||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_overlay_apply);
|
||||
|
||||
int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size,
|
||||
int *ovcs_id)
|
||||
{
|
||||
const void *new_fdt;
|
||||
int ret;
|
||||
u32 size;
|
||||
struct device_node *overlay_root;
|
||||
|
||||
*ovcs_id = 0;
|
||||
ret = 0;
|
||||
|
||||
if (overlay_fdt_size < sizeof(struct fdt_header) ||
|
||||
fdt_check_header(overlay_fdt)) {
|
||||
pr_err("Invalid overlay_fdt header\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
size = fdt_totalsize(overlay_fdt);
|
||||
if (overlay_fdt_size < size)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Must create permanent copy of FDT because of_fdt_unflatten_tree()
|
||||
* will create pointers to the passed in FDT in the unflattened tree.
|
||||
*/
|
||||
new_fdt = kmemdup(overlay_fdt, size, GFP_KERNEL);
|
||||
if (!new_fdt)
|
||||
return -ENOMEM;
|
||||
|
||||
of_fdt_unflatten_tree(new_fdt, NULL, &overlay_root);
|
||||
if (!overlay_root) {
|
||||
pr_err("unable to unflatten overlay_fdt\n");
|
||||
ret = -EINVAL;
|
||||
goto out_free_new_fdt;
|
||||
}
|
||||
|
||||
ret = of_overlay_apply(new_fdt, overlay_root, ovcs_id);
|
||||
if (ret < 0) {
|
||||
/*
|
||||
* new_fdt and overlay_root now belong to the overlay
|
||||
* changeset.
|
||||
* overlay changeset code is responsible for freeing them.
|
||||
*/
|
||||
goto out;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
out_free_new_fdt:
|
||||
kfree(new_fdt);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(of_overlay_fdt_apply);
|
||||
|
||||
/*
|
||||
* Find @np in @tree.
|
||||
|
|
|
@ -269,17 +269,11 @@ int of_resolve_phandles(struct device_node *overlay)
|
|||
goto out;
|
||||
}
|
||||
|
||||
#if 0
|
||||
Temporarily disable check so that old style overlay unittests
|
||||
do not fail when of_resolve_phandles() is moved into
|
||||
of_overlay_apply().
|
||||
|
||||
if (!of_node_check_flag(overlay, OF_DETACHED)) {
|
||||
pr_err("overlay not detached\n");
|
||||
err = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
#endif
|
||||
|
||||
phandle_delta = live_tree_max_phandle() + 1;
|
||||
adjust_overlay_phandles(overlay, phandle_delta);
|
||||
|
|
|
@ -1,8 +1,22 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
DTC_FLAGS_testcases := -Wno-interrupts_property
|
||||
obj-y += testcases.dtb.o
|
||||
|
||||
obj-$(CONFIG_OF_OVERLAY) += overlay.dtb.o \
|
||||
overlay_0.dtb.o \
|
||||
overlay_1.dtb.o \
|
||||
overlay_2.dtb.o \
|
||||
overlay_3.dtb.o \
|
||||
overlay_4.dtb.o \
|
||||
overlay_5.dtb.o \
|
||||
overlay_6.dtb.o \
|
||||
overlay_7.dtb.o \
|
||||
overlay_8.dtb.o \
|
||||
overlay_9.dtb.o \
|
||||
overlay_10.dtb.o \
|
||||
overlay_11.dtb.o \
|
||||
overlay_12.dtb.o \
|
||||
overlay_13.dtb.o \
|
||||
overlay_15.dtb.o \
|
||||
overlay_bad_phandle.dtb.o \
|
||||
overlay_bad_symbol.dtb.o \
|
||||
overlay_base.dtb.o
|
||||
|
@ -10,10 +24,14 @@ obj-$(CONFIG_OF_OVERLAY) += overlay.dtb.o \
|
|||
targets += $(foreach suffix, dtb dtb.S, $(patsubst %.dtb.o,%.$(suffix),$(obj-y)))
|
||||
|
||||
# enable creation of __symbols__ node
|
||||
DTC_FLAGS_overlay := -@
|
||||
DTC_FLAGS_overlay_bad_phandle := -@
|
||||
DTC_FLAGS_overlay_bad_symbol := -@
|
||||
DTC_FLAGS_overlay_base := -@
|
||||
DTC_FLAGS_overlay += -@
|
||||
DTC_FLAGS_overlay_bad_phandle += -@
|
||||
DTC_FLAGS_overlay_bad_symbol += -@
|
||||
DTC_FLAGS_overlay_base += -@
|
||||
DTC_FLAGS_testcases += -@
|
||||
|
||||
# suppress warnings about intentional errors
|
||||
DTC_FLAGS_testcases += -Wno-interrupts_property
|
||||
|
||||
.PRECIOUS: \
|
||||
$(obj)/%.dtb.S \
|
||||
|
|
|
@ -2,76 +2,63 @@
|
|||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/ {
|
||||
&electric_1 {
|
||||
|
||||
fragment@0 {
|
||||
target = <&electric_1>;
|
||||
status = "okay";
|
||||
|
||||
__overlay__ {
|
||||
status = "okay";
|
||||
|
||||
hvac_2: hvac-large-1 {
|
||||
compatible = "ot,hvac-large";
|
||||
heat-range = < 40 75 >;
|
||||
cool-range = < 65 80 >;
|
||||
};
|
||||
};
|
||||
hvac_2: hvac-large-1 {
|
||||
compatible = "ot,hvac-large";
|
||||
heat-range = < 40 75 >;
|
||||
cool-range = < 65 80 >;
|
||||
};
|
||||
|
||||
fragment@1 {
|
||||
target = <&rides_1>;
|
||||
|
||||
__overlay__ {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
status = "okay";
|
||||
|
||||
ride@100 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
track@30 {
|
||||
incline-up = < 48 32 16 >;
|
||||
};
|
||||
|
||||
track@40 {
|
||||
incline-up = < 47 31 15 >;
|
||||
};
|
||||
};
|
||||
|
||||
ride_200: ride@200 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
compatible = "ot,ferris-wheel";
|
||||
reg = < 0x00000200 0x100 >;
|
||||
hvac-provider = < &hvac_2 >;
|
||||
hvac-thermostat = < 27 32 > ;
|
||||
hvac-zones = < 12 5 >;
|
||||
hvac-zone-names = "operator", "snack-bar";
|
||||
spin-controller = < &spin_ctrl_1 3 >;
|
||||
spin-rph = < 30 >;
|
||||
gondolas = < 16 >;
|
||||
gondola-capacity = < 6 >;
|
||||
|
||||
ride_200_left: track@10 {
|
||||
reg = < 0x00000010 0x10 >;
|
||||
};
|
||||
|
||||
ride_200_right: track@20 {
|
||||
reg = < 0x00000020 0x10 >;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fragment@2 {
|
||||
target = <&lights_2>;
|
||||
|
||||
__overlay__ {
|
||||
status = "okay";
|
||||
color = "purple", "white", "red", "green";
|
||||
rate = < 3 256 >;
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
&rides_1 {
|
||||
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
status = "okay";
|
||||
|
||||
ride@100 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
track@30 {
|
||||
incline-up = < 48 32 16 >;
|
||||
};
|
||||
|
||||
track@40 {
|
||||
incline-up = < 47 31 15 >;
|
||||
};
|
||||
};
|
||||
|
||||
ride_200: ride@200 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
compatible = "ot,ferris-wheel";
|
||||
reg = < 0x00000200 0x100 >;
|
||||
hvac-provider = < &hvac_2 >;
|
||||
hvac-thermostat = < 27 32 > ;
|
||||
hvac-zones = < 12 5 >;
|
||||
hvac-zone-names = "operator", "snack-bar";
|
||||
spin-controller = < &spin_ctrl_1 3 >;
|
||||
spin-rph = < 30 >;
|
||||
gondolas = < 16 >;
|
||||
gondola-capacity = < 6 >;
|
||||
|
||||
ride_200_left: track@10 {
|
||||
reg = < 0x00000010 0x10 >;
|
||||
};
|
||||
|
||||
ride_200_right: track@20 {
|
||||
reg = < 0x00000020 0x10 >;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&lights_2 {
|
||||
|
||||
status = "okay";
|
||||
color = "purple", "white", "red", "green";
|
||||
rate = < 3 256 >;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/ {
|
||||
/* overlay_0 - enable using absolute target path */
|
||||
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus/test-unittest0";
|
||||
__overlay__ {
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/ {
|
||||
/* overlay_1 - disable using absolute target path */
|
||||
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus/test-unittest1";
|
||||
__overlay__ {
|
||||
status = "disabled";
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/* overlay_10 */
|
||||
/* overlays 8, 9, 10, 11 application and removal in bad sequence */
|
||||
|
||||
&unittest_test_bus {
|
||||
/* suppress DTC warning */
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
test-unittest10 {
|
||||
compatible = "unittest";
|
||||
status = "okay";
|
||||
reg = <10>;
|
||||
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
test-unittest101 {
|
||||
compatible = "unittest";
|
||||
status = "okay";
|
||||
reg = <1>;
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/* overlay_11 */
|
||||
/* overlays 8, 9, 10, 11 application and removal in bad sequence */
|
||||
|
||||
&unittest_test_bus {
|
||||
/* suppress DTC warning */
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
test-unittest11 {
|
||||
compatible = "unittest";
|
||||
status = "okay";
|
||||
reg = <11>;
|
||||
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
test-unittest111 {
|
||||
compatible = "unittest";
|
||||
status = "okay";
|
||||
reg = <1>;
|
||||
};
|
||||
|
||||
};
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/ {
|
||||
/* overlay_12 - enable using absolute target path (i2c) */
|
||||
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest12";
|
||||
__overlay__ {
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/ {
|
||||
/* overlay_13 - disable using absolute target path (i2c) */
|
||||
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest13";
|
||||
__overlay__ {
|
||||
status = "disabled";
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/* overlay_15 - mux overlay */
|
||||
|
||||
&unittest_i2c_test_bus {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
test-unittest15 {
|
||||
reg = <11>;
|
||||
compatible = "unittest-i2c-mux";
|
||||
status = "okay";
|
||||
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
i2c@0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <0>;
|
||||
|
||||
test-mux-dev {
|
||||
reg = <32>;
|
||||
compatible = "unittest-i2c-dev";
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/* overlay_2 - enable using label */
|
||||
|
||||
&unittest2 {
|
||||
status = "okay";
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/* overlay_3 - disable using label */
|
||||
|
||||
&unittest3 {
|
||||
status = "disabled";
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/* overlay_4 - test insertion of a full node */
|
||||
|
||||
&unittest_test_bus {
|
||||
|
||||
/* suppress DTC warning */
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
test-unittest4 {
|
||||
compatible = "unittest";
|
||||
status = "okay";
|
||||
reg = <4>;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/* overlay_5 - test overlay apply revert */
|
||||
|
||||
&unittest5 {
|
||||
status = "okay";
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/* overlay_6 */
|
||||
/* overlays 6, 7 application and removal in sequence */
|
||||
|
||||
&unittest6 {
|
||||
status = "okay";
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/* overlay_7 */
|
||||
/* overlays 6, 7 application and removal in sequence */
|
||||
|
||||
&unittest7 {
|
||||
status = "okay";
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/* overlay_8 */
|
||||
/* overlays 8, 9, 10, 11 application and removal in bad sequence */
|
||||
|
||||
&unittest8 {
|
||||
status = "okay";
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/* overlay_9 */
|
||||
/* overlays 8, 9, 10, 11 application and removal in bad sequence */
|
||||
|
||||
&unittest8 {
|
||||
property-foo = "bar";
|
||||
};
|
|
@ -2,20 +2,13 @@
|
|||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/ {
|
||||
&electric_1 {
|
||||
|
||||
fragment@0 {
|
||||
target = <&electric_1>;
|
||||
|
||||
__overlay__ {
|
||||
|
||||
// This label should cause an error when the overlay
|
||||
// is applied. There is already a phandle value
|
||||
// in the base tree for motor-1.
|
||||
spin_ctrl_1_conflict: motor-1 {
|
||||
accelerate = < 3 >;
|
||||
decelerate = < 5 >;
|
||||
};
|
||||
};
|
||||
// This label should cause an error when the overlay
|
||||
// is applied. There is already a phandle value
|
||||
// in the base tree for motor-1.
|
||||
spin_ctrl_1_conflict: motor-1 {
|
||||
accelerate = < 3 >;
|
||||
decelerate = < 5 >;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,22 +2,15 @@
|
|||
/dts-v1/;
|
||||
/plugin/;
|
||||
|
||||
/ {
|
||||
&electric_1 {
|
||||
|
||||
fragment@0 {
|
||||
target = <&electric_1>;
|
||||
|
||||
__overlay__ {
|
||||
|
||||
// This label should cause an error when the overlay
|
||||
// is applied. There is already a symbol hvac_1
|
||||
// in the base tree
|
||||
hvac_1: hvac-medium-2 {
|
||||
compatible = "ot,hvac-medium";
|
||||
heat-range = < 50 75 >;
|
||||
cool-range = < 60 80 >;
|
||||
};
|
||||
|
||||
};
|
||||
// This label should cause an error when the overlay
|
||||
// is applied. There is already a symbol hvac_1
|
||||
// in the base tree
|
||||
hvac_1: hvac-medium-2 {
|
||||
compatible = "ot,hvac-medium";
|
||||
heat-range = < 50 75 >;
|
||||
cool-range = < 60 80 >;
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
overlay-node {
|
||||
|
||||
/* test bus */
|
||||
unittestbus: test-bus {
|
||||
unittest_test_bus: test-bus {
|
||||
compatible = "simple-bus";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
@ -70,7 +70,7 @@
|
|||
reg = <8>;
|
||||
};
|
||||
|
||||
i2c-test-bus {
|
||||
unittest_i2c_test_bus: i2c-test-bus {
|
||||
compatible = "unittest-i2c-bus";
|
||||
status = "okay";
|
||||
reg = <50>;
|
||||
|
@ -113,218 +113,5 @@
|
|||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* test enable using absolute target path */
|
||||
overlay0 {
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus/test-unittest0";
|
||||
__overlay__ {
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* test disable using absolute target path */
|
||||
overlay1 {
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus/test-unittest1";
|
||||
__overlay__ {
|
||||
status = "disabled";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* test enable using label */
|
||||
overlay2 {
|
||||
fragment@0 {
|
||||
target = <&unittest2>;
|
||||
__overlay__ {
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* test disable using label */
|
||||
overlay3 {
|
||||
fragment@0 {
|
||||
target = <&unittest3>;
|
||||
__overlay__ {
|
||||
status = "disabled";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* test insertion of a full node */
|
||||
overlay4 {
|
||||
fragment@0 {
|
||||
target = <&unittestbus>;
|
||||
__overlay__ {
|
||||
|
||||
/* suppress DTC warning */
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
test-unittest4 {
|
||||
compatible = "unittest";
|
||||
status = "okay";
|
||||
reg = <4>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* test overlay apply revert */
|
||||
overlay5 {
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus/test-unittest5";
|
||||
__overlay__ {
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* test overlays application and removal in sequence */
|
||||
overlay6 {
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus/test-unittest6";
|
||||
__overlay__ {
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
};
|
||||
overlay7 {
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus/test-unittest7";
|
||||
__overlay__ {
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* test overlays application and removal in bad sequence */
|
||||
overlay8 {
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus/test-unittest8";
|
||||
__overlay__ {
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
};
|
||||
overlay9 {
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus/test-unittest8";
|
||||
__overlay__ {
|
||||
property-foo = "bar";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
overlay10 {
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus";
|
||||
__overlay__ {
|
||||
|
||||
/* suppress DTC warning */
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
test-unittest10 {
|
||||
compatible = "unittest";
|
||||
status = "okay";
|
||||
reg = <10>;
|
||||
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
test-unittest101 {
|
||||
compatible = "unittest";
|
||||
status = "okay";
|
||||
reg = <1>;
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
overlay11 {
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus";
|
||||
__overlay__ {
|
||||
|
||||
/* suppress DTC warning */
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
test-unittest11 {
|
||||
compatible = "unittest";
|
||||
status = "okay";
|
||||
reg = <11>;
|
||||
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
test-unittest111 {
|
||||
compatible = "unittest";
|
||||
status = "okay";
|
||||
reg = <1>;
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* test enable using absolute target path (i2c) */
|
||||
overlay12 {
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest12";
|
||||
__overlay__ {
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* test disable using absolute target path (i2c) */
|
||||
overlay13 {
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus/i2c-test-bus/test-unittest13";
|
||||
__overlay__ {
|
||||
status = "disabled";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* test mux overlay */
|
||||
overlay15 {
|
||||
fragment@0 {
|
||||
target-path = "/testcase-data/overlay-node/test-bus/i2c-test-bus";
|
||||
__overlay__ {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
test-unittest15 {
|
||||
reg = <11>;
|
||||
compatible = "unittest-i2c-mux";
|
||||
status = "okay";
|
||||
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
i2c@0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <0>;
|
||||
|
||||
test-mux-dev {
|
||||
reg = <32>;
|
||||
compatible = "unittest-i2c-dev";
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
|
|
@ -45,6 +45,8 @@ static struct unittest_results {
|
|||
failed; \
|
||||
})
|
||||
|
||||
static int __init overlay_data_apply(const char *overlay_name, int *overlay_id);
|
||||
|
||||
static void __init of_unittest_find_node_by_name(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
@ -997,8 +999,7 @@ static int __init unittest_data_add(void)
|
|||
}
|
||||
|
||||
/*
|
||||
* This lock normally encloses of_overlay_apply() as well as
|
||||
* of_resolve_phandles().
|
||||
* This lock normally encloses of_resolve_phandles()
|
||||
*/
|
||||
of_overlay_mutex_lock();
|
||||
|
||||
|
@ -1191,12 +1192,12 @@ static int of_unittest_device_exists(int unittest_nr, enum overlay_type ovtype)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const char *overlay_path(int nr)
|
||||
static const char *overlay_name_from_nr(int nr)
|
||||
{
|
||||
static char buf[256];
|
||||
|
||||
snprintf(buf, sizeof(buf) - 1,
|
||||
"/testcase-data/overlay%d", nr);
|
||||
"overlay_%d", nr);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
return buf;
|
||||
|
@ -1263,25 +1264,19 @@ static void of_unittest_destroy_tracked_overlays(void)
|
|||
} while (defers > 0);
|
||||
}
|
||||
|
||||
static int of_unittest_apply_overlay(int overlay_nr, int unittest_nr,
|
||||
static int __init of_unittest_apply_overlay(int overlay_nr, int unittest_nr,
|
||||
int *overlay_id)
|
||||
{
|
||||
struct device_node *np = NULL;
|
||||
const char *overlay_name;
|
||||
int ret;
|
||||
|
||||
np = of_find_node_by_path(overlay_path(overlay_nr));
|
||||
if (np == NULL) {
|
||||
unittest(0, "could not find overlay node @\"%s\"\n",
|
||||
overlay_path(overlay_nr));
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
overlay_name = overlay_name_from_nr(overlay_nr);
|
||||
|
||||
*overlay_id = 0;
|
||||
ret = of_overlay_apply(np, overlay_id);
|
||||
if (ret < 0) {
|
||||
unittest(0, "could not create overlay from \"%s\"\n",
|
||||
overlay_path(overlay_nr));
|
||||
ret = overlay_data_apply(overlay_name, overlay_id);
|
||||
if (!ret) {
|
||||
unittest(0, "could not apply overlay \"%s\"\n",
|
||||
overlay_name);
|
||||
goto out;
|
||||
}
|
||||
of_unittest_track_overlay(*overlay_id);
|
||||
|
@ -1295,15 +1290,16 @@ out:
|
|||
}
|
||||
|
||||
/* apply an overlay while checking before and after states */
|
||||
static int of_unittest_apply_overlay_check(int overlay_nr, int unittest_nr,
|
||||
int before, int after, enum overlay_type ovtype)
|
||||
static int __init of_unittest_apply_overlay_check(int overlay_nr,
|
||||
int unittest_nr, int before, int after,
|
||||
enum overlay_type ovtype)
|
||||
{
|
||||
int ret, ovcs_id;
|
||||
|
||||
/* unittest device must not be in before state */
|
||||
if (of_unittest_device_exists(unittest_nr, ovtype) != before) {
|
||||
unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
|
||||
overlay_path(overlay_nr),
|
||||
unittest(0, "%s with device @\"%s\" %s\n",
|
||||
overlay_name_from_nr(overlay_nr),
|
||||
unittest_path(unittest_nr, ovtype),
|
||||
!before ? "enabled" : "disabled");
|
||||
return -EINVAL;
|
||||
|
@ -1318,8 +1314,8 @@ static int of_unittest_apply_overlay_check(int overlay_nr, int unittest_nr,
|
|||
|
||||
/* unittest device must be to set to after state */
|
||||
if (of_unittest_device_exists(unittest_nr, ovtype) != after) {
|
||||
unittest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n",
|
||||
overlay_path(overlay_nr),
|
||||
unittest(0, "%s failed to create @\"%s\" %s\n",
|
||||
overlay_name_from_nr(overlay_nr),
|
||||
unittest_path(unittest_nr, ovtype),
|
||||
!after ? "enabled" : "disabled");
|
||||
return -EINVAL;
|
||||
|
@ -1329,7 +1325,7 @@ static int of_unittest_apply_overlay_check(int overlay_nr, int unittest_nr,
|
|||
}
|
||||
|
||||
/* apply an overlay and then revert it while checking before, after states */
|
||||
static int of_unittest_apply_revert_overlay_check(int overlay_nr,
|
||||
static int __init of_unittest_apply_revert_overlay_check(int overlay_nr,
|
||||
int unittest_nr, int before, int after,
|
||||
enum overlay_type ovtype)
|
||||
{
|
||||
|
@ -1337,8 +1333,8 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr,
|
|||
|
||||
/* unittest device must be in before state */
|
||||
if (of_unittest_device_exists(unittest_nr, ovtype) != before) {
|
||||
unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
|
||||
overlay_path(overlay_nr),
|
||||
unittest(0, "%s with device @\"%s\" %s\n",
|
||||
overlay_name_from_nr(overlay_nr),
|
||||
unittest_path(unittest_nr, ovtype),
|
||||
!before ? "enabled" : "disabled");
|
||||
return -EINVAL;
|
||||
|
@ -1354,8 +1350,8 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr,
|
|||
|
||||
/* unittest device must be in after state */
|
||||
if (of_unittest_device_exists(unittest_nr, ovtype) != after) {
|
||||
unittest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n",
|
||||
overlay_path(overlay_nr),
|
||||
unittest(0, "%s failed to create @\"%s\" %s\n",
|
||||
overlay_name_from_nr(overlay_nr),
|
||||
unittest_path(unittest_nr, ovtype),
|
||||
!after ? "enabled" : "disabled");
|
||||
return -EINVAL;
|
||||
|
@ -1363,16 +1359,16 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr,
|
|||
|
||||
ret = of_overlay_remove(&ovcs_id);
|
||||
if (ret != 0) {
|
||||
unittest(0, "overlay @\"%s\" failed to be destroyed @\"%s\"\n",
|
||||
overlay_path(overlay_nr),
|
||||
unittest(0, "%s failed to be destroyed @\"%s\"\n",
|
||||
overlay_name_from_nr(overlay_nr),
|
||||
unittest_path(unittest_nr, ovtype));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* unittest device must be again in before state */
|
||||
if (of_unittest_device_exists(unittest_nr, PDEV_OVERLAY) != before) {
|
||||
unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
|
||||
overlay_path(overlay_nr),
|
||||
unittest(0, "%s with device @\"%s\" %s\n",
|
||||
overlay_name_from_nr(overlay_nr),
|
||||
unittest_path(unittest_nr, ovtype),
|
||||
!before ? "enabled" : "disabled");
|
||||
return -EINVAL;
|
||||
|
@ -1382,7 +1378,7 @@ static int of_unittest_apply_revert_overlay_check(int overlay_nr,
|
|||
}
|
||||
|
||||
/* test activation of device */
|
||||
static void of_unittest_overlay_0(void)
|
||||
static void __init of_unittest_overlay_0(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -1395,7 +1391,7 @@ static void of_unittest_overlay_0(void)
|
|||
}
|
||||
|
||||
/* test deactivation of device */
|
||||
static void of_unittest_overlay_1(void)
|
||||
static void __init of_unittest_overlay_1(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -1408,7 +1404,7 @@ static void of_unittest_overlay_1(void)
|
|||
}
|
||||
|
||||
/* test activation of device */
|
||||
static void of_unittest_overlay_2(void)
|
||||
static void __init of_unittest_overlay_2(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -1421,7 +1417,7 @@ static void of_unittest_overlay_2(void)
|
|||
}
|
||||
|
||||
/* test deactivation of device */
|
||||
static void of_unittest_overlay_3(void)
|
||||
static void __init of_unittest_overlay_3(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -1434,7 +1430,7 @@ static void of_unittest_overlay_3(void)
|
|||
}
|
||||
|
||||
/* test activation of a full device node */
|
||||
static void of_unittest_overlay_4(void)
|
||||
static void __init of_unittest_overlay_4(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -1447,7 +1443,7 @@ static void of_unittest_overlay_4(void)
|
|||
}
|
||||
|
||||
/* test overlay apply/revert sequence */
|
||||
static void of_unittest_overlay_5(void)
|
||||
static void __init of_unittest_overlay_5(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -1460,19 +1456,19 @@ static void of_unittest_overlay_5(void)
|
|||
}
|
||||
|
||||
/* test overlay application in sequence */
|
||||
static void of_unittest_overlay_6(void)
|
||||
static void __init of_unittest_overlay_6(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
int ret, i, ov_id[2], ovcs_id;
|
||||
int overlay_nr = 6, unittest_nr = 6;
|
||||
int before = 0, after = 1;
|
||||
const char *overlay_name;
|
||||
|
||||
/* unittest device must be in before state */
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (of_unittest_device_exists(unittest_nr + i, PDEV_OVERLAY)
|
||||
!= before) {
|
||||
unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
|
||||
overlay_path(overlay_nr + i),
|
||||
unittest(0, "%s with device @\"%s\" %s\n",
|
||||
overlay_name_from_nr(overlay_nr + i),
|
||||
unittest_path(unittest_nr + i,
|
||||
PDEV_OVERLAY),
|
||||
!before ? "enabled" : "disabled");
|
||||
|
@ -1483,18 +1479,12 @@ static void of_unittest_overlay_6(void)
|
|||
/* apply the overlays */
|
||||
for (i = 0; i < 2; i++) {
|
||||
|
||||
np = of_find_node_by_path(overlay_path(overlay_nr + i));
|
||||
if (np == NULL) {
|
||||
unittest(0, "could not find overlay node @\"%s\"\n",
|
||||
overlay_path(overlay_nr + i));
|
||||
return;
|
||||
}
|
||||
overlay_name = overlay_name_from_nr(overlay_nr + i);
|
||||
|
||||
ovcs_id = 0;
|
||||
ret = of_overlay_apply(np, &ovcs_id);
|
||||
if (ret < 0) {
|
||||
unittest(0, "could not create overlay from \"%s\"\n",
|
||||
overlay_path(overlay_nr + i));
|
||||
ret = overlay_data_apply(overlay_name, &ovcs_id);
|
||||
if (!ret) {
|
||||
unittest(0, "could not apply overlay \"%s\"\n",
|
||||
overlay_name);
|
||||
return;
|
||||
}
|
||||
ov_id[i] = ovcs_id;
|
||||
|
@ -1506,7 +1496,7 @@ static void of_unittest_overlay_6(void)
|
|||
if (of_unittest_device_exists(unittest_nr + i, PDEV_OVERLAY)
|
||||
!= after) {
|
||||
unittest(0, "overlay @\"%s\" failed @\"%s\" %s\n",
|
||||
overlay_path(overlay_nr + i),
|
||||
overlay_name_from_nr(overlay_nr + i),
|
||||
unittest_path(unittest_nr + i,
|
||||
PDEV_OVERLAY),
|
||||
!after ? "enabled" : "disabled");
|
||||
|
@ -1518,8 +1508,8 @@ static void of_unittest_overlay_6(void)
|
|||
ovcs_id = ov_id[i];
|
||||
ret = of_overlay_remove(&ovcs_id);
|
||||
if (ret != 0) {
|
||||
unittest(0, "overlay @\"%s\" failed destroy @\"%s\"\n",
|
||||
overlay_path(overlay_nr + i),
|
||||
unittest(0, "%s failed destroy @\"%s\"\n",
|
||||
overlay_name_from_nr(overlay_nr + i),
|
||||
unittest_path(unittest_nr + i,
|
||||
PDEV_OVERLAY));
|
||||
return;
|
||||
|
@ -1531,8 +1521,8 @@ static void of_unittest_overlay_6(void)
|
|||
/* unittest device must be again in before state */
|
||||
if (of_unittest_device_exists(unittest_nr + i, PDEV_OVERLAY)
|
||||
!= before) {
|
||||
unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n",
|
||||
overlay_path(overlay_nr + i),
|
||||
unittest(0, "%s with device @\"%s\" %s\n",
|
||||
overlay_name_from_nr(overlay_nr + i),
|
||||
unittest_path(unittest_nr + i,
|
||||
PDEV_OVERLAY),
|
||||
!before ? "enabled" : "disabled");
|
||||
|
@ -1544,29 +1534,23 @@ static void of_unittest_overlay_6(void)
|
|||
}
|
||||
|
||||
/* test overlay application in sequence */
|
||||
static void of_unittest_overlay_8(void)
|
||||
static void __init of_unittest_overlay_8(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
int ret, i, ov_id[2], ovcs_id;
|
||||
int overlay_nr = 8, unittest_nr = 8;
|
||||
const char *overlay_name;
|
||||
|
||||
/* we don't care about device state in this test */
|
||||
|
||||
/* apply the overlays */
|
||||
for (i = 0; i < 2; i++) {
|
||||
|
||||
np = of_find_node_by_path(overlay_path(overlay_nr + i));
|
||||
if (np == NULL) {
|
||||
unittest(0, "could not find overlay node @\"%s\"\n",
|
||||
overlay_path(overlay_nr + i));
|
||||
return;
|
||||
}
|
||||
overlay_name = overlay_name_from_nr(overlay_nr + i);
|
||||
|
||||
ovcs_id = 0;
|
||||
ret = of_overlay_apply(np, &ovcs_id);
|
||||
ret = overlay_data_apply(overlay_name, &ovcs_id);
|
||||
if (ret < 0) {
|
||||
unittest(0, "could not create overlay from \"%s\"\n",
|
||||
overlay_path(overlay_nr + i));
|
||||
unittest(0, "could not apply overlay \"%s\"\n",
|
||||
overlay_name);
|
||||
return;
|
||||
}
|
||||
ov_id[i] = ovcs_id;
|
||||
|
@ -1577,8 +1561,8 @@ static void of_unittest_overlay_8(void)
|
|||
ovcs_id = ov_id[0];
|
||||
ret = of_overlay_remove(&ovcs_id);
|
||||
if (ret == 0) {
|
||||
unittest(0, "overlay @\"%s\" was destroyed @\"%s\"\n",
|
||||
overlay_path(overlay_nr + 0),
|
||||
unittest(0, "%s was destroyed @\"%s\"\n",
|
||||
overlay_name_from_nr(overlay_nr + 0),
|
||||
unittest_path(unittest_nr,
|
||||
PDEV_OVERLAY));
|
||||
return;
|
||||
|
@ -1589,8 +1573,8 @@ static void of_unittest_overlay_8(void)
|
|||
ovcs_id = ov_id[i];
|
||||
ret = of_overlay_remove(&ovcs_id);
|
||||
if (ret != 0) {
|
||||
unittest(0, "overlay @\"%s\" not destroyed @\"%s\"\n",
|
||||
overlay_path(overlay_nr + i),
|
||||
unittest(0, "%s not destroyed @\"%s\"\n",
|
||||
overlay_name_from_nr(overlay_nr + i),
|
||||
unittest_path(unittest_nr,
|
||||
PDEV_OVERLAY));
|
||||
return;
|
||||
|
@ -1602,7 +1586,7 @@ static void of_unittest_overlay_8(void)
|
|||
}
|
||||
|
||||
/* test insertion of a bus with parent devices */
|
||||
static void of_unittest_overlay_10(void)
|
||||
static void __init of_unittest_overlay_10(void)
|
||||
{
|
||||
int ret;
|
||||
char *child_path;
|
||||
|
@ -1625,7 +1609,7 @@ static void of_unittest_overlay_10(void)
|
|||
}
|
||||
|
||||
/* test insertion of a bus with parent devices (and revert) */
|
||||
static void of_unittest_overlay_11(void)
|
||||
static void __init of_unittest_overlay_11(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -1891,7 +1875,7 @@ static void of_unittest_overlay_i2c_cleanup(void)
|
|||
i2c_del_driver(&unittest_i2c_dev_driver);
|
||||
}
|
||||
|
||||
static void of_unittest_overlay_i2c_12(void)
|
||||
static void __init of_unittest_overlay_i2c_12(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -1904,7 +1888,7 @@ static void of_unittest_overlay_i2c_12(void)
|
|||
}
|
||||
|
||||
/* test deactivation of device */
|
||||
static void of_unittest_overlay_i2c_13(void)
|
||||
static void __init of_unittest_overlay_i2c_13(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -1921,7 +1905,7 @@ static void of_unittest_overlay_i2c_14(void)
|
|||
{
|
||||
}
|
||||
|
||||
static void of_unittest_overlay_i2c_15(void)
|
||||
static void __init of_unittest_overlay_i2c_15(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -2023,23 +2007,38 @@ static inline void __init of_unittest_overlay(void) { }
|
|||
extern uint8_t __dtb_##name##_begin[]; \
|
||||
extern uint8_t __dtb_##name##_end[]
|
||||
|
||||
#define OVERLAY_INFO(name, expected) \
|
||||
{ .dtb_begin = __dtb_##name##_begin, \
|
||||
.dtb_end = __dtb_##name##_end, \
|
||||
.expected_result = expected, \
|
||||
#define OVERLAY_INFO(overlay_name, expected) \
|
||||
{ .dtb_begin = __dtb_##overlay_name##_begin, \
|
||||
.dtb_end = __dtb_##overlay_name##_end, \
|
||||
.expected_result = expected, \
|
||||
.name = #overlay_name, \
|
||||
}
|
||||
|
||||
struct overlay_info {
|
||||
uint8_t *dtb_begin;
|
||||
uint8_t *dtb_end;
|
||||
void *data;
|
||||
struct device_node *np_overlay;
|
||||
int expected_result;
|
||||
int overlay_id;
|
||||
uint8_t *dtb_begin;
|
||||
uint8_t *dtb_end;
|
||||
int expected_result;
|
||||
int overlay_id;
|
||||
char *name;
|
||||
};
|
||||
|
||||
OVERLAY_INFO_EXTERN(overlay_base);
|
||||
OVERLAY_INFO_EXTERN(overlay);
|
||||
OVERLAY_INFO_EXTERN(overlay_0);
|
||||
OVERLAY_INFO_EXTERN(overlay_1);
|
||||
OVERLAY_INFO_EXTERN(overlay_2);
|
||||
OVERLAY_INFO_EXTERN(overlay_3);
|
||||
OVERLAY_INFO_EXTERN(overlay_4);
|
||||
OVERLAY_INFO_EXTERN(overlay_5);
|
||||
OVERLAY_INFO_EXTERN(overlay_6);
|
||||
OVERLAY_INFO_EXTERN(overlay_7);
|
||||
OVERLAY_INFO_EXTERN(overlay_8);
|
||||
OVERLAY_INFO_EXTERN(overlay_9);
|
||||
OVERLAY_INFO_EXTERN(overlay_10);
|
||||
OVERLAY_INFO_EXTERN(overlay_11);
|
||||
OVERLAY_INFO_EXTERN(overlay_12);
|
||||
OVERLAY_INFO_EXTERN(overlay_13);
|
||||
OVERLAY_INFO_EXTERN(overlay_15);
|
||||
OVERLAY_INFO_EXTERN(overlay_bad_phandle);
|
||||
OVERLAY_INFO_EXTERN(overlay_bad_symbol);
|
||||
|
||||
|
@ -2047,6 +2046,21 @@ OVERLAY_INFO_EXTERN(overlay_bad_symbol);
|
|||
static struct overlay_info overlays[] = {
|
||||
OVERLAY_INFO(overlay_base, -9999),
|
||||
OVERLAY_INFO(overlay, 0),
|
||||
OVERLAY_INFO(overlay_0, 0),
|
||||
OVERLAY_INFO(overlay_1, 0),
|
||||
OVERLAY_INFO(overlay_2, 0),
|
||||
OVERLAY_INFO(overlay_3, 0),
|
||||
OVERLAY_INFO(overlay_4, 0),
|
||||
OVERLAY_INFO(overlay_5, 0),
|
||||
OVERLAY_INFO(overlay_6, 0),
|
||||
OVERLAY_INFO(overlay_7, 0),
|
||||
OVERLAY_INFO(overlay_8, 0),
|
||||
OVERLAY_INFO(overlay_9, 0),
|
||||
OVERLAY_INFO(overlay_10, 0),
|
||||
OVERLAY_INFO(overlay_11, 0),
|
||||
OVERLAY_INFO(overlay_12, 0),
|
||||
OVERLAY_INFO(overlay_13, 0),
|
||||
OVERLAY_INFO(overlay_15, 0),
|
||||
OVERLAY_INFO(overlay_bad_phandle, -EINVAL),
|
||||
OVERLAY_INFO(overlay_bad_symbol, -EINVAL),
|
||||
{}
|
||||
|
@ -2077,6 +2091,7 @@ void __init unittest_unflatten_overlay_base(void)
|
|||
{
|
||||
struct overlay_info *info;
|
||||
u32 data_size;
|
||||
void *new_fdt;
|
||||
u32 size;
|
||||
|
||||
info = &overlays[0];
|
||||
|
@ -2098,17 +2113,16 @@ void __init unittest_unflatten_overlay_base(void)
|
|||
return;
|
||||
}
|
||||
|
||||
info->data = dt_alloc_memory(size, roundup_pow_of_two(FDT_V17_SIZE));
|
||||
if (!info->data) {
|
||||
new_fdt = dt_alloc_memory(size, roundup_pow_of_two(FDT_V17_SIZE));
|
||||
if (!new_fdt) {
|
||||
pr_err("alloc for dtb 'overlay_base' failed");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(info->data, info->dtb_begin, size);
|
||||
memcpy(new_fdt, info->dtb_begin, size);
|
||||
|
||||
__unflatten_device_tree(info->data, NULL, &info->np_overlay,
|
||||
__unflatten_device_tree(new_fdt, NULL, &overlay_base_root,
|
||||
dt_alloc_memory, true);
|
||||
overlay_base_root = info->np_overlay;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -2122,73 +2136,44 @@ void __init unittest_unflatten_overlay_base(void)
|
|||
*
|
||||
* Return 0 on unexpected error.
|
||||
*/
|
||||
static int __init overlay_data_add(int onum)
|
||||
static int __init overlay_data_apply(const char *overlay_name, int *overlay_id)
|
||||
{
|
||||
struct overlay_info *info;
|
||||
int found = 0;
|
||||
int k;
|
||||
int ret;
|
||||
u32 size;
|
||||
u32 size_from_header;
|
||||
|
||||
for (k = 0, info = overlays; info; info++, k++) {
|
||||
if (k == onum)
|
||||
for (k = 0, info = overlays; info && info->name; info++, k++) {
|
||||
if (!strcmp(overlay_name, info->name)) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (onum > k)
|
||||
if (!found) {
|
||||
pr_err("no overlay data for %s\n", overlay_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size = info->dtb_end - info->dtb_begin;
|
||||
if (!size) {
|
||||
pr_err("no overlay to attach, %d\n", onum);
|
||||
pr_err("no overlay data for %s\n", overlay_name);
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
size_from_header = fdt_totalsize(info->dtb_begin);
|
||||
if (size_from_header != size) {
|
||||
pr_err("overlay header totalsize != actual size, %d", onum);
|
||||
return 0;
|
||||
}
|
||||
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;
|
||||
|
||||
/*
|
||||
* Must create permanent copy of FDT because of_fdt_unflatten_tree()
|
||||
* will create pointers to the passed in FDT in the EDT.
|
||||
*/
|
||||
info->data = kmemdup(info->dtb_begin, size, GFP_KERNEL);
|
||||
if (!info->data) {
|
||||
pr_err("unable to allocate memory for data, %d\n", onum);
|
||||
return 0;
|
||||
}
|
||||
|
||||
of_fdt_unflatten_tree(info->data, NULL, &info->np_overlay);
|
||||
if (!info->np_overlay) {
|
||||
pr_err("unable to unflatten overlay, %d\n", onum);
|
||||
ret = 0;
|
||||
goto out_free_data;
|
||||
}
|
||||
|
||||
info->overlay_id = 0;
|
||||
ret = of_overlay_apply(info->np_overlay, &info->overlay_id);
|
||||
if (ret < 0) {
|
||||
pr_err("of_overlay_apply() (ret=%d), %d\n", ret, onum);
|
||||
goto out_free_np_overlay;
|
||||
}
|
||||
|
||||
pr_debug("__dtb_overlay_begin applied, overlay id %d\n", ret);
|
||||
|
||||
goto out;
|
||||
|
||||
out_free_np_overlay:
|
||||
/*
|
||||
* info->np_overlay is the unflattened device tree
|
||||
* It has not been spliced into the live tree.
|
||||
*/
|
||||
|
||||
/* todo: function to free unflattened device tree */
|
||||
|
||||
out_free_data:
|
||||
kfree(info->data);
|
||||
pr_debug("%s applied\n", overlay_name);
|
||||
|
||||
out:
|
||||
if (ret != info->expected_result)
|
||||
pr_err("of_overlay_fdt_apply() expected %d, ret=%d, %s\n",
|
||||
info->expected_result, ret, overlay_name);
|
||||
|
||||
return (ret == info->expected_result);
|
||||
}
|
||||
|
||||
|
@ -2290,18 +2275,29 @@ static __init void of_unittest_overlay_high_level(void)
|
|||
__of_attach_node_sysfs(np);
|
||||
|
||||
if (of_symbols) {
|
||||
struct property *new_prop;
|
||||
for_each_property_of_node(overlay_base_symbols, prop) {
|
||||
ret = __of_add_property(of_symbols, prop);
|
||||
if (ret) {
|
||||
unittest(0,
|
||||
"duplicate property '%s' in overlay_base node __symbols__",
|
||||
|
||||
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_sysfs(of_symbols, prop);
|
||||
ret = __of_add_property(of_symbols, new_prop);
|
||||
if (ret) {
|
||||
unittest(0,
|
||||
"unable to add property '%s' in overlay_base node __symbols__ to sysfs",
|
||||
if (!strcmp(new_prop->name, "name")) {
|
||||
/* auto-generated by unflatten */
|
||||
ret = 0;
|
||||
continue;
|
||||
}
|
||||
unittest(0, "duplicate property '%s' in overlay_base node __symbols__",
|
||||
prop->name);
|
||||
goto err_unlock;
|
||||
}
|
||||
ret = __of_add_property_sysfs(of_symbols, new_prop);
|
||||
if (ret) {
|
||||
unittest(0, "unable to add property '%s' in overlay_base node __symbols__ to sysfs",
|
||||
prop->name);
|
||||
goto err_unlock;
|
||||
}
|
||||
|
@ -2313,13 +2309,13 @@ static __init void of_unittest_overlay_high_level(void)
|
|||
|
||||
/* now do the normal overlay usage test */
|
||||
|
||||
unittest(overlay_data_add(1),
|
||||
unittest(overlay_data_apply("overlay", NULL),
|
||||
"Adding overlay 'overlay' failed\n");
|
||||
|
||||
unittest(overlay_data_add(2),
|
||||
unittest(overlay_data_apply("overlay_bad_phandle", NULL),
|
||||
"Adding overlay 'overlay_bad_phandle' failed\n");
|
||||
|
||||
unittest(overlay_data_add(3),
|
||||
unittest(overlay_data_apply("overlay_bad_symbol", NULL),
|
||||
"Adding overlay 'overlay_bad_symbol' failed\n");
|
||||
|
||||
return;
|
||||
|
|
|
@ -1359,8 +1359,8 @@ struct of_overlay_notify_data {
|
|||
|
||||
#ifdef CONFIG_OF_OVERLAY
|
||||
|
||||
/* ID based overlays; the API for external users */
|
||||
int of_overlay_apply(struct device_node *tree, int *ovcs_id);
|
||||
int of_overlay_fdt_apply(const void *overlay_fdt, u32 overlay_fdt_size,
|
||||
int *ovcs_id);
|
||||
int of_overlay_remove(int *ovcs_id);
|
||||
int of_overlay_remove_all(void);
|
||||
|
||||
|
@ -1369,7 +1369,7 @@ int of_overlay_notifier_unregister(struct notifier_block *nb);
|
|||
|
||||
#else
|
||||
|
||||
static inline int of_overlay_apply(struct device_node *tree, int *ovcs_id)
|
||||
static inline int of_overlay_fdt_apply(void *overlay_fdt, int *ovcs_id)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue