/* exynos_drm_fbdev.c * * Copyright (c) 2011 Samsung Electronics Co., Ltd. * Authors: * Inki Dae * Joonyoung Shim * Seung-Woo Kim * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "drmP.h" #include "drm_crtc.h" #include "drm_fb_helper.h" #include "drm_crtc_helper.h" #include "exynos_drm_drv.h" #include "exynos_drm_fb.h" #include "exynos_drm_gem.h" #include "exynos_drm_buf.h" #define MAX_CONNECTOR 4 #define PREFERRED_BPP 32 #define to_exynos_fbdev(x) container_of(x, struct exynos_drm_fbdev,\ drm_fb_helper) struct exynos_drm_fbdev { struct drm_fb_helper drm_fb_helper; struct exynos_drm_gem_obj *exynos_gem_obj; }; static int exynos_drm_fbdev_set_par(struct fb_info *info) { struct fb_var_screeninfo *var = &info->var; switch (var->bits_per_pixel) { case 32: case 24: case 18: case 16: case 12: info->fix.visual = FB_VISUAL_TRUECOLOR; break; case 1: info->fix.visual = FB_VISUAL_MONO01; break; default: info->fix.visual = FB_VISUAL_PSEUDOCOLOR; break; } info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8; return drm_fb_helper_set_par(info); } static struct fb_ops exynos_drm_fb_ops = { .owner = THIS_MODULE, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, .fb_check_var = drm_fb_helper_check_var, .fb_set_par = exynos_drm_fbdev_set_par, .fb_blank = drm_fb_helper_blank, .fb_pan_display = drm_fb_helper_pan_display, .fb_setcmap = drm_fb_helper_setcmap, }; static int exynos_drm_fbdev_update(struct drm_fb_helper *helper, struct drm_framebuffer *fb) { struct fb_info *fbi = helper->fbdev; struct drm_device *dev = helper->dev; struct exynos_drm_gem_buf *buffer; unsigned int size = fb->width * fb->height * (fb->bits_per_pixel >> 3); unsigned long offset; DRM_DEBUG_KMS("%s\n", __FILE__); drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); drm_fb_helper_fill_var(fbi, helper, fb->width, fb->height); buffer = exynos_drm_fb_get_buf(fb); if (!buffer) { DRM_LOG_KMS("buffer is null.\n"); return -EFAULT; } offset = fbi->var.xoffset * (fb->bits_per_pixel >> 3); offset += fbi->var.yoffset * fb->pitches[0]; dev->mode_config.fb_base = (resource_size_t)buffer->dma_addr; fbi->screen_base = buffer->kvaddr + offset; fbi->fix.smem_start = (unsigned long)(buffer->dma_addr + offset); fbi->screen_size = size; fbi->fix.smem_len = size; return 0; } static int exynos_drm_fbdev_create(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { struct exynos_drm_fbdev *exynos_fbdev = to_exynos_fbdev(helper); struct exynos_drm_gem_obj *exynos_gem_obj; struct drm_device *dev = helper->dev; struct fb_info *fbi; struct drm_mode_fb_cmd2 mode_cmd = { 0 }; struct platform_device *pdev = dev->platformdev; unsigned long size; int ret; DRM_DEBUG_KMS("%s\n", __FILE__); DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d\n", sizes->surface_width, sizes->surface_height, sizes->surface_bpp); mode_cmd.width = sizes->surface_width; mode_cmd.height = sizes->surface_height; mode_cmd.pitches[0] = sizes->surface_width * (sizes->surface_bpp >> 3); mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); mutex_lock(&dev->struct_mutex); fbi = framebuffer_alloc(0, &pdev->dev); if (!fbi) { DRM_ERROR("failed to allocate fb info.\n"); ret = -ENOMEM; goto out; } size = mode_cmd.pitches[0] * mode_cmd.height; exynos_gem_obj = exynos_drm_gem_create(dev, size); if (IS_ERR(exynos_gem_obj)) { ret = PTR_ERR(exynos_gem_obj); goto out; } exynos_fbdev->exynos_gem_obj = exynos_gem_obj; helper->fb = exynos_drm_framebuffer_init(dev, &mode_cmd, &exynos_gem_obj->base); if (IS_ERR_OR_NULL(helper->fb)) { DRM_ERROR("failed to create drm framebuffer.\n"); ret = PTR_ERR(helper->fb); goto out; } helper->fbdev = fbi; fbi->par = helper; fbi->flags = FBINFO_FLAG_DEFAULT; fbi->fbops = &exynos_drm_fb_ops; ret = fb_alloc_cmap(&fbi->cmap, 256, 0); if (ret) { DRM_ERROR("failed to allocate cmap.\n"); goto out; } ret = exynos_drm_fbdev_update(helper, helper->fb); if (ret < 0) { fb_dealloc_cmap(&fbi->cmap); goto out; } /* * if failed, all resources allocated above would be released by * drm_mode_config_cleanup() when drm_load() had been called prior * to any specific driver such as fimd or hdmi driver. */ out: mutex_unlock(&dev->struct_mutex); return ret; } static bool exynos_drm_fbdev_is_samefb(struct drm_framebuffer *fb, struct drm_fb_helper_surface_size *sizes) { if (fb->width != sizes->surface_width) return false; if (fb->height != sizes->surface_height) return false; if (fb->bits_per_pixel != sizes->surface_bpp) return false; if (fb->depth != sizes->surface_depth) return false; return true; } static int exynos_drm_fbdev_recreate(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { struct drm_device *dev = helper->dev; struct exynos_drm_fbdev *exynos_fbdev = to_exynos_fbdev(helper); struct exynos_drm_gem_obj *exynos_gem_obj; struct drm_framebuffer *fb = helper->fb; struct drm_mode_fb_cmd2 mode_cmd = { 0 }; unsigned long size; DRM_DEBUG_KMS("%s\n", __FILE__); if (exynos_drm_fbdev_is_samefb(fb, sizes)) return 0; mode_cmd.width = sizes->surface_width; mode_cmd.height = sizes->surface_height; mode_cmd.pitches[0] = sizes->surface_width * (sizes->surface_bpp >> 3); mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); if (exynos_fbdev->exynos_gem_obj) exynos_drm_gem_destroy(exynos_fbdev->exynos_gem_obj); if (fb->funcs->destroy) fb->funcs->destroy(fb); size = mode_cmd.pitches[0] * mode_cmd.height; exynos_gem_obj = exynos_drm_gem_create(dev, size); if (IS_ERR(exynos_gem_obj)) return PTR_ERR(exynos_gem_obj); exynos_fbdev->exynos_gem_obj = exynos_gem_obj; helper->fb = exynos_drm_framebuffer_init(dev, &mode_cmd, &exynos_gem_obj->base); if (IS_ERR_OR_NULL(helper->fb)) { DRM_ERROR("failed to create drm framebuffer.\n"); return PTR_ERR(helper->fb); } return exynos_drm_fbdev_update(helper, helper->fb); } static int exynos_drm_fbdev_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { int ret = 0; DRM_DEBUG_KMS("%s\n", __FILE__); if (!helper->fb) { ret = exynos_drm_fbdev_create(helper, sizes); if (ret < 0) { DRM_ERROR("failed to create fbdev.\n"); return ret; } /* * fb_helper expects a value more than 1 if succeed * because register_framebuffer() should be called. */ ret = 1; } else { ret = exynos_drm_fbdev_recreate(helper, sizes); if (ret < 0) { DRM_ERROR("failed to reconfigure fbdev\n"); return ret; } } return ret; } static struct drm_fb_helper_funcs exynos_drm_fb_helper_funcs = { .fb_probe = exynos_drm_fbdev_probe, }; int exynos_drm_fbdev_init(struct drm_device *dev) { struct exynos_drm_fbdev *fbdev; struct exynos_drm_private *private = dev->dev_private; struct drm_fb_helper *helper; unsigned int num_crtc; int ret; DRM_DEBUG_KMS("%s\n", __FILE__); if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector) return 0; fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); if (!fbdev) { DRM_ERROR("failed to allocate drm fbdev.\n"); return -ENOMEM; } private->fb_helper = helper = &fbdev->drm_fb_helper; helper->funcs = &exynos_drm_fb_helper_funcs; num_crtc = dev->mode_config.num_crtc; ret = drm_fb_helper_init(dev, helper, num_crtc, MAX_CONNECTOR); if (ret < 0) { DRM_ERROR("failed to initialize drm fb helper.\n"); goto err_init; } ret = drm_fb_helper_single_add_all_connectors(helper); if (ret < 0) { DRM_ERROR("failed to register drm_fb_helper_connector.\n"); goto err_setup; } ret = drm_fb_helper_initial_config(helper, PREFERRED_BPP); if (ret < 0) { DRM_ERROR("failed to set up hw configuration.\n"); goto err_setup; } return 0; err_setup: drm_fb_helper_fini(helper); err_init: private->fb_helper = NULL; kfree(fbdev); return ret; } static void exynos_drm_fbdev_destroy(struct drm_device *dev, struct drm_fb_helper *fb_helper) { struct drm_framebuffer *fb; /* release drm framebuffer and real buffer */ if (fb_helper->fb && fb_helper->fb->funcs) { fb = fb_helper->fb; if (fb && fb->funcs->destroy) fb->funcs->destroy(fb); } /* release linux framebuffer */ if (fb_helper->fbdev) { struct fb_info *info; int ret; info = fb_helper->fbdev; ret = unregister_framebuffer(info); if (ret < 0) DRM_DEBUG_KMS("failed unregister_framebuffer()\n"); if (info->cmap.len) fb_dealloc_cmap(&info->cmap); framebuffer_release(info); } drm_fb_helper_fini(fb_helper); } void exynos_drm_fbdev_fini(struct drm_device *dev) { struct exynos_drm_private *private = dev->dev_private; struct exynos_drm_fbdev *fbdev; if (!private || !private->fb_helper) return; fbdev = to_exynos_fbdev(private->fb_helper); if (fbdev->exynos_gem_obj) exynos_drm_gem_destroy(fbdev->exynos_gem_obj); exynos_drm_fbdev_destroy(dev, private->fb_helper); kfree(fbdev); private->fb_helper = NULL; } void exynos_drm_fbdev_restore_mode(struct drm_device *dev) { struct exynos_drm_private *private = dev->dev_private; if (!private || !private->fb_helper) return; drm_fb_helper_restore_fbdev_mode(private->fb_helper); } int exynos_drm_fbdev_reinit(struct drm_device *dev) { struct exynos_drm_private *private = dev->dev_private; struct drm_fb_helper *fb_helper; int ret; if (!private) return -EINVAL; /* * if all sub drivers were unloaded then num_connector is 0 * so at this time, the framebuffers also should be destroyed. */ if (!dev->mode_config.num_connector) { exynos_drm_fbdev_fini(dev); return 0; } fb_helper = private->fb_helper; if (fb_helper) { struct list_head temp_list; INIT_LIST_HEAD(&temp_list); /* * fb_helper is reintialized but kernel fb is reused * so kernel_fb_list need to be backuped and restored */ if (!list_empty(&fb_helper->kernel_fb_list)) list_replace_init(&fb_helper->kernel_fb_list, &temp_list); drm_fb_helper_fini(fb_helper); ret = drm_fb_helper_init(dev, fb_helper, dev->mode_config.num_crtc, MAX_CONNECTOR); if (ret < 0) { DRM_ERROR("failed to initialize drm fb helper\n"); return ret; } if (!list_empty(&temp_list)) list_replace(&temp_list, &fb_helper->kernel_fb_list); ret = drm_fb_helper_single_add_all_connectors(fb_helper); if (ret < 0) { DRM_ERROR("failed to add fb helper to connectors\n"); goto err; } ret = drm_fb_helper_initial_config(fb_helper, PREFERRED_BPP); if (ret < 0) { DRM_ERROR("failed to set up hw configuration.\n"); goto err; } } else { /* * if drm_load() failed whem drm load() was called prior * to specific drivers, fb_helper must be NULL and so * this fuction should be called again to re-initialize and * re-configure the fb helper. it means that this function * has been called by the specific drivers. */ ret = exynos_drm_fbdev_init(dev); } return ret; err: /* * if drm_load() failed when drm load() was called prior * to specific drivers, the fb_helper must be NULL and so check it. */ if (fb_helper) drm_fb_helper_fini(fb_helper); return ret; } MODULE_AUTHOR("Inki Dae "); MODULE_AUTHOR("Joonyoung Shim "); MODULE_AUTHOR("Seung-Woo Kim "); MODULE_DESCRIPTION("Samsung SoC DRM FBDEV Driver"); MODULE_LICENSE("GPL");