From 9ca52a5df0ff193ea54e5456afad7da40ee09729 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sun, 8 Nov 2026 07:07:11 +0000 Subject: [PATCH 1/8] sensors: detect PrimeSensor PS52xx and BYD BF3703 Two families the Anyka EV200/EV300 SDKs drive that no probe knew: - PrimeSensor PS5250/5260/5268/5270/5280: ID at byte registers 0x00/0x01 of bank 0, which 0xef selects. The bank is put back as it was found, as the SuperPix probe does with its page. - BYD BF3703: ID 0x3703 at byte registers 0xfc/0xfd. They are probed last, and only at addresses a HAL lists for them. --- src/hal/common.h | 2 ++ src/sensors.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/src/hal/common.h b/src/hal/common.h index 94c74a3..329052f 100644 --- a/src/hal/common.h +++ b/src/hal/common.h @@ -65,6 +65,8 @@ enum SENSORS { SENSOR_IMAGEDESIGN, SENSOR_VISEMI, SENSOR_CVSENS, + SENSOR_PRIMESENSOR, + SENSOR_BYD, }; typedef struct { diff --git a/src/sensors.c b/src/sensors.c index fe39b51..f0f3205 100644 --- a/src/sensors.c +++ b/src/sensors.c @@ -1201,6 +1201,66 @@ static int detect_cvsens_sensor(sensor_ctx_t *ctx, int fd, } } +/* PrimeSensor PS52xx, as the Anyka EV200/EV300 drivers read it: the ID is in + * bank 0, registers 0x00/0x01, byte addresses, and the bank is selected + * through 0xef. The bank is put back as it was found, like the SuperPix page: + * a streamer driving the sensor writes its exposure to another bank. */ +static int detect_primesensor_sensor(sensor_ctx_t *ctx, int fd, + unsigned char i2c_addr) { + if (i2c_change_addr(fd, i2c_addr) < 0) + return false; + + int bank = i2c_read_register(fd, i2c_addr, 0xef, 1, 1); + if (bank == -1 || bank == 0xff) + return false; + if (bank) + i2c_write_register(fd, i2c_addr, 0xef, 1, 0x00, 1); + int msb = i2c_read_register(fd, i2c_addr, 0x00, 1, 1); + int lsb = i2c_read_register(fd, i2c_addr, 0x01, 1, 1); + if (bank) + i2c_write_register(fd, i2c_addr, 0xef, 1, bank, 1); + if (msb == -1 || lsb == -1) + return false; + + int res = msb << 8 | lsb; + switch (res) { + case 0x5250: + case 0x5260: + case 0x5268: + case 0x5270: + case 0x5280: + sprintf(ctx->sensor_id, "PS%04x", res); + return true; + default: + SENSOR_ERR("PrimeSensor", res); + return false; + } +} + +/* BYD BF3703 (VGA, Anyka EV200 SDK), ID at byte registers 0xfc/0xfd. */ +static int detect_byd_sensor(sensor_ctx_t *ctx, int fd, + unsigned char i2c_addr) { + if (i2c_change_addr(fd, i2c_addr) < 0) + return false; + + int msb = i2c_read_register(fd, i2c_addr, 0xfc, 1, 1); + if (msb == -1) + return false; + int lsb = i2c_read_register(fd, i2c_addr, 0xfd, 1, 1); + if (lsb == -1) + return false; + + int res = msb << 8 | lsb; + switch (res) { + case 0x3703: + sprintf(ctx->sensor_id, "BF%04x", res); + return true; + default: + SENSOR_ERR("BYD", res); + return false; + } +} + static int detect_possible_sensors(sensor_ctx_t *ctx, int fd, int (*detect_fn)(sensor_ctx_t *ctx, int, unsigned char), @@ -1287,6 +1347,16 @@ static bool get_sensor_id_i2c(sensor_ctx_t *ctx) { SENSOR_CVSENS)) { strcpy(ctx->vendor, "CVSENS"); detected = true; + } else if (detect_possible_sensors(ctx, fd, detect_primesensor_sensor, + SENSOR_PRIMESENSOR)) { + strcpy(ctx->vendor, "PrimeSensor"); + ctx->reg_width = 1; + detected = true; + } else if (detect_possible_sensors(ctx, fd, detect_byd_sensor, + SENSOR_BYD)) { + strcpy(ctx->vendor, "BYD"); + ctx->reg_width = 1; + detected = true; } exit: close_sensor_fd(fd); From d0ae5a02c7196ed784960e02b2c00946adbd99ee Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sun, 8 Nov 2026 07:07:11 +0000 Subject: [PATCH 2/8] anyka: name every generation's part, report media memory, detect the sensor Model. A device tree's root compatible names the part outright ("anyka,ak3918ev200", "anyka,ak3919ev330") and now wins; OpenIPC's kernels and the EV330 vendor ones have one. Otherwise the board string does, but it often names only the package: "AK3918E80PIN" is an EV200 on OpenIPC's board and an EV300 on another camera, "AK3918E" a V380E2's EV200. The chip ID's generation now completes it, so these report AK3918EV200 / AK3918EV300 rather than the package. 0x20120100 is both the Aimer39 AK3918's and some EV200s': the board string or device tree puts it in a generation of its own (family ak3918). The AK3918AV130 keeps its ID at SYSCTRL + 0x3fc (0x565735xx, revisions A and B) and is now read there. Memory. OpenIPC's kernels take mem= alone and give the ISP the rest of the memory the device tree declares, so with no memsize= the total is the device tree's /memory and media what the kernel did not get. Sensor. Anyka sensors answer I2C only while the camera driver has them clocked and out of reset, and the drivers power them down after their own probe, so ipctool found none. On EV200/EV300 the HAL now starts the CIS clock as the drivers do (SYSCTRL 0x18 from the peripheral PLL, 24 MHz) and pulses the reset/power-down GPIOs the device tree names for the camera, then puts back exactly what it changed. When the clock is already running a pipeline is, and nothing is touched. The address table gains the OV9712's 0x60, the GC1024's 0x78, the EV330 GC4653's 0x52, SmartSens' 0x64, ImageDesign, PrimeSensor and BYD. On an AK3918EV200 (OpenIPC, SC1345): model AK3918EV200 (was AK3918E80PIN), ram total 64M / media 24M, sensor SmartSens SC1345 at 0x60 (was none), ipcinfo -s sc1345; the clock and GPIO registers read the same before and after. With a capture running it reads the sensor without touching the clock and the stream keeps 15 fps. The naming was checked against the boot logs of gwell and V380E2 EV200, an AK3916EV301 and EV330 vendor firmware under qemu-hisilicon; anyka_test covers it. --- CLAUDE.md | 37 ++-- src/chipid.c | 4 + src/hal/anyka.c | 416 +++++++++++++++++++++++++++++++++++++++---- src/hal/anyka.h | 16 ++ src/hal/anyka_test.c | 90 ++++++++++ 5 files changed, 513 insertions(+), 50 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 32009f9..51d1b00 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -120,12 +120,13 @@ CMake knobs worth knowing: the project has a Longse camera. Note it uses a local `CHECK` macro rather than `assert()`: the release flags carry `-DNDEBUG`, so an `assert()`-based test compiles away to nothing and passes unconditionally. -- `./build/anyka_test`: the Anyka HAL's three parsers -- the `/proc/cpuinfo` - machine string, the chip-ID table and the media-memory arithmetic -- fed - fixture files through the path arguments `src/hal/anyka.h` exposes for the - purpose. Same situation as `longse_test` and the same `CHECK` macro: nobody - on the project has an Anyka camera, so this is all the verification that - code gets until a reporter runs it. +- `./build/anyka_test`: the Anyka HAL's parsers -- the `/proc/cpuinfo` + machine string, the device tree's compatible, `/memory` and camera GPIO + cells, the chip-ID table, the model naming, the peripheral-PLL decode and + the media-memory arithmetic -- fed fixture files through the path arguments + `src/hal/anyka.h` exposes for the purpose, with inputs taken from real boot + logs. Same `CHECK` macro as `longse_test`. The detection itself, including + the sensor power-up, was run on an AK3918EV200 with OpenIPC's kernel. - `./build/ambarella_test`: the `/proc/cpuinfo` machine string the Ambarella HAL parses, same `CHECK` macro and same reason. - `tools/test_pipeline.sh`: hardware-free end-to-end check of the sensor @@ -204,16 +205,26 @@ per-generation tables in each subcommand that should support it. Adding a new vendor also needs an entry in `IPCHW_OPTIONAL_VENDORS` in `CMakeLists.txt`, an include in `hal/common.h`, and a guarded row in `manufacturers[]`. -Anyka (`src/hal/anyka.c`) is the smallest worked example of that whole shape, -and the one that shows what to do when the SoC hands you no clean key: its -kernels have no device tree and register no UART with `/proc/iomem`, so the -`/proc/cpuinfo` machine string is what names the part +Anyka (`src/hal/anyka.c`) shows what to do when the SoC hands you no clean +key. The vendor 3.4 kernels have no device tree and register no UART with +`/proc/iomem`, so the `/proc/cpuinfo` machine string is what names the part (`CLOUD39EV3_AK3918EV300_MNBD`), while the chip-ID word at `0x08000000` names only the generation -- one vendor kernel prints the same `0x20160100` as AK3916, AK3918 or AK3919 depending on which `CONFIG_CPU_AK39xx` it was built -with. Reading the register is therefore a refinement and never the gate, which -is also what lets detection survive a kernel that will not hand over -`/dev/mem`. +with, and `0x20120100` is both the Aimer39 AK3918's and some EV200s'. Board +strings often carry only the package (`AK3918E80PIN` is an EV200 on one board +and an EV300 on another), so the generation completes the name; a device +tree's root compatible (OpenIPC's and the EV330 vendor kernels) names the part +outright and wins. The AV130 keeps its ID at `0x080003fc` instead. Reading the +registers is a refinement and never the gate, which is also what lets +detection survive a kernel that will not hand over `/dev/mem`. + +Anyka sensors answer I2C only while the camera driver has them clocked and +out of reset, so on EV200/EV300 `hal_enable_sensor_clock` starts the CIS +clock (SYSCTRL `0x18`, from the peripheral PLL in `0x14`) and pulses the +reset/power-down GPIOs the device tree names for the camera node, and +`hal_cleanup` puts back exactly what it changed. It touches nothing when the +clock is already running, which is a pipeline streaming. Ambarella (`src/hal/ambarella.c`) stops at the machine string on purpose. The exact part (S3L33M, S3L55M, S3L66, ...) is a word the DSP firmware writes, diff --git a/src/chipid.c b/src/chipid.c index e9ce82b..b7c46fc 100644 --- a/src/chipid.c +++ b/src/chipid.c @@ -338,6 +338,8 @@ const char *getchipfamily() { return "rv1106"; #endif #ifdef IPCHW_VENDOR_ANYKA + case AK39_EV1: + return "ak3918"; case AK39_EV2: return "ak3918ev200"; case AK39_EV3: @@ -348,6 +350,8 @@ const char *getchipfamily() { return "ak3918ev300l"; case AK39_AV100: return "ak3918av100"; + case AK39_AV130: + return "ak3918av130"; case AK37_D: return "ak37d"; case AK37_E: diff --git a/src/hal/anyka.c b/src/hal/anyka.c index 8a29559..fd6e3c6 100644 --- a/src/hal/anyka.c +++ b/src/hal/anyka.c @@ -1,30 +1,47 @@ +#define _GNU_SOURCE /* memmem() */ #include "hal/anyka.h" +#include +#include #include +#include #include #include +#include #include "chipid.h" #include "hal/common.h" #include "tools.h" -/* Addresses as the vendor's own sensor drivers in - * drivers/media/video/plat-anyka/ spell them, which is the 8-bit write form - * ipctool's tables use. SOI parts sit at either end: the JX-F2x/H63 drivers - * use 0x80 and the H65 one 0x60. */ +/* Addresses as the vendor's own sensor drivers spell them -- the 3.4 SDKs' + * drivers/media/video/plat-anyka/ and the sensor modules on AK3918EV200, + * EV300 and EV330 firmware -- which is the 8-bit write form ipctool's tables + * use. SOI parts sit at either end: the JX-F2x/H63 drivers use 0x80, the + * H42/H62/H65 ones 0x60. The OV9712 shares 0x60 with SmartSens, the GC1024 + * sits at 0x78 and the EV330's GC4653 at 0x52. */ static unsigned char sony_addrs[] = {0x34, 0}; static unsigned char soi_addrs[] = {0x80, 0x60, 0}; -static unsigned char ssens_addrs[] = {0x60, 0}; +static unsigned char ssens_addrs[] = {0x60, 0x64, 0}; static unsigned char onsemi_addrs[] = {0x20, 0}; -static unsigned char omni_addrs[] = {0x6c, 0}; -static unsigned char gc_addrs[] = {0x42, 0x6e, 0}; +static unsigned char omni_addrs[] = {0x6c, 0x60, 0}; +static unsigned char gc_addrs[] = {0x42, 0x6e, 0x78, 0x52, 0}; static unsigned char superpix_addrs[] = {0x7a, 0x78, 0}; +static unsigned char imagedesign_addrs[] = {0x60, 0}; +static unsigned char primesensor_addrs[] = {0x90, 0}; +static unsigned char byd_addrs[] = {0xdc, 0}; static sensor_addr_t anyka_possible_i2c_addrs[] = { - {SENSOR_SONY, sony_addrs}, {SENSOR_SOI, soi_addrs}, - {SENSOR_SMARTSENS, ssens_addrs}, {SENSOR_ONSEMI, onsemi_addrs}, - {SENSOR_OMNIVISION, omni_addrs}, {SENSOR_GALAXYCORE, gc_addrs}, - {SENSOR_SUPERPIX, superpix_addrs}, {0, NULL}}; + {SENSOR_SONY, sony_addrs}, + {SENSOR_SOI, soi_addrs}, + {SENSOR_SMARTSENS, ssens_addrs}, + {SENSOR_ONSEMI, onsemi_addrs}, + {SENSOR_OMNIVISION, omni_addrs}, + {SENSOR_GALAXYCORE, gc_addrs}, + {SENSOR_SUPERPIX, superpix_addrs}, + {SENSOR_IMAGEDESIGN, imagedesign_addrs}, + {SENSOR_PRIMESENSOR, primesensor_addrs}, + {SENSOR_BYD, byd_addrs}, + {0, NULL}}; /* Raw chip ID as it was read, kept so the report can show the number the * kernel's own "ANYKA CPU ... (ID 0x...)" banner prints. Zero when /dev/mem @@ -39,6 +56,8 @@ static const struct { uint32_t shift; int generation; } anyka_socs[] = { + /* The Aimer39 AK3918 and some AK3918EV200 boards share this one; the + * board string or the device tree tells them apart. */ {0x20120100, 0, AK39_EV2}, {0x20150200, 0, AK39_EV2}, {0x20160100, 0, AK39_EV3}, @@ -47,6 +66,8 @@ static const struct { {0x00201902, 8, AK37_E}, {0x00535335, 8, AK39_AV100}, {0x00535434, 8, AK39_EV300L}, + /* AK3918AV130, revisions A and B, read at AK_REG_CHIP_ID_AV. */ + {0x00565735, 8, AK39_AV130}, }; int anyka_generation(uint32_t chip_id) { @@ -61,9 +82,10 @@ int anyka_generation(uint32_t chip_id) { /* These kernels predate device tree on the camera parts -- the 3.4.35 in * issue #134 has no /proc/device-tree at all -- and the SoC registers no UART - * with /proc/iomem, so the machine string is the only signal that does not - * need /dev/mem. Every Anyka camera board spells the part into it: - * "CLOUD39EV3_AK3918EV300_MNBD", "Cloud39EV2_AK3918E80PIN_MNBD", + * with /proc/iomem, so the machine string is the one signal that every + * kernel gives without /dev/mem. Every Anyka camera board spells a part + * number into it: "CLOUD39EV3_AK3918EV300_MNBD", "Cloud39EV2_AK3918E80PIN_MNBD", + * "AK3918EV200_GWELL_V1", "Cloud39E_AK3918E+H42_V1.0.2", * "Aimer39_AK3918_MB_V1.0.0", "AK39EV330". */ bool anyka_machine_name(const char *cpuinfo, char *out, size_t outlen) { if (!line_from_file(cpuinfo, "Hardware.*:.*([Aa][Kk]3[0-9][0-9A-Za-z]*)", @@ -76,60 +98,219 @@ bool anyka_machine_name(const char *cpuinfo, char *out, size_t outlen) { return true; } +static const char *generation_suffix(int generation) { + switch (generation) { + case AK39_EV2: + return "EV200"; + case AK39_EV3: + return "EV300"; + case AK39_EV330: + return "EV330"; + case AK39_AV100: + return "AV100"; + case AK39_AV130: + return "AV130"; + default: + return ""; + } +} + +/* Is s "AK" + two family digits + two part digits, e.g. AK3918? */ +static bool is_part_base(const char *s) { + return (s[0] == 'A' || s[0] == 'a') && (s[1] == 'K' || s[1] == 'k') && + isdigit((unsigned char)s[2]) && isdigit((unsigned char)s[3]) && + isdigit((unsigned char)s[4]) && isdigit((unsigned char)s[5]); +} + +/* ...followed by EVnnn or AVnnn: the whole part number, AK3918EV200. */ +static bool is_full_part(const char *s) { + return is_part_base(s) && (toupper(s[6]) == 'E' || toupper(s[6]) == 'A') && + toupper(s[7]) == 'V' && isdigit((unsigned char)s[8]) && + isdigit((unsigned char)s[9]) && isdigit((unsigned char)s[10]); +} + +/* The part a device tree's root compatible names, "anyka,ak3918ev330" -> + * "AK3918EV330", skipping the generation-wide "anyka,ak39ev330". The list is + * NUL-separated, as /proc/device-tree/compatible holds it. */ +bool anyka_compatible_part(const char *compat, size_t len, char *out, + size_t outlen) { + for (size_t i = 0; i < len; i += strlen(compat + i) + 1) { + const char *c = compat + i; + + if (strncmp(c, "anyka,", 6) || !is_full_part(c + 6)) + continue; + size_t n = 0; + for (const char *p = c + 6; *p && n + 1 < outlen; p++) + out[n++] = toupper((unsigned char)*p); + out[n] = '\0'; + return true; + } + return false; +} + +/* The first AK39 generation's device trees name a bare part -- "anyka,ak3918", + * "anyka,ak3916", "anyka,ak3910" -- where the later ones carry EVnnn. */ +bool anyka_is_aimer39_dt(const char *compat, size_t len) { + for (size_t i = 0; i < len; i += strlen(compat + i) + 1) { + const char *c = compat + i; + + if (!strncmp(c, "anyka,", 6) && is_part_base(c + 6) && !c[12]) + return true; + } + return false; +} + +/* The model to report. The device tree names the exact part where there is + * one (OpenIPC and the EV330 vendor kernels). Otherwise the board string + * does when it carries a whole part number. Where it carries only the + * package -- "AK3918E80PIN" is an EV200 on one board and an EV300 on another, + * "AK3918E" a V380's EV200 -- the chip ID's generation completes it. */ +void anyka_model_name(const char *machine, const char *compat, + size_t compat_len, int generation, char *out, + size_t outlen) { + if (compat && anyka_compatible_part(compat, compat_len, out, outlen)) + return; + + if (!machine || !*machine) { + out[0] = '\0'; + return; + } + if (is_full_part(machine) || !is_part_base(machine) || + !*generation_suffix(generation)) { + snprintf(out, outlen, "%s", machine); + for (char *p = out; *p; p++) + *p = toupper((unsigned char)*p); + return; + } + snprintf(out, outlen, "AK%.4s%s", machine + 2, + generation_suffix(generation)); +} + +static size_t read_file(const char *path, char *buf, size_t len) { + FILE *f = fopen(path, "rb"); + size_t n; + + if (!f) + return 0; + n = fread(buf, 1, len, f); + fclose(f); + return n; +} + bool anyka_detect_cpu(char *chip_name) { /* As wide as chip_name, since that is where it lands. */ - char machine[128]; + char machine[128] = "", compat[512]; + size_t compat_len; uint32_t reg; - if (!anyka_machine_name("/proc/cpuinfo", machine, sizeof(machine))) + compat_len = read_file("/proc/device-tree/compatible", compat, + sizeof(compat) - 1); + compat[compat_len] = '\0'; + + bool by_machine = + anyka_machine_name("/proc/cpuinfo", machine, sizeof(machine)); + /* OpenIPC's kernels name the part in the device tree too; a board string + * is then not needed to claim the SoC. */ + if (!by_machine && !memmem(compat, compat_len, "anyka,", 6)) return false; - strcpy(chip_name, machine); /* Only ever a refinement: the board already named the part, and a kernel - * built with strict devmem filtering can refuse the window outright. */ + * built with strict devmem filtering can refuse the window outright. The + * AV130 keeps its ID at the far end of the same block. */ if (mem_reg(AK_REG_CHIP_ID, ®, OP_READ)) { anyka_chip_id = reg; chip_generation = anyka_generation(reg); + if (!chip_generation && mem_reg(AK_REG_CHIP_ID_AV, ®, OP_READ) && + anyka_generation(reg) == AK39_AV130) { + anyka_chip_id = reg; + chip_generation = AK39_AV130; + } } - return true; + /* 0x20120100 is also the Aimer39 AK3918's, which is a generation of its + * own: the board string or the device tree says which this is. */ + if (anyka_chip_id == 0x20120100) { + char aimer[16]; + + if (line_from_file("/proc/cpuinfo", "Hardware.*:.*(Aimer39)", aimer, + sizeof(aimer)) || + anyka_is_aimer39_dt(compat, compat_len)) + chip_generation = AK39_EV1; + } + + anyka_model_name(by_machine ? machine : NULL, compat, compat_len, + chip_generation, chip_name, 128); + return *chip_name != '\0'; } #ifndef STANDALONE_LIBRARY -/* Total DRAM is not something Linux can see here. The boot loader keeps the - * foot of the bank for the ISP and the encoder and hands the kernel only what - * is left -- 37 MB of a 64 MB part on the camera in issue #134 -- so - * /proc/meminfo is short by exactly the media reservation. The vendor boot - * argument `memsize` carries the real size, which is why the SDK reads it, - * and /proc/iomem says how much of it the kernel ended up with. */ -unsigned long anyka_media_mem(const char *cmdline, const char *iomem) { - char buf[256]; - unsigned long memsize, start, end; - - if (!line_from_file(cmdline, "memsize=([0-9]+)M", buf, sizeof(buf))) - return 0; - memsize = strtoul(buf, NULL, 10) * 1024; +/* Total DRAM is not something Linux can always see here. A vendor boot + * loader keeps the foot of the bank for the ISP and the encoder and hands the + * kernel only what is left -- 37 MB of a 64 MB part on the camera in issue + * #134 -- and says how much there is in `memsize=`, which is why the SDK + * reads it. OpenIPC's kernels take mem= alone and leave the rest of the + * memory the device tree declares to the ISP. Either way /proc/iomem says + * how much of it the kernel ended up with. */ +static bool iomem_kernel_ram(const char *iomem, unsigned long *start, + unsigned long *end) { + char buf[64]; if (!line_from_file(iomem, "^(\\w+)-\\w+ *: *System RAM", buf, sizeof(buf))) - return 0; - start = strtoul(buf, NULL, 16); + return false; + *start = strtoul(buf, NULL, 16); if (!line_from_file(iomem, "^\\w+-(\\w+) *: *System RAM", buf, sizeof(buf))) + return false; + *end = strtoul(buf, NULL, 16); + return *end > *start; +} + +unsigned long anyka_media_mem(const char *cmdline, const char *iomem) { + char buf[256]; + unsigned long memsize, start, end; + + if (!line_from_file(cmdline, "memsize=([0-9]+)M", buf, sizeof(buf))) return 0; - end = strtoul(buf, NULL, 16); + memsize = strtoul(buf, NULL, 10) * 1024; - if (end <= start) + if (!iomem_kernel_ram(iomem, &start, &end)) return 0; unsigned long kernel_ram = (end - start + 1) / 1024; return memsize > kernel_ram ? memsize - kernel_ram : 0; } +/* The device tree's /memory, in kB: one pair of 32-bit + * big-endian cells, as every AK39 device tree has it. ARM_ATAG_DTB_COMPAT + * puts the boot loader's own figure there. */ +unsigned long anyka_dt_memsize(const char *reg_path) { + unsigned char cells[8]; + + if (read_file(reg_path, (char *)cells, sizeof(cells)) != sizeof(cells)) + return 0; + uint32_t size = (uint32_t)cells[4] << 24 | (uint32_t)cells[5] << 16 | + (uint32_t)cells[6] << 8 | cells[7]; + return size / 1024; +} + static unsigned long anyka_totalmem(unsigned long *media_mem) { + unsigned long dt_total, start, end; + *media_mem = anyka_media_mem("/proc/cmdline", "/proc/iomem"); - return *media_mem + kernel_mem(); + if (*media_mem) + return *media_mem + kernel_mem(); + + dt_total = anyka_dt_memsize("/proc/device-tree/memory/reg"); + if (dt_total && iomem_kernel_ram("/proc/iomem", &start, &end)) { + unsigned long kernel_ram = (end - start + 1) / 1024; + if (dt_total > kernel_ram) { + *media_mem = dt_total - kernel_ram; + return dt_total; + } + } + return kernel_mem(); } static void anyka_chip_properties(cJSON *j_inner) { @@ -143,8 +324,169 @@ static void anyka_chip_properties(cJSON *j_inner) { } #endif +/* + * The sensor answers I2C only while it is clocked and out of reset, and on + * these SoCs that is only while the camera driver is using it: it powers the + * sensor up to probe it and down again afterwards. So the probe powers it up + * itself, the way the drivers do -- the AK3918EV200 and the Cloud39EV300 + * SDK's ak_camera.c and OpenIPC's ak-cam.c alike: the CIS clock from the + * peripheral PLL in SYSCTRL 0x18 (divider in bits 15:10, latched by bit 19, + * enabled by bit 18; 24 MHz, which every sensor these SoCs drive accepts), + * then a reset pulse on the board's reset and power-down GPIOs, which only a + * device tree names. Measured on an AK3918EV200 (SC1345): the sensor reads + * 0xffff before and its chip ID after. + * + * Nothing is touched when the clock is already running: then a pipeline may + * be streaming, the sensor already answers, and a reset would stop it. What + * is touched is put back by anyka_hal_cleanup(), bit for bit, and only that. + */ +#define AK_REG_PERI_PLL1 (AK_PA_SYSCTRL + 0x14) +#define AK_REG_PERI_PLL2 (AK_PA_SYSCTRL + 0x18) +#define AK_PA_GPIO 0x20170000 +#define AK_GPIO_DIR(pin) (AK_PA_GPIO + 0x00 + ((pin) >> 5) * 4) +#define AK_GPIO_OUT(pin) (AK_PA_GPIO + 0x0c + ((pin) >> 5) * 4) +#define AK_CIS_MCLK_MHZ 24 + +struct anyka_pin { + int pin; /* -1: none */ + bool active_low; + bool saved_dir, saved_out; +}; + +static struct { + bool armed; + uint32_t pll1, pll2; + struct anyka_pin reset, pwdn; +} anyka_power; + +/* of the first camera node's reset-gpio or pwdn-gpio. + * The AK39 device trees have one GPIO controller, so the phandle is not + * followed; flags bit 0 is GPIO_ACTIVE_LOW. */ +bool anyka_dt_gpio(const char *pattern, int *pin, bool *active_low) { + glob_t g; + unsigned char cells[12]; + bool ok = false; + + if (glob(pattern, 0, NULL, &g)) + return false; + if (g.gl_pathc && + read_file(g.gl_pathv[0], (char *)cells, sizeof(cells)) == 12) { + *pin = cells[6] << 8 | cells[7]; + *active_low = cells[11] & 1; + ok = *pin < 128; + } + globfree(&g); + return ok; +} + +static bool gpio_bit(uint32_t reg, int pin, bool *val) { + uint32_t v; + + if (!mem_reg(reg, &v, OP_READ)) + return false; + *val = v >> (pin & 31) & 1; + return true; +} + +static void gpio_set_bit(uint32_t reg, int pin, bool val) { + uint32_t v; + + if (!mem_reg(reg, &v, OP_READ)) + return; + v = val ? v | 1u << (pin & 31) : v & ~(1u << (pin & 31)); + mem_reg(reg, &v, OP_WRITE); +} + +static void claim_pin(struct anyka_pin *p, const char *prop) { + char pattern[96]; + + snprintf(pattern, sizeof(pattern), + "/proc/device-tree/soc/i2c*/camera@*/%s", prop); + if (!anyka_dt_gpio(pattern, &p->pin, &p->active_low) || + !gpio_bit(AK_GPIO_DIR(p->pin), p->pin, &p->saved_dir) || + !gpio_bit(AK_GPIO_OUT(p->pin), p->pin, &p->saved_out)) + p->pin = -1; +} + +/* Drive a pin to its asserted (on) or released (off) level. */ +static void drive_pin(struct anyka_pin *p, bool assert) { + if (p->pin < 0) + return; + gpio_set_bit(AK_GPIO_OUT(p->pin), p->pin, assert == p->active_low ? 0 : 1); + gpio_set_bit(AK_GPIO_DIR(p->pin), p->pin, 1); +} + +static void restore_pin(struct anyka_pin *p) { + if (p->pin < 0) + return; + gpio_set_bit(AK_GPIO_DIR(p->pin), p->pin, p->saved_dir); + gpio_set_bit(AK_GPIO_OUT(p->pin), p->pin, p->saved_out); +} + +/* Peripheral PLL in MHz, as the camera drivers compute it, or 0 when the + * register holds something they would refuse. */ +unsigned anyka_peri_pll_mhz(uint32_t pll1) { + unsigned m = pll1 & 0xff, n = pll1 >> 8 & 0xf, od = pll1 >> 12 & 0x3; + + if (od < 1 || n < 2 || n > 6 || m < 84 || m > 254) + return 0; + return 12 * m / (n * (1u << od)); +} + +static void anyka_enable_sensor_clock() { + uint32_t pll1, pll2, v; + unsigned pll; + + if (anyka_power.armed) + return; + if (chip_generation != AK39_EV2 && chip_generation != AK39_EV3) + return; + if (!mem_reg(AK_REG_PERI_PLL1, &pll1, OP_READ) || + !mem_reg(AK_REG_PERI_PLL2, &pll2, OP_READ)) + return; + if (pll2 & 1u << 18) + return; /* running: leave it to whoever runs it */ + pll = anyka_peri_pll_mhz(pll1); + if (!pll) + return; + + anyka_power.pll1 = pll1; + anyka_power.pll2 = pll2; + anyka_power.armed = true; + + claim_pin(&anyka_power.reset, "reset-gpio"); + claim_pin(&anyka_power.pwdn, "pwdn-gpio"); + + v = pll1 & ~(1u << 25); + if (v != pll1) + mem_reg(AK_REG_PERI_PLL1, &v, OP_WRITE); + v = (pll2 & ~(0x3fu << 10)) | (pll / AK_CIS_MCLK_MHZ - 1) << 10 | 1u << 19; + mem_reg(AK_REG_PERI_PLL2, &v, OP_WRITE); + v |= 1u << 18; + mem_reg(AK_REG_PERI_PLL2, &v, OP_WRITE); + + drive_pin(&anyka_power.pwdn, false); + drive_pin(&anyka_power.reset, true); + usleep(10000); + drive_pin(&anyka_power.reset, false); + usleep(20000); +} + +static void anyka_hal_cleanup() { + if (anyka_power.armed) { + restore_pin(&anyka_power.reset); + restore_pin(&anyka_power.pwdn); + mem_reg(AK_REG_PERI_PLL2, &anyka_power.pll2, OP_WRITE); + mem_reg(AK_REG_PERI_PLL1, &anyka_power.pll1, OP_WRITE); + anyka_power.armed = false; + } + restore_printk(); +} + void anyka_setup_hal() { possible_i2c_addrs = anyka_possible_i2c_addrs; + hal_enable_sensor_clock = anyka_enable_sensor_clock; + hal_cleanup = anyka_hal_cleanup; #ifndef STANDALONE_LIBRARY hal_totalmem = anyka_totalmem; hal_chip_properties = anyka_chip_properties; diff --git a/src/hal/anyka.h b/src/hal/anyka.h index 9a41313..596c1a3 100644 --- a/src/hal/anyka.h +++ b/src/hal/anyka.h @@ -11,17 +11,24 @@ * "akpcm_AnalogCtrlRegs" -- so it has to be spelled out here. */ #define AK_PA_SYSCTRL 0x08000000 #define AK_REG_CHIP_ID (AK_PA_SYSCTRL + 0x00) +/* The AK3918AV130 keeps its ID at the far end of the same block + * (mach-ak3918av130.c: AK_VA_SYSCTRL + 0x3FC). */ +#define AK_REG_CHIP_ID_AV (AK_PA_SYSCTRL + 0x3FC) /* chip_generation values. The ID word names a generation, never the exact * part: a vendor kernel compares it against the single constant picked by its * CONFIG_CPU_AK39xx and prints whichever name it was built for, so the same * 0x20160100 is announced as AK3916, AK3918 or AK3919 by three different * boards. The part number comes from the machine string instead. */ +/* The first generation, Aimer39 (AK3918, AK3916, AK3910): its 0x20120100 is + * shared with some AK3918EV200 boards, so the board string decides. */ +#define AK39_EV1 0x3918E100 #define AK39_EV2 0x3918E200 #define AK39_EV3 0x3918E300 #define AK39_EV330 0x3939E330 #define AK39_EV300L 0x3918E30C #define AK39_AV100 0x3918A100 +#define AK39_AV130 0x3918A130 #define AK37_D 0x3737D000 #define AK37_E 0x3737E000 @@ -34,6 +41,15 @@ void anyka_setup_hal(); * /proc paths. */ bool anyka_machine_name(const char *cpuinfo, char *out, size_t outlen); int anyka_generation(uint32_t chip_id); +bool anyka_compatible_part(const char *compat, size_t len, char *out, + size_t outlen); +bool anyka_is_aimer39_dt(const char *compat, size_t len); +void anyka_model_name(const char *machine, const char *compat, + size_t compat_len, int generation, char *out, + size_t outlen); unsigned long anyka_media_mem(const char *cmdline, const char *iomem); +unsigned long anyka_dt_memsize(const char *reg_path); +bool anyka_dt_gpio(const char *pattern, int *pin, bool *active_low); +unsigned anyka_peri_pll_mhz(uint32_t pll1); #endif /* HAL_ANYKA_H */ diff --git a/src/hal/anyka_test.c b/src/hal/anyka_test.c index 2c16f1c..db9170a 100644 --- a/src/hal/anyka_test.c +++ b/src/hal/anyka_test.c @@ -137,6 +137,10 @@ static void chip_ids(void) { CHECK(anyka_generation(0x53533542) == AK39_AV100); CHECK(anyka_generation(0x53543400) == AK39_EV300L); + /* AK3918AV130 revisions A and B, at SYSCTRL + 0x3FC. */ + CHECK(anyka_generation(0x56573500) == AK39_AV130); + CHECK(anyka_generation(0x56573502) == AK39_AV130); + /* A shifted row must not swallow a raw one, or an EV300 would report as * whatever 0x201601.. happened to be listed. */ CHECK(anyka_generation(0x20160102) == 0); @@ -145,6 +149,90 @@ static void chip_ids(void) { CHECK(anyka_generation(0xffffffff) == 0); } + +static void expect_model(const char *machine, const char *compat, + size_t compat_len, int generation, const char *want) { + char got[128]; + + anyka_model_name(machine, compat, compat_len, generation, got, + sizeof(got)); + CHECK(!strcmp(got, want)); + if (strcmp(got, want)) + fprintf(stderr, " (wanted \"%s\", got \"%s\")\n", want, got); +} + +/* "AK3918E80PIN" is a package: an EV200's on one board, an EV300's on + * another, so the generation completes it. The boot logs these come from: + * OpenIPC's EV200 kernel, a V380E2 and a gwell AK3918EV200 (vendor 3.4.35, + * ID 0x20150200), an AK3916EV301 camera whose vendor kernel says EV300 + * (0x20160100), and EV330 vendor kernels, which carry a device tree. */ +static void model_names(void) { + static const char ev200_dt[] = "anyka,ak3918ev200"; + static const char ev330_dt[] = "anyka,ak3919ev330\0anyka,ak39ev330"; + static const char generic_dt[] = "anyka,ak39ev330"; + + expect_model("AK3918E80PIN", ev200_dt, sizeof(ev200_dt), AK39_EV2, + "AK3918EV200"); + expect_model("AK3918E80PIN", NULL, 0, AK39_EV2, "AK3918EV200"); + expect_model("AK3918E80PIN", NULL, 0, AK39_EV3, "AK3918EV300"); + expect_model("AK3918E", NULL, 0, AK39_EV2, "AK3918EV200"); + expect_model("AK3918EV200", NULL, 0, AK39_EV2, "AK3918EV200"); + expect_model("AK3918EV300", NULL, 0, AK39_EV3, "AK3918EV300"); + expect_model("AK39EV330", ev330_dt, sizeof(ev330_dt), AK39_EV330, + "AK3919EV330"); + /* Only the generation-wide compatible: keep the board's word. */ + expect_model("AK39EV330", generic_dt, sizeof(generic_dt), AK39_EV330, + "AK39EV330"); + /* Aimer39 has no suffix, and a generation nobody can name adds none. */ + expect_model("AK3918", NULL, 0, AK39_EV1, "AK3918"); + expect_model("AK3918E80PIN", NULL, 0, 0, "AK3918E80PIN"); + expect_model("AK3918AV130", NULL, 0, AK39_AV130, "AK3918AV130"); + /* Device tree alone, no board string to go on. */ + expect_model(NULL, ev200_dt, sizeof(ev200_dt), 0, "AK3918EV200"); + + static const char aimer_dt[] = "anyka,ak3918"; + CHECK(anyka_is_aimer39_dt(aimer_dt, sizeof(aimer_dt))); + CHECK(!anyka_is_aimer39_dt(ev200_dt, sizeof(ev200_dt))); + CHECK(!anyka_is_aimer39_dt(generic_dt, sizeof(generic_dt))); +} + +static const char *binary_fixture(const unsigned char *data, size_t len) { + const char *path = fixture(""); + FILE *f = fopen(path, "wb"); + + if (f) { + fwrite(data, 1, len, f); + fclose(f); + } + return path; +} + +static void device_tree(void) { + /* OpenIPC's EV200: /memory reg = <0x80000000 0x04000000>. */ + static const unsigned char mem[] = {0x80, 0, 0, 0, 0x04, 0, 0, 0}; + const char *path = binary_fixture(mem, sizeof(mem)); + CHECK(anyka_dt_memsize(path) == 64 * 1024); + unlink(path); + + /* reset-gpio = <&gpio 0 GPIO_ACTIVE_LOW>, the gwell camera's. */ + static const unsigned char gpio[] = {0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 1}; + int pin = -1; + bool active_low = false; + path = binary_fixture(gpio, sizeof(gpio)); + CHECK(anyka_dt_gpio(path, &pin, &active_low)); + CHECK(pin == 0 && active_low); + unlink(path); + CHECK(!anyka_dt_gpio("/nonexistent/*/reset-gpio", &pin, &active_low)); + + /* The EV200's peripheral PLL, 0x00a512c8: M 200, N 2, OD 1 -> 600 MHz. + * Values the camera drivers refuse give 0. */ + CHECK(anyka_peri_pll_mhz(0x00a512c8) == 600); + CHECK(anyka_peri_pll_mhz(0x00000000) == 0); + /* OD 0, and N 1: both outside what the drivers accept. */ + CHECK(anyka_peri_pll_mhz(0x000002c8) == 0); + CHECK(anyka_peri_pll_mhz(0x000011c8) == 0); +} + static void media_mem(void) { /* The PC420 again: 64 MB of DDR, of which the kernel was handed * 0x81b00000-0x83ffffff = 37 MB, so 27 MB went to the ISP and encoder. */ @@ -177,6 +265,8 @@ static void media_mem(void) { int main(void) { machine_names(); chip_ids(); + model_names(); + device_tree(); media_mem(); if (failures) { From c219a1f26918a299e14ab58a8a94e4bb3a99b6f0 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sun, 8 Nov 2026 07:14:34 +0000 Subject: [PATCH 3/8] sensors: report 0x1245 as SC2145H, without a revision suffix The _A/_B suffix came from 0x3020, which tells two silicon revisions of the part apart -- the Anyka driver for this ID picks its init variant by it -- rather than two parts. A name that changes with the revision makes a poor key for anything looked up by sensor name, such as a tuning file. --- src/sensors.c | 8 ++++---- src/sensors_golden.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sensors.c b/src/sensors.c index f0f3205..f0c91b8 100644 --- a/src/sensors.c +++ b/src/sensors.c @@ -533,11 +533,11 @@ static int detect_smartsens_sensor(sensor_ctx_t *ctx, int fd, break; case 0x1235: break; - case 0x1245: { - int sw = i2c_read_register(fd, i2c_addr, 0x3020, 2, 1); - sprintf(ctx->sensor_id, "SC2145H_%c", sw == 2 ? 'A' : 'B'); + case 0x1245: + /* 0x3020 tells two silicon revisions apart (the Anyka driver picks + * its init variant by it, 2 or 3); it is not a different part. */ + strcpy(ctx->sensor_id, "SC2145H"); return true; - } case 0x2032: res = 0x2035; break; diff --git a/src/sensors_golden.txt b/src/sensors_golden.txt index 032b068..8323024 100644 --- a/src/sensors_golden.txt +++ b/src/sensors_golden.txt @@ -5,7 +5,7 @@ smartsens 0132 SC132GS smartsens 1045 SC1045 smartsens 1145 SC1145 smartsens 1235 SC1235 -smartsens 1245 SC2145H_B +smartsens 1245 SC2145H smartsens 17cb SC210IoT smartsens 2032 SC2035 smartsens 2045 SC2045 From 26521369e0d4678f4c7a938c77d52a1eeca08e16 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sun, 8 Nov 2026 07:26:22 +0000 Subject: [PATCH 4/8] anyka: report an EV330 the board does not name as AK39XXEV330 EV330 board strings say "AK39EV330", which no part is called. Where the device tree does not name the part either, report the generation the way the vendor kernel's CPU banner does, "AK39XXEV330", rather than the board's short form. --- src/hal/anyka.c | 7 +++++++ src/hal/anyka_test.c | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/hal/anyka.c b/src/hal/anyka.c index fd6e3c6..41f6632 100644 --- a/src/hal/anyka.c +++ b/src/hal/anyka.c @@ -171,6 +171,13 @@ void anyka_model_name(const char *machine, const char *compat, if (compat && anyka_compatible_part(compat, compat_len, out, outlen)) return; + /* EV330 boards say only "AK39EV330", which is no part: name the + * generation as the vendor kernel's CPU banner does. */ + if (generation == AK39_EV330 && !(machine && is_full_part(machine))) { + snprintf(out, outlen, "AK39XXEV330"); + return; + } + if (!machine || !*machine) { out[0] = '\0'; return; diff --git a/src/hal/anyka_test.c b/src/hal/anyka_test.c index db9170a..18f7506 100644 --- a/src/hal/anyka_test.c +++ b/src/hal/anyka_test.c @@ -180,9 +180,12 @@ static void model_names(void) { expect_model("AK3918EV300", NULL, 0, AK39_EV3, "AK3918EV300"); expect_model("AK39EV330", ev330_dt, sizeof(ev330_dt), AK39_EV330, "AK3919EV330"); - /* Only the generation-wide compatible: keep the board's word. */ + /* Only the generation-wide compatible: name the generation, as the + * vendor banner does ("ANYKA CPU AK39XXEV330"), not the board's short + * form. */ expect_model("AK39EV330", generic_dt, sizeof(generic_dt), AK39_EV330, - "AK39EV330"); + "AK39XXEV330"); + expect_model("AK39EV330", NULL, 0, AK39_EV330, "AK39XXEV330"); /* Aimer39 has no suffix, and a generation nobody can name adds none. */ expect_model("AK3918", NULL, 0, AK39_EV1, "AK3918"); expect_model("AK3918E80PIN", NULL, 0, 0, "AK3918E80PIN"); From 164ad7a253762f554bd554da00c7f7fd12a71215 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sun, 8 Nov 2026 07:34:29 +0000 Subject: [PATCH 5/8] hal: send the value in the generic I2C register write universal_i2c_write_register() ignored `data` and wrote `data_width` bytes of the register address, so a write through the generic HAL stored nothing: `ipctool i2cset` did nothing on those SoCs, the SuperPix probe's page select and restore never reached the sensor, and with two-byte addresses not even the whole address went out. Send the address and then the value, most significant byte first, as one write. --- src/hal/common.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/hal/common.c b/src/hal/common.c index 20fa94e..7cefdc6 100644 --- a/src/hal/common.c +++ b/src/hal/common.c @@ -89,19 +89,23 @@ int universal_i2c_write_register(int fd, unsigned char i2c_addr, unsigned int reg_addr, unsigned int reg_width, unsigned int data, unsigned int data_width) { (void)i2c_addr; - (void)data; - char buf[2]; - - if (reg_width == 2) { - buf[0] = (reg_addr >> 8) & 0xff; - buf[1] = reg_addr & 0xff; - } else { - buf[0] = reg_addr & 0xff; - } - - if (write(fd, buf, data_width) != (int)data_width) { + unsigned char buf[4]; + int len = 0; + + /* The register address, then the value, both most significant byte + * first: one I2C write. This used to send `data_width` bytes of the + * address alone, so every write through it -- `ipctool i2cset`, the + * SuperPix page select -- stored nothing, and with two-byte addresses + * not even the whole address went out. */ + if (reg_width == 2) + buf[len++] = (reg_addr >> 8) & 0xff; + buf[len++] = reg_addr & 0xff; + if (data_width == 2) + buf[len++] = (data >> 8) & 0xff; + buf[len++] = data & 0xff; + + if (write(fd, buf, len) != len) return -1; - } return 0; } From 2a04f07506de5e11efd14a0c327c80e212f1b5d5 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sun, 8 Nov 2026 07:34:29 +0000 Subject: [PATCH 6/8] anyka: power the sensor only for a bus that opens, and only its camera - The probe calls hal_cleanup() only after it has opened the bus, so a power-up for an adapter that does not open was never undone. Arm only when the adapter opens. - Take the reset/power-down GPIOs from the camera node under the adapter being probed (/sys/bus/i2c/devices/i2c-N/of_node), not from the first camera node anywhere. Without that link, use the device tree's camera only when there is exactly one, and touch no pin otherwise. - PrimeSensor: give up when the bank cannot be selected rather than read the ID from the wrong bank, and retry the restore once. Raised by the Qodo review of #273. Re-run on the AK3918EV200: the adapter resolves to i2c0@20150000 and its camera@37, SC1345 is found, and the clock and GPIO registers read the same before and after. --- src/hal/anyka.c | 46 +++++++++++++++++++++++++++++++++++++++------- src/sensors.c | 10 +++++++--- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/hal/anyka.c b/src/hal/anyka.c index 41f6632..bb9f6d7 100644 --- a/src/hal/anyka.c +++ b/src/hal/anyka.c @@ -366,9 +366,11 @@ static struct { struct anyka_pin reset, pwdn; } anyka_power; -/* of the first camera node's reset-gpio or pwdn-gpio. - * The AK39 device trees have one GPIO controller, so the phandle is not - * followed; flags bit 0 is GPIO_ACTIVE_LOW. */ +/* of a camera node's reset-gpio or pwdn-gpio. The + * pattern must match one node: with several, which camera's pins these are + * is not known, and none is touched. The AK39 device trees have one GPIO + * controller, so the phandle is not followed; flags bit 0 is + * GPIO_ACTIVE_LOW. */ bool anyka_dt_gpio(const char *pattern, int *pin, bool *active_low) { glob_t g; unsigned char cells[12]; @@ -376,7 +378,7 @@ bool anyka_dt_gpio(const char *pattern, int *pin, bool *active_low) { if (glob(pattern, 0, NULL, &g)) return false; - if (g.gl_pathc && + if (g.gl_pathc == 1 && read_file(g.gl_pathv[0], (char *)cells, sizeof(cells)) == 12) { *pin = cells[6] << 8 | cells[7]; *active_low = cells[11] & 1; @@ -404,11 +406,34 @@ static void gpio_set_bit(uint32_t reg, int pin, bool val) { mem_reg(reg, &v, OP_WRITE); } +/* The camera nodes under the I2C adapter being probed: its device tree node + * is what /sys/bus/i2c/devices/i2c-N/of_node points at. A kernel that does + * not link it leaves the glob over every adapter, which anyka_dt_gpio() + * accepts only when it finds a single camera. */ +static void camera_pattern(char *out, size_t len, const char *prop) { + char link[64], node[256]; + const char *base = "/sys/firmware/devicetree/base/"; + ssize_t n; + + snprintf(link, sizeof(link), "/sys/bus/i2c/devices/i2c-%d/of_node", + i2c_adapter_nr); + n = readlink(link, node, sizeof(node) - 1); + if (n > 0) { + node[n] = '\0'; + char *rel = strstr(node, "devicetree/base/"); + if (rel) { + snprintf(out, len, "%s%s/camera@*/%s", base, + rel + strlen("devicetree/base/"), prop); + return; + } + } + snprintf(out, len, "/proc/device-tree/soc/i2c*/camera@*/%s", prop); +} + static void claim_pin(struct anyka_pin *p, const char *prop) { - char pattern[96]; + char pattern[320]; - snprintf(pattern, sizeof(pattern), - "/proc/device-tree/soc/i2c*/camera@*/%s", prop); + camera_pattern(pattern, sizeof(pattern), prop); if (!anyka_dt_gpio(pattern, &p->pin, &p->active_low) || !gpio_bit(AK_GPIO_DIR(p->pin), p->pin, &p->saved_dir) || !gpio_bit(AK_GPIO_OUT(p->pin), p->pin, &p->saved_out)) @@ -457,6 +482,13 @@ static void anyka_enable_sensor_clock() { if (!pll) return; + /* The probe calls hal_cleanup() only after it has opened the bus, so a + * power-up for a bus that will not open would never be undone. */ + int fd = open_i2c_sensor_fd(i2c_adapter_nr); + if (fd < 0) + return; + close_sensor_fd(fd); + anyka_power.pll1 = pll1; anyka_power.pll2 = pll2; anyka_power.armed = true; diff --git a/src/sensors.c b/src/sensors.c index f0c91b8..84e2e2b 100644 --- a/src/sensors.c +++ b/src/sensors.c @@ -1213,11 +1213,15 @@ static int detect_primesensor_sensor(sensor_ctx_t *ctx, int fd, int bank = i2c_read_register(fd, i2c_addr, 0xef, 1, 1); if (bank == -1 || bank == 0xff) return false; - if (bank) - i2c_write_register(fd, i2c_addr, 0xef, 1, 0x00, 1); + /* A bank that cannot be selected would make the ID reads come from the + * wrong one: give up rather than name the sensor from them. */ + if (bank && i2c_write_register(fd, i2c_addr, 0xef, 1, 0x00, 1) < 0) + return false; int msb = i2c_read_register(fd, i2c_addr, 0x00, 1, 1); int lsb = i2c_read_register(fd, i2c_addr, 0x01, 1, 1); - if (bank) + /* Retried once: a streamer driving this sensor writes to the bank it + * left selected. */ + if (bank && i2c_write_register(fd, i2c_addr, 0xef, 1, bank, 1) < 0) i2c_write_register(fd, i2c_addr, 0xef, 1, bank, 1); if (msb == -1 || lsb == -1) return false; From 79df4842a46e917628384df993da21ffb436c471 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sun, 8 Nov 2026 15:47:10 +0000 Subject: [PATCH 7/8] anyka: keep what every camera carries small ipcinfo grew 4985 bytes, over the size check's 1024, for a family few cameras use. It now grows 968 with every vendor built in and 5 with none. The sensor power-up and the PrimeSensor and BYD detectors move behind !STANDALONE_LIBRARY: ipctool keeps them, ipcinfo does not, so ipcinfo -s names an Anyka sensor only while something already clocks it. Neither sensor has a tuning in the firmware package. The part naming stays in ipcinfo, since ipcinfo -c has to be right, but takes the device tree's first compatible through line_from_file(), and the suffix from the generation constant instead of a table of strings. An Aimer39 is now told by the bare part its board or device tree names. --- CLAUDE.md | 6 ++ src/hal/anyka.c | 187 ++++++++++++++++--------------------------- src/hal/anyka.h | 10 +-- src/hal/anyka_test.c | 49 +++++------- src/sensors.c | 4 + 5 files changed, 103 insertions(+), 153 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 51d1b00..c9ed083 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -226,6 +226,12 @@ reset/power-down GPIOs the device tree names for the camera node, and `hal_cleanup` puts back exactly what it changed. It touches nothing when the clock is already running, which is a pipeline streaming. +That power-up, and the PrimeSensor and BYD detectors only Anyka boards use, +are `ipctool`-only (`#ifndef STANDALONE_LIBRARY`): `ipcinfo` is in every +camera image and the size check holds it to 1 KiB of growth per PR. So +`ipcinfo -s` finds an Anyka sensor only while something is already clocking +it. The part name stays in `ipcinfo`, because `ipcinfo -c` has to be right. + Ambarella (`src/hal/ambarella.c`) stops at the machine string on purpose. The exact part (S3L33M, S3L55M, S3L66, ...) is a word the DSP firmware writes, reachable only through `/dev/iav`, so the report says `S3L`, plus `family: S3Lm` diff --git a/src/hal/anyka.c b/src/hal/anyka.c index bb9f6d7..09a4e0d 100644 --- a/src/hal/anyka.c +++ b/src/hal/anyka.c @@ -1,4 +1,3 @@ -#define _GNU_SOURCE /* memmem() */ #include "hal/anyka.h" #include @@ -27,8 +26,10 @@ static unsigned char omni_addrs[] = {0x6c, 0x60, 0}; static unsigned char gc_addrs[] = {0x42, 0x6e, 0x78, 0x52, 0}; static unsigned char superpix_addrs[] = {0x7a, 0x78, 0}; static unsigned char imagedesign_addrs[] = {0x60, 0}; +#ifndef STANDALONE_LIBRARY static unsigned char primesensor_addrs[] = {0x90, 0}; static unsigned char byd_addrs[] = {0xdc, 0}; +#endif static sensor_addr_t anyka_possible_i2c_addrs[] = { {SENSOR_SONY, sony_addrs}, @@ -39,8 +40,10 @@ static sensor_addr_t anyka_possible_i2c_addrs[] = { {SENSOR_GALAXYCORE, gc_addrs}, {SENSOR_SUPERPIX, superpix_addrs}, {SENSOR_IMAGEDESIGN, imagedesign_addrs}, +#ifndef STANDALONE_LIBRARY {SENSOR_PRIMESENSOR, primesensor_addrs}, {SENSOR_BYD, byd_addrs}, +#endif {0, NULL}}; /* Raw chip ID as it was read, kept so the report can show the number the @@ -84,8 +87,8 @@ int anyka_generation(uint32_t chip_id) { * issue #134 has no /proc/device-tree at all -- and the SoC registers no UART * with /proc/iomem, so the machine string is the one signal that every * kernel gives without /dev/mem. Every Anyka camera board spells a part - * number into it: "CLOUD39EV3_AK3918EV300_MNBD", "Cloud39EV2_AK3918E80PIN_MNBD", - * "AK3918EV200_GWELL_V1", "Cloud39E_AK3918E+H42_V1.0.2", + * number into it: "CLOUD39EV3_AK3918EV300_MNBD", + * "Cloud39EV2_AK3918E80PIN_MNBD", "AK3918EV200_GWELL_V1", "Cloud39E_AK3918E+H42_V1.0.2", * "Aimer39_AK3918_MB_V1.0.0", "AK39EV330". */ bool anyka_machine_name(const char *cpuinfo, char *out, size_t outlen) { if (!line_from_file(cpuinfo, "Hardware.*:.*([Aa][Kk]3[0-9][0-9A-Za-z]*)", @@ -98,128 +101,64 @@ bool anyka_machine_name(const char *cpuinfo, char *out, size_t outlen) { return true; } -static const char *generation_suffix(int generation) { - switch (generation) { - case AK39_EV2: - return "EV200"; - case AK39_EV3: - return "EV300"; - case AK39_EV330: - return "EV330"; - case AK39_AV100: - return "AV100"; - case AK39_AV130: - return "AV130"; - default: - return ""; - } -} - -/* Is s "AK" + two family digits + two part digits, e.g. AK3918? */ +/* Is s "AK" + four digits, e.g. AK3918? */ static bool is_part_base(const char *s) { - return (s[0] == 'A' || s[0] == 'a') && (s[1] == 'K' || s[1] == 'k') && - isdigit((unsigned char)s[2]) && isdigit((unsigned char)s[3]) && - isdigit((unsigned char)s[4]) && isdigit((unsigned char)s[5]); + return (s[0] | 32) == 'a' && (s[1] | 32) == 'k' && isdigit(s[2]) && + isdigit(s[3]) && isdigit(s[4]) && isdigit(s[5]); } -/* ...followed by EVnnn or AVnnn: the whole part number, AK3918EV200. */ -static bool is_full_part(const char *s) { - return is_part_base(s) && (toupper(s[6]) == 'E' || toupper(s[6]) == 'A') && - toupper(s[7]) == 'V' && isdigit((unsigned char)s[8]) && - isdigit((unsigned char)s[9]) && isdigit((unsigned char)s[10]); -} - -/* The part a device tree's root compatible names, "anyka,ak3918ev330" -> - * "AK3918EV330", skipping the generation-wide "anyka,ak39ev330". The list is - * NUL-separated, as /proc/device-tree/compatible holds it. */ -bool anyka_compatible_part(const char *compat, size_t len, char *out, - size_t outlen) { - for (size_t i = 0; i < len; i += strlen(compat + i) + 1) { - const char *c = compat + i; - - if (strncmp(c, "anyka,", 6) || !is_full_part(c + 6)) - continue; - size_t n = 0; - for (const char *p = c + 6; *p && n + 1 < outlen; p++) - out[n++] = toupper((unsigned char)*p); - out[n] = '\0'; - return true; - } - return false; +static bool is_bare_part(const char *s) { + return is_part_base(s) && !s[6]; } -/* The first AK39 generation's device trees name a bare part -- "anyka,ak3918", - * "anyka,ak3916", "anyka,ak3910" -- where the later ones carry EVnnn. */ -bool anyka_is_aimer39_dt(const char *compat, size_t len) { - for (size_t i = 0; i < len; i += strlen(compat + i) + 1) { - const char *c = compat + i; - - if (!strncmp(c, "anyka,", 6) && is_part_base(c + 6) && !c[12]) - return true; - } - return false; +/* ...followed by EVnnn or AVnnn: the whole part number, AK3918EV200. A + * package, "AK3918E80PIN", has no V there. */ +static bool is_full_part(const char *s) { + return is_part_base(s) && s[6] && (s[7] | 32) == 'v' && isdigit(s[8]); } -/* The model to report. The device tree names the exact part where there is - * one (OpenIPC and the EV330 vendor kernels). Otherwise the board string - * does when it carries a whole part number. Where it carries only the - * package -- "AK3918E80PIN" is an EV200 on one board and an EV300 on another, - * "AK3918E" a V380's EV200 -- the chip ID's generation completes it. */ -void anyka_model_name(const char *machine, const char *compat, - size_t compat_len, int generation, char *out, - size_t outlen) { - if (compat && anyka_compatible_part(compat, compat_len, out, outlen)) - return; - - /* EV330 boards say only "AK39EV330", which is no part: name the - * generation as the vendor kernel's CPU banner does. */ - if (generation == AK39_EV330 && !(machine && is_full_part(machine))) { - snprintf(out, outlen, "AK39XXEV330"); - return; - } - - if (!machine || !*machine) { - out[0] = '\0'; - return; +/* The model to report. `compat` is what follows "anyka," in the device tree's + * root compatible, whose first entry is the most specific: the exact part + * where the tree has one. Otherwise the board string names it when it carries + * a whole part number. Where it carries only the package -- "AK3918E80PIN" is + * an EV200 on one board and an EV300 on another -- the chip ID's generation + * completes it, and its constant spells the suffix: 0x3918E200 is EV200. + * Aimer39 has no suffix and the EV300L's constant spells none. EV330 boards + * say only "AK39EV330", which is no part: that one is named as the vendor + * kernel's CPU banner names it. */ +void anyka_model_name(const char *machine, const char *compat, int generation, + char *out, size_t outlen) { + const char *part = compat; + + if (!part || !is_full_part(part)) { + part = machine ? machine : ""; + if (generation == AK39_EV330 && !is_full_part(part)) + part = "AK39XXEV330"; } - if (is_full_part(machine) || !is_part_base(machine) || - !*generation_suffix(generation)) { - snprintf(out, outlen, "%s", machine); - for (char *p = out; *p; p++) - *p = toupper((unsigned char)*p); - return; - } - snprintf(out, outlen, "AK%.4s%s", machine + 2, - generation_suffix(generation)); -} - -static size_t read_file(const char *path, char *buf, size_t len) { - FILE *f = fopen(path, "rb"); - size_t n; - - if (!f) - return 0; - n = fread(buf, 1, len, f); - fclose(f); - return n; + if (is_part_base(part) && !is_full_part(part) && generation >> 24 == 0x39 && + generation != AK39_EV1 && !(generation & 0xf)) + snprintf(out, outlen, "AK%.4s%XV%03X", part + 2, generation >> 12 & 0xf, + generation & 0xfff); + else + snprintf(out, outlen, "%s", part); + for (char *p = out; *p; p++) + *p = toupper((unsigned char)*p); } bool anyka_detect_cpu(char *chip_name) { /* As wide as chip_name, since that is where it lands. */ - char machine[128] = "", compat[512]; - size_t compat_len; + char machine[128] = "", compat[32] = ""; uint32_t reg; - compat_len = read_file("/proc/device-tree/compatible", compat, - sizeof(compat) - 1); - compat[compat_len] = '\0'; - bool by_machine = anyka_machine_name("/proc/cpuinfo", machine, sizeof(machine)); /* OpenIPC's kernels name the part in the device tree too; a board string * is then not needed to claim the SoC. */ - if (!by_machine && !memmem(compat, compat_len, "anyka,", 6)) + if (!line_from_file("/proc/device-tree/compatible", "^anyka,(.*)", compat, + sizeof(compat)) && + !by_machine) return false; + compat[sizeof(compat) - 1] = '\0'; /* Only ever a refinement: the board already named the part, and a kernel * built with strict devmem filtering can refuse the window outright. The @@ -234,23 +173,30 @@ bool anyka_detect_cpu(char *chip_name) { } } - /* 0x20120100 is also the Aimer39 AK3918's, which is a generation of its - * own: the board string or the device tree says which this is. */ - if (anyka_chip_id == 0x20120100) { - char aimer[16]; - - if (line_from_file("/proc/cpuinfo", "Hardware.*:.*(Aimer39)", aimer, - sizeof(aimer)) || - anyka_is_aimer39_dt(compat, compat_len)) - chip_generation = AK39_EV1; - } + /* 0x20120100 is also the Aimer39 AK3918's, a generation of its own. Its + * boards and device trees name a bare part, "Aimer39_AK3918_MB_V1.0.0" + * and "anyka,ak3918", where an EV200's carry more. */ + if (anyka_chip_id == 0x20120100 && + (is_bare_part(machine) || is_bare_part(compat))) + chip_generation = AK39_EV1; - anyka_model_name(by_machine ? machine : NULL, compat, compat_len, - chip_generation, chip_name, 128); + anyka_model_name(by_machine ? machine : NULL, compat, chip_generation, + chip_name, 128); return *chip_name != '\0'; } #ifndef STANDALONE_LIBRARY +static size_t read_file(const char *path, char *buf, size_t len) { + FILE *f = fopen(path, "rb"); + size_t n; + + if (!f) + return 0; + n = fread(buf, 1, len, f); + fclose(f); + return n; +} + /* Total DRAM is not something Linux can always see here. A vendor boot * loader keeps the foot of the bank for the ISP and the encoder and hands the * kernel only what is left -- 37 MB of a 64 MB part on the camera in issue @@ -331,6 +277,7 @@ static void anyka_chip_properties(cJSON *j_inner) { } #endif +#ifndef STANDALONE_LIBRARY /* * The sensor answers I2C only while it is clocked and out of reset, and on * these SoCs that is only while the camera driver is using it: it powers the @@ -522,11 +469,13 @@ static void anyka_hal_cleanup() { restore_printk(); } +#endif /* !STANDALONE_LIBRARY */ + void anyka_setup_hal() { possible_i2c_addrs = anyka_possible_i2c_addrs; +#ifndef STANDALONE_LIBRARY hal_enable_sensor_clock = anyka_enable_sensor_clock; hal_cleanup = anyka_hal_cleanup; -#ifndef STANDALONE_LIBRARY hal_totalmem = anyka_totalmem; hal_chip_properties = anyka_chip_properties; #endif diff --git a/src/hal/anyka.h b/src/hal/anyka.h index 596c1a3..424ab3d 100644 --- a/src/hal/anyka.h +++ b/src/hal/anyka.h @@ -22,6 +22,8 @@ * boards. The part number comes from the machine string instead. */ /* The first generation, Aimer39 (AK3918, AK3916, AK3910): its 0x20120100 is * shared with some AK3918EV200 boards, so the board string decides. */ +/* anyka_model_name() prints the low 16 bits as the part suffix (0xE200 is + * EV200), so a new AK39 constant has to spell its own. */ #define AK39_EV1 0x3918E100 #define AK39_EV2 0x3918E200 #define AK39_EV3 0x3918E300 @@ -41,12 +43,8 @@ void anyka_setup_hal(); * /proc paths. */ bool anyka_machine_name(const char *cpuinfo, char *out, size_t outlen); int anyka_generation(uint32_t chip_id); -bool anyka_compatible_part(const char *compat, size_t len, char *out, - size_t outlen); -bool anyka_is_aimer39_dt(const char *compat, size_t len); -void anyka_model_name(const char *machine, const char *compat, - size_t compat_len, int generation, char *out, - size_t outlen); +void anyka_model_name(const char *machine, const char *compat, int generation, + char *out, size_t outlen); unsigned long anyka_media_mem(const char *cmdline, const char *iomem); unsigned long anyka_dt_memsize(const char *reg_path); bool anyka_dt_gpio(const char *pattern, int *pin, bool *active_low); diff --git a/src/hal/anyka_test.c b/src/hal/anyka_test.c index 18f7506..ea7ebed 100644 --- a/src/hal/anyka_test.c +++ b/src/hal/anyka_test.c @@ -151,11 +151,10 @@ static void chip_ids(void) { static void expect_model(const char *machine, const char *compat, - size_t compat_len, int generation, const char *want) { + int generation, const char *want) { char got[128]; - anyka_model_name(machine, compat, compat_len, generation, got, - sizeof(got)); + anyka_model_name(machine, compat, generation, got, sizeof(got)); CHECK(!strcmp(got, want)); if (strcmp(got, want)) fprintf(stderr, " (wanted \"%s\", got \"%s\")\n", want, got); @@ -167,36 +166,30 @@ static void expect_model(const char *machine, const char *compat, * ID 0x20150200), an AK3916EV301 camera whose vendor kernel says EV300 * (0x20160100), and EV330 vendor kernels, which carry a device tree. */ static void model_names(void) { - static const char ev200_dt[] = "anyka,ak3918ev200"; - static const char ev330_dt[] = "anyka,ak3919ev330\0anyka,ak39ev330"; - static const char generic_dt[] = "anyka,ak39ev330"; - - expect_model("AK3918E80PIN", ev200_dt, sizeof(ev200_dt), AK39_EV2, - "AK3918EV200"); - expect_model("AK3918E80PIN", NULL, 0, AK39_EV2, "AK3918EV200"); - expect_model("AK3918E80PIN", NULL, 0, AK39_EV3, "AK3918EV300"); - expect_model("AK3918E", NULL, 0, AK39_EV2, "AK3918EV200"); - expect_model("AK3918EV200", NULL, 0, AK39_EV2, "AK3918EV200"); - expect_model("AK3918EV300", NULL, 0, AK39_EV3, "AK3918EV300"); - expect_model("AK39EV330", ev330_dt, sizeof(ev330_dt), AK39_EV330, - "AK3919EV330"); + expect_model("AK3918E80PIN", "ak3918ev200", AK39_EV2, "AK3918EV200"); + expect_model("AK3918E80PIN", NULL, AK39_EV2, "AK3918EV200"); + expect_model("AK3918E80PIN", NULL, AK39_EV3, "AK3918EV300"); + expect_model("AK3918E", NULL, AK39_EV2, "AK3918EV200"); + expect_model("AK3918EV200", NULL, AK39_EV2, "AK3918EV200"); + expect_model("AK3918EV300", NULL, AK39_EV3, "AK3918EV300"); + expect_model("AK39EV330", "ak3919ev330", AK39_EV330, "AK3919EV330"); /* Only the generation-wide compatible: name the generation, as the * vendor banner does ("ANYKA CPU AK39XXEV330"), not the board's short * form. */ - expect_model("AK39EV330", generic_dt, sizeof(generic_dt), AK39_EV330, - "AK39XXEV330"); - expect_model("AK39EV330", NULL, 0, AK39_EV330, "AK39XXEV330"); + expect_model("AK39EV330", "ak39ev330", AK39_EV330, "AK39XXEV330"); + expect_model("AK39EV330", NULL, AK39_EV330, "AK39XXEV330"); /* Aimer39 has no suffix, and a generation nobody can name adds none. */ - expect_model("AK3918", NULL, 0, AK39_EV1, "AK3918"); - expect_model("AK3918E80PIN", NULL, 0, 0, "AK3918E80PIN"); - expect_model("AK3918AV130", NULL, 0, AK39_AV130, "AK3918AV130"); + expect_model("AK3918", NULL, AK39_EV1, "AK3918"); + expect_model("AK3918E80PIN", NULL, 0, "AK3918E80PIN"); + expect_model("AK3918AV130", NULL, AK39_AV130, "AK3918AV130"); + /* The suffix comes out of the generation constant. */ + expect_model("AK3918E80PIN", NULL, AK39_AV130, "AK3918AV130"); + expect_model("AK3918E80PIN", NULL, AK39_AV100, "AK3918AV100"); + expect_model("AK3918E80PIN", NULL, AK39_EV300L, "AK3918E80PIN"); + /* AK37 constants spell no AK39 suffix. */ + expect_model("AK3760", NULL, AK37_D, "AK3760"); /* Device tree alone, no board string to go on. */ - expect_model(NULL, ev200_dt, sizeof(ev200_dt), 0, "AK3918EV200"); - - static const char aimer_dt[] = "anyka,ak3918"; - CHECK(anyka_is_aimer39_dt(aimer_dt, sizeof(aimer_dt))); - CHECK(!anyka_is_aimer39_dt(ev200_dt, sizeof(ev200_dt))); - CHECK(!anyka_is_aimer39_dt(generic_dt, sizeof(generic_dt))); + expect_model(NULL, "ak3918ev200", 0, "AK3918EV200"); } static const char *binary_fixture(const unsigned char *data, size_t len) { diff --git a/src/sensors.c b/src/sensors.c index 84e2e2b..dcf5976 100644 --- a/src/sensors.c +++ b/src/sensors.c @@ -1201,6 +1201,7 @@ static int detect_cvsens_sensor(sensor_ctx_t *ctx, int fd, } } +#ifndef STANDALONE_LIBRARY /* PrimeSensor PS52xx, as the Anyka EV200/EV300 drivers read it: the ID is in * bank 0, registers 0x00/0x01, byte addresses, and the bank is selected * through 0xef. The bank is put back as it was found, like the SuperPix page: @@ -1264,6 +1265,7 @@ static int detect_byd_sensor(sensor_ctx_t *ctx, int fd, return false; } } +#endif /* !STANDALONE_LIBRARY */ static int detect_possible_sensors(sensor_ctx_t *ctx, int fd, int (*detect_fn)(sensor_ctx_t *ctx, int, @@ -1351,6 +1353,7 @@ static bool get_sensor_id_i2c(sensor_ctx_t *ctx) { SENSOR_CVSENS)) { strcpy(ctx->vendor, "CVSENS"); detected = true; +#ifndef STANDALONE_LIBRARY } else if (detect_possible_sensors(ctx, fd, detect_primesensor_sensor, SENSOR_PRIMESENSOR)) { strcpy(ctx->vendor, "PrimeSensor"); @@ -1361,6 +1364,7 @@ static bool get_sensor_id_i2c(sensor_ctx_t *ctx) { strcpy(ctx->vendor, "BYD"); ctx->reg_width = 1; detected = true; +#endif } exit: close_sensor_fd(fd); From c2546d4f9b9ba9889e42445dff4f332ac8ff6409 Mon Sep 17 00:00:00 2001 From: Dmitry Ermakov <7954832+dimerr@users.noreply.github.com> Date: Sun, 8 Nov 2026 16:28:52 +0000 Subject: [PATCH 8/8] anyka: power the sensor up in ipcinfo too ipcinfo is how OpenIPC finds the sensor, so it cannot leave the power-up to ipctool. It now starts the CIS clock itself and grows 728 bytes with every vendor built in, 5 with none. The sensor drivers' power-off leaves reset an input, so on a board with no pwdn-gpio the sensor answers once clocked. ipctool also pulses the camera GPIOs. Every write goes through one helper that remembers the register's old value, and cleanup writes those back. ipcinfo takes the part from the device tree and otherwise the board string as before; working a part number out of a vendor board string, and the AV130 and Aimer39 generations, stay in ipctool. --- CLAUDE.md | 18 ++- src/chipid.c | 6 +- src/hal/anyka.c | 345 +++++++++++++++++++++---------------------- src/hal/anyka.h | 14 +- src/hal/anyka_test.c | 48 +++--- 5 files changed, 208 insertions(+), 223 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c9ed083..a12dfb6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -121,8 +121,8 @@ CMake knobs worth knowing: than `assert()`: the release flags carry `-DNDEBUG`, so an `assert()`-based test compiles away to nothing and passes unconditionally. - `./build/anyka_test`: the Anyka HAL's parsers -- the `/proc/cpuinfo` - machine string, the device tree's compatible, `/memory` and camera GPIO - cells, the chip-ID table, the model naming, the peripheral-PLL decode and + machine string, the device tree's `/memory` cells, the chip-ID table, the + model naming, the CIS clock divider and the media-memory arithmetic -- fed fixture files through the path arguments `src/hal/anyka.h` exposes for the purpose, with inputs taken from real boot logs. Same `CHECK` macro as `longse_test`. The detection itself, including @@ -226,11 +226,15 @@ reset/power-down GPIOs the device tree names for the camera node, and `hal_cleanup` puts back exactly what it changed. It touches nothing when the clock is already running, which is a pipeline streaming. -That power-up, and the PrimeSensor and BYD detectors only Anyka boards use, -are `ipctool`-only (`#ifndef STANDALONE_LIBRARY`): `ipcinfo` is in every -camera image and the size check holds it to 1 KiB of growth per PR. So -`ipcinfo -s` finds an Anyka sensor only while something is already clocking -it. The part name stays in `ipcinfo`, because `ipcinfo -c` has to be right. +`ipcinfo` is in every camera image and the size check holds it to 1 KiB of +growth per PR, so it carries the least that works, behind +`#ifndef STANDALONE_LIBRARY` for the rest. It starts the CIS clock and takes +the part from the device tree; the sensor drivers' power-off leaves reset an +input, so that is enough where the camera node has no `pwdn-gpio` (those +drivers do assert power-down, on a board that has the pin). Only `ipctool` +pulses the GPIOs, works a part number out of a vendor kernel's board string, +and knows the PrimeSensor and BYD sensors, which have no tuning in the firmware +package. Ambarella (`src/hal/ambarella.c`) stops at the machine string on purpose. The exact part (S3L33M, S3L55M, S3L66, ...) is a word the DSP firmware writes, diff --git a/src/chipid.c b/src/chipid.c index b7c46fc..8718496 100644 --- a/src/chipid.c +++ b/src/chipid.c @@ -338,8 +338,12 @@ const char *getchipfamily() { return "rv1106"; #endif #ifdef IPCHW_VENDOR_ANYKA +#ifndef STANDALONE_LIBRARY case AK39_EV1: return "ak3918"; + case AK39_AV130: + return "ak3918av130"; +#endif case AK39_EV2: return "ak3918ev200"; case AK39_EV3: @@ -350,8 +354,6 @@ const char *getchipfamily() { return "ak3918ev300l"; case AK39_AV100: return "ak3918av100"; - case AK39_AV130: - return "ak3918av130"; case AK37_D: return "ak37d"; case AK37_E: diff --git a/src/hal/anyka.c b/src/hal/anyka.c index 09a4e0d..a28b18a 100644 --- a/src/hal/anyka.c +++ b/src/hal/anyka.c @@ -1,6 +1,7 @@ #include "hal/anyka.h" #include +#include #include #include #include @@ -49,7 +50,9 @@ static sensor_addr_t anyka_possible_i2c_addrs[] = { /* Raw chip ID as it was read, kept so the report can show the number the * kernel's own "ANYKA CPU ... (ID 0x...)" banner prints. Zero when /dev/mem * would not give it up. */ +#ifndef STANDALONE_LIBRARY static uint32_t anyka_chip_id; +#endif static const struct { uint32_t id; @@ -88,7 +91,8 @@ int anyka_generation(uint32_t chip_id) { * with /proc/iomem, so the machine string is the one signal that every * kernel gives without /dev/mem. Every Anyka camera board spells a part * number into it: "CLOUD39EV3_AK3918EV300_MNBD", - * "Cloud39EV2_AK3918E80PIN_MNBD", "AK3918EV200_GWELL_V1", "Cloud39E_AK3918E+H42_V1.0.2", + * "Cloud39EV2_AK3918E80PIN_MNBD", "AK3918EV200_GWELL_V1", + * "Cloud39E_AK3918E+H42_V1.0.2", * "Aimer39_AK3918_MB_V1.0.0", "AK39EV330". */ bool anyka_machine_name(const char *cpuinfo, char *out, size_t outlen) { if (!line_from_file(cpuinfo, "Hardware.*:.*([Aa][Kk]3[0-9][0-9A-Za-z]*)", @@ -101,10 +105,11 @@ bool anyka_machine_name(const char *cpuinfo, char *out, size_t outlen) { return true; } -/* Is s "AK" + four digits, e.g. AK3918? */ +#ifndef STANDALONE_LIBRARY +/* Is this match of the board or device tree patterns below "AK" + four + * digits, e.g. AK3918, and not the generation-wide AK39EV330? */ static bool is_part_base(const char *s) { - return (s[0] | 32) == 'a' && (s[1] | 32) == 'k' && isdigit(s[2]) && - isdigit(s[3]) && isdigit(s[4]) && isdigit(s[5]); + return s[0] && isdigit(s[4]) && isdigit(s[5]); } static bool is_bare_part(const char *s) { @@ -114,7 +119,7 @@ static bool is_bare_part(const char *s) { /* ...followed by EVnnn or AVnnn: the whole part number, AK3918EV200. A * package, "AK3918E80PIN", has no V there. */ static bool is_full_part(const char *s) { - return is_part_base(s) && s[6] && (s[7] | 32) == 'v' && isdigit(s[8]); + return is_part_base(s) && s[6] && (s[7] | 32) == 'v'; } /* The model to report. `compat` is what follows "anyka," in the device tree's @@ -128,15 +133,12 @@ static bool is_full_part(const char *s) { * kernel's CPU banner names it. */ void anyka_model_name(const char *machine, const char *compat, int generation, char *out, size_t outlen) { - const char *part = compat; + const char *part = is_full_part(compat) ? compat : machine; - if (!part || !is_full_part(part)) { - part = machine ? machine : ""; - if (generation == AK39_EV330 && !is_full_part(part)) - part = "AK39XXEV330"; - } - if (is_part_base(part) && !is_full_part(part) && generation >> 24 == 0x39 && - generation != AK39_EV1 && !(generation & 0xf)) + if (generation == AK39_EV330 && !is_full_part(part)) + part = "AK39XXEV330"; + if (generation && !(generation & 0xf) && is_part_base(part) && + !is_full_part(part)) snprintf(out, outlen, "AK%.4s%XV%03X", part + 2, generation >> 12 & 0xf, generation & 0xfff); else @@ -145,58 +147,70 @@ void anyka_model_name(const char *machine, const char *compat, int generation, *p = toupper((unsigned char)*p); } -bool anyka_detect_cpu(char *chip_name) { +/* Up to len bytes of a file, or 0. */ +static int read_file(const char *path, void *buf, size_t len) { + int fd = open(path, O_RDONLY), n; + + if (fd < 0) + return 0; + n = read(fd, buf, len); + close(fd); + return n; +} + +/* Everything a vendor kernel's board string leaves to be worked out. */ +static bool anyka_full_model(char *chip_name, uint32_t id) { /* As wide as chip_name, since that is where it lands. */ - char machine[128] = "", compat[32] = ""; - uint32_t reg; - - bool by_machine = - anyka_machine_name("/proc/cpuinfo", machine, sizeof(machine)); - /* OpenIPC's kernels name the part in the device tree too; a board string - * is then not needed to claim the SoC. */ - if (!line_from_file("/proc/device-tree/compatible", "^anyka,(.*)", compat, - sizeof(compat)) && - !by_machine) - return false; - compat[sizeof(compat) - 1] = '\0'; + char machine[128], compat[32]; - /* Only ever a refinement: the board already named the part, and a kernel - * built with strict devmem filtering can refuse the window outright. The - * AV130 keeps its ID at the far end of the same block. */ - if (mem_reg(AK_REG_CHIP_ID, ®, OP_READ)) { - anyka_chip_id = reg; - chip_generation = anyka_generation(reg); - if (!chip_generation && mem_reg(AK_REG_CHIP_ID_AV, ®, OP_READ) && - anyka_generation(reg) == AK39_AV130) { - anyka_chip_id = reg; - chip_generation = AK39_AV130; - } - } + machine[0] = compat[0] = '\0'; + anyka_machine_name("/proc/cpuinfo", machine, sizeof(machine)); + line_from_file("/proc/device-tree/compatible", "^anyka,(ak.*)", compat, + sizeof(compat)); + compat[sizeof(compat) - 1] = '\0'; /* 0x20120100 is also the Aimer39 AK3918's, a generation of its own. Its * boards and device trees name a bare part, "Aimer39_AK3918_MB_V1.0.0" * and "anyka,ak3918", where an EV200's carry more. */ - if (anyka_chip_id == 0x20120100 && - (is_bare_part(machine) || is_bare_part(compat))) + if (id == 0x20120100 && (is_bare_part(machine) || is_bare_part(compat))) chip_generation = AK39_EV1; - anyka_model_name(by_machine ? machine : NULL, compat, chip_generation, - chip_name, 128); + anyka_model_name(machine, compat, chip_generation, chip_name, 128); return *chip_name != '\0'; } +#endif -#ifndef STANDALONE_LIBRARY -static size_t read_file(const char *path, char *buf, size_t len) { - FILE *f = fopen(path, "rb"); - size_t n; +bool anyka_detect_cpu(char *chip_name) { + uint32_t reg = 0; - if (!f) - return 0; - n = fread(buf, 1, len, f); - fclose(f); - return n; + /* OpenIPC's kernels name the exact part in the device tree, so a board + * string is then not needed to claim the SoC. */ + if (!line_from_file("/proc/device-tree/compatible", + "^anyka,(ak[0-9]{4}[ae]v.*)", chip_name, 128) && + !anyka_machine_name("/proc/cpuinfo", chip_name, 128)) + return false; + + /* Only ever a refinement: the board already named the part, and a kernel + * built with strict devmem filtering can refuse the window outright. */ + if (mem_reg(AK_REG_CHIP_ID, ®, OP_READ)) + chip_generation = anyka_generation(reg); + +#ifndef STANDALONE_LIBRARY + /* The AV130 keeps its ID at the far end of the same block. */ + uint32_t av; + if (reg && !chip_generation && mem_reg(AK_REG_CHIP_ID_AV, &av, OP_READ) && + anyka_generation(av) == AK39_AV130) { + reg = av; + chip_generation = AK39_AV130; + } + anyka_chip_id = reg; + return anyka_full_model(chip_name, reg); +#else + return true; +#endif } +#ifndef STANDALONE_LIBRARY /* Total DRAM is not something Linux can always see here. A vendor boot * loader keeps the foot of the bank for the ISP and the encoder and hands the * kernel only what is left -- 37 MB of a 64 MB part on the camera in issue @@ -241,7 +255,7 @@ unsigned long anyka_media_mem(const char *cmdline, const char *iomem) { unsigned long anyka_dt_memsize(const char *reg_path) { unsigned char cells[8]; - if (read_file(reg_path, (char *)cells, sizeof(cells)) != sizeof(cells)) + if (read_file(reg_path, cells, sizeof(cells)) != (int)sizeof(cells)) return 0; uint32_t size = (uint32_t)cells[4] << 24 | (uint32_t)cells[5] << 16 | (uint32_t)cells[6] << 8 | cells[7]; @@ -277,7 +291,6 @@ static void anyka_chip_properties(cJSON *j_inner) { } #endif -#ifndef STANDALONE_LIBRARY /* * The sensor answers I2C only while it is clocked and out of reset, and on * these SoCs that is only while the camera driver is using it: it powers the @@ -290,76 +303,69 @@ static void anyka_chip_properties(cJSON *j_inner) { * device tree names. Measured on an AK3918EV200 (SC1345): the sensor reads * 0xffff before and its chip ID after. * + * ipcinfo, in every image, starts the clock alone: the sensor drivers' power + * off leaves those pins as inputs, not held asserted, and the SC1345 answers + * with just the clock. ipctool also pulses the pins. + * * Nothing is touched when the clock is already running: then a pipeline may * be streaming, the sensor already answers, and a reset would stop it. What - * is touched is put back by anyka_hal_cleanup(), bit for bit, and only that. + * is touched is put back by anyka_hal_cleanup(), and only that. */ #define AK_REG_PERI_PLL1 (AK_PA_SYSCTRL + 0x14) #define AK_REG_PERI_PLL2 (AK_PA_SYSCTRL + 0x18) -#define AK_PA_GPIO 0x20170000 -#define AK_GPIO_DIR(pin) (AK_PA_GPIO + 0x00 + ((pin) >> 5) * 4) -#define AK_GPIO_OUT(pin) (AK_PA_GPIO + 0x0c + ((pin) >> 5) * 4) -#define AK_CIS_MCLK_MHZ 24 - -struct anyka_pin { - int pin; /* -1: none */ - bool active_low; - bool saved_dir, saved_out; -}; +#define AK_GPIO_DIR 0x20170000 +#define AK_GPIO_OUT 0x2017000c -static struct { - bool armed; - uint32_t pll1, pll2; - struct anyka_pin reset, pwdn; -} anyka_power; - -/* of a camera node's reset-gpio or pwdn-gpio. The - * pattern must match one node: with several, which camera's pins these are - * is not known, and none is touched. The AK39 device trees have one GPIO - * controller, so the phandle is not followed; flags bit 0 is - * GPIO_ACTIVE_LOW. */ -bool anyka_dt_gpio(const char *pattern, int *pin, bool *active_low) { - glob_t g; - unsigned char cells[12]; - bool ok = false; +/* The CIS clock divider field for 24 MHz from the peripheral PLL register, + * 12 * M / (N * 2^OD) MHz over 24, minus one; 0 for a register the camera + * drivers would refuse. Counted up to keep division out of the binary. */ +unsigned anyka_cis_divider(uint32_t pll1) { + unsigned m = pll1 & 0xff, n = pll1 >> 8 & 0xf, od = pll1 >> 12 & 0x3, d = 0; - if (glob(pattern, 0, NULL, &g)) - return false; - if (g.gl_pathc == 1 && - read_file(g.gl_pathv[0], (char *)cells, sizeof(cells)) == 12) { - *pin = cells[6] << 8 | cells[7]; - *active_low = cells[11] & 1; - ok = *pin < 128; - } - globfree(&g); - return ok; + if (!od || n - 2 > 4) + return 0; + while ((2 * (d + 2) * n << od) <= m) + d++; + return d; } -static bool gpio_bit(uint32_t reg, int pin, bool *val) { - uint32_t v; +/* What each register held before the power-up first wrote it: the PLL's two + * and, in ipctool, direction and level for each of a camera's two pins. + * Restored in one write each: replaying every write to the PLL register left + * its latch bit set on an AK3918EV200. */ +#ifdef STANDALONE_LIBRARY +#define AK_UNDO_MAX 2 +#else +#define AK_UNDO_MAX 6 +#endif +static struct { + uint32_t addr, val; +} anyka_undo[AK_UNDO_MAX]; +static int anyka_undo_n; + +static void anyka_rmw(uint32_t addr, uint32_t clear, uint32_t set) { + uint32_t v = 0; - if (!mem_reg(reg, &v, OP_READ)) - return false; - *val = v >> (pin & 31) & 1; - return true; + mem_reg(addr, &v, OP_READ); + v = (v & ~clear) | set; + mem_reg(addr, &v, OP_WRITE); } -static void gpio_set_bit(uint32_t reg, int pin, bool val) { - uint32_t v; - - if (!mem_reg(reg, &v, OP_READ)) - return; - v = val ? v | 1u << (pin & 31) : v & ~(1u << (pin & 31)); - mem_reg(reg, &v, OP_WRITE); +/* anyka_rmw() for a register's first write, remembering what it held. */ +static void anyka_poke(uint32_t addr, uint32_t clear, uint32_t set) { + anyka_undo[anyka_undo_n].addr = addr; + mem_reg(addr, &anyka_undo[anyka_undo_n++].val, OP_READ); + anyka_rmw(addr, clear, set); } +#ifndef STANDALONE_LIBRARY /* The camera nodes under the I2C adapter being probed: its device tree node * is what /sys/bus/i2c/devices/i2c-N/of_node points at. A kernel that does - * not link it leaves the glob over every adapter, which anyka_dt_gpio() - * accepts only when it finds a single camera. */ -static void camera_pattern(char *out, size_t len, const char *prop) { + * not link it leaves the glob over every adapter, which is used only when it + * finds a single camera. */ +static void camera_gpio_pattern(char *out, size_t len) { char link[64], node[256]; - const char *base = "/sys/firmware/devicetree/base/"; + const char *rel; ssize_t n; snprintf(link, sizeof(link), "/sys/bus/i2c/devices/i2c-%d/of_node", @@ -367,66 +373,62 @@ static void camera_pattern(char *out, size_t len, const char *prop) { n = readlink(link, node, sizeof(node) - 1); if (n > 0) { node[n] = '\0'; - char *rel = strstr(node, "devicetree/base/"); + rel = strstr(node, "devicetree/base/"); if (rel) { - snprintf(out, len, "%s%s/camera@*/%s", base, - rel + strlen("devicetree/base/"), prop); + snprintf(out, len, "/sys/firmware/%s/camera@*/*-gpio", rel); return; } } - snprintf(out, len, "/proc/device-tree/soc/i2c*/camera@*/%s", prop); + snprintf(out, len, "/proc/device-tree/soc/i2c*/camera@*/*-gpio"); } -static void claim_pin(struct anyka_pin *p, const char *prop) { +/* of each GPIO property of the camera node; flags bit 0 + * is GPIO_ACTIVE_LOW. The AK39 device trees have one GPIO controller, so the + * phandle is not followed. More matches than one camera has -- whose pins + * they are is then not known -- and none is touched. Power down is released + * and reset asserted, then reset released. */ +static void anyka_pulse_camera_pins(void) { + uint32_t rst_reg = 0, rst_bit = 0, rst_val = 0; char pattern[320]; + glob_t g; - camera_pattern(pattern, sizeof(pattern), prop); - if (!anyka_dt_gpio(pattern, &p->pin, &p->active_low) || - !gpio_bit(AK_GPIO_DIR(p->pin), p->pin, &p->saved_dir) || - !gpio_bit(AK_GPIO_OUT(p->pin), p->pin, &p->saved_out)) - p->pin = -1; -} - -/* Drive a pin to its asserted (on) or released (off) level. */ -static void drive_pin(struct anyka_pin *p, bool assert) { - if (p->pin < 0) - return; - gpio_set_bit(AK_GPIO_OUT(p->pin), p->pin, assert == p->active_low ? 0 : 1); - gpio_set_bit(AK_GPIO_DIR(p->pin), p->pin, 1); -} - -static void restore_pin(struct anyka_pin *p) { - if (p->pin < 0) + camera_gpio_pattern(pattern, sizeof(pattern)); + if (glob(pattern, 0, NULL, &g)) return; - gpio_set_bit(AK_GPIO_DIR(p->pin), p->pin, p->saved_dir); - gpio_set_bit(AK_GPIO_OUT(p->pin), p->pin, p->saved_out); -} - -/* Peripheral PLL in MHz, as the camera drivers compute it, or 0 when the - * register holds something they would refuse. */ -unsigned anyka_peri_pll_mhz(uint32_t pll1) { - unsigned m = pll1 & 0xff, n = pll1 >> 8 & 0xf, od = pll1 >> 12 & 0x3; - - if (od < 1 || n < 2 || n > 6 || m < 84 || m > 254) - return 0; - return 12 * m / (n * (1u << od)); + for (size_t i = 0; g.gl_pathc <= 2 && i < g.gl_pathc; i++) { + unsigned char c[12]; + + if (read_file(g.gl_pathv[i], c, sizeof(c)) != sizeof(c) || c[7] > 127) + continue; + uint32_t bank = (c[7] >> 5) * 4, bit = 1u << (c[7] & 31); + bool low = c[11] & 1, reset = strstr(g.gl_pathv[i], "reset"); + + anyka_poke(AK_GPIO_OUT + bank, bit, reset != low ? bit : 0); + anyka_poke(AK_GPIO_DIR + bank, 0, bit); + if (reset) { + rst_reg = AK_GPIO_OUT + bank; + rst_bit = bit; + rst_val = low ? bit : 0; + } + } + globfree(&g); + usleep(10000); + if (rst_reg) + anyka_rmw(rst_reg, rst_bit, rst_val); } +#endif static void anyka_enable_sensor_clock() { - uint32_t pll1, pll2, v; - unsigned pll; + uint32_t pll1, pll2; + unsigned div; - if (anyka_power.armed) - return; - if (chip_generation != AK39_EV2 && chip_generation != AK39_EV3) + if (anyka_undo_n || (chip_generation | 0x100) != AK39_EV3 || + !mem_reg(AK_REG_PERI_PLL1, &pll1, OP_READ) || + !mem_reg(AK_REG_PERI_PLL2, &pll2, OP_READ) || + pll2 & 1u << 18) /* running: leave it to whoever runs it */ return; - if (!mem_reg(AK_REG_PERI_PLL1, &pll1, OP_READ) || - !mem_reg(AK_REG_PERI_PLL2, &pll2, OP_READ)) - return; - if (pll2 & 1u << 18) - return; /* running: leave it to whoever runs it */ - pll = anyka_peri_pll_mhz(pll1); - if (!pll) + div = anyka_cis_divider(pll1); + if (!div) return; /* The probe calls hal_cleanup() only after it has opened the bus, so a @@ -436,46 +438,31 @@ static void anyka_enable_sensor_clock() { return; close_sensor_fd(fd); - anyka_power.pll1 = pll1; - anyka_power.pll2 = pll2; - anyka_power.armed = true; - - claim_pin(&anyka_power.reset, "reset-gpio"); - claim_pin(&anyka_power.pwdn, "pwdn-gpio"); - - v = pll1 & ~(1u << 25); - if (v != pll1) - mem_reg(AK_REG_PERI_PLL1, &v, OP_WRITE); - v = (pll2 & ~(0x3fu << 10)) | (pll / AK_CIS_MCLK_MHZ - 1) << 10 | 1u << 19; - mem_reg(AK_REG_PERI_PLL2, &v, OP_WRITE); - v |= 1u << 18; - mem_reg(AK_REG_PERI_PLL2, &v, OP_WRITE); + if (pll1 & 1u << 25) + anyka_poke(AK_REG_PERI_PLL1, 1u << 25, 0); + anyka_poke(AK_REG_PERI_PLL2, 0x3fu << 10, div << 10 | 1u << 19); + anyka_rmw(AK_REG_PERI_PLL2, 0, 1u << 18); - drive_pin(&anyka_power.pwdn, false); - drive_pin(&anyka_power.reset, true); - usleep(10000); - drive_pin(&anyka_power.reset, false); +#ifndef STANDALONE_LIBRARY + anyka_pulse_camera_pins(); +#endif usleep(20000); } static void anyka_hal_cleanup() { - if (anyka_power.armed) { - restore_pin(&anyka_power.reset); - restore_pin(&anyka_power.pwdn); - mem_reg(AK_REG_PERI_PLL2, &anyka_power.pll2, OP_WRITE); - mem_reg(AK_REG_PERI_PLL1, &anyka_power.pll1, OP_WRITE); - anyka_power.armed = false; + while (anyka_undo_n) { + anyka_undo_n--; + mem_reg(anyka_undo[anyka_undo_n].addr, &anyka_undo[anyka_undo_n].val, + OP_WRITE); } restore_printk(); } -#endif /* !STANDALONE_LIBRARY */ - void anyka_setup_hal() { possible_i2c_addrs = anyka_possible_i2c_addrs; -#ifndef STANDALONE_LIBRARY hal_enable_sensor_clock = anyka_enable_sensor_clock; hal_cleanup = anyka_hal_cleanup; +#ifndef STANDALONE_LIBRARY hal_totalmem = anyka_totalmem; hal_chip_properties = anyka_chip_properties; #endif diff --git a/src/hal/anyka.h b/src/hal/anyka.h index 424ab3d..5ae8bb6 100644 --- a/src/hal/anyka.h +++ b/src/hal/anyka.h @@ -22,17 +22,18 @@ * boards. The part number comes from the machine string instead. */ /* The first generation, Aimer39 (AK3918, AK3916, AK3910): its 0x20120100 is * shared with some AK3918EV200 boards, so the board string decides. */ -/* anyka_model_name() prints the low 16 bits as the part suffix (0xE200 is - * EV200), so a new AK39 constant has to spell its own. */ -#define AK39_EV1 0x3918E100 +/* A constant whose low nibble is 0 spells its part's suffix in its low 16 + * bits, 0xE200 being EV200, and anyka_model_name() prints it; the others have + * none to give. */ +#define AK39_EV1 0x3918E101 #define AK39_EV2 0x3918E200 #define AK39_EV3 0x3918E300 #define AK39_EV330 0x3939E330 #define AK39_EV300L 0x3918E30C #define AK39_AV100 0x3918A100 #define AK39_AV130 0x3918A130 -#define AK37_D 0x3737D000 -#define AK37_E 0x3737E000 +#define AK37_D 0x3737D001 +#define AK37_E 0x3737E001 bool anyka_detect_cpu(char *chip_name); void anyka_setup_hal(); @@ -47,7 +48,6 @@ void anyka_model_name(const char *machine, const char *compat, int generation, char *out, size_t outlen); unsigned long anyka_media_mem(const char *cmdline, const char *iomem); unsigned long anyka_dt_memsize(const char *reg_path); -bool anyka_dt_gpio(const char *pattern, int *pin, bool *active_low); -unsigned anyka_peri_pll_mhz(uint32_t pll1); +unsigned anyka_cis_divider(uint32_t pll1); #endif /* HAL_ANYKA_H */ diff --git a/src/hal/anyka_test.c b/src/hal/anyka_test.c index ea7ebed..ed19e05 100644 --- a/src/hal/anyka_test.c +++ b/src/hal/anyka_test.c @@ -167,29 +167,29 @@ static void expect_model(const char *machine, const char *compat, * (0x20160100), and EV330 vendor kernels, which carry a device tree. */ static void model_names(void) { expect_model("AK3918E80PIN", "ak3918ev200", AK39_EV2, "AK3918EV200"); - expect_model("AK3918E80PIN", NULL, AK39_EV2, "AK3918EV200"); - expect_model("AK3918E80PIN", NULL, AK39_EV3, "AK3918EV300"); - expect_model("AK3918E", NULL, AK39_EV2, "AK3918EV200"); - expect_model("AK3918EV200", NULL, AK39_EV2, "AK3918EV200"); - expect_model("AK3918EV300", NULL, AK39_EV3, "AK3918EV300"); + expect_model("AK3918E80PIN", "", AK39_EV2, "AK3918EV200"); + expect_model("AK3918E80PIN", "", AK39_EV3, "AK3918EV300"); + expect_model("AK3918E", "", AK39_EV2, "AK3918EV200"); + expect_model("AK3918EV200", "", AK39_EV2, "AK3918EV200"); + expect_model("AK3918EV300", "", AK39_EV3, "AK3918EV300"); expect_model("AK39EV330", "ak3919ev330", AK39_EV330, "AK3919EV330"); /* Only the generation-wide compatible: name the generation, as the * vendor banner does ("ANYKA CPU AK39XXEV330"), not the board's short * form. */ expect_model("AK39EV330", "ak39ev330", AK39_EV330, "AK39XXEV330"); - expect_model("AK39EV330", NULL, AK39_EV330, "AK39XXEV330"); + expect_model("AK39EV330", "", AK39_EV330, "AK39XXEV330"); /* Aimer39 has no suffix, and a generation nobody can name adds none. */ - expect_model("AK3918", NULL, AK39_EV1, "AK3918"); - expect_model("AK3918E80PIN", NULL, 0, "AK3918E80PIN"); - expect_model("AK3918AV130", NULL, AK39_AV130, "AK3918AV130"); + expect_model("AK3918", "", AK39_EV1, "AK3918"); + expect_model("AK3918E80PIN", "", 0, "AK3918E80PIN"); + expect_model("AK3918AV130", "", AK39_AV130, "AK3918AV130"); /* The suffix comes out of the generation constant. */ - expect_model("AK3918E80PIN", NULL, AK39_AV130, "AK3918AV130"); - expect_model("AK3918E80PIN", NULL, AK39_AV100, "AK3918AV100"); - expect_model("AK3918E80PIN", NULL, AK39_EV300L, "AK3918E80PIN"); + expect_model("AK3918E80PIN", "", AK39_AV130, "AK3918AV130"); + expect_model("AK3918E80PIN", "", AK39_AV100, "AK3918AV100"); + expect_model("AK3918E80PIN", "", AK39_EV300L, "AK3918E80PIN"); /* AK37 constants spell no AK39 suffix. */ - expect_model("AK3760", NULL, AK37_D, "AK3760"); + expect_model("AK3760", "", AK37_D, "AK3760"); /* Device tree alone, no board string to go on. */ - expect_model(NULL, "ak3918ev200", 0, "AK3918EV200"); + expect_model("", "ak3918ev200", 0, "AK3918EV200"); } static const char *binary_fixture(const unsigned char *data, size_t len) { @@ -212,21 +212,13 @@ static void device_tree(void) { /* reset-gpio = <&gpio 0 GPIO_ACTIVE_LOW>, the gwell camera's. */ static const unsigned char gpio[] = {0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 1}; - int pin = -1; - bool active_low = false; - path = binary_fixture(gpio, sizeof(gpio)); - CHECK(anyka_dt_gpio(path, &pin, &active_low)); - CHECK(pin == 0 && active_low); - unlink(path); - CHECK(!anyka_dt_gpio("/nonexistent/*/reset-gpio", &pin, &active_low)); - - /* The EV200's peripheral PLL, 0x00a512c8: M 200, N 2, OD 1 -> 600 MHz. - * Values the camera drivers refuse give 0. */ - CHECK(anyka_peri_pll_mhz(0x00a512c8) == 600); - CHECK(anyka_peri_pll_mhz(0x00000000) == 0); + /* The EV200's peripheral PLL, 0x00a512c8: M 200, N 2, OD 1 -> 600 MHz, + * and 600 / 24 - 1 = 24. Values the camera drivers refuse give 0. */ + CHECK(anyka_cis_divider(0x00a512c8) == 24); + CHECK(anyka_cis_divider(0x00000000) == 0); /* OD 0, and N 1: both outside what the drivers accept. */ - CHECK(anyka_peri_pll_mhz(0x000002c8) == 0); - CHECK(anyka_peri_pll_mhz(0x000011c8) == 0); + CHECK(anyka_cis_divider(0x000002c8) == 0); + CHECK(anyka_cis_divider(0x000011c8) == 0); } static void media_mem(void) {