1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2012 Red Hat |
4 | * |
5 | * based in parts on udlfb.c: |
6 | * Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it> |
7 | * Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com> |
8 | * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com> |
9 | */ |
10 | |
11 | #include <linux/bitfield.h> |
12 | |
13 | #include <drm/drm_atomic.h> |
14 | #include <drm/drm_atomic_helper.h> |
15 | #include <drm/drm_crtc_helper.h> |
16 | #include <drm/drm_damage_helper.h> |
17 | #include <drm/drm_drv.h> |
18 | #include <drm/drm_edid.h> |
19 | #include <drm/drm_fourcc.h> |
20 | #include <drm/drm_gem_atomic_helper.h> |
21 | #include <drm/drm_gem_framebuffer_helper.h> |
22 | #include <drm/drm_gem_shmem_helper.h> |
23 | #include <drm/drm_modeset_helper_vtables.h> |
24 | #include <drm/drm_plane_helper.h> |
25 | #include <drm/drm_probe_helper.h> |
26 | #include <drm/drm_vblank.h> |
27 | |
28 | #include "udl_drv.h" |
29 | #include "udl_proto.h" |
30 | |
31 | /* |
32 | * All DisplayLink bulk operations start with 0xaf (UDL_MSG_BULK), followed by |
33 | * a specific command code. All operations are written to a command buffer, which |
34 | * the driver sends to the device. |
35 | */ |
36 | static char *udl_set_register(char *buf, u8 reg, u8 val) |
37 | { |
38 | *buf++ = UDL_MSG_BULK; |
39 | *buf++ = UDL_CMD_WRITEREG; |
40 | *buf++ = reg; |
41 | *buf++ = val; |
42 | |
43 | return buf; |
44 | } |
45 | |
46 | static char *udl_vidreg_lock(char *buf) |
47 | { |
48 | return udl_set_register(buf, UDL_REG_VIDREG, UDL_VIDREG_LOCK); |
49 | } |
50 | |
51 | static char *udl_vidreg_unlock(char *buf) |
52 | { |
53 | return udl_set_register(buf, UDL_REG_VIDREG, UDL_VIDREG_UNLOCK); |
54 | } |
55 | |
56 | static char *udl_set_blank_mode(char *buf, u8 mode) |
57 | { |
58 | return udl_set_register(buf, UDL_REG_BLANKMODE, val: mode); |
59 | } |
60 | |
61 | static char *udl_set_color_depth(char *buf, u8 selection) |
62 | { |
63 | return udl_set_register(buf, UDL_REG_COLORDEPTH, val: selection); |
64 | } |
65 | |
66 | static char *udl_set_base16bpp(char *buf, u32 base) |
67 | { |
68 | /* the base pointer is 24 bits wide, 0x20 is hi byte. */ |
69 | u8 reg20 = FIELD_GET(UDL_BASE_ADDR2_MASK, base); |
70 | u8 reg21 = FIELD_GET(UDL_BASE_ADDR1_MASK, base); |
71 | u8 reg22 = FIELD_GET(UDL_BASE_ADDR0_MASK, base); |
72 | |
73 | buf = udl_set_register(buf, UDL_REG_BASE16BPP_ADDR2, val: reg20); |
74 | buf = udl_set_register(buf, UDL_REG_BASE16BPP_ADDR1, val: reg21); |
75 | buf = udl_set_register(buf, UDL_REG_BASE16BPP_ADDR0, val: reg22); |
76 | |
77 | return buf; |
78 | } |
79 | |
80 | /* |
81 | * DisplayLink HW has separate 16bpp and 8bpp framebuffers. |
82 | * In 24bpp modes, the low 323 RGB bits go in the 8bpp framebuffer |
83 | */ |
84 | static char *udl_set_base8bpp(char *buf, u32 base) |
85 | { |
86 | /* the base pointer is 24 bits wide, 0x26 is hi byte. */ |
87 | u8 reg26 = FIELD_GET(UDL_BASE_ADDR2_MASK, base); |
88 | u8 reg27 = FIELD_GET(UDL_BASE_ADDR1_MASK, base); |
89 | u8 reg28 = FIELD_GET(UDL_BASE_ADDR0_MASK, base); |
90 | |
91 | buf = udl_set_register(buf, UDL_REG_BASE8BPP_ADDR2, val: reg26); |
92 | buf = udl_set_register(buf, UDL_REG_BASE8BPP_ADDR1, val: reg27); |
93 | buf = udl_set_register(buf, UDL_REG_BASE8BPP_ADDR0, val: reg28); |
94 | |
95 | return buf; |
96 | } |
97 | |
98 | static char *udl_set_register_16(char *wrptr, u8 reg, u16 value) |
99 | { |
100 | wrptr = udl_set_register(buf: wrptr, reg, val: value >> 8); |
101 | return udl_set_register(buf: wrptr, reg: reg+1, val: value); |
102 | } |
103 | |
104 | /* |
105 | * This is kind of weird because the controller takes some |
106 | * register values in a different byte order than other registers. |
107 | */ |
108 | static char *udl_set_register_16be(char *wrptr, u8 reg, u16 value) |
109 | { |
110 | wrptr = udl_set_register(buf: wrptr, reg, val: value); |
111 | return udl_set_register(buf: wrptr, reg: reg+1, val: value >> 8); |
112 | } |
113 | |
114 | /* |
115 | * LFSR is linear feedback shift register. The reason we have this is |
116 | * because the display controller needs to minimize the clock depth of |
117 | * various counters used in the display path. So this code reverses the |
118 | * provided value into the lfsr16 value by counting backwards to get |
119 | * the value that needs to be set in the hardware comparator to get the |
120 | * same actual count. This makes sense once you read above a couple of |
121 | * times and think about it from a hardware perspective. |
122 | */ |
123 | static u16 udl_lfsr16(u16 actual_count) |
124 | { |
125 | u32 lv = 0xFFFF; /* This is the lfsr value that the hw starts with */ |
126 | |
127 | while (actual_count--) { |
128 | lv = ((lv << 1) | |
129 | (((lv >> 15) ^ (lv >> 4) ^ (lv >> 2) ^ (lv >> 1)) & 1)) |
130 | & 0xFFFF; |
131 | } |
132 | |
133 | return (u16) lv; |
134 | } |
135 | |
136 | /* |
137 | * This does LFSR conversion on the value that is to be written. |
138 | * See LFSR explanation above for more detail. |
139 | */ |
140 | static char *udl_set_register_lfsr16(char *wrptr, u8 reg, u16 value) |
141 | { |
142 | return udl_set_register_16(wrptr, reg, value: udl_lfsr16(actual_count: value)); |
143 | } |
144 | |
145 | /* |
146 | * Takes a DRM display mode and converts it into the DisplayLink |
147 | * equivalent register commands. |
148 | */ |
149 | static char *udl_set_display_mode(char *buf, struct drm_display_mode *mode) |
150 | { |
151 | u16 reg01 = mode->crtc_htotal - mode->crtc_hsync_start; |
152 | u16 reg03 = reg01 + mode->crtc_hdisplay; |
153 | u16 reg05 = mode->crtc_vtotal - mode->crtc_vsync_start; |
154 | u16 reg07 = reg05 + mode->crtc_vdisplay; |
155 | u16 reg09 = mode->crtc_htotal - 1; |
156 | u16 reg0b = 1; /* libdlo hardcodes hsync start to 1 */ |
157 | u16 reg0d = mode->crtc_hsync_end - mode->crtc_hsync_start + 1; |
158 | u16 reg0f = mode->hdisplay; |
159 | u16 reg11 = mode->crtc_vtotal; |
160 | u16 reg13 = 0; /* libdlo hardcodes vsync start to 0 */ |
161 | u16 reg15 = mode->crtc_vsync_end - mode->crtc_vsync_start; |
162 | u16 reg17 = mode->crtc_vdisplay; |
163 | u16 reg1b = mode->clock / 5; |
164 | |
165 | buf = udl_set_register_lfsr16(wrptr: buf, UDL_REG_XDISPLAYSTART, value: reg01); |
166 | buf = udl_set_register_lfsr16(wrptr: buf, UDL_REG_XDISPLAYEND, value: reg03); |
167 | buf = udl_set_register_lfsr16(wrptr: buf, UDL_REG_YDISPLAYSTART, value: reg05); |
168 | buf = udl_set_register_lfsr16(wrptr: buf, UDL_REG_YDISPLAYEND, value: reg07); |
169 | buf = udl_set_register_lfsr16(wrptr: buf, UDL_REG_XENDCOUNT, value: reg09); |
170 | buf = udl_set_register_lfsr16(wrptr: buf, UDL_REG_HSYNCSTART, value: reg0b); |
171 | buf = udl_set_register_lfsr16(wrptr: buf, UDL_REG_HSYNCEND, value: reg0d); |
172 | buf = udl_set_register_16(wrptr: buf, UDL_REG_HPIXELS, value: reg0f); |
173 | buf = udl_set_register_lfsr16(wrptr: buf, UDL_REG_YENDCOUNT, value: reg11); |
174 | buf = udl_set_register_lfsr16(wrptr: buf, UDL_REG_VSYNCSTART, value: reg13); |
175 | buf = udl_set_register_lfsr16(wrptr: buf, UDL_REG_VSYNCEND, value: reg15); |
176 | buf = udl_set_register_16(wrptr: buf, UDL_REG_VPIXELS, value: reg17); |
177 | buf = udl_set_register_16be(wrptr: buf, UDL_REG_PIXELCLOCK5KHZ, value: reg1b); |
178 | |
179 | return buf; |
180 | } |
181 | |
182 | static char *udl_dummy_render(char *wrptr) |
183 | { |
184 | *wrptr++ = UDL_MSG_BULK; |
185 | *wrptr++ = UDL_CMD_WRITECOPY16; |
186 | *wrptr++ = 0x00; /* from addr */ |
187 | *wrptr++ = 0x00; |
188 | *wrptr++ = 0x00; |
189 | *wrptr++ = 0x01; /* one pixel */ |
190 | *wrptr++ = 0x00; /* to address */ |
191 | *wrptr++ = 0x00; |
192 | *wrptr++ = 0x00; |
193 | return wrptr; |
194 | } |
195 | |
196 | static long udl_log_cpp(unsigned int cpp) |
197 | { |
198 | if (WARN_ON(!is_power_of_2(cpp))) |
199 | return -EINVAL; |
200 | return __ffs(cpp); |
201 | } |
202 | |
203 | static int udl_handle_damage(struct drm_framebuffer *fb, |
204 | const struct iosys_map *map, |
205 | const struct drm_rect *clip) |
206 | { |
207 | struct drm_device *dev = fb->dev; |
208 | void *vaddr = map->vaddr; /* TODO: Use mapping abstraction properly */ |
209 | int i, ret; |
210 | char *cmd; |
211 | struct urb *urb; |
212 | int log_bpp; |
213 | |
214 | ret = udl_log_cpp(cpp: fb->format->cpp[0]); |
215 | if (ret < 0) |
216 | return ret; |
217 | log_bpp = ret; |
218 | |
219 | urb = udl_get_urb(dev); |
220 | if (!urb) |
221 | return -ENOMEM; |
222 | cmd = urb->transfer_buffer; |
223 | |
224 | for (i = clip->y1; i < clip->y2; i++) { |
225 | const int line_offset = fb->pitches[0] * i; |
226 | const int byte_offset = line_offset + (clip->x1 << log_bpp); |
227 | const int dev_byte_offset = (fb->width * i + clip->x1) << log_bpp; |
228 | const int byte_width = drm_rect_width(r: clip) << log_bpp; |
229 | ret = udl_render_hline(dev, log_bpp, urb_ptr: &urb, front: (char *)vaddr, |
230 | urb_buf_ptr: &cmd, byte_offset, device_byte_offset: dev_byte_offset, |
231 | byte_width); |
232 | if (ret) |
233 | return ret; |
234 | } |
235 | |
236 | if (cmd > (char *)urb->transfer_buffer) { |
237 | /* Send partial buffer remaining before exiting */ |
238 | int len; |
239 | if (cmd < (char *)urb->transfer_buffer + urb->transfer_buffer_length) |
240 | *cmd++ = UDL_MSG_BULK; |
241 | len = cmd - (char *)urb->transfer_buffer; |
242 | ret = udl_submit_urb(dev, urb, len); |
243 | } else { |
244 | udl_urb_completion(urb); |
245 | } |
246 | |
247 | return 0; |
248 | } |
249 | |
250 | /* |
251 | * Primary plane |
252 | */ |
253 | |
254 | static const uint32_t udl_primary_plane_formats[] = { |
255 | DRM_FORMAT_RGB565, |
256 | DRM_FORMAT_XRGB8888, |
257 | }; |
258 | |
259 | static const uint64_t udl_primary_plane_fmtmods[] = { |
260 | DRM_FORMAT_MOD_LINEAR, |
261 | DRM_FORMAT_MOD_INVALID |
262 | }; |
263 | |
264 | static void udl_primary_plane_helper_atomic_update(struct drm_plane *plane, |
265 | struct drm_atomic_state *state) |
266 | { |
267 | struct drm_device *dev = plane->dev; |
268 | struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); |
269 | struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(state: plane_state); |
270 | struct drm_framebuffer *fb = plane_state->fb; |
271 | struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); |
272 | struct drm_atomic_helper_damage_iter iter; |
273 | struct drm_rect damage; |
274 | int ret, idx; |
275 | |
276 | if (!fb) |
277 | return; /* no framebuffer; plane is disabled */ |
278 | |
279 | ret = drm_gem_fb_begin_cpu_access(fb, dir: DMA_FROM_DEVICE); |
280 | if (ret) |
281 | return; |
282 | |
283 | if (!drm_dev_enter(dev, idx: &idx)) |
284 | goto out_drm_gem_fb_end_cpu_access; |
285 | |
286 | drm_atomic_helper_damage_iter_init(iter: &iter, old_state: old_plane_state, new_state: plane_state); |
287 | drm_atomic_for_each_plane_damage(&iter, &damage) { |
288 | udl_handle_damage(fb, map: &shadow_plane_state->data[0], clip: &damage); |
289 | } |
290 | |
291 | drm_dev_exit(idx); |
292 | |
293 | out_drm_gem_fb_end_cpu_access: |
294 | drm_gem_fb_end_cpu_access(fb, dir: DMA_FROM_DEVICE); |
295 | } |
296 | |
297 | static const struct drm_plane_helper_funcs udl_primary_plane_helper_funcs = { |
298 | DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, |
299 | .atomic_check = drm_plane_helper_atomic_check, |
300 | .atomic_update = udl_primary_plane_helper_atomic_update, |
301 | }; |
302 | |
303 | static const struct drm_plane_funcs udl_primary_plane_funcs = { |
304 | .update_plane = drm_atomic_helper_update_plane, |
305 | .disable_plane = drm_atomic_helper_disable_plane, |
306 | .destroy = drm_plane_cleanup, |
307 | DRM_GEM_SHADOW_PLANE_FUNCS, |
308 | }; |
309 | |
310 | /* |
311 | * CRTC |
312 | */ |
313 | |
314 | static void udl_crtc_helper_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state) |
315 | { |
316 | struct drm_device *dev = crtc->dev; |
317 | struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); |
318 | struct drm_display_mode *mode = &crtc_state->mode; |
319 | struct urb *urb; |
320 | char *buf; |
321 | int idx; |
322 | |
323 | if (!drm_dev_enter(dev, idx: &idx)) |
324 | return; |
325 | |
326 | urb = udl_get_urb(dev); |
327 | if (!urb) |
328 | goto out; |
329 | |
330 | buf = (char *)urb->transfer_buffer; |
331 | buf = udl_vidreg_lock(buf); |
332 | buf = udl_set_color_depth(buf, UDL_COLORDEPTH_16BPP); |
333 | /* set base for 16bpp segment to 0 */ |
334 | buf = udl_set_base16bpp(buf, base: 0); |
335 | /* set base for 8bpp segment to end of fb */ |
336 | buf = udl_set_base8bpp(buf, base: 2 * mode->vdisplay * mode->hdisplay); |
337 | buf = udl_set_display_mode(buf, mode); |
338 | buf = udl_set_blank_mode(buf, UDL_BLANKMODE_ON); |
339 | buf = udl_vidreg_unlock(buf); |
340 | buf = udl_dummy_render(wrptr: buf); |
341 | |
342 | udl_submit_urb(dev, urb, len: buf - (char *)urb->transfer_buffer); |
343 | |
344 | out: |
345 | drm_dev_exit(idx); |
346 | } |
347 | |
348 | static void udl_crtc_helper_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state) |
349 | { |
350 | struct drm_device *dev = crtc->dev; |
351 | struct urb *urb; |
352 | char *buf; |
353 | int idx; |
354 | |
355 | if (!drm_dev_enter(dev, idx: &idx)) |
356 | return; |
357 | |
358 | urb = udl_get_urb(dev); |
359 | if (!urb) |
360 | goto out; |
361 | |
362 | buf = (char *)urb->transfer_buffer; |
363 | buf = udl_vidreg_lock(buf); |
364 | buf = udl_set_blank_mode(buf, UDL_BLANKMODE_POWERDOWN); |
365 | buf = udl_vidreg_unlock(buf); |
366 | buf = udl_dummy_render(wrptr: buf); |
367 | |
368 | udl_submit_urb(dev, urb, len: buf - (char *)urb->transfer_buffer); |
369 | |
370 | out: |
371 | drm_dev_exit(idx); |
372 | } |
373 | |
374 | static const struct drm_crtc_helper_funcs udl_crtc_helper_funcs = { |
375 | .atomic_check = drm_crtc_helper_atomic_check, |
376 | .atomic_enable = udl_crtc_helper_atomic_enable, |
377 | .atomic_disable = udl_crtc_helper_atomic_disable, |
378 | }; |
379 | |
380 | static const struct drm_crtc_funcs udl_crtc_funcs = { |
381 | .reset = drm_atomic_helper_crtc_reset, |
382 | .destroy = drm_crtc_cleanup, |
383 | .set_config = drm_atomic_helper_set_config, |
384 | .page_flip = drm_atomic_helper_page_flip, |
385 | .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, |
386 | .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, |
387 | }; |
388 | |
389 | /* |
390 | * Encoder |
391 | */ |
392 | |
393 | static const struct drm_encoder_funcs udl_encoder_funcs = { |
394 | .destroy = drm_encoder_cleanup, |
395 | }; |
396 | |
397 | /* |
398 | * Connector |
399 | */ |
400 | |
401 | static int udl_connector_helper_get_modes(struct drm_connector *connector) |
402 | { |
403 | struct udl_connector *udl_connector = to_udl_connector(connector); |
404 | |
405 | drm_connector_update_edid_property(connector, edid: udl_connector->edid); |
406 | if (udl_connector->edid) |
407 | return drm_add_edid_modes(connector, edid: udl_connector->edid); |
408 | |
409 | return 0; |
410 | } |
411 | |
412 | static const struct drm_connector_helper_funcs udl_connector_helper_funcs = { |
413 | .get_modes = udl_connector_helper_get_modes, |
414 | }; |
415 | |
416 | static int udl_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len) |
417 | { |
418 | struct udl_device *udl = data; |
419 | struct drm_device *dev = &udl->drm; |
420 | struct usb_device *udev = udl_to_usb_device(udl); |
421 | u8 *read_buff; |
422 | int ret; |
423 | size_t i; |
424 | |
425 | read_buff = kmalloc(size: 2, GFP_KERNEL); |
426 | if (!read_buff) |
427 | return -ENOMEM; |
428 | |
429 | for (i = 0; i < len; i++) { |
430 | int bval = (i + block * EDID_LENGTH) << 8; |
431 | |
432 | ret = usb_control_msg(dev: udev, usb_rcvctrlpipe(udev, 0), |
433 | request: 0x02, requesttype: (0x80 | (0x02 << 5)), value: bval, |
434 | index: 0xA1, data: read_buff, size: 2, USB_CTRL_GET_TIMEOUT); |
435 | if (ret < 0) { |
436 | drm_err(dev, "Read EDID byte %zu failed err %x\n" , i, ret); |
437 | goto err_kfree; |
438 | } else if (ret < 1) { |
439 | ret = -EIO; |
440 | drm_err(dev, "Read EDID byte %zu failed\n" , i); |
441 | goto err_kfree; |
442 | } |
443 | |
444 | buf[i] = read_buff[1]; |
445 | } |
446 | |
447 | kfree(objp: read_buff); |
448 | |
449 | return 0; |
450 | |
451 | err_kfree: |
452 | kfree(objp: read_buff); |
453 | return ret; |
454 | } |
455 | |
456 | static enum drm_connector_status udl_connector_detect(struct drm_connector *connector, bool force) |
457 | { |
458 | struct drm_device *dev = connector->dev; |
459 | struct udl_device *udl = to_udl(dev); |
460 | struct udl_connector *udl_connector = to_udl_connector(connector); |
461 | enum drm_connector_status status = connector_status_disconnected; |
462 | int idx; |
463 | |
464 | /* cleanup previous EDID */ |
465 | kfree(objp: udl_connector->edid); |
466 | udl_connector->edid = NULL; |
467 | |
468 | if (!drm_dev_enter(dev, idx: &idx)) |
469 | return connector_status_disconnected; |
470 | |
471 | udl_connector->edid = drm_do_get_edid(connector, get_edid_block: udl_get_edid_block, data: udl); |
472 | if (udl_connector->edid) |
473 | status = connector_status_connected; |
474 | |
475 | drm_dev_exit(idx); |
476 | |
477 | return status; |
478 | } |
479 | |
480 | static void udl_connector_destroy(struct drm_connector *connector) |
481 | { |
482 | struct udl_connector *udl_connector = to_udl_connector(connector); |
483 | |
484 | drm_connector_cleanup(connector); |
485 | kfree(objp: udl_connector->edid); |
486 | kfree(objp: udl_connector); |
487 | } |
488 | |
489 | static const struct drm_connector_funcs udl_connector_funcs = { |
490 | .reset = drm_atomic_helper_connector_reset, |
491 | .detect = udl_connector_detect, |
492 | .fill_modes = drm_helper_probe_single_connector_modes, |
493 | .destroy = udl_connector_destroy, |
494 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
495 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
496 | }; |
497 | |
498 | struct drm_connector *udl_connector_init(struct drm_device *dev) |
499 | { |
500 | struct udl_connector *udl_connector; |
501 | struct drm_connector *connector; |
502 | int ret; |
503 | |
504 | udl_connector = kzalloc(size: sizeof(*udl_connector), GFP_KERNEL); |
505 | if (!udl_connector) |
506 | return ERR_PTR(error: -ENOMEM); |
507 | |
508 | connector = &udl_connector->connector; |
509 | ret = drm_connector_init(dev, connector, funcs: &udl_connector_funcs, DRM_MODE_CONNECTOR_VGA); |
510 | if (ret) |
511 | goto err_kfree; |
512 | |
513 | drm_connector_helper_add(connector, funcs: &udl_connector_helper_funcs); |
514 | |
515 | connector->polled = DRM_CONNECTOR_POLL_HPD | |
516 | DRM_CONNECTOR_POLL_CONNECT | |
517 | DRM_CONNECTOR_POLL_DISCONNECT; |
518 | |
519 | return connector; |
520 | |
521 | err_kfree: |
522 | kfree(objp: udl_connector); |
523 | return ERR_PTR(error: ret); |
524 | } |
525 | |
526 | /* |
527 | * Modesetting |
528 | */ |
529 | |
530 | static enum drm_mode_status udl_mode_config_mode_valid(struct drm_device *dev, |
531 | const struct drm_display_mode *mode) |
532 | { |
533 | struct udl_device *udl = to_udl(dev); |
534 | |
535 | if (udl->sku_pixel_limit) { |
536 | if (mode->vdisplay * mode->hdisplay > udl->sku_pixel_limit) |
537 | return MODE_MEM; |
538 | } |
539 | |
540 | return MODE_OK; |
541 | } |
542 | |
543 | static const struct drm_mode_config_funcs udl_mode_config_funcs = { |
544 | .fb_create = drm_gem_fb_create_with_dirty, |
545 | .mode_valid = udl_mode_config_mode_valid, |
546 | .atomic_check = drm_atomic_helper_check, |
547 | .atomic_commit = drm_atomic_helper_commit, |
548 | }; |
549 | |
550 | int udl_modeset_init(struct drm_device *dev) |
551 | { |
552 | struct udl_device *udl = to_udl(dev); |
553 | struct drm_plane *primary_plane; |
554 | struct drm_crtc *crtc; |
555 | struct drm_encoder *encoder; |
556 | struct drm_connector *connector; |
557 | int ret; |
558 | |
559 | ret = drmm_mode_config_init(dev); |
560 | if (ret) |
561 | return ret; |
562 | |
563 | dev->mode_config.min_width = 640; |
564 | dev->mode_config.min_height = 480; |
565 | dev->mode_config.max_width = 2048; |
566 | dev->mode_config.max_height = 2048; |
567 | dev->mode_config.preferred_depth = 16; |
568 | dev->mode_config.funcs = &udl_mode_config_funcs; |
569 | |
570 | primary_plane = &udl->primary_plane; |
571 | ret = drm_universal_plane_init(dev, plane: primary_plane, possible_crtcs: 0, |
572 | funcs: &udl_primary_plane_funcs, |
573 | formats: udl_primary_plane_formats, |
574 | ARRAY_SIZE(udl_primary_plane_formats), |
575 | format_modifiers: udl_primary_plane_fmtmods, |
576 | type: DRM_PLANE_TYPE_PRIMARY, NULL); |
577 | if (ret) |
578 | return ret; |
579 | drm_plane_helper_add(plane: primary_plane, funcs: &udl_primary_plane_helper_funcs); |
580 | drm_plane_enable_fb_damage_clips(plane: primary_plane); |
581 | |
582 | crtc = &udl->crtc; |
583 | ret = drm_crtc_init_with_planes(dev, crtc, primary: primary_plane, NULL, |
584 | funcs: &udl_crtc_funcs, NULL); |
585 | if (ret) |
586 | return ret; |
587 | drm_crtc_helper_add(crtc, funcs: &udl_crtc_helper_funcs); |
588 | |
589 | encoder = &udl->encoder; |
590 | ret = drm_encoder_init(dev, encoder, funcs: &udl_encoder_funcs, DRM_MODE_ENCODER_DAC, NULL); |
591 | if (ret) |
592 | return ret; |
593 | encoder->possible_crtcs = drm_crtc_mask(crtc); |
594 | |
595 | connector = udl_connector_init(dev); |
596 | if (IS_ERR(ptr: connector)) |
597 | return PTR_ERR(ptr: connector); |
598 | ret = drm_connector_attach_encoder(connector, encoder); |
599 | if (ret) |
600 | return ret; |
601 | |
602 | drm_mode_config_reset(dev); |
603 | |
604 | return 0; |
605 | } |
606 | |