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(desc: hps->enable_gpio, value: 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(dev: &client->dev, size: 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, data: 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(dev: &client->dev, con_id: "enable", flags: GPIOD_OUT_HIGH);
81 if (IS_ERR(ptr: hps->enable_gpio)) {
82 ret = PTR_ERR(ptr: 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(misc: &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, state: false);
94 pm_runtime_enable(dev: &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(dev: &client->dev);
103 misc_deregister(misc: &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, state: 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, state: 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, state: true);
127 return 0;
128}
129static UNIVERSAL_DEV_PM_OPS(hps_pm_ops, hps_suspend, hps_resume, NULL);
130
131static const struct i2c_device_id hps_i2c_id[] = {
132 { "cros-hps", 0 },
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 = &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");
161

source code of linux/drivers/platform/chrome/cros_hps_i2c.c