kendryte-freertos-sdk/lib/bsp/device/dvp.c

234 lines
6.2 KiB
C

/* Copyright 2018 Canaan Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <FreeRTOS.h>
#include <driver.h>
#include <dvp.h>
#include <fpioa.h>
#include <hal.h>
#include <io.h>
#include <plic.h>
#include <semphr.h>
#include <stdio.h>
#include <sysctl.h>
#include "fpioa_cfg.h"
#define COMMON_ENTRY \
dvp_data* data = (dvp_data*)userdata; \
volatile struct dvp_t* dvp = (volatile struct dvp_t*)data->base_addr; \
(void)dvp;
typedef struct
{
enum sysctl_clock_e clock;
uintptr_t base_addr;
struct
{
dvp_on_frame_event frame_event_callback;
void* frame_event_callback_data;
size_t width;
size_t height;
};
} dvp_data;
static void dvp_frame_event_isr(void* userdata);
static void dvp_install(void* userdata)
{
COMMON_ENTRY;
sysctl_clock_enable(data->clock);
pic_set_irq_handler(IRQN_DVP_INTERRUPT, dvp_frame_event_isr, userdata);
pic_set_irq_priority(IRQN_DVP_INTERRUPT, 1);
}
static int dvp_open(void* userdata)
{
return 1;
}
static void dvp_close(void* userdata)
{
}
static void dvp_config(size_t width, size_t height, int auto_enable, void* userdata)
{
COMMON_ENTRY;
configASSERT(width % 8 == 0 && width && height);
uint32_t dvp_cfg = dvp->dvp_cfg;
if (width / 8 % 4 == 0)
{
dvp_cfg |= DVP_CFG_BURST_SIZE_4BEATS;
set_bit_mask(&dvp_cfg, DVP_AXI_GM_MLEN_MASK | DVP_CFG_HREF_BURST_NUM_MASK, DVP_AXI_GM_MLEN_4BYTE | DVP_CFG_HREF_BURST_NUM(width / 8 / 4));
}
else
{
dvp_cfg &= (~DVP_CFG_BURST_SIZE_4BEATS);
set_bit_mask(&dvp_cfg, DVP_AXI_GM_MLEN_MASK, DVP_AXI_GM_MLEN_1BYTE | DVP_CFG_HREF_BURST_NUM(width / 8));
}
set_bit_mask(&dvp_cfg, DVP_CFG_LINE_NUM_MASK, DVP_CFG_LINE_NUM(height));
if (auto_enable)
dvp_cfg |= DVP_CFG_AUTO_ENABLE;
else
dvp_cfg &= ~DVP_CFG_AUTO_ENABLE;
dvp->dvp_cfg = dvp_cfg;
dvp->cmos_cfg |= DVP_CMOS_CLK_DIV(0) | DVP_CMOS_CLK_ENABLE;
data->width = width;
data->height = height;
}
static void dvp_enable_frame(void* userdata)
{
COMMON_ENTRY;
dvp->sts = DVP_STS_DVP_EN | DVP_STS_DVP_EN_WE;
}
static void dvp_set_signal(dvp_signal_type type, int value, void* userdata)
{
COMMON_ENTRY;
switch (type)
{
case DVP_SIG_POWER_DOWN:
if (value)
dvp->cmos_cfg |= DVP_CMOS_POWER_DOWN;
else
dvp->cmos_cfg &= ~DVP_CMOS_POWER_DOWN;
break;
case DVP_SIG_RESET:
if (value)
dvp->cmos_cfg |= DVP_CMOS_RESET;
else
dvp->cmos_cfg &= ~DVP_CMOS_RESET;
break;
default:
configASSERT(!"Invalid signal type.");
break;
}
}
static void dvp_set_output_enable(size_t index, int enable, void* userdata)
{
COMMON_ENTRY;
configASSERT(index < 2);
if (index == 0)
{
if (enable)
dvp->dvp_cfg |= DVP_CFG_AI_OUTPUT_ENABLE;
else
dvp->dvp_cfg &= ~DVP_CFG_AI_OUTPUT_ENABLE;
}
else
{
if (enable)
dvp->dvp_cfg |= DVP_CFG_DISPLAY_OUTPUT_ENABLE;
else
dvp->dvp_cfg &= ~DVP_CFG_DISPLAY_OUTPUT_ENABLE;
}
}
static void dvp_set_output_attributes(size_t index, video_format format, void* output_buffer, void* userdata)
{
COMMON_ENTRY;
configASSERT(index < 2);
if (index == 0)
{
configASSERT(format == VIDEO_FMT_RGB24Planar);
uintptr_t buffer_addr = (uintptr_t)output_buffer;
size_t planar_size = data->width * data->height;
dvp->r_addr = buffer_addr;
dvp->g_addr = buffer_addr + planar_size;
dvp->b_addr = buffer_addr + planar_size * 2;
}
else
{
configASSERT(format == VIDEO_FMT_RGB565);
dvp->rgb_addr = (uintptr_t)output_buffer;
}
}
static void dvp_frame_event_isr(void* userdata)
{
COMMON_ENTRY;
if (dvp->sts & DVP_STS_FRAME_START)
{
dvp_on_frame_event callback;
if ((callback = data->frame_event_callback))
callback(VIDEO_FE_BEGIN, data->frame_event_callback_data);
dvp->sts |= DVP_STS_FRAME_START | DVP_STS_FRAME_START_WE;
}
if (dvp->sts & DVP_STS_FRAME_FINISH)
{
dvp_on_frame_event callback;
if ((callback = data->frame_event_callback))
callback(VIDEO_FE_END, data->frame_event_callback_data);
dvp->sts |= DVP_STS_FRAME_FINISH | DVP_STS_FRAME_FINISH_WE;
}
}
static void dvp_set_frame_event_enable(video_frame_event event, int enable, void* userdata)
{
COMMON_ENTRY;
switch (event)
{
case VIDEO_FE_BEGIN:
if (enable)
{
dvp->sts |= DVP_STS_FRAME_START | DVP_STS_FRAME_START_WE;
dvp->dvp_cfg |= DVP_CFG_START_INT_ENABLE;
}
else
{
dvp->dvp_cfg &= ~DVP_CFG_START_INT_ENABLE;
}
break;
case VIDEO_FE_END:
if (enable)
{
dvp->sts |= DVP_STS_FRAME_FINISH | DVP_STS_FRAME_FINISH_WE;
dvp->dvp_cfg |= DVP_CFG_FINISH_INT_ENABLE;
}
else
{
dvp->dvp_cfg &= ~DVP_CFG_FINISH_INT_ENABLE;
}
break;
default:
configASSERT(!"Invalid event.");
break;
}
pic_set_irq_enable(IRQN_DVP_INTERRUPT, 1);
}
static void dvp_set_on_frame_event(dvp_on_frame_event callback, void* callback_data, void* userdata)
{
COMMON_ENTRY;
data->frame_event_callback_data = callback_data;
data->frame_event_callback = callback;
}
static dvp_data dev0_data = {SYSCTL_CLOCK_DVP, DVP_BASE_ADDR, {0}};
const dvp_driver_t g_dvp_driver_dvp0 = {{&dev0_data, dvp_install, dvp_open, dvp_close}, 2, dvp_config, dvp_enable_frame, dvp_set_signal, dvp_set_output_enable, dvp_set_output_attributes, dvp_set_frame_event_enable, dvp_set_on_frame_event};