From f9aa76a85248565ff13dc0e10633c4343a059f71 Mon Sep 17 00:00:00 2001 From: Dave Airlie Date: Tue, 17 Apr 2012 14:12:29 +0100 Subject: drm/kms: driver for virtual cirrus under qemu This is the initial driver for emulated cirrus GPU found in qemu. This driver only supports the emulated GPU and doesn't attempt to bind to any real cirrus GPUs. This driver is intended to be used with xf86-video-modesetting in userspace. It requires at least version 0.3.0 This follow the same design as ast and mgag200, and is based on work done by Matthew Garrett previously. This GPU has no hw cursor, and it can't scanout 32-bpp, only packed 24-bpp. i.e. it sucks. Reviewed-by: Adam Jackson Signed-off-by: Dave Airlie --- drivers/gpu/drm/cirrus/cirrus_fbdev.c | 307 ++++++++++++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 drivers/gpu/drm/cirrus/cirrus_fbdev.c (limited to 'drivers/gpu/drm/cirrus/cirrus_fbdev.c') diff --git a/drivers/gpu/drm/cirrus/cirrus_fbdev.c b/drivers/gpu/drm/cirrus/cirrus_fbdev.c new file mode 100644 index 000000000000..9a276a536992 --- /dev/null +++ b/drivers/gpu/drm/cirrus/cirrus_fbdev.c @@ -0,0 +1,307 @@ +/* + * Copyright 2012 Red Hat + * + * This file is subject to the terms and conditions of the GNU General + * Public License version 2. See the file COPYING in the main + * directory of this archive for more details. + * + * Authors: Matthew Garrett + * Dave Airlie + */ +#include +#include "drmP.h" +#include "drm.h" +#include "drm_fb_helper.h" + +#include + +#include "cirrus_drv.h" + +static void cirrus_dirty_update(struct cirrus_fbdev *afbdev, + int x, int y, int width, int height) +{ + int i; + struct drm_gem_object *obj; + struct cirrus_bo *bo; + int src_offset, dst_offset; + int bpp = (afbdev->gfb.base.bits_per_pixel + 7)/8; + int ret; + bool unmap = false; + + obj = afbdev->gfb.obj; + bo = gem_to_cirrus_bo(obj); + + ret = cirrus_bo_reserve(bo, true); + if (ret) { + DRM_ERROR("failed to reserve fb bo\n"); + return; + } + + if (!bo->kmap.virtual) { + ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.num_pages, &bo->kmap); + if (ret) { + DRM_ERROR("failed to kmap fb updates\n"); + cirrus_bo_unreserve(bo); + return; + } + unmap = true; + } + for (i = y; i < y + height; i++) { + /* assume equal stride for now */ + src_offset = dst_offset = i * afbdev->gfb.base.pitches[0] + (x * bpp); + memcpy_toio(bo->kmap.virtual + src_offset, afbdev->sysram + src_offset, width * bpp); + + } + if (unmap) + ttm_bo_kunmap(&bo->kmap); + + cirrus_bo_unreserve(bo); +} + +static void cirrus_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct cirrus_fbdev *afbdev = info->par; + sys_fillrect(info, rect); + cirrus_dirty_update(afbdev, rect->dx, rect->dy, rect->width, + rect->height); +} + +static void cirrus_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + struct cirrus_fbdev *afbdev = info->par; + sys_copyarea(info, area); + cirrus_dirty_update(afbdev, area->dx, area->dy, area->width, + area->height); +} + +static void cirrus_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + struct cirrus_fbdev *afbdev = info->par; + sys_imageblit(info, image); + cirrus_dirty_update(afbdev, image->dx, image->dy, image->width, + image->height); +} + + +static struct fb_ops cirrusfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = drm_fb_helper_check_var, + .fb_set_par = drm_fb_helper_set_par, + .fb_fillrect = cirrus_fillrect, + .fb_copyarea = cirrus_copyarea, + .fb_imageblit = cirrus_imageblit, + .fb_pan_display = drm_fb_helper_pan_display, + .fb_blank = drm_fb_helper_blank, + .fb_setcmap = drm_fb_helper_setcmap, +}; + +static int cirrusfb_create_object(struct cirrus_fbdev *afbdev, + struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_object **gobj_p) +{ + struct drm_device *dev = afbdev->helper.dev; + u32 bpp, depth; + u32 size; + struct drm_gem_object *gobj; + + int ret = 0; + drm_fb_get_bpp_depth(mode_cmd->pixel_format, &depth, &bpp); + + if (bpp > 24) + return -EINVAL; + size = mode_cmd->pitches[0] * mode_cmd->height; + ret = cirrus_gem_create(dev, size, true, &gobj); + if (ret) + return ret; + + *gobj_p = gobj; + return ret; +} + +static int cirrusfb_create(struct cirrus_fbdev *gfbdev, + struct drm_fb_helper_surface_size *sizes) +{ + struct drm_device *dev = gfbdev->helper.dev; + struct cirrus_device *cdev = gfbdev->helper.dev->dev_private; + struct fb_info *info; + struct drm_framebuffer *fb; + struct drm_mode_fb_cmd2 mode_cmd; + struct device *device = &dev->pdev->dev; + void *sysram; + struct drm_gem_object *gobj = NULL; + struct cirrus_bo *bo = NULL; + int size, ret; + + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + mode_cmd.pitches[0] = mode_cmd.width * ((sizes->surface_bpp + 7) / 8); + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); + size = mode_cmd.pitches[0] * mode_cmd.height; + + ret = cirrusfb_create_object(gfbdev, &mode_cmd, &gobj); + if (ret) { + DRM_ERROR("failed to create fbcon backing object %d\n", ret); + return ret; + } + + bo = gem_to_cirrus_bo(gobj); + + sysram = vmalloc(size); + if (!sysram) + return -ENOMEM; + + info = framebuffer_alloc(0, device); + if (info == NULL) + return -ENOMEM; + + info->par = gfbdev; + + ret = cirrus_framebuffer_init(cdev->dev, &gfbdev->gfb, &mode_cmd, gobj); + if (ret) + return ret; + + gfbdev->sysram = sysram; + gfbdev->size = size; + + fb = &gfbdev->gfb.base; + if (!fb) { + DRM_INFO("fb is NULL\n"); + return -EINVAL; + } + + /* setup helper */ + gfbdev->helper.fb = fb; + gfbdev->helper.fbdev = info; + + strcpy(info->fix.id, "cirrusdrmfb"); + + + info->flags = FBINFO_DEFAULT; + info->fbops = &cirrusfb_ops; + + drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth); + drm_fb_helper_fill_var(info, &gfbdev->helper, sizes->fb_width, + sizes->fb_height); + + /* setup aperture base/size for vesafb takeover */ + info->apertures = alloc_apertures(1); + if (!info->apertures) { + ret = -ENOMEM; + goto out_iounmap; + } + info->apertures->ranges[0].base = cdev->dev->mode_config.fb_base; + info->apertures->ranges[0].size = cdev->mc.vram_size; + + info->screen_base = sysram; + info->screen_size = size; + + info->fix.mmio_start = 0; + info->fix.mmio_len = 0; + + ret = fb_alloc_cmap(&info->cmap, 256, 0); + if (ret) { + DRM_ERROR("%s: can't allocate color map\n", info->fix.id); + ret = -ENOMEM; + goto out_iounmap; + } + + DRM_INFO("fb mappable at 0x%lX\n", info->fix.smem_start); + DRM_INFO("vram aper at 0x%lX\n", (unsigned long)info->fix.smem_start); + DRM_INFO("size %lu\n", (unsigned long)info->fix.smem_len); + DRM_INFO("fb depth is %d\n", fb->depth); + DRM_INFO(" pitch is %d\n", fb->pitches[0]); + + return 0; +out_iounmap: + return ret; +} + +static int cirrus_fb_find_or_create_single(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size + *sizes) +{ + struct cirrus_fbdev *gfbdev = (struct cirrus_fbdev *)helper; + int new_fb = 0; + int ret; + + if (!helper->fb) { + ret = cirrusfb_create(gfbdev, sizes); + if (ret) + return ret; + new_fb = 1; + } + return new_fb; +} + +static int cirrus_fbdev_destroy(struct drm_device *dev, + struct cirrus_fbdev *gfbdev) +{ + struct fb_info *info; + struct cirrus_framebuffer *gfb = &gfbdev->gfb; + + if (gfbdev->helper.fbdev) { + info = gfbdev->helper.fbdev; + + unregister_framebuffer(info); + if (info->cmap.len) + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + } + + if (gfb->obj) { + drm_gem_object_unreference_unlocked(gfb->obj); + gfb->obj = NULL; + } + + vfree(gfbdev->sysram); + drm_fb_helper_fini(&gfbdev->helper); + drm_framebuffer_cleanup(&gfb->base); + + return 0; +} + +static struct drm_fb_helper_funcs cirrus_fb_helper_funcs = { + .gamma_set = cirrus_crtc_fb_gamma_set, + .gamma_get = cirrus_crtc_fb_gamma_get, + .fb_probe = cirrus_fb_find_or_create_single, +}; + +int cirrus_fbdev_init(struct cirrus_device *cdev) +{ + struct cirrus_fbdev *gfbdev; + int ret; + int bpp_sel = 24; + + /*bpp_sel = 8;*/ + gfbdev = kzalloc(sizeof(struct cirrus_fbdev), GFP_KERNEL); + if (!gfbdev) + return -ENOMEM; + + cdev->mode_info.gfbdev = gfbdev; + gfbdev->helper.funcs = &cirrus_fb_helper_funcs; + + ret = drm_fb_helper_init(cdev->dev, &gfbdev->helper, + cdev->num_crtc, CIRRUSFB_CONN_LIMIT); + if (ret) { + kfree(gfbdev); + return ret; + } + drm_fb_helper_single_add_all_connectors(&gfbdev->helper); + drm_fb_helper_initial_config(&gfbdev->helper, bpp_sel); + + return 0; +} + +void cirrus_fbdev_fini(struct cirrus_device *cdev) +{ + if (!cdev->mode_info.gfbdev) + return; + + cirrus_fbdev_destroy(cdev->dev, cdev->mode_info.gfbdev); + kfree(cdev->mode_info.gfbdev); + cdev->mode_info.gfbdev = NULL; +} -- cgit v1.2.3-59-g8ed1b