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 
23 struct hps_drvdata {
24 	struct i2c_client *client;
25 	struct miscdevice misc_device;
26 	struct gpio_desc *enable_gpio;
27 };
28 
29 static void hps_set_power(struct hps_drvdata *hps, bool state)
30 {
31 	gpiod_set_value_cansleep(hps->enable_gpio, state);
32 }
33 
34 static 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 
43 static 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 
52 static const struct file_operations hps_fops = {
53 	.owner = THIS_MODULE,
54 	.open = hps_open,
55 	.release = hps_release,
56 };
57 
58 static 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 
98 static 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 
112 static 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 
121 static 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 }
129 static UNIVERSAL_DEV_PM_OPS(hps_pm_ops, hps_suspend, hps_resume, NULL);
130 
131 static const struct i2c_device_id hps_i2c_id[] = {
132 	{ "cros-hps", 0 },
133 	{ }
134 };
135 MODULE_DEVICE_TABLE(i2c, hps_i2c_id);
136 
137 #ifdef CONFIG_ACPI
138 static const struct acpi_device_id hps_acpi_id[] = {
139 	{ HPS_ACPI_ID, 0 },
140 	{ }
141 };
142 MODULE_DEVICE_TABLE(acpi, hps_acpi_id);
143 #endif /* CONFIG_ACPI */
144 
145 static 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 };
155 module_i2c_driver(hps_i2c_driver);
156 
157 MODULE_ALIAS("acpi:" HPS_ACPI_ID);
158 MODULE_AUTHOR("Sami Kyöstilä <skyostil@chromium.org>");
159 MODULE_DESCRIPTION("Driver for ChromeOS HPS");
160 MODULE_LICENSE("GPL");
161