mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-25 02:09:42 +00:00
![Maxime Ripard](/assets/img/avatar_default.png)
The drm_fbdev_cma_init function always calls the drm_helper_disable_unused_functions. Since it's part of the usual probe process, all the drivers using that helper will end up having their encoder and CRTC disable functions called at probe if their device has not been reported as enabled. This could be fixed by reading out from the registers the current state of the device if it is enabled, but even that will not handle the case where the device is actually disabled. Moreover, the drivers using the atomic modesetting expect that their enable and disable callback to be called when the device is already enabled or disabled (respectively). We can however fix this issue by moving the call to drm_helper_disable_unused_functions out of drm_fbdev_cma_init and make the drivers needing it (all the drivers calling drm_fbdev_cma_init and not using the atomic modesetting) explicitly call it. Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Link: http://patchwork.freedesktop.org/patch/msgid/1452785109-6172-14-git-send-email-maxime.ripard@free-electrons.com Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
412 lines
11 KiB
C
412 lines
11 KiB
C
/*
|
|
* drm kms/fb cma (contiguous memory allocator) helper functions
|
|
*
|
|
* Copyright (C) 2012 Analog Device Inc.
|
|
* Author: Lars-Peter Clausen <lars@metafoo.de>
|
|
*
|
|
* Based on udl_fbdev.c
|
|
* Copyright (C) 2012 Red Hat
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <drm/drmP.h>
|
|
#include <drm/drm_crtc.h>
|
|
#include <drm/drm_fb_helper.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_gem_cma_helper.h>
|
|
#include <drm/drm_fb_cma_helper.h>
|
|
#include <linux/module.h>
|
|
|
|
struct drm_fb_cma {
|
|
struct drm_framebuffer fb;
|
|
struct drm_gem_cma_object *obj[4];
|
|
};
|
|
|
|
struct drm_fbdev_cma {
|
|
struct drm_fb_helper fb_helper;
|
|
struct drm_fb_cma *fb;
|
|
};
|
|
|
|
static inline struct drm_fbdev_cma *to_fbdev_cma(struct drm_fb_helper *helper)
|
|
{
|
|
return container_of(helper, struct drm_fbdev_cma, fb_helper);
|
|
}
|
|
|
|
static inline struct drm_fb_cma *to_fb_cma(struct drm_framebuffer *fb)
|
|
{
|
|
return container_of(fb, struct drm_fb_cma, fb);
|
|
}
|
|
|
|
static void drm_fb_cma_destroy(struct drm_framebuffer *fb)
|
|
{
|
|
struct drm_fb_cma *fb_cma = to_fb_cma(fb);
|
|
int i;
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
if (fb_cma->obj[i])
|
|
drm_gem_object_unreference_unlocked(&fb_cma->obj[i]->base);
|
|
}
|
|
|
|
drm_framebuffer_cleanup(fb);
|
|
kfree(fb_cma);
|
|
}
|
|
|
|
static int drm_fb_cma_create_handle(struct drm_framebuffer *fb,
|
|
struct drm_file *file_priv, unsigned int *handle)
|
|
{
|
|
struct drm_fb_cma *fb_cma = to_fb_cma(fb);
|
|
|
|
return drm_gem_handle_create(file_priv,
|
|
&fb_cma->obj[0]->base, handle);
|
|
}
|
|
|
|
static struct drm_framebuffer_funcs drm_fb_cma_funcs = {
|
|
.destroy = drm_fb_cma_destroy,
|
|
.create_handle = drm_fb_cma_create_handle,
|
|
};
|
|
|
|
static struct drm_fb_cma *drm_fb_cma_alloc(struct drm_device *dev,
|
|
const const struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_cma_object **obj,
|
|
unsigned int num_planes)
|
|
{
|
|
struct drm_fb_cma *fb_cma;
|
|
int ret;
|
|
int i;
|
|
|
|
fb_cma = kzalloc(sizeof(*fb_cma), GFP_KERNEL);
|
|
if (!fb_cma)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
drm_helper_mode_fill_fb_struct(&fb_cma->fb, mode_cmd);
|
|
|
|
for (i = 0; i < num_planes; i++)
|
|
fb_cma->obj[i] = obj[i];
|
|
|
|
ret = drm_framebuffer_init(dev, &fb_cma->fb, &drm_fb_cma_funcs);
|
|
if (ret) {
|
|
dev_err(dev->dev, "Failed to initialize framebuffer: %d\n", ret);
|
|
kfree(fb_cma);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
return fb_cma;
|
|
}
|
|
|
|
/**
|
|
* drm_fb_cma_create() - (struct drm_mode_config_funcs *)->fb_create callback function
|
|
*
|
|
* If your hardware has special alignment or pitch requirements these should be
|
|
* checked before calling this function.
|
|
*/
|
|
struct drm_framebuffer *drm_fb_cma_create(struct drm_device *dev,
|
|
struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd)
|
|
{
|
|
struct drm_fb_cma *fb_cma;
|
|
struct drm_gem_cma_object *objs[4];
|
|
struct drm_gem_object *obj;
|
|
unsigned int hsub;
|
|
unsigned int vsub;
|
|
int ret;
|
|
int i;
|
|
|
|
hsub = drm_format_horz_chroma_subsampling(mode_cmd->pixel_format);
|
|
vsub = drm_format_vert_chroma_subsampling(mode_cmd->pixel_format);
|
|
|
|
for (i = 0; i < drm_format_num_planes(mode_cmd->pixel_format); i++) {
|
|
unsigned int width = mode_cmd->width / (i ? hsub : 1);
|
|
unsigned int height = mode_cmd->height / (i ? vsub : 1);
|
|
unsigned int min_size;
|
|
|
|
obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[i]);
|
|
if (!obj) {
|
|
dev_err(dev->dev, "Failed to lookup GEM object\n");
|
|
ret = -ENXIO;
|
|
goto err_gem_object_unreference;
|
|
}
|
|
|
|
min_size = (height - 1) * mode_cmd->pitches[i]
|
|
+ width * drm_format_plane_cpp(mode_cmd->pixel_format, i)
|
|
+ mode_cmd->offsets[i];
|
|
|
|
if (obj->size < min_size) {
|
|
drm_gem_object_unreference_unlocked(obj);
|
|
ret = -EINVAL;
|
|
goto err_gem_object_unreference;
|
|
}
|
|
objs[i] = to_drm_gem_cma_obj(obj);
|
|
}
|
|
|
|
fb_cma = drm_fb_cma_alloc(dev, mode_cmd, objs, i);
|
|
if (IS_ERR(fb_cma)) {
|
|
ret = PTR_ERR(fb_cma);
|
|
goto err_gem_object_unreference;
|
|
}
|
|
|
|
return &fb_cma->fb;
|
|
|
|
err_gem_object_unreference:
|
|
for (i--; i >= 0; i--)
|
|
drm_gem_object_unreference_unlocked(&objs[i]->base);
|
|
return ERR_PTR(ret);
|
|
}
|
|
EXPORT_SYMBOL_GPL(drm_fb_cma_create);
|
|
|
|
/**
|
|
* drm_fb_cma_get_gem_obj() - Get CMA GEM object for framebuffer
|
|
* @fb: The framebuffer
|
|
* @plane: Which plane
|
|
*
|
|
* Return the CMA GEM object for given framebuffer.
|
|
*
|
|
* This function will usually be called from the CRTC callback functions.
|
|
*/
|
|
struct drm_gem_cma_object *drm_fb_cma_get_gem_obj(struct drm_framebuffer *fb,
|
|
unsigned int plane)
|
|
{
|
|
struct drm_fb_cma *fb_cma = to_fb_cma(fb);
|
|
|
|
if (plane >= 4)
|
|
return NULL;
|
|
|
|
return fb_cma->obj[plane];
|
|
}
|
|
EXPORT_SYMBOL_GPL(drm_fb_cma_get_gem_obj);
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
/*
|
|
* drm_fb_cma_describe() - Helper to dump information about a single
|
|
* CMA framebuffer object
|
|
*/
|
|
static void drm_fb_cma_describe(struct drm_framebuffer *fb, struct seq_file *m)
|
|
{
|
|
struct drm_fb_cma *fb_cma = to_fb_cma(fb);
|
|
int i, n = drm_format_num_planes(fb->pixel_format);
|
|
|
|
seq_printf(m, "fb: %dx%d@%4.4s\n", fb->width, fb->height,
|
|
(char *)&fb->pixel_format);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
seq_printf(m, " %d: offset=%d pitch=%d, obj: ",
|
|
i, fb->offsets[i], fb->pitches[i]);
|
|
drm_gem_cma_describe(fb_cma->obj[i], m);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* drm_fb_cma_debugfs_show() - Helper to list CMA framebuffer objects
|
|
* in debugfs.
|
|
*/
|
|
int drm_fb_cma_debugfs_show(struct seq_file *m, void *arg)
|
|
{
|
|
struct drm_info_node *node = (struct drm_info_node *) m->private;
|
|
struct drm_device *dev = node->minor->dev;
|
|
struct drm_framebuffer *fb;
|
|
|
|
mutex_lock(&dev->mode_config.fb_lock);
|
|
drm_for_each_fb(fb, dev)
|
|
drm_fb_cma_describe(fb, m);
|
|
mutex_unlock(&dev->mode_config.fb_lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(drm_fb_cma_debugfs_show);
|
|
#endif
|
|
|
|
static struct fb_ops drm_fbdev_cma_ops = {
|
|
.owner = THIS_MODULE,
|
|
.fb_fillrect = drm_fb_helper_sys_fillrect,
|
|
.fb_copyarea = drm_fb_helper_sys_copyarea,
|
|
.fb_imageblit = drm_fb_helper_sys_imageblit,
|
|
.fb_check_var = drm_fb_helper_check_var,
|
|
.fb_set_par = drm_fb_helper_set_par,
|
|
.fb_blank = drm_fb_helper_blank,
|
|
.fb_pan_display = drm_fb_helper_pan_display,
|
|
.fb_setcmap = drm_fb_helper_setcmap,
|
|
};
|
|
|
|
static int drm_fbdev_cma_create(struct drm_fb_helper *helper,
|
|
struct drm_fb_helper_surface_size *sizes)
|
|
{
|
|
struct drm_fbdev_cma *fbdev_cma = to_fbdev_cma(helper);
|
|
struct drm_mode_fb_cmd2 mode_cmd = { 0 };
|
|
struct drm_device *dev = helper->dev;
|
|
struct drm_gem_cma_object *obj;
|
|
struct drm_framebuffer *fb;
|
|
unsigned int bytes_per_pixel;
|
|
unsigned long offset;
|
|
struct fb_info *fbi;
|
|
size_t size;
|
|
int ret;
|
|
|
|
DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d)\n",
|
|
sizes->surface_width, sizes->surface_height,
|
|
sizes->surface_bpp);
|
|
|
|
bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8);
|
|
|
|
mode_cmd.width = sizes->surface_width;
|
|
mode_cmd.height = sizes->surface_height;
|
|
mode_cmd.pitches[0] = sizes->surface_width * bytes_per_pixel;
|
|
mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
|
|
sizes->surface_depth);
|
|
|
|
size = mode_cmd.pitches[0] * mode_cmd.height;
|
|
obj = drm_gem_cma_create(dev, size);
|
|
if (IS_ERR(obj))
|
|
return -ENOMEM;
|
|
|
|
fbi = drm_fb_helper_alloc_fbi(helper);
|
|
if (IS_ERR(fbi)) {
|
|
ret = PTR_ERR(fbi);
|
|
goto err_gem_free_object;
|
|
}
|
|
|
|
fbdev_cma->fb = drm_fb_cma_alloc(dev, &mode_cmd, &obj, 1);
|
|
if (IS_ERR(fbdev_cma->fb)) {
|
|
dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n");
|
|
ret = PTR_ERR(fbdev_cma->fb);
|
|
goto err_fb_info_destroy;
|
|
}
|
|
|
|
fb = &fbdev_cma->fb->fb;
|
|
helper->fb = fb;
|
|
|
|
fbi->par = helper;
|
|
fbi->flags = FBINFO_FLAG_DEFAULT;
|
|
fbi->fbops = &drm_fbdev_cma_ops;
|
|
|
|
drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
|
|
drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height);
|
|
|
|
offset = fbi->var.xoffset * bytes_per_pixel;
|
|
offset += fbi->var.yoffset * fb->pitches[0];
|
|
|
|
dev->mode_config.fb_base = (resource_size_t)obj->paddr;
|
|
fbi->screen_base = obj->vaddr + offset;
|
|
fbi->fix.smem_start = (unsigned long)(obj->paddr + offset);
|
|
fbi->screen_size = size;
|
|
fbi->fix.smem_len = size;
|
|
|
|
return 0;
|
|
|
|
err_fb_info_destroy:
|
|
drm_fb_helper_release_fbi(helper);
|
|
err_gem_free_object:
|
|
dev->driver->gem_free_object(&obj->base);
|
|
return ret;
|
|
}
|
|
|
|
static const struct drm_fb_helper_funcs drm_fb_cma_helper_funcs = {
|
|
.fb_probe = drm_fbdev_cma_create,
|
|
};
|
|
|
|
/**
|
|
* drm_fbdev_cma_init() - Allocate and initializes a drm_fbdev_cma struct
|
|
* @dev: DRM device
|
|
* @preferred_bpp: Preferred bits per pixel for the device
|
|
* @num_crtc: Number of CRTCs
|
|
* @max_conn_count: Maximum number of connectors
|
|
*
|
|
* Returns a newly allocated drm_fbdev_cma struct or a ERR_PTR.
|
|
*/
|
|
struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev,
|
|
unsigned int preferred_bpp, unsigned int num_crtc,
|
|
unsigned int max_conn_count)
|
|
{
|
|
struct drm_fbdev_cma *fbdev_cma;
|
|
struct drm_fb_helper *helper;
|
|
int ret;
|
|
|
|
fbdev_cma = kzalloc(sizeof(*fbdev_cma), GFP_KERNEL);
|
|
if (!fbdev_cma) {
|
|
dev_err(dev->dev, "Failed to allocate drm fbdev.\n");
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
helper = &fbdev_cma->fb_helper;
|
|
|
|
drm_fb_helper_prepare(dev, helper, &drm_fb_cma_helper_funcs);
|
|
|
|
ret = drm_fb_helper_init(dev, helper, num_crtc, max_conn_count);
|
|
if (ret < 0) {
|
|
dev_err(dev->dev, "Failed to initialize drm fb helper.\n");
|
|
goto err_free;
|
|
}
|
|
|
|
ret = drm_fb_helper_single_add_all_connectors(helper);
|
|
if (ret < 0) {
|
|
dev_err(dev->dev, "Failed to add connectors.\n");
|
|
goto err_drm_fb_helper_fini;
|
|
|
|
}
|
|
|
|
ret = drm_fb_helper_initial_config(helper, preferred_bpp);
|
|
if (ret < 0) {
|
|
dev_err(dev->dev, "Failed to set initial hw configuration.\n");
|
|
goto err_drm_fb_helper_fini;
|
|
}
|
|
|
|
return fbdev_cma;
|
|
|
|
err_drm_fb_helper_fini:
|
|
drm_fb_helper_fini(helper);
|
|
err_free:
|
|
kfree(fbdev_cma);
|
|
|
|
return ERR_PTR(ret);
|
|
}
|
|
EXPORT_SYMBOL_GPL(drm_fbdev_cma_init);
|
|
|
|
/**
|
|
* drm_fbdev_cma_fini() - Free drm_fbdev_cma struct
|
|
* @fbdev_cma: The drm_fbdev_cma struct
|
|
*/
|
|
void drm_fbdev_cma_fini(struct drm_fbdev_cma *fbdev_cma)
|
|
{
|
|
drm_fb_helper_unregister_fbi(&fbdev_cma->fb_helper);
|
|
drm_fb_helper_release_fbi(&fbdev_cma->fb_helper);
|
|
|
|
if (fbdev_cma->fb) {
|
|
drm_framebuffer_unregister_private(&fbdev_cma->fb->fb);
|
|
drm_fb_cma_destroy(&fbdev_cma->fb->fb);
|
|
}
|
|
|
|
drm_fb_helper_fini(&fbdev_cma->fb_helper);
|
|
kfree(fbdev_cma);
|
|
}
|
|
EXPORT_SYMBOL_GPL(drm_fbdev_cma_fini);
|
|
|
|
/**
|
|
* drm_fbdev_cma_restore_mode() - Restores initial framebuffer mode
|
|
* @fbdev_cma: The drm_fbdev_cma struct, may be NULL
|
|
*
|
|
* This function is usually called from the DRM drivers lastclose callback.
|
|
*/
|
|
void drm_fbdev_cma_restore_mode(struct drm_fbdev_cma *fbdev_cma)
|
|
{
|
|
if (fbdev_cma)
|
|
drm_fb_helper_restore_fbdev_mode_unlocked(&fbdev_cma->fb_helper);
|
|
}
|
|
EXPORT_SYMBOL_GPL(drm_fbdev_cma_restore_mode);
|
|
|
|
/**
|
|
* drm_fbdev_cma_hotplug_event() - Poll for hotpulug events
|
|
* @fbdev_cma: The drm_fbdev_cma struct, may be NULL
|
|
*
|
|
* This function is usually called from the DRM drivers output_poll_changed
|
|
* callback.
|
|
*/
|
|
void drm_fbdev_cma_hotplug_event(struct drm_fbdev_cma *fbdev_cma)
|
|
{
|
|
if (fbdev_cma)
|
|
drm_fb_helper_hotplug_event(&fbdev_cma->fb_helper);
|
|
}
|
|
EXPORT_SYMBOL_GPL(drm_fbdev_cma_hotplug_event);
|