Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[patch 1/2] Touchscreen support for sharp sl-5500

0 views
Skip to first unread message

Pavel Machek

unread,
Jul 22, 2005, 2:10:12 PM7/22/05
to
This adds support for reading ADCs (etc), neccessary to operate touch
screen on Sharp Zaurus sl-5500.

Please apply,
Pavel

Signed-off-by: Pavel Machek <pa...@suse.cz>


diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -30,3 +30,20 @@ config IBM_ASM

endmenu

+menu "Multimedia Capabilities Port drivers"
+
+config MCP
+ tristate
+
+# Interface drivers
+config MCP_SA1100
+ tristate "Support SA1100 MCP interface"
+ depends on ARCH_SA1100
+ select MCP
+
+# Chip drivers
+config MCP_UCB1200
+ tristate "Support for UCB1200 / UCB1300"
+ depends on MCP
+
+endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -5,3 +5,11 @@ obj- := misc.o # Dummy rule to force bui

obj-$(CONFIG_IBM_ASM) += ibmasm/
obj-$(CONFIG_HDPU_FEATURES) += hdpuftrs/
+
+obj-$(CONFIG_MCP) += mcp-core.o
+obj-$(CONFIG_MCP_UCB1200) += ucb1x00-core.o
+
+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
diff --git a/drivers/misc/mcp-core.c b/drivers/misc/mcp-core.c
new file mode 100644
--- /dev/null
+++ b/drivers/misc/mcp-core.c
@@ -0,0 +1,257 @@
+/*
+ * linux/drivers/misc/mcp-core.c
+ *
+ * Copyright (C) 2001 Russell King
+ *
+ * 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.
+ *
+ * Generic MCP (Multimedia Communications Port) layer. All MCP locking
+ * is solely held within this file.
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/smp.h>
+#include <linux/device.h>
+
+#include <asm/dma.h>
+#include <asm/system.h>
+
+#include <asm/arch-sa1100/mcp.h>
+
+#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)
+{
+ return 1;
+}
+
+static int mcp_bus_probe(struct device *dev)
+{
+ struct mcp *mcp = to_mcp(dev);
+ struct mcp_driver *drv = to_mcp_driver(dev->driver);
+
+ return drv->probe(mcp);
+}
+
+static int mcp_bus_remove(struct device *dev)
+{
+ struct mcp *mcp = to_mcp(dev);
+ struct mcp_driver *drv = to_mcp_driver(dev->driver);
+
+ drv->remove(mcp);
+ return 0;
+}
+
+static int mcp_bus_suspend(struct device *dev, u32 state)
+{
+ struct mcp *mcp = to_mcp(dev);
+ int ret = 0;
+
+ if (dev->driver) {
+ struct mcp_driver *drv = to_mcp_driver(dev->driver);
+
+ ret = drv->suspend(mcp, state);
+ }
+ return ret;
+}
+
+static int mcp_bus_resume(struct device *dev)
+{
+ struct mcp *mcp = to_mcp(dev);
+ int ret = 0;
+
+ if (dev->driver) {
+ struct mcp_driver *drv = to_mcp_driver(dev->driver);
+
+ ret = drv->resume(mcp);
+ }
+ return ret;
+}
+
+static struct bus_type mcp_bus_type = {
+ .name = "mcp",
+ .match = mcp_bus_match,
+ .suspend = mcp_bus_suspend,
+ .resume = mcp_bus_resume,
+};
+
+/**
+ * mcp_set_telecom_divisor - set the telecom divisor
+ * @mcp: MCP interface structure
+ * @div: SIB clock divisor
+ *
+ * Set the telecom divisor on the MCP interface. The resulting
+ * sample rate is SIBCLOCK/div.
+ */
+void mcp_set_telecom_divisor(struct mcp *mcp, unsigned int div)
+{
+ spin_lock_irq(&mcp->lock);
+ mcp->set_telecom_divisor(mcp, div);
+ spin_unlock_irq(&mcp->lock);
+}
+
+/**
+ * mcp_set_audio_divisor - set the audio divisor
+ * @mcp: MCP interface structure
+ * @div: SIB clock divisor
+ *
+ * Set the audio divisor on the MCP interface.
+ */
+void mcp_set_audio_divisor(struct mcp *mcp, unsigned int div)
+{
+ spin_lock_irq(&mcp->lock);
+ mcp->set_audio_divisor(mcp, div);
+ spin_unlock_irq(&mcp->lock);
+}
+
+/**
+ * mcp_reg_write - write a device register
+ * @mcp: MCP interface structure
+ * @reg: 4-bit register index
+ * @val: 16-bit data value
+ *
+ * Write a device register. The MCP interface must be enabled
+ * to prevent this function hanging.
+ */
+void mcp_reg_write(struct mcp *mcp, unsigned int reg, unsigned int val)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&mcp->lock, flags);
+ mcp->reg_write(mcp, reg, val);
+ spin_unlock_irqrestore(&mcp->lock, flags);
+}
+
+/**
+ * mcp_reg_read - read a device register
+ * @mcp: MCP interface structure
+ * @reg: 4-bit register index
+ *
+ * Read a device register and return its value. The MCP interface
+ * must be enabled to prevent this function hanging.
+ */
+unsigned int mcp_reg_read(struct mcp *mcp, unsigned int reg)
+{
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&mcp->lock, flags);
+ val = mcp->reg_read(mcp, reg);
+ spin_unlock_irqrestore(&mcp->lock, flags);
+
+ return val;
+}
+
+/**
+ * mcp_enable - enable the MCP interface
+ * @mcp: MCP interface to enable
+ *
+ * Enable the MCP interface. Each call to mcp_enable will need
+ * a corresponding call to mcp_disable to disable the interface.
+ */
+void mcp_enable(struct mcp *mcp)
+{
+ spin_lock_irq(&mcp->lock);
+ if (mcp->use_count++ == 0)
+ mcp->enable(mcp);
+ spin_unlock_irq(&mcp->lock);
+}
+
+/**
+ * mcp_disable - disable the MCP interface
+ * @mcp: MCP interface to disable
+ *
+ * Disable the MCP interface. The MCP interface will only be
+ * disabled once the number of calls to mcp_enable matches the
+ * number of calls to mcp_disable.
+ */
+void mcp_disable(struct mcp *mcp)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&mcp->lock, flags);
+ if (--mcp->use_count == 0)
+ mcp->disable(mcp);
+ spin_unlock_irqrestore(&mcp->lock, flags);
+}
+
+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)
+{
+ 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)
+{
+ 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)
+{
+ mcpdrv->drv.bus = &mcp_bus_type;
+ mcpdrv->drv.probe = mcp_bus_probe;
+ mcpdrv->drv.remove = mcp_bus_remove;
+ return driver_register(&mcpdrv->drv);
+}
+
+void mcp_driver_unregister(struct mcp_driver *mcpdrv)
+{
+ driver_unregister(&mcpdrv->drv);
+}
+
+static int __init mcp_init(void)
+{
+ return bus_register(&mcp_bus_type);
+}
+
+static void __exit mcp_exit(void)
+{
+ bus_unregister(&mcp_bus_type);
+}
+
+module_init(mcp_init);
+module_exit(mcp_exit);
+
+EXPORT_SYMBOL(mcp_set_telecom_divisor);
+EXPORT_SYMBOL(mcp_set_audio_divisor);
+EXPORT_SYMBOL(mcp_reg_write);
+EXPORT_SYMBOL(mcp_reg_read);
+EXPORT_SYMBOL(mcp_enable);
+EXPORT_SYMBOL(mcp_disable);
+EXPORT_SYMBOL(mcp_host_register);
+EXPORT_SYMBOL(mcp_host_unregister);
+EXPORT_SYMBOL(mcp_driver_register);
+EXPORT_SYMBOL(mcp_driver_unregister);
+
+MODULE_AUTHOR("Russell King <r...@arm.linux.org.uk>");
+MODULE_DESCRIPTION("Core multimedia communications port driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/mcp-sa1100.c b/drivers/misc/mcp-sa1100.c
new file mode 100644
--- /dev/null
+++ b/drivers/misc/mcp-sa1100.c
@@ -0,0 +1,287 @@
+/*
+ * linux/drivers/misc/mcp-sa1100.c
+ *
+ * Copyright (C) 2001 Russell King
+ *
+ * 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.
+ *
+ * SA1100 MCP (Multimedia Communications Port) driver.
+ *
+ * MCP read/write timeouts from Jordi Colomer, rehacked by rmk.
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+
+#include <asm/dma.h>
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
+#include <asm/system.h>
+
+#include <asm/arch/assabet.h>
+
+#include <asm/arch-sa1100/mcp.h>
+
+
+static void
+mcp_sa1100_set_telecom_divisor(struct mcp *mcp, unsigned int divisor)
+{
+ unsigned int mccr0;
+
+ divisor /= 32;
+
+ mccr0 = Ser4MCCR0 & ~0x00007f00;
+ mccr0 |= divisor << 8;
+ Ser4MCCR0 = mccr0;
+}
+
+static void
+mcp_sa1100_set_audio_divisor(struct mcp *mcp, unsigned int divisor)
+{
+ unsigned int mccr0;
+
+ divisor /= 32;
+
+ mccr0 = Ser4MCCR0 & ~0x0000007f;
+ mccr0 |= divisor;
+ Ser4MCCR0 = mccr0;
+}
+
+/*
+ * Write data to the device. The bit should be set after 3 subframe
+ * times (each frame is 64 clocks). We wait a maximum of 6 subframes.
+ * We really should try doing something more productive while we
+ * wait.
+ */
+static void
+mcp_sa1100_write(struct mcp *mcp, unsigned int reg, unsigned int val)
+{
+ int ret = -ETIME;
+ int i;
+
+ Ser4MCDR2 = reg << 17 | MCDR2_Wr | (val & 0xffff);
+
+ for (i = 0; i < 2; i++) {
+ udelay(mcp->rw_timeout);
+ if (Ser4MCSR & MCSR_CWC) {
+ ret = 0;
+ break;
+ }
+ }
+
+ if (ret < 0)
+ printk(KERN_WARNING "mcp: write timed out\n");
+}
+
+/*
+ * Read data from the device. The bit should be set after 3 subframe
+ * times (each frame is 64 clocks). We wait a maximum of 6 subframes.
+ * We really should try doing something more productive while we
+ * wait.
+ */
+static unsigned int
+mcp_sa1100_read(struct mcp *mcp, unsigned int reg)
+{
+ int ret = -ETIME;
+ int i;
+
+ Ser4MCDR2 = reg << 17 | MCDR2_Rd;
+
+ for (i = 0; i < 2; i++) {
+ udelay(mcp->rw_timeout);
+ if (Ser4MCSR & MCSR_CRC) {
+ ret = Ser4MCDR2 & 0xffff;
+ break;
+ }
+ }
+
+ if (ret < 0)
+ printk(KERN_WARNING "mcp: read timed out\n");
+
+ return ret;
+}
+
+static void mcp_sa1100_enable(struct mcp *mcp)
+{
+ Ser4MCSR = -1;
+ Ser4MCCR0 |= MCCR0_MCE;
+}
+
+static void mcp_sa1100_disable(struct mcp *mcp)
+{
+ Ser4MCCR0 &= ~MCCR0_MCE;
+}
+
+/*
+ * Our methods.
+ */
+static struct mcp mcp_sa1100 = {
+ .owner = THIS_MODULE,
+ .lock = SPIN_LOCK_UNLOCKED,
+ .sclk_rate = 11981000,
+ .dma_audio_rd = DMA_Ser4MCP0Rd,
+ .dma_audio_wr = DMA_Ser4MCP0Wr,
+ .dma_telco_rd = DMA_Ser4MCP1Rd,
+ .dma_telco_wr = DMA_Ser4MCP1Wr,
+ .set_telecom_divisor = mcp_sa1100_set_telecom_divisor,
+ .set_audio_divisor = mcp_sa1100_set_audio_divisor,
+ .reg_write = mcp_sa1100_write,
+ .reg_read = mcp_sa1100_read,
+ .enable = mcp_sa1100_enable,
+ .disable = mcp_sa1100_disable,
+};
+
+static int mcp_sa1100_probe(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mcp *mcp = &mcp_sa1100;
+ int ret;
+
+ if (!machine_is_adsbitsy() && !machine_is_assabet() &&
+ !machine_is_cerf() && !machine_is_flexanet() &&
+ !machine_is_freebird() && !machine_is_graphicsclient() &&
+ !machine_is_graphicsmaster() && !machine_is_lart() &&
+ !machine_is_omnimeter() && !machine_is_pfs168() &&
+ !machine_is_shannon() && !machine_is_simpad() &&
+ !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")) {
+ printk(KERN_ERR "MCP-sa1100: Unable to request memory region\n");
+ return -EBUSY;
+ }
+
+ mcp->me = dev;
+ dev_set_drvdata(dev, mcp);
+
+ if (machine_is_assabet()) {
+ 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.
+ */
+ PPDR &= ~PPC_RXD4;
+ PPDR |= PPC_TXD4 | PPC_SCLK | PPC_SFRM;
+ PSDR |= PPC_RXD4;
+ PSDR &= ~(PPC_TXD4 | PPC_SCLK | PPC_SFRM);
+ PPSR &= ~(PPC_TXD4 | PPC_SCLK | PPC_SFRM);
+
+ Ser4MCSR = -1;
+ Ser4MCCR1 = 0;
+ //Ser4MCCR0 = 0x00007f7f | MCCR0_ADM;
+ Ser4MCCR0 = MCCR0_ADM | MCCR0_ExtClk;
+
+ /*
+ * Calculate the read/write timeout (us) from the bit clock
+ * rate. This is the period for 3 64-bit frames. Always
+ * round this time up.
+ */
+ mcp->rw_timeout = (64 * 3 * 1000000 + mcp->sclk_rate - 1) /
+ mcp->sclk_rate;
+
+ ret = mcp_host_register(mcp, &pdev->dev);
+ if (ret != 0) {
+ release_mem_region(0x80060000, 0x60);
+ dev_set_drvdata(dev, NULL);
+ }
+
+ return ret;
+}
+
+static int mcp_sa1100_remove(struct device *dev)
+{
+ struct mcp *mcp = dev_get_drvdata(dev);
+
+ dev_set_drvdata(dev, NULL);
+
+ mcp_host_unregister(mcp);
+ release_mem_region(0x80060000, 0x60);
+
+ return 0;
+}
+
+struct mcp_sa1100_state {
+ u32 mccr0;
+ u32 mccr1;
+};
+
+static int mcp_sa1100_suspend(struct device *dev, pm_message_t state, u32 level)
+{
+ struct mcp_sa1100_state *s = (struct mcp_sa1100_state *)dev->power.saved_state;
+
+ if (!s) {
+ s = kmalloc(sizeof(struct mcp_sa1100_state), GFP_KERNEL);
+ dev->power.saved_state = (unsigned char *)s;
+ }
+
+ if (s) {
+ s->mccr0 = Ser4MCCR0;
+ s->mccr1 = Ser4MCCR1;
+ }
+
+ if (level == SUSPEND_DISABLE)
+ Ser4MCCR0 &= ~MCCR0_MCE;
+ return 0;
+}
+
+static int mcp_sa1100_resume(struct device *dev, u32 level)
+{
+ struct mcp_sa1100_state *s = (struct mcp_sa1100_state *)dev->power.saved_state;
+
+ if (s && level == RESUME_RESTORE_STATE) {
+ Ser4MCCR1 = s->mccr1;
+ Ser4MCCR0 = s->mccr0;
+
+ dev->power.saved_state = NULL;
+ kfree(s);
+ }
+ return 0;
+}
+
+/*
+ * The driver for the SA11x0 MCP port.
+ */
+static struct device_driver mcp_sa1100_driver = {
+ .name = "sa11x0-mcp",
+ .bus = &platform_bus_type,
+ .probe = mcp_sa1100_probe,
+ .remove = mcp_sa1100_remove,
+ .suspend = mcp_sa1100_suspend,
+ .resume = mcp_sa1100_resume,
+};
+
+/*
+ * This needs re-working
+ */
+static int __init mcp_sa1100_init(void)
+{
+ return driver_register(&mcp_sa1100_driver);
+}
+
+static void __exit mcp_sa1100_exit(void)
+{
+ driver_unregister(&mcp_sa1100_driver);
+}
+
+module_init(mcp_sa1100_init);
+module_exit(mcp_sa1100_exit);
+
+MODULE_AUTHOR("Russell King <r...@arm.linux.org.uk>");
+MODULE_DESCRIPTION("SA11x0 multimedia communications port driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/ucb1x00-core.c b/drivers/misc/ucb1x00-core.c
new file mode 100644
--- /dev/null
+++ b/drivers/misc/ucb1x00-core.c
@@ -0,0 +1,638 @@
+/*
+ * linux/drivers/misc/ucb1x00-core.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 as published by
+ * the Free Software Foundation; either version 2 of the License.
+ *
+ * The UCB1x00 core driver provides basic services for handling IO,
+ * the ADC, interrupts, and accessing registers. It is designed
+ * such that everything goes through this layer, thereby providing
+ * a consistent locking methodology, as well as allowing the drivers
+ * to be used on other non-MCP-enabled hardware platforms.
+ *
+ * Note that all locks are private to this file. Nothing else may
+ * touch them.
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+
+#include <asm/dma.h>
+#include <asm/hardware.h>
+#include <asm/irq.h>
+
+#include <asm/arch-sa1100/ucb1x00.h>
+
+/**
+ * ucb1x00_io_set_dir - set IO direction
+ * @ucb: UCB1x00 structure describing chip
+ * @in: bitfield of IO pins to be set as inputs
+ * @out: bitfield of IO pins to be set as outputs
+ *
+ * Set the IO direction of the ten general purpose IO pins on
+ * the UCB1x00 chip. The @in bitfield has priority over the
+ * @out bitfield, in that if you specify a pin as both input
+ * and output, it will end up as an input.
+ *
+ * ucb1x00_enable must have been called to enable the comms
+ * before using this function.
+ *
+ * This function takes a spinlock, disabling interrupts.
+ */
+void ucb1x00_io_set_dir(struct ucb1x00 *ucb, unsigned int in, unsigned int out)
+{
+ unsigned long flags;
+
+ 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);
+}
+
+/**
+ * ucb1x00_io_write - set or clear IO outputs
+ * @ucb: UCB1x00 structure describing chip
+ * @set: bitfield of IO pins to set to logic '1'
+ * @clear: bitfield of IO pins to set to logic '0'
+ *
+ * Set the IO output state of the specified IO pins. The value
+ * is retained if the pins are subsequently configured as inputs.
+ * The @clear bitfield has priority over the @set bitfield -
+ * outputs will be cleared.
+ *
+ * ucb1x00_enable must have been called to enable the comms
+ * before using this function.
+ *
+ * This function takes a spinlock, disabling interrupts.
+ */
+void ucb1x00_io_write(struct ucb1x00 *ucb, unsigned int set, unsigned int clear)
+{
+ unsigned long flags;
+
+ 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);
+}
+
+/**
+ * ucb1x00_io_read - read the current state of the IO pins
+ * @ucb: UCB1x00 structure describing chip
+ *
+ * Return a bitfield describing the logic state of the ten
+ * general purpose IO pins.
+ *
+ * ucb1x00_enable must have been called to enable the comms
+ * before using this function.
+ *
+ * This function does not take any semaphores or spinlocks.
+ */
+unsigned int ucb1x00_io_read(struct ucb1x00 *ucb)
+{
+ return ucb1x00_reg_read(ucb, UCB_IO_DATA);
+}
+
+/*
+ * UCB1300 data sheet says we must:
+ * 1. enable ADC => 5us (including reference startup time)
+ * 2. select input => 51*tsibclk => 4.3us
+ * 3. start conversion => 102*tsibclk => 8.5us
+ * (tsibclk = 1/11981000)
+ * Period between SIB 128-bit frames = 10.7us
+ */
+
+/**
+ * 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.
+ *
+ * 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 ucb1x00_adc_enable(struct ucb1x00 *ucb)
+{
+ down(&ucb->adc_sem);
+
+ ucb->adc_cr |= UCB_ADC_ENA;
+
+ ucb1x00_enable(ucb);
+ ucb1x00_reg_write(ucb, UCB_ADC_CR, ucb->adc_cr);
+}
+
+/**
+ * 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. Note that
+ * synchronised ADC conversions (via the ADCSYNC pin) must wait
+ * until the trigger is asserted and the conversion is finished.
+ *
+ * This function currently spins waiting for the conversion to
+ * complete (2 frames max without sync).
+ *
+ * If called for a synchronised ADC conversion, it may sleep
+ * with the ADC semaphore held.
+ */
+unsigned int ucb1x00_adc_read(struct ucb1x00 *ucb, int adc_channel, int sync)
+{
+ unsigned int val;
+
+ if (sync)
+ adc_channel |= UCB_ADC_SYNC_ENA;
+
+ ucb1x00_reg_write(ucb, UCB_ADC_CR, ucb->adc_cr | adc_channel);
+ ucb1x00_reg_write(ucb, UCB_ADC_CR, ucb->adc_cr | adc_channel | UCB_ADC_START);
+
+ for (;;) {
+ val = ucb1x00_reg_read(ucb, UCB_ADC_DATA);
+ if (val & UCB_ADC_DAT_VAL)
+ break;
+ /* yield to other processes */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ }
+
+ return UCB_ADC_DAT(val);
+}
+
+/**
+ * ucb1x00_adc_disable - disable the ADC converter
+ * @ucb: UCB1x00 structure describing chip
+ *
+ * Disable the ADC converter and release the ADC semaphore.
+ */
+void ucb1x00_adc_disable(struct ucb1x00 *ucb)
+{
+ ucb->adc_cr &= ~UCB_ADC_ENA;
+ ucb1x00_reg_write(ucb, UCB_ADC_CR, ucb->adc_cr);
+ ucb1x00_disable(ucb);
+
+ up(&ucb->adc_sem);
+}
+
+/*
+ * UCB1x00 Interrupt handling.
+ *
+ * The UCB1x00 can generate interrupts when the SIBCLK is stopped.
+ * Since we need to read an internal register, we must re-enable
+ * SIBCLK to talk to the chip. We leave the clock running until
+ * we have finished processing all interrupts from the chip.
+ */
+static irqreturn_t ucb1x00_irq(int irqnr, void *devid, struct pt_regs *regs)
+{
+ struct ucb1x00 *ucb = devid;
+ struct ucb1x00_irq *irq;
+ unsigned int isr, i;
+
+ ucb1x00_enable(ucb);
+ isr = ucb1x00_reg_read(ucb, UCB_IE_STATUS);
+ ucb1x00_reg_write(ucb, UCB_IE_CLEAR, isr);
+ ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0);
+
+ for (i = 0, irq = ucb->irq_handler; i < 16 && isr; i++, isr >>= 1, irq++)
+ if (isr & 1 && irq->fn)
+ irq->fn(i, irq->devid);
+ ucb1x00_disable(ucb);
+
+ 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
+ * @idx: interrupt index
+ * @fn: function to call when interrupt is triggered
+ * @devid: device id to pass to interrupt handler
+ *
+ * Hook the specified interrupt. You can only register one handler
+ * for each interrupt source. The interrupt source is not enabled
+ * by this function; use ucb1x00_enable_irq instead.
+ *
+ * Interrupt handlers will be called with other interrupts enabled.
+ *
+ * Returns zero on success, or one of the following errors:
+ * -EINVAL if the interrupt index is invalid
+ * -EBUSY if the interrupt has already been hooked
+ */
+int ucb1x00_hook_irq(struct ucb1x00 *ucb, unsigned int idx, void (*fn)(int, void *), void *devid)
+{
+ struct ucb1x00_irq *irq;
+ int ret = -EINVAL;
+
+ if (idx < 16) {
+ irq = ucb->irq_handler + idx;
+ ret = -EBUSY;
+
+ spin_lock_irq(&ucb->lock);
+ if (irq->fn == NULL) {
+ irq->devid = devid;
+ irq->fn = fn;
+ ret = 0;
+ }
+ spin_unlock_irq(&ucb->lock);
+ }
+ return ret;
+}
+
+/**
+ * ucb1x00_enable_irq - enable an UCB1x00 interrupt source
+ * @ucb: UCB1x00 structure describing chip
+ * @idx: interrupt index
+ * @edges: interrupt edges to enable
+ *
+ * Enable the specified interrupt to trigger on %UCB_RISING,
+ * %UCB_FALLING or both edges. The interrupt should have been
+ * hooked by ucb1x00_hook_irq.
+ */
+void ucb1x00_enable_irq(struct ucb1x00 *ucb, unsigned int idx, int edges)
+{
+ unsigned long flags;
+
+ if (idx < 16) {
+ spin_lock_irqsave(&ucb->lock, flags);
+ if (edges & UCB_RISING)
+ ucb->irq_ris_enbl |= 1 << idx;
+ if (edges & UCB_FALLING)
+ ucb->irq_fal_enbl |= 1 << idx;
+ 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);
+ }
+}
+
+/**
+ * ucb1x00_disable_irq - disable an UCB1x00 interrupt source
+ * @ucb: UCB1x00 structure describing chip
+ * @edges: interrupt edges to disable
+ *
+ * Disable the specified interrupt triggering on the specified
+ * (%UCB_RISING, %UCB_FALLING or both) edges.
+ */
+void ucb1x00_disable_irq(struct ucb1x00 *ucb, unsigned int idx, int edges)
+{
+ unsigned long flags;
+
+ if (idx < 16) {
+ spin_lock_irqsave(&ucb->lock, flags);
+ if (edges & UCB_RISING)
+ ucb->irq_ris_enbl &= ~(1 << idx);
+ if (edges & UCB_FALLING)
+ ucb->irq_fal_enbl &= ~(1 << idx);
+ 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);
+ }
+}
+
+/**
+ * ucb1x00_free_irq - disable and free the specified UCB1x00 interrupt
+ * @ucb: UCB1x00 structure describing chip
+ * @idx: interrupt index
+ * @devid: device id.
+ *
+ * Disable the interrupt source and remove the handler. devid must
+ * match the devid passed when hooking the interrupt.
+ *
+ * Returns zero on success, or one of the following errors:
+ * -EINVAL if the interrupt index is invalid
+ * -ENOENT if devid does not match
+ */
+int ucb1x00_free_irq(struct ucb1x00 *ucb, unsigned int idx, void *devid)
+{
+ struct ucb1x00_irq *irq;
+ int ret;
+
+ if (idx >= 16)
+ goto bad;
+
+ irq = ucb->irq_handler + idx;
+ ret = -ENOENT;
+
+ spin_lock_irq(&ucb->lock);
+ if (irq->devid == devid) {
+ ucb->irq_ris_enbl &= ~(1 << idx);
+ ucb->irq_fal_enbl &= ~(1 << idx);
+
+ 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:
+ printk(KERN_ERR "Freeing bad UCB1x00 irq %d\n", idx);
+ return -EINVAL;
+}
+
+/*
+ * Try to probe our interrupt, rather than relying on lots of
+ * hard-coded machine dependencies. For reference, the expected
+ * IRQ mappings are:
+ *
+ * Machine Default IRQ
+ * adsbitsy IRQ_GPCIN4
+ * cerf IRQ_GPIO_UCB1200_IRQ
+ * flexanet IRQ_GPIO_GUI
+ * freebird IRQ_GPIO_FREEBIRD_UCB1300_IRQ
+ * graphicsclient ADS_EXT_IRQ(8)
+ * graphicsmaster ADS_EXT_IRQ(8)
+ * lart LART_IRQ_UCB1200
+ * omnimeter IRQ_GPIO23
+ * pfs168 IRQ_GPIO_UCB1300_IRQ
+ * simpad IRQ_GPIO_UCB1300_IRQ
+ * shannon SHANNON_IRQ_GPIO_IRQ_CODEC
+ * yopy IRQ_GPIO_UCB1200_IRQ
+ */
+static int ucb1x00_detect_irq(struct ucb1x00 *ucb)
+{
+ unsigned long mask;
+
+ mask = probe_irq_on();
+ if (!mask)
+ return NO_IRQ;
+
+ /*
+ * Enable the ADC interrupt.
+ */
+ ucb1x00_reg_write(ucb, UCB_IE_RIS, UCB_IE_ADC);
+ ucb1x00_reg_write(ucb, UCB_IE_FAL, UCB_IE_ADC);
+ ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0xffff);
+ ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0);
+
+ /*
+ * Cause an ADC interrupt.
+ */
+ ucb1x00_reg_write(ucb, UCB_ADC_CR, UCB_ADC_ENA);
+ ucb1x00_reg_write(ucb, UCB_ADC_CR, UCB_ADC_ENA | UCB_ADC_START);
+
+ /*
+ * Wait for the conversion to complete.
+ */
+ while ((ucb1x00_reg_read(ucb, UCB_ADC_DATA) & UCB_ADC_DAT_VAL) == 0);
+ ucb1x00_reg_write(ucb, UCB_ADC_CR, 0);
+
+ /*
+ * Disable and clear interrupt.
+ */
+ ucb1x00_reg_write(ucb, UCB_IE_RIS, 0);
+ ucb1x00_reg_write(ucb, UCB_IE_FAL, 0);
+ ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0xffff);
+ ucb1x00_reg_write(ucb, UCB_IE_CLEAR, 0);
+
+ /*
+ * Read triggered interrupt.
+ */
+ return probe_irq_off(mask);
+}
+
+static int ucb1x00_probe(struct mcp *mcp)
+{
+ struct ucb1x00 *ucb;
+ 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 && 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;
+ if (!ucb)
+ goto err_disable;
+
+ memset(ucb, 0, sizeof(struct ucb1x00));
+
+ ucb->cdev.class = &ucb1x00_class;
+ 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->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");
+ ret = -ENODEV;
+ goto err_free;
+ }
+
+ 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);
+ goto err_free;
+ }
+
+ set_irq_type(ucb->irq, IRQT_RISING);
+ mcp_set_drvdata(mcp, ucb);
+
+ ret = class_device_register(&ucb->cdev);
+
+ 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;
+ }
+ }
+
+ if (ret) {
+ free_irq(ucb->irq, ucb);
+ err_free:
+ kfree(ucb);
+ }
+ err_disable:
+ mcp_disable(mcp);
+ return ret;
+}
+
+static void ucb1x00_remove(struct mcp *mcp)
+{
+ struct ucb1x00 *ucb = mcp_get_drvdata(mcp);
+
+ 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);
+ }
+ free_irq(ucb->irq, ucb);
+}
+
+static void ucb1x00_release(struct class_device *dev)
+{
+ struct ucb1x00 *ucb = classdev_to_ucb1x00(dev);
+ kfree(ucb);
+}
+
+static struct class ucb1x00_class = {
+ .name = "ucb1x00",
+ .release = ucb1x00_release,
+};
+
+int ucb1x00_register_interface(struct class_interface *intf)
+{
+ intf->class = &ucb1x00_class;
+ return class_interface_register(intf);
+}
+
+void ucb1x00_unregister_interface(struct class_interface *intf)
+{
+ class_interface_unregister(intf);
+}
+
+static struct mcp_driver ucb1x00_driver = {
+ .drv = {
+ .name = "ucb1x00",
+ },
+ .probe = ucb1x00_probe,
+ .remove = ucb1x00_remove,
+};
+
+static int __init ucb1x00_init(void)
+{
+ int ret = class_register(&ucb1x00_class);
+ if (ret == 0) {
+ ret = mcp_driver_register(&ucb1x00_driver);
+ if (ret)
+ class_unregister(&ucb1x00_class);
+ }
+ return ret;
+}
+
+static void __exit ucb1x00_exit(void)
+{
+ mcp_driver_unregister(&ucb1x00_driver);
+ class_unregister(&ucb1x00_class);
+}
+
+module_init(ucb1x00_init);
+module_exit(ucb1x00_exit);
+
+EXPORT_SYMBOL(ucb1x00_class);
+
+EXPORT_SYMBOL(ucb1x00_io_set_dir);
+EXPORT_SYMBOL(ucb1x00_io_write);
+EXPORT_SYMBOL(ucb1x00_io_read);
+
+EXPORT_SYMBOL(ucb1x00_adc_enable);
+EXPORT_SYMBOL(ucb1x00_adc_read);
+EXPORT_SYMBOL(ucb1x00_adc_disable);
+
+EXPORT_SYMBOL(ucb1x00_hook_irq);
+EXPORT_SYMBOL(ucb1x00_free_irq);
+EXPORT_SYMBOL(ucb1x00_enable_irq);
+EXPORT_SYMBOL(ucb1x00_disable_irq);
+
+EXPORT_SYMBOL(ucb1x00_register_interface);
+EXPORT_SYMBOL(ucb1x00_unregister_interface);
+
+MODULE_AUTHOR("Russell King <r...@arm.linux.org.uk>");
+MODULE_DESCRIPTION("UCB1x00 core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pcmcia/sa1100_generic.h b/drivers/pcmcia/sa1100_generic.h
--- a/drivers/pcmcia/sa1100_generic.h
+++ b/drivers/pcmcia/sa1100_generic.h
@@ -8,6 +8,7 @@ extern int pcmcia_adsbitsy_init(struct d
extern int pcmcia_assabet_init(struct device *);
extern int pcmcia_badge4_init(struct device *);
extern int pcmcia_cerf_init(struct device *);
+extern int pcmcia_collie_init(struct device *);
extern int pcmcia_flexanet_init(struct device *);
extern int pcmcia_freebird_init(struct device *);
extern int pcmcia_gcplus_init(struct device *);
diff --git a/include/asm-arm/arch-sa1100/mcp.h b/include/asm-arm/arch-sa1100/mcp.h
new file mode 100644
--- /dev/null
+++ b/include/asm-arm/arch-sa1100/mcp.h
@@ -0,0 +1,65 @@
+/*
+ * linux/drivers/misc/mcp.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.
+ */
+#ifndef MCP_H
+#define MCP_H
+
+#ifdef CONFIG_ARCH_SA1100
+#include <asm/dma.h>
+#endif
+
+struct mcp {
+ struct module *owner;
+ struct device *me;
+ spinlock_t lock;
+ int use_count;
+ unsigned int sclk_rate;
+#ifdef CONFIG_ARCH_SA1100
+ unsigned int rw_timeout;
+ dma_device_t dma_audio_rd;
+ dma_device_t dma_audio_wr;
+ dma_device_t dma_telco_rd;
+ dma_device_t dma_telco_wr;
+ void (*set_telecom_divisor)(struct mcp *, unsigned int);
+ void (*set_audio_divisor)(struct mcp *, unsigned int);
+#endif
+ void (*reg_write)(struct mcp *, unsigned int, unsigned int);
+ unsigned int (*reg_read)(struct mcp *, unsigned int);
+ void (*enable)(struct mcp *);
+ void (*disable)(struct mcp *);
+ struct device *attached_device;
+ struct completion attached_device_released;
+};
+
+void mcp_set_telecom_divisor(struct mcp *, unsigned int);
+void mcp_set_audio_divisor(struct mcp *, unsigned int);
+void mcp_reg_write(struct mcp *, unsigned int, unsigned int);
+unsigned int mcp_reg_read(struct mcp *, unsigned int);
+void mcp_enable(struct mcp *);
+void mcp_disable(struct mcp *);
+#define mcp_get_sclk_rate(mcp) ((mcp)->sclk_rate)
+
+int mcp_host_register(struct mcp *, struct device *);
+void mcp_host_unregister(struct mcp *);
+
+struct mcp_driver {
+ struct device_driver drv;
+ int (*probe)(struct mcp *);
+ void (*remove)(struct mcp *);
+ int (*suspend)(struct mcp *, u32);
+ int (*resume)(struct mcp *);
+};
+
+int mcp_driver_register(struct mcp_driver *);
+void mcp_driver_unregister(struct mcp_driver *);
+
+#define mcp_get_drvdata(mcp) dev_get_drvdata((mcp)->attached_device)
+#define mcp_set_drvdata(mcp,d) dev_set_drvdata((mcp)->attached_device, d)
+
+#endif
diff --git a/include/asm-arm/arch-sa1100/ucb1x00.h b/include/asm-arm/arch-sa1100/ucb1x00.h
new file mode 100644
--- /dev/null
+++ b/include/asm-arm/arch-sa1100/ucb1x00.h
@@ -0,0 +1,270 @@
+/*
+ * linux/drivers/misc/ucb1x00.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.
+ */
+#ifndef UCB1200_H
+#define UCB1200_H
+
+#ifdef CONFIG_ARCH_PXA
+
+/* 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_ID 0x7e /* 7c is mfr id, 7e part id (from aclink spec) */
+
+#define UCB_ADC_DAT(x) ((x) & 0x3ff)
+
+#else
+
+/* ucb1x00 SIB register mappings: */
+
+#define UCB_IO_DATA 0x00
+#define UCB_IO_DIR 0x01
+#define UCB_IE_RIS 0x02
+#define UCB_IE_FAL 0x03
+#define UCB_IE_STATUS 0x04
+#define UCB_IE_CLEAR 0x04
+#define UCB_TC_A 0x05
+#define UCB_TC_B 0x06
+#define UCB_AC_A 0x07
+#define UCB_AC_B 0x08
+#define UCB_TS_CR 0x09
+#define UCB_ADC_CR 0x0a
+#define UCB_ADC_DATA 0x0b
+#define UCB_ID 0x0c
+#define UCB_MODE 0x0d
+
+#define UCB_ADC_DAT(x) (((x) & 0x7fe0) >> 5)
+
+#endif
+
+
+#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_IRQ_TSPX 12
+
+#define UCB_TC_A_LOOP (1 << 7) /* UCB1200 */
+#define UCB_TC_A_AMPL (1 << 7) /* UCB1300 */
+
+#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_LOW (1 << 12)
+#define UCB_TS_CR_TSMX_LOW (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_DAT_VAL (1 << 15)
+
+#define UCB_ID_1200 0x1004
+#define UCB_ID_1300 0x1005
+#define UCB_ID_1400 0x4304
+#define UCB_ID_1400_BUGGY 0x4303 /* fake ID */
+
+#define UCB_MODE_DYN_VFLAG_ENA (1 << 12)
+#define UCB_MODE_AUD_OFF_CAN (1 << 13)
+
+#include "mcp.h"
+
+struct ucb1x00_irq {
+ void *devid;
+ void (*fn)(int, void *);
+};
+
+extern struct class ucb1x00_class;
+
+struct ucb1x00 {
+ struct mcp *mcp; /* this needs to be first */
+ spinlock_t lock;
+ unsigned int irq;
+ struct semaphore adc_sem;
+ spinlock_t io_lock;
+ wait_queue_head_t irq_wait;
+ struct completion complete;
+ struct task_struct *irq_task;
+ u16 id;
+ u16 io_dir;
+ u16 io_out;
+ u16 adc_cr;
+ u16 irq_fal_enbl;
+ u16 irq_ris_enbl;
+ struct ucb1x00_irq irq_handler[16];
+ struct class_device cdev;
+ void *audio_data;
+ void *telecom_data;
+ void *ts_data;
+};
+
+#define classdev_to_ucb1x00(cd) container_of(cd, struct ucb1x00, cdev)
+
+int ucb1x00_register_interface(struct class_interface *intf);
+void ucb1x00_unregister_interface(struct class_interface *intf);
+
+/**
+ * ucb1x00_clkrate - return the UCB1x00 SIB clock rate
+ * @ucb: UCB1x00 structure describing chip
+ *
+ * Return the SIB clock rate in Hz.
+ */
+static inline unsigned int ucb1x00_clkrate(struct ucb1x00 *ucb)
+{
+ return mcp_get_sclk_rate(ucb->mcp);
+}
+
+/**
+ * ucb1x00_enable - enable the UCB1x00 SIB clock
+ * @ucb: UCB1x00 structure describing chip
+ *
+ * Enable the SIB clock. This can be called multiple times.
+ */
+static inline void ucb1x00_enable(struct ucb1x00 *ucb)
+{
+ mcp_enable(ucb->mcp);
+}
+
+/**
+ * ucb1x00_disable - disable the UCB1x00 SIB clock
+ * @ucb: UCB1x00 structure describing chip
+ *
+ * Disable the SIB clock. The SIB clock will only be disabled
+ * when the number of ucb1x00_enable calls match the number of
+ * ucb1x00_disable calls.
+ */
+static inline void ucb1x00_disable(struct ucb1x00 *ucb)
+{
+ mcp_disable(ucb->mcp);
+}
+
+/**
+ * ucb1x00_reg_write - write a UCB1x00 register
+ * @ucb: UCB1x00 structure describing chip
+ * @reg: UCB1x00 4-bit register index to write
+ * @val: UCB1x00 16-bit value to write
+ *
+ * Write the UCB1x00 register @reg with value @val. The SIB
+ * clock must be running for this function to return.
+ */
+static inline void ucb1x00_reg_write(struct ucb1x00 *ucb, unsigned int reg, unsigned int val)
+{
+ mcp_reg_write(ucb->mcp, reg, val);
+}
+
+/**
+ * ucb1x00_reg_read - read a UCB1x00 register
+ * @ucb: UCB1x00 structure describing chip
+ * @reg: UCB1x00 4-bit register index to write
+ *
+ * Read the UCB1x00 register @reg and return its value. The SIB
+ * clock must be running for this function to return.
+ */
+static inline unsigned int ucb1x00_reg_read(struct ucb1x00 *ucb, unsigned int reg)
+{
+ return mcp_reg_read(ucb->mcp, reg);
+}
+/**
+ * ucb1x00_set_audio_divisor -
+ * @ucb: UCB1x00 structure describing chip
+ * @div: SIB clock divisor
+ */
+static inline void ucb1x00_set_audio_divisor(struct ucb1x00 *ucb, unsigned int div)
+{
+ mcp_set_audio_divisor(ucb->mcp, div);
+}
+
+/**
+ * ucb1x00_set_telecom_divisor -
+ * @ucb: UCB1x00 structure describing chip
+ * @div: SIB clock divisor
+ */
+static inline void ucb1x00_set_telecom_divisor(struct ucb1x00 *ucb, unsigned int div)
+{
+ mcp_set_telecom_divisor(ucb->mcp, div);
+}
+
+#define ucb1x00_get() NULL
+
+void ucb1x00_io_set_dir(struct ucb1x00 *ucb, unsigned int, unsigned int);
+void ucb1x00_io_write(struct ucb1x00 *ucb, unsigned int, unsigned int);
+unsigned int ucb1x00_io_read(struct ucb1x00 *ucb);
+
+#define UCB_NOSYNC (0)
+#define UCB_SYNC (1)
+
+unsigned int ucb1x00_adc_read(struct ucb1x00 *ucb, int adc_channel, int sync);
+void ucb1x00_adc_enable(struct ucb1x00 *ucb);
+void ucb1x00_adc_disable(struct ucb1x00 *ucb);
+
+/*
+ * Which edges of the IRQ do you want to control today?
+ */
+#define UCB_RISING (1 << 0)
+#define UCB_FALLING (1 << 1)
+
+int ucb1x00_hook_irq(struct ucb1x00 *ucb, unsigned int idx, void (*fn)(int, void *), void *devid);
+void ucb1x00_enable_irq(struct ucb1x00 *ucb, unsigned int idx, int edges);
+void ucb1x00_disable_irq(struct ucb1x00 *ucb, unsigned int idx, int edges);
+int ucb1x00_free_irq(struct ucb1x00 *ucb, unsigned int idx, void *devid);
+
+#endif
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majo...@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

Pavel Machek

unread,
Jul 22, 2005, 8:40:13 PM7/22/05
to
This adds support for touchscreen on Sharp Zaurus sl-5500. Vojtech,
please apply,

Signed-off-by: Pavel Machek <pa...@suse.cz>

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -36,6 +36,15 @@ config TOUCHSCREEN_CORGI
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
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -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
diff --git a/drivers/input/touchscreen/collie_ts.c b/drivers/input/touchscreen/collie_ts.c


new file mode 100644
--- /dev/null

+++ b/drivers/input/touchscreen/collie_ts.c
@@ -0,0 +1,377 @@
+/*
+ * 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 <j...@ict.es> :
+ *
+ * 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 completion init_exit;
+ 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;
+ allow_signal(SIGKILL);
+
+ /*
+ * 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;
+

+ complete(&ts->init_exit);
+
+ 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 (signal_pending(tsk))
+ break;
+ }

+
+ ts->rtask = NULL;
+ complete_and_exit(&ts->init_exit, 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;
+
+ if (ts->rtask)
+ panic("ucb1x00: rtask running?");
+
+ 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);
+
+ init_completion(&ts->init_exit);
+ task = kthread_run(ucb1x00_thread, ts, "ktsd");
+ if (!IS_ERR(task)) {
+ wait_for_completion(&ts->init_exit);
+ 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) {
+ send_sig(SIGKILL, ts->rtask, 1);
+ wait_for_completion(&ts->init_exit);
+ }
+
+ 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 <r...@arm.linux.org.uk>");

+MODULE_DESCRIPTION("UCB1x00 touchscreen driver");
+MODULE_LICENSE("GPL");

--
teflon -- maybe it is a trademark, but it should not be.

Dmitry Torokhov

unread,
Jul 23, 2005, 12:00:10 AM7/23/05
to
On Friday 22 July 2005 19:28, Pavel Machek wrote:
> This adds support for touchscreen on Sharp Zaurus sl-5500. Vojtech,
> please apply,

I have couple more commnets...

> +static int ucb1x00_thread(void *_ts)
> +{
> + struct ucb1x00_ts *ts = _ts;
> + struct task_struct *tsk = current;
> + int valid;
> +
> + ts->rtask = tsk;
> + allow_signal(SIGKILL);

This is not needed...

> +
> + /*
> + * 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;
> +
> + complete(&ts->init_exit);
> +

Neither this one - kthread_create does not return until thread is actually
created and started.

> +
> + if (signal_pending(tsk))
> + break;

if (kthread_should_stop(..))
break;

> + }
> +
> + ts->rtask = NULL;
> + complete_and_exit(&ts->init_exit, 0);

This is not needed.

> +static int ucb1x00_ts_open(struct input_dev *idev)
> +{
> + struct ucb1x00_ts *ts = (struct ucb1x00_ts *)idev;
> + int ret = 0;
> + struct task_struct *task;
> +
> + if (ts->rtask)
> + panic("ucb1x00: rtask running?");
> +

Do you really need to panic here???

> +
> + init_completion(&ts->init_exit);
> + task = kthread_run(ucb1x00_thread, ts, "ktsd");
> + if (!IS_ERR(task)) {
> + wait_for_completion(&ts->init_exit);

Just call kthread_run() and kill that init_exit completion.

> +static void ucb1x00_ts_close(struct input_dev *idev)
> +{
> + struct ucb1x00_ts *ts = (struct ucb1x00_ts *)idev;
> +
> + if (ts->rtask) {
> + send_sig(SIGKILL, ts->rtask, 1);
> + wait_for_completion(&ts->init_exit);

kthread_stop().

--
Dmitry

Pavel Machek

unread,
Jul 23, 2005, 9:50:04 AM7/23/05
to
Hi!

> > This adds support for touchscreen on Sharp Zaurus sl-5500. Vojtech,
> > please apply,
>
> I have couple more commnets...

Sorry, I never really worked with kthreads. Applied all those,

> > +static int ucb1x00_ts_open(struct input_dev *idev)
> > +{
> > + struct ucb1x00_ts *ts = (struct ucb1x00_ts *)idev;
> > + int ret = 0;
> > + struct task_struct *task;
> > +
> > + if (ts->rtask)
> > + panic("ucb1x00: rtask running?");
> > +
>
> Do you really need to panic here???

Does BUG_ON() seem better :-). We could also just return failure here,
but I do not see how it could happen => I guess I'd better catch it
with BUG().

Here's what I did, updated patch will follow.
Pavel

diff --git a/drivers/input/touchscreen/collie_ts.c b/drivers/input/touchscreen/collie_ts.c
--- a/drivers/input/touchscreen/collie_ts.c
+++ b/drivers/input/touchscreen/collie_ts.c
@@ -41,7 +41,6 @@ struct ucb1x00_ts {
struct ucb1x00 *ucb;

struct semaphore irq_wait;
- struct completion init_exit;
struct task_struct *rtask;
u16 x_res;
u16 y_res;
@@ -160,7 +159,6 @@ static int ucb1x00_thread(void *_ts)
int valid;

ts->rtask = tsk;
- allow_signal(SIGKILL);

/*


* We run as a real-time thread. However, thus far

@@ -169,10 +167,7 @@ static int ucb1x00_thread(void *_ts)
tsk->policy = SCHED_FIFO;
tsk->rt_priority = 1;

- complete(&ts->init_exit);
-
valid = 0;
-
for (;;) {


unsigned int x, y, p, val;

@@ -237,12 +232,12 @@ static int ucb1x00_thread(void *_ts)
msleep_interruptible(10);
}

- if (signal_pending(tsk))
+ if (kthread_should_stop())
break;
}

ts->rtask = NULL;
- complete_and_exit(&ts->init_exit, 0);
+ return 0;
}

/*
@@ -262,8 +257,7 @@ static int ucb1x00_ts_open(struct input_
int ret = 0;
struct task_struct *task;

- if (ts->rtask)
- panic("ucb1x00: rtask running?");
+ BUG_ON(ts->rtask);

sema_init(&ts->irq_wait, 0);


ret = ucb1x00_hook_irq(ts->ucb, UCB_IRQ_TSPX, ucb1x00_ts_irq, ts);

@@ -279,10 +273,8 @@ static int ucb1x00_ts_open(struct input_
ts->y_res = ucb1x00_ts_read_yres(ts);
ucb1x00_adc_disable(ts->ucb);

- init_completion(&ts->init_exit);


task = kthread_run(ucb1x00_thread, ts, "ktsd");

if (!IS_ERR(task)) {
- wait_for_completion(&ts->init_exit);
ret = 0;
} else {
ucb1x00_free_irq(ts->ucb, UCB_IRQ_TSPX, ts);
@@ -300,10 +292,8 @@ static void ucb1x00_ts_close(struct inpu
{


struct ucb1x00_ts *ts = (struct ucb1x00_ts *)idev;

- if (ts->rtask) {
- send_sig(SIGKILL, ts->rtask, 1);
- wait_for_completion(&ts->init_exit);
- }
+ if (ts->rtask)
+ kthread_stop(ts->rtask);

ucb1x00_enable(ts->ucb);
ucb1x00_free_irq(ts->ucb, UCB_IRQ_TSPX, ts);

--
teflon -- maybe it is a trademark, but it should not be.

Richard Purdie

unread,
Jul 24, 2005, 1:10:17 PM7/24/05
to
On Sun, 2005-07-24 at 17:47 +0100, Russell King wrote:

> On Fri, Jul 22, 2005 at 08:01:09PM +0200, Pavel Machek wrote:
> > 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.
>
> 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.

I've been wondering about suggesting the creation of a drivers/soc
directory. The idea would be for it to contain "system on chip" type
support code. I use that description loosely to fit any code which needs
to support drivers in multiple driver subsections.

An example use in my Zaurus tree is the TSC2101 which contains a
touchscreen, battery monitoring and sound. Handhelds.org has devices
such as the ASIC2/ASIC3 in the ipaqs (and other handhelds) which cover
many different drivers subsections.

Where practical, the sub drivers such as the touchscreen could be placed
into the specific driver areas such as drivers/input/touchscreen/ but
the core chip specific support would be in drivers/soc and the files
would be connected.

Would that be acceptable in mainline?

Richard

randy_dunlap

unread,
Jul 24, 2005, 1:10:16 PM7/24/05
to
On Sun, 24 Jul 2005 17:47:56 +0100 Russell King wrote:

> On Fri, Jul 22, 2005 at 08:01:09PM +0200, Pavel Machek wrote:

> > 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.
>
> 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.

but clearly drivers/misc/ is not empty, unless you mean at its
top level.

The IBM ASM service processor driver is there (added in 2.6.x)
as well as some hdpuftrs/ driver, which is not in any Kconfig file
or defconfig file. :(

---
~Randy

Pavel Machek

unread,
Jul 25, 2005, 10:50:14 AM7/25/05
to
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;

/**


* 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 <r...@arm.linux.org.uk>");
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 <j...@ict.es> :
+ *
+ * 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 <r...@arm.linux.org.uk>");

+MODULE_DESCRIPTION("UCB1x00 touchscreen driver");
+MODULE_LICENSE("GPL");


--
teflon -- maybe it is a trademark, but it should not be.

Dmitry Torokhov

unread,
Jul 25, 2005, 11:20:19 AM7/25/05
to
Hi Pavel,

On 7/24/05, Pavel Machek <pa...@suse.cz> wrote:
>
> 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...

I have couple of nitpicks (below) and one bigger concern - I am
surprised that a driver for a physical device is implemented as an
interface to a class device. This precludes implementing any kind of
power management in the driver and pushes it into the parent and is
generally speaking is a wrong thing to do (IMHO).

If the problem is that you have a single piece of hardware you need to
bind several drivers to - I guess you will have to create a new
sub-device bus for that. Or just register sub-devices on the same bus
the parent device is registered on - I am not sure what is best in
this particular case - I am not familiar with the arch. It is my
understanding that the purpose of interfaces to to present different
"views" to userspace and therefore they are not quie suited for what
you are trying to do...

> +static int ucb1x00_thread(void *_ts)
> +{
> + struct ucb1x00_ts *ts = _ts;
> + struct task_struct *tsk = current;
> + int valid;
> +
> + ts->rtask = tsk;

Just move that assignment into ucb1x00_input_open and kill all this
"current" stuff.

> +
> + /*
> + * 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 (;;) {

Can we change this to "while (!kthread_should_stop())" to make me
completely happy?

Thanks!

--
Dmitry

Russell King

unread,
Jul 25, 2005, 12:00:24 PM7/25/05
to
On Mon, Jul 25, 2005 at 10:16:05AM -0500, Dmitry Torokhov wrote:
> If the problem is that you have a single piece of hardware you need to
> bind several drivers to - I guess you will have to create a new
> sub-device bus for that. Or just register sub-devices on the same bus
> the parent device is registered on - I am not sure what is best in
> this particular case - I am not familiar with the arch.

That is exactly the problem - these kinds of devices do _not_ fit
well into the device model. A struct device for every different
possible sub-unit is completely overkill.

For instance, you may logically use one ADC and some GPIO lines
on the device for X and something else for Y and they logically
end up in different drivers.

The problem is that the parent doesn't actually know how many
devices to create nor what to call them, and they're logically
indistinguishable from each other so there's no logical naming
system.

> Can we change this to "while (!kthread_should_stop())" to make me
> completely happy?

I still ask, and I'll keep repeating this. What is the difference
between this and the reference implementation which is known to
work on other hardware.

Let's not go all out on one implementation for one set of hardware,
but try to work out what we need to do to the generic reference
implementation to make it work on this hardware.

IOW, you're working on the wrong version.

--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of: 2.6 Serial core

Dmitry Torokhov

unread,
Jul 25, 2005, 12:10:09 PM7/25/05
to
On 7/25/05, Russell King <rmk+...@arm.linux.org.uk> wrote:
> On Mon, Jul 25, 2005 at 10:16:05AM -0500, Dmitry Torokhov wrote:
> > If the problem is that you have a single piece of hardware you need to
> > bind several drivers to - I guess you will have to create a new
> > sub-device bus for that. Or just register sub-devices on the same bus
> > the parent device is registered on - I am not sure what is best in
> > this particular case - I am not familiar with the arch.
>
> That is exactly the problem - these kinds of devices do _not_ fit
> well into the device model. A struct device for every different
> possible sub-unit is completely overkill.
>
> For instance, you may logically use one ADC and some GPIO lines
> on the device for X and something else for Y and they logically
> end up in different drivers.
>
> The problem is that the parent doesn't actually know how many
> devices to create nor what to call them, and they're logically
> indistinguishable from each other so there's no logical naming
> system.
>

Then we should probably not try to force them into driver model. Have
parent device register struct device and when sub-drivers register
they could attach class devices (like input devices) directly to the
"main" device thus hiding presence of sub-sections of the chip from
sysfs completely. My point is that we should not be using
class_interface here - its purpose is diferent.

--
Dmitry

Russell King

unread,
Jul 25, 2005, 12:10:08 PM7/25/05
to
On Mon, Jul 25, 2005 at 06:56:07AM +0200, Pavel Machek wrote:
> 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.

It looks like John's version is actually based on a previous revision
of this driver. 8/

> 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...

Err, why should my assabet touchscreen be called "collie_ts" ?
collie is just a platform which happens to use it - it's got
no relevance to the driver naming at all.

> Also it looks to me like mcp.h should go into asm/arch-sa1100, so
> that other drivers can use it...

That doesn't make sense when you have other non-SA1100 devices using
mcp-core.c. Whether that happens or not I've no idea - I can't see
what everyone's using out there (just like I've absolutely zero
idea what collie folk are doing or not doing.)

> > 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?

arch/arm/soc? That means that (a) we end up with another directory to
accumulate crap, (b) it's not a SoC so doesn't belong in a directory
named as such, (c) it means that the MCP and UCB drivers get their
individual files scattered throughout the kernel tree, one in this
directory, one in that directory, one in another random directory.
That's far from ideal.

Anyway, summarising this, the results are that what we have here is
a complete and utter mess. ;(

So, if the collie folk would like to clean their changes up and send
them to me as the driver author, I'll see about integrating them into
my version and we'll take it from there.

--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of: 2.6 Serial core

Russell King

unread,
Jul 25, 2005, 12:20:07 PM7/25/05
to
On Mon, Jul 25, 2005 at 11:02:43AM -0500, Dmitry Torokhov wrote:
> On 7/25/05, Russell King <rmk+...@arm.linux.org.uk> wrote:
> > On Mon, Jul 25, 2005 at 10:16:05AM -0500, Dmitry Torokhov wrote:
> > > If the problem is that you have a single piece of hardware you need to
> > > bind several drivers to - I guess you will have to create a new
> > > sub-device bus for that. Or just register sub-devices on the same bus
> > > the parent device is registered on - I am not sure what is best in
> > > this particular case - I am not familiar with the arch.
> >
> > That is exactly the problem - these kinds of devices do _not_ fit
> > well into the device model. A struct device for every different
> > possible sub-unit is completely overkill.
> >
> > For instance, you may logically use one ADC and some GPIO lines
> > on the device for X and something else for Y and they logically
> > end up in different drivers.
> >
> > The problem is that the parent doesn't actually know how many
> > devices to create nor what to call them, and they're logically
> > indistinguishable from each other so there's no logical naming
> > system.
> >
>
> Then we should probably not try to force them into driver model. Have
> parent device register struct device and when sub-drivers register
> they could attach class devices (like input devices) directly to the
> "main" device thus hiding presence of sub-sections of the chip from
> sysfs completely. My point is that we should not be using
> class_interface here - its purpose is diferent.

If you look at _my_ version, you'll notice that it doesn't use the
class interface stuff. A previous version of it did, and this seems
to be what the collie stuff is based upon.

What I suggest is that the collie folk need to update their driver
to my version so that we don't have two different forks of the same
driver in existance. Then we can start discussing whether things
should be using kthreads or not.

--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of: 2.6 Serial core

Dmitry Torokhov

unread,
Jul 25, 2005, 12:50:10 PM7/25/05
to

I was only commenting on something that was posted on LKML for
inclusion into input subtree that I am interested in. I don't track
ARM development that closely. Where can we see your version, please?



> What I suggest is that the collie folk need to update their driver
> to my version so that we don't have two different forks of the same
> driver in existance. Then we can start discussing whether things
> should be using kthreads or not.

Do you have any reason why, generally speaking, threads should not be
used? They seem to clean up code in drivers quite a bit.

--
Dmitry

Russell King

unread,
Jul 25, 2005, 1:00:21 PM7/25/05
to
On Mon, Jul 25, 2005 at 11:47:25AM -0500, Dmitry Torokhov wrote:
> On 7/25/05, Russell King <rmk+...@arm.linux.org.uk> wrote:
> > If you look at _my_ version, you'll notice that it doesn't use the
> > class interface stuff. A previous version of it did, and this seems
> > to be what the collie stuff is based upon.
>
> I was only commenting on something that was posted on LKML for
> inclusion into input subtree that I am interested in. I don't track
> ARM development that closely. Where can we see your version, please?

See earlier in this thread, 24th July.

> > What I suggest is that the collie folk need to update their driver
> > to my version so that we don't have two different forks of the same
> > driver in existance. Then we can start discussing whether things
> > should be using kthreads or not.
>
> Do you have any reason why, generally speaking, threads should not be
> used? They seem to clean up code in drivers quite a bit.

It depends what the reasoning is behind them. The touchscreen driver
is threaded because it wants to collect touschreen samples independently
of the availability of a user thread. Moreover, obtaining ADC samples
needs a sleeping context since it may take a while to complete.

However, putting all UCB interrupts into a thread does not make sense
to me - if we allow UCB interrupts to sleep, it allows one UCB interrupt
to be processed at the exclusion of the others.

--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of: 2.6 Serial core

Pavel Machek

unread,
Jul 25, 2005, 5:40:13 PM7/25/05
to
Hi!

> > 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...
>
> I have couple of nitpicks (below) and one bigger concern - I am
> surprised that a driver for a physical device is implemented as an
> interface to a class device. This precludes implementing any kind of
> power management in the driver and pushes it into the parent and is
> generally speaking is a wrong thing to do (IMHO).

I'll port my changes to newer version of rmk's tree, that should solve
it.

> > +static int ucb1x00_thread(void *_ts)
> > +{
> > + struct ucb1x00_ts *ts = _ts;
> > + struct task_struct *tsk = current;
> > + int valid;
> > +
> > + ts->rtask = tsk;
>
> Just move that assignment into ucb1x00_input_open and kill all this
> "current" stuff.

It will still want to set the priority, but yes, it cleaned it.

> > + /*
> > + * 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 (;;) {
>
> Can we change this to "while (!kthread_should_stop())" to make me
> completely happy?

:-) Ok.

[Just FYI, I'll post agregated patch when I solve file placement and
port to newer version.]

Cleanups suggested by Dmitri.

---
commit 60814924ed695d863fa226c24b3d4e96054c8b66
tree 0e88e8272c23926c5654ae10bfe14d4cb2af84ab
parent ff30d8505b88064a5f6e6e70bd42028150a864e2
author <pavel@amd.(none)> Mon, 25 Jul 2005 23:35:21 +0200
committer <pavel@amd.(none)> Mon, 25 Jul 2005 23:35:21 +0200

drivers/input/touchscreen/collie_ts.c | 12 ++++--------
1 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/drivers/input/touchscreen/collie_ts.c b/drivers/input/touchscreen/collie_ts.c
--- a/drivers/input/touchscreen/collie_ts.c
+++ b/drivers/input/touchscreen/collie_ts.c

@@ -158,8 +158,6 @@ static int ucb1x00_thread(void *_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.

@@ -168,7 +166,7 @@ static int ucb1x00_thread(void *_ts)
tsk->rt_priority = 1;



valid = 0;
- for (;;) {

+ while (!kthread_should_stop()) {


unsigned int x, y, p, val;

ts->restart = 0;
@@ -231,9 +229,6 @@ static int ucb1x00_thread(void *_ts)

msleep_interruptible(10);
}
-
- if (kthread_should_stop())
- break;
}

ts->rtask = NULL;
@@ -273,11 +268,12 @@ static int ucb1x00_ts_open(struct input_


ts->y_res = ucb1x00_ts_read_yres(ts);
ucb1x00_adc_disable(ts->ucb);

- task = kthread_run(ucb1x00_thread, ts, "ktsd");
- if (!IS_ERR(task)) {
+ ts->rtask = kthread_run(ucb1x00_thread, ts, "ktsd");
+ if (!IS_ERR(ts->task)) {
ret = 0;
} else {
ucb1x00_free_irq(ts->ucb, UCB_IRQ_TSPX, ts);
+ ts->rtask = NULL;
ret = -EFAULT;
}


--
teflon -- maybe it is a trademark, but it should not be.

Pavel Machek

unread,
Jul 25, 2005, 5:50:10 PM7/25/05
to
Hi!

> > Can we change this to "while (!kthread_should_stop())" to make me
> > completely happy?
>
> I still ask, and I'll keep repeating this. What is the difference
> between this and the reference implementation which is known to
> work on other hardware.

I think I posted diffs already, but they were rather big and against
wrong version. I'll try to get better diffs.

> Let's not go all out on one implementation for one set of hardware,
> but try to work out what we need to do to the generic reference
> implementation to make it work on this hardware.

I did not know it is supposed to work on other devices, too. My
fault.
Pavel

--
teflon -- maybe it is a trademark, but it should not be.

Pavel Machek

unread,
Jul 25, 2005, 6:10:11 PM7/25/05
to
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.
>
> It looks like John's version is actually based on a previous revision
> of this driver. 8/

Oops.

> > 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...
>
> Err, why should my assabet touchscreen be called "collie_ts" ?
> collie is just a platform which happens to use it - it's got
> no relevance to the driver naming at all.

Okay, I did not quite realized it was shared.

> > Also it looks to me like mcp.h should go into asm/arch-sa1100, so
> > that other drivers can use it...
>
> That doesn't make sense when you have other non-SA1100 devices using
> mcp-core.c. Whether that happens or not I've no idea - I can't see
> what everyone's using out there (just like I've absolutely zero
> idea what collie folk are doing or not doing.)

set_telecom_divisor relies on CONFIG_SA1100 being set (otherwise it
breaks compilation, because struct members will not be available; at
least in this version), so I doubt it has many non-SA1100 users...

> > > 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?
>
> arch/arm/soc? That means that (a) we end up with another directory to
> accumulate crap, (b) it's not a SoC so doesn't belong in a directory
> named as such, (c) it means that the MCP and UCB drivers get their
> individual files scattered throughout the kernel tree, one in this
> directory, one in that directory, one in another random directory.
> That's far from ideal.

Well, I believe that UCB layer is quite well define and it looks quite
okay for touchscreen driver to be near other touchscreens... ucb-core
still needs to go somewhere, if drivers/misc was vetoed, perhaps
arch/arm/misc would be okay?

> Anyway, summarising this, the results are that what we have here is
> a complete and utter mess. ;(

Yep :-(.

> So, if the collie folk would like to clean their changes up and send
> them to me as the driver author, I'll see about integrating them into
> my version and we'll take it from there.

Okay, will do. [Is there chance to pull your tree using git? It would
help a bit...]
Pavel


--
teflon -- maybe it is a trademark, but it should not be.

Pavel Machek

unread,
Jul 25, 2005, 6:20:15 PM7/25/05
to
Hi!

> > > The problem is that the parent doesn't actually know how many
> > > devices to create nor what to call them, and they're logically
> > > indistinguishable from each other so there's no logical naming
> > > system.
> >
> > Then we should probably not try to force them into driver model. Have
> > parent device register struct device and when sub-drivers register
> > they could attach class devices (like input devices) directly to the
> > "main" device thus hiding presence of sub-sections of the chip from
> > sysfs completely. My point is that we should not be using
> > class_interface here - its purpose is diferent.
>
> If you look at _my_ version, you'll notice that it doesn't use the
> class interface stuff. A previous version of it did, and this seems
> to be what the collie stuff is based upon.
>
> What I suggest is that the collie folk need to update their driver
> to my version so that we don't have two different forks of the same

Yep, will do, and sorry for the confusion.
Pavel


--
teflon -- maybe it is a trademark, but it should not be.

Russell King

unread,
Jul 25, 2005, 7:10:11 PM7/25/05
to
On Tue, Jul 26, 2005 at 12:06:59AM +0200, Pavel Machek wrote:
> > > Also it looks to me like mcp.h should go into asm/arch-sa1100, so
> > > that other drivers can use it...
> >
> > That doesn't make sense when you have other non-SA1100 devices using
> > mcp-core.c. Whether that happens or not I've no idea - I can't see
> > what everyone's using out there (just like I've absolutely zero
> > idea what collie folk are doing or not doing.)
>
> set_telecom_divisor relies on CONFIG_SA1100 being set (otherwise it
> breaks compilation, because struct members will not be available; at
> least in this version), so I doubt it has many non-SA1100 users...

That's not conclusive in itself - if it's only usable on SA1100
platforms, why was that ifdef added?

> > So, if the collie folk would like to clean their changes up and send
> > them to me as the driver author, I'll see about integrating them into
> > my version and we'll take it from there.
>
> Okay, will do. [Is there chance to pull your tree using git? It would
> help a bit...]

My git usage is limited to the final stage of my development - iow
providing an integration and test bed for merging upstream. My work
prior to that is all patch based (as per the tarball of patches I
posted previously) with scripts to manage them - I like the power to
re-order, split, merge, insert and remove patches at random, which
includes whole series of patches vs individual patches themselves.

Consequently, if I were to publish my git trees, what you'll find is
that they're indentical copies of Linus' tree except for maybe when
Linus is away, or hasn't pulled that night, or...

What you're actually seeing with the UCB stuff is the effect of me
stopping maintaining the -rmk trees - code effectively got "dropped"
from public view at that point, and I'm not going to start publishing
such a tree any time soon. It completely detracts from the task of
ensuring mainline kernels work for ARM - since the -rmk tree is/was
seen as the tree for everything ARM to be merged into, and hence
upstream merging became my problem. No, never again will I make a
fool of myself like that. Hence, I'll never again publish a kernel
tree myself, except maybe for very limited purposes.

However, if the UCB stuff is going to get worked on, I don't mind
setting up, maintaining and publishing a git tree for that that,
provided it then vanishes once merged into mainline. That falls
within the "very limited purposes" clause above.

--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of: 2.6 Serial core

Pavel Machek

unread,
Jul 26, 2005, 2:40:05 AM7/26/05
to
Hi!

> > > So, if the collie folk would like to clean their changes up and send
> > > them to me as the driver author, I'll see about integrating them into
> > > my version and we'll take it from there.
> >
> > Okay, will do. [Is there chance to pull your tree using git? It would
> > help a bit...]

...


> However, if the UCB stuff is going to get worked on, I don't mind
> setting up, maintaining and publishing a git tree for that that,
> provided it then vanishes once merged into mainline. That falls
> within the "very limited purposes" clause above.

Yes, that would help a lot, because I'd have a tree to diff against.

Pavel
--
teflon -- maybe it is a trademark, but it should not be.

0 new messages