162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Original author:
662306a36Sopenharmony_ci * Ben Collins <bcollins@ubuntu.com>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Additional work by:
962306a36Sopenharmony_ci * John Brooks <john.brooks@bluecherry.net>
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/kernel.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/kthread.h>
1562306a36Sopenharmony_ci#include <linux/freezer.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <media/v4l2-ioctl.h>
1862306a36Sopenharmony_ci#include <media/v4l2-common.h>
1962306a36Sopenharmony_ci#include <media/v4l2-event.h>
2062306a36Sopenharmony_ci#include <media/videobuf2-v4l2.h>
2162306a36Sopenharmony_ci#include <media/videobuf2-dma-contig.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include "solo6x10.h"
2462306a36Sopenharmony_ci#include "solo6x10-tw28.h"
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/* Image size is two fields, SOLO_HW_BPL is one horizontal line in hardware */
2762306a36Sopenharmony_ci#define SOLO_HW_BPL		2048
2862306a36Sopenharmony_ci#define solo_vlines(__solo)	(__solo->video_vsize * 2)
2962306a36Sopenharmony_ci#define solo_image_size(__solo) (solo_bytesperline(__solo) * \
3062306a36Sopenharmony_ci				 solo_vlines(__solo))
3162306a36Sopenharmony_ci#define solo_bytesperline(__solo) (__solo->video_hsize * 2)
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#define MIN_VID_BUFFERS		2
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic inline void erase_on(struct solo_dev *solo_dev)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, SOLO_VO_DISP_ERASE_ON);
3862306a36Sopenharmony_ci	solo_dev->erasing = 1;
3962306a36Sopenharmony_ci	solo_dev->frame_blank = 0;
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic inline int erase_off(struct solo_dev *solo_dev)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	if (!solo_dev->erasing)
4562306a36Sopenharmony_ci		return 0;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	/* First time around, assert erase off */
4862306a36Sopenharmony_ci	if (!solo_dev->frame_blank)
4962306a36Sopenharmony_ci		solo_reg_write(solo_dev, SOLO_VO_DISP_ERASE, 0);
5062306a36Sopenharmony_ci	/* Keep the erasing flag on for 8 frames minimum */
5162306a36Sopenharmony_ci	if (solo_dev->frame_blank++ >= 8)
5262306a36Sopenharmony_ci		solo_dev->erasing = 0;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	return 1;
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_civoid solo_video_in_isr(struct solo_dev *solo_dev)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	wake_up_interruptible_all(&solo_dev->disp_thread_wait);
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic void solo_win_setup(struct solo_dev *solo_dev, u8 ch,
6362306a36Sopenharmony_ci			   int sx, int sy, int ex, int ey, int scale)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	if (ch >= solo_dev->nr_chans)
6662306a36Sopenharmony_ci		return;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	/* Here, we just keep window/channel the same */
6962306a36Sopenharmony_ci	solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL0(ch),
7062306a36Sopenharmony_ci		       SOLO_VI_WIN_CHANNEL(ch) |
7162306a36Sopenharmony_ci		       SOLO_VI_WIN_SX(sx) |
7262306a36Sopenharmony_ci		       SOLO_VI_WIN_EX(ex) |
7362306a36Sopenharmony_ci		       SOLO_VI_WIN_SCALE(scale));
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	solo_reg_write(solo_dev, SOLO_VI_WIN_CTRL1(ch),
7662306a36Sopenharmony_ci		       SOLO_VI_WIN_SY(sy) |
7762306a36Sopenharmony_ci		       SOLO_VI_WIN_EY(ey));
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistatic int solo_v4l2_ch_ext_4up(struct solo_dev *solo_dev, u8 idx, int on)
8162306a36Sopenharmony_ci{
8262306a36Sopenharmony_ci	u8 ch = idx * 4;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	if (ch >= solo_dev->nr_chans)
8562306a36Sopenharmony_ci		return -EINVAL;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	if (!on) {
8862306a36Sopenharmony_ci		u8 i;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci		for (i = ch; i < ch + 4; i++)
9162306a36Sopenharmony_ci			solo_win_setup(solo_dev, i, solo_dev->video_hsize,
9262306a36Sopenharmony_ci				       solo_vlines(solo_dev),
9362306a36Sopenharmony_ci				       solo_dev->video_hsize,
9462306a36Sopenharmony_ci				       solo_vlines(solo_dev), 0);
9562306a36Sopenharmony_ci		return 0;
9662306a36Sopenharmony_ci	}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	/* Row 1 */
9962306a36Sopenharmony_ci	solo_win_setup(solo_dev, ch, 0, 0, solo_dev->video_hsize / 2,
10062306a36Sopenharmony_ci		       solo_vlines(solo_dev) / 2, 3);
10162306a36Sopenharmony_ci	solo_win_setup(solo_dev, ch + 1, solo_dev->video_hsize / 2, 0,
10262306a36Sopenharmony_ci		       solo_dev->video_hsize, solo_vlines(solo_dev) / 2, 3);
10362306a36Sopenharmony_ci	/* Row 2 */
10462306a36Sopenharmony_ci	solo_win_setup(solo_dev, ch + 2, 0, solo_vlines(solo_dev) / 2,
10562306a36Sopenharmony_ci		       solo_dev->video_hsize / 2, solo_vlines(solo_dev), 3);
10662306a36Sopenharmony_ci	solo_win_setup(solo_dev, ch + 3, solo_dev->video_hsize / 2,
10762306a36Sopenharmony_ci		       solo_vlines(solo_dev) / 2, solo_dev->video_hsize,
10862306a36Sopenharmony_ci		       solo_vlines(solo_dev), 3);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	return 0;
11162306a36Sopenharmony_ci}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_cistatic int solo_v4l2_ch_ext_16up(struct solo_dev *solo_dev, int on)
11462306a36Sopenharmony_ci{
11562306a36Sopenharmony_ci	int sy, ysize, hsize, i;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	if (!on) {
11862306a36Sopenharmony_ci		for (i = 0; i < 16; i++)
11962306a36Sopenharmony_ci			solo_win_setup(solo_dev, i, solo_dev->video_hsize,
12062306a36Sopenharmony_ci				       solo_vlines(solo_dev),
12162306a36Sopenharmony_ci				       solo_dev->video_hsize,
12262306a36Sopenharmony_ci				       solo_vlines(solo_dev), 0);
12362306a36Sopenharmony_ci		return 0;
12462306a36Sopenharmony_ci	}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	ysize = solo_vlines(solo_dev) / 4;
12762306a36Sopenharmony_ci	hsize = solo_dev->video_hsize / 4;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	for (sy = 0, i = 0; i < 4; i++, sy += ysize) {
13062306a36Sopenharmony_ci		solo_win_setup(solo_dev, i * 4, 0, sy, hsize,
13162306a36Sopenharmony_ci			       sy + ysize, 5);
13262306a36Sopenharmony_ci		solo_win_setup(solo_dev, (i * 4) + 1, hsize, sy,
13362306a36Sopenharmony_ci			       hsize * 2, sy + ysize, 5);
13462306a36Sopenharmony_ci		solo_win_setup(solo_dev, (i * 4) + 2, hsize * 2, sy,
13562306a36Sopenharmony_ci			       hsize * 3, sy + ysize, 5);
13662306a36Sopenharmony_ci		solo_win_setup(solo_dev, (i * 4) + 3, hsize * 3, sy,
13762306a36Sopenharmony_ci			       solo_dev->video_hsize, sy + ysize, 5);
13862306a36Sopenharmony_ci	}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	return 0;
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistatic int solo_v4l2_ch(struct solo_dev *solo_dev, u8 ch, int on)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	u8 ext_ch;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	if (ch < solo_dev->nr_chans) {
14862306a36Sopenharmony_ci		solo_win_setup(solo_dev, ch, on ? 0 : solo_dev->video_hsize,
14962306a36Sopenharmony_ci			       on ? 0 : solo_vlines(solo_dev),
15062306a36Sopenharmony_ci			       solo_dev->video_hsize, solo_vlines(solo_dev),
15162306a36Sopenharmony_ci			       on ? 1 : 0);
15262306a36Sopenharmony_ci		return 0;
15362306a36Sopenharmony_ci	}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	if (ch >= solo_dev->nr_chans + solo_dev->nr_ext)
15662306a36Sopenharmony_ci		return -EINVAL;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	ext_ch = ch - solo_dev->nr_chans;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	/* 4up's first */
16162306a36Sopenharmony_ci	if (ext_ch < 4)
16262306a36Sopenharmony_ci		return solo_v4l2_ch_ext_4up(solo_dev, ext_ch, on);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	/* Remaining case is 16up for 16-port */
16562306a36Sopenharmony_ci	return solo_v4l2_ch_ext_16up(solo_dev, on);
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic int solo_v4l2_set_ch(struct solo_dev *solo_dev, u8 ch)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	if (ch >= solo_dev->nr_chans + solo_dev->nr_ext)
17162306a36Sopenharmony_ci		return -EINVAL;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	erase_on(solo_dev);
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	solo_v4l2_ch(solo_dev, solo_dev->cur_disp_ch, 0);
17662306a36Sopenharmony_ci	solo_v4l2_ch(solo_dev, ch, 1);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	solo_dev->cur_disp_ch = ch;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	return 0;
18162306a36Sopenharmony_ci}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic void solo_fillbuf(struct solo_dev *solo_dev,
18462306a36Sopenharmony_ci			 struct vb2_buffer *vb)
18562306a36Sopenharmony_ci{
18662306a36Sopenharmony_ci	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
18762306a36Sopenharmony_ci	dma_addr_t addr;
18862306a36Sopenharmony_ci	unsigned int fdma_addr;
18962306a36Sopenharmony_ci	int error = -1;
19062306a36Sopenharmony_ci	int i;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	addr = vb2_dma_contig_plane_dma_addr(vb, 0);
19362306a36Sopenharmony_ci	if (!addr)
19462306a36Sopenharmony_ci		goto finish_buf;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	if (erase_off(solo_dev)) {
19762306a36Sopenharmony_ci		void *p = vb2_plane_vaddr(vb, 0);
19862306a36Sopenharmony_ci		int image_size = solo_image_size(solo_dev);
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci		for (i = 0; i < image_size; i += 2) {
20162306a36Sopenharmony_ci			((u8 *)p)[i] = 0x80;
20262306a36Sopenharmony_ci			((u8 *)p)[i + 1] = 0x00;
20362306a36Sopenharmony_ci		}
20462306a36Sopenharmony_ci		error = 0;
20562306a36Sopenharmony_ci	} else {
20662306a36Sopenharmony_ci		fdma_addr = SOLO_DISP_EXT_ADDR + (solo_dev->old_write *
20762306a36Sopenharmony_ci				(SOLO_HW_BPL * solo_vlines(solo_dev)));
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci		error = solo_p2m_dma_t(solo_dev, 0, addr, fdma_addr,
21062306a36Sopenharmony_ci				       solo_bytesperline(solo_dev),
21162306a36Sopenharmony_ci				       solo_vlines(solo_dev), SOLO_HW_BPL);
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_cifinish_buf:
21562306a36Sopenharmony_ci	if (!error) {
21662306a36Sopenharmony_ci		vb2_set_plane_payload(vb, 0,
21762306a36Sopenharmony_ci			solo_vlines(solo_dev) * solo_bytesperline(solo_dev));
21862306a36Sopenharmony_ci		vbuf->sequence = solo_dev->sequence++;
21962306a36Sopenharmony_ci		vb->timestamp = ktime_get_ns();
22062306a36Sopenharmony_ci	}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	vb2_buffer_done(vb, error ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_cistatic void solo_thread_try(struct solo_dev *solo_dev)
22662306a36Sopenharmony_ci{
22762306a36Sopenharmony_ci	struct solo_vb2_buf *vb;
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	/* Only "break" from this loop if slock is held, otherwise
23062306a36Sopenharmony_ci	 * just return. */
23162306a36Sopenharmony_ci	for (;;) {
23262306a36Sopenharmony_ci		unsigned int cur_write;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci		cur_write = SOLO_VI_STATUS0_PAGE(
23562306a36Sopenharmony_ci			solo_reg_read(solo_dev, SOLO_VI_STATUS0));
23662306a36Sopenharmony_ci		if (cur_write == solo_dev->old_write)
23762306a36Sopenharmony_ci			return;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci		spin_lock(&solo_dev->slock);
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci		if (list_empty(&solo_dev->vidq_active))
24262306a36Sopenharmony_ci			break;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci		vb = list_first_entry(&solo_dev->vidq_active, struct solo_vb2_buf,
24562306a36Sopenharmony_ci				      list);
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci		solo_dev->old_write = cur_write;
24862306a36Sopenharmony_ci		list_del(&vb->list);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci		spin_unlock(&solo_dev->slock);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci		solo_fillbuf(solo_dev, &vb->vb.vb2_buf);
25362306a36Sopenharmony_ci	}
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	assert_spin_locked(&solo_dev->slock);
25662306a36Sopenharmony_ci	spin_unlock(&solo_dev->slock);
25762306a36Sopenharmony_ci}
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_cistatic int solo_thread(void *data)
26062306a36Sopenharmony_ci{
26162306a36Sopenharmony_ci	struct solo_dev *solo_dev = data;
26262306a36Sopenharmony_ci	DECLARE_WAITQUEUE(wait, current);
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	set_freezable();
26562306a36Sopenharmony_ci	add_wait_queue(&solo_dev->disp_thread_wait, &wait);
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	for (;;) {
26862306a36Sopenharmony_ci		long timeout = schedule_timeout_interruptible(HZ);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci		if (timeout == -ERESTARTSYS || kthread_should_stop())
27162306a36Sopenharmony_ci			break;
27262306a36Sopenharmony_ci		solo_thread_try(solo_dev);
27362306a36Sopenharmony_ci		try_to_freeze();
27462306a36Sopenharmony_ci	}
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	remove_wait_queue(&solo_dev->disp_thread_wait, &wait);
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	return 0;
27962306a36Sopenharmony_ci}
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_cistatic int solo_start_thread(struct solo_dev *solo_dev)
28262306a36Sopenharmony_ci{
28362306a36Sopenharmony_ci	int ret = 0;
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	solo_dev->kthread = kthread_run(solo_thread, solo_dev, SOLO6X10_NAME "_disp");
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	if (IS_ERR(solo_dev->kthread)) {
28862306a36Sopenharmony_ci		ret = PTR_ERR(solo_dev->kthread);
28962306a36Sopenharmony_ci		solo_dev->kthread = NULL;
29062306a36Sopenharmony_ci		return ret;
29162306a36Sopenharmony_ci	}
29262306a36Sopenharmony_ci	solo_irq_on(solo_dev, SOLO_IRQ_VIDEO_IN);
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	return ret;
29562306a36Sopenharmony_ci}
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_cistatic void solo_stop_thread(struct solo_dev *solo_dev)
29862306a36Sopenharmony_ci{
29962306a36Sopenharmony_ci	if (!solo_dev->kthread)
30062306a36Sopenharmony_ci		return;
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	solo_irq_off(solo_dev, SOLO_IRQ_VIDEO_IN);
30362306a36Sopenharmony_ci	kthread_stop(solo_dev->kthread);
30462306a36Sopenharmony_ci	solo_dev->kthread = NULL;
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_cistatic int solo_queue_setup(struct vb2_queue *q,
30862306a36Sopenharmony_ci			   unsigned int *num_buffers, unsigned int *num_planes,
30962306a36Sopenharmony_ci			   unsigned int sizes[], struct device *alloc_devs[])
31062306a36Sopenharmony_ci{
31162306a36Sopenharmony_ci	struct solo_dev *solo_dev = vb2_get_drv_priv(q);
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	sizes[0] = solo_image_size(solo_dev);
31462306a36Sopenharmony_ci	*num_planes = 1;
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	if (*num_buffers < MIN_VID_BUFFERS)
31762306a36Sopenharmony_ci		*num_buffers = MIN_VID_BUFFERS;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	return 0;
32062306a36Sopenharmony_ci}
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_cistatic int solo_start_streaming(struct vb2_queue *q, unsigned int count)
32362306a36Sopenharmony_ci{
32462306a36Sopenharmony_ci	struct solo_dev *solo_dev = vb2_get_drv_priv(q);
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	solo_dev->sequence = 0;
32762306a36Sopenharmony_ci	return solo_start_thread(solo_dev);
32862306a36Sopenharmony_ci}
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_cistatic void solo_stop_streaming(struct vb2_queue *q)
33162306a36Sopenharmony_ci{
33262306a36Sopenharmony_ci	struct solo_dev *solo_dev = vb2_get_drv_priv(q);
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci	solo_stop_thread(solo_dev);
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	spin_lock(&solo_dev->slock);
33762306a36Sopenharmony_ci	while (!list_empty(&solo_dev->vidq_active)) {
33862306a36Sopenharmony_ci		struct solo_vb2_buf *buf = list_entry(
33962306a36Sopenharmony_ci				solo_dev->vidq_active.next,
34062306a36Sopenharmony_ci				struct solo_vb2_buf, list);
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci		list_del(&buf->list);
34362306a36Sopenharmony_ci		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
34462306a36Sopenharmony_ci	}
34562306a36Sopenharmony_ci	spin_unlock(&solo_dev->slock);
34662306a36Sopenharmony_ci	INIT_LIST_HEAD(&solo_dev->vidq_active);
34762306a36Sopenharmony_ci}
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_cistatic void solo_buf_queue(struct vb2_buffer *vb)
35062306a36Sopenharmony_ci{
35162306a36Sopenharmony_ci	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
35262306a36Sopenharmony_ci	struct vb2_queue *vq = vb->vb2_queue;
35362306a36Sopenharmony_ci	struct solo_dev *solo_dev = vb2_get_drv_priv(vq);
35462306a36Sopenharmony_ci	struct solo_vb2_buf *solo_vb =
35562306a36Sopenharmony_ci		container_of(vbuf, struct solo_vb2_buf, vb);
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	spin_lock(&solo_dev->slock);
35862306a36Sopenharmony_ci	list_add_tail(&solo_vb->list, &solo_dev->vidq_active);
35962306a36Sopenharmony_ci	spin_unlock(&solo_dev->slock);
36062306a36Sopenharmony_ci	wake_up_interruptible(&solo_dev->disp_thread_wait);
36162306a36Sopenharmony_ci}
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_cistatic const struct vb2_ops solo_video_qops = {
36462306a36Sopenharmony_ci	.queue_setup	= solo_queue_setup,
36562306a36Sopenharmony_ci	.buf_queue	= solo_buf_queue,
36662306a36Sopenharmony_ci	.start_streaming = solo_start_streaming,
36762306a36Sopenharmony_ci	.stop_streaming = solo_stop_streaming,
36862306a36Sopenharmony_ci	.wait_prepare	= vb2_ops_wait_prepare,
36962306a36Sopenharmony_ci	.wait_finish	= vb2_ops_wait_finish,
37062306a36Sopenharmony_ci};
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_cistatic int solo_querycap(struct file *file, void  *priv,
37362306a36Sopenharmony_ci			 struct v4l2_capability *cap)
37462306a36Sopenharmony_ci{
37562306a36Sopenharmony_ci	strscpy(cap->driver, SOLO6X10_NAME, sizeof(cap->driver));
37662306a36Sopenharmony_ci	strscpy(cap->card, "Softlogic 6x10", sizeof(cap->card));
37762306a36Sopenharmony_ci	return 0;
37862306a36Sopenharmony_ci}
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_cistatic int solo_enum_ext_input(struct solo_dev *solo_dev,
38162306a36Sopenharmony_ci			       struct v4l2_input *input)
38262306a36Sopenharmony_ci{
38362306a36Sopenharmony_ci	int ext = input->index - solo_dev->nr_chans;
38462306a36Sopenharmony_ci	unsigned int nup, first;
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	if (ext >= solo_dev->nr_ext)
38762306a36Sopenharmony_ci		return -EINVAL;
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	nup   = (ext == 4) ? 16 : 4;
39062306a36Sopenharmony_ci	first = (ext & 3) << 2; /* first channel in the n-up */
39162306a36Sopenharmony_ci	snprintf(input->name, sizeof(input->name),
39262306a36Sopenharmony_ci		 "Multi %d-up (cameras %d-%d)",
39362306a36Sopenharmony_ci		 nup, first + 1, first + nup);
39462306a36Sopenharmony_ci	/* Possible outputs:
39562306a36Sopenharmony_ci	 *  Multi 4-up (cameras 1-4)
39662306a36Sopenharmony_ci	 *  Multi 4-up (cameras 5-8)
39762306a36Sopenharmony_ci	 *  Multi 4-up (cameras 9-12)
39862306a36Sopenharmony_ci	 *  Multi 4-up (cameras 13-16)
39962306a36Sopenharmony_ci	 *  Multi 16-up (cameras 1-16)
40062306a36Sopenharmony_ci	 */
40162306a36Sopenharmony_ci	return 0;
40262306a36Sopenharmony_ci}
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_cistatic int solo_enum_input(struct file *file, void *priv,
40562306a36Sopenharmony_ci			   struct v4l2_input *input)
40662306a36Sopenharmony_ci{
40762306a36Sopenharmony_ci	struct solo_dev *solo_dev = video_drvdata(file);
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	if (input->index >= solo_dev->nr_chans) {
41062306a36Sopenharmony_ci		int ret = solo_enum_ext_input(solo_dev, input);
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci		if (ret < 0)
41362306a36Sopenharmony_ci			return ret;
41462306a36Sopenharmony_ci	} else {
41562306a36Sopenharmony_ci		snprintf(input->name, sizeof(input->name), "Camera %d",
41662306a36Sopenharmony_ci			 input->index + 1);
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_ci		/* We can only check this for normal inputs */
41962306a36Sopenharmony_ci		if (!tw28_get_video_status(solo_dev, input->index))
42062306a36Sopenharmony_ci			input->status = V4L2_IN_ST_NO_SIGNAL;
42162306a36Sopenharmony_ci	}
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci	input->type = V4L2_INPUT_TYPE_CAMERA;
42462306a36Sopenharmony_ci	input->std = solo_dev->vfd->tvnorms;
42562306a36Sopenharmony_ci	return 0;
42662306a36Sopenharmony_ci}
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_cistatic int solo_set_input(struct file *file, void *priv, unsigned int index)
42962306a36Sopenharmony_ci{
43062306a36Sopenharmony_ci	struct solo_dev *solo_dev = video_drvdata(file);
43162306a36Sopenharmony_ci	int ret = solo_v4l2_set_ch(solo_dev, index);
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	if (!ret) {
43462306a36Sopenharmony_ci		while (erase_off(solo_dev))
43562306a36Sopenharmony_ci			/* Do nothing */;
43662306a36Sopenharmony_ci	}
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci	return ret;
43962306a36Sopenharmony_ci}
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_cistatic int solo_get_input(struct file *file, void *priv, unsigned int *index)
44262306a36Sopenharmony_ci{
44362306a36Sopenharmony_ci	struct solo_dev *solo_dev = video_drvdata(file);
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	*index = solo_dev->cur_disp_ch;
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	return 0;
44862306a36Sopenharmony_ci}
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_cistatic int solo_enum_fmt_cap(struct file *file, void *priv,
45162306a36Sopenharmony_ci			     struct v4l2_fmtdesc *f)
45262306a36Sopenharmony_ci{
45362306a36Sopenharmony_ci	if (f->index)
45462306a36Sopenharmony_ci		return -EINVAL;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	f->pixelformat = V4L2_PIX_FMT_UYVY;
45762306a36Sopenharmony_ci	return 0;
45862306a36Sopenharmony_ci}
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_cistatic int solo_try_fmt_cap(struct file *file, void *priv,
46162306a36Sopenharmony_ci			    struct v4l2_format *f)
46262306a36Sopenharmony_ci{
46362306a36Sopenharmony_ci	struct solo_dev *solo_dev = video_drvdata(file);
46462306a36Sopenharmony_ci	struct v4l2_pix_format *pix = &f->fmt.pix;
46562306a36Sopenharmony_ci	int image_size = solo_image_size(solo_dev);
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	if (pix->pixelformat != V4L2_PIX_FMT_UYVY)
46862306a36Sopenharmony_ci		return -EINVAL;
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci	pix->width = solo_dev->video_hsize;
47162306a36Sopenharmony_ci	pix->height = solo_vlines(solo_dev);
47262306a36Sopenharmony_ci	pix->sizeimage = image_size;
47362306a36Sopenharmony_ci	pix->field = V4L2_FIELD_INTERLACED;
47462306a36Sopenharmony_ci	pix->pixelformat = V4L2_PIX_FMT_UYVY;
47562306a36Sopenharmony_ci	pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
47662306a36Sopenharmony_ci	return 0;
47762306a36Sopenharmony_ci}
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_cistatic int solo_set_fmt_cap(struct file *file, void *priv,
48062306a36Sopenharmony_ci			    struct v4l2_format *f)
48162306a36Sopenharmony_ci{
48262306a36Sopenharmony_ci	struct solo_dev *solo_dev = video_drvdata(file);
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_ci	if (vb2_is_busy(&solo_dev->vidq))
48562306a36Sopenharmony_ci		return -EBUSY;
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci	/* For right now, if it doesn't match our running config,
48862306a36Sopenharmony_ci	 * then fail */
48962306a36Sopenharmony_ci	return solo_try_fmt_cap(file, priv, f);
49062306a36Sopenharmony_ci}
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_cistatic int solo_get_fmt_cap(struct file *file, void *priv,
49362306a36Sopenharmony_ci			    struct v4l2_format *f)
49462306a36Sopenharmony_ci{
49562306a36Sopenharmony_ci	struct solo_dev *solo_dev = video_drvdata(file);
49662306a36Sopenharmony_ci	struct v4l2_pix_format *pix = &f->fmt.pix;
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	pix->width = solo_dev->video_hsize;
49962306a36Sopenharmony_ci	pix->height = solo_vlines(solo_dev);
50062306a36Sopenharmony_ci	pix->pixelformat = V4L2_PIX_FMT_UYVY;
50162306a36Sopenharmony_ci	pix->field = V4L2_FIELD_INTERLACED;
50262306a36Sopenharmony_ci	pix->sizeimage = solo_image_size(solo_dev);
50362306a36Sopenharmony_ci	pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
50462306a36Sopenharmony_ci	pix->bytesperline = solo_bytesperline(solo_dev);
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ci	return 0;
50762306a36Sopenharmony_ci}
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_cistatic int solo_g_std(struct file *file, void *priv, v4l2_std_id *i)
51062306a36Sopenharmony_ci{
51162306a36Sopenharmony_ci	struct solo_dev *solo_dev = video_drvdata(file);
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC)
51462306a36Sopenharmony_ci		*i = V4L2_STD_NTSC_M;
51562306a36Sopenharmony_ci	else
51662306a36Sopenharmony_ci		*i = V4L2_STD_PAL;
51762306a36Sopenharmony_ci	return 0;
51862306a36Sopenharmony_ci}
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ciint solo_set_video_type(struct solo_dev *solo_dev, bool is_50hz)
52162306a36Sopenharmony_ci{
52262306a36Sopenharmony_ci	int i;
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	/* Make sure all video nodes are idle */
52562306a36Sopenharmony_ci	if (vb2_is_busy(&solo_dev->vidq))
52662306a36Sopenharmony_ci		return -EBUSY;
52762306a36Sopenharmony_ci	for (i = 0; i < solo_dev->nr_chans; i++)
52862306a36Sopenharmony_ci		if (vb2_is_busy(&solo_dev->v4l2_enc[i]->vidq))
52962306a36Sopenharmony_ci			return -EBUSY;
53062306a36Sopenharmony_ci	solo_dev->video_type = is_50hz ? SOLO_VO_FMT_TYPE_PAL :
53162306a36Sopenharmony_ci					 SOLO_VO_FMT_TYPE_NTSC;
53262306a36Sopenharmony_ci	/* Reconfigure for the new standard */
53362306a36Sopenharmony_ci	solo_disp_init(solo_dev);
53462306a36Sopenharmony_ci	solo_enc_init(solo_dev);
53562306a36Sopenharmony_ci	solo_tw28_init(solo_dev);
53662306a36Sopenharmony_ci	for (i = 0; i < solo_dev->nr_chans; i++)
53762306a36Sopenharmony_ci		solo_update_mode(solo_dev->v4l2_enc[i]);
53862306a36Sopenharmony_ci	return solo_v4l2_set_ch(solo_dev, solo_dev->cur_disp_ch);
53962306a36Sopenharmony_ci}
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_cistatic int solo_s_std(struct file *file, void *priv, v4l2_std_id std)
54262306a36Sopenharmony_ci{
54362306a36Sopenharmony_ci	struct solo_dev *solo_dev = video_drvdata(file);
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci	return solo_set_video_type(solo_dev, std & V4L2_STD_625_50);
54662306a36Sopenharmony_ci}
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_cistatic int solo_s_ctrl(struct v4l2_ctrl *ctrl)
54962306a36Sopenharmony_ci{
55062306a36Sopenharmony_ci	struct solo_dev *solo_dev =
55162306a36Sopenharmony_ci		container_of(ctrl->handler, struct solo_dev, disp_hdl);
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	switch (ctrl->id) {
55462306a36Sopenharmony_ci	case V4L2_CID_MOTION_TRACE:
55562306a36Sopenharmony_ci		if (ctrl->val) {
55662306a36Sopenharmony_ci			solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER,
55762306a36Sopenharmony_ci					SOLO_VI_MOTION_Y_ADD |
55862306a36Sopenharmony_ci					SOLO_VI_MOTION_Y_VALUE(0x20) |
55962306a36Sopenharmony_ci					SOLO_VI_MOTION_CB_VALUE(0x10) |
56062306a36Sopenharmony_ci					SOLO_VI_MOTION_CR_VALUE(0x10));
56162306a36Sopenharmony_ci			solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR,
56262306a36Sopenharmony_ci					SOLO_VI_MOTION_CR_ADD |
56362306a36Sopenharmony_ci					SOLO_VI_MOTION_Y_VALUE(0x10) |
56462306a36Sopenharmony_ci					SOLO_VI_MOTION_CB_VALUE(0x80) |
56562306a36Sopenharmony_ci					SOLO_VI_MOTION_CR_VALUE(0x10));
56662306a36Sopenharmony_ci		} else {
56762306a36Sopenharmony_ci			solo_reg_write(solo_dev, SOLO_VI_MOTION_BORDER, 0);
56862306a36Sopenharmony_ci			solo_reg_write(solo_dev, SOLO_VI_MOTION_BAR, 0);
56962306a36Sopenharmony_ci		}
57062306a36Sopenharmony_ci		return 0;
57162306a36Sopenharmony_ci	default:
57262306a36Sopenharmony_ci		break;
57362306a36Sopenharmony_ci	}
57462306a36Sopenharmony_ci	return -EINVAL;
57562306a36Sopenharmony_ci}
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_cistatic const struct v4l2_file_operations solo_v4l2_fops = {
57862306a36Sopenharmony_ci	.owner			= THIS_MODULE,
57962306a36Sopenharmony_ci	.open			= v4l2_fh_open,
58062306a36Sopenharmony_ci	.release		= vb2_fop_release,
58162306a36Sopenharmony_ci	.read			= vb2_fop_read,
58262306a36Sopenharmony_ci	.poll			= vb2_fop_poll,
58362306a36Sopenharmony_ci	.mmap			= vb2_fop_mmap,
58462306a36Sopenharmony_ci	.unlocked_ioctl		= video_ioctl2,
58562306a36Sopenharmony_ci};
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops solo_v4l2_ioctl_ops = {
58862306a36Sopenharmony_ci	.vidioc_querycap		= solo_querycap,
58962306a36Sopenharmony_ci	.vidioc_s_std			= solo_s_std,
59062306a36Sopenharmony_ci	.vidioc_g_std			= solo_g_std,
59162306a36Sopenharmony_ci	/* Input callbacks */
59262306a36Sopenharmony_ci	.vidioc_enum_input		= solo_enum_input,
59362306a36Sopenharmony_ci	.vidioc_s_input			= solo_set_input,
59462306a36Sopenharmony_ci	.vidioc_g_input			= solo_get_input,
59562306a36Sopenharmony_ci	/* Video capture format callbacks */
59662306a36Sopenharmony_ci	.vidioc_enum_fmt_vid_cap	= solo_enum_fmt_cap,
59762306a36Sopenharmony_ci	.vidioc_try_fmt_vid_cap		= solo_try_fmt_cap,
59862306a36Sopenharmony_ci	.vidioc_s_fmt_vid_cap		= solo_set_fmt_cap,
59962306a36Sopenharmony_ci	.vidioc_g_fmt_vid_cap		= solo_get_fmt_cap,
60062306a36Sopenharmony_ci	/* Streaming I/O */
60162306a36Sopenharmony_ci	.vidioc_reqbufs			= vb2_ioctl_reqbufs,
60262306a36Sopenharmony_ci	.vidioc_querybuf		= vb2_ioctl_querybuf,
60362306a36Sopenharmony_ci	.vidioc_qbuf			= vb2_ioctl_qbuf,
60462306a36Sopenharmony_ci	.vidioc_dqbuf			= vb2_ioctl_dqbuf,
60562306a36Sopenharmony_ci	.vidioc_streamon		= vb2_ioctl_streamon,
60662306a36Sopenharmony_ci	.vidioc_streamoff		= vb2_ioctl_streamoff,
60762306a36Sopenharmony_ci	/* Logging and events */
60862306a36Sopenharmony_ci	.vidioc_log_status		= v4l2_ctrl_log_status,
60962306a36Sopenharmony_ci	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
61062306a36Sopenharmony_ci	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
61162306a36Sopenharmony_ci};
61262306a36Sopenharmony_ci
61362306a36Sopenharmony_cistatic const struct video_device solo_v4l2_template = {
61462306a36Sopenharmony_ci	.name			= SOLO6X10_NAME,
61562306a36Sopenharmony_ci	.fops			= &solo_v4l2_fops,
61662306a36Sopenharmony_ci	.ioctl_ops		= &solo_v4l2_ioctl_ops,
61762306a36Sopenharmony_ci	.minor			= -1,
61862306a36Sopenharmony_ci	.release		= video_device_release,
61962306a36Sopenharmony_ci	.tvnorms		= V4L2_STD_NTSC_M | V4L2_STD_PAL,
62062306a36Sopenharmony_ci	.device_caps		= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE |
62162306a36Sopenharmony_ci				  V4L2_CAP_STREAMING,
62262306a36Sopenharmony_ci};
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops solo_ctrl_ops = {
62562306a36Sopenharmony_ci	.s_ctrl = solo_s_ctrl,
62662306a36Sopenharmony_ci};
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_cistatic const struct v4l2_ctrl_config solo_motion_trace_ctrl = {
62962306a36Sopenharmony_ci	.ops = &solo_ctrl_ops,
63062306a36Sopenharmony_ci	.id = V4L2_CID_MOTION_TRACE,
63162306a36Sopenharmony_ci	.name = "Motion Detection Trace",
63262306a36Sopenharmony_ci	.type = V4L2_CTRL_TYPE_BOOLEAN,
63362306a36Sopenharmony_ci	.max = 1,
63462306a36Sopenharmony_ci	.step = 1,
63562306a36Sopenharmony_ci};
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ciint solo_v4l2_init(struct solo_dev *solo_dev, unsigned nr)
63862306a36Sopenharmony_ci{
63962306a36Sopenharmony_ci	int ret;
64062306a36Sopenharmony_ci	int i;
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_ci	init_waitqueue_head(&solo_dev->disp_thread_wait);
64362306a36Sopenharmony_ci	spin_lock_init(&solo_dev->slock);
64462306a36Sopenharmony_ci	mutex_init(&solo_dev->lock);
64562306a36Sopenharmony_ci	INIT_LIST_HEAD(&solo_dev->vidq_active);
64662306a36Sopenharmony_ci
64762306a36Sopenharmony_ci	solo_dev->vfd = video_device_alloc();
64862306a36Sopenharmony_ci	if (!solo_dev->vfd)
64962306a36Sopenharmony_ci		return -ENOMEM;
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_ci	*solo_dev->vfd = solo_v4l2_template;
65262306a36Sopenharmony_ci	solo_dev->vfd->v4l2_dev = &solo_dev->v4l2_dev;
65362306a36Sopenharmony_ci	solo_dev->vfd->queue = &solo_dev->vidq;
65462306a36Sopenharmony_ci	solo_dev->vfd->lock = &solo_dev->lock;
65562306a36Sopenharmony_ci	v4l2_ctrl_handler_init(&solo_dev->disp_hdl, 1);
65662306a36Sopenharmony_ci	v4l2_ctrl_new_custom(&solo_dev->disp_hdl, &solo_motion_trace_ctrl, NULL);
65762306a36Sopenharmony_ci	if (solo_dev->disp_hdl.error) {
65862306a36Sopenharmony_ci		ret = solo_dev->disp_hdl.error;
65962306a36Sopenharmony_ci		goto fail;
66062306a36Sopenharmony_ci	}
66162306a36Sopenharmony_ci	solo_dev->vfd->ctrl_handler = &solo_dev->disp_hdl;
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_ci	video_set_drvdata(solo_dev->vfd, solo_dev);
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_ci	solo_dev->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
66662306a36Sopenharmony_ci	solo_dev->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
66762306a36Sopenharmony_ci	solo_dev->vidq.ops = &solo_video_qops;
66862306a36Sopenharmony_ci	solo_dev->vidq.mem_ops = &vb2_dma_contig_memops;
66962306a36Sopenharmony_ci	solo_dev->vidq.drv_priv = solo_dev;
67062306a36Sopenharmony_ci	solo_dev->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
67162306a36Sopenharmony_ci	solo_dev->vidq.gfp_flags = __GFP_DMA32 | __GFP_KSWAPD_RECLAIM;
67262306a36Sopenharmony_ci	solo_dev->vidq.buf_struct_size = sizeof(struct solo_vb2_buf);
67362306a36Sopenharmony_ci	solo_dev->vidq.lock = &solo_dev->lock;
67462306a36Sopenharmony_ci	solo_dev->vidq.dev = &solo_dev->pdev->dev;
67562306a36Sopenharmony_ci	ret = vb2_queue_init(&solo_dev->vidq);
67662306a36Sopenharmony_ci	if (ret < 0)
67762306a36Sopenharmony_ci		goto fail;
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_ci	/* Cycle all the channels and clear */
68062306a36Sopenharmony_ci	for (i = 0; i < solo_dev->nr_chans; i++) {
68162306a36Sopenharmony_ci		solo_v4l2_set_ch(solo_dev, i);
68262306a36Sopenharmony_ci		while (erase_off(solo_dev))
68362306a36Sopenharmony_ci			/* Do nothing */;
68462306a36Sopenharmony_ci	}
68562306a36Sopenharmony_ci
68662306a36Sopenharmony_ci	/* Set the default display channel */
68762306a36Sopenharmony_ci	solo_v4l2_set_ch(solo_dev, 0);
68862306a36Sopenharmony_ci	while (erase_off(solo_dev))
68962306a36Sopenharmony_ci		/* Do nothing */;
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_ci	ret = video_register_device(solo_dev->vfd, VFL_TYPE_VIDEO, nr);
69262306a36Sopenharmony_ci	if (ret < 0)
69362306a36Sopenharmony_ci		goto fail;
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_ci	snprintf(solo_dev->vfd->name, sizeof(solo_dev->vfd->name), "%s (%i)",
69662306a36Sopenharmony_ci		 SOLO6X10_NAME, solo_dev->vfd->num);
69762306a36Sopenharmony_ci
69862306a36Sopenharmony_ci	dev_info(&solo_dev->pdev->dev, "Display as /dev/video%d with %d inputs (%d extended)\n",
69962306a36Sopenharmony_ci		 solo_dev->vfd->num,
70062306a36Sopenharmony_ci		 solo_dev->nr_chans, solo_dev->nr_ext);
70162306a36Sopenharmony_ci
70262306a36Sopenharmony_ci	return 0;
70362306a36Sopenharmony_ci
70462306a36Sopenharmony_cifail:
70562306a36Sopenharmony_ci	video_device_release(solo_dev->vfd);
70662306a36Sopenharmony_ci	v4l2_ctrl_handler_free(&solo_dev->disp_hdl);
70762306a36Sopenharmony_ci	solo_dev->vfd = NULL;
70862306a36Sopenharmony_ci	return ret;
70962306a36Sopenharmony_ci}
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_civoid solo_v4l2_exit(struct solo_dev *solo_dev)
71262306a36Sopenharmony_ci{
71362306a36Sopenharmony_ci	if (solo_dev->vfd == NULL)
71462306a36Sopenharmony_ci		return;
71562306a36Sopenharmony_ci
71662306a36Sopenharmony_ci	video_unregister_device(solo_dev->vfd);
71762306a36Sopenharmony_ci	v4l2_ctrl_handler_free(&solo_dev->disp_hdl);
71862306a36Sopenharmony_ci	solo_dev->vfd = NULL;
71962306a36Sopenharmony_ci}
720