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 void omap_fbdev_flush(struct fb_info *fbi, int x, int y, int w, int h);
  46static struct drm_fb_helper *get_fb(struct fb_info *fbi);
  47
  48static ssize_t omap_fbdev_write(struct fb_info *fbi, const char __user *buf,
  49                size_t count, loff_t *ppos)
  50{
  51        ssize_t res;
  52
  53        res = fb_sys_write(fbi, buf, count, ppos);
  54        omap_fbdev_flush(fbi, 0, 0, fbi->var.xres, fbi->var.yres);
  55
  56        return res;
  57}
  58
  59static void omap_fbdev_fillrect(struct fb_info *fbi,
  60                const struct fb_fillrect *rect)
  61{
  62        sys_fillrect(fbi, rect);
  63        omap_fbdev_flush(fbi, rect->dx, rect->dy, rect->width, rect->height);
  64}
  65
  66static void omap_fbdev_copyarea(struct fb_info *fbi,
  67                const struct fb_copyarea *area)
  68{
  69        sys_copyarea(fbi, area);
  70        omap_fbdev_flush(fbi, area->dx, area->dy, area->width, area->height);
  71}
  72
  73static void omap_fbdev_imageblit(struct fb_info *fbi,
  74                const struct fb_image *image)
  75{
  76        sys_imageblit(fbi, image);
  77        omap_fbdev_flush(fbi, image->dx, image->dy,
  78                                image->width, image->height);
  79}
  80
  81static void pan_worker(struct work_struct *work)
  82{
  83        struct omap_fbdev *fbdev = container_of(work, struct omap_fbdev, work);
  84        struct fb_info *fbi = fbdev->base.fbdev;
  85        int npages;
  86
  87        /* DMM roll shifts in 4K pages: */
  88        npages = fbi->fix.line_length >> PAGE_SHIFT;
  89        omap_gem_roll(fbdev->bo, fbi->var.yoffset * npages);
  90}
  91
  92static int omap_fbdev_pan_display(struct fb_var_screeninfo *var,
  93                struct fb_info *fbi)
  94{
  95        struct drm_fb_helper *helper = get_fb(fbi);
  96        struct omap_fbdev *fbdev = to_omap_fbdev(helper);
  97
  98        if (!helper)
  99                goto fallback;
 100
 101        if (!fbdev->ywrap_enabled)
 102                goto fallback;
 103
 104        if (drm_can_sleep()) {
 105                pan_worker(&fbdev->work);
 106        } else {
 107                struct omap_drm_private *priv = helper->dev->dev_private;
 108                queue_work(priv->wq, &fbdev->work);
 109        }
 110
 111        return 0;
 112
 113fallback:
 114        return drm_fb_helper_pan_display(var, fbi);
 115}
 116
 117static struct fb_ops omap_fb_ops = {
 118        .owner = THIS_MODULE,
 119
 120        /* Note: to properly handle manual update displays, we wrap the
 121         * basic fbdev ops which write to the framebuffer
 122         */
 123        .fb_read = fb_sys_read,
 124        .fb_write = omap_fbdev_write,
 125        .fb_fillrect = omap_fbdev_fillrect,
 126        .fb_copyarea = omap_fbdev_copyarea,
 127        .fb_imageblit = omap_fbdev_imageblit,
 128
 129        .fb_check_var = drm_fb_helper_check_var,
 130        .fb_set_par = drm_fb_helper_set_par,
 131        .fb_pan_display = omap_fbdev_pan_display,
 132        .fb_blank = drm_fb_helper_blank,
 133        .fb_setcmap = drm_fb_helper_setcmap,
 134};
 135
 136static int omap_fbdev_create(struct drm_fb_helper *helper,
 137                struct drm_fb_helper_surface_size *sizes)
 138{
 139        struct omap_fbdev *fbdev = to_omap_fbdev(helper);
 140        struct drm_device *dev = helper->dev;
 141        struct omap_drm_private *priv = dev->dev_private;
 142        struct drm_framebuffer *fb = NULL;
 143        union omap_gem_size gsize;
 144        struct fb_info *fbi = NULL;
 145        struct drm_mode_fb_cmd2 mode_cmd = {0};
 146        dma_addr_t paddr;
 147        int ret;
 148
 149        /* only doing ARGB32 since this is what is needed to alpha-blend
 150         * with video overlays:
 151         */
 152        sizes->surface_bpp = 32;
 153        sizes->surface_depth = 32;
 154
 155        DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width,
 156                        sizes->surface_height, sizes->surface_bpp,
 157                        sizes->fb_width, sizes->fb_height);
 158
 159        mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
 160                        sizes->surface_depth);
 161
 162        mode_cmd.width = sizes->surface_width;
 163        mode_cmd.height = sizes->surface_height;
 164
 165        mode_cmd.pitches[0] = align_pitch(
 166                        mode_cmd.width * ((sizes->surface_bpp + 7) / 8),
 167                        mode_cmd.width, sizes->surface_bpp);
 168
 169        fbdev->ywrap_enabled = priv->has_dmm && ywrap_enabled;
 170        if (fbdev->ywrap_enabled) {
 171                /* need to align pitch to page size if using DMM scrolling */
 172                mode_cmd.pitches[0] = ALIGN(mode_cmd.pitches[0], PAGE_SIZE);
 173        }
 174
 175        /* allocate backing bo */
 176        gsize = (union omap_gem_size){
 177                .bytes = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height),
 178        };
 179        DBG("allocating %d bytes for fb %d", gsize.bytes, dev->primary->index);
 180        fbdev->bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC);
 181        if (!fbdev->bo) {
 182                dev_err(dev->dev, "failed to allocate buffer object\n");
 183                ret = -ENOMEM;
 184                goto fail;
 185        }
 186
 187        fb = omap_framebuffer_init(dev, &mode_cmd, &fbdev->bo);
 188        if (IS_ERR(fb)) {
 189                dev_err(dev->dev, "failed to allocate fb\n");
 190                /* note: if fb creation failed, we can't rely on fb destroy
 191                 * to unref the bo:
 192                 */
 193                drm_gem_object_unreference(fbdev->bo);
 194                ret = PTR_ERR(fb);
 195                goto fail;
 196        }
 197
 198        /* note: this keeps the bo pinned.. which is perhaps not ideal,
 199         * but is needed as long as we use fb_mmap() to mmap to userspace
 200         * (since this happens using fix.smem_start).  Possibly we could
 201         * implement our own mmap using GEM mmap support to avoid this
 202         * (non-tiled buffer doesn't need to be pinned for fbcon to write
 203         * to it).  Then we just need to be sure that we are able to re-
 204         * pin it in case of an opps.
 205         */
 206        ret = omap_gem_get_paddr(fbdev->bo, &paddr, true);
 207        if (ret) {
 208                dev_err(dev->dev,
 209                        "could not map (paddr)!  Skipping framebuffer alloc\n");
 210                ret = -ENOMEM;
 211                goto fail;
 212        }
 213
 214        mutex_lock(&dev->struct_mutex);
 215
 216        fbi = framebuffer_alloc(0, dev->dev);
 217        if (!fbi) {
 218                dev_err(dev->dev, "failed to allocate fb info\n");
 219                ret = -ENOMEM;
 220                goto fail_unlock;
 221        }
 222
 223        DBG("fbi=%p, dev=%p", fbi, dev);
 224
 225        fbdev->fb = fb;
 226        helper->fb = fb;
 227        helper->fbdev = fbi;
 228
 229        fbi->par = helper;
 230        fbi->flags = FBINFO_DEFAULT;
 231        fbi->fbops = &omap_fb_ops;
 232
 233        strcpy(fbi->fix.id, MODULE_NAME);
 234
 235        ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
 236        if (ret) {
 237                ret = -ENOMEM;
 238                goto fail_unlock;
 239        }
 240
 241        drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
 242        drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height);
 243
 244        dev->mode_config.fb_base = paddr;
 245
 246        fbi->screen_base = omap_gem_vaddr(fbdev->bo);
 247        fbi->screen_size = fbdev->bo->size;
 248        fbi->fix.smem_start = paddr;
 249        fbi->fix.smem_len = fbdev->bo->size;
 250
 251        /* if we have DMM, then we can use it for scrolling by just
 252         * shuffling pages around in DMM rather than doing sw blit.
 253         */
 254        if (fbdev->ywrap_enabled) {
 255                DRM_INFO("Enabling DMM ywrap scrolling\n");
 256                fbi->flags |= FBINFO_HWACCEL_YWRAP | FBINFO_READS_FAST;
 257                fbi->fix.ywrapstep = 1;
 258        }
 259
 260
 261        DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres);
 262        DBG("allocated %dx%d fb", fbdev->fb->width, fbdev->fb->height);
 263
 264        mutex_unlock(&dev->struct_mutex);
 265
 266        return 0;
 267
 268fail_unlock:
 269        mutex_unlock(&dev->struct_mutex);
 270fail:
 271
 272        if (ret) {
 273                if (fbi)
 274                        framebuffer_release(fbi);
 275                if (fb) {
 276                        drm_framebuffer_unregister_private(fb);
 277                        drm_framebuffer_remove(fb);
 278                }
 279        }
 280
 281        return ret;
 282}
 283
 284static void omap_crtc_fb_gamma_set(struct drm_crtc *crtc,
 285                u16 red, u16 green, u16 blue, int regno)
 286{
 287        DBG("fbdev: set gamma");
 288}
 289
 290static void omap_crtc_fb_gamma_get(struct drm_crtc *crtc,
 291                u16 *red, u16 *green, u16 *blue, int regno)
 292{
 293        DBG("fbdev: get gamma");
 294}
 295
 296static struct drm_fb_helper_funcs omap_fb_helper_funcs = {
 297        .gamma_set = omap_crtc_fb_gamma_set,
 298        .gamma_get = omap_crtc_fb_gamma_get,
 299        .fb_probe = omap_fbdev_create,
 300};
 301
 302static struct drm_fb_helper *get_fb(struct fb_info *fbi)
 303{
 304        if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) {
 305                /* these are not the fb's you're looking for */
 306                return NULL;
 307        }
 308        return fbi->par;
 309}
 310
 311/* flush an area of the framebuffer (in case of manual update display that
 312 * is not automatically flushed)
 313 */
 314static void omap_fbdev_flush(struct fb_info *fbi, int x, int y, int w, int h)
 315{
 316        struct drm_fb_helper *helper = get_fb(fbi);
 317
 318        if (!helper)
 319                return;
 320
 321        VERB("flush fbdev: %d,%d %dx%d, fbi=%p", x, y, w, h, fbi);
 322
 323        omap_framebuffer_flush(helper->fb, x, y, w, h);
 324}
 325
 326/* initialize fbdev helper */
 327struct drm_fb_helper *omap_fbdev_init(struct drm_device *dev)
 328{
 329        struct omap_drm_private *priv = dev->dev_private;
 330        struct omap_fbdev *fbdev = NULL;
 331        struct drm_fb_helper *helper;
 332        int ret = 0;
 333
 334        fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
 335        if (!fbdev)
 336                goto fail;
 337
 338        INIT_WORK(&fbdev->work, pan_worker);
 339
 340        helper = &fbdev->base;
 341
 342        helper->funcs = &omap_fb_helper_funcs;
 343
 344        ret = drm_fb_helper_init(dev, helper,
 345                        priv->num_crtcs, priv->num_connectors);
 346        if (ret) {
 347                dev_err(dev->dev, "could not init fbdev: ret=%d\n", ret);
 348                goto fail;
 349        }
 350
 351        drm_fb_helper_single_add_all_connectors(helper);
 352
 353        /* disable all the possible outputs/crtcs before entering KMS mode */
 354        drm_helper_disable_unused_functions(dev);
 355
 356        drm_fb_helper_initial_config(helper, 32);
 357
 358        priv->fbdev = helper;
 359
 360        return helper;
 361
 362fail:
 363        kfree(fbdev);
 364        return NULL;
 365}
 366
 367void omap_fbdev_free(struct drm_device *dev)
 368{
 369        struct omap_drm_private *priv = dev->dev_private;
 370        struct drm_fb_helper *helper = priv->fbdev;
 371        struct omap_fbdev *fbdev;
 372        struct fb_info *fbi;
 373
 374        DBG();
 375
 376        fbi = helper->fbdev;
 377
 378        /* only cleanup framebuffer if it is present */
 379        if (fbi) {
 380                unregister_framebuffer(fbi);
 381                framebuffer_release(fbi);
 382        }
 383
 384        drm_fb_helper_fini(helper);
 385
 386        fbdev = to_omap_fbdev(priv->fbdev);
 387
 388        /* this will free the backing object */
 389        if (fbdev->fb) {
 390                drm_framebuffer_unregister_private(fbdev->fb);
 391                drm_framebuffer_remove(fbdev->fb);
 392        }
 393
 394        kfree(fbdev);
 395
 396        priv->fbdev = NULL;
 397}
 398