30 Jan 2023

AllWinner Nezha D1

I decided to jump on the Linux-capable RISC-V bandwagon by buying myself the AllWinner Nezha D1. Ever since it arrived, I've spent the last couple evenings working with it to gauge its suitability for inclusion in some projects. Overall my impression has been quite positive!

I won't bore you here with specs you can easily find elsewhere, but the gist is the Nezha-D1 is a 64-bit, hard-core, RISC-V based board in a roughly RaspberryPi form-factor, with a RaspberryPi-style 40-pin expansion header.

Yocto

My first question with a new board is always: "can I build an image for it with Yocto?" If I can't use Yocto with it, then I'm not interested. In fact, I always do a test build in Yocto before even thinking of buying a board. Of course I can't know if the image will work until I actually get the board, but if that basic step doesn't work, then the board is not worth my time or money. (Unless, of course, I'm somehow thinking that I might take a crack at adding Yocto support for the board)

To see if Yocto support is even a possibility, check: https://layers.openembedded.org/ . Verify the branch is "master", click on "Machines" and type "nezha" into the search bar: https://layers.openembedded.org/layerindex/branch/master/machines/?q=nezha&search=1 There is a "nezha-allwinner-d1" machine defined in the meta-riscv layer.

The first thing I do is to download meta-riscv, its dependencies (openembedded-core), as well as bitbake, then perform a nodistro core-image-base build to see if it succeeds. It does, so I proceed to buy the board.

Build

Here are the details of my build.

conf/local.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
MACHINE = "nezha-allwinner-d1"
DISTRO = "nodistro"
DL_DIR = "/opt/Downloads"

DISTRO_FEATURES = ""
INHERT += "buildhistory image-buildinfo buildstats-summary"
BUILDHISTORY_COMMIT = "1"

SSTATE_DIR = "/z/sstate"
PACKAGE_CLASSES ?= "package_ipk"
EXTRA_IMAGE_FEATURES ?= "debug-tweaks"
USER_CLASSES ?= "buildstats"
PATCHRESOLVE = "noop"
BB_DISKMON_DIRS ??= "\
    STOPTASKS,${TMPDIR},1G,100K \
    STOPTASKS,${DL_DIR},1G,100K \
    STOPTASKS,${SSTATE_DIR},1G,100K \
    STOPTASKS,/tmp,100M,100K \
    HALT,${TMPDIR},100M,1K \
    HALT,${DL_DIR},100M,1K \
    HALT,${SSTATE_DIR},100M,1K \
    HALT,/tmp,10M,1K"
PACKAGECONFIG:append:pn-qemu-system-native = " sdl"
PACKAGECONFIG:append:pn-nativesdk-qemu = " sdl"
CONF_VERSION = "2"

conf/bblayers.conf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# LAYER_CONF_VERSION is increased each time build/conf/bblayers.conf
# changes incompatibly
LCONF_VERSION = "7"

BBPATH = "${TOPDIR}"
BBFILES ?= ""

BBLAYERS ?= " \
  /z/build-master/nezha/layers/meta-riscv \
  /z/build-master/nezha/layers/openembedded-core/meta \
  "

build

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ bitbake core-image-base
Loading cache: 100% |                                                                 | ETA:  --:--:--
Loaded 0 entries from dependency cache.
Parsing recipes: 100% |################################################################| Time: 0:00:06
Parsing of 907 .bb files complete (0 cached, 907 parsed). 1710 targets, 224 skipped, 0 masked, 0 errors.
NOTE: Resolving any missing task queue dependencies

Build Configuration:
BB_VERSION           = "2.2.0"
BUILD_SYS            = "x86_64-linux"
NATIVELSBSTRING      = "opensuseleap-15.3"
TARGET_SYS           = "riscv64-oe-linux"
MACHINE              = "nezha-allwinner-d1"
DISTRO               = "nodistro"
DISTRO_VERSION       = "nodistro.0"
TUNE_FEATURES        = "riscv64"
meta-riscv           = "master:d6e3efd54a3c1361fecf2a56c6f4f590fbe676d9"
meta                 = "master:47f6a75960b3af2be7f45fd06e2fb73549b6933b"

Initialising tasks: 100% |#############################################################| Time: 0:00:01
Sstate summary: Wanted 765 Local 3 Mirrors 0 Missed 762 Current 0 (0% match, 0% complete)
NOTE: Executing Tasks
WARNING: opensbi-1.1-r0 do_package_qa: QA Issue: File /share/opensbi/lp64/generic/firmware/fw_jump.elf in package opensbi contains reference to TMPDIR
File /share/opensbi/lp64/generic/firmware/fw_dynamic.elf in package opensbi contains reference to TMPDIR
File /share/opensbi/lp64/generic/firmware/fw_payload.elf in package opensbi contains reference to TMPDIR [buildpaths]
WARNING: opensbi-1.1-r0 do_package_qa: QA Issue: File /share/opensbi/lp64/generic/firmware/.debug/fw_jump.elf in package opensbi-dbg contains reference to TMPDIR
File /share/opensbi/lp64/generic/firmware/.debug/fw_dynamic.elf in package opensbi-dbg contains reference to TMPDIR
File /share/opensbi/lp64/generic/firmware/.debug/fw_payload.elf in package opensbi-dbg contains reference to TMPDIR [buildpaths]
NOTE: Tasks Summary: Attempted 2031 tasks of which 3 didn't need to be rerun and all succeeded.

Summary: There were 2 WARNING messages.


First Boot

After the build completes successfully, flash the image to an SDcard. Most BSP layers create an <image>-<machine>.wic file (sometimes it's just wic by itself, sometimes it's compressed in one way or another to produce *wic.gz, *wic.bz2, and/or *wic.xz). One of these wic files needs to be flashed with bmaptool. You could download, build, and install bmaptool yourself, or you could get Yocto to do it for you:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ bitbake bmap-tools-native -caddto_recipe_sysroot
Loading cache: 100% |##################################################################| Time: 0:00:00
Loaded 1709 entries from dependency cache.
Parsing recipes: 100% |################################################################| Time: 0:00:00
Parsing of 907 .bb files complete (906 cached, 1 parsed). 1710 targets, 224 skipped, 0 masked, 0 errors.
NOTE: Resolving any missing task queue dependencies

Build Configuration:
BB_VERSION           = "2.2.0"
BUILD_SYS            = "x86_64-linux"
NATIVELSBSTRING      = "opensuseleap-15.3"
TARGET_SYS           = "riscv64-oe-linux"
MACHINE              = "nezha-allwinner-d1"
DISTRO               = "nodistro"
DISTRO_VERSION       = "nodistro.0"
TUNE_FEATURES        = "riscv64"
meta-riscv           = "master:d6e3efd54a3c1361fecf2a56c6f4f590fbe676d9"
meta                 = "master:47f6a75960b3af2be7f45fd06e2fb73549b6933b"

Initialising tasks: 100% |#############################################################| Time: 0:00:01
Sstate summary: Wanted 0 Local 0 Mirrors 0 Missed 0 Current 76 (0% match, 100% complete)
NOTE: Executing Tasks
NOTE: Tasks Summary: Attempted 343 tasks of which 342 didn't need to be rerun and all succeeded.

Now to invoke your Yocto-built bmaptool use:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ oe-run-native bmap-tools-native bmaptool copy tmp-glibc/deploy/images/nezha-allwinner-d1/core-image-base-nezha-allwinner-d1.wic.gz /dev/sdl
Running bitbake -e bmap-tools-native
bmaptool: info: discovered bmap file 'tmp-glibc/deploy/images/nezha-allwinner-d1/core-image-base-nezha-allwinner-d1.wic.bmap'
bmaptool: info: block map format version 2.0
bmaptool: info: 3601408 blocks of size 4096 (13.7 GiB), mapped 21501 blocks (84.0 MiB or 0.6%)
bmaptool: info: copying image 'core-image-base-nezha-allwinner-d1.wic.gz' to block device '/dev/sdl' using bmap file 'core-image-base-nezha-allwinner-d1.wic.bmap'
bmaptool: WARNING: failed to disable excessive buffering, expect worse system responsiveness (reason: cannot set max. I/O ratio to 1: [Errno 13] Permission denied: '/sys/dev/block/8:176/bdi/max_ratio')
bmaptool: info: 100% copied
bmaptool: info: synchronizing '/dev/sdl'
bmaptool: info: copying time: 38.4s, copying speed 2.2 MiB/sec

(make sure to replace /dev/sdl with which ever device your SDcard is using on your specific host machine)

Plugging your newly-flashed SDcard into your Nezha board, connecting a serial cable, opening up a console application on your host, and applying power to the board you'll see:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
U-Boot SPL 2022.10 (Nov 01 2022 - 04:27:57 +0000)
sunxi_ram_probe: dram-controller@3102000: probing
DRAM only have internal ZQ!!
ddr_efuse_type: 0x0 
[AUTO DEBUG] two rank and full DQ! 
ddr_efuse_type: 0x0 
[AUTO DEBUG] rank 0 row = 15
[AUTO DEBUG] rank 0 bank = 8 
[AUTO DEBUG] rank 0 page size = 2 KB
[AUTO DEBUG] rank 1 row = 15
[AUTO DEBUG] rank 1 bank = 8 
[AUTO DEBUG] rank 1 page size = 2 KB
rank1 config same as rank0
DRAM BOOT DRIVE INFO: V0.24
DRAM CLK = 792 MHz 
DRAM Type = 3 (2:DDR2,3:DDR3)
DRAMC ZQ value: 0x7b7bfb
DRAM ODT value: 0x42.
ddr_efuse_type: 0x0 
DRAM SIZE =1024 M
DRAM simple test OK. 
mxstatus=0xc0408000 mhcr=0x00000109 mcor=0x00000003 mhint=0x00004000
Trying to boot from MMC1
PLL reg = 0xf8216300, freq = 1200000000
SPL size = 81920, sector = 160 
sunxi_ram_get_info: dram-controller@3102000: getting info

OpenSBI v1.1
   ____                    _____ ____ _____
  / __ \                  / ____|  _ \_   _|  
 | |  | |_ __   ___ _ __ | (___ | |_) || |
 | |  | | '_ \ / _ \ '_ \ \___ \|  _ < | | 
 | |__| | |_) |  __/ | | |____) | |_) || |_
  \____/| .__/ \___|_| |_|_____/|____/_____|
        | | 
        |_|

Platform Name             : Allwinner D1 Nezha
Platform Features         : medeleg
Platform HART Count       : 1
Platform IPI Device       : --- 
Platform Timer Device     : --- @ 0Hz 
Platform Console Device   : uart8250
Platform HSM Device       : sun20i-d1-ppu
Platform Reboot Device    : sunxi-wdt-reset
Platform Shutdown Device  : --- 
Firmware Base             : 0x80000000
Firmware Size             : 276 KB
Runtime SBI Version       : 1.0 

Domain0 Name              : root
Domain0 Boot HART         : 0
Domain0 HARTs             : 0*
Domain0 Region00          : 0x0000000080000000-0x000000008007ffff ()
Domain0 Region01          : 0x0000000000000000-0xffffffffffffffff (R,W,X)
Domain0 Next Address      : 0x0000000042e00000
Domain0 Next Arg1         : 0x0000000042e8b7f8
Domain0 Next Mode         : S-mode
Domain0 SysReset          : yes 

Boot HART ID              : 0
Boot HART Domain          : root
Boot HART Priv Version    : v1.11
Boot HART Base ISA        : rv64imafdcvx
Boot HART ISA Extensions  : time
Boot HART PMP Count       : 8 
Boot HART PMP Granularity : 2048
Boot HART PMP Address Bits: 38
Boot HART MHPM Count      : 0 
Boot HART MIDELEG         : 0x0000000000000222
Boot HART MEDELEG         : 0x000000000000b109
sunxi_set_gate: (CLK#24) unhandled


U-Boot 2022.10 (Nov 01 2022 - 04:27:57 +0000) Allwinner Technology

DRAM:  1 GiB
sunxi_set_gate: (CLK#24) unhandled
Core:  54 devices, 20 uclasses, devicetree: separate
WDT:   Started watchdog@6011000 with servicing every 1000ms (16s timeout)
MMC:   mmc@4020000: 0, mmc@4021000: 1
Loading Environment from FAT... PLL reg = 0xf8216300, freq = 1200000000
Unable to read "uboot.env" from mmc0:1...
In:    serial@2500000
Out:   serial@2500000
Err:   serial@2500000
Net:
Warning: ethernet@4500000 (eth0) using random MAC address - c6:3e:31:4e:f8:ec
eth0: ethernet@4500000
starting USB...
Bus usb@4101000: USB EHCI 1.00
Bus usb@4101400: USB OHCI 1.0
Bus usb@4200000: USB EHCI 1.00
Bus usb@4200400: USB OHCI 1.0
scanning bus usb@4101000 for devices... 1 USB Device(s) found
scanning bus usb@4101400 for devices... 1 USB Device(s) found
scanning bus usb@4200000 for devices... 1 USB Device(s) found
scanning bus usb@4200400 for devices... 1 USB Device(s) found
       scanning usb for storage devices... 0 Storage Device(s) found
Hit any key to stop autoboot:  2 ^H^H^H 1 ^H^H^H 0
PLL reg = 0xf8216300, freq = 1200000000
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
Found U-Boot script /boot.scr.uimg
1249 bytes read in 1 ms (1.2 MiB/s)
## Executing script at 41900000
ethernet@4500000 Waiting for PHY auto negotiation to complete......... TIMEOUT !
ethernet@4500000 Waiting for PHY auto negotiation to complete......... TIMEOUT !
ethernet@4500000 Waiting for PHY auto negotiation to complete......... TIMEOUT !
322 bytes read in 1 ms (314.5 KiB/s)
5431248 bytes read in 898 ms (5.8 MiB/s)
## Loading kernel from FIT Image at 41c00000 ...
   Using 'conf-1' configuration
   Trying 'kernel-1' kernel subimage
     Description:  Linux kernel
     Type:         Kernel Image
     Compression:  gzip compressed
     Data Start:   0x41c0010c
     Data Size:    5428090 Bytes = 5.2 MiB
     Architecture: RISC-V
     OS:           Linux
     Load Address: 0x40200000
     Entry Point:  0x40200000
     Hash algo:    sha256
     Hash value:   c0897b7ecc8c79a67ffd0b71af8a90e3bdeb028c9953a13a8a4b2c0dab4e3797
   Verifying Hash Integrity ... sha256+ OK
## Flattened Device Tree blob at 7fd3e180
   Booting using the fdt blob at 0x7fd3e180
Working FDT set to 7fd3e180
   Uncompressing Kernel Image
   Loading Device Tree to 0000000042df2000, end 0000000042dffc27 ... OK
Working FDT set to 42df2000

Starting kernel ...

[    0.000000] Linux version 6.1.0-rc3-nezha (oe-user@oe-host) (riscv64-oe-linux-gcc (GCC) 12.2.0, GNU ld (GNU Binutils) 2.39.0.20220819) #1 PREEMPT Thu Nov  3 04:49:55 UTC 2022
[    0.000000] OF: fdt: Ignoring memory range 0x40000000 - 0x40200000
[    0.000000] Machine model: Allwinner D1 Nezha
[    0.000000] efi: UEFI not found.
[    0.000000] Zone ranges:
[    0.000000]   DMA32    [mem 0x0000000040200000-0x000000007fffffff]
[    0.000000]   Normal   empty
[    0.000000] Movable zone start for each node
[    0.000000] Early memory node ranges
[    0.000000]   node   0: [mem 0x0000000040200000-0x000000007fffffff]
[    0.000000] Initmem setup node 0 [mem 0x0000000040200000-0x000000007fffffff]
[    0.000000] SBI specification v1.0 detected
[    0.000000] SBI implementation ID=0x1 Version=0x10001
[    0.000000] SBI TIME extension detected
[    0.000000] SBI IPI extension detected
[    0.000000] SBI RFENCE extension detected
[    0.000000] SBI SRST extension detected
[    0.000000] riscv: base ISA extensions acdfim
[    0.000000] riscv: ELF capabilities acdfim
[    0.000000] pcpu-alloc: s0 r0 d32768 u32768 alloc=1*32768
[    0.000000] pcpu-alloc: [0] 0
[    0.000000] Built 1 zonelists, mobility grouping on.  Total pages: 257544
[    0.000000] Kernel command line: earlycon=sbi clk_ignore_unused initcall_debug=0 console=ttyS0,115200 loglevel=8 root=/dev/mmcblk0p2 rootwait  init=/sbin/init
[    0.000000] Dentry cache hash table entries: 131072 (order: 8, 1048576 bytes, linear)
[    0.000000] Inode-cache hash table entries: 65536 (order: 7, 524288 bytes, linear)
[    0.000000] mem auto-init: stack:all(zero), heap alloc:off, heap free:off
[    0.000000] Memory: 1009936K/1046528K available (6082K kernel code, 4935K rwdata, 4096K rodata, 2127K init, 309K bss, 36592K reserved, 0K cma-reserved)
[    0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
[    0.000000] rcu: Preemptible hierarchical RCU implementation.
[    0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 25 jiffies.
[    0.000000] NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
[    0.000000] riscv-intc: 64 local interrupts mapped
[    0.000000] plic: interrupt-controller@10000000: mapped 176 interrupts with 1 handlers for 2 contexts.
[    0.000000] rcu: srcu_init: Setting srcu_struct sizes based on contention.
[    0.000000] riscv-timer: riscv_timer_init_dt: Registering clocksource cpuid [0] hartid [0]
[    0.000000] clocksource: riscv_clocksource: mask: 0xffffffffffffffff max_cycles: 0x588fe9dc0, max_idle_ns: 440795202592 ns
[    0.000001] sched_clock: 64 bits at 24MHz, resolution 41ns, wraps every 4398046511097ns
[    0.000518] clocksource: timer: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 79635851949 ns
[    0.001450] Calibrating delay loop (skipped), value calculated using timer frequency.. 48.00 BogoMIPS (lpj=96000)
[    0.001488] pid_max: default: 32768 minimum: 301
[    0.001857] LSM: Security Framework initializing
[    0.002127] Mount-cache hash table entries: 2048 (order: 2, 16384 bytes, linear)
[    0.002175] Mountpoint-cache hash table entries: 2048 (order: 2, 16384 bytes, linear)
[    0.006058] riscv: ELF compat mode unsupported
[    0.006135] ASID allocator using 16 bits (65536 entries)
[    0.006425] rcu: Hierarchical SRCU implementation.
[    0.006439] rcu:     Max phase no-delay instances is 1000.
[    0.006686] EFI services will not be available.
[    0.007641] devtmpfs: initialized
[    0.031148] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns
[    0.031195] futex hash table entries: 256 (order: 0, 6144 bytes, linear)
[    0.031431] pinctrl core: initialized pinctrl subsystem
[    0.034816] NET: Registered PF_NETLINK/PF_ROUTE protocol family
[    0.035494] DMA: preallocated 128 KiB GFP_KERNEL pool for atomic allocations
[    0.035588] DMA: preallocated 128 KiB GFP_KERNEL|GFP_DMA32 pool for atomic allocations
[    0.036553] thermal_sys: Registered thermal governor 'bang_bang'
[    0.036664] thermal_sys: Registered thermal governor 'step_wise'
[    0.036681] thermal_sys: Registered thermal governor 'user_space'
[    0.073318] platform 5460000.tcon-top: Fixing up cyclic dependency with 5200000.mixer
[    0.073562] platform 5460000.tcon-top: Fixing up cyclic dependency with 5100000.mixer
[    0.074743] platform 5461000.lcd-controller: Fixing up cyclic dependency with 5460000.tcon-top
[    0.076089] platform 5470000.lcd-controller: Fixing up cyclic dependency with 5460000.tcon-top
[    0.077337] platform 5500000.hdmi: Fixing up cyclic dependency with 5460000.tcon-top
[    0.081586] platform 7090000.rtc: Fixing up cyclic dependency with 7010000.clock-controller
[    0.085046] platform connector: Fixing up cyclic dependency with 5500000.hdmi
[    0.180753] raid6: int64x8  gen()   156 MB/s
[    0.248633] raid6: int64x4  gen()   198 MB/s
[    0.316715] raid6: int64x2  gen()   180 MB/s
[    0.384610] raid6: int64x1  gen()   161 MB/s
[    0.384629] raid6: using algorithm int64x4 gen() 198 MB/s
[    0.452592] raid6: .... xor() 123 MB/s, rmw enabled
[    0.452607] raid6: using intx1 recovery algorithm
[    0.455199] iommu: Default domain type: Translated
[    0.455218] iommu: DMA domain TLB invalidation policy: strict mode
[    0.456083] SCSI subsystem initialized
[    0.456737] usbcore: registered new interface driver usbfs
[    0.456861] usbcore: registered new interface driver hub
[    0.456981] usbcore: registered new device driver usb
[    0.458805] Advanced Linux Sound Architecture Driver Initialized.
[    0.460733] clocksource: Switched to clocksource timer
[    0.465108] NET: Registered PF_INET protocol family
[    0.465711] IP idents hash table entries: 16384 (order: 5, 131072 bytes, linear)
[    0.473890] tcp_listen_portaddr_hash hash table entries: 512 (order: 0, 4096 bytes, linear)
[    0.473950] Table-perturb hash table entries: 65536 (order: 6, 262144 bytes, linear)
[    0.474007] TCP established hash table entries: 8192 (order: 4, 65536 bytes, linear)
[    0.474262] TCP bind hash table entries: 8192 (order: 5, 131072 bytes, linear)
[    0.474969] TCP: Hash tables configured (established 8192 bind 8192)
[    0.475212] UDP hash table entries: 512 (order: 2, 16384 bytes, linear)
[    0.475322] UDP-Lite hash table entries: 512 (order: 2, 16384 bytes, linear)
[    0.475767] NET: Registered PF_UNIX/PF_LOCAL protocol family
[    0.477573] workingset: timestamp_bits=46 max_order=18 bucket_order=0
[    0.645753] NET: Registered PF_ALG protocol family
[    0.645796] xor: measuring software checksum speed
[    0.659632]    8regs           :   712 MB/sec
[    0.673527]    8regs_prefetch  :   710 MB/sec
[    0.687386]    32regs          :   711 MB/sec
[    0.701289]    32regs_prefetch :   709 MB/sec
[    0.701306] xor: using function: 8regs (712 MB/sec)
[    0.701326] async_tx: api initialized (async)
[    0.743397] Serial: 8250/16550 driver, 6 ports, IRQ sharing disabled
[    0.761274] sun8i-mixer 5100000.mixer: Adding to iommu group 0
[    0.762086] sun8i-mixer 5200000.mixer: Adding to iommu group 0
[    0.772085] PPP generic driver version 2.4.2
[    0.772522] PPP BSD Compression module registered
[    0.772541] PPP Deflate Compression module registered
[    0.772553] NET: Registered PF_PPPOX protocol family
[    0.772610] SLIP: version 0.8.4-NET3.019-NEWTTY (dynamic channels, max=256) (6 bit encapsulation enabled).
[    0.772629] CSLIP: code copyright 1989 Regents of the University of California.
[    0.772638] SLIP linefill/keepalive option.
[    0.774534] usbcore: registered new interface driver uas
[    0.774650] usbcore: registered new interface driver usb-storage
[    0.774924] usbcore: registered new interface driver ch341
[    0.775004] usbserial: USB Serial support registered for ch341-uart
[    0.776114] UDC core: g_ether: couldn't find an available UDC
[    0.776714] i2c_dev: i2c /dev entries driver
[    0.779899] softdog: initialized. soft_noboot=0 soft_margin=60 sec soft_panic=0 (nowayout=0)
[    0.779926] softdog:              soft_reboot_cmd=<not set> soft_active_on_boot=0
[    0.781409] device-mapper: ioctl: 4.47.0-ioctl (2022-07-28) initialised: dm-devel@redhat.com
[    0.784439] ledtrig-cpu: registered to indicate activity on CPUs
[    0.788196] usbcore: registered new interface driver snd-usb-audio
[    0.791267] pktgen: Packet Generator for packet performance testing. Version: 2.75
[    0.792034] ipip: IPv4 and MPLS over IPv4 tunneling driver
[    0.793522] gre: GRE over IPv4 demultiplexor driver
[    0.793541] ip_gre: GRE over IPv4 tunneling driver
[    0.799291] NET: Registered PF_INET6 protocol family
[    0.804235] Segment Routing with IPv6
[    0.804438] In-situ OAM (IOAM) with IPv6
[    0.804669] NET: Registered PF_PACKET protocol family
[    0.804710] NET: Registered PF_KEY protocol family
[    0.805062] 8021q: 802.1Q VLAN Support v1.8
[    0.805652] sctp: Hash tables configured (bind 512/512)
[    0.806479] tipc: Activated (version 2.0.0)
[    0.807089] NET: Registered PF_TIPC protocol family
[    0.807429] tipc: Started in single node mode
[    0.808374] Key type .fscrypt registered
[    0.808395] Key type fscrypt-provisioning registered
[    0.810354] Key type encrypted registered
[    0.863941] sun4i-drm display-engine: Adding to iommu group 0
[    0.878031] sun4i-drm display-engine: bound 5100000.mixer (ops 0xffffffff80b01458)
[    0.883298] sun4i-drm display-engine: bound 5200000.mixer (ops 0xffffffff80b01458)
[    0.883345] sun4i-drm display-engine: bound 5460000.tcon-top (ops 0xffffffff80b057d8)
[    0.884578] sun4i-drm display-engine: No panel or bridge found... RGB output disabled
[    0.884614] sun4i-drm display-engine: bound 5461000.lcd-controller (ops 0xffffffff80afe3c0)
[    0.885415] sun4i-drm display-engine: bound 5470000.lcd-controller (ops 0xffffffff80afe3c0)
[    0.886388] sun8i-dw-hdmi 5500000.hdmi: Detected HDMI TX controller v2.12a with HDCP (sun8i_dw_hdmi_phy)
[    0.887499] sun8i-dw-hdmi 5500000.hdmi: registered DesignWare HDMI I2C bus driver
[    0.888126] sun4i-drm display-engine: bound 5500000.hdmi (ops 0xffffffff80b00528)
[    0.889925] [drm] Initialized sun4i-drm 1.0.0 20150629 for display-engine on minor 0
[    0.900271] input: 2009800.keys as /devices/platform/soc/2009800.keys/input/input0
[    0.918653] sunxi-wdt 6011000.watchdog: Watchdog enabled (timeout=16 sec, nowayout=0)
[    0.964473] sun20i-d1-pinctrl 2000000.pinctrl: initialized sunXi PIO driver
[    0.968642] printk: console [ttyS0] disabled
[    0.989174] 2500000.serial: ttyS0 at MMIO 0x2500000 (irq = 209, base_baud = 1500000) is a 16550A
[    1.931142] printk: console [ttyS0] enabled
[    1.958274] 2500400.serial: ttyS1 at MMIO 0x2500400 (irq = 210, base_baud = 1500000) is a 16550A
[    1.971932] spi-nand spi0.0: Macronix SPI NAND was found.
[    1.977447] spi-nand spi0.0: 256 MiB, block size: 128 KiB, page size: 2048, OOB size: 64
[    1.989585] 4 fixed-partitions partitions found on MTD device spi0.0
[    1.996036] Creating 4 MTD partitions on "spi0.0":
[    2.000888] 0x000000000000-0x000000100000 : "boot0"
[    2.012150] 0x000000100000-0x000000400000 : "uboot"
[    2.030040] 0x000000400000-0x000000500000 : "secure_storage"
[    2.042181] 0x000000500000-0x000010000000 : "sys"
[    2.756834] random: crng init done
[    2.848410] dwmac-sun8i 4500000.ethernet: IRQ eth_wake_irq not found
[    2.854891] dwmac-sun8i 4500000.ethernet: IRQ eth_lpi not found
[    2.861560] dwmac-sun8i 4500000.ethernet: PTP uses main clock
[    2.867444] dwmac-sun8i 4500000.ethernet: Current syscon value is not the default 50006 (expect 0)
[    2.876892] dwmac-sun8i 4500000.ethernet: No HW DMA feature register supported
[    2.884221] dwmac-sun8i 4500000.ethernet: RX Checksum Offload Engine supported
[    2.891498] dwmac-sun8i 4500000.ethernet: COE Type 2
[    2.896565] dwmac-sun8i 4500000.ethernet: TX Checksum insertion supported
[    2.903466] dwmac-sun8i 4500000.ethernet: Normal descriptors
[    2.909191] dwmac-sun8i 4500000.ethernet: Chain mode enabled
[    2.931798] pcf857x 1-0038: probed
[    2.945141] sunxi-mmc 4020000.mmc: Got CD GPIO
[    2.952323] sunxi-mmc 4021000.mmc: allocated mmc-pwrseq
[    2.962105] phy phy-4100400.phy.1: Changing dr_mode to 1
[    2.970229] ehci-platform 4200000.usb: EHCI Host Controller
[    2.977197] usb_phy_generic usb_phy_generic.2.auto: dummy supplies not allowed for exclusive requests
[    2.988506] sunxi-mmc 4020000.mmc: initialized, max. request size: 2047 KB, uses new timings mode
[    2.997607] sunxi-mmc 4021000.mmc: initialized, max. request size: 2047 KB, uses new timings mode
[    3.008127] ohci-platform 4200400.usb: Generic Platform OHCI controller
[    3.015225] ehci-platform 4200000.usb: new USB bus registered, assigned bus number 1
[    3.023483] ohci-platform 4200400.usb: new USB bus registered, assigned bus number 2
[    3.032571] ehci-platform 4200000.usb: irq 218, io mem 0x04200000
[    3.039264] ohci-platform 4200400.usb: irq 220, io mem 0x04200400
[    3.060790] ehci-platform 4200000.usb: USB 2.0 started, EHCI 1.00
[    3.067393] usb usb1: New USB device found, idVendor=1d6b, idProduct=0002, bcdDevice= 6.01
[    3.075819] usb usb1: New USB device strings: Mfr=3, Product=2, SerialNumber=1
[    3.083176] usb usb1: Product: EHCI Host Controller
[    3.088179] usb usb1: Manufacturer: Linux 6.1.0-rc3-nezha ehci_hcd
[    3.096054] mmc1: new high speed SDIO card at address 0001
[    3.101710] usb usb1: SerialNumber: 4200000.usb
[    3.112043] hub 1-0:1.0: USB hub found
[    3.116060] hub 1-0:1.0: 1 port detected
[    3.121446] usb usb2: New USB device found, idVendor=1d6b, idProduct=0001, bcdDevice= 6.01
[    3.130095] mmc0: new high speed SDXC card at address 1234
[    3.137528] mmcblk0: mmc0:1234 SA64G 58.0 GiB
[    3.142114] usb usb2: New USB device strings: Mfr=3, Product=2, SerialNumber=1
[    3.151442] usb usb2: Product: Generic Platform OHCI controller
[    3.157643]  mmcblk0: p1 p2
[    3.160888] usb usb2: Manufacturer: Linux 6.1.0-rc3-nezha ohci_hcd
[    3.167947] usb usb2: SerialNumber: 4200400.usb
[    3.173925] hub 2-0:1.0: USB hub found
[    3.177879] hub 2-0:1.0: 1 port detected
[    4.033013] ohci-platform 4101400.usb: Generic Platform OHCI controller
[    4.039773] ehci-platform 4101000.usb: EHCI Host Controller
[    4.045549] musb-hdrc musb-hdrc.3.auto: MUSB HDRC host driver
[    4.051523] ehci-platform 4101000.usb: new USB bus registered, assigned bus number 3
[    4.059408] musb-hdrc musb-hdrc.3.auto: new USB bus registered, assigned bus number 4
[    4.067363] ohci-platform 4101400.usb: new USB bus registered, assigned bus number 5
[    4.075511] ehci-platform 4101000.usb: irq 217, io mem 0x04101000
[    4.082107] usb usb4: New USB device found, idVendor=1d6b, idProduct=0002, bcdDevice= 6.01
[    4.090707] ohci-platform 4101400.usb: irq 219, io mem 0x04101400
[    4.096982] usb usb4: New USB device strings: Mfr=3, Product=2, SerialNumber=1
[    4.104278] usb usb4: Product: MUSB HDRC host driver
[    4.109297] usb usb4: Manufacturer: Linux 6.1.0-rc3-nezha musb-hcd
[    4.115550] ehci-platform 4101000.usb: USB 2.0 started, EHCI 1.00
[    4.121720] usb usb4: SerialNumber: musb-hdrc.3.auto
[    4.128124] hub 4-0:1.0: USB hub found
[    4.132081] hub 4-0:1.0: 1 port detected
[    4.137396] usb usb3: New USB device found, idVendor=1d6b, idProduct=0002, bcdDevice= 6.01
[    4.146375] using random self ethernet address
[    4.151051] using random host ethernet address
[    4.155661] usb usb3: New USB device strings: Mfr=3, Product=2, SerialNumber=1
[    4.168635] usb0: HOST MAC 0a:2e:24:ef:46:b5
[    4.173025] usb usb3: Product: EHCI Host Controller
[    4.177963] usb0: MAC 06:24:1a:84:1b:76
[    4.181861] usb usb3: Manufacturer: Linux 6.1.0-rc3-nezha ehci_hcd
[    4.188147] g_ether gadget.0: Ethernet Gadget, version: Memorial Day 2008
[    4.195031] usb usb3: SerialNumber: 4101000.usb
[    4.199631] g_ether gadget.0: g_ether ready
[    4.205342] hub 3-0:1.0: USB hub found
[    4.211519] clk: Not disabling unused clocks
[    4.216004] hub 3-0:1.0: 1 port detected
[    4.220024] ALSA device list:
[    4.223052]   #0: sun20i-codec
[    4.227536] usb usb5: New USB device found, idVendor=1d6b, idProduct=0001, bcdDevice= 6.01
[    4.236543] usb usb5: New USB device strings: Mfr=3, Product=2, SerialNumber=1
[    4.243902] usb usb5: Product: Generic Platform OHCI controller
[    4.250050] usb usb5: Manufacturer: Linux 6.1.0-rc3-nezha ohci_hcd
[    4.256313] usb usb5: SerialNumber: 4101400.usb
[    4.262237] hub 5-0:1.0: USB hub found
[    4.266193] hub 5-0:1.0: 1 port detected
[    4.271278] md: Waiting for all devices to be available before autodetect
[    4.278298] md: If you don't use raid, use raid=noautodetect
[    4.284029] md: Autodetecting RAID arrays.
[    4.288170] md: autorun ...
[    4.291006] md: ... autorun DONE.
[    4.322157] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Quota mode: disabled.
[    4.331208] VFS: Mounted root (ext4 filesystem) readonly on device 179:2.
[    4.339657] devtmpfs: mounted
[    4.345575] Freeing unused kernel image (initmem) memory: 2124K
[    4.351621] Run /sbin/init as init process
[    4.355805]   with arguments:
[    4.358815]     /sbin/init
[    4.361565]   with environment:
[    4.364716]     HOME=/
[    4.367160]     TERM=linux
INIT: version 3.04 booting
Framebuffer /dev/fb0 not detected
Boot splashscreen disabled
Starting udev
[    5.300386] udevd[137]: starting version 3.2.11
[    5.380494] udevd[138]: starting eudev-3.2.11
[    6.117599] mtdblock: MTD device 'boot0' is NAND, please consider using UBI block devices instead.
[    6.173478] mtdblock: MTD device 'uboot' is NAND, please consider using UBI block devices instead.
[    6.185120] mtdblock: MTD device 'secure_storage' is NAND, please consider using UBI block devices instead.
[    6.279098] mtdblock: MTD device 'spi0.0' is NAND, please consider using UBI block devices instead.
[    6.297818] mtdblock: MTD device 'sys' is NAND, please consider using UBI block devices instead.
[    7.001016] EXT4-fs (mmcblk0p2): re-mounted. Quota mode: disabled.
hwclock: can't open '/dev/misc/rtc': No such file or directory
Fri Mar  9 12:34:56 UTC 2018
hwclock: can't open '/dev/misc/rtc': No such file or directory
INIT: Entering runlevel: 5
Configuring network interfaces... ifup: unknown address type "inet"
hwclock: can't open '/dev/misc/rtc': No such file or directory
Starting syslogd/klogd: done

OpenEmbedded nodistro.0 nezha-allwinner-d1 /dev/ttyS0

nezha-allwinner-d1 login:

NOTE:

  • the build dates of U-Boot and the kernel are wrong because Yocto enables reproducible builds by default, so although it claims to have been build back in Nov 2022, this image was actually built late Jan 2023

As this image boots, it runs U-Boot (starting at line 75 of the output above). The meta-riscv layer added a script to U-Boot (/boot.scr.uimg) that it finds and runs as part of its boot process (line 105). The script looks like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# This is the default TFTP and MMC u-boot boot script
# The order is as follows:
#  1. TFTP load a uEnv.txt
#  2. TFTP boot a fitImage
#  3. MMC load a uEnv.txt
#  4. MMC load a fitImage
#  5. TFTP load a uImage
#  6. MMC load a fitImage

# Setup the DHCP for a TFTP boot
setenv serverip @SERVERIP@
dhcp

# See if we have a TFTP uEnv.txt file
if tftpboot ${scriptaddr} uEnv.txt; then
    env import -t ${scriptaddr} ${filesize}
    run bootcmd
fi;

# Try to boot a fitImage from the TFTP server
if tftpboot ${ramdisk_addr_r} fitImage; then
  bootm ${ramdisk_addr_r}
fi;

# See if we have a MMC uEnv.txt file
if fatload ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} /uEnv.txt; then
    env import -t ${scriptaddr} ${filesize}
    run bootcmd
fi;

# Try to boot a fitImage from the MMC 
if load ${devtype} ${devnum}:${distro_bootpart} ${ramdisk_addr_r} fitImage; then
  bootm ${ramdisk_addr_r}
fi;

# Fallback to a TFTP uImage
if tftpboot ${kernel_addr_r} uImage; then
  bootm ${kernel_addr_r} - ${fdt_addr_r}
fi;

# Finally fallback to a MMC uImage
if load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} uImage; then
  bootm ${kernel_addr_r} - ${fdt_addr_r}
fi;

This script initializes networking and tries to download an environment and fitImage from a remote server. If those fail, then it tries to do the same from the SDcard. If you don't have a server and haven't provided its IP address to this script then these steps will fail. However, as U-Boot tries to download these items, those attempts have to timeout before it will mark those steps as having failed and proceed to the next steps. In the case where you don't have a server setup, these steps take quite a while to timeout. The boot log that I provided above shows the case where not only have I not setup a server but I also don't have an ethernet cable plugged into the Nezha board. If you do happen to have a cable plugged in (but still don't have a server setup), the boot looks a little different during the U-Boot phase, and takes even longer to timeout and move on to looking for the artifacts on the SDcard.

Here's what the U-Boot portion of the bootup looks like if you don't have a server setup, but you do have an ethernet cable plugged into the Nezha board, and you do have a DHCP server on this network:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
U-Boot 2022.10 (Nov 01 2022 - 04:27:57 +0000) Allwinner Technology

DRAM:  1 GiB 
sunxi_set_gate: (CLK#24) unhandled
Core:  54 devices, 20 uclasses, devicetree: separate
WDT:   Started watchdog@6011000 with servicing every 1000ms (16s timeout)
MMC:   mmc@4020000: 0, mmc@4021000: 1
Loading Environment from FAT... PLL reg = 0xf8216300, freq = 1200000000
Unable to read "uboot.env" from mmc0:1... 
In:    serial@2500000
Out:   serial@2500000
Err:   serial@2500000
Net:   
Warning: ethernet@4500000 (eth0) using random MAC address - 1e:06:7c:ef:f4:0c
eth0: ethernet@4500000
starting USB...
Bus usb@4101000: USB EHCI 1.00
Bus usb@4101400: USB OHCI 1.0 
Bus usb@4200000: USB EHCI 1.00
Bus usb@4200400: USB OHCI 1.0 
scanning bus usb@4101000 for devices... 1 USB Device(s) found
scanning bus usb@4101400 for devices... 1 USB Device(s) found
scanning bus usb@4200000 for devices... 1 USB Device(s) found
scanning bus usb@4200400 for devices... 1 USB Device(s) found
       scanning usb for storage devices... 0 Storage Device(s) found
Hit any key to stop autoboot:  2 ^H^H^H 1 ^H^H^H 0 
PLL reg = 0xf8216300, freq = 1200000000
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
Found U-Boot script /boot.scr.uimg
1249 bytes read in 1 ms (1.2 MiB/s)
## Executing script at 41900000
BOOTP broadcast 1
BOOTP broadcast 2
DHCP client bound to address 10.0.0.51 (987 ms) 
*** Warning: no boot file name; using '0A000033.img'
Using ethernet@4500000 device
TFTP from server 127.0.0.1; our IP address is 10.0.0.51; sending through gateway 10.0.0.1
Filename '0A000033.img'.
Load address: 0x42000000
Loading: *^HT T T T T T T T T T 
Retry count exceeded; starting again
Using ethernet@4500000 device
TFTP from server 127.0.0.1; our IP address is 10.0.0.51; sending through gateway 10.0.0.1
Filename 'uEnv.txt'.
Load address: 0x41900000
Loading: *^HT T T T T T T T T T 
Retry count exceeded; starting again
Using ethernet@4500000 device
TFTP from server 127.0.0.1; our IP address is 10.0.0.51; sending through gateway 10.0.0.1
Filename 'fitImage'.
Load address: 0x41c00000
Loading: *^HT T T T T T T T T T 
Retry count exceeded; starting again
322 bytes read in 1 ms (314.5 KiB/s)
5431248 bytes read in 898 ms (5.8 MiB/s)
## Loading kernel from FIT Image at 41c00000 ... 
   Using 'conf-1' configuration
   Trying 'kernel-1' kernel subimage
     Description:  Linux kernel
     Type:         Kernel Image
     Compression:  gzip compressed
     Data Start:   0x41c0010c
     Data Size:    5428090 Bytes = 5.2 MiB 
     Architecture: RISC-V
     OS:           Linux
     Load Address: 0x40200000
     Entry Point:  0x40200000
     Hash algo:    sha256
     Hash value:   c0897b7ecc8c79a67ffd0b71af8a90e3bdeb028c9953a13a8a4b2c0dab4e3797
   Verifying Hash Integrity ... sha256+ OK
## Flattened Device Tree blob at 7fd3e180
   Booting using the fdt blob at 0x7fd3e180
Working FDT set to 7fd3e180
   Uncompressing Kernel Image
   Loading Device Tree to 0000000042df2000, end 0000000042dffc27 ... OK
Working FDT set to 42df2000

Starting kernel ...

These timeouts take a very long time to expire (longer than the ones with the cable unplugged). While this is a way to do development, you might want to turn this off if you don't plan on using a server this way, or for production. One way to do this would be to create a bbappend in one of your own layers:

1
2
3
4
5
└── recipes-bsp
    └── u-boot
        ├── files
        │   └── tftp-mmc-boot.txt
        └── u-boot-nezha%.bbappend

set the bbappend to contain:

1
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"

and modify the script so that it only contains:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# This is the default TFTP and MMC u-boot boot script
# The order is as follows:
#  3. MMC load a uEnv.txt
#  4. MMC load a fitImage
#  6. MMC load a fitImage

# See if we have a MMC uEnv.txt file
if fatload ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} /uEnv.txt; then
    env import -t ${scriptaddr} ${filesize}
    run bootcmd
fi;

# Try to boot a fitImage from the MMC
if load ${devtype} ${devnum}:${distro_bootpart} ${ramdisk_addr_r} fitImage; then
  bootm ${ramdisk_addr_r}
fi;

# Finally fallback to a MMC uImage
if load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} uImage; then
  bootm ${kernel_addr_r} - ${fdt_addr_r}
fi;

Build, flash, apply power. Now when the board boots, U-Boot won't try to get artifacts over the network and your boot will proceed much quicker.

GPIO

The Nezha-D1 comes in a (roughly) Raspberry Pi form with a (roughly) Raspberry Pi 40-pin header. Using an SBC in a project often involves connecting devices to its header pins and then writing or adding software to manipulate the state of those pins. Learning how to "find" those pins programmatically is an important step towards working with any board. Before writing code to interact with the pins, we can experiment with them on the cmdline.

Starting with release 4.18, the Linux kernel revamped its GPIO subsystem. Therefore the interfaces and procedures to work with GPIOs changed. The current way (as of this writing) to interact with GPIOs from user-space involves working with libgpiod and its tools. If you search for instructions to work with GPIOs on the internet, you might unknowingly come across old information. Under the new scheme individual GPIOs are called "lines". Each line is connected to and controlled by a "gpio chip".

To start with, we need to find a diagram or schematic describing each of the pins on the 40-pin header. Luckily the schematic for the Nezha board is available and can be found here. Looking at the schematic, on page 7/9 we find the details of the 40-pin header (middle, lower, titled: IO EXPAND):

This information pairs the pin number with the SoC's pin name. For example: pin 3, labeled "GPIO1/TWI2-SDA", is connected to SoC pin PB1-S. Looking at the SoC names we find: PBx-S, PCx-S, PDx-S, and PPx. From experience, some SoCs do I/O via ports and have a couple ports to which I/O is connected. So I'm going to guess that PB, for example, stands for "Port B" and that this board exposes portions of the SoC's ports labeled B to D ... and P? At this point I'm fairly sure the P stands for "port", the next letter specifies which port (numbered alphabetically), and the next value is the specific pin of the given port. For example: PD15-S is the 16th pin (counting from 0) of Port D.

I'm not sure what the "-S" means. Looking back at the schematic:


it explains that the "-S" values are simply the raw pins after passing through a resistor. So for the rest of this I'll just drop the "-S" and treat the two synonymously.

On the 40-pin header we don't see any pins from port A, but we see:

  • ports B
    • PB0 (pin 5)
    • PB1 (pin 3)
    • PB3 (pin 38)
    • PB4 (pin 40)
    • PB5 (pin 12)
    • PB6 (pin 35)
    • PB8 (pin 8)
    • PB9 (pin 10)
    • PB12 (pin 15)
  • C
    • PC1 (pin 31)
  • D
    • PD10 (pin 24)
    • PD11 (pin 23)
    • PD12 (pin 19)
    • PD13 (pin 21)
    • PD14 (pin 27)
    • PD15 (pin 29)
    • PD22 (pin 7)
  • P
    • PP0 (pin 13)
    • PP1 (pin 16)
    • PP2 (pin 18)
    • PP3 (pin 38)
    • PP4 (pin 40)
    • PP5 (pin 28)
    • PP6 (pin 37)
    • PP7 (pin 11)
  • 3V3
    • pin 1
    • pin 17
  • 5V
    • pin 2
    • pin 4
  • GND
    • pin 9
    • pin 25
    • pin 39
    • pin 6
    • pin 14
    • pin 20
    • pin 30
    • pin 34

Strangely, pins 32, 33, and 36 aren't connected to anything.

By this point I've already run some of the libgpiod commands. Specifically I've run:

1
2
3
root@nezha-allwinner-d1:~# gpiodetect
gpiochip0 [2000000.pinctrl] (224 lines)
gpiochip1 [pcf8574a] (8 lines)

This tells me that Linux is aware there are 2 gpio chips on this SoC. Digging a little deeper:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
root@nezha-allwinner-d1:~# gpioinfo
gpiochip0 - 224 lines:
        line   0:      unnamed       unused   input  active-high 
        line   1:      unnamed       unused   input  active-high 
        line   2:      unnamed       unused   input  active-high 
        line   3:      unnamed       unused   input  active-high 
        line   4:      unnamed       unused   input  active-high 
        line   5:      unnamed       unused   input  active-high 
        line   6:      unnamed       unused   input  active-high 
        line   7:      unnamed       unused   input  active-high 
        line   8:      unnamed       unused   input  active-high 
        line   9:      unnamed       unused   input  active-high 
        line  10:      unnamed       unused   input  active-high 
        line  11:      unnamed       unused   input  active-high 
        line  12:      unnamed       unused   input  active-high 
        line  13:      unnamed       unused   input  active-high 
        line  14:      unnamed       unused   input  active-high 
        line  15:      unnamed       unused   input  active-high 
        line  16:      unnamed       unused   input  active-high 
        line  17:      unnamed       unused   input  active-high 
        line  18:      unnamed       unused   input  active-high 
        line  19:      unnamed       unused   input  active-high 
        line  20:      unnamed       unused   input  active-high 
        line  21:      unnamed       unused   input  active-high 
        line  22:      unnamed       unused   input  active-high 
        line  23:      unnamed       unused   input  active-high 
        line  24:      unnamed       unused   input  active-high 
        line  25:      unnamed       unused   input  active-high 
        line  26:      unnamed       unused   input  active-high 
        line  27:      unnamed       unused   input  active-high 
        line  28:      unnamed       unused   input  active-high 
        line  29:      unnamed       unused   input  active-high 
        line  30:      unnamed       unused   input  active-high 
        line  31:      unnamed       unused   input  active-high 
        line  32:      unnamed       kernel   input  active-high [used]
        line  33:      unnamed       kernel   input  active-high [used]
        line  34:      unnamed  "interrupt"   input  active-high [used]
        line  35:      unnamed       unused   input  active-high 
        line  36:      unnamed       unused   input  active-high 
        line  37:      unnamed       unused   input  active-high 
        line  38:      unnamed       unused   input  active-high 
        line  39:      unnamed       unused   input  active-high 
        line  40:      unnamed       kernel   input  active-high [used]
        line  41:      unnamed       kernel   input  active-high [used]
        line  42:      unnamed       unused   input  active-high 
        line  43:      unnamed       unused   input  active-high 
        line  44:      unnamed       unused   input  active-high 
        line  45:      unnamed       unused   input  active-high 
        line  46:      unnamed       unused   input  active-high 
        line  47:      unnamed       unused   input  active-high 
        line  48:      unnamed       unused   input  active-high 
        line  49:      unnamed       unused   input  active-high 
        line  50:      unnamed       unused   input  active-high 
        line  51:      unnamed       unused   input  active-high 
        line  52:      unnamed       unused   input  active-high 
        line  53:      unnamed       unused   input  active-high 
        line  54:      unnamed       unused   input  active-high 
        line  55:      unnamed       unused   input  active-high 
        line  56:      unnamed       unused   input  active-high 
        line  57:      unnamed       unused   input  active-high 
        line  58:      unnamed       unused   input  active-high 
        line  59:      unnamed       unused   input  active-high 
        line  60:      unnamed       unused   input  active-high 
        line  61:      unnamed       unused   input  active-high 
        line  62:      unnamed       unused   input  active-high 
        line  63:      unnamed       unused   input  active-high 
        line  64:      unnamed       kernel   input  active-high [used]
        line  65:      unnamed       unused   input  active-high 
        line  66:      unnamed       kernel   input  active-high [used]
        line  67:      unnamed       kernel   input  active-high [used]
        line  68:      unnamed       kernel   input  active-high [used]
        line  69:      unnamed       kernel   input  active-high [used]
        line  70:      unnamed       kernel   input  active-high [used]
        line  71:      unnamed       kernel   input  active-high [used]
        line  72:      unnamed       unused   input  active-high
        line  73:      unnamed       unused   input  active-high
        line  74:      unnamed       unused   input  active-high
        line  75:      unnamed       unused   input  active-high
        line  76:      unnamed       unused   input  active-high
        line  77:      unnamed       unused   input  active-high
        line  78:      unnamed       unused   input  active-high
        line  79:      unnamed       unused   input  active-high
        line  80:      unnamed       unused   input  active-high
        line  81:      unnamed       unused   input  active-high
        line  82:      unnamed       unused   input  active-high
        line  83:      unnamed       unused   input  active-high
        line  84:      unnamed       unused   input  active-high
        line  85:      unnamed       unused   input  active-high
        line  86:      unnamed       unused   input  active-high
        line  87:      unnamed       unused   input  active-high
        line  88:      unnamed       unused   input  active-high
        line  89:      unnamed       unused   input  active-high
        line  90:      unnamed       unused   input  active-high
        line  91:      unnamed       unused   input  active-high
        line  92:      unnamed       unused   input  active-high
        line  93:      unnamed       unused   input  active-high
        line  94:      unnamed       unused   input  active-high
        line  95:      unnamed       unused   input  active-high
        line  96:      unnamed       unused   input  active-high
        line  97:      unnamed       unused   input  active-high
        line  98:      unnamed       unused   input  active-high
        line  99:      unnamed       unused   input  active-high
        line 100:      unnamed       unused   input  active-high
        line 101:      unnamed       unused   input  active-high
        line 102:      unnamed       unused   input  active-high
        line 103:      unnamed       unused   input  active-high
        line 104:      unnamed       unused   input  active-high
        line 105:      unnamed       unused   input  active-high
        line 106:      unnamed       kernel   input  active-high [used]
        line 107:      unnamed       kernel   input  active-high [used]
        line 108:      unnamed       kernel   input  active-high [used]
        line 109:      unnamed       kernel   input  active-high [used]
        line 110:      unnamed       kernel   input  active-high [used]
        line 111:      unnamed       kernel   input  active-high [used]
        line 112:      unnamed       kernel   input  active-high [used]
        line 113:      unnamed       unused   input  active-high
        line 114:      unnamed       unused   input  active-high
        line 115:      unnamed    "usbvbus"  output  active-high [used]
        line 116:      unnamed "usb0_vbus_det" input active-high [used]
        line 117:      unnamed "usb0_id_det" input active-high [used]
        line 118:      unnamed       unused   input  active-high
        line 119:      unnamed       unused   input  active-high
        line 120:      unnamed       unused   input  active-high
        line 121:      unnamed       unused   input  active-high
        line 122:      unnamed       unused   input  active-high
        line 123:      unnamed       unused   input  active-high
        line 124:      unnamed       unused   input  active-high
        line 125:      unnamed       unused   input  active-high
        line 126:      unnamed       unused   input  active-high
        line 127:      unnamed       unused   input  active-high
        line 128:      unnamed       kernel   input  active-high [used]
        line 129:      unnamed       kernel   input  active-high [used]
        line 130:      unnamed       kernel   input  active-high [used]
        line 131:      unnamed       kernel   input  active-high [used]
        line 132:      unnamed       kernel   input  active-high [used]
        line 133:      unnamed       kernel   input  active-high [used]
        line 134:      unnamed       kernel   input  active-high [used]
        line 135:      unnamed       kernel   input  active-high [used]
        line 136:      unnamed       kernel   input  active-high [used]
        line 137:      unnamed       kernel   input  active-high [used]
        line 138:      unnamed       unused   input  active-high
        line 139:      unnamed       kernel   input  active-high [used]
        line 140:      unnamed       kernel   input  active-high [used]
        line 141:      unnamed       kernel   input  active-high [used]
        line 142:      unnamed       kernel   input  active-high [used]
        line 143:      unnamed       kernel   input  active-high [used]
        line 144:      unnamed       unused   input  active-high
        line 145:      unnamed       unused   input  active-high
        line 146:      unnamed       unused   input  active-high
        line 147:      unnamed       unused   input  active-high
        line 148:      unnamed       unused   input  active-high
        line 149:      unnamed       unused   input  active-high
        line 150:      unnamed       unused   input  active-high
        line 151:      unnamed       unused   input  active-high
        line 152:      unnamed       unused   input  active-high
        line 153:      unnamed       unused   input  active-high
        line 154:      unnamed       unused   input  active-high
        line 155:      unnamed       unused   input  active-high
        line 156:      unnamed       unused   input  active-high
        line 157:      unnamed       unused   input  active-high
        line 158:      unnamed       unused   input  active-high
        line 159:      unnamed       unused   input  active-high
        line 160:      unnamed       kernel   input  active-high [used]
        line 161:      unnamed       kernel   input  active-high [used]
        line 162:      unnamed       kernel   input  active-high [used]
        line 163:      unnamed       kernel   input  active-high [used]
        line 164:      unnamed       kernel   input  active-high [used]
        line 165:      unnamed       kernel   input  active-high [used]
        line 166:      unnamed         "cd"   input  active-high [used]
        line 167:      unnamed       unused   input  active-high
        line 168:      unnamed       unused   input  active-high
        line 169:      unnamed       unused   input  active-high
        line 170:      unnamed       unused   input  active-high
        line 171:      unnamed       unused   input  active-high
        line 172:      unnamed       unused   input  active-high
        line 173:      unnamed       unused   input  active-high
        line 174:      unnamed       unused   input  active-high
        line 175:      unnamed       unused   input  active-high
        line 176:      unnamed       unused   input  active-high
        line 177:      unnamed       unused   input  active-high
        line 178:      unnamed       unused   input  active-high
        line 179:      unnamed       unused   input  active-high
        line 180:      unnamed       unused   input  active-high
        line 181:      unnamed       unused   input  active-high
        line 182:      unnamed       unused   input  active-high
        line 183:      unnamed       unused   input  active-high
        line 184:      unnamed       unused   input  active-high
        line 185:      unnamed       unused   input  active-high
        line 186:      unnamed       unused   input  active-high
        line 187:      unnamed       unused   input  active-high
        line 188:      unnamed       unused   input  active-high
        line 189:      unnamed       unused   input  active-high
        line 190:      unnamed       unused   input  active-high
        line 191:      unnamed       unused   input  active-high
        line 192:      unnamed       kernel   input  active-high [used]
        line 193:      unnamed       kernel   input  active-high [used]
        line 194:      unnamed       kernel   input  active-high [used]
        line 195:      unnamed       kernel   input  active-high [used]
        line 196:      unnamed       kernel   input  active-high [used]
        line 197:      unnamed       kernel   input  active-high [used]
        line 198:      unnamed       kernel   input  active-high [used]
        line 199:      unnamed       kernel   input  active-high [used]
        line 200:      unnamed       kernel   input  active-high [used]
        line 201:      unnamed       kernel   input  active-high [used]
        line 202:      unnamed       unused   input  active-high
        line 203:      unnamed       unused   input  active-high
        line 204:      unnamed      "reset"  output   active-low [used]
        line 205:      unnamed       unused   input  active-high
        line 206:      unnamed       unused   input  active-high
        line 207:      unnamed       unused   input  active-high
        line 208:      unnamed       unused   input  active-high
        line 209:      unnamed       unused   input  active-high
        line 210:      unnamed       unused   input  active-high
        line 211:      unnamed       unused   input  active-high
        line 212:      unnamed       unused   input  active-high
        line 213:      unnamed       unused   input  active-high
        line 214:      unnamed       unused   input  active-high
        line 215:      unnamed       unused   input  active-high
        line 216:      unnamed       unused   input  active-high
        line 217:      unnamed       unused   input  active-high
        line 218:      unnamed       unused   input  active-high
        line 219:      unnamed       unused   input  active-high
        line 220:      unnamed       unused   input  active-high
        line 221:      unnamed       unused   input  active-high
        line 222:      unnamed       unused   input  active-high
        line 223:      unnamed       unused   input  active-high
gpiochip1 - 8 lines:
        line   0:      unnamed       unused   input  active-high
        line   1:      unnamed       unused   input  active-high
        line   2:      unnamed       unused   input  active-high
        line   3:      unnamed       unused   input  active-high
        line   4:      unnamed       unused   input  active-high
        line   5:      unnamed       unused   input  active-high
        line   6:      unnamed       unused   input  active-high
        line   7:      unnamed       unused   input  active-high

This doesn't help us too much, at least not immediately. Sometimes, if we're lucky, these lines will have more meaningful names (which are specified in the board's device-tree) but that's not the case here. We have to dig a little deeper to figure out how these lines relate to the port names we identified in the schematic.

Looking through the source for the kernel that we're using, we come across: https://github.com/smaeul/linux/blob/d1/all/drivers/pinctrl/sunxi/pinctrl-sunxi.h#L19



This looks interesting. Previously gpioinfo told us that gpiochip0 has 224 lines (0 to 223). Looking at this header file (drivers/pinctrl/sunxi/pinctrl-sunxi.h) it looks like, on sunxi SoCs, each port has 32 pins. 224 is evenly divisible by 32, which implies this SoC has 7 ports: PortA through PortG. At this point I'll take a guess that we can now find all the PBx to PDx pins that are described in the schematic. For example, if we want to work with pin 40 on the header, aka GPIO25-I2S2-DOUT, aka PB4, I'm betting we'll find it on gpiochip0 line 36 since PortB starts at offset 32, and 32 + 4 = 36. PC1, on the oher hand, would be on gpiochip0 line 65 since PortC starts at offset 64.

We can wire up a simple circuit to test this theory:

NOTE:
  • I have a bunch of LEDs that have the resistor build-in, hence the lack of resistor

Then we can test with:

1
root@nezha-allwinner-d1:~# gpioset gpiochip0 36=1

and we get:


We can move the wire from pin 40 to other pins and test our calculations.

But what about those "Port P" pins? In the schematic we found some pins labeled PP[0-7]. The kernel only has support for I/O up to Port N (in theory), and this specific SoC only appears to have I/O up to Port G (inclusive, according to gpioinfo).  Looking again at the schematic (page 7/9, bottom left):


The designers of this board have decided to use a port expander for 8 of the pins in the 40-pin header. A port expander is a chip that allows you access to a larger number of IOs using a smaller number of SoC IOs. This IO expander, the PCF857x, is an I2C device. If you look carefully at this diagram and the schematic diagram of the 40-pin header, you'll notice that 2 pins from the 40-pin header (PB0 and PB1) are used to control this PCF857x and these 2 pins can be used to control the 8 PPx pins!

Now that we understand how these PPx pins are accessed, how do we go about accessing them? The good news is that we don't have to figure out how to access them since Linux already takes care of this for us. The Linux kernel already has a driver for the PCF857x (drivers/gpio/gpio-pcf857x.c) so all that's needed to get this to work is some glue logic in the device tree to associate PB0 to the PCF857x's SCK signal and PB1 with SDA. Thankfully this is already done for us in the Nezha's device tree (https://github.com/smaeul/linux/blob/d1/all/arch/riscv/boot/dts/allwinner/sun20i-d1-nezha.dts#L116):


If we look at the schematic for the PCF857x that is used on the Nezha, we see that it has an interrupt line that is connected to PB2 (which is confirmed in the comments in the device-tree above). Comparing "PB2" with how the interrupt is specified "1 2 IRQ_TYPE_LEVEL_LOW" it's a good bet that, for this SoC, interrupts are specified as: first number = port, second number = line. Using zero-based counting Port B = 1, and offset 2 = 2.

If we look back at what the kernel told us about its GPIO setup we see:

1
2
3
4
5
6
7
root@nezha-allwinner-d1:~# gpioinfo
gpiochip0 - 224 lines:

        line  32:      unnamed       kernel   input  active-high [used]
        line  33:      unnamed       kernel   input  active-high [used]
        line  34:      unnamed  "interrupt"   input  active-high [used]

This meshes exactly with what we've just discovered: gpiochip0 lines 32 and 33 are PB0 and PB1 (since the offset for Port B is 32 on this device). We also see that line 34, PB2, is marked as an interrupt. gpioinfo marks all 3 lines as being in-use (and therefore not available for us to poke and peek).

Now that we've got all of that figured out, how do we access the PPx pins that come off the GPIO expander? It's probably not a coincidence that gpioinfo told us that there are 8 lines on gpiochip1 and that the PCF857x GPIO expander has 8 pins:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
root@nezha-allwinner-d1:~# gpioinfo gpiochip1
gpiochip1 - 8 lines:
        line   0:      unnamed       unused   input  active-high 
        line   1:      unnamed       unused   input  active-high 
        line   2:      unnamed       unused   input  active-high 
        line   3:      unnamed       unused   input  active-high 
        line   4:      unnamed       unused   input  active-high 
        line   5:      unnamed       unused   input  active-high 
        line   6:      unnamed       unused   input  active-high 
        line   7:      unnamed       unused   input  active-high

If we look at the Linux kernel's driver for the PCF857x (drivers/gpio/gpio-pcf857x.c) we find (https://github.com/smaeul/linux/blob/d1/all/drivers/gpio/gpio-pcf857x.c#L376):

This proves the connection between GPIO expanders and gpiochips. In the Linux kernel, GPIO expanders are exposed as gpiochips. In fact, if we looked carefully at the output of gpiodetect we would have noticed that the kernel was already telling us that gpiochip1 is connected to the PCF857x:

1
2
3
root@nezha-allwinner-d1:~# gpiodetect
gpiochip0 [2000000.pinctrl] (224 lines)
gpiochip1 [pcf8574a] (8 lines)

Let's test this. I'm going to arbitrarily pick PP6, pin 37, and wire up the following test:

run:

1
root@nezha-allwinner-d1:~# gpioset gpiochip1 6=1

and:


nothing happens? It looks like the LED didn't come on. If you look closely, and possibly also dim the room lights, you'll see that the LED did, in fact, come on, though very dimly. At first this seems strange since the schematic shows the PCF857x as being connected to 3V3, so its output should be 3V3, and that should be enough to light the LED more visibly.

I'm not an electrical engineer so I don't know all the details of "why", but the short version is: the PCF857x is better at making zeros than it is at making ones. Trying to drive the LED by connecting the cathode (aka negative/short) of the LED to ground and the anode (aka positive/long pin) to the GPIO isn't going to work as well as we would expect. Instead, when working with the PCF957x, connect the cathode (aka negative/short pin) to the GPIO and the anode (positive/long pin) to 3V3 (we connect to 3V3 and not 5V since the PCF847x is using 3V3 for VDD, see the schematic for the Nezha board):

This time we perform:

1
root@nezha-allwinner-d1:~# gpioset gpiochip1 6=0

and get what we're expecting (a brightly lit led):


As you've probably noticed, the logic with this wiring is backwards. If PP6 is set to 1 then both wires feeding the LED will be giving it 3V3, turning it off. But if you set PP6 to 0 then there will be a potential across the LED and it will turn on. Using the GPIO for the zero, and holding the other side of the LED at 3V3 is why we had to turn the LED around and connect the GPIO to the anode.

RGB LED

Another neat thing about the Nezha board is that it has a user-controlled RGB LED (instead of just a regular single-colour LED). Interestingly enough, the AllWinner SoC has an RGB LED controller (called ledc), and the Linux kernel has a driver for it: CONFIG_LEDS_SUN50I_A100.

If you used the exact SHA1 checkout for your build as I used above, then you'll find a small issue with the meta-riscv layer in that it is using an older name for the driver in its defconfig. As a result, support for the RGB LED is not enabled correctly. That's been fixed, so upgrading to a more recent commit should work better.

With a correctly-working kernel, in user-space you can navigate (on the Nezha board) to /sys/class/leds/rgb:status and play with the RGB LED.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
root@nezha-allwinner-d1:/sys/class/leds/rgb:status# ls -l
total 0
-rw-r--r-- 1 root root 4096 Jan 30 19:22 brightness
lrwxrwxrwx 1 root root    0 Jan 30 20:34 device -> ../../../2008000.led-controller
-r--r--r-- 1 root root 4096 Jan 30 20:34 max_brightness
-r--r--r-- 1 root root 4096 Jan 30 20:34 multi_index
-rw-r--r-- 1 root root 4096 Jan 30 19:50 multi_intensity
drwxr-xr-x 2 root root    0 Jan 30 20:34 power
lrwxrwxrwx 1 root root    0 Dec 20 13:15 subsystem -> ../../../../../../class/leds
-rw-r--r-- 1 root root    0 Jan 30 19:18 trigger
-rw-r--r-- 1 root root 4096 Dec 20 13:15 uevent

First, take a look at the output from multi_index:

1
2
root@nezha-allwinner-d1:/sys/class/leds/rgb:status# cat multi_index 
red green blue

This tells you the expected order of LED values. Check the max_brightness, set the brightness to something small (no need to burn your retinas), and specify your red, green, and blue values:

1
2
3
4
root@nezha-allwinner-d1:/sys/class/leds/rgb:status# cat max_brightness 
255
root@nezha-allwinner-d1:/sys/class/leds/rgb:status# echo 15 > brightness 
root@nezha-allwinner-d1:/sys/class/leds/rgb:status# echo "0 0 255" > multi_intensity

Your RGB LED will now be blue:


You can play with other combinations to your heart's content, or you could download and run this little script I wrote to randomly set the values for you continuously (run it in the background).

Another neat thing to do with a user-led is to ask the kernel to control it to provide various types of feedback. For example, you can set the LED to provide a "heartbeat" to prove your board is alive:

1
2
3
4
root@nezha-allwinner-d1:/sys/class/leds/rgb:status# cat trigger 
[none] kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer oneshot mtd nand-disk heartbeat cpu cpu0 activity panic netdev mmc0 mmc1
root@nezha-allwinner-d1:/sys/class/leds/rgb:status# echo "20 0 20" > multi_intensity
root@nezha-allwinner-d1:/sys/class/leds/rgb:status# echo heartbeat > trigger

Interestingly, in trigger mode the global brightness doesn't seem to have any effect. Brightness is set by the individual values of multi_intensity. Not sure if that's a bug or a feature.