Commit | Line | Data |
---|---|---|
caab277b | 1 | // SPDX-License-Identifier: GPL-2.0-only |
1a396789 BB |
2 | /* |
3 | * Copyright (C) 2014 Traphandler | |
4 | * Copyright (C) 2014 Free Electrons | |
5 | * | |
6 | * Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com> | |
7 | * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> | |
1a396789 BB |
8 | */ |
9 | ||
10 | #include <linux/clk.h> | |
11 | #include <linux/pm.h> | |
12 | #include <linux/pm_runtime.h> | |
16e6004e | 13 | #include <linux/pinctrl/consumer.h> |
1a396789 BB |
14 | |
15 | #include <drm/drm_crtc.h> | |
fcd70cd3 | 16 | #include <drm/drm_probe_helper.h> |
1a396789 BB |
17 | #include <drm/drmP.h> |
18 | ||
19 | #include <video/videomode.h> | |
20 | ||
21 | #include "atmel_hlcdc_dc.h" | |
22 | ||
aca63b76 BB |
23 | /** |
24 | * Atmel HLCDC CRTC state structure | |
25 | * | |
26 | * @base: base CRTC state | |
27 | * @output_mode: RGBXXX output mode | |
28 | */ | |
29 | struct atmel_hlcdc_crtc_state { | |
30 | struct drm_crtc_state base; | |
31 | unsigned int output_mode; | |
32 | }; | |
33 | ||
34 | static inline struct atmel_hlcdc_crtc_state * | |
35 | drm_crtc_state_to_atmel_hlcdc_crtc_state(struct drm_crtc_state *state) | |
36 | { | |
37 | return container_of(state, struct atmel_hlcdc_crtc_state, base); | |
38 | } | |
39 | ||
1a396789 BB |
40 | /** |
41 | * Atmel HLCDC CRTC structure | |
42 | * | |
43 | * @base: base DRM CRTC structure | |
44 | * @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device | |
45 | * @event: pointer to the current page flip event | |
46 | * @id: CRTC id (returned by drm_crtc_index) | |
1a396789 BB |
47 | */ |
48 | struct atmel_hlcdc_crtc { | |
49 | struct drm_crtc base; | |
50 | struct atmel_hlcdc_dc *dc; | |
51 | struct drm_pending_vblank_event *event; | |
52 | int id; | |
1a396789 BB |
53 | }; |
54 | ||
55 | static inline struct atmel_hlcdc_crtc * | |
56 | drm_crtc_to_atmel_hlcdc_crtc(struct drm_crtc *crtc) | |
57 | { | |
58 | return container_of(crtc, struct atmel_hlcdc_crtc, base); | |
59 | } | |
60 | ||
2389fc13 | 61 | static void atmel_hlcdc_crtc_mode_set_nofb(struct drm_crtc *c) |
1a396789 | 62 | { |
1a396789 BB |
63 | struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); |
64 | struct regmap *regmap = crtc->dc->hlcdc->regmap; | |
2389fc13 | 65 | struct drm_display_mode *adj = &c->state->adjusted_mode; |
aca63b76 | 66 | struct atmel_hlcdc_crtc_state *state; |
1a396789 BB |
67 | unsigned long mode_rate; |
68 | struct videomode vm; | |
69 | unsigned long prate; | |
70 | unsigned int cfg; | |
71 | int div; | |
72 | ||
1a396789 BB |
73 | vm.vfront_porch = adj->crtc_vsync_start - adj->crtc_vdisplay; |
74 | vm.vback_porch = adj->crtc_vtotal - adj->crtc_vsync_end; | |
75 | vm.vsync_len = adj->crtc_vsync_end - adj->crtc_vsync_start; | |
76 | vm.hfront_porch = adj->crtc_hsync_start - adj->crtc_hdisplay; | |
77 | vm.hback_porch = adj->crtc_htotal - adj->crtc_hsync_end; | |
78 | vm.hsync_len = adj->crtc_hsync_end - adj->crtc_hsync_start; | |
79 | ||
80 | regmap_write(regmap, ATMEL_HLCDC_CFG(1), | |
81 | (vm.hsync_len - 1) | ((vm.vsync_len - 1) << 16)); | |
82 | ||
83 | regmap_write(regmap, ATMEL_HLCDC_CFG(2), | |
84 | (vm.vfront_porch - 1) | (vm.vback_porch << 16)); | |
85 | ||
86 | regmap_write(regmap, ATMEL_HLCDC_CFG(3), | |
87 | (vm.hfront_porch - 1) | ((vm.hback_porch - 1) << 16)); | |
88 | ||
89 | regmap_write(regmap, ATMEL_HLCDC_CFG(4), | |
90 | (adj->crtc_hdisplay - 1) | | |
91 | ((adj->crtc_vdisplay - 1) << 16)); | |
92 | ||
319711f9 | 93 | cfg = ATMEL_HLCDC_CLKSEL; |
1a396789 | 94 | |
319711f9 | 95 | prate = 2 * clk_get_rate(crtc->dc->hlcdc->sys_clk); |
2389fc13 | 96 | mode_rate = adj->crtc_clock * 1000; |
1a396789 BB |
97 | |
98 | div = DIV_ROUND_UP(prate, mode_rate); | |
319711f9 | 99 | if (div < 2) { |
1a396789 | 100 | div = 2; |
319711f9 PR |
101 | } else if (ATMEL_HLCDC_CLKDIV(div) & ~ATMEL_HLCDC_CLKDIV_MASK) { |
102 | /* The divider ended up too big, try a lower base rate. */ | |
103 | cfg &= ~ATMEL_HLCDC_CLKSEL; | |
104 | prate /= 2; | |
105 | div = DIV_ROUND_UP(prate, mode_rate); | |
106 | if (ATMEL_HLCDC_CLKDIV(div) & ~ATMEL_HLCDC_CLKDIV_MASK) | |
107 | div = ATMEL_HLCDC_CLKDIV_MASK; | |
9946a3a9 PR |
108 | } else { |
109 | int div_low = prate / mode_rate; | |
110 | ||
111 | if (div_low >= 2 && | |
112 | ((prate / div_low - mode_rate) < | |
113 | 10 * (mode_rate - prate / div))) | |
114 | /* | |
115 | * At least 10 times better when using a higher | |
116 | * frequency than requested, instead of a lower. | |
117 | * So, go with that. | |
118 | */ | |
119 | div = div_low; | |
319711f9 | 120 | } |
1a396789 BB |
121 | |
122 | cfg |= ATMEL_HLCDC_CLKDIV(div); | |
123 | ||
124 | regmap_update_bits(regmap, ATMEL_HLCDC_CFG(0), | |
125 | ATMEL_HLCDC_CLKSEL | ATMEL_HLCDC_CLKDIV_MASK | | |
126 | ATMEL_HLCDC_CLKPOL, cfg); | |
127 | ||
128 | cfg = 0; | |
129 | ||
2389fc13 | 130 | if (adj->flags & DRM_MODE_FLAG_NVSYNC) |
1a396789 BB |
131 | cfg |= ATMEL_HLCDC_VSPOL; |
132 | ||
2389fc13 | 133 | if (adj->flags & DRM_MODE_FLAG_NHSYNC) |
1a396789 BB |
134 | cfg |= ATMEL_HLCDC_HSPOL; |
135 | ||
aca63b76 BB |
136 | state = drm_crtc_state_to_atmel_hlcdc_crtc_state(c->state); |
137 | cfg |= state->output_mode << 8; | |
138 | ||
1a396789 BB |
139 | regmap_update_bits(regmap, ATMEL_HLCDC_CFG(5), |
140 | ATMEL_HLCDC_HSPOL | ATMEL_HLCDC_VSPOL | | |
141 | ATMEL_HLCDC_VSPDLYS | ATMEL_HLCDC_VSPDLYE | | |
142 | ATMEL_HLCDC_DISPPOL | ATMEL_HLCDC_DISPDLY | | |
143 | ATMEL_HLCDC_VSPSU | ATMEL_HLCDC_VSPHO | | |
aca63b76 | 144 | ATMEL_HLCDC_GUARDTIME_MASK | ATMEL_HLCDC_MODE_MASK, |
1a396789 | 145 | cfg); |
1a396789 BB |
146 | } |
147 | ||
a57bf53e JA |
148 | static enum drm_mode_status |
149 | atmel_hlcdc_crtc_mode_valid(struct drm_crtc *c, | |
150 | const struct drm_display_mode *mode) | |
5ac44c8b BB |
151 | { |
152 | struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); | |
153 | ||
a57bf53e | 154 | return atmel_hlcdc_dc_mode_valid(crtc->dc, mode); |
5ac44c8b BB |
155 | } |
156 | ||
64581714 LP |
157 | static void atmel_hlcdc_crtc_atomic_disable(struct drm_crtc *c, |
158 | struct drm_crtc_state *old_state) | |
1a396789 | 159 | { |
2389fc13 BB |
160 | struct drm_device *dev = c->dev; |
161 | struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); | |
162 | struct regmap *regmap = crtc->dc->hlcdc->regmap; | |
163 | unsigned int status; | |
164 | ||
2389fc13 BB |
165 | drm_crtc_vblank_off(c); |
166 | ||
167 | pm_runtime_get_sync(dev->dev); | |
168 | ||
169 | regmap_write(regmap, ATMEL_HLCDC_DIS, ATMEL_HLCDC_DISP); | |
170 | while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && | |
171 | (status & ATMEL_HLCDC_DISP)) | |
172 | cpu_relax(); | |
173 | ||
174 | regmap_write(regmap, ATMEL_HLCDC_DIS, ATMEL_HLCDC_SYNC); | |
175 | while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && | |
176 | (status & ATMEL_HLCDC_SYNC)) | |
177 | cpu_relax(); | |
178 | ||
179 | regmap_write(regmap, ATMEL_HLCDC_DIS, ATMEL_HLCDC_PIXEL_CLK); | |
180 | while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && | |
181 | (status & ATMEL_HLCDC_PIXEL_CLK)) | |
182 | cpu_relax(); | |
183 | ||
184 | clk_disable_unprepare(crtc->dc->hlcdc->sys_clk); | |
16e6004e | 185 | pinctrl_pm_select_sleep_state(dev->dev); |
2389fc13 BB |
186 | |
187 | pm_runtime_allow(dev->dev); | |
188 | ||
189 | pm_runtime_put_sync(dev->dev); | |
1a396789 BB |
190 | } |
191 | ||
0b20a0f8 LP |
192 | static void atmel_hlcdc_crtc_atomic_enable(struct drm_crtc *c, |
193 | struct drm_crtc_state *old_state) | |
1a396789 | 194 | { |
2389fc13 BB |
195 | struct drm_device *dev = c->dev; |
196 | struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); | |
197 | struct regmap *regmap = crtc->dc->hlcdc->regmap; | |
198 | unsigned int status; | |
199 | ||
2389fc13 BB |
200 | pm_runtime_get_sync(dev->dev); |
201 | ||
202 | pm_runtime_forbid(dev->dev); | |
203 | ||
16e6004e | 204 | pinctrl_pm_select_default_state(dev->dev); |
2389fc13 BB |
205 | clk_prepare_enable(crtc->dc->hlcdc->sys_clk); |
206 | ||
207 | regmap_write(regmap, ATMEL_HLCDC_EN, ATMEL_HLCDC_PIXEL_CLK); | |
208 | while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && | |
209 | !(status & ATMEL_HLCDC_PIXEL_CLK)) | |
210 | cpu_relax(); | |
211 | ||
212 | ||
213 | regmap_write(regmap, ATMEL_HLCDC_EN, ATMEL_HLCDC_SYNC); | |
214 | while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && | |
215 | !(status & ATMEL_HLCDC_SYNC)) | |
216 | cpu_relax(); | |
217 | ||
218 | regmap_write(regmap, ATMEL_HLCDC_EN, ATMEL_HLCDC_DISP); | |
219 | while (!regmap_read(regmap, ATMEL_HLCDC_SR, &status) && | |
220 | !(status & ATMEL_HLCDC_DISP)) | |
221 | cpu_relax(); | |
222 | ||
223 | pm_runtime_put_sync(dev->dev); | |
224 | ||
225 | drm_crtc_vblank_on(c); | |
f026eb6e SR |
226 | } |
227 | ||
aca63b76 BB |
228 | #define ATMEL_HLCDC_RGB444_OUTPUT BIT(0) |
229 | #define ATMEL_HLCDC_RGB565_OUTPUT BIT(1) | |
230 | #define ATMEL_HLCDC_RGB666_OUTPUT BIT(2) | |
231 | #define ATMEL_HLCDC_RGB888_OUTPUT BIT(3) | |
232 | #define ATMEL_HLCDC_OUTPUT_MODE_MASK GENMASK(3, 0) | |
233 | ||
b6e075c3 PR |
234 | static int atmel_hlcdc_connector_output_mode(struct drm_connector_state *state) |
235 | { | |
236 | struct drm_connector *connector = state->connector; | |
237 | struct drm_display_info *info = &connector->display_info; | |
238 | struct drm_encoder *encoder; | |
239 | unsigned int supported_fmts = 0; | |
240 | int j; | |
241 | ||
242 | encoder = state->best_encoder; | |
243 | if (!encoder) | |
244 | encoder = connector->encoder; | |
245 | ||
246 | switch (atmel_hlcdc_encoder_get_bus_fmt(encoder)) { | |
247 | case 0: | |
248 | break; | |
249 | case MEDIA_BUS_FMT_RGB444_1X12: | |
250 | return ATMEL_HLCDC_RGB444_OUTPUT; | |
251 | case MEDIA_BUS_FMT_RGB565_1X16: | |
252 | return ATMEL_HLCDC_RGB565_OUTPUT; | |
253 | case MEDIA_BUS_FMT_RGB666_1X18: | |
254 | return ATMEL_HLCDC_RGB666_OUTPUT; | |
255 | case MEDIA_BUS_FMT_RGB888_1X24: | |
256 | return ATMEL_HLCDC_RGB888_OUTPUT; | |
257 | default: | |
258 | return -EINVAL; | |
259 | } | |
260 | ||
261 | for (j = 0; j < info->num_bus_formats; j++) { | |
262 | switch (info->bus_formats[j]) { | |
263 | case MEDIA_BUS_FMT_RGB444_1X12: | |
264 | supported_fmts |= ATMEL_HLCDC_RGB444_OUTPUT; | |
265 | break; | |
266 | case MEDIA_BUS_FMT_RGB565_1X16: | |
267 | supported_fmts |= ATMEL_HLCDC_RGB565_OUTPUT; | |
268 | break; | |
269 | case MEDIA_BUS_FMT_RGB666_1X18: | |
270 | supported_fmts |= ATMEL_HLCDC_RGB666_OUTPUT; | |
271 | break; | |
272 | case MEDIA_BUS_FMT_RGB888_1X24: | |
273 | supported_fmts |= ATMEL_HLCDC_RGB888_OUTPUT; | |
274 | break; | |
275 | default: | |
276 | break; | |
277 | } | |
278 | } | |
279 | ||
280 | return supported_fmts; | |
281 | } | |
282 | ||
aca63b76 BB |
283 | static int atmel_hlcdc_crtc_select_output_mode(struct drm_crtc_state *state) |
284 | { | |
285 | unsigned int output_fmts = ATMEL_HLCDC_OUTPUT_MODE_MASK; | |
286 | struct atmel_hlcdc_crtc_state *hstate; | |
287 | struct drm_connector_state *cstate; | |
288 | struct drm_connector *connector; | |
289 | struct atmel_hlcdc_crtc *crtc; | |
290 | int i; | |
291 | ||
292 | crtc = drm_crtc_to_atmel_hlcdc_crtc(state->crtc); | |
293 | ||
d57da16f | 294 | for_each_new_connector_in_state(state->state, connector, cstate, i) { |
aca63b76 | 295 | unsigned int supported_fmts = 0; |
aca63b76 BB |
296 | |
297 | if (!cstate->crtc) | |
298 | continue; | |
299 | ||
b6e075c3 | 300 | supported_fmts = atmel_hlcdc_connector_output_mode(cstate); |
aca63b76 BB |
301 | |
302 | if (crtc->dc->desc->conflicting_output_formats) | |
303 | output_fmts &= supported_fmts; | |
304 | else | |
305 | output_fmts |= supported_fmts; | |
306 | } | |
307 | ||
308 | if (!output_fmts) | |
309 | return -EINVAL; | |
310 | ||
311 | hstate = drm_crtc_state_to_atmel_hlcdc_crtc_state(state); | |
312 | hstate->output_mode = fls(output_fmts) - 1; | |
313 | ||
314 | return 0; | |
315 | } | |
316 | ||
2389fc13 BB |
317 | static int atmel_hlcdc_crtc_atomic_check(struct drm_crtc *c, |
318 | struct drm_crtc_state *s) | |
1a396789 | 319 | { |
aca63b76 | 320 | int ret; |
2389fc13 | 321 | |
aca63b76 BB |
322 | ret = atmel_hlcdc_crtc_select_output_mode(s); |
323 | if (ret) | |
324 | return ret; | |
325 | ||
ebab87ab BB |
326 | ret = atmel_hlcdc_plane_prepare_disc_area(s); |
327 | if (ret) | |
328 | return ret; | |
329 | ||
330 | return atmel_hlcdc_plane_prepare_ahb_routing(s); | |
1a396789 BB |
331 | } |
332 | ||
613d2b27 ML |
333 | static void atmel_hlcdc_crtc_atomic_begin(struct drm_crtc *c, |
334 | struct drm_crtc_state *old_s) | |
1a396789 | 335 | { |
2389fc13 | 336 | struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); |
1a396789 | 337 | |
2389fc13 BB |
338 | if (c->state->event) { |
339 | c->state->event->pipe = drm_crtc_index(c); | |
1a396789 | 340 | |
2389fc13 | 341 | WARN_ON(drm_crtc_vblank_get(c) != 0); |
1a396789 | 342 | |
2389fc13 BB |
343 | crtc->event = c->state->event; |
344 | c->state->event = NULL; | |
1a396789 BB |
345 | } |
346 | } | |
347 | ||
613d2b27 ML |
348 | static void atmel_hlcdc_crtc_atomic_flush(struct drm_crtc *crtc, |
349 | struct drm_crtc_state *old_s) | |
2389fc13 BB |
350 | { |
351 | /* TODO: write common plane control register if available */ | |
352 | } | |
353 | ||
1a396789 | 354 | static const struct drm_crtc_helper_funcs lcdc_crtc_helper_funcs = { |
a57bf53e | 355 | .mode_valid = atmel_hlcdc_crtc_mode_valid, |
2389fc13 | 356 | .mode_set_nofb = atmel_hlcdc_crtc_mode_set_nofb, |
2389fc13 BB |
357 | .atomic_check = atmel_hlcdc_crtc_atomic_check, |
358 | .atomic_begin = atmel_hlcdc_crtc_atomic_begin, | |
359 | .atomic_flush = atmel_hlcdc_crtc_atomic_flush, | |
0b20a0f8 | 360 | .atomic_enable = atmel_hlcdc_crtc_atomic_enable, |
64581714 | 361 | .atomic_disable = atmel_hlcdc_crtc_atomic_disable, |
1a396789 BB |
362 | }; |
363 | ||
364 | static void atmel_hlcdc_crtc_destroy(struct drm_crtc *c) | |
365 | { | |
366 | struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); | |
367 | ||
368 | drm_crtc_cleanup(c); | |
369 | kfree(crtc); | |
370 | } | |
371 | ||
1a396789 BB |
372 | static void atmel_hlcdc_crtc_finish_page_flip(struct atmel_hlcdc_crtc *crtc) |
373 | { | |
374 | struct drm_device *dev = crtc->base.dev; | |
375 | unsigned long flags; | |
376 | ||
377 | spin_lock_irqsave(&dev->event_lock, flags); | |
378 | if (crtc->event) { | |
81767317 | 379 | drm_crtc_send_vblank_event(&crtc->base, crtc->event); |
23a25ed3 | 380 | drm_crtc_vblank_put(&crtc->base); |
1a396789 BB |
381 | crtc->event = NULL; |
382 | } | |
383 | spin_unlock_irqrestore(&dev->event_lock, flags); | |
384 | } | |
385 | ||
386 | void atmel_hlcdc_crtc_irq(struct drm_crtc *c) | |
387 | { | |
548ebe1e | 388 | drm_crtc_handle_vblank(c); |
1a396789 BB |
389 | atmel_hlcdc_crtc_finish_page_flip(drm_crtc_to_atmel_hlcdc_crtc(c)); |
390 | } | |
391 | ||
1ba7db07 | 392 | static void atmel_hlcdc_crtc_reset(struct drm_crtc *crtc) |
aca63b76 BB |
393 | { |
394 | struct atmel_hlcdc_crtc_state *state; | |
395 | ||
aca63b76 | 396 | if (crtc->state) { |
c2e4c994 | 397 | __drm_atomic_helper_crtc_destroy_state(crtc->state); |
aca63b76 BB |
398 | state = drm_crtc_state_to_atmel_hlcdc_crtc_state(crtc->state); |
399 | kfree(state); | |
c2e4c994 | 400 | crtc->state = NULL; |
aca63b76 BB |
401 | } |
402 | ||
403 | state = kzalloc(sizeof(*state), GFP_KERNEL); | |
404 | if (state) { | |
405 | crtc->state = &state->base; | |
406 | crtc->state->crtc = crtc; | |
407 | } | |
408 | } | |
409 | ||
410 | static struct drm_crtc_state * | |
411 | atmel_hlcdc_crtc_duplicate_state(struct drm_crtc *crtc) | |
412 | { | |
413 | struct atmel_hlcdc_crtc_state *state, *cur; | |
414 | ||
415 | if (WARN_ON(!crtc->state)) | |
416 | return NULL; | |
417 | ||
418 | state = kmalloc(sizeof(*state), GFP_KERNEL); | |
58a2ab3a DC |
419 | if (!state) |
420 | return NULL; | |
421 | __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base); | |
aca63b76 BB |
422 | |
423 | cur = drm_crtc_state_to_atmel_hlcdc_crtc_state(crtc->state); | |
424 | state->output_mode = cur->output_mode; | |
425 | ||
426 | return &state->base; | |
427 | } | |
428 | ||
429 | static void atmel_hlcdc_crtc_destroy_state(struct drm_crtc *crtc, | |
430 | struct drm_crtc_state *s) | |
431 | { | |
432 | struct atmel_hlcdc_crtc_state *state; | |
433 | ||
434 | state = drm_crtc_state_to_atmel_hlcdc_crtc_state(s); | |
ec2dc6a0 | 435 | __drm_atomic_helper_crtc_destroy_state(s); |
aca63b76 BB |
436 | kfree(state); |
437 | } | |
438 | ||
82308e27 SG |
439 | static int atmel_hlcdc_crtc_enable_vblank(struct drm_crtc *c) |
440 | { | |
441 | struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); | |
442 | struct regmap *regmap = crtc->dc->hlcdc->regmap; | |
443 | ||
444 | /* Enable SOF (Start Of Frame) interrupt for vblank counting */ | |
445 | regmap_write(regmap, ATMEL_HLCDC_IER, ATMEL_HLCDC_SOF); | |
446 | ||
447 | return 0; | |
448 | } | |
449 | ||
450 | static void atmel_hlcdc_crtc_disable_vblank(struct drm_crtc *c) | |
451 | { | |
452 | struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); | |
453 | struct regmap *regmap = crtc->dc->hlcdc->regmap; | |
454 | ||
455 | regmap_write(regmap, ATMEL_HLCDC_IDR, ATMEL_HLCDC_SOF); | |
456 | } | |
457 | ||
1a396789 | 458 | static const struct drm_crtc_funcs atmel_hlcdc_crtc_funcs = { |
2389fc13 BB |
459 | .page_flip = drm_atomic_helper_page_flip, |
460 | .set_config = drm_atomic_helper_set_config, | |
1a396789 | 461 | .destroy = atmel_hlcdc_crtc_destroy, |
aca63b76 BB |
462 | .reset = atmel_hlcdc_crtc_reset, |
463 | .atomic_duplicate_state = atmel_hlcdc_crtc_duplicate_state, | |
464 | .atomic_destroy_state = atmel_hlcdc_crtc_destroy_state, | |
82308e27 SG |
465 | .enable_vblank = atmel_hlcdc_crtc_enable_vblank, |
466 | .disable_vblank = atmel_hlcdc_crtc_disable_vblank, | |
364a7bf5 | 467 | .gamma_set = drm_atomic_helper_legacy_gamma_set, |
1a396789 BB |
468 | }; |
469 | ||
470 | int atmel_hlcdc_crtc_create(struct drm_device *dev) | |
471 | { | |
9a45d33c | 472 | struct atmel_hlcdc_plane *primary = NULL, *cursor = NULL; |
1a396789 | 473 | struct atmel_hlcdc_dc *dc = dev->dev_private; |
1a396789 BB |
474 | struct atmel_hlcdc_crtc *crtc; |
475 | int ret; | |
476 | int i; | |
477 | ||
478 | crtc = kzalloc(sizeof(*crtc), GFP_KERNEL); | |
479 | if (!crtc) | |
480 | return -ENOMEM; | |
481 | ||
1a396789 BB |
482 | crtc->dc = dc; |
483 | ||
9a45d33c BB |
484 | for (i = 0; i < ATMEL_HLCDC_MAX_LAYERS; i++) { |
485 | if (!dc->layers[i]) | |
486 | continue; | |
487 | ||
488 | switch (dc->layers[i]->desc->type) { | |
489 | case ATMEL_HLCDC_BASE_LAYER: | |
490 | primary = atmel_hlcdc_layer_to_plane(dc->layers[i]); | |
491 | break; | |
492 | ||
493 | case ATMEL_HLCDC_CURSOR_LAYER: | |
494 | cursor = atmel_hlcdc_layer_to_plane(dc->layers[i]); | |
495 | break; | |
496 | ||
497 | default: | |
498 | break; | |
499 | } | |
500 | } | |
501 | ||
502 | ret = drm_crtc_init_with_planes(dev, &crtc->base, &primary->base, | |
503 | &cursor->base, &atmel_hlcdc_crtc_funcs, | |
504 | NULL); | |
1a396789 BB |
505 | if (ret < 0) |
506 | goto fail; | |
507 | ||
508 | crtc->id = drm_crtc_index(&crtc->base); | |
509 | ||
9a45d33c BB |
510 | for (i = 0; i < ATMEL_HLCDC_MAX_LAYERS; i++) { |
511 | struct atmel_hlcdc_plane *overlay; | |
1a396789 | 512 | |
9a45d33c BB |
513 | if (dc->layers[i] && |
514 | dc->layers[i]->desc->type == ATMEL_HLCDC_OVERLAY_LAYER) { | |
515 | overlay = atmel_hlcdc_layer_to_plane(dc->layers[i]); | |
516 | overlay->base.possible_crtcs = 1 << crtc->id; | |
517 | } | |
518 | } | |
1a396789 BB |
519 | |
520 | drm_crtc_helper_add(&crtc->base, &lcdc_crtc_helper_funcs); | |
8c4b4b0d | 521 | drm_crtc_vblank_reset(&crtc->base); |
1a396789 | 522 | |
364a7bf5 PR |
523 | drm_mode_crtc_set_gamma_size(&crtc->base, ATMEL_HLCDC_CLUT_SIZE); |
524 | drm_crtc_enable_color_mgmt(&crtc->base, 0, false, | |
525 | ATMEL_HLCDC_CLUT_SIZE); | |
526 | ||
1a396789 BB |
527 | dc->crtc = &crtc->base; |
528 | ||
529 | return 0; | |
530 | ||
531 | fail: | |
532 | atmel_hlcdc_crtc_destroy(&crtc->base); | |
533 | return ret; | |
534 | } |