Jens Rottmann (JRottmann@LiPPERTembedded.de) just uploaded a new patch set to gerrit, which you can find at http://review.coreboot.org/2581
-gerrit
commit 7ae89c2ee20698137ef484cf867e8d3854ce1bbe Author: Jens Rottmann JRottmann@LiPPERTembedded.de Date: Fri Mar 1 19:41:41 2013 +0100
AMD Inagua: add GEC firmware, document Broadcom BCM57xx Selfboot Patch format
The Broadcom BCM5785 GbE MAC integrated in the AMD Hudson-E1 requires a secret sauce firmware blob to work. As Broadcom wasn't willing to send us any documentation (or a firmware adapted to our Micrel PHY) I had to figure out everything by myself in many weeks of hard detective work.
In the end we had to settle for a different solution, the modified firmware I devised for the Micrel KSZ9021 PHY on our early FrontRunner-AF prototypes is no longer needed for the production version. However the information contained here might be very useful for others who'd like to use a competing PHYs instead of Broadcom's 50610, so it should not get lost. And of course the unmodified, but now in large parts documented Selfboot Patch is needed to get Ethernet on AMD Inagua.
The fun thing is: as Broadcom refused to do any business with us at all, or send us any documentation, we never had to sign an NDA with them. This leaves me free to publish everything I have found out. :-)
Change-Id: Ifa628751d14143f277b27cfd34b1d2771ca1302f Signed-off-by: Jens Rottmann JRottmann@LiPPERTembedded.de --- src/mainboard/amd/inagua/Makefile.inc | 2 +- src/mainboard/amd/inagua/broadcom.c | 360 ++++++++++++++++++++++++++++++++++ src/mainboard/amd/inagua/mainboard.c | 4 + 3 files changed, 365 insertions(+), 1 deletion(-)
diff --git a/src/mainboard/amd/inagua/Makefile.inc b/src/mainboard/amd/inagua/Makefile.inc index b1a3014..1f653b5 100644 --- a/src/mainboard/amd/inagua/Makefile.inc +++ b/src/mainboard/amd/inagua/Makefile.inc @@ -37,4 +37,4 @@ ramstage-y += BiosCallOuts.c ramstage-y += PlatformGnbPcie.c
ramstage-y += reset.c - +ramstage-y += broadcom.c diff --git a/src/mainboard/amd/inagua/broadcom.c b/src/mainboard/amd/inagua/broadcom.c new file mode 100644 index 0000000..357fdbc --- /dev/null +++ b/src/mainboard/amd/inagua/broadcom.c @@ -0,0 +1,360 @@ +/* + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <types.h> +#include <arch/byteorder.h> +#include <console/console.h> +#include <device/device.h> //Coreboot device access +#include <device/pci.h> +#include <delay.h> + +#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 ... +} diff --git a/src/mainboard/amd/inagua/mainboard.c b/src/mainboard/amd/inagua/mainboard.c index 8907ebb..af2f3c7 100644 --- a/src/mainboard/amd/inagua/mainboard.c +++ b/src/mainboard/amd/inagua/mainboard.c @@ -27,6 +27,7 @@ //#include <southbridge/amd/sb800/sb800.h> #include "SBPLATFORM.h" /* Platfrom Specific Definitions */
+void broadcom_init(void); void set_pcie_reset(void); void set_pcie_dereset(void);
@@ -79,6 +80,9 @@ static void mainboard_enable(device_t dev)
/* Inagua mainboard specific setting */ set_pcie_dereset(); + + /* Upload AMD A55E GbE 'NV'RAM contents. */ + broadcom_init(); }
struct chip_operations mainboard_ops = {