diff options
Diffstat (limited to 'drivers/gpu/drm/drm_connector.c')
| -rw-r--r-- | drivers/gpu/drm/drm_connector.c | 2582 |
1 files changed, 2359 insertions, 223 deletions
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 8072e6e4c62c..4d6dc9ebfdb5 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -20,10 +20,25 @@ * OF THIS SOFTWARE. */ -#include <drm/drmP.h> +#include <drm/drm_auth.h> #include <drm/drm_connector.h> +#include <drm/drm_drv.h> #include <drm/drm_edid.h> #include <drm/drm_encoder.h> +#include <drm/drm_file.h> +#include <drm/drm_managed.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_privacy_screen_consumer.h> +#include <drm/drm_sysfs.h> +#include <drm/drm_utils.h> + +#include <linux/export.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/uaccess.h> + +#include <video/cmdline.h> #include "drm_crtc_internal.h" #include "drm_internal.h" @@ -32,7 +47,7 @@ * DOC: overview * * In DRM connectors are the general abstraction for display sinks, and include - * als fixed panels or anything else that can display pixels in some form. As + * also fixed panels or anything else that can display pixels in some form. As * opposed to all other KMS objects representing hardware (like CRTC, encoder or * plane abstractions) connectors can be hotplugged and unplugged at runtime. * Hence they are reference-counted using drm_connector_get() and @@ -47,7 +62,7 @@ * * Connectors must be attached to an encoder to be used. For devices that map * connectors to encoders 1:1, the connector should be attached at - * initialization time with a call to drm_mode_connector_attach_encoder(). The + * initialization time with a call to drm_connector_attach_encoder(). The * driver must also set the &drm_connector.encoder field to point to the * attached encoder. * @@ -58,6 +73,14 @@ * support can instead use e.g. drm_helper_hpd_irq_event(). */ +/* + * Global connector list for drm_connector_find_by_fwnode(). + * Note drm_connector_[un]register() first take connector->lock and then + * take the connector_list_lock. + */ +static DEFINE_MUTEX(connector_list_lock); +static LIST_HEAD(connector_list); + struct drm_conn_prop_enum_list { int type; const char *name; @@ -86,6 +109,9 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = { { DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" }, { DRM_MODE_CONNECTOR_DSI, "DSI" }, { DRM_MODE_CONNECTOR_DPI, "DPI" }, + { DRM_MODE_CONNECTOR_WRITEBACK, "Writeback" }, + { DRM_MODE_CONNECTOR_SPI, "SPI" }, + { DRM_MODE_CONNECTOR_USB, "USB" }, }; void drm_connector_ida_init(void) @@ -105,8 +131,23 @@ void drm_connector_ida_destroy(void) } /** + * drm_get_connector_type_name - return a string for connector type + * @type: The connector type (DRM_MODE_CONNECTOR_*) + * + * Returns: the name of the connector type, or NULL if the type is not valid. + */ +const char *drm_get_connector_type_name(unsigned int type) +{ + if (type < ARRAY_SIZE(drm_connector_enum_list)) + return drm_connector_enum_list[type].name; + + return NULL; +} +EXPORT_SYMBOL(drm_get_connector_type_name); + +/** * drm_connector_get_cmdline_mode - reads the user's cmdline mode - * @connector: connector to quwery + * @connector: connector to query * * The kernel supports per-connector configuration of its consoles through * use of the video= parameter. This function parses that option and @@ -117,9 +158,10 @@ void drm_connector_ida_destroy(void) static void drm_connector_get_cmdline_mode(struct drm_connector *connector) { struct drm_cmdline_mode *mode = &connector->cmdline_mode; - char *option = NULL; + const char *option; - if (fb_get_options(connector->name, &option)) + option = video_get_options(connector->name); + if (!option) return; if (!drm_mode_parse_command_line_for_connector(option, @@ -133,8 +175,15 @@ static void drm_connector_get_cmdline_mode(struct drm_connector *connector) connector->force = mode->force; } - DRM_DEBUG_KMS("cmdline mode for connector %s %dx%d@%dHz%s%s%s\n", - connector->name, + if (mode->panel_orientation != DRM_MODE_PANEL_ORIENTATION_UNKNOWN) { + DRM_INFO("cmdline forces connector %s panel_orientation to %d\n", + connector->name, mode->panel_orientation); + drm_connector_set_panel_orientation(connector, + mode->panel_orientation); + } + + DRM_DEBUG_KMS("cmdline mode for connector %s %s %dx%d@%dHz%s%s%s\n", + connector->name, mode->name, mode->xres, mode->yres, mode->refresh_specified ? mode->refresh : 60, mode->rb ? " reduced blanking" : "", @@ -152,29 +201,40 @@ static void drm_connector_free(struct kref *kref) connector->funcs->destroy(connector); } -/** - * drm_connector_init - Init a preallocated connector - * @dev: DRM device - * @connector: the connector to init - * @funcs: callbacks for this connector - * @connector_type: user visible type of the connector - * - * Initialises a preallocated connector. Connectors should be - * subclassed as part of driver connector objects. - * - * Returns: - * Zero on success, error code on failure. - */ -int drm_connector_init(struct drm_device *dev, - struct drm_connector *connector, - const struct drm_connector_funcs *funcs, - int connector_type) +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); + } +} + +static int drm_connector_init_only(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type, + struct i2c_adapter *ddc) { struct drm_mode_config *config = &dev->mode_config; int ret; struct ida *connector_ida = &drm_connector_enum_list[connector_type].ida; + WARN_ON(drm_drv_uses_atomic_modeset(dev) && + (!funcs->atomic_destroy_state || + !funcs->atomic_duplicate_state)); + ret = __drm_mode_object_add(dev, &connector->base, DRM_MODE_OBJECT_CONNECTOR, false, drm_connector_free); @@ -185,15 +245,20 @@ int drm_connector_init(struct drm_device *dev, connector->dev = dev; connector->funcs = funcs; - ret = ida_simple_get(&config->connector_ida, 0, 0, GFP_KERNEL); - if (ret < 0) + /* connector index is used with 32bit bitmasks */ + ret = ida_alloc_max(&config->connector_ida, 31, GFP_KERNEL); + if (ret < 0) { + DRM_DEBUG_KMS("Failed to allocate %s connector index: %d\n", + drm_connector_enum_list[connector_type].name, + ret); goto out_put; + } connector->index = ret; ret = 0; connector->connector_type = connector_type; connector->connector_type_id = - ida_simple_get(connector_ida, 1, 0, GFP_KERNEL); + ida_alloc_min(connector_ida, 1, GFP_KERNEL); if (connector->connector_type_id < 0) { ret = connector->connector_type_id; goto out_put_id; @@ -207,25 +272,31 @@ int drm_connector_init(struct drm_device *dev, goto out_put_type_id; } + /* provide ddc symlink in sysfs */ + connector->ddc = ddc; + + INIT_LIST_HEAD(&connector->head); + INIT_LIST_HEAD(&connector->global_connector_list_entry); INIT_LIST_HEAD(&connector->probed_modes); INIT_LIST_HEAD(&connector->modes); mutex_init(&connector->mutex); + mutex_init(&connector->cec.mutex); + mutex_init(&connector->eld_mutex); + mutex_init(&connector->edid_override_mutex); + mutex_init(&connector->hdmi.infoframes.lock); + mutex_init(&connector->hdmi_audio.lock); connector->edid_blob_ptr = NULL; + connector->epoch_counter = 0; + connector->tile_blob_ptr = NULL; connector->status = connector_status_unknown; + connector->display_info.panel_orientation = + DRM_MODE_PANEL_ORIENTATION_UNKNOWN; drm_connector_get_cmdline_mode(connector); - /* We should add connectors at the end to avoid upsetting the connector - * index too much. */ - spin_lock_irq(&config->connector_list_lock); - list_add_tail(&connector->head, &config->connector_list); - config->num_connector++; - spin_unlock_irq(&config->connector_list_lock); - - if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL) - drm_object_attach_property(&connector->base, - config->edid_property, - 0); + if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL && + connector_type != DRM_MODE_CONNECTOR_WRITEBACK) + drm_connector_attach_edid_property(connector); drm_object_attach_property(&connector->base, config->dpms_property, 0); @@ -234,6 +305,13 @@ int drm_connector_init(struct drm_device *dev, config->link_status_property, 0); + drm_object_attach_property(&connector->base, + config->non_desktop_property, + 0); + drm_object_attach_property(&connector->base, + config->tile_property, + 0); + if (drm_core_check_feature(dev, DRIVER_ATOMIC)) { drm_object_attach_property(&connector->base, config->prop_crtc_id, 0); } @@ -241,20 +319,334 @@ int drm_connector_init(struct drm_device *dev, connector->debugfs_entry = NULL; out_put_type_id: if (ret) - ida_simple_remove(connector_ida, connector->connector_type_id); + ida_free(connector_ida, connector->connector_type_id); out_put_id: if (ret) - ida_simple_remove(&config->connector_ida, connector->index); + ida_free(&config->connector_ida, connector->index); out_put: if (ret) drm_mode_object_unregister(dev, &connector->base); return ret; } + +static void drm_connector_add(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_mode_config *config = &dev->mode_config; + + if (drm_WARN_ON(dev, !list_empty(&connector->head))) + return; + + spin_lock_irq(&config->connector_list_lock); + list_add_tail(&connector->head, &config->connector_list); + config->num_connector++; + spin_unlock_irq(&config->connector_list_lock); +} + +static void drm_connector_remove(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + + /* + * For dynamic connectors drm_connector_cleanup() can call this function + * before the connector is registered and added to the list. + */ + if (list_empty(&connector->head)) + return; + + spin_lock_irq(&dev->mode_config.connector_list_lock); + list_del_init(&connector->head); + dev->mode_config.num_connector--; + spin_unlock_irq(&dev->mode_config.connector_list_lock); +} + +static int drm_connector_init_and_add(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type, + struct i2c_adapter *ddc) +{ + int ret; + + ret = drm_connector_init_only(dev, connector, funcs, connector_type, ddc); + if (ret) + return ret; + + drm_connector_add(connector); + + return 0; +} + +/** + * drm_connector_init - Init a preallocated connector + * @dev: DRM device + * @connector: the connector to init + * @funcs: callbacks for this connector + * @connector_type: user visible type of the connector + * + * Initialises a preallocated connector. Connectors should be + * subclassed as part of driver connector objects. + * + * At driver unload time the driver's &drm_connector_funcs.destroy hook + * should call drm_connector_cleanup() and free the connector structure. + * The connector structure should not be allocated with devm_kzalloc(). + * + * Note: consider using drmm_connector_init() instead of + * drm_connector_init() to let the DRM managed resource infrastructure + * take care of cleanup and deallocation. + * + * Returns: + * Zero on success, error code on failure. + */ +int drm_connector_init(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type) +{ + if (drm_WARN_ON(dev, !(funcs && funcs->destroy))) + return -EINVAL; + + return drm_connector_init_and_add(dev, connector, funcs, connector_type, NULL); +} EXPORT_SYMBOL(drm_connector_init); /** - * drm_mode_connector_attach_encoder - attach a connector to an encoder + * drm_connector_dynamic_init - Init a preallocated dynamic connector + * @dev: DRM device + * @connector: the connector to init + * @funcs: callbacks for this connector + * @connector_type: user visible type of the connector + * @ddc: pointer to the associated ddc adapter + * + * Initialises a preallocated dynamic connector. Connectors should be + * subclassed as part of driver connector objects. The connector + * structure should not be allocated with devm_kzalloc(). + * + * Drivers should call this for dynamic connectors which can be hotplugged + * after drm_dev_register() has been called already, e.g. DP MST connectors. + * For all other - static - connectors, drivers should call one of the + * drm_connector_init*()/drmm_connector_init*() functions. + * + * After calling this function the drivers must call + * drm_connector_dynamic_register(). + * + * To remove the connector the driver must call drm_connector_unregister() + * followed by drm_connector_put(). Putting the last reference will call the + * driver's &drm_connector_funcs.destroy hook, which in turn must call + * drm_connector_cleanup() and free the connector structure. + * + * Returns: + * Zero on success, error code on failure. + */ +int drm_connector_dynamic_init(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type, + struct i2c_adapter *ddc) +{ + if (drm_WARN_ON(dev, !(funcs && funcs->destroy))) + return -EINVAL; + + return drm_connector_init_only(dev, connector, funcs, connector_type, ddc); +} +EXPORT_SYMBOL(drm_connector_dynamic_init); + +/** + * drm_connector_init_with_ddc - Init a preallocated connector + * @dev: DRM device + * @connector: the connector to init + * @funcs: callbacks for this connector + * @connector_type: user visible type of the connector + * @ddc: pointer to the associated ddc adapter + * + * Initialises a preallocated connector. Connectors should be + * subclassed as part of driver connector objects. + * + * At driver unload time the driver's &drm_connector_funcs.destroy hook + * should call drm_connector_cleanup() and free the connector structure. + * The connector structure should not be allocated with devm_kzalloc(). + * + * Ensures that the ddc field of the connector is correctly set. + * + * Note: consider using drmm_connector_init() instead of + * drm_connector_init_with_ddc() to let the DRM managed resource + * infrastructure take care of cleanup and deallocation. + * + * Returns: + * Zero on success, error code on failure. + */ +int drm_connector_init_with_ddc(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type, + struct i2c_adapter *ddc) +{ + if (drm_WARN_ON(dev, !(funcs && funcs->destroy))) + return -EINVAL; + + return drm_connector_init_and_add(dev, connector, funcs, connector_type, ddc); +} +EXPORT_SYMBOL(drm_connector_init_with_ddc); + +static void drm_connector_cleanup_action(struct drm_device *dev, + void *ptr) +{ + struct drm_connector *connector = ptr; + + drm_connector_cleanup(connector); +} + +/** + * drmm_connector_init - Init a preallocated connector + * @dev: DRM device + * @connector: the connector to init + * @funcs: callbacks for this connector + * @connector_type: user visible type of the connector + * @ddc: optional pointer to the associated ddc adapter + * + * Initialises a preallocated connector. Connectors should be + * subclassed as part of driver connector objects. + * + * Cleanup is automatically handled with a call to + * drm_connector_cleanup() in a DRM-managed action. + * + * The connector structure should be allocated with drmm_kzalloc(). + * + * The @drm_connector_funcs.destroy hook must be NULL. + * + * Returns: + * Zero on success, error code on failure. + */ +int drmm_connector_init(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type, + struct i2c_adapter *ddc) +{ + int ret; + + if (drm_WARN_ON(dev, funcs && funcs->destroy)) + return -EINVAL; + + ret = drm_connector_init_and_add(dev, connector, funcs, connector_type, ddc); + if (ret) + return ret; + + ret = drmm_add_action_or_reset(dev, drm_connector_cleanup_action, + connector); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL(drmm_connector_init); + +/** + * drmm_connector_hdmi_init - Init a preallocated HDMI connector + * @dev: DRM device + * @connector: A pointer to the HDMI connector to init + * @vendor: HDMI Controller Vendor name + * @product: HDMI Controller Product name + * @funcs: callbacks for this connector + * @hdmi_funcs: HDMI-related callbacks for this connector + * @connector_type: user visible type of the connector + * @ddc: optional pointer to the associated ddc adapter + * @supported_formats: Bitmask of @hdmi_colorspace listing supported output formats + * @max_bpc: Maximum bits per char the HDMI connector supports + * + * Initialises a preallocated HDMI connector. Connectors can be + * subclassed as part of driver connector objects. + * + * Cleanup is automatically handled with a call to + * drm_connector_cleanup() in a DRM-managed action. + * + * The connector structure should be allocated with drmm_kzalloc(). + * + * The @drm_connector_funcs.destroy hook must be NULL. + * + * Returns: + * Zero on success, error code on failure. + */ +int drmm_connector_hdmi_init(struct drm_device *dev, + struct drm_connector *connector, + const char *vendor, const char *product, + const struct drm_connector_funcs *funcs, + const struct drm_connector_hdmi_funcs *hdmi_funcs, + int connector_type, + struct i2c_adapter *ddc, + unsigned long supported_formats, + unsigned int max_bpc) +{ + int ret; + + if (!vendor || !product) + return -EINVAL; + + if ((strlen(vendor) > DRM_CONNECTOR_HDMI_VENDOR_LEN) || + (strlen(product) > DRM_CONNECTOR_HDMI_PRODUCT_LEN)) + return -EINVAL; + + if (!(connector_type == DRM_MODE_CONNECTOR_HDMIA || + connector_type == DRM_MODE_CONNECTOR_HDMIB)) + return -EINVAL; + + if (!supported_formats || !(supported_formats & BIT(HDMI_COLORSPACE_RGB))) + return -EINVAL; + + if (connector->ycbcr_420_allowed != !!(supported_formats & BIT(HDMI_COLORSPACE_YUV420))) + return -EINVAL; + + if (!(max_bpc == 8 || max_bpc == 10 || max_bpc == 12)) + return -EINVAL; + + ret = drmm_connector_init(dev, connector, funcs, connector_type, ddc); + if (ret) + return ret; + + connector->hdmi.supported_formats = supported_formats; + strtomem_pad(connector->hdmi.vendor, vendor, 0); + strtomem_pad(connector->hdmi.product, product, 0); + + /* + * drm_connector_attach_max_bpc_property() requires the + * connector to have a state. + */ + if (connector->funcs->reset) + connector->funcs->reset(connector); + + drm_connector_attach_max_bpc_property(connector, 8, max_bpc); + connector->max_bpc = max_bpc; + + if (max_bpc > 8) + drm_connector_attach_hdr_output_metadata_property(connector); + + connector->hdmi.funcs = hdmi_funcs; + + return 0; +} +EXPORT_SYMBOL(drmm_connector_hdmi_init); + +/** + * drm_connector_attach_edid_property - attach edid property. + * @connector: the connector + * + * Some connector types like DRM_MODE_CONNECTOR_VIRTUAL do not get a + * edid property attached by default. This function can be used to + * explicitly enable the edid property in these cases. + */ +void drm_connector_attach_edid_property(struct drm_connector *connector) +{ + struct drm_mode_config *config = &connector->dev->mode_config; + + drm_object_attach_property(&connector->base, + config->edid_property, + 0); +} +EXPORT_SYMBOL(drm_connector_attach_edid_property); + +/** + * drm_connector_attach_encoder - attach a connector to an encoder * @connector: connector to attach * @encoder: encoder to attach @connector to * @@ -265,11 +657,9 @@ EXPORT_SYMBOL(drm_connector_init); * Returns: * Zero on success, negative errno on failure. */ -int drm_mode_connector_attach_encoder(struct drm_connector *connector, - struct drm_encoder *encoder) +int drm_connector_attach_encoder(struct drm_connector *connector, + struct drm_encoder *encoder) { - int i; - /* * In the past, drivers have attempted to model the static association * of connector to encoder in simple connector/encoder devices using a @@ -284,15 +674,27 @@ int drm_mode_connector_attach_encoder(struct drm_connector *connector, if (WARN_ON(connector->encoder)) return -EINVAL; - for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { - if (connector->encoder_ids[i] == 0) { - connector->encoder_ids[i] = encoder->base.id; - return 0; - } - } - return -ENOMEM; + connector->possible_encoders |= drm_encoder_mask(encoder); + + return 0; } -EXPORT_SYMBOL(drm_mode_connector_attach_encoder); +EXPORT_SYMBOL(drm_connector_attach_encoder); + +/** + * drm_connector_has_possible_encoder - check if the connector and encoder are + * associated with each other + * @connector: the connector + * @encoder: the encoder + * + * Returns: + * True if @encoder is one of the possible encoders for @connector. + */ +bool drm_connector_has_possible_encoder(struct drm_connector *connector, + struct drm_encoder *encoder) +{ + return connector->possible_encoders & drm_encoder_mask(encoder); +} +EXPORT_SYMBOL(drm_connector_has_possible_encoder); static void drm_mode_remove(struct drm_connector *connector, struct drm_display_mode *mode) @@ -302,6 +704,46 @@ static void drm_mode_remove(struct drm_connector *connector, } /** + * drm_connector_cec_phys_addr_invalidate - invalidate CEC physical address + * @connector: connector undergoing CEC operation + * + * Invalidated CEC physical address set for this DRM connector. + */ +void drm_connector_cec_phys_addr_invalidate(struct drm_connector *connector) +{ + mutex_lock(&connector->cec.mutex); + + if (connector->cec.funcs && + connector->cec.funcs->phys_addr_invalidate) + connector->cec.funcs->phys_addr_invalidate(connector); + + mutex_unlock(&connector->cec.mutex); +} +EXPORT_SYMBOL(drm_connector_cec_phys_addr_invalidate); + +/** + * drm_connector_cec_phys_addr_set - propagate CEC physical address + * @connector: connector undergoing CEC operation + * + * Propagate CEC physical address from the display_info to this DRM connector. + */ +void drm_connector_cec_phys_addr_set(struct drm_connector *connector) +{ + u16 addr; + + mutex_lock(&connector->cec.mutex); + + addr = connector->display_info.source_physical_address; + + if (connector->cec.funcs && + connector->cec.funcs->phys_addr_set) + connector->cec.funcs->phys_addr_set(connector, addr); + + mutex_unlock(&connector->cec.mutex); +} +EXPORT_SYMBOL(drm_connector_cec_phys_addr_set); + +/** * drm_connector_cleanup - cleans up an initialised connector * @connector: connector to cleanup * @@ -315,9 +757,17 @@ void drm_connector_cleanup(struct drm_connector *connector) /* The connector should have been removed from userspace long before * it is finally destroyed. */ - if (WARN_ON(connector->registered)) + if (WARN_ON(connector->registration_state == + DRM_CONNECTOR_REGISTERED)) drm_connector_unregister(connector); + platform_device_unregister(connector->hdmi_audio.codec_pdev); + + if (connector->privacy_screen) { + drm_privacy_screen_put(connector->privacy_screen); + connector->privacy_screen = NULL; + } + if (connector->tile_group) { drm_mode_put_tile_group(dev, connector->tile_group); connector->tile_group = NULL; @@ -329,29 +779,34 @@ void drm_connector_cleanup(struct drm_connector *connector) list_for_each_entry_safe(mode, t, &connector->modes, head) drm_mode_remove(connector, mode); - ida_simple_remove(&drm_connector_enum_list[connector->connector_type].ida, + ida_free(&drm_connector_enum_list[connector->connector_type].ida, connector->connector_type_id); - ida_simple_remove(&dev->mode_config.connector_ida, - connector->index); + ida_free(&dev->mode_config.connector_ida, connector->index); kfree(connector->display_info.bus_formats); + kfree(connector->display_info.vics); drm_mode_object_unregister(dev, &connector->base); kfree(connector->name); connector->name = NULL; - spin_lock_irq(&dev->mode_config.connector_list_lock); - list_del(&connector->head); - dev->mode_config.num_connector--; - spin_unlock_irq(&dev->mode_config.connector_list_lock); + fwnode_handle_put(connector->fwnode); + connector->fwnode = NULL; + + drm_connector_remove(connector); WARN_ON(connector->state && !connector->funcs->atomic_destroy_state); if (connector->state && connector->funcs->atomic_destroy_state) connector->funcs->atomic_destroy_state(connector, connector->state); + mutex_destroy(&connector->hdmi_audio.lock); + mutex_destroy(&connector->hdmi.infoframes.lock); mutex_destroy(&connector->mutex); memset(connector, 0, sizeof(*connector)); + + if (dev->registered) + drm_sysfs_hotplug_event(dev); } EXPORT_SYMBOL(drm_connector_cleanup); @@ -359,7 +814,16 @@ EXPORT_SYMBOL(drm_connector_cleanup); * drm_connector_register - register a connector * @connector: the connector to register * - * Register userspace interfaces for a connector + * Register userspace interfaces for a connector. Drivers shouldn't call this + * function. Static connectors will be registered automatically by DRM core + * from drm_dev_register(), dynamic connectors (MST) should be registered by + * drivers calling drm_connector_dynamic_register(). + * + * When the connector is no longer available, callers must call + * drm_connector_unregister(). + * + * Note: Existing uses of this function in drivers should be a nop already and + * are scheduled to be removed. * * Returns: * Zero on success, error code on failure. @@ -372,17 +836,14 @@ int drm_connector_register(struct drm_connector *connector) return 0; mutex_lock(&connector->mutex); - if (connector->registered) + if (connector->registration_state != DRM_CONNECTOR_INITIALIZING) goto unlock; ret = drm_sysfs_connector_add(connector); if (ret) goto unlock; - ret = drm_debugfs_connector_add(connector); - if (ret) { - goto err_sysfs; - } + drm_debugfs_connector_add(connector); if (connector->funcs->late_register) { ret = connector->funcs->late_register(connector); @@ -390,14 +851,31 @@ int drm_connector_register(struct drm_connector *connector) goto err_debugfs; } + ret = drm_sysfs_connector_add_late(connector); + if (ret) + goto err_late_register; + drm_mode_object_register(connector->dev, &connector->base); - connector->registered = true; + connector->registration_state = DRM_CONNECTOR_REGISTERED; + + /* Let userspace know we have a new connector */ + drm_sysfs_connector_hotplug_event(connector); + + if (connector->privacy_screen) + drm_privacy_screen_register_notifier(connector->privacy_screen, + &connector->privacy_screen_notifier); + + mutex_lock(&connector_list_lock); + list_add_tail(&connector->global_connector_list_entry, &connector_list); + mutex_unlock(&connector_list_lock); goto unlock; +err_late_register: + if (connector->funcs->early_unregister) + connector->funcs->early_unregister(connector); err_debugfs: drm_debugfs_connector_remove(connector); -err_sysfs: drm_sysfs_connector_remove(connector); unlock: mutex_unlock(&connector->mutex); @@ -406,26 +884,70 @@ unlock: EXPORT_SYMBOL(drm_connector_register); /** + * drm_connector_dynamic_register - register a dynamic connector + * @connector: the connector to register + * + * Register userspace interfaces for a connector. Only call this for connectors + * initialized by calling drm_connector_dynamic_init(). All other connectors + * will be registered automatically when calling drm_dev_register(). + * + * When the connector is no longer available the driver must call + * drm_connector_unregister(). + * + * Returns: + * Zero on success, error code on failure. + */ +int drm_connector_dynamic_register(struct drm_connector *connector) +{ + /* Was the connector inited already? */ + if (WARN_ON(!(connector->funcs && connector->funcs->destroy))) + return -EINVAL; + + drm_connector_add(connector); + + return drm_connector_register(connector); +} +EXPORT_SYMBOL(drm_connector_dynamic_register); + +/** * drm_connector_unregister - unregister a connector * @connector: the connector to unregister * - * Unregister userspace interfaces for a connector + * Unregister userspace interfaces for a connector. Drivers should call this + * for dynamic connectors (MST) only, which were registered explicitly by + * calling drm_connector_dynamic_register(). All other - static - connectors + * will be unregistered automatically by DRM core and drivers shouldn't call + * this function for those. + * + * Note: Existing uses of this function in drivers for static connectors + * should be a nop already and are scheduled to be removed. */ void drm_connector_unregister(struct drm_connector *connector) { mutex_lock(&connector->mutex); - if (!connector->registered) { + if (connector->registration_state != DRM_CONNECTOR_REGISTERED) { mutex_unlock(&connector->mutex); return; } + mutex_lock(&connector_list_lock); + list_del_init(&connector->global_connector_list_entry); + mutex_unlock(&connector_list_lock); + + if (connector->privacy_screen) + drm_privacy_screen_unregister_notifier( + connector->privacy_screen, + &connector->privacy_screen_notifier); + + drm_sysfs_connector_remove_early(connector); + if (connector->funcs->early_unregister) connector->funcs->early_unregister(connector); - drm_sysfs_connector_remove(connector); drm_debugfs_connector_remove(connector); + drm_sysfs_connector_remove(connector); - connector->registered = false; + connector->registration_state = DRM_CONNECTOR_UNREGISTERED; mutex_unlock(&connector->mutex); } EXPORT_SYMBOL(drm_connector_unregister); @@ -466,6 +988,8 @@ int drm_connector_register_all(struct drm_device *dev) * * In contrast to the other drm_get_*_name functions this one here returns a * const pointer and hence is threadsafe. + * + * Returns: connector status string */ const char *drm_get_connector_status_name(enum drm_connector_status status) { @@ -525,11 +1049,30 @@ 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 + * @iter: connector_list iterator * - * Returns the next connector for @iter, or NULL when the list walk has + * Returns: the next connector for @iter, or NULL when the list walk has * completed. */ struct drm_connector * @@ -554,10 +1097,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; } @@ -574,10 +1117,16 @@ 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); - lock_release(&connector_list_iter_dep_map, 0, _RET_IP_); + 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, _RET_IP_); } EXPORT_SYMBOL(drm_connector_list_iter_end); @@ -596,6 +1145,8 @@ static const struct drm_prop_enum_list drm_subpixel_enum_list[] = { * * Note you could abuse this and return something out of bounds, but that * would be a caller error. No unscrubbed user data should make it here. + * + * Returns: string describing an enumerated subpixel property */ const char *drm_get_subpixel_order_name(enum subpixel_order order) { @@ -615,7 +1166,6 @@ static const struct drm_prop_enum_list drm_link_status_enum_list[] = { { DRM_MODE_LINK_STATUS_GOOD, "Good" }, { DRM_MODE_LINK_STATUS_BAD, "Bad" }, }; -DRM_ENUM_NAME_FN(drm_get_link_status_name, drm_link_status_enum_list) /** * drm_display_info_set_bus_formats - set the supported bus formats @@ -626,6 +1176,9 @@ DRM_ENUM_NAME_FN(drm_get_link_status_name, drm_link_status_enum_list) * Store the supported bus formats in display info structure. * See MEDIA_BUS_FMT_* definitions in include/uapi/linux/media-bus-format.h for * a full list of available formats. + * + * Returns: + * 0 on success or a negative error code on failure. */ int drm_display_info_set_bus_formats(struct drm_display_info *info, const u32 *formats, @@ -665,6 +1218,21 @@ 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_content_type_enum_list[] = { + { DRM_MODE_CONTENT_TYPE_NO_DATA, "No Data" }, + { DRM_MODE_CONTENT_TYPE_GRAPHICS, "Graphics" }, + { DRM_MODE_CONTENT_TYPE_PHOTO, "Photo" }, + { DRM_MODE_CONTENT_TYPE_CINEMA, "Cinema" }, + { DRM_MODE_CONTENT_TYPE_GAME, "Game" }, +}; + +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 */ @@ -673,13 +1241,49 @@ static const struct drm_prop_enum_list drm_dvi_i_select_enum_list[] = { DRM_ENUM_NAME_FN(drm_get_dvi_i_select_name, drm_dvi_i_select_enum_list) static const struct drm_prop_enum_list drm_dvi_i_subconnector_enum_list[] = { - { DRM_MODE_SUBCONNECTOR_Unknown, "Unknown" }, /* DVI-I and TV-out */ + { DRM_MODE_SUBCONNECTOR_Unknown, "Unknown" }, /* DVI-I, TV-out and DP */ { DRM_MODE_SUBCONNECTOR_DVID, "DVI-D" }, /* DVI-I */ { DRM_MODE_SUBCONNECTOR_DVIA, "DVI-A" }, /* DVI-I */ }; DRM_ENUM_NAME_FN(drm_get_dvi_i_subconnector_name, drm_dvi_i_subconnector_enum_list) +static const struct drm_prop_enum_list drm_tv_mode_enum_list[] = { + { DRM_MODE_TV_MODE_NTSC, "NTSC" }, + { DRM_MODE_TV_MODE_NTSC_443, "NTSC-443" }, + { DRM_MODE_TV_MODE_NTSC_J, "NTSC-J" }, + { DRM_MODE_TV_MODE_PAL, "PAL" }, + { DRM_MODE_TV_MODE_PAL_M, "PAL-M" }, + { DRM_MODE_TV_MODE_PAL_N, "PAL-N" }, + { DRM_MODE_TV_MODE_SECAM, "SECAM" }, + { DRM_MODE_TV_MODE_MONOCHROME, "Mono" }, +}; +DRM_ENUM_NAME_FN(drm_get_tv_mode_name, drm_tv_mode_enum_list) + +/** + * drm_get_tv_mode_from_name - Translates a TV mode name into its enum value + * @name: TV Mode name we want to convert + * @len: Length of @name + * + * Translates @name into an enum drm_connector_tv_mode. + * + * Returns: the enum value on success, a negative errno otherwise. + */ +int drm_get_tv_mode_from_name(const char *name, size_t len) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(drm_tv_mode_enum_list); i++) { + const struct drm_prop_enum_list *item = &drm_tv_mode_enum_list[i]; + + if (strlen(item->name) == len && !strncmp(item->name, name, len)) + return item->type; + } + + return -EINVAL; +} +EXPORT_SYMBOL(drm_get_tv_mode_from_name); + static const struct drm_prop_enum_list drm_tv_select_enum_list[] = { { DRM_MODE_SUBCONNECTOR_Automatic, "Automatic" }, /* DVI-I and TV-out */ { DRM_MODE_SUBCONNECTOR_Composite, "Composite" }, /* TV-out */ @@ -690,7 +1294,7 @@ static const struct drm_prop_enum_list drm_tv_select_enum_list[] = { DRM_ENUM_NAME_FN(drm_get_tv_select_name, drm_tv_select_enum_list) static const struct drm_prop_enum_list drm_tv_subconnector_enum_list[] = { - { DRM_MODE_SUBCONNECTOR_Unknown, "Unknown" }, /* DVI-I and TV-out */ + { DRM_MODE_SUBCONNECTOR_Unknown, "Unknown" }, /* DVI-I, TV-out and DP */ { DRM_MODE_SUBCONNECTOR_Composite, "Composite" }, /* TV-out */ { DRM_MODE_SUBCONNECTOR_SVIDEO, "SVIDEO" }, /* TV-out */ { DRM_MODE_SUBCONNECTOR_Component, "Component" }, /* TV-out */ @@ -699,6 +1303,146 @@ static const struct drm_prop_enum_list drm_tv_subconnector_enum_list[] = { DRM_ENUM_NAME_FN(drm_get_tv_subconnector_name, drm_tv_subconnector_enum_list) +static const struct drm_prop_enum_list drm_dp_subconnector_enum_list[] = { + { DRM_MODE_SUBCONNECTOR_Unknown, "Unknown" }, /* DVI-I, TV-out and DP */ + { DRM_MODE_SUBCONNECTOR_VGA, "VGA" }, /* DP */ + { DRM_MODE_SUBCONNECTOR_DVID, "DVI-D" }, /* DP */ + { DRM_MODE_SUBCONNECTOR_HDMIA, "HDMI" }, /* DP */ + { DRM_MODE_SUBCONNECTOR_DisplayPort, "DP" }, /* DP */ + { DRM_MODE_SUBCONNECTOR_Wireless, "Wireless" }, /* DP */ + { DRM_MODE_SUBCONNECTOR_Native, "Native" }, /* DP */ +}; + +DRM_ENUM_NAME_FN(drm_get_dp_subconnector_name, + drm_dp_subconnector_enum_list) + + +static const char * const colorspace_names[] = { + /* For Default case, driver will set the colorspace */ + [DRM_MODE_COLORIMETRY_DEFAULT] = "Default", + /* Standard Definition Colorimetry based on CEA 861 */ + [DRM_MODE_COLORIMETRY_SMPTE_170M_YCC] = "SMPTE_170M_YCC", + [DRM_MODE_COLORIMETRY_BT709_YCC] = "BT709_YCC", + /* Standard Definition Colorimetry based on IEC 61966-2-4 */ + [DRM_MODE_COLORIMETRY_XVYCC_601] = "XVYCC_601", + /* High Definition Colorimetry based on IEC 61966-2-4 */ + [DRM_MODE_COLORIMETRY_XVYCC_709] = "XVYCC_709", + /* Colorimetry based on IEC 61966-2-1/Amendment 1 */ + [DRM_MODE_COLORIMETRY_SYCC_601] = "SYCC_601", + /* Colorimetry based on IEC 61966-2-5 [33] */ + [DRM_MODE_COLORIMETRY_OPYCC_601] = "opYCC_601", + /* Colorimetry based on IEC 61966-2-5 */ + [DRM_MODE_COLORIMETRY_OPRGB] = "opRGB", + /* Colorimetry based on ITU-R BT.2020 */ + [DRM_MODE_COLORIMETRY_BT2020_CYCC] = "BT2020_CYCC", + /* Colorimetry based on ITU-R BT.2020 */ + [DRM_MODE_COLORIMETRY_BT2020_RGB] = "BT2020_RGB", + /* Colorimetry based on ITU-R BT.2020 */ + [DRM_MODE_COLORIMETRY_BT2020_YCC] = "BT2020_YCC", + /* Added as part of Additional Colorimetry Extension in 861.G */ + [DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65] = "DCI-P3_RGB_D65", + [DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER] = "DCI-P3_RGB_Theater", + [DRM_MODE_COLORIMETRY_RGB_WIDE_FIXED] = "RGB_WIDE_FIXED", + /* Colorimetry based on scRGB (IEC 61966-2-2) */ + [DRM_MODE_COLORIMETRY_RGB_WIDE_FLOAT] = "RGB_WIDE_FLOAT", + [DRM_MODE_COLORIMETRY_BT601_YCC] = "BT601_YCC", +}; + +/** + * drm_get_colorspace_name - return a string for color encoding + * @colorspace: color space to compute name of + * + * In contrast to the other drm_get_*_name functions this one here returns a + * const pointer and hence is threadsafe. + */ +const char *drm_get_colorspace_name(enum drm_colorspace colorspace) +{ + if (colorspace < ARRAY_SIZE(colorspace_names) && colorspace_names[colorspace]) + return colorspace_names[colorspace]; + else + return "(null)"; +} + +static const u32 hdmi_colorspaces = + BIT(DRM_MODE_COLORIMETRY_SMPTE_170M_YCC) | + BIT(DRM_MODE_COLORIMETRY_BT709_YCC) | + BIT(DRM_MODE_COLORIMETRY_XVYCC_601) | + BIT(DRM_MODE_COLORIMETRY_XVYCC_709) | + BIT(DRM_MODE_COLORIMETRY_SYCC_601) | + BIT(DRM_MODE_COLORIMETRY_OPYCC_601) | + BIT(DRM_MODE_COLORIMETRY_OPRGB) | + BIT(DRM_MODE_COLORIMETRY_BT2020_CYCC) | + BIT(DRM_MODE_COLORIMETRY_BT2020_RGB) | + BIT(DRM_MODE_COLORIMETRY_BT2020_YCC) | + BIT(DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65) | + BIT(DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER); + +/* + * As per DP 1.4a spec, 2.2.5.7.5 VSC SDP Payload for Pixel Encoding/Colorimetry + * Format Table 2-120 + */ +static const u32 dp_colorspaces = + BIT(DRM_MODE_COLORIMETRY_RGB_WIDE_FIXED) | + BIT(DRM_MODE_COLORIMETRY_RGB_WIDE_FLOAT) | + BIT(DRM_MODE_COLORIMETRY_OPRGB) | + BIT(DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65) | + BIT(DRM_MODE_COLORIMETRY_BT2020_RGB) | + BIT(DRM_MODE_COLORIMETRY_BT601_YCC) | + BIT(DRM_MODE_COLORIMETRY_BT709_YCC) | + BIT(DRM_MODE_COLORIMETRY_XVYCC_601) | + BIT(DRM_MODE_COLORIMETRY_XVYCC_709) | + BIT(DRM_MODE_COLORIMETRY_SYCC_601) | + BIT(DRM_MODE_COLORIMETRY_OPYCC_601) | + BIT(DRM_MODE_COLORIMETRY_BT2020_CYCC) | + BIT(DRM_MODE_COLORIMETRY_BT2020_YCC); + +static const struct drm_prop_enum_list broadcast_rgb_names[] = { + { DRM_HDMI_BROADCAST_RGB_AUTO, "Automatic" }, + { DRM_HDMI_BROADCAST_RGB_FULL, "Full" }, + { DRM_HDMI_BROADCAST_RGB_LIMITED, "Limited 16:235" }, +}; + +/* + * drm_hdmi_connector_get_broadcast_rgb_name - Return a string for HDMI connector RGB broadcast selection + * @broadcast_rgb: Broadcast RGB selection to compute name of + * + * Returns: the name of the Broadcast RGB selection, or NULL if the type + * is not valid. + */ +const char * +drm_hdmi_connector_get_broadcast_rgb_name(enum drm_hdmi_broadcast_rgb broadcast_rgb) +{ + if (broadcast_rgb >= ARRAY_SIZE(broadcast_rgb_names)) + return NULL; + + return broadcast_rgb_names[broadcast_rgb].name; +} +EXPORT_SYMBOL(drm_hdmi_connector_get_broadcast_rgb_name); + +static const char * const output_format_str[] = { + [HDMI_COLORSPACE_RGB] = "RGB", + [HDMI_COLORSPACE_YUV420] = "YUV 4:2:0", + [HDMI_COLORSPACE_YUV422] = "YUV 4:2:2", + [HDMI_COLORSPACE_YUV444] = "YUV 4:4:4", +}; + +/* + * drm_hdmi_connector_get_output_format_name() - Return a string for HDMI connector output format + * @fmt: Output format to compute name of + * + * Returns: the name of the output format, or NULL if the type is not + * valid. + */ +const char * +drm_hdmi_connector_get_output_format_name(enum hdmi_colorspace fmt) +{ + if (fmt >= ARRAY_SIZE(output_format_str)) + return NULL; + + return output_format_str[fmt]; +} +EXPORT_SYMBOL(drm_hdmi_connector_get_output_format_name); + /** * DOC: standard connector properties * @@ -708,24 +1452,61 @@ DRM_ENUM_NAME_FN(drm_get_tv_subconnector_name, * Blob property which contains the current EDID read from the sink. This * is useful to parse sink identification information like vendor, model * and serial. Drivers should update this property by calling - * drm_mode_connector_update_edid_property(), usually after having parsed + * drm_connector_update_edid_property(), usually after having parsed * the EDID using drm_add_edid_modes(). Userspace cannot change this * property. + * + * User-space should not parse the EDID to obtain information exposed via + * other KMS properties (because the kernel might apply limits, quirks or + * fixups to the EDID). For instance, user-space should not try to parse + * mode lists from the EDID. * DPMS: * Legacy property for setting the power state of the connector. For atomic * drivers this is only provided for backwards compatibility with existing * drivers, it remaps to controlling the "ACTIVE" property on the CRTC the * connector is linked to. Drivers should never set this property directly, * it is handled by the DRM core by calling the &drm_connector_funcs.dpms - * callback. Atomic drivers should implement this hook using - * drm_atomic_helper_connector_dpms(). This is the only property standard - * connector property that userspace can change. + * callback. For atomic drivers the remapping to the "ACTIVE" property is + * implemented in the DRM core. + * + * On atomic drivers any DPMS setproperty ioctl where the value does not + * change is completely skipped, otherwise a full atomic commit will occur. + * On legacy drivers the exact behavior is driver specific. + * + * Note that this property cannot be set through the MODE_ATOMIC ioctl, + * userspace must use "ACTIVE" on the CRTC instead. + * + * WARNING: + * + * For userspace also running on legacy drivers the "DPMS" semantics are a + * lot more complicated. First, userspace cannot rely on the "DPMS" value + * returned by the GETCONNECTOR actually reflecting reality, because many + * drivers fail to update it. For atomic drivers this is taken care of in + * drm_atomic_helper_update_legacy_modeset_state(). + * + * The second issue is that the DPMS state is only well-defined when the + * connector is connected to a CRTC. In atomic the DRM core enforces that + * "ACTIVE" is off in such a case, no such checks exists for "DPMS". + * + * Finally, when enabling an output using the legacy SETCONFIG ioctl then + * "DPMS" is forced to ON. But see above, that might not be reflected in + * the software value on legacy drivers. + * + * Summarizing: Only set "DPMS" when the connector is known to be enabled, + * assume that a successful SETCONFIG call also sets "DPMS" to on, and + * never read back the value of "DPMS" because it can be incorrect. * PATH: * Connector path property to identify how this sink is physically * connected. Used by DP MST. This should be set by calling - * drm_mode_connector_set_path_property(), in the case of DP MST with the + * drm_connector_set_path_property(), in the case of DP MST with the * path property the MST manager created. Userspace cannot change this * property. + * + * In the case of DP MST, the property has the format + * ``mst:<parent>-<ports>`` where ``<parent>`` is the KMS object ID of the + * parent connector and ``<ports>`` is a hyphen-separated list of DP MST + * port numbers. Note, KMS object IDs are not guaranteed to be stable + * across reboots. * TILE: * Connector tile group property to indicate how a set of DRM connector * compose together into one logical screen. This is used by both high-res @@ -733,19 +1514,294 @@ DRM_ENUM_NAME_FN(drm_get_tv_subconnector_name, * DP MST sinks), or high-res integrated panels (like dual-link DSI) which * are not gen-locked. Note that for tiled panels which are genlocked, like * dual-link LVDS or dual-link DSI, the driver should try to not expose the - * tiling and virtualize both &drm_crtc and &drm_plane if needed. Drivers - * should update this value using drm_mode_connector_set_tile_property(). + * tiling and virtualise both &drm_crtc and &drm_plane if needed. Drivers + * should update this value using drm_connector_set_tile_property(). * Userspace cannot change this property. * link-status: - * Connector link-status property to indicate the status of link. The default - * value of link-status is "GOOD". If something fails during or after modeset, - * the kernel driver may set this to "BAD" and issue a hotplug uevent. Drivers - * should update this value using drm_mode_connector_set_link_status_property(). + * Connector link-status property to indicate the status of link. The + * default value of link-status is "GOOD". If something fails during or + * after modeset, the kernel driver may set this to "BAD" and issue a + * hotplug uevent. Drivers should update this value using + * drm_connector_set_link_status_property(). + * + * When user-space receives the hotplug uevent and detects a "BAD" + * link-status, the sink doesn't receive pixels anymore (e.g. the screen + * becomes completely black). The list of available modes may have + * changed. User-space is expected to pick a new mode if the current one + * has disappeared and perform a new modeset with link-status set to + * "GOOD" to re-enable the connector. + * + * If multiple connectors share the same CRTC and one of them gets a "BAD" + * link-status, the other are unaffected (ie. the sinks still continue to + * receive pixels). + * + * When user-space performs an atomic commit on a connector with a "BAD" + * link-status without resetting the property to "GOOD", the sink may + * still not receive pixels. When user-space performs an atomic commit + * which resets the link-status property to "GOOD" without the + * ALLOW_MODESET flag set, it might fail because a modeset is required. + * + * User-space can only change link-status to "GOOD", changing it to "BAD" + * is a no-op. + * + * For backwards compatibility with non-atomic userspace the kernel + * tries to automatically set the link-status back to "GOOD" in the + * SETCRTC IOCTL. This might fail if the mode is no longer valid, similar + * to how it might fail if a different screen has been connected in the + * interim. + * non_desktop: + * Indicates the output should be ignored for purposes of displaying a + * standard desktop environment or console. This is most likely because + * the output device is not rectilinear. + * Content Protection: + * This property is used by userspace to request the kernel protect future + * content communicated over the link. When requested, kernel will apply + * the appropriate means of protection (most often HDCP), and use the + * property to tell userspace the protection is active. + * + * Drivers can set this up by calling + * drm_connector_attach_content_protection_property() on initialization. + * + * The value of this property can be one of the following: + * + * DRM_MODE_CONTENT_PROTECTION_UNDESIRED = 0 + * The link is not protected, content is transmitted in the clear. + * DRM_MODE_CONTENT_PROTECTION_DESIRED = 1 + * Userspace has requested content protection, but the link is not + * currently protected. When in this state, kernel should enable + * Content Protection as soon as possible. + * DRM_MODE_CONTENT_PROTECTION_ENABLED = 2 + * Userspace has requested content protection, and the link is + * protected. Only the driver can set the property to this value. + * If userspace attempts to set to ENABLED, kernel will return + * -EINVAL. + * + * A few guidelines: + * + * - DESIRED state should be preserved until userspace de-asserts it by + * setting the property to UNDESIRED. This means ENABLED should only + * transition to UNDESIRED when the user explicitly requests it. + * - If the state is DESIRED, kernel should attempt to re-authenticate the + * link whenever possible. This includes across disable/enable, dpms, + * hotplug, downstream device changes, link status failures, etc.. + * - Kernel sends uevent with the connector id and property id through + * @drm_hdcp_update_content_protection, upon below kernel triggered + * scenarios: + * + * - DESIRED -> ENABLED (authentication success) + * - ENABLED -> DESIRED (termination of authentication) + * - Please note no uevents for userspace triggered property state changes, + * which can't fail such as + * + * - DESIRED/ENABLED -> UNDESIRED + * - UNDESIRED -> DESIRED + * - Userspace is responsible for polling the property or listen to uevents + * to determine when the value transitions from ENABLED to DESIRED. + * This signifies the link is no longer protected and userspace should + * take appropriate action (whatever that might be). + * + * HDCP Content Type: + * This Enum property is used by the userspace to declare the content type + * of the display stream, to kernel. Here display stream stands for any + * display content that userspace intended to display through HDCP + * encryption. + * + * Content Type of a stream is decided by the owner of the stream, as + * "HDCP Type0" or "HDCP Type1". + * + * The value of the property can be one of the below: + * - "HDCP Type0": DRM_MODE_HDCP_CONTENT_TYPE0 = 0 + * - "HDCP Type1": DRM_MODE_HDCP_CONTENT_TYPE1 = 1 + * + * When kernel starts the HDCP authentication (see "Content Protection" + * for details), it uses the content type in "HDCP Content Type" + * for performing the HDCP authentication with the display sink. + * + * Please note in HDCP spec versions, a link can be authenticated with + * HDCP 2.2 for Content Type 0/Content Type 1. Where as a link can be + * authenticated with HDCP1.4 only for Content Type 0(though it is implicit + * in nature. As there is no reference for Content Type in HDCP1.4). + * + * HDCP2.2 authentication protocol itself takes the "Content Type" as a + * parameter, which is a input for the DP HDCP2.2 encryption algo. + * + * In case of Type 0 content protection request, kernel driver can choose + * either of HDCP spec versions 1.4 and 2.2. When HDCP2.2 is used for + * "HDCP Type 0", a HDCP 2.2 capable repeater in the downstream can send + * that content to a HDCP 1.4 authenticated HDCP sink (Type0 link). + * But if the content is classified as "HDCP Type 1", above mentioned + * HDCP 2.2 repeater wont send the content to the HDCP sink as it can't + * authenticate the HDCP1.4 capable sink for "HDCP Type 1". + * + * Please note userspace can be ignorant of the HDCP versions used by the + * kernel driver to achieve the "HDCP Content Type". + * + * At current scenario, classifying a content as Type 1 ensures that the + * content will be displayed only through the HDCP2.2 encrypted link. + * + * Note that the HDCP Content Type property is introduced at HDCP 2.2, and + * defaults to type 0. It is only exposed by drivers supporting HDCP 2.2 + * (hence supporting Type 0 and Type 1). Based on how next versions of + * HDCP specs are defined content Type could be used for higher versions + * too. + * + * If content type is changed when "Content Protection" is not UNDESIRED, + * then kernel will disable the HDCP and re-enable with new type in the + * same atomic commit. And when "Content Protection" is ENABLED, it means + * that link is HDCP authenticated and encrypted, for the transmission of + * the Type of stream mentioned at "HDCP Content Type". + * + * HDR_OUTPUT_METADATA: + * Connector property to enable userspace to send HDR Metadata to + * driver. This metadata is based on the composition and blending + * policies decided by user, taking into account the hardware and + * sink capabilities. The driver gets this metadata and creates a + * Dynamic Range and Mastering Infoframe (DRM) in case of HDMI, + * SDP packet (Non-audio INFOFRAME SDP v1.3) for DP. This is then + * sent to sink. This notifies the sink of the upcoming frame's Color + * Encoding and Luminance parameters. + * + * Userspace first need to detect the HDR capabilities of sink by + * reading and parsing the EDID. Details of HDR metadata for HDMI + * are added in CTA 861.G spec. For DP , its defined in VESA DP + * Standard v1.4. It needs to then get the metadata information + * of the video/game/app content which are encoded in HDR (basically + * using HDR transfer functions). With this information it needs to + * decide on a blending policy and compose the relevant + * layers/overlays into a common format. Once this blending is done, + * userspace will be aware of the metadata of the composed frame to + * be send to sink. It then uses this property to communicate this + * metadata to driver which then make a Infoframe packet and sends + * to sink based on the type of encoder connected. + * + * Userspace will be responsible to do Tone mapping operation in case: + * - Some layers are HDR and others are SDR + * - HDR layers luminance is not same as sink + * + * It will even need to do colorspace conversion and get all layers + * to one common colorspace for blending. It can use either GL, Media + * or display engine to get this done based on the capabilities of the + * associated hardware. + * + * Driver expects metadata to be put in &struct hdr_output_metadata + * structure from userspace. This is received as blob and stored in + * &drm_connector_state.hdr_output_metadata. It parses EDID and saves the + * sink metadata in &struct hdr_sink_metadata, as + * &drm_connector.display_info.hdr_sink_metadata. Driver uses + * drm_hdmi_infoframe_set_hdr_metadata() helper to set the HDR metadata, + * hdmi_drm_infoframe_pack() to pack the infoframe as per spec, in case of + * HDMI encoder. + * + * max bpc: + * This range property is used by userspace to limit the bit depth. When + * used the driver would limit the bpc in accordance with the valid range + * supported by the hardware and sink. Drivers to use the function + * drm_connector_attach_max_bpc_property() to create and attach the + * property to the connector during initialization. * * Connectors also have one standardized atomic property: * * 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. This property is initialized by calling + * drm_connector_set_panel_orientation() or + * drm_connector_set_panel_orientation_with_quirk() + * + * scaling mode: + * This property defines how a non-native mode is upscaled to the native + * mode of an LCD panel: + * + * None: + * No upscaling happens, scaling is left to the panel. Not all + * drivers expose this mode. + * Full: + * The output is upscaled to the full resolution of the panel, + * ignoring the aspect ratio. + * Center: + * No upscaling happens, the output is centered within the native + * resolution the panel. + * Full aspect: + * The output is upscaled to maximize either the width or height + * while retaining the aspect ratio. + * + * This property should be set up by calling + * drm_connector_attach_scaling_mode_property(). Note that drivers + * can also expose this property to external outputs, in which case they + * must support "None", which should be the default (since external screens + * have a built-in scaler). + * + * subconnector: + * This property is used by DVI-I, TVout and DisplayPort to indicate different + * connector subtypes. Enum values more or less match with those from main + * connector types. + * For DVI-I and TVout there is also a matching property "select subconnector" + * allowing to switch between signal types. + * DP subconnector corresponds to a downstream port. + * + * privacy-screen sw-state, privacy-screen hw-state: + * These 2 optional properties can be used to query the state of the + * electronic privacy screen that is available on some displays; and in + * some cases also control the state. If a driver implements these + * properties then both properties must be present. + * + * "privacy-screen hw-state" is read-only and reflects the actual state + * of the privacy-screen, possible values: "Enabled", "Disabled, + * "Enabled-locked", "Disabled-locked". The locked states indicate + * that the state cannot be changed through the DRM API. E.g. there + * might be devices where the firmware-setup options, or a hardware + * slider-switch, offer always on / off modes. + * + * "privacy-screen sw-state" can be set to change the privacy-screen state + * when not locked. In this case the driver must update the hw-state + * property to reflect the new state on completion of the commit of the + * sw-state property. Setting the sw-state property when the hw-state is + * locked must be interpreted by the driver as a request to change the + * state to the set state when the hw-state becomes unlocked. E.g. if + * "privacy-screen hw-state" is "Enabled-locked" and the sw-state + * gets set to "Disabled" followed by the user unlocking the state by + * changing the slider-switch position, then the driver must set the + * state to "Disabled" upon receiving the unlock event. + * + * In some cases the privacy-screen's actual state might change outside of + * control of the DRM code. E.g. there might be a firmware handled hotkey + * which toggles the actual state, or the actual state might be changed + * through another userspace API such as writing /proc/acpi/ibm/lcdshadow. + * In this case the driver must update both the hw-state and the sw-state + * to reflect the new value, overwriting any pending state requests in the + * sw-state. Any pending sw-state requests are thus discarded. + * + * Note that the ability for the state to change outside of control of + * the DRM master process means that userspace must not cache the value + * of the sw-state. Caching the sw-state value and including it in later + * atomic commits may lead to overriding a state change done through e.g. + * a firmware handled hotkey. Therefor userspace must not include the + * privacy-screen sw-state in an atomic commit unless it wants to change + * its value. + * + * left margin, right margin, top margin, bottom margin: + * Add margins to the connector's viewport. This is typically used to + * mitigate overscan on TVs. + * + * The value is the size in pixels of the black border which will be + * added. The attached CRTC's content will be scaled to fill the whole + * area inside the margin. + * + * The margins configuration might be sent to the sink, e.g. via HDMI AVI + * InfoFrames. + * + * Drivers can set up these properties by calling + * drm_mode_create_tv_margin_properties(). */ int drm_connector_create_standard_properties(struct drm_device *dev) @@ -789,6 +1845,17 @@ int drm_connector_create_standard_properties(struct drm_device *dev) return -ENOMEM; dev->mode_config.link_status_property = prop; + prop = drm_property_create_bool(dev, DRM_MODE_PROP_IMMUTABLE, "non-desktop"); + if (!prop) + return -ENOMEM; + dev->mode_config.non_desktop_property = prop; + + prop = drm_property_create(dev, DRM_MODE_PROP_BLOB, + "HDR_OUTPUT_METADATA", 0); + if (!prop) + return -ENOMEM; + dev->mode_config.hdr_output_metadata_property = prop; + return 0; } @@ -797,6 +1864,8 @@ int drm_connector_create_standard_properties(struct drm_device *dev) * @dev: DRM device * * Called by a driver the first time a DVI-I connector is made. + * + * Returns: %0 */ int drm_mode_create_dvi_i_properties(struct drm_device *dev) { @@ -824,7 +1893,243 @@ int drm_mode_create_dvi_i_properties(struct drm_device *dev) EXPORT_SYMBOL(drm_mode_create_dvi_i_properties); /** - * drm_create_tv_properties - create TV specific connector properties + * drm_connector_attach_dp_subconnector_property - create subconnector property for DP + * @connector: drm_connector to attach property + * + * Called by a driver when DP connector is created. + */ +void drm_connector_attach_dp_subconnector_property(struct drm_connector *connector) +{ + struct drm_mode_config *mode_config = &connector->dev->mode_config; + + if (!mode_config->dp_subconnector_property) + mode_config->dp_subconnector_property = + drm_property_create_enum(connector->dev, + DRM_MODE_PROP_IMMUTABLE, + "subconnector", + drm_dp_subconnector_enum_list, + ARRAY_SIZE(drm_dp_subconnector_enum_list)); + + drm_object_attach_property(&connector->base, + mode_config->dp_subconnector_property, + DRM_MODE_SUBCONNECTOR_Unknown); +} +EXPORT_SYMBOL(drm_connector_attach_dp_subconnector_property); + +/** + * DOC: HDMI connector properties + * + * Broadcast RGB (HDMI specific) + * Indicates the Quantization Range (Full vs Limited) used. The color + * processing pipeline will be adjusted to match the value of the + * property, and the Infoframes will be generated and sent accordingly. + * + * This property is only relevant if the HDMI output format is RGB. If + * it's one of the YCbCr variant, it will be ignored. + * + * The CRTC attached to the connector must be configured by user-space to + * always produce full-range pixels. + * + * The value of this property can be one of the following: + * + * Automatic: + * The quantization range is selected automatically based on the + * mode according to the HDMI specifications (HDMI 1.4b - Section + * 6.6 - Video Quantization Ranges). + * + * Full: + * Full quantization range is forced. + * + * Limited 16:235: + * Limited quantization range is forced. Unlike the name suggests, + * this works for any number of bits-per-component. + * + * Property values other than Automatic can result in colors being off (if + * limited is selected but the display expects full), or a black screen + * (if full is selected but the display expects limited). + * + * Drivers can set up this property by calling + * drm_connector_attach_broadcast_rgb_property(). + * + * content type (HDMI specific): + * Indicates content type setting to be used in HDMI infoframes to indicate + * content type for the external device, so that it adjusts its display + * settings accordingly. + * + * The value of this property can be one of the following: + * + * No Data: + * Content type is unknown + * Graphics: + * Content type is graphics + * Photo: + * Content type is photo + * Cinema: + * Content type is cinema + * Game: + * Content type is game + * + * The meaning of each content type is defined in CTA-861-G table 15. + * + * Drivers can set up this property by calling + * drm_connector_attach_content_type_property(). Decoding to + * infoframe values is done through drm_hdmi_avi_infoframe_content_type(). + */ + +/* + * TODO: Document the properties: + * - brightness + * - contrast + * - flicker reduction + * - hue + * - mode + * - overscan + * - saturation + * - select subconnector + */ +/** + * DOC: Analog TV Connector Properties + * + * TV Mode: + * Indicates the TV Mode used on an analog TV connector. The value + * of this property can be one of the following: + * + * NTSC: + * TV Mode is CCIR System M (aka 525-lines) together with + * the NTSC Color Encoding. + * + * NTSC-443: + * + * TV Mode is CCIR System M (aka 525-lines) together with + * the NTSC Color Encoding, but with a color subcarrier + * frequency of 4.43MHz + * + * NTSC-J: + * + * TV Mode is CCIR System M (aka 525-lines) together with + * the NTSC Color Encoding, but with a black level equal to + * the blanking level. + * + * PAL: + * + * TV Mode is CCIR System B (aka 625-lines) together with + * the PAL Color Encoding. + * + * PAL-M: + * + * TV Mode is CCIR System M (aka 525-lines) together with + * the PAL Color Encoding. + * + * PAL-N: + * + * TV Mode is CCIR System N together with the PAL Color + * Encoding, a color subcarrier frequency of 3.58MHz, the + * SECAM color space, and narrower channels than other PAL + * variants. + * + * SECAM: + * + * TV Mode is CCIR System B (aka 625-lines) together with + * the SECAM Color Encoding. + * + * Mono: + * + * Use timings appropriate to the DRM mode, including + * equalizing pulses for a 525-line or 625-line mode, + * with no pedestal or color encoding. + * + * Drivers can set up this property by calling + * drm_mode_create_tv_properties(). + */ + +/** + * drm_connector_attach_content_type_property - attach content-type property + * @connector: connector to attach content type property on. + * + * Called by a driver the first time a HDMI connector is made. + * + * Returns: %0 + */ +int drm_connector_attach_content_type_property(struct drm_connector *connector) +{ + if (!drm_mode_create_content_type_property(connector->dev)) + drm_object_attach_property(&connector->base, + connector->dev->mode_config.content_type_property, + DRM_MODE_CONTENT_TYPE_NO_DATA); + return 0; +} +EXPORT_SYMBOL(drm_connector_attach_content_type_property); + +/** + * drm_connector_attach_tv_margin_properties - attach TV connector margin + * properties + * @connector: DRM connector + * + * Called by a driver when it needs to attach TV margin props to a connector. + * Typically used on SDTV and HDMI connectors. + */ +void drm_connector_attach_tv_margin_properties(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + + drm_object_attach_property(&connector->base, + dev->mode_config.tv_left_margin_property, + 0); + drm_object_attach_property(&connector->base, + dev->mode_config.tv_right_margin_property, + 0); + drm_object_attach_property(&connector->base, + dev->mode_config.tv_top_margin_property, + 0); + drm_object_attach_property(&connector->base, + dev->mode_config.tv_bottom_margin_property, + 0); +} +EXPORT_SYMBOL(drm_connector_attach_tv_margin_properties); + +/** + * drm_mode_create_tv_margin_properties - create TV connector margin properties + * @dev: DRM device + * + * Called by a driver's HDMI connector initialization routine, this function + * creates the TV margin properties for a given device. No need to call this + * function for an SDTV connector, it's already called from + * drm_mode_create_tv_properties_legacy(). + * + * Returns: + * 0 on success or a negative error code on failure. + */ +int drm_mode_create_tv_margin_properties(struct drm_device *dev) +{ + if (dev->mode_config.tv_left_margin_property) + return 0; + + dev->mode_config.tv_left_margin_property = + drm_property_create_range(dev, 0, "left margin", 0, 100); + if (!dev->mode_config.tv_left_margin_property) + return -ENOMEM; + + dev->mode_config.tv_right_margin_property = + drm_property_create_range(dev, 0, "right margin", 0, 100); + if (!dev->mode_config.tv_right_margin_property) + return -ENOMEM; + + dev->mode_config.tv_top_margin_property = + drm_property_create_range(dev, 0, "top margin", 0, 100); + if (!dev->mode_config.tv_top_margin_property) + return -ENOMEM; + + dev->mode_config.tv_bottom_margin_property = + drm_property_create_range(dev, 0, "bottom margin", 0, 100); + if (!dev->mode_config.tv_bottom_margin_property) + return -ENOMEM; + + return 0; +} +EXPORT_SYMBOL(drm_mode_create_tv_margin_properties); + +/** + * drm_mode_create_tv_properties_legacy - create TV specific connector properties * @dev: DRM device * @num_modes: number of different TV formats (modes) supported * @modes: array of pointers to strings containing name of each format @@ -833,10 +2138,17 @@ EXPORT_SYMBOL(drm_mode_create_dvi_i_properties); * the TV specific connector properties for a given device. Caller is * responsible for allocating a list of format names and passing them to * this routine. + * + * NOTE: This functions registers the deprecated "mode" connector + * property to select the analog TV mode (ie, NTSC, PAL, etc.). New + * drivers must use drm_mode_create_tv_properties() instead. + * + * Returns: + * 0 on success or a negative error code on failure. */ -int drm_mode_create_tv_properties(struct drm_device *dev, - unsigned int num_modes, - const char * const modes[]) +int drm_mode_create_tv_properties_legacy(struct drm_device *dev, + unsigned int num_modes, + const char * const modes[]) { struct drm_property *tv_selector; struct drm_property *tv_subconnector; @@ -869,35 +2181,20 @@ int drm_mode_create_tv_properties(struct drm_device *dev, /* * Other, TV specific properties: margins & TV modes. */ - dev->mode_config.tv_left_margin_property = - drm_property_create_range(dev, 0, "left margin", 0, 100); - if (!dev->mode_config.tv_left_margin_property) - goto nomem; - - dev->mode_config.tv_right_margin_property = - drm_property_create_range(dev, 0, "right margin", 0, 100); - if (!dev->mode_config.tv_right_margin_property) + if (drm_mode_create_tv_margin_properties(dev)) goto nomem; - dev->mode_config.tv_top_margin_property = - drm_property_create_range(dev, 0, "top margin", 0, 100); - if (!dev->mode_config.tv_top_margin_property) - goto nomem; + if (num_modes) { + dev->mode_config.legacy_tv_mode_property = + drm_property_create(dev, DRM_MODE_PROP_ENUM, + "mode", num_modes); + if (!dev->mode_config.legacy_tv_mode_property) + goto nomem; - dev->mode_config.tv_bottom_margin_property = - drm_property_create_range(dev, 0, "bottom margin", 0, 100); - if (!dev->mode_config.tv_bottom_margin_property) - goto nomem; - - dev->mode_config.tv_mode_property = - drm_property_create(dev, DRM_MODE_PROP_ENUM, - "mode", num_modes); - if (!dev->mode_config.tv_mode_property) - goto nomem; - - for (i = 0; i < num_modes; i++) - drm_property_add_enum(dev->mode_config.tv_mode_property, i, - i, modes[i]); + for (i = 0; i < num_modes; i++) + drm_property_add_enum(dev->mode_config.legacy_tv_mode_property, + i, modes[i]); + } dev->mode_config.tv_brightness_property = drm_property_create_range(dev, 0, "brightness", 0, 100); @@ -933,6 +2230,47 @@ int drm_mode_create_tv_properties(struct drm_device *dev, nomem: return -ENOMEM; } +EXPORT_SYMBOL(drm_mode_create_tv_properties_legacy); + +/** + * drm_mode_create_tv_properties - create TV specific connector properties + * @dev: DRM device + * @supported_tv_modes: Bitmask of TV modes supported (See DRM_MODE_TV_MODE_*) + * + * Called by a driver's TV initialization routine, this function creates + * the TV specific connector properties for a given device. + * + * Returns: + * 0 on success or a negative error code on failure. + */ +int drm_mode_create_tv_properties(struct drm_device *dev, + unsigned int supported_tv_modes) +{ + struct drm_prop_enum_list tv_mode_list[DRM_MODE_TV_MODE_MAX]; + struct drm_property *tv_mode; + unsigned int i, len = 0; + + if (dev->mode_config.tv_mode_property) + return 0; + + for (i = 0; i < DRM_MODE_TV_MODE_MAX; i++) { + if (!(supported_tv_modes & BIT(i))) + continue; + + tv_mode_list[len].type = i; + tv_mode_list[len].name = drm_get_tv_mode_name(i); + len++; + } + + tv_mode = drm_property_create_enum(dev, 0, "TV mode", + tv_mode_list, len); + if (!tv_mode) + return -ENOMEM; + + dev->mode_config.tv_mode_property = tv_mode; + + return drm_mode_create_tv_properties_legacy(dev, 0, NULL); +} EXPORT_SYMBOL(drm_mode_create_tv_properties); /** @@ -943,8 +2281,10 @@ EXPORT_SYMBOL(drm_mode_create_tv_properties); * connectors. * * Atomic drivers should use drm_connector_attach_scaling_mode_property() - * instead to correctly assign &drm_connector_state.picture_aspect_ratio + * instead to correctly assign &drm_connector_state.scaling_mode * in the atomic state. + * + * Returns: %0 */ int drm_mode_create_scaling_mode_property(struct drm_device *dev) { @@ -965,12 +2305,105 @@ int drm_mode_create_scaling_mode_property(struct drm_device *dev) EXPORT_SYMBOL(drm_mode_create_scaling_mode_property); /** + * DOC: Variable refresh properties + * + * Variable refresh rate capable displays can dynamically adjust their + * refresh rate by extending the duration of their vertical front porch + * until page flip or timeout occurs. This can reduce or remove stuttering + * and latency in scenarios where the page flip does not align with the + * vblank interval. + * + * An example scenario would be an application flipping at a constant rate + * of 48Hz on a 60Hz display. The page flip will frequently miss the vblank + * interval and the same contents will be displayed twice. This can be + * observed as stuttering for content with motion. + * + * If variable refresh rate was active on a display that supported a + * variable refresh range from 35Hz to 60Hz no stuttering would be observable + * for the example scenario. The minimum supported variable refresh rate of + * 35Hz is below the page flip frequency and the vertical front porch can + * be extended until the page flip occurs. The vblank interval will be + * directly aligned to the page flip rate. + * + * Not all userspace content is suitable for use with variable refresh rate. + * Large and frequent changes in vertical front porch duration may worsen + * perceived stuttering for input sensitive applications. + * + * Panel brightness will also vary with vertical front porch duration. Some + * panels may have noticeable differences in brightness between the minimum + * vertical front porch duration and the maximum vertical front porch duration. + * Large and frequent changes in vertical front porch duration may produce + * observable flickering for such panels. + * + * Userspace control for variable refresh rate is supported via properties + * on the &drm_connector and &drm_crtc objects. + * + * "vrr_capable": + * Optional &drm_connector boolean property that drivers should attach + * with drm_connector_attach_vrr_capable_property() on connectors that + * could support variable refresh rates. Drivers should update the + * property value by calling drm_connector_set_vrr_capable_property(). + * + * Absence of the property should indicate absence of support. + * + * "VRR_ENABLED": + * Default &drm_crtc boolean property that notifies the driver that the + * content on the CRTC is suitable for variable refresh rate presentation. + * The driver will take this property as a hint to enable variable + * refresh rate support if the receiver supports it, ie. if the + * "vrr_capable" property is true on the &drm_connector object. The + * vertical front porch duration will be extended until page-flip or + * timeout when enabled. + * + * The minimum vertical front porch duration is defined as the vertical + * front porch duration for the current mode. + * + * The maximum vertical front porch duration is greater than or equal to + * the minimum vertical front porch duration. The duration is derived + * from the minimum supported variable refresh rate for the connector. + * + * The driver may place further restrictions within these minimum + * and maximum bounds. + */ + +/** + * drm_connector_attach_vrr_capable_property - creates the + * vrr_capable property + * @connector: connector to create the vrr_capable property on. + * + * This is used by atomic drivers to add support for querying + * variable refresh rate capability for a connector. + * + * Returns: + * Zero on success, negative errno on failure. + */ +int drm_connector_attach_vrr_capable_property( + struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_property *prop; + + if (!connector->vrr_capable_property) { + prop = drm_property_create_bool(dev, DRM_MODE_PROP_IMMUTABLE, + "vrr_capable"); + if (!prop) + return -ENOMEM; + + connector->vrr_capable_property = prop; + drm_object_attach_property(&connector->base, prop, 0); + } + + return 0; +} +EXPORT_SYMBOL(drm_connector_attach_vrr_capable_property); + +/** * drm_connector_attach_scaling_mode_property - attach atomic scaling mode property * @connector: connector to attach scaling mode property on. * @scaling_mode_mask: or'ed mask of BIT(%DRM_MODE_SCALE_\*). * * This is used to add support for scaling mode to atomic drivers. - * The scaling mode will be set to &drm_connector_state.picture_aspect_ratio + * The scaling mode will be set to &drm_connector_state.scaling_mode * and can be used from &drm_connector_helper_funcs->atomic_check for validation. * * This is the atomic version of drm_mode_create_scaling_mode_property(). @@ -983,7 +2416,7 @@ int drm_connector_attach_scaling_mode_property(struct drm_connector *connector, { struct drm_device *dev = connector->dev; struct drm_property *scaling_mode_property; - int i, j = 0; + int i; const unsigned valid_scaling_mode_mask = (1U << ARRAY_SIZE(drm_scaling_mode_enum_list)) - 1; @@ -1004,7 +2437,7 @@ int drm_connector_attach_scaling_mode_property(struct drm_connector *connector, if (!(BIT(i) & scaling_mode_mask)) continue; - ret = drm_property_add_enum(scaling_mode_property, j++, + ret = drm_property_add_enum(scaling_mode_property, drm_scaling_mode_enum_list[i].type, drm_scaling_mode_enum_list[i].name); @@ -1052,10 +2485,209 @@ int drm_mode_create_aspect_ratio_property(struct drm_device *dev) EXPORT_SYMBOL(drm_mode_create_aspect_ratio_property); /** + * DOC: standard connector properties + * + * Colorspace: + * This property is used to inform the driver about the color encoding + * user space configured the pixel operation properties to produce. + * The variants set the colorimetry, transfer characteristics, and which + * YCbCr conversion should be used when necessary. + * The transfer characteristics from HDR_OUTPUT_METADATA takes precedence + * over this property. + * User space always configures the pixel operation properties to produce + * full quantization range data (see the Broadcast RGB property). + * + * Drivers inform the sink about what colorimetry, transfer + * characteristics, YCbCr conversion, and quantization range to expect + * (this can depend on the output mode, output format and other + * properties). Drivers also convert the user space provided data to what + * the sink expects. + * + * User space has to check if the sink supports all of the possible + * colorimetries that the driver is allowed to pick by parsing the EDID. + * + * For historical reasons this property exposes a number of variants which + * result in undefined behavior. + * + * Default: + * The behavior is driver-specific. + * + * BT2020_RGB: + * + * BT2020_YCC: + * User space configures the pixel operation properties to produce + * RGB content with Rec. ITU-R BT.2020 colorimetry, Rec. + * ITU-R BT.2020 (Table 4, RGB) transfer characteristics and full + * quantization range. + * User space can use the HDR_OUTPUT_METADATA property to set the + * transfer characteristics to PQ (Rec. ITU-R BT.2100 Table 4) or + * HLG (Rec. ITU-R BT.2100 Table 5) in which case, user space + * configures pixel operation properties to produce content with + * the respective transfer characteristics. + * User space has to make sure the sink supports Rec. + * ITU-R BT.2020 R'G'B' and Rec. ITU-R BT.2020 Y'C'BC'R + * colorimetry. + * Drivers can configure the sink to use an RGB format, tell the + * sink to expect Rec. ITU-R BT.2020 R'G'B' colorimetry and convert + * to the appropriate quantization range. + * Drivers can configure the sink to use a YCbCr format, tell the + * sink to expect Rec. ITU-R BT.2020 Y'C'BC'R colorimetry, convert + * to YCbCr using the Rec. ITU-R BT.2020 non-constant luminance + * conversion matrix and convert to the appropriate quantization + * range. + * The variants BT2020_RGB and BT2020_YCC are equivalent and the + * driver chooses between RGB and YCbCr on its own. + * + * SMPTE_170M_YCC: + * BT709_YCC: + * XVYCC_601: + * XVYCC_709: + * SYCC_601: + * opYCC_601: + * opRGB: + * BT2020_CYCC: + * DCI-P3_RGB_D65: + * DCI-P3_RGB_Theater: + * RGB_WIDE_FIXED: + * RGB_WIDE_FLOAT: + * + * BT601_YCC: + * The behavior is undefined. + * + * Because between HDMI and DP have different colorspaces, + * drm_mode_create_hdmi_colorspace_property() is used for HDMI connector and + * drm_mode_create_dp_colorspace_property() is used for DP connector. + */ + +static int drm_mode_create_colorspace_property(struct drm_connector *connector, + u32 supported_colorspaces) +{ + struct drm_device *dev = connector->dev; + u32 colorspaces = supported_colorspaces | BIT(DRM_MODE_COLORIMETRY_DEFAULT); + struct drm_prop_enum_list enum_list[DRM_MODE_COLORIMETRY_COUNT]; + int i, len; + + if (connector->colorspace_property) + return 0; + + if (!supported_colorspaces) { + drm_err(dev, "No supported colorspaces provded on [CONNECTOR:%d:%s]\n", + connector->base.id, connector->name); + return -EINVAL; + } + + if ((supported_colorspaces & -BIT(DRM_MODE_COLORIMETRY_COUNT)) != 0) { + drm_err(dev, "Unknown colorspace provded on [CONNECTOR:%d:%s]\n", + connector->base.id, connector->name); + return -EINVAL; + } + + len = 0; + for (i = 0; i < DRM_MODE_COLORIMETRY_COUNT; i++) { + if ((colorspaces & BIT(i)) == 0) + continue; + + enum_list[len].type = i; + enum_list[len].name = colorspace_names[i]; + len++; + } + + connector->colorspace_property = + drm_property_create_enum(dev, DRM_MODE_PROP_ENUM, "Colorspace", + enum_list, + len); + + if (!connector->colorspace_property) + return -ENOMEM; + + return 0; +} + +/** + * drm_mode_create_hdmi_colorspace_property - create hdmi colorspace property + * @connector: connector to create the Colorspace property on. + * @supported_colorspaces: bitmap of supported color spaces + * + * Called by a driver the first time it's needed, must be attached to desired + * HDMI connectors. + * + * Returns: + * Zero on success, negative errno on failure. + */ +int drm_mode_create_hdmi_colorspace_property(struct drm_connector *connector, + u32 supported_colorspaces) +{ + u32 colorspaces; + + if (supported_colorspaces) + colorspaces = supported_colorspaces & hdmi_colorspaces; + else + colorspaces = hdmi_colorspaces; + + return drm_mode_create_colorspace_property(connector, colorspaces); +} +EXPORT_SYMBOL(drm_mode_create_hdmi_colorspace_property); + +/** + * drm_mode_create_dp_colorspace_property - create dp colorspace property + * @connector: connector to create the Colorspace property on. + * @supported_colorspaces: bitmap of supported color spaces + * + * Called by a driver the first time it's needed, must be attached to desired + * DP connectors. + * + * Returns: + * Zero on success, negative errno on failure. + */ +int drm_mode_create_dp_colorspace_property(struct drm_connector *connector, + u32 supported_colorspaces) +{ + u32 colorspaces; + + if (supported_colorspaces) + colorspaces = supported_colorspaces & dp_colorspaces; + else + colorspaces = dp_colorspaces; + + return drm_mode_create_colorspace_property(connector, colorspaces); +} +EXPORT_SYMBOL(drm_mode_create_dp_colorspace_property); + +/** + * drm_mode_create_content_type_property - create content type property + * @dev: DRM device + * + * Called by a driver the first time it's needed, must be attached to desired + * connectors. + * + * Returns: + * Zero on success, negative errno on failure. + */ +int drm_mode_create_content_type_property(struct drm_device *dev) +{ + if (dev->mode_config.content_type_property) + return 0; + + dev->mode_config.content_type_property = + drm_property_create_enum(dev, 0, "content type", + drm_content_type_enum_list, + ARRAY_SIZE(drm_content_type_enum_list)); + + if (dev->mode_config.content_type_property == NULL) + return -ENOMEM; + + return 0; +} +EXPORT_SYMBOL(drm_mode_create_content_type_property); + +/** * drm_mode_create_suggested_offset_properties - create suggests offset properties * @dev: DRM device * - * Create the the suggested x/y offset property for connectors. + * Create the suggested x/y offset property for connectors. + * + * Returns: + * 0 on success or a negative error code on failure. */ int drm_mode_create_suggested_offset_properties(struct drm_device *dev) { @@ -1076,7 +2708,7 @@ int drm_mode_create_suggested_offset_properties(struct drm_device *dev) EXPORT_SYMBOL(drm_mode_create_suggested_offset_properties); /** - * drm_mode_connector_set_path_property - set tile property on connector + * drm_connector_set_path_property - set tile property on connector * @connector: connector to set property on. * @path: path to use for property; must not be NULL. * @@ -1088,34 +2720,36 @@ EXPORT_SYMBOL(drm_mode_create_suggested_offset_properties); * Returns: * Zero on success, negative errno on failure. */ -int drm_mode_connector_set_path_property(struct drm_connector *connector, - const char *path) +int drm_connector_set_path_property(struct drm_connector *connector, + const char *path) { struct drm_device *dev = connector->dev; int ret; ret = drm_property_replace_global_blob(dev, - &connector->path_blob_ptr, - strlen(path) + 1, - path, - &connector->base, - dev->mode_config.path_property); + &connector->path_blob_ptr, + strlen(path) + 1, + path, + &connector->base, + dev->mode_config.path_property); return ret; } -EXPORT_SYMBOL(drm_mode_connector_set_path_property); +EXPORT_SYMBOL(drm_connector_set_path_property); /** - * drm_mode_connector_set_tile_property - set tile property on connector + * drm_connector_set_tile_property - set tile property on connector * @connector: connector to set property on. * * This looks up the tile information for a connector, and creates a * property for userspace to parse if it exists. The property is of * the form of 8 integers using ':' as a separator. + * This is used for dual port tiled displays with DisplayPort SST + * or DisplayPort MST connectors. * * Returns: * Zero on success, errno on failure. */ -int drm_mode_connector_set_tile_property(struct drm_connector *connector) +int drm_connector_set_tile_property(struct drm_connector *connector) { struct drm_device *dev = connector->dev; char tile[256]; @@ -1123,11 +2757,11 @@ int drm_mode_connector_set_tile_property(struct drm_connector *connector) if (!connector->has_tile) { ret = drm_property_replace_global_blob(dev, - &connector->tile_blob_ptr, - 0, - NULL, - &connector->base, - dev->mode_config.tile_property); + &connector->tile_blob_ptr, + 0, + NULL, + &connector->base, + dev->mode_config.tile_property); return ret; } @@ -1138,81 +2772,465 @@ int drm_mode_connector_set_tile_property(struct drm_connector *connector) connector->tile_h_size, connector->tile_v_size); ret = drm_property_replace_global_blob(dev, - &connector->tile_blob_ptr, - strlen(tile) + 1, - tile, - &connector->base, - dev->mode_config.tile_property); + &connector->tile_blob_ptr, + strlen(tile) + 1, + tile, + &connector->base, + dev->mode_config.tile_property); return ret; } -EXPORT_SYMBOL(drm_mode_connector_set_tile_property); +EXPORT_SYMBOL(drm_connector_set_tile_property); /** - * drm_mode_connector_update_edid_property - update the edid property of a connector + * drm_connector_set_link_status_property - Set link status property of a connector * @connector: drm connector - * @edid: new value of the edid property + * @link_status: new value of link status property (0: Good, 1: Bad) * - * This function creates a new blob modeset object and assigns its id to the - * connector's edid property. + * In usual working scenario, this link status property will always be set to + * "GOOD". If something fails during or after a mode set, the kernel driver + * may set this link status property to "BAD". The caller then needs to send a + * hotplug uevent for userspace to re-check the valid modes through + * GET_CONNECTOR_IOCTL and retry modeset. + * + * Note: Drivers cannot rely on userspace to support this property and + * issue a modeset. As such, they may choose to handle issues (like + * re-training a link) without userspace's intervention. + * + * The reason for adding this property is to handle link training failures, but + * it is not limited to DP or link training. For example, if we implement + * asynchronous setcrtc, this property can be used to report any failures in that. + */ +void drm_connector_set_link_status_property(struct drm_connector *connector, + uint64_t link_status) +{ + struct drm_device *dev = connector->dev; + + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); + connector->state->link_status = link_status; + drm_modeset_unlock(&dev->mode_config.connection_mutex); +} +EXPORT_SYMBOL(drm_connector_set_link_status_property); + +/** + * drm_connector_attach_max_bpc_property - attach "max bpc" property + * @connector: connector to attach max bpc property on. + * @min: The minimum bit depth supported by the connector. + * @max: The maximum bit depth supported by the connector. + * + * This is used to add support for limiting the bit depth on a connector. * * Returns: * Zero on success, negative errno on failure. */ -int drm_mode_connector_update_edid_property(struct drm_connector *connector, - const struct edid *edid) +int drm_connector_attach_max_bpc_property(struct drm_connector *connector, + int min, int max) { struct drm_device *dev = connector->dev; - size_t size = 0; - int ret; + struct drm_property *prop; - /* ignore requests to set edid when overridden */ - if (connector->override_edid) - return 0; + prop = connector->max_bpc_property; + if (!prop) { + prop = drm_property_create_range(dev, 0, "max bpc", min, max); + if (!prop) + return -ENOMEM; + + connector->max_bpc_property = prop; + } - if (edid) - size = EDID_LENGTH * (1 + edid->extensions); + drm_object_attach_property(&connector->base, prop, max); + connector->state->max_requested_bpc = max; + connector->state->max_bpc = max; - ret = drm_property_replace_global_blob(dev, - &connector->edid_blob_ptr, - size, - edid, - &connector->base, - dev->mode_config.edid_property); - return ret; + return 0; +} +EXPORT_SYMBOL(drm_connector_attach_max_bpc_property); + +/** + * drm_connector_attach_hdr_output_metadata_property - attach "HDR_OUTPUT_METADA" property + * @connector: connector to attach the property on. + * + * This is used to allow the userspace to send HDR Metadata to the + * driver. + * + * Returns: + * Zero on success, negative errno on failure. + */ +int drm_connector_attach_hdr_output_metadata_property(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_property *prop = dev->mode_config.hdr_output_metadata_property; + + drm_object_attach_property(&connector->base, prop, 0); + + return 0; +} +EXPORT_SYMBOL(drm_connector_attach_hdr_output_metadata_property); + +/** + * drm_connector_attach_broadcast_rgb_property - attach "Broadcast RGB" property + * @connector: connector to attach the property on. + * + * This is used to add support for forcing the RGB range on a connector + * + * Returns: + * Zero on success, negative errno on failure. + */ +int drm_connector_attach_broadcast_rgb_property(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_property *prop; + + prop = connector->broadcast_rgb_property; + if (!prop) { + prop = drm_property_create_enum(dev, DRM_MODE_PROP_ENUM, + "Broadcast RGB", + broadcast_rgb_names, + ARRAY_SIZE(broadcast_rgb_names)); + if (!prop) + return -EINVAL; + + connector->broadcast_rgb_property = prop; + } + + drm_object_attach_property(&connector->base, prop, + DRM_HDMI_BROADCAST_RGB_AUTO); + + return 0; +} +EXPORT_SYMBOL(drm_connector_attach_broadcast_rgb_property); + +/** + * drm_connector_attach_colorspace_property - attach "Colorspace" property + * @connector: connector to attach the property on. + * + * This is used to allow the userspace to signal the output colorspace + * to the driver. + * + * Returns: + * Zero on success, negative errno on failure. + */ +int drm_connector_attach_colorspace_property(struct drm_connector *connector) +{ + struct drm_property *prop = connector->colorspace_property; + + drm_object_attach_property(&connector->base, prop, DRM_MODE_COLORIMETRY_DEFAULT); + + return 0; } -EXPORT_SYMBOL(drm_mode_connector_update_edid_property); +EXPORT_SYMBOL(drm_connector_attach_colorspace_property); /** - * drm_mode_connector_set_link_status_property - Set link status property of a connector + * drm_connector_atomic_hdr_metadata_equal - checks if the hdr metadata changed + * @old_state: old connector state to compare + * @new_state: new connector state to compare + * + * This is used by HDR-enabled drivers to test whether the HDR metadata + * have changed between two different connector state (and thus probably + * requires a full blown mode change). + * + * Returns: + * True if the metadata are equal, False otherwise + */ +bool drm_connector_atomic_hdr_metadata_equal(struct drm_connector_state *old_state, + struct drm_connector_state *new_state) +{ + struct drm_property_blob *old_blob = old_state->hdr_output_metadata; + struct drm_property_blob *new_blob = new_state->hdr_output_metadata; + + if (!old_blob || !new_blob) + return old_blob == new_blob; + + if (old_blob->length != new_blob->length) + return false; + + return !memcmp(old_blob->data, new_blob->data, old_blob->length); +} +EXPORT_SYMBOL(drm_connector_atomic_hdr_metadata_equal); + +/** + * drm_connector_set_vrr_capable_property - sets the variable refresh rate + * capable property for a connector * @connector: drm connector - * @link_status: new value of link status property (0: Good, 1: Bad) + * @capable: True if the connector is variable refresh rate capable * - * In usual working scenario, this link status property will always be set to - * "GOOD". If something fails during or after a mode set, the kernel driver - * may set this link status property to "BAD". The caller then needs to send a - * hotplug uevent for userspace to re-check the valid modes through - * GET_CONNECTOR_IOCTL and retry modeset. + * Should be used by atomic drivers to update the indicated support for + * variable refresh rate over a connector. + */ +void drm_connector_set_vrr_capable_property( + struct drm_connector *connector, bool capable) +{ + if (!connector->vrr_capable_property) + return; + + drm_object_property_set_value(&connector->base, + connector->vrr_capable_property, + capable); +} +EXPORT_SYMBOL(drm_connector_set_vrr_capable_property); + +/** + * drm_connector_set_panel_orientation - sets the connector's panel_orientation + * @connector: connector for which to set the panel-orientation property. + * @panel_orientation: drm_panel_orientation value to set * - * Note: Drivers cannot rely on userspace to support this property and - * issue a modeset. As such, they may choose to handle issues (like - * re-training a link) without userspace's intervention. + * This function sets the connector's panel_orientation and attaches + * a "panel orientation" property to the connector. * - * The reason for adding this property is to handle link training failures, but - * it is not limited to DP or link training. For example, if we implement - * asynchronous setcrtc, this property can be used to report any failures in that. + * Calling this function on a connector where the panel_orientation has + * already been set is a no-op (e.g. the orientation has been overridden with + * a kernel commandline option). + * + * It is allowed to call this function with a panel_orientation of + * DRM_MODE_PANEL_ORIENTATION_UNKNOWN, in which case it is a no-op. + * + * The function shouldn't be called in panel after drm is registered (i.e. + * drm_dev_register() is called in drm). + * + * Returns: + * Zero on success, negative errno on failure. */ -void drm_mode_connector_set_link_status_property(struct drm_connector *connector, - uint64_t link_status) +int drm_connector_set_panel_orientation( + struct drm_connector *connector, + enum drm_panel_orientation panel_orientation) { struct drm_device *dev = connector->dev; + struct drm_display_info *info = &connector->display_info; + struct drm_property *prop; + + /* Already set? */ + if (info->panel_orientation != DRM_MODE_PANEL_ORIENTATION_UNKNOWN) + return 0; + + /* Don't attach the property if the orientation is unknown */ + if (panel_orientation == DRM_MODE_PANEL_ORIENTATION_UNKNOWN) + return 0; + + info->panel_orientation = panel_orientation; + + 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_set_panel_orientation); + +/** + * drm_connector_set_panel_orientation_with_quirk - set the + * connector's panel_orientation after checking for quirks + * @connector: connector for which to init the panel-orientation property. + * @panel_orientation: drm_panel_orientation value to set + * @width: width in pixels of the panel, used for panel quirk detection + * @height: height in pixels of the panel, used for panel quirk detection + * + * Like drm_connector_set_panel_orientation(), but with a check for platform + * specific (e.g. DMI based) quirks overriding the passed in panel_orientation. + * + * Returns: + * Zero on success, negative errno on failure. + */ +int drm_connector_set_panel_orientation_with_quirk( + struct drm_connector *connector, + enum drm_panel_orientation panel_orientation, + int width, int height) +{ + int orientation_quirk; + + orientation_quirk = drm_get_panel_orientation_quirk(width, height); + if (orientation_quirk != DRM_MODE_PANEL_ORIENTATION_UNKNOWN) + panel_orientation = orientation_quirk; + + return drm_connector_set_panel_orientation(connector, + panel_orientation); +} +EXPORT_SYMBOL(drm_connector_set_panel_orientation_with_quirk); + +/** + * drm_connector_set_orientation_from_panel - + * set the connector's panel_orientation from panel's callback. + * @connector: connector for which to init the panel-orientation property. + * @panel: panel that can provide orientation information. + * + * Drm drivers should call this function before drm_dev_register(). + * Orientation is obtained from panel's .get_orientation() callback. + * + * Returns: + * Zero on success, negative errno on failure. + */ +int drm_connector_set_orientation_from_panel( + struct drm_connector *connector, + struct drm_panel *panel) +{ + enum drm_panel_orientation orientation; + + if (panel && panel->funcs && panel->funcs->get_orientation) + orientation = panel->funcs->get_orientation(panel); + else + orientation = DRM_MODE_PANEL_ORIENTATION_UNKNOWN; + + return drm_connector_set_panel_orientation(connector, orientation); +} +EXPORT_SYMBOL(drm_connector_set_orientation_from_panel); + +static const struct drm_prop_enum_list privacy_screen_enum[] = { + { PRIVACY_SCREEN_DISABLED, "Disabled" }, + { PRIVACY_SCREEN_ENABLED, "Enabled" }, + { PRIVACY_SCREEN_DISABLED_LOCKED, "Disabled-locked" }, + { PRIVACY_SCREEN_ENABLED_LOCKED, "Enabled-locked" }, +}; + +/** + * drm_connector_create_privacy_screen_properties - create the drm connecter's + * privacy-screen properties. + * @connector: connector for which to create the privacy-screen properties + * + * This function creates the "privacy-screen sw-state" and "privacy-screen + * hw-state" properties for the connector. They are not attached. + */ +void +drm_connector_create_privacy_screen_properties(struct drm_connector *connector) +{ + if (connector->privacy_screen_sw_state_property) + return; + + /* Note sw-state only supports the first 2 values of the enum */ + connector->privacy_screen_sw_state_property = + drm_property_create_enum(connector->dev, DRM_MODE_PROP_ENUM, + "privacy-screen sw-state", + privacy_screen_enum, 2); + + connector->privacy_screen_hw_state_property = + drm_property_create_enum(connector->dev, + DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_ENUM, + "privacy-screen hw-state", + privacy_screen_enum, + ARRAY_SIZE(privacy_screen_enum)); +} +EXPORT_SYMBOL(drm_connector_create_privacy_screen_properties); + +/** + * drm_connector_attach_privacy_screen_properties - attach the drm connecter's + * privacy-screen properties. + * @connector: connector on which to attach the privacy-screen properties + * + * This function attaches the "privacy-screen sw-state" and "privacy-screen + * hw-state" properties to the connector. The initial state of both is set + * to "Disabled". + */ +void +drm_connector_attach_privacy_screen_properties(struct drm_connector *connector) +{ + if (!connector->privacy_screen_sw_state_property) + return; + + drm_object_attach_property(&connector->base, + connector->privacy_screen_sw_state_property, + PRIVACY_SCREEN_DISABLED); + + drm_object_attach_property(&connector->base, + connector->privacy_screen_hw_state_property, + PRIVACY_SCREEN_DISABLED); +} +EXPORT_SYMBOL(drm_connector_attach_privacy_screen_properties); + +static void drm_connector_update_privacy_screen_properties( + struct drm_connector *connector, bool set_sw_state) +{ + enum drm_privacy_screen_status sw_state, hw_state; + + drm_privacy_screen_get_state(connector->privacy_screen, + &sw_state, &hw_state); + + if (set_sw_state) + connector->state->privacy_screen_sw_state = sw_state; + drm_object_property_set_value(&connector->base, + connector->privacy_screen_hw_state_property, hw_state); +} + +static int drm_connector_privacy_screen_notifier( + struct notifier_block *nb, unsigned long action, void *data) +{ + struct drm_connector *connector = + container_of(nb, struct drm_connector, privacy_screen_notifier); + struct drm_device *dev = connector->dev; drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); - connector->state->link_status = link_status; + drm_connector_update_privacy_screen_properties(connector, true); drm_modeset_unlock(&dev->mode_config.connection_mutex); + + drm_sysfs_connector_property_event(connector, + connector->privacy_screen_sw_state_property); + drm_sysfs_connector_property_event(connector, + connector->privacy_screen_hw_state_property); + + return NOTIFY_DONE; +} + +/** + * drm_connector_attach_privacy_screen_provider - attach a privacy-screen to + * the connector + * @connector: connector to attach the privacy-screen to + * @priv: drm_privacy_screen to attach + * + * Create and attach the standard privacy-screen properties and register + * a generic notifier for generating sysfs-connector-status-events + * on external changes to the privacy-screen status. + * This function takes ownership of the passed in drm_privacy_screen and will + * call drm_privacy_screen_put() on it when the connector is destroyed. + */ +void drm_connector_attach_privacy_screen_provider( + struct drm_connector *connector, struct drm_privacy_screen *priv) +{ + connector->privacy_screen = priv; + connector->privacy_screen_notifier.notifier_call = + drm_connector_privacy_screen_notifier; + + drm_connector_create_privacy_screen_properties(connector); + drm_connector_update_privacy_screen_properties(connector, true); + drm_connector_attach_privacy_screen_properties(connector); } -EXPORT_SYMBOL(drm_mode_connector_set_link_status_property); +EXPORT_SYMBOL(drm_connector_attach_privacy_screen_provider); + +/** + * drm_connector_update_privacy_screen - update connector's privacy-screen sw-state + * @connector_state: connector-state to update the privacy-screen for + * + * This function calls drm_privacy_screen_set_sw_state() on the connector's + * privacy-screen. + * + * If the connector has no privacy-screen, then this is a no-op. + */ +void drm_connector_update_privacy_screen(const struct drm_connector_state *connector_state) +{ + struct drm_connector *connector = connector_state->connector; + int ret; -int drm_mode_connector_set_obj_prop(struct drm_mode_object *obj, + if (!connector->privacy_screen) + return; + + ret = drm_privacy_screen_set_sw_state(connector->privacy_screen, + connector_state->privacy_screen_sw_state); + if (ret) { + drm_err(connector->dev, "Error updating privacy-screen sw_state\n"); + return; + } + + /* The hw_state property value may have changed, update it. */ + drm_connector_update_privacy_screen_properties(connector, false); +} +EXPORT_SYMBOL(drm_connector_update_privacy_screen); + +int drm_connector_set_obj_prop(struct drm_mode_object *obj, struct drm_property *property, uint64_t value) { @@ -1225,14 +3243,13 @@ int drm_mode_connector_set_obj_prop(struct drm_mode_object *obj, } else if (connector->funcs->set_property) ret = connector->funcs->set_property(connector, property, value); - /* store the property value if successful */ if (!ret) drm_object_property_set_value(&connector->base, property, value); return ret; } -int drm_mode_connector_property_set_ioctl(struct drm_device *dev, - void *data, struct drm_file *file_priv) +int drm_connector_property_set_ioctl(struct drm_device *dev, + void *data, struct drm_file *file_priv) { struct drm_mode_connector_set_property *conn_set_prop = data; struct drm_mode_obj_set_property obj_set_prop = { @@ -1249,14 +3266,17 @@ int drm_mode_connector_property_set_ioctl(struct drm_device *dev, static struct drm_encoder *drm_connector_get_encoder(struct drm_connector *connector) { /* For atomic drivers only state objects are synchronously updated and - * protected by modeset locks, so check those first. */ + * protected by modeset locks, so check those first. + */ if (connector->state) return connector->state->best_encoder; return connector->encoder; } -static bool drm_mode_expose_to_userspace(const struct drm_display_mode *mode, - const struct drm_file *file_priv) +static bool +drm_mode_expose_to_userspace(const struct drm_display_mode *mode, + const struct list_head *modes, + const struct drm_file *file_priv) { /* * If user-space hasn't configured the driver to expose the stereo 3D @@ -1264,6 +3284,25 @@ static bool drm_mode_expose_to_userspace(const struct drm_display_mode *mode, */ if (!file_priv->stereo_allowed && drm_mode_is_stereo(mode)) return false; + /* + * If user-space hasn't configured the driver to expose the modes + * with aspect-ratio, don't expose them. However if such a mode + * is unique, let it be exposed, but reset the aspect-ratio flags + * while preparing the list of user-modes. + */ + if (!file_priv->aspect_ratio_allowed) { + const struct drm_display_mode *mode_itr; + + list_for_each_entry(mode_itr, modes, head) { + if (mode_itr->expose_to_userspace && + drm_mode_match(mode_itr, mode, + DRM_MODE_MATCH_TIMINGS | + DRM_MODE_MATCH_CLOCK | + DRM_MODE_MATCH_FLAGS | + DRM_MODE_MATCH_3D_FLAGS)) + return false; + } + } return true; } @@ -1279,36 +3318,32 @@ int drm_mode_getconnector(struct drm_device *dev, void *data, int encoders_count = 0; int ret = 0; int copied = 0; - int i; struct drm_mode_modeinfo u_mode; struct drm_mode_modeinfo __user *mode_ptr; uint32_t __user *encoder_ptr; + bool is_current_master; if (!drm_core_check_feature(dev, DRIVER_MODESET)) - return -EINVAL; + return -EOPNOTSUPP; memset(&u_mode, 0, sizeof(struct drm_mode_modeinfo)); - connector = drm_connector_lookup(dev, out_resp->connector_id); + connector = drm_connector_lookup(dev, file_priv, out_resp->connector_id); if (!connector) return -ENOENT; - for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) - if (connector->encoder_ids[i] != 0) - encoders_count++; + encoders_count = hweight32(connector->possible_encoders); if ((out_resp->count_encoders >= encoders_count) && encoders_count) { copied = 0; encoder_ptr = (uint32_t __user *)(unsigned long)(out_resp->encoders_ptr); - for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { - if (connector->encoder_ids[i] != 0) { - if (put_user(connector->encoder_ids[i], - encoder_ptr + copied)) { - ret = -EFAULT; - goto out; - } - copied++; + + drm_connector_for_each_possible_encoder(connector, encoder) { + if (put_user(encoder->base.id, encoder_ptr + copied)) { + ret = -EFAULT; + goto out; } + copied++; } } out_resp->count_encoders = encoders_count; @@ -1317,11 +3352,17 @@ int drm_mode_getconnector(struct drm_device *dev, void *data, out_resp->connector_type = connector->connector_type; out_resp->connector_type_id = connector->connector_type_id; + is_current_master = drm_is_current_master(file_priv); + mutex_lock(&dev->mode_config.mutex); if (out_resp->count_modes == 0) { - connector->funcs->fill_modes(connector, - dev->mode_config.max_width, - dev->mode_config.max_height); + if (is_current_master) + connector->funcs->fill_modes(connector, + dev->mode_config.max_width, + dev->mode_config.max_height); + else + drm_dbg_kms(dev, "User-space requested a forced probe on [CONNECTOR:%d:%s] but is not the DRM master, demoting to read-only probe\n", + connector->base.id, connector->name); } out_resp->mm_width = connector->display_info.width_mm; @@ -1330,9 +3371,15 @@ int drm_mode_getconnector(struct drm_device *dev, void *data, out_resp->connection = connector->status; /* delayed so we get modes regardless of pre-fill_modes state */ - list_for_each_entry(mode, &connector->modes, head) - if (drm_mode_expose_to_userspace(mode, file_priv)) + list_for_each_entry(mode, &connector->modes, head) { + WARN_ON(mode->expose_to_userspace); + + if (drm_mode_expose_to_userspace(mode, &connector->modes, + file_priv)) { + mode->expose_to_userspace = true; mode_count++; + } + } /* * This ioctl is called twice, once to determine how much space is @@ -1342,20 +3389,42 @@ int drm_mode_getconnector(struct drm_device *dev, void *data, copied = 0; mode_ptr = (struct drm_mode_modeinfo __user *)(unsigned long)out_resp->modes_ptr; list_for_each_entry(mode, &connector->modes, head) { - if (!drm_mode_expose_to_userspace(mode, file_priv)) + if (!mode->expose_to_userspace) continue; + /* Clear the tag for the next time around */ + mode->expose_to_userspace = false; + drm_mode_convert_to_umode(&u_mode, mode); + /* + * Reset aspect ratio flags of user-mode, if modes with + * aspect-ratio are not supported. + */ + if (!file_priv->aspect_ratio_allowed) + u_mode.flags &= ~DRM_MODE_FLAG_PIC_AR_MASK; if (copy_to_user(mode_ptr + copied, &u_mode, sizeof(u_mode))) { ret = -EFAULT; + + /* + * Clear the tag for the rest of + * the modes for the next time around. + */ + list_for_each_entry_continue(mode, &connector->modes, head) + mode->expose_to_userspace = false; + mutex_unlock(&dev->mode_config.mutex); goto out; } copied++; } + } else { + /* Clear the tag for the next time around */ + list_for_each_entry(mode, &connector->modes, head) + mode->expose_to_userspace = false; } + out_resp->count_modes = mode_count; mutex_unlock(&dev->mode_config.mutex); @@ -1367,8 +3436,10 @@ int drm_mode_getconnector(struct drm_device *dev, void *data, out_resp->encoder_id = 0; /* Only grab properties after probing, to make sure EDID and other - * properties reflect the latest status. */ + * properties reflect the latest status. + */ ret = drm_mode_object_get_properties(&connector->base, file_priv->atomic, + file_priv->plane_color_pipeline, (uint32_t __user *)(unsigned long)(out_resp->props_ptr), (uint64_t __user *)(unsigned long)(out_resp->prop_values_ptr), &out_resp->count_props); @@ -1380,6 +3451,69 @@ out: return ret; } +/** + * drm_connector_find_by_fwnode - Find a connector based on the associated fwnode + * @fwnode: fwnode for which to find the matching drm_connector + * + * This functions looks up a drm_connector based on its associated fwnode. When + * a connector is found a reference to the connector is returned. The caller must + * call drm_connector_put() to release this reference when it is done with the + * connector. + * + * Returns: A reference to the found connector or an ERR_PTR(). + */ +struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode) +{ + struct drm_connector *connector, *found = ERR_PTR(-ENODEV); + + if (!fwnode) + return ERR_PTR(-ENODEV); + + mutex_lock(&connector_list_lock); + + list_for_each_entry(connector, &connector_list, global_connector_list_entry) { + if (connector->fwnode == fwnode || + (connector->fwnode && connector->fwnode->secondary == fwnode)) { + drm_connector_get(connector); + found = connector; + break; + } + } + + mutex_unlock(&connector_list_lock); + + return found; +} + +/** + * drm_connector_oob_hotplug_event - Report out-of-band hotplug event to connector + * @connector_fwnode: fwnode_handle to report the event on + * @status: hot plug detect logical state + * + * On some hardware a hotplug event notification may come from outside the display + * driver / device. An example of this is some USB Type-C setups where the hardware + * muxes the DisplayPort data and aux-lines but does not pass the altmode HPD + * status bit to the GPU's DP HPD pin. + * + * This function can be used to report these out-of-band events after obtaining + * a drm_connector reference through calling drm_connector_find_by_fwnode(). + */ +void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode, + enum drm_connector_status status) +{ + struct drm_connector *connector; + + connector = drm_connector_find_by_fwnode(connector_fwnode); + if (IS_ERR(connector)) + return; + + if (connector->funcs->oob_hotplug_event) + connector->funcs->oob_hotplug_event(connector, status); + + drm_connector_put(connector); +} +EXPORT_SYMBOL(drm_connector_oob_hotplug_event); + /** * DOC: Tile group @@ -1397,6 +3531,7 @@ static void drm_tile_group_free(struct kref *kref) { struct drm_tile_group *tg = container_of(kref, struct drm_tile_group, refcount); struct drm_device *dev = tg->dev; + mutex_lock(&dev->mode_config.idr_mutex); idr_remove(&dev->mode_config.tile_idr, tg->id); mutex_unlock(&dev->mode_config.idr_mutex); @@ -1428,10 +3563,11 @@ EXPORT_SYMBOL(drm_mode_put_tile_group); * tile group or NULL if not found. */ struct drm_tile_group *drm_mode_get_tile_group(struct drm_device *dev, - char topology[8]) + const char topology[8]) { struct drm_tile_group *tg; int id; + mutex_lock(&dev->mode_config.idr_mutex); idr_for_each_entry(&dev->mode_config.tile_idr, tg, id) { if (!memcmp(tg->group_data, topology, 8)) { @@ -1455,17 +3591,17 @@ EXPORT_SYMBOL(drm_mode_get_tile_group); * identifier for the tile group. * * RETURNS: - * new tile group or error. + * new tile group or NULL. */ struct drm_tile_group *drm_mode_create_tile_group(struct drm_device *dev, - char topology[8]) + const char topology[8]) { struct drm_tile_group *tg; int ret; tg = kzalloc(sizeof(*tg), GFP_KERNEL); if (!tg) - return ERR_PTR(-ENOMEM); + return NULL; kref_init(&tg->refcount); memcpy(tg->group_data, topology, 8); @@ -1477,7 +3613,7 @@ struct drm_tile_group *drm_mode_create_tile_group(struct drm_device *dev, tg->id = ret; } else { kfree(tg); - tg = ERR_PTR(ret); + tg = NULL; } mutex_unlock(&dev->mode_config.idr_mutex); |
