Running code on the CI20 without uboot
This is part of a large series on the CI20, the CI20 bare-metal project.
Previously we were using uboot to load the code. This is pretty convenient, but it does mean that there are some mysteries about setting up the board’s hardware which uboot hides from view. What does it do? This post and the next one will explore that a little, by writing some code which doesn’t use it at all.
Where is the RAM?
One thing I took for granted previously is that the board has RAM available for us at 0x80000000, i.e. at the start of KSEG0. We know that the board has 1GB of RAM, and that RAM has to be accessible to us somewhere.
Or does it? The JZ4780 programmer’s manual is full of references to a “DDR [RAM] Controller”, which has quite a complicated set-up. But if the DDR controller has to be initialised, where does that initialisation code run?
It turns out that on the JZ4780, there is in fact a separate tiny (16k) bit of static ram, known as the tighly-coupled shared memory area, or TCSM, which solves this problem: you can copy your DDR initialisation code into TCSM and then copy the rest into DDR.
Of course, now we are faced with the problem of getting our code into the TCSM. To deal with this problem, the JZ4780 includes a small ROM capable of loading things into TCSM. This boot room is quite sophisticated, supporting NAND, SD, USB, and SPI loading.
Booting from USB
If we’re going to play around, then USB loading is quite an attractive option because it’s nice and fast (compared with unplugging SD cards and so on). Unfortunately the USB loader is a bit finicky: the device doesn’t even enumerate properly on Mac OS X (though it’s fine on Linux), and it requires a custom loader. I wrote the custom loader, so if you have access to a Linux box (or, perhaps, if you’re running Linux in a VM) you can follow along.
By default, the CI20 requires you to hold down a button to do a USB boot. That sounded a bit tedious, so I soldered some headers to the button terminals:
Connecting a switch to the headers means I can have always-on USB booting.
We’re going to use the USB OTG port as a peripheral. Remove the jumper from the pins next to the port. This turns the port into a USB peripheral, rather than a host. You can then connect a mini-USB cable from your computer to the OTG port and power up the board. If all is well, you should see a new USB device appear (run lsusb on Linux, or use USB Prober, or System Information, on a Mac).
Rather than flashing a LED, this example now communicates over a serial port — the same one, UART4, used by uboot and the Linux kernel, so if you set up a usb-serial cable for previous parts this will work without any changes.
Running the example
I’ve created a new repository for this stuff, which you can check out using git:
$ git clone https://github.com/nfd/ci20-os
$ cd ci20-os
$ git checkout tags/usbloader
You’ll need to build the bootloader (make sure you’ve set up your environment as described in my previous CI20 posts):
$ make bootloader.bin
Now reset the board and load the example using my USB loader. Note that you will need to install pyusb before running this (and that, in turn, will require libusb0).
$ python3 usbloader.py bootloader.bin
If you get “permission denied” errors, you can set up udev rules to give yourself access to the port. Or you can use sudo if you’re naughty.
You should then get a cheery message on the serial port.
This example hasn’t actually initialised the DDR. It’s just running straight out of TCSM at the special TCSM location 0xf4000000. Overall, the board is only just barely initialised enough to get stuff out of the serial port. Next time we’ll look at setting up the DDR so that we can begin to think about loading things back into the familiar 0x80000000 “proper” RAM, and see what other board initialisation is needed by the jz4780.