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 */ 51 enum 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 63 static int ams_i2c_probe(struct i2c_client *client, 64 const struct i2c_device_id *id); 65 static int ams_i2c_remove(struct i2c_client *client); 66 67 static const struct i2c_device_id ams_id[] = { 68 { "MAC,accelerometer_1", 0 }, 69 { } 70 }; 71 MODULE_DEVICE_TABLE(i2c, ams_id); 72 73 static 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 82 static s32 ams_i2c_read(u8 reg) 83 { 84 return i2c_smbus_read_byte_data(ams_info.i2c_client, reg); 85 } 86 87 static int ams_i2c_write(u8 reg, u8 value) 88 { 89 return i2c_smbus_write_byte_data(ams_info.i2c_client, reg, value); 90 } 91 92 static 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 111 static 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 141 static 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 150 static u8 ams_i2c_get_vendor(void) 151 { 152 return ams_i2c_read(AMS_VENDOR); 153 } 154 155 static 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 162 static 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 237 static 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 256 static void ams_i2c_exit(void) 257 { 258 i2c_del_driver(&ams_i2c_driver); 259 } 260 261 int __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