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