linux/drivers/macintosh/ams/ams-i2c.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Apple Motion Sensor driver (I2C variant)
   4 *
   5 * Copyright (C) 2005 Stelian Pop (stelian@popies.net)
   6 * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
   7 *
   8 * Clean room implementation based on the reverse engineered Mac OS X driver by
   9 * Johannes Berg <johannes@sipsolutions.net>, documentation available at
  10 * http://johannes.sipsolutions.net/PowerBook/Apple_Motion_Sensor_Specification
  11 */
  12
  13#include <linux/module.h>
  14#include <linux/types.h>
  15#include <linux/errno.h>
  16#include <linux/init.h>
  17#include <linux/delay.h>
  18
  19#include "ams.h"
  20
  21/* AMS registers */
  22#define AMS_COMMAND     0x00    /* command register */
  23#define AMS_STATUS      0x01    /* status register */
  24#define AMS_CTRL1       0x02    /* read control 1 (number of values) */
  25#define AMS_CTRL2       0x03    /* read control 2 (offset?) */
  26#define AMS_CTRL3       0x04    /* read control 3 (size of each value?) */
  27#define AMS_DATA1       0x05    /* read data 1 */
  28#define AMS_DATA2       0x06    /* read data 2 */
  29#define AMS_DATA3       0x07    /* read data 3 */
  30#define AMS_DATA4       0x08    /* read data 4 */
  31#define AMS_DATAX       0x20    /* data X */
  32#define AMS_DATAY       0x21    /* data Y */
  33#define AMS_DATAZ       0x22    /* data Z */
  34#define AMS_FREEFALL    0x24    /* freefall int control */
  35#define AMS_SHOCK       0x25    /* shock int control */
  36#define AMS_SENSLOW     0x26    /* sensitivity low limit */
  37#define AMS_SENSHIGH    0x27    /* sensitivity high limit */
  38#define AMS_CTRLX       0x28    /* control X */
  39#define AMS_CTRLY       0x29    /* control Y */
  40#define AMS_CTRLZ       0x2A    /* control Z */
  41#define AMS_UNKNOWN1    0x2B    /* unknown 1 */
  42#define AMS_UNKNOWN2    0x2C    /* unknown 2 */
  43#define AMS_UNKNOWN3    0x2D    /* unknown 3 */
  44#define AMS_VENDOR      0x2E    /* vendor */
  45
  46/* AMS commands - use with the AMS_COMMAND register */
  47enum ams_i2c_cmd {
  48        AMS_CMD_NOOP = 0,
  49        AMS_CMD_VERSION,
  50        AMS_CMD_READMEM,
  51        AMS_CMD_WRITEMEM,
  52        AMS_CMD_ERASEMEM,
  53        AMS_CMD_READEE,
  54        AMS_CMD_WRITEEE,
  55        AMS_CMD_RESET,
  56        AMS_CMD_START,
  57};
  58
  59static int ams_i2c_probe(struct i2c_client *client,
  60                         const struct i2c_device_id *id);
  61static int ams_i2c_remove(struct i2c_client *client);
  62
  63static const struct i2c_device_id ams_id[] = {
  64        { "MAC,accelerometer_1", 0 },
  65        { }
  66};
  67MODULE_DEVICE_TABLE(i2c, ams_id);
  68
  69static struct i2c_driver ams_i2c_driver = {
  70        .driver = {
  71                .name   = "ams",
  72        },
  73        .probe          = ams_i2c_probe,
  74        .remove         = ams_i2c_remove,
  75        .id_table       = ams_id,
  76};
  77
  78static s32 ams_i2c_read(u8 reg)
  79{
  80        return i2c_smbus_read_byte_data(ams_info.i2c_client, reg);
  81}
  82
  83static int ams_i2c_write(u8 reg, u8 value)
  84{
  85        return i2c_smbus_write_byte_data(ams_info.i2c_client, reg, value);
  86}
  87
  88static int ams_i2c_cmd(enum ams_i2c_cmd cmd)
  89{
  90        s32 result;
  91        int count = 3;
  92
  93        ams_i2c_write(AMS_COMMAND, cmd);
  94        msleep(5);
  95
  96        while (count--) {
  97                result = ams_i2c_read(AMS_COMMAND);
  98                if (result == 0 || result & 0x80)
  99                        return 0;
 100
 101                schedule_timeout_uninterruptible(HZ / 20);
 102        }
 103
 104        return -1;
 105}
 106
 107static void ams_i2c_set_irq(enum ams_irq reg, char enable)
 108{
 109        if (reg & AMS_IRQ_FREEFALL) {
 110                u8 val = ams_i2c_read(AMS_CTRLX);
 111                if (enable)
 112                        val |= 0x80;
 113                else
 114                        val &= ~0x80;
 115                ams_i2c_write(AMS_CTRLX, val);
 116        }
 117
 118        if (reg & AMS_IRQ_SHOCK) {
 119                u8 val = ams_i2c_read(AMS_CTRLY);
 120                if (enable)
 121                        val |= 0x80;
 122                else
 123                        val &= ~0x80;
 124                ams_i2c_write(AMS_CTRLY, val);
 125        }
 126
 127        if (reg & AMS_IRQ_GLOBAL) {
 128                u8 val = ams_i2c_read(AMS_CTRLZ);
 129                if (enable)
 130                        val |= 0x80;
 131                else
 132                        val &= ~0x80;
 133                ams_i2c_write(AMS_CTRLZ, val);
 134        }
 135}
 136
 137static void ams_i2c_clear_irq(enum ams_irq reg)
 138{
 139        if (reg & AMS_IRQ_FREEFALL)
 140                ams_i2c_write(AMS_FREEFALL, 0);
 141
 142        if (reg & AMS_IRQ_SHOCK)
 143                ams_i2c_write(AMS_SHOCK, 0);
 144}
 145
 146static u8 ams_i2c_get_vendor(void)
 147{
 148        return ams_i2c_read(AMS_VENDOR);
 149}
 150
 151static void ams_i2c_get_xyz(s8 *x, s8 *y, s8 *z)
 152{
 153        *x = ams_i2c_read(AMS_DATAX);
 154        *y = ams_i2c_read(AMS_DATAY);
 155        *z = ams_i2c_read(AMS_DATAZ);
 156}
 157
 158static int ams_i2c_probe(struct i2c_client *client,
 159                         const struct i2c_device_id *id)
 160{
 161        int vmaj, vmin;
 162        int result;
 163
 164        /* There can be only one */
 165        if (unlikely(ams_info.has_device))
 166                return -ENODEV;
 167
 168        ams_info.i2c_client = client;
 169
 170        if (ams_i2c_cmd(AMS_CMD_RESET)) {
 171                printk(KERN_INFO "ams: Failed to reset the device\n");
 172                return -ENODEV;
 173        }
 174
 175        if (ams_i2c_cmd(AMS_CMD_START)) {
 176                printk(KERN_INFO "ams: Failed to start the device\n");
 177                return -ENODEV;
 178        }
 179
 180        /* get version/vendor information */
 181        ams_i2c_write(AMS_CTRL1, 0x02);
 182        ams_i2c_write(AMS_CTRL2, 0x85);
 183        ams_i2c_write(AMS_CTRL3, 0x01);
 184
 185        ams_i2c_cmd(AMS_CMD_READMEM);
 186
 187        vmaj = ams_i2c_read(AMS_DATA1);
 188        vmin = ams_i2c_read(AMS_DATA2);
 189        if (vmaj != 1 || vmin != 52) {
 190                printk(KERN_INFO "ams: Incorrect device version (%d.%d)\n",
 191                        vmaj, vmin);
 192                return -ENODEV;
 193        }
 194
 195        ams_i2c_cmd(AMS_CMD_VERSION);
 196
 197        vmaj = ams_i2c_read(AMS_DATA1);
 198        vmin = ams_i2c_read(AMS_DATA2);
 199        if (vmaj != 0 || vmin != 1) {
 200                printk(KERN_INFO "ams: Incorrect firmware version (%d.%d)\n",
 201                        vmaj, vmin);
 202                return -ENODEV;
 203        }
 204
 205        /* Disable interrupts */
 206        ams_i2c_set_irq(AMS_IRQ_ALL, 0);
 207
 208        result = ams_sensor_attach();
 209        if (result < 0)
 210                return result;
 211
 212        /* Set default values */
 213        ams_i2c_write(AMS_SENSLOW, 0x15);
 214        ams_i2c_write(AMS_SENSHIGH, 0x60);
 215        ams_i2c_write(AMS_CTRLX, 0x08);
 216        ams_i2c_write(AMS_CTRLY, 0x0F);
 217        ams_i2c_write(AMS_CTRLZ, 0x4F);
 218        ams_i2c_write(AMS_UNKNOWN1, 0x14);
 219
 220        /* Clear interrupts */
 221        ams_i2c_clear_irq(AMS_IRQ_ALL);
 222
 223        ams_info.has_device = 1;
 224
 225        /* Enable interrupts */
 226        ams_i2c_set_irq(AMS_IRQ_ALL, 1);
 227
 228        printk(KERN_INFO "ams: Found I2C based motion sensor\n");
 229
 230        return 0;
 231}
 232
 233static int ams_i2c_remove(struct i2c_client *client)
 234{
 235        if (ams_info.has_device) {
 236                ams_sensor_detach();
 237
 238                /* Disable interrupts */
 239                ams_i2c_set_irq(AMS_IRQ_ALL, 0);
 240
 241                /* Clear interrupts */
 242                ams_i2c_clear_irq(AMS_IRQ_ALL);
 243
 244                printk(KERN_INFO "ams: Unloading\n");
 245
 246                ams_info.has_device = 0;
 247        }
 248
 249        return 0;
 250}
 251
 252static void ams_i2c_exit(void)
 253{
 254        i2c_del_driver(&ams_i2c_driver);
 255}
 256
 257int __init ams_i2c_init(struct device_node *np)
 258{
 259        int result;
 260
 261        /* Set implementation stuff */
 262        ams_info.of_node = np;
 263        ams_info.exit = ams_i2c_exit;
 264        ams_info.get_vendor = ams_i2c_get_vendor;
 265        ams_info.get_xyz = ams_i2c_get_xyz;
 266        ams_info.clear_irq = ams_i2c_clear_irq;
 267        ams_info.bustype = BUS_I2C;
 268
 269        result = i2c_add_driver(&ams_i2c_driver);
 270
 271        return result;
 272}
 273