45,14 → 45,15 |
* DOC: fbdev helpers |
* |
* The fb helper functions are useful to provide an fbdev on top of a drm kernel |
* mode setting driver. They can be used mostly independantely from the crtc |
* mode setting driver. They can be used mostly independently from the crtc |
* helper functions used by many drivers to implement the kernel mode setting |
* interfaces. |
* |
* Initialization is done as a three-step process with drm_fb_helper_init(), |
* drm_fb_helper_single_add_all_connectors() and drm_fb_helper_initial_config(). |
* Drivers with fancier requirements than the default beheviour can override the |
* second step with their own code. Teardown is done with drm_fb_helper_fini(). |
* Initialization is done as a four-step process with drm_fb_helper_prepare(), |
* drm_fb_helper_init(), drm_fb_helper_single_add_all_connectors() and |
* drm_fb_helper_initial_config(). Drivers with fancier requirements than the |
* default behaviour can override the third step with their own code. |
* Teardown is done with drm_fb_helper_fini(). |
* |
* At runtime drivers should restore the fbdev console by calling |
* drm_fb_helper_restore_fbdev_mode() from their ->lastclose callback. They |
59,10 → 60,23 |
* should also notify the fb helper code from updates to the output |
* configuration by calling drm_fb_helper_hotplug_event(). For easier |
* integration with the output polling code in drm_crtc_helper.c the modeset |
* code proves a ->output_poll_changed callback. |
* code provides a ->output_poll_changed callback. |
* |
* All other functions exported by the fb helper library can be used to |
* implement the fbdev driver interface by the driver. |
* |
* It is possible, though perhaps somewhat tricky, to implement race-free |
* hotplug detection using the fbdev helpers. The drm_fb_helper_prepare() |
* helper must be called first to initialize the minimum required to make |
* hotplug detection work. Drivers also need to make sure to properly set up |
* the dev->mode_config.funcs member. After calling drm_kms_helper_poll_init() |
* it is safe to enable interrupts and start processing hotplug events. At the |
* same time, drivers should initialize all modeset objects such as CRTCs, |
* encoders and connectors. To finish up the fbdev helper initialization, the |
* drm_fb_helper_init() function is called. To probe for all attached displays |
* and set up an initial configuration using the detected hardware, drivers |
* should call drm_fb_helper_single_add_all_connectors() followed by |
* drm_fb_helper_initial_config(). |
*/ |
|
/** |
105,6 → 119,57 |
} |
EXPORT_SYMBOL(drm_fb_helper_single_add_all_connectors); |
|
int drm_fb_helper_add_one_connector(struct drm_fb_helper *fb_helper, struct drm_connector *connector) |
{ |
struct drm_fb_helper_connector **temp; |
struct drm_fb_helper_connector *fb_helper_connector; |
|
WARN_ON(!mutex_is_locked(&fb_helper->dev->mode_config.mutex)); |
if (fb_helper->connector_count + 1 > fb_helper->connector_info_alloc_count) { |
temp = krealloc(fb_helper->connector_info, sizeof(struct drm_fb_helper_connector) * (fb_helper->connector_count + 1), GFP_KERNEL); |
if (!temp) |
return -ENOMEM; |
|
fb_helper->connector_info_alloc_count = fb_helper->connector_count + 1; |
fb_helper->connector_info = temp; |
} |
|
|
fb_helper_connector = kzalloc(sizeof(struct drm_fb_helper_connector), GFP_KERNEL); |
if (!fb_helper_connector) |
return -ENOMEM; |
|
fb_helper_connector->connector = connector; |
fb_helper->connector_info[fb_helper->connector_count++] = fb_helper_connector; |
return 0; |
} |
EXPORT_SYMBOL(drm_fb_helper_add_one_connector); |
|
int drm_fb_helper_remove_one_connector(struct drm_fb_helper *fb_helper, |
struct drm_connector *connector) |
{ |
struct drm_fb_helper_connector *fb_helper_connector; |
int i, j; |
|
WARN_ON(!mutex_is_locked(&fb_helper->dev->mode_config.mutex)); |
|
for (i = 0; i < fb_helper->connector_count; i++) { |
if (fb_helper->connector_info[i]->connector == connector) |
break; |
} |
|
if (i == fb_helper->connector_count) |
return -EINVAL; |
fb_helper_connector = fb_helper->connector_info[i]; |
|
for (j = i + 1; j < fb_helper->connector_count; j++) { |
fb_helper->connector_info[j - 1] = fb_helper->connector_info[j]; |
} |
fb_helper->connector_count--; |
kfree(fb_helper_connector); |
return 0; |
} |
EXPORT_SYMBOL(drm_fb_helper_remove_one_connector); |
static void drm_fb_helper_save_lut_atomic(struct drm_crtc *crtc, struct drm_fb_helper *helper) |
{ |
uint16_t *r_base, *g_base, *b_base; |
136,6 → 201,55 |
} |
|
|
static bool restore_fbdev_mode(struct drm_fb_helper *fb_helper) |
{ |
struct drm_device *dev = fb_helper->dev; |
struct drm_plane *plane; |
bool error = false; |
int i; |
|
drm_warn_on_modeset_not_all_locked(dev); |
|
list_for_each_entry(plane, &dev->mode_config.plane_list, head) |
if (plane->type != DRM_PLANE_TYPE_PRIMARY) |
drm_plane_force_disable(plane); |
|
for (i = 0; i < fb_helper->crtc_count; i++) { |
struct drm_mode_set *mode_set = &fb_helper->crtc_info[i].mode_set; |
struct drm_crtc *crtc = mode_set->crtc; |
int ret; |
|
if (crtc->funcs->cursor_set) { |
ret = crtc->funcs->cursor_set(crtc, NULL, 0, 0, 0); |
if (ret) |
error = true; |
} |
|
ret = drm_mode_set_config_internal(mode_set); |
if (ret) |
error = true; |
} |
return error; |
} |
/** |
* drm_fb_helper_restore_fbdev_mode_unlocked - restore fbdev configuration |
* @fb_helper: fbcon to restore |
* |
* This should be called from driver's drm ->lastclose callback |
* when implementing an fbcon on top of kms using this helper. This ensures that |
* the user isn't greeted with a black screen when e.g. X dies. |
*/ |
bool drm_fb_helper_restore_fbdev_mode_unlocked(struct drm_fb_helper *fb_helper) |
{ |
struct drm_device *dev = fb_helper->dev; |
bool ret; |
drm_modeset_lock_all(dev); |
ret = restore_fbdev_mode(fb_helper); |
drm_modeset_unlock_all(dev); |
return ret; |
} |
EXPORT_SYMBOL(drm_fb_helper_restore_fbdev_mode_unlocked); |
|
static bool drm_fb_helper_is_bound(struct drm_fb_helper *fb_helper) |
{ |
struct drm_device *dev = fb_helper->dev; |
143,9 → 257,9 |
int bound = 0, crtcs_bound = 0; |
|
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { |
if (crtc->fb) |
if (crtc->primary->fb) |
crtcs_bound++; |
if (crtc->fb == fb_helper->fb) |
if (crtc->primary->fb == fb_helper->fb) |
bound++; |
} |
|
268,6 → 382,24 |
} |
|
/** |
* drm_fb_helper_prepare - setup a drm_fb_helper structure |
* @dev: DRM device |
* @helper: driver-allocated fbdev helper structure to set up |
* @funcs: pointer to structure of functions associate with this helper |
* |
* Sets up the bare minimum to make the framebuffer helper usable. This is |
* useful to implement race-free initialization of the polling helpers. |
*/ |
void drm_fb_helper_prepare(struct drm_device *dev, struct drm_fb_helper *helper, |
const struct drm_fb_helper_funcs *funcs) |
{ |
INIT_LIST_HEAD(&helper->kernel_fb_list); |
helper->funcs = funcs; |
helper->dev = dev; |
} |
EXPORT_SYMBOL(drm_fb_helper_prepare); |
|
/** |
* drm_fb_helper_init - initialize a drm_fb_helper structure |
* @dev: drm device |
* @fb_helper: driver-allocated fbdev helper structure to initialize |
279,8 → 411,7 |
* nor register the fbdev. This is only done in drm_fb_helper_initial_config() |
* to allow driver writes more control over the exact init sequence. |
* |
* Drivers must set fb_helper->funcs before calling |
* drm_fb_helper_initial_config(). |
* Drivers must call drm_fb_helper_prepare() before calling this function. |
* |
* RETURNS: |
* Zero if everything went ok, nonzero otherwise. |
292,10 → 423,9 |
struct drm_crtc *crtc; |
int i; |
|
fb_helper->dev = dev; |
if (!max_conn_count) |
return -EINVAL; |
|
INIT_LIST_HEAD(&fb_helper->kernel_fb_list); |
|
fb_helper->crtc_info = kcalloc(crtc_count, sizeof(struct drm_fb_helper_crtc), GFP_KERNEL); |
if (!fb_helper->crtc_info) |
return -ENOMEM; |
306,6 → 436,7 |
kfree(fb_helper->crtc_info); |
return -ENOMEM; |
} |
fb_helper->connector_info_alloc_count = dev->mode_config.num_connector; |
fb_helper->connector_count = 0; |
|
for (i = 0; i < crtc_count; i++) { |
566,10 → 697,7 |
int drm_fb_helper_set_par(struct fb_info *info) |
{ |
struct drm_fb_helper *fb_helper = info->par; |
struct drm_device *dev = fb_helper->dev; |
struct fb_var_screeninfo *var = &info->var; |
int ret; |
int i; |
|
if (var->pixclock != 0) { |
DRM_ERROR("PIXEL CLOCK SET\n"); |
576,15 → 704,7 |
return -EINVAL; |
} |
|
drm_modeset_lock_all(dev); |
for (i = 0; i < fb_helper->crtc_count; i++) { |
ret = drm_mode_set_config_internal(&fb_helper->crtc_info[i].mode_set); |
if (ret) { |
drm_modeset_unlock_all(dev); |
return ret; |
} |
} |
drm_modeset_unlock_all(dev); |
drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper); |
|
if (fb_helper->delayed_hotplug) { |
fb_helper->delayed_hotplug = false; |
770,7 → 890,6 |
info->fix.ypanstep = 1; /* doing it in hw */ |
info->fix.ywrapstep = 0; |
info->fix.accel = FB_ACCEL_NONE; |
info->fix.type_aux = 0; |
|
info->fix.line_length = pitch; |
return; |
881,13 → 1000,13 |
return count; |
} |
|
static struct drm_display_mode *drm_has_preferred_mode(struct drm_fb_helper_connector *fb_connector, int width, int height) |
struct drm_display_mode *drm_has_preferred_mode(struct drm_fb_helper_connector *fb_connector, int width, int height) |
{ |
struct drm_display_mode *mode; |
|
list_for_each_entry(mode, &fb_connector->connector->modes, head) { |
if (drm_mode_width(mode) > width || |
drm_mode_height(mode) > height) |
if (mode->hdisplay > width || |
mode->vdisplay > height) |
continue; |
if (mode->type & DRM_MODE_TYPE_PREFERRED) |
return mode; |
894,6 → 1013,7 |
} |
return NULL; |
} |
EXPORT_SYMBOL(drm_has_preferred_mode); |
|
static bool drm_has_cmdline_mode(struct drm_fb_helper_connector *fb_connector) |
{ |
902,11 → 1022,12 |
return cmdline_mode->specified; |
} |
|
static struct drm_display_mode *drm_pick_cmdline_mode(struct drm_fb_helper_connector *fb_helper_conn, |
struct drm_display_mode *drm_pick_cmdline_mode(struct drm_fb_helper_connector *fb_helper_conn, |
int width, int height) |
{ |
struct drm_cmdline_mode *cmdline_mode; |
struct drm_display_mode *mode = NULL; |
bool prefer_non_interlace; |
|
return NULL; |
|
920,6 → 1041,8 |
if (cmdline_mode->rb || cmdline_mode->margins) |
goto create_mode; |
|
prefer_non_interlace = !cmdline_mode->interlace; |
again: |
list_for_each_entry(mode, &fb_helper_conn->connector->modes, head) { |
/* check width/height */ |
if (mode->hdisplay != cmdline_mode->xres || |
934,10 → 1057,18 |
if (cmdline_mode->interlace) { |
if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) |
continue; |
} else if (prefer_non_interlace) { |
if (mode->flags & DRM_MODE_FLAG_INTERLACE) |
continue; |
} |
return mode; |
} |
|
if (prefer_non_interlace) { |
prefer_non_interlace = false; |
goto again; |
} |
|
create_mode: |
mode = drm_mode_create_from_cmdline_mode(fb_helper_conn->connector->dev, |
cmdline_mode); |
944,6 → 1075,7 |
list_add(&mode->head, &fb_helper_conn->connector->modes); |
return mode; |
} |
EXPORT_SYMBOL(drm_pick_cmdline_mode); |
|
static bool drm_connector_enabled(struct drm_connector *connector, bool strict) |
{ |
1286,9 → 1418,11 |
|
// drm_fb_helper_parse_command_line(fb_helper); |
|
mutex_lock(&dev->mode_config.mutex); |
count = drm_fb_helper_probe_connector_modes(fb_helper, |
dev->mode_config.max_width, |
dev->mode_config.max_height); |
mutex_unlock(&dev->mode_config.mutex); |
/* |
* we shouldn't end up with no modes here. |
*/ |
1314,8 → 1448,10 |
* either the output polling work or a work item launched from the driver's |
* hotplug interrupt). |
* |
* Note that the driver must ensure that this is only called _after_ the fb has |
* been fully set up, i.e. after the call to drm_fb_helper_initial_config. |
* Note that drivers may call this even before calling |
* drm_fb_helper_initial_config but only aftert drm_fb_helper_init. This allows |
* for a race-free fbcon setup and will make sure that the fbdev emulation will |
* not miss any hotplug events. |
* |
* RETURNS: |
* 0 on success and a non-zero error code otherwise. |
1325,11 → 1461,8 |
struct drm_device *dev = fb_helper->dev; |
u32 max_width, max_height; |
|
if (!fb_helper->fb) |
return 0; |
|
mutex_lock(&fb_helper->dev->mode_config.mutex); |
if (!drm_fb_helper_is_bound(fb_helper)) { |
if (!fb_helper->fb || !drm_fb_helper_is_bound(fb_helper)) { |
fb_helper->delayed_hotplug = true; |
mutex_unlock(&fb_helper->dev->mode_config.mutex); |
return 0; |