summaryrefslogtreecommitdiffstats
path: root/pci.c
blob: 02ad6c2864693854eed98430d3aa9f4a107c1747 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include "io.h"
#include "pci.h"

#define PCI_CONF_TYPE_NONE 0
#define PCI_CONF_TYPE_1    1
#define PCI_CONF_TYPE_2    2

static unsigned char pci_conf_type = PCI_CONF_TYPE_NONE;

#define PCI_CONF1_ADDRESS(bus, dev, fn, reg) \
	(0x80000000 | (bus << 16) | (dev << 11) | (fn << 8) | (reg & ~3))

#define PCI_CONF2_ADDRESS(dev, reg)	(unsigned short)(0xC000 | (dev << 8) | reg)

int pci_conf_read(unsigned bus, unsigned dev, unsigned fn, unsigned reg, unsigned len, unsigned long *value)
{
	int result;

	if (!value || (bus > 255) || (dev > 31) || (fn > 7) || (reg > 255))
		return -1;

	result = -1;
	switch(pci_conf_type) {
	case PCI_CONF_TYPE_1:
		outl(PCI_CONF1_ADDRESS(bus, dev, fn, reg), 0xCF8);
		switch(len) {
		case 1:  *value = inb(0xCFC + (reg & 3)); result = 0; break;
		case 2:  *value = inw(0xCFC + (reg & 2)); result = 0; break;
		case 4:  *value = inl(0xCFC); result = 0; break;
		}
		break;
	case PCI_CONF_TYPE_2:
		outb(0xF0 | (fn << 1), 0xCF8);
		outb(bus, 0xCFA);
		
		switch(len) {
		case 1:  *value = inb(PCI_CONF2_ADDRESS(dev, reg)); result = 0; break;
		case 2:  *value = inw(PCI_CONF2_ADDRESS(dev, reg)); result = 0; break;
		case 4:  *value = inl(PCI_CONF2_ADDRESS(dev, reg)); result = 0; break;
		}
		outb(0, 0xCF8);
		break;
	}
	return result;
}

int pci_conf_write(unsigned bus, unsigned dev, unsigned fn, unsigned reg, unsigned len, unsigned long value)
{
	int result;

	if ((bus > 255) || (dev > 31) || (fn > 7) || (reg > 255))
		return -1;

	result = -1;
	switch(pci_conf_type) {
	case PCI_CONF_TYPE_1:
		outl(PCI_CONF1_ADDRESS(bus, dev, fn, reg), 0xCF8);
		switch(len) {
		case 1:  outb(value, 0xCFC + (reg & 3)); result = 0; break;
		case 2:  outw(value, 0xCFC + (reg & 2)); result = 0; break;
		case 4:  outl(value, 0xCFC); result = 0; break;
		}
		break;
	case PCI_CONF_TYPE_2:
		outb(0xF0 | (fn << 1), 0xCF8);
		outb(bus, 0xCFA);
		
		switch(len) {
		case 1: outb(value, PCI_CONF2_ADDRESS(dev, reg)); result = 0; break;
		case 2: outw(value, PCI_CONF2_ADDRESS(dev, reg)); result = 0; break;
		case 4: outl(value, PCI_CONF2_ADDRESS(dev, reg)); result = 0; break;
		}
		outb(0, 0xCF8);
		break;
	}
	return result;
}

static int pci_sanity_check(void)
{
	unsigned long value;
	int result;
	/* Do a trivial check to make certain we can see a host bridge.
	 * There are reportedly some buggy chipsets from intel and
	 * compaq where this test does not work, I will worry about
	 * that when we support them.
	 */
	result = pci_conf_read(0, 0, 0, PCI_CLASS_DEVICE, 2, &value);
	if (result == 0) {
		result = -1;
		if (value == PCI_CLASS_BRIDGE_HOST) {
			result = 0;
		}
	}
	return result;
}

static int pci_check_direct(void)
{
	unsigned int tmp;

	/* Check if configuration type 1 works. */
	pci_conf_type = PCI_CONF_TYPE_1;
	outb(0x01, 0xCFB);
	tmp = inl(0xCF8);
	outl(0x80000000, 0xCF8);
	if ((inl(0xCF8) == 0x80000000) && (pci_sanity_check() == 0)) {
		outl(tmp, 0xCF8);
		return 0;
	}
	outl(tmp, 0xCF8);

	/* Check if configuration type 2 works. */
	pci_conf_type = PCI_CONF_TYPE_2;
	outb(0x00, 0xCFB);
	outb(0x00, 0xCF8);
	outb(0x00, 0xCFA);
	if (inb(0xCF8) == 0x00 && inb(0xCFA) == 0x00 && (pci_sanity_check() == 0)) {
		return 0;
	}

	/* Nothing worked return an error */
	pci_conf_type = PCI_CONF_TYPE_NONE;
	return -1;
}

int pci_init(void)
{
	int result;
	/* For now just make certain we can directly
	 * use the pci functions.
	 */
	result = pci_check_direct();
	return result;
}