Get the ODROID CloudShell2 LCD and case fan working on an ODROID-XU4 running a modern Armbian "current" (kernel 6.6.x) image — where, out of the box, the LCD stays dark and the case fan is uncontrolled.
One script applies every fix:
git clone https://github.com/<you>/cloudshell2-xu4-kernel6.git
cd cloudshell2-xu4-kernel6
sudo ./install.sh
sudo rebootTested on Armbian 26.5.1 (resolute), kernel
6.6.141-current-odroidxu4, ODROID-XU4 + CloudShell2. Should apply to any Armbian odroidxu4 current (6.x) image that ships thehktft-cs-ogstoverlay.
Use this if, on an XU4 + CloudShell2 with a kernel-6 Armbian image:
- the LCD only shows the backlight (or nothing) — no console/image, and/or
- the case fan never turns on/off under control (always on, or you can't drive it).
The Hardkernel cloudshell-lcd / cloudshell2-fan packages target their own
kernel-5 images and do not make the LCD work on Armbian 6.x as-is.
| # | Problem | Root cause | Fix applied |
|---|---|---|---|
| 1 | CloudShell2 overlays never load | Armbian's /boot/boot.ini runs setenv cs2enable "false" after importing armbianEnv.txt, so setting it in armbianEnv.txt has no effect |
set cs2enable "true" inside boot.ini |
| 2 | LCD backlight off → screen dark | Armbian's hktft-cs-ogst overlay declares the backlight led-gpios active-LOW, but the CloudShell2 backlight is active-HIGH |
flip led-gpios flag 1 → 0 in the overlay |
| 5 | Case fan not controllable | The CloudShell2 case fan is an I2C device (0x60, reg 0x05: 0x00=on, 0x05=off) — not the SoC pwm-fan |
install a small systemd service that drives it by temperature |
| 3 (optional) | fb_hktft32 ... SPI transfer failed: -5 on a marginal link |
s3c64xx SPI DMA timing | force PIO with TXBUFLEN=32 — off by default |
| 4 (optional) | blank/unstable panel at 40 MHz on a marginal link | SPI signal integrity over the cabling | lower clock with SPI_HZ=10000000 — off by default |
The required fixes are #1, #2 and #5. A controlled test (fresh image, stock 40 MHz + DMA, only the backlight-polarity fix) showed the LCD works — so
install.shleaves the SPI clock and DMA at stock by default. #3 and #4 are opt-in fallbacks (SPI_HZ/TXBUFLEN) only for marginal cabling. Every edited file is backed up as*.bak.<timestamp>.
⚠️ Hardware note: the LCD uses SPI and the fan uses I2C through the same XU4 ↔ CloudShell2 board connector. If the fan (I2C) works but the LCD is still blank after these fixes, the SPI lines on that connector are likely not making contact — reseat the board / check the connector. (In our case a marginal connection caused a blank LCD until it was reseated; the fixes above were still required.)
apt install i2c-tools device-tree-compiler/boot/boot.ini→setenv cs2enable "true"/boot/dtb/hktft-cs-ogst.dtbo(decompiled, patched, recompiled):led-gpios→ active-high (the only required LCD overlay change)- (only if
SPI_HZis set) lowerspi-max-frequency - (only if
TXBUFLENis set) addtxbuflen
/usr/local/sbin/cloudshell2-fan-i2c(the fan controller)/etc/cloudshell2-fan.conf(fan settings — kept if it already exists)/etc/systemd/system/cloudshell2-fan.service(enabled at boot)
Fallback tuning (only if the LCD stays blank/unstable on a known-good connection):
SPI_HZ=10000000 TXBUFLEN=32 sudo ./install.shLCD — the Linux text console should appear on the CloudShell2 screen:
ls /dev/fb0 # fb0 should be the fbtft panel
cat /sys/class/graphics/fb0/name # -> fb_hktft32
# quick colour test (R/G/B vertical bars):
python3 - <<'PY'
import struct
w,h=[int(x) for x in open('/sys/class/graphics/fb0/virtual_size').read().split(',')]
s=int(open('/sys/class/graphics/fb0/stride').read())
bars=[0xF800,0x07E0,0x001F]
row=b''.join(struct.pack('<H',bars[min(2,(x*3)//w)]) for x in range(w)).ljust(s,b'\0')
open('/dev/fb0','wb').write(row*h)
PYFan:
systemctl status cloudshell2-fan
journalctl -u cloudshell2-fan -f # watch "temp NNC -> ON/OFF"ON_TEMP=60 # turn fan ON at/above this SoC temperature (°C)
OFF_TEMP=50 # turn fan OFF below this temperature (°C) -> hysteresis
INTERVAL=5 # seconds between checks
BUS=1 # i2c bus with the 0x60 controller (empty = auto-detect)
ALWAYS_ON=0 # 1 = ignore temperature, keep fan always on (Hardkernel default)Apply changes with sudo systemctl restart cloudshell2-fan.
Manual control: sudo i2cset -y 1 0x60 0x05 0x00 (ON) / ... 0x05 (OFF).
The fan coasts for a few seconds after an "off" — a 2–3 s off command can look like "still running". Hold it off ~8 s to confirm it stops.
By default the kernel console is rendered to the LCD. If you want a CloudShell-style
status screen (CPU/temp/disks), install Hardkernel's cloudshell-lcd display daemon
(it writes to tty1, which fbcon renders to the panel):
sudo apt install cloudshell-lcd # from the hardkernel PPA, if available for your release- Re-run after kernel/package updates. A kernel or
linux-dtb/bootinipackage upgrade can replaceboot.inior the overlay and revert the patches. Just runsudo ./install.shagain. - These fixes assume the standard Armbian odroidxu4 overlay set
(
hktft-cs-ogst.dtbo,cs2enableinboot.ini). Other boards/images differ. - The fan controller is on i2c-1 on the tested image; the script auto-detects
the bus if you set
BUS=(empty) in the config.
sudo ./uninstall.sh # removes the fan service, restores boot.ini & overlay backups
sudo rebootThese fixes came out of a long debugging session. Key findings:
cs2enablebelongs inboot.inion Armbian XU4 (thearmbianEnv.txtvalue is overridden), and the stock CloudShell overlay has a backlight-polarity bug.- The LCD is driven by the staging fbtft
fb_hktft32driver (ILI9340, SPI1), not DRM. Stock 40 MHz + DMA works on a healthy link (verified by a controlled test); a lower clock / PIO (SPI_HZ,TXBUFLEN) only matter for marginal cabling. - The CloudShell2 case fan is I2C
0x60(on/off), unrelated to the SoCpwm-fanthat the kernel thermal subsystem drives.
Contributions / corrections welcome.