[Letux-kernel] [internal RFC PATCH 2/2] input: add Austria Microsystem AS5013 joystick driver

Andrey Utkin andrey_utkin at fastmail.com
Sat Jun 18 17:29:54 CEST 2016


Based on existing driver by
Grazvydas Ignotas <notasas at gmail.com>
H. Nikolaus Schaller <hns at goldelico.com>

Signed-off-by: Andrey Utkin <andrey_utkin at fastmail.com>
---
 .../devicetree/bindings/i2c/trivial-devices.txt    |   1 +
 MAINTAINERS                                        |   1 +
 drivers/input/joystick/Kconfig                     |  10 +
 drivers/input/joystick/Makefile                    |   1 +
 drivers/input/joystick/as5013.c                    | 390 +++++++++++++++++++++
 5 files changed, 403 insertions(+)
 create mode 100644 drivers/input/joystick/as5013.c

diff --git a/Documentation/devicetree/bindings/i2c/trivial-devices.txt b/Documentation/devicetree/bindings/i2c/trivial-devices.txt
index 53987449..03b7b80 100644
--- a/Documentation/devicetree/bindings/i2c/trivial-devices.txt
+++ b/Documentation/devicetree/bindings/i2c/trivial-devices.txt
@@ -21,6 +21,7 @@ adi,adt7490		+/-1C TDM Extended Temp Range I.C
 adi,adxl345		Three-Axis Digital Accelerometer
 adi,adxl346		Three-Axis Digital Accelerometer (backward-compatibility value "adi,adxl345" must be listed too)
 ams,iaq-core		AMS iAQ-Core VOC Sensor
+ams,as5013		Austria Microsystems AS5013 joystick
 at,24c08		i2c serial eeprom  (24cxx)
 atmel,at97sc3204t	i2c trusted platform module (TPM)
 capella,cm32181		CM32181: Ambient Light Sensor
diff --git a/MAINTAINERS b/MAINTAINERS
index ee030e2..3f6858a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8660,6 +8660,7 @@ L:	letux-kernel at openphoenux.org
 W:	https://pyra-handheld.com
 T:	git://git.openpandora.org/pyra-kernel.git
 S:	Maintained
+F:	drivers/input/joystick/as5013.c
 
 P54 WIRELESS DRIVER
 M:	Christian Lamparter <chunkeey at googlemail.com>
diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index 4215b53..369a531 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -266,6 +266,16 @@ config JOYSTICK_AS5011
 	  To compile this driver as a module, choose M here: the
 	  module will be called as5011.
 
+config JOYSTICK_AS5013
+	tristate "Austria Microsystem AS5013 joystick"
+	depends on I2C
+	help
+	  Say Y here if you have an AS5013 digital joystick connected to your
+	  system.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called as5013.
+
 config JOYSTICK_JOYDUMP
 	tristate "Gameport data dumper"
 	select GAMEPORT
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
index 92dc0de..769f47a 100644
--- a/drivers/input/joystick/Makefile
+++ b/drivers/input/joystick/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_JOYSTICK_A3D)		+= a3d.o
 obj-$(CONFIG_JOYSTICK_ADI)		+= adi.o
 obj-$(CONFIG_JOYSTICK_AMIGA)		+= amijoy.o
 obj-$(CONFIG_JOYSTICK_AS5011)		+= as5011.o
+obj-$(CONFIG_JOYSTICK_AS5013)		+= as5013.o
 obj-$(CONFIG_JOYSTICK_ANALOG)		+= analog.o
 obj-$(CONFIG_JOYSTICK_COBRA)		+= cobra.o
 obj-$(CONFIG_JOYSTICK_DB9)		+= db9.o
diff --git a/drivers/input/joystick/as5013.c b/drivers/input/joystick/as5013.c
new file mode 100644
index 0000000..ad49198
--- /dev/null
+++ b/drivers/input/joystick/as5013.c
@@ -0,0 +1,390 @@
+/*
+ * Driver for Austria Microsystems joysticks AS5011
+ *
+ * Written by Gražvydas Ignotas <notasas at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/idr.h>
+#include <linux/gpio.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+
+#define POLL_INTERVAL 25 /* msecs */
+
+struct as5013_drvdata {
+	char dev_name[8];
+	struct input_dev *input;
+	struct i2c_client *client;
+	int irq_gpio;
+	/* For polling state without hardware interrupt */
+	struct delayed_work work;
+};
+
+static int as5013_suspend(struct device *dev);
+static int as5013_resume(struct device *dev);
+
+static int as5013_i2c_write(struct i2c_client *client, uint8_t aregaddr,
+			    uint8_t avalue)
+{
+	uint8_t data[2] = { aregaddr, avalue };
+	struct i2c_msg msg = { client->addr, 0, 2, data };
+	int ret;
+
+	ret = i2c_transfer(client->adapter, &msg, 1);
+	if (ret < 0) {
+		dev_err(&client->dev, "write 0x%x failed: ret %d\n",
+			aregaddr, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int as5013_i2c_read(struct i2c_client *client,
+			   uint8_t aregaddr, uint8_t *value)
+{
+	struct i2c_msg msg_set[2] = {
+		{ client->addr, 0, 1, &aregaddr },
+		{ client->addr, I2C_M_RD, 1, value }
+	};
+	int ret;
+
+	ret = i2c_transfer(client->adapter, msg_set, ARRAY_SIZE(msg_set));
+	if (ret < 0) {
+		dev_err(&client->dev, "read 0x%x failed: ret %d\n",
+			aregaddr, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static irqreturn_t as5013_axis_isr(int irq, void *dev_id)
+{
+	struct as5013_drvdata *ddata = dev_id;
+	struct i2c_client *client = ddata->client;
+	int ax;
+	int ay;
+	uint8_t value;
+	int ret;
+
+	/* TODO Read two regs in a single act? */
+	ret = as5013_i2c_read(client, 0x10, &value);
+	if (ret < 0)
+		return IRQ_HANDLED;
+	ax = -(int8_t)value;
+
+	ret = as5013_i2c_read(client, 0x11, &value);
+	if (ret < 0)
+		return IRQ_HANDLED;
+	ay = -(int8_t)value;
+
+	input_report_abs(ddata->input, ABS_X, ax);
+	input_report_abs(ddata->input, ABS_Y, ay);
+	input_sync(ddata->input);
+
+	return IRQ_HANDLED;
+}
+
+/* Repeated device polling for case of no hardware interrupt */
+static void as5013_work(struct work_struct *work)
+{
+	struct as5013_drvdata *ddata;
+	struct i2c_client *client;
+	uint8_t value;
+	int ret;
+
+	ddata = container_of(work, struct as5013_drvdata, work.work);
+	client = ddata->client;
+
+	ret = as5013_i2c_read(client, 0x0f, &value);
+	if (ret < 0)
+		return;
+
+	if (value & 1)
+		as5013_axis_isr(0, ddata);
+
+	schedule_delayed_work(&ddata->work, msecs_to_jiffies(POLL_INTERVAL));
+}
+
+static int as5013_open(struct input_dev *dev)
+{
+	return as5013_resume(&dev->dev);
+}
+
+static void as5013_close(struct input_dev *dev)
+{
+	as5013_suspend(&dev->dev);
+}
+
+static int as5013_input_register(struct as5013_drvdata *ddata)
+{
+	struct input_dev *input = input_allocate_device();
+	int ret;
+
+	if (!input)
+		return -ENOMEM;
+
+	set_bit(EV_ABS, input->evbit);
+	input_set_abs_params(input, ABS_X, -128, 127, 0, 0);
+	input_set_abs_params(input, ABS_Y, -128, 127, 0, 0);
+
+	input->name = ddata->dev_name;
+	input->dev.parent = &ddata->client->dev;
+	input->id.bustype = BUS_I2C;
+	input->open = as5013_open;
+	input->close = as5013_close;
+	ddata->input = input;
+	input_set_drvdata(input, ddata);
+
+	ret = input_register_device(input);
+	if (ret)
+		input_free_device(input);
+
+	return ret;
+}
+
+static void as5013_input_unregister(struct as5013_drvdata *ddata)
+{
+	cancel_delayed_work_sync(&ddata->work);
+	input_unregister_device(ddata->input);
+}
+
+#ifdef CONFIG_OF
+static int as5013_dt_init(struct as5013_drvdata *ddata)
+{
+	struct device_node *np = ddata->client->dev.of_node;
+
+	ddata->irq_gpio = of_get_gpio(np, 0);
+
+	return 0;
+}
+
+static const struct of_device_id as5013_dt_match[] = {
+	{
+	.compatible = "ams,as5013",
+	},
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, as5013_dt_match);
+
+#else
+static int as5013_dt_init(struct as5013_drvdata *ddata)
+{
+	return -ENODEV;
+}
+
+#endif
+
+static int as5013_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct as5013_drvdata *ddata;
+	uint8_t value = 0;
+	int ret;
+	int i;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		dev_err(&client->dev, "can't talk I2C?\n");
+		return -EIO;
+	}
+
+	ddata = kzalloc(sizeof(struct as5013_drvdata), GFP_KERNEL);
+	if (!ddata)
+		return -ENOMEM;
+
+	/*
+	 * 0x40 and 0x41 are only i2c addresses available in non-custom as5013
+	 * samples. So make nicer names for these.
+	 */
+	if (client->addr == 0x40 || client->addr == 0x41) {
+		snprintf(ddata->dev_name, sizeof(ddata->dev_name), "nub%d",
+			 client->addr - 0x40);
+	} else {
+		snprintf(ddata->dev_name, sizeof(ddata->dev_name), "nub0x%02x",
+			 client->addr);
+	}
+
+	ddata->client = client;
+	i2c_set_clientdata(client, ddata);
+
+	ret = as5013_dt_init(ddata);
+	if (ret) {
+		dev_err(&client->dev, "Needs entries in device tree\n");
+		goto free_ddata;
+	}
+
+	/* TODO Read 0xc - 0xf regs in a single act? */
+	ret = as5013_i2c_read(client, 0x0c, &value);
+	if (ret < 0)
+		goto free_ddata;
+	dev_info(&client->dev, "ID code: %02x\n", value);
+
+	ret = as5013_i2c_read(client, 0x0d, &value);
+	if (ret < 0)
+		goto free_ddata;
+	dev_info(&client->dev, "ID version: %02x\n", value);
+
+	ret = as5013_i2c_read(client, 0x0e, &value);
+	if (ret < 0)
+		goto free_ddata;
+	dev_info(&client->dev, "silicon revision: %02x\n", value);
+
+	/* TODO Sanitize. A single read with delay is needed? */
+	for (i = 0; i < 10; i++) {
+		ret = as5013_i2c_read(client, 0x0f, &value);
+		if (ret < 0)
+			goto free_ddata;
+		dev_info(&client->dev, "control: %02x\n", value);
+		if ((value & 0xfe) == 0xf0)
+			break;
+	}
+
+	if ((value & 0xfe) != 0xf0) {
+		dev_err(&client->dev, "bad control: %02x\n", value);
+		ret = -ENODEV;
+		goto free_ddata;
+	}
+
+	if (gpio_is_valid(ddata->irq_gpio)) {
+		ret = gpio_request_one(ddata->irq_gpio, GPIOF_IN, client->name);
+		if (ret < 0) {
+			dev_err(&client->dev,
+				"failed to request GPIO %d, error %d\n",
+				ddata->irq_gpio, ret);
+			goto free_ddata;
+		}
+
+		ret = gpio_to_irq(ddata->irq_gpio);
+		if (ret < 0) {
+			dev_err(&client->dev,
+				"unable to get irq number for GPIO %d, error %d\n",
+				ddata->irq_gpio, ret);
+			goto free_gpio;
+		}
+		client->irq = ret;
+
+		ret = request_threaded_irq(client->irq, NULL, as5013_axis_isr,
+					   IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
+					   "as5013", ddata);
+		if (ret) {
+			dev_err(&client->dev, "unable to claim irq %d, error %d\n",
+				client->irq, ret);
+			goto free_gpio;
+		}
+	} else {
+		ddata->irq_gpio = 0;
+		client->irq = 0;
+		INIT_DELAYED_WORK(&ddata->work, as5013_work);
+	}
+
+	ret = as5013_input_register(ddata);
+	if (ret) {
+		dev_err(&client->dev,
+			"Failed to register input device %s, error %d\n",
+			ddata->dev_name, ret);
+		goto free_irq;
+	}
+
+	dev_dbg(&client->dev, "probe %02x, gpio %i, irq %i, \"%s\"\n",
+		client->addr, ddata->irq_gpio, client->irq, client->name);
+
+	return 0;
+
+free_irq:
+	if (client->irq)
+		free_irq(client->irq, ddata);
+free_gpio:
+	gpio_free(ddata->irq_gpio);
+free_ddata:
+	kfree(ddata);
+	return ret;
+}
+
+static int as5013_remove(struct i2c_client *client)
+{
+	struct as5013_drvdata *ddata = i2c_get_clientdata(client);
+
+	as5013_input_unregister(ddata);
+
+	if (client->irq) {
+		free_irq(client->irq, ddata);
+		gpio_free(ddata->irq_gpio);
+	} else {
+		cancel_delayed_work_sync(&ddata->work);
+	}
+	kfree(ddata);
+
+	return 0;
+}
+
+static int as5013_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct as5013_drvdata *ddata = i2c_get_clientdata(client);
+
+	if (client->irq) {
+		/*
+		 * Set to Low Power mode
+		 * with largest internal wakeup time interval
+		 */
+		as5013_i2c_write(client, 0xf, 0x7c);
+		disable_irq(client->irq);
+		return 0;
+	}
+	return !cancel_delayed_work_sync(&ddata->work);
+}
+
+static int as5013_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct as5013_drvdata *ddata = i2c_get_clientdata(client);
+
+	if (client->irq) {
+		/* Set to default mode */
+		as5013_i2c_write(client, 0xf, 0x80);
+		enable_irq(client->irq);
+		return 0;
+	}
+	return !schedule_delayed_work(&ddata->work,
+				      msecs_to_jiffies(POLL_INTERVAL));
+}
+
+static SIMPLE_DEV_PM_OPS(as5013_pm_ops, as5013_suspend, as5013_resume);
+
+static const struct i2c_device_id as5013_id[] = {
+	{ "as5013", 0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, as5013_id);
+
+static struct i2c_driver as5013_driver = {
+	.driver = {
+		.name	= "as5013",
+		.owner	= THIS_MODULE,
+		.pm	= &as5013_pm_ops,
+		.of_match_table = of_match_ptr(as5013_dt_match),
+	},
+	.probe		= as5013_probe,
+	.remove		= as5013_remove,
+	.id_table	= as5013_id,
+};
+
+module_i2c_driver(as5013_driver);
+
+MODULE_AUTHOR("Grazvydas Ignotas");
+MODULE_AUTHOR("Andrey Utkin <andrey_utkin at fastmail.com>");
+MODULE_DESCRIPTION("Driver for Austria Microsystems AS5013 joystick");
+MODULE_LICENSE("GPL");
-- 
2.8.4



More information about the Letux-kernel mailing list