/*
 * Set the framebuffer parameters for bttv.
 *   tries to ask the X-Server if $DISPLAY is set,
 *   otherwise it checks /dev/fb0
 *
 *  (c) 1998 Gerd Knorr <kraxel@goldbach.in-berlin.de>
 *
 *  Security checks by okir@caldera.de
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <linux/vt.h>
#include <linux/fb.h>

#include "config.h"
#include "videodev.h"

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#ifdef HAVE_LIBXXF86DGA
# include <X11/extensions/xf86dga.h>
#endif

#ifndef major
#define major(dev)  (((dev) >> 8) & 0xff)
#endif

#ifdef __powerpc__

/* this is required for MkLinux */
struct vc_mode {
    int     height;
    int     width;
    int     depth;
    int     pitch;
    int     mode;
    char    name[32];
    unsigned long fb_address;
    unsigned long cmap_adr_address;
    unsigned long cmap_data_address;
    unsigned long disp_reg_address;
};

#define VC_GETMODE      0x7667
#define VC_SETMODE      0x7668
#define VC_INQMODE      0x7669

#define VC_SETCMAP      0x766a
#define VC_GETCMAP      0x766b

int
is_mklinux()
{
    int fd;
    if(-1 == (fd = open("/proc/osfmach3", O_RDONLY)))
	return 0;
    close(fd);
    return 1;
}
#endif

int    verbose = 1;
int    bpp     = 0;
int    shift   = 0;
char  *display = NULL;
char  *device  = "/dev/video";
char  *fbdev;

static int
dev_open(const char *device, int major)
{
    struct stat stb;
    int		fd;

#if 0
    if (!strncmp(device, "/dev/", 5) || strchr(device + 5, '/')) {
        fprintf(stderr, "warning: %s is not a /dev file\n", device);
        exit(1);
    }
#endif

    /* open & check v4l device */
    if (-1 == (fd = open(device,O_RDWR))) {
	fprintf(stderr, "can't open %s: %s\n", device, strerror(errno));
	exit(1);
    }

    if (-1 == fstat(fd,&stb)) {
	fprintf(stderr, "fstat(%s): %s\n", device, strerror(errno));
	exit(1);
    }
    if (!S_ISCHR(stb.st_mode) || (major(stb.st_rdev) != major)) {
	fprintf(stderr, "%s: wrong device\n", device);
	exit(1);
    }
    return fd;
}

int
main(int argc, char *argv[])
{
    /* X11 */
    Display                  *dpy;
    Screen                   *scr;
    Window                   root;
    XWindowAttributes        wts;
    XPixmapFormatValues      *pf;
    int                      depth=0;
#ifdef HAVE_LIBXXF86DGA
    int                      width,bar,foo,flags=0;
    void                     *base = 0;
#endif

    /* fbdev */
    struct fb_fix_screeninfo   fix;
    struct fb_var_screeninfo   var;
    
    /* v4l */
    struct video_capability  capability;
    struct video_buffer      fbuf;
    int                      set_width=0, set_height=0, set_bpp=0, set_bpl=0;
    void                     *set_base=NULL;

    /* misc */
    int                      fd,c,i,n;
    char                     *h;

    /* Make sure fd's 0 1 2 are open, otherwise
     * we might end up sending perror() messages to
     * the `device' file */
    for (i = 0; i < 3; i++) {
	if (-1 == fcntl(i, F_GETFL, &n))
	    exit(1);
    }

    /* take defaults from environment */
    if (NULL != (h = getenv("DISPLAY")))
	display = h;
    if (NULL != (h = getenv("FRAMEBUFFER")))
	fbdev = h;
    
    /* parse options */
    for (;;) {
	if (-1 == (c = getopt(argc, argv, "hqd:c:b:s:f")))
	    break;
	switch (c) {
	case 'q':
	    verbose = 0;
	    break;
	case 'd':
	    display = optarg;
	    break;
	case 'c':
	    device = optarg;
	    break;
	case 'b':
	    bpp = atoi(optarg);
	    break;
	case 's':
	    shift = atoi(optarg);
	    if (shift < 0 || shift > 8192)
		shift = 0;
	    break;
	case 'f':
	    display = NULL;
	    break;
	case 'h':
	default:
	    fprintf(stderr,
		    "usage: %s  [ options ] \n"
		    "\n"
		    "options:\n"
		    "    -q        quiet\n"
		    "    -d <dpy>  X11 Display     [%s]\n"
		    "    -c <dev>  video device    [%s]\n"
		    "    -b <n>    displays color depth is <n> bpp\n"
		    "    -s <n>    shift display by <n> bytes\n"
		    "    -f        query frame buffer device for info\n",
		    argv[0],
		    display ? display : "none",
		    device);
	    exit(1);
	}
    }

#ifdef __powerpc__
    if (is_mklinux()) {
	struct vc_mode mode;
	
	if (-1 == (fd = open("/dev/console",O_RDWR|O_NDELAY))) {
	    fprintf(stderr,"open console: %s\n",strerror(errno));
	    exit(1);
	}
	if (-1 == ioctl(fd, VC_GETMODE, (unsigned long)&mode)) {
	    perror("ioctl VC_GETMODE");
	    exit(1);
	}
	close(fd);
	set_width  = mode.width;
	set_height = mode.height;
	set_bpp    = mode.depth;
	set_bpl    = mode.pitch;
	set_base   = (void*)mode.fb_address;
    } else
#endif
    if (display) {
	/* using X11 */
	if (display[0] != ':') {
	    fprintf(stderr,"non-local display `%s' not allowed, ",device);
	    display = strchr(display,':');
	    if (NULL == display) {
		fprintf(stderr,"exiting");
		exit(1);
	    } else {
		fprintf(stderr,"using `%s' instead\n",device);
	    }
	}
	
	/* get screen params */
	if (NULL == (dpy = XOpenDisplay(display))) {
	    fprintf(stderr,"can't open x11 display %s\n",display);
	    exit(1);
	}

	scr  = DefaultScreenOfDisplay(dpy);
	root = DefaultRootWindow(dpy);
	XGetWindowAttributes(dpy, root, &wts);
	depth = wts.depth;
	
	pf = XListPixmapFormats(dpy,&n);
	for (i = 0; i < n; i++) {
	    if (pf[i].depth == depth) {
		depth = pf[i].bits_per_pixel;
		break;
	    }
	}

	if ((bpp == 32 || bpp == 24) && (depth == 32 || depth == 24))
	    depth = bpp;
	
	set_width  = wts.width;
	set_height = wts.height;
	set_bpp    = (depth+7) & 0xf8;
	set_bpl    = set_width*set_bpp/8;
		
#ifdef HAVE_LIBXXF86DGA
	if (XF86DGAQueryExtension(dpy,&foo,&bar)) {
	    XF86DGAQueryDirectVideo(dpy,XDefaultScreen(dpy),&flags);
	    if (flags & XF86DGADirectPresent) {
		XF86DGAGetVideoLL(dpy,XDefaultScreen(dpy),(int*)&base,&width,&foo,&bar);
		set_bpl  = width * set_bpp/8;
		set_base = base;
	    }
	}
	if (verbose)
	    fprintf(stderr,"using X11 display %s [dga: %savailable]\n",display,
		    flags & XF86DGADirectPresent ? "" : "not ");
#else
	if (verbose)
	    fprintf(stderr,"using X11 display %s [dga: support not compiled]\n",display);
#endif
    } else {

	/* try framebuffer device */
	fbdev = getenv("FRAMEBUFFER");
	if (NULL == fbdev) {
#ifdef FBIOGET_CON2FBMAP
	    struct fb_con2fbmap c2m;
	    struct vt_stat vstat;

	    if (-1 == (fd = open("/dev/tty0",O_RDWR,0))) {
		fprintf(stderr,"open /dev/tty0: %s\n",strerror(errno));
		exit(1);
	    }
	    if (-1 == ioctl(fd, VT_GETSTATE, &vstat)) {
		perror("ioctl VT_GETSTATE");
		exit(1);
	    }
	    close(fd);
	    c2m.console = vstat.v_active;
	    if (-1 == (fd = open("/dev/fb0",O_RDWR,0))) {
		fprintf(stderr,"open /dev/fb0: %s\n",strerror(errno));
		exit(1);
	    }
	    if (-1 == ioctl(fd, FBIOGET_CON2FBMAP, &c2m)) {
		perror("ioctl FBIOGET_CON2FBMAP");
		c2m.framebuffer = 0;
	    }
	    close(fd);
	    fprintf(stderr,"map: vt%02d => fb%d\n",c2m.console,c2m.framebuffer);
	    sprintf(fbdev=malloc(16),"/dev/fb%d",c2m.framebuffer);
#else
	    fbdev="/dev/fb0";
#endif
	}
	/* Open frame buffer device, with security checks */
	fd = dev_open(fbdev, 29 /* VIDEO_MAJOR */);
	if (-1 == ioctl(fd,FBIOGET_FSCREENINFO,&fix)) {
	    perror("ioctl FBIOGET_FSCREENINFO");
	    exit(1);
	}
	if (-1 == ioctl(fd,FBIOGET_VSCREENINFO,&var)) {
	    perror("ioctl FBIOGET_VSCREENINFO");
	    exit(1);
	}
	if (fix.type != FB_TYPE_PACKED_PIXELS) {
	    fprintf(stderr,"can handle only packed pixel frame buffers\n");
	    exit(1);
	}
	close(fd);
	if (verbose)
	    fprintf(stderr,"using framebuffer device %s: %s\n",fbdev,fix.id);

	set_width  = var.xres_virtual;
	set_height = var.yres_virtual;
	set_bpp    = var.bits_per_pixel;
	set_bpl    = fix.line_length;
	set_base   = fix.smem_start;
    }

    /* Open device file, with security checks */
    fd = dev_open(device, 81 /* VIDEO_MAJOR */);
    if (-1 == ioctl(fd,VIDIOCGCAP,&capability)) {
	fprintf(stderr,"%s: ioctl VIDIOCGCAP: %s\n",device,strerror(errno));
	exit(1);
    }
    if (!(capability.type & VID_TYPE_OVERLAY)) {
	fprintf(stderr,"%s: no overlay support\n",device);
	exit(1);
    }

    /* read-modify-write v4l screen parameters */
    if (-1 == ioctl(fd,VIDIOCGFBUF,&fbuf)) {
	fprintf(stderr,"%s: ioctl VIDIOCGFBUF: %s\n",device,strerror(errno));
	exit(1);
    }

    /* set values */
    fbuf.width        = set_width;
    fbuf.height       = set_height;
    fbuf.depth        = set_bpp;
    fbuf.bytesperline = set_bpl;
    if (set_base)
	fbuf.base     = set_base+shift;

    /* XXX bttv confuses color depth and bits/pixel */
    if (wts.depth == 15)
	fbuf.depth = 15;
    if ((bpp == 15 || bpp == 16) && (depth == 16))
	fbuf.depth = bpp;

    if (verbose) {
	fprintf(stderr,"video mode: %dx%d, %d bit/pixel, %d byte/scanline\n",
		fbuf.width,fbuf.height,fbuf.depth,fbuf.bytesperline);
	if (set_base)
	    fprintf(stderr,"framebuffer at %p\n",set_base);
    }

    if (-1 == ioctl(fd,VIDIOCSFBUF,&fbuf)) {
	fprintf(stderr,"%s: ioctl VIDIOCSFBUF: %s\n",device,strerror(errno));
	exit(1);
    }
    if (verbose)
	fprintf(stderr,"ok\n");

    return 0;
}

