diff options
author | Jarod Wilson <jarod@redhat.com> | 2010-07-26 20:30:25 -0300 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2010-08-02 16:43:25 -0300 |
commit | 85cb01afc844f7b4e65df04a96b0c3729db59eba (patch) | |
tree | 91040f617948bc2507a747cc1e3314a55aaf6837 /drivers/staging | |
parent | 4cb613aea4b250cbd66d8494f7b34cb9c26f5c0b (diff) |
V4L/DVB: staging/lirc: add lirc_ite8709 driver
Signed-off-by: Jarod Wilson <jarod@redhat.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/staging')
-rw-r--r-- | drivers/staging/lirc/lirc_ite8709.c | 542 |
1 files changed, 542 insertions, 0 deletions
diff --git a/drivers/staging/lirc/lirc_ite8709.c b/drivers/staging/lirc/lirc_ite8709.c new file mode 100644 index 00000000000..9352f45bbec --- /dev/null +++ b/drivers/staging/lirc/lirc_ite8709.c @@ -0,0 +1,542 @@ +/* + * LIRC driver for ITE8709 CIR port + * + * Copyright (C) 2008 Grégory Lardière <spmf2004-lirc@yahoo.fr> + * + * 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, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/pnp.h> +#include <linux/io.h> + +#include <media/lirc.h> +#include <media/lirc_dev.h> + +#define LIRC_DRIVER_NAME "lirc_ite8709" + +#define BUF_CHUNK_SIZE sizeof(int) +#define BUF_SIZE (128*BUF_CHUNK_SIZE) + +/* + * The ITE8709 device seems to be the combination of IT8512 superIO chip and + * a specific firmware running on the IT8512's embedded micro-controller. + * In addition of the embedded micro-controller, the IT8512 chip contains a + * CIR module and several other modules. A few modules are directly accessible + * by the host CPU, but most of them are only accessible by the + * micro-controller. The CIR module is only accessible by the micro-controller. + * The battery-backed SRAM module is accessible by the host CPU and the + * micro-controller. So one of the MC's firmware role is to act as a bridge + * between the host CPU and the CIR module. The firmware implements a kind of + * communication protocol using the SRAM module as a shared memory. The IT8512 + * specification is publicly available on ITE's web site, but the communication + * protocol is not, so it was reverse-engineered. + */ + +/* ITE8709 Registers addresses and values (reverse-engineered) */ +#define ITE8709_MODE 0x1a +#define ITE8709_REG_ADR 0x1b +#define ITE8709_REG_VAL 0x1c +#define ITE8709_IIR 0x1e /* Interrupt identification register */ +#define ITE8709_RFSR 0x1f /* Receiver FIFO status register */ +#define ITE8709_FIFO_START 0x20 + +#define ITE8709_MODE_READY 0X00 +#define ITE8709_MODE_WRITE 0X01 +#define ITE8709_MODE_READ 0X02 +#define ITE8709_IIR_RDAI 0x02 /* Receiver data available interrupt */ +#define ITE8709_IIR_RFOI 0x04 /* Receiver FIFO overrun interrupt */ +#define ITE8709_RFSR_MASK 0x3f /* FIFO byte count mask */ + +/* + * IT8512 CIR-module registers addresses and values + * (from IT8512 E/F specification v0.4.1) + */ +#define IT8512_REG_MSTCR 0x01 /* Master control register */ +#define IT8512_REG_IER 0x02 /* Interrupt enable register */ +#define IT8512_REG_CFR 0x04 /* Carrier frequency register */ +#define IT8512_REG_RCR 0x05 /* Receive control register */ +#define IT8512_REG_BDLR 0x08 /* Baud rate divisor low byte register */ +#define IT8512_REG_BDHR 0x09 /* Baud rate divisor high byte register */ + +#define IT8512_MSTCR_RESET 0x01 /* Reset registers to default value */ +#define IT8512_MSTCR_FIFOCLR 0x02 /* Clear FIFO */ +#define IT8512_MSTCR_FIFOTL_7 0x04 /* FIFO threshold level : 7 */ +#define IT8512_MSTCR_FIFOTL_25 0x0c /* FIFO threshold level : 25 */ +#define IT8512_IER_RDAIE 0x02 /* Enable data interrupt request */ +#define IT8512_IER_RFOIE 0x04 /* Enable FIFO overrun interrupt req */ +#define IT8512_IER_IEC 0x80 /* Enable interrupt request */ +#define IT8512_CFR_CF_36KHZ 0x09 /* Carrier freq : low speed, 36kHz */ +#define IT8512_RCR_RXDCR_1 0x01 /* Demodulation carrier range : 1 */ +#define IT8512_RCR_RXACT 0x08 /* Receiver active */ +#define IT8512_RCR_RXEN 0x80 /* Receiver enable */ +#define IT8512_BDR_6 6 /* Baud rate divisor : 6 */ + +/* Actual values used by this driver */ +#define CFG_FIFOTL IT8512_MSTCR_FIFOTL_25 +#define CFG_CR_FREQ IT8512_CFR_CF_36KHZ +#define CFG_DCR IT8512_RCR_RXDCR_1 +#define CFG_BDR IT8512_BDR_6 +#define CFG_TIMEOUT 100000 /* Rearm interrupt when a space is > 100 ms */ + +static int debug; + +struct ite8709_device { + int use_count; + int io; + int irq; + spinlock_t hardware_lock; + unsigned long long acc_pulse; + unsigned long long acc_space; + char lastbit; + struct timeval last_tv; + struct lirc_driver driver; + struct tasklet_struct tasklet; + char force_rearm; + char rearmed; + char device_busy; +}; + +#define dprintk(fmt, args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG LIRC_DRIVER_NAME ": " \ + fmt, ## args); \ + } while (0) + + +static unsigned char ite8709_read(struct ite8709_device *dev, + unsigned char port) +{ + outb(port, dev->io); + return inb(dev->io+1); +} + +static void ite8709_write(struct ite8709_device *dev, unsigned char port, + unsigned char data) +{ + outb(port, dev->io); + outb(data, dev->io+1); +} + +static void ite8709_wait_device(struct ite8709_device *dev) +{ + int i = 0; + /* + * loop until device tells it's ready to continue + * iterations count is usually ~750 but can sometimes achieve 13000 + */ + for (i = 0; i < 15000; i++) { + udelay(2); + if (ite8709_read(dev, ITE8709_MODE) == ITE8709_MODE_READY) + break; + } +} + +static void ite8709_write_register(struct ite8709_device *dev, + unsigned char reg_adr, unsigned char reg_value) +{ + ite8709_wait_device(dev); + + ite8709_write(dev, ITE8709_REG_VAL, reg_value); + ite8709_write(dev, ITE8709_REG_ADR, reg_adr); + ite8709_write(dev, ITE8709_MODE, ITE8709_MODE_WRITE); +} + +static void ite8709_init_hardware(struct ite8709_device *dev) +{ + spin_lock_irq(&dev->hardware_lock); + dev->device_busy = 1; + spin_unlock_irq(&dev->hardware_lock); + + ite8709_write_register(dev, IT8512_REG_BDHR, (CFG_BDR >> 8) & 0xff); + ite8709_write_register(dev, IT8512_REG_BDLR, CFG_BDR & 0xff); + ite8709_write_register(dev, IT8512_REG_CFR, CFG_CR_FREQ); + ite8709_write_register(dev, IT8512_REG_IER, + IT8512_IER_IEC | IT8512_IER_RFOIE | IT8512_IER_RDAIE); + ite8709_write_register(dev, IT8512_REG_RCR, CFG_DCR); + ite8709_write_register(dev, IT8512_REG_MSTCR, + CFG_FIFOTL | IT8512_MSTCR_FIFOCLR); + ite8709_write_register(dev, IT8512_REG_RCR, + IT8512_RCR_RXEN | IT8512_RCR_RXACT | CFG_DCR); + + spin_lock_irq(&dev->hardware_lock); + dev->device_busy = 0; + spin_unlock_irq(&dev->hardware_lock); + + tasklet_enable(&dev->tasklet); +} + +static void ite8709_drop_hardware(struct ite8709_device *dev) +{ + tasklet_disable(&dev->tasklet); + + spin_lock_irq(&dev->hardware_lock); + dev->device_busy = 1; + spin_unlock_irq(&dev->hardware_lock); + + ite8709_write_register(dev, IT8512_REG_RCR, 0); + ite8709_write_register(dev, IT8512_REG_MSTCR, + IT8512_MSTCR_RESET | IT8512_MSTCR_FIFOCLR); + + spin_lock_irq(&dev->hardware_lock); + dev->device_busy = 0; + spin_unlock_irq(&dev->hardware_lock); +} + +static int ite8709_set_use_inc(void *data) +{ + struct ite8709_device *dev; + dev = data; + if (dev->use_count == 0) + ite8709_init_hardware(dev); + dev->use_count++; + return 0; +} + +static void ite8709_set_use_dec(void *data) +{ + struct ite8709_device *dev; + dev = data; + dev->use_count--; + if (dev->use_count == 0) + ite8709_drop_hardware(dev); +} + +static void ite8709_add_read_queue(struct ite8709_device *dev, int flag, + unsigned long long val) +{ + int value; + + dprintk("add a %llu usec %s\n", val, flag ? "pulse" : "space"); + + value = (val > PULSE_MASK) ? PULSE_MASK : val; + if (flag) + value |= PULSE_BIT; + + if (!lirc_buffer_full(dev->driver.rbuf)) { + lirc_buffer_write(dev->driver.rbuf, (void *) &value); + wake_up(&dev->driver.rbuf->wait_poll); + } +} + +static irqreturn_t ite8709_interrupt(int irq, void *dev_id) +{ + unsigned char data; + int iir, rfsr, i; + int fifo = 0; + char bit; + struct timeval curr_tv; + + /* Bit duration in microseconds */ + const unsigned long bit_duration = 1000000ul / (115200 / CFG_BDR); + + struct ite8709_device *dev; + dev = dev_id; + + /* + * If device is busy, we simply discard data because we are in one of + * these two cases : shutting down or rearming the device, so this + * doesn't really matter and this avoids waiting too long in IRQ ctx + */ + spin_lock(&dev->hardware_lock); + if (dev->device_busy) { + spin_unlock(&dev->hardware_lock); + return IRQ_RETVAL(IRQ_HANDLED); + } + + iir = ite8709_read(dev, ITE8709_IIR); + + switch (iir) { + case ITE8709_IIR_RFOI: + dprintk("fifo overrun, scheduling forced rearm just in case\n"); + dev->force_rearm = 1; + tasklet_schedule(&dev->tasklet); + spin_unlock(&dev->hardware_lock); + return IRQ_RETVAL(IRQ_HANDLED); + + case ITE8709_IIR_RDAI: + rfsr = ite8709_read(dev, ITE8709_RFSR); + fifo = rfsr & ITE8709_RFSR_MASK; + if (fifo > 32) + fifo = 32; + dprintk("iir: 0x%x rfsr: 0x%x fifo: %d\n", iir, rfsr, fifo); + + if (dev->rearmed) { + do_gettimeofday(&curr_tv); + dev->acc_space += 1000000ull + * (curr_tv.tv_sec - dev->last_tv.tv_sec) + + (curr_tv.tv_usec - dev->last_tv.tv_usec); + dev->rearmed = 0; + } + for (i = 0; i < fifo; i++) { + data = ite8709_read(dev, i+ITE8709_FIFO_START); + data = ~data; + /* Loop through */ + for (bit = 0; bit < 8; ++bit) { + if ((data >> bit) & 1) { + dev->acc_pulse += bit_duration; + if (dev->lastbit == 0) { + ite8709_add_read_queue(dev, 0, + dev->acc_space); + dev->acc_space = 0; + } + } else { + dev->acc_space += bit_duration; + if (dev->lastbit == 1) { + ite8709_add_read_queue(dev, 1, + dev->acc_pulse); + dev->acc_pulse = 0; + } + } + dev->lastbit = (data >> bit) & 1; + } + } + ite8709_write(dev, ITE8709_RFSR, 0); + + if (dev->acc_space > CFG_TIMEOUT) { + dprintk("scheduling rearm IRQ\n"); + do_gettimeofday(&dev->last_tv); + dev->force_rearm = 0; + tasklet_schedule(&dev->tasklet); + } + + spin_unlock(&dev->hardware_lock); + return IRQ_RETVAL(IRQ_HANDLED); + + default: + /* not our irq */ + dprintk("unknown IRQ (shouldn't happen) !!\n"); + spin_unlock(&dev->hardware_lock); + return IRQ_RETVAL(IRQ_NONE); + } +} + +static void ite8709_rearm_irq(unsigned long data) +{ + struct ite8709_device *dev; + unsigned long flags; + dev = (struct ite8709_device *) data; + + spin_lock_irqsave(&dev->hardware_lock, flags); + dev->device_busy = 1; + spin_unlock_irqrestore(&dev->hardware_lock, flags); + + if (dev->force_rearm || dev->acc_space > CFG_TIMEOUT) { + dprintk("rearming IRQ\n"); + ite8709_write_register(dev, IT8512_REG_RCR, + IT8512_RCR_RXACT | CFG_DCR); + ite8709_write_register(dev, IT8512_REG_MSTCR, + CFG_FIFOTL | IT8512_MSTCR_FIFOCLR); + ite8709_write_register(dev, IT8512_REG_RCR, + IT8512_RCR_RXEN | IT8512_RCR_RXACT | CFG_DCR); + if (!dev->force_rearm) + dev->rearmed = 1; + dev->force_rearm = 0; + } + + spin_lock_irqsave(&dev->hardware_lock, flags); + dev->device_busy = 0; + spin_unlock_irqrestore(&dev->hardware_lock, flags); +} + +static int ite8709_cleanup(struct ite8709_device *dev, int stage, int errno, + char *msg) +{ + if (msg != NULL) + printk(KERN_ERR LIRC_DRIVER_NAME ": %s\n", msg); + + switch (stage) { + case 6: + if (dev->use_count > 0) + ite8709_drop_hardware(dev); + case 5: + free_irq(dev->irq, dev); + case 4: + release_region(dev->io, 2); + case 3: + lirc_unregister_driver(dev->driver.minor); + case 2: + lirc_buffer_free(dev->driver.rbuf); + kfree(dev->driver.rbuf); + case 1: + kfree(dev); + case 0: + ; + } + + return errno; +} + +static int __devinit ite8709_pnp_probe(struct pnp_dev *dev, + const struct pnp_device_id *dev_id) +{ + struct lirc_driver *driver; + struct ite8709_device *ite8709_dev; + int ret; + + /* Check resources validity */ + if (!pnp_irq_valid(dev, 0)) + return ite8709_cleanup(NULL, 0, -ENODEV, "invalid IRQ"); + if (!pnp_port_valid(dev, 2)) + return ite8709_cleanup(NULL, 0, -ENODEV, "invalid IO port"); + + /* Allocate memory for device struct */ + ite8709_dev = kzalloc(sizeof(struct ite8709_device), GFP_KERNEL); + if (ite8709_dev == NULL) + return ite8709_cleanup(NULL, 0, -ENOMEM, "kzalloc failed"); + pnp_set_drvdata(dev, ite8709_dev); + + /* Initialize device struct */ + ite8709_dev->use_count = 0; + ite8709_dev->irq = pnp_irq(dev, 0); + ite8709_dev->io = pnp_port_start(dev, 2); + ite8709_dev->hardware_lock = + __SPIN_LOCK_UNLOCKED(ite8709_dev->hardware_lock); + ite8709_dev->acc_pulse = 0; + ite8709_dev->acc_space = 0; + ite8709_dev->lastbit = 0; + do_gettimeofday(&ite8709_dev->last_tv); + tasklet_init(&ite8709_dev->tasklet, ite8709_rearm_irq, + (long) ite8709_dev); + ite8709_dev->force_rearm = 0; + ite8709_dev->rearmed = 0; + ite8709_dev->device_busy = 0; + + /* Initialize driver struct */ + driver = &ite8709_dev->driver; + strcpy(driver->name, LIRC_DRIVER_NAME); + driver->minor = -1; + driver->code_length = sizeof(int) * 8; + driver->sample_rate = 0; + driver->features = LIRC_CAN_REC_MODE2; + driver->data = ite8709_dev; + driver->add_to_buf = NULL; + driver->set_use_inc = ite8709_set_use_inc; + driver->set_use_dec = ite8709_set_use_dec; + driver->dev = &dev->dev; + driver->owner = THIS_MODULE; + + /* Initialize LIRC buffer */ + driver->rbuf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL); + if (!driver->rbuf) + return ite8709_cleanup(ite8709_dev, 1, -ENOMEM, + "can't allocate lirc_buffer"); + if (lirc_buffer_init(driver->rbuf, BUF_CHUNK_SIZE, BUF_SIZE)) + return ite8709_cleanup(ite8709_dev, 1, -ENOMEM, + "lirc_buffer_init() failed"); + + /* Register LIRC driver */ + ret = lirc_register_driver(driver); + if (ret < 0) + return ite8709_cleanup(ite8709_dev, 2, ret, + "lirc_register_driver() failed"); + + /* Reserve I/O port access */ + if (!request_region(ite8709_dev->io, 2, LIRC_DRIVER_NAME)) + return ite8709_cleanup(ite8709_dev, 3, -EBUSY, + "i/o port already in use"); + + /* Reserve IRQ line */ + ret = request_irq(ite8709_dev->irq, ite8709_interrupt, 0, + LIRC_DRIVER_NAME, ite8709_dev); + if (ret < 0) + return ite8709_cleanup(ite8709_dev, 4, ret, + "IRQ already in use"); + + /* Initialize hardware */ + ite8709_drop_hardware(ite8709_dev); /* Shutdown hw until first use */ + + printk(KERN_INFO LIRC_DRIVER_NAME ": device found : irq=%d io=0x%x\n", + ite8709_dev->irq, ite8709_dev->io); + + return 0; +} + +static void __devexit ite8709_pnp_remove(struct pnp_dev *dev) +{ + struct ite8709_device *ite8709_dev; + ite8709_dev = pnp_get_drvdata(dev); + + ite8709_cleanup(ite8709_dev, 6, 0, NULL); + + printk(KERN_INFO LIRC_DRIVER_NAME ": device removed\n"); +} + +#ifdef CONFIG_PM +static int ite8709_pnp_suspend(struct pnp_dev *dev, pm_message_t state) +{ + struct ite8709_device *ite8709_dev; + ite8709_dev = pnp_get_drvdata(dev); + + if (ite8709_dev->use_count > 0) + ite8709_drop_hardware(ite8709_dev); + + return 0; +} + +static int ite8709_pnp_resume(struct pnp_dev *dev) +{ + struct ite8709_device *ite8709_dev; + ite8709_dev = pnp_get_drvdata(dev); + + if (ite8709_dev->use_count > 0) + ite8709_init_hardware(ite8709_dev); + + return 0; +} +#else +#define ite8709_pnp_suspend NULL +#define ite8709_pnp_resume NULL +#endif + +static const struct pnp_device_id pnp_dev_table[] = { + {"ITE8709", 0}, + {} +}; + +MODULE_DEVICE_TABLE(pnp, pnp_dev_table); + +static struct pnp_driver ite8709_pnp_driver = { + .name = LIRC_DRIVER_NAME, + .probe = ite8709_pnp_probe, + .remove = __devexit_p(ite8709_pnp_remove), + .suspend = ite8709_pnp_suspend, + .resume = ite8709_pnp_resume, + .id_table = pnp_dev_table, +}; + +static int __init ite8709_init_module(void) +{ + return pnp_register_driver(&ite8709_pnp_driver); +} +module_init(ite8709_init_module); + +static void __exit ite8709_cleanup_module(void) +{ + pnp_unregister_driver(&ite8709_pnp_driver); +} +module_exit(ite8709_cleanup_module); + +MODULE_DESCRIPTION("LIRC driver for ITE8709 CIR port"); +MODULE_AUTHOR("Grégory Lardière"); +MODULE_LICENSE("GPL"); + +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Enable debugging messages"); |