2019-05-31 17:01:11 +03:00
// SPDX-License-Identifier: MIT
/*
* Copyright 2018 Noralf Trønnes
* Copyright ( c ) 2006 - 2009 Red Hat Inc .
* Copyright ( c ) 2006 - 2008 Intel Corporation
* Jesse Barnes < jesse . barnes @ intel . com >
* Copyright ( c ) 2007 Dave Airlie < airlied @ linux . ie >
*/
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/slab.h>
2019-05-31 17:01:13 +03:00
# include <drm/drm_atomic.h>
2019-05-31 17:01:11 +03:00
# include <drm/drm_client.h>
2019-06-08 18:26:55 +03:00
# include <drm/drm_connector.h>
2019-05-31 17:01:11 +03:00
# include <drm/drm_crtc.h>
# include <drm/drm_device.h>
2019-05-31 17:01:13 +03:00
# include <drm/drm_drv.h>
2019-06-08 18:26:55 +03:00
# include <drm/drm_encoder.h>
# include <drm/drm_print.h>
2019-05-31 17:01:13 +03:00
# include "drm_crtc_internal.h"
# include "drm_internal.h"
2019-05-31 17:01:11 +03:00
2019-06-08 18:26:55 +03:00
# define DRM_CLIENT_MAX_CLONED_CONNECTORS 8
struct drm_client_offset {
int x , y ;
} ;
2019-05-31 17:01:11 +03:00
int drm_client_modeset_create ( struct drm_client_dev * client )
{
struct drm_device * dev = client - > dev ;
unsigned int num_crtc = dev - > mode_config . num_crtc ;
unsigned int max_connector_count = 1 ;
struct drm_mode_set * modeset ;
struct drm_crtc * crtc ;
unsigned int i = 0 ;
/* Add terminating zero entry to enable index less iteration */
client - > modesets = kcalloc ( num_crtc + 1 , sizeof ( * client - > modesets ) , GFP_KERNEL ) ;
if ( ! client - > modesets )
return - ENOMEM ;
mutex_init ( & client - > modeset_mutex ) ;
drm_for_each_crtc ( crtc , dev )
client - > modesets [ i + + ] . crtc = crtc ;
/* Cloning is only supported in the single crtc case. */
if ( num_crtc = = 1 )
max_connector_count = DRM_CLIENT_MAX_CLONED_CONNECTORS ;
for ( modeset = client - > modesets ; modeset - > crtc ; modeset + + ) {
modeset - > connectors = kcalloc ( max_connector_count ,
sizeof ( * modeset - > connectors ) , GFP_KERNEL ) ;
if ( ! modeset - > connectors )
goto err_free ;
}
return 0 ;
err_free :
drm_client_modeset_free ( client ) ;
return - ENOMEM ;
}
2019-06-08 18:26:55 +03:00
static void drm_client_modeset_release ( struct drm_client_dev * client )
2019-05-31 17:01:11 +03:00
{
struct drm_mode_set * modeset ;
unsigned int i ;
drm_client_for_each_modeset ( modeset , client ) {
drm_mode_destroy ( client - > dev , modeset - > mode ) ;
modeset - > mode = NULL ;
modeset - > fb = NULL ;
for ( i = 0 ; i < modeset - > num_connectors ; i + + ) {
drm_connector_put ( modeset - > connectors [ i ] ) ;
modeset - > connectors [ i ] = NULL ;
}
modeset - > num_connectors = 0 ;
}
}
void drm_client_modeset_free ( struct drm_client_dev * client )
{
struct drm_mode_set * modeset ;
mutex_lock ( & client - > modeset_mutex ) ;
drm_client_modeset_release ( client ) ;
drm_client_for_each_modeset ( modeset , client )
kfree ( modeset - > connectors ) ;
mutex_unlock ( & client - > modeset_mutex ) ;
mutex_destroy ( & client - > modeset_mutex ) ;
kfree ( client - > modesets ) ;
}
2019-06-08 18:26:55 +03:00
static struct drm_mode_set *
drm_client_find_modeset ( struct drm_client_dev * client , struct drm_crtc * crtc )
2019-05-31 17:01:11 +03:00
{
struct drm_mode_set * modeset ;
drm_client_for_each_modeset ( modeset , client )
if ( modeset - > crtc = = crtc )
return modeset ;
return NULL ;
}
2019-06-08 18:26:55 +03:00
2019-12-12 00:24:33 +03:00
static struct drm_display_mode *
drm_connector_get_tiled_mode ( struct drm_connector * connector )
{
struct drm_display_mode * mode ;
list_for_each_entry ( mode , & connector - > modes , head ) {
if ( mode - > hdisplay = = connector - > tile_h_size & &
mode - > vdisplay = = connector - > tile_v_size )
return mode ;
}
return NULL ;
}
static struct drm_display_mode *
drm_connector_fallback_non_tiled_mode ( struct drm_connector * connector )
{
struct drm_display_mode * mode ;
list_for_each_entry ( mode , & connector - > modes , head ) {
if ( mode - > hdisplay = = connector - > tile_h_size & &
mode - > vdisplay = = connector - > tile_v_size )
continue ;
return mode ;
}
return NULL ;
}
2019-06-08 18:26:55 +03:00
static struct drm_display_mode *
drm_connector_has_preferred_mode ( struct drm_connector * connector , int width , int height )
{
struct drm_display_mode * mode ;
list_for_each_entry ( mode , & connector - > modes , head ) {
if ( mode - > hdisplay > width | |
mode - > vdisplay > height )
continue ;
if ( mode - > type & DRM_MODE_TYPE_PREFERRED )
return mode ;
}
return NULL ;
}
static struct drm_display_mode *
drm_connector_pick_cmdline_mode ( struct drm_connector * connector )
{
struct drm_cmdline_mode * cmdline_mode ;
struct drm_display_mode * mode ;
bool prefer_non_interlace ;
cmdline_mode = & connector - > cmdline_mode ;
if ( cmdline_mode - > specified = = false )
return NULL ;
/* attempt to find a matching mode in the list of modes
* we have gotten so far , if not add a CVT mode that conforms
*/
if ( cmdline_mode - > rb | | cmdline_mode - > margins )
goto create_mode ;
prefer_non_interlace = ! cmdline_mode - > interlace ;
again :
list_for_each_entry ( mode , & connector - > modes , head ) {
2019-06-19 13:17:50 +03:00
/* Check (optional) mode name first */
if ( ! strcmp ( mode - > name , cmdline_mode - > name ) )
return mode ;
2019-06-08 18:26:55 +03:00
/* check width/height */
if ( mode - > hdisplay ! = cmdline_mode - > xres | |
mode - > vdisplay ! = cmdline_mode - > yres )
continue ;
if ( cmdline_mode - > refresh_specified ) {
if ( mode - > vrefresh ! = cmdline_mode - > refresh )
continue ;
}
if ( cmdline_mode - > interlace ) {
if ( ! ( mode - > flags & DRM_MODE_FLAG_INTERLACE ) )
continue ;
} else if ( prefer_non_interlace ) {
if ( mode - > flags & DRM_MODE_FLAG_INTERLACE )
continue ;
}
return mode ;
}
if ( prefer_non_interlace ) {
prefer_non_interlace = false ;
goto again ;
}
create_mode :
mode = drm_mode_create_from_cmdline_mode ( connector - > dev , cmdline_mode ) ;
2019-07-09 17:51:51 +03:00
if ( mode )
list_add ( & mode - > head , & connector - > modes ) ;
2019-06-08 18:26:55 +03:00
return mode ;
}
static bool drm_connector_enabled ( struct drm_connector * connector , bool strict )
{
bool enable ;
if ( connector - > display_info . non_desktop )
return false ;
if ( strict )
enable = connector - > status = = connector_status_connected ;
else
enable = connector - > status ! = connector_status_disconnected ;
return enable ;
}
static void drm_client_connectors_enabled ( struct drm_connector * * connectors ,
unsigned int connector_count ,
bool * enabled )
{
bool any_enabled = false ;
struct drm_connector * connector ;
int i = 0 ;
for ( i = 0 ; i < connector_count ; i + + ) {
connector = connectors [ i ] ;
enabled [ i ] = drm_connector_enabled ( connector , true ) ;
DRM_DEBUG_KMS ( " connector %d enabled? %s \n " , connector - > base . id ,
connector - > display_info . non_desktop ? " non desktop " : enabled [ i ] ? " yes " : " no " ) ;
any_enabled | = enabled [ i ] ;
}
if ( any_enabled )
return ;
for ( i = 0 ; i < connector_count ; i + + )
enabled [ i ] = drm_connector_enabled ( connectors [ i ] , false ) ;
}
static bool drm_client_target_cloned ( struct drm_device * dev ,
struct drm_connector * * connectors ,
unsigned int connector_count ,
struct drm_display_mode * * modes ,
struct drm_client_offset * offsets ,
bool * enabled , int width , int height )
{
int count , i , j ;
bool can_clone = false ;
struct drm_display_mode * dmt_mode , * mode ;
/* only contemplate cloning in the single crtc case */
if ( dev - > mode_config . num_crtc > 1 )
return false ;
count = 0 ;
for ( i = 0 ; i < connector_count ; i + + ) {
if ( enabled [ i ] )
count + + ;
}
/* only contemplate cloning if more than one connector is enabled */
if ( count < = 1 )
return false ;
/* check the command line or if nothing common pick 1024x768 */
can_clone = true ;
for ( i = 0 ; i < connector_count ; i + + ) {
if ( ! enabled [ i ] )
continue ;
modes [ i ] = drm_connector_pick_cmdline_mode ( connectors [ i ] ) ;
if ( ! modes [ i ] ) {
can_clone = false ;
break ;
}
for ( j = 0 ; j < i ; j + + ) {
if ( ! enabled [ j ] )
continue ;
if ( ! drm_mode_match ( modes [ j ] , modes [ i ] ,
DRM_MODE_MATCH_TIMINGS |
DRM_MODE_MATCH_CLOCK |
DRM_MODE_MATCH_FLAGS |
DRM_MODE_MATCH_3D_FLAGS ) )
can_clone = false ;
}
}
if ( can_clone ) {
DRM_DEBUG_KMS ( " can clone using command line \n " ) ;
return true ;
}
/* try and find a 1024x768 mode on each connector */
can_clone = true ;
dmt_mode = drm_mode_find_dmt ( dev , 1024 , 768 , 60 , false ) ;
for ( i = 0 ; i < connector_count ; i + + ) {
if ( ! enabled [ i ] )
continue ;
list_for_each_entry ( mode , & connectors [ i ] - > modes , head ) {
if ( drm_mode_match ( mode , dmt_mode ,
DRM_MODE_MATCH_TIMINGS |
DRM_MODE_MATCH_CLOCK |
DRM_MODE_MATCH_FLAGS |
DRM_MODE_MATCH_3D_FLAGS ) )
modes [ i ] = mode ;
}
if ( ! modes [ i ] )
can_clone = false ;
}
if ( can_clone ) {
DRM_DEBUG_KMS ( " can clone using 1024x768 \n " ) ;
return true ;
}
DRM_INFO ( " kms: can't enable cloning when we probably wanted to. \n " ) ;
return false ;
}
static int drm_client_get_tile_offsets ( struct drm_connector * * connectors ,
unsigned int connector_count ,
struct drm_display_mode * * modes ,
struct drm_client_offset * offsets ,
int idx ,
int h_idx , int v_idx )
{
struct drm_connector * connector ;
int i ;
int hoffset = 0 , voffset = 0 ;
for ( i = 0 ; i < connector_count ; i + + ) {
connector = connectors [ i ] ;
if ( ! connector - > has_tile )
continue ;
if ( ! modes [ i ] & & ( h_idx | | v_idx ) ) {
DRM_DEBUG_KMS ( " no modes for connector tiled %d %d \n " , i ,
connector - > base . id ) ;
continue ;
}
if ( connector - > tile_h_loc < h_idx )
hoffset + = modes [ i ] - > hdisplay ;
if ( connector - > tile_v_loc < v_idx )
voffset + = modes [ i ] - > vdisplay ;
}
offsets [ idx ] . x = hoffset ;
offsets [ idx ] . y = voffset ;
DRM_DEBUG_KMS ( " returned %d %d for %d %d \n " , hoffset , voffset , h_idx , v_idx ) ;
return 0 ;
}
static bool drm_client_target_preferred ( struct drm_connector * * connectors ,
unsigned int connector_count ,
struct drm_display_mode * * modes ,
struct drm_client_offset * offsets ,
bool * enabled , int width , int height )
{
const u64 mask = BIT_ULL ( connector_count ) - 1 ;
struct drm_connector * connector ;
u64 conn_configured = 0 ;
int tile_pass = 0 ;
2019-12-12 00:24:33 +03:00
int num_tiled_conns = 0 ;
2019-06-08 18:26:55 +03:00
int i ;
2019-12-12 00:24:33 +03:00
for ( i = 0 ; i < connector_count ; i + + ) {
if ( connectors [ i ] - > has_tile & &
connectors [ i ] - > status = = connector_status_connected )
num_tiled_conns + + ;
}
2019-06-08 18:26:55 +03:00
retry :
for ( i = 0 ; i < connector_count ; i + + ) {
connector = connectors [ i ] ;
if ( conn_configured & BIT_ULL ( i ) )
continue ;
if ( enabled [ i ] = = false ) {
conn_configured | = BIT_ULL ( i ) ;
continue ;
}
/* first pass over all the untiled connectors */
if ( tile_pass = = 0 & & connector - > has_tile )
continue ;
if ( tile_pass = = 1 ) {
if ( connector - > tile_h_loc ! = 0 | |
connector - > tile_v_loc ! = 0 )
continue ;
} else {
if ( connector - > tile_h_loc ! = tile_pass - 1 & &
connector - > tile_v_loc ! = tile_pass - 1 )
/* if this tile_pass doesn't cover any of the tiles - keep going */
continue ;
/*
* find the tile offsets for this pass - need to find
* all tiles left and above
*/
drm_client_get_tile_offsets ( connectors , connector_count , modes , offsets , i ,
connector - > tile_h_loc , connector - > tile_v_loc ) ;
}
DRM_DEBUG_KMS ( " looking for cmdline mode on connector %d \n " ,
connector - > base . id ) ;
/* got for command line mode first */
modes [ i ] = drm_connector_pick_cmdline_mode ( connector ) ;
if ( ! modes [ i ] ) {
DRM_DEBUG_KMS ( " looking for preferred mode on connector %d %d \n " ,
connector - > base . id , connector - > tile_group ? connector - > tile_group - > id : 0 ) ;
modes [ i ] = drm_connector_has_preferred_mode ( connector , width , height ) ;
}
/* No preferred modes, pick one off the list */
if ( ! modes [ i ] & & ! list_empty ( & connector - > modes ) ) {
list_for_each_entry ( modes [ i ] , & connector - > modes , head )
break ;
}
2019-12-12 00:24:33 +03:00
/*
* In case of tiled mode if all tiles not present fallback to
* first available non tiled mode .
* After all tiles are present , try to find the tiled mode
* for all and if tiled mode not present due to fbcon size
* limitations , use first non tiled mode only for
* tile 0 , 0 and set to no mode for all other tiles .
*/
if ( connector - > has_tile ) {
if ( num_tiled_conns <
connector - > num_h_tile * connector - > num_v_tile | |
( connector - > tile_h_loc = = 0 & &
connector - > tile_v_loc = = 0 & &
! drm_connector_get_tiled_mode ( connector ) ) ) {
DRM_DEBUG_KMS ( " Falling back to non tiled mode on Connector %d \n " ,
connector - > base . id ) ;
modes [ i ] = drm_connector_fallback_non_tiled_mode ( connector ) ;
} else {
modes [ i ] = drm_connector_get_tiled_mode ( connector ) ;
}
}
2019-06-08 18:26:55 +03:00
DRM_DEBUG_KMS ( " found mode %s \n " , modes [ i ] ? modes [ i ] - > name :
" none " ) ;
conn_configured | = BIT_ULL ( i ) ;
}
if ( ( conn_configured & mask ) ! = mask ) {
tile_pass + + ;
goto retry ;
}
return true ;
}
static bool connector_has_possible_crtc ( struct drm_connector * connector ,
struct drm_crtc * crtc )
{
struct drm_encoder * encoder ;
2019-09-14 02:28:57 +03:00
drm_connector_for_each_possible_encoder ( connector , encoder ) {
2019-06-08 18:26:55 +03:00
if ( encoder - > possible_crtcs & drm_crtc_mask ( crtc ) )
return true ;
}
return false ;
}
static int drm_client_pick_crtcs ( struct drm_client_dev * client ,
struct drm_connector * * connectors ,
unsigned int connector_count ,
struct drm_crtc * * best_crtcs ,
struct drm_display_mode * * modes ,
int n , int width , int height )
{
struct drm_device * dev = client - > dev ;
struct drm_connector * connector ;
int my_score , best_score , score ;
struct drm_crtc * * crtcs , * crtc ;
struct drm_mode_set * modeset ;
int o ;
if ( n = = connector_count )
return 0 ;
connector = connectors [ n ] ;
best_crtcs [ n ] = NULL ;
best_score = drm_client_pick_crtcs ( client , connectors , connector_count ,
best_crtcs , modes , n + 1 , width , height ) ;
if ( modes [ n ] = = NULL )
return best_score ;
crtcs = kcalloc ( connector_count , sizeof ( * crtcs ) , GFP_KERNEL ) ;
if ( ! crtcs )
return best_score ;
my_score = 1 ;
if ( connector - > status = = connector_status_connected )
my_score + + ;
if ( connector - > cmdline_mode . specified )
my_score + + ;
if ( drm_connector_has_preferred_mode ( connector , width , height ) )
my_score + + ;
/*
* select a crtc for this connector and then attempt to configure
* remaining connectors
*/
drm_client_for_each_modeset ( modeset , client ) {
crtc = modeset - > crtc ;
if ( ! connector_has_possible_crtc ( connector , crtc ) )
continue ;
for ( o = 0 ; o < n ; o + + )
if ( best_crtcs [ o ] = = crtc )
break ;
if ( o < n ) {
/* ignore cloning unless only a single crtc */
if ( dev - > mode_config . num_crtc > 1 )
continue ;
if ( ! drm_mode_equal ( modes [ o ] , modes [ n ] ) )
continue ;
}
crtcs [ n ] = crtc ;
memcpy ( crtcs , best_crtcs , n * sizeof ( * crtcs ) ) ;
score = my_score + drm_client_pick_crtcs ( client , connectors , connector_count ,
crtcs , modes , n + 1 , width , height ) ;
if ( score > best_score ) {
best_score = score ;
memcpy ( best_crtcs , crtcs , connector_count * sizeof ( * crtcs ) ) ;
}
}
kfree ( crtcs ) ;
return best_score ;
}
/* Try to read the BIOS display configuration and use it for the initial config */
static bool drm_client_firmware_config ( struct drm_client_dev * client ,
struct drm_connector * * connectors ,
unsigned int connector_count ,
struct drm_crtc * * crtcs ,
struct drm_display_mode * * modes ,
struct drm_client_offset * offsets ,
bool * enabled , int width , int height )
{
unsigned int count = min_t ( unsigned int , connector_count , BITS_PER_LONG ) ;
unsigned long conn_configured , conn_seq , mask ;
struct drm_device * dev = client - > dev ;
int i , j ;
bool * save_enabled ;
bool fallback = true , ret = true ;
int num_connectors_enabled = 0 ;
int num_connectors_detected = 0 ;
2019-12-12 00:24:33 +03:00
int num_tiled_conns = 0 ;
2019-06-08 18:26:55 +03:00
struct drm_modeset_acquire_ctx ctx ;
if ( ! drm_drv_uses_atomic_modeset ( dev ) )
return false ;
save_enabled = kcalloc ( count , sizeof ( bool ) , GFP_KERNEL ) ;
if ( ! save_enabled )
return false ;
drm_modeset_acquire_init ( & ctx , 0 ) ;
while ( drm_modeset_lock_all_ctx ( dev , & ctx ) ! = 0 )
drm_modeset_backoff ( & ctx ) ;
memcpy ( save_enabled , enabled , count ) ;
mask = GENMASK ( count - 1 , 0 ) ;
conn_configured = 0 ;
2019-12-12 00:24:33 +03:00
for ( i = 0 ; i < count ; i + + ) {
if ( connectors [ i ] - > has_tile & &
connectors [ i ] - > status = = connector_status_connected )
num_tiled_conns + + ;
}
2019-06-08 18:26:55 +03:00
retry :
conn_seq = conn_configured ;
for ( i = 0 ; i < count ; i + + ) {
struct drm_connector * connector ;
struct drm_encoder * encoder ;
struct drm_crtc * new_crtc ;
connector = connectors [ i ] ;
if ( conn_configured & BIT ( i ) )
continue ;
if ( conn_seq = = 0 & & ! connector - > has_tile )
continue ;
if ( connector - > status = = connector_status_connected )
num_connectors_detected + + ;
if ( ! enabled [ i ] ) {
DRM_DEBUG_KMS ( " connector %s not enabled, skipping \n " ,
connector - > name ) ;
conn_configured | = BIT ( i ) ;
continue ;
}
if ( connector - > force = = DRM_FORCE_OFF ) {
DRM_DEBUG_KMS ( " connector %s is disabled by user, skipping \n " ,
connector - > name ) ;
enabled [ i ] = false ;
continue ;
}
encoder = connector - > state - > best_encoder ;
if ( ! encoder | | WARN_ON ( ! connector - > state - > crtc ) ) {
if ( connector - > force > DRM_FORCE_OFF )
goto bail ;
DRM_DEBUG_KMS ( " connector %s has no encoder or crtc, skipping \n " ,
connector - > name ) ;
enabled [ i ] = false ;
conn_configured | = BIT ( i ) ;
continue ;
}
num_connectors_enabled + + ;
new_crtc = connector - > state - > crtc ;
/*
* Make sure we ' re not trying to drive multiple connectors
* with a single CRTC , since our cloning support may not
* match the BIOS .
*/
for ( j = 0 ; j < count ; j + + ) {
if ( crtcs [ j ] = = new_crtc ) {
DRM_DEBUG_KMS ( " fallback: cloned configuration \n " ) ;
goto bail ;
}
}
DRM_DEBUG_KMS ( " looking for cmdline mode on connector %s \n " ,
connector - > name ) ;
/* go for command line mode first */
modes [ i ] = drm_connector_pick_cmdline_mode ( connector ) ;
/* try for preferred next */
if ( ! modes [ i ] ) {
DRM_DEBUG_KMS ( " looking for preferred mode on connector %s %d \n " ,
connector - > name , connector - > has_tile ) ;
modes [ i ] = drm_connector_has_preferred_mode ( connector , width , height ) ;
}
/* No preferred mode marked by the EDID? Are there any modes? */
if ( ! modes [ i ] & & ! list_empty ( & connector - > modes ) ) {
DRM_DEBUG_KMS ( " using first mode listed on connector %s \n " ,
connector - > name ) ;
modes [ i ] = list_first_entry ( & connector - > modes ,
struct drm_display_mode ,
head ) ;
}
/* last resort: use current mode */
if ( ! modes [ i ] ) {
/*
* IMPORTANT : We want to use the adjusted mode ( i . e .
* after the panel fitter upscaling ) as the initial
* config , not the input mode , which is what crtc - > mode
* usually contains . But since our current
* code puts a mode derived from the post - pfit timings
* into crtc - > mode this works out correctly .
*
* This is crtc - > mode and not crtc - > state - > mode for the
* fastboot check to work correctly .
*/
DRM_DEBUG_KMS ( " looking for current mode on connector %s \n " ,
connector - > name ) ;
modes [ i ] = & connector - > state - > crtc - > mode ;
}
2019-12-12 00:24:33 +03:00
/*
* In case of tiled modes , if all tiles are not present
* then fallback to a non tiled mode .
*/
if ( connector - > has_tile & &
num_tiled_conns < connector - > num_h_tile * connector - > num_v_tile ) {
DRM_DEBUG_KMS ( " Falling back to non tiled mode on Connector %d \n " ,
connector - > base . id ) ;
modes [ i ] = drm_connector_fallback_non_tiled_mode ( connector ) ;
}
2019-06-08 18:26:55 +03:00
crtcs [ i ] = new_crtc ;
DRM_DEBUG_KMS ( " connector %s on [CRTC:%d:%s]: %dx%d%s \n " ,
connector - > name ,
connector - > state - > crtc - > base . id ,
connector - > state - > crtc - > name ,
modes [ i ] - > hdisplay , modes [ i ] - > vdisplay ,
modes [ i ] - > flags & DRM_MODE_FLAG_INTERLACE ? " i " : " " ) ;
fallback = false ;
conn_configured | = BIT ( i ) ;
}
if ( ( conn_configured & mask ) ! = mask & & conn_configured ! = conn_seq )
goto retry ;
/*
* If the BIOS didn ' t enable everything it could , fall back to have the
* same user experiencing of lighting up as much as possible like the
* fbdev helper library .
*/
if ( num_connectors_enabled ! = num_connectors_detected & &
num_connectors_enabled < dev - > mode_config . num_crtc ) {
DRM_DEBUG_KMS ( " fallback: Not all outputs enabled \n " ) ;
DRM_DEBUG_KMS ( " Enabled: %i, detected: %i \n " , num_connectors_enabled ,
num_connectors_detected ) ;
fallback = true ;
}
if ( fallback ) {
bail :
DRM_DEBUG_KMS ( " Not using firmware configuration \n " ) ;
memcpy ( enabled , save_enabled , count ) ;
ret = false ;
}
drm_modeset_drop_locks ( & ctx ) ;
drm_modeset_acquire_fini ( & ctx ) ;
kfree ( save_enabled ) ;
return ret ;
}
/**
* drm_client_modeset_probe ( ) - Probe for displays
* @ client : DRM client
* @ width : Maximum display mode width ( optional )
* @ height : Maximum display mode height ( optional )
*
* This function sets up display pipelines for enabled connectors and stores the
* config in the client ' s modeset array .
*
* Returns :
* Zero on success or negative error code on failure .
*/
int drm_client_modeset_probe ( struct drm_client_dev * client , unsigned int width , unsigned int height )
{
struct drm_connector * connector , * * connectors = NULL ;
struct drm_connector_list_iter conn_iter ;
struct drm_device * dev = client - > dev ;
unsigned int total_modes_count = 0 ;
struct drm_client_offset * offsets ;
unsigned int connector_count = 0 ;
struct drm_display_mode * * modes ;
struct drm_crtc * * crtcs ;
int i , ret = 0 ;
bool * enabled ;
DRM_DEBUG_KMS ( " \n " ) ;
if ( ! width )
width = dev - > mode_config . max_width ;
if ( ! height )
height = dev - > mode_config . max_height ;
drm_connector_list_iter_begin ( dev , & conn_iter ) ;
drm_client_for_each_connector_iter ( connector , & conn_iter ) {
struct drm_connector * * tmp ;
tmp = krealloc ( connectors , ( connector_count + 1 ) * sizeof ( * connectors ) , GFP_KERNEL ) ;
if ( ! tmp ) {
ret = - ENOMEM ;
goto free_connectors ;
}
connectors = tmp ;
drm_connector_get ( connector ) ;
connectors [ connector_count + + ] = connector ;
}
drm_connector_list_iter_end ( & conn_iter ) ;
if ( ! connector_count )
return 0 ;
crtcs = kcalloc ( connector_count , sizeof ( * crtcs ) , GFP_KERNEL ) ;
modes = kcalloc ( connector_count , sizeof ( * modes ) , GFP_KERNEL ) ;
offsets = kcalloc ( connector_count , sizeof ( * offsets ) , GFP_KERNEL ) ;
enabled = kcalloc ( connector_count , sizeof ( bool ) , GFP_KERNEL ) ;
if ( ! crtcs | | ! modes | | ! enabled | | ! offsets ) {
DRM_ERROR ( " Memory allocation failed \n " ) ;
ret = - ENOMEM ;
goto out ;
}
mutex_lock ( & client - > modeset_mutex ) ;
mutex_lock ( & dev - > mode_config . mutex ) ;
for ( i = 0 ; i < connector_count ; i + + )
total_modes_count + = connectors [ i ] - > funcs - > fill_modes ( connectors [ i ] , width , height ) ;
if ( ! total_modes_count )
DRM_DEBUG_KMS ( " No connectors reported connected with modes \n " ) ;
drm_client_connectors_enabled ( connectors , connector_count , enabled ) ;
if ( ! drm_client_firmware_config ( client , connectors , connector_count , crtcs ,
modes , offsets , enabled , width , height ) ) {
memset ( modes , 0 , connector_count * sizeof ( * modes ) ) ;
memset ( crtcs , 0 , connector_count * sizeof ( * crtcs ) ) ;
memset ( offsets , 0 , connector_count * sizeof ( * offsets ) ) ;
if ( ! drm_client_target_cloned ( dev , connectors , connector_count , modes ,
offsets , enabled , width , height ) & &
! drm_client_target_preferred ( connectors , connector_count , modes ,
offsets , enabled , width , height ) )
DRM_ERROR ( " Unable to find initial modes \n " ) ;
DRM_DEBUG_KMS ( " picking CRTCs for %dx%d config \n " ,
width , height ) ;
drm_client_pick_crtcs ( client , connectors , connector_count ,
crtcs , modes , 0 , width , height ) ;
}
mutex_unlock ( & dev - > mode_config . mutex ) ;
drm_client_modeset_release ( client ) ;
for ( i = 0 ; i < connector_count ; i + + ) {
struct drm_display_mode * mode = modes [ i ] ;
struct drm_crtc * crtc = crtcs [ i ] ;
struct drm_client_offset * offset = & offsets [ i ] ;
if ( mode & & crtc ) {
struct drm_mode_set * modeset = drm_client_find_modeset ( client , crtc ) ;
struct drm_connector * connector = connectors [ i ] ;
DRM_DEBUG_KMS ( " desired mode %s set on crtc %d (%d,%d) \n " ,
mode - > name , crtc - > base . id , offset - > x , offset - > y ) ;
if ( WARN_ON_ONCE ( modeset - > num_connectors = = DRM_CLIENT_MAX_CLONED_CONNECTORS | |
( dev - > mode_config . num_crtc > 1 & & modeset - > num_connectors = = 1 ) ) ) {
ret = - EINVAL ;
break ;
}
modeset - > mode = drm_mode_duplicate ( dev , mode ) ;
drm_connector_get ( connector ) ;
modeset - > connectors [ modeset - > num_connectors + + ] = connector ;
modeset - > x = offset - > x ;
modeset - > y = offset - > y ;
}
}
mutex_unlock ( & client - > modeset_mutex ) ;
out :
kfree ( crtcs ) ;
kfree ( modes ) ;
kfree ( offsets ) ;
kfree ( enabled ) ;
free_connectors :
for ( i = 0 ; i < connector_count ; i + + )
drm_connector_put ( connectors [ i ] ) ;
kfree ( connectors ) ;
return ret ;
}
EXPORT_SYMBOL ( drm_client_modeset_probe ) ;
2019-05-31 17:01:13 +03:00
/**
2019-06-19 13:17:49 +03:00
* drm_client_rotation ( ) - Check the initial rotation value
2019-05-31 17:01:13 +03:00
* @ modeset : DRM modeset
* @ rotation : Returned rotation value
*
2019-06-19 13:17:49 +03:00
* This function checks if the primary plane in @ modeset can hw rotate
* to match the rotation needed on its connector .
2019-05-31 17:01:13 +03:00
*
* Note : Currently only 0 and 180 degrees are supported .
*
* Return :
* True if the plane can do the rotation , false otherwise .
*/
2019-06-19 13:17:49 +03:00
bool drm_client_rotation ( struct drm_mode_set * modeset , unsigned int * rotation )
2019-05-31 17:01:13 +03:00
{
struct drm_connector * connector = modeset - > connectors [ 0 ] ;
struct drm_plane * plane = modeset - > crtc - > primary ;
2019-06-19 13:17:51 +03:00
struct drm_cmdline_mode * cmdline ;
2019-05-31 17:01:13 +03:00
u64 valid_mask = 0 ;
unsigned int i ;
if ( ! modeset - > num_connectors )
return false ;
switch ( connector - > display_info . panel_orientation ) {
case DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP :
* rotation = DRM_MODE_ROTATE_180 ;
break ;
case DRM_MODE_PANEL_ORIENTATION_LEFT_UP :
* rotation = DRM_MODE_ROTATE_90 ;
break ;
case DRM_MODE_PANEL_ORIENTATION_RIGHT_UP :
* rotation = DRM_MODE_ROTATE_270 ;
break ;
default :
* rotation = DRM_MODE_ROTATE_0 ;
}
2019-06-19 13:17:51 +03:00
/**
* The panel already defined the default rotation
* through its orientation . Whatever has been provided
* on the command line needs to be added to that .
*
* Unfortunately , the rotations are at different bit
* indices , so the math to add them up are not as
* trivial as they could .
*
* Reflections on the other hand are pretty trivial to deal with , a
* simple XOR between the two handle the addition nicely .
*/
cmdline = & connector - > cmdline_mode ;
2019-07-11 05:13:13 +03:00
if ( cmdline - > specified & & cmdline - > rotation_reflection ) {
2019-06-19 13:17:51 +03:00
unsigned int cmdline_rest , panel_rest ;
unsigned int cmdline_rot , panel_rot ;
unsigned int sum_rot , sum_rest ;
panel_rot = ilog2 ( * rotation & DRM_MODE_ROTATE_MASK ) ;
cmdline_rot = ilog2 ( cmdline - > rotation_reflection & DRM_MODE_ROTATE_MASK ) ;
sum_rot = ( panel_rot + cmdline_rot ) % 4 ;
panel_rest = * rotation & ~ DRM_MODE_ROTATE_MASK ;
cmdline_rest = cmdline - > rotation_reflection & ~ DRM_MODE_ROTATE_MASK ;
sum_rest = panel_rest ^ cmdline_rest ;
* rotation = ( 1 < < sum_rot ) | sum_rest ;
}
2019-05-31 17:01:13 +03:00
/*
* TODO : support 90 / 270 degree hardware rotation ,
* depending on the hardware this may require the framebuffer
* to be in a specific tiling format .
*/
2020-01-17 18:34:29 +03:00
if ( ( ( * rotation & DRM_MODE_ROTATE_MASK ) ! = DRM_MODE_ROTATE_0 & &
( * rotation & DRM_MODE_ROTATE_MASK ) ! = DRM_MODE_ROTATE_180 ) | |
2019-06-19 13:17:48 +03:00
! plane - > rotation_property )
2019-05-31 17:01:13 +03:00
return false ;
for ( i = 0 ; i < plane - > rotation_property - > num_values ; i + + )
valid_mask | = ( 1ULL < < plane - > rotation_property - > values [ i ] ) ;
if ( ! ( * rotation & valid_mask ) )
return false ;
return true ;
}
2019-06-19 13:17:49 +03:00
EXPORT_SYMBOL ( drm_client_rotation ) ;
2019-05-31 17:01:13 +03:00
static int drm_client_modeset_commit_atomic ( struct drm_client_dev * client , bool active )
{
struct drm_device * dev = client - > dev ;
struct drm_plane * plane ;
struct drm_atomic_state * state ;
struct drm_modeset_acquire_ctx ctx ;
struct drm_mode_set * mode_set ;
int ret ;
drm_modeset_acquire_init ( & ctx , 0 ) ;
state = drm_atomic_state_alloc ( dev ) ;
if ( ! state ) {
ret = - ENOMEM ;
goto out_ctx ;
}
state - > acquire_ctx = & ctx ;
retry :
drm_for_each_plane ( plane , dev ) {
2019-06-19 13:17:48 +03:00
struct drm_plane_state * plane_state ;
2019-05-31 17:01:13 +03:00
plane_state = drm_atomic_get_plane_state ( state , plane ) ;
if ( IS_ERR ( plane_state ) ) {
ret = PTR_ERR ( plane_state ) ;
goto out_state ;
}
plane_state - > rotation = DRM_MODE_ROTATE_0 ;
/* disable non-primary: */
if ( plane - > type = = DRM_PLANE_TYPE_PRIMARY )
continue ;
ret = __drm_atomic_helper_disable_plane ( plane , plane_state ) ;
if ( ret ! = 0 )
goto out_state ;
}
drm_client_for_each_modeset ( mode_set , client ) {
struct drm_plane * primary = mode_set - > crtc - > primary ;
unsigned int rotation ;
2019-06-19 13:17:49 +03:00
if ( drm_client_rotation ( mode_set , & rotation ) ) {
2019-06-19 13:17:48 +03:00
struct drm_plane_state * plane_state ;
2019-05-31 17:01:13 +03:00
/* Cannot fail as we've already gotten the plane state above */
plane_state = drm_atomic_get_new_plane_state ( state , primary ) ;
plane_state - > rotation = rotation ;
}
ret = __drm_atomic_helper_set_config ( mode_set , state ) ;
if ( ret ! = 0 )
goto out_state ;
/*
* __drm_atomic_helper_set_config ( ) sets active when a
* mode is set , unconditionally clear it if we force DPMS off
*/
if ( ! active ) {
struct drm_crtc * crtc = mode_set - > crtc ;
struct drm_crtc_state * crtc_state = drm_atomic_get_new_crtc_state ( state , crtc ) ;
crtc_state - > active = false ;
}
}
ret = drm_atomic_commit ( state ) ;
out_state :
if ( ret = = - EDEADLK )
goto backoff ;
drm_atomic_state_put ( state ) ;
out_ctx :
drm_modeset_drop_locks ( & ctx ) ;
drm_modeset_acquire_fini ( & ctx ) ;
return ret ;
backoff :
drm_atomic_state_clear ( state ) ;
drm_modeset_backoff ( & ctx ) ;
goto retry ;
}
static int drm_client_modeset_commit_legacy ( struct drm_client_dev * client )
{
struct drm_device * dev = client - > dev ;
struct drm_mode_set * mode_set ;
struct drm_plane * plane ;
int ret = 0 ;
drm_modeset_lock_all ( dev ) ;
drm_for_each_plane ( plane , dev ) {
if ( plane - > type ! = DRM_PLANE_TYPE_PRIMARY )
drm_plane_force_disable ( plane ) ;
if ( plane - > rotation_property )
drm_mode_plane_set_obj_prop ( plane ,
plane - > rotation_property ,
DRM_MODE_ROTATE_0 ) ;
}
drm_client_for_each_modeset ( mode_set , client ) {
struct drm_crtc * crtc = mode_set - > crtc ;
if ( crtc - > funcs - > cursor_set2 ) {
ret = crtc - > funcs - > cursor_set2 ( crtc , NULL , 0 , 0 , 0 , 0 , 0 ) ;
if ( ret )
goto out ;
} else if ( crtc - > funcs - > cursor_set ) {
ret = crtc - > funcs - > cursor_set ( crtc , NULL , 0 , 0 , 0 ) ;
if ( ret )
goto out ;
}
ret = drm_mode_set_config_internal ( mode_set ) ;
if ( ret )
goto out ;
}
out :
drm_modeset_unlock_all ( dev ) ;
return ret ;
}
/**
2020-02-04 18:01:44 +03:00
* drm_client_modeset_commit_locked ( ) - Force commit CRTC configuration
2019-05-31 17:01:13 +03:00
* @ client : DRM client
*
2020-02-04 18:01:44 +03:00
* Commit modeset configuration to crtcs without checking if there is a DRM
* master . The assumption is that the caller already holds an internal DRM
* master reference acquired with drm_master_internal_acquire ( ) .
2019-05-31 17:01:13 +03:00
*
* Returns :
* Zero on success or negative error code on failure .
*/
2020-02-04 18:01:44 +03:00
int drm_client_modeset_commit_locked ( struct drm_client_dev * client )
2019-05-31 17:01:13 +03:00
{
struct drm_device * dev = client - > dev ;
int ret ;
mutex_lock ( & client - > modeset_mutex ) ;
if ( drm_drv_uses_atomic_modeset ( dev ) )
ret = drm_client_modeset_commit_atomic ( client , true ) ;
else
ret = drm_client_modeset_commit_legacy ( client ) ;
mutex_unlock ( & client - > modeset_mutex ) ;
return ret ;
}
2020-02-04 18:01:44 +03:00
EXPORT_SYMBOL ( drm_client_modeset_commit_locked ) ;
2019-05-31 17:01:13 +03:00
/**
* drm_client_modeset_commit ( ) - Commit CRTC configuration
* @ client : DRM client
*
* Commit modeset configuration to crtcs .
*
* Returns :
* Zero on success or negative error code on failure .
*/
int drm_client_modeset_commit ( struct drm_client_dev * client )
{
struct drm_device * dev = client - > dev ;
int ret ;
if ( ! drm_master_internal_acquire ( dev ) )
return - EBUSY ;
2020-02-04 18:01:44 +03:00
ret = drm_client_modeset_commit_locked ( client ) ;
2019-05-31 17:01:13 +03:00
drm_master_internal_release ( dev ) ;
return ret ;
}
EXPORT_SYMBOL ( drm_client_modeset_commit ) ;
static void drm_client_modeset_dpms_legacy ( struct drm_client_dev * client , int dpms_mode )
{
struct drm_device * dev = client - > dev ;
struct drm_connector * connector ;
struct drm_mode_set * modeset ;
int j ;
drm_modeset_lock_all ( dev ) ;
drm_client_for_each_modeset ( modeset , client ) {
if ( ! modeset - > crtc - > enabled )
continue ;
for ( j = 0 ; j < modeset - > num_connectors ; j + + ) {
connector = modeset - > connectors [ j ] ;
connector - > funcs - > dpms ( connector , dpms_mode ) ;
drm_object_property_set_value ( & connector - > base ,
dev - > mode_config . dpms_property , dpms_mode ) ;
}
}
drm_modeset_unlock_all ( dev ) ;
}
/**
* drm_client_modeset_dpms ( ) - Set DPMS mode
* @ client : DRM client
* @ mode : DPMS mode
*
* Note : For atomic drivers @ mode is reduced to on / off .
*
* Returns :
* Zero on success or negative error code on failure .
*/
int drm_client_modeset_dpms ( struct drm_client_dev * client , int mode )
{
struct drm_device * dev = client - > dev ;
int ret = 0 ;
if ( ! drm_master_internal_acquire ( dev ) )
return - EBUSY ;
mutex_lock ( & client - > modeset_mutex ) ;
if ( drm_drv_uses_atomic_modeset ( dev ) )
ret = drm_client_modeset_commit_atomic ( client , mode = = DRM_MODE_DPMS_ON ) ;
else
drm_client_modeset_dpms_legacy ( client , mode ) ;
mutex_unlock ( & client - > modeset_mutex ) ;
drm_master_internal_release ( dev ) ;
return ret ;
}
EXPORT_SYMBOL ( drm_client_modeset_dpms ) ;