vsense: add new modes of operation
authorGrazvydas Ignotas <notasas@gmail.com>
Wed, 4 Feb 2009 20:57:58 +0000 (22:57 +0200)
committerGrazvydas Ignotas <notasas@gmail.com>
Wed, 4 Feb 2009 20:57:58 +0000 (22:57 +0200)
This allows the device to act as mouse or scrolling device.
Mode can be switched by writing to special file in
/proc/pandora ("absolute", "mouse" and "scroll").

drivers/input/misc/vsense.c

index 4cbc548..db32b52 100644 (file)
 #include <linux/input.h>
 #include <linux/interrupt.h>
 #include <linux/timer.h>
+#include <linux/uaccess.h>
+#include <linux/ctype.h>
+#include <linux/proc_fs.h>
 #include <linux/i2c/vsense.h>
 #include <linux/gpio.h>
 
+#define VSENSE_MODE_ABS                0
+#define VSENSE_MODE_MOUSE      1
+#define VSENSE_MODE_SCROLL     2
+
 /* hack for Pandora: keep track of usage to prevent reset
  * while other nub is in use
  */
@@ -29,14 +36,17 @@ struct vsense_drvdata {
        struct delayed_work work;
        int reset_gpio;
        int irq_gpio;
+       int mode;
+       int scroll_counter;
        char dev_name[12];
 };
 
 static void vsense_work(struct work_struct *work)
 {
        struct vsense_drvdata *ddata;
+       int ax = 0, ay = 0, rx = 0, ry = 0;
        signed char buff[8];
-       int ret, x = 0, y = 0;
+       int ret;
 
        ddata = container_of(work, struct vsense_drvdata, work.work);
 
@@ -49,14 +59,38 @@ static void vsense_work(struct work_struct *work)
                goto dosync;
        }
 
-       x = (signed int)buff[2] * 8;
-       y = (signed int)buff[3] * 8;
+       rx = (signed int)buff[0];
+       ry = (signed int)buff[1];
+       ax = (signed int)buff[2];
+       ay = (signed int)buff[3];
 
        schedule_delayed_work(&ddata->work, msecs_to_jiffies(25));
 
 dosync:
-       input_report_abs(ddata->input, ABS_X, x);
-       input_report_abs(ddata->input, ABS_Y, y);
+       switch (ddata->mode) {
+       case VSENSE_MODE_MOUSE:
+               input_report_rel(ddata->input, REL_X, rx);
+               input_report_rel(ddata->input, REL_Y, -ry);
+               break;
+       case VSENSE_MODE_SCROLL:
+               if (ddata->scroll_counter++ % 16 != 0)
+                       return;
+               if (ax < 0)
+                       ax = ax < -8 ? ax / 8 : -1;
+               else if (ax > 0)
+                       ax = ax >  8 ? ax / 8 :  1;
+               if (ay < 0)
+                       ay = ay < -8 ? ay / 8 : -1;
+               else if (ay > 0)
+                       ay = ay >  8 ? ay / 8 :  1;
+               input_report_rel(ddata->input, REL_HWHEEL, ax);
+               input_report_rel(ddata->input, REL_WHEEL, -ay);
+               break;
+       default:
+               input_report_abs(ddata->input, ABS_X, ax * 8);
+               input_report_abs(ddata->input, ABS_Y, ay * 8);
+               break;
+       }
        input_sync(ddata->input);
 }
 
@@ -114,12 +148,130 @@ static void vsense_close(struct input_dev *dev)
        BUG_ON(reference_count < 0);
 }
 
+static int vsense_input_register(struct vsense_drvdata *ddata, int mode)
+{
+       struct input_dev *input;
+       int ret;
+
+       input = input_allocate_device();
+       if (input == NULL)
+               return -ENOMEM;
+
+       if (mode != VSENSE_MODE_ABS) {
+               /* pretend to be a mouse */
+               input_set_capability(input, EV_REL, REL_X);
+               input_set_capability(input, EV_REL, REL_Y);
+               input_set_capability(input, EV_REL, REL_WHEEL);
+               input_set_capability(input, EV_REL, REL_HWHEEL);
+               /* add fake buttons to fool X that this is a mouse */
+               input_set_capability(input, EV_KEY, BTN_LEFT);
+               input_set_capability(input, EV_KEY, BTN_RIGHT);
+       } else {
+               input->evbit[BIT_WORD(EV_ABS)] = BIT_MASK(EV_ABS);
+               input_set_abs_params(input, ABS_X, -256, 256, 0, 0);
+               input_set_abs_params(input, ABS_Y, -256, 256, 0, 0);
+       }
+
+       input->name = ddata->dev_name;
+       input->dev.parent = &ddata->client->dev;
+
+       input->id.bustype = BUS_I2C;
+       input->id.version = 0x0091;
+
+       input->open = vsense_open;
+       input->close = vsense_close;
+
+       ddata->input = input;
+       input_set_drvdata(input, ddata);
+
+       ret = input_register_device(input);
+       if (ret) {
+               dev_err(&ddata->client->dev, "failed to register input device,"
+                       " error %d\n", ret);
+               input_free_device(input);
+               return ret;
+       }
+
+       ddata->mode = mode;
+       return 0;
+}
+
+static void vsense_input_unregister(struct vsense_drvdata *ddata)
+{
+       cancel_delayed_work_sync(&ddata->work);
+       input_unregister_device(ddata->input);
+}
+
+static int vsense_proc_read(char *page, char **start, off_t off, int count,
+               int *eof, void *data)
+{
+       struct vsense_drvdata *ddata = data;
+       char *p = page;
+       int len;
+
+       switch (ddata->mode) {
+       case VSENSE_MODE_MOUSE:
+               len = sprintf(p, "mouse\n");
+               break;
+       case VSENSE_MODE_SCROLL:
+               len = sprintf(p, "scroll\n");
+               break;
+       default:
+               len = sprintf(p, "absolute\n");
+               break;
+       }
+
+       *eof = 1;
+       return len + 1;
+}
+
+static int vsense_proc_write(struct file *file, const char __user *buffer,
+               unsigned long count, void *data)
+{
+       struct vsense_drvdata *ddata = data;
+       int mode = ddata->mode;
+       char buff[32], *p;
+       int ret;
+
+       count = strncpy_from_user(buff, buffer,
+                       count < sizeof(buff) ? count : sizeof(buff) - 1);
+       buff[count] = 0;
+
+       p = buff + strlen(buff) - 1;
+       while (p > buff && isspace(*p))
+               p--;
+       p[1] = 0;
+
+       if (strcasecmp(buff, "mouse") == 0)
+               mode = VSENSE_MODE_MOUSE;
+       else if (strcasecmp(buff, "scroll") == 0)
+               mode = VSENSE_MODE_SCROLL;
+       else if (strcasecmp(buff, "absolute") == 0)
+               mode = VSENSE_MODE_ABS;
+       else
+               dev_err(&ddata->client->dev, "unknown mode: %s\n", buff);
+
+       if (mode != ddata->mode) {
+               disable_irq(ddata->client->irq);
+               vsense_input_unregister(ddata);
+               ret = vsense_input_register(ddata, mode);
+               if (ret)
+                       dev_err(&ddata->client->dev, "failed to re-register "
+                               "input as %d, code %d\n", mode, ret);
+               else
+                       enable_irq(ddata->client->irq);
+       }
+
+       return count;
+}
+
 static int vsense_probe(struct i2c_client *client,
                         const struct i2c_device_id *id)
 {
        struct vsense_platform_data *pdata = client->dev.platform_data;
        struct vsense_drvdata *ddata;
-       struct input_dev *input;
+       struct proc_dir_entry *pret;
+       char buff[32];
        int ret;
 
        if (pdata == NULL) {
@@ -132,106 +284,96 @@ static int vsense_probe(struct i2c_client *client,
                return -EIO;
        }
 
-       input = input_allocate_device();
-       if (input == NULL)
-               return -ENOMEM;
-
        ddata = kzalloc(sizeof(struct vsense_drvdata), GFP_KERNEL);
-       if (ddata == NULL) {
-               ret = -ENOMEM;
-               goto fail1;
-       }
+       if (ddata == NULL)
+               return -ENOMEM;
 
        snprintf(ddata->dev_name, sizeof(ddata->dev_name),
                "vsense%02x", client->addr);
 
-       input->evbit[0] = BIT_MASK(EV_ABS);
-       input_set_abs_params(input, ABS_X, -256, 256, 0, 0);
-       input_set_abs_params(input, ABS_Y, -256, 256, 0, 0);
-
-       input->name = ddata->dev_name;
-       input->dev.parent = &client->dev;
-
-       input->id.bustype = BUS_I2C;
-       input->id.version = 0x0091;
-
-       input->open = vsense_open;
-       input->close = vsense_close;
-
        ret = gpio_request(pdata->gpio_irq, client->name);
        if (ret < 0) {
                dev_err(&client->dev, "failed to request GPIO %d,"
                        " error %d\n", pdata->gpio_irq, ret);
-               goto fail2;
+               goto fail0;
        }
 
        ret = gpio_direction_input(pdata->gpio_irq);
        if (ret < 0) {
                dev_err(&client->dev, "failed to configure input direction "
                        "for GPIO %d, error %d\n", pdata->gpio_irq, ret);
-               goto fail3;
+               goto fail1;
        }
 
        ret = gpio_to_irq(pdata->gpio_irq);
        if (ret < 0) {
                dev_err(&client->dev, "unable to get irq number for GPIO %d, "
                        "error %d\n", pdata->gpio_irq, ret);
-               goto fail3;
+               goto fail1;
        }
        client->irq = ret;
 
+       INIT_DELAYED_WORK(&ddata->work, vsense_work);
+       ddata->mode = VSENSE_MODE_ABS;
+       ddata->client = client;
+       ddata->reset_gpio = pdata->gpio_reset;
+       ddata->irq_gpio = pdata->gpio_irq;
+       i2c_set_clientdata(client, ddata);
+
+       ret = vsense_input_register(ddata, ddata->mode);
+       if (ret) {
+               dev_err(&client->dev, "failed to register input device, "
+                       "error %d\n", ret);
+               goto fail1;
+       }
+
        ret = request_irq(client->irq, vsense_isr,
                        IRQF_SAMPLE_RANDOM | IRQF_TRIGGER_FALLING,
                        client->name, ddata);
        if (ret) {
                dev_err(&client->dev, "unable to claim irq %d, error %d\n",
                        client->irq, ret);
-               goto fail3;
+               goto fail1;
        }
 
-       INIT_DELAYED_WORK(&ddata->work, vsense_work);
+       dev_dbg(&client->dev, "probe %02x, gpio %i, irq %i, \"%s\"\n",
+               client->addr, pdata->gpio_irq, client->irq, client->name);
 
-       ret = input_register_device(input);
-       if (ret) {
-               dev_err(&client->dev, "failed to register input device, "
-                       "error %d\n", ret);
-               goto fail4;
+       snprintf(buff, sizeof(buff), "pandora/vsense%02x", client->addr);
+       pret = create_proc_entry(buff, S_IWUGO | S_IRUGO, NULL);
+       if (pret == NULL) {
+               proc_mkdir("pandora", NULL);
+               pret = create_proc_entry(buff, S_IWUSR | S_IRUGO, NULL);
+               if (pret == NULL)
+                       dev_err(&client->dev, "can't create proc file");
        }
 
-       ddata->input = input;
-       ddata->client = client;
-       ddata->reset_gpio = pdata->gpio_reset;
-       ddata->irq_gpio = pdata->gpio_irq;
-       i2c_set_clientdata(client, ddata);
-       input_set_drvdata(input, ddata);
-
-       dev_dbg(&client->dev, "probe %02x, gpio %i, irq %i, \"%s\"\n",
-               client->addr, pdata->gpio_irq, client->irq, client->name);
+       pret->data = ddata;
+       pret->read_proc = vsense_proc_read;
+       pret->write_proc = vsense_proc_write;
 
        return 0;
 
-fail4:
-       free_irq(client->irq, ddata);
-fail3:
+fail1:
        gpio_free(pdata->gpio_irq);
-fail2:
+fail0:
        kfree(ddata);
-fail1:
-       input_free_device(input);
        return ret;
 }
 
 static int __devexit vsense_remove(struct i2c_client *client)
 {
        struct vsense_drvdata *ddata;
+       char buff[32];
 
        dev_dbg(&client->dev, "remove\n");
 
        ddata = i2c_get_clientdata(client);
 
+       snprintf(buff, sizeof(buff), "pandora/vsense%02x", client->addr);
+       remove_proc_entry(buff, NULL);
        free_irq(client->irq, ddata);
-       cancel_delayed_work_sync(&ddata->work);
-       input_unregister_device(ddata->input);
+       vsense_input_unregister(ddata);
        gpio_free(ddata->irq_gpio);
        kfree(ddata);