/* * drivers/staging/omapdrm/omap_fb.c * * Copyright (C) 2011 Texas Instruments * Author: Rob Clark * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "omap_drv.h" #include "drm_crtc.h" #include "drm_crtc_helper.h" /* * framebuffer funcs */ #define to_omap_framebuffer(x) container_of(x, struct omap_framebuffer, base) struct omap_framebuffer { struct drm_framebuffer base; struct drm_gem_object *bo; int size; dma_addr_t paddr; }; static int omap_framebuffer_create_handle(struct drm_framebuffer *fb, struct drm_file *file_priv, unsigned int *handle) { struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); return drm_gem_handle_create(file_priv, omap_fb->bo, handle); } static void omap_framebuffer_destroy(struct drm_framebuffer *fb) { struct drm_device *dev = fb->dev; struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); DBG("destroy: FB ID: %d (%p)", fb->base.id, fb); drm_framebuffer_cleanup(fb); if (omap_fb->bo) { if (omap_fb->paddr && omap_gem_put_paddr(omap_fb->bo)) dev_err(dev->dev, "could not unmap!\n"); drm_gem_object_unreference_unlocked(omap_fb->bo); } kfree(omap_fb); } static int omap_framebuffer_dirty(struct drm_framebuffer *fb, struct drm_file *file_priv, unsigned flags, unsigned color, struct drm_clip_rect *clips, unsigned num_clips) { int i; for (i = 0; i < num_clips; i++) { omap_framebuffer_flush(fb, clips[i].x1, clips[i].y1, clips[i].x2 - clips[i].x1, clips[i].y2 - clips[i].y1); } return 0; } static const struct drm_framebuffer_funcs omap_framebuffer_funcs = { .create_handle = omap_framebuffer_create_handle, .destroy = omap_framebuffer_destroy, .dirty = omap_framebuffer_dirty, }; /* returns the buffer size */ int omap_framebuffer_get_buffer(struct drm_framebuffer *fb, int x, int y, void **vaddr, dma_addr_t *paddr, unsigned int *screen_width) { struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); int bpp = fb->bits_per_pixel / 8; unsigned long offset; offset = (x * bpp) + (y * fb->pitch); if (vaddr) { void *bo_vaddr = omap_gem_vaddr(omap_fb->bo); /* note: we can only count on having a vaddr for buffers that * are allocated physically contiguously to begin with (ie. * dma_alloc_coherent()). But this should be ok because it * is only used by legacy fbdev */ BUG_ON(IS_ERR_OR_NULL(bo_vaddr)); *vaddr = bo_vaddr + offset; } *paddr = omap_fb->paddr + offset; *screen_width = fb->pitch / bpp; return omap_fb->size - offset; } struct drm_gem_object *omap_framebuffer_bo(struct drm_framebuffer *fb) { struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); return omap_fb->bo; } /* iterate thru all the connectors, returning ones that are attached * to the same fb.. */ struct drm_connector *omap_framebuffer_get_next_connector( struct drm_framebuffer *fb, struct drm_connector *from) { struct drm_device *dev = fb->dev; struct list_head *connector_list = &dev->mode_config.connector_list; struct drm_connector *connector = from; if (!from) { return list_first_entry(connector_list, typeof(*from), head); } list_for_each_entry_from(connector, connector_list, head) { if (connector != from) { struct drm_encoder *encoder = connector->encoder; struct drm_crtc *crtc = encoder ? encoder->crtc : NULL; if (crtc && crtc->fb == fb) { return connector; } } } return NULL; } /* flush an area of the framebuffer (in case of manual update display that * is not automatically flushed) */ void omap_framebuffer_flush(struct drm_framebuffer *fb, int x, int y, int w, int h) { struct drm_connector *connector = NULL; VERB("flush: %d,%d %dx%d, fb=%p", x, y, w, h, fb); while ((connector = omap_framebuffer_get_next_connector(fb, connector))) { /* only consider connectors that are part of a chain */ if (connector->encoder && connector->encoder->crtc) { /* TODO: maybe this should propagate thru the crtc who * could do the coordinate translation.. */ struct drm_crtc *crtc = connector->encoder->crtc; int cx = max(0, x - crtc->x); int cy = max(0, y - crtc->y); int cw = w + (x - crtc->x) - cx; int ch = h + (y - crtc->y) - cy; omap_connector_flush(connector, cx, cy, cw, ch); } } } struct drm_framebuffer *omap_framebuffer_create(struct drm_device *dev, struct drm_file *file, struct drm_mode_fb_cmd *mode_cmd) { struct drm_gem_object *bo; struct drm_framebuffer *fb; bo = drm_gem_object_lookup(dev, file, mode_cmd->handle); if (!bo) { return ERR_PTR(-ENOENT); } fb = omap_framebuffer_init(dev, mode_cmd, bo); if (!fb) { return ERR_PTR(-ENOMEM); } return fb; } struct drm_framebuffer *omap_framebuffer_init(struct drm_device *dev, struct drm_mode_fb_cmd *mode_cmd, struct drm_gem_object *bo) { struct omap_framebuffer *omap_fb; struct drm_framebuffer *fb = NULL; int size, ret; DBG("create framebuffer: dev=%p, mode_cmd=%p (%dx%d@%d)", dev, mode_cmd, mode_cmd->width, mode_cmd->height, mode_cmd->bpp); /* in case someone tries to feed us a completely bogus stride: */ mode_cmd->pitch = align_pitch(mode_cmd->pitch, mode_cmd->width, mode_cmd->bpp); omap_fb = kzalloc(sizeof(*omap_fb), GFP_KERNEL); if (!omap_fb) { dev_err(dev->dev, "could not allocate fb\n"); goto fail; } fb = &omap_fb->base; ret = drm_framebuffer_init(dev, fb, &omap_framebuffer_funcs); if (ret) { dev_err(dev->dev, "framebuffer init failed: %d\n", ret); goto fail; } DBG("create: FB ID: %d (%p)", fb->base.id, fb); size = PAGE_ALIGN(mode_cmd->pitch * mode_cmd->height); if (size > bo->size) { dev_err(dev->dev, "provided buffer object is too small!\n"); goto fail; } omap_fb->bo = bo; omap_fb->size = size; if (omap_gem_get_paddr(bo, &omap_fb->paddr, true)) { dev_err(dev->dev, "could not map (paddr)!\n"); goto fail; } drm_helper_mode_fill_fb_struct(fb, mode_cmd); return fb; fail: if (fb) { omap_framebuffer_destroy(fb); } return NULL; }