summaryrefslogtreecommitdiffstats
path: root/arch/ppc/boot/simple/qspan_pci.c
blob: d2966d032a4c80b5383658b3b607676b013ff8a5 (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
/*
 * LinuxPPC arch/ppc/kernel/qspan_pci.c   Dan Malek (dmalek@jlc.net)
 *
 * QSpan Motorola bus to 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/types.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <asm/mpc8xx.h>

/*
 * 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_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;

/* Initialize the QSpan device registers after power up.
*/
void
qspan_init(void)
{
	uint	*qptr;



	qptr = (uint *)PCI_CSR_ADDR;

	/* PCI Configuration/status.  Upper bits written to clear
	 * pending interrupt or status.  Lower bits enable QSPAN as
	 * PCI master, enable memory and I/O cycles, and enable PCI
	 * parity error checking.
	 * IMPORTANT:  The last two bits of this word enable PCI
	 * master cycles into the QBus.  The QSpan is broken and can't
	 * meet the timing specs of the PQ bus for this to work.  Therefore,
	 * if you don't have external bus arbitration, you can't use
	 * this function.
	 */
#ifdef EXTERNAL_PQ_ARB
	qptr[1] = 0xf9000147;
#else
	qptr[1] = 0xf9000144;
#endif

	/* PCI Misc configuration.  Set PCI latency timer resolution
	 * of 8 cycles, set cache size to 4 x 32.
	 */
	qptr[3] = 0;

	/* Set up PCI Target address mapping.  Enable, Posted writes,
	 * 2Gbyte space (processor memory controller determines actual size).
	 */
	qptr[64] = 0x8f000080;

	/* Map processor 0x80000000 to PCI 0x00000000.
	 * Processor address bit 1 determines I/O type access (0x80000000)
	 * or memory type access (0xc0000000).
	 */
	qptr[65] = 0x80000000;

	/* Enable error logging and clear any pending error status.
	*/
	qptr[80] = 0x90000000;

	qptr[512] = 0x000c0003;

	/* Set up Qbus slave image.
	*/
	qptr[960] = 0x01000000;
	qptr[961] = 0x000000d1;
	qptr[964] = 0x00000000;
	qptr[965] = 0x000000d1;

}

/* Functions to support PCI bios-like features to read/write configuration
 * space.  If the function fails for any reason, a -1 (0xffffffff) value
 * must be returned.
 */
#define DEVICE_NOT_FOUND	(-1)
#define SUCCESSFUL		0

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

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

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

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

int qs_pci_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 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_pci_config(*val, QS_CONFIG_DATA, "lwz");
	return SUCCESSFUL;
}

int qs_pci_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 DEVICE_NOT_FOUND;

	qs_pci_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 SUCCESSFUL;
}

int qs_pci_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 DEVICE_NOT_FOUND;

	qs_pci_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 SUCCESSFUL;
}

int qs_pci_write_config_dword(unsigned char bus, unsigned char dev_fn,
				    unsigned char offset, unsigned int val)
{
	if ((bus > 7) || (dev_fn > 127))
		return 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 SUCCESSFUL;
}

OpenPOWER on IntegriCloud