162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Apple Motion Sensor driver (joystick emulation)
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
1162306a36Sopenharmony_ci#include <linux/types.h>
1262306a36Sopenharmony_ci#include <linux/errno.h>
1362306a36Sopenharmony_ci#include <linux/init.h>
1462306a36Sopenharmony_ci#include <linux/delay.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include "ams.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistatic bool joystick;
1962306a36Sopenharmony_cimodule_param(joystick, bool, S_IRUGO);
2062306a36Sopenharmony_ciMODULE_PARM_DESC(joystick, "Enable the input class device on module load");
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic bool invert;
2362306a36Sopenharmony_cimodule_param(invert, bool, S_IWUSR | S_IRUGO);
2462306a36Sopenharmony_ciMODULE_PARM_DESC(invert, "Invert input data on X and Y axis");
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic DEFINE_MUTEX(ams_input_mutex);
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic void ams_idev_poll(struct input_dev *idev)
2962306a36Sopenharmony_ci{
3062306a36Sopenharmony_ci	s8 x, y, z;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	mutex_lock(&ams_info.lock);
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	ams_sensors(&x, &y, &z);
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	x -= ams_info.xcalib;
3762306a36Sopenharmony_ci	y -= ams_info.ycalib;
3862306a36Sopenharmony_ci	z -= ams_info.zcalib;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	input_report_abs(idev, ABS_X, invert ? -x : x);
4162306a36Sopenharmony_ci	input_report_abs(idev, ABS_Y, invert ? -y : y);
4262306a36Sopenharmony_ci	input_report_abs(idev, ABS_Z, z);
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	input_sync(idev);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	mutex_unlock(&ams_info.lock);
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci/* Call with ams_info.lock held! */
5062306a36Sopenharmony_cistatic int ams_input_enable(void)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	struct input_dev *input;
5362306a36Sopenharmony_ci	s8 x, y, z;
5462306a36Sopenharmony_ci	int error;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	ams_sensors(&x, &y, &z);
5762306a36Sopenharmony_ci	ams_info.xcalib = x;
5862306a36Sopenharmony_ci	ams_info.ycalib = y;
5962306a36Sopenharmony_ci	ams_info.zcalib = z;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	input = input_allocate_device();
6262306a36Sopenharmony_ci	if (!input)
6362306a36Sopenharmony_ci		return -ENOMEM;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	input->name = "Apple Motion Sensor";
6662306a36Sopenharmony_ci	input->id.bustype = ams_info.bustype;
6762306a36Sopenharmony_ci	input->id.vendor = 0;
6862306a36Sopenharmony_ci	input->dev.parent = &ams_info.of_dev->dev;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	input_set_abs_params(input, ABS_X, -50, 50, 3, 0);
7162306a36Sopenharmony_ci	input_set_abs_params(input, ABS_Y, -50, 50, 3, 0);
7262306a36Sopenharmony_ci	input_set_abs_params(input, ABS_Z, -50, 50, 3, 0);
7362306a36Sopenharmony_ci	input_set_capability(input, EV_KEY, BTN_TOUCH);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	error = input_setup_polling(input, ams_idev_poll);
7662306a36Sopenharmony_ci	if (error)
7762306a36Sopenharmony_ci		goto err_free_input;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	input_set_poll_interval(input, 25);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	error = input_register_device(input);
8262306a36Sopenharmony_ci	if (error)
8362306a36Sopenharmony_ci		goto err_free_input;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	ams_info.idev = input;
8662306a36Sopenharmony_ci	joystick = true;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	return 0;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cierr_free_input:
9162306a36Sopenharmony_ci	input_free_device(input);
9262306a36Sopenharmony_ci	return error;
9362306a36Sopenharmony_ci}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic void ams_input_disable(void)
9662306a36Sopenharmony_ci{
9762306a36Sopenharmony_ci	if (ams_info.idev) {
9862306a36Sopenharmony_ci		input_unregister_device(ams_info.idev);
9962306a36Sopenharmony_ci		ams_info.idev = NULL;
10062306a36Sopenharmony_ci	}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	joystick = false;
10362306a36Sopenharmony_ci}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic ssize_t ams_input_show_joystick(struct device *dev,
10662306a36Sopenharmony_ci	struct device_attribute *attr, char *buf)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	return sprintf(buf, "%d\n", joystick);
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic ssize_t ams_input_store_joystick(struct device *dev,
11262306a36Sopenharmony_ci	struct device_attribute *attr, const char *buf, size_t count)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	unsigned long enable;
11562306a36Sopenharmony_ci	int error = 0;
11662306a36Sopenharmony_ci	int ret;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	ret = kstrtoul(buf, 0, &enable);
11962306a36Sopenharmony_ci	if (ret)
12062306a36Sopenharmony_ci		return ret;
12162306a36Sopenharmony_ci	if (enable > 1)
12262306a36Sopenharmony_ci		return -EINVAL;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	mutex_lock(&ams_input_mutex);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	if (enable != joystick) {
12762306a36Sopenharmony_ci		if (enable)
12862306a36Sopenharmony_ci			error = ams_input_enable();
12962306a36Sopenharmony_ci		else
13062306a36Sopenharmony_ci			ams_input_disable();
13162306a36Sopenharmony_ci	}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	mutex_unlock(&ams_input_mutex);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	return error ? error : count;
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic DEVICE_ATTR(joystick, S_IRUGO | S_IWUSR,
13962306a36Sopenharmony_ci	ams_input_show_joystick, ams_input_store_joystick);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ciint ams_input_init(void)
14262306a36Sopenharmony_ci{
14362306a36Sopenharmony_ci	if (joystick)
14462306a36Sopenharmony_ci		ams_input_enable();
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	return device_create_file(&ams_info.of_dev->dev, &dev_attr_joystick);
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_civoid ams_input_exit(void)
15062306a36Sopenharmony_ci{
15162306a36Sopenharmony_ci	device_remove_file(&ams_info.of_dev->dev, &dev_attr_joystick);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	mutex_lock(&ams_input_mutex);
15462306a36Sopenharmony_ci	ams_input_disable();
15562306a36Sopenharmony_ci	mutex_unlock(&ams_input_mutex);
15662306a36Sopenharmony_ci}
157