1
0
Fork 0
mirror of synced 2025-03-06 20:59:54 +01:00
linux/drivers/gpu/drm/logicvc/logicvc_drm.c
Douglas Anderson ce3d99c834 drm: Call drm_atomic_helper_shutdown() at shutdown time for misc drivers
Based on grepping through the source code these drivers appear to be
missing a call to drm_atomic_helper_shutdown() at system shutdown
time. Among other things, this means that if a panel is in use that it
won't be cleanly powered off at system shutdown time.

The fact that we should call drm_atomic_helper_shutdown() in the case
of OS shutdown/restart comes straight out of the kernel doc "driver
instance overview" in drm_drv.c.

All of the drivers in this patch were fairly straightforward to fix
since they already had a call to drm_atomic_helper_shutdown() at
remove/unbind time but were just lacking one at system shutdown. The
only hitch is that some of these drivers use the component model to
register/unregister their DRM devices. The shutdown callback is part
of the original device. The typical solution here, based on how other
DRM drivers do this, is to keep track of whether the device is bound
based on drvdata. In most cases the drvdata is the drm_device, so we
can just make sure it is NULL when the device is not bound. In some
drivers, this required minor code changes. To make things simpler,
drm_atomic_helper_shutdown() has been modified to consider a NULL
drm_device as a noop in the patch ("drm/atomic-helper:
drm_atomic_helper_shutdown(NULL) should be a noop").

Suggested-by: Maxime Ripard <mripard@kernel.org>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Tested-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Acked-by: Maxime Ripard <mripard@kernel.org>
Tested-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Reviewed-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Reviewed-by: Sui Jingfeng <suijingfeng@loongson.cn>
Tested-by: Sui Jingfeng <suijingfeng@loongson.cn>
Signed-off-by: Douglas Anderson <dianders@chromium.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20230901163944.RFT.2.I9115e5d094a43e687978b0699cc1fe9f2a3452ea@changeid
2023-09-21 10:41:04 -07:00

514 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2019-2022 Bootlin
* Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/types.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fbdev_dma.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_print.h>
#include "logicvc_crtc.h"
#include "logicvc_drm.h"
#include "logicvc_interface.h"
#include "logicvc_mode.h"
#include "logicvc_layer.h"
#include "logicvc_of.h"
#include "logicvc_regs.h"
DEFINE_DRM_GEM_DMA_FOPS(logicvc_drm_fops);
static int logicvc_drm_gem_dma_dumb_create(struct drm_file *file_priv,
struct drm_device *drm_dev,
struct drm_mode_create_dumb *args)
{
struct logicvc_drm *logicvc = logicvc_drm(drm_dev);
/* Stride is always fixed to its configuration value. */
args->pitch = logicvc->config.row_stride * DIV_ROUND_UP(args->bpp, 8);
return drm_gem_dma_dumb_create_internal(file_priv, drm_dev, args);
}
static struct drm_driver logicvc_drm_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET |
DRIVER_ATOMIC,
.fops = &logicvc_drm_fops,
.name = "logicvc-drm",
.desc = "Xylon LogiCVC DRM driver",
.date = "20200403",
.major = 1,
.minor = 0,
DRM_GEM_DMA_DRIVER_OPS_VMAP_WITH_DUMB_CREATE(logicvc_drm_gem_dma_dumb_create),
};
static struct regmap_config logicvc_drm_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.name = "logicvc-drm",
};
static irqreturn_t logicvc_drm_irq_handler(int irq, void *data)
{
struct logicvc_drm *logicvc = data;
irqreturn_t ret = IRQ_NONE;
u32 stat = 0;
/* Get pending interrupt sources. */
regmap_read(logicvc->regmap, LOGICVC_INT_STAT_REG, &stat);
/* Clear all pending interrupt sources. */
regmap_write(logicvc->regmap, LOGICVC_INT_STAT_REG, stat);
if (stat & LOGICVC_INT_STAT_V_SYNC) {
logicvc_crtc_vblank_handler(logicvc);
ret = IRQ_HANDLED;
}
return ret;
}
static int logicvc_drm_config_parse(struct logicvc_drm *logicvc)
{
struct drm_device *drm_dev = &logicvc->drm_dev;
struct device *dev = drm_dev->dev;
struct device_node *of_node = dev->of_node;
struct logicvc_drm_config *config = &logicvc->config;
struct device_node *layers_node;
int ret;
logicvc_of_property_parse_bool(of_node, LOGICVC_OF_PROPERTY_DITHERING,
&config->dithering);
logicvc_of_property_parse_bool(of_node,
LOGICVC_OF_PROPERTY_BACKGROUND_LAYER,
&config->background_layer);
logicvc_of_property_parse_bool(of_node,
LOGICVC_OF_PROPERTY_LAYERS_CONFIGURABLE,
&config->layers_configurable);
ret = logicvc_of_property_parse_u32(of_node,
LOGICVC_OF_PROPERTY_DISPLAY_INTERFACE,
&config->display_interface);
if (ret)
return ret;
ret = logicvc_of_property_parse_u32(of_node,
LOGICVC_OF_PROPERTY_DISPLAY_COLORSPACE,
&config->display_colorspace);
if (ret)
return ret;
ret = logicvc_of_property_parse_u32(of_node,
LOGICVC_OF_PROPERTY_DISPLAY_DEPTH,
&config->display_depth);
if (ret)
return ret;
ret = logicvc_of_property_parse_u32(of_node,
LOGICVC_OF_PROPERTY_ROW_STRIDE,
&config->row_stride);
if (ret)
return ret;
layers_node = of_get_child_by_name(of_node, "layers");
if (!layers_node) {
drm_err(drm_dev, "Missing non-optional layers node\n");
return -EINVAL;
}
config->layers_count = of_get_child_count(layers_node);
if (!config->layers_count) {
drm_err(drm_dev,
"Missing a non-optional layers children node\n");
return -EINVAL;
}
return 0;
}
static int logicvc_clocks_prepare(struct logicvc_drm *logicvc)
{
struct drm_device *drm_dev = &logicvc->drm_dev;
struct device *dev = drm_dev->dev;
struct {
struct clk **clk;
char *name;
bool optional;
} clocks_map[] = {
{
.clk = &logicvc->vclk,
.name = "vclk",
.optional = false,
},
{
.clk = &logicvc->vclk2,
.name = "vclk2",
.optional = true,
},
{
.clk = &logicvc->lvdsclk,
.name = "lvdsclk",
.optional = true,
},
{
.clk = &logicvc->lvdsclkn,
.name = "lvdsclkn",
.optional = true,
},
};
unsigned int i;
int ret;
for (i = 0; i < ARRAY_SIZE(clocks_map); i++) {
struct clk *clk;
clk = devm_clk_get(dev, clocks_map[i].name);
if (IS_ERR(clk)) {
if (PTR_ERR(clk) == -ENOENT && clocks_map[i].optional)
continue;
drm_err(drm_dev, "Missing non-optional clock %s\n",
clocks_map[i].name);
ret = PTR_ERR(clk);
goto error;
}
ret = clk_prepare_enable(clk);
if (ret) {
drm_err(drm_dev,
"Failed to prepare and enable clock %s\n",
clocks_map[i].name);
goto error;
}
*clocks_map[i].clk = clk;
}
return 0;
error:
for (i = 0; i < ARRAY_SIZE(clocks_map); i++) {
if (!*clocks_map[i].clk)
continue;
clk_disable_unprepare(*clocks_map[i].clk);
*clocks_map[i].clk = NULL;
}
return ret;
}
static int logicvc_clocks_unprepare(struct logicvc_drm *logicvc)
{
struct clk **clocks[] = {
&logicvc->vclk,
&logicvc->vclk2,
&logicvc->lvdsclk,
&logicvc->lvdsclkn,
};
unsigned int i;
for (i = 0; i < ARRAY_SIZE(clocks); i++) {
if (!*clocks[i])
continue;
clk_disable_unprepare(*clocks[i]);
*clocks[i] = NULL;
}
return 0;
}
static const struct logicvc_drm_caps logicvc_drm_caps[] = {
{
.major = 3,
.layer_address = false,
},
{
.major = 4,
.layer_address = true,
},
{
.major = 5,
.layer_address = true,
},
};
static const struct logicvc_drm_caps *
logicvc_drm_caps_match(struct logicvc_drm *logicvc)
{
struct drm_device *drm_dev = &logicvc->drm_dev;
const struct logicvc_drm_caps *caps = NULL;
unsigned int major, minor;
char level;
unsigned int i;
u32 version;
regmap_read(logicvc->regmap, LOGICVC_IP_VERSION_REG, &version);
major = FIELD_GET(LOGICVC_IP_VERSION_MAJOR_MASK, version);
minor = FIELD_GET(LOGICVC_IP_VERSION_MINOR_MASK, version);
level = FIELD_GET(LOGICVC_IP_VERSION_LEVEL_MASK, version) + 'a';
for (i = 0; i < ARRAY_SIZE(logicvc_drm_caps); i++) {
if (logicvc_drm_caps[i].major &&
logicvc_drm_caps[i].major != major)
continue;
if (logicvc_drm_caps[i].minor &&
logicvc_drm_caps[i].minor != minor)
continue;
if (logicvc_drm_caps[i].level &&
logicvc_drm_caps[i].level != level)
continue;
caps = &logicvc_drm_caps[i];
}
drm_info(drm_dev, "LogiCVC version %d.%02d.%c\n", major, minor, level);
return caps;
}
static int logicvc_drm_probe(struct platform_device *pdev)
{
struct device_node *of_node = pdev->dev.of_node;
struct device_node *reserved_mem_node;
struct reserved_mem *reserved_mem = NULL;
const struct logicvc_drm_caps *caps;
struct logicvc_drm *logicvc;
struct device *dev = &pdev->dev;
struct drm_device *drm_dev;
struct regmap *regmap = NULL;
struct resource res;
void __iomem *base;
unsigned int preferred_bpp;
int irq;
int ret;
ret = of_reserved_mem_device_init(dev);
if (ret && ret != -ENODEV) {
dev_err(dev, "Failed to init memory region\n");
goto error_early;
}
reserved_mem_node = of_parse_phandle(of_node, "memory-region", 0);
if (reserved_mem_node) {
reserved_mem = of_reserved_mem_lookup(reserved_mem_node);
of_node_put(reserved_mem_node);
}
/* Get regmap from parent if available. */
if (of_node->parent)
regmap = syscon_node_to_regmap(of_node->parent);
/* Register our own regmap otherwise. */
if (IS_ERR_OR_NULL(regmap)) {
ret = of_address_to_resource(of_node, 0, &res);
if (ret) {
dev_err(dev, "Failed to get resource from address\n");
goto error_reserved_mem;
}
base = devm_ioremap_resource(dev, &res);
if (IS_ERR(base)) {
dev_err(dev, "Failed to map I/O base\n");
ret = PTR_ERR(base);
goto error_reserved_mem;
}
logicvc_drm_regmap_config.max_register = resource_size(&res) -
4;
regmap = devm_regmap_init_mmio(dev, base,
&logicvc_drm_regmap_config);
if (IS_ERR(regmap)) {
dev_err(dev, "Failed to create regmap for I/O\n");
ret = PTR_ERR(regmap);
goto error_reserved_mem;
}
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
ret = -ENODEV;
goto error_reserved_mem;
}
logicvc = devm_drm_dev_alloc(dev, &logicvc_drm_driver,
struct logicvc_drm, drm_dev);
if (IS_ERR(logicvc)) {
ret = PTR_ERR(logicvc);
goto error_reserved_mem;
}
platform_set_drvdata(pdev, logicvc);
drm_dev = &logicvc->drm_dev;
logicvc->regmap = regmap;
INIT_LIST_HEAD(&logicvc->layers_list);
caps = logicvc_drm_caps_match(logicvc);
if (!caps) {
ret = -EINVAL;
goto error_reserved_mem;
}
logicvc->caps = caps;
if (reserved_mem)
logicvc->reserved_mem_base = reserved_mem->base;
ret = logicvc_clocks_prepare(logicvc);
if (ret) {
drm_err(drm_dev, "Failed to prepare clocks\n");
goto error_reserved_mem;
}
ret = devm_request_irq(dev, irq, logicvc_drm_irq_handler, 0,
dev_name(dev), logicvc);
if (ret) {
drm_err(drm_dev, "Failed to request IRQ\n");
goto error_clocks;
}
ret = logicvc_drm_config_parse(logicvc);
if (ret && ret != -ENODEV) {
drm_err(drm_dev, "Failed to parse config\n");
goto error_clocks;
}
ret = drmm_mode_config_init(drm_dev);
if (ret) {
drm_err(drm_dev, "Failed to init mode config\n");
goto error_clocks;
}
ret = logicvc_layers_init(logicvc);
if (ret) {
drm_err(drm_dev, "Failed to initialize layers\n");
goto error_clocks;
}
ret = logicvc_crtc_init(logicvc);
if (ret) {
drm_err(drm_dev, "Failed to initialize CRTC\n");
goto error_clocks;
}
logicvc_layers_attach_crtc(logicvc);
ret = logicvc_interface_init(logicvc);
if (ret) {
if (ret != -EPROBE_DEFER)
drm_err(drm_dev, "Failed to initialize interface\n");
goto error_clocks;
}
logicvc_interface_attach_crtc(logicvc);
ret = logicvc_mode_init(logicvc);
if (ret) {
drm_err(drm_dev, "Failed to initialize KMS\n");
goto error_clocks;
}
ret = drm_dev_register(drm_dev, 0);
if (ret) {
drm_err(drm_dev, "Failed to register DRM device\n");
goto error_mode;
}
switch (drm_dev->mode_config.preferred_depth) {
case 16:
preferred_bpp = 16;
break;
case 24:
case 32:
default:
preferred_bpp = 32;
break;
}
drm_fbdev_dma_setup(drm_dev, preferred_bpp);
return 0;
error_mode:
logicvc_mode_fini(logicvc);
error_clocks:
logicvc_clocks_unprepare(logicvc);
error_reserved_mem:
of_reserved_mem_device_release(dev);
error_early:
return ret;
}
static void logicvc_drm_remove(struct platform_device *pdev)
{
struct logicvc_drm *logicvc = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
struct drm_device *drm_dev = &logicvc->drm_dev;
drm_dev_unregister(drm_dev);
drm_atomic_helper_shutdown(drm_dev);
logicvc_mode_fini(logicvc);
logicvc_clocks_unprepare(logicvc);
of_reserved_mem_device_release(dev);
}
static void logicvc_drm_shutdown(struct platform_device *pdev)
{
struct logicvc_drm *logicvc = platform_get_drvdata(pdev);
struct drm_device *drm_dev = &logicvc->drm_dev;
drm_atomic_helper_shutdown(drm_dev);
}
static const struct of_device_id logicvc_drm_of_table[] = {
{ .compatible = "xylon,logicvc-3.02.a-display" },
{ .compatible = "xylon,logicvc-4.01.a-display" },
{},
};
MODULE_DEVICE_TABLE(of, logicvc_drm_of_table);
static struct platform_driver logicvc_drm_platform_driver = {
.probe = logicvc_drm_probe,
.remove_new = logicvc_drm_remove,
.shutdown = logicvc_drm_shutdown,
.driver = {
.name = "logicvc-drm",
.of_match_table = logicvc_drm_of_table,
},
};
module_platform_driver(logicvc_drm_platform_driver);
MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>");
MODULE_DESCRIPTION("Xylon LogiCVC DRM driver");
MODULE_LICENSE("GPL");