Merge branch 'x86-spinlocks-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[pandora-kernel.git] / drivers / input / touchscreen / tsc2007.c
index fadc115..1f674cb 100644 (file)
@@ -66,7 +66,6 @@ struct ts_event {
 struct tsc2007 {
        struct input_dev        *input;
        char                    phys[32];
-       struct delayed_work     work;
 
        struct i2c_client       *client;
 
@@ -76,9 +75,11 @@ struct tsc2007 {
        unsigned long           poll_delay;
        unsigned long           poll_period;
 
-       bool                    pendown;
        int                     irq;
 
+       wait_queue_head_t       wait;
+       bool                    stopped;
+
        int                     (*get_pendown_state)(void);
        void                    (*clear_penirq)(void);
 };
@@ -141,25 +142,8 @@ static u32 tsc2007_calculate_pressure(struct tsc2007 *tsc, struct ts_event *tc)
        return rt;
 }
 
-static void tsc2007_send_up_event(struct tsc2007 *tsc)
+static bool tsc2007_is_pen_down(struct tsc2007 *ts)
 {
-       struct input_dev *input = tsc->input;
-
-       dev_dbg(&tsc->client->dev, "UP\n");
-
-       input_report_key(input, BTN_TOUCH, 0);
-       input_report_abs(input, ABS_PRESSURE, 0);
-       input_sync(input);
-}
-
-static void tsc2007_work(struct work_struct *work)
-{
-       struct tsc2007 *ts =
-               container_of(to_delayed_work(work), struct tsc2007, work);
-       bool debounced = false;
-       struct ts_event tc;
-       u32 rt;
-
        /*
         * NOTE: We can't rely on the pressure to determine the pen down
         * state, even though this controller has a pressure sensor.
@@ -170,79 +154,82 @@ static void tsc2007_work(struct work_struct *work)
         * The only safe way to check for the pen up condition is in the
         * work function by reading the pen signal state (it's a GPIO
         * and IRQ). Unfortunately such callback is not always available,
-        * in that case we have rely on the pressure anyway.
+        * in that case we assume that the pen is down and expect caller
+        * to fall back on the pressure reading.
         */
-       if (ts->get_pendown_state) {
-               if (unlikely(!ts->get_pendown_state())) {
-                       tsc2007_send_up_event(ts);
-                       ts->pendown = false;
-                       goto out;
-               }
 
-               dev_dbg(&ts->client->dev, "pen is still down\n");
-       }
+       if (!ts->get_pendown_state)
+               return true;
+
+       return ts->get_pendown_state();
+}
+
+static irqreturn_t tsc2007_soft_irq(int irq, void *handle)
+{
+       struct tsc2007 *ts = handle;
+       struct input_dev *input = ts->input;
+       struct ts_event tc;
+       u32 rt;
 
-       tsc2007_read_values(ts, &tc);
+       while (!ts->stopped && tsc2007_is_pen_down(ts)) {
 
-       rt = tsc2007_calculate_pressure(ts, &tc);
-       if (rt > ts->max_rt) {
-               /*
-                * Sample found inconsistent by debouncing or pressure is
-                * beyond the maximum. Don't report it to user space,
-                * repeat at least once more the measurement.
-                */
-               dev_dbg(&ts->client->dev, "ignored pressure %d\n", rt);
-               debounced = true;
-               goto out;
+               /* pen is down, continue with the measurement */
+               tsc2007_read_values(ts, &tc);
 
-       }
+               rt = tsc2007_calculate_pressure(ts, &tc);
 
-       if (rt) {
-               struct input_dev *input = ts->input;
+               if (rt == 0 && !ts->get_pendown_state) {
+                       /*
+                        * If pressure reported is 0 and we don't have
+                        * callback to check pendown state, we have to
+                        * assume that pen was lifted up.
+                        */
+                       break;
+               }
 
-               if (!ts->pendown) {
-                       dev_dbg(&ts->client->dev, "DOWN\n");
+               if (rt <= ts->max_rt) {
+                       dev_dbg(&ts->client->dev,
+                               "DOWN point(%4d,%4d), pressure (%4u)\n",
+                               tc.x, tc.y, rt);
 
                        input_report_key(input, BTN_TOUCH, 1);
-                       ts->pendown = true;
+                       input_report_abs(input, ABS_X, tc.x);
+                       input_report_abs(input, ABS_Y, tc.y);
+                       input_report_abs(input, ABS_PRESSURE, rt);
+
+                       input_sync(input);
+
+               } else {
+                       /*
+                        * Sample found inconsistent by debouncing or pressure is
+                        * beyond the maximum. Don't report it to user space,
+                        * repeat at least once more the measurement.
+                        */
+                       dev_dbg(&ts->client->dev, "ignored pressure %d\n", rt);
                }
 
-               input_report_abs(input, ABS_X, tc.x);
-               input_report_abs(input, ABS_Y, tc.y);
-               input_report_abs(input, ABS_PRESSURE, rt);
+               wait_event_timeout(ts->wait, ts->stopped,
+                                  msecs_to_jiffies(ts->poll_period));
+       }
 
-               input_sync(input);
+       dev_dbg(&ts->client->dev, "UP\n");
 
-               dev_dbg(&ts->client->dev, "point(%4d,%4d), pressure (%4u)\n",
-                       tc.x, tc.y, rt);
+       input_report_key(input, BTN_TOUCH, 0);
+       input_report_abs(input, ABS_PRESSURE, 0);
+       input_sync(input);
 
-       } else if (!ts->get_pendown_state && ts->pendown) {
-               /*
-                * We don't have callback to check pendown state, so we
-                * have to assume that since pressure reported is 0 the
-                * pen was lifted up.
-                */
-               tsc2007_send_up_event(ts);
-               ts->pendown = false;
-       }
+       if (ts->clear_penirq)
+               ts->clear_penirq();
 
- out:
-       if (ts->pendown || debounced)
-               schedule_delayed_work(&ts->work,
-                                     msecs_to_jiffies(ts->poll_period));
-       else
-               enable_irq(ts->irq);
+       return IRQ_HANDLED;
 }
 
-static irqreturn_t tsc2007_irq(int irq, void *handle)
+static irqreturn_t tsc2007_hard_irq(int irq, void *handle)
 {
        struct tsc2007 *ts = handle;
 
-       if (!ts->get_pendown_state || likely(ts->get_pendown_state())) {
-               disable_irq_nosync(ts->irq);
-               schedule_delayed_work(&ts->work,
-                                     msecs_to_jiffies(ts->poll_delay));
-       }
+       if (!ts->get_pendown_state || likely(ts->get_pendown_state()))
+               return IRQ_WAKE_THREAD;
 
        if (ts->clear_penirq)
                ts->clear_penirq();
@@ -250,17 +237,40 @@ static irqreturn_t tsc2007_irq(int irq, void *handle)
        return IRQ_HANDLED;
 }
 
-static void tsc2007_free_irq(struct tsc2007 *ts)
+static void tsc2007_stop(struct tsc2007 *ts)
 {
-       free_irq(ts->irq, ts);
-       if (cancel_delayed_work_sync(&ts->work)) {
-               /*
-                * Work was pending, therefore we need to enable
-                * IRQ here to balance the disable_irq() done in the
-                * interrupt handler.
-                */
-               enable_irq(ts->irq);
+       ts->stopped = true;
+       mb();
+       wake_up(&ts->wait);
+
+       disable_irq(ts->irq);
+}
+
+static int tsc2007_open(struct input_dev *input_dev)
+{
+       struct tsc2007 *ts = input_get_drvdata(input_dev);
+       int err;
+
+       ts->stopped = false;
+       mb();
+
+       enable_irq(ts->irq);
+
+       /* Prepare for touch readings - power down ADC and enable PENIRQ */
+       err = tsc2007_xfer(ts, PWRDOWN);
+       if (err < 0) {
+               tsc2007_stop(ts);
+               return err;
        }
+
+       return 0;
+}
+
+static void tsc2007_close(struct input_dev *input_dev)
+{
+       struct tsc2007 *ts = input_get_drvdata(input_dev);
+
+       tsc2007_stop(ts);
 }
 
 static int __devinit tsc2007_probe(struct i2c_client *client,
@@ -290,7 +300,7 @@ static int __devinit tsc2007_probe(struct i2c_client *client,
        ts->client = client;
        ts->irq = client->irq;
        ts->input = input_dev;
-       INIT_DELAYED_WORK(&ts->work, tsc2007_work);
+       init_waitqueue_head(&ts->wait);
 
        ts->model             = pdata->model;
        ts->x_plate_ohms      = pdata->x_plate_ohms;
@@ -300,6 +310,12 @@ static int __devinit tsc2007_probe(struct i2c_client *client,
        ts->get_pendown_state = pdata->get_pendown_state;
        ts->clear_penirq      = pdata->clear_penirq;
 
+       if (pdata->x_plate_ohms == 0) {
+               dev_err(&client->dev, "x_plate_ohms is not set up in platform data");
+               err = -EINVAL;
+               goto err_free_mem;
+       }
+
        snprintf(ts->phys, sizeof(ts->phys),
                 "%s/input0", dev_name(&client->dev));
 
@@ -307,6 +323,11 @@ static int __devinit tsc2007_probe(struct i2c_client *client,
        input_dev->phys = ts->phys;
        input_dev->id.bustype = BUS_I2C;
 
+       input_dev->open = tsc2007_open;
+       input_dev->close = tsc2007_close;
+
+       input_set_drvdata(input_dev, ts);
+
        input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
        input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
 
@@ -318,17 +339,14 @@ static int __devinit tsc2007_probe(struct i2c_client *client,
        if (pdata->init_platform_hw)
                pdata->init_platform_hw();
 
-       err = request_irq(ts->irq, tsc2007_irq, 0,
-                       client->dev.driver->name, ts);
+       err = request_threaded_irq(ts->irq, tsc2007_hard_irq, tsc2007_soft_irq,
+                                  IRQF_ONESHOT, client->dev.driver->name, ts);
        if (err < 0) {
                dev_err(&client->dev, "irq %d busy?\n", ts->irq);
                goto err_free_mem;
        }
 
-       /* Prepare for touch readings - power down ADC and enable PENIRQ */
-       err = tsc2007_xfer(ts, PWRDOWN);
-       if (err < 0)
-               goto err_free_irq;
+       tsc2007_stop(ts);
 
        err = input_register_device(input_dev);
        if (err)
@@ -339,7 +357,7 @@ static int __devinit tsc2007_probe(struct i2c_client *client,
        return 0;
 
  err_free_irq:
-       tsc2007_free_irq(ts);
+       free_irq(ts->irq, ts);
        if (pdata->exit_platform_hw)
                pdata->exit_platform_hw();
  err_free_mem:
@@ -353,7 +371,7 @@ static int __devexit tsc2007_remove(struct i2c_client *client)
        struct tsc2007  *ts = i2c_get_clientdata(client);
        struct tsc2007_platform_data *pdata = client->dev.platform_data;
 
-       tsc2007_free_irq(ts);
+       free_irq(ts->irq, ts);
 
        if (pdata->exit_platform_hw)
                pdata->exit_platform_hw();