Specification of the AMS (Apple Motion Sensor) in new Power-/IBooks (Reverse engineered)
Contents
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
-
Start by looking for a device that is compatible to accelerometer
-
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.
-
Check for the device's orientation property and save the two u32 values it has, this property must exist.
-
find the bus on which the device is -- use the full path, it contains /i2c-bus@BUSNUMBER
-
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
- write the values 0x02, 0x85, 0x01 to the read control registers
- execute read memory command
- the read data 1/read data 2 registers contain the application version (major/minor), the version should be 1.52 (major.minor)
- execute the read version command
- the read data 1/read data 2 registers contain the firmware(??) version (minor/major), it should be 1.0 (major.minor)
- read the vendor register, set a vflag variable if bit 0x10 is set
init
- start
- get version/vendor, abort if not correct
- write initial values
- 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).