Skip to content

fh: pad mux for FH8852/FH8856/FH8858, V100, V200 and V210 - #254

Merged
openipc-ai merged 2 commits into
masterfrom
fh-padmux
Oct 27, 2026
Merged

openipc-ai merged 2 commits into
masterfrom
fh-padmux

Conversation

@openipc-ai

Copy link
Copy Markdown
Contributor

Fullhan detection never set chip_generation, so every ipchw_padmux_*
call on a Fullhan board answered IPCHW_PADMUX_NO_CHIP. That left no way
to put a pad such as GPIO9 back to GPIO. On a Tenda IC7-L (FH8852V200 +
GC4653) the IR-cut coils are on GPIO9/GPIO10, which boot as SSI0_D2/D3,
and the filter only moved after "mux,GPIO9,GPIO9,0" was written to
/proc/driver/pinctrl by hand (OpenIPC/majestic#354).

The layout is HiSilicon's: one register per pad at 0xF0000080 + 4*pad
(board.c: fh_pinctrl_init(VA_PMU_REG_BASE + 0x80)), selector in bits
24..27 (mfs). It is the same on V100 and V200/V210.

  • tools/gen_fh_padmux.py turns the vendor kernel's iopad.h
    (PINCTRL_FUNC(name, pad, FUNCn, ...)) into src/hal/fh_padmux.h, from
    OpenIPC/linux fullhan-fh8852v200@ee1ef844 and fullhan-fh8852v100@4825e16e.
    It proves that the six V200/V210 chips share one table and the two V100
    chips another, and it refuses duplicate selectors, a selector wider
    than four bits, a pad with two GPIOs, or a GPIO on two pads.
  • src/hal/fh_padmux.c is the backend. A pad is addressed by its GPIO
    number (pad 46 is GPIO9), never by pad id, and "5_2" is refused. A
    write sets the selector plus the input, output and Schmitt enables, as
    the kernel's fh_pinctrl_func_select() does. func_mask covers all four
    bits; get() decodes the selector alone.
  • fh_detect_cpu() sets a CHIP_FH* chip_generation. FH8856V201 and
    FH8858V201 are recognised and answer NO_TABLE.

Boot-flash lines

On V200, GPIO7..GPIO10 are also SSI0_TXD, SSI0_RXD, SSI0_D2 and SSI0_D3,
and SSI0 is the SPI controller the board runs from. Neither the table
nor the hardware says which lines a board's flash uses. On the Tenda,
the input enable reads set on every pad and the output enable reads
clear on every pad, even right after the kernel's own mux.

  • A new optional backend hook, in_use(), lets Fullhan answer true for
    any pad currently carrying an SSI0_* function.
  • ipchw_padmux_set() then returns the new IPCHW_PADMUX_IN_USE and writes
    nothing. Re-selecting the function the pad already carries is allowed.
  • ipchw_padmux_set_forced() and gpio mux <pad> <function> --force are
    for whoever knows the board, such as an owner who has named that pad
    for something else.

Build and tests

  • CMake family "fh", dropped like xm when the Fullhan HAL is not
    selected. padmux_names regenerated.
  • reginfo_test: FH8852V200 and FH8852V100 are in the integrity sweep,
    which now uses the forced call because it cycles every pad through
    every function. test_fh() checks the GPIO9 values from that camera:
    pad 46, SSI0_D2 at selector 0, register 0xF0000138.

Not yet run on a camera beyond that board's /proc/driver/pinctrl dump.
The register reads and writes are host-tested against a fake register
file only.

Verified on hardware (Tenda IC7-L, FH8852V200)

  • gpio mux 9 / 10 / 7 on a fresh boot read [SSI0_D2], [SSI0_D3], [SSI0_TXD], matching the vendor table and that board's /proc/driver/pinctrl.
  • gpio mux 9 GPIO was refused as a flash line (first test build); the board's owner did not force it.
  • devmem of GPIO7, GPIO9 and GPIO10's registers read 0x00001140 on all three, which is why the refusal covers every SSI0 pad rather than trying to guess.
  • A consumer built against this branch switched GPIO9, GPIO10 and GPIO0 to GPIO on that camera from a cold boot (forced for the pins its configuration names), and the IR-cut filter and IR LED both worked. Test binaries: https://github.com/OpenIPC/ipctool/releases/tag/test-fh-padmux-2-20261027

Fullhan detection never set chip_generation, so every ipchw_padmux_*
call on a Fullhan board answered IPCHW_PADMUX_NO_CHIP. That left no way
to put a pad such as GPIO9 back to GPIO. On a Tenda IC7-L (FH8852V200 +
GC4653) the IR-cut coils are on GPIO9/GPIO10, which boot as SSI0_D2/D3,
and the filter only moved after "mux,GPIO9,GPIO9,0" was written to
/proc/driver/pinctrl by hand (OpenIPC/majestic#354).

The layout is HiSilicon's: one register per pad at 0xF0000080 + 4*pad
(board.c: fh_pinctrl_init(VA_PMU_REG_BASE + 0x80)), selector in bits
24..27 (mfs). It is the same on V100 and V200/V210.

- tools/gen_fh_padmux.py turns the vendor kernel's iopad.h
  (PINCTRL_FUNC(name, pad, FUNCn, ...)) into src/hal/fh_padmux.h, from
  OpenIPC/linux fullhan-fh8852v200@ee1ef844 and fullhan-fh8852v100@4825e16e.
  It proves that the six V200/V210 chips share one table and the two V100
  chips another, and it refuses duplicate selectors, a selector wider
  than four bits, a pad with two GPIOs, or a GPIO on two pads.
- src/hal/fh_padmux.c is the backend. A pad is addressed by its GPIO
  number (pad 46 is GPIO9), never by pad id, and "5_2" is refused. A
  write sets the selector plus the input, output and Schmitt enables, as
  the kernel's fh_pinctrl_func_select() does. func_mask covers all four
  bits; get() decodes the selector alone.
- fh_detect_cpu() sets a CHIP_FH* chip_generation. FH8856V201 and
  FH8858V201 are recognised and answer NO_TABLE.

## Boot-flash lines
On V200, GPIO7..GPIO10 are also SSI0_TXD, SSI0_RXD, SSI0_D2 and SSI0_D3,
and SSI0 is the SPI controller the board runs from. Neither the table
nor the hardware says which lines a board's flash uses. On the Tenda,
the input enable reads set on every pad and the output enable reads
clear on every pad, even right after the kernel's own mux.
- A new optional backend hook, in_use(), lets Fullhan answer true for
  any pad currently carrying an SSI0_* function.
- ipchw_padmux_set() then returns the new IPCHW_PADMUX_IN_USE and writes
  nothing. Re-selecting the function the pad already carries is allowed.
- ipchw_padmux_set_forced() and `gpio mux <pad> <function> --force` are
  for whoever knows the board, such as an owner who has named that pad
  for something else.

## Build and tests
- CMake family "fh", dropped like xm when the Fullhan HAL is not
  selected. padmux_names regenerated.
- reginfo_test: FH8852V200 and FH8852V100 are in the integrity sweep,
  which now uses the forced call because it cycles every pad through
  every function. test_fh() checks the GPIO9 values from that camera:
  pad 46, SSI0_D2 at selector 0, register 0xF0000138.

Not yet run on a camera beyond that board's /proc/driver/pinctrl dump.
The register reads and writes are host-tested against a fake register
file only.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add Fullhan pad mux support with boot-flash protection

🐞 Bug fix ✨ Enhancement 🕐 40+ Minutes

Grey Divider

AI Description

• Enable Fullhan chip detection and vendor-derived pad mux tables for supported FH8852, FH8856, and
 FH8858 revisions.
• Refuse changes to pads carrying SSI0 functions unless explicitly forced, protecting potential
 boot-flash lines.
• Add CLI support, documentation, and register-file tests for Fullhan mux reads and writes.
Diagram

graph TD
  CLI["GPIO mux CLI"] --> API["Pad mux API"] --> Dispatch["Backend dispatch"] --> Backend["Fullhan backend"] --> Guard{"SSI0 in use?"} -->|safe or forced| PMU["PMU registers"]
  Detect["Chip detection"] --> Dispatch
  Tables["Vendor mux tables"] --> Backend
  Guard -->|refuse| API
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Board-specific flash-pad allowlists
  • ➕ Could permit unused SSI0 data lines without requiring --force.
  • ➖ Requires reliable board identification and maintenance for each flash configuration.
  • ➖ A missing or incorrect entry could interrupt access to boot flash.
2. Route writes through kernel pinctrl
  • ➕ Would let the kernel perform mux changes rather than writing PMU registers directly.
  • ➖ Depends on a vendor-specific kernel interface being present and usable.
  • ➖ Does not by itself establish which SSI0 lines a board's flash uses.

Recommendation: Keep the shared API guard and explicit forced path: hardware state identifies SSI0 functions but cannot establish whether a board uses each line, making a conservative default appropriate. Board allowlists or kernel pinctrl could be revisited if reliable board ownership information becomes available.

Files changed (16) +2752 / -88

Enhancement (12) +2597 / -82
ipchw.hExpose in-use refusal and forced pad mux setter +10/-0

Expose in-use refusal and forced pad mux setter

• Adds 'IPCHW_PADMUX_IN_USE' and declares 'ipchw_padmux_set_forced()' for callers that knowingly override the safety check.

include/ipchw.h

fh.hDefine Fullhan generation identifiers +16/-0

Define Fullhan generation identifiers

• Introduces chip-generation constants for the supported table families and the two recognized V201 parts without vendor pad tables.

src/hal/fh.h

fh_padmux.cImplement GPIO-addressed Fullhan pad mux operations +243/-0

Implement GPIO-addressed Fullhan pad mux operations

• Adds table selection, lookups, selector decoding, and PMU read-modify-write operations that also set the input, output, and Schmitt enables. Identifies pads currently carrying SSI0 functions for the shared in-use guard; V201 parts return no table.

src/hal/fh_padmux.c

fh_padmux.hAdd generated V100 and V200/V210 pad-function tables +482/-0

Add generated V100 and V200/V210 pad-function tables

• Provides vendor-derived pad, selector, GPIO number, and function-name rows for the two verified table layouts.

src/hal/fh_padmux.h

fh_padmux_types.hDefine compact Fullhan table rows +23/-0

Define compact Fullhan table rows

• Separates the generated tables' row type from their data, storing vendor pad IDs, selectors, GPIO numbers, and shared function-name offsets.

src/hal/fh_padmux_types.h

padmux.cDispatch Fullhan chips and guard ordinary mux writes +32/-1

Dispatch Fullhan chips and guard ordinary mux writes

• Routes detected Fullhan generations to the new backend. Makes ordinary writes refuse a change away from an in-use function, while the forced setter bypasses that check.

src/padmux.c

padmux.hAdd an optional backend in-use hook +11/-0

Add an optional backend in-use hook

• Extends the pad mux operations interface with a hardware-state check used by the shared setter, and declares the Fullhan backend.

src/padmux.h

padmux_names.cAdd Fullhan strings to the generated function-name store +521/-23

Add Fullhan strings to the generated function-name store

• Adds Fullhan-only function names and extends conditional inclusion for names shared with existing pad mux families.

src/padmux_names.c

padmux_names.hAdd Fullhan name fields and offset macros +1042/-46

Add Fullhan name fields and offset macros

• Regenerates the conditional name layout and 'PMX_' offsets used by Fullhan table rows, including names shared with other families.

src/padmux_names.h

reginfo.cSupport forced GPIO mux commands and explain refusals +22/-12

Support forced GPIO mux commands and explain refusals

• Accepts a trailing '--force' for mux writes, calls the forced setter when requested, and reports in-use refusals with guidance. Existing mux queries remain unforced.

src/reginfo.c

gen_fh_padmux.pyGenerate and validate Fullhan vendor pad tables +194/-0

Generate and validate Fullhan vendor pad tables

• Parses vendor 'iopad.h' function rows into the C tables and records source hashes. Rejects conflicting shared layouts, duplicate or oversized selectors, ambiguous GPIO mappings, and stale generated output.

tools/gen_fh_padmux.py

gen_padmux_names.pyInclude Fullhan tables in function-name generation +1/-0

Include Fullhan tables in function-name generation

• Registers the generated Fullhan table as an input when producing the shared, conditionally compiled pad mux name store.

tools/gen_padmux_names.py

Bug fix (1) +11 / -0
fh.cRecord detected Fullhan chip generations +11/-0

Record detected Fullhan chip generations

• Assigns 'chip_generation' for identified FH8852, FH8856, and FH8858 V100, V200, V210, and V201 parts so pad mux dispatch can recognize them.

src/hal/fh.c

Tests (1) +86 / -2
reginfo_test.cExercise Fullhan tables, register writes, and flash safeguards +86/-2

Exercise Fullhan tables, register writes, and flash safeguards

• Adds V100/V200 table integrity coverage and fake-register checks for GPIO numbering, selectors, enable fields, SSI0 refusal, forced writes, and V201 no-table behavior. The all-functions round trip now uses forced writes.

src/reginfo_test.c

Documentation (1) +50 / -1
padmux.mdDocument Fullhan numbering, register behavior, and flash precautions +50/-1

Document Fullhan numbering, register behavior, and flash precautions

• Explains GPIO-number addressing versus vendor pad IDs, selector and enable writes, revision-specific tables, and why SSI0 functions require an explicit force to change.

docs/padmux.md

Other (1) +8 / -3
CMakeLists.txtBuild the Fullhan pad mux backend when its HAL is selected +8/-3

Build the Fullhan pad mux backend when its HAL is selected

• Adds 'fh' to selectable pad mux families and backend sources. Removes it from library builds without the Fullhan HAL, where its chip generations cannot be detected.

CMakeLists.txt

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Oversized GPIO numbers select another pad ✓ Resolved
Description
fh_parse_pad() casts the long returned by strtol() to int without checking whether it fits.
On a platform where long is wider than int, an input such as 4294967297 narrows to GPIO1 and
can pass lookup, so a mistyped numeric pad can address a different register.
Code

src/hal/fh_padmux.c[R230-233]

+    long v = strtol(s, &tail, 10);
+    if (tail == s || *tail != '\0' || v < 0)
+        return -1;
+    return find_pad(table(), (int)v) >= 0 ? (int)v : -1;
Evidence
The new parser checks only for negative values before narrowing; find_pad() accepts the narrowed
value when it matches a table GPIO. The existing generic parser bounds its numeric input before
converting to int.

src/hal/fh_padmux.c[136-141]
src/hal/fh_padmux.c[227-234]
src/padmux.c[246-253]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Oversized numeric pad specifications can narrow to valid GPIO numbers before lookup.
## Fix Focus Areas
- src/hal/fh_padmux.c[227-234]
## Recommended Fix
Reject parsed values outside the Fullhan GPIO range, and conversion errors, before casting to `int` or calling `find_pad()`. Add a parsing test with a value that would otherwise narrow to an existing GPIO.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Numeric selector form never works on Fullhan ✓ Resolved
Description
row() sets func to (sel << 24) | FH_ENABLES, the whole value written to the register, not the
selector. gpio_mux_by() turns a numeric argument into a name by comparing `rows[i].func !=
func_num, so ipctool gpio mux 9 1` (GPIO9 is selector 1) never matches and prints "GPIO 9 has no
function 1" for every selector on every Fullhan pad, even though the comment and usage text say the
numeric form still works.
Code

src/hal/fh_padmux.c[R122-123]

+        .func_mask = FH_SEL_MASK | FH_ENABLES,
+        .func = (int)(((uint32_t)f->sel << FH_SEL_SHIFT) | FH_ENABLES),
Evidence
The test asserts that SSI0_D2 at selector 0 has func == 0x10001100 and that GPIO9 at selector 1 has
gpio_func == 0x11001100. gpio_mux_by passes strtoul(set_func) (for example 1) as func_num and
matches it only by rows[i].func != func_num, so no Fullhan row can ever match a small selector
number.

src/hal/fh_padmux.c[117-134]
src/reginfo.c[3823-3858]
src/reginfo.c[3920-3925]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
On Fullhan, `ipchw_padmux_t.func` holds the selector shifted to bits 24..27 plus the ST/IE/OE enable bits. The CLI's numeric form (`gpio mux <pad> <n>`) compares `n` to `func` directly, so it never matches.
## Fix Focus Areas
- src/reginfo.c[3823-3858]
- src/hal/fh_padmux.c[117-134]
## Recommended Fix
In gpio_mux_by, compare against the selector field: `((uint32_t)rows[i].func & rows[i].func_mask)` shifted right by the lowest set bit of `func_mask` restricted to the selector, or add a backend hook or extra field that holds the raw selector. Alternatively, have the Fullhan backend also match `func_num` against `(func >> 24) & 0xF`. Add a test that runs `gpio mux 9 1` on FH8852V200 and checks that it resolves to GPIO9.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

3. Register read failures are reported as a flash line ✓ Resolved
Description
fh_in_use() treats an IPCHW_PADMUX_IO result from fh_get() as in use, and padmux_set() then
returns IPCHW_PADMUX_IN_USE because the subsequent get() result is not 1. When a non-forced
gpio mux command cannot read the pad register through /dev/mem, the CLI tells the user the pad
carries a boot-flash line and suggests --force, even though the read did not establish that it is
a flash line.
Code

src/hal/fh_padmux.c[R216-221]

+static bool fh_in_use(int pad, const padmux_io_t *io) {
+    ipchw_padmux_t now;
+    const int res = fh_get(pad, &now, io);
+    if (res == IPCHW_PADMUX_IO)
+        return true; /* cannot tell: not known to be free */
+    return res == 1 && !strncmp(now.func_name, "SSI0_", 5);
Evidence
fh_get() returns IPCHW_PADMUX_IO when the register read fails, but fh_in_use() maps that
result to true. The guard in padmux_set() returns IPCHW_PADMUX_IN_USE when in_use() is true
and a subsequent get() does not return 1; the CLI refusal message for IN_USE identifies a
boot-flash line and suggests --force instead of reporting the read failure.

src/hal/fh_padmux.c[216-222]
src/padmux.c[197-202]
src/reginfo.c[3790-3795]
src/hal/fh_padmux.c[164-174]
src/padmux.c[197-204]
src/reginfo.c[3787-3795]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A failed Fullhan pad-register read is reported as `IPCHW_PADMUX_IN_USE` with boot-flash and `--force` advice instead of returning `IPCHW_PADMUX_IO`.
## Fix Focus Areas
- src/padmux.c[197-204]
- src/hal/fh_padmux.c[216-222]
## Recommended Fix
In `padmux_set()`, call `ops->get` before the in-use check and return `IPCHW_PADMUX_IO` or any other negative result without writing. Return `IPCHW_PADMUX_IN_USE` only when the pad is confirmed in use and its current function differs from the requested one, preserving refusal of confirmed SSI0 lines.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can describe a rule in plain language on the Rules page and Qodo drafts it for you

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/hal/fh_padmux.c
Comment thread src/hal/fh_padmux.c
Comment thread src/hal/fh_padmux.c Outdated
…r, report a failed read as one

- fh_parse_pad() bounds the number before narrowing it. On a 64-bit
  host "4294967297" became GPIO1 through an int and passed the lookup.
  GPIO numbers on these parts fit a byte, and strtol() overflow is
  refused.
- `gpio mux <pad> <n>` never matched on Fullhan. The CLI compared n with
  `func`, which here is the whole value a write puts in the register
  (selector in bits 24..27 plus the enables), so "9 1" answered "has no
  function 1". A new optional backend hook, func_number(), gives the
  number a person types: on Fullhan the selector, as /proc/driver/pinctrl
  prints it in `sel`. The CLI matches through padmux_func_number();
  every other backend still compares `func`, so nothing changes there.
- ipchw_padmux_set() reads the pad before applying the flash check and
  returns any read error as it is. A register that could not be read
  used to come back as IPCHW_PADMUX_IN_USE, with advice to --force.
- reginfo_test: the oversized number, selector numbering on GPIO9, and
  a failed read under IO_FAILS.
@openipc-ai

Copy link
Copy Markdown
Contributor Author

Review round addressed in c0ab9ff:

  1. Oversized gpio numbers select another pad: fixed. fh_parse_pad() refuses strtol() overflow and anything above a byte before narrowing, so 4294967297 no longer becomes GPIO1. Tested with and without the GPIO prefix.
  2. Numeric selector form never works on Fullhan: fixed. A new optional backend hook, func_number(), gives the number a person types for a row. On Fullhan that's the selector, as /proc/driver/pinctrl prints it in sel, not func, which is the whole register value with the enables. The CLI matches through padmux_func_number(), and every other backend still compares func, so XM's in-place values and HiSilicon's indices are unchanged. Tested: selector 1 on GPIO9 resolves to GPIO9, and 0 to SSI0_D2.
  3. Register read failures reported as a flash line: fixed. ipchw_padmux_set() reads the pad before applying the flash check and returns a read error as it is; the refusal only applies to a pad confirmed to be on an SSI0_* function. Tested under IO_FAILS: IPCHW_PADMUX_IO, not IN_USE.

reginfo_test passes, as do the fh-only, fh-without-HAL and no-table builds, and gen_padmux_names --check.

@openipc-ai
openipc-ai merged commit 2a19fe0 into master Oct 27, 2026
5 checks passed
@openipc-ai
openipc-ai deleted the fh-padmux branch October 27, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant