From: <hol...@us...> - 2006-11-28 19:59:21
|
Revision: 684 http://svn.sourceforge.net/hackndev/?rev=684&view=rev Author: holger_bocklet Date: 2006-11-28 11:59:12 -0800 (Tue, 28 Nov 2006) Log Message: ----------- t|c touchscreen driver and updates for mach-pxa/palmtc Modified Paths: -------------- linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/Kconfig linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/Makefile linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/palmtc.c linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/palmtc_keyboard.c Added Paths: ----------- linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/palmtc-gpio.h linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/ptc_ucb1400.h linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/ptc_ucb1400_ts.c Modified: linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/Kconfig =================================================================== --- linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/Kconfig 2006-11-28 11:35:37 UTC (rev 683) +++ linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/Kconfig 2006-11-28 19:59:12 UTC (rev 684) @@ -13,3 +13,11 @@ This driver translates button presses on a Palm Tungsten C to Linux input subsystem events +config PALMTC_UCB1400_TS + tristate "Palm Tungsten C ucb1400 touchscreen support" + depends on MACH_OMAP_PALMTC + default y + help + This driver handles the UCB1400 AC97 codec, + which is used to support the touchscreen + Modified: linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/Makefile =================================================================== --- linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/Makefile 2006-11-28 11:35:37 UTC (rev 683) +++ linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/Makefile 2006-11-28 19:59:12 UTC (rev 684) @@ -4,3 +4,5 @@ obj-$(CONFIG_MACH_OMAP_PALMTC) += palmtc.o obj-$(CONFIG_PALMTC_KEYBOARD) += palmtc_keyboard.o +obj-$(CONFIG_PALMTC_AC97) += palmtc_ac97.o +obj-$(CONFIG_PALMTC_UCB1400_TS) += ptc_ucb1400_ts.o Added: linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/palmtc-gpio.h =================================================================== --- linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/palmtc-gpio.h (rev 0) +++ linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/palmtc-gpio.h 2006-11-28 19:59:12 UTC (rev 684) @@ -0,0 +1,26 @@ +/* + * palmtc-gpio.h + * + * Authors: Holger Bocklet <bit...@gm...> + * + */ + +#ifndef _PALMTC_GPIO_H_ +#define _PALMTC_GPIO_H_ + +#include <asm/arch/pxa-regs.h> + +/* Palm Tungsten C GPIOs */ +#define GPIO_NR_PALMTC_EARPHONE_DETECT 2 +#define GPIO_NR_PALMLD_USB_DETECT 4 +#define GPIO_NR_PALMLD_POWER_DETECT 5 +#define GPIO_NR_PALMTC_HOTSYNC_BUTTON 7 +#define GPIO_NR_PALMTC_SD_DETECT 12 // low->high wenn out, high->low when inserted +#define GPIO_NR_PALMLD_BL_POWER 16 + +//#define GPIO_NR_PALMLD_STD_RXD 46 /* IRDA */ +//#define GPIO_NR_PALMLD_STD_TXD 47 + +#define IRQ_GPIO_PALMTC_SD_DETECT IRQ_GPIO(GPIO_NR_PALMTC_SD_DETECT) + +#endif /* _PALMTC_GPIO_H_ */ Modified: linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/palmtc.c =================================================================== --- linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/palmtc.c 2006-11-28 11:35:37 UTC (rev 683) +++ linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/palmtc.c 2006-11-28 19:59:12 UTC (rev 684) @@ -13,6 +13,7 @@ #include <asm/arch/hardware.h> #include <asm/mach/map.h> #include <asm/domain.h> +#include <asm/arch/mmc.h> #include <linux/device.h> #include <linux/platform_device.h> @@ -23,6 +24,7 @@ #include <asm/arch/pxa-regs.h> #include "../generic.h" +#include "palmtc-gpio.h" #define DEBUG @@ -57,10 +59,57 @@ }; + +static int palmtc_mci_init(struct device *dev, irqreturn_t (*palmtc_detect_int)(int, void *, struct pt_regs *), void *data) +{ + int err; + + /** + * Setup an interrupt for detecting card insert/remove events + */ + set_irq_type(IRQ_GPIO_PALMTC_SD_DETECT, IRQT_BOTHEDGE); + err = request_irq(IRQ_GPIO_PALMTC_SD_DETECT, palmtc_detect_int, + SA_INTERRUPT, "SD/MMC card detect", data); + + if(err) { + printk(KERN_ERR "palmld_mci_init: cannot request SD/MMC card detect IRQ\n"); + return -1; + } + + + printk("palmtc_mci_init: irq registered\n"); + + return 0; +} -static struct platform_device palmtc_audio_device = { +static void palmtc_mci_exit(struct device *dev, void *data) +{ + free_irq(IRQ_GPIO_PALMTC_SD_DETECT, data); +} + +static struct pxamci_platform_data palmtc_mci_platform_data = { + .ocr_mask = MMC_VDD_32_33|MMC_VDD_33_34, + .init = palmtc_mci_init, + /* .setpower = palmld_mci_setpower, */ + .exit = palmtc_mci_exit, + +}; + + +static pxa2xx_audio_ops_t palmtc_audio_ops = { + /* + .startup = palmld_audio_startup, + .shutdown = mst_audio_shutdown, + .suspend = mst_audio_suspend, + .resume = mst_audio_resume, + */ +}; + + +static struct platform_device palmtc_ac97_device = { .name = "pxa2xx-ac97", .id = -1, + .dev = { .platform_data = &palmtc_audio_ops }, }; static struct platform_device palmtc_keyboard_device = { @@ -70,7 +119,7 @@ static struct platform_device *devices[] __initdata = { - &palmtc_audio_device, + &palmtc_ac97_device, &palmtc_keyboard_device, }; @@ -78,13 +127,18 @@ { set_pxa_fb_info(&palmtclcd); GCR &= ~GCR_PRIRDY_IEN; + + pxa_set_mci_info( &palmtc_mci_platform_data ); + platform_add_devices (devices, ARRAY_SIZE (devices)); + } MACHINE_START(OMAP_PALMTC, "Palm Tungsten C") /* Maintainer: P3T3, Petr Blaha <p3...@ce...> */ - .phys_ram = 0xa0000000, +// .phys_ram = 0xa0000000, .phys_io = 0x40000000, + .boot_params = 0xa0000100, .io_pg_offst = (io_p2v(0x40000000) >> 18) & 0xfffc, .map_io = pxa_map_io, .init_irq = pxa_init_irq, Modified: linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/palmtc_keyboard.c =================================================================== --- linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/palmtc_keyboard.c 2006-11-28 11:35:37 UTC (rev 683) +++ linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/palmtc_keyboard.c 2006-11-28 19:59:12 UTC (rev 684) @@ -109,10 +109,10 @@ {KEY_D,KEY_KPPLUS,FALSE,0,"D"}, {KEY_H,KEY_MINUS,SHIFT_BIT|FALSE,0,"H/_"}, {KEY_DOT,KEY_SEMICOLON,SHIFT_BIT|FALSE,0,"./:"} }, -/*22*/ {{KEY_A,KEY_2,SHIFT_BIT|FALSE,0,"A/@"}, +/*22*/ {{KEY_A,KEY_EQUAL,FALSE,0,"A/@"}, // changed to "=" {KEY_C,KEY_0,SHIFT_BIT|FALSE,0,"C/)"}, {KEY_Z,KEY_6,FALSE,0,"de:Z us:Y"}, - {KEY_L,KEY_EQUAL,FALSE,0,"L/\xDF"} }, // Keyoard shows "\xDF" but better use "=" + {KEY_L,KEY_COMMA,SHIFT_BIT|FALSE,0,"L/\xDF"} }, // Keyoard shows "\xDF" but better use "<" /*23*/ {{KEY_Q,KEY_1,FALSE,0,"Q"}, //0 {KEY_F,KEY_KPMINUS,FALSE,0,"F"}, //9 {KEY_N,KEY_COMMA,FALSE,0,"N"}, //10 @@ -128,7 +128,7 @@ /*26*/ {{KEY_S,KEY_APOSTROPHE,SHIFT_BIT|FALSE,0,"S"}, {KEY_V,KEY_3,SHIFT_BIT|FALSE,0,"V/#"}, {KEY_M,KEY_SLASH,SHIFT_BIT|FALSE,0,"M/?"}, - {KEY_BACKSPACE,KEY_GRAVE,SHIFT_BIT|FALSE,0,"bs/~"} }, + {KEY_BACKSPACE,KEY_DOT,SHIFT_BIT|FALSE,0,"bs/>"} }, /*27*/ {{KEY_W,KEY_2,FALSE,0,"W"}, {KEY_G,KEY_KPASTERISK,FALSE,0,"G"}, {KEY_K,KEY_APOSTROPHE,FALSE,0,"K"}, @@ -310,7 +310,7 @@ { int row, col; unsigned long ret; - DBG("Probing device\n" ); + //DBG("Probing device\n" ); keyboard_dev = input_allocate_device(); @@ -346,6 +346,7 @@ SA_SAMPLE_RANDOM,"palmtc-kbd", (void*)col_gpio[col]); set_irq_type(IRQ_GPIO(col_gpio[col]), IRQT_BOTHEDGE); } + DBG("device enabled\n"); return 0; } Added: linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/ptc_ucb1400.h =================================================================== --- linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/ptc_ucb1400.h (rev 0) +++ linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/ptc_ucb1400.h 2006-11-28 19:59:12 UTC (rev 684) @@ -0,0 +1,153 @@ +/* + * linux/drivers/mfd/ucb1400.h + * + * Copyright (C) 2001 Russell King, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License. + */ + +/* ucb1400 aclink register mappings: */ + +#define UCB_IO_DATA 0x5a +#define UCB_IO_DIR 0x5c +#define UCB_IE_RIS 0x5e +#define UCB_IE_FAL 0x60 +#define UCB_IE_STATUS 0x62 +#define UCB_IE_CLEAR 0x62 +#define UCB_TS_CR 0x64 +#define UCB_ADC_CR 0x66 +#define UCB_ADC_DATA 0x68 +#define UCB_CSR1 0x6a +#define UCB_CSR2 0x6c + +#define UCB_CS_GPIO_AC97_INT (1 << 2) + +#define UCB_ADC_DAT(x) ((x) & 0x3ff) + +#define UCB_IO_0 (1 << 0) +#define UCB_IO_1 (1 << 1) +#define UCB_IO_2 (1 << 2) +#define UCB_IO_3 (1 << 3) +#define UCB_IO_4 (1 << 4) +#define UCB_IO_5 (1 << 5) +#define UCB_IO_6 (1 << 6) +#define UCB_IO_7 (1 << 7) +#define UCB_IO_8 (1 << 8) +#define UCB_IO_9 (1 << 9) + +#define UCB_IE_ADC (1 << 11) +#define UCB_IE_TSPX (1 << 12) +#define UCB_IE_TSMX (1 << 13) +#define UCB_IE_TCLIP (1 << 14) +#define UCB_IE_ACLIP (1 << 15) + +#define UCB_TC_B_VOICE_ENA (1 << 3) +#define UCB_TC_B_CLIP (1 << 4) +#define UCB_TC_B_ATT (1 << 6) +#define UCB_TC_B_SIDE_ENA (1 << 11) +#define UCB_TC_B_MUTE (1 << 13) +#define UCB_TC_B_IN_ENA (1 << 14) +#define UCB_TC_B_OUT_ENA (1 << 15) + +#define UCB_AC_B_LOOP (1 << 8) +#define UCB_AC_B_MUTE (1 << 13) +#define UCB_AC_B_IN_ENA (1 << 14) +#define UCB_AC_B_OUT_ENA (1 << 15) + +#define UCB_TS_CR_TSMX_POW (1 << 0) +#define UCB_TS_CR_TSPX_POW (1 << 1) +#define UCB_TS_CR_TSMY_POW (1 << 2) +#define UCB_TS_CR_TSPY_POW (1 << 3) +#define UCB_TS_CR_TSMX_GND (1 << 4) +#define UCB_TS_CR_TSPX_GND (1 << 5) +#define UCB_TS_CR_TSMY_GND (1 << 6) +#define UCB_TS_CR_TSPY_GND (1 << 7) +#define UCB_TS_CR_MODE_INT (0 << 8) +#define UCB_TS_CR_MODE_PRES (1 << 8) +#define UCB_TS_CR_MODE_POS (2 << 8) +#define UCB_TS_CR_BIAS_ENA (1 << 11) +#define UCB_TS_CR_TSPX (1 << 12) +#define UCB_TS_CR_TSMX (1 << 13) + +#define UCB_ADC_SYNC_ENA (1 << 0) +#define UCB_ADC_VREFBYP_CON (1 << 1) +#define UCB_ADC_INP_TSPX (0 << 2) +#define UCB_ADC_INP_TSMX (1 << 2) +#define UCB_ADC_INP_TSPY (2 << 2) +#define UCB_ADC_INP_TSMY (3 << 2) +#define UCB_ADC_INP_AD0 (4 << 2) +#define UCB_ADC_INP_AD1 (5 << 2) +#define UCB_ADC_INP_AD2 (6 << 2) +#define UCB_ADC_INP_AD3 (7 << 2) +#define UCB_ADC_EXT_REF (1 << 5) +#define UCB_ADC_START (1 << 7) +#define UCB_ADC_ENA (1 << 15) + +#define UCB_ADC_DATA_MASK 0x3ff +#define UCB_ADC_DATA_VALID (1 << 15) + +#define UCB_ID_1400 0x4304 + +#define UCB_MODE 0x0d +#define UCB_MODE_DYN_VFLAG_ENA (1 << 12) +#define UCB_MODE_AUD_OFF_CAN (1 << 13) + +#define UCB_NOSYNC 0 +#define UCB_SYNC 1 + +#define UCB_RISING (1 << 0) +#define UCB_FALLING (1 << 1) + +#define UCB_TS_X_MIN 110 +#define UCB_TS_X_MAX 860 +#define UCB_TS_Y_MIN 280 +#define UCB_TS_Y_MAX 960 +#define UCB_TS_PRESSURE_MIN 0 +#define UCB_TS_PRESSURE_MAX 740 + + +struct ucb1400_ts { + struct device *dev; + ac97_t *ac97; + struct input_dev *idev; + //spinlock_t lock; + //unsigned int irq; + struct task_struct *thread; + //struct completion irq_wait; + wait_queue_head_t pen_irq_wait; /* Pen IRQ wait queue */ + //struct semaphore ucb_ts_sem; + //u16 id; + //u8 pen_is_down; + int irq_in_work; + //struct ucb1400_ts_irq irq_handler; + struct completion thr_init; + struct completion thr_exit; + u8 last_status; + //unsigned int adcsync:UCB_NOSYNC; +}; + + +//#define classdev_to_ucb1400(cd) container_of(cd, struct ucb1400, cdev) + +//int ucb1400_register_driver(struct ucb1400_driver *); +//void ucb1400_unregister_driver(struct ucb1400_driver *); + + +//void ucb1400_io_set_dir(struct ucb1400 *ucb, unsigned int, unsigned int); +//void ucb1400_io_write(struct ucb1400 *ucb, unsigned int, unsigned int); +//unsigned int ucb1400_io_read(struct ucb1400 *ucb); + +//unsigned int ucb1400_adc_read(struct ucb1400 *ucb, int adc_channel, int sync); +//void ucb1400_adc_enable(struct ucb1400 *ucb); +//void ucb1400_adc_disable(struct ucb1400 *ucb); + +/* + * Which edges of the IRQ do you want to control today? + */ + +//int ucb1400_hook_irq(struct ucb1400 *ucb, unsigned int idx, void (*fn)(int, void *), void *devid); +//void ucb1400_enable_irq(struct ucb1400 *ucb, unsigned int idx, int edges); +//void ucb1400_disable_irq(struct ucb1400 *ucb, unsigned int idx, int edges); +//int ucb1400_free_irq(struct ucb1400 *ucb, unsigned int idx, void *devid); Added: linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/ptc_ucb1400_ts.c =================================================================== --- linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/ptc_ucb1400_ts.c (rev 0) +++ linux4palm/linux/trunk/arch/arm/mach-pxa/palmtc/ptc_ucb1400_ts.c 2006-11-28 19:59:12 UTC (rev 684) @@ -0,0 +1,569 @@ +/* + * linux/arch/arm/mach-pxa/palmtc/ptc_ucb1400_ts.c + * + * Touchscreendriver for Palm Tungsten C AC97 codec ucb1400 + * + * Author: Holger Bocklet <bit...@gm...> + * + * There's noting new under the sun, + * So this peace of software ist derived of + * - the ucb1x00-ts by Russell King, Pavel Machek and + * - patches for the ucb1400 by Nicols Pitre and + * - the wm97xx-driver by Liam Girdwood + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/jiffies.h> +#include <linux/device.h> +#include <linux/workqueue.h> +#include <linux/moduleparam.h> +#include <linux/wait.h> + +#include <asm/delay.h> +#include <asm/system.h> +#include <asm/arch/hardware.h> +#include <asm/arch/pxa-regs.h> +#include <asm/arch/irqs.h> + +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include <asm/mach/map.h> + +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/ac97_codec.h> + +#include <linux/config.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/kthread.h> +#include <linux/device.h> +#include <linux/mutex.h> + +#include "ptc_ucb1400.h" + +#define PALMTC_TS_DEBUG + +#ifdef PALMTC_TS_DEBUG +#define DBG(x...) \ + printk(KERN_INFO "PTC_TS: " x) +#else +#define DBG(x...) do { } while (0) +#endif + +#define TRUE 1 +#define FALSE 0 + +struct ts_reading { + u16 p; + u16 x; + u16 y; +}; + +//DECLARE_MUTEX(battery_update_mutex); + +// Pen sampling frequency (Hz) while touched. +// more is not possible cause jiffies are at 100Hz in mach-pxa (why ???) +static int cont_rate = 100; +module_param(cont_rate, int, 0); +MODULE_PARM_DESC(cont_rate, "Sampling rate while pen down (Hz). Not more than ~100Hz on T|C"); + +/* codec AC97 IO access */ +int ucb1400_reg_read(struct ucb1400_ts *ucb_ts, u16 reg) +{ + if (ucb_ts->ac97) + return ucb_ts->ac97->bus->ops->read(ucb_ts->ac97, reg); + else + return -1; +} + +void ucb1400_reg_write(struct ucb1400_ts *ucb_ts, u16 reg, u16 val) +{ + if (ucb_ts->ac97) + ucb_ts->ac97->bus->ops->write(ucb_ts->ac97, reg, val); +} + +/** + * ucb1x00_adc_enable - enable the ADC converter + * @ucb: UCB1x00 structure describing chip + * + * Enable the ucb1x00 and ADC converter on the UCB1x00 for use. + * Any code wishing to use the ADC converter must call this + * function prior to using it. + * FIXME: this (see below) will be later + * This function takes the ADC semaphore to prevent two or more + * concurrent uses, and therefore may sleep. As a result, it + * can only be called from process context, not interrupt + * context. + * + * You should release the ADC as soon as possible using + * ucb1x00_adc_disable. + */ +void ucb1400_adc_enable(struct ucb1400_ts *ucb_ts) +{ + //down(&ucb->adc_sem); + ucb1400_reg_write(ucb_ts, UCB_ADC_CR, UCB_ADC_ENA); +} + + +/** + * ucb1x00_adc_disable - disable the ADC converter + * @ucb: UCB1x00 structure describing chip + * + * Disable the ADC converter and release the ADC semaphore. + */ +void ucb1400_adc_disable(struct ucb1400_ts *ucb_ts) +{ + u16 val=0; + val &= ~UCB_ADC_ENA; + ucb1400_reg_write(ucb_ts, UCB_ADC_CR, val); + //up(&ucb->adc_sem); +} + + +/** + * ucb1x00_adc_read - read the specified ADC channel + * @ucb: UCB1x00 structure describing chip + * @adc_channel: ADC channel mask + * @sync: wait for syncronisation pulse. + * + * Start an ADC conversion and wait for the result. + * This function currently sleeps waiting for the conversion to + * complete (2 frames max without sync). + */ +unsigned int ucb1400_adc_read(struct ucb1400_ts *ucb_ts, int adc_channel) +{ + unsigned int val; + + adc_channel |= UCB_ADC_ENA; + + ucb1400_reg_write(ucb_ts, UCB_ADC_CR, adc_channel); + ucb1400_reg_write(ucb_ts, UCB_ADC_CR, adc_channel | UCB_ADC_START); + + for (;;) { // TODO: use interrupt mode + val = ucb1400_reg_read(ucb_ts, UCB_ADC_DATA); + if (val & UCB_ADC_DATA_VALID) + break; + /* yield to other processes */ + set_current_state(TASK_INTERRUPTIBLE); //without sync its ~ 22 usec = 1 jiffie ???? + schedule_timeout(3); // try 1 + } + + if (val & UCB_ADC_DATA_VALID) { + return val & UCB_ADC_DATA_MASK; + } else { + return 0; + } +} + + +static inline void ucb1400_ts_report_event(struct ucb1400_ts *ucb_ts, struct ts_reading ts) +{ + input_report_abs(ucb_ts->idev, ABS_X, ts.x); + input_report_abs(ucb_ts->idev, ABS_Y, ts.y); + input_report_abs(ucb_ts->idev, ABS_PRESSURE, ts.p); + input_sync(ucb_ts->idev); +} + + +static inline void ucb1400_ts_event_pen_up(struct ucb1400_ts *ucb_ts) +{ + input_report_abs(ucb_ts->idev, ABS_PRESSURE, 0); + input_sync(ucb_ts->idev); +} + + +/* + * Switch touchscreen to given interrupt mode. +* param = UCB_IE_RIS || UCB_IE_FAL switch ts-mod to int on edge in param +* param != 0 leave edge as it is +* param == 0 switch ts - int off + */ +static inline void ucb1400_ts_mode_int(struct ucb1400_ts *ucb_ts, u16 param) +{ + u16 val; + + // clear remaining ints + val=ucb1400_reg_read(ucb_ts, UCB_IE_STATUS); + ucb1400_reg_write(ucb_ts, UCB_IE_CLEAR, val); + + if (param==0) { // switch off int + val=ucb1400_reg_read(ucb_ts, UCB_IE_FAL); + + ucb1400_reg_write(ucb_ts, UCB_TS_CR, + UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND ); + + val=ucb1400_reg_read(ucb_ts, UCB_IE_FAL); // edge-detection off + ucb1400_reg_write(ucb_ts, UCB_IE_FAL, val & (~UCB_IE_TSPX) & (~UCB_IE_TSMX)); + val=ucb1400_reg_read(ucb_ts, UCB_IE_FAL); + //DBG("ucb1400_intmode_OFF: 0x%x\n",val); + ucb1400_reg_write(ucb_ts, UCB_IE_RIS, val & (~UCB_IE_TSPX) & (~UCB_IE_TSMX)); + + } else { + + switch(param) { + case (UCB_IE_RIS): + val=ucb1400_reg_read(ucb_ts, UCB_IE_RIS); + ucb1400_reg_write(ucb_ts, UCB_IE_RIS, val | UCB_IE_TSPX); + val=ucb1400_reg_read(ucb_ts, UCB_IE_FAL); + ucb1400_reg_write(ucb_ts, UCB_IE_FAL, val & (~UCB_IE_TSPX) ); + //DBG(" ucb1400_intmode_Ris\n"); + break; + case (UCB_IE_FAL): + val=ucb1400_reg_read(ucb_ts, UCB_IE_FAL); + ucb1400_reg_write(ucb_ts, UCB_IE_FAL, val | UCB_IE_TSPX); + val=ucb1400_reg_read(ucb_ts, UCB_IE_RIS); + ucb1400_reg_write(ucb_ts, UCB_IE_RIS, val & (~UCB_IE_TSPX) ); + //DBG(" ucb1400_intmode_Fal\n"); + break; + } + ucb1400_reg_write(ucb_ts, UCB_TS_CR, + UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | + UCB_TS_CR_MODE_INT); + } +} + + +/* + * Switch to pressure mode, and read pressure. We don't need to wait + * here, since both plates are being driven. + */ +static inline unsigned int ucb1400_ts_read_pressure(struct ucb1400_ts *ucb_ts) +{ + ucb1400_reg_write(ucb_ts, UCB_TS_CR, + UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + //udelay(55); + + return ucb1400_adc_read(ucb_ts, UCB_ADC_INP_TSPY); +} + +/* + * Switch to X position mode and measure Y plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline unsigned int ucb1400_ts_read_xpos(struct ucb1400_ts *ucb_ts) +{ + ucb1400_reg_write(ucb_ts, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1400_reg_write(ucb_ts, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + + ucb1400_reg_write(ucb_ts, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + udelay(55); + + return ucb1400_adc_read(ucb_ts, UCB_ADC_INP_TSPY); +} + +/* + * Switch to Y position mode and measure X plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline unsigned int ucb1400_ts_read_ypos(struct ucb1400_ts *ucb_ts) +{ + ucb1400_reg_write(ucb_ts, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1400_reg_write(ucb_ts, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + + ucb1400_reg_write(ucb_ts, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + udelay(55); + + return ucb1400_adc_read(ucb_ts, UCB_ADC_INP_TSPX); +} + + +static inline int ucb1400_ts_pen_is_down(struct ucb1400_ts *ucb_ts) +{ + u16 val; + + val = ucb1400_reg_read(ucb_ts, UCB_TS_CR); + if (((val & UCB_TS_CR_TSPX) == 0) || ((val & UCB_TS_CR_TSMX) ==0 )) { + return TRUE; + } else { + return FALSE; + } +} + + +static void ucb1400_ts_take_reading(struct ucb1400_ts *ucb_ts, struct ts_reading *ts) +{ + ucb1400_adc_enable(ucb_ts); + + ts->x = ucb1400_ts_read_xpos(ucb_ts); + ts->y = ucb1400_ts_read_ypos(ucb_ts); + ts->p = ucb1400_ts_read_pressure(ucb_ts); + + ucb1400_adc_disable(ucb_ts); +} + +// ISR for ac97, shared with pxa2xx-ac97, and later with ucb1400-gpio and ucb1400-adc +static irqreturn_t ucb1400_ts_irq(int irqnr, void *devid, struct pt_regs *regs) +{ + u32 status; + + struct ucb1400_ts *ucb_ts = (struct ucb1400_ts *) devid; + + status=GSR; + if (status & GSR_GSCI) { + GSR&=GSR_GSCI; + if (!(ucb_ts->irq_in_work)) { + //as we get all ac97-interrupts (they are shared), we can't turn them off + // we have to ignore them until slow-handler-thread (below) is thru + + ucb_ts->irq_in_work=TRUE; // FIXME this should be atomic, is it anyway??? + // we might miss an interrupt syncing with kthread this way if ints are comming quickly + // but its not a problem, we only need the last readout anyway + // and if we miss pen-up, we will test for pen-status anyway in kthread + wake_up_interruptible(&ucb_ts->pen_irq_wait); + } + return IRQ_HANDLED; + } else { + return IRQ_NONE; + } +} + +/* +* The touchscreen reader thread. +*/ +static int ucb1400_ts_read_thread(void *data) +{ + unsigned long sleep_time; + u16 val; + int pen_was_down=FALSE; + unsigned long tstamp; // timestamp (jiffies) + struct ts_reading ts_values; + struct ucb1400_ts *ucb_ts = (struct ucb1400_ts *) data; + + /* set up thread context */ + ucb_ts->thread = current; + + daemonize("kucb1400d"); + + if (ucb_ts->ac97 == NULL) { + ucb_ts->thread = NULL; + printk(KERN_ERR "ac97-codec is NULL in read-thread, bailing out\n"); + } + + ucb_ts->irq_in_work=FALSE; + // read-rate in hertz => sleeptime in jiffies + sleep_time = msecs_to_jiffies(1000/cont_rate); + + ucb1400_ts_mode_int(ucb_ts, 0); + ucb1400_ts_mode_int(ucb_ts, UCB_IE_FAL); + complete(&ucb_ts->thr_init); + + /* touch reader loop */ + while (ucb_ts->thread) { + ucb_ts->irq_in_work = FALSE; + wait_event_interruptible( ucb_ts->pen_irq_wait, ucb_ts->irq_in_work); + // check if its our int + val=ucb1400_reg_read(ucb_ts, UCB_IE_STATUS); + if (val & UCB_IE_TSPX) { + do { + ucb1400_ts_mode_int(ucb_ts, 0); // int off / clear int + ucb1400_ts_take_reading(ucb_ts, &ts_values); + ucb1400_ts_mode_int(ucb_ts, UCB_IE_RIS); //int on rising edge=wait for penup + ucb1400_ts_report_event(ucb_ts, ts_values); + pen_was_down=TRUE; + //DBG("ucb1400_kthread x: %d y: %d p: %d, j:%lu\n",ts_values.x,ts_values.y,ts_values.p,jiffies); + + ucb_ts->irq_in_work=FALSE; + tstamp=jiffies; + // This marco needs a TRUE condition to return, Timeout alone without TRUE does NOT return, + // so make up a TRUE when timeout. A bit bloated though .... + wait_event_interruptible_timeout( ucb_ts->pen_irq_wait, (ucb_ts->irq_in_work) || + (((unsigned long)((long)jiffies - (long)tstamp)) >= sleep_time) , sleep_time); + } while(! ucb_ts->irq_in_work); + + ucb1400_ts_mode_int(ucb_ts, UCB_IE_FAL); //int on falling edge=wait for pendwon + if (pen_was_down) { + ucb1400_ts_event_pen_up(ucb_ts); // do pen-up for input + pen_was_down=FALSE; + } else { + printk(KERN_ERR "Unexpected Programmer Failure: Pendown-IRQ without pendown ???\n"); + } + } + try_to_freeze(); + } + complete_and_exit(&ucb_ts->thr_exit, 0); +} + +static int __init ptc_ucb1400_ts_probe(struct device *dev) +{ + u16 val; + int err; + struct ucb1400_ts *ucb_ts; + int ret = -ENODEV; + + ucb_ts = kmalloc(sizeof(struct ucb1400_ts), GFP_KERNEL); + if (!ucb_ts) { + printk(KERN_ERR "Cant allocate memory for device struct\n"); + ret=-ENOMEM; + goto errout; + } + memset(ucb_ts, 0, sizeof(struct ucb1400_ts)); + ucb_ts->ac97 = to_ac97_t(dev); + dev->driver_data = ucb_ts; + val = ucb1400_reg_read(ucb_ts, AC97_VENDOR_ID2); + if (val != UCB_ID_1400) { + printk(KERN_ERR "no ucb1400 Chip ID (%04x) found: %04x\n",UCB_ID_1400, val); + ret=-ENODEV; + goto rollback1; + } + + printk(KERN_INFO "detected a ucb1400 codec on the ac97-bus, enabling touchscreen-driver"); + + /* setup the input device */ + ucb_ts->idev = input_allocate_device(); + if (ucb_ts->idev==NULL) { + printk(KERN_ERR "cannot allocate input-device for ucb1400-touchscreen\n"); + ret= -ENOMEM; + goto rollback1; + } + + ucb_ts->idev->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + ucb_ts->idev->keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH); + input_set_abs_params(ucb_ts->idev, ABS_X, UCB_TS_X_MIN, UCB_TS_X_MAX, 0, 0); + input_set_abs_params(ucb_ts->idev, ABS_Y, UCB_TS_Y_MIN, UCB_TS_Y_MAX, 0, 0); + input_set_abs_params(ucb_ts->idev, ABS_PRESSURE, UCB_TS_PRESSURE_MIN, UCB_TS_PRESSURE_MAX, 1, 1); + + ucb_ts->idev->name = "Palm Tungsten C touchscreen (ucb1400)"; + ucb_ts->idev->dev = dev; + input_register_device(ucb_ts->idev); + + // clear ucb1400-ints if there are + val=ucb1400_reg_read(ucb_ts, UCB_IE_STATUS); + ucb1400_reg_write(ucb_ts, UCB_IE_CLEAR, val); + + // start reader-thread + init_completion(&ucb_ts->thr_init); + init_completion(&ucb_ts->thr_exit); + init_waitqueue_head(&ucb_ts->pen_irq_wait); + ucb1400_ts_mode_int(ucb_ts, 0); + ret = kernel_thread(ucb1400_ts_read_thread, ucb_ts, CLONE_KERNEL); + if (ret > 0) { + wait_for_completion(&ucb_ts->thr_init); + if (ucb_ts->thread == NULL) { + printk(KERN_ERR "cannot start kernel-thread for ucb1400-touchscreen\n"); + ret=-EINVAL; + goto rollback2; + } + } + + // get our share of AC97-Bus interrupt + err = request_irq(IRQ_AC97, ucb1400_ts_irq, SA_SHIRQ, "ucb1400-ts", ucb_ts); + if (err) { + printk(KERN_ERR "ucb1400: IRQ request failed\n"); + ret=-ENODEV; + goto rollback3; + } + + ucb_ts->last_status=0; + val= ucb1400_reg_read(ucb_ts, UCB_CSR1); // remeber old int-status and set ints to ON + if (val & UCB_CS_GPIO_AC97_INT) { + ucb_ts->last_status&=UCB_CS_GPIO_AC97_INT; + } else { + ucb1400_reg_write(ucb_ts, UCB_CSR1, UCB_CS_GPIO_AC97_INT); // enable "gpio"-interrupt on ucb1400 + } + val=GCR; + if (val & GCR_GIE) { + ucb_ts->last_status&=GCR_GIE; + } else { + GCR|=GCR_GIE; // enable "gpio"-interrupt on pxa-ac97 + } + + //DBG("ucb1400_probe done\n"); + return 0; + +rollback3: + if (ucb_ts->thread) { + ucb_ts->thread = NULL; + ucb_ts->irq_in_work = TRUE; + wake_up_interruptible(&ucb_ts->pen_irq_wait); + wait_for_completion(&ucb_ts->thr_exit); + } +rollback2: + input_unregister_device(ucb_ts->idev); +rollback1: + kfree(ucb_ts); +errout: + return ret; +} + +static int ptc_ucb1400_ts_remove(struct device *dev) +{ + struct ucb1400_ts *ucb_ts = dev_get_drvdata(dev); + + // disable "GPIO"-int in pxa-ac97 if we found it disabled + if ((ucb_ts->last_status & GCR_GIE) == 0) + GCR&=~GCR_GIE; + if ((ucb_ts->last_status & UCB_CS_GPIO_AC97_INT) == 0) + ucb1400_reg_write(ucb_ts, UCB_CSR1, UCB_CS_GPIO_AC97_INT); + + ucb1400_ts_mode_int(ucb_ts, 0); + + //stop kthread + if (ucb_ts->thread) { + ucb_ts->thread = NULL; + ucb_ts->irq_in_work = TRUE; + wake_up_interruptible(&ucb_ts->pen_irq_wait); + wait_for_completion(&ucb_ts->thr_exit); + } + + input_unregister_device(ucb_ts->idev); + free_irq(IRQ_AC97, ucb_ts); + kfree(ucb_ts); + return 0; +} + +static struct device_driver ptc_ucb1400_ts_driver = { + .name = "ucb1400-ts", + .bus = &ac97_bus_type, + .probe = ptc_ucb1400_ts_probe, + .remove = ptc_ucb1400_ts_remove, +#ifdef CONFIG_PM + .suspend = NULL, //FIXME do resume and suspend + .resume = NULL, +#endif +}; + +static int __init ptc_ucb1400_ts_init(void) +{ + + return driver_register(&ptc_ucb1400_ts_driver); +} + +static void ptc_ucb1400_ts_exit(void) +{ + driver_unregister(&ptc_ucb1400_ts_driver); +} + +module_init(ptc_ucb1400_ts_init); +module_exit(ptc_ucb1400_ts_exit); + +MODULE_AUTHOR ("Holger Bocklet <bit...@gm...>"); +MODULE_DESCRIPTION ("UCB1400 touchscreen support for Palm Tungsten C"); +MODULE_LICENSE ("GPL"); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |