162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Apple Motion Sensor driver (I2C variant) 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2005 Stelian Pop (stelian@popies.net) 662306a36Sopenharmony_ci * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Clean room implementation based on the reverse engineered Mac OS X driver by 962306a36Sopenharmony_ci * Johannes Berg <johannes@sipsolutions.net>, documentation available at 1062306a36Sopenharmony_ci * http://johannes.sipsolutions.net/PowerBook/Apple_Motion_Sensor_Specification 1162306a36Sopenharmony_ci */ 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/types.h> 1562306a36Sopenharmony_ci#include <linux/errno.h> 1662306a36Sopenharmony_ci#include <linux/init.h> 1762306a36Sopenharmony_ci#include <linux/delay.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include "ams.h" 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/* AMS registers */ 2262306a36Sopenharmony_ci#define AMS_COMMAND 0x00 /* command register */ 2362306a36Sopenharmony_ci#define AMS_STATUS 0x01 /* status register */ 2462306a36Sopenharmony_ci#define AMS_CTRL1 0x02 /* read control 1 (number of values) */ 2562306a36Sopenharmony_ci#define AMS_CTRL2 0x03 /* read control 2 (offset?) */ 2662306a36Sopenharmony_ci#define AMS_CTRL3 0x04 /* read control 3 (size of each value?) */ 2762306a36Sopenharmony_ci#define AMS_DATA1 0x05 /* read data 1 */ 2862306a36Sopenharmony_ci#define AMS_DATA2 0x06 /* read data 2 */ 2962306a36Sopenharmony_ci#define AMS_DATA3 0x07 /* read data 3 */ 3062306a36Sopenharmony_ci#define AMS_DATA4 0x08 /* read data 4 */ 3162306a36Sopenharmony_ci#define AMS_DATAX 0x20 /* data X */ 3262306a36Sopenharmony_ci#define AMS_DATAY 0x21 /* data Y */ 3362306a36Sopenharmony_ci#define AMS_DATAZ 0x22 /* data Z */ 3462306a36Sopenharmony_ci#define AMS_FREEFALL 0x24 /* freefall int control */ 3562306a36Sopenharmony_ci#define AMS_SHOCK 0x25 /* shock int control */ 3662306a36Sopenharmony_ci#define AMS_SENSLOW 0x26 /* sensitivity low limit */ 3762306a36Sopenharmony_ci#define AMS_SENSHIGH 0x27 /* sensitivity high limit */ 3862306a36Sopenharmony_ci#define AMS_CTRLX 0x28 /* control X */ 3962306a36Sopenharmony_ci#define AMS_CTRLY 0x29 /* control Y */ 4062306a36Sopenharmony_ci#define AMS_CTRLZ 0x2A /* control Z */ 4162306a36Sopenharmony_ci#define AMS_UNKNOWN1 0x2B /* unknown 1 */ 4262306a36Sopenharmony_ci#define AMS_UNKNOWN2 0x2C /* unknown 2 */ 4362306a36Sopenharmony_ci#define AMS_UNKNOWN3 0x2D /* unknown 3 */ 4462306a36Sopenharmony_ci#define AMS_VENDOR 0x2E /* vendor */ 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci/* AMS commands - use with the AMS_COMMAND register */ 4762306a36Sopenharmony_cienum ams_i2c_cmd { 4862306a36Sopenharmony_ci AMS_CMD_NOOP = 0, 4962306a36Sopenharmony_ci AMS_CMD_VERSION, 5062306a36Sopenharmony_ci AMS_CMD_READMEM, 5162306a36Sopenharmony_ci AMS_CMD_WRITEMEM, 5262306a36Sopenharmony_ci AMS_CMD_ERASEMEM, 5362306a36Sopenharmony_ci AMS_CMD_READEE, 5462306a36Sopenharmony_ci AMS_CMD_WRITEEE, 5562306a36Sopenharmony_ci AMS_CMD_RESET, 5662306a36Sopenharmony_ci AMS_CMD_START, 5762306a36Sopenharmony_ci}; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic int ams_i2c_probe(struct i2c_client *client); 6062306a36Sopenharmony_cistatic void ams_i2c_remove(struct i2c_client *client); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistatic const struct i2c_device_id ams_id[] = { 6362306a36Sopenharmony_ci { "MAC,accelerometer_1", 0 }, 6462306a36Sopenharmony_ci { } 6562306a36Sopenharmony_ci}; 6662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, ams_id); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic struct i2c_driver ams_i2c_driver = { 6962306a36Sopenharmony_ci .driver = { 7062306a36Sopenharmony_ci .name = "ams", 7162306a36Sopenharmony_ci }, 7262306a36Sopenharmony_ci .probe = ams_i2c_probe, 7362306a36Sopenharmony_ci .remove = ams_i2c_remove, 7462306a36Sopenharmony_ci .id_table = ams_id, 7562306a36Sopenharmony_ci}; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic s32 ams_i2c_read(u8 reg) 7862306a36Sopenharmony_ci{ 7962306a36Sopenharmony_ci return i2c_smbus_read_byte_data(ams_info.i2c_client, reg); 8062306a36Sopenharmony_ci} 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic int ams_i2c_write(u8 reg, u8 value) 8362306a36Sopenharmony_ci{ 8462306a36Sopenharmony_ci return i2c_smbus_write_byte_data(ams_info.i2c_client, reg, value); 8562306a36Sopenharmony_ci} 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic int ams_i2c_cmd(enum ams_i2c_cmd cmd) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci s32 result; 9062306a36Sopenharmony_ci int count = 3; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci ams_i2c_write(AMS_COMMAND, cmd); 9362306a36Sopenharmony_ci msleep(5); 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci while (count--) { 9662306a36Sopenharmony_ci result = ams_i2c_read(AMS_COMMAND); 9762306a36Sopenharmony_ci if (result == 0 || result & 0x80) 9862306a36Sopenharmony_ci return 0; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci schedule_timeout_uninterruptible(HZ / 20); 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci return -1; 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic void ams_i2c_set_irq(enum ams_irq reg, char enable) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci if (reg & AMS_IRQ_FREEFALL) { 10962306a36Sopenharmony_ci u8 val = ams_i2c_read(AMS_CTRLX); 11062306a36Sopenharmony_ci if (enable) 11162306a36Sopenharmony_ci val |= 0x80; 11262306a36Sopenharmony_ci else 11362306a36Sopenharmony_ci val &= ~0x80; 11462306a36Sopenharmony_ci ams_i2c_write(AMS_CTRLX, val); 11562306a36Sopenharmony_ci } 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci if (reg & AMS_IRQ_SHOCK) { 11862306a36Sopenharmony_ci u8 val = ams_i2c_read(AMS_CTRLY); 11962306a36Sopenharmony_ci if (enable) 12062306a36Sopenharmony_ci val |= 0x80; 12162306a36Sopenharmony_ci else 12262306a36Sopenharmony_ci val &= ~0x80; 12362306a36Sopenharmony_ci ams_i2c_write(AMS_CTRLY, val); 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci if (reg & AMS_IRQ_GLOBAL) { 12762306a36Sopenharmony_ci u8 val = ams_i2c_read(AMS_CTRLZ); 12862306a36Sopenharmony_ci if (enable) 12962306a36Sopenharmony_ci val |= 0x80; 13062306a36Sopenharmony_ci else 13162306a36Sopenharmony_ci val &= ~0x80; 13262306a36Sopenharmony_ci ams_i2c_write(AMS_CTRLZ, val); 13362306a36Sopenharmony_ci } 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cistatic void ams_i2c_clear_irq(enum ams_irq reg) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci if (reg & AMS_IRQ_FREEFALL) 13962306a36Sopenharmony_ci ams_i2c_write(AMS_FREEFALL, 0); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci if (reg & AMS_IRQ_SHOCK) 14262306a36Sopenharmony_ci ams_i2c_write(AMS_SHOCK, 0); 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic u8 ams_i2c_get_vendor(void) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci return ams_i2c_read(AMS_VENDOR); 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic void ams_i2c_get_xyz(s8 *x, s8 *y, s8 *z) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci *x = ams_i2c_read(AMS_DATAX); 15362306a36Sopenharmony_ci *y = ams_i2c_read(AMS_DATAY); 15462306a36Sopenharmony_ci *z = ams_i2c_read(AMS_DATAZ); 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_cistatic int ams_i2c_probe(struct i2c_client *client) 15862306a36Sopenharmony_ci{ 15962306a36Sopenharmony_ci int vmaj, vmin; 16062306a36Sopenharmony_ci int result; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci /* There can be only one */ 16362306a36Sopenharmony_ci if (unlikely(ams_info.has_device)) 16462306a36Sopenharmony_ci return -ENODEV; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci ams_info.i2c_client = client; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci if (ams_i2c_cmd(AMS_CMD_RESET)) { 16962306a36Sopenharmony_ci printk(KERN_INFO "ams: Failed to reset the device\n"); 17062306a36Sopenharmony_ci return -ENODEV; 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci if (ams_i2c_cmd(AMS_CMD_START)) { 17462306a36Sopenharmony_ci printk(KERN_INFO "ams: Failed to start the device\n"); 17562306a36Sopenharmony_ci return -ENODEV; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci /* get version/vendor information */ 17962306a36Sopenharmony_ci ams_i2c_write(AMS_CTRL1, 0x02); 18062306a36Sopenharmony_ci ams_i2c_write(AMS_CTRL2, 0x85); 18162306a36Sopenharmony_ci ams_i2c_write(AMS_CTRL3, 0x01); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci ams_i2c_cmd(AMS_CMD_READMEM); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci vmaj = ams_i2c_read(AMS_DATA1); 18662306a36Sopenharmony_ci vmin = ams_i2c_read(AMS_DATA2); 18762306a36Sopenharmony_ci if (vmaj != 1 || vmin != 52) { 18862306a36Sopenharmony_ci printk(KERN_INFO "ams: Incorrect device version (%d.%d)\n", 18962306a36Sopenharmony_ci vmaj, vmin); 19062306a36Sopenharmony_ci return -ENODEV; 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci ams_i2c_cmd(AMS_CMD_VERSION); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci vmaj = ams_i2c_read(AMS_DATA1); 19662306a36Sopenharmony_ci vmin = ams_i2c_read(AMS_DATA2); 19762306a36Sopenharmony_ci if (vmaj != 0 || vmin != 1) { 19862306a36Sopenharmony_ci printk(KERN_INFO "ams: Incorrect firmware version (%d.%d)\n", 19962306a36Sopenharmony_ci vmaj, vmin); 20062306a36Sopenharmony_ci return -ENODEV; 20162306a36Sopenharmony_ci } 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci /* Disable interrupts */ 20462306a36Sopenharmony_ci ams_i2c_set_irq(AMS_IRQ_ALL, 0); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci result = ams_sensor_attach(); 20762306a36Sopenharmony_ci if (result < 0) 20862306a36Sopenharmony_ci return result; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci /* Set default values */ 21162306a36Sopenharmony_ci ams_i2c_write(AMS_SENSLOW, 0x15); 21262306a36Sopenharmony_ci ams_i2c_write(AMS_SENSHIGH, 0x60); 21362306a36Sopenharmony_ci ams_i2c_write(AMS_CTRLX, 0x08); 21462306a36Sopenharmony_ci ams_i2c_write(AMS_CTRLY, 0x0F); 21562306a36Sopenharmony_ci ams_i2c_write(AMS_CTRLZ, 0x4F); 21662306a36Sopenharmony_ci ams_i2c_write(AMS_UNKNOWN1, 0x14); 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci /* Clear interrupts */ 21962306a36Sopenharmony_ci ams_i2c_clear_irq(AMS_IRQ_ALL); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci ams_info.has_device = 1; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci /* Enable interrupts */ 22462306a36Sopenharmony_ci ams_i2c_set_irq(AMS_IRQ_ALL, 1); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci printk(KERN_INFO "ams: Found I2C based motion sensor\n"); 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci return 0; 22962306a36Sopenharmony_ci} 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_cistatic void ams_i2c_remove(struct i2c_client *client) 23262306a36Sopenharmony_ci{ 23362306a36Sopenharmony_ci if (ams_info.has_device) { 23462306a36Sopenharmony_ci ams_sensor_detach(); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci /* Disable interrupts */ 23762306a36Sopenharmony_ci ams_i2c_set_irq(AMS_IRQ_ALL, 0); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci /* Clear interrupts */ 24062306a36Sopenharmony_ci ams_i2c_clear_irq(AMS_IRQ_ALL); 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci printk(KERN_INFO "ams: Unloading\n"); 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci ams_info.has_device = 0; 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ci} 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_cistatic void ams_i2c_exit(void) 24962306a36Sopenharmony_ci{ 25062306a36Sopenharmony_ci i2c_del_driver(&ams_i2c_driver); 25162306a36Sopenharmony_ci} 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ciint __init ams_i2c_init(struct device_node *np) 25462306a36Sopenharmony_ci{ 25562306a36Sopenharmony_ci /* Set implementation stuff */ 25662306a36Sopenharmony_ci ams_info.of_node = np; 25762306a36Sopenharmony_ci ams_info.exit = ams_i2c_exit; 25862306a36Sopenharmony_ci ams_info.get_vendor = ams_i2c_get_vendor; 25962306a36Sopenharmony_ci ams_info.get_xyz = ams_i2c_get_xyz; 26062306a36Sopenharmony_ci ams_info.clear_irq = ams_i2c_clear_irq; 26162306a36Sopenharmony_ci ams_info.bustype = BUS_I2C; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci return i2c_add_driver(&ams_i2c_driver); 26462306a36Sopenharmony_ci} 265