1caab277bSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
28bb0daffSRob Clark /*
31b409fdaSAlexander A. Klimov * Copyright (C) 2011 Texas Instruments Incorporated - https://www.ti.com/
48bb0daffSRob Clark * Author: Rob Clark <rob@ti.com>
58bb0daffSRob Clark */
68bb0daffSRob Clark
795da53d6SThomas Zimmermann #include <linux/fb.h>
895da53d6SThomas Zimmermann
99e69bcd8SThomas Zimmermann #include <drm/drm_drv.h>
109e69bcd8SThomas Zimmermann #include <drm/drm_crtc_helper.h>
112d278f54SLaurent Pinchart #include <drm/drm_fb_helper.h>
1281f6156cSSam Ravnborg #include <drm/drm_file.h>
1381f6156cSSam Ravnborg #include <drm/drm_fourcc.h>
14720cf96dSVille Syrjälä #include <drm/drm_framebuffer.h>
15194c9e20SThomas Zimmermann #include <drm/drm_gem_framebuffer_helper.h>
169e69bcd8SThomas Zimmermann #include <drm/drm_util.h>
178bb0daffSRob Clark
182d278f54SLaurent Pinchart #include "omap_drv.h"
198e3aac3bSThomas Zimmermann #include "omap_fbdev.h"
208bb0daffSRob Clark
218bb0daffSRob Clark MODULE_PARM_DESC(ywrap, "Enable ywrap scrolling (omap44xx and later, default 'y')");
228bb0daffSRob Clark static bool ywrap_enabled = true;
238bb0daffSRob Clark module_param_named(ywrap, ywrap_enabled, bool, 0644);
248bb0daffSRob Clark
258bb0daffSRob Clark /*
268bb0daffSRob Clark * fbdev funcs, to implement legacy fbdev interface on top of drm driver
278bb0daffSRob Clark */
288bb0daffSRob Clark
298bb0daffSRob Clark #define to_omap_fbdev(x) container_of(x, struct omap_fbdev, base)
308bb0daffSRob Clark
318bb0daffSRob Clark struct omap_fbdev {
328bb0daffSRob Clark struct drm_fb_helper base;
338bb0daffSRob Clark bool ywrap_enabled;
348bb0daffSRob Clark
358bb0daffSRob Clark /* for deferred dmm roll when getting called in atomic ctx */
368bb0daffSRob Clark struct work_struct work;
378bb0daffSRob Clark };
388bb0daffSRob Clark
398bb0daffSRob Clark static struct drm_fb_helper *get_fb(struct fb_info *fbi);
408bb0daffSRob Clark
pan_worker(struct work_struct * work)418bb0daffSRob Clark static void pan_worker(struct work_struct *work)
428bb0daffSRob Clark {
438bb0daffSRob Clark struct omap_fbdev *fbdev = container_of(work, struct omap_fbdev, work);
44194c9e20SThomas Zimmermann struct drm_fb_helper *helper = &fbdev->base;
45194c9e20SThomas Zimmermann struct fb_info *fbi = helper->info;
46194c9e20SThomas Zimmermann struct drm_gem_object *bo = drm_gem_fb_get_obj(helper->fb, 0);
478bb0daffSRob Clark int npages;
488bb0daffSRob Clark
498bb0daffSRob Clark /* DMM roll shifts in 4K pages: */
508bb0daffSRob Clark npages = fbi->fix.line_length >> PAGE_SHIFT;
51194c9e20SThomas Zimmermann omap_gem_roll(bo, fbi->var.yoffset * npages);
528bb0daffSRob Clark }
538bb0daffSRob Clark
omap_fbdev_pan_display(struct fb_var_screeninfo * var,struct fb_info * fbi)548bb0daffSRob Clark static int omap_fbdev_pan_display(struct fb_var_screeninfo *var,
558bb0daffSRob Clark struct fb_info *fbi)
568bb0daffSRob Clark {
578bb0daffSRob Clark struct drm_fb_helper *helper = get_fb(fbi);
588bb0daffSRob Clark struct omap_fbdev *fbdev = to_omap_fbdev(helper);
598bb0daffSRob Clark
608bb0daffSRob Clark if (!helper)
618bb0daffSRob Clark goto fallback;
628bb0daffSRob Clark
638bb0daffSRob Clark if (!fbdev->ywrap_enabled)
648bb0daffSRob Clark goto fallback;
658bb0daffSRob Clark
668bb0daffSRob Clark if (drm_can_sleep()) {
678bb0daffSRob Clark pan_worker(&fbdev->work);
688bb0daffSRob Clark } else {
698bb0daffSRob Clark struct omap_drm_private *priv = helper->dev->dev_private;
708bb0daffSRob Clark queue_work(priv->wq, &fbdev->work);
718bb0daffSRob Clark }
728bb0daffSRob Clark
738bb0daffSRob Clark return 0;
748bb0daffSRob Clark
758bb0daffSRob Clark fallback:
768bb0daffSRob Clark return drm_fb_helper_pan_display(var, fbi);
778bb0daffSRob Clark }
788bb0daffSRob Clark
omap_fbdev_fb_mmap(struct fb_info * info,struct vm_area_struct * vma)79da6eb399SThomas Zimmermann static int omap_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
80da6eb399SThomas Zimmermann {
81da6eb399SThomas Zimmermann struct drm_fb_helper *helper = info->par;
82da6eb399SThomas Zimmermann struct drm_framebuffer *fb = helper->fb;
83da6eb399SThomas Zimmermann struct drm_gem_object *bo = drm_gem_fb_get_obj(fb, 0);
84da6eb399SThomas Zimmermann
85da6eb399SThomas Zimmermann return drm_gem_mmap_obj(bo, omap_gem_mmap_size(bo), vma);
86da6eb399SThomas Zimmermann }
87da6eb399SThomas Zimmermann
omap_fbdev_fb_destroy(struct fb_info * info)889e69bcd8SThomas Zimmermann static void omap_fbdev_fb_destroy(struct fb_info *info)
899e69bcd8SThomas Zimmermann {
909e69bcd8SThomas Zimmermann struct drm_fb_helper *helper = info->par;
919e69bcd8SThomas Zimmermann struct drm_framebuffer *fb = helper->fb;
929e69bcd8SThomas Zimmermann struct drm_gem_object *bo = drm_gem_fb_get_obj(fb, 0);
939e69bcd8SThomas Zimmermann struct omap_fbdev *fbdev = to_omap_fbdev(helper);
949e69bcd8SThomas Zimmermann
959e69bcd8SThomas Zimmermann DBG();
969e69bcd8SThomas Zimmermann
979e69bcd8SThomas Zimmermann drm_fb_helper_fini(helper);
989e69bcd8SThomas Zimmermann
999e69bcd8SThomas Zimmermann omap_gem_unpin(bo);
1009e69bcd8SThomas Zimmermann drm_framebuffer_remove(fb);
1019e69bcd8SThomas Zimmermann
1029e69bcd8SThomas Zimmermann drm_client_release(&helper->client);
1039e69bcd8SThomas Zimmermann drm_fb_helper_unprepare(helper);
1049e69bcd8SThomas Zimmermann kfree(fbdev);
1059e69bcd8SThomas Zimmermann }
1069e69bcd8SThomas Zimmermann
107b6ff753aSJani Nikula static const struct fb_ops omap_fb_ops = {
1088bb0daffSRob Clark .owner = THIS_MODULE,
109*b21f187fSThomas Zimmermann __FB_DEFAULT_DMAMEM_OPS_RDWR,
110f9b34a0fSTomi Valkeinen .fb_check_var = drm_fb_helper_check_var,
111f9b34a0fSTomi Valkeinen .fb_set_par = drm_fb_helper_set_par,
112f9b34a0fSTomi Valkeinen .fb_setcmap = drm_fb_helper_setcmap,
113f9b34a0fSTomi Valkeinen .fb_blank = drm_fb_helper_blank,
114f9b34a0fSTomi Valkeinen .fb_pan_display = omap_fbdev_pan_display,
115*b21f187fSThomas Zimmermann __FB_DEFAULT_DMAMEM_OPS_DRAW,
116f9b34a0fSTomi Valkeinen .fb_ioctl = drm_fb_helper_ioctl,
117da6eb399SThomas Zimmermann .fb_mmap = omap_fbdev_fb_mmap,
1189e69bcd8SThomas Zimmermann .fb_destroy = omap_fbdev_fb_destroy,
1198bb0daffSRob Clark };
1208bb0daffSRob Clark
omap_fbdev_create(struct drm_fb_helper * helper,struct drm_fb_helper_surface_size * sizes)1218bb0daffSRob Clark static int omap_fbdev_create(struct drm_fb_helper *helper,
1228bb0daffSRob Clark struct drm_fb_helper_surface_size *sizes)
1238bb0daffSRob Clark {
1248bb0daffSRob Clark struct omap_fbdev *fbdev = to_omap_fbdev(helper);
1258bb0daffSRob Clark struct drm_device *dev = helper->dev;
1268bb0daffSRob Clark struct omap_drm_private *priv = dev->dev_private;
1278bb0daffSRob Clark struct drm_framebuffer *fb = NULL;
1288bb0daffSRob Clark union omap_gem_size gsize;
1298bb0daffSRob Clark struct fb_info *fbi = NULL;
1308bb0daffSRob Clark struct drm_mode_fb_cmd2 mode_cmd = {0};
131194c9e20SThomas Zimmermann struct drm_gem_object *bo;
13216869083SLaurent Pinchart dma_addr_t dma_addr;
1338bb0daffSRob Clark int ret;
1348bb0daffSRob Clark
1358bb0daffSRob Clark sizes->surface_bpp = 32;
1361d977b06STomi Valkeinen sizes->surface_depth = 24;
1378bb0daffSRob Clark
1388bb0daffSRob Clark DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width,
1398bb0daffSRob Clark sizes->surface_height, sizes->surface_bpp,
1408bb0daffSRob Clark sizes->fb_width, sizes->fb_height);
1418bb0daffSRob Clark
1428bb0daffSRob Clark mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
1438bb0daffSRob Clark sizes->surface_depth);
1448bb0daffSRob Clark
1458bb0daffSRob Clark mode_cmd.width = sizes->surface_width;
1468bb0daffSRob Clark mode_cmd.height = sizes->surface_height;
1478bb0daffSRob Clark
148ce481edaSTomi Valkeinen mode_cmd.pitches[0] =
149ce481edaSTomi Valkeinen DIV_ROUND_UP(mode_cmd.width * sizes->surface_bpp, 8);
1508bb0daffSRob Clark
1518bb0daffSRob Clark fbdev->ywrap_enabled = priv->has_dmm && ywrap_enabled;
1528bb0daffSRob Clark if (fbdev->ywrap_enabled) {
1538bb0daffSRob Clark /* need to align pitch to page size if using DMM scrolling */
154743c1671SFabian Frederick mode_cmd.pitches[0] = PAGE_ALIGN(mode_cmd.pitches[0]);
1558bb0daffSRob Clark }
1568bb0daffSRob Clark
1578bb0daffSRob Clark /* allocate backing bo */
1588bb0daffSRob Clark gsize = (union omap_gem_size){
1598bb0daffSRob Clark .bytes = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height),
1608bb0daffSRob Clark };
1618bb0daffSRob Clark DBG("allocating %d bytes for fb %d", gsize.bytes, dev->primary->index);
162194c9e20SThomas Zimmermann bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC);
163194c9e20SThomas Zimmermann if (!bo) {
1648bb0daffSRob Clark dev_err(dev->dev, "failed to allocate buffer object\n");
1658bb0daffSRob Clark ret = -ENOMEM;
1668bb0daffSRob Clark goto fail;
1678bb0daffSRob Clark }
1688bb0daffSRob Clark
169194c9e20SThomas Zimmermann fb = omap_framebuffer_init(dev, &mode_cmd, &bo);
1708bb0daffSRob Clark if (IS_ERR(fb)) {
1718bb0daffSRob Clark dev_err(dev->dev, "failed to allocate fb\n");
1728bb0daffSRob Clark /* note: if fb creation failed, we can't rely on fb destroy
1738bb0daffSRob Clark * to unref the bo:
1748bb0daffSRob Clark */
175194c9e20SThomas Zimmermann drm_gem_object_put(bo);
1768bb0daffSRob Clark ret = PTR_ERR(fb);
1778bb0daffSRob Clark goto fail;
1788bb0daffSRob Clark }
1798bb0daffSRob Clark
1808bb0daffSRob Clark /* note: this keeps the bo pinned.. which is perhaps not ideal,
1818bb0daffSRob Clark * but is needed as long as we use fb_mmap() to mmap to userspace
1828bb0daffSRob Clark * (since this happens using fix.smem_start). Possibly we could
1838bb0daffSRob Clark * implement our own mmap using GEM mmap support to avoid this
1848bb0daffSRob Clark * (non-tiled buffer doesn't need to be pinned for fbcon to write
1858bb0daffSRob Clark * to it). Then we just need to be sure that we are able to re-
1868bb0daffSRob Clark * pin it in case of an opps.
1878bb0daffSRob Clark */
188194c9e20SThomas Zimmermann ret = omap_gem_pin(bo, &dma_addr);
1898bb0daffSRob Clark if (ret) {
190bc20c85cSLaurent Pinchart dev_err(dev->dev, "could not pin framebuffer\n");
1918bb0daffSRob Clark ret = -ENOMEM;
1928bb0daffSRob Clark goto fail;
1938bb0daffSRob Clark }
1948bb0daffSRob Clark
1957fd50bc3SThomas Zimmermann fbi = drm_fb_helper_alloc_info(helper);
196231e6fafSArchit Taneja if (IS_ERR(fbi)) {
1978bb0daffSRob Clark dev_err(dev->dev, "failed to allocate fb info\n");
198231e6fafSArchit Taneja ret = PTR_ERR(fbi);
1993cbd0c58SLaurent Pinchart goto fail;
2008bb0daffSRob Clark }
2018bb0daffSRob Clark
2028bb0daffSRob Clark DBG("fbi=%p, dev=%p", fbi, dev);
2038bb0daffSRob Clark
2048bb0daffSRob Clark helper->fb = fb;
2058bb0daffSRob Clark
2068bb0daffSRob Clark fbi->fbops = &omap_fb_ops;
2078bb0daffSRob Clark
208e8f9ad5aSDaniel Vetter drm_fb_helper_fill_info(fbi, helper, sizes);
2098bb0daffSRob Clark
210f98eb6c0SThomas Zimmermann fbi->flags |= FBINFO_VIRTFB;
211194c9e20SThomas Zimmermann fbi->screen_buffer = omap_gem_vaddr(bo);
212194c9e20SThomas Zimmermann fbi->screen_size = bo->size;
21316869083SLaurent Pinchart fbi->fix.smem_start = dma_addr;
214194c9e20SThomas Zimmermann fbi->fix.smem_len = bo->size;
2158bb0daffSRob Clark
2168bb0daffSRob Clark /* if we have DMM, then we can use it for scrolling by just
2178bb0daffSRob Clark * shuffling pages around in DMM rather than doing sw blit.
2188bb0daffSRob Clark */
2198bb0daffSRob Clark if (fbdev->ywrap_enabled) {
2208bb0daffSRob Clark DRM_INFO("Enabling DMM ywrap scrolling\n");
2218bb0daffSRob Clark fbi->flags |= FBINFO_HWACCEL_YWRAP | FBINFO_READS_FAST;
2228bb0daffSRob Clark fbi->fix.ywrapstep = 1;
2238bb0daffSRob Clark }
2248bb0daffSRob Clark
2258bb0daffSRob Clark
2268bb0daffSRob Clark DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres);
22738129bc9SThomas Zimmermann DBG("allocated %dx%d fb", fb->width, fb->height);
2288bb0daffSRob Clark
2298bb0daffSRob Clark return 0;
2308bb0daffSRob Clark
2318bb0daffSRob Clark fail:
2328bb0daffSRob Clark
2338bb0daffSRob Clark if (ret) {
234b77bc10bSDaniel Vetter if (fb)
2358bb0daffSRob Clark drm_framebuffer_remove(fb);
2368bb0daffSRob Clark }
2378bb0daffSRob Clark
2388bb0daffSRob Clark return ret;
2398bb0daffSRob Clark }
2408bb0daffSRob Clark
2413a493879SThierry Reding static const struct drm_fb_helper_funcs omap_fb_helper_funcs = {
2428bb0daffSRob Clark .fb_probe = omap_fbdev_create,
2438bb0daffSRob Clark };
2448bb0daffSRob Clark
get_fb(struct fb_info * fbi)2458bb0daffSRob Clark static struct drm_fb_helper *get_fb(struct fb_info *fbi)
2468bb0daffSRob Clark {
2478bb0daffSRob Clark if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) {
2488bb0daffSRob Clark /* these are not the fb's you're looking for */
2498bb0daffSRob Clark return NULL;
2508bb0daffSRob Clark }
2518bb0daffSRob Clark return fbi->par;
2528bb0daffSRob Clark }
2538bb0daffSRob Clark
2549e69bcd8SThomas Zimmermann /*
2559e69bcd8SThomas Zimmermann * struct drm_client
2569e69bcd8SThomas Zimmermann */
2578bb0daffSRob Clark
omap_fbdev_client_unregister(struct drm_client_dev * client)2589e69bcd8SThomas Zimmermann static void omap_fbdev_client_unregister(struct drm_client_dev *client)
2599e69bcd8SThomas Zimmermann {
2609e69bcd8SThomas Zimmermann struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
2619e69bcd8SThomas Zimmermann
2629e69bcd8SThomas Zimmermann if (fb_helper->info) {
2639e69bcd8SThomas Zimmermann drm_fb_helper_unregister_info(fb_helper);
2649e69bcd8SThomas Zimmermann } else {
2659e69bcd8SThomas Zimmermann drm_client_release(&fb_helper->client);
2669e69bcd8SThomas Zimmermann drm_fb_helper_unprepare(fb_helper);
2679e69bcd8SThomas Zimmermann kfree(fb_helper);
2689e69bcd8SThomas Zimmermann }
2699e69bcd8SThomas Zimmermann }
2709e69bcd8SThomas Zimmermann
omap_fbdev_client_restore(struct drm_client_dev * client)2719e69bcd8SThomas Zimmermann static int omap_fbdev_client_restore(struct drm_client_dev *client)
2729e69bcd8SThomas Zimmermann {
2739e69bcd8SThomas Zimmermann drm_fb_helper_lastclose(client->dev);
2749e69bcd8SThomas Zimmermann
2759e69bcd8SThomas Zimmermann return 0;
2769e69bcd8SThomas Zimmermann }
2779e69bcd8SThomas Zimmermann
omap_fbdev_client_hotplug(struct drm_client_dev * client)2789e69bcd8SThomas Zimmermann static int omap_fbdev_client_hotplug(struct drm_client_dev *client)
2799e69bcd8SThomas Zimmermann {
2809e69bcd8SThomas Zimmermann struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
2819e69bcd8SThomas Zimmermann struct drm_device *dev = client->dev;
2829e69bcd8SThomas Zimmermann int ret;
2839e69bcd8SThomas Zimmermann
2849e69bcd8SThomas Zimmermann if (dev->fb_helper)
2859e69bcd8SThomas Zimmermann return drm_fb_helper_hotplug_event(dev->fb_helper);
2869e69bcd8SThomas Zimmermann
2879e69bcd8SThomas Zimmermann ret = drm_fb_helper_init(dev, fb_helper);
2889e69bcd8SThomas Zimmermann if (ret)
2899e69bcd8SThomas Zimmermann goto err_drm_err;
2909e69bcd8SThomas Zimmermann
2919e69bcd8SThomas Zimmermann ret = drm_fb_helper_initial_config(fb_helper);
2929e69bcd8SThomas Zimmermann if (ret)
2939e69bcd8SThomas Zimmermann goto err_drm_fb_helper_fini;
2949e69bcd8SThomas Zimmermann
2959e69bcd8SThomas Zimmermann return 0;
2969e69bcd8SThomas Zimmermann
2979e69bcd8SThomas Zimmermann err_drm_fb_helper_fini:
2989e69bcd8SThomas Zimmermann drm_fb_helper_fini(fb_helper);
2999e69bcd8SThomas Zimmermann err_drm_err:
3009e69bcd8SThomas Zimmermann drm_err(dev, "Failed to setup fbdev emulation (ret=%d)\n", ret);
3019e69bcd8SThomas Zimmermann return ret;
3029e69bcd8SThomas Zimmermann }
3039e69bcd8SThomas Zimmermann
3049e69bcd8SThomas Zimmermann static const struct drm_client_funcs omap_fbdev_client_funcs = {
3059e69bcd8SThomas Zimmermann .owner = THIS_MODULE,
3069e69bcd8SThomas Zimmermann .unregister = omap_fbdev_client_unregister,
3079e69bcd8SThomas Zimmermann .restore = omap_fbdev_client_restore,
3089e69bcd8SThomas Zimmermann .hotplug = omap_fbdev_client_hotplug,
3099e69bcd8SThomas Zimmermann };
3109e69bcd8SThomas Zimmermann
omap_fbdev_setup(struct drm_device * dev)3119e69bcd8SThomas Zimmermann void omap_fbdev_setup(struct drm_device *dev)
3129e69bcd8SThomas Zimmermann {
3139e69bcd8SThomas Zimmermann struct omap_fbdev *fbdev;
3149e69bcd8SThomas Zimmermann struct drm_fb_helper *helper;
3159e69bcd8SThomas Zimmermann int ret;
3169e69bcd8SThomas Zimmermann
3179e69bcd8SThomas Zimmermann drm_WARN(dev, !dev->registered, "Device has not been registered.\n");
3189e69bcd8SThomas Zimmermann drm_WARN(dev, dev->fb_helper, "fb_helper is already set!\n");
319da777721SPeter Ujfalusi
3208bb0daffSRob Clark fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
321fffddfd6SLinus Torvalds if (!fbdev)
322c176060aSNathan Chancellor return;
3238bb0daffSRob Clark helper = &fbdev->base;
3248bb0daffSRob Clark
3256c80a93bSThomas Zimmermann drm_fb_helper_prepare(dev, helper, 32, &omap_fb_helper_funcs);
3268bb0daffSRob Clark
3279e69bcd8SThomas Zimmermann ret = drm_client_init(dev, &helper->client, "fbdev", &omap_fbdev_client_funcs);
328efd1f06bSTomi Valkeinen if (ret)
3299e69bcd8SThomas Zimmermann goto err_drm_client_init;
3308bb0daffSRob Clark
3319e69bcd8SThomas Zimmermann INIT_WORK(&fbdev->work, pan_worker);
3329e69bcd8SThomas Zimmermann
3339e69bcd8SThomas Zimmermann drm_client_register(&helper->client);
3348bb0daffSRob Clark
335efd1f06bSTomi Valkeinen return;
3368bb0daffSRob Clark
3379e69bcd8SThomas Zimmermann err_drm_client_init:
3383fb1f62fSThomas Zimmermann drm_fb_helper_unprepare(helper);
3398bb0daffSRob Clark kfree(fbdev);
3408bb0daffSRob Clark }
341