aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/udl/udl_fb.c
diff options
context:
space:
mode:
authorDave Airlie <airlied@gmail.com>2010-12-14 16:14:24 -0500
committerDave Airlie <airlied@redhat.com>2012-03-15 09:35:34 -0400
commit5320918b9a87865223fd6b228e530bf30bc64d9d (patch)
tree2bc55de1fc03c57851fd86d0cfaa7377d34cdc25 /drivers/gpu/drm/udl/udl_fb.c
parent2c07a21d6fb0be47fda696a618b726ea258ed1dd (diff)
drm/udl: initial UDL driver (v4)
This is an initial drm/kms driver for the displaylink devices. Supports fb_defio, supports KMS dumb interface supports 24bpp via conversion to 16bpp, hw can do this better. supports hot unplug using new drm core features. On an unplug, it disables connector polling, unplugs connectors from sysfs, unplugs fbdev layer (using Kay's API), drops all the USB device URBs, and call the drm core to unplug the device. This driver is based in large parts on udlfb.c so I've licensed it under GPLv2. Signed-off-by: Dave Airlie <airlied@redhat.com>
Diffstat (limited to 'drivers/gpu/drm/udl/udl_fb.c')
-rw-r--r--drivers/gpu/drm/udl/udl_fb.c611
1 files changed, 611 insertions, 0 deletions
diff --git a/drivers/gpu/drm/udl/udl_fb.c b/drivers/gpu/drm/udl/udl_fb.c
new file mode 100644
index 000000000000..4d9c3a5d8a45
--- /dev/null
+++ b/drivers/gpu/drm/udl/udl_fb.c
@@ -0,0 +1,611 @@
1/*
2 * Copyright (C) 2012 Red Hat
3 *
4 * based in parts on udlfb.c:
5 * Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it>
6 * Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com>
7 * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com>
8 *
9 * This file is subject to the terms and conditions of the GNU General Public
10 * License v2. See the file COPYING in the main directory of this archive for
11 * more details.
12 */
13#include <linux/module.h>
14#include <linux/slab.h>
15#include <linux/fb.h>
16
17#include "drmP.h"
18#include "drm.h"
19#include "drm_crtc.h"
20#include "drm_crtc_helper.h"
21#include "udl_drv.h"
22
23#include "drm_fb_helper.h"
24
25#define DL_DEFIO_WRITE_DELAY 5 /* fb_deferred_io.delay in jiffies */
26
27static int fb_defio = 1; /* Optionally enable experimental fb_defio mmap support */
28static int fb_bpp = 16;
29
30module_param(fb_bpp, int, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP);
31module_param(fb_defio, int, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP);
32
33struct udl_fbdev {
34 struct drm_fb_helper helper;
35 struct udl_framebuffer ufb;
36 struct list_head fbdev_list;
37 int fb_count;
38};
39
40#define DL_ALIGN_UP(x, a) ALIGN(x, a)
41#define DL_ALIGN_DOWN(x, a) ALIGN(x-(a-1), a)
42
43/** Read the red component (0..255) of a 32 bpp colour. */
44#define DLO_RGB_GETRED(col) (uint8_t)((col) & 0xFF)
45
46/** Read the green component (0..255) of a 32 bpp colour. */
47#define DLO_RGB_GETGRN(col) (uint8_t)(((col) >> 8) & 0xFF)
48
49/** Read the blue component (0..255) of a 32 bpp colour. */
50#define DLO_RGB_GETBLU(col) (uint8_t)(((col) >> 16) & 0xFF)
51
52/** Return red/green component of a 16 bpp colour number. */
53#define DLO_RG16(red, grn) (uint8_t)((((red) & 0xF8) | ((grn) >> 5)) & 0xFF)
54
55/** Return green/blue component of a 16 bpp colour number. */
56#define DLO_GB16(grn, blu) (uint8_t)(((((grn) & 0x1C) << 3) | ((blu) >> 3)) & 0xFF)
57
58/** Return 8 bpp colour number from red, green and blue components. */
59#define DLO_RGB8(red, grn, blu) ((((red) << 5) | (((grn) & 3) << 3) | ((blu) & 7)) & 0xFF)
60
61#if 0
62static uint8_t rgb8(uint32_t col)
63{
64 uint8_t red = DLO_RGB_GETRED(col);
65 uint8_t grn = DLO_RGB_GETGRN(col);
66 uint8_t blu = DLO_RGB_GETBLU(col);
67
68 return DLO_RGB8(red, grn, blu);
69}
70
71static uint16_t rgb16(uint32_t col)
72{
73 uint8_t red = DLO_RGB_GETRED(col);
74 uint8_t grn = DLO_RGB_GETGRN(col);
75 uint8_t blu = DLO_RGB_GETBLU(col);
76
77 return (DLO_RG16(red, grn) << 8) + DLO_GB16(grn, blu);
78}
79#endif
80
81/*
82 * NOTE: fb_defio.c is holding info->fbdefio.mutex
83 * Touching ANY framebuffer memory that triggers a page fault
84 * in fb_defio will cause a deadlock, when it also tries to
85 * grab the same mutex.
86 */
87static void udlfb_dpy_deferred_io(struct fb_info *info,
88 struct list_head *pagelist)
89{
90 struct page *cur;
91 struct fb_deferred_io *fbdefio = info->fbdefio;
92 struct udl_fbdev *ufbdev = info->par;
93 struct drm_device *dev = ufbdev->ufb.base.dev;
94 struct udl_device *udl = dev->dev_private;
95 struct urb *urb;
96 char *cmd;
97 cycles_t start_cycles, end_cycles;
98 int bytes_sent = 0;
99 int bytes_identical = 0;
100 int bytes_rendered = 0;
101
102 if (!fb_defio)
103 return;
104
105 start_cycles = get_cycles();
106
107 urb = udl_get_urb(dev);
108 if (!urb)
109 return;
110
111 cmd = urb->transfer_buffer;
112
113 /* walk the written page list and render each to device */
114 list_for_each_entry(cur, &fbdefio->pagelist, lru) {
115
116 if (udl_render_hline(dev, (ufbdev->ufb.base.bits_per_pixel / 8),
117 &urb, (char *) info->fix.smem_start,
118 &cmd, cur->index << PAGE_SHIFT,
119 PAGE_SIZE, &bytes_identical, &bytes_sent))
120 goto error;
121 bytes_rendered += PAGE_SIZE;
122 }
123
124 if (cmd > (char *) urb->transfer_buffer) {
125 /* Send partial buffer remaining before exiting */
126 int len = cmd - (char *) urb->transfer_buffer;
127 udl_submit_urb(dev, urb, len);
128 bytes_sent += len;
129 } else
130 udl_urb_completion(urb);
131
132error:
133 atomic_add(bytes_sent, &udl->bytes_sent);
134 atomic_add(bytes_identical, &udl->bytes_identical);
135 atomic_add(bytes_rendered, &udl->bytes_rendered);
136 end_cycles = get_cycles();
137 atomic_add(((unsigned int) ((end_cycles - start_cycles)
138 >> 10)), /* Kcycles */
139 &udl->cpu_kcycles_used);
140}
141
142int udl_handle_damage(struct udl_framebuffer *fb, int x, int y,
143 int width, int height)
144{
145 struct drm_device *dev = fb->base.dev;
146 struct udl_device *udl = dev->dev_private;
147 int i, ret;
148 char *cmd;
149 cycles_t start_cycles, end_cycles;
150 int bytes_sent = 0;
151 int bytes_identical = 0;
152 struct urb *urb;
153 int aligned_x;
154 int bpp = (fb->base.bits_per_pixel / 8);
155
156 if (!fb->active_16)
157 return 0;
158
159 if (!fb->obj->vmapping)
160 udl_gem_vmap(fb->obj);
161
162 start_cycles = get_cycles();
163
164 aligned_x = DL_ALIGN_DOWN(x, sizeof(unsigned long));
165 width = DL_ALIGN_UP(width + (x-aligned_x), sizeof(unsigned long));
166 x = aligned_x;
167
168 if ((width <= 0) ||
169 (x + width > fb->base.width) ||
170 (y + height > fb->base.height))
171 return -EINVAL;
172
173 urb = udl_get_urb(dev);
174 if (!urb)
175 return 0;
176 cmd = urb->transfer_buffer;
177
178 for (i = y; i < y + height ; i++) {
179 const int line_offset = fb->base.pitches[0] * i;
180 const int byte_offset = line_offset + (x * bpp);
181
182 if (udl_render_hline(dev, bpp, &urb,
183 (char *) fb->obj->vmapping,
184 &cmd, byte_offset, width * bpp,
185 &bytes_identical, &bytes_sent))
186 goto error;
187 }
188
189 if (cmd > (char *) urb->transfer_buffer) {
190 /* Send partial buffer remaining before exiting */
191 int len = cmd - (char *) urb->transfer_buffer;
192 ret = udl_submit_urb(dev, urb, len);
193 bytes_sent += len;
194 } else
195 udl_urb_completion(urb);
196
197error:
198 atomic_add(bytes_sent, &udl->bytes_sent);
199 atomic_add(bytes_identical, &udl->bytes_identical);
200 atomic_add(width*height*bpp, &udl->bytes_rendered);
201 end_cycles = get_cycles();
202 atomic_add(((unsigned int) ((end_cycles - start_cycles)
203 >> 10)), /* Kcycles */
204 &udl->cpu_kcycles_used);
205
206 return 0;
207}
208
209static int udl_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
210{
211 unsigned long start = vma->vm_start;
212 unsigned long size = vma->vm_end - vma->vm_start;
213 unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
214 unsigned long page, pos;
215
216 if (offset + size > info->fix.smem_len)
217 return -EINVAL;
218
219 pos = (unsigned long)info->fix.smem_start + offset;
220
221 pr_notice("mmap() framebuffer addr:%lu size:%lu\n",
222 pos, size);
223
224 while (size > 0) {
225 page = vmalloc_to_pfn((void *)pos);
226 if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED))
227 return -EAGAIN;
228
229 start += PAGE_SIZE;
230 pos += PAGE_SIZE;
231 if (size > PAGE_SIZE)
232 size -= PAGE_SIZE;
233 else
234 size = 0;
235 }
236
237 vma->vm_flags |= VM_RESERVED; /* avoid to swap out this VMA */
238 return 0;
239}
240
241static void udl_fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
242{
243 struct udl_fbdev *ufbdev = info->par;
244
245 sys_fillrect(info, rect);
246
247 udl_handle_damage(&ufbdev->ufb, rect->dx, rect->dy, rect->width,
248 rect->height);
249}
250
251static void udl_fb_copyarea(struct fb_info *info, const struct fb_copyarea *region)
252{
253 struct udl_fbdev *ufbdev = info->par;
254
255 sys_copyarea(info, region);
256
257 udl_handle_damage(&ufbdev->ufb, region->dx, region->dy, region->width,
258 region->height);
259}
260
261static void udl_fb_imageblit(struct fb_info *info, const struct fb_image *image)
262{
263 struct udl_fbdev *ufbdev = info->par;
264
265 sys_imageblit(info, image);
266
267 udl_handle_damage(&ufbdev->ufb, image->dx, image->dy, image->width,
268 image->height);
269}
270
271/*
272 * It's common for several clients to have framebuffer open simultaneously.
273 * e.g. both fbcon and X. Makes things interesting.
274 * Assumes caller is holding info->lock (for open and release at least)
275 */
276static int udl_fb_open(struct fb_info *info, int user)
277{
278 struct udl_fbdev *ufbdev = info->par;
279 struct drm_device *dev = ufbdev->ufb.base.dev;
280 struct udl_device *udl = dev->dev_private;
281
282 /* If the USB device is gone, we don't accept new opens */
283 if (drm_device_is_unplugged(udl->ddev))
284 return -ENODEV;
285
286 ufbdev->fb_count++;
287
288 if (fb_defio && (info->fbdefio == NULL)) {
289 /* enable defio at last moment if not disabled by client */
290
291 struct fb_deferred_io *fbdefio;
292
293 fbdefio = kmalloc(sizeof(struct fb_deferred_io), GFP_KERNEL);
294
295 if (fbdefio) {
296 fbdefio->delay = DL_DEFIO_WRITE_DELAY;
297 fbdefio->deferred_io = udlfb_dpy_deferred_io;
298 }
299
300 info->fbdefio = fbdefio;
301 fb_deferred_io_init(info);
302 }
303
304 pr_notice("open /dev/fb%d user=%d fb_info=%p count=%d\n",
305 info->node, user, info, ufbdev->fb_count);
306
307 return 0;
308}
309
310
311/*
312 * Assumes caller is holding info->lock mutex (for open and release at least)
313 */
314static int udl_fb_release(struct fb_info *info, int user)
315{
316 struct udl_fbdev *ufbdev = info->par;
317
318 ufbdev->fb_count--;
319
320 if ((ufbdev->fb_count == 0) && (info->fbdefio)) {
321 fb_deferred_io_cleanup(info);
322 kfree(info->fbdefio);
323 info->fbdefio = NULL;
324 info->fbops->fb_mmap = udl_fb_mmap;
325 }
326
327 pr_warn("released /dev/fb%d user=%d count=%d\n",
328 info->node, user, ufbdev->fb_count);
329
330 return 0;
331}
332
333static struct fb_ops udlfb_ops = {
334 .owner = THIS_MODULE,
335 .fb_check_var = drm_fb_helper_check_var,
336 .fb_set_par = drm_fb_helper_set_par,
337 .fb_fillrect = udl_fb_fillrect,
338 .fb_copyarea = udl_fb_copyarea,
339 .fb_imageblit = udl_fb_imageblit,
340 .fb_pan_display = drm_fb_helper_pan_display,
341 .fb_blank = drm_fb_helper_blank,
342 .fb_setcmap = drm_fb_helper_setcmap,
343 .fb_debug_enter = drm_fb_helper_debug_enter,
344 .fb_debug_leave = drm_fb_helper_debug_leave,
345 .fb_mmap = udl_fb_mmap,
346 .fb_open = udl_fb_open,
347 .fb_release = udl_fb_release,
348};
349
350void udl_crtc_fb_gamma_set(struct drm_crtc *crtc, u16 red, u16 green,
351 u16 blue, int regno)
352{
353}
354
355void udl_crtc_fb_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green,
356 u16 *blue, int regno)
357{
358 *red = 0;
359 *green = 0;
360 *blue = 0;
361}
362
363static int udl_user_framebuffer_dirty(struct drm_framebuffer *fb,
364 struct drm_file *file,
365 unsigned flags, unsigned color,
366 struct drm_clip_rect *clips,
367 unsigned num_clips)
368{
369 struct udl_framebuffer *ufb = to_udl_fb(fb);
370 int i;
371
372 if (!ufb->active_16)
373 return 0;
374
375 for (i = 0; i < num_clips; i++) {
376 udl_handle_damage(ufb, clips[i].x1, clips[i].y1,
377 clips[i].x2 - clips[i].x1,
378 clips[i].y2 - clips[i].y1);
379 }
380 return 0;
381}
382
383static void udl_user_framebuffer_destroy(struct drm_framebuffer *fb)
384{
385 struct udl_framebuffer *ufb = to_udl_fb(fb);
386
387 if (ufb->obj)
388 drm_gem_object_unreference_unlocked(&ufb->obj->base);
389
390 drm_framebuffer_cleanup(fb);
391 kfree(ufb);
392}
393
394static const struct drm_framebuffer_funcs udlfb_funcs = {
395 .destroy = udl_user_framebuffer_destroy,
396 .dirty = udl_user_framebuffer_dirty,
397 .create_handle = NULL,
398};
399
400
401static int
402udl_framebuffer_init(struct drm_device *dev,
403 struct udl_framebuffer *ufb,
404 struct drm_mode_fb_cmd2 *mode_cmd,
405 struct udl_gem_object *obj)
406{
407 int ret;
408
409 ufb->obj = obj;
410 ret = drm_framebuffer_init(dev, &ufb->base, &udlfb_funcs);
411 drm_helper_mode_fill_fb_struct(&ufb->base, mode_cmd);
412 return ret;
413}
414
415
416static int udlfb_create(struct udl_fbdev *ufbdev,
417 struct drm_fb_helper_surface_size *sizes)
418{
419 struct drm_device *dev = ufbdev->helper.dev;
420 struct fb_info *info;
421 struct device *device = &dev->usbdev->dev;
422 struct drm_framebuffer *fb;
423 struct drm_mode_fb_cmd2 mode_cmd;
424 struct udl_gem_object *obj;
425 uint32_t size;
426 int ret = 0;
427
428 if (sizes->surface_bpp == 24)
429 sizes->surface_bpp = 32;
430
431 mode_cmd.width = sizes->surface_width;
432 mode_cmd.height = sizes->surface_height;
433 mode_cmd.pitches[0] = mode_cmd.width * ((sizes->surface_bpp + 7) / 8);
434
435 mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
436 sizes->surface_depth);
437
438 size = mode_cmd.pitches[0] * mode_cmd.height;
439 size = ALIGN(size, PAGE_SIZE);
440
441 obj = udl_gem_alloc_object(dev, size);
442 if (!obj)
443 goto out;
444
445 ret = udl_gem_vmap(obj);
446 if (ret) {
447 DRM_ERROR("failed to vmap fb\n");
448 goto out_gfree;
449 }
450
451 info = framebuffer_alloc(0, device);
452 if (!info) {
453 ret = -ENOMEM;
454 goto out_gfree;
455 }
456 info->par = ufbdev;
457
458 ret = udl_framebuffer_init(dev, &ufbdev->ufb, &mode_cmd, obj);
459 if (ret)
460 goto out_gfree;
461
462 fb = &ufbdev->ufb.base;
463
464 ufbdev->helper.fb = fb;
465 ufbdev->helper.fbdev = info;
466
467 strcpy(info->fix.id, "udldrmfb");
468
469 info->screen_base = ufbdev->ufb.obj->vmapping;
470 info->fix.smem_len = size;
471 info->fix.smem_start = (unsigned long)ufbdev->ufb.obj->vmapping;
472
473 info->flags = FBINFO_DEFAULT | FBINFO_CAN_FORCE_OUTPUT;
474 info->fbops = &udlfb_ops;
475 drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth);
476 drm_fb_helper_fill_var(info, &ufbdev->helper, sizes->fb_width, sizes->fb_height);
477
478 ret = fb_alloc_cmap(&info->cmap, 256, 0);
479 if (ret) {
480 ret = -ENOMEM;
481 goto out_gfree;
482 }
483
484
485 DRM_DEBUG_KMS("allocated %dx%d vmal %p\n",
486 fb->width, fb->height,
487 ufbdev->ufb.obj->vmapping);
488
489 return ret;
490out_gfree:
491 drm_gem_object_unreference(&ufbdev->ufb.obj->base);
492out:
493 return ret;
494}
495
496static int udl_fb_find_or_create_single(struct drm_fb_helper *helper,
497 struct drm_fb_helper_surface_size *sizes)
498{
499 struct udl_fbdev *ufbdev = (struct udl_fbdev *)helper;
500 int new_fb = 0;
501 int ret;
502
503 if (!helper->fb) {
504 ret = udlfb_create(ufbdev, sizes);
505 if (ret)
506 return ret;
507
508 new_fb = 1;
509 }
510 return new_fb;
511}
512
513static struct drm_fb_helper_funcs udl_fb_helper_funcs = {
514 .gamma_set = udl_crtc_fb_gamma_set,
515 .gamma_get = udl_crtc_fb_gamma_get,
516 .fb_probe = udl_fb_find_or_create_single,
517};
518
519static void udl_fbdev_destroy(struct drm_device *dev,
520 struct udl_fbdev *ufbdev)
521{
522 struct fb_info *info;
523 if (ufbdev->helper.fbdev) {
524 info = ufbdev->helper.fbdev;
525 unregister_framebuffer(info);
526 if (info->cmap.len)
527 fb_dealloc_cmap(&info->cmap);
528 framebuffer_release(info);
529 }
530 drm_fb_helper_fini(&ufbdev->helper);
531 drm_framebuffer_cleanup(&ufbdev->ufb.base);
532 drm_gem_object_unreference_unlocked(&ufbdev->ufb.obj->base);
533}
534
535int udl_fbdev_init(struct drm_device *dev)
536{
537 struct udl_device *udl = dev->dev_private;
538 int bpp_sel = fb_bpp;
539 struct udl_fbdev *ufbdev;
540 int ret;
541
542 ufbdev = kzalloc(sizeof(struct udl_fbdev), GFP_KERNEL);
543 if (!ufbdev)
544 return -ENOMEM;
545
546 udl->fbdev = ufbdev;
547 ufbdev->helper.funcs = &udl_fb_helper_funcs;
548
549 ret = drm_fb_helper_init(dev, &ufbdev->helper,
550 1, 1);
551 if (ret) {
552 kfree(ufbdev);
553 return ret;
554
555 }
556
557 drm_fb_helper_single_add_all_connectors(&ufbdev->helper);
558 drm_fb_helper_initial_config(&ufbdev->helper, bpp_sel);
559 return 0;
560}
561
562void udl_fbdev_cleanup(struct drm_device *dev)
563{
564 struct udl_device *udl = dev->dev_private;
565 if (!udl->fbdev)
566 return;
567
568 udl_fbdev_destroy(dev, udl->fbdev);
569 kfree(udl->fbdev);
570 udl->fbdev = NULL;
571}
572
573void udl_fbdev_unplug(struct drm_device *dev)
574{
575 struct udl_device *udl = dev->dev_private;
576 struct udl_fbdev *ufbdev;
577 if (!udl->fbdev)
578 return;
579
580 ufbdev = udl->fbdev;
581 if (ufbdev->helper.fbdev) {
582 struct fb_info *info;
583 info = ufbdev->helper.fbdev;
584 unlink_framebuffer(info);
585 }
586}
587
588struct drm_framebuffer *
589udl_fb_user_fb_create(struct drm_device *dev,
590 struct drm_file *file,
591 struct drm_mode_fb_cmd2 *mode_cmd)
592{
593 struct drm_gem_object *obj;
594 struct udl_framebuffer *ufb;
595 int ret;
596
597 obj = drm_gem_object_lookup(dev, file, mode_cmd->handles[0]);
598 if (obj == NULL)
599 return ERR_PTR(-ENOENT);
600
601 ufb = kzalloc(sizeof(*ufb), GFP_KERNEL);
602 if (ufb == NULL)
603 return ERR_PTR(-ENOMEM);
604
605 ret = udl_framebuffer_init(dev, ufb, mode_cmd, to_udl_bo(obj));
606 if (ret) {
607 kfree(ufb);
608 return ERR_PTR(-EINVAL);
609 }
610 return &ufb->base;
611}