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