| /* |
| * 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"); |