1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * shmob_drm_crtc.c -- SH Mobile DRM CRTCs |
4 | * |
5 | * Copyright (C) 2012 Renesas Electronics Corporation |
6 | * |
7 | * Laurent Pinchart (laurent.pinchart@ideasonboard.com) |
8 | */ |
9 | |
10 | #include <linux/clk.h> |
11 | #include <linux/media-bus-format.h> |
12 | #include <linux/of.h> |
13 | #include <linux/of_graph.h> |
14 | #include <linux/pm_runtime.h> |
15 | |
16 | #include <drm/drm_atomic.h> |
17 | #include <drm/drm_atomic_helper.h> |
18 | #include <drm/drm_atomic_state_helper.h> |
19 | #include <drm/drm_atomic_uapi.h> |
20 | #include <drm/drm_bridge.h> |
21 | #include <drm/drm_bridge_connector.h> |
22 | #include <drm/drm_crtc.h> |
23 | #include <drm/drm_crtc_helper.h> |
24 | #include <drm/drm_fb_dma_helper.h> |
25 | #include <drm/drm_fourcc.h> |
26 | #include <drm/drm_framebuffer.h> |
27 | #include <drm/drm_gem_dma_helper.h> |
28 | #include <drm/drm_modeset_helper.h> |
29 | #include <drm/drm_modeset_helper_vtables.h> |
30 | #include <drm/drm_panel.h> |
31 | #include <drm/drm_probe_helper.h> |
32 | #include <drm/drm_simple_kms_helper.h> |
33 | #include <drm/drm_vblank.h> |
34 | |
35 | #include <video/videomode.h> |
36 | |
37 | #include "shmob_drm_crtc.h" |
38 | #include "shmob_drm_drv.h" |
39 | #include "shmob_drm_kms.h" |
40 | #include "shmob_drm_plane.h" |
41 | #include "shmob_drm_regs.h" |
42 | |
43 | /* ----------------------------------------------------------------------------- |
44 | * Page Flip |
45 | */ |
46 | |
47 | void shmob_drm_crtc_finish_page_flip(struct shmob_drm_crtc *scrtc) |
48 | { |
49 | struct drm_pending_vblank_event *event; |
50 | struct drm_device *dev = scrtc->base.dev; |
51 | unsigned long flags; |
52 | |
53 | spin_lock_irqsave(&dev->event_lock, flags); |
54 | event = scrtc->event; |
55 | scrtc->event = NULL; |
56 | if (event) { |
57 | drm_crtc_send_vblank_event(crtc: &scrtc->base, e: event); |
58 | wake_up(&scrtc->flip_wait); |
59 | drm_crtc_vblank_put(crtc: &scrtc->base); |
60 | } |
61 | spin_unlock_irqrestore(lock: &dev->event_lock, flags); |
62 | } |
63 | |
64 | static bool shmob_drm_crtc_page_flip_pending(struct shmob_drm_crtc *scrtc) |
65 | { |
66 | struct drm_device *dev = scrtc->base.dev; |
67 | unsigned long flags; |
68 | bool pending; |
69 | |
70 | spin_lock_irqsave(&dev->event_lock, flags); |
71 | pending = scrtc->event != NULL; |
72 | spin_unlock_irqrestore(lock: &dev->event_lock, flags); |
73 | |
74 | return pending; |
75 | } |
76 | |
77 | static void shmob_drm_crtc_wait_page_flip(struct shmob_drm_crtc *scrtc) |
78 | { |
79 | struct drm_crtc *crtc = &scrtc->base; |
80 | struct shmob_drm_device *sdev = to_shmob_device(dev: crtc->dev); |
81 | |
82 | if (wait_event_timeout(scrtc->flip_wait, |
83 | !shmob_drm_crtc_page_flip_pending(scrtc), |
84 | msecs_to_jiffies(50))) |
85 | return; |
86 | |
87 | dev_warn(sdev->dev, "page flip timeout\n" ); |
88 | |
89 | shmob_drm_crtc_finish_page_flip(scrtc); |
90 | } |
91 | |
92 | /* ----------------------------------------------------------------------------- |
93 | * CRTC |
94 | */ |
95 | |
96 | static const struct { |
97 | u32 fmt; |
98 | u32 ldmt1r; |
99 | } shmob_drm_bus_fmts[] = { |
100 | { MEDIA_BUS_FMT_RGB888_3X8, LDMT1R_MIFTYP_RGB8 }, |
101 | { MEDIA_BUS_FMT_RGB666_2X9_BE, LDMT1R_MIFTYP_RGB9 }, |
102 | { MEDIA_BUS_FMT_RGB888_2X12_BE, LDMT1R_MIFTYP_RGB12A }, |
103 | { MEDIA_BUS_FMT_RGB444_1X12, LDMT1R_MIFTYP_RGB12B }, |
104 | { MEDIA_BUS_FMT_RGB565_1X16, LDMT1R_MIFTYP_RGB16 }, |
105 | { MEDIA_BUS_FMT_RGB666_1X18, LDMT1R_MIFTYP_RGB18 }, |
106 | { MEDIA_BUS_FMT_RGB888_1X24, LDMT1R_MIFTYP_RGB24 }, |
107 | { MEDIA_BUS_FMT_UYVY8_1X16, LDMT1R_MIFTYP_YCBCR }, |
108 | }; |
109 | |
110 | static void shmob_drm_crtc_setup_geometry(struct shmob_drm_crtc *scrtc) |
111 | { |
112 | struct drm_crtc *crtc = &scrtc->base; |
113 | struct shmob_drm_device *sdev = to_shmob_device(dev: crtc->dev); |
114 | const struct drm_display_info *info = &sdev->connector->display_info; |
115 | const struct drm_display_mode *mode = &crtc->mode; |
116 | unsigned int i; |
117 | u32 value; |
118 | |
119 | if (!info->num_bus_formats || !info->bus_formats) { |
120 | dev_warn(sdev->dev, "No bus format reported, using RGB888\n" ); |
121 | value = LDMT1R_MIFTYP_RGB24; |
122 | } else { |
123 | for (i = 0; i < ARRAY_SIZE(shmob_drm_bus_fmts); i++) { |
124 | if (shmob_drm_bus_fmts[i].fmt == info->bus_formats[0]) |
125 | break; |
126 | } |
127 | if (i < ARRAY_SIZE(shmob_drm_bus_fmts)) { |
128 | value = shmob_drm_bus_fmts[i].ldmt1r; |
129 | } else { |
130 | dev_warn(sdev->dev, |
131 | "unsupported bus format 0x%x, using RGB888\n" , |
132 | info->bus_formats[0]); |
133 | value = LDMT1R_MIFTYP_RGB24; |
134 | } |
135 | } |
136 | |
137 | if (info->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE) |
138 | value |= LDMT1R_DWPOL; |
139 | if (info->bus_flags & DRM_BUS_FLAG_DE_LOW) |
140 | value |= LDMT1R_DIPOL; |
141 | if (mode->flags & DRM_MODE_FLAG_NVSYNC) |
142 | value |= LDMT1R_VPOL; |
143 | if (mode->flags & DRM_MODE_FLAG_NHSYNC) |
144 | value |= LDMT1R_HPOL; |
145 | lcdc_write(sdev, LDMT1R, data: value); |
146 | |
147 | value = ((mode->hdisplay / 8) << 16) /* HDCN */ |
148 | | (mode->htotal / 8); /* HTCN */ |
149 | lcdc_write(sdev, LDHCNR, data: value); |
150 | |
151 | value = (((mode->hsync_end - mode->hsync_start) / 8) << 16) /* HSYNW */ |
152 | | (mode->hsync_start / 8); /* HSYNP */ |
153 | lcdc_write(sdev, LDHSYNR, data: value); |
154 | |
155 | value = ((mode->hdisplay & 7) << 24) | ((mode->htotal & 7) << 16) |
156 | | (((mode->hsync_end - mode->hsync_start) & 7) << 8) |
157 | | (mode->hsync_start & 7); |
158 | lcdc_write(sdev, LDHAJR, data: value); |
159 | |
160 | value = ((mode->vdisplay) << 16) /* VDLN */ |
161 | | mode->vtotal; /* VTLN */ |
162 | lcdc_write(sdev, LDVLNR, data: value); |
163 | |
164 | value = ((mode->vsync_end - mode->vsync_start) << 16) /* VSYNW */ |
165 | | mode->vsync_start; /* VSYNP */ |
166 | lcdc_write(sdev, LDVSYNR, data: value); |
167 | } |
168 | |
169 | static void shmob_drm_crtc_start_stop(struct shmob_drm_crtc *scrtc, bool start) |
170 | { |
171 | struct shmob_drm_device *sdev = to_shmob_device(dev: scrtc->base.dev); |
172 | u32 value; |
173 | |
174 | value = lcdc_read(sdev, LDCNT2R); |
175 | if (start) |
176 | lcdc_write(sdev, LDCNT2R, data: value | LDCNT2R_DO); |
177 | else |
178 | lcdc_write(sdev, LDCNT2R, data: value & ~LDCNT2R_DO); |
179 | |
180 | /* Wait until power is applied/stopped. */ |
181 | while (1) { |
182 | value = lcdc_read(sdev, LDPMR) & LDPMR_LPS; |
183 | if ((start && value) || (!start && !value)) |
184 | break; |
185 | |
186 | cpu_relax(); |
187 | } |
188 | |
189 | if (!start) { |
190 | /* Stop the dot clock. */ |
191 | lcdc_write(sdev, LDDCKSTPR, LDDCKSTPR_DCKSTP); |
192 | } |
193 | } |
194 | |
195 | static inline struct shmob_drm_crtc *to_shmob_crtc(struct drm_crtc *crtc) |
196 | { |
197 | return container_of(crtc, struct shmob_drm_crtc, base); |
198 | } |
199 | |
200 | static void shmob_drm_crtc_atomic_enable(struct drm_crtc *crtc, |
201 | struct drm_atomic_state *state) |
202 | { |
203 | struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc); |
204 | struct shmob_drm_device *sdev = to_shmob_device(dev: crtc->dev); |
205 | unsigned int clk_div = sdev->config.clk_div; |
206 | struct device *dev = sdev->dev; |
207 | u32 value; |
208 | int ret; |
209 | |
210 | ret = pm_runtime_resume_and_get(dev); |
211 | if (ret) |
212 | return; |
213 | |
214 | /* Reset and enable the LCDC. */ |
215 | lcdc_write(sdev, LDCNT2R, data: lcdc_read(sdev, LDCNT2R) | LDCNT2R_BR); |
216 | lcdc_wait_bit(sdev, LDCNT2R, LDCNT2R_BR, until: 0); |
217 | lcdc_write(sdev, LDCNT2R, LDCNT2R_ME); |
218 | |
219 | /* Stop the LCDC first and disable all interrupts. */ |
220 | shmob_drm_crtc_start_stop(scrtc, start: false); |
221 | lcdc_write(sdev, LDINTR, data: 0); |
222 | |
223 | /* Configure power supply, dot clocks and start them. */ |
224 | lcdc_write(sdev, LDPMR, data: 0); |
225 | |
226 | value = sdev->lddckr; |
227 | if (clk_div) { |
228 | /* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider |
229 | * denominator. |
230 | */ |
231 | lcdc_write(sdev, LDDCKPAT1R, data: 0); |
232 | lcdc_write(sdev, LDDCKPAT2R, data: (1 << (clk_div / 2)) - 1); |
233 | |
234 | if (clk_div == 1) |
235 | value |= LDDCKR_MOSEL; |
236 | else |
237 | value |= clk_div; |
238 | } |
239 | |
240 | lcdc_write(sdev, LDDCKR, data: value); |
241 | lcdc_write(sdev, LDDCKSTPR, data: 0); |
242 | lcdc_wait_bit(sdev, LDDCKSTPR, mask: ~0, until: 0); |
243 | |
244 | /* Setup geometry, format, frame buffer memory and operation mode. */ |
245 | shmob_drm_crtc_setup_geometry(scrtc); |
246 | |
247 | lcdc_write(sdev, LDSM1R, data: 0); |
248 | |
249 | /* Enable the display output. */ |
250 | lcdc_write(sdev, LDCNT1R, LDCNT1R_DE); |
251 | |
252 | shmob_drm_crtc_start_stop(scrtc, start: true); |
253 | |
254 | /* Turn vertical blank interrupt reporting back on. */ |
255 | drm_crtc_vblank_on(crtc); |
256 | } |
257 | |
258 | static void shmob_drm_crtc_atomic_disable(struct drm_crtc *crtc, |
259 | struct drm_atomic_state *state) |
260 | { |
261 | struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc); |
262 | struct shmob_drm_device *sdev = to_shmob_device(dev: crtc->dev); |
263 | |
264 | /* |
265 | * Disable vertical blank interrupt reporting. We first need to wait |
266 | * for page flip completion before stopping the CRTC as userspace |
267 | * expects page flips to eventually complete. |
268 | */ |
269 | shmob_drm_crtc_wait_page_flip(scrtc); |
270 | drm_crtc_vblank_off(crtc); |
271 | |
272 | /* Stop the LCDC. */ |
273 | shmob_drm_crtc_start_stop(scrtc, start: false); |
274 | |
275 | /* Disable the display output. */ |
276 | lcdc_write(sdev, LDCNT1R, data: 0); |
277 | |
278 | pm_runtime_put(dev: sdev->dev); |
279 | } |
280 | |
281 | static void shmob_drm_crtc_atomic_flush(struct drm_crtc *crtc, |
282 | struct drm_atomic_state *state) |
283 | { |
284 | struct drm_pending_vblank_event *event; |
285 | struct drm_device *dev = crtc->dev; |
286 | unsigned long flags; |
287 | |
288 | if (crtc->state->event) { |
289 | spin_lock_irqsave(&dev->event_lock, flags); |
290 | event = crtc->state->event; |
291 | crtc->state->event = NULL; |
292 | drm_crtc_send_vblank_event(crtc, e: event); |
293 | spin_unlock_irqrestore(lock: &dev->event_lock, flags); |
294 | } |
295 | } |
296 | |
297 | static const struct drm_crtc_helper_funcs crtc_helper_funcs = { |
298 | .atomic_flush = shmob_drm_crtc_atomic_flush, |
299 | .atomic_enable = shmob_drm_crtc_atomic_enable, |
300 | .atomic_disable = shmob_drm_crtc_atomic_disable, |
301 | }; |
302 | |
303 | static int shmob_drm_crtc_page_flip(struct drm_crtc *crtc, |
304 | struct drm_framebuffer *fb, |
305 | struct drm_pending_vblank_event *event, |
306 | uint32_t page_flip_flags, |
307 | struct drm_modeset_acquire_ctx *ctx) |
308 | { |
309 | struct shmob_drm_crtc *scrtc = to_shmob_crtc(crtc); |
310 | struct drm_device *dev = scrtc->base.dev; |
311 | unsigned long flags; |
312 | |
313 | spin_lock_irqsave(&dev->event_lock, flags); |
314 | if (scrtc->event != NULL) { |
315 | spin_unlock_irqrestore(lock: &dev->event_lock, flags); |
316 | return -EBUSY; |
317 | } |
318 | spin_unlock_irqrestore(lock: &dev->event_lock, flags); |
319 | |
320 | drm_atomic_set_fb_for_plane(plane_state: crtc->primary->state, fb); |
321 | |
322 | if (event) { |
323 | event->pipe = 0; |
324 | drm_crtc_vblank_get(crtc: &scrtc->base); |
325 | spin_lock_irqsave(&dev->event_lock, flags); |
326 | scrtc->event = event; |
327 | spin_unlock_irqrestore(lock: &dev->event_lock, flags); |
328 | } |
329 | |
330 | return 0; |
331 | } |
332 | |
333 | static void shmob_drm_crtc_enable_vblank(struct shmob_drm_device *sdev, |
334 | bool enable) |
335 | { |
336 | unsigned long flags; |
337 | u32 ldintr; |
338 | |
339 | /* Be careful not to acknowledge any pending interrupt. */ |
340 | spin_lock_irqsave(&sdev->irq_lock, flags); |
341 | ldintr = lcdc_read(sdev, LDINTR) | LDINTR_STATUS_MASK; |
342 | if (enable) |
343 | ldintr |= LDINTR_VEE; |
344 | else |
345 | ldintr &= ~LDINTR_VEE; |
346 | lcdc_write(sdev, LDINTR, data: ldintr); |
347 | spin_unlock_irqrestore(lock: &sdev->irq_lock, flags); |
348 | } |
349 | |
350 | static int shmob_drm_enable_vblank(struct drm_crtc *crtc) |
351 | { |
352 | struct shmob_drm_device *sdev = to_shmob_device(dev: crtc->dev); |
353 | |
354 | shmob_drm_crtc_enable_vblank(sdev, enable: true); |
355 | |
356 | return 0; |
357 | } |
358 | |
359 | static void shmob_drm_disable_vblank(struct drm_crtc *crtc) |
360 | { |
361 | struct shmob_drm_device *sdev = to_shmob_device(dev: crtc->dev); |
362 | |
363 | shmob_drm_crtc_enable_vblank(sdev, enable: false); |
364 | } |
365 | |
366 | static const struct drm_crtc_funcs crtc_funcs = { |
367 | .reset = drm_atomic_helper_crtc_reset, |
368 | .destroy = drm_crtc_cleanup, |
369 | .set_config = drm_atomic_helper_set_config, |
370 | .page_flip = shmob_drm_crtc_page_flip, |
371 | .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, |
372 | .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, |
373 | .enable_vblank = shmob_drm_enable_vblank, |
374 | .disable_vblank = shmob_drm_disable_vblank, |
375 | }; |
376 | |
377 | int shmob_drm_crtc_create(struct shmob_drm_device *sdev) |
378 | { |
379 | struct drm_crtc *crtc = &sdev->crtc.base; |
380 | struct drm_plane *primary, *plane; |
381 | unsigned int i; |
382 | int ret; |
383 | |
384 | init_waitqueue_head(&sdev->crtc.flip_wait); |
385 | |
386 | primary = shmob_drm_plane_create(sdev, type: DRM_PLANE_TYPE_PRIMARY, index: 0); |
387 | if (IS_ERR(ptr: primary)) |
388 | return PTR_ERR(ptr: primary); |
389 | |
390 | for (i = 1; i < 5; ++i) { |
391 | plane = shmob_drm_plane_create(sdev, type: DRM_PLANE_TYPE_OVERLAY, index: i); |
392 | if (IS_ERR(ptr: plane)) |
393 | return PTR_ERR(ptr: plane); |
394 | } |
395 | |
396 | ret = drm_crtc_init_with_planes(dev: &sdev->ddev, crtc, primary, NULL, |
397 | funcs: &crtc_funcs, NULL); |
398 | if (ret < 0) |
399 | return ret; |
400 | |
401 | drm_crtc_helper_add(crtc, funcs: &crtc_helper_funcs); |
402 | |
403 | /* Start with vertical blank interrupt reporting disabled. */ |
404 | drm_crtc_vblank_off(crtc); |
405 | |
406 | return 0; |
407 | } |
408 | |
409 | /* ----------------------------------------------------------------------------- |
410 | * Legacy Encoder |
411 | */ |
412 | |
413 | static bool shmob_drm_encoder_mode_fixup(struct drm_encoder *encoder, |
414 | const struct drm_display_mode *mode, |
415 | struct drm_display_mode *adjusted_mode) |
416 | { |
417 | struct drm_device *dev = encoder->dev; |
418 | struct shmob_drm_device *sdev = to_shmob_device(dev); |
419 | struct drm_connector *connector = sdev->connector; |
420 | const struct drm_display_mode *panel_mode; |
421 | |
422 | if (list_empty(head: &connector->modes)) { |
423 | dev_dbg(dev->dev, "mode_fixup: empty modes list\n" ); |
424 | return false; |
425 | } |
426 | |
427 | /* The flat panel mode is fixed, just copy it to the adjusted mode. */ |
428 | panel_mode = list_first_entry(&connector->modes, |
429 | struct drm_display_mode, head); |
430 | drm_mode_copy(dst: adjusted_mode, src: panel_mode); |
431 | |
432 | return true; |
433 | } |
434 | |
435 | static const struct drm_encoder_helper_funcs encoder_helper_funcs = { |
436 | .mode_fixup = shmob_drm_encoder_mode_fixup, |
437 | }; |
438 | |
439 | /* ----------------------------------------------------------------------------- |
440 | * Encoder |
441 | */ |
442 | |
443 | int shmob_drm_encoder_create(struct shmob_drm_device *sdev) |
444 | { |
445 | struct drm_encoder *encoder = &sdev->encoder; |
446 | struct drm_bridge *bridge; |
447 | int ret; |
448 | |
449 | encoder->possible_crtcs = 1; |
450 | |
451 | ret = drm_simple_encoder_init(dev: &sdev->ddev, encoder, |
452 | DRM_MODE_ENCODER_DPI); |
453 | if (ret < 0) |
454 | return ret; |
455 | |
456 | if (sdev->pdata) { |
457 | drm_encoder_helper_add(encoder, funcs: &encoder_helper_funcs); |
458 | return 0; |
459 | } |
460 | |
461 | /* Create a panel bridge */ |
462 | bridge = devm_drm_of_get_bridge(dev: sdev->dev, node: sdev->dev->of_node, port: 0, endpoint: 0); |
463 | if (IS_ERR(ptr: bridge)) |
464 | return PTR_ERR(ptr: bridge); |
465 | |
466 | /* Attach the bridge to the encoder */ |
467 | ret = drm_bridge_attach(encoder, bridge, NULL, |
468 | flags: DRM_BRIDGE_ATTACH_NO_CONNECTOR); |
469 | if (ret) { |
470 | dev_err(sdev->dev, "failed to attach bridge: %pe\n" , |
471 | ERR_PTR(ret)); |
472 | return ret; |
473 | } |
474 | |
475 | return 0; |
476 | } |
477 | |
478 | /* ----------------------------------------------------------------------------- |
479 | * Legacy Connector |
480 | */ |
481 | |
482 | static inline struct shmob_drm_connector *to_shmob_connector(struct drm_connector *connector) |
483 | { |
484 | return container_of(connector, struct shmob_drm_connector, base); |
485 | } |
486 | |
487 | static int shmob_drm_connector_get_modes(struct drm_connector *connector) |
488 | { |
489 | struct shmob_drm_connector *scon = to_shmob_connector(connector); |
490 | struct drm_display_mode *mode; |
491 | |
492 | mode = drm_mode_create(dev: connector->dev); |
493 | if (mode == NULL) |
494 | return 0; |
495 | |
496 | mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER; |
497 | |
498 | drm_display_mode_from_videomode(vm: scon->mode, dmode: mode); |
499 | |
500 | drm_mode_probed_add(connector, mode); |
501 | |
502 | return 1; |
503 | } |
504 | |
505 | static struct drm_encoder * |
506 | shmob_drm_connector_best_encoder(struct drm_connector *connector) |
507 | { |
508 | struct shmob_drm_connector *scon = to_shmob_connector(connector); |
509 | |
510 | return scon->encoder; |
511 | } |
512 | |
513 | static const struct drm_connector_helper_funcs connector_helper_funcs = { |
514 | .get_modes = shmob_drm_connector_get_modes, |
515 | .best_encoder = shmob_drm_connector_best_encoder, |
516 | }; |
517 | |
518 | static void shmob_drm_connector_destroy(struct drm_connector *connector) |
519 | { |
520 | drm_connector_unregister(connector); |
521 | drm_connector_cleanup(connector); |
522 | |
523 | kfree(objp: connector); |
524 | } |
525 | |
526 | static const struct drm_connector_funcs connector_funcs = { |
527 | .reset = drm_atomic_helper_connector_reset, |
528 | .fill_modes = drm_helper_probe_single_connector_modes, |
529 | .destroy = shmob_drm_connector_destroy, |
530 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
531 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
532 | }; |
533 | |
534 | static struct drm_connector * |
535 | shmob_drm_connector_init(struct shmob_drm_device *sdev, |
536 | struct drm_encoder *encoder) |
537 | { |
538 | u32 bus_fmt = sdev->pdata->iface.bus_fmt; |
539 | struct shmob_drm_connector *scon; |
540 | struct drm_connector *connector; |
541 | struct drm_display_info *info; |
542 | unsigned int i; |
543 | int ret; |
544 | |
545 | for (i = 0; i < ARRAY_SIZE(shmob_drm_bus_fmts); i++) { |
546 | if (shmob_drm_bus_fmts[i].fmt == bus_fmt) |
547 | break; |
548 | } |
549 | if (i == ARRAY_SIZE(shmob_drm_bus_fmts)) { |
550 | dev_err(sdev->dev, "unsupported bus format 0x%x\n" , bus_fmt); |
551 | return ERR_PTR(error: -EINVAL); |
552 | } |
553 | |
554 | scon = kzalloc(size: sizeof(*scon), GFP_KERNEL); |
555 | if (!scon) |
556 | return ERR_PTR(error: -ENOMEM); |
557 | |
558 | connector = &scon->base; |
559 | scon->encoder = encoder; |
560 | scon->mode = &sdev->pdata->panel.mode; |
561 | |
562 | info = &connector->display_info; |
563 | info->width_mm = sdev->pdata->panel.width_mm; |
564 | info->height_mm = sdev->pdata->panel.height_mm; |
565 | |
566 | if (scon->mode->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE) |
567 | info->bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE; |
568 | if (scon->mode->flags & DISPLAY_FLAGS_DE_LOW) |
569 | info->bus_flags |= DRM_BUS_FLAG_DE_LOW; |
570 | |
571 | ret = drm_display_info_set_bus_formats(info, formats: &bus_fmt, num_formats: 1); |
572 | if (ret < 0) { |
573 | kfree(objp: scon); |
574 | return ERR_PTR(error: ret); |
575 | } |
576 | |
577 | ret = drm_connector_init(dev: &sdev->ddev, connector, funcs: &connector_funcs, |
578 | DRM_MODE_CONNECTOR_DPI); |
579 | if (ret < 0) { |
580 | kfree(objp: scon); |
581 | return ERR_PTR(error: ret); |
582 | } |
583 | |
584 | drm_connector_helper_add(connector, funcs: &connector_helper_funcs); |
585 | |
586 | return connector; |
587 | } |
588 | |
589 | /* ----------------------------------------------------------------------------- |
590 | * Connector |
591 | */ |
592 | |
593 | int shmob_drm_connector_create(struct shmob_drm_device *sdev, |
594 | struct drm_encoder *encoder) |
595 | { |
596 | struct drm_connector *connector; |
597 | int ret; |
598 | |
599 | if (sdev->pdata) |
600 | connector = shmob_drm_connector_init(sdev, encoder); |
601 | else |
602 | connector = drm_bridge_connector_init(drm: &sdev->ddev, encoder); |
603 | if (IS_ERR(ptr: connector)) { |
604 | dev_err(sdev->dev, "failed to created connector: %pe\n" , |
605 | connector); |
606 | return PTR_ERR(ptr: connector); |
607 | } |
608 | |
609 | ret = drm_connector_attach_encoder(connector, encoder); |
610 | if (ret < 0) |
611 | goto error; |
612 | |
613 | connector->dpms = DRM_MODE_DPMS_OFF; |
614 | |
615 | sdev->connector = connector; |
616 | |
617 | return 0; |
618 | |
619 | error: |
620 | drm_connector_cleanup(connector); |
621 | return ret; |
622 | } |
623 | |