/*
 * Copyright 2007-2012 Haiku, Inc.  All rights reserved.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *		Gerald Zajac
 */
 
 
#include <AGP.h>
#include <KernelExport.h>
#include <PCI.h>
#include <drivers/bios.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <graphic_driver.h>
#include <boot_item.h>
 
#include "DriverInterface.h"
 
 
#undef TRACE
 
#ifdef ENABLE_DEBUG_TRACE
#	define TRACE(x...) dprintf("i810: " x)
#else
#	define TRACE(x...) ;
#endif
 
 
#define ACCELERANT_NAME    "intel_810.accelerant"
 
#define ROUND_TO_PAGE_SIZE(x) (((x) + (B_PAGE_SIZE) - 1) & ~((B_PAGE_SIZE) - 1))
 
#define MAX_DEVICES		4
#define DEVICE_FORMAT	"%04X_%04X_%02X%02X%02X"
 
#define VENDOR_ID	0x8086	// Intel vendor ID
 
 
struct ChipInfo {
	uint16		chipID;		// PCI device id of the chip
	const char*	chipName;	// user recognizable name (must be < 32 chars)
};
 
 
// This table maps a PCI device ID to a chip type identifier and the chip name.
 
static const ChipInfo chipTable[] = {
	{ 0x7121, "i810" },
	{ 0x7123, "i810-dc100" },
	{ 0x7125, "i810e" },
	{ 0x1132, "i815" },
	{ 0, NULL }
};
 
 
struct DeviceInfo {
	uint32			openCount;		// how many times device has been opened
	int32			flags;
	area_id 		sharedArea;		// shared between driver and accelerants
	SharedInfo* 	sharedInfo;		// pointer to shared info area memory
	vuint8*	 		regs;			// pointer to memory mapped registers
	const ChipInfo*	pChipInfo;		// info about the selected chip
	pci_info		pciInfo;		// copy of pci info for this device
	area_id			gttArea;		// area used for GTT
	addr_t			gttAddr;		// virtual address of GTT
	char			name[B_OS_NAME_LENGTH]; // name of device
};
 
 
static Benaphore		gLock;
static DeviceInfo		gDeviceInfo[MAX_DEVICES];
static char*			gDeviceNames[MAX_DEVICES + 1];
static pci_module_info*	gPCI;
 
 
// Prototypes for device hook functions.
static status_t device_open(const char* name, uint32 flags, void** cookie);
static status_t device_close(void* dev);
static status_t device_free(void* dev);
static status_t device_read(void* dev, off_t pos, void* buf, size_t* len);
static status_t device_write(void* dev, off_t pos, const void* buf,
	size_t* len);
static status_t device_ioctl(void* dev, uint32 msg, void* buf, size_t len);
 
static device_hooks gDeviceHooks =
{
	device_open,
	device_close,
	device_free,
	device_ioctl,
	device_read,
	device_write,
	NULL,
	NULL,
	NULL,
	NULL
};
 
 
// Video chip register definitions.
// =================================
 
#define INTERRUPT_ENABLED		0x020a0
#define INTERRUPT_MASK			0x020a8
 
// Graphics address translation table.
#define PAGE_TABLE_CONTROL		0x02020
#define PAGE_TABLE_ENABLED		0x01
 
#define PTE_BASE				0x10000
#define PTE_VALID				0x01
 
 
// Macros for memory mapped I/O.
// ==============================
 
#define INREG16(addr)		(*((vuint16*)(di.regs + (addr))))
#define INREG32(addr)		(*((vuint32*)(di.regs + (addr))))
 
#define OUTREG16(addr, val)	(*((vuint16*)(di.regs + (addr))) = (val))
#define OUTREG32(addr, val)	(*((vuint32*)(di.regs + (addr))) = (val))
 
 
static inline uint32
GetPCI(pci_info& info, uint8 offset, uint8 size)
{
	return gPCI->read_pci_config(info.bus, info.device, info.function, offset,
		size);
}
 
 
static inline void
SetPCI(pci_info& info, uint8 offset, uint8 size, uint32 value)
{
	gPCI->write_pci_config(info.bus, info.device, info.function, offset, size,
		value);
}
 
 
static status_t
GetEdidFromBIOS(edid1_raw& edidRaw)
{
	// Get the EDID info from the video BIOS, and return B_OK if successful.
 
	#define ADDRESS_SEGMENT(address) ((addr_t)(address) >> 4)
	#define ADDRESS_OFFSET(address) ((addr_t)(address) & 0xf)
 
	bios_module_info* biosModule;
	status_t status = get_module(B_BIOS_MODULE_NAME, (module_info**)&biosModule);
	if (status != B_OK) {
		TRACE("GetEdidFromBIOS(): failed to get BIOS module: 0x%" B_PRIx32 "\n",
			status);
		return status;
	}
 
	bios_state* state;
	status = biosModule->prepare(&state);
	if (status != B_OK) {
		TRACE("GetEdidFromBIOS(): bios_prepare() failed: 0x%" B_PRIx32 "\n",
			status);
		put_module(B_BIOS_MODULE_NAME);
		return status;
	}
 
	bios_regs regs = {};
	regs.eax = 0x4f15;
	regs.ebx = 0;			// 0 = report DDC service
	regs.ecx = 0;
	regs.es = 0;
	regs.edi = 0;
 
	status = biosModule->interrupt(state, 0x10, &regs);
	if (status == B_OK) {
		// AH contains the error code, and AL determines whether or not the
		// function is supported.
		if (regs.eax != 0x4f)
			status = B_NOT_SUPPORTED;
 
		// Test if DDC is supported by the monitor.
		if ((regs.ebx & 3) == 0)
			status = B_NOT_SUPPORTED;
	}
 
	if (status == B_OK) {
		edid1_raw* edid = (edid1_raw*)biosModule->allocate_mem(state,
			sizeof(edid1_raw));
		if (edid == NULL) {
			status = B_NO_MEMORY;
			goto out;
		}
 
		regs.eax = 0x4f15;
		regs.ebx = 1;		// 1 = read EDID
		regs.ecx = 0;
		regs.edx = 0;
		regs.es  = ADDRESS_SEGMENT(edid);
		regs.edi = ADDRESS_OFFSET(edid);
 
		status = biosModule->interrupt(state, 0x10, &regs);
		if (status == B_OK) {
			if (regs.eax != 0x4f) {
				status = B_NOT_SUPPORTED;
			} else {
				// Copy the EDID info to the caller's location, and compute the
				// checksum of the EDID info while copying.
 
				uint8 sum = 0;
				uint8 allOr = 0;
				uint8* dest = (uint8*)&edidRaw;
				uint8* src = (uint8*)edid;
 
				for (uint32 j = 0; j < sizeof(edidRaw); j++) {
					sum += *src;
					allOr |= *src;
					*dest++ = *src++;
				}
 
				if (allOr == 0) {
					TRACE("GetEdidFromBIOS(); EDID info contains only zeros\n");
					status = B_ERROR;
				} else if (sum != 0) {
					TRACE("GetEdidFromBIOS(); Checksum error in EDID info\n");
					status = B_ERROR;
				}
			}
		}
	}
 
out:
	biosModule->finish(state);
	put_module(B_BIOS_MODULE_NAME);
 
	TRACE("GetEdidFromBIOS() status: 0x%" B_PRIx32 "\n", status);
	return status;
}
 
 
static status_t
InitDevice(DeviceInfo& di)
{
	// Perform initialization and mapping of the device, and return B_OK if
	// sucessful;  else, return error code.
 
	TRACE("enter InitDevice()\n");
 
	// Create the area for shared info with NO user-space read or write
	// permissions, to prevent accidental damage.
 
	size_t sharedSize = (sizeof(SharedInfo) + 7) & ~7;
 
	di.sharedArea = create_area("i810 shared info",
		(void**) &(di.sharedInfo),
		B_ANY_KERNEL_ADDRESS,
		ROUND_TO_PAGE_SIZE(sharedSize),
		B_FULL_LOCK,
		B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_USER_CLONEABLE_AREA);
	if (di.sharedArea < 0)
		return di.sharedArea;	// return error code
 
	SharedInfo& si = *(di.sharedInfo);
	memset(&si, 0, sharedSize);
	si.regsArea = -1;			// indicate area has not yet been created
	si.videoMemArea = -1;
 
	pci_info& pciInfo = di.pciInfo;
 
	si.vendorID = pciInfo.vendor_id;
	si.deviceID = pciInfo.device_id;
	si.revision = pciInfo.revision;
	strcpy(si.chipName, di.pChipInfo->chipName);
 
	// Enable memory mapped IO and bus master.
 
	SetPCI(pciInfo, PCI_command, 2, GetPCI(pciInfo, PCI_command, 2)
		| PCI_command_io | PCI_command_memory | PCI_command_master);
 
	// Map the MMIO register area.
 
	phys_addr_t regsBase = pciInfo.u.h0.base_registers[1];
	uint32 regAreaSize = pciInfo.u.h0.base_register_sizes[1];
 
	si.regsArea = map_physical_memory("i810 mmio registers",
		regsBase,
		regAreaSize,
		B_ANY_KERNEL_ADDRESS,
		B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_USER_CLONEABLE_AREA,
		(void**)&di.regs);
 
	if (si.regsArea < 0) {
		TRACE("Unable to map MMIO, error: 0x%lx\n", si.regsArea);
		return si.regsArea;
	}
 
	// Allocate memory for the GTT which must be 64K for the 810/815 chips.
 
	uint32 gttSize = 64 * 1024;
	di.gttArea = create_area("GTT memory", (void**) &(di.gttAddr),
		B_ANY_KERNEL_ADDRESS, gttSize, B_FULL_LOCK | B_CONTIGUOUS,
		B_READ_AREA | B_WRITE_AREA);
 
	if (di.gttArea < B_OK) {
		TRACE("Unable to create GTT, error: 0x%lx\n", di.gttArea);
		return B_NO_MEMORY;
	}
 
	memset((void*)(di.gttAddr), 0, gttSize);
 
	// Get the physical address of the GTT, and set GTT address in the chip.
 
	physical_entry entry;
	status_t status = get_memory_map((void *)(di.gttAddr),
		B_PAGE_SIZE, &entry, 1);
	if (status < B_OK) {
		TRACE("Unable to get physical address of GTT, error: 0x%lx\n", status);
		return status;
	}
 
	OUTREG32(PAGE_TABLE_CONTROL, entry.address | PAGE_TABLE_ENABLED);
	INREG32(PAGE_TABLE_CONTROL);
 
	// Allocate video memory to be used for the frame buffer.
 
	si.videoMemSize = 4 * 1024 * 1024;
	si.videoMemArea = create_area("video memory", (void**)&(si.videoMemAddr),
		B_ANY_ADDRESS, si.videoMemSize, B_FULL_LOCK,
		B_READ_AREA | B_WRITE_AREA);
	if (si.videoMemArea < B_OK) {
		TRACE("Unable to create video memory, error: 0x%lx\n", si.videoMemArea);
		return B_NO_MEMORY;
	}
 
	// Get the physical address of each page of the video memory, and put
	// the physical address of each page into the GTT table.
 
	for (uint32 offset = 0; offset < si.videoMemSize; offset += B_PAGE_SIZE) {
		status = get_memory_map((void *)(si.videoMemAddr + offset),
			B_PAGE_SIZE, &entry, 1);
		if (status < B_OK) {
			TRACE("Unable to get physical address of video memory page, error:"
				" 0x%lx  offset: %ld\n", status, offset);
			return status;
		}
 
		if (offset == 0)
			si.videoMemPCI = entry.address;
 
		OUTREG32(PTE_BASE + ((offset / B_PAGE_SIZE) * 4),
			entry.address | PTE_VALID);
	}
 
	TRACE("InitDevice() exit OK\n");
	return B_OK;
}
 
 
static void
DeleteAreas(DeviceInfo& di)
{
	// Delete all areas that were created.
 
	if (di.sharedArea >= 0 && di.sharedInfo != NULL) {
		SharedInfo& si = *(di.sharedInfo);
		if (si.regsArea >= 0)
			delete_area(si.regsArea);
		if (si.videoMemArea >= 0)
			delete_area(si.videoMemArea);
	}
 
	if (di.gttArea >= 0)
		delete_area(di.gttArea);
	di.gttArea = -1;
	di.gttAddr = (addr_t)NULL;
 
	if (di.sharedArea >= 0)
		delete_area(di.sharedArea);
	di.sharedArea = -1;
	di.sharedInfo = NULL;
}
 
 
static const ChipInfo*
GetNextSupportedDevice(uint32& pciIndex, pci_info& pciInfo)
{
	// Search the PCI devices for a device that is supported by this driver.
	// The search starts at the device specified by argument pciIndex, and
	// continues until a supported device is found or there are no more devices
	// to examine.  Argument pciIndex is incremented after each device is
	// examined.
 
	// If a supported device is found, return a pointer to the struct containing
	// the chip info; else return NULL.
 
	while (gPCI->get_nth_pci_info(pciIndex, &pciInfo) == B_OK) {
 
		if (pciInfo.vendor_id == VENDOR_ID) {
 
			// Search the table of supported devices to find a chip/device that
			// matches device ID of the current PCI device.
 
			const ChipInfo* pDevice = chipTable;
 
			while (pDevice->chipID != 0) {	// end of table?
				if (pDevice->chipID == pciInfo.device_id)
					return pDevice; // matching device/chip found
 
				pDevice++;
			}
		}
 
		pciIndex++;
	}
 
	return NULL; // no supported device found
}
 
 
//	#pragma mark - Kernel Interface
 
 
status_t
init_hardware(void)
{
	// Return B_OK if a device supported by this driver is found; otherwise,
	// return B_ERROR so the driver will be unloaded.
 
	status_t status = get_module(B_PCI_MODULE_NAME, (module_info**)&gPCI);
	if (status != B_OK) {
		TRACE("PCI module unavailable, error 0x%lx\n", status);
		return status;
	}
 
	// Check pci devices for a device supported by this driver.
 
	uint32 pciIndex = 0;
	pci_info pciInfo;
	const ChipInfo* pDevice = GetNextSupportedDevice(pciIndex, pciInfo);
 
	TRACE("init_hardware() - %s\n",
		pDevice == NULL ? "no supported devices" : "device supported");
 
	put_module(B_PCI_MODULE_NAME);		// put away the module manager
 
	return (pDevice == NULL ? B_ERROR : B_OK);
}
 
 
status_t
init_driver(void)
{
	// Get handle for the pci bus.
 
	status_t status = get_module(B_PCI_MODULE_NAME, (module_info**)&gPCI);
	if (status != B_OK) {
		TRACE("PCI module unavailable, error 0x%lx\n", status);
		return status;
	}
 
	status = gLock.Init("i810 driver lock");
	if (status < B_OK) {
		put_module(B_AGP_GART_MODULE_NAME);
		put_module(B_PCI_MODULE_NAME);
		return status;
	}
 
	// Get info about all the devices supported by this driver.
 
	uint32 pciIndex = 0;
	uint32 count = 0;
 
	while (count < MAX_DEVICES) {
		DeviceInfo& di = gDeviceInfo[count];
 
		const ChipInfo* pDevice = GetNextSupportedDevice(pciIndex, di.pciInfo);
		if (pDevice == NULL)
			break;			// all supported devices have been obtained
 
		// Compose device name.
		sprintf(di.name, "graphics/" DEVICE_FORMAT,
			di.pciInfo.vendor_id, di.pciInfo.device_id,
			di.pciInfo.bus, di.pciInfo.device, di.pciInfo.function);
		TRACE("init_driver() match found; name: %s\n", di.name);
 
		gDeviceNames[count] = di.name;
		di.openCount = 0;		// mark driver as available for R/W open
		di.sharedArea = -1;		// indicate shared area not yet created
		di.sharedInfo = NULL;
		di.gttArea = -1;		// indicate GTT area not yet created
		di.gttAddr = (addr_t)NULL;
		di.pChipInfo = pDevice;
		count++;
		pciIndex++;
	}
 
	gDeviceNames[count] = NULL;	// terminate list with null pointer
 
	TRACE("init_driver() %ld supported devices\n", count);
 
	return B_OK;
}
 
 
void
uninit_driver(void)
{
	// Free the driver data.
 
	gLock.Delete();
	put_module(B_AGP_GART_MODULE_NAME);
	put_module(B_PCI_MODULE_NAME);	// put the pci module away
}
 
 
const char**
publish_devices(void)
{
	return (const char**)gDeviceNames;	// return list of supported devices
}
 
 
device_hooks*
find_device(const char* name)
{
	int i = 0;
	while (gDeviceNames[i] != NULL) {
		if (strcmp(name, gDeviceNames[i]) == 0)
			return &gDeviceHooks;
		i++;
	}
 
	return NULL;
}
 
 
//	#pragma mark - Device Hooks
 
 
static status_t
device_open(const char* name, uint32 /*flags*/, void** cookie)
{
	status_t status = B_OK;
 
	TRACE("device_open() - name: %s, cookie: 0x%" B_PRIXADDR "\n", name,
		(addr_t)cookie);
 
	// Find the device name in the list of devices.
 
	int32 i = 0;
	while (gDeviceNames[i] != NULL && (strcmp(name, gDeviceNames[i]) != 0))
		i++;
 
	if (gDeviceNames[i] == NULL)
		return B_BAD_VALUE;		// device name not found in list of devices
 
	DeviceInfo& di = gDeviceInfo[i];
 
	gLock.Acquire();	// make sure no one else has write access to common data
 
	if (di.openCount == 0) {
		status = InitDevice(di);
		if (status < B_OK)
			DeleteAreas(di);	// error occurred; delete any areas created
	}
 
	gLock.Release();
 
	if (status == B_OK) {
		di.openCount++;		// mark device open
		*cookie = &di;		// send cookie to opener
	}
 
	TRACE("device_open() returning 0x%lx,  open count: %ld\n", status,
		di.openCount);
	return status;
}
 
 
static status_t
device_read(void* dev, off_t pos, void* buf, size_t* len)
{
	// Following 3 lines of code are here to eliminate "unused parameter"
	// warnings.
	(void)dev;
	(void)pos;
	(void)buf;
 
	*len = 0;
	return B_NOT_ALLOWED;
}
 
 
static status_t
device_write(void* dev, off_t pos, const void* buf, size_t* len)
{
	// Following 3 lines of code are here to eliminate "unused parameter"
	// warnings.
	(void)dev;
	(void)pos;
	(void)buf;
 
	*len = 0;
	return B_NOT_ALLOWED;
}
 
 
static status_t
device_close(void* dev)
{
	(void)dev;		// avoid compiler warning for unused arg
 
	TRACE("device_close()\n");
	return B_NO_ERROR;
}
 
 
static status_t
device_free(void* dev)
{
	DeviceInfo& di = *((DeviceInfo*)dev);
 
	TRACE("enter device_free()\n");
 
	gLock.Acquire();		// lock driver
 
	// If opened multiple times, merely decrement the open count and exit.
 
	if (di.openCount <= 1)
		DeleteAreas(di);
 
	if (di.openCount > 0)
		di.openCount--;		// mark device available
 
	gLock.Release();	// unlock driver
 
	TRACE("exit device_free() openCount: %ld\n", di.openCount);
	return B_OK;
}
 
 
static status_t
device_ioctl(void* dev, uint32 msg, void* buffer, size_t bufferLength)
{
	DeviceInfo& di = *((DeviceInfo*)dev);
 
	TRACE("device_ioctl(); ioctl: %lu, buffer: 0x%" B_PRIXADDR ", "
		"bufLen: %lu\n", msg, (addr_t)buffer, bufferLength);
 
	switch (msg) {
		case B_GET_ACCELERANT_SIGNATURE:
			strcpy((char*)buffer, ACCELERANT_NAME);
			TRACE("Intel 810 accelerant: %s\n", ACCELERANT_NAME);
			return B_OK;
 
		case INTEL_DEVICE_NAME:
			strncpy((char*)buffer, di.name, B_OS_NAME_LENGTH);
			((char*)buffer)[B_OS_NAME_LENGTH -1] = '\0';
			return B_OK;
 
		case INTEL_GET_SHARED_DATA:
			if (bufferLength != sizeof(area_id))
				return B_BAD_DATA;
 
			*((area_id*)buffer) = di.sharedArea;
			return B_OK;
 
		case INTEL_GET_EDID:
		{
			if (bufferLength != sizeof(edid1_raw))
				return B_BAD_DATA;
 
			edid1_raw rawEdid;
			status_t status = GetEdidFromBIOS(rawEdid);
			if (status == B_OK)
				user_memcpy((edid1_raw*)buffer, &rawEdid, sizeof(rawEdid));
			return status;
		}
	}
 
	return B_DEV_INVALID_IOCTL;
}

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the third actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the third actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'dprintf' function. The memsize type argument is expected.