Hi, This is a prof-of-concept port of a lirc infrared remote driver to the 2.6 kernel input layer. I picked lirc_i2c.c, simply because I have hardware to play with for that one. For now it works with Hauppauge WinTV cards only. The driver just puts the keys from the infrared remote into the normal keyboard queue, the lircd event dispatcher daemon is bypassed. Usage is very simple: Compile the driver, load the module, press keys on the remote and see them arrive in your shell. The number keys of the remote send KEY_KP<n> events, so your shell will interpret them either as digits or as cursor keys depending on numlock state. The X-Server seems not to see the other keys, try "showkeys" for them. Seems some more work is needed to make that working fully, i.e. "VOL+" arrives as "XF86AudioRaiseVolume"[1] key in X11 apps. Not sure whenever it is a good idea to put the IR keys into the normal keyboard queue. That means that the application with the keyboard focus will also receive the IR input. Depending on how you are using lirc you might call this a bug or a feature. IMHO it is less confusing for the users if keyboard focus and IR focus are identical. But there might be situations where it is useful that they are independant ... Comments? Flames? Gerd PS: I'm not in the lirc list, so please Cc: me on replies. [1] no joke, multimedia keyboards have those ... =========================[ ir-kbd-i2c.c ]========================= /* * keyboard input driver for i2c IR remote controls * * Copyright (c) 2000-2003 Gerd Knorr <kraxel@xxxxxxxxxxx> * modified for PixelView (BT878P+W/FM) by * Michal Kochanowicz <mkochano@xxxxxxxxxx> * Christoph Bartelmus <lirc@xxxxxxxxxxxx> * modified for KNC ONE TV Station/Anubis Typhoon TView Tuner by * Ulrich Mueller <ulrich.mueller42@xxxxxx> * * 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/init.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/string.h> #include <linux/timer.h> #include <linux/delay.h> #include <linux/errno.h> #include <linux/slab.h> #include <linux/i2c.h> #include <linux/workqueue.h> #include <linux/input.h> #include <asm/semaphore.h> struct IR; struct IR { struct i2c_client c; struct input_dev dev; struct work_struct work; struct timer_list timer; /* config */ int (*get_key)(struct IR*, unsigned char*); int *keycodes; /* internal state */ int keypressed; unsigned char key; }; /* ----------------------------------------------------------------------- */ /* insmod parameters */ static int debug = 1; /* debug output */ MODULE_PARM(debug,"i"); /* * If this key changes, a new key was pressed. */ #define REPEAT_TOGGLE_0 192 #define REPEAT_TOGGLE_1 224 #define DEVNAME "ir-kbd-i2c" #define dprintk(level, fmt, arg...) if (debug >= level) \ printk(KERN_DEBUG DEVNAME ": " fmt, ## arg) /* ----------------------------------------------------------------------- */ static int keycodes_haup[256] = { [ 0x00 ] = KEY_KP0, [ 0x01 ] = KEY_KP1, [ 0x02 ] = KEY_KP2, [ 0x03 ] = KEY_KP3, [ 0x04 ] = KEY_KP4, [ 0x05 ] = KEY_KP5, [ 0x06 ] = KEY_KP6, [ 0x07 ] = KEY_KP7, [ 0x08 ] = KEY_KP8, [ 0x09 ] = KEY_KP9, [ 0x0c ] = KEY_RADIO, // radio [ 0x0d ] = KEY_MUTE, // mute [ 0x0f ] = KEY_TV, // tv [ 0x10 ] = KEY_VOLUMEUP, // VOL+ [ 0x11 ] = KEY_VOLUMEDOWN, // VOL- [ 0x20 ] = KEY_CHANNELUP, // CH+ [ 0x21 ] = KEY_CHANNELDOWN, // CH- [ 0x22 ] = KEY_CHANNEL, // source #if 0 [ 0x1e ] = KEY_FIXME, // reserved [ 0x26 ] = KEY_FIXME, // minimize [ 0x2e ] = KEY_FIXME, // fullscreen #endif }; /* ----------------------------------------------------------------------- */ static inline int reverse(int data, int bits) { int i,c; for (c=0,i=0; i<bits; i++) { c |= (((data & (1<<i)) ? 1:0)) << (bits-1-i); } return c; } static int get_key_haup(struct IR *ir, unsigned char* key) { unsigned char buf[3]; /* poll IR chip */ if (3 != i2c_master_recv(&ir->c,buf,3)) return -EIO; dprintk(2,"ir data 0x%02x 0x%02x 0x%02x\n", buf[0], buf[1], buf[2]); /* key pressed ? */ if (buf[0] != REPEAT_TOGGLE_0 && buf[0] != REPEAT_TOGGLE_1) return 0; /* return it */ key[0] = buf[1] >> 2; return 1; } static int get_key_pixelview(struct IR *ir, unsigned char* key) { unsigned char b; /* poll IR chip */ if (1 != i2c_master_recv(&ir->c,&b,1)) { dprintk(1,"read error\n"); return -EIO; } dprintk(2,"key %02x\n", b); key[0] = b; return 1; } static int get_key_pv951(struct IR *ir, unsigned char* key) { unsigned char b; /* poll IR chip */ if (1 != i2c_master_recv(&ir->c,&b,1)) { dprintk(1,"read error\n"); return -EIO; } /* ignore 0xaa */ if (b==0xaa) return 0; dprintk(2,"key %02x\n", b); key[0] = b; return 1; } static int get_key_knc1(struct IR *ir, unsigned char *key) { unsigned char b; /* poll IR chip */ if (1 != i2c_master_recv(&ir->c,&b,1)) { dprintk(1,"read error\n"); return -EIO; } /* it seems that 0xFE indicates that a button is still hold down, while 0xFF indicates that no button is hold down. 0xFE sequences are sometimes interrupted by 0xFF */ dprintk(2,"key %02x\n", b); if(b == 0xFF) return 0; if (b == 0xFE) b = ir->key; key[0] = b; return 1; } /* ----------------------------------------------------------------------- */ static void ir_key_event(struct IR *ir) { int keycode = 0; if (ir->keycodes && ir->keycodes[ir->key]) keycode = ir->keycodes[ir->key]; if (0 == keycode) { dprintk(1,"unknown key: 0x%02x [down=%d]\n", ir->key,ir->keypressed); return; } dprintk(1,"key event code=%d down=%d\n", keycode,ir->keypressed); input_report_key(&ir->dev,keycode,ir->keypressed); input_sync(&ir->dev); } static void ir_key_poll(struct IR *ir) { unsigned char key; int rc; dprintk(2,"ir_poll_key\n"); rc = ir->get_key(ir,&key); if (rc < 0) { dprintk(2,"error\n"); return; } if (0 == rc && ir->keypressed) { ir->keypressed = 0; ir_key_event(ir); } if (rc > 0) { if (ir->keypressed && ir->key != key) { ir->keypressed = 0; ir_key_event(ir); } if (!ir->keypressed) { ir->key = key; ir->keypressed = 1; ir_key_event(ir); } } } static void ir_timer(unsigned long data) { struct IR *ir = (struct IR*)data; schedule_work(&ir->work); } static void ir_work(void *data) { struct IR *ir = data; ir_key_poll(ir); mod_timer(&ir->timer, jiffies+HZ/10); } /* ----------------------------------------------------------------------- */ static int ir_attach(struct i2c_adapter *adap, int addr, unsigned short flags, int kind); static int ir_detach(struct i2c_client *client); static int ir_probe(struct i2c_adapter *adap); static struct i2c_driver driver = { .name = "ir remote kbd driver", .id = I2C_DRIVERID_EXP3, /* FIXME */ .flags = I2C_DF_NOTIFY, .attach_adapter = ir_probe, .detach_client = ir_detach, }; static struct i2c_client client_template = { I2C_DEVNAME("unset"), .driver = &driver }; static int ir_attach(struct i2c_adapter *adap, int addr, unsigned short flags, int kind) { struct IR *ir; int i; if (NULL == (ir = kmalloc(sizeof(struct IR),GFP_KERNEL))) return -ENOMEM; memset(ir,0,sizeof(*ir)); ir->c = client_template; i2c_set_clientdata(&ir->c, ir); ir->c.adapter = adap; ir->c.addr = addr; switch(addr) { case 0x64: strlcpy(ir->c.dev.name,"Pixelview IR",sizeof(ir->c.dev.name)); ir->get_key=get_key_pixelview; //ir->keycodes = keycodes_FIXME; break; case 0x4b: strlcpy(ir->c.dev.name,"PV951 IR",sizeof(ir->c.dev.name)); ir->get_key=get_key_pv951; //ir->keycodes = keycodes_FIXME; break; case 0x18: case 0x1a: strlcpy(ir->c.dev.name,"Hauppauge IR",sizeof(ir->c.dev.name)); ir->get_key=get_key_haup; ir->keycodes = keycodes_haup; break; case 0x30: strlcpy(ir->c.dev.name,"KNC ONE IR",sizeof(ir->c.dev.name)); ir->get_key=get_key_knc1; //ir->keycodes = keycodes_FIXME; break; default: /* shouldn't happen */ printk(DEVNAME ": Huh? unknown i2c address (0x%02x)?\n",addr); kfree(ir); return -1; } printk(DEVNAME ": chip found @ 0x%02x (%s)\n",addr,ir->c.dev.name); /* register i2c device */ i2c_attach_client(&ir->c); /* register input device */ init_input_dev(&ir->dev); ir->dev.name = ir->c.dev.name; ir->dev.evbit[0] = BIT(EV_KEY) | BIT(EV_REP); if (ir->keycodes) { for (i = 0; i < 255; i++) set_bit(ir->keycodes[i], ir->dev.keybit); clear_bit(0, ir->dev.keybit); } input_register_device(&ir->dev); /* start polling via eventd */ INIT_WORK(&ir->work, ir_work, ir); init_timer(&ir->timer); ir->timer.function = ir_timer; ir->timer.data = (unsigned long)ir; schedule_work(&ir->work); return 0; } static int ir_detach(struct i2c_client *client) { struct IR *ir = i2c_get_clientdata(client); /* kill outstanding polls */ del_timer(&ir->timer); flush_scheduled_work(); /* unregister devices */ input_unregister_device(&ir->dev); i2c_detach_client(&ir->c); /* free memory */ kfree(ir); return 0; } static int ir_probe(struct i2c_adapter *adap) { /* The external IR receiver is at i2c address 0x34 (0x35 for reads). Future Hauppauge cards will have an internal receiver at 0x30 (0x31 for reads). In theory, both can be fitted, and Hauppauge suggest an external overrides an internal. That's why we probe 0x1a (~0x34) first. CB */ static const int probe[] = { 0x1a, 0x18, 0x4b, 0x64, 0x30, -1}; struct i2c_client c; char buf; int i,rc; if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848)) { memset(&c,0,sizeof(c)); c.adapter = adap; for (i = 0; -1 != probe[i]; i++) { c.addr = probe[i]; rc = i2c_master_recv(&c,&buf,1); dprintk(1,"probe 0x%02x @ %s: %s\n", probe[i], adap->dev.name, (1 == rc) ? "yes" : "no"); if (1 == rc) { ir_attach(adap,probe[i],0,0); break; } } } return 0; } /* ----------------------------------------------------------------------- */ MODULE_AUTHOR("Gerd Knorr, Michal Kochanowicz, Christoph Bartelmus, Ulrich Mueller"); MODULE_DESCRIPTION("keyboard input driver for i2c IR remote controls"); MODULE_LICENSE("GPL"); static int ir_init(void) { i2c_add_driver(&driver); return 0; } static void ir_fini(void) { i2c_del_driver(&driver); } module_init(ir_init); module_exit(ir_fini); /* * Overrides for Emacs so that we follow Linus's tabbing style. * --------------------------------------------------------------------------- * Local variables: * c-basic-offset: 8 * End: */