/******************************************************************************
 * Copyright (c) 2004, 2008 IBM Corporation
 * Copyright (c) 2009 Pattrick Hueper <phueper@hueper.net>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer
 *   in the documentation and/or other materials provided with the
 *   distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Contributors:
 *     IBM Corporation - initial implementation
 *****************************************************************************/

#include <boot/coreboot_tables.h>
#include <framebuffer_info.h>
#include <string.h>
#include <types.h>

#include <endian.h>

#include "debug.h"

#include <x86emu/x86emu.h>
#include <x86emu/regs.h>
#include "../x86emu/prim_ops.h"

#include "biosemu.h"
#include "io.h"
#include "mem.h"
#include "interrupt.h"
#include "device.h"

#include <delay.h>

#include <vbe.h>

// these structs only store a subset of the VBE defined fields
// only those needed.
typedef struct {
	char signature[4];
	u16 version;
	u8 *oem_string_ptr;
	u32 capabilities;
	u16 video_mode_list[256];	// lets hope we never have more than
					// 256 video modes...
	u16 total_memory;
} vbe_info_t;

// pointer to VBEInfoBuffer, set by vbe_prepare
u8 *vbe_info_buffer = 0;

// virtual BIOS Memory
u8 *biosmem;
u32 biosmem_size;

#if CONFIG(FRAMEBUFFER_SET_VESA_MODE)
static inline u8
vbe_prepare(void)
{
	vbe_info_buffer = biosmem + (VBE_SEGMENT << 4);	// segment:offset off VBE Data Area
	//clear buffer
	memset(vbe_info_buffer, 0, 512);
	//set VbeSignature to "VBE2" to indicate VBE 2.0+ request
	vbe_info_buffer[0] = 'V';
	vbe_info_buffer[1] = 'B';
	vbe_info_buffer[2] = 'E';
	vbe_info_buffer[3] = '2';
	// ES:DI store pointer to buffer in virtual mem see vbe_info_buffer above...
	M.x86.R_EDI = 0x0;
	M.x86.R_ES = VBE_SEGMENT;

	return 0;		// successful init
}

// VBE Function 00h
static u8
vbe_info(vbe_info_t * info)
{
	vbe_prepare();
	// call VBE function 00h (Info Function)
	M.x86.R_EAX = 0x4f00;

	// enable trace
	CHECK_DBG(DEBUG_TRACE_X86EMU) {
		X86EMU_trace_on();
	}
	// run VESA Interrupt
	runInt10();

	if (M.x86.R_AL != 0x4f) {
		DEBUG_PRINTF_VBE("%s: VBE Info Function NOT supported! AL=%x\n",
				 __func__, M.x86.R_AL);
		return -1;
	}

	if (M.x86.R_AH != 0x0) {
		DEBUG_PRINTF_VBE
		    ("%s: VBE Info Function Return Code NOT OK! AH=%x\n",
		     __func__, M.x86.R_AH);
		return M.x86.R_AH;
	}
	//printf("VBE Info Dump:");
	//dump(vbe_info_buffer, 64);

	//offset 0: signature
	info->signature[0] = vbe_info_buffer[0];
	info->signature[1] = vbe_info_buffer[1];
	info->signature[2] = vbe_info_buffer[2];
	info->signature[3] = vbe_info_buffer[3];

	// offset 4: 16bit le containing VbeVersion
	info->version = in16le(vbe_info_buffer + 4);

	// offset 6: 32bit le containing segment:offset of OEM String in virtual Mem.
	info->oem_string_ptr =
	    biosmem + ((in16le(vbe_info_buffer + 8) << 4) +
		       in16le(vbe_info_buffer + 6));

	// offset 10: 32bit le capabilities
	info->capabilities = in32le(vbe_info_buffer + 10);

	// offset 14: 32 bit le containing segment:offset of supported video mode table
	u16 *video_mode_ptr;
	video_mode_ptr =
	    (u16 *) (biosmem +
			  ((in16le(vbe_info_buffer + 16) << 4) +
			   in16le(vbe_info_buffer + 14)));
	u32 i = 0;
	do {
		info->video_mode_list[i] = in16le(video_mode_ptr + i);
		i++;
	}
	while ((i <
		(sizeof(info->video_mode_list) /
		 sizeof(info->video_mode_list[0])))
	       && (info->video_mode_list[i - 1] != 0xFFFF));

	//offset 18: 16bit le total memory in 64KB blocks
	info->total_memory = in16le(vbe_info_buffer + 18);

	return 0;
}

static int mode_info_valid;

// VBE Function 01h
static u8
vbe_get_mode_info(vbe_mode_info_t * mode_info)
{
	vbe_prepare();
	// call VBE function 01h (Return VBE Mode Info Function)
	M.x86.R_EAX = 0x4f01;
	M.x86.R_CX = mode_info->video_mode;

	// enable trace
	CHECK_DBG(DEBUG_TRACE_X86EMU) {
		X86EMU_trace_on();
	}
	// run VESA Interrupt
	runInt10();

	if (M.x86.R_AL != 0x4f) {
		DEBUG_PRINTF_VBE
		    ("%s: VBE Return Mode Info Function NOT supported! AL=%x\n",
		     __func__, M.x86.R_AL);
		return -1;
	}

	if (M.x86.R_AH != 0x0) {
		DEBUG_PRINTF_VBE
		    ("%s: VBE Return Mode Info (mode: %04x) Function Return Code NOT OK! AH=%02x\n",
		     __func__, mode_info->video_mode, M.x86.R_AH);
		return M.x86.R_AH;
	}

	//pointer to mode_info_block is in ES:DI
	memcpy(mode_info->mode_info_block,
	       biosmem + ((M.x86.R_ES << 4) + M.x86.R_DI),
	       sizeof(mode_info->mode_info_block));
	mode_info_valid = 1;

	//printf("Mode Info Dump:");
	//dump(mode_info_block, 64);

	return 0;
}

// VBE Function 02h
static u8
vbe_set_mode(vbe_mode_info_t * mode_info)
{
	vbe_prepare();
	// call VBE function 02h (Set VBE Mode Function)
	M.x86.R_EAX = 0x4f02;
	M.x86.R_BX = mode_info->video_mode;
	M.x86.R_BX |= 0x4000;	// set bit 14 to request linear framebuffer mode
	M.x86.R_BX &= 0x7FFF;	// clear bit 15 to request clearing of framebuffer

	DEBUG_PRINTF_VBE("%s: setting mode: 0x%04x\n", __func__,
			 M.x86.R_BX);

	// enable trace
	CHECK_DBG(DEBUG_TRACE_X86EMU) {
		X86EMU_trace_on();
	}
	// run VESA Interrupt
	runInt10();

	if (M.x86.R_AL != 0x4f) {
		DEBUG_PRINTF_VBE
		    ("%s: VBE Set Mode Function NOT supported! AL=%x\n",
		     __func__, M.x86.R_AL);
		return -1;
	}

	if (M.x86.R_AH != 0x0) {
		DEBUG_PRINTF_VBE
		    ("%s: mode: %x VBE Set Mode Function Return Code NOT OK! AH=%x\n",
		     __func__, mode_info->video_mode, M.x86.R_AH);
		return M.x86.R_AH;
	}
	return 0;
}

#if 0
//VBE Function 08h
static u8
vbe_set_palette_format(u8 format)
{
	vbe_prepare();
	// call VBE function 09h (Set/Get Palette Data Function)
	M.x86.R_EAX = 0x4f08;
	M.x86.R_BL = 0x00;	// set format
	M.x86.R_BH = format;

	DEBUG_PRINTF_VBE("%s: setting palette format: %d\n", __func__,
			 format);

	// enable trace
	CHECK_DBG(DEBUG_TRACE_X86EMU) {
		X86EMU_trace_on();
	}
	// run VESA Interrupt
	runInt10();

	if (M.x86.R_AL != 0x4f) {
		DEBUG_PRINTF_VBE
		    ("%s: VBE Set Palette Format Function NOT supported! AL=%x\n",
		     __func__, M.x86.R_AL);
		return -1;
	}

	if (M.x86.R_AH != 0x0) {
		DEBUG_PRINTF_VBE
		    ("%s: VBE Set Palette Format Function Return Code NOT OK! AH=%x\n",
		     __func__, M.x86.R_AH);
		return M.x86.R_AH;
	}
	return 0;
}

// VBE Function 09h
static u8
vbe_set_color(u16 color_number, u32 color_value)
{
	vbe_prepare();
	// call VBE function 09h (Set/Get Palette Data Function)
	M.x86.R_EAX = 0x4f09;
	M.x86.R_BL = 0x00;	// set color
	M.x86.R_CX = 0x01;	// set only one entry
	M.x86.R_DX = color_number;
	// ES:DI is address where color_value is stored, we store it at 2000:0000
	M.x86.R_ES = 0x2000;
	M.x86.R_DI = 0x0;

	// store color value at ES:DI
	out32le(biosmem + (M.x86.R_ES << 4) + M.x86.R_DI, color_value);

	DEBUG_PRINTF_VBE("%s: setting color #%x: 0x%04x\n", __func__,
			 color_number, color_value);

	// enable trace
	CHECK_DBG(DEBUG_TRACE_X86EMU) {
		X86EMU_trace_on();
	}
	// run VESA Interrupt
	runInt10();

	if (M.x86.R_AL != 0x4f) {
		DEBUG_PRINTF_VBE
		    ("%s: VBE Set Palette Function NOT supported! AL=%x\n",
		     __func__, M.x86.R_AL);
		return -1;
	}

	if (M.x86.R_AH != 0x0) {
		DEBUG_PRINTF_VBE
		    ("%s: VBE Set Palette Function Return Code NOT OK! AH=%x\n",
		     __func__, M.x86.R_AH);
		return M.x86.R_AH;
	}
	return 0;
}

static u8
vbe_get_color(u16 color_number, u32 *color_value)
{
	vbe_prepare();
	// call VBE function 09h (Set/Get Palette Data Function)
	M.x86.R_EAX = 0x4f09;
	M.x86.R_BL = 0x00;	// get color
	M.x86.R_CX = 0x01;	// get only one entry
	M.x86.R_DX = color_number;
	// ES:DI is address where color_value is stored, we store it at 2000:0000
	M.x86.R_ES = 0x2000;
	M.x86.R_DI = 0x0;

	// enable trace
	CHECK_DBG(DEBUG_TRACE_X86EMU) {
		X86EMU_trace_on();
	}
	// run VESA Interrupt
	runInt10();

	if (M.x86.R_AL != 0x4f) {
		DEBUG_PRINTF_VBE
		    ("%s: VBE Set Palette Function NOT supported! AL=%x\n",
		     __func__, M.x86.R_AL);
		return -1;
	}

	if (M.x86.R_AH != 0x0) {
		DEBUG_PRINTF_VBE
		    ("%s: VBE Set Palette Function Return Code NOT OK! AH=%x\n",
		     __func__, M.x86.R_AH);
		return M.x86.R_AH;
	}
	// read color value from ES:DI
	*color_value = in32le(biosmem + (M.x86.R_ES << 4) + M.x86.R_DI);

	DEBUG_PRINTF_VBE("%s: getting color #%x --> 0x%04x\n", __func__,
			 color_number, *color_value);

	return 0;
}

// VBE Function 15h
static u8
vbe_get_ddc_info(vbe_ddc_info_t * ddc_info)
{
	vbe_prepare();
	// call VBE function 15h (DDC Info Function)
	M.x86.R_EAX = 0x4f15;
	M.x86.R_BL = 0x00;	// get DDC Info
	M.x86.R_CX = ddc_info->port_number;
	M.x86.R_ES = 0x0;
	M.x86.R_DI = 0x0;

	// enable trace
	CHECK_DBG(DEBUG_TRACE_X86EMU) {
		X86EMU_trace_on();
	}
	// run VESA Interrupt
	runInt10();

	if (M.x86.R_AL != 0x4f) {
		DEBUG_PRINTF_VBE
		    ("%s: VBE Get DDC Info Function NOT supported! AL=%x\n",
		     __func__, M.x86.R_AL);
		return -1;
	}

	if (M.x86.R_AH != 0x0) {
		DEBUG_PRINTF_VBE
		    ("%s: port: %x VBE Get DDC Info Function Return Code NOT OK! AH=%x\n",
		     __func__, ddc_info->port_number, M.x86.R_AH);
		return M.x86.R_AH;
	}
	// BH = approx. time in seconds to transfer one EDID block
	ddc_info->edid_transfer_time = M.x86.R_BH;
	// BL = DDC Level
	ddc_info->ddc_level = M.x86.R_BL;

	vbe_prepare();
	// call VBE function 15h (DDC Info Function)
	M.x86.R_EAX = 0x4f15;
	M.x86.R_BL = 0x01;	// read EDID
	M.x86.R_CX = ddc_info->port_number;
	M.x86.R_DX = 0x0;	// block number
	// ES:DI is address where EDID is stored, we store it at 2000:0000
	M.x86.R_ES = 0x2000;
	M.x86.R_DI = 0x0;

	// enable trace
	CHECK_DBG(DEBUG_TRACE_X86EMU) {
		X86EMU_trace_on();
	}
	// run VESA Interrupt
	runInt10();

	if (M.x86.R_AL != 0x4f) {
		DEBUG_PRINTF_VBE
		    ("%s: VBE Read EDID Function NOT supported! AL=%x\n",
		     __func__, M.x86.R_AL);
		return -1;
	}

	if (M.x86.R_AH != 0x0) {
		DEBUG_PRINTF_VBE
		    ("%s: port: %x VBE Read EDID Function Return Code NOT OK! AH=%x\n",
		     __func__, ddc_info->port_number, M.x86.R_AH);
		return M.x86.R_AH;
	}

	memcpy(ddc_info->edid_block_zero,
	       biosmem + (M.x86.R_ES << 4) + M.x86.R_DI,
	       sizeof(ddc_info->edid_block_zero));

	return 0;
}

static u32
vbe_get_info(void)
{
	u8 rval;
	int i;

	// XXX FIXME these need to be filled with sane values

	// get a copy of input struct...
	screen_info_input_t input;
	// output is pointer to the address passed as argv[4]
	screen_info_t local_output;
	screen_info_t *output = &local_output;
	// zero input
	memset(&input, 0, sizeof(screen_info_input_t));
	// zero output
	memset(&output, 0, sizeof(screen_info_t));

	vbe_info_t info;
	rval = vbe_info(&info);
	if (rval != 0)
		return rval;

	DEBUG_PRINTF_VBE("VbeSignature: %s\n", info.signature);
	DEBUG_PRINTF_VBE("VbeVersion: 0x%04x\n", info.version);
	DEBUG_PRINTF_VBE("OemString: %s\n", info.oem_string_ptr);
	DEBUG_PRINTF_VBE("Capabilities:\n");
	DEBUG_PRINTF_VBE("\tDAC: %s\n",
			 (info.capabilities & 0x1) ==
			 0 ? "fixed 6bit" : "switchable 6/8bit");
	DEBUG_PRINTF_VBE("\tVGA: %s\n",
			 (info.capabilities & 0x2) ==
			 0 ? "compatible" : "not compatible");
	DEBUG_PRINTF_VBE("\tRAMDAC: %s\n",
			 (info.capabilities & 0x4) ==
			 0 ? "normal" : "use blank bit in Function 09h");

	// argv[4] may be a pointer with enough space to return screen_info_t
	// as input, it must contain a screen_info_input_t with the following content:
	// byte[0:3] = "DDC\0" (zero-terminated signature header)
	// byte[4:5] = reserved space for the return struct... just in case we ever change
	//             the struct and don't have reserved enough memory (and let's hope the struct
	//             never gets larger than 64KB)
	// byte[6] = monitor port number for DDC requests ("only" one byte... so lets hope we never have more than 255 monitors...
	// byte[7:8] = max. screen width (OF may want to limit this)
	// byte[9] = required color depth in bpp
	if (strncmp((char *) input.signature, "DDC", 4) != 0) {
		printf
		    ("%s: Invalid input signature! expected: %s, is: %s\n",
		     __func__, "DDC", input.signature);
		return -1;
	}
	if (input.size_reserved != sizeof(screen_info_t)) {
		printf
		    ("%s: Size of return struct is wrong, required: %d, available: %d\n",
		     __func__, (int) sizeof(screen_info_t),
		     input.size_reserved);
		return -1;
	}

	vbe_ddc_info_t ddc_info;
	ddc_info.port_number = input.monitor_number;
	vbe_get_ddc_info(&ddc_info);

#if 0
	DEBUG_PRINTF_VBE("DDC: edid_transfer_time: %d\n",
			 ddc_info.edid_transfer_time);
	DEBUG_PRINTF_VBE("DDC: ddc_level: %x\n", ddc_info.ddc_level);
	DEBUG_PRINTF_VBE("DDC: EDID:\n");
	CHECK_DBG(DEBUG_VBE) {
		dump(ddc_info.edid_block_zero,
		     sizeof(ddc_info.edid_block_zero));
	}
#endif
/* This could fail because of alignment issues, so use a longer form.
	*((u64 *) ddc_info.edid_block_zero) != (u64) 0x00FFFFFFFFFFFF00ULL
*/
	if (ddc_info.edid_block_zero[0] != 0x00 ||
	    ddc_info.edid_block_zero[1] != 0xFF ||
	    ddc_info.edid_block_zero[2] != 0xFF ||
	    ddc_info.edid_block_zero[3] != 0xFF ||
	    ddc_info.edid_block_zero[4] != 0xFF ||
	    ddc_info.edid_block_zero[5] != 0xFF ||
	    ddc_info.edid_block_zero[6] != 0xFF ||
	    ddc_info.edid_block_zero[7] != 0x00 ) {
		// invalid EDID signature... probably no monitor

		output->display_type = 0x0;
		return 0;
	} else if ((ddc_info.edid_block_zero[20] & 0x80) != 0) {
		// digital display
		output->display_type = 2;
	} else {
		// analog
		output->display_type = 1;
	}
	DEBUG_PRINTF_VBE("DDC: found display type %d\n", output->display_type);
	memcpy(output->edid_block_zero, ddc_info.edid_block_zero,
	       sizeof(ddc_info.edid_block_zero));
	i = 0;
	vbe_mode_info_t mode_info;
	vbe_mode_info_t best_mode_info;
	// initialize best_mode to 0
	memset(&best_mode_info, 0, sizeof(best_mode_info));
	while ((mode_info.video_mode = info.video_mode_list[i]) != 0xFFFF) {
		//DEBUG_PRINTF_VBE("%x: Mode: %04x\n", i, mode_info.video_mode);
		vbe_get_mode_info(&mode_info);

		// FIXME all these values are little endian!

		DEBUG_PRINTF_VBE("Video Mode 0x%04x available, %s\n",
				 mode_info.video_mode,
				 (le16_to_cpu(mode_info.vesa.mode_attributes) & 0x1) ==
				 0 ? "not supported" : "supported");
		DEBUG_PRINTF_VBE("\tTTY: %s\n",
				 (le16_to_cpu(mode_info.vesa.mode_attributes) & 0x4) ==
				 0 ? "no" : "yes");
		DEBUG_PRINTF_VBE("\tMode: %s %s\n",
				 (le16_to_cpu(mode_info.vesa.mode_attributes) & 0x8) ==
				 0 ? "monochrome" : "color",
				 (le16_to_cpu(mode_info.vesa.mode_attributes) & 0x10) ==
				 0 ? "text" : "graphics");
		DEBUG_PRINTF_VBE("\tVGA: %s\n",
				 (le16_to_cpu(mode_info.vesa.mode_attributes) & 0x20) ==
				 0 ? "compatible" : "not compatible");
		DEBUG_PRINTF_VBE("\tWindowed Mode: %s\n",
				 (le16_to_cpu(mode_info.vesa.mode_attributes) & 0x40) ==
				 0 ? "yes" : "no");
		DEBUG_PRINTF_VBE("\tFramebuffer: %s\n",
				 (le16_to_cpu(mode_info.vesa.mode_attributes) & 0x80) ==
				 0 ? "no" : "yes");
		DEBUG_PRINTF_VBE("\tResolution: %dx%d\n",
				 le16_to_cpu(mode_info.vesa.x_resolution),
				 le16_to_cpu(mode_info.vesa.y_resolution));
		DEBUG_PRINTF_VBE("\tChar Size: %dx%d\n",
				 mode_info.vesa.x_charsize, mode_info.vesa.y_charsize);
		DEBUG_PRINTF_VBE("\tColor Depth: %dbpp\n",
				 mode_info.vesa.bits_per_pixel);
		DEBUG_PRINTF_VBE("\tMemory Model: 0x%x\n",
				 mode_info.vesa.memory_model);
		DEBUG_PRINTF_VBE("\tFramebuffer Offset: %08x\n",
				 le32_to_cpu(mode_info.vesa.phys_base_ptr));

		if ((mode_info.vesa.bits_per_pixel == input.color_depth)
		    && (le16_to_cpu(mode_info.vesa.x_resolution) <= input.max_screen_width)
		    && ((le16_to_cpu(mode_info.vesa.mode_attributes) & 0x80) != 0)	// framebuffer mode
		    && ((le16_to_cpu(mode_info.vesa.mode_attributes) & 0x10) != 0)	// graphics
		    && ((le16_to_cpu(mode_info.vesa.mode_attributes) & 0x8) != 0)	// color
		    && (le16_to_cpu(mode_info.vesa.x_resolution) > le16_to_cpu(best_mode_info.vesa.x_resolution)))	// better than previous best_mode
		{
			// yiiiihaah... we found a new best mode
			memcpy(&best_mode_info, &mode_info, sizeof(mode_info));
		}
		i++;
	}

	if (best_mode_info.video_mode != 0) {
		DEBUG_PRINTF_VBE
		    ("Best Video Mode found: 0x%x, %dx%d, %dbpp, framebuffer_address: 0x%x\n",
		     best_mode_info.video_mode,
		     best_mode_info.vesa.x_resolution,
		     best_mode_info.vesa.y_resolution,
		     best_mode_info.vesa.bits_per_pixel,
		     le32_to_cpu(best_mode_info.vesa.phys_base_ptr));

		//printf("Mode Info Dump:");
		//dump(best_mode_info.mode_info_block, 64);

		// set the video mode
		vbe_set_mode(&best_mode_info);

		if ((info.capabilities & 0x1) != 0) {
			// switch to 8 bit palette format
			vbe_set_palette_format(8);
		}
		// setup a palette:
		// - first 216 colors are mixed colors for each component in 6 steps
		//   (6*6*6=216)
		// - then 10 shades of the three primary colors
		// - then 10 shades of grey
		// -------
		// = 256 colors
		//
		// - finally black is color 0 and white color FF (because SLOF expects it
		//   this way...)
		// this resembles the palette that the kernel/X Server seems to expect...

		u8 mixed_color_values[6] =
		    { 0xFF, 0xDA, 0xB3, 0x87, 0x54, 0x00 };
		u8 primary_color_values[10] =
		    { 0xF3, 0xE7, 0xCD, 0xC0, 0xA5, 0x96, 0x77, 0x66, 0x3F,
			0x27
		};
		u8 mc_size = sizeof(mixed_color_values);
		u8 prim_size = sizeof(primary_color_values);

		u8 curr_color_index;
		u32 curr_color;

		u8 r, g, b;
		// 216 mixed colors
		for (r = 0; r < mc_size; r++) {
			for (g = 0; g < mc_size; g++) {
				for (b = 0; b < mc_size; b++) {
					curr_color_index =
					    (r * mc_size * mc_size) +
					    (g * mc_size) + b;
					curr_color = 0;
					curr_color |= ((u32) mixed_color_values[r]) << 16;	//red value
					curr_color |= ((u32) mixed_color_values[g]) << 8;	//green value
					curr_color |= (u32) mixed_color_values[b];	//blue value
					vbe_set_color(curr_color_index,
						      curr_color);
				}
			}
		}

		// 10 shades of each primary color
		// red
		for (r = 0; r < prim_size; r++) {
			curr_color_index = mc_size * mc_size * mc_size + r;
			curr_color = ((u32) primary_color_values[r]) << 16;
			vbe_set_color(curr_color_index, curr_color);
		}
		//green
		for (g = 0; g < prim_size; g++) {
			curr_color_index =
			    mc_size * mc_size * mc_size + prim_size + g;
			curr_color = ((u32) primary_color_values[g]) << 8;
			vbe_set_color(curr_color_index, curr_color);
		}
		//blue
		for (b = 0; b < prim_size; b++) {
			curr_color_index =
			    mc_size * mc_size * mc_size + prim_size * 2 + b;
			curr_color = (u32) primary_color_values[b];
			vbe_set_color(curr_color_index, curr_color);
		}
		// 10 shades of grey
		for (i = 0; i < prim_size; i++) {
			curr_color_index =
			    mc_size * mc_size * mc_size + prim_size * 3 + i;
			curr_color = 0;
			curr_color |= ((u32) primary_color_values[i]) << 16;	//red
			curr_color |= ((u32) primary_color_values[i]) << 8;	//green
			curr_color |= ((u32) primary_color_values[i]);	//blue
			vbe_set_color(curr_color_index, curr_color);
		}

		// SLOF is using color 0x0 (black) and 0xFF (white) to draw to the screen...
		vbe_set_color(0x00, 0x00000000);
		vbe_set_color(0xFF, 0x00FFFFFF);

		output->screen_width = le16_to_cpu(best_mode_info.vesa.x_resolution);
		output->screen_height = le16_to_cpu(best_mode_info.vesa.y_resolution);
		output->screen_linebytes = le16_to_cpu(best_mode_info.vesa.bytes_per_scanline);
		output->color_depth = best_mode_info.vesa.bits_per_pixel;
		output->framebuffer_address =
		    le32_to_cpu(best_mode_info.vesa.phys_base_ptr);
	} else {
		printf("%s: No suitable video mode found!\n", __func__);
		//unset display_type...
		output->display_type = 0;
	}
	return 0;
}
#endif

static vbe_mode_info_t mode_info;

const vbe_mode_info_t *vbe_mode_info(void)
{
	if (!mode_info_valid || !mode_info.vesa.phys_base_ptr)
		return NULL;
	return &mode_info;
}

void vbe_set_graphics(void)
{
	u8 rval;

	vbe_info_t info;
	rval = vbe_info(&info);
	if (rval != 0)
		return;

	DEBUG_PRINTF_VBE("VbeSignature: %s\n", info.signature);
	DEBUG_PRINTF_VBE("VbeVersion: 0x%04x\n", info.version);
	DEBUG_PRINTF_VBE("OemString: %s\n", info.oem_string_ptr);
	DEBUG_PRINTF_VBE("Capabilities:\n");
	DEBUG_PRINTF_VBE("\tDAC: %s\n",
			 (info.capabilities & 0x1) ==
			 0 ? "fixed 6bit" : "switchable 6/8bit");
	DEBUG_PRINTF_VBE("\tVGA: %s\n",
			 (info.capabilities & 0x2) ==
			 0 ? "compatible" : "not compatible");
	DEBUG_PRINTF_VBE("\tRAMDAC: %s\n",
			 (info.capabilities & 0x4) ==
			 0 ? "normal" : "use blank bit in Function 09h");

	mode_info.video_mode = (1 << 14) | CONFIG_FRAMEBUFFER_VESA_MODE;
	vbe_get_mode_info(&mode_info);
	vbe_set_mode(&mode_info);

	const struct lb_framebuffer fb = {
		.physical_address    = mode_info.vesa.phys_base_ptr,
		.x_resolution        = le16_to_cpu(mode_info.vesa.x_resolution),
		.y_resolution        = le16_to_cpu(mode_info.vesa.y_resolution),
		.bytes_per_line      = le16_to_cpu(mode_info.vesa.bytes_per_scanline),
		.bits_per_pixel      = mode_info.vesa.bits_per_pixel,
		.red_mask_pos        = mode_info.vesa.red_mask_pos,
		.red_mask_size       = mode_info.vesa.red_mask_size,
		.green_mask_pos      = mode_info.vesa.green_mask_pos,
		.green_mask_size     = mode_info.vesa.green_mask_size,
		.blue_mask_pos       = mode_info.vesa.blue_mask_pos,
		.blue_mask_size      = mode_info.vesa.blue_mask_size,
		.reserved_mask_pos   = mode_info.vesa.reserved_mask_pos,
		.reserved_mask_size  = mode_info.vesa.reserved_mask_size,
		.orientation         = LB_FB_ORIENTATION_NORMAL,
	};

	fb_add_framebuffer_info_ex(&fb);
}

void vbe_textmode_console(void)
{
	/* Wait, just a little bit more, pleeeease ;-) */
	delay(2);

	M.x86.R_EAX = 0x0003;
	runInt10();
}

#endif