1ee4a77a3SMauro Carvalho Chehab // SPDX-License-Identifier: GPL-2.0
2ee4a77a3SMauro Carvalho Chehab /*
3ee4a77a3SMauro Carvalho Chehab  * SuperH Video Output Unit (VOU) driver
4ee4a77a3SMauro Carvalho Chehab  *
5ee4a77a3SMauro Carvalho Chehab  * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
6ee4a77a3SMauro Carvalho Chehab  */
7ee4a77a3SMauro Carvalho Chehab 
8ee4a77a3SMauro Carvalho Chehab #include <linux/dma-mapping.h>
9ee4a77a3SMauro Carvalho Chehab #include <linux/delay.h>
10ee4a77a3SMauro Carvalho Chehab #include <linux/errno.h>
11ee4a77a3SMauro Carvalho Chehab #include <linux/fs.h>
12ee4a77a3SMauro Carvalho Chehab #include <linux/i2c.h>
13ee4a77a3SMauro Carvalho Chehab #include <linux/init.h>
14ee4a77a3SMauro Carvalho Chehab #include <linux/interrupt.h>
15ee4a77a3SMauro Carvalho Chehab #include <linux/kernel.h>
16ee4a77a3SMauro Carvalho Chehab #include <linux/platform_device.h>
17ee4a77a3SMauro Carvalho Chehab #include <linux/pm_runtime.h>
18ee4a77a3SMauro Carvalho Chehab #include <linux/slab.h>
19ee4a77a3SMauro Carvalho Chehab #include <linux/videodev2.h>
20ee4a77a3SMauro Carvalho Chehab #include <linux/module.h>
21ee4a77a3SMauro Carvalho Chehab 
22ee4a77a3SMauro Carvalho Chehab #include <media/drv-intf/sh_vou.h>
23ee4a77a3SMauro Carvalho Chehab #include <media/v4l2-common.h>
24ee4a77a3SMauro Carvalho Chehab #include <media/v4l2-device.h>
25ee4a77a3SMauro Carvalho Chehab #include <media/v4l2-ioctl.h>
26ee4a77a3SMauro Carvalho Chehab #include <media/v4l2-mediabus.h>
27ee4a77a3SMauro Carvalho Chehab #include <media/videobuf2-v4l2.h>
28ee4a77a3SMauro Carvalho Chehab #include <media/videobuf2-dma-contig.h>
29ee4a77a3SMauro Carvalho Chehab 
30ee4a77a3SMauro Carvalho Chehab /* Mirror addresses are not available for all registers */
31ee4a77a3SMauro Carvalho Chehab #define VOUER	0
32ee4a77a3SMauro Carvalho Chehab #define VOUCR	4
33ee4a77a3SMauro Carvalho Chehab #define VOUSTR	8
34ee4a77a3SMauro Carvalho Chehab #define VOUVCR	0xc
35ee4a77a3SMauro Carvalho Chehab #define VOUISR	0x10
36ee4a77a3SMauro Carvalho Chehab #define VOUBCR	0x14
37ee4a77a3SMauro Carvalho Chehab #define VOUDPR	0x18
38ee4a77a3SMauro Carvalho Chehab #define VOUDSR	0x1c
39ee4a77a3SMauro Carvalho Chehab #define VOUVPR	0x20
40ee4a77a3SMauro Carvalho Chehab #define VOUIR	0x24
41ee4a77a3SMauro Carvalho Chehab #define VOUSRR	0x28
42ee4a77a3SMauro Carvalho Chehab #define VOUMSR	0x2c
43ee4a77a3SMauro Carvalho Chehab #define VOUHIR	0x30
44ee4a77a3SMauro Carvalho Chehab #define VOUDFR	0x34
45ee4a77a3SMauro Carvalho Chehab #define VOUAD1R	0x38
46ee4a77a3SMauro Carvalho Chehab #define VOUAD2R	0x3c
47ee4a77a3SMauro Carvalho Chehab #define VOUAIR	0x40
48ee4a77a3SMauro Carvalho Chehab #define VOUSWR	0x44
49ee4a77a3SMauro Carvalho Chehab #define VOURCR	0x48
50ee4a77a3SMauro Carvalho Chehab #define VOURPR	0x50
51ee4a77a3SMauro Carvalho Chehab 
52ee4a77a3SMauro Carvalho Chehab enum sh_vou_status {
53ee4a77a3SMauro Carvalho Chehab 	SH_VOU_IDLE,
54ee4a77a3SMauro Carvalho Chehab 	SH_VOU_INITIALISING,
55ee4a77a3SMauro Carvalho Chehab 	SH_VOU_RUNNING,
56ee4a77a3SMauro Carvalho Chehab };
57ee4a77a3SMauro Carvalho Chehab 
58ee4a77a3SMauro Carvalho Chehab #define VOU_MIN_IMAGE_WIDTH	16
59ee4a77a3SMauro Carvalho Chehab #define VOU_MAX_IMAGE_WIDTH	720
60ee4a77a3SMauro Carvalho Chehab #define VOU_MIN_IMAGE_HEIGHT	16
61ee4a77a3SMauro Carvalho Chehab 
62ee4a77a3SMauro Carvalho Chehab struct sh_vou_buffer {
63ee4a77a3SMauro Carvalho Chehab 	struct vb2_v4l2_buffer vb;
64ee4a77a3SMauro Carvalho Chehab 	struct list_head list;
65ee4a77a3SMauro Carvalho Chehab };
66ee4a77a3SMauro Carvalho Chehab 
67ee4a77a3SMauro Carvalho Chehab static inline struct
to_sh_vou_buffer(struct vb2_v4l2_buffer * vb2)68ee4a77a3SMauro Carvalho Chehab sh_vou_buffer *to_sh_vou_buffer(struct vb2_v4l2_buffer *vb2)
69ee4a77a3SMauro Carvalho Chehab {
70ee4a77a3SMauro Carvalho Chehab 	return container_of(vb2, struct sh_vou_buffer, vb);
71ee4a77a3SMauro Carvalho Chehab }
72ee4a77a3SMauro Carvalho Chehab 
73ee4a77a3SMauro Carvalho Chehab struct sh_vou_device {
74ee4a77a3SMauro Carvalho Chehab 	struct v4l2_device v4l2_dev;
75ee4a77a3SMauro Carvalho Chehab 	struct video_device vdev;
76ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_pdata *pdata;
77ee4a77a3SMauro Carvalho Chehab 	spinlock_t lock;
78ee4a77a3SMauro Carvalho Chehab 	void __iomem *base;
79ee4a77a3SMauro Carvalho Chehab 	/* State information */
80ee4a77a3SMauro Carvalho Chehab 	struct v4l2_pix_format pix;
81ee4a77a3SMauro Carvalho Chehab 	struct v4l2_rect rect;
82ee4a77a3SMauro Carvalho Chehab 	struct list_head buf_list;
83ee4a77a3SMauro Carvalho Chehab 	v4l2_std_id std;
84ee4a77a3SMauro Carvalho Chehab 	int pix_idx;
85ee4a77a3SMauro Carvalho Chehab 	struct vb2_queue queue;
86ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_buffer *active;
87ee4a77a3SMauro Carvalho Chehab 	enum sh_vou_status status;
88ee4a77a3SMauro Carvalho Chehab 	unsigned sequence;
89ee4a77a3SMauro Carvalho Chehab 	struct mutex fop_lock;
90ee4a77a3SMauro Carvalho Chehab };
91ee4a77a3SMauro Carvalho Chehab 
92ee4a77a3SMauro Carvalho Chehab /* Register access routines for sides A, B and mirror addresses */
sh_vou_reg_a_write(struct sh_vou_device * vou_dev,unsigned int reg,u32 value)93ee4a77a3SMauro Carvalho Chehab static void sh_vou_reg_a_write(struct sh_vou_device *vou_dev, unsigned int reg,
94ee4a77a3SMauro Carvalho Chehab 			       u32 value)
95ee4a77a3SMauro Carvalho Chehab {
96ee4a77a3SMauro Carvalho Chehab 	__raw_writel(value, vou_dev->base + reg);
97ee4a77a3SMauro Carvalho Chehab }
98ee4a77a3SMauro Carvalho Chehab 
sh_vou_reg_ab_write(struct sh_vou_device * vou_dev,unsigned int reg,u32 value)99ee4a77a3SMauro Carvalho Chehab static void sh_vou_reg_ab_write(struct sh_vou_device *vou_dev, unsigned int reg,
100ee4a77a3SMauro Carvalho Chehab 				u32 value)
101ee4a77a3SMauro Carvalho Chehab {
102ee4a77a3SMauro Carvalho Chehab 	__raw_writel(value, vou_dev->base + reg);
103ee4a77a3SMauro Carvalho Chehab 	__raw_writel(value, vou_dev->base + reg + 0x1000);
104ee4a77a3SMauro Carvalho Chehab }
105ee4a77a3SMauro Carvalho Chehab 
sh_vou_reg_m_write(struct sh_vou_device * vou_dev,unsigned int reg,u32 value)106ee4a77a3SMauro Carvalho Chehab static void sh_vou_reg_m_write(struct sh_vou_device *vou_dev, unsigned int reg,
107ee4a77a3SMauro Carvalho Chehab 			       u32 value)
108ee4a77a3SMauro Carvalho Chehab {
109ee4a77a3SMauro Carvalho Chehab 	__raw_writel(value, vou_dev->base + reg + 0x2000);
110ee4a77a3SMauro Carvalho Chehab }
111ee4a77a3SMauro Carvalho Chehab 
sh_vou_reg_a_read(struct sh_vou_device * vou_dev,unsigned int reg)112ee4a77a3SMauro Carvalho Chehab static u32 sh_vou_reg_a_read(struct sh_vou_device *vou_dev, unsigned int reg)
113ee4a77a3SMauro Carvalho Chehab {
114ee4a77a3SMauro Carvalho Chehab 	return __raw_readl(vou_dev->base + reg);
115ee4a77a3SMauro Carvalho Chehab }
116ee4a77a3SMauro Carvalho Chehab 
sh_vou_reg_a_set(struct sh_vou_device * vou_dev,unsigned int reg,u32 value,u32 mask)117ee4a77a3SMauro Carvalho Chehab static void sh_vou_reg_a_set(struct sh_vou_device *vou_dev, unsigned int reg,
118ee4a77a3SMauro Carvalho Chehab 			     u32 value, u32 mask)
119ee4a77a3SMauro Carvalho Chehab {
120ee4a77a3SMauro Carvalho Chehab 	u32 old = __raw_readl(vou_dev->base + reg);
121ee4a77a3SMauro Carvalho Chehab 
122ee4a77a3SMauro Carvalho Chehab 	value = (value & mask) | (old & ~mask);
123ee4a77a3SMauro Carvalho Chehab 	__raw_writel(value, vou_dev->base + reg);
124ee4a77a3SMauro Carvalho Chehab }
125ee4a77a3SMauro Carvalho Chehab 
sh_vou_reg_b_set(struct sh_vou_device * vou_dev,unsigned int reg,u32 value,u32 mask)126ee4a77a3SMauro Carvalho Chehab static void sh_vou_reg_b_set(struct sh_vou_device *vou_dev, unsigned int reg,
127ee4a77a3SMauro Carvalho Chehab 			     u32 value, u32 mask)
128ee4a77a3SMauro Carvalho Chehab {
129ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_a_set(vou_dev, reg + 0x1000, value, mask);
130ee4a77a3SMauro Carvalho Chehab }
131ee4a77a3SMauro Carvalho Chehab 
sh_vou_reg_ab_set(struct sh_vou_device * vou_dev,unsigned int reg,u32 value,u32 mask)132ee4a77a3SMauro Carvalho Chehab static void sh_vou_reg_ab_set(struct sh_vou_device *vou_dev, unsigned int reg,
133ee4a77a3SMauro Carvalho Chehab 			      u32 value, u32 mask)
134ee4a77a3SMauro Carvalho Chehab {
135ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_a_set(vou_dev, reg, value, mask);
136ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_b_set(vou_dev, reg, value, mask);
137ee4a77a3SMauro Carvalho Chehab }
138ee4a77a3SMauro Carvalho Chehab 
139ee4a77a3SMauro Carvalho Chehab struct sh_vou_fmt {
140ee4a77a3SMauro Carvalho Chehab 	u32		pfmt;
141ee4a77a3SMauro Carvalho Chehab 	unsigned char	bpp;
142ee4a77a3SMauro Carvalho Chehab 	unsigned char	bpl;
143ee4a77a3SMauro Carvalho Chehab 	unsigned char	rgb;
144ee4a77a3SMauro Carvalho Chehab 	unsigned char	yf;
145ee4a77a3SMauro Carvalho Chehab 	unsigned char	pkf;
146ee4a77a3SMauro Carvalho Chehab };
147ee4a77a3SMauro Carvalho Chehab 
148ee4a77a3SMauro Carvalho Chehab /* Further pixel formats can be added */
149ee4a77a3SMauro Carvalho Chehab static struct sh_vou_fmt vou_fmt[] = {
150ee4a77a3SMauro Carvalho Chehab 	{
151ee4a77a3SMauro Carvalho Chehab 		.pfmt	= V4L2_PIX_FMT_NV12,
152ee4a77a3SMauro Carvalho Chehab 		.bpp	= 12,
153ee4a77a3SMauro Carvalho Chehab 		.bpl	= 1,
154ee4a77a3SMauro Carvalho Chehab 		.yf	= 0,
155ee4a77a3SMauro Carvalho Chehab 		.rgb	= 0,
156ee4a77a3SMauro Carvalho Chehab 	},
157ee4a77a3SMauro Carvalho Chehab 	{
158ee4a77a3SMauro Carvalho Chehab 		.pfmt	= V4L2_PIX_FMT_NV16,
159ee4a77a3SMauro Carvalho Chehab 		.bpp	= 16,
160ee4a77a3SMauro Carvalho Chehab 		.bpl	= 1,
161ee4a77a3SMauro Carvalho Chehab 		.yf	= 1,
162ee4a77a3SMauro Carvalho Chehab 		.rgb	= 0,
163ee4a77a3SMauro Carvalho Chehab 	},
164ee4a77a3SMauro Carvalho Chehab 	{
165ee4a77a3SMauro Carvalho Chehab 		.pfmt	= V4L2_PIX_FMT_RGB24,
166ee4a77a3SMauro Carvalho Chehab 		.bpp	= 24,
167ee4a77a3SMauro Carvalho Chehab 		.bpl	= 3,
168ee4a77a3SMauro Carvalho Chehab 		.pkf	= 2,
169ee4a77a3SMauro Carvalho Chehab 		.rgb	= 1,
170ee4a77a3SMauro Carvalho Chehab 	},
171ee4a77a3SMauro Carvalho Chehab 	{
172ee4a77a3SMauro Carvalho Chehab 		.pfmt	= V4L2_PIX_FMT_RGB565,
173ee4a77a3SMauro Carvalho Chehab 		.bpp	= 16,
174ee4a77a3SMauro Carvalho Chehab 		.bpl	= 2,
175ee4a77a3SMauro Carvalho Chehab 		.pkf	= 3,
176ee4a77a3SMauro Carvalho Chehab 		.rgb	= 1,
177ee4a77a3SMauro Carvalho Chehab 	},
178ee4a77a3SMauro Carvalho Chehab 	{
179ee4a77a3SMauro Carvalho Chehab 		.pfmt	= V4L2_PIX_FMT_RGB565X,
180ee4a77a3SMauro Carvalho Chehab 		.bpp	= 16,
181ee4a77a3SMauro Carvalho Chehab 		.bpl	= 2,
182ee4a77a3SMauro Carvalho Chehab 		.pkf	= 3,
183ee4a77a3SMauro Carvalho Chehab 		.rgb	= 1,
184ee4a77a3SMauro Carvalho Chehab 	},
185ee4a77a3SMauro Carvalho Chehab };
186ee4a77a3SMauro Carvalho Chehab 
sh_vou_schedule_next(struct sh_vou_device * vou_dev,struct vb2_v4l2_buffer * vbuf)187ee4a77a3SMauro Carvalho Chehab static void sh_vou_schedule_next(struct sh_vou_device *vou_dev,
188ee4a77a3SMauro Carvalho Chehab 				 struct vb2_v4l2_buffer *vbuf)
189ee4a77a3SMauro Carvalho Chehab {
190ee4a77a3SMauro Carvalho Chehab 	dma_addr_t addr1, addr2;
191ee4a77a3SMauro Carvalho Chehab 
192ee4a77a3SMauro Carvalho Chehab 	addr1 = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0);
193ee4a77a3SMauro Carvalho Chehab 	switch (vou_dev->pix.pixelformat) {
194ee4a77a3SMauro Carvalho Chehab 	case V4L2_PIX_FMT_NV12:
195ee4a77a3SMauro Carvalho Chehab 	case V4L2_PIX_FMT_NV16:
196ee4a77a3SMauro Carvalho Chehab 		addr2 = addr1 + vou_dev->pix.width * vou_dev->pix.height;
197ee4a77a3SMauro Carvalho Chehab 		break;
198ee4a77a3SMauro Carvalho Chehab 	default:
199ee4a77a3SMauro Carvalho Chehab 		addr2 = 0;
200ee4a77a3SMauro Carvalho Chehab 	}
201ee4a77a3SMauro Carvalho Chehab 
202ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_m_write(vou_dev, VOUAD1R, addr1);
203ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_m_write(vou_dev, VOUAD2R, addr2);
204ee4a77a3SMauro Carvalho Chehab }
205ee4a77a3SMauro Carvalho Chehab 
sh_vou_stream_config(struct sh_vou_device * vou_dev)206ee4a77a3SMauro Carvalho Chehab static void sh_vou_stream_config(struct sh_vou_device *vou_dev)
207ee4a77a3SMauro Carvalho Chehab {
208ee4a77a3SMauro Carvalho Chehab 	unsigned int row_coeff;
209ee4a77a3SMauro Carvalho Chehab #ifdef __LITTLE_ENDIAN
210ee4a77a3SMauro Carvalho Chehab 	u32 dataswap = 7;
211ee4a77a3SMauro Carvalho Chehab #else
212ee4a77a3SMauro Carvalho Chehab 	u32 dataswap = 0;
213ee4a77a3SMauro Carvalho Chehab #endif
214ee4a77a3SMauro Carvalho Chehab 
215ee4a77a3SMauro Carvalho Chehab 	switch (vou_dev->pix.pixelformat) {
216ee4a77a3SMauro Carvalho Chehab 	default:
217ee4a77a3SMauro Carvalho Chehab 	case V4L2_PIX_FMT_NV12:
218ee4a77a3SMauro Carvalho Chehab 	case V4L2_PIX_FMT_NV16:
219ee4a77a3SMauro Carvalho Chehab 		row_coeff = 1;
220ee4a77a3SMauro Carvalho Chehab 		break;
221ee4a77a3SMauro Carvalho Chehab 	case V4L2_PIX_FMT_RGB565:
222ee4a77a3SMauro Carvalho Chehab 		dataswap ^= 1;
223ee4a77a3SMauro Carvalho Chehab 		fallthrough;
224ee4a77a3SMauro Carvalho Chehab 	case V4L2_PIX_FMT_RGB565X:
225ee4a77a3SMauro Carvalho Chehab 		row_coeff = 2;
226ee4a77a3SMauro Carvalho Chehab 		break;
227ee4a77a3SMauro Carvalho Chehab 	case V4L2_PIX_FMT_RGB24:
228ee4a77a3SMauro Carvalho Chehab 		row_coeff = 3;
229ee4a77a3SMauro Carvalho Chehab 		break;
230ee4a77a3SMauro Carvalho Chehab 	}
231ee4a77a3SMauro Carvalho Chehab 
232ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_a_write(vou_dev, VOUSWR, dataswap);
233ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_ab_write(vou_dev, VOUAIR, vou_dev->pix.width * row_coeff);
234ee4a77a3SMauro Carvalho Chehab }
235ee4a77a3SMauro Carvalho Chehab 
236ee4a77a3SMauro Carvalho Chehab /* Locking: caller holds fop_lock mutex */
sh_vou_queue_setup(struct vb2_queue * vq,unsigned int * nbuffers,unsigned int * nplanes,unsigned int sizes[],struct device * alloc_devs[])237ee4a77a3SMauro Carvalho Chehab static int sh_vou_queue_setup(struct vb2_queue *vq,
238ee4a77a3SMauro Carvalho Chehab 		       unsigned int *nbuffers, unsigned int *nplanes,
239ee4a77a3SMauro Carvalho Chehab 		       unsigned int sizes[], struct device *alloc_devs[])
240ee4a77a3SMauro Carvalho Chehab {
241ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = vb2_get_drv_priv(vq);
242ee4a77a3SMauro Carvalho Chehab 	struct v4l2_pix_format *pix = &vou_dev->pix;
243ee4a77a3SMauro Carvalho Chehab 	int bytes_per_line = vou_fmt[vou_dev->pix_idx].bpp * pix->width / 8;
244ee4a77a3SMauro Carvalho Chehab 
245ee4a77a3SMauro Carvalho Chehab 	dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__);
246ee4a77a3SMauro Carvalho Chehab 
247ee4a77a3SMauro Carvalho Chehab 	if (*nplanes)
248ee4a77a3SMauro Carvalho Chehab 		return sizes[0] < pix->height * bytes_per_line ? -EINVAL : 0;
249ee4a77a3SMauro Carvalho Chehab 	*nplanes = 1;
250ee4a77a3SMauro Carvalho Chehab 	sizes[0] = pix->height * bytes_per_line;
251ee4a77a3SMauro Carvalho Chehab 	return 0;
252ee4a77a3SMauro Carvalho Chehab }
253ee4a77a3SMauro Carvalho Chehab 
sh_vou_buf_prepare(struct vb2_buffer * vb)254ee4a77a3SMauro Carvalho Chehab static int sh_vou_buf_prepare(struct vb2_buffer *vb)
255ee4a77a3SMauro Carvalho Chehab {
256ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = vb2_get_drv_priv(vb->vb2_queue);
257ee4a77a3SMauro Carvalho Chehab 	struct v4l2_pix_format *pix = &vou_dev->pix;
258ee4a77a3SMauro Carvalho Chehab 	unsigned bytes_per_line = vou_fmt[vou_dev->pix_idx].bpp * pix->width / 8;
259ee4a77a3SMauro Carvalho Chehab 	unsigned size = pix->height * bytes_per_line;
260ee4a77a3SMauro Carvalho Chehab 
261ee4a77a3SMauro Carvalho Chehab 	dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__);
262ee4a77a3SMauro Carvalho Chehab 
263ee4a77a3SMauro Carvalho Chehab 	if (vb2_plane_size(vb, 0) < size) {
264ee4a77a3SMauro Carvalho Chehab 		/* User buffer too small */
265ee4a77a3SMauro Carvalho Chehab 		dev_warn(vou_dev->v4l2_dev.dev, "buffer too small (%lu < %u)\n",
266ee4a77a3SMauro Carvalho Chehab 			 vb2_plane_size(vb, 0), size);
267ee4a77a3SMauro Carvalho Chehab 		return -EINVAL;
268ee4a77a3SMauro Carvalho Chehab 	}
269ee4a77a3SMauro Carvalho Chehab 
270ee4a77a3SMauro Carvalho Chehab 	vb2_set_plane_payload(vb, 0, size);
271ee4a77a3SMauro Carvalho Chehab 	return 0;
272ee4a77a3SMauro Carvalho Chehab }
273ee4a77a3SMauro Carvalho Chehab 
274ee4a77a3SMauro Carvalho Chehab /* Locking: caller holds fop_lock mutex and vq->irqlock spinlock */
sh_vou_buf_queue(struct vb2_buffer * vb)275ee4a77a3SMauro Carvalho Chehab static void sh_vou_buf_queue(struct vb2_buffer *vb)
276ee4a77a3SMauro Carvalho Chehab {
277ee4a77a3SMauro Carvalho Chehab 	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
278ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = vb2_get_drv_priv(vb->vb2_queue);
279ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_buffer *shbuf = to_sh_vou_buffer(vbuf);
280ee4a77a3SMauro Carvalho Chehab 	unsigned long flags;
281ee4a77a3SMauro Carvalho Chehab 
282ee4a77a3SMauro Carvalho Chehab 	spin_lock_irqsave(&vou_dev->lock, flags);
283ee4a77a3SMauro Carvalho Chehab 	list_add_tail(&shbuf->list, &vou_dev->buf_list);
284ee4a77a3SMauro Carvalho Chehab 	spin_unlock_irqrestore(&vou_dev->lock, flags);
285ee4a77a3SMauro Carvalho Chehab }
286ee4a77a3SMauro Carvalho Chehab 
sh_vou_start_streaming(struct vb2_queue * vq,unsigned int count)287ee4a77a3SMauro Carvalho Chehab static int sh_vou_start_streaming(struct vb2_queue *vq, unsigned int count)
288ee4a77a3SMauro Carvalho Chehab {
289ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = vb2_get_drv_priv(vq);
290ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_buffer *buf, *node;
291ee4a77a3SMauro Carvalho Chehab 	int ret;
292ee4a77a3SMauro Carvalho Chehab 
293ee4a77a3SMauro Carvalho Chehab 	vou_dev->sequence = 0;
294ee4a77a3SMauro Carvalho Chehab 	ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0,
295ee4a77a3SMauro Carvalho Chehab 					 video, s_stream, 1);
296ee4a77a3SMauro Carvalho Chehab 	if (ret < 0 && ret != -ENOIOCTLCMD) {
297ee4a77a3SMauro Carvalho Chehab 		list_for_each_entry_safe(buf, node, &vou_dev->buf_list, list) {
298ee4a77a3SMauro Carvalho Chehab 			vb2_buffer_done(&buf->vb.vb2_buf,
299ee4a77a3SMauro Carvalho Chehab 					VB2_BUF_STATE_QUEUED);
300ee4a77a3SMauro Carvalho Chehab 			list_del(&buf->list);
301ee4a77a3SMauro Carvalho Chehab 		}
302ee4a77a3SMauro Carvalho Chehab 		vou_dev->active = NULL;
303ee4a77a3SMauro Carvalho Chehab 		return ret;
304ee4a77a3SMauro Carvalho Chehab 	}
305ee4a77a3SMauro Carvalho Chehab 
306ee4a77a3SMauro Carvalho Chehab 	buf = list_entry(vou_dev->buf_list.next, struct sh_vou_buffer, list);
307ee4a77a3SMauro Carvalho Chehab 
308ee4a77a3SMauro Carvalho Chehab 	vou_dev->active = buf;
309ee4a77a3SMauro Carvalho Chehab 
310ee4a77a3SMauro Carvalho Chehab 	/* Start from side A: we use mirror addresses, so, set B */
311ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_a_write(vou_dev, VOURPR, 1);
312ee4a77a3SMauro Carvalho Chehab 	dev_dbg(vou_dev->v4l2_dev.dev, "%s: first buffer status 0x%x\n",
313ee4a77a3SMauro Carvalho Chehab 		__func__, sh_vou_reg_a_read(vou_dev, VOUSTR));
314ee4a77a3SMauro Carvalho Chehab 	sh_vou_schedule_next(vou_dev, &buf->vb);
315ee4a77a3SMauro Carvalho Chehab 
316ee4a77a3SMauro Carvalho Chehab 	buf = list_entry(buf->list.next, struct sh_vou_buffer, list);
317ee4a77a3SMauro Carvalho Chehab 
318ee4a77a3SMauro Carvalho Chehab 	/* Second buffer - initialise register side B */
319ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_a_write(vou_dev, VOURPR, 0);
320ee4a77a3SMauro Carvalho Chehab 	sh_vou_schedule_next(vou_dev, &buf->vb);
321ee4a77a3SMauro Carvalho Chehab 
322ee4a77a3SMauro Carvalho Chehab 	/* Register side switching with frame VSYNC */
323ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_a_write(vou_dev, VOURCR, 5);
324ee4a77a3SMauro Carvalho Chehab 
325ee4a77a3SMauro Carvalho Chehab 	sh_vou_stream_config(vou_dev);
326ee4a77a3SMauro Carvalho Chehab 	/* Enable End-of-Frame (VSYNC) interrupts */
327ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_a_write(vou_dev, VOUIR, 0x10004);
328ee4a77a3SMauro Carvalho Chehab 
329ee4a77a3SMauro Carvalho Chehab 	/* Two buffers on the queue - activate the hardware */
330ee4a77a3SMauro Carvalho Chehab 	vou_dev->status = SH_VOU_RUNNING;
331ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_a_write(vou_dev, VOUER, 0x107);
332ee4a77a3SMauro Carvalho Chehab 	return 0;
333ee4a77a3SMauro Carvalho Chehab }
334ee4a77a3SMauro Carvalho Chehab 
sh_vou_stop_streaming(struct vb2_queue * vq)335ee4a77a3SMauro Carvalho Chehab static void sh_vou_stop_streaming(struct vb2_queue *vq)
336ee4a77a3SMauro Carvalho Chehab {
337ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = vb2_get_drv_priv(vq);
338ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_buffer *buf, *node;
339ee4a77a3SMauro Carvalho Chehab 	unsigned long flags;
340ee4a77a3SMauro Carvalho Chehab 
341ee4a77a3SMauro Carvalho Chehab 	v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0,
342ee4a77a3SMauro Carvalho Chehab 					 video, s_stream, 0);
343ee4a77a3SMauro Carvalho Chehab 	/* disable output */
344ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_a_set(vou_dev, VOUER, 0, 1);
345ee4a77a3SMauro Carvalho Chehab 	/* ...but the current frame will complete */
346ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_a_set(vou_dev, VOUIR, 0, 0x30000);
347ee4a77a3SMauro Carvalho Chehab 	msleep(50);
348ee4a77a3SMauro Carvalho Chehab 	spin_lock_irqsave(&vou_dev->lock, flags);
349ee4a77a3SMauro Carvalho Chehab 	list_for_each_entry_safe(buf, node, &vou_dev->buf_list, list) {
350ee4a77a3SMauro Carvalho Chehab 		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
351ee4a77a3SMauro Carvalho Chehab 		list_del(&buf->list);
352ee4a77a3SMauro Carvalho Chehab 	}
353ee4a77a3SMauro Carvalho Chehab 	vou_dev->active = NULL;
354ee4a77a3SMauro Carvalho Chehab 	spin_unlock_irqrestore(&vou_dev->lock, flags);
355ee4a77a3SMauro Carvalho Chehab }
356ee4a77a3SMauro Carvalho Chehab 
357ee4a77a3SMauro Carvalho Chehab static const struct vb2_ops sh_vou_qops = {
358ee4a77a3SMauro Carvalho Chehab 	.queue_setup		= sh_vou_queue_setup,
359ee4a77a3SMauro Carvalho Chehab 	.buf_prepare		= sh_vou_buf_prepare,
360ee4a77a3SMauro Carvalho Chehab 	.buf_queue		= sh_vou_buf_queue,
361ee4a77a3SMauro Carvalho Chehab 	.start_streaming	= sh_vou_start_streaming,
362ee4a77a3SMauro Carvalho Chehab 	.stop_streaming		= sh_vou_stop_streaming,
363ee4a77a3SMauro Carvalho Chehab 	.wait_prepare		= vb2_ops_wait_prepare,
364ee4a77a3SMauro Carvalho Chehab 	.wait_finish		= vb2_ops_wait_finish,
365ee4a77a3SMauro Carvalho Chehab };
366ee4a77a3SMauro Carvalho Chehab 
367ee4a77a3SMauro Carvalho Chehab /* Video IOCTLs */
sh_vou_querycap(struct file * file,void * priv,struct v4l2_capability * cap)368ee4a77a3SMauro Carvalho Chehab static int sh_vou_querycap(struct file *file, void  *priv,
369ee4a77a3SMauro Carvalho Chehab 			   struct v4l2_capability *cap)
370ee4a77a3SMauro Carvalho Chehab {
371ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = video_drvdata(file);
372ee4a77a3SMauro Carvalho Chehab 
373ee4a77a3SMauro Carvalho Chehab 	dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__);
374ee4a77a3SMauro Carvalho Chehab 
375ee4a77a3SMauro Carvalho Chehab 	strscpy(cap->card, "SuperH VOU", sizeof(cap->card));
376ee4a77a3SMauro Carvalho Chehab 	strscpy(cap->driver, "sh-vou", sizeof(cap->driver));
377ee4a77a3SMauro Carvalho Chehab 	strscpy(cap->bus_info, "platform:sh-vou", sizeof(cap->bus_info));
378ee4a77a3SMauro Carvalho Chehab 	return 0;
379ee4a77a3SMauro Carvalho Chehab }
380ee4a77a3SMauro Carvalho Chehab 
381ee4a77a3SMauro Carvalho Chehab /* Enumerate formats, that the device can accept from the user */
sh_vou_enum_fmt_vid_out(struct file * file,void * priv,struct v4l2_fmtdesc * fmt)382ee4a77a3SMauro Carvalho Chehab static int sh_vou_enum_fmt_vid_out(struct file *file, void  *priv,
383ee4a77a3SMauro Carvalho Chehab 				   struct v4l2_fmtdesc *fmt)
384ee4a77a3SMauro Carvalho Chehab {
385ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = video_drvdata(file);
386ee4a77a3SMauro Carvalho Chehab 
387ee4a77a3SMauro Carvalho Chehab 	if (fmt->index >= ARRAY_SIZE(vou_fmt))
388ee4a77a3SMauro Carvalho Chehab 		return -EINVAL;
389ee4a77a3SMauro Carvalho Chehab 
390ee4a77a3SMauro Carvalho Chehab 	dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__);
391ee4a77a3SMauro Carvalho Chehab 
392ee4a77a3SMauro Carvalho Chehab 	fmt->pixelformat = vou_fmt[fmt->index].pfmt;
393ee4a77a3SMauro Carvalho Chehab 
394ee4a77a3SMauro Carvalho Chehab 	return 0;
395ee4a77a3SMauro Carvalho Chehab }
396ee4a77a3SMauro Carvalho Chehab 
sh_vou_g_fmt_vid_out(struct file * file,void * priv,struct v4l2_format * fmt)397ee4a77a3SMauro Carvalho Chehab static int sh_vou_g_fmt_vid_out(struct file *file, void *priv,
398ee4a77a3SMauro Carvalho Chehab 				struct v4l2_format *fmt)
399ee4a77a3SMauro Carvalho Chehab {
400ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = video_drvdata(file);
401ee4a77a3SMauro Carvalho Chehab 
402ee4a77a3SMauro Carvalho Chehab 	dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__);
403ee4a77a3SMauro Carvalho Chehab 
404ee4a77a3SMauro Carvalho Chehab 	fmt->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
405ee4a77a3SMauro Carvalho Chehab 	fmt->fmt.pix = vou_dev->pix;
406ee4a77a3SMauro Carvalho Chehab 
407ee4a77a3SMauro Carvalho Chehab 	return 0;
408ee4a77a3SMauro Carvalho Chehab }
409ee4a77a3SMauro Carvalho Chehab 
410ee4a77a3SMauro Carvalho Chehab static const unsigned char vou_scale_h_num[] = {1, 9, 2, 9, 4};
411ee4a77a3SMauro Carvalho Chehab static const unsigned char vou_scale_h_den[] = {1, 8, 1, 4, 1};
412ee4a77a3SMauro Carvalho Chehab static const unsigned char vou_scale_h_fld[] = {0, 2, 1, 3};
413ee4a77a3SMauro Carvalho Chehab static const unsigned char vou_scale_v_num[] = {1, 2, 4};
414ee4a77a3SMauro Carvalho Chehab static const unsigned char vou_scale_v_den[] = {1, 1, 1};
415ee4a77a3SMauro Carvalho Chehab static const unsigned char vou_scale_v_fld[] = {0, 1};
416ee4a77a3SMauro Carvalho Chehab 
sh_vou_configure_geometry(struct sh_vou_device * vou_dev,int pix_idx,int w_idx,int h_idx)417ee4a77a3SMauro Carvalho Chehab static void sh_vou_configure_geometry(struct sh_vou_device *vou_dev,
418ee4a77a3SMauro Carvalho Chehab 				      int pix_idx, int w_idx, int h_idx)
419ee4a77a3SMauro Carvalho Chehab {
420ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_fmt *fmt = vou_fmt + pix_idx;
421ee4a77a3SMauro Carvalho Chehab 	unsigned int black_left, black_top, width_max,
422ee4a77a3SMauro Carvalho Chehab 		frame_in_height, frame_out_height, frame_out_top;
423ee4a77a3SMauro Carvalho Chehab 	struct v4l2_rect *rect = &vou_dev->rect;
424ee4a77a3SMauro Carvalho Chehab 	struct v4l2_pix_format *pix = &vou_dev->pix;
425ee4a77a3SMauro Carvalho Chehab 	u32 vouvcr = 0, dsr_h, dsr_v;
426ee4a77a3SMauro Carvalho Chehab 
427ee4a77a3SMauro Carvalho Chehab 	if (vou_dev->std & V4L2_STD_525_60) {
428ee4a77a3SMauro Carvalho Chehab 		width_max = 858;
429ee4a77a3SMauro Carvalho Chehab 		/* height_max = 262; */
430ee4a77a3SMauro Carvalho Chehab 	} else {
431ee4a77a3SMauro Carvalho Chehab 		width_max = 864;
432ee4a77a3SMauro Carvalho Chehab 		/* height_max = 312; */
433ee4a77a3SMauro Carvalho Chehab 	}
434ee4a77a3SMauro Carvalho Chehab 
435ee4a77a3SMauro Carvalho Chehab 	frame_in_height = pix->height / 2;
436ee4a77a3SMauro Carvalho Chehab 	frame_out_height = rect->height / 2;
437ee4a77a3SMauro Carvalho Chehab 	frame_out_top = rect->top / 2;
438ee4a77a3SMauro Carvalho Chehab 
439ee4a77a3SMauro Carvalho Chehab 	/*
440ee4a77a3SMauro Carvalho Chehab 	 * Cropping scheme: max useful image is 720x480, and the total video
441ee4a77a3SMauro Carvalho Chehab 	 * area is 858x525 (NTSC) or 864x625 (PAL). AK8813 / 8814 starts
442ee4a77a3SMauro Carvalho Chehab 	 * sampling data beginning with fixed 276th (NTSC) / 288th (PAL) clock,
443ee4a77a3SMauro Carvalho Chehab 	 * of which the first 33 / 25 clocks HSYNC must be held active. This
444ee4a77a3SMauro Carvalho Chehab 	 * has to be configured in CR[HW]. 1 pixel equals 2 clock periods.
445ee4a77a3SMauro Carvalho Chehab 	 * This gives CR[HW] = 16 / 12, VPR[HVP] = 138 / 144, which gives
446ee4a77a3SMauro Carvalho Chehab 	 * exactly 858 - 138 = 864 - 144 = 720! We call the out-of-display area,
447ee4a77a3SMauro Carvalho Chehab 	 * beyond DSR, specified on the left and top by the VPR register "black
448ee4a77a3SMauro Carvalho Chehab 	 * pixels" and out-of-image area (DPR) "background pixels." We fix VPR
449ee4a77a3SMauro Carvalho Chehab 	 * at 138 / 144 : 20, because that's the HSYNC timing, that our first
450ee4a77a3SMauro Carvalho Chehab 	 * client requires, and that's exactly what leaves us 720 pixels for the
451ee4a77a3SMauro Carvalho Chehab 	 * image; we leave VPR[VVP] at default 20 for now, because the client
452ee4a77a3SMauro Carvalho Chehab 	 * doesn't seem to have any special requirements for it. Otherwise we
453ee4a77a3SMauro Carvalho Chehab 	 * could also set it to max - 240 = 22 / 72. Thus VPR depends only on
454ee4a77a3SMauro Carvalho Chehab 	 * the selected standard, and DPR and DSR are selected according to
455ee4a77a3SMauro Carvalho Chehab 	 * cropping. Q: how does the client detect the first valid line? Does
456ee4a77a3SMauro Carvalho Chehab 	 * HSYNC stay inactive during invalid (black) lines?
457ee4a77a3SMauro Carvalho Chehab 	 */
458ee4a77a3SMauro Carvalho Chehab 	black_left = width_max - VOU_MAX_IMAGE_WIDTH;
459ee4a77a3SMauro Carvalho Chehab 	black_top = 20;
460ee4a77a3SMauro Carvalho Chehab 
461ee4a77a3SMauro Carvalho Chehab 	dsr_h = rect->width + rect->left;
462ee4a77a3SMauro Carvalho Chehab 	dsr_v = frame_out_height + frame_out_top;
463ee4a77a3SMauro Carvalho Chehab 
464ee4a77a3SMauro Carvalho Chehab 	dev_dbg(vou_dev->v4l2_dev.dev,
465ee4a77a3SMauro Carvalho Chehab 		"image %ux%u, black %u:%u, offset %u:%u, display %ux%u\n",
466ee4a77a3SMauro Carvalho Chehab 		pix->width, frame_in_height, black_left, black_top,
467ee4a77a3SMauro Carvalho Chehab 		rect->left, frame_out_top, dsr_h, dsr_v);
468ee4a77a3SMauro Carvalho Chehab 
469ee4a77a3SMauro Carvalho Chehab 	/* VOUISR height - half of a frame height in frame mode */
470ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_ab_write(vou_dev, VOUISR, (pix->width << 16) | frame_in_height);
471ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_ab_write(vou_dev, VOUVPR, (black_left << 16) | black_top);
472ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_ab_write(vou_dev, VOUDPR, (rect->left << 16) | frame_out_top);
473ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_ab_write(vou_dev, VOUDSR, (dsr_h << 16) | dsr_v);
474ee4a77a3SMauro Carvalho Chehab 
475ee4a77a3SMauro Carvalho Chehab 	/*
476ee4a77a3SMauro Carvalho Chehab 	 * if necessary, we could set VOUHIR to
477ee4a77a3SMauro Carvalho Chehab 	 * max(black_left + dsr_h, width_max) here
478ee4a77a3SMauro Carvalho Chehab 	 */
479ee4a77a3SMauro Carvalho Chehab 
480ee4a77a3SMauro Carvalho Chehab 	if (w_idx)
481ee4a77a3SMauro Carvalho Chehab 		vouvcr |= (1 << 15) | (vou_scale_h_fld[w_idx - 1] << 4);
482ee4a77a3SMauro Carvalho Chehab 	if (h_idx)
483ee4a77a3SMauro Carvalho Chehab 		vouvcr |= (1 << 14) | vou_scale_v_fld[h_idx - 1];
484ee4a77a3SMauro Carvalho Chehab 
485ee4a77a3SMauro Carvalho Chehab 	dev_dbg(vou_dev->v4l2_dev.dev, "0x%08x: scaling 0x%x\n",
486ee4a77a3SMauro Carvalho Chehab 		fmt->pfmt, vouvcr);
487ee4a77a3SMauro Carvalho Chehab 
488ee4a77a3SMauro Carvalho Chehab 	/* To produce a colour bar for testing set bit 23 of VOUVCR */
489ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_ab_write(vou_dev, VOUVCR, vouvcr);
490ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_ab_write(vou_dev, VOUDFR,
491ee4a77a3SMauro Carvalho Chehab 			    fmt->pkf | (fmt->yf << 8) | (fmt->rgb << 16));
492ee4a77a3SMauro Carvalho Chehab }
493ee4a77a3SMauro Carvalho Chehab 
494ee4a77a3SMauro Carvalho Chehab struct sh_vou_geometry {
495ee4a77a3SMauro Carvalho Chehab 	struct v4l2_rect output;
496ee4a77a3SMauro Carvalho Chehab 	unsigned int in_width;
497ee4a77a3SMauro Carvalho Chehab 	unsigned int in_height;
498ee4a77a3SMauro Carvalho Chehab 	int scale_idx_h;
499ee4a77a3SMauro Carvalho Chehab 	int scale_idx_v;
500ee4a77a3SMauro Carvalho Chehab };
501ee4a77a3SMauro Carvalho Chehab 
502ee4a77a3SMauro Carvalho Chehab /*
503ee4a77a3SMauro Carvalho Chehab  * Find input geometry, that we can use to produce output, closest to the
504ee4a77a3SMauro Carvalho Chehab  * requested rectangle, using VOU scaling
505ee4a77a3SMauro Carvalho Chehab  */
vou_adjust_input(struct sh_vou_geometry * geo,v4l2_std_id std)506ee4a77a3SMauro Carvalho Chehab static void vou_adjust_input(struct sh_vou_geometry *geo, v4l2_std_id std)
507ee4a77a3SMauro Carvalho Chehab {
508ee4a77a3SMauro Carvalho Chehab 	/* The compiler cannot know, that best and idx will indeed be set */
509ee4a77a3SMauro Carvalho Chehab 	unsigned int best_err = UINT_MAX, best = 0, img_height_max;
510ee4a77a3SMauro Carvalho Chehab 	int i, idx = 0;
511ee4a77a3SMauro Carvalho Chehab 
512ee4a77a3SMauro Carvalho Chehab 	if (std & V4L2_STD_525_60)
513ee4a77a3SMauro Carvalho Chehab 		img_height_max = 480;
514ee4a77a3SMauro Carvalho Chehab 	else
515ee4a77a3SMauro Carvalho Chehab 		img_height_max = 576;
516ee4a77a3SMauro Carvalho Chehab 
517ee4a77a3SMauro Carvalho Chehab 	/* Image width must be a multiple of 4 */
518ee4a77a3SMauro Carvalho Chehab 	v4l_bound_align_image(&geo->in_width,
519ee4a77a3SMauro Carvalho Chehab 			      VOU_MIN_IMAGE_WIDTH, VOU_MAX_IMAGE_WIDTH, 2,
520ee4a77a3SMauro Carvalho Chehab 			      &geo->in_height,
521ee4a77a3SMauro Carvalho Chehab 			      VOU_MIN_IMAGE_HEIGHT, img_height_max, 1, 0);
522ee4a77a3SMauro Carvalho Chehab 
523ee4a77a3SMauro Carvalho Chehab 	/* Select scales to come as close as possible to the output image */
524ee4a77a3SMauro Carvalho Chehab 	for (i = ARRAY_SIZE(vou_scale_h_num) - 1; i >= 0; i--) {
525ee4a77a3SMauro Carvalho Chehab 		unsigned int err;
526ee4a77a3SMauro Carvalho Chehab 		unsigned int found = geo->output.width * vou_scale_h_den[i] /
527ee4a77a3SMauro Carvalho Chehab 			vou_scale_h_num[i];
528ee4a77a3SMauro Carvalho Chehab 
529ee4a77a3SMauro Carvalho Chehab 		if (found > VOU_MAX_IMAGE_WIDTH)
530ee4a77a3SMauro Carvalho Chehab 			/* scales increase */
531ee4a77a3SMauro Carvalho Chehab 			break;
532ee4a77a3SMauro Carvalho Chehab 
533ee4a77a3SMauro Carvalho Chehab 		err = abs(found - geo->in_width);
534ee4a77a3SMauro Carvalho Chehab 		if (err < best_err) {
535ee4a77a3SMauro Carvalho Chehab 			best_err = err;
536ee4a77a3SMauro Carvalho Chehab 			idx = i;
537ee4a77a3SMauro Carvalho Chehab 			best = found;
538ee4a77a3SMauro Carvalho Chehab 		}
539ee4a77a3SMauro Carvalho Chehab 		if (!err)
540ee4a77a3SMauro Carvalho Chehab 			break;
541ee4a77a3SMauro Carvalho Chehab 	}
542ee4a77a3SMauro Carvalho Chehab 
543ee4a77a3SMauro Carvalho Chehab 	geo->in_width = best;
544ee4a77a3SMauro Carvalho Chehab 	geo->scale_idx_h = idx;
545ee4a77a3SMauro Carvalho Chehab 
546ee4a77a3SMauro Carvalho Chehab 	best_err = UINT_MAX;
547ee4a77a3SMauro Carvalho Chehab 
548ee4a77a3SMauro Carvalho Chehab 	/* This loop can be replaced with one division */
549ee4a77a3SMauro Carvalho Chehab 	for (i = ARRAY_SIZE(vou_scale_v_num) - 1; i >= 0; i--) {
550ee4a77a3SMauro Carvalho Chehab 		unsigned int err;
551ee4a77a3SMauro Carvalho Chehab 		unsigned int found = geo->output.height * vou_scale_v_den[i] /
552ee4a77a3SMauro Carvalho Chehab 			vou_scale_v_num[i];
553ee4a77a3SMauro Carvalho Chehab 
554ee4a77a3SMauro Carvalho Chehab 		if (found > img_height_max)
555ee4a77a3SMauro Carvalho Chehab 			/* scales increase */
556ee4a77a3SMauro Carvalho Chehab 			break;
557ee4a77a3SMauro Carvalho Chehab 
558ee4a77a3SMauro Carvalho Chehab 		err = abs(found - geo->in_height);
559ee4a77a3SMauro Carvalho Chehab 		if (err < best_err) {
560ee4a77a3SMauro Carvalho Chehab 			best_err = err;
561ee4a77a3SMauro Carvalho Chehab 			idx = i;
562ee4a77a3SMauro Carvalho Chehab 			best = found;
563ee4a77a3SMauro Carvalho Chehab 		}
564ee4a77a3SMauro Carvalho Chehab 		if (!err)
565ee4a77a3SMauro Carvalho Chehab 			break;
566ee4a77a3SMauro Carvalho Chehab 	}
567ee4a77a3SMauro Carvalho Chehab 
568ee4a77a3SMauro Carvalho Chehab 	geo->in_height = best;
569ee4a77a3SMauro Carvalho Chehab 	geo->scale_idx_v = idx;
570ee4a77a3SMauro Carvalho Chehab }
571ee4a77a3SMauro Carvalho Chehab 
572ee4a77a3SMauro Carvalho Chehab /*
573ee4a77a3SMauro Carvalho Chehab  * Find output geometry, that we can produce, using VOU scaling, closest to
574ee4a77a3SMauro Carvalho Chehab  * the requested rectangle
575ee4a77a3SMauro Carvalho Chehab  */
vou_adjust_output(struct sh_vou_geometry * geo,v4l2_std_id std)576ee4a77a3SMauro Carvalho Chehab static void vou_adjust_output(struct sh_vou_geometry *geo, v4l2_std_id std)
577ee4a77a3SMauro Carvalho Chehab {
578ee4a77a3SMauro Carvalho Chehab 	unsigned int best_err = UINT_MAX, best = geo->in_width,
579ee4a77a3SMauro Carvalho Chehab 		width_max, height_max, img_height_max;
580ee4a77a3SMauro Carvalho Chehab 	int i, idx_h = 0, idx_v = 0;
581ee4a77a3SMauro Carvalho Chehab 
582ee4a77a3SMauro Carvalho Chehab 	if (std & V4L2_STD_525_60) {
583ee4a77a3SMauro Carvalho Chehab 		width_max = 858;
584ee4a77a3SMauro Carvalho Chehab 		height_max = 262 * 2;
585ee4a77a3SMauro Carvalho Chehab 		img_height_max = 480;
586ee4a77a3SMauro Carvalho Chehab 	} else {
587ee4a77a3SMauro Carvalho Chehab 		width_max = 864;
588ee4a77a3SMauro Carvalho Chehab 		height_max = 312 * 2;
589ee4a77a3SMauro Carvalho Chehab 		img_height_max = 576;
590ee4a77a3SMauro Carvalho Chehab 	}
591ee4a77a3SMauro Carvalho Chehab 
592ee4a77a3SMauro Carvalho Chehab 	/* Select scales to come as close as possible to the output image */
593ee4a77a3SMauro Carvalho Chehab 	for (i = 0; i < ARRAY_SIZE(vou_scale_h_num); i++) {
594ee4a77a3SMauro Carvalho Chehab 		unsigned int err;
595ee4a77a3SMauro Carvalho Chehab 		unsigned int found = geo->in_width * vou_scale_h_num[i] /
596ee4a77a3SMauro Carvalho Chehab 			vou_scale_h_den[i];
597ee4a77a3SMauro Carvalho Chehab 
598ee4a77a3SMauro Carvalho Chehab 		if (found > VOU_MAX_IMAGE_WIDTH)
599ee4a77a3SMauro Carvalho Chehab 			/* scales increase */
600ee4a77a3SMauro Carvalho Chehab 			break;
601ee4a77a3SMauro Carvalho Chehab 
602ee4a77a3SMauro Carvalho Chehab 		err = abs(found - geo->output.width);
603ee4a77a3SMauro Carvalho Chehab 		if (err < best_err) {
604ee4a77a3SMauro Carvalho Chehab 			best_err = err;
605ee4a77a3SMauro Carvalho Chehab 			idx_h = i;
606ee4a77a3SMauro Carvalho Chehab 			best = found;
607ee4a77a3SMauro Carvalho Chehab 		}
608ee4a77a3SMauro Carvalho Chehab 		if (!err)
609ee4a77a3SMauro Carvalho Chehab 			break;
610ee4a77a3SMauro Carvalho Chehab 	}
611ee4a77a3SMauro Carvalho Chehab 
612ee4a77a3SMauro Carvalho Chehab 	geo->output.width = best;
613ee4a77a3SMauro Carvalho Chehab 	geo->scale_idx_h = idx_h;
614ee4a77a3SMauro Carvalho Chehab 	if (geo->output.left + best > width_max)
615ee4a77a3SMauro Carvalho Chehab 		geo->output.left = width_max - best;
616ee4a77a3SMauro Carvalho Chehab 
617ee4a77a3SMauro Carvalho Chehab 	pr_debug("%s(): W %u * %u/%u = %u\n", __func__, geo->in_width,
618ee4a77a3SMauro Carvalho Chehab 		 vou_scale_h_num[idx_h], vou_scale_h_den[idx_h], best);
619ee4a77a3SMauro Carvalho Chehab 
620ee4a77a3SMauro Carvalho Chehab 	best_err = UINT_MAX;
621ee4a77a3SMauro Carvalho Chehab 
622ee4a77a3SMauro Carvalho Chehab 	/* This loop can be replaced with one division */
623ee4a77a3SMauro Carvalho Chehab 	for (i = 0; i < ARRAY_SIZE(vou_scale_v_num); i++) {
624ee4a77a3SMauro Carvalho Chehab 		unsigned int err;
625ee4a77a3SMauro Carvalho Chehab 		unsigned int found = geo->in_height * vou_scale_v_num[i] /
626ee4a77a3SMauro Carvalho Chehab 			vou_scale_v_den[i];
627ee4a77a3SMauro Carvalho Chehab 
628ee4a77a3SMauro Carvalho Chehab 		if (found > img_height_max)
629ee4a77a3SMauro Carvalho Chehab 			/* scales increase */
630ee4a77a3SMauro Carvalho Chehab 			break;
631ee4a77a3SMauro Carvalho Chehab 
632ee4a77a3SMauro Carvalho Chehab 		err = abs(found - geo->output.height);
633ee4a77a3SMauro Carvalho Chehab 		if (err < best_err) {
634ee4a77a3SMauro Carvalho Chehab 			best_err = err;
635ee4a77a3SMauro Carvalho Chehab 			idx_v = i;
636ee4a77a3SMauro Carvalho Chehab 			best = found;
637ee4a77a3SMauro Carvalho Chehab 		}
638ee4a77a3SMauro Carvalho Chehab 		if (!err)
639ee4a77a3SMauro Carvalho Chehab 			break;
640ee4a77a3SMauro Carvalho Chehab 	}
641ee4a77a3SMauro Carvalho Chehab 
642ee4a77a3SMauro Carvalho Chehab 	geo->output.height = best;
643ee4a77a3SMauro Carvalho Chehab 	geo->scale_idx_v = idx_v;
644ee4a77a3SMauro Carvalho Chehab 	if (geo->output.top + best > height_max)
645ee4a77a3SMauro Carvalho Chehab 		geo->output.top = height_max - best;
646ee4a77a3SMauro Carvalho Chehab 
647ee4a77a3SMauro Carvalho Chehab 	pr_debug("%s(): H %u * %u/%u = %u\n", __func__, geo->in_height,
648ee4a77a3SMauro Carvalho Chehab 		 vou_scale_v_num[idx_v], vou_scale_v_den[idx_v], best);
649ee4a77a3SMauro Carvalho Chehab }
650ee4a77a3SMauro Carvalho Chehab 
sh_vou_try_fmt_vid_out(struct file * file,void * priv,struct v4l2_format * fmt)651ee4a77a3SMauro Carvalho Chehab static int sh_vou_try_fmt_vid_out(struct file *file, void *priv,
652ee4a77a3SMauro Carvalho Chehab 				  struct v4l2_format *fmt)
653ee4a77a3SMauro Carvalho Chehab {
654ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = video_drvdata(file);
655ee4a77a3SMauro Carvalho Chehab 	struct v4l2_pix_format *pix = &fmt->fmt.pix;
656ee4a77a3SMauro Carvalho Chehab 	unsigned int img_height_max;
657ee4a77a3SMauro Carvalho Chehab 	int pix_idx;
658ee4a77a3SMauro Carvalho Chehab 
659ee4a77a3SMauro Carvalho Chehab 	dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__);
660ee4a77a3SMauro Carvalho Chehab 
661ee4a77a3SMauro Carvalho Chehab 	pix->field = V4L2_FIELD_INTERLACED;
662ee4a77a3SMauro Carvalho Chehab 	pix->colorspace = V4L2_COLORSPACE_SMPTE170M;
663ee4a77a3SMauro Carvalho Chehab 	pix->ycbcr_enc = pix->quantization = 0;
664ee4a77a3SMauro Carvalho Chehab 
665ee4a77a3SMauro Carvalho Chehab 	for (pix_idx = 0; pix_idx < ARRAY_SIZE(vou_fmt); pix_idx++)
666ee4a77a3SMauro Carvalho Chehab 		if (vou_fmt[pix_idx].pfmt == pix->pixelformat)
667ee4a77a3SMauro Carvalho Chehab 			break;
668ee4a77a3SMauro Carvalho Chehab 
669ee4a77a3SMauro Carvalho Chehab 	if (pix_idx == ARRAY_SIZE(vou_fmt))
670ee4a77a3SMauro Carvalho Chehab 		return -EINVAL;
671ee4a77a3SMauro Carvalho Chehab 
672ee4a77a3SMauro Carvalho Chehab 	if (vou_dev->std & V4L2_STD_525_60)
673ee4a77a3SMauro Carvalho Chehab 		img_height_max = 480;
674ee4a77a3SMauro Carvalho Chehab 	else
675ee4a77a3SMauro Carvalho Chehab 		img_height_max = 576;
676ee4a77a3SMauro Carvalho Chehab 
677ee4a77a3SMauro Carvalho Chehab 	v4l_bound_align_image(&pix->width,
678ee4a77a3SMauro Carvalho Chehab 			      VOU_MIN_IMAGE_WIDTH, VOU_MAX_IMAGE_WIDTH, 2,
679ee4a77a3SMauro Carvalho Chehab 			      &pix->height,
680ee4a77a3SMauro Carvalho Chehab 			      VOU_MIN_IMAGE_HEIGHT, img_height_max, 1, 0);
681ee4a77a3SMauro Carvalho Chehab 	pix->bytesperline = pix->width * vou_fmt[pix_idx].bpl;
682ee4a77a3SMauro Carvalho Chehab 	pix->sizeimage = pix->height * ((pix->width * vou_fmt[pix_idx].bpp) >> 3);
683ee4a77a3SMauro Carvalho Chehab 
684ee4a77a3SMauro Carvalho Chehab 	return 0;
685ee4a77a3SMauro Carvalho Chehab }
686ee4a77a3SMauro Carvalho Chehab 
sh_vou_set_fmt_vid_out(struct sh_vou_device * vou_dev,struct v4l2_pix_format * pix)687ee4a77a3SMauro Carvalho Chehab static int sh_vou_set_fmt_vid_out(struct sh_vou_device *vou_dev,
688ee4a77a3SMauro Carvalho Chehab 				struct v4l2_pix_format *pix)
689ee4a77a3SMauro Carvalho Chehab {
690ee4a77a3SMauro Carvalho Chehab 	unsigned int img_height_max;
691ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_geometry geo;
692ee4a77a3SMauro Carvalho Chehab 	struct v4l2_subdev_format format = {
693ee4a77a3SMauro Carvalho Chehab 		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
694ee4a77a3SMauro Carvalho Chehab 		/* Revisit: is this the correct code? */
695ee4a77a3SMauro Carvalho Chehab 		.format.code = MEDIA_BUS_FMT_YUYV8_2X8,
696ee4a77a3SMauro Carvalho Chehab 		.format.field = V4L2_FIELD_INTERLACED,
697ee4a77a3SMauro Carvalho Chehab 		.format.colorspace = V4L2_COLORSPACE_SMPTE170M,
698ee4a77a3SMauro Carvalho Chehab 	};
699ee4a77a3SMauro Carvalho Chehab 	struct v4l2_mbus_framefmt *mbfmt = &format.format;
700ee4a77a3SMauro Carvalho Chehab 	int pix_idx;
701ee4a77a3SMauro Carvalho Chehab 	int ret;
702ee4a77a3SMauro Carvalho Chehab 
703ee4a77a3SMauro Carvalho Chehab 	if (vb2_is_busy(&vou_dev->queue))
704ee4a77a3SMauro Carvalho Chehab 		return -EBUSY;
705ee4a77a3SMauro Carvalho Chehab 
706ee4a77a3SMauro Carvalho Chehab 	for (pix_idx = 0; pix_idx < ARRAY_SIZE(vou_fmt); pix_idx++)
707ee4a77a3SMauro Carvalho Chehab 		if (vou_fmt[pix_idx].pfmt == pix->pixelformat)
708ee4a77a3SMauro Carvalho Chehab 			break;
709ee4a77a3SMauro Carvalho Chehab 
710ee4a77a3SMauro Carvalho Chehab 	geo.in_width = pix->width;
711ee4a77a3SMauro Carvalho Chehab 	geo.in_height = pix->height;
712ee4a77a3SMauro Carvalho Chehab 	geo.output = vou_dev->rect;
713ee4a77a3SMauro Carvalho Chehab 
714ee4a77a3SMauro Carvalho Chehab 	vou_adjust_output(&geo, vou_dev->std);
715ee4a77a3SMauro Carvalho Chehab 
716ee4a77a3SMauro Carvalho Chehab 	mbfmt->width = geo.output.width;
717ee4a77a3SMauro Carvalho Chehab 	mbfmt->height = geo.output.height;
718ee4a77a3SMauro Carvalho Chehab 	ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad,
719ee4a77a3SMauro Carvalho Chehab 					 set_fmt, NULL, &format);
720ee4a77a3SMauro Carvalho Chehab 	/* Must be implemented, so, don't check for -ENOIOCTLCMD */
721ee4a77a3SMauro Carvalho Chehab 	if (ret < 0)
722ee4a77a3SMauro Carvalho Chehab 		return ret;
723ee4a77a3SMauro Carvalho Chehab 
724ee4a77a3SMauro Carvalho Chehab 	dev_dbg(vou_dev->v4l2_dev.dev, "%s(): %ux%u -> %ux%u\n", __func__,
725ee4a77a3SMauro Carvalho Chehab 		geo.output.width, geo.output.height, mbfmt->width, mbfmt->height);
726ee4a77a3SMauro Carvalho Chehab 
727ee4a77a3SMauro Carvalho Chehab 	if (vou_dev->std & V4L2_STD_525_60)
728ee4a77a3SMauro Carvalho Chehab 		img_height_max = 480;
729ee4a77a3SMauro Carvalho Chehab 	else
730ee4a77a3SMauro Carvalho Chehab 		img_height_max = 576;
731ee4a77a3SMauro Carvalho Chehab 
732ee4a77a3SMauro Carvalho Chehab 	/* Sanity checks */
733ee4a77a3SMauro Carvalho Chehab 	if ((unsigned)mbfmt->width > VOU_MAX_IMAGE_WIDTH ||
734ee4a77a3SMauro Carvalho Chehab 	    (unsigned)mbfmt->height > img_height_max ||
735ee4a77a3SMauro Carvalho Chehab 	    mbfmt->code != MEDIA_BUS_FMT_YUYV8_2X8)
736ee4a77a3SMauro Carvalho Chehab 		return -EIO;
737ee4a77a3SMauro Carvalho Chehab 
738ee4a77a3SMauro Carvalho Chehab 	if (mbfmt->width != geo.output.width ||
739ee4a77a3SMauro Carvalho Chehab 	    mbfmt->height != geo.output.height) {
740ee4a77a3SMauro Carvalho Chehab 		geo.output.width = mbfmt->width;
741ee4a77a3SMauro Carvalho Chehab 		geo.output.height = mbfmt->height;
742ee4a77a3SMauro Carvalho Chehab 
743ee4a77a3SMauro Carvalho Chehab 		vou_adjust_input(&geo, vou_dev->std);
744ee4a77a3SMauro Carvalho Chehab 	}
745ee4a77a3SMauro Carvalho Chehab 
746ee4a77a3SMauro Carvalho Chehab 	/* We tried to preserve output rectangle, but it could have changed */
747ee4a77a3SMauro Carvalho Chehab 	vou_dev->rect = geo.output;
748ee4a77a3SMauro Carvalho Chehab 	pix->width = geo.in_width;
749ee4a77a3SMauro Carvalho Chehab 	pix->height = geo.in_height;
750ee4a77a3SMauro Carvalho Chehab 
751ee4a77a3SMauro Carvalho Chehab 	dev_dbg(vou_dev->v4l2_dev.dev, "%s(): %ux%u\n", __func__,
752ee4a77a3SMauro Carvalho Chehab 		pix->width, pix->height);
753ee4a77a3SMauro Carvalho Chehab 
754ee4a77a3SMauro Carvalho Chehab 	vou_dev->pix_idx = pix_idx;
755ee4a77a3SMauro Carvalho Chehab 
756ee4a77a3SMauro Carvalho Chehab 	vou_dev->pix = *pix;
757ee4a77a3SMauro Carvalho Chehab 
758ee4a77a3SMauro Carvalho Chehab 	sh_vou_configure_geometry(vou_dev, pix_idx,
759ee4a77a3SMauro Carvalho Chehab 				  geo.scale_idx_h, geo.scale_idx_v);
760ee4a77a3SMauro Carvalho Chehab 
761ee4a77a3SMauro Carvalho Chehab 	return 0;
762ee4a77a3SMauro Carvalho Chehab }
763ee4a77a3SMauro Carvalho Chehab 
sh_vou_s_fmt_vid_out(struct file * file,void * priv,struct v4l2_format * fmt)764ee4a77a3SMauro Carvalho Chehab static int sh_vou_s_fmt_vid_out(struct file *file, void *priv,
765ee4a77a3SMauro Carvalho Chehab 				struct v4l2_format *fmt)
766ee4a77a3SMauro Carvalho Chehab {
767ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = video_drvdata(file);
768ee4a77a3SMauro Carvalho Chehab 	int ret = sh_vou_try_fmt_vid_out(file, priv, fmt);
769ee4a77a3SMauro Carvalho Chehab 
770ee4a77a3SMauro Carvalho Chehab 	if (ret)
771ee4a77a3SMauro Carvalho Chehab 		return ret;
772ee4a77a3SMauro Carvalho Chehab 	return sh_vou_set_fmt_vid_out(vou_dev, &fmt->fmt.pix);
773ee4a77a3SMauro Carvalho Chehab }
774ee4a77a3SMauro Carvalho Chehab 
sh_vou_enum_output(struct file * file,void * fh,struct v4l2_output * a)775ee4a77a3SMauro Carvalho Chehab static int sh_vou_enum_output(struct file *file, void *fh,
776ee4a77a3SMauro Carvalho Chehab 			      struct v4l2_output *a)
777ee4a77a3SMauro Carvalho Chehab {
778ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = video_drvdata(file);
779ee4a77a3SMauro Carvalho Chehab 
780ee4a77a3SMauro Carvalho Chehab 	if (a->index)
781ee4a77a3SMauro Carvalho Chehab 		return -EINVAL;
782ee4a77a3SMauro Carvalho Chehab 	strscpy(a->name, "Video Out", sizeof(a->name));
783ee4a77a3SMauro Carvalho Chehab 	a->type = V4L2_OUTPUT_TYPE_ANALOG;
784ee4a77a3SMauro Carvalho Chehab 	a->std = vou_dev->vdev.tvnorms;
785ee4a77a3SMauro Carvalho Chehab 	return 0;
786ee4a77a3SMauro Carvalho Chehab }
787ee4a77a3SMauro Carvalho Chehab 
sh_vou_g_output(struct file * file,void * fh,unsigned int * i)788ee4a77a3SMauro Carvalho Chehab static int sh_vou_g_output(struct file *file, void *fh, unsigned int *i)
789ee4a77a3SMauro Carvalho Chehab {
790ee4a77a3SMauro Carvalho Chehab 	*i = 0;
791ee4a77a3SMauro Carvalho Chehab 	return 0;
792ee4a77a3SMauro Carvalho Chehab }
793ee4a77a3SMauro Carvalho Chehab 
sh_vou_s_output(struct file * file,void * fh,unsigned int i)794ee4a77a3SMauro Carvalho Chehab static int sh_vou_s_output(struct file *file, void *fh, unsigned int i)
795ee4a77a3SMauro Carvalho Chehab {
796ee4a77a3SMauro Carvalho Chehab 	return i ? -EINVAL : 0;
797ee4a77a3SMauro Carvalho Chehab }
798ee4a77a3SMauro Carvalho Chehab 
sh_vou_ntsc_mode(enum sh_vou_bus_fmt bus_fmt)799ee4a77a3SMauro Carvalho Chehab static u32 sh_vou_ntsc_mode(enum sh_vou_bus_fmt bus_fmt)
800ee4a77a3SMauro Carvalho Chehab {
801ee4a77a3SMauro Carvalho Chehab 	switch (bus_fmt) {
802ee4a77a3SMauro Carvalho Chehab 	default:
803ee4a77a3SMauro Carvalho Chehab 		pr_warn("%s(): Invalid bus-format code %d, using default 8-bit\n",
804ee4a77a3SMauro Carvalho Chehab 			__func__, bus_fmt);
805ee4a77a3SMauro Carvalho Chehab 		fallthrough;
806ee4a77a3SMauro Carvalho Chehab 	case SH_VOU_BUS_8BIT:
807ee4a77a3SMauro Carvalho Chehab 		return 1;
808ee4a77a3SMauro Carvalho Chehab 	case SH_VOU_BUS_16BIT:
809ee4a77a3SMauro Carvalho Chehab 		return 0;
810ee4a77a3SMauro Carvalho Chehab 	case SH_VOU_BUS_BT656:
811ee4a77a3SMauro Carvalho Chehab 		return 3;
812ee4a77a3SMauro Carvalho Chehab 	}
813ee4a77a3SMauro Carvalho Chehab }
814ee4a77a3SMauro Carvalho Chehab 
sh_vou_s_std(struct file * file,void * priv,v4l2_std_id std_id)815ee4a77a3SMauro Carvalho Chehab static int sh_vou_s_std(struct file *file, void *priv, v4l2_std_id std_id)
816ee4a77a3SMauro Carvalho Chehab {
817ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = video_drvdata(file);
818ee4a77a3SMauro Carvalho Chehab 	int ret;
819ee4a77a3SMauro Carvalho Chehab 
820ee4a77a3SMauro Carvalho Chehab 	dev_dbg(vou_dev->v4l2_dev.dev, "%s(): 0x%llx\n", __func__, std_id);
821ee4a77a3SMauro Carvalho Chehab 
822ee4a77a3SMauro Carvalho Chehab 	if (std_id == vou_dev->std)
823ee4a77a3SMauro Carvalho Chehab 		return 0;
824ee4a77a3SMauro Carvalho Chehab 
825ee4a77a3SMauro Carvalho Chehab 	if (vb2_is_busy(&vou_dev->queue))
826ee4a77a3SMauro Carvalho Chehab 		return -EBUSY;
827ee4a77a3SMauro Carvalho Chehab 
828ee4a77a3SMauro Carvalho Chehab 	ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, video,
829ee4a77a3SMauro Carvalho Chehab 					 s_std_output, std_id);
830ee4a77a3SMauro Carvalho Chehab 	/* Shall we continue, if the subdev doesn't support .s_std_output()? */
831ee4a77a3SMauro Carvalho Chehab 	if (ret < 0 && ret != -ENOIOCTLCMD)
832ee4a77a3SMauro Carvalho Chehab 		return ret;
833ee4a77a3SMauro Carvalho Chehab 
834ee4a77a3SMauro Carvalho Chehab 	vou_dev->rect.top = vou_dev->rect.left = 0;
835ee4a77a3SMauro Carvalho Chehab 	vou_dev->rect.width = VOU_MAX_IMAGE_WIDTH;
836ee4a77a3SMauro Carvalho Chehab 	if (std_id & V4L2_STD_525_60) {
837ee4a77a3SMauro Carvalho Chehab 		sh_vou_reg_ab_set(vou_dev, VOUCR,
838ee4a77a3SMauro Carvalho Chehab 			sh_vou_ntsc_mode(vou_dev->pdata->bus_fmt) << 29, 7 << 29);
839ee4a77a3SMauro Carvalho Chehab 		vou_dev->rect.height = 480;
840ee4a77a3SMauro Carvalho Chehab 	} else {
841ee4a77a3SMauro Carvalho Chehab 		sh_vou_reg_ab_set(vou_dev, VOUCR, 5 << 29, 7 << 29);
842ee4a77a3SMauro Carvalho Chehab 		vou_dev->rect.height = 576;
843ee4a77a3SMauro Carvalho Chehab 	}
844ee4a77a3SMauro Carvalho Chehab 
845ee4a77a3SMauro Carvalho Chehab 	vou_dev->pix.width = vou_dev->rect.width;
846ee4a77a3SMauro Carvalho Chehab 	vou_dev->pix.height = vou_dev->rect.height;
847ee4a77a3SMauro Carvalho Chehab 	vou_dev->pix.bytesperline =
848ee4a77a3SMauro Carvalho Chehab 		vou_dev->pix.width * vou_fmt[vou_dev->pix_idx].bpl;
849ee4a77a3SMauro Carvalho Chehab 	vou_dev->pix.sizeimage = vou_dev->pix.height *
850ee4a77a3SMauro Carvalho Chehab 		((vou_dev->pix.width * vou_fmt[vou_dev->pix_idx].bpp) >> 3);
851ee4a77a3SMauro Carvalho Chehab 	vou_dev->std = std_id;
852ee4a77a3SMauro Carvalho Chehab 	sh_vou_set_fmt_vid_out(vou_dev, &vou_dev->pix);
853ee4a77a3SMauro Carvalho Chehab 
854ee4a77a3SMauro Carvalho Chehab 	return 0;
855ee4a77a3SMauro Carvalho Chehab }
856ee4a77a3SMauro Carvalho Chehab 
sh_vou_g_std(struct file * file,void * priv,v4l2_std_id * std)857ee4a77a3SMauro Carvalho Chehab static int sh_vou_g_std(struct file *file, void *priv, v4l2_std_id *std)
858ee4a77a3SMauro Carvalho Chehab {
859ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = video_drvdata(file);
860ee4a77a3SMauro Carvalho Chehab 
861ee4a77a3SMauro Carvalho Chehab 	dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__);
862ee4a77a3SMauro Carvalho Chehab 
863ee4a77a3SMauro Carvalho Chehab 	*std = vou_dev->std;
864ee4a77a3SMauro Carvalho Chehab 
865ee4a77a3SMauro Carvalho Chehab 	return 0;
866ee4a77a3SMauro Carvalho Chehab }
867ee4a77a3SMauro Carvalho Chehab 
sh_vou_log_status(struct file * file,void * priv)868ee4a77a3SMauro Carvalho Chehab static int sh_vou_log_status(struct file *file, void *priv)
869ee4a77a3SMauro Carvalho Chehab {
870ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = video_drvdata(file);
871ee4a77a3SMauro Carvalho Chehab 
872ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUER:   0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUER));
873ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUCR:   0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUCR));
874ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUSTR:  0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUSTR));
875ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUVCR:  0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUVCR));
876ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUISR:  0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUISR));
877ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUBCR:  0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUBCR));
878ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUDPR:  0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUDPR));
879ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUDSR:  0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUDSR));
880ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUVPR:  0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUVPR));
881ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUIR:   0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUIR));
882ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUSRR:  0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUSRR));
883ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUMSR:  0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUMSR));
884ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUHIR:  0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUHIR));
885ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUDFR:  0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUDFR));
886ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUAD1R: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUAD1R));
887ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUAD2R: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUAD2R));
888ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUAIR:  0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUAIR));
889ee4a77a3SMauro Carvalho Chehab 	pr_info("VOUSWR:  0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUSWR));
890ee4a77a3SMauro Carvalho Chehab 	pr_info("VOURCR:  0x%08x\n", sh_vou_reg_a_read(vou_dev, VOURCR));
891ee4a77a3SMauro Carvalho Chehab 	pr_info("VOURPR:  0x%08x\n", sh_vou_reg_a_read(vou_dev, VOURPR));
892ee4a77a3SMauro Carvalho Chehab 	return 0;
893ee4a77a3SMauro Carvalho Chehab }
894ee4a77a3SMauro Carvalho Chehab 
sh_vou_g_selection(struct file * file,void * fh,struct v4l2_selection * sel)895ee4a77a3SMauro Carvalho Chehab static int sh_vou_g_selection(struct file *file, void *fh,
896ee4a77a3SMauro Carvalho Chehab 			      struct v4l2_selection *sel)
897ee4a77a3SMauro Carvalho Chehab {
898ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = video_drvdata(file);
899ee4a77a3SMauro Carvalho Chehab 
900ee4a77a3SMauro Carvalho Chehab 	if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
901ee4a77a3SMauro Carvalho Chehab 		return -EINVAL;
902ee4a77a3SMauro Carvalho Chehab 	switch (sel->target) {
903ee4a77a3SMauro Carvalho Chehab 	case V4L2_SEL_TGT_COMPOSE:
904ee4a77a3SMauro Carvalho Chehab 		sel->r = vou_dev->rect;
905ee4a77a3SMauro Carvalho Chehab 		break;
906ee4a77a3SMauro Carvalho Chehab 	case V4L2_SEL_TGT_COMPOSE_DEFAULT:
907ee4a77a3SMauro Carvalho Chehab 	case V4L2_SEL_TGT_COMPOSE_BOUNDS:
908ee4a77a3SMauro Carvalho Chehab 		sel->r.left = 0;
909ee4a77a3SMauro Carvalho Chehab 		sel->r.top = 0;
910ee4a77a3SMauro Carvalho Chehab 		sel->r.width = VOU_MAX_IMAGE_WIDTH;
911ee4a77a3SMauro Carvalho Chehab 		if (vou_dev->std & V4L2_STD_525_60)
912ee4a77a3SMauro Carvalho Chehab 			sel->r.height = 480;
913ee4a77a3SMauro Carvalho Chehab 		else
914ee4a77a3SMauro Carvalho Chehab 			sel->r.height = 576;
915ee4a77a3SMauro Carvalho Chehab 		break;
916ee4a77a3SMauro Carvalho Chehab 	default:
917ee4a77a3SMauro Carvalho Chehab 		return -EINVAL;
918ee4a77a3SMauro Carvalho Chehab 	}
919ee4a77a3SMauro Carvalho Chehab 	return 0;
920ee4a77a3SMauro Carvalho Chehab }
921ee4a77a3SMauro Carvalho Chehab 
922ee4a77a3SMauro Carvalho Chehab /* Assume a dull encoder, do all the work ourselves. */
sh_vou_s_selection(struct file * file,void * fh,struct v4l2_selection * sel)923ee4a77a3SMauro Carvalho Chehab static int sh_vou_s_selection(struct file *file, void *fh,
924ee4a77a3SMauro Carvalho Chehab 			      struct v4l2_selection *sel)
925ee4a77a3SMauro Carvalho Chehab {
926ee4a77a3SMauro Carvalho Chehab 	struct v4l2_rect *rect = &sel->r;
927ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = video_drvdata(file);
928ee4a77a3SMauro Carvalho Chehab 	struct v4l2_subdev_selection sd_sel = {
929ee4a77a3SMauro Carvalho Chehab 		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
930ee4a77a3SMauro Carvalho Chehab 		.target = V4L2_SEL_TGT_COMPOSE,
931ee4a77a3SMauro Carvalho Chehab 	};
932ee4a77a3SMauro Carvalho Chehab 	struct v4l2_pix_format *pix = &vou_dev->pix;
933ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_geometry geo;
934ee4a77a3SMauro Carvalho Chehab 	struct v4l2_subdev_format format = {
935ee4a77a3SMauro Carvalho Chehab 		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
936ee4a77a3SMauro Carvalho Chehab 		/* Revisit: is this the correct code? */
937ee4a77a3SMauro Carvalho Chehab 		.format.code = MEDIA_BUS_FMT_YUYV8_2X8,
938ee4a77a3SMauro Carvalho Chehab 		.format.field = V4L2_FIELD_INTERLACED,
939ee4a77a3SMauro Carvalho Chehab 		.format.colorspace = V4L2_COLORSPACE_SMPTE170M,
940ee4a77a3SMauro Carvalho Chehab 	};
941ee4a77a3SMauro Carvalho Chehab 	unsigned int img_height_max;
942ee4a77a3SMauro Carvalho Chehab 	int ret;
943ee4a77a3SMauro Carvalho Chehab 
944ee4a77a3SMauro Carvalho Chehab 	if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT ||
945ee4a77a3SMauro Carvalho Chehab 	    sel->target != V4L2_SEL_TGT_COMPOSE)
946ee4a77a3SMauro Carvalho Chehab 		return -EINVAL;
947ee4a77a3SMauro Carvalho Chehab 
948ee4a77a3SMauro Carvalho Chehab 	if (vb2_is_busy(&vou_dev->queue))
949ee4a77a3SMauro Carvalho Chehab 		return -EBUSY;
950ee4a77a3SMauro Carvalho Chehab 
951ee4a77a3SMauro Carvalho Chehab 	if (vou_dev->std & V4L2_STD_525_60)
952ee4a77a3SMauro Carvalho Chehab 		img_height_max = 480;
953ee4a77a3SMauro Carvalho Chehab 	else
954ee4a77a3SMauro Carvalho Chehab 		img_height_max = 576;
955ee4a77a3SMauro Carvalho Chehab 
956ee4a77a3SMauro Carvalho Chehab 	v4l_bound_align_image(&rect->width,
957ee4a77a3SMauro Carvalho Chehab 			      VOU_MIN_IMAGE_WIDTH, VOU_MAX_IMAGE_WIDTH, 1,
958ee4a77a3SMauro Carvalho Chehab 			      &rect->height,
959ee4a77a3SMauro Carvalho Chehab 			      VOU_MIN_IMAGE_HEIGHT, img_height_max, 1, 0);
960ee4a77a3SMauro Carvalho Chehab 
961ee4a77a3SMauro Carvalho Chehab 	if (rect->width + rect->left > VOU_MAX_IMAGE_WIDTH)
962ee4a77a3SMauro Carvalho Chehab 		rect->left = VOU_MAX_IMAGE_WIDTH - rect->width;
963ee4a77a3SMauro Carvalho Chehab 
964ee4a77a3SMauro Carvalho Chehab 	if (rect->height + rect->top > img_height_max)
965ee4a77a3SMauro Carvalho Chehab 		rect->top = img_height_max - rect->height;
966ee4a77a3SMauro Carvalho Chehab 
967ee4a77a3SMauro Carvalho Chehab 	geo.output = *rect;
968ee4a77a3SMauro Carvalho Chehab 	geo.in_width = pix->width;
969ee4a77a3SMauro Carvalho Chehab 	geo.in_height = pix->height;
970ee4a77a3SMauro Carvalho Chehab 
971ee4a77a3SMauro Carvalho Chehab 	/* Configure the encoder one-to-one, position at 0, ignore errors */
972ee4a77a3SMauro Carvalho Chehab 	sd_sel.r.width = geo.output.width;
973ee4a77a3SMauro Carvalho Chehab 	sd_sel.r.height = geo.output.height;
974ee4a77a3SMauro Carvalho Chehab 	/*
975ee4a77a3SMauro Carvalho Chehab 	 * We first issue a S_SELECTION, so that the subsequent S_FMT delivers the
976ee4a77a3SMauro Carvalho Chehab 	 * final encoder configuration.
977ee4a77a3SMauro Carvalho Chehab 	 */
978ee4a77a3SMauro Carvalho Chehab 	v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad,
979ee4a77a3SMauro Carvalho Chehab 				   set_selection, NULL, &sd_sel);
980ee4a77a3SMauro Carvalho Chehab 	format.format.width = geo.output.width;
981ee4a77a3SMauro Carvalho Chehab 	format.format.height = geo.output.height;
982ee4a77a3SMauro Carvalho Chehab 	ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad,
983ee4a77a3SMauro Carvalho Chehab 					 set_fmt, NULL, &format);
984ee4a77a3SMauro Carvalho Chehab 	/* Must be implemented, so, don't check for -ENOIOCTLCMD */
985ee4a77a3SMauro Carvalho Chehab 	if (ret < 0)
986ee4a77a3SMauro Carvalho Chehab 		return ret;
987ee4a77a3SMauro Carvalho Chehab 
988ee4a77a3SMauro Carvalho Chehab 	/* Sanity checks */
989ee4a77a3SMauro Carvalho Chehab 	if ((unsigned)format.format.width > VOU_MAX_IMAGE_WIDTH ||
990ee4a77a3SMauro Carvalho Chehab 	    (unsigned)format.format.height > img_height_max ||
991ee4a77a3SMauro Carvalho Chehab 	    format.format.code != MEDIA_BUS_FMT_YUYV8_2X8)
992ee4a77a3SMauro Carvalho Chehab 		return -EIO;
993ee4a77a3SMauro Carvalho Chehab 
994ee4a77a3SMauro Carvalho Chehab 	geo.output.width = format.format.width;
995ee4a77a3SMauro Carvalho Chehab 	geo.output.height = format.format.height;
996ee4a77a3SMauro Carvalho Chehab 
997ee4a77a3SMauro Carvalho Chehab 	/*
998ee4a77a3SMauro Carvalho Chehab 	 * No down-scaling. According to the API, current call has precedence:
999ee4a77a3SMauro Carvalho Chehab 	 * https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/crop.html#cropping-structures
1000ee4a77a3SMauro Carvalho Chehab 	 */
1001ee4a77a3SMauro Carvalho Chehab 	vou_adjust_input(&geo, vou_dev->std);
1002ee4a77a3SMauro Carvalho Chehab 
1003ee4a77a3SMauro Carvalho Chehab 	/* We tried to preserve output rectangle, but it could have changed */
1004ee4a77a3SMauro Carvalho Chehab 	vou_dev->rect = geo.output;
1005ee4a77a3SMauro Carvalho Chehab 	pix->width = geo.in_width;
1006ee4a77a3SMauro Carvalho Chehab 	pix->height = geo.in_height;
1007ee4a77a3SMauro Carvalho Chehab 
1008ee4a77a3SMauro Carvalho Chehab 	sh_vou_configure_geometry(vou_dev, vou_dev->pix_idx,
1009ee4a77a3SMauro Carvalho Chehab 				  geo.scale_idx_h, geo.scale_idx_v);
1010ee4a77a3SMauro Carvalho Chehab 
1011ee4a77a3SMauro Carvalho Chehab 	return 0;
1012ee4a77a3SMauro Carvalho Chehab }
1013ee4a77a3SMauro Carvalho Chehab 
sh_vou_isr(int irq,void * dev_id)1014ee4a77a3SMauro Carvalho Chehab static irqreturn_t sh_vou_isr(int irq, void *dev_id)
1015ee4a77a3SMauro Carvalho Chehab {
1016ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = dev_id;
1017ee4a77a3SMauro Carvalho Chehab 	static unsigned long j;
1018ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_buffer *vb;
1019ee4a77a3SMauro Carvalho Chehab 	static int cnt;
1020ee4a77a3SMauro Carvalho Chehab 	u32 irq_status = sh_vou_reg_a_read(vou_dev, VOUIR), masked;
1021ee4a77a3SMauro Carvalho Chehab 	u32 vou_status = sh_vou_reg_a_read(vou_dev, VOUSTR);
1022ee4a77a3SMauro Carvalho Chehab 
1023ee4a77a3SMauro Carvalho Chehab 	if (!(irq_status & 0x300)) {
1024ee4a77a3SMauro Carvalho Chehab 		if (printk_timed_ratelimit(&j, 500))
1025ee4a77a3SMauro Carvalho Chehab 			dev_warn(vou_dev->v4l2_dev.dev, "IRQ status 0x%x!\n",
1026ee4a77a3SMauro Carvalho Chehab 				 irq_status);
1027ee4a77a3SMauro Carvalho Chehab 		return IRQ_NONE;
1028ee4a77a3SMauro Carvalho Chehab 	}
1029ee4a77a3SMauro Carvalho Chehab 
1030ee4a77a3SMauro Carvalho Chehab 	spin_lock(&vou_dev->lock);
1031ee4a77a3SMauro Carvalho Chehab 	if (!vou_dev->active || list_empty(&vou_dev->buf_list)) {
1032ee4a77a3SMauro Carvalho Chehab 		if (printk_timed_ratelimit(&j, 500))
1033ee4a77a3SMauro Carvalho Chehab 			dev_warn(vou_dev->v4l2_dev.dev,
1034ee4a77a3SMauro Carvalho Chehab 				 "IRQ without active buffer: %x!\n", irq_status);
1035ee4a77a3SMauro Carvalho Chehab 		/* Just ack: buf_release will disable further interrupts */
1036ee4a77a3SMauro Carvalho Chehab 		sh_vou_reg_a_set(vou_dev, VOUIR, 0, 0x300);
1037ee4a77a3SMauro Carvalho Chehab 		spin_unlock(&vou_dev->lock);
1038ee4a77a3SMauro Carvalho Chehab 		return IRQ_HANDLED;
1039ee4a77a3SMauro Carvalho Chehab 	}
1040ee4a77a3SMauro Carvalho Chehab 
1041ee4a77a3SMauro Carvalho Chehab 	masked = ~(0x300 & irq_status) & irq_status & 0x30304;
1042ee4a77a3SMauro Carvalho Chehab 	dev_dbg(vou_dev->v4l2_dev.dev,
1043ee4a77a3SMauro Carvalho Chehab 		"IRQ status 0x%x -> 0x%x, VOU status 0x%x, cnt %d\n",
1044ee4a77a3SMauro Carvalho Chehab 		irq_status, masked, vou_status, cnt);
1045ee4a77a3SMauro Carvalho Chehab 
1046ee4a77a3SMauro Carvalho Chehab 	cnt++;
1047ee4a77a3SMauro Carvalho Chehab 	/* side = vou_status & 0x10000; */
1048ee4a77a3SMauro Carvalho Chehab 
1049ee4a77a3SMauro Carvalho Chehab 	/* Clear only set interrupts */
1050ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_a_write(vou_dev, VOUIR, masked);
1051ee4a77a3SMauro Carvalho Chehab 
1052ee4a77a3SMauro Carvalho Chehab 	vb = vou_dev->active;
1053ee4a77a3SMauro Carvalho Chehab 	if (list_is_singular(&vb->list)) {
1054ee4a77a3SMauro Carvalho Chehab 		/* Keep cycling while no next buffer is available */
1055ee4a77a3SMauro Carvalho Chehab 		sh_vou_schedule_next(vou_dev, &vb->vb);
1056ee4a77a3SMauro Carvalho Chehab 		spin_unlock(&vou_dev->lock);
1057ee4a77a3SMauro Carvalho Chehab 		return IRQ_HANDLED;
1058ee4a77a3SMauro Carvalho Chehab 	}
1059ee4a77a3SMauro Carvalho Chehab 
1060ee4a77a3SMauro Carvalho Chehab 	list_del(&vb->list);
1061ee4a77a3SMauro Carvalho Chehab 
1062ee4a77a3SMauro Carvalho Chehab 	vb->vb.vb2_buf.timestamp = ktime_get_ns();
1063ee4a77a3SMauro Carvalho Chehab 	vb->vb.sequence = vou_dev->sequence++;
1064ee4a77a3SMauro Carvalho Chehab 	vb->vb.field = V4L2_FIELD_INTERLACED;
1065ee4a77a3SMauro Carvalho Chehab 	vb2_buffer_done(&vb->vb.vb2_buf, VB2_BUF_STATE_DONE);
1066ee4a77a3SMauro Carvalho Chehab 
1067ee4a77a3SMauro Carvalho Chehab 	vou_dev->active = list_entry(vou_dev->buf_list.next,
1068ee4a77a3SMauro Carvalho Chehab 				     struct sh_vou_buffer, list);
1069ee4a77a3SMauro Carvalho Chehab 
1070ee4a77a3SMauro Carvalho Chehab 	if (list_is_singular(&vou_dev->buf_list)) {
1071ee4a77a3SMauro Carvalho Chehab 		/* Keep cycling while no next buffer is available */
1072ee4a77a3SMauro Carvalho Chehab 		sh_vou_schedule_next(vou_dev, &vou_dev->active->vb);
1073ee4a77a3SMauro Carvalho Chehab 	} else {
1074ee4a77a3SMauro Carvalho Chehab 		struct sh_vou_buffer *new = list_entry(vou_dev->active->list.next,
1075ee4a77a3SMauro Carvalho Chehab 						struct sh_vou_buffer, list);
1076ee4a77a3SMauro Carvalho Chehab 		sh_vou_schedule_next(vou_dev, &new->vb);
1077ee4a77a3SMauro Carvalho Chehab 	}
1078ee4a77a3SMauro Carvalho Chehab 
1079ee4a77a3SMauro Carvalho Chehab 	spin_unlock(&vou_dev->lock);
1080ee4a77a3SMauro Carvalho Chehab 
1081ee4a77a3SMauro Carvalho Chehab 	return IRQ_HANDLED;
1082ee4a77a3SMauro Carvalho Chehab }
1083ee4a77a3SMauro Carvalho Chehab 
sh_vou_hw_init(struct sh_vou_device * vou_dev)1084ee4a77a3SMauro Carvalho Chehab static int sh_vou_hw_init(struct sh_vou_device *vou_dev)
1085ee4a77a3SMauro Carvalho Chehab {
1086ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_pdata *pdata = vou_dev->pdata;
1087ee4a77a3SMauro Carvalho Chehab 	u32 voucr = sh_vou_ntsc_mode(pdata->bus_fmt) << 29;
1088ee4a77a3SMauro Carvalho Chehab 	int i = 100;
1089ee4a77a3SMauro Carvalho Chehab 
1090ee4a77a3SMauro Carvalho Chehab 	/* Disable all IRQs */
1091ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_a_write(vou_dev, VOUIR, 0);
1092ee4a77a3SMauro Carvalho Chehab 
1093ee4a77a3SMauro Carvalho Chehab 	/* Reset VOU interfaces - registers unaffected */
1094ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_a_write(vou_dev, VOUSRR, 0x101);
1095ee4a77a3SMauro Carvalho Chehab 	while (--i && (sh_vou_reg_a_read(vou_dev, VOUSRR) & 0x101))
1096ee4a77a3SMauro Carvalho Chehab 		udelay(1);
1097ee4a77a3SMauro Carvalho Chehab 
1098ee4a77a3SMauro Carvalho Chehab 	if (!i)
1099ee4a77a3SMauro Carvalho Chehab 		return -ETIMEDOUT;
1100ee4a77a3SMauro Carvalho Chehab 
1101ee4a77a3SMauro Carvalho Chehab 	dev_dbg(vou_dev->v4l2_dev.dev, "Reset took %dus\n", 100 - i);
1102ee4a77a3SMauro Carvalho Chehab 
1103ee4a77a3SMauro Carvalho Chehab 	if (pdata->flags & SH_VOU_PCLK_FALLING)
1104ee4a77a3SMauro Carvalho Chehab 		voucr |= 1 << 28;
1105ee4a77a3SMauro Carvalho Chehab 	if (pdata->flags & SH_VOU_HSYNC_LOW)
1106ee4a77a3SMauro Carvalho Chehab 		voucr |= 1 << 27;
1107ee4a77a3SMauro Carvalho Chehab 	if (pdata->flags & SH_VOU_VSYNC_LOW)
1108ee4a77a3SMauro Carvalho Chehab 		voucr |= 1 << 26;
1109ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_ab_set(vou_dev, VOUCR, voucr, 0xfc000000);
1110ee4a77a3SMauro Carvalho Chehab 
1111ee4a77a3SMauro Carvalho Chehab 	/* Manual register side switching at first */
1112ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_a_write(vou_dev, VOURCR, 4);
1113ee4a77a3SMauro Carvalho Chehab 	/* Default - fixed HSYNC length, can be made configurable is required */
1114ee4a77a3SMauro Carvalho Chehab 	sh_vou_reg_ab_write(vou_dev, VOUMSR, 0x800000);
1115ee4a77a3SMauro Carvalho Chehab 
1116ee4a77a3SMauro Carvalho Chehab 	sh_vou_set_fmt_vid_out(vou_dev, &vou_dev->pix);
1117ee4a77a3SMauro Carvalho Chehab 
1118ee4a77a3SMauro Carvalho Chehab 	return 0;
1119ee4a77a3SMauro Carvalho Chehab }
1120ee4a77a3SMauro Carvalho Chehab 
1121ee4a77a3SMauro Carvalho Chehab /* File operations */
sh_vou_open(struct file * file)1122ee4a77a3SMauro Carvalho Chehab static int sh_vou_open(struct file *file)
1123ee4a77a3SMauro Carvalho Chehab {
1124ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = video_drvdata(file);
1125ee4a77a3SMauro Carvalho Chehab 	int err;
1126ee4a77a3SMauro Carvalho Chehab 
1127ee4a77a3SMauro Carvalho Chehab 	if (mutex_lock_interruptible(&vou_dev->fop_lock))
1128ee4a77a3SMauro Carvalho Chehab 		return -ERESTARTSYS;
1129ee4a77a3SMauro Carvalho Chehab 
1130ee4a77a3SMauro Carvalho Chehab 	err = v4l2_fh_open(file);
1131ee4a77a3SMauro Carvalho Chehab 	if (err)
1132ee4a77a3SMauro Carvalho Chehab 		goto done_open;
1133ee4a77a3SMauro Carvalho Chehab 	if (v4l2_fh_is_singular_file(file) &&
1134ee4a77a3SMauro Carvalho Chehab 	    vou_dev->status == SH_VOU_INITIALISING) {
1135ee4a77a3SMauro Carvalho Chehab 		/* First open */
1136ee4a77a3SMauro Carvalho Chehab 		err = pm_runtime_resume_and_get(vou_dev->v4l2_dev.dev);
1137ee4a77a3SMauro Carvalho Chehab 		if (err < 0) {
1138ee4a77a3SMauro Carvalho Chehab 			v4l2_fh_release(file);
1139ee4a77a3SMauro Carvalho Chehab 			goto done_open;
1140ee4a77a3SMauro Carvalho Chehab 		}
1141ee4a77a3SMauro Carvalho Chehab 		err = sh_vou_hw_init(vou_dev);
1142ee4a77a3SMauro Carvalho Chehab 		if (err < 0) {
1143ee4a77a3SMauro Carvalho Chehab 			pm_runtime_put(vou_dev->v4l2_dev.dev);
1144ee4a77a3SMauro Carvalho Chehab 			v4l2_fh_release(file);
1145ee4a77a3SMauro Carvalho Chehab 		} else {
1146ee4a77a3SMauro Carvalho Chehab 			vou_dev->status = SH_VOU_IDLE;
1147ee4a77a3SMauro Carvalho Chehab 		}
1148ee4a77a3SMauro Carvalho Chehab 	}
1149ee4a77a3SMauro Carvalho Chehab done_open:
1150ee4a77a3SMauro Carvalho Chehab 	mutex_unlock(&vou_dev->fop_lock);
1151ee4a77a3SMauro Carvalho Chehab 	return err;
1152ee4a77a3SMauro Carvalho Chehab }
1153ee4a77a3SMauro Carvalho Chehab 
sh_vou_release(struct file * file)1154ee4a77a3SMauro Carvalho Chehab static int sh_vou_release(struct file *file)
1155ee4a77a3SMauro Carvalho Chehab {
1156ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = video_drvdata(file);
1157ee4a77a3SMauro Carvalho Chehab 	bool is_last;
1158ee4a77a3SMauro Carvalho Chehab 
1159ee4a77a3SMauro Carvalho Chehab 	mutex_lock(&vou_dev->fop_lock);
1160ee4a77a3SMauro Carvalho Chehab 	is_last = v4l2_fh_is_singular_file(file);
1161ee4a77a3SMauro Carvalho Chehab 	_vb2_fop_release(file, NULL);
1162ee4a77a3SMauro Carvalho Chehab 	if (is_last) {
1163ee4a77a3SMauro Carvalho Chehab 		/* Last close */
1164ee4a77a3SMauro Carvalho Chehab 		vou_dev->status = SH_VOU_INITIALISING;
1165ee4a77a3SMauro Carvalho Chehab 		sh_vou_reg_a_set(vou_dev, VOUER, 0, 0x101);
1166ee4a77a3SMauro Carvalho Chehab 		pm_runtime_put(vou_dev->v4l2_dev.dev);
1167ee4a77a3SMauro Carvalho Chehab 	}
1168ee4a77a3SMauro Carvalho Chehab 	mutex_unlock(&vou_dev->fop_lock);
1169ee4a77a3SMauro Carvalho Chehab 	return 0;
1170ee4a77a3SMauro Carvalho Chehab }
1171ee4a77a3SMauro Carvalho Chehab 
1172ee4a77a3SMauro Carvalho Chehab /* sh_vou display ioctl operations */
1173ee4a77a3SMauro Carvalho Chehab static const struct v4l2_ioctl_ops sh_vou_ioctl_ops = {
1174ee4a77a3SMauro Carvalho Chehab 	.vidioc_querycap		= sh_vou_querycap,
1175ee4a77a3SMauro Carvalho Chehab 	.vidioc_enum_fmt_vid_out	= sh_vou_enum_fmt_vid_out,
1176ee4a77a3SMauro Carvalho Chehab 	.vidioc_g_fmt_vid_out		= sh_vou_g_fmt_vid_out,
1177ee4a77a3SMauro Carvalho Chehab 	.vidioc_s_fmt_vid_out		= sh_vou_s_fmt_vid_out,
1178ee4a77a3SMauro Carvalho Chehab 	.vidioc_try_fmt_vid_out		= sh_vou_try_fmt_vid_out,
1179ee4a77a3SMauro Carvalho Chehab 	.vidioc_reqbufs			= vb2_ioctl_reqbufs,
1180ee4a77a3SMauro Carvalho Chehab 	.vidioc_create_bufs		= vb2_ioctl_create_bufs,
1181ee4a77a3SMauro Carvalho Chehab 	.vidioc_querybuf		= vb2_ioctl_querybuf,
1182ee4a77a3SMauro Carvalho Chehab 	.vidioc_qbuf			= vb2_ioctl_qbuf,
1183ee4a77a3SMauro Carvalho Chehab 	.vidioc_dqbuf			= vb2_ioctl_dqbuf,
1184ee4a77a3SMauro Carvalho Chehab 	.vidioc_prepare_buf		= vb2_ioctl_prepare_buf,
1185ee4a77a3SMauro Carvalho Chehab 	.vidioc_streamon		= vb2_ioctl_streamon,
1186ee4a77a3SMauro Carvalho Chehab 	.vidioc_streamoff		= vb2_ioctl_streamoff,
1187ee4a77a3SMauro Carvalho Chehab 	.vidioc_expbuf			= vb2_ioctl_expbuf,
1188ee4a77a3SMauro Carvalho Chehab 	.vidioc_g_output		= sh_vou_g_output,
1189ee4a77a3SMauro Carvalho Chehab 	.vidioc_s_output		= sh_vou_s_output,
1190ee4a77a3SMauro Carvalho Chehab 	.vidioc_enum_output		= sh_vou_enum_output,
1191ee4a77a3SMauro Carvalho Chehab 	.vidioc_s_std			= sh_vou_s_std,
1192ee4a77a3SMauro Carvalho Chehab 	.vidioc_g_std			= sh_vou_g_std,
1193ee4a77a3SMauro Carvalho Chehab 	.vidioc_g_selection		= sh_vou_g_selection,
1194ee4a77a3SMauro Carvalho Chehab 	.vidioc_s_selection		= sh_vou_s_selection,
1195ee4a77a3SMauro Carvalho Chehab 	.vidioc_log_status		= sh_vou_log_status,
1196ee4a77a3SMauro Carvalho Chehab };
1197ee4a77a3SMauro Carvalho Chehab 
1198ee4a77a3SMauro Carvalho Chehab static const struct v4l2_file_operations sh_vou_fops = {
1199ee4a77a3SMauro Carvalho Chehab 	.owner		= THIS_MODULE,
1200ee4a77a3SMauro Carvalho Chehab 	.open		= sh_vou_open,
1201ee4a77a3SMauro Carvalho Chehab 	.release	= sh_vou_release,
1202ee4a77a3SMauro Carvalho Chehab 	.unlocked_ioctl	= video_ioctl2,
1203ee4a77a3SMauro Carvalho Chehab 	.mmap		= vb2_fop_mmap,
1204ee4a77a3SMauro Carvalho Chehab 	.poll		= vb2_fop_poll,
1205ee4a77a3SMauro Carvalho Chehab 	.write		= vb2_fop_write,
1206ee4a77a3SMauro Carvalho Chehab };
1207ee4a77a3SMauro Carvalho Chehab 
1208ee4a77a3SMauro Carvalho Chehab static const struct video_device sh_vou_video_template = {
1209ee4a77a3SMauro Carvalho Chehab 	.name		= "sh_vou",
1210ee4a77a3SMauro Carvalho Chehab 	.fops		= &sh_vou_fops,
1211ee4a77a3SMauro Carvalho Chehab 	.ioctl_ops	= &sh_vou_ioctl_ops,
1212ee4a77a3SMauro Carvalho Chehab 	.tvnorms	= V4L2_STD_525_60, /* PAL only supported in 8-bit non-bt656 mode */
1213ee4a77a3SMauro Carvalho Chehab 	.vfl_dir	= VFL_DIR_TX,
1214ee4a77a3SMauro Carvalho Chehab 	.device_caps	= V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE |
1215ee4a77a3SMauro Carvalho Chehab 			  V4L2_CAP_STREAMING,
1216ee4a77a3SMauro Carvalho Chehab };
1217ee4a77a3SMauro Carvalho Chehab 
sh_vou_probe(struct platform_device * pdev)1218ee4a77a3SMauro Carvalho Chehab static int sh_vou_probe(struct platform_device *pdev)
1219ee4a77a3SMauro Carvalho Chehab {
1220ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_pdata *vou_pdata = pdev->dev.platform_data;
1221ee4a77a3SMauro Carvalho Chehab 	struct v4l2_rect *rect;
1222ee4a77a3SMauro Carvalho Chehab 	struct v4l2_pix_format *pix;
1223ee4a77a3SMauro Carvalho Chehab 	struct i2c_adapter *i2c_adap;
1224ee4a77a3SMauro Carvalho Chehab 	struct video_device *vdev;
1225ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev;
1226ee4a77a3SMauro Carvalho Chehab 	struct v4l2_subdev *subdev;
1227ee4a77a3SMauro Carvalho Chehab 	struct vb2_queue *q;
1228ee4a77a3SMauro Carvalho Chehab 	int irq, ret;
1229ee4a77a3SMauro Carvalho Chehab 
1230*1f62bf4fSYangtao Li 	if (!vou_pdata) {
1231ee4a77a3SMauro Carvalho Chehab 		dev_err(&pdev->dev, "Insufficient VOU platform information.\n");
1232ee4a77a3SMauro Carvalho Chehab 		return -ENODEV;
1233ee4a77a3SMauro Carvalho Chehab 	}
1234ee4a77a3SMauro Carvalho Chehab 
1235*1f62bf4fSYangtao Li 	irq = platform_get_irq(pdev, 0);
1236*1f62bf4fSYangtao Li 	if (irq < 0)
1237*1f62bf4fSYangtao Li 		return irq;
1238*1f62bf4fSYangtao Li 
1239ee4a77a3SMauro Carvalho Chehab 	vou_dev = devm_kzalloc(&pdev->dev, sizeof(*vou_dev), GFP_KERNEL);
1240ee4a77a3SMauro Carvalho Chehab 	if (!vou_dev)
1241ee4a77a3SMauro Carvalho Chehab 		return -ENOMEM;
1242ee4a77a3SMauro Carvalho Chehab 
1243ee4a77a3SMauro Carvalho Chehab 	INIT_LIST_HEAD(&vou_dev->buf_list);
1244ee4a77a3SMauro Carvalho Chehab 	spin_lock_init(&vou_dev->lock);
1245ee4a77a3SMauro Carvalho Chehab 	mutex_init(&vou_dev->fop_lock);
1246ee4a77a3SMauro Carvalho Chehab 	vou_dev->pdata = vou_pdata;
1247ee4a77a3SMauro Carvalho Chehab 	vou_dev->status = SH_VOU_INITIALISING;
1248ee4a77a3SMauro Carvalho Chehab 	vou_dev->pix_idx = 1;
1249ee4a77a3SMauro Carvalho Chehab 
1250ee4a77a3SMauro Carvalho Chehab 	rect = &vou_dev->rect;
1251ee4a77a3SMauro Carvalho Chehab 	pix = &vou_dev->pix;
1252ee4a77a3SMauro Carvalho Chehab 
1253ee4a77a3SMauro Carvalho Chehab 	/* Fill in defaults */
1254ee4a77a3SMauro Carvalho Chehab 	vou_dev->std		= V4L2_STD_NTSC_M;
1255ee4a77a3SMauro Carvalho Chehab 	rect->left		= 0;
1256ee4a77a3SMauro Carvalho Chehab 	rect->top		= 0;
1257ee4a77a3SMauro Carvalho Chehab 	rect->width		= VOU_MAX_IMAGE_WIDTH;
1258ee4a77a3SMauro Carvalho Chehab 	rect->height		= 480;
1259ee4a77a3SMauro Carvalho Chehab 	pix->width		= VOU_MAX_IMAGE_WIDTH;
1260ee4a77a3SMauro Carvalho Chehab 	pix->height		= 480;
1261ee4a77a3SMauro Carvalho Chehab 	pix->pixelformat	= V4L2_PIX_FMT_NV16;
1262ee4a77a3SMauro Carvalho Chehab 	pix->field		= V4L2_FIELD_INTERLACED;
1263ee4a77a3SMauro Carvalho Chehab 	pix->bytesperline	= VOU_MAX_IMAGE_WIDTH;
1264ee4a77a3SMauro Carvalho Chehab 	pix->sizeimage		= VOU_MAX_IMAGE_WIDTH * 2 * 480;
1265ee4a77a3SMauro Carvalho Chehab 	pix->colorspace		= V4L2_COLORSPACE_SMPTE170M;
1266ee4a77a3SMauro Carvalho Chehab 
1267*1f62bf4fSYangtao Li 	vou_dev->base = devm_platform_ioremap_resource(pdev, 0);
1268ee4a77a3SMauro Carvalho Chehab 	if (IS_ERR(vou_dev->base))
1269ee4a77a3SMauro Carvalho Chehab 		return PTR_ERR(vou_dev->base);
1270ee4a77a3SMauro Carvalho Chehab 
1271ee4a77a3SMauro Carvalho Chehab 	ret = devm_request_irq(&pdev->dev, irq, sh_vou_isr, 0, "vou", vou_dev);
1272ee4a77a3SMauro Carvalho Chehab 	if (ret < 0)
1273ee4a77a3SMauro Carvalho Chehab 		return ret;
1274ee4a77a3SMauro Carvalho Chehab 
1275ee4a77a3SMauro Carvalho Chehab 	ret = v4l2_device_register(&pdev->dev, &vou_dev->v4l2_dev);
1276ee4a77a3SMauro Carvalho Chehab 	if (ret < 0) {
1277ee4a77a3SMauro Carvalho Chehab 		dev_err(&pdev->dev, "Error registering v4l2 device\n");
1278ee4a77a3SMauro Carvalho Chehab 		return ret;
1279ee4a77a3SMauro Carvalho Chehab 	}
1280ee4a77a3SMauro Carvalho Chehab 
1281ee4a77a3SMauro Carvalho Chehab 	vdev = &vou_dev->vdev;
1282ee4a77a3SMauro Carvalho Chehab 	*vdev = sh_vou_video_template;
1283ee4a77a3SMauro Carvalho Chehab 	if (vou_pdata->bus_fmt == SH_VOU_BUS_8BIT)
1284ee4a77a3SMauro Carvalho Chehab 		vdev->tvnorms |= V4L2_STD_PAL;
1285ee4a77a3SMauro Carvalho Chehab 	vdev->v4l2_dev = &vou_dev->v4l2_dev;
1286ee4a77a3SMauro Carvalho Chehab 	vdev->release = video_device_release_empty;
1287ee4a77a3SMauro Carvalho Chehab 	vdev->lock = &vou_dev->fop_lock;
1288ee4a77a3SMauro Carvalho Chehab 
1289ee4a77a3SMauro Carvalho Chehab 	video_set_drvdata(vdev, vou_dev);
1290ee4a77a3SMauro Carvalho Chehab 
1291ee4a77a3SMauro Carvalho Chehab 	/* Initialize the vb2 queue */
1292ee4a77a3SMauro Carvalho Chehab 	q = &vou_dev->queue;
1293ee4a77a3SMauro Carvalho Chehab 	q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
1294ee4a77a3SMauro Carvalho Chehab 	q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_WRITE;
1295ee4a77a3SMauro Carvalho Chehab 	q->drv_priv = vou_dev;
1296ee4a77a3SMauro Carvalho Chehab 	q->buf_struct_size = sizeof(struct sh_vou_buffer);
1297ee4a77a3SMauro Carvalho Chehab 	q->ops = &sh_vou_qops;
1298ee4a77a3SMauro Carvalho Chehab 	q->mem_ops = &vb2_dma_contig_memops;
1299ee4a77a3SMauro Carvalho Chehab 	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
1300ee4a77a3SMauro Carvalho Chehab 	q->min_buffers_needed = 2;
1301ee4a77a3SMauro Carvalho Chehab 	q->lock = &vou_dev->fop_lock;
1302ee4a77a3SMauro Carvalho Chehab 	q->dev = &pdev->dev;
1303ee4a77a3SMauro Carvalho Chehab 	ret = vb2_queue_init(q);
1304ee4a77a3SMauro Carvalho Chehab 	if (ret)
1305ee4a77a3SMauro Carvalho Chehab 		goto ei2cgadap;
1306ee4a77a3SMauro Carvalho Chehab 
1307ee4a77a3SMauro Carvalho Chehab 	vdev->queue = q;
1308ee4a77a3SMauro Carvalho Chehab 	INIT_LIST_HEAD(&vou_dev->buf_list);
1309ee4a77a3SMauro Carvalho Chehab 
1310ee4a77a3SMauro Carvalho Chehab 	pm_runtime_enable(&pdev->dev);
1311ee4a77a3SMauro Carvalho Chehab 	pm_runtime_resume(&pdev->dev);
1312ee4a77a3SMauro Carvalho Chehab 
1313ee4a77a3SMauro Carvalho Chehab 	i2c_adap = i2c_get_adapter(vou_pdata->i2c_adap);
1314ee4a77a3SMauro Carvalho Chehab 	if (!i2c_adap) {
1315ee4a77a3SMauro Carvalho Chehab 		ret = -ENODEV;
1316ee4a77a3SMauro Carvalho Chehab 		goto ei2cgadap;
1317ee4a77a3SMauro Carvalho Chehab 	}
1318ee4a77a3SMauro Carvalho Chehab 
1319ee4a77a3SMauro Carvalho Chehab 	ret = sh_vou_hw_init(vou_dev);
1320ee4a77a3SMauro Carvalho Chehab 	if (ret < 0)
1321ee4a77a3SMauro Carvalho Chehab 		goto ereset;
1322ee4a77a3SMauro Carvalho Chehab 
1323ee4a77a3SMauro Carvalho Chehab 	subdev = v4l2_i2c_new_subdev_board(&vou_dev->v4l2_dev, i2c_adap,
1324ee4a77a3SMauro Carvalho Chehab 			vou_pdata->board_info, NULL);
1325ee4a77a3SMauro Carvalho Chehab 	if (!subdev) {
1326ee4a77a3SMauro Carvalho Chehab 		ret = -ENOMEM;
1327ee4a77a3SMauro Carvalho Chehab 		goto ei2cnd;
1328ee4a77a3SMauro Carvalho Chehab 	}
1329ee4a77a3SMauro Carvalho Chehab 
1330ee4a77a3SMauro Carvalho Chehab 	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
1331ee4a77a3SMauro Carvalho Chehab 	if (ret < 0)
1332ee4a77a3SMauro Carvalho Chehab 		goto evregdev;
1333ee4a77a3SMauro Carvalho Chehab 
1334ee4a77a3SMauro Carvalho Chehab 	return 0;
1335ee4a77a3SMauro Carvalho Chehab 
1336ee4a77a3SMauro Carvalho Chehab evregdev:
1337ee4a77a3SMauro Carvalho Chehab ei2cnd:
1338ee4a77a3SMauro Carvalho Chehab ereset:
1339ee4a77a3SMauro Carvalho Chehab 	i2c_put_adapter(i2c_adap);
1340ee4a77a3SMauro Carvalho Chehab ei2cgadap:
1341ee4a77a3SMauro Carvalho Chehab 	pm_runtime_disable(&pdev->dev);
1342ee4a77a3SMauro Carvalho Chehab 	v4l2_device_unregister(&vou_dev->v4l2_dev);
1343ee4a77a3SMauro Carvalho Chehab 	return ret;
1344ee4a77a3SMauro Carvalho Chehab }
1345ee4a77a3SMauro Carvalho Chehab 
sh_vou_remove(struct platform_device * pdev)134623f4acfaSUwe Kleine-König static void sh_vou_remove(struct platform_device *pdev)
1347ee4a77a3SMauro Carvalho Chehab {
1348ee4a77a3SMauro Carvalho Chehab 	struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev);
1349ee4a77a3SMauro Carvalho Chehab 	struct sh_vou_device *vou_dev = container_of(v4l2_dev,
1350ee4a77a3SMauro Carvalho Chehab 						struct sh_vou_device, v4l2_dev);
1351ee4a77a3SMauro Carvalho Chehab 	struct v4l2_subdev *sd = list_entry(v4l2_dev->subdevs.next,
1352ee4a77a3SMauro Carvalho Chehab 					    struct v4l2_subdev, list);
1353ee4a77a3SMauro Carvalho Chehab 	struct i2c_client *client = v4l2_get_subdevdata(sd);
1354ee4a77a3SMauro Carvalho Chehab 
1355ee4a77a3SMauro Carvalho Chehab 	pm_runtime_disable(&pdev->dev);
1356ee4a77a3SMauro Carvalho Chehab 	video_unregister_device(&vou_dev->vdev);
1357ee4a77a3SMauro Carvalho Chehab 	i2c_put_adapter(client->adapter);
1358ee4a77a3SMauro Carvalho Chehab 	v4l2_device_unregister(&vou_dev->v4l2_dev);
1359ee4a77a3SMauro Carvalho Chehab }
1360ee4a77a3SMauro Carvalho Chehab 
1361ee4a77a3SMauro Carvalho Chehab static struct platform_driver sh_vou = {
136223f4acfaSUwe Kleine-König 	.remove_new = sh_vou_remove,
1363ee4a77a3SMauro Carvalho Chehab 	.driver  = {
1364ee4a77a3SMauro Carvalho Chehab 		.name	= "sh-vou",
1365ee4a77a3SMauro Carvalho Chehab 	},
1366ee4a77a3SMauro Carvalho Chehab };
1367ee4a77a3SMauro Carvalho Chehab 
1368ee4a77a3SMauro Carvalho Chehab module_platform_driver_probe(sh_vou, sh_vou_probe);
1369ee4a77a3SMauro Carvalho Chehab 
1370ee4a77a3SMauro Carvalho Chehab MODULE_DESCRIPTION("SuperH VOU driver");
1371ee4a77a3SMauro Carvalho Chehab MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>");
1372ee4a77a3SMauro Carvalho Chehab MODULE_LICENSE("GPL v2");
1373ee4a77a3SMauro Carvalho Chehab MODULE_VERSION("0.1.0");
1374ee4a77a3SMauro Carvalho Chehab MODULE_ALIAS("platform:sh-vou");
1375