|  | /* | 
|  | * Copyright (c) 2025 STMicroelectronics. | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT st_stm32_venc | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <stdlib.h> | 
|  |  | 
|  | #include <zephyr/drivers/video.h> | 
|  | #include <zephyr/drivers/clock_control.h> | 
|  | #include <zephyr/drivers/clock_control/stm32_clock_control.h> | 
|  | #include <zephyr/drivers/reset.h> | 
|  | #include <zephyr/irq.h> | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/logging/log.h> | 
|  | #include <zephyr/multi_heap/shared_multi_heap.h> | 
|  |  | 
|  | #include <ewl.h> | 
|  | #include <h264encapi.h> | 
|  | #include <reg_offset_v7.h> | 
|  |  | 
|  | LOG_MODULE_REGISTER(stm32_venc, CONFIG_VIDEO_LOG_LEVEL); | 
|  |  | 
|  | #define VENC_DEFAULT_WIDTH               320 | 
|  | #define VENC_DEFAULT_HEIGHT              240 | 
|  | #define VENC_DEFAULT_IN_FMT              VIDEO_PIX_FMT_NV12 | 
|  | #define VENC_DEFAULT_OUT_FMT             VIDEO_PIX_FMT_H264 | 
|  | #define VENC_DEFAULT_FRAMERATE           30 | 
|  | #define VENC_DEFAULT_LEVEL               H264ENC_LEVEL_4 | 
|  | #define VENC_DEFAULT_QP                  25 | 
|  | #define VENC_ESTIMATED_COMPRESSION_RATIO 10 | 
|  |  | 
|  | #define ALIGNMENT_INCR 8UL | 
|  |  | 
|  | #define EWL_HEAP_ALIGNED_ALLOC(size)\ | 
|  | shared_multi_heap_aligned_alloc(CONFIG_VIDEO_BUFFER_SMH_ATTRIBUTE, ALIGNMENT_INCR, (size)) | 
|  | #define EWL_HEAP_ALIGNED_FREE(block) shared_multi_heap_free(block) | 
|  |  | 
|  | #define EWL_TIMEOUT 100UL | 
|  |  | 
|  | #define MEM_CHUNKS 32 | 
|  |  | 
|  | #define NUM_SLICES_READY_MASK GENMASK(23, 16) | 
|  | #define LOW_LATENCY_HW_ITF_EN 29 | 
|  |  | 
|  | typedef void (*irq_config_func_t)(const struct device *dev); | 
|  |  | 
|  | struct stm32_venc_config { | 
|  | mm_reg_t reg; | 
|  | const struct stm32_pclken pclken; | 
|  | const struct reset_dt_spec reset; | 
|  | irq_config_func_t irq_config; | 
|  | }; | 
|  |  | 
|  | struct stm32_venc_ewl { | 
|  | uint32_t client_type; | 
|  | uint32_t *chunks[MEM_CHUNKS]; | 
|  | uint32_t *aligned_chunks[MEM_CHUNKS]; | 
|  | uint32_t total_chunks; | 
|  | const struct stm32_venc_config *config; | 
|  | struct k_sem complete; | 
|  | uint32_t irq_status; | 
|  | uint32_t irq_cnt; | 
|  | uint32_t mem_cnt; | 
|  | }; | 
|  |  | 
|  | static struct stm32_venc_ewl ewl_instance; | 
|  |  | 
|  | struct stm32_venc_data { | 
|  | const struct device *dev; | 
|  | struct k_mutex lock; | 
|  | struct video_format in_fmt; | 
|  | struct video_format out_fmt; | 
|  | struct k_fifo in_fifo_in; | 
|  | struct k_fifo in_fifo_out; | 
|  | struct k_fifo out_fifo_in; | 
|  | struct k_fifo out_fifo_out; | 
|  | struct video_buffer *vbuf; | 
|  | H264EncInst encoder; | 
|  | uint32_t frame_nb; | 
|  | bool resync; | 
|  | }; | 
|  |  | 
|  | static H264EncPictureType to_h264pixfmt(uint32_t pixelformat) | 
|  | { | 
|  | switch (pixelformat) { | 
|  | case VIDEO_PIX_FMT_NV12: | 
|  | return H264ENC_YUV420_SEMIPLANAR; | 
|  | case VIDEO_PIX_FMT_RGB565: | 
|  | return H264ENC_RGB565; | 
|  | default: | 
|  | __ASSERT_NO_MSG(false); | 
|  | return H264ENC_YUV420_SEMIPLANAR; | 
|  | } | 
|  | } | 
|  |  | 
|  | u32 EWLReadAsicID(void) | 
|  | { | 
|  | const struct stm32_venc_config *config = ewl_instance.config; | 
|  |  | 
|  | return sys_read32(config->reg + BASE_HEncASIC); | 
|  | } | 
|  |  | 
|  | EWLHwConfig_t EWLReadAsicConfig(void) | 
|  | { | 
|  | const struct stm32_venc_config *config = ewl_instance.config; | 
|  | EWLHwConfig_t cfg_info; | 
|  | uint32_t cfgval, cfgval2; | 
|  |  | 
|  | cfgval = sys_read32(config->reg + BASE_HEncSynth); | 
|  | cfgval2 = sys_read32(config->reg + BASE_HEncSynth1); | 
|  |  | 
|  | cfg_info = EWLBuildHwConfig(cfgval, cfgval2); | 
|  |  | 
|  | LOG_DBG("maxEncodedWidth   = %d", cfg_info.maxEncodedWidth); | 
|  | LOG_DBG("h264Enabled       = %d", cfg_info.h264Enabled); | 
|  | LOG_DBG("jpegEnabled       = %d", cfg_info.jpegEnabled); | 
|  | LOG_DBG("vp8Enabled        = %d", cfg_info.vp8Enabled); | 
|  | LOG_DBG("vsEnabled         = %d", cfg_info.vsEnabled); | 
|  | LOG_DBG("rgbEnabled        = %d", cfg_info.rgbEnabled); | 
|  | LOG_DBG("searchAreaSmall   = %d", cfg_info.searchAreaSmall); | 
|  | LOG_DBG("scalingEnabled    = %d", cfg_info.scalingEnabled); | 
|  | LOG_DBG("address64bits     = %d", cfg_info.addr64Support); | 
|  | LOG_DBG("denoiseEnabled    = %d", cfg_info.dnfSupport); | 
|  | LOG_DBG("rfcEnabled        = %d", cfg_info.rfcSupport); | 
|  | LOG_DBG("instanctEnabled   = %d", cfg_info.instantSupport); | 
|  | LOG_DBG("busType           = %d", cfg_info.busType); | 
|  | LOG_DBG("synthesisLanguage = %d", cfg_info.synthesisLanguage); | 
|  | LOG_DBG("busWidth          = %d", cfg_info.busWidth * 32); | 
|  |  | 
|  | return cfg_info; | 
|  | } | 
|  |  | 
|  | const void *EWLInit(EWLInitParam_t *param) | 
|  | { | 
|  | __ASSERT_NO_MSG(param != NULL); | 
|  | __ASSERT_NO_MSG(param->clientType == EWL_CLIENT_TYPE_H264_ENC); | 
|  |  | 
|  | /* sync */ | 
|  | k_sem_init(&ewl_instance.complete, 0, 1); | 
|  |  | 
|  | /* set client type */ | 
|  | ewl_instance.client_type = param->clientType; | 
|  | ewl_instance.irq_cnt = 0; | 
|  |  | 
|  | return (void *)&ewl_instance; | 
|  | } | 
|  |  | 
|  | i32 EWLRelease(const void *inst) | 
|  | { | 
|  | ARG_UNUSED(inst); | 
|  |  | 
|  | return EWL_OK; | 
|  | } | 
|  |  | 
|  | void EWLWriteReg(const void *instance, uint32_t offset, uint32_t val) | 
|  | { | 
|  | struct stm32_venc_ewl *inst = (struct stm32_venc_ewl *)instance; | 
|  | const struct stm32_venc_config *config = inst->config; | 
|  |  | 
|  | sys_write32(val, config->reg + offset); | 
|  | } | 
|  |  | 
|  | void EWLEnableHW(const void *instance, uint32_t offset, uint32_t val) | 
|  | { | 
|  | struct stm32_venc_ewl *inst = (struct stm32_venc_ewl *)instance; | 
|  | const struct stm32_venc_config *config = inst->config; | 
|  |  | 
|  | sys_write32(val, config->reg + offset); | 
|  | } | 
|  |  | 
|  | void EWLDisableHW(const void *instance, uint32_t offset, uint32_t val) | 
|  | { | 
|  | struct stm32_venc_ewl *inst = (struct stm32_venc_ewl *)instance; | 
|  | const struct stm32_venc_config *config = inst->config; | 
|  |  | 
|  | sys_write32(val, config->reg + offset); | 
|  | } | 
|  |  | 
|  | uint32_t EWLReadReg(const void *instance, uint32_t offset) | 
|  | { | 
|  | struct stm32_venc_ewl *inst = (struct stm32_venc_ewl *)instance; | 
|  | const struct stm32_venc_config *config = inst->config; | 
|  |  | 
|  | return sys_read32(config->reg + offset); | 
|  | } | 
|  |  | 
|  | i32 EWLMallocRefFrm(const void *instance, uint32_t size, EWLLinearMem_t *info) | 
|  | { | 
|  | return EWLMallocLinear(instance, size, info); | 
|  | } | 
|  |  | 
|  | void EWLFreeRefFrm(const void *instance, EWLLinearMem_t *info) | 
|  | { | 
|  | EWLFreeLinear(instance, info); | 
|  | } | 
|  |  | 
|  | i32 EWLMallocLinear(const void *instance, uint32_t size, EWLLinearMem_t *info) | 
|  | { | 
|  | struct stm32_venc_ewl *inst = (struct stm32_venc_ewl *)instance; | 
|  |  | 
|  | __ASSERT_NO_MSG(inst != NULL); | 
|  | __ASSERT_NO_MSG(info != NULL); | 
|  |  | 
|  | /* align size */ | 
|  | uint32_t size_aligned = ROUND_UP(size, ALIGNMENT_INCR); | 
|  |  | 
|  | info->size = size_aligned; | 
|  |  | 
|  | /* allocate */ | 
|  | inst->chunks[inst->total_chunks] = (uint32_t *)EWL_HEAP_ALIGNED_ALLOC(size_aligned); | 
|  | if (inst->chunks[inst->total_chunks] == NULL) { | 
|  | LOG_DBG("unable to allocate %8d bytes", size_aligned); | 
|  | return EWL_ERROR; | 
|  | } | 
|  |  | 
|  | /* align given allocated buffer */ | 
|  | inst->aligned_chunks[inst->total_chunks] = | 
|  | (uint32_t *)ROUND_UP((uint32_t)inst->chunks[inst->total_chunks], ALIGNMENT_INCR); | 
|  | /* put the aligned pointer in the return structure */ | 
|  | info->virtualAddress = inst->aligned_chunks[inst->total_chunks]; | 
|  | if (info->virtualAddress == NULL) { | 
|  | LOG_DBG("unable to get chunk for %8d bytes", size_aligned); | 
|  | EWL_HEAP_ALIGNED_FREE(inst->chunks[inst->total_chunks]); | 
|  | return EWL_ERROR; | 
|  | } | 
|  | inst->total_chunks++; | 
|  |  | 
|  | /* bus address is the same as virtual address because no MMU */ | 
|  | info->busAddress = (ptr_t)info->virtualAddress; | 
|  |  | 
|  | inst->mem_cnt += size; | 
|  | LOG_DBG("allocated %8d bytes --> %p / 0x%x. Total : %d", size_aligned, | 
|  | (void *)info->virtualAddress, info->busAddress, inst->mem_cnt); | 
|  |  | 
|  | return EWL_OK; | 
|  | } | 
|  |  | 
|  | void EWLFreeLinear(const void *instance, EWLLinearMem_t *info) | 
|  | { | 
|  | struct stm32_venc_ewl *inst = (struct stm32_venc_ewl *)instance; | 
|  |  | 
|  | __ASSERT_NO_MSG(inst != NULL); | 
|  | __ASSERT_NO_MSG(info != NULL); | 
|  |  | 
|  | /* find the pointer corresponding to the aligned buffer */ | 
|  | for (uint32_t i = 0; i < inst->total_chunks; i++) { | 
|  | if (inst->aligned_chunks[i] == info->virtualAddress) { | 
|  | if (inst->chunks[i] != NULL) { | 
|  | EWL_HEAP_ALIGNED_FREE(inst->chunks[i]); | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  | info->virtualAddress = NULL; | 
|  | info->busAddress = 0; | 
|  | info->size = 0; | 
|  | } | 
|  |  | 
|  | i32 EWLReserveHw(const void *inst) | 
|  | { | 
|  | __ASSERT_NO_MSG(inst != NULL); | 
|  |  | 
|  | return EWL_OK; | 
|  | } | 
|  |  | 
|  | void EWLReleaseHw(const void *inst) | 
|  | { | 
|  | __ASSERT_NO_MSG(inst != NULL); | 
|  | } | 
|  |  | 
|  | void *EWLmalloc(uint32_t n) | 
|  | { | 
|  | struct stm32_venc_ewl *inst = &ewl_instance; | 
|  | void *p = NULL; | 
|  |  | 
|  | p = EWL_HEAP_ALIGNED_ALLOC(n); | 
|  | if (p == NULL) { | 
|  | LOG_ERR("alloc failed for size=%d", n); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | inst->mem_cnt += n; | 
|  | LOG_DBG("%8d bytes --> %p, total : %d", n, p, inst->mem_cnt); | 
|  |  | 
|  | return p; | 
|  | } | 
|  |  | 
|  | void EWLfree(void *p) | 
|  | { | 
|  | if (p != NULL) { | 
|  | EWL_HEAP_ALIGNED_FREE(p); | 
|  | } | 
|  | } | 
|  |  | 
|  | void *EWLcalloc(uint32_t n, uint32_t s) | 
|  | { | 
|  | void *p = EWLmalloc(n * s); | 
|  |  | 
|  | if (p != NULL) { | 
|  | EWLmemset(p, 0, n * s); | 
|  | } | 
|  |  | 
|  | return p; | 
|  | } | 
|  |  | 
|  | void *EWLmemcpy(void *d, const void *s, uint32_t n) | 
|  | { | 
|  | return memcpy(d, s, (size_t)n); | 
|  | } | 
|  |  | 
|  | void *EWLmemset(void *d, i32 c, uint32_t n) | 
|  | { | 
|  | return memset(d, c, (size_t)n); | 
|  | } | 
|  |  | 
|  | int EWLmemcmp(const void *s1, const void *s2, uint32_t n) | 
|  | { | 
|  | return memcmp(s1, s2, n); | 
|  | } | 
|  |  | 
|  | i32 EWLWaitHwRdy(const void *instance, uint32_t *slicesReady) | 
|  | { | 
|  | struct stm32_venc_ewl *inst = (struct stm32_venc_ewl *)instance; | 
|  | const struct stm32_venc_config *config = inst->config; | 
|  | uint32_t ret = EWL_HW_WAIT_TIMEOUT; | 
|  | volatile uint32_t irq_stats; | 
|  | uint32_t prevSlicesReady = 0; | 
|  | k_timepoint_t timeout = sys_timepoint_calc(K_MSEC(EWL_TIMEOUT)); | 
|  | uint32_t start = sys_clock_tick_get_32(); | 
|  |  | 
|  | __ASSERT_NO_MSG(inst != NULL); | 
|  |  | 
|  | /* check how to clear IRQ flags for VENC */ | 
|  | uint32_t clrByWrite1 = EWLReadReg(inst, BASE_HWFuse2) & HWCFGIrqClearSupport; | 
|  |  | 
|  | do { | 
|  | irq_stats = sys_read32(config->reg + BASE_HEncIRQ); | 
|  | /* get the number of completed slices from ASIC registers. */ | 
|  | if (slicesReady != NULL && *slicesReady > prevSlicesReady) { | 
|  | *slicesReady = FIELD_GET(NUM_SLICES_READY_MASK, | 
|  | sys_read32(config->reg + BASE_HEncControl7)); | 
|  | } | 
|  |  | 
|  | LOG_DBG("IRQ stat = %08x", irq_stats); | 
|  |  | 
|  | uint32_t hw_handshake_status = IS_BIT_SET( | 
|  | sys_read32(config->reg + BASE_HEncInstantInput), LOW_LATENCY_HW_ITF_EN); | 
|  |  | 
|  | /* ignore the irq status of input line buffer in hw handshake mode */ | 
|  | if ((irq_stats == ASIC_STATUS_LINE_BUFFER_DONE) && (hw_handshake_status != 0UL)) { | 
|  | sys_write32(ASIC_STATUS_FUSE, config->reg + BASE_HEncIRQ); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if ((irq_stats & ASIC_STATUS_ALL) != 0UL) { | 
|  | /* clear IRQ and slice ready status */ | 
|  | uint32_t clr_stats; | 
|  |  | 
|  | irq_stats &= ~(ASIC_STATUS_SLICE_READY | ASIC_IRQ_LINE); | 
|  |  | 
|  | if (clrByWrite1 != 0UL) { | 
|  | clr_stats = ASIC_STATUS_SLICE_READY | ASIC_IRQ_LINE; | 
|  | } else { | 
|  | clr_stats = irq_stats; | 
|  | } | 
|  |  | 
|  | sys_write32(clr_stats, config->reg + BASE_HEncIRQ); | 
|  | ret = EWL_OK; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (slicesReady != NULL && *slicesReady > prevSlicesReady) { | 
|  | ret = EWL_OK; | 
|  | break; | 
|  | } | 
|  |  | 
|  | } while (!sys_timepoint_expired(timeout)); | 
|  |  | 
|  | LOG_DBG("encoding = %d ms", k_ticks_to_ms_ceil32(sys_clock_tick_get_32() - start)); | 
|  |  | 
|  | if (slicesReady != NULL) { | 
|  | LOG_DBG("slicesReady = %d", *slicesReady); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void EWLassert(bool expr, const char *str_expr, const char *file, unsigned int line) | 
|  | { | 
|  | __ASSERT(expr, "ASSERTION FAIL [%s] @ %s:%d", str_expr, file, line); | 
|  | } | 
|  |  | 
|  | /* Set CONFIG_VC8000NANOE_LOG_LEVEL_DBG to enable library tracing */ | 
|  | void EWLtrace(const char *s) | 
|  | { | 
|  | printk("%s\n", s); | 
|  | } | 
|  |  | 
|  | void EWLtraceparam(const char *fmt, const char *param, unsigned int val) | 
|  | { | 
|  | printk(fmt, param, val); | 
|  | } | 
|  |  | 
|  | static int stm32_venc_enable_clock(const struct device *dev) | 
|  | { | 
|  | const struct stm32_venc_config *config = dev->config; | 
|  | const struct device *clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); | 
|  |  | 
|  | if (!device_is_ready(clk)) { | 
|  | LOG_ERR("clock control device not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | if (clock_control_on(clk, (clock_control_subsys_t)&config->pclken) != 0) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int stm32_venc_set_fmt(const struct device *dev, struct video_format *fmt) | 
|  | { | 
|  | struct stm32_venc_data *data = dev->data; | 
|  |  | 
|  | if (fmt->type == VIDEO_BUF_TYPE_INPUT) { | 
|  | if ((fmt->pixelformat != VIDEO_PIX_FMT_NV12) && | 
|  | (fmt->pixelformat != VIDEO_PIX_FMT_RGB565)) { | 
|  | LOG_ERR("invalid input pixel format"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | fmt->pitch = fmt->width * video_bits_per_pixel(fmt->pixelformat) / BITS_PER_BYTE; | 
|  | data->in_fmt = *fmt; | 
|  | } else { | 
|  | if (fmt->pixelformat != VIDEO_PIX_FMT_H264) { | 
|  | LOG_ERR("invalid output pixel format"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | fmt->size = ROUND_UP(fmt->width * fmt->height / VENC_ESTIMATED_COMPRESSION_RATIO, | 
|  | ALIGNMENT_INCR); | 
|  |  | 
|  | data->out_fmt = *fmt; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int stm32_venc_get_fmt(const struct device *dev, struct video_format *fmt) | 
|  | { | 
|  | struct stm32_venc_data *data = dev->data; | 
|  |  | 
|  | if (fmt->type == VIDEO_BUF_TYPE_INPUT) { | 
|  | *fmt = data->in_fmt; | 
|  | } else { | 
|  | *fmt = data->out_fmt; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int encoder_prepare(struct stm32_venc_data *data) | 
|  | { | 
|  | H264EncRet ret; | 
|  | H264EncConfig cfg = {0}; | 
|  | H264EncPreProcessingCfg preproc_cfg = {0}; | 
|  | H264EncRateCtrl ratectrl_cfg = {0}; | 
|  | H264EncCodingCtrl codingctrl_cfg = {0}; | 
|  |  | 
|  | data->frame_nb = 0; | 
|  |  | 
|  | /* set config to 1 reference frame */ | 
|  | cfg.refFrameAmount = 1; | 
|  | /* frame rate */ | 
|  | cfg.frameRateDenom = 1; | 
|  | cfg.frameRateNum = VENC_DEFAULT_FRAMERATE; | 
|  | /* image resolution */ | 
|  | cfg.width = data->out_fmt.width; | 
|  | cfg.height = data->out_fmt.height; | 
|  | /* stream type */ | 
|  | cfg.streamType = H264ENC_BYTE_STREAM; | 
|  |  | 
|  | /* encoding level*/ | 
|  | cfg.level = VENC_DEFAULT_LEVEL; | 
|  | cfg.svctLevel = 0; | 
|  | cfg.viewMode = H264ENC_BASE_VIEW_SINGLE_BUFFER; | 
|  |  | 
|  | ret = H264EncInit(&cfg, &data->encoder); | 
|  | if (ret != H264ENC_OK) { | 
|  | LOG_ERR("H264EncInit error=%d", ret); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* set format conversion for preprocessing */ | 
|  | ret = H264EncGetPreProcessing(data->encoder, &preproc_cfg); | 
|  | if (ret != H264ENC_OK) { | 
|  | LOG_ERR("H264EncGetPreProcessing error=%d", ret); | 
|  | return -EIO; | 
|  | } | 
|  | preproc_cfg.inputType = to_h264pixfmt(data->in_fmt.pixelformat); | 
|  | ret = H264EncSetPreProcessing(data->encoder, &preproc_cfg); | 
|  | if (ret != H264ENC_OK) { | 
|  | LOG_ERR("H264EncSetPreProcessing error=%d", ret); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* setup coding ctrl */ | 
|  | ret = H264EncGetCodingCtrl(data->encoder, &codingctrl_cfg); | 
|  | if (ret != H264ENC_OK) { | 
|  | LOG_ERR("H264EncGetCodingCtrl error=%d", ret); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | ret = H264EncSetCodingCtrl(data->encoder, &codingctrl_cfg); | 
|  | if (ret != H264ENC_OK) { | 
|  | LOG_ERR("H264EncSetCodingCtrl error=%d", ret); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* set bit rate configuration */ | 
|  | ret = H264EncGetRateCtrl(data->encoder, &ratectrl_cfg); | 
|  | if (ret != H264ENC_OK) { | 
|  | LOG_ERR("H264EncGetRateCtrl error=%d", ret); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* Constant bitrate */ | 
|  | ratectrl_cfg.pictureRc = 0; | 
|  | ratectrl_cfg.mbRc = 0; | 
|  | ratectrl_cfg.pictureSkip = 0; | 
|  | ratectrl_cfg.hrd = 0; | 
|  | ratectrl_cfg.qpHdr = VENC_DEFAULT_QP; | 
|  | ratectrl_cfg.qpMin = ratectrl_cfg.qpHdr; | 
|  | ratectrl_cfg.qpMax = ratectrl_cfg.qpHdr; | 
|  |  | 
|  | ret = H264EncSetRateCtrl(data->encoder, &ratectrl_cfg); | 
|  | if (ret != H264ENC_OK) { | 
|  | LOG_ERR("H264EncSetRateCtrl error=%d", ret); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int encoder_start(struct stm32_venc_data *data, struct video_buffer *output) | 
|  | { | 
|  | H264EncRet ret; | 
|  | H264EncIn encIn = {0}; | 
|  | H264EncOut encOut = {0}; | 
|  |  | 
|  | encIn.pOutBuf = (uint32_t *)output->buffer; | 
|  | encIn.busOutBuf = (uint32_t)encIn.pOutBuf; | 
|  | encIn.outBufSize = output->size; | 
|  |  | 
|  | /* create stream */ | 
|  | ret = H264EncStrmStart(data->encoder, &encIn, &encOut); | 
|  | if (ret != H264ENC_OK) { | 
|  | LOG_ERR("H264EncStrmStart error=%d", ret); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | output->bytesused = encOut.streamSize; | 
|  | LOG_DBG("SPS/PPS generated, size= %d", output->bytesused); | 
|  |  | 
|  | data->resync = true; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int encoder_end(struct stm32_venc_data *data) | 
|  | { | 
|  | H264EncIn encIn = {0}; | 
|  | H264EncOut encOut = {0}; | 
|  |  | 
|  | if (data->encoder != NULL) { | 
|  | H264EncStrmEnd(data->encoder, &encIn, &encOut); | 
|  | data->encoder = NULL; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int encode_frame(struct stm32_venc_data *data) | 
|  | { | 
|  | int ret = H264ENC_FRAME_READY; | 
|  | struct video_buffer *input; | 
|  | struct video_buffer *output; | 
|  | H264EncIn encIn = {0}; | 
|  | H264EncOut encOut = {0}; | 
|  |  | 
|  | if (k_fifo_is_empty(&data->in_fifo_in) || k_fifo_is_empty(&data->out_fifo_in)) { | 
|  | /* Encoding deferred to next buffer queueing */ | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | input = k_fifo_get(&data->in_fifo_in, K_NO_WAIT); | 
|  | output = k_fifo_get(&data->out_fifo_in, K_NO_WAIT); | 
|  |  | 
|  | if (data->encoder == NULL) { | 
|  | ret = encoder_prepare(data); | 
|  | if (ret) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | ret = encoder_start(data, output); | 
|  |  | 
|  | /* | 
|  | * Output buffer is now filled with SPS/PPS header, return it to application. | 
|  | * Input buffer is dropped to not introduce latency. | 
|  | */ | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* one key frame every seconds */ | 
|  | if ((data->frame_nb % VENC_DEFAULT_FRAMERATE) == 0 || data->resync) { | 
|  | /* if frame is the first or resync needed: set as intra coded */ | 
|  | encIn.codingType = H264ENC_INTRA_FRAME; | 
|  | } else { | 
|  | /* if there was a frame previously, set as predicted */ | 
|  | encIn.timeIncrement = 1; | 
|  | encIn.codingType = H264ENC_PREDICTED_FRAME; | 
|  | } | 
|  |  | 
|  | encIn.ipf = H264ENC_REFERENCE_AND_REFRESH; | 
|  | encIn.ltrf = H264ENC_REFERENCE; | 
|  |  | 
|  | /* set input buffers to structures */ | 
|  | encIn.busLuma = (ptr_t)input->buffer; | 
|  | encIn.busChromaU = (ptr_t)encIn.busLuma + data->in_fmt.width * data->in_fmt.height; | 
|  |  | 
|  | encIn.pOutBuf = (uint32_t *)output->buffer; | 
|  | encIn.busOutBuf = (uint32_t)encIn.pOutBuf; | 
|  | encIn.outBufSize = output->size; | 
|  | encOut.streamSize = 0; | 
|  |  | 
|  | ret = H264EncStrmEncode(data->encoder, &encIn, &encOut, NULL, NULL, NULL); | 
|  | output->bytesused = encOut.streamSize; | 
|  | LOG_DBG("output=%p, encOut.streamSize=%d", output, encOut.streamSize); | 
|  |  | 
|  | switch (ret) { | 
|  | case H264ENC_FRAME_READY: | 
|  | /* save stream */ | 
|  | if (encOut.streamSize == 0) { | 
|  | /* Nothing encoded */ | 
|  | data->resync = true; | 
|  | goto out; | 
|  | } | 
|  | output->bytesused = encOut.streamSize; | 
|  | break; | 
|  | case H264ENC_FUSE_ERROR: | 
|  | LOG_ERR("H264EncStrmEncode error=%d", ret); | 
|  |  | 
|  | LOG_ERR("DCMIPP and VENC desync at frame %d, restart the video", data->frame_nb); | 
|  | encoder_end(data); | 
|  |  | 
|  | ret = encoder_start(data, output); | 
|  | if (ret) { | 
|  | goto out; | 
|  | } | 
|  | break; | 
|  | default: | 
|  | LOG_ERR("H264EncStrmEncode error=%d", ret); | 
|  | LOG_ERR("error encoding frame %d", data->frame_nb); | 
|  |  | 
|  | encoder_end(data); | 
|  |  | 
|  | ret = encoder_start(data, output); | 
|  | if (ret) { | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | data->resync = true; | 
|  |  | 
|  | ret = -EIO; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | data->frame_nb++; | 
|  |  | 
|  | out: | 
|  | k_fifo_put(&data->in_fifo_out, input); | 
|  | k_fifo_put(&data->out_fifo_out, output); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int stm32_venc_set_stream(const struct device *dev, bool enable, enum video_buf_type type) | 
|  | { | 
|  | struct stm32_venc_data *data = dev->data; | 
|  |  | 
|  | ARG_UNUSED(type); | 
|  |  | 
|  | if (!enable) { | 
|  | /* Stop VENC */ | 
|  | encoder_end(data); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int stm32_venc_enqueue(const struct device *dev, struct video_buffer *vbuf) | 
|  | { | 
|  | struct stm32_venc_data *data = dev->data; | 
|  | int ret = 0; | 
|  |  | 
|  | k_mutex_lock(&data->lock, K_FOREVER); | 
|  |  | 
|  | if (vbuf->type == VIDEO_BUF_TYPE_INPUT) { | 
|  | k_fifo_put(&data->in_fifo_in, vbuf); | 
|  | } else { | 
|  | k_fifo_put(&data->out_fifo_in, vbuf); | 
|  | } | 
|  |  | 
|  | ret = encode_frame(data); | 
|  |  | 
|  | k_mutex_unlock(&data->lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int stm32_venc_dequeue(const struct device *dev, struct video_buffer **vbuf, | 
|  | k_timeout_t timeout) | 
|  | { | 
|  | struct stm32_venc_data *data = dev->data; | 
|  | int ret = 0; | 
|  |  | 
|  | k_mutex_lock(&data->lock, K_FOREVER); | 
|  |  | 
|  | if ((*vbuf)->type == VIDEO_BUF_TYPE_INPUT) { | 
|  | *vbuf = k_fifo_get(&data->in_fifo_out, timeout); | 
|  | } else { | 
|  | *vbuf = k_fifo_get(&data->out_fifo_out, timeout); | 
|  | } | 
|  |  | 
|  | if (*vbuf == NULL) { | 
|  | ret = -EAGAIN; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | out: | 
|  | k_mutex_unlock(&data->lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ISR_DIRECT_DECLARE(stm32_venc_isr) | 
|  | { | 
|  | struct stm32_venc_ewl *inst = &ewl_instance; | 
|  | const struct stm32_venc_config *config = inst->config; | 
|  | uint32_t hw_handshake_status = | 
|  | IS_BIT_SET(sys_read32(config->reg + BASE_HEncInstantInput), LOW_LATENCY_HW_ITF_EN); | 
|  | uint32_t irq_status = sys_read32(config->reg + BASE_HEncIRQ); | 
|  |  | 
|  | inst->irq_status = irq_status; | 
|  | inst->irq_cnt++; | 
|  |  | 
|  | if (hw_handshake_status == 0 && (irq_status & ASIC_STATUS_FUSE) != 0) { | 
|  | sys_write32(ASIC_STATUS_FUSE | ASIC_IRQ_LINE, config->reg + BASE_HEncIRQ); | 
|  | /* read back the IRQ status to update its value */ | 
|  | irq_status = sys_read32(config->reg + BASE_HEncIRQ); | 
|  | } | 
|  |  | 
|  | if (irq_status != 0U) { | 
|  | /* status flag is raised, | 
|  | * clear the ones that the IRQ needs to clear | 
|  | * and signal to EWLWaitHwRdy | 
|  | */ | 
|  | sys_write32(ASIC_STATUS_SLICE_READY | ASIC_IRQ_LINE, config->reg + BASE_HEncIRQ); | 
|  | } | 
|  |  | 
|  | k_sem_give(&inst->complete); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #define VENC_FORMAT_CAP(pixfmt)                \ | 
|  | {                                      \ | 
|  | .pixelformat = pixfmt,         \ | 
|  | .width_min = 48,               \ | 
|  | .width_max = 1920,             \ | 
|  | .height_min = 48,              \ | 
|  | .height_max = 1088,            \ | 
|  | .width_step = 16,              \ | 
|  | .height_step = 16,             \ | 
|  | } | 
|  |  | 
|  | static const struct video_format_cap in_fmts[] = { | 
|  | VENC_FORMAT_CAP(VIDEO_PIX_FMT_NV12), | 
|  | VENC_FORMAT_CAP(VIDEO_PIX_FMT_RGB565), | 
|  | {0}, | 
|  | }; | 
|  |  | 
|  | static const struct video_format_cap out_fmts[] = { | 
|  | VENC_FORMAT_CAP(VIDEO_PIX_FMT_H264), | 
|  | {0}, | 
|  | }; | 
|  |  | 
|  | static int stm32_venc_get_caps(const struct device *dev, struct video_caps *caps) | 
|  | { | 
|  | if (caps->type == VIDEO_BUF_TYPE_INPUT) { | 
|  | caps->format_caps = in_fmts; | 
|  | } else { | 
|  | caps->format_caps = out_fmts; | 
|  | } | 
|  |  | 
|  | /* VENC produces full frames */ | 
|  | caps->min_vbuf_count = 1; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static DEVICE_API(video, stm32_venc_driver_api) = { | 
|  | .set_format = stm32_venc_set_fmt, | 
|  | .get_format = stm32_venc_get_fmt, | 
|  | .set_stream = stm32_venc_set_stream, | 
|  | .enqueue = stm32_venc_enqueue, | 
|  | .dequeue = stm32_venc_dequeue, | 
|  | .get_caps = stm32_venc_get_caps, | 
|  | }; | 
|  |  | 
|  | static void stm32_venc_irq_config_func(const struct device *dev) | 
|  | { | 
|  | IRQ_DIRECT_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), stm32_venc_isr, 0); | 
|  | irq_enable(DT_INST_IRQN(0)); | 
|  | } | 
|  |  | 
|  | static struct stm32_venc_data stm32_venc_data_0 = {}; | 
|  |  | 
|  | static const struct stm32_venc_config stm32_venc_config_0 = { | 
|  | .reg = DT_INST_REG_ADDR(0), | 
|  | .pclken = {.bus = DT_INST_CLOCKS_CELL(0, bus), .enr = DT_INST_CLOCKS_CELL(0, bits)}, | 
|  | .reset = RESET_DT_SPEC_INST_GET_BY_IDX(0, 0), | 
|  | .irq_config = stm32_venc_irq_config_func, | 
|  | }; | 
|  |  | 
|  | static void RISAF_Config(void) | 
|  | { | 
|  | /* Define and initialize the master configuration structure */ | 
|  | RIMC_MasterConfig_t RIMC_master = {0}; | 
|  |  | 
|  | /* Enable the clock for the RIFSC (RIF Security Controller) */ | 
|  | __HAL_RCC_RIFSC_CLK_ENABLE(); | 
|  |  | 
|  | RIMC_master.MasterCID = RIF_CID_1; | 
|  | RIMC_master.SecPriv = RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV; | 
|  |  | 
|  | /* Configure the master attributes for the video encoder peripheral (VENC) */ | 
|  | HAL_RIF_RIMC_ConfigMasterAttributes(RIF_MASTER_INDEX_VENC, &RIMC_master); | 
|  |  | 
|  | /* Set the secure and privileged attributes for the VENC as a slave */ | 
|  | HAL_RIF_RISC_SetSlaveSecureAttributes(RIF_RISC_PERIPH_INDEX_VENC, | 
|  | RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV); | 
|  | } | 
|  |  | 
|  | static int stm32_venc_init(const struct device *dev) | 
|  | { | 
|  | const struct stm32_venc_config *config = dev->config; | 
|  | struct stm32_venc_data *data = dev->data; | 
|  | int err; | 
|  |  | 
|  | /* Enable VENC clock */ | 
|  | err = stm32_venc_enable_clock(dev); | 
|  | if (err < 0) { | 
|  | LOG_ERR("clock enabling failed."); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | /* Reset VENC */ | 
|  | if (!device_is_ready(config->reset.dev)) { | 
|  | LOG_ERR("reset controller not ready"); | 
|  | return -ENODEV; | 
|  | } | 
|  | reset_line_toggle_dt(&config->reset); | 
|  |  | 
|  | data->dev = dev; | 
|  | k_mutex_init(&data->lock); | 
|  | k_fifo_init(&data->in_fifo_in); | 
|  | k_fifo_init(&data->in_fifo_out); | 
|  | k_fifo_init(&data->out_fifo_in); | 
|  | k_fifo_init(&data->out_fifo_out); | 
|  |  | 
|  | /* Run IRQ init */ | 
|  | config->irq_config(dev); | 
|  |  | 
|  | RISAF_Config(); | 
|  |  | 
|  | LOG_DBG("CPU frequency    : %d", HAL_RCC_GetCpuClockFreq() / 1000000); | 
|  | LOG_DBG("sysclk frequency : %d", HAL_RCC_GetSysClockFreq() / 1000000); | 
|  | LOG_DBG("pclk5 frequency  : %d", HAL_RCC_GetPCLK5Freq() / 1000000); | 
|  |  | 
|  | /* default input */ | 
|  | data->in_fmt.width = VENC_DEFAULT_WIDTH; | 
|  | data->in_fmt.height = VENC_DEFAULT_HEIGHT; | 
|  | data->in_fmt.pixelformat = VENC_DEFAULT_IN_FMT; | 
|  | data->in_fmt.pitch = data->in_fmt.width; | 
|  |  | 
|  | /* default output */ | 
|  | data->out_fmt.width = VENC_DEFAULT_WIDTH; | 
|  | data->out_fmt.height = VENC_DEFAULT_HEIGHT; | 
|  | data->out_fmt.pixelformat = VENC_DEFAULT_OUT_FMT; | 
|  |  | 
|  | /* store config for register accesses */ | 
|  | ewl_instance.config = config; | 
|  |  | 
|  | LOG_DBG("%s inited", dev->name); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | DEVICE_DT_INST_DEFINE(0, &stm32_venc_init, NULL, &stm32_venc_data_0, &stm32_venc_config_0, | 
|  | POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &stm32_venc_driver_api); |