Commit 34c8a892 authored by Daniel Vetter's avatar Daniel Vetter

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

R-Car DU changes for v5.3:

- R8A774A1 SoC support
- LVDS dual-link mode support
- Support for additional formats
- Misc fixes
Signed-off-by: default avatarDaniel Vetter <daniel.vetter@ffwll.ch>
From: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20190608134652.GE4786@pendragon.ideasonboard.com
parents 396f9aca cb5f15b7
...@@ -9,6 +9,7 @@ Required properties: ...@@ -9,6 +9,7 @@ Required properties:
- compatible : Shall contain one of - compatible : Shall contain one of
- "renesas,r8a7743-lvds" for R8A7743 (RZ/G1M) compatible LVDS encoders - "renesas,r8a7743-lvds" for R8A7743 (RZ/G1M) compatible LVDS encoders
- "renesas,r8a7744-lvds" for R8A7744 (RZ/G1N) compatible LVDS encoders - "renesas,r8a7744-lvds" for R8A7744 (RZ/G1N) compatible LVDS encoders
- "renesas,r8a774a1-lvds" for R8A774A1 (RZ/G2M) compatible LVDS encoders
- "renesas,r8a774c0-lvds" for R8A774C0 (RZ/G2E) compatible LVDS encoders - "renesas,r8a774c0-lvds" for R8A774C0 (RZ/G2E) compatible LVDS encoders
- "renesas,r8a7790-lvds" for R8A7790 (R-Car H2) 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,r8a7791-lvds" for R8A7791 (R-Car M2-W) compatible LVDS encoders
...@@ -45,14 +46,24 @@ OF graph bindings specified in Documentation/devicetree/bindings/graph.txt. ...@@ -45,14 +46,24 @@ OF graph bindings specified in Documentation/devicetree/bindings/graph.txt.
Each port shall have a single endpoint. Each port shall have a single endpoint.
Optional properties:
- renesas,companion : phandle to the companion LVDS encoder. This property is
mandatory for the first LVDS encoder on D3 and E3 SoCs, and shall point to
the second encoder to be used as a companion in dual-link mode. It shall not
be set for any other LVDS encoder.
Example: Example:
lvds0: lvds@feb90000 { lvds0: lvds@feb90000 {
compatible = "renesas,r8a7790-lvds"; compatible = "renesas,r8a77990-lvds";
reg = <0 0xfeb90000 0 0x1c>; reg = <0 0xfeb90000 0 0x20>;
clocks = <&cpg CPG_MOD 726>; clocks = <&cpg CPG_MOD 727>;
resets = <&cpg 726>; power-domains = <&sysc R8A77990_PD_ALWAYS_ON>;
resets = <&cpg 727>;
renesas,companion = <&lvds1>;
ports { ports {
#address-cells = <1>; #address-cells = <1>;
......
...@@ -28,6 +28,12 @@ Optional video port nodes: ...@@ -28,6 +28,12 @@ Optional video port nodes:
- port@1: Second LVDS input port - port@1: Second LVDS input port
- port@3: Second digital CMOS/TTL parallel output - port@3: Second digital CMOS/TTL parallel output
The device can operate in single-link mode or dual-link mode. In single-link
mode, all pixels are received on port@0, and port@1 shall not contain any
endpoint. In dual-link mode, even-numbered pixels are received on port@0 and
odd-numbered pixels on port@1, and both port@0 and port@1 shall contain
endpoints.
Example: Example:
-------- --------
......
...@@ -7,6 +7,7 @@ Required Properties: ...@@ -7,6 +7,7 @@ Required Properties:
- "renesas,du-r8a7744" for R8A7744 (RZ/G1N) compatible DU - "renesas,du-r8a7744" for R8A7744 (RZ/G1N) compatible DU
- "renesas,du-r8a7745" for R8A7745 (RZ/G1E) compatible DU - "renesas,du-r8a7745" for R8A7745 (RZ/G1E) compatible DU
- "renesas,du-r8a77470" for R8A77470 (RZ/G1C) compatible DU - "renesas,du-r8a77470" for R8A77470 (RZ/G1C) compatible DU
- "renesas,du-r8a774a1" for R8A774A1 (RZ/G2M) compatible DU
- "renesas,du-r8a774c0" for R8A774C0 (RZ/G2E) compatible DU - "renesas,du-r8a774c0" for R8A774C0 (RZ/G2E) compatible DU
- "renesas,du-r8a7779" for R8A7779 (R-Car H1) compatible DU - "renesas,du-r8a7779" for R8A7779 (R-Car H1) compatible DU
- "renesas,du-r8a7790" for R8A7790 (R-Car H2) compatible DU - "renesas,du-r8a7790" for R8A7790 (R-Car H2) compatible DU
...@@ -58,6 +59,7 @@ corresponding to each DU output. ...@@ -58,6 +59,7 @@ corresponding to each DU output.
R8A7744 (RZ/G1N) DPAD 0 LVDS 0 - - R8A7744 (RZ/G1N) DPAD 0 LVDS 0 - -
R8A7745 (RZ/G1E) DPAD 0 DPAD 1 - - R8A7745 (RZ/G1E) DPAD 0 DPAD 1 - -
R8A77470 (RZ/G1C) DPAD 0 DPAD 1 LVDS 0 - R8A77470 (RZ/G1C) DPAD 0 DPAD 1 LVDS 0 -
R8A774A1 (RZ/G2M) DPAD 0 HDMI 0 LVDS 0 -
R8A774C0 (RZ/G2E) DPAD 0 LVDS 0 LVDS 1 - R8A774C0 (RZ/G2E) DPAD 0 LVDS 0 LVDS 1 -
R8A7779 (R-Car H1) DPAD 0 DPAD 1 - - R8A7779 (R-Car H1) DPAD 0 DPAD 1 - -
R8A7790 (R-Car H2) DPAD 0 LVDS 0 LVDS 1 - R8A7790 (R-Car H2) DPAD 0 LVDS 0 LVDS 1 -
......
...@@ -33,6 +33,8 @@ struct thc63_dev { ...@@ -33,6 +33,8 @@ struct thc63_dev {
struct drm_bridge bridge; struct drm_bridge bridge;
struct drm_bridge *next; struct drm_bridge *next;
struct drm_bridge_timings timings;
}; };
static inline struct thc63_dev *to_thc63(struct drm_bridge *bridge) static inline struct thc63_dev *to_thc63(struct drm_bridge *bridge)
...@@ -50,15 +52,28 @@ static int thc63_attach(struct drm_bridge *bridge) ...@@ -50,15 +52,28 @@ static int thc63_attach(struct drm_bridge *bridge)
static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge, static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge,
const struct drm_display_mode *mode) const struct drm_display_mode *mode)
{ {
struct thc63_dev *thc63 = to_thc63(bridge);
unsigned int min_freq;
unsigned int max_freq;
/* /*
* The THC63LVD1024 clock frequency range is 8 to 135 MHz in single-in * The THC63LVD1024 pixel rate range is 8 to 135 MHz in all modes but
* mode. Note that the limits are different in dual-in, single-out mode, * dual-in, single-out where it is 40 to 150 MHz. As dual-in, dual-out
* and will need to be adjusted accordingly. * isn't supported by the driver yet, simply derive the limits from the
* input mode.
*/ */
if (mode->clock < 8000) if (thc63->timings.dual_link) {
min_freq = 40000;
max_freq = 150000;
} else {
min_freq = 8000;
max_freq = 135000;
}
if (mode->clock < min_freq)
return MODE_CLOCK_LOW; return MODE_CLOCK_LOW;
if (mode->clock > 135000) if (mode->clock > max_freq)
return MODE_CLOCK_HIGH; return MODE_CLOCK_HIGH;
return MODE_OK; return MODE_OK;
...@@ -103,19 +118,19 @@ static const struct drm_bridge_funcs thc63_bridge_func = { ...@@ -103,19 +118,19 @@ static const struct drm_bridge_funcs thc63_bridge_func = {
static int thc63_parse_dt(struct thc63_dev *thc63) static int thc63_parse_dt(struct thc63_dev *thc63)
{ {
struct device_node *thc63_out; struct device_node *endpoint;
struct device_node *remote; struct device_node *remote;
thc63_out = of_graph_get_endpoint_by_regs(thc63->dev->of_node, endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node,
THC63_RGB_OUT0, -1); THC63_RGB_OUT0, -1);
if (!thc63_out) { if (!endpoint) {
dev_err(thc63->dev, "Missing endpoint in port@%u\n", dev_err(thc63->dev, "Missing endpoint in port@%u\n",
THC63_RGB_OUT0); THC63_RGB_OUT0);
return -ENODEV; return -ENODEV;
} }
remote = of_graph_get_remote_port_parent(thc63_out); remote = of_graph_get_remote_port_parent(endpoint);
of_node_put(thc63_out); of_node_put(endpoint);
if (!remote) { if (!remote) {
dev_err(thc63->dev, "Endpoint in port@%u unconnected\n", dev_err(thc63->dev, "Endpoint in port@%u unconnected\n",
THC63_RGB_OUT0); THC63_RGB_OUT0);
...@@ -134,6 +149,22 @@ static int thc63_parse_dt(struct thc63_dev *thc63) ...@@ -134,6 +149,22 @@ static int thc63_parse_dt(struct thc63_dev *thc63)
if (!thc63->next) if (!thc63->next)
return -EPROBE_DEFER; return -EPROBE_DEFER;
endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node,
THC63_LVDS_IN1, -1);
if (endpoint) {
remote = of_graph_get_remote_port_parent(endpoint);
of_node_put(endpoint);
if (remote) {
if (of_device_is_available(remote))
thc63->timings.dual_link = true;
of_node_put(remote);
}
}
dev_dbg(thc63->dev, "operating in %s-link mode\n",
thc63->timings.dual_link ? "dual" : "single");
return 0; return 0;
} }
...@@ -190,6 +221,7 @@ static int thc63_probe(struct platform_device *pdev) ...@@ -190,6 +221,7 @@ static int thc63_probe(struct platform_device *pdev)
thc63->bridge.driver_private = thc63; thc63->bridge.driver_private = thc63;
thc63->bridge.of_node = pdev->dev.of_node; thc63->bridge.of_node = pdev->dev.of_node;
thc63->bridge.funcs = &thc63_bridge_func; thc63->bridge.funcs = &thc63_bridge_func;
thc63->bridge.timings = &thc63->timings;
drm_bridge_add(&thc63->bridge); drm_bridge_add(&thc63->bridge);
......
...@@ -102,6 +102,35 @@ static const struct rcar_du_device_info rzg1_du_r8a77470_info = { ...@@ -102,6 +102,35 @@ static const struct rcar_du_device_info rzg1_du_r8a77470_info = {
}, },
}; };
static const struct rcar_du_device_info rcar_du_r8a774a1_info = {
.gen = 3,
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
| RCAR_DU_FEATURE_VSP1_SOURCE
| RCAR_DU_FEATURE_INTERLACED
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(2) | BIT(1) | BIT(0),
.routes = {
/*
* R8A774A1 has one RGB output, one LVDS output and one HDMI
* output.
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(2),
.port = 0,
},
[RCAR_DU_OUTPUT_HDMI0] = {
.possible_crtcs = BIT(1),
.port = 1,
},
[RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0),
.port = 2,
},
},
.num_lvds = 1,
.dpll_mask = BIT(1),
};
static const struct rcar_du_device_info rcar_du_r8a774c0_info = { static const struct rcar_du_device_info rcar_du_r8a774c0_info = {
.gen = 3, .gen = 3,
.features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
...@@ -386,6 +415,7 @@ static const struct of_device_id rcar_du_of_table[] = { ...@@ -386,6 +415,7 @@ static const struct of_device_id rcar_du_of_table[] = {
{ .compatible = "renesas,du-r8a7744", .data = &rzg1_du_r8a7743_info }, { .compatible = "renesas,du-r8a7744", .data = &rzg1_du_r8a7743_info },
{ .compatible = "renesas,du-r8a7745", .data = &rzg1_du_r8a7745_info }, { .compatible = "renesas,du-r8a7745", .data = &rzg1_du_r8a7745_info },
{ .compatible = "renesas,du-r8a77470", .data = &rzg1_du_r8a77470_info }, { .compatible = "renesas,du-r8a77470", .data = &rzg1_du_r8a77470_info },
{ .compatible = "renesas,du-r8a774a1", .data = &rcar_du_r8a774a1_info },
{ .compatible = "renesas,du-r8a774c0", .data = &rcar_du_r8a774c0_info }, { .compatible = "renesas,du-r8a774c0", .data = &rcar_du_r8a774c0_info },
{ .compatible = "renesas,du-r8a7779", .data = &rcar_du_r8a7779_info }, { .compatible = "renesas,du-r8a7779", .data = &rcar_du_r8a7779_info },
{ .compatible = "renesas,du-r8a7790", .data = &rcar_du_r8a7790_info }, { .compatible = "renesas,du-r8a7790", .data = &rcar_du_r8a7790_info },
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "rcar_du_drv.h" #include "rcar_du_drv.h"
#include "rcar_du_encoder.h" #include "rcar_du_encoder.h"
#include "rcar_du_kms.h" #include "rcar_du_kms.h"
#include "rcar_lvds.h"
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* Encoder * Encoder
...@@ -97,6 +98,17 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu, ...@@ -97,6 +98,17 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
} }
} }
/*
* On Gen3 skip the LVDS1 output if the LVDS1 encoder is used as a
* companion for LVDS0 in dual-link mode.
*/
if (rcdu->info->gen >= 3 && output == RCAR_DU_OUTPUT_LVDS1) {
if (rcar_lvds_dual_link(bridge)) {
ret = -ENOLINK;
goto done;
}
}
ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs, ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs,
DRM_MODE_ENCODER_NONE, NULL); DRM_MODE_ENCODER_NONE, NULL);
if (ret < 0) if (ret < 0)
......
...@@ -123,6 +123,66 @@ static const struct rcar_du_format_info rcar_du_format_infos[] = { ...@@ -123,6 +123,66 @@ static const struct rcar_du_format_info rcar_du_format_infos[] = {
.v4l2 = V4L2_PIX_FMT_XRGB444, .v4l2 = V4L2_PIX_FMT_XRGB444,
.bpp = 16, .bpp = 16,
.planes = 1, .planes = 1,
}, {
.fourcc = DRM_FORMAT_RGBA4444,
.v4l2 = V4L2_PIX_FMT_RGBA444,
.bpp = 16,
.planes = 1,
}, {
.fourcc = DRM_FORMAT_RGBX4444,
.v4l2 = V4L2_PIX_FMT_RGBX444,
.bpp = 16,
.planes = 1,
}, {
.fourcc = DRM_FORMAT_ABGR4444,
.v4l2 = V4L2_PIX_FMT_ABGR444,
.bpp = 16,
.planes = 1,
}, {
.fourcc = DRM_FORMAT_XBGR4444,
.v4l2 = V4L2_PIX_FMT_XBGR444,
.bpp = 16,
.planes = 1,
}, {
.fourcc = DRM_FORMAT_BGRA4444,
.v4l2 = V4L2_PIX_FMT_BGRA444,
.bpp = 16,
.planes = 1,
}, {
.fourcc = DRM_FORMAT_BGRX4444,
.v4l2 = V4L2_PIX_FMT_BGRX444,
.bpp = 16,
.planes = 1,
}, {
.fourcc = DRM_FORMAT_RGBA5551,
.v4l2 = V4L2_PIX_FMT_RGBA555,
.bpp = 16,
.planes = 1,
}, {
.fourcc = DRM_FORMAT_RGBX5551,
.v4l2 = V4L2_PIX_FMT_RGBX555,
.bpp = 16,
.planes = 1,
}, {
.fourcc = DRM_FORMAT_ABGR1555,
.v4l2 = V4L2_PIX_FMT_ABGR555,
.bpp = 16,
.planes = 1,
}, {
.fourcc = DRM_FORMAT_XBGR1555,
.v4l2 = V4L2_PIX_FMT_XBGR555,
.bpp = 16,
.planes = 1,
}, {
.fourcc = DRM_FORMAT_BGRA5551,
.v4l2 = V4L2_PIX_FMT_BGRA555,
.bpp = 16,
.planes = 1,
}, {
.fourcc = DRM_FORMAT_BGRX5551,
.v4l2 = V4L2_PIX_FMT_BGRX555,
.bpp = 16,
.planes = 1,
}, { }, {
.fourcc = DRM_FORMAT_BGR888, .fourcc = DRM_FORMAT_BGR888,
.v4l2 = V4L2_PIX_FMT_RGB24, .v4l2 = V4L2_PIX_FMT_RGB24,
...@@ -133,6 +193,26 @@ static const struct rcar_du_format_info rcar_du_format_infos[] = { ...@@ -133,6 +193,26 @@ static const struct rcar_du_format_info rcar_du_format_infos[] = {
.v4l2 = V4L2_PIX_FMT_BGR24, .v4l2 = V4L2_PIX_FMT_BGR24,
.bpp = 24, .bpp = 24,
.planes = 1, .planes = 1,
}, {
.fourcc = DRM_FORMAT_RGBA8888,
.v4l2 = V4L2_PIX_FMT_BGRA32,
.bpp = 32,
.planes = 1,
}, {
.fourcc = DRM_FORMAT_RGBX8888,
.v4l2 = V4L2_PIX_FMT_BGRX32,
.bpp = 32,
.planes = 1,
}, {
.fourcc = DRM_FORMAT_ABGR8888,
.v4l2 = V4L2_PIX_FMT_RGBA32,
.bpp = 32,
.planes = 1,
}, {
.fourcc = DRM_FORMAT_XBGR8888,
.v4l2 = V4L2_PIX_FMT_RGBX32,
.bpp = 32,
.planes = 1,
}, { }, {
.fourcc = DRM_FORMAT_BGRA8888, .fourcc = DRM_FORMAT_BGRA8888,
.v4l2 = V4L2_PIX_FMT_ARGB32, .v4l2 = V4L2_PIX_FMT_ARGB32,
...@@ -378,7 +458,7 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu, ...@@ -378,7 +458,7 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
} }
ret = rcar_du_encoder_init(rcdu, output, entity); ret = rcar_du_encoder_init(rcdu, output, entity);
if (ret && ret != -EPROBE_DEFER) if (ret && ret != -EPROBE_DEFER && ret != -ENOLINK)
dev_warn(rcdu->dev, dev_warn(rcdu->dev,
"failed to initialize encoder %pOF on output %u (%d), skipping\n", "failed to initialize encoder %pOF on output %u (%d), skipping\n",
entity, output, ret); entity, output, ret);
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "rcar_du_crtc.h" #include "rcar_du_crtc.h"
#include "rcar_du_drv.h" #include "rcar_du_drv.h"
#include "rcar_du_kms.h" #include "rcar_du_kms.h"
#include "rcar_du_writeback.h"
/** /**
* struct rcar_du_wb_conn_state - Driver-specific writeback connector state * struct rcar_du_wb_conn_state - Driver-specific writeback connector state
......
...@@ -63,10 +63,12 @@ struct rcar_lvds { ...@@ -63,10 +63,12 @@ struct rcar_lvds {
struct clk *extal; /* External clock */ struct clk *extal; /* External clock */
struct clk *dotclkin[2]; /* External DU clocks */ struct clk *dotclkin[2]; /* External DU clocks */
} clocks; } clocks;
bool enabled;
struct drm_display_mode display_mode; struct drm_display_mode display_mode;
enum rcar_lvds_mode mode; enum rcar_lvds_mode mode;
struct drm_bridge *companion;
bool dual_link;
}; };
#define bridge_to_rcar_lvds(bridge) \ #define bridge_to_rcar_lvds(bridge) \
...@@ -368,15 +370,12 @@ int rcar_lvds_clk_enable(struct drm_bridge *bridge, unsigned long freq) ...@@ -368,15 +370,12 @@ int rcar_lvds_clk_enable(struct drm_bridge *bridge, unsigned long freq)
dev_dbg(lvds->dev, "enabling LVDS PLL, freq=%luHz\n", freq); dev_dbg(lvds->dev, "enabling LVDS PLL, freq=%luHz\n", freq);
WARN_ON(lvds->enabled);
ret = clk_prepare_enable(lvds->clocks.mod); ret = clk_prepare_enable(lvds->clocks.mod);
if (ret < 0) if (ret < 0)
return ret; return ret;
__rcar_lvds_pll_setup_d3_e3(lvds, freq, true); __rcar_lvds_pll_setup_d3_e3(lvds, freq, true);
lvds->enabled = true;
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(rcar_lvds_clk_enable); EXPORT_SYMBOL_GPL(rcar_lvds_clk_enable);
...@@ -390,13 +389,9 @@ void rcar_lvds_clk_disable(struct drm_bridge *bridge) ...@@ -390,13 +389,9 @@ void rcar_lvds_clk_disable(struct drm_bridge *bridge)
dev_dbg(lvds->dev, "disabling LVDS PLL\n"); dev_dbg(lvds->dev, "disabling LVDS PLL\n");
WARN_ON(!lvds->enabled);
rcar_lvds_write(lvds, LVDPLLCR, 0); rcar_lvds_write(lvds, LVDPLLCR, 0);
clk_disable_unprepare(lvds->clocks.mod); clk_disable_unprepare(lvds->clocks.mod);
lvds->enabled = false;
} }
EXPORT_SYMBOL_GPL(rcar_lvds_clk_disable); EXPORT_SYMBOL_GPL(rcar_lvds_clk_disable);
...@@ -408,21 +403,18 @@ static void rcar_lvds_enable(struct drm_bridge *bridge) ...@@ -408,21 +403,18 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
{ {
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge); struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
const struct drm_display_mode *mode = &lvds->display_mode; 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 lvdhcr; u32 lvdhcr;
u32 lvdcr0; u32 lvdcr0;
int ret; int ret;
WARN_ON(lvds->enabled);
ret = clk_prepare_enable(lvds->clocks.mod); ret = clk_prepare_enable(lvds->clocks.mod);
if (ret < 0) if (ret < 0)
return; return;
/* Enable the companion LVDS encoder in dual-link mode. */
if (lvds->dual_link && lvds->companion)
lvds->companion->funcs->enable(lvds->companion);
/* /*
* Hardcode the channels and control signals routing for now. * Hardcode the channels and control signals routing for now.
* *
...@@ -445,17 +437,33 @@ static void rcar_lvds_enable(struct drm_bridge *bridge) ...@@ -445,17 +437,33 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
rcar_lvds_write(lvds, LVDCHCR, lvdhcr); rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK) { if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK) {
/* Disable dual-link mode. */ /*
rcar_lvds_write(lvds, LVDSTRIPE, 0); * Configure vertical stripe based on the mode of operation of
* the connected device.
*/
rcar_lvds_write(lvds, LVDSTRIPE,
lvds->dual_link ? LVDSTRIPE_ST_ON : 0);
} }
/* PLL clock configuration. */ /*
lvds->info->pll_setup(lvds, mode->clock * 1000); * PLL clock configuration on all instances but the companion in
* dual-link mode.
*/
if (!lvds->dual_link || lvds->companion)
lvds->info->pll_setup(lvds, mode->clock * 1000);
/* Set the LVDS mode and select the input. */ /* Set the LVDS mode and select the input. */
lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT; lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
if (drm_crtc_index(crtc) == 2)
lvdcr0 |= LVDCR0_DUSEL; if (lvds->bridge.encoder) {
/*
* FIXME: We should really retrieve the CRTC through the state,
* but how do we get a state pointer?
*/
if (drm_crtc_index(lvds->bridge.encoder->crtc) == 2)
lvdcr0 |= LVDCR0_DUSEL;
}
rcar_lvds_write(lvds, LVDCR0, lvdcr0); rcar_lvds_write(lvds, LVDCR0, lvdcr0);
/* Turn all the channels on. */ /* Turn all the channels on. */
...@@ -507,16 +515,12 @@ static void rcar_lvds_enable(struct drm_bridge *bridge) ...@@ -507,16 +515,12 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
drm_panel_prepare(lvds->panel); drm_panel_prepare(lvds->panel);
drm_panel_enable(lvds->panel); drm_panel_enable(lvds->panel);
} }
lvds->enabled = true;
} }
static void rcar_lvds_disable(struct drm_bridge *bridge) static void rcar_lvds_disable(struct drm_bridge *bridge)
{ {
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge); struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
WARN_ON(!lvds->enabled);
if (lvds->panel) { if (lvds->panel) {
drm_panel_disable(lvds->panel); drm_panel_disable(lvds->panel);
drm_panel_unprepare(lvds->panel); drm_panel_unprepare(lvds->panel);
...@@ -526,9 +530,11 @@ static void rcar_lvds_disable(struct drm_bridge *bridge) ...@@ -526,9 +530,11 @@ static void rcar_lvds_disable(struct drm_bridge *bridge)
rcar_lvds_write(lvds, LVDCR1, 0); rcar_lvds_write(lvds, LVDCR1, 0);
rcar_lvds_write(lvds, LVDPLLCR, 0); rcar_lvds_write(lvds, LVDPLLCR, 0);
clk_disable_unprepare(lvds->clocks.mod); /* Disable the companion LVDS encoder in dual-link mode. */
if (lvds->dual_link && lvds->companion)
lvds->companion->funcs->disable(lvds->companion);
lvds->enabled = false; clk_disable_unprepare(lvds->clocks.mod);
} }
static bool rcar_lvds_mode_fixup(struct drm_bridge *bridge, static bool rcar_lvds_mode_fixup(struct drm_bridge *bridge,
...@@ -592,8 +598,6 @@ static void rcar_lvds_mode_set(struct drm_bridge *bridge, ...@@ -592,8 +598,6 @@ static void rcar_lvds_mode_set(struct drm_bridge *bridge,
{ {
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge); struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
WARN_ON(lvds->enabled);
lvds->display_mode = *adjusted_mode; lvds->display_mode = *adjusted_mode;
rcar_lvds_get_lvds_mode(lvds); rcar_lvds_get_lvds_mode(lvds);
...@@ -646,10 +650,57 @@ static const struct drm_bridge_funcs rcar_lvds_bridge_ops = { ...@@ -646,10 +650,57 @@ static const struct drm_bridge_funcs rcar_lvds_bridge_ops = {
.mode_set = rcar_lvds_mode_set, .mode_set = rcar_lvds_mode_set,
}; };
bool rcar_lvds_dual_link(struct drm_bridge *bridge)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
return lvds->dual_link;
}
EXPORT_SYMBOL_GPL(rcar_lvds_dual_link);
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* Probe & Remove * Probe & Remove
*/ */
static int rcar_lvds_parse_dt_companion(struct rcar_lvds *lvds)
{
const struct of_device_id *match;
struct device_node *companion;
struct device *dev = lvds->dev;
int ret = 0;
/* Locate the companion LVDS encoder for dual-link operation, if any. */
companion = of_parse_phandle(dev->of_node, "renesas,companion", 0);
if (!companion) {
dev_err(dev, "Companion LVDS encoder not found\n");
return -ENXIO;
}
/*
* Sanity check: the companion encoder must have the same compatible
* string.
*/
match = of_match_device(dev->driver->of_match_table, dev);
if (!of_device_is_compatible(companion, match->compatible)) {
dev_err(dev, "Companion LVDS encoder is invalid\n");
ret = -ENXIO;
goto done;
}
lvds->companion = of_drm_find_bridge(companion);
if (!lvds->companion) {
ret = -EPROBE_DEFER;
goto done;
}
dev_dbg(dev, "Found companion encoder %pOF\n", companion);
done:
of_node_put(companion);
return ret;
}
static int rcar_lvds_parse_dt(struct rcar_lvds *lvds) static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
{ {
struct device_node *local_output = NULL; struct device_node *local_output = NULL;
...@@ -700,14 +751,26 @@ static int rcar_lvds_parse_dt(struct rcar_lvds *lvds) ...@@ -700,14 +751,26 @@ static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
if (is_bridge) { if (is_bridge) {
lvds->next_bridge = of_drm_find_bridge(remote); lvds->next_bridge = of_drm_find_bridge(remote);
if (!lvds->next_bridge) if (!lvds->next_bridge) {
ret = -EPROBE_DEFER; ret = -EPROBE_DEFER;
goto done;
}
if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK)
lvds->dual_link = lvds->next_bridge->timings
? lvds->next_bridge->timings->dual_link
: false;
} else { } else {
lvds->panel = of_drm_find_panel(remote); lvds->panel = of_drm_find_panel(remote);
if (IS_ERR(lvds->panel)) if (IS_ERR(lvds->panel)) {
ret = PTR_ERR(lvds->panel); ret = PTR_ERR(lvds->panel);
goto done;
}
} }
if (lvds->dual_link)
ret = rcar_lvds_parse_dt_companion(lvds);
done: done:
of_node_put(local_output); of_node_put(local_output);
of_node_put(remote_input); of_node_put(remote_input);
...@@ -793,7 +856,6 @@ static int rcar_lvds_probe(struct platform_device *pdev) ...@@ -793,7 +856,6 @@ static int rcar_lvds_probe(struct platform_device *pdev)
lvds->dev = &pdev->dev; lvds->dev = &pdev->dev;
lvds->info = of_device_get_match_data(&pdev->dev); lvds->info = of_device_get_match_data(&pdev->dev);
lvds->enabled = false;
ret = rcar_lvds_parse_dt(lvds); ret = rcar_lvds_parse_dt(lvds);
if (ret < 0) if (ret < 0)
...@@ -866,6 +928,7 @@ static const struct rcar_lvds_device_info rcar_lvds_r8a77995_info = { ...@@ -866,6 +928,7 @@ static const struct rcar_lvds_device_info rcar_lvds_r8a77995_info = {
static const struct of_device_id rcar_lvds_of_table[] = { static const struct of_device_id rcar_lvds_of_table[] = {
{ .compatible = "renesas,r8a7743-lvds", .data = &rcar_lvds_gen2_info }, { .compatible = "renesas,r8a7743-lvds", .data = &rcar_lvds_gen2_info },
{ .compatible = "renesas,r8a7744-lvds", .data = &rcar_lvds_gen2_info }, { .compatible = "renesas,r8a7744-lvds", .data = &rcar_lvds_gen2_info },
{ .compatible = "renesas,r8a774a1-lvds", .data = &rcar_lvds_gen3_info },
{ .compatible = "renesas,r8a774c0-lvds", .data = &rcar_lvds_r8a77990_info }, { .compatible = "renesas,r8a774c0-lvds", .data = &rcar_lvds_r8a77990_info },
{ .compatible = "renesas,r8a7790-lvds", .data = &rcar_lvds_r8a7790_info }, { .compatible = "renesas,r8a7790-lvds", .data = &rcar_lvds_r8a7790_info },
{ .compatible = "renesas,r8a7791-lvds", .data = &rcar_lvds_gen2_info }, { .compatible = "renesas,r8a7791-lvds", .data = &rcar_lvds_gen2_info },
......
...@@ -15,6 +15,7 @@ struct drm_bridge; ...@@ -15,6 +15,7 @@ struct drm_bridge;
#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS) #if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
int rcar_lvds_clk_enable(struct drm_bridge *bridge, unsigned long freq); int rcar_lvds_clk_enable(struct drm_bridge *bridge, unsigned long freq);
void rcar_lvds_clk_disable(struct drm_bridge *bridge); void rcar_lvds_clk_disable(struct drm_bridge *bridge);
bool rcar_lvds_dual_link(struct drm_bridge *bridge);
#else #else
static inline int rcar_lvds_clk_enable(struct drm_bridge *bridge, static inline int rcar_lvds_clk_enable(struct drm_bridge *bridge,
unsigned long freq) unsigned long freq)
...@@ -22,6 +23,10 @@ static inline int rcar_lvds_clk_enable(struct drm_bridge *bridge, ...@@ -22,6 +23,10 @@ static inline int rcar_lvds_clk_enable(struct drm_bridge *bridge,
return -ENOSYS; return -ENOSYS;
} }
static inline void rcar_lvds_clk_disable(struct drm_bridge *bridge) { } static inline void rcar_lvds_clk_disable(struct drm_bridge *bridge) { }
static inline bool rcar_lvds_dual_link(struct drm_bridge *bridge)
{
return false;
}
#endif /* CONFIG_DRM_RCAR_LVDS */ #endif /* CONFIG_DRM_RCAR_LVDS */
#endif /* __RCAR_LVDS_H__ */ #endif /* __RCAR_LVDS_H__ */
...@@ -265,6 +265,14 @@ struct drm_bridge_timings { ...@@ -265,6 +265,14 @@ struct drm_bridge_timings {
* input signal after the clock edge. * input signal after the clock edge.
*/ */
u32 hold_time_ps; u32 hold_time_ps;
/**
* @dual_link:
*
* True if the bus operates in dual-link mode. The exact meaning is
* dependent on the bus type. For LVDS buses, this indicates that even-
* and odd-numbered pixels are received on separate links.
*/
bool dual_link;
}; };
/** /**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment