162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <media/drv-intf/saa7146_vv.h>
562306a36Sopenharmony_ci#include <linux/module.h>
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci/****************************************************************************/
862306a36Sopenharmony_ci/* resource management functions, shamelessly stolen from saa7134 driver */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ciint saa7146_res_get(struct saa7146_dev *dev, unsigned int bit)
1162306a36Sopenharmony_ci{
1262306a36Sopenharmony_ci	struct saa7146_vv *vv = dev->vv_data;
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci	if (vv->resources & bit) {
1562306a36Sopenharmony_ci		DEB_D("already allocated! want: 0x%02x, cur:0x%02x\n",
1662306a36Sopenharmony_ci		      bit, vv->resources);
1762306a36Sopenharmony_ci		/* have it already allocated */
1862306a36Sopenharmony_ci		return 1;
1962306a36Sopenharmony_ci	}
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci	/* is it free? */
2262306a36Sopenharmony_ci	if (vv->resources & bit) {
2362306a36Sopenharmony_ci		DEB_D("locked! vv->resources:0x%02x, we want:0x%02x\n",
2462306a36Sopenharmony_ci		      vv->resources, bit);
2562306a36Sopenharmony_ci		/* no, someone else uses it */
2662306a36Sopenharmony_ci		return 0;
2762306a36Sopenharmony_ci	}
2862306a36Sopenharmony_ci	/* it's free, grab it */
2962306a36Sopenharmony_ci	vv->resources |= bit;
3062306a36Sopenharmony_ci	DEB_D("res: get 0x%02x, cur:0x%02x\n", bit, vv->resources);
3162306a36Sopenharmony_ci	return 1;
3262306a36Sopenharmony_ci}
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_civoid saa7146_res_free(struct saa7146_dev *dev, unsigned int bits)
3562306a36Sopenharmony_ci{
3662306a36Sopenharmony_ci	struct saa7146_vv *vv = dev->vv_data;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	WARN_ON((vv->resources & bits) != bits);
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	vv->resources &= ~bits;
4162306a36Sopenharmony_ci	DEB_D("res: put 0x%02x, cur:0x%02x\n", bits, vv->resources);
4262306a36Sopenharmony_ci}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci/********************************************************************************/
4662306a36Sopenharmony_ci/* common buffer functions */
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ciint saa7146_buffer_queue(struct saa7146_dev *dev,
4962306a36Sopenharmony_ci			 struct saa7146_dmaqueue *q,
5062306a36Sopenharmony_ci			 struct saa7146_buf *buf)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	assert_spin_locked(&dev->slock);
5362306a36Sopenharmony_ci	DEB_EE("dev:%p, dmaq:%p, buf:%p\n", dev, q, buf);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	if (WARN_ON(!q))
5662306a36Sopenharmony_ci		return -EIO;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	if (NULL == q->curr) {
5962306a36Sopenharmony_ci		q->curr = buf;
6062306a36Sopenharmony_ci		DEB_D("immediately activating buffer %p\n", buf);
6162306a36Sopenharmony_ci		buf->activate(dev,buf,NULL);
6262306a36Sopenharmony_ci	} else {
6362306a36Sopenharmony_ci		list_add_tail(&buf->list, &q->queue);
6462306a36Sopenharmony_ci		DEB_D("adding buffer %p to queue. (active buffer present)\n",
6562306a36Sopenharmony_ci		      buf);
6662306a36Sopenharmony_ci	}
6762306a36Sopenharmony_ci	return 0;
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_civoid saa7146_buffer_finish(struct saa7146_dev *dev,
7162306a36Sopenharmony_ci			   struct saa7146_dmaqueue *q,
7262306a36Sopenharmony_ci			   int state)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	struct saa7146_vv *vv = dev->vv_data;
7562306a36Sopenharmony_ci	struct saa7146_buf *buf = q->curr;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	assert_spin_locked(&dev->slock);
7862306a36Sopenharmony_ci	DEB_EE("dev:%p, dmaq:%p, state:%d\n", dev, q, state);
7962306a36Sopenharmony_ci	DEB_EE("q->curr:%p\n", q->curr);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	/* finish current buffer */
8262306a36Sopenharmony_ci	if (!buf) {
8362306a36Sopenharmony_ci		DEB_D("aiii. no current buffer\n");
8462306a36Sopenharmony_ci		return;
8562306a36Sopenharmony_ci	}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	q->curr = NULL;
8862306a36Sopenharmony_ci	buf->vb.vb2_buf.timestamp = ktime_get_ns();
8962306a36Sopenharmony_ci	if (vv->video_fmt.field == V4L2_FIELD_ALTERNATE)
9062306a36Sopenharmony_ci		buf->vb.field = vv->last_field;
9162306a36Sopenharmony_ci	else if (vv->video_fmt.field == V4L2_FIELD_ANY)
9262306a36Sopenharmony_ci		buf->vb.field = (vv->video_fmt.height > vv->standard->v_max_out / 2)
9362306a36Sopenharmony_ci			? V4L2_FIELD_INTERLACED
9462306a36Sopenharmony_ci			: V4L2_FIELD_BOTTOM;
9562306a36Sopenharmony_ci	else
9662306a36Sopenharmony_ci		buf->vb.field = vv->video_fmt.field;
9762306a36Sopenharmony_ci	buf->vb.sequence = vv->seqnr++;
9862306a36Sopenharmony_ci	vb2_buffer_done(&buf->vb.vb2_buf, state);
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_civoid saa7146_buffer_next(struct saa7146_dev *dev,
10262306a36Sopenharmony_ci			 struct saa7146_dmaqueue *q, int vbi)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	struct saa7146_buf *buf,*next = NULL;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	if (WARN_ON(!q))
10762306a36Sopenharmony_ci		return;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	DEB_INT("dev:%p, dmaq:%p, vbi:%d\n", dev, q, vbi);
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	assert_spin_locked(&dev->slock);
11262306a36Sopenharmony_ci	if (!list_empty(&q->queue)) {
11362306a36Sopenharmony_ci		/* activate next one from queue */
11462306a36Sopenharmony_ci		buf = list_entry(q->queue.next, struct saa7146_buf, list);
11562306a36Sopenharmony_ci		list_del(&buf->list);
11662306a36Sopenharmony_ci		if (!list_empty(&q->queue))
11762306a36Sopenharmony_ci			next = list_entry(q->queue.next, struct saa7146_buf, list);
11862306a36Sopenharmony_ci		q->curr = buf;
11962306a36Sopenharmony_ci		DEB_INT("next buffer: buf:%p, prev:%p, next:%p\n",
12062306a36Sopenharmony_ci			buf, q->queue.prev, q->queue.next);
12162306a36Sopenharmony_ci		buf->activate(dev,buf,next);
12262306a36Sopenharmony_ci	} else {
12362306a36Sopenharmony_ci		DEB_INT("no next buffer. stopping.\n");
12462306a36Sopenharmony_ci		if( 0 != vbi ) {
12562306a36Sopenharmony_ci			/* turn off video-dma3 */
12662306a36Sopenharmony_ci			saa7146_write(dev,MC1, MASK_20);
12762306a36Sopenharmony_ci		} else {
12862306a36Sopenharmony_ci			/* nothing to do -- just prevent next video-dma1 transfer
12962306a36Sopenharmony_ci			   by lowering the protection address */
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci			// fixme: fix this for vflip != 0
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci			saa7146_write(dev, PROT_ADDR1, 0);
13462306a36Sopenharmony_ci			saa7146_write(dev, MC2, (MASK_02|MASK_18));
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci			/* write the address of the rps-program */
13762306a36Sopenharmony_ci			saa7146_write(dev, RPS_ADDR0, dev->d_rps0.dma_handle);
13862306a36Sopenharmony_ci			/* turn on rps */
13962306a36Sopenharmony_ci			saa7146_write(dev, MC1, (MASK_12 | MASK_28));
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci/*
14262306a36Sopenharmony_ci			printk("vdma%d.base_even:     0x%08x\n", 1,saa7146_read(dev,BASE_EVEN1));
14362306a36Sopenharmony_ci			printk("vdma%d.base_odd:      0x%08x\n", 1,saa7146_read(dev,BASE_ODD1));
14462306a36Sopenharmony_ci			printk("vdma%d.prot_addr:     0x%08x\n", 1,saa7146_read(dev,PROT_ADDR1));
14562306a36Sopenharmony_ci			printk("vdma%d.base_page:     0x%08x\n", 1,saa7146_read(dev,BASE_PAGE1));
14662306a36Sopenharmony_ci			printk("vdma%d.pitch:         0x%08x\n", 1,saa7146_read(dev,PITCH1));
14762306a36Sopenharmony_ci			printk("vdma%d.num_line_byte: 0x%08x\n", 1,saa7146_read(dev,NUM_LINE_BYTE1));
14862306a36Sopenharmony_ci*/
14962306a36Sopenharmony_ci		}
15062306a36Sopenharmony_ci		del_timer(&q->timeout);
15162306a36Sopenharmony_ci	}
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_civoid saa7146_buffer_timeout(struct timer_list *t)
15562306a36Sopenharmony_ci{
15662306a36Sopenharmony_ci	struct saa7146_dmaqueue *q = from_timer(q, t, timeout);
15762306a36Sopenharmony_ci	struct saa7146_dev *dev = q->dev;
15862306a36Sopenharmony_ci	unsigned long flags;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	DEB_EE("dev:%p, dmaq:%p\n", dev, q);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	spin_lock_irqsave(&dev->slock,flags);
16362306a36Sopenharmony_ci	if (q->curr) {
16462306a36Sopenharmony_ci		DEB_D("timeout on %p\n", q->curr);
16562306a36Sopenharmony_ci		saa7146_buffer_finish(dev, q, VB2_BUF_STATE_ERROR);
16662306a36Sopenharmony_ci	}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	/* we don't restart the transfer here like other drivers do. when
16962306a36Sopenharmony_ci	   a streaming capture is disabled, the timeout function will be
17062306a36Sopenharmony_ci	   called for the current buffer. if we activate the next buffer now,
17162306a36Sopenharmony_ci	   we mess up our capture logic. if a timeout occurs on another buffer,
17262306a36Sopenharmony_ci	   then something is seriously broken before, so no need to buffer the
17362306a36Sopenharmony_ci	   next capture IMHO... */
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	saa7146_buffer_next(dev, q, 0);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	spin_unlock_irqrestore(&dev->slock,flags);
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci/********************************************************************************/
18162306a36Sopenharmony_ci/* file operations */
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic ssize_t fops_write(struct file *file, const char __user *data, size_t count, loff_t *ppos)
18462306a36Sopenharmony_ci{
18562306a36Sopenharmony_ci	struct video_device *vdev = video_devdata(file);
18662306a36Sopenharmony_ci	struct saa7146_dev *dev = video_drvdata(file);
18762306a36Sopenharmony_ci	int ret;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	if (vdev->vfl_type != VFL_TYPE_VBI || !dev->ext_vv_data->vbi_fops.write)
19062306a36Sopenharmony_ci		return -EINVAL;
19162306a36Sopenharmony_ci	if (mutex_lock_interruptible(vdev->lock))
19262306a36Sopenharmony_ci		return -ERESTARTSYS;
19362306a36Sopenharmony_ci	ret = dev->ext_vv_data->vbi_fops.write(file, data, count, ppos);
19462306a36Sopenharmony_ci	mutex_unlock(vdev->lock);
19562306a36Sopenharmony_ci	return ret;
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic const struct v4l2_file_operations video_fops =
19962306a36Sopenharmony_ci{
20062306a36Sopenharmony_ci	.owner		= THIS_MODULE,
20162306a36Sopenharmony_ci	.open		= v4l2_fh_open,
20262306a36Sopenharmony_ci	.release	= vb2_fop_release,
20362306a36Sopenharmony_ci	.read		= vb2_fop_read,
20462306a36Sopenharmony_ci	.write		= fops_write,
20562306a36Sopenharmony_ci	.poll		= vb2_fop_poll,
20662306a36Sopenharmony_ci	.mmap		= vb2_fop_mmap,
20762306a36Sopenharmony_ci	.unlocked_ioctl	= video_ioctl2,
20862306a36Sopenharmony_ci};
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_cistatic void vv_callback(struct saa7146_dev *dev, unsigned long status)
21162306a36Sopenharmony_ci{
21262306a36Sopenharmony_ci	u32 isr = status;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	DEB_INT("dev:%p, isr:0x%08x\n", dev, (u32)status);
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	if (0 != (isr & (MASK_27))) {
21762306a36Sopenharmony_ci		DEB_INT("irq: RPS0 (0x%08x)\n", isr);
21862306a36Sopenharmony_ci		saa7146_video_uops.irq_done(dev,isr);
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	if (0 != (isr & (MASK_28))) {
22262306a36Sopenharmony_ci		u32 mc2 = saa7146_read(dev, MC2);
22362306a36Sopenharmony_ci		if( 0 != (mc2 & MASK_15)) {
22462306a36Sopenharmony_ci			DEB_INT("irq: RPS1 vbi workaround (0x%08x)\n", isr);
22562306a36Sopenharmony_ci			wake_up(&dev->vv_data->vbi_wq);
22662306a36Sopenharmony_ci			saa7146_write(dev,MC2, MASK_31);
22762306a36Sopenharmony_ci			return;
22862306a36Sopenharmony_ci		}
22962306a36Sopenharmony_ci		DEB_INT("irq: RPS1 (0x%08x)\n", isr);
23062306a36Sopenharmony_ci		saa7146_vbi_uops.irq_done(dev,isr);
23162306a36Sopenharmony_ci	}
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops saa7146_ctrl_ops = {
23562306a36Sopenharmony_ci	.s_ctrl = saa7146_s_ctrl,
23662306a36Sopenharmony_ci};
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ciint saa7146_vv_init(struct saa7146_dev* dev, struct saa7146_ext_vv *ext_vv)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler;
24162306a36Sopenharmony_ci	struct v4l2_pix_format *fmt;
24262306a36Sopenharmony_ci	struct v4l2_vbi_format *vbi;
24362306a36Sopenharmony_ci	struct saa7146_vv *vv;
24462306a36Sopenharmony_ci	int err;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	err = v4l2_device_register(&dev->pci->dev, &dev->v4l2_dev);
24762306a36Sopenharmony_ci	if (err)
24862306a36Sopenharmony_ci		return err;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	v4l2_ctrl_handler_init(hdl, 6);
25162306a36Sopenharmony_ci	v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops,
25262306a36Sopenharmony_ci		V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
25362306a36Sopenharmony_ci	v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops,
25462306a36Sopenharmony_ci		V4L2_CID_CONTRAST, 0, 127, 1, 64);
25562306a36Sopenharmony_ci	v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops,
25662306a36Sopenharmony_ci		V4L2_CID_SATURATION, 0, 127, 1, 64);
25762306a36Sopenharmony_ci	v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops,
25862306a36Sopenharmony_ci		V4L2_CID_VFLIP, 0, 1, 1, 0);
25962306a36Sopenharmony_ci	v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops,
26062306a36Sopenharmony_ci		V4L2_CID_HFLIP, 0, 1, 1, 0);
26162306a36Sopenharmony_ci	if (hdl->error) {
26262306a36Sopenharmony_ci		err = hdl->error;
26362306a36Sopenharmony_ci		v4l2_ctrl_handler_free(hdl);
26462306a36Sopenharmony_ci		v4l2_device_unregister(&dev->v4l2_dev);
26562306a36Sopenharmony_ci		return err;
26662306a36Sopenharmony_ci	}
26762306a36Sopenharmony_ci	dev->v4l2_dev.ctrl_handler = hdl;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	vv = kzalloc(sizeof(struct saa7146_vv), GFP_KERNEL);
27062306a36Sopenharmony_ci	if (vv == NULL) {
27162306a36Sopenharmony_ci		ERR("out of memory. aborting.\n");
27262306a36Sopenharmony_ci		v4l2_ctrl_handler_free(hdl);
27362306a36Sopenharmony_ci		v4l2_device_unregister(&dev->v4l2_dev);
27462306a36Sopenharmony_ci		return -ENOMEM;
27562306a36Sopenharmony_ci	}
27662306a36Sopenharmony_ci	ext_vv->vid_ops = saa7146_video_ioctl_ops;
27762306a36Sopenharmony_ci	ext_vv->vbi_ops = saa7146_vbi_ioctl_ops;
27862306a36Sopenharmony_ci	ext_vv->core_ops = &saa7146_video_ioctl_ops;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	DEB_EE("dev:%p\n", dev);
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	/* set default values for video parts of the saa7146 */
28362306a36Sopenharmony_ci	saa7146_write(dev, BCS_CTRL, 0x80400040);
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	/* enable video-port pins */
28662306a36Sopenharmony_ci	saa7146_write(dev, MC1, (MASK_10 | MASK_26));
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	/* save per-device extension data (one extension can
28962306a36Sopenharmony_ci	   handle different devices that might need different
29062306a36Sopenharmony_ci	   configuration data) */
29162306a36Sopenharmony_ci	dev->ext_vv_data = ext_vv;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	saa7146_video_uops.init(dev,vv);
29462306a36Sopenharmony_ci	if (dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE)
29562306a36Sopenharmony_ci		saa7146_vbi_uops.init(dev,vv);
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	fmt = &vv->video_fmt;
29862306a36Sopenharmony_ci	fmt->width = 384;
29962306a36Sopenharmony_ci	fmt->height = 288;
30062306a36Sopenharmony_ci	fmt->pixelformat = V4L2_PIX_FMT_BGR24;
30162306a36Sopenharmony_ci	fmt->field = V4L2_FIELD_INTERLACED;
30262306a36Sopenharmony_ci	fmt->colorspace = V4L2_COLORSPACE_SMPTE170M;
30362306a36Sopenharmony_ci	fmt->bytesperline = 3 * fmt->width;
30462306a36Sopenharmony_ci	fmt->sizeimage = fmt->bytesperline * fmt->height;
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	vbi = &vv->vbi_fmt;
30762306a36Sopenharmony_ci	vbi->sampling_rate	= 27000000;
30862306a36Sopenharmony_ci	vbi->offset		= 248; /* todo */
30962306a36Sopenharmony_ci	vbi->samples_per_line	= 720 * 2;
31062306a36Sopenharmony_ci	vbi->sample_format	= V4L2_PIX_FMT_GREY;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	/* fixme: this only works for PAL */
31362306a36Sopenharmony_ci	vbi->start[0] = 5;
31462306a36Sopenharmony_ci	vbi->count[0] = 16;
31562306a36Sopenharmony_ci	vbi->start[1] = 312;
31662306a36Sopenharmony_ci	vbi->count[1] = 16;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	timer_setup(&vv->vbi_read_timeout, NULL, 0);
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	dev->vv_data = vv;
32162306a36Sopenharmony_ci	dev->vv_callback = &vv_callback;
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	return 0;
32462306a36Sopenharmony_ci}
32562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(saa7146_vv_init);
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ciint saa7146_vv_release(struct saa7146_dev* dev)
32862306a36Sopenharmony_ci{
32962306a36Sopenharmony_ci	struct saa7146_vv *vv = dev->vv_data;
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	DEB_EE("dev:%p\n", dev);
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	v4l2_device_unregister(&dev->v4l2_dev);
33462306a36Sopenharmony_ci	v4l2_ctrl_handler_free(&dev->ctrl_handler);
33562306a36Sopenharmony_ci	kfree(vv);
33662306a36Sopenharmony_ci	dev->vv_data = NULL;
33762306a36Sopenharmony_ci	dev->vv_callback = NULL;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	return 0;
34062306a36Sopenharmony_ci}
34162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(saa7146_vv_release);
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ciint saa7146_register_device(struct video_device *vfd, struct saa7146_dev *dev,
34462306a36Sopenharmony_ci			    char *name, int type)
34562306a36Sopenharmony_ci{
34662306a36Sopenharmony_ci	struct vb2_queue *q;
34762306a36Sopenharmony_ci	int err;
34862306a36Sopenharmony_ci	int i;
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	DEB_EE("dev:%p, name:'%s', type:%d\n", dev, name, type);
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	vfd->fops = &video_fops;
35362306a36Sopenharmony_ci	if (type == VFL_TYPE_VIDEO) {
35462306a36Sopenharmony_ci		vfd->ioctl_ops = &dev->ext_vv_data->vid_ops;
35562306a36Sopenharmony_ci		q = &dev->vv_data->video_dmaq.q;
35662306a36Sopenharmony_ci	} else {
35762306a36Sopenharmony_ci		vfd->ioctl_ops = &dev->ext_vv_data->vbi_ops;
35862306a36Sopenharmony_ci		q = &dev->vv_data->vbi_dmaq.q;
35962306a36Sopenharmony_ci	}
36062306a36Sopenharmony_ci	vfd->release = video_device_release_empty;
36162306a36Sopenharmony_ci	vfd->lock = &dev->v4l2_lock;
36262306a36Sopenharmony_ci	vfd->v4l2_dev = &dev->v4l2_dev;
36362306a36Sopenharmony_ci	vfd->tvnorms = 0;
36462306a36Sopenharmony_ci	for (i = 0; i < dev->ext_vv_data->num_stds; i++)
36562306a36Sopenharmony_ci		vfd->tvnorms |= dev->ext_vv_data->stds[i].id;
36662306a36Sopenharmony_ci	strscpy(vfd->name, name, sizeof(vfd->name));
36762306a36Sopenharmony_ci	vfd->device_caps = V4L2_CAP_VIDEO_CAPTURE |
36862306a36Sopenharmony_ci			   V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
36962306a36Sopenharmony_ci	vfd->device_caps |= dev->ext_vv_data->capabilities;
37062306a36Sopenharmony_ci	if (type == VFL_TYPE_VIDEO) {
37162306a36Sopenharmony_ci		vfd->device_caps &=
37262306a36Sopenharmony_ci			~(V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_OUTPUT);
37362306a36Sopenharmony_ci	} else if (vfd->device_caps & V4L2_CAP_SLICED_VBI_OUTPUT) {
37462306a36Sopenharmony_ci		vfd->vfl_dir = VFL_DIR_TX;
37562306a36Sopenharmony_ci		vfd->device_caps &= ~(V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
37662306a36Sopenharmony_ci				      V4L2_CAP_AUDIO | V4L2_CAP_TUNER);
37762306a36Sopenharmony_ci	} else {
37862306a36Sopenharmony_ci		vfd->device_caps &= ~V4L2_CAP_VIDEO_CAPTURE;
37962306a36Sopenharmony_ci	}
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	q->type = type == VFL_TYPE_VIDEO ? V4L2_BUF_TYPE_VIDEO_CAPTURE : V4L2_BUF_TYPE_VBI_CAPTURE;
38262306a36Sopenharmony_ci	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
38362306a36Sopenharmony_ci	q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF;
38462306a36Sopenharmony_ci	q->ops = type == VFL_TYPE_VIDEO ? &video_qops : &vbi_qops;
38562306a36Sopenharmony_ci	q->mem_ops = &vb2_dma_sg_memops;
38662306a36Sopenharmony_ci	q->drv_priv = dev;
38762306a36Sopenharmony_ci	q->gfp_flags = __GFP_DMA32;
38862306a36Sopenharmony_ci	q->buf_struct_size = sizeof(struct saa7146_buf);
38962306a36Sopenharmony_ci	q->lock = &dev->v4l2_lock;
39062306a36Sopenharmony_ci	q->min_buffers_needed = 2;
39162306a36Sopenharmony_ci	q->dev = &dev->pci->dev;
39262306a36Sopenharmony_ci	err = vb2_queue_init(q);
39362306a36Sopenharmony_ci	if (err)
39462306a36Sopenharmony_ci		return err;
39562306a36Sopenharmony_ci	vfd->queue = q;
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	video_set_drvdata(vfd, dev);
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	err = video_register_device(vfd, type, -1);
40062306a36Sopenharmony_ci	if (err < 0) {
40162306a36Sopenharmony_ci		ERR("cannot register v4l2 device. skipping.\n");
40262306a36Sopenharmony_ci		return err;
40362306a36Sopenharmony_ci	}
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	pr_info("%s: registered device %s [v4l2]\n",
40662306a36Sopenharmony_ci		dev->name, video_device_node_name(vfd));
40762306a36Sopenharmony_ci	return 0;
40862306a36Sopenharmony_ci}
40962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(saa7146_register_device);
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ciint saa7146_unregister_device(struct video_device *vfd, struct saa7146_dev *dev)
41262306a36Sopenharmony_ci{
41362306a36Sopenharmony_ci	DEB_EE("dev:%p\n", dev);
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	video_unregister_device(vfd);
41662306a36Sopenharmony_ci	return 0;
41762306a36Sopenharmony_ci}
41862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(saa7146_unregister_device);
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_cistatic int __init saa7146_vv_init_module(void)
42162306a36Sopenharmony_ci{
42262306a36Sopenharmony_ci	return 0;
42362306a36Sopenharmony_ci}
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_cistatic void __exit saa7146_vv_cleanup_module(void)
42762306a36Sopenharmony_ci{
42862306a36Sopenharmony_ci}
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_cimodule_init(saa7146_vv_init_module);
43162306a36Sopenharmony_cimodule_exit(saa7146_vv_cleanup_module);
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ciMODULE_AUTHOR("Michael Hunold <michael@mihu.de>");
43462306a36Sopenharmony_ciMODULE_DESCRIPTION("video4linux driver for saa7146-based hardware");
43562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
436