2011-10-04 19:19:01 +09:00
/* exynos_drm_fbdev.c
*
* Copyright ( c ) 2011 Samsung Electronics Co . , Ltd .
* Authors :
* Inki Dae < inki . dae @ samsung . com >
* Joonyoung Shim < jy0922 . shim @ samsung . com >
* Seung - Woo Kim < sw0312 . kim @ samsung . com >
*
2012-12-18 02:30:17 +09:00
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation ; either version 2 of the License , or ( at your
* option ) any later version .
2011-10-04 19:19:01 +09:00
*/
2012-10-02 18:01:07 +01:00
# include <drm/drmP.h>
# include <drm/drm_crtc.h>
# include <drm/drm_fb_helper.h>
# include <drm/drm_crtc_helper.h>
2011-10-04 19:19:01 +09:00
# include "exynos_drm_drv.h"
# include "exynos_drm_fb.h"
2011-11-12 15:23:32 +09:00
# include "exynos_drm_gem.h"
2012-12-21 17:59:20 +09:00
# include "exynos_drm_iommu.h"
2011-10-04 19:19:01 +09:00
# 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 {
2011-12-13 14:46:57 +09:00
struct drm_fb_helper drm_fb_helper ;
struct exynos_drm_gem_obj * exynos_gem_obj ;
2011-10-04 19:19:01 +09:00
} ;
2012-11-19 13:55:28 +05:30
static int exynos_drm_fb_mmap ( struct fb_info * info ,
struct vm_area_struct * vma )
{
struct drm_fb_helper * helper = info - > par ;
struct exynos_drm_fbdev * exynos_fbd = to_exynos_fbdev ( helper ) ;
struct exynos_drm_gem_obj * exynos_gem_obj = exynos_fbd - > exynos_gem_obj ;
struct exynos_drm_gem_buf * buffer = exynos_gem_obj - > buffer ;
unsigned long vm_size ;
int ret ;
DRM_DEBUG_KMS ( " %s \n " , __func__ ) ;
vma - > vm_flags | = VM_IO | VM_DONTEXPAND | VM_DONTDUMP ;
vm_size = vma - > vm_end - vma - > vm_start ;
if ( vm_size > buffer - > size )
return - EINVAL ;
2012-12-07 17:51:27 +09:00
ret = dma_mmap_attrs ( helper - > dev - > dev , vma , buffer - > pages ,
2012-11-19 13:55:28 +05:30
buffer - > dma_addr , buffer - > size , & buffer - > dma_attrs ) ;
if ( ret < 0 ) {
DRM_ERROR ( " failed to mmap. \n " ) ;
return ret ;
}
return 0 ;
}
2011-10-04 19:19:01 +09:00
static struct fb_ops exynos_drm_fb_ops = {
. owner = THIS_MODULE ,
2012-11-19 13:55:28 +05:30
. fb_mmap = exynos_drm_fb_mmap ,
2011-10-04 19:19:01 +09:00
. fb_fillrect = cfb_fillrect ,
. fb_copyarea = cfb_copyarea ,
. fb_imageblit = cfb_imageblit ,
. fb_check_var = drm_fb_helper_check_var ,
2012-02-01 11:38:37 +01:00
. fb_set_par = drm_fb_helper_set_par ,
2011-10-04 19:19:01 +09:00
. fb_blank = drm_fb_helper_blank ,
. fb_pan_display = drm_fb_helper_pan_display ,
. fb_setcmap = drm_fb_helper_setcmap ,
} ;
2011-10-14 13:29:46 +09:00
static int exynos_drm_fbdev_update ( struct drm_fb_helper * helper ,
2011-11-04 13:44:38 +09:00
struct drm_framebuffer * fb )
2011-10-04 19:19:01 +09:00
{
struct fb_info * fbi = helper - > fbdev ;
struct drm_device * dev = helper - > dev ;
2011-11-12 15:23:32 +09:00
struct exynos_drm_gem_buf * buffer ;
2011-11-04 13:44:38 +09:00
unsigned int size = fb - > width * fb - > height * ( fb - > bits_per_pixel > > 3 ) ;
2011-10-14 13:29:46 +09:00
unsigned long offset ;
2011-10-04 19:19:01 +09:00
DRM_DEBUG_KMS ( " %s \n " , __FILE__ ) ;
2011-12-20 00:06:49 +02:00
drm_fb_helper_fill_fix ( fbi , fb - > pitches [ 0 ] , fb - > depth ) ;
2011-11-04 13:44:38 +09:00
drm_fb_helper_fill_var ( fbi , helper , fb - > width , fb - > height ) ;
2011-10-04 19:19:01 +09:00
2011-12-15 14:36:22 +09:00
/* RGB formats use only one buffer */
buffer = exynos_drm_fb_buffer ( fb , 0 ) ;
2011-11-12 15:23:32 +09:00
if ( ! buffer ) {
DRM_LOG_KMS ( " buffer is null. \n " ) ;
2011-10-14 13:29:46 +09:00
return - EFAULT ;
}
2011-10-04 19:19:01 +09:00
2012-12-07 17:51:27 +09:00
/* map pages with kernel virtual space. */
if ( ! buffer - > kvaddr ) {
2012-12-21 17:59:20 +09:00
if ( is_drm_iommu_supported ( dev ) ) {
unsigned int nr_pages = buffer - > size > > PAGE_SHIFT ;
buffer - > kvaddr = vmap ( buffer - > pages , nr_pages , VM_MAP ,
2012-12-07 17:51:27 +09:00
pgprot_writecombine ( PAGE_KERNEL ) ) ;
2012-12-21 17:59:20 +09:00
} else {
phys_addr_t dma_addr = buffer - > dma_addr ;
if ( dma_addr )
buffer - > kvaddr = phys_to_virt ( dma_addr ) ;
else
buffer - > kvaddr = ( void __iomem * ) NULL ;
}
2012-12-07 17:51:27 +09:00
if ( ! buffer - > kvaddr ) {
DRM_ERROR ( " failed to map pages to kernel space. \n " ) ;
return - EIO ;
}
}
2012-08-20 20:05:56 +09:00
/* buffer count to framebuffer always is 1 at booting time. */
exynos_drm_fb_set_buf_cnt ( fb , 1 ) ;
2011-10-14 13:29:46 +09:00
offset = fbi - > var . xoffset * ( fb - > bits_per_pixel > > 3 ) ;
2011-12-20 00:06:49 +02:00
offset + = fbi - > var . yoffset * fb - > pitches [ 0 ] ;
2011-10-04 19:19:01 +09:00
2011-11-12 15:23:32 +09:00
dev - > mode_config . fb_base = ( resource_size_t ) buffer - > dma_addr ;
fbi - > screen_base = buffer - > kvaddr + offset ;
2012-12-21 17:59:20 +09:00
if ( is_drm_iommu_supported ( dev ) )
fbi - > fix . smem_start = ( unsigned long )
2012-11-22 12:18:35 +05:30
( page_to_phys ( sg_page ( buffer - > sgt - > sgl ) ) + offset ) ;
2012-12-21 17:59:20 +09:00
else
fbi - > fix . smem_start = ( unsigned long ) buffer - > dma_addr ;
2011-10-04 19:19:01 +09:00
fbi - > screen_size = size ;
fbi - > fix . smem_len = size ;
2011-10-14 13:29:46 +09:00
return 0 ;
2011-10-04 19:19:01 +09:00
}
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 ) ;
2011-12-13 14:46:57 +09:00
struct exynos_drm_gem_obj * exynos_gem_obj ;
2011-10-04 19:19:01 +09:00
struct drm_device * dev = helper - > dev ;
struct fb_info * fbi ;
2011-12-08 15:05:19 +09:00
struct drm_mode_fb_cmd2 mode_cmd = { 0 } ;
2011-10-04 19:19:01 +09:00
struct platform_device * pdev = dev - > platformdev ;
2011-12-13 14:46:57 +09:00
unsigned long size ;
2011-10-04 19:19:01 +09:00
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 ;
2011-12-08 15:05:19 +09:00
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 ) ;
2011-10-04 19:19:01 +09:00
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 ;
}
2011-12-13 14:46:57 +09:00
size = mode_cmd . pitches [ 0 ] * mode_cmd . height ;
2012-03-16 18:47:05 +09:00
/* 0 means to allocate physically continuous memory */
exynos_gem_obj = exynos_drm_gem_create ( dev , 0 , size ) ;
2011-12-13 14:46:57 +09:00
if ( IS_ERR ( exynos_gem_obj ) ) {
ret = PTR_ERR ( exynos_gem_obj ) ;
2012-12-07 18:06:43 +09:00
goto err_release_framebuffer ;
2011-12-13 14:46:57 +09:00
}
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 ) ) {
2011-10-04 19:19:01 +09:00
DRM_ERROR ( " failed to create drm framebuffer. \n " ) ;
2011-12-13 14:46:57 +09:00
ret = PTR_ERR ( helper - > fb ) ;
2012-12-07 18:06:43 +09:00
goto err_destroy_gem ;
2011-10-04 19:19:01 +09:00
}
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 " ) ;
2012-12-07 18:06:43 +09:00
goto err_destroy_framebuffer ;
2011-10-04 19:19:01 +09:00
}
2011-11-04 13:44:38 +09:00
ret = exynos_drm_fbdev_update ( helper , helper - > fb ) ;
2012-12-07 18:06:43 +09:00
if ( ret < 0 )
goto err_dealloc_cmap ;
mutex_unlock ( & dev - > struct_mutex ) ;
return ret ;
err_dealloc_cmap :
fb_dealloc_cmap ( & fbi - > cmap ) ;
err_destroy_framebuffer :
drm_framebuffer_cleanup ( helper - > fb ) ;
err_destroy_gem :
exynos_drm_gem_destroy ( exynos_gem_obj ) ;
err_release_framebuffer :
framebuffer_release ( fbi ) ;
2011-10-04 19:19:01 +09:00
/*
* 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 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__ ) ;
2012-02-15 11:25:21 +09:00
/*
* with ! helper - > fb , it means that this funcion is called first time
* and after that , the helper - > fb would be used as clone mode .
*/
2011-10-04 19:19:01 +09:00
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 ;
}
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 )
{
2012-12-07 17:51:27 +09:00
struct exynos_drm_fbdev * exynos_fbd = to_exynos_fbdev ( fb_helper ) ;
struct exynos_drm_gem_obj * exynos_gem_obj = exynos_fbd - > exynos_gem_obj ;
2011-10-04 19:19:01 +09:00
struct drm_framebuffer * fb ;
2012-12-21 17:59:20 +09:00
if ( is_drm_iommu_supported ( dev ) & & exynos_gem_obj - > buffer - > kvaddr )
2012-12-07 17:51:27 +09:00
vunmap ( exynos_gem_obj - > buffer - > kvaddr ) ;
2011-10-04 19:19:01 +09:00
/* release drm framebuffer and real buffer */
if ( fb_helper - > fb & & fb_helper - > fb - > funcs ) {
fb = fb_helper - > fb ;
2012-09-05 21:48:38 +00:00
if ( fb )
drm_framebuffer_remove ( fb ) ;
2011-10-04 19:19:01 +09:00
}
/* 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 ) ;
2011-12-13 14:46:57 +09:00
if ( fbdev - > exynos_gem_obj )
exynos_drm_gem_destroy ( fbdev - > exynos_gem_obj ) ;
2011-10-04 19:19:01 +09:00
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 ) ;
}