/* a module to convert a US NTSC based TiVo to PAL input and output tridge@samba.org, 2000 */ #include #include #include extern int iicWrite(int module, int subaddress, unsigned char *data, int len); extern int iicRead(int module, int subaddress, unsigned char *data, int len); extern int iicReadMsp(int module, int subaddress, int d1, int reg, u32 *v); extern int iicWriteMsp(int module, int subaddress, int d1, int reg, u32 *v); extern int iicWriteNoSubAddr(int module, unsigned char *data, int len); /* useful to know what channel we are on */ static int channel = -1; /* write one byte to a i2c module */ static int iicw1(int module, int subaddress, unsigned char v) { return iicWrite(module, subaddress, &v, 1); } /* read one byte to a i2c module */ static unsigned char iicr1(int module, int subaddress) { unsigned char v; iicRead(module, subaddress, &v, 1); return v; } /* write to a CXD1922 register. The CXD is mapped into virtual memory at 0xfc000000 */ static void CxdWrite(u32 addr, u16 value) { u16 *dest = (u16 *)(0xfc000000 | addr); *dest = value; } /* a hook for the fn in i2c.o that writes to a i2c module that has no subaddess (ie. the tuner) useful for debugging */ static int iicWriteNoSubAddr_x(int module, unsigned char *data, int len) { extern int iicWriteNoSubAddr(int module, unsigned char *data, int len); int ret; ret = iicWriteNoSubAddr(module, data, 4); return ret; } /* a hook for i2c read fn in i2c.o */ static int iicRead_x(int module, int subaddress, unsigned char *data, int len) { int ret; ret = iicRead(module, subaddress, data, len); #if DEBUG printk("iicRead(0x%x, 0x%x, %p, %d) -> %d\n", module, subaddress, data, len, ret); #endif return ret; } /* a hook for the basic i2c write fn */ static int iicWrite_x(int module, int subaddress, unsigned char *data, int len) { int ret; /* i2c bus 0x88 holds the MSP audio chip */ if (module == 0x88) { u16 *v = (u16 *)data; printk("iicWriteMsp(0x%x, 0x%x, 0x%x)\n", subaddress, v[0], v[1]); /* if we are on a broadcast channel then we need to force the audio to come from the right pin */ if (channel >= 0 && v[0] == 0x13) { /* force the tuner audio - attached to scart1 */ v[1] = 0; } } /* if we are on the tuner then force the tuner input in the 7114 */ if (module == 0x42 && subaddress == 2 && channel >= 0) { return iicw1(0x42, 0x2, 0x80); } ret = iicWrite(module, subaddress, data, len); #if DEBUG printk("iicWrite(0x%x, 0x%x, %p, %d) -> %d\n", module, subaddress, data, len, ret); #endif return ret; } /* a hook for the lowtune fn - not currently used as we fake up composite/line input rather than using the fpga7114 tuner code */ static int lowtune_x(u32 a1, u32 a2, u32 a3) { extern int lowtune(u32 a1, u32 a2, u32 a3); int ret; ret = lowtune(a1, a2, a3); // printk("lowtune(%x, %x, %x) -> %d COULDNT_TUNE=%x\n", // a1, a2, a3, ret, COULDNT_TUNE); return ret; } /* unused hook */ static int check_video_lock_x(u32 a1, u32 a2, u32 a3) { extern int check_video_lock(u32 a1, u32 a2, u32 a3); int ret; ret = check_video_lock(a1, a2, a3); // printk("check_video_lock(%x, %x, %x) -> %d\n", // a1, a2, a3, ret); return ret; } static int standin_tune_x(u32 a1, u32 a2, u32 a3) { extern int standin_tune(u32 a1, u32 a2, u32 a3); int ret; // printk("channel=%d\n", *((u32 *)(a1+16))); *((int *)(a1+16)) = -1; ret = standin_tune(a1, a2, a3); // printk("standin_tune(%x, %x, %x) -> %d\n", // a1, *(u32 *)a2, a3, ret); return ret; } static void delay(int n) { extern volatile u32 jiffies; u32 x = n + jiffies*4; while (jiffies < x) schedule(); } /* fine-tuning routine */ static void fine_tune(u32 *v0) { u16 buf[2]; u16 buf0[2]; int i; u16 min, max; buf0[0] = ((u16 *)v0)[0]; buf0[1] = ((u16 *)v0)[1]; buf[1] = buf0[1]; /* scan until we get a good lock */ for (i=0; i<0x2; i++) { buf[0] = buf0[0] + i*8; printk("trying %x\n", (u32)buf[0]); iicWriteNoSubAddr(0xc2, buf, 4); delay(1); if (iicr1(0x42, 0x1f) == 0x81) break; buf[0] = buf0[0] - i*8; printk("trying %x\n", (u32)buf[0]); iicWriteNoSubAddr(0xc2, buf, 4); delay(1); if (iicr1(0x42, 0x1f) == 0x81) break; } if (iicr1(0x42, 0x1f) != 0x81) { printk("fine_tune failed to lock!\n"); return; } buf0[0] = buf[0]; /* now find the range that the channel covers */ min = max = buf0[0]; for (i=0; i<0x2; i++) { buf[0] = buf0[0] - i*8; printk("trying2 %x\n", (u32)buf[0]); iicWriteNoSubAddr(0xc2, buf, 4); delay(1); if (iicr1(0x42, 0x1f) != 0x81) break; min = buf[0]; } for (i=0; i<0x2; i++) { buf[0] = buf0[0] + i*8; printk("trying2 %x\n", (u32)buf[0]); iicWriteNoSubAddr(0xc2, buf, 4); delay(1); if (iicr1(0x42, 0x1f) != 0x81) break; max = buf[0]; } buf0[0] = (min+max)/2; iicWriteNoSubAddr(0xc2, buf0, 4); printk("fine_tune: tuned to 0x%08x (was 0x%08x)\n", *(u32 *)buf0, *v0); *v0 = *(u32 *)buf0; } static int fpga_hw_ioctl_x(u32 a1, u32 a2, u32 a3, u32 a4) { extern int fpga_hw_ioctl(u32 a1, u32 a2, u32 a3, u32 a4); int ret; if (a3 == 0x8000) { u32 *p = (u32 *)a4; channel = p[1]; printk("8000: %x %x %x %x\n", p[0], p[1], p[2], p[3]); printk("channel=%d\n", channel); p[1] = ~0; p[3] = 0x220; } ret = fpga_hw_ioctl(a1, a2, a3, a4); if (channel >= 0) { int i; struct { int chan; u32 v; } chans[] = { { 7, 0x1fde8e30 }, /* Test */ { 9, 0x30de8e30 }, /* Test */ { 21, 0x1fde8e30 }, { 27, 0x22de8e30 }, { 29, 0x23de8e30 }, { 28, 0x2b5e8e30 }, /* puesto para probar C+ en ch 28 test */ { 31, 0x24de8e30 }, { 34, 0x265e8e30 }, { 37, 0x27de8e30 }, { 39, 0x28de8e30 }, { 44, 0x2b5e8e30 }, { 48, 0x2d5e8e30 }, { 52, 0x2f5e8e30 }, { 55, 0x30de8e30 }, { -1, 0}}; for (i=0; chans[i].chan != -1; i++) { if (chans[i].chan == channel) break; } if (chans[i].chan != -1) { u32 mspv = 0x00130000; iicw1(0x42, 0x2, 0x80); iicWrite(0x88, 0x12, &mspv, 4); printk("tuned to 0x%x\n", chans[i].v); iicWriteNoSubAddr(0xc2, &chans[i].v, 4); // fine_tune(&chans[i].v); } } //printk("fpga_hw_ioctl(%x, %x, %x, %x) -> %d\n", // a1, a2, a3, a4, ret); return ret; } static void cache_flush(u32 addr) { __asm ("dcbf 0,%0\n\t" "sync \n\t" "icbi 0,%0 \n\t" "isync \n\t" : : "r" (addr)); } static void patch_cs22(int pal) { extern void cs22_init(void); extern void cs22NTSCConfig(void); u16 *p = (u16 *)(((u32)cs22NTSCConfig) + (0xd6e - 0xd38)); *p = pal?8192:0; cache_flush((u32)p); } static void patch_i2c(u8 v) { extern void acquire_iic(void); extern int getFreq(int, int, u32 *, u32 *); extern int lowtune(void); extern int check_video_lock(void); u8 *p; p = (u8 *)(((u32)acquire_iic) + (0x77 - 0x40)); *p = v; cache_flush((u32)p); p = (u8 *) (((u32)getFreq) + (0x4e8b - 0x4b78)); *p = v; cache_flush((u32)p); //p = (u8 *) (((u32)check_video_lock) + (0x6030 - 0x5b94)); //p[0] = 0x48; //p[1] = 0x0; //cache_flush((u32)p); //p2 = (u32 *) (((u32)lowtune) + (0x5084 - 0x5064)); //*p2 = 0x38600000; //cache_flush((u32)p2); // use_tuner(); } static void video_setup(void) { int pal=1; static int last_pal = 0; /* auto-detect PAL or NTSC */ if ((iicr1(0x42, 0x1f) & 0x60) == 0x20) { pal = 0; } patch_cs22(pal); patch_i2c(0xc2); if (last_pal != pal) { cs22_init(); } last_pal = pal; if (pal) { CxdWrite(0x1e0, 1); // CxdWrite(0x204, 1); CxdWrite(0x222, -16); CxdWrite(0x224, 2); } else { CxdWrite(0x1e0, 0); // CxdWrite(0x204, 0); CxdWrite(0x222, 0); CxdWrite(0x224, 0); } if (pal) { iicw1(0x42, 0x08, 0x98); iicw1(0x42, 0x03, 0x33); iicw1(0x42, 0x04, 0x49); iicw1(0x42, 0x05, 0x49); iicw1(0x42, 0x09, 0x00); iicw1(0x42, 0x0e, 0x04); iicw1(0x42, 0x0f, 0x00); iicw1(0x42, 0x86, 0x40); iicw1(0x42, 0x9a, 0x22); iicw1(0x42, 0x9b, 0x1); } else { iicw1(0x42, 0x08, 0xc8); iicw1(0x42, 0x03, 0x23); iicw1(0x42, 0x09, 0x40); iicw1(0x42, 0x0e, 0x1); iicw1(0x42, 0x86, 0xc0); iicw1(0x42, 0x9a, 0xf2); iicw1(0x42, 0x9b, 0x0); } if (pal) { iicw1(0x8c, 0x61, 0x82); iicw1(0x8c, 0x63, 0xcb); iicw1(0x8c, 0x64, 0x8a); iicw1(0x8c, 0x65, 0x09); iicw1(0x8c, 0x66, 0x2a); iicw1(0x8c, 0x6e, 0x80); iicw1(0x8c, 0x7a, 0x14); iicw1(0x8c, 0x7b, 0x35); } else { iicw1(0x8c, 0x61, 0x80); iicw1(0x8c, 0x63, 0x1f); iicw1(0x8c, 0x64, 0x7c); iicw1(0x8c, 0x65, 0xf0); iicw1(0x8c, 0x66, 0x21); iicw1(0x8c, 0x6e, 0x80); iicw1(0x8c, 0x7a, 0x14); iicw1(0x8c, 0x7b, 0x35); } printk("palmod: video setup for %s\n", pal?"PAL":"NTSC"); } static int BangRegs_x(int a1, int a2, int a3) { int ret; extern int MsTestAndResetBits(void); int (*BangRegs)(int,int,int) = (int (*)(int,int,int))((((u32)MsTestAndResetBits)-0x1bd4)+0x1bfc); printk("palmod: BangRegs(%x, %x, %x)\n", a1, a2, a3); ret = BangRegs(a1, a2, a3); video_setup(); return ret; } static int getFreq_x(int chan, int u1, u32 *p1, u32 *p2) { extern int getFreq(int, int, u32 *, u32 *); unsigned char v; u32 f; int i; struct { int chan; u32 freq; } chans[] = { { 0, 46250 }, { 1, 57250 }, { 2, 64250 }, { 3, 86250 }, { 4, 95250 }, { 5, 102250 }, { 6, 175250 }, { 7, 182250 }, { 8, 189250 }, { 9, 196250 }, { 10, 209250 }, { 11, 216250 }, { 28, 527250 }, { 29, 534250 }, { 30, 541250 }, { 31, 548250 }, { 32, 555250 }, { 33, 562250 }, { 34, 569250 }, { 35, 576250 }, { 36, 591250 }, { 39, 604250 }, { 40, 611250 }, { 41, 618250 }, { 42, 625250 }, { 43, 632250 }, { 44, 639250 }, { 45, 646250 }, { 46, 653250 }, { 47, 660250 }, { 48, 667250 }, { 49, 674250 }, { 50, 681250 }, { 51, 688250 }, { 52, 695250 }, { 53, 702250 }, { 54, 709250 }, { 55, 716250 }, { 56, 723250 }, { 57, 730250 }, { 58, 737250 }, { 59, 744250 }, { 60, 751250 }, { 61, 758250 }, { 62, 765250 }, { 63, 772250 }, { 64, 779250 }, { 65, 786250 }, { 66, 793250 }, { 67, 800250 }, { 68, 807250 }, { 69, 814250 }, {0,0} }; // printk("getFreq chan=%d\n", chan); for (i=0;chans[i].freq;i++) { if (chans[i].chan == chan) break; } if (chans[i].chan == 0) { f = 0; } else { f = (chans[i].freq+45750)*1000; } iicRead(0x42, 0x1f, &v, 1); *p1 = f; if (chans[i].freq > 385250) { *p2 = 2; } else if (chans[i].freq > 137250) { *p2 = 1; } else { *p2 = 0; } v = -1; return 0; } static void install_bypass(u32 base, u32 newfn, u32 call) { u32 old; call += base; old = *(u32 *)call; *(u32 *)call = (0x48000001) | ((newfn - call)&0xFFFFFF); cache_flush(call); printk("palmod: patched at 0x%x with 0x%x (was 0x%x)\n", call, *(u32 *)call, old); } static void install_bypass_fn(u32 newfn, u32 call) { extern int MsTestAndResetBits(void); install_bypass(((u32)MsTestAndResetBits)-0x1bd4, newfn, call); } int init_module(void) { install_bypass_fn((u32)BangRegs_x, 0x2a58); install_bypass_fn((u32)BangRegs_x, 0x3390); install_bypass_fn((u32)getFreq_x, 0x4de0); install_bypass_fn((u32)iicWriteNoSubAddr_x, 0x4ea4); install_bypass_fn((u32)iicRead_x, 0x3d34); install_bypass_fn((u32)iicRead_x, 0x3e38); install_bypass_fn((u32)iicRead_x, 0x3ec4); install_bypass_fn((u32)iicRead_x, 0x3fac); { u32 addrs[] = { 0x3468, 0x357c, 0x3590, 0x35c4, 0x35d8, 0x3600, 0x3668, 0x36ec, 0x3704, 0x3718, 0x379c, 0x37b4, 0x37c8, 0x384c, 0x3864, 0x3878, 0x390c, 0x394c, 0x3a54, 0x3a98, 0x3ae0, 0x3b18, 0x3bd0, 0x3c14, 0x3c70, 0x3e58, 0x3e78, 0x3ee4, 0x3fd0, 0x4018, 0x4098, 0x40c8, 0x40f4, 0x4184, 0x41a0, 0x4230, 0x4260, 0x428c, 0x42fc, 0x432c, 0x4358, 0x43a4, 0x43d4, 0}; int i; for (i=0; addrs[i]; i++) { install_bypass_fn((u32)iicWrite_x, addrs[i]); } } install_bypass_fn((u32)check_video_lock_x, 0x1380); install_bypass_fn((u32)standin_tune_x, 0x1590); install_bypass_fn((u32)standin_tune_x, 0x15b4); install_bypass_fn((u32)fpga_hw_ioctl_x, 0x4c); { u32 addrs[] = { 0x5fb0, 0}; int i; for (i=0; addrs[i]; i++) { install_bypass_fn((u32)lowtune_x, addrs[i]); } } video_setup(); printk("palmod bypass installed\n"); return 0; } void cleanup_module(void) { printk("palmod bypass removed!\n"); }