15f995254SDan Callaghan // SPDX-License-Identifier: GPL-2.0
25f995254SDan Callaghan /*
35f995254SDan Callaghan * Driver for the ChromeOS human presence sensor (HPS), attached via I2C.
45f995254SDan Callaghan *
55f995254SDan Callaghan * The driver exposes HPS as a character device, although currently no read or
65f995254SDan Callaghan * write operations are supported. Instead, the driver only controls the power
75f995254SDan Callaghan * state of the sensor, keeping it on only while userspace holds an open file
85f995254SDan Callaghan * descriptor to the HPS device.
95f995254SDan Callaghan *
105f995254SDan Callaghan * Copyright 2022 Google LLC.
115f995254SDan Callaghan */
125f995254SDan Callaghan
135f995254SDan Callaghan #include <linux/acpi.h>
145f995254SDan Callaghan #include <linux/fs.h>
155f995254SDan Callaghan #include <linux/gpio/consumer.h>
165f995254SDan Callaghan #include <linux/i2c.h>
175f995254SDan Callaghan #include <linux/miscdevice.h>
185f995254SDan Callaghan #include <linux/module.h>
195f995254SDan Callaghan #include <linux/pm_runtime.h>
205f995254SDan Callaghan
215f995254SDan Callaghan #define HPS_ACPI_ID "GOOG0020"
225f995254SDan Callaghan
235f995254SDan Callaghan struct hps_drvdata {
245f995254SDan Callaghan struct i2c_client *client;
255f995254SDan Callaghan struct miscdevice misc_device;
265f995254SDan Callaghan struct gpio_desc *enable_gpio;
275f995254SDan Callaghan };
285f995254SDan Callaghan
hps_set_power(struct hps_drvdata * hps,bool state)295f995254SDan Callaghan static void hps_set_power(struct hps_drvdata *hps, bool state)
305f995254SDan Callaghan {
315f995254SDan Callaghan gpiod_set_value_cansleep(hps->enable_gpio, state);
325f995254SDan Callaghan }
335f995254SDan Callaghan
hps_open(struct inode * inode,struct file * file)345f995254SDan Callaghan static int hps_open(struct inode *inode, struct file *file)
355f995254SDan Callaghan {
365f995254SDan Callaghan struct hps_drvdata *hps = container_of(file->private_data,
375f995254SDan Callaghan struct hps_drvdata, misc_device);
385f995254SDan Callaghan struct device *dev = &hps->client->dev;
395f995254SDan Callaghan
405f995254SDan Callaghan return pm_runtime_resume_and_get(dev);
415f995254SDan Callaghan }
425f995254SDan Callaghan
hps_release(struct inode * inode,struct file * file)435f995254SDan Callaghan static int hps_release(struct inode *inode, struct file *file)
445f995254SDan Callaghan {
455f995254SDan Callaghan struct hps_drvdata *hps = container_of(file->private_data,
465f995254SDan Callaghan struct hps_drvdata, misc_device);
475f995254SDan Callaghan struct device *dev = &hps->client->dev;
485f995254SDan Callaghan
495f995254SDan Callaghan return pm_runtime_put(dev);
505f995254SDan Callaghan }
515f995254SDan Callaghan
525f995254SDan Callaghan static const struct file_operations hps_fops = {
535f995254SDan Callaghan .owner = THIS_MODULE,
545f995254SDan Callaghan .open = hps_open,
555f995254SDan Callaghan .release = hps_release,
565f995254SDan Callaghan };
575f995254SDan Callaghan
hps_i2c_probe(struct i2c_client * client)585f995254SDan Callaghan static int hps_i2c_probe(struct i2c_client *client)
595f995254SDan Callaghan {
605f995254SDan Callaghan struct hps_drvdata *hps;
615f995254SDan Callaghan int ret;
625f995254SDan Callaghan
635f995254SDan Callaghan hps = devm_kzalloc(&client->dev, sizeof(*hps), GFP_KERNEL);
645f995254SDan Callaghan if (!hps)
655f995254SDan Callaghan return -ENOMEM;
665f995254SDan Callaghan
675f995254SDan Callaghan hps->misc_device.parent = &client->dev;
685f995254SDan Callaghan hps->misc_device.minor = MISC_DYNAMIC_MINOR;
695f995254SDan Callaghan hps->misc_device.name = "cros-hps";
705f995254SDan Callaghan hps->misc_device.fops = &hps_fops;
715f995254SDan Callaghan
725f995254SDan Callaghan i2c_set_clientdata(client, hps);
735f995254SDan Callaghan hps->client = client;
745f995254SDan Callaghan
755f995254SDan Callaghan /*
765f995254SDan Callaghan * HPS is powered on from firmware before entering the kernel, so we
775f995254SDan Callaghan * acquire the line with GPIOD_OUT_HIGH here to preserve the existing
785f995254SDan Callaghan * state. The peripheral is powered off after successful probe below.
795f995254SDan Callaghan */
805f995254SDan Callaghan hps->enable_gpio = devm_gpiod_get(&client->dev, "enable", GPIOD_OUT_HIGH);
815f995254SDan Callaghan if (IS_ERR(hps->enable_gpio)) {
825f995254SDan Callaghan ret = PTR_ERR(hps->enable_gpio);
835f995254SDan Callaghan dev_err(&client->dev, "failed to get enable gpio: %d\n", ret);
845f995254SDan Callaghan return ret;
855f995254SDan Callaghan }
865f995254SDan Callaghan
875f995254SDan Callaghan ret = misc_register(&hps->misc_device);
885f995254SDan Callaghan if (ret) {
895f995254SDan Callaghan dev_err(&client->dev, "failed to initialize misc device: %d\n", ret);
905f995254SDan Callaghan return ret;
915f995254SDan Callaghan }
925f995254SDan Callaghan
935f995254SDan Callaghan hps_set_power(hps, false);
945f995254SDan Callaghan pm_runtime_enable(&client->dev);
955f995254SDan Callaghan return 0;
965f995254SDan Callaghan }
975f995254SDan Callaghan
hps_i2c_remove(struct i2c_client * client)98d8cb88f1SDan Callaghan static void hps_i2c_remove(struct i2c_client *client)
995f995254SDan Callaghan {
1005f995254SDan Callaghan struct hps_drvdata *hps = i2c_get_clientdata(client);
1015f995254SDan Callaghan
1025f995254SDan Callaghan pm_runtime_disable(&client->dev);
1035f995254SDan Callaghan misc_deregister(&hps->misc_device);
1045f995254SDan Callaghan
1055f995254SDan Callaghan /*
1065f995254SDan Callaghan * Re-enable HPS, in order to return it to its default state
1075f995254SDan Callaghan * (i.e. powered on).
1085f995254SDan Callaghan */
1095f995254SDan Callaghan hps_set_power(hps, true);
1105f995254SDan Callaghan }
1115f995254SDan Callaghan
hps_suspend(struct device * dev)1125f995254SDan Callaghan static int hps_suspend(struct device *dev)
1135f995254SDan Callaghan {
1145f995254SDan Callaghan struct i2c_client *client = to_i2c_client(dev);
1155f995254SDan Callaghan struct hps_drvdata *hps = i2c_get_clientdata(client);
1165f995254SDan Callaghan
1175f995254SDan Callaghan hps_set_power(hps, false);
1185f995254SDan Callaghan return 0;
1195f995254SDan Callaghan }
1205f995254SDan Callaghan
hps_resume(struct device * dev)1215f995254SDan Callaghan static int hps_resume(struct device *dev)
1225f995254SDan Callaghan {
1235f995254SDan Callaghan struct i2c_client *client = to_i2c_client(dev);
1245f995254SDan Callaghan struct hps_drvdata *hps = i2c_get_clientdata(client);
1255f995254SDan Callaghan
1265f995254SDan Callaghan hps_set_power(hps, true);
1275f995254SDan Callaghan return 0;
1285f995254SDan Callaghan }
1295f995254SDan Callaghan static UNIVERSAL_DEV_PM_OPS(hps_pm_ops, hps_suspend, hps_resume, NULL);
1305f995254SDan Callaghan
1315f995254SDan Callaghan static const struct i2c_device_id hps_i2c_id[] = {
1325f995254SDan Callaghan { "cros-hps", 0 },
1335f995254SDan Callaghan { }
1345f995254SDan Callaghan };
1355f995254SDan Callaghan MODULE_DEVICE_TABLE(i2c, hps_i2c_id);
1365f995254SDan Callaghan
1375f995254SDan Callaghan #ifdef CONFIG_ACPI
1385f995254SDan Callaghan static const struct acpi_device_id hps_acpi_id[] = {
1395f995254SDan Callaghan { HPS_ACPI_ID, 0 },
1405f995254SDan Callaghan { }
1415f995254SDan Callaghan };
1425f995254SDan Callaghan MODULE_DEVICE_TABLE(acpi, hps_acpi_id);
1435f995254SDan Callaghan #endif /* CONFIG_ACPI */
1445f995254SDan Callaghan
1455f995254SDan Callaghan static struct i2c_driver hps_i2c_driver = {
146*f5bb4e38SUwe Kleine-König .probe = hps_i2c_probe,
1475f995254SDan Callaghan .remove = hps_i2c_remove,
1485f995254SDan Callaghan .id_table = hps_i2c_id,
1495f995254SDan Callaghan .driver = {
1505f995254SDan Callaghan .name = "cros-hps",
1515f995254SDan Callaghan .pm = &hps_pm_ops,
1525f995254SDan Callaghan .acpi_match_table = ACPI_PTR(hps_acpi_id),
1535f995254SDan Callaghan },
1545f995254SDan Callaghan };
1555f995254SDan Callaghan module_i2c_driver(hps_i2c_driver);
1565f995254SDan Callaghan
1575f995254SDan Callaghan MODULE_ALIAS("acpi:" HPS_ACPI_ID);
1585f995254SDan Callaghan MODULE_AUTHOR("Sami Kyöstilä <skyostil@chromium.org>");
1595f995254SDan Callaghan MODULE_DESCRIPTION("Driver for ChromeOS HPS");
1605f995254SDan Callaghan MODULE_LICENSE("GPL");
161