162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * SuperH Video Output Unit (VOU) driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/dma-mapping.h> 962306a36Sopenharmony_ci#include <linux/delay.h> 1062306a36Sopenharmony_ci#include <linux/errno.h> 1162306a36Sopenharmony_ci#include <linux/fs.h> 1262306a36Sopenharmony_ci#include <linux/i2c.h> 1362306a36Sopenharmony_ci#include <linux/init.h> 1462306a36Sopenharmony_ci#include <linux/interrupt.h> 1562306a36Sopenharmony_ci#include <linux/kernel.h> 1662306a36Sopenharmony_ci#include <linux/platform_device.h> 1762306a36Sopenharmony_ci#include <linux/pm_runtime.h> 1862306a36Sopenharmony_ci#include <linux/slab.h> 1962306a36Sopenharmony_ci#include <linux/videodev2.h> 2062306a36Sopenharmony_ci#include <linux/module.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#include <media/drv-intf/sh_vou.h> 2362306a36Sopenharmony_ci#include <media/v4l2-common.h> 2462306a36Sopenharmony_ci#include <media/v4l2-device.h> 2562306a36Sopenharmony_ci#include <media/v4l2-ioctl.h> 2662306a36Sopenharmony_ci#include <media/v4l2-mediabus.h> 2762306a36Sopenharmony_ci#include <media/videobuf2-v4l2.h> 2862306a36Sopenharmony_ci#include <media/videobuf2-dma-contig.h> 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci/* Mirror addresses are not available for all registers */ 3162306a36Sopenharmony_ci#define VOUER 0 3262306a36Sopenharmony_ci#define VOUCR 4 3362306a36Sopenharmony_ci#define VOUSTR 8 3462306a36Sopenharmony_ci#define VOUVCR 0xc 3562306a36Sopenharmony_ci#define VOUISR 0x10 3662306a36Sopenharmony_ci#define VOUBCR 0x14 3762306a36Sopenharmony_ci#define VOUDPR 0x18 3862306a36Sopenharmony_ci#define VOUDSR 0x1c 3962306a36Sopenharmony_ci#define VOUVPR 0x20 4062306a36Sopenharmony_ci#define VOUIR 0x24 4162306a36Sopenharmony_ci#define VOUSRR 0x28 4262306a36Sopenharmony_ci#define VOUMSR 0x2c 4362306a36Sopenharmony_ci#define VOUHIR 0x30 4462306a36Sopenharmony_ci#define VOUDFR 0x34 4562306a36Sopenharmony_ci#define VOUAD1R 0x38 4662306a36Sopenharmony_ci#define VOUAD2R 0x3c 4762306a36Sopenharmony_ci#define VOUAIR 0x40 4862306a36Sopenharmony_ci#define VOUSWR 0x44 4962306a36Sopenharmony_ci#define VOURCR 0x48 5062306a36Sopenharmony_ci#define VOURPR 0x50 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cienum sh_vou_status { 5362306a36Sopenharmony_ci SH_VOU_IDLE, 5462306a36Sopenharmony_ci SH_VOU_INITIALISING, 5562306a36Sopenharmony_ci SH_VOU_RUNNING, 5662306a36Sopenharmony_ci}; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci#define VOU_MIN_IMAGE_WIDTH 16 5962306a36Sopenharmony_ci#define VOU_MAX_IMAGE_WIDTH 720 6062306a36Sopenharmony_ci#define VOU_MIN_IMAGE_HEIGHT 16 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistruct sh_vou_buffer { 6362306a36Sopenharmony_ci struct vb2_v4l2_buffer vb; 6462306a36Sopenharmony_ci struct list_head list; 6562306a36Sopenharmony_ci}; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic inline struct 6862306a36Sopenharmony_cish_vou_buffer *to_sh_vou_buffer(struct vb2_v4l2_buffer *vb2) 6962306a36Sopenharmony_ci{ 7062306a36Sopenharmony_ci return container_of(vb2, struct sh_vou_buffer, vb); 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistruct sh_vou_device { 7462306a36Sopenharmony_ci struct v4l2_device v4l2_dev; 7562306a36Sopenharmony_ci struct video_device vdev; 7662306a36Sopenharmony_ci struct sh_vou_pdata *pdata; 7762306a36Sopenharmony_ci spinlock_t lock; 7862306a36Sopenharmony_ci void __iomem *base; 7962306a36Sopenharmony_ci /* State information */ 8062306a36Sopenharmony_ci struct v4l2_pix_format pix; 8162306a36Sopenharmony_ci struct v4l2_rect rect; 8262306a36Sopenharmony_ci struct list_head buf_list; 8362306a36Sopenharmony_ci v4l2_std_id std; 8462306a36Sopenharmony_ci int pix_idx; 8562306a36Sopenharmony_ci struct vb2_queue queue; 8662306a36Sopenharmony_ci struct sh_vou_buffer *active; 8762306a36Sopenharmony_ci enum sh_vou_status status; 8862306a36Sopenharmony_ci unsigned sequence; 8962306a36Sopenharmony_ci struct mutex fop_lock; 9062306a36Sopenharmony_ci}; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci/* Register access routines for sides A, B and mirror addresses */ 9362306a36Sopenharmony_cistatic void sh_vou_reg_a_write(struct sh_vou_device *vou_dev, unsigned int reg, 9462306a36Sopenharmony_ci u32 value) 9562306a36Sopenharmony_ci{ 9662306a36Sopenharmony_ci __raw_writel(value, vou_dev->base + reg); 9762306a36Sopenharmony_ci} 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cistatic void sh_vou_reg_ab_write(struct sh_vou_device *vou_dev, unsigned int reg, 10062306a36Sopenharmony_ci u32 value) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci __raw_writel(value, vou_dev->base + reg); 10362306a36Sopenharmony_ci __raw_writel(value, vou_dev->base + reg + 0x1000); 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic void sh_vou_reg_m_write(struct sh_vou_device *vou_dev, unsigned int reg, 10762306a36Sopenharmony_ci u32 value) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci __raw_writel(value, vou_dev->base + reg + 0x2000); 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistatic u32 sh_vou_reg_a_read(struct sh_vou_device *vou_dev, unsigned int reg) 11362306a36Sopenharmony_ci{ 11462306a36Sopenharmony_ci return __raw_readl(vou_dev->base + reg); 11562306a36Sopenharmony_ci} 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_cistatic void sh_vou_reg_a_set(struct sh_vou_device *vou_dev, unsigned int reg, 11862306a36Sopenharmony_ci u32 value, u32 mask) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci u32 old = __raw_readl(vou_dev->base + reg); 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci value = (value & mask) | (old & ~mask); 12362306a36Sopenharmony_ci __raw_writel(value, vou_dev->base + reg); 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cistatic void sh_vou_reg_b_set(struct sh_vou_device *vou_dev, unsigned int reg, 12762306a36Sopenharmony_ci u32 value, u32 mask) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci sh_vou_reg_a_set(vou_dev, reg + 0x1000, value, mask); 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic void sh_vou_reg_ab_set(struct sh_vou_device *vou_dev, unsigned int reg, 13362306a36Sopenharmony_ci u32 value, u32 mask) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci sh_vou_reg_a_set(vou_dev, reg, value, mask); 13662306a36Sopenharmony_ci sh_vou_reg_b_set(vou_dev, reg, value, mask); 13762306a36Sopenharmony_ci} 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_cistruct sh_vou_fmt { 14062306a36Sopenharmony_ci u32 pfmt; 14162306a36Sopenharmony_ci unsigned char bpp; 14262306a36Sopenharmony_ci unsigned char bpl; 14362306a36Sopenharmony_ci unsigned char rgb; 14462306a36Sopenharmony_ci unsigned char yf; 14562306a36Sopenharmony_ci unsigned char pkf; 14662306a36Sopenharmony_ci}; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci/* Further pixel formats can be added */ 14962306a36Sopenharmony_cistatic struct sh_vou_fmt vou_fmt[] = { 15062306a36Sopenharmony_ci { 15162306a36Sopenharmony_ci .pfmt = V4L2_PIX_FMT_NV12, 15262306a36Sopenharmony_ci .bpp = 12, 15362306a36Sopenharmony_ci .bpl = 1, 15462306a36Sopenharmony_ci .yf = 0, 15562306a36Sopenharmony_ci .rgb = 0, 15662306a36Sopenharmony_ci }, 15762306a36Sopenharmony_ci { 15862306a36Sopenharmony_ci .pfmt = V4L2_PIX_FMT_NV16, 15962306a36Sopenharmony_ci .bpp = 16, 16062306a36Sopenharmony_ci .bpl = 1, 16162306a36Sopenharmony_ci .yf = 1, 16262306a36Sopenharmony_ci .rgb = 0, 16362306a36Sopenharmony_ci }, 16462306a36Sopenharmony_ci { 16562306a36Sopenharmony_ci .pfmt = V4L2_PIX_FMT_RGB24, 16662306a36Sopenharmony_ci .bpp = 24, 16762306a36Sopenharmony_ci .bpl = 3, 16862306a36Sopenharmony_ci .pkf = 2, 16962306a36Sopenharmony_ci .rgb = 1, 17062306a36Sopenharmony_ci }, 17162306a36Sopenharmony_ci { 17262306a36Sopenharmony_ci .pfmt = V4L2_PIX_FMT_RGB565, 17362306a36Sopenharmony_ci .bpp = 16, 17462306a36Sopenharmony_ci .bpl = 2, 17562306a36Sopenharmony_ci .pkf = 3, 17662306a36Sopenharmony_ci .rgb = 1, 17762306a36Sopenharmony_ci }, 17862306a36Sopenharmony_ci { 17962306a36Sopenharmony_ci .pfmt = V4L2_PIX_FMT_RGB565X, 18062306a36Sopenharmony_ci .bpp = 16, 18162306a36Sopenharmony_ci .bpl = 2, 18262306a36Sopenharmony_ci .pkf = 3, 18362306a36Sopenharmony_ci .rgb = 1, 18462306a36Sopenharmony_ci }, 18562306a36Sopenharmony_ci}; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cistatic void sh_vou_schedule_next(struct sh_vou_device *vou_dev, 18862306a36Sopenharmony_ci struct vb2_v4l2_buffer *vbuf) 18962306a36Sopenharmony_ci{ 19062306a36Sopenharmony_ci dma_addr_t addr1, addr2; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci addr1 = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); 19362306a36Sopenharmony_ci switch (vou_dev->pix.pixelformat) { 19462306a36Sopenharmony_ci case V4L2_PIX_FMT_NV12: 19562306a36Sopenharmony_ci case V4L2_PIX_FMT_NV16: 19662306a36Sopenharmony_ci addr2 = addr1 + vou_dev->pix.width * vou_dev->pix.height; 19762306a36Sopenharmony_ci break; 19862306a36Sopenharmony_ci default: 19962306a36Sopenharmony_ci addr2 = 0; 20062306a36Sopenharmony_ci } 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci sh_vou_reg_m_write(vou_dev, VOUAD1R, addr1); 20362306a36Sopenharmony_ci sh_vou_reg_m_write(vou_dev, VOUAD2R, addr2); 20462306a36Sopenharmony_ci} 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cistatic void sh_vou_stream_config(struct sh_vou_device *vou_dev) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci unsigned int row_coeff; 20962306a36Sopenharmony_ci#ifdef __LITTLE_ENDIAN 21062306a36Sopenharmony_ci u32 dataswap = 7; 21162306a36Sopenharmony_ci#else 21262306a36Sopenharmony_ci u32 dataswap = 0; 21362306a36Sopenharmony_ci#endif 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci switch (vou_dev->pix.pixelformat) { 21662306a36Sopenharmony_ci default: 21762306a36Sopenharmony_ci case V4L2_PIX_FMT_NV12: 21862306a36Sopenharmony_ci case V4L2_PIX_FMT_NV16: 21962306a36Sopenharmony_ci row_coeff = 1; 22062306a36Sopenharmony_ci break; 22162306a36Sopenharmony_ci case V4L2_PIX_FMT_RGB565: 22262306a36Sopenharmony_ci dataswap ^= 1; 22362306a36Sopenharmony_ci fallthrough; 22462306a36Sopenharmony_ci case V4L2_PIX_FMT_RGB565X: 22562306a36Sopenharmony_ci row_coeff = 2; 22662306a36Sopenharmony_ci break; 22762306a36Sopenharmony_ci case V4L2_PIX_FMT_RGB24: 22862306a36Sopenharmony_ci row_coeff = 3; 22962306a36Sopenharmony_ci break; 23062306a36Sopenharmony_ci } 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOUSWR, dataswap); 23362306a36Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUAIR, vou_dev->pix.width * row_coeff); 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci/* Locking: caller holds fop_lock mutex */ 23762306a36Sopenharmony_cistatic int sh_vou_queue_setup(struct vb2_queue *vq, 23862306a36Sopenharmony_ci unsigned int *nbuffers, unsigned int *nplanes, 23962306a36Sopenharmony_ci unsigned int sizes[], struct device *alloc_devs[]) 24062306a36Sopenharmony_ci{ 24162306a36Sopenharmony_ci struct sh_vou_device *vou_dev = vb2_get_drv_priv(vq); 24262306a36Sopenharmony_ci struct v4l2_pix_format *pix = &vou_dev->pix; 24362306a36Sopenharmony_ci int bytes_per_line = vou_fmt[vou_dev->pix_idx].bpp * pix->width / 8; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci if (*nplanes) 24862306a36Sopenharmony_ci return sizes[0] < pix->height * bytes_per_line ? -EINVAL : 0; 24962306a36Sopenharmony_ci *nplanes = 1; 25062306a36Sopenharmony_ci sizes[0] = pix->height * bytes_per_line; 25162306a36Sopenharmony_ci return 0; 25262306a36Sopenharmony_ci} 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_cistatic int sh_vou_buf_prepare(struct vb2_buffer *vb) 25562306a36Sopenharmony_ci{ 25662306a36Sopenharmony_ci struct sh_vou_device *vou_dev = vb2_get_drv_priv(vb->vb2_queue); 25762306a36Sopenharmony_ci struct v4l2_pix_format *pix = &vou_dev->pix; 25862306a36Sopenharmony_ci unsigned bytes_per_line = vou_fmt[vou_dev->pix_idx].bpp * pix->width / 8; 25962306a36Sopenharmony_ci unsigned size = pix->height * bytes_per_line; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci if (vb2_plane_size(vb, 0) < size) { 26462306a36Sopenharmony_ci /* User buffer too small */ 26562306a36Sopenharmony_ci dev_warn(vou_dev->v4l2_dev.dev, "buffer too small (%lu < %u)\n", 26662306a36Sopenharmony_ci vb2_plane_size(vb, 0), size); 26762306a36Sopenharmony_ci return -EINVAL; 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci vb2_set_plane_payload(vb, 0, size); 27162306a36Sopenharmony_ci return 0; 27262306a36Sopenharmony_ci} 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci/* Locking: caller holds fop_lock mutex and vq->irqlock spinlock */ 27562306a36Sopenharmony_cistatic void sh_vou_buf_queue(struct vb2_buffer *vb) 27662306a36Sopenharmony_ci{ 27762306a36Sopenharmony_ci struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); 27862306a36Sopenharmony_ci struct sh_vou_device *vou_dev = vb2_get_drv_priv(vb->vb2_queue); 27962306a36Sopenharmony_ci struct sh_vou_buffer *shbuf = to_sh_vou_buffer(vbuf); 28062306a36Sopenharmony_ci unsigned long flags; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci spin_lock_irqsave(&vou_dev->lock, flags); 28362306a36Sopenharmony_ci list_add_tail(&shbuf->list, &vou_dev->buf_list); 28462306a36Sopenharmony_ci spin_unlock_irqrestore(&vou_dev->lock, flags); 28562306a36Sopenharmony_ci} 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_cistatic int sh_vou_start_streaming(struct vb2_queue *vq, unsigned int count) 28862306a36Sopenharmony_ci{ 28962306a36Sopenharmony_ci struct sh_vou_device *vou_dev = vb2_get_drv_priv(vq); 29062306a36Sopenharmony_ci struct sh_vou_buffer *buf, *node; 29162306a36Sopenharmony_ci int ret; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci vou_dev->sequence = 0; 29462306a36Sopenharmony_ci ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, 29562306a36Sopenharmony_ci video, s_stream, 1); 29662306a36Sopenharmony_ci if (ret < 0 && ret != -ENOIOCTLCMD) { 29762306a36Sopenharmony_ci list_for_each_entry_safe(buf, node, &vou_dev->buf_list, list) { 29862306a36Sopenharmony_ci vb2_buffer_done(&buf->vb.vb2_buf, 29962306a36Sopenharmony_ci VB2_BUF_STATE_QUEUED); 30062306a36Sopenharmony_ci list_del(&buf->list); 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci vou_dev->active = NULL; 30362306a36Sopenharmony_ci return ret; 30462306a36Sopenharmony_ci } 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci buf = list_entry(vou_dev->buf_list.next, struct sh_vou_buffer, list); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci vou_dev->active = buf; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci /* Start from side A: we use mirror addresses, so, set B */ 31162306a36Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOURPR, 1); 31262306a36Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s: first buffer status 0x%x\n", 31362306a36Sopenharmony_ci __func__, sh_vou_reg_a_read(vou_dev, VOUSTR)); 31462306a36Sopenharmony_ci sh_vou_schedule_next(vou_dev, &buf->vb); 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci buf = list_entry(buf->list.next, struct sh_vou_buffer, list); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci /* Second buffer - initialise register side B */ 31962306a36Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOURPR, 0); 32062306a36Sopenharmony_ci sh_vou_schedule_next(vou_dev, &buf->vb); 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci /* Register side switching with frame VSYNC */ 32362306a36Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOURCR, 5); 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci sh_vou_stream_config(vou_dev); 32662306a36Sopenharmony_ci /* Enable End-of-Frame (VSYNC) interrupts */ 32762306a36Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOUIR, 0x10004); 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci /* Two buffers on the queue - activate the hardware */ 33062306a36Sopenharmony_ci vou_dev->status = SH_VOU_RUNNING; 33162306a36Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOUER, 0x107); 33262306a36Sopenharmony_ci return 0; 33362306a36Sopenharmony_ci} 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_cistatic void sh_vou_stop_streaming(struct vb2_queue *vq) 33662306a36Sopenharmony_ci{ 33762306a36Sopenharmony_ci struct sh_vou_device *vou_dev = vb2_get_drv_priv(vq); 33862306a36Sopenharmony_ci struct sh_vou_buffer *buf, *node; 33962306a36Sopenharmony_ci unsigned long flags; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, 34262306a36Sopenharmony_ci video, s_stream, 0); 34362306a36Sopenharmony_ci /* disable output */ 34462306a36Sopenharmony_ci sh_vou_reg_a_set(vou_dev, VOUER, 0, 1); 34562306a36Sopenharmony_ci /* ...but the current frame will complete */ 34662306a36Sopenharmony_ci sh_vou_reg_a_set(vou_dev, VOUIR, 0, 0x30000); 34762306a36Sopenharmony_ci msleep(50); 34862306a36Sopenharmony_ci spin_lock_irqsave(&vou_dev->lock, flags); 34962306a36Sopenharmony_ci list_for_each_entry_safe(buf, node, &vou_dev->buf_list, list) { 35062306a36Sopenharmony_ci vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); 35162306a36Sopenharmony_ci list_del(&buf->list); 35262306a36Sopenharmony_ci } 35362306a36Sopenharmony_ci vou_dev->active = NULL; 35462306a36Sopenharmony_ci spin_unlock_irqrestore(&vou_dev->lock, flags); 35562306a36Sopenharmony_ci} 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_cistatic const struct vb2_ops sh_vou_qops = { 35862306a36Sopenharmony_ci .queue_setup = sh_vou_queue_setup, 35962306a36Sopenharmony_ci .buf_prepare = sh_vou_buf_prepare, 36062306a36Sopenharmony_ci .buf_queue = sh_vou_buf_queue, 36162306a36Sopenharmony_ci .start_streaming = sh_vou_start_streaming, 36262306a36Sopenharmony_ci .stop_streaming = sh_vou_stop_streaming, 36362306a36Sopenharmony_ci .wait_prepare = vb2_ops_wait_prepare, 36462306a36Sopenharmony_ci .wait_finish = vb2_ops_wait_finish, 36562306a36Sopenharmony_ci}; 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci/* Video IOCTLs */ 36862306a36Sopenharmony_cistatic int sh_vou_querycap(struct file *file, void *priv, 36962306a36Sopenharmony_ci struct v4l2_capability *cap) 37062306a36Sopenharmony_ci{ 37162306a36Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci strscpy(cap->card, "SuperH VOU", sizeof(cap->card)); 37662306a36Sopenharmony_ci strscpy(cap->driver, "sh-vou", sizeof(cap->driver)); 37762306a36Sopenharmony_ci strscpy(cap->bus_info, "platform:sh-vou", sizeof(cap->bus_info)); 37862306a36Sopenharmony_ci return 0; 37962306a36Sopenharmony_ci} 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci/* Enumerate formats, that the device can accept from the user */ 38262306a36Sopenharmony_cistatic int sh_vou_enum_fmt_vid_out(struct file *file, void *priv, 38362306a36Sopenharmony_ci struct v4l2_fmtdesc *fmt) 38462306a36Sopenharmony_ci{ 38562306a36Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci if (fmt->index >= ARRAY_SIZE(vou_fmt)) 38862306a36Sopenharmony_ci return -EINVAL; 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci fmt->pixelformat = vou_fmt[fmt->index].pfmt; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci return 0; 39562306a36Sopenharmony_ci} 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_cistatic int sh_vou_g_fmt_vid_out(struct file *file, void *priv, 39862306a36Sopenharmony_ci struct v4l2_format *fmt) 39962306a36Sopenharmony_ci{ 40062306a36Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci fmt->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; 40562306a36Sopenharmony_ci fmt->fmt.pix = vou_dev->pix; 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci return 0; 40862306a36Sopenharmony_ci} 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_cistatic const unsigned char vou_scale_h_num[] = {1, 9, 2, 9, 4}; 41162306a36Sopenharmony_cistatic const unsigned char vou_scale_h_den[] = {1, 8, 1, 4, 1}; 41262306a36Sopenharmony_cistatic const unsigned char vou_scale_h_fld[] = {0, 2, 1, 3}; 41362306a36Sopenharmony_cistatic const unsigned char vou_scale_v_num[] = {1, 2, 4}; 41462306a36Sopenharmony_cistatic const unsigned char vou_scale_v_den[] = {1, 1, 1}; 41562306a36Sopenharmony_cistatic const unsigned char vou_scale_v_fld[] = {0, 1}; 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_cistatic void sh_vou_configure_geometry(struct sh_vou_device *vou_dev, 41862306a36Sopenharmony_ci int pix_idx, int w_idx, int h_idx) 41962306a36Sopenharmony_ci{ 42062306a36Sopenharmony_ci struct sh_vou_fmt *fmt = vou_fmt + pix_idx; 42162306a36Sopenharmony_ci unsigned int black_left, black_top, width_max, 42262306a36Sopenharmony_ci frame_in_height, frame_out_height, frame_out_top; 42362306a36Sopenharmony_ci struct v4l2_rect *rect = &vou_dev->rect; 42462306a36Sopenharmony_ci struct v4l2_pix_format *pix = &vou_dev->pix; 42562306a36Sopenharmony_ci u32 vouvcr = 0, dsr_h, dsr_v; 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci if (vou_dev->std & V4L2_STD_525_60) { 42862306a36Sopenharmony_ci width_max = 858; 42962306a36Sopenharmony_ci /* height_max = 262; */ 43062306a36Sopenharmony_ci } else { 43162306a36Sopenharmony_ci width_max = 864; 43262306a36Sopenharmony_ci /* height_max = 312; */ 43362306a36Sopenharmony_ci } 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci frame_in_height = pix->height / 2; 43662306a36Sopenharmony_ci frame_out_height = rect->height / 2; 43762306a36Sopenharmony_ci frame_out_top = rect->top / 2; 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci /* 44062306a36Sopenharmony_ci * Cropping scheme: max useful image is 720x480, and the total video 44162306a36Sopenharmony_ci * area is 858x525 (NTSC) or 864x625 (PAL). AK8813 / 8814 starts 44262306a36Sopenharmony_ci * sampling data beginning with fixed 276th (NTSC) / 288th (PAL) clock, 44362306a36Sopenharmony_ci * of which the first 33 / 25 clocks HSYNC must be held active. This 44462306a36Sopenharmony_ci * has to be configured in CR[HW]. 1 pixel equals 2 clock periods. 44562306a36Sopenharmony_ci * This gives CR[HW] = 16 / 12, VPR[HVP] = 138 / 144, which gives 44662306a36Sopenharmony_ci * exactly 858 - 138 = 864 - 144 = 720! We call the out-of-display area, 44762306a36Sopenharmony_ci * beyond DSR, specified on the left and top by the VPR register "black 44862306a36Sopenharmony_ci * pixels" and out-of-image area (DPR) "background pixels." We fix VPR 44962306a36Sopenharmony_ci * at 138 / 144 : 20, because that's the HSYNC timing, that our first 45062306a36Sopenharmony_ci * client requires, and that's exactly what leaves us 720 pixels for the 45162306a36Sopenharmony_ci * image; we leave VPR[VVP] at default 20 for now, because the client 45262306a36Sopenharmony_ci * doesn't seem to have any special requirements for it. Otherwise we 45362306a36Sopenharmony_ci * could also set it to max - 240 = 22 / 72. Thus VPR depends only on 45462306a36Sopenharmony_ci * the selected standard, and DPR and DSR are selected according to 45562306a36Sopenharmony_ci * cropping. Q: how does the client detect the first valid line? Does 45662306a36Sopenharmony_ci * HSYNC stay inactive during invalid (black) lines? 45762306a36Sopenharmony_ci */ 45862306a36Sopenharmony_ci black_left = width_max - VOU_MAX_IMAGE_WIDTH; 45962306a36Sopenharmony_ci black_top = 20; 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci dsr_h = rect->width + rect->left; 46262306a36Sopenharmony_ci dsr_v = frame_out_height + frame_out_top; 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, 46562306a36Sopenharmony_ci "image %ux%u, black %u:%u, offset %u:%u, display %ux%u\n", 46662306a36Sopenharmony_ci pix->width, frame_in_height, black_left, black_top, 46762306a36Sopenharmony_ci rect->left, frame_out_top, dsr_h, dsr_v); 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_ci /* VOUISR height - half of a frame height in frame mode */ 47062306a36Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUISR, (pix->width << 16) | frame_in_height); 47162306a36Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUVPR, (black_left << 16) | black_top); 47262306a36Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUDPR, (rect->left << 16) | frame_out_top); 47362306a36Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUDSR, (dsr_h << 16) | dsr_v); 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci /* 47662306a36Sopenharmony_ci * if necessary, we could set VOUHIR to 47762306a36Sopenharmony_ci * max(black_left + dsr_h, width_max) here 47862306a36Sopenharmony_ci */ 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci if (w_idx) 48162306a36Sopenharmony_ci vouvcr |= (1 << 15) | (vou_scale_h_fld[w_idx - 1] << 4); 48262306a36Sopenharmony_ci if (h_idx) 48362306a36Sopenharmony_ci vouvcr |= (1 << 14) | vou_scale_v_fld[h_idx - 1]; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "0x%08x: scaling 0x%x\n", 48662306a36Sopenharmony_ci fmt->pfmt, vouvcr); 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci /* To produce a colour bar for testing set bit 23 of VOUVCR */ 48962306a36Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUVCR, vouvcr); 49062306a36Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUDFR, 49162306a36Sopenharmony_ci fmt->pkf | (fmt->yf << 8) | (fmt->rgb << 16)); 49262306a36Sopenharmony_ci} 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_cistruct sh_vou_geometry { 49562306a36Sopenharmony_ci struct v4l2_rect output; 49662306a36Sopenharmony_ci unsigned int in_width; 49762306a36Sopenharmony_ci unsigned int in_height; 49862306a36Sopenharmony_ci int scale_idx_h; 49962306a36Sopenharmony_ci int scale_idx_v; 50062306a36Sopenharmony_ci}; 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci/* 50362306a36Sopenharmony_ci * Find input geometry, that we can use to produce output, closest to the 50462306a36Sopenharmony_ci * requested rectangle, using VOU scaling 50562306a36Sopenharmony_ci */ 50662306a36Sopenharmony_cistatic void vou_adjust_input(struct sh_vou_geometry *geo, v4l2_std_id std) 50762306a36Sopenharmony_ci{ 50862306a36Sopenharmony_ci /* The compiler cannot know, that best and idx will indeed be set */ 50962306a36Sopenharmony_ci unsigned int best_err = UINT_MAX, best = 0, img_height_max; 51062306a36Sopenharmony_ci int i, idx = 0; 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci if (std & V4L2_STD_525_60) 51362306a36Sopenharmony_ci img_height_max = 480; 51462306a36Sopenharmony_ci else 51562306a36Sopenharmony_ci img_height_max = 576; 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci /* Image width must be a multiple of 4 */ 51862306a36Sopenharmony_ci v4l_bound_align_image(&geo->in_width, 51962306a36Sopenharmony_ci VOU_MIN_IMAGE_WIDTH, VOU_MAX_IMAGE_WIDTH, 2, 52062306a36Sopenharmony_ci &geo->in_height, 52162306a36Sopenharmony_ci VOU_MIN_IMAGE_HEIGHT, img_height_max, 1, 0); 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci /* Select scales to come as close as possible to the output image */ 52462306a36Sopenharmony_ci for (i = ARRAY_SIZE(vou_scale_h_num) - 1; i >= 0; i--) { 52562306a36Sopenharmony_ci unsigned int err; 52662306a36Sopenharmony_ci unsigned int found = geo->output.width * vou_scale_h_den[i] / 52762306a36Sopenharmony_ci vou_scale_h_num[i]; 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci if (found > VOU_MAX_IMAGE_WIDTH) 53062306a36Sopenharmony_ci /* scales increase */ 53162306a36Sopenharmony_ci break; 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci err = abs(found - geo->in_width); 53462306a36Sopenharmony_ci if (err < best_err) { 53562306a36Sopenharmony_ci best_err = err; 53662306a36Sopenharmony_ci idx = i; 53762306a36Sopenharmony_ci best = found; 53862306a36Sopenharmony_ci } 53962306a36Sopenharmony_ci if (!err) 54062306a36Sopenharmony_ci break; 54162306a36Sopenharmony_ci } 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci geo->in_width = best; 54462306a36Sopenharmony_ci geo->scale_idx_h = idx; 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_ci best_err = UINT_MAX; 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci /* This loop can be replaced with one division */ 54962306a36Sopenharmony_ci for (i = ARRAY_SIZE(vou_scale_v_num) - 1; i >= 0; i--) { 55062306a36Sopenharmony_ci unsigned int err; 55162306a36Sopenharmony_ci unsigned int found = geo->output.height * vou_scale_v_den[i] / 55262306a36Sopenharmony_ci vou_scale_v_num[i]; 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci if (found > img_height_max) 55562306a36Sopenharmony_ci /* scales increase */ 55662306a36Sopenharmony_ci break; 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci err = abs(found - geo->in_height); 55962306a36Sopenharmony_ci if (err < best_err) { 56062306a36Sopenharmony_ci best_err = err; 56162306a36Sopenharmony_ci idx = i; 56262306a36Sopenharmony_ci best = found; 56362306a36Sopenharmony_ci } 56462306a36Sopenharmony_ci if (!err) 56562306a36Sopenharmony_ci break; 56662306a36Sopenharmony_ci } 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci geo->in_height = best; 56962306a36Sopenharmony_ci geo->scale_idx_v = idx; 57062306a36Sopenharmony_ci} 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci/* 57362306a36Sopenharmony_ci * Find output geometry, that we can produce, using VOU scaling, closest to 57462306a36Sopenharmony_ci * the requested rectangle 57562306a36Sopenharmony_ci */ 57662306a36Sopenharmony_cistatic void vou_adjust_output(struct sh_vou_geometry *geo, v4l2_std_id std) 57762306a36Sopenharmony_ci{ 57862306a36Sopenharmony_ci unsigned int best_err = UINT_MAX, best = geo->in_width, 57962306a36Sopenharmony_ci width_max, height_max, img_height_max; 58062306a36Sopenharmony_ci int i, idx_h = 0, idx_v = 0; 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci if (std & V4L2_STD_525_60) { 58362306a36Sopenharmony_ci width_max = 858; 58462306a36Sopenharmony_ci height_max = 262 * 2; 58562306a36Sopenharmony_ci img_height_max = 480; 58662306a36Sopenharmony_ci } else { 58762306a36Sopenharmony_ci width_max = 864; 58862306a36Sopenharmony_ci height_max = 312 * 2; 58962306a36Sopenharmony_ci img_height_max = 576; 59062306a36Sopenharmony_ci } 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci /* Select scales to come as close as possible to the output image */ 59362306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(vou_scale_h_num); i++) { 59462306a36Sopenharmony_ci unsigned int err; 59562306a36Sopenharmony_ci unsigned int found = geo->in_width * vou_scale_h_num[i] / 59662306a36Sopenharmony_ci vou_scale_h_den[i]; 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci if (found > VOU_MAX_IMAGE_WIDTH) 59962306a36Sopenharmony_ci /* scales increase */ 60062306a36Sopenharmony_ci break; 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci err = abs(found - geo->output.width); 60362306a36Sopenharmony_ci if (err < best_err) { 60462306a36Sopenharmony_ci best_err = err; 60562306a36Sopenharmony_ci idx_h = i; 60662306a36Sopenharmony_ci best = found; 60762306a36Sopenharmony_ci } 60862306a36Sopenharmony_ci if (!err) 60962306a36Sopenharmony_ci break; 61062306a36Sopenharmony_ci } 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci geo->output.width = best; 61362306a36Sopenharmony_ci geo->scale_idx_h = idx_h; 61462306a36Sopenharmony_ci if (geo->output.left + best > width_max) 61562306a36Sopenharmony_ci geo->output.left = width_max - best; 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci pr_debug("%s(): W %u * %u/%u = %u\n", __func__, geo->in_width, 61862306a36Sopenharmony_ci vou_scale_h_num[idx_h], vou_scale_h_den[idx_h], best); 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_ci best_err = UINT_MAX; 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci /* This loop can be replaced with one division */ 62362306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(vou_scale_v_num); i++) { 62462306a36Sopenharmony_ci unsigned int err; 62562306a36Sopenharmony_ci unsigned int found = geo->in_height * vou_scale_v_num[i] / 62662306a36Sopenharmony_ci vou_scale_v_den[i]; 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci if (found > img_height_max) 62962306a36Sopenharmony_ci /* scales increase */ 63062306a36Sopenharmony_ci break; 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_ci err = abs(found - geo->output.height); 63362306a36Sopenharmony_ci if (err < best_err) { 63462306a36Sopenharmony_ci best_err = err; 63562306a36Sopenharmony_ci idx_v = i; 63662306a36Sopenharmony_ci best = found; 63762306a36Sopenharmony_ci } 63862306a36Sopenharmony_ci if (!err) 63962306a36Sopenharmony_ci break; 64062306a36Sopenharmony_ci } 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci geo->output.height = best; 64362306a36Sopenharmony_ci geo->scale_idx_v = idx_v; 64462306a36Sopenharmony_ci if (geo->output.top + best > height_max) 64562306a36Sopenharmony_ci geo->output.top = height_max - best; 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_ci pr_debug("%s(): H %u * %u/%u = %u\n", __func__, geo->in_height, 64862306a36Sopenharmony_ci vou_scale_v_num[idx_v], vou_scale_v_den[idx_v], best); 64962306a36Sopenharmony_ci} 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_cistatic int sh_vou_try_fmt_vid_out(struct file *file, void *priv, 65262306a36Sopenharmony_ci struct v4l2_format *fmt) 65362306a36Sopenharmony_ci{ 65462306a36Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 65562306a36Sopenharmony_ci struct v4l2_pix_format *pix = &fmt->fmt.pix; 65662306a36Sopenharmony_ci unsigned int img_height_max; 65762306a36Sopenharmony_ci int pix_idx; 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci pix->field = V4L2_FIELD_INTERLACED; 66262306a36Sopenharmony_ci pix->colorspace = V4L2_COLORSPACE_SMPTE170M; 66362306a36Sopenharmony_ci pix->ycbcr_enc = pix->quantization = 0; 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_ci for (pix_idx = 0; pix_idx < ARRAY_SIZE(vou_fmt); pix_idx++) 66662306a36Sopenharmony_ci if (vou_fmt[pix_idx].pfmt == pix->pixelformat) 66762306a36Sopenharmony_ci break; 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_ci if (pix_idx == ARRAY_SIZE(vou_fmt)) 67062306a36Sopenharmony_ci return -EINVAL; 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci if (vou_dev->std & V4L2_STD_525_60) 67362306a36Sopenharmony_ci img_height_max = 480; 67462306a36Sopenharmony_ci else 67562306a36Sopenharmony_ci img_height_max = 576; 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci v4l_bound_align_image(&pix->width, 67862306a36Sopenharmony_ci VOU_MIN_IMAGE_WIDTH, VOU_MAX_IMAGE_WIDTH, 2, 67962306a36Sopenharmony_ci &pix->height, 68062306a36Sopenharmony_ci VOU_MIN_IMAGE_HEIGHT, img_height_max, 1, 0); 68162306a36Sopenharmony_ci pix->bytesperline = pix->width * vou_fmt[pix_idx].bpl; 68262306a36Sopenharmony_ci pix->sizeimage = pix->height * ((pix->width * vou_fmt[pix_idx].bpp) >> 3); 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci return 0; 68562306a36Sopenharmony_ci} 68662306a36Sopenharmony_ci 68762306a36Sopenharmony_cistatic int sh_vou_set_fmt_vid_out(struct sh_vou_device *vou_dev, 68862306a36Sopenharmony_ci struct v4l2_pix_format *pix) 68962306a36Sopenharmony_ci{ 69062306a36Sopenharmony_ci unsigned int img_height_max; 69162306a36Sopenharmony_ci struct sh_vou_geometry geo; 69262306a36Sopenharmony_ci struct v4l2_subdev_format format = { 69362306a36Sopenharmony_ci .which = V4L2_SUBDEV_FORMAT_ACTIVE, 69462306a36Sopenharmony_ci /* Revisit: is this the correct code? */ 69562306a36Sopenharmony_ci .format.code = MEDIA_BUS_FMT_YUYV8_2X8, 69662306a36Sopenharmony_ci .format.field = V4L2_FIELD_INTERLACED, 69762306a36Sopenharmony_ci .format.colorspace = V4L2_COLORSPACE_SMPTE170M, 69862306a36Sopenharmony_ci }; 69962306a36Sopenharmony_ci struct v4l2_mbus_framefmt *mbfmt = &format.format; 70062306a36Sopenharmony_ci int pix_idx; 70162306a36Sopenharmony_ci int ret; 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci if (vb2_is_busy(&vou_dev->queue)) 70462306a36Sopenharmony_ci return -EBUSY; 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ci for (pix_idx = 0; pix_idx < ARRAY_SIZE(vou_fmt); pix_idx++) 70762306a36Sopenharmony_ci if (vou_fmt[pix_idx].pfmt == pix->pixelformat) 70862306a36Sopenharmony_ci break; 70962306a36Sopenharmony_ci 71062306a36Sopenharmony_ci geo.in_width = pix->width; 71162306a36Sopenharmony_ci geo.in_height = pix->height; 71262306a36Sopenharmony_ci geo.output = vou_dev->rect; 71362306a36Sopenharmony_ci 71462306a36Sopenharmony_ci vou_adjust_output(&geo, vou_dev->std); 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_ci mbfmt->width = geo.output.width; 71762306a36Sopenharmony_ci mbfmt->height = geo.output.height; 71862306a36Sopenharmony_ci ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad, 71962306a36Sopenharmony_ci set_fmt, NULL, &format); 72062306a36Sopenharmony_ci /* Must be implemented, so, don't check for -ENOIOCTLCMD */ 72162306a36Sopenharmony_ci if (ret < 0) 72262306a36Sopenharmony_ci return ret; 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s(): %ux%u -> %ux%u\n", __func__, 72562306a36Sopenharmony_ci geo.output.width, geo.output.height, mbfmt->width, mbfmt->height); 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_ci if (vou_dev->std & V4L2_STD_525_60) 72862306a36Sopenharmony_ci img_height_max = 480; 72962306a36Sopenharmony_ci else 73062306a36Sopenharmony_ci img_height_max = 576; 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci /* Sanity checks */ 73362306a36Sopenharmony_ci if ((unsigned)mbfmt->width > VOU_MAX_IMAGE_WIDTH || 73462306a36Sopenharmony_ci (unsigned)mbfmt->height > img_height_max || 73562306a36Sopenharmony_ci mbfmt->code != MEDIA_BUS_FMT_YUYV8_2X8) 73662306a36Sopenharmony_ci return -EIO; 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci if (mbfmt->width != geo.output.width || 73962306a36Sopenharmony_ci mbfmt->height != geo.output.height) { 74062306a36Sopenharmony_ci geo.output.width = mbfmt->width; 74162306a36Sopenharmony_ci geo.output.height = mbfmt->height; 74262306a36Sopenharmony_ci 74362306a36Sopenharmony_ci vou_adjust_input(&geo, vou_dev->std); 74462306a36Sopenharmony_ci } 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_ci /* We tried to preserve output rectangle, but it could have changed */ 74762306a36Sopenharmony_ci vou_dev->rect = geo.output; 74862306a36Sopenharmony_ci pix->width = geo.in_width; 74962306a36Sopenharmony_ci pix->height = geo.in_height; 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s(): %ux%u\n", __func__, 75262306a36Sopenharmony_ci pix->width, pix->height); 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci vou_dev->pix_idx = pix_idx; 75562306a36Sopenharmony_ci 75662306a36Sopenharmony_ci vou_dev->pix = *pix; 75762306a36Sopenharmony_ci 75862306a36Sopenharmony_ci sh_vou_configure_geometry(vou_dev, pix_idx, 75962306a36Sopenharmony_ci geo.scale_idx_h, geo.scale_idx_v); 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci return 0; 76262306a36Sopenharmony_ci} 76362306a36Sopenharmony_ci 76462306a36Sopenharmony_cistatic int sh_vou_s_fmt_vid_out(struct file *file, void *priv, 76562306a36Sopenharmony_ci struct v4l2_format *fmt) 76662306a36Sopenharmony_ci{ 76762306a36Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 76862306a36Sopenharmony_ci int ret = sh_vou_try_fmt_vid_out(file, priv, fmt); 76962306a36Sopenharmony_ci 77062306a36Sopenharmony_ci if (ret) 77162306a36Sopenharmony_ci return ret; 77262306a36Sopenharmony_ci return sh_vou_set_fmt_vid_out(vou_dev, &fmt->fmt.pix); 77362306a36Sopenharmony_ci} 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_cistatic int sh_vou_enum_output(struct file *file, void *fh, 77662306a36Sopenharmony_ci struct v4l2_output *a) 77762306a36Sopenharmony_ci{ 77862306a36Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 77962306a36Sopenharmony_ci 78062306a36Sopenharmony_ci if (a->index) 78162306a36Sopenharmony_ci return -EINVAL; 78262306a36Sopenharmony_ci strscpy(a->name, "Video Out", sizeof(a->name)); 78362306a36Sopenharmony_ci a->type = V4L2_OUTPUT_TYPE_ANALOG; 78462306a36Sopenharmony_ci a->std = vou_dev->vdev.tvnorms; 78562306a36Sopenharmony_ci return 0; 78662306a36Sopenharmony_ci} 78762306a36Sopenharmony_ci 78862306a36Sopenharmony_cistatic int sh_vou_g_output(struct file *file, void *fh, unsigned int *i) 78962306a36Sopenharmony_ci{ 79062306a36Sopenharmony_ci *i = 0; 79162306a36Sopenharmony_ci return 0; 79262306a36Sopenharmony_ci} 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_cistatic int sh_vou_s_output(struct file *file, void *fh, unsigned int i) 79562306a36Sopenharmony_ci{ 79662306a36Sopenharmony_ci return i ? -EINVAL : 0; 79762306a36Sopenharmony_ci} 79862306a36Sopenharmony_ci 79962306a36Sopenharmony_cistatic u32 sh_vou_ntsc_mode(enum sh_vou_bus_fmt bus_fmt) 80062306a36Sopenharmony_ci{ 80162306a36Sopenharmony_ci switch (bus_fmt) { 80262306a36Sopenharmony_ci default: 80362306a36Sopenharmony_ci pr_warn("%s(): Invalid bus-format code %d, using default 8-bit\n", 80462306a36Sopenharmony_ci __func__, bus_fmt); 80562306a36Sopenharmony_ci fallthrough; 80662306a36Sopenharmony_ci case SH_VOU_BUS_8BIT: 80762306a36Sopenharmony_ci return 1; 80862306a36Sopenharmony_ci case SH_VOU_BUS_16BIT: 80962306a36Sopenharmony_ci return 0; 81062306a36Sopenharmony_ci case SH_VOU_BUS_BT656: 81162306a36Sopenharmony_ci return 3; 81262306a36Sopenharmony_ci } 81362306a36Sopenharmony_ci} 81462306a36Sopenharmony_ci 81562306a36Sopenharmony_cistatic int sh_vou_s_std(struct file *file, void *priv, v4l2_std_id std_id) 81662306a36Sopenharmony_ci{ 81762306a36Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 81862306a36Sopenharmony_ci int ret; 81962306a36Sopenharmony_ci 82062306a36Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s(): 0x%llx\n", __func__, std_id); 82162306a36Sopenharmony_ci 82262306a36Sopenharmony_ci if (std_id == vou_dev->std) 82362306a36Sopenharmony_ci return 0; 82462306a36Sopenharmony_ci 82562306a36Sopenharmony_ci if (vb2_is_busy(&vou_dev->queue)) 82662306a36Sopenharmony_ci return -EBUSY; 82762306a36Sopenharmony_ci 82862306a36Sopenharmony_ci ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, video, 82962306a36Sopenharmony_ci s_std_output, std_id); 83062306a36Sopenharmony_ci /* Shall we continue, if the subdev doesn't support .s_std_output()? */ 83162306a36Sopenharmony_ci if (ret < 0 && ret != -ENOIOCTLCMD) 83262306a36Sopenharmony_ci return ret; 83362306a36Sopenharmony_ci 83462306a36Sopenharmony_ci vou_dev->rect.top = vou_dev->rect.left = 0; 83562306a36Sopenharmony_ci vou_dev->rect.width = VOU_MAX_IMAGE_WIDTH; 83662306a36Sopenharmony_ci if (std_id & V4L2_STD_525_60) { 83762306a36Sopenharmony_ci sh_vou_reg_ab_set(vou_dev, VOUCR, 83862306a36Sopenharmony_ci sh_vou_ntsc_mode(vou_dev->pdata->bus_fmt) << 29, 7 << 29); 83962306a36Sopenharmony_ci vou_dev->rect.height = 480; 84062306a36Sopenharmony_ci } else { 84162306a36Sopenharmony_ci sh_vou_reg_ab_set(vou_dev, VOUCR, 5 << 29, 7 << 29); 84262306a36Sopenharmony_ci vou_dev->rect.height = 576; 84362306a36Sopenharmony_ci } 84462306a36Sopenharmony_ci 84562306a36Sopenharmony_ci vou_dev->pix.width = vou_dev->rect.width; 84662306a36Sopenharmony_ci vou_dev->pix.height = vou_dev->rect.height; 84762306a36Sopenharmony_ci vou_dev->pix.bytesperline = 84862306a36Sopenharmony_ci vou_dev->pix.width * vou_fmt[vou_dev->pix_idx].bpl; 84962306a36Sopenharmony_ci vou_dev->pix.sizeimage = vou_dev->pix.height * 85062306a36Sopenharmony_ci ((vou_dev->pix.width * vou_fmt[vou_dev->pix_idx].bpp) >> 3); 85162306a36Sopenharmony_ci vou_dev->std = std_id; 85262306a36Sopenharmony_ci sh_vou_set_fmt_vid_out(vou_dev, &vou_dev->pix); 85362306a36Sopenharmony_ci 85462306a36Sopenharmony_ci return 0; 85562306a36Sopenharmony_ci} 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_cistatic int sh_vou_g_std(struct file *file, void *priv, v4l2_std_id *std) 85862306a36Sopenharmony_ci{ 85962306a36Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 86062306a36Sopenharmony_ci 86162306a36Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); 86262306a36Sopenharmony_ci 86362306a36Sopenharmony_ci *std = vou_dev->std; 86462306a36Sopenharmony_ci 86562306a36Sopenharmony_ci return 0; 86662306a36Sopenharmony_ci} 86762306a36Sopenharmony_ci 86862306a36Sopenharmony_cistatic int sh_vou_log_status(struct file *file, void *priv) 86962306a36Sopenharmony_ci{ 87062306a36Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 87162306a36Sopenharmony_ci 87262306a36Sopenharmony_ci pr_info("VOUER: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUER)); 87362306a36Sopenharmony_ci pr_info("VOUCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUCR)); 87462306a36Sopenharmony_ci pr_info("VOUSTR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUSTR)); 87562306a36Sopenharmony_ci pr_info("VOUVCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUVCR)); 87662306a36Sopenharmony_ci pr_info("VOUISR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUISR)); 87762306a36Sopenharmony_ci pr_info("VOUBCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUBCR)); 87862306a36Sopenharmony_ci pr_info("VOUDPR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUDPR)); 87962306a36Sopenharmony_ci pr_info("VOUDSR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUDSR)); 88062306a36Sopenharmony_ci pr_info("VOUVPR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUVPR)); 88162306a36Sopenharmony_ci pr_info("VOUIR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUIR)); 88262306a36Sopenharmony_ci pr_info("VOUSRR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUSRR)); 88362306a36Sopenharmony_ci pr_info("VOUMSR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUMSR)); 88462306a36Sopenharmony_ci pr_info("VOUHIR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUHIR)); 88562306a36Sopenharmony_ci pr_info("VOUDFR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUDFR)); 88662306a36Sopenharmony_ci pr_info("VOUAD1R: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUAD1R)); 88762306a36Sopenharmony_ci pr_info("VOUAD2R: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUAD2R)); 88862306a36Sopenharmony_ci pr_info("VOUAIR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUAIR)); 88962306a36Sopenharmony_ci pr_info("VOUSWR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUSWR)); 89062306a36Sopenharmony_ci pr_info("VOURCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOURCR)); 89162306a36Sopenharmony_ci pr_info("VOURPR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOURPR)); 89262306a36Sopenharmony_ci return 0; 89362306a36Sopenharmony_ci} 89462306a36Sopenharmony_ci 89562306a36Sopenharmony_cistatic int sh_vou_g_selection(struct file *file, void *fh, 89662306a36Sopenharmony_ci struct v4l2_selection *sel) 89762306a36Sopenharmony_ci{ 89862306a36Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 89962306a36Sopenharmony_ci 90062306a36Sopenharmony_ci if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) 90162306a36Sopenharmony_ci return -EINVAL; 90262306a36Sopenharmony_ci switch (sel->target) { 90362306a36Sopenharmony_ci case V4L2_SEL_TGT_COMPOSE: 90462306a36Sopenharmony_ci sel->r = vou_dev->rect; 90562306a36Sopenharmony_ci break; 90662306a36Sopenharmony_ci case V4L2_SEL_TGT_COMPOSE_DEFAULT: 90762306a36Sopenharmony_ci case V4L2_SEL_TGT_COMPOSE_BOUNDS: 90862306a36Sopenharmony_ci sel->r.left = 0; 90962306a36Sopenharmony_ci sel->r.top = 0; 91062306a36Sopenharmony_ci sel->r.width = VOU_MAX_IMAGE_WIDTH; 91162306a36Sopenharmony_ci if (vou_dev->std & V4L2_STD_525_60) 91262306a36Sopenharmony_ci sel->r.height = 480; 91362306a36Sopenharmony_ci else 91462306a36Sopenharmony_ci sel->r.height = 576; 91562306a36Sopenharmony_ci break; 91662306a36Sopenharmony_ci default: 91762306a36Sopenharmony_ci return -EINVAL; 91862306a36Sopenharmony_ci } 91962306a36Sopenharmony_ci return 0; 92062306a36Sopenharmony_ci} 92162306a36Sopenharmony_ci 92262306a36Sopenharmony_ci/* Assume a dull encoder, do all the work ourselves. */ 92362306a36Sopenharmony_cistatic int sh_vou_s_selection(struct file *file, void *fh, 92462306a36Sopenharmony_ci struct v4l2_selection *sel) 92562306a36Sopenharmony_ci{ 92662306a36Sopenharmony_ci struct v4l2_rect *rect = &sel->r; 92762306a36Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 92862306a36Sopenharmony_ci struct v4l2_subdev_selection sd_sel = { 92962306a36Sopenharmony_ci .which = V4L2_SUBDEV_FORMAT_ACTIVE, 93062306a36Sopenharmony_ci .target = V4L2_SEL_TGT_COMPOSE, 93162306a36Sopenharmony_ci }; 93262306a36Sopenharmony_ci struct v4l2_pix_format *pix = &vou_dev->pix; 93362306a36Sopenharmony_ci struct sh_vou_geometry geo; 93462306a36Sopenharmony_ci struct v4l2_subdev_format format = { 93562306a36Sopenharmony_ci .which = V4L2_SUBDEV_FORMAT_ACTIVE, 93662306a36Sopenharmony_ci /* Revisit: is this the correct code? */ 93762306a36Sopenharmony_ci .format.code = MEDIA_BUS_FMT_YUYV8_2X8, 93862306a36Sopenharmony_ci .format.field = V4L2_FIELD_INTERLACED, 93962306a36Sopenharmony_ci .format.colorspace = V4L2_COLORSPACE_SMPTE170M, 94062306a36Sopenharmony_ci }; 94162306a36Sopenharmony_ci unsigned int img_height_max; 94262306a36Sopenharmony_ci int ret; 94362306a36Sopenharmony_ci 94462306a36Sopenharmony_ci if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || 94562306a36Sopenharmony_ci sel->target != V4L2_SEL_TGT_COMPOSE) 94662306a36Sopenharmony_ci return -EINVAL; 94762306a36Sopenharmony_ci 94862306a36Sopenharmony_ci if (vb2_is_busy(&vou_dev->queue)) 94962306a36Sopenharmony_ci return -EBUSY; 95062306a36Sopenharmony_ci 95162306a36Sopenharmony_ci if (vou_dev->std & V4L2_STD_525_60) 95262306a36Sopenharmony_ci img_height_max = 480; 95362306a36Sopenharmony_ci else 95462306a36Sopenharmony_ci img_height_max = 576; 95562306a36Sopenharmony_ci 95662306a36Sopenharmony_ci v4l_bound_align_image(&rect->width, 95762306a36Sopenharmony_ci VOU_MIN_IMAGE_WIDTH, VOU_MAX_IMAGE_WIDTH, 1, 95862306a36Sopenharmony_ci &rect->height, 95962306a36Sopenharmony_ci VOU_MIN_IMAGE_HEIGHT, img_height_max, 1, 0); 96062306a36Sopenharmony_ci 96162306a36Sopenharmony_ci if (rect->width + rect->left > VOU_MAX_IMAGE_WIDTH) 96262306a36Sopenharmony_ci rect->left = VOU_MAX_IMAGE_WIDTH - rect->width; 96362306a36Sopenharmony_ci 96462306a36Sopenharmony_ci if (rect->height + rect->top > img_height_max) 96562306a36Sopenharmony_ci rect->top = img_height_max - rect->height; 96662306a36Sopenharmony_ci 96762306a36Sopenharmony_ci geo.output = *rect; 96862306a36Sopenharmony_ci geo.in_width = pix->width; 96962306a36Sopenharmony_ci geo.in_height = pix->height; 97062306a36Sopenharmony_ci 97162306a36Sopenharmony_ci /* Configure the encoder one-to-one, position at 0, ignore errors */ 97262306a36Sopenharmony_ci sd_sel.r.width = geo.output.width; 97362306a36Sopenharmony_ci sd_sel.r.height = geo.output.height; 97462306a36Sopenharmony_ci /* 97562306a36Sopenharmony_ci * We first issue a S_SELECTION, so that the subsequent S_FMT delivers the 97662306a36Sopenharmony_ci * final encoder configuration. 97762306a36Sopenharmony_ci */ 97862306a36Sopenharmony_ci v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad, 97962306a36Sopenharmony_ci set_selection, NULL, &sd_sel); 98062306a36Sopenharmony_ci format.format.width = geo.output.width; 98162306a36Sopenharmony_ci format.format.height = geo.output.height; 98262306a36Sopenharmony_ci ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad, 98362306a36Sopenharmony_ci set_fmt, NULL, &format); 98462306a36Sopenharmony_ci /* Must be implemented, so, don't check for -ENOIOCTLCMD */ 98562306a36Sopenharmony_ci if (ret < 0) 98662306a36Sopenharmony_ci return ret; 98762306a36Sopenharmony_ci 98862306a36Sopenharmony_ci /* Sanity checks */ 98962306a36Sopenharmony_ci if ((unsigned)format.format.width > VOU_MAX_IMAGE_WIDTH || 99062306a36Sopenharmony_ci (unsigned)format.format.height > img_height_max || 99162306a36Sopenharmony_ci format.format.code != MEDIA_BUS_FMT_YUYV8_2X8) 99262306a36Sopenharmony_ci return -EIO; 99362306a36Sopenharmony_ci 99462306a36Sopenharmony_ci geo.output.width = format.format.width; 99562306a36Sopenharmony_ci geo.output.height = format.format.height; 99662306a36Sopenharmony_ci 99762306a36Sopenharmony_ci /* 99862306a36Sopenharmony_ci * No down-scaling. According to the API, current call has precedence: 99962306a36Sopenharmony_ci * https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/crop.html#cropping-structures 100062306a36Sopenharmony_ci */ 100162306a36Sopenharmony_ci vou_adjust_input(&geo, vou_dev->std); 100262306a36Sopenharmony_ci 100362306a36Sopenharmony_ci /* We tried to preserve output rectangle, but it could have changed */ 100462306a36Sopenharmony_ci vou_dev->rect = geo.output; 100562306a36Sopenharmony_ci pix->width = geo.in_width; 100662306a36Sopenharmony_ci pix->height = geo.in_height; 100762306a36Sopenharmony_ci 100862306a36Sopenharmony_ci sh_vou_configure_geometry(vou_dev, vou_dev->pix_idx, 100962306a36Sopenharmony_ci geo.scale_idx_h, geo.scale_idx_v); 101062306a36Sopenharmony_ci 101162306a36Sopenharmony_ci return 0; 101262306a36Sopenharmony_ci} 101362306a36Sopenharmony_ci 101462306a36Sopenharmony_cistatic irqreturn_t sh_vou_isr(int irq, void *dev_id) 101562306a36Sopenharmony_ci{ 101662306a36Sopenharmony_ci struct sh_vou_device *vou_dev = dev_id; 101762306a36Sopenharmony_ci static unsigned long j; 101862306a36Sopenharmony_ci struct sh_vou_buffer *vb; 101962306a36Sopenharmony_ci static int cnt; 102062306a36Sopenharmony_ci u32 irq_status = sh_vou_reg_a_read(vou_dev, VOUIR), masked; 102162306a36Sopenharmony_ci u32 vou_status = sh_vou_reg_a_read(vou_dev, VOUSTR); 102262306a36Sopenharmony_ci 102362306a36Sopenharmony_ci if (!(irq_status & 0x300)) { 102462306a36Sopenharmony_ci if (printk_timed_ratelimit(&j, 500)) 102562306a36Sopenharmony_ci dev_warn(vou_dev->v4l2_dev.dev, "IRQ status 0x%x!\n", 102662306a36Sopenharmony_ci irq_status); 102762306a36Sopenharmony_ci return IRQ_NONE; 102862306a36Sopenharmony_ci } 102962306a36Sopenharmony_ci 103062306a36Sopenharmony_ci spin_lock(&vou_dev->lock); 103162306a36Sopenharmony_ci if (!vou_dev->active || list_empty(&vou_dev->buf_list)) { 103262306a36Sopenharmony_ci if (printk_timed_ratelimit(&j, 500)) 103362306a36Sopenharmony_ci dev_warn(vou_dev->v4l2_dev.dev, 103462306a36Sopenharmony_ci "IRQ without active buffer: %x!\n", irq_status); 103562306a36Sopenharmony_ci /* Just ack: buf_release will disable further interrupts */ 103662306a36Sopenharmony_ci sh_vou_reg_a_set(vou_dev, VOUIR, 0, 0x300); 103762306a36Sopenharmony_ci spin_unlock(&vou_dev->lock); 103862306a36Sopenharmony_ci return IRQ_HANDLED; 103962306a36Sopenharmony_ci } 104062306a36Sopenharmony_ci 104162306a36Sopenharmony_ci masked = ~(0x300 & irq_status) & irq_status & 0x30304; 104262306a36Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, 104362306a36Sopenharmony_ci "IRQ status 0x%x -> 0x%x, VOU status 0x%x, cnt %d\n", 104462306a36Sopenharmony_ci irq_status, masked, vou_status, cnt); 104562306a36Sopenharmony_ci 104662306a36Sopenharmony_ci cnt++; 104762306a36Sopenharmony_ci /* side = vou_status & 0x10000; */ 104862306a36Sopenharmony_ci 104962306a36Sopenharmony_ci /* Clear only set interrupts */ 105062306a36Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOUIR, masked); 105162306a36Sopenharmony_ci 105262306a36Sopenharmony_ci vb = vou_dev->active; 105362306a36Sopenharmony_ci if (list_is_singular(&vb->list)) { 105462306a36Sopenharmony_ci /* Keep cycling while no next buffer is available */ 105562306a36Sopenharmony_ci sh_vou_schedule_next(vou_dev, &vb->vb); 105662306a36Sopenharmony_ci spin_unlock(&vou_dev->lock); 105762306a36Sopenharmony_ci return IRQ_HANDLED; 105862306a36Sopenharmony_ci } 105962306a36Sopenharmony_ci 106062306a36Sopenharmony_ci list_del(&vb->list); 106162306a36Sopenharmony_ci 106262306a36Sopenharmony_ci vb->vb.vb2_buf.timestamp = ktime_get_ns(); 106362306a36Sopenharmony_ci vb->vb.sequence = vou_dev->sequence++; 106462306a36Sopenharmony_ci vb->vb.field = V4L2_FIELD_INTERLACED; 106562306a36Sopenharmony_ci vb2_buffer_done(&vb->vb.vb2_buf, VB2_BUF_STATE_DONE); 106662306a36Sopenharmony_ci 106762306a36Sopenharmony_ci vou_dev->active = list_entry(vou_dev->buf_list.next, 106862306a36Sopenharmony_ci struct sh_vou_buffer, list); 106962306a36Sopenharmony_ci 107062306a36Sopenharmony_ci if (list_is_singular(&vou_dev->buf_list)) { 107162306a36Sopenharmony_ci /* Keep cycling while no next buffer is available */ 107262306a36Sopenharmony_ci sh_vou_schedule_next(vou_dev, &vou_dev->active->vb); 107362306a36Sopenharmony_ci } else { 107462306a36Sopenharmony_ci struct sh_vou_buffer *new = list_entry(vou_dev->active->list.next, 107562306a36Sopenharmony_ci struct sh_vou_buffer, list); 107662306a36Sopenharmony_ci sh_vou_schedule_next(vou_dev, &new->vb); 107762306a36Sopenharmony_ci } 107862306a36Sopenharmony_ci 107962306a36Sopenharmony_ci spin_unlock(&vou_dev->lock); 108062306a36Sopenharmony_ci 108162306a36Sopenharmony_ci return IRQ_HANDLED; 108262306a36Sopenharmony_ci} 108362306a36Sopenharmony_ci 108462306a36Sopenharmony_cistatic int sh_vou_hw_init(struct sh_vou_device *vou_dev) 108562306a36Sopenharmony_ci{ 108662306a36Sopenharmony_ci struct sh_vou_pdata *pdata = vou_dev->pdata; 108762306a36Sopenharmony_ci u32 voucr = sh_vou_ntsc_mode(pdata->bus_fmt) << 29; 108862306a36Sopenharmony_ci int i = 100; 108962306a36Sopenharmony_ci 109062306a36Sopenharmony_ci /* Disable all IRQs */ 109162306a36Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOUIR, 0); 109262306a36Sopenharmony_ci 109362306a36Sopenharmony_ci /* Reset VOU interfaces - registers unaffected */ 109462306a36Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOUSRR, 0x101); 109562306a36Sopenharmony_ci while (--i && (sh_vou_reg_a_read(vou_dev, VOUSRR) & 0x101)) 109662306a36Sopenharmony_ci udelay(1); 109762306a36Sopenharmony_ci 109862306a36Sopenharmony_ci if (!i) 109962306a36Sopenharmony_ci return -ETIMEDOUT; 110062306a36Sopenharmony_ci 110162306a36Sopenharmony_ci dev_dbg(vou_dev->v4l2_dev.dev, "Reset took %dus\n", 100 - i); 110262306a36Sopenharmony_ci 110362306a36Sopenharmony_ci if (pdata->flags & SH_VOU_PCLK_FALLING) 110462306a36Sopenharmony_ci voucr |= 1 << 28; 110562306a36Sopenharmony_ci if (pdata->flags & SH_VOU_HSYNC_LOW) 110662306a36Sopenharmony_ci voucr |= 1 << 27; 110762306a36Sopenharmony_ci if (pdata->flags & SH_VOU_VSYNC_LOW) 110862306a36Sopenharmony_ci voucr |= 1 << 26; 110962306a36Sopenharmony_ci sh_vou_reg_ab_set(vou_dev, VOUCR, voucr, 0xfc000000); 111062306a36Sopenharmony_ci 111162306a36Sopenharmony_ci /* Manual register side switching at first */ 111262306a36Sopenharmony_ci sh_vou_reg_a_write(vou_dev, VOURCR, 4); 111362306a36Sopenharmony_ci /* Default - fixed HSYNC length, can be made configurable is required */ 111462306a36Sopenharmony_ci sh_vou_reg_ab_write(vou_dev, VOUMSR, 0x800000); 111562306a36Sopenharmony_ci 111662306a36Sopenharmony_ci sh_vou_set_fmt_vid_out(vou_dev, &vou_dev->pix); 111762306a36Sopenharmony_ci 111862306a36Sopenharmony_ci return 0; 111962306a36Sopenharmony_ci} 112062306a36Sopenharmony_ci 112162306a36Sopenharmony_ci/* File operations */ 112262306a36Sopenharmony_cistatic int sh_vou_open(struct file *file) 112362306a36Sopenharmony_ci{ 112462306a36Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 112562306a36Sopenharmony_ci int err; 112662306a36Sopenharmony_ci 112762306a36Sopenharmony_ci if (mutex_lock_interruptible(&vou_dev->fop_lock)) 112862306a36Sopenharmony_ci return -ERESTARTSYS; 112962306a36Sopenharmony_ci 113062306a36Sopenharmony_ci err = v4l2_fh_open(file); 113162306a36Sopenharmony_ci if (err) 113262306a36Sopenharmony_ci goto done_open; 113362306a36Sopenharmony_ci if (v4l2_fh_is_singular_file(file) && 113462306a36Sopenharmony_ci vou_dev->status == SH_VOU_INITIALISING) { 113562306a36Sopenharmony_ci /* First open */ 113662306a36Sopenharmony_ci err = pm_runtime_resume_and_get(vou_dev->v4l2_dev.dev); 113762306a36Sopenharmony_ci if (err < 0) { 113862306a36Sopenharmony_ci v4l2_fh_release(file); 113962306a36Sopenharmony_ci goto done_open; 114062306a36Sopenharmony_ci } 114162306a36Sopenharmony_ci err = sh_vou_hw_init(vou_dev); 114262306a36Sopenharmony_ci if (err < 0) { 114362306a36Sopenharmony_ci pm_runtime_put(vou_dev->v4l2_dev.dev); 114462306a36Sopenharmony_ci v4l2_fh_release(file); 114562306a36Sopenharmony_ci } else { 114662306a36Sopenharmony_ci vou_dev->status = SH_VOU_IDLE; 114762306a36Sopenharmony_ci } 114862306a36Sopenharmony_ci } 114962306a36Sopenharmony_cidone_open: 115062306a36Sopenharmony_ci mutex_unlock(&vou_dev->fop_lock); 115162306a36Sopenharmony_ci return err; 115262306a36Sopenharmony_ci} 115362306a36Sopenharmony_ci 115462306a36Sopenharmony_cistatic int sh_vou_release(struct file *file) 115562306a36Sopenharmony_ci{ 115662306a36Sopenharmony_ci struct sh_vou_device *vou_dev = video_drvdata(file); 115762306a36Sopenharmony_ci bool is_last; 115862306a36Sopenharmony_ci 115962306a36Sopenharmony_ci mutex_lock(&vou_dev->fop_lock); 116062306a36Sopenharmony_ci is_last = v4l2_fh_is_singular_file(file); 116162306a36Sopenharmony_ci _vb2_fop_release(file, NULL); 116262306a36Sopenharmony_ci if (is_last) { 116362306a36Sopenharmony_ci /* Last close */ 116462306a36Sopenharmony_ci vou_dev->status = SH_VOU_INITIALISING; 116562306a36Sopenharmony_ci sh_vou_reg_a_set(vou_dev, VOUER, 0, 0x101); 116662306a36Sopenharmony_ci pm_runtime_put(vou_dev->v4l2_dev.dev); 116762306a36Sopenharmony_ci } 116862306a36Sopenharmony_ci mutex_unlock(&vou_dev->fop_lock); 116962306a36Sopenharmony_ci return 0; 117062306a36Sopenharmony_ci} 117162306a36Sopenharmony_ci 117262306a36Sopenharmony_ci/* sh_vou display ioctl operations */ 117362306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops sh_vou_ioctl_ops = { 117462306a36Sopenharmony_ci .vidioc_querycap = sh_vou_querycap, 117562306a36Sopenharmony_ci .vidioc_enum_fmt_vid_out = sh_vou_enum_fmt_vid_out, 117662306a36Sopenharmony_ci .vidioc_g_fmt_vid_out = sh_vou_g_fmt_vid_out, 117762306a36Sopenharmony_ci .vidioc_s_fmt_vid_out = sh_vou_s_fmt_vid_out, 117862306a36Sopenharmony_ci .vidioc_try_fmt_vid_out = sh_vou_try_fmt_vid_out, 117962306a36Sopenharmony_ci .vidioc_reqbufs = vb2_ioctl_reqbufs, 118062306a36Sopenharmony_ci .vidioc_create_bufs = vb2_ioctl_create_bufs, 118162306a36Sopenharmony_ci .vidioc_querybuf = vb2_ioctl_querybuf, 118262306a36Sopenharmony_ci .vidioc_qbuf = vb2_ioctl_qbuf, 118362306a36Sopenharmony_ci .vidioc_dqbuf = vb2_ioctl_dqbuf, 118462306a36Sopenharmony_ci .vidioc_prepare_buf = vb2_ioctl_prepare_buf, 118562306a36Sopenharmony_ci .vidioc_streamon = vb2_ioctl_streamon, 118662306a36Sopenharmony_ci .vidioc_streamoff = vb2_ioctl_streamoff, 118762306a36Sopenharmony_ci .vidioc_expbuf = vb2_ioctl_expbuf, 118862306a36Sopenharmony_ci .vidioc_g_output = sh_vou_g_output, 118962306a36Sopenharmony_ci .vidioc_s_output = sh_vou_s_output, 119062306a36Sopenharmony_ci .vidioc_enum_output = sh_vou_enum_output, 119162306a36Sopenharmony_ci .vidioc_s_std = sh_vou_s_std, 119262306a36Sopenharmony_ci .vidioc_g_std = sh_vou_g_std, 119362306a36Sopenharmony_ci .vidioc_g_selection = sh_vou_g_selection, 119462306a36Sopenharmony_ci .vidioc_s_selection = sh_vou_s_selection, 119562306a36Sopenharmony_ci .vidioc_log_status = sh_vou_log_status, 119662306a36Sopenharmony_ci}; 119762306a36Sopenharmony_ci 119862306a36Sopenharmony_cistatic const struct v4l2_file_operations sh_vou_fops = { 119962306a36Sopenharmony_ci .owner = THIS_MODULE, 120062306a36Sopenharmony_ci .open = sh_vou_open, 120162306a36Sopenharmony_ci .release = sh_vou_release, 120262306a36Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 120362306a36Sopenharmony_ci .mmap = vb2_fop_mmap, 120462306a36Sopenharmony_ci .poll = vb2_fop_poll, 120562306a36Sopenharmony_ci .write = vb2_fop_write, 120662306a36Sopenharmony_ci}; 120762306a36Sopenharmony_ci 120862306a36Sopenharmony_cistatic const struct video_device sh_vou_video_template = { 120962306a36Sopenharmony_ci .name = "sh_vou", 121062306a36Sopenharmony_ci .fops = &sh_vou_fops, 121162306a36Sopenharmony_ci .ioctl_ops = &sh_vou_ioctl_ops, 121262306a36Sopenharmony_ci .tvnorms = V4L2_STD_525_60, /* PAL only supported in 8-bit non-bt656 mode */ 121362306a36Sopenharmony_ci .vfl_dir = VFL_DIR_TX, 121462306a36Sopenharmony_ci .device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE | 121562306a36Sopenharmony_ci V4L2_CAP_STREAMING, 121662306a36Sopenharmony_ci}; 121762306a36Sopenharmony_ci 121862306a36Sopenharmony_cistatic int sh_vou_probe(struct platform_device *pdev) 121962306a36Sopenharmony_ci{ 122062306a36Sopenharmony_ci struct sh_vou_pdata *vou_pdata = pdev->dev.platform_data; 122162306a36Sopenharmony_ci struct v4l2_rect *rect; 122262306a36Sopenharmony_ci struct v4l2_pix_format *pix; 122362306a36Sopenharmony_ci struct i2c_adapter *i2c_adap; 122462306a36Sopenharmony_ci struct video_device *vdev; 122562306a36Sopenharmony_ci struct sh_vou_device *vou_dev; 122662306a36Sopenharmony_ci struct v4l2_subdev *subdev; 122762306a36Sopenharmony_ci struct vb2_queue *q; 122862306a36Sopenharmony_ci int irq, ret; 122962306a36Sopenharmony_ci 123062306a36Sopenharmony_ci if (!vou_pdata) { 123162306a36Sopenharmony_ci dev_err(&pdev->dev, "Insufficient VOU platform information.\n"); 123262306a36Sopenharmony_ci return -ENODEV; 123362306a36Sopenharmony_ci } 123462306a36Sopenharmony_ci 123562306a36Sopenharmony_ci irq = platform_get_irq(pdev, 0); 123662306a36Sopenharmony_ci if (irq < 0) 123762306a36Sopenharmony_ci return irq; 123862306a36Sopenharmony_ci 123962306a36Sopenharmony_ci vou_dev = devm_kzalloc(&pdev->dev, sizeof(*vou_dev), GFP_KERNEL); 124062306a36Sopenharmony_ci if (!vou_dev) 124162306a36Sopenharmony_ci return -ENOMEM; 124262306a36Sopenharmony_ci 124362306a36Sopenharmony_ci INIT_LIST_HEAD(&vou_dev->buf_list); 124462306a36Sopenharmony_ci spin_lock_init(&vou_dev->lock); 124562306a36Sopenharmony_ci mutex_init(&vou_dev->fop_lock); 124662306a36Sopenharmony_ci vou_dev->pdata = vou_pdata; 124762306a36Sopenharmony_ci vou_dev->status = SH_VOU_INITIALISING; 124862306a36Sopenharmony_ci vou_dev->pix_idx = 1; 124962306a36Sopenharmony_ci 125062306a36Sopenharmony_ci rect = &vou_dev->rect; 125162306a36Sopenharmony_ci pix = &vou_dev->pix; 125262306a36Sopenharmony_ci 125362306a36Sopenharmony_ci /* Fill in defaults */ 125462306a36Sopenharmony_ci vou_dev->std = V4L2_STD_NTSC_M; 125562306a36Sopenharmony_ci rect->left = 0; 125662306a36Sopenharmony_ci rect->top = 0; 125762306a36Sopenharmony_ci rect->width = VOU_MAX_IMAGE_WIDTH; 125862306a36Sopenharmony_ci rect->height = 480; 125962306a36Sopenharmony_ci pix->width = VOU_MAX_IMAGE_WIDTH; 126062306a36Sopenharmony_ci pix->height = 480; 126162306a36Sopenharmony_ci pix->pixelformat = V4L2_PIX_FMT_NV16; 126262306a36Sopenharmony_ci pix->field = V4L2_FIELD_INTERLACED; 126362306a36Sopenharmony_ci pix->bytesperline = VOU_MAX_IMAGE_WIDTH; 126462306a36Sopenharmony_ci pix->sizeimage = VOU_MAX_IMAGE_WIDTH * 2 * 480; 126562306a36Sopenharmony_ci pix->colorspace = V4L2_COLORSPACE_SMPTE170M; 126662306a36Sopenharmony_ci 126762306a36Sopenharmony_ci vou_dev->base = devm_platform_ioremap_resource(pdev, 0); 126862306a36Sopenharmony_ci if (IS_ERR(vou_dev->base)) 126962306a36Sopenharmony_ci return PTR_ERR(vou_dev->base); 127062306a36Sopenharmony_ci 127162306a36Sopenharmony_ci ret = devm_request_irq(&pdev->dev, irq, sh_vou_isr, 0, "vou", vou_dev); 127262306a36Sopenharmony_ci if (ret < 0) 127362306a36Sopenharmony_ci return ret; 127462306a36Sopenharmony_ci 127562306a36Sopenharmony_ci ret = v4l2_device_register(&pdev->dev, &vou_dev->v4l2_dev); 127662306a36Sopenharmony_ci if (ret < 0) { 127762306a36Sopenharmony_ci dev_err(&pdev->dev, "Error registering v4l2 device\n"); 127862306a36Sopenharmony_ci return ret; 127962306a36Sopenharmony_ci } 128062306a36Sopenharmony_ci 128162306a36Sopenharmony_ci vdev = &vou_dev->vdev; 128262306a36Sopenharmony_ci *vdev = sh_vou_video_template; 128362306a36Sopenharmony_ci if (vou_pdata->bus_fmt == SH_VOU_BUS_8BIT) 128462306a36Sopenharmony_ci vdev->tvnorms |= V4L2_STD_PAL; 128562306a36Sopenharmony_ci vdev->v4l2_dev = &vou_dev->v4l2_dev; 128662306a36Sopenharmony_ci vdev->release = video_device_release_empty; 128762306a36Sopenharmony_ci vdev->lock = &vou_dev->fop_lock; 128862306a36Sopenharmony_ci 128962306a36Sopenharmony_ci video_set_drvdata(vdev, vou_dev); 129062306a36Sopenharmony_ci 129162306a36Sopenharmony_ci /* Initialize the vb2 queue */ 129262306a36Sopenharmony_ci q = &vou_dev->queue; 129362306a36Sopenharmony_ci q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; 129462306a36Sopenharmony_ci q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_WRITE; 129562306a36Sopenharmony_ci q->drv_priv = vou_dev; 129662306a36Sopenharmony_ci q->buf_struct_size = sizeof(struct sh_vou_buffer); 129762306a36Sopenharmony_ci q->ops = &sh_vou_qops; 129862306a36Sopenharmony_ci q->mem_ops = &vb2_dma_contig_memops; 129962306a36Sopenharmony_ci q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; 130062306a36Sopenharmony_ci q->min_buffers_needed = 2; 130162306a36Sopenharmony_ci q->lock = &vou_dev->fop_lock; 130262306a36Sopenharmony_ci q->dev = &pdev->dev; 130362306a36Sopenharmony_ci ret = vb2_queue_init(q); 130462306a36Sopenharmony_ci if (ret) 130562306a36Sopenharmony_ci goto ei2cgadap; 130662306a36Sopenharmony_ci 130762306a36Sopenharmony_ci vdev->queue = q; 130862306a36Sopenharmony_ci INIT_LIST_HEAD(&vou_dev->buf_list); 130962306a36Sopenharmony_ci 131062306a36Sopenharmony_ci pm_runtime_enable(&pdev->dev); 131162306a36Sopenharmony_ci pm_runtime_resume(&pdev->dev); 131262306a36Sopenharmony_ci 131362306a36Sopenharmony_ci i2c_adap = i2c_get_adapter(vou_pdata->i2c_adap); 131462306a36Sopenharmony_ci if (!i2c_adap) { 131562306a36Sopenharmony_ci ret = -ENODEV; 131662306a36Sopenharmony_ci goto ei2cgadap; 131762306a36Sopenharmony_ci } 131862306a36Sopenharmony_ci 131962306a36Sopenharmony_ci ret = sh_vou_hw_init(vou_dev); 132062306a36Sopenharmony_ci if (ret < 0) 132162306a36Sopenharmony_ci goto ereset; 132262306a36Sopenharmony_ci 132362306a36Sopenharmony_ci subdev = v4l2_i2c_new_subdev_board(&vou_dev->v4l2_dev, i2c_adap, 132462306a36Sopenharmony_ci vou_pdata->board_info, NULL); 132562306a36Sopenharmony_ci if (!subdev) { 132662306a36Sopenharmony_ci ret = -ENOMEM; 132762306a36Sopenharmony_ci goto ei2cnd; 132862306a36Sopenharmony_ci } 132962306a36Sopenharmony_ci 133062306a36Sopenharmony_ci ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); 133162306a36Sopenharmony_ci if (ret < 0) 133262306a36Sopenharmony_ci goto evregdev; 133362306a36Sopenharmony_ci 133462306a36Sopenharmony_ci return 0; 133562306a36Sopenharmony_ci 133662306a36Sopenharmony_cievregdev: 133762306a36Sopenharmony_ciei2cnd: 133862306a36Sopenharmony_ciereset: 133962306a36Sopenharmony_ci i2c_put_adapter(i2c_adap); 134062306a36Sopenharmony_ciei2cgadap: 134162306a36Sopenharmony_ci pm_runtime_disable(&pdev->dev); 134262306a36Sopenharmony_ci v4l2_device_unregister(&vou_dev->v4l2_dev); 134362306a36Sopenharmony_ci return ret; 134462306a36Sopenharmony_ci} 134562306a36Sopenharmony_ci 134662306a36Sopenharmony_cistatic void sh_vou_remove(struct platform_device *pdev) 134762306a36Sopenharmony_ci{ 134862306a36Sopenharmony_ci struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev); 134962306a36Sopenharmony_ci struct sh_vou_device *vou_dev = container_of(v4l2_dev, 135062306a36Sopenharmony_ci struct sh_vou_device, v4l2_dev); 135162306a36Sopenharmony_ci struct v4l2_subdev *sd = list_entry(v4l2_dev->subdevs.next, 135262306a36Sopenharmony_ci struct v4l2_subdev, list); 135362306a36Sopenharmony_ci struct i2c_client *client = v4l2_get_subdevdata(sd); 135462306a36Sopenharmony_ci 135562306a36Sopenharmony_ci pm_runtime_disable(&pdev->dev); 135662306a36Sopenharmony_ci video_unregister_device(&vou_dev->vdev); 135762306a36Sopenharmony_ci i2c_put_adapter(client->adapter); 135862306a36Sopenharmony_ci v4l2_device_unregister(&vou_dev->v4l2_dev); 135962306a36Sopenharmony_ci} 136062306a36Sopenharmony_ci 136162306a36Sopenharmony_cistatic struct platform_driver sh_vou = { 136262306a36Sopenharmony_ci .remove_new = sh_vou_remove, 136362306a36Sopenharmony_ci .driver = { 136462306a36Sopenharmony_ci .name = "sh-vou", 136562306a36Sopenharmony_ci }, 136662306a36Sopenharmony_ci}; 136762306a36Sopenharmony_ci 136862306a36Sopenharmony_cimodule_platform_driver_probe(sh_vou, sh_vou_probe); 136962306a36Sopenharmony_ci 137062306a36Sopenharmony_ciMODULE_DESCRIPTION("SuperH VOU driver"); 137162306a36Sopenharmony_ciMODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); 137262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 137362306a36Sopenharmony_ciMODULE_VERSION("0.1.0"); 137462306a36Sopenharmony_ciMODULE_ALIAS("platform:sh-vou"); 1375