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 >
*
* 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"
2011-11-12 15:23:32 +09:00
# include "exynos_drm_gem.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
} ;
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 ,
} ;
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
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 ;
fbi - > fix . smem_start = ( unsigned long ) ( buffer - > dma_addr + offset ) ;
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 ;
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 ) ) {
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 ) ;
2011-10-04 19:19:01 +09:00
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 ;
}
2011-11-04 13:44:38 +09:00
ret = exynos_drm_fbdev_update ( helper , helper - > fb ) ;
2011-12-13 14:46:57 +09:00
if ( ret < 0 ) {
2011-10-14 13:29:46 +09:00
fb_dealloc_cmap ( & fbi - > cmap ) ;
2011-12-13 14:46:57 +09:00
goto out ;
}
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-14 11:18:28 +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 )
{
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 ) ;
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 ) ;
}
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 ;
2011-10-14 13:29:50 +09:00
/*
* if all sub drivers were unloaded then num_connector is 0
* so at this time , the framebuffers also should be destroyed .
*/
2011-10-04 19:19:01 +09:00
if ( ! dev - > mode_config . num_connector ) {
exynos_drm_fbdev_fini ( dev ) ;
return 0 ;
}
fb_helper = private - > fb_helper ;
if ( fb_helper ) {
2011-11-04 13:41:46 +09:00
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 ) ;
2011-10-04 19:19:01 +09:00
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 ;
}
2011-11-04 13:41:46 +09:00
if ( ! list_empty ( & temp_list ) )
list_replace ( & temp_list , & fb_helper - > kernel_fb_list ) ;
2011-10-04 19:19:01 +09:00
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 .
*/
2011-10-14 13:29:50 +09:00
ret = exynos_drm_fbdev_init ( dev ) ;
2011-10-04 19:19:01 +09:00
}
2011-10-14 13:29:49 +09:00
return ret ;
2011-10-04 19:19:01 +09:00
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 <inki.dae@samsung.com> " ) ;
MODULE_AUTHOR ( " Joonyoung Shim <jy0922.shim@samsung.com> " ) ;
MODULE_AUTHOR ( " Seung-Woo Kim <sw0312.kim@samsung.com> " ) ;
MODULE_DESCRIPTION ( " Samsung SoC DRM FBDEV Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;