-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
input: PCAP2 based touchscreen driver
Touchscreen driver for the PCAP2 multi function device used in Motorola EZX smartphones. Signed-off-by: Daniel Ribeiro <drwyrm@gmail.com> Signed-off-by: Stefan Schmidt <stefan@datenfreihafen.org> Signed-off-by: Antonio Ospite <ospite@studenti.unina.it> Acked-by: Dmitry Torokhov <dtor@mail.ru> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
- Loading branch information
Daniel Ribeiro
authored and
Samuel Ortiz
committed
Sep 17, 2009
1 parent
e397e7e
commit 0387e10
Showing
3 changed files
with
281 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,271 @@ | ||
/* | ||
* Driver for Motorola PCAP2 touchscreen as found in the EZX phone platform. | ||
* | ||
* Copyright (C) 2006 Harald Welte <laforge@openezx.org> | ||
* Copyright (C) 2009 Daniel Ribeiro <drwyrm@gmail.com> | ||
* | ||
* 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/module.h> | ||
#include <linux/init.h> | ||
#include <linux/fs.h> | ||
#include <linux/string.h> | ||
#include <linux/pm.h> | ||
#include <linux/timer.h> | ||
#include <linux/interrupt.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/input.h> | ||
#include <linux/mfd/ezx-pcap.h> | ||
|
||
struct pcap_ts { | ||
struct pcap_chip *pcap; | ||
struct input_dev *input; | ||
struct delayed_work work; | ||
u16 x, y; | ||
u16 pressure; | ||
u8 read_state; | ||
}; | ||
|
||
#define SAMPLE_DELAY 20 /* msecs */ | ||
|
||
#define X_AXIS_MIN 0 | ||
#define X_AXIS_MAX 1023 | ||
#define Y_AXIS_MAX X_AXIS_MAX | ||
#define Y_AXIS_MIN X_AXIS_MIN | ||
#define PRESSURE_MAX X_AXIS_MAX | ||
#define PRESSURE_MIN X_AXIS_MIN | ||
|
||
static void pcap_ts_read_xy(void *data, u16 res[2]) | ||
{ | ||
struct pcap_ts *pcap_ts = data; | ||
|
||
switch (pcap_ts->read_state) { | ||
case PCAP_ADC_TS_M_PRESSURE: | ||
/* pressure reading is unreliable */ | ||
if (res[0] > PRESSURE_MIN && res[0] < PRESSURE_MAX) | ||
pcap_ts->pressure = res[0]; | ||
pcap_ts->read_state = PCAP_ADC_TS_M_XY; | ||
schedule_delayed_work(&pcap_ts->work, 0); | ||
break; | ||
case PCAP_ADC_TS_M_XY: | ||
pcap_ts->y = res[0]; | ||
pcap_ts->x = res[1]; | ||
if (pcap_ts->x <= X_AXIS_MIN || pcap_ts->x >= X_AXIS_MAX || | ||
pcap_ts->y <= Y_AXIS_MIN || pcap_ts->y >= Y_AXIS_MAX) { | ||
/* pen has been released */ | ||
input_report_abs(pcap_ts->input, ABS_PRESSURE, 0); | ||
input_report_key(pcap_ts->input, BTN_TOUCH, 0); | ||
|
||
pcap_ts->read_state = PCAP_ADC_TS_M_STANDBY; | ||
schedule_delayed_work(&pcap_ts->work, 0); | ||
} else { | ||
/* pen is touching the screen */ | ||
input_report_abs(pcap_ts->input, ABS_X, pcap_ts->x); | ||
input_report_abs(pcap_ts->input, ABS_Y, pcap_ts->y); | ||
input_report_key(pcap_ts->input, BTN_TOUCH, 1); | ||
input_report_abs(pcap_ts->input, ABS_PRESSURE, | ||
pcap_ts->pressure); | ||
|
||
/* switch back to pressure read mode */ | ||
pcap_ts->read_state = PCAP_ADC_TS_M_PRESSURE; | ||
schedule_delayed_work(&pcap_ts->work, | ||
msecs_to_jiffies(SAMPLE_DELAY)); | ||
} | ||
input_sync(pcap_ts->input); | ||
break; | ||
default: | ||
dev_warn(&pcap_ts->input->dev, | ||
"pcap_ts: Warning, unhandled read_state %d\n", | ||
pcap_ts->read_state); | ||
break; | ||
} | ||
} | ||
|
||
static void pcap_ts_work(struct work_struct *work) | ||
{ | ||
struct delayed_work *dw = container_of(work, struct delayed_work, work); | ||
struct pcap_ts *pcap_ts = container_of(dw, struct pcap_ts, work); | ||
u8 ch[2]; | ||
|
||
pcap_set_ts_bits(pcap_ts->pcap, | ||
pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT); | ||
|
||
if (pcap_ts->read_state == PCAP_ADC_TS_M_STANDBY) | ||
return; | ||
|
||
/* start adc conversion */ | ||
ch[0] = PCAP_ADC_CH_TS_X1; | ||
ch[1] = PCAP_ADC_CH_TS_Y1; | ||
pcap_adc_async(pcap_ts->pcap, PCAP_ADC_BANK_1, 0, ch, | ||
pcap_ts_read_xy, pcap_ts); | ||
} | ||
|
||
static irqreturn_t pcap_ts_event_touch(int pirq, void *data) | ||
{ | ||
struct pcap_ts *pcap_ts = data; | ||
|
||
if (pcap_ts->read_state == PCAP_ADC_TS_M_STANDBY) { | ||
pcap_ts->read_state = PCAP_ADC_TS_M_PRESSURE; | ||
schedule_delayed_work(&pcap_ts->work, 0); | ||
} | ||
return IRQ_HANDLED; | ||
} | ||
|
||
static int pcap_ts_open(struct input_dev *dev) | ||
{ | ||
struct pcap_ts *pcap_ts = input_get_drvdata(dev); | ||
|
||
pcap_ts->read_state = PCAP_ADC_TS_M_STANDBY; | ||
schedule_delayed_work(&pcap_ts->work, 0); | ||
|
||
return 0; | ||
} | ||
|
||
static void pcap_ts_close(struct input_dev *dev) | ||
{ | ||
struct pcap_ts *pcap_ts = input_get_drvdata(dev); | ||
|
||
cancel_delayed_work_sync(&pcap_ts->work); | ||
|
||
pcap_ts->read_state = PCAP_ADC_TS_M_NONTS; | ||
pcap_set_ts_bits(pcap_ts->pcap, | ||
pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT); | ||
} | ||
|
||
static int __devinit pcap_ts_probe(struct platform_device *pdev) | ||
{ | ||
struct input_dev *input_dev; | ||
struct pcap_ts *pcap_ts; | ||
int err = -ENOMEM; | ||
|
||
pcap_ts = kzalloc(sizeof(*pcap_ts), GFP_KERNEL); | ||
if (!pcap_ts) | ||
return err; | ||
|
||
pcap_ts->pcap = dev_get_drvdata(pdev->dev.parent); | ||
platform_set_drvdata(pdev, pcap_ts); | ||
|
||
input_dev = input_allocate_device(); | ||
if (!input_dev) | ||
goto fail; | ||
|
||
INIT_DELAYED_WORK(&pcap_ts->work, pcap_ts_work); | ||
|
||
pcap_ts->read_state = PCAP_ADC_TS_M_NONTS; | ||
pcap_set_ts_bits(pcap_ts->pcap, | ||
pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT); | ||
|
||
pcap_ts->input = input_dev; | ||
input_set_drvdata(input_dev, pcap_ts); | ||
|
||
input_dev->name = "pcap-touchscreen"; | ||
input_dev->phys = "pcap_ts/input0"; | ||
input_dev->id.bustype = BUS_HOST; | ||
input_dev->id.vendor = 0x0001; | ||
input_dev->id.product = 0x0002; | ||
input_dev->id.version = 0x0100; | ||
input_dev->dev.parent = &pdev->dev; | ||
input_dev->open = pcap_ts_open; | ||
input_dev->close = pcap_ts_close; | ||
|
||
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); | ||
input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); | ||
input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0); | ||
input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0); | ||
input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN, | ||
PRESSURE_MAX, 0, 0); | ||
|
||
err = input_register_device(pcap_ts->input); | ||
if (err) | ||
goto fail_allocate; | ||
|
||
err = request_irq(pcap_to_irq(pcap_ts->pcap, PCAP_IRQ_TS), | ||
pcap_ts_event_touch, 0, "Touch Screen", pcap_ts); | ||
if (err) | ||
goto fail_register; | ||
|
||
return 0; | ||
|
||
fail_register: | ||
input_unregister_device(input_dev); | ||
goto fail; | ||
fail_allocate: | ||
input_free_device(input_dev); | ||
fail: | ||
kfree(pcap_ts); | ||
|
||
return err; | ||
} | ||
|
||
static int __devexit pcap_ts_remove(struct platform_device *pdev) | ||
{ | ||
struct pcap_ts *pcap_ts = platform_get_drvdata(pdev); | ||
|
||
free_irq(pcap_to_irq(pcap_ts->pcap, PCAP_IRQ_TS), pcap_ts); | ||
cancel_delayed_work_sync(&pcap_ts->work); | ||
|
||
input_unregister_device(pcap_ts->input); | ||
|
||
kfree(pcap_ts); | ||
|
||
return 0; | ||
} | ||
|
||
#ifdef CONFIG_PM | ||
static int pcap_ts_suspend(struct device *dev) | ||
{ | ||
struct pcap_ts *pcap_ts = dev_get_drvdata(dev); | ||
|
||
pcap_set_ts_bits(pcap_ts->pcap, PCAP_ADC_TS_REF_LOWPWR); | ||
return 0; | ||
} | ||
|
||
static int pcap_ts_resume(struct device *dev) | ||
{ | ||
struct pcap_ts *pcap_ts = dev_get_drvdata(dev); | ||
|
||
pcap_set_ts_bits(pcap_ts->pcap, | ||
pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT); | ||
return 0; | ||
} | ||
|
||
static struct dev_pm_ops pcap_ts_pm_ops = { | ||
.suspend = pcap_ts_suspend, | ||
.resume = pcap_ts_resume, | ||
}; | ||
#define PCAP_TS_PM_OPS (&pcap_ts_pm_ops) | ||
#else | ||
#define PCAP_TS_PM_OPS NULL | ||
#endif | ||
|
||
static struct platform_driver pcap_ts_driver = { | ||
.probe = pcap_ts_probe, | ||
.remove = __devexit_p(pcap_ts_remove), | ||
.driver = { | ||
.name = "pcap-ts", | ||
.owner = THIS_MODULE, | ||
.pm = PCAP_TS_PM_OPS, | ||
}, | ||
}; | ||
|
||
static int __init pcap_ts_init(void) | ||
{ | ||
return platform_driver_register(&pcap_ts_driver); | ||
} | ||
|
||
static void __exit pcap_ts_exit(void) | ||
{ | ||
platform_driver_unregister(&pcap_ts_driver); | ||
} | ||
|
||
module_init(pcap_ts_init); | ||
module_exit(pcap_ts_exit); | ||
|
||
MODULE_DESCRIPTION("Motorola PCAP2 touchscreen driver"); | ||
MODULE_AUTHOR("Daniel Ribeiro / Harald Welte"); | ||
MODULE_LICENSE("GPL"); | ||
MODULE_ALIAS("platform:pcap_ts"); |