Programming the LAN9221 ethernet controller | |
(on the Foenix A2560K in C) | |
I am not a professional programmer and I had never tried to | |
write anything like a 'device driver' or similar. I was faced | |
with the challenge to write my 'own' driver for my Foenix | |
A2560K modern retro machine. It doesn't come with an OS, you | |
can't run Linux on it. So all you got is the bare machine. And | |
I needed connectivity, preferably through Ethernet. So what do | |
you do? | |
For me, there are 2 important sources of information, of which | |
one you'll find here. A assembly-wizard I met on the Foenix | |
Retro Discord (by the screenname of 'gadget') created excellent | |
Motorola 680x0 assembly code that can read and write to the | |
controller. That code gave a pretty good insight into the way | |
the LAN controller should be handled. But..it was assembly code | |
and it was an extract from real purposed application code, so | |
not really a reuseable library. | |
The other resource is, of course, the original datasheet of the | |
Microchip LAN9221 controller: | |
The LAN9221 Datasheet | |
As you can see from the datasheet the LAN9221 has an enormous | |
array of registers that you must use to control it. The MAC and | |
PHY modules can not be accessed directly, but need indirect | |
read/write operations by commands that you have to give through | |
separate command and data registers for these modules. We'll | |
get to that later. | |
The datasheet translates into an extensive header file, in | |
which you'll recognize much of the datasheet's nomenclature: | |
The lan9221.h header file | |
The basics of controlling the 9221 are about reading and writing | |
its registers. So the first thhin we need to be able to do, | |
even before we are going to try to initialize the controller, we | |
need to be able to read and write to its internal registers. | |
Reading the registers: eth_reg_read() | |
This should be very straightforward, right? What could be | |
complicated about reading or writing to a known memory location? | |
Well, there is one caveat: the endiness of the 32 bits (4 bytes) | |
that you read or write. | |
<explain the endiness here> | |
Now that we know this, writing the registers should be pretty | |
straightforward: | |
Writing the registers: eth_reg_write() | |
Now that we know how to read and write the registers, we are ready | |
to initialize the module in our Foenix. | |
To initialize the module, we do the following: | |
step 1: wait until the module is 'up' | |
step 2: check the byte-endiness by reading the BYTE_TEST register | |
check: this register ALWAYS returns 0x87654321 | |
step 3: read the device ID (for fun) | |
step 4: check the physical connection | |
The following code-snippet implements this as eth_init(): | |
Initialize controller: eth_init() | |
Set MAC address (eth_set_mac_address) | |