/*************************************************************************
 *
 * fbinit.c - Frame buffer initialisation for FB-graphics ICA client.
 *
 * $Id$
 *
 * Copyright 2004-2014 Citrix Systems, Inc.  All Rights Reserved.
 *
 *************************************************************************/

#include "citrix.h"

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <limits.h>
#include <malloc.h>

#include "fb_plugin.h"

#define DEVICE "/dev/fb0"

struct fbprog_screeninfo framebuffer;   /* Information about FB. */

int fb_init()
{
	struct fb_var_screeninfo        fb_var;
	struct fb_fix_screeninfo        fb_fix;
#if PXL_SIZE != 8
        struct fb_bitfield             *bfp;
#endif

	framebuffer.fd = open(DEVICE,O_RDWR);
        if (framebuffer.fd < 0) {
		perror("Cannot open framebuffer");
                return -1;
        }
	if (ioctl(framebuffer.fd, FBIOGET_VSCREENINFO, &fb_var) < 0) {
		perror("ioctl FBIOGET_VSCREENINFO");
                return -1;
        }
	if (ioctl(framebuffer.fd, FBIOGET_FSCREENINFO, &fb_fix) < 0) {
		perror("ioctl FBIOGET_FSCREENINFO");
                return -1;
        }
	if (fb_fix.type != FB_TYPE_PACKED_PIXELS ||
#if PXL_SIZE == 8
            fb_fix.visual != FB_VISUAL_PSEUDOCOLOR ||
#else
	    (fb_fix.visual != FB_VISUAL_TRUECOLOR &&
             fb_fix.visual != FB_VISUAL_DIRECTCOLOR) ||
#endif
            fb_var.bits_per_pixel != PXL_SIZE ||
            fb_var.xres * (PXL_SIZE / 8) > fb_fix.line_length) {
                fprintf(stderr,
                        "Incompatible frame buffer:\n"
                        "  Pixel type: %d Visual type: %d\n"
                        "  Screen: %dx%dx%d\n"
                        "  Offset: %dx%d\n"
                        "  Line length: %d bytes\n"
                        "  Size: %d bytes\n"
                        "Expected Pixel type %d, Visual type %d (%s)"
                        " and depth %d with minimum line length %d\n",
                        fb_fix.type, fb_fix.visual,
                        fb_var.xres, fb_var.yres, fb_var.bits_per_pixel,
                        fb_var.xoffset, fb_var.yoffset,
                        fb_fix.line_length,
                        fb_fix.line_length * fb_var.yres,
                        FB_TYPE_PACKED_PIXELS,
#if PXL_SIZE == 8
                        FB_VISUAL_PSEUDOCOLOR, "Pseudocolor",
#else
                        FB_VISUAL_TRUECOLOR, "Truecolor",
#endif
                        PXL_SIZE, fb_var.xres * (PXL_SIZE / 8));
                exit(1);
        }

#if PXL_SIZE == 32
        /* Check the colour bitfields are as expected. */

        bfp = &fb_var.red;
        do {
            if (bfp->msb_right || bfp->length != 8)
                break;
        } while (++bfp < &fb_var.transp);

        if (bfp != &fb_var.transp ||
            fb_var.red.offset != 16 ||
            fb_var.green.offset != 8 ||
            fb_var.blue.offset != 0) {
                fprintf(stderr,
                        "Color bitfields incompatible with expected"
                        "RGB: 8/16,8/8,8/0\n");
                exit(1);
        }
#endif

	framebuffer.visual = fb_fix.visual;
	framebuffer.line_length = fb_fix.line_length;
	framebuffer.xoffset = fb_var.xoffset; 
	framebuffer.yoffset = fb_var.yoffset;
	framebuffer.xres = fb_var.xres;
        framebuffer.yres = fb_var.yres;
	framebuffer.bits_per_pixel = fb_var.bits_per_pixel; 
	framebuffer.bytes_per_pixel = fb_var.bits_per_pixel <= 8 ? 1 :
                                       (fb_var.bits_per_pixel <= 16 ? 2 : 4);
	framebuffer.size = framebuffer.line_length * framebuffer.yres;
	framebuffer.fbmem = (unsigned char *)
            mmap(NULL, framebuffer.size, PROT_READ | PROT_WRITE, MAP_SHARED,
                 framebuffer.fd, 0);
        if ((int)framebuffer.fbmem == -1) {
            perror("Failed to map framebuffer to memory");
            exit(1);
        }

        /* Find start of visible frame.  (I never saw non-zero offsets!) */

        framebuffer.frame = framebuffer.fbmem +
                            framebuffer.yoffset * framebuffer.line_length +
                            framebuffer.xoffset * framebuffer.bytes_per_pixel;

        printf("The framebuffer is %d bytes mapped at %p "
               "from file descriptor %d,\n", 
               framebuffer.size, framebuffer.fbmem, framebuffer.fd);
        printf("size %dx%d with row length %u and offsets %d, %d.\n",
               framebuffer.xres, framebuffer.yres, framebuffer.line_length,
               framebuffer.xoffset, framebuffer.yoffset);
        return 0;
}

#ifdef DEBUG
void print_cmap_info(void)
{
        struct fb_cmap oldcmap;
        int i;
	
	oldcmap.start=0;
	oldcmap.len=256;
	oldcmap.red=calloc(256, sizeof(__u16));
	oldcmap.green=calloc(256, sizeof(__u16));
	oldcmap.blue=calloc(256, sizeof(__u16));

        if (ioctl(framebuffer.fd, FBIOGETCMAP, &oldcmap) < 0) {
		perror("could not do ioctl FBIOGETCMAP");
	} else {
		for ( i = 0 ; i < oldcmap.len ; i++ )
		{
			printf ( " %d \t ", i);
			printf  ("red: %#04x \t", (oldcmap.red)[i]  ) ;
			printf ("green: %#04x \t", (oldcmap.green)[i]);
			printf ("blue: %#04x \n", (oldcmap.blue)[i] );
		}
	}
}
#endif /* DEBUG */

void fb_fini()
{
	munmap(framebuffer.fbmem, framebuffer.size);
	close(framebuffer.fd);
}
