/*
 * pci.c (C) 2002 Minoru Murashima
 *
 * Copyright 2002, Minoru Murashima. All rights reserved.
 * Distributed under the terms of the BSD License.
 *
 * PCI制御
 */


#include"types.h"
#include"lib.h"
#include"errno.h"
#include"pci.h"


/**************************************************************************
 *
 * その他
 *
 **************************************************************************/


static uint pciMem=0xfef00000;


/*
 * IO memory manager.
 * 0xfec00000〜0xfec00040,0xfee00000〜0xfee00400,
 * IO APIC,               LOCAL APIC
 * parameters : need size
 * return : memory address
 */
uint getPciMemAddress(int size)
{
	int addr;


	/* 0x100000ごとにメモリーを割り当てる。 */
	addr=pciMem;
	pciMem+=ROUNDUP(size,0x100000);

	return addr;
}


/**************************************************************************
 *
 * PCI CONFIGURATION
 *
 **************************************************************************/


enum{
	/* IO port */
	CONFIG_ADDR=0xcf8,
	CONFIG_DATA=0xcfc,
};


/*
 * Read pci config register
 * parameters : PCI_INFO,Register
 * return : Read value
 */
uint read_pci_config(PCI_INFO *pci,int reg)
{
	uint value;


	/* Read IO */
	outdw(CONFIG_ADDR,(1<<31)|(pci->bus<<16)|(pci->dev<<11)|(pci->func<<8)|(reg&~3));
	value=indw(CONFIG_DATA);
	outdw(CONFIG_ADDR,0);

	return value>>((reg&3)*8);
}


/*
 * Write double word value to pci config register
 * parameters : PCI_INFO,Register,Write value
 */
void writedw_pci_config(PCI_INFO *pci,int reg,uint value)
{
	outdw(CONFIG_ADDR,(1<<31)|(pci->bus<<16)|(pci->dev<<11)|(pci->func<<8)|(reg&~3));
	outdw(CONFIG_DATA+(reg&3),value);
	outdw(CONFIG_ADDR,0);
}


/*
 * Write word value to pci config register
 * parameters : PCI_INFO,Register,Write value
 */
void writew_pci_config(PCI_INFO *pci,int reg,ushort value)
{
	outdw(CONFIG_ADDR,(1<<31)|(pci->bus<<16)|(pci->dev<<11)|(pci->func<<8)|(reg&~3));
	outw(CONFIG_DATA+(reg&3),value);
	outdw(CONFIG_ADDR,0);
}


/*
 * Write byte value to pci config register
 * parameters : PCI_INFO,Register,Write value
 */
void writeb_pci_config(PCI_INFO *pci,int reg,uchar value)
{
	outdw(CONFIG_ADDR,(1<<31)|(pci->bus<<16)|(pci->dev<<11)|(pci->func<<8)|(reg&~3));
	outb(CONFIG_DATA+(reg&3),value);
	outdw(CONFIG_ADDR,0);
}


/*
 * Search PCI device from class coad.
 * 与えられたPCIクラス値から検索を始める。
 * parameters : Class coad,Buffer for writing
 * return : 0 or error number
 */
int search_pci_class(int class, PCI_INFO *inf)
{
	for(;inf->bus < 255; ++inf->bus){
		for(;inf->dev < 32; ++inf->dev){
			for(;inf->func < 8; ++inf->func){
				if ((ushort)read_pci_config(inf,PCI_CONF_VENDER) == 0xffff){
					continue;
				}
				if(read_pci_config(inf, PCI_CONF_CLASS) == class){
					inf->vidDid = read_pci_config(inf, PCI_CONF_VENDER);
					return 0;
				}
			}
			inf->func = 0;
		}
		inf->dev = 0;
	}

	return -ENODEV;
}
/*
// VenderID と DeviceIDで探すバージョン
int search_pci_class(uint VenderID, uint DeviceID, PCI_INFO *inf)
{
	uint vidDid = (VenderID & 0xffff) | (DeviceID << 16);

	for (;inf->bus < 255; ++inf->bus){
		for (;inf->dev < 32; ++inf->dev){
			for (;inf->func < 8; ++inf->func){
				if ((ushort)read_pci_config(inf,PCI_CONF_VENDER) == 0xffff){
					continue;
				}
				if (read_pci_config(inf, PCI_CONF_VENDER) == vidDid){
					inf->vidDid = vidDid;
					inf->class = read_pci_config(inf, PCI_CONF_CLASS);
					return 0;
				}
			}
			inf->func = 0;
		}
		inf->dev = 0;
	}

	return -ENODEV;
}
*/

/***************************************************************************************************
 *
 * PCI IRQ Routing Table
 *
 ***************************************************************************************************/


typedef struct{
	uchar  bus;							/* PCI Bus Number. */
	uchar  dev;							/* PCI Device Number (in upper five bits). */
	struct{
		uchar  value;					/* Link Value. */
		ushort bitmap;					/* IRQ Bitmap. */
	} __attribute__((packed)) irq[4];
	uchar  number;						/* Slot Number. */
	char   reserve;						/* Reserved. */
}SLOT_ENTRY;

typedef struct{
	uint       sign;		/* Signature. */
	ushort     version;		/* Version. */
	ushort     tblsize;		/* Table Size. */
	uchar      bus;			/* PCI Interrupt Router's Bus. */
	uchar      devfunc;		/* PCI Interrupt Router's DevFunc. */
	ushort     excirq;		/* PCI Exclusive IRQs. */
	uint       router;		/* Compatible PCI Interrupt Router. */
	uint       mport;		/* Miniport Data. */
	char       reserve[11];	/* Reserved (Zero). */
	uchar      checksum;	/* Checksum. */
	SLOT_ENTRY entry[0];	/* Slot Entry. */
}PCI_IRQ_ROUT_TABLE;


/* PRIVATE */
static PCI_IRQ_ROUT_TABLE *pciIrqTbl;


/*
 * PRIVATE
 * Search PCI IRQ Routing Table.
 * return : address or failed=NULL
 */
PCI_IRQ_ROUT_TABLE *searchPciIrqTable()
{
	enum{
		BEGIN_ADDR = 0xf0000,
		LAST_ADDR = 0xfffff,
		SIG1 = '$',
		SIG2 = 'P',
		SIG3 = 'I',
		SIG4 = 'R',
	};

	char *p;

	for (p = (char*)BEGIN_ADDR; p < (char*)(LAST_ADDR - 3);){
		if ((*p++ == SIG1) && (*p == SIG2) && (*++p == SIG3) && (*++p == SIG4)){
			return (PCI_IRQ_ROUT_TABLE*)(p - 3);
		}
	}

	return NULL;
}


/*
 * PUBLIC
 * Init PCI IRQ Routing Table.
 * return : 0
 */
static int initPciIrqTable()
{
	pciIrqTbl = searchPciIrqTable();

	return 0;
}


/*
 * GLOBAL
 * Get PCI IRQ number.
 * parameters : PCI bus,PCI device,interrupt pin number
 * return : PCI IRQ number or failed=-1
 */
int getPciIrq(PCI_INFO *pci)
{
	int pin;
	int i,last;
	SLOT_ENTRY *p;


	if(pciIrqTbl==NULL)return -1;

	if((pin=read_pci_config(pci,PCI_CONF_INTRPIN)&0xff)==0)return -1;
	last=(pciIrqTbl->tblsize-sizeof(PCI_IRQ_ROUT_TABLE))/sizeof(SLOT_ENTRY);
	p=pciIrqTbl->entry;
	for(i=0;i<last;++i)
		if((p[i].bus==pci->bus)&&(p[i].dev>>3==pci->dev))
			return p[i].irq[pin-1].value&0x3;

	return -1;
}


/**************************************************************************
 *
 * 初期化
 *
 **************************************************************************/


/*
 * GLOBAL
 * Init pci.
 * return : 0
 */
int initPci()
{
	initPciIrqTable();

	return 0;
}


/*************************************************************************************/
void test_pci()
{
	PCI_INFO pci={0,0,0,0};

	if(search_pci_class(PCI_CLASS_ISA_BRIDGE,&pci)<0)
	{
		printk("No ISA bridge\n");
		return;
	}
	printk("%x\n",read_pci_config(&pci,0x60));
/*	writeb_pci_config(&pci,0x61,0x05);
	printk("%x\n",read_pci_config(&pci,0x60));*/
}
