| 1 | |
| 2 | * Copyright (C) 2013, NVIDIA Corporation. All rights reserved.
|
| 3 | *
|
| 4 | * Permission is hereby granted, free of charge, to any person obtaining a
|
| 5 | * copy of this software and associated documentation files (the "Software"),
|
| 6 | * to deal in the Software without restriction, including without limitation
|
| 7 | * the rights to use, copy, modify, merge, publish, distribute, sub license,
|
| 8 | * and/or sell copies of the Software, and to permit persons to whom the
|
| 9 | * Software is furnished to do so, subject to the following conditions:
|
| 10 | *
|
| 11 | * The above copyright notice and this permission notice (including the
|
| 12 | * next paragraph) shall be included in all copies or substantial portions
|
| 13 | * of the Software.
|
| 14 | *
|
| 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
|
| 18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
| 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
| 21 | * DEALINGS IN THE SOFTWARE.
|
| 22 |
|
| 23 |
|
| 24 | #include <linux/backlight.h>
|
| 25 | #include <linux/err.h>
|
| 26 | #include <linux/export.h>
|
| 27 | #include <linux/module.h>
|
| 28 | #include <linux/of.h>
|
| 29 |
|
| 30 | #include <drm/drm_crtc.h>
|
| 31 | #include <drm/drm_of.h>
|
| 32 | #include <drm/drm_panel.h>
|
| 33 | #include <drm/drm_print.h>
|
| 34 |
|
| 35 | static DEFINE_MUTEX(panel_lock);
|
| 36 | static LIST_HEAD(panel_list);
|
| 37 |
|
| 38 | |
| 39 | * DOC: drm panel
|
| 40 | *
|
| 41 | * The DRM panel helpers allow drivers to register panel objects with a
|
| 42 | * central registry and provide functions to retrieve those panels in display
|
| 43 | * drivers.
|
| 44 | *
|
| 45 | * For easy integration into drivers using the &drm_bridge infrastructure please
|
| 46 | * take look at drm_panel_bridge_add() and devm_drm_panel_bridge_add().
|
| 47 |
|
| 48 |
|
| 49 | |
| 50 | * drm_panel_init - initialize a panel
|
| 51 | * @panel: DRM panel
|
| 52 | * @dev: parent device of the panel
|
| 53 | * @funcs: panel operations
|
| 54 | * @connector_type: the connector type (DRM_MODE_CONNECTOR_*) corresponding to
|
| 55 | * the panel interface (must NOT be DRM_MODE_CONNECTOR_Unknown)
|
| 56 | *
|
| 57 | * Initialize the panel structure for subsequent registration with
|
| 58 | * drm_panel_add().
|
| 59 |
|
| 60 | static void drm_panel_init(struct drm_panel *panel, struct device *dev,
|
| 61 | const struct drm_panel_funcs *funcs,
|
| 62 | int connector_type)
|
| 63 | {
|
| 64 | if (connector_type == DRM_MODE_CONNECTOR_Unknown)
|
| 65 | DRM_WARN("%s: %s: a valid connector type is required!\n", __func__, dev_name(dev));
|
| 66 |
|
| 67 | INIT_LIST_HEAD(&panel->list);
|
| 68 | INIT_LIST_HEAD(&panel->followers);
|
| 69 | mutex_init(&panel->follower_lock);
|
| 70 | panel->dev = dev;
|
| 71 | panel->funcs = funcs;
|
| 72 | panel->connector_type = connector_type;
|
| 73 | }
|
| 74 |
|
| 75 | |
| 76 | * drm_panel_add - add a panel to the global registry
|
| 77 | * @panel: panel to add
|
| 78 | *
|
| 79 | * Add a panel to the global registry so that it can be looked
|
| 80 | * up by display drivers. The panel to be added must have been
|
| 81 | * allocated by devm_drm_panel_alloc().
|
| 82 |
|
| 83 | void drm_panel_add(struct drm_panel *panel)
|
| 84 | {
|
| 85 | drm_panel_get(panel);
|
| 86 | mutex_lock(&panel_lock);
|
| 87 | list_add_tail(&panel->list, &panel_list);
|
| 88 | mutex_unlock(&panel_lock);
|
| 89 | }
|
| 90 | EXPORT_SYMBOL(drm_panel_add);
|
| 91 |
|
| 92 | |
| 93 | * drm_panel_remove - remove a panel from the global registry
|
| 94 | * @panel: DRM panel
|
| 95 | *
|
| 96 | * Removes a panel from the global registry.
|
| 97 |
|
| 98 | void drm_panel_remove(struct drm_panel *panel)
|
| 99 | {
|
| 100 | mutex_lock(&panel_lock);
|
| 101 | list_del_init(&panel->list);
|
| 102 | mutex_unlock(&panel_lock);
|
| 103 | drm_panel_put(panel);
|
| 104 | }
|
| 105 | EXPORT_SYMBOL(drm_panel_remove);
|
| 106 |
|
| 107 | static void drm_panel_add_release(void *data)
|
| 108 | {
|
| 109 | drm_panel_remove(data);
|
| 110 | }
|
| 111 |
|
| 112 | |
| 113 | * devm_drm_panel_add - add a panel to the global registry using devres
|
| 114 | * @dev: device to which the panel is attached
|
| 115 | * @panel: panel to add
|
| 116 | *
|
| 117 | * Add a panel to the global registry so that it can be looked
|
| 118 | * up by display drivers. The panel to be added must have been
|
| 119 | * allocated by devm_drm_panel_alloc(). Unlike drm_panel_add() with this
|
| 120 | * function there is no need to call drm_panel_remove(), it will be called
|
| 121 | * automatically.
|
| 122 |
|
| 123 | int devm_drm_panel_add(struct device *dev, struct drm_panel *panel)
|
| 124 | {
|
| 125 | drm_panel_add(panel);
|
| 126 |
|
| 127 | return devm_add_action_or_reset(dev, drm_panel_add_release, panel);
|
| 128 | }
|
| 129 | EXPORT_SYMBOL(devm_drm_panel_add);
|
| 130 |
|
| 131 | |
| 132 | * drm_panel_prepare - power on a panel
|
| 133 | * @panel: DRM panel
|
| 134 | *
|
| 135 | * Calling this function will enable power and deassert any reset signals to
|
| 136 | * the panel. After this has completed it is possible to communicate with any
|
| 137 | * integrated circuitry via a command bus. This function cannot fail (as it is
|
| 138 | * called from the pre_enable call chain). There will always be a call to
|
| 139 | * drm_panel_disable() afterwards.
|
| 140 |
|
| 141 | void drm_panel_prepare(struct drm_panel *panel)
|
| 142 | {
|
| 143 | struct drm_panel_follower *follower;
|
| 144 | int ret;
|
| 145 |
|
| 146 | if (!panel)
|
| 147 | return;
|
| 148 |
|
| 149 | if (panel->prepared) {
|
| 150 | dev_warn(panel->dev, "Skipping prepare of already prepared panel\n");
|
| 151 | return;
|
| 152 | }
|
| 153 |
|
| 154 | mutex_lock(&panel->follower_lock);
|
| 155 |
|
| 156 | if (panel->funcs && panel->funcs->prepare) {
|
| 157 | ret = panel->funcs->prepare(panel);
|
| 158 | if (ret < 0)
|
| 159 | goto exit;
|
| 160 | }
|
| 161 | panel->prepared = true;
|
| 162 |
|
| 163 | list_for_each_entry(follower, &panel->followers, list) {
|
| 164 | if (!follower->funcs->panel_prepared)
|
| 165 | continue;
|
| 166 |
|
| 167 | ret = follower->funcs->panel_prepared(follower);
|
| 168 | if (ret < 0)
|
| 169 | dev_info(panel->dev, "%ps failed: %d\n",
|
| 170 | follower->funcs->panel_prepared, ret);
|
| 171 | }
|
| 172 |
|
| 173 | exit:
|
| 174 | mutex_unlock(&panel->follower_lock);
|
| 175 | }
|
| 176 | EXPORT_SYMBOL(drm_panel_prepare);
|
| 177 |
|
| 178 | |
| 179 | * drm_panel_unprepare - power off a panel
|
| 180 | * @panel: DRM panel
|
| 181 | *
|
| 182 | * Calling this function will completely power off a panel (assert the panel's
|
| 183 | * reset, turn off power supplies, ...). After this function has completed, it
|
| 184 | * is usually no longer possible to communicate with the panel until another
|
| 185 | * call to drm_panel_prepare().
|
| 186 |
|
| 187 | void drm_panel_unprepare(struct drm_panel *panel)
|
| 188 | {
|
| 189 | struct drm_panel_follower *follower;
|
| 190 | int ret;
|
| 191 |
|
| 192 | if (!panel)
|
| 193 | return;
|
| 194 |
|
| 195 | |
| 196 | * If you are seeing the warning below it likely means one of two things:
|
| 197 | * - Your panel driver incorrectly calls drm_panel_unprepare() in its
|
| 198 | * shutdown routine. You should delete this.
|
| 199 | * - You are using panel-edp or panel-simple and your DRM modeset
|
| 200 | * driver's shutdown() callback happened after the panel's shutdown().
|
| 201 | * In this case the warning is harmless though ideally you should
|
| 202 | * figure out how to reverse the order of the shutdown() callbacks.
|
| 203 |
|
| 204 | if (!panel->prepared) {
|
| 205 | dev_warn(panel->dev, "Skipping unprepare of already unprepared panel\n");
|
| 206 | return;
|
| 207 | }
|
| 208 |
|
| 209 | mutex_lock(&panel->follower_lock);
|
| 210 |
|
| 211 | list_for_each_entry(follower, &panel->followers, list) {
|
| 212 | if (!follower->funcs->panel_unpreparing)
|
| 213 | continue;
|
| 214 |
|
| 215 | ret = follower->funcs->panel_unpreparing(follower);
|
| 216 | if (ret < 0)
|
| 217 | dev_info(panel->dev, "%ps failed: %d\n",
|
| 218 | follower->funcs->panel_unpreparing, ret);
|
| 219 | }
|
| 220 |
|
| 221 | if (panel->funcs && panel->funcs->unprepare) {
|
| 222 | ret = panel->funcs->unprepare(panel);
|
| 223 | if (ret < 0)
|
| 224 | goto exit;
|
| 225 | }
|
| 226 | panel->prepared = false;
|
| 227 |
|
| 228 | exit:
|
| 229 | mutex_unlock(&panel->follower_lock);
|
| 230 | }
|
| 231 | EXPORT_SYMBOL(drm_panel_unprepare);
|
| 232 |
|
| 233 | |
| 234 | * drm_panel_enable - enable a panel
|
| 235 | * @panel: DRM panel
|
| 236 | *
|
| 237 | * Calling this function will cause the panel display drivers to be turned on
|
| 238 | * and the backlight to be enabled. Content will be visible on screen after
|
| 239 | * this call completes. This function cannot fail (as it is called from the
|
| 240 | * enable call chain). There will always be a call to drm_panel_disable()
|
| 241 | * afterwards.
|
| 242 |
|
| 243 | void drm_panel_enable(struct drm_panel *panel)
|
| 244 | {
|
| 245 | struct drm_panel_follower *follower;
|
| 246 | int ret;
|
| 247 |
|
| 248 | if (!panel)
|
| 249 | return;
|
| 250 |
|
| 251 | if (panel->enabled) {
|
| 252 | dev_warn(panel->dev, "Skipping enable of already enabled panel\n");
|
| 253 | return;
|
| 254 | }
|
| 255 |
|
| 256 | mutex_lock(&panel->follower_lock);
|
| 257 |
|
| 258 | if (panel->funcs && panel->funcs->enable) {
|
| 259 | ret = panel->funcs->enable(panel);
|
| 260 | if (ret < 0)
|
| 261 | goto exit;
|
| 262 | }
|
| 263 | panel->enabled = true;
|
| 264 |
|
| 265 | ret = backlight_enable(panel->backlight);
|
| 266 | if (ret < 0)
|
| 267 | DRM_DEV_INFO(panel->dev, "failed to enable backlight: %d\n",
|
| 268 | ret);
|
| 269 |
|
| 270 | list_for_each_entry(follower, &panel->followers, list) {
|
| 271 | if (!follower->funcs->panel_enabled)
|
| 272 | continue;
|
| 273 |
|
| 274 | ret = follower->funcs->panel_enabled(follower);
|
| 275 | if (ret < 0)
|
| 276 | dev_info(panel->dev, "%ps failed: %d\n",
|
| 277 | follower->funcs->panel_enabled, ret);
|
| 278 | }
|
| 279 |
|
| 280 | exit:
|
| 281 | mutex_unlock(&panel->follower_lock);
|
| 282 | }
|
| 283 | EXPORT_SYMBOL(drm_panel_enable);
|
| 284 |
|
| 285 | |
| 286 | * drm_panel_disable - disable a panel
|
| 287 | * @panel: DRM panel
|
| 288 | *
|
| 289 | * This will typically turn off the panel's backlight or disable the display
|
| 290 | * drivers. For smart panels it should still be possible to communicate with
|
| 291 | * the integrated circuitry via any command bus after this call.
|
| 292 |
|
| 293 | void drm_panel_disable(struct drm_panel *panel)
|
| 294 | {
|
| 295 | struct drm_panel_follower *follower;
|
| 296 | int ret;
|
| 297 |
|
| 298 | if (!panel)
|
| 299 | return;
|
| 300 |
|
| 301 | |
| 302 | * If you are seeing the warning below it likely means one of two things:
|
| 303 | * - Your panel driver incorrectly calls drm_panel_disable() in its
|
| 304 | * shutdown routine. You should delete this.
|
| 305 | * - You are using panel-edp or panel-simple and your DRM modeset
|
| 306 | * driver's shutdown() callback happened after the panel's shutdown().
|
| 307 | * In this case the warning is harmless though ideally you should
|
| 308 | * figure out how to reverse the order of the shutdown() callbacks.
|
| 309 |
|
| 310 | if (!panel->enabled) {
|
| 311 | dev_warn(panel->dev, "Skipping disable of already disabled panel\n");
|
| 312 | return;
|
| 313 | }
|
| 314 |
|
| 315 | mutex_lock(&panel->follower_lock);
|
| 316 |
|
| 317 | list_for_each_entry(follower, &panel->followers, list) {
|
| 318 | if (!follower->funcs->panel_disabling)
|
| 319 | continue;
|
| 320 |
|
| 321 | ret = follower->funcs->panel_disabling(follower);
|
| 322 | if (ret < 0)
|
| 323 | dev_info(panel->dev, "%ps failed: %d\n",
|
| 324 | follower->funcs->panel_disabling, ret);
|
| 325 | }
|
| 326 |
|
| 327 | ret = backlight_disable(panel->backlight);
|
| 328 | if (ret < 0)
|
| 329 | DRM_DEV_INFO(panel->dev, "failed to disable backlight: %d\n",
|
| 330 | ret);
|
| 331 |
|
| 332 | if (panel->funcs && panel->funcs->disable) {
|
| 333 | ret = panel->funcs->disable(panel);
|
| 334 | if (ret < 0)
|
| 335 | goto exit;
|
| 336 | }
|
| 337 | panel->enabled = false;
|
| 338 |
|
| 339 | exit:
|
| 340 | mutex_unlock(&panel->follower_lock);
|
| 341 | }
|
| 342 | EXPORT_SYMBOL(drm_panel_disable);
|
| 343 |
|
| 344 | |
| 345 | * drm_panel_get_modes - probe the available display modes of a panel
|
| 346 | * @panel: DRM panel
|
| 347 | * @connector: DRM connector
|
| 348 | *
|
| 349 | * The modes probed from the panel are automatically added to the connector
|
| 350 | * that the panel is attached to.
|
| 351 | *
|
| 352 | * Return: The number of modes available from the panel on success, or 0 on
|
| 353 | * failure (no modes).
|
| 354 |
|
| 355 | int drm_panel_get_modes(struct drm_panel *panel,
|
| 356 | struct drm_connector *connector)
|
| 357 | {
|
| 358 | if (!panel)
|
| 359 | return 0;
|
| 360 |
|
| 361 | if (panel->funcs && panel->funcs->get_modes) {
|
| 362 | int num;
|
| 363 |
|
| 364 | num = panel->funcs->get_modes(panel, connector);
|
| 365 | if (num > 0)
|
| 366 | return num;
|
| 367 | }
|
| 368 |
|
| 369 | return 0;
|
| 370 | }
|
| 371 | EXPORT_SYMBOL(drm_panel_get_modes);
|
| 372 |
|
| 373 | static void __drm_panel_free(struct kref *kref)
|
| 374 | {
|
| 375 | struct drm_panel *panel = container_of(kref, struct drm_panel, refcount);
|
| 376 |
|
| 377 | kfree(panel->container);
|
| 378 | }
|
| 379 |
|
| 380 | |
| 381 | * drm_panel_get - Acquire a panel reference
|
| 382 | * @panel: DRM panel
|
| 383 | *
|
| 384 | * This function increments the panel's refcount.
|
| 385 | * Returns:
|
| 386 | * Pointer to @panel
|
| 387 |
|
| 388 | struct drm_panel *drm_panel_get(struct drm_panel *panel)
|
| 389 | {
|
| 390 | if (!panel)
|
| 391 | return panel;
|
| 392 |
|
| 393 | kref_get(&panel->refcount);
|
| 394 |
|
| 395 | return panel;
|
| 396 | }
|
| 397 | EXPORT_SYMBOL(drm_panel_get);
|
| 398 |
|
| 399 | |
| 400 | * drm_panel_put - Release a panel reference
|
| 401 | * @panel: DRM panel
|
| 402 | *
|
| 403 | * This function decrements the panel's reference count and frees the
|
| 404 | * object if the reference count drops to zero.
|
| 405 |
|
| 406 | void drm_panel_put(struct drm_panel *panel)
|
| 407 | {
|
| 408 | if (panel)
|
| 409 | kref_put(&panel->refcount, __drm_panel_free);
|
| 410 | }
|
| 411 | EXPORT_SYMBOL(drm_panel_put);
|
| 412 |
|
| 413 | |
| 414 | * drm_panel_put_void - wrapper to drm_panel_put() taking a void pointer
|
| 415 | *
|
| 416 | * @data: pointer to @struct drm_panel, cast to a void pointer
|
| 417 | *
|
| 418 | * Wrapper of drm_panel_put() to be used when a function taking a void
|
| 419 | * pointer is needed, for example as a devm action.
|
| 420 |
|
| 421 | static void drm_panel_put_void(void *data)
|
| 422 | {
|
| 423 | struct drm_panel *panel = (struct drm_panel *)data;
|
| 424 |
|
| 425 | drm_panel_put(panel);
|
| 426 | }
|
| 427 |
|
| 428 | void *__devm_drm_panel_alloc(struct device *dev, size_t size, size_t offset,
|
| 429 | const struct drm_panel_funcs *funcs,
|
| 430 | int connector_type)
|
| 431 | {
|
| 432 | void *container;
|
| 433 | struct drm_panel *panel;
|
| 434 | int err;
|
| 435 |
|
| 436 | if (!funcs) {
|
| 437 | dev_warn(dev, "Missing funcs pointer\n");
|
| 438 | return ERR_PTR(-EINVAL);
|
| 439 | }
|
| 440 |
|
| 441 | container = kzalloc(size, GFP_KERNEL);
|
| 442 | if (!container)
|
| 443 | return ERR_PTR(-ENOMEM);
|
| 444 |
|
| 445 | panel = container + offset;
|
| 446 | panel->container = container;
|
| 447 | panel->funcs = funcs;
|
| 448 | kref_init(&panel->refcount);
|
| 449 |
|
| 450 | err = devm_add_action_or_reset(dev, drm_panel_put_void, panel);
|
| 451 | if (err)
|
| 452 | return ERR_PTR(err);
|
| 453 |
|
| 454 | drm_panel_init(panel, dev, funcs, connector_type);
|
| 455 |
|
| 456 | return container;
|
| 457 | }
|
| 458 | EXPORT_SYMBOL(__devm_drm_panel_alloc);
|
| 459 |
|
| 460 | #ifdef CONFIG_OF
|
| 461 | |
| 462 | * of_drm_find_panel - look up and reference a panel by device tree node
|
| 463 | * @np: device tree node of the panel
|
| 464 | *
|
| 465 | * Searches the set of registered panels for one that matches the given device
|
| 466 | * tree node. If a matching panel is found, the panel's reference count is
|
| 467 | * incremented before returning a pointer to it. The caller must call
|
| 468 | * drm_panel_put() when it no longer needs the panel pointer.
|
| 469 | *
|
| 470 | * Return: A reference-counted pointer to the panel registered for the specified
|
| 471 | * device tree node or an ERR_PTR() if no panel matching the device tree node
|
| 472 | * can be found.
|
| 473 | *
|
| 474 | * Possible error codes returned by this function:
|
| 475 | *
|
| 476 | * - EPROBE_DEFER: the panel device has not been probed yet, and the caller
|
| 477 | * should retry later
|
| 478 | * - ENODEV: the device is not available (status != "okay" or "ok")
|
| 479 |
|
| 480 | struct drm_panel *of_drm_find_panel(const struct device_node *np)
|
| 481 | {
|
| 482 | struct drm_panel *panel;
|
| 483 |
|
| 484 | if (!of_device_is_available(np))
|
| 485 | return ERR_PTR(-ENODEV);
|
| 486 |
|
| 487 | mutex_lock(&panel_lock);
|
| 488 |
|
| 489 | list_for_each_entry(panel, &panel_list, list) {
|
| 490 | if (panel->dev->of_node == np) {
|
| 491 | drm_panel_get(panel);
|
| 492 | mutex_unlock(&panel_lock);
|
| 493 | return panel;
|
| 494 | }
|
| 495 | }
|
| 496 |
|
| 497 | mutex_unlock(&panel_lock);
|
| 498 | return ERR_PTR(-EPROBE_DEFER);
|
| 499 | }
|
| 500 | EXPORT_SYMBOL(of_drm_find_panel);
|
| 501 | #endif
|
| 502 |
|
| 503 | |
| 504 | * Find panel by fwnode, returning a counted reference.
|
| 505 | *
|
| 506 | * Behaves identically to of_drm_find_panel(). On success the returned
|
| 507 | * pointer has been passed through drm_panel_get(); the caller must call
|
| 508 | * drm_panel_put() when done with it.
|
| 509 |
|
| 510 | static struct drm_panel *find_panel_by_fwnode(const struct fwnode_handle *fwnode)
|
| 511 | {
|
| 512 | struct drm_panel *panel;
|
| 513 |
|
| 514 | if (!fwnode_device_is_available(fwnode))
|
| 515 | return ERR_PTR(-ENODEV);
|
| 516 |
|
| 517 | mutex_lock(&panel_lock);
|
| 518 |
|
| 519 | list_for_each_entry(panel, &panel_list, list) {
|
| 520 | if (dev_fwnode(panel->dev) == fwnode) {
|
| 521 | drm_panel_get(panel);
|
| 522 | mutex_unlock(&panel_lock);
|
| 523 | return panel;
|
| 524 | }
|
| 525 | }
|
| 526 |
|
| 527 | mutex_unlock(&panel_lock);
|
| 528 |
|
| 529 | return ERR_PTR(-EPROBE_DEFER);
|
| 530 | }
|
| 531 |
|
| 532 |
|
| 533 | static struct drm_panel *find_panel_by_dev(struct device *follower_dev)
|
| 534 | {
|
| 535 | struct fwnode_handle *fwnode;
|
| 536 | struct drm_panel *panel;
|
| 537 |
|
| 538 | fwnode = fwnode_find_reference(dev_fwnode(follower_dev), "panel", 0);
|
| 539 | if (IS_ERR(fwnode))
|
| 540 | return ERR_PTR(-ENODEV);
|
| 541 |
|
| 542 | panel = find_panel_by_fwnode(fwnode);
|
| 543 | fwnode_handle_put(fwnode);
|
| 544 |
|
| 545 | return panel;
|
| 546 | }
|
| 547 |
|
| 548 | |
| 549 | * drm_is_panel_follower() - Check if the device is a panel follower
|
| 550 | * @dev: The 'struct device' to check
|
| 551 | *
|
| 552 | * This checks to see if a device needs to be power sequenced together with
|
| 553 | * a panel using the panel follower API.
|
| 554 | *
|
| 555 | * The "panel" property of the follower points to the panel to be followed.
|
| 556 | *
|
| 557 | * Return: true if we should be power sequenced with a panel; false otherwise.
|
| 558 |
|
| 559 | bool drm_is_panel_follower(struct device *dev)
|
| 560 | {
|
| 561 | |
| 562 | * The "panel" property is actually a phandle, but for simplicity we
|
| 563 | * don't bother trying to parse it here. We just need to know if the
|
| 564 | * property is there.
|
| 565 |
|
| 566 | return device_property_present(dev, "panel");
|
| 567 | }
|
| 568 | EXPORT_SYMBOL(drm_is_panel_follower);
|
| 569 |
|
| 570 | |
| 571 | * drm_panel_add_follower() - Register something to follow panel state.
|
| 572 | * @follower_dev: The 'struct device' for the follower.
|
| 573 | * @follower: The panel follower descriptor for the follower.
|
| 574 | *
|
| 575 | * A panel follower is called right after preparing/enabling the panel and right
|
| 576 | * before unpreparing/disabling the panel. It's primary intention is to power on
|
| 577 | * an associated touchscreen, though it could be used for any similar devices.
|
| 578 | * Multiple devices are allowed the follow the same panel.
|
| 579 | *
|
| 580 | * If a follower is added to a panel that's already been prepared/enabled, the
|
| 581 | * follower's prepared/enabled callback is called right away.
|
| 582 | *
|
| 583 | * The "panel" property of the follower points to the panel to be followed.
|
| 584 | *
|
| 585 | * Return: 0 or an error code. Note that -ENODEV means that we detected that
|
| 586 | * follower_dev is not actually following a panel. The caller may
|
| 587 | * choose to ignore this return value if following a panel is optional.
|
| 588 |
|
| 589 | int drm_panel_add_follower(struct device *follower_dev,
|
| 590 | struct drm_panel_follower *follower)
|
| 591 | {
|
| 592 | struct drm_panel *panel;
|
| 593 | int ret;
|
| 594 |
|
| 595 | panel = find_panel_by_dev(follower_dev);
|
| 596 | if (IS_ERR(panel))
|
| 597 | return PTR_ERR(panel);
|
| 598 |
|
| 599 | get_device(panel->dev);
|
| 600 | follower->panel = panel;
|
| 601 |
|
| 602 | mutex_lock(&panel->follower_lock);
|
| 603 |
|
| 604 | list_add_tail(&follower->list, &panel->followers);
|
| 605 | if (panel->prepared && follower->funcs->panel_prepared) {
|
| 606 | ret = follower->funcs->panel_prepared(follower);
|
| 607 | if (ret < 0)
|
| 608 | dev_info(panel->dev, "%ps failed: %d\n",
|
| 609 | follower->funcs->panel_prepared, ret);
|
| 610 | }
|
| 611 | if (panel->enabled && follower->funcs->panel_enabled) {
|
| 612 | ret = follower->funcs->panel_enabled(follower);
|
| 613 | if (ret < 0)
|
| 614 | dev_info(panel->dev, "%ps failed: %d\n",
|
| 615 | follower->funcs->panel_enabled, ret);
|
| 616 | }
|
| 617 |
|
| 618 | mutex_unlock(&panel->follower_lock);
|
| 619 |
|
| 620 | return 0;
|
| 621 | }
|
| 622 | EXPORT_SYMBOL(drm_panel_add_follower);
|
| 623 |
|
| 624 | |
| 625 | * drm_panel_remove_follower() - Reverse drm_panel_add_follower().
|
| 626 | * @follower: The panel follower descriptor for the follower.
|
| 627 | *
|
| 628 | * Undo drm_panel_add_follower(). This includes calling the follower's
|
| 629 | * unpreparing/disabling function if we're removed from a panel that's currently
|
| 630 | * prepared/enabled.
|
| 631 | *
|
| 632 | * Return: 0 or an error code.
|
| 633 |
|
| 634 | void drm_panel_remove_follower(struct drm_panel_follower *follower)
|
| 635 | {
|
| 636 | struct drm_panel *panel = follower->panel;
|
| 637 | int ret;
|
| 638 |
|
| 639 | mutex_lock(&panel->follower_lock);
|
| 640 |
|
| 641 | if (panel->enabled && follower->funcs->panel_disabling) {
|
| 642 | ret = follower->funcs->panel_disabling(follower);
|
| 643 | if (ret < 0)
|
| 644 | dev_info(panel->dev, "%ps failed: %d\n",
|
| 645 | follower->funcs->panel_disabling, ret);
|
| 646 | }
|
| 647 | if (panel->prepared && follower->funcs->panel_unpreparing) {
|
| 648 | ret = follower->funcs->panel_unpreparing(follower);
|
| 649 | if (ret < 0)
|
| 650 | dev_info(panel->dev, "%ps failed: %d\n",
|
| 651 | follower->funcs->panel_unpreparing, ret);
|
| 652 | }
|
| 653 | list_del_init(&follower->list);
|
| 654 |
|
| 655 | mutex_unlock(&panel->follower_lock);
|
| 656 |
|
| 657 | put_device(panel->dev);
|
| 658 | drm_panel_put(panel);
|
| 659 | }
|
| 660 | EXPORT_SYMBOL(drm_panel_remove_follower);
|
| 661 |
|
| 662 | static void drm_panel_remove_follower_void(void *follower)
|
| 663 | {
|
| 664 | drm_panel_remove_follower(follower);
|
| 665 | }
|
| 666 |
|
| 667 | |
| 668 | * devm_drm_panel_add_follower() - devm version of drm_panel_add_follower()
|
| 669 | * @follower_dev: The 'struct device' for the follower.
|
| 670 | * @follower: The panel follower descriptor for the follower.
|
| 671 | *
|
| 672 | * Handles calling drm_panel_remove_follower() using devm on the follower_dev.
|
| 673 | *
|
| 674 | * Return: 0 or an error code.
|
| 675 |
|
| 676 | int devm_drm_panel_add_follower(struct device *follower_dev,
|
| 677 | struct drm_panel_follower *follower)
|
| 678 | {
|
| 679 | int ret;
|
| 680 |
|
| 681 | ret = drm_panel_add_follower(follower_dev, follower);
|
| 682 | if (ret)
|
| 683 | return ret;
|
| 684 |
|
| 685 | return devm_add_action_or_reset(follower_dev,
|
| 686 | drm_panel_remove_follower_void, follower);
|
| 687 | }
|
| 688 | EXPORT_SYMBOL(devm_drm_panel_add_follower);
|
| 689 |
|
| 690 | #if IS_REACHABLE(CONFIG_BACKLIGHT_CLASS_DEVICE)
|
| 691 | |
| 692 | * drm_panel_of_backlight - use backlight device node for backlight
|
| 693 | * @panel: DRM panel
|
| 694 | *
|
| 695 | * Use this function to enable backlight handling if your panel
|
| 696 | * uses device tree and has a backlight phandle.
|
| 697 | *
|
| 698 | * When the panel is enabled backlight will be enabled after a
|
| 699 | * successful call to &drm_panel_funcs.enable()
|
| 700 | *
|
| 701 | * When the panel is disabled backlight will be disabled before the
|
| 702 | * call to &drm_panel_funcs.disable().
|
| 703 | *
|
| 704 | * A typical implementation for a panel driver supporting device tree
|
| 705 | * will call this function at probe time. Backlight will then be handled
|
| 706 | * transparently without requiring any intervention from the driver.
|
| 707 | *
|
| 708 | * Return: 0 on success or a negative error code on failure.
|
| 709 |
|
| 710 | int drm_panel_of_backlight(struct drm_panel *panel)
|
| 711 | {
|
| 712 | struct backlight_device *backlight;
|
| 713 |
|
| 714 | if (!panel || !panel->dev)
|
| 715 | return -EINVAL;
|
| 716 |
|
| 717 | backlight = devm_of_find_backlight(panel->dev);
|
| 718 |
|
| 719 | if (IS_ERR(backlight))
|
| 720 | return PTR_ERR(backlight);
|
| 721 |
|
| 722 | panel->backlight = backlight;
|
| 723 | return 0;
|
| 724 | }
|
| 725 | EXPORT_SYMBOL(drm_panel_of_backlight);
|
| 726 | #endif
|
| 727 |
|
| 728 | MODULE_AUTHOR("Thierry Reding <[email protected]>");
|
| 729 | MODULE_DESCRIPTION("DRM panel infrastructure");
|
| 730 | MODULE_LICENSE("GPL and additional rights");
|