Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/nab/target...
[pandora-kernel.git] / drivers / video / udlfb.c
index 087fc99..3473e75 100644 (file)
@@ -48,20 +48,30 @@ static const u32 udlfb_info_flags = FBINFO_DEFAULT | FBINFO_READS_FAST |
                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
- * globally on VID. TODO: Probe() needs to detect when we might be running
- * "future" chips, and bail on those, so a compatible driver can match.
+ * There are many DisplayLink-based graphics products, all with unique PIDs.
+ * So we match on DisplayLink's VID + Vendor-Defined Interface Class (0xff)
+ * We also require a match on SubClass (0x00) and Protocol (0x00),
+ * which is compatible with all known USB 2.0 era graphics chips and firmware,
+ * but allows DisplayLink to increment those for any future incompatible chips
  */
 static struct usb_device_id id_table[] = {
-       {.idVendor = 0x17e9, .match_flags = USB_DEVICE_ID_MATCH_VENDOR,},
+       {.idVendor = 0x17e9,
+        .bInterfaceClass = 0xff,
+        .bInterfaceSubClass = 0x00,
+        .bInterfaceProtocol = 0x00,
+        .match_flags = USB_DEVICE_ID_MATCH_VENDOR |
+               USB_DEVICE_ID_MATCH_INT_CLASS |
+               USB_DEVICE_ID_MATCH_INT_SUBCLASS |
+               USB_DEVICE_ID_MATCH_INT_PROTOCOL,
+       },
        {},
 };
 MODULE_DEVICE_TABLE(usb, id_table);
 
 /* module options */
-static int console;   /* Optionally allow fbcon to consume first framebuffer */
-static int fb_defio;  /* Optionally enable experimental fb_defio mmap support */
+static int console = 1; /* Allow fbcon to open framebuffer */
+static int fb_defio = 1;  /* Detect mmap writes using page faults */
+static int shadow = 1; /* Optionally disable shadow framebuffer */
 
 /* dlfb keeps a list of urbs for efficient bulk transfers */
 static void dlfb_urb_completion(struct urb *urb);
@@ -94,17 +104,39 @@ static char *dlfb_vidreg_unlock(char *buf)
 }
 
 /*
- * On/Off for driving the DisplayLink framebuffer to the display
- *  0x00 H and V sync on
- *  0x01 H and V sync off (screen blank but powered)
- *  0x07 DPMS powerdown (requires modeset to come back)
+ * Map FB_BLANK_* to DisplayLink register
+ * DLReg FB_BLANK_*
+ * ----- -----------------------------
+ *  0x00 FB_BLANK_UNBLANK (0)
+ *  0x01 FB_BLANK (1)
+ *  0x03 FB_BLANK_VSYNC_SUSPEND (2)
+ *  0x05 FB_BLANK_HSYNC_SUSPEND (3)
+ *  0x07 FB_BLANK_POWERDOWN (4) Note: requires modeset to come back
  */
-static char *dlfb_enable_hvsync(char *buf, bool enable)
+static char *dlfb_blanking(char *buf, int fb_blank)
 {
-       if (enable)
-               return dlfb_set_register(buf, 0x1F, 0x00);
-       else
-               return dlfb_set_register(buf, 0x1F, 0x07);
+       u8 reg;
+
+       switch (fb_blank) {
+       case FB_BLANK_POWERDOWN:
+               reg = 0x07;
+               break;
+       case FB_BLANK_HSYNC_SUSPEND:
+               reg = 0x05;
+               break;
+       case FB_BLANK_VSYNC_SUSPEND:
+               reg = 0x03;
+               break;
+       case FB_BLANK_NORMAL:
+               reg = 0x01;
+               break;
+       default:
+               reg = 0x00;
+       }
+
+       buf = dlfb_set_register(buf, 0x1F, reg);
+
+       return buf;
 }
 
 static char *dlfb_set_color_depth(char *buf, u8 selection)
@@ -272,13 +304,15 @@ static int dlfb_set_video_mode(struct dlfb_data *dev,
        wrptr = dlfb_set_base8bpp(wrptr, dev->info->fix.smem_len);
 
        wrptr = dlfb_set_vid_cmds(wrptr, var);
-       wrptr = dlfb_enable_hvsync(wrptr, true);
+       wrptr = dlfb_blanking(wrptr, FB_BLANK_UNBLANK);
        wrptr = dlfb_vidreg_unlock(wrptr);
 
        writesize = wrptr - buf;
 
        retval = dlfb_submit_urb(dev, urb, writesize);
 
+       dev->blank_mode = FB_BLANK_UNBLANK;
+
        return retval;
 }
 
@@ -752,14 +786,13 @@ static int dlfb_ops_ioctl(struct fb_info *info, unsigned int cmd,
 {
 
        struct dlfb_data *dev = info->par;
-       struct dloarea *area = NULL;
 
        if (!atomic_read(&dev->usb_active))
                return 0;
 
        /* TODO: Update X server to get this from sysfs instead */
        if (cmd == DLFB_IOCTL_RETURN_EDID) {
-               char *edid = (char *)arg;
+               void __user *edid = (void __user *)arg;
                if (copy_to_user(edid, dev->edid, dev->edid_size))
                        return -EFAULT;
                return 0;
@@ -767,6 +800,11 @@ static int dlfb_ops_ioctl(struct fb_info *info, unsigned int cmd,
 
        /* TODO: Help propose a standard fb.h ioctl to report mmap damage */
        if (cmd == DLFB_IOCTL_REPORT_DAMAGE) {
+               struct dloarea area;
+
+               if (copy_from_user(&area, (void __user *)arg,
+                                 sizeof(struct dloarea)))
+                       return -EFAULT;
 
                /*
                 * If we have a damage-aware client, turn fb_defio "off"
@@ -778,21 +816,19 @@ static int dlfb_ops_ioctl(struct fb_info *info, unsigned int cmd,
                if (info->fbdefio)
                        info->fbdefio->delay = DL_DEFIO_WRITE_DISABLE;
 
-               area = (struct dloarea *)arg;
+               if (area.x < 0)
+                       area.x = 0;
 
-               if (area->x < 0)
-                       area->x = 0;
+               if (area.x > info->var.xres)
+                       area.x = info->var.xres;
 
-               if (area->x > info->var.xres)
-                       area->x = info->var.xres;
+               if (area.y < 0)
+                       area.y = 0;
 
-               if (area->y < 0)
-                       area->y = 0;
+               if (area.y > info->var.yres)
+                       area.y = info->var.yres;
 
-               if (area->y > info->var.yres)
-                       area->y = info->var.yres;
-
-               dlfb_handle_damage(dev, area->x, area->y, area->w, area->h,
+               dlfb_handle_damage(dev, area.x, area.y, area.w, area.h,
                           info->screen_base);
        }
 
@@ -840,7 +876,7 @@ static int dlfb_ops_open(struct fb_info *info, int user)
         * preventing other clients (X) from working properly. Usually
         * not what the user wants. Fail by default with option to enable.
         */
-       if ((user == 0) & (!console))
+       if ((user == 0) && (!console))
                return -EBUSY;
 
        /* If the USB device is gone, we don't accept new opens */
@@ -1039,32 +1075,57 @@ static int dlfb_ops_set_par(struct fb_info *info)
        return result;
 }
 
+/* To fonzi the jukebox (e.g. make blanking changes take effect) */
+static char *dlfb_dummy_render(char *buf)
+{
+       *buf++ = 0xAF;
+       *buf++ = 0x6A; /* copy */
+       *buf++ = 0x00; /* from address*/
+       *buf++ = 0x00;
+       *buf++ = 0x00;
+       *buf++ = 0x01; /* one pixel */
+       *buf++ = 0x00; /* to address */
+       *buf++ = 0x00;
+       *buf++ = 0x00;
+       return buf;
+}
+
 /*
  * In order to come back from full DPMS off, we need to set the mode again
  */
 static int dlfb_ops_blank(int blank_mode, struct fb_info *info)
 {
        struct dlfb_data *dev = info->par;
+       char *bufptr;
+       struct urb *urb;
 
-       if (blank_mode != FB_BLANK_UNBLANK) {
-               char *bufptr;
-               struct urb *urb;
-
-               urb = dlfb_get_urb(dev);
-               if (!urb)
-                       return 0;
+       pr_info("/dev/fb%d FB_BLANK mode %d --> %d\n",
+               info->node, dev->blank_mode, blank_mode);
 
-               bufptr = (char *) urb->transfer_buffer;
-               bufptr = dlfb_vidreg_lock(bufptr);
-               bufptr = dlfb_enable_hvsync(bufptr, false);
-               bufptr = dlfb_vidreg_unlock(bufptr);
+       if ((dev->blank_mode == FB_BLANK_POWERDOWN) &&
+           (blank_mode != FB_BLANK_POWERDOWN)) {
 
-               dlfb_submit_urb(dev, urb, bufptr -
-                               (char *) urb->transfer_buffer);
-       } else {
+               /* returning from powerdown requires a fresh modeset */
                dlfb_set_video_mode(dev, &info->var);
        }
 
+       urb = dlfb_get_urb(dev);
+       if (!urb)
+               return 0;
+
+       bufptr = (char *) urb->transfer_buffer;
+       bufptr = dlfb_vidreg_lock(bufptr);
+       bufptr = dlfb_blanking(bufptr, blank_mode);
+       bufptr = dlfb_vidreg_unlock(bufptr);
+
+       /* seems like a render op is needed to have blank change take effect */
+       bufptr = dlfb_dummy_render(bufptr);
+
+       dlfb_submit_urb(dev, urb, bufptr -
+                       (char *) urb->transfer_buffer);
+
+       dev->blank_mode = blank_mode;
+
        return 0;
 }
 
@@ -1097,7 +1158,7 @@ static int dlfb_realloc_framebuffer(struct dlfb_data *dev, struct fb_info *info)
        int new_len;
        unsigned char *old_fb = info->screen_base;
        unsigned char *new_fb;
-       unsigned char *new_back;
+       unsigned char *new_back = 0;
 
        pr_warn("Reallocating framebuffer. Addresses will change!\n");
 
@@ -1129,7 +1190,8 @@ static int dlfb_realloc_framebuffer(struct dlfb_data *dev, struct fb_info *info)
                 * But with imperfect damage info we may send pixels over USB
                 * that were, in fact, unchanged - wasting limited USB bandwidth
                 */
-               new_back = vzalloc(new_len);
+               if (shadow)
+                       new_back = vzalloc(new_len);
                if (!new_back)
                        pr_info("No shadow/backing buffer allocated\n");
                else {
@@ -1430,21 +1492,30 @@ static int dlfb_select_std_channel(struct dlfb_data *dev)
 }
 
 static int dlfb_parse_vendor_descriptor(struct dlfb_data *dev,
-                                       struct usb_device *usbdev)
+                                       struct usb_interface *interface)
 {
        char *desc;
        char *buf;
        char *desc_end;
 
-       u8 total_len = 0;
+       int total_len = 0;
 
        buf = kzalloc(MAX_VENDOR_DESCRIPTOR_SIZE, GFP_KERNEL);
        if (!buf)
                return false;
        desc = buf;
 
-       total_len = usb_get_descriptor(usbdev, 0x5f, /* vendor specific */
-                                   0, desc, MAX_VENDOR_DESCRIPTOR_SIZE);
+       total_len = usb_get_descriptor(interface_to_usbdev(interface),
+                                       0x5f, /* vendor specific */
+                                       0, desc, MAX_VENDOR_DESCRIPTOR_SIZE);
+
+       /* if not found, look in configuration descriptor */
+       if (total_len < 0) {
+               if (0 == usb_get_extra_descriptor(interface->cur_altsetting,
+                       0x5f, &desc))
+                       total_len = (int) desc[0];
+       }
+
        if (total_len > 5) {
                pr_info("vendor descriptor length:%x data:%02x %02x %02x %02x" \
                        "%02x %02x %02x %02x %02x %02x %02x\n",
@@ -1485,6 +1556,8 @@ static int dlfb_parse_vendor_descriptor(struct dlfb_data *dev,
                        }
                        desc += length;
                }
+       } else {
+               pr_info("vendor descriptor not available (%d)\n", total_len);
        }
 
        goto success;
@@ -1531,10 +1604,11 @@ static int dlfb_usb_probe(struct usb_interface *interface,
                usbdev->descriptor.bcdDevice, dev);
        pr_info("console enable=%d\n", console);
        pr_info("fb_defio enable=%d\n", fb_defio);
+       pr_info("shadow enable=%d\n", shadow);
 
        dev->sku_pixel_limit = 2048 * 1152; /* default to maximum */
 
-       if (!dlfb_parse_vendor_descriptor(dev, usbdev)) {
+       if (!dlfb_parse_vendor_descriptor(dev, interface)) {
                pr_err("firmware not recognized. Assume incompatible device\n");
                goto error;
        }
@@ -1548,7 +1622,7 @@ static int dlfb_usb_probe(struct usb_interface *interface,
        /* 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);
+       info = framebuffer_alloc(0, &interface->dev);
        if (!info) {
                retval = -ENOMEM;
                pr_err("framebuffer_alloc failed\n");
@@ -1883,10 +1957,13 @@ static int dlfb_submit_urb(struct dlfb_data *dev, struct urb *urb, size_t len)
 }
 
 module_param(console, bool, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP);
-MODULE_PARM_DESC(console, "Allow fbcon to consume first framebuffer found");
+MODULE_PARM_DESC(console, "Allow fbcon to open framebuffer");
 
 module_param(fb_defio, bool, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP);
-MODULE_PARM_DESC(fb_defio, "Enable fb_defio mmap support. *Experimental*");
+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_AUTHOR("Roberto De Ioris <roberto@unbit.it>, "
              "Jaya Kumar <jayakumar.lkml@gmail.com>, "