diff options
Diffstat (limited to 'drivers/gpu/drm/omap/omap_fb.c')
-rw-r--r-- | drivers/gpu/drm/omap/omap_fb.c | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/drivers/gpu/drm/omap/omap_fb.c b/drivers/gpu/drm/omap/omap_fb.c new file mode 100644 index 00000000000..b3ac7fb21ac --- /dev/null +++ b/drivers/gpu/drm/omap/omap_fb.c @@ -0,0 +1,368 @@ +/* + * linux/drivers/gpu/drm/omap/omap_fb.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <plat/vram.h> + +#include <linux/omap_gpu.h> +#include "omap_gpu_priv.h" + +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + + +static char *def_vram; +module_param_named(vram, def_vram, charp, 0); + +/* + * framebuffer funcs + */ + +#define to_omap_framebuffer(x) container_of(x, struct omap_framebuffer, base) + +struct omap_framebuffer { + struct drm_framebuffer base; + + /* framebuffer size/phys-addr/virt-addr */ + int size; + unsigned long paddr; + void __iomem *vaddr; +}; + + +/* copied from omapfb-main.c to preserve vram param syntax */ +static int parse_vram_param(const char *param, int max_entries, + unsigned long *sizes, unsigned long *paddrs) +{ + int fbnum; + unsigned long size; + unsigned long paddr = 0; + char *p, *start; + + DBG("vram: %s", param); + + start = (char *)param; + + while (1) { + p = start; + + fbnum = simple_strtoul(p, &p, 10); + + if ((p == param) || (*p != ':') || (fbnum >= max_entries)) + return -EINVAL; + + size = memparse(p + 1, &p); + + if (!size) + return -EINVAL; + + paddr = 0; + + if (*p == '@') { + paddr = simple_strtoul(p + 1, &p, 16); + + if (!paddr) + return -EINVAL; + } + + paddrs[fbnum] = paddr; + sizes[fbnum] = size; + + if (*p == 0) + break; + + if (*p != ',') + return -EINVAL; + + ++p; + + start = p; + } + + return 0; +} + +#define MAX_FBS 10 + +static int allocate_vram(struct drm_framebuffer *fb, int idx, int size) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + struct drm_device *dev = fb->dev; + unsigned long sizes[MAX_FBS] = {0}; + unsigned long paddrs[MAX_FBS] = {0}; + unsigned long paddr = 0; + int ret = ret; + + if (idx >= MAX_FBS) { + dev_err(dev->dev, "invalid fb number: %d\n", idx); + goto fail; + } + + if (def_vram) { + if (parse_vram_param(def_vram, MAX_FBS, sizes, paddrs)) { + dev_err(dev->dev, "failed to parse vram parameter\n"); + memset(&sizes, 0, sizeof(sizes)); + memset(&paddrs, 0, sizeof(paddrs)); + } else { + size = sizes[idx]; + paddr = paddrs[idx]; + } + } + + size = PAGE_ALIGN(size); + + if (paddr) { + DBG("reserving %d bytes at %lx for fb %d", size, paddr, idx); + ret = omap_vram_reserve(paddr, size); + } else { + DBG("allocating %d bytes for fb %d", size, idx); + ret = omap_vram_alloc(OMAP_VRAM_MEMTYPE_SDRAM, size, &paddr); + } + + if (ret) { + dev_err(dev->dev, "failed to allocate vram\n"); + goto fail; + } + + omap_fb->size = size; + omap_fb->paddr = paddr; + omap_fb->vaddr = ioremap_wc(paddr, size); + + if (!omap_fb->vaddr) { + dev_err(dev->dev, "failed to ioremap framebuffer\n"); + ret = -ENOMEM; + goto fail; + } + + //memset(vaddr, 0, size); + + return 0; + +fail: + if (omap_fb->paddr) { + omap_vram_free(paddr, size); + } + + omap_fb->size = 0; + omap_fb->vaddr = NULL; + omap_fb->paddr = 0; + + return ret; +} + +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); + DBG("framebuffer: get handle: %p", omap_fb); + + // TODO, I suppose this really should be some sort of GEM handle + // to the framebuffer object, in case it needs to be mapped or + // something. Right now this will go-exist badly with PVR, who + // implements the mmap() fxn.. need to think about how to handle + // this.. + + *handle = 42; + + return 0; +} + +static void omap_framebuffer_destroy(struct drm_framebuffer *fb) +{ + /* omap_vram_free() doesn't really do what you'd think.. (or at + * least not if you think it'd return vram to the pool), so + * disabling this for now until there is a better way to alloc + * and free coherant.. + */ + DBG("destroy: FB ID: %d (%p)", fb->base.id, 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, +}; + +int omap_framebuffer_get_buffer(struct drm_framebuffer *fb, int x, int y, + void **vaddr, unsigned long *paddr, int *screen_width) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + int bpp = 4; //XXX fb->depth / 8; + unsigned long offset; + + offset = (x * bpp) + (y * fb->pitch); + + *vaddr = omap_fb->vaddr + offset; + *paddr = omap_fb->paddr + offset; + *screen_width = fb->pitch / bpp; + + return omap_fb->size; +} + +/* 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; +} +EXPORT_SYMBOL(omap_framebuffer_get_next_connector); + +/* 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); + } + } +} +EXPORT_SYMBOL(omap_framebuffer_flush); + +struct drm_framebuffer * omap_framebuffer_create(struct drm_device *dev, + struct drm_file *file, struct drm_mode_fb_cmd *mode_cmd) +{ + return omap_framebuffer_init(dev, mode_cmd); +} + +struct drm_framebuffer * omap_framebuffer_init(struct drm_device *dev, + struct drm_mode_fb_cmd *mode_cmd) +{ + struct omap_gpu_private *priv = dev->dev_private; + struct omap_framebuffer *omap_fb = NULL; + struct drm_framebuffer *fb; + int ret; + + /* in case someone tries to feed us a completely bogus stride: */ + mode_cmd->pitch = max(mode_cmd->pitch, + mode_cmd->width * mode_cmd->bpp / 8); + + /* pvr needs to have a stride that is a multiple of 8 pixels: */ + mode_cmd->pitch = ALIGN(mode_cmd->pitch, 8 * (mode_cmd->bpp / 8)); + + /* for now, we can only support a single fb.. so we need to resize + * the old one instead of creating a new one of different size. If + * we can eventually get rid of the vram pool and had a better way + * to allocate contiguous memory after boot time, this restriction + * should be lifted. + */ + if (priv->fb) { + fb = priv->fb; + DBG("recycle: FB ID: %d (%p)", fb->base.id, fb); + goto recycle; /* ugg, we need CMA! */ + } + + DBG("create framebuffer: dev=%p, mode_cmd=%p (%dx%d)", dev, + mode_cmd, mode_cmd->width, mode_cmd->height); + + 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); + + ret = allocate_vram(fb, dev->primary->index, + mode_cmd->pitch * mode_cmd->height); + if (ret) { + dev_err(dev->dev, "failed to allocate framebuffer\n"); + goto fail; + } + +recycle: + drm_helper_mode_fill_fb_struct(fb, mode_cmd); + + priv->fb = fb; + + if (priv->fbdev) { + /* if fbdev is already created, we need to update it to + * be attached to the new fb + */ + omap_fbdev_update(priv->fbdev, fb); + } + + return fb; + +fail: + if (omap_fb) { + kfree(omap_fb); + } + return NULL; +} + |