aboutsummaryrefslogtreecommitdiff
path: root/arch/x86/pci/olpc.c
blob: b34815408f582bd002854d83c8e5736b6de37b24 (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
/*
 * Low-level PCI config space access for OLPC systems who lack the VSA
 * PCI virtualization software.
 *
 * Copyright © 2006  Advanced Micro Devices, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * The AMD Geode chipset (ie: GX2 processor, cs5536 I/O companion device)
 * has some I/O functions (display, southbridge, sound, USB HCIs, etc)
 * that more or less behave like PCI devices, but the hardware doesn't
 * directly implement the PCI configuration space headers.  AMD provides
 * "VSA" (Virtual System Architecture) software that emulates PCI config
 * space for these devices, by trapping I/O accesses to PCI config register
 * (CF8/CFC) and running some code in System Management Mode interrupt state.
 * On the OLPC platform, we don't want to use that VSA code because
 * (a) it slows down suspend/resume, and (b) recompiling it requires special
 * compilers that are hard to get.  So instead of letting the complex VSA
 * code simulate the PCI config registers for the on-chip devices, we
 * just simulate them the easy way, by inserting the code into the
 * pci_write_config and pci_read_config path.  Most of the config registers
 * are read-only anyway, so the bulk of the simulation is just table lookup.
 */

#include <linux/pci.h>
#include <linux/init.h>
#include <asm/olpc.h>
#include <asm/geode.h>
#include <asm/pci_x86.h>

/*
 * In the tables below, the first two line (8 longwords) are the
 * size masks that are used when the higher level PCI code determines
 * the size of the region by writing ~0 to a base address register
 * and reading back the result.
 *
 * The following lines are the values that are read during normal
 * PCI config access cycles, i.e. not after just having written
 * ~0 to a base address register.
 */

static const uint32_t lxnb_hdr[] = {  /* dev 1 function 0 - devfn = 8 */
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,

	0x281022, 0x2200005, 0x6000021, 0x80f808,	/* AMD Vendor ID */
	0x0,	0x0,	0x0,	0x0,   /* No virtual registers, hence no BAR */
	0x0,	0x0,	0x0,	0x28100b,
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,
};

static const uint32_t gxnb_hdr[] = {  /* dev 1 function 0 - devfn = 8 */
	0xfffffffd, 0x0, 0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,

	0x28100b, 0x2200005, 0x6000021, 0x80f808,	/* NSC Vendor ID */
	0xac1d,	0x0,	0x0,	0x0,  /* I/O BAR - base of virtual registers */
	0x0,	0x0,	0x0,	0x28100b,
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,
};

static const uint32_t lxfb_hdr[] = {  /* dev 1 function 1 - devfn = 9 */
	0xff000008, 0xffffc000, 0xffffc000, 0xffffc000,
	0xffffc000,	0x0,	0x0,	0x0,

	0x20811022, 0x2200003, 0x3000000, 0x0,		/* AMD Vendor ID */
	0xfd000000, 0xfe000000, 0xfe004000, 0xfe008000, /* FB, GP, VG, DF */
	0xfe00c000, 0x0, 0x0,	0x30100b,		/* VIP */
	0x0,	0x0,	0x0,	0x10e,	   /* INTA, IRQ14 for graphics accel */
	0x0,	0x0,	0x0,	0x0,
	0x3d0,	0x3c0,	0xa0000, 0x0,	    /* VG IO, VG IO, EGA FB, MONO FB */
	0x0,	0x0,	0x0,	0x0,
};

static const uint32_t gxfb_hdr[] = {  /* dev 1 function 1 - devfn = 9 */
	0xff800008, 0xffffc000, 0xffffc000, 0xffffc000,
	0x0,	0x0,	0x0,	0x0,

	0x30100b, 0x2200003, 0x3000000, 0x0,		/* NSC Vendor ID */
	0xfd000000, 0xfe000000, 0xfe004000, 0xfe008000,	/* FB, GP, VG, DF */
	0x0,	0x0,	0x0,	0x30100b,
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,
	0x3d0,	0x3c0,	0xa0000, 0x0,  	    /* VG IO, VG IO, EGA FB, MONO FB */
	0x0,	0x0,	0x0,	0x0,
};

static const uint32_t aes_hdr[] = {	/* dev 1 function 2 - devfn = 0xa */
	0xffffc000, 0x0, 0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,

	0x20821022, 0x2a00006, 0x10100000, 0x8,		/* NSC Vendor ID */
	0xfe010000, 0x0, 0x0,	0x0,			/* AES registers */
	0x0,	0x0,	0x0,	0x20821022,
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,
};


static const uint32_t isa_hdr[] = {  /* dev f function 0 - devfn = 78 */
	0xfffffff9, 0xffffff01, 0xffffffc1, 0xffffffe1,
	0xffffff81, 0xffffffc1, 0x0, 0x0,

	0x20901022, 0x2a00049, 0x6010003, 0x802000,
	0x18b1,	0x1001,	0x1801,	0x1881,	/* SMB-8   GPIO-256 MFGPT-64  IRQ-32 */
	0x1401,	0x1841,	0x0,	0x20901022,		/* PMS-128 ACPI-64 */
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0xaa5b,			/* IRQ steering */
	0x0,	0x0,	0x0,	0x0,
};

static const uint32_t ac97_hdr[] = {  /* dev f function 3 - devfn = 7b */
	0xffffff81, 0x0, 0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,

	0x20931022, 0x2a00041, 0x4010001, 0x0,
	0x1481,	0x0,	0x0,	0x0,			/* I/O BAR-128 */
	0x0,	0x0,	0x0,	0x20931022,
	0x0,	0x0,	0x0,	0x205,			/* IntB, IRQ5 */
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,
};

static const uint32_t ohci_hdr[] = {  /* dev f function 4 - devfn = 7c */
	0xfffff000, 0x0, 0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,

	0x20941022, 0x2300006, 0xc031002, 0x0,
	0xfe01a000, 0x0, 0x0,	0x0,			/* MEMBAR-1000 */
	0x0,	0x0,	0x0,	0x20941022,
	0x0,	0x40,	0x0,	0x40a,			/* CapPtr INT-D, IRQA */
	0xc8020001, 0x0, 0x0,	0x0,	/* Capabilities - 40 is R/O,
					   44 is mask 8103 (power control) */
	0x0,	0x0,	0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,
};

static const uint32_t ehci_hdr[] = {  /* dev f function 4 - devfn = 7d */
	0xfffff000, 0x0, 0x0,	0x0,
	0x0,	0x0,	0x0,	0x0,

	0x20951022, 0x2300006, 0xc032002, 0x0,
	0xfe01b000, 0x0, 0x0,	0x0,			/* MEMBAR-1000 */
	0x0,	0x0,	0x0,	0x20951022,
	0x0,	0x40,	0x0,	0x40a,			/* CapPtr INT-D, IRQA */
	0xc8020001, 0x0, 0x0,	0x0,	/* Capabilities - 40 is R/O, 44 is
					   mask 8103 (power control) */
#if 0
	0x1,	0x40080000, 0x0, 0x0,	/* EECP - see EHCI spec section 2.1.7 */
#endif
	0x01000001, 0x0, 0x0,	0x0,	/* EECP - see EHCI spec section 2.1.7 */
	0x2020,	0x0,	0x0,	0x0,	/* (EHCI page 8) 60 SBRN (R/O),
					   61 FLADJ (R/W), PORTWAKECAP  */
};

static uint32_t ff_loc = ~0;
static uint32_t zero_loc;
static int bar_probing;		/* Set after a write of ~0 to a BAR */
static int is_lx;

#define NB_SLOT 0x1	/* Northbridge - GX chip - Device 1 */
#define SB_SLOT 0xf	/* Southbridge - CS5536 chip - Device F */

static int is_simulated(unsigned int bus, unsigned int devfn)
{
	return (!bus && ((PCI_SLOT(devfn) == NB_SLOT) ||
			(PCI_SLOT(devfn) == SB_SLOT)));
}

static uint32_t *hdr_addr(const uint32_t *hdr, int reg)
{
	uint32_t addr;

	/*
	 * This is a little bit tricky.  The header maps consist of
	 * 0x20 bytes of size masks, followed by 0x70 bytes of header data.
	 * In the normal case, when not probing a BAR's size, we want
	 * to access the header data, so we add 0x20 to the reg offset,
	 * thus skipping the size mask area.
	 * In the BAR probing case, we want to access the size mask for
	 * the BAR, so we subtract 0x10 (the config header offset for
	 * BAR0), and don't skip the size mask area.
	 */

	addr = (uint32_t)hdr + reg + (bar_probing ? -0x10 : 0x20);

	bar_probing = 0;
	return (uint32_t *)addr;
}

static int pci_olpc_read(unsigned int seg, unsigned int bus,
		unsigned int devfn, int reg, int len, uint32_t *value)
{
	uint32_t *addr;

	/* Use the hardware mechanism for non-simulated devices */
	if (!is_simulated(bus, devfn))
		return pci_direct_conf1.read(seg, bus, devfn, reg, len, value);

	/*
	 * No device has config registers past 0x70, so we save table space
	 * by not storing entries for the nonexistent registers
	 */
	if (reg >= 0x70)
		addr = &zero_loc;
	else {
		switch (devfn) {
		case  0x8:
			addr = hdr_addr(is_lx ? lxnb_hdr : gxnb_hdr, reg);
			break;
		case  0x9:
			addr = hdr_addr(is_lx ? lxfb_hdr : gxfb_hdr, reg);
			break;
		case  0xa:
			addr = is_lx ? hdr_addr(aes_hdr, reg) : &ff_loc;
			break;
		case 0x78:
			addr = hdr_addr(isa_hdr, reg);
			break;
		case 0x7b:
			addr = hdr_addr(ac97_hdr, reg);
			break;
		case 0x7c:
			addr = hdr_addr(ohci_hdr, reg);
			break;
		case 0x7d:
			addr = hdr_addr(ehci_hdr, reg);
			break;
		default:
			addr = &ff_loc;
			break;
		}
	}
	switch (len) {
	case 1:
		*value = *(uint8_t *)addr;
		break;
	case 2:
		*value = *(uint16_t *)addr;
		break;
	case 4:
		*value = *addr;
		break;
	default:
		BUG();
	}

	return 0;
}

static int pci_olpc_write(unsigned int seg, unsigned int bus,
		unsigned int devfn, int reg, int len, uint32_t value)
{
	/* Use the hardware mechanism for non-simulated devices */
	if (!is_simulated(bus, devfn))
		return pci_direct_conf1.write(seg, bus, devfn, reg, len, value);

	/* XXX we may want to extend this to simulate EHCI power management */

	/*
	 * Mostly we just discard writes, but if the write is a size probe
	 * (i.e. writing ~0 to a BAR), we remember it and arrange to return
	 * the appropriate size mask on the next read.  This is cheating
	 * to some extent, because it depends on the fact that the next
	 * access after such a write will always be a read to the same BAR.
	 */

	if ((reg >= 0x10) && (reg < 0x2c)) {
		/* write is to a BAR */
		if (value == ~0)
			bar_probing = 1;
	} else {
		/*
		 * No warning on writes to ROM BAR, CMD, LATENCY_TIMER,
		 * CACHE_LINE_SIZE, or PM registers.
		 */
		if ((reg != PCI_ROM_ADDRESS) && (reg != PCI_COMMAND_MASTER) &&
				(reg != PCI_LATENCY_TIMER) &&
				(reg != PCI_CACHE_LINE_SIZE) && (reg != 0x44))
			printk(KERN_WARNING "OLPC PCI: Config write to devfn"
				" %x reg %x value %x\n", devfn, reg, value);
	}

	return 0;
}

static struct pci_raw_ops pci_olpc_conf = {
	.read =	pci_olpc_read,
	.write = pci_olpc_write,
};

int __init pci_olpc_init(void)
{
	printk(KERN_INFO "PCI: Using configuration type OLPC\n");
	raw_pci_ops = &pci_olpc_conf;
	is_lx = is_geode_lx();
	return 0;
}