input: vsense: mbuttons functionality, more /proc control, refactoring
authorGrazvydas Ignotas <notasas@gmail.com>
Mon, 26 Apr 2010 11:24:21 +0000 (14:24 +0300)
committerGrazvydas Ignotas <notasas@gmail.com>
Mon, 26 Apr 2010 11:24:21 +0000 (14:24 +0300)
drivers/input/misc/vsense.c

index 8e1c103..b390f11 100644 (file)
 #include <linux/uaccess.h>
 #include <linux/ctype.h>
 #include <linux/proc_fs.h>
+#include <linux/idr.h>
 #include <linux/i2c/vsense.h>
 #include <linux/gpio.h>
 
+#define VSENSE_INTERVAL                25
+
 #define VSENSE_MODE_ABS                0
 #define VSENSE_MODE_MOUSE      1
 #define VSENSE_MODE_SCROLL     2
+#define VSENSE_MODE_MBUTTONS   3
+
+static DEFINE_IDR(vsense_proc_id);
+static DEFINE_MUTEX(vsense_mutex);
 
 /* Reset state is shared between both nubs, so we keep
  * track of it here.
 static int vsense_reset_state;
 
 struct vsense_drvdata {
+       char dev_name[12];
        struct input_dev *input;
        struct i2c_client *client;
        struct delayed_work work;
        int reset_gpio;
        int irq_gpio;
        int mode;
+       int proc_id;
+       struct proc_dir_entry *proc_root;
+       int mouse_multiplier;   /* 24.8 */
+       int scrollx_multiplier;
+       int scrolly_multiplier;
        int scroll_counter;
-       char dev_name[12];
+       int scroll_steps;
+       int mbutton_threshold;
+       int mbutton_stage;
 };
 
 static void vsense_work(struct work_struct *work)
@@ -46,7 +61,7 @@ static void vsense_work(struct work_struct *work)
        struct vsense_drvdata *ddata;
        int ax = 0, ay = 0, rx = 0, ry = 0;
        signed char buff[4];
-       int ret;
+       int ret, l, r;
 
        ddata = container_of(work, struct vsense_drvdata, work.work);
 
@@ -64,27 +79,46 @@ static void vsense_work(struct work_struct *work)
        ax = (signed int)buff[2];
        ay = (signed int)buff[3];
 
-       schedule_delayed_work(&ddata->work, msecs_to_jiffies(30));
+       schedule_delayed_work(&ddata->work, msecs_to_jiffies(VSENSE_INTERVAL));
 
 dosync:
        switch (ddata->mode) {
        case VSENSE_MODE_MOUSE:
+               rx = rx * ddata->mouse_multiplier / 256;
+               ry = -ry * ddata->mouse_multiplier / 256;
                input_report_rel(ddata->input, REL_X, rx);
-               input_report_rel(ddata->input, REL_Y, -ry);
+               input_report_rel(ddata->input, REL_Y, ry);
                break;
        case VSENSE_MODE_SCROLL:
-               if (ddata->scroll_counter++ % 16 != 0)
+               if (++(ddata->scroll_counter) < ddata->scroll_steps)
                        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;
+               ddata->scroll_counter = 0;
+               ax = ax * ddata->scrollx_multiplier / 256;
+               ay = ay * ddata->scrolly_multiplier / 256;
                input_report_rel(ddata->input, REL_HWHEEL, ax);
-               input_report_rel(ddata->input, REL_WHEEL, -ay);
+               input_report_rel(ddata->input, REL_WHEEL, ay);
+               break;
+       case VSENSE_MODE_MBUTTONS:
+               l = r = 0;
+               if (ax <= -ddata->mbutton_threshold)
+                       l = 1;
+               else if (ax >= ddata->mbutton_threshold)
+                       r = 1;
+               if (ay >= ddata->mbutton_threshold) {
+                       ddata->mbutton_stage++;
+                       l = r = 0;
+                       switch (ddata->mbutton_stage) {
+                       case 1:
+                       case 2:
+                       case 5:
+                       case 6:
+                               l = 1;
+                               break;
+                       }
+               } else
+                       ddata->mbutton_stage = 0;
+               input_report_key(ddata->input, BTN_LEFT, l);
+               input_report_key(ddata->input, BTN_RIGHT, r);
                break;
        default:
                input_report_abs(ddata->input, ABS_X, ax * 8);
@@ -182,7 +216,6 @@ static int vsense_input_register(struct vsense_drvdata *ddata, int mode)
                return ret;
        }
 
-       ddata->mode = mode;
        return 0;
 }
 
@@ -192,7 +225,7 @@ static void vsense_input_unregister(struct vsense_drvdata *ddata)
        input_unregister_device(ddata->input);
 }
 
-static int vsense_proc_read(char *page, char **start, off_t off, int count,
+static int vsense_proc_mode_read(char *page, char **start, off_t off, int count,
                int *eof, void *data)
 {
        struct vsense_drvdata *ddata = data;
@@ -206,6 +239,9 @@ static int vsense_proc_read(char *page, char **start, off_t off, int count,
        case VSENSE_MODE_SCROLL:
                len = sprintf(p, "scroll\n");
                break;
+       case VSENSE_MODE_MBUTTONS:
+               len = sprintf(p, "mbuttons\n");
+               break;
        default:
                len = sprintf(p, "absolute\n");
                break;
@@ -215,7 +251,7 @@ static int vsense_proc_read(char *page, char **start, off_t off, int count,
        return len + 1;
 }
 
-static int vsense_proc_write(struct file *file, const char __user *buffer,
+static int vsense_proc_mode_write(struct file *file, const char __user *buffer,
                unsigned long count, void *data)
 {
        struct vsense_drvdata *ddata = data;
@@ -236,12 +272,17 @@ static int vsense_proc_write(struct file *file, const char __user *buffer,
                mode = VSENSE_MODE_MOUSE;
        else if (strcasecmp(buff, "scroll") == 0)
                mode = VSENSE_MODE_SCROLL;
+       else if (strcasecmp(buff, "mbuttons") == 0)
+               mode = VSENSE_MODE_MBUTTONS;
        else if (strcasecmp(buff, "absolute") == 0)
                mode = VSENSE_MODE_ABS;
-       else
+       else {
                dev_err(&ddata->client->dev, "unknown mode: %s\n", buff);
+               return -EINVAL;
+       }
 
-       if (mode != ddata->mode) {
+       if ((mode == VSENSE_MODE_ABS && ddata->mode != VSENSE_MODE_ABS) ||
+           (mode != VSENSE_MODE_ABS && ddata->mode == VSENSE_MODE_ABS)) {
                disable_irq(ddata->client->irq);
                vsense_input_unregister(ddata);
                ret = vsense_input_register(ddata, mode);
@@ -251,10 +292,106 @@ static int vsense_proc_write(struct file *file, const char __user *buffer,
                else
                        enable_irq(ddata->client->irq);
        }
+       ddata->mode = mode;
+
+       return count;
+}
+
+static int vsense_proc_int_read(char *page, char **start, off_t off,
+               int count, int *eof, void *data)
+{
+       int *val = data;
+       int len;
+
+       len = sprintf(page, "%d\n", *val);
+       *eof = 1;
+       return len + 1;
+}
+
+static int vsense_proc_int_write(const char __user *buffer,
+               unsigned long count, int *value)
+{
+       char buff[32];
+       long val;
+       int ret;
+
+       count = strncpy_from_user(buff, buffer,
+                       count < sizeof(buff) ? count : sizeof(buff) - 1);
+       buff[count] = 0;
 
+       ret = strict_strtol(buff, 0, &val);
+       if (ret < 0)
+               return ret;
+       *value = val;
        return count;
 }
 
+static int vsense_proc_mult_read(char *page, char **start, off_t off,
+               int count, int *eof, void *data)
+{
+       int *multiplier = data;
+       int val = *multiplier * 100 / 256;
+       return vsense_proc_int_read(page, start, off, count, eof, &val);
+}
+
+static int vsense_proc_mult_write(struct file *file, const char __user *buffer,
+               unsigned long count, void *data)
+{
+       int *multiplier = data;
+       int ret, val;
+
+       ret = vsense_proc_int_write(buffer, count, &val);
+       if (ret < 0)
+               return ret;
+       if (val == 0)
+               return -EINVAL;
+
+       *multiplier = val * 256 / 100;
+       return ret;
+}
+
+static int vsense_proc_rate_read(char *page, char **start, off_t off,
+               int count, int *eof, void *data)
+{
+       int *steps = data;
+       int val = 1000 / VSENSE_INTERVAL / *steps;
+       return vsense_proc_int_read(page, start, off, count, eof, &val);
+}
+
+static int vsense_proc_rate_write(struct file *file, const char __user *buffer,
+               unsigned long count, void *data)
+{
+       int *steps = data;
+       int ret, val;
+
+       ret = vsense_proc_int_write(buffer, count, &val);
+       if (ret < 0)
+               return ret;
+       if (val < 1)
+               return -EINVAL;
+
+       *steps = 1000 / VSENSE_INTERVAL / val;
+       if (*steps < 1)
+               *steps = 1;
+       return ret;
+}
+
+static int vsense_proc_treshold_write(struct file *file, const char __user *buffer,
+               unsigned long count, void *data)
+{
+       int *value = data;
+       int ret, val;
+
+       ret = vsense_proc_int_write(buffer, count, &val);
+       if (ret < 0)
+               return ret;
+       if (val < 1 || val > 32)
+               return -EINVAL;
+
+       *value = val;
+       return ret;
+}
+
 static ssize_t
 vsense_show_reset(struct device *dev, struct device_attribute *attr, char *buf)
 {
@@ -285,12 +422,28 @@ vsense_set_reset(struct device *dev, struct device_attribute *attr,
 static DEVICE_ATTR(reset, S_IRUGO | S_IWUSR,
        vsense_show_reset, vsense_set_reset);
 
+static void vsense_create_proc(struct vsense_drvdata *ddata,
+                              void *pdata, const char *name,
+                              read_proc_t *read_proc, write_proc_t *write_proc)
+{
+       struct proc_dir_entry *pret;
+       
+       pret = create_proc_entry(name, S_IWUGO | S_IRUGO, ddata->proc_root);
+       if (pret == NULL) {
+               dev_err(&ddata->client->dev, "failed to create proc file %s\n", name);
+               return;
+       }
+
+       pret->data = pdata;
+       pret->read_proc = read_proc;
+       pret->write_proc = write_proc;
+}
+
 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 proc_dir_entry *pret;
        char buff[32];
        int ret;
 
@@ -308,36 +461,53 @@ static int vsense_probe(struct i2c_client *client,
        if (ddata == NULL)
                return -ENOMEM;
 
-       snprintf(ddata->dev_name, sizeof(ddata->dev_name),
-               "vsense%02x", client->addr);
+       ret = idr_pre_get(&vsense_proc_id, GFP_KERNEL);
+       if (ret == 0) {
+               ret = -ENOMEM;
+               goto fail0;
+       }
+
+       mutex_lock(&vsense_mutex);
+       ret = idr_get_new(&vsense_proc_id, client, &ddata->proc_id);
+       mutex_unlock(&vsense_mutex);
+       if (ret < 0)
+               goto fail0;
 
        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 fail0;
+               goto fail1;
        }
 
        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 fail1;
+               goto fail2;
        }
 
        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 fail1;
+               goto fail2;
        }
        client->irq = ret;
 
+       snprintf(ddata->dev_name, sizeof(ddata->dev_name),
+                "nub%d", ddata->proc_id);
+
        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;
+       ddata->mouse_multiplier = 170 * 256 / 100;
+       ddata->scrollx_multiplier =
+       ddata->scrolly_multiplier = 8 * 256 / 100;
+       ddata->scroll_steps = 1000 / VSENSE_INTERVAL / 3;
+       ddata->mbutton_threshold = 20;
        i2c_set_clientdata(client, ddata);
 
        vsense_reset_state = 1;
@@ -347,7 +517,7 @@ static int vsense_probe(struct i2c_client *client,
        if (ret) {
                dev_err(&client->dev, "failed to register input device, "
                        "error %d\n", ret);
-               goto fail1;
+               goto fail2;
        }
 
        ret = request_irq(client->irq, vsense_isr,
@@ -356,31 +526,45 @@ static int vsense_probe(struct i2c_client *client,
        if (ret) {
                dev_err(&client->dev, "unable to claim irq %d, error %d\n",
                        client->irq, ret);
-               goto fail1;
+               goto fail3;
        }
 
        dev_dbg(&client->dev, "probe %02x, gpio %i, irq %i, \"%s\"\n",
                client->addr, pdata->gpio_irq, client->irq, client->name);
 
-       snprintf(buff, sizeof(buff), "pandora/vsense%02x", client->addr);
-       pret = create_proc_entry(buff, S_IWUGO | S_IRUGO, NULL);
-       if (pret == NULL) {
+       snprintf(buff, sizeof(buff), "pandora/nub%d", ddata->proc_id);
+       ddata->proc_root = proc_mkdir(buff, NULL);
+       if (ddata->proc_root == 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->proc_root = proc_mkdir(buff, NULL);
        }
 
-       ret = device_create_file(&client->dev, &dev_attr_reset);
+       if (ddata->proc_root != NULL) {
+               vsense_create_proc(ddata, ddata, "mode",
+                               vsense_proc_mode_read, vsense_proc_mode_write);
+               vsense_create_proc(ddata, &ddata->mouse_multiplier, "mouse_sensitivity",
+                               vsense_proc_mult_read, vsense_proc_mult_write);
+               vsense_create_proc(ddata, &ddata->scrollx_multiplier, "scrollx_sensitivity",
+                               vsense_proc_mult_read, vsense_proc_mult_write);
+               vsense_create_proc(ddata, &ddata->scrolly_multiplier, "scrolly_sensitivity",
+                               vsense_proc_mult_read, vsense_proc_mult_write);
+               vsense_create_proc(ddata, &ddata->scroll_steps, "scroll_rate",
+                               vsense_proc_rate_read, vsense_proc_rate_write);
+               vsense_create_proc(ddata, &ddata->mbutton_threshold, "mbutton_threshold",
+                               vsense_proc_int_read, vsense_proc_treshold_write);
+       } else
+               dev_err(&client->dev, "can't create proc dir");
 
-       pret->data = ddata;
-       pret->read_proc = vsense_proc_read;
-       pret->write_proc = vsense_proc_write;
+       ret = device_create_file(&client->dev, &dev_attr_reset);
 
        return 0;
 
-fail1:
+fail3:
+       vsense_input_unregister(ddata);
+fail2:
        gpio_free(pdata->gpio_irq);
+fail1:
+       idr_remove(&vsense_proc_id, ddata->proc_id);
 fail0:
        kfree(ddata);
        return ret;
@@ -399,8 +583,17 @@ static int __devexit vsense_remove(struct i2c_client *client)
        vsense_reset(ddata, 1);
 
        device_remove_file(&client->dev, &dev_attr_reset);
-       snprintf(buff, sizeof(buff), "pandora/vsense%02x", client->addr);
+
+       remove_proc_entry("mode", ddata->proc_root);
+       remove_proc_entry("mouse_sensitivity", ddata->proc_root);
+       remove_proc_entry("scrollx_sensitivity", ddata->proc_root);
+       remove_proc_entry("scrolly_sensitivity", ddata->proc_root);
+       remove_proc_entry("scroll_rate", ddata->proc_root);
+       remove_proc_entry("mbutton_threshold", ddata->proc_root);
+       snprintf(buff, sizeof(buff), "pandora/nub%d", ddata->proc_id);
        remove_proc_entry(buff, NULL);
+       idr_remove(&vsense_proc_id, ddata->proc_id);
+
        free_irq(client->irq, ddata);
        vsense_input_unregister(ddata);
        gpio_free(ddata->irq_gpio);