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