1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * dell-smo8800.c - Dell Latitude ACPI SMO88XX freefall sensor driver 4 * 5 * Copyright (C) 2012 Sonal Santan <sonal.santan@gmail.com> 6 * Copyright (C) 2014 Pali Rohár <pali@kernel.org> 7 * 8 * This is loosely based on lis3lv02d driver. 9 */ 10 11 #define DRIVER_NAME "smo8800" 12 13 #include <linux/kernel.h> 14 #include <linux/module.h> 15 #include <linux/acpi.h> 16 #include <linux/interrupt.h> 17 #include <linux/miscdevice.h> 18 #include <linux/uaccess.h> 19 #include <linux/fs.h> 20 21 struct smo8800_device { 22 u32 irq; /* acpi device irq */ 23 atomic_t counter; /* count after last read */ 24 struct miscdevice miscdev; /* for /dev/freefall */ 25 unsigned long misc_opened; /* whether the device is open */ 26 wait_queue_head_t misc_wait; /* Wait queue for the misc dev */ 27 struct device *dev; /* acpi device */ 28 }; 29 30 static irqreturn_t smo8800_interrupt_quick(int irq, void *data) 31 { 32 struct smo8800_device *smo8800 = data; 33 34 atomic_inc(&smo8800->counter); 35 wake_up_interruptible(&smo8800->misc_wait); 36 return IRQ_WAKE_THREAD; 37 } 38 39 static irqreturn_t smo8800_interrupt_thread(int irq, void *data) 40 { 41 struct smo8800_device *smo8800 = data; 42 43 dev_info(smo8800->dev, "detected free fall\n"); 44 return IRQ_HANDLED; 45 } 46 47 static acpi_status smo8800_get_resource(struct acpi_resource *resource, 48 void *context) 49 { 50 struct acpi_resource_extended_irq *irq; 51 52 if (resource->type != ACPI_RESOURCE_TYPE_EXTENDED_IRQ) 53 return AE_OK; 54 55 irq = &resource->data.extended_irq; 56 if (!irq || !irq->interrupt_count) 57 return AE_OK; 58 59 *((u32 *)context) = irq->interrupts[0]; 60 return AE_CTRL_TERMINATE; 61 } 62 63 static u32 smo8800_get_irq(struct acpi_device *device) 64 { 65 u32 irq = 0; 66 acpi_status status; 67 68 status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, 69 smo8800_get_resource, &irq); 70 if (ACPI_FAILURE(status)) { 71 dev_err(&device->dev, "acpi_walk_resources failed\n"); 72 return 0; 73 } 74 75 return irq; 76 } 77 78 static ssize_t smo8800_misc_read(struct file *file, char __user *buf, 79 size_t count, loff_t *pos) 80 { 81 struct smo8800_device *smo8800 = container_of(file->private_data, 82 struct smo8800_device, miscdev); 83 84 u32 data = 0; 85 unsigned char byte_data; 86 ssize_t retval = 1; 87 88 if (count < 1) 89 return -EINVAL; 90 91 atomic_set(&smo8800->counter, 0); 92 retval = wait_event_interruptible(smo8800->misc_wait, 93 (data = atomic_xchg(&smo8800->counter, 0))); 94 95 if (retval) 96 return retval; 97 98 retval = 1; 99 100 if (data < 255) 101 byte_data = data; 102 else 103 byte_data = 255; 104 105 if (put_user(byte_data, buf)) 106 retval = -EFAULT; 107 108 return retval; 109 } 110 111 static int smo8800_misc_open(struct inode *inode, struct file *file) 112 { 113 struct smo8800_device *smo8800 = container_of(file->private_data, 114 struct smo8800_device, miscdev); 115 116 if (test_and_set_bit(0, &smo8800->misc_opened)) 117 return -EBUSY; /* already open */ 118 119 atomic_set(&smo8800->counter, 0); 120 return 0; 121 } 122 123 static int smo8800_misc_release(struct inode *inode, struct file *file) 124 { 125 struct smo8800_device *smo8800 = container_of(file->private_data, 126 struct smo8800_device, miscdev); 127 128 clear_bit(0, &smo8800->misc_opened); /* release the device */ 129 return 0; 130 } 131 132 static const struct file_operations smo8800_misc_fops = { 133 .owner = THIS_MODULE, 134 .read = smo8800_misc_read, 135 .open = smo8800_misc_open, 136 .release = smo8800_misc_release, 137 }; 138 139 static int smo8800_add(struct acpi_device *device) 140 { 141 int err; 142 struct smo8800_device *smo8800; 143 144 smo8800 = devm_kzalloc(&device->dev, sizeof(*smo8800), GFP_KERNEL); 145 if (!smo8800) { 146 dev_err(&device->dev, "failed to allocate device data\n"); 147 return -ENOMEM; 148 } 149 150 smo8800->dev = &device->dev; 151 smo8800->miscdev.minor = MISC_DYNAMIC_MINOR; 152 smo8800->miscdev.name = "freefall"; 153 smo8800->miscdev.fops = &smo8800_misc_fops; 154 155 init_waitqueue_head(&smo8800->misc_wait); 156 157 err = misc_register(&smo8800->miscdev); 158 if (err) { 159 dev_err(&device->dev, "failed to register misc dev: %d\n", err); 160 return err; 161 } 162 163 device->driver_data = smo8800; 164 165 smo8800->irq = smo8800_get_irq(device); 166 if (!smo8800->irq) { 167 dev_err(&device->dev, "failed to obtain IRQ\n"); 168 err = -EINVAL; 169 goto error; 170 } 171 172 err = request_threaded_irq(smo8800->irq, smo8800_interrupt_quick, 173 smo8800_interrupt_thread, 174 IRQF_TRIGGER_RISING | IRQF_ONESHOT, 175 DRIVER_NAME, smo8800); 176 if (err) { 177 dev_err(&device->dev, 178 "failed to request thread for IRQ %d: %d\n", 179 smo8800->irq, err); 180 goto error; 181 } 182 183 dev_dbg(&device->dev, "device /dev/freefall registered with IRQ %d\n", 184 smo8800->irq); 185 return 0; 186 187 error: 188 misc_deregister(&smo8800->miscdev); 189 return err; 190 } 191 192 static int smo8800_remove(struct acpi_device *device) 193 { 194 struct smo8800_device *smo8800 = device->driver_data; 195 196 free_irq(smo8800->irq, smo8800); 197 misc_deregister(&smo8800->miscdev); 198 dev_dbg(&device->dev, "device /dev/freefall unregistered\n"); 199 return 0; 200 } 201 202 /* NOTE: Keep this list in sync with drivers/i2c/busses/i2c-i801.c */ 203 static const struct acpi_device_id smo8800_ids[] = { 204 { "SMO8800", 0 }, 205 { "SMO8801", 0 }, 206 { "SMO8810", 0 }, 207 { "SMO8811", 0 }, 208 { "SMO8820", 0 }, 209 { "SMO8821", 0 }, 210 { "SMO8830", 0 }, 211 { "SMO8831", 0 }, 212 { "", 0 }, 213 }; 214 215 MODULE_DEVICE_TABLE(acpi, smo8800_ids); 216 217 static struct acpi_driver smo8800_driver = { 218 .name = DRIVER_NAME, 219 .class = "Latitude", 220 .ids = smo8800_ids, 221 .ops = { 222 .add = smo8800_add, 223 .remove = smo8800_remove, 224 }, 225 .owner = THIS_MODULE, 226 }; 227 228 module_acpi_driver(smo8800_driver); 229 230 MODULE_DESCRIPTION("Dell Latitude freefall driver (ACPI SMO88XX)"); 231 MODULE_LICENSE("GPL"); 232 MODULE_AUTHOR("Sonal Santan, Pali Rohár"); 233