@@ -255,6 +255,15 @@ config JOYSTICK_AMIGA
To compile this driver as a module, choose M here: the
module will be called amijoy.
+config JOYSTICK_AS5011
+ tristate "Austria Microsystem AS5011 joystick"
+ depends on I2C
+ help
+ Say Y here if you have an AS5011 digital joystick.
+
+ To compile this driver as a module, choose M here: the
+ module will be called as5011.
+
config JOYSTICK_JOYDUMP
tristate "Gameport data dumper"
select GAMEPORT
@@ -7,6 +7,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_ANALOG) += analog.o
obj-$(CONFIG_JOYSTICK_COBRA) += cobra.o
obj-$(CONFIG_JOYSTICK_DB9) += db9.o
new file mode 100644
@@ -0,0 +1,454 @@
+/*
+ * Copyright (c) 2010 Fabien Marteau <fabien.marteau@armadeus.com>
+ *
+ * Sponsored by ARMadeus Systems
+ */
+
+/*
+ * Driver for Austria Microsystems joysticks AS5011
+ */
+
+/*
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * TODO:
+ * - Manage power mode
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/string.h>
+#include <linux/list.h>
+#include <linux/sysfs.h>
+#include <linux/ctype.h>
+#include <linux/hwmon-sysfs.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+
+#include <linux/input.h>
+#include <linux/gpio.h>
+#include <linux/workqueue.h>
+
+#include <linux/as5011.h>
+#define DRIVER_DESC "Driver for Austria Microsystems AS5011 joystick"
+
+MODULE_AUTHOR("Fabien Marteau <fabien.marteau@armadeus.com>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+#define SIZE_NAME 30
+#define AS5011_MAX_AXIS 80
+#define AS5011_MIN_AXIS (-80)
+#define AS5011_FUZZ 8
+#define AS5011_FLAT 40
+
+static int g_num = 1; /* device counter */
+
+static signed char as5011_i2c_read(struct i2c_client *client,
+ uint8_t aRegAddr);
+static int as5011_i2c_write(struct i2c_client *client,
+ uint8_t aRegAddr,
+ uint8_t aValue);
+
+/*
+ * interrupts operations
+ */
+
+static irqreturn_t button_interrupt(int irq, void *dev_id)
+{
+ struct as5011_platform_data *plat_dat =
+ (struct as5011_platform_data *)dev_id;
+ int ret;
+
+ ret = gpio_get_value(plat_dat->button_gpio);
+ if (ret)
+ input_report_key(plat_dat->input_dev, BTN_JOYSTICK, 0);
+ else
+ input_report_key(plat_dat->input_dev, BTN_JOYSTICK, 1);
+ input_sync(plat_dat->input_dev);
+ return IRQ_HANDLED;
+}
+
+static void as5011_update_axes(struct work_struct *work)
+{
+ struct as5011_platform_data *plat_dat =
+ container_of(work,
+ struct as5011_platform_data,
+ update_axes_work);
+ signed char x, y;
+
+ x = as5011_i2c_read(plat_dat->i2c_client, AS5011_X_RES_INT);
+ y = as5011_i2c_read(plat_dat->i2c_client, AS5011_Y_RES_INT);
+ input_report_abs(plat_dat->input_dev, ABS_X, x);
+ input_report_abs(plat_dat->input_dev, ABS_Y, y);
+ input_sync(plat_dat->input_dev);
+}
+
+static irqreturn_t as5011_int_interrupt(int irq, void *dev_id)
+{
+ struct as5011_platform_data *plat_dat =
+ (struct as5011_platform_data *)dev_id;
+
+ queue_work(plat_dat->workqueue, &plat_dat->update_axes_work);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * I2C bus operation
+ */
+
+static int as5011_i2c_write(struct i2c_client *client,
+ uint8_t aRegAddr,
+ uint8_t aValue)
+{
+ int ret;
+ uint8_t data[2] = { aRegAddr, aValue };
+ struct i2c_msg msg = { client->addr,
+ I2C_M_IGNORE_NAK,
+ 2,
+ (uint8_t *)data };
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ if (ret < 0)
+ return -EINVAL;
+ return 1;
+}
+
+static signed char as5011_i2c_read(struct i2c_client *client,
+ uint8_t aRegAddr)
+{
+ int ret;
+ uint8_t data[2];
+ struct i2c_msg msg_set[2];
+ struct i2c_msg msg1 = { client->addr,
+ I2C_M_REV_DIR_ADDR,
+ 1,
+ (uint8_t *)data};
+ struct i2c_msg msg2 = { client->addr,
+ I2C_M_RD|I2C_M_NOSTART,
+ 1,
+ (uint8_t *)data};
+
+ data[0] = aRegAddr;
+ msg_set[0] = msg1;
+ msg_set[1] = msg2;
+
+ ret = i2c_transfer(client->adapter, msg_set, 2);
+ if (ret < 0)
+ return -EINVAL;
+
+ if (data[0] & 0x80)
+ return -1*(1+(~data[0]));
+ else
+ return data[0];
+}
+
+static int as5011_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct as5011_platform_data *plat_dat = client->dev.platform_data;
+ int retval = 0;
+
+ plat_dat->num = g_num;
+ g_num++;
+
+ retval = snprintf(plat_dat->workqueue_name,
+ SIZE_NAME,
+ "as5011_%d_workqueue", plat_dat->num);
+ if (retval < 0) {
+ dev_err(&client->dev, "as5011: Failed to give workqueue name\n");
+ retval = -1;
+ goto workqueue_name_error;
+ }
+ plat_dat->workqueue =
+ create_singlethread_workqueue(plat_dat->workqueue_name);
+ if (plat_dat->workqueue == NULL) {
+ dev_err(&client->dev, "as5011: Failed to create workqueue\n");
+ retval = -EINVAL;
+ goto workqueue_create_error;
+ }
+
+ INIT_WORK(&plat_dat->update_axes_work, as5011_update_axes);
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_PROTOCOL_MANGLING)) {
+ dev_err(&client->dev,
+ "i2c bus does not support protocol mangling, as5011 can't work\n");
+ retval = -ENODEV;
+ goto i2c_protocol_error;
+ }
+ plat_dat->i2c_client = client;
+
+ plat_dat->input_dev = input_allocate_device();
+ if (plat_dat->input_dev < 0) {
+ dev_err(&client->dev,
+ "not enough memory for input devices structure\n");
+ retval = -ENOMEM;
+ goto input_allocate_device_error;
+ }
+
+ plat_dat->input_dev->name = "Austria Microsystem as5011 joystick";
+ plat_dat->input_dev->uniq = "Austria0";
+ plat_dat->input_dev->id.bustype = BUS_I2C;
+ plat_dat->input_dev->phys = "/dev/input/event0";
+ plat_dat->input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ plat_dat->input_dev->keybit[BIT_WORD(BTN_JOYSTICK)] =
+ BIT_MASK(BTN_JOYSTICK);
+
+ input_set_abs_params(plat_dat->input_dev,
+ ABS_X,
+ AS5011_MIN_AXIS,
+ AS5011_MAX_AXIS,
+ AS5011_FUZZ,
+ AS5011_FLAT);
+ input_set_abs_params(plat_dat->input_dev,
+ ABS_Y,
+ AS5011_MIN_AXIS,
+ AS5011_MAX_AXIS,
+ AS5011_FUZZ,
+ AS5011_FLAT);
+
+ plat_dat->button_irq_name =
+ kmalloc(sizeof(unsigned char)*SIZE_NAME,
+ GFP_KERNEL);
+ if (plat_dat->button_irq_name == NULL) {
+ dev_err(&client->dev,
+ "not enough memory for input devices irq name\n");
+ retval = -ENOMEM;
+ goto input_allocate_device_name_error;
+ }
+
+
+ retval = snprintf(plat_dat->button_irq_name,
+ SIZE_NAME,
+ "as5011_%d_button",
+ plat_dat->num);
+ if (retval < 0) {
+ dev_err(&client->dev, "as5011: Failed to give button irq name\n");
+ retval = -EINVAL;
+ goto button_irq_name_error;
+ }
+
+ if (request_irq(plat_dat->button_irq, button_interrupt,
+ 0, plat_dat->button_irq_name, (void *)plat_dat)) {
+ dev_err(&client->dev, "as5011: Can't allocate irq %d\n",
+ plat_dat->button_irq);
+ retval = -EBUSY;
+ goto request_irq_error;
+ }
+
+ plat_dat->int_irq_name =
+ kmalloc(sizeof(unsigned char)*SIZE_NAME,
+ GFP_KERNEL);
+ if (plat_dat->int_irq_name == NULL) {
+ dev_err(&client->dev,
+ "not enough memory for input devices int irq name\n");
+ retval = -ENOMEM;
+ goto input_allocate_int_device_name_error;
+ }
+
+ retval = snprintf(plat_dat->int_irq_name,
+ SIZE_NAME,
+ "as5011_%d_int",
+ plat_dat->num);
+ if (retval < 0) {
+ dev_err(&client->dev, "as5011: Failed to give int irq name\n");
+ retval = -EINVAL;
+ goto int_irq_name_error;
+ }
+
+
+ if (request_irq(plat_dat->int_irq, as5011_int_interrupt,
+ 0, plat_dat->int_irq_name, (void *)plat_dat)) {
+ dev_err(&client->dev, "as5011: Can't allocate int irq %d\n",
+ plat_dat->int_irq);
+ retval = -EBUSY;
+ goto request_int_irq_error;
+ }
+
+ retval = input_register_device(plat_dat->input_dev);
+ if (retval) {
+ dev_err(&client->dev, "as5011: Failed to register device\n");
+ retval = -EINVAL;
+ goto input_register_device_error;
+ }
+
+ retval = plat_dat->init_gpio();
+ if (retval < 0) {
+ dev_err(&client->dev, "as5011: Failed to init gpios\n");
+ retval = -EINVAL;
+ goto init_gpio_error;
+ }
+
+ /* chip soft reset */
+ retval = as5011_i2c_write(plat_dat->i2c_client,
+ AS5011_CTRL1,
+ AS5011_CTRL1_SOFT_RST);
+ if (retval < 0) {
+ dev_err(&plat_dat->i2c_client->dev,
+ "as5011: soft reset failed\n");
+ retval = -EINVAL;
+ goto soft_reset_error;
+ }
+
+ retval = as5011_i2c_write(plat_dat->i2c_client,
+ AS5011_CTRL1,
+ AS5011_CTRL1_LP_PULSED |
+ AS5011_CTRL1_LP_ACTIVE |
+ AS5011_CTRL1_INT_ACT_EN
+ );
+ if (retval < 0) {
+ dev_err(&plat_dat->i2c_client->dev,
+ "as5011: power config failed\n");
+ retval = -EINVAL;
+ goto soft_reset_error;
+ }
+
+ retval = as5011_i2c_write(plat_dat->i2c_client,
+ AS5011_CTRL2,
+ AS5011_CTRL2_INV_SPINNING);
+ if (retval < 0) {
+ dev_err(&plat_dat->i2c_client->dev,
+ "as5011: can't invert spinning\n");
+ retval = -EINVAL;
+ goto invert_spinning_error;
+ }
+
+ /* write threshold */
+ retval = as5011_i2c_write(plat_dat->i2c_client,
+ AS5011_XP,
+ plat_dat->Xp);
+ if (retval < 0) {
+ dev_err(&plat_dat->i2c_client->dev,
+ "as5011: can't write threshold\n");
+ retval = -EINVAL;
+ goto threshold_error;
+ }
+
+ retval = as5011_i2c_write(plat_dat->i2c_client,
+ AS5011_XN,
+ plat_dat->Xn);
+ if (retval < 0) {
+ dev_err(&plat_dat->i2c_client->dev,
+ "as5011: can't write threshold\n");
+ retval = -EINVAL;
+ goto threshold_error;
+ }
+
+ retval = as5011_i2c_write(plat_dat->i2c_client,
+ AS5011_YP,
+ plat_dat->Yp);
+ if (retval < 0) {
+ dev_err(&plat_dat->i2c_client->dev,
+ "as5011: can't write threshold\n");
+ retval = -EINVAL;
+ goto threshold_error;
+ }
+
+ retval = as5011_i2c_write(plat_dat->i2c_client,
+ AS5011_YN,
+ plat_dat->Yn);
+ if (retval < 0) {
+ dev_err(&plat_dat->i2c_client->dev,
+ "as5011: can't write threshold\n");
+ retval = -EINVAL;
+ goto threshold_error;
+ }
+
+ /* to free irq gpio in chip*/
+ as5011_i2c_read(plat_dat->i2c_client, AS5011_X_RES_INT);
+
+ queue_work(plat_dat->workqueue, &plat_dat->update_axes_work);
+
+ return 0;
+
+ /* Error management */
+threshold_error:
+invert_spinning_error:
+soft_reset_error:
+ plat_dat->exit_gpio();
+init_gpio_error:
+ input_unregister_device(plat_dat->input_dev);
+input_register_device_error:
+ free_irq(plat_dat->int_irq, as5011_int_interrupt);
+request_int_irq_error:
+int_irq_name_error:
+ kfree(plat_dat->int_irq_name);
+input_allocate_int_device_name_error:
+ free_irq(plat_dat->button_irq, button_interrupt);
+request_irq_error:
+button_irq_name_error:
+ kfree(plat_dat->button_irq_name);
+input_allocate_device_name_error:
+ input_free_device(plat_dat->input_dev);
+input_allocate_device_error:
+i2c_protocol_error:
+ destroy_workqueue(plat_dat->workqueue);
+workqueue_create_error:
+workqueue_name_error:
+ return retval;
+}
+static int as5011_remove(struct i2c_client *client)
+{
+ struct as5011_platform_data *plat_dat = client->dev.platform_data;
+
+ destroy_workqueue(plat_dat->workqueue);
+ input_unregister_device(plat_dat->input_dev);
+ free_irq(plat_dat->button_irq, plat_dat);
+ free_irq(plat_dat->int_irq, plat_dat);
+ kfree(plat_dat->button_irq_name);
+ input_free_device(plat_dat->input_dev);
+ plat_dat->exit_gpio();
+
+ return 0;
+}
+static const struct i2c_device_id as5011_id[] = {
+ { "as5011", 0 },
+ { }
+};
+
+static struct i2c_driver as5011_driver = {
+ .driver = {
+ .name = "as5011",
+ },
+ .probe = as5011_probe,
+ .remove = as5011_remove,
+ .id_table = as5011_id,
+};
+
+/*
+ * Module initialization
+ */
+
+static int __init as5011_init(void)
+{
+ return i2c_add_driver(&as5011_driver);
+}
+
+static void __exit as5011_exit(void)
+{
+ i2c_del_driver(&as5011_driver);
+}
+
+module_init(as5011_init);
+module_exit(as5011_exit);
new file mode 100644
@@ -0,0 +1,72 @@
+#ifndef _AS5011_H
+#define _AS5011_H
+
+/*
+ * Copyright (c) 2010 Fabien Marteau
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+
+#define AS5011_MAX_NAME_LENGTH 64
+#define AS5011_MAX_CNAME_LENGTH 16
+#define AS5011_MAX_PHYS_LENGTH 64
+#define AS5011_MAX_LENGTH 64
+
+/* registers */
+#define AS5011_CTRL1 0x76
+#define AS5011_CTRL2 0x75
+#define AS5011_XP 0x43
+#define AS5011_XN 0x44
+#define AS5011_YP 0x53
+#define AS5011_YN 0x54
+#define AS5011_X_REG 0x41
+#define AS5011_Y_REG 0x42
+#define AS5011_X_RES_INT 0x51
+#define AS5011_Y_RES_INT 0x52
+
+/* CTRL1 bits */
+#define AS5011_CTRL1_LP_PULSED 0x80
+#define AS5011_CTRL1_LP_ACTIVE 0x40
+#define AS5011_CTRL1_LP_CONTINUE 0x20
+#define AS5011_CTRL1_INT_WUP_EN 0x10
+#define AS5011_CTRL1_INT_ACT_EN 0x08
+#define AS5011_CTRL1_EXT_CLK_EN 0x04
+#define AS5011_CTRL1_SOFT_RST 0x02
+#define AS5011_CTRL1_DATA_VALID 0x01
+
+/* CTRL2 bits */
+#define AS5011_CTRL2_EXT_SAMPLE_EN 0x08
+#define AS5011_CTRL2_RC_BIAS_ON 0x04
+#define AS5011_CTRL2_INV_SPINNING 0x02
+
+
+struct as5011_platform_data {
+ /* public */
+ int button_gpio;
+ int button_irq;
+ int int_gpio;
+ int int_irq;
+ char Xp, Xn; /* threshold for x axis */
+ char Yp, Yn; /* threshold for y axis */
+
+ int (*init_gpio)(void); /* init interrupts gpios */
+ void (*exit_gpio)(void);/* exit gpios */
+
+ /* private */
+ int num;
+ struct input_dev *input_dev;
+ struct i2c_client *i2c_client;
+ unsigned char *button_irq_name;
+ unsigned char *int_irq_name;
+ char name[AS5011_MAX_NAME_LENGTH];
+ char cname[AS5011_MAX_CNAME_LENGTH];
+ char phys[AS5011_MAX_PHYS_LENGTH];
+ unsigned char data[AS5011_MAX_LENGTH];
+ char workqueue_name[AS5011_MAX_NAME_LENGTH];
+ struct workqueue_struct *workqueue;
+ struct work_struct update_axes_work;
+};
+
+#endif /* _AS5011_H */