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
  86        /* Note: to properly handle manual update displays, we wrap the
  87         * basic fbdev ops which write to the framebuffer
  88         */
  89        .fb_read = drm_fb_helper_sys_read,
  90        .fb_write = drm_fb_helper_sys_write,
  91        .fb_fillrect = drm_fb_helper_sys_fillrect,
  92        .fb_copyarea = drm_fb_helper_sys_copyarea,
  93        .fb_imageblit = drm_fb_helper_sys_imageblit,
  94
  95        .fb_check_var = drm_fb_helper_check_var,
  96        .fb_set_par = drm_fb_helper_set_par,
  97        .fb_pan_display = omap_fbdev_pan_display,
  98        .fb_blank = drm_fb_helper_blank,
  99        .fb_setcmap = drm_fb_helper_setcmap,
 100};
 101
 102static int omap_fbdev_create(struct drm_fb_helper *helper,
 103                struct drm_fb_helper_surface_size *sizes)
 104{
 105        struct omap_fbdev *fbdev = to_omap_fbdev(helper);
 106        struct drm_device *dev = helper->dev;
 107        struct omap_drm_private *priv = dev->dev_private;
 108        struct drm_framebuffer *fb = NULL;
 109        union omap_gem_size gsize;
 110        struct fb_info *fbi = NULL;
 111        struct drm_mode_fb_cmd2 mode_cmd = {0};
 112        dma_addr_t paddr;
 113        int ret;
 114
 115        sizes->surface_bpp = 32;
 116        sizes->surface_depth = 24;
 117
 118        DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width,
 119                        sizes->surface_height, sizes->surface_bpp,
 120                        sizes->fb_width, sizes->fb_height);
 121
 122        mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
 123                        sizes->surface_depth);
 124
 125        mode_cmd.width = sizes->surface_width;
 126        mode_cmd.height = sizes->surface_height;
 127
 128        mode_cmd.pitches[0] = align_pitch(
 129                        mode_cmd.width * ((sizes->surface_bpp + 7) / 8),
 130                        mode_cmd.width, sizes->surface_bpp);
 131
 132        fbdev->ywrap_enabled = priv->has_dmm && ywrap_enabled;
 133        if (fbdev->ywrap_enabled) {
 134                /* need to align pitch to page size if using DMM scrolling */
 135                mode_cmd.pitches[0] = PAGE_ALIGN(mode_cmd.pitches[0]);
 136        }
 137
 138        /* allocate backing bo */
 139        gsize = (union omap_gem_size){
 140                .bytes = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height),
 141        };
 142        DBG("allocating %d bytes for fb %d", gsize.bytes, dev->primary->index);
 143        fbdev->bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC);
 144        if (!fbdev->bo) {
 145                dev_err(dev->dev, "failed to allocate buffer object\n");
 146                ret = -ENOMEM;
 147                goto fail;
 148        }
 149
 150        fb = omap_framebuffer_init(dev, &mode_cmd, &fbdev->bo);
 151        if (IS_ERR(fb)) {
 152                dev_err(dev->dev, "failed to allocate fb\n");
 153                /* note: if fb creation failed, we can't rely on fb destroy
 154                 * to unref the bo:
 155                 */
 156                drm_gem_object_unreference(fbdev->bo);
 157                ret = PTR_ERR(fb);
 158                goto fail;
 159        }
 160
 161        /* note: this keeps the bo pinned.. which is perhaps not ideal,
 162         * but is needed as long as we use fb_mmap() to mmap to userspace
 163         * (since this happens using fix.smem_start).  Possibly we could
 164         * implement our own mmap using GEM mmap support to avoid this
 165         * (non-tiled buffer doesn't need to be pinned for fbcon to write
 166         * to it).  Then we just need to be sure that we are able to re-
 167         * pin it in case of an opps.
 168         */
 169        ret = omap_gem_get_paddr(fbdev->bo, &paddr, true);
 170        if (ret) {
 171                dev_err(dev->dev,
 172                        "could not map (paddr)!  Skipping framebuffer alloc\n");
 173                ret = -ENOMEM;
 174                goto fail;
 175        }
 176
 177        mutex_lock(&dev->struct_mutex);
 178
 179        fbi = drm_fb_helper_alloc_fbi(helper);
 180        if (IS_ERR(fbi)) {
 181                dev_err(dev->dev, "failed to allocate fb info\n");
 182                ret = PTR_ERR(fbi);
 183                goto fail_unlock;
 184        }
 185
 186        DBG("fbi=%p, dev=%p", fbi, dev);
 187
 188        fbdev->fb = fb;
 189        helper->fb = fb;
 190
 191        fbi->par = helper;
 192        fbi->flags = FBINFO_DEFAULT;
 193        fbi->fbops = &omap_fb_ops;
 194
 195        strcpy(fbi->fix.id, MODULE_NAME);
 196
 197        drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
 198        drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height);
 199
 200        dev->mode_config.fb_base = paddr;
 201
 202        fbi->screen_base = omap_gem_vaddr(fbdev->bo);
 203        fbi->screen_size = fbdev->bo->size;
 204        fbi->fix.smem_start = paddr;
 205        fbi->fix.smem_len = fbdev->bo->size;
 206
 207        /* if we have DMM, then we can use it for scrolling by just
 208         * shuffling pages around in DMM rather than doing sw blit.
 209         */
 210        if (fbdev->ywrap_enabled) {
 211                DRM_INFO("Enabling DMM ywrap scrolling\n");
 212                fbi->flags |= FBINFO_HWACCEL_YWRAP | FBINFO_READS_FAST;
 213                fbi->fix.ywrapstep = 1;
 214        }
 215
 216
 217        DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres);
 218        DBG("allocated %dx%d fb", fbdev->fb->width, fbdev->fb->height);
 219
 220        mutex_unlock(&dev->struct_mutex);
 221
 222        return 0;
 223
 224fail_unlock:
 225        mutex_unlock(&dev->struct_mutex);
 226fail:
 227
 228        if (ret) {
 229
 230                drm_fb_helper_release_fbi(helper);
 231
 232                if (fb) {
 233                        drm_framebuffer_unregister_private(fb);
 234                        drm_framebuffer_remove(fb);
 235                }
 236        }
 237
 238        return ret;
 239}
 240
 241static const struct drm_fb_helper_funcs omap_fb_helper_funcs = {
 242        .fb_probe = omap_fbdev_create,
 243};
 244
 245static struct drm_fb_helper *get_fb(struct fb_info *fbi)
 246{
 247        if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) {
 248                /* these are not the fb's you're looking for */
 249                return NULL;
 250        }
 251        return fbi->par;
 252}
 253
 254/* initialize fbdev helper */
 255struct drm_fb_helper *omap_fbdev_init(struct drm_device *dev)
 256{
 257        struct omap_drm_private *priv = dev->dev_private;
 258        struct omap_fbdev *fbdev = NULL;
 259        struct drm_fb_helper *helper;
 260        int ret = 0;
 261
 262        fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
 263        if (!fbdev)
 264                goto fail;
 265
 266        INIT_WORK(&fbdev->work, pan_worker);
 267
 268        helper = &fbdev->base;
 269
 270        drm_fb_helper_prepare(dev, helper, &omap_fb_helper_funcs);
 271
 272        ret = drm_fb_helper_init(dev, helper,
 273                        priv->num_crtcs, priv->num_connectors);
 274        if (ret) {
 275                dev_err(dev->dev, "could not init fbdev: ret=%d\n", ret);
 276                goto fail;
 277        }
 278
 279        ret = drm_fb_helper_single_add_all_connectors(helper);
 280        if (ret)
 281                goto fini;
 282
 283        /* disable all the possible outputs/crtcs before entering KMS mode */
 284        drm_helper_disable_unused_functions(dev);
 285
 286        ret = drm_fb_helper_initial_config(helper, 32);
 287        if (ret)
 288                goto fini;
 289
 290        priv->fbdev = helper;
 291
 292        return helper;
 293
 294fini:
 295        drm_fb_helper_fini(helper);
 296fail:
 297        kfree(fbdev);
 298
 299        dev_warn(dev->dev, "omap_fbdev_init failed\n");
 300        /* well, limp along without an fbdev.. maybe X11 will work? */
 301
 302        return NULL;
 303}
 304
 305void omap_fbdev_free(struct drm_device *dev)
 306{
 307        struct omap_drm_private *priv = dev->dev_private;
 308        struct drm_fb_helper *helper = priv->fbdev;
 309        struct omap_fbdev *fbdev;
 310
 311        DBG();
 312
 313        drm_fb_helper_unregister_fbi(helper);
 314        drm_fb_helper_release_fbi(helper);
 315
 316        drm_fb_helper_fini(helper);
 317
 318        fbdev = to_omap_fbdev(priv->fbdev);
 319
 320        /* release the ref taken in omap_fbdev_create() */
 321        omap_gem_put_paddr(fbdev->bo);
 322
 323        /* this will free the backing object */
 324        if (fbdev->fb) {
 325                drm_framebuffer_unregister_private(fbdev->fb);
 326                drm_framebuffer_remove(fbdev->fb);
 327        }
 328
 329        kfree(fbdev);
 330
 331        priv->fbdev = NULL;
 332}
 333