/** VIDIX driver for Hauppauge PVR 350. Copyright 2007 Lutz Koschorreck. Based on genfb_vid.c and ivtv_xv.c This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 09.05.2007 Lutz Koschorreck First version: Tested with ivtv-0.10.1, xine-ui-0.99.5, xine-lib-1.1.6 20.05.2007 Lutz Koschorreck Some Scaling and zooming problems fixed. By default the vidix driver now controlls the setting of alphablending. So there is no need to use ivtvfbctl anymore. To disable this feature set the following environment variable:VIDIXIVTVALPHA=disable. Special thanx to Ian Armstrong. 23.07.2007 Lutz Koschorreck Support for 2.6.22 kernel added. PCI scan added. 07.10.2007 Lutz Koschorreck Restore old alpha value correctly. Fix capability struct values. **/ #include #include #include #include #include #include #include #include #include #include #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22) #include #endif #include #include #include "vidix.h" #include "vidixlib.h" #include "fourcc.h" #include "dha.h" #include "pci_ids.h" #include "pci_names.h" #define VIDIX_STATIC ivtv_ #define IVTV_MSG "[ivtv-vid] " #define MAXLINE 128 #define IVTVMAXWIDTH 720 #define IVTVMAXHEIGHT 576 static int fbdev = -1; static int yuvdev = -1; static void *memBase = NULL; static int frameSize = 0; static int probed = 0; static int ivtv_verbose; static vidix_rect_t destVideo; static vidix_rect_t srcVideo; static unsigned char *outbuf = NULL; double fb_width; double fb_height; #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) static struct ivtvfb_ioctl_state_info fb_state_old; static struct ivtvfb_ioctl_state_info fb_state_hide; #else struct v4l2_format format_old, format_hide; #endif int alpha_disable = 0; /* VIDIX exports */ static vidix_capability_t ivtv_cap = { "Hauppauge PVR 350 YUV Video", "Lutz Koschorreck", TYPE_OUTPUT, { 0, 0, 0, 0 }, IVTVMAXWIDTH, IVTVMAXHEIGHT, 4, 4, -1, FLAG_UPSCALER|FLAG_DOWNSCALER, -1, -1, { 0, 0, 0, 0 } }; static void de_macro_y(unsigned char *src, unsigned char *dst, unsigned int w, unsigned int h, int src_x, int src_y, int height __attribute__ ((unused)), int width) { unsigned int x, y, i; unsigned char *dst_2; unsigned int h_tail, w_tail; unsigned int h_size, w_size; // Always round the origin, but compensate by increasing the size if (src_x & 15) { w += src_x & 15; src_x &= ~15; } if (src_y & 15) { h += src_y & 15; src_y &= ~15; } // The right / bottom edge might not be a multiple of 16 h_tail = h & 15; w_tail = w & 15; // One block is 16 pixels high h_size = 16; // descramble Y plane for (y = 0; y < h; y += 16) { // Clip if we've reached the bottom & the size isn't a multiple of 16 if (y + 16 > h) h_size = h_tail; for (x = 0; x < w; x += 16) { if (x + 16 > w) w_size = w_tail; else w_size = 16; dst_2 = dst + (720 * y) + (720 * src_y) + (256 * (src_x>>4)) + (x * 16); for (i = 0; i < h_size; i++) { memcpy(dst_2, src + src_x + x + (y + i) * width + (src_y * width), w_size); dst_2 += 16; } } } } static void de_macro_uv(unsigned char *srcu, unsigned char *srcv, unsigned char *dst, unsigned int w, unsigned int h, int src_x, int src_y, int height, int width) { unsigned int x, y, i, f; unsigned char *dst_2; unsigned int h_tail, w_tail; unsigned int h_size; // The uv plane is half the size of the y plane, so 'correct' all dimensions. w /= 2; h /= 2; src_x /= 2; src_y /= 2; height /= 2; width /= 2; // Always round the origin, but compensate by increasing the size if (src_x & 7) { w += src_x & 7; src_x &= ~7; } if (src_y & 15) { h += src_y & 15; src_y &= ~15; } // The right / bottom edge may not be a multiple of 16 h_tail = h & 15; w_tail = w & 7; h_size = 16; // descramble U/V plane for (y = 0; y < h; y += 16) { if ( y + 16 > h ) h_size = h_tail; for (x = 0; x < w; x += 8) { dst_2 = dst + (720 * y) + (720 * src_y) + (256 * (src_x>>3)) + (x * 32); if (x + 8 <= w) { for (i = 0; i < h_size; i++) { int idx = src_x + x + ((y + i) * width) + (src_y * width); dst_2[0] = srcu[idx + 0]; dst_2[1] = srcv[idx + 0]; dst_2[2] = srcu[idx + 1]; dst_2[3] = srcv[idx + 1]; dst_2[4] = srcu[idx + 2]; dst_2[5] = srcv[idx + 2]; dst_2[6] = srcu[idx + 3]; dst_2[7] = srcv[idx + 3]; dst_2[8] = srcu[idx + 4]; dst_2[9] = srcv[idx + 4]; dst_2[10] = srcu[idx + 5]; dst_2[11] = srcv[idx + 5]; dst_2[12] = srcu[idx + 6]; dst_2[13] = srcv[idx + 6]; dst_2[14] = srcu[idx + 7]; dst_2[15] = srcv[idx + 7]; dst_2 += 16; } } else { for (i = 0; i < h_size; i ++) { int idx = src_x + x + ((y + i) * width) + (src_y * width); for (f = 0; f < w_tail; f++) { dst_2[0] = srcu[idx + f]; dst_2[1] = srcv[idx + f]; dst_2 += 2; } /* // Used for testing edge cutoff. Sets colour to Green for (f = w_tail;f < 8;f ++) { dst_2[0] = 0; dst_2[1] = 0; dst_2 += 2; } */ dst_2 += 16 - (w_tail << 1); } } } } } int ivtv_probe(int verbose,int force __attribute__ ((unused))) { unsigned char fb_number = 0; char *device_name = NULL; char *alpha = NULL; struct fb_var_screeninfo vinfo; char fb_dev_name[] = "/dev/fb0\0"; pciinfo_t lst[MAX_PCI_DEVICES]; int err = 0; unsigned int i, num_pci = 0; if(verbose) printf(IVTV_MSG"probe\n"); ivtv_verbose = verbose; err = pci_scan(lst, &num_pci); if(err) { printf(IVTV_MSG"Error occured during pci scan: %s\n", strerror(err)); return err; } if(ivtv_verbose) printf(IVTV_MSG"Found %d pci devices\n", num_pci); for(i = 0; i < num_pci; i++) { if(2 == ivtv_verbose) printf(IVTV_MSG"Found chip [%04X:%04X] '%s' '%s'\n" ,lst[i].vendor ,lst[i].device ,pci_vendor_name(lst[i].vendor) ,pci_device_name(lst[i].vendor,lst[i].device)); if(VENDOR_INTERNEXT == lst[i].vendor) { switch(lst[i].device) { case DEVICE_INTERNEXT_ITVC15_MPEG_2_ENCODER: if(ivtv_verbose) printf(IVTV_MSG"Found PVR 350\n"); goto card_found; } } } if(ivtv_verbose) printf(IVTV_MSG"Can't find chip\n"); return(ENXIO); card_found: device_name = getenv("FRAMEBUFFER"); if(NULL == device_name) { device_name = fb_dev_name; } fb_number = atoi(device_name+strlen("/dev/fb")); fbdev = open(device_name, O_RDWR); if(-1 != fbdev) { if(ioctl(fbdev, FBIOGET_VSCREENINFO, &vinfo) < 0) { printf(IVTV_MSG"Unable to read screen info\n"); close(fbdev); return(ENXIO); } else { fb_width = vinfo.xres; fb_height = vinfo.yres; if(2 == ivtv_verbose) { printf(IVTV_MSG"framebuffer width : %3.0f\n",fb_width); printf(IVTV_MSG"framebuffer height: %3.0f\n",fb_height); } } if(NULL != (alpha = getenv("VIDIXIVTVALPHA"))) { if(0 == strcmp(alpha, "disable")) { alpha_disable = 1; } } } else { printf(IVTV_MSG"Failed to open /dev/fb%u\n", fb_number); return(ENXIO); } /* Try to find YUV device */ unsigned char yuv_device_number = 48, yuv_device = 48 + fb_number; char yuv_device_name[] = "/dev/videoXXX\0"; do { sprintf(yuv_device_name, "/dev/video%u", yuv_device); yuvdev = open(yuv_device_name, O_RDWR); if(-1 != yuvdev) { if(ivtv_verbose) printf(IVTV_MSG"YUV device found /dev/video%u\n", yuv_device); goto yuv_found; } else { if(ivtv_verbose) printf(IVTV_MSG"YUV device not found: /dev/video%u\n", yuv_device); } } while(yuv_device-- > yuv_device_number); return(ENXIO); yuv_found: if(0 == alpha_disable) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) if(ioctl(fbdev, IVTVFB_IOCTL_GET_STATE, &fb_state_old) < 0) { printf(IVTV_MSG"Unable to read fb state\n"); close(yuvdev); close(fbdev); return(ENXIO); } else { if(ivtv_verbose) { printf(IVTV_MSG"old alpha : %ld\n",fb_state_old.alpha); printf(IVTV_MSG"old status: 0x%lx\n",fb_state_old.status); } fb_state_hide.alpha = 0; fb_state_hide.status = fb_state_old.status | IVTVFB_STATUS_GLOBAL_ALPHA; } #else memset(&format_old, 0, sizeof(format_old)); format_old.type = format_hide.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY; if(ioctl(yuvdev, VIDIOC_G_FMT , &format_old) < 0) { printf(IVTV_MSG"Unable to read fb state\n"); close(yuvdev); close(fbdev); return(ENXIO); } else { if(ivtv_verbose) { printf(IVTV_MSG"old alpha : %d\n",format_old.fmt.win.global_alpha); } memcpy(&format_hide, &format_old, sizeof(format_old)); format_hide.fmt.win.global_alpha = 0; } #endif } probed = 1; return(0); } int ivtv_init(const char *args __attribute__ ((unused))) { if(ivtv_verbose) printf(IVTV_MSG"init\n"); if (!probed) { if(ivtv_verbose) printf(IVTV_MSG"Driver was not probed but is being initialized\n"); return(EINTR); } outbuf = malloc((IVTVMAXHEIGHT * IVTVMAXWIDTH) + (IVTVMAXHEIGHT * IVTVMAXWIDTH / 2)); if(NULL == outbuf) { if(ivtv_verbose) printf(IVTV_MSG"Not enough memory availabe!\n"); return(EINTR); } return(0); } void ivtv_destroy(void) { if(ivtv_verbose) printf(IVTV_MSG"destroy\n"); if(-1 != yuvdev) close(yuvdev); if(-1 != fbdev) close(fbdev); if(NULL != outbuf) free(outbuf); if(NULL != memBase) free(memBase); } int ivtv_get_caps(vidix_capability_t *to) { if(ivtv_verbose) printf(IVTV_MSG"GetCap\n"); memcpy(to, &ivtv_cap, sizeof(vidix_capability_t)); return(0); } int ivtv_query_fourcc(vidix_fourcc_t *to) { if(ivtv_verbose) printf(IVTV_MSG"query fourcc (%x)\n", to->fourcc); int supports = 0; switch(to->fourcc) { case IMGFMT_YV12: supports = 1; break; default: supports = 0; } if(!supports) { to->depth = to->flags = 0; return(ENOTSUP); } to->depth = VID_DEPTH_12BPP | VID_DEPTH_15BPP | VID_DEPTH_16BPP | VID_DEPTH_24BPP | VID_DEPTH_32BPP; to->flags = 0; return(0); } int ivtv_config_playback(vidix_playback_t *info) { if(ivtv_verbose) printf(IVTV_MSG"config playback\n"); if(2 == ivtv_verbose){ printf(IVTV_MSG"src : x:%d y:%d w:%d h:%d\n", info->src.x, info->src.y, info->src.w, info->src.h); printf(IVTV_MSG"dest: x:%d y:%d w:%d h:%d\n", info->dest.x, info->dest.y, info->dest.w, info->dest.h); } memcpy(&destVideo, &info->dest, sizeof(vidix_rect_t)); memcpy(&srcVideo, &info->src, sizeof(vidix_rect_t)); info->num_frames = 2; info->frame_size = frameSize = info->src.w*info->src.h+(info->src.w*info->src.h)/2; info->dest.pitch.y = 1; info->dest.pitch.u = info->dest.pitch.v = 2; info->offsets[0] = 0; info->offsets[1] = info->frame_size; info->offset.y = 0; info->offset.u = info->src.w * info->src.h; info->offset.v = info->offset.u + ((info->src.w * info->src.h)/4); info->dga_addr = memBase = malloc(info->num_frames*info->frame_size); if(ivtv_verbose) printf(IVTV_MSG"frame_size: %d, dga_addr: %p\n", info->frame_size, info->dga_addr); return(0); } int ivtv_playback_on(void) { if(ivtv_verbose) printf(IVTV_MSG"playback on\n"); if(0 == alpha_disable) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) if (-1 != fbdev) { if (ioctl(fbdev, IVTVFB_IOCTL_SET_STATE, &fb_state_hide) < 0) printf (IVTV_MSG"Failed to set fb state\n"); } #else if (-1 != yuvdev) { if (ioctl(yuvdev, VIDIOC_S_FMT, &format_hide) < 0) printf (IVTV_MSG"Failed to set fb state\n"); } #endif } return(0); } int ivtv_playback_off(void) { if(ivtv_verbose) printf(IVTV_MSG"playback off\n"); if(0 == alpha_disable) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) if (-1 != fbdev) { if (ioctl(fbdev, IVTVFB_IOCTL_SET_STATE, &fb_state_old) < 0) printf (IVTV_MSG"Failed to restore fb state\n"); } #else if (-1 != yuvdev) { if (ioctl(yuvdev, VIDIOC_S_FMT, &format_old) < 0) printf (IVTV_MSG"Failed to restore fb state\n"); } #endif } return(0); } int ivtv_frame_sel(unsigned int frame) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) struct ivtvyuv_ioctl_dma_host_to_ivtv_args args; #else struct ivtv_dma_frame args; #endif unsigned char *curMemBase = (unsigned char *)memBase + (frame * frameSize); de_macro_y(curMemBase, outbuf, srcVideo.w, srcVideo.h, srcVideo.x, srcVideo.y, srcVideo.h, srcVideo.w); de_macro_uv(curMemBase + (srcVideo.w * srcVideo.h) + srcVideo.w * srcVideo.h / 4, curMemBase + (srcVideo.w * srcVideo.h), outbuf + (IVTVMAXHEIGHT * IVTVMAXWIDTH), srcVideo.w, srcVideo.h, srcVideo.x, srcVideo.y, srcVideo.h, srcVideo.w); args.y_source = outbuf; args.uv_source = outbuf + (IVTVMAXHEIGHT * IVTVMAXWIDTH); #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) args.src_x = srcVideo.x; args.src_y = srcVideo.y; args.dst_x = destVideo.x; args.dst_y = destVideo.y; args.src_w = srcVideo.w; args.dst_w = destVideo.w; args.srcBuf_width = srcVideo.w; args.src_h = srcVideo.h; args.dst_h = destVideo.h; args.srcBuf_height = srcVideo.h; args.yuv_type = 0; #else args.src.left = srcVideo.x; args.src.top = srcVideo.y; args.dst.left = destVideo.x; args.dst.top = destVideo.y; args.src.width = srcVideo.w; args.dst.width = destVideo.w; args.src_width = srcVideo.w; args.src.height = srcVideo.h; args.dst.height = destVideo.h; args.src_height = srcVideo.h; args.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) if(ioctl(yuvdev, IVTV_IOC_PREP_FRAME_YUV, &args) == -1) { #else if(ioctl(yuvdev, IVTV_IOC_DMA_FRAME, &args) == -1) { #endif printf("Ioctl IVTV_IOC_DMA_FRAME returned failed Error\n"); } return(0); } VDXDriver ivtv_drv = { "ivtv", NULL, .probe = ivtv_probe, .get_caps = ivtv_get_caps, .query_fourcc = ivtv_query_fourcc, .init = ivtv_init, .destroy = ivtv_destroy, .config_playback = ivtv_config_playback, .playback_on = ivtv_playback_on, .playback_off = ivtv_playback_off, .frame_sel = ivtv_frame_sel, };