platform/chrome: add a driver for HPS

JIRA: https://issues.redhat.com/browse/RHEL-33550
Omitted-fix: d8cb88f1541fdc3602dbc87ede78ec704c11546f
	upstream ed5c2f5fd10d ("i2c: Make remove callback return void")
changed the return type of remove to void in between the
submission and acceptance of this driver, and this commit
changes the remove return to match. But ed5c2f5fd10d wasn't
backported to RHEL-9 and the fix is not necessary.

commit 5f9952548d91263eaf70a2ca71f8897c2a638cf1
Author: Dan Callaghan <dcallagh@chromium.org>
Date: Tue, 18 Oct 2022 12:53:15 +0000

This patch introduces a driver for the ChromeOS human presence
sensor (aka. HPS). The driver supports a sensor connected to the I2C bus
and identified as "GOOG0020" in the ACPI tables.

When loaded, the driver exports the sensor to userspace through a
character device. This device only supports power management, i.e.,
communication with the sensor must be done through regular I2C
transmissions from userspace.

Power management is implemented by enabling the respective power GPIO
while at least one userspace process holds an open fd on the character
device. By default, the device is powered down if there are no active
clients.

Note that the driver makes no effort to preserve the state of the sensor
between power down and power up events. Userspace is responsible for
reinitializing any needed state once power has been restored.

The device firmware, I2C protocol and other documentation is available
at https://chromium.googlesource.com/chromiumos/platform/hps-firmware.

Co-developed-by: Sami Kyöstilä <skyostil@chromium.org>
Signed-off-by: Sami Kyöstilä <skyostil@chromium.org>
Signed-off-by: Dan Callaghan <dcallagh@chromium.org>
Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
Link: https://lore.kernel.org/r/20221018040623.2173441-1-dcallagh@chromium.org
Signed-off-by: Mark Langsdorf <mlangsdo@redhat.com>
This commit is contained in:
Mark Langsdorf 2024-04-19 12:24:55 -05:00
parent 2c30c7310b
commit 921c6d94b8
4 changed files with 179 additions and 0 deletions

View File

@ -4516,6 +4516,12 @@ F: drivers/power/supply/cros_usbpd-charger.c
N: cros_ec
N: cros-ec
CHROMEOS HPS DRIVER
M: Dan Callaghan <dcallagh@chromium.org>
R: Sami Kyöstilä <skyostil@chromium.org>
S: Maintained
F: drivers/platform/chrome/cros_hps_i2c.c
CHRONTEL CH7322 CEC DRIVER
M: Jeff Chase <jnchase@google.com>
L: linux-media@vger.kernel.org

View File

@ -228,6 +228,16 @@ config CROS_EC_TYPEC
To compile this driver as a module, choose M here: the module will be
called cros_ec_typec.
config CROS_HPS_I2C
tristate "ChromeOS HPS device"
depends on HID && I2C && PM
help
Say Y here if you want to enable support for the ChromeOS
human presence sensor (HPS), attached via I2C. The driver supports a
sensor connected to the I2C bus and exposes it as a character device.
To save power, the sensor is automatically powered down when no
clients are accessing it.
config CROS_USBPD_LOGGER
tristate "Logging driver for USB PD charger"
depends on CHARGER_CROS_USBPD

View File

@ -26,6 +26,7 @@ obj-$(CONFIG_CROS_EC_DEBUGFS) += cros_ec_debugfs.o
cros-ec-sensorhub-objs := cros_ec_sensorhub.o cros_ec_sensorhub_ring.o
obj-$(CONFIG_CROS_EC_SENSORHUB) += cros-ec-sensorhub.o
obj-$(CONFIG_CROS_EC_SYSFS) += cros_ec_sysfs.o
obj-$(CONFIG_CROS_HPS_I2C) += cros_hps_i2c.o
obj-$(CONFIG_CROS_USBPD_LOGGER) += cros_usbpd_logger.o
obj-$(CONFIG_CROS_USBPD_NOTIFY) += cros_usbpd_notify.o

View File

@ -0,0 +1,162 @@
// 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 int 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);
return 0;
}
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");