diff --git a/src/app_config.c b/src/app_config.c index 512c588d..71fc5edd 100644 --- a/src/app_config.c +++ b/src/app_config.c @@ -98,11 +98,16 @@ int app_config_save(void) { fprintf(file, " enable: %s\n", app_config.night_mode_enable ? "true" : "false"); fprintf(file, " ir_sensor_pin: %d\n", app_config.ir_sensor_pin); fprintf(file, " check_interval_s: %d\n", app_config.check_interval_s); + if (app_config.smartir_gain_night || app_config.smartir_gain_day) { + fprintf(file, " smartir_gain_night: %d\n", app_config.smartir_gain_night); + fprintf(file, " smartir_gain_day: %d\n", app_config.smartir_gain_day); + } fprintf(file, " ir_cut_pin1: %d\n", app_config.ir_cut_pin1); fprintf(file, " ir_cut_pin2: %d\n", app_config.ir_cut_pin2); fprintf(file, " ir_led_pin: %d\n", app_config.ir_led_pin); fprintf(file, " pin_switch_delay_us: %d\n", app_config.pin_switch_delay_us); fprintf(file, " adc_device: %s\n", app_config.adc_device); + fprintf(file, " lamp: %s\n", app_config.night_lamp); fprintf(file, " adc_threshold: %d\n", app_config.adc_threshold); fprintf(file, "isp:\n"); @@ -277,6 +282,7 @@ enum ConfigError app_config_parse(void) { app_config.pin_switch_delay_us = 250; app_config.check_interval_s = 10; app_config.adc_device[0] = 0; + strcpy(app_config.night_lamp, "ir"); app_config.adc_threshold = 128; struct IniConfig ini; @@ -344,11 +350,17 @@ enum ConfigError app_config_parse(void) { #define PIN_MAX 95 if (app_config.night_mode_enable) { parse_int( - &ini, "night_mode", "ir_sensor_pin", 0, PIN_MAX, + &ini, "night_mode", "ir_sensor_pin", 0, 999, /* 999 = no sensor (the default) */ &app_config.ir_sensor_pin); parse_int( &ini, "night_mode", "check_interval_s", 0, 600, &app_config.check_interval_s); + parse_int( + &ini, "night_mode", "smartir_gain_night", 0, 65535, + &app_config.smartir_gain_night); + parse_int( + &ini, "night_mode", "smartir_gain_day", 0, 65535, + &app_config.smartir_gain_day); parse_int( &ini, "night_mode", "ir_cut_pin1", 0, PIN_MAX, &app_config.ir_cut_pin1); @@ -363,6 +375,17 @@ enum ConfigError app_config_parse(void) { &app_config.pin_switch_delay_us); parse_param_value( &ini, "night_mode", "adc_device", app_config.adc_device); + { + /* parse_param_value() sprintf()s into the caller's buffer with no + * size, so read into a temporary the size of the largest config + * string in this file and accept only the lamps the HAL drives. */ + char lamp[128] = {0}; + if (parse_param_value(&ini, "night_mode", "lamp", lamp) == CONFIG_OK && + (EQUALS(lamp, "ir") || EQUALS(lamp, "white") || EQUALS(lamp, "none"))) { + strncpy(app_config.night_lamp, lamp, sizeof(app_config.night_lamp) - 1); + app_config.night_lamp[sizeof(app_config.night_lamp) - 1] = 0; + } + } parse_int( &ini, "night_mode", "adc_threshold", INT_MIN, INT_MAX, &app_config.adc_threshold); diff --git a/src/app_config.h b/src/app_config.h index 1bf42c5f..ce68812c 100644 --- a/src/app_config.h +++ b/src/app_config.h @@ -35,7 +35,10 @@ struct AppConfig { unsigned int check_interval_s; unsigned int pin_switch_delay_us; char adc_device[128]; + char night_lamp[8]; /* ir (default), white (colour night vision), none */ int adc_threshold; + int smartir_gain_night; /* Fullhan SmartIR: image gain above which it is night (0 = library default) */ + int smartir_gain_day; /* ... and below which it is day again */ // [isp] bool mirror; diff --git a/src/gpio.c b/src/gpio.c index cb19919d..a2219bab 100644 --- a/src/gpio.c +++ b/src/gpio.c @@ -72,11 +72,26 @@ int gpio_init(void) { return EXIT_SUCCESS; } +/* The exported line's sysfs directory is "gpio" on most kernels but + * "GPIO" on Fullhan (fh) kernels; probe both and cache the format. */ +static const char *gpio_node(char pin) { + static char fmt[24]; + static char dir[40]; + if (!fmt[0]) { + char probe[40]; + sprintf(probe, "/sys/class/gpio/GPIO%d", pin); + strcpy(fmt, access(probe, F_OK) == 0 ? + "/sys/class/gpio/GPIO%d" : "/sys/class/gpio/gpio%d"); + } + sprintf(dir, fmt, pin); + return dir; +} + static inline int gpio_direction(char pin, char *mode) { - char path[40]; - sprintf(path, "/sys/class/gpio/gpio%d/direction", pin); + char path[48]; + sprintf(path, "%s/direction", gpio_node(pin)); int fd = open(path, O_WRONLY); - if (!fd) + if (fd < 0) HAL_ERROR("gpio", "Unable to control the direction of GPIO pin %d!\n", pin); if (write(fd, mode, strlen(mode)) < 0) { close(fd); @@ -91,7 +106,7 @@ static inline int gpio_export(char pin, bool create) { char path[40]; int fd = open(create ? "/sys/class/gpio/export" : "/sys/class/gpio/unexport", O_WRONLY); - if (!fd) + if (fd < 0) HAL_ERROR("gpio", "Unable to (un)export a GPIO pin!\n"); char val[4]; @@ -110,10 +125,10 @@ int gpio_read(char pin, bool *value) { gpio_export(pin, true); if (gpio_direction(pin, "in")) return EXIT_FAILURE; - char path[40]; - sprintf(path, "/sys/class/gpio/gpio%d/value", pin); + char path[48]; + sprintf(path, "%s/value", gpio_node(pin)); int fd = open(path, O_RDONLY); - if (!fd) + if (fd < 0) HAL_ERROR("gpio", "Unable to read from GPIO pin %d!\n", pin); char val = 0; @@ -136,10 +151,10 @@ int gpio_write(char pin, bool value) { gpio_export(pin, true); if (gpio_direction(pin, "out")) return EXIT_FAILURE; - char path[40]; - sprintf(path, "/sys/class/gpio/gpio%d/value", pin); + char path[48]; + sprintf(path, "%s/value", gpio_node(pin)); int fd = open(path, O_WRONLY); - if (!fd) + if (fd < 0) HAL_ERROR("gpio", "Unable to write to GPIO pin %d!\n", pin); char val = value ? '1' : '0'; diff --git a/src/hal/fh/fh_aud.h b/src/hal/fh/fh_aud.h new file mode 100644 index 00000000..5aa0e2b8 --- /dev/null +++ b/src/hal/fh/fh_aud.h @@ -0,0 +1,77 @@ +#pragma once + +#include "fh_common.h" + +/* FH_AC_Set_Config() payload (audio codec on the ARC coprocessor, /dev/fh_audio) */ +typedef struct { + unsigned int ioType; /* 0/1 = capture, 2/3 = playback */ + unsigned int sampleRate; + unsigned int bitWidth; /* 16 or 24 */ + unsigned int encFormat; /* 0 = PCM, 1..5 = other formats (forces 16 bit) */ + unsigned int channels; + unsigned int frameSamples; /* >= 80; vendor uses 320 at 8 kHz */ + unsigned int volume; /* 0..100 */ +} fh_aud_cnf; + +typedef struct { + unsigned int length; /* filled with the number of bytes read */ + void *data; +} fh_aud_frm; + +typedef struct { + void *handle; + + int (*fnDeinit)(void); + int (*fnDisable)(void); + int (*fnEnable)(void); + int (*fnGetFrame)(fh_aud_frm *frame, unsigned long long *timestamp); + int (*fnInit)(void); + int (*fnSetConfig)(fh_aud_cnf *config); + int (*fnSetMicVolume)(unsigned int volume); + int (*fnSetVolume)(unsigned int volume); +} fh_aud_impl; + +static int fh_aud_load(fh_aud_impl *aud_lib) { + if (!(aud_lib->handle = dlopen("libacw_mpi.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_aud", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(aud_lib->fnDeinit = (int(*)(void)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_DeInit"))) + return EXIT_FAILURE; + + if (!(aud_lib->fnDisable = (int(*)(void)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_AI_Disable"))) + return EXIT_FAILURE; + + if (!(aud_lib->fnEnable = (int(*)(void)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_AI_Enable"))) + return EXIT_FAILURE; + + if (!(aud_lib->fnGetFrame = (int(*)(fh_aud_frm *frame, unsigned long long *timestamp)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_AI_GetFrameWithPts"))) + return EXIT_FAILURE; + + if (!(aud_lib->fnInit = (int(*)(void)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_Init"))) + return EXIT_FAILURE; + + if (!(aud_lib->fnSetConfig = (int(*)(fh_aud_cnf *config)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_Set_Config"))) + return EXIT_FAILURE; + + if (!(aud_lib->fnSetMicVolume = (int(*)(unsigned int volume)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_AI_MICIN_SetVol"))) + return EXIT_FAILURE; + + if (!(aud_lib->fnSetVolume = (int(*)(unsigned int volume)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_AI_SetVol"))) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} + +static void fh_aud_unload(fh_aud_impl *aud_lib) { + if (aud_lib->handle) dlclose(aud_lib->handle); + aud_lib->handle = NULL; + memset(aud_lib, 0, sizeof(*aud_lib)); +} diff --git a/src/hal/fh/fh_common.h b/src/hal/fh/fh_common.h new file mode 100644 index 00000000..2c8530d2 --- /dev/null +++ b/src/hal/fh/fh_common.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +#include "../symbols.h" +#include "../types.h" + +/* + * Fullhan FH8852/FH8856 (V100 generation, SDK V1.2.0 "OSDRV" libraries). + * + * The SDK ships as binary-only shared objects without headers; the interface + * below was recovered from the libraries and from a vendor application that + * statically links the same SDK. Only the subset divinus needs is declared. + */ + +#define FH_VPSS_CHN_NUM 4 +#define FH_VENC_CHN_NUM 8 + +/* Error codes returned by the MPI (negative) */ +#define FH_ERR_NODEV (-0x3e9) +#define FH_ERR_PARAM (-0x3ed) +#define FH_ERR_CHN (-0x3f0) + +typedef enum { + FH_VENC_TYPE_JPEG = 0x01, + FH_VENC_TYPE_MJPEG = 0x02, + FH_VENC_TYPE_H264 = 0x04, + FH_VENC_TYPE_H264B = 0x08, /* variant that also needs a BGM gop threshold */ + FH_VENC_TYPE_H265 = 0x10, + FH_VENC_TYPE_H265B = 0x20 +} fh_venc_type; + +typedef enum { + FH_VENC_RC_H264_CBR = 3, /* reported as VBR by /proc/driver/enc but it is the working CBR path; mode 8 boot-loops */ + FH_VENC_RC_H264_VBR = 4, + FH_VENC_RC_H264_FIXQP = 5, + FH_VENC_RC_H264_AVBR = 6, + FH_VENC_RC_H265_CBR = 7, + FH_VENC_RC_H265_VBR = 8 +} fh_venc_rcmode; + +typedef struct { + unsigned int width; + unsigned int height; +} fh_common_dim; diff --git a/src/hal/fh/fh_compat.c b/src/hal/fh/fh_compat.c new file mode 100644 index 00000000..d3918e9d --- /dev/null +++ b/src/hal/fh/fh_compat.c @@ -0,0 +1,86 @@ +#if defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + +/* + * The Fullhan 3.0.8 kernels oops in rtnl_fill_ifinfo() on any netlink + * interface dump, which kills the calling process. musl implements + * getifaddrs() over netlink, so replace it with the SIOCGIFCONF ioctl + * interface for this platform. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct fh_ifaddr { + struct ifaddrs ifa; + char name[IFNAMSIZ]; + struct sockaddr_in addr, netmask, broadcast; +}; + +int getifaddrs(struct ifaddrs **ifap) +{ + struct ifconf ifc; + struct ifreq *reqs; + int fd, count, len = 32 * sizeof(struct ifreq); + struct fh_ifaddr *list = NULL, *last = NULL; + + *ifap = NULL; + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + return -1; + if (!(reqs = malloc(len))) { + close(fd); + return -1; + } + ifc.ifc_len = len; + ifc.ifc_req = reqs; + if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) { + free(reqs); + close(fd); + return -1; + } + count = ifc.ifc_len / sizeof(struct ifreq); + + for (int i = 0; i < count; i++) { + struct ifreq req = reqs[i]; + struct fh_ifaddr *entry = calloc(1, sizeof(*entry)); + if (!entry) break; + strncpy(entry->name, req.ifr_name, IFNAMSIZ - 1); + entry->ifa.ifa_name = entry->name; + memcpy(&entry->addr, &req.ifr_addr, sizeof(entry->addr)); + entry->ifa.ifa_addr = (struct sockaddr*)&entry->addr; + if (!ioctl(fd, SIOCGIFFLAGS, &req)) + entry->ifa.ifa_flags = req.ifr_flags; + if (!ioctl(fd, SIOCGIFNETMASK, &req)) { + memcpy(&entry->netmask, &req.ifr_netmask, sizeof(entry->netmask)); + entry->ifa.ifa_netmask = (struct sockaddr*)&entry->netmask; + } + if (!ioctl(fd, SIOCGIFBRDADDR, &req)) { + memcpy(&entry->broadcast, &req.ifr_broadaddr, sizeof(entry->broadcast)); + entry->ifa.ifa_broadaddr = (struct sockaddr*)&entry->broadcast; + } + if (last) last->ifa.ifa_next = &entry->ifa; + else list = entry; + last = entry; + } + + free(reqs); + close(fd); + *ifap = list ? &list->ifa : NULL; + return 0; +} + +void freeifaddrs(struct ifaddrs *ifa) +{ + while (ifa) { + struct ifaddrs *next = ifa->ifa_next; + free(ifa); + ifa = next; + } +} + +#endif diff --git a/src/hal/fh/fh_hal.c b/src/hal/fh/fh_hal.c new file mode 100644 index 00000000..17f76aa4 --- /dev/null +++ b/src/hal/fh/fh_hal.c @@ -0,0 +1,1248 @@ +#if defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + +#include "fh_hal.h" +#include "../../gpio.h" +#include + +#include +#include +#include + +fh_aud_impl fh_aud; +fh_isp_impl fh_isp; +fh_sys_impl fh_sys; +fh_venc_impl fh_venc; +fh_vpss_impl fh_vpss; + +hal_chnstate fh_state[FH_VENC_CHN_NUM] = {0}; +int (*fh_aud_cb)(hal_audframe*); +int (*fh_vid_cb)(char, hal_vidstream*); + +static fh_snr_driver *_fh_snr; +static fh_isp_snrops *_fh_snr_ops; +static fh_common_dim _fh_snr_dim; +static int _fh_snr_fmt; +static char _fh_isp_on, _fh_isp_busy; +static unsigned int _fh_venc_type[FH_VENC_CHN_NUM]; +static fh_common_dim _fh_venc_made[FH_VENC_CHN_NUM]; /* size FH_VENC_CreateChn reserved */ +static fh_common_dim _fh_vpss_made[FH_VPSS_CHN_NUM]; /* size FH_VPSS_ChnInitMem reserved */ +static char _fh_vpss_open[FH_VPSS_CHN_NUM]; +static fh_common_dim _fh_vpss_dim[FH_VPSS_CHN_NUM]; +static char _fh_vpss_fps[FH_VPSS_CHN_NUM]; +static char _fh_param_path[128]; +static int _fh_vpss_count = FH_VPSS_CHN_NUM; +static fh_common_dim _fh_want_dim[FH_VENC_CHN_NUM]; +static signed char _fh_vpss_src[FH_VENC_CHN_NUM]; /* scaler channel feeding each encoder */ +static char fh_vpss_pick(char index); + +/* Graphic overlay: one 16-bit ARGB1555 plane at sensor resolution, blended before scaling */ +typedef struct { + unsigned int enable; + unsigned int physAddr; + unsigned int alpha; + unsigned int reserved3, reserved4; + unsigned int width, height; + unsigned int x, y; + unsigned int reserved9, reserved10; + unsigned int stride; /* bytes per line */ +} fh_vpss_graph; +#define FH_OSD_MAX 8 +typedef struct { char used; hal_rect rect; unsigned char opal; } fh_osd_rgn; +static fh_osd_rgn _fh_osd[FH_OSD_MAX]; +static unsigned int _fh_osd_phys, _fh_osd_size; +static unsigned short *_fh_osd_virt; +static char _fh_osd_on; +static unsigned char _fh_osd_alpha = 255; +static pthread_mutex_t _fh_strm_mtx = PTHREAD_MUTEX_INITIALIZER; +static char _fh_aud_on; +static unsigned int _fh_aud_frame, _fh_aud_rate; +static char _fh_smartir, _fh_night_prev; +static unsigned char *_fh_mjpeg_cache; static unsigned int _fh_mjpeg_len, _fh_mjpeg_cap; static pthread_mutex_t _fh_mjpeg_mtx = PTHREAD_MUTEX_INITIALIZER; + +/* libc compatibility shims for the SDK libraries live in fh_compat.c */ + +static void fh_proc_write(const char *path, const char *value) +{ + FILE *file = fopen(path, "w"); + if (!file) { + HAL_WARNING("fh_hal", "Cannot write %s to %s!\n", value, path); + return; + } + fputs(value, file); + fclose(file); +} + +void fh_hal_deinit(void) +{ + fh_aud_unload(&fh_aud); + fh_venc_unload(&fh_venc); + fh_vpss_unload(&fh_vpss); + fh_isp_unload(&fh_isp); + fh_sys_unload(&fh_sys); +} + +int fh_hal_init(void) +{ + int ret; + + if (ret = fh_sys_load(&fh_sys)) + return ret; + if (ret = fh_isp_load(&fh_isp)) + return ret; + if (ret = fh_vpss_load(&fh_vpss)) + return ret; + if (ret = fh_venc_load(&fh_venc)) + return ret; + if (fh_aud_load(&fh_aud)) + HAL_WARNING("fh_hal", "Audio library unavailable, audio disabled\n"); + + return EXIT_SUCCESS; +} + +void fh_audio_deinit(void) +{ + if (!_fh_aud_on) return; + _fh_aud_on = 0; + fh_aud.fnDisable(); + fh_aud.fnDeinit(); +} + +int fh_audio_init(int samplerate) +{ + int ret; + + if (!fh_aud.handle) + HAL_ERROR("fh_aud", "Audio library is not loaded!\n"); + + if (ret = fh_aud.fnInit()) { + HAL_DANGER("fh_aud", "FH_AC_Init failed with %#x\n", ret); + return ret; + } + + { + /* A sample rate the config parser rejected arrives here as 0 (a saved + * "srate: 0" is outside the 8000..96000 range); fall back to 8 kHz rather + * than asking the codec for zero-length frames */ + if (!samplerate) samplerate = 8000; + /* 40 ms frames of 16-bit mono PCM, as the vendor application does */ + _fh_aud_frame = samplerate / 25; + _fh_aud_rate = samplerate; + fh_aud_cnf config = { .ioType = 0, .sampleRate = samplerate, .bitWidth = 16, + .encFormat = 0, .channels = 1, .frameSamples = _fh_aud_frame, .volume = 85 }; + if (ret = fh_aud.fnSetConfig(&config)) { + HAL_DANGER("fh_aud", "FH_AC_Set_Config(%d Hz, %u samples) failed with %#x\n", samplerate, _fh_aud_frame, ret); + return ret; + } + } + if (ret = fh_aud.fnEnable()) { + HAL_DANGER("fh_aud", "FH_AC_AI_Enable failed with %#x\n", ret); + return ret; + } + fh_aud.fnSetMicVolume(2); + fh_aud.fnSetVolume(85); + + _fh_aud_on = 1; + return EXIT_SUCCESS; +} + +void *fh_audio_thread(void) +{ + unsigned char *buf = malloc(_fh_aud_frame * 2 + 64); + unsigned int seq = 0; + + while (keepRunning && audioOn && _fh_aud_on) { + fh_aud_frm frame = { .length = 0, .data = buf }; + unsigned long long pts = 0; + int ret = fh_aud.fnGetFrame(&frame, &pts); + if (ret || !frame.length) { + usleep(5000); + continue; + } + { + /* The board picks up mains hum (50 Hz and harmonics) on the mic + * path; a 2nd-order Butterworth high-pass at ~120 Hz removes most + * of it and leaves speech intact. Coefficients follow the sample rate. */ + static float x1, x2, y1, y2, b0, b1, b2, a1, a2; static unsigned int forRate; + if (forRate != _fh_aud_rate) { + float w0 = 2.0f * 3.14159265f * 120.0f / (float)_fh_aud_rate, c = cosf(w0); + float alpha = sinf(w0) / (2.0f * 0.7071f), a0 = 1.0f + alpha; + b0 = (1.0f + c) / 2.0f / a0; b1 = -(1.0f + c) / a0; b2 = b0; + a1 = -2.0f * c / a0; a2 = (1.0f - alpha) / a0; + x1 = x2 = y1 = y2 = 0; forRate = _fh_aud_rate; + } + short *pcm = (short*)buf; + for (unsigned int i = 0; i < frame.length / 2; i++) { + float x0 = pcm[i]; + float y0 = b0 * x0 + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; + x2 = x1; x1 = x0; y2 = y1; y1 = y0; + pcm[i] = y0 > 32767 ? 32767 : y0 < -32768 ? -32768 : (short)y0; + } + } + if (fh_aud_cb) { + hal_audframe outFrame; + memset(&outFrame, 0, sizeof(outFrame)); + outFrame.channelCnt = 1; + outFrame.data[0] = buf; + outFrame.length[0] = frame.length; + outFrame.seq = seq++; + outFrame.timestamp = pts / 1000; + (fh_aud_cb)(&outFrame); + } + } + free(buf); + HAL_INFO("fh_aud", "Shutting down capture thread...\n"); + return NULL; +} + +int fh_channel_bind(char index) +{ + int ret; + char src = _fh_vpss_src[index] < 0 ? fh_vpss_pick(index) : _fh_vpss_src[index]; + + for (int i = 0; i < 5; i++) { + if (!(ret = fh_sys.fnBindVpu2Enc(src, index))) + return EXIT_SUCCESS; + usleep(100000); + } + HAL_DANGER("fh_hal", "Binding scaler channel %d to encoder %d failed with %#x!\n", src, index, ret); + return ret; +} + +static int fh_vpss_setup(char index, short width, short height, char framerate) +{ + int ret; + int snrFps = _fh_snr->fnFramerateForFormat(_fh_snr_fmt); + if (framerate > snrFps) framerate = snrFps; + + if (_fh_vpss_open[index] && _fh_vpss_dim[index].width == width && + _fh_vpss_dim[index].height == height && _fh_vpss_fps[index] == framerate) + return EXIT_SUCCESS; + + if (_fh_vpss_open[index]) { + fh_vpss.fnCloseChannel(index); + _fh_vpss_open[index] = 0; + } + + { + fh_common_dim dim = { .width = width, .height = height }; + /* Like the encoder pools, a scaler channel's memory cannot be released + * again (a second ChnInitMem fails with -1), so allocate once and keep + * reconfiguring within that size */ + if (_fh_vpss_made[index].width) { + if (dim.width > _fh_vpss_made[index].width || dim.height > _fh_vpss_made[index].height) + HAL_ERROR("fh_hal", "Scaler channel %d was sized %ux%u; %ux%u needs a restart\n", index, + _fh_vpss_made[index].width, _fh_vpss_made[index].height, dim.width, dim.height); + } else { + int (*fnInitChannelMem)(unsigned int, unsigned int, unsigned int) = + dlsym(fh_vpss.handle, "FH_VPSS_ChnInitMem"); + if (fnInitChannelMem && (ret = fnInitChannelMem(index, dim.width, dim.height))) + HAL_WARNING("fh_hal", "Allocating scaler channel %d memory failed with %#x!\n", index, ret); + else _fh_vpss_made[index] = dim; + } + if (ret = fh_vpss.fnSetChannelConfig(index, &dim)) + return ret; + _fh_vpss_dim[index] = dim; + } + + { + fh_vpss_framectrl ctrl = { .srcRate = framerate, .dstRate = 1 }; + if (ret = fh_vpss.fnSetFrameControl(index, &ctrl)) + return ret; + _fh_vpss_fps[index] = framerate; + } + + if (ret = fh_vpss.fnOpenChannel(index)) + return ret; + _fh_vpss_open[index] = 1; + + return EXIT_SUCCESS; +} + +static char fh_vpss_pick(char index) +{ + /* Prefer an open scaler channel with the same size, else the main one */ + for (char i = 0; i < _fh_vpss_count; i++) + if (_fh_vpss_open[i] && _fh_vpss_dim[i].width == _fh_want_dim[index].width && + _fh_vpss_dim[i].height == _fh_want_dim[index].height) + return i; + return 0; +} + +int fh_channel_create(char index, short width, short height, char framerate, char jpeg) +{ + if (index >= FH_VENC_CHN_NUM) + HAL_ERROR("fh_hal", "Only %d encoder channels are available!\n", FH_VENC_CHN_NUM); + + _fh_want_dim[index].width = width; + _fh_want_dim[index].height = height; + if (index >= _fh_vpss_count) { + _fh_vpss_src[index] = -1; + HAL_INFO("fh_hal", "Channel %d: no free scaler channel, it will share one at bind time\n", index); + return EXIT_SUCCESS; + } + _fh_vpss_src[index] = index; + + if (width > _fh_snr_dim.width || height > _fh_snr_dim.height) { + HAL_WARNING("fh_hal", "Channel %d: requested %dx%d too large, using sensor %dx%d\n", + index, width, height, _fh_snr_dim.width, _fh_snr_dim.height); + width = _fh_snr_dim.width; + height = _fh_snr_dim.height; + } + + return fh_vpss_setup(index, width, height, framerate); +} + +void fh_channel_destroy(char index) +{ + /* Keep the scaler channel open: closing and reopening it around a codec or + * bitrate change left it delivering no frames on the FH8856 (encoder frame + * count stayed at 0, main-channel snapshots timed out), and fh_vpss_setup() + * reuses an open channel of the same size and rate anyway. */ +} + +/* + * Colour vs monochrome is the ISP saturation record (20 bytes: enable word, + * then the curve). The tuning file sets a valid colour record; a zeroed record + * is monochrome (what the vendor's mono path writes). libadvapi's + * FHAdv_Isp_SetColorMode() cannot be used here: its "restore colour" writes + * the library's own copy, which is never filled on this build, so it zeroed + * the record instead. Keep the record from the tuning load and write it back. + */ +static unsigned char _fh_sat_colour[32]; +static char _fh_sat_ok, _fh_gray_want; + +static void fh_grayscale_apply(void) +{ + unsigned char zero[32] = {0}; + if (_fh_gray_want) + fh_isp.fnSetSaturation(zero); + else if (_fh_sat_ok) + fh_isp.fnSetSaturation(_fh_sat_colour); + /* else: nothing captured yet, the ISP is still in its tuned colour state */ +} + +int fh_channel_grayscale(char enable) +{ + _fh_gray_want = enable ? 1 : 0; + fh_grayscale_apply(); + return EXIT_SUCCESS; +} + +int fh_channel_unbind(char index) +{ + fh_sys.fnUnbindByDst(index); + + return EXIT_SUCCESS; +} + +/* + * ISP tuning ("_attr.hex", 3268 bytes): the vendor files are + * generated for a newer SDK build; the older library only warns about the + * version word and still loads them. + */ +int fh_config_load(char *path) +{ + static unsigned char param[4096]; + FILE *file; + size_t len; + + if (EQUALS(path, _fh_param_path)) + return EXIT_SUCCESS; + if (!(file = fopen(path, "rb"))) + return EXIT_FAILURE; + memset(param, 0, sizeof(param)); + len = fread(param, 1, sizeof(param), file); + fclose(file); + if (len < 0x2c) + return EXIT_FAILURE; + + HAL_INFO("fh_hal", "Loading ISP parameters from %s (%zu bytes)\n", path, len); + if (fh_isp.fnLoadParam(param)) + return EXIT_FAILURE; + { + unsigned char sat[32] = {0}; + if (!fh_isp.fnGetSaturation(sat) && sat[5]) { + memcpy(_fh_sat_colour, sat, sizeof(_fh_sat_colour)); + _fh_sat_ok = 1; + fh_grayscale_apply(); /* honour a mode requested before the tuning load */ + } else + HAL_WARNING("fh_hal", "Could not read the colour saturation record; grayscale off will be a no-op\n"); + } + strncpy(_fh_param_path, path, sizeof(_fh_param_path) - 1); + { + int ret = fh_isp.fnAdvInit(); + return ret; + } +} + +void *fh_image_thread(void) +{ + while (keepRunning) { + if (_fh_isp_on) { + _fh_isp_busy = 1; + fh_isp.fnRun(); + _fh_isp_busy = 0; + usleep(10000); + } else usleep(100000); + } + HAL_INFO("fh_hal", "Shutting down ISP thread...\n"); + return NULL; +} + +/* Anti-banding: the ISP AE stores a 2-bit flicker mode at param offset 0x3a + * bits 6-7 (0 off, 1 = 50 Hz, 2 = 60 Hz), set through AE command 0xa. The + * newer vendor SDK adds dedicated commands 0x1b-0x1d, absent from these libs. */ +int fh_set_antiflicker(char hz) +{ + unsigned int mode = hz >= 60 ? 2 : hz >= 50 ? 1 : 0; + if (!fh_isp.fnAeSendCmd) + return EXIT_FAILURE; + return fh_isp.fnAeSendCmd(0x0a, &mode); +} + +int fh_pipeline_create(short width, short height, char mirror, char flip, char framerate, char antiflicker) +{ + int ret; + char path[128]; + + /* Kernel-side buffer sizing must precede FH_SYS_Init() */ + { + char value[64]; + snprintf(value, sizeof(value), "vi_%u_%u", _fh_snr_dim.width, _fh_snr_dim.height); + fh_proc_write("/proc/driver/vpu", value); + /* Scaler channel memory is allocated on demand in fh_channel_create() */ + for (int i = 0; i < FH_VPSS_CHN_NUM; i++) { + snprintf(value, sizeof(value), "cap_%d_0_0", i); + fh_proc_write("/proc/driver/vpu", value); + snprintf(value, sizeof(value), "buf_%d_2", i); + fh_proc_write("/proc/driver/vpu", value); + } + fh_proc_write("/proc/driver/vpu", "support4k_off"); + fh_proc_write("/proc/driver/isp", "support4k_off"); + fh_proc_write("/proc/driver/vpu", "sublimit_off"); + fh_proc_write("/proc/driver/isp", "sublimit_off"); + fh_proc_write("/proc/driver/isp", "wdr_off"); + fh_proc_write("/proc/driver/isp", "cir_off"); + fh_proc_write("/proc/driver/enc", "stm_2621440"); + fh_proc_write("/proc/driver/hevc", "stm_2621440"); + fh_proc_write("/proc/driver/hevc", "usebfrm_0"); + /* JPEG snapshot buffer: the driver's own rule is width*height/2, forcing 128 KiB + * (the vendor value) overruns at 2560x1440 and crashes the JPEG thread */ + snprintf(value, sizeof(value), "mem_1_%u", ALIGN_UP((unsigned int)width * height / 2 + 8, 4096)); + fh_proc_write("/proc/driver/jpeg", value); + /* Motion JPEG pool: three 512 KiB slots (larger pools fail to ioremap on 32 MiB systems) */ + { + unsigned int jpgSize = 512 * 1024; + snprintf(value, sizeof(value), "mjpg_%u_%u", jpgSize * 3, jpgSize); + fh_proc_write("/proc/driver/jpeg", value); + } + } + + if (ret = fh_sys.fnInit()) + return ret; + + { + int (*fnGetCapability)(unsigned int, void *) = dlsym(fh_vpss.handle, "FH_VPSS_GetChnCapality"); + unsigned int cap[8]; + _fh_vpss_count = 0; + while (fnGetCapability && _fh_vpss_count < FH_VPSS_CHN_NUM && !fnGetCapability(_fh_vpss_count, cap)) + _fh_vpss_count++; + if (!_fh_vpss_count) _fh_vpss_count = 1; + HAL_INFO("fh_hal", "%d scaler channels available\n", _fh_vpss_count); + } + + if (ret = fh_vpss.fnSetInputConfig(&_fh_snr_dim)) + return ret; + if (ret = fh_vpss.fnEnable(0)) + return ret; + + _fh_snr_ops = _fh_snr->fnCreate(&fh_isp); + _fh_snr_fmt = _fh_snr->fnFormatForFramerate(framerate); + + /* The ISP only starts delivering frames when a scaler channel is already open */ + if (width > _fh_snr_dim.width) width = _fh_snr_dim.width; + if (height > _fh_snr_dim.height) height = _fh_snr_dim.height; + if (ret = fh_vpss_setup(0, width, height, framerate)) + return ret; + + if (ret = fh_isp.fnMemInit(_fh_snr_dim.width, _fh_snr_dim.height)) + return ret; + if (ret = fh_isp.fnRegisterSensor(0, _fh_snr_ops)) + return ret; + if (ret = fh_isp.fnSensorInit()) + return ret; + if (ret = fh_isp.fnSetSensorFormat(_fh_snr_fmt)) + return ret; + if (ret = fh_isp.fnInit()) + return ret; + + snprintf(path, sizeof(path), "/etc/sensors/%s_attr.hex", _fh_snr->name); + if (fh_config_load(path)) { + snprintf(path, sizeof(path), "/usr/lib/sensors/params/%s_attr.hex", _fh_snr->name); + if (fh_config_load(path)) + HAL_WARNING("fh_hal", "No ISP tuning file found for %s, image quality will suffer!\n", + _fh_snr->name); + } + + if (mirror || flip) + fh_isp.fnSetFlipMirrorEx(mirror ? 1 : 0, flip ? 1 : 0, _fh_snr->bayer); + + fh_set_antiflicker(antiflicker); + + /* Image-gain based day/night detection (no external light sensor needed) */ + if (fh_isp.fnSmartIrInit && !fh_isp.fnSmartIrInit()) { + if (fh_isp.fnSmartIrSetAttr) + fh_isp.fnSmartIrSetAttr(0); + _fh_smartir = fh_isp.fnSmartIrStatus != NULL; + HAL_INFO("fh_hal", "SmartIR day/night detection %s\n", _fh_smartir ? "enabled" : "unavailable"); + if (_fh_smartir && fh_isp.fnSmartIrGetThreshold && fh_isp.fnSmartIrSetThreshold) { + /* The status function re-reads the gain thresholds from the ISP + * parameter block (loaded from the sensor tuning file) on every + * call, which silently replaces the library defaults; the GC4653 + * file's values flipped the PB1 to night in daylight at 1.1x gain. + * The vendor application always writes them back; do the same. */ + unsigned short th[4] = {0}; + fh_isp.fnSmartIrGetThreshold(th); + fh_isp.fnSmartIrSetThreshold(th); + HAL_INFO("fh_hal", "SmartIR thresholds: %u %u %u %u\n", th[0], th[1], th[2], th[3]); + } + } + fh_irled(0); + fh_whitelamp(0); + + _fh_isp_on = 1; + + return EXIT_SUCCESS; +} + +void fh_pipeline_destroy(void) +{ + _fh_isp_on = 0; + for (int i = 0; i < 50 && _fh_isp_busy; i++) + usleep(10000); + + fh_isp.fnExit(); + fh_isp.fnUnregisterSensor(0); + if (_fh_snr_ops && _fh_snr_ops->fnDeinit) + _fh_snr_ops->fnDeinit(); + + for (int i = 0; i < FH_VPSS_CHN_NUM; i++) + fh_channel_destroy(i); + fh_vpss.fnDisable(0); +} + +static void fh_venc_fill_rc(fh_venc_rc *rc, hal_vidconfig *config, int h265, int framerate) +{ + unsigned int bitrate = (unsigned int)config->bitrate << 10; + unsigned int maxBitrate = (unsigned int)MAX(config->bitrate, config->maxBitrate) << 10; + unsigned int fps = (unsigned int)framerate | (1u << 16); + + memset(rc, 0, sizeof(*rc)); + switch (config->mode) { + case HAL_VIDMODE_QP: + if (h265) goto cbr; + rc->mode = FH_VENC_RC_H264_FIXQP; + rc->param[0] = config->maxQual; + rc->param[1] = config->maxQual; + rc->param[2] = fps; + break; + case HAL_VIDMODE_VBR: + case HAL_VIDMODE_ABR: + case HAL_VIDMODE_AVBR: + rc->mode = h265 ? FH_VENC_RC_H265_VBR : FH_VENC_RC_H264_VBR; + rc->param[0] = 35; + rc->param[1] = maxBitrate; + rc->param[2] = fps; + rc->param[3] = 120; + rc->param[6] = 5; + rc->param[7] = 1; + break; + default: +cbr: + rc->mode = h265 ? FH_VENC_RC_H265_CBR : FH_VENC_RC_H264_CBR; + rc->param[0] = 35; + rc->param[1] = bitrate; + rc->param[2] = 28; + rc->param[3] = 50; + rc->param[4] = 35; + rc->param[5] = 50; + rc->param[6] = fps; + rc->param[7] = 120; + rc->param[10] = 5; + rc->param[11] = 1; + break; + } +} + +static int fh_osd_setup(void) +{ + int ret; + /* libvmm helper: fills { phys, virt, size } */ + int (*fnAlloc)(void *, unsigned int, unsigned int, const char *) = + dlsym(fh_sys.handleVmm, "buffer_malloc_withname"); + int (*fnSetGraph)(fh_vpss_graph *) = dlsym(fh_vpss.handle, "FH_VPSS_SetGraph"); + fh_vpss_graph graph; + struct { unsigned int phys; unsigned short *virt; unsigned int size; } buf = { 0 }; + + if (_fh_osd_on) return EXIT_SUCCESS; + if (!fnAlloc || !fnSetGraph) + HAL_ERROR("fh_osd", "Overlay symbols are unavailable!\n"); + + _fh_osd_size = _fh_snr_dim.width * _fh_snr_dim.height * 2; + if (ret = fnAlloc(&buf, _fh_osd_size, 8, "divinus-osd")) + HAL_ERROR("fh_osd", "Allocating the overlay plane failed with %#x!\n", ret); + if (!buf.virt) + HAL_ERROR("fh_osd", "The overlay plane has no user mapping!\n"); + _fh_osd_phys = buf.phys; + _fh_osd_virt = buf.virt; + HAL_INFO("fh_osd", "Overlay plane %ux%u at %#x\n", _fh_snr_dim.width, _fh_snr_dim.height, _fh_osd_phys); + memset(_fh_osd_virt, 0, _fh_osd_size); + + memset(&graph, 0, sizeof(graph)); + graph.enable = 1; + graph.physAddr = _fh_osd_phys; + graph.alpha = _fh_osd_alpha; + graph.width = _fh_snr_dim.width; + graph.height = _fh_snr_dim.height; + graph.stride = _fh_snr_dim.width * 2; + if (ret = fnSetGraph(&graph)) + HAL_ERROR("fh_osd", "Enabling the overlay plane failed with %#x!\n", ret); + _fh_osd_on = 1; + return EXIT_SUCCESS; +} + +/* + * The hardware carries one alpha for the whole graphics plane, and the pixel + * format is ARGB1555 whose alpha bit only says opaque or transparent, so a + * per-region opacity cannot be honoured individually. Apply the highest + * opacity any active region asks for: a region is then never more transparent + * than configured, and with the usual single OSD region it is exact. + */ +static void fh_osd_apply_alpha(void) +{ + int (*fnSetGraph)(fh_vpss_graph *) = dlsym(fh_vpss.handle, "FH_VPSS_SetGraph"); + fh_vpss_graph graph; + unsigned char alpha = 0; + int any = 0; + + for (int i = 0; i < FH_OSD_MAX; i++) + if (_fh_osd[i].used) { any = 1; if (_fh_osd[i].opal > alpha) alpha = _fh_osd[i].opal; } + if (!any) alpha = 255; + if (!_fh_osd_on || !fnSetGraph || alpha == _fh_osd_alpha) { _fh_osd_alpha = alpha; return; } + + _fh_osd_alpha = alpha; + memset(&graph, 0, sizeof(graph)); + graph.enable = 1; + graph.physAddr = _fh_osd_phys; + graph.alpha = _fh_osd_alpha; + graph.width = _fh_snr_dim.width; + graph.height = _fh_snr_dim.height; + graph.stride = _fh_snr_dim.width * 2; + if (fnSetGraph(&graph)) + HAL_WARNING("fh_osd", "Could not set the overlay alpha to %u\n", _fh_osd_alpha); +} + +/* divinus positions regions in main-stream pixels; the plane is at sensor size */ +static void fh_osd_scale(hal_rect *rect) +{ + unsigned int sw = _fh_vpss_dim[0].width ? _fh_vpss_dim[0].width : _fh_snr_dim.width; + unsigned int sh = _fh_vpss_dim[0].height ? _fh_vpss_dim[0].height : _fh_snr_dim.height; + rect->x = (unsigned int)rect->x * _fh_snr_dim.width / sw; + rect->y = (unsigned int)rect->y * _fh_snr_dim.height / sh; +} + +static void fh_osd_clear(hal_rect rect) +{ + unsigned int w; + + /* rect.x/y are unsigned short and OSD positions are only range-checked + * against SHRT_MAX, so a region placed past the plane made + * _fh_snr_dim.width - rect.x wrap and memset most of memory. Reject the + * rectangle before it is used to derive any width or offset. */ + if (!_fh_osd_virt) return; + if (rect.x >= _fh_snr_dim.width || rect.y >= _fh_snr_dim.height) return; + + w = rect.width; + if ((unsigned int)rect.x + w > _fh_snr_dim.width) + w = _fh_snr_dim.width - rect.x; + + for (unsigned int y = 0; y < rect.height; y++) { + unsigned int py = (unsigned int)rect.y + y; + if (py >= _fh_snr_dim.height) break; + memset(_fh_osd_virt + py * _fh_snr_dim.width + rect.x, 0, w * 2); + } +} + +int fh_region_create(int *handle, hal_rect rect, short opacity) +{ + int ret; + if (ret = fh_osd_setup()) + return ret; + if (*handle < 0 || *handle >= FH_OSD_MAX) { + for (int i = 0; i < FH_OSD_MAX; i++) + if (!_fh_osd[i].used) { *handle = i; break; } + if (*handle < 0 || *handle >= FH_OSD_MAX) + HAL_ERROR("fh_osd", "No free overlay region!\n"); + } + if (_fh_osd[*handle].used) + fh_osd_clear(_fh_osd[*handle].rect); + fh_osd_scale(&rect); + _fh_osd[*handle].used = 1; + _fh_osd[*handle].rect = rect; + _fh_osd[*handle].opal = opacity < 0 ? 0 : (opacity > 255 ? 255 : (unsigned char)opacity); + fh_osd_apply_alpha(); + return EXIT_SUCCESS; +} + +void fh_region_destroy(int *handle) +{ + if (*handle < 0 || *handle >= FH_OSD_MAX || !_fh_osd[*handle].used) return; + fh_osd_clear(_fh_osd[*handle].rect); + _fh_osd[*handle].used = 0; + *handle = -1; + fh_osd_apply_alpha(); +} + +/* Bitmaps arrive as BGR555LE; set the alpha bit for opaque pixels (ARGB1555) */ +int fh_region_setbitmap(int *handle, hal_bitmap *bitmap) +{ + + if (*handle < 0 || *handle >= FH_OSD_MAX || !_fh_osd[*handle].used || !_fh_osd_virt) + return EXIT_FAILURE; + fh_osd_rgn *rgn = &_fh_osd[*handle]; + fh_osd_clear(rgn->rect); + if (rgn->rect.x >= _fh_snr_dim.width || rgn->rect.y >= _fh_snr_dim.height) + return EXIT_SUCCESS; + + /* The bitmap is rendered in main-stream pixels while this plane covers the + * whole sensor frame, so it is scaled by the same ratio fh_osd_scale() + * applies to the position. Scaling only the position left the overlay + * placed for one resolution and drawn at another whenever the main stream + * was smaller than the sensor. Nearest neighbour is enough for OSD text. */ + unsigned int sw = _fh_vpss_dim[0].width ? _fh_vpss_dim[0].width : _fh_snr_dim.width; + unsigned int sh = _fh_vpss_dim[0].height ? _fh_vpss_dim[0].height : _fh_snr_dim.height; + unsigned int bw = bitmap->dim.width, bh = bitmap->dim.height; + unsigned int dw = bw * _fh_snr_dim.width / sw; + unsigned int dh = bh * _fh_snr_dim.height / sh; + if (!bw || !bh) return EXIT_SUCCESS; + if (!dw) dw = 1; + if (!dh) dh = 1; + if (dw > 0xffff) dw = 0xffff; + if (dh > 0xffff) dh = 0xffff; + + rgn->rect.width = dw; + rgn->rect.height = dh; + unsigned short *src = bitmap->data; + for (unsigned int y = 0; y < dh; y++) { + unsigned int py = (unsigned int)rgn->rect.y + y; + if (py >= _fh_snr_dim.height) break; + unsigned int sy = y * bh / dh; + unsigned short *dst = _fh_osd_virt + py * _fh_snr_dim.width + rgn->rect.x; + for (unsigned int x = 0; x < dw && (unsigned int)rgn->rect.x + x < _fh_snr_dim.width; x++) { + unsigned short p = src[sy * bw + (x * bw / dw)]; + dst[x] = p ? (p | 0x8000) : 0; + } + } + return EXIT_SUCCESS; +} + +int fh_video_create(char index, hal_vidconfig *config) +{ + int ret; + unsigned int type; + + if (index >= FH_VENC_CHN_NUM) + HAL_ERROR("fh_hal", "Only %d encoder channels are available!\n", FH_VENC_CHN_NUM); + + switch (config->codec) { + case HAL_VIDCODEC_JPG: type = FH_VENC_TYPE_JPEG; break; + case HAL_VIDCODEC_MJPG: type = FH_VENC_TYPE_MJPEG; break; + case HAL_VIDCODEC_H264: type = FH_VENC_TYPE_H264; break; + case HAL_VIDCODEC_H265: type = FH_VENC_TYPE_H265; break; + default: HAL_ERROR("fh_venc", "This codec is not supported by the hardware!\n"); + } + + if (config->width > _fh_snr_dim.width || config->height > _fh_snr_dim.height) { + config->width = _fh_snr_dim.width; + config->height = _fh_snr_dim.height; + } + + /* The scaler output mode must match the encoder before it is configured */ + if (_fh_vpss_src[index] >= 0) { + if (ret = fh_vpss.fnSetOutputMode(index, type == FH_VENC_TYPE_H265 ? 1 : 0)) + HAL_WARNING("fh_vpss", "Setting output mode on channel %d failed with %#x!\n", index, ret); + } else { + /* Shared scaler channel: the encoder must use that channel's size */ + char src = fh_vpss_pick(index); + config->width = _fh_vpss_dim[src].width; + config->height = _fh_vpss_dim[src].height; + } + + if (_fh_venc_made[index].width) { + /* There is no FH_VENC_DestroyChn: a channel's memory pool stays allocated + * until FH_SYS_Exit and a second CreateChn fails (-0x3f3). The vendor + * application creates each channel once with both codecs reserved and + * switches with SetChnAttr; do the same, but never grow past the pool. */ + if (config->width > _fh_venc_made[index].width || config->height > _fh_venc_made[index].height) + HAL_ERROR("fh_venc", "Channel %d was created at %ux%u; %ux%u needs a restart\n", index, + _fh_venc_made[index].width, _fh_venc_made[index].height, config->width, config->height); + } else { + /* The vendor application always reserves memory for both H.264 and H.265 */ + fh_venc_cfg cfg = { .types = (type & (FH_VENC_TYPE_H264 | FH_VENC_TYPE_H265)) ? + (FH_VENC_TYPE_H264 | FH_VENC_TYPE_H265) : type, .width = config->width, .height = config->height }; + if (ret = fh_venc.fnCreateChannel(index, &cfg)) { + HAL_DANGER("fh_venc", "Creating channel %d (%ux%u) failed with %#x!\n", index, cfg.width, cfg.height, ret); + return ret; + } + _fh_venc_made[index].width = cfg.width; + _fh_venc_made[index].height = cfg.height; + } + + if (type == FH_VENC_TYPE_JPEG) { + fh_venc_jpgattr attr; + memset(&attr, 0, sizeof(attr)); + attr.type = type; + attr.quality = MAX(config->maxQual, 1); + attr.rateIndex = 4; + if (ret = fh_venc.fnSetChannelConfig(index, &attr)) + return ret; + } else if (type == FH_VENC_TYPE_MJPEG) { + /* { type, width, height, rotation(0..3), rateIndex(0..9), ..., rc } */ + fh_venc_attr attr; + unsigned int quality = MAX(config->maxQual, 1); + memset(&attr, 0, sizeof(attr)); + attr.type = type; + attr.profile = config->width; + attr.gop = config->height; + attr.width = 0; /* rotation 0..3 */ + attr.height = 4; /* rate table index */ + if (config->mode == HAL_VIDMODE_QP) { + /* fixed quality: { 0, quality(1..99), fps | den << 16 } */ + attr.rc.mode = 0; + attr.rc.param[0] = quality > 99 ? 99 : quality; + attr.rc.param[1] = (unsigned int)config->framerate | (1u << 16); + } else { + /* rate controlled: { 1, bitrate, ?, fps(u16) | den(u16) << 16 } */ + attr.rc.mode = 1; + attr.rc.param[0] = (unsigned int)config->bitrate << 10; + attr.rc.param[1] = (unsigned int)config->bitrate << 10; + attr.rc.param[2] = (unsigned int)config->framerate | (1u << 16); + } + if (ret = fh_venc.fnSetChannelConfig(index, &attr)) { + HAL_DANGER("fh_venc", "Configuring MJPEG channel %d failed with %#x!\n", index, ret); + return ret; + } + } else { + fh_venc_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.type = type; + if (type == FH_VENC_TYPE_H265) + attr.profile = 1; + else { + /* The vendor application only ever sets Main (77); High (100) is + * rejected by FH_VENC_SetChnAttr with 0x19d and would leave the + * channel closed, so every H.264 profile maps to Main here */ + if (config->profile != HAL_VIDPROFILE_MAIN) + HAL_WARNING("fh_venc", "Channel %d: only the H.264 Main profile is supported, using it\n", index); + attr.profile = 77; + } + attr.gop = config->gop ? config->gop : config->framerate * 2; + attr.width = config->width; + attr.height = config->height; + fh_venc_fill_rc(&attr.rc, config, type == FH_VENC_TYPE_H265, config->framerate); + if (ret = fh_venc.fnSetChannelConfig(index, &attr)) { + HAL_DANGER("fh_venc", "Configuring channel %d failed with %#x!\n", index, ret); + return ret; + } + } + + _fh_venc_type[index] = type; + + if (type != FH_VENC_TYPE_JPEG && (ret = fh_venc.fnStartReceiving(index))) { + HAL_DANGER("fh_venc", "Starting channel %d failed with %#x!\n", index, ret); + return ret; + } + + fh_state[index].payload = config->codec; + + return EXIT_SUCCESS; +} + +int fh_video_destroy(char index) +{ + fh_state[index].enable = 0; + fh_state[index].payload = HAL_VIDCODEC_UNSPEC; + + fh_venc.fnStopReceiving(index); + fh_sys.fnUnbindByDst(index); + _fh_venc_type[index] = 0; + fh_channel_destroy(index); + + return EXIT_SUCCESS; +} + +int fh_video_destroy_all(void) +{ + for (char i = 0; i < FH_VENC_CHN_NUM; i++) + if (fh_state[i].enable) + fh_video_destroy(i); + + return EXIT_SUCCESS; +} + +void fh_video_request_idr(char index) +{ + fh_venc.fnRequestIdr(index); +} + +int fh_video_snapshot_grab(signed char index, hal_jpegdata *jpeg) +{ + int ret = EXIT_FAILURE; + fh_venc_strm strm; + unsigned int type = FH_VENC_TYPE_JPEG; + + /* index < 0: serve the most recent MJPEG frame cached by the video thread. + * A real JPEG encoder shares the main scaler channel at bind time (the VPU + * only has main + one sub), so snapshots come out at the main resolution; + * this path is the fallback if that encoder cannot deliver a frame. */ + if (index < 0) { + for (int w = 0; w < 100; w++) { + pthread_mutex_lock(&_fh_mjpeg_mtx); + if (_fh_mjpeg_len) { + if (_fh_mjpeg_len > jpeg->length) { + /* keep the old buffer if this fails; assigning the result + * straight to jpeg->data leaked it and then memcpy'd NULL */ + unsigned char *grown = realloc(jpeg->data, _fh_mjpeg_len); + if (!grown) { + pthread_mutex_unlock(&_fh_mjpeg_mtx); + HAL_ERROR("fh_venc", "Growing the snapshot buffer to %u bytes failed!\n", + _fh_mjpeg_len); + } + jpeg->data = grown; + jpeg->length = _fh_mjpeg_len; + } + memcpy(jpeg->data, _fh_mjpeg_cache, _fh_mjpeg_len); + jpeg->jpegSize = _fh_mjpeg_len; + pthread_mutex_unlock(&_fh_mjpeg_mtx); + return EXIT_SUCCESS; + } + pthread_mutex_unlock(&_fh_mjpeg_mtx); + usleep(20000); + } + HAL_ERROR("fh_venc", "No MJPEG frame available for snapshot!\n"); + } else if (_fh_venc_type[index] != FH_VENC_TYPE_JPEG) + HAL_ERROR("fh_venc", "Channel %d is not a JPEG encoder!\n", index); + + pthread_mutex_lock(&_fh_strm_mtx); + + { + char src = _fh_vpss_src[index] < 0 ? fh_vpss_pick(index) : _fh_vpss_src[index]; + if (ret = fh_sys.fnBindVpu2Enc(src, index)) { + HAL_DANGER("fh_venc", "Binding the encoder channel %d failed with %#x!\n", index, ret); + goto fallback; + } + } + + for (int i = 0; i < 100; i++) { + memset(&strm, 0, sizeof(strm)); + strm.type = type; + if (!(ret = fh_venc.fnGetStream(type, &strm))) + break; + usleep(20000); + } + if (ret) { + HAL_DANGER("fh_venc", "Getting a JPEG frame timed out (%#x)!\n", ret); + goto fallback; + } + + { + /* JPEG: base = data address, frameType = length. MJPEG: NALU-style packets. */ + if (type == FH_VENC_TYPE_JPEG) { + unsigned int len = strm.frameType; + if (len > jpeg->length) { + unsigned char *grown = realloc(jpeg->data, len); + if (!grown) { + HAL_DANGER("fh_venc", "Growing the snapshot buffer to %u bytes failed!\n", len); + fh_venc.fnReleaseStream(strm.channel); + ret = EXIT_FAILURE; + goto abort; + } + jpeg->data = grown; + jpeg->length = len; + } + memcpy(jpeg->data, (void*)strm.base, len); + jpeg->jpegSize = len; + } else { + unsigned int total = 0; + for (unsigned int i = 0; i < strm.naluCount && i < 28; i++) + total += strm.nalu[i].length; + if (total > jpeg->length) { + unsigned char *grown = realloc(jpeg->data, total); + if (!grown) { + HAL_DANGER("fh_venc", "Growing the snapshot buffer to %u bytes failed!\n", total); + fh_venc.fnReleaseStream(strm.channel); + ret = EXIT_FAILURE; + goto abort; + } + jpeg->data = grown; + jpeg->length = total; + } + jpeg->jpegSize = 0; + for (unsigned int i = 0; i < strm.naluCount && i < 28; i++) { + memcpy(jpeg->data + jpeg->jpegSize, (void*)strm.nalu[i].addr, strm.nalu[i].length); + jpeg->jpegSize += strm.nalu[i].length; + } + } + } + fh_venc.fnReleaseStream(strm.channel); + ret = EXIT_SUCCESS; + +abort: + fh_sys.fnUnbindByDst(index); + pthread_mutex_unlock(&_fh_strm_mtx); + + return ret; + +fallback: + fh_sys.fnUnbindByDst(index); + pthread_mutex_unlock(&_fh_strm_mtx); + HAL_WARNING("fh_venc", "Falling back to the MJPEG sub-stream for the snapshot\n"); + + return fh_video_snapshot_grab(-1, jpeg); +} + +void *fh_video_thread(void) +{ + unsigned int mask = FH_VENC_TYPE_H264 | FH_VENC_TYPE_H265 | FH_VENC_TYPE_MJPEG; + fh_venc_strm strm; + + while (keepRunning) { + char active = 0; + for (int i = 0; i < FH_VENC_CHN_NUM; i++) + if (fh_state[i].enable && fh_state[i].mainLoop) active = 1; + if (!active) { + usleep(100000); + continue; + } + + memset(&strm, 0, sizeof(strm)); + strm.type = mask; + if (fh_venc.fnGetStreamBlocking(mask, &strm)) { + usleep(10000); + continue; + } + + char index = strm.channel; + /* The stream lives in the SDK's DMA buffer, which is mapped uncached: + * every pass over it (start-code scans, RTP payload copies) reads at + * bus speed and a 300 KB keyframe cost ~400 ms of CPU. Copy each frame + * once into a cached buffer, hand the SDK buffer back at once, and let + * every consumer work on the copy. */ + { + static unsigned char *vbuf; static unsigned int vcap; + unsigned int need = 0, off = 0; + if (strm.type == FH_VENC_TYPE_MJPEG || _fh_venc_type[index] == FH_VENC_TYPE_MJPEG) + need = strm.frameType; + else for (unsigned int i = 0; i < strm.naluCount && i < 28; i++) need += strm.nalu[i].length; + if (need > vcap) { + unsigned char *n = realloc(vbuf, need + 65536); + if (n) { vbuf = n; vcap = need + 65536; } + } + if (vbuf && need <= vcap) { + if (strm.type == FH_VENC_TYPE_MJPEG || _fh_venc_type[index] == FH_VENC_TYPE_MJPEG) { + memcpy(vbuf, (void*)strm.base, need); + strm.base = (unsigned int)vbuf; + } else for (unsigned int i = 0; i < strm.naluCount && i < 28; i++) { + memcpy(vbuf + off, (void*)strm.nalu[i].addr, strm.nalu[i].length); + strm.nalu[i].addr = (unsigned int)(vbuf + off); + off += strm.nalu[i].length; + } + fh_venc.fnReleaseStream(strm.channel); + strm.channel |= 0x100; /* released: skip the release below */ + } + } + if (index < FH_VENC_CHN_NUM && fh_state[index].enable && fh_vid_cb) { + /* The stream record's type field does not match the fh_venc_type the + * channel was configured with for HEVC (VPS/SPS/PPS were parsed with + * H.264 rules and never recognised), so go by what was configured */ + unsigned int chnType = _fh_venc_type[index]; + char h265 = chnType == FH_VENC_TYPE_H265; + hal_vidstream outStrm; + hal_vidpack outPack[28]; + unsigned long long pts = ((unsigned long long)strm.ptsHigh << 32) | strm.ptsLow; + memset(outPack, 0, sizeof(outPack)); + outStrm.seq = 0; + outStrm.pack = outPack; + + if (chnType == FH_VENC_TYPE_MJPEG || strm.type == FH_VENC_TYPE_MJPEG) { + /* MJPEG/JPEG frames arrive whole at base/frameType, not as NALUs */ + unsigned int mlen = strm.frameType; + pthread_mutex_lock(&_fh_mjpeg_mtx); + if (mlen > _fh_mjpeg_cap) { + _fh_mjpeg_cache = realloc(_fh_mjpeg_cache, mlen); + _fh_mjpeg_cap = mlen; + } + if (_fh_mjpeg_cache) { + memcpy(_fh_mjpeg_cache, (void*)strm.base, mlen); + _fh_mjpeg_len = mlen; + } + pthread_mutex_unlock(&_fh_mjpeg_mtx); + outStrm.count = 1; + outPack[0].data = (unsigned char*)strm.base; + outPack[0].length = strm.frameType; + outPack[0].offset = 0; + outPack[0].timestamp = pts; + } else { + outStrm.count = strm.naluCount > 28 ? 28 : strm.naluCount; + for (unsigned int i = 0; i < outStrm.count; i++) { + unsigned char *data = (unsigned char*)strm.nalu[i].addr; + unsigned int len = strm.nalu[i].length; + outPack[i].data = data; + outPack[i].length = len; + outPack[i].offset = 0; + outPack[i].timestamp = pts; + /* The HEVC encoder hands VPS, SPS and PPS bundled into the IDR + * entry (3-byte start codes inside it), and the MP4 and raw + * consumers trust these per-NALU entries, so split each entry + * on start codes; RTP splits on its own anyway */ + unsigned int cnt = 0, pos = 0; + while (pos + 3 < len && cnt < 8) { + unsigned int sc = 0, next = pos + 3; + if (data[pos] == 0 && data[pos + 1] == 0 && data[pos + 2] == 1) sc = 3; + else if (pos + 4 < len && data[pos] == 0 && data[pos + 1] == 0 && + data[pos + 2] == 0 && data[pos + 3] == 1) sc = 4; + if (!sc) break; + for (next = pos + sc + 1; next + 2 < len; next++) + if (data[next] == 0 && data[next + 1] == 0 && + (data[next + 2] == 1 || (next + 3 < len && data[next + 2] == 0 && data[next + 3] == 1))) + break; + if (next + 2 >= len) next = len; + outPack[i].nalu[cnt].offset = pos; + outPack[i].nalu[cnt].length = next - pos; + outPack[i].nalu[cnt].type = h265 ? + ((data[pos + sc] >> 1) & 0x3f) : (data[pos + sc] & 0x1f); + cnt++; + pos = next; + } + if (!cnt) { + /* no start code found: pass the entry through as one unit */ + outPack[i].nalu[0].offset = 0; + outPack[i].nalu[0].length = len; + outPack[i].nalu[0].type = 0; + cnt = 1; + } + outPack[i].naluCnt = cnt; + } + } + (*fh_vid_cb)(index, &outStrm); + } + + if (!(strm.channel & 0x100)) + fh_venc.fnReleaseStream(strm.channel); + } + HAL_INFO("fh_venc", "Shutting down encoding thread...\n"); + return NULL; +} + +void fh_system_deinit(void) +{ + fh_sys.fnExit(); +} + +int fh_system_init(char *snrConfig) +{ + { + fh_sys_ver version; + if (!fh_sys.fnGetVersion(&version)) + HAL_INFO("fh_hal", "SDK build %08x (%08x), package id %#x\n", + version.date, version.commit, version.package); + } + + _fh_snr = NULL; + for (int i = 0; fh_snr_drivers[i]; i++) { + if (fh_snr_drivers[i]->fnProbe()) continue; + _fh_snr = fh_snr_drivers[i]; + break; + } + if (!_fh_snr) + HAL_ERROR("fh_hal", "No supported sensor found on the I2C bus!\n"); + + _fh_snr_dim = _fh_snr->dim; + strncpy(sensor, _fh_snr->name, sizeof(sensor) - 1); + HAL_INFO("fh_hal", "Detected sensor %s (%ux%u)\n", _fh_snr->name, + _fh_snr_dim.width, _fh_snr_dim.height); + + return EXIT_SUCCESS; +} + +int fh_night_available(void) +{ + return _fh_smartir; +} + +/* 1 = scene is dark (night), 0 = day. Fed the previous decision for hysteresis. */ +/* + * Illuminators (from the vendor application's gpio table and lamp code): each + * lamp has an enable GPIO and a brightness PWM, set through /proc/driver/pwm as + * "id,output_mask,duty_ns,period_ns,pulses,delay_ns,phase_ns,stop" (the mask is + * written raw to GLOBAL_CTRL2, so 1 << id). IR: GPIO7 + PWM3. White light: + * GPIO11 + PWM7 (the board file calls GPIO11 "PHY reset"; it is not wired as + * one on the PB1). GPIO11 is pulled up, so the white light is on from reset + * until something drives it low. + */ +#define FH_PWM_ON(id, mask) id "," mask ",60,200,0,0,0,0" /* 30%, the driver default */ +#define FH_PWM_OFF(id, mask) id "," mask ",0,200,0,0,0,0" /* held low */ +#define FH_WHITELAMP_GPIO 11 + +int fh_irled(char enable) +{ + /* Brightness only; the enable is GPIO7, which divinus drives as ir_led_pin */ + fh_proc_write("/proc/driver/pwm", enable ? FH_PWM_ON("3", "8") : FH_PWM_OFF("3", "8")); + return EXIT_SUCCESS; +} + +int fh_whitelamp(char enable) +{ + fh_proc_write("/proc/driver/pwm", enable ? FH_PWM_ON("7", "128") : FH_PWM_OFF("7", "128")); + gpio_write(FH_WHITELAMP_GPIO, enable); + return EXIT_SUCCESS; +} + +/* Override SmartIR's gain thresholds (th[0] day->night, th[1] night->day); 0 keeps the default */ +int fh_night_thresholds(unsigned short night, unsigned short day) +{ + unsigned short th[4] = {0}; + if (!_fh_smartir || !fh_isp.fnSmartIrGetThreshold || !fh_isp.fnSmartIrSetThreshold) + return EXIT_FAILURE; + if (fh_isp.fnSmartIrGetThreshold(th)) + return EXIT_FAILURE; + if (night) th[0] = night; + if (day) th[1] = day; + HAL_INFO("fh_hal", "SmartIR thresholds set to %u %u %u %u\n", th[0], th[1], th[2], th[3]); + return fh_isp.fnSmartIrSetThreshold(th); +} + +int fh_night_status(void) +{ + if (!_fh_smartir) + return 0; + { + static int lastRaw = -1, calls = 0; + int raw = fh_isp.fnSmartIrStatus(_fh_night_prev); + /* Log every verdict change and a sample every ~10 min (at 40 ms polls) with + * the exposure/gain/total the library decided on and the live thresholds */ + if (raw != lastRaw || !(++calls % 15000)) { + unsigned int ae[16] = {0}; + unsigned short th[4] = {0}; + if (fh_isp.fnGetAeInfo) fh_isp.fnGetAeInfo(ae); + if (fh_isp.fnSmartIrGetThreshold) fh_isp.fnSmartIrGetThreshold(th); + HAL_INFO("fh_hal", "SmartIR raw status %d (prev %d): exp %u gain %u total %u, thresholds %u %u\n", + raw, _fh_night_prev, ae[0], ae[1], ae[4], th[0], th[1]); + lastRaw = raw; + } + _fh_night_prev = raw ? 1 : 0; + } + return _fh_night_prev; +} + +#endif diff --git a/src/hal/fh/fh_hal.h b/src/hal/fh/fh_hal.h new file mode 100644 index 00000000..36931106 --- /dev/null +++ b/src/hal/fh/fh_hal.h @@ -0,0 +1,57 @@ +#pragma once + +#include "fh_common.h" +#include "fh_aud.h" +#include "fh_isp.h" +#include "fh_snr.h" +#include "fh_sys.h" +#include "fh_venc.h" +#include "fh_vpss.h" + +#include "../config.h" +#include "../globals.h" + +#include + +extern hal_chnstate fh_state[FH_VENC_CHN_NUM]; +extern int (*fh_aud_cb)(hal_audframe*); +extern int (*fh_vid_cb)(char, hal_vidstream*); + +void fh_hal_deinit(void); +int fh_hal_init(void); + +void fh_audio_deinit(void); +int fh_audio_init(int samplerate); +void *fh_audio_thread(void); + +int fh_channel_bind(char index); +int fh_channel_create(char index, short width, short height, char framerate, char jpeg); +int fh_channel_grayscale(char enable); +int fh_channel_unbind(char index); + +int fh_config_load(char *path); + +void *fh_image_thread(void); + +int fh_pipeline_create(short width, short height, char mirror, char flip, char framerate, char antiflicker); +int fh_set_antiflicker(char hz); +int fh_night_available(void); +int fh_night_status(void); +int fh_night_thresholds(unsigned short night, unsigned short day); +int fh_irled(char enable); +int fh_whitelamp(char enable); +void fh_pipeline_destroy(void); + +int fh_region_create(int *handle, hal_rect rect, short opacity); +void fh_region_destroy(int *handle); +int fh_region_setbitmap(int *handle, hal_bitmap *bitmap); + +int fh_video_create(char index, hal_vidconfig *config); +int fh_video_destroy(char index); +int fh_video_destroy_all(void); +void fh_video_request_idr(char index); +int fh_video_snapshot_grab(signed char index, hal_jpegdata *jpeg); +void *fh_video_thread(void); + +void fh_system_deinit(void); +int fh_system_init(char *snrConfig); diff --git a/src/hal/fh/fh_isp.h b/src/hal/fh/fh_isp.h new file mode 100644 index 00000000..ffdcae05 --- /dev/null +++ b/src/hal/fh/fh_isp.h @@ -0,0 +1,226 @@ +#pragma once + +#include "fh_common.h" + +/* Video input attributes as produced by the sensor driver and consumed by the ISP (28 bytes) */ +typedef struct { + unsigned short vts; /* frame length in lines */ + unsigned short hts; /* line length in pixel clocks */ + unsigned short height; + unsigned short width; + unsigned short xOffset; + unsigned short yOffset; + unsigned short height2; /* equal to height when not in WDR */ + unsigned short width2; + unsigned int reserved; + unsigned int format; /* bayer/mode word, low 2 bits used */ + unsigned int lanes; /* stored as (lanes - 4) & 0xf */ +} fh_isp_viattr; + +/* libmipi mipi_init() argument */ +typedef struct { + int freqRange; + int sensorMode; + int rawType; + int longFrameVc; /* 0xff = single frame */ + int shortFrameVc; + int laneNum; +} fh_isp_mipi; + +/* + * Sensor operations table registered with API_ISP_SensorRegCb() (0x68 bytes, + * copied by value into the ISP core). Optional entries may be NULL. + */ +typedef struct { + const char *name; /* 0x00 */ + int (*fnSetGain)(unsigned int gain); /* 0x04 gain in 1/64 steps (64 = 1x) */ + int (*fnGetInputAttr)(fh_isp_viattr *attr); /* 0x08 */ + int (*fnGetGain)(unsigned int *gain); /* 0x0c */ + int (*fnSetIntegration)(unsigned int lines); /* 0x10 */ + int (*fnSetFrameHeight)(int multiplier); /* 0x14 VTS = nominal VTS * multiplier */ + int (*fnGetIntegration)(unsigned int *lines); /* 0x18 */ + int (*fnSetFlipMirror)(unsigned int bits); /* 0x1c bit0 flip, bit1 mirror */ + int (*fnGetFlipMirror)(unsigned int *bits); /* 0x20 */ + void *reserved24; + int (*fnInit)(void); /* 0x28 */ + int (*fnReset)(void); /* 0x2c */ + int (*fnDeinit)(void); /* 0x30 */ + int (*fnSetFormat)(int format); /* 0x34 */ + int (*fnKick)(void); /* 0x38 optional */ + int (*fnSetRegister)(unsigned int reg, unsigned int val); /* 0x3c */ + void *reserved40; + int (*fnSetExposureRatio)(void *ratio); /* 0x44 WDR, optional */ + int (*fnGetExposureRatio)(void *ratio); /* 0x48 WDR, optional */ + void *reserved4c; + int (*fnSetChipId)(unsigned int id); /* 0x50 */ + int (*fnGetRegister)(unsigned int reg, unsigned short *val); /* 0x54 */ + int (*fnGetAwbGain)(unsigned int rgb[3]); /* 0x58 optional */ + int (*fnSetAwbGain)(unsigned int rgb[3]); /* 0x5c optional */ + void *reserved60; + void *reserved64; +} fh_isp_snrops; + +typedef struct { + unsigned int frameStart; /* counters */ + unsigned int frameEnd; + unsigned int framerate; + unsigned int width; + unsigned int height; + unsigned int reserved; + unsigned int overflow; +} fh_isp_vistate; + +typedef struct { + void *handle, *handleCore, *handleMipi, *handleAdv; + + int (*fnEnableAe)(unsigned int enable); + int (*fnAeSendCmd)(unsigned int cmd, void *arg); + int (*fnEnableAwb)(unsigned int enable); + int (*fnExit)(void); + int (*fnGetInputAttr)(fh_isp_viattr *attr); + int (*fnGetInputState)(fh_isp_vistate *state); + int (*fnInit)(void); + int (*fnLoadParam)(void *param); + int (*fnMemInit)(unsigned int width, unsigned int height); + int (*fnRegisterSensor)(unsigned int index, fh_isp_snrops *ops); + int (*fnRun)(void); + int (*fnSensorInit)(void); + int (*fnSetFlipMirror)(unsigned int mirror, unsigned int flip); + int (*fnSetFlipMirrorEx)(unsigned int mirror, unsigned int flip, unsigned int bayer); + int (*fnSetFramerateDiv)(unsigned int div); + int (*fnSetSensorFormat)(unsigned int format); + int (*fnUnregisterSensor)(unsigned int index); + + int (*fnAdvInit)(void); + int (*fnSmartIrInit)(void); + int (*fnSmartIrSetAttr)(int rgbir); + unsigned char (*fnSmartIrStatus)(int prevStatus); + int (*fnSmartIrGetThreshold)(unsigned short *th); /* 4 x u16, optional */ + int (*fnSmartIrSetThreshold)(unsigned short *th); + int (*fnGetAeInfo)(void *info); /* [1] gain, [3] exposure, [4] total (1/64) */ + int (*fnAdvSetColorMode)(int color); + int (*fnGetSaturation)(void *sat); /* 20-byte record, byte 5 nonzero = colour */ + int (*fnSetSaturation)(void *sat); + void (*fnMipiInit)(fh_isp_mipi *config); +} fh_isp_impl; + +static int fh_isp_load(fh_isp_impl *isp_lib) { + /* libisp resolves isp_core_* against libispcore at load time */ + if (!(isp_lib->handleCore = dlopen("libispcore.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_isp", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(isp_lib->handle = dlopen("libisp.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_isp", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(isp_lib->handleMipi = dlopen("libmipi.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_isp", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(isp_lib->handleAdv = dlopen("libadvapi.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_isp", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(isp_lib->fnEnableAe = (int(*)(unsigned int enable)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_AEAlgEn"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnAeSendCmd = (int(*)(unsigned int cmd, void *arg)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_AESendCmd"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnEnableAwb = (int(*)(unsigned int enable)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_AWBAlgEn"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnExit = (int(*)(void)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_Exit"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnGetInputAttr = (int(*)(fh_isp_viattr *attr)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_GetViAttr"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnGetInputState = (int(*)(fh_isp_vistate *state)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_GetVIState"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnInit = (int(*)(void)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_Init"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnLoadParam = (int(*)(void *param)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_LoadIspParam"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnMemInit = (int(*)(unsigned int width, unsigned int height)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_MemInit"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnRegisterSensor = (int(*)(unsigned int index, fh_isp_snrops *ops)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SensorRegCb"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnRun = (int(*)(void)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_Run"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnSensorInit = (int(*)(void)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SensorInit"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnSetFlipMirror = (int(*)(unsigned int mirror, unsigned int flip)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SetMirrorAndflip"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnSetFlipMirrorEx = (int(*)(unsigned int mirror, unsigned int flip, unsigned int bayer)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SetMirrorAndflipEx"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnSetFramerateDiv = (int(*)(unsigned int div)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SetSensorFrameRate"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnSetSensorFormat = (int(*)(unsigned int format)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SetSensorFmt"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnUnregisterSensor = (int(*)(unsigned int index)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SensorUnRegCb"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnAdvInit = (int(*)(void)) + hal_symbol_load("fh_isp", isp_lib->handleAdv, "FHAdv_Isp_Init"))) + return EXIT_FAILURE; + + /* SmartIR: image-gain based day/night detection, optional */ + isp_lib->fnSmartIrInit = (int(*)(void))dlsym(isp_lib->handleAdv, "FHAdv_SmartIR_Init"); + isp_lib->fnSmartIrSetAttr = (int(*)(int))dlsym(isp_lib->handleAdv, "FHAdv_SmartIR_SetAttr"); + isp_lib->fnSmartIrStatus = (unsigned char(*)(int))dlsym(isp_lib->handleAdv, "FHAdv_SmartIR_GetDayNightStatus"); + isp_lib->fnSmartIrGetThreshold = (int(*)(unsigned short*))dlsym(isp_lib->handleAdv, "FHAdv_SmartIR_Getthreshold"); + isp_lib->fnSmartIrSetThreshold = (int(*)(unsigned short*))dlsym(isp_lib->handleAdv, "FHAdv_SmartIR_Setthreshold"); + isp_lib->fnGetAeInfo = (int(*)(void*))dlsym(isp_lib->handle, "API_ISP_GetAeInfo"); + + if (!(isp_lib->fnGetSaturation = (int(*)(void *sat)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_GetSaturation"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnSetSaturation = (int(*)(void *sat)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SetSaturation"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnAdvSetColorMode = (int(*)(int color)) + hal_symbol_load("fh_isp", isp_lib->handleAdv, "FHAdv_Isp_SetColorMode"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnMipiInit = (void(*)(fh_isp_mipi *config)) + hal_symbol_load("fh_isp", isp_lib->handleMipi, "mipi_init"))) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} + +static void fh_isp_unload(fh_isp_impl *isp_lib) { + if (isp_lib->handleAdv) dlclose(isp_lib->handleAdv); + if (isp_lib->handleMipi) dlclose(isp_lib->handleMipi); + if (isp_lib->handle) dlclose(isp_lib->handle); + if (isp_lib->handleCore) dlclose(isp_lib->handleCore); + memset(isp_lib, 0, sizeof(*isp_lib)); +} diff --git a/src/hal/fh/fh_snr.h b/src/hal/fh/fh_snr.h new file mode 100644 index 00000000..8a620645 --- /dev/null +++ b/src/hal/fh/fh_snr.h @@ -0,0 +1,25 @@ +#pragma once + +#include "fh_isp.h" + +/* + * Sensor drivers for the Fullhan ISP live in user space: the ISP core calls + * back through an fh_isp_snrops table for exposure, gain, VTS and flip control, + * and the driver programs the sensor over /dev/i2c-0 itself. + */ +typedef struct { + const char *name; /* matches the ISP tuning file prefix, e.g. "gc4653_mipi" */ + fh_common_dim dim; /* native output size */ + unsigned int bayer; /* value for API_ISP_SetMirrorAndflipEx() */ + int (*fnProbe)(void); /* returns 0 if the sensor answers on the bus */ + int (*fnFormatForFramerate)(int framerate); /* fh sensor format code */ + int (*fnFramerateForFormat)(int format); + fh_isp_snrops *(*fnCreate)(fh_isp_impl *isp); +} fh_snr_driver; + +extern fh_snr_driver fh_snr_gc4653; + +static fh_snr_driver *fh_snr_drivers[] = { + &fh_snr_gc4653, + NULL +}; diff --git a/src/hal/fh/fh_snr_gc4653.c b/src/hal/fh/fh_snr_gc4653.c new file mode 100644 index 00000000..3a16f1f6 --- /dev/null +++ b/src/hal/fh/fh_snr_gc4653.c @@ -0,0 +1,299 @@ +#if defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + +/* + * GalaxyCore GC4653 (2560x1440, RAW10, 2-lane MIPI) driver for the Fullhan + * V100 ISP sensor callback interface. Registers are programmed through the + * standard Linux i2c-dev interface on /dev/i2c-0 (16-bit address, 8-bit data). + */ +#include "fh_snr.h" +#include "fh_snr_gc4653_regs.h" + +#include +#include +#include +#include +#include + +#define GC4653_CHIP_ID 0x4653 +#define GC4653_REG_ID_H 0x03f0 +#define GC4653_REG_ID_L 0x03f1 +#define GC4653_REG_FLIP 0x0101 +#define GC4653_REG_EXP_H 0x0202 +#define GC4653_REG_EXP_L 0x0203 +#define GC4653_REG_DIG_H 0x020e +#define GC4653_REG_DIG_L 0x020f +#define GC4653_REG_VTS_H 0x0340 +#define GC4653_REG_VTS_L 0x0341 + +/* Fullhan sensor format codes understood by this driver */ +#define GC4653_FMT_15FPS 0x601 +#define GC4653_FMT_20FPS 0x602 +#define GC4653_FMT_25FPS 0x603 +#define GC4653_FMT_30FPS 0x604 + +#define GC4653_GAIN_ROWS (sizeof(gc4653_gain_level) / sizeof(*gc4653_gain_level)) + +static const unsigned char gc4653_i2c_addrs[] = { 0x29, 0x10 }; +static const unsigned short gc4653_gain_reg_addr[7] = + { 0x02b3, 0x02b4, 0x02b8, 0x02b9, 0x0515, 0x0519, 0x02d9 }; + +static fh_isp_impl *_gc4653_isp; +static int _gc4653_fd = -1; +static unsigned char _gc4653_addr; +static int _gc4653_fmt = GC4653_FMT_25FPS; +static unsigned int _gc4653_gain = 64, _gc4653_intt = 0x6f; +static char _gc4653_ready; + +static int gc4653_read(unsigned short reg) +{ + unsigned char wbuf[2] = { reg >> 8, reg & 0xff }, rbuf[1] = { 0 }; + struct i2c_msg msgs[2] = { + { .addr = _gc4653_addr, .flags = 0, .len = 2, .buf = wbuf }, + { .addr = _gc4653_addr, .flags = I2C_M_RD, .len = 1, .buf = rbuf }, + }; + struct i2c_rdwr_ioctl_data data = { .msgs = msgs, .nmsgs = 2 }; + if (ioctl(_gc4653_fd, I2C_RDWR, &data) < 0) + return -1; + return rbuf[0]; +} + +static int gc4653_write(unsigned short reg, unsigned char val) +{ + unsigned char wbuf[3] = { reg >> 8, reg & 0xff, val }; + struct i2c_msg msg = { .addr = _gc4653_addr, .flags = 0, .len = 3, .buf = wbuf }; + struct i2c_rdwr_ioctl_data data = { .msgs = &msg, .nmsgs = 1 }; + if (ioctl(_gc4653_fd, I2C_RDWR, &data) < 0) { + HAL_WARNING("fh_snr", "GC4653 write of register %#x failed!\n", reg); + return -1; + } + return 0; +} + +static void gc4653_write_table(const gc_reg *table, int count) +{ + for (int i = 0; i < count; i++) + gc4653_write(table[i].reg, table[i].val); +} + +static int gc4653_open(unsigned char addr) +{ + if (_gc4653_fd >= 0) + close(_gc4653_fd); + if ((_gc4653_fd = open("/dev/i2c-0", O_RDWR)) < 0) + return -1; + _gc4653_addr = addr; + ioctl(_gc4653_fd, I2C_TENBIT, 0); + ioctl(_gc4653_fd, I2C_SLAVE, addr); + return 0; +} + +static int gc4653_probe(void) +{ + for (unsigned int i = 0; i < sizeof(gc4653_i2c_addrs); i++) { + if (gc4653_open(gc4653_i2c_addrs[i])) + return -1; + int high = gc4653_read(GC4653_REG_ID_H), low = gc4653_read(GC4653_REG_ID_L); + if (high >= 0 && low >= 0 && ((high << 8) | low) == GC4653_CHIP_ID) + return 0; + } + close(_gc4653_fd); + _gc4653_fd = -1; + return -1; +} + +static int gc4653_set_gain(unsigned int gain) +{ + unsigned int row = 0; + if (gain < gc4653_gain_level[0]) + gain = gc4653_gain_level[0]; + while (row + 1 < GC4653_GAIN_ROWS && gain >= gc4653_gain_level[row + 1]) + row++; + _gc4653_gain = gain; + for (int i = 0; i < 7; i++) + gc4653_write(gc4653_gain_reg_addr[i], gc4653_gain_regs[row][i]); + unsigned int digital = (gain << 6) / gc4653_gain_level[row]; /* 6.6 fixed point */ + gc4653_write(GC4653_REG_DIG_H, digital >> 6); + gc4653_write(GC4653_REG_DIG_L, (digital & 0x3f) << 2); + return 0; +} + +static int gc4653_get_gain(unsigned int *gain) +{ + *gain = _gc4653_gain; + return 0; +} + +static int gc4653_get_input_attr(fh_isp_viattr *attr) +{ + if (!attr) + return -0xbba; + memset(attr, 0, sizeof(*attr)); + switch (_gc4653_fmt) { + case GC4653_FMT_15FPS: attr->vts = 1500; attr->hts = 9600; break; + case GC4653_FMT_20FPS: attr->vts = 2250; attr->hts = 4800; break; + case GC4653_FMT_25FPS: attr->vts = 1800; attr->hts = 4800; break; + case GC4653_FMT_30FPS: attr->vts = 1500; attr->hts = 4800; break; + default: return -0xbbd; + } + attr->width = attr->width2 = 2560; + attr->height = attr->height2 = 1440; + attr->format = 1; + return 0; +} + +static int gc4653_set_integration(unsigned int lines) +{ + _gc4653_intt = lines; + gc4653_write(GC4653_REG_EXP_H, (lines >> 8) & 0xff); + gc4653_write(GC4653_REG_EXP_L, lines & 0xff); + return 0; +} + +static int gc4653_get_integration(unsigned int *lines) +{ + *lines = _gc4653_intt; + return 0; +} + +static int gc4653_set_frame_height(int multiplier) +{ + fh_isp_viattr attr; + gc4653_get_input_attr(&attr); + unsigned int vts = attr.vts * multiplier; + gc4653_write(GC4653_REG_VTS_H, (vts >> 8) & 0xff); + gc4653_write(GC4653_REG_VTS_L, vts & 0xff); + return 0; +} + +/* ISP passes bit 0 = flip, bit 1 = mirror; the sensor register has bit 0 = mirror, bit 1 = flip */ +static int gc4653_set_flip_mirror(unsigned int bits) +{ + int val = gc4653_read(GC4653_REG_FLIP); + if (val < 0) return -1; + return gc4653_write(GC4653_REG_FLIP, (val & 0xfc) | ((bits >> 1) & 1) | ((bits & 1) << 1)); +} + +static int gc4653_get_flip_mirror(unsigned int *bits) +{ + int val = gc4653_read(GC4653_REG_FLIP); + if (val < 0) return -1; + *bits = ((val >> 1) & 1) | ((val & 1) << 1); + return 0; +} + +static int gc4653_init(void) +{ + if (gc4653_probe()) + return -1; + _gc4653_gain = 64; + _gc4653_intt = 0x6f; + _gc4653_ready = 1; + return 0; +} + +static int gc4653_reset(void) { return 0; } + +static int gc4653_deinit(void) +{ + if (_gc4653_fd >= 0) close(_gc4653_fd); + _gc4653_fd = -1; + _gc4653_ready = 0; + return 0; +} + +static int gc4653_set_format(int format) +{ + fh_isp_mipi mipi = { .freqRange = 8, .sensorMode = 0, .rawType = 0, + .longFrameVc = 0xff, .shortFrameVc = 0, .laneNum = 2 }; + _gc4653_fmt = format; + _gc4653_isp->fnMipiInit(&mipi); + if (!_gc4653_ready) + return 0; + switch (format) { + case GC4653_FMT_15FPS: gc4653_write_table(gc4653_init_15fps, sizeof(gc4653_init_15fps) / sizeof(gc_reg)); break; + case GC4653_FMT_20FPS: gc4653_write_table(gc4653_init_20fps, sizeof(gc4653_init_20fps) / sizeof(gc_reg)); break; + case GC4653_FMT_25FPS: gc4653_write_table(gc4653_init_25fps, sizeof(gc4653_init_25fps) / sizeof(gc_reg)); break; + case GC4653_FMT_30FPS: gc4653_write_table(gc4653_init_30fps, sizeof(gc4653_init_30fps) / sizeof(gc_reg)); break; + default: return -1; + } + gc4653_set_integration(500); + gc4653_set_gain(64); + return 0; +} + +static int gc4653_set_register(unsigned int reg, unsigned int val) +{ + return gc4653_write(reg, val); +} + +static int gc4653_get_register(unsigned int reg, unsigned short *val) +{ + int ret = gc4653_read(reg); + if (ret < 0) return -1; + *val = ret; + return 0; +} + +static int gc4653_set_chip_id(unsigned int id) { (void)id; return 0; } + +static fh_isp_snrops gc4653_ops = { + .name = "gc4653_mipi", + .fnSetGain = gc4653_set_gain, + .fnGetInputAttr = gc4653_get_input_attr, + .fnGetGain = gc4653_get_gain, + .fnSetIntegration = gc4653_set_integration, + .fnSetFrameHeight = gc4653_set_frame_height, + .fnGetIntegration = gc4653_get_integration, + .fnSetFlipMirror = gc4653_set_flip_mirror, + .fnGetFlipMirror = gc4653_get_flip_mirror, + .fnInit = gc4653_init, + .fnReset = gc4653_reset, + .fnDeinit = gc4653_deinit, + .fnSetFormat = gc4653_set_format, + .fnSetRegister = gc4653_set_register, + .fnSetChipId = gc4653_set_chip_id, + .fnGetRegister = gc4653_get_register, +}; + +static int gc4653_format_for_framerate(int framerate) +{ + if (framerate <= 15) return GC4653_FMT_15FPS; + if (framerate <= 20) return GC4653_FMT_20FPS; + if (framerate <= 25) return GC4653_FMT_25FPS; + return GC4653_FMT_30FPS; +} + +static int gc4653_framerate_for_format(int format) +{ + switch (format) { + case GC4653_FMT_15FPS: return 15; + case GC4653_FMT_20FPS: return 20; + case GC4653_FMT_25FPS: return 25; + default: return 30; + } +} + +static int gc4653_probe_once(void) +{ + int ret = gc4653_probe(); + gc4653_deinit(); + return ret; +} + +static fh_isp_snrops *gc4653_create(fh_isp_impl *isp) +{ + _gc4653_isp = isp; + return &gc4653_ops; +} + +fh_snr_driver fh_snr_gc4653 = { + .name = "gc4653_mipi", + .dim = { .width = 2560, .height = 1440 }, + .bayer = 1, + .fnProbe = gc4653_probe_once, + .fnFormatForFramerate = gc4653_format_for_framerate, + .fnFramerateForFormat = gc4653_framerate_for_format, + .fnCreate = gc4653_create, +}; + +#endif diff --git a/src/hal/fh/fh_snr_gc4653_regs.h b/src/hal/fh/fh_snr_gc4653_regs.h new file mode 100644 index 00000000..5f8a101c --- /dev/null +++ b/src/hal/fh/fh_snr_gc4653_regs.h @@ -0,0 +1,132 @@ +/* GC4653 register tables: GalaxyCore reference settings, 2560x1440 RAW10 over 2-lane MIPI, for 15/20/25/30 fps */ +#pragma once +#include +typedef struct { uint16_t reg, val; } gc_reg; +static const gc_reg gc4653_init_15fps[] = { + {0x03fe,0xf0}, {0x03fe,0x00}, {0x0317,0x00}, {0x0320,0x77}, {0x0324,0xc8}, {0x0325,0x06}, + {0x0326,0x6c}, {0x0327,0x03}, {0x0334,0x40}, {0x0336,0x6c}, {0x0337,0x82}, {0x0315,0x25}, + {0x031c,0xc6}, {0x0287,0x18}, {0x0084,0x00}, {0x0087,0x50}, {0x029d,0x08}, {0x0290,0x00}, + {0x0340,0x05}, {0x0341,0xdc}, {0x0345,0x06}, {0x034b,0xb0}, {0x0352,0x08}, {0x0354,0x08}, + {0x02d1,0xb0}, {0x023c,0x05}, {0x0223,0xfb}, {0x0232,0xc4}, {0x0279,0x53}, {0x02d3,0x01}, + {0x0243,0x06}, {0x02ce,0xbf}, {0x02ee,0x30}, {0x026f,0x70}, {0x0257,0x09}, {0x0211,0x02}, + {0x0219,0x09}, {0x023f,0x2d}, {0x0518,0x00}, {0x0519,0x01}, {0x0515,0x08}, {0x02d9,0x3f}, + {0x02da,0x02}, {0x02db,0xe8}, {0x02e6,0x20}, {0x021b,0x10}, {0x0252,0x22}, {0x024e,0x22}, + {0x02c4,0x01}, {0x021d,0x07}, {0x024a,0x01}, {0x02ca,0x02}, {0x0262,0x10}, {0x029a,0x20}, + {0x021c,0x0e}, {0x0298,0x03}, {0x029c,0x00}, {0x027e,0x14}, {0x02c2,0x10}, {0x0540,0x20}, + {0x0546,0x01}, {0x0548,0x01}, {0x0544,0x01}, {0x0242,0x1b}, {0x02c0,0x1b}, {0x02c3,0x20}, + {0x02e4,0x10}, {0x022e,0x00}, {0x027b,0x3f}, {0x0269,0x0f}, {0x02d2,0x40}, {0x027c,0x08}, + {0x023a,0x2e}, {0x0245,0xce}, {0x0530,0x20}, {0x0531,0x02}, {0x0228,0x28}, {0x02ab,0x00}, + {0x0250,0x00}, {0x0221,0x28}, {0x02ac,0x00}, {0x02a5,0x02}, {0x0260,0x0b}, {0x0216,0x04}, + {0x0299,0x1c}, {0x02bb,0x0d}, {0x02a3,0x02}, {0x02a4,0x02}, {0x021e,0x02}, {0x024f,0x08}, + {0x028c,0x08}, {0x0532,0x3f}, {0x0533,0x02}, {0x0277,0x58}, {0x0276,0x70}, {0x0239,0xc0}, + {0x0202,0x05}, {0x0203,0xd0}, {0x0205,0xc0}, {0x02b0,0x90}, {0x0002,0xa9}, {0x0004,0x01}, + {0x0342,0x0c}, {0x0343,0x80}, {0x03fe,0x10}, {0x03fe,0x00}, {0x0106,0x78}, {0x0108,0x0c}, + {0x0114,0x01}, {0x0115,0x12}, {0x0180,0x46}, {0x0181,0x30}, {0x0182,0x05}, {0x0185,0x01}, + {0x03fe,0x10}, {0x03fe,0x00}, {0x0100,0x09}, {0x021a,0x98}, {0x0266,0xc0}, {0x0020,0x01}, + {0x0021,0x03}, {0x0022,0x00}, {0x0023,0x04}, +}; +static const gc_reg gc4653_init_20fps[] = { + {0x03fe,0xf0}, {0x03fe,0x00}, {0x0317,0x00}, {0x0320,0x77}, {0x0324,0xc8}, {0x0325,0x06}, + {0x0326,0x6c}, {0x0327,0x03}, {0x0334,0x40}, {0x0336,0x6c}, {0x0337,0x82}, {0x0315,0x25}, + {0x031c,0xc6}, {0x0287,0x18}, {0x0084,0x00}, {0x0087,0x50}, {0x029d,0x08}, {0x0290,0x00}, + {0x0340,0x08}, {0x0341,0xca}, {0x0345,0x06}, {0x034b,0xb0}, {0x0352,0x08}, {0x0354,0x08}, + {0x02d1,0xe0}, {0x0223,0xf2}, {0x0238,0xa4}, {0x02ce,0x7f}, {0x0232,0xc4}, {0x02d3,0x05}, + {0x0243,0x06}, {0x02ee,0x30}, {0x026f,0x70}, {0x0257,0x09}, {0x0211,0x02}, {0x0219,0x09}, + {0x023f,0x2d}, {0x0518,0x00}, {0x0519,0x01}, {0x0515,0x08}, {0x02d9,0x3f}, {0x02da,0x02}, + {0x02db,0xe8}, {0x02e6,0x20}, {0x021b,0x10}, {0x0252,0x22}, {0x024e,0x22}, {0x02c4,0x01}, + {0x021d,0x17}, {0x024a,0x01}, {0x02ca,0x02}, {0x0262,0x10}, {0x029a,0x20}, {0x021c,0x0e}, + {0x0298,0x03}, {0x029c,0x00}, {0x027e,0x14}, {0x02c2,0x10}, {0x0540,0x20}, {0x0546,0x01}, + {0x0548,0x01}, {0x0544,0x01}, {0x0242,0x1b}, {0x02c0,0x1b}, {0x02c3,0x20}, {0x02e4,0x10}, + {0x022e,0x00}, {0x027b,0x3f}, {0x0269,0x0f}, {0x02d2,0x40}, {0x027c,0x08}, {0x023a,0x2e}, + {0x0245,0xce}, {0x0530,0x20}, {0x0531,0x02}, {0x0228,0x50}, {0x02ab,0x00}, {0x0250,0x00}, + {0x0221,0x50}, {0x02ac,0x00}, {0x02a5,0x02}, {0x0260,0x0b}, {0x0216,0x04}, {0x0299,0x1c}, + {0x02bb,0x0d}, {0x02a3,0x02}, {0x02a4,0x02}, {0x021e,0x02}, {0x024f,0x08}, {0x028c,0x08}, + {0x0532,0x3f}, {0x0533,0x02}, {0x0277,0x58}, {0x0276,0xc0}, {0x0239,0xc0}, {0x0202,0x05}, + {0x0203,0xd0}, {0x0205,0xc0}, {0x02b0,0x68}, {0x0002,0xa9}, {0x0004,0x01}, {0x021a,0x98}, + {0x0266,0xa0}, {0x0020,0x01}, {0x0021,0x03}, {0x0022,0x00}, {0x0023,0x04}, {0x0342,0x06}, + {0x0343,0x40}, {0x03fe,0x10}, {0x03fe,0x00}, {0x0106,0x78}, {0x0108,0x0c}, {0x0114,0x01}, + {0x0115,0x12}, {0x0180,0x46}, {0x0181,0x30}, {0x0182,0x05}, {0x0185,0x01}, {0x03fe,0x10}, + {0x03fe,0x00}, {0x0100,0x09}, {0x000f,0x00}, {0x0080,0x02}, {0x0097,0x0a}, {0x0098,0x10}, + {0x0099,0x05}, {0x009a,0xb0}, {0x0317,0x08}, {0x0a67,0x80}, {0x0a70,0x03}, {0x0a82,0x00}, + {0x0a83,0x10}, {0x0a80,0x2b}, {0x05be,0x00}, {0x05a9,0x01}, {0x0313,0x80}, {0x05be,0x01}, + {0x0317,0x00}, {0x0a67,0x00}, +}; +static const gc_reg gc4653_init_25fps[] = { + {0x03fe,0xf0}, {0x03fe,0x00}, {0x0317,0x00}, {0x0320,0x77}, {0x0324,0xc8}, {0x0325,0x06}, + {0x0326,0x6c}, {0x0327,0x03}, {0x0334,0x40}, {0x0336,0x6c}, {0x0337,0x82}, {0x0315,0x25}, + {0x031c,0xc6}, {0x0287,0x18}, {0x0084,0x00}, {0x0087,0x50}, {0x029d,0x08}, {0x0290,0x00}, + {0x0340,0x07}, {0x0341,0x08}, {0x0345,0x06}, {0x034b,0xb0}, {0x0352,0x08}, {0x0354,0x08}, + {0x02d1,0xe0}, {0x0223,0xf2}, {0x0238,0xa4}, {0x02ce,0x7f}, {0x0232,0xc4}, {0x02d3,0x05}, + {0x0243,0x06}, {0x02ee,0x30}, {0x026f,0x70}, {0x0257,0x09}, {0x0211,0x02}, {0x0219,0x09}, + {0x023f,0x2d}, {0x0518,0x00}, {0x0519,0x01}, {0x0515,0x08}, {0x02d9,0x3f}, {0x02da,0x02}, + {0x02db,0xe8}, {0x02e6,0x20}, {0x021b,0x10}, {0x0252,0x22}, {0x024e,0x22}, {0x02c4,0x01}, + {0x021d,0x17}, {0x024a,0x01}, {0x02ca,0x02}, {0x0262,0x10}, {0x029a,0x20}, {0x021c,0x0e}, + {0x0298,0x03}, {0x029c,0x00}, {0x027e,0x14}, {0x02c2,0x10}, {0x0540,0x20}, {0x0546,0x01}, + {0x0548,0x01}, {0x0544,0x01}, {0x0242,0x1b}, {0x02c0,0x1b}, {0x02c3,0x20}, {0x02e4,0x10}, + {0x022e,0x00}, {0x027b,0x3f}, {0x0269,0x0f}, {0x02d2,0x40}, {0x027c,0x08}, {0x023a,0x2e}, + {0x0245,0xce}, {0x0530,0x20}, {0x0531,0x02}, {0x0228,0x50}, {0x02ab,0x00}, {0x0250,0x00}, + {0x0221,0x50}, {0x02ac,0x00}, {0x02a5,0x02}, {0x0260,0x0b}, {0x0216,0x04}, {0x0299,0x1c}, + {0x02bb,0x0d}, {0x02a3,0x02}, {0x02a4,0x02}, {0x021e,0x02}, {0x024f,0x08}, {0x028c,0x08}, + {0x0532,0x3f}, {0x0533,0x02}, {0x0277,0x58}, {0x0276,0xc0}, {0x0239,0xc0}, {0x0202,0x05}, + {0x0203,0xd0}, {0x0205,0xc0}, {0x02b0,0x68}, {0x0002,0xa9}, {0x0004,0x01}, {0x021a,0x98}, + {0x0266,0xa0}, {0x0020,0x01}, {0x0021,0x03}, {0x0022,0x00}, {0x0023,0x04}, {0x0342,0x06}, + {0x0343,0x40}, {0x03fe,0x10}, {0x03fe,0x00}, {0x0106,0x78}, {0x0108,0x0c}, {0x0114,0x01}, + {0x0115,0x12}, {0x0180,0x46}, {0x0181,0x30}, {0x0182,0x05}, {0x0185,0x01}, {0x03fe,0x10}, + {0x03fe,0x00}, {0x0100,0x09}, {0x000f,0x00}, {0x0080,0x02}, {0x0097,0x0a}, {0x0098,0x10}, + {0x0099,0x05}, {0x009a,0xb0}, {0x0317,0x08}, {0x0a67,0x80}, {0x0a70,0x03}, {0x0a82,0x00}, + {0x0a83,0x10}, {0x0a80,0x2b}, {0x05be,0x00}, {0x05a9,0x01}, {0x0313,0x80}, {0x05be,0x01}, + {0x0317,0x00}, {0x0a67,0x00}, +}; +static const gc_reg gc4653_init_30fps[] = { + {0x03fe,0xf0}, {0x03fe,0x00}, {0x0317,0x00}, {0x0320,0x77}, {0x0324,0xc8}, {0x0325,0x06}, + {0x0326,0x6c}, {0x0327,0x03}, {0x0334,0x40}, {0x0336,0x6c}, {0x0337,0x82}, {0x0315,0x25}, + {0x031c,0xc6}, {0x0287,0x18}, {0x0084,0x00}, {0x0087,0x50}, {0x029d,0x08}, {0x0290,0x00}, + {0x0340,0x05}, {0x0341,0xdc}, {0x0345,0x06}, {0x034b,0xb0}, {0x0352,0x08}, {0x0354,0x08}, + {0x02d1,0xe0}, {0x0223,0xf2}, {0x0238,0xa4}, {0x02ce,0x7f}, {0x0232,0xc4}, {0x02d3,0x05}, + {0x0243,0x06}, {0x02ee,0x30}, {0x026f,0x70}, {0x0257,0x09}, {0x0211,0x02}, {0x0219,0x09}, + {0x023f,0x2d}, {0x0518,0x00}, {0x0519,0x01}, {0x0515,0x08}, {0x02d9,0x3f}, {0x02da,0x02}, + {0x02db,0xe8}, {0x02e6,0x20}, {0x021b,0x10}, {0x0252,0x22}, {0x024e,0x22}, {0x02c4,0x01}, + {0x021d,0x17}, {0x024a,0x01}, {0x02ca,0x02}, {0x0262,0x10}, {0x029a,0x20}, {0x021c,0x0e}, + {0x0298,0x03}, {0x029c,0x00}, {0x027e,0x14}, {0x02c2,0x10}, {0x0540,0x20}, {0x0546,0x01}, + {0x0548,0x01}, {0x0544,0x01}, {0x0242,0x1b}, {0x02c0,0x1b}, {0x02c3,0x20}, {0x02e4,0x10}, + {0x022e,0x00}, {0x027b,0x3f}, {0x0269,0x0f}, {0x02d2,0x40}, {0x027c,0x08}, {0x023a,0x2e}, + {0x0245,0xce}, {0x0530,0x20}, {0x0531,0x02}, {0x0228,0x50}, {0x02ab,0x00}, {0x0250,0x00}, + {0x0221,0x50}, {0x02ac,0x00}, {0x02a5,0x02}, {0x0260,0x0b}, {0x0216,0x04}, {0x0299,0x1c}, + {0x02bb,0x0d}, {0x02a3,0x02}, {0x02a4,0x02}, {0x021e,0x02}, {0x024f,0x08}, {0x028c,0x08}, + {0x0532,0x3f}, {0x0533,0x02}, {0x0277,0x58}, {0x0276,0xc0}, {0x0239,0xc0}, {0x0202,0x05}, + {0x0203,0xd0}, {0x0205,0xc0}, {0x02b0,0x90}, {0x0002,0xa9}, {0x0004,0x01}, {0x021a,0x98}, + {0x0266,0xa0}, {0x0020,0x01}, {0x0021,0x03}, {0x0022,0x00}, {0x0023,0x04}, {0x0342,0x06}, + {0x0343,0x40}, {0x03fe,0x10}, {0x03fe,0x00}, {0x0106,0x78}, {0x0108,0x0c}, {0x0114,0x01}, + {0x0115,0x12}, {0x0180,0x46}, {0x0181,0x30}, {0x0182,0x05}, {0x0185,0x01}, {0x03fe,0x10}, + {0x03fe,0x00}, {0x0100,0x09}, +}; +static const unsigned gc4653_gain_level[26] = {64, 75, 89, 106, 128, 151, 179, 212, 256, 303, 358, 424, 512, 606, 716, 849, 1024, 1213, 1433, 1698, 2048, 2426, 2867, 3397, 4096, 4853}; +/* regs 0x2b3 0x2b4 0x2b8 0x2b9 0x515 0x519 0x2d9 (one byte each) */ +static const uint8_t gc4653_gain_regs[26][7] = { + {0x00, 0x00, 0x01, 0x00, 0x30, 0x1e, 0x5c}, + {0x20, 0x00, 0x01, 0x0b, 0x30, 0x1e, 0x5c}, + {0x01, 0x00, 0x01, 0x19, 0x30, 0x1d, 0x5b}, + {0x21, 0x00, 0x01, 0x2a, 0x30, 0x1e, 0x5c}, + {0x02, 0x00, 0x02, 0x00, 0x30, 0x1e, 0x5c}, + {0x22, 0x00, 0x02, 0x17, 0x30, 0x1d, 0x5b}, + {0x03, 0x00, 0x02, 0x33, 0x20, 0x16, 0x54}, + {0x23, 0x00, 0x03, 0x14, 0x20, 0x17, 0x55}, + {0x04, 0x00, 0x04, 0x00, 0x20, 0x17, 0x55}, + {0x24, 0x00, 0x04, 0x2f, 0x20, 0x19, 0x57}, + {0x05, 0x00, 0x05, 0x26, 0x20, 0x19, 0x57}, + {0x25, 0x00, 0x06, 0x28, 0x20, 0x1b, 0x59}, + {0x0c, 0x00, 0x08, 0x00, 0x20, 0x1d, 0x5b}, + {0x2c, 0x00, 0x09, 0x1e, 0x20, 0x1f, 0x5d}, + {0x0d, 0x00, 0x0b, 0x0c, 0x20, 0x21, 0x5f}, + {0x2d, 0x00, 0x0d, 0x11, 0x20, 0x24, 0x62}, + {0x1c, 0x00, 0x10, 0x00, 0x20, 0x26, 0x64}, + {0x3c, 0x00, 0x12, 0x3d, 0x18, 0x2a, 0x68}, + {0x5c, 0x00, 0x16, 0x19, 0x18, 0x2c, 0x6a}, + {0x7c, 0x00, 0x1a, 0x22, 0x18, 0x2e, 0x6c}, + {0x9c, 0x00, 0x20, 0x00, 0x18, 0x32, 0x70}, + {0xbc, 0x00, 0x25, 0x3a, 0x18, 0x35, 0x73}, + {0xdc, 0x00, 0x2c, 0x33, 0x10, 0x36, 0x74}, + {0xfc, 0x00, 0x35, 0x05, 0x10, 0x38, 0x76}, + {0x1c, 0x01, 0x40, 0x00, 0x10, 0x3c, 0x7a}, + {0x3c, 0x01, 0x4b, 0x35, 0x10, 0x42, 0x80}, +}; diff --git a/src/hal/fh/fh_sys.h b/src/hal/fh/fh_sys.h new file mode 100644 index 00000000..3badbf8f --- /dev/null +++ b/src/hal/fh/fh_sys.h @@ -0,0 +1,61 @@ +#pragma once + +#include "fh_common.h" + +#define FH_SYS_API "1.2.0" + +typedef struct { + unsigned int date; /* 0x20200324 */ + unsigned int commit; /* git hash */ + unsigned int package; /* pkg_id, 0x7840d on FH8856 */ + unsigned int reserved; +} fh_sys_ver; + +typedef struct { + void *handle, *handleVmm; + + int (*fnBindVpu2Enc)(unsigned int vpssChn, unsigned int vencChn); + int (*fnUnbindByDst)(unsigned int vencChn); + int (*fnExit)(void); + int (*fnGetVersion)(fh_sys_ver *version); + int (*fnInit)(void); +} fh_sys_impl; + +static int fh_sys_load(fh_sys_impl *sys_lib) { + /* libdsp needs the allocator from libvmm; it must be resolvable globally */ + if (!(sys_lib->handleVmm = dlopen("libvmm.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_sys", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(sys_lib->handle = dlopen("libdsp.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_sys", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(sys_lib->fnBindVpu2Enc = (int(*)(unsigned int vpssChn, unsigned int vencChn)) + hal_symbol_load("fh_sys", sys_lib->handle, "FH_SYS_BindVpu2Enc"))) + return EXIT_FAILURE; + + if (!(sys_lib->fnUnbindByDst = (int(*)(unsigned int vencChn)) + hal_symbol_load("fh_sys", sys_lib->handle, "FH_SYS_UnBindbyDst"))) + return EXIT_FAILURE; + + if (!(sys_lib->fnExit = (int(*)(void)) + hal_symbol_load("fh_sys", sys_lib->handle, "FH_SYS_Exit"))) + return EXIT_FAILURE; + + if (!(sys_lib->fnGetVersion = (int(*)(fh_sys_ver *version)) + hal_symbol_load("fh_sys", sys_lib->handle, "FH_SYS_GetVersion"))) + return EXIT_FAILURE; + + if (!(sys_lib->fnInit = (int(*)(void)) + hal_symbol_load("fh_sys", sys_lib->handle, "FH_SYS_Init"))) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} + +static void fh_sys_unload(fh_sys_impl *sys_lib) { + if (sys_lib->handle) dlclose(sys_lib->handle); + sys_lib->handle = NULL; + if (sys_lib->handleVmm) dlclose(sys_lib->handleVmm); + sys_lib->handleVmm = NULL; + memset(sys_lib, 0, sizeof(*sys_lib)); +} diff --git a/src/hal/fh/fh_venc.h b/src/hal/fh/fh_venc.h new file mode 100644 index 00000000..ce219ded --- /dev/null +++ b/src/hal/fh/fh_venc.h @@ -0,0 +1,125 @@ +#pragma once + +#include "fh_common.h" + +/* Channel creation: which encoders may later be selected, and the maximum frame size */ +typedef struct { + unsigned int types; /* OR of fh_venc_type */ + unsigned int width; + unsigned int height; +} fh_venc_cfg; + +/* + * Rate control block, also the payload of FH_VENC_SetRCAttr(). + * Layouts (vendor application defaults): + * H264/H265 CBR: { mode, 35, bitrate_bps, minQp(28), maxQp(50), targetQp, 50, fps | den << 16, 120, 0, 0, 5, 1, 0 } + * H264/H265 VBR: { mode, 35, bitrate_bps, fps | den << 16, 120, 0, 0, 5, 1 } + * H264 fixed QP: { mode, iQp, pQp, fps | den << 16 } + */ +typedef struct { + unsigned int mode; /* fh_venc_rcmode */ + unsigned int param[21]; +} fh_venc_rc; + +/* FH_VENC_SetChnAttr() payload, 0xac bytes */ +typedef struct { + unsigned int type; /* single fh_venc_type; 0 destroys the encoder */ + unsigned int profile; /* H264: 66 baseline, 77 main, 100 high; H265: 1 */ + unsigned int gop; + unsigned int width; + unsigned int height; + unsigned int reserved[16]; + fh_venc_rc rc; +} fh_venc_attr; + +/* JPEG snapshot encoder attributes share the same 0xac-byte buffer */ +typedef struct { + unsigned int type; /* FH_VENC_TYPE_JPEG */ + unsigned int quality; /* 1..99 */ + unsigned int reserved; + unsigned int rateIndex; /* 0..9, vendor uses 4 */ + unsigned int pad[39]; +} fh_venc_jpgattr; + +typedef struct { + unsigned int type; /* H264: 7 SPS, 8 PPS, 1 slice (IDR too, check frame type) */ + unsigned int length; + unsigned int addr; /* user virtual address, starts with the 4-byte start code */ +} fh_venc_nalu; + +/* Output of FH_VENC_GetStream[_Block](), 0x174 bytes. The first word is the type mask on input. */ +typedef struct { + unsigned int type; /* fh_venc_type of the returned frame */ + unsigned int reserved1; + unsigned int channel; + unsigned int base; /* JPEG: data address */ + unsigned int frameType; /* 2 = I frame, 0 = P frame; JPEG: data length */ + unsigned int length; /* total length of the frame */ + unsigned int ptsLow; /* microseconds */ + unsigned int ptsHigh; + unsigned int naluCount; + fh_venc_nalu nalu[28]; +} fh_venc_strm; + +typedef struct { + void *handle; + + int (*fnCreateChannel)(unsigned int channel, fh_venc_cfg *config); + int (*fnGetStream)(unsigned int typeMask, fh_venc_strm *stream); + int (*fnGetStreamBlocking)(unsigned int typeMask, fh_venc_strm *stream); + int (*fnReleaseStream)(unsigned int channel); + int (*fnRequestIdr)(unsigned int channel); + int (*fnSetChannelConfig)(unsigned int channel, void *config); + int (*fnSetRateControl)(unsigned int channel, fh_venc_rc *rc); + int (*fnStartReceiving)(unsigned int channel); + int (*fnStopReceiving)(unsigned int channel); +} fh_venc_impl; + +static int fh_venc_load(fh_venc_impl *venc_lib) { + if (!(venc_lib->handle = dlopen("libdsp.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_venc", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(venc_lib->fnCreateChannel = (int(*)(unsigned int channel, fh_venc_cfg *config)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_CreateChn"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnGetStream = (int(*)(unsigned int typeMask, fh_venc_strm *stream)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_GetStream"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnGetStreamBlocking = (int(*)(unsigned int typeMask, fh_venc_strm *stream)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_GetStream_Block"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnReleaseStream = (int(*)(unsigned int channel)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_ReleaseStream"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnRequestIdr = (int(*)(unsigned int channel)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_RequestIDR"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnSetChannelConfig = (int(*)(unsigned int channel, void *config)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_SetChnAttr"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnSetRateControl = (int(*)(unsigned int channel, fh_venc_rc *rc)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_SetRCAttr"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnStartReceiving = (int(*)(unsigned int channel)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_StartRecvPic"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnStopReceiving = (int(*)(unsigned int channel)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_StopRecvPic"))) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} + +static void fh_venc_unload(fh_venc_impl *venc_lib) { + if (venc_lib->handle) dlclose(venc_lib->handle); + venc_lib->handle = NULL; + memset(venc_lib, 0, sizeof(*venc_lib)); +} diff --git a/src/hal/fh/fh_vpss.h b/src/hal/fh/fh_vpss.h new file mode 100644 index 00000000..512a8fb7 --- /dev/null +++ b/src/hal/fh/fh_vpss.h @@ -0,0 +1,86 @@ +#pragma once + +#include "fh_common.h" + +typedef struct { + unsigned short srcRate; /* input frames considered */ + unsigned short dstRate; /* output frames kept */ +} fh_vpss_framectrl; + +typedef struct { + void *handle; + + int (*fnCloseChannel)(unsigned int channel); + int (*fnDisable)(unsigned int group); + int (*fnEnable)(unsigned int group); + int (*fnFreezeVideo)(void); + int (*fnGetChannelConfig)(unsigned int channel, fh_common_dim *dim); + int (*fnGetFrameControl)(unsigned int channel, fh_vpss_framectrl *ctrl); + int (*fnOpenChannel)(unsigned int channel); + int (*fnSetChannelConfig)(unsigned int channel, fh_common_dim *dim); + int (*fnSetFrameControl)(unsigned int channel, fh_vpss_framectrl *ctrl); + int (*fnSetInputConfig)(fh_common_dim *dim); + int (*fnSetOutputMode)(unsigned int channel, unsigned int mode); + int (*fnUnfreezeVideo)(void); +} fh_vpss_impl; + +static int fh_vpss_load(fh_vpss_impl *vpss_lib) { + if (!(vpss_lib->handle = dlopen("libdsp.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_vpss", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(vpss_lib->fnCloseChannel = (int(*)(unsigned int channel)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_CloseChn"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnDisable = (int(*)(unsigned int group)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_Disable"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnEnable = (int(*)(unsigned int group)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_Enable"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnFreezeVideo = (int(*)(void)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_FreezeVideo"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnGetChannelConfig = (int(*)(unsigned int channel, fh_common_dim *dim)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_GetChnAttr"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnGetFrameControl = (int(*)(unsigned int channel, fh_vpss_framectrl *ctrl)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_GetFramectrl"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnOpenChannel = (int(*)(unsigned int channel)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_OpenChn"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnSetChannelConfig = (int(*)(unsigned int channel, fh_common_dim *dim)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_SetChnAttr"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnSetFrameControl = (int(*)(unsigned int channel, fh_vpss_framectrl *ctrl)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_SetFramectrl"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnSetInputConfig = (int(*)(fh_common_dim *dim)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_SetViAttr"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnSetOutputMode = (int(*)(unsigned int channel, unsigned int mode)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_SetVOMode"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnUnfreezeVideo = (int(*)(void)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_UnfreezeVideo"))) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} + +static void fh_vpss_unload(fh_vpss_impl *vpss_lib) { + if (vpss_lib->handle) dlclose(vpss_lib->handle); + vpss_lib->handle = NULL; + memset(vpss_lib, 0, sizeof(*vpss_lib)); +} diff --git a/src/hal/hisi/quirks.c b/src/hal/hisi/quirks.c index 7a0d97fe..5c614240 100644 --- a/src/hal/hisi/quirks.c +++ b/src/hal/hisi/quirks.c @@ -7,9 +7,13 @@ int (*fnISP_AlgRegisterDrc)(int); int (*fnISP_AlgRegisterLdci)(int); int (*fnMPI_ISP_IrAutoRunOnce)(int, void*); +/* The Fullhan ISP core library exports its own isp_malloc(); with -rdynamic + * this trampoline would shadow it, so leave it out of ARMv6 (Fullhan) builds */ +#if __ARM_ARCH != 6 void *isp_malloc(unsigned long size) { return fnIsp_Malloc(size); } +#endif int isp_alg_register_acs(int pipeId) { return fnISP_AlgRegisterAcs(pipeId); } diff --git a/src/hal/support.c b/src/hal/support.c index e261bca6..98cd463e 100644 --- a/src/hal/support.c +++ b/src/hal/support.c @@ -139,6 +139,26 @@ void hal_identify(void) { #endif #if defined(__arm__) && !defined(__ARM_PCS_VFP) +#if __ARM_ARCH == 6 + if (file = fopen("/proc/driver/chip", "r")) { + char name[16] = {0}; + while (fgets(line, 200, file)) + if (sscanf(line, "chip_name : %15s", name) == 1) break; + fclose(file); + if (!strncmp(name, "FH", 2)) { + plat = HAL_PLATFORM_FH; + strncpy(chip, name, sizeof(chip) - 1); + strcpy(family, "fullhan"); + chnCount = FH_VENC_CHN_NUM; + chnState = (hal_chnstate*)fh_state; + aud_thread = fh_audio_thread; + isp_thread = fh_image_thread; + vid_thread = fh_video_thread; + return; + } + } +#endif + if (!access("/dev/vpd", F_OK)) { plat = HAL_PLATFORM_GM; strcpy(chip, "GM813x"); diff --git a/src/hal/support.h b/src/hal/support.h index 26a3ea0e..d2132910 100644 --- a/src/hal/support.h +++ b/src/hal/support.h @@ -15,6 +15,9 @@ #include "hisi/v2_hal.h" #include "hisi/v3_hal.h" #include "hisi/v4_hal.h" +#if __ARM_ARCH == 6 +#include "fh/fh_hal.h" +#endif #elif defined(__mips__) #include "inge/t31_hal.h" #elif defined(__riscv) || defined(__riscv__) diff --git a/src/hal/types.h b/src/hal/types.h index 4b0714b8..baa3d9c5 100644 --- a/src/hal/types.h +++ b/src/hal/types.h @@ -6,6 +6,7 @@ typedef enum { HAL_PLATFORM_UNK, HAL_PLATFORM_AK, HAL_PLATFORM_CVI, + HAL_PLATFORM_FH, HAL_PLATFORM_GM, HAL_PLATFORM_I3, HAL_PLATFORM_I6, diff --git a/src/jpeg.c b/src/jpeg.c index babf71f9..24074c92 100644 --- a/src/jpeg.c +++ b/src/jpeg.c @@ -47,6 +47,9 @@ int jpeg_init() { case HAL_PLATFORM_V2: ret = v2_video_create(jpeg_index, &config); break; case HAL_PLATFORM_V3: ret = v3_video_create(jpeg_index, &config); break; case HAL_PLATFORM_V4: ret = v4_video_create(jpeg_index, &config); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_video_create(jpeg_index, &config); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_video_create(jpeg_index, &config); break; #elif defined(__riscv) || defined(__riscv__) @@ -87,6 +90,11 @@ void jpeg_deinit() { case HAL_PLATFORM_V2: v2_video_destroy(jpeg_index); break; case HAL_PLATFORM_V3: v3_video_destroy(jpeg_index); break; case HAL_PLATFORM_V4: v4_video_destroy(jpeg_index); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: + fh_video_destroy(jpeg_index); + break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: if (app_config.mjpeg_enable) goto active; @@ -127,6 +135,9 @@ int jpeg_get(short width, short height, char quality, char grayscale, case HAL_PLATFORM_V2: ret = v2_video_snapshot_grab(jpeg_index, jpeg); break; case HAL_PLATFORM_V3: ret = v3_video_snapshot_grab(jpeg_index, jpeg); break; case HAL_PLATFORM_V4: ret = v4_video_snapshot_grab(jpeg_index, jpeg); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_video_snapshot_grab(jpeg_index, jpeg); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_video_snapshot_grab(app_config.mjpeg_enable ? -1 : jpeg_index, jpeg); break; diff --git a/src/media.c b/src/media.c index f3d6f58f..a9b649eb 100644 --- a/src/media.c +++ b/src/media.c @@ -268,6 +268,9 @@ void request_idr(void) { case HAL_PLATFORM_V2: v2_video_request_idr(index); break; case HAL_PLATFORM_V3: v3_video_request_idr(index); break; case HAL_PLATFORM_V4: v4_video_request_idr(index); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_video_request_idr(index); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_video_request_idr(index); break; #elif defined(__riscv) || defined(__riscv__) @@ -291,6 +294,9 @@ void set_grayscale(bool active) { case HAL_PLATFORM_V2: v2_channel_grayscale(active); break; case HAL_PLATFORM_V3: v3_channel_grayscale(active); break; case HAL_PLATFORM_V4: v4_channel_grayscale(active); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_channel_grayscale(active); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_channel_grayscale(active); break; #elif defined(__riscv) || defined(__riscv__) @@ -332,6 +338,10 @@ int create_channel(char index, short width, short height, char framerate, char j app_config.mirror, app_config.flip, framerate); case HAL_PLATFORM_V4: return v4_channel_create(index, app_config.mirror, app_config.flip, framerate); +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: return fh_channel_create(index, width, height, + framerate, jpeg); +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: return t31_channel_create(index, width, height, framerate, jpeg); @@ -356,6 +366,9 @@ int bind_channel(char index, char framerate, char jpeg) { case HAL_PLATFORM_V2: return v2_channel_bind(index); case HAL_PLATFORM_V3: return v3_channel_bind(index); case HAL_PLATFORM_V4: return v4_channel_bind(index); +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: return fh_channel_bind(index); +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: return t31_channel_bind(index); #elif defined(__riscv) || defined(__riscv__) @@ -378,6 +391,9 @@ int unbind_channel(char index, char jpeg) { case HAL_PLATFORM_V2: return v2_channel_unbind(index); case HAL_PLATFORM_V3: return v3_channel_unbind(index); case HAL_PLATFORM_V4: return v4_channel_unbind(index); +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: return fh_channel_unbind(index); +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: return t31_channel_unbind(index); #elif defined(__riscv) || defined(__riscv__) @@ -400,6 +416,9 @@ int media_video_disable(char index, char jpeg) { case HAL_PLATFORM_V2: return v2_video_destroy(index); case HAL_PLATFORM_V3: return v3_video_destroy(index); case HAL_PLATFORM_V4: return v4_video_destroy(index); +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: return fh_video_destroy(index); +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: return t31_video_destroy(index); #elif defined(__riscv) || defined(__riscv__) @@ -431,6 +450,9 @@ void media_audio_disable(void) { case HAL_PLATFORM_V2: v2_audio_deinit(); break; case HAL_PLATFORM_V3: v3_audio_deinit(); break; case HAL_PLATFORM_V4: v4_audio_deinit(); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_audio_deinit(); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_audio_deinit(); break; #elif defined(__riscv) || defined(__riscv__) @@ -459,6 +481,9 @@ int media_audio_enable(void) { case HAL_PLATFORM_V2: ret = v2_audio_init(app_config.audio_srate); break; case HAL_PLATFORM_V3: ret = v3_audio_init(app_config.audio_srate); break; case HAL_PLATFORM_V4: ret = v4_audio_init(app_config.audio_srate); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_audio_init(app_config.audio_srate); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_audio_init(app_config.audio_srate); break; #elif defined(__riscv) || defined(__riscv__) @@ -574,6 +599,9 @@ int media_mjpeg_enable(void) { case HAL_PLATFORM_V2: ret = v2_video_create(index, &config); break; case HAL_PLATFORM_V3: ret = v3_video_create(index, &config); break; case HAL_PLATFORM_V4: ret = v4_video_create(index, &config); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_video_create(index, &config); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_video_create(index, &config); break; #elif defined(__riscv) || defined(__riscv__) @@ -649,6 +677,9 @@ int media_mp4_enable(void) { case HAL_PLATFORM_V2: ret = v2_video_create(index, &config); break; case HAL_PLATFORM_V3: ret = v3_video_create(index, &config); break; case HAL_PLATFORM_V4: ret = v4_video_create(index, &config); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_video_create(index, &config); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_video_create(index, &config); break; #elif defined(__riscv) || defined(__riscv__) @@ -689,6 +720,9 @@ int sdk_start(void) { case HAL_PLATFORM_V2: ret = v2_hal_init(); break; case HAL_PLATFORM_V3: ret = v3_hal_init(); break; case HAL_PLATFORM_V4: ret = v4_hal_init(); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_hal_init(); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_hal_init(); break; #elif defined(__riscv) || defined(__riscv__) @@ -738,6 +772,12 @@ int sdk_start(void) { v4_aud_cb = save_audio_stream; v4_vid_cb = save_video_stream; break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: + fh_aud_cb = save_audio_stream; + fh_vid_cb = save_video_stream; + break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_aud_cb = save_audio_stream; @@ -765,6 +805,9 @@ int sdk_start(void) { case HAL_PLATFORM_V2: ret = v2_system_init(app_config.sensor_config); break; case HAL_PLATFORM_V3: ret = v3_system_init(app_config.sensor_config); break; case HAL_PLATFORM_V4: ret = v4_system_init(app_config.sensor_config); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_system_init(app_config.sensor_config); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_system_init(); break; #elif defined(__riscv) || defined(__riscv__) @@ -777,9 +820,18 @@ int sdk_start(void) { if (app_config.audio_enable) { ret = media_audio_enable(); - if (ret) + if (ret) { +#if defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + /* Fullhan only: a broken mic/audio config must not abort the SDK start, + * which on this watchdog-guarded SoC means a reboot loop */ + HAL_WARNING("media", "Audio initialization failed with %#x, continuing without audio!\n%s\n", + ret, errstr(ret)); + app_config.audio_enable = false; +#else HAL_ERROR("media", "Audio initialization failed with %#x!\n%s\n", ret, errstr(ret)); +#endif + } } short width = MAX(app_config.mp4_width, app_config.mjpeg_width); @@ -804,6 +856,10 @@ int sdk_start(void) { case HAL_PLATFORM_V2: ret = v2_pipeline_create(); break; case HAL_PLATFORM_V3: ret = v3_pipeline_create(); break; case HAL_PLATFORM_V4: ret = v4_pipeline_create(); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_pipeline_create(width, height, + app_config.mirror, app_config.flip, framerate, app_config.antiflicker); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_pipeline_create(app_config.mirror, app_config.flip, app_config.antiflicker, framerate); break; @@ -863,6 +919,8 @@ int sdk_start(void) { case HAL_PLATFORM_I6: i6_config_load(app_config.sensor_config); break; case HAL_PLATFORM_I6C: i6c_config_load(app_config.sensor_config); break; case HAL_PLATFORM_M6: m6_config_load(app_config.sensor_config); break; +#elif defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_config_load(app_config.sensor_config); break; #elif defined(__mips__) case HAL_PLATFORM_T31: t31_config_load(app_config.sensor_config); break; #endif @@ -892,6 +950,9 @@ int sdk_stop(void) { case HAL_PLATFORM_V2: v2_video_destroy_all(); break; case HAL_PLATFORM_V3: v3_video_destroy_all(); break; case HAL_PLATFORM_V4: v4_video_destroy_all(); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_video_destroy_all(); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_video_destroy_all(); break; #elif defined(__riscv) || defined(__riscv__) @@ -912,6 +973,9 @@ int sdk_stop(void) { case HAL_PLATFORM_V2: v2_pipeline_destroy(); break; case HAL_PLATFORM_V3: v3_pipeline_destroy(); break; case HAL_PLATFORM_V4: v4_pipeline_destroy(); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_pipeline_destroy(); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_pipeline_destroy(); break; #elif defined(__riscv) || defined(__riscv__) @@ -939,6 +1003,9 @@ int sdk_stop(void) { case HAL_PLATFORM_V2: v2_system_deinit(); break; case HAL_PLATFORM_V3: v3_system_deinit(); break; case HAL_PLATFORM_V4: v4_system_deinit(); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_system_deinit(); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_system_deinit(); break; #elif defined(__riscv) || defined(__riscv__) @@ -969,6 +1036,9 @@ int sdk_stop(void) { case HAL_PLATFORM_V2: v2_hal_deinit(); break; case HAL_PLATFORM_V3: v3_hal_deinit(); break; case HAL_PLATFORM_V4: v4_hal_deinit(); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_hal_deinit(); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_hal_deinit(); break; #elif defined(__riscv) || defined(__riscv__) diff --git a/src/night.c b/src/night.c index a0c8c44c..5da2236e 100644 --- a/src/night.c +++ b/src/night.c @@ -29,6 +29,9 @@ void night_ircut(bool enable) { } void night_irled(bool enable) { +#if defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + if (plat == HAL_PLATFORM_FH) fh_irled(enable); /* PWM3 brightness; GPIO7 enable follows */ +#endif gpio_write(app_config.ir_led_pin, enable); irled = enable; } @@ -37,6 +40,23 @@ void night_manual(bool enable) { manual = enable; } void night_mode(bool enable) { HAL_INFO("night", "Changing mode to %s\n", enable ? "NIGHT" : "DAY"); +#if defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + if (plat == HAL_PLATFORM_FH && EQUALS(app_config.night_lamp, "white")) { + /* Colour night vision: light the scene with the white lamp and keep the + * IR-cut filter in and the image in colour; the IR lamp stays off */ + night_grayscale(false); + night_ircut(true); + night_irled(false); + fh_whitelamp(enable); + return; + } + if (plat == HAL_PLATFORM_FH && EQUALS(app_config.night_lamp, "none")) { + night_grayscale(enable); + night_ircut(!enable); + night_irled(false); + return; + } +#endif night_grayscale(enable); night_ircut(!enable); night_irled(enable); @@ -48,6 +68,24 @@ void *night_thread(void) { night_mode(night_mode_on()); +#if defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + if (plat == HAL_PLATFORM_FH && fh_night_available()) { + HAL_INFO("night", "Using SmartIR (image gain) for day/night switching\n"); + if (app_config.smartir_gain_night || app_config.smartir_gain_day) + fh_night_thresholds(app_config.smartir_gain_night, app_config.smartir_gain_day); + while (keepRunning && nightOn) { + /* SmartIR is polled; only touch the IR-cut/LED GPIOs when the verdict changes, + * otherwise night_mode() pulses the IR-cut solenoid every interval */ + static int applied = -1; + int state = fh_night_status(); + if (manual) applied = -1; + else if (state != applied) { night_mode(state); applied = state; } + /* SmartIR's debounce counters are per call and sized for the vendor's + * 40 ms polling loop, so don't pace it with check_interval_s */ + usleep(40000); + } + } else +#endif if (app_config.adc_device[0]) { int adc_fd = -1; fd_set adc_fds; diff --git a/src/region.c b/src/region.c index 23785663..01007a11 100644 --- a/src/region.c +++ b/src/region.c @@ -426,6 +426,12 @@ found_font:; v4_region_create(id, rect, osds[id].opal); v4_region_setbitmap(id, &bitmap); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: + fh_region_create(&osds[id].hand, rect, osds[id].opal); + fh_region_setbitmap(&osds[id].hand, &bitmap); + break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_region_create(&osds[id].hand, rect, osds[id].opal); @@ -493,6 +499,12 @@ found_font:; v4_region_create(id, rect, osds[id].opal); v4_region_setbitmap(id, &bitmap); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: + fh_region_create(&osds[id].hand, rect, osds[id].opal); + fh_region_setbitmap(&osds[id].hand, &bitmap); + break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_region_create(&osds[id].hand, rect, osds[id].opal); @@ -516,6 +528,9 @@ found_font:; case HAL_PLATFORM_V2: v2_region_destroy(id); break; case HAL_PLATFORM_V3: v3_region_destroy(id); break; case HAL_PLATFORM_V4: v4_region_destroy(id); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_region_destroy(&osds[id].hand); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_region_destroy(&osds[id].hand); break; #endif