summaryrefslogtreecommitdiffstats
path: root/arch/ppc/syslib/qspan_pci.c
blob: 57f4ed5e5ae1a1b6dbab371f5590409c0fa2b20d (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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
/*
 * QSpan pci routines.
 * Most 8xx boards use 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).
 *
 * The RPX Classic implementation shares a chip select for normal
 * PCI access and QSpan control register addresses.  The selection is
 * further selected by a bit setting in a board control register.
 * Although it should happen, we disable interrupts during this operation
 * to make sure some driver doesn't accidentally access the PCI while
 * we have switched the chip select.
 */

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

#include <asm/io.h>
#include <asm/mpc8xx.h>
#include <asm/system.h>
#include <asm/machdep.h>
#include <asm/pci-bridge.h>


/*
 * This blows......
 * 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_qspan_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) : " %0")

#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;

static spinlock_t pcibios_lock = SPIN_LOCK_UNLOCKED;

int qspan_pcibios_read_config_byte(unsigned char bus, unsigned char dev_fn,
				  unsigned char offset, unsigned char *val)
{
	uint	temp;
	u_char	*cp;
#ifdef CONFIG_RPXCLASSIC
	unsigned long flags;
#endif

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

#ifdef CONFIG_RPXCLASSIC
	/* disable interrupts */
	spin_lock_irqsave(&pcibios_lock, flags);
	*((uint *)RPX_CSR_ADDR) &= ~BCSR2_QSPACESEL;
	eieio();
#endif

	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_qspan_pci_config(temp, QS_CONFIG_DATA, "lwz");

#ifdef CONFIG_RPXCLASSIC
	*((uint *)RPX_CSR_ADDR) |= BCSR2_QSPACESEL;
	eieio();
	spin_unlock_irqrestore(&pcibios_lock, flags);
#endif

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

int qspan_pcibios_read_config_word(unsigned char bus, unsigned char dev_fn,
				  unsigned char offset, unsigned short *val)
{
	uint	temp;
	ushort	*sp;
#ifdef CONFIG_RPXCLASSIC
	unsigned long flags;
#endif

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

#ifdef CONFIG_RPXCLASSIC
	/* disable interrupts */
	spin_lock_irqsave(&pcibios_lock, flags);
	*((uint *)RPX_CSR_ADDR) &= ~BCSR2_QSPACESEL;
	eieio();
#endif

	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_qspan_pci_config(temp, QS_CONFIG_DATA, "lwz");
	offset ^= 0x02;

#ifdef CONFIG_RPXCLASSIC
	*((uint *)RPX_CSR_ADDR) |= BCSR2_QSPACESEL;
	eieio();
	spin_unlock_irqrestore(&pcibios_lock, flags);
#endif

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

int qspan_pcibios_read_config_dword(unsigned char bus, unsigned char dev_fn,
				   unsigned char offset, unsigned int *val)
{
#ifdef CONFIG_RPXCLASSIC
	unsigned long flags;
#endif

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

#ifdef CONFIG_RPXCLASSIC
	/* disable interrupts */
	spin_lock_irqsave(&pcibios_lock, flags);
	*((uint *)RPX_CSR_ADDR) &= ~BCSR2_QSPACESEL;
	eieio();
#endif

	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_qspan_pci_config(*val, QS_CONFIG_DATA, "lwz");

#ifdef CONFIG_RPXCLASSIC
	*((uint *)RPX_CSR_ADDR) |= BCSR2_QSPACESEL;
	eieio();
	spin_unlock_irqrestore(&pcibios_lock, flags);
#endif

	return PCIBIOS_SUCCESSFUL;
}

int qspan_pcibios_write_config_byte(unsigned char bus, unsigned char dev_fn,
				   unsigned char offset, unsigned char val)
{
	uint	temp;
	u_char	*cp;
#ifdef CONFIG_RPXCLASSIC
	unsigned long flags;
#endif

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

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

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

#ifdef CONFIG_RPXCLASSIC
	/* disable interrupts */
	spin_lock_irqsave(&pcibios_lock, flags);
	*((uint *)RPX_CSR_ADDR) &= ~BCSR2_QSPACESEL;
	eieio();
#endif

	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;

#ifdef CONFIG_RPXCLASSIC
	*((uint *)RPX_CSR_ADDR) |= BCSR2_QSPACESEL;
	eieio();
	spin_unlock_irqrestore(&pcibios_lock, flags);
#endif

	return PCIBIOS_SUCCESSFUL;
}

int qspan_pcibios_write_config_word(unsigned char bus, unsigned char dev_fn,
				   unsigned char offset, unsigned short val)
{
	uint	temp;
	ushort	*sp;
#ifdef CONFIG_RPXCLASSIC
	unsigned long flags;
#endif

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

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

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

#ifdef CONFIG_RPXCLASSIC
	/* disable interrupts */
	spin_lock_irqsave(&pcibios_lock, flags);
	*((uint *)RPX_CSR_ADDR) &= ~BCSR2_QSPACESEL;
	eieio();
#endif

	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;

#ifdef CONFIG_RPXCLASSIC
	*((uint *)RPX_CSR_ADDR) |= BCSR2_QSPACESEL;
	eieio();
	spin_unlock_irqrestore(&pcibios_lock, flags);
#endif

	return PCIBIOS_SUCCESSFUL;
}

int qspan_pcibios_write_config_dword(unsigned char bus, unsigned char dev_fn,
				    unsigned char offset, unsigned int val)
{
#ifdef CONFIG_RPXCLASSIC
	unsigned long flags;
#endif

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

#ifdef CONFIG_RPXCLASSIC
	/* disable interrupts */
	spin_lock_irqsave(&pcibios_lock, flags);
	*((uint *)RPX_CSR_ADDR) &= ~BCSR2_QSPACESEL;
	eieio();
#endif

	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;

#ifdef CONFIG_RPXCLASSIC
	*((uint *)RPX_CSR_ADDR) |= BCSR2_QSPACESEL;
	eieio();
	spin_unlock_irqrestore(&pcibios_lock, flags);
#endif

	return PCIBIOS_SUCCESSFUL;
}

int qspan_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++) {
	qspan_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 qspan_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++) {
	qspan_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;
}

void __init
m8xx_pcibios_fixup(void))
{
   /* Lots to do here, all board and configuration specific. */
}

void __init
m8xx_setup_pci_ptrs(void))
{
	set_config_access_method(qspan);

	ppc_md.pcibios_fixup = m8xx_pcibios_fixup;
}

OpenPOWER on IntegriCloud