ROCK(Pro)64 won't boot with Linux 6.12

Here’s what ended up working for me:

Flashed an older DietPi build and booted from USB.

As soon as I got SSH access, I hit Ctrl + C to interrupt the auto-update and ran:

cd /tmp  
wget https://dietpi.com/downloads/binaries/testing/linux-u-boot-rockpro64-current.deb  
dpkg -i linux-u-boot-rockpro64-current.deb  
/boot/dietpi/func/dietpi-set_hardware flash-uboot-mmc  
reboot

After rebooting, I installed mtd-utils to enable SPI flashing:

apt update  
apt install mtd-utils

Then flashed the SPI bootloader:

source /usr/lib/u-boot/platform_install.sh  
write_uboot_platform_mtd "$DIR"

Rebooted again, then finally ran:

dietpi-update

The device is now booting fine with the new kernel!

Note: On the older DietPi build I used (v9.12), dietpi-config only had the option to update the MMC bootloader — not the SPI bootloader. I guess that was added later, as I now see the option to update the SPI in v9.14.

Thanks everyone for the help — much appreciated!

2 Likes

Yes exactly, it was added it for ROCK64 and ROCKPro64 with DietPi v9.14, hence the need to call the write_uboot_platform_mtd manually until then.

Okay great that this got all finally sorted. Not sure what the issue with the old bootloader and new kernel was, most likely some too narrow addresses to load the grown kernel and/or initramfs to, so that some parts were overwriting each other. So in the end my original guess was right, and it was the very same thing with the 2017 U-Boot on SPI, matching also the report at the Armbian forum: ROCK(Pro)64 won't boot with Linux 6.12 - #14 by MichaIng

But what I was not aware of is that on both SBCs, the SPI bootloader is always used with priority, if present. Maybe it makes sense to define an own set of functional load addresses for each kernel, not relying on the bootloader to do this with enough space for recent kernel and initramfs.

Quick update:

After getting the device to boot with the new bootloader flashed, I noticed an issue with reboots.

If I run sudo reboot, the device doesn’t come back online — it just hangs silently.
However, if I run sudo poweroff, then physically disconnect and reconnect the power, it boots fine every time.

So cold boots work, but warm reboots (via reboot) don’t.

I’ve since updated the device to Debian Trixie using:

sudo bash -c "$(curl -sSf 'https://raw.githubusercontent.com/MichaIng/DietPi/dev/.meta/dietpi-trixie-upgrade')"

Since the upgrade, reboots now work as intended no more needing to sudo poweroff & cut power manually.

While performing the upgrade, I noticed this in the output, which I suspect might be related to the reboot issue I was having:

update-initramfs: Generating /boot/initrd.img-6.12.35-current-rockchip64
update-initramfs: Converting to U-Boot format
Image Name:   uInitrd
Created:      Fri Aug  8 18:14:25 2025
Image Type:   AArch64 Linux RAMDisk Image (gzip compressed)
Data Size:    18780134 Bytes = 18339.97 KiB = 17.91 MiB
Load Address: 00000000
Entry Point:  00000000
'/boot/uInitrd' -> 'uInitrd-6.12.35-current-rockchip64'

So it looks like something in the initramfs/U-Boot conversion during the upgrade may have resolved it.

Thanks everyone for the help and guidance along the way, much appreciated!

Hi

I got a personal server with this issue, and I didn’t want to try the update since I would be very annoyed if it couldn’t reboot, so I was preparing another one in my free time, expecting to move services on the other when it is ready, but there is still a lot to do because I don’t have much time for it actually.

So after each upgrade, I just reinstalled the last kernel package that worked form me (24.11.0-trunk-dietpi1) until now, and it was all I needed until my secondary server becomes ready.

But today, after the last update, I couldn’t reinstall kernel 24.11.0-trunk-dietpi1 anymore, so I was stuck with a machine I couldn’t reboot anymore because I really need it working…

I then tried to update my SPI with the methods given in this thread, but I had several issues:

  • With dietpi-config, the install ends with:

flashcp: invalid option – ‘p’

  • So I tried the manual way, and noticed I had to change the package name from linux-u-boot-rock64-current.deb to linux-u-boot-rockpro64-current.deb. Both are so close I didn’t notice the example was for Rock64, not for RockPro64. Then I launched the following commends, until write_uboot_platform_mtd, and got again:

flashcp: invalid option – ‘p’

I had a look in /boot/dietpi/dietpi-config and found the flash command, but without any ‘p’ option. I found however the write_uboot_platform_mtdb but I can’t find it anywhere in the system or in the aliases. I have no idea where it comes from, but the problem seems to lie there….

I am stucked with a freshly upgraded machine I can’t reboot for now, because I know it can’t reboot at all for now… could it be possible to have some help, either giving me a way to find my old kernel, or by providing a way to correct this write_uboot_platform_mtdb command ?

Thanks

After some more researches, I discovered the source command I didn’t know. So I had a look in /usr/lib/u-boot/platform_install.sh, found the write_uboot_platform_mtd function and removed the -p option from the line containing flashcp, then it seemed to work this time.

I would be glad, however, if someone could tell me if this is really fine, or if something supposed to be handled by this -p option may be missing… it doesn’t seem to be the case to me, but a more expert opinion would be appreciated.

Thanks

Interesting, but your flashcp does support the -p/--partition option, right?

root@DietPi:~# flashcp --help
...
   -p | --partition      Only copy different block from file to device

Or is your’s a Bullseye system, and flashcp supports that option since Bookworm only? This script uses it without condition, in other cases it is tested, whether it is supported, e.g. here in case of Odroid HC4: build/config/boards/odroidhc4.conf at e696c2eb3b284b2b0b3eadd6df83441a60cbf24a · armbian/build · GitHub

1 Like

Thank you, I understand it better already.

Yes, I am using a Bullseye system since I need to prepare another server to take over in case something goes wrong when upgrading to Bookworm, or even Trixie if possible, and I had little time to work on it for a while.

In fact, the -p option doesn’t exist on my flashcp version. This is what caused the failure.

But the description i get from flascp -h tells that flashcp [options] do the job, so it should work without the -p option… am I right ?

Okay good to know. I’ll apply a function rewrite to remove the -p flag on Bullseye systems. It makes flashcp write only changed bits to the SPI flash, so it works well without it, just a little slower.

EDIT: dietpi-config: fix flashcp call on Bullseye · MichaIng/DietPi@8b4f314 · GitHub

Hey everyone,

Just a quick update from my side:
Over the last 3–4 upgrades, the original issue has started to reappear. After applying updates, the board no longer reboots cleanly — the only way I can get it to come back up and running is

sudo poweroff

and then unplug and replug the AC power manually.

Regular reboots just hang, exactly like before.

Does anyone else notice the same behavior ?

Reboots still fails on v9.18.1 — only way to boot cleanly is

sudo poweroff, then disconnect and reconnect power.

I have two of these devices, running from various ages of install and I have no problems with updates, reboots, restarts, apt update, apt upgrade, dietpi-upgrade etc…. Basically whenever I realize an update is out I install it and no problems so far.

Hey All - I just tried to bring to life a ROCK64 I had running on DietPi Buster (yay, pandemic project!). After updating it, it would not boot.

I tried doing a fresh headless automated install with DietPi Bookworm and Trixie (and even just Armbian Bookworm), but there seems to be some issue at the … PHY layer? (detects ethernet, but can’t get a link)

So two questions:
1 - Is this at all related to the latest kernels and needing to update the SPI bootloader? I’m guessing now, but after trying various builds and configs, I’m grasping at straws.

2 - If not, is there any best practice for debugging a headless install, as I only seem to find first boot logs and the HDMI out also doesn’t want to work. :confused:

Thanks!

Usually a UART adapter would be required to be able to debug a headless system

So, this is interesting topic for me because I have 100 ROCK64 boards. They are very stable hardware boards.

I also tried “DietPi_ROCK64-ARMv8-Trixie.img” and it “seemed not to boot” as indicated by the HMI monitor having no display output.

I started looking at this blog and was confused about u-boot on SPI flash and U-boot on the flashed SD card with OS image; i.e., u-boot changes and the associated source file locations and usage. Again, this is confusing, as there is no hard indication of which source file is used for SPI uboot flash or SD uboot. Also how to modify them using some-what of a step-by-step process, as there is a bit of a chicken and egg effect… note kernel 5.1 from bullseye image is no where to be found.

The Good news for me is that after putting a serial console USB converter on pins 6 (GRN), 8 and 10 (RX/TX) the system is indeed booting but not giving HMI console output. I can get the IP address and then ssh in for a most robust session. I can also put a I2C LCD screen on with current IP and uptime to tell me the login… Thus I can uses these Rock64 boards w/o HMI display, which my my general use case anyway. Also “reboot” and “shutdown” do work. Thus it seem that u-boot on these board will still boot Linux kernels version 6 plus.

Hurray !

Also I just noticed that the “dietpi-config” program has “advanced” option “Update SPI bootloader : Flash current U-Boot to SPI storage”. Therefore, if you can find the “SPI bootloader file” and flash it , then maybe the HMI display will work. I think I will stick with the current SPI uboot version, as do not want to risk “bricking”.

Here is python I2C program to show 2 line splash screen.

root@DietPi ~ [1]# cat I2C_LCD_splash.py 
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Original code found at:
# https://gist.github.com/DenisFromHR/cc863375a6e19dce359d

"""
Compiled, mashed and generally mutilated 2014-2015 by Denis Pleic
Made available under GNU GENERAL PUBLIC LICENSE
...
"""

# i2c bus (0 -- original Pi, 1 -- Rev 2 Pi)
I2CBUS = 1

# LCD Address
ADDRESS = 0x3f

import smbus
from time import sleep
# 🌟 NEW: Added subprocess to run the bash commands
import subprocess
import sys

# --- I2C Device Class ---

class i2c_device:
    def __init__(self, addr, port=I2CBUS):
        self.addr = addr
        # Handle the case where smbus might not be available or the port is wrong
        try:
            self.bus = smbus.SMBus(port)
        except FileNotFoundError:
            print(f"Error: Could not open I2C bus {port}. Is the I2C kernel module loaded?")
            sys.exit(1)

    # Write a single command
    def write_cmd(self, cmd):
        self.bus.write_byte(self.addr, cmd)
        sleep(0.0001)
    
    # ... (rest of i2c_device methods remain the same) ...

    # Write a command and argument
    def write_cmd_arg(self, cmd, data):
      self.bus.write_byte_data(self.addr, cmd, data)
      sleep(0.0001)

    # Write a block of data
    def write_block_data(self, cmd, data):
      self.bus.write_block_data(self.addr, cmd, data)
      sleep(0.0001)

    # Read a single byte
    def read(self):
      return self.bus.read_byte(self.addr)

    # Read
    def read_data(self, cmd):
      return self.bus.read_byte_data(self.addr, cmd)

    # Read a block of data
    def read_block_data(self, cmd):
      return self.bus.read_block_data(self.addr, cmd)


# commands
LCD_CLEARDISPLAY = 0x01
# ... (rest of constants remain the same) ...
LCD_RETURNHOME = 0x02
LCD_ENTRYMODESET = 0x04
LCD_DISPLAYCONTROL = 0x08
LCD_CURSORSHIFT = 0x10
LCD_FUNCTIONSET = 0x20
LCD_SETCGRAMADDR = 0x40
LCD_SETDDRAMADDR = 0x80

# flags for display entry mode
LCD_ENTRYRIGHT = 0x00
LCD_ENTRYLEFT = 0x02
LCD_ENTRYSHIFTINCREMENT = 0x01
LCD_ENTRYSHIFTDECREMENT = 0x00

# flags for display on/off control
LCD_DISPLAYON = 0x04
LCD_DISPLAYOFF = 0x00
LCD_CURSORON = 0x02
LCD_CURSOROFF = 0x00
LCD_BLINKON = 0x01
LCD_BLINKOFF = 0x00

# flags for display/cursor shift
LCD_DISPLAYMOVE = 0x08
LCD_CURSORMOVE = 0x00
LCD_MOVERIGHT = 0x04
LCD_MOVELEFT = 0x00

# flags for function set
LCD_8BITMODE = 0x10
LCD_4BITMODE = 0x00
LCD_2LINE = 0x08
LCD_1LINE = 0x00
LCD_5x10DOTS = 0x04
LCD_5x8DOTS = 0x00

# flags for backlight control
LCD_BACKLIGHT = 0x08
LCD_NOBACKLIGHT = 0x00

En = 0b00000100 # Enable bit
Rw = 0b00000010 # Read/Write bit
Rs = 0b00000001 # Register select bit

# --- LCD Class ---

class lcd:
    #initializes objects and lcd
    def __init__(self):
        self.lcd_device = i2c_device(ADDRESS)

        self.lcd_write(0x03)
        self.lcd_write(0x03)
        self.lcd_write(0x03)
        self.lcd_write(0x02)

        self.lcd_write(LCD_FUNCTIONSET | LCD_2LINE | LCD_5x8DOTS | LCD_4BITMODE)
        self.lcd_write(LCD_DISPLAYCONTROL | LCD_DISPLAYON)
        self.lcd_write(LCD_CLEARDISPLAY)
        self.lcd_write(LCD_ENTRYMODESET | LCD_ENTRYLEFT)
        sleep(0.2)


    # clocks EN to latch command
    def lcd_strobe(self, data):
        self.lcd_device.write_cmd(data | En | LCD_BACKLIGHT)
        sleep(.0005)
        self.lcd_device.write_cmd(((data & ~En) | LCD_BACKLIGHT))
        sleep(.0001)

    def lcd_write_four_bits(self, data):
        self.lcd_device.write_cmd(data | LCD_BACKLIGHT)
        self.lcd_strobe(data)

    # write a command to lcd
    def lcd_write(self, cmd, mode=0):
        self.lcd_write_four_bits(mode | (cmd & 0xF0))
        self.lcd_write_four_bits(mode | ((cmd << 4) & 0xF0))

    # write a character to lcd (or character rom) 0x09: backlight | RS=DR<
    # works!
    def lcd_write_char(self, charvalue, mode=1):
        self.lcd_write_four_bits(mode | (charvalue & 0xF0))
        self.lcd_write_four_bits(mode | ((charvalue << 4) & 0xF0))
    
    # put string function with optional char positioning
    # Note: Your bash script was calling an external '/root/put2LinesToLcd', 
    # which is now integrated by calling this method.
    def lcd_display_string(self, string, line=1, pos=0):
        if line == 1:
            pos_new = pos
        elif line == 2:
            pos_new = 0x40 + pos
        elif line == 3:
            pos_new = 0x14 + pos
        elif line == 4:
            pos_new = 0x54 + pos

        self.lcd_write(0x80 + pos_new)

        # Truncate the string to fit the line (assuming 16x2/20x4, you may need to adjust)
        max_chars = 16 # Adjust based on your screen size (e.g., 20)
        display_str = string[:max_chars]

        for char in display_str:
            self.lcd_write(ord(char), Rs)

    # clear lcd and set to home
    def lcd_clear(self):
        self.lcd_write(LCD_CLEARDISPLAY)
        self.lcd_write(LCD_RETURNHOME)

    # define backlight on/off (lcd.backlight(1); off= lcd.backlight(0)
    # Note: Your bash script was calling an external '/root/lcd_backlight 0', 
    # which is now integrated by calling this method.
    def backlight(self, state): # for state, 1 = on, 0 = off
        if state == 1:
            self.lcd_device.write_cmd(LCD_BACKLIGHT)
        elif state == 0:
            self.lcd_device.write_cmd(LCD_NOBACKLIGHT)

    # add custom characters (0 - 7)
    def lcd_load_custom_chars(self, fontdata):
        self.lcd_write(0x40);
        for char in fontdata:
            for line in char:
                self.lcd_write_char(line)          

# ----------------------------------------------------
# 🌟 NEW: Integrated Frontend/Main Logic Function
# ----------------------------------------------------

def get_bash_output(command):
    """
    Executes a bash command and returns the stripped output as a string.
    """
    try:
        # We use shell=True because the commands use pipes and complex logic (awk, sed)
        result = subprocess.run(
            command, 
            shell=True, 
            check=True,  # Raise CalledProcessError for non-zero exit codes
            text=True,   # Decode output as string
            capture_output=True
        )
        return result.stdout.strip()
    except subprocess.CalledProcessError as e:
        print(f"Error running command: '{command}'. Error: {e.stderr.strip()}")
        return f"BASH FAILED: {e.returncode}"
    except Exception as e:
        print(f"General error: {e}")
        return "UNKNOWN ERROR"

def run_ip_display_frontend():
    """
    Implements the logic from the show_ip_on_lcd bash script.
    """
    # 1. Initialize the LCD and turn on backlight
    display = lcd()
    display.backlight(1) # Ensure backlight is on for initial display
    display.lcd_clear()
    
    # 2. Extract Data using Bash commands
    print("Gathering system data...")

    # Date and Time
    date_cmd = "date +'%m/%d/%y %H:%M'"
    d = get_bash_output(date_cmd) # + "    " # clear display OR add a few more spaces to overwrite previous line char.

    # IP Address (eth0)
    # addr=$(ip -4 addr show eth0 | awk '/inet / { split($2, a, "/"); print a[1] }')
    addr_cmd = "ip -4 addr show eth0 | awk '/inet / { split($2, a, \"/\"); print a[1] }'"
    addr = get_bash_output(addr_cmd)
    if not addr or addr.startswith('BASH FAILED'):
        # Fallback to the first active interface if eth0 failed or is down
        addr_cmd = "ip -4 addr show | awk '/inet / { split($2, a, \"/\"); print a[1]; exit }'"
        addr = get_bash_output(addr_cmd)
        if not addr or addr.startswith('BASH FAILED'):
             addr = "No IP (eth0 down)"

    # Hostname (uses built-in Python function for simplicity and reliability)
    import socket
    hostname = socket.gethostname()
    
    # Uptime
    # up=$(uptime -p|sed -e 's/weeks\?/wk/' -e 's/days\?/day/' -e 's/hours\?/hr/' -e 's/minutes\?/min/' -e 's/,//g')
    up_cmd = "uptime -p | sed -e 's/weeks\\?/wk/' -e 's/days\\?/day/' -e 's/hours\\?/hr/' -e 's/minutes\\?/min/' -e 's/,//g'"
    up = get_bash_output(up_cmd)
    
    # 3. Display Screen 1: Hostname and IP
    print(f"Display 1: Hostname: {hostname}, IP: {addr}")
    display.lcd_display_string(hostname, line=1, pos=0)
    display.lcd_display_string(addr, line=2, pos=0)
    sleep(7)

    # 4. Display Screen 2: Date and Uptime
    print(f"Display 2: Date: {d}, Uptime: {up}")
    display.lcd_clear()
    display.lcd_display_string(d, line=1, pos=0)
    display.lcd_display_string(up, line=2, pos=0)
    sleep(7)
    
    # 5. Turn Backlight Off
    # Equivalent to /root/lcd_backlight 0
    print("Turning off backlight.")
    display.backlight(0)
    
# ----------------------------------------------------
# 🌟 Main Execution Block
# ----------------------------------------------------

if __name__ == "__main__":
    try:
        run_ip_display_frontend()
    except KeyboardInterrupt:
        print("\nExiting script.")
        # Optional: Add cleanup code here (e.g., clear screen, set backlight on/off)
        try:
            display = lcd()
            display.backlight(0)
            display.lcd_clear()
        except Exception:
            pass # Ignore cleanup errors on exit
    except Exception as e:
        print(f"A fatal error occurred: {e}")