Difference between revisions of "Terminator Remote Kernel Patch Fedora HOWTO"
m (added to Fedora and HOWTO categories) |
m (added to Patches category) |
||
Line 475: | Line 475: | ||
[[Category:Fedora]] | [[Category:Fedora]] | ||
[[Category:HOWTO]] | [[Category:HOWTO]] | ||
+ | [[Category:Patches]] |
Revision as of 18:56, 9 March 2008
Introduction
The remote for the KWorld Global TV Terminator doesn't work with stock kernels. The knowledge on how to make it work has existed for quite awhile starting with a patch done by Henry Wong. Unfortunately the V4L folks have never been able to approve changes. I believe some major redesign is needed before a patch will be accepted. So we will likely have to keep applying patches for quite awhile.
Disclaimer 1: This worked for me. There may be a better way. Your experience may vary.
Disclaimer 2: I do all this as root. That's not required and may not be safe if you are not careful. If you like you can always run as a normal user and use sudo when required.
Patching Kernel
Use the following commands to get the get the kernel sources:
# yum -y install yum-utils rpmdevtools sparse # yumdownloader --source kernel
For this to work we need a fairly recent kernel. If it fails, update to the latest kernel, re-boot, and try again.
We should now have a kernel source rpm in your directory. For this description we will assume it is kernel-2.6.22.9-91.fc7.src.rpm. Install the source:
rpm -ivh kernel-2.6.22.9-91.fc7.src.rpm
Ignore the 'user does not exist' diagnostics.
rpmbuild -bp --target=$(uname -m) /usr/src/redhat/SPECS/kernel-2.6.spec
Our sources are now ready to build in /usr/src/redhat/BUILD/kernel-2.6.22/linux-2.6.22.i686. The exact directory will vary depending on our specific kernel. Go into this directory and build the kernel:
# cd /usr/src/redhat/BUILD/kernel-2.6.22/linux-2.6.22.i686 # make -j2
If the kernel doesn't build you need to figure out why before going any further. Once we can successfully build the kernel copy the patch below to a file "patch.txt" and apply:
# cd /usr/src/redhat/BUILD/kernel-2.6.22/linux-2.6.22.i686/drivers/media/video # patch -p0 < patch.txt
Unless you happen to have exactly the kernel I used you will see some rejects. These are parts of the patch that cannot be automatically applied since the source has changed. You can look at the .rej files and try to add them in the correct spot. You will need to resolve all the rejects.
This patch adds a new entry to the card array. In my kernel there are already 115 cards so this new card is #116. Your kernel may have a different number of cards defined. You will need to change this line:
#define SAA7134_BOARD_KWORLD_GLOBAL_TV_TERMINATOR 116
Now we need to rebuild:
# cd /usr/src/redhat/BUILD/kernel-2.6.22/linux-2.6.22.i686 # make -j2
We should see it rebuild the saa7134 and ir-kbd-i2c modules:
LD [M] drivers/media/video/ir-kbd-i2c.ko LD [M] drivers/media/video/saa7134/saa7134-alsa.ko LD [M] drivers/media/video/saa7134/saa7134-dvb.ko LD [M] drivers/media/video/saa7134/saa7134-empress.ko LD [M] drivers/media/video/saa7134/saa7134.ko
At this point we need to install the new modules. We could probably just do "make install" in the kernel directory but that would update everything and we only need these few modules. First make a copy of each:
# cd /lib/modules/2.6.22.9-91.fc7/kernel/drivers/media/video # cp ir-kbd-i2c.ko ir-kbd-i2c.ko.bak # cd sa7134 # cp saa7134-alsa.ko saa7134-alsa.ko.bak # cp saa7134-dvb.ko saa7134-dvb.ko.bak # cp saa7134-empress.ko saa7134-empress.ko.bak # cp saa7134.ko saa7134.ko.bak
And then copy the new modules in their place:
# cd /lib/modules/2.6.22.9-91.fc7/kernel/drivers/media/video # cp /usr/src/redhat/BUILD/kernel-2.6.22/linux-2.6.22.i686/drivers/media/video/ir-kbd-i2c.ko . # cd saa7134 # cp /usr/src/redhat/BUILD/kernel-2.6.22/linux-2.6.22.i686/drivers/media/video/saa7134/saa7134-alsa.ko . # cp /usr/src/redhat/BUILD/kernel-2.6.22/linux-2.6.22.i686/drivers/media/video/saa7134/saa7134-dvb.ko . # cp /usr/src/redhat/BUILD/kernel-2.6.22/linux-2.6.22.i686/drivers/media/video/saa7134/saa7134-empress.ko . # cp /usr/src/redhat/BUILD/kernel-2.6.22/linux2.6.22.i686/drivers/media/video/saa7134/saa7134.ko .
So now you have patched modules installed.
Remember we are going to use card 116 now? We need to modify our /etc/modprobe.conf to specify this. Here is mine:
# kworld global tv terminator alias char-major-81-0 saa7134 options saa7134 card=116 alias snd-card-1 saa7134-alsa options snd-card-1 index=1 install saa7134 /sbin/modprobe --ignore-install saa7134; /sbin/modprobe saa7134-alsa remove saa7134-alsa { /usr/sbin/alsactl store 0 >/dev/null 2>&1 || : ; }; /sbin/modprobe -r --ignore-remove saa7134-alsa
We can test and make sure everything else still works. If not you can always use the .bak files to revert.
The fun is still not over since we still need a bit of work to get LIRC setup for the remote.
Setup LIRC
The remote works with the linux input layer. Get the lircd.conf from bytesex.org:
# cd /etc # wget http://linux.bytesex.org/v4l2/linux-input-layer-lircd.conf -O lircd.conf
Now you need to determine the correct options for lircd. Look in /proc/bus/input/devices. You should see a line such as:
I: Bus=0018 Vendor=0000 Product=0000 Version=0000 N: Name="Global TV Terminato" P: Phys=i2c-1/1-0030/ir0 S: Sysfs=/class/input/input2 U: Uniq= H: Handlers=kbd event2 B: EV=100003 B: KEY=50c0006 110000 0 0 0 0 200c000 80 c0000803 1e0000 4000 0 ffc
We need to tell lirc to use this device. Assuming you have a relatively new version of lirc you can use the name. Add the following to /etc/sysconfig/lirc:
LIRCD_OPTIONS="-H dev/input -d name=\"Global TV Terminato\""
If specifying by name doesn't work you could also use -d /dev/input/event2 but this can be a problem as it isn't guaranteed it will be event2 on your next boot. You can deal with that with a udev rule but that is added hassle. You can use -d /dev/input/remote with a rule like this:
# In /etc/udev/rules.d/60-tremote.rules KERNEL=="event*", SYSFS{name}=="Global TV Terminato", SYMLINK+="input/remote"
Now restart lirc and test with irw by pressing a few keys on the remote:
# service lirc restart # irw 0000000000010192 00 CHANNELUP linux-input-layer 0000000000010193 00 CHANNELDOWN linux-input-layer 0000000000010002 00 1 linux-input-layer 0000000000010003 00 2 linux-input-layer 00000000000100ce 00 CLOSE linux-input-layer
Now you just need to create a .lircrc for mythtv. Here are a few lines of mine as an example:
begin prog = mythtv button = CHANNELUP config = Up end begin prog = mythtv button = CHANNELDOWN config = Down end
Kernel Patch
diff -r -c /root/rpmbuild/BUILD/kernel-2.6.22.orig/linux-2.6.22.i686/drivers/media/video/ir-kbd-i2c.c ./ir-kbd-i2c.c *** /root/rpmbuild/BUILD/kernel-2.6.22.orig/linux-2.6.22.i686/drivers/media/video/ir-kbd-i2c.c 2007-10-17 21:19:58.000000000 -0700 --- ./ir-kbd-i2c.c 2007-10-17 21:57:31.000000000 -0700 *************** *** 58,63 **** --- 58,65 ---- #define dprintk(level, fmt, arg...) if (debug >= level) \ printk(KERN_DEBUG DEVNAME ": " fmt , ## arg) + static int polling_interval = 100; /* ms */ + /* ----------------------------------------------------------------------- */ static int get_key_haup(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) *************** *** 271,277 **** { struct IR_i2c *ir = container_of(work, struct IR_i2c, work); ir_key_poll(ir); ! mod_timer(&ir->timer, jiffies+HZ/10); } /* ----------------------------------------------------------------------- */ --- 273,279 ---- { struct IR_i2c *ir = container_of(work, struct IR_i2c, work); ir_key_poll(ir); ! mod_timer(&ir->timer, jiffies+polling_interval*HZ/1000); } /* ----------------------------------------------------------------------- */ *************** *** 347,352 **** --- 349,359 ---- break; case 0x30: name = "KNC One"; + if (ir->c.adapter->id == I2C_HW_SAA7134) { + /* Handled by saa7134-input */ + polling_interval = 50; /* ms */ + name = "SAA713x remote"; + } ir->get_key = get_key_knc1; ir_type = IR_TYPE_OTHER; ir_codes = ir_codes_empty; *************** *** 448,454 **** */ static const int probe_bttv[] = { 0x1a, 0x18, 0x4b, 0x64, 0x30, -1}; ! static const int probe_saa7134[] = { 0x7a, 0x47, 0x71, -1 }; static const int probe_em28XX[] = { 0x30, 0x47, -1 }; const int *probe = NULL; struct i2c_client c; --- 455,461 ---- */ static const int probe_bttv[] = { 0x1a, 0x18, 0x4b, 0x64, 0x30, -1}; ! static const int probe_saa7134[] = { 0x7a, 0x47, 0x30, 0x71, -1 }; static const int probe_em28XX[] = { 0x30, 0x47, -1 }; const int *probe = NULL; struct i2c_client c; *************** *** 476,482 **** c.adapter = adap; for (i = 0; -1 != probe[i]; i++) { c.addr = probe[i]; ! rc = i2c_master_recv(&c,&buf,0); dprintk(1,"probe 0x%02x @ %s: %s\n", probe[i], adap->name, (0 == rc) ? "yes" : "no"); --- 483,521 ---- c.adapter = adap; for (i = 0; -1 != probe[i]; i++) { c.addr = probe[i]; ! ! /* Special case for KS003 controllers */ ! if (c.adapter->id == I2C_HW_SAA7134 && probe[i] == 0x30) ! { ! struct i2c_client c2; ! int j; ! ! memset (&c2, 0, sizeof(c2)); ! c2.adapter = c.adapter; ! ! /* KS003 controller doesn't seem to ! respond to probes unless we read something from an ! existing device. Weird... */ ! ! /* Find a device that responds. Can we save this ! somewhere so we don't have to repeat this loop? */ ! for (j=127; j >= 0; j--) ! { ! c2.addr = j; ! if (0 == i2c_master_recv(&c2,&buf,0)) ! break; ! } ! ! /* Now do the probe. The controller does not respond ! to 0-byte reads, so we use a 1-byte read instead. */ ! rc = i2c_master_recv(&c,&buf,1); ! rc--; ! } ! else ! { ! rc = i2c_master_recv(&c,&buf,0); ! } ! dprintk(1,"probe 0x%02x @ %s: %s\n", probe[i], adap->name, (0 == rc) ? "yes" : "no"); diff -r -c /root/rpmbuild/BUILD/kernel-2.6.22.orig/linux-2.6.22.i686/drivers/media/video/saa7134/saa7134-cards.c ./saa7134/saa7134-cards.c *** /root/rpmbuild/BUILD/kernel-2.6.22.orig/linux-2.6.22.i686/drivers/media/video/saa7134/saa7134-cards.c 2007-10-17 21:19:58.000000000 -0700 --- ./saa7134/saa7134-cards.c 2007-10-20 16:16:22.000000000 -0700 *************** *** 3502,3507 **** --- 3502,3539 ---- .amux = TV, }, }, + [SAA7134_BOARD_KWORLD_GLOBAL_TV_TERMINATOR] = { + /* Kworld Global TV Terminator */ + .name = "Global TV Terminator", + .audio_clock = 0x00187de7, + .tuner_type = TUNER_PHILIPS_TDA8290, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .gpiomask = 1 << 21, + .inputs = {{ + .name = name_tv, + .vmux = 1, + .amux = TV, + .gpio = 0x0000000, + .tv = 1, + },{ + .name = name_comp1, /* Composite input */ + .vmux = 3, + .amux = LINE2, + .gpio = 0x0000000, + },{ + .name = name_svideo, /* S-Video input */ + .vmux = 8, + .amux = LINE2, + .gpio = 0x0000000, + }}, + .radio = { + .name = name_radio, + .amux = TV, + .gpio = 0x0200000, + }, + }, }; const unsigned int saa7134_bcount = ARRAY_SIZE(saa7134_boards); *************** *** 4389,4394 **** --- 4421,4427 ---- case SAA7134_BOARD_PINNACLE_PCTV_110i: case SAA7134_BOARD_PINNACLE_PCTV_310i: case SAA7134_BOARD_UPMOST_PURPLE_TV: + case SAA7134_BOARD_KWORLD_GLOBAL_TV_TERMINATOR: case SAA7134_BOARD_HAUPPAUGE_HVR1110: dev->has_remote = SAA7134_REMOTE_I2C; break; diff -r -c /root/rpmbuild/BUILD/kernel-2.6.22.orig/linux-2.6.22.i686/drivers/media/video/saa7134/saa7134.h ./saa7134/saa7134.h *** /root/rpmbuild/BUILD/kernel-2.6.22.orig/linux-2.6.22.i686/drivers/media/video/saa7134/saa7134.h 2007-10-17 21:19:58.000000000 -0700 --- ./saa7134/saa7134.h 2007-10-17 22:05:56.000000000 -0700 *************** *** 238,243 **** --- 238,244 ---- #define SAA7134_BOARD_ECS_TVP3XP_4CB6 113 #define SAA7134_BOARD_KWORLD_DVBT_210 114 #define SAA7134_BOARD_SABRENT_TV_PCB05 115 + #define SAA7134_BOARD_KWORLD_GLOBAL_TV_TERMINATOR 116 #define SAA7134_MAXBOARDS 8 #define SAA7134_INPUT_MAX 8 diff -r -c /root/rpmbuild/BUILD/kernel-2.6.22.orig/linux-2.6.22.i686/drivers/media/video/saa7134/saa7134-i2c.c ./saa7134/saa7134-i2c.c *** /root/rpmbuild/BUILD/kernel-2.6.22.orig/linux-2.6.22.i686/drivers/media/video/saa7134/saa7134-i2c.c 2007-10-17 21:19:58.000000000 -0700 --- ./saa7134/saa7134-i2c.c 2007-10-17 22:07:00.000000000 -0700 *************** *** 341,346 **** --- 341,347 ---- switch (client->addr) { case 0x7a: case 0x47: + case 0x30: case 0x71: { struct IR_i2c *ir = i2c_get_clientdata(client); diff -r -c /root/rpmbuild/BUILD/kernel-2.6.22.orig/linux-2.6.22.i686/drivers/media/video/saa7134/saa7134-input.c ./saa7134/saa7134-input.c *** /root/rpmbuild/BUILD/kernel-2.6.22.orig/linux-2.6.22.i686/drivers/media/video/saa7134/saa7134-input.c 2007-10-17 21:19:58.000000000 -0700 --- ./saa7134/saa7134-input.c 2007-10-17 22:08:19.000000000 -0700 *************** *** 94,101 **** --- 94,177 ---- return 0; } + + static IR_KEYTAB_TYPE ir_codes_kworld_global_tv_terminator[IR_KEYTAB_SIZE] = { + [ 0x1e ] = KEY_POWER, + [ 0x07 ] = KEY_TUNER, /* Source */ + [ 0x1c ] = KEY_SEARCH, /* Scan */ + [ 0x18 ] = KEY_MUTE, + [ 0x03 ] = KEY_RADIO, /* TV/FM */ + [ 0x01 ] = KEY_1, + [ 0x0b ] = KEY_2, + [ 0x1b ] = KEY_3, + [ 0x05 ] = KEY_4, + [ 0x09 ] = KEY_5, + [ 0x15 ] = KEY_6, + [ 0x06 ] = KEY_7, + [ 0x0a ] = KEY_8, + [ 0x12 ] = KEY_9, + [ 0x0c ] = KEY_FORWARD, + [ 0x02 ] = KEY_0, + [ 0x10 ] = KEY_KPPLUS, + [ 0x13 ] = KEY_AGAIN, /* Recall */ + [ 0x04 ] = KEY_BACK, + [ 0x00 ] = KEY_RECORD, + [ 0x08 ] = KEY_STOP, + [ 0x11 ] = KEY_PLAY, + [ 0x0f ] = KEY_CLOSE, /* Minimize */ + [ 0x19 ] = KEY_ZOOM, + [ 0x14 ] = KEY_VOLUMEDOWN, + [ 0x16 ] = KEY_VOLUMEUP, + [ 0x17 ] = KEY_CHANNELDOWN, + [ 0x1f ] = KEY_CHANNELUP, + [ 0x1a ] = KEY_SHUFFLE, /* Snapshot */ + [ 0x0d ] = KEY_LANGUAGE, /* MTS */ + [ 0x0e ] = KEY_MENU, /* Function */ + [ 0x1d ] = KEY_RESTART /* Reset */ + }; + /* --------------------- Chip specific I2C key builders ----------------- */ + static int get_key_kworld_global_tv_terminator(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) + { + unsigned char b; + int rc; + int gpio; + + /* We need this to access GPIO. Used by the saa_readl macro. */ + struct saa7134_dev *dev = ir->c.adapter->algo_data; + if (dev == NULL) { + dprintk ("ir->c.adapter->algo_data is NULL!\n"); + return -EIO; + } + + /* rising SAA7134_GPIO_GPRESCAN reads the status */ + saa_clearb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN); + saa_setb(SAA7134_GPIO_GPMODE3,SAA7134_GPIO_GPRESCAN); + + gpio = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2); + + /* GPIO&0x40 is pulsed low when a button is pressed. Don't do + I2C receive if gpio&0x40 is not low. */ + if (gpio & 0x40) + return 0; /* No button press */ + + /* GPIO says there is a button press. Get it. */ + if (1 != (rc=i2c_master_recv(&ir->c,&b,1))) { + dprintk("read error %d\n", rc); + return -EIO; + } + + /* No button press */ + if (b == 0xFF) + return 0; + + /* Button pressed */ + *ir_key = b; + *ir_raw = b; + return 1; + } + static int get_key_purpletv(struct IR_i2c *ir, u32 *ir_key, u32 *ir_raw) { unsigned char b; *************** *** 435,440 **** --- 511,522 ---- ir->get_key = get_key_hvr1110; ir->ir_codes = ir_codes_hauppauge_new; break; + case SAA7134_BOARD_KWORLD_GLOBAL_TV_TERMINATOR: + snprintf(ir->c.name, sizeof(ir->c.name), "Global TV Terminator"); + ir->get_key = get_key_kworld_global_tv_terminator; + ir->ir_codes = ir_codes_kworld_global_tv_terminator; + break; + default: dprintk("Shouldn't get here: Unknown board %x for I2C IR?\n",dev->board); break;