diff options
Diffstat (limited to 'drivers/gpu/drm/arm/hdlcd_fb_helper.c')
-rw-r--r-- | drivers/gpu/drm/arm/hdlcd_fb_helper.c | 601 |
1 files changed, 601 insertions, 0 deletions
diff --git a/drivers/gpu/drm/arm/hdlcd_fb_helper.c b/drivers/gpu/drm/arm/hdlcd_fb_helper.c new file mode 100644 index 000000000000..1312e01ef784 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_fb_helper.c @@ -0,0 +1,601 @@ +/* + * Copied from drivers/gpu/drm/drm_fb_cma_helper.c which has the following + * copyright and notices... + * + * 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 "hdlcd_fb_helper.h" +#include <linux/dma-buf.h> +#include <linux/module.h> +#include "hdlcd_drv.h" + +#define MAX_FRAMES 2 + +static int hdlcd_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg); + +/****************************************************************************** + * Code copied from drivers/gpu/drm/drm_fb_helper.c as of Linux 3.18 + ******************************************************************************/ + +/** + * Copy of drm_fb_helper_check_var modified to allow MAX_FRAMES * height + */ +int hdlcd_fb_helper_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct drm_fb_helper *fb_helper = info->par; + struct drm_framebuffer *fb = fb_helper->fb; + int depth; + + if (var->pixclock != 0 || in_dbg_master()) + return -EINVAL; + + /* Need to resize the fb object !!! */ + if (var->bits_per_pixel > fb->bits_per_pixel || + var->xres > fb->width || var->yres > fb->height || + var->xres_virtual > fb->width || var->yres_virtual > fb->height * MAX_FRAMES) { + DRM_DEBUG("fb userspace requested width/height/bpp is greater than current fb " + "request %dx%d-%d (virtual %dx%d) > %dx%d-%d\n", + var->xres, var->yres, var->bits_per_pixel, + var->xres_virtual, var->yres_virtual, + fb->width, fb->height, fb->bits_per_pixel); + return -EINVAL; + } + + switch (var->bits_per_pixel) { + case 16: + depth = (var->green.length == 6) ? 16 : 15; + break; + case 32: + depth = (var->transp.length > 0) ? 32 : 24; + break; + default: + depth = var->bits_per_pixel; + break; + } + + switch (depth) { + case 8: + var->red.offset = 0; + var->green.offset = 0; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 0; + var->transp.offset = 0; + break; + case 15: + var->red.offset = 10; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 5; + var->blue.length = 5; + var->transp.length = 1; + var->transp.offset = 15; + break; + case 16: + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + var->transp.length = 0; + var->transp.offset = 0; + break; + case 24: + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 0; + var->transp.offset = 0; + break; + case 32: + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 8; + var->transp.offset = 24; + break; + default: + return -EINVAL; + } + return 0; +} + +/****************************************************************************** + * Code copied from drivers/gpu/drm/drm_fb_cma_helper.c as of Linux 3.18 + ******************************************************************************/ + +struct hdlcd_fb { + struct drm_framebuffer fb; + struct drm_gem_cma_object *obj[4]; +}; + +struct hdlcd_drm_fbdev { + struct drm_fb_helper fb_helper; + struct hdlcd_fb *fb; +}; + +static inline struct hdlcd_drm_fbdev *to_hdlcd_fbdev(struct drm_fb_helper *helper) +{ + return container_of(helper, struct hdlcd_drm_fbdev, fb_helper); +} + +static inline struct hdlcd_fb *to_hdlcd_fb(struct drm_framebuffer *fb) +{ + return container_of(fb, struct hdlcd_fb, fb); +} + +static void hdlcd_fb_destroy(struct drm_framebuffer *fb) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb); + int i; + + for (i = 0; i < 4; i++) { + if (hdlcd_fb->obj[i]) + drm_gem_object_unreference_unlocked(&hdlcd_fb->obj[i]->base); + } + + drm_framebuffer_cleanup(fb); + kfree(hdlcd_fb); +} + +static int hdlcd_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, unsigned int *handle) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb); + + return drm_gem_handle_create(file_priv, + &hdlcd_fb->obj[0]->base, handle); +} + +static struct drm_framebuffer_funcs hdlcd_fb_funcs = { + .destroy = hdlcd_fb_destroy, + .create_handle = hdlcd_fb_create_handle, +}; + +static struct hdlcd_fb *hdlcd_fb_alloc(struct drm_device *dev, + struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_cma_object **obj, + unsigned int num_planes) +{ + struct hdlcd_fb *hdlcd_fb; + int ret; + int i; + + hdlcd_fb = kzalloc(sizeof(*hdlcd_fb), GFP_KERNEL); + if (!hdlcd_fb) + return ERR_PTR(-ENOMEM); + + drm_helper_mode_fill_fb_struct(&hdlcd_fb->fb, mode_cmd); + + for (i = 0; i < num_planes; i++) + hdlcd_fb->obj[i] = obj[i]; + + ret = drm_framebuffer_init(dev, &hdlcd_fb->fb, &hdlcd_fb_funcs); + if (ret) { + dev_err(dev->dev, "Failed to initialize framebuffer: %d\n", ret); + kfree(hdlcd_fb); + return ERR_PTR(ret); + } + + return hdlcd_fb; +} + +/** + * hdlcd_fb_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 *hdlcd_fb_create(struct drm_device *dev, + struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct hdlcd_fb *hdlcd_fb; + 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); + } + + hdlcd_fb = hdlcd_fb_alloc(dev, mode_cmd, objs, i); + if (IS_ERR(hdlcd_fb)) { + ret = PTR_ERR(hdlcd_fb); + goto err_gem_object_unreference; + } + + return &hdlcd_fb->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(hdlcd_fb_create); + +/** + * hdlcd_fb_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 *hdlcd_fb_get_gem_obj(struct drm_framebuffer *fb, + unsigned int plane) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb); + + if (plane >= 4) + return NULL; + + return hdlcd_fb->obj[plane]; +} +EXPORT_SYMBOL_GPL(hdlcd_fb_get_gem_obj); + +#ifdef CONFIG_DEBUG_FS +/* + * hdlcd_fb_describe() - Helper to dump information about a single + * CMA framebuffer object + */ +static void hdlcd_fb_describe(struct drm_framebuffer *fb, struct seq_file *m) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(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(hdlcd_fb->obj[i], m); + } +} + +/** + * hdlcd_fb_debugfs_show() - Helper to list CMA framebuffer objects + * in debugfs. + */ +int hdlcd_fb_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) + hdlcd_fb_describe(fb, m); + mutex_unlock(&dev->mode_config.fb_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(hdlcd_fb_debugfs_show); +#endif + +static struct fb_ops hdlcd_drm_fbdev_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 = hdlcd_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, + .fb_ioctl = hdlcd_fb_ioctl, + .fb_compat_ioctl= hdlcd_fb_ioctl, +}; + +static int hdlcd_drm_fbdev_create(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct hdlcd_drm_fbdev *hdlcd_fbdev = to_hdlcd_fbdev(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 * MAX_FRAMES; + 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_drm_gem_cma_free_object; + } + + hdlcd_fbdev->fb = hdlcd_fb_alloc(dev, &mode_cmd, &obj, 1); + if (IS_ERR(hdlcd_fbdev->fb)) { + dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n"); + ret = PTR_ERR(hdlcd_fbdev->fb); + goto err_fb_info_destroy; + } + + fb = &hdlcd_fbdev->fb->fb; + helper->fb = fb; + + fbi->par = helper; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->fbops = &hdlcd_drm_fbdev_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; + fbi->var.yres_virtual = fbi->var.yres * MAX_FRAMES; + + return 0; + +err_fb_info_destroy: + drm_fb_helper_release_fbi(helper); +err_drm_gem_cma_free_object: + drm_gem_cma_free_object(&obj->base); + return ret; +} + +static const struct drm_fb_helper_funcs hdlcd_fb_helper_funcs = { + .fb_probe = hdlcd_drm_fbdev_create, +}; + +/** + * hdlcd_drm_fbdev_init() - Allocate and initializes a hdlcd_drm_fbdev 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 hdlcd_drm_fbdev struct or a ERR_PTR. + */ +struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init(struct drm_device *dev, + unsigned int preferred_bpp, unsigned int num_crtc, + unsigned int max_conn_count) +{ + struct hdlcd_drm_fbdev *hdlcd_fbdev; + struct drm_fb_helper *helper; + int ret; + + hdlcd_fbdev = kzalloc(sizeof(*hdlcd_fbdev), GFP_KERNEL); + if (!hdlcd_fbdev) { + dev_err(dev->dev, "Failed to allocate drm fbdev.\n"); + return ERR_PTR(-ENOMEM); + } + + helper = &hdlcd_fbdev->fb_helper; + + drm_fb_helper_prepare(dev, helper, &hdlcd_fb_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; + + } + + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(dev); + + 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 hdlcd_fbdev; + +err_drm_fb_helper_fini: + drm_fb_helper_fini(helper); +err_free: + kfree(hdlcd_fbdev); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_init); + +/** + * hdlcd_drm_fbdev_fini() - Free hdlcd_drm_fbdev struct + * @hdlcd_fbdev: The hdlcd_drm_fbdev struct + */ +void hdlcd_drm_fbdev_fini(struct hdlcd_drm_fbdev *hdlcd_fbdev) +{ + drm_fb_helper_unregister_fbi(&hdlcd_fbdev->fb_helper); + drm_fb_helper_release_fbi(&hdlcd_fbdev->fb_helper); + + if (hdlcd_fbdev->fb) { + drm_framebuffer_unregister_private(&hdlcd_fbdev->fb->fb); + hdlcd_fb_destroy(&hdlcd_fbdev->fb->fb); + } + + drm_fb_helper_fini(&hdlcd_fbdev->fb_helper); + kfree(hdlcd_fbdev); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_fini); + +/** + * hdlcd_drm_fbdev_restore_mode() - Restores initial framebuffer mode + * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL + * + * This function is usually called from the DRM drivers lastclose callback. + */ +void hdlcd_drm_fbdev_restore_mode(struct hdlcd_drm_fbdev *hdlcd_fbdev) +{ + if (hdlcd_fbdev) + drm_fb_helper_restore_fbdev_mode_unlocked(&hdlcd_fbdev->fb_helper); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_restore_mode); + +/** + * hdlcd_drm_fbdev_hotplug_event() - Poll for hotpulug events + * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL + * + * This function is usually called from the DRM drivers output_poll_changed + * callback. + */ +void hdlcd_drm_fbdev_hotplug_event(struct hdlcd_drm_fbdev *hdlcd_fbdev) +{ + if (hdlcd_fbdev) + drm_fb_helper_hotplug_event(&hdlcd_fbdev->fb_helper); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_hotplug_event); + + +/****************************************************************************** + * IOCTL Interface + ******************************************************************************/ + +/* + * Used for sharing buffers with Mali userspace + */ +struct fb_dmabuf_export { + uint32_t fd; + uint32_t flags; +}; + +#define FBIOGET_DMABUF _IOR('F', 0x21, struct fb_dmabuf_export) + +static int hdlcd_get_dmabuf_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct fb_dmabuf_export ebuf; + struct drm_fb_helper *helper = info->par; + struct hdlcd_drm_private *hdlcd = helper->dev->dev_private; + struct drm_gem_cma_object *obj = hdlcd->fbdev->fb->obj[0]; + struct dma_buf *dma_buf; + uint32_t fd; + + if (copy_from_user(&ebuf, argp, sizeof(ebuf))) + return -EFAULT; + + /* + * We need a reference on the gem object. This will be released by + * drm_gem_dmabuf_release when the file descriptor is closed. + */ + drm_gem_object_reference(&obj->base); + + dma_buf = drm_gem_prime_export(helper->dev, &obj->base, ebuf.flags | O_RDWR); + if (!dma_buf) { + dev_info(info->dev, "Failed to export DMA buffer\n"); + goto err_export; + } + + fd = dma_buf_fd(dma_buf, O_CLOEXEC); + if (fd < 0) { + dev_info(info->dev, "Failed to get file descriptor for DMA buffer\n"); + goto err_export_fd; + } + ebuf.fd = fd; + + if (copy_to_user(argp, &ebuf, sizeof(ebuf))) + goto err_export_fd; + + return 0; + +err_export_fd: + dma_buf_put(dma_buf); +err_export: + drm_gem_object_unreference(&obj->base); + return -EFAULT; +} + +static int hdlcd_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case FBIOGET_DMABUF: + return hdlcd_get_dmabuf_ioctl(info, cmd, arg); + case FBIO_WAITFORVSYNC: + return 0; /* Nothing to do as we wait when page flipping anyway */ + default: + printk(KERN_INFO "HDLCD FB does not handle ioctl 0x%x\n", cmd); + } + + return -EFAULT; +} |