d8cb88f154
Commited5c2f5fd1
("i2c: Make remove callback return void") changed the return type of the 'remove' callback to void, but this driver was originally written before that change landed. Update the remove callback to match. Fixes:5f9952548d
("platform/chrome: add a driver for HPS") Reported-by: kernel test robot <lkp@intel.com> Signed-off-by: Dan Callaghan <dcallagh@chromium.org> Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org> Link: https://lore.kernel.org/r/20221018235237.2274969-1-dcallagh@chromium.org
161 lines
4.0 KiB
C
161 lines
4.0 KiB
C
// 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;
|
|
}
|
|
|
|
static void hps_i2c_remove(struct i2c_client *client)
|
|
{
|
|
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");
|