diff --git a/kernel/rv1106-enablement/DRIVER-PARITY.md b/kernel/rv1106-enablement/DRIVER-PARITY.md index 6abed45..a4a54be 100644 --- a/kernel/rv1106-enablement/DRIVER-PARITY.md +++ b/kernel/rv1106-enablement/DRIVER-PARITY.md @@ -18,11 +18,11 @@ c8a3, not just compiled. | uart1 / uart4 | ttyS1, ttyS4 | mainline | โœ… batch1 | | I2C (dw-apb, ff460000=i2c3) | ff460000.i2c | mainline | โœ… batch1 (i2c-3) | | watchdog (dw-wdt, ff5a0000) | ff5a0000.watchdog | mainline | โœ… batch1 (watchdog0) | -| thermal governor | rockchip_thermal | mainline | ๐Ÿ”จ gov up; tsadc node TODO | +| tsadc thermal (ff3c8000) | rockchip_thermal | ported (data+init+macros) | โœ… soc-thermal reads 39.8ยฐC | | SARADC (ff3c0000) | ff3c0000.saradc | ported (2-ch v2 data) | ๐Ÿ”จ driver added; probe -22 (clk-rate) | | GPIO_SYSFS (legacy /sys/class/gpio) | โ€” | mainline (config) | โฌœ goodix script needs it | | PWM (rockchip) | โ€” | mainline (=m) | โฌœ batch2 =y (backlight) | -| RTC (rv1106-rtc) | โ€” | **no mainline driver** | โฌœ port (low prio) | +| RTC (rv1106-rtc) | โ€” | ported (vendor driver) | โœ… /dev/rtc0 registers + reads | | USB2 phy (inno, rv1106) | rockchip_usb2phy_* | ported (data, no tuning) | โœ… probes โ†’ USB up | | USB host (DWC3โ†’xhci, ffb00000) | xhci-hcd:usb1 | mainline | โœ… xhci host registered | | USB OTG gadget (DWC3, eth0) | eth0 | mainline dwc3 | ๐Ÿ”จ host works; eth0 needs dr_mode=peripheral | @@ -35,9 +35,9 @@ c8a3, not just compiled. | GPIO_SYSFS / crypto / CFG80211 | โ€” | mainline (config) | โœ… =y (batch2) | | AIC8800 wifi (bsp/fdrv) | aic8800_* | **out-of-tree** | โฌœ M5 | | AIC8800 BT (btlpm) | aic8800_btlpm | **out-of-tree** | โฌœ M5 | -| NPU (rknpu, ff660000) | rknpu, ff660000.npu | **out-of-tree** | โฌœ M6 | +| NPU (rknpu, ff660000) | rknpu, ff660000.npu | **out-of-tree** | โฌœ M6 โ€” plan: `npu/PORT-PLAN.md` | | RGA 2D (rga2) | rga2 | rockchip | โฌœ M6 | -| I2S audio | i2s | rockchip | โฌœ M6 | +| I2S audio (i2s-tdm) | i2s | rv1126 fallback | ๐Ÿ”จ DAI built; needs acodec+card | | FIQ debugger (ttyFIQ0) | fiq_glue | rockchip | โฌœ optional (we use ttyS2) | Legend: โœ… verified on hardware ยท ๐Ÿ”จ built, not yet verified ยท โฌœ not started. diff --git a/kernel/rv1106-enablement/dts/rv1106-warden.dts b/kernel/rv1106-enablement/dts/rv1106-warden.dts index 4d9977f..2886869 100644 --- a/kernel/rv1106-enablement/dts/rv1106-warden.dts +++ b/kernel/rv1106-enablement/dts/rv1106-warden.dts @@ -146,3 +146,16 @@ status = "okay"; dr_mode = "otg"; }; + +/* --- sweep: RTC + thermal (tsadc) + I2S audio DAI --- */ +&rtc { + status = "okay"; +}; + +&tsadc { + status = "okay"; +}; + +&i2s0_8ch { + status = "okay"; +}; diff --git a/kernel/rv1106-enablement/npu/PORT-PLAN.md b/kernel/rv1106-enablement/npu/PORT-PLAN.md new file mode 100644 index 0000000..c096b54 --- /dev/null +++ b/kernel/rv1106-enablement/npu/PORT-PLAN.md @@ -0,0 +1,392 @@ +# RKNPU kernel driver โ†’ 6.18 โ€” port plan (M6 class) + +Scope: port the **kernel driver only** (`rknpu.ko`'s source, statically built into +our tree) so `/dev/dri/cardN` (or `renderD1xx`) binds on the RV1106 NPU and answers +a version-query ioctl. This does **not** run a model โ€” see ยง3 for why that's a +separate, much bigger, and largely closed problem. Written against the same +target as the rest of this port: **Linux 6.18.46 vanilla** +(`flare-edge/research/linux-6.18.46/`), forward-ported from vendor 5.10.160, built +with our `arm-rockchip830-...-gcc 8.3` toolchain โ€” see `../PORT-STATUS.md` and +`../../docs/bringup.md` for the method and milestones this slots into (M6, listed +in `../DRIVER-PARITY.md` as "NPU (rknpu, ff660000) | out-of-tree | โฌœ M6"). + +Builds on `warden-sdk/docs/npu-graphics-feasibility.md`, which already read this +same driver source to answer a narrower question (can the NPU do graphics โ€” no). +This document answers the porting question that doc explicitly deferred. + +--- + +## 1. What we're forward-porting (vendor SDK source) + +Source: `flare-edge/sdk/sysdrv/source/kernel/drivers/rknpu/` โ€” vendor version +**0.9.2** (`DRIVER_MAJOR/MINOR/PATCHLEVEL` in `include/rknpu_drv.h:34-36`, +`DRIVER_DATE "20230825"`), currently loaded on the shipping 5.10.160 kernel as +`rknpu.ko`, IRQ `ff660000.npu`. + +### The driver already targets RV1106 natively โ€” this is not a from-scratch adaptation + +The vendor driver is a **single multi-SoC codebase**, not something written for +RK3588 that we'd have to adapt. `rknpu_drv.c`'s `of_match` table already carries a +dedicated RV1106 entry and config struct: + +```c +// rknpu_drv.c:196-198 +{ .compatible = "rockchip,rv1106-rknpu", .data = &rv1106_rknpu_config }, + +// rknpu_drv.c:143-160 +static const struct rknpu_config rv1106_rknpu_config = { + .dma_mask = DMA_BIT_MASK(32), + .pc_data_amount_scale = 2, + .pc_task_number_bits = 16, + .pc_task_number_mask = 0xffff, + .pc_task_status_offset = 0x3c, + .pc_dma_ctrl = 0, + .bw_enable = 1, + .irqs = rknpu_irqs, .resets = rknpu_resets, + .nbuf_phyaddr = 0, .nbuf_size = 0, // no NBUF wired for this SoC + .max_submit_number = (1 << 16) - 1, +}; +``` + +And the devicetree side is **already complete** in `rv1106.dtsi` (base tree, +`status = "disabled"`) โ€” nothing to author, only enable: + +```dts +// arch/arm/boot/dts/rv1106.dtsi:1127-1138 +npu: npu@ff660000 { + compatible = "rockchip,rv1106-rknpu"; + reg = <0xff660000 0x10000>; + interrupts = ; + clocks = <&cru ACLK_RKNN>, <&cru HCLK_RKNN>; + clock-names = "aclk", "hclk"; + assigned-clocks = <&cru ACLK_RKNN>; + assigned-clock-rates = <420000000>; + resets = <&cru SRST_A_RKNN>, <&cru SRST_H_RKNN>; + reset-names = "srst_a", "srst_h"; + status = "disabled"; +}; +``` + +No `power-domains` property (RV1106's NPU is single-core, single-rail โ€” unlike +RK3588's 3-core NPU which needs `genpd_dev_npu0/1/2`) and no `iommus` property +(matches the 5.10 boot-log finding already on record: `"rknpu iommu device-tree +entry not found!, using non-iommu mode"`, `npu-graphics-feasibility.md:123-126`). +Both facts materially shrink the v1 port's scope โ€” see ยง2.3 and ยง2.4. + +### The uapi/job model (for context โ€” already characterized in the feasibility doc) + +`include/rknpu_ioctl.h`: `struct rknpu_task` (`regcfg_amount`, `regcfg_offset`, +`regcmd_addr`) + `struct rknpu_submit` (`task_obj_addr`, `regcfg_obj_addr`, +`core_mask`, `fence_fd`) โ€” a **register command-list (regcmd) task-queue model**, +submitted via `DRM_IOCTL_RKNPU_SUBMIT` and executed to completion with the driver +blocking on a hardware IRQ (`rknpu_job.c`, `wait_event_timeout`). Six ioctls total: +`RKNPU_ACTION`, `RKNPU_SUBMIT`, `RKNPU_MEM_{CREATE,MAP,DESTROY,SYNC}` +(`rknpu_ioctl.h:288-322`). `RKNPU_ACTION` carries the version queries this plan's +verify step uses: `RKNPU_GET_HW_VERSION = 0`, `RKNPU_GET_DRV_VERSION = 1` +(`rknpu_ioctl.h:113-114`). + +### Memory manager choice: DRM GEM (not DMA-heap) + +`Kconfig` offers a mutually-exclusive choice: `ROCKCHIP_RKNPU_DRM_GEM` (default) +vs `ROCKCHIP_RKNPU_DMA_HEAP`. **Pick DRM_GEM** โ€” M4 (VOP2 display) already pulls +the DRM core into this kernel for the panel, so there's no new subsystem cost, and +DRM_GEM is the vendor's default/most-tested path. This also means the node that +appears is a classic DRM char device (`/dev/dri/cardN` / `renderD1xx` via +`drm_dev_alloc`/`drm_dev_register`, `rknpu_drv.c:725,730`) โ€” **not** the newer +`/dev/accel/` framework mainline's own `rocket` driver uses (ยง3). Don't confuse +the two node namespaces when verifying. + +--- + +## 2. Files, config, and the concrete 5.10โ†’6.18 API deltas + +### 2.1 File list โ€” v1 minimal port + +| File | Bring in? | Why | +|---|---|---| +| `rknpu_drv.c` / `include/rknpu_drv.h` | **Yes** | probe/remove, of_match table, DRM driver registration, power get/put | +| `rknpu_job.c` / `include/rknpu_job.h` | **Yes** | job submit, IRQ handler, PC task-list execution | +| `rknpu_gem.c` / `include/rknpu_gem.h` | **Yes** | GEM memory manager (DRM_GEM path) | +| `rknpu_reset.c` / `include/rknpu_reset.h` | **Yes** | `SRST_A_RKNN`/`SRST_H_RKNN` reset control | +| `rknpu_iommu.c` / `include/rknpu_iommu.h` | Yes, but dead code path | 61 lines, self-contained, already version-gated to 6.1; harmless to carry even though `iommu_en` stays false on our non-IOMMU DT (ยง2.4) | +| `rknpu_debugger.c` / `include/rknpu_debugger.h` | Yes (optional) | `/proc/rknpu/load` โ€” the Monitor page already polls this on 5.10 (`npu-graphics-feasibility.md:151-158`); keep for continuity even though wiring the UI back up is out of scope here | +| `rknpu_mem.c` | **No** | only for `ROCKCHIP_RKNPU_DMA_HEAP` โ€” we're not using that memory manager | +| `rknpu_mm.c` / `include/rknpu_mm.h` | **No** | SRAM/NBUF allocator (`ROCKCHIP_RKNPU_SRAM`, needs `NO_GKI`); `rv1106_rknpu_config` has `nbuf_phyaddr=0, nbuf_size=0` โ€” dead weight on this SoC | +| `rknpu_fence.c` / `include/rknpu_fence.h` | **No (v1)** | `ROCKCHIP_RKNPU_FENCE`/`SYNC_FILE` โ€” dma-fence cross-driver sync, not needed to prove basic binding; revisit if a real workload needs fenced submission later | +| `Kconfig`, `Makefile` | **Yes, trimmed** | drop the `rknpu_mem.o`/`rknpu_mm.o`/`rknpu_fence.o` conditional lines' configs (leave the `Makefile` structure as-is โ€” it's already `obj-$(CONFIG_...)`-gated per file, so simply not enabling those Kconfig symbols is sufficient; no Makefile edit required) | + +### 2.2 Config symbols (built-in, matching this port's established pattern โ€” +M1โ€“M3 build everything statically to avoid the vermagic class of bug that +blocks `aic8800.ko` today, `PORT-STATUS.md:114-115`) + +``` +CONFIG_ROCKCHIP_RKNPU=y +CONFIG_ROCKCHIP_RKNPU_DRM_GEM=y +CONFIG_ROCKCHIP_RKNPU_DEBUG_FS=y # depends on DEBUG_FS (already on for bring-up) +CONFIG_ROCKCHIP_RKNPU_PROC_FS=y # /proc/rknpu/load continuity +# leave off for v1: ROCKCHIP_RKNPU_DMA_HEAP, ROCKCHIP_RKNPU_SRAM, ROCKCHIP_RKNPU_FENCE +CONFIG_DRM=y # already required by M4 (VOP2) +``` + +### 2.3 DT change + +One-line status flip on the board DT (the same pattern M2/M3 used for +`grf-clock-controller` and the eMMC node โ€” override in the board file, don't +touch the base `rv1106.dtsi`): + +```dts +&npu { + status = "okay"; +}; +``` + +No new properties needed โ€” `compatible`/`reg`/`interrupts`/`clocks`/`resets` are +already correct and match the driver's own `rv1106_rknpu_config` exactly (ยง1). +**Do not add an `iommus =` property for v1** โ€” see ยง2.4. + +### 2.4 The real build blocker: four vendor-only `soc/rockchip/*.h` headers + +This is the one item in this plan that isn't "already handled by the vendor's own +version gates" โ€” verified directly against our `flare-edge/research/linux-6.18.46/` +tree, not assumed: + +```c +// rknpu_drv.c:37-40 (inside #ifndef FPGA_PLATFORM, which is never defined for +// our build โ€” grep of Makefile/Kconfig shows no FPGA_PLATFORM define anywhere) +#include +#include +#include +#include +``` + +`find flare-edge/research/linux-6.18.46/include -iname 'rockchip_{iommu,opp_select, +system_monitor,ipa}.h'` returns **nothing** โ€” all four are Rockchip downstream-BSP +convenience headers (DVFS/OPP-table selection, thermal/system-monitor +registration, IPA power-model, and a vendor wrapper around the IOMMU-core API) +that were never upstreamed. `rknpu_drv.h:21-23` pulls in `rockchip_opp_select.h` +unconditionally too, gated only by `KERNEL_VERSION(5,10,0) <= LINUX_VERSION_CODE` +โ€” true for 6.18, so it's compiled by default, not something a Kconfig toggle +avoids. + +**All four are genuinely dead code for RV1106 at runtime**, which is what makes +this a small, well-scoped fix rather than a real feature to build: +- `rockchip_iommu_is_enabled()` (`rknpu_drv.c:902`) is only called inside + `if (rknpu_dev->multiple_domains)` โ€” true only for RK3588's 3-core NPU; RV1106 + never sets it (no `power-domains` property, ยง1). +- The OPP/system-monitor/IPA calls drive dynamic frequency/voltage scaling and + thermal cooling-device registration against an OPP table โ€” RV1106's DT pins a + single fixed clock rate (`assigned-clock-rates = <420000000>`) and has no + `operating-points-v2` table; none of this is exercised today either. + +**Fix**: add small local compat shim headers (in this port's own include path, +ahead of the vendor source's include search path) providing just the symbols +these call sites reference โ€” `rockchip_iommu_is_enabled()` returning `false`, and +no-op/`-ENOTSUPP` stand-ins for the opp/monitor/ipa registration calls actually +referenced in `rknpu_drv.c`. This is the same "compat shim for a header that moved +or doesn't exist upstream" pattern already used for `clk-rv1106.c`'s +`panic_notifier_list` move (`../PORT-STATUS.md:18-20`) โ€” same class of fix, +same low risk, because the code behind it is provably dead for this SoC's DT. +**Do not** reach for `#define FPGA_PLATFORM` as a shortcut โ€” that macro also +guards the reset-control logic in `rknpu_reset.c` (nearly the whole file is +`#ifndef FPGA_PLATFORM`), which we need live; it's too blunt an instrument here. + +### 2.5 Surfaces already handled by the vendor driver's own version gates +(verified against 6.18.46 headers directly, not assumed) + +The driver was already written to track multiple kernel versions +(`npu-graphics-feasibility.md:264-269` first flagged this). Checked what's +actually still true at 6.18: + +| Vendor gate (`rknpu_drv.c`) | 6.18 status (verified) | +|---|---| +| `#if KERNEL_VERSION(6,1,0) > LINUX_VERSION_CODE` around `.gem_free_object_unlocked` | Correctly **skipped** โ€” that field is gone from `struct drm_driver` in 6.18's `include/drm/drm_drv.h` (grepped, zero hits), and the driver's `#else` branch already uses the modern `struct drm_gem_object_funcs` (`.free`, `.export`, `.get_sg_table`, `.vmap`, `.vunmap`, `.mmap` โ€” `rknpu_gem.c:352-358`) | +| `DEFINE_DRM_GEM_FOPS(...)` (6.1+) vs hand-rolled `file_operations` | 6.1+ branch applies; macro is a standard DRM-core helper, present in 6.18 | +| `.gem_prime_mmap = drm_gem_prime_mmap` (6.1+) vs a custom `rknpu_gem_prime_mmap` | 6.1+ branch applies | +| `struct drm_driver` fields the vendor initializer sets (`major`, `minor`, `patchlevel`, `driver_features`, `dumb_create`, `dumb_map_offset`) | All still present in 6.18's `drm_drv.h` (line-checked) | +| `DRM_IOCTL_DEF_DRV(...)` macro (ioctl table) | Still defined in 6.18's `include/drm/drm_ioctl.h:151` | +| `iommu_map()` / `iommu_unmap()` / `iommu_get_domain_for_dev()` / `iommu_attach_device()` / `iommu_detach_device()` (`rknpu_gem.c`, `rknpu_reset.c`) | `iommu_map()`'s extern signature is unchanged in 6.18's `include/linux/iommu.h:914` (mainline did add a newer `iommu_map_nosync()` alongside it, but didn't remove the classic call) โ€” moot anyway since this path is dead on our non-IOMMU DT (ยง2.4) | +| `devm_reset_control_get`, `clk_bulk_data`, `pm_runtime_get_sync`/`put_sync`/`resume_and_get` | Stable mainline APIs across the whole 5.10โ†’6.18 span; no gate needed | + +Net: outside the four-header fix in ยง2.4, this is expected to be a **build-fix-build +pass**, not a rewrite โ€” confirm by actually compiling into the tree (the checks +above are header-presence/signature verification, not a build). + +### 2.6 Explicitly deferred (not required to prove the driver binds) + +- **IOMMU enablement.** Stays off โ€” matches current 5.10 runtime behavior and + avoids the newer-kernel IOMMU-core churn entirely (mainline replaced + `iommu_domain_alloc(bus)` with device-based `iommu_paging_domain_alloc(dev)` + somewhere in the 6.x series โ€” confirmed by grepping 6.18.46's `iommu.h`, which + has the new call and no bus-based `iommu_domain_alloc`). Since our DT carries no + `iommus=` property, this churn never gets compiled against in the first place. +- **dma-fence / `ROCKCHIP_RKNPU_FENCE`.** Cross-driver sync primitive, not needed + to answer a version-query ioctl. +- **DVFS / thermal cooling / multi-power-domain.** RV1106-inapplicable per ยง2.4; + stubbed out, not implemented. +- **SRAM/NBUF allocator.** Dead weight on this SoC's config table (ยง2.1). + +--- + +## 3. Verify steps (driver binding only โ€” no model, no RKNN runtime) + +Uses the same proven safe-test loop as M2/M3: the A/B `_b`-slot one-shot boot on +`warden-c8a3` (`../../docs/m2-boot-on-c8a3.md`) โ€” never touches the working `_a` +slot, auto-reverts on hang. + +1. **Build**: `CONFIG_ROCKCHIP_RKNPU=y` (+ the symbols in ยง2.2) added to the + defconfig fragment; `rknpu_drv.o`/`rknpu_job.o`/`rknpu_gem.o`/`rknpu_reset.o`/ + `rknpu_iommu.o`/`rknpu_debugger.o` compile clean into `built-in.a` โ€” this is + where the ยง2.4 shim headers get proven, not just inspected. +2. **DT**: `npu@ff660000` flipped to `okay`; `dtc -W` clean, no warnings, no + overrun of the existing DTB-placement fix from M2 (`PORT-STATUS.md`'s "place + the fdt high" note โ€” a bigger built-in.a makes this worth re-checking). +3. **Boot** (via the `_b`-slot loop): `dmesg | grep -i rknpu` shows the + `platform_driver` probing without error โ€” clock/reset/IRQ acquired, no panic, + no `-EPROBE_DEFER` stall. Compare against the 5.10 baseline probe log for the + same node if available. +4. **Node appears**: `ls -la /dev/dri/` shows a new `cardN`/`renderD1xx` for the + npu โ€” classic DRM char device (not `/dev/accel/`, see ยง1). +5. **Trivial ioctl, not a model**: a small host-buildable C program opens the DRM + node and issues `DRM_IOCTL_RKNPU_ACTION` with `{.flags = RKNPU_GET_DRV_VERSION}` + (`rknpu_ioctl.h:114`), checks the returned `value` decodes to `0.9.2` + (`RKNPU_GET_DRV_VERSION_MAJOR/MINOR/PATCHLEVEL` macros, + `rknpu_ioctl.h:52-54`) โ€” and/or `RKNPU_GET_HW_VERSION` returns something + plausible. This exercises the full ioctl-dispatch โ†’ power-get/put โ†’ + clock/reset path with zero dependency on a regcmd buffer or the RKNN runtime. +6. **Explicitly not required for "done" here**: `DRM_IOCTL_RKNPU_SUBMIT`, any + `.rknn` model, `librknnrt`. That's the userspace question โ€” ยง4. + +### Effort estimate + +Smaller than M1 (clk/pinctrl โ€” required inferring an unknown CPU-clock mux from a +sibling diff) and smaller than the display work ahead in M4 (register-map +guesswork against RV1126/RK3568 siblings). This one is closer in shape to M3 ("the +eMMC node was all M3 needed" โ€” `PORT-STATUS.md:99`): the DT is already fully +specified upstream, the driver's C source already has a dedicated, tested RV1106 +config table and of_match entry (ยง1), and the GEM/DRM surface is already correctly +version-gated past 6.1 (ยง2.5). The concentrated risk is (a) actually compiling +the ยง2.4 shims against real 6.18 headers rather than trusting the header-presence +check above, and (b) the possibility of additional vendor-only symbols not +surfaced by this read-through. Realistic order of magnitude: low-single-digit +engineer-days to a clean probe + version-ioctl round trip on hardware, assuming +no surprise blocks the way M2's boot-image format did. + +--- + +## 4. The userspace-runtime reality: what porting the kernel driver does โ€” and does NOT โ€” unlock + +The instruction that motivated this document was explicit: don't let "the driver +is open source" imply the NPU becomes open-source-usable. It doesn't. This section +updates `npu-graphics-feasibility.md ยง4`'s conclusion with the current +(2026-08-24) state of every open effort found. + +### 4.1 The kernel driver itself: genuinely open, and this is a real forward-port of Rockchip's own code + +`rknpu_drv.c` et al. are SPDX `GPL-2.0`, authored by Rockchip +(`Felix Zeng `), and are the actual vendor driver โ€” the +same driver every Rockchip Linux SDK ships, mirrored at +[`github.com/airockchip/rknpu`](https://github.com/airockchip/rknpu) / +[`github.com/rockchip-linux/rknpu`](https://github.com/rockchip-linux/rknpu). +Porting it forward is legitimate, license-clean work, not a workaround. **This is +a different codebase from mainline's own driver** (ยง4.2) โ€” don't conflate "port +the vendor driver" with "adopt mainline's `accel/rocket`"; they are unrelated +implementations of the same hardware class, and only one of them (the vendor one) +covers RV1106 at all. + +### 4.2 Mainline `accel/rocket`: real, merged, and does not reach RV1106 + +- Merged into mainline Linux and Mesa in **2025-07** โ€” Tomeu Vizoso, ["Rockchip NPU + update 6: We are in mainline!"](https://blog.tomeuvizoso.net/2025/07/rockchip-npu-update-6-we-are-in-mainline.html), + following the LKML series + ["[PATCH v2 0/7] New DRM accel driver for Rockchip's RKNN NPU"](https://lkml.iu.edu/hypermail/linux/kernel/2502.3/02497.html). + Present verbatim in our own vendored `flare-edge/research/linux-6.18.46/drivers/accel/rocket/`. +- **Kconfig hard-excludes RV1106 by architecture, before generation is even a + question**: `depends on (ARCH_ROCKCHIP && ARM64) || COMPILE_TEST` + (`drivers/accel/rocket/Kconfig` in our 6.18.46 tree). RV1106 is a 32-bit + Cortex-A7 (`arch/arm`, confirmed on our own hardware: + `PORT-STATUS.md`'s boot log โ€” `CPU: ARMv7 Processor`). This is a Kconfig + dependency, not necessarily an unfixable technical wall on its own โ€” but it + signals no one has done the 32-bit validation work, on top of the register-level + work below. +- **Hardware coverage, per the driver's own docs** + (`Documentation/accel/rocket/index.rst` in our tree): *"Hardware currently + supported: * RK3588."* Nothing else, as shipped in 6.18.46. +- **RK3576** โ€” active, but incomplete, and not in our tree. A 2026-07-15 + reverse-engineering effort ( + [CNX Software](https://www.cnx-software.com/2026/07/15/reverse-engineering-brings-rk3576-npu-support-to-open-source-rocket-driver-for-mainline-linux/), + code at [`gahingwoo/linux-rk3576-npu`](https://github.com/gahingwoo/linux-rk3576-npu)) + got single-task inference working end-to-end on a Radxa ROCK 4D running Linux + 7.1-rc5 โ€” but **multi-task chained inference (any real multi-layer network) + fails: only the first task per NPU power session actually computes.** Not + merged into the kernel we're building against. Cited here because it's the + closest active precedent to "port Rocket to a new RKNPU generation," and even + that isn't production-usable yet. +- **RK3568/RK3566** โ€” an out-of-tree community fork exists (Armbian forum, + ["ODROID-M1: RK3568 NPU on the open stack"](https://forum.armbian.com/topic/61651-odroid-m1-rk3568-npu-on-the-open-stack-rocket-kernel-driver-mesa-teflon/)), + built on `accel/rocket` "with local fixes" atop the RK3588 Mesa merge request, + requiring **byte-level comparison against captured vendor command streams** to + work out weight-layout and CBUF differences from RK3588. Not merged upstream. + Confirms the general pattern: **porting Rocket to a new RKNPU generation is a + bespoke reverse-engineering project per SoC, not a recompile** โ€” the same + conclusion `npu-graphics-feasibility.md` already reached, now with two more + data points (RK3576, RK3568) supporting it. +- **RV1106/RV1103** โ€” zero hits in this research. No mainline coverage, no known + public fork, no known RE project targeting it specifically (unlike RK3568 and + RK3576, which both have named, in-progress efforts). This is the least-covered + tier of the RKNPU family in the open-source world today. + +### 4.3 Mesa Teflon (the TFLite delegate): entirely downstream of Rocket's coverage + +Merged into Mesa 24.1 ([Phoronix](https://www.phoronix.com/news/Gallium3D-Teflon-Merged), +[docs.mesa3d.org/teflon.html](https://docs.mesa3d.org/teflon.html)) as a Gallium3D +frontend for TensorFlow Lite. Per its own docs: **"Teflon only works with etnaviv +or rocket gallium drivers."** There is no Teflon path independent of a working +Rocket kernel driver underneath it โ€” so Teflon's real-world Rockchip coverage is +exactly Rocket's: solid on RK3588, experimental/WIP on RK3576 and (unofficially) +RK3568, absent for RV1106. + +### 4.4 Independent RE efforts on the closed regcmd ISA: exploratory, not a compiler + +[`phhusson/rknpu-reverse-engineering`](https://github.com/phhusson/rknpu-reverse-engineering) +("Because RKNPU only knows 4D") targets **RK3588** specifically, is in an +exploratory/documentation stage (structures like `regcfg_amount`/`regcmd_addr` +identified, DRM device enumerated) with **no compiled tool, compiler, or runtime +output**, and does not touch RV1106. This matches +`npu-graphics-feasibility.md`'s existing finding that the regcmd ISA is +undocumented outside Rockchip and reasoned-about only, not published โ€” no new +project has changed that for any SoC generation, let alone this one. + +### 4.5 Net conclusion (updated, still holds) + +Porting `rknpu.ko` to 6.18 gets you an open, working **kernel-level** path: the +char/DRM device, GEM memory management, clock/reset/power sequencing, and the +raw `DRM_IOCTL_RKNPU_SUBMIT` job-queue mechanism. It does **not** get you an open +way to *produce* a valid job for that queue. For RV1106 specifically, unlike +RK3588 (has mainline Rocket + Teflon) or even RK3576/RK3568 (have active, +imperfect RE efforts), **there is no open compiler, no open runtime, and no known +public reverse-engineering project of any kind.** Every real inference workload on +this NPU has to go through the closed pipeline +(`npu-graphics-feasibility.md ยง1`: RKNN-Toolkit2 on a PC โ†’ `.rknn` blob โ†’ +`librknnrt`/RKNN C API on-device) for the foreseeable future โ€” porting the kernel +driver is worth doing (it's real, bounded, evidence-backed work, same class as +RGA), but it does not change that reality, and shouldn't be scoped or sold as if +it does. + +--- + +## Layout (this directory, once work starts) + +``` +npu/ + PORT-PLAN.md this document + compat/ (to add) local stub headers for ยง2.4: + soc/rockchip/rockchip_iommu.h + soc/rockchip/rockchip_opp_select.h + soc/rockchip/rockchip_system_monitor.h + soc/rockchip/rockchip_ipa.h +``` +The vendor driver source itself is not duplicated here (same convention as +`clk/`, `pinctrl/`, `mach/` โ€” vendor source stays forward-ported in +`flare-edge/research/linux-6.18.46/` as a scratch tree; only the durable delta โ€” +compat shims, DT fragment, config fragment โ€” belongs in this repo, captured as a +patch series once M6 actually lands). diff --git a/kernel/rv1106-enablement/rga/PORT-PLAN.md b/kernel/rv1106-enablement/rga/PORT-PLAN.md new file mode 100644 index 0000000..c6660db --- /dev/null +++ b/kernel/rv1106-enablement/rga/PORT-PLAN.md @@ -0,0 +1,319 @@ +# M6 โ€” RGA 2D accelerator port to 6.18 + +Target: `/dev/rga` present and working on the self-built Linux 6.18.46 RV1106 port +(`../PORT-STATUS.md`, `../DRIVER-PARITY.md` row "RGA 2D (rga2)"), at parity with the +running vendor 5.10 kernel and with zero WardenOS UI-side code changes. + +## Decision: port the vendor char-dev driver. Do not touch mainline V4L2 rga. + +**WardenOS depends on the vendor `/dev/rga` ioctl uapi via librga's IM2D API โ€” not +V4L2.** `ui-src/src/warden/warden_rga.c` (`flare-edge/major-app-additions/`) includes +`` and `` and calls `wrapbuffer_fd_t()`, `improcess(..., IM_SYNC)` +and `querystring(RGA_VERSION)` (warden_rga.c:46-48, 377-390, 458, 614-631) โ€” the +Rockchip **im2d/librga** user-space API, which talks to the kernel purely through the +vendor char-device ioctl protocol (`RGA_IOC_REQUEST_SUBMIT` etc., see below). There is +no V4L2 (`/dev/videoN`, `VIDIOC_*`) code anywhere in WardenOS's RGA path, and no +V4L2-backed librga build exists upstream to switch to even if we wanted one. Porting the +vendor driver is therefore not a preference, it is the only path that keeps the existing +UI code (and its measured 20%โ†’8% CPU win, warden_rga.c:419) working unmodified. + +The candidate alternative โ€” adding an `rv1106` compatible string to mainline's +`drivers/media/platform/rockchip/rga/` V4L2 M2M driver โ€” is rejected on both counts the +task asked to check: + +1. **Register incompatibility.** Mainline's driver (current `torvalds/linux` master, + representative of 6.18) matches only `rockchip,rk3288-rga`, `rockchip,rk3399-rga` + (both mapped to one `rga2_hw` struct, `.features = FLIP | ROTATE | BG_COLOR` only โ€” + no scaling, no format conversion) and `rockchip,rk3588-rga3`. Per Rockchip's own FAQ + ("Although RGA on both RK3399 and RV1126 is RGA2-ENHANCE, their sub versions are + different" โ€” `librga/docs/Rockchip_FAQ_RGA_EN.md`, Q2.10) RK3399's RGA is already + **RGA2-ENHANCE**, a newer/richer core than RK3288's baseline RGA2 that mainline's + `rga2_hw` was written against โ€” so mainline's claim of rk3399 compatibility is itself + the reduced-feature subset, not proof of a shared register map. RV1106 is a *third* + point in that family: our own vendor driver (`rga_drv.c:1402-1409`) special-cases the + exact hardware version string `"3.3.87975"` to a distinct `rga2e_1106_data` table, + separate from the generic `rga2e_data` and from the IOMMU-capable `rga2e_iommu_data` + used by other RGA2-ENHANCE chips โ€” i.e. even Rockchip's own driver does not treat + RV1106 as register-identical to its closest relatives, let alone to RK3288/RK3399's + older baseline core. A pending upstream series (Jianfeng Liu, + `20240322052915.3507937-1-liujianfeng1994@gmail.com`, "media: rockchip: rga: Add + rk3568 support") explicitly states "RGA2 on rk3568 is the same core as RGA2 on + rk3288" โ€” confirming mainline's whole `rga2_hw` lineage targets the *old* core, not + the ENHANCE family RV1106 belongs to. +2. **Feature/uapi mismatch.** Even where mainline's driver runs, it only implements + flip/rotate/solid-fill via V4L2 M2M โ€” no im2d, no `/dev/rga` char device, no + `wrapbuffer_fd_t`/`improcess` surface. Adopting it would mean rewriting + `warden_rga.c`'s draw-unit and buffer-sync-copy paths against `VIDIOC_*` ioctls from + scratch, on hardware nobody has shown is even electrically the same core. + +## SDK source map (vendor 5.10, what we are porting) + +Real path: `flare-edge/sdk/sysdrv/source/kernel/drivers/video/rockchip/rga3/` +(`sdk` is a symlink to `/sdk`). This is Rockchip's +**"multicore" RGA driver** (`CONFIG_ROCKCHIP_MULTI_RGA`, module name `rga3.ko`/built-in +`rga3.o`) โ€” open source, `SPDX-License-Identifier: GPL-2.0`, driver version **1.3.1** +(`include/rga_drv.h:88-94`; the current `airockchip/librga` upstream ships a matching +**1.3.3** for RK3588 on 6.1/6.4 Armbian kernels โ€” same lineage, already proven building +against 6.x elsewhere). There are three other RGA driver trees in the SDK +(`drivers/video/rockchip/rga/`, `rga2/`, and the mainline-style +`drivers/media/platform/rockchip/rga/`) โ€” **none of these are built** for RV1106 +(`grep CONFIG_ROCKCHIP_MULTI_RGA arch/arm/configs/*rv1106*defconfig` is the only RGA +symbol set; `drivers/media/platform/rockchip/rga` isn't referenced by any rv1106 +defconfig or DT). Ignore them; `rga3/` is the one true source. + +| File | Role | +|---|---| +| `rga_drv.c` | probe/remove, misc char-dev (`"rga"` โ†’ `/dev/rga`), ioctl dispatch, IRQ, clocks, hrtimer scheduler tick | +| `rga_common.c`, `rga_job.c`, `rga_mm.c` | job/request lifecycle, buffer-descriptor management | +| `rga2_reg_info.c`, `rga3_reg_info.c`, `rga_hw_config.c` | per-core register programming (the actual "how to talk to the silicon" โ€” this is what a mainline single-core driver does NOT have for RGA2-ENHANCE) | +| `rga_dma_buf.c` | dma-buf import/map (`dma_buf_attach` + `dma_buf_map_attachment`, `iosys_map`) | +| `rga_iommu.c` | IOMMU attach โ€” **not exercised on RV1106** (see below) | +| `rga_fence.c` | `dma_fence`/`sync_file` for the async ioctl path โ€” **not needed**, WardenOS is sync-only (see Scope cuts) | +| `rga_debugger.c` | optional procfs/debugfs introspection | +| `include/rga.h` | uapi: ioctl numbers or `RGA_IOC_MAGIC='r'` โ€” `RGA_IOC_GET_DRVIER_VERSION`, `RGA_IOC_GET_HW_VERSION`, `RGA_IOC_IMPORT_BUFFER`, `RGA_IOC_REQUEST_CREATE/SUBMIT/CONFIG/CANCEL`, plus the legacy `RGA_BLIT_SYNC`(0x5017)/`RGA_BLIT_ASYNC`/`RGA_GET_VERSION` numeric ioctls. **This is the exact uapi `librga.so` (userspace) calls** โ€” nothing here changes; we port the kernel side only. | + +**DT match for our chip:** `rga2_dt_ids[]` (`rga_drv.c:1249-1258`) matches +`compatible = "rockchip,rga2_core0"` โ†’ `rk3588_rga2_match_data` (clock names +`"aclk_rga2","hclk_rga2","clk_rga2"`, `rga_drv.c:1195-1199`) โ€” this is **exactly** our +vendor DT node: +```c +// rv1106.dtsi:1154-1161 +rga2: rga@ff980000 { + compatible = "rockchip,rga2_core0"; + reg = <0xff980000 0x1000>; + interrupts = ; + clocks = <&cru ACLK_RGA2E>, <&cru HCLK_RGA2E>, <&cru CLK_CORE_RGA2E>; + clock-names = "aclk_rga2", "hclk_rga2", "clk_rga2"; + status = "disabled"; +}; +``` +No `resets =`, no `iommus =` on this node โ€” confirmed correct: `rga_drv.c` never calls +`reset_control_*` at all (unlike VOP, which needed named resets added for mainline, +`../display/README.md`), and probe only calls `rga_iommu_probe()` when +`scheduler->data->mmu == RGA_IOMMU` (`rga_drv.c:1420-1425`) โ€” for RV1106's matched +`rga2e_1106_data` (selected by exact HW-version-string match `"3.3.87975"`, +`rga_drv.c:1402-1409`) that table has no IOMMU, so the branch never runs. **RV1106's RGA2 +is physically-contiguous-only**, which is exactly why `warden_rga.c` allocates its +canvas/scanout buffers from `/dev/dma_heap/cma` (CMA dma-buf heap) rather than any +generic malloc โ€” no driver-side change needed here, the existing WardenOS allocation +strategy already matches the hardware constraint. + +**IRQ name:** `dev_driver_string(dev)` = the platform_driver's `.driver.name`, which for +the `rga2_dt_ids` match is literally `"rga2"` (`rga_drv.c:1478-1481`) โ€” this is the exact +string that shows up as `rga2` in `/proc/interrupts` on the running 5.10 image, confirming +the task's framing and that we're looking at the right driver. + +**DT enable status (5.10, for parity):** the shipped 86-Panel board DT already turns this +node on โ€” `rv1106g-luckfox-pico-86panel.dts` โ†’ `rv1106-luckfox-pico-86panel-ipc.dtsi` โ†’ +`#include "rv1106-evb.dtsi"` โ†’ `&rga2 { status = "okay"; };` (`rv1106-evb.dtsi:59-61`). +`sdk-patches/kernel/configs/flare-edge.config:21-29` (flare-edge repo) is the config +fragment: `CONFIG_ROCKCHIP_MULTI_RGA=y` (built-in, not `=m`, so devtmpfs creates +`/dev/rga` with no insmod step) + `CONFIG_DMABUF_HEAPS=y` + `CONFIG_DMABUF_HEAPS_CMA=y`. + +## Userspace: already built, nothing to port + +`librga.so`/`.a` for our exact target triple already exists prebuilt in the SDK: +`sdk/media/rga/release_rga_rv1106_arm-rockchip830-linux-uclibcgnueabihf/lib/librga.so` +(Apache-2.0 licensed headers, `rga.h`/`im2d.h`). It links against the kernel uapi in +`include/rga.h` above, which is unchanged by this port. Once `/dev/rga` exists with the +same ioctl numbers, the existing `librga.so` and the existing `warden_rga.c` binary/object +need **no changes** โ€” this is a pure kernel-side port. + +## API-delta checklist, 5.10 โ†’ 6.18 (checked against the real target tree) + +Checked directly against `flare-edge/research/linux-6.18.46/` (the tree M1-M3 already +build and boot on), not guessed: + +| # | Delta | Evidence | Fix | +|---|---|---|---| +| 1 | **`platform_driver.remove` is now `void`, not `int`.** | `linux-6.18.46/include/linux/platform_device.h:233`: `void (*remove)(struct platform_device *);`. Vendor `rga_drv_remove()` (`rga_drv.c`) returns `int`. Used by all 3 `platform_driver` structs (rga3_core0/1, rga2). | Change signature to `void`, drop the `return ret;`/`return 0;`, keep the body. Mechanical, 1 function + 3 struct references. | +| 2 | **`hrtimer_init()` is gone; `hrtimer_setup()` merges init+callback.** | `grep hrtimer_init` on `linux-6.18.46/include/linux/hrtimer.h` returns nothing callable โ€” only `hrtimer_setup()`/`hrtimer_setup_on_stack()` (`hrtimer.h:230-235`). Vendor `rga_drv.c:362-366` does the old split form: `hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); timer.function = hrtimer_handler;`. | `hrtimer_setup(&timer, hrtimer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL);` โ€” one call site, but it's the RGA scheduler tick (`rga_drv.c:327-371`), so build-clean is necessary but not sufficient: exercise it under load (the blit test below) since a hrtimer regression shows up as stalled/duplicate completions, not a compile error. **This is the delta most likely to bite in a way the compiler won't catch โ€” treat it as the highest-scrutiny item.** | +| 3 | `dma_buf_attach()` / `dma_buf_map_attachment()` | `linux-6.18.46/include/linux/dma-buf.h:571,588` โ€” signatures (`dmabuf, dev`) / (`attach, dir`) โ†’ `sg_table` are **unchanged** from what `rga_dma_buf.c:443,450,494,501` already calls. Vendor code already uses `struct iosys_map` (post-5.18 API) at `rga_dma_buf.c:398`, so it's already ahead of 5.10 baseline here. | No change needed; compile-verify only. | +| 4 | `class_create()` losing its `owner` arg (6.4) | Not applicable โ€” `rga3/` has **zero** `class_create` calls (grep across the whole dir + `include/`); it registers `/dev/rga` via `misc_register()` (`rga_drv.c:1520`), which needs no class. | Nothing to fix. (Flagged in the task brief as a risk; verified moot for this driver.) | +| 5 | IOMMU API drift (`iommu_domain_alloc`, `iommu_attach_device`, rockchip IOMMU v2) | `rga_iommu.c` uses `iommu_group_get`/`iommu_get_domain_for_dev` โ€” but as established above, **RV1106 never calls `rga_iommu_probe()`** (its match_data has no `RGA_IOMMU` flag). | Out of scope for RV1106; don't even need to build-fix `rga_iommu.c`'s IOMMU-attach path correctness, only that it compiles (dead code on our chip). | +| 6 | `proc_ops`/`debugfs_create_file` | `rga_debugger.c:499,553,597,605,633,686` already uses `struct proc_ops` (post-5.6) and plain `debugfs_create_file`/`proc_create_data` โ€” both stable, unchanged APIs in 6.18. | No change needed. Optional subsystem anyway (see Scope cuts). | +| 7 | GRF/syscon regmap lookups | None โ€” `rga3/` has zero `syscon`/`regmap`/`rockchip,grf` references, unlike VOP which needed the `grf_ctx` fix in `PORT-STATUS.md` M2 item 3. | Nothing to fix; simpler than the VOP port in this respect. | + +## Scope cuts (reduce port surface to exactly what WardenOS uses) + +- **Do not enable `CONFIG_ROCKCHIP_RGA_ASYNC`.** `warden_rga.c`'s own header comment is + explicit: "The RGA blit uses the synchronous path. No fences" (warden_rga.h:23), and + every call site uses `improcess(..., IM_SYNC)` (warden_rga.c:388,629). The + `Makefile`'s `rga3-$(CONFIG_ROCKCHIP_RGA_ASYNC) += rga_fence.o` means leaving this + config off **excludes `rga_fence.c` (dma_fence/sync_file) from the build entirely** โ€” + removing an entire API-delta surface (dma_fence context allocation, sync_file + lifetime) that WardenOS never exercises. Matches the vendor default (`ROCKCHIP_RGA_ASYNC` + defaults to `y` in Kconfig but is safe to turn off; verify nothing else in the SDK + userspace on this image needs it โ€” nothing does; MPP/ISP aren't used on the 86-Panel). +- **`CONFIG_ROCKCHIP_RGA_PROC_FS`/`_DEBUG_FS`/`_DEBUGGER`: leave off initially.** Useful + for bring-up (dumps registered buffers/jobs) but not required for `/dev/rga` to exist + or work; add later if debugging needs it. +- **Only `rga3/` (`CONFIG_ROCKCHIP_MULTI_RGA`) is ported** โ€” not `drivers/video/rockchip/rga/` + or `rga2/` (the older single-core drivers), matching the vendor 5.10 build exactly. + +## File / config / DT plan + +### 1. Kernel source (new directory in the 6.18 tree) + +`drivers/video/rockchip/` does not exist yet in `linux-6.18.46` (`ls` confirms). Create: + +``` +drivers/video/rockchip/Kconfig # source "drivers/video/rockchip/rga3/Kconfig" +drivers/video/rockchip/Makefile # obj-$(CONFIG_ROCKCHIP_MULTI_RGA) += rga3/ +drivers/video/rockchip/rga3/ # forward-ported rga_drv.c, rga_common.c, + # rga3_reg_info.c, rga2_reg_info.c, + # rga_hw_config.c, rga_mm.c, rga_dma_buf.c, + # rga_iommu.c, rga_policy.c, Kconfig, Makefile + # (rga_fence.c, rga_debugger.c omitted โ€” + # see Scope cuts) +``` + +Hook into the parent build (mirrors the vendor's own top-level wiring, +`sdk/.../drivers/video/{Kconfig,Makefile}`): +- `drivers/video/Kconfig`: add `source "drivers/video/rockchip/Kconfig"` +- `drivers/video/Makefile`: add `obj-y += rockchip/` + +`rga3/Makefile` (trimmed for the scope cuts above): +```make +# SPDX-License-Identifier: GPL-2.0 +ccflags-y += -I$(srctree)/$(src)/include +rga3-y := rga_drv.o rga_common.o rga3_reg_info.o rga_iommu.o rga_dma_buf.o \ + rga_job.o rga_hw_config.o rga2_reg_info.o rga_policy.o rga_mm.o +obj-$(CONFIG_ROCKCHIP_MULTI_RGA) += rga3.o +``` +(drops the `rga3-$(CONFIG_ROCKCHIP_RGA_ASYNC) += rga_fence.o` and +`rga3-$(CONFIG_ROCKCHIP_RGA_DEBUGGER) += rga_debugger.o` lines from the vendor Makefile.) + +Apply the two API-delta fixes from the table above (`platform_driver.remove` โ†’ void, +`hrtimer_init`+`.function=` โ†’ `hrtimer_setup()`) during the transplant, same +build-fix-build loop already used for `clk-rv1106.c`/`pinctrl-rockchip.c` +(`PORT-STATUS.md` M1). + +### 2. Kconfig fragment (defconfig / config fragment) + +``` +CONFIG_ROCKCHIP_MULTI_RGA=y +CONFIG_DMABUF_HEAPS=y +CONFIG_DMABUF_HEAPS_CMA=y +``` +(`CONFIG_DMABUF_HEAPS` is currently `# CONFIG_DMABUF_HEAPS is not set` in +`linux-6.18.46/.config` โ€” confirmed by grep โ€” so this is a required addition, not +already-on. `CONFIG_ARCH_ROCKCHIP=y` is already set.) `=y` not `=m`, matching the +vendor fragment's own rationale (`flare-edge.config:23`: builtin so devtmpfs creates +the node with no insmod step). + +### 3. Devicetree โ€” two additions to `dts/rv1106-warden.dts` + +**(a) Enable the node.** `rv1106.dtsi` is already `#include`d wholesale by +`rv1106-warden.dts` (`dts/README.md`), and it already carries the `rga2` node +byte-for-byte (clocks, IRQ, compatible) โ€” just disabled. Add the same one-line override +the vendor board DT uses: +```dts +&rga2 { + status = "okay"; +}; +``` +No clock/reset/iommu properties to add โ€” all three RGA2E clocks +(`HCLK_RGA2E`=269, `ACLK_RGA2E`=270, `CLK_CORE_RGA2E`=271) are **already wired** in the +ported `clk-rv1106.c` (`clk/clk-rv1106.c:924-930`, landed in M1, `PORT-STATUS.md`) off +the same `hclk_vo_root`/`aclk_vo_root` parents that VOP already uses successfully on +hardware (M4, verified) โ€” this is the lowest-risk clock story of any M6 driver. + +**(b) Add the CMA reserved-memory pool โ€” currently missing from the ported DT.** +`warden_rga.c`'s canvas allocator opens `/dev/dma_heap/cma` +(`warden_rga.c:70,269-274`), which requires a `linux,cma`/`shared-dma-pool` +reserved-memory node to exist. On the vendor 5.10 board DT this lives in +`rv1106-luckfox-pico-86panel-ipc.dtsi` (**not** `rv1106.dtsi`, and confirmed **not yet +present** in `dts/rv1106-warden.dts` โ€” grep for `reserved-memory`/`linux,cma` there +returns nothing): +```dts +// rv1106-luckfox-pico-86panel-ipc.dtsi:91-107 +reserved_memory: reserved-memory { + status = "okay"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + linux,cma { + status = "okay"; + compatible = "shared-dma-pool"; + inactive; + reusable; + size = <0xA00000>; /* 10 MiB */ + linux,cma-default; + }; +}; +``` +Port just the `linux,cma` child (the sibling `drm-logo`/`mmc_ecsd` reserved regions +belong to the display/boot-logo and eMMC-ECSD milestones respectively, not RGA) into +`rv1106-warden.dts`. Without this the RGA kernel driver itself will still probe and +`/dev/rga` will still appear (the driver has no CMA dependency of its own โ€” DMA-BUF +import works from any dma-buf exporter), but WardenOS's own canvas/scanout allocator +will fail open and the UI falls back to the CPU path silently (`warden_rga.c:269-274` +returns `-1` on `open()` failure) โ€” so this step is required for the *offload* to be +observably working, even though it isn't required for `/dev/rga` to exist. + +### 4. Rootfs / userspace + +None needed โ€” `librga.so`/`.a` for `rv1106_arm-rockchip830-linux-uclibcgnueabihf` +already ships in the SDK (`sdk/media/rga/release_rga_rv1106_.../lib/`) and is already +what the current Buildroot overlay installs and what `warden_rga.c` already links +against with `WARDEN_USE_RGA=1`. No rebuild of librga or of WardenOS's C code is +implied by this kernel port. + +## Verify steps + +1. **Build:** `rga3.o` compiles with zero warnings/errors against `linux-6.18.46` + (same evidentiary bar as `PORT-STATUS.md`'s M1 entries โ€” "COMPILES CLEAN"). +2. **Boot + node:** on warden-c8a3 (A/B `_b`-slot loop, `../docs/m2-boot-on-c8a3.md`), + confirm dmesg shows `rga2, irq = 87, match scheduler` and + `rga2 hardware loaded successfully, hw_version:3.3.87975.` (the exact version string + `rga_drv_probe` selects `rga2e_1106_data` on, `rga_drv.c:1403`) โ€” a different version + string here would mean the wrong match-data table and is a stop-ship signal. + `ls -l /dev/rga` exists with no manual `mknod`/`insmod`. +3. **CMA heap:** `ls /dev/dma_heap/cma` exists (needs step 3(b) above). +4. **Blit test (hardware-observable, no display needed):** the simplest self-contained + check is `librga`'s own CLI/test harness if the SDK ships one, or a ~20-line C + program using the already-present `librga.so`: allocate two small dma-buf CMA + buffers via `/dev/dma_heap/cma`, fill one with a known pattern, call + `improcess(src, dst, ..., IM_SYNC)` for a straight copy, and memcmp the result โ€” + this exercises exactly the ioctl path (`RGA_IOC_REQUEST_SUBMIT`/`RGA_BLIT_SYNC`) and + the hrtimer-driven completion path (API-delta #2) that WardenOS's real usage + exercises, without needing DRM/VOP (M4) to be finished first. +5. **On-target UI evidence (once M4/display lands):** boot the real WardenOS build with + `WARDEN_USE_RGA=1`, open the graph/Monitor page, confirm `warden_rga_available()` + reports true (`querystring(RGA_VERSION)` succeeds โ€” `warden_rga.c:458-466`) and the + Monitor page's "RGA" load metric moves during graph scroll (`warden_rga_load_pct()`, + `warden_rga.c:226-241`) โ€” the same on-target evidence bar as every other change in + this repo (`../../CLAUDE.md`: "UI/daemon changes are verified on a real panel"). + +## Summary of residual risk (PORT-VERIFY-class items) + +- **hrtimer_setup() correctness under load** (delta #2) โ€” compiles clean is not enough; + needs the blit-test loop run repeatedly/concurrently to rule out a scheduler-tick + regression. +- **CMA pool sizing** โ€” 10 MiB was sized against the 5.10 image's actual usage (graph + canvas + scanout mirror at 720ร—720ร—4B โ‰ˆ 2 MiB each); carry the same size unless a + future accounting shows it's tight. +- **Driver-parity table** (`../DRIVER-PARITY.md`) should move `RGA 2D (rga2)` from โฌœ to + ๐Ÿ”จ/โœ… as these steps land, same convention as every other M-milestone row. + +## Sources + +- Vendor SDK (primary evidence for all file/line citations above): + `flare-edge/sdk/sysdrv/source/kernel/drivers/video/rockchip/rga3/` and + `.../arch/arm/boot/dts/{rv1106.dtsi,rv1106-evb.dtsi,rv1106-luckfox-pico-86panel-ipc.dtsi}` +- WardenOS RGA integration: `flare-edge/major-app-additions/ui-src/src/warden/warden_rga.{c,h}`, + `flare-edge/major-app-additions/sdk-patches/kernel/configs/flare-edge.config` +- This port's own live status/conventions: `../PORT-STATUS.md`, `../DRIVER-PARITY.md`, + `../OVERNIGHT-PLAN.md`, `../display/README.md` (VOP port, same-family precedent), + `../clk/rv1106-cru.h`, `../clk/clk-rv1106.c` +- Target kernel tree (ground truth for the API-delta table): + `flare-edge/research/linux-6.18.46/{include/linux/platform_device.h,hrtimer.h,dma-buf.h}`, + `.config` +- [airockchip/librga](https://github.com/airockchip/librga) โ€” upstream userspace IM2D + library (Apache-2.0), RV1106 target support, 1.3.3 driver version on RK3588/6.1-6.4 +- [librga FAQ โ€” RGA hardware family notes](https://github.com/airockchip/librga/blob/main/docs/Rockchip_FAQ_RGA_EN.md) (Q2.10: RK3399/RV1126 both RGA2-ENHANCE, differing sub-versions, ROP cut on RV1126) +- [torvalds/linux โ€” drivers/media/platform/rockchip/rga/rga.c](https://raw.githubusercontent.com/torvalds/linux/master/drivers/media/platform/rockchip/rga/rga.c) โ€” mainline V4L2 driver's `of_device_id` table (rk3288-rga, rk3399-rga โ†’ `rga2_hw`; rk3588-rga3 โ†’ `rga3_hw`) +- [LWN โ€” "media: platform: rga: Add RGA3 support"](https://lwn.net/Articles/1041152/) โ€” RK3588 RGA3 mainlining, one `/dev/video` per core, no multicore scheduling in-kernel +- [lore.kernel.org โ€” "media: rockchip: rga: Add rk3568 support" (Jianfeng Liu)](https://lore.kernel.org/lkml/20240322052915.3507937-1-liujianfeng1994@gmail.com/) โ€” "RGA2 on rk3568 is the same core as RGA2 on rk3288" (confirms mainline's RGA2 lineage is the older/baseline core, not RGA2-ENHANCE) +- CNX Software, [Rockchip RK3588 mainline Linux support](https://www.cnx-software.com/2024/12/21/rockchip-rk3588-mainline-linux-support-current-status-and-future-work-for-2025/) โ€” RGA2 V4L2 mainlining timeline context diff --git a/kernel/rv1106-enablement/rtc/README.md b/kernel/rv1106-enablement/rtc/README.md new file mode 100644 index 0000000..4750bb3 --- /dev/null +++ b/kernel/rv1106-enablement/rtc/README.md @@ -0,0 +1,7 @@ +# RTC โ€” rockchip,rv1106-rtc + +`rtc-rockchip.c` โ€” the vendor internal-RTC driver (open source, from the SDK), +ported to 6.18. Only 5.10โ†’6.18 API delta: `rtc_register_device` โ†’ +`devm_rtc_register_device` (paired with the existing `devm_rtc_allocate_device`). +Enable `CONFIG_RTC_DRV_ROCKCHIP=y` + `&rtc { status="okay"; }`. Verified on +warden-c8a3: `/dev/rtc0` registers and reads. diff --git a/kernel/rv1106-enablement/rtc/rtc-rockchip.c b/kernel/rv1106-enablement/rtc/rtc-rockchip.c new file mode 100644 index 0000000..5c088a9 --- /dev/null +++ b/kernel/rv1106-enablement/rtc/rtc-rockchip.c @@ -0,0 +1,824 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022 Rockchip Electronics Co., Ltd + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* RTC_CTRL_REG bitfields */ + +#define RTC_REG(x) ((x)) +#define RTC_SET_SECONDS RTC_REG(0x0) +#define RTC_SET_MINUTES RTC_REG(0x4) +#define RTC_SET_HOURS RTC_REG(0x8) +#define RTC_SET_DAYS RTC_REG(0xc) +#define RTC_SET_MONTHS RTC_REG(0x10) +#define RTC_SET_YEARL RTC_REG(0x14) +#define RTC_SET_YEARH RTC_REG(0x18) +#define RTC_SET_WEEKS RTC_REG(0x1c) +#define RTC_ALARM_SECONDS RTC_REG(0x20) +#define RTC_ALARM_MINUTES RTC_REG(0x24) +#define RTC_ALARM_HOURS RTC_REG(0x28) +#define RTC_ALARM_DAYS RTC_REG(0x2c) +#define RTC_ALARM_MONTHS RTC_REG(0x30) +#define RTC_ALARM_YEARL RTC_REG(0x34) +#define RTC_ALARM_YEARH RTC_REG(0x38) +#define RTC_CTRL RTC_REG(0x3C) +#define RTC_STATUS0 RTC_REG(0x40) +#define RTC_STATUS1 RTC_REG(0x44) +#define RTC_INT0_EN RTC_REG(0x48) +#define RTC_INT1_EN RTC_REG(0x4c) +#define RTC_MSEC_CTRL RTC_REG(0x50) +#define RTC_MSEC_CNT RTC_REG(0x54) +#define RTC_COMP_H RTC_REG(0x58) +#define RTC_COMP_D RTC_REG(0x5c) +#define RTC_COMP_M RTC_REG(0x60) +#define RTC_ANALOG_CTRL RTC_REG(0x64) +#define RTC_ANALOG_TEST RTC_REG(0x68) +#define RTC_LDO_CTRL RTC_REG(0x6c) +#define RTC_XO_TRIM0 RTC_REG(0x70) +#define RTC_XO_TRIM1 RTC_REG(0x74) +#define RTC_VPTAT_TRIM RTC_REG(0x78) +#define RTC_ANALOG_EN RTC_REG(0x7c) +#define RTC_CLK32K_TEST RTC_REG(0x80) +#define RTC_TEST_ST RTC_REG(0x84) +#define RTC_TEST_LEN RTC_REG(0x88) +#define RTC_CNT_0 RTC_REG(0x8c) +#define RTC_CNT_1 RTC_REG(0x90) +#define RTC_CNT_2 RTC_REG(0x94) +#define RTC_CNT_3 RTC_REG(0x98) +#define RTC_MAX_REGISTER RTC_CNT_3 + +#define VI_GRF_VI_MISC_CON0 0x50000 +#define RTC_CLAMP_EN BIT(6) + +/* RTC_CTRL_REG bitfields */ +#define RTC_CTRL_REG_START_RTC BIT(0) +#define RTC_TIMEOUT (3000 * 1000) + +/* RK630 has a shadowed register for saving a "frozen" RTC time. + * When user setting "GET_TIME" to 1, the time will save in this shadowed + * register. If set "READSEL" to 1, user read rtc time register, actually + * get the time of that moment. If we need the real time, clr this bit. + */ +#define RTC_CTRL_REG_RTC_GET_TIME BIT(6) +#define RTC_CTRL_REG_RTC_READSEL_M BIT(7) +#define RTC_INT_REG_ALARM_EN BIT(7) +#define RTC_D2A_XO_EN BIT(0) +#define RTC_D2A_CLK_OUT_EN BIT(5) + +#define RTC_STATUS_MASK 0xFF + +#define SECONDS_REG_MSK 0x7F +#define MINUTES_REG_MAK 0x7F +#define HOURS_REG_MSK 0x3F +#define DAYS_REG_MSK 0x3F +#define MONTHS_REG_MSK 0x1F +#define YEARS_REG_MSK 0xFF +#define WEEKS_REG_MSK 0x7 + +#define RTC_VREF_INIT 0x40 + +#define CLK_32K_ENABLE BIT(5) +#define D2A_POR_REG_SEL1 BIT(4) +#define D2A_POR_REG_SEL0 BIT(1) + +#define NUM_TIME_REGS 8 +#define NUM_ALARM_REGS 7 + +#define DISABLE_ALARM_INT 0x3F +#define ENABLE_ALARM_INT 0xFF +#define ALARM_INT_STATUS BIT(4) + +#define CLK32K_TEST_EN BIT(0) +#define CLK32K_TEST_START BIT(0) +#define CLK32K_TEST_STATUS BIT(1) +#define CLK32K_TEST_DONE BIT(2) +#define CLK32K_TEST_LEN 1 + +#define CLK32K_COMP_DIR_ADD BIT(7) +#define CLK32K_COMP_EN BIT(2) +#define CLK32K_NO_COMP 0x1 + +#define CLK32K_TEST_REF_CLK 24000000 + +#define RTC_WRITE_MASK 0xc4522900 + +enum { + ROCKCHIP_RV1106_RTC = 1, +}; + +struct rockchip_rtc { + struct regmap *regmap; + struct rtc_device *rtc; + struct regmap *grf; + struct clk_bulk_data *clks; + int num_clks; + int irq; + unsigned int flag; + unsigned int mode; + struct delayed_work trim_work; + bool suspend_bypass; +}; + +static unsigned int rockchip_rtc_write(struct regmap *map, + u32 offset, u32 val) +{ + return regmap_write(map, offset, val | RTC_WRITE_MASK); +} + +static unsigned int rockchip_rtc_update_bits(struct regmap *map, + u32 offset, u32 mask, + u32 set) +{ + unsigned int val; + + regmap_read(map, offset, &val); + return regmap_write(map, offset, (val & ~mask) | set | RTC_WRITE_MASK); +} + +/* Read current time and date in RTC */ +static int rockchip_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct rockchip_rtc *rtc = dev_get_drvdata(dev); + u32 rtc_data[NUM_TIME_REGS]; + int ret; + int yearl, yearh; + + /* No shadowed registers, need read time three time to update time */ + ret = regmap_bulk_read(rtc->regmap, RTC_SET_SECONDS, + rtc_data, NUM_TIME_REGS); + if (ret) { + dev_err(dev, "Failed to bulk read rtc_data: %d\n", ret); + return ret; + } + ret = regmap_bulk_read(rtc->regmap, RTC_SET_SECONDS, + rtc_data, NUM_TIME_REGS); + if (ret) { + dev_err(dev, "Failed to bulk read rtc_data: %d\n", ret); + return ret; + } + ret = regmap_bulk_read(rtc->regmap, RTC_SET_SECONDS, + rtc_data, NUM_TIME_REGS); + if (ret) { + dev_err(dev, "Failed to bulk read rtc_data: %d\n", ret); + return ret; + } + + tm->tm_sec = bcd2bin(rtc_data[0] & SECONDS_REG_MSK); + tm->tm_min = bcd2bin(rtc_data[1] & MINUTES_REG_MAK); + tm->tm_hour = bcd2bin(rtc_data[2] & HOURS_REG_MSK); + tm->tm_mday = bcd2bin(rtc_data[3] & DAYS_REG_MSK); + tm->tm_mon = (bcd2bin(rtc_data[4] & MONTHS_REG_MSK)) - 1; + yearl = (bcd2bin(rtc_data[5] & YEARS_REG_MSK)); + yearh = (bcd2bin(rtc_data[6] & YEARS_REG_MSK)); + tm->tm_year = yearh * 100 + yearl + 100; + tm->tm_wday = bcd2bin(rtc_data[7] & WEEKS_REG_MSK); + + dev_dbg(dev, "RTC date/time %4d-%02d-%02d(%d) %02d:%02d:%02d\n", + 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, + tm->tm_wday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + return ret; +} + +/* Set current time and date in RTC */ +static int rockchip_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct rockchip_rtc *rtc = dev_get_drvdata(dev); + u32 rtc_data[NUM_TIME_REGS]; + int ret, status = 0; + int yearl, yearh; + + dev_dbg(dev, "set RTC date/time %4d-%02d-%02d(%d) %02d:%02d:%02d\n", + 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, + tm->tm_wday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + rtc_data[0] = bin2bcd(tm->tm_sec) | RTC_WRITE_MASK; + rtc_data[1] = bin2bcd(tm->tm_min) | RTC_WRITE_MASK; + rtc_data[2] = bin2bcd(tm->tm_hour) | RTC_WRITE_MASK; + rtc_data[3] = bin2bcd(tm->tm_mday) | RTC_WRITE_MASK; + rtc_data[4] = bin2bcd(tm->tm_mon + 1) | RTC_WRITE_MASK; + if (tm->tm_year > 199) { + yearh = (tm->tm_year - 100) / 100; + yearl = tm->tm_year - 100 - yearh * 100; + } else { + yearh = 0; + yearl = tm->tm_year - 100 - yearh * 100; + } + rtc_data[5] = bin2bcd(yearl) | RTC_WRITE_MASK; + rtc_data[6] = bin2bcd(yearh) | RTC_WRITE_MASK; + rtc_data[7] = bin2bcd(tm->tm_wday) | RTC_WRITE_MASK; + + /* Stop RTC while updating the RTC registers */ + ret = rockchip_rtc_update_bits(rtc->regmap, RTC_CTRL, + RTC_CTRL_REG_START_RTC, 0); + if (ret) { + dev_err(dev, "Failed to update RTC control: %d\n", ret); + return ret; + } + + ret = regmap_read_poll_timeout(rtc->regmap, RTC_STATUS1, status, + !(status & RTC_CTRL_REG_START_RTC), + 0, RTC_TIMEOUT); + if (ret) + dev_err(dev, + "%s:timeout Update RTC_STATUS1 : %d\n", + __func__, ret); + + ret = regmap_bulk_write(rtc->regmap, RTC_SET_SECONDS, + rtc_data, NUM_TIME_REGS); + if (ret) { + dev_err(dev, "Failed to bull write rtc_data: %d\n", ret); + return ret; + } + + /* Start RTC again */ + ret = rockchip_rtc_update_bits(rtc->regmap, RTC_CTRL, + RTC_CTRL_REG_RTC_READSEL_M | + RTC_CTRL_REG_START_RTC, + RTC_CTRL_REG_RTC_READSEL_M | + RTC_CTRL_REG_START_RTC); + if (ret) { + dev_err(dev, "Failed to update RTC control: %d\n", ret); + return ret; + } + + ret = regmap_read_poll_timeout(rtc->regmap, RTC_STATUS1, status, + (status & RTC_CTRL_REG_START_RTC), + 0, RTC_TIMEOUT); + if (ret) + dev_err(dev, + "%s:timeout Update RTC_STATUS1 : %d\n", + __func__, ret); + + return 0; +} + +/* Read alarm time and date in RTC */ +static int rockchip_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rockchip_rtc *rtc = dev_get_drvdata(dev); + u32 alrm_data[NUM_ALARM_REGS]; + u32 int_reg; + int yearl, yearh; + int ret; + + ret = regmap_bulk_read(rtc->regmap, + RTC_ALARM_SECONDS, + alrm_data, NUM_ALARM_REGS); + if (ret) { + dev_err(dev, "Failed to read RTC alarm date REG: %d\n", ret); + return ret; + } + + alrm->time.tm_sec = bcd2bin(alrm_data[0] & SECONDS_REG_MSK); + alrm->time.tm_min = bcd2bin(alrm_data[1] & MINUTES_REG_MAK); + alrm->time.tm_hour = bcd2bin(alrm_data[2] & HOURS_REG_MSK); + alrm->time.tm_mday = bcd2bin(alrm_data[3] & DAYS_REG_MSK); + alrm->time.tm_mon = (bcd2bin(alrm_data[4] & MONTHS_REG_MSK)) - 1; + yearl = (bcd2bin(alrm_data[5] & YEARS_REG_MSK)); + yearh = (bcd2bin(alrm_data[6] & YEARS_REG_MSK)); + alrm->time.tm_year = yearh * 100 + yearl + 100; + + ret = regmap_read(rtc->regmap, RTC_INT0_EN, &int_reg); + if (ret) { + dev_err(dev, "Failed to read RTC INT REG: %d\n", ret); + return ret; + } + + dev_dbg(dev, + "alrm read RTC date/time %4d-%02d-%02d(%d) %02d:%02d:%02d\n", + 1900 + alrm->time.tm_year, alrm->time.tm_mon + 1, + alrm->time.tm_mday, alrm->time.tm_wday, alrm->time.tm_hour, + alrm->time.tm_min, alrm->time.tm_sec); + + alrm->enabled = (int_reg & RTC_INT_REG_ALARM_EN) ? 1 : 0; + + return 0; +} + +static int rockchip_rtc_stop_alarm(struct rockchip_rtc *rtc) +{ + int ret; + + ret = rockchip_rtc_write(rtc->regmap, RTC_INT0_EN, 0); + + return ret; +} + +static int rockchip_rtc_start_alarm(struct rockchip_rtc *rtc) +{ + int ret; + + ret = rockchip_rtc_write(rtc->regmap, RTC_STATUS0, RTC_STATUS_MASK); + ret = rockchip_rtc_write(rtc->regmap, RTC_STATUS0, 0); + ret = rockchip_rtc_write(rtc->regmap, RTC_INT0_EN, ENABLE_ALARM_INT); + + return ret; +} + +static int rockchip_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct rockchip_rtc *rtc = dev_get_drvdata(dev); + u32 alrm_data[NUM_ALARM_REGS]; + int yearl, yearh; + int ret; + + ret = rockchip_rtc_stop_alarm(rtc); + if (ret) { + dev_err(dev, "Failed to stop alarm: %d\n", ret); + return ret; + } + dev_dbg(dev, + "alrm set RTC date/time %4d-%02d-%02d(%d) %02d:%02d:%02d\n", + 1900 + alrm->time.tm_year, alrm->time.tm_mon + 1, + alrm->time.tm_mday, alrm->time.tm_wday, alrm->time.tm_hour, + alrm->time.tm_min, alrm->time.tm_sec); + + alrm_data[0] = bin2bcd(alrm->time.tm_sec) | RTC_WRITE_MASK; + alrm_data[1] = bin2bcd(alrm->time.tm_min) | RTC_WRITE_MASK; + alrm_data[2] = bin2bcd(alrm->time.tm_hour) | RTC_WRITE_MASK; + alrm_data[3] = bin2bcd(alrm->time.tm_mday) | RTC_WRITE_MASK; + alrm_data[4] = bin2bcd(alrm->time.tm_mon + 1) | RTC_WRITE_MASK; + if (alrm->time.tm_year > 199) { + yearh = (alrm->time.tm_year - 100) / 100; + yearl = alrm->time.tm_year - 100 - yearh * 100; + } else { + yearh = 0; + yearl = alrm->time.tm_year - 100 - yearh * 100; + } + alrm_data[5] = bin2bcd(yearl) | RTC_WRITE_MASK; + alrm_data[6] = bin2bcd(yearh) | RTC_WRITE_MASK; + + ret = regmap_bulk_write(rtc->regmap, + RTC_ALARM_SECONDS, + alrm_data, NUM_ALARM_REGS); + if (ret) { + dev_err(dev, "Failed to bulk write: %d\n", ret); + return ret; + } + if (alrm->enabled) { + ret = rockchip_rtc_start_alarm(rtc); + if (ret) { + dev_err(dev, "Failed to start alarm: %d\n", ret); + return ret; + } + } + return 0; +} + +static int rockchip_rtc_alarm_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct rockchip_rtc *rtc = dev_get_drvdata(dev); + + if (enabled) + return rockchip_rtc_start_alarm(rtc); + + return rockchip_rtc_stop_alarm(rtc); +} + +/* + * We will just handle setting the frequency and make use the framework for + * reading the periodic interrupts. + * + */ +static irqreturn_t rockchip_rtc_alarm_irq(int irq, void *data) +{ + struct rockchip_rtc *rtc = data; + int ret, status; + + ret = regmap_read(rtc->regmap, RTC_STATUS0, &status); + if (ret) { + pr_err("Failed to read RTC INT REG: %d\n", ret); + return ret; + } + + ret = rockchip_rtc_write(rtc->regmap, RTC_STATUS0, status); + if (ret) { + pr_err("%s:Failed to update RTC status: %d\n", __func__, ret); + return ret; + } + if (status & ALARM_INT_STATUS) { + pr_info("Alarm by: %s\n", __func__); + rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF); + } + + return IRQ_HANDLED; +} + +static const struct rtc_class_ops rockchip_rtc_ops = { + .read_time = rockchip_rtc_read_time, + .set_time = rockchip_rtc_set_time, + .read_alarm = rockchip_rtc_readalarm, + .set_alarm = rockchip_rtc_setalarm, + .alarm_irq_enable = rockchip_rtc_alarm_irq_enable, +}; + +/* + * Due to the analog generator 32k clock affected by + * temperature, voltage, clock precision need test + * with the environment change. In rtc test, + * use 24M clock as reference clock to measure the 32k clock. + * Before start test 32k clock, we should enable clk32k test(0x80), + * and configure test length, when rtc test done(0x84[2]), + * latch the 24M clock domain counter, + * and read out the counter from rtc_test + * registers(0x8c~0x98) via apb bus. + * In RTC digital design, we set three level compensation, + * the compensation value due to the + * RTC 32k clock test result, and if we need compensation, + * we need configure the compensation enable bit. + * Comp every hour, compensation at last minute every hour, + * and support add time and sub time by the MSB bit. + * Comp every day, compensation at last minute in last hour every day, + * and support add time and sub time by the MSB bit. + * Comp every month, compensation at last minute + * in last hour in last day every month, + * and support add time and sub time by the MSB bit. + */ +static void rockchip_rtc_compensation_delay_work(struct work_struct *work) +{ + struct rockchip_rtc *rtc = container_of(work, struct rockchip_rtc, trim_work.work); + u64 camp; + u32 count[4], counts, g_ref, tcamp; + int ret, done = 0, trim_dir, c_hour, + c_day, c_det_day, c_mon, c_det_mon; + + ret = rockchip_rtc_update_bits(rtc->regmap, RTC_CLK32K_TEST, + CLK32K_TEST_EN, CLK32K_TEST_EN); + if (ret) { + pr_err("%s:Failed to update RTC CLK32K TEST: %d\n", + __func__, ret); + return; + } + + ret = rockchip_rtc_write(rtc->regmap, RTC_TEST_LEN, + CLK32K_TEST_LEN); + if (ret) { + pr_err("%s:Failed to update RTC CLK32K TEST LEN: %d\n", + __func__, ret); + return; + } + + ret = rockchip_rtc_update_bits(rtc->regmap, RTC_TEST_ST, + CLK32K_TEST_START, + CLK32K_TEST_START); + if (ret) { + pr_err("%s:Failed to update RTC CLK32K TEST STATUS : %d\n", + __func__, ret); + return; + } + ret = regmap_read_poll_timeout(rtc->regmap, RTC_TEST_ST, done, + (done & CLK32K_TEST_DONE), 20000, RTC_TIMEOUT); + if (ret) + pr_err("%s:timeout waiting for RTC TEST STATUS : %d\n", + __func__, ret); + + ret = regmap_bulk_read(rtc->regmap, + RTC_CNT_0, + count, 4); + if (ret) { + pr_err("Failed to read RTC count REG: %d\n", ret); + return; + } + + counts = count[0] | (count[1] << 8) | + (count[2] << 16) | (count[3] << 24); + g_ref = CLK32K_TEST_REF_CLK * (CLK32K_TEST_LEN + 1); + + if (counts > g_ref) { + trim_dir = 0; + camp = 36ULL * (32768 * (counts - g_ref)); + do_div(camp, (g_ref / 100)); + } else { + trim_dir = CLK32K_COMP_DIR_ADD; + camp = 36ULL * (32768 * (g_ref - counts)); + do_div(camp, (g_ref / 100)); + } + tcamp = (u32)camp; + c_hour = DIV_ROUND_CLOSEST(tcamp, 32768); + c_day = DIV_ROUND_CLOSEST(24 * tcamp, 32768); + c_mon = DIV_ROUND_CLOSEST(30 * 24 * tcamp, 32768); + + if (c_hour > 1) + rockchip_rtc_write(rtc->regmap, RTC_COMP_H, bin2bcd((c_hour - 1)) | trim_dir); + else + rockchip_rtc_write(rtc->regmap, RTC_COMP_H, CLK32K_NO_COMP); + + if (c_day > c_hour * 23) { + c_det_day = c_day - c_hour * 23; + trim_dir = CLK32K_COMP_DIR_ADD; + } else { + c_det_day = c_hour * 24 - c_day; + trim_dir = 0; + } + + if (c_det_day > 1) + rockchip_rtc_write(rtc->regmap, RTC_COMP_D, + bin2bcd((c_det_day - 1)) | trim_dir); + else + rockchip_rtc_write(rtc->regmap, RTC_COMP_D, CLK32K_NO_COMP); + + if (c_mon > (29 * c_day + 23 * c_hour)) { + c_det_mon = c_mon - 29 * c_day - 23 * c_hour; + trim_dir = CLK32K_COMP_DIR_ADD; + } else { + c_det_mon = 29 * c_day + 23 * c_hour - c_mon; + trim_dir = 0; + } + + if (c_det_mon) + rockchip_rtc_write(rtc->regmap, RTC_COMP_M, + bin2bcd((c_det_mon - 1)) | trim_dir); + else + rockchip_rtc_write(rtc->regmap, RTC_COMP_M, CLK32K_NO_COMP); + + ret = regmap_read(rtc->regmap, RTC_CTRL, &done); + if (ret) { + pr_err("Failed to read RTC_CTRL: %d\n", ret); + return; + } + + ret = rockchip_rtc_update_bits(rtc->regmap, RTC_CTRL, + CLK32K_COMP_EN, CLK32K_COMP_EN); + if (ret) { + pr_err("%s:Failed to update RTC CTRL : %d\n", __func__, ret); + return; + } + return; +} + +static bool rockchip_rtc_is_trimed(struct rockchip_rtc *rtc) +{ + int ret, comp_done; + + ret = regmap_read(rtc->regmap, RTC_CTRL, &comp_done); + if (ret) { + pr_err("%s: Failed to read RTC_CTRL: %d\n", __func__, ret); + return false; + } + return (comp_done & CLK32K_COMP_EN) == CLK32K_COMP_EN; +} + +static void rockchip_rtc_trim_start(struct rockchip_rtc *rtc) +{ + if (!rockchip_rtc_is_trimed(rtc)) + queue_delayed_work(system_long_wq, &rtc->trim_work, + msecs_to_jiffies(5000)); +} + +static void __maybe_unused rockchip_rtc_trim_close(struct rockchip_rtc *rtc) +{ + if (!rockchip_rtc_is_trimed(rtc)) + cancel_delayed_work_sync(&rtc->trim_work); +} + +/* Enable the alarm if it should be enabled (in case it was disabled to + * prevent use as a wake source). + */ +#ifdef CONFIG_PM_SLEEP +/* Turn off the alarm if it should not be a wake source. */ +static int rockchip_rtc_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rockchip_rtc *rtc = dev_get_drvdata(&pdev->dev); + + if (rtc->suspend_bypass) + return 0; + + if (device_may_wakeup(dev)) + enable_irq_wake(rtc->irq); + + rockchip_rtc_trim_close(rtc); + + if (rtc->grf) { + switch (rtc->mode) { + case ROCKCHIP_RV1106_RTC: + regmap_write(rtc->grf, VI_GRF_VI_MISC_CON0, + (RTC_CLAMP_EN << 16)); + break; + default: + return -EINVAL; + } + } + clk_bulk_disable_unprepare(rtc->num_clks, rtc->clks); + + return 0; +} + +/* Enable the alarm if it should be enabled (in case it was disabled to + * prevent use as a wake source). + */ +static int rockchip_rtc_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rockchip_rtc *rtc = dev_get_drvdata(&pdev->dev); + int ret; + + if (rtc->suspend_bypass) + return 0; + + if (device_may_wakeup(dev)) + disable_irq_wake(rtc->irq); + + if (rtc->grf) { + switch (rtc->mode) { + case ROCKCHIP_RV1106_RTC: + regmap_write(rtc->grf, VI_GRF_VI_MISC_CON0, + (RTC_CLAMP_EN << 16) | RTC_CLAMP_EN); + break; + default: + return -EINVAL; + } + } + ret = clk_bulk_prepare_enable(rtc->num_clks, rtc->clks); + if (ret) { + dev_err(dev, "Cannot enable clock.\n"); + return ret; + } + rockchip_rtc_trim_start(rtc); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(rockchip_rtc_pm_ops, + rockchip_rtc_suspend, rockchip_rtc_resume); + +static const struct of_device_id rockchip_rtc_of_match[] = { + { + .compatible = "rockchip,rv1106-rtc", + .data = (void *)ROCKCHIP_RV1106_RTC + }, + {}, +}; +MODULE_DEVICE_TABLE(of, rockchip_rtc_of_match); + +static void rockchip_rtc_clk_disable(void *data) +{ + struct rockchip_rtc *rtc = data; + + clk_bulk_disable_unprepare(rtc->num_clks, rtc->clks); +} + +static int rockchip_rtc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct rockchip_rtc *rtc; + int ret; + struct rtc_time tm_read, tm = { + .tm_wday = 0, + .tm_year = 121, + .tm_mon = 0, + .tm_mday = 1, + .tm_hour = 12, + .tm_min = 0, + .tm_sec = 0, + }; + + rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL); + if (!rtc) + return -ENOMEM; + + rtc->regmap = device_node_to_regmap(np); + if (IS_ERR(rtc->regmap)) + return dev_err_probe(dev, PTR_ERR(rtc->regmap), + "no regmap available\n"); + + rtc->mode = (uintptr_t)device_get_match_data(dev); + rtc->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + if (IS_ERR(rtc->grf)) { + dev_warn(dev, "Missing rockchip,grf property\n"); + rtc->grf = NULL; + } else { + switch (rtc->mode) { + case ROCKCHIP_RV1106_RTC: + regmap_write(rtc->grf, VI_GRF_VI_MISC_CON0, + (RTC_CLAMP_EN << 16) | RTC_CLAMP_EN); + break; + default: + return -EINVAL; + } + } + + platform_set_drvdata(pdev, rtc); + + rtc->num_clks = devm_clk_bulk_get_all(&pdev->dev, &rtc->clks); + if (rtc->num_clks < 1) + return -ENODEV; + ret = clk_bulk_prepare_enable(rtc->num_clks, rtc->clks); + if (ret) + return dev_err_probe(dev, ret, "Cannot enable clock.\n"); + ret = devm_add_action_or_reset(dev, rockchip_rtc_clk_disable, rtc); + if (ret) + return dev_err_probe(dev, ret, + "Failed to add clk disable action."); + + ret = rockchip_rtc_update_bits(rtc->regmap, RTC_VPTAT_TRIM, + D2A_POR_REG_SEL1 | + CLK_32K_ENABLE, + D2A_POR_REG_SEL1 | + CLK_32K_ENABLE); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to write RTC_VPTAT_TRIM\n"); + ret = rockchip_rtc_update_bits(rtc->regmap, RTC_ANALOG_EN, + D2A_POR_REG_SEL0, + 0x00); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to write RTC_ANALOG_EN\n"); + + ret = rockchip_rtc_update_bits(rtc->regmap, RTC_LDO_CTRL, + RTC_D2A_XO_EN, + RTC_D2A_XO_EN); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to update RTC_LDO_CTRL\n"); + + ret = rockchip_rtc_update_bits(rtc->regmap, RTC_ANALOG_EN, + RTC_D2A_CLK_OUT_EN, + RTC_D2A_CLK_OUT_EN); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to update RTC_ANALOG_EN\n"); + + /* start rtc running by default, and use shadowed timer. */ + ret = rockchip_rtc_update_bits(rtc->regmap, RTC_CTRL, + RTC_CTRL_REG_START_RTC | + RTC_CTRL_REG_RTC_READSEL_M, + RTC_CTRL_REG_RTC_READSEL_M | + RTC_CTRL_REG_START_RTC); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to update RTC control\n"); + + ret = rockchip_rtc_write(rtc->regmap, RTC_STATUS0, RTC_STATUS_MASK); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to write RTC status0\n"); + + ret = rockchip_rtc_write(rtc->regmap, RTC_STATUS0, 0); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to write RTC status0\n"); + + device_init_wakeup(&pdev->dev, 1); + + rockchip_rtc_read_time(&pdev->dev, &tm_read); + if (rtc_valid_tm(&tm_read) != 0) + rockchip_rtc_set_time(&pdev->dev, &tm); + + rtc->rtc = devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(rtc->rtc)) + return PTR_ERR(rtc->rtc); + + rtc->rtc->ops = &rockchip_rtc_ops; + + rtc->irq = platform_get_irq(pdev, 0); + if (rtc->irq < 0) + return dev_err_probe(&pdev->dev, rtc->irq, "No IRQ resource\n"); + + /* request alarm irq of rtc */ + ret = devm_request_threaded_irq(&pdev->dev, rtc->irq, NULL, + &rockchip_rtc_alarm_irq, IRQF_ONESHOT, + "RTC alarm", rtc); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to request alarm IRQ %d\n", + rtc->irq); + + /* If rtc 32k used as time for deep sleep, the rtc suspend func bypass do nothing. */ + rtc->suspend_bypass = device_property_read_bool(&pdev->dev, + "rockchip,rtc-suspend-bypass"); + + INIT_DELAYED_WORK(&rtc->trim_work, rockchip_rtc_compensation_delay_work); + rockchip_rtc_trim_start(rtc); + + return devm_rtc_register_device(rtc->rtc); +} + +static struct platform_driver rockchip_rtc_driver = { + .probe = rockchip_rtc_probe, + .driver = { + .name = "rockchip-rtc", + .pm = &rockchip_rtc_pm_ops, + .of_match_table = rockchip_rtc_of_match, + }, +}; + +module_platform_driver(rockchip_rtc_driver); + +MODULE_DESCRIPTION("RTC driver for the rockchip"); +MODULE_AUTHOR("Zhang Qing "); +MODULE_LICENSE("GPL"); diff --git a/kernel/rv1106-enablement/thermal/rv1106_tsadc.c.frag b/kernel/rv1106-enablement/thermal/rv1106_tsadc.c.frag new file mode 100644 index 0000000..4ab575f --- /dev/null +++ b/kernel/rv1106-enablement/thermal/rv1106_tsadc.c.frag @@ -0,0 +1,40 @@ +/* rv1106 tsadc (TSADCV9 + VOGRF) register defs, ported from vendor */ +#define TSADCV9_Q_MAX 0x210 +#define TSADCV9_FLOW_CON 0x218 +#define TSADCV9_AUTO_SRC (0x10001 << 0) +#define TSADCV9_PD_MODE (0x10001 << 4) +#define TSADCV9_Q_MAX_VAL (0xffff0400 << 0) +#define RV1106_VOGRF_TSADC_CON 0x6000C +#define RV1106_VOGRF_TSADC_TSEN (0x10001 << 8) +#define RV1106_VOGRF_TSADC_ANA (0xff0007 << 0) + + +/** + * struct tsadc_table - code to temperature conversion table + * @code: the value of adc channel + * @temp: the temperature + * Note: + * code to temperature mapping of the temperature sensor is a piece wise linear + * curve.Any temperature, code faling between to 2 give temperatures can be + * linearly interpolated. + * Code to Temperature mapping should be updated based on manufacturer results. + */ +struct tsadc_table { + u32 code; + int temp; +}; + +static const struct tsadc_table rv1108_table[] = { + {0, -40000}, + {374, -40000}, + {382, -35000}, + {389, -30000}, + {397, -25000}, + {405, -20000}, + {413, -15000}, + {421, -10000}, + {429, -5000}, + {436, 0}, + {444, 5000}, + {452, 10000}, + {460, 15000}, diff --git a/kernel/rv1106-enablement/thermal/rv1106_tsadc_data.c.frag b/kernel/rv1106-enablement/thermal/rv1106_tsadc_data.c.frag new file mode 100644 index 0000000..4201626 --- /dev/null +++ b/kernel/rv1106-enablement/thermal/rv1106_tsadc_data.c.frag @@ -0,0 +1,62 @@ +/* --- rv1106 tsadc port (from vendor) --- */ +static const struct tsadc_table rv1106_code_table[] = { + {0, MIN_TEMP}, + {363, MIN_TEMP}, + {396, -40000}, + {504, 25000}, + {605, 85000}, + {673, 125000}, + {758, MAX_TEMP}, + {TSADCV2_DATA_MASK, MAX_TEMP}, +}; + +static void rk_tsadcv9_initialize(struct regmap *grf, void __iomem *regs, + enum tshut_polarity tshut_polarity) +{ + regmap_write(grf, RV1106_VOGRF_TSADC_CON, RV1106_VOGRF_TSADC_TSEN); + udelay(10); + regmap_write(grf, RV1106_VOGRF_TSADC_CON, RV1106_VOGRF_TSADC_ANA); + udelay(100); + + writel_relaxed(TSADCV2_AUTO_PERIOD_TIME, regs + TSADCV3_AUTO_PERIOD); + writel_relaxed(TSADCV2_AUTO_PERIOD_TIME, + regs + TSADCV3_AUTO_PERIOD_HT); + writel_relaxed(TSADCV2_HIGHT_INT_DEBOUNCE_COUNT, + regs + TSADCV3_HIGHT_INT_DEBOUNCE); + writel_relaxed(TSADCV2_HIGHT_TSHUT_DEBOUNCE_COUNT, + regs + TSADCV3_HIGHT_TSHUT_DEBOUNCE); + writel_relaxed(TSADCV9_AUTO_SRC, regs + TSADCV2_INT_PD); + writel_relaxed(TSADCV9_PD_MODE, regs + TSADCV9_FLOW_CON); + writel_relaxed(TSADCV9_Q_MAX_VAL, regs + TSADCV9_Q_MAX); + if (tshut_polarity == TSHUT_HIGH_ACTIVE) + writel_relaxed(TSADCV2_AUTO_TSHUT_POLARITY_HIGH | + TSADCV2_AUTO_TSHUT_POLARITY_MASK, + regs + TSADCV2_AUTO_CON); + else + writel_relaxed(TSADCV2_AUTO_TSHUT_POLARITY_MASK, + regs + TSADCV2_AUTO_CON); + writel_relaxed(TSADCV3_AUTO_Q_SEL_EN | (TSADCV3_AUTO_Q_SEL_EN << 16), + regs + TSADCV2_AUTO_CON); +} + +static const struct rockchip_tsadc_chip rv1106_tsadc_data = { + /* top, big_core0, big_core1, little_core, center, gpu, npu */ + .chn_id[SENSOR_CPU] = 0, /* cpu sensor is channel 0 */ + .chn_num = 1, /* seven channels for tsadc */ + .tshut_mode = TSHUT_MODE_CRU, /* default TSHUT via CRU */ + .tshut_polarity = TSHUT_LOW_ACTIVE, /* default TSHUT LOW ACTIVE */ + .tshut_temp = 95000, + .initialize = rk_tsadcv9_initialize, + .irq_ack = rk_tsadcv4_irq_ack, + .control = rk_tsadcv4_control, + .get_temp = rk_tsadcv4_get_temp, + .set_alarm_temp = rk_tsadcv3_alarm_temp, + .set_tshut_temp = rk_tsadcv3_tshut_temp, + .set_tshut_mode = rk_tsadcv4_tshut_mode, + .table = { + .id = rv1106_code_table, + .length = ARRAY_SIZE(rv1106_code_table), + .data_mask = TSADCV2_DATA_MASK, + .mode = ADC_INCREMENT, + }, +}; diff --git a/kernel/rv1106-enablement/thermal/rv1106_tsadc_macros.c.frag b/kernel/rv1106-enablement/thermal/rv1106_tsadc_macros.c.frag new file mode 100644 index 0000000..3d0ef07 --- /dev/null +++ b/kernel/rv1106-enablement/thermal/rv1106_tsadc_macros.c.frag @@ -0,0 +1,9 @@ +/* rv1106 tsadc (TSADCV9 + VOGRF) register defs, ported from vendor */ +#define TSADCV9_Q_MAX 0x210 +#define TSADCV9_FLOW_CON 0x218 +#define TSADCV9_AUTO_SRC (0x10001 << 0) +#define TSADCV9_PD_MODE (0x10001 << 4) +#define TSADCV9_Q_MAX_VAL (0xffff0400 << 0) +#define RV1106_VOGRF_TSADC_CON 0x6000C +#define RV1106_VOGRF_TSADC_TSEN (0x10001 << 8) +#define RV1106_VOGRF_TSADC_ANA (0xff0007 << 0)