V4L/DVB (9979): em28xx: move usb probe code to a proper place
[firefly-linux-kernel-4.4.55.git] / drivers / media / video / em28xx / em28xx-cards.c
index e5d9424d03f9b1150fde4a11ea015c67b034fd4f..42978f9592b77ef57bc4ef053f0e65849ddc09f7 100644 (file)
@@ -37,6 +37,8 @@
 
 #include "em28xx.h"
 
+#define DRIVER_NAME         "em28xx"
+
 static int tuner = -1;
 module_param(tuner, int, 0444);
 MODULE_PARM_DESC(tuner, "tuner type");
@@ -45,6 +47,13 @@ static unsigned int disable_ir;
 module_param(disable_ir, int, 0444);
 MODULE_PARM_DESC(disable_ir, "disable infrared remote support");
 
+static unsigned int card[]     = {[0 ... (EM28XX_MAXBOARDS - 1)] = UNSET };
+module_param_array(card,  int, NULL, 0444);
+MODULE_PARM_DESC(card,     "card type");
+
+/* Bitmask marking allocated devices from 0 to EM28XX_MAXBOARDS */
+static unsigned long em28xx_devused;
+
 struct em28xx_hash_table {
        unsigned long hash;
        unsigned int  model;
@@ -1801,3 +1810,424 @@ void em28xx_card_setup(struct em28xx *dev)
        em28xx_ir_init(dev);
 }
 
+
+#if defined(CONFIG_MODULES) && defined(MODULE)
+static void request_module_async(struct work_struct *work)
+{
+       struct em28xx *dev = container_of(work,
+                            struct em28xx, request_module_wk);
+
+       if (dev->has_audio_class)
+               request_module("snd-usb-audio");
+       else if (dev->has_alsa_audio)
+               request_module("em28xx-alsa");
+
+       if (dev->board.has_dvb)
+               request_module("em28xx-dvb");
+}
+
+static void request_modules(struct em28xx *dev)
+{
+       INIT_WORK(&dev->request_module_wk, request_module_async);
+       schedule_work(&dev->request_module_wk);
+}
+#else
+#define request_modules(dev)
+#endif /* CONFIG_MODULES */
+
+/*
+ * em28xx_realease_resources()
+ * unregisters the v4l2,i2c and usb devices
+ * called when the device gets disconected or at module unload
+*/
+void em28xx_release_resources(struct em28xx *dev)
+{
+       if (dev->sbutton_input_dev)
+               em28xx_deregister_snapshot_button(dev);
+
+       if (dev->ir)
+               em28xx_ir_fini(dev);
+
+       /*FIXME: I2C IR should be disconnected */
+
+       em28xx_release_analog_resources(dev);
+
+       em28xx_remove_from_devlist(dev);
+
+       em28xx_i2c_unregister(dev);
+       usb_put_dev(dev->udev);
+
+       /* Mark device as unused */
+       em28xx_devused &= ~(1 << dev->devno);
+};
+
+/*
+ * em28xx_init_dev()
+ * allocates and inits the device structs, registers i2c bus and v4l device
+ */
+int em28xx_init_dev(struct em28xx **devhandle, struct usb_device *udev,
+                          int minor)
+{
+       struct em28xx *dev = *devhandle;
+       int retval = -ENOMEM;
+       int errCode;
+
+       dev->udev = udev;
+       mutex_init(&dev->ctrl_urb_lock);
+       spin_lock_init(&dev->slock);
+       init_waitqueue_head(&dev->open);
+       init_waitqueue_head(&dev->wait_frame);
+       init_waitqueue_head(&dev->wait_stream);
+
+       dev->em28xx_write_regs = em28xx_write_regs;
+       dev->em28xx_read_reg = em28xx_read_reg;
+       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->board.is_em2800 = em28xx_boards[dev->model].is_em2800;
+
+       em28xx_pre_card_setup(dev);
+
+       if (!dev->board.is_em2800) {
+               /* Sets I2C speed to 100 KHz */
+               retval = em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, 0x40);
+               if (retval < 0) {
+                       em28xx_errdev("%s: em28xx_write_regs_req failed!"
+                                     " retval [%d]\n",
+                                     __func__, retval);
+                       return retval;
+               }
+       }
+
+       /* register i2c bus */
+       errCode = em28xx_i2c_register(dev);
+       if (errCode < 0) {
+               em28xx_errdev("%s: em28xx_i2c_register - errCode [%d]!\n",
+                       __func__, errCode);
+               return errCode;
+       }
+
+       /* Do board specific init and eeprom reading */
+       em28xx_card_setup(dev);
+
+       /* Configure audio */
+       errCode = em28xx_audio_setup(dev);
+       if (errCode < 0) {
+               em28xx_errdev("%s: Error while setting audio - errCode [%d]!\n",
+                       __func__, errCode);
+       }
+
+       /* wake i2c devices */
+       em28xx_wake_i2c(dev);
+
+       /* init video dma queues */
+       INIT_LIST_HEAD(&dev->vidq.active);
+       INIT_LIST_HEAD(&dev->vidq.queued);
+
+
+       if (dev->board.has_msp34xx) {
+               /* Send a reset to other chips via gpio */
+               errCode = em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xf7);
+               if (errCode < 0) {
+                       em28xx_errdev("%s: em28xx_write_regs_req - "
+                                     "msp34xx(1) failed! errCode [%d]\n",
+                                     __func__, errCode);
+                       return errCode;
+               }
+               msleep(3);
+
+               errCode = em28xx_write_reg(dev, EM28XX_R08_GPIO, 0xff);
+               if (errCode < 0) {
+                       em28xx_errdev("%s: em28xx_write_regs_req - "
+                                     "msp34xx(2) failed! errCode [%d]\n",
+                                     __func__, errCode);
+                       return errCode;
+               }
+               msleep(3);
+       }
+
+       em28xx_add_into_devlist(dev);
+
+       errCode = em28xx_analog_config(dev);
+       if (errCode) {
+               em28xx_errdev("error configuring device\n");
+               return -ENOMEM;
+       }
+
+       retval = em28xx_register_analog_devices(dev);
+       if (retval < 0) {
+               em28xx_release_resources(dev);
+               goto fail_reg_devices;
+       }
+
+       em28xx_init_extension(dev);
+
+       /* Save some power by putting tuner to sleep */
+       em28xx_i2c_call_clients(dev, TUNER_SET_STANDBY, NULL);
+
+       return 0;
+
+fail_reg_devices:
+       return retval;
+}
+
+/*
+ * em28xx_usb_probe()
+ * checks for supported devices
+ */
+static int em28xx_usb_probe(struct usb_interface *interface,
+                           const struct usb_device_id *id)
+{
+       const struct usb_endpoint_descriptor *endpoint;
+       struct usb_device *udev;
+       struct usb_interface *uif;
+       struct em28xx *dev = NULL;
+       int retval = -ENODEV;
+       int i, nr, ifnum, isoc_pipe;
+       char *speed;
+       char descr[255] = "";
+
+       udev = usb_get_dev(interface_to_usbdev(interface));
+       ifnum = interface->altsetting[0].desc.bInterfaceNumber;
+
+       /* Check to see next free device and mark as used */
+       nr = find_first_zero_bit(&em28xx_devused, EM28XX_MAXBOARDS);
+       em28xx_devused |= 1<<nr;
+
+       /* Don't register audio interfaces */
+       if (interface->altsetting[0].desc.bInterfaceClass == USB_CLASS_AUDIO) {
+               em28xx_err(DRIVER_NAME " audio device (%04x:%04x): "
+                       "interface %i, class %i\n",
+                       le16_to_cpu(udev->descriptor.idVendor),
+                       le16_to_cpu(udev->descriptor.idProduct),
+                       ifnum,
+                       interface->altsetting[0].desc.bInterfaceClass);
+
+               em28xx_devused &= ~(1<<nr);
+               return -ENODEV;
+       }
+
+       endpoint = &interface->cur_altsetting->endpoint[0].desc;
+
+       /* check if the device has the iso in endpoint at the correct place */
+       if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
+           USB_ENDPOINT_XFER_ISOC &&
+           (interface->altsetting[1].endpoint[0].desc.wMaxPacketSize == 940)) {
+               /* It's a newer em2874/em2875 device */
+               isoc_pipe = 0;
+       } else {
+               int check_interface = 1;
+               isoc_pipe = 1;
+               endpoint = &interface->cur_altsetting->endpoint[1].desc;
+               if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
+                   USB_ENDPOINT_XFER_ISOC)
+                       check_interface = 0;
+
+               if ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT)
+                       check_interface = 0;
+
+               if (!check_interface) {
+                       em28xx_err(DRIVER_NAME " video device (%04x:%04x): "
+                               "interface %i, class %i found.\n",
+                               le16_to_cpu(udev->descriptor.idVendor),
+                               le16_to_cpu(udev->descriptor.idProduct),
+                               ifnum,
+                               interface->altsetting[0].desc.bInterfaceClass);
+
+                       em28xx_err(DRIVER_NAME " This is an anciliary "
+                               "interface not used by the driver\n");
+
+                       em28xx_devused &= ~(1<<nr);
+                       return -ENODEV;
+               }
+       }
+
+       switch (udev->speed) {
+       case USB_SPEED_LOW:
+               speed = "1.5";
+               break;
+       case USB_SPEED_UNKNOWN:
+       case USB_SPEED_FULL:
+               speed = "12";
+               break;
+       case USB_SPEED_HIGH:
+               speed = "480";
+               break;
+       default:
+               speed = "unknown";
+       }
+
+       if (udev->manufacturer)
+               strlcpy(descr, udev->manufacturer, sizeof(descr));
+
+       if (udev->product) {
+               if (*descr)
+                       strlcat(descr, " ", sizeof(descr));
+               strlcat(descr, udev->product, sizeof(descr));
+       }
+       if (*descr)
+               strlcat(descr, " ", sizeof(descr));
+
+       printk(DRIVER_NAME ": New device %s@ %s Mbps "
+               "(%04x:%04x, interface %d, class %d)\n",
+               descr,
+               speed,
+               le16_to_cpu(udev->descriptor.idVendor),
+               le16_to_cpu(udev->descriptor.idProduct),
+               ifnum,
+               interface->altsetting->desc.bInterfaceNumber);
+
+       if (nr >= EM28XX_MAXBOARDS) {
+               printk(DRIVER_NAME ": Supports only %i em28xx boards.\n",
+                               EM28XX_MAXBOARDS);
+               em28xx_devused &= ~(1<<nr);
+               return -ENOMEM;
+       }
+
+       /* allocate memory for our device state and initialize it */
+       dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+       if (dev == NULL) {
+               em28xx_err(DRIVER_NAME ": out of memory!\n");
+               em28xx_devused &= ~(1<<nr);
+               return -ENOMEM;
+       }
+
+       snprintf(dev->name, 29, "em28xx #%d", nr);
+       dev->devno = nr;
+       dev->model = id->driver_info;
+       dev->alt   = -1;
+
+       /* Checks if audio is provided by some interface */
+       for (i = 0; i < udev->config->desc.bNumInterfaces; i++) {
+               uif = udev->config->interface[i];
+               if (uif->altsetting[0].desc.bInterfaceClass == USB_CLASS_AUDIO) {
+                       dev->has_audio_class = 1;
+                       break;
+               }
+       }
+
+       /* compute alternate max packet sizes */
+       uif = udev->actconfig->interface[0];
+
+       dev->num_alt = uif->num_altsetting;
+       dev->alt_max_pkt_size = kmalloc(32 * dev->num_alt, GFP_KERNEL);
+
+       if (dev->alt_max_pkt_size == NULL) {
+               em28xx_errdev("out of memory!\n");
+               em28xx_devused &= ~(1<<nr);
+               kfree(dev);
+               return -ENOMEM;
+       }
+
+       for (i = 0; i < dev->num_alt ; i++) {
+               u16 tmp = le16_to_cpu(uif->altsetting[i].endpoint[isoc_pipe].desc.wMaxPacketSize);
+               dev->alt_max_pkt_size[i] =
+                   (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
+       }
+
+       if ((card[nr] >= 0) && (card[nr] < em28xx_bcount))
+               dev->model = card[nr];
+
+       /* allocate device struct */
+       mutex_init(&dev->lock);
+       mutex_lock(&dev->lock);
+       retval = em28xx_init_dev(&dev, udev, nr);
+       if (retval) {
+               em28xx_devused &= ~(1<<dev->devno);
+               kfree(dev);
+
+               return retval;
+       }
+
+       /* save our data pointer in this interface device */
+       usb_set_intfdata(interface, dev);
+
+       request_modules(dev);
+
+       /* Should be the last thing to do, to avoid newer udev's to
+          open the device before fully initializing it
+        */
+       mutex_unlock(&dev->lock);
+
+       return 0;
+}
+
+/*
+ * em28xx_usb_disconnect()
+ * called when the device gets diconencted
+ * video device will be unregistered on v4l2_close in case it is still open
+ */
+static void em28xx_usb_disconnect(struct usb_interface *interface)
+{
+       struct em28xx *dev;
+
+       dev = usb_get_intfdata(interface);
+       usb_set_intfdata(interface, NULL);
+
+       if (!dev)
+               return;
+
+       em28xx_info("disconnecting %s\n", dev->vdev->name);
+
+       /* wait until all current v4l2 io is finished then deallocate
+          resources */
+       mutex_lock(&dev->lock);
+
+       wake_up_interruptible_all(&dev->open);
+
+       if (dev->users) {
+               em28xx_warn
+                   ("device /dev/video%d is open! Deregistration and memory "
+                    "deallocation are deferred on close.\n",
+                               dev->vdev->num);
+
+               dev->state |= DEV_MISCONFIGURED;
+               em28xx_uninit_isoc(dev);
+               dev->state |= DEV_DISCONNECTED;
+               wake_up_interruptible(&dev->wait_frame);
+               wake_up_interruptible(&dev->wait_stream);
+       } else {
+               dev->state |= DEV_DISCONNECTED;
+               em28xx_release_resources(dev);
+       }
+
+       em28xx_close_extension(dev);
+
+       mutex_unlock(&dev->lock);
+
+       if (!dev->users) {
+               kfree(dev->alt_max_pkt_size);
+               kfree(dev);
+       }
+}
+
+static struct usb_driver em28xx_usb_driver = {
+       .name = "em28xx",
+       .probe = em28xx_usb_probe,
+       .disconnect = em28xx_usb_disconnect,
+       .id_table = em28xx_id_table,
+};
+
+static int __init em28xx_module_init(void)
+{
+       int result;
+
+       /* register this driver with the USB subsystem */
+       result = usb_register(&em28xx_usb_driver);
+       if (result)
+               em28xx_err(DRIVER_NAME
+                          " usb_register failed. Error number %d.\n", result);
+
+       printk(KERN_INFO DRIVER_NAME " driver loaded\n");
+
+       return result;
+}
+
+static void __exit em28xx_module_exit(void)
+{
+       /* deregister this driver with the USB subsystem */
+       usb_deregister(&em28xx_usb_driver);
+}
+
+module_init(em28xx_module_init);
+module_exit(em28xx_module_exit);