diff options
author | Maxim Levitsky <maximlevitsky@gmail.com> | 2010-07-31 11:59:25 -0300 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2010-08-08 23:43:00 -0300 |
commit | 9ea53b74df9c4681f5bb2da6b2e10e37d87ea6d6 (patch) | |
tree | 7f3dc2e209bdcca8388e0aac88c0b7ea2022c6c8 /drivers/media/IR/ene_ir.c | |
parent | ed4d3876d18d95406e0258a6421b1f4eda51d629 (diff) |
V4L/DVB: STAGING: remove lirc_ene0100 driver
Add latest unported version of this driver to media/IR.
Next patch will port it to ir core.
Signed-off-by: Maxim Levitsky <maximlevitsky@gmail.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/IR/ene_ir.c')
-rw-r--r-- | drivers/media/IR/ene_ir.c | 1176 |
1 files changed, 1176 insertions, 0 deletions
diff --git a/drivers/media/IR/ene_ir.c b/drivers/media/IR/ene_ir.c new file mode 100644 index 000000000000..9d11caf55b03 --- /dev/null +++ b/drivers/media/IR/ene_ir.c @@ -0,0 +1,1176 @@ +/* + * driver for ENE KB3926 B/C/D CIR (also known as ENE0100/ENE0200/ENE0201) + * + * Copyright (C) 2010 Maxim Levitsky <maximlevitsky@gmail.com> + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/pnp.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/uaccess.h> +#include "lirc_ene0100.h" + + +static int sample_period = -1; +static int enable_idle = 1; +static int enable_duty_carrier; +static int input = 1; +static int debug; +static int txsim; + +static void ene_rx_set_idle(struct ene_device *dev, int idle); +static int ene_irq_status(struct ene_device *dev); +static void ene_send_sample(struct ene_device *dev, unsigned long sample); + +/* read a hardware register */ +static u8 ene_hw_read_reg(struct ene_device *dev, u16 reg) +{ + u8 retval; + outb(reg >> 8, dev->hw_io + ENE_ADDR_HI); + outb(reg & 0xFF, dev->hw_io + ENE_ADDR_LO); + retval = inb(dev->hw_io + ENE_IO); + + ene_dbg_verbose("reg %04x == %02x", reg, retval); + return retval; +} + +/* write a hardware register */ +static void ene_hw_write_reg(struct ene_device *dev, u16 reg, u8 value) +{ + outb(reg >> 8, dev->hw_io + ENE_ADDR_HI); + outb(reg & 0xFF, dev->hw_io + ENE_ADDR_LO); + outb(value, dev->hw_io + ENE_IO); + + ene_dbg_verbose("reg %04x <- %02x", reg, value); +} + +/* change specific bits in hardware register */ +static void ene_hw_write_reg_mask(struct ene_device *dev, + u16 reg, u8 value, u8 mask) +{ + u8 regvalue; + + outb(reg >> 8, dev->hw_io + ENE_ADDR_HI); + outb(reg & 0xFF, dev->hw_io + ENE_ADDR_LO); + + regvalue = inb(dev->hw_io + ENE_IO) & ~mask; + regvalue |= (value & mask); + outb(regvalue, dev->hw_io + ENE_IO); + + ene_dbg_verbose("reg %04x <- %02x (mask=%02x)", reg, value, mask); +} + +/* detect hardware features */ +static int ene_hw_detect(struct ene_device *dev) +{ + u8 chip_major, chip_minor; + u8 hw_revision, old_ver; + u8 tmp; + u8 fw_capabilities; + + tmp = ene_hw_read_reg(dev, ENE_HW_UNK); + ene_hw_write_reg(dev, ENE_HW_UNK, tmp & ~ENE_HW_UNK_CLR); + + chip_major = ene_hw_read_reg(dev, ENE_HW_VER_MAJOR); + chip_minor = ene_hw_read_reg(dev, ENE_HW_VER_MINOR); + + ene_hw_write_reg(dev, ENE_HW_UNK, tmp); + hw_revision = ene_hw_read_reg(dev, ENE_HW_VERSION); + old_ver = ene_hw_read_reg(dev, ENE_HW_VER_OLD); + + if (hw_revision == 0xFF) { + + ene_printk(KERN_WARNING, "device seems to be disabled\n"); + ene_printk(KERN_WARNING, + "send a mail to lirc-list@lists.sourceforge.net\n"); + ene_printk(KERN_WARNING, "please attach output of acpidump\n"); + return -ENODEV; + } + + if (chip_major == 0x33) { + ene_printk(KERN_WARNING, "chips 0x33xx aren't supported\n"); + return -ENODEV; + } + + if (chip_major == 0x39 && chip_minor == 0x26 && hw_revision == 0xC0) { + dev->hw_revision = ENE_HW_C; + } else if (old_ver == 0x24 && hw_revision == 0xC0) { + dev->hw_revision = ENE_HW_B; + ene_printk(KERN_NOTICE, "KB3926B detected\n"); + } else { + dev->hw_revision = ENE_HW_D; + ene_printk(KERN_WARNING, + "unknown ENE chip detected, assuming KB3926D\n"); + ene_printk(KERN_WARNING, + "driver support might be not complete"); + + } + + ene_printk(KERN_DEBUG, + "chip is 0x%02x%02x - kbver = 0x%02x, rev = 0x%02x\n", + chip_major, chip_minor, old_ver, hw_revision); + + /* detect features hardware supports */ + if (dev->hw_revision < ENE_HW_C) + return 0; + + fw_capabilities = ene_hw_read_reg(dev, ENE_FW2); + ene_dbg("Firmware capabilities: %02x", fw_capabilities); + + dev->hw_gpio40_learning = fw_capabilities & ENE_FW2_GP40_AS_LEARN; + dev->hw_learning_and_tx_capable = fw_capabilities & ENE_FW2_LEARNING; + + dev->hw_fan_as_normal_input = dev->hw_learning_and_tx_capable && + (fw_capabilities & ENE_FW2_FAN_AS_NRML_IN); + + ene_printk(KERN_NOTICE, "hardware features:\n"); + ene_printk(KERN_NOTICE, + "learning and transmit %s, gpio40_learn %s, fan_in %s\n", + dev->hw_learning_and_tx_capable ? "on" : "off", + dev->hw_gpio40_learning ? "on" : "off", + dev->hw_fan_as_normal_input ? "on" : "off"); + + if (dev->hw_learning_and_tx_capable) { + ene_printk(KERN_WARNING, + "Device supports transmitting, but that support is\n"); + ene_printk(KERN_WARNING, + "lightly tested. Please test it and mail\n"); + ene_printk(KERN_WARNING, + "lirc-list@lists.sourceforge.net\n"); + } + return 0; +} + +/* this enables/disables IR input via gpio40*/ +static void ene_enable_gpio40_recieve(struct ene_device *dev, int enable) +{ + ene_hw_write_reg_mask(dev, ENE_CIR_CONF2, enable ? + 0 : ENE_CIR_CONF2_GPIO40DIS, + ENE_CIR_CONF2_GPIO40DIS); +} + +/* this enables/disables IR via standard input */ +static void ene_enable_normal_recieve(struct ene_device *dev, int enable) +{ + ene_hw_write_reg(dev, ENE_CIR_CONF1, enable ? ENE_CIR_CONF1_RX_ON : 0); +} + +/* this enables/disables IR input via unused fan tachtometer input */ +static void ene_enable_fan_recieve(struct ene_device *dev, int enable) +{ + if (!enable) + ene_hw_write_reg(dev, ENE_FAN_AS_IN1, 0); + else { + ene_hw_write_reg(dev, ENE_FAN_AS_IN1, ENE_FAN_AS_IN1_EN); + ene_hw_write_reg(dev, ENE_FAN_AS_IN2, ENE_FAN_AS_IN2_EN); + } + dev->rx_fan_input_inuse = enable; +} + + +/* Sense current recieved carrier */ +static int ene_rx_sense_carrier(struct ene_device *dev) +{ + int period = ene_hw_read_reg(dev, ENE_RX_CARRIER); + int carrier; + ene_dbg("RX: hardware carrier period = %02x", period); + + if (!(period & ENE_RX_CARRIER_VALID)) + return 0; + + period &= ~ENE_RX_CARRIER_VALID; + + if (!period) + return 0; + + carrier = 2000000 / period; + ene_dbg("RX: sensed carrier = %d Hz", carrier); + return carrier; +} + +/* determine which input to use*/ +static void ene_rx_set_inputs(struct ene_device *dev) +{ + int learning_mode = dev->learning_enabled || dev->rx_carrier_sense; + + ene_dbg("RX: setup reciever, learning mode = %d", learning_mode); + + ene_enable_normal_recieve(dev, 1); + + /* old hardware doesn't support learning mode for sure */ + if (dev->hw_revision <= ENE_HW_B) + return; + + /* reciever not learning capable, still set gpio40 correctly */ + if (!dev->hw_learning_and_tx_capable) { + ene_enable_gpio40_recieve(dev, !dev->hw_gpio40_learning); + return; + } + + /* enable learning mode */ + if (learning_mode) { + ene_enable_gpio40_recieve(dev, dev->hw_gpio40_learning); + + /* fan input is not used for learning */ + if (dev->hw_fan_as_normal_input) + ene_enable_fan_recieve(dev, 0); + + /* disable learning mode */ + } else { + if (dev->hw_fan_as_normal_input) { + ene_enable_fan_recieve(dev, 1); + ene_enable_normal_recieve(dev, 0); + } else + ene_enable_gpio40_recieve(dev, + !dev->hw_gpio40_learning); + } + + /* set few additional settings for this mode */ + ene_hw_write_reg_mask(dev, ENE_CIR_CONF1, learning_mode ? + ENE_CIR_CONF1_LEARN1 : 0, ENE_CIR_CONF1_LEARN1); + + ene_hw_write_reg_mask(dev, ENE_CIR_CONF2, learning_mode ? + ENE_CIR_CONF2_LEARN2 : 0, ENE_CIR_CONF2_LEARN2); +} + +/* Enable the device for receive */ +static void ene_rx_enable(struct ene_device *dev) +{ + u8 reg_value; + + if (dev->hw_revision < ENE_HW_C) { + ene_hw_write_reg(dev, ENEB_IRQ, dev->irq << 1); + ene_hw_write_reg(dev, ENEB_IRQ_UNK1, 0x01); + } else { + reg_value = ene_hw_read_reg(dev, ENEC_IRQ) & 0xF0; + reg_value |= ENEC_IRQ_UNK_EN; + reg_value &= ~ENEC_IRQ_STATUS; + reg_value |= (dev->irq & ENEC_IRQ_MASK); + ene_hw_write_reg(dev, ENEC_IRQ, reg_value); + ene_hw_write_reg(dev, ENE_TX_UNK1, 0x63); + } + + ene_hw_write_reg(dev, ENE_CIR_CONF2, 0x00); + ene_rx_set_inputs(dev); + + /* set sampling period */ + ene_hw_write_reg(dev, ENE_CIR_SAMPLE_PERIOD, sample_period); + + /* ack any pending irqs - just in case */ + ene_irq_status(dev); + + /* enter idle mode */ + ene_rx_set_idle(dev, 1); + + /* enable firmware bits */ + ene_hw_write_reg_mask(dev, ENE_FW1, + ENE_FW1_ENABLE | ENE_FW1_IRQ, + ENE_FW1_ENABLE | ENE_FW1_IRQ); +} + +/* Disable the device reciever */ +static void ene_rx_disable(struct ene_device *dev) +{ + /* disable inputs */ + ene_enable_normal_recieve(dev, 0); + + if (dev->hw_fan_as_normal_input) + ene_enable_fan_recieve(dev, 0); + + /* disable hardware IRQ and firmware flag */ + ene_hw_write_reg_mask(dev, ENE_FW1, 0, ENE_FW1_ENABLE | ENE_FW1_IRQ); + + ene_rx_set_idle(dev, 1); +} + +/* send current sample to the user */ +static void ene_rx_flush(struct ene_device *dev, int timeout) +{ + unsigned long value; + + value = dev->rx_sample_pulse ? LIRC_PULSE(dev->rx_sample) : + LIRC_SPACE(dev->rx_sample); + ene_send_sample(dev, value); + dev->rx_sample = 0; + dev->rx_sample_pulse = 0; +} + +/* recieve new sample and process it */ +static void ene_rx_sample(struct ene_device *dev, int sample, int is_pulse) +{ + ene_dbg("RX: sample %8d (%s)", sample, is_pulse ? "pulse" : "space"); + + /* ignore spaces in idle mode, can get them on revC */ + /* also ignore a space in front of first pulse */ + if (dev->rx_idle && !is_pulse) + return; + + /* get out of idle mode now */ + if (dev->rx_idle) + ene_rx_set_idle(dev, 0); + + if (!dev->rx_sample) { + dev->rx_sample = sample; + dev->rx_sample_pulse = is_pulse; + } else if (is_pulse == dev->rx_sample_pulse) + dev->rx_sample += sample; + else { + ene_rx_flush(dev, 0); + dev->rx_sample = sample; + dev->rx_sample_pulse = is_pulse; + } + + if (is_pulse) + return; + + /* overflow sample from fan input recieved, enable idle mode */ + if (dev->rx_fan_input_inuse && + sample == ENE_FAN_VALUE_MASK * ENE_SAMPLE_PERIOD_FAN) { + ene_rx_set_idle(dev, 1); + return; + } + + if (!dev->rx_fan_input_inuse) { + /* Report timeout if enabled */ + if (dev->rx_timeout && dev->rx_send_timeout_packet && + !dev->rx_timeout_sent && + dev->rx_sample > dev->rx_timeout) { + ene_dbg("RX: sending timeout sample"); + ene_send_sample(dev, LIRC_TIMEOUT(dev->rx_sample)); + dev->rx_timeout_sent = 1; + } + + /* too large sample accumulated via normal input. + note that on revC, hardware idle mode turns on automaticly, + so max gap should be less that the gap after which + hw stops sending samples */ + if (dev->rx_sample > ENE_MAXGAP) { + ene_rx_set_idle(dev, 1); + return; + } + } +} + +/* enable or disable idle mode */ +static void ene_rx_set_idle(struct ene_device *dev, int idle) +{ + struct timeval now; + int disable_sampler = 0; + + + /* Also put hardware sampler in 'idle' mode on revB*/ + /* revC and higher do that automaticly (firmware does?) */ + if ((dev->hw_revision < ENE_HW_C) && enable_idle) + if (idle) + disable_sampler = 1; + + ene_hw_write_reg_mask(dev, ENE_CIR_SAMPLE_PERIOD, + disable_sampler ? 0 : ENE_CIR_SAMPLE_OVERFLOW, + ENE_CIR_SAMPLE_OVERFLOW); + dev->rx_idle = idle; + + /* remember when we have entered the idle mode */ + if (idle) { + ene_dbg("RX: going into idle mode"); + do_gettimeofday(&dev->rx_gap_start); + return; + } + + ene_dbg("RX: back from idle mode"); + + /* send the gap between keypresses now */ + do_gettimeofday(&now); + + if (dev->rx_sample_pulse) { + ene_dbg("RX: somehow we recieved a pulse before idle mode???"); + return; + } + + /* manually calculate and recieve the gap between keypresses */ + if (now.tv_sec - dev->rx_gap_start.tv_sec > 16) + dev->rx_sample = LIRC_SPACE(LIRC_VALUE_MASK); + else + dev->rx_sample += + 1000000ull * (now.tv_sec - dev->rx_gap_start.tv_sec) + + now.tv_usec - dev->rx_gap_start.tv_usec; + + if (dev->rx_sample > LIRC_SPACE(LIRC_VALUE_MASK)) + dev->rx_sample = LIRC_SPACE(LIRC_VALUE_MASK); + + ene_rx_flush(dev, 0); + dev->rx_timeout_sent = 0; +} + +/* prepare transmission */ +static void ene_tx_prepare(struct ene_device *dev) +{ + u8 conf1; + + conf1 = ene_hw_read_reg(dev, ENE_CIR_CONF1); + dev->saved_conf1 = conf1; + + if (dev->hw_revision == ENE_HW_C) + conf1 &= ~ENE_CIR_CONF1_TX_CLEAR; + + /* Enable TX engine */ + conf1 |= ENE_CIR_CONF1_TX_ON; + + /* Set carrier */ + if (dev->tx_period) { + + int tx_period_in500ns = dev->tx_period * 2; + + int tx_pulse_width_in_500ns = + tx_period_in500ns / (100 / dev->tx_duty_cycle); + + if (!tx_pulse_width_in_500ns) + tx_pulse_width_in_500ns = 1; + + ene_dbg("TX: pulse distance = %d * 500 ns", tx_period_in500ns); + ene_dbg("TX: pulse width = %d * 500 ns", + tx_pulse_width_in_500ns); + + ene_hw_write_reg(dev, ENE_TX_PERIOD, ENE_TX_PERIOD_UNKBIT | + tx_period_in500ns); + + ene_hw_write_reg(dev, ENE_TX_PERIOD_PULSE, + tx_pulse_width_in_500ns); + + conf1 |= ENE_CIR_CONF1_TX_CARR; + } else + conf1 &= ~ENE_CIR_CONF1_TX_CARR; + + ene_hw_write_reg(dev, ENE_CIR_CONF1, conf1); + dev->tx_underway = 1; + +} + +/* end transmission */ +static void ene_tx_complete(struct ene_device *dev) +{ + ene_hw_write_reg(dev, ENE_CIR_CONF1, dev->saved_conf1); + dev->tx_underway = 0; +} + +/* set transmit mask */ +static void ene_tx_set_transmiter_mask(struct ene_device *dev) +{ + u8 txport1 = ene_hw_read_reg(dev, ENE_TX_PORT1) & ~ENE_TX_PORT1_EN; + u8 txport2 = ene_hw_read_reg(dev, ENE_TX_PORT2) & ~ENE_TX_PORT2_EN; + + if (dev->transmitter_mask & 0x01) + txport1 |= ENE_TX_PORT1_EN; + + if (dev->transmitter_mask & 0x02) + txport2 |= ENE_TX_PORT2_EN; + + ene_hw_write_reg(dev, ENE_TX_PORT1, txport1); + ene_hw_write_reg(dev, ENE_TX_PORT2, txport2); +} + +/* TX one sample - must be called with dev->hw_lock*/ +static void ene_tx_sample(struct ene_device *dev) +{ + u8 raw_tx; + u32 sample; + + if (!dev->tx_underway) { + ene_dbg("TX: attempt to transmit while hw isn't setup"); + return; + } + + /* Grab next TX sample */ + if (!dev->tx_sample) { +again: + if (dev->tx_pos == dev->tx_len + 1) { + if (!dev->tx_done) { + ene_dbg("TX: no more data to send"); + dev->tx_done = 1; + goto exit; + } else { + ene_dbg("TX: last sample sent by hardware"); + ene_tx_complete(dev); + complete(&dev->tx_complete); + return; + } + } + + sample = dev->tx_buffer[dev->tx_pos++]; + dev->tx_sample_pulse = !dev->tx_sample_pulse; + + ene_dbg("TX: sample %8d (%s)", sample, dev->tx_sample_pulse ? + "pulse" : "space"); + + dev->tx_sample = DIV_ROUND_CLOSEST(sample, ENE_TX_SMPL_PERIOD); + + /* guard against too short samples */ + if (!dev->tx_sample) + goto again; + } + + raw_tx = min(dev->tx_sample , (unsigned int)ENE_TX_SMLP_MASK); + dev->tx_sample -= raw_tx; + + if (dev->tx_sample_pulse) + raw_tx |= ENE_TX_PULSE_MASK; + + ene_hw_write_reg(dev, ENE_TX_INPUT1 + dev->tx_reg, raw_tx); + dev->tx_reg = !dev->tx_reg; +exit: + /* simulate TX done interrupt */ + if (txsim) + mod_timer(&dev->tx_sim_timer, jiffies + HZ / 500); +} + +/* timer to simulate tx done interrupt */ +static void ene_tx_irqsim(unsigned long data) +{ + struct ene_device *dev = (struct ene_device *)data; + unsigned long flags; + + spin_lock_irqsave(&dev->hw_lock, flags); + ene_tx_sample(dev); + spin_unlock_irqrestore(&dev->hw_lock, flags); +} + + +/* read irq status and ack it */ +static int ene_irq_status(struct ene_device *dev) +{ + u8 irq_status; + u8 fw_flags1, fw_flags2; + int cur_rx_pointer; + int retval = 0; + + fw_flags2 = ene_hw_read_reg(dev, ENE_FW2); + cur_rx_pointer = !!(fw_flags2 & ENE_FW2_BUF_HIGH); + + if (dev->hw_revision < ENE_HW_C) { + irq_status = ene_hw_read_reg(dev, ENEB_IRQ_STATUS); + + if (!(irq_status & ENEB_IRQ_STATUS_IR)) + return 0; + + ene_hw_write_reg(dev, ENEB_IRQ_STATUS, + irq_status & ~ENEB_IRQ_STATUS_IR); + dev->rx_pointer = cur_rx_pointer; + return ENE_IRQ_RX; + } + + irq_status = ene_hw_read_reg(dev, ENEC_IRQ); + + if (!(irq_status & ENEC_IRQ_STATUS)) + return 0; + + /* original driver does that twice - a workaround ? */ + ene_hw_write_reg(dev, ENEC_IRQ, irq_status & ~ENEC_IRQ_STATUS); + ene_hw_write_reg(dev, ENEC_IRQ, irq_status & ~ENEC_IRQ_STATUS); + + /* clear unknown flag in F8F9 */ + if (fw_flags2 & ENE_FW2_IRQ_CLR) + ene_hw_write_reg(dev, ENE_FW2, fw_flags2 & ~ENE_FW2_IRQ_CLR); + + /* check if this is a TX interrupt */ + fw_flags1 = ene_hw_read_reg(dev, ENE_FW1); + if (fw_flags1 & ENE_FW1_TXIRQ) { + ene_hw_write_reg(dev, ENE_FW1, fw_flags1 & ~ENE_FW1_TXIRQ); + retval |= ENE_IRQ_TX; + } + + /* Check if this is RX interrupt */ + if (dev->rx_pointer != cur_rx_pointer) { + retval |= ENE_IRQ_RX; + dev->rx_pointer = cur_rx_pointer; + + } else if (!(retval & ENE_IRQ_TX)) { + ene_dbg("RX: interrupt without change in RX pointer(%d)", + dev->rx_pointer); + retval |= ENE_IRQ_RX; + } + + if ((retval & ENE_IRQ_RX) && (retval & ENE_IRQ_TX)) + ene_dbg("both RX and TX interrupt at same time"); + + return retval; +} + +/* interrupt handler */ +static irqreturn_t ene_isr(int irq, void *data) +{ + u16 hw_value; + int i, hw_sample; + int pulse; + int irq_status; + unsigned long flags; + int carrier = 0; + irqreturn_t retval = IRQ_NONE; + struct ene_device *dev = (struct ene_device *)data; + + + spin_lock_irqsave(&dev->hw_lock, flags); + irq_status = ene_irq_status(dev); + + if (!irq_status) + goto unlock; + + retval = IRQ_HANDLED; + + if (irq_status & ENE_IRQ_TX) { + + if (!dev->hw_learning_and_tx_capable) { + ene_dbg("TX interrupt on unsupported device!"); + goto unlock; + } + ene_tx_sample(dev); + } + + if (!(irq_status & ENE_IRQ_RX)) + goto unlock; + + + if ((debug && dev->learning_enabled) || dev->rx_carrier_sense) + carrier = ene_rx_sense_carrier(dev); + + if (dev->rx_carrier_sense && carrier) + ene_send_sample(dev, LIRC_FREQUENCY(carrier)); + + + for (i = 0; i < ENE_SAMPLES_SIZE; i++) { + hw_value = ene_hw_read_reg(dev, + ENE_SAMPLE_BUFFER + dev->rx_pointer * 4 + i); + + if (dev->rx_fan_input_inuse) { + /* read high part of the sample */ + hw_value |= ene_hw_read_reg(dev, + ENE_SAMPLE_BUFFER_FAN + + dev->rx_pointer * 4 + i) << 8; + pulse = hw_value & ENE_FAN_SMPL_PULS_MSK; + + /* clear space bit, and other unused bits */ + hw_value &= ENE_FAN_VALUE_MASK; + hw_sample = hw_value * ENE_SAMPLE_PERIOD_FAN; + + } else { + pulse = !(hw_value & ENE_SAMPLE_SPC_MASK); + hw_value &= ENE_SAMPLE_VALUE_MASK; + hw_sample = hw_value * sample_period; + } + /* no more data */ + if (!(hw_value)) + break; + + ene_rx_sample(dev, hw_sample, pulse); + } +unlock: + spin_unlock_irqrestore(&dev->hw_lock, flags); + return retval; +} + +/* Initialize default settings */ +static void ene_setup_settings(struct ene_device *dev) +{ + dev->rx_send_timeout_packet = 0; + dev->rx_timeout = ENE_MAXGAP; + dev->tx_period = 32; + dev->tx_duty_cycle = 25; /*%*/ + dev->transmitter_mask = 3; + + /* Force learning mode if (input == 2), otherwise + let user set it with LIRC_SET_REC_CARRIER */ + dev->learning_enabled = + (input == 2 && dev->hw_learning_and_tx_capable); + + /* Clear accumulated sample bufer */ + dev->rx_sample = 0; + dev->rx_sample_pulse = 0; + dev->rx_pointer = -1; + dev->rx_carrier_sense = 0; + +} + +/* outside interface: called on first open*/ +static int ene_open(void *data) +{ + struct ene_device *dev = (struct ene_device *)data; + unsigned long flags; + + spin_lock_irqsave(&dev->hw_lock, flags); + dev->in_use = 1; + ene_setup_settings(dev); + ene_rx_enable(dev); + spin_unlock_irqrestore(&dev->hw_lock, flags); + return 0; +} + +/* outside interface: called on device close*/ +static void ene_close(void *data) +{ + struct ene_device *dev = (struct ene_device *)data; + unsigned long flags; + spin_lock_irqsave(&dev->hw_lock, flags); + + ene_rx_disable(dev); + dev->in_use = 0; + spin_unlock_irqrestore(&dev->hw_lock, flags); +} + +/* outside interface for settings */ +static int ene_ioctl(struct inode *node, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int lvalue = 0, retval, tmp; + unsigned long flags; + struct ene_device *dev = lirc_get_pdata(file); + + + switch (cmd) { + case LIRC_SET_SEND_CARRIER: + case LIRC_SET_SEND_DUTY_CYCLE: + case LIRC_SET_TRANSMITTER_MASK: + case LIRC_SET_MEASURE_CARRIER_MODE: + case LIRC_SET_REC_CARRIER: + /* All these aren't possible without this */ + if (!dev->hw_learning_and_tx_capable) + return -ENOSYS; + /* Fall through */ + case LIRC_SET_REC_TIMEOUT: + case LIRC_SET_REC_TIMEOUT_REPORTS: + retval = get_user(lvalue, (unsigned int *) arg); + if (retval) + return retval; + } + + switch (cmd) { + case LIRC_SET_SEND_CARRIER: + ene_dbg("TX: attempt to set tx carrier to %d kHz", lvalue); + tmp = 1000000 / lvalue; /* (1 / freq) (* # usec in 1 sec) */ + + if (tmp && (tmp > ENE_TX_PERIOD_MAX || + tmp < ENE_TX_PERIOD_MIN)) { + + ene_dbg("TX: out of range %d-%d carrier, " + "falling back to 32 kHz", + 1000 / ENE_TX_PERIOD_MIN, + 1000 / ENE_TX_PERIOD_MAX); + + tmp = 32; /* this is just a coincidence!!! */ + } + ene_dbg("TX: set carrier to %d kHz", lvalue); + + spin_lock_irqsave(&dev->hw_lock, flags); + dev->tx_period = tmp; + spin_unlock_irqrestore(&dev->hw_lock, flags); + break; + case LIRC_SET_SEND_DUTY_CYCLE: + ene_dbg("TX: attempt to set duty cycle to %d%%", lvalue); + + if ((lvalue >= 100) || (lvalue <= 0)) { + retval = -EINVAL; + break; + } + spin_lock_irqsave(&dev->hw_lock, flags); + dev->tx_duty_cycle = lvalue; + spin_unlock_irqrestore(&dev->hw_lock, flags); + break; + case LIRC_SET_TRANSMITTER_MASK: + ene_dbg("TX: attempt to set transmitter mask %02x", lvalue); + + /* invalid txmask */ + if (!lvalue || lvalue & ~0x3) { + ene_dbg("TX: invalid mask"); + /* this supposed to return num of transmitters */ + retval = 2; + break; + } + spin_lock_irqsave(&dev->hw_lock, flags); + dev->transmitter_mask = lvalue; + spin_unlock_irqrestore(&dev->hw_lock, flags); + break; + case LIRC_SET_REC_CARRIER: + tmp = (lvalue > ENE_NORMAL_RX_HI || lvalue < ENE_NORMAL_RX_LOW); + + if (tmp != dev->learning_enabled) { + spin_lock_irqsave(&dev->hw_lock, flags); + dev->learning_enabled = tmp; + ene_rx_set_inputs(dev); + spin_unlock_irqrestore(&dev->hw_lock, flags); + } + break; + case LIRC_SET_REC_TIMEOUT: + spin_lock_irqsave(&dev->hw_lock, flags); + dev->rx_timeout = lvalue; + spin_unlock_irqrestore(&dev->hw_lock, flags); + ene_dbg("RX: set rx report timeout to %d", dev->rx_timeout); + break; + case LIRC_SET_REC_TIMEOUT_REPORTS: + spin_lock_irqsave(&dev->hw_lock, flags); + dev->rx_send_timeout_packet = lvalue; + spin_unlock_irqrestore(&dev->hw_lock, flags); + ene_dbg("RX: %sable timeout reports", + dev->rx_send_timeout_packet ? "en" : "dis"); + break; + case LIRC_SET_MEASURE_CARRIER_MODE: + if (dev->rx_carrier_sense == lvalue) + break; + spin_lock_irqsave(&dev->hw_lock, flags); + dev->rx_carrier_sense = lvalue; + ene_rx_set_inputs(dev); + spin_unlock_irqrestore(&dev->hw_lock, flags); + break; + case LIRC_GET_REC_RESOLUTION: + tmp = dev->rx_fan_input_inuse ? + ENE_SAMPLE_PERIOD_FAN : sample_period; + retval = put_user(tmp, (unsigned long *) arg); + break; + default: + retval = -ENOIOCTLCMD; + break; + } + + return retval; +} + +/* outside interface: transmit */ +static ssize_t ene_transmit(struct file *file, const char *buf, + size_t n, loff_t *ppos) +{ + struct ene_device *dev = lirc_get_pdata(file); + unsigned long flags; + + if (!dev) + return -EFAULT; + + if (!dev->hw_learning_and_tx_capable) + return -ENODEV; + + if (n % sizeof(int)) + return -EINVAL; + + if (n > ENE_TXBUF_SIZE * sizeof(int)) + return -ENOMEM; + + if (copy_from_user(dev->tx_buffer, buf, n)) + return -EFAULT; + + dev->tx_len = n / sizeof(int); + dev->tx_pos = 0; + dev->tx_reg = 0; + dev->tx_done = 0; + dev->tx_sample = 0; + dev->tx_sample_pulse = 0; + + ene_dbg("TX: %d samples", dev->tx_len); + + spin_lock_irqsave(&dev->hw_lock, flags); + + ene_tx_set_transmiter_mask(dev); + ene_tx_prepare(dev); + + /* Transmit first two samples */ + ene_tx_sample(dev); + ene_tx_sample(dev); + + spin_unlock_irqrestore(&dev->hw_lock, flags); + + if (wait_for_completion_timeout(&dev->tx_complete, 2 * HZ) == 0) { + ene_dbg("TX: timeout"); + spin_lock_irqsave(&dev->hw_lock, flags); + ene_tx_complete(dev); + spin_unlock_irqrestore(&dev->hw_lock, flags); + } else + ene_dbg("TX: done"); + + return n; +} + +/* Sends one sample to the user */ +static void ene_send_sample(struct ene_device *dev, unsigned long sample) +{ + if (!lirc_buffer_full(dev->lirc_driver->rbuf)) { + lirc_buffer_write(dev->lirc_driver->rbuf, (void *)&sample); + wake_up(&dev->lirc_driver->rbuf->wait_poll); + } +} + + +static const struct file_operations ene_fops = { + .owner = THIS_MODULE, + .write = ene_transmit, + .ioctl = ene_ioctl, +}; + +/* main load function */ +static int ene_probe(struct pnp_dev *pnp_dev, + const struct pnp_device_id *dev_id) +{ + struct ene_device *dev; + struct lirc_driver *lirc_driver; + int error = -ENOMEM; + + dev = kzalloc(sizeof(struct ene_device), GFP_KERNEL); + + if (!dev) + goto err1; + + dev->pnp_dev = pnp_dev; + pnp_set_drvdata(pnp_dev, dev); + + /* prepare lirc interface */ + error = -ENOMEM; + lirc_driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL); + + if (!lirc_driver) + goto err2; + + dev->lirc_driver = lirc_driver; + + strcpy(lirc_driver->name, ENE_DRIVER_NAME); + lirc_driver->minor = -1; + lirc_driver->code_length = sizeof(int) * 8; + lirc_driver->features = LIRC_CAN_REC_MODE2 | + LIRC_CAN_GET_REC_RESOLUTION | + LIRC_CAN_SET_REC_TIMEOUT; + lirc_driver->data = dev; + lirc_driver->set_use_inc = ene_open; + lirc_driver->set_use_dec = ene_close; + lirc_driver->dev = &pnp_dev->dev; + lirc_driver->owner = THIS_MODULE; + lirc_driver->fops = &ene_fops; + lirc_driver->min_timeout = ENE_MINGAP; + lirc_driver->max_timeout = ENE_MAXGAP; + lirc_driver->rbuf = kzalloc(sizeof(struct lirc_buffer), GFP_KERNEL); + + if (!lirc_driver->rbuf) + goto err3; + + if (lirc_buffer_init(lirc_driver->rbuf, sizeof(int), sizeof(int) * 512)) + goto err4; + + /* validate resources */ + if (!pnp_port_valid(pnp_dev, 0) || + pnp_port_len(pnp_dev, 0) < ENE_MAX_IO) + goto err5; + + if (!pnp_irq_valid(pnp_dev, 0)) + goto err5; + + dev->hw_io = pnp_port_start(pnp_dev, 0); + dev->irq = pnp_irq(pnp_dev, 0); + spin_lock_init(&dev->hw_lock); + + /* claim the resources */ + error = -EBUSY; + if (!request_region(dev->hw_io, ENE_MAX_IO, ENE_DRIVER_NAME)) + goto err5; + + if (request_irq(dev->irq, ene_isr, + IRQF_SHARED, ENE_DRIVER_NAME, (void *)dev)) + goto err6; + + /* detect hardware version and features */ + error = ene_hw_detect(dev); + if (error) + goto err7; + + ene_setup_settings(dev); + + if (!dev->hw_learning_and_tx_capable && txsim) { + dev->hw_learning_and_tx_capable = 1; + setup_timer(&dev->tx_sim_timer, ene_tx_irqsim, + (long unsigned int)dev); + ene_printk(KERN_WARNING, + "Simulation of TX activated\n"); + } + + if (dev->hw_learning_and_tx_capable) { + lirc_driver->features |= LIRC_CAN_SEND_PULSE | + LIRC_CAN_SET_SEND_CARRIER | + LIRC_CAN_SET_TRANSMITTER_MASK; + + if (enable_duty_carrier) + lirc_driver->features |= LIRC_CAN_SET_SEND_DUTY_CYCLE; + + if (input == 0) + lirc_driver->features |= LIRC_CAN_SET_REC_CARRIER; + + init_completion(&dev->tx_complete); + } + + /* don't allow too short/long sample periods */ + if (sample_period < 5 || sample_period > 0x7F) + sample_period = -1; + + /* choose default sample period */ + if (sample_period == -1) { + + sample_period = 50; + + /* on revB, hardware idle mode eats first sample + if we set too low sample period */ + if (dev->hw_revision == ENE_HW_B && enable_idle) + sample_period = 75; + } + + device_set_wakeup_capable(&pnp_dev->dev, 1); + device_set_wakeup_enable(&pnp_dev->dev, 1); + + error = -ENODEV; + if (lirc_register_driver(lirc_driver)) + goto err7; + + ene_printk(KERN_NOTICE, "driver has been succesfully loaded\n"); + return 0; + +err7: + free_irq(dev->irq, dev); +err6: + release_region(dev->hw_io, ENE_MAX_IO); +err5: + lirc_buffer_free(lirc_driver->rbuf); +err4: + kfree(lirc_driver->rbuf); +err3: + kfree(lirc_driver); +err2: + kfree(dev); +err1: + return error; +} + +/* main unload function */ +static void ene_remove(struct pnp_dev *pnp_dev) +{ + struct ene_device *dev = pnp_get_drvdata(pnp_dev); + unsigned long flags; + + spin_lock_irqsave(&dev->hw_lock, flags); + ene_rx_disable(dev); + spin_unlock_irqrestore(&dev->hw_lock, flags); + + free_irq(dev->irq, dev); + release_region(dev->hw_io, ENE_MAX_IO); + lirc_unregister_driver(dev->lirc_driver->minor); + lirc_buffer_free(dev->lirc_driver->rbuf); + kfree(dev->lirc_driver); + kfree(dev); +} + +/* enable wake on IR (wakes on specific button on original remote) */ +static void ene_enable_wake(struct ene_device *dev, int enable) +{ + enable = enable && device_may_wakeup(&dev->pnp_dev->dev); + + ene_dbg("wake on IR %s", enable ? "enabled" : "disabled"); + + ene_hw_write_reg_mask(dev, ENE_FW1, enable ? + ENE_FW1_WAKE : 0, ENE_FW1_WAKE); +} + +#ifdef CONFIG_PM +static int ene_suspend(struct pnp_dev *pnp_dev, pm_message_t state) +{ + struct ene_device *dev = pnp_get_drvdata(pnp_dev); + ene_enable_wake(dev, 1); + return 0; +} + +static int ene_resume(struct pnp_dev *pnp_dev) +{ + struct ene_device *dev = pnp_get_drvdata(pnp_dev); + if (dev->in_use) + ene_rx_enable(dev); + + ene_enable_wake(dev, 0); + return 0; +} +#endif + +static void ene_shutdown(struct pnp_dev *pnp_dev) +{ + struct ene_device *dev = pnp_get_drvdata(pnp_dev); + ene_enable_wake(dev, 1); +} + +static const struct pnp_device_id ene_ids[] = { + {.id = "ENE0100",}, + {.id = "ENE0200",}, + {.id = "ENE0201",}, + {}, +}; + +static struct pnp_driver ene_driver = { + .name = ENE_DRIVER_NAME, + .id_table = ene_ids, + .flags = PNP_DRIVER_RES_DO_NOT_CHANGE, + + .probe = ene_probe, + .remove = __devexit_p(ene_remove), +#ifdef CONFIG_PM + .suspend = ene_suspend, + .resume = ene_resume, +#endif + .shutdown = ene_shutdown, +}; + +static int __init ene_init(void) +{ + return pnp_register_driver(&ene_driver); +} + +static void ene_exit(void) +{ + pnp_unregister_driver(&ene_driver); +} + +module_param(sample_period, int, S_IRUGO); +MODULE_PARM_DESC(sample_period, "Hardware sample period (50 us default)"); + +module_param(enable_idle, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(enable_idle, + "Enables turning off signal sampling after long inactivity time; " + "if disabled might help detecting input signal (default: enabled)" + " (KB3926B only)"); + +module_param(input, bool, S_IRUGO); +MODULE_PARM_DESC(input, "select which input to use " + "0 - auto, 1 - standard, 2 - wideband(KB3926C+)"); + +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Enable debug (debug=2 verbose debug output)"); + +module_param(txsim, bool, S_IRUGO); +MODULE_PARM_DESC(txsim, + "Simulate TX features on unsupported hardware (dangerous)"); + +module_param(enable_duty_carrier, bool, S_IRUGO); +MODULE_PARM_DESC(enable_duty_carrier, |