linux/drivers/gpu/drm/exynos/exynos_drm_fbdev.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/* exynos_drm_fbdev.c
   3 *
   4 * Copyright (c) 2011 Samsung Electronics Co., Ltd.
   5 * Authors:
   6 *      Inki Dae <inki.dae@samsung.com>
   7 *      Joonyoung Shim <jy0922.shim@samsung.com>
   8 *      Seung-Woo Kim <sw0312.kim@samsung.com>
   9 */
  10
  11#include <linux/console.h>
  12#include <linux/dma-mapping.h>
  13#include <linux/vmalloc.h>
  14
  15#include <drm/drm_crtc.h>
  16#include <drm/drm_fb_helper.h>
  17#include <drm/drm_fourcc.h>
  18#include <drm/drm_probe_helper.h>
  19#include <drm/exynos_drm.h>
  20
  21#include "exynos_drm_drv.h"
  22#include "exynos_drm_fb.h"
  23#include "exynos_drm_fbdev.h"
  24
  25#define MAX_CONNECTOR           4
  26#define PREFERRED_BPP           32
  27
  28#define to_exynos_fbdev(x)      container_of(x, struct exynos_drm_fbdev,\
  29                                drm_fb_helper)
  30
  31struct exynos_drm_fbdev {
  32        struct drm_fb_helper    drm_fb_helper;
  33        struct exynos_drm_gem   *exynos_gem;
  34};
  35
  36static int exynos_drm_fb_mmap(struct fb_info *info,
  37                        struct vm_area_struct *vma)
  38{
  39        struct drm_fb_helper *helper = info->par;
  40        struct exynos_drm_fbdev *exynos_fbd = to_exynos_fbdev(helper);
  41        struct exynos_drm_gem *exynos_gem = exynos_fbd->exynos_gem;
  42        unsigned long vm_size;
  43        int ret;
  44
  45        vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
  46
  47        vm_size = vma->vm_end - vma->vm_start;
  48
  49        if (vm_size > exynos_gem->size)
  50                return -EINVAL;
  51
  52        ret = dma_mmap_attrs(to_dma_dev(helper->dev), vma, exynos_gem->cookie,
  53                             exynos_gem->dma_addr, exynos_gem->size,
  54                             exynos_gem->dma_attrs);
  55        if (ret < 0) {
  56                DRM_DEV_ERROR(to_dma_dev(helper->dev), "failed to mmap.\n");
  57                return ret;
  58        }
  59
  60        return 0;
  61}
  62
  63static struct fb_ops exynos_drm_fb_ops = {
  64        .owner          = THIS_MODULE,
  65        DRM_FB_HELPER_DEFAULT_OPS,
  66        .fb_mmap        = exynos_drm_fb_mmap,
  67        .fb_fillrect    = drm_fb_helper_cfb_fillrect,
  68        .fb_copyarea    = drm_fb_helper_cfb_copyarea,
  69        .fb_imageblit   = drm_fb_helper_cfb_imageblit,
  70};
  71
  72static int exynos_drm_fbdev_update(struct drm_fb_helper *helper,
  73                                   struct drm_fb_helper_surface_size *sizes,
  74                                   struct exynos_drm_gem *exynos_gem)
  75{
  76        struct fb_info *fbi;
  77        struct drm_framebuffer *fb = helper->fb;
  78        unsigned int size = fb->width * fb->height * fb->format->cpp[0];
  79        unsigned int nr_pages;
  80        unsigned long offset;
  81
  82        fbi = drm_fb_helper_alloc_fbi(helper);
  83        if (IS_ERR(fbi)) {
  84                DRM_DEV_ERROR(to_dma_dev(helper->dev),
  85                              "failed to allocate fb info.\n");
  86                return PTR_ERR(fbi);
  87        }
  88
  89        fbi->fbops = &exynos_drm_fb_ops;
  90
  91        drm_fb_helper_fill_info(fbi, helper, sizes);
  92
  93        nr_pages = exynos_gem->size >> PAGE_SHIFT;
  94
  95        exynos_gem->kvaddr = (void __iomem *) vmap(exynos_gem->pages, nr_pages,
  96                                VM_MAP, pgprot_writecombine(PAGE_KERNEL));
  97        if (!exynos_gem->kvaddr) {
  98                DRM_DEV_ERROR(to_dma_dev(helper->dev),
  99                              "failed to map pages to kernel space.\n");
 100                return -EIO;
 101        }
 102
 103        offset = fbi->var.xoffset * fb->format->cpp[0];
 104        offset += fbi->var.yoffset * fb->pitches[0];
 105
 106        fbi->screen_base = exynos_gem->kvaddr + offset;
 107        fbi->screen_size = size;
 108        fbi->fix.smem_len = size;
 109
 110        return 0;
 111}
 112
 113static int exynos_drm_fbdev_create(struct drm_fb_helper *helper,
 114                                    struct drm_fb_helper_surface_size *sizes)
 115{
 116        struct exynos_drm_fbdev *exynos_fbdev = to_exynos_fbdev(helper);
 117        struct exynos_drm_gem *exynos_gem;
 118        struct drm_device *dev = helper->dev;
 119        struct drm_mode_fb_cmd2 mode_cmd = { 0 };
 120        unsigned long size;
 121        int ret;
 122
 123        DRM_DEV_DEBUG_KMS(dev->dev,
 124                          "surface width(%d), height(%d) and bpp(%d\n",
 125                          sizes->surface_width, sizes->surface_height,
 126                          sizes->surface_bpp);
 127
 128        mode_cmd.width = sizes->surface_width;
 129        mode_cmd.height = sizes->surface_height;
 130        mode_cmd.pitches[0] = sizes->surface_width * (sizes->surface_bpp >> 3);
 131        mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
 132                                                          sizes->surface_depth);
 133
 134        size = mode_cmd.pitches[0] * mode_cmd.height;
 135
 136        exynos_gem = exynos_drm_gem_create(dev, EXYNOS_BO_CONTIG, size);
 137        /*
 138         * If physically contiguous memory allocation fails and if IOMMU is
 139         * supported then try to get buffer from non physically contiguous
 140         * memory area.
 141         */
 142        if (IS_ERR(exynos_gem) && is_drm_iommu_supported(dev)) {
 143                dev_warn(dev->dev, "contiguous FB allocation failed, falling back to non-contiguous\n");
 144                exynos_gem = exynos_drm_gem_create(dev, EXYNOS_BO_NONCONTIG,
 145                                                   size);
 146        }
 147
 148        if (IS_ERR(exynos_gem))
 149                return PTR_ERR(exynos_gem);
 150
 151        exynos_fbdev->exynos_gem = exynos_gem;
 152
 153        helper->fb =
 154                exynos_drm_framebuffer_init(dev, &mode_cmd, &exynos_gem, 1);
 155        if (IS_ERR(helper->fb)) {
 156                DRM_DEV_ERROR(dev->dev, "failed to create drm framebuffer.\n");
 157                ret = PTR_ERR(helper->fb);
 158                goto err_destroy_gem;
 159        }
 160
 161        ret = exynos_drm_fbdev_update(helper, sizes, exynos_gem);
 162        if (ret < 0)
 163                goto err_destroy_framebuffer;
 164
 165        return ret;
 166
 167err_destroy_framebuffer:
 168        drm_framebuffer_cleanup(helper->fb);
 169err_destroy_gem:
 170        exynos_drm_gem_destroy(exynos_gem);
 171
 172        /*
 173         * if failed, all resources allocated above would be released by
 174         * drm_mode_config_cleanup() when drm_load() had been called prior
 175         * to any specific driver such as fimd or hdmi driver.
 176         */
 177
 178        return ret;
 179}
 180
 181static const struct drm_fb_helper_funcs exynos_drm_fb_helper_funcs = {
 182        .fb_probe =     exynos_drm_fbdev_create,
 183};
 184
 185int exynos_drm_fbdev_init(struct drm_device *dev)
 186{
 187        struct exynos_drm_fbdev *fbdev;
 188        struct exynos_drm_private *private = dev->dev_private;
 189        struct drm_fb_helper *helper;
 190        int ret;
 191
 192        if (!dev->mode_config.num_crtc)
 193                return 0;
 194
 195        fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
 196        if (!fbdev)
 197                return -ENOMEM;
 198
 199        private->fb_helper = helper = &fbdev->drm_fb_helper;
 200
 201        drm_fb_helper_prepare(dev, helper, &exynos_drm_fb_helper_funcs);
 202
 203        ret = drm_fb_helper_init(dev, helper, MAX_CONNECTOR);
 204        if (ret < 0) {
 205                DRM_DEV_ERROR(dev->dev,
 206                              "failed to initialize drm fb helper.\n");
 207                goto err_init;
 208        }
 209
 210        ret = drm_fb_helper_single_add_all_connectors(helper);
 211        if (ret < 0) {
 212                DRM_DEV_ERROR(dev->dev,
 213                              "failed to register drm_fb_helper_connector.\n");
 214                goto err_setup;
 215
 216        }
 217
 218        ret = drm_fb_helper_initial_config(helper, PREFERRED_BPP);
 219        if (ret < 0) {
 220                DRM_DEV_ERROR(dev->dev,
 221                              "failed to set up hw configuration.\n");
 222                goto err_setup;
 223        }
 224
 225        return 0;
 226
 227err_setup:
 228        drm_fb_helper_fini(helper);
 229
 230err_init:
 231        private->fb_helper = NULL;
 232        kfree(fbdev);
 233
 234        return ret;
 235}
 236
 237static void exynos_drm_fbdev_destroy(struct drm_device *dev,
 238                                      struct drm_fb_helper *fb_helper)
 239{
 240        struct exynos_drm_fbdev *exynos_fbd = to_exynos_fbdev(fb_helper);
 241        struct exynos_drm_gem *exynos_gem = exynos_fbd->exynos_gem;
 242        struct drm_framebuffer *fb;
 243
 244        vunmap(exynos_gem->kvaddr);
 245
 246        /* release drm framebuffer and real buffer */
 247        if (fb_helper->fb && fb_helper->fb->funcs) {
 248                fb = fb_helper->fb;
 249                if (fb)
 250                        drm_framebuffer_remove(fb);
 251        }
 252
 253        drm_fb_helper_unregister_fbi(fb_helper);
 254
 255        drm_fb_helper_fini(fb_helper);
 256}
 257
 258void exynos_drm_fbdev_fini(struct drm_device *dev)
 259{
 260        struct exynos_drm_private *private = dev->dev_private;
 261        struct exynos_drm_fbdev *fbdev;
 262
 263        if (!private || !private->fb_helper)
 264                return;
 265
 266        fbdev = to_exynos_fbdev(private->fb_helper);
 267
 268        exynos_drm_fbdev_destroy(dev, private->fb_helper);
 269        kfree(fbdev);
 270        private->fb_helper = NULL;
 271}
 272
 273