/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2012  Alexandru Gagniuc <mr.nuke.me@gmail.com>
 *
 * 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.
 */

#include <arch/io.h>
#include <console/console.h>
#include <device/pci.h>
#include <device/pci_ids.h>
#include <pc80/vga_io.h>
#include <stdlib.h>

#include "vx900.h"

#define CHROME_9_HD_MIN_FB_SIZE   8
#define CHROME_9_HD_MAX_FB_SIZE 512

/**
 * @file chrome9hd.c
 *
 * \brief Initialization for Chrome9HD integrated graphics adapters
 *
 * This takes care of the initialization we need to do before calling the VGA
 * BIOS. The device is not documented in the VX900 datasheet.
 *
 * The device is documented in:
 *	Open Graphics Programming Manual
 *	Chrome9GraphicsHD Processor
 *	VX900 Series System Processor
 *	Part I: Graphics Core / 2D
 *
 * This document was released by VIA to the Xorg project, and is available at:
 * <http://www.x.org/docs/via/OGPM_Chrome9%20HD%20DX9%20_R100_PartI_Core_2D.pdf>
 *
 * STATUS:
 * We do the minimal initialization described in VIA documents. Running the VGA
 * option ROM does not get us a usable display. We configure the framebuffer and
 * the IGP is able to use it. GRUB2 and linux are capable of getting a usable
 * text console, which uses the monitor's native resolution (even 1920x1080).
 * The graphical console (linux) does not work properly.
 * @todo
 * 1. Figure out what sequence we need to do to get the VGA BIOS running
 *    properly. Use the code provided by VIA and compare their sequence to ours,
 *    fill in any missing steps, etc.
 * 2. Make BAR2 and the framebuffer use the same memory space. This is a feature
 *    called "Direct framebuffer access" which allows us to save memory space by
 *    setting BAR2 of the VGA to the location in memory of the framebuffer. This
 *    reduces the amount of PCI MMIO space we need below 4G, and is especially
 *    useful considering we only have 8GB (33 bits) of memory-mapped space.
 */

/* Helper to determine the framebuffer size */
u32 chrome9hd_fb_size(void)
{
	static u32 fb_size = 0;
	u8 reg8, ranksize;
	u32 size_mb, tom_mb, max_size_mb;
	int i;
	/* We do some PCI and CMOS IO to find our value, so if we've already
	 * found it, save some time */
	if (fb_size != 0)
		return fb_size;
	/* FIXME: read fb_size from CMOS, but until that is implemented, start
	 * from 512MB */
	size_mb = 512;

	/* The minimum framebuffer size is 8MB. */
	size_mb = MAX(size_mb, CHROME_9_HD_MIN_FB_SIZE);

	const device_t mcu = dev_find_device(PCI_VENDOR_ID_VIA,
					     PCI_DEVICE_ID_VIA_VX900_MEMCTRL,
					     0);
	/*
	 * We have two limitations on the maximum framebuffer size:
	 * 1) (Sanity) No more that 1/4 of system RAM
	 * 2) (Hardware limitation) No larger than DRAM in last rank
	 * Check both of these limitations and apply them to our framebuffer */
	tom_mb = (pci_read_config16(mcu, 0x88) & 0x07ff) << (24 - 20);
	max_size_mb = tom_mb >> 2;
	if (size_mb > max_size_mb) {
		printk(BIOS_ALERT, "The framebuffer size of of %dMB is larger"
		       " than 1/4 of available memory.\n"
		       " Limiting framebuffer to %dMB\n", size_mb, max_size_mb);
		size_mb = max_size_mb;
	}

	/* Now handle limitation #2
	 * Look at the ending address of the memory ranks, from last to first,
	 * until we find one that is not zero. That is our last rank, and its
	 * size is the limit of our framebuffer. */
	/* FIXME:  This has a bug. If we remap memory above 4G, we consider the
	 * memory hole as part of our RAM. Thus if we install 3G, with a TOLM of
	 * 2.5G, our TOM will be at 5G and we'll assume we have 5G RAM instead
	 * of the actual 3.5G */
	for (i = VX900_MAX_MEM_RANKS - 1; i > -1; i--) {
		reg8 = pci_read_config8(mcu, 0x40 + i);
		if (reg8 == 0)
			continue;
		/* We've reached the last populated rank */
		ranksize = reg8 - pci_read_config8(mcu, 0x48 + i);
		max_size_mb = ranksize << 6;
		/* That's it. We got what we needed. */
		break;
	};
	if (size_mb > max_size_mb) {
		printk(BIOS_ALERT, "The framebuffer size of %dMB is larger"
		       " than size of the last DRAM rank.\n"
		       " Limiting framebuffer to %dMB\n", size_mb, max_size_mb);
		size_mb = max_size_mb;
	}

	/* Now round the framebuffer size to the closest power of 2 */
	u8 fb_pow = 0;
	while (size_mb >> fb_pow)
		fb_pow++;
	fb_pow--;
	size_mb = (1 << fb_pow);
	/* We store the framebuffer size in bytes, for simplicity */
	fb_size = size_mb << 20;
	return fb_size;
}

/**
 * vx900_int15
 *
 * \brief INT15 helpers for Chrome9HD IGP
 *
 * The following are helpers for INT15 handlers for the VGA BIOS. The full set
 * of INT15 callbacks is described in
 *
 *	VIA/S3Graphics
 *	Video BIOS External Interface Specification for Chrome9 Series IGP
 *	VX900 Series
 *
 * This document is only available under NDA, however, the callbacks are very
 * similar to other VIA/Intel IGP callbacks.
 *
 * Callback 0x5f18 is the most important one. It informs the VGA BIOS of the
 * RAM speed and framebuffer size. The other callbacks seem to be optional.
 * @{
 */

/**
 * \brief Get X86_BL value for VGA INT15 function 5f18
 *
 * Int15 5f18 lets the VGA BIOS know the framebuffer size and the memory speed.
 * This handler is very important. If it is not implemented, the VGA BIOS will
 * not work correctly.
 *
 * To use, just call this from the 15f18 handler, and place the return value in
 * X86_BL
 *
 * @code{.c}
 *	case 0x5f18:
 *		X86_BX = vx900_int15_get_5f18_bl();
 *		res = 0;
 *		break;
 * @endcode
 *
 */
u8 vx900_int15_get_5f18_bl(void)
{
	u8 reg8, ret;
	device_t dev;
	/*
	 * BL Bit[7:4]
	 * Memory Data Rate (not to be confused with fCLK)
	 * 0000: 66MHz
	 * 0001: 100MHz
	 * 0010: 133MHz
	 * 0011: 200MHz ( DDR200 )
	 * 0100: 266MHz ( DDR266 )
	 * 0101: 333MHz ( DDR333 )
	 * 0110: 400MHz ( DDR400 )
	 * 0111: 533MHz ( DDR I/II 533)
	 * 1000: 667MHz ( DDR I/II 667)
	 * 1001: 800MHz  ( DDR3 800)
	 * 1010: 1066MHz ( DDR3 1066)
	 * 1011: 1333MHz ( DDR3 1333)
	 * Bit[3:0]
	 * N:  Frame Buffer Size 2^N  MB
	 */
	dev = dev_find_slot(0, PCI_DEVFN(0, 3));
	reg8 = pci_read_config8(dev, 0xa1);
	ret = (u32) ((reg8 & 0x70) >> 4) + 2;
	reg8 = pci_read_config8(dev, 0x90);
	reg8 = ((reg8 & 0x07) + 3) << 4;
	ret |= (u32) reg8;

	return ret;
}

/** @} */

static void chrome9hd_set_sid_vid(u16 vendor, u16 device)
{
	vga_sr_write(0x36, vendor >> 8);	/* SVID high byte */
	vga_sr_write(0x35, vendor & 0xff);	/* SVID low  byte */
	vga_sr_write(0x38, device >> 8);	/*  SID high byte */
	vga_sr_write(0x37, device & 0xff);	/*  SID low  byte */
}

static void chrome9hd_handle_uma(device_t dev)
{
	/* Mirror mirror, shiny glass, tell me that is not my ass */
	u32 fb_size = chrome9hd_fb_size() >> 20;

	u8 fb_pow = 0;
	while (fb_size >> fb_pow)
		fb_pow++;
	fb_pow--;

	/* Step 6 - Let MCU know the framebuffer size */
	device_t mcu = dev_find_device(PCI_VENDOR_ID_VIA,
				       PCI_DEVICE_ID_VIA_VX900_MEMCTRL, 0);
	pci_mod_config8(mcu, 0xa1, 7 << 4, (fb_pow - 2) << 4);

	/* Step 7 - Let GFX know the framebuffer size (through PCI and IOCTL)
	 * The size we set here affects the behavior of BAR2, and the amount of
	 * MMIO space it requests. The default is 512MB, so if we don't set this
	 * before reading the resources, we could waste space below 4G */
	pci_write_config8(dev, 0xb2, ((0xff << (fb_pow - 2)) & ~(1 << 7)));
	vga_sr_write(0x68, (0xff << (fb_pow - 1)));
	/* And also that the framebuffer is in the system, RAM */
	pci_mod_config8(dev, 0xb0, 0, 1 << 0);
}

/**
 * \brief Initialization sequence before running the VGA BIOS
 *
 * This is the initialization sequence described in:
 *
 *	BIOS Porting Guide
 *	VX900 Series
 *	All-in-One System Processor
 *
 * This document is only available under NDA.
 */
static void chrome9hd_biosguide_init_seq(device_t dev)
{
	device_t mcu = dev_find_device(PCI_VENDOR_ID_VIA,
				       PCI_DEVICE_ID_VIA_VX900_MEMCTRL, 0);
	device_t host = dev_find_device(PCI_VENDOR_ID_VIA,
					PCI_DEVICE_ID_VIA_VX900_HOST_BR, 0);

	/* Step 1 - Enable VGA controller */
	/* FIXME: This is the VGA hole @ 640k-768k, and the vga port io
	 * We need the port IO, but can we disable the memory hole? */
	pci_mod_config8(mcu, 0xa4, 0, (1 << 7));	/* VGA memory hole */

	/* Step 2 - Forward MDA cycles to GFX */
	pci_mod_config8(host, 0x4e, 0, (1 << 1));

	/* Step 3 - Enable GFX I/O space */
	pci_mod_config8(dev, PCI_COMMAND, 0, PCI_COMMAND_IO);

	/* Step 4 - Enable video subsystem */
	vga_enable_mask((1 << 0), (1 << 0));

	/* FIXME: VGA IO Address Select. 3B5 or 3D5? */
	vga_misc_mask((1 << 0), (1 << 0));

	/* Step 5 - Unlock accessing of IO space */
	vga_sr_write(0x10, 0x01);

	chrome9hd_handle_uma(dev);

	uint64_t gfx_base = get_uma_memory_base();
	if (gfx_base == 0)
		die("uma_memory_base not set. Abandon ship!\n");

	/* Step 8 - Enable memory base register on the GFX */
	vga_sr_write(0x6d, (gfx_base >> 21) & 0xff);	/* base 28:21 */
	vga_sr_write(0x6e, (gfx_base >> 29) & 0xff);	/* base 36:29 */
	vga_sr_write(0x6f, 0x00);	/* base 43:37 */

	/* Step 9 - Set SID/VID */
	chrome9hd_set_sid_vid(0x1106, 0x7122);

}

static void chrome9hd_init(device_t dev)
{
	printk(BIOS_DEBUG, "======================================================\n");
	printk(BIOS_DEBUG, "== Chrome9 HD INIT\n");
	printk(BIOS_DEBUG, "======================================================\n");

	chrome9hd_biosguide_init_seq(dev);

	/* Prime PLL FIXME: bad comment */
	vga_sr_mask(0x3c, 1 << 2, 1 << 2);

	/* FIXME: recheck; VGA IO Address Select. 3B5 or 3D5? */
	vga_misc_mask(1 << 0, 1 << 0);

	/* FIXME: recheck; Enable Base VGA 16 Bits Decode */

	u32 fb_address = pci_read_config32(dev, PCI_BASE_ADDRESS_2);
	fb_address &= ~0x0F;
	if (!fb_address) {
		printk(BIOS_WARNING, "Chrome9HD: No FB BAR assigned!\n");
		return;
	}

	printk(BIOS_INFO, "Chrome: Using %dMB Framebuffer at 0x%08X.\n",
	       256, fb_address);

	printk(BIOS_DEBUG, "Initializing VGA...\n");

	pci_dev_init(dev);

	printk(BIOS_DEBUG, "Enable VGA console\n");

	dump_pci_device(dev);
}

static void chrome9hd_enable(device_t dev)
{
	device_t mcu = dev_find_device(PCI_VENDOR_ID_VIA,
				       PCI_DEVICE_ID_VIA_VX900_MEMCTRL, 0);
	/* FIXME: here? -=- ACLK 250MHz */
	pci_mod_config8(mcu, 0xbb, 0, 0x01);
}

static void chrome9hd_disable(device_t dev)
{
	device_t mcu = dev_find_device(PCI_VENDOR_ID_VIA,
				       PCI_DEVICE_ID_VIA_VX900_MEMCTRL, 0);
	/* Disable GFX - This step effectively renders the GFX inert
	 * It won't even show up as a PCI device during enumeration */
	pci_mod_config8(mcu, 0xa1, 1 << 7, 0);
}

static struct device_operations chrome9hd_operations = {
	.read_resources = pci_dev_read_resources,
	.set_resources = pci_dev_set_resources,
	.enable_resources = pci_dev_enable_resources,
	.init = chrome9hd_init,
	.disable = chrome9hd_disable,
	.enable = chrome9hd_enable,
	.ops_pci = 0,
};

static const struct pci_driver chrome9hd_driver __pci_driver = {
	.ops = &chrome9hd_operations,
	.vendor = PCI_VENDOR_ID_VIA,
	.device = PCI_DEVICE_ID_VIA_VX900_VGA,
};
