diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c index 3f4de09..80bc40b 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c @@ -566,7 +566,7 @@ int sun6i_csi_update_config(struct sun6i_csi *csi, return 0; } -int sun6i_csi_update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr) +void sun6i_csi_update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr) { struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi); /* transform physical address to bus address */ @@ -580,11 +580,9 @@ int sun6i_csi_update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr) if (sdev->planar_offset[2] != -1) regmap_write(sdev->regmap, CSI_CH_F2_BUFA_REG, (bus_addr + sdev->planar_offset[2]) >> 2); - - return 0; } -int sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable) +void sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable) { struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi); struct regmap *regmap = sdev->regmap; @@ -592,7 +590,7 @@ int sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable) if (!enable) { regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, 0); regmap_write(regmap, CSI_CH_INT_EN_REG, 0); - return 0; + return; } regmap_write(regmap, CSI_CH_INT_STA_REG, 0xFF); @@ -606,8 +604,6 @@ int sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable) regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, CSI_CAP_CH0_VCAP_ON); - - return 0; } /* ----------------------------------------------------------------------------- diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h index 12508ff..9bc758b 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h @@ -92,14 +92,14 @@ int sun6i_csi_update_config(struct sun6i_csi *csi, * @csi: pointer to the csi * @addr: frame buffer's physical address */ -int sun6i_csi_update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr); +void sun6i_csi_update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr); /** * sun6i_csi_set_stream() - start/stop csi streaming * @csi: pointer to the csi * @enable: start/stop */ -int sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable); +void sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable); static inline int v4l2_pixformat_get_bpp(unsigned int pixformat) { diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c index 0cebcbd..0819b71 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c @@ -29,6 +29,7 @@ struct sun6i_csi_buffer { struct list_head list; dma_addr_t dma_addr; + bool queued_to_csi; }; static struct sun6i_csi_format * @@ -135,6 +136,7 @@ static int sun6i_video_start_streaming(struct vb2_queue *vq, unsigned int count) { struct sun6i_video *video = vb2_get_drv_priv(vq); struct sun6i_csi_buffer *buf; + struct sun6i_csi_buffer *next_buf; struct sun6i_csi_config config; unsigned long flags; int ret; @@ -143,11 +145,7 @@ static int sun6i_video_start_streaming(struct vb2_queue *vq, unsigned int count) ret = media_pipeline_start(&video->vdev.entity, &video->vdev.pipe); if (ret < 0) - goto err_start_pipeline; - - ret = sun6i_pipeline_set_stream(video, true); - if (ret < 0) - goto err_start_stream; + goto clear_dma_queue; config.pixelformat = video->fmt.fmt.pix.pixelformat; config.code = video->current_fmt->mbus_code; @@ -157,31 +155,50 @@ static int sun6i_video_start_streaming(struct vb2_queue *vq, unsigned int count) ret = sun6i_csi_update_config(video->csi, &config); if (ret < 0) - goto err_update_config; + goto stop_media_pipeline; spin_lock_irqsave(&video->dma_queue_lock, flags); - video->cur_frm = list_first_entry(&video->dma_queue, - struct sun6i_csi_buffer, list); - list_del(&video->cur_frm->list); - spin_unlock_irqrestore(&video->dma_queue_lock, flags); - ret = sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr); - if (ret < 0) - goto err_update_addr; + buf = list_first_entry(&video->dma_queue, + struct sun6i_csi_buffer, list); + buf->queued_to_csi = true; + sun6i_csi_update_buf_addr(video->csi, buf->dma_addr); + + sun6i_csi_set_stream(video->csi, true); + + /* CSI will lookup the next dma buffer for next frame before the + * the current frame done IRQ triggered. This is not documented + * but reported by Ondřej Jirman. + * The BSP code has workaround for this too. It skip to mark the + * first buffer as frame done for VB2 and pass the second buffer + * to CSI in the first frame done ISR call. Then in second frame + * done ISR call, it mark the first buffer as frame done for VB2 + * and pass the third buffer to CSI. And so on. The bad thing is + * that the first buffer will be written twice and the first frame + * is dropped even the queued buffer is sufficient. + * So, I make some improvement here. Pass the next buffer to CSI + * just follow starting the CSI. In this case, the first frame + * will be stored in first buffer, second frame in second buffer. + * This mothed is used to avoid dropping the first frame, it + * would also drop frame when lack of queued buffer. + */ + next_buf = list_next_entry(buf, list); + next_buf->queued_to_csi = true; + sun6i_csi_update_buf_addr(video->csi, next_buf->dma_addr); - ret = sun6i_csi_set_stream(video->csi, true); + spin_unlock_irqrestore(&video->dma_queue_lock, flags); + + ret = sun6i_pipeline_set_stream(video, true); if (ret < 0) - goto err_csi_stream; + goto stop_csi_stream; return 0; -err_csi_stream: -err_update_addr: -err_update_config: - sun6i_pipeline_set_stream(video, false); -err_start_stream: +stop_csi_stream: + sun6i_csi_set_stream(video->csi, false); +stop_media_pipeline: media_pipeline_stop(&video->vdev.entity); -err_start_pipeline: +clear_dma_queue: spin_lock_irqsave(&video->dma_queue_lock, flags); list_for_each_entry(buf, &video->dma_queue, list) vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); @@ -205,11 +222,6 @@ static void sun6i_video_stop_streaming(struct vb2_queue *vq) /* Release all active buffers */ spin_lock_irqsave(&video->dma_queue_lock, flags); - if (unlikely(video->cur_frm)) { - vb2_buffer_done(&video->cur_frm->vb.vb2_buf, - VB2_BUF_STATE_ERROR); - video->cur_frm = NULL; - } list_for_each_entry(buf, &video->dma_queue, list) vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); INIT_LIST_HEAD(&video->dma_queue); @@ -225,39 +237,58 @@ static void sun6i_video_buffer_queue(struct vb2_buffer *vb) unsigned long flags; spin_lock_irqsave(&video->dma_queue_lock, flags); - if (!video->cur_frm && list_empty(&video->dma_queue) && - vb2_is_streaming(vb->vb2_queue)) { - video->cur_frm = buf; - sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr); - sun6i_csi_set_stream(video->csi, 1); - } else - list_add_tail(&buf->list, &video->dma_queue); + buf->queued_to_csi = false; + list_add_tail(&buf->list, &video->dma_queue); spin_unlock_irqrestore(&video->dma_queue_lock, flags); } void sun6i_video_frame_done(struct sun6i_video *video) { + struct sun6i_csi_buffer *buf; + struct sun6i_csi_buffer *next_buf; + struct vb2_v4l2_buffer *vbuf ; + spin_lock(&video->dma_queue_lock); - if (video->cur_frm) { - struct vb2_v4l2_buffer *vbuf = &video->cur_frm->vb; - struct vb2_buffer *vb = &vbuf->vb2_buf; + video->sequence++; + + buf = list_first_entry(&video->dma_queue, + struct sun6i_csi_buffer, list); + if (list_is_last(&buf->list, &video->dma_queue)) { + dev_dbg(video->csi->dev, "Frame droped!\n"); + goto unlock; + } - vb->timestamp = ktime_get_ns(); - vbuf->sequence = video->sequence++; - vb2_buffer_done(vb, VB2_BUF_STATE_DONE); - video->cur_frm = NULL; + next_buf = list_next_entry(buf, list); + /* If a new buffer (#next_buf) had not been queued to CSI, the old + * buffer (#buf) is still holding by CSI for storing the next + * frame. So, we queue a new buffer (#next_buf) to CSI then wait + * for next ISR call. + */ + if (!next_buf->queued_to_csi) { + next_buf->queued_to_csi = true; + sun6i_csi_update_buf_addr(video->csi, next_buf->dma_addr); + dev_dbg(video->csi->dev, "Frame droped!\n"); + goto unlock; } - if (!list_empty(&video->dma_queue) - && vb2_is_streaming(&video->vb2_vidq)) { - video->cur_frm = list_first_entry(&video->dma_queue, - struct sun6i_csi_buffer, list); - list_del(&video->cur_frm->list); - sun6i_csi_update_buf_addr(video->csi, video->cur_frm->dma_addr); - } else - sun6i_csi_set_stream(video->csi, 0); + list_del(&buf->list); + vbuf = &buf->vb; + vbuf->vb2_buf.timestamp = ktime_get_ns(); + vbuf->sequence = video->sequence; + vb2_buffer_done(&vbuf->vb2_buf, VB2_BUF_STATE_DONE); + if (list_is_last(&next_buf->list, &video->dma_queue)) + goto unlock; + + /* Prepare buffer for next frame but one. */ + next_buf = list_next_entry(next_buf, list); + if (!list_is_last(&next_buf->list, &video->dma_queue)) { + next_buf->queued_to_csi = true; + sun6i_csi_update_buf_addr(video->csi, next_buf->dma_addr); + } + +unlock: spin_unlock(&video->dma_queue_lock); } @@ -664,7 +695,6 @@ int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi, INIT_LIST_HEAD(&video->dma_queue); spin_lock_init(&video->dma_queue_lock); - video->cur_frm = NULL; video->sequence = 0; video->num_formats = 0; @@ -677,7 +707,7 @@ int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi, vidq->mem_ops = &vb2_dma_contig_memops; vidq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; vidq->lock = &video->lock; - vidq->min_buffers_needed = 1; + vidq->min_buffers_needed = 2; vidq->dev = csi->dev; ret = vb2_queue_init(vidq); diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h index 14eac6e..b5a3d34 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h @@ -43,7 +43,6 @@ struct sun6i_video { spinlock_t dma_queue_lock; struct list_head dma_queue; - struct sun6i_csi_buffer *cur_frm; unsigned int sequence; struct sun6i_csi_format *formats;