2022-10-18 15:06:23 +11:00
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for the ChromeOS human presence sensor ( HPS ) , attached via I2C .
*
* The driver exposes HPS as a character device , although currently no read or
* write operations are supported . Instead , the driver only controls the power
* state of the sensor , keeping it on only while userspace holds an open file
* descriptor to the HPS device .
*
* Copyright 2022 Google LLC .
*/
# include <linux/acpi.h>
# include <linux/fs.h>
# include <linux/gpio/consumer.h>
# include <linux/i2c.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <linux/pm_runtime.h>
# define HPS_ACPI_ID "GOOG0020"
struct hps_drvdata {
struct i2c_client * client ;
struct miscdevice misc_device ;
struct gpio_desc * enable_gpio ;
} ;
static void hps_set_power ( struct hps_drvdata * hps , bool state )
{
gpiod_set_value_cansleep ( hps - > enable_gpio , state ) ;
}
static int hps_open ( struct inode * inode , struct file * file )
{
struct hps_drvdata * hps = container_of ( file - > private_data ,
struct hps_drvdata , misc_device ) ;
struct device * dev = & hps - > client - > dev ;
return pm_runtime_resume_and_get ( dev ) ;
}
static int hps_release ( struct inode * inode , struct file * file )
{
struct hps_drvdata * hps = container_of ( file - > private_data ,
struct hps_drvdata , misc_device ) ;
struct device * dev = & hps - > client - > dev ;
return pm_runtime_put ( dev ) ;
}
static const struct file_operations hps_fops = {
. owner = THIS_MODULE ,
. open = hps_open ,
. release = hps_release ,
} ;
static int hps_i2c_probe ( struct i2c_client * client )
{
struct hps_drvdata * hps ;
int ret ;
hps = devm_kzalloc ( & client - > dev , sizeof ( * hps ) , GFP_KERNEL ) ;
if ( ! hps )
return - ENOMEM ;
hps - > misc_device . parent = & client - > dev ;
hps - > misc_device . minor = MISC_DYNAMIC_MINOR ;
hps - > misc_device . name = " cros-hps " ;
hps - > misc_device . fops = & hps_fops ;
i2c_set_clientdata ( client , hps ) ;
hps - > client = client ;
/*
* HPS is powered on from firmware before entering the kernel , so we
* acquire the line with GPIOD_OUT_HIGH here to preserve the existing
* state . The peripheral is powered off after successful probe below .
*/
hps - > enable_gpio = devm_gpiod_get ( & client - > dev , " enable " , GPIOD_OUT_HIGH ) ;
if ( IS_ERR ( hps - > enable_gpio ) ) {
ret = PTR_ERR ( hps - > enable_gpio ) ;
dev_err ( & client - > dev , " failed to get enable gpio: %d \n " , ret ) ;
return ret ;
}
ret = misc_register ( & hps - > misc_device ) ;
if ( ret ) {
dev_err ( & client - > dev , " failed to initialize misc device: %d \n " , ret ) ;
return ret ;
}
hps_set_power ( hps , false ) ;
pm_runtime_enable ( & client - > dev ) ;
return 0 ;
}
2022-10-19 10:52:37 +11:00
static void hps_i2c_remove ( struct i2c_client * client )
2022-10-18 15:06:23 +11:00
{
struct hps_drvdata * hps = i2c_get_clientdata ( client ) ;
pm_runtime_disable ( & client - > dev ) ;
misc_deregister ( & hps - > misc_device ) ;
/*
* Re - enable HPS , in order to return it to its default state
* ( i . e . powered on ) .
*/
hps_set_power ( hps , true ) ;
}
static int hps_suspend ( struct device * dev )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct hps_drvdata * hps = i2c_get_clientdata ( client ) ;
hps_set_power ( hps , false ) ;
return 0 ;
}
static int hps_resume ( struct device * dev )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct hps_drvdata * hps = i2c_get_clientdata ( client ) ;
hps_set_power ( hps , true ) ;
return 0 ;
}
static UNIVERSAL_DEV_PM_OPS ( hps_pm_ops , hps_suspend , hps_resume , NULL ) ;
static const struct i2c_device_id hps_i2c_id [ ] = {
{ " cros-hps " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , hps_i2c_id ) ;
# ifdef CONFIG_ACPI
static const struct acpi_device_id hps_acpi_id [ ] = {
{ HPS_ACPI_ID , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( acpi , hps_acpi_id ) ;
# endif /* CONFIG_ACPI */
static struct i2c_driver hps_i2c_driver = {
. probe_new = hps_i2c_probe ,
. remove = hps_i2c_remove ,
. id_table = hps_i2c_id ,
. driver = {
. name = " cros-hps " ,
. pm = & hps_pm_ops ,
. acpi_match_table = ACPI_PTR ( hps_acpi_id ) ,
} ,
} ;
module_i2c_driver ( hps_i2c_driver ) ;
MODULE_ALIAS ( " acpi: " HPS_ACPI_ID ) ;
MODULE_AUTHOR ( " Sami Kyöstilä <skyostil@chromium.org> " ) ;
MODULE_DESCRIPTION ( " Driver for ChromeOS HPS " ) ;
MODULE_LICENSE ( " GPL " ) ;