1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (C) 2012-2013 Avionic Design GmbH 4 * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved. 5 * 6 * Based on the KMS/FB DMA helpers 7 * Copyright (C) 2012 Analog Devices Inc. 8 */ 9 10 #include <linux/console.h> 11 #include <linux/fb.h> 12 #include <linux/vmalloc.h> 13 14 #include <drm/drm_drv.h> 15 #include <drm/drm_crtc_helper.h> 16 #include <drm/drm_fb_helper.h> 17 #include <drm/drm_fourcc.h> 18 #include <drm/drm_framebuffer.h> 19 #include <drm/drm_gem_framebuffer_helper.h> 20 #include <drm/drm_modeset_helper.h> 21 22 #include "drm.h" 23 #include "gem.h" 24 25 static int tegra_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) 26 { 27 struct drm_fb_helper *helper = info->par; 28 struct tegra_bo *bo; 29 int err; 30 31 bo = tegra_fb_get_plane(helper->fb, 0); 32 33 err = drm_gem_mmap_obj(&bo->gem, bo->gem.size, vma); 34 if (err < 0) 35 return err; 36 37 return __tegra_gem_mmap(&bo->gem, vma); 38 } 39 40 static void tegra_fbdev_fb_destroy(struct fb_info *info) 41 { 42 struct drm_fb_helper *helper = info->par; 43 struct drm_framebuffer *fb = helper->fb; 44 struct tegra_bo *bo = tegra_fb_get_plane(fb, 0); 45 46 drm_fb_helper_fini(helper); 47 48 /* Undo the special mapping we made in fbdev probe. */ 49 if (bo->pages) { 50 vunmap(bo->vaddr); 51 bo->vaddr = NULL; 52 } 53 drm_framebuffer_remove(fb); 54 55 drm_client_release(&helper->client); 56 drm_fb_helper_unprepare(helper); 57 kfree(helper); 58 } 59 60 static const struct fb_ops tegra_fb_ops = { 61 .owner = THIS_MODULE, 62 __FB_DEFAULT_DMAMEM_OPS_RDWR, 63 DRM_FB_HELPER_DEFAULT_OPS, 64 __FB_DEFAULT_DMAMEM_OPS_DRAW, 65 .fb_mmap = tegra_fb_mmap, 66 .fb_destroy = tegra_fbdev_fb_destroy, 67 }; 68 69 static int tegra_fbdev_probe(struct drm_fb_helper *helper, 70 struct drm_fb_helper_surface_size *sizes) 71 { 72 struct tegra_drm *tegra = helper->dev->dev_private; 73 struct drm_device *drm = helper->dev; 74 struct drm_mode_fb_cmd2 cmd = { 0 }; 75 unsigned int bytes_per_pixel; 76 struct drm_framebuffer *fb; 77 unsigned long offset; 78 struct fb_info *info; 79 struct tegra_bo *bo; 80 size_t size; 81 int err; 82 83 bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8); 84 85 cmd.width = sizes->surface_width; 86 cmd.height = sizes->surface_height; 87 cmd.pitches[0] = round_up(sizes->surface_width * bytes_per_pixel, 88 tegra->pitch_align); 89 90 cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, 91 sizes->surface_depth); 92 93 size = cmd.pitches[0] * cmd.height; 94 95 bo = tegra_bo_create(drm, size, 0); 96 if (IS_ERR(bo)) 97 return PTR_ERR(bo); 98 99 info = drm_fb_helper_alloc_info(helper); 100 if (IS_ERR(info)) { 101 dev_err(drm->dev, "failed to allocate framebuffer info\n"); 102 drm_gem_object_put(&bo->gem); 103 return PTR_ERR(info); 104 } 105 106 fb = tegra_fb_alloc(drm, &cmd, &bo, 1); 107 if (IS_ERR(fb)) { 108 err = PTR_ERR(fb); 109 dev_err(drm->dev, "failed to allocate DRM framebuffer: %d\n", 110 err); 111 drm_gem_object_put(&bo->gem); 112 return PTR_ERR(fb); 113 } 114 115 helper->fb = fb; 116 helper->info = info; 117 118 info->fbops = &tegra_fb_ops; 119 120 drm_fb_helper_fill_info(info, helper, sizes); 121 122 offset = info->var.xoffset * bytes_per_pixel + 123 info->var.yoffset * fb->pitches[0]; 124 125 if (bo->pages) { 126 bo->vaddr = vmap(bo->pages, bo->num_pages, VM_MAP, 127 pgprot_writecombine(PAGE_KERNEL)); 128 if (!bo->vaddr) { 129 dev_err(drm->dev, "failed to vmap() framebuffer\n"); 130 err = -ENOMEM; 131 goto destroy; 132 } 133 } 134 135 info->flags |= FBINFO_VIRTFB; 136 info->screen_buffer = bo->vaddr + offset; 137 info->screen_size = size; 138 info->fix.smem_start = (unsigned long)(bo->iova + offset); 139 info->fix.smem_len = size; 140 141 return 0; 142 143 destroy: 144 drm_framebuffer_remove(fb); 145 return err; 146 } 147 148 static const struct drm_fb_helper_funcs tegra_fb_helper_funcs = { 149 .fb_probe = tegra_fbdev_probe, 150 }; 151 152 /* 153 * struct drm_client 154 */ 155 156 static void tegra_fbdev_client_unregister(struct drm_client_dev *client) 157 { 158 struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); 159 160 if (fb_helper->info) { 161 drm_fb_helper_unregister_info(fb_helper); 162 } else { 163 drm_client_release(&fb_helper->client); 164 drm_fb_helper_unprepare(fb_helper); 165 kfree(fb_helper); 166 } 167 } 168 169 static int tegra_fbdev_client_restore(struct drm_client_dev *client) 170 { 171 drm_fb_helper_lastclose(client->dev); 172 173 return 0; 174 } 175 176 static int tegra_fbdev_client_hotplug(struct drm_client_dev *client) 177 { 178 struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); 179 struct drm_device *dev = client->dev; 180 int ret; 181 182 if (dev->fb_helper) 183 return drm_fb_helper_hotplug_event(dev->fb_helper); 184 185 ret = drm_fb_helper_init(dev, fb_helper); 186 if (ret) 187 goto err_drm_err; 188 189 if (!drm_drv_uses_atomic_modeset(dev)) 190 drm_helper_disable_unused_functions(dev); 191 192 ret = drm_fb_helper_initial_config(fb_helper); 193 if (ret) 194 goto err_drm_fb_helper_fini; 195 196 return 0; 197 198 err_drm_fb_helper_fini: 199 drm_fb_helper_fini(fb_helper); 200 err_drm_err: 201 drm_err(dev, "Failed to setup fbdev emulation (ret=%d)\n", ret); 202 return ret; 203 } 204 205 static const struct drm_client_funcs tegra_fbdev_client_funcs = { 206 .owner = THIS_MODULE, 207 .unregister = tegra_fbdev_client_unregister, 208 .restore = tegra_fbdev_client_restore, 209 .hotplug = tegra_fbdev_client_hotplug, 210 }; 211 212 void tegra_fbdev_setup(struct drm_device *dev) 213 { 214 struct drm_fb_helper *helper; 215 int ret; 216 217 drm_WARN(dev, !dev->registered, "Device has not been registered.\n"); 218 drm_WARN(dev, dev->fb_helper, "fb_helper is already set!\n"); 219 220 helper = kzalloc(sizeof(*helper), GFP_KERNEL); 221 if (!helper) 222 return; 223 drm_fb_helper_prepare(dev, helper, 32, &tegra_fb_helper_funcs); 224 225 ret = drm_client_init(dev, &helper->client, "fbdev", &tegra_fbdev_client_funcs); 226 if (ret) 227 goto err_drm_client_init; 228 229 drm_client_register(&helper->client); 230 231 return; 232 233 err_drm_client_init: 234 drm_fb_helper_unprepare(helper); 235 kfree(helper); 236 } 237