29,6 → 29,7 |
* Jesse Barnes <jesse.barnes@intel.com> |
*/ |
|
#include <linux/kernel.h> |
#include <linux/export.h> |
#include <linux/moduleparam.h> |
|
72,160 → 73,16 |
} |
EXPORT_SYMBOL(drm_helper_move_panel_connectors_to_head); |
|
static bool drm_kms_helper_poll = true; |
module_param_named(poll, drm_kms_helper_poll, bool, 0600); |
|
static void drm_mode_validate_flag(struct drm_connector *connector, |
int flags) |
{ |
struct drm_display_mode *mode; |
|
if (flags == (DRM_MODE_FLAG_DBLSCAN | DRM_MODE_FLAG_INTERLACE | |
DRM_MODE_FLAG_3D_MASK)) |
return; |
|
list_for_each_entry(mode, &connector->modes, head) { |
if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && |
!(flags & DRM_MODE_FLAG_INTERLACE)) |
mode->status = MODE_NO_INTERLACE; |
if ((mode->flags & DRM_MODE_FLAG_DBLSCAN) && |
!(flags & DRM_MODE_FLAG_DBLSCAN)) |
mode->status = MODE_NO_DBLESCAN; |
if ((mode->flags & DRM_MODE_FLAG_3D_MASK) && |
!(flags & DRM_MODE_FLAG_3D_MASK)) |
mode->status = MODE_NO_STEREO; |
} |
|
return; |
} |
|
/** |
* drm_helper_probe_single_connector_modes - get complete set of display modes |
* @connector: connector to probe |
* @maxX: max width for modes |
* @maxY: max height for modes |
* |
* LOCKING: |
* Caller must hold mode config lock. |
* |
* Based on the helper callbacks implemented by @connector try to detect all |
* valid modes. Modes will first be added to the connector's probed_modes list, |
* then culled (based on validity and the @maxX, @maxY parameters) and put into |
* the normal modes list. |
* |
* Intended to be use as a generic implementation of the ->fill_modes() |
* @connector vfunc for drivers that use the crtc helpers for output mode |
* filtering and detection. |
* |
* RETURNS: |
* Number of modes found on @connector. |
*/ |
int drm_helper_probe_single_connector_modes(struct drm_connector *connector, |
uint32_t maxX, uint32_t maxY) |
{ |
struct drm_device *dev = connector->dev; |
struct drm_display_mode *mode; |
struct drm_connector_helper_funcs *connector_funcs = |
connector->helper_private; |
int count = 0; |
int mode_flags = 0; |
bool verbose_prune = true; |
|
DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id, |
drm_get_connector_name(connector)); |
/* set all modes to the unverified state */ |
list_for_each_entry(mode, &connector->modes, head) |
mode->status = MODE_UNVERIFIED; |
|
if (connector->force) { |
if (connector->force == DRM_FORCE_ON) |
connector->status = connector_status_connected; |
else |
connector->status = connector_status_disconnected; |
if (connector->funcs->force) |
connector->funcs->force(connector); |
} else { |
connector->status = connector->funcs->detect(connector, true); |
} |
|
/* Re-enable polling in case the global poll config changed. */ |
if (drm_kms_helper_poll != dev->mode_config.poll_running) |
drm_kms_helper_poll_enable(dev); |
|
dev->mode_config.poll_running = drm_kms_helper_poll; |
|
if (connector->status == connector_status_disconnected) { |
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] disconnected\n", |
connector->base.id, drm_get_connector_name(connector)); |
drm_mode_connector_update_edid_property(connector, NULL); |
verbose_prune = false; |
goto prune; |
} |
|
#ifdef CONFIG_DRM_LOAD_EDID_FIRMWARE |
count = drm_load_edid_firmware(connector); |
if (count == 0) |
#endif |
count = (*connector_funcs->get_modes)(connector); |
|
if (count == 0 && connector->status == connector_status_connected) |
count = drm_add_modes_noedid(connector, 1024, 768); |
if (count == 0) |
goto prune; |
|
drm_mode_connector_list_update(connector); |
|
if (maxX && maxY) |
drm_mode_validate_size(dev, &connector->modes, maxX, |
maxY, 0); |
|
if (connector->interlace_allowed) |
mode_flags |= DRM_MODE_FLAG_INTERLACE; |
if (connector->doublescan_allowed) |
mode_flags |= DRM_MODE_FLAG_DBLSCAN; |
if (connector->stereo_allowed) |
mode_flags |= DRM_MODE_FLAG_3D_MASK; |
drm_mode_validate_flag(connector, mode_flags); |
|
list_for_each_entry(mode, &connector->modes, head) { |
if (mode->status == MODE_OK) |
mode->status = connector_funcs->mode_valid(connector, |
mode); |
} |
|
prune: |
drm_mode_prune_invalid(dev, &connector->modes, verbose_prune); |
|
if (list_empty(&connector->modes)) |
return 0; |
|
list_for_each_entry(mode, &connector->modes, head) |
mode->vrefresh = drm_mode_vrefresh(mode); |
|
drm_mode_sort(&connector->modes); |
|
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] probed modes :\n", connector->base.id, |
drm_get_connector_name(connector)); |
list_for_each_entry(mode, &connector->modes, head) { |
drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V); |
drm_mode_debug_printmodeline(mode); |
} |
|
return count; |
} |
EXPORT_SYMBOL(drm_helper_probe_single_connector_modes); |
|
/** |
* drm_helper_encoder_in_use - check if a given encoder is in use |
* @encoder: encoder to check |
* |
* LOCKING: |
* Caller must hold mode config lock. |
* Checks whether @encoder is with the current mode setting output configuration |
* in use by any connector. This doesn't mean that it is actually enabled since |
* the DPMS state is tracked separately. |
* |
* Walk @encoders's DRM device's mode_config and see if it's in use. |
* |
* RETURNS: |
* True if @encoder is part of the mode_config, false otherwise. |
* Returns: |
* True if @encoder is used, false otherwise. |
*/ |
bool drm_helper_encoder_in_use(struct drm_encoder *encoder) |
{ |
242,13 → 99,12 |
* drm_helper_crtc_in_use - check if a given CRTC is in a mode_config |
* @crtc: CRTC to check |
* |
* LOCKING: |
* Caller must hold mode config lock. |
* Checks whether @crtc is with the current mode setting output configuration |
* in use by any connector. This doesn't mean that it is actually enabled since |
* the DPMS state is tracked separately. |
* |
* Walk @crtc's DRM device's mode_config and see if it's in use. |
* |
* RETURNS: |
* True if @crtc is part of the mode_config, false otherwise. |
* Returns: |
* True if @crtc is used, false otherwise. |
*/ |
bool drm_helper_crtc_in_use(struct drm_crtc *crtc) |
{ |
279,33 → 135,17 |
encoder->bridge->funcs->post_disable(encoder->bridge); |
} |
|
/** |
* drm_helper_disable_unused_functions - disable unused objects |
* @dev: DRM device |
* |
* LOCKING: |
* Caller must hold mode config lock. |
* |
* If an connector or CRTC isn't part of @dev's mode_config, it can be disabled |
* by calling its dpms function, which should power it off. |
*/ |
void drm_helper_disable_unused_functions(struct drm_device *dev) |
static void __drm_helper_disable_unused_functions(struct drm_device *dev) |
{ |
struct drm_encoder *encoder; |
struct drm_connector *connector; |
struct drm_crtc *crtc; |
|
list_for_each_entry(connector, &dev->mode_config.connector_list, head) { |
if (!connector->encoder) |
continue; |
if (connector->status == connector_status_disconnected) |
connector->encoder = NULL; |
} |
drm_warn_on_modeset_not_all_locked(dev); |
|
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { |
if (!drm_helper_encoder_in_use(encoder)) { |
drm_encoder_disable(encoder); |
/* disconnector encoder from any connector */ |
/* disconnect encoder from any connector */ |
encoder->crtc = NULL; |
} |
} |
318,10 → 158,27 |
(*crtc_funcs->disable)(crtc); |
else |
(*crtc_funcs->dpms)(crtc, DRM_MODE_DPMS_OFF); |
crtc->fb = NULL; |
crtc->primary->fb = NULL; |
} |
} |
} |
|
/** |
* drm_helper_disable_unused_functions - disable unused objects |
* @dev: DRM device |
* |
* This function walks through the entire mode setting configuration of @dev. It |
* will remove any crtc links of unused encoders and encoder links of |
* disconnected connectors. Then it will disable all unused encoders and crtcs |
* either by calling their disable callback if available or by calling their |
* dpms callback with DRM_MODE_DPMS_OFF. |
*/ |
void drm_helper_disable_unused_functions(struct drm_device *dev) |
{ |
drm_modeset_lock_all(dev); |
__drm_helper_disable_unused_functions(dev); |
drm_modeset_unlock_all(dev); |
} |
EXPORT_SYMBOL(drm_helper_disable_unused_functions); |
|
/* |
355,9 → 212,6 |
* @y: vertical offset into the surface |
* @old_fb: old framebuffer, for cleanup |
* |
* LOCKING: |
* Caller must hold mode config lock. |
* |
* Try to set @mode on @crtc. Give @crtc and its associated connectors a chance |
* to fixup or reject the mode prior to trying to set it. This is an internal |
* helper that drivers could e.g. use to update properties that require the |
367,8 → 221,8 |
* drm_crtc_helper_set_config() helper function to drive the mode setting |
* sequence. |
* |
* RETURNS: |
* True if the mode was set successfully, or false otherwise. |
* Returns: |
* True if the mode was set successfully, false otherwise. |
*/ |
bool drm_crtc_helper_set_mode(struct drm_crtc *crtc, |
struct drm_display_mode *mode, |
384,6 → 238,8 |
struct drm_encoder *encoder; |
bool ret = true; |
|
drm_warn_on_modeset_not_all_locked(dev); |
|
saved_enabled = crtc->enabled; |
crtc->enabled = drm_helper_crtc_in_use(crtc); |
if (!crtc->enabled) |
472,7 → 328,7 |
continue; |
|
DRM_DEBUG_KMS("[ENCODER:%d:%s] set [MODE:%d:%s]\n", |
encoder->base.id, drm_get_encoder_name(encoder), |
encoder->base.id, encoder->name, |
mode->base.id, mode->name); |
encoder_funcs = encoder->helper_private; |
encoder_funcs->mode_set(encoder, mode, adjusted_mode); |
523,8 → 379,7 |
} |
EXPORT_SYMBOL(drm_crtc_helper_set_mode); |
|
|
static int |
static void |
drm_crtc_helper_disable(struct drm_crtc *crtc) |
{ |
struct drm_device *dev = crtc->dev; |
552,8 → 407,7 |
} |
} |
|
drm_helper_disable_unused_functions(dev); |
return 0; |
__drm_helper_disable_unused_functions(dev); |
} |
|
/** |
560,17 → 414,14 |
* drm_crtc_helper_set_config - set a new config from userspace |
* @set: mode set configuration |
* |
* LOCKING: |
* Caller must hold mode config lock. |
* |
* Setup a new configuration, provided by the upper layers (either an ioctl call |
* from userspace or internally e.g. from the fbdev suppport code) in @set, and |
* from userspace or internally e.g. from the fbdev support code) in @set, and |
* enable it. This is the main helper functions for drivers that implement |
* kernel mode setting with the crtc helper functions and the assorted |
* ->prepare(), ->modeset() and ->commit() helper callbacks. |
* |
* RETURNS: |
* Returns 0 on success, -ERRNO on failure. |
* Returns: |
* Returns 0 on success, negative errno numbers on failure. |
*/ |
int drm_crtc_helper_set_config(struct drm_mode_set *set) |
{ |
607,11 → 458,14 |
(int)set->num_connectors, set->x, set->y); |
} else { |
DRM_DEBUG_KMS("[CRTC:%d] [NOFB]\n", set->crtc->base.id); |
return drm_crtc_helper_disable(set->crtc); |
drm_crtc_helper_disable(set->crtc); |
return 0; |
} |
|
dev = set->crtc->dev; |
|
drm_warn_on_modeset_not_all_locked(dev); |
|
/* |
* Allocate space for the backup of all (non-pointer) encoder and |
* connector data. |
647,19 → 501,19 |
save_set.mode = &set->crtc->mode; |
save_set.x = set->crtc->x; |
save_set.y = set->crtc->y; |
save_set.fb = set->crtc->fb; |
save_set.fb = set->crtc->primary->fb; |
|
/* We should be able to check here if the fb has the same properties |
* and then just flip_or_move it */ |
if (set->crtc->fb != set->fb) { |
if (set->crtc->primary->fb != set->fb) { |
/* If we have no fb then treat it as a full mode set */ |
if (set->crtc->fb == NULL) { |
if (set->crtc->primary->fb == NULL) { |
DRM_DEBUG_KMS("crtc has no fb, full mode set\n"); |
mode_changed = true; |
} else if (set->fb == NULL) { |
mode_changed = true; |
} else if (set->fb->pixel_format != |
set->crtc->fb->pixel_format) { |
set->crtc->primary->fb->pixel_format) { |
mode_changed = true; |
} else |
fb_changed = true; |
689,12 → 543,13 |
if (new_encoder == NULL) |
/* don't break so fail path works correct */ |
fail = 1; |
break; |
|
if (connector->dpms != DRM_MODE_DPMS_ON) { |
DRM_DEBUG_KMS("connector dpms not on, full mode switch\n"); |
mode_changed = true; |
} |
|
break; |
} |
} |
|
743,11 → 598,11 |
} |
if (new_crtc) { |
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] to [CRTC:%d]\n", |
connector->base.id, drm_get_connector_name(connector), |
connector->base.id, connector->name, |
new_crtc->base.id); |
} else { |
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] to [NOCRTC]\n", |
connector->base.id, drm_get_connector_name(connector)); |
connector->base.id, connector->name); |
} |
} |
|
760,13 → 615,13 |
DRM_DEBUG_KMS("attempting to set mode from" |
" userspace\n"); |
drm_mode_debug_printmodeline(set->mode); |
set->crtc->fb = set->fb; |
set->crtc->primary->fb = set->fb; |
if (!drm_crtc_helper_set_mode(set->crtc, set->mode, |
set->x, set->y, |
save_set.fb)) { |
DRM_ERROR("failed to set mode on [CRTC:%d]\n", |
set->crtc->base.id); |
set->crtc->fb = save_set.fb; |
set->crtc->primary->fb = save_set.fb; |
ret = -EINVAL; |
goto fail; |
} |
773,21 → 628,21 |
DRM_DEBUG_KMS("Setting connector DPMS state to on\n"); |
for (i = 0; i < set->num_connectors; i++) { |
DRM_DEBUG_KMS("\t[CONNECTOR:%d:%s] set DPMS on\n", set->connectors[i]->base.id, |
drm_get_connector_name(set->connectors[i])); |
set->connectors[i]->name); |
set->connectors[i]->funcs->dpms(set->connectors[i], DRM_MODE_DPMS_ON); |
} |
} |
drm_helper_disable_unused_functions(dev); |
__drm_helper_disable_unused_functions(dev); |
} else if (fb_changed) { |
set->crtc->x = set->x; |
set->crtc->y = set->y; |
set->crtc->fb = set->fb; |
set->crtc->primary->fb = set->fb; |
ret = crtc_funcs->mode_set_base(set->crtc, |
set->x, set->y, save_set.fb); |
if (ret != 0) { |
set->crtc->x = save_set.x; |
set->crtc->y = save_set.y; |
set->crtc->fb = save_set.fb; |
set->crtc->primary->fb = save_set.fb; |
goto fail; |
} |
} |
924,7 → 779,15 |
} |
EXPORT_SYMBOL(drm_helper_connector_dpms); |
|
int drm_helper_mode_fill_fb_struct(struct drm_framebuffer *fb, |
/** |
* drm_helper_mode_fill_fb_struct - fill out framebuffer metadata |
* @fb: drm_framebuffer object to fill out |
* @mode_cmd: metadata from the userspace fb creation request |
* |
* This helper can be used in a drivers fb_create callback to pre-fill the fb's |
* metadata fields. |
*/ |
void drm_helper_mode_fill_fb_struct(struct drm_framebuffer *fb, |
struct drm_mode_fb_cmd2 *mode_cmd) |
{ |
int i; |
938,18 → 801,39 |
drm_fb_get_bpp_depth(mode_cmd->pixel_format, &fb->depth, |
&fb->bits_per_pixel); |
fb->pixel_format = mode_cmd->pixel_format; |
|
return 0; |
fb->flags = mode_cmd->flags; |
} |
EXPORT_SYMBOL(drm_helper_mode_fill_fb_struct); |
|
int drm_helper_resume_force_mode(struct drm_device *dev) |
/** |
* drm_helper_resume_force_mode - force-restore mode setting configuration |
* @dev: drm_device which should be restored |
* |
* Drivers which use the mode setting helpers can use this function to |
* force-restore the mode setting configuration e.g. on resume or when something |
* else might have trampled over the hw state (like some overzealous old BIOSen |
* tended to do). |
* |
* This helper doesn't provide a error return value since restoring the old |
* config should never fail due to resource allocation issues since the driver |
* has successfully set the restored configuration already. Hence this should |
* boil down to the equivalent of a few dpms on calls, which also don't provide |
* an error code. |
* |
* Drivers where simply restoring an old configuration again might fail (e.g. |
* due to slight differences in allocating shared resources when the |
* configuration is restored in a different order than when userspace set it up) |
* need to use their own restore logic. |
*/ |
void drm_helper_resume_force_mode(struct drm_device *dev) |
{ |
struct drm_crtc *crtc; |
struct drm_encoder *encoder; |
struct drm_crtc_helper_funcs *crtc_funcs; |
int ret, encoder_dpms; |
int encoder_dpms; |
bool ret; |
|
drm_modeset_lock_all(dev); |
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { |
|
if (!crtc->enabled) |
956,8 → 840,9 |
continue; |
|
ret = drm_crtc_helper_set_mode(crtc, &crtc->mode, |
crtc->x, crtc->y, crtc->fb); |
crtc->x, crtc->y, crtc->primary->fb); |
|
/* Restoring the old config should never fail! */ |
if (ret == false) |
DRM_ERROR("failed to set mode on crtc %p\n", crtc); |
|
980,154 → 865,9 |
drm_helper_choose_crtc_dpms(crtc)); |
} |
} |
|
/* disable the unused connectors while restoring the modesetting */ |
drm_helper_disable_unused_functions(dev); |
return 0; |
__drm_helper_disable_unused_functions(dev); |
drm_modeset_unlock_all(dev); |
} |
EXPORT_SYMBOL(drm_helper_resume_force_mode); |
|
void drm_kms_helper_hotplug_event(struct drm_device *dev) |
{ |
/* send a uevent + call fbdev */ |
if (dev->mode_config.funcs->output_poll_changed) |
dev->mode_config.funcs->output_poll_changed(dev); |
} |
EXPORT_SYMBOL(drm_kms_helper_hotplug_event); |
|
#define DRM_OUTPUT_POLL_PERIOD (10*HZ) |
static void output_poll_execute(struct work_struct *work) |
{ |
struct delayed_work *delayed_work = to_delayed_work(work); |
struct drm_device *dev = container_of(delayed_work, struct drm_device, mode_config.output_poll_work); |
struct drm_connector *connector; |
enum drm_connector_status old_status; |
bool repoll = false, changed = false; |
|
if (!drm_kms_helper_poll) |
return; |
|
mutex_lock(&dev->mode_config.mutex); |
list_for_each_entry(connector, &dev->mode_config.connector_list, head) { |
|
/* Ignore forced connectors. */ |
if (connector->force) |
continue; |
|
/* Ignore HPD capable connectors and connectors where we don't |
* want any hotplug detection at all for polling. */ |
if (!connector->polled || connector->polled == DRM_CONNECTOR_POLL_HPD) |
continue; |
|
repoll = true; |
|
old_status = connector->status; |
/* if we are connected and don't want to poll for disconnect |
skip it */ |
if (old_status == connector_status_connected && |
!(connector->polled & DRM_CONNECTOR_POLL_DISCONNECT)) |
continue; |
|
connector->status = connector->funcs->detect(connector, false); |
if (old_status != connector->status) { |
const char *old, *new; |
|
old = drm_get_connector_status_name(old_status); |
new = drm_get_connector_status_name(connector->status); |
|
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] " |
"status updated from %s to %s\n", |
connector->base.id, |
drm_get_connector_name(connector), |
old, new); |
|
changed = true; |
} |
} |
|
mutex_unlock(&dev->mode_config.mutex); |
|
if (changed) |
drm_kms_helper_hotplug_event(dev); |
|
if (repoll) |
schedule_delayed_work(delayed_work, DRM_OUTPUT_POLL_PERIOD); |
} |
|
void drm_kms_helper_poll_disable(struct drm_device *dev) |
{ |
if (!dev->mode_config.poll_enabled) |
return; |
// cancel_delayed_work_sync(&dev->mode_config.output_poll_work); |
} |
EXPORT_SYMBOL(drm_kms_helper_poll_disable); |
|
void drm_kms_helper_poll_enable(struct drm_device *dev) |
{ |
bool poll = false; |
struct drm_connector *connector; |
|
if (!dev->mode_config.poll_enabled || !drm_kms_helper_poll) |
return; |
|
list_for_each_entry(connector, &dev->mode_config.connector_list, head) { |
if (connector->polled & (DRM_CONNECTOR_POLL_CONNECT | |
DRM_CONNECTOR_POLL_DISCONNECT)) |
poll = true; |
} |
|
if (poll) |
schedule_delayed_work(&dev->mode_config.output_poll_work, DRM_OUTPUT_POLL_PERIOD); |
} |
EXPORT_SYMBOL(drm_kms_helper_poll_enable); |
|
void drm_kms_helper_poll_init(struct drm_device *dev) |
{ |
INIT_DELAYED_WORK(&dev->mode_config.output_poll_work, output_poll_execute); |
dev->mode_config.poll_enabled = true; |
|
drm_kms_helper_poll_enable(dev); |
} |
EXPORT_SYMBOL(drm_kms_helper_poll_init); |
|
void drm_kms_helper_poll_fini(struct drm_device *dev) |
{ |
drm_kms_helper_poll_disable(dev); |
} |
EXPORT_SYMBOL(drm_kms_helper_poll_fini); |
|
bool drm_helper_hpd_irq_event(struct drm_device *dev) |
{ |
struct drm_connector *connector; |
enum drm_connector_status old_status; |
bool changed = false; |
|
if (!dev->mode_config.poll_enabled) |
return false; |
|
mutex_lock(&dev->mode_config.mutex); |
list_for_each_entry(connector, &dev->mode_config.connector_list, head) { |
|
/* Only handle HPD capable connectors. */ |
if (!(connector->polled & DRM_CONNECTOR_POLL_HPD)) |
continue; |
|
old_status = connector->status; |
|
connector->status = connector->funcs->detect(connector, false); |
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %s to %s\n", |
connector->base.id, |
drm_get_connector_name(connector), |
drm_get_connector_status_name(old_status), |
drm_get_connector_status_name(connector->status)); |
if (old_status != connector->status) |
changed = true; |
} |
|
mutex_unlock(&dev->mode_config.mutex); |
|
if (changed) |
drm_kms_helper_hotplug_event(dev); |
|
return changed; |
} |
EXPORT_SYMBOL(drm_helper_hpd_irq_event); |