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