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, |
415 | clip: &damage, state: &shadow_plane_state->fmtcnv_state); |
416 | } |
417 | |
418 | drm_dev_exit(idx); |
419 | } |
420 | |
421 | static const struct drm_plane_helper_funcs cirrus_primary_plane_helper_funcs = { |
422 | DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, |
423 | .atomic_check = cirrus_primary_plane_helper_atomic_check, |
424 | .atomic_update = cirrus_primary_plane_helper_atomic_update, |
425 | }; |
426 | |
427 | static struct drm_plane_state * |
428 | cirrus_primary_plane_atomic_duplicate_state(struct drm_plane *plane) |
429 | { |
430 | struct drm_plane_state *plane_state = plane->state; |
431 | struct cirrus_primary_plane_state *primary_plane_state = |
432 | to_cirrus_primary_plane_state(plane_state); |
433 | struct cirrus_primary_plane_state *new_primary_plane_state; |
434 | struct drm_shadow_plane_state *new_shadow_plane_state; |
435 | |
436 | if (!plane_state) |
437 | return NULL; |
438 | |
439 | new_primary_plane_state = kzalloc(size: sizeof(*new_primary_plane_state), GFP_KERNEL); |
440 | if (!new_primary_plane_state) |
441 | return NULL; |
442 | new_shadow_plane_state = &new_primary_plane_state->base; |
443 | |
444 | __drm_gem_duplicate_shadow_plane_state(plane, new_shadow_plane_state); |
445 | new_primary_plane_state->format = primary_plane_state->format; |
446 | new_primary_plane_state->pitch = primary_plane_state->pitch; |
447 | |
448 | return &new_shadow_plane_state->base; |
449 | } |
450 | |
451 | static void cirrus_primary_plane_atomic_destroy_state(struct drm_plane *plane, |
452 | struct drm_plane_state *plane_state) |
453 | { |
454 | struct cirrus_primary_plane_state *primary_plane_state = |
455 | to_cirrus_primary_plane_state(plane_state); |
456 | |
457 | __drm_gem_destroy_shadow_plane_state(shadow_plane_state: &primary_plane_state->base); |
458 | kfree(objp: primary_plane_state); |
459 | } |
460 | |
461 | static void cirrus_reset_primary_plane(struct drm_plane *plane) |
462 | { |
463 | struct cirrus_primary_plane_state *primary_plane_state; |
464 | |
465 | if (plane->state) { |
466 | cirrus_primary_plane_atomic_destroy_state(plane, plane_state: plane->state); |
467 | plane->state = NULL; /* must be set to NULL here */ |
468 | } |
469 | |
470 | primary_plane_state = kzalloc(size: sizeof(*primary_plane_state), GFP_KERNEL); |
471 | if (!primary_plane_state) |
472 | return; |
473 | __drm_gem_reset_shadow_plane(plane, shadow_plane_state: &primary_plane_state->base); |
474 | } |
475 | |
476 | static const struct drm_plane_funcs cirrus_primary_plane_funcs = { |
477 | .update_plane = drm_atomic_helper_update_plane, |
478 | .disable_plane = drm_atomic_helper_disable_plane, |
479 | .destroy = drm_plane_cleanup, |
480 | .reset = cirrus_reset_primary_plane, |
481 | .atomic_duplicate_state = cirrus_primary_plane_atomic_duplicate_state, |
482 | .atomic_destroy_state = cirrus_primary_plane_atomic_destroy_state, |
483 | }; |
484 | |
485 | static int cirrus_crtc_helper_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) |
486 | { |
487 | struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); |
488 | int ret; |
489 | |
490 | if (!crtc_state->enable) |
491 | return 0; |
492 | |
493 | ret = drm_atomic_helper_check_crtc_primary_plane(crtc_state); |
494 | if (ret) |
495 | return ret; |
496 | |
497 | return 0; |
498 | } |
499 | |
500 | static void cirrus_crtc_helper_atomic_enable(struct drm_crtc *crtc, |
501 | struct drm_atomic_state *state) |
502 | { |
503 | struct cirrus_device *cirrus = to_cirrus(crtc->dev); |
504 | struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); |
505 | int idx; |
506 | |
507 | if (!drm_dev_enter(dev: &cirrus->dev, idx: &idx)) |
508 | return; |
509 | |
510 | cirrus_mode_set(cirrus, mode: &crtc_state->mode); |
511 | |
512 | /* Unblank (needed on S3 resume, vgabios doesn't do it then) */ |
513 | outb(VGA_AR_ENABLE_DISPLAY, VGA_ATT_W); |
514 | |
515 | drm_dev_exit(idx); |
516 | } |
517 | |
518 | static const struct drm_crtc_helper_funcs cirrus_crtc_helper_funcs = { |
519 | .atomic_check = cirrus_crtc_helper_atomic_check, |
520 | .atomic_enable = cirrus_crtc_helper_atomic_enable, |
521 | }; |
522 | |
523 | static const struct drm_crtc_funcs cirrus_crtc_funcs = { |
524 | .reset = drm_atomic_helper_crtc_reset, |
525 | .destroy = drm_crtc_cleanup, |
526 | .set_config = drm_atomic_helper_set_config, |
527 | .page_flip = drm_atomic_helper_page_flip, |
528 | .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, |
529 | .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, |
530 | }; |
531 | |
532 | static const struct drm_encoder_funcs cirrus_encoder_funcs = { |
533 | .destroy = drm_encoder_cleanup, |
534 | }; |
535 | |
536 | static int cirrus_connector_helper_get_modes(struct drm_connector *connector) |
537 | { |
538 | int count; |
539 | |
540 | count = drm_add_modes_noedid(connector, |
541 | hdisplay: connector->dev->mode_config.max_width, |
542 | vdisplay: connector->dev->mode_config.max_height); |
543 | drm_set_preferred_mode(connector, hpref: 1024, vpref: 768); |
544 | return count; |
545 | } |
546 | |
547 | static const struct drm_connector_helper_funcs cirrus_connector_helper_funcs = { |
548 | .get_modes = cirrus_connector_helper_get_modes, |
549 | }; |
550 | |
551 | static const struct drm_connector_funcs cirrus_connector_funcs = { |
552 | .fill_modes = drm_helper_probe_single_connector_modes, |
553 | .destroy = drm_connector_cleanup, |
554 | .reset = drm_atomic_helper_connector_reset, |
555 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
556 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
557 | }; |
558 | |
559 | static int cirrus_pipe_init(struct cirrus_device *cirrus) |
560 | { |
561 | struct drm_device *dev = &cirrus->dev; |
562 | struct drm_plane *primary_plane; |
563 | struct drm_crtc *crtc; |
564 | struct drm_encoder *encoder; |
565 | struct drm_connector *connector; |
566 | int ret; |
567 | |
568 | primary_plane = &cirrus->primary_plane; |
569 | ret = drm_universal_plane_init(dev, plane: primary_plane, possible_crtcs: 0, |
570 | funcs: &cirrus_primary_plane_funcs, |
571 | formats: cirrus_primary_plane_formats, |
572 | ARRAY_SIZE(cirrus_primary_plane_formats), |
573 | format_modifiers: cirrus_primary_plane_format_modifiers, |
574 | type: DRM_PLANE_TYPE_PRIMARY, NULL); |
575 | if (ret) |
576 | return ret; |
577 | drm_plane_helper_add(plane: primary_plane, funcs: &cirrus_primary_plane_helper_funcs); |
578 | drm_plane_enable_fb_damage_clips(plane: primary_plane); |
579 | |
580 | crtc = &cirrus->crtc; |
581 | ret = drm_crtc_init_with_planes(dev, crtc, primary: primary_plane, NULL, |
582 | funcs: &cirrus_crtc_funcs, NULL); |
583 | if (ret) |
584 | return ret; |
585 | drm_crtc_helper_add(crtc, funcs: &cirrus_crtc_helper_funcs); |
586 | |
587 | encoder = &cirrus->encoder; |
588 | ret = drm_encoder_init(dev, encoder, funcs: &cirrus_encoder_funcs, |
589 | DRM_MODE_ENCODER_DAC, NULL); |
590 | if (ret) |
591 | return ret; |
592 | encoder->possible_crtcs = drm_crtc_mask(crtc); |
593 | |
594 | connector = &cirrus->connector; |
595 | ret = drm_connector_init(dev, connector, funcs: &cirrus_connector_funcs, |
596 | DRM_MODE_CONNECTOR_VGA); |
597 | if (ret) |
598 | return ret; |
599 | drm_connector_helper_add(connector, funcs: &cirrus_connector_helper_funcs); |
600 | |
601 | ret = drm_connector_attach_encoder(connector, encoder); |
602 | if (ret) |
603 | return ret; |
604 | |
605 | return 0; |
606 | } |
607 | |
608 | /* ------------------------------------------------------------------ */ |
609 | /* cirrus framebuffers & mode config */ |
610 | |
611 | static enum drm_mode_status cirrus_mode_config_mode_valid(struct drm_device *dev, |
612 | const struct drm_display_mode *mode) |
613 | { |
614 | const struct drm_format_info *format = drm_format_info(DRM_FORMAT_XRGB8888); |
615 | uint64_t pitch = drm_format_info_min_pitch(info: format, plane: 0, buffer_width: mode->hdisplay); |
616 | |
617 | if (pitch * mode->vdisplay > CIRRUS_VRAM_SIZE) |
618 | return MODE_MEM; |
619 | |
620 | return MODE_OK; |
621 | } |
622 | |
623 | static const struct drm_mode_config_funcs cirrus_mode_config_funcs = { |
624 | .fb_create = drm_gem_fb_create_with_dirty, |
625 | .mode_valid = cirrus_mode_config_mode_valid, |
626 | .atomic_check = drm_atomic_helper_check, |
627 | .atomic_commit = drm_atomic_helper_commit, |
628 | }; |
629 | |
630 | static int cirrus_mode_config_init(struct cirrus_device *cirrus) |
631 | { |
632 | struct drm_device *dev = &cirrus->dev; |
633 | int ret; |
634 | |
635 | ret = drmm_mode_config_init(dev); |
636 | if (ret) |
637 | return ret; |
638 | |
639 | dev->mode_config.min_width = 0; |
640 | dev->mode_config.min_height = 0; |
641 | dev->mode_config.max_width = CIRRUS_MAX_PITCH / 2; |
642 | dev->mode_config.max_height = 1024; |
643 | dev->mode_config.preferred_depth = 16; |
644 | dev->mode_config.prefer_shadow = 0; |
645 | dev->mode_config.funcs = &cirrus_mode_config_funcs; |
646 | |
647 | return 0; |
648 | } |
649 | |
650 | /* ------------------------------------------------------------------ */ |
651 | |
652 | DEFINE_DRM_GEM_FOPS(cirrus_fops); |
653 | |
654 | static const struct drm_driver cirrus_driver = { |
655 | .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, |
656 | |
657 | .name = DRIVER_NAME, |
658 | .desc = DRIVER_DESC, |
659 | .date = DRIVER_DATE, |
660 | .major = DRIVER_MAJOR, |
661 | .minor = DRIVER_MINOR, |
662 | |
663 | .fops = &cirrus_fops, |
664 | DRM_GEM_SHMEM_DRIVER_OPS, |
665 | }; |
666 | |
667 | static int cirrus_pci_probe(struct pci_dev *pdev, |
668 | const struct pci_device_id *ent) |
669 | { |
670 | struct drm_device *dev; |
671 | struct cirrus_device *cirrus; |
672 | int ret; |
673 | |
674 | ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, req_driver: &cirrus_driver); |
675 | if (ret) |
676 | return ret; |
677 | |
678 | ret = pcim_enable_device(pdev); |
679 | if (ret) |
680 | return ret; |
681 | |
682 | ret = pci_request_regions(pdev, DRIVER_NAME); |
683 | if (ret) |
684 | return ret; |
685 | |
686 | ret = -ENOMEM; |
687 | cirrus = devm_drm_dev_alloc(&pdev->dev, &cirrus_driver, |
688 | struct cirrus_device, dev); |
689 | if (IS_ERR(ptr: cirrus)) |
690 | return PTR_ERR(ptr: cirrus); |
691 | |
692 | dev = &cirrus->dev; |
693 | |
694 | cirrus->vram = devm_ioremap(dev: &pdev->dev, pci_resource_start(pdev, 0), |
695 | pci_resource_len(pdev, 0)); |
696 | if (cirrus->vram == NULL) |
697 | return -ENOMEM; |
698 | |
699 | cirrus->mmio = devm_ioremap(dev: &pdev->dev, pci_resource_start(pdev, 1), |
700 | pci_resource_len(pdev, 1)); |
701 | if (cirrus->mmio == NULL) |
702 | return -ENOMEM; |
703 | |
704 | ret = cirrus_mode_config_init(cirrus); |
705 | if (ret) |
706 | return ret; |
707 | |
708 | ret = cirrus_pipe_init(cirrus); |
709 | if (ret < 0) |
710 | return ret; |
711 | |
712 | drm_mode_config_reset(dev); |
713 | |
714 | pci_set_drvdata(pdev, data: dev); |
715 | ret = drm_dev_register(dev, flags: 0); |
716 | if (ret) |
717 | return ret; |
718 | |
719 | drm_fbdev_generic_setup(dev, preferred_bpp: 16); |
720 | return 0; |
721 | } |
722 | |
723 | static void cirrus_pci_remove(struct pci_dev *pdev) |
724 | { |
725 | struct drm_device *dev = pci_get_drvdata(pdev); |
726 | |
727 | drm_dev_unplug(dev); |
728 | drm_atomic_helper_shutdown(dev); |
729 | } |
730 | |
731 | static void cirrus_pci_shutdown(struct pci_dev *pdev) |
732 | { |
733 | drm_atomic_helper_shutdown(dev: pci_get_drvdata(pdev)); |
734 | } |
735 | |
736 | static const struct pci_device_id pciidlist[] = { |
737 | { |
738 | .vendor = PCI_VENDOR_ID_CIRRUS, |
739 | .device = PCI_DEVICE_ID_CIRRUS_5446, |
740 | /* only bind to the cirrus chip in qemu */ |
741 | .subvendor = PCI_SUBVENDOR_ID_REDHAT_QUMRANET, |
742 | .subdevice = PCI_SUBDEVICE_ID_QEMU, |
743 | }, { |
744 | .vendor = PCI_VENDOR_ID_CIRRUS, |
745 | .device = PCI_DEVICE_ID_CIRRUS_5446, |
746 | .subvendor = PCI_VENDOR_ID_XEN, |
747 | .subdevice = 0x0001, |
748 | }, |
749 | { /* end if list */ } |
750 | }; |
751 | |
752 | static struct pci_driver cirrus_pci_driver = { |
753 | .name = DRIVER_NAME, |
754 | .id_table = pciidlist, |
755 | .probe = cirrus_pci_probe, |
756 | .remove = cirrus_pci_remove, |
757 | .shutdown = cirrus_pci_shutdown, |
758 | }; |
759 | |
760 | drm_module_pci_driver(cirrus_pci_driver) |
761 | |
762 | MODULE_DEVICE_TABLE(pci, pciidlist); |
763 | MODULE_LICENSE("GPL" ); |
764 | |