162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Apple Motion Sensor driver
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
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/types.h>
1162306a36Sopenharmony_ci#include <linux/errno.h>
1262306a36Sopenharmony_ci#include <linux/init.h>
1362306a36Sopenharmony_ci#include <linux/of_platform.h>
1462306a36Sopenharmony_ci#include <asm/pmac_pfunc.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include "ams.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci/* There is only one motion sensor per machine */
1962306a36Sopenharmony_cistruct ams ams_info;
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic bool verbose;
2262306a36Sopenharmony_cimodule_param(verbose, bool, 0644);
2362306a36Sopenharmony_ciMODULE_PARM_DESC(verbose, "Show free falls and shocks in kernel output");
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci/* Call with ams_info.lock held! */
2662306a36Sopenharmony_civoid ams_sensors(s8 *x, s8 *y, s8 *z)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	u32 orient = ams_info.vflag? ams_info.orient1 : ams_info.orient2;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	if (orient & 0x80)
3162306a36Sopenharmony_ci		/* X and Y swapped */
3262306a36Sopenharmony_ci		ams_info.get_xyz(y, x, z);
3362306a36Sopenharmony_ci	else
3462306a36Sopenharmony_ci		ams_info.get_xyz(x, y, z);
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	if (orient & 0x04)
3762306a36Sopenharmony_ci		*z = ~(*z);
3862306a36Sopenharmony_ci	if (orient & 0x02)
3962306a36Sopenharmony_ci		*y = ~(*y);
4062306a36Sopenharmony_ci	if (orient & 0x01)
4162306a36Sopenharmony_ci		*x = ~(*x);
4262306a36Sopenharmony_ci}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic ssize_t ams_show_current(struct device *dev,
4562306a36Sopenharmony_ci	struct device_attribute *attr, char *buf)
4662306a36Sopenharmony_ci{
4762306a36Sopenharmony_ci	s8 x, y, z;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	mutex_lock(&ams_info.lock);
5062306a36Sopenharmony_ci	ams_sensors(&x, &y, &z);
5162306a36Sopenharmony_ci	mutex_unlock(&ams_info.lock);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	return sysfs_emit(buf, "%d %d %d\n", x, y, z);
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic DEVICE_ATTR(current, S_IRUGO, ams_show_current, NULL);
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic void ams_handle_irq(void *data)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	enum ams_irq irq = *((enum ams_irq *)data);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	spin_lock(&ams_info.irq_lock);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	ams_info.worker_irqs |= irq;
6562306a36Sopenharmony_ci	schedule_work(&ams_info.worker);
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	spin_unlock(&ams_info.irq_lock);
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic enum ams_irq ams_freefall_irq_data = AMS_IRQ_FREEFALL;
7162306a36Sopenharmony_cistatic struct pmf_irq_client ams_freefall_client = {
7262306a36Sopenharmony_ci	.owner = THIS_MODULE,
7362306a36Sopenharmony_ci	.handler = ams_handle_irq,
7462306a36Sopenharmony_ci	.data = &ams_freefall_irq_data,
7562306a36Sopenharmony_ci};
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistatic enum ams_irq ams_shock_irq_data = AMS_IRQ_SHOCK;
7862306a36Sopenharmony_cistatic struct pmf_irq_client ams_shock_client = {
7962306a36Sopenharmony_ci	.owner = THIS_MODULE,
8062306a36Sopenharmony_ci	.handler = ams_handle_irq,
8162306a36Sopenharmony_ci	.data = &ams_shock_irq_data,
8262306a36Sopenharmony_ci};
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci/* Once hard disk parking is implemented in the kernel, this function can
8562306a36Sopenharmony_ci * trigger it.
8662306a36Sopenharmony_ci */
8762306a36Sopenharmony_cistatic void ams_worker(struct work_struct *work)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	unsigned long flags;
9062306a36Sopenharmony_ci	u8 irqs_to_clear;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	mutex_lock(&ams_info.lock);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	spin_lock_irqsave(&ams_info.irq_lock, flags);
9562306a36Sopenharmony_ci	irqs_to_clear = ams_info.worker_irqs;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	if (ams_info.worker_irqs & AMS_IRQ_FREEFALL) {
9862306a36Sopenharmony_ci		if (verbose)
9962306a36Sopenharmony_ci			printk(KERN_INFO "ams: freefall detected!\n");
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci		ams_info.worker_irqs &= ~AMS_IRQ_FREEFALL;
10262306a36Sopenharmony_ci	}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	if (ams_info.worker_irqs & AMS_IRQ_SHOCK) {
10562306a36Sopenharmony_ci		if (verbose)
10662306a36Sopenharmony_ci			printk(KERN_INFO "ams: shock detected!\n");
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci		ams_info.worker_irqs &= ~AMS_IRQ_SHOCK;
10962306a36Sopenharmony_ci	}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	spin_unlock_irqrestore(&ams_info.irq_lock, flags);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	ams_info.clear_irq(irqs_to_clear);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	mutex_unlock(&ams_info.lock);
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci/* Call with ams_info.lock held! */
11962306a36Sopenharmony_ciint ams_sensor_attach(void)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	int result;
12262306a36Sopenharmony_ci	const u32 *prop;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	/* Get orientation */
12562306a36Sopenharmony_ci	prop = of_get_property(ams_info.of_node, "orientation", NULL);
12662306a36Sopenharmony_ci	if (!prop)
12762306a36Sopenharmony_ci		return -ENODEV;
12862306a36Sopenharmony_ci	ams_info.orient1 = *prop;
12962306a36Sopenharmony_ci	ams_info.orient2 = *(prop + 1);
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	/* Register freefall interrupt handler */
13262306a36Sopenharmony_ci	result = pmf_register_irq_client(ams_info.of_node,
13362306a36Sopenharmony_ci			"accel-int-1",
13462306a36Sopenharmony_ci			&ams_freefall_client);
13562306a36Sopenharmony_ci	if (result < 0)
13662306a36Sopenharmony_ci		return -ENODEV;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	/* Reset saved irqs */
13962306a36Sopenharmony_ci	ams_info.worker_irqs = 0;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	/* Register shock interrupt handler */
14262306a36Sopenharmony_ci	result = pmf_register_irq_client(ams_info.of_node,
14362306a36Sopenharmony_ci			"accel-int-2",
14462306a36Sopenharmony_ci			&ams_shock_client);
14562306a36Sopenharmony_ci	if (result < 0)
14662306a36Sopenharmony_ci		goto release_freefall;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	/* Create device */
14962306a36Sopenharmony_ci	ams_info.of_dev = of_platform_device_create(ams_info.of_node, "ams", NULL);
15062306a36Sopenharmony_ci	if (!ams_info.of_dev) {
15162306a36Sopenharmony_ci		result = -ENODEV;
15262306a36Sopenharmony_ci		goto release_shock;
15362306a36Sopenharmony_ci	}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	/* Create attributes */
15662306a36Sopenharmony_ci	result = device_create_file(&ams_info.of_dev->dev, &dev_attr_current);
15762306a36Sopenharmony_ci	if (result)
15862306a36Sopenharmony_ci		goto release_of;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	ams_info.vflag = !!(ams_info.get_vendor() & 0x10);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	/* Init input device */
16362306a36Sopenharmony_ci	result = ams_input_init();
16462306a36Sopenharmony_ci	if (result)
16562306a36Sopenharmony_ci		goto release_device_file;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	return result;
16862306a36Sopenharmony_cirelease_device_file:
16962306a36Sopenharmony_ci	device_remove_file(&ams_info.of_dev->dev, &dev_attr_current);
17062306a36Sopenharmony_cirelease_of:
17162306a36Sopenharmony_ci	of_device_unregister(ams_info.of_dev);
17262306a36Sopenharmony_cirelease_shock:
17362306a36Sopenharmony_ci	pmf_unregister_irq_client(&ams_shock_client);
17462306a36Sopenharmony_cirelease_freefall:
17562306a36Sopenharmony_ci	pmf_unregister_irq_client(&ams_freefall_client);
17662306a36Sopenharmony_ci	return result;
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic int __init ams_init(void)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	struct device_node *np;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	spin_lock_init(&ams_info.irq_lock);
18462306a36Sopenharmony_ci	mutex_init(&ams_info.lock);
18562306a36Sopenharmony_ci	INIT_WORK(&ams_info.worker, ams_worker);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci#ifdef CONFIG_SENSORS_AMS_I2C
18862306a36Sopenharmony_ci	np = of_find_node_by_name(NULL, "accelerometer");
18962306a36Sopenharmony_ci	if (np && of_device_is_compatible(np, "AAPL,accelerometer_1"))
19062306a36Sopenharmony_ci		/* Found I2C motion sensor */
19162306a36Sopenharmony_ci		return ams_i2c_init(np);
19262306a36Sopenharmony_ci#endif
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci#ifdef CONFIG_SENSORS_AMS_PMU
19562306a36Sopenharmony_ci	np = of_find_node_by_name(NULL, "sms");
19662306a36Sopenharmony_ci	if (np && of_device_is_compatible(np, "sms"))
19762306a36Sopenharmony_ci		/* Found PMU motion sensor */
19862306a36Sopenharmony_ci		return ams_pmu_init(np);
19962306a36Sopenharmony_ci#endif
20062306a36Sopenharmony_ci	return -ENODEV;
20162306a36Sopenharmony_ci}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_civoid ams_sensor_detach(void)
20462306a36Sopenharmony_ci{
20562306a36Sopenharmony_ci	/* Remove input device */
20662306a36Sopenharmony_ci	ams_input_exit();
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	/* Remove attributes */
20962306a36Sopenharmony_ci	device_remove_file(&ams_info.of_dev->dev, &dev_attr_current);
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	/* Flush interrupt worker
21262306a36Sopenharmony_ci	 *
21362306a36Sopenharmony_ci	 * We do this after ams_info.exit(), because an interrupt might
21462306a36Sopenharmony_ci	 * have arrived before disabling them.
21562306a36Sopenharmony_ci	 */
21662306a36Sopenharmony_ci	flush_work(&ams_info.worker);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	/* Remove device */
21962306a36Sopenharmony_ci	of_device_unregister(ams_info.of_dev);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	/* Remove handler */
22262306a36Sopenharmony_ci	pmf_unregister_irq_client(&ams_shock_client);
22362306a36Sopenharmony_ci	pmf_unregister_irq_client(&ams_freefall_client);
22462306a36Sopenharmony_ci}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_cistatic void __exit ams_exit(void)
22762306a36Sopenharmony_ci{
22862306a36Sopenharmony_ci	/* Shut down implementation */
22962306a36Sopenharmony_ci	ams_info.exit();
23062306a36Sopenharmony_ci}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ciMODULE_AUTHOR("Stelian Pop, Michael Hanselmann");
23362306a36Sopenharmony_ciMODULE_DESCRIPTION("Apple Motion Sensor driver");
23462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cimodule_init(ams_init);
23762306a36Sopenharmony_cimodule_exit(ams_exit);
238