lkml.org 
[lkml]   [2010]   [Dec]   [30]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Date
    From
    Subject[PATCH] input: joystick: Adding Austria Microsystem AS5011 joystick driver (second version)
    Driver for EasyPoint AS5011 2 axis joystick chip. This chip is plugged
    on an I2C bus. It has been tested on ARM processor (i.MX27).

    Interruptions are now managed by threaded irq.

    Signed-off-by: Fabien Marteau <fabien.marteau@armadeus.com>
    ---
    drivers/input/joystick/Kconfig | 9 +
    drivers/input/joystick/Makefile | 1 +
    drivers/input/joystick/as5011.c | 417 +++++++++++++++++++++++++++++++++++++++
    include/linux/as5011.h | 76 +++++++
    4 files changed, 503 insertions(+), 0 deletions(-)
    create mode 100644 drivers/input/joystick/as5011.c
    create mode 100644 include/linux/as5011.h

    diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
    index 5b59616..5fad03e 100644
    --- a/drivers/input/joystick/Kconfig
    +++ b/drivers/input/joystick/Kconfig
    @@ -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
    diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
    index f3a8cbe..92dc0de 100644
    --- a/drivers/input/joystick/Makefile
    +++ b/drivers/input/joystick/Makefile
    @@ -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
    diff --git a/drivers/input/joystick/as5011.c b/drivers/input/joystick/as5011.c
    new file mode 100644
    index 0000000..9f65357
    --- /dev/null
    +++ b/drivers/input/joystick/as5011.c
    @@ -0,0 +1,417 @@
    +/*
    + * 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/delay.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 SIZE_EVENT_PATH 40
    +#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;
    +
    + mutex_lock(&plat_dat->button_lock);
    + 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);
    + mutex_unlock(&plat_dat->button_lock);
    +
    + return IRQ_HANDLED;
    +}
    +
    +static void as5011_update_axes(struct as5011_platform_data *plat_dat)
    +{
    + 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;
    +
    + mutex_lock(&plat_dat->update_lock);
    + as5011_update_axes(plat_dat);
    + mutex_unlock(&plat_dat->update_lock);
    +
    + 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 ret;
    + 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 ret;
    +
    + if (data[0] & 0x80)
    + return -1*(1+(~data[0]));
    + else
    + return data[0];
    +}
    +
    +static int __devinit 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++;
    +
    + 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 err_out;
    + }
    + plat_dat->i2c_client = client;
    +
    +
    + plat_dat->input_dev = input_allocate_device();
    + if (plat_dat->input_dev == NULL) {
    + dev_err(&client->dev,
    + "not enough memory for input devices structure\n");
    + retval = -ENOMEM;
    + goto err_out;
    + }
    +
    + 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 = NULL;
    + 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(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 err_input_free_device;
    + }
    +
    + scnprintf(plat_dat->button_irq_name,
    + SIZE_NAME,
    + "as5011_%d_button",
    + plat_dat->num);
    +
    + retval = request_threaded_irq(plat_dat->button_irq,
    + NULL, button_interrupt,
    + 0, plat_dat->button_irq_name,
    + (void *)plat_dat);
    + if (retval < 0) {
    + dev_err(&client->dev, "Can't allocate irq %d\n",
    + plat_dat->button_irq);
    + retval = -EBUSY;
    + goto err_free_button_irq_name;
    + }
    +
    + if (plat_dat->init_gpio != NULL) {
    + retval = plat_dat->init_gpio();
    + if (retval < 0) {
    + dev_err(&client->dev, "Failed to init gpios\n");
    + goto err_free_irq_button_interrupt;
    + }
    + }
    +
    + /* 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,
    + "Soft reset failed\n");
    + goto err_exit_gpio;
    + }
    +
    + mdelay(10);
    +
    + 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,
    + "Power config failed\n");
    + goto err_exit_gpio;
    + }
    +
    + retval = as5011_i2c_write(plat_dat->i2c_client,
    + AS5011_CTRL2,
    + AS5011_CTRL2_INV_SPINNING);
    + if (retval < 0) {
    + dev_err(&plat_dat->i2c_client->dev,
    + "Can't invert spinning\n");
    + goto err_exit_gpio;
    + }
    +
    + /* 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,
    + "Can't write threshold\n");
    + goto err_exit_gpio;
    + }
    +
    + retval = as5011_i2c_write(plat_dat->i2c_client,
    + AS5011_XN,
    + plat_dat->Xn);
    + if (retval < 0) {
    + dev_err(&plat_dat->i2c_client->dev,
    + "Can't write threshold\n");
    + goto err_exit_gpio;
    + }
    +
    + retval = as5011_i2c_write(plat_dat->i2c_client,
    + AS5011_YP,
    + plat_dat->Yp);
    + if (retval < 0) {
    + dev_err(&plat_dat->i2c_client->dev,
    + "Can't write threshold\n");
    + goto err_exit_gpio;
    + }
    +
    + retval = as5011_i2c_write(plat_dat->i2c_client,
    + AS5011_YN,
    + plat_dat->Yn);
    + if (retval < 0) {
    + dev_err(&plat_dat->i2c_client->dev,
    + "Can't write threshold\n");
    + goto err_exit_gpio;
    + }
    +
    + /* request irq */
    + plat_dat->int_irq_name = kmalloc(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 err_exit_gpio;
    + }
    +
    +
    + /* to free irq gpio in chip*/
    + as5011_i2c_read(plat_dat->i2c_client, AS5011_X_RES_INT);
    +
    + /* initialize mutex */
    + mutex_init(&plat_dat->update_lock);
    + mutex_init(&plat_dat->button_lock);
    +
    + scnprintf(plat_dat->int_irq_name,
    + SIZE_NAME,
    + "as5011_%d_int",
    + plat_dat->num);
    +
    + if (request_threaded_irq(plat_dat->int_irq, NULL,
    + as5011_int_interrupt,
    + 0, plat_dat->int_irq_name, (void *)plat_dat)) {
    + dev_err(&client->dev, "Can't allocate int irq %d\n",
    + plat_dat->int_irq);
    + retval = -EBUSY;
    + goto err_free_int_irq_name;
    + }
    +
    + retval = input_register_device(plat_dat->input_dev);
    + if (retval) {
    + dev_err(&client->dev, "Failed to register device\n");
    + goto err_free_irq_int;
    + }
    +
    + return 0;
    +
    + /* Error management */
    +err_free_irq_int:
    + free_irq(plat_dat->int_irq, as5011_int_interrupt);
    +err_free_int_irq_name:
    + kfree(plat_dat->int_irq_name);
    +err_exit_gpio:
    + if (plat_dat->exit_gpio != NULL)
    + plat_dat->exit_gpio();
    +err_free_irq_button_interrupt:
    + free_irq(plat_dat->button_irq, button_interrupt);
    +err_free_button_irq_name:
    + kfree(plat_dat->button_irq_name);
    +err_input_free_device:
    + input_free_device(plat_dat->input_dev);
    +err_out:
    + return retval;
    +}
    +static int as5011_remove(struct i2c_client *client)
    +{
    + struct as5011_platform_data *plat_dat = client->dev.platform_data;
    +
    + 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);
    + if (plat_dat->exit_gpio != NULL)
    + 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 = __devexit_p(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);
    diff --git a/include/linux/as5011.h b/include/linux/as5011.h
    new file mode 100644
    index 0000000..c224752
    --- /dev/null
    +++ b/include/linux/as5011.h
    @@ -0,0 +1,76 @@
    +#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.
    + */
    +
    +#include <linux/mutex.h>
    +
    +#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;
    + struct mutex update_lock;
    + struct mutex button_lock;
    +};
    +
    +#endif /* _AS5011_H */
    --
    1.7.0.4

    \
     
     \ /
      Last update: 2010-12-30 11:47    [W:0.048 / U:36.340 seconds]
    ©2003-2016 Jasper Spaans. hosted at Digital OceanAdvertise on this site