1 | /* SPDX-License-Identifier: GPL-2.0 */ |
2 | /* |
3 | * Copyright 2012-2019 Red Hat |
4 | * |
5 | * This file is subject to the terms and conditions of the GNU General |
6 | * Public License version 2. See the file COPYING in the main |
7 | * directory of this archive for more details. |
8 | * |
9 | * Authors: Matthew Garrett |
10 | * Dave Airlie |
11 | * Gerd Hoffmann |
12 | * |
13 | * Portions of this code derived from cirrusfb.c: |
14 | * drivers/video/cirrusfb.c - driver for Cirrus Logic chipsets |
15 | * |
16 | * Copyright 1999-2001 Jeff Garzik <jgarzik@pobox.com> |
17 | */ |
18 | |
19 | #include <linux/iosys-map.h> |
20 | #include <linux/module.h> |
21 | #include <linux/pci.h> |
22 | |
23 | #include <video/cirrus.h> |
24 | #include <video/vga.h> |
25 | |
26 | #include <drm/drm_aperture.h> |
27 | #include <drm/drm_atomic.h> |
28 | #include <drm/drm_atomic_helper.h> |
29 | #include <drm/drm_atomic_state_helper.h> |
30 | #include <drm/drm_connector.h> |
31 | #include <drm/drm_damage_helper.h> |
32 | #include <drm/drm_drv.h> |
33 | #include <drm/drm_edid.h> |
34 | #include <drm/drm_fbdev_generic.h> |
35 | #include <drm/drm_file.h> |
36 | #include <drm/drm_format_helper.h> |
37 | #include <drm/drm_fourcc.h> |
38 | #include <drm/drm_framebuffer.h> |
39 | #include <drm/drm_gem_atomic_helper.h> |
40 | #include <drm/drm_gem_framebuffer_helper.h> |
41 | #include <drm/drm_gem_shmem_helper.h> |
42 | #include <drm/drm_ioctl.h> |
43 | #include <drm/drm_managed.h> |
44 | #include <drm/drm_modeset_helper_vtables.h> |
45 | #include <drm/drm_module.h> |
46 | #include <drm/drm_probe_helper.h> |
47 | |
48 | #define DRIVER_NAME "cirrus" |
49 | #define DRIVER_DESC "qemu cirrus vga" |
50 | #define DRIVER_DATE "2019" |
51 | #define DRIVER_MAJOR 2 |
52 | #define DRIVER_MINOR 0 |
53 | |
54 | #define CIRRUS_MAX_PITCH (0x1FF << 3) /* (4096 - 1) & ~111b bytes */ |
55 | #define CIRRUS_VRAM_SIZE (4 * 1024 * 1024) /* 4 MB */ |
56 | |
57 | struct cirrus_device { |
58 | struct drm_device dev; |
59 | |
60 | /* modesetting pipeline */ |
61 | struct drm_plane primary_plane; |
62 | struct drm_crtc crtc; |
63 | struct drm_encoder encoder; |
64 | struct drm_connector connector; |
65 | |
66 | /* HW resources */ |
67 | void __iomem *vram; |
68 | void __iomem *mmio; |
69 | }; |
70 | |
71 | #define to_cirrus(_dev) container_of(_dev, struct cirrus_device, dev) |
72 | |
73 | struct cirrus_primary_plane_state { |
74 | struct drm_shadow_plane_state base; |
75 | |
76 | /* HW scanout buffer */ |
77 | const struct drm_format_info *format; |
78 | unsigned int pitch; |
79 | }; |
80 | |
81 | static inline struct cirrus_primary_plane_state * |
82 | to_cirrus_primary_plane_state(struct drm_plane_state *plane_state) |
83 | { |
84 | return container_of(plane_state, struct cirrus_primary_plane_state, base.base); |
85 | }; |
86 | |
87 | /* ------------------------------------------------------------------ */ |
88 | /* |
89 | * The meat of this driver. The core passes us a mode and we have to program |
90 | * it. The modesetting here is the bare minimum required to satisfy the qemu |
91 | * emulation of this hardware, and running this against a real device is |
92 | * likely to result in an inadequately programmed mode. We've already had |
93 | * the opportunity to modify the mode, so whatever we receive here should |
94 | * be something that can be correctly programmed and displayed |
95 | */ |
96 | |
97 | #define SEQ_INDEX 4 |
98 | #define SEQ_DATA 5 |
99 | |
100 | static u8 rreg_seq(struct cirrus_device *cirrus, u8 reg) |
101 | { |
102 | iowrite8(reg, cirrus->mmio + SEQ_INDEX); |
103 | return ioread8(cirrus->mmio + SEQ_DATA); |
104 | } |
105 | |
106 | static void wreg_seq(struct cirrus_device *cirrus, u8 reg, u8 val) |
107 | { |
108 | iowrite8(reg, cirrus->mmio + SEQ_INDEX); |
109 | iowrite8(val, cirrus->mmio + SEQ_DATA); |
110 | } |
111 | |
112 | #define CRT_INDEX 0x14 |
113 | #define CRT_DATA 0x15 |
114 | |
115 | static u8 rreg_crt(struct cirrus_device *cirrus, u8 reg) |
116 | { |
117 | iowrite8(reg, cirrus->mmio + CRT_INDEX); |
118 | return ioread8(cirrus->mmio + CRT_DATA); |
119 | } |
120 | |
121 | static void wreg_crt(struct cirrus_device *cirrus, u8 reg, u8 val) |
122 | { |
123 | iowrite8(reg, cirrus->mmio + CRT_INDEX); |
124 | iowrite8(val, cirrus->mmio + CRT_DATA); |
125 | } |
126 | |
127 | #define GFX_INDEX 0xe |
128 | #define GFX_DATA 0xf |
129 | |
130 | static void wreg_gfx(struct cirrus_device *cirrus, u8 reg, u8 val) |
131 | { |
132 | iowrite8(reg, cirrus->mmio + GFX_INDEX); |
133 | iowrite8(val, cirrus->mmio + GFX_DATA); |
134 | } |
135 | |
136 | #define VGA_DAC_MASK 0x06 |
137 | |
138 | static void wreg_hdr(struct cirrus_device *cirrus, u8 val) |
139 | { |
140 | ioread8(cirrus->mmio + VGA_DAC_MASK); |
141 | ioread8(cirrus->mmio + VGA_DAC_MASK); |
142 | ioread8(cirrus->mmio + VGA_DAC_MASK); |
143 | ioread8(cirrus->mmio + VGA_DAC_MASK); |
144 | iowrite8(val, cirrus->mmio + VGA_DAC_MASK); |
145 | } |
146 | |
147 | static const struct drm_format_info *cirrus_convert_to(struct drm_framebuffer *fb) |
148 | { |
149 | if (fb->format->format == DRM_FORMAT_XRGB8888 && fb->pitches[0] > CIRRUS_MAX_PITCH) { |
150 | if (fb->width * 3 <= CIRRUS_MAX_PITCH) |
151 | /* convert from XR24 to RG24 */ |
152 | return drm_format_info(DRM_FORMAT_RGB888); |
153 | else |
154 | /* convert from XR24 to RG16 */ |
155 | return drm_format_info(DRM_FORMAT_RGB565); |
156 | } |
157 | return NULL; |
158 | } |
159 | |
160 | static const struct drm_format_info *cirrus_format(struct drm_framebuffer *fb) |
161 | { |
162 | const struct drm_format_info *format = cirrus_convert_to(fb); |
163 | |
164 | if (format) |
165 | return format; |
166 | return fb->format; |
167 | } |
168 | |
169 | static int cirrus_pitch(struct drm_framebuffer *fb) |
170 | { |
171 | const struct drm_format_info *format = cirrus_convert_to(fb); |
172 | |
173 | if (format) |
174 | return drm_format_info_min_pitch(info: format, plane: 0, buffer_width: fb->width); |
175 | return fb->pitches[0]; |
176 | } |
177 | |
178 | static void cirrus_set_start_address(struct cirrus_device *cirrus, u32 offset) |
179 | { |
180 | u32 addr; |
181 | u8 tmp; |
182 | |
183 | addr = offset >> 2; |
184 | wreg_crt(cirrus, reg: 0x0c, val: (u8)((addr >> 8) & 0xff)); |
185 | wreg_crt(cirrus, reg: 0x0d, val: (u8)(addr & 0xff)); |
186 | |
187 | tmp = rreg_crt(cirrus, reg: 0x1b); |
188 | tmp &= 0xf2; |
189 | tmp |= (addr >> 16) & 0x01; |
190 | tmp |= (addr >> 15) & 0x0c; |
191 | wreg_crt(cirrus, reg: 0x1b, val: tmp); |
192 | |
193 | tmp = rreg_crt(cirrus, reg: 0x1d); |
194 | tmp &= 0x7f; |
195 | tmp |= (addr >> 12) & 0x80; |
196 | wreg_crt(cirrus, reg: 0x1d, val: tmp); |
197 | } |
198 | |
199 | static void cirrus_mode_set(struct cirrus_device *cirrus, |
200 | struct drm_display_mode *mode) |
201 | { |
202 | int hsyncstart, hsyncend, htotal, hdispend; |
203 | int vtotal, vdispend; |
204 | int tmp; |
205 | |
206 | htotal = mode->htotal / 8; |
207 | hsyncend = mode->hsync_end / 8; |
208 | hsyncstart = mode->hsync_start / 8; |
209 | hdispend = mode->hdisplay / 8; |
210 | |
211 | vtotal = mode->vtotal; |
212 | vdispend = mode->vdisplay; |
213 | |
214 | vdispend -= 1; |
215 | vtotal -= 2; |
216 | |
217 | htotal -= 5; |
218 | hdispend -= 1; |
219 | hsyncstart += 1; |
220 | hsyncend += 1; |
221 | |
222 | wreg_crt(cirrus, VGA_CRTC_V_SYNC_END, val: 0x20); |
223 | wreg_crt(cirrus, VGA_CRTC_H_TOTAL, val: htotal); |
224 | wreg_crt(cirrus, VGA_CRTC_H_DISP, val: hdispend); |
225 | wreg_crt(cirrus, VGA_CRTC_H_SYNC_START, val: hsyncstart); |
226 | wreg_crt(cirrus, VGA_CRTC_H_SYNC_END, val: hsyncend); |
227 | wreg_crt(cirrus, VGA_CRTC_V_TOTAL, val: vtotal & 0xff); |
228 | wreg_crt(cirrus, VGA_CRTC_V_DISP_END, val: vdispend & 0xff); |
229 | |
230 | tmp = 0x40; |
231 | if ((vdispend + 1) & 512) |
232 | tmp |= 0x20; |
233 | wreg_crt(cirrus, VGA_CRTC_MAX_SCAN, val: tmp); |
234 | |
235 | /* |
236 | * Overflow bits for values that don't fit in the standard registers |
237 | */ |
238 | tmp = 0x10; |
239 | if (vtotal & 0x100) |
240 | tmp |= 0x01; |
241 | if (vdispend & 0x100) |
242 | tmp |= 0x02; |
243 | if ((vdispend + 1) & 0x100) |
244 | tmp |= 0x08; |
245 | if (vtotal & 0x200) |
246 | tmp |= 0x20; |
247 | if (vdispend & 0x200) |
248 | tmp |= 0x40; |
249 | wreg_crt(cirrus, VGA_CRTC_OVERFLOW, val: tmp); |
250 | |
251 | tmp = 0; |
252 | |
253 | /* More overflow bits */ |
254 | |
255 | if ((htotal + 5) & 0x40) |
256 | tmp |= 0x10; |
257 | if ((htotal + 5) & 0x80) |
258 | tmp |= 0x20; |
259 | if (vtotal & 0x100) |
260 | tmp |= 0x40; |
261 | if (vtotal & 0x200) |
262 | tmp |= 0x80; |
263 | |
264 | wreg_crt(cirrus, CL_CRT1A, val: tmp); |
265 | |
266 | /* Disable Hercules/CGA compatibility */ |
267 | wreg_crt(cirrus, VGA_CRTC_MODE, val: 0x03); |
268 | } |
269 | |
270 | static void cirrus_format_set(struct cirrus_device *cirrus, |
271 | const struct drm_format_info *format) |
272 | { |
273 | u8 sr07, hdr; |
274 | |
275 | sr07 = rreg_seq(cirrus, reg: 0x07); |
276 | sr07 &= 0xe0; |
277 | |
278 | switch (format->format) { |
279 | case DRM_FORMAT_C8: |
280 | sr07 |= 0x11; |
281 | hdr = 0x00; |
282 | break; |
283 | case DRM_FORMAT_RGB565: |
284 | sr07 |= 0x17; |
285 | hdr = 0xc1; |
286 | break; |
287 | case DRM_FORMAT_RGB888: |
288 | sr07 |= 0x15; |
289 | hdr = 0xc5; |
290 | break; |
291 | case DRM_FORMAT_XRGB8888: |
292 | sr07 |= 0x19; |
293 | hdr = 0xc5; |
294 | break; |
295 | default: |
296 | return; |
297 | } |
298 | |
299 | wreg_seq(cirrus, reg: 0x7, val: sr07); |
300 | |
301 | /* Enable high-colour modes */ |
302 | wreg_gfx(cirrus, VGA_GFX_MODE, val: 0x40); |
303 | |
304 | /* And set graphics mode */ |
305 | wreg_gfx(cirrus, VGA_GFX_MISC, val: 0x01); |
306 | |
307 | wreg_hdr(cirrus, val: hdr); |
308 | } |
309 | |
310 | static void cirrus_pitch_set(struct cirrus_device *cirrus, unsigned int pitch) |
311 | { |
312 | u8 cr13, cr1b; |
313 | |
314 | /* Program the pitch */ |
315 | cr13 = pitch / 8; |
316 | wreg_crt(cirrus, VGA_CRTC_OFFSET, val: cr13); |
317 | |
318 | /* Enable extended blanking and pitch bits, and enable full memory */ |
319 | cr1b = 0x22; |
320 | cr1b |= (pitch >> 7) & 0x10; |
321 | cr1b |= (pitch >> 6) & 0x40; |
322 | wreg_crt(cirrus, reg: 0x1b, val: cr1b); |
323 | |
324 | cirrus_set_start_address(cirrus, offset: 0); |
325 | } |
326 | |
327 | /* ------------------------------------------------------------------ */ |
328 | /* cirrus display pipe */ |
329 | |
330 | static const uint32_t cirrus_primary_plane_formats[] = { |
331 | DRM_FORMAT_RGB565, |
332 | DRM_FORMAT_RGB888, |
333 | DRM_FORMAT_XRGB8888, |
334 | }; |
335 | |
336 | static const uint64_t cirrus_primary_plane_format_modifiers[] = { |
337 | DRM_FORMAT_MOD_LINEAR, |
338 | DRM_FORMAT_MOD_INVALID |
339 | }; |
340 | |
341 | static int cirrus_primary_plane_helper_atomic_check(struct drm_plane *plane, |
342 | struct drm_atomic_state *state) |
343 | { |
344 | struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); |
345 | struct cirrus_primary_plane_state *new_primary_plane_state = |
346 | to_cirrus_primary_plane_state(plane_state: new_plane_state); |
347 | struct drm_framebuffer *fb = new_plane_state->fb; |
348 | struct drm_crtc *new_crtc = new_plane_state->crtc; |
349 | struct drm_crtc_state *new_crtc_state = NULL; |
350 | int ret; |
351 | unsigned int pitch; |
352 | |
353 | if (new_crtc) |
354 | new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc: new_crtc); |
355 | |
356 | ret = drm_atomic_helper_check_plane_state(plane_state: new_plane_state, crtc_state: new_crtc_state, |
357 | DRM_PLANE_NO_SCALING, |
358 | DRM_PLANE_NO_SCALING, |
359 | can_position: false, can_update_disabled: false); |
360 | if (ret) |
361 | return ret; |
362 | else if (!new_plane_state->visible) |
363 | return 0; |
364 | |
365 | pitch = cirrus_pitch(fb); |
366 | |
367 | /* validate size constraints */ |
368 | if (pitch > CIRRUS_MAX_PITCH) |
369 | return -EINVAL; |
370 | else if (pitch * fb->height > CIRRUS_VRAM_SIZE) |
371 | return -EINVAL; |
372 | |
373 | new_primary_plane_state->format = cirrus_format(fb); |
374 | new_primary_plane_state->pitch = pitch; |
375 | |
376 | return 0; |
377 | } |
378 | |
379 | static void cirrus_primary_plane_helper_atomic_update(struct drm_plane *plane, |
380 | struct drm_atomic_state *state) |
381 | { |
382 | struct cirrus_device *cirrus = to_cirrus(plane->dev); |
383 | struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); |
384 | struct cirrus_primary_plane_state *primary_plane_state = |
385 | to_cirrus_primary_plane_state(plane_state); |
386 | struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(state: plane_state); |
387 | struct drm_framebuffer *fb = plane_state->fb; |
388 | const struct drm_format_info *format = primary_plane_state->format; |
389 | unsigned int pitch = primary_plane_state->pitch; |
390 | struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); |
391 | struct cirrus_primary_plane_state *old_primary_plane_state = |
392 | to_cirrus_primary_plane_state(plane_state: old_plane_state); |
393 | struct iosys_map vaddr = IOSYS_MAP_INIT_VADDR_IOMEM(cirrus->vram); |
394 | struct drm_atomic_helper_damage_iter iter; |
395 | struct drm_rect damage; |
396 | int idx; |
397 | |
398 | if (!fb) |
399 | return; |
400 | |
401 | if (!drm_dev_enter(dev: &cirrus->dev, idx: &idx)) |
402 | return; |
403 | |
404 | if (old_primary_plane_state->format != format) |
405 | cirrus_format_set(cirrus, format); |
406 | if (old_primary_plane_state->pitch != pitch) |
407 | cirrus_pitch_set(cirrus, pitch); |
408 | |
409 | drm_atomic_helper_damage_iter_init(iter: &iter, old_state: old_plane_state, new_state: plane_state); |
410 | drm_atomic_for_each_plane_damage(&iter, &damage) { |
411 | unsigned int offset = drm_fb_clip_offset(pitch, format, clip: &damage); |
412 | struct iosys_map dst = IOSYS_MAP_INIT_OFFSET(&vaddr, offset); |
413 | |
414 | drm_fb_blit(dst: &dst, dst_pitch: &pitch, dst_format: format->format, src: shadow_plane_state->data, fb, rect: &damage); |
415 | } |
416 | |
417 | drm_dev_exit(idx); |
418 | } |
419 | |
420 | static const struct drm_plane_helper_funcs cirrus_primary_plane_helper_funcs = { |
421 | DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, |
422 | .atomic_check = cirrus_primary_plane_helper_atomic_check, |
423 | .atomic_update = cirrus_primary_plane_helper_atomic_update, |
424 | }; |
425 | |
426 | static struct drm_plane_state * |
427 | cirrus_primary_plane_atomic_duplicate_state(struct drm_plane *plane) |
428 | { |
429 | struct drm_plane_state *plane_state = plane->state; |
430 | struct cirrus_primary_plane_state *primary_plane_state = |
431 | to_cirrus_primary_plane_state(plane_state); |
432 | struct cirrus_primary_plane_state *new_primary_plane_state; |
433 | struct drm_shadow_plane_state *new_shadow_plane_state; |
434 | |
435 | if (!plane_state) |
436 | return NULL; |
437 | |
438 | new_primary_plane_state = kzalloc(size: sizeof(*new_primary_plane_state), GFP_KERNEL); |
439 | if (!new_primary_plane_state) |
440 | return NULL; |
441 | new_shadow_plane_state = &new_primary_plane_state->base; |
442 | |
443 | __drm_gem_duplicate_shadow_plane_state(plane, new_shadow_plane_state); |
444 | new_primary_plane_state->format = primary_plane_state->format; |
445 | new_primary_plane_state->pitch = primary_plane_state->pitch; |
446 | |
447 | return &new_shadow_plane_state->base; |
448 | } |
449 | |
450 | static void cirrus_primary_plane_atomic_destroy_state(struct drm_plane *plane, |
451 | struct drm_plane_state *plane_state) |
452 | { |
453 | struct cirrus_primary_plane_state *primary_plane_state = |
454 | to_cirrus_primary_plane_state(plane_state); |
455 | |
456 | __drm_gem_destroy_shadow_plane_state(shadow_plane_state: &primary_plane_state->base); |
457 | kfree(objp: primary_plane_state); |
458 | } |
459 | |
460 | static void cirrus_reset_primary_plane(struct drm_plane *plane) |
461 | { |
462 | struct cirrus_primary_plane_state *primary_plane_state; |
463 | |
464 | if (plane->state) { |
465 | cirrus_primary_plane_atomic_destroy_state(plane, plane_state: plane->state); |
466 | plane->state = NULL; /* must be set to NULL here */ |
467 | } |
468 | |
469 | primary_plane_state = kzalloc(size: sizeof(*primary_plane_state), GFP_KERNEL); |
470 | if (!primary_plane_state) |
471 | return; |
472 | __drm_gem_reset_shadow_plane(plane, shadow_plane_state: &primary_plane_state->base); |
473 | } |
474 | |
475 | static const struct drm_plane_funcs cirrus_primary_plane_funcs = { |
476 | .update_plane = drm_atomic_helper_update_plane, |
477 | .disable_plane = drm_atomic_helper_disable_plane, |
478 | .destroy = drm_plane_cleanup, |
479 | .reset = cirrus_reset_primary_plane, |
480 | .atomic_duplicate_state = cirrus_primary_plane_atomic_duplicate_state, |
481 | .atomic_destroy_state = cirrus_primary_plane_atomic_destroy_state, |
482 | }; |
483 | |
484 | static int cirrus_crtc_helper_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) |
485 | { |
486 | struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); |
487 | int ret; |
488 | |
489 | if (!crtc_state->enable) |
490 | return 0; |
491 | |
492 | ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state); |
493 | if (ret) |
494 | return ret; |
495 | |
496 | return 0; |
497 | } |
498 | |
499 | static void cirrus_crtc_helper_atomic_enable(struct drm_crtc *crtc, |
500 | struct drm_atomic_state *state) |
501 | { |
502 | struct cirrus_device *cirrus = to_cirrus(crtc->dev); |
503 | struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); |
504 | int idx; |
505 | |
506 | if (!drm_dev_enter(dev: &cirrus->dev, idx: &idx)) |
507 | return; |
508 | |
509 | cirrus_mode_set(cirrus, mode: &crtc_state->mode); |
510 | |
511 | /* Unblank (needed on S3 resume, vgabios doesn't do it then) */ |
512 | outb(VGA_AR_ENABLE_DISPLAY, VGA_ATT_W); |
513 | |
514 | drm_dev_exit(idx); |
515 | } |
516 | |
517 | static const struct drm_crtc_helper_funcs cirrus_crtc_helper_funcs = { |
518 | .atomic_check = cirrus_crtc_helper_atomic_check, |
519 | .atomic_enable = cirrus_crtc_helper_atomic_enable, |
520 | }; |
521 | |
522 | static const struct drm_crtc_funcs cirrus_crtc_funcs = { |
523 | .reset = drm_atomic_helper_crtc_reset, |
524 | .destroy = drm_crtc_cleanup, |
525 | .set_config = drm_atomic_helper_set_config, |
526 | .page_flip = drm_atomic_helper_page_flip, |
527 | .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, |
528 | .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, |
529 | }; |
530 | |
531 | static const struct drm_encoder_funcs cirrus_encoder_funcs = { |
532 | .destroy = drm_encoder_cleanup, |
533 | }; |
534 | |
535 | static int cirrus_connector_helper_get_modes(struct drm_connector *connector) |
536 | { |
537 | int count; |
538 | |
539 | count = drm_add_modes_noedid(connector, |
540 | hdisplay: connector->dev->mode_config.max_width, |
541 | vdisplay: connector->dev->mode_config.max_height); |
542 | drm_set_preferred_mode(connector, hpref: 1024, vpref: 768); |
543 | return count; |
544 | } |
545 | |
546 | static const struct drm_connector_helper_funcs cirrus_connector_helper_funcs = { |
547 | .get_modes = cirrus_connector_helper_get_modes, |
548 | }; |
549 | |
550 | static const struct drm_connector_funcs cirrus_connector_funcs = { |
551 | .fill_modes = drm_helper_probe_single_connector_modes, |
552 | .destroy = drm_connector_cleanup, |
553 | .reset = drm_atomic_helper_connector_reset, |
554 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
555 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
556 | }; |
557 | |
558 | static int cirrus_pipe_init(struct cirrus_device *cirrus) |
559 | { |
560 | struct drm_device *dev = &cirrus->dev; |
561 | struct drm_plane *primary_plane; |
562 | struct drm_crtc *crtc; |
563 | struct drm_encoder *encoder; |
564 | struct drm_connector *connector; |
565 | int ret; |
566 | |
567 | primary_plane = &cirrus->primary_plane; |
568 | ret = drm_universal_plane_init(dev, plane: primary_plane, possible_crtcs: 0, |
569 | funcs: &cirrus_primary_plane_funcs, |
570 | formats: cirrus_primary_plane_formats, |
571 | ARRAY_SIZE(cirrus_primary_plane_formats), |
572 | format_modifiers: cirrus_primary_plane_format_modifiers, |
573 | type: DRM_PLANE_TYPE_PRIMARY, NULL); |
574 | if (ret) |
575 | return ret; |
576 | drm_plane_helper_add(plane: primary_plane, funcs: &cirrus_primary_plane_helper_funcs); |
577 | drm_plane_enable_fb_damage_clips(plane: primary_plane); |
578 | |
579 | crtc = &cirrus->crtc; |
580 | ret = drm_crtc_init_with_planes(dev, crtc, primary: primary_plane, NULL, |
581 | funcs: &cirrus_crtc_funcs, NULL); |
582 | if (ret) |
583 | return ret; |
584 | drm_crtc_helper_add(crtc, funcs: &cirrus_crtc_helper_funcs); |
585 | |
586 | encoder = &cirrus->encoder; |
587 | ret = drm_encoder_init(dev, encoder, funcs: &cirrus_encoder_funcs, |
588 | DRM_MODE_ENCODER_DAC, NULL); |
589 | if (ret) |
590 | return ret; |
591 | encoder->possible_crtcs = drm_crtc_mask(crtc); |
592 | |
593 | connector = &cirrus->connector; |
594 | ret = drm_connector_init(dev, connector, funcs: &cirrus_connector_funcs, |
595 | DRM_MODE_CONNECTOR_VGA); |
596 | if (ret) |
597 | return ret; |
598 | drm_connector_helper_add(connector, funcs: &cirrus_connector_helper_funcs); |
599 | |
600 | ret = drm_connector_attach_encoder(connector, encoder); |
601 | if (ret) |
602 | return ret; |
603 | |
604 | return 0; |
605 | } |
606 | |
607 | /* ------------------------------------------------------------------ */ |
608 | /* cirrus framebuffers & mode config */ |
609 | |
610 | static enum drm_mode_status cirrus_mode_config_mode_valid(struct drm_device *dev, |
611 | const struct drm_display_mode *mode) |
612 | { |
613 | const struct drm_format_info *format = drm_format_info(DRM_FORMAT_XRGB8888); |
614 | uint64_t pitch = drm_format_info_min_pitch(info: format, plane: 0, buffer_width: mode->hdisplay); |
615 | |
616 | if (pitch * mode->vdisplay > CIRRUS_VRAM_SIZE) |
617 | return MODE_MEM; |
618 | |
619 | return MODE_OK; |
620 | } |
621 | |
622 | static const struct drm_mode_config_funcs cirrus_mode_config_funcs = { |
623 | .fb_create = drm_gem_fb_create_with_dirty, |
624 | .mode_valid = cirrus_mode_config_mode_valid, |
625 | .atomic_check = drm_atomic_helper_check, |
626 | .atomic_commit = drm_atomic_helper_commit, |
627 | }; |
628 | |
629 | static int cirrus_mode_config_init(struct cirrus_device *cirrus) |
630 | { |
631 | struct drm_device *dev = &cirrus->dev; |
632 | int ret; |
633 | |
634 | ret = drmm_mode_config_init(dev); |
635 | if (ret) |
636 | return ret; |
637 | |
638 | dev->mode_config.min_width = 0; |
639 | dev->mode_config.min_height = 0; |
640 | dev->mode_config.max_width = CIRRUS_MAX_PITCH / 2; |
641 | dev->mode_config.max_height = 1024; |
642 | dev->mode_config.preferred_depth = 16; |
643 | dev->mode_config.prefer_shadow = 0; |
644 | dev->mode_config.funcs = &cirrus_mode_config_funcs; |
645 | |
646 | return 0; |
647 | } |
648 | |
649 | /* ------------------------------------------------------------------ */ |
650 | |
651 | DEFINE_DRM_GEM_FOPS(cirrus_fops); |
652 | |
653 | static const struct drm_driver cirrus_driver = { |
654 | .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, |
655 | |
656 | .name = DRIVER_NAME, |
657 | .desc = DRIVER_DESC, |
658 | .date = DRIVER_DATE, |
659 | .major = DRIVER_MAJOR, |
660 | .minor = DRIVER_MINOR, |
661 | |
662 | .fops = &cirrus_fops, |
663 | DRM_GEM_SHMEM_DRIVER_OPS, |
664 | }; |
665 | |
666 | static int cirrus_pci_probe(struct pci_dev *pdev, |
667 | const struct pci_device_id *ent) |
668 | { |
669 | struct drm_device *dev; |
670 | struct cirrus_device *cirrus; |
671 | int ret; |
672 | |
673 | ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, req_driver: &cirrus_driver); |
674 | if (ret) |
675 | return ret; |
676 | |
677 | ret = pcim_enable_device(pdev); |
678 | if (ret) |
679 | return ret; |
680 | |
681 | ret = pci_request_regions(pdev, DRIVER_NAME); |
682 | if (ret) |
683 | return ret; |
684 | |
685 | ret = -ENOMEM; |
686 | cirrus = devm_drm_dev_alloc(&pdev->dev, &cirrus_driver, |
687 | struct cirrus_device, dev); |
688 | if (IS_ERR(ptr: cirrus)) |
689 | return PTR_ERR(ptr: cirrus); |
690 | |
691 | dev = &cirrus->dev; |
692 | |
693 | cirrus->vram = devm_ioremap(dev: &pdev->dev, pci_resource_start(pdev, 0), |
694 | pci_resource_len(pdev, 0)); |
695 | if (cirrus->vram == NULL) |
696 | return -ENOMEM; |
697 | |
698 | cirrus->mmio = devm_ioremap(dev: &pdev->dev, pci_resource_start(pdev, 1), |
699 | pci_resource_len(pdev, 1)); |
700 | if (cirrus->mmio == NULL) |
701 | return -ENOMEM; |
702 | |
703 | ret = cirrus_mode_config_init(cirrus); |
704 | if (ret) |
705 | return ret; |
706 | |
707 | ret = cirrus_pipe_init(cirrus); |
708 | if (ret < 0) |
709 | return ret; |
710 | |
711 | drm_mode_config_reset(dev); |
712 | |
713 | pci_set_drvdata(pdev, data: dev); |
714 | ret = drm_dev_register(dev, flags: 0); |
715 | if (ret) |
716 | return ret; |
717 | |
718 | drm_fbdev_generic_setup(dev, preferred_bpp: 16); |
719 | return 0; |
720 | } |
721 | |
722 | static void cirrus_pci_remove(struct pci_dev *pdev) |
723 | { |
724 | struct drm_device *dev = pci_get_drvdata(pdev); |
725 | |
726 | drm_dev_unplug(dev); |
727 | drm_atomic_helper_shutdown(dev); |
728 | } |
729 | |
730 | static void cirrus_pci_shutdown(struct pci_dev *pdev) |
731 | { |
732 | drm_atomic_helper_shutdown(dev: pci_get_drvdata(pdev)); |
733 | } |
734 | |
735 | static const struct pci_device_id pciidlist[] = { |
736 | { |
737 | .vendor = PCI_VENDOR_ID_CIRRUS, |
738 | .device = PCI_DEVICE_ID_CIRRUS_5446, |
739 | /* only bind to the cirrus chip in qemu */ |
740 | .subvendor = PCI_SUBVENDOR_ID_REDHAT_QUMRANET, |
741 | .subdevice = PCI_SUBDEVICE_ID_QEMU, |
742 | }, { |
743 | .vendor = PCI_VENDOR_ID_CIRRUS, |
744 | .device = PCI_DEVICE_ID_CIRRUS_5446, |
745 | .subvendor = PCI_VENDOR_ID_XEN, |
746 | .subdevice = 0x0001, |
747 | }, |
748 | { /* end if list */ } |
749 | }; |
750 | |
751 | static struct pci_driver cirrus_pci_driver = { |
752 | .name = DRIVER_NAME, |
753 | .id_table = pciidlist, |
754 | .probe = cirrus_pci_probe, |
755 | .remove = cirrus_pci_remove, |
756 | .shutdown = cirrus_pci_shutdown, |
757 | }; |
758 | |
759 | drm_module_pci_driver(cirrus_pci_driver) |
760 | |
761 | MODULE_DEVICE_TABLE(pci, pciidlist); |
762 | MODULE_LICENSE("GPL" ); |
763 | |