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