udlfb: Add module_param to allow forcing pixel_limit
[firefly-linux-kernel-4.4.55.git] / drivers / video / udlfb.c
index a19773149bd7edaa7844ff2777ebf771e9fe9013..86c8b256e306f0d1fbf7224f2ecc1c1d79a58451 100644 (file)
@@ -72,6 +72,7 @@ MODULE_DEVICE_TABLE(usb, id_table);
 static bool console = 1; /* Allow fbcon to open framebuffer */
 static bool fb_defio = 1;  /* Detect mmap writes using page faults */
 static bool shadow = 1; /* Optionally disable shadow framebuffer */
+static int pixel_limit; /* Optionally force a pixel resolution limit */
 
 /* dlfb keeps a list of urbs for efficient bulk transfers */
 static void dlfb_urb_completion(struct urb *urb);
@@ -918,10 +919,6 @@ static void dlfb_free(struct kref *kref)
 {
        struct dlfb_data *dev = container_of(kref, struct dlfb_data, kref);
 
-       /* this function will wait for all in-flight urbs to complete */
-       if (dev->urbs.count > 0)
-               dlfb_free_urb_list(dev);
-
        if (dev->backing_buffer)
                vfree(dev->backing_buffer);
 
@@ -940,35 +937,42 @@ static void dlfb_release_urb_work(struct work_struct *work)
        up(&unode->dev->urbs.limit_sem);
 }
 
-static void dlfb_free_framebuffer_work(struct work_struct *work)
+static void dlfb_free_framebuffer(struct dlfb_data *dev)
 {
-       struct dlfb_data *dev = container_of(work, struct dlfb_data,
-                                            free_framebuffer_work.work);
        struct fb_info *info = dev->info;
-       int node = info->node;
 
-       unregister_framebuffer(info);
+       if (info) {
+               int node = info->node;
 
-       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);
+               unregister_framebuffer(info);
 
-       fb_destroy_modelist(&info->modelist);
+               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);
 
-       dev->info = 0;
+               fb_destroy_modelist(&info->modelist);
 
-       /* Assume info structure is freed after this point */
-       framebuffer_release(info);
+               dev->info = NULL;
 
-       pr_warn("fb_info for /dev/fb%d has been freed\n", node);
+               /* Assume info structure is freed after this point */
+               framebuffer_release(info);
+
+               pr_warn("fb_info for /dev/fb%d has been freed\n", node);
+       }
 
        /* ref taken in probe() as part of registering framebfufer */
        kref_put(&dev->kref, dlfb_free);
 }
 
+static void dlfb_free_framebuffer_work(struct work_struct *work)
+{
+       struct dlfb_data *dev = container_of(work, struct dlfb_data,
+                                            free_framebuffer_work.work);
+       dlfb_free_framebuffer(dev);
+}
 /*
  * Assumes caller is holding info->lock mutex (for open and release at least)
  */
@@ -1012,7 +1016,8 @@ static int dlfb_is_valid_mode(struct fb_videomode *mode,
                return 0;
        }
 
-       pr_info("%dx%d valid mode\n", mode->xres, mode->yres);
+       pr_info("%dx%d @ %d Hz valid mode\n", mode->xres, mode->yres,
+               mode->refresh);
 
        return 1;
 }
@@ -1537,7 +1542,7 @@ static int dlfb_parse_vendor_descriptor(struct dlfb_data *dev,
                        u8 length;
                        u16 key;
 
-                       key = *((u16 *) desc);
+                       key = le16_to_cpu(*((u16 *) desc));
                        desc += sizeof(u16);
                        length = *desc;
                        desc++;
@@ -1570,14 +1575,15 @@ success:
        kfree(buf);
        return true;
 }
+
+static void dlfb_init_framebuffer_work(struct work_struct *work);
+
 static int dlfb_usb_probe(struct usb_interface *interface,
                        const struct usb_device_id *id)
 {
        struct usb_device *usbdev;
        struct dlfb_data *dev = 0;
-       struct fb_info *info = 0;
        int retval = -ENOMEM;
-       int i;
 
        /* usb initialization */
 
@@ -1589,9 +1595,7 @@ static int dlfb_usb_probe(struct usb_interface *interface,
                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 free_framebuffer_work */
 
        dev->udev = usbdev;
        dev->gdev = &usbdev->dev; /* our generic struct device * */
@@ -1613,16 +1617,53 @@ static int dlfb_usb_probe(struct usb_interface *interface,
                goto error;
        }
 
+       if (pixel_limit) {
+               pr_warn("DL chip limit of %d overriden"
+                       " by module param to %d\n",
+                       dev->sku_pixel_limit, pixel_limit);
+               dev->sku_pixel_limit = pixel_limit;
+       }
+
+
        if (!dlfb_alloc_urb_list(dev, WRITES_IN_FLIGHT, MAX_TRANSFER)) {
                retval = -ENOMEM;
                pr_err("dlfb_alloc_urb_list failed\n");
                goto error;
        }
 
+       kref_get(&dev->kref); /* matching kref_put in free_framebuffer_work */
+
        /* We don't register a new USB class. Our client interface is fbdev */
 
+       /* Workitem keep things fast & simple during USB enumeration */
+       INIT_DELAYED_WORK(&dev->init_framebuffer_work,
+                         dlfb_init_framebuffer_work);
+       schedule_delayed_work(&dev->init_framebuffer_work, 0);
+
+       return 0;
+
+error:
+       if (dev) {
+
+               kref_put(&dev->kref, dlfb_free); /* ref for framebuffer */
+               kref_put(&dev->kref, dlfb_free); /* last ref from kref_init */
+
+               /* dev has been deallocated. Do not dereference */
+       }
+
+       return retval;
+}
+
+static void dlfb_init_framebuffer_work(struct work_struct *work)
+{
+       struct dlfb_data *dev = container_of(work, struct dlfb_data,
+                                            init_framebuffer_work.work);
+       struct fb_info *info;
+       int retval;
+       int i;
+
        /* allocates framebuffer driver structure, not framebuffer memory */
-       info = framebuffer_alloc(0, &interface->dev);
+       info = framebuffer_alloc(0, dev->gdev);
        if (!info) {
                retval = -ENOMEM;
                pr_err("framebuffer_alloc failed\n");
@@ -1668,15 +1709,13 @@ static int dlfb_usb_probe(struct usb_interface *interface,
        for (i = 0; i < ARRAY_SIZE(fb_device_attrs); i++) {
                retval = device_create_file(info->dev, &fb_device_attrs[i]);
                if (retval) {
-                       pr_err("device_create_file failed %d\n", retval);
-                       goto err_del_attrs;
+                       pr_warn("device_create_file failed %d\n", retval);
                }
        }
 
        retval = device_create_bin_file(info->dev, &edid_attr);
        if (retval) {
-               pr_err("device_create_bin_file failed %d\n", retval);
-               goto err_del_attrs;
+               pr_warn("device_create_bin_file failed %d\n", retval);
        }
 
        pr_info("DisplayLink USB device /dev/fb%d attached. %dx%d resolution."
@@ -1684,38 +1723,10 @@ static int dlfb_usb_probe(struct usb_interface *interface,
                        info->var.xres, info->var.yres,
                        ((dev->backing_buffer) ?
                        info->fix.smem_len * 2 : info->fix.smem_len) >> 10);
-       return 0;
-
-err_del_attrs:
-       for (i -= 1; i >= 0; i--)
-               device_remove_file(info->dev, &fb_device_attrs[i]);
+       return;
 
 error:
-       if (dev) {
-
-               if (info) {
-                       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);
-               }
-
-               if (dev->backing_buffer)
-                       vfree(dev->backing_buffer);
-
-               kref_put(&dev->kref, dlfb_free); /* ref for framebuffer */
-               kref_put(&dev->kref, dlfb_free); /* last ref from kref_init */
-
-               /* dev has been deallocated. Do not dereference */
-       }
-
-       return retval;
+       dlfb_free_framebuffer(dev);
 }
 
 static void dlfb_usb_disconnect(struct usb_interface *interface)
@@ -1735,12 +1746,24 @@ static void dlfb_usb_disconnect(struct usb_interface *interface)
        /* When non-active we'll update virtual framebuffer, but no new urbs */
        atomic_set(&dev->usb_active, 0);
 
-       /* remove udlfb's sysfs interfaces */
-       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) {
+
+               /* remove udlfb's sysfs interfaces */
+               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);
+
+               /* it's safe to uncomment next line if your kernel
+                  doesn't yet have this function exported */
+               unlink_framebuffer(info);
+       }
 
        usb_set_intfdata(interface, NULL);
+       dev->udev = NULL;
+       dev->gdev = NULL;
 
        /* if clients still have us open, will be freed on last close */
        if (dev->fb_count == 0)
@@ -1806,12 +1829,12 @@ static void dlfb_free_urb_list(struct dlfb_data *dev)
        int ret;
        unsigned long flags;
 
-       pr_notice("Waiting for completes and freeing all render urbs\n");
+       pr_notice("Freeing all render urbs\n");
 
        /* keep waiting and freeing, until we've got 'em all */
        while (count--) {
 
-               /* Getting interrupted means a leak, but ok at shutdown*/
+               /* Getting interrupted means a leak, but ok at disconnect */
                ret = down_interruptible(&dev->urbs.limit_sem);
                if (ret)
                        break;
@@ -1833,6 +1856,7 @@ static void dlfb_free_urb_list(struct dlfb_data *dev)
                kfree(node);
        }
 
+       dev->urbs.count = 0;
 }
 
 static int dlfb_alloc_urb_list(struct dlfb_data *dev, int count, size_t size)
@@ -1948,6 +1972,9 @@ MODULE_PARM_DESC(fb_defio, "Page fault detection of mmap writes");
 module_param(shadow, bool, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP);
 MODULE_PARM_DESC(shadow, "Shadow vid mem. Disable to save mem but lose perf");
 
+module_param(pixel_limit, int, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP);
+MODULE_PARM_DESC(pixel_limit, "Force limit on max mode (in x*y pixels)");
+
 MODULE_AUTHOR("Roberto De Ioris <roberto@unbit.it>, "
              "Jaya Kumar <jayakumar.lkml@gmail.com>, "
              "Bernie Thompson <bernie@plugable.com>");