Hi! > > This adds support for reading ADCs (etc), neccessary to operate touch > > screen on Sharp Zaurus sl-5500. > > I would like to know what the diffs are between my version (attached) > and this version before they get applied.
Hmm, diff looks quite big (attached), and I got it from lenz for 99% part. I have made quite a lot of cleanups to touchscreen part, and it seems to be acceptable by input people. I think it should go into drivers/input/touchscreen/collie_ts.c... Also it looks to me like mcp.h should go into asm/arch-sa1100, so that other drivers can use it... > The only reason my version has not been submitted is because it lives > in the drivers/misc directory, and mainline kernel folk don't like > drivers which clutter up that directory. In fact, I had been told > that drivers/misc should remain completely empty - which makes this > set of miscellaneous drivers homeless. Could they simply live in arch/arm/mach-sa1100? Or is arch/arm/soc better place? Pavel --- linux-rmk/drivers/input/touchscreen/Kconfig 2005-07-14 00:41:02.000000000 +0200 +++ linux-z/drivers/input/touchscreen/Kconfig 2005-07-21 17:22:31.000000000 +0200 @@ -36,6 +36,15 @@ To compile this driver as a module, choose M here: the module will be called ads7846_ts. +config TOUCHSCREEN_COLLIE + tristate "Collie touchscreen (for Sharp SL-5500)" + depends on MCP_UCB1200 + help + Say Y here to enable the driver for the touchscreen on the + Sharp SL-5500 series of PDAs. + + If unsure, say N. + config TOUCHSCREEN_GUNZE tristate "Gunze AHL-51S touchscreen" select SERIO --- linux-rmk/drivers/input/touchscreen/Makefile 2005-07-14 00:41:02.000000000 +0200 +++ linux-z/drivers/input/touchscreen/Makefile 2005-07-21 06:39:52.000000000 +0200 @@ -6,6 +6,7 @@ obj-$(CONFIG_TOUCHSCREEN_BITSY) += h3600_ts_input.o obj-$(CONFIG_TOUCHSCREEN_CORGI) += corgi_ts.o +obj-$(CONFIG_TOUCHSCREEN_COLLIE)+= collie_ts.o obj-$(CONFIG_TOUCHSCREEN_GUNZE) += gunze.o obj-$(CONFIG_TOUCHSCREEN_ELO) += elo.o obj-$(CONFIG_TOUCHSCREEN_MTOUCH) += mtouch.o --- linux-rmk/drivers/misc/Makefile 2005-07-25 05:17:11.000000000 +0200 +++ linux-z/drivers/misc/Makefile 2005-07-21 06:36:17.000000000 +0200 @@ -6,12 +6,15 @@ obj-$(CONFIG_IBM_ASM) += ibmasm/ obj-$(CONFIG_HDPU_FEATURES) += hdpuftrs/ -obj-$(CONFIG_MCP) += mcp-core.o -obj-$(CONFIG_MCP_SA1100) += mcp-sa1100.o -obj-$(CONFIG_MCP_UCB1200) += ucb1x00-core.o -obj-$(CONFIG_MCP_UCB1200_AUDIO) += ucb1x00-audio.o -obj-$(CONFIG_MCP_UCB1200_TS) += ucb1x00-ts.o +obj-$(CONFIG_MCP) += mcp-core.o +obj-$(CONFIG_MCP_UCB1200) += ucb1x00-core.o +obj-$(CONFIG_MCP_UCB1200_AUDIO) += ucb1x00-audio.o ifeq ($(CONFIG_SA1100_ASSABET),y) -obj-$(CONFIG_MCP_UCB1200) += ucb1x00-assabet.o +obj-$(CONFIG_MCP_UCB1200) += ucb1x00-assabet.o endif + +obj-$(CONFIG_MCP_SA1100) += mcp-sa1100.o + +ucb1400-core-y := ucb1x00-core.o mcp-ac97.o +obj-$(CONFIG_UCB1400_TS) += ucb1400-core.o ucb1x00-ts.o Only in linux-z/drivers/misc: mcp-ac97.c --- linux-rmk/drivers/misc/mcp-core.c 2005-07-25 05:17:11.000000000 +0200 +++ linux-z/drivers/misc/mcp-core.c 2005-07-21 06:57:36.000000000 +0200 @@ -19,9 +19,9 @@ #include <asm/dma.h> #include <asm/system.h> -#include "mcp.h" +#include <asm/arch-sa1100/mcp.h> -#define to_mcp(d) container_of(d, struct mcp, attached_device) +#define to_mcp(d) ((struct mcp *)(d)->platform_data) #define to_mcp_driver(d) container_of(d, struct mcp_driver, drv) static int mcp_bus_match(struct device *dev, struct device_driver *drv) @@ -46,7 +46,7 @@ return 0; } -static int mcp_bus_suspend(struct device *dev, pm_message_t state) +static int mcp_bus_suspend(struct device *dev, u32 state) { struct mcp *mcp = to_mcp(dev); int ret = 0; @@ -179,26 +179,40 @@ spin_unlock_irqrestore(&mcp->lock, flags); } -static void mcp_release(struct device *dev) -{ - struct mcp *mcp = container_of(dev, struct mcp, attached_device); - - kfree(mcp); +static void mcp_host_release(struct device *dev) { + struct mcp *mcp = dev->platform_data; + complete(&mcp->attached_device_released); } int mcp_host_register(struct mcp *mcp, struct device *parent) { - mcp->attached_device.parent = parent; - mcp->attached_device.bus = &mcp_bus_type; - mcp->attached_device.dma_mask = parent->dma_mask; - mcp->attached_device.release = mcp_release; - strcpy(mcp->attached_device.bus_id, "mcp0"); - return device_register(&mcp->attached_device); + int ret; + struct device *dev = kmalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + memset(dev, 0, sizeof(*dev)); + dev->platform_data = mcp; + dev->parent = parent; + dev->bus = &mcp_bus_type; + dev->dma_mask = parent->dma_mask; + dev->release = mcp_host_release; + strcpy(dev->bus_id, "mcp0"); + mcp->attached_device = dev; + ret = device_register(dev); + if (ret) { + mcp->attached_device = NULL; + kfree(dev); + } + return ret; } void mcp_host_unregister(struct mcp *mcp) { - device_unregister_wait(&mcp->attached_device); + init_completion(&mcp->attached_device_released); + device_unregister(mcp->attached_device); + wait_for_completion(&mcp->attached_device_released); + kfree(mcp->attached_device); + mcp->attached_device = NULL; } int mcp_driver_register(struct mcp_driver *mcpdrv) --- linux-rmk/drivers/misc/mcp-sa1100.c 2005-07-25 05:17:11.000000000 +0200 +++ linux-z/drivers/misc/mcp-sa1100.c 2005-07-21 06:58:49.000000000 +0200 @@ -27,7 +27,8 @@ #include <asm/arch/assabet.h> -#include "mcp.h" +#include <asm/arch-sa1100/mcp.h> + static void mcp_sa1100_set_telecom_divisor(struct mcp *mcp, unsigned int divisor) @@ -140,7 +141,7 @@ static int mcp_sa1100_probe(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); - struct mcp *mcp; + struct mcp *mcp = &mcp_sa1100; int ret; if (!machine_is_adsbitsy() && !machine_is_assabet() && @@ -149,20 +150,16 @@ !machine_is_graphicsmaster() && !machine_is_lart() && !machine_is_omnimeter() && !machine_is_pfs168() && !machine_is_shannon() && !machine_is_simpad() && - !machine_is_yopy()) + !machine_is_yopy() && !machine_is_collie()) { + printk(KERN_WARNING "MCP-sa1100: machine is not supported\n"); return -ENODEV; + } - if (!request_mem_region(0x80060000, 0x60, "sa11x0-mcp")) + if (!request_mem_region(0x80060000, 0x60, "sa11x0-mcp")) { + printk(KERN_ERR "MCP-sa1100: Unable to request memory region\n"); return -EBUSY; - - mcp = kmalloc(sizeof(struct mcp), GFP_KERNEL); - if (!mcp) { - ret = -ENOMEM; - goto release; } - *mcp = mcp_sa1100; - mcp->me = dev; dev_set_drvdata(dev, mcp); @@ -170,6 +167,12 @@ ASSABET_BCR_set(ASSABET_BCR_CODEC_RST); } + if (machine_is_collie()) { + GAFR &= ~(GPIO_GPIO(16)); + GPDR |= GPIO_GPIO(16); + GPSR |= GPIO_GPIO(16); + } + /* * Setup the PPC unit correctly. */ @@ -181,7 +184,8 @@ Ser4MCSR = -1; Ser4MCCR1 = 0; - Ser4MCCR0 = 0x00007f7f | MCCR0_ADM; + //Ser4MCCR0 = 0x00007f7f | MCCR0_ADM; + Ser4MCCR0 = MCCR0_ADM | MCCR0_ExtClk; /* * Calculate the read/write timeout (us) from the bit clock @@ -192,14 +196,11 @@ mcp->sclk_rate; ret = mcp_host_register(mcp, &pdev->dev); - if (ret == 0) - goto out; - - release: - release_mem_region(0x80060000, 0x60); - dev_set_drvdata(dev, NULL); + if (ret != 0) { + release_mem_region(0x80060000, 0x60); + dev_set_drvdata(dev, NULL); + } - out: return ret; } @@ -208,6 +209,7 @@ struct mcp *mcp = dev_get_drvdata(dev); dev_set_drvdata(dev, NULL); + mcp_host_unregister(mcp); release_mem_region(0x80060000, 0x60); Only in linux-rmk/drivers/misc: mcp.h --- linux-rmk/drivers/misc/ucb1x00-assabet.c 2005-07-25 05:17:11.000000000 +0200 +++ linux-z/drivers/misc/ucb1x00-assabet.c 2005-07-21 06:55:31.000000000 +0200 @@ -35,34 +35,34 @@ UCB1X00_ATTR(vcharger, UCB_ADC_INP_AD0); UCB1X00_ATTR(batt_temp, UCB_ADC_INP_AD2); -static int ucb1x00_assabet_add(struct ucb1x00_dev *dev) +static int ucb1x00_assabet_add(struct class_device *dev) { - class_device_create_file(&dev->ucb->cdev, &class_device_attr_vbatt); - class_device_create_file(&dev->ucb->cdev, &class_device_attr_vcharger); - class_device_create_file(&dev->ucb->cdev, &class_device_attr_batt_temp); + class_device_create_file(dev, &class_device_attr_vbatt); + class_device_create_file(dev, &class_device_attr_vcharger); + class_device_create_file(dev, &class_device_attr_batt_temp); return 0; } -static void ucb1x00_assabet_remove(struct ucb1x00_dev *dev) +static void ucb1x00_assabet_remove(struct class_device *dev) { - class_device_remove_file(&dev->ucb->cdev, &class_device_attr_batt_temp); - class_device_remove_file(&dev->ucb->cdev, &class_device_attr_vcharger); - class_device_remove_file(&dev->ucb->cdev, &class_device_attr_vbatt); + class_device_remove_file(dev, &class_device_attr_batt_temp); + class_device_remove_file(dev, &class_device_attr_vcharger); + class_device_remove_file(dev, &class_device_attr_vbatt); } -static struct ucb1x00_driver ucb1x00_assabet_driver = { +static struct class_interface ucb1x00_assabet_interface = { .add = ucb1x00_assabet_add, .remove = ucb1x00_assabet_remove, }; static int __init ucb1x00_assabet_init(void) { - return ucb1x00_register_driver(&ucb1x00_assabet_driver); + return ucb1x00_register_interface(&ucb1x00_assabet_interface); } static void __exit ucb1x00_assabet_exit(void) { - ucb1x00_unregister_driver(&ucb1x00_assabet_driver); + ucb1x00_unregister_interface(&ucb1x00_assabet_interface); } module_init(ucb1x00_assabet_init); --- linux-rmk/drivers/misc/ucb1x00-audio.c 2005-07-25 05:17:11.000000000 +0200 +++ linux-z/drivers/misc/ucb1x00-audio.c 2005-07-21 06:55:31.000000000 +0200 @@ -50,11 +50,6 @@ unsigned short input_level; }; -struct ucb1x00_devdata { - struct ucb1x00_audio audio; - struct ucb1x00_audio telecom; -}; - #define REC_MASK (SOUND_MASK_VOLUME | SOUND_MASK_MIC) #define DEV_MASK REC_MASK @@ -285,122 +280,134 @@ return sa1100_audio_attach(inode, file, &ucba->state); } -static int -ucb1x00_audio_add_one(struct ucb1x00 *ucb, struct ucb1x00_audio *a, int telecom) +static struct ucb1x00_audio *ucb1x00_audio_alloc(struct ucb1x00 *ucb) { - memset(a, 0, sizeof(*a)); + struct ucb1x00_audio *ucba; - a->magic = MAGIC; - a->ucb = ucb; - a->fops.owner = THIS_MODULE; - a->fops.open = ucb1x00_audio_open; - a->mops.owner = THIS_MODULE; - a->mops.ioctl = ucb1x00_mixer_ioctl; - a->state.output_stream = &a->output_stream; - a->state.input_stream = &a->input_stream; - a->state.data = a; - a->state.hw_init = ucb1x00_audio_startup; - a->state.hw_shutdown = ucb1x00_audio_shutdown; - a->state.client_ioctl = ucb1x00_audio_ioctl; - - /* There is a bug in the StrongARM causes corrupt MCP data to be sent to - * the codec when the FIFOs are empty and writes are made to the OS timer - * match register 0. To avoid this we must make sure that data is always - * sent to the codec. - */ - a->state.need_tx_for_rx = 1; + ucba = kmalloc(sizeof(*ucba), GFP_KERNEL); + if (ucba) { + memset(ucba, 0, sizeof(*ucba)); + + ucba->magic = MAGIC; + ucba->ucb = ucb; + ucba->fops.owner = THIS_MODULE; + ucba->fops.open = ucb1x00_audio_open; + ucba->mops.owner = THIS_MODULE; + ucba->mops.ioctl = ucb1x00_mixer_ioctl; + ucba->state.output_stream = &ucba->output_stream; + ucba->state.input_stream = &ucba->input_stream; + ucba->state.data = ucba; + ucba->state.hw_init = ucb1x00_audio_startup; + ucba->state.hw_shutdown = ucb1x00_audio_shutdown; + ucba->state.client_ioctl = ucb1x00_audio_ioctl; + + /* There is a bug in the StrongARM causes corrupt MCP data to be sent to + * the codec when the FIFOs are empty and writes are made to the OS timer + * match register 0. To avoid this we must make sure that data is always + * sent to the codec. + */ + ucba->state.need_tx_for_rx = 1; + + init_MUTEX(&ucba->state.sem); + ucba->rate = 8000; + } + return ucba; +} + +static struct ucb1x00_audio *ucb1x00_audio_add_one(struct ucb1x00 *ucb, int telecom) +{ + struct ucb1x00_audio *a; - init_MUTEX(&a->state.sem); - a->rate = 8000; - a->telecom = telecom; - a->input_stream.dev = ucb->cdev.dev; - a->output_stream.dev = ucb->cdev.dev; - a->ctrl_a = 0; - - if (a->telecom) { - a->input_stream.dma_dev = ucb->mcp->dma_telco_rd; - a->input_stream.id = "UCB1x00 telco in"; - a->output_stream.dma_dev = ucb->mcp->dma_telco_wr; - a->output_stream.id = "UCB1x00 telco out"; - a->ctrl_b = UCB_TC_B_IN_ENA|UCB_TC_B_OUT_ENA; + a = ucb1x00_audio_alloc(ucb); + if (a) { + a->telecom = telecom; + + a->input_stream.dev = ucb->cdev.dev; + a->output_stream.dev = ucb->cdev.dev; + a->ctrl_a = 0; + + if (a->telecom) { + a->input_stream.dma_dev = ucb->mcp->dma_telco_rd; + a->input_stream.id = "UCB1x00 telco in"; + a->output_stream.dma_dev = ucb->mcp->dma_telco_wr; + a->output_stream.id = "UCB1x00 telco out"; + a->ctrl_b = UCB_TC_B_IN_ENA|UCB_TC_B_OUT_ENA; #if 0 - a->daa_oh_bit = UCB_IO_8; + a->daa_oh_bit = UCB_IO_8; - ucb1x00_enable(ucb); - ucb1x00_io_write(ucb, a->daa_oh_bit, 0); - ucb1x00_io_set_dir(ucb, UCB_IO_7 | UCB_IO_6, a->daa_oh_bit); - ucb1x00_disable(ucb); + ucb1x00_enable(ucb); + ucb1x00_io_write(ucb, a->daa_oh_bit, 0); + ucb1x00_io_set_dir(ucb, UCB_IO_7 | UCB_IO_6, a->daa_oh_bit); + ucb1x00_disable(ucb); #endif - } else { - a->input_stream.dma_dev = ucb->mcp->dma_audio_rd; - a->input_stream.id = "UCB1x00 audio in"; - a->output_stream.dma_dev = ucb->mcp->dma_audio_wr; - a->output_stream.id = "UCB1x00 audio out"; - a->ctrl_b = UCB_AC_B_IN_ENA|UCB_AC_B_OUT_ENA; - } + } else { + a->input_stream.dma_dev = ucb->mcp->dma_audio_rd; + a->input_stream.id = "UCB1x00 audio in"; + a->output_stream.dma_dev = ucb->mcp->dma_audio_wr; + a->output_stream.id = "UCB1x00 audio out"; + a->ctrl_b = UCB_AC_B_IN_ENA|UCB_AC_B_OUT_ENA; + } - a->dev_id = register_sound_dsp(&a->fops, -1); - a->mix_id = register_sound_mixer(&a->mops, -1); + a->dev_id = register_sound_dsp(&a->fops, -1); + a->mix_id = register_sound_mixer(&a->mops, -1); - printk("Sound: UCB1x00 %s: dsp id %d mixer id %d\n", - a->telecom ? "telecom" : "audio", - a->dev_id, a->mix_id); + printk("Sound: UCB1x00 %s: dsp id %d mixer id %d\n", + a->telecom ? "telecom" : "audio", + a->dev_id, a->mix_id); + } - return 0; + return a; } static void ucb1x00_audio_remove_one(struct ucb1x00_audio *a) { unregister_sound_dsp(a->dev_id); unregister_sound_mixer(a->mix_id); + kfree(a); } -static int ucb1x00_audio_add(struct ucb1x00_dev *dev) +static int ucb1x00_audio_add(struct class_device *cdev) { - struct ucb1x00_devdata *dd; - struct ucb1x00 *ucb = dev->ucb; + struct ucb1x00 *ucb = classdev_to_ucb1x00(cdev); if (ucb->cdev.dev == NULL || ucb->cdev.dev->dma_mask == NULL) return -ENXIO; - dd = kmalloc(sizeof(struct ucb1x00_devdata), GFP_KERNEL); - if (!dd) - return -ENOMEM; - - ucb1x00_audio_add_one(ucb, &dd->audio, 0); - ucb1x00_audio_add_one(ucb, &dd->telecom, 1); - - dev->priv = dd; + ucb->audio_data = ucb1x00_audio_add_one(ucb, 0); + ucb->telecom_data = ucb1x00_audio_add_one(ucb, 1); return 0; } -static void ucb1x00_audio_remove(struct ucb1x00_dev *dev) +static void ucb1x00_audio_remove(struct class_device *cdev) { - struct ucb1x00_devdata *dd = dev->priv; + struct ucb1x00 *ucb = classdev_to_ucb1x00(cdev); - ucb1x00_audio_remove_one(&dd->audio); - ucb1x00_audio_remove_one(&dd->telecom); - kfree(dd); + ucb1x00_audio_remove_one(ucb->audio_data); + ucb1x00_audio_remove_one(ucb->telecom_data); } -#ifdef CONFIG_PM -static int ucb1x00_audio_suspend(struct ucb1x00_dev *dev, pm_message_t state) +#if 0 //def CONFIG_PM +static int ucb1x00_audio_suspend(struct ucb1x00 *ucb, u32 state) { - struct ucb1x00_devdata *dd = dev->priv; + struct ucb1x00_audio *a; - sa1100_audio_suspend(&dd->audio.state, state); - sa1100_audio_suspend(&dd->telecom.state, state); + a = ucb->audio_data; + sa1100_audio_suspend(&a->state, state); + a = ucb->telecom_data; + sa1100_audio_suspend(&a->state, state); return 0; } -static int ucb1x00_audio_resume(struct ucb1x00_dev *dev) +static int ucb1x00_audio_resume(struct ucb1x00 *ucb) { - struct ucb1x00_devdata *dd = dev->priv; + struct ucb1x00_audio *a; - sa1100_audio_resume(&dd->audio.state); - sa1100_audio_resume(&dd->telecom.state); + a = ucb->audio_data; + sa1100_audio_resume(&a->state); + a = ucb->telecom_data; + sa1100_audio_resume(&a->state); return 0; } @@ -409,21 +416,19 @@ #define ucb1x00_audio_resume NULL #endif -static struct ucb1x00_driver ucb1x00_audio_driver = { +static struct class_interface ucb1x00_audio_interface = { .add = ucb1x00_audio_add, .remove = ucb1x00_audio_remove, - .suspend = ucb1x00_audio_suspend, - .resume = ucb1x00_audio_resume, }; static int __init ucb1x00_audio_init(void) { - return ucb1x00_register_driver(&ucb1x00_audio_driver); + return ucb1x00_register_interface(&ucb1x00_audio_interface); } static void __exit ucb1x00_audio_exit(void) { - ucb1x00_unregister_driver(&ucb1x00_audio_driver); + ucb1x00_unregister_interface(&ucb1x00_audio_interface); } module_init(ucb1x00_audio_init); --- linux-rmk/drivers/misc/ucb1x00-core.c 2005-07-25 05:17:11.000000000 +0200 +++ linux-z/drivers/misc/ucb1x00-core.c 2005-07-22 03:28:07.000000000 +0200 @@ -29,11 +29,7 @@ #include <asm/hardware.h> #include <asm/irq.h> -#include "ucb1x00.h" - -static DECLARE_MUTEX(ucb1x00_sem); -static LIST_HEAD(ucb1x00_drivers); -static LIST_HEAD(ucb1x00_devices); +#include <asm/arch-sa1100/ucb1x00.h> /** * ucb1x00_io_set_dir - set IO direction @@ -58,9 +54,9 @@ spin_lock_irqsave(&ucb->io_lock, flags); ucb->io_dir |= out; ucb->io_dir &= ~in; + spin_unlock_irqrestore(&ucb->io_lock, flags); ucb1x00_reg_write(ucb, UCB_IO_DIR, ucb->io_dir); - spin_unlock_irqrestore(&ucb->io_lock, flags); } /** @@ -86,9 +82,9 @@ spin_lock_irqsave(&ucb->io_lock, flags); ucb->io_out |= set; ucb->io_out &= ~clear; + spin_unlock_irqrestore(&ucb->io_lock, flags); ucb1x00_reg_write(ucb, UCB_IO_DATA, ucb->io_out); - spin_unlock_irqrestore(&ucb->io_lock, flags); } /** @@ -174,7 +170,7 @@ if (val & UCB_ADC_DAT_VAL) break; /* yield to other processes */ - set_current_state(TASK_INTERRUPTIBLE); + set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(1); } @@ -223,6 +219,57 @@ return IRQ_HANDLED; } +/* + * A restriction with interrupts exists when using the ucb1400, as + * the codec read/write routines may sleep while waiting for codec + * access completion and uses semaphores for access control to the + * AC97 bus. A complete codec read cycle could take anywhere from + * 60 to 100uSec so we *definitely* don't want to spin inside the + * interrupt handler waiting for codec access. So, we handle the + * interrupt by scheduling a RT kernel thread to run in process + * context instead of interrupt context. + */ +static int ucb1x00_thread(void *_ucb) +{ + struct task_struct *tsk = current; + DECLARE_WAITQUEUE(wait, tsk); + struct ucb1x00 *ucb = _ucb; + + ucb->irq_task = tsk; + daemonize("kUCB1x00d"); + allow_signal(SIGKILL); + tsk->policy = SCHED_FIFO; + tsk->rt_priority = 1; + + add_wait_queue(&ucb->irq_wait, &wait); + set_task_state(tsk, TASK_INTERRUPTIBLE); + complete(&ucb->complete); + + for (;;) { + if (signal_pending(tsk)) + break; + schedule(); + ucb1x00_irq(-1, ucb, NULL); + set_task_state(tsk, TASK_INTERRUPTIBLE); + enable_irq(ucb->irq); + } + + remove_wait_queue(&ucb->irq_wait, &wait); + ucb->irq_task = NULL; + complete_and_exit(&ucb->complete, 0); +} + +static irqreturn_t ucb1x00_threaded_irq(int irqnr, void *devid, struct pt_regs *regs) +{ + struct ucb1x00 *ucb = devid; + if (irqnr == ucb->irq) { + disable_irq(ucb->irq); + wake_up(&ucb->irq_wait); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + /** * ucb1x00_hook_irq - hook a UCB1x00 interrupt * @ucb: UCB1x00 structure describing chip @@ -276,18 +323,22 @@ if (idx < 16) { spin_lock_irqsave(&ucb->lock, flags); - - ucb1x00_enable(ucb); - if (edges & UCB_RISING) { + if (edges & UCB_RISING) ucb->irq_ris_enbl |= 1 << idx; - ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl); - } - if (edges & UCB_FALLING) { + if (edges & UCB_FALLING) ucb->irq_fal_enbl |= 1 << idx; - ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl); - } - ucb1x00_disable(ucb); spin_unlock_irqrestore(&ucb->lock, flags); + + ucb1x00_enable(ucb); + + /* This prevents spurious interrupts on the UCB1400 */ + ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 1 << idx); + ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0); + + ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl); + ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl); + + ucb1x00_disable(ucb); } } @@ -305,18 +356,16 @@ if (idx < 16) { spin_lock_irqsave(&ucb->lock, flags); - - ucb1x00_enable(ucb); - if (edges & UCB_RISING) { + if (edges & UCB_RISING) ucb->irq_ris_enbl &= ~(1 << idx); - ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl); - } - if (edges & UCB_FALLING) { + if (edges & UCB_FALLING) ucb->irq_fal_enbl &= ~(1 << idx); - ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl); - } - ucb1x00_disable(ucb); spin_unlock_irqrestore(&ucb->lock, flags); + + ucb1x00_enable(ucb); + ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl); + ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl); + ucb1x00_disable(ucb); } } @@ -349,16 +398,17 @@ ucb->irq_ris_enbl &= ~(1 << idx); ucb->irq_fal_enbl &= ~(1 << idx); - ucb1x00_enable(ucb); - ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl); - ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl); - ucb1x00_disable(ucb); - irq->fn = NULL; irq->devid = NULL; ret = 0; } spin_unlock_irq(&ucb->lock); + + ucb1x00_enable(ucb); + ucb1x00_reg_write(ucb, UCB_IE_RIS, ucb->irq_ris_enbl); + ucb1x00_reg_write(ucb, UCB_IE_FAL, ucb->irq_fal_enbl); + ucb1x00_disable(ucb); + return ret; bad: @@ -366,36 +416,6 @@ return -EINVAL; } -static int ucb1x00_add_dev(struct ucb1x00 *ucb, struct ucb1x00_driver *drv) -{ - struct ucb1x00_dev *dev; - int ret = -ENOMEM; - - dev = kmalloc(sizeof(struct ucb1x00_dev), GFP_KERNEL); - if (dev) { - dev->ucb = ucb; - dev->drv = drv; - - ret = drv->add(dev); - - if (ret == 0) { - list_add(&dev->dev_node, &ucb->devs); - list_add(&dev->drv_node, &drv->devs); - } else { - kfree(dev); - } - } - return ret; -} - -static void ucb1x00_remove_dev(struct ucb1x00_dev *dev) -{ - dev->drv->remove(dev); - list_del(&dev->dev_node); - list_del(&dev->drv_node); - kfree(dev); -} - /* * Try to probe our interrupt, rather than relying on lots of * hard-coded machine dependencies. For reference, the expected @@ -460,17 +480,16 @@ static int ucb1x00_probe(struct mcp *mcp) { struct ucb1x00 *ucb; - struct ucb1x00_driver *drv; unsigned int id; int ret = -ENODEV; mcp_enable(mcp); id = mcp_reg_read(mcp, UCB_ID); - if (id != UCB_ID_1200 && id != UCB_ID_1300) { + /*if (id != UCB_ID_1200 && id != UCB_ID_1300 && id != UCB_ID_1400) { printk(KERN_WARNING "UCB1x00 ID not found: %04x\n", id); goto err_disable; - } + }*/ ucb = kmalloc(sizeof(struct ucb1x00), GFP_KERNEL); ret = -ENOMEM; @@ -480,15 +499,20 @@ memset(ucb, 0, sizeof(struct ucb1x00)); ucb->cdev.class = &ucb1x00_class; - ucb->cdev.dev = &mcp->attached_device; + ucb->cdev.dev = mcp->attached_device; strlcpy(ucb->cdev.class_id, "ucb1x00", sizeof(ucb->cdev.class_id)); spin_lock_init(&ucb->lock); spin_lock_init(&ucb->io_lock); sema_init(&ucb->adc_sem, 1); + init_waitqueue_head(&ucb->irq_wait); - ucb->id = id; ucb->mcp = mcp; + ucb->id = id; + /* distinguish between UCB1400 revs 1B and 2A */ + if (id == UCB_ID_1400 && mcp_reg_read(mcp, 0x00) == 0x002a) + ucb->id = UCB_ID_1400_BUGGY; + ucb->irq = ucb1x00_detect_irq(ucb); if (ucb->irq == NO_IRQ) { printk(KERN_ERR "UCB1x00: IRQ probe failed\n"); @@ -496,7 +520,9 @@ goto err_free; } - ret = request_irq(ucb->irq, ucb1x00_irq, 0, "UCB1x00", ucb); + ret = request_irq(ucb->irq, + id != UCB_ID_1400 ? ucb1x00_irq : ucb1x00_threaded_irq, + 0, "UCB1x00", ucb); if (ret) { printk(KERN_ERR "ucb1x00: unable to grab irq%d: %d\n", ucb->irq, ret); @@ -507,43 +533,36 @@ mcp_set_drvdata(mcp, ucb); ret = class_device_register(&ucb->cdev); - if (ret) - goto err_irq; - INIT_LIST_HEAD(&ucb->devs); - down(&ucb1x00_sem); - list_add(&ucb->node, &ucb1x00_devices); - list_for_each_entry(drv, &ucb1x00_drivers, node) { - ucb1x00_add_dev(ucb, drv); + if (!ret && id == UCB_ID_1400) { + init_completion(&ucb->complete); + ret = kernel_thread(ucb1x00_thread, ucb, CLONE_KERNEL); + if (ret >= 0) { + wait_for_completion(&ucb->complete); + ret = 0; + } } - up(&ucb1x00_sem); - goto out; - err_irq: - free_irq(ucb->irq, ucb); + if (ret) { + free_irq(ucb->irq, ucb); err_free: - kfree(ucb); + kfree(ucb); + } err_disable: mcp_disable(mcp); - out: return ret; } static void ucb1x00_remove(struct mcp *mcp) { struct ucb1x00 *ucb = mcp_get_drvdata(mcp); - struct list_head *l, *n; - down(&ucb1x00_sem); - list_del(&ucb->node); - list_for_each_safe(l, n, &ucb->devs) { - struct ucb1x00_dev *dev = list_entry(l, struct ucb1x00_dev, dev_node); - ucb1x00_remove_dev(dev); + class_device_unregister(&ucb->cdev); + if (ucb->id == UCB_ID_1400 || ucb->id == UCB_ID_1400_BUGGY) { + send_sig(SIGKILL, ucb->irq_task, 1); + wait_for_completion(&ucb->complete); } - up(&ucb1x00_sem); - free_irq(ucb->irq, ucb); - class_device_unregister(&ucb->cdev); } static void ucb1x00_release(struct class_device *dev) @@ -557,59 +576,15 @@ .release = ucb1x00_release, }; -int ucb1x00_register_driver(struct ucb1x00_driver *drv) -{ - struct ucb1x00 *ucb; - - INIT_LIST_HEAD(&drv->devs); - down(&ucb1x00_sem); - list_add(&drv->node, &ucb1x00_drivers); - list_for_each_entry(ucb, &ucb1x00_devices, node) { - ucb1x00_add_dev(ucb, drv); - } - up(&ucb1x00_sem); - return 0; -} - -void ucb1x00_unregister_driver(struct ucb1x00_driver *drv) +int ucb1x00_register_interface(struct class_interface *intf) { - struct list_head *n, *l; - - down(&ucb1x00_sem); - list_del(&drv->node); - list_for_each_safe(l, n, &drv->devs) { - struct ucb1x00_dev *dev = list_entry(l, struct ucb1x00_dev, drv_node); - ucb1x00_remove_dev(dev); - } - up(&ucb1x00_sem); + intf->class = &ucb1x00_class; + return class_interface_register(intf); } -static int ucb1x00_suspend(struct mcp *mcp, pm_message_t state) +void ucb1x00_unregister_interface(struct class_interface *intf) { - struct ucb1x00 *ucb = mcp_get_drvdata(mcp); - struct ucb1x00_dev *dev; - - down(&ucb1x00_sem); - list_for_each_entry(dev, &ucb->devs, dev_node) { - if (dev->drv->suspend) - dev->drv->suspend(dev, state); - } - up(&ucb1x00_sem); - return 0; -} - -static int ucb1x00_resume(struct mcp *mcp) -{ - struct ucb1x00 *ucb = mcp_get_drvdata(mcp); - struct ucb1x00_dev *dev; - - down(&ucb1x00_sem); - list_for_each_entry(dev, &ucb->devs, dev_node) { - if (dev->drv->resume) - dev->drv->resume(dev); - } - up(&ucb1x00_sem); - return 0; + class_interface_unregister(intf); } static struct mcp_driver ucb1x00_driver = { @@ -618,8 +593,6 @@ }, .probe = ucb1x00_probe, .remove = ucb1x00_remove, - .suspend = ucb1x00_suspend, - .resume = ucb1x00_resume, }; static int __init ucb1x00_init(void) @@ -657,8 +630,8 @@ EXPORT_SYMBOL(ucb1x00_enable_irq); EXPORT_SYMBOL(ucb1x00_disable_irq); -EXPORT_SYMBOL(ucb1x00_register_driver); -EXPORT_SYMBOL(ucb1x00_unregister_driver); +EXPORT_SYMBOL(ucb1x00_register_interface); +EXPORT_SYMBOL(ucb1x00_unregister_interface); MODULE_AUTHOR("Russell King <[EMAIL PROTECTED]>"); MODULE_DESCRIPTION("UCB1x00 core driver"); Only in linux-rmk/drivers/misc: ucb1x00-ts.c Only in linux-rmk/drivers/misc: ucb1x00.h --- /dev/null 2005-07-11 13:10:49.000000000 +0200 +++ linux-z/drivers/input/touchscreen/collie_ts.c 2005-07-23 14:13:28.000000000 +0200 @@ -0,0 +1,367 @@ +/* + * linux/drivers/input/touchscreen/collie_ts.c + * + * 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 version 2 as + * published by the Free Software Foundation. + * + * 21-Jan-2002 <[EMAIL PROTECTED]> : + * + * Added support for synchronous A/D mode. This mode is useful to + * avoid noise induced in the touchpanel by the LCD, provided that + * the UCB1x00 has a valid LCD sync signal routed to its ADCSYNC pin. + * It is important to note that the signal connected to the ADCSYNC + * pin should provide pulses even when the LCD is blanked, otherwise + * a pen touch needed to unblank the LCD will never be read. + */ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/sched.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/input.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/kthread.h> + +#include <asm/dma.h> +#include <asm/semaphore.h> + +#include <asm/arch-sa1100/ucb1x00.h> + + +struct ucb1x00_ts { + struct input_dev idev; + struct ucb1x00 *ucb; + + struct semaphore irq_wait; + struct task_struct *rtask; + u16 x_res; + u16 y_res; + + int restart:1; + int adcsync:1; +}; + +/* + * Switch to interrupt mode. + */ +static inline void ucb1x00_ts_mode_int(struct ucb1x00_ts *ts) +{ + int val = 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; + if (ts->ucb->id == UCB_ID_1400_BUGGY) + val &= ~(UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW); + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, val); +} + +/* + * Switch to pressure mode, and read pressure. We don't need to wait + * here, since both plates are being driven. + */ +static inline unsigned int ucb1x00_ts_read_pressure(struct ucb1x00_ts *ts) +{ + ucb1x00_reg_write(ts->ucb, 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); + + return ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync); +} + +/* + * 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 ucb1x00_ts_read_xpos(struct ucb1x00_ts *ts) +{ + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1x00_reg_write(ts->ucb, 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 ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPY, ts->adcsync); +} + +/* + * 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 ucb1x00_ts_read_ypos(struct ucb1x00_ts *ts) +{ + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1x00_reg_write(ts->ucb, 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 ucb1x00_adc_read(ts->ucb, UCB_ADC_INP_TSPX, ts->adcsync); +} + +/* + * Switch to X plate resistance mode. Set MX to ground, PX to + * supply. Measure current. + */ +static inline unsigned int ucb1x00_ts_read_xres(struct ucb1x00_ts *ts) +{ + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + return ucb1x00_adc_read(ts->ucb, 0, ts->adcsync); +} + +/* + * Switch to Y plate resistance mode. Set MY to ground, PY to + * supply. Measure current. + */ +static inline unsigned int ucb1x00_ts_read_yres(struct ucb1x00_ts *ts) +{ + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + return ucb1x00_adc_read(ts->ucb, 0, ts->adcsync); +} + +/* + * This is a RT kernel thread that handles the ADC accesses + * (mainly so we can use semaphores in the UCB1200 core code + * to serialise accesses to the ADC). The UCB1400 access + * functions are expected to be able to sleep as well. + */ +static int ucb1x00_thread(void *_ts) +{ + struct ucb1x00_ts *ts = _ts; + struct task_struct *tsk = current; + int valid; + + ts->rtask = tsk; + + /* + * We run as a real-time thread. However, thus far + * this doesn't seem to be necessary. + */ + tsk->policy = SCHED_FIFO; + tsk->rt_priority = 1; + + valid = 0; + for (;;) { + unsigned int x, y, p, val; + + ts->restart = 0; + + ucb1x00_adc_enable(ts->ucb); + + x = ucb1x00_ts_read_xpos(ts); + y = ucb1x00_ts_read_ypos(ts); + p = ucb1x00_ts_read_pressure(ts); + + /* + * Switch back to interrupt mode. + */ + ucb1x00_ts_mode_int(ts); + ucb1x00_adc_disable(ts->ucb); + + msleep(10); + + ucb1x00_enable(ts->ucb); + val = ucb1x00_reg_read(ts->ucb, UCB_TS_CR); + + if (val & (UCB_TS_CR_TSPX_LOW | UCB_TS_CR_TSMX_LOW)) { + ucb1x00_enable_irq(ts->ucb, UCB_IRQ_TSPX, UCB_FALLING); + ucb1x00_disable(ts->ucb); + + /* + * If we spat out a valid sample set last time, + * spit out a "pen off" sample here. + */ + if (valid) { + input_report_abs(&ts->idev, ABS_PRESSURE, 0); + input_sync(&ts->idev); + valid = 0; + } + + /* + * Since ucb1x00_enable_irq() might sleep due + * to the way the UCB1400 regs are accessed, we + * can't use set_task_state() before that call, + * and not changing state before enabling the + * interrupt is racy. A semaphore solves all + * those issues quite nicely. + */ + down_interruptible(&ts->irq_wait); + } else { + ucb1x00_disable(ts->ucb); + + /* + * Filtering is policy. Policy belongs in user + * space. We therefore leave it to user space + * to do any filtering they please. + */ + if (!ts->restart) { + input_report_abs(&ts->idev, ABS_X, x); + input_report_abs(&ts->idev, ABS_Y, y); + input_report_abs(&ts->idev, ABS_PRESSURE, p); + input_sync(&ts->idev); + valid = 1; + } + + msleep_interruptible(10); + } + + if (kthread_should_stop()) + break; + } + + ts->rtask = NULL; + return 0; +} + +/* + * We only detect touch screen _touches_ with this interrupt + * handler, and even then we just schedule our task. + */ +static void ucb1x00_ts_irq(int idx, void *id) +{ + struct ucb1x00_ts *ts = id; + ucb1x00_disable_irq(ts->ucb, UCB_IRQ_TSPX, UCB_FALLING); + up(&ts->irq_wait); +} + +static int ucb1x00_ts_open(struct input_dev *idev) +{ + struct ucb1x00_ts *ts = (struct ucb1x00_ts *)idev; + int ret = 0; + struct task_struct *task; + + BUG_ON(ts->rtask); + + sema_init(&ts->irq_wait, 0); + ret = ucb1x00_hook_irq(ts->ucb, UCB_IRQ_TSPX, ucb1x00_ts_irq, ts); + if (ret < 0) + goto out; + + /* + * If we do this at all, we should allow the user to + * measure and read the X and Y resistance at any time. + */ + ucb1x00_adc_enable(ts->ucb); + ts->x_res = ucb1x00_ts_read_xres(ts); + ts->y_res = ucb1x00_ts_read_yres(ts); + ucb1x00_adc_disable(ts->ucb); + + task = kthread_run(ucb1x00_thread, ts, "ktsd"); + if (!IS_ERR(task)) { + ret = 0; + } else { + ucb1x00_free_irq(ts->ucb, UCB_IRQ_TSPX, ts); + ret = -EFAULT; + } + + out: + return ret; +} + +/* + * Release touchscreen resources. Disable IRQs. + */ +static void ucb1x00_ts_close(struct input_dev *idev) +{ + struct ucb1x00_ts *ts = (struct ucb1x00_ts *)idev; + + if (ts->rtask) + kthread_stop(ts->rtask); + + ucb1x00_enable(ts->ucb); + ucb1x00_free_irq(ts->ucb, UCB_IRQ_TSPX, ts); + ucb1x00_reg_write(ts->ucb, UCB_TS_CR, 0); + ucb1x00_disable(ts->ucb); +} + +/* + * Initialisation. + */ +static int ucb1x00_ts_add(struct class_device *dev) +{ + struct ucb1x00 *ucb = classdev_to_ucb1x00(dev); + struct ucb1x00_ts *ts; + + ts = kmalloc(sizeof(struct ucb1x00_ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + memset(ts, 0, sizeof(struct ucb1x00_ts)); + + ts->ucb = ucb; + ts->adcsync = UCB_NOSYNC; + + ts->idev.name = "Touchscreen panel"; + ts->idev.id.product = ts->ucb->id; + ts->idev.open = ucb1x00_ts_open; + ts->idev.close = ucb1x00_ts_close; + + set_bit(EV_ABS, ts->idev.evbit); + set_bit(ABS_X, ts->idev.absbit); + set_bit(ABS_Y, ts->idev.absbit); + set_bit(ABS_PRESSURE, ts->idev.absbit); + + input_register_device(&ts->idev); + + ucb->ts_data = ts; + + return 0; +} + +static void ucb1x00_ts_remove(struct class_device *dev) +{ + struct ucb1x00 *ucb = classdev_to_ucb1x00(dev); + struct ucb1x00_ts *ts = ucb->ts_data; + + input_unregister_device(&ts->idev); + kfree(ts); +} + +static struct class_interface ucb1x00_ts_interface = { + .add = ucb1x00_ts_add, + .remove = ucb1x00_ts_remove, +}; + +static int __init ucb1x00_ts_init(void) +{ + return ucb1x00_register_interface(&ucb1x00_ts_interface); +} + +static void __exit ucb1x00_ts_exit(void) +{ + ucb1x00_unregister_interface(&ucb1x00_ts_interface); +} + +module_init(ucb1x00_ts_init); +module_exit(ucb1x00_ts_exit); + +MODULE_AUTHOR("Russell King <[EMAIL PROTECTED]>"); +MODULE_DESCRIPTION("UCB1x00 touchscreen driver"); +MODULE_LICENSE("GPL"); -- teflon -- maybe it is a trademark, but it should not be. - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/