2015-02-02 14:26:25 +03:00
/*
* cros_ec_dev - expose the Chrome OS Embedded Controller to user - space
*
* Copyright ( C ) 2014 Google , Inc .
*
* 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 , see < http : //www.gnu.org/licenses/>.
*/
# include <linux/fs.h>
2016-08-01 12:54:38 +03:00
# include <linux/mfd/core.h>
2015-02-02 14:26:25 +03:00
# include <linux/module.h>
# include <linux/platform_device.h>
2017-05-16 18:46:48 +03:00
# include <linux/pm.h>
2015-06-09 14:04:42 +03:00
# include <linux/slab.h>
2015-02-02 14:26:25 +03:00
# include <linux/uaccess.h>
# include "cros_ec_dev.h"
2017-11-20 19:15:25 +03:00
# define DRV_NAME "cros-ec-dev"
2015-02-02 14:26:25 +03:00
/* Device variables */
# define CROS_MAX_DEV 128
static int ec_major ;
2015-06-09 14:04:47 +03:00
static const struct attribute_group * cros_ec_groups [ ] = {
& cros_ec_attr_group ,
& cros_ec_lightbar_attr_group ,
2015-09-21 16:38:22 +03:00
& cros_ec_vbc_attr_group ,
2015-06-09 14:04:47 +03:00
NULL ,
} ;
static struct class cros_class = {
. owner = THIS_MODULE ,
. name = " chromeos " ,
. dev_groups = cros_ec_groups ,
} ;
2015-02-02 14:26:25 +03:00
/* Basic communication */
2015-06-09 14:04:47 +03:00
static int ec_get_version ( struct cros_ec_dev * ec , char * str , int maxlen )
2015-02-02 14:26:25 +03:00
{
struct ec_response_get_version * resp ;
static const char * const current_image_name [ ] = {
" unknown " , " read-only " , " read-write " , " invalid " ,
} ;
2015-06-09 14:04:42 +03:00
struct cros_ec_command * msg ;
2015-02-02 14:26:25 +03:00
int ret ;
2015-06-09 14:04:42 +03:00
msg = kmalloc ( sizeof ( * msg ) + sizeof ( * resp ) , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
msg - > version = 0 ;
2015-06-09 14:04:47 +03:00
msg - > command = EC_CMD_GET_VERSION + ec - > cmd_offset ;
2015-06-09 14:04:42 +03:00
msg - > insize = sizeof ( * resp ) ;
msg - > outsize = 0 ;
2015-06-09 14:04:47 +03:00
ret = cros_ec_cmd_xfer ( ec - > ec_dev , msg ) ;
2015-02-02 14:26:25 +03:00
if ( ret < 0 )
2015-06-09 14:04:42 +03:00
goto exit ;
2015-02-02 14:26:25 +03:00
2015-06-09 14:04:42 +03:00
if ( msg - > result ! = EC_RES_SUCCESS ) {
2015-02-02 14:26:25 +03:00
snprintf ( str , maxlen ,
" %s \n Unknown EC version: EC returned %d \n " ,
2015-06-09 14:04:42 +03:00
CROS_EC_DEV_VERSION , msg - > result ) ;
ret = - EINVAL ;
goto exit ;
2015-02-02 14:26:25 +03:00
}
2015-06-09 14:04:42 +03:00
resp = ( struct ec_response_get_version * ) msg - > data ;
2015-02-02 14:26:25 +03:00
if ( resp - > current_image > = ARRAY_SIZE ( current_image_name ) )
resp - > current_image = 3 ; /* invalid */
2015-03-03 15:22:32 +03:00
snprintf ( str , maxlen , " %s \n %s \n %s \n %s \n " , CROS_EC_DEV_VERSION ,
2015-02-02 14:26:25 +03:00
resp - > version_string_ro , resp - > version_string_rw ,
current_image_name [ resp - > current_image ] ) ;
2015-06-09 14:04:42 +03:00
ret = 0 ;
exit :
kfree ( msg ) ;
return ret ;
2015-02-02 14:26:25 +03:00
}
2016-08-01 12:54:37 +03:00
static int cros_ec_check_features ( struct cros_ec_dev * ec , int feature )
{
struct cros_ec_command * msg ;
int ret ;
if ( ec - > features [ 0 ] = = - 1U & & ec - > features [ 1 ] = = - 1U ) {
/* features bitmap not read yet */
msg = kmalloc ( sizeof ( * msg ) + sizeof ( ec - > features ) , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
msg - > version = 0 ;
msg - > command = EC_CMD_GET_FEATURES + ec - > cmd_offset ;
msg - > insize = sizeof ( ec - > features ) ;
msg - > outsize = 0 ;
ret = cros_ec_cmd_xfer ( ec - > ec_dev , msg ) ;
if ( ret < 0 | | msg - > result ! = EC_RES_SUCCESS ) {
dev_warn ( ec - > dev , " cannot get EC features: %d/%d \n " ,
ret , msg - > result ) ;
memset ( ec - > features , 0 , sizeof ( ec - > features ) ) ;
}
memcpy ( ec - > features , msg - > data , sizeof ( ec - > features ) ) ;
dev_dbg ( ec - > dev , " EC features %08x %08x \n " ,
ec - > features [ 0 ] , ec - > features [ 1 ] ) ;
kfree ( msg ) ;
}
return ec - > features [ feature / 32 ] & EC_FEATURE_MASK_0 ( feature ) ;
}
2015-02-02 14:26:25 +03:00
/* Device file ops */
static int ec_device_open ( struct inode * inode , struct file * filp )
{
2015-06-09 14:04:47 +03:00
struct cros_ec_dev * ec = container_of ( inode - > i_cdev ,
struct cros_ec_dev , cdev ) ;
filp - > private_data = ec ;
nonseekable_open ( inode , filp ) ;
2015-02-02 14:26:25 +03:00
return 0 ;
}
static int ec_device_release ( struct inode * inode , struct file * filp )
{
return 0 ;
}
static ssize_t ec_device_read ( struct file * filp , char __user * buffer ,
size_t length , loff_t * offset )
{
2015-06-09 14:04:47 +03:00
struct cros_ec_dev * ec = filp - > private_data ;
2015-02-02 14:26:25 +03:00
char msg [ sizeof ( struct ec_response_get_version ) +
sizeof ( CROS_EC_DEV_VERSION ) ] ;
size_t count ;
int ret ;
if ( * offset ! = 0 )
return 0 ;
ret = ec_get_version ( ec , msg , sizeof ( msg ) ) ;
if ( ret )
return ret ;
count = min ( length , strlen ( msg ) ) ;
if ( copy_to_user ( buffer , msg , count ) )
return - EFAULT ;
* offset = count ;
return count ;
}
/* Ioctls */
2015-06-09 14:04:47 +03:00
static long ec_device_ioctl_xcmd ( struct cros_ec_dev * ec , void __user * arg )
2015-02-02 14:26:25 +03:00
{
long ret ;
2015-06-09 14:04:42 +03:00
struct cros_ec_command u_cmd ;
struct cros_ec_command * s_cmd ;
2015-02-02 14:26:25 +03:00
2015-06-09 14:04:42 +03:00
if ( copy_from_user ( & u_cmd , arg , sizeof ( u_cmd ) ) )
2015-02-02 14:26:25 +03:00
return - EFAULT ;
2016-03-08 20:13:52 +03:00
if ( ( u_cmd . outsize > EC_MAX_MSG_BYTES ) | |
( u_cmd . insize > EC_MAX_MSG_BYTES ) )
return - EINVAL ;
2015-06-09 14:04:42 +03:00
s_cmd = kmalloc ( sizeof ( * s_cmd ) + max ( u_cmd . outsize , u_cmd . insize ) ,
GFP_KERNEL ) ;
if ( ! s_cmd )
return - ENOMEM ;
if ( copy_from_user ( s_cmd , arg , sizeof ( * s_cmd ) + u_cmd . outsize ) ) {
ret = - EFAULT ;
goto exit ;
}
2016-06-21 16:58:46 +03:00
if ( u_cmd . outsize ! = s_cmd - > outsize | |
u_cmd . insize ! = s_cmd - > insize ) {
ret = - EINVAL ;
goto exit ;
}
2015-06-09 14:04:47 +03:00
s_cmd - > command + = ec - > cmd_offset ;
ret = cros_ec_cmd_xfer ( ec - > ec_dev , s_cmd ) ;
2015-02-02 14:26:25 +03:00
/* Only copy data to userland if data was received. */
if ( ret < 0 )
2015-06-09 14:04:42 +03:00
goto exit ;
2015-02-02 14:26:25 +03:00
2016-06-21 16:58:46 +03:00
if ( copy_to_user ( arg , s_cmd , sizeof ( * s_cmd ) + s_cmd - > insize ) )
2015-06-09 14:04:42 +03:00
ret = - EFAULT ;
exit :
kfree ( s_cmd ) ;
return ret ;
2015-02-02 14:26:25 +03:00
}
2015-06-09 14:04:47 +03:00
static long ec_device_ioctl_readmem ( struct cros_ec_dev * ec , void __user * arg )
2015-02-02 14:26:25 +03:00
{
2015-06-09 14:04:47 +03:00
struct cros_ec_device * ec_dev = ec - > ec_dev ;
2015-02-02 14:26:25 +03:00
struct cros_ec_readmem s_mem = { } ;
long num ;
/* Not every platform supports direct reads */
2015-06-09 14:04:47 +03:00
if ( ! ec_dev - > cmd_readmem )
2015-02-02 14:26:25 +03:00
return - ENOTTY ;
if ( copy_from_user ( & s_mem , arg , sizeof ( s_mem ) ) )
return - EFAULT ;
2015-06-09 14:04:47 +03:00
num = ec_dev - > cmd_readmem ( ec_dev , s_mem . offset , s_mem . bytes ,
s_mem . buffer ) ;
2015-02-02 14:26:25 +03:00
if ( num < = 0 )
return num ;
if ( copy_to_user ( ( void __user * ) arg , & s_mem , sizeof ( s_mem ) ) )
return - EFAULT ;
return 0 ;
}
static long ec_device_ioctl ( struct file * filp , unsigned int cmd ,
unsigned long arg )
{
2015-06-09 14:04:47 +03:00
struct cros_ec_dev * ec = filp - > private_data ;
2015-02-02 14:26:25 +03:00
if ( _IOC_TYPE ( cmd ) ! = CROS_EC_DEV_IOC )
return - ENOTTY ;
switch ( cmd ) {
case CROS_EC_DEV_IOCXCMD :
return ec_device_ioctl_xcmd ( ec , ( void __user * ) arg ) ;
case CROS_EC_DEV_IOCRDMEM :
return ec_device_ioctl_readmem ( ec , ( void __user * ) arg ) ;
}
return - ENOTTY ;
}
/* Module initialization */
static const struct file_operations fops = {
. open = ec_device_open ,
. release = ec_device_release ,
. read = ec_device_read ,
. unlocked_ioctl = ec_device_ioctl ,
2016-04-15 05:35:29 +03:00
# ifdef CONFIG_COMPAT
. compat_ioctl = ec_device_ioctl ,
# endif
2015-02-02 14:26:25 +03:00
} ;
2015-06-09 14:04:47 +03:00
static void __remove ( struct device * dev )
{
struct cros_ec_dev * ec = container_of ( dev , struct cros_ec_dev ,
class_dev ) ;
kfree ( ec ) ;
}
2016-08-01 12:54:38 +03:00
static void cros_ec_sensors_register ( struct cros_ec_dev * ec )
{
/*
* Issue a command to get the number of sensor reported .
* Build an array of sensors driver and register them all .
*/
int ret , i , id , sensor_num ;
struct mfd_cell * sensor_cells ;
struct cros_ec_sensor_platform * sensor_platforms ;
int sensor_type [ MOTIONSENSE_TYPE_MAX ] ;
struct ec_params_motion_sense * params ;
struct ec_response_motion_sense * resp ;
struct cros_ec_command * msg ;
msg = kzalloc ( sizeof ( struct cros_ec_command ) +
max ( sizeof ( * params ) , sizeof ( * resp ) ) , GFP_KERNEL ) ;
if ( msg = = NULL )
return ;
msg - > version = 2 ;
msg - > command = EC_CMD_MOTION_SENSE_CMD + ec - > cmd_offset ;
msg - > outsize = sizeof ( * params ) ;
msg - > insize = sizeof ( * resp ) ;
params = ( struct ec_params_motion_sense * ) msg - > data ;
params - > cmd = MOTIONSENSE_CMD_DUMP ;
ret = cros_ec_cmd_xfer ( ec - > ec_dev , msg ) ;
if ( ret < 0 | | msg - > result ! = EC_RES_SUCCESS ) {
dev_warn ( ec - > dev , " cannot get EC sensor information: %d/%d \n " ,
ret , msg - > result ) ;
goto error ;
}
resp = ( struct ec_response_motion_sense * ) msg - > data ;
sensor_num = resp - > dump . sensor_count ;
/* Allocate 2 extra sensors in case lid angle or FIFO are needed */
sensor_cells = kzalloc ( sizeof ( struct mfd_cell ) * ( sensor_num + 2 ) ,
GFP_KERNEL ) ;
if ( sensor_cells = = NULL )
goto error ;
sensor_platforms = kzalloc ( sizeof ( struct cros_ec_sensor_platform ) *
( sensor_num + 1 ) , GFP_KERNEL ) ;
if ( sensor_platforms = = NULL )
goto error_platforms ;
memset ( sensor_type , 0 , sizeof ( sensor_type ) ) ;
id = 0 ;
for ( i = 0 ; i < sensor_num ; i + + ) {
params - > cmd = MOTIONSENSE_CMD_INFO ;
params - > info . sensor_num = i ;
ret = cros_ec_cmd_xfer ( ec - > ec_dev , msg ) ;
if ( ret < 0 | | msg - > result ! = EC_RES_SUCCESS ) {
dev_warn ( ec - > dev , " no info for EC sensor %d : %d/%d \n " ,
i , ret , msg - > result ) ;
continue ;
}
switch ( resp - > info . type ) {
case MOTIONSENSE_TYPE_ACCEL :
sensor_cells [ id ] . name = " cros-ec-accel " ;
break ;
2017-01-24 16:41:41 +03:00
case MOTIONSENSE_TYPE_BARO :
sensor_cells [ id ] . name = " cros-ec-baro " ;
break ;
2016-08-01 12:54:38 +03:00
case MOTIONSENSE_TYPE_GYRO :
sensor_cells [ id ] . name = " cros-ec-gyro " ;
break ;
case MOTIONSENSE_TYPE_MAG :
sensor_cells [ id ] . name = " cros-ec-mag " ;
break ;
case MOTIONSENSE_TYPE_PROX :
sensor_cells [ id ] . name = " cros-ec-prox " ;
break ;
case MOTIONSENSE_TYPE_LIGHT :
sensor_cells [ id ] . name = " cros-ec-light " ;
break ;
case MOTIONSENSE_TYPE_ACTIVITY :
sensor_cells [ id ] . name = " cros-ec-activity " ;
break ;
default :
dev_warn ( ec - > dev , " unknown type %d \n " , resp - > info . type ) ;
continue ;
}
sensor_platforms [ id ] . sensor_num = i ;
sensor_cells [ id ] . id = sensor_type [ resp - > info . type ] ;
sensor_cells [ id ] . platform_data = & sensor_platforms [ id ] ;
sensor_cells [ id ] . pdata_size =
sizeof ( struct cros_ec_sensor_platform ) ;
sensor_type [ resp - > info . type ] + + ;
id + + ;
}
if ( sensor_type [ MOTIONSENSE_TYPE_ACCEL ] > = 2 ) {
sensor_platforms [ id ] . sensor_num = sensor_num ;
sensor_cells [ id ] . name = " cros-ec-angle " ;
sensor_cells [ id ] . id = 0 ;
sensor_cells [ id ] . platform_data = & sensor_platforms [ id ] ;
sensor_cells [ id ] . pdata_size =
sizeof ( struct cros_ec_sensor_platform ) ;
id + + ;
}
if ( cros_ec_check_features ( ec , EC_FEATURE_MOTION_SENSE_FIFO ) ) {
sensor_cells [ id ] . name = " cros-ec-ring " ;
id + + ;
}
ret = mfd_add_devices ( ec - > dev , 0 , sensor_cells , id ,
NULL , 0 , NULL ) ;
if ( ret )
dev_err ( ec - > dev , " failed to add EC sensors \n " ) ;
kfree ( sensor_platforms ) ;
error_platforms :
kfree ( sensor_cells ) ;
error :
kfree ( msg ) ;
}
2015-02-02 14:26:25 +03:00
static int ec_device_probe ( struct platform_device * pdev )
{
2015-06-09 14:04:47 +03:00
int retval = - ENOMEM ;
struct device * dev = & pdev - > dev ;
struct cros_ec_platform * ec_platform = dev_get_platdata ( dev ) ;
struct cros_ec_dev * ec = kzalloc ( sizeof ( * ec ) , GFP_KERNEL ) ;
if ( ! ec )
return retval ;
2015-02-02 14:26:25 +03:00
2015-06-09 14:04:47 +03:00
dev_set_drvdata ( dev , ec ) ;
ec - > ec_dev = dev_get_drvdata ( dev - > parent ) ;
ec - > dev = dev ;
ec - > cmd_offset = ec_platform - > cmd_offset ;
2016-08-01 12:54:37 +03:00
ec - > features [ 0 ] = - 1U ; /* Not cached yet */
ec - > features [ 1 ] = - 1U ; /* Not cached yet */
2015-06-09 14:04:47 +03:00
device_initialize ( & ec - > class_dev ) ;
2015-02-02 14:26:25 +03:00
cdev_init ( & ec - > cdev , & fops ) ;
2015-06-09 14:04:47 +03:00
/*
* Add the class device
* Link to the character device for creating the / dev entry
* in devtmpfs .
*/
2017-03-17 21:48:14 +03:00
ec - > class_dev . devt = MKDEV ( ec_major , pdev - > id ) ;
2015-06-09 14:04:47 +03:00
ec - > class_dev . class = & cros_class ;
ec - > class_dev . parent = dev ;
ec - > class_dev . release = __remove ;
retval = dev_set_name ( & ec - > class_dev , " %s " , ec_platform - > ec_name ) ;
if ( retval ) {
dev_err ( dev , " dev_set_name failed => %d \n " , retval ) ;
2017-03-17 21:48:14 +03:00
goto failed ;
2015-02-02 14:26:25 +03:00
}
2017-03-17 21:48:14 +03:00
retval = cdev_device_add ( & ec - > cdev , & ec - > class_dev ) ;
2015-06-09 14:04:47 +03:00
if ( retval ) {
2017-03-17 21:48:14 +03:00
dev_err ( dev , " cdev_device_add failed => %d \n " , retval ) ;
goto failed ;
2015-06-09 14:04:47 +03:00
}
2015-02-02 14:26:27 +03:00
2017-05-16 18:46:48 +03:00
if ( cros_ec_debugfs_init ( ec ) )
dev_warn ( dev , " failed to create debugfs directory \n " ) ;
2016-08-01 12:54:38 +03:00
/* check whether this EC is a sensor hub. */
if ( cros_ec_check_features ( ec , EC_FEATURE_MOTION_SENSE ) )
cros_ec_sensors_register ( ec ) ;
2017-05-16 18:46:48 +03:00
/* Take control of the lightbar from the EC. */
2017-05-16 18:46:48 +03:00
lb_manual_suspend_ctrl ( ec , 1 ) ;
2017-05-16 18:46:48 +03:00
2015-02-02 14:26:25 +03:00
return 0 ;
2015-06-09 14:04:47 +03:00
2017-03-17 21:48:14 +03:00
failed :
put_device ( & ec - > class_dev ) ;
2015-06-09 14:04:47 +03:00
return retval ;
2015-02-02 14:26:25 +03:00
}
static int ec_device_remove ( struct platform_device * pdev )
{
2015-06-09 14:04:47 +03:00
struct cros_ec_dev * ec = dev_get_drvdata ( & pdev - > dev ) ;
2017-05-16 18:46:48 +03:00
2017-05-16 18:46:48 +03:00
/* Let the EC take over the lightbar again. */
2017-05-16 18:46:48 +03:00
lb_manual_suspend_ctrl ( ec , 0 ) ;
2017-05-16 18:46:48 +03:00
2017-05-16 18:46:48 +03:00
cros_ec_debugfs_remove ( ec ) ;
2015-02-02 14:26:25 +03:00
cdev_del ( & ec - > cdev ) ;
2015-06-09 14:04:47 +03:00
device_unregister ( & ec - > class_dev ) ;
2015-02-02 14:26:25 +03:00
return 0 ;
}
2015-06-22 09:27:20 +03:00
static const struct platform_device_id cros_ec_id [ ] = {
2017-11-20 19:15:25 +03:00
{ DRV_NAME , 0 } ,
2015-06-22 09:27:20 +03:00
{ /* sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( platform , cros_ec_id ) ;
2017-06-27 18:36:36 +03:00
static __maybe_unused int ec_device_suspend ( struct device * dev )
2017-05-16 18:46:48 +03:00
{
struct cros_ec_dev * ec = dev_get_drvdata ( dev ) ;
2017-05-16 18:46:48 +03:00
lb_suspend ( ec ) ;
2017-05-16 18:46:48 +03:00
return 0 ;
}
2017-06-27 18:36:36 +03:00
static __maybe_unused int ec_device_resume ( struct device * dev )
2017-05-16 18:46:48 +03:00
{
struct cros_ec_dev * ec = dev_get_drvdata ( dev ) ;
2017-05-16 18:46:48 +03:00
lb_resume ( ec ) ;
2017-05-16 18:46:48 +03:00
return 0 ;
}
static const struct dev_pm_ops cros_ec_dev_pm_ops = {
# ifdef CONFIG_PM_SLEEP
. suspend = ec_device_suspend ,
. resume = ec_device_resume ,
# endif
} ;
2015-02-02 14:26:25 +03:00
static struct platform_driver cros_ec_dev_driver = {
. driver = {
2017-11-20 19:15:25 +03:00
. name = DRV_NAME ,
2017-05-16 18:46:48 +03:00
. pm = & cros_ec_dev_pm_ops ,
2015-02-02 14:26:25 +03:00
} ,
. probe = ec_device_probe ,
. remove = ec_device_remove ,
} ;
static int __init cros_ec_dev_init ( void )
{
int ret ;
dev_t dev = 0 ;
2015-06-09 14:04:47 +03:00
ret = class_register ( & cros_class ) ;
if ( ret ) {
2015-02-02 14:26:25 +03:00
pr_err ( CROS_EC_DEV_NAME " : failed to register device class \n " ) ;
2015-06-09 14:04:47 +03:00
return ret ;
2015-02-02 14:26:25 +03:00
}
/* Get a range of minor numbers (starting with 0) to work with */
ret = alloc_chrdev_region ( & dev , 0 , CROS_MAX_DEV , CROS_EC_DEV_NAME ) ;
if ( ret < 0 ) {
pr_err ( CROS_EC_DEV_NAME " : alloc_chrdev_region() failed \n " ) ;
goto failed_chrdevreg ;
}
ec_major = MAJOR ( dev ) ;
/* Register the driver */
ret = platform_driver_register ( & cros_ec_dev_driver ) ;
if ( ret < 0 ) {
pr_warn ( CROS_EC_DEV_NAME " : can't register driver: %d \n " , ret ) ;
goto failed_devreg ;
}
return 0 ;
failed_devreg :
unregister_chrdev_region ( MKDEV ( ec_major , 0 ) , CROS_MAX_DEV ) ;
failed_chrdevreg :
2015-06-09 14:04:47 +03:00
class_unregister ( & cros_class ) ;
2015-02-02 14:26:25 +03:00
return ret ;
}
static void __exit cros_ec_dev_exit ( void )
{
platform_driver_unregister ( & cros_ec_dev_driver ) ;
unregister_chrdev ( ec_major , CROS_EC_DEV_NAME ) ;
2015-06-09 14:04:47 +03:00
class_unregister ( & cros_class ) ;
2015-02-02 14:26:25 +03:00
}
module_init ( cros_ec_dev_init ) ;
module_exit ( cros_ec_dev_exit ) ;
2017-11-20 19:15:25 +03:00
MODULE_ALIAS ( " platform: " DRV_NAME ) ;
2015-02-02 14:26:25 +03:00
MODULE_AUTHOR ( " Bill Richardson <wfrichar@chromium.org> " ) ;
MODULE_DESCRIPTION ( " Userspace interface to the Chrome OS Embedded Controller " ) ;
MODULE_VERSION ( " 1.0 " ) ;
MODULE_LICENSE ( " GPL " ) ;