2012-03-19 01:37:33 +04:00
/*
drm_edid_load . c : use a built - in EDID data set or load it via the firmware
interface
Copyright ( C ) 2012 Carsten Emde < C . Emde @ osadl . org >
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 .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 51 Franklin St , Fifth Floor , Boston , MA 02110 - 1301 USA .
*/
# include <linux/module.h>
# include <linux/firmware.h>
2012-10-02 21:01:07 +04:00
# include <drm/drmP.h>
# include <drm/drm_crtc.h>
# include <drm/drm_crtc_helper.h>
# include <drm/drm_edid.h>
2012-03-19 01:37:33 +04:00
static char edid_firmware [ PATH_MAX ] ;
module_param_string ( edid_firmware , edid_firmware , sizeof ( edid_firmware ) , 0644 ) ;
MODULE_PARM_DESC ( edid_firmware , " Do not probe monitor, use specified EDID blob "
" from built-in data or /lib/firmware instead. " ) ;
2014-05-23 19:01:43 +04:00
# define GENERIC_EDIDS 6
2015-08-31 15:09:25 +03:00
static const char * const generic_edid_name [ GENERIC_EDIDS ] = {
2014-05-23 19:01:43 +04:00
" edid/800x600.bin " ,
2012-03-19 01:37:33 +04:00
" edid/1024x768.bin " ,
" edid/1280x1024.bin " ,
2013-04-06 20:01:34 +04:00
" edid/1600x1200.bin " ,
2012-03-19 01:37:33 +04:00
" edid/1680x1050.bin " ,
" edid/1920x1080.bin " ,
} ;
2013-10-02 14:12:53 +04:00
static const u8 generic_edid [ GENERIC_EDIDS ] [ 128 ] = {
2014-05-23 19:01:43 +04:00
{
0x00 , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff , 0x00 ,
0x31 , 0xd8 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
0x05 , 0x16 , 0x01 , 0x03 , 0x6d , 0x1b , 0x14 , 0x78 ,
0xea , 0x5e , 0xc0 , 0xa4 , 0x59 , 0x4a , 0x98 , 0x25 ,
0x20 , 0x50 , 0x54 , 0x01 , 0x00 , 0x00 , 0x45 , 0x40 ,
0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 ,
0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0xa0 , 0x0f ,
0x20 , 0x00 , 0x31 , 0x58 , 0x1c , 0x20 , 0x28 , 0x80 ,
0x14 , 0x00 , 0x15 , 0xd0 , 0x10 , 0x00 , 0x00 , 0x1e ,
0x00 , 0x00 , 0x00 , 0xff , 0x00 , 0x4c , 0x69 , 0x6e ,
0x75 , 0x78 , 0x20 , 0x23 , 0x30 , 0x0a , 0x20 , 0x20 ,
0x20 , 0x20 , 0x00 , 0x00 , 0x00 , 0xfd , 0x00 , 0x3b ,
0x3d , 0x24 , 0x26 , 0x05 , 0x00 , 0x0a , 0x20 , 0x20 ,
0x20 , 0x20 , 0x20 , 0x20 , 0x00 , 0x00 , 0x00 , 0xfc ,
0x00 , 0x4c , 0x69 , 0x6e , 0x75 , 0x78 , 0x20 , 0x53 ,
0x56 , 0x47 , 0x41 , 0x0a , 0x20 , 0x20 , 0x00 , 0xc2 ,
} ,
2012-03-19 01:37:33 +04:00
{
0x00 , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff , 0x00 ,
0x31 , 0xd8 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
0x05 , 0x16 , 0x01 , 0x03 , 0x6d , 0x23 , 0x1a , 0x78 ,
0xea , 0x5e , 0xc0 , 0xa4 , 0x59 , 0x4a , 0x98 , 0x25 ,
0x20 , 0x50 , 0x54 , 0x00 , 0x08 , 0x00 , 0x61 , 0x40 ,
0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 ,
0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x64 , 0x19 ,
0x00 , 0x40 , 0x41 , 0x00 , 0x26 , 0x30 , 0x08 , 0x90 ,
0x36 , 0x00 , 0x63 , 0x0a , 0x11 , 0x00 , 0x00 , 0x18 ,
0x00 , 0x00 , 0x00 , 0xff , 0x00 , 0x4c , 0x69 , 0x6e ,
0x75 , 0x78 , 0x20 , 0x23 , 0x30 , 0x0a , 0x20 , 0x20 ,
0x20 , 0x20 , 0x00 , 0x00 , 0x00 , 0xfd , 0x00 , 0x3b ,
0x3d , 0x2f , 0x31 , 0x07 , 0x00 , 0x0a , 0x20 , 0x20 ,
0x20 , 0x20 , 0x20 , 0x20 , 0x00 , 0x00 , 0x00 , 0xfc ,
0x00 , 0x4c , 0x69 , 0x6e , 0x75 , 0x78 , 0x20 , 0x58 ,
0x47 , 0x41 , 0x0a , 0x20 , 0x20 , 0x20 , 0x00 , 0x55 ,
} ,
{
0x00 , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff , 0x00 ,
0x31 , 0xd8 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
0x05 , 0x16 , 0x01 , 0x03 , 0x6d , 0x2c , 0x23 , 0x78 ,
0xea , 0x5e , 0xc0 , 0xa4 , 0x59 , 0x4a , 0x98 , 0x25 ,
0x20 , 0x50 , 0x54 , 0x00 , 0x00 , 0x00 , 0x81 , 0x80 ,
0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 ,
0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x30 , 0x2a ,
0x00 , 0x98 , 0x51 , 0x00 , 0x2a , 0x40 , 0x30 , 0x70 ,
0x13 , 0x00 , 0xbc , 0x63 , 0x11 , 0x00 , 0x00 , 0x1e ,
0x00 , 0x00 , 0x00 , 0xff , 0x00 , 0x4c , 0x69 , 0x6e ,
0x75 , 0x78 , 0x20 , 0x23 , 0x30 , 0x0a , 0x20 , 0x20 ,
0x20 , 0x20 , 0x00 , 0x00 , 0x00 , 0xfd , 0x00 , 0x3b ,
0x3d , 0x3e , 0x40 , 0x0b , 0x00 , 0x0a , 0x20 , 0x20 ,
0x20 , 0x20 , 0x20 , 0x20 , 0x00 , 0x00 , 0x00 , 0xfc ,
0x00 , 0x4c , 0x69 , 0x6e , 0x75 , 0x78 , 0x20 , 0x53 ,
0x58 , 0x47 , 0x41 , 0x0a , 0x20 , 0x20 , 0x00 , 0xa0 ,
} ,
{
0x00 , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff , 0x00 ,
0x31 , 0xd8 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
2013-04-06 20:01:34 +04:00
0x05 , 0x16 , 0x01 , 0x03 , 0x6d , 0x37 , 0x29 , 0x78 ,
0xea , 0x5e , 0xc0 , 0xa4 , 0x59 , 0x4a , 0x98 , 0x25 ,
0x20 , 0x50 , 0x54 , 0x00 , 0x00 , 0x00 , 0xa9 , 0x40 ,
0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 ,
0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x48 , 0x3f ,
0x40 , 0x30 , 0x62 , 0xb0 , 0x32 , 0x40 , 0x40 , 0xc0 ,
0x13 , 0x00 , 0x2b , 0xa0 , 0x21 , 0x00 , 0x00 , 0x1e ,
0x00 , 0x00 , 0x00 , 0xff , 0x00 , 0x4c , 0x69 , 0x6e ,
0x75 , 0x78 , 0x20 , 0x23 , 0x30 , 0x0a , 0x20 , 0x20 ,
0x20 , 0x20 , 0x00 , 0x00 , 0x00 , 0xfd , 0x00 , 0x3b ,
0x3d , 0x4a , 0x4c , 0x11 , 0x00 , 0x0a , 0x20 , 0x20 ,
0x20 , 0x20 , 0x20 , 0x20 , 0x00 , 0x00 , 0x00 , 0xfc ,
0x00 , 0x4c , 0x69 , 0x6e , 0x75 , 0x78 , 0x20 , 0x55 ,
0x58 , 0x47 , 0x41 , 0x0a , 0x20 , 0x20 , 0x00 , 0x9d ,
} ,
{
0x00 , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff , 0x00 ,
0x31 , 0xd8 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
2012-03-19 01:37:33 +04:00
0x05 , 0x16 , 0x01 , 0x03 , 0x6d , 0x2b , 0x1b , 0x78 ,
0xea , 0x5e , 0xc0 , 0xa4 , 0x59 , 0x4a , 0x98 , 0x25 ,
0x20 , 0x50 , 0x54 , 0x00 , 0x00 , 0x00 , 0xb3 , 0x00 ,
0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 ,
0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x21 , 0x39 ,
0x90 , 0x30 , 0x62 , 0x1a , 0x27 , 0x40 , 0x68 , 0xb0 ,
0x36 , 0x00 , 0xb5 , 0x11 , 0x11 , 0x00 , 0x00 , 0x1e ,
0x00 , 0x00 , 0x00 , 0xff , 0x00 , 0x4c , 0x69 , 0x6e ,
0x75 , 0x78 , 0x20 , 0x23 , 0x30 , 0x0a , 0x20 , 0x20 ,
0x20 , 0x20 , 0x00 , 0x00 , 0x00 , 0xfd , 0x00 , 0x3b ,
0x3d , 0x40 , 0x42 , 0x0f , 0x00 , 0x0a , 0x20 , 0x20 ,
0x20 , 0x20 , 0x20 , 0x20 , 0x00 , 0x00 , 0x00 , 0xfc ,
0x00 , 0x4c , 0x69 , 0x6e , 0x75 , 0x78 , 0x20 , 0x57 ,
0x53 , 0x58 , 0x47 , 0x41 , 0x0a , 0x20 , 0x00 , 0x26 ,
} ,
{
0x00 , 0xff , 0xff , 0xff , 0xff , 0xff , 0xff , 0x00 ,
0x31 , 0xd8 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
0x05 , 0x16 , 0x01 , 0x03 , 0x6d , 0x32 , 0x1c , 0x78 ,
0xea , 0x5e , 0xc0 , 0xa4 , 0x59 , 0x4a , 0x98 , 0x25 ,
0x20 , 0x50 , 0x54 , 0x00 , 0x00 , 0x00 , 0xd1 , 0xc0 ,
0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 ,
0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x01 , 0x02 , 0x3a ,
0x80 , 0x18 , 0x71 , 0x38 , 0x2d , 0x40 , 0x58 , 0x2c ,
0x45 , 0x00 , 0xf4 , 0x19 , 0x11 , 0x00 , 0x00 , 0x1e ,
0x00 , 0x00 , 0x00 , 0xff , 0x00 , 0x4c , 0x69 , 0x6e ,
0x75 , 0x78 , 0x20 , 0x23 , 0x30 , 0x0a , 0x20 , 0x20 ,
0x20 , 0x20 , 0x00 , 0x00 , 0x00 , 0xfd , 0x00 , 0x3b ,
0x3d , 0x42 , 0x44 , 0x0f , 0x00 , 0x0a , 0x20 , 0x20 ,
0x20 , 0x20 , 0x20 , 0x20 , 0x00 , 0x00 , 0x00 , 0xfc ,
0x00 , 0x4c , 0x69 , 0x6e , 0x75 , 0x78 , 0x20 , 0x46 ,
0x48 , 0x44 , 0x0a , 0x20 , 0x20 , 0x20 , 0x00 , 0x05 ,
} ,
} ;
2013-10-02 14:12:53 +04:00
static int edid_size ( const u8 * edid , int data_size )
{
if ( data_size < EDID_LENGTH )
return 0 ;
return ( edid [ 0x7e ] + 1 ) * EDID_LENGTH ;
}
2013-11-19 15:15:05 +04:00
static void * edid_load ( struct drm_connector * connector , const char * name ,
2013-07-11 01:21:46 +04:00
const char * connector_name )
2012-03-19 01:37:33 +04:00
{
2013-10-02 14:12:53 +04:00
const struct firmware * fw = NULL ;
const u8 * fwdata ;
u8 * edid ;
int fwsize , builtin ;
2012-03-19 01:37:33 +04:00
int i , valid_extensions = 0 ;
2012-08-09 19:25:51 +04:00
bool print_bad_edid = ! connector - > bad_edid_counter | | ( drm_debug & DRM_UT_KMS ) ;
2012-03-19 01:37:33 +04:00
2016-03-18 00:22:23 +03:00
builtin = match_string ( generic_edid_name , GENERIC_EDIDS , name ) ;
if ( builtin > = 0 ) {
fwdata = generic_edid [ builtin ] ;
fwsize = sizeof ( generic_edid [ builtin ] ) ;
} else {
2013-10-02 14:12:53 +04:00
struct platform_device * pdev ;
int err ;
2012-03-19 01:37:33 +04:00
2013-10-02 14:12:53 +04:00
pdev = platform_device_register_simple ( connector_name , - 1 , NULL , 0 ) ;
if ( IS_ERR ( pdev ) ) {
DRM_ERROR ( " Failed to register EDID firmware platform device "
" for connector \" %s \" \n " , connector_name ) ;
return ERR_CAST ( pdev ) ;
}
err = request_firmware ( & fw , name , & pdev - > dev ) ;
platform_device_unregister ( pdev ) ;
if ( err ) {
DRM_ERROR ( " Requesting EDID firmware \" %s \" failed (err=%d) \n " ,
name , err ) ;
return ERR_PTR ( err ) ;
}
2012-03-19 01:37:33 +04:00
2013-10-02 14:12:53 +04:00
fwdata = fw - > data ;
2012-03-19 01:37:33 +04:00
fwsize = fw - > size ;
}
2013-10-02 14:12:53 +04:00
if ( edid_size ( fwdata , fwsize ) ! = fwsize ) {
2012-03-19 01:37:33 +04:00
DRM_ERROR ( " Size of EDID firmware \" %s \" is invalid "
2013-10-02 14:12:53 +04:00
" (expected %d, got %d \n " , name ,
edid_size ( fwdata , fwsize ) , ( int ) fwsize ) ;
edid = ERR_PTR ( - EINVAL ) ;
goto out ;
2012-03-19 01:37:33 +04:00
}
2013-05-23 01:06:30 +04:00
edid = kmemdup ( fwdata , fwsize , GFP_KERNEL ) ;
2012-03-19 01:37:33 +04:00
if ( edid = = NULL ) {
2013-10-02 14:12:53 +04:00
edid = ERR_PTR ( - ENOMEM ) ;
goto out ;
2012-03-19 01:37:33 +04:00
}
2015-04-21 21:09:41 +03:00
if ( ! drm_edid_block_valid ( edid , 0 , print_bad_edid ,
& connector - > edid_corrupt ) ) {
2012-08-09 19:25:51 +04:00
connector - > bad_edid_counter + + ;
2012-03-19 01:37:33 +04:00
DRM_ERROR ( " Base block of EDID firmware \" %s \" is invalid " ,
name ) ;
kfree ( edid ) ;
2013-10-02 14:12:53 +04:00
edid = ERR_PTR ( - EINVAL ) ;
goto out ;
2012-03-19 01:37:33 +04:00
}
for ( i = 1 ; i < = edid [ 0x7e ] ; i + + ) {
if ( i ! = valid_extensions + 1 )
memcpy ( edid + ( valid_extensions + 1 ) * EDID_LENGTH ,
edid + i * EDID_LENGTH , EDID_LENGTH ) ;
2015-04-21 21:09:41 +03:00
if ( drm_edid_block_valid ( edid + i * EDID_LENGTH , i ,
print_bad_edid ,
NULL ) )
2012-03-19 01:37:33 +04:00
valid_extensions + + ;
}
if ( valid_extensions ! = edid [ 0x7e ] ) {
2013-10-02 14:12:53 +04:00
u8 * new_edid ;
2012-03-19 01:37:33 +04:00
edid [ EDID_LENGTH - 1 ] + = edid [ 0x7e ] - valid_extensions ;
DRM_INFO ( " Found %d valid extensions instead of %d in EDID data "
" \" %s \" for connector \" %s \" \n " , valid_extensions ,
edid [ 0x7e ] , name , connector_name ) ;
edid [ 0x7e ] = valid_extensions ;
2013-10-02 14:12:53 +04:00
2012-08-07 16:23:06 +04:00
new_edid = krealloc ( edid , ( valid_extensions + 1 ) * EDID_LENGTH ,
2013-10-02 14:12:53 +04:00
GFP_KERNEL ) ;
if ( new_edid )
edid = new_edid ;
2012-03-19 01:37:33 +04:00
}
DRM_INFO ( " Got %s EDID base block and %d extension%s from "
2016-03-18 00:22:23 +03:00
" \" %s \" for connector \" %s \" \n " , ( builtin > = 0 ) ? " built-in " :
2012-03-19 01:37:33 +04:00
" external " , valid_extensions , valid_extensions = = 1 ? " " : " s " ,
name , connector_name ) ;
out :
2014-11-19 18:33:17 +03:00
release_firmware ( fw ) ;
2012-08-15 13:32:39 +04:00
return edid ;
2012-03-19 01:37:33 +04:00
}
int drm_load_edid_firmware ( struct drm_connector * connector )
{
2014-06-03 15:56:20 +04:00
const char * connector_name = connector - > name ;
2015-08-27 20:04:13 +03:00
char * edidname , * last , * colon , * fwstr , * edidstr , * fallback = NULL ;
2012-05-17 15:27:21 +04:00
int ret ;
2012-08-15 13:32:39 +04:00
struct edid * edid ;
2012-03-19 01:37:33 +04:00
2015-08-27 20:04:13 +03:00
if ( edid_firmware [ 0 ] = = ' \0 ' )
2012-05-17 15:27:21 +04:00
return 0 ;
2012-03-19 01:37:33 +04:00
2015-08-27 20:04:13 +03:00
/*
* If there are multiple edid files specified and separated
* by commas , search through the list looking for one that
* matches the connector .
*
* If there ' s one or more that don ' t ' t specify a connector , keep
* the last one found one as a fallback .
*/
fwstr = kstrdup ( edid_firmware , GFP_KERNEL ) ;
edidstr = fwstr ;
while ( ( edidname = strsep ( & edidstr , " , " ) ) ) {
colon = strchr ( edidname , ' : ' ) ;
if ( colon ! = NULL ) {
if ( strncmp ( connector_name , edidname , colon - edidname ) )
continue ;
edidname = colon + 1 ;
break ;
}
if ( * edidname ! = ' \0 ' ) /* corner case: multiple ',' */
fallback = edidname ;
}
if ( ! edidname ) {
if ( ! fallback ) {
kfree ( fwstr ) ;
2012-05-17 15:27:21 +04:00
return 0 ;
2015-08-27 20:04:13 +03:00
}
edidname = fallback ;
2012-03-19 01:37:33 +04:00
}
last = edidname + strlen ( edidname ) - 1 ;
if ( * last = = ' \n ' )
* last = ' \0 ' ;
2013-11-19 15:15:05 +04:00
edid = edid_load ( connector , edidname , connector_name ) ;
2015-08-27 20:04:13 +03:00
kfree ( fwstr ) ;
2012-08-15 13:32:39 +04:00
if ( IS_ERR_OR_NULL ( edid ) )
2012-03-19 01:37:33 +04:00
return 0 ;
2012-08-15 13:32:39 +04:00
drm_mode_connector_update_edid_property ( connector , edid ) ;
ret = drm_add_edid_modes ( connector , edid ) ;
2015-03-26 11:42:00 +03:00
drm_edid_to_eld ( connector , edid ) ;
2012-08-15 13:32:39 +04:00
kfree ( edid ) ;
2012-03-19 01:37:33 +04:00
2012-08-15 13:32:39 +04:00
return ret ;
2012-03-19 01:37:33 +04:00
}