Add mode limits for DL-125/DL-165 on higher res monitors.
Solves problem of black screen when connecting to monitors with
a higher preferred/native resolution then the chip supports.
Uses code already in place to fall back to best match between chip and monitor.
However - won't yet work with DL X Server, which uses EDID, not mode list.
(Will see successful green screen, but X will be visible but messed up)
Done by implementing support for DisplayLink's vendor extentions to firmware
descriptors. We read the max_area characteristic to learn what is the last
mode the chip can support, before screen is blanked out.
Thanks to DisplayLink for supplying info on this.
--- a/udlfb.c
+++ b/udlfb.c
@@ -917,8 +917,13 @@ static int dlfb_is_valid_mode(struct fb_
{
struct dlfb_data *dev = info->par;
- if (mode->xres * mode->yres > dev->sku_pixel_limit)
+ if (mode->xres * mode->yres > dev->sku_pixel_limit) {
+ dl_warn("mode %dx%d beyond chip capabilities\n",
+ mode->xres, mode->yres);
return 0;
+ }
+
+ dl_warn("mode %dx%d valid\n", mode->xres, mode->yres);
return 1;
}
@@ -1057,6 +1062,8 @@ static int dlfb_parse_edid(struct dlfb_d
if (dlfb_is_valid_mode(&info->monspecs.modedb[i], info))
fb_add_videomode(&info->monspecs.modedb[i],
&info->modelist);
+ else /* if we've removed top/best mode */
+ info->monspecs.misc &= ~FB_MISC_1ST_DETAIL;
}
default_vmode = fb_find_best_display(&info->monspecs,
@@ -1091,8 +1098,11 @@ static int dlfb_parse_edid(struct dlfb_d
&info->modelist);
}
- fb_videomode_to_var(var, default_vmode);
- dlfb_var_color_format(var);
+ if (default_vmode != NULL) {
+ fb_videomode_to_var(var, default_vmode);
+ dlfb_var_color_format(var);
+ } else
+ result = -EINVAL;
return result;
}
@@ -1312,6 +1322,70 @@ static int dlfb_select_std_channel(struc
return ret;
}
+static int dlfb_parse_vendor_descriptor(struct dlfb_data *dev,
+ struct usb_device *usbdev)
+{
+ char *desc;
+ char *desc_end;
+
+ u8 total_len = 0;
+
+ desc = kzalloc(MAX_VENDOR_DESCRIPTOR_SIZE, GFP_KERNEL);
+ if (!desc)
+ return false;
+
+ total_len = usb_get_descriptor(usbdev, 0x5f, /* vendor specific */
+ 0, desc, MAX_VENDOR_DESCRIPTOR_SIZE);
+ if (total_len > 5) {
+ dl_info("descriptor length:%x data:%02x %02x %02x %02x %02x " \
+ "%02x %02x %02x %02x %02x %02x\n", total_len, desc[0],
+ desc[1], desc[2], desc[3], desc[4], desc[5], desc[6],
+ desc[7], desc[8], desc[9], desc[10]);
+
+ if ((desc[0] != total_len) || /* descriptor length */
+ (desc[1] != 0x5f) || /* vendor descriptor type */
+ (desc[2] != 0x01) || /* version (2 bytes) */
+ (desc[3] != 0x00) ||
+ (desc[4] != total_len - 2)) /* length after type */
+ goto unrecognized;
+
+ desc_end = desc + total_len;
+ desc += 5; /* the fixed header we've already parsed */
+
+ while (desc < desc_end) {
+ u8 length;
+ u16 key;
+
+ key = *((u16 *) desc);
+ desc += sizeof(u16);
+ length = *desc;
+ desc++;
+
+ switch (key) {
+ case 0x0200: { /* max_area */
+ u32 max_area;
+ max_area = le32_to_cpu(*((u32 *)desc));
+ dl_err("DisplayLink chip limited to %d pixels\n", max_area);
+ dev->sku_pixel_limit = max_area;
+ break;
+ }
+ default:
+ break;
+ }
+ desc += length;
+ }
+ }
+
+ goto success;
+
+unrecognized:
+ /* allow udlfb to load for now even if firmware unrecognized */
+ dl_err("Unrecognized vendor firmware descriptor\n");
+
+success:
+ kfree(desc);
+ return true;
+}
static int dlfb_usb_probe(struct usb_interface *interface,
const struct usb_device_id *id)
@@ -1345,6 +1419,11 @@ static int dlfb_usb_probe(struct usb_int
dev->gdev = &usbdev->dev; /* our generic struct device * */
usb_set_intfdata(interface, dev);
+ if (!dlfb_parse_vendor_descriptor(dev, usbdev)) {
+ dl_err("firmware not recognized. Assuming incompatible device\n");
+ goto error;
+ }
+
if (!dlfb_alloc_urb_list(dev, WRITES_IN_FLIGHT, MAX_TRANSFER)) {
retval = -ENOMEM;
dl_err("dlfb_alloc_urb_list failed\n");
@@ -1369,12 +1448,16 @@ static int dlfb_usb_probe(struct usb_int
var = &info->var;
- /* TODO set limit based on actual SKU detection */
- dev->sku_pixel_limit = 2048 * 1152;
+ /* If we didn't figure this out earlier, set to highest possible */
+ if (dev->sku_pixel_limit == 0)
+ dev->sku_pixel_limit = 2048 * 1152;
INIT_LIST_HEAD(&info->modelist);
- dlfb_parse_edid(dev, var, info);
-
+ retval = dlfb_parse_edid(dev, var, info);
+ if (retval != 0) {
+ dl_err("unable to find mode match between display and adapter\n");
+ goto error;
+ }
/*
* ok, now that we've got the size info, we can alloc our framebuffer.
*/
--- a/udlfb.h
+++ b/udlfb.h
@@ -70,6 +70,8 @@ struct dlfb_data {
#define MAX_TRANSFER (PAGE_SIZE*16 - BULK_SIZE)
#define WRITES_IN_FLIGHT (4)
+#define MAX_VENDOR_DESCRIPTOR_SIZE 256
+
#define GET_URB_TIMEOUT HZ
#define FREE_URB_TIMEOUT (HZ*2)