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_new = 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