diff options
Diffstat (limited to 'drivers/gpu/drm/drm_connector.c')
| -rw-r--r-- | drivers/gpu/drm/drm_connector.c | 2122 |
1 files changed, 1818 insertions, 304 deletions
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index da8ae80c2750..4d6dc9ebfdb5 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -20,16 +20,26 @@ * OF THIS SOFTWARE. */ +#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_utils.h> -#include <drm/drm_print.h> -#include <drm/drm_drv.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" @@ -37,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 @@ -63,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; @@ -92,6 +110,8 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = { { 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) @@ -111,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 @@ -123,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, @@ -139,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" : "", @@ -177,23 +220,11 @@ void drm_connector_free_work_fn(struct work_struct *work) } } -/** - * 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) +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; @@ -215,7 +246,7 @@ int drm_connector_init(struct drm_device *dev, connector->funcs = funcs; /* connector index is used with 32bit bitmasks */ - ret = ida_simple_get(&config->connector_ida, 0, 32, GFP_KERNEL); + 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, @@ -227,7 +258,7 @@ int drm_connector_init(struct drm_device *dev, 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; @@ -241,23 +272,28 @@ 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 && connector_type != DRM_MODE_CONNECTOR_WRITEBACK) drm_connector_attach_edid_property(connector); @@ -272,6 +308,9 @@ int drm_connector_init(struct drm_device *dev, 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); @@ -280,19 +319,315 @@ 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_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 * @@ -325,8 +660,6 @@ EXPORT_SYMBOL(drm_connector_attach_edid_property); 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 @@ -341,18 +674,15 @@ int drm_connector_attach_encoder(struct drm_connector *connector, if (WARN_ON(connector->encoder)) return -EINVAL; - for (i = 0; i < ARRAY_SIZE(connector->encoder_ids); 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_connector_attach_encoder); /** - * drm_connector_has_possible_encoder - check if the connector and encoder are assosicated with each other + * drm_connector_has_possible_encoder - check if the connector and encoder are + * associated with each other * @connector: the connector * @encoder: the encoder * @@ -362,15 +692,7 @@ EXPORT_SYMBOL(drm_connector_attach_encoder); bool drm_connector_has_possible_encoder(struct drm_connector *connector, struct drm_encoder *encoder) { - struct drm_encoder *enc; - int i; - - drm_connector_for_each_possible_encoder(connector, enc, i) { - if (enc == encoder) - return true; - } - - return false; + return connector->possible_encoders & drm_encoder_mask(encoder); } EXPORT_SYMBOL(drm_connector_has_possible_encoder); @@ -382,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 * @@ -399,6 +761,13 @@ void drm_connector_cleanup(struct drm_connector *connector) 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; @@ -410,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); @@ -440,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. @@ -460,10 +843,7 @@ int drm_connector_register(struct drm_connector *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); @@ -471,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->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); @@ -487,10 +884,43 @@ 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) { @@ -500,11 +930,22 @@ void drm_connector_unregister(struct drm_connector *connector) 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->registration_state = DRM_CONNECTOR_UNREGISTERED; mutex_unlock(&connector->mutex); @@ -547,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) { @@ -629,7 +1072,7 @@ __drm_connector_put_safe(struct drm_connector *conn) * drm_connector_list_iter_next - return next connector * @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 * @@ -683,7 +1126,7 @@ void drm_connector_list_iter_end(struct drm_connector_list_iter *iter) __drm_connector_put_safe(iter->conn); spin_unlock_irqrestore(&config->connector_list_lock, flags); } - lock_release(&connector_list_iter_dep_map, 0, _RET_IP_); + lock_release(&connector_list_iter_dep_map, _RET_IP_); } EXPORT_SYMBOL(drm_connector_list_iter_end); @@ -702,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) { @@ -731,6 +1176,9 @@ static const struct drm_prop_enum_list 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, @@ -793,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 */ @@ -810,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 */ @@ -819,12 +1303,145 @@ 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 struct drm_prop_enum_list drm_cp_enum_list[] = { - { DRM_MODE_CONTENT_PROTECTION_UNDESIRED, "Undesired" }, - { DRM_MODE_CONTENT_PROTECTION_DESIRED, "Desired" }, - { DRM_MODE_CONTENT_PROTECTION_ENABLED, "Enabled" }, +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_ENUM_NAME_FN(drm_get_content_protection_name, drm_cp_enum_list) + +/* + * 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 @@ -838,6 +1455,11 @@ DRM_ENUM_NAME_FN(drm_get_content_protection_name, drm_cp_enum_list) * 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 @@ -845,8 +1467,11 @@ DRM_ENUM_NAME_FN(drm_get_content_protection_name, drm_cp_enum_list) * 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. For atomic drivers the remapping to the "ACTIVE" property is - * implemented in the DRM core. This is the only standard connector - * property that userspace can change. + * 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. @@ -876,6 +1501,12 @@ DRM_ENUM_NAME_FN(drm_get_content_protection_name, drm_cp_enum_list) * 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 @@ -883,7 +1514,7 @@ DRM_ENUM_NAME_FN(drm_get_content_protection_name, drm_cp_enum_list) * 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 + * 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: @@ -892,6 +1523,32 @@ DRM_ENUM_NAME_FN(drm_get_content_protection_name, drm_cp_enum_list) * 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 @@ -927,10 +1584,113 @@ DRM_ENUM_NAME_FN(drm_get_content_protection_name, drm_cp_enum_list) * - 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.. - * - Userspace is responsible for polling the property 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). + * - 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 @@ -955,7 +1715,8 @@ DRM_ENUM_NAME_FN(drm_get_content_protection_name, drm_cp_enum_list) * 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_init_panel_orientation_property(). + * 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 @@ -979,6 +1740,68 @@ DRM_ENUM_NAME_FN(drm_get_content_protection_name, drm_cp_enum_list) * 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) @@ -1027,6 +1850,12 @@ int drm_connector_create_standard_properties(struct drm_device *dev) 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; } @@ -1035,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) { @@ -1062,11 +1893,67 @@ int drm_mode_create_dvi_i_properties(struct drm_device *dev) EXPORT_SYMBOL(drm_mode_create_dvi_i_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 it's display + * 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: @@ -1082,16 +1969,86 @@ EXPORT_SYMBOL(drm_mode_create_dvi_i_properties); * 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) { @@ -1103,42 +2060,76 @@ int drm_connector_attach_content_type_property(struct drm_connector *connector) } 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_hdmi_avi_infoframe_content_type() - fill the HDMI AVI infoframe - * content type information, based - * on correspondent DRM property. - * @frame: HDMI AVI infoframe - * @conn_state: DRM display connector state + * 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. */ -void drm_hdmi_avi_infoframe_content_type(struct hdmi_avi_infoframe *frame, - const struct drm_connector_state *conn_state) +int drm_mode_create_tv_margin_properties(struct drm_device *dev) { - switch (conn_state->content_type) { - case DRM_MODE_CONTENT_TYPE_GRAPHICS: - frame->content_type = HDMI_CONTENT_TYPE_GRAPHICS; - break; - case DRM_MODE_CONTENT_TYPE_CINEMA: - frame->content_type = HDMI_CONTENT_TYPE_CINEMA; - break; - case DRM_MODE_CONTENT_TYPE_GAME: - frame->content_type = HDMI_CONTENT_TYPE_GAME; - break; - case DRM_MODE_CONTENT_TYPE_PHOTO: - frame->content_type = HDMI_CONTENT_TYPE_PHOTO; - break; - default: - /* Graphics is the default(0) */ - frame->content_type = HDMI_CONTENT_TYPE_GRAPHICS; - } + 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; - frame->itc = conn_state->content_type != DRM_MODE_CONTENT_TYPE_NO_DATA; + 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_hdmi_avi_infoframe_content_type); +EXPORT_SYMBOL(drm_mode_create_tv_margin_properties); /** - * drm_create_tv_properties - create TV specific connector 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 @@ -1147,10 +2138,17 @@ EXPORT_SYMBOL(drm_hdmi_avi_infoframe_content_type); * 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; @@ -1183,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) + if (drm_mode_create_tv_margin_properties(dev)) 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) - 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; - - 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; + 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; - for (i = 0; i < num_modes; i++) - drm_property_add_enum(dev->mode_config.tv_mode_property, - 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); @@ -1247,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); /** @@ -1257,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) { @@ -1320,7 +2346,7 @@ EXPORT_SYMBOL(drm_mode_create_scaling_mode_property); * * Absence of the property should indicate absence of support. * - * "vrr_enabled": + * "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 @@ -1338,12 +2364,6 @@ EXPORT_SYMBOL(drm_mode_create_scaling_mode_property); * * The driver may place further restrictions within these minimum * and maximum bounds. - * - * The semantics for the vertical blank timestamp differ when - * variable refresh rate is active. The vertical blank timestamp - * is defined to be an estimate using the current mode's fixed - * refresh rate timings. The semantics for the page-flip event - * timestamp remain the same. */ /** @@ -1355,7 +2375,7 @@ EXPORT_SYMBOL(drm_mode_create_scaling_mode_property); * variable refresh rate capability for a connector. * * Returns: - * Zero on success, negative errono on failure. + * Zero on success, negative errno on failure. */ int drm_connector_attach_vrr_capable_property( struct drm_connector *connector) @@ -1383,7 +2403,7 @@ EXPORT_SYMBOL(drm_connector_attach_vrr_capable_property); * @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(). @@ -1438,67 +2458,200 @@ int drm_connector_attach_scaling_mode_property(struct drm_connector *connector, EXPORT_SYMBOL(drm_connector_attach_scaling_mode_property); /** - * drm_connector_attach_content_protection_property - attach content protection - * property - * - * @connector: connector to attach CP property on. - * - * This is used to add support for content protection on select connectors. - * Content Protection is intentionally vague to allow for different underlying - * technologies, however it is most implemented by HDCP. + * drm_mode_create_aspect_ratio_property - create aspect ratio property + * @dev: DRM device * - * The content protection will be set to &drm_connector_state.content_protection + * 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_connector_attach_content_protection_property( - struct drm_connector *connector) +int drm_mode_create_aspect_ratio_property(struct drm_device *dev) { - struct drm_device *dev = connector->dev; - struct drm_property *prop; + if (dev->mode_config.aspect_ratio_property) + return 0; - prop = drm_property_create_enum(dev, 0, "Content Protection", - drm_cp_enum_list, - ARRAY_SIZE(drm_cp_enum_list)); - if (!prop) + dev->mode_config.aspect_ratio_property = + drm_property_create_enum(dev, 0, "aspect ratio", + drm_aspect_ratio_enum_list, + ARRAY_SIZE(drm_aspect_ratio_enum_list)); + + if (dev->mode_config.aspect_ratio_property == NULL) return -ENOMEM; - drm_object_attach_property(&connector->base, prop, - DRM_MODE_CONTENT_PROTECTION_UNDESIRED); + return 0; +} +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; - connector->content_protection_property = prop; + 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; } -EXPORT_SYMBOL(drm_connector_attach_content_protection_property); /** - * drm_mode_create_aspect_ratio_property - create aspect ratio property - * @dev: DRM device + * 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 - * connectors. + * HDMI connectors. * * Returns: * Zero on success, negative errno on failure. */ -int drm_mode_create_aspect_ratio_property(struct drm_device *dev) +int drm_mode_create_hdmi_colorspace_property(struct drm_connector *connector, + u32 supported_colorspaces) { - if (dev->mode_config.aspect_ratio_property) - return 0; + u32 colorspaces; - dev->mode_config.aspect_ratio_property = - drm_property_create_enum(dev, 0, "aspect ratio", - drm_aspect_ratio_enum_list, - ARRAY_SIZE(drm_aspect_ratio_enum_list)); + if (supported_colorspaces) + colorspaces = supported_colorspaces & hdmi_colorspaces; + else + colorspaces = hdmi_colorspaces; - if (dev->mode_config.aspect_ratio_property == NULL) - return -ENOMEM; + return drm_mode_create_colorspace_property(connector, colorspaces); +} +EXPORT_SYMBOL(drm_mode_create_hdmi_colorspace_property); - return 0; +/** + * 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_aspect_ratio_property); +EXPORT_SYMBOL(drm_mode_create_dp_colorspace_property); /** * drm_mode_create_content_type_property - create content type property @@ -1531,7 +2684,10 @@ 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) { @@ -1571,11 +2727,11 @@ int drm_connector_set_path_property(struct drm_connector *connector, 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_connector_set_path_property); @@ -1587,6 +2743,8 @@ EXPORT_SYMBOL(drm_connector_set_path_property); * 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. @@ -1599,11 +2757,11 @@ int drm_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; } @@ -1614,68 +2772,16 @@ int drm_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_connector_set_tile_property); /** - * drm_connector_update_edid_property - update the edid property of a connector - * @connector: drm connector - * @edid: new value of the edid property - * - * This function creates a new blob modeset object and assigns its id to the - * connector's edid property. - * - * Returns: - * Zero on success, negative errno on failure. - */ -int drm_connector_update_edid_property(struct drm_connector *connector, - const struct edid *edid) -{ - struct drm_device *dev = connector->dev; - size_t size = 0; - int ret; - - /* ignore requests to set edid when overridden */ - if (connector->override_edid) - return 0; - - if (edid) - size = EDID_LENGTH * (1 + edid->extensions); - - /* Set the display info, using edid if available, otherwise - * reseting the values to defaults. This duplicates the work - * done in drm_add_edid_modes, but that function is not - * consistently called before this one in all drivers and the - * computation is cheap enough that it seems better to - * duplicate it rather than attempt to ensure some arbitrary - * ordering of calls. - */ - if (edid) - drm_add_display_info(connector, edid); - else - drm_reset_display_info(connector); - - drm_object_property_set_value(&connector->base, - dev->mode_config.non_desktop_property, - connector->display_info.non_desktop); - - ret = drm_property_replace_global_blob(dev, - &connector->edid_blob_ptr, - size, - edid, - &connector->base, - dev->mode_config.edid_property); - return ret; -} -EXPORT_SYMBOL(drm_connector_update_edid_property); - -/** * drm_connector_set_link_status_property - Set link status property of a connector * @connector: drm connector * @link_status: new value of link status property (0: Good, 1: Bad) @@ -1740,6 +2846,108 @@ int drm_connector_attach_max_bpc_property(struct drm_connector *connector, 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_connector_attach_colorspace_property); + +/** + * 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 @@ -1751,6 +2959,9 @@ EXPORT_SYMBOL(drm_connector_attach_max_bpc_property); 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); @@ -1758,38 +2969,44 @@ void drm_connector_set_vrr_capable_property( EXPORT_SYMBOL(drm_connector_set_vrr_capable_property); /** - * drm_connector_init_panel_orientation_property - - * initialize the connecters panel_orientation property - * @connector: connector for which to init the panel-orientation property. - * @width: width in pixels of the panel, used for panel quirk detection - * @height: height in pixels of the panel, used for panel quirk detection + * 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 * - * This function should only be called for built-in panels, after setting - * connector->display_info.panel_orientation first (if known). + * This function sets the connector's panel_orientation and attaches + * a "panel orientation" property to the connector. * - * This function will check for platform specific (e.g. DMI based) quirks - * overriding display_info.panel_orientation first, then if panel_orientation - * is not DRM_MODE_PANEL_ORIENTATION_UNKNOWN it will attach the - * "panel orientation" property to the connector. + * 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. */ -int drm_connector_init_panel_orientation_property( - struct drm_connector *connector, int width, int height) +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; - int orientation_quirk; - orientation_quirk = drm_get_panel_orientation_quirk(width, height); - if (orientation_quirk != DRM_MODE_PANEL_ORIENTATION_UNKNOWN) - info->panel_orientation = orientation_quirk; + /* Already set? */ + if (info->panel_orientation != DRM_MODE_PANEL_ORIENTATION_UNKNOWN) + return 0; - if (info->panel_orientation == DRM_MODE_PANEL_ORIENTATION_UNKNOWN) + /* 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, @@ -1806,7 +3023,212 @@ int drm_connector_init_panel_orientation_property( info->panel_orientation); return 0; } -EXPORT_SYMBOL(drm_connector_init_panel_orientation_property); +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); + 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_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; + + 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, @@ -1844,7 +3266,8 @@ int drm_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; @@ -1852,7 +3275,7 @@ static struct drm_encoder *drm_connector_get_encoder(struct drm_connector *conne static bool drm_mode_expose_to_userspace(const struct drm_display_mode *mode, - const struct list_head *export_list, + const struct list_head *modes, const struct drm_file *file_priv) { /* @@ -1868,15 +3291,17 @@ drm_mode_expose_to_userspace(const struct drm_display_mode *mode, * while preparing the list of user-modes. */ if (!file_priv->aspect_ratio_allowed) { - struct drm_display_mode *mode_itr; + const struct drm_display_mode *mode_itr; - list_for_each_entry(mode_itr, export_list, export_head) - if (drm_mode_match(mode_itr, mode, + 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; @@ -1893,11 +3318,10 @@ 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; - LIST_HEAD(export_list); + bool is_current_master; if (!drm_core_check_feature(dev, DRIVER_MODESET)) return -EOPNOTSUPP; @@ -1908,14 +3332,13 @@ int drm_mode_getconnector(struct drm_device *dev, void *data, if (!connector) return -ENOENT; - drm_connector_for_each_possible_encoder(connector, encoder, i) - 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); - drm_connector_for_each_possible_encoder(connector, encoder, i) { + drm_connector_for_each_possible_encoder(connector, encoder) { if (put_user(encoder->base.id, encoder_ptr + copied)) { ret = -EFAULT; goto out; @@ -1929,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; @@ -1942,25 +3371,30 @@ 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, &export_list, + 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)) { - list_add_tail(&mode->export_head, &export_list); + mode->expose_to_userspace = true; mode_count++; } + } /* * This ioctl is called twice, once to determine how much space is * needed, and the 2nd time to fill it. - * The modes that need to be exposed to the user are maintained in the - * 'export_list'. When the ioctl is called first time to determine the, - * space, the export_list gets filled, to find the no.of modes. In the - * 2nd time, the user modes are filled, one by one from the export_list. */ if ((out_resp->count_modes >= mode_count) && mode_count) { copied = 0; mode_ptr = (struct drm_mode_modeinfo __user *)(unsigned long)out_resp->modes_ptr; - list_for_each_entry(mode, &export_list, export_head) { + list_for_each_entry(mode, &connector->modes, head) { + 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 @@ -1971,13 +3405,26 @@ int drm_mode_getconnector(struct drm_device *dev, void *data, 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); @@ -1989,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); @@ -2002,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 @@ -2019,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); @@ -2050,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)) { @@ -2077,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); @@ -2099,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); |
