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