Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Driver for the ChromeOS human presence sensor (HPS), attached via I2C.
  4 *
  5 * The driver exposes HPS as a character device, although currently no read or
  6 * write operations are supported. Instead, the driver only controls the power
  7 * state of the sensor, keeping it on only while userspace holds an open file
  8 * descriptor to the HPS device.
  9 *
 10 * Copyright 2022 Google LLC.
 11 */
 12
 13#include <linux/acpi.h>
 14#include <linux/fs.h>
 15#include <linux/gpio/consumer.h>
 16#include <linux/i2c.h>
 17#include <linux/miscdevice.h>
 18#include <linux/module.h>
 19#include <linux/pm_runtime.h>
 20
 21#define HPS_ACPI_ID		"GOOG0020"
 22
 23struct hps_drvdata {
 24	struct i2c_client *client;
 25	struct miscdevice misc_device;
 26	struct gpio_desc *enable_gpio;
 27};
 28
 29static void hps_set_power(struct hps_drvdata *hps, bool state)
 30{
 31	gpiod_set_value_cansleep(hps->enable_gpio, state);
 32}
 33
 34static int hps_open(struct inode *inode, struct file *file)
 35{
 36	struct hps_drvdata *hps = container_of(file->private_data,
 37					       struct hps_drvdata, misc_device);
 38	struct device *dev = &hps->client->dev;
 39
 40	return pm_runtime_resume_and_get(dev);
 41}
 42
 43static int hps_release(struct inode *inode, struct file *file)
 44{
 45	struct hps_drvdata *hps = container_of(file->private_data,
 46					       struct hps_drvdata, misc_device);
 47	struct device *dev = &hps->client->dev;
 48
 49	return pm_runtime_put(dev);
 50}
 51
 52static const struct file_operations hps_fops = {
 53	.owner = THIS_MODULE,
 54	.open = hps_open,
 55	.release = hps_release,
 56};
 57
 58static int hps_i2c_probe(struct i2c_client *client)
 59{
 60	struct hps_drvdata *hps;
 61	int ret;
 62
 63	hps = devm_kzalloc(&client->dev, sizeof(*hps), GFP_KERNEL);
 64	if (!hps)
 65		return -ENOMEM;
 66
 67	hps->misc_device.parent = &client->dev;
 68	hps->misc_device.minor = MISC_DYNAMIC_MINOR;
 69	hps->misc_device.name = "cros-hps";
 70	hps->misc_device.fops = &hps_fops;
 71
 72	i2c_set_clientdata(client, hps);
 73	hps->client = client;
 74
 75	/*
 76	 * HPS is powered on from firmware before entering the kernel, so we
 77	 * acquire the line with GPIOD_OUT_HIGH here to preserve the existing
 78	 * state. The peripheral is powered off after successful probe below.
 79	 */
 80	hps->enable_gpio = devm_gpiod_get(&client->dev, "enable", GPIOD_OUT_HIGH);
 81	if (IS_ERR(hps->enable_gpio)) {
 82		ret = PTR_ERR(hps->enable_gpio);
 83		dev_err(&client->dev, "failed to get enable gpio: %d\n", ret);
 84		return ret;
 85	}
 86
 87	ret = misc_register(&hps->misc_device);
 88	if (ret) {
 89		dev_err(&client->dev, "failed to initialize misc device: %d\n", ret);
 90		return ret;
 91	}
 92
 93	hps_set_power(hps, false);
 94	pm_runtime_enable(&client->dev);
 95	return 0;
 96}
 97
 98static void hps_i2c_remove(struct i2c_client *client)
 99{
100	struct hps_drvdata *hps = i2c_get_clientdata(client);
101
102	pm_runtime_disable(&client->dev);
103	misc_deregister(&hps->misc_device);
104
105	/*
106	 * Re-enable HPS, in order to return it to its default state
107	 * (i.e. powered on).
108	 */
109	hps_set_power(hps, true);
110}
111
112static int hps_suspend(struct device *dev)
113{
114	struct i2c_client *client = to_i2c_client(dev);
115	struct hps_drvdata *hps = i2c_get_clientdata(client);
116
117	hps_set_power(hps, false);
118	return 0;
119}
120
121static int hps_resume(struct device *dev)
122{
123	struct i2c_client *client = to_i2c_client(dev);
124	struct hps_drvdata *hps = i2c_get_clientdata(client);
125
126	hps_set_power(hps, true);
127	return 0;
128}
129static DEFINE_RUNTIME_DEV_PM_OPS(hps_pm_ops, hps_suspend, hps_resume, NULL);
130
131static const struct i2c_device_id hps_i2c_id[] = {
132	{ "cros-hps" },
133	{ }
134};
135MODULE_DEVICE_TABLE(i2c, hps_i2c_id);
136
137#ifdef CONFIG_ACPI
138static const struct acpi_device_id hps_acpi_id[] = {
139	{ HPS_ACPI_ID, 0 },
140	{ }
141};
142MODULE_DEVICE_TABLE(acpi, hps_acpi_id);
143#endif /* CONFIG_ACPI */
144
145static struct i2c_driver hps_i2c_driver = {
146	.probe = hps_i2c_probe,
147	.remove = hps_i2c_remove,
148	.id_table = hps_i2c_id,
149	.driver = {
150		.name = "cros-hps",
151		.pm = pm_ptr(&hps_pm_ops),
152		.acpi_match_table = ACPI_PTR(hps_acpi_id),
153	},
154};
155module_i2c_driver(hps_i2c_driver);
156
157MODULE_ALIAS("acpi:" HPS_ACPI_ID);
158MODULE_AUTHOR("Sami Kyöstilä <skyostil@chromium.org>");
159MODULE_DESCRIPTION("Driver for ChromeOS HPS");
160MODULE_LICENSE("GPL");