From b41aa132d6a72eb209e64eb4d09f7c90c1b55cfa Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin Date: Wed, 3 Jun 2026 17:09:19 +0300 Subject: [PATCH 1/2] agent/cv6xx: support CV6xx flash agent via fastboot composite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds end-to-end agent support for the HiSilicon CV6xx family (hi3516cv608, hi3516cv610, hi3516dv500, hi3519dv500) by wiring up the CV6xx fastboot path through `defib agent upload`. CV6xx fastboot is structurally different from HiSilicon Standard (SPL + agent two-stage) — it loads a single composite (GSL + DDR tables + U-Boot section) and the bootrom parses the 1024-byte U-Boot header at byte 0 of the final stage. The header's code-offset field at byte 8 is 0x400, so the bootrom jumps to `load_addr + 0x400`, not `load_addr`. Existing CV6xx agent attempts crashed because the binary was linked at 0x41000000 but actually executed at 0x41000400, so all link-time absolute references (page table, etc.) pointed at wrong memory. Changes: * `agent/Makefile` hi3516cv610 stanza: - LOAD_ADDR 0x41000000 → 0x41000400 (account for U-Boot header). - CRG_BASE 0x12010000 → 0x11010000 (CV6xx moved the peripheral CRG block; the old V3/V4 address is unmapped on CV6xx and the read triggered an external abort during flash_init). - WDT_BASE 0x12030000 → 0x11030000 and SYSCTRL_REBOOT 0x12020004 → 0x11020004 for the same reason. - FLASH_MEM 0x14000000 → 0x0F000000 (CV6xx FMC MMIO window). - New per-SoC `FMC_CRG_OFFSET` / `FMC_CRG_CLK_BIT` so the FMC clock-gate register layout can differ per family (CV6xx puts `fmc_cken` on bit 4 at CRG+0x3F40 instead of bit 1 at CRG+0x0144). * `agent/spi_flash.c`: REG_FMC_CRG now uses the configurable FMC_CRG_OFFSET / FMC_CRG_CLK_BIT macros, with the V3/V4 default preserved. * `src/defib/protocol/hisilicon_cv6xx.py`: - `wrap_cv6xx_payload(header_source, payload)` helper — clones the 1024-byte U-Boot header from a known-good composite and patches the length field at byte 36 so the bootrom validates and jumps to the wrapped payload. - `HiSiliconCV6xx.send_firmware(..., uboot_override=None)` so the caller can replace the U-Boot section with an arbitrary payload (the agent, in our case) while reusing the composite's GSL + DDR tables. * `src/defib/cli/app.py` `_agent_upload_async`: - Dispatches on protocol family via `find_protocol(chip)`. The HiSilicon Standard path is unchanged; CV6xx routes to a new `_agent_upload_cv6xx` helper. - The CV6xx path takes a `-f/--file` composite (no auto-download yet — OpenIPC doesn't ship a prebuilt for these SoCs), parses it, wraps the agent with `wrap_cv6xx_payload`, and runs the full handshake → GSL → DDR → wrapped-agent flow. Uses the spawn-handshake-before-power-cycle ordering (PR #109) so it works on Vectis-bridged boards via `--power-cycle`. * `src/defib/agent/client.py`: alias `hi3516cv608 → hi3516cv610` agent binary, mirroring the existing same-family aliases for hi3516av300 / hi3516dv300 → hi3516cv500. Verified live on a Vectis-bridged hi3516cv608 (OpenIPC Vectis on `rfc2217://127.0.0.1:35240`): `defib agent upload --power-cycle` brings the agent up cleanly — RAM 0x40000000, flash 16 MiB, sector 64 KiB, agent_version 4, capabilities 0xFF. Known follow-up: the agent's `flash_read_id` returns 0x00 0x00 0x00 on this CV6xx board even though flash_size is correct via the detect_size fallback. The FMC register-mode result path (currently reads from FLASH_MEM after issuing READ_ID) likely needs CV6xx pinmux or FMC data-buffer tuning. Tracked separately — does not affect the upload/INFO path. 68 pytest tests + 5412 agent C unit tests still pass. Ruff and mypy clean. agent-hi3516cv610.bin still built by `make SOC=hi3516cv610`. --- agent/Makefile | 32 +++-- agent/spi_flash.c | 18 ++- src/defib/agent/client.py | 1 + src/defib/cli/app.py | 179 +++++++++++++++++++++++++- src/defib/protocol/hisilicon_cv6xx.py | 29 ++++- 5 files changed, 240 insertions(+), 19 deletions(-) diff --git a/agent/Makefile b/agent/Makefile index ec1f237..33b19e7 100644 --- a/agent/Makefile +++ b/agent/Makefile @@ -106,15 +106,27 @@ else ifeq ($(SOC),hi3518ev200) CRG_BASE = 0x12010000 SYSCTRL_REBOOT = 0x12020004 else ifeq ($(SOC),hi3516cv610) - UART_BASE = 0x11040000 - UART_CLOCK = 24000000 - LOAD_ADDR = 0x41000000 - FLASH_MEM = 0x14000000 - FMC_BASE = 0x10000000 - RAM_BASE = 0x40000000 - WDT_BASE = 0x12030000 - CRG_BASE = 0x12010000 - SYSCTRL_REBOOT = 0x12020004 + # CV6xx family (cv608, cv610, dv500, dv500). The CV6xx fastboot + # bootrom treats the final-stage payload as U-Boot and parses its + # 1024-byte header: the code_offset field at byte 8 is 0x400, so + # the bootrom jumps to load_addr + 0x400. The agent must therefore + # be linked at 0x41000000 + 0x400 = 0x41000400 to match where its + # first instruction actually lands after the wrap. + # CRG/peripheral block moved to 0x11xxxxxx; FMC clock control sits + # at CRG+0x3F40 with the enable on bit 4 (not CRG+0x144/bit1 like + # V3/V4 chips). UART0 confirms the 0x11xxxxxx range from the + # vendor user guide and from the running kernel's earlycon arg. + UART_BASE = 0x11040000 + UART_CLOCK = 24000000 + LOAD_ADDR = 0x41000400 + FLASH_MEM = 0x0F000000 + FMC_BASE = 0x10000000 + RAM_BASE = 0x40000000 + WDT_BASE = 0x11030000 + CRG_BASE = 0x11010000 + SYSCTRL_REBOOT = 0x11020004 + FMC_CRG_OFFSET = 0x3F40 + FMC_CRG_CLK_BIT = 4 else ifeq ($(SOC),hi3519v101) # V3A generation (3519v101 family: hi3519v101, hi3516av200) — Cortex-A7 # with V3-era peripheral addresses (UART 0x12100000, WDT 0x12080000) @@ -183,6 +195,8 @@ CFLAGS = -mcpu=$(CPU_TYPE) -marm -O2 -ffreestanding -nostdlib \ $(if $(SYSCTRL_BASE),-DSYSCTRL_BASE=$(SYSCTRL_BASE)) \ $(if $(BOOTROM_BASE),-DBOOTROM_BASE=$(BOOTROM_BASE) -DBOOTROM_SIZE=$(BOOTROM_SIZE)) \ $(if $(UART_CKSEL_REG),-DUART_CKSEL_REG=$(UART_CKSEL_REG) -DUART_CKSEL_BIT=$(UART_CKSEL_BIT)) \ + $(if $(FMC_CRG_OFFSET),-DFMC_CRG_OFFSET=$(FMC_CRG_OFFSET)) \ + $(if $(FMC_CRG_CLK_BIT),-DFMC_CRG_CLK_BIT=$(FMC_CRG_CLK_BIT)) \ -mno-unaligned-access -Wall -Wextra LDFLAGS = -nostdlib -T link.ld -Ttext=$(LOAD_ADDR) --defsym=LOAD_ADDR=$(LOAD_ADDR) diff --git a/agent/spi_flash.c b/agent/spi_flash.c index 96e1f34..eb6b962 100644 --- a/agent/spi_flash.c +++ b/agent/spi_flash.c @@ -122,10 +122,20 @@ #define NAND_PAGE_SIZE 2048 #define NAND_BLOCK_SIZE (64 * NAND_PAGE_SIZE) /* 128 KiB */ -/* CRG register for FMC clock — CRG_BASE is per-SoC (set via -DCRG_BASE=...) */ -#define REG_FMC_CRG (*(volatile uint32_t *)(CRG_BASE + 0x0144)) -#define FMC_CLK_ENABLE (1 << 1) -#define FMC_SOFT_RESET (1 << 0) +/* CRG register for FMC clock — CRG_BASE + per-family offset and bit + * positions. The V3/V4 default (offset 0x0144, clock-enable on bit 1, + * soft-reset on bit 0) is overridden via Makefile for chips where the + * CRG layout differs — notably CV6xx (offset 0x3F40, clock-enable bit + * 4) which moved every peripheral CRG entry. */ +#ifndef FMC_CRG_OFFSET +#define FMC_CRG_OFFSET 0x0144 +#endif +#ifndef FMC_CRG_CLK_BIT +#define FMC_CRG_CLK_BIT 1 +#endif +#define REG_FMC_CRG (*(volatile uint32_t *)(CRG_BASE + FMC_CRG_OFFSET)) +#define FMC_CLK_ENABLE (1u << FMC_CRG_CLK_BIT) +#define FMC_SOFT_RESET (1u << 0) /* SPI timing: TCSH=6 [15:12], TCSS=6 [11:8], TSHSL=0xF [7:0] */ #define SPI_TIMING_VAL ((6 << 12) | (6 << 8) | 0xF) /* 0x660F */ diff --git a/src/defib/agent/client.py b/src/defib/agent/client.py index b90959b..798fa08 100644 --- a/src/defib/agent/client.py +++ b/src/defib/agent/client.py @@ -162,6 +162,7 @@ def get_agent_binary(chip: str) -> Path | None: "hi3519v101": "hi3519v101", "hi3516av200": "hi3519v101", # 3519v101 family, same memory map "hi3516cv610": "hi3516cv610", + "hi3516cv608": "hi3516cv610", # cv6xx-family, same memory map "hi3518ev200": "hi3518ev200", "hi3520dv200": "hi3520dv200", # V1-era, HISFC350 SPI controller } diff --git a/src/defib/cli/app.py b/src/defib/cli/app.py index bf50c05..f873d52 100644 --- a/src/defib/cli/app.py +++ b/src/defib/cli/app.py @@ -2,6 +2,8 @@ from __future__ import annotations +from typing import Any + import typer app = typer.Typer( @@ -999,13 +1001,19 @@ def agent_upload( chip: str = typer.Option(..., "-c", "--chip", help="Chip model name"), port: str = typer.Option("/dev/ttyUSB0", "-p", "--port", help="Serial device (/dev/ttyUSB0), tcp://host:port, rfc2217://host:port, or socket:///path"), output: str = typer.Option("human", "--output", help="Output mode: human, json"), + file: str | None = typer.Option(None, "-f", "--file", help="CV6xx composite boot file (GSL+DDR+U-Boot); required for CV6xx, ignored for other protocols"), + power_cycle: bool = typer.Option(False, "--power-cycle", help="Auto power-cycle via configured controller (DEFIB_POWER_TYPE)"), ) -> None: """Upload flash agent to device via boot protocol (requires power-cycle).""" import asyncio - asyncio.run(_agent_upload_async(chip, port, output)) + asyncio.run(_agent_upload_async(chip, port, output, file, power_cycle)) -async def _agent_upload_async(chip: str, port: str, output: str) -> None: +async def _agent_upload_async( + chip: str, port: str, output: str, + composite_path: str | None = None, + power_cycle: bool = False, +) -> None: import json as json_mod from rich.console import Console @@ -1013,7 +1021,9 @@ async def _agent_upload_async(chip: str, port: str, output: str) -> None: from defib.agent.client import FlashAgentClient, get_agent_binary from defib.firmware import get_cached_path from defib.profiles.loader import load_profile + from defib.protocol.hisilicon_cv6xx import HiSiliconCV6xx from defib.protocol.hisilicon_standard import HiSiliconStandard + from defib.protocol.registry import find_protocol from defib.recovery.events import ProgressEvent from defib.transport.serial_platform import ( create_transport, normalize_port_name, @@ -1033,7 +1043,27 @@ async def _agent_upload_async(chip: str, port: str, output: str) -> None: agent_data = agent_path.read_bytes() - # Get real SPL from cached U-Boot + # Dispatch on protocol family. CV6xx fastboot expects a single + # composite (GSL + DDR + U-Boot-shaped) blob — the agent ships in + # place of the U-Boot section, wrapped in a 1024-byte header so the + # bootrom jumps to LOAD_ADDR + 0x400 where the agent's first + # instruction is linked. + protocol_cls = find_protocol(chip) + if protocol_cls is HiSiliconCV6xx: + await _agent_upload_cv6xx( + chip=chip, + port=port, + output=output, + console=console, + agent_path=agent_path, + agent_data=agent_data, + composite_path=composite_path, + power_cycle=power_cycle, + ) + return + + # HiSiliconStandard / V500 path — needs SoC profile for the SPL+agent + # two-stage upload. profile = load_profile(chip) cached_fw = get_cached_path(chip) if not cached_fw: @@ -1127,6 +1157,149 @@ def on_progress(e: ProgressEvent) -> None: await transport.close() +async def _agent_upload_cv6xx( + *, + chip: str, + port: str, + output: str, + console: Any, + agent_path: Any, + agent_data: bytes, + composite_path: str | None, + power_cycle: bool, +) -> None: + """CV6xx fastboot agent upload — single-composite, no separate SPL.""" + import asyncio + import json as json_mod + + from defib.agent.client import FlashAgentClient + from defib.power.factory import power_controller_from_env + from defib.power.vectis import VectisController + from defib.protocol.hisilicon_cv6xx import ( + HiSiliconCV6xx, parse_cv6xx_boot, wrap_cv6xx_payload, + ) + from defib.recovery.events import ProgressEvent + from defib.transport.rfc2217 import Rfc2217Transport + from defib.transport.serial_platform import ( + create_transport, normalize_port_name, + ) + + if not composite_path: + msg = ( + f"CV6xx chip '{chip}' needs a composite boot file (GSL+DDR+U-Boot) " + "to derive the bootrom-loadable header — pass it via -f/--file." + ) + if output == "json": + print(json_mod.dumps({"event": "error", "message": msg})) + else: + console.print(f"[red]{msg}[/red]") + raise typer.Exit(2) + + composite = open(composite_path, "rb").read() + try: + parts = parse_cv6xx_boot(composite) + except Exception as e: + msg = f"Failed to parse composite '{composite_path}': {e}" + if output == "json": + print(json_mod.dumps({"event": "error", "message": msg})) + else: + console.print(f"[red]{msg}[/red]") + raise typer.Exit(2) + + wrapped = wrap_cv6xx_payload(parts.uboot_data, agent_data) + + if output == "human": + console.print(f"Agent: [cyan]{agent_path.name}[/cyan] ({len(agent_data)} bytes)") + console.print(f"Composite: [cyan]{composite_path}[/cyan] ({len(composite)} bytes)") + console.print(f"Wrapped payload: {len(wrapped)} bytes (1024 B header + agent)") + + power = power_controller_from_env() if power_cycle else None + + transport = await create_transport(normalize_port_name(port)) + if power is not None and isinstance(power, VectisController) and isinstance(transport, Rfc2217Transport): + power.attach_transport(transport) + + protocol = HiSiliconCV6xx() + + def on_progress(e: ProgressEvent) -> None: + if e.message: + if output == "human": + console.print(f" {e.message}") + elif output == "json": + print(json_mod.dumps({"event": "progress", "message": e.message}), flush=True) + + # Spawn handshake before reset — same ordering as RecoverySession's + # proactive-blast path (see #109). + if output == "human" and power is not None: + console.print(f"Power-cycling via {power.name()}...") + elif output == "human": + console.print("\n[yellow]Power-cycle the camera now![/yellow]\n") + + await transport.flush_input() + hs_task = asyncio.create_task(protocol.handshake(transport, on_progress)) + if power is not None: + await asyncio.sleep(0.05) + try: + await power.power_cycle("") + except Exception as e: + hs_task.cancel() + try: + await hs_task + except BaseException: + pass + if output == "json": + print(json_mod.dumps({"event": "error", "message": f"Power cycle failed: {e}"})) + else: + console.print(f"[red]Power cycle failed:[/red] {e}") + await transport.close() + raise typer.Exit(1) + + hs = await hs_task + if not hs.success: + if output == "json": + print(json_mod.dumps({"event": "error", "message": "Handshake failed"})) + else: + console.print("[red]Handshake failed[/red]") + await transport.close() + raise typer.Exit(1) + + result = await protocol.send_firmware( + transport, composite, on_progress, uboot_override=wrapped, + ) + if not result.success: + if output == "json": + print(json_mod.dumps({"event": "error", "message": result.error or "Upload failed"})) + else: + console.print(f"[red]Upload failed:[/red] {result.error}") + await transport.close() + raise typer.Exit(1) + + if output == "human": + console.print("[green]Agent uploaded![/green] Waiting for READY...") + + client = FlashAgentClient(transport, chip) + ready = await client.connect(timeout=10.0) + if ready: + info = await client.get_info() + if output == "human": + console.print("[green bold]Agent ready![/green bold]") + console.print(f" RAM: 0x{info.get('ram_base', 0):08x}") + console.print(f" Flash: {int(info.get('flash_size', 0)) // 1024}KB") + elif output == "json": + print(json_mod.dumps({"event": "ready", **info})) + else: + if output == "json": + print(json_mod.dumps({"event": "error", "message": "Agent not responding"})) + else: + console.print("[red]Agent not responding[/red]") + await transport.close() + raise typer.Exit(1) + + await transport.close() + if power is not None: + await power.close() + + @agent_app.command("flash") def agent_flash( chip: str = typer.Option(..., "-c", "--chip", help="Chip model name"), diff --git a/src/defib/protocol/hisilicon_cv6xx.py b/src/defib/protocol/hisilicon_cv6xx.py index 14e4280..1f20eca 100644 --- a/src/defib/protocol/hisilicon_cv6xx.py +++ b/src/defib/protocol/hisilicon_cv6xx.py @@ -122,6 +122,25 @@ def parse_cv6xx_boot(file_data: bytes) -> CV6xxBootParts: ) +def wrap_cv6xx_payload(header_source: bytes, payload: bytes) -> bytes: + """Wrap an arbitrary blob in the CV6xx U-Boot header. + + The CV6xx bootrom validates the U-Boot magic at byte 0 and reads + the code-offset field at byte 8 (= 0x400) to decide where to jump + inside the loaded blob. ``header_source`` is the uboot_data section + of a valid composite boot file (e.g. ``parse_cv6xx_boot(blob).uboot_data``); + we reuse its 1024-byte header verbatim and patch only the payload + length field at byte 36. Callers must link the payload at + ``UBOOT_LOAD_ADDR + 0x400`` so its first instruction lands where + the bootrom jumps. + """ + if len(header_source) < 1024: + raise ValueError("header_source must be at least 1024 bytes") + header = bytearray(header_source[:1024]) + struct.pack_into(" bytes: """Build the DDR initialization table for a specific board ID.""" mapping = parts.board_mapping @@ -327,6 +346,7 @@ async def send_firmware( transport: Transport, firmware: bytes, on_progress: Callable[[ProgressEvent], None] | None = None, + uboot_override: bytes | None = None, ) -> RecoveryResult: stages: list[Stage] = [] @@ -336,9 +356,11 @@ async def send_firmware( except ProtocolError as e: return RecoveryResult(success=False, error=str(e)) + uboot_payload = uboot_override if uboot_override is not None else parts.uboot_data + logger.info( "CV6xx boot file: GSL=%d bytes, DDR tables=%d (size=%d), U-Boot=%d bytes", - len(parts.gsl_data), parts.table_count, parts.table_size, len(parts.uboot_data), + len(parts.gsl_data), parts.table_count, parts.table_size, len(uboot_payload), ) # 1. Send GSL @@ -406,9 +428,10 @@ async def send_firmware( )) stages.append(Stage.DDR_TRAINING) - # 5. Send U-Boot + # 5. Send U-Boot (or the override payload, e.g. a flash agent + # wrapped via wrap_cv6xx_payload using the composite's header). if not await self._send_data_to_bootrom( - transport, parts.uboot_data, UBOOT_LOAD_ADDR, + transport, uboot_payload, UBOOT_LOAD_ADDR, Stage.UBOOT, on_progress, ): return RecoveryResult( From 4bf3446c93e86955d6a06fbeeccd137c098106a9 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin Date: Wed, 3 Jun 2026 17:19:39 +0300 Subject: [PATCH 2/2] =?UTF-8?q?agent/cv6xx:=20fix=20SPI=20pinmux=20for=20C?= =?UTF-8?q?V6xx=20=E2=80=94=20JEDEC=20ID=20+=20flash=20reads=20now=20work?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first CV6xx agent commit got the upload path running but flash_read_id returned zeros and flash CMD_READ failed because the agent was writing to the V3/V4 pinctrl block (0x100C0000) with V3/V4 pad-drive values. On CV6xx the pinctrl block lives at 0x10260000 and the register offsets + drive values are different. Source of truth: dimerr/u-boot-hi3516cv6xx `drivers/mtd/fmc_hi3516cv610.c::hi3516cv610_spi_io_config()`. Changes: * `agent/spi_flash.c::fmc_enter_normal()` — pinmux config now parameterised by `SPI_PIN_BASE` and a `SPI_PINS_CV6XX` family flag. Default (V3/V4) layout is preserved; with `-DSPI_PINS_CV6XX=1` the function uses CV6xx offsets / values (CLK 0x10=0x1291, CS0 0x18= 0x1131, MOSI_IO0 0x0C=0x1261, MISO_IO1 0x1C=0x1261, WP_IO2 0x20= 0x12E1, HOLD_IO3 0x14=0x1161 — 3.3V single-CS). * `agent/Makefile` hi3516cv610 stanza now sets `SPI_PIN_BASE=0x10260000` and `SPI_PINS_CV6XX=1`, and the global CFLAGS pass them through. Verified live on hi3516cv608 over Vectis: jedec_id=a14018 (FM25Q128A 16 MiB ✓) flash_size=16777216, sector_size=65536, ram_base=0x40000000 flash[0..64] via MMIO window matches the U-Boot composite we burned earlier, byte-for-byte — proving real flash content is being read through 0x0F000000. 68 pytest + 5412 agent C tests still pass. --- agent/Makefile | 7 +++++++ agent/spi_flash.c | 28 ++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/agent/Makefile b/agent/Makefile index 33b19e7..d31fb9b 100644 --- a/agent/Makefile +++ b/agent/Makefile @@ -127,6 +127,11 @@ else ifeq ($(SOC),hi3516cv610) SYSCTRL_REBOOT = 0x11020004 FMC_CRG_OFFSET = 0x3F40 FMC_CRG_CLK_BIT = 4 + # CV6xx moved the SPI pinctrl block from 0x100C0000 to 0x10260000 + # and changed register offsets + pad-drive values. The values come + # from the dimerr u-boot-hi3516cv6xx fork (drivers/mtd/fmc_hi3516cv610.c). + SPI_PIN_BASE = 0x10260000 + SPI_PINS_CV6XX = 1 else ifeq ($(SOC),hi3519v101) # V3A generation (3519v101 family: hi3519v101, hi3516av200) — Cortex-A7 # with V3-era peripheral addresses (UART 0x12100000, WDT 0x12080000) @@ -197,6 +202,8 @@ CFLAGS = -mcpu=$(CPU_TYPE) -marm -O2 -ffreestanding -nostdlib \ $(if $(UART_CKSEL_REG),-DUART_CKSEL_REG=$(UART_CKSEL_REG) -DUART_CKSEL_BIT=$(UART_CKSEL_BIT)) \ $(if $(FMC_CRG_OFFSET),-DFMC_CRG_OFFSET=$(FMC_CRG_OFFSET)) \ $(if $(FMC_CRG_CLK_BIT),-DFMC_CRG_CLK_BIT=$(FMC_CRG_CLK_BIT)) \ + $(if $(SPI_PIN_BASE),-DSPI_PIN_BASE=$(SPI_PIN_BASE)) \ + $(if $(SPI_PINS_CV6XX),-DSPI_PINS_CV6XX=1) \ -mno-unaligned-access -Wall -Wextra LDFLAGS = -nostdlib -T link.ld -Ttext=$(LOAD_ADDR) --defsym=LOAD_ADDR=$(LOAD_ADDR) diff --git a/agent/spi_flash.c b/agent/spi_flash.c index eb6b962..29903b3 100644 --- a/agent/spi_flash.c +++ b/agent/spi_flash.c @@ -152,20 +152,40 @@ static void fmc_wait_ready(void); static void spi_wait_wip(void); /* Mode switching: normal mode for register commands, boot mode for reads */ -/* I/O pad configuration base for SPI flash pins */ -#define IO_BASE 0x100C0000 -#define io_reg(off) (*(volatile uint32_t *)(IO_BASE + (off))) +/* I/O pad configuration base for SPI flash pins. Per-family override + * via -DSPI_PIN_BASE=...; default targets V3/V4 chips. */ +#ifndef SPI_PIN_BASE +#define SPI_PIN_BASE 0x100C0000 +#endif +#define io_reg(off) (*(volatile uint32_t *)(SPI_PIN_BASE + (off))) static void fmc_enter_normal(void) { /* Full FMC init for register-mode operations (matching U-Boot) */ - /* Configure SPI flash I/O pads (SPL may have left them in boot-mode config) */ + /* Configure SPI flash I/O pads (SPL may have left them in boot-mode + * config). CV6xx moved the pinctrl block from 0x100C0000 to + * 0x10260000 and uses different register offsets + values; the + * dimerr/u-boot-hi3516cv610 driver fmc_hi3516cv610.c is the source + * of truth for the CV6xx values. */ +#ifdef SPI_PINS_CV6XX + /* CV6xx layout (per drivers/mtd/fmc_hi3516cv610.c, 3.3V single-CS): + * 0x0C MOSI_IO0, 0x10 CLK, 0x14 HOLD_IO3, 0x18 CS0, + * 0x1C MISO_IO1, 0x20 WP_IO2. */ + io_reg(0x0C) = 0x1261; /* sfc_mosi_io0 */ + io_reg(0x10) = 0x1291; /* sfc_clk */ + io_reg(0x14) = 0x1161; /* sfc_hold_io3 */ + io_reg(0x18) = 0x1131; /* sfc_cs0 */ + io_reg(0x1C) = 0x1261; /* sfc_miso_io1 */ + io_reg(0x20) = 0x12E1; /* sfc_wp_io2 */ +#else + /* V3/V4 layout (hi3516ev300 / cv500 / gk7205 / ...) */ io_reg(0x14) = 0x401; /* sfc_clk */ io_reg(0x18) = 0x461; /* sfc_hold_io0 */ io_reg(0x1C) = 0x461; /* sfc_miso_io1 */ io_reg(0x20) = 0x461; /* sfc_wp_io2 */ io_reg(0x24) = 0x461; /* sfc_mosi_io3 */ io_reg(0x28) = 0x461; /* sfc_csn */ +#endif fmc_reg(FMC_CFG) = 0x1821; /* OP_MODE_NORMAL | bootrom defaults */ fmc_reg(FMC_SPI_TIMING_CFG) = SPI_TIMING_VAL;