2022-11-03 18:14:44 +03:00
// SPDX-License-Identifier: MIT
# include <linux/moduleparam.h>
2022-11-07 15:53:27 +03:00
# include <linux/vmalloc.h>
2022-11-03 18:14:44 +03:00
# include <drm/drm_crtc_helper.h>
# include <drm/drm_drv.h>
# include <drm/drm_fb_helper.h>
# include <drm/drm_framebuffer.h>
2023-03-20 18:07:48 +03:00
# include <drm/drm_gem.h>
2022-11-03 18:14:44 +03:00
# include <drm/drm_print.h>
# include <drm/drm_fbdev_generic.h>
/* @user: 1=userspace, 0=fbcon */
2023-03-20 18:07:51 +03:00
static int drm_fbdev_generic_fb_open ( struct fb_info * info , int user )
2022-11-03 18:14:44 +03:00
{
struct drm_fb_helper * fb_helper = info - > par ;
/* No need to take a ref for fbcon because it unbinds on unregister */
if ( user & & ! try_module_get ( fb_helper - > dev - > driver - > fops - > owner ) )
return - ENODEV ;
return 0 ;
}
2023-03-20 18:07:51 +03:00
static int drm_fbdev_generic_fb_release ( struct fb_info * info , int user )
2022-11-03 18:14:44 +03:00
{
struct drm_fb_helper * fb_helper = info - > par ;
if ( user )
module_put ( fb_helper - > dev - > driver - > fops - > owner ) ;
return 0 ;
}
2023-07-29 22:26:49 +03:00
FB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS ( drm_fbdev_generic ,
drm_fb_helper_damage_range ,
drm_fb_helper_damage_area ) ;
2023-05-30 18:12:27 +03:00
2023-03-20 18:07:51 +03:00
static void drm_fbdev_generic_fb_destroy ( struct fb_info * info )
2022-11-03 18:14:44 +03:00
{
2023-01-25 23:04:14 +03:00
struct drm_fb_helper * fb_helper = info - > par ;
2023-03-20 18:07:44 +03:00
void * shadow = info - > screen_buffer ;
2022-11-03 18:14:44 +03:00
if ( ! fb_helper - > dev )
return ;
2023-03-20 18:07:44 +03:00
fb_deferred_io_cleanup ( info ) ;
2022-11-03 18:14:44 +03:00
drm_fb_helper_fini ( fb_helper ) ;
2023-03-20 18:07:44 +03:00
vfree ( shadow ) ;
2022-11-03 18:14:44 +03:00
drm_client_framebuffer_delete ( fb_helper - > buffer ) ;
2023-02-16 17:06:20 +03:00
2023-03-20 18:07:44 +03:00
drm_client_release ( & fb_helper - > client ) ;
2023-02-16 17:06:20 +03:00
drm_fb_helper_unprepare ( fb_helper ) ;
2022-11-03 18:14:44 +03:00
kfree ( fb_helper ) ;
}
2023-03-20 18:07:51 +03:00
static const struct fb_ops drm_fbdev_generic_fb_ops = {
2022-11-03 18:14:44 +03:00
. owner = THIS_MODULE ,
2023-03-20 18:07:51 +03:00
. fb_open = drm_fbdev_generic_fb_open ,
. fb_release = drm_fbdev_generic_fb_release ,
2023-05-30 18:12:27 +03:00
FB_DEFAULT_DEFERRED_OPS ( drm_fbdev_generic ) ,
2023-03-20 18:07:44 +03:00
DRM_FB_HELPER_DEFAULT_OPS ,
2023-03-20 18:07:51 +03:00
. fb_destroy = drm_fbdev_generic_fb_destroy ,
2022-11-03 18:14:44 +03:00
} ;
/*
* This function uses the client API to create a framebuffer backed by a dumb buffer .
*/
2023-03-20 18:07:51 +03:00
static int drm_fbdev_generic_helper_fb_probe ( struct drm_fb_helper * fb_helper ,
struct drm_fb_helper_surface_size * sizes )
2022-11-03 18:14:44 +03:00
{
struct drm_client_dev * client = & fb_helper - > client ;
struct drm_device * dev = fb_helper - > dev ;
struct drm_client_buffer * buffer ;
2023-01-25 23:04:15 +03:00
struct fb_info * info ;
2023-03-20 18:07:48 +03:00
size_t screen_size ;
2023-03-20 18:07:49 +03:00
void * screen_buffer ;
2022-11-03 18:14:44 +03:00
u32 format ;
int ret ;
drm_dbg_kms ( dev , " surface width(%d), height(%d) and bpp(%d) \n " ,
sizes - > surface_width , sizes - > surface_height ,
sizes - > surface_bpp ) ;
format = drm_mode_legacy_fb_format ( sizes - > surface_bpp , sizes - > surface_depth ) ;
buffer = drm_client_framebuffer_create ( client , sizes - > surface_width ,
sizes - > surface_height , format ) ;
if ( IS_ERR ( buffer ) )
return PTR_ERR ( buffer ) ;
fb_helper - > buffer = buffer ;
fb_helper - > fb = buffer - > fb ;
2023-03-20 18:07:49 +03:00
2023-03-20 18:07:48 +03:00
screen_size = buffer - > gem - > size ;
2023-03-20 18:07:49 +03:00
screen_buffer = vzalloc ( screen_size ) ;
if ( ! screen_buffer ) {
ret = - ENOMEM ;
goto err_drm_client_framebuffer_delete ;
}
2022-11-03 18:14:44 +03:00
2023-01-25 23:04:15 +03:00
info = drm_fb_helper_alloc_info ( fb_helper ) ;
2023-03-20 18:07:49 +03:00
if ( IS_ERR ( info ) ) {
ret = PTR_ERR ( info ) ;
goto err_vfree ;
}
drm_fb_helper_fill_info ( info , fb_helper , sizes ) ;
2022-11-03 18:14:44 +03:00
2023-03-20 18:07:51 +03:00
info - > fbops = & drm_fbdev_generic_fb_ops ;
2022-11-03 18:14:44 +03:00
2023-03-20 18:07:49 +03:00
/* screen */
2023-03-20 18:07:44 +03:00
info - > flags | = FBINFO_VIRTFB | FBINFO_READS_FAST ;
2023-03-20 18:07:49 +03:00
info - > screen_buffer = screen_buffer ;
2023-03-20 18:07:44 +03:00
info - > fix . smem_start = page_to_phys ( vmalloc_to_page ( info - > screen_buffer ) ) ;
2023-03-20 18:07:49 +03:00
info - > fix . smem_len = screen_size ;
2023-01-21 22:24:18 +03:00
2023-03-20 18:07:49 +03:00
/* deferred I/O */
2023-03-20 18:07:44 +03:00
fb_helper - > fbdefio . delay = HZ / 20 ;
fb_helper - > fbdefio . deferred_io = drm_fb_helper_deferred_io ;
info - > fbdefio = & fb_helper - > fbdefio ;
ret = fb_deferred_io_init ( info ) ;
if ( ret )
2023-03-20 18:07:49 +03:00
goto err_drm_fb_helper_release_info ;
2022-11-03 18:14:44 +03:00
return 0 ;
2023-03-20 18:07:49 +03:00
err_drm_fb_helper_release_info :
drm_fb_helper_release_info ( fb_helper ) ;
err_vfree :
vfree ( screen_buffer ) ;
err_drm_client_framebuffer_delete :
fb_helper - > fb = NULL ;
fb_helper - > buffer = NULL ;
drm_client_framebuffer_delete ( buffer ) ;
return ret ;
2022-11-03 18:14:44 +03:00
}
2023-03-20 18:07:51 +03:00
static void drm_fbdev_generic_damage_blit_real ( struct drm_fb_helper * fb_helper ,
struct drm_clip_rect * clip ,
struct iosys_map * dst )
2022-11-03 18:14:44 +03:00
{
struct drm_framebuffer * fb = fb_helper - > fb ;
size_t offset = clip - > y1 * fb - > pitches [ 0 ] ;
size_t len = clip - > x2 - clip - > x1 ;
unsigned int y ;
void * src ;
switch ( drm_format_info_bpp ( fb - > format , 0 ) ) {
case 1 :
offset + = clip - > x1 / 8 ;
len = DIV_ROUND_UP ( len + clip - > x1 % 8 , 8 ) ;
break ;
case 2 :
offset + = clip - > x1 / 4 ;
len = DIV_ROUND_UP ( len + clip - > x1 % 4 , 4 ) ;
break ;
case 4 :
offset + = clip - > x1 / 2 ;
len = DIV_ROUND_UP ( len + clip - > x1 % 2 , 2 ) ;
break ;
default :
offset + = clip - > x1 * fb - > format - > cpp [ 0 ] ;
len * = fb - > format - > cpp [ 0 ] ;
break ;
}
src = fb_helper - > info - > screen_buffer + offset ;
iosys_map_incr ( dst , offset ) ; /* go to first pixel within clip rect */
for ( y = clip - > y1 ; y < clip - > y2 ; y + + ) {
iosys_map_memcpy_to ( dst , 0 , src , len ) ;
iosys_map_incr ( dst , fb - > pitches [ 0 ] ) ;
src + = fb - > pitches [ 0 ] ;
}
}
2023-03-20 18:07:51 +03:00
static int drm_fbdev_generic_damage_blit ( struct drm_fb_helper * fb_helper ,
struct drm_clip_rect * clip )
2022-11-03 18:14:44 +03:00
{
struct drm_client_buffer * buffer = fb_helper - > buffer ;
struct iosys_map map , dst ;
int ret ;
/*
* We have to pin the client buffer to its current location while
* flushing the shadow buffer . In the general case , concurrent
* modesetting operations could try to move the buffer and would
* fail . The modeset has to be serialized by acquiring the reservation
* object of the underlying BO here .
*
* For fbdev emulation , we only have to protect against fbdev modeset
* operations . Nothing else will involve the client buffer ' s BO . So it
* is sufficient to acquire struct drm_fb_helper . lock here .
*/
mutex_lock ( & fb_helper - > lock ) ;
ret = drm_client_buffer_vmap ( buffer , & map ) ;
if ( ret )
goto out ;
dst = map ;
2023-03-20 18:07:51 +03:00
drm_fbdev_generic_damage_blit_real ( fb_helper , clip , & dst ) ;
2022-11-03 18:14:44 +03:00
drm_client_buffer_vunmap ( buffer ) ;
out :
mutex_unlock ( & fb_helper - > lock ) ;
return ret ;
}
2023-03-20 18:07:51 +03:00
static int drm_fbdev_generic_helper_fb_dirty ( struct drm_fb_helper * helper ,
struct drm_clip_rect * clip )
2022-11-03 18:14:44 +03:00
{
struct drm_device * dev = helper - > dev ;
int ret ;
/* Call damage handlers only if necessary */
if ( ! ( clip - > x1 < clip - > x2 & & clip - > y1 < clip - > y2 ) )
return 0 ;
2023-03-20 18:07:51 +03:00
ret = drm_fbdev_generic_damage_blit ( helper , clip ) ;
2023-03-20 18:07:44 +03:00
if ( drm_WARN_ONCE ( dev , ret , " Damage blitter failed: ret=%d \n " , ret ) )
return ret ;
2022-11-03 18:14:44 +03:00
if ( helper - > fb - > funcs - > dirty ) {
ret = helper - > fb - > funcs - > dirty ( helper - > fb , NULL , 0 , 0 , clip , 1 ) ;
if ( drm_WARN_ONCE ( dev , ret , " Dirty helper failed: ret=%d \n " , ret ) )
return ret ;
}
return 0 ;
}
2023-03-20 18:07:51 +03:00
static const struct drm_fb_helper_funcs drm_fbdev_generic_helper_funcs = {
. fb_probe = drm_fbdev_generic_helper_fb_probe ,
. fb_dirty = drm_fbdev_generic_helper_fb_dirty ,
2022-11-03 18:14:44 +03:00
} ;
2023-03-20 18:07:51 +03:00
static void drm_fbdev_generic_client_unregister ( struct drm_client_dev * client )
2022-11-03 18:14:44 +03:00
{
struct drm_fb_helper * fb_helper = drm_fb_helper_from_client ( client ) ;
2023-01-25 23:04:13 +03:00
if ( fb_helper - > info ) {
2022-11-03 18:14:44 +03:00
drm_fb_helper_unregister_info ( fb_helper ) ;
2023-01-25 23:04:13 +03:00
} else {
drm_client_release ( & fb_helper - > client ) ;
drm_fb_helper_unprepare ( fb_helper ) ;
kfree ( fb_helper ) ;
}
2022-11-03 18:14:44 +03:00
}
2023-03-20 18:07:51 +03:00
static int drm_fbdev_generic_client_restore ( struct drm_client_dev * client )
2022-11-03 18:14:44 +03:00
{
drm_fb_helper_lastclose ( client - > dev ) ;
return 0 ;
}
2023-03-20 18:07:51 +03:00
static int drm_fbdev_generic_client_hotplug ( struct drm_client_dev * client )
2022-11-03 18:14:44 +03:00
{
struct drm_fb_helper * fb_helper = drm_fb_helper_from_client ( client ) ;
struct drm_device * dev = client - > dev ;
int ret ;
if ( dev - > fb_helper )
return drm_fb_helper_hotplug_event ( dev - > fb_helper ) ;
ret = drm_fb_helper_init ( dev , fb_helper ) ;
if ( ret )
2023-01-25 23:04:12 +03:00
goto err_drm_err ;
2022-11-03 18:14:44 +03:00
if ( ! drm_drv_uses_atomic_modeset ( dev ) )
drm_helper_disable_unused_functions ( dev ) ;
2023-01-25 23:04:11 +03:00
ret = drm_fb_helper_initial_config ( fb_helper ) ;
2022-11-03 18:14:44 +03:00
if ( ret )
2023-01-25 23:04:12 +03:00
goto err_drm_fb_helper_fini ;
2022-11-03 18:14:44 +03:00
return 0 ;
2023-01-25 23:04:12 +03:00
err_drm_fb_helper_fini :
drm_fb_helper_fini ( fb_helper ) ;
err_drm_err :
2022-11-03 18:14:44 +03:00
drm_err ( dev , " fbdev: Failed to setup generic emulation (ret=%d) \n " , ret ) ;
return ret ;
}
2023-03-20 18:07:51 +03:00
static const struct drm_client_funcs drm_fbdev_generic_client_funcs = {
2022-11-03 18:14:44 +03:00
. owner = THIS_MODULE ,
2023-03-20 18:07:51 +03:00
. unregister = drm_fbdev_generic_client_unregister ,
. restore = drm_fbdev_generic_client_restore ,
. hotplug = drm_fbdev_generic_client_hotplug ,
2022-11-03 18:14:44 +03:00
} ;
/**
* drm_fbdev_generic_setup ( ) - Setup generic fbdev emulation
* @ dev : DRM device
* @ preferred_bpp : Preferred bits per pixel for the device .
*
* This function sets up generic fbdev emulation for drivers that supports
* dumb buffers with a virtual address and that can be mmap ' ed .
* drm_fbdev_generic_setup ( ) shall be called after the DRM driver registered
* the new DRM device with drm_dev_register ( ) .
*
* Restore , hotplug events and teardown are all taken care of . Drivers that do
* suspend / resume need to call drm_fb_helper_set_suspend_unlocked ( ) themselves .
* Simple drivers might use drm_mode_config_helper_suspend ( ) .
*
2023-03-20 18:07:44 +03:00
* In order to provide fixed mmap - able memory ranges , generic fbdev emulation
* uses a shadow buffer in system memory . The implementation blits the shadow
* fbdev buffer onto the real buffer in regular intervals .
2022-11-03 18:14:44 +03:00
*
* This function is safe to call even when there are no connectors present .
* Setup will be retried on the next hotplug event .
*
* The fbdev is destroyed by drm_dev_unregister ( ) .
*/
2023-03-20 18:07:51 +03:00
void drm_fbdev_generic_setup ( struct drm_device * dev , unsigned int preferred_bpp )
2022-11-03 18:14:44 +03:00
{
struct drm_fb_helper * fb_helper ;
int ret ;
drm_WARN ( dev , ! dev - > registered , " Device has not been registered. \n " ) ;
drm_WARN ( dev , dev - > fb_helper , " fb_helper is already set! \n " ) ;
fb_helper = kzalloc ( sizeof ( * fb_helper ) , GFP_KERNEL ) ;
if ( ! fb_helper )
return ;
2023-03-20 18:07:51 +03:00
drm_fb_helper_prepare ( dev , fb_helper , preferred_bpp , & drm_fbdev_generic_helper_funcs ) ;
2022-11-03 18:14:44 +03:00
2023-03-20 18:07:51 +03:00
ret = drm_client_init ( dev , & fb_helper - > client , " fbdev " , & drm_fbdev_generic_client_funcs ) ;
2022-11-03 18:14:44 +03:00
if ( ret ) {
drm_err ( dev , " Failed to register client: %d \n " , ret ) ;
2023-01-25 23:04:09 +03:00
goto err_drm_client_init ;
2022-11-03 18:14:44 +03:00
}
drm_client_register ( & fb_helper - > client ) ;
2023-01-25 23:04:09 +03:00
return ;
err_drm_client_init :
drm_fb_helper_unprepare ( fb_helper ) ;
kfree ( fb_helper ) ;
return ;
2022-11-03 18:14:44 +03:00
}
EXPORT_SYMBOL ( drm_fbdev_generic_setup ) ;