linux/drivers/macintosh/ams/ams-core.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Apple Motion Sensor driver
   4 *
   5 * Copyright (C) 2005 Stelian Pop (stelian@popies.net)
   6 * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
   7 */
   8
   9#include <linux/module.h>
  10#include <linux/types.h>
  11#include <linux/errno.h>
  12#include <linux/init.h>
  13#include <linux/of_platform.h>
  14#include <asm/pmac_pfunc.h>
  15
  16#include "ams.h"
  17
  18/* There is only one motion sensor per machine */
  19struct ams ams_info;
  20
  21static bool verbose;
  22module_param(verbose, bool, 0644);
  23MODULE_PARM_DESC(verbose, "Show free falls and shocks in kernel output");
  24
  25/* Call with ams_info.lock held! */
  26void ams_sensors(s8 *x, s8 *y, s8 *z)
  27{
  28        u32 orient = ams_info.vflag? ams_info.orient1 : ams_info.orient2;
  29
  30        if (orient & 0x80)
  31                /* X and Y swapped */
  32                ams_info.get_xyz(y, x, z);
  33        else
  34                ams_info.get_xyz(x, y, z);
  35
  36        if (orient & 0x04)
  37                *z = ~(*z);
  38        if (orient & 0x02)
  39                *y = ~(*y);
  40        if (orient & 0x01)
  41                *x = ~(*x);
  42}
  43
  44static ssize_t ams_show_current(struct device *dev,
  45        struct device_attribute *attr, char *buf)
  46{
  47        s8 x, y, z;
  48
  49        mutex_lock(&ams_info.lock);
  50        ams_sensors(&x, &y, &z);
  51        mutex_unlock(&ams_info.lock);
  52
  53        return snprintf(buf, PAGE_SIZE, "%d %d %d\n", x, y, z);
  54}
  55
  56static DEVICE_ATTR(current, S_IRUGO, ams_show_current, NULL);
  57
  58static void ams_handle_irq(void *data)
  59{
  60        enum ams_irq irq = *((enum ams_irq *)data);
  61
  62        spin_lock(&ams_info.irq_lock);
  63
  64        ams_info.worker_irqs |= irq;
  65        schedule_work(&ams_info.worker);
  66
  67        spin_unlock(&ams_info.irq_lock);
  68}
  69
  70static enum ams_irq ams_freefall_irq_data = AMS_IRQ_FREEFALL;
  71static struct pmf_irq_client ams_freefall_client = {
  72        .owner = THIS_MODULE,
  73        .handler = ams_handle_irq,
  74        .data = &ams_freefall_irq_data,
  75};
  76
  77static enum ams_irq ams_shock_irq_data = AMS_IRQ_SHOCK;
  78static struct pmf_irq_client ams_shock_client = {
  79        .owner = THIS_MODULE,
  80        .handler = ams_handle_irq,
  81        .data = &ams_shock_irq_data,
  82};
  83
  84/* Once hard disk parking is implemented in the kernel, this function can
  85 * trigger it.
  86 */
  87static void ams_worker(struct work_struct *work)
  88{
  89        unsigned long flags;
  90        u8 irqs_to_clear;
  91
  92        mutex_lock(&ams_info.lock);
  93
  94        spin_lock_irqsave(&ams_info.irq_lock, flags);
  95        irqs_to_clear = ams_info.worker_irqs;
  96
  97        if (ams_info.worker_irqs & AMS_IRQ_FREEFALL) {
  98                if (verbose)
  99                        printk(KERN_INFO "ams: freefall detected!\n");
 100
 101                ams_info.worker_irqs &= ~AMS_IRQ_FREEFALL;
 102        }
 103
 104        if (ams_info.worker_irqs & AMS_IRQ_SHOCK) {
 105                if (verbose)
 106                        printk(KERN_INFO "ams: shock detected!\n");
 107
 108                ams_info.worker_irqs &= ~AMS_IRQ_SHOCK;
 109        }
 110
 111        spin_unlock_irqrestore(&ams_info.irq_lock, flags);
 112
 113        ams_info.clear_irq(irqs_to_clear);
 114
 115        mutex_unlock(&ams_info.lock);
 116}
 117
 118/* Call with ams_info.lock held! */
 119int ams_sensor_attach(void)
 120{
 121        int result;
 122        const u32 *prop;
 123
 124        /* Get orientation */
 125        prop = of_get_property(ams_info.of_node, "orientation", NULL);
 126        if (!prop)
 127                return -ENODEV;
 128        ams_info.orient1 = *prop;
 129        ams_info.orient2 = *(prop + 1);
 130
 131        /* Register freefall interrupt handler */
 132        result = pmf_register_irq_client(ams_info.of_node,
 133                        "accel-int-1",
 134                        &ams_freefall_client);
 135        if (result < 0)
 136                return -ENODEV;
 137
 138        /* Reset saved irqs */
 139        ams_info.worker_irqs = 0;
 140
 141        /* Register shock interrupt handler */
 142        result = pmf_register_irq_client(ams_info.of_node,
 143                        "accel-int-2",
 144                        &ams_shock_client);
 145        if (result < 0)
 146                goto release_freefall;
 147
 148        /* Create device */
 149        ams_info.of_dev = of_platform_device_create(ams_info.of_node, "ams", NULL);
 150        if (!ams_info.of_dev) {
 151                result = -ENODEV;
 152                goto release_shock;
 153        }
 154
 155        /* Create attributes */
 156        result = device_create_file(&ams_info.of_dev->dev, &dev_attr_current);
 157        if (result)
 158                goto release_of;
 159
 160        ams_info.vflag = !!(ams_info.get_vendor() & 0x10);
 161
 162        /* Init input device */
 163        result = ams_input_init();
 164        if (result)
 165                goto release_device_file;
 166
 167        return result;
 168release_device_file:
 169        device_remove_file(&ams_info.of_dev->dev, &dev_attr_current);
 170release_of:
 171        of_device_unregister(ams_info.of_dev);
 172release_shock:
 173        pmf_unregister_irq_client(&ams_shock_client);
 174release_freefall:
 175        pmf_unregister_irq_client(&ams_freefall_client);
 176        return result;
 177}
 178
 179int __init ams_init(void)
 180{
 181        struct device_node *np;
 182
 183        spin_lock_init(&ams_info.irq_lock);
 184        mutex_init(&ams_info.lock);
 185        INIT_WORK(&ams_info.worker, ams_worker);
 186
 187#ifdef CONFIG_SENSORS_AMS_I2C
 188        np = of_find_node_by_name(NULL, "accelerometer");
 189        if (np && of_device_is_compatible(np, "AAPL,accelerometer_1"))
 190                /* Found I2C motion sensor */
 191                return ams_i2c_init(np);
 192#endif
 193
 194#ifdef CONFIG_SENSORS_AMS_PMU
 195        np = of_find_node_by_name(NULL, "sms");
 196        if (np && of_device_is_compatible(np, "sms"))
 197                /* Found PMU motion sensor */
 198                return ams_pmu_init(np);
 199#endif
 200        return -ENODEV;
 201}
 202
 203void ams_sensor_detach(void)
 204{
 205        /* Remove input device */
 206        ams_input_exit();
 207
 208        /* Remove attributes */
 209        device_remove_file(&ams_info.of_dev->dev, &dev_attr_current);
 210
 211        /* Flush interrupt worker
 212         *
 213         * We do this after ams_info.exit(), because an interrupt might
 214         * have arrived before disabling them.
 215         */
 216        flush_work(&ams_info.worker);
 217
 218        /* Remove device */
 219        of_device_unregister(ams_info.of_dev);
 220
 221        /* Remove handler */
 222        pmf_unregister_irq_client(&ams_shock_client);
 223        pmf_unregister_irq_client(&ams_freefall_client);
 224}
 225
 226static void __exit ams_exit(void)
 227{
 228        /* Shut down implementation */
 229        ams_info.exit();
 230}
 231
 232MODULE_AUTHOR("Stelian Pop, Michael Hanselmann");
 233MODULE_DESCRIPTION("Apple Motion Sensor driver");
 234MODULE_LICENSE("GPL");
 235
 236module_init(ams_init);
 237module_exit(ams_exit);
 238