aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>2018-01-09 22:47:42 -0500
committerLaurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>2018-03-07 11:06:49 -0500
commitc6a27fa41fabb35fcf0273c32a86f1424fa7de91 (patch)
tree0bff05e17443c829b495e0e21bce05f577144f2c
parent81c0e3dd82927064a2f56a31a0974a0d110fcdb0 (diff)
drm: rcar-du: Convert LVDS encoder code to bridge driver
The LVDS encoders used to be described in DT as part of the DU. They now have their own DT node, linked to the DU using the OF graph bindings. This allows moving internal LVDS encoder support to a separate driver modelled as a DRM bridge. Backward compatibility is retained as legacy DT is patched live to move to the new bindings. Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com> Reviewed-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
-rw-r--r--drivers/gpu/drm/rcar-du/Kconfig4
-rw-r--r--drivers/gpu/drm/rcar-du/Makefile3
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_drv.c21
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_drv.h5
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_encoder.c175
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_encoder.h12
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_kms.c14
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c93
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h24
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c238
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h64
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_lvds.c524
12 files changed, 561 insertions, 616 deletions
diff --git a/drivers/gpu/drm/rcar-du/Kconfig b/drivers/gpu/drm/rcar-du/Kconfig
index 3f83352a7313..edde8d4b87a3 100644
--- a/drivers/gpu/drm/rcar-du/Kconfig
+++ b/drivers/gpu/drm/rcar-du/Kconfig
@@ -19,8 +19,8 @@ config DRM_RCAR_DW_HDMI
19 Enable support for R-Car Gen3 internal HDMI encoder. 19 Enable support for R-Car Gen3 internal HDMI encoder.
20 20
21config DRM_RCAR_LVDS 21config DRM_RCAR_LVDS
22 bool "R-Car DU LVDS Encoder Support" 22 tristate "R-Car DU LVDS Encoder Support"
23 depends on DRM_RCAR_DU 23 depends on DRM && DRM_BRIDGE && OF
24 select DRM_PANEL 24 select DRM_PANEL
25 select OF_FLATTREE 25 select OF_FLATTREE
26 select OF_OVERLAY 26 select OF_OVERLAY
diff --git a/drivers/gpu/drm/rcar-du/Makefile b/drivers/gpu/drm/rcar-du/Makefile
index 86b337b4be5d..3e58ed93d5b1 100644
--- a/drivers/gpu/drm/rcar-du/Makefile
+++ b/drivers/gpu/drm/rcar-du/Makefile
@@ -4,10 +4,8 @@ rcar-du-drm-y := rcar_du_crtc.o \
4 rcar_du_encoder.o \ 4 rcar_du_encoder.o \
5 rcar_du_group.o \ 5 rcar_du_group.o \
6 rcar_du_kms.o \ 6 rcar_du_kms.o \
7 rcar_du_lvdscon.o \
8 rcar_du_plane.o 7 rcar_du_plane.o
9 8
10rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.o
11rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_of.o \ 9rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_of.o \
12 rcar_du_of_lvds_r8a7790.dtb.o \ 10 rcar_du_of_lvds_r8a7790.dtb.o \
13 rcar_du_of_lvds_r8a7791.dtb.o \ 11 rcar_du_of_lvds_r8a7791.dtb.o \
@@ -18,3 +16,4 @@ rcar-du-drm-$(CONFIG_DRM_RCAR_VSP) += rcar_du_vsp.o
18 16
19obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o 17obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o
20obj-$(CONFIG_DRM_RCAR_DW_HDMI) += rcar_dw_hdmi.o 18obj-$(CONFIG_DRM_RCAR_DW_HDMI) += rcar_dw_hdmi.o
19obj-$(CONFIG_DRM_RCAR_LVDS) += rcar_lvds.o
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
index 6e02c762a557..06a3fbdd728a 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_drv.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c
@@ -29,6 +29,7 @@
29 29
30#include "rcar_du_drv.h" 30#include "rcar_du_drv.h"
31#include "rcar_du_kms.h" 31#include "rcar_du_kms.h"
32#include "rcar_du_of.h"
32#include "rcar_du_regs.h" 33#include "rcar_du_regs.h"
33 34
34/* ----------------------------------------------------------------------------- 35/* -----------------------------------------------------------------------------
@@ -74,7 +75,6 @@ static const struct rcar_du_device_info rzg1_du_r8a7745_info = {
74 .port = 1, 75 .port = 1,
75 }, 76 },
76 }, 77 },
77 .num_lvds = 0,
78}; 78};
79 79
80static const struct rcar_du_device_info rcar_du_r8a7779_info = { 80static const struct rcar_du_device_info rcar_du_r8a7779_info = {
@@ -95,14 +95,13 @@ static const struct rcar_du_device_info rcar_du_r8a7779_info = {
95 .port = 1, 95 .port = 1,
96 }, 96 },
97 }, 97 },
98 .num_lvds = 0,
99}; 98};
100 99
101static const struct rcar_du_device_info rcar_du_r8a7790_info = { 100static const struct rcar_du_device_info rcar_du_r8a7790_info = {
102 .gen = 2, 101 .gen = 2,
103 .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK 102 .features = RCAR_DU_FEATURE_CRTC_IRQ_CLOCK
104 | RCAR_DU_FEATURE_EXT_CTRL_REGS, 103 | RCAR_DU_FEATURE_EXT_CTRL_REGS,
105 .quirks = RCAR_DU_QUIRK_ALIGN_128B | RCAR_DU_QUIRK_LVDS_LANES, 104 .quirks = RCAR_DU_QUIRK_ALIGN_128B,
106 .num_crtcs = 3, 105 .num_crtcs = 3,
107 .routes = { 106 .routes = {
108 /* 107 /*
@@ -164,7 +163,6 @@ static const struct rcar_du_device_info rcar_du_r8a7792_info = {
164 .port = 1, 163 .port = 1,
165 }, 164 },
166 }, 165 },
167 .num_lvds = 0,
168}; 166};
169 167
170static const struct rcar_du_device_info rcar_du_r8a7794_info = { 168static const struct rcar_du_device_info rcar_du_r8a7794_info = {
@@ -186,7 +184,6 @@ static const struct rcar_du_device_info rcar_du_r8a7794_info = {
186 .port = 1, 184 .port = 1,
187 }, 185 },
188 }, 186 },
189 .num_lvds = 0,
190}; 187};
191 188
192static const struct rcar_du_device_info rcar_du_r8a7795_info = { 189static const struct rcar_du_device_info rcar_du_r8a7795_info = {
@@ -434,7 +431,19 @@ static struct platform_driver rcar_du_platform_driver = {
434 }, 431 },
435}; 432};
436 433
437module_platform_driver(rcar_du_platform_driver); 434static int __init rcar_du_init(void)
435{
436 rcar_du_of_init(rcar_du_of_table);
437
438 return platform_driver_register(&rcar_du_platform_driver);
439}
440module_init(rcar_du_init);
441
442static void __exit rcar_du_exit(void)
443{
444 platform_driver_unregister(&rcar_du_platform_driver);
445}
446module_exit(rcar_du_exit);
438 447
439MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); 448MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
440MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver"); 449MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver");
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
index f400fde65a0c..5c7ec15818c7 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_drv.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h
@@ -26,14 +26,12 @@ struct device;
26struct drm_device; 26struct drm_device;
27struct drm_fbdev_cma; 27struct drm_fbdev_cma;
28struct rcar_du_device; 28struct rcar_du_device;
29struct rcar_du_lvdsenc;
30 29
31#define RCAR_DU_FEATURE_CRTC_IRQ_CLOCK (1 << 0) /* Per-CRTC IRQ and clock */ 30#define RCAR_DU_FEATURE_CRTC_IRQ_CLOCK (1 << 0) /* Per-CRTC IRQ and clock */
32#define RCAR_DU_FEATURE_EXT_CTRL_REGS (1 << 1) /* Has extended control registers */ 31#define RCAR_DU_FEATURE_EXT_CTRL_REGS (1 << 1) /* Has extended control registers */
33#define RCAR_DU_FEATURE_VSP1_SOURCE (1 << 2) /* Has inputs from VSP1 */ 32#define RCAR_DU_FEATURE_VSP1_SOURCE (1 << 2) /* Has inputs from VSP1 */
34 33
35#define RCAR_DU_QUIRK_ALIGN_128B (1 << 0) /* Align pitches to 128 bytes */ 34#define RCAR_DU_QUIRK_ALIGN_128B (1 << 0) /* Align pitches to 128 bytes */
36#define RCAR_DU_QUIRK_LVDS_LANES (1 << 1) /* LVDS lanes 1 and 3 inverted */
37 35
38/* 36/*
39 * struct rcar_du_output_routing - Output routing specification 37 * struct rcar_du_output_routing - Output routing specification
@@ -70,7 +68,6 @@ struct rcar_du_device_info {
70 68
71#define RCAR_DU_MAX_CRTCS 4 69#define RCAR_DU_MAX_CRTCS 4
72#define RCAR_DU_MAX_GROUPS DIV_ROUND_UP(RCAR_DU_MAX_CRTCS, 2) 70#define RCAR_DU_MAX_GROUPS DIV_ROUND_UP(RCAR_DU_MAX_CRTCS, 2)
73#define RCAR_DU_MAX_LVDS 2
74#define RCAR_DU_MAX_VSPS 4 71#define RCAR_DU_MAX_VSPS 4
75 72
76struct rcar_du_device { 73struct rcar_du_device {
@@ -96,8 +93,6 @@ struct rcar_du_device {
96 93
97 unsigned int dpad0_source; 94 unsigned int dpad0_source;
98 unsigned int vspd1_sink; 95 unsigned int vspd1_sink;
99
100 struct rcar_du_lvdsenc *lvds[RCAR_DU_MAX_LVDS];
101}; 96};
102 97
103static inline bool rcar_du_has(struct rcar_du_device *rcdu, 98static inline bool rcar_du_has(struct rcar_du_device *rcdu,
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
index ba8d2804c1d1..f9c933d3bae6 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c
@@ -21,134 +21,22 @@
21#include "rcar_du_drv.h" 21#include "rcar_du_drv.h"
22#include "rcar_du_encoder.h" 22#include "rcar_du_encoder.h"
23#include "rcar_du_kms.h" 23#include "rcar_du_kms.h"
24#include "rcar_du_lvdscon.h"
25#include "rcar_du_lvdsenc.h"
26 24
27/* ----------------------------------------------------------------------------- 25/* -----------------------------------------------------------------------------
28 * Encoder 26 * Encoder
29 */ 27 */
30 28
31static void rcar_du_encoder_disable(struct drm_encoder *encoder)
32{
33 struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
34
35 if (renc->connector && renc->connector->panel) {
36 drm_panel_disable(renc->connector->panel);
37 drm_panel_unprepare(renc->connector->panel);
38 }
39
40 if (renc->lvds)
41 rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, false);
42}
43
44static void rcar_du_encoder_enable(struct drm_encoder *encoder)
45{
46 struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
47
48 if (renc->lvds)
49 rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, true);
50
51 if (renc->connector && renc->connector->panel) {
52 drm_panel_prepare(renc->connector->panel);
53 drm_panel_enable(renc->connector->panel);
54 }
55}
56
57static int rcar_du_encoder_atomic_check(struct drm_encoder *encoder,
58 struct drm_crtc_state *crtc_state,
59 struct drm_connector_state *conn_state)
60{
61 struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
62 struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
63 const struct drm_display_mode *mode = &crtc_state->mode;
64 struct drm_connector *connector = conn_state->connector;
65 struct drm_device *dev = encoder->dev;
66
67 /*
68 * Only panel-related encoder types require validation here, everything
69 * else is handled by the bridge drivers.
70 */
71 if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) {
72 const struct drm_display_mode *panel_mode;
73
74 if (list_empty(&connector->modes)) {
75 dev_dbg(dev->dev, "encoder: empty modes list\n");
76 return -EINVAL;
77 }
78
79 panel_mode = list_first_entry(&connector->modes,
80 struct drm_display_mode, head);
81
82 /* We're not allowed to modify the resolution. */
83 if (mode->hdisplay != panel_mode->hdisplay ||
84 mode->vdisplay != panel_mode->vdisplay)
85 return -EINVAL;
86
87 /*
88 * The flat panel mode is fixed, just copy it to the adjusted
89 * mode.
90 */
91 drm_mode_copy(adjusted_mode, panel_mode);
92 }
93
94 if (renc->lvds)
95 rcar_du_lvdsenc_atomic_check(renc->lvds, adjusted_mode);
96
97 return 0;
98}
99
100static void rcar_du_encoder_mode_set(struct drm_encoder *encoder, 29static void rcar_du_encoder_mode_set(struct drm_encoder *encoder,
101 struct drm_crtc_state *crtc_state, 30 struct drm_crtc_state *crtc_state,
102 struct drm_connector_state *conn_state) 31 struct drm_connector_state *conn_state)
103{ 32{
104 struct rcar_du_encoder *renc = to_rcar_encoder(encoder); 33 struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
105 struct drm_display_info *info = &conn_state->connector->display_info;
106 enum rcar_lvds_mode mode;
107 34
108 rcar_du_crtc_route_output(crtc_state->crtc, renc->output); 35 rcar_du_crtc_route_output(crtc_state->crtc, renc->output);
109
110 if (!renc->lvds) {
111 /*
112 * The DU driver creates connectors only for the outputs of the
113 * internal LVDS encoders.
114 */
115 renc->connector = NULL;
116 return;
117 }
118
119 renc->connector = to_rcar_connector(conn_state->connector);
120
121 if (!info->num_bus_formats || !info->bus_formats) {
122 dev_err(encoder->dev->dev, "no LVDS bus format reported\n");
123 return;
124 }
125
126 switch (info->bus_formats[0]) {
127 case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
128 case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
129 mode = RCAR_LVDS_MODE_JEIDA;
130 break;
131 case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
132 mode = RCAR_LVDS_MODE_VESA;
133 break;
134 default:
135 dev_err(encoder->dev->dev,
136 "unsupported LVDS bus format 0x%04x\n",
137 info->bus_formats[0]);
138 return;
139 }
140
141 if (info->bus_flags & DRM_BUS_FLAG_DATA_LSB_TO_MSB)
142 mode |= RCAR_LVDS_MODE_MIRROR;
143
144 rcar_du_lvdsenc_set_mode(renc->lvds, mode);
145} 36}
146 37
147static const struct drm_encoder_helper_funcs encoder_helper_funcs = { 38static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
148 .atomic_mode_set = rcar_du_encoder_mode_set, 39 .atomic_mode_set = rcar_du_encoder_mode_set,
149 .disable = rcar_du_encoder_disable,
150 .enable = rcar_du_encoder_enable,
151 .atomic_check = rcar_du_encoder_atomic_check,
152}; 40};
153 41
154static const struct drm_encoder_funcs encoder_funcs = { 42static const struct drm_encoder_funcs encoder_funcs = {
@@ -172,33 +60,14 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
172 renc->output = output; 60 renc->output = output;
173 encoder = rcar_encoder_to_drm_encoder(renc); 61 encoder = rcar_encoder_to_drm_encoder(renc);
174 62
175 switch (output) { 63 dev_dbg(rcdu->dev, "initializing encoder %pOF for output %u\n",
176 case RCAR_DU_OUTPUT_LVDS0: 64 enc_node, output);
177 renc->lvds = rcdu->lvds[0];
178 break;
179 65
180 case RCAR_DU_OUTPUT_LVDS1: 66 /* Locate the DRM bridge from the encoder DT node. */
181 renc->lvds = rcdu->lvds[1]; 67 bridge = of_drm_find_bridge(enc_node);
182 break; 68 if (!bridge) {
183 69 ret = -EPROBE_DEFER;
184 default: 70 goto done;
185 break;
186 }
187
188 if (enc_node) {
189 dev_dbg(rcdu->dev, "initializing encoder %pOF for output %u\n",
190 enc_node, output);
191
192 /* Locate the DRM bridge from the encoder DT node. */
193 bridge = of_drm_find_bridge(enc_node);
194 if (!bridge) {
195 ret = -EPROBE_DEFER;
196 goto done;
197 }
198 } else {
199 dev_dbg(rcdu->dev,
200 "initializing internal encoder for output %u\n",
201 output);
202 } 71 }
203 72
204 ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs, 73 ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs,
@@ -208,28 +77,14 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
208 77
209 drm_encoder_helper_add(encoder, &encoder_helper_funcs); 78 drm_encoder_helper_add(encoder, &encoder_helper_funcs);
210 79
211 if (bridge) { 80 /*
212 /* 81 * Attach the bridge to the encoder. The bridge will create the
213 * Attach the bridge to the encoder. The bridge will create the 82 * connector.
214 * connector. 83 */
215 */ 84 ret = drm_bridge_attach(encoder, bridge, NULL);
216 ret = drm_bridge_attach(encoder, bridge, NULL); 85 if (ret) {
217 if (ret) { 86 drm_encoder_cleanup(encoder);
218 drm_encoder_cleanup(encoder); 87 return ret;
219 return ret;
220 }
221 } else {
222 /* There's no bridge, create the connector manually. */
223 switch (output) {
224 case RCAR_DU_OUTPUT_LVDS0:
225 case RCAR_DU_OUTPUT_LVDS1:
226 ret = rcar_du_lvds_connector_init(rcdu, renc, con_node);
227 break;
228
229 default:
230 ret = -EINVAL;
231 break;
232 }
233 } 88 }
234 89
235done: 90done:
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h
index 5422fa4df272..2d2abcacd169 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h
+++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h
@@ -19,13 +19,10 @@
19 19
20struct drm_panel; 20struct drm_panel;
21struct rcar_du_device; 21struct rcar_du_device;
22struct rcar_du_lvdsenc;
23 22
24struct rcar_du_encoder { 23struct rcar_du_encoder {
25 struct drm_encoder base; 24 struct drm_encoder base;
26 enum rcar_du_output output; 25 enum rcar_du_output output;
27 struct rcar_du_connector *connector;
28 struct rcar_du_lvdsenc *lvds;
29}; 26};
30 27
31#define to_rcar_encoder(e) \ 28#define to_rcar_encoder(e) \
@@ -33,15 +30,6 @@ struct rcar_du_encoder {
33 30
34#define rcar_encoder_to_drm_encoder(e) (&(e)->base) 31#define rcar_encoder_to_drm_encoder(e) (&(e)->base)
35 32
36struct rcar_du_connector {
37 struct drm_connector connector;
38 struct rcar_du_encoder *encoder;
39 struct drm_panel *panel;
40};
41
42#define to_rcar_connector(c) \
43 container_of(c, struct rcar_du_connector, connector)
44
45int rcar_du_encoder_init(struct rcar_du_device *rcdu, 33int rcar_du_encoder_init(struct rcar_du_device *rcdu,
46 enum rcar_du_output output, 34 enum rcar_du_output output,
47 struct device_node *enc_node, 35 struct device_node *enc_node,
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.c b/drivers/gpu/drm/rcar-du/rcar_du_kms.c
index 566d1a948c8f..0329b354bfa0 100644
--- a/drivers/gpu/drm/rcar-du/rcar_du_kms.c
+++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.c
@@ -27,7 +27,6 @@
27#include "rcar_du_drv.h" 27#include "rcar_du_drv.h"
28#include "rcar_du_encoder.h" 28#include "rcar_du_encoder.h"
29#include "rcar_du_kms.h" 29#include "rcar_du_kms.h"
30#include "rcar_du_lvdsenc.h"
31#include "rcar_du_regs.h" 30#include "rcar_du_regs.h"
32#include "rcar_du_vsp.h" 31#include "rcar_du_vsp.h"
33 32
@@ -341,11 +340,10 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
341 of_node_put(entity_ep_node); 340 of_node_put(entity_ep_node);
342 341
343 if (!encoder) { 342 if (!encoder) {
344 /* 343 dev_warn(rcdu->dev,
345 * If no encoder has been found the entity must be the 344 "no encoder found for endpoint %pOF, skipping\n",
346 * connector. 345 ep->local_node);
347 */ 346 return -ENODEV;
348 connector = entity;
349 } 347 }
350 348
351 ret = rcar_du_encoder_init(rcdu, output, encoder, connector); 349 ret = rcar_du_encoder_init(rcdu, output, encoder, connector);
@@ -595,10 +593,6 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
595 } 593 }
596 594
597 /* Initialize the encoders. */ 595 /* Initialize the encoders. */
598 ret = rcar_du_lvdsenc_init(rcdu);
599 if (ret < 0)
600 return ret;
601
602 ret = rcar_du_encoders_init(rcdu); 596 ret = rcar_du_encoders_init(rcdu);
603 if (ret < 0) 597 if (ret < 0)
604 return ret; 598 return ret;
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c b/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c
deleted file mode 100644
index e96f2df0c305..000000000000
--- a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c
+++ /dev/null
@@ -1,93 +0,0 @@
1/*
2 * rcar_du_lvdscon.c -- R-Car Display Unit LVDS Connector
3 *
4 * Copyright (C) 2013-2014 Renesas Electronics Corporation
5 *
6 * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 */
13
14#include <drm/drmP.h>
15#include <drm/drm_atomic_helper.h>
16#include <drm/drm_crtc.h>
17#include <drm/drm_crtc_helper.h>
18#include <drm/drm_panel.h>
19
20#include <video/display_timing.h>
21#include <video/of_display_timing.h>
22#include <video/videomode.h>
23
24#include "rcar_du_drv.h"
25#include "rcar_du_encoder.h"
26#include "rcar_du_kms.h"
27#include "rcar_du_lvdscon.h"
28
29static int rcar_du_lvds_connector_get_modes(struct drm_connector *connector)
30{
31 struct rcar_du_connector *rcon = to_rcar_connector(connector);
32
33 return drm_panel_get_modes(rcon->panel);
34}
35
36static const struct drm_connector_helper_funcs connector_helper_funcs = {
37 .get_modes = rcar_du_lvds_connector_get_modes,
38};
39
40static void rcar_du_lvds_connector_destroy(struct drm_connector *connector)
41{
42 struct rcar_du_connector *rcon = to_rcar_connector(connector);
43
44 drm_panel_detach(rcon->panel);
45 drm_connector_cleanup(connector);
46}
47
48static const struct drm_connector_funcs connector_funcs = {
49 .reset = drm_atomic_helper_connector_reset,
50 .fill_modes = drm_helper_probe_single_connector_modes,
51 .destroy = rcar_du_lvds_connector_destroy,
52 .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
53 .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
54};
55
56int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
57 struct rcar_du_encoder *renc,
58 const struct device_node *np)
59{
60 struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc);
61 struct rcar_du_connector *rcon;
62 struct drm_connector *connector;
63 int ret;
64
65 rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL);
66 if (rcon == NULL)
67 return -ENOMEM;
68
69 connector = &rcon->connector;
70
71 rcon->panel = of_drm_find_panel(np);
72 if (!rcon->panel)
73 return -EPROBE_DEFER;
74
75 ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs,
76 DRM_MODE_CONNECTOR_LVDS);
77 if (ret < 0)
78 return ret;
79
80 drm_connector_helper_add(connector, &connector_helper_funcs);
81
82 ret = drm_mode_connector_attach_encoder(connector, encoder);
83 if (ret < 0)
84 return ret;
85
86 ret = drm_panel_attach(rcon->panel, connector);
87 if (ret < 0)
88 return ret;
89
90 rcon->encoder = renc;
91
92 return 0;
93}
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h b/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h
deleted file mode 100644
index 639071dd235c..000000000000
--- a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h
+++ /dev/null
@@ -1,24 +0,0 @@
1/*
2 * rcar_du_lvdscon.h -- R-Car Display Unit LVDS Connector
3 *
4 * Copyright (C) 2013-2014 Renesas Electronics Corporation
5 *
6 * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 */
13
14#ifndef __RCAR_DU_LVDSCON_H__
15#define __RCAR_DU_LVDSCON_H__
16
17struct rcar_du_device;
18struct rcar_du_encoder;
19
20int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
21 struct rcar_du_encoder *renc,
22 const struct device_node *np);
23
24#endif /* __RCAR_DU_LVDSCON_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c b/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c
deleted file mode 100644
index 4defa8123eb2..000000000000
--- a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c
+++ /dev/null
@@ -1,238 +0,0 @@
1/*
2 * rcar_du_lvdsenc.c -- R-Car Display Unit LVDS Encoder
3 *
4 * Copyright (C) 2013-2014 Renesas Electronics Corporation
5 *
6 * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 */
13
14#include <linux/clk.h>
15#include <linux/delay.h>
16#include <linux/io.h>
17#include <linux/platform_device.h>
18#include <linux/slab.h>
19
20#include "rcar_du_drv.h"
21#include "rcar_du_encoder.h"
22#include "rcar_du_lvdsenc.h"
23#include "rcar_lvds_regs.h"
24
25struct rcar_du_lvdsenc {
26 struct rcar_du_device *dev;
27
28 unsigned int index;
29 void __iomem *mmio;
30 struct clk *clock;
31 bool enabled;
32
33 enum rcar_lvds_input input;
34 enum rcar_lvds_mode mode;
35};
36
37static void rcar_lvds_write(struct rcar_du_lvdsenc *lvds, u32 reg, u32 data)
38{
39 iowrite32(data, lvds->mmio + reg);
40}
41
42static u32 rcar_lvds_lvdpllcr_gen2(unsigned int freq)
43{
44 if (freq < 39000)
45 return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
46 else if (freq < 61000)
47 return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
48 else if (freq < 121000)
49 return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
50 else
51 return LVDPLLCR_PLLDLYCNT_150M;
52}
53
54static u32 rcar_lvds_lvdpllcr_gen3(unsigned int freq)
55{
56 if (freq < 42000)
57 return LVDPLLCR_PLLDIVCNT_42M;
58 else if (freq < 85000)
59 return LVDPLLCR_PLLDIVCNT_85M;
60 else if (freq < 128000)
61 return LVDPLLCR_PLLDIVCNT_128M;
62 else
63 return LVDPLLCR_PLLDIVCNT_148M;
64}
65
66static int rcar_du_lvdsenc_start(struct rcar_du_lvdsenc *lvds,
67 struct rcar_du_crtc *rcrtc)
68{
69 const struct drm_display_mode *mode = &rcrtc->crtc.mode;
70 u32 lvdpllcr;
71 u32 lvdhcr;
72 u32 lvdcr0;
73 int ret;
74
75 if (lvds->enabled)
76 return 0;
77
78 ret = clk_prepare_enable(lvds->clock);
79 if (ret < 0)
80 return ret;
81
82 /*
83 * Hardcode the channels and control signals routing for now.
84 *
85 * HSYNC -> CTRL0
86 * VSYNC -> CTRL1
87 * DISP -> CTRL2
88 * 0 -> CTRL3
89 */
90 rcar_lvds_write(lvds, LVDCTRCR, LVDCTRCR_CTR3SEL_ZERO |
91 LVDCTRCR_CTR2SEL_DISP | LVDCTRCR_CTR1SEL_VSYNC |
92 LVDCTRCR_CTR0SEL_HSYNC);
93
94 if (rcar_du_needs(lvds->dev, RCAR_DU_QUIRK_LVDS_LANES))
95 lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 3)
96 | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 1);
97 else
98 lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 1)
99 | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 3);
100
101 rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
102
103 /* PLL clock configuration. */
104 if (lvds->dev->info->gen < 3)
105 lvdpllcr = rcar_lvds_lvdpllcr_gen2(mode->clock);
106 else
107 lvdpllcr = rcar_lvds_lvdpllcr_gen3(mode->clock);
108 rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
109
110 /* Set the LVDS mode and select the input. */
111 lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
112 if (rcrtc->index == 2)
113 lvdcr0 |= LVDCR0_DUSEL;
114 rcar_lvds_write(lvds, LVDCR0, lvdcr0);
115
116 /* Turn all the channels on. */
117 rcar_lvds_write(lvds, LVDCR1,
118 LVDCR1_CHSTBY(3) | LVDCR1_CHSTBY(2) |
119 LVDCR1_CHSTBY(1) | LVDCR1_CHSTBY(0) | LVDCR1_CLKSTBY);
120
121 if (lvds->dev->info->gen < 3) {
122 /* Enable LVDS operation and turn the bias circuitry on. */
123 lvdcr0 |= LVDCR0_BEN | LVDCR0_LVEN;
124 rcar_lvds_write(lvds, LVDCR0, lvdcr0);
125 }
126
127 /* Turn the PLL on. */
128 lvdcr0 |= LVDCR0_PLLON;
129 rcar_lvds_write(lvds, LVDCR0, lvdcr0);
130
131 if (lvds->dev->info->gen > 2) {
132 /* Set LVDS normal mode. */
133 lvdcr0 |= LVDCR0_PWD;
134 rcar_lvds_write(lvds, LVDCR0, lvdcr0);
135 }
136
137 /* Wait for the startup delay. */
138 usleep_range(100, 150);
139
140 /* Turn the output on. */
141 lvdcr0 |= LVDCR0_LVRES;
142 rcar_lvds_write(lvds, LVDCR0, lvdcr0);
143
144 lvds->enabled = true;
145
146 return 0;
147}
148
149static void rcar_du_lvdsenc_stop(struct rcar_du_lvdsenc *lvds)
150{
151 if (!lvds->enabled)
152 return;
153
154 rcar_lvds_write(lvds, LVDCR0, 0);
155 rcar_lvds_write(lvds, LVDCR1, 0);
156
157 clk_disable_unprepare(lvds->clock);
158
159 lvds->enabled = false;
160}
161
162int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, struct drm_crtc *crtc,
163 bool enable)
164{
165 if (!enable) {
166 rcar_du_lvdsenc_stop(lvds);
167 return 0;
168 } else if (crtc) {
169 struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
170 return rcar_du_lvdsenc_start(lvds, rcrtc);
171 } else
172 return -EINVAL;
173}
174
175void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
176 struct drm_display_mode *mode)
177{
178 /*
179 * The internal LVDS encoder has a restricted clock frequency operating
180 * range (31MHz to 148.5MHz). Clamp the clock accordingly.
181 */
182 mode->clock = clamp(mode->clock, 31000, 148500);
183}
184
185void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
186 enum rcar_lvds_mode mode)
187{
188 lvds->mode = mode;
189}
190
191static int rcar_du_lvdsenc_get_resources(struct rcar_du_lvdsenc *lvds,
192 struct platform_device *pdev)
193{
194 struct resource *mem;
195 char name[7];
196
197 sprintf(name, "lvds.%u", lvds->index);
198
199 mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
200 lvds->mmio = devm_ioremap_resource(&pdev->dev, mem);
201 if (IS_ERR(lvds->mmio))
202 return PTR_ERR(lvds->mmio);
203
204 lvds->clock = devm_clk_get(&pdev->dev, name);
205 if (IS_ERR(lvds->clock)) {
206 dev_err(&pdev->dev, "failed to get clock for %s\n", name);
207 return PTR_ERR(lvds->clock);
208 }
209
210 return 0;
211}
212
213int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
214{
215 struct platform_device *pdev = to_platform_device(rcdu->dev);
216 struct rcar_du_lvdsenc *lvds;
217 unsigned int i;
218 int ret;
219
220 for (i = 0; i < rcdu->info->num_lvds; ++i) {
221 lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
222 if (lvds == NULL)
223 return -ENOMEM;
224
225 lvds->dev = rcdu;
226 lvds->index = i;
227 lvds->input = i ? RCAR_LVDS_INPUT_DU1 : RCAR_LVDS_INPUT_DU0;
228 lvds->enabled = false;
229
230 ret = rcar_du_lvdsenc_get_resources(lvds, pdev);
231 if (ret < 0)
232 return ret;
233
234 rcdu->lvds[i] = lvds;
235 }
236
237 return 0;
238}
diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h b/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h
deleted file mode 100644
index 7218ac89333e..000000000000
--- a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h
+++ /dev/null
@@ -1,64 +0,0 @@
1/*
2 * rcar_du_lvdsenc.h -- R-Car Display Unit LVDS Encoder
3 *
4 * Copyright (C) 2013-2014 Renesas Electronics Corporation
5 *
6 * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 */
13
14#ifndef __RCAR_DU_LVDSENC_H__
15#define __RCAR_DU_LVDSENC_H__
16
17#include <linux/io.h>
18#include <linux/module.h>
19
20struct rcar_drm_crtc;
21struct rcar_du_lvdsenc;
22
23enum rcar_lvds_input {
24 RCAR_LVDS_INPUT_DU0,
25 RCAR_LVDS_INPUT_DU1,
26 RCAR_LVDS_INPUT_DU2,
27};
28
29/* Keep in sync with the LVDCR0.LVMD hardware register values. */
30enum rcar_lvds_mode {
31 RCAR_LVDS_MODE_JEIDA = 0,
32 RCAR_LVDS_MODE_MIRROR = 1,
33 RCAR_LVDS_MODE_VESA = 4,
34};
35
36#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
37int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu);
38void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
39 enum rcar_lvds_mode mode);
40int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds,
41 struct drm_crtc *crtc, bool enable);
42void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
43 struct drm_display_mode *mode);
44#else
45static inline int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
46{
47 return 0;
48}
49static inline void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
50 enum rcar_lvds_mode mode)
51{
52}
53static inline int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds,
54 struct drm_crtc *crtc, bool enable)
55{
56 return 0;
57}
58static inline void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
59 struct drm_display_mode *mode)
60{
61}
62#endif
63
64#endif /* __RCAR_DU_LVDSENC_H__ */
diff --git a/drivers/gpu/drm/rcar-du/rcar_lvds.c b/drivers/gpu/drm/rcar-du/rcar_lvds.c
new file mode 100644
index 000000000000..1247e26a0559
--- /dev/null
+++ b/drivers/gpu/drm/rcar-du/rcar_lvds.c
@@ -0,0 +1,524 @@
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * rcar_lvds.c -- R-Car LVDS Encoder
4 *
5 * Copyright (C) 2013-2018 Renesas Electronics Corporation
6 *
7 * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
8 */
9
10#include <linux/clk.h>
11#include <linux/delay.h>
12#include <linux/io.h>
13#include <linux/of.h>
14#include <linux/of_device.h>
15#include <linux/of_graph.h>
16#include <linux/platform_device.h>
17#include <linux/slab.h>
18
19#include <drm/drm_atomic.h>
20#include <drm/drm_atomic_helper.h>
21#include <drm/drm_bridge.h>
22#include <drm/drm_crtc_helper.h>
23#include <drm/drm_panel.h>
24
25#include "rcar_lvds_regs.h"
26
27/* Keep in sync with the LVDCR0.LVMD hardware register values. */
28enum rcar_lvds_mode {
29 RCAR_LVDS_MODE_JEIDA = 0,
30 RCAR_LVDS_MODE_MIRROR = 1,
31 RCAR_LVDS_MODE_VESA = 4,
32};
33
34#define RCAR_LVDS_QUIRK_LANES (1 << 0) /* LVDS lanes 1 and 3 inverted */
35
36struct rcar_lvds_device_info {
37 unsigned int gen;
38 unsigned int quirks;
39};
40
41struct rcar_lvds {
42 struct device *dev;
43 const struct rcar_lvds_device_info *info;
44
45 struct drm_bridge bridge;
46
47 struct drm_bridge *next_bridge;
48 struct drm_connector connector;
49 struct drm_panel *panel;
50
51 void __iomem *mmio;
52 struct clk *clock;
53 bool enabled;
54
55 struct drm_display_mode display_mode;
56 enum rcar_lvds_mode mode;
57};
58
59#define bridge_to_rcar_lvds(bridge) \
60 container_of(bridge, struct rcar_lvds, bridge)
61
62#define connector_to_rcar_lvds(connector) \
63 container_of(connector, struct rcar_lvds, connector)
64
65static void rcar_lvds_write(struct rcar_lvds *lvds, u32 reg, u32 data)
66{
67 iowrite32(data, lvds->mmio + reg);
68}
69
70/* -----------------------------------------------------------------------------
71 * Connector & Panel
72 */
73
74static int rcar_lvds_connector_get_modes(struct drm_connector *connector)
75{
76 struct rcar_lvds *lvds = connector_to_rcar_lvds(connector);
77
78 return drm_panel_get_modes(lvds->panel);
79}
80
81static int rcar_lvds_connector_atomic_check(struct drm_connector *connector,
82 struct drm_connector_state *state)
83{
84 struct rcar_lvds *lvds = connector_to_rcar_lvds(connector);
85 const struct drm_display_mode *panel_mode;
86 struct drm_crtc_state *crtc_state;
87
88 if (list_empty(&connector->modes)) {
89 dev_dbg(lvds->dev, "connector: empty modes list\n");
90 return -EINVAL;
91 }
92
93 panel_mode = list_first_entry(&connector->modes,
94 struct drm_display_mode, head);
95
96 /* We're not allowed to modify the resolution. */
97 crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
98 if (IS_ERR(crtc_state))
99 return PTR_ERR(crtc_state);
100
101 if (crtc_state->mode.hdisplay != panel_mode->hdisplay ||
102 crtc_state->mode.vdisplay != panel_mode->vdisplay)
103 return -EINVAL;
104
105 /* The flat panel mode is fixed, just copy it to the adjusted mode. */
106 drm_mode_copy(&crtc_state->adjusted_mode, panel_mode);
107
108 return 0;
109}
110
111static const struct drm_connector_helper_funcs rcar_lvds_conn_helper_funcs = {
112 .get_modes = rcar_lvds_connector_get_modes,
113 .atomic_check = rcar_lvds_connector_atomic_check,
114};
115
116static const struct drm_connector_funcs rcar_lvds_conn_funcs = {
117 .reset = drm_atomic_helper_connector_reset,
118 .fill_modes = drm_helper_probe_single_connector_modes,
119 .destroy = drm_connector_cleanup,
120 .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
121 .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
122};
123
124/* -----------------------------------------------------------------------------
125 * Bridge
126 */
127
128static u32 rcar_lvds_lvdpllcr_gen2(unsigned int freq)
129{
130 if (freq < 39000)
131 return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
132 else if (freq < 61000)
133 return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
134 else if (freq < 121000)
135 return LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
136 else
137 return LVDPLLCR_PLLDLYCNT_150M;
138}
139
140static u32 rcar_lvds_lvdpllcr_gen3(unsigned int freq)
141{
142 if (freq < 42000)
143 return LVDPLLCR_PLLDIVCNT_42M;
144 else if (freq < 85000)
145 return LVDPLLCR_PLLDIVCNT_85M;
146 else if (freq < 128000)
147 return LVDPLLCR_PLLDIVCNT_128M;
148 else
149 return LVDPLLCR_PLLDIVCNT_148M;
150}
151
152static void rcar_lvds_enable(struct drm_bridge *bridge)
153{
154 struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
155 const struct drm_display_mode *mode = &lvds->display_mode;
156 /*
157 * FIXME: We should really retrieve the CRTC through the state, but how
158 * do we get a state pointer?
159 */
160 struct drm_crtc *crtc = lvds->bridge.encoder->crtc;
161 u32 lvdpllcr;
162 u32 lvdhcr;
163 u32 lvdcr0;
164 int ret;
165
166 WARN_ON(lvds->enabled);
167
168 ret = clk_prepare_enable(lvds->clock);
169 if (ret < 0)
170 return;
171
172 /*
173 * Hardcode the channels and control signals routing for now.
174 *
175 * HSYNC -> CTRL0
176 * VSYNC -> CTRL1
177 * DISP -> CTRL2
178 * 0 -> CTRL3
179 */
180 rcar_lvds_write(lvds, LVDCTRCR, LVDCTRCR_CTR3SEL_ZERO |
181 LVDCTRCR_CTR2SEL_DISP | LVDCTRCR_CTR1SEL_VSYNC |
182 LVDCTRCR_CTR0SEL_HSYNC);
183
184 if (lvds->info->quirks & RCAR_LVDS_QUIRK_LANES)
185 lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 3)
186 | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 1);
187 else
188 lvdhcr = LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 1)
189 | LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 3);
190
191 rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
192
193 /* PLL clock configuration. */
194 if (lvds->info->gen < 3)
195 lvdpllcr = rcar_lvds_lvdpllcr_gen2(mode->clock);
196 else
197 lvdpllcr = rcar_lvds_lvdpllcr_gen3(mode->clock);
198 rcar_lvds_write(lvds, LVDPLLCR, lvdpllcr);
199
200 /* Set the LVDS mode and select the input. */
201 lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
202 if (drm_crtc_index(crtc) == 2)
203 lvdcr0 |= LVDCR0_DUSEL;
204 rcar_lvds_write(lvds, LVDCR0, lvdcr0);
205
206 /* Turn all the channels on. */
207 rcar_lvds_write(lvds, LVDCR1,
208 LVDCR1_CHSTBY(3) | LVDCR1_CHSTBY(2) |
209 LVDCR1_CHSTBY(1) | LVDCR1_CHSTBY(0) | LVDCR1_CLKSTBY);
210
211 if (lvds->info->gen < 3) {
212 /* Enable LVDS operation and turn the bias circuitry on. */
213 lvdcr0 |= LVDCR0_BEN | LVDCR0_LVEN;
214 rcar_lvds_write(lvds, LVDCR0, lvdcr0);
215 }
216
217 /* Turn the PLL on. */
218 lvdcr0 |= LVDCR0_PLLON;
219 rcar_lvds_write(lvds, LVDCR0, lvdcr0);
220
221 if (lvds->info->gen > 2) {
222 /* Set LVDS normal mode. */
223 lvdcr0 |= LVDCR0_PWD;
224 rcar_lvds_write(lvds, LVDCR0, lvdcr0);
225 }
226
227 /* Wait for the startup delay. */
228 usleep_range(100, 150);
229
230 /* Turn the output on. */
231 lvdcr0 |= LVDCR0_LVRES;
232 rcar_lvds_write(lvds, LVDCR0, lvdcr0);
233
234 if (lvds->panel) {
235 drm_panel_prepare(lvds->panel);
236 drm_panel_enable(lvds->panel);
237 }
238
239 lvds->enabled = true;
240}
241
242static void rcar_lvds_disable(struct drm_bridge *bridge)
243{
244 struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
245
246 WARN_ON(!lvds->enabled);
247
248 if (lvds->panel) {
249 drm_panel_disable(lvds->panel);
250 drm_panel_unprepare(lvds->panel);
251 }
252
253 rcar_lvds_write(lvds, LVDCR0, 0);
254 rcar_lvds_write(lvds, LVDCR1, 0);
255
256 clk_disable_unprepare(lvds->clock);
257
258 lvds->enabled = false;
259}
260
261static bool rcar_lvds_mode_fixup(struct drm_bridge *bridge,
262 const struct drm_display_mode *mode,
263 struct drm_display_mode *adjusted_mode)
264{
265 /*
266 * The internal LVDS encoder has a restricted clock frequency operating
267 * range (31MHz to 148.5MHz). Clamp the clock accordingly.
268 */
269 adjusted_mode->clock = clamp(adjusted_mode->clock, 31000, 148500);
270
271 return true;
272}
273
274static void rcar_lvds_get_lvds_mode(struct rcar_lvds *lvds)
275{
276 struct drm_display_info *info = &lvds->connector.display_info;
277 enum rcar_lvds_mode mode;
278
279 /*
280 * There is no API yet to retrieve LVDS mode from a bridge, only panels
281 * are supported.
282 */
283 if (!lvds->panel)
284 return;
285
286 if (!info->num_bus_formats || !info->bus_formats) {
287 dev_err(lvds->dev, "no LVDS bus format reported\n");
288 return;
289 }
290
291 switch (info->bus_formats[0]) {
292 case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
293 case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
294 mode = RCAR_LVDS_MODE_JEIDA;
295 break;
296 case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
297 mode = RCAR_LVDS_MODE_VESA;
298 break;
299 default:
300 dev_err(lvds->dev, "unsupported LVDS bus format 0x%04x\n",
301 info->bus_formats[0]);
302 return;
303 }
304
305 if (info->bus_flags & DRM_BUS_FLAG_DATA_LSB_TO_MSB)
306 mode |= RCAR_LVDS_MODE_MIRROR;
307
308 lvds->mode = mode;
309}
310
311static void rcar_lvds_mode_set(struct drm_bridge *bridge,
312 struct drm_display_mode *mode,
313 struct drm_display_mode *adjusted_mode)
314{
315 struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
316
317 WARN_ON(lvds->enabled);
318
319 lvds->display_mode = *adjusted_mode;
320
321 rcar_lvds_get_lvds_mode(lvds);
322}
323
324static int rcar_lvds_attach(struct drm_bridge *bridge)
325{
326 struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
327 struct drm_connector *connector = &lvds->connector;
328 struct drm_encoder *encoder = bridge->encoder;
329 int ret;
330
331 /* If we have a next bridge just attach it. */
332 if (lvds->next_bridge)
333 return drm_bridge_attach(bridge->encoder, lvds->next_bridge,
334 bridge);
335
336 /* Otherwise we have a panel, create a connector. */
337 ret = drm_connector_init(bridge->dev, connector, &rcar_lvds_conn_funcs,
338 DRM_MODE_CONNECTOR_LVDS);
339 if (ret < 0)
340 return ret;
341
342 drm_connector_helper_add(connector, &rcar_lvds_conn_helper_funcs);
343
344 ret = drm_mode_connector_attach_encoder(connector, encoder);
345 if (ret < 0)
346 return ret;
347
348 return drm_panel_attach(lvds->panel, connector);
349}
350
351static void rcar_lvds_detach(struct drm_bridge *bridge)
352{
353 struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
354
355 if (lvds->panel)
356 drm_panel_detach(lvds->panel);
357}
358
359static const struct drm_bridge_funcs rcar_lvds_bridge_ops = {
360 .attach = rcar_lvds_attach,
361 .detach = rcar_lvds_detach,
362 .enable = rcar_lvds_enable,
363 .disable = rcar_lvds_disable,
364 .mode_fixup = rcar_lvds_mode_fixup,
365 .mode_set = rcar_lvds_mode_set,
366};
367
368/* -----------------------------------------------------------------------------
369 * Probe & Remove
370 */
371
372static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
373{
374 struct device_node *local_output = NULL;
375 struct device_node *remote_input = NULL;
376 struct device_node *remote = NULL;
377 struct device_node *node;
378 bool is_bridge = false;
379 int ret = 0;
380
381 local_output = of_graph_get_endpoint_by_regs(lvds->dev->of_node, 1, 0);
382 if (!local_output) {
383 dev_dbg(lvds->dev, "unconnected port@1\n");
384 return -ENODEV;
385 }
386
387 /*
388 * Locate the connected entity and infer its type from the number of
389 * endpoints.
390 */
391 remote = of_graph_get_remote_port_parent(local_output);
392 if (!remote) {
393 dev_dbg(lvds->dev, "unconnected endpoint %pOF\n", local_output);
394 ret = -ENODEV;
395 goto done;
396 }
397
398 if (!of_device_is_available(remote)) {
399 dev_dbg(lvds->dev, "connected entity %pOF is disabled\n",
400 remote);
401 ret = -ENODEV;
402 goto done;
403 }
404
405 remote_input = of_graph_get_remote_endpoint(local_output);
406
407 for_each_endpoint_of_node(remote, node) {
408 if (node != remote_input) {
409 /*
410 * We've found one endpoint other than the input, this
411 * must be a bridge.
412 */
413 is_bridge = true;
414 of_node_put(node);
415 break;
416 }
417 }
418
419 if (is_bridge) {
420 lvds->next_bridge = of_drm_find_bridge(remote);
421 if (!lvds->next_bridge)
422 ret = -EPROBE_DEFER;
423 } else {
424 lvds->panel = of_drm_find_panel(remote);
425 if (!lvds->panel)
426 ret = -EPROBE_DEFER;
427 }
428
429done:
430 of_node_put(local_output);
431 of_node_put(remote_input);
432 of_node_put(remote);
433
434 return ret;
435}
436
437static int rcar_lvds_probe(struct platform_device *pdev)
438{
439 struct rcar_lvds *lvds;
440 struct resource *mem;
441 int ret;
442
443 lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
444 if (lvds == NULL)
445 return -ENOMEM;
446
447 platform_set_drvdata(pdev, lvds);
448
449 lvds->dev = &pdev->dev;
450 lvds->info = of_device_get_match_data(&pdev->dev);
451 lvds->enabled = false;
452
453 ret = rcar_lvds_parse_dt(lvds);
454 if (ret < 0)
455 return ret;
456
457 lvds->bridge.driver_private = lvds;
458 lvds->bridge.funcs = &rcar_lvds_bridge_ops;
459 lvds->bridge.of_node = pdev->dev.of_node;
460
461 mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
462 lvds->mmio = devm_ioremap_resource(&pdev->dev, mem);
463 if (IS_ERR(lvds->mmio))
464 return PTR_ERR(lvds->mmio);
465
466 lvds->clock = devm_clk_get(&pdev->dev, NULL);
467 if (IS_ERR(lvds->clock)) {
468 dev_err(&pdev->dev, "failed to get clock\n");
469 return PTR_ERR(lvds->clock);
470 }
471
472 drm_bridge_add(&lvds->bridge);
473
474 return 0;
475}
476
477static int rcar_lvds_remove(struct platform_device *pdev)
478{
479 struct rcar_lvds *lvds = platform_get_drvdata(pdev);
480
481 drm_bridge_remove(&lvds->bridge);
482
483 return 0;
484}
485
486static const struct rcar_lvds_device_info rcar_lvds_gen2_info = {
487 .gen = 2,
488};
489
490static const struct rcar_lvds_device_info rcar_lvds_r8a7790_info = {
491 .gen = 2,
492 .quirks = RCAR_LVDS_QUIRK_LANES,
493};
494
495static const struct rcar_lvds_device_info rcar_lvds_gen3_info = {
496 .gen = 3,
497};
498
499static const struct of_device_id rcar_lvds_of_table[] = {
500 { .compatible = "renesas,r8a7743-lvds", .data = &rcar_lvds_gen2_info },
501 { .compatible = "renesas,r8a7790-lvds", .data = &rcar_lvds_r8a7790_info },
502 { .compatible = "renesas,r8a7791-lvds", .data = &rcar_lvds_gen2_info },
503 { .compatible = "renesas,r8a7793-lvds", .data = &rcar_lvds_gen2_info },
504 { .compatible = "renesas,r8a7795-lvds", .data = &rcar_lvds_gen3_info },
505 { .compatible = "renesas,r8a7796-lvds", .data = &rcar_lvds_gen3_info },
506 { }
507};
508
509MODULE_DEVICE_TABLE(of, rcar_lvds_of_table);
510
511static struct platform_driver rcar_lvds_platform_driver = {
512 .probe = rcar_lvds_probe,
513 .remove = rcar_lvds_remove,
514 .driver = {
515 .name = "rcar-lvds",
516 .of_match_table = rcar_lvds_of_table,
517 },
518};
519
520module_platform_driver(rcar_lvds_platform_driver);
521
522MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
523MODULE_DESCRIPTION("Renesas R-Car LVDS Encoder Driver");
524MODULE_LICENSE("GPL");