#include <kernel/kernel.h>
#include <kernel/driver.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>

/*!
 *  \ingroup	drivers
 *  \defgroup	pci PCI bus
 *  @{
 */

typedef struct pci_cfg_t pci_cfg_t;
struct pci_cfg_t
{
	/* normal header stuff */
	word vendor_id;
	word device_id;
	
	word command;
	word status;
	
	byte revision_id;
	byte interface;
	byte sub_class;
	byte base_class;
	
	byte cache_line_size;
	byte latency_timer;
	byte header_type;
	byte bist;	
	
	/* device info */
	byte bus;
	byte dev;
	byte func;
	byte irq;

	/* base registers */	
	dword base[6];
	dword size[6];

	word subsys_vendor;
	word subsys;
};

typedef struct confadd
{
	byte reg:8;
	byte func:3;
	byte dev:5;
	byte bus:8;
	byte rsvd:7;
	byte enable:1;
} confadd;

const struct
{
	byte base_class;
	byte sub_class;
	byte interface;
	const wchar_t* name;
} classes[] =
{
	{ 0x00, 0x00, 0x00, L"Undefined" },
	{ 0x00, 0x01, 0x00, L"VGA" },

	{ 0x01, 0x00, 0x00, L"SCSI" },
	{ 0x01, 0x01, 0x00, L"IDE" },
	{ 0x01, 0x02, 0x00, L"Floppy" },
	{ 0x01, 0x03, 0x00, L"IPI" },
	{ 0x01, 0x04, 0x00, L"RAID" },
	{ 0x01, 0x80, 0x00, L"Other" },

	{ 0x02, 0x00, 0x00, L"Ethernet" },
	{ 0x02, 0x01, 0x00, L"Token Ring" },
	{ 0x02, 0x02, 0x00, L"FDDI" },
	{ 0x02, 0x03, 0x00, L"ATM" },
	{ 0x02, 0x04, 0x00, L"ISDN" },
	{ 0x02, 0x80, 0x00, L"Other" },

	{ 0x03, 0x00, 0x00, L"VGA" },
	{ 0x03, 0x00, 0x01, L"VGA+8514" },
	{ 0x03, 0x01, 0x00, L"XGA" },
	{ 0x03, 0x02, 0x00, L"3D" },
	{ 0x03, 0x80, 0x00, L"Other" },

	{ 0x04, 0x00, 0x00, L"Video" },
	{ 0x04, 0x01, 0x00, L"Audio" },
	{ 0x04, 0x02, 0x00, L"Telephony" },
	{ 0x04, 0x80, 0x00, L"Other" },

	{ 0x05, 0x00, 0x00, L"RAM" },
	{ 0x05, 0x01, 0x00, L"Flash" },
	{ 0x05, 0x80, 0x00, L"Other" },

	{ 0x06, 0x00, 0x00, L"PCI to HOST" },
	{ 0x06, 0x01, 0x00, L"PCI to ISA" },
	{ 0x06, 0x02, 0x00, L"PCI to EISA" },
	{ 0x06, 0x03, 0x00, L"PCI to MCA" },
	{ 0x06, 0x04, 0x00, L"PCI to PCI" },
	{ 0x06, 0x04, 0x01, L"PCI to PCI (Subtractive Decode)" },
	{ 0x06, 0x05, 0x00, L"PCI to PCMCIA" },
	{ 0x06, 0x06, 0x00, L"PCI to NuBUS" },
	{ 0x06, 0x07, 0x00, L"PCI to Cardbus" },
	{ 0x06, 0x08, 0x00, L"PCI to RACEway" },
	{ 0x06, 0x09, 0x00, L"PCI to PCI" },
	{ 0x06, 0x0A, 0x00, L"PCI to InfiBand" },
	{ 0x06, 0x80, 0x00, L"PCI to Other" },

	{ 0x07, 0x00, 0x00, L"Serial" },
	{ 0x07, 0x00, 0x01, L"Serial - 16450" },
	{ 0x07, 0x00, 0x02, L"Serial - 16550" },
	{ 0x07, 0x00, 0x03, L"Serial - 16650" },
	{ 0x07, 0x00, 0x04, L"Serial - 16750" },
	{ 0x07, 0x00, 0x05, L"Serial - 16850" },
	{ 0x07, 0x00, 0x06, L"Serial - 16950" },
	{ 0x07, 0x01, 0x00, L"Parallel" },
	{ 0x07, 0x01, 0x01, L"Parallel - BiDir" },
	{ 0x07, 0x01, 0x02, L"Parallel - ECP" },
	{ 0x07, 0x01, 0x03, L"Parallel - IEEE1284" },
	{ 0x07, 0x01, 0xFE, L"Parallel - IEEE1284 Target" },
	{ 0x07, 0x02, 0x00, L"Multiport Serial" },
	{ 0x07, 0x03, 0x00, L"Hayes Compatible Modem" },
	{ 0x07, 0x03, 0x01, L"Hayes Compatible Modem, 16450" },
	{ 0x07, 0x03, 0x02, L"Hayes Compatible Modem, 16550" },
	{ 0x07, 0x03, 0x03, L"Hayes Compatible Modem, 16650" },
	{ 0x07, 0x03, 0x04, L"Hayes Compatible Modem, 16750" },
	{ 0x07, 0x80, 0x00, L"Other" },

	{ 0x08, 0x00, 0x00, L"PIC" },
	{ 0x08, 0x00, 0x01, L"ISA PIC" },
	{ 0x08, 0x00, 0x02, L"EISA PIC" },
	{ 0x08, 0x00, 0x10, L"I/O APIC" },
	{ 0x08, 0x00, 0x20, L"I/O(x) APIC" },
	{ 0x08, 0x01, 0x00, L"DMA" },
	{ 0x08, 0x01, 0x01, L"ISA DMA" },
	{ 0x08, 0x01, 0x02, L"EISA DMA" },
	{ 0x08, 0x02, 0x00, L"Timer" },
	{ 0x08, 0x02, 0x01, L"ISA Timer" },
	{ 0x08, 0x02, 0x02, L"EISA Timer" },
	{ 0x08, 0x03, 0x00, L"RTC" },
	{ 0x08, 0x03, 0x00, L"ISA RTC" },
	{ 0x08, 0x03, 0x00, L"Hot-Plug" },
	{ 0x08, 0x80, 0x00, L"Other" },

	{ 0x09, 0x00, 0x00, L"Keyboard" },
	{ 0x09, 0x01, 0x00, L"Pen" },
	{ 0x09, 0x02, 0x00, L"Mouse" },
	{ 0x09, 0x03, 0x00, L"Scanner" },
	{ 0x09, 0x04, 0x00, L"Game Port" },
	{ 0x09, 0x80, 0x00, L"Other" },

	{ 0x0a, 0x00, 0x00, L"Generic" },
	{ 0x0a, 0x80, 0x00, L"Other" },

	{ 0x0b, 0x00, 0x00, L"386" },
	{ 0x0b, 0x01, 0x00, L"486" },
	{ 0x0b, 0x02, 0x00, L"Pentium" },
	{ 0x0b, 0x03, 0x00, L"PentiumPro" },
	{ 0x0b, 0x10, 0x00, L"DEC Alpha" },
	{ 0x0b, 0x20, 0x00, L"PowerPC" },
	{ 0x0b, 0x30, 0x00, L"MIPS" },
	{ 0x0b, 0x40, 0x00, L"Coprocessor" },
	{ 0x0b, 0x80, 0x00, L"Other" },

	{ 0x0c, 0x00, 0x00, L"FireWire" },
	{ 0x0c, 0x00, 0x10, L"OHCI FireWire" },
	{ 0x0c, 0x01, 0x00, L"Access.bus" },
	{ 0x0c, 0x02, 0x00, L"SSA" },
	{ 0x0c, 0x03, 0x00, L"USB (UHCI)" },
	{ 0x0c, 0x03, 0x10, L"USB (OHCI)" },
	{ 0x0c, 0x03, 0x80, L"USB" },
	{ 0x0c, 0x03, 0xFE, L"USB Device" },
	{ 0x0c, 0x04, 0x00, L"Fiber" },
	{ 0x0c, 0x05, 0x00, L"SMBus Controller" },
	{ 0x0c, 0x06, 0x00, L"InfiniBand" },
	{ 0x0c, 0x80, 0x00, L"Other" },

	{ 0x0d, 0x00, 0x00, L"iRDA" },
	{ 0x0d, 0x01, 0x00, L"Consumer IR" },
	{ 0x0d, 0x10, 0x00, L"RF" },
	{ 0x0d, 0x80, 0x00, L"Other" },

	{ 0x0e, 0x00, 0x00, L"I2O" },
	{ 0x0e, 0x80, 0x00, L"Other" },

	{ 0x0f, 0x01, 0x00, L"TV" },
	{ 0x0f, 0x02, 0x00, L"Audio" },
	{ 0x0f, 0x03, 0x00, L"Voice" },
	{ 0x0f, 0x04, 0x00, L"Data" },
	{ 0x0f, 0x80, 0x00, L"Other" },

	{ 0x10, 0x00, 0x00, L"Network" },
	{ 0x10, 0x10, 0x00, L"Entertainment" },
	{ 0x10, 0x80, 0x00, L"Other" },

	{ 0x11, 0x00, 0x00, L"DPIO Modules" },
	{ 0x11, 0x01, 0x00, L"Performance Counters" },
	{ 0x11, 0x10, 0x00, L"Comm Sync, Time+Frequency Measurement" },
	{ 0x11, 0x80, 0x00, L"Other" },
};

dword pciRead(int bus, int dev, int func, int reg, int bytes)
{
	word base;
	
	union {
		confadd c;
		dword n;
	} u;
	
	u.n = 0;
	u.c.enable = 1;
	u.c.rsvd = 0;
	u.c.bus = bus;
	u.c.dev = dev;
	u.c.func = func;
	u.c.reg = reg & 0xFC;
	
	out32(0xCF8, u.n);
	
	base = 0xCFC + (reg & 0x03);
		
	switch(bytes){
	case 1: return in(base);
	case 2: return in16(base);
	case 4: return in32(base);
	default: return 0;
	}
}

void pciWrite(int bus, int dev, int func, int reg, dword v, int bytes)
{
	word base;
	
	union {
		confadd c;
		dword n;
	} u;
	
	u.n = 0;
	u.c.enable = 1;
	u.c.rsvd = 0;
	u.c.bus = bus;
	u.c.dev = dev;
	u.c.func = func;
	u.c.reg = reg & 0xFC;
	
	base = 0xCFC + (reg & 0x03);
	out32(0xCF8, u.n);
	switch(bytes){
	case 1: out(base, (byte) v); break;
	case 2: out16(base, (word) v); break;
	case 4: out32(base, v); break;
	}
	
}

bool pciProbe(int bus, int dev, int func, pci_cfg_t *cfg)
{
	dword *word = (dword *) cfg;
	dword v;	
	int i;
	for(i=0;i<4;i++){
		word[i] = pciRead(bus,dev,func,4*i,4);
	}
	if(cfg->vendor_id == 0xffff) return false;

	cfg->bus = bus;
	cfg->dev = dev;
	cfg->func = func;
	cfg->subsys_vendor = pciRead(bus, dev, func, 0x2c, 2);
	cfg->subsys = pciRead(bus, dev, func, 0x2e, 2);

#if 0
	wprintf(L"Device Info: /bus/pci/%d/%d/%d\n",bus,dev,func);
	wprintf(L"  * Vendor: %X   Device: %X  Class/SubClass/Interface %X/%X/%X\n",
		   cfg->vendor_id,cfg->device_id,cfg->base_class,cfg->sub_class,cfg->interface);
	wprintf(L"  * Status: %X  Command: %X  BIST/Type/Lat/CLS: %X/%X/%X/%X\n",
		   cfg->status, cfg->command, cfg->bist, cfg->header_type, 
		   cfg->latency_timer, cfg->cache_line_size);
#endif
			
	switch(cfg->header_type & 0x7F){
	case 0: /* normal device */
		for(i=0;i<6;i++){
			v = pciRead(bus,dev,func,i*4 + 0x10, 4);
			if(v) {
				int v2;
				pciWrite(bus,dev,func,i*4 + 0x10, 0xffffffff, 4);
				v2 = pciRead(bus,dev,func,i*4+0x10, 4) & 0xfffffff0;
				pciWrite(bus,dev,func,i*4 + 0x10, v, 4);
				v2 = 1 + ~v2;
				if(v & 1) {
//					printf("  * Base Register %d IO: %x (%x)\n",i,v&0xfff0,v2&0xffff);
					cfg->base[i] = v & 0xffff;
					cfg->size[i] = v2 & 0xffff;
				} else {
//					printf("  * Base Register %d MM: %x (%x)\n",i,v&0xfffffff0,v2);
					cfg->base[i] = v;
					cfg->size[i] = v2;
				}
			} else {
				cfg->base[i] = 0;
				cfg->size[i] = 0;
			}
			
		}
		v = pciRead(bus,dev,func,0x3c,1);
		cfg->irq = (v == 0xff ? 0 : v);
			
		//wprintf(L"  * Interrupt Line: %X\n",cfg->irq);
		break;
	case 1:
		//wprintf(L"  * PCI <-> PCI Bridge\n");
		break;
	default:
		//wprintf(L"  * Unknown Header Type\n");
	}
	return true;	
}

bool STDCALL INIT_CODE drvInit(driver_t *drv)
{
	word bus, dev, func;
	device_t *pci;
	wchar_t name[20];
	device_config_t* cfg;
	pci_cfg_t pcfg;
	int i, j;

	pci = hndAlloc(sizeof(device_t), NULL);
	pci->driver = drv;
	pci->req_first = pci->req_last = NULL;
	pci->request = NULL;
	devRegister(L"pci", pci, NULL);

	for (bus = 0; bus < 2/*255*/; bus++)
	{
		wprintf(L"PCI scan: %d%%\r", (bus * 100) / 255);

		for (dev = 0; dev < 32; dev++)
		{
			for (func = 0; func < 8; func++)
			{
				if (pciProbe(bus, dev, func, &pcfg))
				{
					//swprintf(name, L"pci/%d/%d", bus, dev);
					wcscpy(name, L"pcidev");
					
					cfg = hndAlloc(sizeof(device_config_t), NULL);
					cfg->parent = pci;
					cfg->vendor_id = pcfg.vendor_id;
					cfg->device_id = pcfg.device_id;
					cfg->subsystem = pcfg.subsys_vendor << 16 | pcfg.subsys;

					if (pcfg.irq)
						cfg->num_resources = 1;
					else
						cfg->num_resources = 0;

					for (i = 0; i < countof(pcfg.base); i++)
						if (pcfg.base[i])
							cfg->num_resources++;

					cfg->resources = hndAlloc(sizeof(device_resource_t) * 
						cfg->num_resources, NULL);
					for (i = 0, j = 0; i < countof(pcfg.base); i++)
					{
						if (pcfg.base[i])
						{
							if ((pcfg.base[i] & 0xffff0000) == 0)
							{
								cfg->resources[j].cls = dresIo;
								cfg->resources[j].u.io.base = pcfg.base[i];
								cfg->resources[j].u.io.length = pcfg.size[i];
							}
							else
							{
								cfg->resources[j].cls = dresMemory;
								cfg->resources[j].u.memory.base = pcfg.base[i];
								cfg->resources[j].u.memory.length = pcfg.size[i];
							}

							j++;
						}
					}

					if (pcfg.irq)
					{
						cfg->resources[j].cls = dresIrq;
						cfg->resources[j].u.irq = pcfg.irq;
					}
					
					for (i = 0; i < countof(classes); i++)
						if (pcfg.base_class == classes[i].base_class &&
							pcfg.sub_class == classes[i].sub_class &&
							pcfg.interface == classes[i].interface)
						{
							_cputws(classes[i].name);
							putwchar(' ');
							break;
						}

					devRegister(name, NULL, cfg);
				}
			}
		}
	}

	wprintf(L"PCI: finished\n");
	return true;
}

//@}