diff options
-rw-r--r-- | drivers/gpu/drm/rcar-du/Kconfig | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/rcar-du/Makefile | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/rcar-du/rcar_du_drv.c | 21 | ||||
-rw-r--r-- | drivers/gpu/drm/rcar-du/rcar_du_drv.h | 5 | ||||
-rw-r--r-- | drivers/gpu/drm/rcar-du/rcar_du_encoder.c | 175 | ||||
-rw-r--r-- | drivers/gpu/drm/rcar-du/rcar_du_encoder.h | 12 | ||||
-rw-r--r-- | drivers/gpu/drm/rcar-du/rcar_du_kms.c | 14 | ||||
-rw-r--r-- | drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c | 93 | ||||
-rw-r--r-- | drivers/gpu/drm/rcar-du/rcar_du_lvdscon.h | 24 | ||||
-rw-r--r-- | drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c | 238 | ||||
-rw-r--r-- | drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h | 64 | ||||
-rw-r--r-- | drivers/gpu/drm/rcar-du/rcar_lvds.c | 524 |
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 | ||
21 | config DRM_RCAR_LVDS | 21 | config 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 | ||
10 | rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.o | ||
11 | rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_of.o \ | 9 | rcar-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 | ||
19 | obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o | 17 | obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o |
20 | obj-$(CONFIG_DRM_RCAR_DW_HDMI) += rcar_dw_hdmi.o | 18 | obj-$(CONFIG_DRM_RCAR_DW_HDMI) += rcar_dw_hdmi.o |
19 | obj-$(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 | ||
80 | static const struct rcar_du_device_info rcar_du_r8a7779_info = { | 80 | static const struct rcar_du_device_info rcar_du_r8a7779_info = { |
@@ -95,14 +95,13 @@ static const struct rcar_du_device_info rcar_du_r8a7779_info = { | |||
95 | .port = 1, | 95 | .port = 1, |
96 | }, | 96 | }, |
97 | }, | 97 | }, |
98 | .num_lvds = 0, | ||
99 | }; | 98 | }; |
100 | 99 | ||
101 | static const struct rcar_du_device_info rcar_du_r8a7790_info = { | 100 | static 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 | ||
170 | static const struct rcar_du_device_info rcar_du_r8a7794_info = { | 168 | static const struct rcar_du_device_info rcar_du_r8a7794_info = { |
@@ -186,7 +184,6 @@ static const struct rcar_du_device_info rcar_du_r8a7794_info = { | |||
186 | .port = 1, | 184 | .port = 1, |
187 | }, | 185 | }, |
188 | }, | 186 | }, |
189 | .num_lvds = 0, | ||
190 | }; | 187 | }; |
191 | 188 | ||
192 | static const struct rcar_du_device_info rcar_du_r8a7795_info = { | 189 | static 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 | ||
437 | module_platform_driver(rcar_du_platform_driver); | 434 | static 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 | } | ||
440 | module_init(rcar_du_init); | ||
441 | |||
442 | static void __exit rcar_du_exit(void) | ||
443 | { | ||
444 | platform_driver_unregister(&rcar_du_platform_driver); | ||
445 | } | ||
446 | module_exit(rcar_du_exit); | ||
438 | 447 | ||
439 | MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); | 448 | MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); |
440 | MODULE_DESCRIPTION("Renesas R-Car Display Unit DRM Driver"); | 449 | MODULE_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; | |||
26 | struct drm_device; | 26 | struct drm_device; |
27 | struct drm_fbdev_cma; | 27 | struct drm_fbdev_cma; |
28 | struct rcar_du_device; | 28 | struct rcar_du_device; |
29 | struct 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 | ||
76 | struct rcar_du_device { | 73 | struct 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 | ||
103 | static inline bool rcar_du_has(struct rcar_du_device *rcdu, | 98 | static 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 | ||
31 | static 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 | |||
44 | static 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 | |||
57 | static 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 | |||
100 | static void rcar_du_encoder_mode_set(struct drm_encoder *encoder, | 29 | static 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 | ||
147 | static const struct drm_encoder_helper_funcs encoder_helper_funcs = { | 38 | static 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 | ||
154 | static const struct drm_encoder_funcs encoder_funcs = { | 42 | static 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 | ||
235 | done: | 90 | done: |
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 | ||
20 | struct drm_panel; | 20 | struct drm_panel; |
21 | struct rcar_du_device; | 21 | struct rcar_du_device; |
22 | struct rcar_du_lvdsenc; | ||
23 | 22 | ||
24 | struct rcar_du_encoder { | 23 | struct 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 | ||
36 | struct 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 | |||
45 | int rcar_du_encoder_init(struct rcar_du_device *rcdu, | 33 | int 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 | |||
29 | static 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 | |||
36 | static const struct drm_connector_helper_funcs connector_helper_funcs = { | ||
37 | .get_modes = rcar_du_lvds_connector_get_modes, | ||
38 | }; | ||
39 | |||
40 | static 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 | |||
48 | static 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 | |||
56 | int 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 | |||
17 | struct rcar_du_device; | ||
18 | struct rcar_du_encoder; | ||
19 | |||
20 | int 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 | |||
25 | struct 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 | |||
37 | static void rcar_lvds_write(struct rcar_du_lvdsenc *lvds, u32 reg, u32 data) | ||
38 | { | ||
39 | iowrite32(data, lvds->mmio + reg); | ||
40 | } | ||
41 | |||
42 | static 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 | |||
54 | static 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 | |||
66 | static 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 | |||
149 | static 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 | |||
162 | int 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 | |||
175 | void 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 | |||
185 | void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds, | ||
186 | enum rcar_lvds_mode mode) | ||
187 | { | ||
188 | lvds->mode = mode; | ||
189 | } | ||
190 | |||
191 | static 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 | |||
213 | int 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 | |||
20 | struct rcar_drm_crtc; | ||
21 | struct rcar_du_lvdsenc; | ||
22 | |||
23 | enum 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. */ | ||
30 | enum 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) | ||
37 | int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu); | ||
38 | void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds, | ||
39 | enum rcar_lvds_mode mode); | ||
40 | int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, | ||
41 | struct drm_crtc *crtc, bool enable); | ||
42 | void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds, | ||
43 | struct drm_display_mode *mode); | ||
44 | #else | ||
45 | static inline int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu) | ||
46 | { | ||
47 | return 0; | ||
48 | } | ||
49 | static inline void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds, | ||
50 | enum rcar_lvds_mode mode) | ||
51 | { | ||
52 | } | ||
53 | static inline int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, | ||
54 | struct drm_crtc *crtc, bool enable) | ||
55 | { | ||
56 | return 0; | ||
57 | } | ||
58 | static 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. */ | ||
28 | enum 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 | |||
36 | struct rcar_lvds_device_info { | ||
37 | unsigned int gen; | ||
38 | unsigned int quirks; | ||
39 | }; | ||
40 | |||
41 | struct 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 | |||
65 | static 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 | |||
74 | static 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 | |||
81 | static 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 | |||
111 | static 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 | |||
116 | static 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 | |||
128 | static 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 | |||
140 | static 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 | |||
152 | static 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 | |||
242 | static 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 | |||
261 | static 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 | |||
274 | static 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 | |||
311 | static 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 | |||
324 | static 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 | |||
351 | static 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 | |||
359 | static 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 | |||
372 | static 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 | |||
429 | done: | ||
430 | of_node_put(local_output); | ||
431 | of_node_put(remote_input); | ||
432 | of_node_put(remote); | ||
433 | |||
434 | return ret; | ||
435 | } | ||
436 | |||
437 | static 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 | |||
477 | static 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 | |||
486 | static const struct rcar_lvds_device_info rcar_lvds_gen2_info = { | ||
487 | .gen = 2, | ||
488 | }; | ||
489 | |||
490 | static const struct rcar_lvds_device_info rcar_lvds_r8a7790_info = { | ||
491 | .gen = 2, | ||
492 | .quirks = RCAR_LVDS_QUIRK_LANES, | ||
493 | }; | ||
494 | |||
495 | static const struct rcar_lvds_device_info rcar_lvds_gen3_info = { | ||
496 | .gen = 3, | ||
497 | }; | ||
498 | |||
499 | static 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 | |||
509 | MODULE_DEVICE_TABLE(of, rcar_lvds_of_table); | ||
510 | |||
511 | static 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 | |||
520 | module_platform_driver(rcar_lvds_platform_driver); | ||
521 | |||
522 | MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); | ||
523 | MODULE_DESCRIPTION("Renesas R-Car LVDS Encoder Driver"); | ||
524 | MODULE_LICENSE("GPL"); | ||