2006-01-21 01:08:59 +03:00
2005-04-17 02:20:36 +04:00
/*
* drm_sysfs . c - Modifications to drm_sysfs_class . c to support
* extra sysfs attribute from DRM . Normal drm_sysfs_class
* does not allow adding attributes .
*
* Copyright ( c ) 2004 Jon Smirl < jonsmirl @ gmail . com >
* Copyright ( c ) 2003 - 2004 Greg Kroah - Hartman < greg @ kroah . com >
* Copyright ( c ) 2003 - 2004 IBM Corp .
*
* This file is released under the GPLv2
*
*/
# include <linux/device.h>
# include <linux/kdev_t.h>
# include <linux/err.h>
2009-08-17 18:28:37 +04:00
# include "drm_sysfs.h"
2005-04-17 02:20:36 +04:00
# include "drm_core.h"
2005-09-05 15:33:44 +04:00
# include "drmP.h"
2005-04-17 02:20:36 +04:00
2008-04-21 10:47:32 +04:00
# define to_drm_minor(d) container_of(d, struct drm_minor, kdev)
2008-11-08 01:05:41 +03:00
# define to_drm_connector(d) container_of(d, struct drm_connector, kdev)
2007-11-22 07:02:38 +03:00
2009-08-20 13:02:31 +04:00
static struct device_type drm_sysfs_device_minor = {
. name = " drm_minor "
} ;
2007-11-22 07:02:38 +03:00
/**
2009-08-20 13:02:31 +04:00
* drm_class_suspend - DRM class suspend hook
2007-11-22 07:02:38 +03:00
* @ dev : Linux device to suspend
* @ state : power state to enter
*
* Just figures out what the actual struct drm_device associated with
* @ dev is and calls its suspend hook , if present .
*/
2009-08-20 13:02:31 +04:00
static int drm_class_suspend ( struct device * dev , pm_message_t state )
2007-11-22 07:02:38 +03:00
{
2009-08-20 13:02:31 +04:00
if ( dev - > type = = & drm_sysfs_device_minor ) {
struct drm_minor * drm_minor = to_drm_minor ( dev ) ;
struct drm_device * drm_dev = drm_minor - > dev ;
if ( drm_minor - > type = = DRM_MINOR_LEGACY & &
! drm_core_check_feature ( drm_dev , DRIVER_MODESET ) & &
drm_dev - > driver - > suspend )
return drm_dev - > driver - > suspend ( drm_dev , state ) ;
}
2007-11-22 07:02:38 +03:00
return 0 ;
}
/**
2009-08-20 13:02:31 +04:00
* drm_class_resume - DRM class resume hook
2007-11-22 07:02:38 +03:00
* @ dev : Linux device to resume
*
* Just figures out what the actual struct drm_device associated with
* @ dev is and calls its resume hook , if present .
*/
2009-08-20 13:02:31 +04:00
static int drm_class_resume ( struct device * dev )
2007-11-22 07:02:38 +03:00
{
2009-08-20 13:02:31 +04:00
if ( dev - > type = = & drm_sysfs_device_minor ) {
struct drm_minor * drm_minor = to_drm_minor ( dev ) ;
struct drm_device * drm_dev = drm_minor - > dev ;
if ( drm_minor - > type = = DRM_MINOR_LEGACY & &
! drm_core_check_feature ( drm_dev , DRIVER_MODESET ) & &
drm_dev - > driver - > resume )
return drm_dev - > driver - > resume ( drm_dev ) ;
}
2007-11-22 07:02:38 +03:00
return 0 ;
}
2005-04-17 02:20:36 +04:00
/* Display the version of drm_core. This doesn't work right in current design */
static ssize_t version_show ( struct class * dev , char * buf )
{
return sprintf ( buf , " %s %d.%d.%d %s \n " , CORE_NAME , CORE_MAJOR ,
CORE_MINOR , CORE_PATCHLEVEL , CORE_DATE ) ;
}
2009-09-19 01:01:12 +04:00
static char * drm_devnode ( struct device * dev , mode_t * mode )
2009-04-30 17:23:42 +04:00
{
return kasprintf ( GFP_KERNEL , " dri/%s " , dev_name ( dev ) ) ;
}
2005-04-17 02:20:36 +04:00
static CLASS_ATTR ( version , S_IRUGO , version_show , NULL ) ;
/**
* drm_sysfs_create - create a struct drm_sysfs_class structure
* @ owner : pointer to the module that is to " own " this struct drm_sysfs_class
* @ name : pointer to a string for the name of this class .
*
2007-11-22 07:02:38 +03:00
* This is used to create DRM class pointer that can then be used
2005-04-17 02:20:36 +04:00
* in calls to drm_sysfs_device_add ( ) .
*
* Note , the pointer created here is to be destroyed when finished by making a
* call to drm_sysfs_destroy ( ) .
*/
2006-01-21 01:08:59 +03:00
struct class * drm_sysfs_create ( struct module * owner , char * name )
2005-04-17 02:20:36 +04:00
{
2006-01-21 01:08:59 +03:00
struct class * class ;
2006-10-11 01:23:37 +04:00
int err ;
2006-01-21 01:08:59 +03:00
class = class_create ( owner , name ) ;
2006-12-09 02:49:47 +03:00
if ( IS_ERR ( class ) ) {
err = PTR_ERR ( class ) ;
2006-10-11 01:23:37 +04:00
goto err_out ;
}
2009-08-20 13:02:31 +04:00
class - > suspend = drm_class_suspend ;
class - > resume = drm_class_resume ;
2007-11-22 07:02:38 +03:00
2006-10-11 01:23:37 +04:00
err = class_create_file ( class , & class_attr_version ) ;
if ( err )
goto err_out_class ;
2006-01-21 01:08:59 +03:00
2009-09-19 01:01:12 +04:00
class - > devnode = drm_devnode ;
2009-04-30 17:23:42 +04:00
2006-01-21 01:08:59 +03:00
return class ;
2006-10-11 01:23:37 +04:00
err_out_class :
class_destroy ( class ) ;
err_out :
return ERR_PTR ( err ) ;
2005-04-17 02:20:36 +04:00
}
/**
2007-11-22 07:02:38 +03:00
* drm_sysfs_destroy - destroys DRM class
2005-04-17 02:20:36 +04:00
*
2007-11-22 07:02:38 +03:00
* Destroy the DRM device class .
2005-04-17 02:20:36 +04:00
*/
2007-11-22 07:02:38 +03:00
void drm_sysfs_destroy ( void )
2005-04-17 02:20:36 +04:00
{
2007-11-22 07:02:38 +03:00
if ( ( drm_class = = NULL ) | | ( IS_ERR ( drm_class ) ) )
2005-04-17 02:20:36 +04:00
return ;
2007-11-22 07:02:38 +03:00
class_remove_file ( drm_class , & class_attr_version ) ;
class_destroy ( drm_class ) ;
2005-04-17 02:20:36 +04:00
}
2007-11-22 07:02:38 +03:00
/**
* drm_sysfs_device_release - do nothing
* @ dev : Linux device
*
* Normally , this would free the DRM device associated with @ dev , along
* with cleaning up any other stuff . But we do that in the DRM core , so
* this function can just return and hope that the core does its job .
*/
static void drm_sysfs_device_release ( struct device * dev )
{
2009-04-16 13:51:25 +04:00
memset ( dev , 0 , sizeof ( struct device ) ) ;
2007-11-22 07:02:38 +03:00
return ;
}
2008-11-08 01:05:41 +03:00
/*
* Connector properties
*/
static ssize_t status_show ( struct device * device ,
struct device_attribute * attr ,
char * buf )
{
struct drm_connector * connector = to_drm_connector ( device ) ;
enum drm_connector_status status ;
status = connector - > funcs - > detect ( connector ) ;
2009-05-31 07:42:25 +04:00
return snprintf ( buf , PAGE_SIZE , " %s \n " ,
2008-11-08 01:05:41 +03:00
drm_get_connector_status_name ( status ) ) ;
}
static ssize_t dpms_show ( struct device * device ,
struct device_attribute * attr ,
char * buf )
{
struct drm_connector * connector = to_drm_connector ( device ) ;
struct drm_device * dev = connector - > dev ;
uint64_t dpms_status ;
int ret ;
ret = drm_connector_property_get_value ( connector ,
dev - > mode_config . dpms_property ,
& dpms_status ) ;
if ( ret )
return 0 ;
2009-05-31 07:42:25 +04:00
return snprintf ( buf , PAGE_SIZE , " %s \n " ,
2008-11-08 01:05:41 +03:00
drm_get_dpms_name ( ( int ) dpms_status ) ) ;
}
static ssize_t enabled_show ( struct device * device ,
struct device_attribute * attr ,
char * buf )
{
struct drm_connector * connector = to_drm_connector ( device ) ;
2009-05-31 07:42:25 +04:00
return snprintf ( buf , PAGE_SIZE , " %s \n " , connector - > encoder ? " enabled " :
2008-11-08 01:05:41 +03:00
" disabled " ) ;
}
static ssize_t edid_show ( struct kobject * kobj , struct bin_attribute * attr ,
char * buf , loff_t off , size_t count )
{
struct device * connector_dev = container_of ( kobj , struct device , kobj ) ;
struct drm_connector * connector = to_drm_connector ( connector_dev ) ;
unsigned char * edid ;
size_t size ;
if ( ! connector - > edid_blob_ptr )
return 0 ;
edid = connector - > edid_blob_ptr - > data ;
size = connector - > edid_blob_ptr - > length ;
if ( ! edid )
return 0 ;
if ( off > = size )
return 0 ;
if ( off + count > size )
count = size - off ;
memcpy ( buf , edid + off , count ) ;
return count ;
}
static ssize_t modes_show ( struct device * device ,
struct device_attribute * attr ,
char * buf )
{
struct drm_connector * connector = to_drm_connector ( device ) ;
struct drm_display_mode * mode ;
int written = 0 ;
list_for_each_entry ( mode , & connector - > modes , head ) {
written + = snprintf ( buf + written , PAGE_SIZE - written , " %s \n " ,
mode - > name ) ;
}
return written ;
}
static ssize_t subconnector_show ( struct device * device ,
struct device_attribute * attr ,
char * buf )
{
struct drm_connector * connector = to_drm_connector ( device ) ;
struct drm_device * dev = connector - > dev ;
struct drm_property * prop = NULL ;
uint64_t subconnector ;
int is_tv = 0 ;
int ret ;
switch ( connector - > connector_type ) {
case DRM_MODE_CONNECTOR_DVII :
prop = dev - > mode_config . dvi_i_subconnector_property ;
break ;
case DRM_MODE_CONNECTOR_Composite :
case DRM_MODE_CONNECTOR_SVIDEO :
case DRM_MODE_CONNECTOR_Component :
2009-08-02 06:19:18 +04:00
case DRM_MODE_CONNECTOR_TV :
2008-11-08 01:05:41 +03:00
prop = dev - > mode_config . tv_subconnector_property ;
is_tv = 1 ;
break ;
default :
DRM_ERROR ( " Wrong connector type for this property \n " ) ;
return 0 ;
}
if ( ! prop ) {
DRM_ERROR ( " Unable to find subconnector property \n " ) ;
return 0 ;
}
ret = drm_connector_property_get_value ( connector , prop , & subconnector ) ;
if ( ret )
return 0 ;
return snprintf ( buf , PAGE_SIZE , " %s " , is_tv ?
drm_get_tv_subconnector_name ( ( int ) subconnector ) :
drm_get_dvi_i_subconnector_name ( ( int ) subconnector ) ) ;
}
static ssize_t select_subconnector_show ( struct device * device ,
struct device_attribute * attr ,
char * buf )
{
struct drm_connector * connector = to_drm_connector ( device ) ;
struct drm_device * dev = connector - > dev ;
struct drm_property * prop = NULL ;
uint64_t subconnector ;
int is_tv = 0 ;
int ret ;
switch ( connector - > connector_type ) {
case DRM_MODE_CONNECTOR_DVII :
prop = dev - > mode_config . dvi_i_select_subconnector_property ;
break ;
case DRM_MODE_CONNECTOR_Composite :
case DRM_MODE_CONNECTOR_SVIDEO :
case DRM_MODE_CONNECTOR_Component :
2009-08-02 06:19:18 +04:00
case DRM_MODE_CONNECTOR_TV :
2008-11-08 01:05:41 +03:00
prop = dev - > mode_config . tv_select_subconnector_property ;
is_tv = 1 ;
break ;
default :
DRM_ERROR ( " Wrong connector type for this property \n " ) ;
return 0 ;
}
if ( ! prop ) {
DRM_ERROR ( " Unable to find select subconnector property \n " ) ;
return 0 ;
}
ret = drm_connector_property_get_value ( connector , prop , & subconnector ) ;
if ( ret )
return 0 ;
return snprintf ( buf , PAGE_SIZE , " %s " , is_tv ?
drm_get_tv_select_name ( ( int ) subconnector ) :
drm_get_dvi_i_select_name ( ( int ) subconnector ) ) ;
}
static struct device_attribute connector_attrs [ ] = {
__ATTR_RO ( status ) ,
__ATTR_RO ( enabled ) ,
__ATTR_RO ( dpms ) ,
__ATTR_RO ( modes ) ,
} ;
/* These attributes are for both DVI-I connectors and all types of tv-out. */
static struct device_attribute connector_attrs_opt1 [ ] = {
__ATTR_RO ( subconnector ) ,
__ATTR_RO ( select_subconnector ) ,
} ;
static struct bin_attribute edid_attr = {
. attr . name = " edid " ,
2009-05-31 07:42:26 +04:00
. attr . mode = 0444 ,
2008-11-08 01:05:41 +03:00
. size = 128 ,
. read = edid_show ,
} ;
/**
* drm_sysfs_connector_add - add an connector to sysfs
* @ connector : connector to add
*
* Create an connector device in sysfs , along with its associated connector
* properties ( so far , connection status , dpms , mode list & edid ) and
* generate a hotplug event so userspace knows there ' s a new connector
* available .
*
* Note :
* This routine should only be called * once * for each DRM minor registered .
* A second call for an already registered device will trigger the BUG_ON
* below .
*/
int drm_sysfs_connector_add ( struct drm_connector * connector )
{
struct drm_device * dev = connector - > dev ;
int ret = 0 , i , j ;
/* We shouldn't get called more than once for the same connector */
BUG_ON ( device_is_registered ( & connector - > kdev ) ) ;
connector - > kdev . parent = & dev - > primary - > kdev ;
connector - > kdev . class = drm_class ;
connector - > kdev . release = drm_sysfs_device_release ;
DRM_DEBUG ( " adding \" %s \" to sysfs \n " ,
drm_get_connector_name ( connector ) ) ;
2009-03-25 02:38:22 +03:00
dev_set_name ( & connector - > kdev , " card%d-%s " ,
dev - > primary - > index , drm_get_connector_name ( connector ) ) ;
2008-11-08 01:05:41 +03:00
ret = device_register ( & connector - > kdev ) ;
if ( ret ) {
DRM_ERROR ( " failed to register connector device: %d \n " , ret ) ;
goto out ;
}
/* Standard attributes */
for ( i = 0 ; i < ARRAY_SIZE ( connector_attrs ) ; i + + ) {
ret = device_create_file ( & connector - > kdev , & connector_attrs [ i ] ) ;
if ( ret )
goto err_out_files ;
}
/* Optional attributes */
/*
* In the long run it maybe a good idea to make one set of
* optionals per connector type .
*/
switch ( connector - > connector_type ) {
case DRM_MODE_CONNECTOR_DVII :
case DRM_MODE_CONNECTOR_Composite :
case DRM_MODE_CONNECTOR_SVIDEO :
case DRM_MODE_CONNECTOR_Component :
2009-08-02 06:19:18 +04:00
case DRM_MODE_CONNECTOR_TV :
2008-11-08 01:05:41 +03:00
for ( i = 0 ; i < ARRAY_SIZE ( connector_attrs_opt1 ) ; i + + ) {
ret = device_create_file ( & connector - > kdev , & connector_attrs_opt1 [ i ] ) ;
if ( ret )
goto err_out_files ;
}
break ;
default :
break ;
}
ret = sysfs_create_bin_file ( & connector - > kdev . kobj , & edid_attr ) ;
if ( ret )
goto err_out_files ;
/* Let userspace know we have a new connector */
drm_sysfs_hotplug_event ( dev ) ;
return 0 ;
err_out_files :
if ( i > 0 )
for ( j = 0 ; j < i ; j + + )
device_remove_file ( & connector - > kdev ,
& connector_attrs [ i ] ) ;
device_unregister ( & connector - > kdev ) ;
out :
return ret ;
}
EXPORT_SYMBOL ( drm_sysfs_connector_add ) ;
/**
* drm_sysfs_connector_remove - remove an connector device from sysfs
* @ connector : connector to remove
*
* Remove @ connector and its associated attributes from sysfs . Note that
* the device model core will take care of sending the " remove " uevent
* at this time , so we don ' t need to do it .
*
* Note :
* This routine should only be called if the connector was previously
* successfully registered . If @ connector hasn ' t been registered yet ,
* you ' ll likely see a panic somewhere deep in sysfs code when called .
*/
void drm_sysfs_connector_remove ( struct drm_connector * connector )
{
int i ;
DRM_DEBUG ( " removing \" %s \" from sysfs \n " ,
drm_get_connector_name ( connector ) ) ;
for ( i = 0 ; i < ARRAY_SIZE ( connector_attrs ) ; i + + )
device_remove_file ( & connector - > kdev , & connector_attrs [ i ] ) ;
sysfs_remove_bin_file ( & connector - > kdev . kobj , & edid_attr ) ;
device_unregister ( & connector - > kdev ) ;
}
EXPORT_SYMBOL ( drm_sysfs_connector_remove ) ;
/**
* drm_sysfs_hotplug_event - generate a DRM uevent
* @ dev : DRM device
*
* Send a uevent for the DRM device specified by @ dev . Currently we only
* set HOTPLUG = 1 in the uevent environment , but this could be expanded to
* deal with other types of events .
*/
void drm_sysfs_hotplug_event ( struct drm_device * dev )
{
char * event_string = " HOTPLUG=1 " ;
char * envp [ ] = { event_string , NULL } ;
DRM_DEBUG ( " generating hotplug event \n " ) ;
kobject_uevent_env ( & dev - > primary - > kdev . kobj , KOBJ_CHANGE , envp ) ;
}
2009-04-01 01:11:15 +04:00
EXPORT_SYMBOL ( drm_sysfs_hotplug_event ) ;
2008-11-08 01:05:41 +03:00
2005-04-17 02:20:36 +04:00
/**
* drm_sysfs_device_add - adds a class device to sysfs for a character driver
2007-11-22 07:02:38 +03:00
* @ dev : DRM device to be added
* @ head : DRM head in question
2005-04-17 02:20:36 +04:00
*
2007-11-22 07:02:38 +03:00
* Add a DRM device to the DRM ' s device model class . We use @ dev ' s PCI device
* as the parent for the Linux device , and make sure it has a file containing
* the driver we ' re using ( for userspace compatibility ) .
2005-04-17 02:20:36 +04:00
*/
2008-04-21 10:47:32 +04:00
int drm_sysfs_device_add ( struct drm_minor * minor )
2005-04-17 02:20:36 +04:00
{
2007-11-22 07:02:38 +03:00
int err ;
2008-04-21 10:47:32 +04:00
char * minor_str ;
2007-11-22 07:02:38 +03:00
2008-04-21 10:47:32 +04:00
minor - > kdev . parent = & minor - > dev - > pdev - > dev ;
minor - > kdev . class = drm_class ;
minor - > kdev . release = drm_sysfs_device_release ;
minor - > kdev . devt = minor - > device ;
2009-08-20 13:02:31 +04:00
minor - > kdev . type = & drm_sysfs_device_minor ;
2008-11-08 01:05:41 +03:00
if ( minor - > type = = DRM_MINOR_CONTROL )
minor_str = " controlD%d " ;
else if ( minor - > type = = DRM_MINOR_RENDER )
minor_str = " renderD%d " ;
else
minor_str = " card%d " ;
2007-11-22 07:02:38 +03:00
2009-01-06 21:44:41 +03:00
dev_set_name ( & minor - > kdev , minor_str , minor - > index ) ;
2008-04-21 10:47:32 +04:00
err = device_register ( & minor - > kdev ) ;
2007-11-22 07:02:38 +03:00
if ( err ) {
DRM_ERROR ( " device add failed: %d \n " , err ) ;
2006-10-11 01:23:37 +04:00
goto err_out ;
}
2005-04-17 02:20:36 +04:00
2007-11-22 07:02:38 +03:00
return 0 ;
2006-10-11 01:23:37 +04:00
err_out :
2007-11-22 07:02:38 +03:00
return err ;
2005-04-17 02:20:36 +04:00
}
/**
2007-11-22 07:02:38 +03:00
* drm_sysfs_device_remove - remove DRM device
* @ dev : DRM device to remove
2005-04-17 02:20:36 +04:00
*
* This call unregisters and cleans up a class device that was created with a
* call to drm_sysfs_device_add ( )
*/
2008-04-21 10:47:32 +04:00
void drm_sysfs_device_remove ( struct drm_minor * minor )
2005-04-17 02:20:36 +04:00
{
2008-04-21 10:47:32 +04:00
device_unregister ( & minor - > kdev ) ;
2005-04-17 02:20:36 +04:00
}
2009-08-17 18:28:37 +04:00
/**
* drm_class_device_register - Register a struct device in the drm class .
*
* @ dev : pointer to struct device to register .
*
* @ dev should have all relevant members pre - filled with the exception
* of the class member . In particular , the device_type member must
* be set .
*/
int drm_class_device_register ( struct device * dev )
{
dev - > class = drm_class ;
return device_register ( dev ) ;
}
EXPORT_SYMBOL_GPL ( drm_class_device_register ) ;
void drm_class_device_unregister ( struct device * dev )
{
return device_unregister ( dev ) ;
}
EXPORT_SYMBOL_GPL ( drm_class_device_unregister ) ;