Apple Motion Sensor Specification

Specification of the AMS (Apple Motion Sensor) in new Power-/IBooks (Reverse engineered)

Overview

The AMS is connected to the I2C bus in the machine, the details are available in the PROM (cf. /proc/device-tree).

It constantly measures (while enabled) the current acceleration in all three directions of the device.

It can also fire interrupts when these go out of defined ranges.

Programming Details

checking for the device

  1. Start by looking for a device that is compatible to accelerometer

  2. Check it is the right one -- must be compatible to AAPL,accelerometer_1

    Attention!

    Note: I did it this way. But I suppose just looking for the latter should be good too.

  3. Check for the device's orientation property and save the two u32 values it has, this property must exist.

  4. find the bus on which the device is -- use the full path, it contains /i2c-bus@BUSNUMBER

  5. find bus address of the device in the lower 8 bits of the reg property, shifted right by 1

(compare to the therm_adt746x module, it is almost identical except of course for the specific properties)

attaching the device

You'll of course need to do all the linux driver details (that is, attaching the i2c client, registering an input driver, ...).

When the i2c client is attached, you double check the hardware by calling the reset routine (see below). If it fails or the return value is not 0, then this device cannot properly be used, and you should abort attaching.

Attention!

FIXME: need some information about interrupts here, but I have no idea how to extract them from OF. The relevant properties are the gpio ones, and the device needs to attach to both interrupts!

If all is OK, then initialise the device by calling the init routine.

Hardware Details

Registers

The chip has the following 8-bit registers:

offset meaning
0x00 command register
0x01 status register
0x02 read control 1 (number of values)
0x03 read control 2 (offset?)
0x04 read control 3 (size of each value? always 1)
0x05 read data 1
0x06 read data 2
0x07 read data 3
0x08 read data 4
0x20 data X
0x21 data Y
0x22 data Z
0x24 freefall int control
0x25 shock int control
0x26 sensitivity low limit
0x27 sensitivity high limit
0x28 control X
0x29 control Y
0x2A control Z
0x2B unknown 1
0x2C unknown 2
0x2D unknown 3
0x2E vendor

Basic Operations

reading a register

use i2c_smbus_read_byte_data

writing a register

use i2c_smbus_write_byte_data

sending a command

In order to send a command, write its code to the command register. Sleep for 5 ms to give the hardware a chance to react, and then read the command result code. The result code is read from the command register, and is valid if it is 0 or if the highest bit is set. You should retry reading the result code a couple of times, but not forever and fail at some time.

Valid command codes are:

code meaning
0 no op
1 read version
2 read memory
3 write memory
4 erase memory
5 read EE memory
6 write EE memory
7 reset
8 start

higher level routines

reset

In order to reset the hardware, send it the reset command.

start

send the start command

get version/vendor

  1. write the values 0x02, 0x85, 0x01 to the read control registers
  2. execute read memory command
  3. the read data 1/read data 2 registers contain the application version (major/minor), the version should be 1.52 (major.minor)
  4. execute the read version command
  5. the read data 1/read data 2 registers contain the firmware(??) version (minor/major), it should be 1.0 (major.minor)
  6. read the vendor register, set a vflag variable if bit 0x10 is set

init

  1. start
  2. get version/vendor, abort if not correct
  3. write initial values
  4. clear interrupts (set the freefall and shock interrupt control registers to 0)

The initial values are the following:

register value
sensitivity low 0x15
sensitivity high 0x60
control X 0x08
control Y 0x0F
control Z 0x4F
unknown 1 (0x2B) 0x14

reading sensor values

The raw sensor values are available in the three data registers. These are signed values. They must be post-processed, depending on the vflag and on the orientation data read earlier from the PROM.

If the vflag is set, the second word of the orientation data is used below, otherwise the first.

The orientation word is comprised of bitflags:

bits 7 6 5 4 3 2 1 0
meaning X and Y swapped
Z inverted Y inverted X inverted

Possible actions to take thus are: first check if X and Y are swapped, if so swap them back. Then, if any values must be inverted, bitwise invert them (they still are signed quantities).

This will give you the processed sensor values. I installed a kernel thread reporting these to the input layer (with a bit of fuzz) and in horizontal position got values of about -5 for X, 0 for Y and constant acceleration (gravity!) of about -50 for Z. I think that the value for X should probably be 0 as well, but maybe my hardware has a small fault or it generally isn't too reliable when it comes to absolute readings. I do get proper values when turning the powerbook.

If I account for fact that the value of X is off by -5, I always get pretty good readings for the relative acceleration in all directions, depending on how the powerbook is arranged relative to the vector of earth's gravitation.

interrupt information

Warning

Because I have no clue how to programatically attach to the interrupts (those pesky GPIO properties in OF!), this is completely untested.

The highest bit in the X/Y/Z control denotes whether interrupts for this direction are enabled. Enabling or disabling any X/Y/Z interrupt is done by simply toggling the highest bit in the relevant register.

If an interrupt has happened, the highest bit in the freefall or shock interrupt register is set.

interrupt numbers

I'm not sure how to get the interrupt numbers. In any case, the properties platform-accel-int-1 and platform-accel-int-2 are relevant, and maybe somehow point to the gpio accelerometer-1/accelerometer-2 items that each have an interrupts property containing the interrupt number and an platform-do-accel-int-X property containing the gpio information.

I have no idea how to parse these things, but the tumbler powermac alsa driver does something similar with some (e.g. headphone) interrupts.

The interrupt 1 is the freefall interrupt, and 2 is the shock interrupt.

clear interrupt

In order to clear the freefall or shock interrupt, write 0 to the respective register.

checking for bad interrupts

(This ought to be done as part of the attach process above, but since I didn't do any interrupt stuff yet, it isn't)

The OSX driver assumes that the hardware is in a horizontal position and relatively stable (well, stable and horizontal enough to not get any interrupts). To check for bad interrupts, enable interrupts for X and Y directions, clear both the freefall and the shock interrupt, and check after sleeping for 30 ms if they are still clear.

handling an interrupt

When you actually get an interrupt, of course do whatever needs to be done due to it (OSX will always simply send the HD a park command).

But you also need to clear that interrupt again. (Otherwise it probably stays asserted).