korena's blog

14. Bootloader Implementation - part 4

In this post, we'll start by looking at the schematics, and define what is connected to what, and what internal controller is responsible for operating the DM9000A chip we have on board. We'll then fetch the required information from the documentation, and define the required setup.

schematics

The following picture shows the connection of the DM9000A chip to the s5pv210 processor:

since I have the Tiny210 V1, this chip does not reside on the tiny210 board, but on tinySDK (V1.0_1205) platform, looking at the schematics, the S5PV210 chip connects to the DM9000AEP chip as follows:

Address line pin Xm0ADDR2 of s5pv210 is connected to CMD pin of DM9000AEP.
port xm0DATA[15:0] pins of s5pv210 memory port 0 are connected to SD[15:0] pins of DM9000AEP chip.
pin XEINT7/GPH0_7 of S5PV210 is connected to INT pin of DM9000AEP chip.
pin Xm0OEn/MP01_6 of S5PV210 memory port 0 is connected to IOR# pin of DM9000AEP chip.
pin xm0WEn/MP01_7 of S5PV210 memory port 0 is connected to IOW# pin of DM9000AEP chip.
pin xm0CSn1 of the S5PV210 memory port 0 is connected to #CS pin of DM9000AEP chip.
pin XnRSTOUT of the S5PV210 memory port 0 is coneected to PWRST# pin of DM9000AEP chip

we know from the connection of xm0CSn1 that SROMC_BANK1 is selected!, from 2.1.1 DEVICE SPECIFIC ADDRESS SPACE of S5PV210 UM , we know that address 0x8800_0000 to 0x8FFF_FFFF, 128MB space is for SROM Bank 1, this is where we figure out that, the controller responsible for driving the DM9000 chip is SROC.

so I'll set 0x88000000 as the base address for our DM9000AEP device.

The SROM controller of the S5PV210 is described in the user manual, it goes into timing diagrams and properties essential for controlling SRAM modules, so one has to match these values to whatever module is used. In my case, the controlled module is the DM9000AEP chip, which acts as an SRAM device, and seen by the SoC as one.
The timing data is described in the DM9000AEP datasheet, fortunately, I did not have to go through it, as the timing configuration was available in the Linux kernel port of this chip's driver, I simply copied it, and trusted it.

In bl2/include/dm9000.h:

#define S5P_SROM_BCX__PMC__SHIFT               0
#define S5P_SROM_BCX__TACP__SHIFT              4
#define S5P_SROM_BCX__TCAH__SHIFT              8
#define S5P_SROM_BCX__TCOH__SHIFT              12
#define S5P_SROM_BCX__TACC__SHIFT              16
#define S5P_SROM_BCX__TCOS__SHIFT              24
#define S5P_SROM_BCX__TACS__SHIFT              28

In bl2/src/dm9000.c:

__raw_writel((0x1 << S5P_SROM_BCX__PMC__SHIFT) |
   	(0x9 << S5P_SROM_BCX__TACP__SHIFT) |
        (0xc << S5P_SROM_BCX__TCAH__SHIFT) |
        (0x1 << S5P_SROM_BCX__TCOH__SHIFT) |
        (0x6 << S5P_SROM_BCX__TACC__SHIFT) |
        (0x1 << S5P_SROM_BCX__TCOS__SHIFT) |
        (0x1 << S5P_SROM_BCX__TACS__SHIFT), 0xE8000008);

From DM9000AEP datasheet, we know that we have two distinct operating modes, our chip corresponds to the 16-bit operating mode, so we expect our data bus to be 16-bits wide, looking at S5PV210's user manual, section 2.4.1.1 SROM BusWidth &Wait Control Register, we see that bit [4] controls the width of bank one's data bus, and that it defaults to 8-bit mode, so this needs to be changed. We also observe that SROM address base for memory bank 1 defaults to half word base address, so we'll keep this bit [5] unchanged. We'll also set bit [6] which is wait enable, and also enable bit [7] which is byte enable, these last two bits do not have any effect on the results I get, because of the fact that they're not shown as connected to the DM9000A chip on the schematics, the simple truth is, I was having trouble getting the DM9000 chip to respond in a reasonable way, and I set out to try every possible route before giving up, and these bits were set when I actually caught the cause of the issue I was facing, so I just left them in, we'll discuss the problem I faced next.

basic communication with DM9000AEP chip

The application note of DM9000AEP declares that the IO address is selected based on some EEPROM value, OR based on hard wiring , the tiny210 seems to be hard wiring the DM9000AEP IO base address to 300H. this maps the base of the selected SROM bank on the s5pv210 side (Bank1 in our case) to the address 300H in the DM9000AEP chip, meaning if your program accesses the base address of this SROM bank , it's like accessing the contents of address 300H, of course, it's not that simple, there's a procedure for accessing the contents of whatever register is at that address (NCR if you're wondering what's at addr 300H of the DM9000AEP chip), and to read/write data of this register, you just add 0x04 to 300H, this is what the application notes of the DM9000AEP says, however, because of some not so obvious issue with the way the SROMC is connected, we'll be using 0x08 instead of 0x04. To make things clear, let's take an example of reading the contents of NCR register:

  1. perform select command sequence by clearing CMD line (LOW), and passing 300H (code passes DM9000_IO) through the hardware magic box (read 2.2 FUNCTIONAL DESCRIPTION of the UM), this selects NCR register.

  2. read contents of the register by setting CMD line (HIGH), and grabbing what is placed in address 300H + 8 (DM9000_DATA = DM9000_IO+8)

This is where things got nasty, I could find no explanation to this (+8) instead of (+4) behavior, monitoring the lines and printing the values, I can see that writing to base address 0x88000000 (our SROM bank 1 base address, that's DM9000_IO above) fills the next 6 address locations with that written value (0x88000000 through 0x88000006 will have that same value written to 0x88000000!), and the DM9000 chip will accept/return the value of the selected register in 0x88000008, which fills the next 6 address locations with that accepted/returned value. This looks like and addressing issue, but who cares, by virtue of brute force, I managed to ignore this issue by defining DM9000_DATA to be DM9000_IO+8 and moved on.

DM9000AEP internal registers

This is something you can spend hours fetching from the datasheet of the chip, or you can head over to some open source project and grab the whole thing and use it, I got this from u-boot (or Linux, not sure):

dm9000.h


/*
 *  dm9000 Ethernet
 *   
 */
#ifndef DM9000_H_
#define DM9000_H_

#define DM9000_ID               0x90000A46
#define DM9000_PKT_MAX          1536    /* Received packet max size */
#define DM9000_PKT_RDY          0x01    /* Packet ready to receive */
#define DM9000_16BIT_DATA
/* although the registers are 16 bit, they are 32-bit aligned.
 *  */

#define DM9000_NCR             0x00
#define DM9000_NSR             0x01
#define DM9000_TCR             0x02
#define DM9000_TSR1            0x03
#define DM9000_TSR2            0x04
#define DM9000_RCR             0x05
#define DM9000_RSR             0x06
#define DM9000_ROCR            0x07
#define DM9000_BPTR            0x08
#define DM9000_FCTR            0x09
#define DM9000_FCR             0x0A
#define DM9000_EPCR            0x0B
#define DM9000_EPAR            0x0C
#define DM9000_EPDRL           0x0D
#define DM9000_EPDRH           0x0E
#define DM9000_WCR             0x0F

#define DM9000_PAR             0x10
#define DM9000_MAR             0x16

#define DM9000_GPCR            0x1e
#define DM9000_GPR             0x1f
#define DM9000_TRPAL           0x22
#define DM9000_TRPAH           0x23
#define DM9000_RWPAL           0x24
#define DM9000_RWPAH           0x25

#define DM9000_VIDL            0x28
#define DM9000_VIDH            0x29
#define DM9000_PIDL            0x2A
#define DM9000_PIDH            0x2B

#define DM9000_CHIPR           0x2C
#define DM9000_SMCR            0x2F

#define DM9000_PHY              0x40    /* PHY address 0x01 */

#define DM9000_MRCMDX          0xF0
#define DM9000_MRCMD           0xF2
#define DM9000_MRRL            0xF4
#define DM9000_MRRH            0xF5
#define DM9000_MWCMDX           0xF6
#define DM9000_MWCMD           0xF8
#define DM9000_MWRL            0xFA
#define DM9000_MWRH            0xFB
#define DM9000_TXPLL           0xFC
#define DM9000_TXPLH           0xFD
#define DM9000_ISR             0xFE
#define DM9000_IMR             0xFF

#define NCR_EXT_PHY             (1<<7)
#define NCR_WAKEEN              (1<<6)
#define NCR_FCOL                (1<<4)
#define NCR_FDX                 (1<<3)
#define NCR_LBK                 (3<<1)
#define NCR_LBK_INT_MAC         (1<<1)
#define NCR_LBK_INT_PHY         (2<<1)
#define NCR_RST                 (1<<0)

#define NSR_SPEED               (1<<7)
#define NSR_LINKST              (1<<6)
#define NSR_WAKEST              (1<<5)
#define NSR_TX2END              (1<<3)
#define NSR_TX1END              (1<<2)
#define NSR_RXOV                (1<<1)

#define TCR_TJDIS               (1<<6)
#define TCR_EXCECM              (1<<5)
#define TCR_PAD_DIS2    (1<<4)
#define TCR_CRC_DIS2    (1<<3)
#define TCR_PAD_DIS1    (1<<2)
#define TCR_CRC_DIS1    (1<<1)
#define TCR_TXREQ               (1<<0)

#define TSR_TJTO                (1<<7)
#define TSR_LC                  (1<<6)
#define TSR_NC                  (1<<5)
#define TSR_LCOL                (1<<4)
#define TSR_COL                 (1<<3)
#define TSR_EC                  (1<<2)

#define RCR_WTDIS               (1<<6)
#define RCR_DIS_LONG    (1<<5)
#define RCR_DIS_CRC             (1<<4)
#define RCR_ALL                 (1<<3)
#define RCR_RUNT                (1<<2)
#define RCR_PRMSC               (1<<1)
#define RCR_RXEN                (1<<0)

#define RSR_RF                  (1<<7)
#define RSR_MF                  (1<<6)
#define RSR_LCS                 (1<<5)
#define RSR_RWTO                (1<<4)
#define RSR_PLE                 (1<<3)
#define RSR_AE                  (1<<2)
#define RSR_CE                  (1<<1)
#define RSR_FOE                 (1<<0)

#define EPCR_EPOS_PHY           (1<<3)
#define EPCR_EPOS_EE            (0<<3)
#define EPCR_ERPRR              (1<<2)
#define EPCR_ERPRW              (1<<1)
#define EPCR_ERRE               (1<<0)

#define FCTR_HWOT(ot)   (( ot & 0xf ) << 4 )
#define FCTR_LWOT(ot)   ( ot & 0xf )

#define BPTR_BPHW(x)    ((x) << 4)
#define BPTR_JPT_200US          (0x07)
#define BPTR_JPT_600US          (0x0f)

#define IMR_PAR                 (1<<7)
#define IMR_ROOM                (1<<3)
#define IMR_ROM                 (1<<2)
#define IMR_PTM                 (1<<1)
#define IMR_PRM                 (1<<0)

#define ISR_ROOS                (1<<3)
#define ISR_ROS                 (1<<2)
#define ISR_PTS                 (1<<1)
#define ISR_PRS                 (1<<0)

#define GPCR_GPIO0_OUT          (1<<0)

#define GPR_PHY_PWROFF          (1<<0)


#define S5P_SROM_BCX__PMC__SHIFT               0
#define S5P_SROM_BCX__TACP__SHIFT              4
#define S5P_SROM_BCX__TCAH__SHIFT              8
#define S5P_SROM_BCX__TCOH__SHIFT              12
#define S5P_SROM_BCX__TACC__SHIFT              16
#define S5P_SROM_BCX__TCOS__SHIFT              24
#define S5P_SROM_BCX__TACS__SHIFT              28



/**************** function prototypes ********************/
int dm9000_initialize(void);
void dm9000_write_srom_word(int offset, uint16_t val);
void dm9000_read_srom_word(int offset, uint8_t *to);

#define CONFIG_DM9000_DEBUG

/**************** TINY210 *************/
#define SROMC_BW                0xE8000000
#define CONFIG_DM9000_BASE       0x88000000   // SROMC_BANK1 base
#define DM9000_IO       CONFIG_DM9000_BASE
#define DM9000_DATA     (CONFIG_DM9000_BASE+8)

#endif

my own modification starts at line 142 onward, I basically defined the timing data for SROM bank 1, exposed some functions to be used by other modules, and defined the base addresses to control the DM9000 chip.

Porting the code

The process of porting the driver is a matter of stripping it's dependency on the echo system of u-boot, and having it hook to our own simple interfaces. Before we can do that, we have to take a deeper look at how u-boot handles the networking stack.

Driver initialization path in u-boot

NOTE: If you want to have a painless u-boot source code browsing experience, head over to code.metager.de, thank me now.

U-boot has two ways of dealing with network drivers, a newer way which doesn't concern us right now, and an old one that is used to manage the driver for the DM9000AEP chip. There are quiet a lot of structures involved in the process of managing and using drivers, what we need to focus on are answers to the following questions:

Where do I start?

Everything network related is in the following directories:

  • /net, where you'll find implementations of stuff like PING and ARP handling, initialization entry points, state machine loops and various protocol handlers.

  • /drivers/net, where you'll find header files defining driver specific macros, and the actual driver implementation of the supported network chips.

  • /include/net.h header file.

How does u-boot initialize DM9000A driver?

read ethernet drivers readme (Old), what's important here is the calling graph:

board_init() eth_initialize() board_eth_init() / cpu_eth_init() driver_register() initialize eth_device eth_register()

which tells us that we have to look for a function called eth_initialize(), since it's probably where drivers differ in their specific initialization procedures.

we find this function in file /net/eth.c, the first thing this function does is call some static function called eth_common_init(), which in turns, depending on some conditions, calls driver specific initialization functions. The relevant code inside eth_common_init looks like:

if (board_eth_init != __def_eth_init) {
             if (board_eth_init(gd->bd) < 0)
                     printf("Board Net Initialization Failed\n");
     } else if (cpu_eth_init != __def_eth_init) {
             if (cpu_eth_init(gd->bd) < 0)
                     printf("CPU Net Initialization Failed\n");
     } else {
#ifndef CONFIG_DM_ETH
             printf("Net Initialization Skipped\n");
#endif
     }

inspecting board_eth_init, we see that it leads to multiple possible function implementations, each residing in a board specific code. To get a feel of what such an implementation looks like, lets take the implementation under the first result we get, which is /denx/u-boot/board/bf538f-ezkit/bf538f-ezkit.c, we see the following:

int board_eth_init(bd_t *bis)
{
      return smc91111_initialize(0, CONFIG_SMC91111_BASE);
}

we can see that smc9111_initialize(...) is a call into SMC9111 Ethernet chip driver initialization, now we don't know which board in u-boot uses our specific Ethernet driver (it's not that hard to find out though), but we know that u-boot calls our driver's initialization function the same way it does all other drivers, note that we could have started the other way around, we could have walked from the driver implementation inside /drivers/net/dm9000x.c backward, but we want to know what happens before the call to dm9000_initialize(...), mainly because there's a structure that is passed to the initialization function, and we'd like to figure out if we can drop it all together, or if supplying it would make our life easier ... OK, the real reason is that I just went this way, and the last sentence is just a useless justification, its definitely more sensible to start from the driver implementation, but bear with me.

From the listing above, we can see that the structure bd_t is completely ignored, this is probably due to the old/new way of hooking Ethernet drivers to u-boot, let's go to our driver initialization code and see if this structure is used in anyway

In /drivers/net/dm9000x.c:

int dm9000_initialize(bd_t *bis)
{
     struct eth_device *dev = &(dm9000_info.netdev);

     /* Load MAC address from EEPROM */
     dm9000_get_enetaddr(dev);

     dev->init = dm9000_init;
     dev->halt = dm9000_halt;
     dev->send = dm9000_send;
     dev->recv = dm9000_rx;
     sprintf(dev->name, "dm9000");

     eth_register(dev);

     return 0;
}

We see that bd_t structure is completely ignored, so we'll pay no attention to it, since we're here, lets take a look at the function body, and see what external structures it needs. The first thing we see is a struct eth_device, In /include/net.h this thing looks like:

struct eth_device {
     char name[16];
     unsigned char enetaddr[6];
     phys_addr_t iobase;
     int state;

     int (*init)(struct eth_device *, bd_t *);
     int (*send)(struct eth_device *, void *packet, int length);
     int (*recv)(struct eth_device *);
     void (*halt)(struct eth_device *);
#ifdef CONFIG_MCAST_TFTP
     int (*mcast)(struct eth_device *, const u8 *enetaddr, u8 set);
#endif
     int (*write_hwaddr)(struct eth_device *);
     struct eth_device *next;
     int index;
     void *priv;
};

it's obviously a linked list element, since we have only one eth_device in our board, we'll ignore the linked list structure, and treat this as a simple structure. We notice that this structure depends on something called phys_addr_t , this looks important, so we'll find out what it is ... turns out it's just an unsigned long, so we'll just use uint32_t and move on. The rest of the structure is simple, and contains basic types only.

Back to int dm9000_initialize(bd_t *bis) function, we have :

 struct eth_device *dev = &(dm9000_info.netdev);

we're assigning the address of 'something' to eth_device pointer dev, so there's a struct dm9000_info somewhere, searching for it gives the following in the same dm9000x.c file:

/* Structure/enum declaration ------------------------------- */
typedef struct board_info {
      u32 runt_length_counter;        /* counter: RX length < 64byte */
      u32 long_length_counter;        /* counter: RX length > 1514byte */
      u32 reset_counter;      /* counter: RESET */
      u32 reset_tx_timeout;   /* RESET caused by TX Timeout */
      u32 reset_rx_status;    /* RESET caused by RX Status wrong */
      u16 tx_pkt_cnt;
      u16 queue_start_addr;
      u16 dbug_cnt;
      u8 phy_addr;
      u8 device_wait_reset;   /* device state */
      unsigned char srom[128];
      void (*outblk)(volatile void *data_ptr, int count);
      void (*inblk)(void *data_ptr, int count);
      void (*rx_status)(u16 *RxStatus, u16 *RxLen);
      struct eth_device netdev;
} board_info_t;
static board_info_t dm9000_info;

This structure looks like a state saving/management data structure, we may have some use for it, so we'll keep it as is, since it has generic types, and causes no hassle.

Next, the initialization function calls dm9000_get_enetaddr, following the source, we see that this is a conditionally implemented static function, used to basically read the Ethernet hardware address from the internal SROM (EEPROM in our chip) register, we'll keep this bit, but it does not look useful to me know, let's see if we'll ever need it.

The code then moves on to register routines for sending, receiving, initializing and halting the Ethernet device, so let's get into the initialization routine:

/* Initialize dm9000 board
*/
static int dm9000_init(struct eth_device *dev, bd_t *bd)
{
        int i, oft, lnk;
        u8 io_mode;
        struct board_info *db = &dm9000_info;

        DM9000_DBG("%s\n", __func__);

        /* RESET device */
        dm9000_reset();

        if (dm9000_probe() < 0)
                return -1;

        /* Auto-detect 8/16/32 bit mode, ISR Bit 6+7 indicate bus width */
        io_mode = DM9000_ior(DM9000_ISR) >> 6;

        switch (io_mode) {
        case 0x0:  /* 16-bit mode */
                printf("DM9000: running in 16 bit mode\n");
                db->outblk    = dm9000_outblk_16bit;
                db->inblk     = dm9000_inblk_16bit;
                db->rx_status = dm9000_rx_status_16bit;
                break;
        case 0x01:  /* 32-bit mode */
                printf("DM9000: running in 32 bit mode\n");
                db->outblk    = dm9000_outblk_32bit;
                db->inblk     = dm9000_inblk_32bit;
                db->rx_status = dm9000_rx_status_32bit;
                break;
        case 0x02: /* 8 bit mode */
                printf("DM9000: running in 8 bit mode\n");
                db->outblk    = dm9000_outblk_8bit;
                db->inblk     = dm9000_inblk_8bit;
                db->rx_status = dm9000_rx_status_8bit;
                break;
        default:
                /* Assume 8 bit mode, will probably not work anyway */
                printf("DM9000: Undefined IO-mode:0x%x\n", io_mode);
                db->outblk    = dm9000_outblk_8bit;
                db->inblk     = dm9000_inblk_8bit;
                db->rx_status = dm9000_rx_status_8bit;
                break;
        }

        /* Program operating register, only internal phy supported */
        DM9000_iow(DM9000_NCR, 0x0);
        /* TX Polling clear */
        DM9000_iow(DM9000_TCR, 0);
        /* Less 3Kb, 200us */
        DM9000_iow(DM9000_BPTR, BPTR_BPHW(3) | BPTR_JPT_600US);
        /* Flow Control : High/Low Water */
        DM9000_iow(DM9000_FCTR, FCTR_HWOT(3) | FCTR_LWOT(8));
        /* SH FIXME: This looks strange! Flow Control */
        DM9000_iow(DM9000_FCR, 0x0);
        /* Special Mode */
        DM9000_iow(DM9000_SMCR, 0);
        /* clear TX status */
        DM9000_iow(DM9000_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END);
        /* Clear interrupt status */
        DM9000_iow(DM9000_ISR, ISR_ROOS | ISR_ROS | ISR_PTS | ISR_PRS);

        printf("MAC: %pM\n", dev->enetaddr);
        if (!is_valid_ethaddr(dev->enetaddr)) {
                printf("WARNING: Bad MAC address (uninitialized EEPROM?)\n");
        }

        /* fill device MAC address registers */
        for (i = 0, oft = DM9000_PAR; i < 6; i++, oft++)
                DM9000_iow(oft, dev->enetaddr[i]);
        for (i = 0, oft = 0x16; i < 8; i++, oft++)
                DM9000_iow(oft, 0xff);

        /* read back mac, just to be sure */
        for (i = 0, oft = 0x10; i < 6; i++, oft++)
                DM9000_DBG("%02x:", DM9000_ior(oft));
        DM9000_DBG("\n");

        /* Activate DM9000 */
        /* RX enable */
        DM9000_iow(DM9000_RCR, RCR_DIS_LONG | RCR_DIS_CRC | RCR_RXEN);
        /* Enable TX/RX interrupt mask */
        DM9000_iow(DM9000_IMR, IMR_PAR);

        i = 0;
        while (!(dm9000_phy_read(1) & 0x20)) {  /* autonegation complete bit */
                udelay(1000);
                i++;
                if (i == 10000) {
                        printf("could not establish link\n");
                        return 0;
                }
        }

        /* see what we've got */
        lnk = dm9000_phy_read(17) >> 12;
        printf("operating at ");
        switch (lnk) {
        case 1:
                printf("10M half duplex ");
                break;
        case 2:
                printf("10M full duplex ");
                break;
        case 4:
                printf("100M half duplex ");
                break;
        case 8:
                printf("100M full duplex ");
                break;
        default:
                printf("unknown: %d ", lnk);
                break;
        }
        printf("mode\n");
        return 0;
}

We see that there are a lot of functions used in communicating with the DM9000A chip, namely:

  • DM9000_ior (read a byte from specified address)
  • DM9000_iow (write byte to specified address)
  • dm9000_outblk_16bit (output half word data to specified register)
  • dm9000_inblk_16bit (read half word data from specified register)
  • dm9000_rx_status_16bit (read half word status data)

We've ignored the functions that are not expected to be used, those are the ones used when the detected operating mode is something other than 16 bit.

Let's take the first function, DM9000_ior :

/*
   Read a byte from I/O port
*/
static u8
DM9000_ior(int reg)
{
        DM9000_outb(reg, DM9000_IO);
        return DM9000_inb(DM9000_DATA);
}

This function uses two functions:

  • void DM9000_outb(uint32_t reg, uint8_t data)
  • uint8_t DM9000_inb(uint32_t reg)

These functions read and write byte data to and from registers, there are other functions that read and write half word and word data, so let's implement them real quick:

creating the file bl2/asm/io.s:

.text
.code 32

.global __raw_writeb
.global __raw_writehw
.global __raw_writel
.global __raw_readb
.global __raw_readhw
.global __raw_readl

__raw_writeb:
        push    {lr}
        strb    r0,[r1]
        pop     {pc}

__raw_writehw:
        push    {lr}
        strh    r0,[r1]
        pop     {pc}

__raw_writel:
        push    {lr}
        str     r0,[r1]
        pop     {pc}

__raw_readb:
        push    {lr}
        ldrb    r0,[r0]
        pop     {pc}

__raw_readhw:
        push    {lr}
        ldrh    r0,[r0]
        pop     {pc}

__raw_readl:
        push    {lr}
        ldr     r0,[r0]
        pop     {pc}

and then defining in bl2/src/dm9000.c:

#define DM9000_outb(d, r) __raw_writeb(d, r)
#define DM9000_outw(d, r) __raw_writehw(d, r)
#define DM9000_outl(d, r) __raw_writel(d, r)
#define DM9000_inb(r) __raw_readb(r)
#define DM9000_inw(r) __raw_readhw(r)
#define DM9000_inl(r) __raw_readl(r)

dm9000_init function also makes use of DM9000_DBG, which we defined as :

#define DM9000_DBG(fmt,...) print_format(fmt,##__VA_ARGS__)

print_format is our own implementation of printf in a previous post. Going through dm9000_init function, we see that it makes use of some other static functions that we went on and implemented following the same logic above. This makes our dm9000_init function complete, note that we also ignored the bd_t argument, cause it's ignored within the function.

Following the steps above, I finished porting dm9000.c driver, and added some headers for various defines, you can checkout the full code in the repository

Conclusion

We've ported the driver to initialize without depending on u-boot's echo system, a call from our simple boatloader to dm9000_initialize() should successfully initialize the chip and all relevant structures. Because of the amount of work needed to get this done, I've only provided some pointers to the approach I used to achieve this. We've also walked through the details of how the Ethernet chip is connected to our SoC, and the basic setup needed for our SROMC to control the DM9000A module. Next, we'll be looking at higher levels of control in the network stack inside u-boot, and try to trim it down to match our simple bootloader. We'll look into the receiving routine dm9000_rx, and try to initialize, then capture some data from a connection.

References