Index: linux/drivers/ucb/ucb1x00.c =================================================================== --- /dev/null +++ linux/drivers/ucb/ucb1x00.c @@ -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); Index: linux/include/linux/ucb1x00.h =================================================================== --- /dev/null +++ linux/include/linux/ucb1x00.h @@ -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 Index: linux/drivers/ucb/Makefile =================================================================== --- /dev/null +++ linux/drivers/ucb/Makefile @@ -0,0 +1,3 @@ + +# Core functionality. +obj-$(CONFIG_UCB1x00) += ucb1x00.o Index: linux/drivers/ucb/Kconfig =================================================================== --- /dev/null +++ linux/drivers/ucb/Kconfig @@ -0,0 +1,10 @@ + +menu "UCB1x00 devices" + +config UCB1x00 + tristate "Core UCB1x00 support" + help + This option provides the the core support for UCB1x00 devices. + If unsure, say N. + +endmenu