V4L/DVB (6536): Add a hint for boards without unique USB ID
authorMauro Carvalho Chehab <mchehab@infradead.org>
Sun, 4 Nov 2007 00:20:59 +0000 (21:20 -0300)
committerMauro Carvalho Chehab <mchehab@infradead.org>
Fri, 25 Jan 2008 21:01:55 +0000 (19:01 -0200)
This patch adds a function to allow trying to detect boards that shares
the generic IDs.

The current detection method is based at eeprom checksum.

Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
drivers/media/video/em28xx/em28xx-cards.c
drivers/media/video/em28xx/em28xx-i2c.c
drivers/media/video/em28xx/em28xx-video.c
drivers/media/video/em28xx/em28xx.h

index 05264c6..4cd4941 100644 (file)
 #include "em28xx.h"
 #include "tuner-xc2028.h"
 
+static int tuner = -1;
+module_param(tuner, int, 0444);
+MODULE_PARM_DESC(tuner, "tuner type");
+
+struct em28xx_hash_table {
+       unsigned long hash;
+       unsigned int  model;
+       unsigned int  tuner;
+};
+
 struct em28xx_board em28xx_boards[] = {
        [EM2800_BOARD_UNKNOWN] = {
                .name         = "Unknown EM2800 video grabber",
@@ -342,70 +352,178 @@ struct usb_device_id em28xx_id_table [] = {
        { USB_DEVICE(0x0ccd, 0x0047), .driver_info = EM2880_BOARD_TERRATEC_PRODIGY_XS },
        { },
 };
+MODULE_DEVICE_TABLE (usb, em28xx_id_table);
+
+static struct em28xx_hash_table em28xx_hash [] = {
+       { 0, 0, 0 },
+};
 
+/* Since em28xx_pre_card_setup() requires a proper dev->model,
+ * this won't work for boards with generic PCI IDs
+ */
 void em28xx_pre_card_setup(struct em28xx *dev)
 {
        /* request some modules */
        switch(dev->model){
-               case EM2880_BOARD_TERRATEC_PRODIGY_XS:
-               case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
-               case EM2880_BOARD_TERRATEC_HYBRID_XS:
-                       {
-                               em28xx_write_regs_req(dev, 0x00, 0x08, "\x7d", 1); // reset through GPIO?
-                               break;
-                       }
+       case EM2880_BOARD_TERRATEC_PRODIGY_XS:
+       case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
+       case EM2880_BOARD_TERRATEC_HYBRID_XS:
+               /* reset through GPIO? */
+               em28xx_write_regs_req(dev, 0x00, 0x08, "\x7d", 1);
+               break;
+       }
+}
+
+static int em28xx_tuner_callback(void *ptr, int command, int arg)
+{
+       int rc = 0;
+       struct em28xx *dev = ptr;
+
+       if (dev->tuner_type != TUNER_XC2028)
+               return 0;
+
+       switch (command) {
+       case XC2028_TUNER_RESET:
+               /* FIXME: This is device-dependent */
+               dev->em28xx_write_regs_req(dev, 0x00, 0x48, "\x00", 1);
+               dev->em28xx_write_regs_req(dev, 0x00, 0x12, "\x67", 1);
+
+               msleep(140);
+               break;
        }
+       return rc;
 }
 
 static void em28xx_config_tuner (struct em28xx *dev)
 {
        struct v4l2_priv_tun_config  xc2028_cfg;
        struct xc2028_ctrl           ctl;
+       struct tuner_setup           tun_setup;
+       struct v4l2_frequency        f;
+
+       if (!dev->has_tuner)
+               return;
+
+       tun_setup.mode_mask = T_ANALOG_TV | T_RADIO;
+       tun_setup.type = dev->tuner_type;
+       tun_setup.addr = dev->tuner_addr;
+       tun_setup.tuner_callback = em28xx_tuner_callback;
+
+       em28xx_i2c_call_clients(dev, TUNER_SET_TYPE_ADDR, &tun_setup);
+
+       if (dev->tuner_type == TUNER_XC2028) {
+               memset (&ctl, 0, sizeof(ctl));
+
+               ctl.fname   = XC2028_DEFAULT_FIRMWARE;
+               ctl.max_len = 64;
+
+               xc2028_cfg.tuner = TUNER_XC2028;
+               xc2028_cfg.priv  = &ctl;
 
-       memset (&ctl,0,sizeof(ctl));
+               em28xx_i2c_call_clients(dev, TUNER_SET_CONFIG, &xc2028_cfg);
+       }
+
+       /* configure tuner */
+       f.tuner = 0;
+       f.type = V4L2_TUNER_ANALOG_TV;
+       f.frequency = 9076;     /* just a magic number */
+       dev->ctl_freq = f.frequency;
+       em28xx_i2c_call_clients(dev, VIDIOC_S_FREQUENCY, &f);
+}
+
+static int em28xx_hint_board(struct em28xx *dev)
+{
+       int i;
 
-       ctl.fname   = XC2028_DEFAULT_FIRMWARE;
-       ctl.max_len = 64;
+       for (i = 0; i < ARRAY_SIZE(em28xx_hash); i++) {
+               if (dev->hash == em28xx_hash[i].hash) {
+                       dev->model = em28xx_hash[i].model;
+                       dev->tuner_type = em28xx_hash[i].tuner;
 
-       xc2028_cfg.tuner = TUNER_XC2028;
-       xc2028_cfg.priv  = &ctl;
+                       em28xx_errdev("Your board has no unique USB ID.\n");
+                       em28xx_errdev("A hint were successfully done, "
+                                     "based on eeprom hash.\n");
+                       em28xx_errdev("This method is not 100%% failproof.\n");
+                       em28xx_errdev("If the board were missdetected, "
+                                     "please email this log to:\n");
+                       em28xx_errdev("\tV4L Mailing List "
+                                     " <video4linux-list@redhat.com>\n");
+                       em28xx_errdev("Board detected as %s\n",
+                                     em28xx_boards[dev->model].name);
 
-       em28xx_i2c_call_clients(dev, TUNER_SET_CONFIG, &xc2028_cfg);
+                       return 0;
+               }
+       }
+       em28xx_errdev("Your board has no unique USB ID and thus need a "
+                     "hint to be detected.\n");
+       em28xx_errdev("You may try to use card=<n> insmod option to "
+                     "workaround that.\n");
+       em28xx_errdev("Please send an email with this log to:\n");
+       em28xx_errdev("\tV4L Mailing List <video4linux-list@redhat.com>\n");
+       em28xx_errdev("Board eeprom hash is 0x%08lx\n", dev->hash);
+
+       em28xx_errdev("Here is a list of valid choices for the card=<n>"
+                     " insmod option:\n");
+       for (i = 0; i < em28xx_bcount; i++) {
+               em28xx_errdev("    card=%d -> %s\n",
+                               i, em28xx_boards[i].name);
+       }
+       return -1;
 }
 
 void em28xx_card_setup(struct em28xx *dev)
 {
        /* request some modules */
-       switch(dev->model){
-               case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2:
-                       {
-                               struct tveeprom tv;
+       switch (dev->model) {
+       case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2:
+       {
+               struct tveeprom tv;
 #ifdef CONFIG_MODULES
-                               request_module("tveeprom");
-                               request_module("ir-kbd-i2c");
-                               request_module("msp3400");
+               request_module("tveeprom");
+               request_module("ir-kbd-i2c");
 #endif
-                               /* Call first TVeeprom */
-
-                               dev->i2c_client.addr = 0xa0 >> 1;
-                               tveeprom_hauppauge_analog(&dev->i2c_client, &tv, dev->eedata);
-
-                               dev->tuner_type= tv.tuner_type;
-                               if (tv.audio_processor == AUDIO_CHIP_MSP34XX) {
-                                       dev->i2s_speed=2048000;
-                                       dev->has_msp34xx=1;
-                               } else
-                                       dev->has_msp34xx=0;
-                               break;
-                       }
-               case EM2820_BOARD_KWORLD_PVRTV2800RF:
-                       {
-                               em28xx_write_regs_req(dev,0x00,0x08, "\xf9", 1); // GPIO enables sound on KWORLD PVR TV 2800RF
-                               break;
-                       }
+               /* Call first TVeeprom */
+
+               dev->i2c_client.addr = 0xa0 >> 1;
+               tveeprom_hauppauge_analog(&dev->i2c_client, &tv, dev->eedata);
 
+               dev->tuner_type = tv.tuner_type;
+               if (tv.audio_processor == AUDIO_CHIP_MSP34XX) {
+                       dev->i2s_speed = 2048000;
+                       dev->has_msp34xx = 1;
+               }
+               break;
        }
+       case EM2820_BOARD_KWORLD_PVRTV2800RF:
+               /* GPIO enables sound on KWORLD PVR TV 2800RF */
+               em28xx_write_regs_req(dev, 0x00, 0x08, "\xf9", 1);
+               break;
+       case EM2820_BOARD_UNKNOWN:
+       case EM2800_BOARD_UNKNOWN:
+               em28xx_hint_board(dev);
+       }
+
+       dev->is_em2800 = em28xx_boards[dev->model].is_em2800;
+       dev->has_tuner = em28xx_boards[dev->model].has_tuner;
+       dev->has_msp34xx = em28xx_boards[dev->model].has_msp34xx;
+       dev->tda9887_conf = em28xx_boards[dev->model].tda9887_conf;
+       dev->decoder = em28xx_boards[dev->model].decoder;
+       dev->video_inputs = em28xx_boards[dev->model].vchannels;
+
+       if (tuner >= 0)
+               dev->tuner_type = tuner;
+
+#ifdef CONFIG_MODULES
+       /* request some modules */
+       if (dev->has_msp34xx)
+               request_module("msp3400");
+       if (dev->decoder == EM28XX_SAA7113 || dev->decoder == EM28XX_SAA7114)
+               request_module("saa7115");
+       if (dev->decoder == EM28XX_TVP5150)
+               request_module("tvp5150");
+       if (dev->has_tuner)
+               request_module("tuner");
+#endif
+
        em28xx_config_tuner (dev);
 }
-
-MODULE_DEVICE_TABLE (usb, em28xx_id_table);
index a33878e..e200345 100644 (file)
@@ -292,6 +292,31 @@ static int em28xx_i2c_xfer(struct i2c_adapter *i2c_adap,
        return rc;
 }
 
+/* based on linux/sunrpc/svcauth.h and linux/hash.h
+ * The original hash function returns a different value, if arch is x86_64
+ *  or i386.
+ */
+static inline unsigned long em28xx_hash_mem(char *buf, int length, int bits)
+{
+       unsigned long hash = 0;
+       unsigned long l = 0;
+       int len = 0;
+       unsigned char c;
+       do {
+               if (len == length) {
+                       c = (char)len;
+                       len = -1;
+               } else
+                       c = *buf++;
+               l = (l << 8) | c;
+               len++;
+               if ((len & (32 / 8 - 1)) == 0)
+                       hash = ((hash^l) * 0x9e370001UL);
+       } while (len);
+
+       return (hash >> (32 - bits)) & 0xffffffffUL;
+}
+
 static int em28xx_i2c_eeprom(struct em28xx *dev, unsigned char *eedata, int len)
 {
        unsigned char buf, *p = eedata;
@@ -335,7 +360,11 @@ static int em28xx_i2c_eeprom(struct em28xx *dev, unsigned char *eedata, int len)
                        printk("\n");
        }
 
-       printk(KERN_INFO "EEPROM ID= 0x%08x\n", em_eeprom->id);
+       if (em_eeprom->id == 0x9567eb1a)
+               dev->hash = em28xx_hash_mem(eedata, len, 32);
+
+       printk(KERN_INFO "EEPROM ID= 0x%08x, hash = 0x%08lx\n",
+              em_eeprom->id, dev->hash);
        printk(KERN_INFO "Vendor/Product ID= %04x:%04x\n", em_eeprom->vendor_ID,
               em_eeprom->product_ID);
 
@@ -392,43 +421,6 @@ static u32 functionality(struct i2c_adapter *adap)
 }
 
 
-static int em28xx_tuner_callback(void *ptr, int command, int arg)
-{
-       int rc = 0;
-       struct em28xx *dev = ptr;
-
-       if (dev->tuner_type != TUNER_XC2028)
-               return 0;
-
-       switch (command) {
-       case XC2028_TUNER_RESET:
-               /* FIXME: This is device-dependent */
-               dev->em28xx_write_regs_req(dev, 0x00, 0x48, "\x00", 1);
-               dev->em28xx_write_regs_req(dev, 0x00, 0x12, "\x67", 1);
-
-               msleep(140);
-               break;
-       }
-       return rc;
-}
-
-static int em28xx_set_tuner(int check_eeprom, struct i2c_client *client)
-{
-       struct em28xx *dev = client->adapter->algo_data;
-       struct tuner_setup tun_setup;
-
-       if (dev->has_tuner) {
-               tun_setup.mode_mask = T_ANALOG_TV | T_RADIO;
-               tun_setup.type = dev->tuner_type;
-               tun_setup.addr = dev->tuner_addr;
-               tun_setup.tuner_callback = em28xx_tuner_callback;
-
-               em28xx_i2c_call_clients(dev, TUNER_SET_TYPE_ADDR, &tun_setup);
-       }
-
-       return (0);
-}
-
 /*
  * attach_inform()
  * gets called when a device attaches to the i2c bus
@@ -487,9 +479,11 @@ static int attach_inform(struct i2c_client *client)
                        break;
 
                default:
+                       if (!dev->tuner_addr)
+                               dev->tuner_addr = client->addr;
+
                        dprintk1(1,"attach inform: detected I2C address %x\n", client->addr << 1);
-                       dev->tuner_addr = client->addr;
-                       em28xx_set_tuner(-1, client);
+
        }
 
        return 0;
index fb0fa57..18b8568 100644 (file)
@@ -37,7 +37,6 @@
 #include <linux/mutex.h>
 
 #include "em28xx.h"
-#include <media/tuner.h>
 #include <media/v4l2-common.h>
 #include <media/msp3400.h>
 
@@ -71,10 +70,6 @@ MODULE_PARM_DESC(card,"card type");
 MODULE_PARM_DESC(video_nr,"video device numbers");
 MODULE_PARM_DESC(vbi_nr,"vbi device numbers");
 
-static int tuner = -1;
-module_param(tuner, int, 0444);
-MODULE_PARM_DESC(tuner, "tuner type");
-
 static unsigned int video_debug = 0;
 module_param(video_debug,int,0644);
 MODULE_PARM_DESC(video_debug,"enable debug messages [video]");
@@ -171,7 +166,6 @@ static int em28xx_config(struct em28xx *dev)
  */
 static void em28xx_config_i2c(struct em28xx *dev)
 {
-       struct v4l2_frequency f;
        struct v4l2_routing route;
 
        route.input = INPUT(dev->ctl_input)->vmux;
@@ -179,13 +173,6 @@ static void em28xx_config_i2c(struct em28xx *dev)
        em28xx_i2c_call_clients(dev, VIDIOC_INT_RESET, NULL);
        em28xx_i2c_call_clients(dev, VIDIOC_INT_S_VIDEO_ROUTING, &route);
        em28xx_i2c_call_clients(dev, VIDIOC_STREAMON, NULL);
-
-       /* configure tuner */
-       f.tuner = 0;
-       f.type = V4L2_TUNER_ANALOG_TV;
-       f.frequency = 9076;     /* FIXME:remove magic number */
-       dev->ctl_freq = f.frequency;
-       em28xx_i2c_call_clients(dev, VIDIOC_S_FREQUENCY, &f);
 }
 
 /*
@@ -1495,7 +1482,7 @@ static const struct file_operations em28xx_v4l_fops = {
  * allocates and inits the device structs, registers i2c bus and v4l device
  */
 static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev,
-                          int minor, int model)
+                          int minor)
 {
        struct em28xx *dev = *devhandle;
        int retval = -ENOMEM;
@@ -1503,7 +1490,6 @@ static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev,
        unsigned int maxh, maxw;
 
        dev->udev = udev;
-       dev->model = model;
        mutex_init(&dev->lock);
        init_waitqueue_head(&dev->open);
 
@@ -1512,43 +1498,6 @@ static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev,
        dev->em28xx_read_reg_req_len = em28xx_read_reg_req_len;
        dev->em28xx_write_regs_req = em28xx_write_regs_req;
        dev->em28xx_read_reg_req = em28xx_read_reg_req;
-       dev->is_em2800 = em28xx_boards[model].is_em2800;
-       dev->has_tuner = em28xx_boards[model].has_tuner;
-       dev->has_msp34xx = em28xx_boards[model].has_msp34xx;
-       dev->tda9887_conf = em28xx_boards[model].tda9887_conf;
-       dev->decoder = em28xx_boards[model].decoder;
-
-       if (tuner >= 0)
-               dev->tuner_type = tuner;
-       else
-               dev->tuner_type = em28xx_boards[model].tuner_type;
-
-       dev->video_inputs = em28xx_boards[model].vchannels;
-
-       for (i = 0; i < TVNORMS; i++)
-               if (em28xx_boards[model].norm == tvnorms[i].mode)
-                       break;
-       if (i == TVNORMS)
-               i = 0;
-
-       dev->tvnorm = &tvnorms[i];      /* set default norm */
-
-       em28xx_videodbg("tvnorm=%s\n", dev->tvnorm->name);
-
-       maxw = norm_maxw(dev);
-       maxh = norm_maxh(dev);
-
-       /* set default image size */
-       dev->width = maxw;
-       dev->height = maxh;
-       dev->interlaced = EM28XX_INTERLACED_DEFAULT;
-       dev->field_size = dev->width * dev->height;
-       dev->frame_size =
-           dev->interlaced ? dev->field_size << 1 : dev->field_size;
-       dev->bytesperline = dev->width * 2;
-       dev->hscale = 0;
-       dev->vscale = 0;
-       dev->ctl_input = 2;
 
        /* setup video picture settings for saa7113h */
        memset(&dev->vpic, 0, sizeof(dev->vpic));
@@ -1561,24 +1510,17 @@ static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev,
        dev->vpic.palette = VIDEO_PALETTE_YUV422;
 
        em28xx_pre_card_setup(dev);
-#ifdef CONFIG_MODULES
-       /* request some modules */
-       if (dev->decoder == EM28XX_SAA7113 || dev->decoder == EM28XX_SAA7114)
-               request_module("saa7115");
-       if (dev->decoder == EM28XX_TVP5150)
-               request_module("tvp5150");
-       if (dev->has_tuner)
-               request_module("tuner");
-#endif
+
        errCode = em28xx_config(dev);
        if (errCode) {
                em28xx_errdev("error configuring device\n");
-               em28xx_devused&=~(1<<dev->devno);
+               em28xx_devused &= ~(1<<dev->devno);
                kfree(dev);
                return -ENOMEM;
        }
 
        mutex_lock(&dev->lock);
+
        /* register i2c bus */
        em28xx_i2c_register(dev);
 
@@ -1590,12 +1532,33 @@ static int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev,
 
        mutex_unlock(&dev->lock);
 
+       for (i = 0; i < TVNORMS; i++)
+               if (em28xx_boards[dev->model].norm == tvnorms[i].mode)
+                       break;
+       if (i == TVNORMS)
+               i = 0;
+
+       dev->tvnorm = &tvnorms[i];      /* set default norm */
+
+       em28xx_videodbg("tvnorm=%s\n", dev->tvnorm->name);
+
+       maxw = norm_maxw(dev);
+       maxh = norm_maxh(dev);
+
+       /* set default image size */
+       dev->width = maxw;
+       dev->height = maxh;
+       dev->interlaced = EM28XX_INTERLACED_DEFAULT;
+       dev->field_size = dev->width * dev->height;
+       dev->frame_size =
+           dev->interlaced ? dev->field_size << 1 : dev->field_size;
+       dev->bytesperline = dev->width * 2;
+       dev->hscale = 0;
+       dev->vscale = 0;
+       dev->ctl_input = 2;
+
        errCode = em28xx_config(dev);
 
-#ifdef CONFIG_MODULES
-       if (dev->has_msp34xx)
-               request_module("msp3400");
-#endif
        /* allocate and fill v4l2 device struct */
        dev->vdev = video_device_alloc();
        if (NULL == dev->vdev) {
@@ -1695,7 +1658,7 @@ static int em28xx_usb_probe(struct usb_interface *interface,
        struct usb_interface *uif;
        struct em28xx *dev = NULL;
        int retval = -ENODEV;
-       int model,i,nr,ifnum;
+       int i, nr, ifnum;
 
        udev = usb_get_dev(interface_to_usbdev(interface));
        ifnum = interface->altsetting[0].desc.bInterfaceNumber;
@@ -1735,8 +1698,6 @@ static int em28xx_usb_probe(struct usb_interface *interface,
                return -ENODEV;
        }
 
-       model=id->driver_info;
-
        if (nr >= EM28XX_MAXBOARDS) {
                printk (DRIVER_NAME ": Supports only %i em28xx boards.\n",EM28XX_MAXBOARDS);
                em28xx_devused&=~(1<<nr);
@@ -1752,7 +1713,8 @@ static int em28xx_usb_probe(struct usb_interface *interface,
        }
 
        snprintf(dev->name, 29, "em28xx #%d", nr);
-       dev->devno=nr;
+       dev->devno = nr;
+       dev->model = id->driver_info;
 
        /* compute alternate max packet sizes */
        uif = udev->actconfig->interface[0];
@@ -1779,26 +1741,14 @@ static int em28xx_usb_probe(struct usb_interface *interface,
        }
 
        if ((card[nr]>=0)&&(card[nr]<em28xx_bcount))
-               model=card[nr];
-
-       if ((model==EM2800_BOARD_UNKNOWN)||(model==EM2820_BOARD_UNKNOWN)) {
-               em28xx_errdev("Your board has no unique USB ID and thus can't be autodetected.\n");
-               em28xx_errdev("Please pass card=<n> insmod option to workaround that.\n");
-               em28xx_errdev("If there isn't any card number for you, please send an email to:\n");
-               em28xx_errdev("\tV4L Mailing List <video4linux-list@redhat.com>\n");
-               em28xx_errdev("Here is a list of valid choices for the card=<n> insmod option:\n");
-               for (i = 0; i < em28xx_bcount; i++) {
-                       em28xx_errdev("    card=%d -> %s\n", i,
-                                                       em28xx_boards[i].name);
-               }
-       }
+               dev->model = card[nr];
 
        /* allocate device struct */
-       retval = em28xx_init_dev(&dev, udev, nr, model);
+       retval = em28xx_init_dev(&dev, udev, nr);
        if (retval)
                return retval;
 
-       em28xx_info("Found %s\n", em28xx_boards[model].name);
+       em28xx_info("Found %s\n", em28xx_boards[dev->model].name);
 
        /* save our data pointer in this interface device */
        usb_set_intfdata(interface, dev);
index d8fcc9e..e98dac7 100644 (file)
@@ -257,6 +257,8 @@ struct em28xx {
        int interlaced;         /* 1=interlace fileds, 0=just top fileds */
        int type;
 
+       unsigned long hash;     /* eeprom hash - for boards with generic ID */
+
        /* states */
        enum em28xx_dev_state state;
        enum em28xx_stream_state stream;