18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * SuperH Video Output Unit (VOU) driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h> 98c2ecf20Sopenharmony_ci#include <linux/delay.h> 108c2ecf20Sopenharmony_ci#include <linux/errno.h> 118c2ecf20Sopenharmony_ci#include <linux/fs.h> 128c2ecf20Sopenharmony_ci#include <linux/i2c.h> 138c2ecf20Sopenharmony_ci#include <linux/init.h> 148c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 158c2ecf20Sopenharmony_ci#include <linux/kernel.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 178c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h> 188c2ecf20Sopenharmony_ci#include <linux/slab.h> 198c2ecf20Sopenharmony_ci#include <linux/videodev2.h> 208c2ecf20Sopenharmony_ci#include <linux/module.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci#include <media/drv-intf/sh_vou.h> 238c2ecf20Sopenharmony_ci#include <media/v4l2-common.h> 248c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 258c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h> 268c2ecf20Sopenharmony_ci#include <media/v4l2-mediabus.h> 278c2ecf20Sopenharmony_ci#include <media/videobuf2-v4l2.h> 288c2ecf20Sopenharmony_ci#include <media/videobuf2-dma-contig.h> 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* Mirror addresses are not available for all registers */ 318c2ecf20Sopenharmony_ci#define VOUER 0 328c2ecf20Sopenharmony_ci#define VOUCR 4 338c2ecf20Sopenharmony_ci#define VOUSTR 8 348c2ecf20Sopenharmony_ci#define VOUVCR 0xc 358c2ecf20Sopenharmony_ci#define VOUISR 0x10 368c2ecf20Sopenharmony_ci#define VOUBCR 0x14 378c2ecf20Sopenharmony_ci#define VOUDPR 0x18 388c2ecf20Sopenharmony_ci#define VOUDSR 0x1c 398c2ecf20Sopenharmony_ci#define VOUVPR 0x20 408c2ecf20Sopenharmony_ci#define VOUIR 0x24 418c2ecf20Sopenharmony_ci#define VOUSRR 0x28 428c2ecf20Sopenharmony_ci#define VOUMSR 0x2c 438c2ecf20Sopenharmony_ci#define VOUHIR 0x30 448c2ecf20Sopenharmony_ci#define VOUDFR 0x34 458c2ecf20Sopenharmony_ci#define VOUAD1R 0x38 468c2ecf20Sopenharmony_ci#define VOUAD2R 0x3c 478c2ecf20Sopenharmony_ci#define VOUAIR 0x40 488c2ecf20Sopenharmony_ci#define VOUSWR 0x44 498c2ecf20Sopenharmony_ci#define VOURCR 0x48 508c2ecf20Sopenharmony_ci#define VOURPR 0x50 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cienum sh_vou_status { 538c2ecf20Sopenharmony_ci SH_VOU_IDLE, 548c2ecf20Sopenharmony_ci SH_VOU_INITIALISING, 558c2ecf20Sopenharmony_ci SH_VOU_RUNNING, 568c2ecf20Sopenharmony_ci}; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci#define VOU_MIN_IMAGE_WIDTH 16 598c2ecf20Sopenharmony_ci#define VOU_MAX_IMAGE_WIDTH 720 608c2ecf20Sopenharmony_ci#define VOU_MIN_IMAGE_HEIGHT 16 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistruct sh_vou_buffer { 638c2ecf20Sopenharmony_ci struct vb2_v4l2_buffer vb; 648c2ecf20Sopenharmony_ci struct list_head list; 658c2ecf20Sopenharmony_ci}; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic inline struct 688c2ecf20Sopenharmony_cish_vou_buffer *to_sh_vou_buffer(struct vb2_v4l2_buffer *vb2) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci return container_of(vb2, struct sh_vou_buffer, vb); 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistruct sh_vou_device { 748c2ecf20Sopenharmony_ci struct v4l2_device v4l2_dev; 758c2ecf20Sopenharmony_ci struct video_device vdev; 768c2ecf20Sopenharmony_ci struct sh_vou_pdata *pdata; 778c2ecf20Sopenharmony_ci spinlock_t lock; 788c2ecf20Sopenharmony_ci void __iomem *base; 798c2ecf20Sopenharmony_ci /* State information */ 808c2ecf20Sopenharmony_ci struct v4l2_pix_format pix; 818c2ecf20Sopenharmony_ci struct v4l2_rect rect; 828c2ecf20Sopenharmony_ci struct list_head buf_list; 838c2ecf20Sopenharmony_ci v4l2_std_id std; 848c2ecf20Sopenharmony_ci int pix_idx; 858c2ecf20Sopenharmony_ci struct vb2_queue queue; 868c2ecf20Sopenharmony_ci struct sh_vou_buffer *active; 878c2ecf20Sopenharmony_ci enum sh_vou_status status; 888c2ecf20Sopenharmony_ci unsigned sequence; 898c2ecf20Sopenharmony_ci struct mutex fop_lock; 908c2ecf20Sopenharmony_ci}; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci/* Register access routines for sides A, B and mirror addresses */ 938c2ecf20Sopenharmony_cistatic void sh_vou_reg_a_write(struct sh_vou_device *vou_dev, unsigned int reg, 948c2ecf20Sopenharmony_ci u32 value) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci __raw_writel(value, vou_dev->base + reg); 978c2ecf20Sopenharmony_ci} 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cistatic void sh_vou_reg_ab_write(struct sh_vou_device *vou_dev, unsigned int reg, 1008c2ecf20Sopenharmony_ci u32 value) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci __raw_writel(value, vou_dev->base + reg); 1038c2ecf20Sopenharmony_ci __raw_writel(value, vou_dev->base + reg + 0x1000); 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic void sh_vou_reg_m_write(struct sh_vou_device *vou_dev, unsigned int reg, 1078c2ecf20Sopenharmony_ci u32 value) 1088c2ecf20Sopenharmony_ci{ 1098c2ecf20Sopenharmony_ci __raw_writel(value, vou_dev->base + reg + 0x2000); 1108c2ecf20Sopenharmony_ci} 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_cistatic u32 sh_vou_reg_a_read(struct sh_vou_device *vou_dev, unsigned int reg) 1138c2ecf20Sopenharmony_ci{ 1148c2ecf20Sopenharmony_ci return __raw_readl(vou_dev->base + reg); 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_cistatic void sh_vou_reg_a_set(struct sh_vou_device *vou_dev, unsigned int reg, 1188c2ecf20Sopenharmony_ci u32 value, u32 mask) 1198c2ecf20Sopenharmony_ci{ 1208c2ecf20Sopenharmony_ci u32 old = __raw_readl(vou_dev->base + reg); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci value = (value & mask) | (old & ~mask); 1238c2ecf20Sopenharmony_ci __raw_writel(value, vou_dev->base + reg); 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_cistatic void sh_vou_reg_b_set(struct sh_vou_device *vou_dev, unsigned int reg, 1278c2ecf20Sopenharmony_ci u32 value, u32 mask) 1288c2ecf20Sopenharmony_ci{ 1298c2ecf20Sopenharmony_ci sh_vou_reg_a_set(vou_dev, reg + 0x1000, value, mask); 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic void sh_vou_reg_ab_set(struct sh_vou_device *vou_dev, unsigned int reg, 1338c2ecf20Sopenharmony_ci u32 value, u32 mask) 1348c2ecf20Sopenharmony_ci{ 1358c2ecf20Sopenharmony_ci sh_vou_reg_a_set(vou_dev, reg, value, mask); 1368c2ecf20Sopenharmony_ci sh_vou_reg_b_set(vou_dev, reg, value, mask); 1378c2ecf20Sopenharmony_ci} 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_cistruct sh_vou_fmt { 1408c2ecf20Sopenharmony_ci u32 pfmt; 1418c2ecf20Sopenharmony_ci unsigned char bpp; 1428c2ecf20Sopenharmony_ci unsigned char bpl; 1438c2ecf20Sopenharmony_ci unsigned char rgb; 1448c2ecf20Sopenharmony_ci unsigned char yf; 1458c2ecf20Sopenharmony_ci unsigned char pkf; 1468c2ecf20Sopenharmony_ci}; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci/* Further pixel formats can be added */ 1498c2ecf20Sopenharmony_cistatic struct sh_vou_fmt vou_fmt[] = { 1508c2ecf20Sopenharmony_ci { 1518c2ecf20Sopenharmony_ci .pfmt = V4L2_PIX_FMT_NV12, 1528c2ecf20Sopenharmony_ci .bpp = 12, 1538c2ecf20Sopenharmony_ci .bpl = 1, 1548c2ecf20Sopenharmony_ci .yf = 0, 1558c2ecf20Sopenharmony_ci .rgb = 0, 1568c2ecf20Sopenharmony_ci }, 1578c2ecf20Sopenharmony_ci { 1588c2ecf20Sopenharmony_ci .pfmt = V4L2_PIX_FMT_NV16, 1598c2ecf20Sopenharmony_ci .bpp = 16, 1608c2ecf20Sopenharmony_ci .bpl = 1, 1618c2ecf20Sopenharmony_ci .yf = 1, 1628c2ecf20Sopenharmony_ci .rgb = 0, 1638c2ecf20Sopenharmony_ci }, 1648c2ecf20Sopenharmony_ci { 1658c2ecf20Sopenharmony_ci .pfmt = V4L2_PIX_FMT_RGB24, 1668c2ecf20Sopenharmony_ci .bpp = 24, 1678c2ecf20Sopenharmony_ci .bpl = 3, 1688c2ecf20Sopenharmony_ci .pkf = 2, 1698c2ecf20Sopenharmony_ci .rgb = 1, 1708c2ecf20Sopenharmony_ci }, 1718c2ecf20Sopenharmony_ci { 1728c2ecf20Sopenharmony_ci .pfmt = V4L2_PIX_FMT_RGB565, 1738c2ecf20Sopenharmony_ci .bpp = 16, 1748c2ecf20Sopenharmony_ci .bpl = 2, 1758c2ecf20Sopenharmony_ci .pkf = 3, 1768c2ecf20Sopenharmony_ci .rgb = 1, 1778c2ecf20Sopenharmony_ci }, 1788c2ecf20Sopenharmony_ci { 1798c2ecf20Sopenharmony_ci .pfmt = V4L2_PIX_FMT_RGB565X, 1808c2ecf20Sopenharmony_ci .bpp = 16, 1818c2ecf20Sopenharmony_ci .bpl = 2, 1828c2ecf20Sopenharmony_ci .pkf = 3, 1838c2ecf20Sopenharmony_ci .rgb = 1, 1848c2ecf20Sopenharmony_ci }, 1858c2ecf20Sopenharmony_ci}; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_cistatic void sh_vou_schedule_next(struct sh_vou_device *vou_dev, 1888c2ecf20Sopenharmony_ci struct vb2_v4l2_buffer *vbuf) 1898c2ecf20Sopenharmony_ci{ 1908c2ecf20Sopenharmony_ci dma_addr_t addr1, addr2; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci addr1 = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); 1938c2ecf20Sopenharmony_ci switch (vou_dev->pix.pixelformat) { 1948c2ecf20Sopenharmony_ci case V4L2_PIX_FMT_NV12: 1958c2ecf20Sopenharmony_ci case V4L2_PIX_FMT_NV16: 1968c2ecf20Sopenharmony_ci addr2 = addr1 + vou_dev->pix.width * vou_dev->pix.height; 1978c2ecf20Sopenharmony_ci break; 1988c2ecf20Sopenharmony_ci default: 1998c2ecf20Sopenharmony_ci addr2 = 0; 2008c2ecf20Sopenharmony_ci } 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci sh_vou_reg_m_write(vou_dev, VOUAD1R, addr1); 2038c2ecf20Sopenharmony_ci sh_vou_reg_m_write(vou_dev, VOUAD2R, addr2); 2048c2ecf20Sopenharmony_ci} 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_cistatic void sh_vou_stream_config(struct sh_vou_device *vou_dev) 2078c2ecf20Sopenharmony_ci{ 2088c2ecf20Sopenharmony_ci unsigned int row_coeff; 2098c2ecf20Sopenharmony_ci#ifdef __LITTLE_ENDIAN 2108c2ecf20Sopenharmony_ci u32 dataswap = 7; 2118c2ecf20Sopenharmony_ci#else 2128c2ecf20Sopenharmony_ci u32 dataswap = 0; 2138c2ecf20Sopenharmony_ci#endif 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci switch (vou_dev->pix.pixelformat) { 2168c2ecf20Sopenharmony_ci default: 2178c2ecf20Sopenharmony_ci case V4L2_PIX_FMT_NV12: 2188c2ecf20Sopenharmony_ci case V4L2_PIX_FMT_NV16: 2198c2ecf20Sopenharmony_ci row_coeff = 1; 2208c2ecf20Sopenharmony_ci break; 2218c2ecf20Sopenharmony_ci case V4L2_PIX_FMT_RGB565: 2228c2ecf20Sopenharmony_ci dataswap ^= 1; 2238c2ecf20Sopenharmony_ci fallthrough; 2248c2ecf20Sopenharmony_ci case V4L2_PIX_FMT_RGB565X: 2258c2ecf20Sopenharmony_ci row_coeff = 2; 2268c2ecf20Sopenharmony_ci break; 2278c2ecf20Sopenharmony_ci case V4L2_PIX_FMT_RGB24: 2288c2ecf20Sopenharmony_ci row_coeff = 3; 2298c2ecf20Sopenharmony_ci break; 2308c2ecf20Sopenharmony_ci } 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOUSWR, dataswap); 2338c2ecf20Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUAIR, vou_dev->pix.width * row_coeff); 2348c2ecf20Sopenharmony_ci} 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci/* Locking: caller holds fop_lock mutex */ 2378c2ecf20Sopenharmony_cistatic int sh_vou_queue_setup(struct vb2_queue *vq, 2388c2ecf20Sopenharmony_ci unsigned int *nbuffers, unsigned int *nplanes, 2398c2ecf20Sopenharmony_ci unsigned int sizes[], struct device *alloc_devs[]) 2408c2ecf20Sopenharmony_ci{ 2418c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = vb2_get_drv_priv(vq); 2428c2ecf20Sopenharmony_ci struct v4l2_pix_format *pix = &vou_dev->pix; 2438c2ecf20Sopenharmony_ci int bytes_per_line = vou_fmt[vou_dev->pix_idx].bpp * pix->width / 8; 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci if (*nplanes) 2488c2ecf20Sopenharmony_ci return sizes[0] < pix->height * bytes_per_line ? -EINVAL : 0; 2498c2ecf20Sopenharmony_ci *nplanes = 1; 2508c2ecf20Sopenharmony_ci sizes[0] = pix->height * bytes_per_line; 2518c2ecf20Sopenharmony_ci return 0; 2528c2ecf20Sopenharmony_ci} 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic int sh_vou_buf_prepare(struct vb2_buffer *vb) 2558c2ecf20Sopenharmony_ci{ 2568c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = vb2_get_drv_priv(vb->vb2_queue); 2578c2ecf20Sopenharmony_ci struct v4l2_pix_format *pix = &vou_dev->pix; 2588c2ecf20Sopenharmony_ci unsigned bytes_per_line = vou_fmt[vou_dev->pix_idx].bpp * pix->width / 8; 2598c2ecf20Sopenharmony_ci unsigned size = pix->height * bytes_per_line; 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci if (vb2_plane_size(vb, 0) < size) { 2648c2ecf20Sopenharmony_ci /* User buffer too small */ 2658c2ecf20Sopenharmony_ci dev_warn(vou_dev->v4l2_dev.dev, "buffer too small (%lu < %u)\n", 2668c2ecf20Sopenharmony_ci vb2_plane_size(vb, 0), size); 2678c2ecf20Sopenharmony_ci return -EINVAL; 2688c2ecf20Sopenharmony_ci } 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci vb2_set_plane_payload(vb, 0, size); 2718c2ecf20Sopenharmony_ci return 0; 2728c2ecf20Sopenharmony_ci} 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci/* Locking: caller holds fop_lock mutex and vq->irqlock spinlock */ 2758c2ecf20Sopenharmony_cistatic void sh_vou_buf_queue(struct vb2_buffer *vb) 2768c2ecf20Sopenharmony_ci{ 2778c2ecf20Sopenharmony_ci struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); 2788c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = vb2_get_drv_priv(vb->vb2_queue); 2798c2ecf20Sopenharmony_ci struct sh_vou_buffer *shbuf = to_sh_vou_buffer(vbuf); 2808c2ecf20Sopenharmony_ci unsigned long flags; 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci spin_lock_irqsave(&vou_dev->lock, flags); 2838c2ecf20Sopenharmony_ci list_add_tail(&shbuf->list, &vou_dev->buf_list); 2848c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&vou_dev->lock, flags); 2858c2ecf20Sopenharmony_ci} 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_cistatic int sh_vou_start_streaming(struct vb2_queue *vq, unsigned int count) 2888c2ecf20Sopenharmony_ci{ 2898c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = vb2_get_drv_priv(vq); 2908c2ecf20Sopenharmony_ci struct sh_vou_buffer *buf, *node; 2918c2ecf20Sopenharmony_ci int ret; 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci vou_dev->sequence = 0; 2948c2ecf20Sopenharmony_ci ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, 2958c2ecf20Sopenharmony_ci video, s_stream, 1); 2968c2ecf20Sopenharmony_ci if (ret < 0 && ret != -ENOIOCTLCMD) { 2978c2ecf20Sopenharmony_ci list_for_each_entry_safe(buf, node, &vou_dev->buf_list, list) { 2988c2ecf20Sopenharmony_ci vb2_buffer_done(&buf->vb.vb2_buf, 2998c2ecf20Sopenharmony_ci VB2_BUF_STATE_QUEUED); 3008c2ecf20Sopenharmony_ci list_del(&buf->list); 3018c2ecf20Sopenharmony_ci } 3028c2ecf20Sopenharmony_ci vou_dev->active = NULL; 3038c2ecf20Sopenharmony_ci return ret; 3048c2ecf20Sopenharmony_ci } 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci buf = list_entry(vou_dev->buf_list.next, struct sh_vou_buffer, list); 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci vou_dev->active = buf; 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_ci /* Start from side A: we use mirror addresses, so, set B */ 3118c2ecf20Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOURPR, 1); 3128c2ecf20Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s: first buffer status 0x%x\n", 3138c2ecf20Sopenharmony_ci __func__, sh_vou_reg_a_read(vou_dev, VOUSTR)); 3148c2ecf20Sopenharmony_ci sh_vou_schedule_next(vou_dev, &buf->vb); 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci buf = list_entry(buf->list.next, struct sh_vou_buffer, list); 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci /* Second buffer - initialise register side B */ 3198c2ecf20Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOURPR, 0); 3208c2ecf20Sopenharmony_ci sh_vou_schedule_next(vou_dev, &buf->vb); 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci /* Register side switching with frame VSYNC */ 3238c2ecf20Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOURCR, 5); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci sh_vou_stream_config(vou_dev); 3268c2ecf20Sopenharmony_ci /* Enable End-of-Frame (VSYNC) interrupts */ 3278c2ecf20Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOUIR, 0x10004); 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci /* Two buffers on the queue - activate the hardware */ 3308c2ecf20Sopenharmony_ci vou_dev->status = SH_VOU_RUNNING; 3318c2ecf20Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOUER, 0x107); 3328c2ecf20Sopenharmony_ci return 0; 3338c2ecf20Sopenharmony_ci} 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_cistatic void sh_vou_stop_streaming(struct vb2_queue *vq) 3368c2ecf20Sopenharmony_ci{ 3378c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = vb2_get_drv_priv(vq); 3388c2ecf20Sopenharmony_ci struct sh_vou_buffer *buf, *node; 3398c2ecf20Sopenharmony_ci unsigned long flags; 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, 3428c2ecf20Sopenharmony_ci video, s_stream, 0); 3438c2ecf20Sopenharmony_ci /* disable output */ 3448c2ecf20Sopenharmony_ci sh_vou_reg_a_set(vou_dev, VOUER, 0, 1); 3458c2ecf20Sopenharmony_ci /* ...but the current frame will complete */ 3468c2ecf20Sopenharmony_ci sh_vou_reg_a_set(vou_dev, VOUIR, 0, 0x30000); 3478c2ecf20Sopenharmony_ci msleep(50); 3488c2ecf20Sopenharmony_ci spin_lock_irqsave(&vou_dev->lock, flags); 3498c2ecf20Sopenharmony_ci list_for_each_entry_safe(buf, node, &vou_dev->buf_list, list) { 3508c2ecf20Sopenharmony_ci vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); 3518c2ecf20Sopenharmony_ci list_del(&buf->list); 3528c2ecf20Sopenharmony_ci } 3538c2ecf20Sopenharmony_ci vou_dev->active = NULL; 3548c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&vou_dev->lock, flags); 3558c2ecf20Sopenharmony_ci} 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_cistatic const struct vb2_ops sh_vou_qops = { 3588c2ecf20Sopenharmony_ci .queue_setup = sh_vou_queue_setup, 3598c2ecf20Sopenharmony_ci .buf_prepare = sh_vou_buf_prepare, 3608c2ecf20Sopenharmony_ci .buf_queue = sh_vou_buf_queue, 3618c2ecf20Sopenharmony_ci .start_streaming = sh_vou_start_streaming, 3628c2ecf20Sopenharmony_ci .stop_streaming = sh_vou_stop_streaming, 3638c2ecf20Sopenharmony_ci .wait_prepare = vb2_ops_wait_prepare, 3648c2ecf20Sopenharmony_ci .wait_finish = vb2_ops_wait_finish, 3658c2ecf20Sopenharmony_ci}; 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_ci/* Video IOCTLs */ 3688c2ecf20Sopenharmony_cistatic int sh_vou_querycap(struct file *file, void *priv, 3698c2ecf20Sopenharmony_ci struct v4l2_capability *cap) 3708c2ecf20Sopenharmony_ci{ 3718c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_ci strscpy(cap->card, "SuperH VOU", sizeof(cap->card)); 3768c2ecf20Sopenharmony_ci strscpy(cap->driver, "sh-vou", sizeof(cap->driver)); 3778c2ecf20Sopenharmony_ci strscpy(cap->bus_info, "platform:sh-vou", sizeof(cap->bus_info)); 3788c2ecf20Sopenharmony_ci return 0; 3798c2ecf20Sopenharmony_ci} 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_ci/* Enumerate formats, that the device can accept from the user */ 3828c2ecf20Sopenharmony_cistatic int sh_vou_enum_fmt_vid_out(struct file *file, void *priv, 3838c2ecf20Sopenharmony_ci struct v4l2_fmtdesc *fmt) 3848c2ecf20Sopenharmony_ci{ 3858c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_ci if (fmt->index >= ARRAY_SIZE(vou_fmt)) 3888c2ecf20Sopenharmony_ci return -EINVAL; 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci fmt->pixelformat = vou_fmt[fmt->index].pfmt; 3938c2ecf20Sopenharmony_ci 3948c2ecf20Sopenharmony_ci return 0; 3958c2ecf20Sopenharmony_ci} 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_cistatic int sh_vou_g_fmt_vid_out(struct file *file, void *priv, 3988c2ecf20Sopenharmony_ci struct v4l2_format *fmt) 3998c2ecf20Sopenharmony_ci{ 4008c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_ci fmt->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; 4058c2ecf20Sopenharmony_ci fmt->fmt.pix = vou_dev->pix; 4068c2ecf20Sopenharmony_ci 4078c2ecf20Sopenharmony_ci return 0; 4088c2ecf20Sopenharmony_ci} 4098c2ecf20Sopenharmony_ci 4108c2ecf20Sopenharmony_cistatic const unsigned char vou_scale_h_num[] = {1, 9, 2, 9, 4}; 4118c2ecf20Sopenharmony_cistatic const unsigned char vou_scale_h_den[] = {1, 8, 1, 4, 1}; 4128c2ecf20Sopenharmony_cistatic const unsigned char vou_scale_h_fld[] = {0, 2, 1, 3}; 4138c2ecf20Sopenharmony_cistatic const unsigned char vou_scale_v_num[] = {1, 2, 4}; 4148c2ecf20Sopenharmony_cistatic const unsigned char vou_scale_v_den[] = {1, 1, 1}; 4158c2ecf20Sopenharmony_cistatic const unsigned char vou_scale_v_fld[] = {0, 1}; 4168c2ecf20Sopenharmony_ci 4178c2ecf20Sopenharmony_cistatic void sh_vou_configure_geometry(struct sh_vou_device *vou_dev, 4188c2ecf20Sopenharmony_ci int pix_idx, int w_idx, int h_idx) 4198c2ecf20Sopenharmony_ci{ 4208c2ecf20Sopenharmony_ci struct sh_vou_fmt *fmt = vou_fmt + pix_idx; 4218c2ecf20Sopenharmony_ci unsigned int black_left, black_top, width_max, 4228c2ecf20Sopenharmony_ci frame_in_height, frame_out_height, frame_out_top; 4238c2ecf20Sopenharmony_ci struct v4l2_rect *rect = &vou_dev->rect; 4248c2ecf20Sopenharmony_ci struct v4l2_pix_format *pix = &vou_dev->pix; 4258c2ecf20Sopenharmony_ci u32 vouvcr = 0, dsr_h, dsr_v; 4268c2ecf20Sopenharmony_ci 4278c2ecf20Sopenharmony_ci if (vou_dev->std & V4L2_STD_525_60) { 4288c2ecf20Sopenharmony_ci width_max = 858; 4298c2ecf20Sopenharmony_ci /* height_max = 262; */ 4308c2ecf20Sopenharmony_ci } else { 4318c2ecf20Sopenharmony_ci width_max = 864; 4328c2ecf20Sopenharmony_ci /* height_max = 312; */ 4338c2ecf20Sopenharmony_ci } 4348c2ecf20Sopenharmony_ci 4358c2ecf20Sopenharmony_ci frame_in_height = pix->height / 2; 4368c2ecf20Sopenharmony_ci frame_out_height = rect->height / 2; 4378c2ecf20Sopenharmony_ci frame_out_top = rect->top / 2; 4388c2ecf20Sopenharmony_ci 4398c2ecf20Sopenharmony_ci /* 4408c2ecf20Sopenharmony_ci * Cropping scheme: max useful image is 720x480, and the total video 4418c2ecf20Sopenharmony_ci * area is 858x525 (NTSC) or 864x625 (PAL). AK8813 / 8814 starts 4428c2ecf20Sopenharmony_ci * sampling data beginning with fixed 276th (NTSC) / 288th (PAL) clock, 4438c2ecf20Sopenharmony_ci * of which the first 33 / 25 clocks HSYNC must be held active. This 4448c2ecf20Sopenharmony_ci * has to be configured in CR[HW]. 1 pixel equals 2 clock periods. 4458c2ecf20Sopenharmony_ci * This gives CR[HW] = 16 / 12, VPR[HVP] = 138 / 144, which gives 4468c2ecf20Sopenharmony_ci * exactly 858 - 138 = 864 - 144 = 720! We call the out-of-display area, 4478c2ecf20Sopenharmony_ci * beyond DSR, specified on the left and top by the VPR register "black 4488c2ecf20Sopenharmony_ci * pixels" and out-of-image area (DPR) "background pixels." We fix VPR 4498c2ecf20Sopenharmony_ci * at 138 / 144 : 20, because that's the HSYNC timing, that our first 4508c2ecf20Sopenharmony_ci * client requires, and that's exactly what leaves us 720 pixels for the 4518c2ecf20Sopenharmony_ci * image; we leave VPR[VVP] at default 20 for now, because the client 4528c2ecf20Sopenharmony_ci * doesn't seem to have any special requirements for it. Otherwise we 4538c2ecf20Sopenharmony_ci * could also set it to max - 240 = 22 / 72. Thus VPR depends only on 4548c2ecf20Sopenharmony_ci * the selected standard, and DPR and DSR are selected according to 4558c2ecf20Sopenharmony_ci * cropping. Q: how does the client detect the first valid line? Does 4568c2ecf20Sopenharmony_ci * HSYNC stay inactive during invalid (black) lines? 4578c2ecf20Sopenharmony_ci */ 4588c2ecf20Sopenharmony_ci black_left = width_max - VOU_MAX_IMAGE_WIDTH; 4598c2ecf20Sopenharmony_ci black_top = 20; 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_ci dsr_h = rect->width + rect->left; 4628c2ecf20Sopenharmony_ci dsr_v = frame_out_height + frame_out_top; 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, 4658c2ecf20Sopenharmony_ci "image %ux%u, black %u:%u, offset %u:%u, display %ux%u\n", 4668c2ecf20Sopenharmony_ci pix->width, frame_in_height, black_left, black_top, 4678c2ecf20Sopenharmony_ci rect->left, frame_out_top, dsr_h, dsr_v); 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ci /* VOUISR height - half of a frame height in frame mode */ 4708c2ecf20Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUISR, (pix->width << 16) | frame_in_height); 4718c2ecf20Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUVPR, (black_left << 16) | black_top); 4728c2ecf20Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUDPR, (rect->left << 16) | frame_out_top); 4738c2ecf20Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUDSR, (dsr_h << 16) | dsr_v); 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_ci /* 4768c2ecf20Sopenharmony_ci * if necessary, we could set VOUHIR to 4778c2ecf20Sopenharmony_ci * max(black_left + dsr_h, width_max) here 4788c2ecf20Sopenharmony_ci */ 4798c2ecf20Sopenharmony_ci 4808c2ecf20Sopenharmony_ci if (w_idx) 4818c2ecf20Sopenharmony_ci vouvcr |= (1 << 15) | (vou_scale_h_fld[w_idx - 1] << 4); 4828c2ecf20Sopenharmony_ci if (h_idx) 4838c2ecf20Sopenharmony_ci vouvcr |= (1 << 14) | vou_scale_v_fld[h_idx - 1]; 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "0x%08x: scaling 0x%x\n", 4868c2ecf20Sopenharmony_ci fmt->pfmt, vouvcr); 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_ci /* To produce a colour bar for testing set bit 23 of VOUVCR */ 4898c2ecf20Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUVCR, vouvcr); 4908c2ecf20Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUDFR, 4918c2ecf20Sopenharmony_ci fmt->pkf | (fmt->yf << 8) | (fmt->rgb << 16)); 4928c2ecf20Sopenharmony_ci} 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_cistruct sh_vou_geometry { 4958c2ecf20Sopenharmony_ci struct v4l2_rect output; 4968c2ecf20Sopenharmony_ci unsigned int in_width; 4978c2ecf20Sopenharmony_ci unsigned int in_height; 4988c2ecf20Sopenharmony_ci int scale_idx_h; 4998c2ecf20Sopenharmony_ci int scale_idx_v; 5008c2ecf20Sopenharmony_ci}; 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_ci/* 5038c2ecf20Sopenharmony_ci * Find input geometry, that we can use to produce output, closest to the 5048c2ecf20Sopenharmony_ci * requested rectangle, using VOU scaling 5058c2ecf20Sopenharmony_ci */ 5068c2ecf20Sopenharmony_cistatic void vou_adjust_input(struct sh_vou_geometry *geo, v4l2_std_id std) 5078c2ecf20Sopenharmony_ci{ 5088c2ecf20Sopenharmony_ci /* The compiler cannot know, that best and idx will indeed be set */ 5098c2ecf20Sopenharmony_ci unsigned int best_err = UINT_MAX, best = 0, img_height_max; 5108c2ecf20Sopenharmony_ci int i, idx = 0; 5118c2ecf20Sopenharmony_ci 5128c2ecf20Sopenharmony_ci if (std & V4L2_STD_525_60) 5138c2ecf20Sopenharmony_ci img_height_max = 480; 5148c2ecf20Sopenharmony_ci else 5158c2ecf20Sopenharmony_ci img_height_max = 576; 5168c2ecf20Sopenharmony_ci 5178c2ecf20Sopenharmony_ci /* Image width must be a multiple of 4 */ 5188c2ecf20Sopenharmony_ci v4l_bound_align_image(&geo->in_width, 5198c2ecf20Sopenharmony_ci VOU_MIN_IMAGE_WIDTH, VOU_MAX_IMAGE_WIDTH, 2, 5208c2ecf20Sopenharmony_ci &geo->in_height, 5218c2ecf20Sopenharmony_ci VOU_MIN_IMAGE_HEIGHT, img_height_max, 1, 0); 5228c2ecf20Sopenharmony_ci 5238c2ecf20Sopenharmony_ci /* Select scales to come as close as possible to the output image */ 5248c2ecf20Sopenharmony_ci for (i = ARRAY_SIZE(vou_scale_h_num) - 1; i >= 0; i--) { 5258c2ecf20Sopenharmony_ci unsigned int err; 5268c2ecf20Sopenharmony_ci unsigned int found = geo->output.width * vou_scale_h_den[i] / 5278c2ecf20Sopenharmony_ci vou_scale_h_num[i]; 5288c2ecf20Sopenharmony_ci 5298c2ecf20Sopenharmony_ci if (found > VOU_MAX_IMAGE_WIDTH) 5308c2ecf20Sopenharmony_ci /* scales increase */ 5318c2ecf20Sopenharmony_ci break; 5328c2ecf20Sopenharmony_ci 5338c2ecf20Sopenharmony_ci err = abs(found - geo->in_width); 5348c2ecf20Sopenharmony_ci if (err < best_err) { 5358c2ecf20Sopenharmony_ci best_err = err; 5368c2ecf20Sopenharmony_ci idx = i; 5378c2ecf20Sopenharmony_ci best = found; 5388c2ecf20Sopenharmony_ci } 5398c2ecf20Sopenharmony_ci if (!err) 5408c2ecf20Sopenharmony_ci break; 5418c2ecf20Sopenharmony_ci } 5428c2ecf20Sopenharmony_ci 5438c2ecf20Sopenharmony_ci geo->in_width = best; 5448c2ecf20Sopenharmony_ci geo->scale_idx_h = idx; 5458c2ecf20Sopenharmony_ci 5468c2ecf20Sopenharmony_ci best_err = UINT_MAX; 5478c2ecf20Sopenharmony_ci 5488c2ecf20Sopenharmony_ci /* This loop can be replaced with one division */ 5498c2ecf20Sopenharmony_ci for (i = ARRAY_SIZE(vou_scale_v_num) - 1; i >= 0; i--) { 5508c2ecf20Sopenharmony_ci unsigned int err; 5518c2ecf20Sopenharmony_ci unsigned int found = geo->output.height * vou_scale_v_den[i] / 5528c2ecf20Sopenharmony_ci vou_scale_v_num[i]; 5538c2ecf20Sopenharmony_ci 5548c2ecf20Sopenharmony_ci if (found > img_height_max) 5558c2ecf20Sopenharmony_ci /* scales increase */ 5568c2ecf20Sopenharmony_ci break; 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci err = abs(found - geo->in_height); 5598c2ecf20Sopenharmony_ci if (err < best_err) { 5608c2ecf20Sopenharmony_ci best_err = err; 5618c2ecf20Sopenharmony_ci idx = i; 5628c2ecf20Sopenharmony_ci best = found; 5638c2ecf20Sopenharmony_ci } 5648c2ecf20Sopenharmony_ci if (!err) 5658c2ecf20Sopenharmony_ci break; 5668c2ecf20Sopenharmony_ci } 5678c2ecf20Sopenharmony_ci 5688c2ecf20Sopenharmony_ci geo->in_height = best; 5698c2ecf20Sopenharmony_ci geo->scale_idx_v = idx; 5708c2ecf20Sopenharmony_ci} 5718c2ecf20Sopenharmony_ci 5728c2ecf20Sopenharmony_ci/* 5738c2ecf20Sopenharmony_ci * Find output geometry, that we can produce, using VOU scaling, closest to 5748c2ecf20Sopenharmony_ci * the requested rectangle 5758c2ecf20Sopenharmony_ci */ 5768c2ecf20Sopenharmony_cistatic void vou_adjust_output(struct sh_vou_geometry *geo, v4l2_std_id std) 5778c2ecf20Sopenharmony_ci{ 5788c2ecf20Sopenharmony_ci unsigned int best_err = UINT_MAX, best = geo->in_width, 5798c2ecf20Sopenharmony_ci width_max, height_max, img_height_max; 5808c2ecf20Sopenharmony_ci int i, idx_h = 0, idx_v = 0; 5818c2ecf20Sopenharmony_ci 5828c2ecf20Sopenharmony_ci if (std & V4L2_STD_525_60) { 5838c2ecf20Sopenharmony_ci width_max = 858; 5848c2ecf20Sopenharmony_ci height_max = 262 * 2; 5858c2ecf20Sopenharmony_ci img_height_max = 480; 5868c2ecf20Sopenharmony_ci } else { 5878c2ecf20Sopenharmony_ci width_max = 864; 5888c2ecf20Sopenharmony_ci height_max = 312 * 2; 5898c2ecf20Sopenharmony_ci img_height_max = 576; 5908c2ecf20Sopenharmony_ci } 5918c2ecf20Sopenharmony_ci 5928c2ecf20Sopenharmony_ci /* Select scales to come as close as possible to the output image */ 5938c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(vou_scale_h_num); i++) { 5948c2ecf20Sopenharmony_ci unsigned int err; 5958c2ecf20Sopenharmony_ci unsigned int found = geo->in_width * vou_scale_h_num[i] / 5968c2ecf20Sopenharmony_ci vou_scale_h_den[i]; 5978c2ecf20Sopenharmony_ci 5988c2ecf20Sopenharmony_ci if (found > VOU_MAX_IMAGE_WIDTH) 5998c2ecf20Sopenharmony_ci /* scales increase */ 6008c2ecf20Sopenharmony_ci break; 6018c2ecf20Sopenharmony_ci 6028c2ecf20Sopenharmony_ci err = abs(found - geo->output.width); 6038c2ecf20Sopenharmony_ci if (err < best_err) { 6048c2ecf20Sopenharmony_ci best_err = err; 6058c2ecf20Sopenharmony_ci idx_h = i; 6068c2ecf20Sopenharmony_ci best = found; 6078c2ecf20Sopenharmony_ci } 6088c2ecf20Sopenharmony_ci if (!err) 6098c2ecf20Sopenharmony_ci break; 6108c2ecf20Sopenharmony_ci } 6118c2ecf20Sopenharmony_ci 6128c2ecf20Sopenharmony_ci geo->output.width = best; 6138c2ecf20Sopenharmony_ci geo->scale_idx_h = idx_h; 6148c2ecf20Sopenharmony_ci if (geo->output.left + best > width_max) 6158c2ecf20Sopenharmony_ci geo->output.left = width_max - best; 6168c2ecf20Sopenharmony_ci 6178c2ecf20Sopenharmony_ci pr_debug("%s(): W %u * %u/%u = %u\n", __func__, geo->in_width, 6188c2ecf20Sopenharmony_ci vou_scale_h_num[idx_h], vou_scale_h_den[idx_h], best); 6198c2ecf20Sopenharmony_ci 6208c2ecf20Sopenharmony_ci best_err = UINT_MAX; 6218c2ecf20Sopenharmony_ci 6228c2ecf20Sopenharmony_ci /* This loop can be replaced with one division */ 6238c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(vou_scale_v_num); i++) { 6248c2ecf20Sopenharmony_ci unsigned int err; 6258c2ecf20Sopenharmony_ci unsigned int found = geo->in_height * vou_scale_v_num[i] / 6268c2ecf20Sopenharmony_ci vou_scale_v_den[i]; 6278c2ecf20Sopenharmony_ci 6288c2ecf20Sopenharmony_ci if (found > img_height_max) 6298c2ecf20Sopenharmony_ci /* scales increase */ 6308c2ecf20Sopenharmony_ci break; 6318c2ecf20Sopenharmony_ci 6328c2ecf20Sopenharmony_ci err = abs(found - geo->output.height); 6338c2ecf20Sopenharmony_ci if (err < best_err) { 6348c2ecf20Sopenharmony_ci best_err = err; 6358c2ecf20Sopenharmony_ci idx_v = i; 6368c2ecf20Sopenharmony_ci best = found; 6378c2ecf20Sopenharmony_ci } 6388c2ecf20Sopenharmony_ci if (!err) 6398c2ecf20Sopenharmony_ci break; 6408c2ecf20Sopenharmony_ci } 6418c2ecf20Sopenharmony_ci 6428c2ecf20Sopenharmony_ci geo->output.height = best; 6438c2ecf20Sopenharmony_ci geo->scale_idx_v = idx_v; 6448c2ecf20Sopenharmony_ci if (geo->output.top + best > height_max) 6458c2ecf20Sopenharmony_ci geo->output.top = height_max - best; 6468c2ecf20Sopenharmony_ci 6478c2ecf20Sopenharmony_ci pr_debug("%s(): H %u * %u/%u = %u\n", __func__, geo->in_height, 6488c2ecf20Sopenharmony_ci vou_scale_v_num[idx_v], vou_scale_v_den[idx_v], best); 6498c2ecf20Sopenharmony_ci} 6508c2ecf20Sopenharmony_ci 6518c2ecf20Sopenharmony_cistatic int sh_vou_try_fmt_vid_out(struct file *file, void *priv, 6528c2ecf20Sopenharmony_ci struct v4l2_format *fmt) 6538c2ecf20Sopenharmony_ci{ 6548c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 6558c2ecf20Sopenharmony_ci struct v4l2_pix_format *pix = &fmt->fmt.pix; 6568c2ecf20Sopenharmony_ci unsigned int img_height_max; 6578c2ecf20Sopenharmony_ci int pix_idx; 6588c2ecf20Sopenharmony_ci 6598c2ecf20Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); 6608c2ecf20Sopenharmony_ci 6618c2ecf20Sopenharmony_ci pix->field = V4L2_FIELD_INTERLACED; 6628c2ecf20Sopenharmony_ci pix->colorspace = V4L2_COLORSPACE_SMPTE170M; 6638c2ecf20Sopenharmony_ci pix->ycbcr_enc = pix->quantization = 0; 6648c2ecf20Sopenharmony_ci 6658c2ecf20Sopenharmony_ci for (pix_idx = 0; pix_idx < ARRAY_SIZE(vou_fmt); pix_idx++) 6668c2ecf20Sopenharmony_ci if (vou_fmt[pix_idx].pfmt == pix->pixelformat) 6678c2ecf20Sopenharmony_ci break; 6688c2ecf20Sopenharmony_ci 6698c2ecf20Sopenharmony_ci if (pix_idx == ARRAY_SIZE(vou_fmt)) 6708c2ecf20Sopenharmony_ci return -EINVAL; 6718c2ecf20Sopenharmony_ci 6728c2ecf20Sopenharmony_ci if (vou_dev->std & V4L2_STD_525_60) 6738c2ecf20Sopenharmony_ci img_height_max = 480; 6748c2ecf20Sopenharmony_ci else 6758c2ecf20Sopenharmony_ci img_height_max = 576; 6768c2ecf20Sopenharmony_ci 6778c2ecf20Sopenharmony_ci v4l_bound_align_image(&pix->width, 6788c2ecf20Sopenharmony_ci VOU_MIN_IMAGE_WIDTH, VOU_MAX_IMAGE_WIDTH, 2, 6798c2ecf20Sopenharmony_ci &pix->height, 6808c2ecf20Sopenharmony_ci VOU_MIN_IMAGE_HEIGHT, img_height_max, 1, 0); 6818c2ecf20Sopenharmony_ci pix->bytesperline = pix->width * vou_fmt[pix_idx].bpl; 6828c2ecf20Sopenharmony_ci pix->sizeimage = pix->height * ((pix->width * vou_fmt[pix_idx].bpp) >> 3); 6838c2ecf20Sopenharmony_ci 6848c2ecf20Sopenharmony_ci return 0; 6858c2ecf20Sopenharmony_ci} 6868c2ecf20Sopenharmony_ci 6878c2ecf20Sopenharmony_cistatic int sh_vou_set_fmt_vid_out(struct sh_vou_device *vou_dev, 6888c2ecf20Sopenharmony_ci struct v4l2_pix_format *pix) 6898c2ecf20Sopenharmony_ci{ 6908c2ecf20Sopenharmony_ci unsigned int img_height_max; 6918c2ecf20Sopenharmony_ci struct sh_vou_geometry geo; 6928c2ecf20Sopenharmony_ci struct v4l2_subdev_format format = { 6938c2ecf20Sopenharmony_ci .which = V4L2_SUBDEV_FORMAT_ACTIVE, 6948c2ecf20Sopenharmony_ci /* Revisit: is this the correct code? */ 6958c2ecf20Sopenharmony_ci .format.code = MEDIA_BUS_FMT_YUYV8_2X8, 6968c2ecf20Sopenharmony_ci .format.field = V4L2_FIELD_INTERLACED, 6978c2ecf20Sopenharmony_ci .format.colorspace = V4L2_COLORSPACE_SMPTE170M, 6988c2ecf20Sopenharmony_ci }; 6998c2ecf20Sopenharmony_ci struct v4l2_mbus_framefmt *mbfmt = &format.format; 7008c2ecf20Sopenharmony_ci int pix_idx; 7018c2ecf20Sopenharmony_ci int ret; 7028c2ecf20Sopenharmony_ci 7038c2ecf20Sopenharmony_ci if (vb2_is_busy(&vou_dev->queue)) 7048c2ecf20Sopenharmony_ci return -EBUSY; 7058c2ecf20Sopenharmony_ci 7068c2ecf20Sopenharmony_ci for (pix_idx = 0; pix_idx < ARRAY_SIZE(vou_fmt); pix_idx++) 7078c2ecf20Sopenharmony_ci if (vou_fmt[pix_idx].pfmt == pix->pixelformat) 7088c2ecf20Sopenharmony_ci break; 7098c2ecf20Sopenharmony_ci 7108c2ecf20Sopenharmony_ci geo.in_width = pix->width; 7118c2ecf20Sopenharmony_ci geo.in_height = pix->height; 7128c2ecf20Sopenharmony_ci geo.output = vou_dev->rect; 7138c2ecf20Sopenharmony_ci 7148c2ecf20Sopenharmony_ci vou_adjust_output(&geo, vou_dev->std); 7158c2ecf20Sopenharmony_ci 7168c2ecf20Sopenharmony_ci mbfmt->width = geo.output.width; 7178c2ecf20Sopenharmony_ci mbfmt->height = geo.output.height; 7188c2ecf20Sopenharmony_ci ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad, 7198c2ecf20Sopenharmony_ci set_fmt, NULL, &format); 7208c2ecf20Sopenharmony_ci /* Must be implemented, so, don't check for -ENOIOCTLCMD */ 7218c2ecf20Sopenharmony_ci if (ret < 0) 7228c2ecf20Sopenharmony_ci return ret; 7238c2ecf20Sopenharmony_ci 7248c2ecf20Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s(): %ux%u -> %ux%u\n", __func__, 7258c2ecf20Sopenharmony_ci geo.output.width, geo.output.height, mbfmt->width, mbfmt->height); 7268c2ecf20Sopenharmony_ci 7278c2ecf20Sopenharmony_ci if (vou_dev->std & V4L2_STD_525_60) 7288c2ecf20Sopenharmony_ci img_height_max = 480; 7298c2ecf20Sopenharmony_ci else 7308c2ecf20Sopenharmony_ci img_height_max = 576; 7318c2ecf20Sopenharmony_ci 7328c2ecf20Sopenharmony_ci /* Sanity checks */ 7338c2ecf20Sopenharmony_ci if ((unsigned)mbfmt->width > VOU_MAX_IMAGE_WIDTH || 7348c2ecf20Sopenharmony_ci (unsigned)mbfmt->height > img_height_max || 7358c2ecf20Sopenharmony_ci mbfmt->code != MEDIA_BUS_FMT_YUYV8_2X8) 7368c2ecf20Sopenharmony_ci return -EIO; 7378c2ecf20Sopenharmony_ci 7388c2ecf20Sopenharmony_ci if (mbfmt->width != geo.output.width || 7398c2ecf20Sopenharmony_ci mbfmt->height != geo.output.height) { 7408c2ecf20Sopenharmony_ci geo.output.width = mbfmt->width; 7418c2ecf20Sopenharmony_ci geo.output.height = mbfmt->height; 7428c2ecf20Sopenharmony_ci 7438c2ecf20Sopenharmony_ci vou_adjust_input(&geo, vou_dev->std); 7448c2ecf20Sopenharmony_ci } 7458c2ecf20Sopenharmony_ci 7468c2ecf20Sopenharmony_ci /* We tried to preserve output rectangle, but it could have changed */ 7478c2ecf20Sopenharmony_ci vou_dev->rect = geo.output; 7488c2ecf20Sopenharmony_ci pix->width = geo.in_width; 7498c2ecf20Sopenharmony_ci pix->height = geo.in_height; 7508c2ecf20Sopenharmony_ci 7518c2ecf20Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s(): %ux%u\n", __func__, 7528c2ecf20Sopenharmony_ci pix->width, pix->height); 7538c2ecf20Sopenharmony_ci 7548c2ecf20Sopenharmony_ci vou_dev->pix_idx = pix_idx; 7558c2ecf20Sopenharmony_ci 7568c2ecf20Sopenharmony_ci vou_dev->pix = *pix; 7578c2ecf20Sopenharmony_ci 7588c2ecf20Sopenharmony_ci sh_vou_configure_geometry(vou_dev, pix_idx, 7598c2ecf20Sopenharmony_ci geo.scale_idx_h, geo.scale_idx_v); 7608c2ecf20Sopenharmony_ci 7618c2ecf20Sopenharmony_ci return 0; 7628c2ecf20Sopenharmony_ci} 7638c2ecf20Sopenharmony_ci 7648c2ecf20Sopenharmony_cistatic int sh_vou_s_fmt_vid_out(struct file *file, void *priv, 7658c2ecf20Sopenharmony_ci struct v4l2_format *fmt) 7668c2ecf20Sopenharmony_ci{ 7678c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 7688c2ecf20Sopenharmony_ci int ret = sh_vou_try_fmt_vid_out(file, priv, fmt); 7698c2ecf20Sopenharmony_ci 7708c2ecf20Sopenharmony_ci if (ret) 7718c2ecf20Sopenharmony_ci return ret; 7728c2ecf20Sopenharmony_ci return sh_vou_set_fmt_vid_out(vou_dev, &fmt->fmt.pix); 7738c2ecf20Sopenharmony_ci} 7748c2ecf20Sopenharmony_ci 7758c2ecf20Sopenharmony_cistatic int sh_vou_enum_output(struct file *file, void *fh, 7768c2ecf20Sopenharmony_ci struct v4l2_output *a) 7778c2ecf20Sopenharmony_ci{ 7788c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 7798c2ecf20Sopenharmony_ci 7808c2ecf20Sopenharmony_ci if (a->index) 7818c2ecf20Sopenharmony_ci return -EINVAL; 7828c2ecf20Sopenharmony_ci strscpy(a->name, "Video Out", sizeof(a->name)); 7838c2ecf20Sopenharmony_ci a->type = V4L2_OUTPUT_TYPE_ANALOG; 7848c2ecf20Sopenharmony_ci a->std = vou_dev->vdev.tvnorms; 7858c2ecf20Sopenharmony_ci return 0; 7868c2ecf20Sopenharmony_ci} 7878c2ecf20Sopenharmony_ci 7888c2ecf20Sopenharmony_cistatic int sh_vou_g_output(struct file *file, void *fh, unsigned int *i) 7898c2ecf20Sopenharmony_ci{ 7908c2ecf20Sopenharmony_ci *i = 0; 7918c2ecf20Sopenharmony_ci return 0; 7928c2ecf20Sopenharmony_ci} 7938c2ecf20Sopenharmony_ci 7948c2ecf20Sopenharmony_cistatic int sh_vou_s_output(struct file *file, void *fh, unsigned int i) 7958c2ecf20Sopenharmony_ci{ 7968c2ecf20Sopenharmony_ci return i ? -EINVAL : 0; 7978c2ecf20Sopenharmony_ci} 7988c2ecf20Sopenharmony_ci 7998c2ecf20Sopenharmony_cistatic u32 sh_vou_ntsc_mode(enum sh_vou_bus_fmt bus_fmt) 8008c2ecf20Sopenharmony_ci{ 8018c2ecf20Sopenharmony_ci switch (bus_fmt) { 8028c2ecf20Sopenharmony_ci default: 8038c2ecf20Sopenharmony_ci pr_warn("%s(): Invalid bus-format code %d, using default 8-bit\n", 8048c2ecf20Sopenharmony_ci __func__, bus_fmt); 8058c2ecf20Sopenharmony_ci fallthrough; 8068c2ecf20Sopenharmony_ci case SH_VOU_BUS_8BIT: 8078c2ecf20Sopenharmony_ci return 1; 8088c2ecf20Sopenharmony_ci case SH_VOU_BUS_16BIT: 8098c2ecf20Sopenharmony_ci return 0; 8108c2ecf20Sopenharmony_ci case SH_VOU_BUS_BT656: 8118c2ecf20Sopenharmony_ci return 3; 8128c2ecf20Sopenharmony_ci } 8138c2ecf20Sopenharmony_ci} 8148c2ecf20Sopenharmony_ci 8158c2ecf20Sopenharmony_cistatic int sh_vou_s_std(struct file *file, void *priv, v4l2_std_id std_id) 8168c2ecf20Sopenharmony_ci{ 8178c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 8188c2ecf20Sopenharmony_ci int ret; 8198c2ecf20Sopenharmony_ci 8208c2ecf20Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s(): 0x%llx\n", __func__, std_id); 8218c2ecf20Sopenharmony_ci 8228c2ecf20Sopenharmony_ci if (std_id == vou_dev->std) 8238c2ecf20Sopenharmony_ci return 0; 8248c2ecf20Sopenharmony_ci 8258c2ecf20Sopenharmony_ci if (vb2_is_busy(&vou_dev->queue)) 8268c2ecf20Sopenharmony_ci return -EBUSY; 8278c2ecf20Sopenharmony_ci 8288c2ecf20Sopenharmony_ci ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, video, 8298c2ecf20Sopenharmony_ci s_std_output, std_id); 8308c2ecf20Sopenharmony_ci /* Shall we continue, if the subdev doesn't support .s_std_output()? */ 8318c2ecf20Sopenharmony_ci if (ret < 0 && ret != -ENOIOCTLCMD) 8328c2ecf20Sopenharmony_ci return ret; 8338c2ecf20Sopenharmony_ci 8348c2ecf20Sopenharmony_ci vou_dev->rect.top = vou_dev->rect.left = 0; 8358c2ecf20Sopenharmony_ci vou_dev->rect.width = VOU_MAX_IMAGE_WIDTH; 8368c2ecf20Sopenharmony_ci if (std_id & V4L2_STD_525_60) { 8378c2ecf20Sopenharmony_ci sh_vou_reg_ab_set(vou_dev, VOUCR, 8388c2ecf20Sopenharmony_ci sh_vou_ntsc_mode(vou_dev->pdata->bus_fmt) << 29, 7 << 29); 8398c2ecf20Sopenharmony_ci vou_dev->rect.height = 480; 8408c2ecf20Sopenharmony_ci } else { 8418c2ecf20Sopenharmony_ci sh_vou_reg_ab_set(vou_dev, VOUCR, 5 << 29, 7 << 29); 8428c2ecf20Sopenharmony_ci vou_dev->rect.height = 576; 8438c2ecf20Sopenharmony_ci } 8448c2ecf20Sopenharmony_ci 8458c2ecf20Sopenharmony_ci vou_dev->pix.width = vou_dev->rect.width; 8468c2ecf20Sopenharmony_ci vou_dev->pix.height = vou_dev->rect.height; 8478c2ecf20Sopenharmony_ci vou_dev->pix.bytesperline = 8488c2ecf20Sopenharmony_ci vou_dev->pix.width * vou_fmt[vou_dev->pix_idx].bpl; 8498c2ecf20Sopenharmony_ci vou_dev->pix.sizeimage = vou_dev->pix.height * 8508c2ecf20Sopenharmony_ci ((vou_dev->pix.width * vou_fmt[vou_dev->pix_idx].bpp) >> 3); 8518c2ecf20Sopenharmony_ci vou_dev->std = std_id; 8528c2ecf20Sopenharmony_ci sh_vou_set_fmt_vid_out(vou_dev, &vou_dev->pix); 8538c2ecf20Sopenharmony_ci 8548c2ecf20Sopenharmony_ci return 0; 8558c2ecf20Sopenharmony_ci} 8568c2ecf20Sopenharmony_ci 8578c2ecf20Sopenharmony_cistatic int sh_vou_g_std(struct file *file, void *priv, v4l2_std_id *std) 8588c2ecf20Sopenharmony_ci{ 8598c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 8608c2ecf20Sopenharmony_ci 8618c2ecf20Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); 8628c2ecf20Sopenharmony_ci 8638c2ecf20Sopenharmony_ci *std = vou_dev->std; 8648c2ecf20Sopenharmony_ci 8658c2ecf20Sopenharmony_ci return 0; 8668c2ecf20Sopenharmony_ci} 8678c2ecf20Sopenharmony_ci 8688c2ecf20Sopenharmony_cistatic int sh_vou_log_status(struct file *file, void *priv) 8698c2ecf20Sopenharmony_ci{ 8708c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 8718c2ecf20Sopenharmony_ci 8728c2ecf20Sopenharmony_ci pr_info("VOUER: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUER)); 8738c2ecf20Sopenharmony_ci pr_info("VOUCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUCR)); 8748c2ecf20Sopenharmony_ci pr_info("VOUSTR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUSTR)); 8758c2ecf20Sopenharmony_ci pr_info("VOUVCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUVCR)); 8768c2ecf20Sopenharmony_ci pr_info("VOUISR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUISR)); 8778c2ecf20Sopenharmony_ci pr_info("VOUBCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUBCR)); 8788c2ecf20Sopenharmony_ci pr_info("VOUDPR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUDPR)); 8798c2ecf20Sopenharmony_ci pr_info("VOUDSR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUDSR)); 8808c2ecf20Sopenharmony_ci pr_info("VOUVPR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUVPR)); 8818c2ecf20Sopenharmony_ci pr_info("VOUIR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUIR)); 8828c2ecf20Sopenharmony_ci pr_info("VOUSRR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUSRR)); 8838c2ecf20Sopenharmony_ci pr_info("VOUMSR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUMSR)); 8848c2ecf20Sopenharmony_ci pr_info("VOUHIR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUHIR)); 8858c2ecf20Sopenharmony_ci pr_info("VOUDFR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUDFR)); 8868c2ecf20Sopenharmony_ci pr_info("VOUAD1R: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUAD1R)); 8878c2ecf20Sopenharmony_ci pr_info("VOUAD2R: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUAD2R)); 8888c2ecf20Sopenharmony_ci pr_info("VOUAIR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUAIR)); 8898c2ecf20Sopenharmony_ci pr_info("VOUSWR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUSWR)); 8908c2ecf20Sopenharmony_ci pr_info("VOURCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOURCR)); 8918c2ecf20Sopenharmony_ci pr_info("VOURPR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOURPR)); 8928c2ecf20Sopenharmony_ci return 0; 8938c2ecf20Sopenharmony_ci} 8948c2ecf20Sopenharmony_ci 8958c2ecf20Sopenharmony_cistatic int sh_vou_g_selection(struct file *file, void *fh, 8968c2ecf20Sopenharmony_ci struct v4l2_selection *sel) 8978c2ecf20Sopenharmony_ci{ 8988c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 8998c2ecf20Sopenharmony_ci 9008c2ecf20Sopenharmony_ci if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) 9018c2ecf20Sopenharmony_ci return -EINVAL; 9028c2ecf20Sopenharmony_ci switch (sel->target) { 9038c2ecf20Sopenharmony_ci case V4L2_SEL_TGT_COMPOSE: 9048c2ecf20Sopenharmony_ci sel->r = vou_dev->rect; 9058c2ecf20Sopenharmony_ci break; 9068c2ecf20Sopenharmony_ci case V4L2_SEL_TGT_COMPOSE_DEFAULT: 9078c2ecf20Sopenharmony_ci case V4L2_SEL_TGT_COMPOSE_BOUNDS: 9088c2ecf20Sopenharmony_ci sel->r.left = 0; 9098c2ecf20Sopenharmony_ci sel->r.top = 0; 9108c2ecf20Sopenharmony_ci sel->r.width = VOU_MAX_IMAGE_WIDTH; 9118c2ecf20Sopenharmony_ci if (vou_dev->std & V4L2_STD_525_60) 9128c2ecf20Sopenharmony_ci sel->r.height = 480; 9138c2ecf20Sopenharmony_ci else 9148c2ecf20Sopenharmony_ci sel->r.height = 576; 9158c2ecf20Sopenharmony_ci break; 9168c2ecf20Sopenharmony_ci default: 9178c2ecf20Sopenharmony_ci return -EINVAL; 9188c2ecf20Sopenharmony_ci } 9198c2ecf20Sopenharmony_ci return 0; 9208c2ecf20Sopenharmony_ci} 9218c2ecf20Sopenharmony_ci 9228c2ecf20Sopenharmony_ci/* Assume a dull encoder, do all the work ourselves. */ 9238c2ecf20Sopenharmony_cistatic int sh_vou_s_selection(struct file *file, void *fh, 9248c2ecf20Sopenharmony_ci struct v4l2_selection *sel) 9258c2ecf20Sopenharmony_ci{ 9268c2ecf20Sopenharmony_ci struct v4l2_rect *rect = &sel->r; 9278c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 9288c2ecf20Sopenharmony_ci struct v4l2_subdev_selection sd_sel = { 9298c2ecf20Sopenharmony_ci .which = V4L2_SUBDEV_FORMAT_ACTIVE, 9308c2ecf20Sopenharmony_ci .target = V4L2_SEL_TGT_COMPOSE, 9318c2ecf20Sopenharmony_ci }; 9328c2ecf20Sopenharmony_ci struct v4l2_pix_format *pix = &vou_dev->pix; 9338c2ecf20Sopenharmony_ci struct sh_vou_geometry geo; 9348c2ecf20Sopenharmony_ci struct v4l2_subdev_format format = { 9358c2ecf20Sopenharmony_ci .which = V4L2_SUBDEV_FORMAT_ACTIVE, 9368c2ecf20Sopenharmony_ci /* Revisit: is this the correct code? */ 9378c2ecf20Sopenharmony_ci .format.code = MEDIA_BUS_FMT_YUYV8_2X8, 9388c2ecf20Sopenharmony_ci .format.field = V4L2_FIELD_INTERLACED, 9398c2ecf20Sopenharmony_ci .format.colorspace = V4L2_COLORSPACE_SMPTE170M, 9408c2ecf20Sopenharmony_ci }; 9418c2ecf20Sopenharmony_ci unsigned int img_height_max; 9428c2ecf20Sopenharmony_ci int ret; 9438c2ecf20Sopenharmony_ci 9448c2ecf20Sopenharmony_ci if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || 9458c2ecf20Sopenharmony_ci sel->target != V4L2_SEL_TGT_COMPOSE) 9468c2ecf20Sopenharmony_ci return -EINVAL; 9478c2ecf20Sopenharmony_ci 9488c2ecf20Sopenharmony_ci if (vb2_is_busy(&vou_dev->queue)) 9498c2ecf20Sopenharmony_ci return -EBUSY; 9508c2ecf20Sopenharmony_ci 9518c2ecf20Sopenharmony_ci if (vou_dev->std & V4L2_STD_525_60) 9528c2ecf20Sopenharmony_ci img_height_max = 480; 9538c2ecf20Sopenharmony_ci else 9548c2ecf20Sopenharmony_ci img_height_max = 576; 9558c2ecf20Sopenharmony_ci 9568c2ecf20Sopenharmony_ci v4l_bound_align_image(&rect->width, 9578c2ecf20Sopenharmony_ci VOU_MIN_IMAGE_WIDTH, VOU_MAX_IMAGE_WIDTH, 1, 9588c2ecf20Sopenharmony_ci &rect->height, 9598c2ecf20Sopenharmony_ci VOU_MIN_IMAGE_HEIGHT, img_height_max, 1, 0); 9608c2ecf20Sopenharmony_ci 9618c2ecf20Sopenharmony_ci if (rect->width + rect->left > VOU_MAX_IMAGE_WIDTH) 9628c2ecf20Sopenharmony_ci rect->left = VOU_MAX_IMAGE_WIDTH - rect->width; 9638c2ecf20Sopenharmony_ci 9648c2ecf20Sopenharmony_ci if (rect->height + rect->top > img_height_max) 9658c2ecf20Sopenharmony_ci rect->top = img_height_max - rect->height; 9668c2ecf20Sopenharmony_ci 9678c2ecf20Sopenharmony_ci geo.output = *rect; 9688c2ecf20Sopenharmony_ci geo.in_width = pix->width; 9698c2ecf20Sopenharmony_ci geo.in_height = pix->height; 9708c2ecf20Sopenharmony_ci 9718c2ecf20Sopenharmony_ci /* Configure the encoder one-to-one, position at 0, ignore errors */ 9728c2ecf20Sopenharmony_ci sd_sel.r.width = geo.output.width; 9738c2ecf20Sopenharmony_ci sd_sel.r.height = geo.output.height; 9748c2ecf20Sopenharmony_ci /* 9758c2ecf20Sopenharmony_ci * We first issue a S_SELECTION, so that the subsequent S_FMT delivers the 9768c2ecf20Sopenharmony_ci * final encoder configuration. 9778c2ecf20Sopenharmony_ci */ 9788c2ecf20Sopenharmony_ci v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad, 9798c2ecf20Sopenharmony_ci set_selection, NULL, &sd_sel); 9808c2ecf20Sopenharmony_ci format.format.width = geo.output.width; 9818c2ecf20Sopenharmony_ci format.format.height = geo.output.height; 9828c2ecf20Sopenharmony_ci ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad, 9838c2ecf20Sopenharmony_ci set_fmt, NULL, &format); 9848c2ecf20Sopenharmony_ci /* Must be implemented, so, don't check for -ENOIOCTLCMD */ 9858c2ecf20Sopenharmony_ci if (ret < 0) 9868c2ecf20Sopenharmony_ci return ret; 9878c2ecf20Sopenharmony_ci 9888c2ecf20Sopenharmony_ci /* Sanity checks */ 9898c2ecf20Sopenharmony_ci if ((unsigned)format.format.width > VOU_MAX_IMAGE_WIDTH || 9908c2ecf20Sopenharmony_ci (unsigned)format.format.height > img_height_max || 9918c2ecf20Sopenharmony_ci format.format.code != MEDIA_BUS_FMT_YUYV8_2X8) 9928c2ecf20Sopenharmony_ci return -EIO; 9938c2ecf20Sopenharmony_ci 9948c2ecf20Sopenharmony_ci geo.output.width = format.format.width; 9958c2ecf20Sopenharmony_ci geo.output.height = format.format.height; 9968c2ecf20Sopenharmony_ci 9978c2ecf20Sopenharmony_ci /* 9988c2ecf20Sopenharmony_ci * No down-scaling. According to the API, current call has precedence: 9998c2ecf20Sopenharmony_ci * https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/crop.html#cropping-structures 10008c2ecf20Sopenharmony_ci */ 10018c2ecf20Sopenharmony_ci vou_adjust_input(&geo, vou_dev->std); 10028c2ecf20Sopenharmony_ci 10038c2ecf20Sopenharmony_ci /* We tried to preserve output rectangle, but it could have changed */ 10048c2ecf20Sopenharmony_ci vou_dev->rect = geo.output; 10058c2ecf20Sopenharmony_ci pix->width = geo.in_width; 10068c2ecf20Sopenharmony_ci pix->height = geo.in_height; 10078c2ecf20Sopenharmony_ci 10088c2ecf20Sopenharmony_ci sh_vou_configure_geometry(vou_dev, vou_dev->pix_idx, 10098c2ecf20Sopenharmony_ci geo.scale_idx_h, geo.scale_idx_v); 10108c2ecf20Sopenharmony_ci 10118c2ecf20Sopenharmony_ci return 0; 10128c2ecf20Sopenharmony_ci} 10138c2ecf20Sopenharmony_ci 10148c2ecf20Sopenharmony_cistatic irqreturn_t sh_vou_isr(int irq, void *dev_id) 10158c2ecf20Sopenharmony_ci{ 10168c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = dev_id; 10178c2ecf20Sopenharmony_ci static unsigned long j; 10188c2ecf20Sopenharmony_ci struct sh_vou_buffer *vb; 10198c2ecf20Sopenharmony_ci static int cnt; 10208c2ecf20Sopenharmony_ci u32 irq_status = sh_vou_reg_a_read(vou_dev, VOUIR), masked; 10218c2ecf20Sopenharmony_ci u32 vou_status = sh_vou_reg_a_read(vou_dev, VOUSTR); 10228c2ecf20Sopenharmony_ci 10238c2ecf20Sopenharmony_ci if (!(irq_status & 0x300)) { 10248c2ecf20Sopenharmony_ci if (printk_timed_ratelimit(&j, 500)) 10258c2ecf20Sopenharmony_ci dev_warn(vou_dev->v4l2_dev.dev, "IRQ status 0x%x!\n", 10268c2ecf20Sopenharmony_ci irq_status); 10278c2ecf20Sopenharmony_ci return IRQ_NONE; 10288c2ecf20Sopenharmony_ci } 10298c2ecf20Sopenharmony_ci 10308c2ecf20Sopenharmony_ci spin_lock(&vou_dev->lock); 10318c2ecf20Sopenharmony_ci if (!vou_dev->active || list_empty(&vou_dev->buf_list)) { 10328c2ecf20Sopenharmony_ci if (printk_timed_ratelimit(&j, 500)) 10338c2ecf20Sopenharmony_ci dev_warn(vou_dev->v4l2_dev.dev, 10348c2ecf20Sopenharmony_ci "IRQ without active buffer: %x!\n", irq_status); 10358c2ecf20Sopenharmony_ci /* Just ack: buf_release will disable further interrupts */ 10368c2ecf20Sopenharmony_ci sh_vou_reg_a_set(vou_dev, VOUIR, 0, 0x300); 10378c2ecf20Sopenharmony_ci spin_unlock(&vou_dev->lock); 10388c2ecf20Sopenharmony_ci return IRQ_HANDLED; 10398c2ecf20Sopenharmony_ci } 10408c2ecf20Sopenharmony_ci 10418c2ecf20Sopenharmony_ci masked = ~(0x300 & irq_status) & irq_status & 0x30304; 10428c2ecf20Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, 10438c2ecf20Sopenharmony_ci "IRQ status 0x%x -> 0x%x, VOU status 0x%x, cnt %d\n", 10448c2ecf20Sopenharmony_ci irq_status, masked, vou_status, cnt); 10458c2ecf20Sopenharmony_ci 10468c2ecf20Sopenharmony_ci cnt++; 10478c2ecf20Sopenharmony_ci /* side = vou_status & 0x10000; */ 10488c2ecf20Sopenharmony_ci 10498c2ecf20Sopenharmony_ci /* Clear only set interrupts */ 10508c2ecf20Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOUIR, masked); 10518c2ecf20Sopenharmony_ci 10528c2ecf20Sopenharmony_ci vb = vou_dev->active; 10538c2ecf20Sopenharmony_ci if (list_is_singular(&vb->list)) { 10548c2ecf20Sopenharmony_ci /* Keep cycling while no next buffer is available */ 10558c2ecf20Sopenharmony_ci sh_vou_schedule_next(vou_dev, &vb->vb); 10568c2ecf20Sopenharmony_ci spin_unlock(&vou_dev->lock); 10578c2ecf20Sopenharmony_ci return IRQ_HANDLED; 10588c2ecf20Sopenharmony_ci } 10598c2ecf20Sopenharmony_ci 10608c2ecf20Sopenharmony_ci list_del(&vb->list); 10618c2ecf20Sopenharmony_ci 10628c2ecf20Sopenharmony_ci vb->vb.vb2_buf.timestamp = ktime_get_ns(); 10638c2ecf20Sopenharmony_ci vb->vb.sequence = vou_dev->sequence++; 10648c2ecf20Sopenharmony_ci vb->vb.field = V4L2_FIELD_INTERLACED; 10658c2ecf20Sopenharmony_ci vb2_buffer_done(&vb->vb.vb2_buf, VB2_BUF_STATE_DONE); 10668c2ecf20Sopenharmony_ci 10678c2ecf20Sopenharmony_ci vou_dev->active = list_entry(vou_dev->buf_list.next, 10688c2ecf20Sopenharmony_ci struct sh_vou_buffer, list); 10698c2ecf20Sopenharmony_ci 10708c2ecf20Sopenharmony_ci if (list_is_singular(&vou_dev->buf_list)) { 10718c2ecf20Sopenharmony_ci /* Keep cycling while no next buffer is available */ 10728c2ecf20Sopenharmony_ci sh_vou_schedule_next(vou_dev, &vou_dev->active->vb); 10738c2ecf20Sopenharmony_ci } else { 10748c2ecf20Sopenharmony_ci struct sh_vou_buffer *new = list_entry(vou_dev->active->list.next, 10758c2ecf20Sopenharmony_ci struct sh_vou_buffer, list); 10768c2ecf20Sopenharmony_ci sh_vou_schedule_next(vou_dev, &new->vb); 10778c2ecf20Sopenharmony_ci } 10788c2ecf20Sopenharmony_ci 10798c2ecf20Sopenharmony_ci spin_unlock(&vou_dev->lock); 10808c2ecf20Sopenharmony_ci 10818c2ecf20Sopenharmony_ci return IRQ_HANDLED; 10828c2ecf20Sopenharmony_ci} 10838c2ecf20Sopenharmony_ci 10848c2ecf20Sopenharmony_cistatic int sh_vou_hw_init(struct sh_vou_device *vou_dev) 10858c2ecf20Sopenharmony_ci{ 10868c2ecf20Sopenharmony_ci struct sh_vou_pdata *pdata = vou_dev->pdata; 10878c2ecf20Sopenharmony_ci u32 voucr = sh_vou_ntsc_mode(pdata->bus_fmt) << 29; 10888c2ecf20Sopenharmony_ci int i = 100; 10898c2ecf20Sopenharmony_ci 10908c2ecf20Sopenharmony_ci /* Disable all IRQs */ 10918c2ecf20Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOUIR, 0); 10928c2ecf20Sopenharmony_ci 10938c2ecf20Sopenharmony_ci /* Reset VOU interfaces - registers unaffected */ 10948c2ecf20Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOUSRR, 0x101); 10958c2ecf20Sopenharmony_ci while (--i && (sh_vou_reg_a_read(vou_dev, VOUSRR) & 0x101)) 10968c2ecf20Sopenharmony_ci udelay(1); 10978c2ecf20Sopenharmony_ci 10988c2ecf20Sopenharmony_ci if (!i) 10998c2ecf20Sopenharmony_ci return -ETIMEDOUT; 11008c2ecf20Sopenharmony_ci 11018c2ecf20Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "Reset took %dus\n", 100 - i); 11028c2ecf20Sopenharmony_ci 11038c2ecf20Sopenharmony_ci if (pdata->flags & SH_VOU_PCLK_FALLING) 11048c2ecf20Sopenharmony_ci voucr |= 1 << 28; 11058c2ecf20Sopenharmony_ci if (pdata->flags & SH_VOU_HSYNC_LOW) 11068c2ecf20Sopenharmony_ci voucr |= 1 << 27; 11078c2ecf20Sopenharmony_ci if (pdata->flags & SH_VOU_VSYNC_LOW) 11088c2ecf20Sopenharmony_ci voucr |= 1 << 26; 11098c2ecf20Sopenharmony_ci sh_vou_reg_ab_set(vou_dev, VOUCR, voucr, 0xfc000000); 11108c2ecf20Sopenharmony_ci 11118c2ecf20Sopenharmony_ci /* Manual register side switching at first */ 11128c2ecf20Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOURCR, 4); 11138c2ecf20Sopenharmony_ci /* Default - fixed HSYNC length, can be made configurable is required */ 11148c2ecf20Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUMSR, 0x800000); 11158c2ecf20Sopenharmony_ci 11168c2ecf20Sopenharmony_ci sh_vou_set_fmt_vid_out(vou_dev, &vou_dev->pix); 11178c2ecf20Sopenharmony_ci 11188c2ecf20Sopenharmony_ci return 0; 11198c2ecf20Sopenharmony_ci} 11208c2ecf20Sopenharmony_ci 11218c2ecf20Sopenharmony_ci/* File operations */ 11228c2ecf20Sopenharmony_cistatic int sh_vou_open(struct file *file) 11238c2ecf20Sopenharmony_ci{ 11248c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 11258c2ecf20Sopenharmony_ci int err; 11268c2ecf20Sopenharmony_ci 11278c2ecf20Sopenharmony_ci if (mutex_lock_interruptible(&vou_dev->fop_lock)) 11288c2ecf20Sopenharmony_ci return -ERESTARTSYS; 11298c2ecf20Sopenharmony_ci 11308c2ecf20Sopenharmony_ci err = v4l2_fh_open(file); 11318c2ecf20Sopenharmony_ci if (err) 11328c2ecf20Sopenharmony_ci goto done_open; 11338c2ecf20Sopenharmony_ci if (v4l2_fh_is_singular_file(file) && 11348c2ecf20Sopenharmony_ci vou_dev->status == SH_VOU_INITIALISING) { 11358c2ecf20Sopenharmony_ci /* First open */ 11368c2ecf20Sopenharmony_ci err = pm_runtime_resume_and_get(vou_dev->v4l2_dev.dev); 11378c2ecf20Sopenharmony_ci if (err < 0) { 11388c2ecf20Sopenharmony_ci v4l2_fh_release(file); 11398c2ecf20Sopenharmony_ci goto done_open; 11408c2ecf20Sopenharmony_ci } 11418c2ecf20Sopenharmony_ci err = sh_vou_hw_init(vou_dev); 11428c2ecf20Sopenharmony_ci if (err < 0) { 11438c2ecf20Sopenharmony_ci pm_runtime_put(vou_dev->v4l2_dev.dev); 11448c2ecf20Sopenharmony_ci v4l2_fh_release(file); 11458c2ecf20Sopenharmony_ci } else { 11468c2ecf20Sopenharmony_ci vou_dev->status = SH_VOU_IDLE; 11478c2ecf20Sopenharmony_ci } 11488c2ecf20Sopenharmony_ci } 11498c2ecf20Sopenharmony_cidone_open: 11508c2ecf20Sopenharmony_ci mutex_unlock(&vou_dev->fop_lock); 11518c2ecf20Sopenharmony_ci return err; 11528c2ecf20Sopenharmony_ci} 11538c2ecf20Sopenharmony_ci 11548c2ecf20Sopenharmony_cistatic int sh_vou_release(struct file *file) 11558c2ecf20Sopenharmony_ci{ 11568c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 11578c2ecf20Sopenharmony_ci bool is_last; 11588c2ecf20Sopenharmony_ci 11598c2ecf20Sopenharmony_ci mutex_lock(&vou_dev->fop_lock); 11608c2ecf20Sopenharmony_ci is_last = v4l2_fh_is_singular_file(file); 11618c2ecf20Sopenharmony_ci _vb2_fop_release(file, NULL); 11628c2ecf20Sopenharmony_ci if (is_last) { 11638c2ecf20Sopenharmony_ci /* Last close */ 11648c2ecf20Sopenharmony_ci vou_dev->status = SH_VOU_INITIALISING; 11658c2ecf20Sopenharmony_ci sh_vou_reg_a_set(vou_dev, VOUER, 0, 0x101); 11668c2ecf20Sopenharmony_ci pm_runtime_put(vou_dev->v4l2_dev.dev); 11678c2ecf20Sopenharmony_ci } 11688c2ecf20Sopenharmony_ci mutex_unlock(&vou_dev->fop_lock); 11698c2ecf20Sopenharmony_ci return 0; 11708c2ecf20Sopenharmony_ci} 11718c2ecf20Sopenharmony_ci 11728c2ecf20Sopenharmony_ci/* sh_vou display ioctl operations */ 11738c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops sh_vou_ioctl_ops = { 11748c2ecf20Sopenharmony_ci .vidioc_querycap = sh_vou_querycap, 11758c2ecf20Sopenharmony_ci .vidioc_enum_fmt_vid_out = sh_vou_enum_fmt_vid_out, 11768c2ecf20Sopenharmony_ci .vidioc_g_fmt_vid_out = sh_vou_g_fmt_vid_out, 11778c2ecf20Sopenharmony_ci .vidioc_s_fmt_vid_out = sh_vou_s_fmt_vid_out, 11788c2ecf20Sopenharmony_ci .vidioc_try_fmt_vid_out = sh_vou_try_fmt_vid_out, 11798c2ecf20Sopenharmony_ci .vidioc_reqbufs = vb2_ioctl_reqbufs, 11808c2ecf20Sopenharmony_ci .vidioc_create_bufs = vb2_ioctl_create_bufs, 11818c2ecf20Sopenharmony_ci .vidioc_querybuf = vb2_ioctl_querybuf, 11828c2ecf20Sopenharmony_ci .vidioc_qbuf = vb2_ioctl_qbuf, 11838c2ecf20Sopenharmony_ci .vidioc_dqbuf = vb2_ioctl_dqbuf, 11848c2ecf20Sopenharmony_ci .vidioc_prepare_buf = vb2_ioctl_prepare_buf, 11858c2ecf20Sopenharmony_ci .vidioc_streamon = vb2_ioctl_streamon, 11868c2ecf20Sopenharmony_ci .vidioc_streamoff = vb2_ioctl_streamoff, 11878c2ecf20Sopenharmony_ci .vidioc_expbuf = vb2_ioctl_expbuf, 11888c2ecf20Sopenharmony_ci .vidioc_g_output = sh_vou_g_output, 11898c2ecf20Sopenharmony_ci .vidioc_s_output = sh_vou_s_output, 11908c2ecf20Sopenharmony_ci .vidioc_enum_output = sh_vou_enum_output, 11918c2ecf20Sopenharmony_ci .vidioc_s_std = sh_vou_s_std, 11928c2ecf20Sopenharmony_ci .vidioc_g_std = sh_vou_g_std, 11938c2ecf20Sopenharmony_ci .vidioc_g_selection = sh_vou_g_selection, 11948c2ecf20Sopenharmony_ci .vidioc_s_selection = sh_vou_s_selection, 11958c2ecf20Sopenharmony_ci .vidioc_log_status = sh_vou_log_status, 11968c2ecf20Sopenharmony_ci}; 11978c2ecf20Sopenharmony_ci 11988c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations sh_vou_fops = { 11998c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 12008c2ecf20Sopenharmony_ci .open = sh_vou_open, 12018c2ecf20Sopenharmony_ci .release = sh_vou_release, 12028c2ecf20Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 12038c2ecf20Sopenharmony_ci .mmap = vb2_fop_mmap, 12048c2ecf20Sopenharmony_ci .poll = vb2_fop_poll, 12058c2ecf20Sopenharmony_ci .write = vb2_fop_write, 12068c2ecf20Sopenharmony_ci}; 12078c2ecf20Sopenharmony_ci 12088c2ecf20Sopenharmony_cistatic const struct video_device sh_vou_video_template = { 12098c2ecf20Sopenharmony_ci .name = "sh_vou", 12108c2ecf20Sopenharmony_ci .fops = &sh_vou_fops, 12118c2ecf20Sopenharmony_ci .ioctl_ops = &sh_vou_ioctl_ops, 12128c2ecf20Sopenharmony_ci .tvnorms = V4L2_STD_525_60, /* PAL only supported in 8-bit non-bt656 mode */ 12138c2ecf20Sopenharmony_ci .vfl_dir = VFL_DIR_TX, 12148c2ecf20Sopenharmony_ci .device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE | 12158c2ecf20Sopenharmony_ci V4L2_CAP_STREAMING, 12168c2ecf20Sopenharmony_ci}; 12178c2ecf20Sopenharmony_ci 12188c2ecf20Sopenharmony_cistatic int sh_vou_probe(struct platform_device *pdev) 12198c2ecf20Sopenharmony_ci{ 12208c2ecf20Sopenharmony_ci struct sh_vou_pdata *vou_pdata = pdev->dev.platform_data; 12218c2ecf20Sopenharmony_ci struct v4l2_rect *rect; 12228c2ecf20Sopenharmony_ci struct v4l2_pix_format *pix; 12238c2ecf20Sopenharmony_ci struct i2c_adapter *i2c_adap; 12248c2ecf20Sopenharmony_ci struct video_device *vdev; 12258c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev; 12268c2ecf20Sopenharmony_ci struct resource *reg_res; 12278c2ecf20Sopenharmony_ci struct v4l2_subdev *subdev; 12288c2ecf20Sopenharmony_ci struct vb2_queue *q; 12298c2ecf20Sopenharmony_ci int irq, ret; 12308c2ecf20Sopenharmony_ci 12318c2ecf20Sopenharmony_ci reg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 12328c2ecf20Sopenharmony_ci irq = platform_get_irq(pdev, 0); 12338c2ecf20Sopenharmony_ci 12348c2ecf20Sopenharmony_ci if (!vou_pdata || !reg_res || irq <= 0) { 12358c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Insufficient VOU platform information.\n"); 12368c2ecf20Sopenharmony_ci return -ENODEV; 12378c2ecf20Sopenharmony_ci } 12388c2ecf20Sopenharmony_ci 12398c2ecf20Sopenharmony_ci vou_dev = devm_kzalloc(&pdev->dev, sizeof(*vou_dev), GFP_KERNEL); 12408c2ecf20Sopenharmony_ci if (!vou_dev) 12418c2ecf20Sopenharmony_ci return -ENOMEM; 12428c2ecf20Sopenharmony_ci 12438c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&vou_dev->buf_list); 12448c2ecf20Sopenharmony_ci spin_lock_init(&vou_dev->lock); 12458c2ecf20Sopenharmony_ci mutex_init(&vou_dev->fop_lock); 12468c2ecf20Sopenharmony_ci vou_dev->pdata = vou_pdata; 12478c2ecf20Sopenharmony_ci vou_dev->status = SH_VOU_INITIALISING; 12488c2ecf20Sopenharmony_ci vou_dev->pix_idx = 1; 12498c2ecf20Sopenharmony_ci 12508c2ecf20Sopenharmony_ci rect = &vou_dev->rect; 12518c2ecf20Sopenharmony_ci pix = &vou_dev->pix; 12528c2ecf20Sopenharmony_ci 12538c2ecf20Sopenharmony_ci /* Fill in defaults */ 12548c2ecf20Sopenharmony_ci vou_dev->std = V4L2_STD_NTSC_M; 12558c2ecf20Sopenharmony_ci rect->left = 0; 12568c2ecf20Sopenharmony_ci rect->top = 0; 12578c2ecf20Sopenharmony_ci rect->width = VOU_MAX_IMAGE_WIDTH; 12588c2ecf20Sopenharmony_ci rect->height = 480; 12598c2ecf20Sopenharmony_ci pix->width = VOU_MAX_IMAGE_WIDTH; 12608c2ecf20Sopenharmony_ci pix->height = 480; 12618c2ecf20Sopenharmony_ci pix->pixelformat = V4L2_PIX_FMT_NV16; 12628c2ecf20Sopenharmony_ci pix->field = V4L2_FIELD_INTERLACED; 12638c2ecf20Sopenharmony_ci pix->bytesperline = VOU_MAX_IMAGE_WIDTH; 12648c2ecf20Sopenharmony_ci pix->sizeimage = VOU_MAX_IMAGE_WIDTH * 2 * 480; 12658c2ecf20Sopenharmony_ci pix->colorspace = V4L2_COLORSPACE_SMPTE170M; 12668c2ecf20Sopenharmony_ci 12678c2ecf20Sopenharmony_ci vou_dev->base = devm_ioremap_resource(&pdev->dev, reg_res); 12688c2ecf20Sopenharmony_ci if (IS_ERR(vou_dev->base)) 12698c2ecf20Sopenharmony_ci return PTR_ERR(vou_dev->base); 12708c2ecf20Sopenharmony_ci 12718c2ecf20Sopenharmony_ci ret = devm_request_irq(&pdev->dev, irq, sh_vou_isr, 0, "vou", vou_dev); 12728c2ecf20Sopenharmony_ci if (ret < 0) 12738c2ecf20Sopenharmony_ci return ret; 12748c2ecf20Sopenharmony_ci 12758c2ecf20Sopenharmony_ci ret = v4l2_device_register(&pdev->dev, &vou_dev->v4l2_dev); 12768c2ecf20Sopenharmony_ci if (ret < 0) { 12778c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Error registering v4l2 device\n"); 12788c2ecf20Sopenharmony_ci return ret; 12798c2ecf20Sopenharmony_ci } 12808c2ecf20Sopenharmony_ci 12818c2ecf20Sopenharmony_ci vdev = &vou_dev->vdev; 12828c2ecf20Sopenharmony_ci *vdev = sh_vou_video_template; 12838c2ecf20Sopenharmony_ci if (vou_pdata->bus_fmt == SH_VOU_BUS_8BIT) 12848c2ecf20Sopenharmony_ci vdev->tvnorms |= V4L2_STD_PAL; 12858c2ecf20Sopenharmony_ci vdev->v4l2_dev = &vou_dev->v4l2_dev; 12868c2ecf20Sopenharmony_ci vdev->release = video_device_release_empty; 12878c2ecf20Sopenharmony_ci vdev->lock = &vou_dev->fop_lock; 12888c2ecf20Sopenharmony_ci 12898c2ecf20Sopenharmony_ci video_set_drvdata(vdev, vou_dev); 12908c2ecf20Sopenharmony_ci 12918c2ecf20Sopenharmony_ci /* Initialize the vb2 queue */ 12928c2ecf20Sopenharmony_ci q = &vou_dev->queue; 12938c2ecf20Sopenharmony_ci q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; 12948c2ecf20Sopenharmony_ci q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_WRITE; 12958c2ecf20Sopenharmony_ci q->drv_priv = vou_dev; 12968c2ecf20Sopenharmony_ci q->buf_struct_size = sizeof(struct sh_vou_buffer); 12978c2ecf20Sopenharmony_ci q->ops = &sh_vou_qops; 12988c2ecf20Sopenharmony_ci q->mem_ops = &vb2_dma_contig_memops; 12998c2ecf20Sopenharmony_ci q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; 13008c2ecf20Sopenharmony_ci q->min_buffers_needed = 2; 13018c2ecf20Sopenharmony_ci q->lock = &vou_dev->fop_lock; 13028c2ecf20Sopenharmony_ci q->dev = &pdev->dev; 13038c2ecf20Sopenharmony_ci ret = vb2_queue_init(q); 13048c2ecf20Sopenharmony_ci if (ret) 13058c2ecf20Sopenharmony_ci goto ei2cgadap; 13068c2ecf20Sopenharmony_ci 13078c2ecf20Sopenharmony_ci vdev->queue = q; 13088c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&vou_dev->buf_list); 13098c2ecf20Sopenharmony_ci 13108c2ecf20Sopenharmony_ci pm_runtime_enable(&pdev->dev); 13118c2ecf20Sopenharmony_ci pm_runtime_resume(&pdev->dev); 13128c2ecf20Sopenharmony_ci 13138c2ecf20Sopenharmony_ci i2c_adap = i2c_get_adapter(vou_pdata->i2c_adap); 13148c2ecf20Sopenharmony_ci if (!i2c_adap) { 13158c2ecf20Sopenharmony_ci ret = -ENODEV; 13168c2ecf20Sopenharmony_ci goto ei2cgadap; 13178c2ecf20Sopenharmony_ci } 13188c2ecf20Sopenharmony_ci 13198c2ecf20Sopenharmony_ci ret = sh_vou_hw_init(vou_dev); 13208c2ecf20Sopenharmony_ci if (ret < 0) 13218c2ecf20Sopenharmony_ci goto ereset; 13228c2ecf20Sopenharmony_ci 13238c2ecf20Sopenharmony_ci subdev = v4l2_i2c_new_subdev_board(&vou_dev->v4l2_dev, i2c_adap, 13248c2ecf20Sopenharmony_ci vou_pdata->board_info, NULL); 13258c2ecf20Sopenharmony_ci if (!subdev) { 13268c2ecf20Sopenharmony_ci ret = -ENOMEM; 13278c2ecf20Sopenharmony_ci goto ei2cnd; 13288c2ecf20Sopenharmony_ci } 13298c2ecf20Sopenharmony_ci 13308c2ecf20Sopenharmony_ci ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); 13318c2ecf20Sopenharmony_ci if (ret < 0) 13328c2ecf20Sopenharmony_ci goto evregdev; 13338c2ecf20Sopenharmony_ci 13348c2ecf20Sopenharmony_ci return 0; 13358c2ecf20Sopenharmony_ci 13368c2ecf20Sopenharmony_cievregdev: 13378c2ecf20Sopenharmony_ciei2cnd: 13388c2ecf20Sopenharmony_ciereset: 13398c2ecf20Sopenharmony_ci i2c_put_adapter(i2c_adap); 13408c2ecf20Sopenharmony_ciei2cgadap: 13418c2ecf20Sopenharmony_ci pm_runtime_disable(&pdev->dev); 13428c2ecf20Sopenharmony_ci v4l2_device_unregister(&vou_dev->v4l2_dev); 13438c2ecf20Sopenharmony_ci return ret; 13448c2ecf20Sopenharmony_ci} 13458c2ecf20Sopenharmony_ci 13468c2ecf20Sopenharmony_cistatic int sh_vou_remove(struct platform_device *pdev) 13478c2ecf20Sopenharmony_ci{ 13488c2ecf20Sopenharmony_ci struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev); 13498c2ecf20Sopenharmony_ci struct sh_vou_device *vou_dev = container_of(v4l2_dev, 13508c2ecf20Sopenharmony_ci struct sh_vou_device, v4l2_dev); 13518c2ecf20Sopenharmony_ci struct v4l2_subdev *sd = list_entry(v4l2_dev->subdevs.next, 13528c2ecf20Sopenharmony_ci struct v4l2_subdev, list); 13538c2ecf20Sopenharmony_ci struct i2c_client *client = v4l2_get_subdevdata(sd); 13548c2ecf20Sopenharmony_ci 13558c2ecf20Sopenharmony_ci pm_runtime_disable(&pdev->dev); 13568c2ecf20Sopenharmony_ci video_unregister_device(&vou_dev->vdev); 13578c2ecf20Sopenharmony_ci i2c_put_adapter(client->adapter); 13588c2ecf20Sopenharmony_ci v4l2_device_unregister(&vou_dev->v4l2_dev); 13598c2ecf20Sopenharmony_ci return 0; 13608c2ecf20Sopenharmony_ci} 13618c2ecf20Sopenharmony_ci 13628c2ecf20Sopenharmony_cistatic struct platform_driver __refdata sh_vou = { 13638c2ecf20Sopenharmony_ci .remove = sh_vou_remove, 13648c2ecf20Sopenharmony_ci .driver = { 13658c2ecf20Sopenharmony_ci .name = "sh-vou", 13668c2ecf20Sopenharmony_ci }, 13678c2ecf20Sopenharmony_ci}; 13688c2ecf20Sopenharmony_ci 13698c2ecf20Sopenharmony_cimodule_platform_driver_probe(sh_vou, sh_vou_probe); 13708c2ecf20Sopenharmony_ci 13718c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SuperH VOU driver"); 13728c2ecf20Sopenharmony_ciMODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); 13738c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 13748c2ecf20Sopenharmony_ciMODULE_VERSION("0.1.0"); 13758c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:sh-vou"); 1376