Touchscreen driver based on the MFD PCAP available on the EZX mobile phones. Signed-off-by: Daniel Ribeiro <wyrm@xxxxxxxxxxx> PATCH FOLLOWS KernelVersion: 2.6-arm-git pxa branch Index: linux-2.6-arm/drivers/input/touchscreen/Kconfig =================================================================== --- linux-2.6-arm.orig/drivers/input/touchscreen/Kconfig +++ linux-2.6-arm/drivers/input/touchscreen/Kconfig @@ -316,4 +316,13 @@ bool "GoTop Super_Q2/GogoPen/PenPower tablet device support" if EMBEDDED depends on TOUCHSCREEN_USB_COMPOSITE +config TOUCHSCREEN_PCAP + tristate "Motorola PCAP touchscreen" + depends on EZX_PCAP + help + Say Y here if you have a Motorola EZX telephone and + want to support the built-in touchscreen. + + If unsure, say N. + endif Index: linux-2.6-arm/drivers/input/touchscreen/pcap_ts.c =================================================================== --- /dev/null +++ linux-2.6-arm/drivers/input/touchscreen/pcap_ts.c @@ -0,0 +1,316 @@ +/* + * pcap_ts.c - Touchscreen driver for Motorola PCAP2 based touchscreen as found + * in the EZX phone platform. + * + * Copyright (C) 2006 Harald Welte <laforge@xxxxxxxxxxx> + * Copyright (C) 2007 Daniel Ribeiro <wyrm@xxxxxxxxxxx> + * + * Based on information found in the original Motorola 2.4.x ezx-ts.c driver. + * + * 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> + +#include <asm/arch/hardware.h> +#include <asm/arch/pxa-regs.h> + +/* PCAP_ADC_TS_M modes */ +#define POSITION_X_MEASUREMENT 0 +#define POSITION_XY_MEASUREMENT 1 +#define PRESSURE_MEASUREMENT 2 +#define PLATE_X_MEASUREMENT 3 +#define PLATE_Y_MEASUREMENT 4 +#define STANDBY_MODE 5 +#define NONTS_MODE 6 + +struct pcap_ts { + int irq_xy; + int irq_touch; + struct input_dev *input; + struct timer_list timer; + u_int16_t x, y; + u_int16_t pressure; + u_int8_t read_state; +}; + +#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 + +/* if we try to read faster, pressure reading becomes unreliable */ +#define SAMPLE_INTERVAL (HZ/50) + + +static void pcap_ts_mode(struct pcap_ts *pcap_ts, u_int32_t mode) +{ + u_int32_t tmp; + + pcap_ts->read_state = mode; + ezx_pcap_read(PCAP_REG_ADC, &tmp); + tmp &= ~PCAP_ADC_TS_M_MASK; + tmp |= ((mode << PCAP_ADC_TS_M_SHIFT) & PCAP_ADC_TS_M_MASK); + ezx_pcap_write(PCAP_REG_ADC, tmp); +} + +/* issue a XY read command to the ADC of PCAP2. Well get an ADCDONE interrupt + * once the result of the conversion is available */ +static void pcap_ts_start_xy_read(struct pcap_ts *pcap_ts) +{ + u_int32_t tmp; + + ezx_pcap_read(PCAP_REG_ADC, &tmp); + tmp &= ~(PCAP_ADC_RAND | PCAP_ADC_ADA1_MASK | PCAP_ADC_ADA2_MASK); + tmp |= (PCAP_ADC_ADEN | PCAP_ADC_AD_SEL1 | + PCAP_ADC_AD_SEL2 | (5 << PCAP_ADC_ADA1_SHIFT) | + (3 << PCAP_ADC_ADA2_SHIFT)); + ezx_pcap_write(PCAP_REG_ADC, tmp); + + ezx_pcap_write(PCAP_REG_ADR, PCAP_ADR_ASC); +} + +/* read the XY result from the ADC of PCAP2 */ +static void pcap_ts_get_xy_value(struct pcap_ts *pcap_ts) +{ + u_int32_t tmp; + + ezx_pcap_read(PCAP_REG_ADR, &tmp); + + if (pcap_ts->read_state == POSITION_XY_MEASUREMENT) { + pcap_ts->x = (tmp & PCAP_ADR_ADD1_MASK) >> + PCAP_ADR_ADD1_SHIFT; + pcap_ts->y = (tmp & PCAP_ADR_ADD2_MASK) >> + PCAP_ADR_ADD2_SHIFT; + } else { + pcap_ts->pressure = (tmp & PCAP_ADR_ADD2_MASK) >> + PCAP_ADR_ADD2_SHIFT; + } +} + +/* PCAP2 interrupts us when ADC conversion result is available */ +static irqreturn_t pcap_ts_irq_xy(int irq, void *dev_id) +{ + struct pcap_ts *pcap_ts = dev_id; + + pcap_ts_get_xy_value(pcap_ts); + switch (pcap_ts->read_state) { + case PRESSURE_MEASUREMENT: + if (!(pcap_ts->pressure >= PRESSURE_MAX || + pcap_ts->pressure <= PRESSURE_MIN)) { + /* pen has been touched down */ + input_report_key(pcap_ts->input, BTN_TOUCH, 1); + input_report_abs(pcap_ts->input, ABS_PRESSURE, + pcap_ts->pressure); + } + /* switch state machine into coordinate read mode */ + pcap_ts_mode(pcap_ts, POSITION_XY_MEASUREMENT); + pcap_ts_start_xy_read(pcap_ts); + break; + case POSITION_XY_MEASUREMENT: + 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_key(pcap_ts->input, BTN_TOUCH, 0); + input_report_abs(pcap_ts->input, ABS_PRESSURE, 0); + + /* no need for timer, we'll get interrupted with + * next touch down event */ + del_timer(&pcap_ts->timer); + + /* ask PCAP2 to interrupt us if touch event happens + * again */ + pcap_ts_mode(pcap_ts, STANDBY_MODE); + enable_irq(pcap_ts->irq_touch); + } else { + input_report_abs(pcap_ts->input, ABS_X, pcap_ts->x); + input_report_abs(pcap_ts->input, ABS_Y, pcap_ts->y); + + /* switch back to pressure read mode */ + pcap_ts_mode(pcap_ts, PRESSURE_MEASUREMENT); + mod_timer(&pcap_ts->timer, jiffies + SAMPLE_INTERVAL); + } + input_sync(pcap_ts->input); + break; + default: + break; + } + return IRQ_HANDLED; +} + +/* PCAP2 interrupts us on pen down/up */ +static irqreturn_t pcap_ts_irq_touch(int irq, void *dev_id) +{ + struct pcap_ts *pcap_ts = dev_id; + /* mask Touchscreen interrupt bit, prevents further touch events + * from being reported to us until we're finished with reading + * both pressure and x/y from ADC */ + disable_irq(pcap_ts->irq_touch); + + pcap_ts_mode(pcap_ts, PRESSURE_MEASUREMENT); + pcap_ts_start_xy_read(pcap_ts); + return IRQ_HANDLED; +} + +static void pcap_ts_timer_fn(unsigned long data) +{ + struct pcap_ts *pcap_ts = (struct pcap_ts *) data; + + pcap_ts_start_xy_read(pcap_ts); +} + +static int __init ezxts_probe(struct platform_device *pdev) +{ + struct pcap_ts *pcap_ts; + struct input_dev *input_dev; + int err = -ENOMEM; + + pcap_ts = kzalloc(sizeof(*pcap_ts), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!pcap_ts || !input_dev) + goto fail; + + pcap_ts->irq_xy = platform_get_irq(pdev, 0); + if (pcap_ts->irq_xy < 0) { + err = pcap_ts->irq_xy; + goto fail; + } + + pcap_ts->irq_touch = platform_get_irq(pdev, 1); + if (pcap_ts->irq_touch < 0) { + err = pcap_ts->irq_touch; + goto fail; + } + + ezx_pcap_write(PCAP_REG_ADC, 0); + ezx_pcap_write(PCAP_REG_ADR, 0); + pcap_ts_mode(pcap_ts, STANDBY_MODE); + + err = request_irq(pcap_ts->irq_xy, pcap_ts_irq_xy, IRQF_DISABLED, + "pcap-ts X/Y", pcap_ts); + if (err < 0) { + printk(KERN_ERR "pcap_ts: can't grab xy irq %d: %d\n", + pcap_ts->irq_xy, err); + goto fail; + } + + err = request_irq(pcap_ts->irq_touch, pcap_ts_irq_touch, IRQF_DISABLED, + "pcap-ts touch", pcap_ts); + if (err < 0) { + printk(KERN_ERR "pcap_ts: can't grab touch irq %d: %d\n", + pcap_ts->irq_touch, err); + goto fail_xy; + } + + pcap_ts->input = input_dev; + init_timer(&pcap_ts->timer); + pcap_ts->timer.data = (unsigned long) pcap_ts; + pcap_ts->timer.function = &pcap_ts_timer_fn; + + platform_set_drvdata(pdev, pcap_ts); + + input_dev->name = "pcap-touchscreen"; + input_dev->phys = "ezxts/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->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); + + input_register_device(pcap_ts->input); + + return 0; + +fail_xy: + free_irq(pcap_ts->irq_xy, pcap_ts); +fail: + input_free_device(input_dev); + kfree(pcap_ts); + + return err; +} + +static int ezxts_remove(struct platform_device *pdev) +{ + struct pcap_ts *pcap_ts = platform_get_drvdata(pdev); + + del_timer_sync(&pcap_ts->timer); + + free_irq(pcap_ts->irq_touch, pcap_ts); + free_irq(pcap_ts->irq_xy, pcap_ts); + + input_unregister_device(pcap_ts->input); + kfree(pcap_ts); + + return 0; +} + +static int ezxts_suspend(struct platform_device *dev, pm_message_t state) +{ + u_int32_t tmp; + ezx_pcap_read(PCAP_REG_ADC, &tmp); + tmp |= PCAP_ADC_TS_REF_LOWPWR; + ezx_pcap_write(PCAP_REG_ADC, tmp); + return 0; +} + +static int ezxts_resume(struct platform_device *dev) +{ + u_int32_t tmp; + ezx_pcap_read(PCAP_REG_ADC, &tmp); + tmp &= ~PCAP_ADC_TS_REF_LOWPWR; + ezx_pcap_write(PCAP_REG_ADC, tmp); + return 0; +} + + +static struct platform_driver ezxts_driver = { + .probe = ezxts_probe, + .remove = ezxts_remove, + .suspend = ezxts_suspend, + .resume = ezxts_resume, + .driver = { + .name = "pcap-ts", + .owner = THIS_MODULE, + }, +}; + +static int __init ezxts_init(void) +{ + return platform_driver_register(&ezxts_driver); +} + +static void __exit ezxts_exit(void) +{ + platform_driver_unregister(&ezxts_driver); +} + +module_init(ezxts_init); +module_exit(ezxts_exit); + +MODULE_DESCRIPTION("Motorola PCAP2 touchscreen driver"); +MODULE_AUTHOR("Harald Welte <laforge@xxxxxxxxxxx>"); +MODULE_LICENSE("GPL"); Index: linux-2.6-arm/drivers/input/touchscreen/Makefile =================================================================== --- linux-2.6-arm.orig/drivers/input/touchscreen/Makefile +++ linux-2.6-arm/drivers/input/touchscreen/Makefile @@ -26,3 +26,4 @@ wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712) += wm9712.o wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9713) += wm9713.o obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o +obj-$(CONFIG_TOUCHSCREEN_PCAP) += pcap_ts.o Index: linux-2.6-arm/arch/arm/mach-pxa/ezx.c =================================================================== --- linux-2.6-arm.orig/arch/arm/mach-pxa/ezx.c +++ linux-2.6-arm/arch/arm/mach-pxa/ezx.c @@ -244,6 +244,30 @@ .setpower = ezx_mci_setpower, .exit = ezx_mci_exit, }; +/* PCAP_TS */ +struct resource pcap_ts_resources[] = { + [0] = { + .start = EZX_IRQ_ADCDONE, + .end = EZX_IRQ_ADCDONE, + .flags = IORESOURCE_IRQ, + }, + [1] = { + .start = EZX_IRQ_TS, + .end = EZX_IRQ_TS, + .flags = IORESOURCE_IRQ, + } +}; + +struct platform_device pcap_ts_device = { + .name = "pcap-ts", + .id = -1, + .dev = { + .parent = &ezx_pcap_device.dev, + }, + .num_resources = ARRAY_SIZE(pcap_ts_resources), + .resource = pcap_ts_resources, +}; + static struct platform_device *devices[] __initdata = { &ezx_backlight_device, @@ -294,6 +318,14 @@ else set_pxa_fb_info(&ezx_fb_info_2); platform_device_register(&ezx_pcap_device); + /* A780 and E680 AP is connected to PCAP port 2 */ + if (machine_is_ezx_a780() || machine_is_ezx_e680()) { + pcap_ts_resources[0].start = EZX_IRQ_ADCDONE2; + pcap_ts_resources[0].end = EZX_IRQ_ADCDONE2; + } + /* A910 and E2 dont have a touchscreen */ + if (!(machine_is_ezx_a910() || machine_is_ezx_e2())) + platform_device_register(&pcap_ts_device); pxa_set_mci_info(&ezx_mci_platform_data); platform_add_devices(devices, ARRAY_SIZE(devices)); } -- -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html