diff -Naur -X ../linux/Documentation/dontdiff linux-2.6.12-rc5-base/arch/arm/mach-sa1100/generic.h linux-2.6.12-rc5-ucb/arch/arm/mach-sa1100/generic.h --- linux-2.6.12-rc5-base/arch/arm/mach-sa1100/generic.h 2005-03-02 01:38:13.000000000 -0600 +++ linux-2.6.12-rc5-ucb/arch/arm/mach-sa1100/generic.h 2005-06-06 14:30:18.840522804 -0500 @@ -36,3 +36,5 @@ struct irda_platform_data; void sa11x0_set_irda_data(struct irda_platform_data *irda); + +int sa1100_ucb_init(struct resource *resources, int nr, int ext_clock); diff -Naur -X ../linux/Documentation/dontdiff linux-2.6.12-rc5-base/arch/arm/mach-sa1100/ucb1x00.c linux-2.6.12-rc5-ucb/arch/arm/mach-sa1100/ucb1x00.c --- linux-2.6.12-rc5-base/arch/arm/mach-sa1100/ucb1x00.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.12-rc5-ucb/arch/arm/mach-sa1100/ucb1x00.c 2005-06-06 14:30:18.840522804 -0500 @@ -0,0 +1,242 @@ +/* + * linux/arch/arm/mach-sa1100/ucb1x00.c + * + * Copyright (C) 2005 John Lenz + * Based on code written by 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 UCB Register access driver. + * + * MCP read/write timeouts from Jordi Colomer, rehacked by rmk. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct ucb_sa1100_reg_access { + struct ucb1x00_reg_access reg_access; + + unsigned int rw_timeout; + +#ifdef CONFIG_PM + /* used for suspend and resume */ + u32 mccr0; + u32 mccr1; +#endif +}; +#define UCB_SA1100_REG_ACCESS(access) container_of(access, struct ucb_sa1100_reg_access, reg_access) + +#define UCB_SA1100_SCLK_RATE 11981000 + +static void +ucb_sa1100_set_telecom_divisor(struct ucb1x00_reg_access *access, unsigned int divisor) +{ + unsigned int mccr0; + + divisor /= 32; + + mccr0 = Ser4MCCR0 & ~0x00007f00; + mccr0 |= divisor << 8; + Ser4MCCR0 = mccr0; +} + +static void +ucb_sa1100_set_audio_divisor(struct ucb1x00_reg_access *access, 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 +ucb_sa1100_write(struct ucb1x00_reg_access *access, unsigned int reg, unsigned int val) +{ + int ret = -ETIME; + int i; + struct ucb_sa1100_reg_access *ucb = UCB_SA1100_REG_ACCESS(access); + + Ser4MCDR2 = reg << 17 | MCDR2_Wr | (val & 0xffff); + + for (i = 0; i < 2; i++) { + udelay(ucb->rw_timeout); + if (Ser4MCSR & MCSR_CWC) { + ret = 0; + break; + } + } + + if (ret < 0) + printk(KERN_WARNING "ucb sa1100: 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 +ucb_sa1100_read(struct ucb1x00_reg_access *access, unsigned int reg) +{ + int ret = -ETIME; + int i; + struct ucb_sa1100_reg_access *ucb = UCB_SA1100_REG_ACCESS(access); + + Ser4MCDR2 = reg << 17 | MCDR2_Rd; + + for (i = 0; i < 2; i++) { + udelay(ucb->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 ucb_sa1100_enable(struct ucb1x00_reg_access *access) +{ + Ser4MCSR = -1; + Ser4MCCR0 |= MCCR0_MCE; +} + +static void ucb_sa1100_disable(struct ucb1x00_reg_access *access) +{ + Ser4MCCR0 &= ~MCCR0_MCE; +} + +#ifdef CONFIG_PM +static int ucb_sa1100_suspend(struct ucb1x00_reg_access *access, pm_message_t state, u32 level) +{ + struct ucb_sa1100_reg_access *ucb = UCB_SA1100_REG_ACCESS(access); + + if (ucb) { + ucb->mccr0 = Ser4MCCR0; + ucb->mccr1 = Ser4MCCR1; + } + + if (level == SUSPEND_DISABLE) + Ser4MCCR0 &= ~MCCR0_MCE; + return 0; +} + +static int ucb_sa1100_resume(struct ucb1x00_reg_access *access, u32 level) +{ + struct ucb_sa1100_reg_access *ucb = UCB_SA1100_REG_ACCESS(access); + + if (ucb && level == RESUME_RESTORE_STATE) { + Ser4MCCR1 = ucb->mccr1; + Ser4MCCR0 = ucb->mccr0; + } + return 0; +} +#else +#define ucb_sa1100_suspend NULL +#define ucb_sa1100_resume NULL +#endif + +static struct ucb_sa1100_reg_access ucb_sa1100_reg_access = { + .reg_access = { + .enable = ucb_sa1100_enable, + .disable = ucb_sa1100_disable, + .read = ucb_sa1100_read, + .write = ucb_sa1100_write, + + .audio_divisor = ucb_sa1100_set_audio_divisor, + .telecom_divisor = ucb_sa1100_set_telecom_divisor, + + .clkrate = UCB_SA1100_SCLK_RATE, + + .suspend = ucb_sa1100_suspend, + .resume = ucb_sa1100_resume, + }, +}; + +static struct platform_device ucb_sa1100_dev = { + .name = "ucb1x00", + .id = 0, + .dev = { + .platform_data = &ucb_sa1100_reg_access, + }, +}; + +int sa1100_ucb_init(struct resource *resources, int nr, int ext_clock) +{ + int ret; + + ucb_sa1100_dev.resource = resources; + ucb_sa1100_dev.num_resources = nr; + + if (!request_mem_region(0x80060000, 0x60, "sa11x0-mcp")) + return -EBUSY; + + /*if (machine_is_assabet()) { + ASSABET_BCR_set(ASSABET_BCR_CODEC_RST); + }*/ + + /* + * 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; + if (ext_clock) + Ser4MCCR0 = 0x00007f7f | MCCR0_ADM | MCCR0_ExtClk; + else + Ser4MCCR0 = 0x00007f7f | MCCR0_ADM | MCCR0_IntClk; + + /* + * 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. + */ + ucb_sa1100_reg_access.rw_timeout = (64 * 3 * 1000000 + ucb_sa1100_reg_access.reg_access.clkrate - 1) / + ucb_sa1100_reg_access.reg_access.clkrate; + + ret = platform_device_register(&ucb_sa1100_dev); + if (ret) + printk(KERN_ERR "ucb sa1100: unable to register platform device\n"); + + return ret; +} + +/*void ucb_sa1100_exit(void) +{ + platform_device_unregister(&ucb_sa1100_dev); + release_mem_region(0x80060000, 0x60); +}*/ + +MODULE_AUTHOR("Russell King "); +MODULE_DESCRIPTION("SA11x0 UCB register access driver"); +MODULE_LICENSE("GPL"); diff -Naur -X ../linux/Documentation/dontdiff linux-2.6.12-rc5-base/drivers/ucb/Kconfig linux-2.6.12-rc5-ucb/drivers/ucb/Kconfig --- linux-2.6.12-rc5-base/drivers/ucb/Kconfig 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.12-rc5-ucb/drivers/ucb/Kconfig 2005-06-06 14:30:18.829525258 -0500 @@ -0,0 +1,14 @@ + +menu "UCB1x00 devices" + +config UCB1x00 + tristate "Core UCB1x00 support" + help + This option provides the the core support for UCB1x00 devices. + If unsure, say N. + +config UCB1x00_TS + tristate "Touchscreen interface support" + depends on UCB1x00 && INPUT + +endmenu diff -Naur -X ../linux/Documentation/dontdiff linux-2.6.12-rc5-base/drivers/ucb/Makefile linux-2.6.12-rc5-ucb/drivers/ucb/Makefile --- linux-2.6.12-rc5-base/drivers/ucb/Makefile 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.12-rc5-ucb/drivers/ucb/Makefile 2005-06-06 14:30:18.828525481 -0500 @@ -0,0 +1,4 @@ + +# Core functionality. +obj-$(CONFIG_UCB1x00) += ucb1x00.o +obj-$(CONFIG_UCB1x00_TS) += ucb1x00-ts.o diff -Naur -X ../linux/Documentation/dontdiff linux-2.6.12-rc5-base/drivers/ucb/ucb1x00-ts.c linux-2.6.12-rc5-ucb/drivers/ucb/ucb1x00-ts.c --- linux-2.6.12-rc5-base/drivers/ucb/ucb1x00-ts.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.12-rc5-ucb/drivers/ucb/ucb1x00-ts.c 2005-06-06 14:30:18.828525481 -0500 @@ -0,0 +1,458 @@ +/* + * linux/drivers/misc/ucb1x00-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 : + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + + +struct ucb1x00_ts { + struct input_dev idev; + struct ucb1x00_dev *ucb; + + struct semaphore irq_wait; + struct semaphore sem; + struct completion init_exit; + struct task_struct *rtask; + int use_count; + u16 x_res; + u16 y_res; + + int restart:1; + int adcsync:1; +}; + +static int adcsync = UCB_NOSYNC; + +static inline void ucb1x00_ts_evt_add(struct ucb1x00_ts *ts, u16 pressure, u16 x, u16 y) +{ + input_report_abs(&ts->idev, ABS_X, x); + input_report_abs(&ts->idev, ABS_Y, y); + input_report_abs(&ts->idev, ABS_PRESSURE, pressure); + input_sync(&ts->idev); +} + +static inline void ucb1x00_ts_event_release(struct ucb1x00_ts *ts) +{ + input_report_abs(&ts->idev, ABS_PRESSURE, 0); + input_sync(&ts->idev); +} + +/* + * 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->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; + + daemonize("ktsd"); + /* only want to receive SIGKILL */ + allow_signal(SIGKILL); + + /* + * We could 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); + + set_task_state(tsk, TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ / 100); + if (signal_pending(tsk)) + break; + + 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) { + ucb1x00_ts_event_release(ts); + 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) { + ucb1x00_ts_evt_add(ts, p, x, y); + valid = 1; + } + + set_task_state(tsk, TASK_INTERRUPTIBLE); + schedule_timeout(HZ / 100); + } + + 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; + + if (down_interruptible(&ts->sem)) + return -EINTR; + + if (ts->use_count++ != 0) + goto out; + + 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); + ret = kernel_thread(ucb1x00_thread, ts, CLONE_KERNEL); + if (ret >= 0) { + wait_for_completion(&ts->init_exit); + ret = 0; + } else { + ucb1x00_free_irq(ts->ucb, UCB_IRQ_TSPX, ts); + } + + out: + if (ret) + ts->use_count--; + up(&ts->sem); + return ret; +} + +/* + * Release touchscreen resources. Disable IRQs. + */ +static void ucb1x00_ts_close(struct input_dev *idev) +{ + struct ucb1x00_ts *ts = (struct ucb1x00_ts *)idev; + + down(&ts->sem); + if (--ts->use_count == 0) { + 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); + } + up(&ts->sem); +} + +#if 0 +static int ucb1x00_ts_resume(struct device *_dev, u32 level) +{ + struct ucb1x00_device *dev = ucb1x00_dev(_dev); + struct ucb1x00_ts *ts = ucb1x00_get_drvdata(dev); + + if (level == RESUME_ENABLE && ts->rtask != NULL) { + /* + * Restart the TS thread to ensure the + * TS interrupt mode is set up again + * after sleep. + */ + ts->restart = 1; + up(&ts->irq_wait); + } + return 0; +} +#endif + + +/* + * Initialisation. + */ +static int ucb1x00_ts_probe(struct ucb1x00_dev *udev) +{ + 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 = udev; + ts->adcsync = adcsync; + init_MUTEX(&ts->sem); + + ts->idev.name = "Touchscreen panel"; + ts->idev.id.product = ts->ucb->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); + + ucb1x00_set_drvdata(udev, ts); + + return 0; +} + +static int ucb1x00_ts_remove(struct ucb1x00_dev *udev) +{ + struct ucb1x00_ts *ts = ucb1x00_get_drvdata(udev); + + input_unregister_device(&ts->idev); + kfree(ts); + + return 0; +} + +static struct ucb1x00_driver ucb1x00_ts_driver = { + .devid = UCB1X00_DEVID_TOUCHSCREEN, + .probe = ucb1x00_ts_probe, + .remove = ucb1x00_ts_remove, +}; + +static int __init ucb1x00_ts_init(void) +{ + return ucb1x00_driver_register(&ucb1x00_ts_driver); +} + +static void __exit ucb1x00_ts_exit(void) +{ + ucb1x00_driver_unregister(&ucb1x00_ts_driver); +} + +#ifndef MODULE + +/* + * Parse kernel command-line options. + * + * syntax : ucbts=[sync|nosync],... + */ +static int __init ucb1x00_ts_setup(char *str) +{ + char *p; + + while ((p = strsep(&str, ",")) != NULL) { + if (strcmp(p, "sync") == 0) + adcsync = UCB_SYNC; + } + + return 1; +} + +__setup("ucbts=", ucb1x00_ts_setup); + +#else + +MODULE_PARM(adcsync, "i"); +MODULE_PARM_DESC(adcsync, "Enable use of ADCSYNC signal"); + +#endif + +module_init(ucb1x00_ts_init); +module_exit(ucb1x00_ts_exit); + +MODULE_AUTHOR("Russell King "); +MODULE_DESCRIPTION("UCB1x00 touchscreen driver"); +MODULE_LICENSE("GPL"); diff -Naur -X ../linux/Documentation/dontdiff linux-2.6.12-rc5-base/drivers/ucb/ucb1x00.c linux-2.6.12-rc5-ucb/drivers/ucb/ucb1x00.c --- linux-2.6.12-rc5-base/drivers/ucb/ucb1x00.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.12-rc5-ucb/drivers/ucb/ucb1x00.c 2005-06-06 14:30:18.816528159 -0500 @@ -0,0 +1,869 @@ +/* + * linux/arch/arm/common/ucb1x00.c + * + * Support for UCB 1x00 devices + * + * 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. + * + * All initialization functions provided here are intended to be called + * from machine specific code with proper arguments when required. + * + * Based on locomo.c and sa1111.c + * Also based on the ucb1x00 2.4 code by Russell King + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct ucb1x00_irq { + void *devid; + void (*fn)(int, void *); +}; + +/* the following is the overall data for the ucb1x00 chip */ +struct ucb1x00 { + struct device * dev; + struct ucb1x00_reg_access *reg_access; + + spinlock_t io_lock; + struct semaphore adc_sem; + + /* for interrupt handeling */ + unsigned int irq; + struct task_struct *irq_task; + wait_queue_head_t irq_wait; + struct completion complete; + struct ucb1x00_irq irq_handler[16]; + spinlock_t irq_lock; + + u16 id; + u16 io_dir; + u16 io_out; + u16 adc_cr; + u16 irq_fal_enbl; + u16 irq_ris_enbl; + u16 irq_types; +}; + +struct ucb1x00_dev_info { + unsigned int devid; + unsigned int irq[1]; + const char * name; +}; + +static struct ucb1x00_dev_info ucb1x00_devices[] = { + { + .devid = UCB1X00_DEVID_AUDIO, + .irq = {}, + .name = "audio", + }, + { + .devid = UCB1X00_DEVID_TOUCHSCREEN, + .irq = { + UCB_IRQ_TSPX, + }, + .name = "touchscreen", + }, +}; + +static void ucb1x00_dev_release(struct device *_dev) +{ + struct ucb1x00_dev *dev = UCB1X00_DEV(_dev); + + kfree(dev); +} + +/* + * 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 *uchip = devid; + struct ucb1x00_irq *irq; + unsigned int isr, i; + + uchip->reg_access->enable(uchip->reg_access); + isr = uchip->reg_access->read(uchip->reg_access, UCB_IE_STATUS); + uchip->reg_access->write(uchip->reg_access, UCB_IE_CLEAR, isr); + uchip->reg_access->write(uchip->reg_access, UCB_IE_CLEAR, 0); + + for (i = 0, irq = uchip->irq_handler; i < 16 && isr; i++, isr >>= 1, irq++) + if (isr & 1 && irq->fn) + irq->fn(i, irq->devid); + uchip->reg_access->disable(uchip->reg_access); + + 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; +} + +static int +ucb1x00_init_one_child(struct ucb1x00 *uchip, struct ucb1x00_dev_info *info) +{ + struct ucb1x00_dev *dev; + int ret; + + dev = kmalloc(sizeof(struct ucb1x00_dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + memset(dev, 0, sizeof(struct ucb1x00_dev)); + + strncpy(dev->dev.bus_id,info->name,sizeof(dev->dev.bus_id)); + /* + * If the parent device has a DMA mask associated with it, + * propagate it down to the children. + */ + if (uchip->dev->dma_mask) { + dev->dma_mask = *uchip->dev->dma_mask; + dev->dev.dma_mask = &dev->dma_mask; + } + + dev->devid = info->devid; + dev->dev.parent = uchip->dev; + dev->dev.bus = &ucb1x00_bus_type; + dev->dev.release = ucb1x00_dev_release; + dev->dev.coherent_dma_mask = uchip->dev->coherent_dma_mask; + dev->ucb_id = uchip->id; + + memmove(dev->irq, info->irq, sizeof(dev->irq)); + + ret = device_register(&dev->dev); + if (ret) { + kfree(dev); + } + return ret; +} + +/** + * ucb1x00_probe - probe for a single ucb1x00 chip. + * + * Probe for a ucb1x00 chip. This must be called + * before any other ucb1x00-specific code. + * + * Returns: + * %-ENOMEM out of memory. + * %0 successful. + */ +static int +__ucb1x00_probe(struct device *me, struct ucb1x00_reg_access *reg_access, int irq) +{ + struct ucb1x00 *uchip; + int i = 0, ret = 0; + + uchip = kmalloc(sizeof(struct ucb1x00), GFP_KERNEL); + if (!uchip) + return -ENOMEM; + + memset(uchip, 0, sizeof(struct ucb1x00)); + + spin_lock_init(&uchip->io_lock); + spin_lock_init(&uchip->irq_lock); + sema_init(&uchip->adc_sem, 1); + init_waitqueue_head(&uchip->irq_wait); + + uchip->dev = me; + dev_set_drvdata(uchip->dev, uchip); + + uchip->irq = irq; + uchip->reg_access = reg_access; + + reg_access->enable(reg_access); + + uchip->id = reg_access->read(reg_access, UCB_ID); + if (uchip->id == UCB_ID_1400 && reg_access->read(reg_access, 0x00) == 0x002a) + uchip->id = UCB_ID_1400_BUGGY; + + reg_access->disable(reg_access); + + /* + * The interrupt controller must be initialised before any + * other device to ensure that the interrupts are available. + */ + if (uchip->irq != NO_IRQ) { + ret = request_irq(uchip->irq, + uchip->id != UCB_ID_1400 ? ucb1x00_irq : ucb1x00_threaded_irq, + 0, "UCB1x00", uchip); + if (ret) { + printk(KERN_ERR "ucb1x00: unable to grab irq%d: %d\n", uchip->irq, ret); + goto free_ucb; + } + } + + if (uchip->id == UCB_ID_1400) { + init_completion(&uchip->complete); + ret = kernel_thread(ucb1x00_thread, uchip, CLONE_KERNEL); + if (ret >= 0) { + wait_for_completion(&uchip->complete); + ret = 0; + } else { + goto free_irq; + } + } + + for (i = 0; i < ARRAY_SIZE(ucb1x00_devices); i++) + ucb1x00_init_one_child(uchip, &ucb1x00_devices[i]); + + return 0; +free_irq: + free_irq(uchip->irq, uchip); +free_ucb: + dev_set_drvdata(uchip->dev, 0); + kfree(uchip); + return ret; +} + +static void __ucb1x00_remove(struct ucb1x00 *uchip) +{ + struct list_head *l, *n; + + list_for_each_safe(l, n, &uchip->dev->children) { + struct device *d = list_to_dev(l); + + device_unregister(d); + } + + if (uchip->id == UCB_ID_1400 || uchip->id == UCB_ID_1400_BUGGY) { + send_sig(SIGKILL, uchip->irq_task, 1); + wait_for_completion(&uchip->complete); + } + + if (uchip->irq != NO_IRQ) { + free_irq(uchip->irq, uchip); + } + + kfree(uchip); +} + +#ifdef CONFIG_PM +static int ucb1x00_suspend(struct device *dev, pm_message_t state, u32 level) +{ + struct ucb1x00 *uchip = dev_get_drvdata(dev); + int ret = 0; + + if (uchip->reg_access->suspend) + ret = uchip->reg_access->suspend(uchip->reg_access, state, level); + + return ret; +} + +static int ucb1x00_resume(struct device *dev, u32 level) +{ + struct ucb1x00 *uchip = dev_get_drvdata(dev); + unsigned int isr; + int ret = 0; + + uchip->reg_access->enable(uchip->reg_access); + isr = uchip->reg_access->read(uchip->reg_access, UCB_IE_STATUS); + uchip->reg_access->write(uchip->reg_access, UCB_IE_CLEAR, isr); + uchip->reg_access->write(uchip->reg_access, UCB_IE_CLEAR, 0); + uchip->reg_access->disable(uchip->reg_access); + + if (uchip->reg_access->resume) + ret = uchip->reg_access->resume(uchip->reg_access, level); + + return ret; +} +#else +#define ucb1x00_suspend NULL +#define ucb1x00_resume NULL +#endif + +static int ucb1x00_probe(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ucb1x00_reg_access *reg_access; + int irq; + + irq = platform_get_irq(pdev, 0); + reg_access = dev->platform_data; + + return __ucb1x00_probe(dev, reg_access, irq); +} + +static int ucb1x00_remove(struct device *dev) +{ + struct ucb1x00 *uchip = dev_get_drvdata(dev); + + if (uchip) { + __ucb1x00_remove(uchip); + dev_set_drvdata(dev, NULL); + } + + return 0; +} + +/* + * Not sure if this should be on the system bus or not yet. + * We really want some way to register a system device at + * the per-machine level, and then have this driver pick + * up the registered devices. + */ +static struct device_driver ucb1x00_device_driver = { + .name = "ucb1x00", + .bus = &platform_bus_type, + .probe = ucb1x00_probe, + .remove = ucb1x00_remove, + .suspend = ucb1x00_suspend, + .resume = ucb1x00_resume, +}; + +/* + * Get the parent device driver (us) structure + * from a child function device + */ +static inline struct ucb1x00 *ucb1x00_chip_driver(struct ucb1x00_dev *udev) +{ + return (struct ucb1x00 *)dev_get_drvdata(udev->dev.parent); +} + +void ucb1x00_enable(struct ucb1x00_dev *udev) +{ + struct ucb1x00 *uchip = ucb1x00_chip_driver(udev); + uchip->reg_access->enable(uchip->reg_access); +} +EXPORT_SYMBOL(ucb1x00_enable); + +void ucb1x00_disable(struct ucb1x00_dev *udev) +{ + struct ucb1x00 *uchip = ucb1x00_chip_driver(udev); + uchip->reg_access->disable(uchip->reg_access); +} +EXPORT_SYMBOL(ucb1x00_disable); + +unsigned int ucb1x00_reg_read(struct ucb1x00_dev *udev, unsigned int reg) +{ + struct ucb1x00 *uchip = ucb1x00_chip_driver(udev); + return uchip->reg_access->read(uchip->reg_access, reg); +} +EXPORT_SYMBOL(ucb1x00_reg_read); + +void ucb1x00_reg_write(struct ucb1x00_dev *udev, unsigned int reg, unsigned int val) +{ + struct ucb1x00 *uchip = ucb1x00_chip_driver(udev); + uchip->reg_access->write(uchip->reg_access, reg, val); +} +EXPORT_SYMBOL(ucb1x00_reg_write); + +/** + * ucb1x00_io_set_dir - set IO direction + * @udev: 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_dev *udev, unsigned int in, unsigned int out) +{ + struct ucb1x00 *uchip = ucb1x00_chip_driver(udev); + unsigned long flags; + + spin_lock_irqsave(&uchip->io_lock, flags); + uchip->io_dir |= out; + uchip->io_dir &= ~in; + + uchip->reg_access->write(uchip->reg_access, UCB_IO_DIR, uchip->io_dir); + + spin_unlock_irqrestore(&uchip->io_lock, flags); +} +EXPORT_SYMBOL(ucb1x00_io_set_dir); + +/** + * ucb1x00_io_write - set or clear IO outputs + * @udev: 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_dev *udev, unsigned int set, unsigned int clear) +{ + struct ucb1x00 *uchip = ucb1x00_chip_driver(udev); + unsigned long flags; + + spin_lock_irqsave(&uchip->io_lock, flags); + uchip->io_out |= set; + uchip->io_out &= ~clear; + + uchip->reg_access->write(uchip->reg_access, UCB_IO_DATA, uchip->io_out); + + spin_unlock_irqrestore(&uchip->io_lock, flags); +} +EXPORT_SYMBOL(ucb1x00_io_write); + +/** + * ucb1x00_io_read - read the current state of the IO pins + * @udev: 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_dev *udev) +{ + struct ucb1x00 *uchip = ucb1x00_chip_driver(udev); + + return uchip->reg_access->read(uchip->reg_access, UCB_IO_DATA); +} +EXPORT_SYMBOL(ucb1x00_io_read); + +/* + * 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 + * @udev: 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_dev *udev) +{ + struct ucb1x00 *uchip = ucb1x00_chip_driver(udev); + + down(&uchip->adc_sem); + + uchip->adc_cr |= UCB_ADC_ENA; + + uchip->reg_access->enable(uchip->reg_access); + uchip->reg_access->write(uchip->reg_access, UCB_ADC_CR, uchip->adc_cr); +} +EXPORT_SYMBOL(ucb1x00_adc_enable); + +/** + * 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_dev *udev, int adc_channel, int sync) +{ + struct ucb1x00 *uchip = ucb1x00_chip_driver(udev); + unsigned int val; + + if (sync) + adc_channel |= UCB_ADC_SYNC_ENA; + + uchip->reg_access->write(uchip->reg_access, UCB_ADC_CR, uchip->adc_cr | adc_channel); + uchip->reg_access->write(uchip->reg_access, UCB_ADC_CR, uchip->adc_cr | adc_channel | UCB_ADC_START); + + for (;;) { + val = uchip->reg_access->read(uchip->reg_access, UCB_ADC_DATA); + if (val & UCB_ADC_DAT_VAL) + break; + /* yield to other processes */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + } + + return UCB_ADC_DAT(val); +} +EXPORT_SYMBOL(ucb1x00_adc_read); + +/** + * ucb1x00_adc_disable - disable the ADC converter + * @udev: UCB1x00 structure describing chip + * + * Disable the ADC converter and release the ADC semaphore. + */ +void ucb1x00_adc_disable(struct ucb1x00_dev *udev) +{ + struct ucb1x00 *uchip = ucb1x00_chip_driver(udev); + + uchip->adc_cr &= ~UCB_ADC_ENA; + uchip->reg_access->write(uchip->reg_access, UCB_ADC_CR, uchip->adc_cr); + uchip->reg_access->disable(uchip->reg_access); + + up(&uchip->adc_sem); +} +EXPORT_SYMBOL(ucb1x00_adc_disable); + +unsigned int ucb1x00_clkrate(struct ucb1x00_dev *udev) +{ + struct ucb1x00 *uchip = ucb1x00_chip_driver(udev); + return uchip->reg_access->clkrate; +} +EXPORT_SYMBOL(ucb1x00_clkrate); + +void ucb1x00_set_audio_divisor(struct ucb1x00_dev *udev, unsigned int div) +{ + struct ucb1x00 *uchip = ucb1x00_chip_driver(udev); + uchip->reg_access->audio_divisor(uchip->reg_access, div); +} +EXPORT_SYMBOL(ucb1x00_set_audio_divisor); + +void ucb1x00_set_telecom_divisor(struct ucb1x00_dev *udev, unsigned int div) +{ + struct ucb1x00 *uchip = ucb1x00_chip_driver(udev); + uchip->reg_access->telecom_divisor(uchip->reg_access, div); +} +EXPORT_SYMBOL(ucb1x00_set_telecom_divisor); + +/** + * 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_dev *udev, unsigned int idx, void (*fn)(int, void *), void *devid) +{ + struct ucb1x00 *ucb = ucb1x00_chip_driver(udev); + struct ucb1x00_irq *irq; + int ret = -EINVAL; + + if (idx < 16) { + irq = ucb->irq_handler + idx; + ret = -EBUSY; + + spin_lock_irq(&ucb->irq_lock); + if (irq->fn == NULL) { + irq->devid = devid; + irq->fn = fn; + ret = 0; + } + spin_unlock_irq(&ucb->irq_lock); + } + return ret; +} +EXPORT_SYMBOL(ucb1x00_hook_irq); + +/** + * 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_dev *udev, unsigned int idx, int edges) +{ + struct ucb1x00 *ucb = ucb1x00_chip_driver(udev); + unsigned long flags; + + if (idx < 16) { + spin_lock_irqsave(&ucb->irq_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->irq_lock, flags); + + ucb->reg_access->enable(ucb->reg_access); + + /* This prevents spurious interrupts on the UCB1400 */ + ucb->reg_access->write(ucb->reg_access, UCB_IE_CLEAR, 1 << idx); + ucb->reg_access->write(ucb->reg_access, UCB_IE_CLEAR, 0); + + ucb->reg_access->write(ucb->reg_access, UCB_IE_RIS, ucb->irq_ris_enbl); + ucb->reg_access->write(ucb->reg_access, UCB_IE_FAL, ucb->irq_fal_enbl); + + ucb->reg_access->disable(ucb->reg_access); + } +} +EXPORT_SYMBOL(ucb1x00_enable_irq); + +/** + * 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_dev *udev, unsigned int idx, int edges) +{ + struct ucb1x00 *ucb = ucb1x00_chip_driver(udev); + unsigned long flags; + + if (idx < 16) { + spin_lock_irqsave(&ucb->irq_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->irq_lock, flags); + + ucb->reg_access->enable(ucb->reg_access); + ucb->reg_access->write(ucb->reg_access, UCB_IE_RIS, ucb->irq_ris_enbl); + ucb->reg_access->write(ucb->reg_access, UCB_IE_FAL, ucb->irq_fal_enbl); + ucb->reg_access->disable(ucb->reg_access); + } +} +EXPORT_SYMBOL(ucb1x00_disable_irq); + +/** + * 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_dev *udev, unsigned int idx, void *devid) +{ + struct ucb1x00 *ucb = ucb1x00_chip_driver(udev); + struct ucb1x00_irq *irq; + int ret; + + if (idx >= 16) + goto bad; + + irq = ucb->irq_handler + idx; + ret = -ENOENT; + + spin_lock_irq(&ucb->irq_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->irq_lock); + + ucb->reg_access->enable(ucb->reg_access); + ucb->reg_access->write(ucb->reg_access, UCB_IE_RIS, ucb->irq_ris_enbl); + ucb->reg_access->write(ucb->reg_access, UCB_IE_FAL, ucb->irq_fal_enbl); + ucb->reg_access->disable(ucb->reg_access); + + return ret; + +bad: + printk(KERN_ERR "Freeing bad UCB1x00 irq %d\n", idx); + return -EINVAL; +} +EXPORT_SYMBOL(ucb1x00_free_irq); + +/* + * UCB1x00 "Register Access Bus." + * + * We model this as a regular bus type, and hang devices directly + * off this. + */ +static int ucb1x00_match(struct device *_dev, struct device_driver *_drv) +{ + struct ucb1x00_dev *dev = UCB1X00_DEV(_dev); + struct ucb1x00_driver *drv = UCB1X00_DRV(_drv); + + return dev->devid == drv->devid; +} + +static int ucb1x00_bus_suspend(struct device *dev, pm_message_t state) +{ + struct ucb1x00_dev *udev = UCB1X00_DEV(dev); + struct ucb1x00_driver *drv = UCB1X00_DRV(dev->driver); + int ret = 0; + + if (drv && drv->suspend) + ret = drv->suspend(udev, state); + return ret; +} + +static int ucb1x00_bus_resume(struct device *dev) +{ + struct ucb1x00_dev *udev = UCB1X00_DEV(dev); + struct ucb1x00_driver *drv = UCB1X00_DRV(dev->driver); + int ret = 0; + + if (drv && drv->resume) + ret = drv->resume(udev); + return ret; +} + +static int ucb1x00_bus_probe(struct device *dev) +{ + struct ucb1x00_dev *udev = UCB1X00_DEV(dev); + struct ucb1x00_driver *drv = UCB1X00_DRV(dev->driver); + int ret = -ENODEV; + + if (drv->probe) + ret = drv->probe(udev); + return ret; +} + +static int ucb1x00_bus_remove(struct device *dev) +{ + struct ucb1x00_dev *udev = UCB1X00_DEV(dev); + struct ucb1x00_driver *drv = UCB1X00_DRV(dev->driver); + int ret = 0; + + if (drv->remove) + ret = drv->remove(udev); + return ret; +} + +struct bus_type ucb1x00_bus_type = { + .name = "ucb1x00-bus", + .match = ucb1x00_match, + .suspend = ucb1x00_bus_suspend, + .resume = ucb1x00_bus_resume, +}; + +int ucb1x00_driver_register(struct ucb1x00_driver *driver) +{ + driver->drv.probe = ucb1x00_bus_probe; + driver->drv.remove = ucb1x00_bus_remove; + driver->drv.bus = &ucb1x00_bus_type; + return driver_register(&driver->drv); +} + +void ucb1x00_driver_unregister(struct ucb1x00_driver *driver) +{ + driver_unregister(&driver->drv); +} + +static int __init ucb1x00_init(void) +{ + int ret = bus_register(&ucb1x00_bus_type); + if (ret == 0) + driver_register(&ucb1x00_device_driver); + return ret; +} + +static void __exit ucb1x00_exit(void) +{ + driver_unregister(&ucb1x00_device_driver); + bus_unregister(&ucb1x00_bus_type); +} + +module_init(ucb1x00_init); +module_exit(ucb1x00_exit); + +MODULE_DESCRIPTION("UCB 1x00 core driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("John Lenz "); + +EXPORT_SYMBOL(ucb1x00_driver_register); +EXPORT_SYMBOL(ucb1x00_driver_unregister); diff -Naur -X ../linux/Documentation/dontdiff linux-2.6.12-rc5-base/include/linux/ucb1x00.h linux-2.6.12-rc5-ucb/include/linux/ucb1x00.h --- linux-2.6.12-rc5-base/include/linux/ucb1x00.h 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.12-rc5-ucb/include/linux/ucb1x00.h 2005-06-06 14:30:18.816528159 -0500 @@ -0,0 +1,213 @@ +/* + * linux/include/asm-arm/hardware/ucb1x00.h + * + * This file contains the definitions for the UCB1x00 Chip + * + * (C) Copyright 2005 John Lenz + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + * Based on sa1111.h locomo.h + * Also based on 2.4 ucb1x00.h by Russell King + */ +#ifndef _ASM_ARCH_UCB1x00 +#define _ASM_ARCH_UCB1x00 + +#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) + + +extern struct bus_type ucb1x00_bus_type; + +#define UCB1X00_DEVID_AUDIO 0 +#define UCB1X00_DEVID_TOUCHSCREEN 1 + +struct ucb1x00_dev { + struct device dev; + unsigned int devid; + unsigned int irq[1]; + + int ucb_id; + + u64 dma_mask; +}; + +#define UCB1X00_DEV(_d) container_of((_d), struct ucb1x00_dev, dev) + +#define ucb1x00_get_drvdata(d) dev_get_drvdata(&(d)->dev) +#define ucb1x00_set_drvdata(d,p) dev_set_drvdata(&(d)->dev, p) + +struct ucb1x00_driver { + struct device_driver drv; + unsigned int devid; + int (*probe)(struct ucb1x00_dev *); + int (*remove)(struct ucb1x00_dev *); + int (*suspend)(struct ucb1x00_dev *, u32); + int (*resume)(struct ucb1x00_dev *); +}; + +#define UCB1X00_DRV(_d) container_of((_d), struct ucb1x00_driver, drv) + +#define UCB1X00_DRIVER_NAME(_udev) ((_udev)->dev.driver->name) + +struct ucb1x00_reg_access { + void (*enable)(struct ucb1x00_reg_access *); + void (*disable)(struct ucb1x00_reg_access *); + + unsigned int (*read)(struct ucb1x00_reg_access *, unsigned int); + void (*write)(struct ucb1x00_reg_access *, unsigned int, unsigned int); + + unsigned int clkrate; + + void (*audio_divisor)(struct ucb1x00_reg_access *, unsigned int); + void (*telecom_divisor)(struct ucb1x00_reg_access *, unsigned int); + + int (*suspend)(struct ucb1x00_reg_access *, pm_message_t, u32); + int (*resume)(struct ucb1x00_reg_access *, u32); +}; + +int ucb1x00_driver_register(struct ucb1x00_driver *); +void ucb1x00_driver_unregister(struct ucb1x00_driver *); + +void ucb1x00_enable(struct ucb1x00_dev *udev); +void ucb1x00_disable(struct ucb1x00_dev *udev); +unsigned int ucb1x00_reg_read(struct ucb1x00_dev *udev, unsigned int reg); +void ucb1x00_reg_write(struct ucb1x00_dev *udev, unsigned int reg, unsigned int val); + +void ucb1x00_io_set_dir(struct ucb1x00_dev *udev, unsigned int in, unsigned int out); +void ucb1x00_io_write(struct ucb1x00_dev *udev, unsigned int set, unsigned int clear); +unsigned int ucb1x00_io_read(struct ucb1x00_dev *udev); + +#define UCB_NOSYNC (0) +#define UCB_SYNC (1) + +void ucb1x00_adc_enable(struct ucb1x00_dev *udev); +unsigned int ucb1x00_adc_read(struct ucb1x00_dev *udev, int adc_channel, int sync); +void ucb1x00_adc_disable(struct ucb1x00_dev *udev); + +unsigned int ucb1x00_clkrate(struct ucb1x00_dev *udev); +void ucb1x00_set_audio_divisor(struct ucb1x00_dev *udev, unsigned int div); +void ucb1x00_set_telecom_divisor(struct ucb1x00_dev *udev, unsigned int div); + +#define UCB_RISING (1 << 0) +#define UCB_FALLING (1 << 1) + +int ucb1x00_hook_irq(struct ucb1x00_dev *udev, unsigned int idx, void (*fn)(int, void *), void *devid); +void ucb1x00_enable_irq(struct ucb1x00_dev *udev, unsigned int idx, int edges); +void ucb1x00_disable_irq(struct ucb1x00_dev *udev, unsigned int idx, int edges); +int ucb1x00_free_irq(struct ucb1x00_dev *udev, unsigned int idx, void *devid); + +#endif