#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/of.h>
#include <drm/drm_crtc.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
static DEFINE_MUTEX(panel_lock);
static LIST_HEAD(panel_list);
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;
}
EXPORT_SYMBOL(drm_panel_init);
void drm_panel_add(struct drm_panel *panel)
{
mutex_lock(&panel_lock);
list_add_tail(&panel->list, &panel_list);
mutex_unlock(&panel_lock);
}
EXPORT_SYMBOL(drm_panel_add);
void drm_panel_remove(struct drm_panel *panel)
{
mutex_lock(&panel_lock);
list_del_init(&panel->list);
mutex_unlock(&panel_lock);
}
EXPORT_SYMBOL(drm_panel_remove);
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);
void drm_panel_unprepare(struct drm_panel *panel)
{
struct drm_panel_follower *follower;
int ret;
if (!panel)
return;
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);
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);
void drm_panel_disable(struct drm_panel *panel)
{
struct drm_panel_follower *follower;
int ret;
if (!panel)
return;
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);
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);
static void __drm_panel_free(struct kref *kref)
{
struct drm_panel *panel = container_of(kref, struct drm_panel, refcount);
kfree(panel->container);
}
struct drm_panel *drm_panel_get(struct drm_panel *panel)
{
if (!panel)
return panel;
kref_get(&panel->refcount);
return panel;
}
EXPORT_SYMBOL(drm_panel_get);
void drm_panel_put(struct drm_panel *panel)
{
if (panel)
kref_put(&panel->refcount, __drm_panel_free);
}
EXPORT_SYMBOL(drm_panel_put);
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)
{
void *container;
struct drm_panel *panel;
int err;
if (!funcs) {
dev_warn(dev, "Missing funcs pointer\n");
return ERR_PTR(-EINVAL);
}
container = kzalloc(size, GFP_KERNEL);
if (!container)
return ERR_PTR(-ENOMEM);
panel = container + offset;
panel->container = container;
panel->funcs = funcs;
kref_init(&panel->refcount);
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
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) {
mutex_unlock(&panel_lock);
return panel;
}
}
mutex_unlock(&panel_lock);
return ERR_PTR(-EPROBE_DEFER);
}
EXPORT_SYMBOL(of_drm_find_panel);
int of_drm_get_panel_orientation(const struct device_node *np,
enum drm_panel_orientation *orientation)
{
int rotation, ret;
ret = of_property_read_u32(np, "rotation", &rotation);
if (ret == -EINVAL) {
*orientation = DRM_MODE_PANEL_ORIENTATION_UNKNOWN;
return 0;
}
if (ret < 0)
return ret;
if (rotation == 0)
*orientation = DRM_MODE_PANEL_ORIENTATION_NORMAL;
else if (rotation == 90)
*orientation = DRM_MODE_PANEL_ORIENTATION_RIGHT_UP;
else if (rotation == 180)
*orientation = DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP;
else if (rotation == 270)
*orientation = DRM_MODE_PANEL_ORIENTATION_LEFT_UP;
else
return -EINVAL;
return 0;
}
EXPORT_SYMBOL(of_drm_get_panel_orientation);
#endif
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) {
mutex_unlock(&panel_lock);
return panel;
}
}
mutex_unlock(&panel_lock);
return ERR_PTR(-EPROBE_DEFER);
}
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;
}
bool drm_is_panel_follower(struct device *dev)
{
return device_property_present(dev, "panel");
}
EXPORT_SYMBOL(drm_is_panel_follower);
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);
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);
}
EXPORT_SYMBOL(drm_panel_remove_follower);
static void drm_panel_remove_follower_void(void *follower)
{
drm_panel_remove_follower(follower);
}
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)
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");