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