[PATCH v7 2/2] media: platform: Add Aspeed Video Engine driver

Hans Verkuil hverkuil at xs4all.nl
Tue Dec 11 21:44:40 AEDT 2018


On 12/10/18 11:26 PM, Eddie James wrote:
> The Video Engine (VE) embedded in the Aspeed AST2400 and AST2500 SOCs
> can capture and compress video data from digital or analog sources. With
> the Aspeed chip acting a service processor, the Video Engine can capture
> the host processor graphics output.
> 
> Add a V4L2 driver to capture video data and compress it to JPEG images.
> Make the video frames available through the V4L2 streaming interface.
> 
> Signed-off-by: Eddie James <eajames at linux.ibm.com>
> ---

<snip>

> +static void aspeed_video_irq_res_change(struct aspeed_video *video)
> +{
> +	dev_dbg(video->dev, "Resolution changed; resetting\n");
> +
> +	set_bit(VIDEO_RES_CHANGE, &video->flags);
> +	clear_bit(VIDEO_FRAME_INPRG, &video->flags);
> +
> +	aspeed_video_off(video);
> +	aspeed_video_bufs_done(video, VB2_BUF_STATE_ERROR);
> +
> +	schedule_delayed_work(&video->res_work, RESOLUTION_CHANGE_DELAY);
> +}
> +
> +static irqreturn_t aspeed_video_irq(int irq, void *arg)
> +{
> +	struct aspeed_video *video = arg;
> +	u32 sts = aspeed_video_read(video, VE_INTERRUPT_STATUS);
> +
> +	/* Resolution changed; reset entire engine and reinitialize */
> +	if (sts & VE_INTERRUPT_MODE_DETECT_WD) {

Does this only trigger when the resolution changed, or also when the signal
disappears? For the purpose of this review I assume that it is also triggered
when the signal goes away. The comment should be updated in that case that
it is not just resolution changes, but also a dropped video signal.

> +		aspeed_video_irq_res_change(video);
> +		return IRQ_HANDLED;
> +	}
> +
> +	if (sts & VE_INTERRUPT_MODE_DETECT) {
> +		if (test_bit(VIDEO_RES_DETECT, &video->flags)) {
> +			aspeed_video_update(video, VE_INTERRUPT_CTRL,
> +					    VE_INTERRUPT_MODE_DETECT, 0);
> +			aspeed_video_write(video, VE_INTERRUPT_STATUS,
> +					   VE_INTERRUPT_MODE_DETECT);
> +
> +			set_bit(VIDEO_MODE_DETECT_DONE, &video->flags);
> +			wake_up_interruptible_all(&video->wait);
> +		} else {
> +			aspeed_video_irq_res_change(video);
> +			return IRQ_HANDLED;
> +		}
> +	}
> +
> +	if ((sts & VE_INTERRUPT_COMP_COMPLETE) &&
> +	    (sts & VE_INTERRUPT_CAPTURE_COMPLETE)) {
> +		struct aspeed_video_buffer *buf;
> +		u32 frame_size = aspeed_video_read(video,
> +						   VE_OFFSET_COMP_STREAM);
> +
> +		spin_lock(&video->lock);
> +		clear_bit(VIDEO_FRAME_INPRG, &video->flags);
> +		buf = list_first_entry_or_null(&video->buffers,
> +					       struct aspeed_video_buffer,
> +					       link);
> +		if (buf) {
> +			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, frame_size);
> +
> +			if (!list_is_last(&buf->link, &video->buffers)) {
> +				buf->vb.vb2_buf.timestamp = ktime_get_ns();
> +				buf->vb.sequence = video->sequence++;
> +				buf->vb.field = V4L2_FIELD_NONE;
> +				vb2_buffer_done(&buf->vb.vb2_buf,
> +						VB2_BUF_STATE_DONE);
> +				list_del(&buf->link);
> +			}
> +		}
> +		spin_unlock(&video->lock);
> +
> +		aspeed_video_update(video, VE_SEQ_CTRL,
> +				    VE_SEQ_CTRL_TRIG_CAPTURE |
> +				    VE_SEQ_CTRL_FORCE_IDLE |
> +				    VE_SEQ_CTRL_TRIG_COMP, 0);
> +		aspeed_video_update(video, VE_INTERRUPT_CTRL,
> +				    VE_INTERRUPT_COMP_COMPLETE |
> +				    VE_INTERRUPT_CAPTURE_COMPLETE, 0);
> +		aspeed_video_write(video, VE_INTERRUPT_STATUS,
> +				   VE_INTERRUPT_COMP_COMPLETE |
> +				   VE_INTERRUPT_CAPTURE_COMPLETE);
> +
> +		if (test_bit(VIDEO_STREAMING, &video->flags) && buf)
> +			aspeed_video_start_frame(video);
> +	}
> +
> +	return IRQ_HANDLED;
> +}

<snip>

> +static void aspeed_video_get_resolution(struct aspeed_video *video)
> +{
> +	bool invalid_resolution = true;
> +	int rc;
> +	int tries = 0;
> +	u32 mds;
> +	u32 src_lr_edge;
> +	u32 src_tb_edge;
> +	u32 sync;
> +	struct v4l2_bt_timings *det = &video->detected_timings;
> +
> +	det->width = MIN_WIDTH;
> +	det->height = MIN_HEIGHT;
> +	video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL;
> +
> +	/*
> +	 * Since we need max buffer size for detection, free the second source
> +	 * buffer first.
> +	 */
> +	if (video->srcs[1].size)
> +		aspeed_video_free_buf(video, &video->srcs[1]);
> +
> +	if (video->srcs[0].size < VE_MAX_SRC_BUFFER_SIZE) {
> +		if (video->srcs[0].size)
> +			aspeed_video_free_buf(video, &video->srcs[0]);
> +
> +		if (!aspeed_video_alloc_buf(video, &video->srcs[0],
> +					    VE_MAX_SRC_BUFFER_SIZE)) {
> +			dev_err(video->dev,
> +				"Failed to allocate source buffers\n");
> +			return;
> +		}
> +	}
> +
> +	aspeed_video_write(video, VE_SRC0_ADDR, video->srcs[0].dma);
> +
> +	do {
> +		if (tries) {
> +			set_current_state(TASK_INTERRUPTIBLE);
> +			if (schedule_timeout(INVALID_RESOLUTION_DELAY))
> +				return;
> +		}
> +
> +		set_bit(VIDEO_RES_DETECT, &video->flags);
> +		aspeed_video_enable_mode_detect(video);
> +
> +		rc = wait_event_interruptible_timeout(video->wait,
> +						      res_check(video),
> +						      MODE_DETECT_TIMEOUT);
> +		if (!rc) {
> +			dev_err(video->dev, "Timed out; first mode detect\n");
> +			clear_bit(VIDEO_RES_DETECT, &video->flags);
> +			return;
> +		}
> +
> +		/* Disable mode detect in order to re-trigger */
> +		aspeed_video_update(video, VE_SEQ_CTRL,
> +				    VE_SEQ_CTRL_TRIG_MODE_DET, 0);
> +
> +		aspeed_video_check_and_set_polarity(video);
> +
> +		aspeed_video_enable_mode_detect(video);
> +
> +		rc = wait_event_interruptible_timeout(video->wait,
> +						      res_check(video),
> +						      MODE_DETECT_TIMEOUT);
> +		clear_bit(VIDEO_RES_DETECT, &video->flags);
> +		if (!rc) {
> +			dev_err(video->dev, "Timed out; second mode detect\n");
> +			return;
> +		}
> +
> +		src_lr_edge = aspeed_video_read(video, VE_SRC_LR_EDGE_DET);
> +		src_tb_edge = aspeed_video_read(video, VE_SRC_TB_EDGE_DET);
> +		mds = aspeed_video_read(video, VE_MODE_DETECT_STATUS);
> +		sync = aspeed_video_read(video, VE_SYNC_STATUS);
> +
> +		video->frame_bottom = (src_tb_edge & VE_SRC_TB_EDGE_DET_BOT) >>
> +			VE_SRC_TB_EDGE_DET_BOT_SHF;
> +		video->frame_top = src_tb_edge & VE_SRC_TB_EDGE_DET_TOP;
> +		det->vfrontporch = video->frame_top;
> +		det->vbackporch = ((mds & VE_MODE_DETECT_V_LINES) >>
> +			VE_MODE_DETECT_V_LINES_SHF) - video->frame_bottom;
> +		det->vsync = (sync & VE_SYNC_STATUS_VSYNC) >>
> +			VE_SYNC_STATUS_VSYNC_SHF;
> +		if (video->frame_top > video->frame_bottom)
> +			continue;
> +
> +		video->frame_right = (src_lr_edge & VE_SRC_LR_EDGE_DET_RT) >>
> +			VE_SRC_LR_EDGE_DET_RT_SHF;
> +		video->frame_left = src_lr_edge & VE_SRC_LR_EDGE_DET_LEFT;
> +		det->hfrontporch = video->frame_left;
> +		det->hbackporch = (mds & VE_MODE_DETECT_H_PIXELS) -
> +			video->frame_right;
> +		det->hsync = sync & VE_SYNC_STATUS_HSYNC;
> +		if (video->frame_left > video->frame_right)
> +			continue;
> +
> +		invalid_resolution = false;
> +	} while (invalid_resolution && (tries++ < INVALID_RESOLUTION_RETRIES));
> +
> +	if (invalid_resolution) {
> +		dev_err(video->dev, "Invalid resolution detected\n");
> +		return;
> +	}
> +
> +	det->height = (video->frame_bottom - video->frame_top) + 1;
> +	det->width = (video->frame_right - video->frame_left) + 1;
> +	video->v4l2_input_status = 0;
> +
> +	/*
> +	 * Enable mode-detect watchdog, resolution-change watchdog and
> +	 * automatic compression after frame capture.
> +	 */
> +	aspeed_video_update(video, VE_INTERRUPT_CTRL, 0,
> +			    VE_INTERRUPT_MODE_DETECT_WD);
> +	aspeed_video_update(video, VE_SEQ_CTRL, 0,
> +			    VE_SEQ_CTRL_AUTO_COMP | VE_SEQ_CTRL_EN_WATCHDOG);
> +
> +	dev_dbg(video->dev, "Got resolution: %dx%d\n", det->width,
> +		det->height);
> +}
> +
> +static void aspeed_video_set_resolution(struct aspeed_video *video)
> +{
> +	struct v4l2_bt_timings *act = &video->active_timings;
> +	unsigned int size = act->width * act->height;
> +
> +	aspeed_video_calc_compressed_size(video, size);
> +
> +	/* Don't use direct mode below 1024 x 768 (irqs don't fire) */
> +	if (size < DIRECT_FETCH_THRESHOLD) {
> +		aspeed_video_write(video, VE_TGS_0,
> +				   FIELD_PREP(VE_TGS_FIRST,
> +					      video->frame_left - 1) |
> +				   FIELD_PREP(VE_TGS_LAST,
> +					      video->frame_right));
> +		aspeed_video_write(video, VE_TGS_1,
> +				   FIELD_PREP(VE_TGS_FIRST, video->frame_top) |
> +				   FIELD_PREP(VE_TGS_LAST,
> +					      video->frame_bottom + 1));
> +		aspeed_video_update(video, VE_CTRL, 0, VE_CTRL_INT_DE);
> +	} else {
> +		aspeed_video_update(video, VE_CTRL, 0, VE_CTRL_DIRECT_FETCH);
> +	}
> +
> +	/* Set capture/compression frame sizes */
> +	aspeed_video_write(video, VE_CAP_WINDOW,
> +			   act->width << 16 | act->height);
> +	aspeed_video_write(video, VE_COMP_WINDOW,
> +			   act->width << 16 | act->height);
> +	aspeed_video_write(video, VE_SRC_SCANLINE_OFFSET, act->width * 4);
> +
> +	size *= 4;
> +
> +	if (size == video->srcs[0].size / 2) {
> +		aspeed_video_write(video, VE_SRC1_ADDR,
> +				   video->srcs[0].dma + size);
> +	} else if (size == video->srcs[0].size) {
> +		if (!aspeed_video_alloc_buf(video, &video->srcs[1], size))
> +			goto err_mem;
> +
> +		aspeed_video_write(video, VE_SRC1_ADDR, video->srcs[1].dma);
> +	} else {
> +		aspeed_video_free_buf(video, &video->srcs[0]);
> +
> +		if (!aspeed_video_alloc_buf(video, &video->srcs[0], size))
> +			goto err_mem;
> +
> +		if (!aspeed_video_alloc_buf(video, &video->srcs[1], size))
> +			goto err_mem;
> +
> +		aspeed_video_write(video, VE_SRC0_ADDR, video->srcs[0].dma);
> +		aspeed_video_write(video, VE_SRC1_ADDR, video->srcs[1].dma);
> +	}
> +
> +	return;
> +
> +err_mem:
> +	dev_err(video->dev, "Failed to allocate source buffers\n");
> +
> +	if (video->srcs[0].size)
> +		aspeed_video_free_buf(video, &video->srcs[0]);
> +}
> +
> +static void aspeed_video_init_regs(struct aspeed_video *video)
> +{
> +	u32 comp_ctrl = VE_COMP_CTRL_RSVD |
> +		FIELD_PREP(VE_COMP_CTRL_DCT_LUM, video->jpeg_quality) |
> +		FIELD_PREP(VE_COMP_CTRL_DCT_CHR, video->jpeg_quality | 0x10);
> +	u32 ctrl = VE_CTRL_AUTO_OR_CURSOR;
> +	u32 seq_ctrl = VE_SEQ_CTRL_JPEG_MODE;
> +
> +	if (video->frame_rate)
> +		ctrl |= FIELD_PREP(VE_CTRL_FRC, video->frame_rate);
> +
> +	if (video->yuv420)
> +		seq_ctrl |= VE_SEQ_CTRL_YUV420;
> +
> +	/* Unlock VE registers */
> +	aspeed_video_write(video, VE_PROTECTION_KEY, VE_PROTECTION_KEY_UNLOCK);
> +
> +	/* Disable interrupts */
> +	aspeed_video_write(video, VE_INTERRUPT_CTRL, 0);
> +	aspeed_video_write(video, VE_INTERRUPT_STATUS, 0xffffffff);
> +
> +	/* Clear the offset */
> +	aspeed_video_write(video, VE_COMP_PROC_OFFSET, 0);
> +	aspeed_video_write(video, VE_COMP_OFFSET, 0);
> +
> +	aspeed_video_write(video, VE_JPEG_ADDR, video->jpeg.dma);
> +
> +	/* Set control registers */
> +	aspeed_video_write(video, VE_SEQ_CTRL, seq_ctrl);
> +	aspeed_video_write(video, VE_CTRL, ctrl);
> +	aspeed_video_write(video, VE_COMP_CTRL, comp_ctrl);
> +
> +	/* Don't downscale */
> +	aspeed_video_write(video, VE_SCALING_FACTOR, 0x10001000);
> +	aspeed_video_write(video, VE_SCALING_FILTER0, 0x00200000);
> +	aspeed_video_write(video, VE_SCALING_FILTER1, 0x00200000);
> +	aspeed_video_write(video, VE_SCALING_FILTER2, 0x00200000);
> +	aspeed_video_write(video, VE_SCALING_FILTER3, 0x00200000);
> +
> +	/* Set mode detection defaults */
> +	aspeed_video_write(video, VE_MODE_DETECT, 0x22666500);
> +}
> +
> +static void aspeed_video_start(struct aspeed_video *video)
> +{
> +	aspeed_video_on(video);
> +
> +	aspeed_video_init_regs(video);
> +
> +	aspeed_video_get_resolution(video);
> +
> +	/* Set timings since the device is being opened for the first time */
> +	video->active_timings = video->detected_timings;

OK, so as far as I can tell the get_resolution() function sets detected_timings
to a default value (VGA) if it can't find a valid signal.

Correct?

> +	aspeed_video_set_resolution(video);
> +
> +	video->pix_fmt.width = video->active_timings.width;
> +	video->pix_fmt.height = video->active_timings.height;
> +	video->pix_fmt.sizeimage = video->max_compressed_size;
> +}
> +
> +static void aspeed_video_stop(struct aspeed_video *video)
> +{
> +	set_bit(VIDEO_STOPPED, &video->flags);
> +	cancel_delayed_work_sync(&video->res_work);
> +
> +	aspeed_video_off(video);
> +
> +	if (video->srcs[0].size)
> +		aspeed_video_free_buf(video, &video->srcs[0]);
> +
> +	if (video->srcs[1].size)
> +		aspeed_video_free_buf(video, &video->srcs[1]);
> +
> +	video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL;
> +	video->flags = 0;
> +}

<snip>

> +static int aspeed_video_query_dv_timings(struct file *file, void *fh,
> +					 struct v4l2_dv_timings *timings)
> +{
> +	int rc;
> +	struct aspeed_video *video = video_drvdata(file);
> +
> +	/*
> +	 * This blocks only if the driver is currently in the process of
> +	 * detecting a new resolution; in the event of no signal or timeout
> +	 * this function is woken up.
> +	 */
> +	if (file->f_flags & O_NONBLOCK) {
> +		if (test_bit(VIDEO_RES_CHANGE, &video->flags))
> +			return -EAGAIN;
> +	} else {
> +		rc = wait_event_interruptible(video->wait,
> +					      !test_bit(VIDEO_RES_CHANGE,
> +							&video->flags));
> +		if (rc)
> +			return -EINTR;
> +	}
> +
> +	timings->type = V4L2_DV_BT_656_1120;
> +	timings->bt = video->detected_timings;
> +
> +	return 0;


This does not return the right error if no signal was found.

I think this should be:

	return video->v4l2_input_status ? -ENOLINK : 0;

> +}

<snip>

> +static void aspeed_video_resolution_work(struct work_struct *work)
> +{
> +	struct delayed_work *dwork = to_delayed_work(work);
> +	struct aspeed_video *video = container_of(dwork, struct aspeed_video,
> +						  res_work);
> +
> +	aspeed_video_on(video);
> +
> +	/* Exit early in case no clients remain */
> +	if (test_bit(VIDEO_STOPPED, &video->flags))
> +		goto done;
> +
> +	aspeed_video_init_regs(video);
> +
> +	aspeed_video_get_resolution(video);
> +
> +	if (video->detected_timings.width != video->active_timings.width ||
> +	    video->detected_timings.height != video->active_timings.height) {

I don't think this test is sufficient. Suppose the active timings are VGA,
and now the signal disappears. The detected_timings will be set to VGA as
well in that case, so there is no change. This test should take whether the
'signal present' status changes as well.

> +		static const struct v4l2_event ev = {
> +			.type = V4L2_EVENT_SOURCE_CHANGE,
> +			.u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
> +		};
> +
> +		v4l2_event_queue(&video->vdev, &ev);
> +	} else if (test_bit(VIDEO_STREAMING, &video->flags)) {
> +		/* No resolution change so just restart streaming */
> +		aspeed_video_start_frame(video);
> +	}
> +
> +done:
> +	clear_bit(VIDEO_RES_CHANGE, &video->flags);
> +	wake_up_interruptible_all(&video->wait);
> +}
> +
> +static int aspeed_video_open(struct file *file)
> +{
> +	int rc;
> +	struct aspeed_video *video = video_drvdata(file);
> +
> +	mutex_lock(&video->video_lock);
> +
> +	rc = v4l2_fh_open(file);
> +	if (rc) {
> +		mutex_unlock(&video->video_lock);
> +		return rc;
> +	}
> +
> +	if (v4l2_fh_is_singular_file(file))
> +		aspeed_video_start(video);
> +
> +	mutex_unlock(&video->video_lock);
> +
> +	return 0;
> +}
> +
> +static int aspeed_video_release(struct file *file)
> +{
> +	int rc;
> +	struct aspeed_video *video = video_drvdata(file);
> +
> +	mutex_lock(&video->video_lock);
> +
> +	if (v4l2_fh_is_singular_file(file))
> +		aspeed_video_stop(video);
> +
> +	rc = _vb2_fop_release(file, NULL);
> +
> +	mutex_unlock(&video->video_lock);
> +
> +	return rc;
> +}

<snip>

This is now looking very good. Just a few small issues remaining.

Running checkpatch.pl --strict over the patch gives me three 'CHECK' items
they you should also address:

CHECK: struct mutex definition without comment
#312: FILE: drivers/media/platform/aspeed-video.c:222:
+       struct mutex video_lock;

CHECK: spinlock_t definition without comment
#315: FILE: drivers/media/platform/aspeed-video.c:225:
+       spinlock_t lock;

CHECK: usleep_range is preferred over udelay; see Documentation/timers/timers-howto.txt
#580: FILE: drivers/media/platform/aspeed-video.c:490:
+       udelay(100);

I think v8 can be the final version. It probably won't make 4.21, though.
If I'll get a v8 today, then there is a small chance it might still make it.
If not, then it'll be merged early in the 4.22 cycle.

Regards,

	Hans


More information about the Linux-aspeed mailing list