| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Copyright (C) 2013, NVIDIA Corporation. All rights reserved. | |
| * Copyright (C) 2016 Laurent Pinchart <laurent.pinchart@ideasonboard.com> | |
| * Copyright (C) 2017 Broadcom | |
| */ | |
| #include <linux/backlight.h> | |
| #include <linux/debugfs.h> | |
| #include <linux/err.h> | |
| #include <linux/export.h> | |
| #include <linux/module.h> | |
| #include <linux/of.h> | |
| #include <drm/drm_atomic_helper.h> | |
| #include <drm/drm_bridge.h> | |
| #include <drm/drm_connector.h> | |
| #include <drm/drm_crtc.h> | |
| #include <drm/drm_encoder.h> | |
| #include <drm/drm_managed.h> | |
| #include <drm/drm_modeset_helper_vtables.h> | |
| #include <drm/drm_of.h> | |
| #include <drm/drm_panel.h> | |
| #include <drm/drm_print.h> | |
| #include <drm/drm_probe_helper.h> | |
| static DEFINE_MUTEX(panel_lock); | |
| static LIST_HEAD(panel_list); | |
| /** | |
| * DOC: drm panel | |
| * | |
| * The DRM panel helpers allow drivers to register panel objects with a | |
| * central registry and provide functions to retrieve those panels in display | |
| * drivers. | |
| * | |
| * For easy integration into drivers using the &drm_bridge infrastructure please | |
| * take look at drm_panel_bridge_add() and devm_drm_panel_bridge_add(). | |
| */ | |
| static inline struct drm_panel * | |
| drm_bridge_to_panel(const struct drm_bridge *bridge) | |
| { | |
| return container_of(bridge, struct drm_panel, bridge); | |
| } | |
| static inline struct drm_panel * | |
| drm_connector_to_panel(const struct drm_connector *connector) | |
| { | |
| return container_of(connector, struct drm_panel, connector); | |
| } | |
| struct panel_bridge { | |
| struct drm_bridge bridge; | |
| struct drm_connector connector; | |
| struct drm_panel *panel; | |
| u32 connector_type; | |
| }; | |
| static int panel_bridge_connector_get_modes(struct drm_connector *connector) | |
| { | |
| struct drm_panel *panel = drm_connector_to_panel(connector); | |
| return drm_panel_get_modes(panel, connector); | |
| } | |
| static const struct drm_connector_helper_funcs | |
| panel_bridge_connector_helper_funcs = { | |
| .get_modes = panel_bridge_connector_get_modes, | |
| }; | |
| static const struct drm_connector_funcs panel_bridge_connector_funcs = { | |
| .reset = drm_atomic_helper_connector_reset, | |
| .fill_modes = drm_helper_probe_single_connector_modes, | |
| .destroy = drm_connector_cleanup, | |
| .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, | |
| .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | |
| }; | |
| static int panel_bridge_attach(struct drm_bridge *bridge, | |
| struct drm_encoder *encoder, | |
| enum drm_bridge_attach_flags flags) | |
| { | |
| struct drm_panel *panel = drm_bridge_to_panel(bridge); | |
| struct drm_connector *connector = &panel->connector; | |
| int ret; | |
| if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) | |
| return 0; | |
| drm_connector_helper_add(connector, | |
| &panel_bridge_connector_helper_funcs); | |
| ret = drm_connector_init(bridge->dev, connector, | |
| &panel_bridge_connector_funcs, | |
| panel->connector_type); | |
| if (ret) { | |
| DRM_ERROR("Failed to initialize connector\n"); | |
| return ret; | |
| } | |
| drm_panel_bridge_set_orientation(connector, bridge); | |
| drm_connector_attach_encoder(connector, encoder); | |
| if (bridge->dev->registered) { | |
| if (connector->funcs->reset) | |
| connector->funcs->reset(connector); | |
| drm_connector_register(connector); | |
| } | |
| return 0; | |
| } | |
| static void panel_bridge_detach(struct drm_bridge *bridge) | |
| { | |
| struct drm_panel *panel = drm_bridge_to_panel(bridge); | |
| struct drm_connector *connector = &panel->connector; | |
| /* Cleanup the connector if we know it was initialized */ | |
| if (connector->dev) | |
| drm_connector_cleanup(connector); | |
| } | |
| static void panel_bridge_atomic_pre_enable(struct drm_bridge *bridge, | |
| struct drm_atomic_commit *atomic_state) | |
| { | |
| struct drm_panel *panel = drm_bridge_to_panel(bridge); | |
| struct drm_encoder *encoder = bridge->encoder; | |
| struct drm_crtc *crtc; | |
| struct drm_crtc_state *old_crtc_state; | |
| crtc = drm_atomic_get_new_crtc_for_encoder(atomic_state, encoder); | |
| if (!crtc) | |
| return; | |
| old_crtc_state = drm_atomic_get_old_crtc_state(atomic_state, crtc); | |
| if (old_crtc_state && old_crtc_state->self_refresh_active) | |
| return; | |
| drm_panel_prepare(panel); | |
| } | |
| static void panel_bridge_atomic_enable(struct drm_bridge *bridge, | |
| struct drm_atomic_commit *atomic_state) | |
| { | |
| struct drm_panel *panel = drm_bridge_to_panel(bridge); | |
| struct drm_encoder *encoder = bridge->encoder; | |
| struct drm_crtc *crtc; | |
| struct drm_crtc_state *old_crtc_state; | |
| crtc = drm_atomic_get_new_crtc_for_encoder(atomic_state, encoder); | |
| if (!crtc) | |
| return; | |
| old_crtc_state = drm_atomic_get_old_crtc_state(atomic_state, crtc); | |
| if (old_crtc_state && old_crtc_state->self_refresh_active) | |
| return; | |
| drm_panel_enable(panel); | |
| } | |
| static void panel_bridge_atomic_disable(struct drm_bridge *bridge, | |
| struct drm_atomic_commit *atomic_state) | |
| { | |
| struct drm_panel *panel = drm_bridge_to_panel(bridge); | |
| struct drm_encoder *encoder = bridge->encoder; | |
| struct drm_crtc *crtc; | |
| struct drm_crtc_state *new_crtc_state; | |
| crtc = drm_atomic_get_old_crtc_for_encoder(atomic_state, encoder); | |
| if (!crtc) | |
| return; | |
| new_crtc_state = drm_atomic_get_new_crtc_state(atomic_state, crtc); | |
| if (new_crtc_state && new_crtc_state->self_refresh_active) | |
| return; | |
| drm_panel_disable(panel); | |
| } | |
| static void panel_bridge_atomic_post_disable(struct drm_bridge *bridge, | |
| struct drm_atomic_commit *atomic_state) | |
| { | |
| struct drm_panel *panel = drm_bridge_to_panel(bridge); | |
| struct drm_encoder *encoder = bridge->encoder; | |
| struct drm_crtc *crtc; | |
| struct drm_crtc_state *new_crtc_state; | |
| crtc = drm_atomic_get_old_crtc_for_encoder(atomic_state, encoder); | |
| if (!crtc) | |
| return; | |
| new_crtc_state = drm_atomic_get_new_crtc_state(atomic_state, crtc); | |
| if (new_crtc_state && new_crtc_state->self_refresh_active) | |
| return; | |
| drm_panel_unprepare(panel); | |
| } | |
| static int panel_bridge_get_modes(struct drm_bridge *bridge, | |
| struct drm_connector *connector) | |
| { | |
| struct drm_panel *panel = drm_bridge_to_panel(bridge); | |
| return drm_panel_get_modes(panel, connector); | |
| } | |
| static void panel_bridge_debugfs_init(struct drm_bridge *bridge, | |
| struct dentry *root) | |
| { | |
| struct drm_panel *panel = drm_bridge_to_panel(bridge); | |
| root = debugfs_create_dir("panel", root); | |
| if (panel->funcs->debugfs_init) | |
| panel->funcs->debugfs_init(panel, root); | |
| } | |
| static const struct drm_bridge_funcs panel_bridge_bridge_funcs = { | |
| .attach = panel_bridge_attach, | |
| .detach = panel_bridge_detach, | |
| .atomic_pre_enable = panel_bridge_atomic_pre_enable, | |
| .atomic_enable = panel_bridge_atomic_enable, | |
| .atomic_disable = panel_bridge_atomic_disable, | |
| .atomic_post_disable = panel_bridge_atomic_post_disable, | |
| .get_modes = panel_bridge_get_modes, | |
| .atomic_create_state = drm_atomic_helper_bridge_create_state, | |
| .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, | |
| .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, | |
| .atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt, | |
| .debugfs_init = panel_bridge_debugfs_init, | |
| }; | |
| /** | |
| * drm_bridge_is_panel - Checks if a drm_bridge is a panel_bridge. | |
| * | |
| * @bridge: The drm_bridge to be checked. | |
| * | |
| * Returns true if the bridge is a panel bridge, or false otherwise. | |
| */ | |
| bool drm_bridge_is_panel(const struct drm_bridge *bridge) | |
| { | |
| return bridge->funcs == &panel_bridge_bridge_funcs; | |
| } | |
| EXPORT_SYMBOL(drm_bridge_is_panel); | |
| /** | |
| * drm_panel_bridge_add - Creates a &drm_bridge and &drm_connector that | |
| * just calls the appropriate functions from &drm_panel. | |
| * | |
| * @panel: The drm_panel being wrapped. Must be non-NULL. | |
| * | |
| * This function is deprecated. A drm_bridge is embedded into every panel. | |
| * Don't use it in new drivers and update your driver to stop using it. | |
| * | |
| * For drivers converting from directly using drm_panel: The expected | |
| * usage pattern is that during either encoder module probe or DSI | |
| * host attach, a drm_panel will be looked up through | |
| * drm_of_find_panel_or_bridge(). drm_panel_bridge_add() is used to | |
| * wrap that panel in the new bridge, and the result can then be | |
| * passed to drm_bridge_attach(). The drm_panel_prepare() and related | |
| * functions can be dropped from the encoder driver (they're now | |
| * called by the KMS helpers before calling into the encoder), along | |
| * with connector creation. When done with the bridge (after | |
| * drm_mode_config_cleanup() if the bridge has already been attached), then | |
| * drm_panel_bridge_remove() to free it. | |
| * | |
| * The connector type is set to @panel->connector_type, which must be set to a | |
| * known type. Calling this function with a panel whose connector type is | |
| * DRM_MODE_CONNECTOR_Unknown will return ERR_PTR(-EINVAL). | |
| * | |
| * See devm_drm_panel_bridge_add() for an automatically managed version of this | |
| * function. | |
| */ | |
| struct drm_bridge *drm_panel_bridge_add(struct drm_panel *panel) | |
| { | |
| if (WARN_ON(panel->connector_type == DRM_MODE_CONNECTOR_Unknown)) | |
| return ERR_PTR(-EINVAL); | |
| return drm_panel_bridge_add_typed(panel, panel->connector_type); | |
| } | |
| EXPORT_SYMBOL(drm_panel_bridge_add); | |
| /** | |
| * drm_panel_bridge_add_typed - Pretend to create a &drm_bridge and &drm_connector with | |
| * an explicit connector type. | |
| * @panel: The drm_panel being wrapped. Must be non-NULL. | |
| * @connector_type: The connector type (DRM_MODE_CONNECTOR_*) | |
| * | |
| * This is just like drm_panel_bridge_add(), but forces the connector type to | |
| * @connector_type instead of infering it from the panel. | |
| * | |
| * This function is deprecated. A drm_bridge is embedded into every panel. | |
| * Don't use it in new drivers and update your driver to stop using it. | |
| */ | |
| struct drm_bridge *drm_panel_bridge_add_typed(struct drm_panel *panel, | |
| u32 connector_type) | |
| { | |
| if (!panel) | |
| return ERR_PTR(-EINVAL); | |
| return drm_bridge_get(&panel->bridge); | |
| } | |
| EXPORT_SYMBOL(drm_panel_bridge_add_typed); | |
| /** | |
| * drm_panel_bridge_remove - Unregisters and frees a drm_bridge | |
| * created by drm_panel_bridge_add(). | |
| * | |
| * @bridge: The drm_bridge being freed. | |
| * | |
| * This function is deprecated. A drm_bridge is embedded into every panel | |
| * and removed when the panel is removed. Don't use it in new drivers and | |
| * update your driver to stop using it. | |
| */ | |
| void drm_panel_bridge_remove(struct drm_bridge *bridge) | |
| { | |
| if (!bridge) | |
| return; | |
| if (!drm_bridge_is_panel(bridge)) { | |
| drm_warn(bridge->dev, "%s: called on non-panel bridge!\n", __func__); | |
| return; | |
| } | |
| drm_bridge_put(bridge); | |
| } | |
| EXPORT_SYMBOL(drm_panel_bridge_remove); | |
| /** | |
| * drm_panel_bridge_set_orientation - Set the connector's panel orientation | |
| * from the bridge that can be transformed to panel bridge. | |
| * | |
| * @connector: The connector to be set panel orientation. | |
| * @bridge: The drm_bridge whose orientation should be set. | |
| * | |
| * Returns 0 on success, negative errno on failure. | |
| */ | |
| int drm_panel_bridge_set_orientation(struct drm_connector *connector, | |
| struct drm_bridge *bridge) | |
| { | |
| struct drm_panel *panel = drm_bridge_to_panel(bridge); | |
| return drm_connector_set_orientation_from_panel(connector, panel); | |
| } | |
| EXPORT_SYMBOL(drm_panel_bridge_set_orientation); | |
| static void devm_drm_panel_bridge_release(struct device *dev, void *res) | |
| { | |
| struct drm_bridge *bridge = *(struct drm_bridge **)res; | |
| if (!bridge) | |
| return; | |
| drm_bridge_put(bridge); | |
| } | |
| /** | |
| * devm_drm_panel_bridge_add - Creates a managed &drm_bridge and &drm_connector | |
| * that just calls the appropriate functions from &drm_panel. | |
| * @dev: device to tie the bridge lifetime to | |
| * @panel: The drm_panel being wrapped. Must be non-NULL. | |
| * | |
| * This function is deprecated. A drm_bridge is embedded into every panel. | |
| * Don't use it in new drivers and update your driver to stop using it. | |
| * | |
| * This is the managed version of drm_panel_bridge_add() which automatically | |
| * calls drm_panel_bridge_remove() when @dev is unbound. | |
| */ | |
| struct drm_bridge *devm_drm_panel_bridge_add(struct device *dev, | |
| struct drm_panel *panel) | |
| { | |
| if (WARN_ON(panel->connector_type == DRM_MODE_CONNECTOR_Unknown)) | |
| return ERR_PTR(-EINVAL); | |
| return devm_drm_panel_bridge_add_typed(dev, panel, | |
| panel->connector_type); | |
| } | |
| EXPORT_SYMBOL(devm_drm_panel_bridge_add); | |
| /** | |
| * devm_drm_panel_bridge_add_typed - Creates a managed &drm_bridge and | |
| * &drm_connector with an explicit connector type. | |
| * @dev: device to tie the bridge lifetime to | |
| * @panel: The drm_panel being wrapped. Must be non-NULL. | |
| * @connector_type: The connector type (DRM_MODE_CONNECTOR_*) | |
| * | |
| * This is just like devm_drm_panel_bridge_add(), but forces the connector type | |
| * to @connector_type instead of infering it from the panel. | |
| * | |
| * This function is deprecated. A drm_bridge is embedded into every panel. | |
| * Don't use it in new drivers and update your driver to stop using it. | |
| */ | |
| struct drm_bridge *devm_drm_panel_bridge_add_typed(struct device *dev, | |
| struct drm_panel *panel, | |
| u32 connector_type) | |
| { | |
| struct drm_bridge **ptr, *bridge; | |
| ptr = devres_alloc(devm_drm_panel_bridge_release, sizeof(*ptr), | |
| GFP_KERNEL); | |
| if (!ptr) | |
| return ERR_PTR(-ENOMEM); | |
| bridge = drm_panel_bridge_add_typed(panel, connector_type); | |
| if (IS_ERR(bridge)) { | |
| devres_free(ptr); | |
| return bridge; | |
| } | |
| *ptr = bridge; | |
| devres_add(dev, ptr); | |
| return &panel->bridge; | |
| } | |
| EXPORT_SYMBOL(devm_drm_panel_bridge_add_typed); | |
| static void drmm_drm_panel_bridge_release(struct drm_device *drm, void *ptr) | |
| { | |
| struct drm_bridge *bridge = ptr; | |
| drm_panel_bridge_remove(bridge); | |
| } | |
| /** | |
| * drmm_panel_bridge_add - Creates a DRM-managed &drm_bridge and | |
| * &drm_connector that just calls the | |
| * appropriate functions from &drm_panel. | |
| * | |
| * @drm: DRM device to tie the bridge lifetime to | |
| * @panel: The drm_panel being wrapped. Must be non-NULL. | |
| * | |
| * This function is deprecated. A drm_bridge is embedded into every panel. | |
| * Don't use it in new drivers and update your driver to stop using it. | |
| * | |
| * This is the DRM-managed version of drm_panel_bridge_add() which | |
| * automatically calls drm_panel_bridge_remove() when @dev is cleaned | |
| * up. | |
| */ | |
| struct drm_bridge *drmm_panel_bridge_add(struct drm_device *drm, | |
| struct drm_panel *panel) | |
| { | |
| struct drm_bridge *bridge; | |
| int ret; | |
| bridge = drm_panel_bridge_add_typed(panel, panel->connector_type); | |
| if (IS_ERR(bridge)) | |
| return bridge; | |
| ret = drmm_add_action_or_reset(drm, drmm_drm_panel_bridge_release, | |
| bridge); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| return bridge; | |
| } | |
| EXPORT_SYMBOL(drmm_panel_bridge_add); | |
| /** | |
| * drm_panel_bridge_connector - return the connector for the panel (for | |
| * legacy drivers not using DRM_BRIDGE_ATTACH_NO_CONNECTOR) | |
| * @bridge: The drm_bridge. | |
| * | |
| * This function gives external access to the connector. | |
| * | |
| * Returns: Pointer to drm_connector | |
| */ | |
| struct drm_connector *drm_panel_bridge_connector(struct drm_bridge *bridge) | |
| { | |
| struct drm_panel *panel; | |
| panel = drm_bridge_to_panel(bridge); | |
| return &panel->connector; | |
| } | |
| EXPORT_SYMBOL(drm_panel_bridge_connector); | |
| #ifdef CONFIG_OF | |
| /** | |
| * devm_drm_of_get_bridge - Return next bridge in the chain | |
| * @dev: device to tie the bridge lifetime to | |
| * @np: device tree node containing encoder output ports | |
| * @port: port in the device tree node | |
| * @endpoint: endpoint in the device tree node | |
| * | |
| * This function is deprecated. A drm_bridge is embedded into every panel. | |
| * Don't use it in new drivers and convert your driver to | |
| * of_drm_get_bridge_by_endpoint(). | |
| * | |
| * Given a DT node's port and endpoint number, finds the connected node | |
| * and returns the associated bridge if any, or creates and returns a | |
| * drm panel bridge instance if a panel is connected. | |
| * | |
| * Returns a pointer to the bridge if successful, or an error pointer | |
| * otherwise. | |
| */ | |
| struct drm_bridge *devm_drm_of_get_bridge(struct device *dev, | |
| struct device_node *np, | |
| u32 port, u32 endpoint) | |
| { | |
| struct drm_bridge *bridge; | |
| struct drm_panel *panel; | |
| int ret; | |
| ret = drm_of_find_panel_or_bridge(np, port, endpoint, | |
| &panel, &bridge); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| if (panel) { | |
| bridge = devm_drm_panel_bridge_add(dev, panel); | |
| drm_panel_put(panel); | |
| } | |
| return bridge; | |
| } | |
| EXPORT_SYMBOL(devm_drm_of_get_bridge); | |
| /** | |
| * drmm_of_get_bridge - Return next bridge in the chain | |
| * @drm: device to tie the bridge lifetime to | |
| * @np: device tree node containing encoder output ports | |
| * @port: port in the device tree node | |
| * @endpoint: endpoint in the device tree node | |
| * | |
| * This function is deprecated. A drm_bridge is embedded into every panel. | |
| * Don't use it in new drivers and convert your driver to | |
| * of_drm_get_bridge_by_endpoint(). | |
| * | |
| * Given a DT node's port and endpoint number, finds the connected node | |
| * and returns the associated bridge if any, or creates and returns a | |
| * drm panel bridge instance if a panel is connected. | |
| * | |
| * Returns a drmm managed pointer to the bridge if successful, or an error | |
| * pointer otherwise. | |
| */ | |
| struct drm_bridge *drmm_of_get_bridge(struct drm_device *drm, | |
| struct device_node *np, | |
| u32 port, u32 endpoint) | |
| { | |
| struct drm_bridge *bridge; | |
| struct drm_panel *panel; | |
| int ret; | |
| ret = drm_of_find_panel_or_bridge(np, port, endpoint, | |
| &panel, &bridge); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| if (panel) { | |
| bridge = drmm_panel_bridge_add(drm, panel); | |
| drm_panel_put(panel); | |
| } | |
| return bridge; | |
| } | |
| EXPORT_SYMBOL(drmm_of_get_bridge); | |
| #endif | |
| /** | |
| * drm_panel_init - initialize a panel | |
| * @panel: DRM panel | |
| * @dev: parent device of the panel | |
| * @funcs: panel operations | |
| * @connector_type: the connector type (DRM_MODE_CONNECTOR_*) corresponding to | |
| * the panel interface (must NOT be DRM_MODE_CONNECTOR_Unknown) | |
| * | |
| * Initialize the panel structure for subsequent registration with | |
| * drm_panel_add(). | |
| */ | |
| static void drm_panel_init(struct drm_panel *panel, struct device *dev, | |
| const struct drm_panel_funcs *funcs, | |
| int connector_type) | |
| { | |
| if (connector_type == DRM_MODE_CONNECTOR_Unknown) | |
| DRM_WARN("%s: %s: a valid connector type is required!\n", __func__, dev_name(dev)); | |
| INIT_LIST_HEAD(&panel->list); | |
| INIT_LIST_HEAD(&panel->followers); | |
| mutex_init(&panel->follower_lock); | |
| panel->dev = dev; | |
| panel->funcs = funcs; | |
| panel->connector_type = connector_type; | |
| } | |
| /** | |
| * drm_panel_add - add a panel to the global registry | |
| * @panel: panel to add | |
| * | |
| * Add a panel to the global registry so that it can be looked | |
| * up by display drivers. The panel to be added must have been | |
| * allocated by devm_drm_panel_alloc(). | |
| */ | |
| void drm_panel_add(struct drm_panel *panel) | |
| { | |
| drm_panel_get(panel); | |
| mutex_lock(&panel_lock); | |
| list_add_tail(&panel->list, &panel_list); | |
| mutex_unlock(&panel_lock); | |
| panel->bridge.of_node = panel->dev->of_node; | |
| panel->bridge.ops = DRM_BRIDGE_OP_MODES; | |
| panel->bridge.type = panel->connector_type; | |
| panel->bridge.pre_enable_prev_first = panel->prepare_prev_first; | |
| drm_bridge_add(&panel->bridge); | |
| } | |
| EXPORT_SYMBOL(drm_panel_add); | |
| /** | |
| * drm_panel_remove - remove a panel from the global registry | |
| * @panel: DRM panel | |
| * | |
| * Removes a panel from the global registry. | |
| */ | |
| void drm_panel_remove(struct drm_panel *panel) | |
| { | |
| drm_bridge_remove(&panel->bridge); | |
| mutex_lock(&panel_lock); | |
| list_del_init(&panel->list); | |
| mutex_unlock(&panel_lock); | |
| drm_panel_put(panel); | |
| } | |
| EXPORT_SYMBOL(drm_panel_remove); | |
| static void drm_panel_add_release(void *data) | |
| { | |
| drm_panel_remove(data); | |
| } | |
| /** | |
| * devm_drm_panel_add - add a panel to the global registry using devres | |
| * @dev: device to which the panel is attached | |
| * @panel: panel to add | |
| * | |
| * Add a panel to the global registry so that it can be looked | |
| * up by display drivers. The panel to be added must have been | |
| * allocated by devm_drm_panel_alloc(). Unlike drm_panel_add() with this | |
| * function there is no need to call drm_panel_remove(), it will be called | |
| * automatically. | |
| */ | |
| int devm_drm_panel_add(struct device *dev, struct drm_panel *panel) | |
| { | |
| drm_panel_add(panel); | |
| return devm_add_action_or_reset(dev, drm_panel_add_release, panel); | |
| } | |
| EXPORT_SYMBOL(devm_drm_panel_add); | |
| /** | |
| * drm_panel_prepare - power on a panel | |
| * @panel: DRM panel | |
| * | |
| * Calling this function will enable power and deassert any reset signals to | |
| * the panel. After this has completed it is possible to communicate with any | |
| * integrated circuitry via a command bus. This function cannot fail (as it is | |
| * called from the pre_enable call chain). There will always be a call to | |
| * drm_panel_disable() afterwards. | |
| */ | |
| void drm_panel_prepare(struct drm_panel *panel) | |
| { | |
| struct drm_panel_follower *follower; | |
| int ret; | |
| if (!panel) | |
| return; | |
| if (panel->prepared) { | |
| dev_warn(panel->dev, "Skipping prepare of already prepared panel\n"); | |
| return; | |
| } | |
| mutex_lock(&panel->follower_lock); | |
| if (panel->funcs && panel->funcs->prepare) { | |
| ret = panel->funcs->prepare(panel); | |
| if (ret < 0) | |
| goto exit; | |
| } | |
| panel->prepared = true; | |
| list_for_each_entry(follower, &panel->followers, list) { | |
| if (!follower->funcs->panel_prepared) | |
| continue; | |
| ret = follower->funcs->panel_prepared(follower); | |
| if (ret < 0) | |
| dev_info(panel->dev, "%ps failed: %d\n", | |
| follower->funcs->panel_prepared, ret); | |
| } | |
| exit: | |
| mutex_unlock(&panel->follower_lock); | |
| } | |
| EXPORT_SYMBOL(drm_panel_prepare); | |
| /** | |
| * drm_panel_unprepare - power off a panel | |
| * @panel: DRM panel | |
| * | |
| * Calling this function will completely power off a panel (assert the panel's | |
| * reset, turn off power supplies, ...). After this function has completed, it | |
| * is usually no longer possible to communicate with the panel until another | |
| * call to drm_panel_prepare(). | |
| */ | |
| void drm_panel_unprepare(struct drm_panel *panel) | |
| { | |
| struct drm_panel_follower *follower; | |
| int ret; | |
| if (!panel) | |
| return; | |
| /* | |
| * If you are seeing the warning below it likely means one of two things: | |
| * - Your panel driver incorrectly calls drm_panel_unprepare() in its | |
| * shutdown routine. You should delete this. | |
| * - You are using panel-edp or panel-simple and your DRM modeset | |
| * driver's shutdown() callback happened after the panel's shutdown(). | |
| * In this case the warning is harmless though ideally you should | |
| * figure out how to reverse the order of the shutdown() callbacks. | |
| */ | |
| if (!panel->prepared) { | |
| dev_warn(panel->dev, "Skipping unprepare of already unprepared panel\n"); | |
| return; | |
| } | |
| mutex_lock(&panel->follower_lock); | |
| list_for_each_entry(follower, &panel->followers, list) { | |
| if (!follower->funcs->panel_unpreparing) | |
| continue; | |
| ret = follower->funcs->panel_unpreparing(follower); | |
| if (ret < 0) | |
| dev_info(panel->dev, "%ps failed: %d\n", | |
| follower->funcs->panel_unpreparing, ret); | |
| } | |
| if (panel->funcs && panel->funcs->unprepare) { | |
| ret = panel->funcs->unprepare(panel); | |
| if (ret < 0) | |
| goto exit; | |
| } | |
| panel->prepared = false; | |
| exit: | |
| mutex_unlock(&panel->follower_lock); | |
| } | |
| EXPORT_SYMBOL(drm_panel_unprepare); | |
| /** | |
| * drm_panel_enable - enable a panel | |
| * @panel: DRM panel | |
| * | |
| * Calling this function will cause the panel display drivers to be turned on | |
| * and the backlight to be enabled. Content will be visible on screen after | |
| * this call completes. This function cannot fail (as it is called from the | |
| * enable call chain). There will always be a call to drm_panel_disable() | |
| * afterwards. | |
| */ | |
| void drm_panel_enable(struct drm_panel *panel) | |
| { | |
| struct drm_panel_follower *follower; | |
| int ret; | |
| if (!panel) | |
| return; | |
| if (panel->enabled) { | |
| dev_warn(panel->dev, "Skipping enable of already enabled panel\n"); | |
| return; | |
| } | |
| mutex_lock(&panel->follower_lock); | |
| if (panel->funcs && panel->funcs->enable) { | |
| ret = panel->funcs->enable(panel); | |
| if (ret < 0) | |
| goto exit; | |
| } | |
| panel->enabled = true; | |
| ret = backlight_enable(panel->backlight); | |
| if (ret < 0) | |
| DRM_DEV_INFO(panel->dev, "failed to enable backlight: %d\n", | |
| ret); | |
| list_for_each_entry(follower, &panel->followers, list) { | |
| if (!follower->funcs->panel_enabled) | |
| continue; | |
| ret = follower->funcs->panel_enabled(follower); | |
| if (ret < 0) | |
| dev_info(panel->dev, "%ps failed: %d\n", | |
| follower->funcs->panel_enabled, ret); | |
| } | |
| exit: | |
| mutex_unlock(&panel->follower_lock); | |
| } | |
| EXPORT_SYMBOL(drm_panel_enable); | |
| /** | |
| * drm_panel_disable - disable a panel | |
| * @panel: DRM panel | |
| * | |
| * This will typically turn off the panel's backlight or disable the display | |
| * drivers. For smart panels it should still be possible to communicate with | |
| * the integrated circuitry via any command bus after this call. | |
| */ | |
| void drm_panel_disable(struct drm_panel *panel) | |
| { | |
| struct drm_panel_follower *follower; | |
| int ret; | |
| if (!panel) | |
| return; | |
| /* | |
| * If you are seeing the warning below it likely means one of two things: | |
| * - Your panel driver incorrectly calls drm_panel_disable() in its | |
| * shutdown routine. You should delete this. | |
| * - You are using panel-edp or panel-simple and your DRM modeset | |
| * driver's shutdown() callback happened after the panel's shutdown(). | |
| * In this case the warning is harmless though ideally you should | |
| * figure out how to reverse the order of the shutdown() callbacks. | |
| */ | |
| if (!panel->enabled) { | |
| dev_warn(panel->dev, "Skipping disable of already disabled panel\n"); | |
| return; | |
| } | |
| mutex_lock(&panel->follower_lock); | |
| list_for_each_entry(follower, &panel->followers, list) { | |
| if (!follower->funcs->panel_disabling) | |
| continue; | |
| ret = follower->funcs->panel_disabling(follower); | |
| if (ret < 0) | |
| dev_info(panel->dev, "%ps failed: %d\n", | |
| follower->funcs->panel_disabling, ret); | |
| } | |
| ret = backlight_disable(panel->backlight); | |
| if (ret < 0) | |
| DRM_DEV_INFO(panel->dev, "failed to disable backlight: %d\n", | |
| ret); | |
| if (panel->funcs && panel->funcs->disable) { | |
| ret = panel->funcs->disable(panel); | |
| if (ret < 0) | |
| goto exit; | |
| } | |
| panel->enabled = false; | |
| exit: | |
| mutex_unlock(&panel->follower_lock); | |
| } | |
| EXPORT_SYMBOL(drm_panel_disable); | |
| /** | |
| * drm_panel_get_modes - probe the available display modes of a panel | |
| * @panel: DRM panel | |
| * @connector: DRM connector | |
| * | |
| * The modes probed from the panel are automatically added to the connector | |
| * that the panel is attached to. | |
| * | |
| * Return: The number of modes available from the panel on success, or 0 on | |
| * failure (no modes). | |
| */ | |
| int drm_panel_get_modes(struct drm_panel *panel, | |
| struct drm_connector *connector) | |
| { | |
| if (!panel) | |
| return 0; | |
| if (panel->funcs && panel->funcs->get_modes) { | |
| int num; | |
| num = panel->funcs->get_modes(panel, connector); | |
| if (num > 0) | |
| return num; | |
| } | |
| return 0; | |
| } | |
| EXPORT_SYMBOL(drm_panel_get_modes); | |
| /** | |
| * drm_panel_get - Acquire a panel reference | |
| * @panel: DRM panel | |
| * | |
| * This function increments the panel's refcount. | |
| * Returns: | |
| * Pointer to @panel | |
| */ | |
| struct drm_panel *drm_panel_get(struct drm_panel *panel) | |
| { | |
| if (panel) | |
| drm_bridge_get(&panel->bridge); | |
| return panel; | |
| } | |
| EXPORT_SYMBOL(drm_panel_get); | |
| /** | |
| * drm_panel_put - Release a panel reference | |
| * @panel: DRM panel | |
| * | |
| * This function decrements the panel's reference count and frees the | |
| * object if the reference count drops to zero. | |
| */ | |
| void drm_panel_put(struct drm_panel *panel) | |
| { | |
| if (panel) | |
| drm_bridge_put(&panel->bridge); | |
| } | |
| EXPORT_SYMBOL(drm_panel_put); | |
| /** | |
| * drm_panel_put_void - wrapper to drm_panel_put() taking a void pointer | |
| * | |
| * @data: pointer to @struct drm_panel, cast to a void pointer | |
| * | |
| * Wrapper of drm_panel_put() to be used when a function taking a void | |
| * pointer is needed, for example as a devm action. | |
| */ | |
| static void drm_panel_put_void(void *data) | |
| { | |
| struct drm_panel *panel = (struct drm_panel *)data; | |
| drm_panel_put(panel); | |
| } | |
| void *__devm_drm_panel_alloc(struct device *dev, size_t size, size_t offset, | |
| const struct drm_panel_funcs *funcs, | |
| int connector_type) | |
| { | |
| /* | |
| * Struct embedding and offsets: | |
| * | |
| * |--------------- user container struct ------------| | |
| * : |---------- struct drm_panel ------------| | |
| * : : |----- struct drm_bridge ------| | |
| * A B C | |
| * | |
| * B - A = offset (passed as argument) | |
| * C - B = panel_bridge_offset | |
| * C - A = alloc_bridge_offset | |
| */ | |
| const size_t panel_bridge_offset = offsetof(struct drm_panel, bridge); | |
| const size_t alloc_bridge_offset = offset + panel_bridge_offset; | |
| struct drm_panel *panel; | |
| void *container; | |
| int err; | |
| if (!funcs) { | |
| dev_warn(dev, "Missing funcs pointer\n"); | |
| return ERR_PTR(-EINVAL); | |
| } | |
| container = __devm_drm_bridge_alloc(dev, size, alloc_bridge_offset, | |
| &panel_bridge_bridge_funcs); | |
| if (IS_ERR(container)) | |
| return container; | |
| panel = container + offset; | |
| panel->funcs = funcs; | |
| panel->bridge.of_node = dev->of_node; | |
| drm_panel_get(panel); | |
| err = devm_add_action_or_reset(dev, drm_panel_put_void, panel); | |
| if (err) | |
| return ERR_PTR(err); | |
| drm_panel_init(panel, dev, funcs, connector_type); | |
| return container; | |
| } | |
| EXPORT_SYMBOL(__devm_drm_panel_alloc); | |
| #ifdef CONFIG_OF | |
| /** | |
| * of_drm_find_panel - look up and reference a panel by device tree node | |
| * @np: device tree node of the panel | |
| * | |
| * Searches the set of registered panels for one that matches the given device | |
| * tree node. If a matching panel is found, the panel's reference count is | |
| * incremented before returning a pointer to it. The caller must call | |
| * drm_panel_put() when it no longer needs the panel pointer. | |
| * | |
| * Return: A reference-counted pointer to the panel registered for the specified | |
| * device tree node or an ERR_PTR() if no panel matching the device tree node | |
| * can be found. | |
| * | |
| * Possible error codes returned by this function: | |
| * | |
| * - EPROBE_DEFER: the panel device has not been probed yet, and the caller | |
| * should retry later | |
| * - ENODEV: the device is not available (status != "okay" or "ok") | |
| */ | |
| struct drm_panel *of_drm_find_panel(const struct device_node *np) | |
| { | |
| struct drm_panel *panel; | |
| if (!of_device_is_available(np)) | |
| return ERR_PTR(-ENODEV); | |
| mutex_lock(&panel_lock); | |
| list_for_each_entry(panel, &panel_list, list) { | |
| if (panel->dev->of_node == np) { | |
| drm_panel_get(panel); | |
| mutex_unlock(&panel_lock); | |
| return panel; | |
| } | |
| } | |
| mutex_unlock(&panel_lock); | |
| return ERR_PTR(-EPROBE_DEFER); | |
| } | |
| EXPORT_SYMBOL(of_drm_find_panel); | |
| /** | |
| * drm_of_find_panel_or_bridge - return connected panel or bridge device | |
| * @np: device tree node containing encoder output ports | |
| * @port: port in the device tree node | |
| * @endpoint: endpoint in the device tree node | |
| * @panel: pointer to hold returned drm_panel, must not be NULL. On success | |
| * the caller must call drm_panel_put() when done with the panel | |
| * @bridge: pointer to hold returned drm_bridge | |
| * | |
| * Given a DT node's port and endpoint number, find the connected node and | |
| * return either the associated struct drm_panel or drm_bridge device. | |
| * | |
| * This function is deprecated and should not be used in new drivers. Use | |
| * of_drm_get_bridge_by_endpoint() instead when not looking for a panel, or | |
| * devm_drm_of_get_bridge() otherwise. | |
| * | |
| * Returns zero if successful, or one of the standard error codes if it fails. | |
| */ | |
| int drm_of_find_panel_or_bridge(const struct device_node *np, | |
| int port, int endpoint, | |
| struct drm_panel **panel, | |
| struct drm_bridge **bridge) | |
| { | |
| if (WARN_ON(!panel)) | |
| return -EINVAL; | |
| *panel = NULL; | |
| if (bridge) | |
| *bridge = NULL; | |
| /* | |
| * of_graph_get_remote_node() produces a noisy error message if port | |
| * node isn't found and the absence of the port is a legit case here, | |
| * so at first we silently check whether a graph is present in the | |
| * device-tree node. | |
| */ | |
| if (!of_graph_is_present(np)) | |
| return -ENODEV; | |
| struct device_node *remote __free(device_node) = | |
| of_graph_get_remote_node(np, port, endpoint); | |
| if (!remote) | |
| return -ENODEV; | |
| *panel = of_drm_find_panel(remote); | |
| if (!IS_ERR(*panel)) | |
| return 0; | |
| *panel = NULL; | |
| if (bridge) { | |
| /* No panel found yet, check for a bridge next. */ | |
| *bridge = of_drm_find_bridge(remote); | |
| if (*bridge) | |
| return 0; | |
| *bridge = NULL; | |
| } | |
| return -EPROBE_DEFER; | |
| } | |
| EXPORT_SYMBOL_GPL(drm_of_find_panel_or_bridge); | |
| #endif | |
| /* | |
| * Find panel by fwnode, returning a counted reference. | |
| * | |
| * Behaves identically to of_drm_find_panel(). On success the returned | |
| * pointer has been passed through drm_panel_get(); the caller must call | |
| * drm_panel_put() when done with it. | |
| */ | |
| static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode) | |
| { | |
| struct drm_panel *panel; | |
| if (!fwnode_device_is_available(fwnode)) | |
| return ERR_PTR(-ENODEV); | |
| mutex_lock(&panel_lock); | |
| list_for_each_entry(panel, &panel_list, list) { | |
| if (dev_fwnode(panel->dev) == fwnode) { | |
| drm_panel_get(panel); | |
| mutex_unlock(&panel_lock); | |
| return panel; | |
| } | |
| } | |
| mutex_unlock(&panel_lock); | |
| return ERR_PTR(-EPROBE_DEFER); | |
| } | |
| /* Find panel by follower device */ | |
| static struct drm_panel *find_panel_by_dev(struct device *follower_dev) | |
| { | |
| struct fwnode_handle *fwnode; | |
| struct drm_panel *panel; | |
| fwnode = fwnode_find_reference(dev_fwnode(follower_dev), "panel", 0); | |
| if (IS_ERR(fwnode)) | |
| return ERR_PTR(-ENODEV); | |
| panel = find_panel_by_fwnode(fwnode); | |
| fwnode_handle_put(fwnode); | |
| return panel; | |
| } | |
| /** | |
| * drm_is_panel_follower() - Check if the device is a panel follower | |
| * @dev: The 'struct device' to check | |
| * | |
| * This checks to see if a device needs to be power sequenced together with | |
| * a panel using the panel follower API. | |
| * | |
| * The "panel" property of the follower points to the panel to be followed. | |
| * | |
| * Return: true if we should be power sequenced with a panel; false otherwise. | |
| */ | |
| bool drm_is_panel_follower(struct device *dev) | |
| { | |
| /* | |
| * The "panel" property is actually a phandle, but for simplicity we | |
| * don't bother trying to parse it here. We just need to know if the | |
| * property is there. | |
| */ | |
| return device_property_present(dev, "panel"); | |
| } | |
| EXPORT_SYMBOL(drm_is_panel_follower); | |
| /** | |
| * drm_panel_add_follower() - Register something to follow panel state. | |
| * @follower_dev: The 'struct device' for the follower. | |
| * @follower: The panel follower descriptor for the follower. | |
| * | |
| * A panel follower is called right after preparing/enabling the panel and right | |
| * before unpreparing/disabling the panel. It's primary intention is to power on | |
| * an associated touchscreen, though it could be used for any similar devices. | |
| * Multiple devices are allowed the follow the same panel. | |
| * | |
| * If a follower is added to a panel that's already been prepared/enabled, the | |
| * follower's prepared/enabled callback is called right away. | |
| * | |
| * The "panel" property of the follower points to the panel to be followed. | |
| * | |
| * Return: 0 or an error code. Note that -ENODEV means that we detected that | |
| * follower_dev is not actually following a panel. The caller may | |
| * choose to ignore this return value if following a panel is optional. | |
| */ | |
| int drm_panel_add_follower(struct device *follower_dev, | |
| struct drm_panel_follower *follower) | |
| { | |
| struct drm_panel *panel; | |
| int ret; | |
| panel = find_panel_by_dev(follower_dev); | |
| if (IS_ERR(panel)) | |
| return PTR_ERR(panel); | |
| get_device(panel->dev); | |
| follower->panel = panel; | |
| mutex_lock(&panel->follower_lock); | |
| list_add_tail(&follower->list, &panel->followers); | |
| if (panel->prepared && follower->funcs->panel_prepared) { | |
| ret = follower->funcs->panel_prepared(follower); | |
| if (ret < 0) | |
| dev_info(panel->dev, "%ps failed: %d\n", | |
| follower->funcs->panel_prepared, ret); | |
| } | |
| if (panel->enabled && follower->funcs->panel_enabled) { | |
| ret = follower->funcs->panel_enabled(follower); | |
| if (ret < 0) | |
| dev_info(panel->dev, "%ps failed: %d\n", | |
| follower->funcs->panel_enabled, ret); | |
| } | |
| mutex_unlock(&panel->follower_lock); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL(drm_panel_add_follower); | |
| /** | |
| * drm_panel_remove_follower() - Reverse drm_panel_add_follower(). | |
| * @follower: The panel follower descriptor for the follower. | |
| * | |
| * Undo drm_panel_add_follower(). This includes calling the follower's | |
| * unpreparing/disabling function if we're removed from a panel that's currently | |
| * prepared/enabled. | |
| * | |
| * Return: 0 or an error code. | |
| */ | |
| void drm_panel_remove_follower(struct drm_panel_follower *follower) | |
| { | |
| struct drm_panel *panel = follower->panel; | |
| int ret; | |
| mutex_lock(&panel->follower_lock); | |
| if (panel->enabled && follower->funcs->panel_disabling) { | |
| ret = follower->funcs->panel_disabling(follower); | |
| if (ret < 0) | |
| dev_info(panel->dev, "%ps failed: %d\n", | |
| follower->funcs->panel_disabling, ret); | |
| } | |
| if (panel->prepared && follower->funcs->panel_unpreparing) { | |
| ret = follower->funcs->panel_unpreparing(follower); | |
| if (ret < 0) | |
| dev_info(panel->dev, "%ps failed: %d\n", | |
| follower->funcs->panel_unpreparing, ret); | |
| } | |
| list_del_init(&follower->list); | |
| mutex_unlock(&panel->follower_lock); | |
| put_device(panel->dev); | |
| drm_panel_put(panel); | |
| } | |
| EXPORT_SYMBOL(drm_panel_remove_follower); | |
| static void drm_panel_remove_follower_void(void *follower) | |
| { | |
| drm_panel_remove_follower(follower); | |
| } | |
| /** | |
| * devm_drm_panel_add_follower() - devm version of drm_panel_add_follower() | |
| * @follower_dev: The 'struct device' for the follower. | |
| * @follower: The panel follower descriptor for the follower. | |
| * | |
| * Handles calling drm_panel_remove_follower() using devm on the follower_dev. | |
| * | |
| * Return: 0 or an error code. | |
| */ | |
| int devm_drm_panel_add_follower(struct device *follower_dev, | |
| struct drm_panel_follower *follower) | |
| { | |
| int ret; | |
| ret = drm_panel_add_follower(follower_dev, follower); | |
| if (ret) | |
| return ret; | |
| return devm_add_action_or_reset(follower_dev, | |
| drm_panel_remove_follower_void, follower); | |
| } | |
| EXPORT_SYMBOL(devm_drm_panel_add_follower); | |
| #if IS_REACHABLE(CONFIG_BACKLIGHT_CLASS_DEVICE) | |
| /** | |
| * drm_panel_of_backlight - use backlight device node for backlight | |
| * @panel: DRM panel | |
| * | |
| * Use this function to enable backlight handling if your panel | |
| * uses device tree and has a backlight phandle. | |
| * | |
| * When the panel is enabled backlight will be enabled after a | |
| * successful call to &drm_panel_funcs.enable() | |
| * | |
| * When the panel is disabled backlight will be disabled before the | |
| * call to &drm_panel_funcs.disable(). | |
| * | |
| * A typical implementation for a panel driver supporting device tree | |
| * will call this function at probe time. Backlight will then be handled | |
| * transparently without requiring any intervention from the driver. | |
| * | |
| * Return: 0 on success or a negative error code on failure. | |
| */ | |
| int drm_panel_of_backlight(struct drm_panel *panel) | |
| { | |
| struct backlight_device *backlight; | |
| if (!panel || !panel->dev) | |
| return -EINVAL; | |
| backlight = devm_of_find_backlight(panel->dev); | |
| if (IS_ERR(backlight)) | |
| return PTR_ERR(backlight); | |
| panel->backlight = backlight; | |
| return 0; | |
| } | |
| EXPORT_SYMBOL(drm_panel_of_backlight); | |
| #endif | |
| MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>"); | |
| MODULE_DESCRIPTION("DRM panel infrastructure"); | |
| MODULE_LICENSE("GPL and additional rights"); |