diff options
Diffstat (limited to 'drivers/gpu/drm/drm_connector.c')
-rw-r--r-- | drivers/gpu/drm/drm_connector.c | 138 |
1 files changed, 134 insertions, 4 deletions
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 25f4b2e9a44f..e6a21e69059c 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -24,6 +24,7 @@ #include <drm/drm_connector.h> #include <drm/drm_edid.h> #include <drm/drm_encoder.h> +#include <drm/drm_utils.h> #include "drm_crtc_internal.h" #include "drm_internal.h" @@ -152,6 +153,25 @@ static void drm_connector_free(struct kref *kref) connector->funcs->destroy(connector); } +void drm_connector_free_work_fn(struct work_struct *work) +{ + struct drm_connector *connector, *n; + struct drm_device *dev = + container_of(work, struct drm_device, mode_config.connector_free_work); + struct drm_mode_config *config = &dev->mode_config; + unsigned long flags; + struct llist_node *freed; + + spin_lock_irqsave(&config->connector_list_lock, flags); + freed = llist_del_all(&config->connector_free_list); + spin_unlock_irqrestore(&config->connector_list_lock, flags); + + llist_for_each_entry_safe(connector, n, freed, free_node) { + drm_mode_object_unregister(dev, &connector->base); + connector->funcs->destroy(connector); + } +} + /** * drm_connector_init - Init a preallocated connector * @dev: DRM device @@ -212,6 +232,8 @@ int drm_connector_init(struct drm_device *dev, mutex_init(&connector->mutex); connector->edid_blob_ptr = NULL; connector->status = connector_status_unknown; + connector->display_info.panel_orientation = + DRM_MODE_PANEL_ORIENTATION_UNKNOWN; drm_connector_get_cmdline_mode(connector); @@ -529,6 +551,25 @@ void drm_connector_list_iter_begin(struct drm_device *dev, } EXPORT_SYMBOL(drm_connector_list_iter_begin); +/* + * Extra-safe connector put function that works in any context. Should only be + * used from the connector_iter functions, where we never really expect to + * actually release the connector when dropping our final reference. + */ +static void +__drm_connector_put_safe(struct drm_connector *conn) +{ + struct drm_mode_config *config = &conn->dev->mode_config; + + lockdep_assert_held(&config->connector_list_lock); + + if (!refcount_dec_and_test(&conn->base.refcount.refcount)) + return; + + llist_add(&conn->free_node, &config->connector_free_list); + schedule_work(&config->connector_free_work); +} + /** * drm_connector_list_iter_next - return next connector * @iter: connectr_list iterator @@ -558,10 +599,10 @@ drm_connector_list_iter_next(struct drm_connector_list_iter *iter) /* loop until it's not a zombie connector */ } while (!kref_get_unless_zero(&iter->conn->base.refcount)); - spin_unlock_irqrestore(&config->connector_list_lock, flags); if (old_conn) - drm_connector_put(old_conn); + __drm_connector_put_safe(old_conn); + spin_unlock_irqrestore(&config->connector_list_lock, flags); return iter->conn; } @@ -578,9 +619,15 @@ EXPORT_SYMBOL(drm_connector_list_iter_next); */ void drm_connector_list_iter_end(struct drm_connector_list_iter *iter) { + struct drm_mode_config *config = &iter->dev->mode_config; + unsigned long flags; + iter->dev = NULL; - if (iter->conn) - drm_connector_put(iter->conn); + if (iter->conn) { + spin_lock_irqsave(&config->connector_list_lock, flags); + __drm_connector_put_safe(iter->conn); + spin_unlock_irqrestore(&config->connector_list_lock, flags); + } lock_release(&connector_list_iter_dep_map, 0, _RET_IP_); } EXPORT_SYMBOL(drm_connector_list_iter_end); @@ -668,6 +715,13 @@ static const struct drm_prop_enum_list drm_aspect_ratio_enum_list[] = { { DRM_MODE_PICTURE_ASPECT_16_9, "16:9" }, }; +static const struct drm_prop_enum_list drm_panel_orientation_enum_list[] = { + { DRM_MODE_PANEL_ORIENTATION_NORMAL, "Normal" }, + { DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP, "Upside Down" }, + { DRM_MODE_PANEL_ORIENTATION_LEFT_UP, "Left Side Up" }, + { DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, "Right Side Up" }, +}; + static const struct drm_prop_enum_list drm_dvi_i_select_enum_list[] = { { DRM_MODE_SUBCONNECTOR_Automatic, "Automatic" }, /* DVI-I and TV-out */ { DRM_MODE_SUBCONNECTOR_DVID, "DVI-D" }, /* DVI-I */ @@ -776,6 +830,18 @@ DRM_ENUM_NAME_FN(drm_get_tv_subconnector_name, * * CRTC_ID: * Mode object ID of the &drm_crtc this connector should be connected to. + * + * Connectors for LCD panels may also have one standardized property: + * + * panel orientation: + * On some devices the LCD panel is mounted in the casing in such a way + * that the up/top side of the panel does not match with the top side of + * the device. Userspace can use this property to check for this. + * Note that input coordinates from touchscreens (input devices with + * INPUT_PROP_DIRECT) will still map 1:1 to the actual LCD panel + * coordinates, so if userspace rotates the picture to adjust for + * the orientation it must also apply the same transformation to the + * touchscreen input coordinates. */ int drm_connector_create_standard_properties(struct drm_device *dev) @@ -1207,6 +1273,19 @@ int drm_mode_connector_update_edid_property(struct drm_connector *connector, if (edid) size = EDID_LENGTH * (1 + edid->extensions); + /* Set the display info, using edid if available, otherwise + * reseting the values to defaults. This duplicates the work + * done in drm_add_edid_modes, but that function is not + * consistently called before this one in all drivers and the + * computation is cheap enough that it seems better to + * duplicate it rather than attempt to ensure some arbitrary + * ordering of calls. + */ + if (edid) + drm_add_display_info(connector, edid); + else + drm_reset_display_info(connector); + drm_object_property_set_value(&connector->base, dev->mode_config.non_desktop_property, connector->display_info.non_desktop); @@ -1251,6 +1330,57 @@ void drm_mode_connector_set_link_status_property(struct drm_connector *connector } EXPORT_SYMBOL(drm_mode_connector_set_link_status_property); +/** + * drm_connector_init_panel_orientation_property - + * initialize the connecters panel_orientation property + * @connector: connector for which to init the panel-orientation property. + * @width: width in pixels of the panel, used for panel quirk detection + * @height: height in pixels of the panel, used for panel quirk detection + * + * This function should only be called for built-in panels, after setting + * connector->display_info.panel_orientation first (if known). + * + * This function will check for platform specific (e.g. DMI based) quirks + * overriding display_info.panel_orientation first, then if panel_orientation + * is not DRM_MODE_PANEL_ORIENTATION_UNKNOWN it will attach the + * "panel orientation" property to the connector. + * + * Returns: + * Zero on success, negative errno on failure. + */ +int drm_connector_init_panel_orientation_property( + struct drm_connector *connector, int width, int height) +{ + struct drm_device *dev = connector->dev; + struct drm_display_info *info = &connector->display_info; + struct drm_property *prop; + int orientation_quirk; + + orientation_quirk = drm_get_panel_orientation_quirk(width, height); + if (orientation_quirk != DRM_MODE_PANEL_ORIENTATION_UNKNOWN) + info->panel_orientation = orientation_quirk; + + if (info->panel_orientation == DRM_MODE_PANEL_ORIENTATION_UNKNOWN) + return 0; + + prop = dev->mode_config.panel_orientation_property; + if (!prop) { + prop = drm_property_create_enum(dev, DRM_MODE_PROP_IMMUTABLE, + "panel orientation", + drm_panel_orientation_enum_list, + ARRAY_SIZE(drm_panel_orientation_enum_list)); + if (!prop) + return -ENOMEM; + + dev->mode_config.panel_orientation_property = prop; + } + + drm_object_attach_property(&connector->base, prop, + info->panel_orientation); + return 0; +} +EXPORT_SYMBOL(drm_connector_init_panel_orientation_property); + int drm_mode_connector_set_obj_prop(struct drm_mode_object *obj, struct drm_property *property, uint64_t value) |