11c248b7dSInki Dae /* exynos_drm_fbdev.c 21c248b7dSInki Dae * 31c248b7dSInki Dae * Copyright (c) 2011 Samsung Electronics Co., Ltd. 41c248b7dSInki Dae * Authors: 51c248b7dSInki Dae * Inki Dae <inki.dae@samsung.com> 61c248b7dSInki Dae * Joonyoung Shim <jy0922.shim@samsung.com> 71c248b7dSInki Dae * Seung-Woo Kim <sw0312.kim@samsung.com> 81c248b7dSInki Dae * 9d81aecb5SInki Dae * This program is free software; you can redistribute it and/or modify it 10d81aecb5SInki Dae * under the terms of the GNU General Public License as published by the 11d81aecb5SInki Dae * Free Software Foundation; either version 2 of the License, or (at your 12d81aecb5SInki Dae * option) any later version. 131c248b7dSInki Dae */ 141c248b7dSInki Dae 15760285e7SDavid Howells #include <drm/drmP.h> 16760285e7SDavid Howells #include <drm/drm_crtc.h> 17760285e7SDavid Howells #include <drm/drm_fb_helper.h> 18760285e7SDavid Howells #include <drm/drm_crtc_helper.h> 19a1bfacf4SVikas Sajjan #include <drm/exynos_drm.h> 201c248b7dSInki Dae 211c248b7dSInki Dae #include "exynos_drm_drv.h" 221c248b7dSInki Dae #include "exynos_drm_fb.h" 23e30655d0SMark Brown #include "exynos_drm_fbdev.h" 242c871127SInki Dae #include "exynos_drm_gem.h" 25c704f1b4SInki Dae #include "exynos_drm_iommu.h" 261c248b7dSInki Dae 271c248b7dSInki Dae #define MAX_CONNECTOR 4 281c248b7dSInki Dae #define PREFERRED_BPP 32 291c248b7dSInki Dae 301c248b7dSInki Dae #define to_exynos_fbdev(x) container_of(x, struct exynos_drm_fbdev,\ 311c248b7dSInki Dae drm_fb_helper) 321c248b7dSInki Dae 331c248b7dSInki Dae struct exynos_drm_fbdev { 341c248b7dSInki Dae struct drm_fb_helper drm_fb_helper; 35e1533c08SJoonyoung Shim struct exynos_drm_gem_obj *exynos_gem_obj; 361c248b7dSInki Dae }; 371c248b7dSInki Dae 38dd265850SPrathyush K static int exynos_drm_fb_mmap(struct fb_info *info, 39dd265850SPrathyush K struct vm_area_struct *vma) 40dd265850SPrathyush K { 41dd265850SPrathyush K struct drm_fb_helper *helper = info->par; 42dd265850SPrathyush K struct exynos_drm_fbdev *exynos_fbd = to_exynos_fbdev(helper); 43dd265850SPrathyush K struct exynos_drm_gem_obj *exynos_gem_obj = exynos_fbd->exynos_gem_obj; 44dd265850SPrathyush K struct exynos_drm_gem_buf *buffer = exynos_gem_obj->buffer; 45dd265850SPrathyush K unsigned long vm_size; 46dd265850SPrathyush K int ret; 47dd265850SPrathyush K 48dd265850SPrathyush K vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP; 49dd265850SPrathyush K 50dd265850SPrathyush K vm_size = vma->vm_end - vma->vm_start; 51dd265850SPrathyush K 52dd265850SPrathyush K if (vm_size > buffer->size) 53dd265850SPrathyush K return -EINVAL; 54dd265850SPrathyush K 554744ad24SInki Dae ret = dma_mmap_attrs(helper->dev->dev, vma, buffer->pages, 56dd265850SPrathyush K buffer->dma_addr, buffer->size, &buffer->dma_attrs); 57dd265850SPrathyush K if (ret < 0) { 58dd265850SPrathyush K DRM_ERROR("failed to mmap.\n"); 59dd265850SPrathyush K return ret; 60dd265850SPrathyush K } 61dd265850SPrathyush K 62dd265850SPrathyush K return 0; 63dd265850SPrathyush K } 64dd265850SPrathyush K 651c248b7dSInki Dae static struct fb_ops exynos_drm_fb_ops = { 661c248b7dSInki Dae .owner = THIS_MODULE, 67dd265850SPrathyush K .fb_mmap = exynos_drm_fb_mmap, 681c248b7dSInki Dae .fb_fillrect = cfb_fillrect, 691c248b7dSInki Dae .fb_copyarea = cfb_copyarea, 701c248b7dSInki Dae .fb_imageblit = cfb_imageblit, 711c248b7dSInki Dae .fb_check_var = drm_fb_helper_check_var, 7283b316fdSSascha Hauer .fb_set_par = drm_fb_helper_set_par, 731c248b7dSInki Dae .fb_blank = drm_fb_helper_blank, 741c248b7dSInki Dae .fb_pan_display = drm_fb_helper_pan_display, 751c248b7dSInki Dae .fb_setcmap = drm_fb_helper_setcmap, 761c248b7dSInki Dae }; 771c248b7dSInki Dae 7819c8b834SInki Dae static int exynos_drm_fbdev_update(struct drm_fb_helper *helper, 79aa6b2b6cSSeung-Woo Kim struct drm_framebuffer *fb) 801c248b7dSInki Dae { 811c248b7dSInki Dae struct fb_info *fbi = helper->fbdev; 821c248b7dSInki Dae struct drm_device *dev = helper->dev; 832c871127SInki Dae struct exynos_drm_gem_buf *buffer; 84aa6b2b6cSSeung-Woo Kim unsigned int size = fb->width * fb->height * (fb->bits_per_pixel >> 3); 8519c8b834SInki Dae unsigned long offset; 861c248b7dSInki Dae 8701f2c773SVille Syrjälä drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); 88aa6b2b6cSSeung-Woo Kim drm_fb_helper_fill_var(fbi, helper, fb->width, fb->height); 891c248b7dSInki Dae 90229d3534SSeung-Woo Kim /* RGB formats use only one buffer */ 91229d3534SSeung-Woo Kim buffer = exynos_drm_fb_buffer(fb, 0); 922c871127SInki Dae if (!buffer) { 93133dcdebSLespiau, Damien DRM_DEBUG_KMS("buffer is null.\n"); 9419c8b834SInki Dae return -EFAULT; 9519c8b834SInki Dae } 961c248b7dSInki Dae 974744ad24SInki Dae /* map pages with kernel virtual space. */ 984744ad24SInki Dae if (!buffer->kvaddr) { 99c704f1b4SInki Dae if (is_drm_iommu_supported(dev)) { 1004744ad24SInki Dae unsigned int nr_pages = buffer->size >> PAGE_SHIFT; 101c704f1b4SInki Dae 102fafb3837SSachin Kamat buffer->kvaddr = (void __iomem *) vmap(buffer->pages, 103fafb3837SSachin Kamat nr_pages, VM_MAP, 1044744ad24SInki Dae pgprot_writecombine(PAGE_KERNEL)); 105c704f1b4SInki Dae } else { 106c704f1b4SInki Dae phys_addr_t dma_addr = buffer->dma_addr; 107c704f1b4SInki Dae if (dma_addr) 108fafb3837SSachin Kamat buffer->kvaddr = (void __iomem *)phys_to_virt(dma_addr); 109c704f1b4SInki Dae else 110c704f1b4SInki Dae buffer->kvaddr = (void __iomem *)NULL; 111c704f1b4SInki Dae } 1124744ad24SInki Dae if (!buffer->kvaddr) { 1134744ad24SInki Dae DRM_ERROR("failed to map pages to kernel space.\n"); 1144744ad24SInki Dae return -EIO; 1154744ad24SInki Dae } 1164744ad24SInki Dae } 1174744ad24SInki Dae 11801ed8126SInki Dae /* buffer count to framebuffer always is 1 at booting time. */ 11901ed8126SInki Dae exynos_drm_fb_set_buf_cnt(fb, 1); 12001ed8126SInki Dae 12119c8b834SInki Dae offset = fbi->var.xoffset * (fb->bits_per_pixel >> 3); 12201f2c773SVille Syrjälä offset += fbi->var.yoffset * fb->pitches[0]; 1231c248b7dSInki Dae 1242c871127SInki Dae fbi->screen_base = buffer->kvaddr + offset; 1251c248b7dSInki Dae fbi->screen_size = size; 12619c8b834SInki Dae 12719c8b834SInki Dae return 0; 1281c248b7dSInki Dae } 1291c248b7dSInki Dae 1301c248b7dSInki Dae static int exynos_drm_fbdev_create(struct drm_fb_helper *helper, 1311c248b7dSInki Dae struct drm_fb_helper_surface_size *sizes) 1321c248b7dSInki Dae { 1331c248b7dSInki Dae struct exynos_drm_fbdev *exynos_fbdev = to_exynos_fbdev(helper); 134e1533c08SJoonyoung Shim struct exynos_drm_gem_obj *exynos_gem_obj; 1351c248b7dSInki Dae struct drm_device *dev = helper->dev; 1361c248b7dSInki Dae struct fb_info *fbi; 137a794d57dSJoonyoung Shim struct drm_mode_fb_cmd2 mode_cmd = { 0 }; 1381c248b7dSInki Dae struct platform_device *pdev = dev->platformdev; 139e1533c08SJoonyoung Shim unsigned long size; 1401c248b7dSInki Dae int ret; 1411c248b7dSInki Dae 1421c248b7dSInki Dae DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d\n", 1431c248b7dSInki Dae sizes->surface_width, sizes->surface_height, 1441c248b7dSInki Dae sizes->surface_bpp); 1451c248b7dSInki Dae 1461c248b7dSInki Dae mode_cmd.width = sizes->surface_width; 1471c248b7dSInki Dae mode_cmd.height = sizes->surface_height; 148a794d57dSJoonyoung Shim mode_cmd.pitches[0] = sizes->surface_width * (sizes->surface_bpp >> 3); 149a794d57dSJoonyoung Shim mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, 150a794d57dSJoonyoung Shim sizes->surface_depth); 1511c248b7dSInki Dae 1521c248b7dSInki Dae mutex_lock(&dev->struct_mutex); 1531c248b7dSInki Dae 1541c248b7dSInki Dae fbi = framebuffer_alloc(0, &pdev->dev); 1551c248b7dSInki Dae if (!fbi) { 1561c248b7dSInki Dae DRM_ERROR("failed to allocate fb info.\n"); 1571c248b7dSInki Dae ret = -ENOMEM; 1581c248b7dSInki Dae goto out; 1591c248b7dSInki Dae } 1601c248b7dSInki Dae 161e1533c08SJoonyoung Shim size = mode_cmd.pitches[0] * mode_cmd.height; 1622b35892eSInki Dae 163a1bfacf4SVikas Sajjan exynos_gem_obj = exynos_drm_gem_create(dev, EXYNOS_BO_CONTIG, size); 164a1bfacf4SVikas Sajjan /* 165a1bfacf4SVikas Sajjan * If physically contiguous memory allocation fails and if IOMMU is 166a1bfacf4SVikas Sajjan * supported then try to get buffer from non physically contiguous 167a1bfacf4SVikas Sajjan * memory area. 168a1bfacf4SVikas Sajjan */ 169a1bfacf4SVikas Sajjan if (IS_ERR(exynos_gem_obj) && is_drm_iommu_supported(dev)) { 170a1bfacf4SVikas Sajjan dev_warn(&pdev->dev, "contiguous FB allocation failed, falling back to non-contiguous\n"); 171a1bfacf4SVikas Sajjan exynos_gem_obj = exynos_drm_gem_create(dev, EXYNOS_BO_NONCONTIG, 172a1bfacf4SVikas Sajjan size); 173a1bfacf4SVikas Sajjan } 174a1bfacf4SVikas Sajjan 175e1533c08SJoonyoung Shim if (IS_ERR(exynos_gem_obj)) { 176e1533c08SJoonyoung Shim ret = PTR_ERR(exynos_gem_obj); 177662aa6d7SInki Dae goto err_release_framebuffer; 1781c248b7dSInki Dae } 1791c248b7dSInki Dae 180e1533c08SJoonyoung Shim exynos_fbdev->exynos_gem_obj = exynos_gem_obj; 181e1533c08SJoonyoung Shim 182e1533c08SJoonyoung Shim helper->fb = exynos_drm_framebuffer_init(dev, &mode_cmd, 183e1533c08SJoonyoung Shim &exynos_gem_obj->base); 18441eab402SSachin Kamat if (IS_ERR(helper->fb)) { 185e1533c08SJoonyoung Shim DRM_ERROR("failed to create drm framebuffer.\n"); 186e1533c08SJoonyoung Shim ret = PTR_ERR(helper->fb); 187662aa6d7SInki Dae goto err_destroy_gem; 188e1533c08SJoonyoung Shim } 189e1533c08SJoonyoung Shim 1901c248b7dSInki Dae helper->fbdev = fbi; 1911c248b7dSInki Dae 1921c248b7dSInki Dae fbi->par = helper; 1931c248b7dSInki Dae fbi->flags = FBINFO_FLAG_DEFAULT; 1941c248b7dSInki Dae fbi->fbops = &exynos_drm_fb_ops; 1951c248b7dSInki Dae 1961c248b7dSInki Dae ret = fb_alloc_cmap(&fbi->cmap, 256, 0); 1971c248b7dSInki Dae if (ret) { 1981c248b7dSInki Dae DRM_ERROR("failed to allocate cmap.\n"); 199662aa6d7SInki Dae goto err_destroy_framebuffer; 2001c248b7dSInki Dae } 2011c248b7dSInki Dae 202aa6b2b6cSSeung-Woo Kim ret = exynos_drm_fbdev_update(helper, helper->fb); 203662aa6d7SInki Dae if (ret < 0) 204662aa6d7SInki Dae goto err_dealloc_cmap; 205662aa6d7SInki Dae 206662aa6d7SInki Dae mutex_unlock(&dev->struct_mutex); 207662aa6d7SInki Dae return ret; 208662aa6d7SInki Dae 209662aa6d7SInki Dae err_dealloc_cmap: 21019c8b834SInki Dae fb_dealloc_cmap(&fbi->cmap); 211662aa6d7SInki Dae err_destroy_framebuffer: 212662aa6d7SInki Dae drm_framebuffer_cleanup(helper->fb); 213662aa6d7SInki Dae err_destroy_gem: 214662aa6d7SInki Dae exynos_drm_gem_destroy(exynos_gem_obj); 215662aa6d7SInki Dae err_release_framebuffer: 216662aa6d7SInki Dae framebuffer_release(fbi); 2171c248b7dSInki Dae 2181c248b7dSInki Dae /* 2191c248b7dSInki Dae * if failed, all resources allocated above would be released by 2201c248b7dSInki Dae * drm_mode_config_cleanup() when drm_load() had been called prior 2211c248b7dSInki Dae * to any specific driver such as fimd or hdmi driver. 2221c248b7dSInki Dae */ 2231c248b7dSInki Dae out: 2241c248b7dSInki Dae mutex_unlock(&dev->struct_mutex); 2251c248b7dSInki Dae return ret; 2261c248b7dSInki Dae } 2271c248b7dSInki Dae 2281c248b7dSInki Dae static struct drm_fb_helper_funcs exynos_drm_fb_helper_funcs = { 229cd5428a5SDaniel Vetter .fb_probe = exynos_drm_fbdev_create, 2301c248b7dSInki Dae }; 2311c248b7dSInki Dae 232*9230ffc4SJingoo Han static bool exynos_drm_fbdev_is_anything_connected(struct drm_device *dev) 2333eb578e2SAndrzej Hajda { 2343eb578e2SAndrzej Hajda struct drm_connector *connector; 2353eb578e2SAndrzej Hajda bool ret = false; 2363eb578e2SAndrzej Hajda 2373eb578e2SAndrzej Hajda mutex_lock(&dev->mode_config.mutex); 2383eb578e2SAndrzej Hajda list_for_each_entry(connector, &dev->mode_config.connector_list, head) { 2393eb578e2SAndrzej Hajda if (connector->status != connector_status_connected) 2403eb578e2SAndrzej Hajda continue; 2413eb578e2SAndrzej Hajda 2423eb578e2SAndrzej Hajda ret = true; 2433eb578e2SAndrzej Hajda break; 2443eb578e2SAndrzej Hajda } 2453eb578e2SAndrzej Hajda mutex_unlock(&dev->mode_config.mutex); 2463eb578e2SAndrzej Hajda 2473eb578e2SAndrzej Hajda return ret; 2483eb578e2SAndrzej Hajda } 2493eb578e2SAndrzej Hajda 2501c248b7dSInki Dae int exynos_drm_fbdev_init(struct drm_device *dev) 2511c248b7dSInki Dae { 2521c248b7dSInki Dae struct exynos_drm_fbdev *fbdev; 2531c248b7dSInki Dae struct exynos_drm_private *private = dev->dev_private; 2541c248b7dSInki Dae struct drm_fb_helper *helper; 2551c248b7dSInki Dae unsigned int num_crtc; 2561c248b7dSInki Dae int ret; 2571c248b7dSInki Dae 2581c248b7dSInki Dae if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector) 2591c248b7dSInki Dae return 0; 2601c248b7dSInki Dae 2613eb578e2SAndrzej Hajda if (!exynos_drm_fbdev_is_anything_connected(dev)) 2623eb578e2SAndrzej Hajda return 0; 2633eb578e2SAndrzej Hajda 2641c248b7dSInki Dae fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); 26538bb5253SSachin Kamat if (!fbdev) 2661c248b7dSInki Dae return -ENOMEM; 2671c248b7dSInki Dae 2681c248b7dSInki Dae private->fb_helper = helper = &fbdev->drm_fb_helper; 2691c248b7dSInki Dae helper->funcs = &exynos_drm_fb_helper_funcs; 2701c248b7dSInki Dae 2711c248b7dSInki Dae num_crtc = dev->mode_config.num_crtc; 2721c248b7dSInki Dae 2731c248b7dSInki Dae ret = drm_fb_helper_init(dev, helper, num_crtc, MAX_CONNECTOR); 2741c248b7dSInki Dae if (ret < 0) { 2751c248b7dSInki Dae DRM_ERROR("failed to initialize drm fb helper.\n"); 2761c248b7dSInki Dae goto err_init; 2771c248b7dSInki Dae } 2781c248b7dSInki Dae 2791c248b7dSInki Dae ret = drm_fb_helper_single_add_all_connectors(helper); 2801c248b7dSInki Dae if (ret < 0) { 2811c248b7dSInki Dae DRM_ERROR("failed to register drm_fb_helper_connector.\n"); 2821c248b7dSInki Dae goto err_setup; 2831c248b7dSInki Dae 2841c248b7dSInki Dae } 2851c248b7dSInki Dae 28676a39dbfSDaniel Vetter /* disable all the possible outputs/crtcs before entering KMS mode */ 28776a39dbfSDaniel Vetter drm_helper_disable_unused_functions(dev); 28876a39dbfSDaniel Vetter 2891c248b7dSInki Dae ret = drm_fb_helper_initial_config(helper, PREFERRED_BPP); 2901c248b7dSInki Dae if (ret < 0) { 2911c248b7dSInki Dae DRM_ERROR("failed to set up hw configuration.\n"); 2921c248b7dSInki Dae goto err_setup; 2931c248b7dSInki Dae } 2941c248b7dSInki Dae 2951c248b7dSInki Dae return 0; 2961c248b7dSInki Dae 2971c248b7dSInki Dae err_setup: 2981c248b7dSInki Dae drm_fb_helper_fini(helper); 2991c248b7dSInki Dae 3001c248b7dSInki Dae err_init: 3011c248b7dSInki Dae private->fb_helper = NULL; 3021c248b7dSInki Dae kfree(fbdev); 3031c248b7dSInki Dae 3041c248b7dSInki Dae return ret; 3051c248b7dSInki Dae } 3061c248b7dSInki Dae 3071c248b7dSInki Dae static void exynos_drm_fbdev_destroy(struct drm_device *dev, 3081c248b7dSInki Dae struct drm_fb_helper *fb_helper) 3091c248b7dSInki Dae { 3104744ad24SInki Dae struct exynos_drm_fbdev *exynos_fbd = to_exynos_fbdev(fb_helper); 3114744ad24SInki Dae struct exynos_drm_gem_obj *exynos_gem_obj = exynos_fbd->exynos_gem_obj; 3121c248b7dSInki Dae struct drm_framebuffer *fb; 3131c248b7dSInki Dae 314c704f1b4SInki Dae if (is_drm_iommu_supported(dev) && exynos_gem_obj->buffer->kvaddr) 3154744ad24SInki Dae vunmap(exynos_gem_obj->buffer->kvaddr); 3164744ad24SInki Dae 3171c248b7dSInki Dae /* release drm framebuffer and real buffer */ 3181c248b7dSInki Dae if (fb_helper->fb && fb_helper->fb->funcs) { 3191c248b7dSInki Dae fb = fb_helper->fb; 32036206361SDaniel Vetter if (fb) { 32136206361SDaniel Vetter drm_framebuffer_unregister_private(fb); 322f7eff60eSRob Clark drm_framebuffer_remove(fb); 3231c248b7dSInki Dae } 32436206361SDaniel Vetter } 3251c248b7dSInki Dae 3261c248b7dSInki Dae /* release linux framebuffer */ 3271c248b7dSInki Dae if (fb_helper->fbdev) { 3281c248b7dSInki Dae struct fb_info *info; 3291c248b7dSInki Dae int ret; 3301c248b7dSInki Dae 3311c248b7dSInki Dae info = fb_helper->fbdev; 3321c248b7dSInki Dae ret = unregister_framebuffer(info); 3331c248b7dSInki Dae if (ret < 0) 3341c248b7dSInki Dae DRM_DEBUG_KMS("failed unregister_framebuffer()\n"); 3351c248b7dSInki Dae 3361c248b7dSInki Dae if (info->cmap.len) 3371c248b7dSInki Dae fb_dealloc_cmap(&info->cmap); 3381c248b7dSInki Dae 3391c248b7dSInki Dae framebuffer_release(info); 3401c248b7dSInki Dae } 3411c248b7dSInki Dae 3421c248b7dSInki Dae drm_fb_helper_fini(fb_helper); 3431c248b7dSInki Dae } 3441c248b7dSInki Dae 3451c248b7dSInki Dae void exynos_drm_fbdev_fini(struct drm_device *dev) 3461c248b7dSInki Dae { 3471c248b7dSInki Dae struct exynos_drm_private *private = dev->dev_private; 3481c248b7dSInki Dae struct exynos_drm_fbdev *fbdev; 3491c248b7dSInki Dae 3501c248b7dSInki Dae if (!private || !private->fb_helper) 3511c248b7dSInki Dae return; 3521c248b7dSInki Dae 3531c248b7dSInki Dae fbdev = to_exynos_fbdev(private->fb_helper); 3541c248b7dSInki Dae 355e1533c08SJoonyoung Shim if (fbdev->exynos_gem_obj) 356e1533c08SJoonyoung Shim exynos_drm_gem_destroy(fbdev->exynos_gem_obj); 357e1533c08SJoonyoung Shim 3581c248b7dSInki Dae exynos_drm_fbdev_destroy(dev, private->fb_helper); 3591c248b7dSInki Dae kfree(fbdev); 3601c248b7dSInki Dae private->fb_helper = NULL; 3611c248b7dSInki Dae } 3621c248b7dSInki Dae 3631c248b7dSInki Dae void exynos_drm_fbdev_restore_mode(struct drm_device *dev) 3641c248b7dSInki Dae { 3651c248b7dSInki Dae struct exynos_drm_private *private = dev->dev_private; 3661c248b7dSInki Dae 3671c248b7dSInki Dae if (!private || !private->fb_helper) 3681c248b7dSInki Dae return; 3691c248b7dSInki Dae 3706aed8ec3SDaniel Vetter drm_modeset_lock_all(dev); 3711c248b7dSInki Dae drm_fb_helper_restore_fbdev_mode(private->fb_helper); 3726aed8ec3SDaniel Vetter drm_modeset_unlock_all(dev); 3731c248b7dSInki Dae } 374