diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ee04ae..7076535 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -278,7 +278,8 @@ set(_common_lib_src_tail # Full set, used by the ipctool executable set(COMMON_LIB_SRC_ALL ${_common_lib_src_head} ${IPCHW_HISI_SRC_ALL} - ${_common_lib_src_tail} ${IPCHW_PADMUX_BACKENDS_ALL}) + ${_common_lib_src_tail} ${IPCHW_PADMUX_BACKENDS_ALL} + src/hal/sstar_gpio.c src/hal/sstar_gpio.h) foreach(_v IN LISTS IPCHW_OPTIONAL_VENDORS) list(APPEND COMMON_LIB_SRC_ALL src/hal/${_v}.c src/hal/${_v}.h) string(TOUPPER ${_v} _V) @@ -295,6 +296,9 @@ foreach(_v IN LISTS _ipchw_selected) "expected 'all', 'none', or a subset of ${IPCHW_OPTIONAL_VENDORS}") endif() list(APPEND COMMON_LIB_SRC src/hal/${_v}.c src/hal/${_v}.h) + if(_v STREQUAL "sstar") + list(APPEND COMMON_LIB_SRC src/hal/sstar_gpio.c src/hal/sstar_gpio.h) + endif() string(TOUPPER ${_v} _V) list(APPEND IPCHW_VENDOR_DEFS IPCHW_VENDOR_${_V}) endforeach() diff --git a/docs/gpio.md b/docs/gpio.md index cc97530..d3388b7 100644 --- a/docs/gpio.md +++ b/docs/gpio.md @@ -15,6 +15,14 @@ ipctool gpio set # WRITES: drive the pad `` is accepted in either spelling, `5_6` (group 5, bit 6) or `46` (linear, = group * 8 + bit). Both appear in `gpio scan` output. +On SigmaStar the two spellings are one: pads are already numbered linearly and +there is no `group_bit` form. The pad number is the number the kernel's +gpiochip uses, so `gpio get 23` and `/sys/class/gpio/gpio23` describe the same +wire. The `gpio scan` baseline differs in shape as well — one `Pad:` line per +GPIO pad, decoded from the pad's own one-byte register into what it reads +(`in`), what the SoC is driving (`out`) and whether it drives at all (`oe_n`, +1 meaning input/Hi-Z) — rather than the per-group data and direction words. + ## `gpio scan` It prints a baseline table of every GPIO pad, then polls in a loop and reports @@ -110,6 +118,11 @@ to reporting every output currently sitting low, which is a much broader guess. So a short list means the first rule fired and is worth trusting; a long one means the second did. +On SigmaStar the two rules survive with pads in place of groups: when the +streamer maps the GPIO block, a board whose whole pad list holds only one or +two driving pads gets exactly those, and without a streamer every pad +currently driving low is reported. + IR-cut is nearly always a *pair* of pads, driven in opposite directions to flip the filter between its two positions. To confirm a pair, run `gpio scan` and switch the camera between day and night mode in the web UI. Both pads change in diff --git a/src/hal/sstar_gpio.c b/src/hal/sstar_gpio.c new file mode 100644 index 0000000..1d6c1b3 --- /dev/null +++ b/src/hal/sstar_gpio.c @@ -0,0 +1,95 @@ +/* SigmaStar per-pad GPIO registers. + * + * Where HiSilicon banks eight pins behind one data and one direction word, + * SigmaStar's Infinity6C gives every pad its own one-byte register: bit 0 is + * the pin level, bit 1 the output value, and bit 2 the output enable, which + * on this family reads 1 while the pad drives Hi-Z (an input) and 0 while it + * drives. Like the pad-mux ports these registers are 16-bit slots in + * four-byte RIU holes, so they are reached with the 16-bit accessors and a + * 32-bit store would write two bytes that do not exist. + * + * The table below is the one the vendor's own driver carries + * (drivers/sstar/gpio/infinity6c/mhal_gpio.c in the SigmaStar kernel, GPL-2) + * converted from RIU byte offset r to physical 0x1F207C00 + 2 * r, and the + * result was measured against a live Infinity6C board: writing pads 12 and 30 + * through sysfs moves their bytes at exactly these addresses, and the idle + * levels of pads 10, 23 and 40/41 read back the states their exporters had + * left. The four PAD_ETH_* pads the vendor adds by hand sit outside the + * gpiochip range and are not listed. */ + +#include "hal/sstar_gpio.h" + +#include + +#include "chipid.h" +#include "hal/sstar.h" +#include "tools.h" + +static const uint32_t i6c_gpio_regs[] = { + 0x1F207C00, 0x1F207C04, 0x1F207C08, 0x1F207C0C, 0x1F207C10, 0x1F207C14, + 0x1F207C18, 0x1F207C1C, 0x1F207C20, 0x1F207C24, 0x1F207C28, 0x1F207C2C, + 0x1F207C30, 0x1F207C34, 0x1F207C38, 0x1F207C3C, 0x1F207C40, 0x1F207C44, + 0x1F207C48, 0x1F207C4C, 0x1F207C50, 0x1F207C54, 0x1F207C58, 0x1F207C5C, + 0x1F207C64, 0x1F207C68, 0x1F207C6C, 0x1F207C70, 0x1F207C74, 0x1F207C78, + 0x1F207C7C, 0x1F207C80, 0x1F207C84, 0x1F207C88, 0x1F207C8C, 0x1F207C90, + 0x1F207C94, 0x1F207C98, 0x1F207C9C, 0x1F207CA0, 0x1F207CA4, 0x1F207CA8, + 0x1F207CC4, 0x1F207CC8, 0x1F207CCC, 0x1F207CD0, 0x1F207CD4, 0x1F207CD8, + 0x1F207CDC, 0x1F207CE0, 0x1F207CE4, 0x1F207CE8, 0x1F207CEC, 0x1F207CF0, + 0x1F207CF4, 0x1F207CF8, 0x1F207CFC, 0x1F207D00, 0x1F207D04, 0x1F207D08, + 0x1F207D0C, 0x1F207D10, 0x1F207D14, 0x1F207D18, 0x1F207D1C, 0x1F207D20, + 0x1F207D24, 0x1F207D28, 0x1F207D2C, 0x1F207D30, 0x1F207D34, 0x1F207D38, + 0x1F207D3C, 0x1F207D40, 0x1F207D44, 0x1F207D48, 0x1F207D4C, 0x1F207D50, + 0x1F207D54, 0x1F207D58, 0x1F207D5C, 0x1F207D60, +}; + +bool sstar_gpio_supported(void) { + switch (chip_generation) { + case INFINITY6C: + return true; + default: + return false; + } +} + +int sstar_gpio_num_pads(void) { + if (!sstar_gpio_supported()) + return 0; + return (int)(sizeof(i6c_gpio_regs) / sizeof(i6c_gpio_regs[0])); +} + +uint32_t sstar_gpio_pad_addr(int pad) { + if (pad < 0 || pad >= sstar_gpio_num_pads()) + return 0; + return i6c_gpio_regs[pad]; +} + +bool sstar_gpio_window(uint32_t *base, uint32_t *len) { + int nr_pads = sstar_gpio_num_pads(); + if (!nr_pads) + return false; + *base = sstar_gpio_pad_addr(0) & ~0xfffu; + *len = sstar_gpio_pad_addr(nr_pads - 1) + 4 - *base; + return true; +} + +bool sstar_gpio_read(int pad, uint8_t *val) { + uint32_t addr = sstar_gpio_pad_addr(pad); + if (!addr) + return false; + uint32_t data; + if (!mem_reg(addr, &data, OP_READ_16)) + return false; + *val = (uint8_t)(data & 0xff); + return true; +} + +bool sstar_gpio_write(int pad, uint8_t val) { + uint32_t addr = sstar_gpio_pad_addr(pad); + if (!addr) + return false; + uint32_t data; + if (!mem_reg(addr, &data, OP_READ_16)) + return false; + data = (data & 0xff00) | val; + return mem_reg(addr, &data, OP_WRITE_16); +} diff --git a/src/hal/sstar_gpio.h b/src/hal/sstar_gpio.h new file mode 100644 index 0000000..86cf516 --- /dev/null +++ b/src/hal/sstar_gpio.h @@ -0,0 +1,31 @@ +#ifndef HAL_SSTAR_GPIO_H +#define HAL_SSTAR_GPIO_H + +#include +#include + +/* Bit layout of the per-pad register, as the vendor driver uses it. */ +#define SSTAR_GPIO_BIT_IN 0x01 +#define SSTAR_GPIO_BIT_OUT 0x02 +#define SSTAR_GPIO_BIT_INPUT 0x04 + +bool sstar_gpio_supported(void); +int sstar_gpio_num_pads(void); + +/* Physical address of the pad's register, or 0 when there is no table for the + * current chip family or the pad is out of range. */ +uint32_t sstar_gpio_pad_addr(int pad); + +/* The one window every pad register of the chip sits in, from the page the + * first pad lives on to the end of the last pad's slot: what a daemon has to + * have mapped to be holding the pads. False when there is no table. */ +bool sstar_gpio_window(uint32_t *base, uint32_t *len); + +/* One pad's register through /dev/mem. Reads take the low byte of the 16-bit + * slot; writes read-modify-write it and leave the upper byte alone, because a + * wider store reaches RIU bytes that are not mapped. Serialise the callers: + * mem_reg() is single-threaded by contract. */ +bool sstar_gpio_read(int pad, uint8_t *val); +bool sstar_gpio_write(int pad, uint8_t val); + +#endif /* HAL_SSTAR_GPIO_H */ diff --git a/src/reginfo.c b/src/reginfo.c index ee24102..a76ce95 100644 --- a/src/reginfo.c +++ b/src/reginfo.c @@ -3,13 +3,15 @@ #include "hal/hisi/hal_hisi.h" #include "hal/ingenic.h" #include "hal/sstar.h" +#include "hal/sstar_gpio.h" #include "padmux.h" #include "tools.h" #include "ipchw.h" -#include #include +#include +#include #include #include #include @@ -3132,6 +3134,28 @@ const padmux_ops_t PADMUX_OPS_HISI = { .set = hisi_set, }; +bool parse_gpio_level(const char *arg, unsigned *level) { + char *tail; + errno = 0; + unsigned long val = strtoul(arg, &tail, 10); + if (errno || tail == arg || *tail || val > 1) + return false; + *level = (unsigned)val; + return true; +} + +uint32_t gpio_windows_in_mapping(unsigned long off, unsigned long len, + uint32_t base, uint32_t stride, int nwin) { + uint32_t mask = 0; + for (int i = 0; i < nwin && i < 32; i++) { + uint32_t lo = base + (uint32_t)i * stride; + uint32_t hi = lo + stride; + if (off < hi && off + len > lo) + mask |= 1u << i; + } + return mask; +} + /* Everything below is the command-line half of ipctool: it prints, it parses * argv, and it calls print_usage() out of main.c. STANDALONE_LIBRARY builds * (libipchw) take the tables and the lookups above and stop here. */ @@ -3147,6 +3171,9 @@ const padmux_ops_t PADMUX_OPS_HISI = { static int gpio_mux_by(const char *gpio_number, int func_num, const char *set_func); static int padmux_refuse(int code, const char *pad_spec, const char *func); +static int sstar_gpio_getset(char **argv, bool set_op); +static int sstar_gpio_scan_cmd(void); +static char *sstar_gpio_possible_ircut(char *outbuf, size_t outlen); static void show_function(const uint16_t *func, unsigned val) { for (size_t i = 0; func[i]; i++) @@ -3405,6 +3432,8 @@ static int gpio_manipulate(char **argv, bool set_op) { size_t GPIO_Offset = 0; getchipname(); + if (sstar_gpio_supported()) + return sstar_gpio_getset(argv, set_op); if (!get_chip_gpio_adress(&GPIO_Base, &GPIO_Offset, &GPIO_Groups)) { fprintf(stderr, "Chip is not supported\n"); return EXIT_FAILURE; @@ -3426,9 +3455,13 @@ static int gpio_manipulate(char **argv, bool set_op) { uint32_t val, address = GPIO_Base + (group * GPIO_Offset) + mask; if (set_op) { - unsigned val = strtoul(argv[2], NULL, 10); - if (val > 1) + /* Refused before any register is touched: the old parse made + * `gpio set 12 foo` into a real drive-low. */ + unsigned level; + if (!parse_gpio_level(argv[2], &level)) { + fprintf(stderr, "Level '%s' is not 0 or 1\n", argv[2]); return EXIT_FAILURE; + } size_t daddress = GPIO_Base + (group * GPIO_Offset) + 0x400; uint32_t direct; @@ -3445,7 +3478,7 @@ static int gpio_manipulate(char **argv, bool set_op) { } } - uint32_t cmd = val << num; + uint32_t cmd = level << num; if (!mem_reg(address, &cmd, OP_WRITE)) { printf("write reg %#x error\n", address); return EXIT_FAILURE; @@ -3644,17 +3677,36 @@ static bool fill_enabled_gpios(size_t *enabled, size_t GPIO_Groups) { return true; } -static uint32_t find_streamer_gpio_groups(size_t GPIO_Base, size_t GPIO_Offset, - int GPIO_Groups) { - uint32_t mapped = 0; +/* A GPIO block's registers, found through a daemon's own /dev/mem mappings. + * + * One walk answers both vendors' question: HiSilicon asks which of its + * group-sized windows are visible and gets a mask; SigmaStar asks whether the + * single window its pads share is visible and reads bit 0. Any process whose + * /proc name is all digits is visited -- including init, which on a minimal + * firmware is the streamer. A mapping counts from its full interval: the + * file offset is its physical address and the vaddr span is its length, so a + * daemon holding one broad RIU window from well below is mapped here the + * same as one that mapped the registers exactly. */ +static uint32_t dev_mem_windows_marked(uint32_t base, uint32_t stride, + int nwin) { + uint32_t marked = 0; DIR *proc = opendir("/proc"); if (!proc) return 0; - size_t GPIO_End = GPIO_Base + GPIO_Groups * GPIO_Offset; + char self[16]; + snprintf(self, sizeof(self), "%d", (int)getpid()); + struct dirent *ent; while ((ent = readdir(proc))) { - if (ent->d_name[0] < '2' || ent->d_name[0] > '9') + /* Every digit-only name is a pid entry; "." and ".." and the + * non-numeric /proc files fail this test on their first character. */ + if (ent->d_name[strspn(ent->d_name, "0123456789")]) + continue; + /* Not ourselves: mem_reg() keeps its last /dev/mem window mapped, so + * after any register read ipctool holds the very window it is asking + * about and would always find a "streamer" -- itself. */ + if (!strcmp(ent->d_name, self)) continue; char path[64]; @@ -3664,21 +3716,19 @@ static uint32_t find_streamer_gpio_groups(size_t GPIO_Base, size_t GPIO_Offset, continue; char line[256]; + unsigned long start, end, off; while (fgets(line, sizeof(line), f)) { if (!strstr(line, "/dev/mem")) continue; - unsigned long offset; - if (sscanf(line, "%*x-%*x %*s %lx", &offset) != 1) + if (sscanf(line, "%lx-%lx %*s %lx", &start, &end, &off) != 3) continue; - if (offset >= GPIO_Base && offset < GPIO_End) { - int group = (offset - GPIO_Base) / GPIO_Offset; - mapped |= (1u << group); - } + marked |= + gpio_windows_in_mapping(off, end - start, base, stride, nwin); } fclose(f); } closedir(proc); - return mapped; + return marked; } char *gpio_possible_ircut(char *outbuf, size_t outlen) { @@ -3687,11 +3737,13 @@ char *gpio_possible_ircut(char *outbuf, size_t outlen) { size_t GPIO_Offset = 0; *outbuf = 0; + if (sstar_gpio_supported()) + return sstar_gpio_possible_ircut(outbuf, outlen); if (!get_chip_gpio_adress(&GPIO_Base, &GPIO_Offset, &GPIO_Groups)) return NULL; uint32_t streamer_groups = - find_streamer_gpio_groups(GPIO_Base, GPIO_Offset, GPIO_Groups); + dev_mem_windows_marked(GPIO_Base, GPIO_Offset, GPIO_Groups); size_t enabled[GPIO_Groups]; if (!fill_enabled_gpios(enabled, GPIO_Groups)) @@ -3757,6 +3809,181 @@ char *gpio_possible_ircut(char *outbuf, size_t outlen) { return NULL; } +/* Whether a vendor daemon holds this SigmaStar's pad registers through a + * raw /dev/mem mapping of its own -- the same question the HiSilicon walk + * asks of its bank words. The pad registers of one chip share a single page, + * so the answer is one bit for every pad rather than a per-group mask. */ +static bool sstar_gpio_streamer_mapped(void) { + uint32_t base, len; + if (!sstar_gpio_window(&base, &len)) + return false; + return dev_mem_windows_marked(base, len, 1) != 0; +} + +/* `gpio get/set ` on SigmaStar: the pad number is a pad, its register is + * one byte, and there are no bank words and no group_pin syntax. The mux + * line is printed here, matching where the HiSilicon path prints it. */ +static int sstar_gpio_getset(char **argv, bool set_op) { + const char *gpio_num = argv[1]; + + gpio_mux_by(gpio_num, -1, NULL); + + char *tail; + long pad = strtol(gpio_num, &tail, 10); + if (tail == gpio_num || *tail || pad < 0 || pad >= sstar_gpio_num_pads()) { + fprintf(stderr, "GPIO %s is out of range\n", gpio_num); + return EXIT_FAILURE; + } + + uint8_t val; + if (!sstar_gpio_read((int)pad, &val)) { + fprintf(stderr, "read reg %#x error\n", sstar_gpio_pad_addr((int)pad)); + return EXIT_FAILURE; + } + + if (set_op) { + /* Refused before the register changes: the old parse made + * `gpio set 12 foo` into a real drive-low. */ + unsigned level; + if (!parse_gpio_level(argv[2], &level)) { + fprintf(stderr, "Level '%s' is not 0 or 1\n", argv[2]); + return EXIT_FAILURE; + } + + if (val & SSTAR_GPIO_BIT_INPUT) + val &= (uint8_t)~SSTAR_GPIO_BIT_INPUT; + if (level) + val |= SSTAR_GPIO_BIT_OUT; + else + val &= (uint8_t)~SSTAR_GPIO_BIT_OUT; + if (!sstar_gpio_write((int)pad, val)) { + fprintf(stderr, "write reg %#x error\n", + sstar_gpio_pad_addr((int)pad)); + return EXIT_FAILURE; + } + } else { + bool is_input = val & SSTAR_GPIO_BIT_INPUT; + printf("%d\n", is_input ? !!(val & SSTAR_GPIO_BIT_IN) + : !!(val & SSTAR_GPIO_BIT_OUT)); + } + + return EXIT_SUCCESS; +} + +/* The IR-cut question on SigmaStar: the HiSilicon heuristic walks bank words + * and keeps the output pins of a lightly-loaded, streamer-held group; the + * same idea over pads keeps the output pads, pared back the same two ways -- + * to the whole block when a daemon holds it and drives at most two pins, and + * otherwise to the outputs standing at zero. */ +static char *sstar_gpio_possible_ircut(char *outbuf, size_t outlen) { + int nr_pads = sstar_gpio_num_pads(); + if (!nr_pads || ipchw_padmux_by_prefix("", NULL, 0) < 0) + return NULL; + + uint8_t levels[nr_pads]; + bool is_gpio[nr_pads]; + memset(levels, 0, sizeof(levels)); + memset(is_gpio, 0, sizeof(is_gpio)); + + int output_count = 0; + for (int pad = 0; pad < nr_pads; pad++) { + if (!padmux_pad_is_gpio(pad)) + continue; + uint8_t val; + if (!sstar_gpio_read(pad, &val)) { + fprintf(stderr, "Error at %#x\n", sstar_gpio_pad_addr(pad)); + return NULL; + } + is_gpio[pad] = true; + levels[pad] = val; + if (!(val & SSTAR_GPIO_BIT_INPUT)) + output_count++; + } + + bool streamer = sstar_gpio_streamer_mapped(); + char *ptr = outbuf; + + for (int pad = 0; pad < nr_pads; pad++) { + if (!is_gpio[pad]) + continue; + uint8_t val = levels[pad]; + if (val & SSTAR_GPIO_BIT_INPUT) + continue; + if (streamer && output_count > 2) + continue; + if (!streamer && (val & SSTAR_GPIO_BIT_OUT)) + continue; + + int nlen = snprintf(ptr, outlen, ",%d", pad); + outlen -= nlen; + ptr += nlen; + } + + if (strlen(outbuf) > 0) + return outbuf + 1; + return NULL; +} + +/* `gpio scan` on SigmaStar: watch every pad muxed to plain GPIO. The HiSilicon + * loop prints eight pins of a bank at a time; this one prints one line per + * pad, and only the three bits a pad register has. */ +static int sstar_gpio_scan_cmd(void) { + int nr_pads = sstar_gpio_num_pads(); + if (!nr_pads || ipchw_padmux_by_prefix("", NULL, 0) < 0) { + fprintf(stderr, "Platform is not supported\n"); + return EXIT_FAILURE; + } + + uint8_t state[nr_pads]; + memset(state, 0, sizeof(state)); + + for (int pad = 0; pad < nr_pads; pad++) { + if (!padmux_pad_is_gpio(pad)) + continue; + uint8_t val; + if (!sstar_gpio_read(pad, &val)) { + fprintf(stderr, "Error at %#x\n", sstar_gpio_pad_addr(pad)); + return EXIT_FAILURE; + } + state[pad] = val; + printf("Pad:%2d, Addr:0x%08x, Data:0x%02X = in:%d out:%d oe_n:%d\n", + pad, sstar_gpio_pad_addr(pad), val, val & 1, (val >> 1) & 1, + (val >> 2) & 1); + } + + print_line(86); + printf("Waiting for while something changes...\n"); + while (1) { + for (int pad = 0; pad < nr_pads; pad++) { + if (!padmux_pad_is_gpio(pad)) + continue; + uint8_t val; + if (!sstar_gpio_read(pad, &val)) { + /* A /dev/mem that has stopped reading will not start again; + * a blind watcher that retries every 100 ms and names the + * same pad each time is worse than stopping. */ + fprintf(stderr, "Error at %#x\n", sstar_gpio_pad_addr(pad)); + return EXIT_FAILURE; + } + if (val == state[pad]) + continue; + + bool is_input = val & SSTAR_GPIO_BIT_INPUT; + printf("Pad:%d, Addr:0x%08x, 0x%02X --> 0x%02X = in:%d out:%d " + "oe_n:%d, Dir:%s, Level:%d\n", + pad, sstar_gpio_pad_addr(pad), state[pad], val, val & 1, + (val >> 1) & 1, (val >> 2) & 1, + is_input ? "Input" : "Output", + is_input ? (val & SSTAR_GPIO_BIT_IN) != 0 + : (val & SSTAR_GPIO_BIT_OUT) != 0); + state[pad] = val; + } + usleep(100000); + } + + return EXIT_SUCCESS; +} + static int gpio_scan_cmd() { int GPIO_Groups = 0; size_t GPIO_Base = 0; @@ -3769,6 +3996,8 @@ static int gpio_scan_cmd() { setvbuf(stdout, NULL, _IOLBF, 0); getchipname(); + if (sstar_gpio_supported()) + return sstar_gpio_scan_cmd(); if (!get_chip_gpio_adress(&GPIO_Base, &GPIO_Offset, &GPIO_Groups)) return EXIT_FAILURE; @@ -3809,8 +4038,11 @@ static int gpio_scan_cmd() { size_t address = GPIO_Base + (group * GPIO_Offset) + mask; uint32_t value; if (!mem_reg(address, &value, OP_READ)) { + /* A /dev/mem that has stopped reading will not start again; + * a blind watcher that retries every 100 ms and names the + * same register each time is worse than stopping. */ fprintf(stderr, "Error at %#zx\n", address); - break; + return EXIT_FAILURE; } if (state[group] != value) { bool HeaderByte = false; diff --git a/src/reginfo.h b/src/reginfo.h index 5c5abe3..cae62fc 100644 --- a/src/reginfo.h +++ b/src/reginfo.h @@ -1,6 +1,7 @@ #ifndef REGINFO_H #define REGINFO_H +#include #include #include @@ -31,4 +32,19 @@ int reginfo_cmd(int argc, char **argv); int gpio_cmd(int argc, char **argv); char *gpio_possible_ircut(char *outbuf, size_t outlen); +/* The level a `gpio set` may write, parsed strictly. strtoul answers 0 for + * every string that is not a number, and 0 is a level the command really + * writes -- it switches the pad to an output and drives it low -- so a bare + * strtoul turned every typo into hardware. The number must end the string + * and be 0 or 1. */ +bool parse_gpio_level(const char *arg, unsigned *level); + +/* Which of nwin equal windows, the first at base and each stride wide, one + * /dev/mem mapping covers. A /dev/mem mapping's file offset is its physical + * address and its length is the vaddr span it names, so the interval under + * test is [off, off + len): a daemon mapping one broad window from well + * below is holding the windows the same as one mapped exactly. */ +uint32_t gpio_windows_in_mapping(unsigned long off, unsigned long len, + uint32_t base, uint32_t stride, int nwin); + #endif /* REGINFO_H */ diff --git a/src/reginfo_test.c b/src/reginfo_test.c index fb1f019..bfd3752 100644 --- a/src/reginfo_test.c +++ b/src/reginfo_test.c @@ -13,8 +13,10 @@ #include "hal/hisi/hal_hisi.h" #include "hal/ingenic.h" #include "hal/sstar.h" +#include "hal/sstar_gpio.h" #include "ipchw.h" #include "padmux.h" +#include "reginfo.h" static int failures; @@ -1056,6 +1058,90 @@ static void test_table_integrity(void) { } } +/* The per-pad register addresses of the Infinity6C GPIO block, as measured on + * a live board: pads 12 and 30 move their bytes at exactly these addresses + * when written through sysfs, and the idle levels of 10, 23 and 40/41 read + * back what their exporters left. This pins the table an address edit would + * silently move. */ +#ifdef IPCHW_VENDOR_SSTAR +static void test_sstar_gpio_regs(void) { + puts("SigmaStar: per-pad GPIO registers (Infinity6C)"); + as_chip(INFINITY6C, "SSC37X"); + + CHECK(sstar_gpio_supported()); + CHECK(sstar_gpio_num_pads() == 82); + CHECK(sstar_gpio_pad_addr(0) == 0x1F207C00); + CHECK(sstar_gpio_pad_addr(12) == 0x1F207C30); + CHECK(sstar_gpio_pad_addr(23) == 0x1F207C5C); + CHECK(sstar_gpio_pad_addr(30) == 0x1F207C7C); + CHECK(sstar_gpio_pad_addr(41) == 0x1F207CA8); + CHECK(sstar_gpio_pad_addr(42) == 0x1F207CC4); + CHECK(sstar_gpio_pad_addr(81) == 0x1F207D60); + + uint32_t prev = 0; + for (int pad = 0; pad < 82; pad++) { + uint32_t addr = sstar_gpio_pad_addr(pad); + CHECK(addr >= 0x1F207C00 && addr <= 0x1F207D60); + CHECK((addr & 3) == 0); + CHECK(addr > prev); + prev = addr; + } + + /* The window the IR-cut hint asks the /proc walk about -- the call + * itself, not a stand-in for it: one page-aligned start, an end that is + * not. */ + uint32_t base, len; + CHECK(sstar_gpio_window(&base, &len)); + CHECK(base == 0x1F207000 && len == 0xD64); + CHECK(gpio_windows_in_mapping(0x1F000000, 0x400000, base, len, 1) == 0x1); + CHECK(gpio_windows_in_mapping(0x1F200000, 0x10000, base, len, 1) == 0x1); + CHECK(gpio_windows_in_mapping(0x1F207000, 0x1000, base, len, 1) == 0x1); + CHECK(gpio_windows_in_mapping(0x1F206000, 0x1000, base, len, 1) == 0x0); + CHECK(gpio_windows_in_mapping(0x1F208000, 0x1000, base, len, 1) == 0x0); + + as_chip(0, "none"); + CHECK(!sstar_gpio_supported()); + CHECK(sstar_gpio_num_pads() == 0); + CHECK(sstar_gpio_pad_addr(31) == 0); + CHECK(!sstar_gpio_window(&base, &len)); +} +#endif + +/* parse_gpio_level: 0 is a level the command really writes, so the string + * must be exactly the level, and every string strtoul reads as 0 is a + * refusal. The old bare strtoul made `gpio set 12 foo` drive the pad low. */ +static void test_gpio_level_parse(void) { + puts("gpio set: a level is the string, and only 0 or 1 is a level"); + unsigned level; + + CHECK(parse_gpio_level("0", &level) && level == 0); + CHECK(parse_gpio_level("1", &level) && level == 1); + CHECK(parse_gpio_level("01", &level) && level == 1); + + CHECK(!parse_gpio_level("", &level)); + CHECK(!parse_gpio_level("foo", &level)); + CHECK(!parse_gpio_level("1x", &level)); + CHECK(!parse_gpio_level("0x1", &level)); + CHECK(!parse_gpio_level("2", &level)); + CHECK(!parse_gpio_level("-1", &level)); + CHECK(!parse_gpio_level("18446744073709551616", &level)); +} + +/* gpio_windows_in_mapping: a mapping covers what its length covers, not + * only what its start address falls inside -- a daemon holding one broad + * window from below the registers is holding them the same as one that + * mapped the exact page. */ +static void test_gpio_windows_in_mapping(void) { + puts("gpio reports: a mapping covers what its length covers"); + uint32_t base = 0x1000, stride = 0x100; + + CHECK(gpio_windows_in_mapping(0x1000, 0x100, base, stride, 3) == 0x1); + CHECK(gpio_windows_in_mapping(0x0, 0x1200, base, stride, 3) == 0x3); + CHECK(gpio_windows_in_mapping(0x1100, 0x200, base, stride, 3) == 0x6); + CHECK(gpio_windows_in_mapping(0x1300, 0x100, base, stride, 3) == 0x0); + CHECK(gpio_windows_in_mapping(0x1250, 0x20, base, stride, 3) == 0x4); +} + int main(void) { /* Every register the tests below touch is one of these, not a camera's. */ padmux_set_io(&FAKE_IO); @@ -1080,6 +1166,11 @@ int main(void) { #ifdef IPCHW_PADMUX_SSTAR test_sstar(); #endif +#ifdef IPCHW_VENDOR_SSTAR + test_sstar_gpio_regs(); +#endif + test_gpio_level_parse(); + test_gpio_windows_in_mapping(); #ifdef IPCHW_PADMUX_INGENIC test_ingenic(); #endif