Terminator Remote Kernel Patch Fedora HOWTO

From MythTV Official Wiki
Revision as of 06:42, 14 September 2010 by Wagnerrp (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

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;