/* * tveeprom - eeprom decoder for tvcard configuration eeproms * * Data and decoding routines shamelessly borrowed from bttv-cards.c * eeprom access routine shamelessly borrowed from bttv-if.c * which are: Copyright (C) 1996,97,98 Ralph Metzler (rjkm@thp.uni-koeln.de) & Marcus Metzler (mocm@thp.uni-koeln.de) (c) 1999-2001 Gerd Knorr * Adjustments to fit a more general model and all bugs: Copyright (C) 2003 John Klar * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MODULE_DESCRIPTION("i2c eeprom decoder driver"); MODULE_AUTHOR("John Klar"); MODULE_LICENSE("GPL"); #include #include #include "tuner.h" #ifndef I2C_DRIVERID_TVEEPROM #warning Using temporary hack for missing I2C driver-ID for tveeprom #define I2C_DRIVERID_TVEEPROM I2C_DRIVERID_EXP2 #endif static int debug = 1; MODULE_PARM(debug, "i"); MODULE_PARM_DESC(debug, "Debug level (0-2)"); //static int verbose = 0; MODULE_PARM(verbose, "i"); MODULE_PARM_DESC(verbose, "Verbose level (0-1)"); #define STRM(array,i) (i < sizeof(array)/sizeof(char*) ? array[i] : "unknown") #define dprintk(num, format, args...) \ do { \ if (debug >= num) \ printk(format, ##args); \ } while (0) /* ----------------------------------------------------------------------- */ static unsigned char eeprom_buf[256]; struct tveeprom { u32 has_radio; u32 tuner_type; u32 tuner_formats; u32 digitizer; u32 digitizer_formats; u32 audio_processor; /* a_p_fmts? */ u32 model; u32 revision; u32 serial_number; char rev_str[5]; }; #define I2C_TVEEPROM 0xA0 #define I2C_TVEEPROMA 0xA0 #define I2C_DELAY 10 #define REG_ADDR(x) (((x) << 1) + 1) #define LOBYTE(x) ((unsigned char)((x) & 0xff)) #define HIBYTE(x) ((unsigned char)(((x) >> 8) & 0xff)) #define LOWORD(x) ((unsigned short int)((x) & 0xffff)) #define HIWORD(x) ((unsigned short int)(((x) >> 16) & 0xffff)) /* ----------------------------------------------------------------------- */ /* some hauppauge specific stuff */ static struct HAUPPAUGE_TUNER_FMT { int id; char *name; } hauppauge_tuner_fmt[] __devinitdata = { { 0x00000000, "unknown1" }, { 0x00000000, "unknown2" }, { 0x00000007, "PAL(B/G)" }, { 0x00001000, "NTSC(M)" }, { 0x00000010, "PAL(I)" }, { 0x00400000, "SECAM(L/Lī)" }, { 0x00000e00, "PAL(D/K)" }, { 0x03000000, "ATSC Digital" }, }; static struct HAUPPAUGE_TUNER { int id; char *name; } hauppauge_tuner[] __devinitdata = { { TUNER_ABSENT, "" }, { TUNER_ABSENT, "External" }, { TUNER_ABSENT, "Unspecified" }, { TUNER_PHILIPS_PAL, "Philips FI1216" }, { TUNER_PHILIPS_SECAM, "Philips FI1216MF" }, { TUNER_PHILIPS_NTSC, "Philips FI1236" }, { TUNER_PHILIPS_PAL_I, "Philips FI1246" }, { TUNER_PHILIPS_PAL_DK,"Philips FI1256" }, { TUNER_PHILIPS_PAL, "Philips FI1216 MK2" }, { TUNER_PHILIPS_SECAM, "Philips FI1216MF MK2" }, { TUNER_PHILIPS_NTSC, "Philips FI1236 MK2" }, { TUNER_PHILIPS_PAL_I, "Philips FI1246 MK2" }, { TUNER_PHILIPS_PAL_DK,"Philips FI1256 MK2" }, { TUNER_TEMIC_NTSC, "Temic 4032FY5" }, { TUNER_TEMIC_PAL, "Temic 4002FH5" }, { TUNER_TEMIC_PAL_I, "Temic 4062FY5" }, { TUNER_PHILIPS_PAL, "Philips FR1216 MK2" }, { TUNER_PHILIPS_SECAM, "Philips FR1216MF MK2" }, { TUNER_PHILIPS_NTSC, "Philips FR1236 MK2" }, { TUNER_PHILIPS_PAL_I, "Philips FR1246 MK2" }, { TUNER_PHILIPS_PAL_DK,"Philips FR1256 MK2" }, { TUNER_PHILIPS_PAL, "Philips FM1216" }, { TUNER_PHILIPS_SECAM, "Philips FM1216MF" }, { TUNER_PHILIPS_NTSC, "Philips FM1236" }, { TUNER_PHILIPS_PAL_I, "Philips FM1246" }, { TUNER_PHILIPS_PAL_DK,"Philips FM1256" }, { TUNER_TEMIC_4036FY5_NTSC, "Temic 4036FY5" }, { TUNER_ABSENT, "Samsung TCPN9082D" }, { TUNER_ABSENT, "Samsung TCPM9092P" }, { TUNER_TEMIC_4006FH5_PAL, "Temic 4006FH5" }, { TUNER_ABSENT, "Samsung TCPN9085D" }, { TUNER_ABSENT, "Samsung TCPB9085P" }, { TUNER_ABSENT, "Samsung TCPL9091P" }, { TUNER_TEMIC_4039FR5_NTSC, "Temic 4039FR5" }, { TUNER_PHILIPS_FQ1216ME, "Philips FQ1216 ME" }, { TUNER_TEMIC_4066FY5_PAL_I, "Temic 4066FY5" }, { TUNER_ABSENT, "Philips TD1536" }, { TUNER_ABSENT, "Philips TD1536D" }, { TUNER_PHILIPS_NTSC, "Philips FMR1236" }, /* mono radio */ { TUNER_ABSENT, "Philips FI1256MP" }, { TUNER_ABSENT, "Samsung TCPQ9091P" }, { TUNER_TEMIC_4006FN5_MULTI_PAL, "Temic 4006FN5" }, { TUNER_TEMIC_4009FR5_PAL, "Temic 4009FR5" }, { TUNER_TEMIC_4046FM5, "Temic 4046FM5" }, { TUNER_TEMIC_4009FN5_MULTI_PAL_FM, "Temic 4009FN5" }, { TUNER_ABSENT, "Philips TD1536D_FH_44"}, { TUNER_LG_NTSC_FM, "LG TP18NSR01F"}, { TUNER_LG_PAL_FM, "LG TP18PSB01D"}, { TUNER_LG_PAL, "LG TP18PSB11D"}, { TUNER_LG_PAL_I_FM, "LG TAPC-I001D"}, { TUNER_LG_PAL_I, "LG TAPC-I701D"} }; static char *sndtype[] = { "None", "TEA6300", "TEA6320", "TDA9850", "MSP3400C", "MSP3410D", "MSP3415", "MSP3430", "MSP3438", "CS5331", "MSP3435", "MSP3440", "MSP3445", "MSP3411", "MSP3416", "MSP3425", "Type 0x10","Type 0x11","Type 0x12","Type 0x13", "Type 0x14","Type 0x15","Type 0x16","Type 0x17", "Type 0x18","MSP4418","Type 0x1a","MSP4448", "Type 0x1c","Type 0x1d","Type 0x1e","Type 0x1f", }; static void __devinit hauppauge_eeprom(struct tveeprom *tvee, unsigned char *eeprom_data) { /* ---------------------------------------------- ** The hauppauge eeprom format is tagged ** ** if packet[0] == 0x84, then packet[0..1] == length ** else length = packet[0] & 3f; ** if packet[0] & f8 == f8, then EOD and packet[1] == checksum ** ** In our (ivtv) case we're interested in the following: ** tuner type: tag [00].05 or [0a].01 (index into hauppauge_tuners) ** tuner fmts: tag [00].04 or [0a].00 (bitmask index into hauppauge_fmts) ** radio: tag [00].{last} or [0e].00 (bitmask. bit2=FM) ** audio proc: tag [02].01 or [05].00 (lower nibble indexes lut?) ** Fun info: ** model: tag [00].07-08 or [06].00-01 ** revision: tag [00].09-0b or [06].04-06 ** serial#: tag [01].05-07 or [04].04-06 ** # of inputs/outputs ??? */ int i,j,len,done,tag,tuner=0,t_format=0; char *t_name = NULL, *t_fmt_name = NULL; tvee->revision = done = len = 0; for(i=0; !done && i<256; i+=len) { dprintk(2, KERN_INFO "tvee: processing pos=%02x (%02x,%02x)\n", i,eeprom_data[i],eeprom_data[i+1]); if(eeprom_data[i] == 0x84) { len = eeprom_data[i+1] + (eeprom_data[i+2] << 8); i+=3; } else if((eeprom_data[i] & 0xf0) == 0x70) { if((eeprom_data[i] & 0x08)) { /* verify checksum! */ done = 1; break; } len = eeprom_data[i] & 0x07; ++i; } else { printk(KERN_WARNING "Encountered bad packet header [%02x]. " "Corrupt or not a Hauppauge eeprom.\n",eeprom_data[i]); return; } dprintk(1, KERN_INFO "%3d [%02x] ", len,eeprom_data[i]); for(j=1; jhas_radio = eeprom_data[i+len-1]; tvee->model = eeprom_data[i+8] + (eeprom_data[i+9] << 8); tvee->revision = eeprom_data[i+10] + (eeprom_data[i+11] << 8) + (eeprom_data[i+12] << 16); break; case 0x01: tvee->serial_number = eeprom_data[i+6] + (eeprom_data[i+7] << 8) + (eeprom_data[i+8] << 16); break; case 0x02: tvee->audio_processor = eeprom_data[i+2] & 0x0f; break; case 0x04: tvee->serial_number = eeprom_data[i+5] + (eeprom_data[i+6] << 8) + (eeprom_data[i+7] << 16); break; case 0x05: tvee->audio_processor = eeprom_data[i+1] & 0x0f; break; case 0x06: tvee->model = eeprom_data[i+1] + (eeprom_data[i+2] << 8); tvee->revision = eeprom_data[i+5] + (eeprom_data[i+6] << 8) + (eeprom_data[i+7] << 16); break; case 0x0a: tuner = eeprom_data[i+2]; t_format = eeprom_data[i+1]; break; case 0x0e: tvee->has_radio = eeprom_data[i+1]; break; default: printk(KERN_WARNING "Not sure what to do with tag [%02x]\n",tag); /* dump the rest of the packet? */ } } if(!done) { printk(KERN_WARNING "Ran out of data!\n"); return; } if(tvee->revision != 0) { tvee->rev_str[0] = 32 + ((tvee->revision >> 18) & 0x3f); tvee->rev_str[1] = 32 + ((tvee->revision >> 12) & 0x3f); tvee->rev_str[2] = 32 + ((tvee->revision >> 6) & 0x3f); tvee->rev_str[3] = 32 + ( tvee->revision & 0x3f); tvee->rev_str[4] = 0; } if (tuner < sizeof(hauppauge_tuner)/sizeof(struct HAUPPAUGE_TUNER)) { tvee->tuner_type = hauppauge_tuner[tuner].id; t_name = hauppauge_tuner[tuner].name; } else { t_name = ""; } tvee->tuner_formats = 0; t_fmt_name = ""; for(i=0; i<8; i++) { if( (t_format & (1<tuner_formats |= hauppauge_tuner_fmt[i].id; /* yuck */ t_fmt_name = hauppauge_tuner_fmt[i].name; } } #if 0 if (t_format < sizeof(hauppauge_tuner_fmt)/sizeof(struct HAUPPAUGE_TUNER_FMT)) { tvee->tuner_formats = hauppauge_tuner_fmt[t_format].id; t_fmt_name = hauppauge_tuner_fmt[t_format].name; } else { t_fmt_name = ""; } #endif printk(KERN_INFO "tvee: Hauppauge: model=%d, rev=%s, serial#=%d\n", tvee->model, tvee->rev_str, tvee->serial_number); printk(KERN_INFO "tvee: tuner=%s (idx=%d, type=%d)\n", t_name, tuner, tvee->tuner_type); printk(KERN_INFO "tvee: tuner fmt=%s (eeprom=0x%02x, v4l2=0x%08x)\n", t_fmt_name, t_format, tvee->tuner_formats); printk(KERN_INFO "tvee: audio_processor=%s (type=%d)\n", STRM(sndtype,tvee->audio_processor), tvee->audio_processor); } /* ----------------------------------------------------------------------- */ /* write I2C */ int tvee_I2CWrite(struct i2c_client *client, unsigned char b1, unsigned char b2, int both) { unsigned char buffer[2]; int bytes = both ? 2 : 1; buffer[0] = b1; buffer[1] = b2; if (bytes != i2c_master_send(client, buffer, bytes)) return -1; return 0; } void __devinit tvee_readee(struct i2c_client *client, unsigned char *eedata) { int i; if (tvee_I2CWrite(client, 0, -1, 0)<0) { printk(KERN_WARNING "tvee: readee error\n"); return; } for (i=0; i<256; i+=16) { if (16 != i2c_master_recv(client,eedata+i,16)) { printk(KERN_WARNING "tvee: readee error\n"); break; } } } /* ----------------------------------------------------------------------- */ static int tveeprom_command (struct i2c_client *client, unsigned int cmd, void *arg) { struct tveeprom *eeprom = client->data; u32 *eeprom_props = arg; switch (cmd) { case 0: eeprom_props[0] = eeprom->tuner_type; eeprom_props[1] = eeprom->tuner_formats; eeprom_props[2] = eeprom->model; eeprom_props[3] = eeprom->revision; break; default: return -EINVAL; } return 0; } /* ----------------------------------------------------------------------- */ /* i2c implementation */ #ifndef NEW_I2C /* pre i2c-2.8.0 */ static void tveeprom_inc_use (struct i2c_client *client) { #ifdef MODULE /* MOD_INC_USE_COUNT; */ #endif } static void tveeprom_dec_use (struct i2c_client *client) { #ifdef MODULE /* MOD_DEC_USE_COUNT; */ #endif } #else /* i2c-2.8.0 and later */ #endif /* * Generic i2c probe * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' */ static unsigned short normal_i2c[] = { I2C_TVEEPROM >> 1, I2C_TVEEPROMA >> 1, I2C_CLIENT_END }; static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; I2C_CLIENT_INSMOD; static int tveeprom_i2c_id = 0; struct i2c_driver i2c_driver_tveeprom; static int tveeprom_detect_client (struct i2c_adapter *adapter, int address, unsigned short flags, int kind) { struct i2c_client *client; struct tveeprom *eeprom; dprintk(1, KERN_INFO "tveeprom.c: detecting tveeprom client on address 0x%x\n", address << 1); /* Check if the adapter supports the needed features */ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) return 0; client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); if (client == 0) return -ENOMEM; memset(client, 0, sizeof(struct i2c_client)); client->addr = address; client->adapter = adapter; client->driver = &i2c_driver_tveeprom; client->flags = I2C_CLIENT_ALLOW_USE; client->id = tveeprom_i2c_id++; snprintf(client->name, sizeof(client->name) - 1, "tveeprom[%d]", client->id); client->data = eeprom = kmalloc(sizeof(struct tveeprom), GFP_KERNEL); if (eeprom == NULL) { kfree(client); return -ENOMEM; } memset(eeprom, 0, sizeof(struct tveeprom)); eeprom->tuner_type = -1; eeprom->has_radio = 0; eeprom->model = 0; tvee_readee(client,eeprom_buf); hauppauge_eeprom(eeprom,eeprom_buf); return 0; } static int tveeprom_attach_adapter (struct i2c_adapter *adapter) { if(adapter->id != (I2C_ALGO_BIT | I2C_HW_B_BT848)) return 0; dprintk(1, KERN_INFO "tveeprom.c: starting probe for adapter %s (0x%x)\n", adapter->name, adapter->id); return i2c_probe(adapter, &addr_data, tveeprom_detect_client); } static int tveeprom_detach_client (struct i2c_client *client) { struct tveeprom *eeprom = client->data; int err; err = i2c_detach_client(client); if (err) { return err; } kfree(eeprom); kfree(client); return 0; } /* ----------------------------------------------------------------------- */ /* i2c implementation */ struct i2c_driver i2c_driver_tveeprom = { .name = "tveeprom", .id = I2C_DRIVERID_TVEEPROM, .flags = I2C_DF_NOTIFY, .attach_adapter = tveeprom_attach_adapter, .detach_client = tveeprom_detach_client, .command = tveeprom_command, #ifndef NEW_I2C /* pre i2c-2.8.0 */ .inc_use = tveeprom_inc_use, .dec_use = tveeprom_dec_use, #else /* i2c-2.8.0 and later */ .owner = THIS_MODULE, #endif }; EXPORT_NO_SYMBOLS; static int __init tveeprom_init (void) { return i2c_add_driver(&i2c_driver_tveeprom); } static void __exit tveeprom_exit (void) { i2c_del_driver(&i2c_driver_tveeprom); } module_init(tveeprom_init); module_exit(tveeprom_exit);