[media] em28xx: move some video-specific functions to em28xx-video
Now that we want to split the video handling to a separate module, move all video-specific functions to em28xx-video. No functional changes. Reviewed-by: Frank Schäfer <fschaefer.oss@googlemail.com> Signed-off-by: Mauro Carvalho Chehab <m.chehab@samsung.com>
This commit is contained in:
parent
547bf25081
commit
0560f337f3
4 changed files with 371 additions and 370 deletions
|
@ -2529,113 +2529,6 @@ static void em28xx_pre_card_setup(struct em28xx *dev)
|
||||||
em28xx_set_mode(dev, EM28XX_SUSPEND);
|
em28xx_set_mode(dev, EM28XX_SUSPEND);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void em28xx_setup_xc3028(struct em28xx *dev, struct xc2028_ctrl *ctl)
|
|
||||||
{
|
|
||||||
memset(ctl, 0, sizeof(*ctl));
|
|
||||||
|
|
||||||
ctl->fname = XC2028_DEFAULT_FIRMWARE;
|
|
||||||
ctl->max_len = 64;
|
|
||||||
ctl->mts = em28xx_boards[dev->model].mts_firmware;
|
|
||||||
|
|
||||||
switch (dev->model) {
|
|
||||||
case EM2880_BOARD_EMPIRE_DUAL_TV:
|
|
||||||
case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
|
|
||||||
case EM2882_BOARD_TERRATEC_HYBRID_XS:
|
|
||||||
ctl->demod = XC3028_FE_ZARLINK456;
|
|
||||||
break;
|
|
||||||
case EM2880_BOARD_TERRATEC_HYBRID_XS:
|
|
||||||
case EM2880_BOARD_TERRATEC_HYBRID_XS_FR:
|
|
||||||
case EM2881_BOARD_PINNACLE_HYBRID_PRO:
|
|
||||||
ctl->demod = XC3028_FE_ZARLINK456;
|
|
||||||
break;
|
|
||||||
case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2:
|
|
||||||
case EM2882_BOARD_PINNACLE_HYBRID_PRO_330E:
|
|
||||||
ctl->demod = XC3028_FE_DEFAULT;
|
|
||||||
break;
|
|
||||||
case EM2880_BOARD_AMD_ATI_TV_WONDER_HD_600:
|
|
||||||
ctl->demod = XC3028_FE_DEFAULT;
|
|
||||||
ctl->fname = XC3028L_DEFAULT_FIRMWARE;
|
|
||||||
break;
|
|
||||||
case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850:
|
|
||||||
case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950:
|
|
||||||
case EM2880_BOARD_PINNACLE_PCTV_HD_PRO:
|
|
||||||
/* FIXME: Better to specify the needed IF */
|
|
||||||
ctl->demod = XC3028_FE_DEFAULT;
|
|
||||||
break;
|
|
||||||
case EM2883_BOARD_KWORLD_HYBRID_330U:
|
|
||||||
case EM2882_BOARD_DIKOM_DK300:
|
|
||||||
case EM2882_BOARD_KWORLD_VS_DVBT:
|
|
||||||
ctl->demod = XC3028_FE_CHINA;
|
|
||||||
ctl->fname = XC2028_DEFAULT_FIRMWARE;
|
|
||||||
break;
|
|
||||||
case EM2882_BOARD_EVGA_INDTUBE:
|
|
||||||
ctl->demod = XC3028_FE_CHINA;
|
|
||||||
ctl->fname = XC3028L_DEFAULT_FIRMWARE;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ctl->demod = XC3028_FE_OREN538;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void em28xx_tuner_setup(struct em28xx *dev)
|
|
||||||
{
|
|
||||||
struct tuner_setup tun_setup;
|
|
||||||
struct v4l2_frequency f;
|
|
||||||
|
|
||||||
if (dev->tuner_type == TUNER_ABSENT)
|
|
||||||
return;
|
|
||||||
|
|
||||||
memset(&tun_setup, 0, sizeof(tun_setup));
|
|
||||||
|
|
||||||
tun_setup.mode_mask = T_ANALOG_TV | T_RADIO;
|
|
||||||
tun_setup.tuner_callback = em28xx_tuner_callback;
|
|
||||||
|
|
||||||
if (dev->board.radio.type) {
|
|
||||||
tun_setup.type = dev->board.radio.type;
|
|
||||||
tun_setup.addr = dev->board.radio_addr;
|
|
||||||
|
|
||||||
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_type_addr, &tun_setup);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((dev->tuner_type != TUNER_ABSENT) && (dev->tuner_type)) {
|
|
||||||
tun_setup.type = dev->tuner_type;
|
|
||||||
tun_setup.addr = dev->tuner_addr;
|
|
||||||
|
|
||||||
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_type_addr, &tun_setup);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dev->tda9887_conf) {
|
|
||||||
struct v4l2_priv_tun_config tda9887_cfg;
|
|
||||||
|
|
||||||
tda9887_cfg.tuner = TUNER_TDA9887;
|
|
||||||
tda9887_cfg.priv = &dev->tda9887_conf;
|
|
||||||
|
|
||||||
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_config, &tda9887_cfg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dev->tuner_type == TUNER_XC2028) {
|
|
||||||
struct v4l2_priv_tun_config xc2028_cfg;
|
|
||||||
struct xc2028_ctrl ctl;
|
|
||||||
|
|
||||||
memset(&xc2028_cfg, 0, sizeof(xc2028_cfg));
|
|
||||||
memset(&ctl, 0, sizeof(ctl));
|
|
||||||
|
|
||||||
em28xx_setup_xc3028(dev, &ctl);
|
|
||||||
|
|
||||||
xc2028_cfg.tuner = TUNER_XC2028;
|
|
||||||
xc2028_cfg.priv = &ctl;
|
|
||||||
|
|
||||||
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_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;
|
|
||||||
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, &f);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int em28xx_hint_board(struct em28xx *dev)
|
static int em28xx_hint_board(struct em28xx *dev)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
|
@ -53,14 +53,6 @@ MODULE_PARM_DESC(reg_debug, "enable debug messages [URB reg]");
|
||||||
printk(KERN_INFO "%s %s :"fmt, \
|
printk(KERN_INFO "%s %s :"fmt, \
|
||||||
dev->name, __func__ , ##arg); } while (0)
|
dev->name, __func__ , ##arg); } while (0)
|
||||||
|
|
||||||
static int alt;
|
|
||||||
module_param(alt, int, 0644);
|
|
||||||
MODULE_PARM_DESC(alt, "alternate setting to use for video endpoint");
|
|
||||||
|
|
||||||
static unsigned int disable_vbi;
|
|
||||||
module_param(disable_vbi, int, 0644);
|
|
||||||
MODULE_PARM_DESC(disable_vbi, "disable vbi support");
|
|
||||||
|
|
||||||
/* FIXME */
|
/* FIXME */
|
||||||
#define em28xx_isocdbg(fmt, arg...) do {\
|
#define em28xx_isocdbg(fmt, arg...) do {\
|
||||||
if (core_debug) \
|
if (core_debug) \
|
||||||
|
@ -603,24 +595,6 @@ init_audio:
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(em28xx_audio_setup);
|
EXPORT_SYMBOL_GPL(em28xx_audio_setup);
|
||||||
|
|
||||||
int em28xx_colorlevels_set_default(struct em28xx *dev)
|
|
||||||
{
|
|
||||||
em28xx_write_reg(dev, EM28XX_R20_YGAIN, CONTRAST_DEFAULT);
|
|
||||||
em28xx_write_reg(dev, EM28XX_R21_YOFFSET, BRIGHTNESS_DEFAULT);
|
|
||||||
em28xx_write_reg(dev, EM28XX_R22_UVGAIN, SATURATION_DEFAULT);
|
|
||||||
em28xx_write_reg(dev, EM28XX_R23_UOFFSET, BLUE_BALANCE_DEFAULT);
|
|
||||||
em28xx_write_reg(dev, EM28XX_R24_VOFFSET, RED_BALANCE_DEFAULT);
|
|
||||||
em28xx_write_reg(dev, EM28XX_R25_SHARPNESS, SHARPNESS_DEFAULT);
|
|
||||||
|
|
||||||
em28xx_write_reg(dev, EM28XX_R14_GAMMA, 0x20);
|
|
||||||
em28xx_write_reg(dev, EM28XX_R15_RGAIN, 0x20);
|
|
||||||
em28xx_write_reg(dev, EM28XX_R16_GGAIN, 0x20);
|
|
||||||
em28xx_write_reg(dev, EM28XX_R17_BGAIN, 0x20);
|
|
||||||
em28xx_write_reg(dev, EM28XX_R18_ROFFSET, 0x00);
|
|
||||||
em28xx_write_reg(dev, EM28XX_R19_GOFFSET, 0x00);
|
|
||||||
return em28xx_write_reg(dev, EM28XX_R1A_BOFFSET, 0x00);
|
|
||||||
}
|
|
||||||
|
|
||||||
const struct em28xx_led *em28xx_find_led(struct em28xx *dev,
|
const struct em28xx_led *em28xx_find_led(struct em28xx *dev,
|
||||||
enum em28xx_led_role role)
|
enum em28xx_led_role role)
|
||||||
{
|
{
|
||||||
|
@ -696,227 +670,6 @@ int em28xx_capture_start(struct em28xx *dev, int start)
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
int em28xx_vbi_supported(struct em28xx *dev)
|
|
||||||
{
|
|
||||||
/* Modprobe option to manually disable */
|
|
||||||
if (disable_vbi == 1)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (dev->board.is_webcam)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* FIXME: check subdevices for VBI support */
|
|
||||||
|
|
||||||
if (dev->chip_id == CHIP_ID_EM2860 ||
|
|
||||||
dev->chip_id == CHIP_ID_EM2883)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
/* Version of em28xx that does not support VBI */
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int em28xx_set_outfmt(struct em28xx *dev)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
u8 fmt, vinctrl;
|
|
||||||
|
|
||||||
fmt = dev->format->reg;
|
|
||||||
if (!dev->is_em25xx)
|
|
||||||
fmt |= 0x20;
|
|
||||||
/*
|
|
||||||
* NOTE: it's not clear if this is really needed !
|
|
||||||
* The datasheets say bit 5 is a reserved bit and devices seem to work
|
|
||||||
* fine without it. But the Windows driver sets it for em2710/50+em28xx
|
|
||||||
* devices and we've always been setting it, too.
|
|
||||||
*
|
|
||||||
* em2765 (em25xx, em276x/7x/8x) devices do NOT work with this bit set,
|
|
||||||
* it's likely used for an additional (compressed ?) format there.
|
|
||||||
*/
|
|
||||||
ret = em28xx_write_reg(dev, EM28XX_R27_OUTFMT, fmt);
|
|
||||||
if (ret < 0)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
ret = em28xx_write_reg(dev, EM28XX_R10_VINMODE, dev->vinmode);
|
|
||||||
if (ret < 0)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
vinctrl = dev->vinctl;
|
|
||||||
if (em28xx_vbi_supported(dev) == 1) {
|
|
||||||
vinctrl |= EM28XX_VINCTRL_VBI_RAW;
|
|
||||||
em28xx_write_reg(dev, EM28XX_R34_VBI_START_H, 0x00);
|
|
||||||
em28xx_write_reg(dev, EM28XX_R36_VBI_WIDTH, dev->vbi_width/4);
|
|
||||||
em28xx_write_reg(dev, EM28XX_R37_VBI_HEIGHT, dev->vbi_height);
|
|
||||||
if (dev->norm & V4L2_STD_525_60) {
|
|
||||||
/* NTSC */
|
|
||||||
em28xx_write_reg(dev, EM28XX_R35_VBI_START_V, 0x09);
|
|
||||||
} else if (dev->norm & V4L2_STD_625_50) {
|
|
||||||
/* PAL */
|
|
||||||
em28xx_write_reg(dev, EM28XX_R35_VBI_START_V, 0x07);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return em28xx_write_reg(dev, EM28XX_R11_VINCTRL, vinctrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int em28xx_accumulator_set(struct em28xx *dev, u8 xmin, u8 xmax,
|
|
||||||
u8 ymin, u8 ymax)
|
|
||||||
{
|
|
||||||
em28xx_coredbg("em28xx Scale: (%d,%d)-(%d,%d)\n",
|
|
||||||
xmin, ymin, xmax, ymax);
|
|
||||||
|
|
||||||
em28xx_write_regs(dev, EM28XX_R28_XMIN, &xmin, 1);
|
|
||||||
em28xx_write_regs(dev, EM28XX_R29_XMAX, &xmax, 1);
|
|
||||||
em28xx_write_regs(dev, EM28XX_R2A_YMIN, &ymin, 1);
|
|
||||||
return em28xx_write_regs(dev, EM28XX_R2B_YMAX, &ymax, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void em28xx_capture_area_set(struct em28xx *dev, u8 hstart, u8 vstart,
|
|
||||||
u16 width, u16 height)
|
|
||||||
{
|
|
||||||
u8 cwidth = width >> 2;
|
|
||||||
u8 cheight = height >> 2;
|
|
||||||
u8 overflow = (height >> 9 & 0x02) | (width >> 10 & 0x01);
|
|
||||||
/* NOTE: size limit: 2047x1023 = 2MPix */
|
|
||||||
|
|
||||||
em28xx_coredbg("capture area set to (%d,%d): %dx%d\n",
|
|
||||||
hstart, vstart,
|
|
||||||
((overflow & 2) << 9 | cwidth << 2),
|
|
||||||
((overflow & 1) << 10 | cheight << 2));
|
|
||||||
|
|
||||||
em28xx_write_regs(dev, EM28XX_R1C_HSTART, &hstart, 1);
|
|
||||||
em28xx_write_regs(dev, EM28XX_R1D_VSTART, &vstart, 1);
|
|
||||||
em28xx_write_regs(dev, EM28XX_R1E_CWIDTH, &cwidth, 1);
|
|
||||||
em28xx_write_regs(dev, EM28XX_R1F_CHEIGHT, &cheight, 1);
|
|
||||||
em28xx_write_regs(dev, EM28XX_R1B_OFLOW, &overflow, 1);
|
|
||||||
|
|
||||||
/* FIXME: function/meaning of these registers ? */
|
|
||||||
/* FIXME: align width+height to multiples of 4 ?! */
|
|
||||||
if (dev->is_em25xx) {
|
|
||||||
em28xx_write_reg(dev, 0x34, width >> 4);
|
|
||||||
em28xx_write_reg(dev, 0x35, height >> 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int em28xx_scaler_set(struct em28xx *dev, u16 h, u16 v)
|
|
||||||
{
|
|
||||||
u8 mode;
|
|
||||||
/* the em2800 scaler only supports scaling down to 50% */
|
|
||||||
|
|
||||||
if (dev->board.is_em2800) {
|
|
||||||
mode = (v ? 0x20 : 0x00) | (h ? 0x10 : 0x00);
|
|
||||||
} else {
|
|
||||||
u8 buf[2];
|
|
||||||
|
|
||||||
buf[0] = h;
|
|
||||||
buf[1] = h >> 8;
|
|
||||||
em28xx_write_regs(dev, EM28XX_R30_HSCALELOW, (char *)buf, 2);
|
|
||||||
|
|
||||||
buf[0] = v;
|
|
||||||
buf[1] = v >> 8;
|
|
||||||
em28xx_write_regs(dev, EM28XX_R32_VSCALELOW, (char *)buf, 2);
|
|
||||||
/* it seems that both H and V scalers must be active
|
|
||||||
to work correctly */
|
|
||||||
mode = (h || v) ? 0x30 : 0x00;
|
|
||||||
}
|
|
||||||
return em28xx_write_reg_bits(dev, EM28XX_R26_COMPR, mode, 0x30);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FIXME: this only function read values from dev */
|
|
||||||
int em28xx_resolution_set(struct em28xx *dev)
|
|
||||||
{
|
|
||||||
int width, height;
|
|
||||||
width = norm_maxw(dev);
|
|
||||||
height = norm_maxh(dev);
|
|
||||||
|
|
||||||
/* Properly setup VBI */
|
|
||||||
dev->vbi_width = 720;
|
|
||||||
if (dev->norm & V4L2_STD_525_60)
|
|
||||||
dev->vbi_height = 12;
|
|
||||||
else
|
|
||||||
dev->vbi_height = 18;
|
|
||||||
|
|
||||||
em28xx_set_outfmt(dev);
|
|
||||||
|
|
||||||
em28xx_accumulator_set(dev, 1, (width - 4) >> 2, 1, (height - 4) >> 2);
|
|
||||||
|
|
||||||
/* If we don't set the start position to 2 in VBI mode, we end up
|
|
||||||
with line 20/21 being YUYV encoded instead of being in 8-bit
|
|
||||||
greyscale. The core of the issue is that line 21 (and line 23 for
|
|
||||||
PAL WSS) are inside of active video region, and as a result they
|
|
||||||
get the pixelformatting associated with that area. So by cropping
|
|
||||||
it out, we end up with the same format as the rest of the VBI
|
|
||||||
region */
|
|
||||||
if (em28xx_vbi_supported(dev) == 1)
|
|
||||||
em28xx_capture_area_set(dev, 0, 2, width, height);
|
|
||||||
else
|
|
||||||
em28xx_capture_area_set(dev, 0, 0, width, height);
|
|
||||||
|
|
||||||
return em28xx_scaler_set(dev, dev->hscale, dev->vscale);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set USB alternate setting for analog video */
|
|
||||||
int em28xx_set_alternate(struct em28xx *dev)
|
|
||||||
{
|
|
||||||
int errCode;
|
|
||||||
int i;
|
|
||||||
unsigned int min_pkt_size = dev->width * 2 + 4;
|
|
||||||
|
|
||||||
/* NOTE: for isoc transfers, only alt settings > 0 are allowed
|
|
||||||
bulk transfers seem to work only with alt=0 ! */
|
|
||||||
dev->alt = 0;
|
|
||||||
if ((alt > 0) && (alt < dev->num_alt)) {
|
|
||||||
em28xx_coredbg("alternate forced to %d\n", dev->alt);
|
|
||||||
dev->alt = alt;
|
|
||||||
goto set_alt;
|
|
||||||
}
|
|
||||||
if (dev->analog_xfer_bulk)
|
|
||||||
goto set_alt;
|
|
||||||
|
|
||||||
/* When image size is bigger than a certain value,
|
|
||||||
the frame size should be increased, otherwise, only
|
|
||||||
green screen will be received.
|
|
||||||
*/
|
|
||||||
if (dev->width * 2 * dev->height > 720 * 240 * 2)
|
|
||||||
min_pkt_size *= 2;
|
|
||||||
|
|
||||||
for (i = 0; i < dev->num_alt; i++) {
|
|
||||||
/* stop when the selected alt setting offers enough bandwidth */
|
|
||||||
if (dev->alt_max_pkt_size_isoc[i] >= min_pkt_size) {
|
|
||||||
dev->alt = i;
|
|
||||||
break;
|
|
||||||
/* otherwise make sure that we end up with the maximum bandwidth
|
|
||||||
because the min_pkt_size equation might be wrong...
|
|
||||||
*/
|
|
||||||
} else if (dev->alt_max_pkt_size_isoc[i] >
|
|
||||||
dev->alt_max_pkt_size_isoc[dev->alt])
|
|
||||||
dev->alt = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_alt:
|
|
||||||
/* NOTE: for bulk transfers, we need to call usb_set_interface()
|
|
||||||
* even if the previous settings were the same. Otherwise streaming
|
|
||||||
* fails with all urbs having status = -EOVERFLOW ! */
|
|
||||||
if (dev->analog_xfer_bulk) {
|
|
||||||
dev->max_pkt_size = 512; /* USB 2.0 spec */
|
|
||||||
dev->packet_multiplier = EM28XX_BULK_PACKET_MULTIPLIER;
|
|
||||||
} else { /* isoc */
|
|
||||||
em28xx_coredbg("minimum isoc packet size: %u (alt=%d)\n",
|
|
||||||
min_pkt_size, dev->alt);
|
|
||||||
dev->max_pkt_size =
|
|
||||||
dev->alt_max_pkt_size_isoc[dev->alt];
|
|
||||||
dev->packet_multiplier = EM28XX_NUM_ISOC_PACKETS;
|
|
||||||
}
|
|
||||||
em28xx_coredbg("setting alternate %d with wMaxPacketSize=%u\n",
|
|
||||||
dev->alt, dev->max_pkt_size);
|
|
||||||
errCode = usb_set_interface(dev->udev, 0, dev->alt);
|
|
||||||
if (errCode < 0) {
|
|
||||||
em28xx_errdev("cannot change alternate number to %d (error=%i)\n",
|
|
||||||
dev->alt, errCode);
|
|
||||||
return errCode;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int em28xx_gpio_set(struct em28xx *dev, struct em28xx_reg_seq *gpio)
|
int em28xx_gpio_set(struct em28xx *dev, struct em28xx_reg_seq *gpio)
|
||||||
{
|
{
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
@ -1281,18 +1034,6 @@ int em28xx_init_usb_xfer(struct em28xx *dev, enum em28xx_mode mode,
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(em28xx_init_usb_xfer);
|
EXPORT_SYMBOL_GPL(em28xx_init_usb_xfer);
|
||||||
|
|
||||||
/*
|
|
||||||
* em28xx_wake_i2c()
|
|
||||||
* configure i2c attached devices
|
|
||||||
*/
|
|
||||||
void em28xx_wake_i2c(struct em28xx *dev)
|
|
||||||
{
|
|
||||||
v4l2_device_call_all(&dev->v4l2_dev, 0, core, reset, 0);
|
|
||||||
v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_routing,
|
|
||||||
INPUT(dev->ctl_input)->vmux, 0, 0);
|
|
||||||
v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Device control list
|
* Device control list
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -53,15 +53,23 @@
|
||||||
|
|
||||||
#define EM28XX_VERSION "0.2.0"
|
#define EM28XX_VERSION "0.2.0"
|
||||||
|
|
||||||
|
static unsigned int isoc_debug;
|
||||||
|
module_param(isoc_debug, int, 0644);
|
||||||
|
MODULE_PARM_DESC(isoc_debug, "enable debug messages [isoc transfers]");
|
||||||
|
|
||||||
|
static unsigned int disable_vbi;
|
||||||
|
module_param(disable_vbi, int, 0644);
|
||||||
|
MODULE_PARM_DESC(disable_vbi, "disable vbi support");
|
||||||
|
|
||||||
|
static int alt;
|
||||||
|
module_param(alt, int, 0644);
|
||||||
|
MODULE_PARM_DESC(alt, "alternate setting to use for video endpoint");
|
||||||
|
|
||||||
#define em28xx_videodbg(fmt, arg...) do {\
|
#define em28xx_videodbg(fmt, arg...) do {\
|
||||||
if (video_debug) \
|
if (video_debug) \
|
||||||
printk(KERN_INFO "%s %s :"fmt, \
|
printk(KERN_INFO "%s %s :"fmt, \
|
||||||
dev->name, __func__ , ##arg); } while (0)
|
dev->name, __func__ , ##arg); } while (0)
|
||||||
|
|
||||||
static unsigned int isoc_debug;
|
|
||||||
module_param(isoc_debug, int, 0644);
|
|
||||||
MODULE_PARM_DESC(isoc_debug, "enable debug messages [isoc transfers]");
|
|
||||||
|
|
||||||
#define em28xx_isocdbg(fmt, arg...) \
|
#define em28xx_isocdbg(fmt, arg...) \
|
||||||
do {\
|
do {\
|
||||||
if (isoc_debug) { \
|
if (isoc_debug) { \
|
||||||
|
@ -135,6 +143,257 @@ static struct em28xx_fmt format[] = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
int em28xx_vbi_supported(struct em28xx *dev)
|
||||||
|
{
|
||||||
|
/* Modprobe option to manually disable */
|
||||||
|
if (disable_vbi == 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (dev->board.is_webcam)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* FIXME: check subdevices for VBI support */
|
||||||
|
|
||||||
|
if (dev->chip_id == CHIP_ID_EM2860 ||
|
||||||
|
dev->chip_id == CHIP_ID_EM2883)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
/* Version of em28xx that does not support VBI */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* em28xx_wake_i2c()
|
||||||
|
* configure i2c attached devices
|
||||||
|
*/
|
||||||
|
void em28xx_wake_i2c(struct em28xx *dev)
|
||||||
|
{
|
||||||
|
v4l2_device_call_all(&dev->v4l2_dev, 0, core, reset, 0);
|
||||||
|
v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_routing,
|
||||||
|
INPUT(dev->ctl_input)->vmux, 0, 0);
|
||||||
|
v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int em28xx_colorlevels_set_default(struct em28xx *dev)
|
||||||
|
{
|
||||||
|
em28xx_write_reg(dev, EM28XX_R20_YGAIN, CONTRAST_DEFAULT);
|
||||||
|
em28xx_write_reg(dev, EM28XX_R21_YOFFSET, BRIGHTNESS_DEFAULT);
|
||||||
|
em28xx_write_reg(dev, EM28XX_R22_UVGAIN, SATURATION_DEFAULT);
|
||||||
|
em28xx_write_reg(dev, EM28XX_R23_UOFFSET, BLUE_BALANCE_DEFAULT);
|
||||||
|
em28xx_write_reg(dev, EM28XX_R24_VOFFSET, RED_BALANCE_DEFAULT);
|
||||||
|
em28xx_write_reg(dev, EM28XX_R25_SHARPNESS, SHARPNESS_DEFAULT);
|
||||||
|
|
||||||
|
em28xx_write_reg(dev, EM28XX_R14_GAMMA, 0x20);
|
||||||
|
em28xx_write_reg(dev, EM28XX_R15_RGAIN, 0x20);
|
||||||
|
em28xx_write_reg(dev, EM28XX_R16_GGAIN, 0x20);
|
||||||
|
em28xx_write_reg(dev, EM28XX_R17_BGAIN, 0x20);
|
||||||
|
em28xx_write_reg(dev, EM28XX_R18_ROFFSET, 0x00);
|
||||||
|
em28xx_write_reg(dev, EM28XX_R19_GOFFSET, 0x00);
|
||||||
|
return em28xx_write_reg(dev, EM28XX_R1A_BOFFSET, 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
int em28xx_set_outfmt(struct em28xx *dev)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
u8 fmt, vinctrl;
|
||||||
|
|
||||||
|
fmt = dev->format->reg;
|
||||||
|
if (!dev->is_em25xx)
|
||||||
|
fmt |= 0x20;
|
||||||
|
/*
|
||||||
|
* NOTE: it's not clear if this is really needed !
|
||||||
|
* The datasheets say bit 5 is a reserved bit and devices seem to work
|
||||||
|
* fine without it. But the Windows driver sets it for em2710/50+em28xx
|
||||||
|
* devices and we've always been setting it, too.
|
||||||
|
*
|
||||||
|
* em2765 (em25xx, em276x/7x/8x) devices do NOT work with this bit set,
|
||||||
|
* it's likely used for an additional (compressed ?) format there.
|
||||||
|
*/
|
||||||
|
ret = em28xx_write_reg(dev, EM28XX_R27_OUTFMT, fmt);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = em28xx_write_reg(dev, EM28XX_R10_VINMODE, dev->vinmode);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
vinctrl = dev->vinctl;
|
||||||
|
if (em28xx_vbi_supported(dev) == 1) {
|
||||||
|
vinctrl |= EM28XX_VINCTRL_VBI_RAW;
|
||||||
|
em28xx_write_reg(dev, EM28XX_R34_VBI_START_H, 0x00);
|
||||||
|
em28xx_write_reg(dev, EM28XX_R36_VBI_WIDTH, dev->vbi_width/4);
|
||||||
|
em28xx_write_reg(dev, EM28XX_R37_VBI_HEIGHT, dev->vbi_height);
|
||||||
|
if (dev->norm & V4L2_STD_525_60) {
|
||||||
|
/* NTSC */
|
||||||
|
em28xx_write_reg(dev, EM28XX_R35_VBI_START_V, 0x09);
|
||||||
|
} else if (dev->norm & V4L2_STD_625_50) {
|
||||||
|
/* PAL */
|
||||||
|
em28xx_write_reg(dev, EM28XX_R35_VBI_START_V, 0x07);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return em28xx_write_reg(dev, EM28XX_R11_VINCTRL, vinctrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int em28xx_accumulator_set(struct em28xx *dev, u8 xmin, u8 xmax,
|
||||||
|
u8 ymin, u8 ymax)
|
||||||
|
{
|
||||||
|
em28xx_videodbg("em28xx Scale: (%d,%d)-(%d,%d)\n",
|
||||||
|
xmin, ymin, xmax, ymax);
|
||||||
|
|
||||||
|
em28xx_write_regs(dev, EM28XX_R28_XMIN, &xmin, 1);
|
||||||
|
em28xx_write_regs(dev, EM28XX_R29_XMAX, &xmax, 1);
|
||||||
|
em28xx_write_regs(dev, EM28XX_R2A_YMIN, &ymin, 1);
|
||||||
|
return em28xx_write_regs(dev, EM28XX_R2B_YMAX, &ymax, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void em28xx_capture_area_set(struct em28xx *dev, u8 hstart, u8 vstart,
|
||||||
|
u16 width, u16 height)
|
||||||
|
{
|
||||||
|
u8 cwidth = width >> 2;
|
||||||
|
u8 cheight = height >> 2;
|
||||||
|
u8 overflow = (height >> 9 & 0x02) | (width >> 10 & 0x01);
|
||||||
|
/* NOTE: size limit: 2047x1023 = 2MPix */
|
||||||
|
|
||||||
|
em28xx_videodbg("capture area set to (%d,%d): %dx%d\n",
|
||||||
|
hstart, vstart,
|
||||||
|
((overflow & 2) << 9 | cwidth << 2),
|
||||||
|
((overflow & 1) << 10 | cheight << 2));
|
||||||
|
|
||||||
|
em28xx_write_regs(dev, EM28XX_R1C_HSTART, &hstart, 1);
|
||||||
|
em28xx_write_regs(dev, EM28XX_R1D_VSTART, &vstart, 1);
|
||||||
|
em28xx_write_regs(dev, EM28XX_R1E_CWIDTH, &cwidth, 1);
|
||||||
|
em28xx_write_regs(dev, EM28XX_R1F_CHEIGHT, &cheight, 1);
|
||||||
|
em28xx_write_regs(dev, EM28XX_R1B_OFLOW, &overflow, 1);
|
||||||
|
|
||||||
|
/* FIXME: function/meaning of these registers ? */
|
||||||
|
/* FIXME: align width+height to multiples of 4 ?! */
|
||||||
|
if (dev->is_em25xx) {
|
||||||
|
em28xx_write_reg(dev, 0x34, width >> 4);
|
||||||
|
em28xx_write_reg(dev, 0x35, height >> 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int em28xx_scaler_set(struct em28xx *dev, u16 h, u16 v)
|
||||||
|
{
|
||||||
|
u8 mode;
|
||||||
|
/* the em2800 scaler only supports scaling down to 50% */
|
||||||
|
|
||||||
|
if (dev->board.is_em2800) {
|
||||||
|
mode = (v ? 0x20 : 0x00) | (h ? 0x10 : 0x00);
|
||||||
|
} else {
|
||||||
|
u8 buf[2];
|
||||||
|
|
||||||
|
buf[0] = h;
|
||||||
|
buf[1] = h >> 8;
|
||||||
|
em28xx_write_regs(dev, EM28XX_R30_HSCALELOW, (char *)buf, 2);
|
||||||
|
|
||||||
|
buf[0] = v;
|
||||||
|
buf[1] = v >> 8;
|
||||||
|
em28xx_write_regs(dev, EM28XX_R32_VSCALELOW, (char *)buf, 2);
|
||||||
|
/* it seems that both H and V scalers must be active
|
||||||
|
to work correctly */
|
||||||
|
mode = (h || v) ? 0x30 : 0x00;
|
||||||
|
}
|
||||||
|
return em28xx_write_reg_bits(dev, EM28XX_R26_COMPR, mode, 0x30);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FIXME: this only function read values from dev */
|
||||||
|
int em28xx_resolution_set(struct em28xx *dev)
|
||||||
|
{
|
||||||
|
int width, height;
|
||||||
|
width = norm_maxw(dev);
|
||||||
|
height = norm_maxh(dev);
|
||||||
|
|
||||||
|
/* Properly setup VBI */
|
||||||
|
dev->vbi_width = 720;
|
||||||
|
if (dev->norm & V4L2_STD_525_60)
|
||||||
|
dev->vbi_height = 12;
|
||||||
|
else
|
||||||
|
dev->vbi_height = 18;
|
||||||
|
|
||||||
|
em28xx_set_outfmt(dev);
|
||||||
|
|
||||||
|
em28xx_accumulator_set(dev, 1, (width - 4) >> 2, 1, (height - 4) >> 2);
|
||||||
|
|
||||||
|
/* If we don't set the start position to 2 in VBI mode, we end up
|
||||||
|
with line 20/21 being YUYV encoded instead of being in 8-bit
|
||||||
|
greyscale. The core of the issue is that line 21 (and line 23 for
|
||||||
|
PAL WSS) are inside of active video region, and as a result they
|
||||||
|
get the pixelformatting associated with that area. So by cropping
|
||||||
|
it out, we end up with the same format as the rest of the VBI
|
||||||
|
region */
|
||||||
|
if (em28xx_vbi_supported(dev) == 1)
|
||||||
|
em28xx_capture_area_set(dev, 0, 2, width, height);
|
||||||
|
else
|
||||||
|
em28xx_capture_area_set(dev, 0, 0, width, height);
|
||||||
|
|
||||||
|
return em28xx_scaler_set(dev, dev->hscale, dev->vscale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set USB alternate setting for analog video */
|
||||||
|
int em28xx_set_alternate(struct em28xx *dev)
|
||||||
|
{
|
||||||
|
int errCode;
|
||||||
|
int i;
|
||||||
|
unsigned int min_pkt_size = dev->width * 2 + 4;
|
||||||
|
|
||||||
|
/* NOTE: for isoc transfers, only alt settings > 0 are allowed
|
||||||
|
bulk transfers seem to work only with alt=0 ! */
|
||||||
|
dev->alt = 0;
|
||||||
|
if ((alt > 0) && (alt < dev->num_alt)) {
|
||||||
|
em28xx_videodbg("alternate forced to %d\n", dev->alt);
|
||||||
|
dev->alt = alt;
|
||||||
|
goto set_alt;
|
||||||
|
}
|
||||||
|
if (dev->analog_xfer_bulk)
|
||||||
|
goto set_alt;
|
||||||
|
|
||||||
|
/* When image size is bigger than a certain value,
|
||||||
|
the frame size should be increased, otherwise, only
|
||||||
|
green screen will be received.
|
||||||
|
*/
|
||||||
|
if (dev->width * 2 * dev->height > 720 * 240 * 2)
|
||||||
|
min_pkt_size *= 2;
|
||||||
|
|
||||||
|
for (i = 0; i < dev->num_alt; i++) {
|
||||||
|
/* stop when the selected alt setting offers enough bandwidth */
|
||||||
|
if (dev->alt_max_pkt_size_isoc[i] >= min_pkt_size) {
|
||||||
|
dev->alt = i;
|
||||||
|
break;
|
||||||
|
/* otherwise make sure that we end up with the maximum bandwidth
|
||||||
|
because the min_pkt_size equation might be wrong...
|
||||||
|
*/
|
||||||
|
} else if (dev->alt_max_pkt_size_isoc[i] >
|
||||||
|
dev->alt_max_pkt_size_isoc[dev->alt])
|
||||||
|
dev->alt = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_alt:
|
||||||
|
/* NOTE: for bulk transfers, we need to call usb_set_interface()
|
||||||
|
* even if the previous settings were the same. Otherwise streaming
|
||||||
|
* fails with all urbs having status = -EOVERFLOW ! */
|
||||||
|
if (dev->analog_xfer_bulk) {
|
||||||
|
dev->max_pkt_size = 512; /* USB 2.0 spec */
|
||||||
|
dev->packet_multiplier = EM28XX_BULK_PACKET_MULTIPLIER;
|
||||||
|
} else { /* isoc */
|
||||||
|
em28xx_videodbg("minimum isoc packet size: %u (alt=%d)\n",
|
||||||
|
min_pkt_size, dev->alt);
|
||||||
|
dev->max_pkt_size =
|
||||||
|
dev->alt_max_pkt_size_isoc[dev->alt];
|
||||||
|
dev->packet_multiplier = EM28XX_NUM_ISOC_PACKETS;
|
||||||
|
}
|
||||||
|
em28xx_videodbg("setting alternate %d with wMaxPacketSize=%u\n",
|
||||||
|
dev->alt, dev->max_pkt_size);
|
||||||
|
errCode = usb_set_interface(dev->udev, 0, dev->alt);
|
||||||
|
if (errCode < 0) {
|
||||||
|
em28xx_errdev("cannot change alternate number to %d (error=%i)\n",
|
||||||
|
dev->alt, errCode);
|
||||||
|
return errCode;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------
|
/* ------------------------------------------------------------------
|
||||||
DMA and thread functions
|
DMA and thread functions
|
||||||
------------------------------------------------------------------*/
|
------------------------------------------------------------------*/
|
||||||
|
@ -1817,6 +2076,113 @@ static struct video_device *em28xx_vdev_init(struct em28xx *dev,
|
||||||
return vfd;
|
return vfd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void em28xx_setup_xc3028(struct em28xx *dev, struct xc2028_ctrl *ctl)
|
||||||
|
{
|
||||||
|
memset(ctl, 0, sizeof(*ctl));
|
||||||
|
|
||||||
|
ctl->fname = XC2028_DEFAULT_FIRMWARE;
|
||||||
|
ctl->max_len = 64;
|
||||||
|
ctl->mts = em28xx_boards[dev->model].mts_firmware;
|
||||||
|
|
||||||
|
switch (dev->model) {
|
||||||
|
case EM2880_BOARD_EMPIRE_DUAL_TV:
|
||||||
|
case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
|
||||||
|
case EM2882_BOARD_TERRATEC_HYBRID_XS:
|
||||||
|
ctl->demod = XC3028_FE_ZARLINK456;
|
||||||
|
break;
|
||||||
|
case EM2880_BOARD_TERRATEC_HYBRID_XS:
|
||||||
|
case EM2880_BOARD_TERRATEC_HYBRID_XS_FR:
|
||||||
|
case EM2881_BOARD_PINNACLE_HYBRID_PRO:
|
||||||
|
ctl->demod = XC3028_FE_ZARLINK456;
|
||||||
|
break;
|
||||||
|
case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2:
|
||||||
|
case EM2882_BOARD_PINNACLE_HYBRID_PRO_330E:
|
||||||
|
ctl->demod = XC3028_FE_DEFAULT;
|
||||||
|
break;
|
||||||
|
case EM2880_BOARD_AMD_ATI_TV_WONDER_HD_600:
|
||||||
|
ctl->demod = XC3028_FE_DEFAULT;
|
||||||
|
ctl->fname = XC3028L_DEFAULT_FIRMWARE;
|
||||||
|
break;
|
||||||
|
case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850:
|
||||||
|
case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950:
|
||||||
|
case EM2880_BOARD_PINNACLE_PCTV_HD_PRO:
|
||||||
|
/* FIXME: Better to specify the needed IF */
|
||||||
|
ctl->demod = XC3028_FE_DEFAULT;
|
||||||
|
break;
|
||||||
|
case EM2883_BOARD_KWORLD_HYBRID_330U:
|
||||||
|
case EM2882_BOARD_DIKOM_DK300:
|
||||||
|
case EM2882_BOARD_KWORLD_VS_DVBT:
|
||||||
|
ctl->demod = XC3028_FE_CHINA;
|
||||||
|
ctl->fname = XC2028_DEFAULT_FIRMWARE;
|
||||||
|
break;
|
||||||
|
case EM2882_BOARD_EVGA_INDTUBE:
|
||||||
|
ctl->demod = XC3028_FE_CHINA;
|
||||||
|
ctl->fname = XC3028L_DEFAULT_FIRMWARE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ctl->demod = XC3028_FE_OREN538;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void em28xx_tuner_setup(struct em28xx *dev)
|
||||||
|
{
|
||||||
|
struct tuner_setup tun_setup;
|
||||||
|
struct v4l2_frequency f;
|
||||||
|
|
||||||
|
if (dev->tuner_type == TUNER_ABSENT)
|
||||||
|
return;
|
||||||
|
|
||||||
|
memset(&tun_setup, 0, sizeof(tun_setup));
|
||||||
|
|
||||||
|
tun_setup.mode_mask = T_ANALOG_TV | T_RADIO;
|
||||||
|
tun_setup.tuner_callback = em28xx_tuner_callback;
|
||||||
|
|
||||||
|
if (dev->board.radio.type) {
|
||||||
|
tun_setup.type = dev->board.radio.type;
|
||||||
|
tun_setup.addr = dev->board.radio_addr;
|
||||||
|
|
||||||
|
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_type_addr, &tun_setup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((dev->tuner_type != TUNER_ABSENT) && (dev->tuner_type)) {
|
||||||
|
tun_setup.type = dev->tuner_type;
|
||||||
|
tun_setup.addr = dev->tuner_addr;
|
||||||
|
|
||||||
|
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_type_addr, &tun_setup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev->tda9887_conf) {
|
||||||
|
struct v4l2_priv_tun_config tda9887_cfg;
|
||||||
|
|
||||||
|
tda9887_cfg.tuner = TUNER_TDA9887;
|
||||||
|
tda9887_cfg.priv = &dev->tda9887_conf;
|
||||||
|
|
||||||
|
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_config, &tda9887_cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev->tuner_type == TUNER_XC2028) {
|
||||||
|
struct v4l2_priv_tun_config xc2028_cfg;
|
||||||
|
struct xc2028_ctrl ctl;
|
||||||
|
|
||||||
|
memset(&xc2028_cfg, 0, sizeof(xc2028_cfg));
|
||||||
|
memset(&ctl, 0, sizeof(ctl));
|
||||||
|
|
||||||
|
em28xx_setup_xc3028(dev, &ctl);
|
||||||
|
|
||||||
|
xc2028_cfg.tuner = TUNER_XC2028;
|
||||||
|
xc2028_cfg.priv = &ctl;
|
||||||
|
|
||||||
|
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_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;
|
||||||
|
v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, &f);
|
||||||
|
}
|
||||||
|
|
||||||
int em28xx_register_analog_devices(struct em28xx *dev)
|
int em28xx_register_analog_devices(struct em28xx *dev)
|
||||||
{
|
{
|
||||||
u8 val;
|
u8 val;
|
||||||
|
|
|
@ -748,6 +748,7 @@ void em28xx_init_extension(struct em28xx *dev);
|
||||||
void em28xx_close_extension(struct em28xx *dev);
|
void em28xx_close_extension(struct em28xx *dev);
|
||||||
|
|
||||||
/* Provided by em28xx-video.c */
|
/* Provided by em28xx-video.c */
|
||||||
|
void em28xx_tuner_setup(struct em28xx *dev);
|
||||||
int em28xx_vb2_setup(struct em28xx *dev);
|
int em28xx_vb2_setup(struct em28xx *dev);
|
||||||
int em28xx_register_analog_devices(struct em28xx *dev);
|
int em28xx_register_analog_devices(struct em28xx *dev);
|
||||||
void em28xx_release_analog_resources(struct em28xx *dev);
|
void em28xx_release_analog_resources(struct em28xx *dev);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue