diff options
Diffstat (limited to 'drivers/gpu/drm/drm_fb_helper.c')
-rw-r--r-- | drivers/gpu/drm/drm_fb_helper.c | 469 |
1 files changed, 407 insertions, 62 deletions
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index 99487237111..055b5be7887 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -747,32 +747,30 @@ EXPORT_SYMBOL(drm_fb_helper_pan_display); int drm_fb_helper_single_fb_probe(struct drm_device *dev, int preferred_bpp, - int (*fb_create)(struct drm_device *dev, - uint32_t fb_width, - uint32_t fb_height, - uint32_t surface_width, - uint32_t surface_height, - uint32_t surface_depth, - uint32_t surface_bpp, - struct drm_framebuffer **fb_ptr)) + int (*fb_find_or_create)(struct drm_device *dev, + struct drm_fb_helper_surface_size *sizes, + struct drm_fb_helper **fb_ptr)) { struct drm_crtc *crtc; struct drm_connector *connector; - unsigned int fb_width = (unsigned)-1, fb_height = (unsigned)-1; - unsigned int surface_width = 0, surface_height = 0; int new_fb = 0; int crtc_count = 0; int ret, i, conn_count = 0; struct fb_info *info; - struct drm_framebuffer *fb; struct drm_mode_set *modeset = NULL; struct drm_fb_helper *fb_helper; - uint32_t surface_depth = 24, surface_bpp = 32; + struct drm_fb_helper_surface_size sizes; + + memset(&sizes, 0, sizeof(struct drm_fb_helper_surface_size)); + sizes.surface_depth = 24; + sizes.surface_bpp = 32; + sizes.fb_width = (unsigned)-1; + sizes.fb_height = (unsigned)-1; /* if driver picks 8 or 16 by default use that for both depth/bpp */ - if (preferred_bpp != surface_bpp) { - surface_depth = surface_bpp = preferred_bpp; + if (preferred_bpp != sizes.surface_bpp) { + sizes.surface_depth = sizes.surface_bpp = preferred_bpp; } /* first up get a count of crtcs now in use and new min/maxes width/heights */ list_for_each_entry(connector, &dev->mode_config.connector_list, head) { @@ -788,21 +786,21 @@ int drm_fb_helper_single_fb_probe(struct drm_device *dev, if (cmdline_mode->bpp_specified) { switch (cmdline_mode->bpp) { case 8: - surface_depth = surface_bpp = 8; + sizes.surface_depth = sizes.surface_bpp = 8; break; case 15: - surface_depth = 15; - surface_bpp = 16; + sizes.surface_depth = 15; + sizes.surface_bpp = 16; break; case 16: - surface_depth = surface_bpp = 16; + sizes.surface_depth = sizes.surface_bpp = 16; break; case 24: - surface_depth = surface_bpp = 24; + sizes.surface_depth = sizes.surface_bpp = 24; break; case 32: - surface_depth = 24; - surface_bpp = 32; + sizes.surface_depth = 24; + sizes.surface_bpp = 32; break; } break; @@ -812,59 +810,41 @@ int drm_fb_helper_single_fb_probe(struct drm_device *dev, list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { if (drm_helper_crtc_in_use(crtc)) { if (crtc->desired_mode) { - if (crtc->desired_mode->hdisplay < fb_width) - fb_width = crtc->desired_mode->hdisplay; + if (crtc->desired_mode->hdisplay < sizes.fb_width) + sizes.fb_width = crtc->desired_mode->hdisplay; - if (crtc->desired_mode->vdisplay < fb_height) - fb_height = crtc->desired_mode->vdisplay; + if (crtc->desired_mode->vdisplay < sizes.fb_height) + sizes.fb_height = crtc->desired_mode->vdisplay; - if (crtc->desired_mode->hdisplay > surface_width) - surface_width = crtc->desired_mode->hdisplay; + if (crtc->desired_mode->hdisplay > sizes.surface_width) + sizes.surface_width = crtc->desired_mode->hdisplay; - if (crtc->desired_mode->vdisplay > surface_height) - surface_height = crtc->desired_mode->vdisplay; + if (crtc->desired_mode->vdisplay > sizes.surface_height) + sizes.surface_height = crtc->desired_mode->vdisplay; } crtc_count++; } } - if (crtc_count == 0 || fb_width == -1 || fb_height == -1) { + if (crtc_count == 0 || sizes.fb_width == -1 || sizes.fb_height == -1) { /* hmm everyone went away - assume VGA cable just fell out and will come back later. */ return 0; } - /* do we have an fb already? */ - if (list_empty(&dev->mode_config.fb_kernel_list)) { - ret = (*fb_create)(dev, fb_width, fb_height, surface_width, - surface_height, surface_depth, surface_bpp, - &fb); - if (ret) - return -EINVAL; - new_fb = 1; - } else { - fb = list_first_entry(&dev->mode_config.fb_kernel_list, - struct drm_framebuffer, filp_head); - - /* if someone hotplugs something bigger than we have already allocated, we are pwned. - As really we can't resize an fbdev that is in the wild currently due to fbdev - not really being designed for the lower layers moving stuff around under it. - - so in the grand style of things - punt. */ - if ((fb->width < surface_width) || - (fb->height < surface_height)) { - DRM_ERROR("Framebuffer not large enough to scale console onto.\n"); - return -EINVAL; - } - } + /* push down into drivers */ + new_fb = (*fb_find_or_create)(dev, &sizes, + &fb_helper); + if (new_fb < 0) + return new_fb; - info = fb->fbdev; - fb_helper = info->par; + info = fb_helper->fbdev; crtc_count = 0; /* okay we need to setup new connector sets in the crtcs */ list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { modeset = &fb_helper->crtc_info[crtc_count].mode_set; - modeset->fb = fb; + modeset->fb = fb_helper->fb; conn_count = 0; list_for_each_entry(connector, &dev->mode_config.connector_list, head) { if (connector->encoder) @@ -891,7 +871,6 @@ int drm_fb_helper_single_fb_probe(struct drm_device *dev, } } fb_helper->crtc_count = crtc_count; - fb_helper->fb = fb; if (new_fb) { info->var.pixclock = 0; @@ -902,11 +881,13 @@ int drm_fb_helper_single_fb_probe(struct drm_device *dev, fb_dealloc_cmap(&info->cmap); return -EINVAL; } + + printk(KERN_INFO "fb%d: %s frame buffer device\n", info->node, + info->fix.id); + } else { drm_fb_helper_set_par(info); } - printk(KERN_INFO "fb%d: %s frame buffer device\n", info->node, - info->fix.id); /* Switch back to kernel console on panic */ /* multi card linked list maybe */ @@ -916,7 +897,9 @@ int drm_fb_helper_single_fb_probe(struct drm_device *dev, &paniced); register_sysrq_key('v', &sysrq_drm_fb_helper_restore_op); } - list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list); + if (new_fb) + list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list); + return 0; } EXPORT_SYMBOL(drm_fb_helper_single_fb_probe); @@ -931,7 +914,7 @@ void drm_fb_helper_free(struct drm_fb_helper *helper) unregister_sysrq_key('v', &sysrq_drm_fb_helper_restore_op); } drm_fb_helper_crtc_free(helper); - fb_dealloc_cmap(&helper->fb->fbdev->cmap); + fb_dealloc_cmap(&helper->fbdev->cmap); } EXPORT_SYMBOL(drm_fb_helper_free); @@ -953,10 +936,11 @@ void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch, } EXPORT_SYMBOL(drm_fb_helper_fill_fix); -void drm_fb_helper_fill_var(struct fb_info *info, struct drm_framebuffer *fb, +void drm_fb_helper_fill_var(struct fb_info *info, struct drm_fb_helper *fb_helper, uint32_t fb_width, uint32_t fb_height) { - info->pseudo_palette = fb->pseudo_palette; + struct drm_framebuffer *fb = fb_helper->fb; + info->pseudo_palette = fb_helper->pseudo_palette; info->var.xres_virtual = fb->width; info->var.yres_virtual = fb->height; info->var.bits_per_pixel = fb->bits_per_pixel; @@ -1024,3 +1008,364 @@ void drm_fb_helper_fill_var(struct fb_info *info, struct drm_framebuffer *fb, info->var.yres = fb_height; } EXPORT_SYMBOL(drm_fb_helper_fill_var); + +static int drm_helper_probe_connector_modes(struct drm_device *dev, uint32_t maxX, + uint32_t maxY) +{ + struct drm_connector *connector; + int count = 0; + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + count += connector->funcs->fill_modes(connector, maxX, maxY); + } + + return count; +} + +static struct drm_display_mode *drm_has_preferred_mode(struct drm_connector *connector, int width, int height) +{ + struct drm_display_mode *mode; + + list_for_each_entry(mode, &connector->modes, head) { + if (drm_mode_width(mode) > width || + drm_mode_height(mode) > height) + continue; + if (mode->type & DRM_MODE_TYPE_PREFERRED) + return mode; + } + return NULL; +} + +static bool drm_has_cmdline_mode(struct drm_connector *connector) +{ + struct drm_fb_helper_connector *fb_help_conn = connector->fb_helper_private; + struct drm_fb_helper_cmdline_mode *cmdline_mode; + + if (!fb_help_conn) + return false; + + cmdline_mode = &fb_help_conn->cmdline_mode; + return cmdline_mode->specified; +} + +static struct drm_display_mode *drm_pick_cmdline_mode(struct drm_connector *connector, int width, int height) +{ + struct drm_fb_helper_connector *fb_help_conn = connector->fb_helper_private; + struct drm_fb_helper_cmdline_mode *cmdline_mode; + struct drm_display_mode *mode = NULL; + + if (!fb_help_conn) + return mode; + + cmdline_mode = &fb_help_conn->cmdline_mode; + if (cmdline_mode->specified == false) + return mode; + + /* attempt to find a matching mode in the list of modes + * we have gotten so far, if not add a CVT mode that conforms + */ + if (cmdline_mode->rb || cmdline_mode->margins) + goto create_mode; + + list_for_each_entry(mode, &connector->modes, head) { + /* check width/height */ + if (mode->hdisplay != cmdline_mode->xres || + mode->vdisplay != cmdline_mode->yres) + continue; + + if (cmdline_mode->refresh_specified) { + if (mode->vrefresh != cmdline_mode->refresh) + continue; + } + + if (cmdline_mode->interlace) { + if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) + continue; + } + return mode; + } + +create_mode: + mode = drm_cvt_mode(connector->dev, cmdline_mode->xres, + cmdline_mode->yres, + cmdline_mode->refresh_specified ? cmdline_mode->refresh : 60, + cmdline_mode->rb, cmdline_mode->interlace, + cmdline_mode->margins); + drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V); + list_add(&mode->head, &connector->modes); + return mode; +} + +static bool drm_connector_enabled(struct drm_connector *connector, bool strict) +{ + bool enable; + + if (strict) { + enable = connector->status == connector_status_connected; + } else { + enable = connector->status != connector_status_disconnected; + } + return enable; +} + +static void drm_enable_connectors(struct drm_device *dev, bool *enabled) +{ + bool any_enabled = false; + struct drm_connector *connector; + int i = 0; + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + enabled[i] = drm_connector_enabled(connector, true); + DRM_DEBUG_KMS("connector %d enabled? %s\n", connector->base.id, + enabled[i] ? "yes" : "no"); + any_enabled |= enabled[i]; + i++; + } + + if (any_enabled) + return; + + i = 0; + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + enabled[i] = drm_connector_enabled(connector, false); + i++; + } +} + +static bool drm_target_preferred(struct drm_device *dev, + struct drm_display_mode **modes, + bool *enabled, int width, int height) +{ + struct drm_connector *connector; + int i = 0; + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + + if (enabled[i] == false) { + i++; + continue; + } + + DRM_DEBUG_KMS("looking for cmdline mode on connector %d\n", + connector->base.id); + + /* got for command line mode first */ + modes[i] = drm_pick_cmdline_mode(connector, width, height); + if (!modes[i]) { + DRM_DEBUG_KMS("looking for preferred mode on connector %d\n", + connector->base.id); + modes[i] = drm_has_preferred_mode(connector, width, height); + } + /* No preferred modes, pick one off the list */ + if (!modes[i] && !list_empty(&connector->modes)) { + list_for_each_entry(modes[i], &connector->modes, head) + break; + } + DRM_DEBUG_KMS("found mode %s\n", modes[i] ? modes[i]->name : + "none"); + i++; + } + return true; +} + +static int drm_pick_crtcs(struct drm_device *dev, + struct drm_crtc **best_crtcs, + struct drm_display_mode **modes, + int n, int width, int height) +{ + int c, o; + struct drm_connector *connector; + struct drm_connector_helper_funcs *connector_funcs; + struct drm_encoder *encoder; + struct drm_crtc *best_crtc; + int my_score, best_score, score; + struct drm_crtc **crtcs, *crtc; + + if (n == dev->mode_config.num_connector) + return 0; + c = 0; + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (c == n) + break; + c++; + } + + best_crtcs[n] = NULL; + best_crtc = NULL; + best_score = drm_pick_crtcs(dev, best_crtcs, modes, n+1, width, height); + if (modes[n] == NULL) + return best_score; + + crtcs = kmalloc(dev->mode_config.num_connector * + sizeof(struct drm_crtc *), GFP_KERNEL); + if (!crtcs) + return best_score; + + my_score = 1; + if (connector->status == connector_status_connected) + my_score++; + if (drm_has_cmdline_mode(connector)) + my_score++; + if (drm_has_preferred_mode(connector, width, height)) + my_score++; + + connector_funcs = connector->helper_private; + encoder = connector_funcs->best_encoder(connector); + if (!encoder) + goto out; + + connector->encoder = encoder; + + /* select a crtc for this connector and then attempt to configure + remaining connectors */ + c = 0; + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + + if ((encoder->possible_crtcs & (1 << c)) == 0) { + c++; + continue; + } + + for (o = 0; o < n; o++) + if (best_crtcs[o] == crtc) + break; + + if (o < n) { + /* ignore cloning for now */ + c++; + continue; + } + + crtcs[n] = crtc; + memcpy(crtcs, best_crtcs, n * sizeof(struct drm_crtc *)); + score = my_score + drm_pick_crtcs(dev, crtcs, modes, n + 1, + width, height); + if (score > best_score) { + best_crtc = crtc; + best_score = score; + memcpy(best_crtcs, crtcs, + dev->mode_config.num_connector * + sizeof(struct drm_crtc *)); + } + c++; + } +out: + kfree(crtcs); + return best_score; +} + +static void drm_setup_crtcs(struct drm_device *dev) +{ + struct drm_crtc **crtcs; + struct drm_display_mode **modes; + struct drm_encoder *encoder; + struct drm_connector *connector; + bool *enabled; + int width, height; + int i, ret; + + DRM_DEBUG_KMS("\n"); + + width = dev->mode_config.max_width; + height = dev->mode_config.max_height; + + /* clean out all the encoder/crtc combos */ + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + encoder->crtc = NULL; + } + + crtcs = kcalloc(dev->mode_config.num_connector, + sizeof(struct drm_crtc *), GFP_KERNEL); + modes = kcalloc(dev->mode_config.num_connector, + sizeof(struct drm_display_mode *), GFP_KERNEL); + enabled = kcalloc(dev->mode_config.num_connector, + sizeof(bool), GFP_KERNEL); + + drm_enable_connectors(dev, enabled); + + ret = drm_target_preferred(dev, modes, enabled, width, height); + if (!ret) + DRM_ERROR("Unable to find initial modes\n"); + + DRM_DEBUG_KMS("picking CRTCs for %dx%d config\n", width, height); + + drm_pick_crtcs(dev, crtcs, modes, 0, width, height); + + i = 0; + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + struct drm_display_mode *mode = modes[i]; + struct drm_crtc *crtc = crtcs[i]; + + if (connector->encoder == NULL) { + i++; + continue; + } + + if (mode && crtc) { + DRM_DEBUG_KMS("desired mode %s set on crtc %d\n", + mode->name, crtc->base.id); + crtc->desired_mode = mode; + connector->encoder->crtc = crtc; + } else { + connector->encoder->crtc = NULL; + connector->encoder = NULL; + } + i++; + } + + kfree(crtcs); + kfree(modes); + kfree(enabled); +} + +/** + * drm_helper_initial_config - setup a sane initial connector configuration + * @dev: DRM device + * + * LOCKING: + * Called at init time, must take mode config lock. + * + * Scan the CRTCs and connectors and try to put together an initial setup. + * At the moment, this is a cloned configuration across all heads with + * a new framebuffer object as the backing store. + * + * RETURNS: + * Zero if everything went ok, nonzero otherwise. + */ +bool drm_helper_initial_config(struct drm_device *dev) +{ + int count = 0; + + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(dev); + + drm_fb_helper_parse_command_line(dev); + + count = drm_helper_probe_connector_modes(dev, + dev->mode_config.max_width, + dev->mode_config.max_height); + + /* + * we shouldn't end up with no modes here. + */ + if (count == 0) + printk(KERN_INFO "No connectors reported connected with modes\n"); + + drm_setup_crtcs(dev); + + return 0; +} +EXPORT_SYMBOL(drm_helper_initial_config); + +bool drm_helper_fb_hotplug_event(struct drm_device *dev) +{ + DRM_DEBUG_KMS("\n"); + + drm_helper_probe_connector_modes(dev, dev->mode_config.max_width, + dev->mode_config.max_height); + + drm_setup_crtcs(dev); + + return true; +} +EXPORT_SYMBOL(drm_helper_fb_hotplug_event); |