Commit | Line | Data |
---|---|---|
fb1d9738 JB |
1 | /************************************************************************** |
2 | * | |
3 | * Copyright © 2007 David Airlie | |
54fbde8a | 4 | * Copyright © 2009-2015 VMware, Inc., Palo Alto, CA., USA |
fb1d9738 JB |
5 | * All Rights Reserved. |
6 | * | |
7 | * Permission is hereby granted, free of charge, to any person obtaining a | |
8 | * copy of this software and associated documentation files (the | |
9 | * "Software"), to deal in the Software without restriction, including | |
10 | * without limitation the rights to use, copy, modify, merge, publish, | |
11 | * distribute, sub license, and/or sell copies of the Software, and to | |
12 | * permit persons to whom the Software is furnished to do so, subject to | |
13 | * the following conditions: | |
14 | * | |
15 | * The above copyright notice and this permission notice (including the | |
16 | * next paragraph) shall be included in all copies or substantial portions | |
17 | * of the Software. | |
18 | * | |
19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
21 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL | |
22 | * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, | |
23 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |
24 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE | |
25 | * USE OR OTHER DEALINGS IN THE SOFTWARE. | |
26 | * | |
27 | **************************************************************************/ | |
28 | ||
2d1a8a48 PG |
29 | #include <linux/export.h> |
30 | ||
760285e7 | 31 | #include <drm/drmP.h> |
fb1d9738 | 32 | #include "vmwgfx_drv.h" |
a278724a | 33 | #include "vmwgfx_kms.h" |
fb1d9738 | 34 | |
760285e7 | 35 | #include <drm/ttm/ttm_placement.h> |
fb1d9738 JB |
36 | |
37 | #define VMW_DIRTY_DELAY (HZ / 30) | |
38 | ||
39 | struct vmw_fb_par { | |
40 | struct vmw_private *vmw_priv; | |
41 | ||
42 | void *vmalloc; | |
43 | ||
a278724a | 44 | struct mutex bo_mutex; |
fb1d9738 JB |
45 | struct vmw_dma_buffer *vmw_bo; |
46 | struct ttm_bo_kmap_obj map; | |
a278724a TH |
47 | void *bo_ptr; |
48 | unsigned bo_size; | |
49 | struct drm_framebuffer *set_fb; | |
50 | struct drm_display_mode *set_mode; | |
51 | u32 fb_x; | |
52 | u32 fb_y; | |
53 | bool bo_iowrite; | |
fb1d9738 JB |
54 | |
55 | u32 pseudo_palette[17]; | |
56 | ||
fb1d9738 JB |
57 | unsigned max_width; |
58 | unsigned max_height; | |
59 | ||
fb1d9738 JB |
60 | struct { |
61 | spinlock_t lock; | |
62 | bool active; | |
63 | unsigned x1; | |
64 | unsigned y1; | |
65 | unsigned x2; | |
66 | unsigned y2; | |
67 | } dirty; | |
a278724a TH |
68 | |
69 | struct drm_crtc *crtc; | |
70 | struct drm_connector *con; | |
772269f9 | 71 | struct delayed_work local_work; |
fb1d9738 JB |
72 | }; |
73 | ||
74 | static int vmw_fb_setcolreg(unsigned regno, unsigned red, unsigned green, | |
75 | unsigned blue, unsigned transp, | |
76 | struct fb_info *info) | |
77 | { | |
78 | struct vmw_fb_par *par = info->par; | |
79 | u32 *pal = par->pseudo_palette; | |
80 | ||
81 | if (regno > 15) { | |
82 | DRM_ERROR("Bad regno %u.\n", regno); | |
83 | return 1; | |
84 | } | |
85 | ||
a278724a | 86 | switch (par->set_fb->depth) { |
fb1d9738 JB |
87 | case 24: |
88 | case 32: | |
89 | pal[regno] = ((red & 0xff00) << 8) | | |
90 | (green & 0xff00) | | |
91 | ((blue & 0xff00) >> 8); | |
92 | break; | |
93 | default: | |
a278724a TH |
94 | DRM_ERROR("Bad depth %u, bpp %u.\n", par->set_fb->depth, |
95 | par->set_fb->bits_per_pixel); | |
fb1d9738 JB |
96 | return 1; |
97 | } | |
98 | ||
99 | return 0; | |
100 | } | |
101 | ||
102 | static int vmw_fb_check_var(struct fb_var_screeninfo *var, | |
103 | struct fb_info *info) | |
104 | { | |
105 | int depth = var->bits_per_pixel; | |
106 | struct vmw_fb_par *par = info->par; | |
107 | struct vmw_private *vmw_priv = par->vmw_priv; | |
108 | ||
109 | switch (var->bits_per_pixel) { | |
110 | case 32: | |
111 | depth = (var->transp.length > 0) ? 32 : 24; | |
112 | break; | |
113 | default: | |
114 | DRM_ERROR("Bad bpp %u.\n", var->bits_per_pixel); | |
115 | return -EINVAL; | |
116 | } | |
117 | ||
118 | switch (depth) { | |
119 | case 24: | |
120 | var->red.offset = 16; | |
121 | var->green.offset = 8; | |
122 | var->blue.offset = 0; | |
123 | var->red.length = 8; | |
124 | var->green.length = 8; | |
125 | var->blue.length = 8; | |
126 | var->transp.length = 0; | |
127 | var->transp.offset = 0; | |
128 | break; | |
129 | case 32: | |
130 | var->red.offset = 16; | |
131 | var->green.offset = 8; | |
132 | var->blue.offset = 0; | |
133 | var->red.length = 8; | |
134 | var->green.length = 8; | |
135 | var->blue.length = 8; | |
136 | var->transp.length = 8; | |
137 | var->transp.offset = 24; | |
138 | break; | |
139 | default: | |
140 | DRM_ERROR("Bad depth %u.\n", depth); | |
141 | return -EINVAL; | |
142 | } | |
143 | ||
d7e1958d JB |
144 | if ((var->xoffset + var->xres) > par->max_width || |
145 | (var->yoffset + var->yres) > par->max_height) { | |
fb1d9738 JB |
146 | DRM_ERROR("Requested geom can not fit in framebuffer\n"); |
147 | return -EINVAL; | |
148 | } | |
149 | ||
e133e737 | 150 | if (!vmw_kms_validate_mode_vram(vmw_priv, |
aa6de142 | 151 | var->xres * var->bits_per_pixel/8, |
e133e737 TH |
152 | var->yoffset + var->yres)) { |
153 | DRM_ERROR("Requested geom can not fit in framebuffer\n"); | |
154 | return -EINVAL; | |
155 | } | |
156 | ||
fb1d9738 JB |
157 | return 0; |
158 | } | |
159 | ||
fb1d9738 JB |
160 | static int vmw_fb_blank(int blank, struct fb_info *info) |
161 | { | |
162 | return 0; | |
163 | } | |
164 | ||
165 | /* | |
166 | * Dirty code | |
167 | */ | |
168 | ||
772269f9 | 169 | static void vmw_fb_dirty_flush(struct work_struct *work) |
fb1d9738 | 170 | { |
772269f9 TH |
171 | struct vmw_fb_par *par = container_of(work, struct vmw_fb_par, |
172 | local_work.work); | |
fb1d9738 JB |
173 | struct vmw_private *vmw_priv = par->vmw_priv; |
174 | struct fb_info *info = vmw_priv->fb_info; | |
a278724a TH |
175 | unsigned long irq_flags; |
176 | s32 dst_x1, dst_x2, dst_y1, dst_y2, w, h; | |
177 | u32 cpp, max_x, max_y; | |
178 | struct drm_clip_rect clip; | |
179 | struct drm_framebuffer *cur_fb; | |
180 | u8 *src_ptr, *dst_ptr; | |
fb1d9738 | 181 | |
09e2601b TH |
182 | if (vmw_priv->suspended) |
183 | return; | |
184 | ||
a278724a TH |
185 | mutex_lock(&par->bo_mutex); |
186 | cur_fb = par->set_fb; | |
187 | if (!cur_fb) | |
188 | goto out_unlock; | |
fb1d9738 | 189 | |
a278724a TH |
190 | spin_lock_irqsave(&par->dirty.lock, irq_flags); |
191 | if (!par->dirty.active) { | |
192 | spin_unlock_irqrestore(&par->dirty.lock, irq_flags); | |
193 | goto out_unlock; | |
fb1d9738 JB |
194 | } |
195 | ||
a278724a TH |
196 | /* |
197 | * Handle panning when copying from vmalloc to framebuffer. | |
198 | * Clip dirty area to framebuffer. | |
199 | */ | |
200 | cpp = (cur_fb->bits_per_pixel + 7) / 8; | |
201 | max_x = par->fb_x + cur_fb->width; | |
202 | max_y = par->fb_y + cur_fb->height; | |
203 | ||
204 | dst_x1 = par->dirty.x1 - par->fb_x; | |
205 | dst_y1 = par->dirty.y1 - par->fb_y; | |
206 | dst_x1 = max_t(s32, dst_x1, 0); | |
207 | dst_y1 = max_t(s32, dst_y1, 0); | |
208 | ||
209 | dst_x2 = par->dirty.x2 - par->fb_x; | |
210 | dst_y2 = par->dirty.y2 - par->fb_y; | |
211 | dst_x2 = min_t(s32, dst_x2, max_x); | |
212 | dst_y2 = min_t(s32, dst_y2, max_y); | |
213 | w = dst_x2 - dst_x1; | |
214 | h = dst_y2 - dst_y1; | |
215 | w = max_t(s32, 0, w); | |
216 | h = max_t(s32, 0, h); | |
fb1d9738 | 217 | |
a278724a TH |
218 | par->dirty.x1 = par->dirty.x2 = 0; |
219 | par->dirty.y1 = par->dirty.y2 = 0; | |
220 | spin_unlock_irqrestore(&par->dirty.lock, irq_flags); | |
221 | ||
222 | if (w && h) { | |
223 | dst_ptr = (u8 *)par->bo_ptr + | |
224 | (dst_y1 * par->set_fb->pitches[0] + dst_x1 * cpp); | |
225 | src_ptr = (u8 *)par->vmalloc + | |
226 | ((dst_y1 + par->fb_y) * info->fix.line_length + | |
227 | (dst_x1 + par->fb_x) * cpp); | |
228 | ||
229 | while (h-- > 0) { | |
230 | memcpy(dst_ptr, src_ptr, w*cpp); | |
231 | dst_ptr += par->set_fb->pitches[0]; | |
232 | src_ptr += info->fix.line_length; | |
233 | } | |
234 | ||
235 | clip.x1 = dst_x1; | |
236 | clip.x2 = dst_x2; | |
237 | clip.y1 = dst_y1; | |
238 | clip.y2 = dst_y2; | |
239 | ||
240 | WARN_ON_ONCE(par->set_fb->funcs->dirty(cur_fb, NULL, 0, 0, | |
241 | &clip, 1)); | |
242 | vmw_fifo_flush(vmw_priv, false); | |
fb1d9738 | 243 | } |
a278724a TH |
244 | out_unlock: |
245 | mutex_unlock(&par->bo_mutex); | |
fb1d9738 JB |
246 | } |
247 | ||
248 | static void vmw_fb_dirty_mark(struct vmw_fb_par *par, | |
249 | unsigned x1, unsigned y1, | |
250 | unsigned width, unsigned height) | |
251 | { | |
fb1d9738 JB |
252 | unsigned long flags; |
253 | unsigned x2 = x1 + width; | |
254 | unsigned y2 = y1 + height; | |
255 | ||
256 | spin_lock_irqsave(&par->dirty.lock, flags); | |
257 | if (par->dirty.x1 == par->dirty.x2) { | |
258 | par->dirty.x1 = x1; | |
259 | par->dirty.y1 = y1; | |
260 | par->dirty.x2 = x2; | |
261 | par->dirty.y2 = y2; | |
262 | /* if we are active start the dirty work | |
263 | * we share the work with the defio system */ | |
264 | if (par->dirty.active) | |
772269f9 TH |
265 | schedule_delayed_work(&par->local_work, |
266 | VMW_DIRTY_DELAY); | |
fb1d9738 JB |
267 | } else { |
268 | if (x1 < par->dirty.x1) | |
269 | par->dirty.x1 = x1; | |
270 | if (y1 < par->dirty.y1) | |
271 | par->dirty.y1 = y1; | |
272 | if (x2 > par->dirty.x2) | |
273 | par->dirty.x2 = x2; | |
274 | if (y2 > par->dirty.y2) | |
275 | par->dirty.y2 = y2; | |
276 | } | |
277 | spin_unlock_irqrestore(&par->dirty.lock, flags); | |
278 | } | |
279 | ||
a278724a TH |
280 | static int vmw_fb_pan_display(struct fb_var_screeninfo *var, |
281 | struct fb_info *info) | |
282 | { | |
283 | struct vmw_fb_par *par = info->par; | |
284 | ||
285 | if ((var->xoffset + var->xres) > var->xres_virtual || | |
286 | (var->yoffset + var->yres) > var->yres_virtual) { | |
287 | DRM_ERROR("Requested panning can not fit in framebuffer\n"); | |
288 | return -EINVAL; | |
289 | } | |
290 | ||
291 | mutex_lock(&par->bo_mutex); | |
292 | par->fb_x = var->xoffset; | |
293 | par->fb_y = var->yoffset; | |
294 | if (par->set_fb) | |
295 | vmw_fb_dirty_mark(par, par->fb_x, par->fb_y, par->set_fb->width, | |
296 | par->set_fb->height); | |
297 | mutex_unlock(&par->bo_mutex); | |
298 | ||
299 | return 0; | |
300 | } | |
301 | ||
fb1d9738 JB |
302 | static void vmw_deferred_io(struct fb_info *info, |
303 | struct list_head *pagelist) | |
304 | { | |
305 | struct vmw_fb_par *par = info->par; | |
306 | unsigned long start, end, min, max; | |
307 | unsigned long flags; | |
308 | struct page *page; | |
309 | int y1, y2; | |
310 | ||
311 | min = ULONG_MAX; | |
312 | max = 0; | |
313 | list_for_each_entry(page, pagelist, lru) { | |
314 | start = page->index << PAGE_SHIFT; | |
315 | end = start + PAGE_SIZE - 1; | |
316 | min = min(min, start); | |
317 | max = max(max, end); | |
318 | } | |
319 | ||
320 | if (min < max) { | |
321 | y1 = min / info->fix.line_length; | |
322 | y2 = (max / info->fix.line_length) + 1; | |
323 | ||
324 | spin_lock_irqsave(&par->dirty.lock, flags); | |
325 | par->dirty.x1 = 0; | |
326 | par->dirty.y1 = y1; | |
327 | par->dirty.x2 = info->var.xres; | |
328 | par->dirty.y2 = y2; | |
329 | spin_unlock_irqrestore(&par->dirty.lock, flags); | |
fb1d9738 | 330 | |
772269f9 TH |
331 | /* |
332 | * Since we've already waited on this work once, try to | |
333 | * execute asap. | |
334 | */ | |
335 | cancel_delayed_work(&par->local_work); | |
336 | schedule_delayed_work(&par->local_work, 0); | |
337 | } | |
fb1d9738 JB |
338 | }; |
339 | ||
b9eb1a61 | 340 | static struct fb_deferred_io vmw_defio = { |
fb1d9738 JB |
341 | .delay = VMW_DIRTY_DELAY, |
342 | .deferred_io = vmw_deferred_io, | |
343 | }; | |
344 | ||
345 | /* | |
346 | * Draw code | |
347 | */ | |
348 | ||
349 | static void vmw_fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) | |
350 | { | |
351 | cfb_fillrect(info, rect); | |
352 | vmw_fb_dirty_mark(info->par, rect->dx, rect->dy, | |
353 | rect->width, rect->height); | |
354 | } | |
355 | ||
356 | static void vmw_fb_copyarea(struct fb_info *info, const struct fb_copyarea *region) | |
357 | { | |
358 | cfb_copyarea(info, region); | |
359 | vmw_fb_dirty_mark(info->par, region->dx, region->dy, | |
360 | region->width, region->height); | |
361 | } | |
362 | ||
363 | static void vmw_fb_imageblit(struct fb_info *info, const struct fb_image *image) | |
364 | { | |
365 | cfb_imageblit(info, image); | |
366 | vmw_fb_dirty_mark(info->par, image->dx, image->dy, | |
367 | image->width, image->height); | |
368 | } | |
369 | ||
370 | /* | |
371 | * Bring up code | |
372 | */ | |
373 | ||
fb1d9738 JB |
374 | static int vmw_fb_create_bo(struct vmw_private *vmw_priv, |
375 | size_t size, struct vmw_dma_buffer **out) | |
376 | { | |
377 | struct vmw_dma_buffer *vmw_bo; | |
fb1d9738 JB |
378 | int ret; |
379 | ||
294adf7d | 380 | (void) ttm_write_lock(&vmw_priv->reservation_sem, false); |
fb1d9738 JB |
381 | |
382 | vmw_bo = kmalloc(sizeof(*vmw_bo), GFP_KERNEL); | |
294adf7d TH |
383 | if (!vmw_bo) { |
384 | ret = -ENOMEM; | |
fb1d9738 | 385 | goto err_unlock; |
294adf7d | 386 | } |
fb1d9738 JB |
387 | |
388 | ret = vmw_dmabuf_init(vmw_priv, vmw_bo, size, | |
a278724a | 389 | &vmw_sys_placement, |
fb1d9738 JB |
390 | false, |
391 | &vmw_dmabuf_bo_free); | |
392 | if (unlikely(ret != 0)) | |
393 | goto err_unlock; /* init frees the buffer on failure */ | |
394 | ||
395 | *out = vmw_bo; | |
12617971 | 396 | ttm_write_unlock(&vmw_priv->reservation_sem); |
fb1d9738 JB |
397 | |
398 | return 0; | |
399 | ||
400 | err_unlock: | |
12617971 | 401 | ttm_write_unlock(&vmw_priv->reservation_sem); |
fb1d9738 JB |
402 | return ret; |
403 | } | |
404 | ||
a278724a TH |
405 | static int vmw_fb_compute_depth(struct fb_var_screeninfo *var, |
406 | int *depth) | |
407 | { | |
408 | switch (var->bits_per_pixel) { | |
409 | case 32: | |
410 | *depth = (var->transp.length > 0) ? 32 : 24; | |
411 | break; | |
412 | default: | |
413 | DRM_ERROR("Bad bpp %u.\n", var->bits_per_pixel); | |
414 | return -EINVAL; | |
415 | } | |
416 | ||
417 | return 0; | |
418 | } | |
419 | ||
420 | static int vmw_fb_kms_detach(struct vmw_fb_par *par, | |
421 | bool detach_bo, | |
422 | bool unref_bo) | |
423 | { | |
424 | struct drm_framebuffer *cur_fb = par->set_fb; | |
425 | int ret; | |
426 | ||
427 | /* Detach the KMS framebuffer from crtcs */ | |
428 | if (par->set_mode) { | |
429 | struct drm_mode_set set; | |
430 | ||
431 | set.crtc = par->crtc; | |
432 | set.x = 0; | |
433 | set.y = 0; | |
434 | set.mode = NULL; | |
435 | set.fb = NULL; | |
436 | set.num_connectors = 1; | |
437 | set.connectors = &par->con; | |
438 | ret = drm_mode_set_config_internal(&set); | |
439 | if (ret) { | |
440 | DRM_ERROR("Could not unset a mode.\n"); | |
441 | return ret; | |
442 | } | |
443 | drm_mode_destroy(par->vmw_priv->dev, par->set_mode); | |
444 | par->set_mode = NULL; | |
445 | } | |
446 | ||
447 | if (cur_fb) { | |
448 | drm_framebuffer_unreference(cur_fb); | |
449 | par->set_fb = NULL; | |
450 | } | |
451 | ||
452 | if (par->vmw_bo && detach_bo) { | |
453 | if (par->bo_ptr) { | |
454 | ttm_bo_kunmap(&par->map); | |
455 | par->bo_ptr = NULL; | |
456 | } | |
457 | if (unref_bo) | |
458 | vmw_dmabuf_unreference(&par->vmw_bo); | |
459 | else | |
460 | vmw_dmabuf_unpin(par->vmw_priv, par->vmw_bo, false); | |
461 | } | |
462 | ||
463 | return 0; | |
464 | } | |
465 | ||
466 | static int vmw_fb_kms_framebuffer(struct fb_info *info) | |
467 | { | |
468 | struct drm_mode_fb_cmd mode_cmd; | |
469 | struct vmw_fb_par *par = info->par; | |
470 | struct fb_var_screeninfo *var = &info->var; | |
471 | struct drm_framebuffer *cur_fb; | |
472 | struct vmw_framebuffer *vfb; | |
473 | int ret = 0; | |
474 | size_t new_bo_size; | |
475 | ||
476 | ret = vmw_fb_compute_depth(var, &mode_cmd.depth); | |
477 | if (ret) | |
478 | return ret; | |
479 | ||
480 | mode_cmd.width = var->xres; | |
481 | mode_cmd.height = var->yres; | |
482 | mode_cmd.bpp = var->bits_per_pixel; | |
483 | mode_cmd.pitch = ((mode_cmd.bpp + 7) / 8) * mode_cmd.width; | |
484 | ||
485 | cur_fb = par->set_fb; | |
486 | if (cur_fb && cur_fb->width == mode_cmd.width && | |
487 | cur_fb->height == mode_cmd.height && | |
488 | cur_fb->bits_per_pixel == mode_cmd.bpp && | |
489 | cur_fb->depth == mode_cmd.depth && | |
490 | cur_fb->pitches[0] == mode_cmd.pitch) | |
491 | return 0; | |
492 | ||
493 | /* Need new buffer object ? */ | |
494 | new_bo_size = (size_t) mode_cmd.pitch * (size_t) mode_cmd.height; | |
495 | ret = vmw_fb_kms_detach(par, | |
496 | par->bo_size < new_bo_size || | |
497 | par->bo_size > 2*new_bo_size, | |
498 | true); | |
499 | if (ret) | |
500 | return ret; | |
501 | ||
502 | if (!par->vmw_bo) { | |
503 | ret = vmw_fb_create_bo(par->vmw_priv, new_bo_size, | |
504 | &par->vmw_bo); | |
505 | if (ret) { | |
506 | DRM_ERROR("Failed creating a buffer object for " | |
507 | "fbdev.\n"); | |
508 | return ret; | |
509 | } | |
510 | par->bo_size = new_bo_size; | |
511 | } | |
512 | ||
513 | vfb = vmw_kms_new_framebuffer(par->vmw_priv, par->vmw_bo, NULL, | |
514 | true, &mode_cmd); | |
515 | if (IS_ERR(vfb)) | |
516 | return PTR_ERR(vfb); | |
517 | ||
518 | par->set_fb = &vfb->base; | |
519 | ||
520 | if (!par->bo_ptr) { | |
521 | /* | |
522 | * Pin before mapping. Since we don't know in what placement | |
523 | * to pin, call into KMS to do it for us. | |
524 | */ | |
525 | ret = vfb->pin(vfb); | |
526 | if (ret) { | |
527 | DRM_ERROR("Could not pin the fbdev framebuffer.\n"); | |
528 | return ret; | |
529 | } | |
530 | ||
531 | ret = ttm_bo_kmap(&par->vmw_bo->base, 0, | |
532 | par->vmw_bo->base.num_pages, &par->map); | |
533 | if (ret) { | |
534 | vfb->unpin(vfb); | |
535 | DRM_ERROR("Could not map the fbdev framebuffer.\n"); | |
536 | return ret; | |
537 | } | |
538 | ||
539 | par->bo_ptr = ttm_kmap_obj_virtual(&par->map, &par->bo_iowrite); | |
540 | } | |
541 | ||
542 | return 0; | |
543 | } | |
544 | ||
545 | static int vmw_fb_set_par(struct fb_info *info) | |
546 | { | |
547 | struct vmw_fb_par *par = info->par; | |
548 | struct vmw_private *vmw_priv = par->vmw_priv; | |
549 | struct drm_mode_set set; | |
550 | struct fb_var_screeninfo *var = &info->var; | |
551 | struct drm_display_mode new_mode = { DRM_MODE("fb_mode", | |
552 | DRM_MODE_TYPE_DRIVER, | |
553 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
554 | DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC) | |
555 | }; | |
556 | struct drm_display_mode *old_mode; | |
557 | struct drm_display_mode *mode; | |
558 | int ret; | |
559 | ||
560 | old_mode = par->set_mode; | |
561 | mode = drm_mode_duplicate(vmw_priv->dev, &new_mode); | |
562 | if (!mode) { | |
563 | DRM_ERROR("Could not create new fb mode.\n"); | |
564 | return -ENOMEM; | |
565 | } | |
566 | ||
567 | mode->hdisplay = var->xres; | |
568 | mode->vdisplay = var->yres; | |
569 | vmw_guess_mode_timing(mode); | |
570 | ||
571 | if (old_mode && drm_mode_equal(old_mode, mode)) { | |
572 | drm_mode_destroy(vmw_priv->dev, mode); | |
573 | mode = old_mode; | |
574 | old_mode = NULL; | |
575 | } else if (!vmw_kms_validate_mode_vram(vmw_priv, | |
7851496a SY |
576 | mode->hdisplay * |
577 | DIV_ROUND_UP(var->bits_per_pixel, 8), | |
578 | mode->vdisplay)) { | |
a278724a TH |
579 | drm_mode_destroy(vmw_priv->dev, mode); |
580 | return -EINVAL; | |
581 | } | |
582 | ||
583 | mutex_lock(&par->bo_mutex); | |
584 | drm_modeset_lock_all(vmw_priv->dev); | |
585 | ret = vmw_fb_kms_framebuffer(info); | |
586 | if (ret) | |
587 | goto out_unlock; | |
588 | ||
589 | par->fb_x = var->xoffset; | |
590 | par->fb_y = var->yoffset; | |
591 | ||
592 | set.crtc = par->crtc; | |
593 | set.x = 0; | |
594 | set.y = 0; | |
595 | set.mode = mode; | |
596 | set.fb = par->set_fb; | |
597 | set.num_connectors = 1; | |
598 | set.connectors = &par->con; | |
599 | ||
600 | ret = drm_mode_set_config_internal(&set); | |
601 | if (ret) | |
602 | goto out_unlock; | |
603 | ||
604 | vmw_fb_dirty_mark(par, par->fb_x, par->fb_y, | |
605 | par->set_fb->width, par->set_fb->height); | |
606 | ||
607 | /* If there already was stuff dirty we wont | |
608 | * schedule a new work, so lets do it now */ | |
609 | ||
772269f9 | 610 | schedule_delayed_work(&par->local_work, 0); |
a278724a TH |
611 | |
612 | out_unlock: | |
613 | if (old_mode) | |
614 | drm_mode_destroy(vmw_priv->dev, old_mode); | |
615 | par->set_mode = mode; | |
616 | ||
617 | drm_modeset_unlock_all(vmw_priv->dev); | |
618 | mutex_unlock(&par->bo_mutex); | |
619 | ||
620 | return ret; | |
621 | } | |
622 | ||
623 | ||
624 | static struct fb_ops vmw_fb_ops = { | |
625 | .owner = THIS_MODULE, | |
626 | .fb_check_var = vmw_fb_check_var, | |
627 | .fb_set_par = vmw_fb_set_par, | |
628 | .fb_setcolreg = vmw_fb_setcolreg, | |
629 | .fb_fillrect = vmw_fb_fillrect, | |
630 | .fb_copyarea = vmw_fb_copyarea, | |
631 | .fb_imageblit = vmw_fb_imageblit, | |
632 | .fb_pan_display = vmw_fb_pan_display, | |
633 | .fb_blank = vmw_fb_blank, | |
634 | }; | |
635 | ||
fb1d9738 JB |
636 | int vmw_fb_init(struct vmw_private *vmw_priv) |
637 | { | |
638 | struct device *device = &vmw_priv->dev->pdev->dev; | |
639 | struct vmw_fb_par *par; | |
640 | struct fb_info *info; | |
fb1d9738 | 641 | unsigned fb_width, fb_height; |
6558429b | 642 | unsigned fb_bpp, fb_depth, fb_offset, fb_pitch, fb_size; |
a278724a | 643 | struct drm_display_mode *init_mode; |
fb1d9738 JB |
644 | int ret; |
645 | ||
6558429b | 646 | fb_bpp = 32; |
fb1d9738 JB |
647 | fb_depth = 24; |
648 | ||
d7e1958d JB |
649 | /* XXX As shouldn't these be as well. */ |
650 | fb_width = min(vmw_priv->fb_max_width, (unsigned)2048); | |
651 | fb_height = min(vmw_priv->fb_max_height, (unsigned)2048); | |
fb1d9738 | 652 | |
6558429b | 653 | fb_pitch = fb_width * fb_bpp / 8; |
d7e1958d | 654 | fb_size = fb_pitch * fb_height; |
fb1d9738 | 655 | fb_offset = vmw_read(vmw_priv, SVGA_REG_FB_OFFSET); |
fb1d9738 JB |
656 | |
657 | info = framebuffer_alloc(sizeof(*par), device); | |
658 | if (!info) | |
659 | return -ENOMEM; | |
660 | ||
661 | /* | |
662 | * Par | |
663 | */ | |
664 | vmw_priv->fb_info = info; | |
665 | par = info->par; | |
a278724a | 666 | memset(par, 0, sizeof(*par)); |
772269f9 | 667 | INIT_DELAYED_WORK(&par->local_work, &vmw_fb_dirty_flush); |
fb1d9738 | 668 | par->vmw_priv = vmw_priv; |
fb1d9738 JB |
669 | par->vmalloc = NULL; |
670 | par->max_width = fb_width; | |
671 | par->max_height = fb_height; | |
672 | ||
a278724a TH |
673 | drm_modeset_lock_all(vmw_priv->dev); |
674 | ret = vmw_kms_fbdev_init_data(vmw_priv, 0, par->max_width, | |
675 | par->max_height, &par->con, | |
676 | &par->crtc, &init_mode); | |
677 | if (ret) { | |
678 | drm_modeset_unlock_all(vmw_priv->dev); | |
679 | goto err_kms; | |
680 | } | |
681 | ||
682 | info->var.xres = init_mode->hdisplay; | |
683 | info->var.yres = init_mode->vdisplay; | |
684 | drm_modeset_unlock_all(vmw_priv->dev); | |
685 | ||
fb1d9738 JB |
686 | /* |
687 | * Create buffers and alloc memory | |
688 | */ | |
a278724a | 689 | par->vmalloc = vzalloc(fb_size); |
fb1d9738 JB |
690 | if (unlikely(par->vmalloc == NULL)) { |
691 | ret = -ENOMEM; | |
692 | goto err_free; | |
693 | } | |
694 | ||
fb1d9738 JB |
695 | /* |
696 | * Fixed and var | |
697 | */ | |
698 | strcpy(info->fix.id, "svgadrmfb"); | |
699 | info->fix.type = FB_TYPE_PACKED_PIXELS; | |
700 | info->fix.visual = FB_VISUAL_TRUECOLOR; | |
701 | info->fix.type_aux = 0; | |
702 | info->fix.xpanstep = 1; /* doing it in hw */ | |
703 | info->fix.ypanstep = 1; /* doing it in hw */ | |
704 | info->fix.ywrapstep = 0; | |
705 | info->fix.accel = FB_ACCEL_NONE; | |
706 | info->fix.line_length = fb_pitch; | |
707 | ||
708 | info->fix.smem_start = 0; | |
709 | info->fix.smem_len = fb_size; | |
710 | ||
fb1d9738 | 711 | info->pseudo_palette = par->pseudo_palette; |
b9eb1a61 | 712 | info->screen_base = (char __iomem *)par->vmalloc; |
fb1d9738 JB |
713 | info->screen_size = fb_size; |
714 | ||
715 | info->flags = FBINFO_DEFAULT; | |
716 | info->fbops = &vmw_fb_ops; | |
717 | ||
718 | /* 24 depth per default */ | |
719 | info->var.red.offset = 16; | |
720 | info->var.green.offset = 8; | |
721 | info->var.blue.offset = 0; | |
722 | info->var.red.length = 8; | |
723 | info->var.green.length = 8; | |
724 | info->var.blue.length = 8; | |
725 | info->var.transp.offset = 0; | |
726 | info->var.transp.length = 0; | |
727 | ||
728 | info->var.xres_virtual = fb_width; | |
729 | info->var.yres_virtual = fb_height; | |
a278724a | 730 | info->var.bits_per_pixel = fb_bpp; |
fb1d9738 JB |
731 | info->var.xoffset = 0; |
732 | info->var.yoffset = 0; | |
733 | info->var.activate = FB_ACTIVATE_NOW; | |
734 | info->var.height = -1; | |
735 | info->var.width = -1; | |
736 | ||
fb2a99e1 | 737 | /* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */ |
1471ca9a MS |
738 | info->apertures = alloc_apertures(1); |
739 | if (!info->apertures) { | |
740 | ret = -ENOMEM; | |
741 | goto err_aper; | |
742 | } | |
743 | info->apertures->ranges[0].base = vmw_priv->vram_start; | |
744 | info->apertures->ranges[0].size = vmw_priv->vram_size; | |
f2d12b8e | 745 | |
fb1d9738 JB |
746 | /* |
747 | * Dirty & Deferred IO | |
748 | */ | |
749 | par->dirty.x1 = par->dirty.x2 = 0; | |
c39721c7 | 750 | par->dirty.y1 = par->dirty.y2 = 0; |
fb1d9738 JB |
751 | par->dirty.active = true; |
752 | spin_lock_init(&par->dirty.lock); | |
a278724a | 753 | mutex_init(&par->bo_mutex); |
fb1d9738 JB |
754 | info->fbdefio = &vmw_defio; |
755 | fb_deferred_io_init(info); | |
756 | ||
757 | ret = register_framebuffer(info); | |
758 | if (unlikely(ret != 0)) | |
759 | goto err_defio; | |
760 | ||
a278724a TH |
761 | vmw_fb_set_par(info); |
762 | ||
fb1d9738 JB |
763 | return 0; |
764 | ||
765 | err_defio: | |
766 | fb_deferred_io_cleanup(info); | |
1471ca9a | 767 | err_aper: |
fb1d9738 JB |
768 | err_free: |
769 | vfree(par->vmalloc); | |
a278724a | 770 | err_kms: |
fb1d9738 JB |
771 | framebuffer_release(info); |
772 | vmw_priv->fb_info = NULL; | |
773 | ||
774 | return ret; | |
775 | } | |
776 | ||
777 | int vmw_fb_close(struct vmw_private *vmw_priv) | |
778 | { | |
779 | struct fb_info *info; | |
780 | struct vmw_fb_par *par; | |
fb1d9738 JB |
781 | |
782 | if (!vmw_priv->fb_info) | |
783 | return 0; | |
784 | ||
785 | info = vmw_priv->fb_info; | |
786 | par = info->par; | |
fb1d9738 JB |
787 | |
788 | /* ??? order */ | |
789 | fb_deferred_io_cleanup(info); | |
772269f9 | 790 | cancel_delayed_work_sync(&par->local_work); |
fb1d9738 JB |
791 | unregister_framebuffer(info); |
792 | ||
a278724a | 793 | (void) vmw_fb_kms_detach(par, true, true); |
fb1d9738 JB |
794 | |
795 | vfree(par->vmalloc); | |
796 | framebuffer_release(info); | |
797 | ||
798 | return 0; | |
799 | } | |
800 | ||
fb1d9738 JB |
801 | int vmw_fb_off(struct vmw_private *vmw_priv) |
802 | { | |
803 | struct fb_info *info; | |
804 | struct vmw_fb_par *par; | |
805 | unsigned long flags; | |
806 | ||
807 | if (!vmw_priv->fb_info) | |
808 | return -EINVAL; | |
809 | ||
810 | info = vmw_priv->fb_info; | |
811 | par = info->par; | |
812 | ||
813 | spin_lock_irqsave(&par->dirty.lock, flags); | |
814 | par->dirty.active = false; | |
815 | spin_unlock_irqrestore(&par->dirty.lock, flags); | |
816 | ||
43829731 | 817 | flush_delayed_work(&info->deferred_work); |
772269f9 | 818 | flush_delayed_work(&par->local_work); |
fb1d9738 | 819 | |
a278724a TH |
820 | mutex_lock(&par->bo_mutex); |
821 | (void) vmw_fb_kms_detach(par, true, false); | |
822 | mutex_unlock(&par->bo_mutex); | |
fb1d9738 JB |
823 | |
824 | return 0; | |
825 | } | |
826 | ||
827 | int vmw_fb_on(struct vmw_private *vmw_priv) | |
828 | { | |
829 | struct fb_info *info; | |
830 | struct vmw_fb_par *par; | |
831 | unsigned long flags; | |
fb1d9738 JB |
832 | |
833 | if (!vmw_priv->fb_info) | |
834 | return -EINVAL; | |
835 | ||
836 | info = vmw_priv->fb_info; | |
837 | par = info->par; | |
838 | ||
a278724a | 839 | vmw_fb_set_par(info); |
fb1d9738 JB |
840 | spin_lock_irqsave(&par->dirty.lock, flags); |
841 | par->dirty.active = true; | |
842 | spin_unlock_irqrestore(&par->dirty.lock, flags); | |
a278724a | 843 | |
fb1d9738 JB |
844 | return 0; |
845 | } |