Commit | Line | Data |
---|---|---|
29e57fab MR |
1 | /* |
2 | * Copyright (C) 2015 Free Electrons | |
3 | * Copyright (C) 2015 NextThing Co | |
4 | * | |
5 | * Maxime Ripard <maxime.ripard@free-electrons.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU General Public License as | |
9 | * published by the Free Software Foundation; either version 2 of | |
10 | * the License, or (at your option) any later version. | |
11 | */ | |
12 | ||
13 | #include <linux/clk.h> | |
14 | ||
15 | #include <drm/drmP.h> | |
16 | #include <drm/drm_atomic_helper.h> | |
17 | #include <drm/drm_crtc_helper.h> | |
18 | #include <drm/drm_panel.h> | |
19 | ||
20 | #include "sun4i_drv.h" | |
21 | #include "sun4i_tcon.h" | |
0c3ff44c | 22 | #include "sun4i_rgb.h" |
29e57fab MR |
23 | |
24 | struct sun4i_rgb { | |
25 | struct drm_connector connector; | |
26 | struct drm_encoder encoder; | |
27 | ||
28 | struct sun4i_drv *drv; | |
29 | }; | |
30 | ||
31 | static inline struct sun4i_rgb * | |
32 | drm_connector_to_sun4i_rgb(struct drm_connector *connector) | |
33 | { | |
34 | return container_of(connector, struct sun4i_rgb, | |
35 | connector); | |
36 | } | |
37 | ||
38 | static inline struct sun4i_rgb * | |
39 | drm_encoder_to_sun4i_rgb(struct drm_encoder *encoder) | |
40 | { | |
41 | return container_of(encoder, struct sun4i_rgb, | |
42 | encoder); | |
43 | } | |
44 | ||
45 | static int sun4i_rgb_get_modes(struct drm_connector *connector) | |
46 | { | |
47 | struct sun4i_rgb *rgb = | |
48 | drm_connector_to_sun4i_rgb(connector); | |
49 | struct sun4i_drv *drv = rgb->drv; | |
50 | struct sun4i_tcon *tcon = drv->tcon; | |
51 | ||
52 | return drm_panel_get_modes(tcon->panel); | |
53 | } | |
54 | ||
55 | static int sun4i_rgb_mode_valid(struct drm_connector *connector, | |
56 | struct drm_display_mode *mode) | |
57 | { | |
bb43d40d MR |
58 | struct sun4i_rgb *rgb = drm_connector_to_sun4i_rgb(connector); |
59 | struct sun4i_drv *drv = rgb->drv; | |
60 | struct sun4i_tcon *tcon = drv->tcon; | |
29e57fab MR |
61 | u32 hsync = mode->hsync_end - mode->hsync_start; |
62 | u32 vsync = mode->vsync_end - mode->vsync_start; | |
bb43d40d MR |
63 | unsigned long rate = mode->clock * 1000; |
64 | long rounded_rate; | |
29e57fab MR |
65 | |
66 | DRM_DEBUG_DRIVER("Validating modes...\n"); | |
67 | ||
68 | if (hsync < 1) | |
69 | return MODE_HSYNC_NARROW; | |
70 | ||
71 | if (hsync > 0x3ff) | |
72 | return MODE_HSYNC_WIDE; | |
73 | ||
74 | if ((mode->hdisplay < 1) || (mode->htotal < 1)) | |
75 | return MODE_H_ILLEGAL; | |
76 | ||
77 | if ((mode->hdisplay > 0x7ff) || (mode->htotal > 0xfff)) | |
78 | return MODE_BAD_HVALUE; | |
79 | ||
80 | DRM_DEBUG_DRIVER("Horizontal parameters OK\n"); | |
81 | ||
82 | if (vsync < 1) | |
83 | return MODE_VSYNC_NARROW; | |
84 | ||
85 | if (vsync > 0x3ff) | |
86 | return MODE_VSYNC_WIDE; | |
87 | ||
88 | if ((mode->vdisplay < 1) || (mode->vtotal < 1)) | |
89 | return MODE_V_ILLEGAL; | |
90 | ||
91 | if ((mode->vdisplay > 0x7ff) || (mode->vtotal > 0xfff)) | |
92 | return MODE_BAD_VVALUE; | |
93 | ||
94 | DRM_DEBUG_DRIVER("Vertical parameters OK\n"); | |
95 | ||
bb43d40d MR |
96 | rounded_rate = clk_round_rate(tcon->dclk, rate); |
97 | if (rounded_rate < rate) | |
98 | return MODE_CLOCK_LOW; | |
99 | ||
100 | if (rounded_rate > rate) | |
101 | return MODE_CLOCK_HIGH; | |
102 | ||
103 | DRM_DEBUG_DRIVER("Clock rate OK\n"); | |
104 | ||
29e57fab MR |
105 | return MODE_OK; |
106 | } | |
107 | ||
29e57fab MR |
108 | static struct drm_connector_helper_funcs sun4i_rgb_con_helper_funcs = { |
109 | .get_modes = sun4i_rgb_get_modes, | |
110 | .mode_valid = sun4i_rgb_mode_valid, | |
29e57fab MR |
111 | }; |
112 | ||
113 | static enum drm_connector_status | |
114 | sun4i_rgb_connector_detect(struct drm_connector *connector, bool force) | |
115 | { | |
116 | return connector_status_connected; | |
117 | } | |
118 | ||
119 | static void | |
120 | sun4i_rgb_connector_destroy(struct drm_connector *connector) | |
121 | { | |
122 | struct sun4i_rgb *rgb = drm_connector_to_sun4i_rgb(connector); | |
123 | struct sun4i_drv *drv = rgb->drv; | |
124 | struct sun4i_tcon *tcon = drv->tcon; | |
125 | ||
126 | drm_panel_detach(tcon->panel); | |
127 | drm_connector_cleanup(connector); | |
128 | } | |
129 | ||
130 | static struct drm_connector_funcs sun4i_rgb_con_funcs = { | |
131 | .dpms = drm_atomic_helper_connector_dpms, | |
132 | .detect = sun4i_rgb_connector_detect, | |
133 | .fill_modes = drm_helper_probe_single_connector_modes, | |
134 | .destroy = sun4i_rgb_connector_destroy, | |
135 | .reset = drm_atomic_helper_connector_reset, | |
136 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, | |
137 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | |
138 | }; | |
139 | ||
140 | static int sun4i_rgb_atomic_check(struct drm_encoder *encoder, | |
141 | struct drm_crtc_state *crtc_state, | |
142 | struct drm_connector_state *conn_state) | |
143 | { | |
144 | return 0; | |
145 | } | |
146 | ||
147 | static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder) | |
148 | { | |
149 | struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); | |
150 | struct sun4i_drv *drv = rgb->drv; | |
151 | struct sun4i_tcon *tcon = drv->tcon; | |
152 | ||
153 | DRM_DEBUG_DRIVER("Enabling RGB output\n"); | |
154 | ||
4b309502 JL |
155 | if (!IS_ERR(tcon->panel)) { |
156 | drm_panel_prepare(tcon->panel); | |
894f5a9f | 157 | drm_panel_enable(tcon->panel); |
4b309502 | 158 | } |
894f5a9f | 159 | |
b5644a5e CYT |
160 | /* encoder->bridge can be NULL; drm_bridge_enable checks for it */ |
161 | drm_bridge_enable(encoder->bridge); | |
894f5a9f | 162 | |
29e57fab MR |
163 | sun4i_tcon_channel_enable(tcon, 0); |
164 | } | |
165 | ||
166 | static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) | |
167 | { | |
168 | struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); | |
169 | struct sun4i_drv *drv = rgb->drv; | |
170 | struct sun4i_tcon *tcon = drv->tcon; | |
171 | ||
172 | DRM_DEBUG_DRIVER("Disabling RGB output\n"); | |
173 | ||
174 | sun4i_tcon_channel_disable(tcon, 0); | |
894f5a9f | 175 | |
b5644a5e CYT |
176 | /* encoder->bridge can be NULL; drm_bridge_disable checks for it */ |
177 | drm_bridge_disable(encoder->bridge); | |
894f5a9f | 178 | |
4b309502 | 179 | if (!IS_ERR(tcon->panel)) { |
894f5a9f | 180 | drm_panel_disable(tcon->panel); |
4b309502 JL |
181 | drm_panel_unprepare(tcon->panel); |
182 | } | |
29e57fab MR |
183 | } |
184 | ||
185 | static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder, | |
186 | struct drm_display_mode *mode, | |
187 | struct drm_display_mode *adjusted_mode) | |
188 | { | |
189 | struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); | |
190 | struct sun4i_drv *drv = rgb->drv; | |
191 | struct sun4i_tcon *tcon = drv->tcon; | |
192 | ||
193 | sun4i_tcon0_mode_set(tcon, mode); | |
194 | ||
195 | clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); | |
196 | ||
197 | /* FIXME: This seems to be board specific */ | |
198 | clk_set_phase(tcon->dclk, 120); | |
199 | } | |
200 | ||
201 | static struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = { | |
202 | .atomic_check = sun4i_rgb_atomic_check, | |
203 | .mode_set = sun4i_rgb_encoder_mode_set, | |
204 | .disable = sun4i_rgb_encoder_disable, | |
205 | .enable = sun4i_rgb_encoder_enable, | |
206 | }; | |
207 | ||
208 | static void sun4i_rgb_enc_destroy(struct drm_encoder *encoder) | |
209 | { | |
210 | drm_encoder_cleanup(encoder); | |
211 | } | |
212 | ||
213 | static struct drm_encoder_funcs sun4i_rgb_enc_funcs = { | |
214 | .destroy = sun4i_rgb_enc_destroy, | |
215 | }; | |
216 | ||
217 | int sun4i_rgb_init(struct drm_device *drm) | |
218 | { | |
219 | struct sun4i_drv *drv = drm->dev_private; | |
220 | struct sun4i_tcon *tcon = drv->tcon; | |
894f5a9f | 221 | struct drm_encoder *encoder; |
29e57fab MR |
222 | struct sun4i_rgb *rgb; |
223 | int ret; | |
224 | ||
29e57fab MR |
225 | rgb = devm_kzalloc(drm->dev, sizeof(*rgb), GFP_KERNEL); |
226 | if (!rgb) | |
227 | return -ENOMEM; | |
228 | rgb->drv = drv; | |
894f5a9f | 229 | encoder = &rgb->encoder; |
29e57fab | 230 | |
a8444c7e | 231 | tcon->panel = sun4i_tcon_find_panel(tcon->dev->of_node); |
894f5a9f MR |
232 | encoder->bridge = sun4i_tcon_find_bridge(tcon->dev->of_node); |
233 | if (IS_ERR(tcon->panel) && IS_ERR(encoder->bridge)) { | |
234 | dev_info(drm->dev, "No panel or bridge found... RGB output disabled\n"); | |
a8444c7e MR |
235 | return 0; |
236 | } | |
237 | ||
29e57fab MR |
238 | drm_encoder_helper_add(&rgb->encoder, |
239 | &sun4i_rgb_enc_helper_funcs); | |
240 | ret = drm_encoder_init(drm, | |
241 | &rgb->encoder, | |
242 | &sun4i_rgb_enc_funcs, | |
243 | DRM_MODE_ENCODER_NONE, | |
244 | NULL); | |
245 | if (ret) { | |
246 | dev_err(drm->dev, "Couldn't initialise the rgb encoder\n"); | |
247 | goto err_out; | |
248 | } | |
249 | ||
250 | /* The RGB encoder can only work with the TCON channel 0 */ | |
251 | rgb->encoder.possible_crtcs = BIT(0); | |
252 | ||
894f5a9f MR |
253 | if (!IS_ERR(tcon->panel)) { |
254 | drm_connector_helper_add(&rgb->connector, | |
255 | &sun4i_rgb_con_helper_funcs); | |
256 | ret = drm_connector_init(drm, &rgb->connector, | |
257 | &sun4i_rgb_con_funcs, | |
258 | DRM_MODE_CONNECTOR_Unknown); | |
259 | if (ret) { | |
260 | dev_err(drm->dev, "Couldn't initialise the rgb connector\n"); | |
261 | goto err_cleanup_connector; | |
262 | } | |
263 | ||
264 | drm_mode_connector_attach_encoder(&rgb->connector, | |
265 | &rgb->encoder); | |
266 | ||
267 | ret = drm_panel_attach(tcon->panel, &rgb->connector); | |
268 | if (ret) { | |
269 | dev_err(drm->dev, "Couldn't attach our panel\n"); | |
270 | goto err_cleanup_connector; | |
271 | } | |
29e57fab MR |
272 | } |
273 | ||
894f5a9f MR |
274 | if (!IS_ERR(encoder->bridge)) { |
275 | encoder->bridge->encoder = &rgb->encoder; | |
29e57fab | 276 | |
894f5a9f MR |
277 | ret = drm_bridge_attach(drm, encoder->bridge); |
278 | if (ret) { | |
279 | dev_err(drm->dev, "Couldn't attach our bridge\n"); | |
280 | goto err_cleanup_connector; | |
281 | } | |
b5644a5e CYT |
282 | } else { |
283 | encoder->bridge = NULL; | |
894f5a9f | 284 | } |
29e57fab MR |
285 | |
286 | return 0; | |
287 | ||
288 | err_cleanup_connector: | |
289 | drm_encoder_cleanup(&rgb->encoder); | |
290 | err_out: | |
291 | return ret; | |
292 | } | |
293 | EXPORT_SYMBOL(sun4i_rgb_init); |