aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/media/video/sh_vou.c
blob: d394187eb701d04aa81ebe4a4aff4966e5e63257 (plain) (tree)
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344



















                                                                       
                       






































                                                          
                                   




















































































































































































































































































































































































































































































                                                                                   
                                                                   

                       



                                     


                                                                        
                                                                        































                                                                              
                                           






















                                                                           

                                                                      




                                     
                                     


                                     
                                     





































                                                                            
                                           



























                                                                       
                                    



                                                        
                                                






















                                                                          




                                           

                                                                     
                                                                     



















                                                                                
                                                      
                                                  

















































































































































































































                                                                                     
                                                


                                                        
                                    







                                                                       




                                           
                                                                      
                                                                      



                                                               

                                                          























                                                                             
                                                      
                                                  


























































































































































































































































































































































                                                                                           
                                      
                                                      
                                      


                                                          
                                                                









































































































































                                                                                
/*
 * SuperH Video Output Unit (VOU) driver
 *
 * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/videodev2.h>

#include <media/sh_vou.h>
#include <media/v4l2-common.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mediabus.h>
#include <media/videobuf-dma-contig.h>

/* Mirror addresses are not available for all registers */
#define VOUER	0
#define VOUCR	4
#define VOUSTR	8
#define VOUVCR	0xc
#define VOUISR	0x10
#define VOUBCR	0x14
#define VOUDPR	0x18
#define VOUDSR	0x1c
#define VOUVPR	0x20
#define VOUIR	0x24
#define VOUSRR	0x28
#define VOUMSR	0x2c
#define VOUHIR	0x30
#define VOUDFR	0x34
#define VOUAD1R	0x38
#define VOUAD2R	0x3c
#define VOUAIR	0x40
#define VOUSWR	0x44
#define VOURCR	0x48
#define VOURPR	0x50

enum sh_vou_status {
	SH_VOU_IDLE,
	SH_VOU_INITIALISING,
	SH_VOU_RUNNING,
};

#define VOU_MAX_IMAGE_WIDTH	720
#define VOU_MAX_IMAGE_HEIGHT	576

struct sh_vou_device {
	struct v4l2_device v4l2_dev;
	struct video_device *vdev;
	atomic_t use_count;
	struct sh_vou_pdata *pdata;
	spinlock_t lock;
	void __iomem *base;
	/* State information */
	struct v4l2_pix_format pix;
	struct v4l2_rect rect;
	struct list_head queue;
	v4l2_std_id std;
	int pix_idx;
	struct videobuf_buffer *active;
	enum sh_vou_status status;
};

struct sh_vou_file {
	struct videobuf_queue vbq;
};

/* Register access routines for sides A, B and mirror addresses */
static void sh_vou_reg_a_write(struct sh_vou_device *vou_dev, unsigned int reg,
			       u32 value)
{
	__raw_writel(value, vou_dev->base + reg);
}

static void sh_vou_reg_ab_write(struct sh_vou_device *vou_dev, unsigned int reg,
				u32 value)
{
	__raw_writel(value, vou_dev->base + reg);
	__raw_writel(value, vou_dev->base + reg + 0x1000);
}

static void sh_vou_reg_m_write(struct sh_vou_device *vou_dev, unsigned int reg,
			       u32 value)
{
	__raw_writel(value, vou_dev->base + reg + 0x2000);
}

static u32 sh_vou_reg_a_read(struct sh_vou_device *vou_dev, unsigned int reg)
{
	return __raw_readl(vou_dev->base + reg);
}

static void sh_vou_reg_a_set(struct sh_vou_device *vou_dev, unsigned int reg,
			     u32 value, u32 mask)
{
	u32 old = __raw_readl(vou_dev->base + reg);

	value = (value & mask) | (old & ~mask);
	__raw_writel(value, vou_dev->base + reg);
}

static void sh_vou_reg_b_set(struct sh_vou_device *vou_dev, unsigned int reg,
			     u32 value, u32 mask)
{
	sh_vou_reg_a_set(vou_dev, reg + 0x1000, value, mask);
}

static void sh_vou_reg_ab_set(struct sh_vou_device *vou_dev, unsigned int reg,
			      u32 value, u32 mask)
{
	sh_vou_reg_a_set(vou_dev, reg, value, mask);
	sh_vou_reg_b_set(vou_dev, reg, value, mask);
}

struct sh_vou_fmt {
	u32		pfmt;
	char		*desc;
	unsigned char	bpp;
	unsigned char	rgb;
	unsigned char	yf;
	unsigned char	pkf;
};

/* Further pixel formats can be added */
static struct sh_vou_fmt vou_fmt[] = {
	{
		.pfmt	= V4L2_PIX_FMT_NV12,
		.bpp	= 12,
		.desc	= "YVU420 planar",
		.yf	= 0,
		.rgb	= 0,
	},
	{
		.pfmt	= V4L2_PIX_FMT_NV16,
		.bpp	= 16,
		.desc	= "YVYU planar",
		.yf	= 1,
		.rgb	= 0,
	},
	{
		.pfmt	= V4L2_PIX_FMT_RGB24,
		.bpp	= 24,
		.desc	= "RGB24",
		.pkf	= 2,
		.rgb	= 1,
	},
	{
		.pfmt	= V4L2_PIX_FMT_RGB565,
		.bpp	= 16,
		.desc	= "RGB565",
		.pkf	= 3,
		.rgb	= 1,
	},
	{
		.pfmt	= V4L2_PIX_FMT_RGB565X,
		.bpp	= 16,
		.desc	= "RGB565 byteswapped",
		.pkf	= 3,
		.rgb	= 1,
	},
};

static void sh_vou_schedule_next(struct sh_vou_device *vou_dev,
				 struct videobuf_buffer *vb)
{
	dma_addr_t addr1, addr2;

	addr1 = videobuf_to_dma_contig(vb);
	switch (vou_dev->pix.pixelformat) {
	case V4L2_PIX_FMT_NV12:
	case V4L2_PIX_FMT_NV16:
		addr2 = addr1 + vou_dev->pix.width * vou_dev->pix.height;
		break;
	default:
		addr2 = 0;
	}

	sh_vou_reg_m_write(vou_dev, VOUAD1R, addr1);
	sh_vou_reg_m_write(vou_dev, VOUAD2R, addr2);
}

static void sh_vou_stream_start(struct sh_vou_device *vou_dev,
				struct videobuf_buffer *vb)
{
	unsigned int row_coeff;
#ifdef __LITTLE_ENDIAN
	u32 dataswap = 7;
#else
	u32 dataswap = 0;
#endif

	switch (vou_dev->pix.pixelformat) {
	case V4L2_PIX_FMT_NV12:
	case V4L2_PIX_FMT_NV16:
		row_coeff = 1;
		break;
	case V4L2_PIX_FMT_RGB565:
		dataswap ^= 1;
	case V4L2_PIX_FMT_RGB565X:
		row_coeff = 2;
		break;
	case V4L2_PIX_FMT_RGB24:
		row_coeff = 3;
		break;
	}

	sh_vou_reg_a_write(vou_dev, VOUSWR, dataswap);
	sh_vou_reg_ab_write(vou_dev, VOUAIR, vou_dev->pix.width * row_coeff);
	sh_vou_schedule_next(vou_dev, vb);
}

static void free_buffer(struct videobuf_queue *vq, struct videobuf_buffer *vb)
{
	BUG_ON(in_interrupt());

	/* Wait until this buffer is no longer in STATE_QUEUED or STATE_ACTIVE */
	videobuf_waiton(vb, 0, 0);
	videobuf_dma_contig_free(vq, vb);
	vb->state = VIDEOBUF_NEEDS_INIT;
}

/* Locking: caller holds vq->vb_lock mutex */
static int sh_vou_buf_setup(struct videobuf_queue *vq, unsigned int *count,
			    unsigned int *size)
{
	struct video_device *vdev = vq->priv_data;
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);

	*size = vou_fmt[vou_dev->pix_idx].bpp * vou_dev->pix.width *
		vou_dev->pix.height / 8;

	if (*count < 2)
		*count = 2;

	/* Taking into account maximum frame size, *count will stay >= 2 */
	if (PAGE_ALIGN(*size) * *count > 4 * 1024 * 1024)
		*count = 4 * 1024 * 1024 / PAGE_ALIGN(*size);

	dev_dbg(vq->dev, "%s(): count=%d, size=%d\n", __func__, *count, *size);

	return 0;
}

/* Locking: caller holds vq->vb_lock mutex */
static int sh_vou_buf_prepare(struct videobuf_queue *vq,
			      struct videobuf_buffer *vb,
			      enum v4l2_field field)
{
	struct video_device *vdev = vq->priv_data;
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);
	struct v4l2_pix_format *pix = &vou_dev->pix;
	int bytes_per_line = vou_fmt[vou_dev->pix_idx].bpp * pix->width / 8;
	int ret;

	dev_dbg(vq->dev, "%s()\n", __func__);

	if (vb->width	!= pix->width ||
	    vb->height	!= pix->height ||
	    vb->field	!= pix->field) {
		vb->width	= pix->width;
		vb->height	= pix->height;
		vb->field	= field;
		if (vb->state != VIDEOBUF_NEEDS_INIT)
			free_buffer(vq, vb);
	}

	vb->size = vb->height * bytes_per_line;
	if (vb->baddr && vb->bsize < vb->size) {
		/* User buffer too small */
		dev_warn(vq->dev, "User buffer too small: [%u] @ %lx\n",
			 vb->bsize, vb->baddr);
		return -EINVAL;
	}

	if (vb->state == VIDEOBUF_NEEDS_INIT) {
		ret = videobuf_iolock(vq, vb, NULL);
		if (ret < 0) {
			dev_warn(vq->dev, "IOLOCK buf-type %d: %d\n",
				 vb->memory, ret);
			return ret;
		}
		vb->state = VIDEOBUF_PREPARED;
	}

	dev_dbg(vq->dev,
		"%s(): fmt #%d, %u bytes per line, phys 0x%x, type %d, state %d\n",
		__func__, vou_dev->pix_idx, bytes_per_line,
		videobuf_to_dma_contig(vb), vb->memory, vb->state);

	return 0;
}

/* Locking: caller holds vq->vb_lock mutex and vq->irqlock spinlock */
static void sh_vou_buf_queue(struct videobuf_queue *vq,
			     struct videobuf_buffer *vb)
{
	struct video_device *vdev = vq->priv_data;
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);

	dev_dbg(vq->dev, "%s()\n", __func__);

	vb->state = VIDEOBUF_QUEUED;
	list_add_tail(&vb->queue, &vou_dev->queue);

	if (vou_dev->status == SH_VOU_RUNNING) {
		return;
	} else if (!vou_dev->active) {
		vou_dev->active = vb;
		/* Start from side A: we use mirror addresses, so, set B */
		sh_vou_reg_a_write(vou_dev, VOURPR, 1);
		dev_dbg(vq->dev, "%s: first buffer status 0x%x\n", __func__,
			sh_vou_reg_a_read(vou_dev, VOUSTR));
		sh_vou_schedule_next(vou_dev, vb);
		/* Only activate VOU after the second buffer */
	} else if (vou_dev->active->queue.next == &vb->queue) {
		/* Second buffer - initialise register side B */
		sh_vou_reg_a_write(vou_dev, VOURPR, 0);
		sh_vou_stream_start(vou_dev, vb);

		/* Register side switching with frame VSYNC */
		sh_vou_reg_a_write(vou_dev, VOURCR, 5);
		dev_dbg(vq->dev, "%s: second buffer status 0x%x\n", __func__,
			sh_vou_reg_a_read(vou_dev, VOUSTR));

		/* Enable End-of-Frame (VSYNC) interrupts */
		sh_vou_reg_a_write(vou_dev, VOUIR, 0x10004);
		/* Two buffers on the queue - activate the hardware */

		vou_dev->status = SH_VOU_RUNNING;
		sh_vou_reg_a_write(vou_dev, VOUER, 0x107);
	}
}

static void sh_vou_buf_release(struct videobuf_queue *vq,
			       struct videobuf_buffer *vb)
{
	struct video_device *vdev = vq->priv_data;
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);
	unsigned long flags;

	dev_dbg(vq->dev, "%s()\n", __func__);

	spin_lock_irqsave(&vou_dev->lock, flags);

	if (vou_dev->active == vb) {
		/* disable output */
		sh_vou_reg_a_set(vou_dev, VOUER, 0, 1);
		/* ...but the current frame will complete */
		sh_vou_reg_a_set(vou_dev, VOUIR, 0, 0x30000);
		vou_dev->active = NULL;
	}

	if ((vb->state == VIDEOBUF_ACTIVE || vb->state == VIDEOBUF_QUEUED)) {
		vb->state = VIDEOBUF_ERROR;
		list_del(&vb->queue);
	}

	spin_unlock_irqrestore(&vou_dev->lock, flags);

	free_buffer(vq, vb);
}

static struct videobuf_queue_ops sh_vou_video_qops = {
	.buf_setup	= sh_vou_buf_setup,
	.buf_prepare	= sh_vou_buf_prepare,
	.buf_queue	= sh_vou_buf_queue,
	.buf_release	= sh_vou_buf_release,
};

/* Video IOCTLs */
static int sh_vou_querycap(struct file *file, void  *priv,
			   struct v4l2_capability *cap)
{
	struct sh_vou_file *vou_file = priv;

	dev_dbg(vou_file->vbq.dev, "%s()\n", __func__);

	strlcpy(cap->card, "SuperH VOU", sizeof(cap->card));
	cap->version = KERNEL_VERSION(0, 1, 0);
	cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
	return 0;
}

/* Enumerate formats, that the device can accept from the user */
static int sh_vou_enum_fmt_vid_out(struct file *file, void  *priv,
				   struct v4l2_fmtdesc *fmt)
{
	struct sh_vou_file *vou_file = priv;

	if (fmt->index >= ARRAY_SIZE(vou_fmt))
		return -EINVAL;

	dev_dbg(vou_file->vbq.dev, "%s()\n", __func__);

	fmt->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	strlcpy(fmt->description, vou_fmt[fmt->index].desc,
		sizeof(fmt->description));
	fmt->pixelformat = vou_fmt[fmt->index].pfmt;

	return 0;
}

static int sh_vou_g_fmt_vid_out(struct file *file, void *priv,
				struct v4l2_format *fmt)
{
	struct video_device *vdev = video_devdata(file);
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);

	dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__);

	fmt->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	fmt->fmt.pix = vou_dev->pix;

	return 0;
}

static const unsigned char vou_scale_h_num[] = {1, 9, 2, 9, 4};
static const unsigned char vou_scale_h_den[] = {1, 8, 1, 4, 1};
static const unsigned char vou_scale_h_fld[] = {0, 2, 1, 3};
static const unsigned char vou_scale_v_num[] = {1, 2, 4};
static const unsigned char vou_scale_v_den[] = {1, 1, 1};
static const unsigned char vou_scale_v_fld[] = {0, 1};

static void sh_vou_configure_geometry(struct sh_vou_device *vou_dev,
				      int pix_idx, int w_idx, int h_idx)
{
	struct sh_vou_fmt *fmt = vou_fmt + pix_idx;
	unsigned int black_left, black_top, width_max, height_max,
		frame_in_height, frame_out_height, frame_out_top;
	struct v4l2_rect *rect = &vou_dev->rect;
	struct v4l2_pix_format *pix = &vou_dev->pix;
	u32 vouvcr = 0, dsr_h, dsr_v;

	if (vou_dev->std & V4L2_STD_525_60) {
		width_max = 858;
		height_max = 262;
	} else {
		width_max = 864;
		height_max = 312;
	}

	frame_in_height = pix->height / 2;
	frame_out_height = rect->height / 2;
	frame_out_top = rect->top / 2;

	/*
	 * Cropping scheme: max useful image is 720x480, and the total video
	 * area is 858x525 (NTSC) or 864x625 (PAL). AK8813 / 8814 starts
	 * sampling data beginning with fixed 276th (NTSC) / 288th (PAL) clock,
	 * of which the first 33 / 25 clocks HSYNC must be held active. This
	 * has to be configured in CR[HW]. 1 pixel equals 2 clock periods.
	 * This gives CR[HW] = 16 / 12, VPR[HVP] = 138 / 144, which gives
	 * exactly 858 - 138 = 864 - 144 = 720! We call the out-of-display area,
	 * beyond DSR, specified on the left and top by the VPR register "black
	 * pixels" and out-of-image area (DPR) "background pixels." We fix VPR
	 * at 138 / 144 : 20, because that's the HSYNC timing, that our first
	 * client requires, and that's exactly what leaves us 720 pixels for the
	 * image; we leave VPR[VVP] at default 20 for now, because the client
	 * doesn't seem to have any special requirements for it. Otherwise we
	 * could also set it to max - 240 = 22 / 72. Thus VPR depends only on
	 * the selected standard, and DPR and DSR are selected according to
	 * cropping. Q: how does the client detect the first valid line? Does
	 * HSYNC stay inactive during invalid (black) lines?
	 */
	black_left = width_max - VOU_MAX_IMAGE_WIDTH;
	black_top = 20;

	dsr_h = rect->width + rect->left;
	dsr_v = frame_out_height + frame_out_top;

	dev_dbg(vou_dev->v4l2_dev.dev,
		"image %ux%u, black %u:%u, offset %u:%u, display %ux%u\n",
		pix->width, frame_in_height, black_left, black_top,
		rect->left, frame_out_top, dsr_h, dsr_v);

	/* VOUISR height - half of a frame height in frame mode */
	sh_vou_reg_ab_write(vou_dev, VOUISR, (pix->width << 16) | frame_in_height);
	sh_vou_reg_ab_write(vou_dev, VOUVPR, (black_left << 16) | black_top);
	sh_vou_reg_ab_write(vou_dev, VOUDPR, (rect->left << 16) | frame_out_top);
	sh_vou_reg_ab_write(vou_dev, VOUDSR, (dsr_h << 16) | dsr_v);

	/*
	 * if necessary, we could set VOUHIR to
	 * max(black_left + dsr_h, width_max) here
	 */

	if (w_idx)
		vouvcr |= (1 << 15) | (vou_scale_h_fld[w_idx - 1] << 4);
	if (h_idx)
		vouvcr |= (1 << 14) | vou_scale_v_fld[h_idx - 1];

	dev_dbg(vou_dev->v4l2_dev.dev, "%s: scaling 0x%x\n", fmt->desc, vouvcr);

	/* To produce a colour bar for testing set bit 23 of VOUVCR */
	sh_vou_reg_ab_write(vou_dev, VOUVCR, vouvcr);
	sh_vou_reg_ab_write(vou_dev, VOUDFR,
			    fmt->pkf | (fmt->yf << 8) | (fmt->rgb << 16));
}

struct sh_vou_geometry {
	struct v4l2_rect output;
	unsigned int in_width;
	unsigned int in_height;
	int scale_idx_h;
	int scale_idx_v;
};

/*
 * Find input geometry, that we can use to produce output, closest to the
 * requested rectangle, using VOU scaling
 */
static void vou_adjust_input(struct sh_vou_geometry *geo, v4l2_std_id std)
{
	/* The compiler cannot know, that best and idx will indeed be set */
	unsigned int best_err = UINT_MAX, best = 0, img_height_max;
	int i, idx = 0;

	if (std & V4L2_STD_525_60)
		img_height_max = 480;
	else
		img_height_max = 576;

	/* Image width must be a multiple of 4 */
	v4l_bound_align_image(&geo->in_width, 0, VOU_MAX_IMAGE_WIDTH, 2,
			      &geo->in_height, 0, img_height_max, 1, 0);

	/* Select scales to come as close as possible to the output image */
	for (i = ARRAY_SIZE(vou_scale_h_num) - 1; i >= 0; i--) {
		unsigned int err;
		unsigned int found = geo->output.width * vou_scale_h_den[i] /
			vou_scale_h_num[i];

		if (found > VOU_MAX_IMAGE_WIDTH)
			/* scales increase */
			break;

		err = abs(found - geo->in_width);
		if (err < best_err) {
			best_err = err;
			idx = i;
			best = found;
		}
		if (!err)
			break;
	}

	geo->in_width = best;
	geo->scale_idx_h = idx;

	best_err = UINT_MAX;

	/* This loop can be replaced with one division */
	for (i = ARRAY_SIZE(vou_scale_v_num) - 1; i >= 0; i--) {
		unsigned int err;
		unsigned int found = geo->output.height * vou_scale_v_den[i] /
			vou_scale_v_num[i];

		if (found > img_height_max)
			/* scales increase */
			break;

		err = abs(found - geo->in_height);
		if (err < best_err) {
			best_err = err;
			idx = i;
			best = found;
		}
		if (!err)
			break;
	}

	geo->in_height = best;
	geo->scale_idx_v = idx;
}

/*
 * Find output geometry, that we can produce, using VOU scaling, closest to
 * the requested rectangle
 */
static void vou_adjust_output(struct sh_vou_geometry *geo, v4l2_std_id std)
{
	unsigned int best_err = UINT_MAX, best, width_max, height_max,
		img_height_max;
	int i, idx;

	if (std & V4L2_STD_525_60) {
		width_max = 858;
		height_max = 262 * 2;
		img_height_max = 480;
	} else {
		width_max = 864;
		height_max = 312 * 2;
		img_height_max = 576;
	}

	/* Select scales to come as close as possible to the output image */
	for (i = 0; i < ARRAY_SIZE(vou_scale_h_num); i++) {
		unsigned int err;
		unsigned int found = geo->in_width * vou_scale_h_num[i] /
			vou_scale_h_den[i];

		if (found > VOU_MAX_IMAGE_WIDTH)
			/* scales increase */
			break;

		err = abs(found - geo->output.width);
		if (err < best_err) {
			best_err = err;
			idx = i;
			best = found;
		}
		if (!err)
			break;
	}

	geo->output.width = best;
	geo->scale_idx_h = idx;
	if (geo->output.left + best > width_max)
		geo->output.left = width_max - best;

	pr_debug("%s(): W %u * %u/%u = %u\n", __func__, geo->in_width,
		 vou_scale_h_num[idx], vou_scale_h_den[idx], best);

	best_err = UINT_MAX;

	/* This loop can be replaced with one division */
	for (i = 0; i < ARRAY_SIZE(vou_scale_v_num); i++) {
		unsigned int err;
		unsigned int found = geo->in_height * vou_scale_v_num[i] /
			vou_scale_v_den[i];

		if (found > img_height_max)
			/* scales increase */
			break;

		err = abs(found - geo->output.height);
		if (err < best_err) {
			best_err = err;
			idx = i;
			best = found;
		}
		if (!err)
			break;
	}

	geo->output.height = best;
	geo->scale_idx_v = idx;
	if (geo->output.top + best > height_max)
		geo->output.top = height_max - best;

	pr_debug("%s(): H %u * %u/%u = %u\n", __func__, geo->in_height,
		 vou_scale_v_num[idx], vou_scale_v_den[idx], best);
}

static int sh_vou_s_fmt_vid_out(struct file *file, void *priv,
				struct v4l2_format *fmt)
{
	struct video_device *vdev = video_devdata(file);
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);
	struct v4l2_pix_format *pix = &fmt->fmt.pix;
	unsigned int img_height_max;
	int pix_idx;
	struct sh_vou_geometry geo;
	struct v4l2_mbus_framefmt mbfmt = {
		/* Revisit: is this the correct code? */
		.code = V4L2_MBUS_FMT_YUYV8_2X8,
		.field = V4L2_FIELD_INTERLACED,
		.colorspace = V4L2_COLORSPACE_SMPTE170M,
	};
	int ret;

	dev_dbg(vou_dev->v4l2_dev.dev, "%s(): %ux%u -> %ux%u\n", __func__,
		vou_dev->rect.width, vou_dev->rect.height,
		pix->width, pix->height);

	if (pix->field == V4L2_FIELD_ANY)
		pix->field = V4L2_FIELD_NONE;

	if (fmt->type != V4L2_BUF_TYPE_VIDEO_OUTPUT ||
	    pix->field != V4L2_FIELD_NONE)
		return -EINVAL;

	for (pix_idx = 0; pix_idx < ARRAY_SIZE(vou_fmt); pix_idx++)
		if (vou_fmt[pix_idx].pfmt == pix->pixelformat)
			break;

	if (pix_idx == ARRAY_SIZE(vou_fmt))
		return -EINVAL;

	if (vou_dev->std & V4L2_STD_525_60)
		img_height_max = 480;
	else
		img_height_max = 576;

	/* Image width must be a multiple of 4 */
	v4l_bound_align_image(&pix->width, 0, VOU_MAX_IMAGE_WIDTH, 2,
			      &pix->height, 0, img_height_max, 1, 0);

	geo.in_width = pix->width;
	geo.in_height = pix->height;
	geo.output = vou_dev->rect;

	vou_adjust_output(&geo, vou_dev->std);

	mbfmt.width = geo.output.width;
	mbfmt.height = geo.output.height;
	ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, video,
					 s_mbus_fmt, &mbfmt);
	/* Must be implemented, so, don't check for -ENOIOCTLCMD */
	if (ret < 0)
		return ret;

	dev_dbg(vou_dev->v4l2_dev.dev, "%s(): %ux%u -> %ux%u\n", __func__,
		geo.output.width, geo.output.height, mbfmt.width, mbfmt.height);

	/* Sanity checks */
	if ((unsigned)mbfmt.width > VOU_MAX_IMAGE_WIDTH ||
	    (unsigned)mbfmt.height > img_height_max ||
	    mbfmt.code != V4L2_MBUS_FMT_YUYV8_2X8)
		return -EIO;

	if (mbfmt.width != geo.output.width ||
	    mbfmt.height != geo.output.height) {
		geo.output.width = mbfmt.width;
		geo.output.height = mbfmt.height;

		vou_adjust_input(&geo, vou_dev->std);
	}

	/* We tried to preserve output rectangle, but it could have changed */
	vou_dev->rect = geo.output;
	pix->width = geo.in_width;
	pix->height = geo.in_height;

	dev_dbg(vou_dev->v4l2_dev.dev, "%s(): %ux%u\n", __func__,
		pix->width, pix->height);

	vou_dev->pix_idx = pix_idx;

	vou_dev->pix = *pix;

	sh_vou_configure_geometry(vou_dev, pix_idx,
				  geo.scale_idx_h, geo.scale_idx_v);

	return 0;
}

static int sh_vou_try_fmt_vid_out(struct file *file, void *priv,
				  struct v4l2_format *fmt)
{
	struct sh_vou_file *vou_file = priv;
	struct v4l2_pix_format *pix = &fmt->fmt.pix;
	int i;

	dev_dbg(vou_file->vbq.dev, "%s()\n", __func__);

	fmt->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	pix->field = V4L2_FIELD_NONE;

	v4l_bound_align_image(&pix->width, 0, VOU_MAX_IMAGE_WIDTH, 1,
			      &pix->height, 0, VOU_MAX_IMAGE_HEIGHT, 1, 0);

	for (i = 0; ARRAY_SIZE(vou_fmt); i++)
		if (vou_fmt[i].pfmt == pix->pixelformat)
			return 0;

	pix->pixelformat = vou_fmt[0].pfmt;

	return 0;
}

static int sh_vou_reqbufs(struct file *file, void *priv,
			  struct v4l2_requestbuffers *req)
{
	struct sh_vou_file *vou_file = priv;

	dev_dbg(vou_file->vbq.dev, "%s()\n", __func__);

	if (req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
		return -EINVAL;

	return videobuf_reqbufs(&vou_file->vbq, req);
}

static int sh_vou_querybuf(struct file *file, void *priv,
			   struct v4l2_buffer *b)
{
	struct sh_vou_file *vou_file = priv;

	dev_dbg(vou_file->vbq.dev, "%s()\n", __func__);

	return videobuf_querybuf(&vou_file->vbq, b);
}

static int sh_vou_qbuf(struct file *file, void *priv, struct v4l2_buffer *b)
{
	struct sh_vou_file *vou_file = priv;

	dev_dbg(vou_file->vbq.dev, "%s()\n", __func__);

	return videobuf_qbuf(&vou_file->vbq, b);
}

static int sh_vou_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b)
{
	struct sh_vou_file *vou_file = priv;

	dev_dbg(vou_file->vbq.dev, "%s()\n", __func__);

	return videobuf_dqbuf(&vou_file->vbq, b, file->f_flags & O_NONBLOCK);
}

static int sh_vou_streamon(struct file *file, void *priv,
			   enum v4l2_buf_type buftype)
{
	struct video_device *vdev = video_devdata(file);
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);
	struct sh_vou_file *vou_file = priv;
	int ret;

	dev_dbg(vou_file->vbq.dev, "%s()\n", __func__);

	ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0,
					 video, s_stream, 1);
	if (ret < 0 && ret != -ENOIOCTLCMD)
		return ret;

	/* This calls our .buf_queue() (== sh_vou_buf_queue) */
	return videobuf_streamon(&vou_file->vbq);
}

static int sh_vou_streamoff(struct file *file, void *priv,
			    enum v4l2_buf_type buftype)
{
	struct video_device *vdev = video_devdata(file);
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);
	struct sh_vou_file *vou_file = priv;

	dev_dbg(vou_file->vbq.dev, "%s()\n", __func__);

	/*
	 * This calls buf_release from host driver's videobuf_queue_ops for all
	 * remaining buffers. When the last buffer is freed, stop streaming
	 */
	videobuf_streamoff(&vou_file->vbq);
	v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, video, s_stream, 0);

	return 0;
}

static u32 sh_vou_ntsc_mode(enum sh_vou_bus_fmt bus_fmt)
{
	switch (bus_fmt) {
	default:
		pr_warning("%s(): Invalid bus-format code %d, using default 8-bit\n",
			   __func__, bus_fmt);
	case SH_VOU_BUS_8BIT:
		return 1;
	case SH_VOU_BUS_16BIT:
		return 0;
	case SH_VOU_BUS_BT656:
		return 3;
	}
}

static int sh_vou_s_std(struct file *file, void *priv, v4l2_std_id *std_id)
{
	struct video_device *vdev = video_devdata(file);
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);
	int ret;

	dev_dbg(vou_dev->v4l2_dev.dev, "%s(): 0x%llx\n", __func__, *std_id);

	if (*std_id & ~vdev->tvnorms)
		return -EINVAL;

	ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, video,
					 s_std_output, *std_id);
	/* Shall we continue, if the subdev doesn't support .s_std_output()? */
	if (ret < 0 && ret != -ENOIOCTLCMD)
		return ret;

	if (*std_id & V4L2_STD_525_60)
		sh_vou_reg_ab_set(vou_dev, VOUCR,
			sh_vou_ntsc_mode(vou_dev->pdata->bus_fmt) << 29, 7 << 29);
	else
		sh_vou_reg_ab_set(vou_dev, VOUCR, 5 << 29, 7 << 29);

	vou_dev->std = *std_id;

	return 0;
}

static int sh_vou_g_std(struct file *file, void *priv, v4l2_std_id *std)
{
	struct video_device *vdev = video_devdata(file);
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);

	dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__);

	*std = vou_dev->std;

	return 0;
}

static int sh_vou_g_crop(struct file *file, void *fh, struct v4l2_crop *a)
{
	struct video_device *vdev = video_devdata(file);
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);

	dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__);

	a->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	a->c = vou_dev->rect;

	return 0;
}

/* Assume a dull encoder, do all the work ourselves. */
static int sh_vou_s_crop(struct file *file, void *fh, struct v4l2_crop *a)
{
	struct video_device *vdev = video_devdata(file);
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);
	struct v4l2_rect *rect = &a->c;
	struct v4l2_crop sd_crop = {.type = V4L2_BUF_TYPE_VIDEO_OUTPUT};
	struct v4l2_pix_format *pix = &vou_dev->pix;
	struct sh_vou_geometry geo;
	struct v4l2_mbus_framefmt mbfmt = {
		/* Revisit: is this the correct code? */
		.code = V4L2_MBUS_FMT_YUYV8_2X8,
		.field = V4L2_FIELD_INTERLACED,
		.colorspace = V4L2_COLORSPACE_SMPTE170M,
	};
	unsigned int img_height_max;
	int ret;

	dev_dbg(vou_dev->v4l2_dev.dev, "%s(): %ux%u@%u:%u\n", __func__,
		rect->width, rect->height, rect->left, rect->top);

	if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
		return -EINVAL;

	if (vou_dev->std & V4L2_STD_525_60)
		img_height_max = 480;
	else
		img_height_max = 576;

	v4l_bound_align_image(&rect->width, 0, VOU_MAX_IMAGE_WIDTH, 1,
			      &rect->height, 0, img_height_max, 1, 0);

	if (rect->width + rect->left > VOU_MAX_IMAGE_WIDTH)
		rect->left = VOU_MAX_IMAGE_WIDTH - rect->width;

	if (rect->height + rect->top > img_height_max)
		rect->top = img_height_max - rect->height;

	geo.output = *rect;
	geo.in_width = pix->width;
	geo.in_height = pix->height;

	/* Configure the encoder one-to-one, position at 0, ignore errors */
	sd_crop.c.width = geo.output.width;
	sd_crop.c.height = geo.output.height;
	/*
	 * We first issue a S_CROP, so that the subsequent S_FMT delivers the
	 * final encoder configuration.
	 */
	v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, video,
				   s_crop, &sd_crop);
	mbfmt.width = geo.output.width;
	mbfmt.height = geo.output.height;
	ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, video,
					 s_mbus_fmt, &mbfmt);
	/* Must be implemented, so, don't check for -ENOIOCTLCMD */
	if (ret < 0)
		return ret;

	/* Sanity checks */
	if ((unsigned)mbfmt.width > VOU_MAX_IMAGE_WIDTH ||
	    (unsigned)mbfmt.height > img_height_max ||
	    mbfmt.code != V4L2_MBUS_FMT_YUYV8_2X8)
		return -EIO;

	geo.output.width = mbfmt.width;
	geo.output.height = mbfmt.height;

	/*
	 * No down-scaling. According to the API, current call has precedence:
	 * http://v4l2spec.bytesex.org/spec/x1904.htm#AEN1954 paragraph two.
	 */
	vou_adjust_input(&geo, vou_dev->std);

	/* We tried to preserve output rectangle, but it could have changed */
	vou_dev->rect = geo.output;
	pix->width = geo.in_width;
	pix->height = geo.in_height;

	sh_vou_configure_geometry(vou_dev, vou_dev->pix_idx,
				  geo.scale_idx_h, geo.scale_idx_v);

	return 0;
}

/*
 * Total field: NTSC 858 x 2 * 262/263, PAL 864 x 2 * 312/313, default rectangle
 * is the initial register values, height takes the interlaced format into
 * account. The actual image can only go up to 720 x 2 * 240, So, VOUVPR can
 * actually only meaningfully contain values <= 720 and <= 240 respectively, and
 * not <= 864 and <= 312.
 */
static int sh_vou_cropcap(struct file *file, void *priv,
			  struct v4l2_cropcap *a)
{
	struct sh_vou_file *vou_file = priv;

	dev_dbg(vou_file->vbq.dev, "%s()\n", __func__);

	a->type				= V4L2_BUF_TYPE_VIDEO_OUTPUT;
	a->bounds.left			= 0;
	a->bounds.top			= 0;
	a->bounds.width			= VOU_MAX_IMAGE_WIDTH;
	a->bounds.height		= VOU_MAX_IMAGE_HEIGHT;
	/* Default = max, set VOUDPR = 0, which is not hardware default */
	a->defrect.left			= 0;
	a->defrect.top			= 0;
	a->defrect.width		= VOU_MAX_IMAGE_WIDTH;
	a->defrect.height		= VOU_MAX_IMAGE_HEIGHT;
	a->pixelaspect.numerator	= 1;
	a->pixelaspect.denominator	= 1;

	return 0;
}

static irqreturn_t sh_vou_isr(int irq, void *dev_id)
{
	struct sh_vou_device *vou_dev = dev_id;
	static unsigned long j;
	struct videobuf_buffer *vb;
	static int cnt;
	static int side;
	u32 irq_status = sh_vou_reg_a_read(vou_dev, VOUIR), masked;
	u32 vou_status = sh_vou_reg_a_read(vou_dev, VOUSTR);

	if (!(irq_status & 0x300)) {
		if (printk_timed_ratelimit(&j, 500))
			dev_warn(vou_dev->v4l2_dev.dev, "IRQ status 0x%x!\n",
				 irq_status);
		return IRQ_NONE;
	}

	spin_lock(&vou_dev->lock);
	if (!vou_dev->active || list_empty(&vou_dev->queue)) {
		if (printk_timed_ratelimit(&j, 500))
			dev_warn(vou_dev->v4l2_dev.dev,
				 "IRQ without active buffer: %x!\n", irq_status);
		/* Just ack: buf_release will disable further interrupts */
		sh_vou_reg_a_set(vou_dev, VOUIR, 0, 0x300);
		spin_unlock(&vou_dev->lock);
		return IRQ_HANDLED;
	}

	masked = ~(0x300 & irq_status) & irq_status & 0x30304;
	dev_dbg(vou_dev->v4l2_dev.dev,
		"IRQ status 0x%x -> 0x%x, VOU status 0x%x, cnt %d\n",
		irq_status, masked, vou_status, cnt);

	cnt++;
	side = vou_status & 0x10000;

	/* Clear only set interrupts */
	sh_vou_reg_a_write(vou_dev, VOUIR, masked);

	vb = vou_dev->active;
	list_del(&vb->queue);

	vb->state = VIDEOBUF_DONE;
	do_gettimeofday(&vb->ts);
	vb->field_count++;
	wake_up(&vb->done);

	if (list_empty(&vou_dev->queue)) {
		/* Stop VOU */
		dev_dbg(vou_dev->v4l2_dev.dev, "%s: queue empty after %d\n",
			__func__, cnt);
		sh_vou_reg_a_set(vou_dev, VOUER, 0, 1);
		vou_dev->active = NULL;
		vou_dev->status = SH_VOU_INITIALISING;
		/* Disable End-of-Frame (VSYNC) interrupts */
		sh_vou_reg_a_set(vou_dev, VOUIR, 0, 0x30000);
		spin_unlock(&vou_dev->lock);
		return IRQ_HANDLED;
	}

	vou_dev->active = list_entry(vou_dev->queue.next,
				     struct videobuf_buffer, queue);

	if (vou_dev->active->queue.next != &vou_dev->queue) {
		struct videobuf_buffer *new = list_entry(vou_dev->active->queue.next,
						struct videobuf_buffer, queue);
		sh_vou_schedule_next(vou_dev, new);
	}

	spin_unlock(&vou_dev->lock);

	return IRQ_HANDLED;
}

static int sh_vou_hw_init(struct sh_vou_device *vou_dev)
{
	struct sh_vou_pdata *pdata = vou_dev->pdata;
	u32 voucr = sh_vou_ntsc_mode(pdata->bus_fmt) << 29;
	int i = 100;

	/* Disable all IRQs */
	sh_vou_reg_a_write(vou_dev, VOUIR, 0);

	/* Reset VOU interfaces - registers unaffected */
	sh_vou_reg_a_write(vou_dev, VOUSRR, 0x101);
	while (--i && (sh_vou_reg_a_read(vou_dev, VOUSRR) & 0x101))
		udelay(1);

	if (!i)
		return -ETIMEDOUT;

	dev_dbg(vou_dev->v4l2_dev.dev, "Reset took %dus\n", 100 - i);

	if (pdata->flags & SH_VOU_PCLK_FALLING)
		voucr |= 1 << 28;
	if (pdata->flags & SH_VOU_HSYNC_LOW)
		voucr |= 1 << 27;
	if (pdata->flags & SH_VOU_VSYNC_LOW)
		voucr |= 1 << 26;
	sh_vou_reg_ab_set(vou_dev, VOUCR, voucr, 0xfc000000);

	/* Manual register side switching at first */
	sh_vou_reg_a_write(vou_dev, VOURCR, 4);
	/* Default - fixed HSYNC length, can be made configurable is required */
	sh_vou_reg_ab_write(vou_dev, VOUMSR, 0x800000);

	return 0;
}

/* File operations */
static int sh_vou_open(struct file *file)
{
	struct video_device *vdev = video_devdata(file);
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);
	struct sh_vou_file *vou_file = kzalloc(sizeof(struct sh_vou_file),
					       GFP_KERNEL);

	if (!vou_file)
		return -ENOMEM;

	dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__);

	file->private_data = vou_file;

	if (atomic_inc_return(&vou_dev->use_count) == 1) {
		int ret;
		/* First open */
		vou_dev->status = SH_VOU_INITIALISING;
		pm_runtime_get_sync(vdev->v4l2_dev->dev);
		ret = sh_vou_hw_init(vou_dev);
		if (ret < 0) {
			atomic_dec(&vou_dev->use_count);
			pm_runtime_put(vdev->v4l2_dev->dev);
			vou_dev->status = SH_VOU_IDLE;
			return ret;
		}
	}

	videobuf_queue_dma_contig_init(&vou_file->vbq, &sh_vou_video_qops,
				       vou_dev->v4l2_dev.dev, &vou_dev->lock,
				       V4L2_BUF_TYPE_VIDEO_OUTPUT,
				       V4L2_FIELD_NONE,
				       sizeof(struct videobuf_buffer), vdev);

	return 0;
}

static int sh_vou_release(struct file *file)
{
	struct video_device *vdev = video_devdata(file);
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);
	struct sh_vou_file *vou_file = file->private_data;

	dev_dbg(vou_file->vbq.dev, "%s()\n", __func__);

	if (!atomic_dec_return(&vou_dev->use_count)) {
		/* Last close */
		vou_dev->status = SH_VOU_IDLE;
		sh_vou_reg_a_set(vou_dev, VOUER, 0, 0x101);
		pm_runtime_put(vdev->v4l2_dev->dev);
	}

	file->private_data = NULL;
	kfree(vou_file);

	return 0;
}

static int sh_vou_mmap(struct file *file, struct vm_area_struct *vma)
{
	struct sh_vou_file *vou_file = file->private_data;

	dev_dbg(vou_file->vbq.dev, "%s()\n", __func__);

	return videobuf_mmap_mapper(&vou_file->vbq, vma);
}

static unsigned int sh_vou_poll(struct file *file, poll_table *wait)
{
	struct sh_vou_file *vou_file = file->private_data;

	dev_dbg(vou_file->vbq.dev, "%s()\n", __func__);

	return videobuf_poll_stream(file, &vou_file->vbq, wait);
}

static int sh_vou_g_chip_ident(struct file *file, void *fh,
				   struct v4l2_dbg_chip_ident *id)
{
	struct video_device *vdev = video_devdata(file);
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);

	return v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, core, g_chip_ident, id);
}

#ifdef CONFIG_VIDEO_ADV_DEBUG
static int sh_vou_g_register(struct file *file, void *fh,
				 struct v4l2_dbg_register *reg)
{
	struct video_device *vdev = video_devdata(file);
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);

	return v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, core, g_register, reg);
}

static int sh_vou_s_register(struct file *file, void *fh,
				 struct v4l2_dbg_register *reg)
{
	struct video_device *vdev = video_devdata(file);
	struct sh_vou_device *vou_dev = video_get_drvdata(vdev);

	return v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, core, s_register, reg);
}
#endif

/* sh_vou display ioctl operations */
static const struct v4l2_ioctl_ops sh_vou_ioctl_ops = {
	.vidioc_querycap        	= sh_vou_querycap,
	.vidioc_enum_fmt_vid_out	= sh_vou_enum_fmt_vid_out,
	.vidioc_g_fmt_vid_out		= sh_vou_g_fmt_vid_out,
	.vidioc_s_fmt_vid_out		= sh_vou_s_fmt_vid_out,
	.vidioc_try_fmt_vid_out		= sh_vou_try_fmt_vid_out,
	.vidioc_reqbufs			= sh_vou_reqbufs,
	.vidioc_querybuf		= sh_vou_querybuf,
	.vidioc_qbuf			= sh_vou_qbuf,
	.vidioc_dqbuf			= sh_vou_dqbuf,
	.vidioc_streamon		= sh_vou_streamon,
	.vidioc_streamoff		= sh_vou_streamoff,
	.vidioc_s_std			= sh_vou_s_std,
	.vidioc_g_std			= sh_vou_g_std,
	.vidioc_cropcap			= sh_vou_cropcap,
	.vidioc_g_crop			= sh_vou_g_crop,
	.vidioc_s_crop			= sh_vou_s_crop,
	.vidioc_g_chip_ident		= sh_vou_g_chip_ident,
#ifdef CONFIG_VIDEO_ADV_DEBUG
	.vidioc_g_register		= sh_vou_g_register,
	.vidioc_s_register		= sh_vou_s_register,
#endif
};

static const struct v4l2_file_operations sh_vou_fops = {
	.owner		= THIS_MODULE,
	.open		= sh_vou_open,
	.release	= sh_vou_release,
	.ioctl		= video_ioctl2,
	.mmap		= sh_vou_mmap,
	.poll		= sh_vou_poll,
};

static const struct video_device sh_vou_video_template = {
	.name		= "sh_vou",
	.fops		= &sh_vou_fops,
	.ioctl_ops	= &sh_vou_ioctl_ops,
	.tvnorms	= V4L2_STD_525_60, /* PAL only supported in 8-bit non-bt656 mode */
	.current_norm	= V4L2_STD_NTSC_M,
};

static int __devinit sh_vou_probe(struct platform_device *pdev)
{
	struct sh_vou_pdata *vou_pdata = pdev->dev.platform_data;
	struct v4l2_rect *rect;
	struct v4l2_pix_format *pix;
	struct i2c_adapter *i2c_adap;
	struct video_device *vdev;
	struct sh_vou_device *vou_dev;
	struct resource *reg_res, *region;
	struct v4l2_subdev *subdev;
	int irq, ret;

	reg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	irq = platform_get_irq(pdev, 0);

	if (!vou_pdata || !reg_res || irq <= 0) {
		dev_err(&pdev->dev, "Insufficient VOU platform information.\n");
		return -ENODEV;
	}

	vou_dev = kzalloc(sizeof(*vou_dev), GFP_KERNEL);
	if (!vou_dev)
		return -ENOMEM;

	INIT_LIST_HEAD(&vou_dev->queue);
	spin_lock_init(&vou_dev->lock);
	atomic_set(&vou_dev->use_count, 0);
	vou_dev->pdata = vou_pdata;
	vou_dev->status = SH_VOU_IDLE;

	rect = &vou_dev->rect;
	pix = &vou_dev->pix;

	/* Fill in defaults */
	vou_dev->std		= sh_vou_video_template.current_norm;
	rect->left		= 0;
	rect->top		= 0;
	rect->width		= VOU_MAX_IMAGE_WIDTH;
	rect->height		= 480;
	pix->width		= VOU_MAX_IMAGE_WIDTH;
	pix->height		= 480;
	pix->pixelformat	= V4L2_PIX_FMT_YVYU;
	pix->field		= V4L2_FIELD_NONE;
	pix->bytesperline	= VOU_MAX_IMAGE_WIDTH * 2;
	pix->sizeimage		= VOU_MAX_IMAGE_WIDTH * 2 * 480;
	pix->colorspace		= V4L2_COLORSPACE_SMPTE170M;

	region = request_mem_region(reg_res->start, resource_size(reg_res),
				    pdev->name);
	if (!region) {
		dev_err(&pdev->dev, "VOU region already claimed\n");
		ret = -EBUSY;
		goto ereqmemreg;
	}

	vou_dev->base = ioremap(reg_res->start, resource_size(reg_res));
	if (!vou_dev->base) {
		ret = -ENOMEM;
		goto emap;
	}

	ret = request_irq(irq, sh_vou_isr, 0, "vou", vou_dev);
	if (ret < 0)
		goto ereqirq;

	ret = v4l2_device_register(&pdev->dev, &vou_dev->v4l2_dev);
	if (ret < 0) {
		dev_err(&pdev->dev, "Error registering v4l2 device\n");
		goto ev4l2devreg;
	}

	/* Allocate memory for video device */
	vdev = video_device_alloc();
	if (vdev == NULL) {
		ret = -ENOMEM;
		goto evdevalloc;
	}

	*vdev = sh_vou_video_template;
	if (vou_pdata->bus_fmt == SH_VOU_BUS_8BIT)
		vdev->tvnorms |= V4L2_STD_PAL;
	vdev->v4l2_dev = &vou_dev->v4l2_dev;
	vdev->release = video_device_release;

	vou_dev->vdev = vdev;
	video_set_drvdata(vdev, vou_dev);

	pm_runtime_enable(&pdev->dev);
	pm_runtime_resume(&pdev->dev);

	i2c_adap = i2c_get_adapter(vou_pdata->i2c_adap);
	if (!i2c_adap) {
		ret = -ENODEV;
		goto ei2cgadap;
	}

	ret = sh_vou_hw_init(vou_dev);
	if (ret < 0)
		goto ereset;

	subdev = v4l2_i2c_new_subdev_board(&vou_dev->v4l2_dev, i2c_adap,
			vou_pdata->module_name, vou_pdata->board_info, NULL);
	if (!subdev) {
		ret = -ENOMEM;
		goto ei2cnd;
	}

	ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
	if (ret < 0)
		goto evregdev;

	return 0;

evregdev:
ei2cnd:
ereset:
	i2c_put_adapter(i2c_adap);
ei2cgadap:
	video_device_release(vdev);
	pm_runtime_disable(&pdev->dev);
evdevalloc:
	v4l2_device_unregister(&vou_dev->v4l2_dev);
ev4l2devreg:
	free_irq(irq, vou_dev);
ereqirq:
	iounmap(vou_dev->base);
emap:
	release_mem_region(reg_res->start, resource_size(reg_res));
ereqmemreg:
	kfree(vou_dev);
	return ret;
}

static int __devexit sh_vou_remove(struct platform_device *pdev)
{
	int irq = platform_get_irq(pdev, 0);
	struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev);
	struct sh_vou_device *vou_dev = container_of(v4l2_dev,
						struct sh_vou_device, v4l2_dev);
	struct v4l2_subdev *sd = list_entry(v4l2_dev->subdevs.next,
					    struct v4l2_subdev, list);
	struct i2c_client *client = v4l2_get_subdevdata(sd);
	struct resource *reg_res;

	if (irq > 0)
		free_irq(irq, vou_dev);
	pm_runtime_disable(&pdev->dev);
	video_unregister_device(vou_dev->vdev);
	i2c_put_adapter(client->adapter);
	v4l2_device_unregister(&vou_dev->v4l2_dev);
	iounmap(vou_dev->base);
	reg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (reg_res)
		release_mem_region(reg_res->start, resource_size(reg_res));
	kfree(vou_dev);
	return 0;
}

static struct platform_driver __refdata sh_vou = {
	.remove  = __devexit_p(sh_vou_remove),
	.driver  = {
		.name	= "sh-vou",
		.owner	= THIS_MODULE,
	},
};

static int __init sh_vou_init(void)
{
	return platform_driver_probe(&sh_vou, sh_vou_probe);
}

static void __exit sh_vou_exit(void)
{
	platform_driver_unregister(&sh_vou);
}

module_init(sh_vou_init);
module_exit(sh_vou_exit);

MODULE_DESCRIPTION("SuperH VOU driver");
MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:sh-vou");