1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (C) 2011 Texas Instruments Incorporated - https://www.ti.com/ 4 * Author: Rob Clark <rob@ti.com> 5 */ 6 7 #include <drm/drm_drv.h> 8 #include <drm/drm_crtc_helper.h> 9 #include <drm/drm_fb_helper.h> 10 #include <drm/drm_file.h> 11 #include <drm/drm_fourcc.h> 12 #include <drm/drm_framebuffer.h> 13 #include <drm/drm_gem_framebuffer_helper.h> 14 #include <drm/drm_util.h> 15 16 #include "omap_drv.h" 17 #include "omap_fbdev.h" 18 19 MODULE_PARM_DESC(ywrap, "Enable ywrap scrolling (omap44xx and later, default 'y')"); 20 static bool ywrap_enabled = true; 21 module_param_named(ywrap, ywrap_enabled, bool, 0644); 22 23 /* 24 * fbdev funcs, to implement legacy fbdev interface on top of drm driver 25 */ 26 27 #define to_omap_fbdev(x) container_of(x, struct omap_fbdev, base) 28 29 struct omap_fbdev { 30 struct drm_fb_helper base; 31 bool ywrap_enabled; 32 33 /* for deferred dmm roll when getting called in atomic ctx */ 34 struct work_struct work; 35 }; 36 37 static struct drm_fb_helper *get_fb(struct fb_info *fbi); 38 39 static void pan_worker(struct work_struct *work) 40 { 41 struct omap_fbdev *fbdev = container_of(work, struct omap_fbdev, work); 42 struct drm_fb_helper *helper = &fbdev->base; 43 struct fb_info *fbi = helper->info; 44 struct drm_gem_object *bo = drm_gem_fb_get_obj(helper->fb, 0); 45 int npages; 46 47 /* DMM roll shifts in 4K pages: */ 48 npages = fbi->fix.line_length >> PAGE_SHIFT; 49 omap_gem_roll(bo, fbi->var.yoffset * npages); 50 } 51 52 static int omap_fbdev_pan_display(struct fb_var_screeninfo *var, 53 struct fb_info *fbi) 54 { 55 struct drm_fb_helper *helper = get_fb(fbi); 56 struct omap_fbdev *fbdev = to_omap_fbdev(helper); 57 58 if (!helper) 59 goto fallback; 60 61 if (!fbdev->ywrap_enabled) 62 goto fallback; 63 64 if (drm_can_sleep()) { 65 pan_worker(&fbdev->work); 66 } else { 67 struct omap_drm_private *priv = helper->dev->dev_private; 68 queue_work(priv->wq, &fbdev->work); 69 } 70 71 return 0; 72 73 fallback: 74 return drm_fb_helper_pan_display(var, fbi); 75 } 76 77 static void omap_fbdev_fb_destroy(struct fb_info *info) 78 { 79 struct drm_fb_helper *helper = info->par; 80 struct drm_framebuffer *fb = helper->fb; 81 struct drm_gem_object *bo = drm_gem_fb_get_obj(fb, 0); 82 struct omap_fbdev *fbdev = to_omap_fbdev(helper); 83 84 DBG(); 85 86 drm_fb_helper_fini(helper); 87 88 omap_gem_unpin(bo); 89 drm_framebuffer_remove(fb); 90 91 drm_client_release(&helper->client); 92 drm_fb_helper_unprepare(helper); 93 kfree(fbdev); 94 } 95 96 static const struct fb_ops omap_fb_ops = { 97 .owner = THIS_MODULE, 98 99 .fb_check_var = drm_fb_helper_check_var, 100 .fb_set_par = drm_fb_helper_set_par, 101 .fb_setcmap = drm_fb_helper_setcmap, 102 .fb_blank = drm_fb_helper_blank, 103 .fb_pan_display = omap_fbdev_pan_display, 104 .fb_ioctl = drm_fb_helper_ioctl, 105 106 .fb_read = drm_fb_helper_sys_read, 107 .fb_write = drm_fb_helper_sys_write, 108 .fb_fillrect = drm_fb_helper_sys_fillrect, 109 .fb_copyarea = drm_fb_helper_sys_copyarea, 110 .fb_imageblit = drm_fb_helper_sys_imageblit, 111 112 .fb_destroy = omap_fbdev_fb_destroy, 113 }; 114 115 static int omap_fbdev_create(struct drm_fb_helper *helper, 116 struct drm_fb_helper_surface_size *sizes) 117 { 118 struct omap_fbdev *fbdev = to_omap_fbdev(helper); 119 struct drm_device *dev = helper->dev; 120 struct omap_drm_private *priv = dev->dev_private; 121 struct drm_framebuffer *fb = NULL; 122 union omap_gem_size gsize; 123 struct fb_info *fbi = NULL; 124 struct drm_mode_fb_cmd2 mode_cmd = {0}; 125 struct drm_gem_object *bo; 126 dma_addr_t dma_addr; 127 int ret; 128 129 sizes->surface_bpp = 32; 130 sizes->surface_depth = 24; 131 132 DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width, 133 sizes->surface_height, sizes->surface_bpp, 134 sizes->fb_width, sizes->fb_height); 135 136 mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, 137 sizes->surface_depth); 138 139 mode_cmd.width = sizes->surface_width; 140 mode_cmd.height = sizes->surface_height; 141 142 mode_cmd.pitches[0] = 143 DIV_ROUND_UP(mode_cmd.width * sizes->surface_bpp, 8); 144 145 fbdev->ywrap_enabled = priv->has_dmm && ywrap_enabled; 146 if (fbdev->ywrap_enabled) { 147 /* need to align pitch to page size if using DMM scrolling */ 148 mode_cmd.pitches[0] = PAGE_ALIGN(mode_cmd.pitches[0]); 149 } 150 151 /* allocate backing bo */ 152 gsize = (union omap_gem_size){ 153 .bytes = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height), 154 }; 155 DBG("allocating %d bytes for fb %d", gsize.bytes, dev->primary->index); 156 bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC); 157 if (!bo) { 158 dev_err(dev->dev, "failed to allocate buffer object\n"); 159 ret = -ENOMEM; 160 goto fail; 161 } 162 163 fb = omap_framebuffer_init(dev, &mode_cmd, &bo); 164 if (IS_ERR(fb)) { 165 dev_err(dev->dev, "failed to allocate fb\n"); 166 /* note: if fb creation failed, we can't rely on fb destroy 167 * to unref the bo: 168 */ 169 drm_gem_object_put(bo); 170 ret = PTR_ERR(fb); 171 goto fail; 172 } 173 174 /* note: this keeps the bo pinned.. which is perhaps not ideal, 175 * but is needed as long as we use fb_mmap() to mmap to userspace 176 * (since this happens using fix.smem_start). Possibly we could 177 * implement our own mmap using GEM mmap support to avoid this 178 * (non-tiled buffer doesn't need to be pinned for fbcon to write 179 * to it). Then we just need to be sure that we are able to re- 180 * pin it in case of an opps. 181 */ 182 ret = omap_gem_pin(bo, &dma_addr); 183 if (ret) { 184 dev_err(dev->dev, "could not pin framebuffer\n"); 185 ret = -ENOMEM; 186 goto fail; 187 } 188 189 fbi = drm_fb_helper_alloc_info(helper); 190 if (IS_ERR(fbi)) { 191 dev_err(dev->dev, "failed to allocate fb info\n"); 192 ret = PTR_ERR(fbi); 193 goto fail; 194 } 195 196 DBG("fbi=%p, dev=%p", fbi, dev); 197 198 helper->fb = fb; 199 200 fbi->fbops = &omap_fb_ops; 201 202 drm_fb_helper_fill_info(fbi, helper, sizes); 203 204 fbi->screen_buffer = omap_gem_vaddr(bo); 205 fbi->screen_size = bo->size; 206 fbi->fix.smem_start = dma_addr; 207 fbi->fix.smem_len = bo->size; 208 209 /* if we have DMM, then we can use it for scrolling by just 210 * shuffling pages around in DMM rather than doing sw blit. 211 */ 212 if (fbdev->ywrap_enabled) { 213 DRM_INFO("Enabling DMM ywrap scrolling\n"); 214 fbi->flags |= FBINFO_HWACCEL_YWRAP | FBINFO_READS_FAST; 215 fbi->fix.ywrapstep = 1; 216 } 217 218 219 DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres); 220 DBG("allocated %dx%d fb", fb->width, fb->height); 221 222 return 0; 223 224 fail: 225 226 if (ret) { 227 if (fb) 228 drm_framebuffer_remove(fb); 229 } 230 231 return ret; 232 } 233 234 static const struct drm_fb_helper_funcs omap_fb_helper_funcs = { 235 .fb_probe = omap_fbdev_create, 236 }; 237 238 static struct drm_fb_helper *get_fb(struct fb_info *fbi) 239 { 240 if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) { 241 /* these are not the fb's you're looking for */ 242 return NULL; 243 } 244 return fbi->par; 245 } 246 247 /* 248 * struct drm_client 249 */ 250 251 static void omap_fbdev_client_unregister(struct drm_client_dev *client) 252 { 253 struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); 254 255 if (fb_helper->info) { 256 drm_fb_helper_unregister_info(fb_helper); 257 } else { 258 drm_client_release(&fb_helper->client); 259 drm_fb_helper_unprepare(fb_helper); 260 kfree(fb_helper); 261 } 262 } 263 264 static int omap_fbdev_client_restore(struct drm_client_dev *client) 265 { 266 drm_fb_helper_lastclose(client->dev); 267 268 return 0; 269 } 270 271 static int omap_fbdev_client_hotplug(struct drm_client_dev *client) 272 { 273 struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); 274 struct drm_device *dev = client->dev; 275 int ret; 276 277 if (dev->fb_helper) 278 return drm_fb_helper_hotplug_event(dev->fb_helper); 279 280 ret = drm_fb_helper_init(dev, fb_helper); 281 if (ret) 282 goto err_drm_err; 283 284 ret = drm_fb_helper_initial_config(fb_helper); 285 if (ret) 286 goto err_drm_fb_helper_fini; 287 288 return 0; 289 290 err_drm_fb_helper_fini: 291 drm_fb_helper_fini(fb_helper); 292 err_drm_err: 293 drm_err(dev, "Failed to setup fbdev emulation (ret=%d)\n", ret); 294 return ret; 295 } 296 297 static const struct drm_client_funcs omap_fbdev_client_funcs = { 298 .owner = THIS_MODULE, 299 .unregister = omap_fbdev_client_unregister, 300 .restore = omap_fbdev_client_restore, 301 .hotplug = omap_fbdev_client_hotplug, 302 }; 303 304 void omap_fbdev_setup(struct drm_device *dev) 305 { 306 struct omap_fbdev *fbdev; 307 struct drm_fb_helper *helper; 308 int ret; 309 310 drm_WARN(dev, !dev->registered, "Device has not been registered.\n"); 311 drm_WARN(dev, dev->fb_helper, "fb_helper is already set!\n"); 312 313 fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); 314 if (!fbdev) 315 return; 316 helper = &fbdev->base; 317 318 drm_fb_helper_prepare(dev, helper, 32, &omap_fb_helper_funcs); 319 320 ret = drm_client_init(dev, &helper->client, "fbdev", &omap_fbdev_client_funcs); 321 if (ret) 322 goto err_drm_client_init; 323 324 INIT_WORK(&fbdev->work, pan_worker); 325 326 ret = omap_fbdev_client_hotplug(&helper->client); 327 if (ret) 328 drm_dbg_kms(dev, "client hotplug ret=%d\n", ret); 329 330 drm_client_register(&helper->client); 331 332 return; 333 334 err_drm_client_init: 335 drm_fb_helper_unprepare(helper); 336 kfree(fbdev); 337 } 338