| /* |
| * i2c IR lirc driver for devices with zilog IR processors |
| * |
| * Copyright (c) 2000 Gerd Knorr <kraxel@goldbach.in-berlin.de> |
| * modified for PixelView (BT878P+W/FM) by |
| * Michal Kochanowicz <mkochano@pld.org.pl> |
| * Christoph Bartelmus <lirc@bartelmus.de> |
| * modified for KNC ONE TV Station/Anubis Typhoon TView Tuner by |
| * Ulrich Mueller <ulrich.mueller42@web.de> |
| * modified for Asus TV-Box and Creative/VisionTek BreakOut-Box by |
| * Stefan Jahn <stefan@lkcc.org> |
| * modified for inclusion into kernel sources by |
| * Jerome Brock <jbrock@users.sourceforge.net> |
| * modified for Leadtek Winfast PVR2000 by |
| * Thomas Reitmayr (treitmayr@yahoo.com) |
| * modified for Hauppauge PVR-150 IR TX device by |
| * Mark Weaver <mark@npsl.co.uk> |
| * changed name from lirc_pvr150 to lirc_zilog, works on more than pvr-150 |
| * Jarod Wilson <jarod@redhat.com> |
| * |
| * parts are cut&pasted from the lirc_i2c.c driver |
| * |
| * Numerous changes updating lirc_zilog.c in kernel 2.6.38 and later are |
| * Copyright (C) 2011 Andy Walls <awalls@md.metrocast.net> |
| * |
| * 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/version.h> |
| #include <linux/module.h> |
| #include <linux/kmod.h> |
| #include <linux/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/fs.h> |
| #include <linux/poll.h> |
| #include <linux/string.h> |
| #include <linux/timer.h> |
| #include <linux/delay.h> |
| #include <linux/completion.h> |
| #include <linux/errno.h> |
| #include <linux/slab.h> |
| #include <linux/i2c.h> |
| #include <linux/firmware.h> |
| #include <linux/vmalloc.h> |
| |
| #include <linux/mutex.h> |
| #include <linux/kthread.h> |
| |
| #include <media/lirc_dev.h> |
| #include <media/lirc.h> |
| |
| struct IR_rx { |
| /* RX device */ |
| struct i2c_client *c; |
| |
| /* RX device buffer & lock */ |
| struct lirc_buffer buf; |
| struct mutex buf_lock; |
| |
| /* RX polling thread data */ |
| struct task_struct *task; |
| |
| /* RX read data */ |
| unsigned char b[3]; |
| bool hdpvr_data_fmt; |
| }; |
| |
| struct IR_tx { |
| /* TX device */ |
| struct i2c_client *c; |
| |
| /* TX additional actions needed */ |
| int need_boot; |
| bool post_tx_ready_poll; |
| }; |
| |
| struct IR { |
| struct lirc_driver l; |
| |
| struct mutex ir_lock; |
| int open; |
| |
| struct i2c_adapter *adapter; |
| struct IR_rx *rx; |
| struct IR_tx *tx; |
| }; |
| |
| /* Minor -> data mapping */ |
| static struct mutex ir_devices_lock; |
| static struct IR *ir_devices[MAX_IRCTL_DEVICES]; |
| |
| /* Block size for IR transmitter */ |
| #define TX_BLOCK_SIZE 99 |
| |
| /* Hauppauge IR transmitter data */ |
| struct tx_data_struct { |
| /* Boot block */ |
| unsigned char *boot_data; |
| |
| /* Start of binary data block */ |
| unsigned char *datap; |
| |
| /* End of binary data block */ |
| unsigned char *endp; |
| |
| /* Number of installed codesets */ |
| unsigned int num_code_sets; |
| |
| /* Pointers to codesets */ |
| unsigned char **code_sets; |
| |
| /* Global fixed data template */ |
| int fixed[TX_BLOCK_SIZE]; |
| }; |
| |
| static struct tx_data_struct *tx_data; |
| static struct mutex tx_data_lock; |
| |
| #define zilog_notify(s, args...) printk(KERN_NOTICE KBUILD_MODNAME ": " s, \ |
| ## args) |
| #define zilog_error(s, args...) printk(KERN_ERR KBUILD_MODNAME ": " s, ## args) |
| #define zilog_info(s, args...) printk(KERN_INFO KBUILD_MODNAME ": " s, ## args) |
| |
| /* module parameters */ |
| static int debug; /* debug output */ |
| static int tx_only; /* only handle the IR Tx function */ |
| static int minor = -1; /* minor number */ |
| |
| #define dprintk(fmt, args...) \ |
| do { \ |
| if (debug) \ |
| printk(KERN_DEBUG KBUILD_MODNAME ": " fmt, \ |
| ## args); \ |
| } while (0) |
| |
| static int add_to_buf(struct IR *ir) |
| { |
| __u16 code; |
| unsigned char codes[2]; |
| unsigned char keybuf[6]; |
| int got_data = 0; |
| int ret; |
| int failures = 0; |
| unsigned char sendbuf[1] = { 0 }; |
| struct IR_rx *rx = ir->rx; |
| |
| if (rx == NULL) |
| return -ENXIO; |
| |
| if (lirc_buffer_full(&rx->buf)) { |
| dprintk("buffer overflow\n"); |
| return -EOVERFLOW; |
| } |
| |
| /* |
| * service the device as long as it is returning |
| * data and we have space |
| */ |
| do { |
| if (kthread_should_stop()) |
| return -ENODATA; |
| |
| /* |
| * Lock i2c bus for the duration. RX/TX chips interfere so |
| * this is worth it |
| */ |
| mutex_lock(&ir->ir_lock); |
| |
| if (kthread_should_stop()) { |
| mutex_unlock(&ir->ir_lock); |
| return -ENODATA; |
| } |
| |
| /* |
| * Send random "poll command" (?) Windows driver does this |
| * and it is a good point to detect chip failure. |
| */ |
| ret = i2c_master_send(rx->c, sendbuf, 1); |
| if (ret != 1) { |
| zilog_error("i2c_master_send failed with %d\n", ret); |
| if (failures >= 3) { |
| mutex_unlock(&ir->ir_lock); |
| zilog_error("unable to read from the IR chip " |
| "after 3 resets, giving up\n"); |
| return ret; |
| } |
| |
| /* Looks like the chip crashed, reset it */ |
| zilog_error("polling the IR receiver chip failed, " |
| "trying reset\n"); |
| |
| set_current_state(TASK_UNINTERRUPTIBLE); |
| if (kthread_should_stop()) { |
| mutex_unlock(&ir->ir_lock); |
| return -ENODATA; |
| } |
| schedule_timeout((100 * HZ + 999) / 1000); |
| ir->tx->need_boot = 1; |
| |
| ++failures; |
| mutex_unlock(&ir->ir_lock); |
| continue; |
| } |
| |
| if (kthread_should_stop()) { |
| mutex_unlock(&ir->ir_lock); |
| return -ENODATA; |
| } |
| ret = i2c_master_recv(rx->c, keybuf, sizeof(keybuf)); |
| mutex_unlock(&ir->ir_lock); |
| if (ret != sizeof(keybuf)) { |
| zilog_error("i2c_master_recv failed with %d -- " |
| "keeping last read buffer\n", ret); |
| } else { |
| rx->b[0] = keybuf[3]; |
| rx->b[1] = keybuf[4]; |
| rx->b[2] = keybuf[5]; |
| dprintk("key (0x%02x/0x%02x)\n", rx->b[0], rx->b[1]); |
| } |
| |
| /* key pressed ? */ |
| if (rx->hdpvr_data_fmt) { |
| if (got_data && (keybuf[0] == 0x80)) |
| return 0; |
| else if (got_data && (keybuf[0] == 0x00)) |
| return -ENODATA; |
| } else if ((rx->b[0] & 0x80) == 0) |
| return got_data ? 0 : -ENODATA; |
| |
| /* look what we have */ |
| code = (((__u16)rx->b[0] & 0x7f) << 6) | (rx->b[1] >> 2); |
| |
| codes[0] = (code >> 8) & 0xff; |
| codes[1] = code & 0xff; |
| |
| /* return it */ |
| lirc_buffer_write(&rx->buf, codes); |
| ++got_data; |
| } while (!lirc_buffer_full(&rx->buf)); |
| |
| return 0; |
| } |
| |
| /* |
| * Main function of the polling thread -- from lirc_dev. |
| * We don't fit the LIRC model at all anymore. This is horrible, but |
| * basically we have a single RX/TX device with a nasty failure mode |
| * that needs to be accounted for across the pair. lirc lets us provide |
| * fops, but prevents us from using the internal polling, etc. if we do |
| * so. Hence the replication. Might be neater to extend the LIRC model |
| * to account for this but I'd think it's a very special case of seriously |
| * messed up hardware. |
| */ |
| static int lirc_thread(void *arg) |
| { |
| struct IR *ir = arg; |
| struct IR_rx *rx = ir->rx; |
| |
| dprintk("poll thread started\n"); |
| |
| while (!kthread_should_stop()) { |
| set_current_state(TASK_INTERRUPTIBLE); |
| |
| /* if device not opened, we can sleep half a second */ |
| if (!ir->open) { |
| schedule_timeout(HZ/2); |
| continue; |
| } |
| |
| /* |
| * This is ~113*2 + 24 + jitter (2*repeat gap + code length). |
| * We use this interval as the chip resets every time you poll |
| * it (bad!). This is therefore just sufficient to catch all |
| * of the button presses. It makes the remote much more |
| * responsive. You can see the difference by running irw and |
| * holding down a button. With 100ms, the old polling |
| * interval, you'll notice breaks in the repeat sequence |
| * corresponding to lost keypresses. |
| */ |
| schedule_timeout((260 * HZ) / 1000); |
| if (kthread_should_stop()) |
| break; |
| if (!add_to_buf(ir)) |
| wake_up_interruptible(&rx->buf.wait_poll); |
| } |
| |
| dprintk("poll thread ended\n"); |
| return 0; |
| } |
| |
| static int set_use_inc(void *data) |
| { |
| struct IR *ir = data; |
| |
| if (ir->l.owner == NULL || try_module_get(ir->l.owner) == 0) |
| return -ENODEV; |
| |
| /* lock bttv in memory while /dev/lirc is in use */ |
| /* |
| * this is completely broken code. lirc_unregister_driver() |
| * must be possible even when the device is open |
| */ |
| if (ir->rx != NULL) |
| i2c_use_client(ir->rx->c); |
| if (ir->tx != NULL) |
| i2c_use_client(ir->tx->c); |
| |
| return 0; |
| } |
| |
| static void set_use_dec(void *data) |
| { |
| struct IR *ir = data; |
| |
| if (ir->rx) |
| i2c_release_client(ir->rx->c); |
| if (ir->tx) |
| i2c_release_client(ir->tx->c); |
| if (ir->l.owner != NULL) |
| module_put(ir->l.owner); |
| } |
| |
| /* safe read of a uint32 (always network byte order) */ |
| static int read_uint32(unsigned char **data, |
| unsigned char *endp, unsigned int *val) |
| { |
| if (*data + 4 > endp) |
| return 0; |
| *val = ((*data)[0] << 24) | ((*data)[1] << 16) | |
| ((*data)[2] << 8) | (*data)[3]; |
| *data += 4; |
| return 1; |
| } |
| |
| /* safe read of a uint8 */ |
| static int read_uint8(unsigned char **data, |
| unsigned char *endp, unsigned char *val) |
| { |
| if (*data + 1 > endp) |
| return 0; |
| *val = *((*data)++); |
| return 1; |
| } |
| |
| /* safe skipping of N bytes */ |
| static int skip(unsigned char **data, |
| unsigned char *endp, unsigned int distance) |
| { |
| if (*data + distance > endp) |
| return 0; |
| *data += distance; |
| return 1; |
| } |
| |
| /* decompress key data into the given buffer */ |
| static int get_key_data(unsigned char *buf, |
| unsigned int codeset, unsigned int key) |
| { |
| unsigned char *data, *endp, *diffs, *key_block; |
| unsigned char keys, ndiffs, id; |
| unsigned int base, lim, pos, i; |
| |
| /* Binary search for the codeset */ |
| for (base = 0, lim = tx_data->num_code_sets; lim; lim >>= 1) { |
| pos = base + (lim >> 1); |
| data = tx_data->code_sets[pos]; |
| |
| if (!read_uint32(&data, tx_data->endp, &i)) |
| goto corrupt; |
| |
| if (i == codeset) |
| break; |
| else if (codeset > i) { |
| base = pos + 1; |
| --lim; |
| } |
| } |
| /* Not found? */ |
| if (!lim) |
| return -EPROTO; |
| |
| /* Set end of data block */ |
| endp = pos < tx_data->num_code_sets - 1 ? |
| tx_data->code_sets[pos + 1] : tx_data->endp; |
| |
| /* Read the block header */ |
| if (!read_uint8(&data, endp, &keys) || |
| !read_uint8(&data, endp, &ndiffs) || |
| ndiffs > TX_BLOCK_SIZE || keys == 0) |
| goto corrupt; |
| |
| /* Save diffs & skip */ |
| diffs = data; |
| if (!skip(&data, endp, ndiffs)) |
| goto corrupt; |
| |
| /* Read the id of the first key */ |
| if (!read_uint8(&data, endp, &id)) |
| goto corrupt; |
| |
| /* Unpack the first key's data */ |
| for (i = 0; i < TX_BLOCK_SIZE; ++i) { |
| if (tx_data->fixed[i] == -1) { |
| if (!read_uint8(&data, endp, &buf[i])) |
| goto corrupt; |
| } else { |
| buf[i] = (unsigned char)tx_data->fixed[i]; |
| } |
| } |
| |
| /* Early out key found/not found */ |
| if (key == id) |
| return 0; |
| if (keys == 1) |
| return -EPROTO; |
| |
| /* Sanity check */ |
| key_block = data; |
| if (!skip(&data, endp, (keys - 1) * (ndiffs + 1))) |
| goto corrupt; |
| |
| /* Binary search for the key */ |
| for (base = 0, lim = keys - 1; lim; lim >>= 1) { |
| /* Seek to block */ |
| unsigned char *key_data; |
| pos = base + (lim >> 1); |
| key_data = key_block + (ndiffs + 1) * pos; |
| |
| if (*key_data == key) { |
| /* skip key id */ |
| ++key_data; |
| |
| /* found, so unpack the diffs */ |
| for (i = 0; i < ndiffs; ++i) { |
| unsigned char val; |
| if (!read_uint8(&key_data, endp, &val) || |
| diffs[i] >= TX_BLOCK_SIZE) |
| goto corrupt; |
| buf[diffs[i]] = val; |
| } |
| |
| return 0; |
| } else if (key > *key_data) { |
| base = pos + 1; |
| --lim; |
| } |
| } |
| /* Key not found */ |
| return -EPROTO; |
| |
| corrupt: |
| zilog_error("firmware is corrupt\n"); |
| return -EFAULT; |
| } |
| |
| /* send a block of data to the IR TX device */ |
| static int send_data_block(struct IR_tx *tx, unsigned char *data_block) |
| { |
| int i, j, ret; |
| unsigned char buf[5]; |
| |
| for (i = 0; i < TX_BLOCK_SIZE;) { |
| int tosend = TX_BLOCK_SIZE - i; |
| if (tosend > 4) |
| tosend = 4; |
| buf[0] = (unsigned char)(i + 1); |
| for (j = 0; j < tosend; ++j) |
| buf[1 + j] = data_block[i + j]; |
| dprintk("%02x %02x %02x %02x %02x", |
| buf[0], buf[1], buf[2], buf[3], buf[4]); |
| ret = i2c_master_send(tx->c, buf, tosend + 1); |
| if (ret != tosend + 1) { |
| zilog_error("i2c_master_send failed with %d\n", ret); |
| return ret < 0 ? ret : -EFAULT; |
| } |
| i += tosend; |
| } |
| return 0; |
| } |
| |
| /* send boot data to the IR TX device */ |
| static int send_boot_data(struct IR_tx *tx) |
| { |
| int ret, i; |
| unsigned char buf[4]; |
| |
| /* send the boot block */ |
| ret = send_data_block(tx, tx_data->boot_data); |
| if (ret != 0) |
| return ret; |
| |
| /* Hit the go button to activate the new boot data */ |
| buf[0] = 0x00; |
| buf[1] = 0x20; |
| ret = i2c_master_send(tx->c, buf, 2); |
| if (ret != 2) { |
| zilog_error("i2c_master_send failed with %d\n", ret); |
| return ret < 0 ? ret : -EFAULT; |
| } |
| |
| /* |
| * Wait for zilog to settle after hitting go post boot block upload. |
| * Without this delay, the HD-PVR and HVR-1950 both return an -EIO |
| * upon attempting to get firmware revision, and tx probe thus fails. |
| */ |
| for (i = 0; i < 10; i++) { |
| ret = i2c_master_send(tx->c, buf, 1); |
| if (ret == 1) |
| break; |
| udelay(100); |
| } |
| |
| if (ret != 1) { |
| zilog_error("i2c_master_send failed with %d\n", ret); |
| return ret < 0 ? ret : -EFAULT; |
| } |
| |
| /* Here comes the firmware version... (hopefully) */ |
| ret = i2c_master_recv(tx->c, buf, 4); |
| if (ret != 4) { |
| zilog_error("i2c_master_recv failed with %d\n", ret); |
| return 0; |
| } |
| if ((buf[0] != 0x80) && (buf[0] != 0xa0)) { |
| zilog_error("unexpected IR TX init response: %02x\n", buf[0]); |
| return 0; |
| } |
| zilog_notify("Zilog/Hauppauge IR blaster firmware version " |
| "%d.%d.%d loaded\n", buf[1], buf[2], buf[3]); |
| |
| return 0; |
| } |
| |
| /* unload "firmware", lock held */ |
| static void fw_unload_locked(void) |
| { |
| if (tx_data) { |
| if (tx_data->code_sets) |
| vfree(tx_data->code_sets); |
| |
| if (tx_data->datap) |
| vfree(tx_data->datap); |
| |
| vfree(tx_data); |
| tx_data = NULL; |
| dprintk("successfully unloaded IR blaster firmware\n"); |
| } |
| } |
| |
| /* unload "firmware" for the IR TX device */ |
| static void fw_unload(void) |
| { |
| mutex_lock(&tx_data_lock); |
| fw_unload_locked(); |
| mutex_unlock(&tx_data_lock); |
| } |
| |
| /* load "firmware" for the IR TX device */ |
| static int fw_load(struct IR_tx *tx) |
| { |
| int ret; |
| unsigned int i; |
| unsigned char *data, version, num_global_fixed; |
| const struct firmware *fw_entry; |
| |
| /* Already loaded? */ |
| mutex_lock(&tx_data_lock); |
| if (tx_data) { |
| ret = 0; |
| goto out; |
| } |
| |
| /* Request codeset data file */ |
| ret = request_firmware(&fw_entry, "haup-ir-blaster.bin", &tx->c->dev); |
| if (ret != 0) { |
| zilog_error("firmware haup-ir-blaster.bin not available " |
| "(%d)\n", ret); |
| ret = ret < 0 ? ret : -EFAULT; |
| goto out; |
| } |
| dprintk("firmware of size %zu loaded\n", fw_entry->size); |
| |
| /* Parse the file */ |
| tx_data = vmalloc(sizeof(*tx_data)); |
| if (tx_data == NULL) { |
| zilog_error("out of memory\n"); |
| release_firmware(fw_entry); |
| ret = -ENOMEM; |
| goto out; |
| } |
| tx_data->code_sets = NULL; |
| |
| /* Copy the data so hotplug doesn't get confused and timeout */ |
| tx_data->datap = vmalloc(fw_entry->size); |
| if (tx_data->datap == NULL) { |
| zilog_error("out of memory\n"); |
| release_firmware(fw_entry); |
| vfree(tx_data); |
| ret = -ENOMEM; |
| goto out; |
| } |
| memcpy(tx_data->datap, fw_entry->data, fw_entry->size); |
| tx_data->endp = tx_data->datap + fw_entry->size; |
| release_firmware(fw_entry); fw_entry = NULL; |
| |
| /* Check version */ |
| data = tx_data->datap; |
| if (!read_uint8(&data, tx_data->endp, &version)) |
| goto corrupt; |
| if (version != 1) { |
| zilog_error("unsupported code set file version (%u, expected" |
| "1) -- please upgrade to a newer driver", |
| version); |
| fw_unload_locked(); |
| ret = -EFAULT; |
| goto out; |
| } |
| |
| /* Save boot block for later */ |
| tx_data->boot_data = data; |
| if (!skip(&data, tx_data->endp, TX_BLOCK_SIZE)) |
| goto corrupt; |
| |
| if (!read_uint32(&data, tx_data->endp, |
| &tx_data->num_code_sets)) |
| goto corrupt; |
| |
| dprintk("%u IR blaster codesets loaded\n", tx_data->num_code_sets); |
| |
| tx_data->code_sets = vmalloc( |
| tx_data->num_code_sets * sizeof(char *)); |
| if (tx_data->code_sets == NULL) { |
| fw_unload_locked(); |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| for (i = 0; i < TX_BLOCK_SIZE; ++i) |
| tx_data->fixed[i] = -1; |
| |
| /* Read global fixed data template */ |
| if (!read_uint8(&data, tx_data->endp, &num_global_fixed) || |
| num_global_fixed > TX_BLOCK_SIZE) |
| goto corrupt; |
| for (i = 0; i < num_global_fixed; ++i) { |
| unsigned char pos, val; |
| if (!read_uint8(&data, tx_data->endp, &pos) || |
| !read_uint8(&data, tx_data->endp, &val) || |
| pos >= TX_BLOCK_SIZE) |
| goto corrupt; |
| tx_data->fixed[pos] = (int)val; |
| } |
| |
| /* Filch out the position of each code set */ |
| for (i = 0; i < tx_data->num_code_sets; ++i) { |
| unsigned int id; |
| unsigned char keys; |
| unsigned char ndiffs; |
| |
| /* Save the codeset position */ |
| tx_data->code_sets[i] = data; |
| |
| /* Read header */ |
| if (!read_uint32(&data, tx_data->endp, &id) || |
| !read_uint8(&data, tx_data->endp, &keys) || |
| !read_uint8(&data, tx_data->endp, &ndiffs) || |
| ndiffs > TX_BLOCK_SIZE || keys == 0) |
| goto corrupt; |
| |
| /* skip diff positions */ |
| if (!skip(&data, tx_data->endp, ndiffs)) |
| goto corrupt; |
| |
| /* |
| * After the diffs we have the first key id + data - |
| * global fixed |
| */ |
| if (!skip(&data, tx_data->endp, |
| 1 + TX_BLOCK_SIZE - num_global_fixed)) |
| goto corrupt; |
| |
| /* Then we have keys-1 blocks of key id+diffs */ |
| if (!skip(&data, tx_data->endp, |
| (ndiffs + 1) * (keys - 1))) |
| goto corrupt; |
| } |
| ret = 0; |
| goto out; |
| |
| corrupt: |
| zilog_error("firmware is corrupt\n"); |
| fw_unload_locked(); |
| ret = -EFAULT; |
| |
| out: |
| mutex_unlock(&tx_data_lock); |
| return ret; |
| } |
| |
| /* initialise the IR TX device */ |
| static int tx_init(struct IR_tx *tx) |
| { |
| int ret; |
| |
| /* Load 'firmware' */ |
| ret = fw_load(tx); |
| if (ret != 0) |
| return ret; |
| |
| /* Send boot block */ |
| ret = send_boot_data(tx); |
| if (ret != 0) |
| return ret; |
| tx->need_boot = 0; |
| |
| /* Looks good */ |
| return 0; |
| } |
| |
| /* do nothing stub to make LIRC happy */ |
| static loff_t lseek(struct file *filep, loff_t offset, int orig) |
| { |
| return -ESPIPE; |
| } |
| |
| /* copied from lirc_dev */ |
| static ssize_t read(struct file *filep, char *outbuf, size_t n, loff_t *ppos) |
| { |
| struct IR *ir = filep->private_data; |
| struct IR_rx *rx = ir->rx; |
| int ret = 0, written = 0; |
| DECLARE_WAITQUEUE(wait, current); |
| |
| dprintk("read called\n"); |
| if (rx == NULL) |
| return -ENODEV; |
| |
| if (mutex_lock_interruptible(&rx->buf_lock)) |
| return -ERESTARTSYS; |
| |
| if (n % rx->buf.chunk_size) { |
| dprintk("read result = -EINVAL\n"); |
| mutex_unlock(&rx->buf_lock); |
| return -EINVAL; |
| } |
| |
| /* |
| * we add ourselves to the task queue before buffer check |
| * to avoid losing scan code (in case when queue is awaken somewhere |
| * between while condition checking and scheduling) |
| */ |
| add_wait_queue(&rx->buf.wait_poll, &wait); |
| set_current_state(TASK_INTERRUPTIBLE); |
| |
| /* |
| * while we didn't provide 'length' bytes, device is opened in blocking |
| * mode and 'copy_to_user' is happy, wait for data. |
| */ |
| while (written < n && ret == 0) { |
| if (lirc_buffer_empty(&rx->buf)) { |
| /* |
| * According to the read(2) man page, 'written' can be |
| * returned as less than 'n', instead of blocking |
| * again, returning -EWOULDBLOCK, or returning |
| * -ERESTARTSYS |
| */ |
| if (written) |
| break; |
| if (filep->f_flags & O_NONBLOCK) { |
| ret = -EWOULDBLOCK; |
| break; |
| } |
| if (signal_pending(current)) { |
| ret = -ERESTARTSYS; |
| break; |
| } |
| schedule(); |
| set_current_state(TASK_INTERRUPTIBLE); |
| } else { |
| unsigned char buf[rx->buf.chunk_size]; |
| lirc_buffer_read(&rx->buf, buf); |
| ret = copy_to_user((void *)outbuf+written, buf, |
| rx->buf.chunk_size); |
| written += rx->buf.chunk_size; |
| } |
| } |
| |
| remove_wait_queue(&rx->buf.wait_poll, &wait); |
| set_current_state(TASK_RUNNING); |
| mutex_unlock(&rx->buf_lock); |
| |
| dprintk("read result = %s (%d)\n", |
| ret ? "-EFAULT" : "OK", ret); |
| |
| return ret ? ret : written; |
| } |
| |
| /* send a keypress to the IR TX device */ |
| static int send_code(struct IR_tx *tx, unsigned int code, unsigned int key) |
| { |
| unsigned char data_block[TX_BLOCK_SIZE]; |
| unsigned char buf[2]; |
| int i, ret; |
| |
| /* Get data for the codeset/key */ |
| ret = get_key_data(data_block, code, key); |
| |
| if (ret == -EPROTO) { |
| zilog_error("failed to get data for code %u, key %u -- check " |
| "lircd.conf entries\n", code, key); |
| return ret; |
| } else if (ret != 0) |
| return ret; |
| |
| /* Send the data block */ |
| ret = send_data_block(tx, data_block); |
| if (ret != 0) |
| return ret; |
| |
| /* Send data block length? */ |
| buf[0] = 0x00; |
| buf[1] = 0x40; |
| ret = i2c_master_send(tx->c, buf, 2); |
| if (ret != 2) { |
| zilog_error("i2c_master_send failed with %d\n", ret); |
| return ret < 0 ? ret : -EFAULT; |
| } |
| |
| /* Give the z8 a moment to process data block */ |
| for (i = 0; i < 10; i++) { |
| ret = i2c_master_send(tx->c, buf, 1); |
| if (ret == 1) |
| break; |
| udelay(100); |
| } |
| |
| if (ret != 1) { |
| zilog_error("i2c_master_send failed with %d\n", ret); |
| return ret < 0 ? ret : -EFAULT; |
| } |
| |
| /* Send finished download? */ |
| ret = i2c_master_recv(tx->c, buf, 1); |
| if (ret != 1) { |
| zilog_error("i2c_master_recv failed with %d\n", ret); |
| return ret < 0 ? ret : -EFAULT; |
| } |
| if (buf[0] != 0xA0) { |
| zilog_error("unexpected IR TX response #1: %02x\n", |
| buf[0]); |
| return -EFAULT; |
| } |
| |
| /* Send prepare command? */ |
| buf[0] = 0x00; |
| buf[1] = 0x80; |
| ret = i2c_master_send(tx->c, buf, 2); |
| if (ret != 2) { |
| zilog_error("i2c_master_send failed with %d\n", ret); |
| return ret < 0 ? ret : -EFAULT; |
| } |
| |
| /* |
| * The sleep bits aren't necessary on the HD PVR, and in fact, the |
| * last i2c_master_recv always fails with a -5, so for now, we're |
| * going to skip this whole mess and say we're done on the HD PVR |
| */ |
| if (!tx->post_tx_ready_poll) { |
| dprintk("sent code %u, key %u\n", code, key); |
| return 0; |
| } |
| |
| /* |
| * This bit NAKs until the device is ready, so we retry it |
| * sleeping a bit each time. This seems to be what the windows |
| * driver does, approximately. |
| * Try for up to 1s. |
| */ |
| for (i = 0; i < 20; ++i) { |
| set_current_state(TASK_UNINTERRUPTIBLE); |
| schedule_timeout((50 * HZ + 999) / 1000); |
| ret = i2c_master_send(tx->c, buf, 1); |
| if (ret == 1) |
| break; |
| dprintk("NAK expected: i2c_master_send " |
| "failed with %d (try %d)\n", ret, i+1); |
| } |
| if (ret != 1) { |
| zilog_error("IR TX chip never got ready: last i2c_master_send " |
| "failed with %d\n", ret); |
| return ret < 0 ? ret : -EFAULT; |
| } |
| |
| /* Seems to be an 'ok' response */ |
| i = i2c_master_recv(tx->c, buf, 1); |
| if (i != 1) { |
| zilog_error("i2c_master_recv failed with %d\n", ret); |
| return -EFAULT; |
| } |
| if (buf[0] != 0x80) { |
| zilog_error("unexpected IR TX response #2: %02x\n", buf[0]); |
| return -EFAULT; |
| } |
| |
| /* Oh good, it worked */ |
| dprintk("sent code %u, key %u\n", code, key); |
| return 0; |
| } |
| |
| /* |
| * Write a code to the device. We take in a 32-bit number (an int) and then |
| * decode this to a codeset/key index. The key data is then decompressed and |
| * sent to the device. We have a spin lock as per i2c documentation to prevent |
| * multiple concurrent sends which would probably cause the device to explode. |
| */ |
| static ssize_t write(struct file *filep, const char *buf, size_t n, |
| loff_t *ppos) |
| { |
| struct IR *ir = filep->private_data; |
| struct IR_tx *tx = ir->tx; |
| size_t i; |
| int failures = 0; |
| |
| if (tx == NULL) |
| return -ENODEV; |
| |
| /* Validate user parameters */ |
| if (n % sizeof(int)) |
| return -EINVAL; |
| |
| /* Lock i2c bus for the duration */ |
| mutex_lock(&ir->ir_lock); |
| |
| /* Send each keypress */ |
| for (i = 0; i < n;) { |
| int ret = 0; |
| int command; |
| |
| if (copy_from_user(&command, buf + i, sizeof(command))) { |
| mutex_unlock(&ir->ir_lock); |
| return -EFAULT; |
| } |
| |
| /* Send boot data first if required */ |
| if (tx->need_boot == 1) { |
| ret = send_boot_data(tx); |
| if (ret == 0) |
| tx->need_boot = 0; |
| } |
| |
| /* Send the code */ |
| if (ret == 0) { |
| ret = send_code(tx, (unsigned)command >> 16, |
| (unsigned)command & 0xFFFF); |
| if (ret == -EPROTO) { |
| mutex_unlock(&ir->ir_lock); |
| return ret; |
| } |
| } |
| |
| /* |
| * Hmm, a failure. If we've had a few then give up, otherwise |
| * try a reset |
| */ |
| if (ret != 0) { |
| /* Looks like the chip crashed, reset it */ |
| zilog_error("sending to the IR transmitter chip " |
| "failed, trying reset\n"); |
| |
| if (failures >= 3) { |
| zilog_error("unable to send to the IR chip " |
| "after 3 resets, giving up\n"); |
| mutex_unlock(&ir->ir_lock); |
| return ret; |
| } |
| set_current_state(TASK_UNINTERRUPTIBLE); |
| schedule_timeout((100 * HZ + 999) / 1000); |
| tx->need_boot = 1; |
| ++failures; |
| } else |
| i += sizeof(int); |
| } |
| |
| /* Release i2c bus */ |
| mutex_unlock(&ir->ir_lock); |
| |
| /* All looks good */ |
| return n; |
| } |
| |
| /* copied from lirc_dev */ |
| static unsigned int poll(struct file *filep, poll_table *wait) |
| { |
| struct IR *ir = filep->private_data; |
| struct IR_rx *rx = ir->rx; |
| unsigned int ret; |
| |
| dprintk("poll called\n"); |
| if (rx == NULL) |
| return -ENODEV; |
| |
| mutex_lock(&rx->buf_lock); |
| |
| poll_wait(filep, &rx->buf.wait_poll, wait); |
| |
| dprintk("poll result = %s\n", |
| lirc_buffer_empty(&rx->buf) ? "0" : "POLLIN|POLLRDNORM"); |
| |
| ret = lirc_buffer_empty(&rx->buf) ? 0 : (POLLIN|POLLRDNORM); |
| |
| mutex_unlock(&rx->buf_lock); |
| return ret; |
| } |
| |
| static long ioctl(struct file *filep, unsigned int cmd, unsigned long arg) |
| { |
| struct IR *ir = filep->private_data; |
| int result; |
| unsigned long mode, features = 0; |
| |
| features |= LIRC_CAN_SEND_PULSE; |
| if (ir->rx != NULL) |
| features |= LIRC_CAN_REC_LIRCCODE; |
| |
| switch (cmd) { |
| case LIRC_GET_LENGTH: |
| result = put_user((unsigned long)13, |
| (unsigned long *)arg); |
| break; |
| case LIRC_GET_FEATURES: |
| result = put_user(features, (unsigned long *) arg); |
| break; |
| case LIRC_GET_REC_MODE: |
| if (!(features&LIRC_CAN_REC_MASK)) |
| return -ENOSYS; |
| |
| result = put_user(LIRC_REC2MODE |
| (features&LIRC_CAN_REC_MASK), |
| (unsigned long *)arg); |
| break; |
| case LIRC_SET_REC_MODE: |
| if (!(features&LIRC_CAN_REC_MASK)) |
| return -ENOSYS; |
| |
| result = get_user(mode, (unsigned long *)arg); |
| if (!result && !(LIRC_MODE2REC(mode) & features)) |
| result = -EINVAL; |
| break; |
| case LIRC_GET_SEND_MODE: |
| result = put_user(LIRC_MODE_PULSE, (unsigned long *) arg); |
| break; |
| case LIRC_SET_SEND_MODE: |
| result = get_user(mode, (unsigned long *) arg); |
| if (!result && mode != LIRC_MODE_PULSE) |
| return -EINVAL; |
| break; |
| default: |
| return -EINVAL; |
| } |
| return result; |
| } |
| |
| /* ir_devices_lock must be held */ |
| static struct IR *find_ir_device_by_minor(unsigned int minor) |
| { |
| if (minor >= MAX_IRCTL_DEVICES) |
| return NULL; |
| |
| return ir_devices[minor]; |
| } |
| |
| /* |
| * Open the IR device. Get hold of our IR structure and |
| * stash it in private_data for the file |
| */ |
| static int open(struct inode *node, struct file *filep) |
| { |
| struct IR *ir; |
| int ret; |
| unsigned int minor = MINOR(node->i_rdev); |
| |
| /* find our IR struct */ |
| mutex_lock(&ir_devices_lock); |
| ir = find_ir_device_by_minor(minor); |
| mutex_unlock(&ir_devices_lock); |
| |
| if (ir == NULL) |
| return -ENODEV; |
| |
| /* increment in use count */ |
| mutex_lock(&ir->ir_lock); |
| ++ir->open; |
| ret = set_use_inc(ir); |
| if (ret != 0) { |
| --ir->open; |
| mutex_unlock(&ir->ir_lock); |
| return ret; |
| } |
| mutex_unlock(&ir->ir_lock); |
| |
| /* stash our IR struct */ |
| filep->private_data = ir; |
| |
| return 0; |
| } |
| |
| /* Close the IR device */ |
| static int close(struct inode *node, struct file *filep) |
| { |
| /* find our IR struct */ |
| struct IR *ir = filep->private_data; |
| if (ir == NULL) { |
| zilog_error("close: no private_data attached to the file!\n"); |
| return -ENODEV; |
| } |
| |
| /* decrement in use count */ |
| mutex_lock(&ir->ir_lock); |
| --ir->open; |
| set_use_dec(ir); |
| mutex_unlock(&ir->ir_lock); |
| |
| return 0; |
| } |
| |
| static struct lirc_driver lirc_template = { |
| .name = "lirc_zilog", |
| .set_use_inc = set_use_inc, |
| .set_use_dec = set_use_dec, |
| .owner = THIS_MODULE |
| }; |
| |
| static int ir_remove(struct i2c_client *client); |
| static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id); |
| |
| #define ID_FLAG_TX 0x01 |
| #define ID_FLAG_HDPVR 0x02 |
| |
| static const struct i2c_device_id ir_transceiver_id[] = { |
| { "ir_tx_z8f0811_haup", ID_FLAG_TX }, |
| { "ir_rx_z8f0811_haup", 0 }, |
| { "ir_tx_z8f0811_hdpvr", ID_FLAG_HDPVR | ID_FLAG_TX }, |
| { "ir_rx_z8f0811_hdpvr", ID_FLAG_HDPVR }, |
| { } |
| }; |
| |
| static struct i2c_driver driver = { |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = "Zilog/Hauppauge i2c IR", |
| }, |
| .probe = ir_probe, |
| .remove = ir_remove, |
| .id_table = ir_transceiver_id, |
| }; |
| |
| static const struct file_operations lirc_fops = { |
| .owner = THIS_MODULE, |
| .llseek = lseek, |
| .read = read, |
| .write = write, |
| .poll = poll, |
| .unlocked_ioctl = ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = ioctl, |
| #endif |
| .open = open, |
| .release = close |
| }; |
| |
| static void destroy_rx_kthread(struct IR_rx *rx) |
| { |
| /* end up polling thread */ |
| if (rx != NULL && !IS_ERR_OR_NULL(rx->task)) { |
| kthread_stop(rx->task); |
| rx->task = NULL; |
| } |
| } |
| |
| /* ir_devices_lock must be held */ |
| static int add_ir_device(struct IR *ir) |
| { |
| int i; |
| |
| for (i = 0; i < MAX_IRCTL_DEVICES; i++) |
| if (ir_devices[i] == NULL) { |
| ir_devices[i] = ir; |
| break; |
| } |
| |
| return i == MAX_IRCTL_DEVICES ? -ENOMEM : i; |
| } |
| |
| /* ir_devices_lock must be held */ |
| static void del_ir_device(struct IR *ir) |
| { |
| int i; |
| |
| for (i = 0; i < MAX_IRCTL_DEVICES; i++) |
| if (ir_devices[i] == ir) { |
| ir_devices[i] = NULL; |
| break; |
| } |
| } |
| |
| static int ir_remove(struct i2c_client *client) |
| { |
| struct IR *ir = i2c_get_clientdata(client); |
| |
| mutex_lock(&ir_devices_lock); |
| |
| if (ir == NULL) { |
| /* We destroyed everything when the first client came through */ |
| mutex_unlock(&ir_devices_lock); |
| return 0; |
| } |
| |
| /* Good-bye LIRC */ |
| lirc_unregister_driver(ir->l.minor); |
| |
| /* Good-bye Rx */ |
| destroy_rx_kthread(ir->rx); |
| if (ir->rx != NULL) { |
| if (ir->rx->buf.fifo_initialized) |
| lirc_buffer_free(&ir->rx->buf); |
| i2c_set_clientdata(ir->rx->c, NULL); |
| kfree(ir->rx); |
| } |
| |
| /* Good-bye Tx */ |
| i2c_set_clientdata(ir->tx->c, NULL); |
| kfree(ir->tx); |
| |
| /* Good-bye IR */ |
| del_ir_device(ir); |
| kfree(ir); |
| |
| mutex_unlock(&ir_devices_lock); |
| return 0; |
| } |
| |
| |
| /* ir_devices_lock must be held */ |
| static struct IR *find_ir_device_by_adapter(struct i2c_adapter *adapter) |
| { |
| int i; |
| struct IR *ir = NULL; |
| |
| for (i = 0; i < MAX_IRCTL_DEVICES; i++) |
| if (ir_devices[i] != NULL && |
| ir_devices[i]->adapter == adapter) { |
| ir = ir_devices[i]; |
| break; |
| } |
| |
| return ir; |
| } |
| |
| static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id) |
| { |
| struct IR *ir; |
| struct i2c_adapter *adap = client->adapter; |
| int ret; |
| bool tx_probe = false; |
| |
| dprintk("%s: %s on i2c-%d (%s), client addr=0x%02x\n", |
| __func__, id->name, adap->nr, adap->name, client->addr); |
| |
| /* |
| * The IR receiver is at i2c address 0x71. |
| * The IR transmitter is at i2c address 0x70. |
| */ |
| |
| if (id->driver_data & ID_FLAG_TX) |
| tx_probe = true; |
| else if (tx_only) /* module option */ |
| return -ENXIO; |
| |
| zilog_info("probing IR %s on %s (i2c-%d)\n", |
| tx_probe ? "Tx" : "Rx", adap->name, adap->nr); |
| |
| mutex_lock(&ir_devices_lock); |
| |
| /* Use a single struct IR instance for both the Rx and Tx functions */ |
| ir = find_ir_device_by_adapter(adap); |
| if (ir == NULL) { |
| ir = kzalloc(sizeof(struct IR), GFP_KERNEL); |
| if (ir == NULL) { |
| ret = -ENOMEM; |
| goto out_no_ir; |
| } |
| /* store for use in ir_probe() again, and open() later on */ |
| ret = add_ir_device(ir); |
| if (ret) |
| goto out_free_ir; |
| |
| ir->adapter = adap; |
| mutex_init(&ir->ir_lock); |
| |
| /* set lirc_dev stuff */ |
| memcpy(&ir->l, &lirc_template, sizeof(struct lirc_driver)); |
| ir->l.minor = minor; /* module option */ |
| ir->l.code_length = 13; |
| ir->l.rbuf = NULL; |
| ir->l.fops = &lirc_fops; |
| ir->l.data = ir; |
| ir->l.dev = &adap->dev; |
| ir->l.sample_rate = 0; |
| } |
| |
| if (tx_probe) { |
| /* Set up a struct IR_tx instance */ |
| ir->tx = kzalloc(sizeof(struct IR_tx), GFP_KERNEL); |
| if (ir->tx == NULL) { |
| ret = -ENOMEM; |
| goto out_free_xx; |
| } |
| |
| ir->tx->c = client; |
| ir->tx->need_boot = 1; |
| ir->tx->post_tx_ready_poll = |
| (id->driver_data & ID_FLAG_HDPVR) ? false : true; |
| } else { |
| /* Set up a struct IR_rx instance */ |
| ir->rx = kzalloc(sizeof(struct IR_rx), GFP_KERNEL); |
| if (ir->rx == NULL) { |
| ret = -ENOMEM; |
| goto out_free_xx; |
| } |
| |
| ret = lirc_buffer_init(&ir->rx->buf, 2, BUFLEN / 2); |
| if (ret) |
| goto out_free_xx; |
| |
| mutex_init(&ir->rx->buf_lock); |
| ir->rx->c = client; |
| ir->rx->hdpvr_data_fmt = |
| (id->driver_data & ID_FLAG_HDPVR) ? true : false; |
| |
| /* set lirc_dev stuff */ |
| ir->l.rbuf = &ir->rx->buf; |
| } |
| |
| i2c_set_clientdata(client, ir); |
| |
| /* Proceed only if we have the required Tx and Rx clients ready to go */ |
| if (ir->tx == NULL || |
| (ir->rx == NULL && !tx_only)) { |
| zilog_info("probe of IR %s on %s (i2c-%d) done. Waiting on " |
| "IR %s.\n", tx_probe ? "Tx" : "Rx", adap->name, |
| adap->nr, tx_probe ? "Rx" : "Tx"); |
| goto out_ok; |
| } |
| |
| /* initialise RX device */ |
| if (ir->rx != NULL) { |
| /* try to fire up polling thread */ |
| ir->rx->task = kthread_run(lirc_thread, ir, |
| "zilog-rx-i2c-%d", adap->nr); |
| if (IS_ERR(ir->rx->task)) { |
| ret = PTR_ERR(ir->rx->task); |
| zilog_error("%s: could not start IR Rx polling thread" |
| "\n", __func__); |
| goto out_free_xx; |
| } |
| } |
| |
| /* register with lirc */ |
| ir->l.minor = lirc_register_driver(&ir->l); |
| if (ir->l.minor < 0 || ir->l.minor >= MAX_IRCTL_DEVICES) { |
| zilog_error("%s: \"minor\" must be between 0 and %d (%d)!\n", |
| __func__, MAX_IRCTL_DEVICES-1, ir->l.minor); |
| ret = -EBADRQC; |
| goto out_free_thread; |
| } |
| |
| /* |
| * if we have the tx device, load the 'firmware'. We do this |
| * after registering with lirc as otherwise hotplug seems to take |
| * 10s to create the lirc device. |
| */ |
| ret = tx_init(ir->tx); |
| if (ret != 0) |
| goto out_unregister; |
| |
| zilog_info("probe of IR %s on %s (i2c-%d) done. IR unit ready.\n", |
| tx_probe ? "Tx" : "Rx", adap->name, adap->nr); |
| out_ok: |
| mutex_unlock(&ir_devices_lock); |
| return 0; |
| |
| out_unregister: |
| lirc_unregister_driver(ir->l.minor); |
| out_free_thread: |
| destroy_rx_kthread(ir->rx); |
| out_free_xx: |
| if (ir->rx != NULL) { |
| if (ir->rx->buf.fifo_initialized) |
| lirc_buffer_free(&ir->rx->buf); |
| if (ir->rx->c != NULL) |
| i2c_set_clientdata(ir->rx->c, NULL); |
| kfree(ir->rx); |
| } |
| if (ir->tx != NULL) { |
| if (ir->tx->c != NULL) |
| i2c_set_clientdata(ir->tx->c, NULL); |
| kfree(ir->tx); |
| } |
| out_free_ir: |
| del_ir_device(ir); |
| kfree(ir); |
| out_no_ir: |
| zilog_error("%s: probing IR %s on %s (i2c-%d) failed with %d\n", |
| __func__, tx_probe ? "Tx" : "Rx", adap->name, adap->nr, |
| ret); |
| mutex_unlock(&ir_devices_lock); |
| return ret; |
| } |
| |
| static int __init zilog_init(void) |
| { |
| int ret; |
| |
| zilog_notify("Zilog/Hauppauge IR driver initializing\n"); |
| |
| mutex_init(&tx_data_lock); |
| mutex_init(&ir_devices_lock); |
| |
| request_module("firmware_class"); |
| |
| ret = i2c_add_driver(&driver); |
| if (ret) |
| zilog_error("initialization failed\n"); |
| else |
| zilog_notify("initialization complete\n"); |
| |
| return ret; |
| } |
| |
| static void __exit zilog_exit(void) |
| { |
| i2c_del_driver(&driver); |
| /* if loaded */ |
| fw_unload(); |
| zilog_notify("Zilog/Hauppauge IR driver unloaded\n"); |
| } |
| |
| module_init(zilog_init); |
| module_exit(zilog_exit); |
| |
| MODULE_DESCRIPTION("Zilog/Hauppauge infrared transmitter driver (i2c stack)"); |
| MODULE_AUTHOR("Gerd Knorr, Michal Kochanowicz, Christoph Bartelmus, " |
| "Ulrich Mueller, Stefan Jahn, Jerome Brock, Mark Weaver, " |
| "Andy Walls"); |
| MODULE_LICENSE("GPL"); |
| /* for compat with old name, which isn't all that accurate anymore */ |
| MODULE_ALIAS("lirc_pvr150"); |
| |
| module_param(minor, int, 0444); |
| MODULE_PARM_DESC(minor, "Preferred minor device number"); |
| |
| module_param(debug, bool, 0644); |
| MODULE_PARM_DESC(debug, "Enable debugging messages"); |
| |
| module_param(tx_only, bool, 0644); |
| MODULE_PARM_DESC(tx_only, "Only handle the IR transmit function"); |