Tinker Board Forum

Full Version: Booting from SPI dataflash
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Hello.

I prepared and tested step by step procedure to setup SPI dataflash for booting (u-boot) for everyone interested  Big Grin.

This is usable for following scenarios  Idea :
Follow the steps:
  1. Connect SPI dataflash to SPI2/CS0.
  2. Compile u-boot for SPI dataflash.
  3. Flash u-boot to SPI dataflash.
  4. Connect optional serial line to interact with u-boot.
  5. Modify root mount options on USB with TinkerOS (or other distribution).

Theory of operation:
RK3288 processor has embedded Bootrom that can boot from SPI (exactly SPI2 and chipselect CS0) (see TRM pages 13-16).
  1. The Bootrom code scans SPI (addresses 0x0000, 0x2000, 0x4000 ...) with usual command for SPI dataflash "0x03 + 3 bytes address" reads 64 bytes and check valid signature.
  2. If signature is found then 32kB code is download to embedded SRAM (96kB) from SPI dataflash with special addressing "base+0, base+4k, base+8k, base+12k ... and only 2k are downloaded (this is needed because some SPI flash does not have 2048B page but 2112B page to have space for ECC correction data (for NAND flash type it is required)). This is referred as SPL code (secondary program loader).
  3. SPL code is run from SRAM and must contain DRAM initialization and can return to Bootrom code or continue to load next stage usually U-boot code.
  4. U-boot code code is located in SPI dataflash at address 128kB and can be large (as configured). U-boot code has specific header (magic, len, checksum, load address, start entry address). U-boot is read in continuous mode (AT45 command 0xE8).
  5. U-boot code is executed and parses U-boot variables and tries to load linux kernel with parameters for example from USB FAT formated storage.
  6. Linux kernel is started.

Step A: Connect SPI dataflash to SPI2/CS0:
Choose, buy and connect supported SPI flash (>=512kB). I used Atmel/Adesto AT45DB041D (~ 1€, 4Gbit = 512kB, in 256B sector mode) and connect it to connector pins #19, #21, #23 and #24, 3.3V and GND (/WP and /RESET are connected to 3.3V) (newer AT45DB041E should work).

Step B: Compile u-boot for SPI dataflash:
I prepared special variant of U-boot to match some limitation (like size down SPL code binary to ~30000B). I published modified sources on github (see last 3 commits!, limited support only to AT45DB041).
Example cross compile on Ubuntu1604:
Code:
# git clone --depth 1 --branch tinkerboard-spi https://github.com/mcerveny/rockchip-u-boot.git
# cd rockchip-u-boot
# cp dot_config .config
# export ARCH=arm
# export CROSS_COMPILE=arm-linux-gnueabihf-
# make all
# ### check SPL+DTB ~ 30000B !
# ls -l spl/
-rw-r--r--  1 root root  30084 Jul  5 19:19 u-boot-spl-dtb.bin
# ### make image for SPI dataflash
# tools/mkimage -n rk3288 -T rkspi -d spl/u-boot-spl-dtb.bin spl.img
# dd if=spl.img of=all.img bs=128K conv=sync
# cat u-boot-dtb.img >> all.img
# scp all.img "to tinkerboard or other system with SPI flash capability"

Step C: Flash u-boot to SPI dataflash:
I created and attached simple python script for basic SPI AT45DB* flashing with TinkerBoard. Beware of bug in SPI driver (UPDATE: resolved with TinkerOS >= 2.0.1).
Code:
# ### convert AT45 from 264B to 256B sector mode
# python spi.py sector
JEDEC: 1f 24 00 00 00 00
sector mode 264->256 ...
# ### power on-off SPI flash
# ### write all image
# python spi.py write all.img
JEDEC: 1f 24 00 00 00 00
write ...
00000000 size 100: 3b 8c dc fc  ...  wait  wait  wait  wait  wait  wait  
00000100 size 100: 2d f8 ba 7e  ...  wait  wait  wait  wait  wait  wait  wait  wait  
00000200 size 100: 00 00 00 00  ...  wait  wait  wait  wait  wait  wait  wait  wait  
00000300 size 100: 00 00 00 00  ...  wait  wait  wait  wait  wait  wait  wait  wait  wait  
00000400 size 100: 00 00 00 00  ...  wait  wait  wait  wait  wait  wait  wait  
00000500 size 100: 00 00 00 00  ...  wait  wait  wait  wait  wait  wait  
...
0007e500 size 100: 6e 63 65 00  ...  wait  wait  wait  wait  wait  wait  wait  
0007e600 size 100: 6c 65 72 00  ...  wait  wait  wait  wait  wait  wait  wait  
0007e700 size 100: 62 6f 6f 74  ...  wait  wait  wait  wait  wait  wait  wait
# ### compare SPI dataflash and sources
# python spi.py compare all.img
JEDEC: 1f 24 00 00 00 00
compare ...
# ### ok if no differences shown

Step D: optional serial line to interact with u-boot.
I connected serial-usb converter (compatible with 3.3V outputs) to TinkerBoard UART2 connector pins #32 and #33.
Example output from autoboot from USB TikerOS root.
Code:
U-Boot SPL 2017.05-g6f6fd51 (Jul 05 2017 - 17:27:25)
Trying to boot from SPI

U-Boot 2017.05-g6f6fd51 (Jul 05 2017 - 17:27:25 +0200)

Model: Tinker-RK3288
DRAM:  rockchip_sdram_size ff73009c 32a0d2a0
rank 1 col 10 bk 3 cs0_row 15 bw 2 row_3_4 0
rank 1 col 10 bk 3 cs0_row 15 bw 2 row_3_4 0
size 80000000
SDRAM base=0, size=80000000
2 GiB
MMC:   dwmmc@ff0c0000: 1
Using default environment

In:    serial
Out:   serial
Err:   serial
Net:   eth0: ethernet@ff290000
Hit any key to stop autoboot:  0
MMC Device 0 not found
no mmc device at slot 0
Card did not respond to voltage select!
mmc_init: -95, time 2118
MMC Device 1 not found
no mmc device at slot 1
starting USB...
USB0:   Core Release: 3.10a
USB1:   Core Release: 3.10a
dwc_otg_core_host_init: Timeout!
...
dwc_otg_core_host_init: Timeout!
scanning bus 0 for devices... 5 USB Device(s) found
scanning bus 1 for devices... 1 USB Device(s) found
      scanning usb for storage devices... 1 Storage Device(s) found

USB device 0:
   Device 0: Vendor: Generic  Rev: 0272 Prod: STORAGE DEVICE  
           Type: Removable Hard Disk
           Capacity: 7580.0 MB = 7.4 GB (15523840 x 512)
... is now current device
Scanning usb 0:1...
Found /extlinux/extlinux.conf
Retrieving file: /extlinux/extlinux.conf
reading /extlinux/extlinux.conf
158 bytes read in 23 ms (5.9 KiB/s)
1:    kernel-4.4
Retrieving file: /zImage
reading /zImage
7456672 bytes read in 4168 ms (1.7 MiB/s)
append: earlyprintk console=ttyS2,115200n8 console=tty1 root=/dev/sda2 rw init=/sbin/init
Retrieving file: /rk3288-miniarm.dtb
reading /rk3288-miniarm.dtb
45696 bytes read in 50 ms (891.6 KiB/s)
## Flattened Device Tree blob at 01f00000
  Booting using the fdt blob at 0x1f00000
  Loading Device Tree to 0fff1000, end 0ffff27f ... OK

Starting kernel ...

[    0.000000] Booting Linux on physical CPU 0x500
[    0.000000] Initializing cgroup subsys cpuset
[    0.000000] Initializing cgroup subsys cpu
[    0.000000] Initializing cgroup subsys cpuacct
....

Example output from autoboot with no device attached (see try from MMC, USB OTG, USB, PXE and finally U-boot prompt).
Code:
U-Boot SPL 2017.05-g0edf75f (Jul 06 2017 - 16:09:59)
Trying to boot from SPI

U-Boot 2017.05-g0edf75f (Jul 06 2017 - 16:09:59 +0200)

Model: Tinker-RK3288
DRAM:  rockchip_sdram_size ff73009c 32a0d2a0
rank 1 col 10 bk 3 cs0_row 15 bw 2 row_3_4 0
rank 1 col 10 bk 3 cs0_row 15 bw 2 row_3_4 0
size 80000000
SDRAM base=0, size=80000000
2 GiB
MMC:   dwmmc@ff0c0000: 1
Using default environment

In:    serial
Out:   serial
Err:   serial
Net:   eth0: ethernet@ff290000
Hit any key to stop autoboot:  0
MMC Device 0 not found
no mmc device at slot 0
Card did not respond to voltage select!
mmc_init: -95, time 2119
MMC Device 1 not found
no mmc device at slot 1
starting USB...
USB0:   Core Release: 3.10a
USB1:   Core Release: 3.10a
dwc_otg_core_host_init: Timeout!
...
dwc_otg_core_host_init: Timeout!
scanning bus 0 for devices... 4 USB Device(s) found
scanning bus 1 for devices... 1 USB Device(s) found
      scanning usb for storage devices... 0 Storage Device(s) found

USB device 0: unknown device
ethernet@ff290000 Waiting for PHY auto negotiation to complete....... done
Speed: 1000, full duplex
BOOTP broadcast 1
BOOTP broadcast 2
BOOTP broadcast 3
*** Unhandled DHCP Option in OFFER/ACK: 2
*** Unhandled DHCP Option in OFFER/ACK: 42
*** Unhandled DHCP Option in OFFER/ACK: 4
*** Unhandled DHCP Option in OFFER/ACK: 252
BOOTP broadcast 4
*** Unhandled DHCP Option in OFFER/ACK: 2
*** Unhandled DHCP Option in OFFER/ACK: 42
*** Unhandled DHCP Option in OFFER/ACK: 4
*** Unhandled DHCP Option in OFFER/ACK: 252
*** Unhandled DHCP Option in OFFER/ACK: 2
*** Unhandled DHCP Option in OFFER/ACK: 42
*** Unhandled DHCP Option in OFFER/ACK: 4
*** Unhandled DHCP Option in OFFER/ACK: 252
DHCP client bound to address 192.168.1.131 (6314 ms)
*** Warning: no boot file name; using 'C0A80183.img'
Using ethernet@ff290000 device
TFTP from server 0.0.0.0; our IP address is 192.168.1.131; sending through gateway 192.168.1.1
Filename 'C0A80183.img'.
Load address: 0x800800
Loading: T T T
TFTP error: 'Access violation' (2)
Not retrying...
missing environment variable: pxeuuid
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/01-2c-4d-54-##-##-##
Speed: 1000, full duplex
*** ERROR: `serverip' not set
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/C0A80183
Speed: 1000, full duplex
*** ERROR: `serverip' not set
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/C0A8018
Speed: 1000, full duplex
*** ERROR: `serverip' not set
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/C0A801
Speed: 1000, full duplex
*** ERROR: `serverip' not set
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/C0A80
Speed: 1000, full duplex
*** ERROR: `serverip' not set
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/C0A8
Speed: 1000, full duplex
*** ERROR: `serverip' not set
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/C0A
Speed: 1000, full duplex
*** ERROR: `serverip' not set
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/C0
Speed: 1000, full duplex
*** ERROR: `serverip' not set
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/C
Speed: 1000, full duplex
*** ERROR: `serverip' not set
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/default-arm-rockchip
Speed: 1000, full duplex
*** ERROR: `serverip' not set
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/default-arm
Speed: 1000, full duplex
*** ERROR: `serverip' not set
missing environment variable: bootfile
Retrieving file: pxelinux.cfg/default
Speed: 1000, full duplex
*** ERROR: `serverip' not set
Config file not found
Speed: 1000, full duplex
BOOTP broadcast 1
BOOTP broadcast 2
BOOTP broadcast 3
*** Unhandled DHCP Option in OFFER/ACK: 2
*** Unhandled DHCP Option in OFFER/ACK: 42
*** Unhandled DHCP Option in OFFER/ACK: 4
*** Unhandled DHCP Option in OFFER/ACK: 252
*** Unhandled DHCP Option in OFFER/ACK: 2
*** Unhandled DHCP Option in OFFER/ACK: 42
*** Unhandled DHCP Option in OFFER/ACK: 4
*** Unhandled DHCP Option in OFFER/ACK: 252
DHCP client bound to address 192.168.1.131 (1296 ms)
Using ethernet@ff290000 device
TFTP from server 0.0.0.0; our IP address is 192.168.1.131; sending through gateway 192.168.1.1
Filename 'boot.scr.uimg'.
Load address: 0x0
Loading: *
TFTP error: 'Access violation' (2)
Not retrying...
Speed: 1000, full duplex
BOOTP broadcast 1
BOOTP broadcast 2
*** Unhandled DHCP Option in OFFER/ACK: 2
*** Unhandled DHCP Option in OFFER/ACK: 42
*** Unhandled DHCP Option in OFFER/ACK: 4
*** Unhandled DHCP Option in OFFER/ACK: 252
BOOTP broadcast 3
*** Unhandled DHCP Option in OFFER/ACK: 2
*** Unhandled DHCP Option in OFFER/ACK: 42
*** Unhandled DHCP Option in OFFER/ACK: 4
*** Unhandled DHCP Option in OFFER/ACK: 252
*** Unhandled DHCP Option in OFFER/ACK: 2
*** Unhandled DHCP Option in OFFER/ACK: 42
*** Unhandled DHCP Option in OFFER/ACK: 4
*** Unhandled DHCP Option in OFFER/ACK: 252
DHCP client bound to address 192.168.1.131 (5304 ms)
Using ethernet@ff290000 device
TFTP from server 0.0.0.0; our IP address is 192.168.1.131; sending through gateway 192.168.1.1
Filename 'boot.scr.uimg'.
Load address: 0x2000000
Loading: T T
TFTP error: 'Access violation' (2)
Not retrying...
=>

Example output from printenv in u-boot. You can also see "startup script" and correctly setup ethernet address from TinkerBoard i2c eeprom. Actual environment is not saved (can be modified to save too SPI dataflash but untested).
Code:
=> printenv
arch=arm
baudrate=115200
board=tinker_rk3288
board_name=tinker_rk3288
boot_a_script=load ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} ${prefix}${script}; source ${scriptaddr}
boot_efi_binary=load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} efi/boot/bootarm.efi; if fdt addr ${fdt_addr_r}; then bootefi ${kernel_addr_r} ${fdt_addr_r};else bootefi ${kernel_addr_r} ${fdtcontroladdr};fi
boot_extlinux=sysboot ${devtype} ${devnum}:${distro_bootpart} any ${scriptaddr} ${prefix}extlinux/extlinux.conf
boot_net_usb_start=usb start
boot_prefixes=/ /boot/
boot_script_dhcp=boot.scr.uimg
boot_scripts=boot.scr.uimg boot.scr
boot_targets=mmc0 mmc1 usb0 pxe dhcp
bootcmd=run distro_bootcmd
bootcmd_dhcp=run boot_net_usb_start; if dhcp ${scriptaddr} ${boot_script_dhcp}; then source ${scriptaddr}; fi;setenv efi_fdtfile ${fdtfile}; if test -z "${fdtfile}" -a -n "${soc}"; then setenv efi_fdtfile ${soc}-${board}${boardver}.dtb; fi; setenv efi_old_vci ${bootp_vci};setenv efi_old_arch ${bootp_arch};setenv bootp_vci PXEClient:Arch:00010:UNDI:003000;setenv bootp_arch 0xa;if dhcp ${kernel_addr_r}; then tftpboot ${fdt_addr_r} dtb/${efi_fdtfile};if fdt addr ${fdt_addr_r}; then bootefi ${kernel_addr_r} ${fdt_addr_r}; else bootefi ${kernel_addr_r} ${fdtcontroladdr};fi;fi;setenv bootp_vci ${efi_old_vci};setenv bootp_arch ${efi_old_arch};setenv efi_fdtfile;setenv efi_old_arch;setenv efi_old_vci;
bootcmd_mmc0=setenv devnum 0; run mmc_boot
bootcmd_mmc1=setenv devnum 1; run mmc_boot
bootcmd_pxe=run boot_net_usb_start; dhcp; if pxe get; then pxe boot; fi
bootcmd_usb0=setenv devnum 0; run usb_boot
bootdelay=2
cpu=armv7
devnum=0
distro_bootcmd=for target in ${boot_targets}; do run bootcmd_${target}; done
efi_dtb_prefixes=/ /dtb/ /dtb/current/
ethact=ethernet@ff290000
ethaddr=2c:4d:54:##:##:##
fdt_addr_r=0x01f00000
fdt_high=0x0fffffff
fdtcontroladdr=7df5bf70
initrd_high=0x0fffffff
kernel_addr_r=0x02000000
load_efi_dtb=load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} ${prefix}${efi_fdtfile}
mmc_boot=if mmc dev ${devnum}; then setenv devtype mmc; run scan_dev_for_boot_part; fi
partitions=uuid_disk=${uuid_gpt_disk};name=loader1,start=32K,size=4000K,uuid=${uuid_gpt_loader1};name=reserved1,size=64K,uuid=${uuid_gpt_reserved1};name=reserved2,size=4M,uuid=${uuid_gpt_reserved2};name=loader2,size=4MB,uuid=${uuid_gpt_loader2};name=atf,size=4M,uuid=${uuid_gpt_atf};name=boot,size=112M,bootable,uuid=${uuid_gpt_boot};name=rootfs,size=-,uuid=${uuid_gpt_rootfs};
pxefile_addr_r=0x00100000
ramdisk_addr_r=0x04000000
scan_dev_for_boot=echo Scanning ${devtype} ${devnum}:${distro_bootpart}...; for prefix in ${boot_prefixes}; do run scan_dev_for_extlinux; run scan_dev_for_scripts; done;run scan_dev_for_efi;
scan_dev_for_boot_part=part list ${devtype} ${devnum} -bootable devplist; env exists devplist || setenv devplist 1; for distro_bootpart in ${devplist}; do if fstype ${devtype} ${devnum}:${distro_bootpart} bootfstype; then run scan_dev_for_boot; fi; done
scan_dev_for_efi=setenv efi_fdtfile ${fdtfile}; if test -z "${fdtfile}" -a -n "${soc}"; then setenv efi_fdtfile ${soc}-${board}${boardver}.dtb; fi; for prefix in ${efi_dtb_prefixes}; do if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${efi_fdtfile}; then run load_efi_dtb; fi;done;if test -e ${devtype} ${devnum}:${distro_bootpart} efi/boot/bootarm.efi; then echo Found EFI removable media binary efi/boot/bootarm.efi; run boot_efi_binary; echo EFI LOAD FAILED: continuing...; fi; setenv efi_fdtfile
scan_dev_for_extlinux=if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}extlinux/extlinux.conf; then echo Found ${prefix}extlinux/extlinux.conf; run boot_extlinux; echo SCRIPT FAILED: continuing...; fi
scan_dev_for_scripts=for script in ${boot_scripts}; do if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${script}; then echo Found U-Boot script ${prefix}${script}; run boot_a_script; echo SCRIPT FAILED: continuing...; fi; done
scriptaddr=0x00000000
soc=rockchip
usb_boot=usb start; if usb dev ${devnum}; then setenv devtype usb; run scan_dev_for_boot_part; fi
vendor=rockchip

Environment size: 4177/8188 bytes
=>

Step E: Modify root mount options on USB with TinkerOS (or other distribution).
Modify kernel command line "extlinux/extlinux.conf" TinkerOS (FAT boot partition) to boot from USB (see changed "root=" to usb disk also "console=" to serial line UART2).
Code:
label kernel-4.4
   kernel /zImage
   fdt /rk3288-miniarm.dtb
   append  earlyprintk console=ttyS2,115200n8 console=tty1 root=/dev/sda2 rw init=/sbin/init

Modify also "/etc/fstab" on rootfs. Change to /dev/sda1 and /dev/sda2 or hide with comment # (root is already mounted over /dev/root and /boot is than mounted to /madia/...).
Code:
proc            /proc           proc    defaults          0       0
#/dev/mmcblk0p1  /boot           vfat    defaults          0       2
#/dev/mmcblk0p2  /               ext4    defaults,noatime  0       1

Happy hacking !
I think this is exactly what I need after breaking the SD card slot. Million thanks for the tutorial. Will try asap.
Awesome write up mcerveny, I thought of this when I looked at the Rock64 specs, then got a couple wrong P/N's for SPI NOR's ( 1.8V Sad )and hadn't had time to try it out. You may want to take a look at the Armbian build environment for the Tinker Board U-boot, the SPL+dtb is 26.5k, I'm pretty sure the spl has to be under 32k for the RK3288 to boot from any media.

I've added my voice to your entry about SPI DMA, I saw it on Tinker Board Armbian and some other team members saw it on Rock64 and also disabled DMA to correct short-term.
Old thread, but maybe you're still around.

Instead of flashing an onboard chip, are there enough pins exposed to emulate SPI flash via GPIO from another board?