From 339dbcc79a80d67bbda5b599e8ca5f6b1541dc16 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Mon, 19 Oct 2026 19:02:16 +0300 Subject: [PATCH 1/2] xm: pad mux for XM510 and XM530/XM550 Xiongmai selects a pad's function with one enable bit per alternative in the GPIO block at 0x10020000, all clear meaning GPIO: - XM530/XM550: in the pad's own register, bits 1-3 (MUX1..MUX3); - XM510: in its port's word of eight pins, bits b, 8 + b and 16 + b. That is HiSilicon's shape -- one register, one field, every row a plain read-modify-write -- so reginfo --pads and gpio mux now work on both. Pads are numbered as the vendor does: GPIO N on XM530, port * 8 + pin on XM510 ("8_1", "GPIO8_1"). XM530 refuses HiSilicon's "5_2", which would land on pad 42. Xiongmai publishes no table of what MUX1..3 carry, so rows are named FUNC1, FUNC2 and FUNC3, except where the vendor's own code names them: - XM530's SPI flash pads 9..14 (FLASH_*) and SD pads 43..50 and 52 (SDIO0_*); - XM510's SD and Wi-Fi slots (SDIO1 on 5_0..5_3, SDIO2 on 6_0..6_3). xm_detect_cpu() now sets chip_generation to CHIP_XM510 or CHIP_XM530, so the front door can pick the backend. Checked on a lab XM510 and a lab XM550: - reginfo --pads agreed with every mux register read through devmem (all 100 pads on XM550, compared by script); - gpio mux set and cleared exactly the pad's own bits, keeping XM550's drive-strength bits. reginfo_test decodes those boards' registers at rest and sweeps both tables. --- CMakeLists.txt | 6 +- docs/padmux.md | 61 ++++++++++- src/hal/xm.c | 3 + src/hal/xm.h | 8 ++ src/hal/xm_padmux.c | 245 ++++++++++++++++++++++++++++++++++++++++++++ src/padmux.c | 6 ++ src/padmux.h | 6 ++ src/reginfo_test.c | 87 ++++++++++++++++ 8 files changed, 418 insertions(+), 4 deletions(-) create mode 100644 src/hal/xm_padmux.c diff --git a/CMakeLists.txt b/CMakeLists.txt index e0cffe69..281e85ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ endif() # Same shape as IPCHW_VENDORS above, and for the same reason -- set(... CACHE) # here means a parent project has to FORCE it. set(IPCHW_PADMUX_FAMILIES v1 v2 v2a v3 v3a v4 v4a v5 3536c 3536d sstar - ingenic novatek) + ingenic novatek xm) set(IPCHW_PADMUX "all" CACHE STRING "Pad-mux tables compiled into libipchw: 'all', 'none', or a subset of ${IPCHW_PADMUX_FAMILIES}") @@ -107,13 +107,13 @@ message(STATUS "libipchw pad-mux tables: ${_padmux_selected}") # SigmaStar tables in a build that detects nothing else, which is exactly what # reginfo_test does. set(IPCHW_PADMUX_BACKENDS "") -foreach(_f sstar ingenic novatek) +foreach(_f sstar ingenic novatek xm) if(_f IN_LIST _padmux_selected) list(APPEND IPCHW_PADMUX_BACKENDS src/hal/${_f}_padmux.c) endif() endforeach() set(IPCHW_PADMUX_BACKENDS_ALL src/hal/sstar_padmux.c src/hal/ingenic_padmux.c - src/hal/novatek_padmux.c) + src/hal/novatek_padmux.c src/hal/xm_padmux.c) # Which HiSilicon generations libipchw can identify. IPCHW_VENDORS does not # reach the CODE: chipid.c dispatches to the HiSilicon HAL directly rather diff --git a/docs/padmux.md b/docs/padmux.md index d50868d6..8f5980a1 100644 --- a/docs/padmux.md +++ b/docs/padmux.md @@ -23,7 +23,7 @@ puts the pad back. > pad to GPIO. A pad that a MISC-style table drop hides from `reginfo --pads` > still answers here, which is the one place to see it. -## Four vendors, four mechanisms +## Five vendors, four mechanisms This is the thing to understand before touching any of it. The API looks like one question, and underneath it is four different pieces of silicon. @@ -350,6 +350,38 @@ for SIF. Where neither names a pad the name is the option's own -- `SENSOR_12BIT `MIPI_LVDS_CLK0`, `ETH_RMII` -- which, like SigmaStar's `I2C1_MODE_3`, names a bus and not a wire. +### Xiongmai -- an enable bit per alternative + +XM510 and XM530/XM550 keep a pad's mux in the GPIO block at `0x10020000`, as +one enable bit per alternative with all bits clear meaning GPIO. They differ +only in where the bits live: + +| part | register | a pad's field | +|---|---|---| +| XM530/XM550 | `0x10020000 + 4 * pad`, one per pad, pads 0..99 | bits 1-3, MUX1..MUX3 (`GPIO_MUX1_EN`..`GPIO_MUX3_EN` in the vendor's `mach-xm530/platform.h`); the same word holds the drive strength and GPIO state | +| XM510 | `0x10020000 + 4 * port`, one per port of eight pins, ports 0..8 (`GPIOn_MULT_USE_EN`) | pin `b`'s bits `b`, `8 + b` and `16 + b` | + +So it is HiSilicon's shape -- one register, one field, every row a plain +read-modify-write that leaves the rest of the word alone -- with a bit per +function where HiSilicon has a selector nibble. Hence `func` for MUX1 on +XM530 is `0x2`, and `gpio mux 6 2` and `gpio mux 6 FUNC1` mean the same. + +Xiongmai publishes no table of what each pad's alternatives carry, so rows are +named after the selector -- `FUNC1`, `FUNC2`, `FUNC3` -- except where the +vendor's own code says what it puts there, under its own names: + +| part | pads | alternative | names | source | +|---|---|---|---|---| +| XM530/XM550 | 9..14 | MUX2 | `FLASH_CLK`, `FLASH_CSN`, `FLASH_DI`, `FLASH_DO`, `FLASH_WPN`, `FLASH_HOLD` | `platform.h` `GPIO_FLASH_*`, `xmsfc_v2.c` | +| XM530/XM550 | 43..50, 52 | MUX1 | `SDIO0_CLK`, `SDIO0_CMD`, `SDIO0_DATA0`..`3`, `SDIO0_DETECT`, `SDIO0_PWSV`, `SDIO0_LENON` | `sdio0_sd.c` `sdio0_powerup()` | +| XM510 | `5_0`..`5_3` | 1st | `SDIO1` | `sdio1_sd.c`, port 5's word | +| XM510 | `6_0`..`6_3` | 1st | `SDIO2` | `sdio2_wifi.c`, port 6's word | + +XM510's SD and Wi-Fi drivers write a port's whole word and never say which pin +is clock, command or data, so those pads carry the bus's name, as SigmaStar's +modes do. `chip_generation` is `CHIP_XM510` or `CHIP_XM530` (XM550 is the +XM530 die), set by `xm_detect_cpu()`. + ## Pad numbers and names `gpio_pad` is the number the kernel uses, and it is the number to speak to @@ -361,6 +393,8 @@ every consumer: | SigmaStar | the vendor pad id, which is the gpiochip index (base 0) | `"PAD_SR_IO03"` | | Ingenic | `port * 32 + pin` | `"PB25"` | | Novatek | the Linux GPIO number, `group * 32 + pin` | `"P_GPIO22"` | +| Xiongmai XM530/XM550 | the pad, as the vendor's `GPIO N` | `"GPIO43"` | +| Xiongmai XM510 | `port * 8 + pin` | `"GPIO8_1"` | Function names are the vendor's own and are **not portable**, not even within HiSilicon: `PWM_OUT0` on V1, `PWM0` from V2, `PWM0_OUT1` on V5. `"PWM"` also @@ -538,6 +572,31 @@ in. that is their report, without posted output. NA51055 and NA51084 have not been run at all. + Xiongmai has, on a lab XM510 + JX-H62 and a lab XM550: + - **XM510:** `reginfo --pads` agreed with every port's mux word read + through `devmem`: `0_4`/`0_5` and `1_0`/`1_2` on their 2nd function, + `1_4`/`1_5` on their 3rd, `5_0`..`5_3` as `SDIO1`, `7_0` on its 1st, + everything else GPIO. `gpio mux 3_0 FUNC1` set port 3's bit 0 and + nothing else, and `gpio mux 3_0 GPIO` cleared it. + - **XM550:** all 100 pads agreed with their registers, compared by + script. The SPI flash pads showed as `FLASH_*`; the SD pads 43..48 and + 52 as `SDIO0_*`. `gpio mux 51 FUNC3` took pad 51 from `0x80` (8 mA + drive) to `0x88` and `gpio mux 51 GPIO` back to `0x80`. + +- **Most Xiongmai alternatives are unnamed.** Only the pads above are named, + because nothing public says what MUX1..3 carry anywhere else. Two groups + are left as selectors on purpose, though the vendor touches them: + - XM510 `0_4`/`0_5`, which `mach-xm510/core.c` moves from their 1st to + their 2nd function at boot, beside the UART clock setup, without saying + what it is; + - XM510 `5_4`..`5_7`, which `sdio1_sd.c` puts on their 1st function for + card detect, power save, write protect and LED -- but its two branches + disagree on which pin is which. + + XM510's port 8 word at `+0x20` is inferred from the layout of the other + eight; the vendor only ever names `GPIO78_MULT_USE_EN` (`+0x1C`), and on + the lab board `+0x20` reads zero. + ## Adding a family 1. Find the vendor's tables and check which of the three mechanisms it is. diff --git a/src/hal/xm.c b/src/hal/xm.c index ab65cee5..effba6df 100644 --- a/src/hal/xm.c +++ b/src/hal/xm.c @@ -6,7 +6,9 @@ #include #include +#include "chipid.h" #include "hal/common.h" +#include "hal/xm.h" #include "ram.h" #include "tools.h" @@ -145,6 +147,7 @@ bool xm_detect_cpu(char *chip_name, uint32_t base) { *ptr = toupper(*ptr); ptr++; } + chip_generation = !strcmp(chip_name, "XM510") ? CHIP_XM510 : CHIP_XM530; if (!strcmp(chip_name, "XM530")) { uint32_t reg; if (mem_reg(0x100B001C, (uint32_t *)®, OP_READ)) { diff --git a/src/hal/xm.h b/src/hal/xm.h index 3f809124..25d8750c 100644 --- a/src/hal/xm.h +++ b/src/hal/xm.h @@ -1,8 +1,16 @@ #ifndef HAL_XM_H #define HAL_XM_H +#include #include +/* chip_generation for the two pad layouts (hal/xm_padmux.c). XM550 is the + * XM530 die with more DRAM and shares its pad controller. */ +enum { + CHIP_XM510 = 0x584D0510, /* "XM" 510 */ + CHIP_XM530 = 0x584D0530, +}; + #define CMD_I2C_WRITE 0x01 #define CMD_I2C_READ 0x03 diff --git a/src/hal/xm_padmux.c b/src/hal/xm_padmux.c new file mode 100644 index 00000000..a391716a --- /dev/null +++ b/src/hal/xm_padmux.c @@ -0,0 +1,245 @@ +/* Xiongmai XM510 and XM530/XM550: the pad-mux backend. + * + * Both parts select a pad's function with one enable bit per alternative, + * all clear meaning the pad is a GPIO, in the GPIO block at 0x10020000. They + * differ in where those bits live: + * + * XM530/XM550 one register per pad at 0x10020000 + 4 * pad. Bits 1, 2 + * and 3 are MUX1, MUX2 and MUX3 (mach-xm530 platform.h's + * GPIO_MUX1_EN..GPIO_MUX3_EN); the same register carries the + * pad's drive strength and GPIO state, which a mux write has + * to leave alone. Pad N is GPIO N. + * XM510 one register per PORT of eight pins at 0x10020000 + 4 * + * port, ports 0..8 (the vendor's GPIOn_MULT_USE_EN words). + * Pin b's three alternatives are bits b, 8 + b and 16 + b; + * pad P * 8 + b is GPIO P_b, the vendor's "gpio81" for 8_1. + * + * So in both a pad's alternatives are values of one field in one register, + * and every row is a plain read-modify-write -- the HiSilicon shape, with a + * bit per function instead of a selector nibble. + * + * Xiongmai publishes no table of what MUX1..3 carry on each pad. The rows are + * therefore named after the selector -- FUNC1, FUNC2, FUNC3 -- except where + * the vendor's own code says what it puts there, and those names are its + * own: + * + * XM530 9..14 MUX2 FLASH_CLK..FLASH_HOLD platform.h GPIO_FLASH_*, + * xmsfc_v2.c + * 43..50, 52 SDIO0_CLK..SDIO0_LENON sdio0_sd.c sdio0_powerup() + * MUX1 + * XM510 5_0..5_3 SDIO1 sdio1_sd.c, port 5's word + * 6_0..6_3 SDIO2 sdio2_wifi.c, port 6's word + * + * XM510's SD and Wi-Fi drivers mux a port's whole word and do not say which + * pin is clock, command or data, so those pads carry the bus's name. */ + +#include +#include +#include + +#include "chipid.h" +#include "hal/xm.h" +#include "padmux.h" + +#define XM_GPIO_BASE 0x10020000u + +#define XM530_PADS 100 +#define XM510_PORTS 9 +#define XM510_PADS (XM510_PORTS * 8) + +#define XM530_MUX_MASK 0xEu /* MUX1..MUX3, bits 1-3 */ + +/* A function the vendor names, on one pad and one of its three selectors. */ +typedef struct { + int pad; + int alt; /* 1..3 */ + const char *name; +} xm_named_t; + +static const xm_named_t xm530_named[] = { + {9, 2, "FLASH_CLK"}, {10, 2, "FLASH_CSN"}, {11, 2, "FLASH_DI"}, + {12, 2, "FLASH_DO"}, {13, 2, "FLASH_WPN"}, {14, 2, "FLASH_HOLD"}, + {43, 1, "SDIO0_CLK"}, {44, 1, "SDIO0_CMD"}, {45, 1, "SDIO0_DATA0"}, + {46, 1, "SDIO0_DATA1"}, {47, 1, "SDIO0_DATA2"}, {48, 1, "SDIO0_DATA3"}, + {49, 1, "SDIO0_DETECT"}, {50, 1, "SDIO0_PWSV"}, {52, 1, "SDIO0_LENON"}, +}; + +static const xm_named_t xm510_named[] = { + {5 * 8 + 0, 1, "SDIO1"}, {5 * 8 + 1, 1, "SDIO1"}, {5 * 8 + 2, 1, "SDIO1"}, + {5 * 8 + 3, 1, "SDIO1"}, {6 * 8 + 0, 1, "SDIO2"}, {6 * 8 + 1, 1, "SDIO2"}, + {6 * 8 + 2, 1, "SDIO2"}, {6 * 8 + 3, 1, "SDIO2"}, +}; + +static const char *const ALT_NAMES[4] = {NULL, "FUNC1", "FUNC2", "FUNC3"}; + +/* The pad names, "GPIO43" or "GPIO8_1", built once: a row hands out a + * `const char *` that has to outlive the call. */ +static char xm530_gpio_names[XM530_PADS][8]; +static char xm510_gpio_names[XM510_PADS][8]; + +static bool is_xm510(void) { return chip_generation == CHIP_XM510; } + +static bool is_xm530(void) { return chip_generation == CHIP_XM530; } + +static int npads(void) { + return is_xm510() ? XM510_PADS : is_xm530() ? XM530_PADS : 0; +} + +static const char *gpio_name(int pad) { + if (is_xm510()) { + char *s = xm510_gpio_names[pad]; + if (!*s) + snprintf(s, sizeof(xm510_gpio_names[0]), "GPIO%d_%d", pad / 8, + pad % 8); + return s; + } + char *s = xm530_gpio_names[pad]; + if (!*s) + snprintf(s, sizeof(xm530_gpio_names[0]), "GPIO%d", pad); + return s; +} + +static uint32_t reg_addr(int pad) { + return XM_GPIO_BASE + 4u * (uint32_t)(is_xm510() ? pad / 8 : pad); +} + +/* The field holding this pad's alternatives, in place. */ +static uint32_t field_mask(int pad) { + if (is_xm510()) { + unsigned b = (unsigned)pad % 8; + return (1u << b) | (1u << (8 + b)) | (1u << (16 + b)); + } + return XM530_MUX_MASK; +} + +/* Alternative 1..3 of this pad, in place; 0 is GPIO. */ +static uint32_t alt_value(int pad, int alt) { + if (alt == 0) + return 0; + if (is_xm510()) + return 1u << ((unsigned)(alt - 1) * 8 + (unsigned)pad % 8); + return 1u << alt; +} + +static const char *alt_name(int pad, int alt) { + const xm_named_t *t = is_xm510() ? xm510_named : xm530_named; + size_t n = is_xm510() ? sizeof(xm510_named) / sizeof(*xm510_named) + : sizeof(xm530_named) / sizeof(*xm530_named); + + for (size_t i = 0; i < n; i++) + if (t[i].pad == pad && t[i].alt == alt) + return t[i].name; + return ALT_NAMES[alt]; +} + +static ipchw_padmux_t row(int pad, int alt) { + ipchw_padmux_t r = { + .address = reg_addr(pad), + .func_mask = field_mask(pad), + .func = (int)alt_value(pad, alt), + .func_name = alt == 0 ? gpio_name(pad) : alt_name(pad, alt), + .gpio_name = gpio_name(pad), + .gpio_pad = pad, + .gpio_func = 0, + .flags = IPCHW_PADMUX_F_RMW | IPCHW_PADMUX_F_SHARED_REG | + (alt == 0 ? IPCHW_PADMUX_F_GPIO : 0), + }; + return r; +} + +static int xm_walk(padmux_match_fn match, const void *arg, int pad, + ipchw_padmux_t *out, int max) { + int n = npads(); + if (n == 0) + return IPCHW_PADMUX_NO_TABLE; + if (pad >= n) + return 0; + + int found = 0; + for (int p = pad < 0 ? 0 : pad; p < (pad < 0 ? n : pad + 1); p++) { + for (int alt = 0; alt <= 3; alt++) { + ipchw_padmux_t r = row(p, alt); + if (match != NULL && !match(r.func_name, arg)) + continue; + if (found < max) + out[found] = r; + found++; + } + } + return found; +} + +static int xm_get(int pad, ipchw_padmux_t *out, const padmux_io_t *io) { + int n = npads(); + if (n == 0) + return IPCHW_PADMUX_NO_TABLE; + if (pad < 0 || pad >= n) + return IPCHW_PADMUX_NO_PAD; + + uint32_t v; + if (!io->read(reg_addr(pad), &v, 32)) + return IPCHW_PADMUX_IO; + + uint32_t sel = v & field_mask(pad); + for (int alt = 0; alt <= 3; alt++) { + if (sel == alt_value(pad, alt)) { + *out = row(pad, alt); + return 1; + } + } + return 0; /* more than one enable set: nothing this table names */ +} + +static int xm_set(int pad, const char *func_name, const padmux_io_t *io) { + int n = npads(); + if (n == 0) + return IPCHW_PADMUX_NO_TABLE; + if (pad < 0 || pad >= n) + return IPCHW_PADMUX_NO_PAD; + + int alt = -1; + if (!strcmp(func_name, IPCHW_PADMUX_GPIO)) + alt = 0; + for (int a = 0; a <= 3 && alt < 0; a++) + if (!strcmp(func_name, row(pad, a).func_name)) + alt = a; + if (alt < 0) + return IPCHW_PADMUX_NO_FUNC; + + uint32_t v; + if (!io->read(reg_addr(pad), &v, 32)) + return IPCHW_PADMUX_IO; + v = (v & ~field_mask(pad)) | alt_value(pad, alt); + if (!io->write(reg_addr(pad), v, 32)) + return IPCHW_PADMUX_IO; + return 0; +} + +/* XM530 numbers pads as the vendor does, GPIO N, so the HiSilicon "5_2" is + * refused there: it would land on pad 42. XM510 is bank-of-eight, which is + * exactly that spelling. Both also take the vendor's own "GPIO..." form. */ +static int xm_parse_pad(const char *spec) { + int a, b, end = 0; + const char *s = strncmp(spec, "GPIO", 4) == 0 ? spec + 4 : spec; + + if (is_xm510() && sscanf(s, "%d_%d%n", &a, &b, &end) == 2 && + s[end] == '\0') { + if (a < 0 || a >= XM510_PORTS || b < 0 || b > 7) + return -1; + return a * 8 + b; + } + + char *tail; + long v = strtol(s, &tail, 10); + if (tail == s || *tail != '\0' || v < 0 || v >= npads()) + return -1; + return (int)v; +} + +const padmux_ops_t PADMUX_OPS_XM = { + .name = "xm", + .walk = xm_walk, + .get = xm_get, + .set = xm_set, + .parse_pad = xm_parse_pad, +}; diff --git a/src/padmux.c b/src/padmux.c index a5e6493e..62847e5a 100644 --- a/src/padmux.c +++ b/src/padmux.c @@ -15,6 +15,7 @@ #include "hal/ingenic.h" #include "hal/novatek.h" #include "hal/sstar.h" +#include "hal/xm.h" #include "tools.h" #include @@ -68,6 +69,11 @@ const padmux_ops_t *padmux_ops(void) { case CHIP_NA51084: case CHIP_NA51089: return &PADMUX_OPS_NOVATEK; +#endif +#ifdef IPCHW_PADMUX_XM + case CHIP_XM510: + case CHIP_XM530: + return &PADMUX_OPS_XM; #endif default: break; diff --git a/src/padmux.h b/src/padmux.h index 36ccff24..5dcc32f2 100644 --- a/src/padmux.h +++ b/src/padmux.h @@ -26,6 +26,9 @@ * pad that has to be cleared before any of them reaches * it. One pad's function is a field in one register and a * bit in another. + * Xiongmai HiSilicon's shape with an enable bit per alternative + * instead of a selector: the pad's own register on + * XM530/XM550, the port's word of eight pins on XM510. * * What a caller sees is a flat ipchw_padmux_t in every case. * ---------------------------------------------------------------------- */ @@ -86,6 +89,9 @@ extern const padmux_ops_t PADMUX_OPS_INGENIC; #ifdef IPCHW_PADMUX_NOVATEK extern const padmux_ops_t PADMUX_OPS_NOVATEK; #endif +#ifdef IPCHW_PADMUX_XM +extern const padmux_ops_t PADMUX_OPS_XM; +#endif /* The backend for the SoC that has been detected, or NULL when this build * carries no pad-mux table that could serve it. */ diff --git a/src/reginfo_test.c b/src/reginfo_test.c index dce2dbfa..91cb9586 100644 --- a/src/reginfo_test.c +++ b/src/reginfo_test.c @@ -16,6 +16,7 @@ #include "hal/novatek_gpio.h" #include "hal/sstar.h" #include "hal/sstar_gpio.h" +#include "hal/xm.h" #include "ipchw.h" #include "padmux.h" #include "reginfo.h" @@ -1375,6 +1376,10 @@ static void test_table_integrity(void) { {CHIP_NA51089, "NT98566"}, {CHIP_NA51055, "NA51055"}, {CHIP_NA51084, "NT98528"}, +#endif +#ifdef IPCHW_PADMUX_XM + {CHIP_XM510, "XM510"}, + {CHIP_XM530, "XM550"}, #endif }; @@ -1487,6 +1492,85 @@ static void test_gpio_windows_in_mapping(void) { CHECK(gpio_windows_in_mapping(0x1250, 0x20, base, stride, 3) == 0x4); } +#ifdef IPCHW_PADMUX_XM +/* Xiongmai: a bit per alternative in the pad's (XM530) or the port's (XM510) + * register. The values seeded below are the lab boards' registers at rest, + * so get() is checked against what a camera actually holds. */ +static void test_xm(void) { + ipchw_padmux_t r; + + puts("Xiongmai XM530/XM550: MUX1..3 in the pad's own register"); + as_chip(CHIP_XM530, "XM550"); + CHECK(padmux_parse_pad("43") == 43); + CHECK(padmux_parse_pad("GPIO43") == 43); + CHECK(padmux_parse_pad("5_2") == -1); /* HiSilicon's banking, pad 42 */ + CHECK(padmux_parse_pad("100") == -1); + CHECK(ipchw_padmux_get(100, &r) == IPCHW_PADMUX_NO_PAD); + + regs_reset(); + fake_write(0x10020000 + 4 * 43, 0x42, 32); /* sdio0_clk: MUX1, 4 mA */ + fake_write(0x10020000 + 4 * 9, 0x84, 32); /* FLASH_CLK: MUX2, 8 mA */ + fake_write(0x10020000 + 4 * 1, 0xC00, 32); /* IR-cut pad: output high */ + fake_write(0x10020000 + 4 * 20, 0x6, 32); /* two enables: no name */ + CHECK(ipchw_padmux_get(43, &r) == 1 && !strcmp(r.func_name, "SDIO0_CLK")); + CHECK(r.func_mask == 0xE && r.func == 0x2 && r.address == 0x100200AC); + CHECK(ipchw_padmux_get(9, &r) == 1 && !strcmp(r.func_name, "FLASH_CLK")); + CHECK(ipchw_padmux_get(1, &r) == 1 && (r.flags & IPCHW_PADMUX_F_GPIO)); + CHECK(!strcmp(r.func_name, "GPIO1") && !strcmp(r.gpio_name, "GPIO1")); + CHECK(ipchw_padmux_get(20, &r) == 0); + CHECK(ipchw_padmux_get(30, &r) == 1 && (r.flags & IPCHW_PADMUX_F_GPIO)); + + /* A mux write leaves the drive strength and the GPIO state alone. */ + CHECK(ipchw_padmux_set(1, "FUNC3") == 0); + CHECK(reg_of(0x10020004) == (0xC00 | 0x8)); + CHECK(ipchw_padmux_set(1, IPCHW_PADMUX_GPIO) == 0); + CHECK(reg_of(0x10020004) == 0xC00); + CHECK(ipchw_padmux_set(43, "GPIO43") == 0); + CHECK(reg_of(0x100200AC) == 0x40); + CHECK(ipchw_padmux_set(43, "SDIO0_CLK") == 0); + CHECK(reg_of(0x100200AC) == 0x42); + /* A named alternative is not also offered under its selector's name. */ + CHECK(ipchw_padmux_set(43, "FUNC1") == IPCHW_PADMUX_NO_FUNC); + CHECK(ipchw_padmux_set(43, "FLASH_CLK") == IPCHW_PADMUX_NO_FUNC); + + puts("Xiongmai XM510: three enables per pin in its port's word"); + as_chip(CHIP_XM510, "XM510"); + CHECK(padmux_parse_pad("8_1") == 65); + CHECK(padmux_parse_pad("GPIO8_1") == 65); + CHECK(padmux_parse_pad("65") == 65); + CHECK(padmux_parse_pad("9_0") == -1); + CHECK(padmux_parse_pad("72") == -1); + + regs_reset(); + fake_write(0x10020000, 0x3000, 32); /* port 0: pins 4, 5 on their 2nd */ + fake_write(0x10020004, 0x300500, 32); /* port 1: 0, 2 on 2nd; 4, 5 on 3rd */ + fake_write(0x10020014, 0xF, 32); /* port 5: the SD slot */ + fake_write(0x1002001C, 0x1, 32); /* port 7: pin 0 on its 1st */ + CHECK(ipchw_padmux_get(4, &r) == 1 && !strcmp(r.func_name, "FUNC2")); + CHECK(r.address == 0x10020000 && r.func_mask == 0x101010 && + r.func == 0x1000); + CHECK(ipchw_padmux_get(8 + 0, &r) == 1 && !strcmp(r.func_name, "FUNC2")); + CHECK(ipchw_padmux_get(8 + 4, &r) == 1 && !strcmp(r.func_name, "FUNC3")); + CHECK(ipchw_padmux_get(8 + 1, &r) == 1 && (r.flags & IPCHW_PADMUX_F_GPIO)); + CHECK(!strcmp(r.gpio_name, "GPIO1_1")); + CHECK(ipchw_padmux_get(40, &r) == 1 && !strcmp(r.func_name, "SDIO1")); + CHECK(ipchw_padmux_get(44, &r) == 1 && (r.flags & IPCHW_PADMUX_F_GPIO)); + CHECK(ipchw_padmux_get(56, &r) == 1 && !strcmp(r.func_name, "FUNC1")); + CHECK(ipchw_padmux_get(65, &r) == 1 && (r.flags & IPCHW_PADMUX_F_GPIO)); + + /* One pin's bits only; the port's other pins keep theirs. */ + CHECK(ipchw_padmux_set(56, "FUNC2") == 0); + CHECK(reg_of(0x1002001C) == 0x100); + CHECK(ipchw_padmux_set(41, IPCHW_PADMUX_GPIO) == 0); + CHECK(reg_of(0x10020014) == 0xD); + CHECK(ipchw_padmux_set(41, "SDIO1") == 0); + CHECK(reg_of(0x10020014) == 0xF); + CHECK(ipchw_padmux_set(52, "SDIO2") == IPCHW_PADMUX_NO_FUNC); /* 6_4 */ + + as_chip(T31, "T31"); +} +#endif + int main(void) { /* Every register the tests below touch is one of these, not a camera's. */ padmux_set_io(&FAKE_IO); @@ -1527,6 +1611,9 @@ int main(void) { #endif #ifdef IPCHW_VENDOR_NOVATEK test_novatek_gpio_regs(); +#endif +#ifdef IPCHW_PADMUX_XM + test_xm(); #endif test_refusals_do_not_exit(); test_table_integrity(); From 99a4a42881900b45020ae27da3356fd381261a7a Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Mon, 19 Oct 2026 19:18:38 +0300 Subject: [PATCH 2/2] xm: pad names as constant data, a layout only for known parts, no backend without the HAL Review of the first cut. - Pad names were written into shared buffers on first lookup. include/ipchw.h promises lookups read only constant data and may run concurrently, so they are now fixed-width const tables built at compile time. - xm_detect_cpu() gave every Xiongmai name but XM510 the XM530 layout. It now sets a generation for exactly XM510 and XM530 (XM550 is that die), and any other part keeps 0, so pad mux reports no table rather than writing registers on a layout nobody checked. - The backend is compiled into libipchw only when the Xiongmai HAL is: only that HAL sets the generations that reach it. Every camera's libipchw is built with the default 'all' tables, and without this each non-Xiongmai one would have carried code nothing can call. Measured as the firmware builds it (shared, -Os) on arm32: ipcinfo is unchanged for IPCHW_VENDORS=none, sstar and ingenic, and xm_padmux.o is absent from their libipchw.a. Xiongmai's own ipcinfo grows 102 bytes. --- CMakeLists.txt | 9 +++++++++ src/hal/xm.c | 7 ++++++- src/hal/xm_padmux.c | 34 +++++++++++++++++++--------------- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 281e85ff..6a8cea68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,6 +98,15 @@ foreach(_f IN LISTS _padmux_selected) string(TOUPPER ${_f} _F) list(APPEND IPCHW_PADMUX_DEFS IPCHW_PADMUX_${_F}) endforeach() + +# Xiongmai's backend is reached only through a chip_generation that only the +# Xiongmai HAL sets, so without that HAL it is code nothing can call. Every +# camera's libipchw is built with the default 'all' tables; this keeps the +# Xiongmai one out of all of them but Xiongmai's own. +if("xm" IN_LIST _padmux_selected AND NOT "xm" IN_LIST _ipchw_selected) + list(REMOVE_ITEM _padmux_selected xm) + list(REMOVE_ITEM IPCHW_PADMUX_DEFS IPCHW_PADMUX_XM) +endif() message(STATUS "libipchw pad-mux tables: ${_padmux_selected}") # The vendors whose pad-mux is its own backend rather than a table in diff --git a/src/hal/xm.c b/src/hal/xm.c index effba6df..a489323f 100644 --- a/src/hal/xm.c +++ b/src/hal/xm.c @@ -147,8 +147,13 @@ bool xm_detect_cpu(char *chip_name, uint32_t base) { *ptr = toupper(*ptr); ptr++; } - chip_generation = !strcmp(chip_name, "XM510") ? CHIP_XM510 : CHIP_XM530; + /* Only the two layouts hal/xm_padmux.c knows. Any other name keeps + * generation 0, and pad mux answers that it has no table rather than + * reading registers on a layout nobody checked. */ + if (!strcmp(chip_name, "XM510")) + chip_generation = CHIP_XM510; if (!strcmp(chip_name, "XM530")) { + chip_generation = CHIP_XM530; /* XM550 is the same die */ uint32_t reg; if (mem_reg(0x100B001C, (uint32_t *)®, OP_READ)) { // bank_size, 0x2 for 512M and 0x3 for 1024M diff --git a/src/hal/xm_padmux.c b/src/hal/xm_padmux.c index a391716a..4b4e4c78 100644 --- a/src/hal/xm_padmux.c +++ b/src/hal/xm_padmux.c @@ -72,10 +72,24 @@ static const xm_named_t xm510_named[] = { static const char *const ALT_NAMES[4] = {NULL, "FUNC1", "FUNC2", "FUNC3"}; -/* The pad names, "GPIO43" or "GPIO8_1", built once: a row hands out a - * `const char *` that has to outlive the call. */ -static char xm530_gpio_names[XM530_PADS][8]; -static char xm510_gpio_names[XM510_PADS][8]; +/* The pad names, "GPIO43" or "GPIO8_1", as constant data: a row hands out a + * `const char *` that has to outlive the call, and lookups promise + * (include/ipchw.h) to read nothing but constant data. */ +#define XM_DEC(p) \ + p "0", p "1", p "2", p "3", p "4", p "5", p "6", p "7", p "8", p "9" +#define XM_PORT(p) p "0", p "1", p "2", p "3", p "4", p "5", p "6", p "7" + +static const char xm530_gpio_names[XM530_PADS][7] = { + XM_DEC("GPIO"), XM_DEC("GPIO1"), XM_DEC("GPIO2"), XM_DEC("GPIO3"), + XM_DEC("GPIO4"), XM_DEC("GPIO5"), XM_DEC("GPIO6"), XM_DEC("GPIO7"), + XM_DEC("GPIO8"), XM_DEC("GPIO9"), +}; + +static const char xm510_gpio_names[XM510_PADS][8] = { + XM_PORT("GPIO0_"), XM_PORT("GPIO1_"), XM_PORT("GPIO2_"), + XM_PORT("GPIO3_"), XM_PORT("GPIO4_"), XM_PORT("GPIO5_"), + XM_PORT("GPIO6_"), XM_PORT("GPIO7_"), XM_PORT("GPIO8_"), +}; static bool is_xm510(void) { return chip_generation == CHIP_XM510; } @@ -86,17 +100,7 @@ static int npads(void) { } static const char *gpio_name(int pad) { - if (is_xm510()) { - char *s = xm510_gpio_names[pad]; - if (!*s) - snprintf(s, sizeof(xm510_gpio_names[0]), "GPIO%d_%d", pad / 8, - pad % 8); - return s; - } - char *s = xm530_gpio_names[pad]; - if (!*s) - snprintf(s, sizeof(xm530_gpio_names[0]), "GPIO%d", pad); - return s; + return is_xm510() ? xm510_gpio_names[pad] : xm530_gpio_names[pad]; } static uint32_t reg_addr(int pad) {