diff options
Diffstat (limited to 'drivers/gpu/drm/exynos/exynos_drm_fbdev.c')
| -rw-r--r-- | drivers/gpu/drm/exynos/exynos_drm_fbdev.c | 184 |
1 files changed, 117 insertions, 67 deletions
diff --git a/drivers/gpu/drm/exynos/exynos_drm_fbdev.c b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c index 67eb6ba56ed..d771b467cf0 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fbdev.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c @@ -6,34 +6,23 @@ * Joonyoung Shim <jy0922.shim@samsung.com> * Seung-Woo Kim <sw0312.kim@samsung.com> * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR - * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. + * 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. */ #include <drm/drmP.h> #include <drm/drm_crtc.h> #include <drm/drm_fb_helper.h> #include <drm/drm_crtc_helper.h> +#include <drm/exynos_drm.h> #include "exynos_drm_drv.h" #include "exynos_drm_fb.h" +#include "exynos_drm_fbdev.h" #include "exynos_drm_gem.h" +#include "exynos_drm_iommu.h" #define MAX_CONNECTOR 4 #define PREFERRED_BPP 32 @@ -46,8 +35,36 @@ struct exynos_drm_fbdev { struct exynos_drm_gem_obj *exynos_gem_obj; }; +static int exynos_drm_fb_mmap(struct fb_info *info, + struct vm_area_struct *vma) +{ + struct drm_fb_helper *helper = info->par; + struct exynos_drm_fbdev *exynos_fbd = to_exynos_fbdev(helper); + struct exynos_drm_gem_obj *exynos_gem_obj = exynos_fbd->exynos_gem_obj; + struct exynos_drm_gem_buf *buffer = exynos_gem_obj->buffer; + unsigned long vm_size; + int ret; + + vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP; + + vm_size = vma->vm_end - vma->vm_start; + + if (vm_size > buffer->size) + return -EINVAL; + + ret = dma_mmap_attrs(helper->dev->dev, vma, buffer->pages, + buffer->dma_addr, buffer->size, &buffer->dma_attrs); + if (ret < 0) { + DRM_ERROR("failed to mmap.\n"); + return ret; + } + + return 0; +} + static struct fb_ops exynos_drm_fb_ops = { .owner = THIS_MODULE, + .fb_mmap = exynos_drm_fb_mmap, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, @@ -67,29 +84,45 @@ static int exynos_drm_fbdev_update(struct drm_fb_helper *helper, unsigned int size = fb->width * fb->height * (fb->bits_per_pixel >> 3); unsigned long offset; - DRM_DEBUG_KMS("%s\n", __FILE__); - drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); drm_fb_helper_fill_var(fbi, helper, fb->width, fb->height); /* RGB formats use only one buffer */ buffer = exynos_drm_fb_buffer(fb, 0); if (!buffer) { - DRM_LOG_KMS("buffer is null.\n"); + DRM_DEBUG_KMS("buffer is null.\n"); return -EFAULT; } + /* map pages with kernel virtual space. */ + if (!buffer->kvaddr) { + if (is_drm_iommu_supported(dev)) { + unsigned int nr_pages = buffer->size >> PAGE_SHIFT; + + buffer->kvaddr = (void __iomem *) vmap(buffer->pages, + nr_pages, VM_MAP, + pgprot_writecombine(PAGE_KERNEL)); + } else { + phys_addr_t dma_addr = buffer->dma_addr; + if (dma_addr) + buffer->kvaddr = (void __iomem *)phys_to_virt(dma_addr); + else + buffer->kvaddr = (void __iomem *)NULL; + } + if (!buffer->kvaddr) { + DRM_ERROR("failed to map pages to kernel space.\n"); + return -EIO; + } + } + /* buffer count to framebuffer always is 1 at booting time. */ exynos_drm_fb_set_buf_cnt(fb, 1); offset = fbi->var.xoffset * (fb->bits_per_pixel >> 3); offset += fbi->var.yoffset * fb->pitches[0]; - dev->mode_config.fb_base = (resource_size_t)buffer->dma_addr; fbi->screen_base = buffer->kvaddr + offset; - fbi->fix.smem_start = (unsigned long)(buffer->dma_addr + offset); fbi->screen_size = size; - fbi->fix.smem_len = size; return 0; } @@ -106,8 +139,6 @@ static int exynos_drm_fbdev_create(struct drm_fb_helper *helper, unsigned long size; int ret; - DRM_DEBUG_KMS("%s\n", __FILE__); - DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d\n", sizes->surface_width, sizes->surface_height, sizes->surface_bpp); @@ -129,21 +160,31 @@ static int exynos_drm_fbdev_create(struct drm_fb_helper *helper, size = mode_cmd.pitches[0] * mode_cmd.height; - /* 0 means to allocate physically continuous memory */ - exynos_gem_obj = exynos_drm_gem_create(dev, 0, size); + exynos_gem_obj = exynos_drm_gem_create(dev, EXYNOS_BO_CONTIG, size); + /* + * If physically contiguous memory allocation fails and if IOMMU is + * supported then try to get buffer from non physically contiguous + * memory area. + */ + if (IS_ERR(exynos_gem_obj) && is_drm_iommu_supported(dev)) { + dev_warn(&pdev->dev, "contiguous FB allocation failed, falling back to non-contiguous\n"); + exynos_gem_obj = exynos_drm_gem_create(dev, EXYNOS_BO_NONCONTIG, + size); + } + if (IS_ERR(exynos_gem_obj)) { ret = PTR_ERR(exynos_gem_obj); - goto out; + goto err_release_framebuffer; } exynos_fbdev->exynos_gem_obj = exynos_gem_obj; helper->fb = exynos_drm_framebuffer_init(dev, &mode_cmd, &exynos_gem_obj->base); - if (IS_ERR_OR_NULL(helper->fb)) { + if (IS_ERR(helper->fb)) { DRM_ERROR("failed to create drm framebuffer.\n"); ret = PTR_ERR(helper->fb); - goto out; + goto err_destroy_gem; } helper->fbdev = fbi; @@ -155,14 +196,24 @@ static int exynos_drm_fbdev_create(struct drm_fb_helper *helper, ret = fb_alloc_cmap(&fbi->cmap, 256, 0); if (ret) { DRM_ERROR("failed to allocate cmap.\n"); - goto out; + goto err_destroy_framebuffer; } ret = exynos_drm_fbdev_update(helper, helper->fb); - if (ret < 0) { - fb_dealloc_cmap(&fbi->cmap); - goto out; - } + if (ret < 0) + goto err_dealloc_cmap; + + mutex_unlock(&dev->struct_mutex); + return ret; + +err_dealloc_cmap: + fb_dealloc_cmap(&fbi->cmap); +err_destroy_framebuffer: + drm_framebuffer_cleanup(helper->fb); +err_destroy_gem: + exynos_drm_gem_destroy(exynos_gem_obj); +err_release_framebuffer: + framebuffer_release(fbi); /* * if failed, all resources allocated above would be released by @@ -174,38 +225,28 @@ out: return ret; } -static int exynos_drm_fbdev_probe(struct drm_fb_helper *helper, - struct drm_fb_helper_surface_size *sizes) +static struct drm_fb_helper_funcs exynos_drm_fb_helper_funcs = { + .fb_probe = exynos_drm_fbdev_create, +}; + +static bool exynos_drm_fbdev_is_anything_connected(struct drm_device *dev) { - int ret = 0; + struct drm_connector *connector; + bool ret = false; - DRM_DEBUG_KMS("%s\n", __FILE__); + mutex_lock(&dev->mode_config.mutex); + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->status != connector_status_connected) + continue; - /* - * with !helper->fb, it means that this funcion is called first time - * and after that, the helper->fb would be used as clone mode. - */ - if (!helper->fb) { - ret = exynos_drm_fbdev_create(helper, sizes); - if (ret < 0) { - DRM_ERROR("failed to create fbdev.\n"); - return ret; - } - - /* - * fb_helper expects a value more than 1 if succeed - * because register_framebuffer() should be called. - */ - ret = 1; + ret = true; + break; } + mutex_unlock(&dev->mode_config.mutex); return ret; } -static struct drm_fb_helper_funcs exynos_drm_fb_helper_funcs = { - .fb_probe = exynos_drm_fbdev_probe, -}; - int exynos_drm_fbdev_init(struct drm_device *dev) { struct exynos_drm_fbdev *fbdev; @@ -214,16 +255,15 @@ int exynos_drm_fbdev_init(struct drm_device *dev) unsigned int num_crtc; int ret; - DRM_DEBUG_KMS("%s\n", __FILE__); - if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector) return 0; + if (!exynos_drm_fbdev_is_anything_connected(dev)) + return 0; + fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); - if (!fbdev) { - DRM_ERROR("failed to allocate drm fbdev.\n"); + if (!fbdev) return -ENOMEM; - } private->fb_helper = helper = &fbdev->drm_fb_helper; helper->funcs = &exynos_drm_fb_helper_funcs; @@ -243,6 +283,9 @@ int exynos_drm_fbdev_init(struct drm_device *dev) } + /* 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) { DRM_ERROR("failed to set up hw configuration.\n"); @@ -264,13 +307,20 @@ err_init: static void exynos_drm_fbdev_destroy(struct drm_device *dev, struct drm_fb_helper *fb_helper) { + struct exynos_drm_fbdev *exynos_fbd = to_exynos_fbdev(fb_helper); + struct exynos_drm_gem_obj *exynos_gem_obj = exynos_fbd->exynos_gem_obj; struct drm_framebuffer *fb; + if (is_drm_iommu_supported(dev) && exynos_gem_obj->buffer->kvaddr) + vunmap(exynos_gem_obj->buffer->kvaddr); + /* release drm framebuffer and real buffer */ if (fb_helper->fb && fb_helper->fb->funcs) { fb = fb_helper->fb; - if (fb) + if (fb) { + drm_framebuffer_unregister_private(fb); drm_framebuffer_remove(fb); + } } /* release linux framebuffer */ @@ -317,5 +367,5 @@ void exynos_drm_fbdev_restore_mode(struct drm_device *dev) if (!private || !private->fb_helper) return; - drm_fb_helper_restore_fbdev_mode(private->fb_helper); + drm_fb_helper_restore_fbdev_mode_unlocked(private->fb_helper); } |
