summaryrefslogtreecommitdiffstats
path: root/src/mainboard/amd/inagua/broadcom.c
blob: 2153e02f8eef1f774ade8481637b0a87b043acb7 (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
/*
 * Initialize Broadcom 5785 GbE MAC embedded in AMD A55E (Hudson-E1) Southbridge
 * by uploading a Selfboot Patch to the A55E's shadow ROM area.  The patch
 * itself supports the Broadcom 50610(M) PHY on the AMD Inagua.  It is
 * equivalent to Broadcom's SelfBoot patch V1.11 (sb5785m1.11).
 * A modified variant, selected by CONFIG_BOARD_LIPPERT_FRONTRUNNER_AF supports
 * the Micrel KSZ9021 PHY that was used on LiPPERT FrontRunner-AF (CFR-AF)
 * revision 0v0, the first prototype.  The board is history and this code now
 * serves only to document the proprietary Selfboot Patch format and how to
 * adapt it to a PHY unsupported by Broadcom.
 *
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2012 LiPPERT ADLINK Technology GmbH
 * (Written by Jens Rottmann <JRottmann@LiPPERTembedded.de>)
 *
 * 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; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc.
 */

#include <types.h>
#include <console/console.h>
#include <device/device.h>	//Coreboot device access
#include <device/pci.h>
#include <delay.h>
#include <endian.h>

void broadcom_init(void);

#define be16(x)		cpu_to_be16(x)	//a little easier to type
#define be(x)		cpu_to_be32(x)	//this is used a lot!

/* C forces us to specify these before defining struct selfboot_patch  :-( */
#if !CONFIG_BOARD_LIPPERT_FRONTRUNNER_AF
#define INIT1_LENGTH		9
#define INIT2_LENGTH		10
#define INIT3_LENGTH		3
#define INIT4_LENGTH		7	//this one may be 0
#define PWRDN_LENGTH		5
#else
#define INIT1_LENGTH		13
#define INIT2_LENGTH		6
#define INIT3_LENGTH		3
#define INIT4_LENGTH		11	//this one may be 0
#define PWRDN_LENGTH		4
#endif


/* The AMD A55E (Hudson-E1) Southbridge contains an integrated Gigabit Ethernet
 * MAC, however AMD's documentation merely defines the related balls (without
 * fully describing their function) and states that only Broadcom 50610(M) PHYs
 * will be supported, that's all. The Hudson register reference skips all MAC
 * registers entirely, even AMD support doesn't seem to know more about it.
 *
 * As Broadcom refused to sell us any 50610 chips or provide any docs (or indeed
 * even a price list) below $100K expected sales we had to figure out everything
 * by ourselves. *Everything* below is the result of months of detective work,
 * documented here lest it get lost:
 *
 * The AMD A55E's GbE MAC is a Broadcom 5785, which AMD obviously licensed as IP
 * core. It uses a standard RGMII/MII interface and the Broadcom drivers will
 * recognize it by its unchanged PCI ID 14E4:1699, however there are some
 * specialties.
 *
 * The 5785 MAC can detect the link with 4 additional inputs, "phy_status[3:0]",
 * 'snooping' on the PHY's LED outputs. Interpretation of the LEDs' patterns is
 * programmed with register 0x5A4 of the MAC. AMD renamed them to "GBE_STAT" and
 * won't say anything about their purpose. Appearently hardware designers are
 * expected to blindly copy the Inagua reference schematic: GBE_STAT2:
 * 0=activity; GBE_STAT[1:0]: 11=no link, 10=10Mbit, 01=100Mbit, 00=1Gbit.
 *
 * For package processing the 5785 also features a MIPS-based RISC CPU, booting
 * from an internal ROM. The firmware loads config data and supplements (e.g. to
 * support specific PHYs), named "Selfboot Patches", via the "NVRAM Interface",
 * usually from an external EEPROM. The A55E doesn't have any balls for an ext.
 * EEPROM, instead AMD added a small internal RAM. The BIOS is expected to copy
 * the correct contents into this RAM (which only supports byte access!) upon
 * each powerup. The A55E can trigger an SMI upon writes, enabling the BIOS to
 * forward any changes to an actually 'NV' location, e.g. the BIOS's SPI flash,
 * behind the scenes. AMD calls it "GEC shadow ROM", not describing what it's
 * for nor mentioning the term "NVRAM". broadcom_init() below documents a
 * procedure how to upload the patch. No SMI magic is installed, therefore
 * 'NV'RAM writes won't be persistent.
 *
 * The "Selfboot Patch" can execute simple commands at various points during
 * main firmware execution. This can be used to change config registers,
 * initialize a specific PHY or work around firmware bugs. Broadcom provides
 * suitable Patches only for their AC131 and 50610 PHYs (as binary blobs). I
 * found them in DOS\sb_patch\5785\*\sb5785*.* in Driver_14_6_4_2.zip. (Note
 * that every 32bit-word of these files must be byte-swapped before uploading
 * them to the A55E.)
 *
 * Below is a derived Patch supporting the Micrel KSZ9021 PHY used on the
 * LiPPERT CFR-AF PC/104 SBC instead, with detailled description of the format.
 * (Here in correct order for upload.)
 *
 * This Patch made Ethernet work with Linux 3.3 - without having to modify the
 * tg3.ko driver. Broadcom's Windows-Drivers still fail with "Code 10" however;
 * disassembly showed they check the PHY ID and abort, because the Micrel PHY is
 * not supported.
 */

static struct selfboot_patch {		//Watch out: all values are *BIG-ENDIAN*!

	struct {	/* Global header */
		u8 signature;		//0xA5
		u8 format;		//bits 7-3: patch format; 2-0: revision
		u8 mac_addr[6];
		u16 subsys_device;	//IDs will be loaded into PCI config space
		u16 subsys_vendor;
		u16 pci_device;		//PCI device ID; vendor is always Broadcom (0x14E4)
		u8 unknown1[8];		//?, noticed no effect
		u16 basic_config;	//?, see below
		u8 checksum;		//byte sum of header == 0
		u8 unknown2;		//?, patch rejected if changed
		u16 patch_version;	//10-8: major; 7-0: minor; 15-11: variant (1=a, 2=b, ...)
	} header;

	struct {	/* Init code */
		u8 checksum;		//byte sum of init == 0
		u8 unknown;		//?, looks unused
		u8 num_hunks;		//0x60 = 3 hunks, 0x80 = 4 hunks, other values not supported
		u8 size;		//total size of all hunk#_code[] in bytes
		u8 hunk1_when;		//mark when hunk1_code gets executed
		u8 hunk1_size;		//sizeof(hunk1_code)
		u8 hunk2_when;
		u8 hunk2_size;
		u8 hunk3_when;
		u8 hunk3_size;
		u8 hunk4_when;		//0x00 (padding) if only 3 hunks
		u8 hunk4_size;		//dito
		u32 hunk1_code[INIT1_LENGTH]; //actual commands, see below
		u32 hunk2_code[INIT2_LENGTH];
		u32 hunk3_code[INIT3_LENGTH];
		u32 hunk4_code[INIT4_LENGTH]; //missing (zero length) if only 3 hunks
	} init;

	struct {	/* Power down code */
		u8 checksum;		//byte sum of powerdown == 0
		u8 unknown;		//?, looks unused
		u8 num_hunks;		//0x20 = 1 hunk, other values not supported
		u8 size;		//total size of all hunk#_code[] in bytes
		u8 hunk1_when;		//mark when hunk1_code gets executed
		u8 hunk1_size;		//sizeof(hunk1_code)
		u16 padding;		//0x0000, hunk2 is not supported
		u32 hunk1_code[PWRDN_LENGTH]; //commands, see below
	} powerdown;

} selfboot_patch = {

/* Keep the following invariant for valid Selfboot patches */
	.header.signature = 0xA5,
	.header.format = 0x23,		//format 1 revision 3
	.header.unknown1 = { 0x61, 0xB1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
	.header.checksum = 0,		//calculated later
	.header.unknown2 = 0x30,
	.init.checksum = 0,		//calculated later
	.init.unknown = 0x00,
	.init.num_hunks = sizeof(selfboot_patch.init.hunk4_code) ? 0x80 : 0x60,
	.init.size = sizeof(selfboot_patch.init.hunk1_code)
	           + sizeof(selfboot_patch.init.hunk2_code)
	           + sizeof(selfboot_patch.init.hunk3_code)
	           + sizeof(selfboot_patch.init.hunk4_code),
	.init.hunk1_size = sizeof(selfboot_patch.init.hunk1_code),
	.init.hunk2_size = sizeof(selfboot_patch.init.hunk2_code),
	.init.hunk3_size = sizeof(selfboot_patch.init.hunk3_code),
	.init.hunk4_size = sizeof(selfboot_patch.init.hunk4_code),
	.powerdown.checksum = 0,	//calculated later
	.powerdown.unknown = 0x00,
	.powerdown.num_hunks = 0x20,
	.powerdown.size = sizeof(selfboot_patch.powerdown.hunk1_code),
	.powerdown.hunk1_size = sizeof(selfboot_patch.powerdown.hunk1_code),
	.powerdown.padding = be16(0x0000),

/* Only the lines below may be adapted to your needs ... */
#if !CONFIG_BOARD_LIPPERT_FRONTRUNNER_AF
	.header.mac_addr = { 0x00, 0x10, 0x18, 0x00, 0x00, 0x00 }, //Broadcom
	.header.subsys_device = be16(0x1699),	//same as pci_device
	.header.subsys_vendor = be16(0x14E4),	//Broadcom
#else
	.header.mac_addr = { 0x00, 0x20, 0x9D, 0x00, 0x00, 0x00 }, //LiPPERT
	.header.subsys_device = be16(0x1699),	//simply kept this
	.header.subsys_vendor = be16(0x121D),	//LiPPERT
#endif
	.header.pci_device = be16(0x1699),	//Broadcom 5785 with GbE PHY
#if !CONFIG_BOARD_LIPPERT_FRONTRUNNER_AF
	.header.patch_version = be16(0x010B),	//1.11 (Broadcom's sb5785m1.11)
#else
	.header.patch_version = be16(0x110B),	//1.11b, i.e. hacked  :-)
#endif
	/* Bitfield enabling general features/codepaths in the firmware or
	 * selecting support for one of several supported PHYs?
	 * Bits not listed had no appearent effect:
	 * 14-11: any bit 1=firmware execution seemed delayed
	 * 10: 0=firmware execution seemed delayed
	 * 9,2,0: select PHY type, affects these registers, probably more
	 *  9 2 0 | reg 0x05A4  PHY reg 31  PHY 23,24,28        Notes
	 * -------+----------------------------------------------------------
	 *  0 0 0 | 0x331C71C1      -         changed   Inband Status enabled
	 *  0 1 0 | 0x3210C500      -         changed             -
	 *  0 X 1 | 0x33FF66C0   changed         -         10/100 Mbit only
	 *  1 X 0 | 0x330C5180      -            -                -
	 *  1 X 1 | 0x391C6140      -            -                -
	 */
#if !CONFIG_BOARD_LIPPERT_FRONTRUNNER_AF
	.header.basic_config = be16(0x0404),	//original for B50610
#else
	.header.basic_config = be16(0x0604),	//bit 9 set so not to mess up PHY regs, kept other bits unchanged
#endif

	/* Tag that defines when / on what occasion the commands are interpreted.
	 * Bits 2-0 = 0 i.e. possible values are 0x00, 08, 10, ..., F8.
	 * On a RISC CPU reset every tag except 0x38, A0, F0, F8 is used. 0x38
	 * seems to be run before a reset is performed(?), the other 3 I have
	 * never seen used. Generally, lower values appear to be run earlier.
	 * An "ifconfig up" with Linux' "tg3" driver causes the tags 0x50, 60,
	 * 68, 20, 70, 80 to be interpreted in this order.
	 * All tests were performed with .basic_config=0x0604.
	 */
	.init.hunk1_when = 0x10,	//only once at RISC CPU reset?
	/* Instructions are obviously a specialized bytecode interpreted by the
	 * main firmware, rather than MIPS machine code. Commands consist of 1-3
	 * 32-bit words. In the following, 0-9,A-F = hex literals, a-z,_ = variable
	 * parts, each character = 4 bits.
	 * 0610offs newvalue: write (32-bit) <newvalue> to 5785-internal shared mem at <offs>
	 * 08rgvalu: write <valu> to PHY register, <rg> = 0x20 + register number
	 * C610rgnr newvalue: write <newvalue> to MAC register <rgnr>
	 * C1F0rgnr andvalue or_value: modify MAC register <rgnr> by ANDing with <andvalue> and then ORing with <or_value>
	 * C4btrgnr: clear bit in 32-bit MAC register <rgnr>, <bt> = bit number << 3
	 * C3btrgnr: set bit, see C4...; example: command 0xC3200454 sets bit 4 of 32-bit register 0x0454
	 * CBbtrgnr: run next command only if bit (see C4...) == 1 (so far only seen before F7F0...)
	 * F7F0skip: unconditional jump i.e. skip next <skip> code bytes (only seen small positive <skip>)
	 * F7Fxaddr: call function at <addr> in main firmware? <x> = 3 or 4, bool parameter?? Wild guess!
	 * F7FFFadr somvalue: also call func. at <adr>, but with <somvalue> as parameter?? More guessing!
	 * More commands probably exist, but all code I've ever seen was kept
	 * included below, commented out if not suitable for the CFR-AF. v1.xx
	 * is Broadcom's Selfboot patch version sb5785m1.xx where the command
	 * was added, for reference see Broadcom's changelog.
	 */
	.init.hunk1_code = {
#if CONFIG_BOARD_LIPPERT_FRONTRUNNER_AF
		be(0x082B8104),					//CFR-AF: PHY0B: KSZ9021 select PHY104
		be(0x082CF0F0),					//CFR-AF: PHY0C: KSZ9021 clk/ctl skew (advised by Micrel)
		be(0x082B8105),					//CFR-AF: PHY0B: KSZ9021 select PHY105
		be(0x082C3333),					//CFR-AF: PHY0C: KSZ9021 RX data skew (empirical)
#endif
		be(0xC1F005A0), be(0xFEFFEFFF), be(0x01001000),	//v1.05 : 5A0.24,12=1: auto-clock-switch
		be(0x06100D34), be(0x00000000),			//v1.03 : MemD34: clear config vars
		be(0x06100D38), be(0x00000000),			//v1.03 :    -  |
		be(0x06100D3C), be(0x00000000),			//v1.03 : MemD3F|
	}, //-->INIT1_LENGTH!

	.init.hunk2_when = 0x30,	//after global reset, PHY reset
	.init.hunk2_code = {
#if !CONFIG_BOARD_LIPPERT_FRONTRUNNER_AF
		be(0x08370F08),					//v1.06 : PHY17: B50610 select reg. 08
		be(0x08350001),					//v1.06 : PHY15: B50610 slow link fix
		be(0x08370F00),					//v1.06 : PHY17: B50610 disable reg. 08
		be(0x083C2C00),					//v1.11 : PHY1C: B50610 Shadow 0B
#endif
		be(0xF7F301E6),					//v1.09+: ?: subroutine calls to
		be(0xF7FFF0B6), be(0x0000FFE7),			//v1.09+: ?| restore Port Mode ???
		be(0xF7FFF0F6), be(0x00008000),			//v1.09+: ?|
		be(0xF7F401E6),					//v1.09+: ?|
	}, //-->INIT2_LENGTH!

	.init.hunk3_when = 0xA8,	//?, I'd guess quite late
	.init.hunk3_code = {
		be(0xC1F03604), be(0xFFE0FFFF), be(0x00110000),	//v1.08 : 3604.20-16: 10Mb clock = 12.5MHz
	}, //-->INIT3_LENGTH!

#if !CONFIG_BOARD_LIPPERT_FRONTRUNNER_AF
	.init.hunk4_when = 0xD8,	//original for B50610
#else
	.init.hunk4_when = 0x80,	//run last, after Linux' "ifconfig up"
#endif
	.init.hunk4_code = {
#if CONFIG_BOARD_LIPPERT_FRONTRUNNER_AF
		be(0x083F4300),					//CFR-AF: PHY1F: IRQ active high
		be(0x083C0000),					//CFR-AF: PHY1C: revert driver writes
		be(0x08380000),					//CFR-AF: PHY18|
		be(0x083C0000),					//CFR-AF: PHY1C|
#endif
		be(0xCB0005A4), be(0xF7F0000C),			//v1.01 : if 5A4.0==1 -->skip next 12 bytes
#if !CONFIG_BOARD_LIPPERT_FRONTRUNNER_AF
		be(0xC61005A4), be(0x3210C500),			//v1.01 : 5A4: PHY LED mode
#else
		be(0xC61005A4), be(0x331C71CE),			//CFR-AF: 5A4: fake LED mode
#endif
		be(0xF7F00008),					//v1.01 : -->skip next 8 bytes
		be(0xC61005A4), be(0x331C71C1),			//v1.01 : 5A4: inband LED mode
		//be(0xC3200454),				//CFR-AF: 454.4: auto link polling
	}, //-->INIT4_LENGTH!

	.powerdown.hunk1_when = 0x50,	//prior to IDDQ MAC
	.powerdown.hunk1_code = {
#if !CONFIG_BOARD_LIPPERT_FRONTRUNNER_AF
		be(0x083CB001),					//v1.10 : PHY1C: IDDQ B50610 PHY
#endif
		be(0xF7F30116),					//        IDDQ PHY
		be(0xC40005A0),					//v1.09 : 5A0.0=0: Port Mode = MII
		be(0xC4180400),					//v1.09 : 400.3=0|
		be(0xC3100400),					//v1.09 : 400.2=1|
	}, //-->PWRDN_LENGTH!

};

/* Upload 'NV'RAM contents for BCM5785 GbE MAC integrated in A55E.
 * Call this from mainboard.c.
 */
void broadcom_init(void)
{
	volatile u32 *gec_base;	//Gigabit Ethernet Controller base addr
	u8 *gec_shadow;		//base addr of shadow 'NV'RAM for GbE MAC in A55E
	u8 sum;
	int i;

	gec_base = (u32*)(long)dev_find_slot(0, PCI_DEVFN(0x14, 6))->resource_list->base;
	gec_shadow = (u8*)(pci_read_config32(dev_find_slot(0, PCI_DEVFN(0x14, 3)), 0x9C) & 0xFFFFFC00);
	printk(BIOS_DEBUG, "Upload GbE 'NV'RAM contents @ 0x%08lx\n", (unsigned long)gec_shadow);

	/* Halt RISC CPU before uploading the firmware patch */
	for (i=10000; i > 0; i--) {
		gec_base[0x5004/4] = 0xFFFFFFFF; //clear CPU state
		gec_base[0x5000/4] |= (1<<10);   //issue RISC halt
		if (gec_base[0x5000/4] | (1<<10))
			break;
		udelay(10);
	}
	if (!i)
		printk(BIOS_ERR, "Failed to halt RISC CPU!\n");

	/* Calculate checksums (standard byte sum) */
	for (sum = 0, i = 0; i < sizeof(selfboot_patch.header); i++)
		sum -= ((u8*)&selfboot_patch.header)[i];
	selfboot_patch.header.checksum = sum;
	for (sum = 0, i = 0; i < sizeof(selfboot_patch.init); i++)
		sum -= ((u8*)&selfboot_patch.init)[i];
	selfboot_patch.init.checksum = sum;
	for (sum = 0, i = 0; i < sizeof(selfboot_patch.powerdown); i++)
		sum -= ((u8*)&selfboot_patch.powerdown)[i];
	selfboot_patch.powerdown.checksum = sum;

	/* Upload firmware patch to shadow 'NV'RAM */
	for (i = 0; i < sizeof(selfboot_patch); i++)
		gec_shadow[i] = ((u8*)&selfboot_patch)[i]; //access byte-wise!

	/* Restart BCM5785's CPU */
	gec_base[0x5004/4] = 0xFFFFFFFF; //clear CPU state
	gec_base[0x5000/4] = 0x00000001; //reset RISC processor
	//usually we'd have to wait for the reset bit to clear again ...
}