diff options
Diffstat (limited to 'drivers/media/usb/em28xx')
-rw-r--r-- | drivers/media/usb/em28xx/Kconfig | 2 | ||||
-rw-r--r-- | drivers/media/usb/em28xx/em28xx-audio.c | 108 | ||||
-rw-r--r-- | drivers/media/usb/em28xx/em28xx-camera.c | 4 | ||||
-rw-r--r-- | drivers/media/usb/em28xx/em28xx-cards.c | 129 | ||||
-rw-r--r-- | drivers/media/usb/em28xx/em28xx-core.c | 54 | ||||
-rw-r--r-- | drivers/media/usb/em28xx/em28xx-dvb.c | 193 | ||||
-rw-r--r-- | drivers/media/usb/em28xx/em28xx-i2c.c | 41 | ||||
-rw-r--r-- | drivers/media/usb/em28xx/em28xx-input.c | 55 | ||||
-rw-r--r-- | drivers/media/usb/em28xx/em28xx-video.c | 56 | ||||
-rw-r--r-- | drivers/media/usb/em28xx/em28xx.h | 15 |
10 files changed, 551 insertions, 106 deletions
diff --git a/drivers/media/usb/em28xx/Kconfig b/drivers/media/usb/em28xx/Kconfig index a1fccf3096d..d23a912096f 100644 --- a/drivers/media/usb/em28xx/Kconfig +++ b/drivers/media/usb/em28xx/Kconfig @@ -53,8 +53,10 @@ config VIDEO_EM28XX_DVB select DVB_MB86A20S if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18212 if MEDIA_SUBDRV_AUTOSELECT select DVB_M88DS3103 if MEDIA_SUBDRV_AUTOSELECT select MEDIA_TUNER_M88TS2022 if MEDIA_SUBDRV_AUTOSELECT + select DVB_DRX39XYJ if MEDIA_SUBDRV_AUTOSELECT ---help--- This adds support for DVB cards based on the Empiatech em28xx chips. diff --git a/drivers/media/usb/em28xx/em28xx-audio.c b/drivers/media/usb/em28xx/em28xx-audio.c index 05e9bd11a3f..342490f44ed 100644 --- a/drivers/media/usb/em28xx/em28xx-audio.c +++ b/drivers/media/usb/em28xx/em28xx-audio.c @@ -252,7 +252,7 @@ static int snd_em28xx_capture_open(struct snd_pcm_substream *substream) { struct em28xx *dev = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; - int ret = 0; + int nonblock, ret = 0; if (!dev) { em28xx_err("BUG: em28xx can't find device struct." @@ -265,45 +265,48 @@ static int snd_em28xx_capture_open(struct snd_pcm_substream *substream) dprintk("opening device and trying to acquire exclusive lock\n"); + nonblock = !!(substream->f_flags & O_NONBLOCK); + if (nonblock) { + if (!mutex_trylock(&dev->lock)) + return -EAGAIN; + } else + mutex_lock(&dev->lock); + runtime->hw = snd_em28xx_hw_capture; - if ((dev->alt == 0 || dev->is_audio_only) && dev->adev.users == 0) { - int nonblock = !!(substream->f_flags & O_NONBLOCK); - - if (nonblock) { - if (!mutex_trylock(&dev->lock)) - return -EAGAIN; - } else - mutex_lock(&dev->lock); - if (dev->is_audio_only) - /* vendor audio is on a separate interface */ - dev->alt = 1; - else - /* vendor audio is on the same interface as video */ - dev->alt = 7; - /* - * FIXME: The intention seems to be to select the alt - * setting with the largest wMaxPacketSize for the video - * endpoint. - * At least dev->alt should be used instead, but we - * should probably not touch it at all if it is - * already >0, because wMaxPacketSize of the audio - * endpoints seems to be the same for all. - */ - - dprintk("changing alternate number on interface %d to %d\n", - dev->ifnum, dev->alt); - usb_set_interface(dev->udev, dev->ifnum, dev->alt); + + if (dev->adev.users == 0) { + if (dev->alt == 0 || dev->is_audio_only) { + if (dev->is_audio_only) + /* audio is on a separate interface */ + dev->alt = 1; + else + /* audio is on the same interface as video */ + dev->alt = 7; + /* + * FIXME: The intention seems to be to select + * the alt setting with the largest + * wMaxPacketSize for the video endpoint. + * At least dev->alt should be used instead, but + * we should probably not touch it at all if it + * is already >0, because wMaxPacketSize of the + * audio endpoints seems to be the same for all. + */ + dprintk("changing alternate number on interface %d to %d\n", + dev->ifnum, dev->alt); + usb_set_interface(dev->udev, dev->ifnum, dev->alt); + } /* Sets volume, mute, etc */ dev->mute = 0; ret = em28xx_audio_analog_set(dev); if (ret < 0) goto err; - - dev->adev.users++; - mutex_unlock(&dev->lock); } + kref_get(&dev->ref); + dev->adev.users++; + mutex_unlock(&dev->lock); + /* Dynamically adjust the period size */ snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, @@ -341,6 +344,7 @@ static int snd_em28xx_pcm_close(struct snd_pcm_substream *substream) substream->runtime->dma_area = NULL; } mutex_unlock(&dev->lock); + kref_put(&dev->ref, em28xx_free_device); return 0; } @@ -895,13 +899,15 @@ static int em28xx_audio_init(struct em28xx *dev) em28xx_info("Binding audio extension\n"); + kref_get(&dev->ref); + printk(KERN_INFO "em28xx-audio.c: Copyright (C) 2006 Markus " "Rechberger\n"); printk(KERN_INFO "em28xx-audio.c: Copyright (C) 2007-2014 Mauro Carvalho Chehab\n"); - err = snd_card_create(index[devnr], "Em28xx Audio", THIS_MODULE, 0, - &card); + err = snd_card_new(&dev->udev->dev, index[devnr], "Em28xx Audio", + THIS_MODULE, 0, &card); if (err < 0) return err; @@ -918,7 +924,6 @@ static int em28xx_audio_init(struct em28xx *dev) pcm->private_data = dev; strcpy(pcm->name, "Empia 28xx Capture"); - snd_card_set_dev(card, &dev->udev->dev); strcpy(card->driver, "Em28xx-Audio"); strcpy(card->shortname, "Em28xx Audio"); strcpy(card->longname, "Empia Em28xx Audio"); @@ -967,7 +972,7 @@ static int em28xx_audio_fini(struct em28xx *dev) if (dev == NULL) return 0; - if (dev->has_alsa_audio != 1) { + if (!dev->has_alsa_audio) { /* This device does not support the extension (in this case the device is expecting the snd-usb-audio module or doesn't have analog audio support at all) */ @@ -986,6 +991,35 @@ static int em28xx_audio_fini(struct em28xx *dev) dev->adev.sndcard = NULL; } + kref_put(&dev->ref, em28xx_free_device); + return 0; +} + +static int em28xx_audio_suspend(struct em28xx *dev) +{ + if (dev == NULL) + return 0; + + if (!dev->has_alsa_audio) + return 0; + + em28xx_info("Suspending audio extension"); + em28xx_deinit_isoc_audio(dev); + atomic_set(&dev->stream_started, 0); + return 0; +} + +static int em28xx_audio_resume(struct em28xx *dev) +{ + if (dev == NULL) + return 0; + + if (!dev->has_alsa_audio) + return 0; + + em28xx_info("Resuming audio extension"); + /* Nothing to do other than schedule_work() ?? */ + schedule_work(&dev->wq_trigger); return 0; } @@ -994,6 +1028,8 @@ static struct em28xx_ops audio_ops = { .name = "Em28xx Audio Extension", .init = em28xx_audio_init, .fini = em28xx_audio_fini, + .suspend = em28xx_audio_suspend, + .resume = em28xx_audio_resume, }; static int __init em28xx_alsa_register(void) @@ -1008,7 +1044,7 @@ static void __exit em28xx_alsa_unregister(void) MODULE_LICENSE("GPL"); MODULE_AUTHOR("Markus Rechberger <mrechberger@gmail.com>"); -MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@redhat.com>"); +MODULE_AUTHOR("Mauro Carvalho Chehab"); MODULE_DESCRIPTION(DRIVER_DESC " - audio interface"); MODULE_VERSION(EM28XX_VERSION); diff --git a/drivers/media/usb/em28xx/em28xx-camera.c b/drivers/media/usb/em28xx/em28xx-camera.c index c29f5c4e7b4..505e0505be0 100644 --- a/drivers/media/usb/em28xx/em28xx-camera.c +++ b/drivers/media/usb/em28xx/em28xx-camera.c @@ -120,7 +120,7 @@ static int em28xx_probe_sensor_micron(struct em28xx *dev) reg = 0x00; ret = i2c_master_send(&client, ®, 1); if (ret < 0) { - if (ret != -ENODEV) + if (ret != -ENXIO) em28xx_errdev("couldn't read from i2c device 0x%02x: error %i\n", client.addr << 1, ret); continue; @@ -218,7 +218,7 @@ static int em28xx_probe_sensor_omnivision(struct em28xx *dev) reg = 0x1c; ret = i2c_smbus_read_byte_data(&client, reg); if (ret < 0) { - if (ret != -ENODEV) + if (ret != -ENXIO) em28xx_errdev("couldn't read from i2c device 0x%02x: error %i\n", client.addr << 1, ret); continue; diff --git a/drivers/media/usb/em28xx/em28xx-cards.c b/drivers/media/usb/em28xx/em28xx-cards.c index 4d97a76cc3b..50aa5a5317f 100644 --- a/drivers/media/usb/em28xx/em28xx-cards.c +++ b/drivers/media/usb/em28xx/em28xx-cards.c @@ -66,7 +66,7 @@ MODULE_PARM_DESC(usb_xfer_mode, /* Bitmask marking allocated devices from 0 to EM28XX_MAXBOARDS - 1 */ -DECLARE_BITMAP(em28xx_devused, EM28XX_MAXBOARDS); +static DECLARE_BITMAP(em28xx_devused, EM28XX_MAXBOARDS); struct em28xx_hash_table { unsigned long hash; @@ -189,6 +189,14 @@ static struct em28xx_reg_seq kworld_a340_digital[] = { { -1, -1, -1, -1}, }; +static struct em28xx_reg_seq kworld_ub435q_v3_digital[] = { + {EM2874_R80_GPIO_P0_CTRL, 0xff, 0xff, 100}, + {EM2874_R80_GPIO_P0_CTRL, 0xfe, 0xff, 100}, + {EM2874_R80_GPIO_P0_CTRL, 0xbe, 0xff, 100}, + {EM2874_R80_GPIO_P0_CTRL, 0xfe, 0xff, 100}, + { -1, -1, -1, -1}, +}; + /* Pinnacle Hybrid Pro eb1a:2881 */ static struct em28xx_reg_seq pinnacle_hybrid_pro_analog[] = { {EM2820_R08_GPIO_CTRL, 0xfd, ~EM_GPIO_4, 10}, @@ -214,6 +222,17 @@ static struct em28xx_reg_seq terratec_cinergy_USB_XS_FR_digital[] = { { -1, -1, -1, -1}, }; +/* PCTV HD Mini (80e) GPIOs + 0-5: not used + 6: demod reset, active low + 7: LED on, active high */ +static struct em28xx_reg_seq em2874_pctv_80e_digital[] = { + {EM28XX_R06_I2C_CLK, 0x45, 0xff, 10}, /*400 KHz*/ + {EM2874_R80_GPIO_P0_CTRL, 0x00, 0xff, 100},/*Demod reset*/ + {EM2874_R80_GPIO_P0_CTRL, 0x40, 0xff, 10}, + { -1, -1, -1, -1}, +}; + /* eb1a:2868 Reddo DVB-C USB TV Box GPIO4 - CU1216L NIM Other GPIOs seems to be don't care. */ @@ -497,6 +516,27 @@ static struct em28xx_led speedlink_vad_laplace_leds[] = { {-1, 0, 0, 0}, }; +static struct em28xx_led kworld_ub435q_v3_leds[] = { + { + .role = EM28XX_LED_DIGITAL_CAPTURING, + .gpio_reg = EM2874_R80_GPIO_P0_CTRL, + .gpio_mask = 0x80, + .inverted = 1, + }, + {-1, 0, 0, 0}, +}; + +static struct em28xx_led pctv_80e_leds[] = { + { + .role = EM28XX_LED_DIGITAL_CAPTURING, + .gpio_reg = EM2874_R80_GPIO_P0_CTRL, + .gpio_mask = 0x80, + .inverted = 0, + }, + {-1, 0, 0, 0}, +}; + + /* * Board definitions */ @@ -2128,6 +2168,29 @@ struct em28xx_board em28xx_boards[] = { .tuner_gpio = default_tuner_gpio, .def_i2c_bus = 1, }, + /* + * 1b80:e34c KWorld USB ATSC TV Stick UB435-Q V3 + * Empia EM2874B + LG DT3305 + NXP TDA18271HDC2 + */ + [EM2874_BOARD_KWORLD_UB435Q_V3] = { + .name = "KWorld USB ATSC TV Stick UB435-Q V3", + .tuner_type = TUNER_ABSENT, + .has_dvb = 1, + .tuner_gpio = kworld_ub435q_v3_digital, + .def_i2c_bus = 1, + .i2c_speed = EM28XX_I2C_CLK_WAIT_ENABLE | + EM28XX_I2C_FREQ_100_KHZ, + .leds = kworld_ub435q_v3_leds, + }, + [EM2874_BOARD_PCTV_HD_MINI_80E] = { + .name = "Pinnacle PCTV HD Mini", + .tuner_type = TUNER_ABSENT, + .has_dvb = 1, + .dvb_gpio = em2874_pctv_80e_digital, + .decoder = EM28XX_NODECODER, + .ir_codes = RC_MAP_PINNACLE_PCTV_HD, + .leds = pctv_80e_leds, + }, /* 1ae7:9003/9004 SpeedLink Vicious And Devine Laplace webcam * Empia EM2765 + OmniVision OV2640 */ [EM2765_BOARD_SPEEDLINK_VAD_LAPLACE] = { @@ -2290,6 +2353,8 @@ struct usb_device_id em28xx_id_table[] = { .driver_info = EM2882_BOARD_PINNACLE_HYBRID_PRO_330E }, { USB_DEVICE(0x2304, 0x0227), .driver_info = EM2880_BOARD_PINNACLE_PCTV_HD_PRO }, + { USB_DEVICE(0x2304, 0x023f), + .driver_info = EM2874_BOARD_PCTV_HD_MINI_80E }, { USB_DEVICE(0x0413, 0x6023), .driver_info = EM2800_BOARD_LEADTEK_WINFAST_USBII }, { USB_DEVICE(0x093b, 0xa003), @@ -2304,6 +2369,8 @@ struct usb_device_id em28xx_id_table[] = { .driver_info = EM2870_BOARD_KWORLD_A340 }, { USB_DEVICE(0x1b80, 0xe346), .driver_info = EM2874_BOARD_KWORLD_UB435Q_V2 }, + { USB_DEVICE(0x1b80, 0xe34c), + .driver_info = EM2874_BOARD_KWORLD_UB435Q_V3 }, { USB_DEVICE(0x2013, 0x024f), .driver_info = EM28174_BOARD_PCTV_290E }, { USB_DEVICE(0x2013, 0x024c), @@ -2872,7 +2939,7 @@ static void flush_request_modules(struct em28xx *dev) * unregisters the v4l2,i2c and usb devices * called when the device gets disconnected or at module unload */ -void em28xx_release_resources(struct em28xx *dev) +static void em28xx_release_resources(struct em28xx *dev) { /*FIXME: I2C IR should be disconnected */ @@ -2889,7 +2956,27 @@ void em28xx_release_resources(struct em28xx *dev) mutex_unlock(&dev->lock); }; -EXPORT_SYMBOL_GPL(em28xx_release_resources); + +/** + * em28xx_free_device() - Free em28xx device + * + * @ref: struct kref for em28xx device + * + * This is called when all extensions and em28xx core unregisters a device + */ +void em28xx_free_device(struct kref *ref) +{ + struct em28xx *dev = kref_to_dev(ref); + + em28xx_info("Freeing device\n"); + + if (!dev->disconnected) + em28xx_release_resources(dev); + + kfree(dev->alt_max_pkt_size_isoc); + kfree(dev); +} +EXPORT_SYMBOL_GPL(em28xx_free_device); /* * em28xx_init_dev() @@ -3331,8 +3418,8 @@ static int em28xx_usb_probe(struct usb_interface *interface, if (has_video) { if (!dev->analog_ep_isoc || (try_bulk && dev->analog_ep_bulk)) dev->analog_xfer_bulk = 1; - em28xx_info("analog set to %s mode.\n", - dev->analog_xfer_bulk ? "bulk" : "isoc"); + em28xx_info("analog set to %s mode.\n", + dev->analog_xfer_bulk ? "bulk" : "isoc"); } if (has_dvb) { if (!dev->dvb_ep_isoc || (try_bulk && dev->dvb_ep_bulk)) @@ -3342,6 +3429,8 @@ static int em28xx_usb_probe(struct usb_interface *interface, dev->dvb_xfer_bulk ? "bulk" : "isoc"); } + kref_init(&dev->ref); + request_modules(dev); /* Should be the last thing to do, to avoid newer udev's to @@ -3386,17 +3475,39 @@ static void em28xx_usb_disconnect(struct usb_interface *interface) em28xx_close_extension(dev); em28xx_release_resources(dev); + kref_put(&dev->ref, em28xx_free_device); +} - if (!dev->users) { - kfree(dev->alt_max_pkt_size_isoc); - kfree(dev); - } +static int em28xx_usb_suspend(struct usb_interface *interface, + pm_message_t message) +{ + struct em28xx *dev; + + dev = usb_get_intfdata(interface); + if (!dev) + return 0; + em28xx_suspend_extension(dev); + return 0; +} + +static int em28xx_usb_resume(struct usb_interface *interface) +{ + struct em28xx *dev; + + dev = usb_get_intfdata(interface); + if (!dev) + return 0; + em28xx_resume_extension(dev); + return 0; } static struct usb_driver em28xx_usb_driver = { .name = "em28xx", .probe = em28xx_usb_probe, .disconnect = em28xx_usb_disconnect, + .suspend = em28xx_usb_suspend, + .resume = em28xx_usb_resume, + .reset_resume = em28xx_usb_resume, .id_table = em28xx_id_table, }; diff --git a/drivers/media/usb/em28xx/em28xx-core.c b/drivers/media/usb/em28xx/em28xx-core.c index 898fb9bd88a..523d7e92bf4 100644 --- a/drivers/media/usb/em28xx/em28xx-core.c +++ b/drivers/media/usb/em28xx/em28xx-core.c @@ -619,6 +619,7 @@ EXPORT_SYMBOL_GPL(em28xx_find_led); int em28xx_capture_start(struct em28xx *dev, int start) { int rc; + const struct em28xx_led *led = NULL; if (dev->chip_id == CHIP_ID_EM2874 || dev->chip_id == CHIP_ID_EM2884 || @@ -643,6 +644,8 @@ int em28xx_capture_start(struct em28xx *dev, int start) /* Enable video capture */ rc = em28xx_write_reg(dev, 0x48, 0x00); + if (rc < 0) + return rc; if (dev->mode == EM28XX_ANALOG_MODE) rc = em28xx_write_reg(dev, @@ -650,6 +653,8 @@ int em28xx_capture_start(struct em28xx *dev, int start) else rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x37); + if (rc < 0) + return rc; msleep(6); } else { @@ -658,19 +663,16 @@ int em28xx_capture_start(struct em28xx *dev, int start) } } - if (rc < 0) - return rc; - - /* Switch (explicitly controlled) analog capturing LED on/off */ - if (dev->mode == EM28XX_ANALOG_MODE) { - const struct em28xx_led *led; + if (dev->mode == EM28XX_ANALOG_MODE) led = em28xx_find_led(dev, EM28XX_LED_ANALOG_CAPTURING); - if (led) - em28xx_write_reg_bits(dev, led->gpio_reg, - (!start ^ led->inverted) ? - ~led->gpio_mask : led->gpio_mask, - led->gpio_mask); - } + else + led = em28xx_find_led(dev, EM28XX_LED_DIGITAL_CAPTURING); + + if (led) + em28xx_write_reg_bits(dev, led->gpio_reg, + (!start ^ led->inverted) ? + ~led->gpio_mask : led->gpio_mask, + led->gpio_mask); return rc; } @@ -1106,3 +1108,31 @@ void em28xx_close_extension(struct em28xx *dev) list_del(&dev->devlist); mutex_unlock(&em28xx_devlist_mutex); } + +int em28xx_suspend_extension(struct em28xx *dev) +{ + const struct em28xx_ops *ops = NULL; + + em28xx_info("Suspending extensions"); + mutex_lock(&em28xx_devlist_mutex); + list_for_each_entry(ops, &em28xx_extension_devlist, next) { + if (ops->suspend) + ops->suspend(dev); + } + mutex_unlock(&em28xx_devlist_mutex); + return 0; +} + +int em28xx_resume_extension(struct em28xx *dev) +{ + const struct em28xx_ops *ops = NULL; + + em28xx_info("Resuming extensions"); + mutex_lock(&em28xx_devlist_mutex); + list_for_each_entry(ops, &em28xx_extension_devlist, next) { + if (ops->resume) + ops->resume(dev); + } + mutex_unlock(&em28xx_devlist_mutex); + return 0; +} diff --git a/drivers/media/usb/em28xx/em28xx-dvb.c b/drivers/media/usb/em28xx/em28xx-dvb.c index a0a669e8136..f599b18ef7c 100644 --- a/drivers/media/usb/em28xx/em28xx-dvb.c +++ b/drivers/media/usb/em28xx/em28xx-dvb.c @@ -41,6 +41,7 @@ #include "mt352.h" #include "mt352_priv.h" /* FIXME */ #include "tda1002x.h" +#include "drx39xyj/drx39xxj.h" #include "tda18271.h" #include "s921.h" #include "drxd.h" @@ -48,6 +49,7 @@ #include "tda18271c2dd.h" #include "drxk.h" #include "tda10071.h" +#include "tda18212.h" #include "a8293.h" #include "qt1010.h" #include "mb86a20s.h" @@ -161,6 +163,8 @@ static inline int em28xx_dvb_urb_data_copy(struct em28xx *dev, struct urb *urb) if (urb->status != -EPROTO) continue; } + if (!urb->actual_length) + continue; dvb_dmx_swfilter(&dev->dvb->demux, urb->transfer_buffer, urb->actual_length); } else { @@ -170,6 +174,8 @@ static inline int em28xx_dvb_urb_data_copy(struct em28xx *dev, struct urb *urb) if (urb->iso_frame_desc[i].status != -EPROTO) continue; } + if (!urb->iso_frame_desc[i].actual_length) + continue; dvb_dmx_swfilter(&dev->dvb->demux, urb->transfer_buffer + urb->iso_frame_desc[i].offset, @@ -208,10 +214,10 @@ static int em28xx_start_streaming(struct em28xx_dvb *dvb) if (rc < 0) return rc; - dprintk(1, "Using %d buffers each with %d x %d bytes\n", + dprintk(1, "Using %d buffers each with %d x %d bytes, alternate %d\n", EM28XX_DVB_NUM_BUFS, packet_multiplier, - dvb_max_packet_size); + dvb_max_packet_size, dvb_alt); return em28xx_init_usb_xfer(dev, EM28XX_DIGITAL_MODE, dev->dvb_xfer_bulk, @@ -315,6 +321,18 @@ static struct lgdt3305_config em2874_lgdt3305_dev = { .qam_if_khz = 4000, }; +static struct lgdt3305_config em2874_lgdt3305_nogate_dev = { + .i2c_addr = 0x0e, + .demod_chip = LGDT3305, + .spectral_inversion = 1, + .deny_i2c_rptr = 1, + .mpeg_mode = LGDT3305_MPEG_SERIAL, + .tpclk_edge = LGDT3305_TPCLK_FALLING_EDGE, + .tpvalid_polarity = LGDT3305_TP_VALID_HIGH, + .vsb_if_khz = 3600, + .qam_if_khz = 3600, +}; + static struct s921_config sharp_isdbt = { .demod_address = 0x30 >> 1 }; @@ -351,6 +369,12 @@ static struct tda18271_config kworld_ub435q_v2_config = { .gate = TDA18271_GATE_DIGITAL, }; +static struct tda18212_config kworld_ub435q_v3_config = { + .i2c_address = 0x60, + .if_atsc_vsb = 3600, + .if_atsc_qam = 3600, +}; + static struct zl10353_config em28xx_zl10353_xc3028_no_i2c_gate = { .demod_address = (0x1e >> 1), .no_tuner = 1, @@ -693,7 +717,8 @@ static void pctv_520e_init(struct em28xx *dev) static int em28xx_pctv_290e_set_lna(struct dvb_frontend *fe) { struct dtv_frontend_properties *c = &fe->dtv_property_cache; - struct em28xx *dev = fe->dvb->priv; + struct em28xx_i2c_bus *i2c_bus = fe->dvb->priv; + struct em28xx *dev = i2c_bus->dev; #ifdef CONFIG_GPIOLIB struct em28xx_dvb *dvb = dev->dvb; int ret; @@ -817,6 +842,20 @@ static const struct m88ds3103_config pctv_461e_m88ds3103_config = { .agc = 0x99, }; + +static struct tda18271_std_map drx_j_std_map = { + .atsc_6 = { .if_freq = 5000, .agc_mode = 3, .std = 0, .if_lvl = 1, + .rfagc_top = 0x37, }, + .qam_6 = { .if_freq = 5380, .agc_mode = 3, .std = 3, .if_lvl = 1, + .rfagc_top = 0x37, }, +}; + +static struct tda18271_config pinnacle_80e_dvb_config = { + .std_map = &drx_j_std_map, + .gate = TDA18271_GATE_DIGITAL, + .role = TDA18271_MASTER, +}; + /* ------------------------------------------------------------------ */ static int em28xx_attach_xc3028(u8 addr, struct em28xx *dev) @@ -1005,7 +1044,6 @@ static int em28xx_dvb_init(struct em28xx *dev) em28xx_info("Binding DVB extension\n"); dvb = kzalloc(sizeof(struct em28xx_dvb), GFP_KERNEL); - if (dvb == NULL) { em28xx_info("em28xx_dvb: memory allocation failed\n"); return -ENOMEM; @@ -1370,10 +1408,40 @@ static int em28xx_dvb_init(struct em28xx *dev) goto out_free; } break; + case EM2874_BOARD_KWORLD_UB435Q_V3: + dvb->fe[0] = dvb_attach(lgdt3305_attach, + &em2874_lgdt3305_nogate_dev, + &dev->i2c_adap[dev->def_i2c_bus]); + if (!dvb->fe[0]) { + result = -EINVAL; + goto out_free; + } + + /* Attach the demodulator. */ + if (!dvb_attach(tda18212_attach, dvb->fe[0], + &dev->i2c_adap[dev->def_i2c_bus], + &kworld_ub435q_v3_config)) { + result = -EINVAL; + goto out_free; + } + break; + case EM2874_BOARD_PCTV_HD_MINI_80E: + dvb->fe[0] = dvb_attach(drx39xxj_attach, &dev->i2c_adap[dev->def_i2c_bus]); + if (dvb->fe[0] != NULL) { + dvb->fe[0] = dvb_attach(tda18271_attach, dvb->fe[0], 0x60, + &dev->i2c_adap[dev->def_i2c_bus], + &pinnacle_80e_dvb_config); + if (!dvb->fe[0]) { + result = -EINVAL; + goto out_free; + } + } + break; case EM28178_BOARD_PCTV_461E: { /* demod I2C adapter */ struct i2c_adapter *i2c_adapter; + struct i2c_client *client; struct i2c_board_info info; struct m88ts2022_config m88ts2022_config = { .clock = 27000000, @@ -1396,7 +1464,19 @@ static int em28xx_dvb_init(struct em28xx *dev) info.addr = 0x60; info.platform_data = &m88ts2022_config; request_module("m88ts2022"); - dvb->i2c_client_tuner = i2c_new_device(i2c_adapter, &info); + client = i2c_new_device(i2c_adapter, &info); + if (client == NULL || client->dev.driver == NULL) { + dvb_frontend_detach(dvb->fe[0]); + result = -ENODEV; + goto out_free; + } + + if (!try_module_get(client->dev.driver->owner)) { + i2c_unregister_device(client); + dvb_frontend_detach(dvb->fe[0]); + result = -ENODEV; + goto out_free; + } /* delegate signal strength measurement to tuner */ dvb->fe[0]->ops.read_signal_strength = @@ -1406,10 +1486,14 @@ static int em28xx_dvb_init(struct em28xx *dev) if (!dvb_attach(a8293_attach, dvb->fe[0], &dev->i2c_adap[dev->def_i2c_bus], &em28xx_a8293_config)) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); dvb_frontend_detach(dvb->fe[0]); result = -ENODEV; goto out_free; } + + dvb->i2c_client_tuner = client; } break; default: @@ -1437,6 +1521,9 @@ static int em28xx_dvb_init(struct em28xx *dev) dvb->adapter.mfe_shared = mfe_shared; em28xx_info("DVB extension successfully initialized\n"); + + kref_get(&dev->ref); + ret: em28xx_set_mode(dev, EM28XX_SUSPEND); mutex_unlock(&dev->lock); @@ -1457,6 +1544,9 @@ static inline void prevent_sleep(struct dvb_frontend_ops *ops) static int em28xx_dvb_fini(struct em28xx *dev) { + struct em28xx_dvb *dvb; + struct i2c_client *client; + if (dev->is_audio_only) { /* Shouldn't initialize IR for this interface */ return 0; @@ -1467,23 +1557,96 @@ static int em28xx_dvb_fini(struct em28xx *dev) return 0; } + if (!dev->dvb) + return 0; + em28xx_info("Closing DVB extension"); + dvb = dev->dvb; + client = dvb->i2c_client_tuner; + + em28xx_uninit_usb_xfer(dev, EM28XX_DIGITAL_MODE); + + if (dev->disconnected) { + /* We cannot tell the device to sleep + * once it has been unplugged. */ + if (dvb->fe[0]) + prevent_sleep(&dvb->fe[0]->ops); + if (dvb->fe[1]) + prevent_sleep(&dvb->fe[1]->ops); + } + + /* remove I2C tuner */ + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); + } + + em28xx_unregister_dvb(dvb); + kfree(dvb); + dev->dvb = NULL; + kref_put(&dev->ref, em28xx_free_device); + + return 0; +} + +static int em28xx_dvb_suspend(struct em28xx *dev) +{ + int ret = 0; + + if (dev->is_audio_only) + return 0; + + if (!dev->board.has_dvb) + return 0; + + em28xx_info("Suspending DVB extension"); if (dev->dvb) { struct em28xx_dvb *dvb = dev->dvb; - em28xx_uninit_usb_xfer(dev, EM28XX_DIGITAL_MODE); + if (dvb->fe[0]) { + ret = dvb_frontend_suspend(dvb->fe[0]); + em28xx_info("fe0 suspend %d", ret); + } + if (dvb->fe[1]) { + dvb_frontend_suspend(dvb->fe[1]); + em28xx_info("fe1 suspend %d", ret); + } + } + + return 0; +} + +static int em28xx_dvb_resume(struct em28xx *dev) +{ + int ret = 0; - if (dev->disconnected) { - /* We cannot tell the device to sleep - * once it has been unplugged. */ - if (dvb->fe[0]) - prevent_sleep(&dvb->fe[0]->ops); - if (dvb->fe[1]) - prevent_sleep(&dvb->fe[1]->ops); + if (dev->is_audio_only) + return 0; + + if (!dev->board.has_dvb) + return 0; + + em28xx_info("Resuming DVB extension"); + if (dev->dvb) { + struct em28xx_dvb *dvb = dev->dvb; + struct i2c_client *client = dvb->i2c_client_tuner; + + if (dvb->fe[0]) { + ret = dvb_frontend_resume(dvb->fe[0]); + em28xx_info("fe0 resume %d", ret); + } + + if (dvb->fe[1]) { + ret = dvb_frontend_resume(dvb->fe[1]); + em28xx_info("fe1 resume %d", ret); + } + /* remove I2C tuner */ + if (client) { + module_put(client->dev.driver->owner); + i2c_unregister_device(client); } - i2c_release_client(dvb->i2c_client_tuner); em28xx_unregister_dvb(dvb); kfree(dvb); dev->dvb = NULL; @@ -1497,6 +1660,8 @@ static struct em28xx_ops dvb_ops = { .name = "Em28xx dvb Extension", .init = em28xx_dvb_init, .fini = em28xx_dvb_fini, + .suspend = em28xx_dvb_suspend, + .resume = em28xx_dvb_resume, }; static int __init em28xx_dvb_register(void) diff --git a/drivers/media/usb/em28xx/em28xx-i2c.c b/drivers/media/usb/em28xx/em28xx-i2c.c index 7e1724076ac..ba6433c3a64 100644 --- a/drivers/media/usb/em28xx/em28xx-i2c.c +++ b/drivers/media/usb/em28xx/em28xx-i2c.c @@ -81,7 +81,7 @@ static int em2800_i2c_send_bytes(struct em28xx *dev, u8 addr, u8 *buf, u16 len) return len; if (ret == 0x94 + len - 1) { if (i2c_debug == 1) - em28xx_warn("R05 returned 0x%02x: I2C timeout", + em28xx_warn("R05 returned 0x%02x: I2C ACK error\n", ret); return -ENXIO; } @@ -128,7 +128,7 @@ static int em2800_i2c_recv_bytes(struct em28xx *dev, u8 addr, u8 *buf, u16 len) break; if (ret == 0x94 + len - 1) { if (i2c_debug == 1) - em28xx_warn("R05 returned 0x%02x: I2C timeout", + em28xx_warn("R05 returned 0x%02x: I2C ACK error\n", ret); return -ENXIO; } @@ -210,7 +210,7 @@ static int em28xx_i2c_send_bytes(struct em28xx *dev, u16 addr, u8 *buf, return len; if (ret == 0x10) { if (i2c_debug == 1) - em28xx_warn("I2C transfer timeout on writing to addr 0x%02x", + em28xx_warn("I2C ACK error on writing to addr 0x%02x\n", addr); return -ENXIO; } @@ -226,10 +226,18 @@ static int em28xx_i2c_send_bytes(struct em28xx *dev, u16 addr, u8 *buf, * (even with high payload) ... */ } - if (i2c_debug) - em28xx_warn("write to i2c device at 0x%x timed out (status=%i)\n", - addr, ret); - return -ETIMEDOUT; + + if (ret == 0x02 || ret == 0x04) { + /* NOTE: these errors seem to be related to clock stretching */ + if (i2c_debug) + em28xx_warn("write to i2c device at 0x%x timed out (status=%i)\n", + addr, ret); + return -ETIMEDOUT; + } + + em28xx_warn("write to i2c device at 0x%x failed with unknown error (status=%i)\n", + addr, ret); + return -EIO; } /* @@ -274,13 +282,22 @@ static int em28xx_i2c_recv_bytes(struct em28xx *dev, u16 addr, u8 *buf, u16 len) } if (ret == 0x10) { if (i2c_debug == 1) - em28xx_warn("I2C transfer timeout on writing to addr 0x%02x", + em28xx_warn("I2C ACK error on writing to addr 0x%02x\n", addr); return -ENXIO; } - em28xx_warn("unknown i2c error (status=%i)\n", ret); - return -ETIMEDOUT; + if (ret == 0x02 || ret == 0x04) { + /* NOTE: these errors seem to be related to clock stretching */ + if (i2c_debug) + em28xx_warn("write to i2c device at 0x%x timed out (status=%i)\n", + addr, ret); + return -ETIMEDOUT; + } + + em28xx_warn("write to i2c device at 0x%x failed with unknown error (status=%i)\n", + addr, ret); + return -EIO; } /* @@ -337,7 +354,7 @@ static int em25xx_bus_B_send_bytes(struct em28xx *dev, u16 addr, u8 *buf, return len; else if (ret > 0) { if (i2c_debug == 1) - em28xx_warn("Bus B R08 returned 0x%02x: I2C timeout", + em28xx_warn("Bus B R08 returned 0x%02x: I2C ACK error\n", ret); return -ENXIO; } @@ -392,7 +409,7 @@ static int em25xx_bus_B_recv_bytes(struct em28xx *dev, u16 addr, u8 *buf, return len; else if (ret > 0) { if (i2c_debug == 1) - em28xx_warn("Bus B R08 returned 0x%02x: I2C timeout", + em28xx_warn("Bus B R08 returned 0x%02x: I2C ACK error\n", ret); return -ENXIO; } diff --git a/drivers/media/usb/em28xx/em28xx-input.c b/drivers/media/usb/em28xx/em28xx-input.c index 18f65d89d4b..56ef49df4f8 100644 --- a/drivers/media/usb/em28xx/em28xx-input.c +++ b/drivers/media/usb/em28xx/em28xx-input.c @@ -676,6 +676,8 @@ static int em28xx_ir_init(struct em28xx *dev) return 0; } + kref_get(&dev->ref); + if (dev->board.buttons) em28xx_init_buttons(dev); @@ -725,7 +727,7 @@ static int em28xx_ir_init(struct em28xx *dev) case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2: rc->map_name = RC_MAP_HAUPPAUGE; ir->get_key_i2c = em28xx_get_key_em_haup; - rc->allowed_protos = RC_BIT_RC5; + rc_set_allowed_protocols(rc, RC_BIT_RC5); break; case EM2820_BOARD_LEADTEK_WINFAST_USBII_DELUXE: rc->map_name = RC_MAP_WINFAST_USBII_DELUXE; @@ -741,7 +743,7 @@ static int em28xx_ir_init(struct em28xx *dev) switch (dev->chip_id) { case CHIP_ID_EM2860: case CHIP_ID_EM2883: - rc->allowed_protos = RC_BIT_RC5 | RC_BIT_NEC; + rc_set_allowed_protocols(rc, RC_BIT_RC5 | RC_BIT_NEC); ir->get_key = default_polling_getkey; break; case CHIP_ID_EM2884: @@ -749,8 +751,8 @@ static int em28xx_ir_init(struct em28xx *dev) case CHIP_ID_EM28174: case CHIP_ID_EM28178: ir->get_key = em2874_polling_getkey; - rc->allowed_protos = RC_BIT_RC5 | RC_BIT_NEC | - RC_BIT_RC6_0; + rc_set_allowed_protocols(rc, RC_BIT_RC5 | RC_BIT_NEC | + RC_BIT_RC6_0); break; default: err = -ENODEV; @@ -816,7 +818,7 @@ static int em28xx_ir_fini(struct em28xx *dev) /* skip detach on non attached boards */ if (!ir) - return 0; + goto ref_put; if (ir->rc) rc_unregister_device(ir->rc); @@ -824,6 +826,45 @@ static int em28xx_ir_fini(struct em28xx *dev) /* done */ kfree(ir); dev->ir = NULL; + +ref_put: + kref_put(&dev->ref, em28xx_free_device); + + return 0; +} + +static int em28xx_ir_suspend(struct em28xx *dev) +{ + struct em28xx_IR *ir = dev->ir; + + if (dev->is_audio_only) + return 0; + + em28xx_info("Suspending input extension"); + if (ir) + cancel_delayed_work_sync(&ir->work); + cancel_delayed_work_sync(&dev->buttons_query_work); + /* is canceling delayed work sufficient or does the rc event + kthread needs stopping? kthread is stopped in + ir_raw_event_unregister() */ + return 0; +} + +static int em28xx_ir_resume(struct em28xx *dev) +{ + struct em28xx_IR *ir = dev->ir; + + if (dev->is_audio_only) + return 0; + + em28xx_info("Resuming input extension"); + /* if suspend calls ir_raw_event_unregister(), the should call + ir_raw_event_register() */ + if (ir) + schedule_delayed_work(&ir->work, msecs_to_jiffies(ir->polling)); + if (dev->num_button_polling_addresses) + schedule_delayed_work(&dev->buttons_query_work, + msecs_to_jiffies(dev->button_polling_interval)); return 0; } @@ -832,6 +873,8 @@ static struct em28xx_ops rc_ops = { .name = "Em28xx Input Extension", .init = em28xx_ir_init, .fini = em28xx_ir_fini, + .suspend = em28xx_ir_suspend, + .resume = em28xx_ir_resume, }; static int __init em28xx_rc_register(void) @@ -845,7 +888,7 @@ static void __exit em28xx_rc_unregister(void) } MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@redhat.com>"); +MODULE_AUTHOR("Mauro Carvalho Chehab"); MODULE_DESCRIPTION(DRIVER_DESC " - input interface"); MODULE_VERSION(EM28XX_VERSION); diff --git a/drivers/media/usb/em28xx/em28xx-video.c b/drivers/media/usb/em28xx/em28xx-video.c index c3c928937dc..0856e5d367b 100644 --- a/drivers/media/usb/em28xx/em28xx-video.c +++ b/drivers/media/usb/em28xx/em28xx-video.c @@ -1029,7 +1029,7 @@ static int em28xx_vb2_setup(struct em28xx *dev) q = &dev->vb_vidq; q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; q->io_modes = VB2_READ | VB2_MMAP | VB2_USERPTR | VB2_DMABUF; - q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; q->drv_priv = dev; q->buf_struct_size = sizeof(struct em28xx_buffer); q->ops = &em28xx_video_qops; @@ -1043,7 +1043,7 @@ static int em28xx_vb2_setup(struct em28xx *dev) q = &dev->vb_vbiq; q->type = V4L2_BUF_TYPE_VBI_CAPTURE; q->io_modes = VB2_READ | VB2_MMAP | VB2_USERPTR; - q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; q->drv_priv = dev; q->buf_struct_size = sizeof(struct em28xx_buffer); q->ops = &em28xx_vbi_qops; @@ -1837,7 +1837,6 @@ static int em28xx_v4l2_open(struct file *filp) video_device_node_name(vdev), v4l2_type_names[fh_type], dev->users); - if (mutex_lock_interruptible(&dev->lock)) return -ERESTARTSYS; fh = kzalloc(sizeof(struct em28xx_fh), GFP_KERNEL); @@ -1869,6 +1868,7 @@ static int em28xx_v4l2_open(struct file *filp) v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_radio); } + kref_get(&dev->ref); dev->users++; mutex_unlock(&dev->lock); @@ -1918,18 +1918,43 @@ static int em28xx_v4l2_fini(struct em28xx *dev) video_unregister_device(dev->vdev); } + v4l2_ctrl_handler_free(&dev->ctrl_handler); + v4l2_device_unregister(&dev->v4l2_dev); + if (dev->clk) { v4l2_clk_unregister_fixed(dev->clk); dev->clk = NULL; } - v4l2_ctrl_handler_free(&dev->ctrl_handler); - v4l2_device_unregister(&dev->v4l2_dev); - - if (dev->users) - em28xx_warn("Device is open ! Memory deallocation is deferred on last close.\n"); mutex_unlock(&dev->lock); + kref_put(&dev->ref, em28xx_free_device); + + return 0; +} + +static int em28xx_v4l2_suspend(struct em28xx *dev) +{ + if (dev->is_audio_only) + return 0; + if (!dev->has_video) + return 0; + + em28xx_info("Suspending video extension"); + em28xx_stop_urbs(dev); + return 0; +} + +static int em28xx_v4l2_resume(struct em28xx *dev) +{ + if (dev->is_audio_only) + return 0; + + if (!dev->has_video) + return 0; + + em28xx_info("Resuming video extension"); + /* what do we do here */ return 0; } @@ -1950,11 +1975,9 @@ static int em28xx_v4l2_close(struct file *filp) mutex_lock(&dev->lock); if (dev->users == 1) { - /* free the remaining resources if device is disconnected */ - if (dev->disconnected) { - kfree(dev->alt_max_pkt_size_isoc); + /* No sense to try to write to the device */ + if (dev->disconnected) goto exit; - } /* Save some power by putting tuner to sleep */ v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_power, 0); @@ -1975,6 +1998,8 @@ static int em28xx_v4l2_close(struct file *filp) exit: dev->users--; mutex_unlock(&dev->lock); + kref_put(&dev->ref, em28xx_free_device); + return 0; } @@ -2273,7 +2298,8 @@ static int em28xx_v4l2_init(struct em28xx *dev) } em28xx_tuner_setup(dev); - em28xx_init_camera(dev); + if (dev->em28xx_sensor != EM28XX_NOSENSOR) + em28xx_init_camera(dev); /* Configure audio */ ret = em28xx_audio_setup(dev); @@ -2488,6 +2514,8 @@ static int em28xx_v4l2_init(struct em28xx *dev) em28xx_info("V4L2 extension successfully initialized\n"); + kref_get(&dev->ref); + mutex_unlock(&dev->lock); return 0; @@ -2504,6 +2532,8 @@ static struct em28xx_ops v4l2_ops = { .name = "Em28xx v4l2 Extension", .init = em28xx_v4l2_init, .fini = em28xx_v4l2_fini, + .suspend = em28xx_v4l2_suspend, + .resume = em28xx_v4l2_resume, }; static int __init em28xx_video_register(void) diff --git a/drivers/media/usb/em28xx/em28xx.h b/drivers/media/usb/em28xx/em28xx.h index 32d8a4bb796..2051fc9fb93 100644 --- a/drivers/media/usb/em28xx/em28xx.h +++ b/drivers/media/usb/em28xx/em28xx.h @@ -32,6 +32,7 @@ #include <linux/workqueue.h> #include <linux/i2c.h> #include <linux/mutex.h> +#include <linux/kref.h> #include <linux/videodev2.h> #include <media/videobuf2-vmalloc.h> @@ -104,6 +105,7 @@ #define EM2882_BOARD_PINNACLE_HYBRID_PRO_330E 56 #define EM2883_BOARD_KWORLD_HYBRID_330U 57 #define EM2820_BOARD_COMPRO_VIDEOMATE_FORYOU 58 +#define EM2874_BOARD_PCTV_HD_MINI_80E 59 #define EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850 60 #define EM2820_BOARD_PROLINK_PLAYTV_BOX4_USB2 61 #define EM2820_BOARD_GADMEI_TVR200 62 @@ -137,6 +139,7 @@ #define EM2874_BOARD_KWORLD_UB435Q_V2 90 #define EM2765_BOARD_SPEEDLINK_VAD_LAPLACE 91 #define EM28178_BOARD_PCTV_461E 92 +#define EM2874_BOARD_KWORLD_UB435Q_V3 93 /* Limits minimum and default number of buffers */ #define EM28XX_MIN_BUF 4 @@ -399,6 +402,7 @@ enum em28xx_adecoder { enum em28xx_led_role { EM28XX_LED_ANALOG_CAPTURING = 0, + EM28XX_LED_DIGITAL_CAPTURING, EM28XX_LED_ILLUMINATION, EM28XX_NUM_LED_ROLES, /* must be the last */ }; @@ -533,9 +537,10 @@ struct em28xx_i2c_bus { enum em28xx_i2c_algo_type algo_type; }; - /* main device struct */ struct em28xx { + struct kref ref; + /* generic device properties */ char name[30]; /* name (including minor) of the device */ int model; /* index in the device_data struct */ @@ -707,12 +712,16 @@ struct em28xx { struct em28xx_dvb *dvb; }; +#define kref_to_dev(d) container_of(d, struct em28xx, ref) + struct em28xx_ops { struct list_head next; char *name; int id; int (*init)(struct em28xx *); int (*fini)(struct em28xx *); + int (*suspend)(struct em28xx *); + int (*resume)(struct em28xx *); }; /* Provided by em28xx-i2c.c */ @@ -758,13 +767,15 @@ int em28xx_register_extension(struct em28xx_ops *dev); void em28xx_unregister_extension(struct em28xx_ops *dev); void em28xx_init_extension(struct em28xx *dev); void em28xx_close_extension(struct em28xx *dev); +int em28xx_suspend_extension(struct em28xx *dev); +int em28xx_resume_extension(struct em28xx *dev); /* Provided by em28xx-cards.c */ extern struct em28xx_board em28xx_boards[]; extern struct usb_device_id em28xx_id_table[]; int em28xx_tuner_callback(void *ptr, int component, int command, int arg); void em28xx_setup_xc3028(struct em28xx *dev, struct xc2028_ctrl *ctl); -void em28xx_release_resources(struct em28xx *dev); +void em28xx_free_device(struct kref *ref); /* Provided by em28xx-camera.c */ int em28xx_detect_sensor(struct em28xx *dev); |