初始化奇妙小屏幕控制器项目

This commit is contained in:
2026-09-08 22:56:52 +08:00
commit 8d368de3b5
491 changed files with 67678 additions and 0 deletions
@@ -0,0 +1,31 @@
CC ?= gcc
CFLAGS ?= -O3 -std=c11 -Wall -Wextra -Werror -fPIC -pthread
LDFLAGS ?= -pthread
LIBRARY := libh618_hub75.so
BENCHMARK := hub75_benchmark
UNIT_TEST := hub75_native_test
SAFEOFF := hub75_safeoff
.PHONY: all clean test
all: $(LIBRARY) $(BENCHMARK) $(UNIT_TEST) $(SAFEOFF)
$(LIBRARY): h618_hub75.c h618_hub75.h
$(CC) $(CFLAGS) -shared h618_hub75.c -o $@ $(LDFLAGS)
$(BENCHMARK): benchmark.c h618_hub75.c h618_hub75.h
$(CC) $(CFLAGS) benchmark.c h618_hub75.c -o $@ $(LDFLAGS)
$(UNIT_TEST): native_test.c h618_hub75.c h618_hub75.h
$(CC) $(CFLAGS) native_test.c h618_hub75.c -o $@ $(LDFLAGS)
$(SAFEOFF): safeoff.c h618_hub75.c h618_hub75.h
$(CC) $(CFLAGS) safeoff.c h618_hub75.c -o $@ $(LDFLAGS)
test: $(UNIT_TEST) $(SAFEOFF)
./$(UNIT_TEST)
./$(SAFEOFF) --fake
clean:
rm -f -- $(LIBRARY) $(BENCHMARK) $(UNIT_TEST) $(SAFEOFF)
@@ -0,0 +1,30 @@
# Third-party notices
The H618 PIO register layout and WalnutPi 40-pin physical mapping used by
`h618_hub75.c` were derived from:
- Project: `walnutpi/gpioc`
- Source: https://github.com/walnutpi/gpioc
- Commit: `220a46267469cc9cb1cbc35cd72f35eb1699a6e8`
- License: MIT
- Copyright: Copyright (c) 2023 酥饼
## MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,188 @@
#define _POSIX_C_SOURCE 200809L
#include "h618_hub75.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
static double monotonic_seconds(void) {
struct timespec value;
clock_gettime(CLOCK_MONOTONIC, &value);
return (double)value.tv_sec + (double)value.tv_nsec / 1000000000.0;
}
static int integer_argument(const char *value, const char *name) {
char *end = NULL;
long parsed = strtol(value, &end, 10);
if (value[0] == '\0' || end == NULL || *end != '\0' || parsed < 0 || parsed > 1000000) {
fprintf(stderr, "invalid %s: %s\n", name, value);
exit(2);
}
return (int)parsed;
}
static int hex_nibble(char value) {
if (value >= '0' && value <= '9') return value - '0';
if (value >= 'a' && value <= 'f') return value - 'a' + 10;
if (value >= 'A' && value <= 'F') return value - 'A' + 10;
return -1;
}
static void color_argument(const char *value, uint8_t output[3]) {
if (strlen(value) != 6) {
fprintf(stderr, "invalid color: %s (expected RRGGBB)\n", value);
exit(2);
}
for (unsigned channel = 0; channel < 3; ++channel) {
int high = hex_nibble(value[channel * 2]);
int low = hex_nibble(value[channel * 2 + 1]);
if (high < 0 || low < 0) {
fprintf(stderr, "invalid color: %s (expected RRGGBB)\n", value);
exit(2);
}
output[channel] = (uint8_t)((high << 4) | low);
}
}
int main(int argc, char **argv) {
int duration = 10;
int refresh_rate = 100;
int brightness = 40;
int use_dev_mem = 0;
int use_static_color = 0;
uint8_t static_color[3] = {0};
for (int index = 1; index < argc; ++index) {
if (strcmp(argv[index], "--duration") == 0 && index + 1 < argc) {
duration = integer_argument(argv[++index], "duration");
} else if (strcmp(argv[index], "--refresh-rate") == 0 && index + 1 < argc) {
refresh_rate = integer_argument(argv[++index], "refresh rate");
} else if (strcmp(argv[index], "--brightness") == 0 && index + 1 < argc) {
brightness = integer_argument(argv[++index], "brightness");
} else if (strcmp(argv[index], "--dev-mem") == 0) {
use_dev_mem = 1;
} else if (strcmp(argv[index], "--color") == 0 && index + 1 < argc) {
color_argument(argv[++index], static_color);
use_static_color = 1;
} else {
fprintf(stderr, "usage: %s [--duration N] [--refresh-rate N] [--brightness N] [--color RRGGBB] [--dev-mem]\n", argv[0]);
return 2;
}
}
if (duration < 1) {
fputs("duration must be at least one second\n", stderr);
return 2;
}
hub75_config config = {
.brightness = brightness,
.refresh_rate_hz = refresh_rate,
.cpu_affinity = 3,
.realtime_priority = 50,
.use_dev_mem = use_dev_mem,
};
char error[512] = "";
hub75_context *context = hub75_create(&config, error, sizeof(error));
if (context == NULL) {
fprintf(stderr, "create failed: %s\n", error);
return 1;
}
if (hub75_start(context) != 0) {
fputs("start failed\n", stderr);
hub75_destroy(context);
return 1;
}
uint8_t frame[HUB75_FRAME_BYTES];
if (use_static_color) {
for (unsigned pixel = 0; pixel < HUB75_WIDTH * HUB75_HEIGHT; ++pixel) {
memcpy(frame + pixel * 3u, static_color, sizeof(static_color));
}
if (hub75_submit_rgb(context, frame, sizeof(frame)) != 0) {
fputs("initial static submit failed\n", stderr);
hub75_destroy(context);
return 1;
}
}
double started = monotonic_seconds();
unsigned submitted = 0;
while (monotonic_seconds() - started < duration) {
if (!use_static_color) {
for (unsigned pixel = 0; pixel < HUB75_WIDTH * HUB75_HEIGHT; ++pixel) {
frame[pixel * 3] = (uint8_t)(pixel + submitted);
frame[pixel * 3 + 1] = (uint8_t)(pixel * 3u + submitted);
frame[pixel * 3 + 2] = (uint8_t)(pixel * 7u + submitted);
}
if (hub75_submit_rgb(context, frame, sizeof(frame)) != 0) {
fputs("submit failed\n", stderr);
hub75_destroy(context);
return 1;
}
++submitted;
}
struct timespec delay = {.tv_sec = 0, .tv_nsec = 20000000};
nanosleep(&delay, NULL);
}
hub75_stats stats;
if (hub75_get_stats(context, &stats) != 0) {
fputs("stats failed\n", stderr);
hub75_destroy(context);
return 1;
}
hub75_destroy(context);
int expected_scans = hub75_scans_per_frame_for_brightness(brightness);
double miss_rate = stats.completed_frames == 0 ? 100.0 :
(double)stats.deadline_misses * 100.0 / (double)stats.completed_frames;
printf(
"{\"mode\":\"%s\",\"configured_hz\":%d,\"actual_hz\":%.3f,"
"\"panel_scan_hz\":%.3f,\"completed_frames\":%llu,"
"\"completed_scans\":%llu,\"scans_per_frame\":%d,"
"\"deadline_misses\":%llu,"
"\"deadline_miss_percent\":%.6f,\"buffer_swaps\":%llu,"
"\"submitted_frames\":%llu,\"oe_timing_backend\":\"%s\","
"\"oe_pulse_faults\":%llu,\"oe_forced_blanks\":%llu,"
"\"max_programmed_oe_ns\":%llu,\"oe_timing_error\":\"%s\","
"\"cpu_affinity_active\":%s,"
"\"realtime_priority_active\":%s,\"memory_locked\":%s,"
"\"governor\":\"%s\",\"affinity_error\":\"%s\","
"\"realtime_error\":\"%s\",\"memory_lock_error\":\"%s\","
"\"last_error\":\"%s\"}\n",
use_dev_mem ? "dev-mem" : "fake-registers",
refresh_rate,
stats.actual_refresh_rate_hz,
stats.panel_scan_rate_hz,
(unsigned long long)stats.completed_frames,
(unsigned long long)stats.completed_scans,
stats.scans_per_frame,
(unsigned long long)stats.deadline_misses,
miss_rate,
(unsigned long long)stats.buffer_swaps,
(unsigned long long)stats.submitted_frames,
stats.oe_timing_backend,
(unsigned long long)stats.oe_pulse_faults,
(unsigned long long)stats.oe_forced_blanks,
(unsigned long long)stats.max_programmed_oe_ns,
stats.oe_timing_error,
stats.cpu_affinity_active ? "true" : "false",
stats.realtime_priority_active ? "true" : "false",
stats.memory_locked ? "true" : "false",
stats.governor,
stats.affinity_error,
stats.realtime_error,
stats.memory_lock_error,
stats.last_error
);
if (
stats.completed_frames == 0 ||
stats.actual_refresh_rate_hz <= 0.0 ||
stats.panel_scan_rate_hz <= 0.0 ||
stats.scans_per_frame != expected_scans ||
stats.completed_scans != stats.completed_frames * (uint64_t)expected_scans ||
stats.oe_pulse_faults != 0 ||
strcmp(stats.oe_timing_backend, HUB75_OE_TIMING_BACKEND) != 0
) return 1;
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,138 @@
/*
* Native HUB75 refresh driver for WalnutPi ZeroW / Allwinner H618.
*
* GPIO register layout and physical-pin mapping are derived from WalnutPi's
* MIT-licensed gpioc project, commit 220a46267469cc9cb1cbc35cd72f35eb1699a6e8:
* https://github.com/walnutpi/gpioc
*
* This project keeps the upstream MIT license in THIRD_PARTY_NOTICES.md.
*/
#ifndef WALNUTPI_H618_HUB75_H
#define WALNUTPI_H618_HUB75_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define HUB75_WIDTH 64
#define HUB75_HEIGHT 64
#define HUB75_SCAN_ROWS 32
#define HUB75_PWM_BITS 7
#define HUB75_INTERNAL_PWM_BITS 11
#define HUB75_PWM_PLANE_OFFSET (HUB75_INTERNAL_PWM_BITS - HUB75_PWM_BITS)
#define HUB75_SCANS_PER_FRAME 1
#define HUB75_FRAME_BYTES (HUB75_WIDTH * HUB75_HEIGHT * 3)
#define HUB75_BITPLANE_WORDS (HUB75_SCAN_ROWS * HUB75_PWM_BITS * HUB75_WIDTH)
#define HUB75_HARDWARE_MAPPING "walnutpi-pi-bank-pwm-oe-v2"
#define HUB75_OE_TIMING_BACKEND "h618-pwm4"
#define HUB75_OE_PWM_TICK_NS 125u
typedef struct hub75_context hub75_context;
typedef struct hub75_config {
int brightness;
int refresh_rate_hz;
int cpu_affinity;
int realtime_priority;
int use_dev_mem;
int fake_pwm5_mode;
int fake_pwm4_stuck;
} hub75_config;
typedef struct hub75_stats {
double actual_refresh_rate_hz;
double panel_scan_rate_hz;
uint64_t completed_frames;
uint64_t completed_scans;
uint64_t deadline_misses;
uint64_t buffer_swaps;
uint64_t submitted_frames;
uint64_t oe_pulse_faults;
uint64_t oe_forced_blanks;
uint64_t max_programmed_oe_ns;
int cpu_affinity;
int cpu_affinity_active;
int realtime_priority;
int realtime_priority_active;
int memory_locked;
int running;
int brightness;
int refresh_rate_hz;
int scans_per_frame;
char governor[32];
char affinity_error[128];
char realtime_error[128];
char memory_lock_error[128];
char oe_timing_backend[32];
char oe_timing_error[256];
char safeoff_error[256];
char last_error[256];
} hub75_stats;
hub75_context *hub75_create(const hub75_config *config, char *error, size_t error_size);
int hub75_start(hub75_context *context);
int hub75_submit_rgb(hub75_context *context, const uint8_t *rgb, size_t length);
int hub75_set_brightness(hub75_context *context, int brightness);
int hub75_set_refresh_rate(hub75_context *context, int refresh_rate_hz);
int hub75_clear(hub75_context *context);
int hub75_get_stats(hub75_context *context, hub75_stats *stats);
void hub75_destroy(hub75_context *context);
const char *hub75_hardware_mapping(void);
int hub75_force_safeoff(int use_dev_mem, char *error, size_t error_size);
/* Pure conversion hook used by tests; output words contain PI0..PI5 colors. */
int hub75_build_bitplanes(
const uint8_t *rgb,
size_t length,
uint16_t *output,
size_t output_words
);
/* Raspberry-Pi-compatible CIE1931 brightness mapping into 11-bit color. */
int hub75_build_bitplanes_for_brightness(
const uint8_t *rgb,
size_t length,
int brightness,
uint16_t *output,
size_t output_words
);
/* Pure 8-bit RGB channel to 11-bit CIE1931 mapping hook. */
uint16_t hub75_map_channel_for_brightness(uint8_t value, int brightness);
/* Pure scan-count hook used by native tests and the benchmark. */
int hub75_scans_per_frame_for_brightness(int brightness);
/* Fixed binary OE weights matching internal bitplanes 4..10. */
uint32_t hub75_oe_ticks_for_plane(
uint64_t scan_slot_ns,
int brightness,
unsigned plane,
uint64_t *remainder
);
typedef struct hub75_fake_registers {
uint32_t mux0;
uint32_t mux1;
uint32_t data;
uint32_t pwm_pccr45;
uint32_t pwm_per;
uint32_t pwm_pcr4;
uint32_t pwm_ppr4;
uint32_t pwm_pcr5;
uint32_t pwm_ppr5;
} hub75_fake_registers;
int hub75_get_fake_registers(
hub75_context *context,
hub75_fake_registers *registers
);
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,195 @@
#define _POSIX_C_SOURCE 200809L
#include "h618_hub75.h"
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
enum {
PI_R1 = 1u << 0,
PI_G1 = 1u << 1,
PI_B1 = 1u << 2,
PI_R2 = 1u << 3,
PI_G2 = 1u << 4,
PI_B2 = 1u << 5,
};
static size_t word_index(unsigned row, unsigned plane, unsigned column) {
return ((size_t)row * HUB75_PWM_BITS + plane) * HUB75_WIDTH + column;
}
int main(void) {
assert(strcmp(hub75_hardware_mapping(), HUB75_HARDWARE_MAPPING) == 0);
assert(HUB75_INTERNAL_PWM_BITS == 11);
assert(HUB75_PWM_PLANE_OFFSET == 4);
assert(HUB75_SCANS_PER_FRAME == 1);
assert(hub75_scans_per_frame_for_brightness(1) == 1);
assert(hub75_scans_per_frame_for_brightness(20) == 1);
assert(hub75_scans_per_frame_for_brightness(100) == 1);
assert(hub75_scans_per_frame_for_brightness(0) == -1);
assert(hub75_scans_per_frame_for_brightness(101) == -1);
uint16_t previous_mapped = 0;
for (int brightness = 1; brightness <= 100; ++brightness) {
uint16_t mapped = hub75_map_channel_for_brightness(255, brightness);
assert(mapped >= previous_mapped);
previous_mapped = mapped;
for (unsigned plane = 0; plane < HUB75_PWM_BITS; ++plane) {
uint64_t remainder = 123u;
assert(hub75_oe_ticks_for_plane(
10250000, brightness, plane, &remainder
) == (1u << (plane + HUB75_PWM_PLANE_OFFSET)));
assert(remainder == 0);
}
}
assert(previous_mapped == 2047u);
assert(hub75_map_channel_for_brightness(255, 0) == UINT16_MAX);
uint64_t invalid_remainder = 0;
assert(hub75_oe_ticks_for_plane(5000000, 0, 0, &invalid_remainder) == UINT32_MAX);
assert(hub75_oe_ticks_for_plane(5000000, 1, HUB75_PWM_BITS, &invalid_remainder) == UINT32_MAX);
uint64_t full_scale_remainder = 0;
assert(hub75_oe_ticks_for_plane(
10250000, 100, HUB75_PWM_BITS - 1, &full_scale_remainder
) == 1024u);
uint8_t *frame = calloc(1, HUB75_FRAME_BYTES);
uint16_t *planes = calloc(HUB75_BITPLANE_WORDS, sizeof(*planes));
assert(frame != NULL);
assert(planes != NULL);
/* Compare extracted planes with the Raspberry-compatible 11-bit map. */
frame[0] = 0xff;
frame[1] = 0x80;
frame[2] = 0x40;
size_t bottom = (size_t)HUB75_SCAN_ROWS * HUB75_WIDTH * 3u;
frame[bottom] = 0x20;
frame[bottom + 1] = 0x10;
frame[bottom + 2] = 0x08;
assert(hub75_build_bitplanes(
frame,
HUB75_FRAME_BYTES,
planes,
HUB75_BITPLANE_WORDS
) == 0);
const uint8_t values[] = {0xff, 0x80, 0x40, 0x20, 0x10, 0x08};
const uint16_t bits[] = {PI_R1, PI_G1, PI_B1, PI_R2, PI_G2, PI_B2};
for (unsigned plane = 0; plane < HUB75_PWM_BITS; ++plane) {
uint16_t expected = 0;
for (unsigned component = 0; component < 6; ++component) {
uint16_t mapped = hub75_map_channel_for_brightness(values[component], 100);
if ((mapped >> (plane + HUB75_PWM_PLANE_OFFSET)) & 1u) {
expected |= bits[component];
}
}
assert(planes[word_index(0, plane, 0)] == expected);
}
memset(frame, 0xff, HUB75_FRAME_BYTES);
assert(hub75_build_bitplanes(
frame,
HUB75_FRAME_BYTES,
planes,
HUB75_BITPLANE_WORDS
) == 0);
for (size_t index = 0; index < HUB75_BITPLANE_WORDS; ++index) {
assert(planes[index] == (PI_R1 | PI_G1 | PI_B1 | PI_R2 | PI_G2 | PI_B2));
}
/* Brightness changes bitplane data rather than OE pulse duration. */
memset(frame, 0, HUB75_FRAME_BYTES);
frame[0] = 0xff;
frame[1] = 0x4f;
frame[2] = 0xa3;
uint16_t low_planes[HUB75_BITPLANE_WORDS];
assert(hub75_build_bitplanes_for_brightness(
frame, HUB75_FRAME_BYTES, 100, planes, HUB75_BITPLANE_WORDS
) == 0);
assert(hub75_build_bitplanes_for_brightness(
frame, HUB75_FRAME_BYTES, 20, low_planes, HUB75_BITPLANE_WORDS
) == 0);
assert(memcmp(low_planes, planes, sizeof(low_planes)) != 0);
assert(hub75_build_bitplanes(frame, 1, planes, HUB75_BITPLANE_WORDS) == -1);
assert(hub75_build_bitplanes(frame, HUB75_FRAME_BYTES, planes, 1) == -1);
hub75_config config = {
.brightness = 40,
.refresh_rate_hz = 100,
.cpu_affinity = 0,
.realtime_priority = 1,
.use_dev_mem = 0,
};
char error[512] = {0};
hub75_context *context = hub75_create(&config, error, sizeof(error));
assert(context != NULL);
hub75_fake_registers registers;
assert(hub75_get_fake_registers(context, &registers) == 0);
assert(((registers.mux1 >> ((14u - 8u) * 4u)) & 0xfu) == 0x5u);
assert(((registers.mux1 >> ((15u - 8u) * 4u)) & 0xfu) == 0x1u);
assert((registers.pwm_pccr45 & (1u << 4)) != 0);
assert((registers.pwm_pccr45 & ((0xfu) | (1u << 5) | (0x3u << 7))) == 0);
assert((registers.pwm_per & (1u << 4)) != 0);
assert((registers.pwm_pcr4 & 0x1ffu) == 2u);
assert((registers.pwm_pcr4 & (1u << 9)) != 0);
assert(registers.pwm_ppr4 == (65535u << 16));
hub75_stats stats;
assert(hub75_get_stats(context, &stats) == 0);
assert(strcmp(stats.oe_timing_backend, HUB75_OE_TIMING_BACKEND) == 0);
assert(stats.oe_pulse_faults == 0);
memset(frame, 0xff, HUB75_FRAME_BYTES);
assert(hub75_submit_rgb(context, frame, HUB75_FRAME_BYTES) == 0);
assert(hub75_set_brightness(context, 100) == 0);
assert(hub75_start(context) == 0);
struct timespec pulse_test_delay = {.tv_sec = 0, .tv_nsec = 100000000};
nanosleep(&pulse_test_delay, NULL);
assert(hub75_get_stats(context, &stats) == 0);
assert(stats.max_programmed_oe_ns == 128000u);
assert(stats.oe_pulse_faults == 0);
assert(hub75_get_fake_registers(context, &registers) == 0);
assert((registers.pwm_pcr4 & (1u << 9)) != 0);
assert((registers.pwm_pcr4 & (1u << 10)) == 0);
assert((registers.pwm_per & (1u << 4)) != 0);
assert((registers.pwm_ppr4 & 0xffffu) != 0);
assert((registers.pwm_ppr4 >> 16) + 1u ==
(registers.pwm_ppr4 & 0xffffu));
hub75_destroy(context);
config.fake_pwm4_stuck = 1;
context = hub75_create(&config, error, sizeof(error));
assert(context != NULL);
assert(hub75_start(context) == 0);
struct timespec stuck_test_delay = {.tv_sec = 0, .tv_nsec = 100000000};
nanosleep(&stuck_test_delay, NULL);
assert(hub75_get_stats(context, &stats) == 0);
assert(stats.oe_pulse_faults == 1);
assert(stats.oe_forced_blanks == 1);
assert(strstr(stats.oe_timing_error, "start bit did not clear") != NULL);
assert(hub75_get_fake_registers(context, &registers) == 0);
assert((registers.pwm_per & (1u << 4)) == 0);
assert(((registers.mux1 >> ((14u - 8u) * 4u)) & 0xfu) == 0x1u);
hub75_destroy(context);
config.fake_pwm4_stuck = 0;
config.fake_pwm5_mode = 1;
context = hub75_create(&config, error, sizeof(error));
assert(context != NULL);
assert(hub75_get_fake_registers(context, &registers) == 0);
assert((registers.pwm_per & (1u << 5)) != 0);
assert((registers.pwm_pccr45 & (1u << 6)) != 0);
assert(registers.pwm_pcr5 == 0x100u);
assert(registers.pwm_ppr5 == 0x000b0006u);
hub75_destroy(context);
config.fake_pwm5_mode = 2;
context = hub75_create(&config, error, sizeof(error));
assert(context == NULL);
assert(strstr(error, "incompatible shared PWM4/5 clock") != NULL);
assert(hub75_force_safeoff(0, error, sizeof(error)) == 0);
free(planes);
free(frame);
puts("native_bitplane_tests=PASS");
return 0;
}
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Read-only H618 PI-bank snapshot for no-load safety verification."""
from __future__ import annotations
import json
import mmap
import os
import struct
from pathlib import Path
PIO_BASE = 0x0300B000
PIO_LENGTH = 0x400
PI_OFFSET = 0x120
DATA_OFFSET = 0x10
OWNED_BITS = tuple([*range(0, 7), *range(9, 16)])
RESERVED_BITS = (7, 8, 16)
def read_u32(mapped: mmap.mmap, offset: int) -> int:
return struct.unpack_from("<I", mapped, offset)[0]
def mux_mode(mapped: mmap.mmap, pin: int) -> int:
register = PI_OFFSET + (pin // 8) * 4
return (read_u32(mapped, register) >> ((pin % 8) * 4)) & 0xF
def device_tree_contract() -> dict:
node = Path("/proc/device-tree/soc/pinctrl@300b000")
compatible = (node / "compatible").read_bytes().rstrip(b"\0").decode("ascii")
values = struct.unpack(">II", (node / "reg").read_bytes())
if compatible != "allwinner,sun50i-h616-pinctrl" or values != (PIO_BASE, PIO_LENGTH):
raise RuntimeError(f"unexpected pinctrl contract: {compatible=} {values=}")
return {"compatible": compatible, "base": f"0x{values[0]:08X}", "length": values[1]}
def main() -> None:
contract = device_tree_contract()
descriptor = os.open("/dev/mem", os.O_RDONLY | os.O_SYNC)
try:
with mmap.mmap(descriptor, PIO_LENGTH, access=mmap.ACCESS_READ, offset=PIO_BASE) as mapped:
data = read_u32(mapped, PI_OFFSET + DATA_OFFSET)
modes = {f"PI{pin}": mux_mode(mapped, pin) for pin in range(17)}
finally:
os.close(descriptor)
owned_mask = sum(1 << bit for bit in OWNED_BITS)
reserved_mask = sum(1 << bit for bit in RESERVED_BITS)
print(json.dumps({
"device_tree": contract,
"data_register": f"0x{data:08X}",
"owned_data_bits": f"0x{data & owned_mask:08X}",
"reserved_data_bits": f"0x{data & reserved_mask:08X}",
"modes": modes,
}, sort_keys=True))
if __name__ == "__main__":
main()
@@ -0,0 +1,22 @@
#include "h618_hub75.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
int use_dev_mem = 1;
if (argc == 2 && strcmp(argv[1], "--fake") == 0) {
use_dev_mem = 0;
} else if (argc != 1) {
fprintf(stderr, "usage: %s [--fake]\n", argv[0]);
return 2;
}
char error[512] = {0};
if (hub75_force_safeoff(use_dev_mem, error, sizeof(error)) != 0) {
fprintf(stderr, "HUB75 safeoff failed: %s\n", error[0] ? error : "unknown error");
return 1;
}
puts("HUB75 safeoff complete");
return 0;
}