From: Liviu Dudau Date: Mon, 30 Jun 2014 17:36:57 +0000 (+0100) Subject: drm: arm: Add support for PRIME and dma_buf buffer sharing. X-Git-Tag: submit/tizen/20141203.153721~88 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=d02f99cc9eae0da45123ec47bce6ae2fd460d8c1;p=platform%2Fkernel%2Flinux-arm64.git drm: arm: Add support for PRIME and dma_buf buffer sharing. Add support for buffer sharing in HDLCD and implement proprietary IOCTL from ARM Mali driver to get a file descriptor for the buffer. This is used by the Mali kernel driver to import the buffers into DDK and enable double buffering. Signed-off-by: Liviu Dudau --- diff --git a/drivers/gpu/drm/arm/Kconfig b/drivers/gpu/drm/arm/Kconfig index 8de5b511a96..acb046fec51 100644 --- a/drivers/gpu/drm/arm/Kconfig +++ b/drivers/gpu/drm/arm/Kconfig @@ -20,7 +20,7 @@ config DRM_HDLCD If M is selected the module will be called hdlcd. config DRM_VIRTUAL_HDLCD - bool "Support for virtual HDLCD" - depends on DRM_HDLCD - help - Enable support for virtual HDLCD as emulated by ARM's Fast Models. + bool "Support for virtual HDLCD" + depends on DRM_HDLCD + help + Enable support for virtual HDLCD as emulated by ARM's Fast Models. diff --git a/drivers/gpu/drm/arm/Makefile b/drivers/gpu/drm/arm/Makefile index 0f28e22dbdb..429644c28a2 100644 --- a/drivers/gpu/drm/arm/Makefile +++ b/drivers/gpu/drm/arm/Makefile @@ -1,4 +1,4 @@ -hdlcd-y := hdlcd_drv.o hdlcd_crtc.o hdlcd_hdmi_encoder.o hdlcd_vexpress_encoder.o +hdlcd-y := hdlcd_drv.o hdlcd_crtc.o hdlcd_hdmi_encoder.o hdlcd_vexpress_encoder.o hdlcd_fb.o hdlcd-$(CONFIG_DRM_VIRTUAL_HDLCD) += hdlcd_virt_encoder.o obj-$(CONFIG_DRM_HDLCD) += hdlcd.o diff --git a/drivers/gpu/drm/arm/hdlcd_crtc.c b/drivers/gpu/drm/arm/hdlcd_crtc.c index 91ed56c59bd..39cf6c0deec 100644 --- a/drivers/gpu/drm/arm/hdlcd_crtc.c +++ b/drivers/gpu/drm/arm/hdlcd_crtc.c @@ -34,17 +34,20 @@ static void hdlcd_crtc_destroy(struct drm_crtc *crtc) void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd) { struct drm_framebuffer *fb = hdlcd->crtc.primary->fb; - struct drm_gem_cma_object *gem; + struct hdlcd_bo *bo; unsigned int depth, bpp; dma_addr_t scanout_start; drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp); - gem = drm_fb_cma_get_gem_obj(fb, 0); + bo = hdlcd->bo; - scanout_start = gem->paddr + fb->offsets[0] + + scanout_start = bo->dma_addr + fb->offsets[0] + (hdlcd->crtc.y * fb->pitches[0]) + (hdlcd->crtc.x * bpp/8); - hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start); + if (scanout_start != hdlcd->scanout_buf) { + hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start); + hdlcd->scanout_buf = scanout_start; + } } static int hdlcd_crtc_page_flip(struct drm_crtc *crtc, @@ -105,6 +108,7 @@ static bool hdlcd_crtc_mode_fixup(struct drm_crtc *crtc, static void hdlcd_crtc_prepare(struct drm_crtc *crtc) { + drm_vblank_pre_modeset(crtc->dev, 0); hdlcd_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); } @@ -114,6 +118,20 @@ static void hdlcd_crtc_commit(struct drm_crtc *crtc) hdlcd_crtc_dpms(crtc, DRM_MODE_DPMS_ON); } +static bool hdlcd_fb_mode_equal(struct drm_framebuffer *oldfb, + struct drm_framebuffer *newfb) +{ + if (!oldfb || !newfb) + return false; + + if (oldfb->pixel_format == newfb->pixel_format && + oldfb->width == newfb->width && + oldfb->height == newfb->height) + return true; + + return false; +} + static int hdlcd_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode, @@ -128,7 +146,12 @@ static int hdlcd_crtc_mode_set(struct drm_crtc *crtc, default_color = 0x00ff000000; #endif - drm_vblank_pre_modeset(crtc->dev, 0); + /* This function gets called when the only change is the start of + the scanout buffer. Detect that and bail out early */ + if (hdlcd->initialised && hdlcd_fb_mode_equal(oldfb, crtc->primary->fb)) { + hdlcd_set_scanout(hdlcd); + return 0; + } /* Preset the number of bits per colour */ drm_fb_get_bpp_depth(crtc->primary->fb->pixel_format, &depth, &bpp); @@ -204,7 +227,17 @@ static int hdlcd_crtc_mode_set(struct drm_crtc *crtc, clk_enable(hdlcd->clk); hdlcd_set_scanout(hdlcd); + hdlcd->initialised = true; + + return 0; +} + +int hdlcd_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *oldfb) +{ + struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); + hdlcd_set_scanout(hdlcd); return 0; } @@ -218,33 +251,17 @@ static const struct drm_crtc_helper_funcs hdlcd_crtc_helper_funcs = { .prepare = hdlcd_crtc_prepare, .commit = hdlcd_crtc_commit, .mode_set = hdlcd_crtc_mode_set, + .mode_set_base = hdlcd_crtc_mode_set_base, .load_lut = hdlcd_crtc_load_lut, }; -static void hdlcd_fb_output_poll_changed(struct drm_device *dev) -{ - struct hdlcd_drm_private *hdlcd = dev->dev_private; - if (hdlcd->fbdev) - drm_fbdev_cma_hotplug_event(hdlcd->fbdev); -} - -static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = { - .fb_create = drm_fb_cma_create, - .output_poll_changed = hdlcd_fb_output_poll_changed, -}; - int hdlcd_setup_crtc(struct drm_device *dev) { struct hdlcd_drm_private *hdlcd = dev->dev_private; int ret; drm_mode_config_init(dev); - - dev->mode_config.min_width = 0; - dev->mode_config.min_height = 0; - dev->mode_config.max_width = HDLCD_MAX_XRES; - dev->mode_config.max_height = HDLCD_MAX_YRES; - dev->mode_config.funcs = &hdlcd_mode_config_funcs; + hdlcd_drm_mode_config_init(dev); ret = drm_crtc_init(dev, &hdlcd->crtc, &hdlcd_crtc_funcs); if (ret < 0) diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c index 08341ef66e5..62f34a01be5 100644 --- a/drivers/gpu/drm/arm/hdlcd_drv.c +++ b/drivers/gpu/drm/arm/hdlcd_drv.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -29,8 +30,8 @@ static int hdlcd_unload(struct drm_device *dev) struct hdlcd_drm_private *hdlcd = dev->dev_private; drm_kms_helper_poll_fini(dev); - if (hdlcd->fbdev) - drm_fbdev_cma_fini(hdlcd->fbdev); + if (hdlcd->fb_helper) + drm_fb_helper_fini(hdlcd->fb_helper); drm_vblank_cleanup(dev); drm_mode_config_cleanup(dev); @@ -72,6 +73,7 @@ static int hdlcd_load(struct drm_device *dev, unsigned long flags) atomic_set(&hdlcd->vsync_count, 0); atomic_set(&hdlcd->dma_end_count, 0); #endif + hdlcd->initialised = false; dev->dev_private = hdlcd; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -123,21 +125,29 @@ static int hdlcd_load(struct drm_device *dev, unsigned long flags) */ if (hdlcd->slave_node) { ret = hdlcd_create_digital_connector(dev, hdlcd); - if (ret < 0) + if (ret < 0) { + dev_err(dev->dev, "failed to create digital connector, trying board setup: %d\n", ret); ret = hdlcd_create_vexpress_connector(dev, hdlcd); + } if (ret < 0) { - dev_err(dev->dev, "failed to create digital connector\n"); + dev_err(dev->dev, "failed to create board connector: %d\n", ret); goto fail; } } else { ret = hdlcd_create_virtual_connector(dev); if (ret < 0) { - dev_err(dev->dev, "failed to create virtual connector\n"); + dev_err(dev->dev, "failed to create virtual connector: %d\n", ret); goto fail; } } + ret = hdlcd_fbdev_init(dev); + if (ret < 0) { + dev_err(dev->dev, "failed to init the framebuffer (%d)\n", ret); + goto fail; + } + platform_set_drvdata(pdev, dev); ret = drm_irq_install(dev); @@ -146,6 +156,7 @@ static int hdlcd_load(struct drm_device *dev, unsigned long flags) goto fail; } + init_completion(&hdlcd->vsync_completion); ret = drm_vblank_init(dev, 1); if (ret < 0) { dev_err(dev->dev, "failed to initialise vblank\n"); @@ -154,8 +165,6 @@ static int hdlcd_load(struct drm_device *dev, unsigned long flags) dev_info(dev->dev, "initialised vblank\n"); } - hdlcd->fbdev = drm_fbdev_cma_init(dev, 32, dev->mode_config.num_crtc, - dev->mode_config.num_connector); drm_kms_helper_poll_init(dev); return 0; @@ -172,7 +181,11 @@ static void hdlcd_preclose(struct drm_device *dev, struct drm_file *file) static void hdlcd_lastclose(struct drm_device *dev) { struct hdlcd_drm_private *hdlcd = dev->dev_private; - drm_fbdev_cma_restore_mode(hdlcd->fbdev); + + drm_modeset_lock_all(dev); + if (hdlcd->fb_helper) + drm_fb_helper_restore_fbdev_mode(hdlcd->fb_helper); + drm_modeset_unlock_all(dev); } static irqreturn_t hdlcd_irq(int irq, void *arg) @@ -213,6 +226,7 @@ static irqreturn_t hdlcd_irq(int irq, void *arg) drm_vblank_put(dev, 0); } spin_unlock_irqrestore(&dev->event_lock, flags); + complete(&hdlcd->vsync_completion); } /* acknowledge interrupt(s) */ @@ -309,7 +323,7 @@ static void hdlcd_debugfs_cleanup(struct drm_minor *minor) } #endif -static const struct file_operations fops = { +static const struct file_operations hdlcd_fops = { .owner = THIS_MODULE, .open = drm_open, .release = drm_release, @@ -317,10 +331,10 @@ static const struct file_operations fops = { #ifdef CONFIG_COMPAT .compat_ioctl = drm_compat_ioctl, #endif + .mmap = drm_gem_cma_mmap, .poll = drm_poll, .read = drm_read, .llseek = no_llseek, - .mmap = drm_gem_cma_mmap, }; static struct drm_driver hdlcd_driver = { @@ -337,8 +351,10 @@ static struct drm_driver hdlcd_driver = { .get_vblank_counter = drm_vblank_count, .enable_vblank = hdlcd_enable_vblank, .disable_vblank = hdlcd_disable_vblank, + .gem_free_object = drm_gem_cma_free_object, .gem_vm_ops = &drm_gem_cma_vm_ops, + .dumb_create = drm_gem_cma_dumb_create, .dumb_map_offset = drm_gem_cma_dumb_map_offset, .dumb_destroy = drm_gem_dumb_destroy, @@ -354,7 +370,8 @@ static struct drm_driver hdlcd_driver = { .debugfs_init = hdlcd_debugfs_init, .debugfs_cleanup = hdlcd_debugfs_cleanup, #endif - .fops = &fops, + .fops = &hdlcd_fops, + .name = "hdlcd", .desc = "ARM HDLCD Controller DRM", .date = "20130505", @@ -392,21 +409,11 @@ static struct platform_driver hdlcd_platform_driver = { static int __init hdlcd_init(void) { - int err = platform_driver_register(&hdlcd_platform_driver); - -#ifdef HDLCD_COUNT_BUFFERUNDERRUNS - if (!err) - hdlcd_underrun_init(); -#endif - - return err; + return platform_driver_register(&hdlcd_platform_driver); } static void __exit hdlcd_exit(void) { -#ifdef HDLCD_COUNT_BUFFERUNDERRUNS - hdlcd_underrun_close(); -#endif platform_driver_unregister(&hdlcd_platform_driver); } diff --git a/drivers/gpu/drm/arm/hdlcd_drv.h b/drivers/gpu/drm/arm/hdlcd_drv.h index 408e982ccba..cd2923a335c 100644 --- a/drivers/gpu/drm/arm/hdlcd_drv.h +++ b/drivers/gpu/drm/arm/hdlcd_drv.h @@ -5,14 +5,22 @@ #ifndef __HDLCD_DRV_H__ #define __HDLCD_DRV_H__ +struct hdlcd_bo { + struct drm_gem_object gem; + dma_addr_t dma_addr; + void *cpu_addr; +}; + struct hdlcd_drm_private { void __iomem *mmio; struct clk *clk; - struct drm_fbdev_cma *fbdev; - struct drm_framebuffer *fb; + struct drm_fb_helper *fb_helper; + struct hdlcd_bo *bo; + dma_addr_t scanout_buf; struct drm_pending_vblank_event *event; struct drm_crtc crtc; struct device_node *slave_node; + struct completion vsync_completion; #ifdef CONFIG_DEBUG_FS atomic_t buffer_underrun_count; atomic_t bus_error_count; @@ -20,8 +28,10 @@ struct hdlcd_drm_private { atomic_t dma_end_count; #endif int dpms; + bool initialised; }; +#define to_hdlcd_bo_obj(x) container_of(x, struct hdlcd_bo, gem) #define crtc_to_hdlcd_priv(x) container_of(x, struct hdlcd_drm_private, crtc) static inline void @@ -64,6 +74,8 @@ static inline int hdlcd_create_virtual_connector(struct drm_device *dev) #endif void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd); +void hdlcd_drm_mode_config_init(struct drm_device *dev); +int hdlcd_fbdev_init(struct drm_device *dev); /* common function used by all connectors */ extern struct drm_encoder *hdlcd_connector_best_encoder(struct drm_connector *con); diff --git a/drivers/gpu/drm/arm/hdlcd_fb.c b/drivers/gpu/drm/arm/hdlcd_fb.c new file mode 100644 index 00000000000..cae947a8726 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_fb.c @@ -0,0 +1,675 @@ +/* + * Copyright (C) 2013,2014 ARM Limited + * Author: Liviu Dudau + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * Implementation of the DRM fbdev compatibility mode for the HDLCD driver. + * Mainly used for doing dumb double buffering as expected by the ARM Mali driver. + */ + +#include +#include +#include +#include + +#include "hdlcd_drv.h" +#include "hdlcd_regs.h" + +#define MAX_FRAMES 2 + +#define to_hdlcd_fb(x) container_of(x, struct hdlcd_fb, fb) + +struct hdlcd_dmabuf_attachment { + struct sg_table *sgt; + enum dma_data_direction dir; +}; + +static struct drm_framebuffer *hdlcd_fb_alloc(struct drm_device *dev, + struct drm_mode_fb_cmd2 *mode_cmd); + +static int hdlcd_dmabuf_attach(struct dma_buf *dma_buf, + struct device *target_dev, struct dma_buf_attachment *attach) +{ + struct hdlcd_dmabuf_attachment *hdlcd_attach; + + hdlcd_attach = kzalloc(sizeof(*hdlcd_attach), GFP_KERNEL); + if (!hdlcd_attach) + return -ENOMEM; + + hdlcd_attach->dir = DMA_NONE; + attach->priv = hdlcd_attach; + + return 0; +} + +static void hdlcd_dmabuf_detach(struct dma_buf *dma_buf, + struct dma_buf_attachment *attach) +{ + struct hdlcd_dmabuf_attachment *hdlcd_attach = attach->priv; + struct sg_table *sgt; + + if (!hdlcd_attach) + return; + + sgt = hdlcd_attach->sgt; + if (sgt) { + sg_free_table(sgt); + } + + kfree(sgt); + kfree(hdlcd_attach); + attach->priv = NULL; +} + +static void hdlcd_dmabuf_release(struct dma_buf *dma_buf) +{ + struct hdlcd_drm_private *hdlcd = dma_buf->priv; + struct drm_gem_object *obj = &hdlcd->bo->gem; + + drm_gem_object_unreference_unlocked(obj); +} + +static struct sg_table *hdlcd_map_dma_buf(struct dma_buf_attachment *attach, + enum dma_data_direction dir) +{ + struct hdlcd_dmabuf_attachment *hdlcd_attach = attach->priv; + struct hdlcd_drm_private *hdlcd = attach->dmabuf->priv; + struct sg_table *sgt; + int size, ret; + + if (dir == DMA_NONE || !hdlcd_attach) + return ERR_PTR(-EINVAL); + + /* return the cached mapping when possible */ + if (hdlcd_attach->dir == dir) + return hdlcd_attach->sgt; + + /* don't allow two different directions for the same attachment */ + if (hdlcd_attach->dir != DMA_NONE) + return ERR_PTR(-EBUSY); + + size = hdlcd->bo->gem.size; + + sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); + if (!sgt) { + DRM_ERROR("Failed to allocate sg_table\n"); + return ERR_PTR(-ENOMEM); + } + + ret = sg_alloc_table(sgt, 1, GFP_KERNEL); + if (ret < 0) { + DRM_ERROR("Failed to allocate page table\n"); + kfree(sgt); + return ERR_PTR(-ENOMEM); + } + + sg_dma_len(sgt->sgl) = size; + sg_set_page(sgt->sgl, pfn_to_page(PFN_DOWN(hdlcd->bo->dma_addr)), size, 0); + sg_dma_address(sgt->sgl) = hdlcd->bo->dma_addr; + ret = dma_map_sg(attach->dev, sgt->sgl, sgt->orig_nents, dir); + if (!ret) { + DRM_ERROR("failed to map sgl with IOMMU\n"); + sg_free_table(sgt); + kfree(sgt); + return ERR_PTR(-EIO); + } + hdlcd_attach->sgt = sgt; + hdlcd_attach->dir = dir; + + return sgt; +} + +static void hdlcd_unmap_dma_buf(struct dma_buf_attachment *attach, + struct sg_table *sgt, enum dma_data_direction dir) +{ + struct hdlcd_dmabuf_attachment *hdlcd_attach = attach->priv; + + if (hdlcd_attach->dir != DMA_NONE) + dma_unmap_sg(attach->dev, sgt->sgl, sgt->nents, dir); + sg_free_table(sgt); + kfree(sgt); + hdlcd_attach->sgt = NULL; + hdlcd_attach->dir = DMA_NONE; +} + +static void *hdlcd_dmabuf_kmap(struct dma_buf *dma_buf, unsigned long page_num) +{ + return NULL; +} + +static void hdlcd_dmabuf_kunmap(struct dma_buf *dma_buf, + unsigned long page_num, void *addr) +{ +} + +static int hdlcd_dmabuf_mmap(struct dma_buf *dma_buf, struct vm_area_struct *vma) +{ + struct hdlcd_drm_private *hdlcd = dma_buf->priv; + struct drm_gem_object *obj = &hdlcd->bo->gem; + struct hdlcd_bo *bo = to_hdlcd_bo_obj(obj); + int ret; + + mutex_lock(&obj->dev->struct_mutex); + ret = drm_gem_mmap_obj(obj, obj->size, vma); + mutex_unlock(&obj->dev->struct_mutex); + if (ret < 0) + return ret; + + /* + * Clear the VM_PFNMAP flag that was set by drm_gem_mmap(), and set the + * vm_pgoff (used as a fake buffer offset by DRM) to 0 as we want to map + * the whole buffer. + */ + vma->vm_flags &= ~VM_PFNMAP; + vma->vm_pgoff = 0; + ret = dma_mmap_writecombine(obj->dev->dev, vma, bo->cpu_addr, bo->dma_addr, + vma->vm_end - vma->vm_start); + if (ret) + drm_gem_vm_close(vma); + + return 0; +} + +static void *hdlcd_dmabuf_vmap(struct dma_buf *dma_buf) +{ + return ERR_PTR(-EINVAL); +} + +static void hdlcd_dmabuf_vunmap(struct dma_buf *dma_buf, void *cpu_addr) +{ +} + +struct dma_buf_ops hdlcd_buf_ops = { + .attach = hdlcd_dmabuf_attach, + .detach = hdlcd_dmabuf_detach, + .map_dma_buf = hdlcd_map_dma_buf, + .unmap_dma_buf = hdlcd_unmap_dma_buf, + .release = hdlcd_dmabuf_release, + .kmap = hdlcd_dmabuf_kmap, + .kmap_atomic = hdlcd_dmabuf_kmap, + .kunmap = hdlcd_dmabuf_kunmap, + .kunmap_atomic = hdlcd_dmabuf_kunmap, + .mmap = hdlcd_dmabuf_mmap, + .vmap = hdlcd_dmabuf_vmap, + .vunmap = hdlcd_dmabuf_vunmap, +}; + +/* + * 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 dma_buf *dma_buf; + uint32_t fd; + + if (copy_from_user(&ebuf, argp, sizeof(ebuf))) + return -EFAULT; + + dma_buf = dma_buf_export(hdlcd, &hdlcd_buf_ops, + info->screen_size, 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: + return -EFAULT; +} + +static int hdlcd_wait_for_vsync(struct fb_info *info) +{ +#if 0 + struct drm_fb_helper *helper = info->par; + struct hdlcd_drm_private *hdlcd = helper->dev->dev_private; + int ret; + + drm_crtc_vblank_on(&hdlcd->crtc); + ret = wait_for_completion_interruptible_timeout(&hdlcd->vsync_completion, + msecs_to_jiffies(100)); + drm_crtc_vblank_off(&hdlcd->crtc); + if (ret) + return -ETIMEDOUT; +#endif + + return 0; +} + +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 hdlcd_wait_for_vsync(info); + default: + printk(KERN_INFO "HDLCD FB does not handle ioctl 0x%x\n", cmd); + } + + return -EFAULT; +} + +static int hdlcd_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct drm_fb_helper *helper = info->par; + struct hdlcd_drm_private *hdlcd = helper->dev->dev_private; + struct drm_gem_object *obj = &hdlcd->bo->gem; + struct hdlcd_bo *bo = to_hdlcd_bo_obj(obj); + DEFINE_DMA_ATTRS(attrs); + size_t vm_size; + + dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs); + + vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP; + vm_size = vma->vm_end - vma->vm_start; + + if (vm_size > bo->gem.size) + return -EINVAL; + + return dma_mmap_attrs(helper->dev->dev, vma, bo->cpu_addr, + bo->dma_addr, bo->gem.size, &attrs); +} + +static int hdlcd_fb_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; +} + +static struct fb_ops hdlcd_fb_ops = { + .owner = THIS_MODULE, + .fb_fillrect = sys_fillrect, + .fb_copyarea = sys_copyarea, + .fb_imageblit = sys_imageblit, + .fb_check_var = hdlcd_fb_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_mmap = hdlcd_fb_mmap, +}; + +static struct hdlcd_bo *hdlcd_fb_bo_create(struct drm_device *drm, size_t size) +{ + int err; + struct hdlcd_bo *bo; + struct hdlcd_drm_private *hdlcd = drm->dev_private; + DEFINE_DMA_ATTRS(attrs); + + bo = kzalloc(sizeof(*bo), GFP_KERNEL); + if (!bo) + return ERR_PTR(-ENOMEM); + + size = round_up(size, PAGE_SIZE); + err = drm_gem_object_init(drm, &bo->gem, size); + if (err) + goto err_init; + + dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs); + bo->cpu_addr = dma_alloc_attrs(drm->dev, size, &bo->dma_addr, + GFP_KERNEL | __GFP_NOWARN, &attrs); + + if (!bo->cpu_addr) { + dev_err(drm->dev, "failed to allocate buffer of size %zu\n", size); + err = -ENOMEM; + goto err_dma; + } + +#if 0 + err = drm_gem_create_mmap_offset(&bo->gem); + if (err) + goto err_map; +#endif + + hdlcd->bo = bo; + return bo; + +#if 0 +err_map: + dma_free_attrs(drm->dev, size, bo->cpu_addr, bo->dma_addr, &attrs); +#endif +err_dma: + drm_gem_object_release(&bo->gem); +err_init: + kfree(bo); + + return ERR_PTR(err); +} + +void hdlcd_fb_bo_free(struct hdlcd_bo *bo) +{ + DEFINE_DMA_ATTRS(attrs); + dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs); + + drm_gem_object_release(&bo->gem); + dma_free_attrs(bo->gem.dev->dev, bo->gem.size, bo->cpu_addr, bo->dma_addr, &attrs); + kfree(bo); +} + +static int hdlcd_fb_probe(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct hdlcd_drm_private *hdlcd = helper->dev->dev_private; + struct fb_info *info; + struct hdlcd_bo *bo = hdlcd->bo; + struct drm_mode_fb_cmd2 cmd = { 0 }; + unsigned int bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8); + unsigned long offset; + size_t size; + int err; + + cmd.width = sizes->surface_width; + cmd.height = sizes->surface_height; + cmd.pitches[0] = sizes->surface_width * bytes_per_pixel; + cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); + + size = cmd.pitches[0] * cmd.height * MAX_FRAMES; + + bo = hdlcd_fb_bo_create(helper->dev, size); + if (IS_ERR(bo)) + return PTR_ERR(bo); + + info = framebuffer_alloc(0, helper->dev->dev); + if (!info) { + dev_err(helper->dev->dev, "failed to allocate framebuffer info\n"); + hdlcd_fb_bo_free(bo); + return -ENOMEM; + } + + helper->fb = hdlcd_fb_alloc(helper->dev, &cmd); + helper->fbdev = info; + + info->par = helper; + info->flags = FBINFO_FLAG_DEFAULT; + info->fbops = &hdlcd_fb_ops; + + err = fb_alloc_cmap(&info->cmap, 256, 0); + if (err < 0) { + dev_err(helper->dev->dev, "failed to allocate color map: %d\n", err); + goto cleanup; + } + + drm_fb_helper_fill_fix(info, helper->fb->pitches[0], helper->fb->depth); + drm_fb_helper_fill_var(info, helper, helper->fb->width, sizes->surface_height); + + offset = info->var.xoffset * bytes_per_pixel + + info->var.yoffset * helper->fb->pitches[0]; + + helper->dev->mode_config.fb_base = (resource_size_t)bo->dma_addr; + info->screen_base = (void __iomem *)bo->cpu_addr + offset; + info->screen_size = size; + info->var.yres_virtual = info->var.yres * MAX_FRAMES; + info->fix.smem_start = (unsigned long)bo->dma_addr + offset; + info->fix.smem_len = size; + + return 0; + +cleanup: + drm_framebuffer_unregister_private(helper->fb); + hdlcd_fb_bo_free(bo); + framebuffer_release(info); + return err; +} + +static struct drm_fb_helper_funcs hdlcd_fb_helper_funcs = { + .fb_probe = hdlcd_fb_probe, +}; + +static void hdlcd_fb_destroy(struct drm_framebuffer *fb) +{ + struct hdlcd_drm_private *hdlcd = fb->dev->dev_private; + drm_gem_object_unreference_unlocked(&hdlcd->bo->gem); + drm_framebuffer_cleanup(fb); + kfree(hdlcd->bo); +} + +static int hdlcd_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, + unsigned int *handle) +{ + struct hdlcd_drm_private *hdlcd = fb->dev->dev_private; + return drm_gem_handle_create(file_priv, &hdlcd->bo->gem, handle); +} + +static int hdlcd_fb_dirty(struct drm_framebuffer *fb, + struct drm_file *file_priv, unsigned flags, + unsigned color, struct drm_clip_rect *clips, + unsigned num_clips) +{ + return 0; +} + +static struct drm_framebuffer_funcs hdlcd_fb_funcs = { + .destroy = hdlcd_fb_destroy, + .create_handle = hdlcd_fb_create_handle, + .dirty = hdlcd_fb_dirty, +}; + +static struct drm_framebuffer *hdlcd_fb_alloc(struct drm_device *dev, + struct drm_mode_fb_cmd2 *mode_cmd) +{ + int err; + struct drm_framebuffer *fb; + + dev_info(dev->dev, "Linux is here %s", __func__); + fb = kzalloc(sizeof(*fb), GFP_KERNEL); + if (!fb) + return ERR_PTR(-ENOMEM); + + drm_helper_mode_fill_fb_struct(fb, mode_cmd); + + err = drm_framebuffer_init(dev, fb, &hdlcd_fb_funcs); + if (err) { + dev_err(dev->dev, "failed to initialize framebuffer.\n"); + kfree(fb); + return ERR_PTR(err); + } + + return fb; +} + +static struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev, + struct drm_file *file_priv, + struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct drm_gem_object *obj; + struct hdlcd_bo *bo; + struct hdlcd_drm_private *hdlcd = dev->dev_private; + + obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[0]); + if (!obj) { + dev_err(dev->dev, "failed to lookup GEM object\n"); + return ERR_PTR(-ENXIO); + } + + bo = to_hdlcd_bo_obj(obj); + hdlcd->fb_helper->fb = hdlcd_fb_alloc(dev, mode_cmd); + if (IS_ERR(hdlcd->fb_helper->fb)) { + dev_err(dev->dev, "failed to allocate DRM framebuffer\n"); + } + hdlcd->bo = bo; + + return hdlcd->fb_helper->fb; +} + +int hdlcd_fbdev_init(struct drm_device *dev) +{ + int ret; + struct hdlcd_drm_private *hdlcd = dev->dev_private; + + hdlcd->fb_helper = kzalloc(sizeof(*hdlcd->fb_helper), GFP_KERNEL); + if (!hdlcd->fb_helper) + return -ENOMEM; + + hdlcd->fb_helper->funcs = &hdlcd_fb_helper_funcs; + ret = drm_fb_helper_init(dev, hdlcd->fb_helper, dev->mode_config.num_crtc, + dev->mode_config.num_connector); + 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(hdlcd->fb_helper); + if (ret < 0) { + dev_err(dev->dev, "Failed to add connectors.\n"); + goto err_helper_fini; + } + + drm_helper_disable_unused_functions(dev); + + /* disable all the possible outputs/crtcs before entering KMS mode */ + ret = drm_fb_helper_initial_config(hdlcd->fb_helper, 32); + if (ret < 0) { + dev_err(dev->dev, "Failed to set initial hw configuration.\n"); + goto err_helper_fini; + } + + return 0; + +err_helper_fini: + drm_fb_helper_fini(hdlcd->fb_helper); + +err_free: + kfree(hdlcd->fb_helper); + hdlcd->fb_helper = NULL; + + return ret; +} + +static void hdlcd_fb_output_poll_changed(struct drm_device *dev) +{ + struct hdlcd_drm_private *hdlcd = dev->dev_private; + struct drm_fb_helper *fb_helper = hdlcd->fb_helper; + + drm_fb_helper_hotplug_event(fb_helper); +} + +static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = { + .fb_create = hdlcd_fb_create, + .output_poll_changed = hdlcd_fb_output_poll_changed, +}; + + +void hdlcd_drm_mode_config_init(struct drm_device *dev) +{ + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + dev->mode_config.max_width = HDLCD_MAX_XRES; + dev->mode_config.max_height = HDLCD_MAX_YRES; + dev->mode_config.funcs = &hdlcd_mode_config_funcs; +}