// SPDX-License-Identifier: GPL-2.0-or-later /* * * device driver for philips saa7134 based TV cards * video4linux video interface * * (c) 2001-03 Gerd Knorr [SuSE Labs] */ #include "saa7134.h" #include "saa7134-reg.h" #include #include #include #include #include #include #include #include #include /* ------------------------------------------------------------------ */ unsigned int video_debug; static unsigned int gbuffers = 8; static unsigned int noninterlaced; /* 0 */ static unsigned int gbufsize = 720*576*4; static unsigned int gbufsize_max = 720*576*4; static char secam[] = "--"; module_param(video_debug, int, 0644); MODULE_PARM_DESC(video_debug,"enable debug messages [video]"); module_param(gbuffers, int, 0444); MODULE_PARM_DESC(gbuffers,"number of capture buffers, range 2-32"); module_param(noninterlaced, int, 0644); MODULE_PARM_DESC(noninterlaced,"capture non interlaced video"); module_param_string(secam, secam, sizeof(secam), 0644); MODULE_PARM_DESC(secam, "force SECAM variant, either DK,L or Lc"); #define video_dbg(fmt, arg...) do { \ if (video_debug & 0x04) \ printk(KERN_DEBUG pr_fmt("video: " fmt), ## arg); \ } while (0) /* ------------------------------------------------------------------ */ /* Defines for Video Output Port Register at address 0x191 */ /* Bit 0: VIP code T bit polarity */ #define VP_T_CODE_P_NON_INVERTED 0x00 #define VP_T_CODE_P_INVERTED 0x01 /* ------------------------------------------------------------------ */ /* Defines for Video Output Port Register at address 0x195 */ /* Bit 2: Video output clock delay control */ #define VP_CLK_CTRL2_NOT_DELAYED 0x00 #define VP_CLK_CTRL2_DELAYED 0x04 /* Bit 1: Video output clock invert control */ #define VP_CLK_CTRL1_NON_INVERTED 0x00 #define VP_CLK_CTRL1_INVERTED 0x02 /* ------------------------------------------------------------------ */ /* Defines for Video Output Port Register at address 0x196 */ /* Bits 2 to 0: VSYNC pin video vertical sync type */ #define VP_VS_TYPE_MASK 0x07 #define VP_VS_TYPE_OFF 0x00 #define VP_VS_TYPE_V123 0x01 #define VP_VS_TYPE_V_ITU 0x02 #define VP_VS_TYPE_VGATE_L 0x03 #define VP_VS_TYPE_RESERVED1 0x04 #define VP_VS_TYPE_RESERVED2 0x05 #define VP_VS_TYPE_F_ITU 0x06 #define VP_VS_TYPE_SC_FID 0x07 /* ------------------------------------------------------------------ */ /* data structs for video */ static int video_out[][9] = { [CCIR656] = { 0x00, 0xb1, 0x00, 0xa1, 0x00, 0x04, 0x06, 0x00, 0x00 }, }; static struct saa7134_format formats[] = { { .fourcc = V4L2_PIX_FMT_GREY, .depth = 8, .pm = 0x06, },{ .fourcc = V4L2_PIX_FMT_RGB555, .depth = 16, .pm = 0x13 | 0x80, },{ .fourcc = V4L2_PIX_FMT_RGB555X, .depth = 16, .pm = 0x13 | 0x80, .bswap = 1, },{ .fourcc = V4L2_PIX_FMT_RGB565, .depth = 16, .pm = 0x10 | 0x80, },{ .fourcc = V4L2_PIX_FMT_RGB565X, .depth = 16, .pm = 0x10 | 0x80, .bswap = 1, },{ .fourcc = V4L2_PIX_FMT_BGR24, .depth = 24, .pm = 0x11, },{ .fourcc = V4L2_PIX_FMT_RGB24, .depth = 24, .pm = 0x11, .bswap = 1, },{ .fourcc = V4L2_PIX_FMT_BGR32, .depth = 32, .pm = 0x12, },{ .fourcc = V4L2_PIX_FMT_RGB32, .depth = 32, .pm = 0x12, .bswap = 1, .wswap = 1, },{ .fourcc = V4L2_PIX_FMT_YUYV, .depth = 16, .pm = 0x00, .bswap = 1, .yuv = 1, },{ .fourcc = V4L2_PIX_FMT_UYVY, .depth = 16, .pm = 0x00, .yuv = 1, },{ .fourcc = V4L2_PIX_FMT_YUV422P, .depth = 16, .pm = 0x09, .yuv = 1, .planar = 1, .hshift = 1, .vshift = 0, },{ .fourcc = V4L2_PIX_FMT_YUV420, .depth = 12, .pm = 0x0a, .yuv = 1, .planar = 1, .hshift = 1, .vshift = 1, },{ .fourcc = V4L2_PIX_FMT_YVU420, .depth = 12, .pm = 0x0a, .yuv = 1, .planar = 1, .uvswap = 1, .hshift = 1, .vshift = 1, } }; #define FORMATS ARRAY_SIZE(formats) #define NORM_625_50 \ .h_start = 0, \ .h_stop = 719, \ .video_v_start = 24, \ .video_v_stop = 311, \ .vbi_v_start_0 = 7, \ .vbi_v_stop_0 = 23, \ .vbi_v_start_1 = 319, \ .src_timing = 4 #define NORM_525_60 \ .h_start = 0, \ .h_stop = 719, \ .video_v_start = 23, \ .video_v_stop = 262, \ .vbi_v_start_0 = 10, \ .vbi_v_stop_0 = 21, \ .vbi_v_start_1 = 273, \ .src_timing = 7 static struct saa7134_tvnorm tvnorms[] = { { .name = "PAL", /* autodetect */ .id = V4L2_STD_PAL, NORM_625_50, .sync_control = 0x18, .luma_control = 0x40, .chroma_ctrl1 = 0x81, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x06, .vgate_misc = 0x1c, },{ .name = "PAL-BG", .id = V4L2_STD_PAL_BG, NORM_625_50, .sync_control = 0x18, .luma_control = 0x40, .chroma_ctrl1 = 0x81, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x06, .vgate_misc = 0x1c, },{ .name = "PAL-I", .id = V4L2_STD_PAL_I, NORM_625_50, .sync_control = 0x18, .luma_control = 0x40, .chroma_ctrl1 = 0x81, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x06, .vgate_misc = 0x1c, },{ .name = "PAL-DK", .id = V4L2_STD_PAL_DK, NORM_625_50, .sync_control = 0x18, .luma_control = 0x40, .chroma_ctrl1 = 0x81, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x06, .vgate_misc = 0x1c, },{ .name = "NTSC", .id = V4L2_STD_NTSC, NORM_525_60, .sync_control = 0x59, .luma_control = 0x40, .chroma_ctrl1 = 0x89, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x0e, .vgate_misc = 0x18, },{ .name = "SECAM", .id = V4L2_STD_SECAM, NORM_625_50, .sync_control = 0x18, .luma_control = 0x1b, .chroma_ctrl1 = 0xd1, .chroma_gain = 0x80, .chroma_ctrl2 = 0x00, .vgate_misc = 0x1c, },{ .name = "SECAM-DK", .id = V4L2_STD_SECAM_DK, NORM_625_50, .sync_control = 0x18, .luma_control = 0x1b, .chroma_ctrl1 = 0xd1, .chroma_gain = 0x80, .chroma_ctrl2 = 0x00, .vgate_misc = 0x1c, },{ .name = "SECAM-L", .id = V4L2_STD_SECAM_L, NORM_625_50, .sync_control = 0x18, .luma_control = 0x1b, .chroma_ctrl1 = 0xd1, .chroma_gain = 0x80, .chroma_ctrl2 = 0x00, .vgate_misc = 0x1c, },{ .name = "SECAM-Lc", .id = V4L2_STD_SECAM_LC, NORM_625_50, .sync_control = 0x18, .luma_control = 0x1b, .chroma_ctrl1 = 0xd1, .chroma_gain = 0x80, .chroma_ctrl2 = 0x00, .vgate_misc = 0x1c, },{ .name = "PAL-M", .id = V4L2_STD_PAL_M, NORM_525_60, .sync_control = 0x59, .luma_control = 0x40, .chroma_ctrl1 = 0xb9, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x0e, .vgate_misc = 0x18, },{ .name = "PAL-Nc", .id = V4L2_STD_PAL_Nc, NORM_625_50, .sync_control = 0x18, .luma_control = 0x40, .chroma_ctrl1 = 0xa1, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x06, .vgate_misc = 0x1c, },{ .name = "PAL-60", .id = V4L2_STD_PAL_60, .h_start = 0, .h_stop = 719, .video_v_start = 23, .video_v_stop = 262, .vbi_v_start_0 = 10, .vbi_v_stop_0 = 21, .vbi_v_start_1 = 273, .src_timing = 7, .sync_control = 0x18, .luma_control = 0x40, .chroma_ctrl1 = 0x81, .chroma_gain = 0x2a, .chroma_ctrl2 = 0x06, .vgate_misc = 0x1c, } }; #define TVNORMS ARRAY_SIZE(tvnorms) static struct saa7134_format* format_by_fourcc(unsigned int fourcc) { unsigned int i; for (i = 0; i < FORMATS; i++) if (formats[i].fourcc == fourcc) return formats+i; return NULL; } /* ------------------------------------------------------------------ */ static void set_tvnorm(struct saa7134_dev *dev, struct saa7134_tvnorm *norm) { video_dbg("set tv norm = %s\n", norm->name); dev->tvnorm = norm; /* setup cropping */ dev->crop_bounds.left = norm->h_start; dev->crop_defrect.left = norm->h_start; dev->crop_bounds.width = norm->h_stop - norm->h_start +1; dev->crop_defrect.width = norm->h_stop - norm->h_start +1; dev->crop_bounds.top = (norm->vbi_v_stop_0+1)*2; dev->crop_defrect.top = norm->video_v_start*2; dev->crop_bounds.height = ((norm->id & V4L2_STD_525_60) ? 524 : 624) - dev->crop_bounds.top; dev->crop_defrect.height = (norm->video_v_stop - norm->video_v_start +1)*2; dev->crop_current = dev->crop_defrect; saa7134_set_tvnorm_hw(dev); } static void video_mux(struct saa7134_dev *dev, int input) { video_dbg("video input = %d [%s]\n", input, saa7134_input_name[card_in(dev, input).type]); dev->ctl_input = input; set_tvnorm(dev, dev->tvnorm); saa7134_tvaudio_setinput(dev, &card_in(dev, input)); } static void saa7134_set_decoder(struct saa7134_dev *dev) { int luma_control, sync_control, chroma_ctrl1, mux; struct saa7134_tvnorm *norm = dev->tvnorm; mux = card_in(dev, dev->ctl_input).vmux; luma_control = norm->luma_control; sync_control = norm->sync_control; chroma_ctrl1 = norm->chroma_ctrl1; if (mux > 5) luma_control |= 0x80; /* svideo */ if (noninterlaced || dev->nosignal) sync_control |= 0x20; /* switch on auto standard detection */ sync_control |= SAA7134_SYNC_CTRL_AUFD; chroma_ctrl1 |= SAA7134_CHROMA_CTRL1_AUTO0; chroma_ctrl1 &= ~SAA7134_CHROMA_CTRL1_FCTC; luma_control &= ~SAA7134_LUMA_CTRL_LDEL; /* setup video decoder */ saa_writeb(SAA7134_INCR_DELAY, 0x08); saa_writeb(SAA7134_ANALOG_IN_CTRL1, 0xc0 | mux); saa_writeb(SAA7134_ANALOG_IN_CTRL2, 0x00); saa_writeb(SAA7134_ANALOG_IN_CTRL3, 0x90); saa_writeb(SAA7134_ANALOG_IN_CTRL4, 0x90); saa_writeb(SAA7134_HSYNC_START, 0xeb); saa_writeb(SAA7134_HSYNC_STOP, 0xe0); saa_writeb(SAA7134_SOURCE_TIMING1, norm->src_timing); saa_writeb(SAA7134_SYNC_CTRL, sync_control); saa_writeb(SAA7134_LUMA_CTRL, luma_control); saa_writeb(SAA7134_DEC_LUMA_BRIGHT, dev->ctl_bright); saa_writeb(SAA7134_DEC_LUMA_CONTRAST, dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast); saa_writeb(SAA7134_DEC_CHROMA_SATURATION, dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation); saa_writeb(SAA7134_DEC_CHROMA_HUE, dev->ctl_hue); saa_writeb(SAA7134_CHROMA_CTRL1, chroma_ctrl1); saa_writeb(SAA7134_CHROMA_GAIN, norm->chroma_gain); saa_writeb(SAA7134_CHROMA_CTRL2, norm->chroma_ctrl2); saa_writeb(SAA7134_MODE_DELAY_CTRL, 0x00); saa_writeb(SAA7134_ANALOG_ADC, 0x01); saa_writeb(SAA7134_VGATE_START, 0x11); saa_writeb(SAA7134_VGATE_STOP, 0xfe); saa_writeb(SAA7134_MISC_VGATE_MSB, norm->vgate_misc); saa_writeb(SAA7134_RAW_DATA_GAIN, 0x40); saa_writeb(SAA7134_RAW_DATA_OFFSET, 0x80); } void saa7134_set_tvnorm_hw(struct saa7134_dev *dev) { saa7134_set_decoder(dev); saa_call_all(dev, video, s_std, dev->tvnorm->id); /* Set the correct norm for the saa6752hs. This function does nothing if there is no saa6752hs. */ saa_call_empress(dev, video, s_std, dev->tvnorm->id); } static void set_h_prescale(struct saa7134_dev *dev, int task, int prescale) { static const struct { int xpsc; int xacl; int xc2_1; int xdcg; int vpfy; } vals[] = { /* XPSC XACL XC2_1 XDCG VPFY */ { 1, 0, 0, 0, 0 }, { 2, 2, 1, 2, 2 }, { 3, 4, 1, 3, 2 }, { 4, 8, 1, 4, 2 }, { 5, 8, 1, 4, 2 }, { 6, 8, 1, 4, 3 }, { 7, 8, 1, 4, 3 }, { 8, 15, 0, 4, 3 }, { 9, 15, 0, 4, 3 }, { 10, 16, 1, 5, 3 }, }; static const int count = ARRAY_SIZE(vals); int i; for (i = 0; i < count; i++) if (vals[i].xpsc == prescale) break; if (i == count) return; saa_writeb(SAA7134_H_PRESCALE(task), vals[i].xpsc); saa_writeb(SAA7134_ACC_LENGTH(task), vals[i].xacl); saa_writeb(SAA7134_LEVEL_CTRL(task), (vals[i].xc2_1 << 3) | (vals[i].xdcg)); saa_andorb(SAA7134_FIR_PREFILTER_CTRL(task), 0x0f, (vals[i].vpfy << 2) | vals[i].vpfy); } static void set_v_scale(struct saa7134_dev *dev, int task, int yscale) { int val,mirror; saa_writeb(SAA7134_V_SCALE_RATIO1(task), yscale & 0xff); saa_writeb(SAA7134_V_SCALE_RATIO2(task), yscale >> 8); mirror = (dev->ctl_mirror) ? 0x02 : 0x00; if (yscale < 2048) { /* LPI */ video_dbg("yscale LPI yscale=%d\n", yscale); saa_writeb(SAA7134_V_FILTER(task), 0x00 | mirror); saa_writeb(SAA7134_LUMA_CONTRAST(task), 0x40); saa_writeb(SAA7134_CHROMA_SATURATION(task), 0x40); } else { /* ACM */ val = 0x40 * 1024 / yscale; video_dbg("yscale ACM yscale=%d val=0x%x\n", yscale, val); saa_writeb(SAA7134_V_FILTER(task), 0x01 | mirror); saa_writeb(SAA7134_LUMA_CONTRAST(task), val); saa_writeb(SAA7134_CHROMA_SATURATION(task), val); } saa_writeb(SAA7134_LUMA_BRIGHT(task), 0x80); } static void set_size(struct saa7134_dev *dev, int task, int width, int height, int interlace) { int prescale,xscale,yscale,y_even,y_odd; int h_start, h_stop, v_start, v_stop; int div = interlace ? 2 : 1; /* setup video scaler */ h_start = dev->crop_current.left; v_start = dev->crop_current.top/2; h_stop = (dev->crop_current.left + dev->crop_current.width -1); v_stop = (dev->crop_current.top + dev->crop_current.height -1)/2; saa_writeb(SAA7134_VIDEO_H_START1(task), h_start & 0xff); saa_writeb(SAA7134_VIDEO_H_START2(task), h_start >> 8); saa_writeb(SAA7134_VIDEO_H_STOP1(task), h_stop & 0xff); saa_writeb(SAA7134_VIDEO_H_STOP2(task), h_stop >> 8); saa_writeb(SAA7134_VIDEO_V_START1(task), v_start & 0xff); saa_writeb(SAA7134_VIDEO_V_START2(task), v_start >> 8); saa_writeb(SAA7134_VIDEO_V_STOP1(task), v_stop & 0xff); saa_writeb(SAA7134_VIDEO_V_STOP2(task), v_stop >> 8); prescale = dev->crop_current.width / width; if (0 == prescale) prescale = 1; xscale = 1024 * dev->crop_current.width / prescale / width; yscale = 512 * div * dev->crop_current.height / height; video_dbg("prescale=%d xscale=%d yscale=%d\n", prescale, xscale, yscale); set_h_prescale(dev,task,prescale); saa_writeb(SAA7134_H_SCALE_INC1(task), xscale & 0xff); saa_writeb(SAA7134_H_SCALE_INC2(task), xscale >> 8); set_v_scale(dev,task,yscale); saa_writeb(SAA7134_VIDEO_PIXELS1(task), width & 0xff); saa_writeb(SAA7134_VIDEO_PIXELS2(task), width >> 8); saa_writeb(SAA7134_VIDEO_LINES1(task), height/div & 0xff); saa_writeb(SAA7134_VIDEO_LINES2(task), height/div >> 8); /* deinterlace y offsets */ y_odd = dev->ctl_y_odd; y_even = dev->ctl_y_even; saa_writeb(SAA7134_V_PHASE_OFFSET0(task), y_odd); saa_writeb(SAA7134_V_PHASE_OFFSET1(task), y_even); saa_writeb(SAA7134_V_PHASE_OFFSET2(task), y_odd); saa_writeb(SAA7134_V_PHASE_OFFSET3(task), y_even); } /* ------------------------------------------------------------------ */ /* * Media Controller helper functions */ static int saa7134_enable_analog_tuner(struct saa7134_dev *dev) { #ifdef CONFIG_MEDIA_CONTROLLER struct media_device *mdev = dev->media_dev; struct media_entity *source; struct media_link *link, *found_link = NULL; int ret, active_links = 0; if (!mdev || !dev->decoder) return 0; /* * This will find the tuner that is connected into the decoder. * Technically, this is not 100% correct, as the device may be * using an analog input instead of the tuner. However, as we can't * do DVB streaming while the DMA engine is being used for V4L2, * this should be enough for the actual needs. */ list_for_each_entry(link, &dev->decoder->links, list) { if (link->sink->entity == dev->decoder) { found_link = link; if (link->flags & MEDIA_LNK_FL_ENABLED) active_links++; break; } } if (active_links == 1 || !found_link) return 0; source = found_link->source->entity; list_for_each_entry(link, &source->links, list) { struct media_entity *sink; int flags = 0; sink = link->sink->entity; if (sink == dev->decoder) flags = MEDIA_LNK_FL_ENABLED; ret = media_entity_setup_link(link, flags); if (ret) { pr_err("Couldn't change link %s->%s to %s. Error %d\n", source->name, sink->name, flags ? "enabled" : "disabled", ret); return ret; } } #endif return 0; } /* ------------------------------------------------------------------ */ static int buffer_activate(struct saa7134_dev *dev, struct saa7134_buf *buf, struct saa7134_buf *next) { struct saa7134_dmaqueue *dmaq = buf->vb2.vb2_buf.vb2_queue->drv_priv; unsigned long base,control,bpl; unsigned long bpl_uv, lines_uv, base2, base3; /* planar */ video_dbg("buffer_activate buf=%p\n", buf); buf->top_seen = 0; set_size(dev, TASK_A, dev->width, dev->height, V4L2_FIELD_HAS_BOTH(dev->field)); if (dev->fmt->yuv) saa_andorb(SAA7134_DATA_PATH(TASK_A), 0x3f, 0x03); else saa_andorb(SAA7134_DATA_PATH(TASK_A), 0x3f, 0x01); saa_writeb(SAA7134_OFMT_VIDEO_A, dev->fmt->pm); /* DMA: setup channel 0 (= Video Task A0) */ base = saa7134_buffer_base(buf); if (dev->fmt->planar) bpl = dev->width; else bpl = (dev->width * dev->fmt->depth) / 8; control = SAA7134_RS_CONTROL_BURST_16 | SAA7134_RS_CONTROL_ME | (dmaq->pt.dma >> 12); if (dev->fmt->bswap) control |= SAA7134_RS_CONTROL_BSWAP; if (dev->fmt->wswap) control |= SAA7134_RS_CONTROL_WSWAP; if (V4L2_FIELD_HAS_BOTH(dev->field)) { /* interlaced */ saa_writel(SAA7134_RS_BA1(0),base); saa_writel(SAA7134_RS_BA2(0),base+bpl); saa_writel(SAA7134_RS_PITCH(0),bpl*2); } else { /* non-interlaced */ saa_writel(SAA7134_RS_BA1(0),base); saa_writel(SAA7134_RS_BA2(0),base); saa_writel(SAA7134_RS_PITCH(0),bpl); } saa_writel(SAA7134_RS_CONTROL(0),control); if (dev->fmt->planar) { /* DMA: setup channel 4+5 (= planar task A) */ bpl_uv = bpl >> dev->fmt->hshift; lines_uv = dev->height >> dev->fmt->vshift; base2 = base + bpl * dev->height; base3 = base2 + bpl_uv * lines_uv; if (dev->fmt->uvswap) swap(base2, base3); video_dbg("uv: bpl=%ld lines=%ld base2/3=%ld/%ld\n", bpl_uv,lines_uv,base2,base3); if (V4L2_FIELD_HAS_BOTH(dev->field)) { /* interlaced */ saa_writel(SAA7134_RS_BA1(4),base2); saa_writel(SAA7134_RS_BA2(4),base2+bpl_uv); saa_writel(SAA7134_RS_PITCH(4),bpl_uv*2); saa_writel(SAA7134_RS_BA1(5),base3); saa_writel(SAA7134_RS_BA2(5),base3+bpl_uv); saa_writel(SAA7134_RS_PITCH(5),bpl_uv*2); } else { /* non-interlaced */ saa_writel(SAA7134_RS_BA1(4),base2); saa_writel(SAA7134_RS_BA2(4),base2); saa_writel(SAA7134_RS_PITCH(4),bpl_uv); saa_writel(SAA7134_RS_BA1(5),base3); saa_writel(SAA7134_RS_BA2(5),base3); saa_writel(SAA7134_RS_PITCH(5),bpl_uv); } saa_writel(SAA7134_RS_CONTROL(4),control); saa_writel(SAA7134_RS_CONTROL(5),control); } /* start DMA */ saa7134_set_dmabits(dev); mod_timer(&dmaq->timeout, jiffies + BUFFER_TIMEOUT); return 0; } static int buffer_init(struct vb2_buffer *vb2) { struct saa7134_dmaqueue *dmaq = vb2->vb2_queue->drv_priv; struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2); struct saa7134_buf *buf = container_of(vbuf, struct saa7134_buf, vb2); dmaq->curr = NULL; buf->activate = buffer_activate; return 0; } static int buffer_prepare(struct vb2_buffer *vb2) { struct saa7134_dmaqueue *dmaq = vb2->vb2_queue->drv_priv; struct saa7134_dev *dev = dmaq->dev; struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2); struct saa7134_buf *buf = container_of(vbuf, struct saa7134_buf, vb2); struct sg_table *dma = vb2_dma_sg_plane_desc(vb2, 0); unsigned int size; if (dma->sgl->offset) { pr_err("The buffer is not page-aligned\n"); return -EINVAL; } size = (dev->width * dev->height * dev->fmt->depth) >> 3; if (vb2_plane_size(vb2, 0) < size) return -EINVAL; vb2_set_plane_payload(vb2, 0, size); vbuf->field = dev->field; return saa7134_pgtable_build(dev->pci, &dmaq->pt, dma->sgl, dma->nents, saa7134_buffer_startpage(buf)); } static int queue_setup(struct vb2_queue *q, unsigned int *nbuffers, unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[]) { struct saa7134_dmaqueue *dmaq = q->drv_priv; struct saa7134_dev *dev = dmaq->dev; int size = dev->fmt->depth * dev->width * dev->height >> 3; if (dev->width < 48 || dev->height < 32 || dev->width/4 > dev->crop_current.width || dev->height/4 > dev->crop_current.height || dev->width > dev->crop_bounds.width || dev->height > dev->crop_bounds.height) return -EINVAL; *nbuffers = saa7134_buffer_count(size, *nbuffers); *nplanes = 1; sizes[0] = size; saa7134_enable_analog_tuner(dev); return 0; } /* * move buffer to hardware queue */ void saa7134_vb2_buffer_queue(struct vb2_buffer *vb) { struct saa7134_dmaqueue *dmaq = vb->vb2_queue->drv_priv; struct saa7134_dev *dev = dmaq->dev; struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct saa7134_buf *buf = container_of(vbuf, struct saa7134_buf, vb2); saa7134_buffer_queue(dev, dmaq, buf); } EXPORT_SYMBOL_GPL(saa7134_vb2_buffer_queue); int saa7134_vb2_start_streaming(struct vb2_queue *vq, unsigned int count) { struct saa7134_dmaqueue *dmaq = vq->drv_priv; struct saa7134_dev *dev = dmaq->dev; /* * Planar video capture and TS share the same DMA channel, * so only one can be active at a time. */ if (card_is_empress(dev) && vb2_is_busy(&dev->empress_vbq) && dmaq == &dev->video_q && dev->fmt->planar) { struct saa7134_buf *buf, *tmp; list_for_each_entry_safe(buf, tmp, &dmaq->queue, entry) { list_del(&buf->entry); vb2_buffer_done(&buf->vb2.vb2_buf, VB2_BUF_STATE_QUEUED); } if (dmaq->curr) { vb2_buffer_done(&dmaq->curr->vb2.vb2_buf, VB2_BUF_STATE_QUEUED); dmaq->curr = NULL; } return -EBUSY; } /* The SAA7134 has a 1K FIFO; the datasheet suggests that when * configured conservatively, there's 22 usec of buffering for video. * We therefore request a DMA latency of 20 usec, giving us 2 usec of * margin in case the FIFO is configured differently to the datasheet. * Unfortunately, I lack register-level documentation to check the * Linux FIFO setup and confirm the perfect value. */ if ((dmaq == &dev->video_q && !vb2_is_streaming(&dev->vbi_vbq)) || (dmaq == &dev->vbi_q && !vb2_is_streaming(&dev->video_vbq))) cpu_latency_qos_add_request(&dev->qos_request, 20); dmaq->seq_nr = 0; return 0; } void saa7134_vb2_stop_streaming(struct vb2_queue *vq) { struct saa7134_dmaqueue *dmaq = vq->drv_priv; struct saa7134_dev *dev = dmaq->dev; saa7134_stop_streaming(dev, dmaq); if ((dmaq == &dev->video_q && !vb2_is_streaming(&dev->vbi_vbq)) || (dmaq == &dev->vbi_q && !vb2_is_streaming(&dev->video_vbq))) cpu_latency_qos_remove_request(&dev->qos_request); } static const struct vb2_ops vb2_qops = { .queue_setup = queue_setup, .buf_init = buffer_init, .buf_prepare = buffer_prepare, .buf_queue = saa7134_vb2_buffer_queue, .start_streaming = saa7134_vb2_start_streaming, .stop_streaming = saa7134_vb2_stop_streaming, }; /* ------------------------------------------------------------------ */ static int saa7134_s_ctrl(struct v4l2_ctrl *ctrl) { struct saa7134_dev *dev = container_of(ctrl->handler, struct saa7134_dev, ctrl_handler); switch (ctrl->id) { case V4L2_CID_BRIGHTNESS: dev->ctl_bright = ctrl->val; saa_writeb(SAA7134_DEC_LUMA_BRIGHT, ctrl->val); break; case V4L2_CID_HUE: dev->ctl_hue = ctrl->val; saa_writeb(SAA7134_DEC_CHROMA_HUE, ctrl->val); break; case V4L2_CID_CONTRAST: dev->ctl_contrast = ctrl->val; saa_writeb(SAA7134_DEC_LUMA_CONTRAST, dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast); break; case V4L2_CID_SATURATION: dev->ctl_saturation = ctrl->val; saa_writeb(SAA7134_DEC_CHROMA_SATURATION, dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation); break; case V4L2_CID_AUDIO_MUTE: dev->ctl_mute = ctrl->val; saa7134_tvaudio_setmute(dev); break; case V4L2_CID_AUDIO_VOLUME: dev->ctl_volume = ctrl->val; saa7134_tvaudio_setvolume(dev,dev->ctl_volume); break; case V4L2_CID_PRIVATE_INVERT: dev->ctl_invert = ctrl->val; saa_writeb(SAA7134_DEC_LUMA_CONTRAST, dev->ctl_invert ? -dev->ctl_contrast : dev->ctl_contrast); saa_writeb(SAA7134_DEC_CHROMA_SATURATION, dev->ctl_invert ? -dev->ctl_saturation : dev->ctl_saturation); break; case V4L2_CID_HFLIP: dev->ctl_mirror = ctrl->val; break; case V4L2_CID_PRIVATE_Y_EVEN: dev->ctl_y_even = ctrl->val; break; case V4L2_CID_PRIVATE_Y_ODD: dev->ctl_y_odd = ctrl->val; break; case V4L2_CID_PRIVATE_AUTOMUTE: { struct v4l2_priv_tun_config tda9887_cfg; tda9887_cfg.tuner = TUNER_TDA9887; tda9887_cfg.priv = &dev->tda9887_conf; dev->ctl_automute = ctrl->val; if (dev->tda9887_conf) { if (dev->ctl_automute) dev->tda9887_conf |= TDA9887_AUTOMUTE; else dev->tda9887_conf &= ~TDA9887_AUTOMUTE; saa_call_all(dev, tuner, s_config, &tda9887_cfg); } break; } default: return -EINVAL; } return 0; } /* ------------------------------------------------------------------ */ static int video_open(struct file *file) { struct video_device *vdev = video_devdata(file); struct saa7134_dev *dev = video_drvdata(file); int ret = v4l2_fh_open(file); if (ret < 0) return ret; mutex_lock(&dev->lock); if (vdev->vfl_type == VFL_TYPE_RADIO) { /* switch to radio mode */ saa7134_tvaudio_setinput(dev, &card(dev).radio); saa_call_all(dev, tuner, s_radio); } else { /* switch to video/vbi mode */ video_mux(dev, dev->ctl_input); } mutex_unlock(&dev->lock); return 0; } static int video_release(struct file *file) { struct video_device *vdev = video_devdata(file); struct saa7134_dev *dev = video_drvdata(file); struct saa6588_command cmd; mutex_lock(&dev->lock); saa7134_tvaudio_close(dev); if (vdev->vfl_type == VFL_TYPE_RADIO) v4l2_fh_release(file); else _vb2_fop_release(file, NULL); /* ts-capture will not work in planar mode, so turn it off Hac: 04.05*/ saa_andorb(SAA7134_OFMT_VIDEO_A, 0x1f, 0); saa_andorb(SAA7134_OFMT_VIDEO_B, 0x1f, 0); saa_andorb(SAA7134_OFMT_DATA_A, 0x1f, 0); saa_andorb(SAA7134_OFMT_DATA_B, 0x1f, 0); saa_call_all(dev, tuner, standby); if (vdev->vfl_type == VFL_TYPE_RADIO) saa_call_all(dev, core, command, SAA6588_CMD_CLOSE, &cmd); mutex_unlock(&dev->lock); return 0; } static ssize_t radio_read(struct file *file, char __user *data, size_t count, loff_t *ppos) { struct saa7134_dev *dev = video_drvdata(file); struct saa6588_command cmd; cmd.block_count = count/3; cmd.nonblocking = file->f_flags & O_NONBLOCK; cmd.buffer = data; cmd.instance = file; cmd.result = -ENODEV; mutex_lock(&dev->lock); saa_call_all(dev, core, command, SAA6588_CMD_READ, &cmd); mutex_unlock(&dev->lock); return cmd.result; } static __poll_t radio_poll(struct file *file, poll_table *wait) { struct saa7134_dev *dev = video_drvdata(file); struct saa6588_command cmd; __poll_t rc = v4l2_ctrl_poll(file, wait); cmd.instance = file; cmd.event_list = wait; cmd.poll_mask = 0; mutex_lock(&dev->lock); saa_call_all(dev, core, command, SAA6588_CMD_POLL, &cmd); mutex_unlock(&dev->lock); return rc | cmd.poll_mask; } /* ------------------------------------------------------------------ */ static int saa7134_try_get_set_fmt_vbi_cap(struct file *file, void *priv, struct v4l2_format *f) { struct saa7134_dev *dev = video_drvdata(file); struct saa7134_tvnorm *norm = dev->tvnorm; memset(&f->fmt.vbi.reserved, 0, sizeof(f->fmt.vbi.reserved)); f->fmt.vbi.sampling_rate = 6750000 * 4; f->fmt.vbi.samples_per_line = 2048 /* VBI_LINE_LENGTH */; f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY; f->fmt.vbi.offset = 64 * 4; f->fmt.vbi.start[0] = norm->vbi_v_start_0; f->fmt.vbi.count[0] = norm->vbi_v_stop_0 - norm->vbi_v_start_0 +1; f->fmt.vbi.start[1] = norm->vbi_v_start_1; f->fmt.vbi.count[1] = f->fmt.vbi.count[0]; f->fmt.vbi.flags = 0; /* VBI_UNSYNC VBI_INTERLACED */ return 0; } static int saa7134_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct saa7134_dev *dev = video_drvdata(file); f->fmt.pix.width = dev->width; f->fmt.pix.height = dev->height; f->fmt.pix.field = dev->field; f->fmt.pix.pixelformat = dev->fmt->fourcc; if (dev->fmt->planar) f->fmt.pix.bytesperline = f->fmt.pix.width; else f->fmt.pix.bytesperline = (f->fmt.pix.width * dev->fmt->depth) / 8; f->fmt.pix.sizeimage = (f->fmt.pix.height * f->fmt.pix.width * dev->fmt->depth) / 8; f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; return 0; } static int saa7134_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct saa7134_dev *dev = video_drvdata(file); struct saa7134_format *fmt; enum v4l2_field field; unsigned int maxw, maxh; fmt = format_by_fourcc(f->fmt.pix.pixelformat); if (NULL == fmt) return -EINVAL; field = f->fmt.pix.field; maxw = min(dev->crop_current.width*4, dev->crop_bounds.width); maxh = min(dev->crop_current.height*4, dev->crop_bounds.height); if (V4L2_FIELD_ANY == field) { field = (f->fmt.pix.height > maxh/2) ? V4L2_FIELD_INTERLACED : V4L2_FIELD_BOTTOM; } switch (field) { case V4L2_FIELD_TOP: case V4L2_FIELD_BOTTOM: maxh = maxh / 2; break; default: field = V4L2_FIELD_INTERLACED; break; } f->fmt.pix.field = field; if (f->fmt.pix.width < 48) f->fmt.pix.width = 48; if (f->fmt.pix.height < 32) f->fmt.pix.height = 32; if (f->fmt.pix.width > maxw) f->fmt.pix.width = maxw; if (f->fmt.pix.height > maxh) f->fmt.pix.height = maxh; f->fmt.pix.width &= ~0x03; if (fmt->planar) f->fmt.pix.bytesperline = f->fmt.pix.width; else f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) / 8; f->fmt.pix.sizeimage = (f->fmt.pix.height * f->fmt.pix.width * fmt->depth) / 8; f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; return 0; } static int saa7134_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct saa7134_dev *dev = video_drvdata(file); int err; err = saa7134_try_fmt_vid_cap(file, priv, f); if (0 != err) return err; dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat); dev->width = f->fmt.pix.width; dev->height = f->fmt.pix.height; dev->field = f->fmt.pix.field; return 0; } int saa7134_enum_input(struct file *file, void *priv, struct v4l2_input *i) { struct saa7134_dev *dev = video_drvdata(file); unsigned int n; n = i->index; if (n >= SAA7134_INPUT_MAX) return -EINVAL; if (card_in(dev, i->index).type == SAA7134_NO_INPUT) return -EINVAL; i->index = n; strscpy(i->name, saa7134_input_name[card_in(dev, n).type], sizeof(i->name)); switch (card_in(dev, n).type) { case SAA7134_INPUT_TV: case SAA7134_INPUT_TV_MONO: i->type = V4L2_INPUT_TYPE_TUNER; break; default: i->type = V4L2_INPUT_TYPE_CAMERA; break; } if (n == dev->ctl_input) { int v1 = saa_readb(SAA7134_STATUS_VIDEO1); int v2 = saa_readb(SAA7134_STATUS_VIDEO2); if (0 != (v1 & 0x40)) i->status |= V4L2_IN_ST_NO_H_LOCK; if (0 != (v2 & 0x40)) i->status |= V4L2_IN_ST_NO_SIGNAL; if (0 != (v2 & 0x0e)) i->status |= V4L2_IN_ST_MACROVISION; } i->std = SAA7134_NORMS; return 0; } EXPORT_SYMBOL_GPL(saa7134_enum_input); int saa7134_g_input(struct file *file, void *priv, unsigned int *i) { struct saa7134_dev *dev = video_drvdata(file); *i = dev->ctl_input; return 0; } EXPORT_SYMBOL_GPL(saa7134_g_input); int saa7134_s_input(struct file *file, void *priv, unsigned int i) { struct saa7134_dev *dev = video_drvdata(file); if (i >= SAA7134_INPUT_MAX) return -EINVAL; if (card_in(dev, i).type == SAA7134_NO_INPUT) return -EINVAL; video_mux(dev, i); return 0; } EXPORT_SYMBOL_GPL(saa7134_s_input); int saa7134_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { struct saa7134_dev *dev = video_drvdata(file); strscpy(cap->driver, "saa7134", sizeof(cap->driver)); strscpy(cap->card, saa7134_boards[dev->board].name, sizeof(cap->card)); cap->capabilities = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | V4L2_CAP_RADIO | V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VBI_CAPTURE | V4L2_CAP_DEVICE_CAPS; if (dev->tuner_type != TUNER_ABSENT && dev->tuner_type != UNSET) cap->capabilities |= V4L2_CAP_TUNER; if (dev->has_rds) cap->capabilities |= V4L2_CAP_RDS_CAPTURE; return 0; } EXPORT_SYMBOL_GPL(saa7134_querycap); int saa7134_s_std(struct file *file, void *priv, v4l2_std_id id) { struct saa7134_dev *dev = video_drvdata(file); unsigned int i; v4l2_std_id fixup; for (i = 0; i < TVNORMS; i++) if (id == tvnorms[i].id) break; if (i == TVNORMS) for (i = 0; i < TVNORMS; i++) if (id & tvnorms[i].id) break; if (i == TVNORMS) return -EINVAL; if ((id & V4L2_STD_SECAM) && (secam[0] != '-')) { if (secam[0] == 'L' || secam[0] == 'l') { if (secam[1] == 'C' || secam[1] == 'c') fixup = V4L2_STD_SECAM_LC; else fixup = V4L2_STD_SECAM_L; } else { if (secam[0] == 'D' || secam[0] == 'd') fixup = V4L2_STD_SECAM_DK; else fixup = V4L2_STD_SECAM; } for (i = 0; i < TVNORMS; i++) { if (fixup == tvnorms[i].id) break; } if (i == TVNORMS) return -EINVAL; } set_tvnorm(dev, &tvnorms[i]); saa7134_tvaudio_do_scan(dev); return 0; } EXPORT_SYMBOL_GPL(saa7134_s_std); int saa7134_g_std(struct file *file, void *priv, v4l2_std_id *id) { struct saa7134_dev *dev = video_drvdata(file); *id = dev->tvnorm->id; return 0; } EXPORT_SYMBOL_GPL(saa7134_g_std); static v4l2_std_id saa7134_read_std(struct saa7134_dev *dev) { static v4l2_std_id stds[] = { V4L2_STD_UNKNOWN, V4L2_STD_NTSC, V4L2_STD_PAL, V4L2_STD_SECAM }; v4l2_std_id result = 0; u8 st1 = saa_readb(SAA7134_STATUS_VIDEO1); u8 st2 = saa_readb(SAA7134_STATUS_VIDEO2); if (!(st2 & 0x1)) /* RDCAP == 0 */ result = V4L2_STD_UNKNOWN; else result = stds[st1 & 0x03]; return result; } int saa7134_querystd(struct file *file, void *priv, v4l2_std_id *std) { struct saa7134_dev *dev = video_drvdata(file); *std &= saa7134_read_std(dev); return 0; } EXPORT_SYMBOL_GPL(saa7134_querystd); static int saa7134_g_pixelaspect(struct file *file, void *priv, int type, struct v4l2_fract *f) { struct saa7134_dev *dev = video_drvdata(file); if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; if (dev->tvnorm->id & V4L2_STD_525_60) { f->numerator = 11; f->denominator = 10; } if (dev->tvnorm->id & V4L2_STD_625_50) { f->numerator = 54; f->denominator = 59; } return 0; } static int saa7134_g_selection(struct file *file, void *f, struct v4l2_selection *sel) { struct saa7134_dev *dev = video_drvdata(file); if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; switch (sel->target) { case V4L2_SEL_TGT_CROP: sel->r = dev->crop_current; break; case V4L2_SEL_TGT_CROP_DEFAULT: sel->r = dev->crop_defrect; break; case V4L2_SEL_TGT_CROP_BOUNDS: sel->r = dev->crop_bounds; break; default: return -EINVAL; } return 0; } static int saa7134_s_selection(struct file *file, void *f, struct v4l2_selection *sel) { struct saa7134_dev *dev = video_drvdata(file); struct v4l2_rect *b = &dev->crop_bounds; struct v4l2_rect *c = &dev->crop_current; if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; if (sel->target != V4L2_SEL_TGT_CROP) return -EINVAL; if (vb2_is_streaming(&dev->video_vbq)) return -EBUSY; *c = sel->r; if (c->top < b->top) c->top = b->top; if (c->top > b->top + b->height) c->top = b->top + b->height; if (c->height > b->top - c->top + b->height) c->height = b->top - c->top + b->height; if (c->left < b->left) c->left = b->left; if (c->left > b->left + b->width) c->left = b->left + b->width; if (c->width > b->left - c->left + b->width) c->width = b->left - c->left + b->width; sel->r = *c; return 0; } int saa7134_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t) { struct saa7134_dev *dev = video_drvdata(file); int n; if (0 != t->index) return -EINVAL; memset(t, 0, sizeof(*t)); for (n = 0; n < SAA7134_INPUT_MAX; n++) { if (card_in(dev, n).type == SAA7134_INPUT_TV || card_in(dev, n).type == SAA7134_INPUT_TV_MONO) break; } if (n == SAA7134_INPUT_MAX) return -EINVAL; if (card_in(dev, n).type != SAA7134_NO_INPUT) { strscpy(t->name, "Television", sizeof(t->name)); t->type = V4L2_TUNER_ANALOG_TV; saa_call_all(dev, tuner, g_tuner, t); t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2; t->rxsubchans = saa7134_tvaudio_getstereo(dev); t->audmode = saa7134_tvaudio_rx2mode(t->rxsubchans); } if (0 != (saa_readb(SAA7134_STATUS_VIDEO1) & 0x03)) t->signal = 0xffff; return 0; } EXPORT_SYMBOL_GPL(saa7134_g_tuner); int saa7134_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *t) { struct saa7134_dev *dev = video_drvdata(file); int rx, mode; if (0 != t->index) return -EINVAL; mode = dev->thread.mode; if (UNSET == mode) { rx = saa7134_tvaudio_getstereo(dev); mode = saa7134_tvaudio_rx2mode(rx); } if (mode != t->audmode) dev->thread.mode = t->audmode; return 0; } EXPORT_SYMBOL_GPL(saa7134_s_tuner); int saa7134_g_frequency(struct file *file, void *priv, struct v4l2_frequency *f) { struct saa7134_dev *dev = video_drvdata(file); if (0 != f->tuner) return -EINVAL; saa_call_all(dev, tuner, g_frequency, f); return 0; } EXPORT_SYMBOL_GPL(saa7134_g_frequency); int saa7134_s_frequency(struct file *file, void *priv, const struct v4l2_frequency *f) { struct saa7134_dev *dev = video_drvdata(file); if (0 != f->tuner) return -EINVAL; saa_call_all(dev, tuner, s_frequency, f); saa7134_tvaudio_do_scan(dev); return 0; } EXPORT_SYMBOL_GPL(saa7134_s_frequency); static int saa7134_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) { if (f->index >= FORMATS) return -EINVAL; f->pixelformat = formats[f->index].fourcc; return 0; } #ifdef CONFIG_VIDEO_ADV_DEBUG static int vidioc_g_register (struct file *file, void *priv, struct v4l2_dbg_register *reg) { struct saa7134_dev *dev = video_drvdata(file); reg->val = saa_readb(reg->reg & 0xffffff); reg->size = 1; return 0; } static int vidioc_s_register (struct file *file, void *priv, const struct v4l2_dbg_register *reg) { struct saa7134_dev *dev = video_drvdata(file); saa_writeb(reg->reg & 0xffffff, reg->val); return 0; } #endif static int radio_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t) { struct saa7134_dev *dev = video_drvdata(file); if (0 != t->index) return -EINVAL; strscpy(t->name, "Radio", sizeof(t->name)); saa_call_all(dev, tuner, g_tuner, t); t->audmode &= V4L2_TUNER_MODE_MONO | V4L2_TUNER_MODE_STEREO; if (dev->input->amux == TV) { t->signal = 0xf800 - ((saa_readb(0x581) & 0x1f) << 11); t->rxsubchans = (saa_readb(0x529) & 0x08) ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; } return 0; } static int radio_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *t) { struct saa7134_dev *dev = video_drvdata(file); if (0 != t->index) return -EINVAL; saa_call_all(dev, tuner, s_tuner, t); return 0; } static const struct v4l2_file_operations video_fops = { .owner = THIS_MODULE, .open = video_open, .release = video_release, .read = vb2_fop_read, .poll = vb2_fop_poll, .mmap = vb2_fop_mmap, .unlocked_ioctl = video_ioctl2, }; static const struct v4l2_ioctl_ops video_ioctl_ops = { .vidioc_querycap = saa7134_querycap, .vidioc_enum_fmt_vid_cap = saa7134_enum_fmt_vid_cap, .vidioc_g_fmt_vid_cap = saa7134_g_fmt_vid_cap, .vidioc_try_fmt_vid_cap = saa7134_try_fmt_vid_cap, .vidioc_s_fmt_vid_cap = saa7134_s_fmt_vid_cap, .vidioc_g_fmt_vbi_cap = saa7134_try_get_set_fmt_vbi_cap, .vidioc_try_fmt_vbi_cap = saa7134_try_get_set_fmt_vbi_cap, .vidioc_s_fmt_vbi_cap = saa7134_try_get_set_fmt_vbi_cap, .vidioc_g_pixelaspect = saa7134_g_pixelaspect, .vidioc_reqbufs = vb2_ioctl_reqbufs, .vidioc_querybuf = vb2_ioctl_querybuf, .vidioc_qbuf = vb2_ioctl_qbuf, .vidioc_dqbuf = vb2_ioctl_dqbuf, .vidioc_expbuf = vb2_ioctl_expbuf, .vidioc_s_std = saa7134_s_std, .vidioc_g_std = saa7134_g_std, .vidioc_querystd = saa7134_querystd, .vidioc_enum_input = saa7134_enum_input, .vidioc_g_input = saa7134_g_input, .vidioc_s_input = saa7134_s_input, .vidioc_streamon = vb2_ioctl_streamon, .vidioc_streamoff = vb2_ioctl_streamoff, .vidioc_g_tuner = saa7134_g_tuner, .vidioc_s_tuner = saa7134_s_tuner, .vidioc_g_selection = saa7134_g_selection, .vidioc_s_selection = saa7134_s_selection, .vidioc_g_frequency = saa7134_g_frequency, .vidioc_s_frequency = saa7134_s_frequency, #ifdef CONFIG_VIDEO_ADV_DEBUG .vidioc_g_register = vidioc_g_register, .vidioc_s_register = vidioc_s_register, #endif .vidioc_log_status = v4l2_ctrl_log_status, .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; static const struct v4l2_file_operations radio_fops = { .owner = THIS_MODULE, .open = video_open, .read = radio_read, .release = video_release, .unlocked_ioctl = video_ioctl2, .poll = radio_poll, }; static const struct v4l2_ioctl_ops radio_ioctl_ops = { .vidioc_querycap = saa7134_querycap, .vidioc_g_tuner = radio_g_tuner, .vidioc_s_tuner = radio_s_tuner, .vidioc_g_frequency = saa7134_g_frequency, .vidioc_s_frequency = saa7134_s_frequency, .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; /* ----------------------------------------------------------- */ /* exported stuff */ struct video_device saa7134_video_template = { .name = "saa7134-video", .fops = &video_fops, .ioctl_ops = &video_ioctl_ops, .tvnorms = SAA7134_NORMS, }; struct video_device saa7134_radio_template = { .name = "saa7134-radio", .fops = &radio_fops, .ioctl_ops = &radio_ioctl_ops, }; static const struct v4l2_ctrl_ops saa7134_ctrl_ops = { .s_ctrl = saa7134_s_ctrl, }; static const struct v4l2_ctrl_config saa7134_ctrl_invert = { .ops = &saa7134_ctrl_ops, .id = V4L2_CID_PRIVATE_INVERT, .name = "Invert", .type = V4L2_CTRL_TYPE_BOOLEAN, .min = 0, .max = 1, .step = 1, }; static const struct v4l2_ctrl_config saa7134_ctrl_y_odd = { .ops = &saa7134_ctrl_ops, .id = V4L2_CID_PRIVATE_Y_ODD, .name = "Y Offset Odd Field", .type = V4L2_CTRL_TYPE_INTEGER, .min = 0, .max = 128, .step = 1, }; static const struct v4l2_ctrl_config saa7134_ctrl_y_even = { .ops = &saa7134_ctrl_ops, .id = V4L2_CID_PRIVATE_Y_EVEN, .name = "Y Offset Even Field", .type = V4L2_CTRL_TYPE_INTEGER, .min = 0, .max = 128, .step = 1, }; static const struct v4l2_ctrl_config saa7134_ctrl_automute = { .ops = &saa7134_ctrl_ops, .id = V4L2_CID_PRIVATE_AUTOMUTE, .name = "Automute", .type = V4L2_CTRL_TYPE_BOOLEAN, .min = 0, .max = 1, .step = 1, .def = 1, }; int saa7134_video_init1(struct saa7134_dev *dev) { struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler; struct vb2_queue *q; int ret; /* sanitycheck insmod options */ if (gbuffers < 2 || gbuffers > VIDEO_MAX_FRAME) gbuffers = 2; if (gbufsize > gbufsize_max) gbufsize = gbufsize_max; gbufsize = (gbufsize + PAGE_SIZE - 1) & PAGE_MASK; v4l2_ctrl_handler_init(hdl, 11); v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, V4L2_CID_CONTRAST, 0, 127, 1, 68); v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, V4L2_CID_SATURATION, 0, 127, 1, 64); v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, V4L2_CID_HUE, -128, 127, 1, 0); v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); v4l2_ctrl_new_std(hdl, &saa7134_ctrl_ops, V4L2_CID_AUDIO_VOLUME, -15, 15, 1, 0); v4l2_ctrl_new_custom(hdl, &saa7134_ctrl_invert, NULL); v4l2_ctrl_new_custom(hdl, &saa7134_ctrl_y_odd, NULL); v4l2_ctrl_new_custom(hdl, &saa7134_ctrl_y_even, NULL); v4l2_ctrl_new_custom(hdl, &saa7134_ctrl_automute, NULL); if (hdl->error) return hdl->error; if (card_has_radio(dev)) { hdl = &dev->radio_ctrl_handler; v4l2_ctrl_handler_init(hdl, 2); v4l2_ctrl_add_handler(hdl, &dev->ctrl_handler, v4l2_ctrl_radio_filter, false); if (hdl->error) return hdl->error; } dev->ctl_mute = 1; if (dev->tda9887_conf && saa7134_ctrl_automute.def) dev->tda9887_conf |= TDA9887_AUTOMUTE; dev->automute = 0; INIT_LIST_HEAD(&dev->video_q.queue); timer_setup(&dev->video_q.timeout, saa7134_buffer_timeout, 0); dev->video_q.dev = dev; dev->fmt = format_by_fourcc(V4L2_PIX_FMT_BGR24); dev->width = 720; dev->height = 576; dev->field = V4L2_FIELD_INTERLACED; if (saa7134_boards[dev->board].video_out) saa7134_videoport_init(dev); q = &dev->video_vbq; q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; /* * Do not add VB2_USERPTR unless explicitly requested: the saa7134 DMA * engine cannot handle transfers that do not start at the beginning * of a page. A user-provided pointer can start anywhere in a page, so * USERPTR support is a no-go unless the application knows about these * limitations and has special support for this. */ q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ; if (saa7134_userptr) q->io_modes |= VB2_USERPTR; q->drv_priv = &dev->video_q; q->ops = &vb2_qops; q->gfp_flags = GFP_DMA32; q->mem_ops = &vb2_dma_sg_memops; q->buf_struct_size = sizeof(struct saa7134_buf); q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; q->lock = &dev->lock; q->dev = &dev->pci->dev; ret = vb2_queue_init(q); if (ret) return ret; saa7134_pgtable_alloc(dev->pci, &dev->video_q.pt); q = &dev->vbi_vbq; q->type = V4L2_BUF_TYPE_VBI_CAPTURE; /* Don't add VB2_USERPTR, see comment above */ q->io_modes = VB2_MMAP | VB2_READ; if (saa7134_userptr) q->io_modes |= VB2_USERPTR; q->drv_priv = &dev->vbi_q; q->ops = &saa7134_vbi_qops; q->gfp_flags = GFP_DMA32; q->mem_ops = &vb2_dma_sg_memops; q->buf_struct_size = sizeof(struct saa7134_buf); q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; q->lock = &dev->lock; q->dev = &dev->pci->dev; ret = vb2_queue_init(q); if (ret) return ret; saa7134_pgtable_alloc(dev->pci, &dev->vbi_q.pt); return 0; } void saa7134_video_fini(struct saa7134_dev *dev) { del_timer_sync(&dev->video_q.timeout); /* free stuff */ saa7134_pgtable_free(dev->pci, &dev->video_q.pt); saa7134_pgtable_free(dev->pci, &dev->vbi_q.pt); v4l2_ctrl_handler_free(&dev->ctrl_handler); if (card_has_radio(dev)) v4l2_ctrl_handler_free(&dev->radio_ctrl_handler); } int saa7134_videoport_init(struct saa7134_dev *dev) { /* enable video output */ int vo = saa7134_boards[dev->board].video_out; int video_reg; unsigned int vid_port_opts = saa7134_boards[dev->board].vid_port_opts; /* Configure videoport */ saa_writeb(SAA7134_VIDEO_PORT_CTRL0, video_out[vo][0]); video_reg = video_out[vo][1]; if (vid_port_opts & SET_T_CODE_POLARITY_NON_INVERTED) video_reg &= ~VP_T_CODE_P_INVERTED; saa_writeb(SAA7134_VIDEO_PORT_CTRL1, video_reg); saa_writeb(SAA7134_VIDEO_PORT_CTRL2, video_out[vo][2]); saa_writeb(SAA7134_VIDEO_PORT_CTRL4, video_out[vo][4]); video_reg = video_out[vo][5]; if (vid_port_opts & SET_CLOCK_NOT_DELAYED) video_reg &= ~VP_CLK_CTRL2_DELAYED; if (vid_port_opts & SET_CLOCK_INVERTED) video_reg |= VP_CLK_CTRL1_INVERTED; saa_writeb(SAA7134_VIDEO_PORT_CTRL5, video_reg); video_reg = video_out[vo][6]; if (vid_port_opts & SET_VSYNC_OFF) { video_reg &= ~VP_VS_TYPE_MASK; video_reg |= VP_VS_TYPE_OFF; } saa_writeb(SAA7134_VIDEO_PORT_CTRL6, video_reg); saa_writeb(SAA7134_VIDEO_PORT_CTRL7, video_out[vo][7]); saa_writeb(SAA7134_VIDEO_PORT_CTRL8, video_out[vo][8]); /* Start videoport */ saa_writeb(SAA7134_VIDEO_PORT_CTRL3, video_out[vo][3]); return 0; } int saa7134_video_init2(struct saa7134_dev *dev) { /* init video hw */ set_tvnorm(dev,&tvnorms[0]); video_mux(dev,0); v4l2_ctrl_handler_setup(&dev->ctrl_handler); saa7134_tvaudio_setmute(dev); saa7134_tvaudio_setvolume(dev,dev->ctl_volume); return 0; } void saa7134_irq_video_signalchange(struct saa7134_dev *dev) { static const char *st[] = { "(no signal)", "NTSC", "PAL", "SECAM" }; u32 st1,st2; st1 = saa_readb(SAA7134_STATUS_VIDEO1); st2 = saa_readb(SAA7134_STATUS_VIDEO2); video_dbg("DCSDT: pll: %s, sync: %s, norm: %s\n", (st1 & 0x40) ? "not locked" : "locked", (st2 & 0x40) ? "no" : "yes", st[st1 & 0x03]); dev->nosignal = (st1 & 0x40) || (st2 & 0x40) || !(st2 & 0x1); if (dev->nosignal) { /* no video signal -> mute audio */ if (dev->ctl_automute) dev->automute = 1; saa7134_tvaudio_setmute(dev); } else { /* wake up tvaudio audio carrier scan thread */ saa7134_tvaudio_do_scan(dev); } if ((st2 & 0x80) && !noninterlaced && !dev->nosignal) saa_clearb(SAA7134_SYNC_CTRL, 0x20); else saa_setb(SAA7134_SYNC_CTRL, 0x20); if (dev->mops && dev->mops->signal_change) dev->mops->signal_change(dev); } void saa7134_irq_video_done(struct saa7134_dev *dev, unsigned long status) { enum v4l2_field field; spin_lock(&dev->slock); if (dev->video_q.curr) { field = dev->field; if (V4L2_FIELD_HAS_BOTH(field)) { /* make sure we have seen both fields */ if ((status & 0x10) == 0x00) { dev->video_q.curr->top_seen = 1; goto done; } if (!dev->video_q.curr->top_seen) goto done; } else if (field == V4L2_FIELD_TOP) { if ((status & 0x10) != 0x10) goto done; } else if (field == V4L2_FIELD_BOTTOM) { if ((status & 0x10) != 0x00) goto done; } saa7134_buffer_finish(dev, &dev->video_q, VB2_BUF_STATE_DONE); } saa7134_buffer_next(dev, &dev->video_q); done: spin_unlock(&dev->slock); }