Basic implemenetation of radio support, also working support for DVR3200H tuners (xc2028 and xc4000).
From f2cd361613526d3fb400becf8d6618745f44e59d Mon Sep 17 00:00:00 2001 From: Miroslav <thunder.m@xxxxxxxx> Date: Sat, 17 Dec 2011 01:23:22 +0100 Subject: [PATCH] Add support for radio tuners to cx23885 driver, and add example of radio support for Leadtek DVR3200 H tuners. --- drivers/media/video/cx23885/cx23885-cards.c | 160 ++++++++++++++++++- drivers/media/video/cx23885/cx23885-video.c | 222 ++++++++++++++++++++++++--- drivers/media/video/cx23885/cx23885.h | 23 +++- 3 files changed, 374 insertions(+), 31 deletions(-) diff --git a/drivers/media/video/cx23885/cx23885-cards.c b/drivers/media/video/cx23885/cx23885-cards.c index 187c462..240e7dd 100644 --- a/drivers/media/video/cx23885/cx23885-cards.c +++ b/drivers/media/video/cx23885/cx23885-cards.c @@ -205,35 +205,79 @@ struct cx23885_board cx23885_boards[] = { }, [CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H] = { .name = "Leadtek Winfast PxDVR3200 H", + .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_ENCODER, .portc = CX23885_MPEG_DVB, + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .radio_type = UNSET, + .radio_addr = ADDR_UNSET, + .tuner_bus = 1, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN2_CH1 | + CX25840_VIN5_CH2 | + CX25840_NONE0_CH3, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_COMPOSITE1, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_SVIDEO_LUMA3 | + CX25840_SVIDEO_CHROMA4, + .gpio0 = 0, + }, { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_VIN7_CH1 | + CX25840_VIN6_CH2 | + CX25840_VIN8_CH3 | + CX25840_COMPONENT_ON, + .gpio0 = 0, + } }, + .radio = { + .type = CX23885_RADIO, + .gpio0 = 0, + }, }, [CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000] = { .name = "Leadtek Winfast PxDVR3200 H XC4000", .porta = CX23885_ANALOG_VIDEO, + .portb = CX23885_MPEG_ENCODER, .portc = CX23885_MPEG_DVB, .tuner_type = TUNER_XC4000, .tuner_addr = 0x61, .radio_type = UNSET, .radio_addr = ADDR_UNSET, + .tuner_bus = 1, .input = {{ .type = CX23885_VMUX_TELEVISION, .vmux = CX25840_VIN2_CH1 | CX25840_VIN5_CH2 | CX25840_NONE0_CH3, + .gpio0 = 0, }, { .type = CX23885_VMUX_COMPOSITE1, .vmux = CX25840_COMPOSITE1, + .gpio0 = 0, }, { .type = CX23885_VMUX_SVIDEO, .vmux = CX25840_SVIDEO_LUMA3 | CX25840_SVIDEO_CHROMA4, + .gpio0 = 0, }, { .type = CX23885_VMUX_COMPONENT, .vmux = CX25840_VIN7_CH1 | CX25840_VIN6_CH2 | CX25840_VIN8_CH3 | CX25840_COMPONENT_ON, + .gpio0 = 0, } }, + .radio = { + .type = CX23885_RADIO, + .gpio0 = 0, + }, }, [CX23885_BOARD_COMPRO_VIDEOMATE_E650F] = { .name = "Compro VideoMate E650F", @@ -818,27 +862,95 @@ static void hauppauge_eeprom(struct cx23885_dev *dev, u8 *eeprom_data) dev->name, tv.model); } +static int cx23885_xc2028_leadtek_callback(struct cx23885_dev *dev, + int command, int arg) +{ + switch (command) { + case XC2028_TUNER_RESET: + /* GPIO 12 (xc2028 tuner reset) */ + cx_set(GP0_IO, 0x00040000); + mdelay(50); + cx_clear(GP0_IO, 0x00000004); + mdelay(75); + cx_set(GP0_IO, 0x00040004); + mdelay(75); + return 0; + case XC2028_RESET_CLK: + case XC2028_I2C_FLUSH: + break; + } + return -EINVAL; +} + +static int cx23885_xc4000_leadtek_callback(struct cx23885_dev *dev, + int command, int arg) +{ + switch (command) { + case XC4000_TUNER_RESET: + /* GPIO 12 (xc4000 tuner reset) */ + cx_set(GP0_IO, 0x00040000); + mdelay(50); + cx_clear(GP0_IO, 0x00000004); + mdelay(75); + cx_set(GP0_IO, 0x00040004); + mdelay(75); + return 0; + } + return -EINVAL; +} + +static int cx23885_xc2028_tuner_callback(struct cx23885_dev *dev, + int command, int arg) +{ + /* Board-specific callbacks */ + switch (dev->board) { + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: + return cx23885_xc2028_leadtek_callback(dev, command, arg); + } + + return -EINVAL; +} + +static int cx23885_xc4000_tuner_callback(struct cx23885_dev *dev, + int command, int arg) +{ + /* Board-specific callbacks */ + switch (dev->board) { + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000: + return cx23885_xc4000_leadtek_callback(dev, command, arg); + } + + return -EINVAL; +} + int cx23885_tuner_callback(void *priv, int component, int command, int arg) { struct cx23885_tsport *port = priv; - struct cx23885_dev *dev = port->dev; + struct cx23885_dev *dev; u32 bitmask = 0; - if (command == XC2028_RESET_CLK) - return 0; + if (!port) { + printk(KERN_ERR "cx23885: Error - private data undefined.\n"); + return -EINVAL; + } + + dev = port->dev; - if (command != 0) { - printk(KERN_ERR "%s(): Unknown command 0x%x.\n", - __func__, command); + if (!dev) { + printk(KERN_ERR "cx23885: Error - device struct undefined.\n"); return -EINVAL; } switch (dev->board) { + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: + printk(KERN_INFO "%s: Calling XC2028/3028 callback\n", dev->name); + return cx23885_xc2028_tuner_callback(dev, command, arg); + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000: + printk(KERN_INFO "%s: Calling XC4000 callback\n", dev->name); + return cx23885_xc4000_tuner_callback(dev, command, arg); case CX23885_BOARD_HAUPPAUGE_HVR1400: case CX23885_BOARD_HAUPPAUGE_HVR1500: case CX23885_BOARD_HAUPPAUGE_HVR1500Q: - case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: - case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000: case CX23885_BOARD_COMPRO_VIDEOMATE_E650F: case CX23885_BOARD_COMPRO_VIDEOMATE_E800: case CX23885_BOARD_LEADTEK_WINFAST_PXTV1200: @@ -861,6 +973,9 @@ int cx23885_tuner_callback(void *priv, int component, int command, int arg) case CX23885_BOARD_NETUP_DUAL_DVB_T_C_CI_RF: altera_ci_tuner_reset(dev, port->nr); break; + default: + printk(KERN_ERR "cx23885: Error: Calling callback for card %d\n", dev->board); + break; } if (bitmask) { @@ -872,6 +987,7 @@ int cx23885_tuner_callback(void *priv, int component, int command, int arg) return 0; } +EXPORT_SYMBOL(cx23885_tuner_callback); void cx23885_gpio_setup(struct cx23885_dev *dev) { @@ -999,7 +1115,11 @@ void cx23885_gpio_setup(struct cx23885_dev *dev) cx_set(GP0_IO, 0x000f000f); break; case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: + cx23885_xc2028_leadtek_callback(dev, XC2028_TUNER_RESET, 0); + break; case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H_XC4000: + cx23885_xc4000_leadtek_callback(dev, XC4000_TUNER_RESET, 0); + break; case CX23885_BOARD_COMPRO_VIDEOMATE_E650F: case CX23885_BOARD_COMPRO_VIDEOMATE_E800: case CX23885_BOARD_LEADTEK_WINFAST_PXTV1200: @@ -1312,6 +1432,30 @@ void cx23885_ir_pci_int_enable(struct cx23885_dev *dev) } } +void cx23885_setup_xc3028(struct cx23885_dev *dev, struct xc2028_ctrl *ctl) +{ + memset(ctl, 0, sizeof(*ctl)); + + ctl->fname = XC2028_DEFAULT_FIRMWARE; + ctl->max_len = 64; + + switch (dev->board) { + case CX23885_BOARD_LEADTEK_WINFAST_PXTV1200: + break; + case CX23885_BOARD_COMPRO_VIDEOMATE_E650F: + case CX23885_BOARD_COMPRO_VIDEOMATE_E800: + case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: + case CX23885_BOARD_DVICO_FUSIONHDTV_DVB_T_DUAL_EXP: + ctl->demod = XC3028_FE_ZARLINK456; + break; + default: + ctl->demod = XC3028_FE_OREN538; + ctl->mts = 1; + break; + } +} +EXPORT_SYMBOL_GPL(cx23885_setup_xc3028); + void cx23885_card_setup(struct cx23885_dev *dev) { struct cx23885_tsport *ts1 = &dev->ts1; diff --git a/drivers/media/video/cx23885/cx23885-video.c b/drivers/media/video/cx23885/cx23885-video.c index e730b92..9a8fab1 100644 --- a/drivers/media/video/cx23885/cx23885-video.c +++ b/drivers/media/video/cx23885/cx23885-video.c @@ -36,6 +36,7 @@ #include <media/v4l2-ioctl.h> #include "cx23885-ioctl.h" #include "tuner-xc2028.h" +#include "xc4000.h" #include <media/cx25840.h> @@ -501,7 +502,6 @@ static int cx23885_video_mux(struct cx23885_dev *dev, unsigned int input) /* Tell the internal A/V decoder */ v4l2_subdev_call(dev->sd_cx25840, video, s_routing, INPUT(input)->vmux, 0, 0); - if ((dev->board == CX23885_BOARD_HAUPPAUGE_HVR1800) || (dev->board == CX23885_BOARD_MPX885)) { /* Configure audio routing */ @@ -871,20 +871,37 @@ static int video_open(struct file *file) fh->height = 240; fh->fmt = format_by_fourcc(V4L2_PIX_FMT_YUYV); + mutex_lock(&dev->lock); + videobuf_queue_sg_init(&fh->vidq, &cx23885_video_qops, &dev->pci->dev, &dev->slock, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_INTERLACED, sizeof(struct cx23885_buffer), fh, NULL); - videobuf_queue_sg_init(&fh->vbiq, &cx23885_vbi_qops, - &dev->pci->dev, &dev->slock, - V4L2_BUF_TYPE_VBI_CAPTURE, - V4L2_FIELD_SEQ_TB, - sizeof(struct cx23885_buffer), - fh, NULL); + &dev->pci->dev, &dev->slock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_SEQ_TB, + sizeof(struct cx23885_buffer), + fh, NULL); + if (fh->radio) { + dprintk(1,"video_open: setting radio device\n"); + cx_write(GPIO_3, cx23885_boards[dev->board].radio.gpio3); + cx_write(GPIO_0, cx23885_boards[dev->board].radio.gpio0); + cx_write(GPIO_1, cx23885_boards[dev->board].radio.gpio1); + cx_write(GPIO_2, cx23885_boards[dev->board].radio.gpio2); + if (cx23885_boards[dev->board].radio.amux) { + /* switch audio input and set tvaudio here */ + } else { + /* set tvaudio here */ + } + call_all(dev, tuner, s_radio); + } + + dev->users++; + mutex_unlock(&dev->lock); dprintk(1, "post videobuf_queue_init()\n"); @@ -981,13 +998,24 @@ static int video_release(struct file *file) } videobuf_mmap_free(&fh->vidq); + videobuf_mmap_free(&fh->vbiq); + + mutex_lock(&dev->lock); file->private_data = NULL; kfree(fh); + dev->users--; + /* We are not putting the tuner to sleep here on exit, because * we want to use the mpeg encoder in another session to capture * tuner video. Closing this will result in no video to the encoder. */ +#if 0 + if (!dev->users) + call_all(dev, core, s_power, 0); +#endif + + mutex_unlock(&dev->lock); return 0; } @@ -1255,17 +1283,13 @@ static int cx23885_enum_input(struct cx23885_dev *dev, struct v4l2_input *i) [CX23885_VMUX_DVB] = "DVB", [CX23885_VMUX_DEBUG] = "for debug only", }; - unsigned int n; + unsigned int n = i->index; dprintk(1, "%s()\n", __func__); - n = i->index; if (n >= MAX_CX23885_INPUT) return -EINVAL; - if (0 == INPUT(n)->type) return -EINVAL; - - i->index = n; i->type = V4L2_INPUT_TYPE_CAMERA; strcpy(i->name, iname[INPUT(n)->type]); if ((CX23885_VMUX_TELEVISION == INPUT(n)->type) || @@ -1505,6 +1529,108 @@ static int vidioc_s_frequency(struct file *file, void *priv, } /* ----------------------------------------------------------- */ +/* RADIO ESPECIFIC IOCTLS */ +/* ----------------------------------------------------------- */ + +static int radio_querycap (struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + + strcpy(cap->driver, "cx23885"); + strlcpy(cap->card, cx23885_boards[dev->board].name, sizeof(cap->card)); + sprintf(cap->bus_info,"PCIe:%s", pci_name(dev->pci)); + cap->capabilities = V4L2_CAP_TUNER; + return 0; +} + +static int radio_g_tuner (struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + + if (unlikely(t->index > 0)) + return -EINVAL; + + strcpy(t->name, "Radio"); + t->type = V4L2_TUNER_RADIO; + + call_all(dev, tuner, g_tuner, t); + return 0; +} + +static int radio_enum_input (struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index != 0) + return -EINVAL; + strcpy(i->name,"Radio"); + i->type = V4L2_INPUT_TYPE_TUNER; + + return 0; +} + +static int radio_g_audio (struct file *file, void *priv, struct v4l2_audio *a) +{ + if (unlikely(a->index)) + return -EINVAL; + + strcpy(a->name,"Radio"); + return 0; +} + +/* FIXME: Should add a standard for radio */ + +static int radio_s_tuner (struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct cx23885_dev *dev = ((struct cx23885_fh *)priv)->dev; + + if (0 != t->index) + return -EINVAL; + + call_all(dev, tuner, s_tuner, t); + + return 0; +} + +static int radio_s_audio (struct file *file, void *fh, + struct v4l2_audio *a) +{ + return 0; +} + +static int radio_s_input (struct file *file, void *fh, unsigned int i) +{ + return 0; +} + +static int radio_queryctrl (struct file *file, void *priv, + struct v4l2_queryctrl *c) +{ + int i; + + if (c->id < V4L2_CID_BASE || + c->id >= V4L2_CID_LASTP1) + return -EINVAL; + if (c->id == V4L2_CID_AUDIO_MUTE || + c->id == V4L2_CID_AUDIO_VOLUME || + c->id == V4L2_CID_AUDIO_BALANCE) { + for (i = 0; i < CX23885_CTLS; i++) { + if (cx23885_ctls[i].v.id == c->id) + break; + } + if (i == CX23885_CTLS) { + *c = no_ctl; + return 0; + } + *c = cx23885_ctls[i].v; + } else + *c = no_ctl; + return 0; +} + +/* ----------------------------------------------------------- */ static void cx23885_vid_timeout(unsigned long data) { @@ -1652,12 +1778,43 @@ static const struct v4l2_file_operations radio_fops = { .ioctl = video_ioctl2, }; +static const struct v4l2_ioctl_ops radio_ioctl_ops = { + .vidioc_querycap = radio_querycap, + .vidioc_g_tuner = radio_g_tuner, + .vidioc_enum_input = radio_enum_input, + .vidioc_g_audio = radio_g_audio, + .vidioc_s_tuner = radio_s_tuner, + .vidioc_s_audio = radio_s_audio, + .vidioc_s_input = radio_s_input, + .vidioc_queryctrl = radio_queryctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = cx23885_g_register, + .vidioc_s_register = cx23885_s_register, +#endif +}; + +static struct video_device cx23885_radio_template = { + .name = "cx23885-radio", + .fops = &radio_fops, + .ioctl_ops = &radio_ioctl_ops, +}; void cx23885_video_unregister(struct cx23885_dev *dev) { dprintk(1, "%s()\n", __func__); cx23885_irq_remove(dev, 0x01); + if (dev->radio_dev) { + if (video_is_registered(dev->radio_dev)) + video_unregister_device(dev->radio_dev); + else + video_device_release(dev->radio_dev); + dev->radio_dev = NULL; + } if (dev->vbi_dev) { if (video_is_registered(dev->vbi_dev)) video_unregister_device(dev->vbi_dev); @@ -1730,22 +1887,28 @@ int cx23885_video_register(struct cx23885_dev *dev) struct tuner_setup tun_setup; memset(&tun_setup, 0, sizeof(tun_setup)); - tun_setup.mode_mask = T_ANALOG_TV; + + tun_setup.mode_mask = T_ANALOG_TV | T_RADIO; tun_setup.type = dev->tuner_type; tun_setup.addr = v4l2_i2c_subdev_addr(sd); + tun_setup.tuner_callback = cx23885_tuner_callback; v4l2_subdev_call(sd, tuner, s_type_addr, &tun_setup); - if (dev->board == CX23885_BOARD_LEADTEK_WINFAST_PXTV1200) { - struct xc2028_ctrl ctrl = { - .fname = XC2028_DEFAULT_FIRMWARE, - .max_len = 64 - }; - struct v4l2_priv_tun_config cfg = { - .tuner = dev->tuner_type, - .priv = &ctrl - }; + if (dev->tuner_type == TUNER_XC2028) { + struct v4l2_priv_tun_config cfg; + struct xc2028_ctrl ctl; + + /* Fills device-dependent initialization parameters */ + cx23885_setup_xc3028(dev, &ctl); + + memset(&cfg, 0, sizeof(cfg)); + cfg.tuner = TUNER_XC2028; + cfg.priv = &ctl; + + printk(KERN_INFO "%s: Asking xc2028/3028 to load firmware %s\n", + dev->name, ctl.fname); v4l2_subdev_call(sd, tuner, s_config, &cfg); } } @@ -1777,6 +1940,21 @@ int cx23885_video_register(struct cx23885_dev *dev) printk(KERN_INFO "%s: registered device %s\n", dev->name, video_device_node_name(dev->vbi_dev)); + if (dev->radio_type == CX23885_RADIO) { + dev->radio_dev = cx23885_vdev_init(dev, dev->pci, + &cx23885_radio_template, "radio"); + video_set_drvdata(dev->radio_dev, dev); + err = video_register_device(dev->radio_dev, VFL_TYPE_RADIO, + radio_nr[dev->nr]); + if (err < 0) { + printk(KERN_ERR "%s: can't register radio device\n", + dev->name); + goto fail_unreg; + } + printk(KERN_INFO "%s: registered device %s\n", + dev->name, video_device_node_name(dev->radio_dev)); + } + /* Register ALSA audio device */ dev->audio_dev = cx23885_audio_register(dev); diff --git a/drivers/media/video/cx23885/cx23885.h b/drivers/media/video/cx23885/cx23885.h index b49036f..5fe977e 100644 --- a/drivers/media/video/cx23885/cx23885.h +++ b/drivers/media/video/cx23885/cx23885.h @@ -35,6 +35,7 @@ #include "btcx-risc.h" #include "cx23885-reg.h" #include "media/cx2341x.h" +#include "tuner-xc2028.h" #include <linux/mutex.h> @@ -225,6 +226,7 @@ struct cx23885_board { */ u32 clk_freq; struct cx23885_input input[MAX_CX23885_INPUT]; + struct cx23885_input radio; int ci_type; /* for NetUP */ }; @@ -234,6 +236,20 @@ struct cx23885_subid { u32 card; }; +enum cx23885_tvaudio { + WW_NONE = 1, + WW_BTSC, + WW_BG, + WW_DK, + WW_I, + WW_L, + WW_EIAJ, + WW_I2SPT, + WW_FM, + WW_I2SADC, + WW_M +}; + struct cx23885_i2c { struct cx23885_dev *dev; @@ -393,7 +409,7 @@ struct cx23885_dev { u32 resources; unsigned int input; unsigned int audinput; /* Selectable audio input */ - u32 tvaudio; + enum cx23885_tvaudio tvaudio; v4l2_std_id tvnorm; unsigned int tuner_type; unsigned char tuner_addr; @@ -416,6 +432,9 @@ struct cx23885_dev { /* V4l */ u32 freq; + int users; + int mpeg_users; + struct video_device *video_dev; struct video_device *vbi_dev; struct video_device *radio_dev; @@ -554,6 +573,8 @@ extern void cx23885_gpio_setup(struct cx23885_dev *dev); extern void cx23885_card_setup(struct cx23885_dev *dev); extern void cx23885_card_setup_pre_i2c(struct cx23885_dev *dev); +extern void cx23885_setup_xc3028(struct cx23885_dev *dev, struct xc2028_ctrl *ctl); + extern int cx23885_dvb_register(struct cx23885_tsport *port); extern int cx23885_dvb_unregister(struct cx23885_tsport *port); -- 1.7.2.3