/* 
 * Copyright 2010 Stefan Lankes, Chair for Operating Systems,
 *                               RWTH Aachen University
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * This file is part of MetalSVM. 
 */

#include <metalsvm/stdio.h>
#include <metalsvm/string.h>
#include <metalsvm/errno.h>
#include <asm/irqflags.h>
#include <asm/io.h>

#ifdef CONFIG_PCI

#include <asm/pci.h>
#include "pcihdr.h"

/*
 * PCI configuration registers
 */
#define	PCI_CFID	0x00	/* Configuration ID */
#define	PCI_CFCS	0x04	/* Configurtion Command/Status */
#define	PCI_CFRV	0x08	/* Configuration Revision */
#define	PCI_CFLT	0x0c	/* Configuration Latency Timer */
#define	PCI_CBIO	0x10	/* Configuration Base IO Address */
#define	PCI_CBMA	0x14	/* Configuration Base Memory Address */
#define	PCI_CFIT	0x3c	/* Configuration Interrupt */
#define	PCI_CFDA	0x40	/* Configuration Driver Area */

#define PHYS_IO_MEM_START	0
#define	PCI_MEM			0
#define	PCI_INTA		0
#define PCI_NSLOTS		22
#define PCI_NBUS		0

#define	PCI_CONF_ADDR_REG	0xcf8
#define	PCI_CONF_FRWD_REG	0xcf8
#define	PCI_CONF_DATA_REG	0xcfc

#define PCI_IO_CONF_START	0xc000

#define MAX_BUS			16
#define MAX_SLOTS		32

static uint32_t mechanism = 0;
static uint32_t adapters[MAX_BUS][MAX_SLOTS] = {[0 ... MAX_BUS-1][0 ... MAX_SLOTS-1] = -1};

static void pci_conf_write(uint32_t bus, uint32_t slot, uint32_t off, uint32_t val)
{
	if (mechanism == 1) {
		outportl(PCI_CONF_FRWD_REG, bus);
		outportl(PCI_CONF_ADDR_REG, 0xf0);
		outportl(PCI_IO_CONF_START | (slot << 8) | off, val);
	} else {
		outportl(PCI_CONF_ADDR_REG,
		      (0x80000000 | (bus << 16) | (slot << 11) | off));
		outportl(PCI_CONF_DATA_REG, val);
	}
}

static uint32_t pci_conf_read(uint32_t bus, uint32_t slot, uint32_t off)
{
	uint32_t data = -1;

	outportl(PCI_CONF_ADDR_REG,
	      (0x80000000 | (bus << 16) | (slot << 11) | off));
	data = inportl(PCI_CONF_DATA_REG);

	if ((data == 0xffffffff) && (slot < 0x10)) {
		outportl(PCI_CONF_FRWD_REG, bus);
		outportl(PCI_CONF_ADDR_REG, 0xf0);
		data = inportl(PCI_IO_CONF_START | (slot << 8) | off);
		if (data == 0xffffffff)
			return data;
		if (!mechanism)
			mechanism = 1;
	} else if (!mechanism)
		mechanism = 2;

	return data;
}

static inline uint32_t pci_what_irq(uint32_t bus, uint32_t slot)
{
	return pci_conf_read(bus, slot, PCI_CFIT) & 0xFF;
}

static inline uint32_t pci_what_iobase(uint32_t bus, uint32_t slot)
{
	return pci_conf_read(bus, slot, PCI_CBIO) & 0xFFFFFFFC;
}

int pci_init(void)
{
	uint32_t slot, bus;
	
	for (bus = 0; bus < MAX_BUS; bus++)
		for (slot = 0; slot < MAX_SLOTS; slot++)
			adapters[bus][slot] = pci_conf_read(bus, slot, PCI_CFID);
	
	return 0;
}

int pci_get_device_info(uint32_t vendor_id, uint32_t device_id, uint32_t* iobase, uint32_t* irq)
{
	uint32_t slot, bus;

	if (!iobase || !irq)
		return -EINVAL;

	if (!mechanism)
		pci_init();

	for (bus = 0; bus < MAX_BUS; bus++) {
		for (slot = 0; slot < MAX_SLOTS; slot++) {
			if (adapters[bus][slot] != -1) {
				if (((adapters[bus][slot] & 0xffff) == vendor_id) && 
				   (((adapters[bus][slot] & 0xffff0000) >> 16) == device_id)) {
					*iobase = pci_what_iobase(bus, slot);
					*irq = pci_what_irq(bus, slot);
					return 0;
				}
			}
		}
	}

	return -EINVAL;
}

int print_pci_adapters(void)
{
	uint32_t slot, bus;
	uint32_t i, counter = 0;

	if (!mechanism)
		pci_init();

	for (bus = 0; bus < MAX_BUS; bus++) {
                for (slot = 0; slot < MAX_SLOTS; slot++) {

		if (adapters[bus][slot] != -1) {
				counter++;
				kprintf("%d) Vendor ID: 0x%x  Device Id: 0x%x\n",
					counter, adapters[bus][slot] & 0xffff, 
					(adapters[bus][slot] & 0xffff0000) >> 16);

				for (i=0; i<PCI_VENTABLE_LEN; i++) {
					if ((adapters[bus][slot] & 0xffff) ==
					    (uint32_t)PciVenTable[i].VenId)
						kprintf("\tVendor is %s\n",
							PciVenTable[i].VenShort);
				}

				for (i=0; i<PCI_DEVTABLE_LEN; i++) {
					if ((adapters[bus][slot] & 0xffff) ==
					    (uint32_t)PciDevTable[i].VenId) {
						if (((adapters[bus][slot] & 0xffff0000) >> 16) ==
						    PciDevTable[i].DevId) {
							kprintf
							    ("\tChip: %s ChipDesc: %s\n",
							     PciDevTable[i].Chip,
							     PciDevTable[i].ChipDesc);
						}
					}
				}
			}
		}
	}

	return 0;
}

#endif