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        { "ams", 0 },
  69        { }
  70};
  71MODULE_DEVICE_TABLE(i2c, ams_id);
  72
  73static struct i2c_driver ams_i2c_driver = {
  74        .driver = {
  75                .name   = "ams",
  76                .owner  = THIS_MODULE,
  77        },
  78        .probe          = ams_i2c_probe,
  79        .remove         = ams_i2c_remove,
  80        .id_table       = ams_id,
  81};
  82
  83static s32 ams_i2c_read(u8 reg)
  84{
  85        return i2c_smbus_read_byte_data(ams_info.i2c_client, reg);
  86}
  87
  88static int ams_i2c_write(u8 reg, u8 value)
  89{
  90        return i2c_smbus_write_byte_data(ams_info.i2c_client, reg, value);
  91}
  92
  93static int ams_i2c_cmd(enum ams_i2c_cmd cmd)
  94{
  95        s32 result;
  96        int count = 3;
  97
  98        ams_i2c_write(AMS_COMMAND, cmd);
  99        msleep(5);
 100
 101        while (count--) {
 102                result = ams_i2c_read(AMS_COMMAND);
 103                if (result == 0 || result & 0x80)
 104                        return 0;
 105
 106                schedule_timeout_uninterruptible(HZ / 20);
 107        }
 108
 109        return -1;
 110}
 111
 112static void ams_i2c_set_irq(enum ams_irq reg, char enable)
 113{
 114        if (reg & AMS_IRQ_FREEFALL) {
 115                u8 val = ams_i2c_read(AMS_CTRLX);
 116                if (enable)
 117                        val |= 0x80;
 118                else
 119                        val &= ~0x80;
 120                ams_i2c_write(AMS_CTRLX, val);
 121        }
 122
 123        if (reg & AMS_IRQ_SHOCK) {
 124                u8 val = ams_i2c_read(AMS_CTRLY);
 125                if (enable)
 126                        val |= 0x80;
 127                else
 128                        val &= ~0x80;
 129                ams_i2c_write(AMS_CTRLY, val);
 130        }
 131
 132        if (reg & AMS_IRQ_GLOBAL) {
 133                u8 val = ams_i2c_read(AMS_CTRLZ);
 134                if (enable)
 135                        val |= 0x80;
 136                else
 137                        val &= ~0x80;
 138                ams_i2c_write(AMS_CTRLZ, val);
 139        }
 140}
 141
 142static void ams_i2c_clear_irq(enum ams_irq reg)
 143{
 144        if (reg & AMS_IRQ_FREEFALL)
 145                ams_i2c_write(AMS_FREEFALL, 0);
 146
 147        if (reg & AMS_IRQ_SHOCK)
 148                ams_i2c_write(AMS_SHOCK, 0);
 149}
 150
 151static u8 ams_i2c_get_vendor(void)
 152{
 153        return ams_i2c_read(AMS_VENDOR);
 154}
 155
 156static void ams_i2c_get_xyz(s8 *x, s8 *y, s8 *z)
 157{
 158        *x = ams_i2c_read(AMS_DATAX);
 159        *y = ams_i2c_read(AMS_DATAY);
 160        *z = ams_i2c_read(AMS_DATAZ);
 161}
 162
 163static int ams_i2c_probe(struct i2c_client *client,
 164                         const struct i2c_device_id *id)
 165{
 166        int vmaj, vmin;
 167        int result;
 168
 169        /* There can be only one */
 170        if (unlikely(ams_info.has_device))
 171                return -ENODEV;
 172
 173        ams_info.i2c_client = client;
 174
 175        if (ams_i2c_cmd(AMS_CMD_RESET)) {
 176                printk(KERN_INFO "ams: Failed to reset the device\n");
 177                return -ENODEV;
 178        }
 179
 180        if (ams_i2c_cmd(AMS_CMD_START)) {
 181                printk(KERN_INFO "ams: Failed to start the device\n");
 182                return -ENODEV;
 183        }
 184
 185        /* get version/vendor information */
 186        ams_i2c_write(AMS_CTRL1, 0x02);
 187        ams_i2c_write(AMS_CTRL2, 0x85);
 188        ams_i2c_write(AMS_CTRL3, 0x01);
 189
 190        ams_i2c_cmd(AMS_CMD_READMEM);
 191
 192        vmaj = ams_i2c_read(AMS_DATA1);
 193        vmin = ams_i2c_read(AMS_DATA2);
 194        if (vmaj != 1 || vmin != 52) {
 195                printk(KERN_INFO "ams: Incorrect device version (%d.%d)\n",
 196                        vmaj, vmin);
 197                return -ENODEV;
 198        }
 199
 200        ams_i2c_cmd(AMS_CMD_VERSION);
 201
 202        vmaj = ams_i2c_read(AMS_DATA1);
 203        vmin = ams_i2c_read(AMS_DATA2);
 204        if (vmaj != 0 || vmin != 1) {
 205                printk(KERN_INFO "ams: Incorrect firmware version (%d.%d)\n",
 206                        vmaj, vmin);
 207                return -ENODEV;
 208        }
 209
 210        /* Disable interrupts */
 211        ams_i2c_set_irq(AMS_IRQ_ALL, 0);
 212
 213        result = ams_sensor_attach();
 214        if (result < 0)
 215                return result;
 216
 217        /* Set default values */
 218        ams_i2c_write(AMS_SENSLOW, 0x15);
 219        ams_i2c_write(AMS_SENSHIGH, 0x60);
 220        ams_i2c_write(AMS_CTRLX, 0x08);
 221        ams_i2c_write(AMS_CTRLY, 0x0F);
 222        ams_i2c_write(AMS_CTRLZ, 0x4F);
 223        ams_i2c_write(AMS_UNKNOWN1, 0x14);
 224
 225        /* Clear interrupts */
 226        ams_i2c_clear_irq(AMS_IRQ_ALL);
 227
 228        ams_info.has_device = 1;
 229
 230        /* Enable interrupts */
 231        ams_i2c_set_irq(AMS_IRQ_ALL, 1);
 232
 233        printk(KERN_INFO "ams: Found I2C based motion sensor\n");
 234
 235        return 0;
 236}
 237
 238static int ams_i2c_remove(struct i2c_client *client)
 239{
 240        if (ams_info.has_device) {
 241                ams_sensor_detach();
 242
 243                /* Disable interrupts */
 244                ams_i2c_set_irq(AMS_IRQ_ALL, 0);
 245
 246                /* Clear interrupts */
 247                ams_i2c_clear_irq(AMS_IRQ_ALL);
 248
 249                printk(KERN_INFO "ams: Unloading\n");
 250
 251                ams_info.has_device = 0;
 252        }
 253
 254        return 0;
 255}
 256
 257static void ams_i2c_exit(void)
 258{
 259        i2c_del_driver(&ams_i2c_driver);
 260}
 261
 262int __init ams_i2c_init(struct device_node *np)
 263{
 264        int result;
 265
 266        /* Set implementation stuff */
 267        ams_info.of_node = np;
 268        ams_info.exit = ams_i2c_exit;
 269        ams_info.get_vendor = ams_i2c_get_vendor;
 270        ams_info.get_xyz = ams_i2c_get_xyz;
 271        ams_info.clear_irq = ams_i2c_clear_irq;
 272        ams_info.bustype = BUS_I2C;
 273
 274        result = i2c_add_driver(&ams_i2c_driver);
 275
 276        return result;
 277}
 278