mirror of
https://gitee.com/Vancouver2017/luban-lite-t3e-pro.git
synced 2025-12-14 18:38:55 +00:00
558 lines
14 KiB
C
558 lines
14 KiB
C
/*
|
|
* Copyright (c) 2022-2024, ArtInChip Technology Co., Ltd
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Authors: matteo <duanmt@artinchip.com>
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include "aic_core.h"
|
|
#include "aic_list.h"
|
|
#include "aic_hal_clk.h"
|
|
#include "mpp_types.h"
|
|
|
|
#include "hal_dvp.h"
|
|
#include "drv_dvp.h"
|
|
|
|
#define DVP_FIRST_BUF 0
|
|
#define BUF_IS_INVALID(index) (((index) < 0) || ((index) >= VIN_MAX_BUF_NUM))
|
|
|
|
struct aic_dvp g_dvp = {0};
|
|
static u32 g_dvp_full_cnt = 0;
|
|
|
|
static const struct {
|
|
u32 fmt;
|
|
enum dvp_input_yuv_seq dvp;
|
|
} aic_dvp_in_fmt[] = {
|
|
{MEDIA_BUS_FMT_YUYV8_2X8, DVP_YUV_DATA_SEQ_YUYV},
|
|
{MEDIA_BUS_FMT_YVYU8_2X8, DVP_YUV_DATA_SEQ_YVYU},
|
|
{MEDIA_BUS_FMT_UYVY8_2X8, DVP_YUV_DATA_SEQ_UYVY},
|
|
{MEDIA_BUS_FMT_VYUY8_2X8, DVP_YUV_DATA_SEQ_VYUY},
|
|
};
|
|
|
|
static const struct {
|
|
enum mpp_pixel_format pixelformat;
|
|
enum dvp_output dvp;
|
|
} aic_dvp_out_fmt[] = {
|
|
{MPP_FMT_NV16, DVP_OUT_YUV422_COMBINED_NV16},
|
|
{MPP_FMT_NV12, DVP_OUT_YUV420_COMBINED_NV12},
|
|
// TODO: Add RAW_PASSTHROUGH
|
|
};
|
|
|
|
static int aic_dvp_out_fmt_valid(u32 pixelformat)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(aic_dvp_out_fmt); i++) {
|
|
if (aic_dvp_out_fmt[i].pixelformat == pixelformat)
|
|
return aic_dvp_out_fmt[i].dvp;
|
|
}
|
|
pr_err("Invalid pixelformat: 0x%x\n", pixelformat);
|
|
return -1;
|
|
}
|
|
|
|
static int aic_dvp_in_fmt_valid(u32 fmt)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(aic_dvp_in_fmt); i++) {
|
|
if (aic_dvp_in_fmt[i].fmt == fmt)
|
|
return aic_dvp_in_fmt[i].dvp;
|
|
}
|
|
|
|
pr_err("Invalid input format: 0x%x\n", fmt);
|
|
return -1;
|
|
}
|
|
|
|
int aic_dvp_set_in_fmt(struct mpp_video_fmt *fmt)
|
|
{
|
|
int ret = 0;
|
|
struct aic_dvp_config *cfg = &g_dvp.cfg;
|
|
|
|
ret = aic_dvp_in_fmt_valid(fmt->code);
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
cfg->input_seq = (enum dvp_input_yuv_seq)ret;
|
|
|
|
if (fmt->bus_type == MEDIA_BUS_BT656)
|
|
cfg->input = DVP_IN_BT656;
|
|
else
|
|
cfg->input = DVP_IN_YUV422;
|
|
|
|
#ifdef AIC_USING_CAMERA_OV5640
|
|
/* Should inverse the HSYNC signal of OV5640 */
|
|
if (fmt->flags & MEDIA_SIGNAL_HSYNC_ACTIVE_HIGH)
|
|
cfg->flags = (fmt->flags & ~MEDIA_SIGNAL_HSYNC_ACTIVE_HIGH)
|
|
| MEDIA_SIGNAL_HSYNC_ACTIVE_LOW;
|
|
else
|
|
cfg->flags = (fmt->flags & ~MEDIA_SIGNAL_HSYNC_ACTIVE_LOW)
|
|
| MEDIA_SIGNAL_HSYNC_ACTIVE_HIGH;
|
|
#else
|
|
cfg->flags = fmt->flags;
|
|
#endif
|
|
|
|
if (fmt->flags & MEDIA_SIGNAL_INTERLACED_MODE)
|
|
cfg->interlaced = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void _aic_dvp_try_fmt(struct dvp_out_fmt *pix)
|
|
{
|
|
int ret;
|
|
unsigned int i;
|
|
|
|
ret = aic_dvp_out_fmt_valid(pix->pixelformat);
|
|
if (ret < 0)
|
|
return;
|
|
|
|
pix->num_planes = DVP_PLANE_NUM;
|
|
for (i = 0; i < DVP_PLANE_NUM; i++) {
|
|
pix->plane_fmt[i].bytesperline = ALIGN_UP(pix->width, 8);
|
|
pix->plane_fmt[i].sizeimage = ALIGN_UP(pix->plane_fmt[i].bytesperline * pix->height, 8);
|
|
if ((ret == DVP_OUT_YUV420_COMBINED_NV12) && (i > 0))
|
|
pix->plane_fmt[i].sizeimage >>= 1;
|
|
}
|
|
}
|
|
|
|
int aic_dvp_set_out_fmt(struct dvp_out_fmt *fmt)
|
|
{
|
|
int i;
|
|
|
|
_aic_dvp_try_fmt(fmt);
|
|
g_dvp.fmt = *fmt;
|
|
|
|
/* Save the configuration for DVP controller */
|
|
g_dvp.cfg.output = aic_dvp_out_fmt_valid(g_dvp.fmt.pixelformat);
|
|
g_dvp.cfg.width = g_dvp.fmt.width;
|
|
g_dvp.cfg.height = g_dvp.fmt.height;
|
|
for (i = 0; i < DVP_PLANE_NUM; i++) {
|
|
g_dvp.cfg.stride[i] = g_dvp.fmt.plane_fmt[i].bytesperline;
|
|
g_dvp.cfg.sizeimage[i] = g_dvp.fmt.plane_fmt[i].sizeimage;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int aic_dvp_stream_on(void)
|
|
{
|
|
return vin_vb_stream_on(&g_dvp.queue);
|
|
}
|
|
|
|
int aic_dvp_stream_off(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = vin_vb_stream_off(&g_dvp.queue);
|
|
|
|
INIT_LIST_HEAD(&g_dvp.active_list);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int aic_dvp_req_buf(char *buf, u32 size, struct vin_video_buf *vbuf)
|
|
{
|
|
struct aic_dvp_config *cfg = &g_dvp.cfg;
|
|
int i;
|
|
|
|
if (!vbuf) {
|
|
pr_err("Invalid parameter\n");
|
|
return -1;
|
|
}
|
|
|
|
memset(vbuf, 0, sizeof(struct vin_video_buf));
|
|
vbuf->num_planes = DVP_PLANE_NUM;
|
|
for (i = 0; i < DVP_PLANE_NUM; i++)
|
|
vbuf->planes[i].len = cfg->sizeimage[i];
|
|
|
|
return vin_vb_req_buf(&g_dvp.queue, buf, size, vbuf);
|
|
}
|
|
|
|
int aic_dvp_q_buf(u32 index)
|
|
{
|
|
if (index >= g_dvp.queue.num_buffers) {
|
|
pr_err("Invalid index out of range: %d\n", index);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return vin_vb_q_buf(&g_dvp.queue, index);
|
|
}
|
|
|
|
int aic_dvp_dq_buf(u32 *pindex)
|
|
{
|
|
if (pindex == NULL) {
|
|
pr_err("Invalid parameter\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return vin_vb_dq_buf(&g_dvp.queue, pindex);
|
|
}
|
|
|
|
static int aic_dvp_buf_reload(struct aic_dvp *dvp, struct vb_buffer *buf)
|
|
{
|
|
buf->hw_using = 1;
|
|
pr_debug("Set %d buf 0x%x-0x%x to register\n", buf->index,
|
|
(long)buf->planes[0].buf, (long)buf->planes[1].buf);
|
|
hal_dvp_update_buf_addr(buf->planes[0].buf, buf->planes[1].buf, 0);
|
|
hal_dvp_update_ctl();
|
|
return 0;
|
|
}
|
|
|
|
static void aic_dvp_buf_mark_done(struct aic_dvp *dvp,
|
|
struct vb_buffer *vb,
|
|
unsigned int sequence, u32 err)
|
|
{
|
|
if (err)
|
|
vin_vb_buffer_done(vb, VB_BUF_STATE_ERROR);
|
|
else
|
|
vin_vb_buffer_done(vb, VB_BUF_STATE_DONE);
|
|
vb->hw_using = 0;
|
|
}
|
|
|
|
static int aic_dvp_top_field_done(struct aic_dvp *dvp, u32 err)
|
|
{
|
|
struct vb_buffer *cur_buf = NULL;
|
|
|
|
if (list_empty(&dvp->active_list)) {
|
|
pr_err("No buf available!\n");
|
|
return 0;
|
|
}
|
|
|
|
cur_buf = list_first_entry(&dvp->active_list, struct vb_buffer, active_entry);
|
|
pr_debug("cur: index %d, dvp_using %d\n",
|
|
cur_buf->vb.vb2_buf.index, cur_buf->dvp_using);
|
|
if (BUF_IS_INVALID(cur_buf->index)) {
|
|
pr_err("Invalid buf %d\n", cur_buf->index);
|
|
return -1;
|
|
}
|
|
|
|
pr_debug("Add offset %d of cur buf %d", dvp->cfg.stride[0], cur_buf->index);
|
|
|
|
hal_dvp_update_buf_addr(cur_buf->planes[0].buf, cur_buf->planes[1].buf,
|
|
dvp->cfg.stride[0]);
|
|
hal_dvp_update_ctl();
|
|
dvp->sequence++;
|
|
return 0;
|
|
}
|
|
|
|
static int aic_dvp_frame_done(struct aic_dvp *dvp, int err)
|
|
{
|
|
struct vb_buffer *cur_buf = NULL;
|
|
|
|
if (list_empty(&dvp->active_list)) {
|
|
pr_err("No buf available!\n");
|
|
return 0;
|
|
}
|
|
|
|
cur_buf = list_first_entry(&dvp->active_list, struct vb_buffer, active_entry);
|
|
pr_debug("cur: index %d, hw_using %d, err %d\n",
|
|
cur_buf->index, cur_buf->hw_using, err);
|
|
if (BUF_IS_INVALID(cur_buf->index)) {
|
|
pr_err("Invalid buf %d\n", cur_buf->index);
|
|
return -1;
|
|
}
|
|
|
|
/* If cur_buf is a new one queued, DVP should use it first */
|
|
if (!cur_buf->hw_using) {
|
|
pr_debug("Good! Buf %d is free again\n", cur_buf->index);
|
|
aic_dvp_buf_reload(dvp, cur_buf);
|
|
dvp->sequence++;
|
|
return 0;
|
|
}
|
|
|
|
/* Release the current buffer from DVP driver */
|
|
list_del(&cur_buf->active_entry);
|
|
aic_dvp_buf_mark_done(dvp, cur_buf, dvp->sequence, err);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aic_dvp_update_addr(struct aic_dvp *dvp)
|
|
{
|
|
struct vb_buffer *cur_buf;
|
|
struct vb_buffer *next_buf;
|
|
|
|
if (!dvp->streaming)
|
|
return 0;
|
|
|
|
if (list_empty(&dvp->active_list)) {
|
|
pr_warn("No buf available!\n");
|
|
return -1;
|
|
}
|
|
|
|
cur_buf = list_first_entry(&dvp->active_list, struct vb_buffer, active_entry);
|
|
pr_debug("cur: index %d, hw_using %d\n", cur_buf->index, cur_buf->hw_using);
|
|
if (BUF_IS_INVALID(cur_buf->index)) {
|
|
pr_err("Cur buf %d is invalid\n", cur_buf->index);
|
|
return -1;
|
|
}
|
|
|
|
if (!cur_buf->hw_using) {
|
|
aic_dvp_buf_reload(dvp, cur_buf);
|
|
dvp->sequence++;
|
|
return 0;
|
|
}
|
|
|
|
if (cur_buf == list_last_entry(&dvp->active_list, struct vb_buffer,
|
|
active_entry)) {
|
|
pr_warn("It's the last buf!\n");
|
|
return 0;
|
|
}
|
|
|
|
next_buf = list_next_entry(cur_buf, active_entry);
|
|
if (!next_buf || BUF_IS_INVALID(next_buf->index)) {
|
|
pr_err("Next buf is invalid\n");
|
|
return -1;
|
|
}
|
|
pr_debug("Next: index %d, hw_using %d\n",
|
|
next_buf->index, next_buf->hw_using);
|
|
|
|
/* DVP can use the next buf as output. */
|
|
if (!next_buf->hw_using) {
|
|
aic_dvp_buf_reload(dvp, next_buf);
|
|
dvp->sequence++;
|
|
} else {
|
|
/* This should not happened! */
|
|
pr_warn("Weird! DVP is using two buf %d & %d!\n",
|
|
cur_buf->index, next_buf->index);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void aic_dvp_buf_queue(struct vb_buffer *vb)
|
|
{
|
|
pr_debug("Queue buf %d\n", vb->index);
|
|
|
|
list_add_tail(&vb->active_entry, &g_dvp.active_list);
|
|
vb->hw_using = 0;
|
|
}
|
|
|
|
static void aic_dvp_reclaim_all_buffers(struct aic_dvp *dvp,
|
|
enum vb_buffer_state state)
|
|
{
|
|
struct vb_buffer *vb, *node;
|
|
|
|
rt_base_t level = rt_hw_interrupt_disable();
|
|
|
|
list_for_each_entry_safe(vb, node, &dvp->active_list, active_entry) {
|
|
vin_vb_buffer_done(vb, state);
|
|
list_del(&vb->active_entry);
|
|
}
|
|
|
|
rt_hw_interrupt_enable(level);
|
|
}
|
|
|
|
static int aic_dvp_start_streaming(struct vb_queue *q)
|
|
{
|
|
struct vb_buffer *vb;
|
|
int ret = 0;
|
|
struct aic_dvp *dvp = &g_dvp;
|
|
|
|
pr_debug("Starting capture\n");
|
|
|
|
dvp->sequence = 0;
|
|
hal_dvp_field_tag_clr();
|
|
|
|
hal_dvp_set_cfg(&dvp->cfg);
|
|
hal_dvp_set_pol(dvp->cfg.flags);
|
|
hal_dvp_record_mode();
|
|
|
|
hal_dvp_clr_int();
|
|
hal_dvp_enable_int(&dvp->cfg, 1);
|
|
|
|
/* Prepare our active_uffers in hardware */
|
|
vb = list_first_entry(&dvp->active_list, struct vb_buffer, active_entry);
|
|
ret = aic_dvp_buf_reload(dvp, vb);
|
|
if (ret)
|
|
goto err_disable_pipeline;
|
|
|
|
hal_dvp_capture_start();
|
|
hal_dvp_update_ctl();
|
|
|
|
dvp->streaming = 1;
|
|
return 0;
|
|
|
|
err_disable_pipeline:
|
|
aic_dvp_reclaim_all_buffers(dvp, VB_BUF_STATE_QUEUED);
|
|
return ret;
|
|
}
|
|
|
|
static void aic_dvp_stop_streaming(struct vb_queue *q)
|
|
{
|
|
struct aic_dvp *dvp = &g_dvp;
|
|
|
|
pr_debug("Stopping capture\n");
|
|
|
|
hal_dvp_capture_stop();
|
|
hal_dvp_enable_int(&dvp->cfg, 0);
|
|
hal_dvp_update_ctl();
|
|
|
|
/* Release all active buffers */
|
|
aic_dvp_reclaim_all_buffers(dvp, VB_BUF_STATE_ERROR);
|
|
dvp->streaming = 0;
|
|
}
|
|
|
|
static const struct vb_ops aic_dvp_vb_ops = {
|
|
.buf_queue = aic_dvp_buf_queue,
|
|
.start_streaming = aic_dvp_start_streaming,
|
|
.stop_streaming = aic_dvp_stop_streaming,
|
|
};
|
|
|
|
static irqreturn_t aic_dvp_isr(int irq, void *data)
|
|
{
|
|
struct aic_dvp *dvp = &g_dvp;
|
|
u32 sta, err = 0;
|
|
static u32 recv_first_field = 0;
|
|
|
|
sta = hal_dvp_clr_int();
|
|
pr_debug("IRQ status 0x%x, sequence %d\n", sta, dvp->sequence);
|
|
|
|
if (sta & DVP_IRQ_STA_BUF_FULL) {
|
|
g_dvp_full_cnt++;
|
|
/* should tag the buf error, so APP can ignore it */
|
|
err = 1;
|
|
pr_debug("DVP FIFO is full! Count %d (0x%x)\n", g_dvp_full_cnt, sta);
|
|
} else if (sta & DVP_IRQ_STA_XY_CODE_ERR) {
|
|
err = 1;
|
|
pr_warn("DVP checksum has error! (0x%x)\n", sta);
|
|
hal_dvp_clr_fifo();
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (sta & DVP_IRQ_EN_FRAME_DONE) {
|
|
if (err)
|
|
hal_dvp_clr_fifo();
|
|
|
|
if (dvp->cfg.interlaced) {
|
|
/* If the first field is a bottom field, ignore it */
|
|
if (!recv_first_field && hal_dvp_is_bottom_field()) {
|
|
pr_info("The first is bottom field - ignored\n");
|
|
hal_dvp_clr_fifo();
|
|
recv_first_field = 1;
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (hal_dvp_is_top_field()) {
|
|
recv_first_field = 1;
|
|
return IRQ_HANDLED;
|
|
}
|
|
}
|
|
|
|
aic_dvp_frame_done(dvp, err);
|
|
}
|
|
|
|
if (sta & DVP_IRQ_STA_HNUM) {
|
|
if (dvp->cfg.interlaced) {
|
|
hal_dvp_get_current_xy();
|
|
|
|
if (hal_dvp_is_top_field()) {
|
|
aic_dvp_top_field_done(dvp, err);
|
|
recv_first_field = 1;
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/* If the first field is a bottom field, ignore it */
|
|
if (!recv_first_field) {
|
|
pr_debug("The first is bottom field - ignore\n");
|
|
return IRQ_HANDLED;
|
|
}
|
|
}
|
|
|
|
aic_dvp_update_addr(dvp);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
int aic_dvp_probe(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = aicos_request_irq(DVP_IRQn, aic_dvp_isr, 0, "AIC_DVP_NAME", NULL);
|
|
if (ret < 0) {
|
|
pr_err("Failed to request DVP IRQ\n");
|
|
return -1;
|
|
}
|
|
|
|
memset(&g_dvp, 0, sizeof(struct aic_dvp));
|
|
INIT_LIST_HEAD(&g_dvp.active_list);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int aic_dvp_vb_init(void)
|
|
{
|
|
if (vin_vb_init(&g_dvp.queue, &aic_dvp_vb_ops))
|
|
return -1;
|
|
|
|
INIT_LIST_HEAD(&g_dvp.active_list);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void aic_dvp_vb_deinit(void)
|
|
{
|
|
vin_vb_deinit(&g_dvp.queue);
|
|
}
|
|
|
|
int aic_dvp_open(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (hal_clk_is_enabled(CLK_DVP)) {
|
|
pr_debug("DVP has been enabled\n");
|
|
return 0;
|
|
}
|
|
|
|
ret = hal_clk_enable(CLK_DVP);
|
|
if (ret < 0) {
|
|
pr_err("DVP clk enable failed!\n");
|
|
return -1;
|
|
}
|
|
|
|
ret = hal_clk_set_freq(CLK_DVP, AIC_DVP_CLK_RATE);
|
|
if (ret < 0) {
|
|
pr_err("Failed to set DVP clk %d\n", AIC_DVP_CLK_RATE);
|
|
return -1;
|
|
}
|
|
|
|
ret = hal_clk_enable_deassertrst(CLK_DVP);
|
|
if (ret < 0) {
|
|
pr_err("DVP reset enable failed!\n");
|
|
return -1;
|
|
}
|
|
|
|
hal_dvp_qos_cfg(AIC_DVP_QOS_HIGH, AIC_DVP_QOS_LOW, 0x100, 0x80);
|
|
hal_dvp_enable(&g_dvp.cfg, 1);
|
|
|
|
g_dvp_full_cnt = 0;
|
|
return 0;
|
|
}
|
|
|
|
int aic_dvp_close(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
hal_dvp_enable(&g_dvp.cfg, 0);
|
|
|
|
ret = hal_clk_disable_assertrst(CLK_DVP);
|
|
if (ret < 0) {
|
|
pr_err("DVP reset disable failed!\n");
|
|
return -1;
|
|
}
|
|
|
|
ret = hal_clk_disable(CLK_DVP);
|
|
if (ret < 0) {
|
|
pr_err("DVP clk disable failed!\n");
|
|
return -1;
|
|
}
|
|
|
|
if (g_dvp_full_cnt)
|
|
pr_info("DVP FIFO full happened %d times\n", g_dvp_full_cnt);
|
|
|
|
return 0;
|
|
}
|