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