2012-11-15 21:28:22 +00:00
/*
2013-03-22 16:34:08 +02:00
* Copyright ( C ) 2012 - 2013 Avionic Design GmbH
2012-11-15 21:28:22 +00:00
* Copyright ( C ) 2012 NVIDIA CORPORATION . All rights reserved .
*
2013-03-22 16:34:08 +02:00
* Based on the KMS / FB CMA helpers
* Copyright ( C ) 2012 Analog Device Inc .
*
2012-11-15 21:28:22 +00:00
* 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 .
*/
# include "drm.h"
2013-03-22 16:34:08 +02:00
# include "gem.h"
static inline struct tegra_fb * to_tegra_fb ( struct drm_framebuffer * fb )
{
return container_of ( fb , struct tegra_fb , base ) ;
}
2013-10-31 13:28:50 +01:00
# ifdef CONFIG_DRM_TEGRA_FBDEV
2013-03-22 16:34:08 +02:00
static inline struct tegra_fbdev * to_tegra_fbdev ( struct drm_fb_helper * helper )
{
return container_of ( helper , struct tegra_fbdev , base ) ;
}
2013-10-31 13:28:50 +01:00
# endif
2013-03-22 16:34:08 +02:00
struct tegra_bo * tegra_fb_get_plane ( struct drm_framebuffer * framebuffer ,
unsigned int index )
{
struct tegra_fb * fb = to_tegra_fb ( framebuffer ) ;
if ( index > = drm_format_num_planes ( framebuffer - > pixel_format ) )
return NULL ;
return fb - > planes [ index ] ;
}
2013-10-07 09:47:58 +02:00
bool tegra_fb_is_bottom_up ( struct drm_framebuffer * framebuffer )
{
struct tegra_fb * fb = to_tegra_fb ( framebuffer ) ;
if ( fb - > planes [ 0 ] - > flags & TEGRA_BO_BOTTOM_UP )
return true ;
return false ;
}
2013-10-04 22:34:01 +02:00
bool tegra_fb_is_tiled ( struct drm_framebuffer * framebuffer )
{
struct tegra_fb * fb = to_tegra_fb ( framebuffer ) ;
if ( fb - > planes [ 0 ] - > flags & TEGRA_BO_TILED )
return true ;
return false ;
}
2013-03-22 16:34:08 +02:00
static void tegra_fb_destroy ( struct drm_framebuffer * framebuffer )
{
struct tegra_fb * fb = to_tegra_fb ( framebuffer ) ;
unsigned int i ;
for ( i = 0 ; i < fb - > num_planes ; i + + ) {
struct tegra_bo * bo = fb - > planes [ i ] ;
if ( bo )
drm_gem_object_unreference_unlocked ( & bo - > gem ) ;
}
drm_framebuffer_cleanup ( framebuffer ) ;
kfree ( fb - > planes ) ;
kfree ( fb ) ;
}
static int tegra_fb_create_handle ( struct drm_framebuffer * framebuffer ,
struct drm_file * file , unsigned int * handle )
{
struct tegra_fb * fb = to_tegra_fb ( framebuffer ) ;
return drm_gem_handle_create ( file , & fb - > planes [ 0 ] - > gem , handle ) ;
}
static struct drm_framebuffer_funcs tegra_fb_funcs = {
. destroy = tegra_fb_destroy ,
. create_handle = tegra_fb_create_handle ,
} ;
static struct tegra_fb * tegra_fb_alloc ( struct drm_device * drm ,
struct drm_mode_fb_cmd2 * mode_cmd ,
struct tegra_bo * * planes ,
unsigned int num_planes )
{
struct tegra_fb * fb ;
unsigned int i ;
int err ;
fb = kzalloc ( sizeof ( * fb ) , GFP_KERNEL ) ;
if ( ! fb )
return ERR_PTR ( - ENOMEM ) ;
fb - > planes = kzalloc ( num_planes * sizeof ( * planes ) , GFP_KERNEL ) ;
2013-11-12 13:25:26 +03:00
if ( ! fb - > planes ) {
kfree ( fb ) ;
2013-03-22 16:34:08 +02:00
return ERR_PTR ( - ENOMEM ) ;
2013-11-12 13:25:26 +03:00
}
2013-03-22 16:34:08 +02:00
fb - > num_planes = num_planes ;
drm_helper_mode_fill_fb_struct ( & fb - > base , mode_cmd ) ;
for ( i = 0 ; i < fb - > num_planes ; i + + )
fb - > planes [ i ] = planes [ i ] ;
err = drm_framebuffer_init ( drm , & fb - > base , & tegra_fb_funcs ) ;
if ( err < 0 ) {
dev_err ( drm - > dev , " failed to initialize framebuffer: %d \n " ,
err ) ;
kfree ( fb - > planes ) ;
kfree ( fb ) ;
return ERR_PTR ( err ) ;
}
return fb ;
}
static struct drm_framebuffer * tegra_fb_create ( struct drm_device * drm ,
struct drm_file * file ,
struct drm_mode_fb_cmd2 * cmd )
{
unsigned int hsub , vsub , i ;
struct tegra_bo * planes [ 4 ] ;
struct drm_gem_object * gem ;
struct tegra_fb * fb ;
int err ;
hsub = drm_format_horz_chroma_subsampling ( cmd - > pixel_format ) ;
vsub = drm_format_vert_chroma_subsampling ( cmd - > pixel_format ) ;
for ( i = 0 ; i < drm_format_num_planes ( cmd - > pixel_format ) ; i + + ) {
unsigned int width = cmd - > width / ( i ? hsub : 1 ) ;
unsigned int height = cmd - > height / ( i ? vsub : 1 ) ;
unsigned int size , bpp ;
gem = drm_gem_object_lookup ( drm , file , cmd - > handles [ i ] ) ;
if ( ! gem ) {
err = - ENXIO ;
goto unreference ;
}
bpp = drm_format_plane_cpp ( cmd - > pixel_format , i ) ;
size = ( height - 1 ) * cmd - > pitches [ i ] +
width * bpp + cmd - > offsets [ i ] ;
if ( gem - > size < size ) {
err = - EINVAL ;
goto unreference ;
}
planes [ i ] = to_tegra_bo ( gem ) ;
}
fb = tegra_fb_alloc ( drm , cmd , planes , i ) ;
if ( IS_ERR ( fb ) ) {
err = PTR_ERR ( fb ) ;
goto unreference ;
}
return & fb - > base ;
unreference :
while ( i - - )
drm_gem_object_unreference_unlocked ( & planes [ i ] - > gem ) ;
2012-11-15 21:28:22 +00:00
2013-03-22 16:34:08 +02:00
return ERR_PTR ( err ) ;
}
2013-10-31 13:28:50 +01:00
# ifdef CONFIG_DRM_TEGRA_FBDEV
2013-03-22 16:34:08 +02:00
static struct fb_ops tegra_fb_ops = {
. owner = THIS_MODULE ,
. fb_fillrect = sys_fillrect ,
. fb_copyarea = sys_copyarea ,
. fb_imageblit = sys_imageblit ,
. fb_check_var = drm_fb_helper_check_var ,
. fb_set_par = drm_fb_helper_set_par ,
. fb_blank = drm_fb_helper_blank ,
. fb_pan_display = drm_fb_helper_pan_display ,
. fb_setcmap = drm_fb_helper_setcmap ,
} ;
static int tegra_fbdev_probe ( struct drm_fb_helper * helper ,
struct drm_fb_helper_surface_size * sizes )
{
struct tegra_fbdev * fbdev = to_tegra_fbdev ( helper ) ;
struct drm_device * drm = helper - > dev ;
struct drm_mode_fb_cmd2 cmd = { 0 } ;
unsigned int bytes_per_pixel ;
struct drm_framebuffer * fb ;
unsigned long offset ;
struct fb_info * info ;
struct tegra_bo * bo ;
size_t size ;
int err ;
bytes_per_pixel = DIV_ROUND_UP ( sizes - > surface_bpp , 8 ) ;
cmd . width = sizes - > surface_width ;
cmd . height = sizes - > surface_height ;
cmd . pitches [ 0 ] = sizes - > surface_width * bytes_per_pixel ;
cmd . pixel_format = drm_mode_legacy_fb_format ( sizes - > surface_bpp ,
sizes - > surface_depth ) ;
size = cmd . pitches [ 0 ] * cmd . height ;
2013-10-04 22:34:01 +02:00
bo = tegra_bo_create ( drm , size , 0 ) ;
2013-03-22 16:34:08 +02:00
if ( IS_ERR ( bo ) )
return PTR_ERR ( bo ) ;
info = framebuffer_alloc ( 0 , drm - > dev ) ;
if ( ! info ) {
dev_err ( drm - > dev , " failed to allocate framebuffer info \n " ) ;
tegra_bo_free_object ( & bo - > gem ) ;
return - ENOMEM ;
}
fbdev - > fb = tegra_fb_alloc ( drm , & cmd , & bo , 1 ) ;
if ( IS_ERR ( fbdev - > fb ) ) {
dev_err ( drm - > dev , " failed to allocate DRM framebuffer \n " ) ;
err = PTR_ERR ( fbdev - > fb ) ;
goto release ;
}
fb = & fbdev - > fb - > base ;
helper - > fb = fb ;
helper - > fbdev = info ;
info - > par = helper ;
info - > flags = FBINFO_FLAG_DEFAULT ;
info - > fbops = & tegra_fb_ops ;
err = fb_alloc_cmap ( & info - > cmap , 256 , 0 ) ;
if ( err < 0 ) {
dev_err ( drm - > dev , " failed to allocate color map: %d \n " , err ) ;
goto destroy ;
}
drm_fb_helper_fill_fix ( info , fb - > pitches [ 0 ] , fb - > depth ) ;
drm_fb_helper_fill_var ( info , helper , fb - > width , fb - > height ) ;
offset = info - > var . xoffset * bytes_per_pixel +
info - > var . yoffset * fb - > pitches [ 0 ] ;
drm - > mode_config . fb_base = ( resource_size_t ) bo - > paddr ;
2013-11-08 13:18:14 +01:00
info - > screen_base = ( void __iomem * ) bo - > vaddr + offset ;
2013-03-22 16:34:08 +02:00
info - > screen_size = size ;
info - > fix . smem_start = ( unsigned long ) ( bo - > paddr + offset ) ;
info - > fix . smem_len = size ;
return 0 ;
destroy :
drm_framebuffer_unregister_private ( fb ) ;
tegra_fb_destroy ( fb ) ;
release :
framebuffer_release ( info ) ;
return err ;
}
static struct drm_fb_helper_funcs tegra_fb_helper_funcs = {
. fb_probe = tegra_fbdev_probe ,
} ;
static struct tegra_fbdev * tegra_fbdev_create ( struct drm_device * drm ,
unsigned int preferred_bpp ,
unsigned int num_crtc ,
unsigned int max_connectors )
{
struct drm_fb_helper * helper ;
struct tegra_fbdev * fbdev ;
int err ;
fbdev = kzalloc ( sizeof ( * fbdev ) , GFP_KERNEL ) ;
if ( ! fbdev ) {
dev_err ( drm - > dev , " failed to allocate DRM fbdev \n " ) ;
return ERR_PTR ( - ENOMEM ) ;
}
fbdev - > base . funcs = & tegra_fb_helper_funcs ;
helper = & fbdev - > base ;
err = drm_fb_helper_init ( drm , & fbdev - > base , num_crtc , max_connectors ) ;
if ( err < 0 ) {
dev_err ( drm - > dev , " failed to initialize DRM FB helper \n " ) ;
goto free ;
}
err = drm_fb_helper_single_add_all_connectors ( & fbdev - > base ) ;
if ( err < 0 ) {
dev_err ( drm - > dev , " failed to add connectors \n " ) ;
goto fini ;
}
drm_helper_disable_unused_functions ( drm ) ;
err = drm_fb_helper_initial_config ( & fbdev - > base , preferred_bpp ) ;
if ( err < 0 ) {
dev_err ( drm - > dev , " failed to set initial configuration \n " ) ;
goto fini ;
}
return fbdev ;
fini :
drm_fb_helper_fini ( & fbdev - > base ) ;
free :
kfree ( fbdev ) ;
return ERR_PTR ( err ) ;
}
static void tegra_fbdev_free ( struct tegra_fbdev * fbdev )
{
struct fb_info * info = fbdev - > base . fbdev ;
if ( info ) {
int err ;
err = unregister_framebuffer ( info ) ;
if ( err < 0 )
DRM_DEBUG_KMS ( " failed to unregister framebuffer \n " ) ;
if ( info - > cmap . len )
fb_dealloc_cmap ( & info - > cmap ) ;
framebuffer_release ( info ) ;
}
if ( fbdev - > fb ) {
drm_framebuffer_unregister_private ( & fbdev - > fb - > base ) ;
tegra_fb_destroy ( & fbdev - > fb - > base ) ;
}
drm_fb_helper_fini ( & fbdev - > base ) ;
kfree ( fbdev ) ;
}
2013-10-31 13:28:50 +01:00
void tegra_fbdev_restore_mode ( struct tegra_fbdev * fbdev )
{
if ( fbdev ) {
drm_modeset_lock_all ( fbdev - > base . dev ) ;
drm_fb_helper_restore_fbdev_mode ( & fbdev - > base ) ;
drm_modeset_unlock_all ( fbdev - > base . dev ) ;
}
}
2013-03-22 16:34:08 +02:00
static void tegra_fb_output_poll_changed ( struct drm_device * drm )
2012-11-15 21:28:22 +00:00
{
2013-09-24 13:22:17 +02:00
struct tegra_drm * tegra = drm - > dev_private ;
2012-11-15 21:28:22 +00:00
2013-09-24 13:22:17 +02:00
if ( tegra - > fbdev )
drm_fb_helper_hotplug_event ( & tegra - > fbdev - > base ) ;
2012-11-15 21:28:22 +00:00
}
2013-10-31 13:28:50 +01:00
# endif
2012-11-15 21:28:22 +00:00
static const struct drm_mode_config_funcs tegra_drm_mode_funcs = {
2013-03-22 16:34:08 +02:00
. fb_create = tegra_fb_create ,
2013-10-31 13:28:50 +01:00
# ifdef CONFIG_DRM_TEGRA_FBDEV
2013-03-22 16:34:08 +02:00
. output_poll_changed = tegra_fb_output_poll_changed ,
2013-10-31 13:28:50 +01:00
# endif
2012-11-15 21:28:22 +00:00
} ;
int tegra_drm_fb_init ( struct drm_device * drm )
{
2013-10-31 13:28:50 +01:00
# ifdef CONFIG_DRM_TEGRA_FBDEV
2013-09-24 13:22:17 +02:00
struct tegra_drm * tegra = drm - > dev_private ;
2013-10-31 13:28:50 +01:00
# endif
2012-11-15 21:28:22 +00:00
drm - > mode_config . min_width = 0 ;
drm - > mode_config . min_height = 0 ;
drm - > mode_config . max_width = 4096 ;
drm - > mode_config . max_height = 4096 ;
drm - > mode_config . funcs = & tegra_drm_mode_funcs ;
2013-10-31 13:28:50 +01:00
# ifdef CONFIG_DRM_TEGRA_FBDEV
tegra - > fbdev = tegra_fbdev_create ( drm , 32 , drm - > mode_config . num_crtc ,
drm - > mode_config . num_connector ) ;
if ( IS_ERR ( tegra - > fbdev ) )
return PTR_ERR ( tegra - > fbdev ) ;
# endif
2012-11-15 21:28:22 +00:00
return 0 ;
}
void tegra_drm_fb_exit ( struct drm_device * drm )
{
2013-10-31 13:28:50 +01:00
# ifdef CONFIG_DRM_TEGRA_FBDEV
2013-09-24 13:22:17 +02:00
struct tegra_drm * tegra = drm - > dev_private ;
2012-11-15 21:28:22 +00:00
2013-09-24 13:22:17 +02:00
tegra_fbdev_free ( tegra - > fbdev ) ;
2013-10-31 13:28:50 +01:00
# endif
2012-11-15 21:28:22 +00:00
}