kernel: RTC + tsadc ported (open-source, from vendor source); npu/rga plans
RTC: ported the vendor rockchip,rv1106-rtc driver (rtc-rockchip.c) to 6.18 — one API delta (rtc_register_device -> devm_rtc_register_device). Verified on warden-c8a3: /dev/rtc0 registers and reads. tsadc thermal: ported the rv1106 data + rk_tsadcv9_initialize + the TSADCV9/VOGRF macros + code table from the vendor; adapted .chn_id[SENSOR_CPU] -> .chn_offset for 6.18. Verified: thermal_zone0 "soc-thermal" reads 39.8 C. i2s-tdm: rv1126 fallback compatible added (DAI builds; sound card needs the acodec port next). saradc still -22 (clk-rv1106 SARADC divider — deferred). Includes the NPU + RGA open-source port plans (research): rknpu kernel driver is a small port (RV1106 config already in the vendor driver; 4 dead-code headers need compat stubs) but no open userspace exists for this NPU gen; RGA = port the vendor char-dev rga3 driver (WardenOS's librga uses /dev/rga, not V4L2), + a cma pool + hrtimer_setup. All ports are open source (vendor C source / mainline siblings). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017wB8KB3MMQztRDXCMCkPrf
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
13ab5c7303
commit
749f73568a
@@ -18,11 +18,11 @@ c8a3, not just compiled.
|
|||||||
| uart1 / uart4 | ttyS1, ttyS4 | mainline | ✅ batch1 |
|
| uart1 / uart4 | ttyS1, ttyS4 | mainline | ✅ batch1 |
|
||||||
| I2C (dw-apb, ff460000=i2c3) | ff460000.i2c | mainline | ✅ batch1 (i2c-3) |
|
| I2C (dw-apb, ff460000=i2c3) | ff460000.i2c | mainline | ✅ batch1 (i2c-3) |
|
||||||
| watchdog (dw-wdt, ff5a0000) | ff5a0000.watchdog | mainline | ✅ batch1 (watchdog0) |
|
| 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) |
|
| 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 |
|
| GPIO_SYSFS (legacy /sys/class/gpio) | — | mainline (config) | ⬜ goodix script needs it |
|
||||||
| PWM (rockchip) | — | mainline (=m) | ⬜ batch2 =y (backlight) |
|
| 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 |
|
| 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 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 |
|
| 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) |
|
| GPIO_SYSFS / crypto / CFG80211 | — | mainline (config) | ✅ =y (batch2) |
|
||||||
| AIC8800 wifi (bsp/fdrv) | aic8800_* | **out-of-tree** | ⬜ M5 |
|
| AIC8800 wifi (bsp/fdrv) | aic8800_* | **out-of-tree** | ⬜ M5 |
|
||||||
| AIC8800 BT (btlpm) | aic8800_btlpm | **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 |
|
| 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) |
|
| FIQ debugger (ttyFIQ0) | fiq_glue | rockchip | ⬜ optional (we use ttyS2) |
|
||||||
|
|
||||||
Legend: ✅ verified on hardware · 🔨 built, not yet verified · ⬜ not started.
|
Legend: ✅ verified on hardware · 🔨 built, not yet verified · ⬜ not started.
|
||||||
|
|||||||
@@ -146,3 +146,16 @@
|
|||||||
status = "okay";
|
status = "okay";
|
||||||
dr_mode = "otg";
|
dr_mode = "otg";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* --- sweep: RTC + thermal (tsadc) + I2S audio DAI --- */
|
||||||
|
&rtc {
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
|
|
||||||
|
&tsadc {
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
|
|
||||||
|
&i2s0_8ch {
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
|
|||||||
@@ -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 = <GIC_SPI 109 IRQ_TYPE_LEVEL_HIGH>;
|
||||||
|
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 <soc/rockchip/rockchip_iommu.h>
|
||||||
|
#include <soc/rockchip/rockchip_opp_select.h>
|
||||||
|
#include <soc/rockchip/rockchip_system_monitor.h>
|
||||||
|
#include <soc/rockchip/rockchip_ipa.h>
|
||||||
|
```
|
||||||
|
|
||||||
|
`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 <felix.zeng@rock-chips.com>`), 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).
|
||||||
@@ -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
|
||||||
|
`<rga/rga.h>` and `<rga/im2d.h>` 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 `<flare-edge>/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 = <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>;
|
||||||
|
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
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,824 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 Rockchip Electronics Co., Ltd
|
||||||
|
*/
|
||||||
|
#include <linux/bcd.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/mfd/syscon.h>
|
||||||
|
#include <linux/clk.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
#include <linux/rtc.h>
|
||||||
|
|
||||||
|
/* 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 <zhangqing@rock-chips.com>");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
@@ -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},
|
||||||
@@ -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,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user