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