Skip to content
Draft
83 changes: 56 additions & 27 deletions docs/nanopc-T6.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,45 +32,74 @@ Documentation used for NixOS/Dogebox support:
- CONFIG_VENDOR_FRIENDLYELEC=y
- 'Zero memory on allocation'

## Reset Button Support
## Buttons

The NanoPC-T6 has a physical reset/power button that is **connected to the RK806 PMIC's pwrkey input**, not to a GPIO pin. This button is configured in the device tree patch (`rk3588-nanopc-t6.dtsi.patch`) as follows:
The NanoPC-T6 has **three distinct physical buttons** with different hardware connections and behaviors:

- **Connection**: RK806 PMIC pwrkey input
- **Driver**: `rk805-pwrkey` (automatically instantiated by MFD driver)
- **Key Code**: KEY_POWER
- **Kernel Config**:
- `CONFIG_INPUT_RK805_PWRKEY=y`
- `CONFIG_MFD_RK806_SPI=y` (FriendlyARM kernel variant)
### 1. Power Button (PWRON)

The button works by triggering interrupts (PWRON_FALL and PWRON_RISE) on the RK806 PMIC, which are handled by the kernel's rk805-pwrkey driver. This is the same mechanism used in U-Boot and the FriendlyARM kernel fork.
- **Hardware**: Connected to the RK806 PMIC's **PWRON** input pin
- **Mechanism**: PMIC-level power control with interrupt notification to kernel
- **Kernel driver**: `rk805-pwrkey` (auto-created by mainline `rk8xx-core.c` MFD driver — no device tree `pwrkey` node needed)
- **Input event**: `KEY_POWER`
- **Behavior**: Handled by `systemd-logind` (`HandlePowerKey`). NixOS default is `poweroff`.

### Device Tree Configuration
#### Kernel Configuration

In the PMIC node (`&spi2 > pmic@0`), the pwrkey node is enabled after the DVS pinctrl definitions and before the `regulators` block:
The following kernel config options are required (added via `structuredExtraConfig` since nabam's kernel doesn't include them):

```dts
rk806_dvs3_null: dvs3-null-pins {
pins = "gpio_pwrctrl3";
function = "pin_fun0";
};
- `CONFIG_MFD_RK8XX_SPI=y` — RK806 PMIC MFD driver via SPI bus
- `CONFIG_INPUT_RK805_PWRKEY=y` — Power key input driver for RK8XX PMICs
- `CONFIG_PINCTRL_RK805=y` — RK8XX family pinctrl driver

pwrkey {
status = "okay";
};
These are **mainline kernel** config names. The FriendlyARM vendor kernel (v6.1.y) uses different names (`CONFIG_MFD_RK806_SPI`, etc.) — do not confuse them.

regulators {
...
}
```
### 2. Reset Button (RESETB)

This enables the MFD driver to instantiate the power key device, which registers as a standard input device generating KEY_POWER events.
- **Hardware**: Connected to the RK806 PMIC's **RESETB** input pin
- **Mechanism**: Pure hardware reset — asserts RESETB low, which triggers the PMIC's reset function. **Bypasses the kernel entirely** (no software event, no clean shutdown)
- **Kernel driver**: None — handled at PMIC hardware level
- **Input event**: None
- **Behavior**: Configured by `RST_FUN` bits [7:6] in the RK806's `SYS_CFG3` register (0x72). The mainline kernel DT binding (`rockchip,reset-mode`) and FriendlyARM vendor DT property (`pmic-reset-func`) both map to this register:
- Mode 0: **Restart PMU** — full power cycle of all regulators
- Mode 1: Reset all power-off registers, force state to ACTIVE mode (FriendlyARM default)
- Mode 2: Same as mode 1, also pulls RESETB pin low for 5ms (resets SoC via CHIP_RESETB)

**Important Note**: The pwrkey node placement matches the Rockchip kernel device tree structure, which differs from mainline Linux. It should come after the pinctrl definitions and before the regulators block in the Rockchip kernel.
**Current approach** (two-part fix):

**Kernel Variant**: This system uses the FriendlyARM-based Rockchip kernel (`kernel_linux_latest_rockchip_stable`), which uses `rk806-core.c` MFD driver instead of mainline's `rk8xx-core.c`. The FriendlyARM driver checks for the pwrkey device tree node and requires it to be explicitly enabled with `status = "okay"`.
1. **Kernel patch** (`rk806-disable-slave-restart.patch`): The mainline `rk8xx-core.c` MFD driver unconditionally enables `SLAVE_RESTART_FUN` (SYS_CFG3 bit[1]) during probe for multi-PMIC setups where a master can restart slave PMICs via the RESETB pin. On the NanoPC-T6 (single PMIC), this is unnecessary and may interfere with RESETB button input handling. The kernel patch changes the `rk806_pre_init_reg[]` entry from `RK806_SLAVE_RESTART_FUN_EN` to `RK806_SLAVE_RESTART_FUN_OFF`.

**Previous Incorrect Approach**: Earlier attempts tried to configure the button as a GPIO key on GPIO1_PC0, but this was incorrect. The button is physically wired to the PMIC, not to a regular GPIO pin.
2. **DT property** (`rockchip,reset-mode = <2>`): Mode 2 resets PMIC registers, forces ACTIVE state, AND explicitly pulls the RESETB output low for 5ms. This ensures the SoC's reset input (CHIP_RESETB_N) sees the reset signal. Mode 0 (PMU restart) might not reliably reset the SoC if bypass capacitors hold power rails above the POR threshold. Mode 2 avoids this by using the dedicated reset signal path.

FriendlyARM's vendor kernel uses `pmic-reset-func = <1>` and their vendor PMIC driver (`rk806-core.c`) handles it differently from mainline.

### 3. Mask ROM Button (SARADC)

- **Hardware**: Connected to **SARADC channel 0** via voltage divider
- **Mechanism**: ADC-based key detection (reads analog voltage level)
- **Kernel driver**: `adc-keys` (already in mainline DTS as `adc-keys-0` node)
- **Input event**: `KEY_SETUP` (Mask Rom)
- **Behavior**: Used for entering Mask ROM/recovery mode when held during power-on. In U-Boot, detected via `CONFIG_BUTTON_ADC`.

### Important: Mainline vs FriendlyARM Vendor Kernel

This system uses `nabam/nixos-rockchip`'s `kernel_linux_latest_rockchip_stable`, which is the **mainline Linux kernel** with Rockchip-specific config options. It is NOT the FriendlyARM vendor kernel (v6.1.y).

Key differences:
- **Mainline kernel**: `rk8xx-core.c` unconditionally creates pwrkey MFD cell for RK806. Unconditionally enables `SLAVE_RESTART_FUN` in pre_init_reg. Reset mode configured via `rockchip,reset-mode` DT property.
- **FriendlyARM kernel**: `rk806-core.c` requires an explicit `pwrkey { status = "okay"; }` DT node. Does NOT enable `SLAVE_RESTART_FUN`. Reset mode configured via `pmic-reset-func` DT property.

### Previous Incorrect Approaches

Earlier attempts tried:
1. Configuring the reset button as a GPIO key (GPIO1_PC0) — incorrect, reset is wired to PMIC RESETB
2. Adding `pwrkey { status = "okay"; }` DT node — only works with FriendlyARM vendor kernel
3. Treating the power button and reset button as the same button — they are separate hardware
4. Configuring `HandlePowerKey=reboot` in systemd-logind — this changes the power button behavior, not the reset button which is hardware-only
5. Setting `rockchip,reset-mode = <1>` — was never actually tested due to malformed patch (build failed)
6. Setting `rockchip,reset-mode = <0>` — mode 0 (restart PMU) tested but didn't work; SoC may not reset if caps hold voltage
7. Removing `rockchip,reset-mode` entirely — tested but didn't work; letting U-Boot config persist wasn't enough because the MFD driver's `pre_init_reg` still modifies SYS_CFG3 (enables SLAVE_RESTART_FUN)
8. **Current fix**: Disable SLAVE_RESTART_FUN via kernel patch + set mode 2 via DT for explicit RESETB output assertion

## Device peripheral firmware

Expand Down
60 changes: 41 additions & 19 deletions nix/builders/nanopc-t6/base.nix
Original file line number Diff line number Diff line change
Expand Up @@ -55,31 +55,53 @@
boot.loader.generic-extlinux-compatible.enable = true;
boot.loader.timeout = 1;

# NanoPC-T6 has three physical buttons:
#
# 1. Power button (PWRON) — connected to RK806 PMIC pwrkey input.
# Generates KEY_POWER via rk805-pwrkey driver. Handled by systemd-logind.
# Default NixOS behavior: short press = poweroff, which is correct.
#
# 2. Reset button (RESETB) — connected to RK806 PMIC RESETB pin.
# Hardware-level reset, bypasses kernel entirely. Requires two fixes:
# a) Kernel patch disables SLAVE_RESTART_FUN (rk806-disable-slave-restart.patch)
# so the MFD driver doesn't repurpose RESETB for multi-PMIC slave restart.
# b) DT sets rockchip,reset-mode = <2> which resets PMIC registers and forces
# ACTIVE state so the SoC power-on-resets.
#
# 3. Mask ROM button — connected to SARADC channel 0.
# Used for entering Mask ROM/recovery mode during boot.

boot.kernelPackages =
let
# Use nabam's mainline-based rockchip kernel
# Use nabam's mainline-based rockchip kernel (linux_latest with rockchip config).
# nabam's config includes REGULATOR_RK808, GPIO_ROCKCHIP, PINCTRL_ROCKCHIP, SPI_ROCKCHIP
# but is missing the RK8XX MFD SPI driver and pwrkey input driver needed for the
# RK806 PMIC on the NanoPC-T6.
#
# Use function-form override to merge our additions with nabam's existing
# structuredExtraConfig and to append our kernel patches (DTS + driver fix)
# directly into the kernel derivation.
baseKernel = inputs.rockchip.legacyPackages.aarch64-linux.kernel_linux_latest_rockchip_stable;

# Override with mainline RK8XX config options for RK806 PMIC support
customKernel = baseKernel.kernel.override {
structuredExtraConfig = with lib.kernel; {
# Mainline kernel RK8XX drivers (supports RK806)
MFD_RK8XX_SPI = yes; # MFD driver for RK806 via SPI
REGULATOR_RK808 = yes; # Regulator driver (covers RK806)
PINCTRL_RK805 = yes; # Pinctrl driver (covers RK806)
INPUT_RK805_PWRKEY = yes; # Power key input driver
};
};
customKernel = baseKernel.kernel.override (prev: {
structuredExtraConfig = (prev.structuredExtraConfig or {}) // (with lib.kernel; {
MFD_RK8XX_SPI = yes; # RK806 PMIC MFD driver via SPI
PINCTRL_RK805 = yes; # RK8XX family pinctrl driver
INPUT_RK805_PWRKEY = yes; # RK8XX power key input driver
});
kernelPatches = (prev.kernelPatches or []) ++ [
{
name = "rk3588-nanopc-t6.dtsi.patch";
patch = ./rk3588-nanopc-t6.dtsi.patch;
}
{
name = "rk806-disable-slave-restart.patch";
patch = ./rk806-disable-slave-restart.patch;
}
];
});
in
lib.mkForce (pkgs.linuxPackagesFor customKernel);

boot.kernelPatches = [
{
name = "rk3588-nanopc-t6.dtsi.patch";
patch = ./rk3588-nanopc-t6.dtsi.patch;
}
];


boot.initrd.availableKernelModules = [
"nvme"
Expand Down
15 changes: 8 additions & 7 deletions nix/builders/nanopc-t6/rk3588-nanopc-t6.dtsi.patch
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@
};
};

@@ -793,6 +811,9 @@
gpio-controller;
#gpio-cells = <2>;
+ pwrkey {
+ status = "okay";
+ };
@@ -775,6 +793,8 @@

system-power-controller;

rk806_dvs1_null: dvs1-null-pins {
+ rockchip,reset-mode = <2>;
+
vcc1-supply = <&vcc4v0_sys>;
vcc2-supply = <&vcc4v0_sys>;
vcc3-supply = <&vcc4v0_sys>;
10 changes: 10 additions & 0 deletions nix/builders/nanopc-t6/rk806-disable-slave-restart.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--- a/drivers/mfd/rk8xx-core.c
+++ b/drivers/mfd/rk8xx-core.c
@@ -154,7 +154,7 @@
static const struct rk808_reg_data rk806_pre_init_reg[] = {
{ RK806_GPIO_INT_CONFIG, RK806_INT_POL_MSK, RK806_INT_POL_L },
- { RK806_SYS_CFG3, RK806_SLAVE_RESTART_FUN_MSK, RK806_SLAVE_RESTART_FUN_EN },
+ { RK806_SYS_CFG3, RK806_SLAVE_RESTART_FUN_MSK, RK806_SLAVE_RESTART_FUN_OFF },
{ RK806_SYS_OPTION, RK806_SYS_ENB2_2M_MSK, RK806_SYS_ENB2_2M_EN },
};

Loading