2019-01-29 10:38:15 +01:00
// SPDX-License-Identifier: GPL-2.0+
// Expose the ChromeOS EC through sysfs
//
// Copyright (C) 2014 Google, Inc.
2015-02-02 12:26:27 +01:00
# include <linux/ctype.h>
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/fs.h>
# include <linux/kobject.h>
# include <linux/module.h>
2019-09-02 11:53:05 +02:00
# include <linux/platform_data/cros_ec_commands.h>
# include <linux/platform_data/cros_ec_proto.h>
2015-02-02 12:26:27 +01:00
# include <linux/platform_device.h>
# include <linux/printk.h>
2015-06-09 13:04:42 +02:00
# include <linux/slab.h>
2015-02-02 12:26:27 +01:00
# include <linux/stat.h>
# include <linux/types.h>
# include <linux/uaccess.h>
2018-12-12 18:34:00 +01:00
# define DRV_NAME "cros-ec-sysfs"
2015-02-02 12:26:27 +01:00
/* Accessor functions */
2018-03-23 18:42:44 +01:00
static ssize_t reboot_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
2015-02-02 12:26:27 +01:00
{
int count = 0 ;
2022-12-05 14:10:42 +08:00
count + = sysfs_emit_at ( buf , count ,
" ro|rw|cancel|cold|disable-jump|hibernate|cold-ap-off " ) ;
count + = sysfs_emit_at ( buf , count , " [at-shutdown] \n " ) ;
2015-02-02 12:26:27 +01:00
return count ;
}
2018-03-23 18:42:44 +01:00
static ssize_t reboot_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
2015-02-02 12:26:27 +01:00
{
static const struct {
const char * const str ;
uint8_t cmd ;
uint8_t flags ;
} words [ ] = {
{ " cancel " , EC_REBOOT_CANCEL , 0 } ,
{ " ro " , EC_REBOOT_JUMP_RO , 0 } ,
{ " rw " , EC_REBOOT_JUMP_RW , 0 } ,
2020-12-21 12:12:24 +08:00
{ " cold-ap-off " , EC_REBOOT_COLD_AP_OFF , 0 } ,
2015-02-02 12:26:27 +01:00
{ " cold " , EC_REBOOT_COLD , 0 } ,
{ " disable-jump " , EC_REBOOT_DISABLE_JUMP , 0 } ,
{ " hibernate " , EC_REBOOT_HIBERNATE , 0 } ,
{ " at-shutdown " , - 1 , EC_REBOOT_FLAG_ON_AP_SHUTDOWN } ,
} ;
2015-06-09 13:04:42 +02:00
struct cros_ec_command * msg ;
struct ec_params_reboot_ec * param ;
2015-02-02 12:26:27 +01:00
int got_cmd = 0 , offset = 0 ;
int i ;
int ret ;
2018-03-23 18:42:43 +01:00
struct cros_ec_dev * ec = to_cros_ec_dev ( dev ) ;
2015-02-02 12:26:27 +01:00
2015-06-09 13:04:42 +02:00
msg = kmalloc ( sizeof ( * msg ) + sizeof ( * param ) , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
param = ( struct ec_params_reboot_ec * ) msg - > data ;
2015-02-02 12:26:27 +01:00
param - > flags = 0 ;
while ( 1 ) {
/* Find word to start scanning */
while ( buf [ offset ] & & isspace ( buf [ offset ] ) )
offset + + ;
if ( ! buf [ offset ] )
break ;
for ( i = 0 ; i < ARRAY_SIZE ( words ) ; i + + ) {
if ( ! strncasecmp ( words [ i ] . str , buf + offset ,
strlen ( words [ i ] . str ) ) ) {
if ( words [ i ] . flags ) {
param - > flags | = words [ i ] . flags ;
} else {
param - > cmd = words [ i ] . cmd ;
got_cmd = 1 ;
}
break ;
}
}
/* On to the next word, if any */
while ( buf [ offset ] & & ! isspace ( buf [ offset ] ) )
offset + + ;
}
2015-06-09 13:04:42 +02:00
if ( ! got_cmd ) {
count = - EINVAL ;
goto exit ;
2015-02-02 12:26:27 +01:00
}
2015-06-09 13:04:42 +02:00
msg - > version = 0 ;
2015-06-09 13:04:47 +02:00
msg - > command = EC_CMD_REBOOT_EC + ec - > cmd_offset ;
2015-06-09 13:04:42 +02:00
msg - > outsize = sizeof ( * param ) ;
msg - > insize = 0 ;
2018-03-23 18:42:42 +01:00
ret = cros_ec_cmd_xfer_status ( ec - > ec_dev , msg ) ;
if ( ret < 0 )
2015-06-09 13:04:42 +02:00
count = ret ;
exit :
kfree ( msg ) ;
2015-02-02 12:26:27 +01:00
return count ;
}
2018-03-23 18:42:44 +01:00
static ssize_t version_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
2015-02-02 12:26:27 +01:00
{
static const char * const image_names [ ] = { " unknown " , " RO " , " RW " } ;
struct ec_response_get_version * r_ver ;
struct ec_response_get_chip_info * r_chip ;
struct ec_response_board_version * r_board ;
2015-06-09 13:04:42 +02:00
struct cros_ec_command * msg ;
2015-02-02 12:26:27 +01:00
int ret ;
int count = 0 ;
2018-03-23 18:42:43 +01:00
struct cros_ec_dev * ec = to_cros_ec_dev ( dev ) ;
2015-02-02 12:26:27 +01:00
2015-06-09 13:04:42 +02:00
msg = kmalloc ( sizeof ( * msg ) + EC_HOST_PARAM_SIZE , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
2015-02-02 12:26:27 +01:00
/* Get versions. RW may change. */
2015-06-09 13:04:42 +02:00
msg - > version = 0 ;
2015-06-09 13:04:47 +02:00
msg - > command = EC_CMD_GET_VERSION + ec - > cmd_offset ;
2015-06-09 13:04:42 +02:00
msg - > insize = sizeof ( * r_ver ) ;
msg - > outsize = 0 ;
2018-03-23 18:42:42 +01:00
ret = cros_ec_cmd_xfer_status ( ec - > ec_dev , msg ) ;
2015-06-09 13:04:42 +02:00
if ( ret < 0 ) {
count = ret ;
goto exit ;
}
r_ver = ( struct ec_response_get_version * ) msg - > data ;
2015-02-02 12:26:27 +01:00
/* Strings should be null-terminated, but let's be sure. */
r_ver - > version_string_ro [ sizeof ( r_ver - > version_string_ro ) - 1 ] = ' \0 ' ;
r_ver - > version_string_rw [ sizeof ( r_ver - > version_string_rw ) - 1 ] = ' \0 ' ;
2022-12-05 14:10:42 +08:00
count + = sysfs_emit_at ( buf , count , " RO version: %s \n " , r_ver - > version_string_ro ) ;
count + = sysfs_emit_at ( buf , count , " RW version: %s \n " , r_ver - > version_string_rw ) ;
count + = sysfs_emit_at ( buf , count , " Firmware copy: %s \n " ,
2015-02-02 12:26:27 +01:00
( r_ver - > current_image < ARRAY_SIZE ( image_names ) ?
image_names [ r_ver - > current_image ] : " ? " ) ) ;
/* Get build info. */
2015-06-09 13:04:47 +02:00
msg - > command = EC_CMD_GET_BUILD_INFO + ec - > cmd_offset ;
2015-06-09 13:04:42 +02:00
msg - > insize = EC_HOST_PARAM_SIZE ;
2020-02-20 16:58:56 +01:00
ret = cros_ec_cmd_xfer_status ( ec - > ec_dev , msg ) ;
2020-08-22 08:08:53 -07:00
if ( ret < 0 ) {
2022-12-05 14:10:42 +08:00
count + = sysfs_emit_at ( buf , count ,
2020-08-22 08:08:53 -07:00
" Build info: XFER / EC ERROR %d / %d \n " ,
ret , msg - > result ) ;
2020-02-20 16:58:56 +01:00
} else {
2017-12-04 15:49:48 +01:00
msg - > data [ EC_HOST_PARAM_SIZE - 1 ] = ' \0 ' ;
2022-12-05 14:10:42 +08:00
count + = sysfs_emit_at ( buf , count , " Build info: %s \n " , msg - > data ) ;
2015-02-02 12:26:27 +01:00
}
/* Get chip info. */
2015-06-09 13:04:47 +02:00
msg - > command = EC_CMD_GET_CHIP_INFO + ec - > cmd_offset ;
2015-06-09 13:04:42 +02:00
msg - > insize = sizeof ( * r_chip ) ;
2020-02-20 16:58:56 +01:00
ret = cros_ec_cmd_xfer_status ( ec - > ec_dev , msg ) ;
2020-08-22 08:08:53 -07:00
if ( ret < 0 ) {
2022-12-05 14:10:42 +08:00
count + = sysfs_emit_at ( buf , count ,
2020-08-22 08:08:53 -07:00
" Chip info: XFER / EC ERROR %d / %d \n " ,
ret , msg - > result ) ;
2020-02-20 16:58:56 +01:00
} else {
2015-06-09 13:04:42 +02:00
r_chip = ( struct ec_response_get_chip_info * ) msg - > data ;
2015-02-02 12:26:27 +01:00
r_chip - > vendor [ sizeof ( r_chip - > vendor ) - 1 ] = ' \0 ' ;
r_chip - > name [ sizeof ( r_chip - > name ) - 1 ] = ' \0 ' ;
r_chip - > revision [ sizeof ( r_chip - > revision ) - 1 ] = ' \0 ' ;
2022-12-05 14:10:42 +08:00
count + = sysfs_emit_at ( buf , count , " Chip vendor: %s \n " , r_chip - > vendor ) ;
count + = sysfs_emit_at ( buf , count , " Chip name: %s \n " , r_chip - > name ) ;
count + = sysfs_emit_at ( buf , count , " Chip revision: %s \n " , r_chip - > revision ) ;
2015-02-02 12:26:27 +01:00
}
/* Get board version */
2015-06-09 13:04:47 +02:00
msg - > command = EC_CMD_GET_BOARD_VERSION + ec - > cmd_offset ;
2015-06-09 13:04:42 +02:00
msg - > insize = sizeof ( * r_board ) ;
2020-02-20 16:58:56 +01:00
ret = cros_ec_cmd_xfer_status ( ec - > ec_dev , msg ) ;
2020-08-22 08:08:53 -07:00
if ( ret < 0 ) {
2022-12-05 14:10:42 +08:00
count + = sysfs_emit_at ( buf , count ,
2020-08-22 08:08:53 -07:00
" Board version: XFER / EC ERROR %d / %d \n " ,
ret , msg - > result ) ;
2020-02-20 16:58:56 +01:00
} else {
2015-06-09 13:04:42 +02:00
r_board = ( struct ec_response_board_version * ) msg - > data ;
2015-02-02 12:26:27 +01:00
2022-12-05 14:10:42 +08:00
count + = sysfs_emit_at ( buf , count ,
2015-02-02 12:26:27 +01:00
" Board version: %d \n " ,
r_board - > board_version ) ;
}
2015-06-09 13:04:42 +02:00
exit :
kfree ( msg ) ;
2015-02-02 12:26:27 +01:00
return count ;
}
2018-03-23 18:42:44 +01:00
static ssize_t flashinfo_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
2015-02-02 12:26:27 +01:00
{
struct ec_response_flash_info * resp ;
2015-06-09 13:04:42 +02:00
struct cros_ec_command * msg ;
2015-02-02 12:26:27 +01:00
int ret ;
2018-03-23 18:42:43 +01:00
struct cros_ec_dev * ec = to_cros_ec_dev ( dev ) ;
2015-02-02 12:26:27 +01:00
2015-06-09 13:04:42 +02:00
msg = kmalloc ( sizeof ( * msg ) + sizeof ( * resp ) , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
2015-02-02 12:26:27 +01:00
/* The flash info shouldn't ever change, but ask each time anyway. */
2015-06-09 13:04:42 +02:00
msg - > version = 0 ;
2015-06-09 13:04:47 +02:00
msg - > command = EC_CMD_FLASH_INFO + ec - > cmd_offset ;
2015-06-09 13:04:42 +02:00
msg - > insize = sizeof ( * resp ) ;
msg - > outsize = 0 ;
2018-03-23 18:42:42 +01:00
ret = cros_ec_cmd_xfer_status ( ec - > ec_dev , msg ) ;
2015-02-02 12:26:27 +01:00
if ( ret < 0 )
2015-06-09 13:04:42 +02:00
goto exit ;
resp = ( struct ec_response_flash_info * ) msg - > data ;
2022-12-02 16:56:04 +08:00
ret = sysfs_emit ( buf ,
2015-06-09 13:04:42 +02:00
" FlashSize %d \n WriteSize %d \n "
" EraseSize %d \n ProtectSize %d \n " ,
resp - > flash_size , resp - > write_block_size ,
resp - > erase_block_size , resp - > protect_block_size ) ;
exit :
kfree ( msg ) ;
return ret ;
2015-02-02 12:26:27 +01:00
}
2018-03-23 18:42:47 +01:00
/* Keyboard wake angle control */
static ssize_t kb_wake_angle_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct cros_ec_dev * ec = to_cros_ec_dev ( dev ) ;
struct ec_response_motion_sense * resp ;
struct ec_params_motion_sense * param ;
struct cros_ec_command * msg ;
int ret ;
msg = kmalloc ( sizeof ( * msg ) + EC_HOST_PARAM_SIZE , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
param = ( struct ec_params_motion_sense * ) msg - > data ;
msg - > command = EC_CMD_MOTION_SENSE_CMD + ec - > cmd_offset ;
msg - > version = 2 ;
param - > cmd = MOTIONSENSE_CMD_KB_WAKE_ANGLE ;
param - > kb_wake_angle . data = EC_MOTION_SENSE_NO_VALUE ;
msg - > outsize = sizeof ( * param ) ;
msg - > insize = sizeof ( * resp ) ;
ret = cros_ec_cmd_xfer_status ( ec - > ec_dev , msg ) ;
if ( ret < 0 )
goto exit ;
resp = ( struct ec_response_motion_sense * ) msg - > data ;
2022-12-02 16:56:04 +08:00
ret = sysfs_emit ( buf , " %d \n " , resp - > kb_wake_angle . ret ) ;
2018-03-23 18:42:47 +01:00
exit :
kfree ( msg ) ;
return ret ;
}
static ssize_t kb_wake_angle_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
struct cros_ec_dev * ec = to_cros_ec_dev ( dev ) ;
struct ec_params_motion_sense * param ;
struct cros_ec_command * msg ;
u16 angle ;
int ret ;
ret = kstrtou16 ( buf , 0 , & angle ) ;
if ( ret )
return ret ;
msg = kmalloc ( sizeof ( * msg ) + EC_HOST_PARAM_SIZE , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
param = ( struct ec_params_motion_sense * ) msg - > data ;
msg - > command = EC_CMD_MOTION_SENSE_CMD + ec - > cmd_offset ;
msg - > version = 2 ;
param - > cmd = MOTIONSENSE_CMD_KB_WAKE_ANGLE ;
param - > kb_wake_angle . data = angle ;
msg - > outsize = sizeof ( * param ) ;
msg - > insize = sizeof ( struct ec_response_motion_sense ) ;
ret = cros_ec_cmd_xfer_status ( ec - > ec_dev , msg ) ;
kfree ( msg ) ;
if ( ret < 0 )
return ret ;
return count ;
}
2015-02-02 12:26:27 +01:00
/* Module initialization */
2018-03-23 18:42:44 +01:00
static DEVICE_ATTR_RW ( reboot ) ;
static DEVICE_ATTR_RO ( version ) ;
static DEVICE_ATTR_RO ( flashinfo ) ;
2018-03-23 18:42:47 +01:00
static DEVICE_ATTR_RW ( kb_wake_angle ) ;
2015-02-02 12:26:27 +01:00
static struct attribute * __ec_attrs [ ] = {
2018-03-23 18:42:47 +01:00
& dev_attr_kb_wake_angle . attr ,
2015-02-02 12:26:27 +01:00
& dev_attr_reboot . attr ,
& dev_attr_version . attr ,
& dev_attr_flashinfo . attr ,
NULL ,
} ;
2018-03-23 18:42:47 +01:00
static umode_t cros_ec_ctrl_visible ( struct kobject * kobj ,
struct attribute * a , int n )
{
2020-09-26 15:03:37 +08:00
struct device * dev = kobj_to_dev ( kobj ) ;
2018-03-23 18:42:47 +01:00
struct cros_ec_dev * ec = to_cros_ec_dev ( dev ) ;
if ( a = = & dev_attr_kb_wake_angle . attr & & ! ec - > has_kb_wake_angle )
return 0 ;
return a - > mode ;
}
2021-01-09 01:17:48 +01:00
static const struct attribute_group cros_ec_attr_group = {
2015-02-02 12:26:27 +01:00
. attrs = __ec_attrs ,
2018-03-23 18:42:47 +01:00
. is_visible = cros_ec_ctrl_visible ,
2015-02-02 12:26:27 +01:00
} ;
2018-12-12 18:34:00 +01:00
static int cros_ec_sysfs_probe ( struct platform_device * pd )
{
struct cros_ec_dev * ec_dev = dev_get_drvdata ( pd - > dev . parent ) ;
struct device * dev = & pd - > dev ;
int ret ;
ret = sysfs_create_group ( & ec_dev - > class_dev . kobj , & cros_ec_attr_group ) ;
if ( ret < 0 )
dev_err ( dev , " failed to create attributes. err=%d \n " , ret ) ;
return ret ;
}
2023-09-27 10:10:18 +02:00
static void cros_ec_sysfs_remove ( struct platform_device * pd )
2018-12-12 18:34:00 +01:00
{
struct cros_ec_dev * ec_dev = dev_get_drvdata ( pd - > dev . parent ) ;
sysfs_remove_group ( & ec_dev - > class_dev . kobj , & cros_ec_attr_group ) ;
}
static struct platform_driver cros_ec_sysfs_driver = {
. driver = {
. name = DRV_NAME ,
} ,
. probe = cros_ec_sysfs_probe ,
2023-09-27 10:10:18 +02:00
. remove_new = cros_ec_sysfs_remove ,
2018-12-12 18:34:00 +01:00
} ;
module_platform_driver ( cros_ec_sysfs_driver ) ;
2015-02-02 12:26:27 +01:00
2017-11-20 17:15:25 +01:00
MODULE_LICENSE ( " GPL " ) ;
2019-01-29 10:38:15 +01:00
MODULE_DESCRIPTION ( " Expose the ChromeOS EC through sysfs " ) ;
2018-12-12 18:34:00 +01:00
MODULE_ALIAS ( " platform: " DRV_NAME ) ;