*
* Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it>
* Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com>
+ * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com>
*
* This file is subject to the terms and conditions of the GNU General Public
* License v2. See the file COPYING in the main directory of this archive for
#include "udlfb.h"
-#define DRIVER_VERSION "DisplayLink Framebuffer Driver 0.4.1"
-
static struct fb_fix_screeninfo dlfb_fix = {
- .id = "displaylinkfb",
+ .id = "udlfb",
.type = FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_TRUECOLOR,
.xpanstep = 0,
.accel = FB_ACCEL_NONE,
};
+static const u32 udlfb_info_flags = FBINFO_DEFAULT | FBINFO_READS_FAST |
+#ifdef FBINFO_VIRTFB
+ FBINFO_VIRTFB |
+#endif
+ FBINFO_HWACCEL_IMAGEBLIT | FBINFO_HWACCEL_FILLRECT |
+ FBINFO_HWACCEL_COPYAREA | FBINFO_MISC_ALWAYS_SETPAR;
+
/*
* There are many DisplayLink-based products, all with unique PIDs. We are able
* to support all volume ones (circa 2009) with a single driver, so we match
}
/*
+ * Called by fbdev as last part of unregister_framebuffer() process
+ * No new clients can open connections. Deallocate everything fb_info.
+ */
+static void dlfb_ops_destroy(struct fb_info *info)
+{
+ struct dlfb_data *dev = info->par;
+
+ if (info->cmap.len != 0)
+ fb_dealloc_cmap(&info->cmap);
+ if (info->monspecs.modedb)
+ fb_destroy_modedb(info->monspecs.modedb);
+ if (info->screen_base)
+ vfree(info->screen_base);
+
+ fb_destroy_modelist(&info->modelist);
+
+ framebuffer_release(info);
+
+ /* ref taken before register_framebuffer() for dlfb_data clients */
+ kref_put(&dev->kref, dlfb_delete);
+}
+
+/*
* Check whether a video mode is supported by the DisplayLink chip
* We start from monitor's modes, so don't need to filter that here
*/
var->blue = blue;
}
+static int dlfb_ops_check_var(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ struct fb_videomode mode;
+
+ /* TODO: support dynamically changing framebuffer size */
+ if ((var->xres * var->yres * 2) > info->fix.smem_len)
+ return -EINVAL;
+
+ /* set device-specific elements of var unrelated to mode */
+ dlfb_var_color_format(var);
+
+ fb_var_to_videomode(&mode, var);
+
+ if (!dlfb_is_valid_mode(&mode, info))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int dlfb_ops_set_par(struct fb_info *info)
+{
+ struct dlfb_data *dev = info->par;
+
+ dl_notice("set_par mode %dx%d\n", info->var.xres, info->var.yres);
+
+ return dlfb_set_video_mode(dev, &info->var);
+}
+
static int dlfb_ops_blank(int blank_mode, struct fb_info *info)
{
struct dlfb_data *dev_info = info->par;
}
static struct fb_ops dlfb_ops = {
+ .owner = THIS_MODULE,
.fb_setcolreg = dlfb_ops_setcolreg,
.fb_fillrect = dlfb_ops_fillrect,
.fb_copyarea = dlfb_ops_copyarea,
.fb_ioctl = dlfb_ops_ioctl,
.fb_release = dlfb_ops_release,
.fb_blank = dlfb_ops_blank,
+ .fb_check_var = dlfb_ops_check_var,
+ .fb_set_par = dlfb_ops_set_par,
};
/*
return ret;
}
-static int dlfb_probe(struct usb_interface *interface,
+
+static int dlfb_usb_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
- struct device *mydev;
struct usb_device *usbdev;
struct dlfb_data *dev;
struct fb_info *info;
int videomemorysize;
+ int i;
unsigned char *videomemory;
int retval = -ENOMEM;
struct fb_var_screeninfo *var;
- struct fb_bitfield red = { 11, 5, 0 };
- struct fb_bitfield green = { 5, 6, 0 };
- struct fb_bitfield blue = { 0, 5, 0 };
+ int registered = 0;
+ u16 *pix_framebuffer;
- usbdev = usb_get_dev(interface_to_usbdev(interface));
- mydev = &usbdev->dev;
+ /* usb initialization */
+
+ usbdev = interface_to_usbdev(interface);
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (dev == NULL) {
- dev_err(mydev, "failed alloc of dev struct\n");
- goto err_devalloc;
+ err("dlfb_usb_probe: failed alloc of dev struct\n");
+ goto error;
}
+ /* we need to wait for both usb and fbdev to spin down on disconnect */
+ kref_init(&dev->kref); /* matching kref_put in usb .disconnect fn */
+ kref_get(&dev->kref); /* matching kref_put in .fb_destroy function*/
+
mutex_init(&dev->bulk_mutex);
+
dev->udev = usbdev;
dev->gdev = &usbdev->dev; /* our generic struct device * */
- dev->interface = interface;
usb_set_intfdata(interface, dev);
- dev_info(mydev, "dlfb_probe: setting up DisplayLink device\n");
+ if (!dlfb_alloc_urb_list(dev, WRITES_IN_FLIGHT, MAX_TRANSFER)) {
+ retval = -ENOMEM;
+ dl_err("dlfb_alloc_urb_list failed\n");
+ goto error;
+ }
+
+ mutex_init(&dev->fb_open_lock);
/*
* TODO: replace single 64K buffer with buffer list
*/
dev->buf = kmalloc(BUF_SIZE, GFP_KERNEL);
if (dev->buf == NULL) {
- dev_err(mydev, "unable to allocate memory for dlfb commands\n");
- goto err_usballoc;
+ dl_err("unable to allocate memory for dlfb commands\n");
+ goto error;
}
dev->bufend = dev->buf + BUF_SIZE;
usb_sndbulkpipe(dev->udev, 1), dev->buf, 0,
dlfb_bulk_callback, dev);
- /* allocates framebuffer driver structure, not framebuffer memory */
- info = framebuffer_alloc(0, mydev);
- if (!info)
- goto err_fballoc;
+ /* We don't register a new USB class. Our client interface is fbdev */
+ /* allocates framebuffer driver structure, not framebuffer memory */
+ info = framebuffer_alloc(0, &usbdev->dev);
+ if (!info) {
+ retval = -ENOMEM;
+ dl_err("framebuffer_alloc failed\n");
+ goto error;
+ }
dev->info = info;
info->par = dev;
info->pseudo_palette = dev->pseudo_palette;
+ info->fbops = &dlfb_ops;
var = &info->var;
- retval = dlfb_get_var_from_edid(dev, var);
- if (retval) {
- /* had a problem getting edid. so fallback to 640x480 */
- dev_err(mydev, "Problem %d with EDID.\n", retval);
- var->xres = 640;
- var->yres = 480;
- }
+
+ /* TODO set limit based on actual SKU detection */
+ dev->sku_pixel_limit = 2048 * 1152;
+
+ INIT_LIST_HEAD(&info->modelist);
+ dlfb_parse_edid(dev, var, info);
/*
* ok, now that we've got the size info, we can alloc our framebuffer.
- * We are using 16bpp.
*/
- info->var.bits_per_pixel = 16;
info->fix = dlfb_fix;
info->fix.line_length = var->xres * (var->bits_per_pixel / 8);
videomemorysize = info->fix.line_length * var->yres;
/*
* The big chunk of system memory we use as a virtual framebuffer.
- * Pages don't need to be set RESERVED (non-swap) immediately on 2.6
- * remap_pfn_page() syscall in our mmap and/or defio will handle.
+ * TODO: Handle fbcon cursor code calling blit in interrupt context
*/
videomemory = vmalloc(videomemorysize);
- if (!videomemory)
- goto err_vidmem;
- memset(videomemory, 0, videomemorysize);
+ if (!videomemory) {
+ retval = -ENOMEM;
+ dl_err("Virtual framebuffer alloc failed\n");
+ goto error;
+ }
info->screen_base = videomemory;
info->fix.smem_len = PAGE_ALIGN(videomemorysize);
info->fix.smem_start = (unsigned long) videomemory;
- info->flags =
- FBINFO_DEFAULT | FBINFO_READS_FAST | FBINFO_HWACCEL_IMAGEBLIT |
- FBINFO_HWACCEL_COPYAREA | FBINFO_HWACCEL_FILLRECT;
+ info->flags = udlfb_info_flags;
+
/*
* Second framebuffer copy, mirroring the state of the framebuffer
* But with imperfect damage info we may end up sending pixels over USB
* that were, in fact, unchanged -- wasting limited USB bandwidth
*/
- dev->backing_buffer = vmalloc(dev->screen_size);
+ dev->backing_buffer = vmalloc(videomemorysize);
if (!dev->backing_buffer)
- dev_info(mydev, "No backing buffer allocated!\n");
+ dl_warn("No shadow/backing buffer allcoated\n");
+ else
+ memset(dev->backing_buffer, 0, videomemorysize);
- info->fbops = &dlfb_ops;
+ retval = fb_alloc_cmap(&info->cmap, 256, 0);
+ if (retval < 0) {
+ dl_err("fb_alloc_cmap failed %x\n", retval);
+ goto error;
+ }
- var->vmode = FB_VMODE_NONINTERLACED;
- var->red = red;
- var->green = green;
- var->blue = blue;
+ /* ready to begin using device */
- /*
- * TODO: Enable FB_CONFIG_DEFIO support
+/*
+#ifdef CONFIG_FB_DEFERRED_IO
+ atomic_set(&dev->use_defio, 1);
+#endif
+*/
+ atomic_set(&dev->usb_active, 1);
+ dlfb_select_std_channel(dev);
- info->fbdefio = &dlfb_defio;
- fb_deferred_io_init(info);
+ dlfb_ops_check_var(var, info);
+ dlfb_ops_set_par(info);
- */
+ /* paint greenscreen */
+/*
+ pix_framebuffer = (u16 *) videomemory;
+ for (i = 0; i < videomemorysize / 2; i++)
+ pix_framebuffer[i] = 0x37e6;
- retval = fb_alloc_cmap(&info->cmap, 256, 0);
+ dlfb_handle_damage(dev, 0, 0, info->var.xres, info->var.yres,
+ videomemory);
+*/
+ retval = register_framebuffer(info);
if (retval < 0) {
- dev_err(mydev, "Failed to allocate colormap\n");
- goto err_cmap;
+ dl_err("register_framebuffer failed %d\n", retval);
+ goto error;
}
+ registered = 1;
- dlfb_select_std_channel(dev);
- dlfb_set_video_mode(dev, var);
- /* TODO: dlfb_dpy_update(dev); */
-
- retval = register_framebuffer(info);
- if (retval < 0)
- goto err_regfb;
+ for (i = 0; i < ARRAY_SIZE(fb_device_attrs); i++)
+ device_create_file(info->dev, &fb_device_attrs[i]);
- /* paint "successful" green screen */
- draw_rect(dev, 0, 0, dev->info->var.xres,
- dev->info->var.yres, 0x30, 0xff, 0x30);
+ device_create_bin_file(info->dev, &edid_attr);
- dev_info(mydev, "DisplayLink USB device %d now attached, "
- "using %dK of memory\n", info->node,
- ((dev->backing_buffer) ?
- videomemorysize * 2 : videomemorysize) >> 10);
+ dl_err("DisplayLink USB device /dev/fb%d attached. %dx%d resolution."
+ " Using %dK framebuffer memory\n", info->node,
+ var->xres, var->yres,
+ ((dev->backing_buffer) ?
+ videomemorysize * 2 : videomemorysize) >> 10);
return 0;
-err_regfb:
- fb_dealloc_cmap(&info->cmap);
-err_cmap:
- /* TODO: fb_deferred_io_cleanup(info); */
- vfree(videomemory);
-err_vidmem:
- framebuffer_release(info);
-err_fballoc:
- kfree(dev->buf);
-err_usballoc:
- usb_set_intfdata(interface, NULL);
- usb_put_dev(dev->udev);
- kfree(dev);
-err_devalloc:
+error:
+ if (dev) {
+ if (registered) {
+ unregister_framebuffer(info);
+ dlfb_ops_destroy(info);
+ } else
+ kref_put(&dev->kref, dlfb_delete);
+
+ if (dev->urbs.count > 0)
+ dlfb_free_urb_list(dev);
+ kref_put(&dev->kref, dlfb_delete); /* last ref from kref_init */
+
+ /* dev has been deallocated. Do not dereference */
+ }
+
return retval;
}
-static void dlfb_disconnect(struct usb_interface *interface)
+static void dlfb_usb_disconnect(struct usb_interface *interface)
{
struct dlfb_data *dev;
struct fb_info *info;
+ int i;
dev = usb_get_intfdata(interface);
+ info = dev->info;
+
+ /* when non-active we'll update virtual framebuffer, but no new urbs */
+ atomic_set(&dev->usb_active, 0);
+
usb_set_intfdata(interface, NULL);
- usb_put_dev(dev->udev);
- /*
- * TODO: since, upon usb disconnect(), usb will cancel in-flight urbs
- * and error out any new ones, look at eliminating need for mutex
- */
- mutex_lock(&dev->bulk_mutex);
- dev->interface = NULL;
- info = dev->info;
- mutex_unlock(&dev->bulk_mutex);
+ for (i = 0; i < ARRAY_SIZE(fb_device_attrs); i++)
+ device_remove_file(info->dev, &fb_device_attrs[i]);
+
+ device_remove_bin_file(info->dev, &edid_attr);
+
+ /* this function will wait for all in-flight urbs to complete */
+ dlfb_free_urb_list(dev);
if (info) {
- dev_info(&interface->dev, "Detaching DisplayLink device %d.\n",
- info->node);
+ dl_notice("Detaching /dev/fb%d\n", info->node);
unregister_framebuffer(info);
- fb_dealloc_cmap(&info->cmap);
- /* TODO: fb_deferred_io_cleanup(info); */
- fb_dealloc_cmap(&info->cmap);
- vfree((void __force *)info->screen_base);
- framebuffer_release(info);
+ dlfb_ops_destroy(info);
}
- if (dev->backing_buffer)
- vfree(dev->backing_buffer);
+ /* release reference taken by kref_init in probe() */
+ kref_put(&dev->kref, dlfb_delete);
- kfree(dev);
+ /* consider dlfb_data freed */
+
+ return;
}
static struct usb_driver dlfb_driver = {
.name = "udlfb",
- .probe = dlfb_probe,
- .disconnect = dlfb_disconnect,
+ .probe = dlfb_usb_probe,
+ .disconnect = dlfb_usb_disconnect,
.id_table = id_table,
};
-static int __init dlfb_init(void)
+static int __init dlfb_module_init(void)
{
int res;
return res;
}
-static void __exit dlfb_exit(void)
+static void __exit dlfb_module_exit(void)
{
usb_deregister(&dlfb_driver);
}
-module_init(dlfb_init);
-module_exit(dlfb_exit);
+module_init(dlfb_module_init);
+module_exit(dlfb_module_exit);
static void dlfb_urb_completion(struct urb *urb)
{
}
MODULE_AUTHOR("Roberto De Ioris <roberto@unbit.it>, "
- "Jaya Kumar <jayakumar.lkml@gmail.com>");
-MODULE_DESCRIPTION(DRIVER_VERSION);
+ "Jaya Kumar <jayakumar.lkml@gmail.com>, "
+ "Bernie Thompson <bernie@plugable.com>");
+MODULE_DESCRIPTION("DisplayLink kernel framebuffer driver");
MODULE_LICENSE("GPL");
+