2019-11-19 13:45:45 +01:00
// SPDX-License-Identifier: GPL-2.0
/*
* Sensor HUB driver that discovers sensors behind a ChromeOS Embedded
* Controller .
*
* Copyright 2019 Google LLC
*/
# include <linux/init.h>
# include <linux/device.h>
# include <linux/module.h>
# include <linux/platform_data/cros_ec_commands.h>
# include <linux/platform_data/cros_ec_proto.h>
# include <linux/platform_data/cros_ec_sensorhub.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/types.h>
# define DRV_NAME "cros-ec-sensorhub"
static void cros_ec_sensorhub_free_sensor ( void * arg )
{
struct platform_device * pdev = arg ;
platform_device_unregister ( pdev ) ;
}
static int cros_ec_sensorhub_allocate_sensor ( struct device * parent ,
char * sensor_name ,
int sensor_num )
{
struct cros_ec_sensor_platform sensor_platforms = {
. sensor_num = sensor_num ,
} ;
struct platform_device * pdev ;
pdev = platform_device_register_data ( parent , sensor_name ,
PLATFORM_DEVID_AUTO ,
& sensor_platforms ,
sizeof ( sensor_platforms ) ) ;
if ( IS_ERR ( pdev ) )
return PTR_ERR ( pdev ) ;
return devm_add_action_or_reset ( parent ,
cros_ec_sensorhub_free_sensor ,
pdev ) ;
}
static int cros_ec_sensorhub_register ( struct device * dev ,
struct cros_ec_sensorhub * sensorhub )
{
int sensor_type [ MOTIONSENSE_TYPE_MAX ] = { 0 } ;
2020-03-27 15:34:33 -07:00
struct cros_ec_command * msg = sensorhub - > msg ;
2019-11-19 13:45:45 +01:00
struct cros_ec_dev * ec = sensorhub - > ec ;
2020-04-27 15:59:02 -07:00
int ret , i ;
2019-11-19 13:45:45 +01:00
char * name ;
msg - > version = 1 ;
2020-03-27 15:34:33 -07:00
msg - > insize = sizeof ( struct ec_response_motion_sense ) ;
msg - > outsize = sizeof ( struct ec_params_motion_sense ) ;
2019-11-19 13:45:45 +01:00
2020-04-27 15:59:02 -07:00
for ( i = 0 ; i < sensorhub - > sensor_num ; i + + ) {
2020-03-27 15:34:33 -07:00
sensorhub - > params - > cmd = MOTIONSENSE_CMD_INFO ;
sensorhub - > params - > info . sensor_num = i ;
2019-11-19 13:45:45 +01:00
ret = cros_ec_cmd_xfer_status ( ec - > ec_dev , msg ) ;
if ( ret < 0 ) {
dev_warn ( dev , " no info for EC sensor %d : %d/%d \n " ,
i , ret , msg - > result ) ;
continue ;
}
2020-03-27 15:34:33 -07:00
switch ( sensorhub - > resp - > info . type ) {
2019-11-19 13:45:45 +01:00
case MOTIONSENSE_TYPE_ACCEL :
name = " cros-ec-accel " ;
break ;
case MOTIONSENSE_TYPE_BARO :
name = " cros-ec-baro " ;
break ;
case MOTIONSENSE_TYPE_GYRO :
name = " cros-ec-gyro " ;
break ;
case MOTIONSENSE_TYPE_MAG :
name = " cros-ec-mag " ;
break ;
case MOTIONSENSE_TYPE_PROX :
name = " cros-ec-prox " ;
break ;
case MOTIONSENSE_TYPE_LIGHT :
name = " cros-ec-light " ;
break ;
case MOTIONSENSE_TYPE_ACTIVITY :
name = " cros-ec-activity " ;
break ;
default :
2020-03-27 15:34:33 -07:00
dev_warn ( dev , " unknown type %d \n " ,
sensorhub - > resp - > info . type ) ;
2019-11-19 13:45:45 +01:00
continue ;
}
ret = cros_ec_sensorhub_allocate_sensor ( dev , name , i ) ;
if ( ret )
2020-03-27 15:34:33 -07:00
return ret ;
2019-11-19 13:45:45 +01:00
2020-03-27 15:34:33 -07:00
sensor_type [ sensorhub - > resp - > info . type ] + + ;
2019-11-19 13:45:45 +01:00
}
if ( sensor_type [ MOTIONSENSE_TYPE_ACCEL ] > = 2 )
ec - > has_kb_wake_angle = true ;
if ( cros_ec_check_features ( ec ,
EC_FEATURE_REFINED_TABLET_MODE_HYSTERESIS ) ) {
ret = cros_ec_sensorhub_allocate_sensor ( dev ,
" cros-ec-lid-angle " ,
0 ) ;
if ( ret )
2020-03-27 15:34:33 -07:00
return ret ;
2019-11-19 13:45:45 +01:00
}
return 0 ;
}
static int cros_ec_sensorhub_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
2020-03-27 15:34:33 -07:00
struct cros_ec_dev * ec = dev_get_drvdata ( dev - > parent ) ;
2019-11-19 13:45:45 +01:00
struct cros_ec_sensorhub * data ;
2020-03-27 15:34:33 -07:00
struct cros_ec_command * msg ;
2020-04-27 15:59:02 -07:00
int ret , i , sensor_num ;
2019-11-19 13:45:45 +01:00
2020-03-27 15:34:33 -07:00
msg = devm_kzalloc ( dev , sizeof ( struct cros_ec_command ) +
max ( ( u16 ) sizeof ( struct ec_params_motion_sense ) ,
ec - > ec_dev - > max_response ) , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
msg - > command = EC_CMD_MOTION_SENSE_CMD + ec - > cmd_offset ;
2019-11-19 13:45:45 +01:00
data = devm_kzalloc ( dev , sizeof ( struct cros_ec_sensorhub ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
2020-03-27 15:34:33 -07:00
mutex_init ( & data - > cmd_lock ) ;
data - > dev = dev ;
data - > ec = ec ;
data - > msg = msg ;
data - > params = ( struct ec_params_motion_sense * ) msg - > data ;
data - > resp = ( struct ec_response_motion_sense * ) msg - > data ;
2019-11-19 13:45:45 +01:00
dev_set_drvdata ( dev , data ) ;
/* Check whether this EC is a sensor hub. */
2020-04-27 15:59:02 -07:00
if ( cros_ec_check_features ( ec , EC_FEATURE_MOTION_SENSE ) ) {
sensor_num = cros_ec_get_sensor_count ( ec ) ;
if ( sensor_num < 0 ) {
dev_err ( dev ,
" Unable to retrieve sensor information (err:%d) \n " ,
sensor_num ) ;
return sensor_num ;
}
if ( sensor_num = = 0 ) {
dev_err ( dev , " Zero sensors reported. \n " ) ;
return - EINVAL ;
}
data - > sensor_num = sensor_num ;
/*
* Prepare the ring handler before enumering the
* sensors .
*/
if ( cros_ec_check_features ( ec , EC_FEATURE_MOTION_SENSE_FIFO ) ) {
ret = cros_ec_sensorhub_ring_allocate ( data ) ;
if ( ret )
return ret ;
}
/* Enumerate the sensors.*/
2019-11-19 13:45:45 +01:00
ret = cros_ec_sensorhub_register ( dev , data ) ;
if ( ret )
return ret ;
2020-04-27 15:59:02 -07:00
/*
* When the EC does not have a FIFO , the sensors will query
* their data themselves via sysfs or a software trigger .
*/
if ( cros_ec_check_features ( ec , EC_FEATURE_MOTION_SENSE_FIFO ) ) {
ret = cros_ec_sensorhub_ring_add ( data ) ;
if ( ret )
return ret ;
/*
* The msg and its data is not under the control of the
* ring handler .
*/
return devm_add_action_or_reset ( dev ,
cros_ec_sensorhub_ring_remove ,
data ) ;
}
2019-11-19 13:45:45 +01:00
} else {
/*
* If the device has sensors but does not claim to
* be a sensor hub , we are in legacy mode .
*/
2020-03-27 15:34:32 -07:00
data - > sensor_num = 2 ;
for ( i = 0 ; i < data - > sensor_num ; i + + ) {
2019-11-19 13:45:45 +01:00
ret = cros_ec_sensorhub_allocate_sensor ( dev ,
" cros-ec-accel-legacy " , i ) ;
if ( ret )
return ret ;
}
}
2020-03-27 15:34:33 -07:00
return 0 ;
}
# ifdef CONFIG_PM_SLEEP
/*
* When the EC is suspending , we must stop sending interrupt ,
* we may use the same interrupt line for waking up the device .
* Tell the EC to stop sending non - interrupt event on the iio ring .
*/
static int cros_ec_sensorhub_suspend ( struct device * dev )
{
2021-09-20 11:05:20 +02:00
struct cros_ec_sensorhub * sensorhub = dev_get_drvdata ( dev ) ;
2020-03-27 15:34:33 -07:00
struct cros_ec_dev * ec = sensorhub - > ec ;
if ( cros_ec_check_features ( ec , EC_FEATURE_MOTION_SENSE_FIFO ) )
return cros_ec_sensorhub_ring_fifo_enable ( sensorhub , false ) ;
2019-11-19 13:45:45 +01:00
return 0 ;
}
2020-03-27 15:34:33 -07:00
static int cros_ec_sensorhub_resume ( struct device * dev )
{
2021-09-20 11:05:20 +02:00
struct cros_ec_sensorhub * sensorhub = dev_get_drvdata ( dev ) ;
2020-03-27 15:34:33 -07:00
struct cros_ec_dev * ec = sensorhub - > ec ;
if ( cros_ec_check_features ( ec , EC_FEATURE_MOTION_SENSE_FIFO ) )
return cros_ec_sensorhub_ring_fifo_enable ( sensorhub , true ) ;
return 0 ;
}
# endif
static SIMPLE_DEV_PM_OPS ( cros_ec_sensorhub_pm_ops ,
cros_ec_sensorhub_suspend ,
cros_ec_sensorhub_resume ) ;
2019-11-19 13:45:45 +01:00
static struct platform_driver cros_ec_sensorhub_driver = {
. driver = {
. name = DRV_NAME ,
2020-03-27 15:34:33 -07:00
. pm = & cros_ec_sensorhub_pm_ops ,
2019-11-19 13:45:45 +01:00
} ,
. probe = cros_ec_sensorhub_probe ,
} ;
module_platform_driver ( cros_ec_sensorhub_driver ) ;
MODULE_ALIAS ( " platform: " DRV_NAME ) ;
MODULE_AUTHOR ( " Gwendal Grignou <gwendal@chromium.org> " ) ;
MODULE_DESCRIPTION ( " ChromeOS EC MEMS Sensor Hub Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;