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