summaryrefslogtreecommitdiffstats
path: root/arch/ppc/kernel/mbx_pci.c
blob: 30b7b118422275800989f819272f3bd3f3b3fc54 (plain)
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/*
 * MBX pci routines.
 * The MBX uses the QSpan PCI bridge.  The config address register
 * is located 0x500 from the base of the bridge control/status registers.
 * The data register is located at 0x504.
 * This is a two step operation.  First, the address register is written,
 * then the data register is read/written as required.
 * I don't know what to do about interrupts (yet).
 */

#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/bios32.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/init.h>

#include <asm/io.h>
#include <asm/mbx.h>


/*
 * This blows......The MBX uses the Tundra QSpan PCI bridge.  When
 * reading the configuration space, if something does not respond
 * the bus times out and we get a machine check interrupt.  So, the
 * good ol' exception tables come to mind to trap it and return some
 * value.
 *
 * On an error we just return a -1, since that is what the caller wants
 * returned if nothing is present.  I copied this from __get_user_asm,
 * with the only difference of returning -1 instead of EFAULT.
 * There is an associated hack in the machine check trap code.
 *
 * The QSPAN is also a big endian device, that is it makes the PCI
 * look big endian to us.  This presents a problem for the Linux PCI
 * functions, which assume little endian.  For example, we see the
 * first 32-bit word like this:
 *	------------------------
 *	| Device ID | Vendor ID |
 *	------------------------
 * If we read/write as a double word, that's OK.  But in our world,
 * when read as a word, device ID is at location 0, not location 2 as
 * the little endian PCI would believe.  We have to switch bits in
 * the PCI addresses given to us to get the data to/from the correct
 * byte lanes.
 *
 * The QSPAN only supports 4 bits of "slot" in the dev_fn instead of 5.
 * It always forces the MS bit to zero.  Therefore, dev_fn values
 * greater than 128 are returned as "no device found" errors.
 *
 * The QSPAN can only perform long word (32-bit) configuration cycles.
 * The "offset" must have the two LS bits set to zero.  Read operations
 * require we read the entire word and then sort out what should be
 * returned.  Write operations other than long word require that we
 * read the long word, update the proper word or byte, then write the
 * entire long word back.
 *
 * PCI Bridge hack.  We assume (correctly) that bus 0 is the primary
 * PCI bus from the QSPAN.  If we are called with a bus number other
 * than zero, we create a Type 1 configuration access that a downstream
 * PCI bridge will interpret.
 */

#define __get_mbx_pci_config(x, addr, op)		\
	__asm__ __volatile__(				\
		"1:	"op" %0,0(%1)\n"		\
		"	eieio\n"			\
		"2:\n"					\
		".section .fixup,\"ax\"\n"		\
		"3:	li %0,-1\n"			\
		"	b 2b\n"				\
		".section __ex_table,\"a\"\n"		\
		"	.align 2\n"			\
		"	.long 1b,3b\n"			\
		".text"					\
		: "=r"(x) : "r"(addr))

#define QS_CONFIG_ADDR	((volatile uint *)(PCI_CSR_ADDR + 0x500))
#define QS_CONFIG_DATA	((volatile uint *)(PCI_CSR_ADDR + 0x504))

#define mk_config_addr(bus, dev, offset) \
	(((bus)<<16) | ((dev)<<8) | (offset & 0xfc))

#define mk_config_type1(bus, dev, offset) \
	mk_config_addr(bus, dev, offset) | 1;

int mbx_pcibios_read_config_byte(unsigned char bus, unsigned char dev_fn,
				  unsigned char offset, unsigned char *val)
{
	uint	temp;
	u_char	*cp;

	if ((bus > 7) || (dev_fn > 127)) {
		*val = 0xff;
		return PCIBIOS_DEVICE_NOT_FOUND;
	}

	if (bus == 0)
		*QS_CONFIG_ADDR = mk_config_addr(bus, dev_fn, offset);
	else
		*QS_CONFIG_ADDR = mk_config_type1(bus, dev_fn, offset);
	__get_mbx_pci_config(temp, QS_CONFIG_DATA, "lwz");

	offset ^= 0x03;
	cp = ((u_char *)&temp) + (offset & 0x03);
	*val = *cp;
	return PCIBIOS_SUCCESSFUL;
}

int mbx_pcibios_read_config_word(unsigned char bus, unsigned char dev_fn,
				  unsigned char offset, unsigned short *val)
{
	uint	temp;
	ushort	*sp;

	if ((bus > 7) || (dev_fn > 127)) {
		*val = 0xffff;
		return PCIBIOS_DEVICE_NOT_FOUND;
	}

	if (bus == 0)
		*QS_CONFIG_ADDR = mk_config_addr(bus, dev_fn, offset);
	else
		*QS_CONFIG_ADDR = mk_config_type1(bus, dev_fn, offset);
	__get_mbx_pci_config(temp, QS_CONFIG_DATA, "lwz");
	offset ^= 0x02;

	sp = ((ushort *)&temp) + ((offset >> 1) & 1);
	*val = *sp;
	return PCIBIOS_SUCCESSFUL;
}

int mbx_pcibios_read_config_dword(unsigned char bus, unsigned char dev_fn,
				   unsigned char offset, unsigned int *val)
{
	if ((bus > 7) || (dev_fn > 127)) {
		*val = 0xffffffff;
		return PCIBIOS_DEVICE_NOT_FOUND;
	}
	if (bus == 0)
		*QS_CONFIG_ADDR = mk_config_addr(bus, dev_fn, offset);
	else
		*QS_CONFIG_ADDR = mk_config_type1(bus, dev_fn, offset);
	__get_mbx_pci_config(*val, QS_CONFIG_DATA, "lwz");
	return PCIBIOS_SUCCESSFUL;
}

int mbx_pcibios_write_config_byte(unsigned char bus, unsigned char dev_fn,
				   unsigned char offset, unsigned char val)
{
	uint	temp;
	u_char	*cp;

	if ((bus > 7) || (dev_fn > 127))
		return PCIBIOS_DEVICE_NOT_FOUND;

	mbx_pcibios_read_config_dword(bus, dev_fn, offset, &temp);

	offset ^= 0x03;
	cp = ((u_char *)&temp) + (offset & 0x03);
	*cp = val;

	if (bus == 0)
		*QS_CONFIG_ADDR = mk_config_addr(bus, dev_fn, offset);
	else
		*QS_CONFIG_ADDR = mk_config_type1(bus, dev_fn, offset);
	*QS_CONFIG_DATA = temp;

	return PCIBIOS_SUCCESSFUL;
}

int mbx_pcibios_write_config_word(unsigned char bus, unsigned char dev_fn,
				   unsigned char offset, unsigned short val)
{
	uint	temp;
	ushort	*sp;

	if ((bus > 7) || (dev_fn > 127))
		return PCIBIOS_DEVICE_NOT_FOUND;

	mbx_pcibios_read_config_dword(bus, dev_fn, offset, &temp);

	offset ^= 0x02;
	sp = ((ushort *)&temp) + ((offset >> 1) & 1);
	*sp = val;

	if (bus == 0)
		*QS_CONFIG_ADDR = mk_config_addr(bus, dev_fn, offset);
	else
		*QS_CONFIG_ADDR = mk_config_type1(bus, dev_fn, offset);
	*QS_CONFIG_DATA = temp;

	return PCIBIOS_SUCCESSFUL;
}

int mbx_pcibios_write_config_dword(unsigned char bus, unsigned char dev_fn,
				    unsigned char offset, unsigned int val)
{
	if ((bus > 7) || (dev_fn > 127))
		return PCIBIOS_DEVICE_NOT_FOUND;

	if (bus == 0)
		*QS_CONFIG_ADDR = mk_config_addr(bus, dev_fn, offset);
	else
		*QS_CONFIG_ADDR = mk_config_type1(bus, dev_fn, offset);
	*(unsigned int *)QS_CONFIG_DATA = val;

	return PCIBIOS_SUCCESSFUL;
}

int mbx_pcibios_find_device(unsigned short vendor, unsigned short dev_id,
			     unsigned short index, unsigned char *bus_ptr,
			     unsigned char *dev_fn_ptr)
{
    int num, devfn;
    unsigned int x, vendev;

    if (vendor == 0xffff)
	return PCIBIOS_BAD_VENDOR_ID;
    vendev = (dev_id << 16) + vendor;
    num = 0;
    for (devfn = 0;  devfn < 32;  devfn++) {
	mbx_pcibios_read_config_dword(0, devfn<<3, PCI_VENDOR_ID, &x);
	if (x == vendev) {
	    if (index == num) {
		*bus_ptr = 0;
		*dev_fn_ptr = devfn<<3;
		return PCIBIOS_SUCCESSFUL;
	    }
	    ++num;
	}
    }
    return PCIBIOS_DEVICE_NOT_FOUND;
}

int mbx_pcibios_find_class(unsigned int class_code, unsigned short index,
			    unsigned char *bus_ptr, unsigned char *dev_fn_ptr)
{
    int devnr, x, num;

    num = 0;
    for (devnr = 0;  devnr < 32;  devnr++) {
	mbx_pcibios_read_config_dword(0, devnr<<3, PCI_CLASS_REVISION, &x);
	if ((x>>8) == class_code) {
	    if (index == num) {
		*bus_ptr = 0;
		*dev_fn_ptr = devnr<<3;
		return PCIBIOS_SUCCESSFUL;
	    }
	    ++num;
	}
    }
    return PCIBIOS_DEVICE_NOT_FOUND;
}