diff options
Diffstat (limited to 'meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0013-IMR-driver-interim-patch.patch')
-rw-r--r-- | meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0013-IMR-driver-interim-patch.patch | 2104 |
1 files changed, 2104 insertions, 0 deletions
diff --git a/meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0013-IMR-driver-interim-patch.patch b/meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0013-IMR-driver-interim-patch.patch new file mode 100644 index 0000000..30cf006 --- /dev/null +++ b/meta-rcar-gen3-adas/recipes-kernel/linux/linux-renesas/0013-IMR-driver-interim-patch.patch @@ -0,0 +1,2104 @@ +From 240504b182a2816696c6a02fe37a5048925cc1fb Mon Sep 17 00:00:00 2001 +From: Konstantin Kozhevnikov <Konstantin.Kozhevnikov@cogentembedded.com> +Date: Wed, 7 Sep 2016 22:55:37 +0300 +Subject: [PATCH] IMR driver - interim patch + +Signed-off-by: Konstantin Kozhevnikov <Konstantin.Kozhevnikov@cogentembedded.com> +--- + arch/arm64/boot/dts/renesas/r8a7795-es1.dtsi | 32 + + arch/arm64/boot/dts/renesas/r8a7795.dtsi | 32 + + drivers/clk/renesas/r8a7795-cpg-mssr.c | 4 + + drivers/media/platform/Kconfig | 11 + + drivers/media/platform/Makefile | 1 + + drivers/media/platform/rcar_imr.c | 1840 ++++++++++++++++++++++++++ + include/uapi/linux/rcar-imr.h | 98 ++ + 7 files changed, 2018 insertions(+) + create mode 100644 drivers/media/platform/rcar_imr.c + create mode 100644 include/uapi/linux/rcar-imr.h + +diff --git a/arch/arm64/boot/dts/renesas/r8a7795-es1.dtsi b/arch/arm64/boot/dts/renesas/r8a7795-es1.dtsi +index 09e1284..d530100 100644 +--- a/arch/arm64/boot/dts/renesas/r8a7795-es1.dtsi ++++ b/arch/arm64/boot/dts/renesas/r8a7795-es1.dtsi +@@ -2713,5 +2713,37 @@ + }; + }; + }; ++ ++ imrlx4_ch0: imr-lx4@fe860000 { ++ compatible = "renesas,imr-lx4"; ++ reg = <0 0xfe860000 0 0x2000>; ++ interrupts = <GIC_SPI 192 IRQ_TYPE_LEVEL_HIGH>; ++ clocks = <&cpg CPG_MOD 823>; ++ power-domains = <&sysc R8A7795_PD_A3VC>; ++ }; ++ ++ imrlx4_ch1: imr-lx4@fe870000 { ++ compatible = "renesas,imr-lx4"; ++ reg = <0 0xfe870000 0 0x2000>; ++ interrupts = <GIC_SPI 193 IRQ_TYPE_LEVEL_HIGH>; ++ clocks = <&cpg CPG_MOD 822>; ++ power-domains = <&sysc R8A7795_PD_A3VC>; ++ }; ++ ++ imrlx4_ch2: imr-lx4@fe880000 { ++ compatible = "renesas,imr-lx4"; ++ reg = <0 0xfe880000 0 0x2000>; ++ interrupts = <GIC_SPI 194 IRQ_TYPE_LEVEL_HIGH>; ++ clocks = <&cpg CPG_MOD 821>; ++ power-domains = <&sysc R8A7795_PD_A3VC>; ++ }; ++ ++ imrlx4_ch3: imr-lx4@fe890000 { ++ compatible = "renesas,imr-lx4"; ++ reg = <0 0xfe890000 0 0x2000>; ++ interrupts = <GIC_SPI 195 IRQ_TYPE_LEVEL_HIGH>; ++ clocks = <&cpg CPG_MOD 820>; ++ power-domains = <&sysc R8A7795_PD_A3VC>; ++ }; + }; + }; +diff --git a/arch/arm64/boot/dts/renesas/r8a7795.dtsi b/arch/arm64/boot/dts/renesas/r8a7795.dtsi +index 82ebfd4..9b4ee2f 100644 +--- a/arch/arm64/boot/dts/renesas/r8a7795.dtsi ++++ b/arch/arm64/boot/dts/renesas/r8a7795.dtsi +@@ -2699,5 +2699,37 @@ + }; + }; + }; ++ ++ imrlx4_ch0: imr-lx4@fe860000 { ++ compatible = "renesas,imr-lx4"; ++ reg = <0 0xfe860000 0 0x2000>; ++ interrupts = <GIC_SPI 192 IRQ_TYPE_LEVEL_HIGH>; ++ clocks = <&cpg CPG_MOD 823>; ++ power-domains = <&sysc R8A7795_PD_A3VC>; ++ }; ++ ++ imrlx4_ch1: imr-lx4@fe870000 { ++ compatible = "renesas,imr-lx4"; ++ reg = <0 0xfe870000 0 0x2000>; ++ interrupts = <GIC_SPI 193 IRQ_TYPE_LEVEL_HIGH>; ++ clocks = <&cpg CPG_MOD 822>; ++ power-domains = <&sysc R8A7795_PD_A3VC>; ++ }; ++ ++ imrlx4_ch2: imr-lx4@fe880000 { ++ compatible = "renesas,imr-lx4"; ++ reg = <0 0xfe880000 0 0x2000>; ++ interrupts = <GIC_SPI 194 IRQ_TYPE_LEVEL_HIGH>; ++ clocks = <&cpg CPG_MOD 821>; ++ power-domains = <&sysc R8A7795_PD_A3VC>; ++ }; ++ ++ imrlx4_ch3: imr-lx4@fe890000 { ++ compatible = "renesas,imr-lx4"; ++ reg = <0 0xfe890000 0 0x2000>; ++ interrupts = <GIC_SPI 195 IRQ_TYPE_LEVEL_HIGH>; ++ clocks = <&cpg CPG_MOD 820>; ++ power-domains = <&sysc R8A7795_PD_A3VC>; ++ }; + }; + }; +diff --git a/drivers/clk/renesas/r8a7795-cpg-mssr.c b/drivers/clk/renesas/r8a7795-cpg-mssr.c +index 718afd6..f833031 100644 +--- a/drivers/clk/renesas/r8a7795-cpg-mssr.c ++++ b/drivers/clk/renesas/r8a7795-cpg-mssr.c +@@ -229,6 +229,10 @@ enum clk_ids { + DEF_MOD("vin0", 811, R8A7795_CLK_S0D2), + DEF_MOD("etheravb", 812, R8A7795_CLK_S0D6), + DEF_MOD("sata0", 815, R8A7795_CLK_S3D2), ++ DEF_MOD("imr3", 820, R8A7795_CLK_S2D1), ++ DEF_MOD("imr2", 821, R8A7795_CLK_S2D1), ++ DEF_MOD("imr1", 822, R8A7795_CLK_S2D1), ++ DEF_MOD("imr0", 823, R8A7795_CLK_S2D1), + DEF_MOD("gpio7", 905, R8A7795_CLK_S3D4), + DEF_MOD("gpio6", 906, R8A7795_CLK_S3D4), + DEF_MOD("gpio5", 907, R8A7795_CLK_S3D4), +diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig +index ba2b892..f4d12f5 100644 +--- a/drivers/media/platform/Kconfig ++++ b/drivers/media/platform/Kconfig +@@ -315,6 +315,17 @@ config VIDEO_RENESAS_FCP + To compile this driver as a module, choose M here: the module + will be called rcar-fcp. + ++config VIDEO_RENESAS_IMR ++ tristate "Renesas Image Renderer (Distortion Correction) Unit" ++ depends on VIDEO_DEV && VIDEO_V4L2 ++ select VIDEOBUF2_DMA_CONTIG ++ select V4L2_MEM2MEM_DEV ++ ---help--- ++ This is a V4L2 driver for the Renesas IMR-X2/LX2/LX4 Processing Unit. ++ ++ To compile this driver as a module, choose M here: the module ++ will be called rcar_imr. ++ + config VIDEO_RENESAS_VSP1 + tristate "Renesas VSP1 Video Processing Engine" + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && HAS_DMA +diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile +index 40b18d1..9e9464c 100644 +--- a/drivers/media/platform/Makefile ++++ b/drivers/media/platform/Makefile +@@ -49,6 +49,7 @@ obj-$(CONFIG_SOC_CAMERA) += soc_camera/ + + obj-$(CONFIG_VIDEO_RENESAS_FCP) += rcar-fcp.o + obj-$(CONFIG_VIDEO_RENESAS_JPU) += rcar_jpu.o ++obj-$(CONFIG_VIDEO_RENESAS_IMR) += rcar_imr.o + obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1/ + + obj-y += omap/ +diff --git a/drivers/media/platform/rcar_imr.c b/drivers/media/platform/rcar_imr.c +new file mode 100644 +index 0000000..30c6742 +--- /dev/null ++++ b/drivers/media/platform/rcar_imr.c +@@ -0,0 +1,1840 @@ ++/* ++ * rcar_imr.c -- R-Car IMR-LX4 Driver ++ * ++ * Copyright (C) 2015 Cogent Embedded, Inc. <source@cogentembedded.com> ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/module.h> ++#include <linux/platform_device.h> ++#include <linux/clk.h> ++#include <linux/interrupt.h> ++#include <linux/pm_runtime.h> ++#include <linux/delay.h> ++#include <linux/rcar-imr.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-fh.h> ++#include <media/v4l2-mem2mem.h> ++#include <media/v4l2-ioctl.h> ++#include <media/videobuf2-dma-contig.h> ++ ++#define DRV_NAME "rcar_imr" ++ ++/******************************************************************************* ++ * Module parameters ++ ******************************************************************************/ ++ ++static int debug; ++module_param(debug, int, 0644); ++MODULE_PARM_DESC(debug, "Debug level (0-4)"); ++ ++/******************************************************************************* ++ * Local types definitions ++ ******************************************************************************/ ++ ++/* ...configuration data */ ++struct imr_cfg { ++ /* ...display-list main program data */ ++ void *dl_vaddr; ++ dma_addr_t dl_dma_addr; ++ u32 dl_size; ++ u32 dl_start_offset; ++ ++ /* ...pointers to the source/destination planes */ ++ u32 *src_pa_ptr[2]; ++ u32 *dst_pa_ptr[2]; ++ ++ /* ...subpixel destination coordinates space */ ++ int dst_subpixel; ++ ++ /* ...reference counter */ ++ u32 refcount; ++ ++ /* ...identifier (for debug output) */ ++ u32 id; ++}; ++ ++struct imr_buffer { ++ /* ...standard M2M buffer descriptor */ ++ struct v4l2_m2m_buffer buf; ++ ++ /* ...pointer to mesh configuration for processing */ ++ struct imr_cfg *cfg; ++}; ++ ++struct imr_q_data { ++ /* ...latched pixel format */ ++ struct v4l2_pix_format fmt; ++ ++ /* ...current format flags */ ++ u32 flags; ++}; ++ ++struct imr_format_info { ++ char *name; ++ u32 fourcc; ++ u32 flags; ++}; ++ ++/* ...per-device data */ ++struct imr_device { ++ struct device *dev; ++ struct clk *clock; ++ void __iomem *mmio; ++ int irq; ++ struct mutex mutex; ++ spinlock_t lock; ++ ++ struct v4l2_device v4l2_dev; ++ struct video_device video_dev; ++ struct v4l2_m2m_dev *m2m_dev; ++ ++ /* ...do we need that counter really? framework counts fh structures for us - tbd */ ++ int refcount; ++ ++ /* ...should we include media-dev? likely, no - tbd */ ++}; ++ ++/* ...per file-handle context */ ++struct imr_ctx { ++ struct v4l2_fh fh; ++ struct imr_device *imr; ++ struct v4l2_m2m_ctx *m2m_ctx; ++ struct imr_q_data queue[2]; ++ ++ /* ...current job configuration */ ++ struct imr_cfg *cfg; ++ ++ /* ...frame sequence counter */ ++ u32 sequence; ++ ++ /* ...cropping parameters (in pixels) */ ++ u16 crop[4]; ++ ++ /* ...number of active configurations (debugging) */ ++ u32 cfg_num; ++}; ++ ++/******************************************************************************* ++ * IMR registers ++ ******************************************************************************/ ++ ++#define IMR_CR 0x08 ++#define IMR_CR_RS (1 << 0) ++#define IMR_CR_SWRST (1 << 15) ++ ++#define IMR_SR 0x0C ++#define IMR_SRCR 0x10 ++#define IMR_SR_TRA (1 << 0) ++#define IMR_SR_IER (1 << 1) ++#define IMR_SR_INT (1 << 2) ++#define IMR_SR_REN (1 << 5) ++ ++#define IMR_ICR 0x14 ++#define IMR_IMR 0x18 ++#define IMR_ICR_TRAEN (1 << 0) ++#define IMR_ICR_IEREN (1 << 1) ++#define IMR_ICR_INTEN (1 << 2) ++ ++#define IMR_DLSP 0x1C ++#define IMR_DLSR 0x20 ++#define IMR_DLSAR 0x30 ++ ++#define IMR_DSAR 0x34 ++#define IMR_SSAR 0x38 ++#define IMR_DSTR 0x3C ++#define IMR_SSTR 0x40 ++#define IMR_DSOR 0x50 ++ ++#define IMR_CMRCR 0x54 ++#define IMR_CMRCSR 0x58 ++#define IMR_CMRCCR 0x5C ++#define IMR_CMR_LUCE (1 << 1) ++#define IMR_CMR_CLCE (1 << 2) ++#define IMR_CMR_DUV_SHIFT 3 ++#define IMR_CMR_DUV_MASK (3 << IMR_CMR_DUV_SHIFT) ++#define IMR_CMR_SUV_SHIFT 5 ++#define IMR_CMR_SUV_MASK (3 << IMR_CMR_SUV_SHIFT) ++#define IMR_CMR_YISM (1 << 7) ++#define IMR_CMR_DY10 (1 << 8) ++#define IMR_CMR_DY12 (1 << 9) ++#define IMR_CMR_SY10 (1 << 11) ++#define IMR_CMR_SY12 (1 << 12) ++#define IMR_CMR_YCM (1 << 14) ++#define IMR_CMR_CP16E (1 << 15) ++ ++#define IMR_CMRCR2 0xE4 ++#define IMR_CMRCSR2 0xE8 ++#define IMR_CMRCCR2 0xEC ++#define IMR_CMR2_LUTE (1 << 0) ++#define IMR_CMR2_YUV422E (1 << 2) ++#define IMR_CMR2_YUV422FORM (1 << 5) ++#define IMR_CMR2_UVFORM (1 << 6) ++#define IMR_CMR2_TCTE (1 << 12) ++#define IMR_CMR2_DCTE (1 << 15) ++ ++#define IMR_TRIMR 0x60 ++#define IMR_TRIMSR 0x64 ++#define IMR_TRIMCR 0x68 ++#define IMR_TRIM_TME (1 << 0) ++#define IMR_TRIM_BFE (1 << 1) ++#define IMR_TRIM_AUTODG (1 << 2) ++#define IMR_TRIM_AUTOSG (1 << 3) ++#define IMR_TRIM_DYDXM (1 << 4) ++#define IMR_TRIM_DUDVM (1 << 5) ++#define IMR_TRIM_TCM (1 << 6) ++ ++#define IMR_TRICR 0x6C ++#define IMR_TRIC_YCFORM (1 << 31) ++ ++#define IMR_UVDPOR 0x70 ++#define IMR_SUSR 0x74 ++#define IMR_SVSR 0x78 ++ ++#define IMR_XMINR 0x80 ++#define IMR_YMINR 0x84 ++#define IMR_XMAXR 0x88 ++#define IMR_YMAXR 0x8C ++ ++#define IMR_AMXSR 0x90 ++#define IMR_AMYSR 0x94 ++#define IMR_AMXOR 0x98 ++#define IMR_AMYOR 0x9C ++ ++#define IMR_CPDPOR 0xD0 ++#define IMR_CPDP_YLDPO_SHIFT 8 ++#define IMR_CPDP_UBDPO_SHIFT 4 ++#define IMR_CPDP_VRDPO_SHIFT 0 ++ ++/******************************************************************************* ++ * Auxiliary helpers ++ ******************************************************************************/ ++ ++static inline struct imr_ctx * fh_to_ctx(struct v4l2_fh *fh) ++{ ++ return container_of(fh, struct imr_ctx, fh); ++} ++ ++static inline struct imr_buffer * to_imr_buffer(struct vb2_v4l2_buffer *vbuf) ++{ ++ struct v4l2_m2m_buffer *b = container_of(vbuf, struct v4l2_m2m_buffer, vb); ++ ++ return container_of(b, struct imr_buffer, buf); ++} ++ ++/******************************************************************************* ++ * Local constants definition ++ ******************************************************************************/ ++ ++#define IMR_F_Y8 (1 << 0) ++#define IMR_F_Y10 (1 << 1) ++#define IMR_F_Y12 (1 << 2) ++#define IMR_F_UV8 (1 << 3) ++#define IMR_F_UV10 (1 << 4) ++#define IMR_F_UV12 (1 << 5) ++#define IMR_F_PLANAR (1 << 6) ++#define IMR_F_INTERLEAVED (1 << 7) ++#define IMR_F_PLANES_MASK ((1 << 8) - 1) ++#define IMR_F_UV_SWAP (1 << 8) ++#define IMR_F_YUV_SWAP (1 << 9) ++ ++/* ...get common planes bits */ ++static inline u32 __imr_flags_common(u32 iflags, u32 oflags) ++{ ++ return (iflags & oflags) & IMR_F_PLANES_MASK; ++} ++ ++static const struct imr_format_info imr_lx4_formats[] = { ++ { ++ .name = "YUV 4:2:2 semiplanar (NV16)", ++ .fourcc = V4L2_PIX_FMT_NV16, ++ .flags = IMR_F_Y8 | IMR_F_UV8 | IMR_F_PLANAR, ++ }, ++ { ++ .name = "YVU 4:2:2 semiplanar (NV61)", ++ .fourcc = V4L2_PIX_FMT_NV61, ++ .flags = IMR_F_Y8 | IMR_F_UV8 | IMR_F_PLANAR | IMR_F_UV_SWAP, ++ }, ++ { ++ .name = "YUV 4:2:2 interleaved (YUYV)", ++ .fourcc = V4L2_PIX_FMT_YUYV, ++ .flags = IMR_F_Y8 | IMR_F_UV8, ++ }, ++ { ++ .name = "YUV 4:2:2 interleaved (UYVY)", ++ .fourcc = V4L2_PIX_FMT_UYVY, ++ .flags = IMR_F_Y8 | IMR_F_UV8 | IMR_F_YUV_SWAP, ++ }, ++ { ++ .name = "YUV 4:2:2 interleaved (YVYU)", ++ .fourcc = V4L2_PIX_FMT_YVYU, ++ .flags = IMR_F_Y8 | IMR_F_UV8 | IMR_F_UV_SWAP, ++ }, ++ { ++ .name = "YUV 4:2:2 interleaved (UYVY)", ++ .fourcc = V4L2_PIX_FMT_VYUY, ++ .flags = IMR_F_Y8 | IMR_F_UV8 | IMR_F_UV_SWAP | IMR_F_YUV_SWAP, ++ }, ++ { ++ .name = "Greyscale 8-bit", ++ .fourcc = V4L2_PIX_FMT_GREY, ++ .flags = IMR_F_Y8 | IMR_F_PLANAR, ++ }, ++ { ++ .name = "Greyscale 10-bit", ++ .fourcc = V4L2_PIX_FMT_Y10, ++ .flags = IMR_F_Y8 | IMR_F_Y10 | IMR_F_PLANAR, ++ }, ++ { ++ .name = "Greyscale 12-bit", ++ .fourcc = V4L2_PIX_FMT_Y12, ++ .flags = IMR_F_Y8 | IMR_F_Y10 | IMR_F_Y12 | IMR_F_PLANAR, ++ }, ++ { ++ .name = "Chrominance UV 8-bit", ++ .fourcc = V4L2_PIX_FMT_UV8, ++ .flags = IMR_F_UV8 | IMR_F_PLANAR, ++ }, ++}; ++ ++/* ...mesh configuration constructor */ ++static struct imr_cfg * imr_cfg_create(struct imr_ctx *ctx, u32 dl_size, u32 dl_start) ++{ ++ struct imr_device *imr = ctx->imr; ++ struct imr_cfg *cfg; ++ ++ /* ...allocate configuration descriptor */ ++ cfg = kmalloc(sizeof(*cfg), GFP_KERNEL); ++ if (!cfg) { ++ v4l2_err(&imr->v4l2_dev, "failed to allocate configuration descriptor\n"); ++ return ERR_PTR(-ENOMEM); ++ } ++ ++ /* ...allocate contiguous memory for a display list */ ++ cfg->dl_vaddr = dma_alloc_writecombine(imr->dev, dl_size, &cfg->dl_dma_addr, GFP_KERNEL); ++ if (!cfg->dl_vaddr) { ++ v4l2_err(&imr->v4l2_dev, "failed to allocate %u bytes for a DL\n", dl_size); ++ kfree(cfg); ++ return ERR_PTR(-ENOMEM); ++ } ++ ++ cfg->dl_size = dl_size; ++ cfg->dl_start_offset = dl_start; ++ cfg->refcount = 1; ++ cfg->id = ctx->sequence; ++ ++ /* ...for debugging purposes, advance number of active configurations */ ++ ctx->cfg_num++; ++ ++ return cfg; ++} ++ ++/* ...add reference to the current configuration */ ++static inline struct imr_cfg * imr_cfg_ref(struct imr_ctx *ctx) ++{ ++ struct imr_cfg *cfg = ctx->cfg; ++ ++ BUG_ON(!cfg); ++ cfg->refcount++; ++ return cfg; ++} ++ ++/* ...mesh configuration destructor */ ++static void imr_cfg_unref(struct imr_ctx *ctx, struct imr_cfg *cfg) ++{ ++ struct imr_device *imr = ctx->imr; ++ ++ /* ...no atomicity is required as operation is locked with device mutex */ ++ if (!cfg || --cfg->refcount) ++ return; ++ ++ /* ...release memory allocated for a display list */ ++ if (cfg->dl_vaddr) ++ dma_free_writecombine(imr->dev, cfg->dl_size, cfg->dl_vaddr, cfg->dl_dma_addr); ++ ++ /* ...destroy the configuration structure */ ++ kfree(cfg); ++ ++ /* ...decrement number of active configurations (debugging) */ ++ WARN_ON(!ctx->cfg_num--); ++} ++ ++ ++ ++/******************************************************************************* ++ * Context processing queue ++ ******************************************************************************/ ++ ++static int imr_queue_setup(struct vb2_queue *vq, ++ unsigned int *nbuffers, unsigned int *nplanes, ++ unsigned int sizes[], struct device *alloc_devs[]) ++{ ++ struct imr_ctx *ctx = vb2_get_drv_priv(vq); ++ struct imr_q_data *q_data = &ctx->queue[V4L2_TYPE_IS_OUTPUT(vq->type) ? 0 : 1]; ++ int w = q_data->fmt.width; ++ int h = q_data->fmt.height; ++ ++ /* ...we use only single-plane formats */ ++ *nplanes = 1; ++ ++ /* ...specify plane size */ ++ switch (q_data->fmt.pixelformat) { ++ case V4L2_PIX_FMT_UYVY: ++ case V4L2_PIX_FMT_YUYV: ++ case V4L2_PIX_FMT_VYUY: ++ case V4L2_PIX_FMT_YVYU: ++ case V4L2_PIX_FMT_NV16: ++ case V4L2_PIX_FMT_Y10: ++ case V4L2_PIX_FMT_Y16: ++ sizes[0] = w * h * 2; ++ break; ++ ++ case V4L2_PIX_FMT_UV8: ++ case V4L2_PIX_FMT_GREY: ++ sizes[0] = w * h; ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int imr_buf_prepare(struct vb2_buffer *vb) ++{ ++ /* ...unclear yet if we want to prepare a buffer somehow (cache invalidation? - tbd) */ ++ return 0; ++} ++ ++static void imr_buf_queue(struct vb2_buffer *vb) ++{ ++ struct vb2_queue *q = vb->vb2_queue; ++ struct imr_ctx *ctx = vb2_get_drv_priv(q); ++ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); ++ ++ WARN_ON_ONCE(!mutex_is_locked(&ctx->imr->mutex)); ++ ++ v4l2_dbg(3, debug, &ctx->imr->v4l2_dev, "%sput buffer <0x%08llx> submitted\n", ++ q->is_output ? "in" : "out", ++ vb2_dma_contig_plane_dma_addr(vb, 0)); ++ ++ /* ...for input buffer, put current configuration pointer (add reference) */ ++ if (q->is_output) ++ to_imr_buffer(vbuf)->cfg = imr_cfg_ref(ctx); ++ ++ v4l2_m2m_buf_queue(ctx->m2m_ctx, vbuf); ++} ++ ++static void imr_buf_finish(struct vb2_buffer *vb) ++{ ++ struct vb2_queue *q = vb->vb2_queue; ++ struct imr_ctx *ctx = vb2_get_drv_priv(q); ++ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); ++ ++ WARN_ON(!mutex_is_locked(&ctx->imr->mutex)); ++ ++ /* ...any special processing of completed buffer? - tbd */ ++ v4l2_dbg(3, debug, &ctx->imr->v4l2_dev, "%sput buffer <0x%08llx> done\n", ++ q->is_output ? "in" : "out", ++ vb2_dma_contig_plane_dma_addr(vb, 0)); ++ ++ /* ...unref configuration pointer as needed */ ++ if (q->is_output) ++ imr_cfg_unref(ctx, to_imr_buffer(vbuf)->cfg); ++} ++ ++static int imr_start_streaming(struct vb2_queue *vq, unsigned int count) ++{ ++ struct imr_ctx *ctx = vb2_get_drv_priv(vq); ++ int ret; ++ ++ ret = 0;//pm_runtime_get_sync(ctx->imr->dev); ++ if (ret < 0) { ++ v4l2_err(&ctx->imr->v4l2_dev, "failed to start %s streaming: %d\n", ++ (V4L2_TYPE_IS_OUTPUT(vq->type) ? "output" : "capture"), ret); ++ return ret; ++ } else { ++ v4l2_dbg(1, debug, &ctx->imr->v4l2_dev, "%s streaming started\n", ++ (V4L2_TYPE_IS_OUTPUT(vq->type) ? "output" : "capture")); ++ return 0; ++ } ++} ++ ++static void imr_stop_streaming(struct vb2_queue *vq) ++{ ++ struct imr_ctx *ctx = vb2_get_drv_priv(vq); ++ struct vb2_v4l2_buffer *vb; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&ctx->imr->lock, flags); ++ ++ /* ...purge all buffers from a queue */ ++ if (V4L2_TYPE_IS_OUTPUT(vq->type)) { ++ while ((vb = v4l2_m2m_src_buf_remove(ctx->m2m_ctx)) != NULL) ++ v4l2_m2m_buf_done(vb, VB2_BUF_STATE_ERROR); ++ } else { ++ while ((vb = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx)) != NULL) ++ v4l2_m2m_buf_done(vb, VB2_BUF_STATE_ERROR); ++ } ++ ++ spin_unlock_irqrestore(&ctx->imr->lock, flags); ++ ++ v4l2_dbg(1, debug, &ctx->imr->v4l2_dev, "%s streaming stopped\n", ++ (V4L2_TYPE_IS_OUTPUT(vq->type) ? "output" : "capture")); ++ ++ //pm_runtime_put(ctx->imr->dev); ++} ++ ++/* ...buffer queue operations */ ++static struct vb2_ops imr_qops = { ++ .queue_setup = imr_queue_setup, ++ .buf_prepare = imr_buf_prepare, ++ .buf_queue = imr_buf_queue, ++ .buf_finish = imr_buf_finish, ++ .start_streaming = imr_start_streaming, ++ .stop_streaming = imr_stop_streaming, ++ .wait_prepare = vb2_ops_wait_prepare, ++ .wait_finish = vb2_ops_wait_finish, ++}; ++ ++/* ...M2M device processing queue initialization */ ++static int imr_queue_init(void *priv, struct vb2_queue *src_vq, ++ struct vb2_queue *dst_vq) ++{ ++ struct imr_ctx *ctx = priv; ++ int ret; ++ ++ memset(src_vq, 0, sizeof(*src_vq)); ++ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; ++ src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; ++ src_vq->drv_priv = ctx; ++ src_vq->buf_struct_size = sizeof(struct imr_buffer); ++ src_vq->ops = &imr_qops; ++ src_vq->mem_ops = &vb2_dma_contig_memops; ++ src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; ++ src_vq->lock = &ctx->imr->mutex; ++ src_vq->dev = ctx->imr->v4l2_dev.dev; ++ ret = vb2_queue_init(src_vq); ++ if (ret) ++ return ret; ++ ++ memset(dst_vq, 0, sizeof(*dst_vq)); ++ dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ++ dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; ++ dst_vq->drv_priv = ctx; ++ dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); ++ dst_vq->ops = &imr_qops; ++ dst_vq->mem_ops = &vb2_dma_contig_memops; ++ dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; ++ dst_vq->lock = &ctx->imr->mutex; ++ ret = vb2_queue_init(dst_vq); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++/******************************************************************************* ++ * Display list commands ++ ******************************************************************************/ ++ ++/* ...display list opcodes */ ++#define IMR_OP_TRI(n) ((0x8A << 24) | ((n) & 0xFFFF)) ++#define IMR_OP_LINE(n) ((0x8B << 24) | ((n) & 0xFFFF)) ++#define IMR_OP_NOP(n) ((0x80 << 24) | ((n) & 0xFFFF)) ++#define IMR_OP_TRAP ((0x8F << 24)) ++#define IMR_OP_WTL(add, n) ((0x81 << 24) | (((add) / 4) << 16) | ((n) & 0xFFFF)) ++#define IMR_OP_WTS(add, data) ((0x82 << 24) | (((add) / 4) << 16) | ((data) & 0xFFFF)) ++#define IMR_OP_WTL2(add, n) ((0x83 << 24) | (((add) / 4) << 10) | ((n) & 0x3FF)) ++#define IMR_OP_INT ((0x88 << 24)) ++#define IMR_OP_SYNCM ((0x86 << 24)) ++#define IMR_OP_GOSUB ((0x8C << 24)) ++#define IMR_OP_RET ((0x8D << 24)) ++ ++/******************************************************************************* ++ * Operation type decoding helpers ++ ******************************************************************************/ ++ ++static inline u16 __imr_auto_sg_dg_tcm(u32 type) ++{ ++ return (type & IMR_MAP_AUTOSG ? IMR_TRIM_AUTOSG : (type & IMR_MAP_AUTODG ? IMR_TRIM_AUTODG : 0)) | ++ (type & IMR_MAP_TCM ? IMR_TRIM_TCM : 0); ++} ++ ++static inline u16 __imr_uvdp(u32 type) ++{ ++ return __IMR_MAP_UVDPOR(type) | (type & IMR_MAP_DDP ? (1 << 8) : 0); ++} ++ ++static inline u16 __imr_cpdp(u32 type) ++{ ++ return (__IMR_MAP_YLDPO(type) << 8) | (__IMR_MAP_UBDPO(type) << 4) | __IMR_MAP_VRDPO(type); ++} ++ ++static inline u16 __imr_luce(u32 type) ++{ ++ return (type & IMR_MAP_LUCE ? IMR_CMR_LUCE : 0); ++} ++ ++static inline u16 __imr_clce(u32 type) ++{ ++ return (type & IMR_MAP_CLCE ? IMR_CMR_CLCE : 0); ++} ++ ++/******************************************************************************* ++ * Type A (absolute coordinates of source/destination) mapping ++ ******************************************************************************/ ++ ++/* ...return size of the subroutine for type "a" mapping */ ++static inline u32 imr_tri_type_a_get_length(struct imr_mesh *mesh, int item_size) ++{ ++ return ((mesh->columns * (item_size / 2) + 1) * (mesh->rows - 1) + 1) * sizeof(u32); ++} ++ ++/* ...set a mesh rows * columns using absolute coordinates */ ++static inline u32 * imr_tri_set_type_a(u32 *dl, void *map, struct imr_mesh *mesh, int item_size) ++{ ++ int rows = mesh->rows; ++ int columns = mesh->columns; ++ u32 stride = item_size * columns; ++ int i, j; ++ ++ /* ...convert lattice into set of stripes */ ++ for (i = 0; i < rows - 1; i++) { ++ *dl++ = IMR_OP_TRI(columns * 2); ++ for (j = 0; j < columns; j++) { ++ memcpy((void *)dl, map, item_size); ++ memcpy((void *)dl + item_size, map + stride, item_size); ++ dl += item_size / 2; ++ map += item_size; ++ } ++ } ++ ++ *dl++ = IMR_OP_RET; ++ return dl; ++} ++ ++/******************************************************************************* ++ * Type B mapping (automatically generated source or destination coordinates) ++ ******************************************************************************/ ++ ++/* ...calculate length of a type "b" mapping */ ++static inline u32 imr_tri_type_b_get_length(struct imr_mesh *mesh, int item_size) ++{ ++ return ((mesh->columns * (item_size / 2) + 2) * (mesh->rows - 1) + 4) * sizeof(u32); ++} ++ ++/* ...set an auto-generated mesh n * m for a source/destination */ ++static inline u32 * imr_tri_set_type_b(u32 *dl, void *map, struct imr_mesh *mesh, int item_size) ++{ ++ int rows = mesh->rows; ++ int columns = mesh->columns; ++ int x0 = mesh->x0; ++ int y0 = mesh->y0; ++ int dx = mesh->dx; ++ int dy = mesh->dy; ++ int stride = item_size * columns; ++ int i, j; ++ ++ /* ...set mesh configuration */ ++ *dl++ = IMR_OP_WTS(IMR_AMXSR, dx); ++ *dl++ = IMR_OP_WTS(IMR_AMYSR, dy); ++ ++ /* ...origin by "x" coordinate is the same across all rows */ ++ *dl++ = IMR_OP_WTS(IMR_AMXOR, x0); ++ ++ /* ...convert lattice into set of stripes */ ++ for (i = 0; i < rows - 1; i++, y0 += dy) { ++ /* ...set origin by "y" coordinate for a current row */ ++ *dl++ = IMR_OP_WTS(IMR_AMYOR, y0); ++ *dl++ = IMR_OP_TRI(2 * columns); ++ ++ /* ...fill single row */ ++ for (j = 0; j < columns; j++) { ++ memcpy((void *)dl, map, item_size); ++ memcpy((void *)dl + item_size, map + stride, item_size); ++ dl += item_size / 2; ++ map += item_size; ++ } ++ } ++ ++ *dl++ = IMR_OP_RET; ++ return dl; ++} ++ ++/******************************************************************************* ++ * Type C mapping (vertex-buffer-object) ++ ******************************************************************************/ ++ ++/* ...calculate length of a type "c" mapping */ ++static inline u32 imr_tri_type_c_get_length(struct imr_vbo *vbo, int item_size) ++{ ++ return ((4 + 3 * item_size) * vbo->num + 4); ++} ++ ++/* ...set a VBO mapping using absolute coordinates */ ++static inline u32 * imr_tri_set_type_c(u32 *dl, void *map, struct imr_vbo *vbo, int item_size) ++{ ++ int num = vbo->num; ++ int i; ++ ++ /* ...prepare list of triangles to draw */ ++ for (i = 0; i < num; i++) { ++ *dl++ = IMR_OP_TRI(3); ++ memcpy((void *)dl, map, 3 * item_size); ++ dl += 3 * item_size / 4; ++ map += 3 * item_size; ++ } ++ ++ *dl++ = IMR_OP_RET; ++ return dl; ++} ++ ++/******************************************************************************* ++ * DL program creation ++ ******************************************************************************/ ++ ++/* ...return length of a DL main program */ ++static inline u32 imr_dl_program_length(struct imr_ctx *ctx) ++{ ++ u32 iflags = ctx->queue[0].flags; ++ u32 oflags = ctx->queue[1].flags; ++ u32 cflags = __imr_flags_common(iflags, oflags); ++ ++ /* ...check if formats are compatible */ ++ if (((iflags & IMR_F_PLANAR) != 0 && (oflags & IMR_F_PLANAR) == 0) || (cflags == 0)) { ++ v4l2_err(&ctx->imr->v4l2_dev, "formats are incompatible: if=%x, of=%x, cf=%x\n", iflags, oflags, cflags); ++ return 0; ++ } ++ ++ /* ...maximal possible length of the program is 27 32-bits words; round up to 32 */ ++ return 32 << 2; ++} ++ ++/* ...setup DL for Y/YUV planar/interleaved processing */ ++static inline void imr_dl_program_setup(struct imr_ctx *ctx, struct imr_cfg *cfg, u32 type, u32 *dl, u32 subaddr) ++{ ++ u32 iflags = ctx->queue[0].flags; ++ u32 oflags = ctx->queue[1].flags; ++ u32 cflags = __imr_flags_common(iflags, oflags); ++ u16 src_y_fmt = (iflags & IMR_F_Y12 ? IMR_CMR_SY12 : (iflags & IMR_F_Y10 ? IMR_CMR_SY10 : 0)); ++ u16 src_uv_fmt = (iflags & IMR_F_UV12 ? 2 : (iflags & IMR_F_UV10 ? 1 : 0)) << IMR_CMR_SUV_SHIFT; ++ u16 dst_y_fmt = (cflags & IMR_F_Y12 ? IMR_CMR_DY12 : (cflags & IMR_F_Y10 ? IMR_CMR_DY10 : 0)); ++ u16 dst_uv_fmt = (cflags & IMR_F_UV12 ? 2 : (cflags & IMR_F_UV10 ? 1 : 0)) << IMR_CMR_DUV_SHIFT; ++ int w = ctx->queue[0].fmt.width; ++ int h = ctx->queue[0].fmt.height; ++ int W = ctx->queue[1].fmt.width; ++ int H = ctx->queue[1].fmt.height; ++ ++ v4l2_dbg(2, debug, &ctx->imr->v4l2_dev, "setup %u*%u -> %u*%u mapping (type=%x)\n", w, h, W, H, type); ++ ++ /* ...set triangle mode register from user-supplied descriptor */ ++ *dl++ = IMR_OP_WTS(IMR_TRIMCR, 0xFFFF); ++ ++ /* ...set automatic source / destination coordinates generation flags */ ++ *dl++ = IMR_OP_WTS(IMR_TRIMSR, __imr_auto_sg_dg_tcm(type) | IMR_TRIM_BFE | IMR_TRIM_TME); ++ ++ /* ...set source / destination coordinate precision */ ++ *dl++ = IMR_OP_WTS(IMR_UVDPOR, __imr_uvdp(type)); ++ ++ /* ...set luminance/chromacity correction parameters precision */ ++ *dl++ = IMR_OP_WTS(IMR_CPDPOR, __imr_cpdp(type)); ++ ++ /* ...reset rendering mode registers */ ++ *dl++ = IMR_OP_WTS(IMR_CMRCCR, 0xFFFF); ++ *dl++ = IMR_OP_WTS(IMR_CMRCCR2, 0xFFFF); ++ ++ /* ...set source/destination addresses of Y/UV plane */ ++ *dl++ = IMR_OP_WTL(IMR_DSAR, 2); ++ cfg->dst_pa_ptr[0] = dl++; ++ cfg->src_pa_ptr[0] = dl++; ++ ++ /* ...select planar/interleaved mode basing on input format */ ++ if (iflags & IMR_F_PLANAR) { ++ /* ...planar input means planar output; set Y-plane precision */ ++ if (cflags & IMR_F_Y8) { ++ /* ...setup Y-plane processing: YCM=0, SY/DY=xx, SUV/DUV=0 */ ++ *dl++ = IMR_OP_WTS(IMR_CMRCSR, src_y_fmt | src_uv_fmt | dst_y_fmt | dst_uv_fmt | __imr_luce(type)); ++ ++ /* ...set source/destination strides basing on Y-plane precision */ ++ *dl++ = IMR_OP_WTS(IMR_DSTR, W << (cflags & IMR_F_Y10 ? 1 : 0)); ++ *dl++ = IMR_OP_WTS(IMR_SSTR, w << (iflags & IMR_F_Y10 ? 1 : 0)); ++ } else { ++ /* ...setup UV-plane processing only */ ++ *dl++ = IMR_OP_WTS(IMR_CMRCSR, IMR_CMR_YCM | src_uv_fmt | dst_uv_fmt | __imr_clce(type)); ++ ++ /* ...set source/destination strides basing on UV-plane precision */ ++ *dl++ = IMR_OP_WTS(IMR_DSTR, W << (cflags & IMR_F_UV10 ? 1 : 0)); ++ *dl++ = IMR_OP_WTS(IMR_SSTR, w << (iflags & IMR_F_UV10 ? 1 : 0)); ++ } ++ } else { ++ u16 src_fmt = (iflags & IMR_F_UV_SWAP ? IMR_CMR2_UVFORM : 0) | (iflags & IMR_F_YUV_SWAP ? IMR_CMR2_YUV422FORM : 0); ++ u32 dst_fmt = (oflags & IMR_F_YUV_SWAP ? IMR_TRIC_YCFORM : 0); ++ ++ /* ...interleaved input; output is either interleaved or planar */ ++ *dl++ = IMR_OP_WTS(IMR_CMRCSR2, IMR_CMR2_YUV422E | src_fmt); ++ ++ /* ...destination is always YUYV or UYVY */ ++ *dl++ = IMR_OP_WTL(IMR_TRICR, 1); ++ *dl++ = dst_fmt; ++ ++ /* ...set precision of Y/UV planes and required correction */ ++ *dl++ = IMR_OP_WTS(IMR_CMRCSR, src_y_fmt | src_uv_fmt | dst_y_fmt | dst_uv_fmt | __imr_clce(type) | __imr_luce(type)); ++ ++ /* ...set source stride basing on precision (2 or 4 bytes/pixel) */ ++ *dl++ = IMR_OP_WTS(IMR_SSTR, w << (iflags & IMR_F_Y10 ? 2 : 1)); ++ ++ /* ...if output is planar, put the offset value */ ++ if (oflags & IMR_F_PLANAR) { ++ /* ...specify offset of a destination UV plane */ ++ *dl++ = IMR_OP_WTL(IMR_DSOR, 1); ++ *dl++ = W * H; ++ ++ /* ...destination stride is 1 or 2 bytes/pixel (same for both Y and UV planes) */ ++ *dl++ = IMR_OP_WTS(IMR_DSTR, W << (cflags & IMR_F_Y10 ? 1 : 0)); ++ } else { ++ /* ...destination stride if 2 or 4 bytes/pixel (Y and UV planes interleaved) */ ++ *dl++ = IMR_OP_WTS(IMR_DSTR, W << (cflags & IMR_F_Y10 ? 2 : 1)); ++ } ++ } ++ ++ /* ...set source width/height of Y/UV plane (for Y plane upper part of SUSR is ignored) */ ++ *dl++ = IMR_OP_WTL(IMR_SUSR, 2); ++ *dl++ = ((w - 2) << 16) | (w - 1); ++ *dl++ = h - 1; ++ ++ /* ...invoke subroutine for triangles drawing */ ++ *dl++ = IMR_OP_GOSUB; ++ *dl++ = subaddr; ++ ++ /* ...if we have a planar output with both Y and UV planes available */ ++ if ((cflags & (IMR_F_PLANAR | IMR_F_Y8 | IMR_F_UV8)) == (IMR_F_PLANAR | IMR_F_Y8 | IMR_F_UV8)) { ++ /* ...select UV-plane processing mode; put sync before switching */ ++ *dl++ = IMR_OP_SYNCM; ++ ++ /* ...setup UV-plane source/destination addresses */ ++ *dl++ = IMR_OP_WTL(IMR_DSAR, 2); ++ cfg->dst_pa_ptr[1] = dl++; ++ cfg->src_pa_ptr[1] = dl++; ++ ++ /* ...select correction mode */ ++ *dl++ = IMR_OP_WTS(IMR_CMRCSR, IMR_CMR_YCM | __imr_clce(type)); ++ ++ /* ...luminance correction bit must be cleared (if it was set) */ ++ *dl++ = IMR_OP_WTS(IMR_CMRCCR, IMR_CMR_LUCE); ++ ++ /* ...draw triangles */ ++ *dl++ = IMR_OP_GOSUB; ++ *dl++ = subaddr; ++ } else { ++ /* ...clear pointers to the source/destination UV-planes addresses */ ++ cfg->src_pa_ptr[1] = cfg->dst_pa_ptr[1] = NULL; ++ } ++ ++ /* ...signal completion of the operation */ ++ *dl++ = IMR_OP_SYNCM; ++ *dl++ = IMR_OP_TRAP; ++} ++ ++/******************************************************************************* ++ * Mapping specification processing ++ ******************************************************************************/ ++ ++/* ...set mapping data (function called with video device lock held) */ ++static int imr_ioctl_map(struct imr_ctx *ctx, struct imr_map_desc *desc) ++{ ++ struct imr_device *imr = ctx->imr; ++ struct imr_mesh *mesh; ++ struct imr_vbo *vbo; ++ struct imr_cfg *cfg; ++ void *buf, *map; ++ u32 type; ++ u32 length, item_size; ++ u32 tri_length; ++ void *dl_vaddr; ++ u32 dl_size; ++ u32 dl_start_offset; ++ dma_addr_t dl_dma_addr; ++ int ret; ++ ++ /* ...read remainder of data into temporary buffer */ ++ length = desc->size; ++ buf = kmalloc(length, GFP_KERNEL); ++ if (!buf) { ++ v4l2_err(&imr->v4l2_dev, "failed to allocate %u bytes for mapping reading\n", length); ++ return -ENOMEM; ++ } ++ ++ /* ...copy mesh data */ ++ if (copy_from_user(buf, (void __user *)desc->data, length)) { ++ v4l2_err(&imr->v4l2_dev, "failed to read %u bytes of mapping specification\n", length); ++ ret = -EFAULT; ++ goto out; ++ } ++ ++ type = desc->type; ++ ++ /* ...mesh item size calculation */ ++ item_size = (type & IMR_MAP_LUCE ? 4 : 0) + (type & IMR_MAP_CLCE ? 4 : 0); ++ ++ /* ...calculate the length of a display list */ ++ if (type & IMR_MAP_MESH) { ++ /* ...assure we have proper mesh descriptor */ ++ if (length < sizeof(struct imr_mesh)) { ++ v4l2_err(&imr->v4l2_dev, "invalid mesh specification size: %u\n", length); ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ mesh = (struct imr_mesh *)buf; ++ length -= sizeof(struct imr_mesh); ++ map = buf + sizeof(struct imr_mesh); ++ ++ if (type & (IMR_MAP_AUTODG | IMR_MAP_AUTOSG)) { ++ /* ...source / destination vertex size is 4 bytes */ ++ item_size += 4; ++ ++ /* ...mapping is given using automatic generation pattern; check size */ ++ if (mesh->rows * mesh->columns * item_size != length) { ++ v4l2_err(&imr->v4l2_dev, "invalid mesh size: %u*%u*%u != %u\n", mesh->rows, mesh->columns, item_size, length); ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ /* ...calculate size of triangles drawing subroutine */ ++ tri_length = imr_tri_type_b_get_length(mesh, item_size); ++ } else { ++ /* ...source / destination vertes size if 8 bytes */ ++ item_size += 8; ++ ++ /* ...mapping is done with absolute coordinates */ ++ if (mesh->rows * mesh->columns * item_size != length) { ++ v4l2_err(&imr->v4l2_dev, "invalid mesh size: %u*%u*%u != %u\n", mesh->rows, mesh->columns, item_size, length); ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ /* ...calculate size of triangles drawing subroutine */ ++ tri_length = imr_tri_type_a_get_length(mesh, item_size); ++ } ++ } else { ++ /* ...assure we have proper VBO descriptor */ ++ if (length < sizeof(struct imr_vbo)) { ++ v4l2_err(&imr->v4l2_dev, "invalid vbo specification size: %u\n", length); ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ /* ...make sure there is no automatic-generation flags */ ++ if (type & (IMR_MAP_AUTODG | IMR_MAP_AUTOSG)) { ++ v4l2_err(&imr->v4l2_dev, "invalid auto-dg/sg flags: 0x%x\n", type); ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ vbo = (struct imr_vbo *)buf; ++ length -= sizeof(struct imr_vbo); ++ map = buf + sizeof(struct imr_vbo); ++ ++ /* ...vertex is given with absolute coordinates */ ++ item_size += 8; ++ ++ /* ...check the length is sane */ ++ if (length != vbo->num * 3 * item_size) { ++ v4l2_err(&imr->v4l2_dev, "invalid vbo size: %u*%u*3 != %u\n", vbo->num, item_size, length); ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ /* ...calculate size of trangles drawing subroutine */ ++ tri_length = imr_tri_type_c_get_length(vbo, item_size); ++ } ++ ++ /* ...DL main program shall start with 8-byte aligned address */ ++ dl_start_offset = (tri_length + 7) & ~7; ++ ++ /* ...calculate main routine length */ ++ dl_size = imr_dl_program_length(ctx); ++ if (!dl_size) { ++ v4l2_err(&imr->v4l2_dev, "format configuration error\n"); ++ ret = -EINVAL; ++ goto out; ++ } ++ ++ /* ...we use a single display list, with TRI subroutine prepending MAIN */ ++ dl_size += dl_start_offset; ++ ++ /* ...unref current configuration (will not be used by subsequent jobs) */ ++ imr_cfg_unref(ctx, ctx->cfg); ++ ++ /* ...create new configuration */ ++ ctx->cfg = cfg = imr_cfg_create(ctx, dl_size, dl_start_offset); ++ if (IS_ERR(cfg)) { ++ ret = PTR_ERR(cfg); ++ v4l2_err(&imr->v4l2_dev, "failed to create configuration: %d\n", ret); ++ goto out; ++ } ++ ++ /* ...get pointer to the new display list */ ++ dl_vaddr = cfg->dl_vaddr; ++ dl_dma_addr = cfg->dl_dma_addr; ++ ++ /* ...prepare a triangles drawing subroutine */ ++ if (type & IMR_MAP_MESH) { ++ if (type & (IMR_MAP_AUTOSG | IMR_MAP_AUTODG)) { ++ imr_tri_set_type_b(dl_vaddr, map, mesh, item_size); ++ } else { ++ imr_tri_set_type_a(dl_vaddr, map, mesh, item_size); ++ } ++ } else { ++ imr_tri_set_type_c(dl_vaddr, map, vbo, item_size); ++ } ++ ++ /* ...prepare main DL-program */ ++ imr_dl_program_setup(ctx, cfg, type, dl_vaddr + dl_start_offset, (u32)dl_dma_addr); ++ ++ /* ...update cropping parameters */ ++ cfg->dst_subpixel = (type & IMR_MAP_DDP ? 2 : 0); ++ ++ /* ...display list updated successfully */ ++ v4l2_dbg(2, debug, &ctx->imr->v4l2_dev, "display-list created: #%u[%08X]:%u[%u]\n", ++ cfg->id, (u32)dl_dma_addr, dl_size, dl_start_offset); ++ ++ if (debug >= 4) ++ print_hex_dump_bytes("DL-", DUMP_PREFIX_OFFSET, dl_vaddr + dl_start_offset, dl_size - dl_start_offset); ++ ++ /* ...success */ ++ ret = 0; ++ ++out: ++ /* ...release interim buffer */ ++ kfree(buf); ++ ++ return ret; ++} ++ ++/******************************************************************************* ++ * V4L2 I/O controls ++ ******************************************************************************/ ++ ++/* ...test for a format supported */ ++static int __imr_try_fmt(struct imr_ctx *ctx, struct v4l2_format *f) ++{ ++ struct v4l2_pix_format *pix = &f->fmt.pix; ++ u32 fourcc = pix->pixelformat; ++ int i; ++ ++ /* ...both output and capture interface have the same set of supported formats */ ++ for (i = 0; i < ARRAY_SIZE(imr_lx4_formats); i++) { ++ if (fourcc == imr_lx4_formats[i].fourcc) { ++ /* ...fix-up format specification as needed */ ++ pix->field = V4L2_FIELD_NONE; ++ ++ v4l2_dbg(1, debug, &ctx->imr->v4l2_dev, "format request: '%c%c%c%c', %d*%d\n", ++ (fourcc >> 0) & 0xff, (fourcc >> 8) & 0xff, ++ (fourcc >> 16) & 0xff, (fourcc >> 24) & 0xff, ++ pix->width, pix->height); ++ ++ /* ...verify source/destination image dimensions */ ++ if (V4L2_TYPE_IS_OUTPUT(f->type)) ++ v4l_bound_align_image(&pix->width, 128, 2048, 7, &pix->height, 1, 2048, 0, 0); ++ else ++ v4l_bound_align_image(&pix->width, 64, 2048, 6, &pix->height, 1, 2048, 0, 0); ++ ++ return i; ++ } ++ } ++ ++ v4l2_err(&ctx->imr->v4l2_dev, "unsupported format request: '%c%c%c%c'\n", ++ (fourcc >> 0) & 0xff, (fourcc >> 8) & 0xff, ++ (fourcc >> 16) & 0xff, (fourcc >> 24) & 0xff); ++ ++ return -EINVAL; ++} ++ ++/* ...capabilities query */ ++static int imr_querycap(struct file *file, void *priv, struct v4l2_capability *cap) ++{ ++ strlcpy(cap->driver, DRV_NAME, sizeof(cap->driver)); ++ strlcpy(cap->card, DRV_NAME, sizeof(cap->card)); ++ strlcpy(cap->bus_info, DRV_NAME, sizeof(cap->bus_info)); ++ ++ cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT | ++ V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING; ++ ++ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; ++ ++ return 0; ++} ++ ++/* ...enumerate supported formats */ ++static int imr_enum_fmt(struct file *file, void *priv, struct v4l2_fmtdesc *f) ++{ ++ /* ...no distinction between output/capture formats */ ++ if (f->index < ARRAY_SIZE(imr_lx4_formats)) { ++ const struct imr_format_info *fmt = &imr_lx4_formats[f->index]; ++ strlcpy(f->description, fmt->name, sizeof(f->description)); ++ f->pixelformat = fmt->fourcc; ++ return 0; ++ } ++ ++ return -EINVAL; ++} ++ ++/* ...retrieve current queue format; operation is locked ? */ ++static int imr_g_fmt(struct file *file, void *priv, struct v4l2_format *f) ++{ ++ struct imr_ctx *ctx = fh_to_ctx(priv); ++ struct vb2_queue *vq; ++ struct imr_q_data *q_data; ++ ++ vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); ++ if (!vq) ++ return -EINVAL; ++ ++ q_data = &ctx->queue[V4L2_TYPE_IS_OUTPUT(f->type) ? 0 : 1]; ++ ++ /* ...processing is locked? tbd */ ++ f->fmt.pix = q_data->fmt; ++ ++ return 0; ++} ++ ++/* ...test particular format; operation is not locked */ ++static int imr_try_fmt(struct file *file, void *priv, struct v4l2_format *f) ++{ ++ struct imr_ctx *ctx = fh_to_ctx(priv); ++ struct vb2_queue *vq; ++ ++ /* ...make sure we have a queue of particular type */ ++ vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); ++ if (!vq) ++ return -EINVAL; ++ ++ /* ...test if format is supported (adjust as appropriate) */ ++ return (__imr_try_fmt(ctx, f) >= 0 ? 0 : -EINVAL); ++} ++ ++/* ...apply queue format; operation is locked ? */ ++static int imr_s_fmt(struct file *file, void *priv, struct v4l2_format *f) ++{ ++ struct imr_ctx *ctx = fh_to_ctx(priv); ++ struct vb2_queue *vq; ++ struct imr_q_data *q_data; ++ int i; ++ ++ vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); ++ if (!vq) ++ return -EINVAL; ++ ++ /* ...check if queue is busy */ ++ if (vb2_is_busy(vq)) ++ return -EBUSY; ++ ++ /* ...test if format is supported (adjust as appropriate) */ ++ i = __imr_try_fmt(ctx, f); ++ if (i < 0) ++ return -EINVAL; ++ ++ /* ...format is supported; save current format in a queue-specific data */ ++ q_data = &ctx->queue[V4L2_TYPE_IS_OUTPUT(f->type) ? 0 : 1]; ++ ++ /* ...processing is locked? tbd */ ++ q_data->fmt = f->fmt.pix; ++ q_data->flags = imr_lx4_formats[i].flags; ++ ++ /* ...set default crop factors */ ++ if (V4L2_TYPE_IS_OUTPUT(f->type) == 0) { ++ ctx->crop[0] = 0; ++ ctx->crop[1] = f->fmt.pix.width - 1; ++ ctx->crop[2] = 0; ++ ctx->crop[3] = f->fmt.pix.height - 1; ++ } ++ ++ return 0; ++} ++ ++static int imr_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *reqbufs) ++{ ++ struct imr_ctx *ctx = fh_to_ctx(priv); ++ ++ return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); ++} ++ ++static int imr_querybuf(struct file *file, void *priv, struct v4l2_buffer *buf) ++{ ++ struct imr_ctx *ctx = fh_to_ctx(priv); ++ ++ return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); ++} ++ ++static int imr_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) ++{ ++ struct imr_ctx *ctx = fh_to_ctx(priv); ++ ++ /* ...operation is protected with a queue lock */ ++ WARN_ON(!mutex_is_locked(&ctx->imr->mutex)); ++ ++ /* ...verify the configuration is complete */ ++ if (!V4L2_TYPE_IS_OUTPUT(buf->type) && !ctx->cfg) { ++ v4l2_err(&ctx->imr->v4l2_dev, "stream configuration is not complete\n"); ++ return -EINVAL; ++ } ++ ++ return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); ++} ++ ++static int imr_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) ++{ ++ struct imr_ctx *ctx = fh_to_ctx(priv); ++ ++ return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); ++} ++ ++static int imr_expbuf(struct file *file, void *priv, struct v4l2_exportbuffer *eb) ++{ ++ struct imr_ctx *ctx = fh_to_ctx(priv); ++ ++ return v4l2_m2m_expbuf(file, ctx->m2m_ctx, eb); ++} ++ ++static int imr_streamon(struct file *file, void *priv, enum v4l2_buf_type type) ++{ ++ struct imr_ctx *ctx = fh_to_ctx(priv); ++ ++ /* ...context is prepared for a streaming */ ++ return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); ++} ++ ++static int imr_streamoff(struct file *file, void *priv, enum v4l2_buf_type type) ++{ ++ struct imr_ctx *ctx = fh_to_ctx(priv); ++ ++ return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); ++} ++ ++static int imr_g_crop(struct file *file, void *priv, struct v4l2_crop *cr) ++{ ++ struct imr_ctx *ctx = fh_to_ctx(priv); ++ ++ /* ...subpixel resolution of output buffer is not counted here */ ++ cr->c.left = ctx->crop[0]; ++ cr->c.top = ctx->crop[2]; ++ cr->c.width = ctx->crop[1] - ctx->crop[0]; ++ cr->c.height = ctx->crop[3] - ctx->crop[2]; ++ ++ return 0; ++} ++ ++static int imr_s_crop(struct file *file, void *priv, const struct v4l2_crop *cr) ++{ ++ struct imr_ctx *ctx = fh_to_ctx(priv); ++ int x0 = cr->c.left; ++ int y0 = cr->c.top; ++ int x1 = x0 + cr->c.width; ++ int y1 = y0 + cr->c.height; ++ ++ if (x0 < 0 || x1 >= 2048 || y0 < 0 || y1 >= 2048) { ++ v4l2_err(&ctx->imr->v4l2_dev, "invalid cropping: %d/%d/%d/%d\n", x0, x1, y0, y1); ++ return -EINVAL; ++ } ++ ++ /* ...subpixel resolution of output buffer is not counted here */ ++ ctx->crop[0] = x0; ++ ctx->crop[1] = x1; ++ ctx->crop[2] = y0; ++ ctx->crop[3] = y1; ++ ++ return 0; ++} ++ ++/* ...customized I/O control processing */ ++static long imr_default(struct file *file, void *fh, bool valid_prio, unsigned int cmd, void *arg) ++{ ++ struct imr_ctx *ctx = fh_to_ctx(fh); ++ ++ switch (cmd) { ++ case VIDIOC_IMR_MESH: ++ /* ...set mesh data */ ++ return imr_ioctl_map(ctx, (struct imr_map_desc *)arg); ++ ++ default: ++ return -ENOIOCTLCMD; ++ } ++} ++ ++static const struct v4l2_ioctl_ops imr_ioctl_ops = { ++ .vidioc_querycap = imr_querycap, ++ ++ .vidioc_enum_fmt_vid_cap = imr_enum_fmt, ++ .vidioc_enum_fmt_vid_out = imr_enum_fmt, ++ .vidioc_g_fmt_vid_cap = imr_g_fmt, ++ .vidioc_g_fmt_vid_out = imr_g_fmt, ++ .vidioc_try_fmt_vid_cap = imr_try_fmt, ++ .vidioc_try_fmt_vid_out = imr_try_fmt, ++ .vidioc_s_fmt_vid_cap = imr_s_fmt, ++ .vidioc_s_fmt_vid_out = imr_s_fmt, ++ ++ .vidioc_reqbufs = imr_reqbufs, ++ .vidioc_querybuf = imr_querybuf, ++ .vidioc_qbuf = imr_qbuf, ++ .vidioc_dqbuf = imr_dqbuf, ++ .vidioc_expbuf = imr_expbuf, ++ .vidioc_streamon = imr_streamon, ++ .vidioc_streamoff = imr_streamoff, ++ ++ .vidioc_g_crop = imr_g_crop, ++ .vidioc_s_crop = imr_s_crop, ++ ++ .vidioc_default = imr_default, ++}; ++ ++/******************************************************************************* ++ * Generic device file operations ++ ******************************************************************************/ ++ ++static int imr_open(struct file *file) ++{ ++ struct imr_device *imr = video_drvdata(file); ++ struct video_device *vfd = video_devdata(file); ++ struct imr_ctx *ctx; ++ int ret; ++ ++ /* ...allocate processing context associated with given instance */ ++ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); ++ if (!ctx) ++ return -ENOMEM; ++ ++ /* ...initialize per-file-handle structure */ ++ v4l2_fh_init(&ctx->fh, vfd); ++ //ctx->fh.ctrl_handler = &ctx->ctrl_handler; ++ file->private_data = &ctx->fh; ++ v4l2_fh_add(&ctx->fh); ++ ++ /* ...set default source / destination formats - need that? */ ++ ctx->imr = imr; ++ ctx->queue[0].fmt.pixelformat = 0; ++ ctx->queue[1].fmt.pixelformat = 0; ++ ++ /* ...set default cropping parameters */ ++ ctx->crop[1] = ctx->crop[3] = 0x3FF; ++ ++ /* ...initialize M2M processing context */ ++ ctx->m2m_ctx = v4l2_m2m_ctx_init(imr->m2m_dev, ctx, imr_queue_init); ++ if (IS_ERR(ctx->m2m_ctx)) { ++ ret = PTR_ERR(ctx->m2m_ctx); ++ goto v4l_prepare_rollback; ++ } ++ ++#if 0 ++ /* ...initialize controls and stuff */ ++ ret = imr_controls_create(ctx); ++ if (ret < 0) ++ goto v4l_prepare_rollback; ++#endif ++ ++ /* ...lock access to global device data */ ++ if (mutex_lock_interruptible(&imr->mutex)) { ++ ret = -ERESTARTSYS; ++ goto v4l_prepare_rollback; ++ } ++ ++ /* ...bring-up device as needed */ ++ if (imr->refcount == 0) { ++ ret = clk_prepare_enable(imr->clock); ++ if (ret < 0) ++ goto device_prepare_rollback; ++ } ++ ++ imr->refcount++; ++ ++ mutex_unlock(&imr->mutex); ++ ++ v4l2_dbg(1, debug, &imr->v4l2_dev, "IMR device opened (refcount=%u)\n", imr->refcount); ++ ++ return 0; ++ ++device_prepare_rollback: ++ /* ...unlock global device data */ ++ mutex_unlock(&imr->mutex); ++ ++v4l_prepare_rollback: ++ /* ...destroy context */ ++ v4l2_fh_del(&ctx->fh); ++ v4l2_fh_exit(&ctx->fh); ++ kfree(ctx); ++ ++ return ret; ++} ++ ++static int imr_release(struct file *file) ++{ ++ struct imr_device *imr = video_drvdata(file); ++ struct imr_ctx *ctx = fh_to_ctx(file->private_data); ++ ++ /* ...I don't need to get a device-scope lock here really - tbd */ ++ mutex_lock(&imr->mutex); ++ ++ /* ...destroy M2M device processing context */ ++ v4l2_m2m_ctx_release(ctx->m2m_ctx); ++ //v4l2_ctrl_handler_free(&ctx->ctrl_handler); ++ v4l2_fh_del(&ctx->fh); ++ v4l2_fh_exit(&ctx->fh); ++ ++ /* ...drop active configuration as needed */ ++ imr_cfg_unref(ctx, ctx->cfg); ++ ++ /* ...make sure there are no more active configs */ ++ WARN_ON(ctx->cfg_num); ++ ++ /* ...destroy context data */ ++ kfree(ctx); ++ ++ /* ...disable hardware operation */ ++ if (--imr->refcount == 0) ++ clk_disable_unprepare(imr->clock); ++ ++ mutex_unlock(&imr->mutex); ++ ++ v4l2_dbg(1, debug, &imr->v4l2_dev, "closed device instance\n"); ++ ++ return 0; ++} ++ ++static unsigned int imr_poll(struct file *file, struct poll_table_struct *wait) ++{ ++ struct imr_device *imr = video_drvdata(file); ++ struct imr_ctx *ctx = fh_to_ctx(file->private_data); ++ unsigned int res; ++ ++ if (mutex_lock_interruptible(&imr->mutex)) ++ return -ERESTARTSYS; ++ ++ res = v4l2_m2m_poll(file, ctx->m2m_ctx, wait); ++ mutex_unlock(&imr->mutex); ++ ++ return res; ++} ++ ++static int imr_mmap(struct file *file, struct vm_area_struct *vma) ++{ ++ struct imr_device *imr = video_drvdata(file); ++ struct imr_ctx *ctx = fh_to_ctx(file->private_data); ++ int ret; ++ ++ /* ...should we protect all M2M operations with mutex? - tbd */ ++ if (mutex_lock_interruptible(&imr->mutex)) ++ return -ERESTARTSYS; ++ ++ ret = v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); ++ ++ mutex_unlock(&imr->mutex); ++ ++ return ret; ++} ++ ++static const struct v4l2_file_operations imr_fops = { ++ .owner = THIS_MODULE, ++ .open = imr_open, ++ .release = imr_release, ++ .poll = imr_poll, ++ .mmap = imr_mmap, ++ .unlocked_ioctl = video_ioctl2, ++}; ++ ++/******************************************************************************* ++ * M2M device interface ++ ******************************************************************************/ ++ ++#if 0 ++/* ...job cleanup function */ ++static void imr_cleanup(struct imr_ctx *ctx) ++{ ++ struct imr_device *imr = ctx->imr; ++ struct vb2_v4l2_buffer *src_buf, *dst_buf; ++ unsigned long flags; ++ ++ /* ...interlock buffer handling with interrupt */ ++ spin_lock_irqsave(&imr->lock, flags); ++ ++ while ((src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx)) != NULL) ++ v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); ++ ++ while ((dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx)) != NULL) ++ v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); ++ ++ /* ...release lock before we mark current job as finished */ ++ spin_unlock_irqrestore(&imr->lock, flags); ++} ++#endif ++ ++/* ...job execution function */ ++static void imr_device_run(void *priv) ++{ ++ struct imr_ctx *ctx = priv; ++ struct imr_device *imr = ctx->imr; ++ struct imr_cfg *cfg; ++ struct vb2_buffer *src_buf, *dst_buf; ++ u32 src_addr, dst_addr; ++ unsigned long flags; ++ ++ v4l2_dbg(3, debug, &imr->v4l2_dev, "run next job...\n"); ++ ++ /* ...protect access to internal device state */ ++ spin_lock_irqsave(&imr->lock, flags); ++ ++ /* ...retrieve input/output buffers */ ++ src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); ++ dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); ++ ++ /* ...take configuration pointer associated with input buffer */ ++ cfg = to_imr_buffer(to_vb2_v4l2_buffer(src_buf))->cfg; ++ ++ /* ...cancel software reset state as needed */ ++ iowrite32(0, imr->mmio + IMR_CR); ++ ++ /* ...set cropping data with respect to destination sub-pixel mode */ ++ iowrite32(ctx->crop[0] << cfg->dst_subpixel, imr->mmio + IMR_XMINR); ++ iowrite32(ctx->crop[1] << cfg->dst_subpixel, imr->mmio + IMR_XMAXR); ++ iowrite32(ctx->crop[2] << cfg->dst_subpixel, imr->mmio + IMR_YMINR); ++ iowrite32(ctx->crop[3] << cfg->dst_subpixel, imr->mmio + IMR_YMAXR); ++ ++ /* ...adjust source/destination parameters of the program (interleaved / semiplanar) */ ++ *cfg->src_pa_ptr[0] = src_addr = (u32)vb2_dma_contig_plane_dma_addr(src_buf, 0); ++ *cfg->dst_pa_ptr[0] = dst_addr = (u32)vb2_dma_contig_plane_dma_addr(dst_buf, 0); ++ ++ /* ...adjust source/destination parameters of the UV-plane as needed */ ++ if (cfg->src_pa_ptr[1] && cfg->dst_pa_ptr[1]) { ++ *cfg->src_pa_ptr[1] = src_addr + ctx->queue[0].fmt.width * ctx->queue[0].fmt.height; ++ *cfg->dst_pa_ptr[1] = dst_addr + ctx->queue[1].fmt.width * ctx->queue[1].fmt.height; ++ } ++ ++ v4l2_dbg(3, debug, &imr->v4l2_dev, "process buffer-pair 0x%08x:0x%08x\n", ++ *cfg->src_pa_ptr[0], *cfg->dst_pa_ptr[0]); ++ ++ /* ...force clearing of status register bits */ ++ iowrite32(0x7, imr->mmio + IMR_SRCR); ++ ++ /* ...unmask/enable interrupts */ ++ iowrite32(ioread32(imr->mmio + IMR_ICR) | (IMR_ICR_TRAEN | IMR_ICR_IEREN | IMR_ICR_INTEN), imr->mmio + IMR_ICR); ++ iowrite32(ioread32(imr->mmio + IMR_IMR) & ~(IMR_ICR_TRAEN | IMR_ICR_IEREN | IMR_ICR_INTEN), imr->mmio + IMR_IMR); ++ ++ /* ...set display list address */ ++ iowrite32(cfg->dl_dma_addr + cfg->dl_start_offset, imr->mmio + IMR_DLSAR); ++ ++ /* ...explicitly flush any pending write operations (don't need that, I guess) */ ++ wmb(); ++ ++ /* ...start rendering operation */ ++ iowrite32(IMR_CR_RS, imr->mmio + IMR_CR); ++ ++ /* ...timestamp input buffer */ ++ src_buf->timestamp = ktime_get_ns(); ++ ++ /* ...unlock device access */ ++ spin_unlock_irqrestore(&imr->lock, flags); ++ ++ v4l2_dbg(1, debug, &imr->v4l2_dev, "rendering started: status=%X, DLSAR=0x%08X, DLPR=0x%08X\n", ioread32(imr->mmio + IMR_SR), ioread32(imr->mmio + IMR_DLSAR), ioread32(imr->mmio + IMR_DLSR)); ++} ++ ++/* ...check whether a job is ready for execution */ ++static int imr_job_ready(void *priv) ++{ ++ /* ...no specific requirements on the job readiness */ ++ return 1; ++} ++ ++/* ...abort currently processed job */ ++static void imr_job_abort(void *priv) ++{ ++ struct imr_ctx *ctx = priv; ++ struct imr_device *imr = ctx->imr; ++ unsigned long flags; ++ ++ /* ...protect access to internal device state */ ++ spin_lock_irqsave(&imr->lock, flags); ++ ++ /* ...make sure current job is still current (may get finished by interrupt already) */ ++ if (v4l2_m2m_get_curr_priv(imr->m2m_dev) == ctx) { ++ v4l2_dbg(1, debug, &imr->v4l2_dev, "abort job: status=%X, DLSAR=0x%08X, DLPR=0x%08X\n", ++ ioread32(imr->mmio + IMR_SR), ioread32(imr->mmio + IMR_DLSAR), ioread32(imr->mmio + IMR_DLSR)); ++ ++ /* ...force device reset to stop processing of the buffers */ ++ //iowrite32(IMR_CR_SWRST, imr->mmio + IMR_CR); ++ ++ /* ...resetting the module while operation is active may lead to hw-stall */ ++ spin_unlock_irqrestore(&imr->lock, flags); ++ ++ /* ...finish current job as interrupt will probably not occur */ ++ //v4l2_m2m_job_finish(imr->m2m_dev, ctx->m2m_ctx); ++ } else { ++ spin_unlock_irqrestore(&imr->lock, flags); ++ v4l2_dbg(1, debug, &imr->v4l2_dev, "job has completed already\n"); ++ } ++} ++ ++/* ...M2M interface definition */ ++static struct v4l2_m2m_ops imr_m2m_ops = { ++ .device_run = imr_device_run, ++ .job_ready = imr_job_ready, ++ .job_abort = imr_job_abort, ++}; ++ ++/******************************************************************************* ++ * Interrupt handling ++ ******************************************************************************/ ++ ++static irqreturn_t imr_irq_handler(int irq, void *data) ++{ ++ struct imr_device *imr = data; ++ struct imr_ctx *ctx; ++ struct vb2_v4l2_buffer *src_buf, *dst_buf; ++ u32 status; ++ irqreturn_t ret = IRQ_NONE; ++ ++ /* ...check and ack interrupt status */ ++ status = ioread32(imr->mmio + IMR_SR); ++ iowrite32(status, imr->mmio + IMR_SRCR); ++ if (!(status & (IMR_SR_INT | IMR_SR_IER | IMR_SR_TRA))) { ++ v4l2_err(&imr->v4l2_dev, "spurious interrupt: %x\n", status); ++ return ret; ++ } ++ ++ /* ...protect access to current context */ ++ spin_lock(&imr->lock); ++ ++ /* ...get current job context (may have been cancelled already) */ ++ ctx = v4l2_m2m_get_curr_priv(imr->m2m_dev); ++ if (!ctx) { ++ v4l2_dbg(3, debug, &imr->v4l2_dev, "no active job\n"); ++ goto handled; ++ } ++ ++ /* ...remove buffers (may have been removed already?) */ ++ src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); ++ dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); ++ if (!src_buf || !dst_buf) { ++ v4l2_dbg(3, debug, &imr->v4l2_dev, "no buffers associated with current context\n"); ++ goto handled; ++ } ++ ++ /* ...check for a TRAP interrupt indicating completion of current DL */ ++ if (status & IMR_SR_TRA) { ++ /* ...operation completed normally; timestamp output buffer */ ++ dst_buf->vb2_buf.timestamp = ktime_get_ns(); ++ if (src_buf->flags & V4L2_BUF_FLAG_TIMECODE) ++ dst_buf->timecode = src_buf->timecode; ++ dst_buf->flags = src_buf->flags & (V4L2_BUF_FLAG_TIMECODE | V4L2_BUF_FLAG_KEYFRAME | ++ V4L2_BUF_FLAG_PFRAME | V4L2_BUF_FLAG_BFRAME | V4L2_BUF_FLAG_TSTAMP_SRC_MASK); ++ dst_buf->sequence = src_buf->sequence = ctx->sequence++; ++ v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); ++ v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE); ++ ++ v4l2_dbg(3, debug, &imr->v4l2_dev, "buffers <0x%08x,0x%08x> done\n", ++ (u32)vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0), ++ (u32)vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0)); ++ } else { ++ /* ...operation completed in error; no way to understand what exactly went wrong */ ++ v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); ++ v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); ++ ++ v4l2_dbg(3, debug, &imr->v4l2_dev, "buffers <0x%08x,0x%08x> done in error\n", ++ (u32)vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0), ++ (u32)vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0)); ++ } ++ ++ spin_unlock(&imr->lock); ++ ++ /* ...finish current job (and start any pending) */ ++ v4l2_m2m_job_finish(imr->m2m_dev, ctx->m2m_ctx); ++ ++ return IRQ_HANDLED; ++ ++handled: ++ /* ...again, what exactly is to be protected? */ ++ spin_unlock(&imr->lock); ++ ++ return IRQ_HANDLED; ++} ++ ++/******************************************************************************* ++ * Device probing / removal interface ++ ******************************************************************************/ ++ ++static int imr_probe(struct platform_device *pdev) ++{ ++ struct imr_device *imr; ++ struct resource *res; ++ int ret; ++ ++ imr = devm_kzalloc(&pdev->dev, sizeof(*imr), GFP_KERNEL); ++ if (!imr) ++ return -ENOMEM; ++ ++ mutex_init(&imr->mutex); ++ spin_lock_init(&imr->lock); ++ imr->dev = &pdev->dev; ++ ++ /* ...memory-mapped registers */ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ dev_err(&pdev->dev, "cannot get memory region\n"); ++ return -EINVAL; ++ } ++ ++ imr->mmio = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(imr->mmio)) ++ return PTR_ERR(imr->mmio); ++ ++ /* ...interrupt service routine registration */ ++ imr->irq = ret = platform_get_irq(pdev, 0); ++ if (ret < 0) { ++ dev_err(&pdev->dev, "cannot find IRQ\n"); ++ return ret; ++ } ++ ++ ret = devm_request_irq(&pdev->dev, imr->irq, imr_irq_handler, 0, dev_name(&pdev->dev), imr); ++ if (ret) { ++ dev_err(&pdev->dev, "cannot claim IRQ %d\n", imr->irq); ++ return ret; ++ } ++ ++ imr->clock = devm_clk_get(&pdev->dev, NULL); ++ if (IS_ERR(imr->clock)) { ++ dev_err(&pdev->dev, "cannot get clock\n"); ++ return PTR_ERR(imr->clock); ++ } ++ ++ /* ...create v4l2 device */ ++ ret = v4l2_device_register(&pdev->dev, &imr->v4l2_dev); ++ if (ret) { ++ dev_err(&pdev->dev, "Failed to register v4l2 device\n"); ++ return ret; ++ } ++ ++ /* ...create mem2mem device handle */ ++ imr->m2m_dev = v4l2_m2m_init(&imr_m2m_ops); ++ if (IS_ERR(imr->m2m_dev)) { ++ v4l2_err(&imr->v4l2_dev, "Failed to init mem2mem device\n"); ++ ret = PTR_ERR(imr->m2m_dev); ++ goto device_register_rollback; ++ } ++ ++ strlcpy(imr->video_dev.name, dev_name(&pdev->dev), sizeof(imr->video_dev.name)); ++ imr->video_dev.fops = &imr_fops; ++ imr->video_dev.ioctl_ops = &imr_ioctl_ops; ++ imr->video_dev.minor = -1; ++ imr->video_dev.release = video_device_release_empty; ++ imr->video_dev.lock = &imr->mutex; ++ imr->video_dev.v4l2_dev = &imr->v4l2_dev; ++ imr->video_dev.vfl_dir = VFL_DIR_M2M; ++ ++ ret = video_register_device(&imr->video_dev, VFL_TYPE_GRABBER, -1); ++ if (ret) { ++ v4l2_err(&imr->v4l2_dev, "Failed to register video device\n"); ++ goto m2m_init_rollback; ++ } ++ ++ video_set_drvdata(&imr->video_dev, imr); ++ platform_set_drvdata(pdev, imr); ++ //pm_runtime_enable(&pdev->dev); ++ ++ v4l2_info(&imr->v4l2_dev, "IMR device (pdev: %d) registered as /dev/video%d\n", pdev->id, imr->video_dev.num); ++ ++ return 0; ++ ++m2m_init_rollback: ++ v4l2_m2m_release(imr->m2m_dev); ++ ++device_register_rollback: ++ v4l2_device_unregister(&imr->v4l2_dev); ++ ++ return ret; ++} ++ ++static int imr_remove(struct platform_device *pdev) ++{ ++ struct imr_device *imr = platform_get_drvdata(pdev); ++ ++ //pm_runtime_disable(imr->v4l2_dev.dev); ++ video_unregister_device(&imr->video_dev); ++ v4l2_m2m_release(imr->m2m_dev); ++ v4l2_device_unregister(&imr->v4l2_dev); ++ ++ return 0; ++} ++ ++/******************************************************************************* ++ * Power management ++ ******************************************************************************/ ++ ++#ifdef CONFIG_PM_SLEEP ++ ++/* ...device suspend hook; clock control only - tbd */ ++static int imr_pm_suspend(struct device *dev) ++{ ++ struct imr_device *imr = dev_get_drvdata(dev); ++ ++ WARN_ON(mutex_is_locked(&imr->mutex)); ++ ++ if (imr->refcount == 0) ++ return 0; ++ ++ clk_disable_unprepare(imr->clock); ++ ++ return 0; ++} ++ ++/* ...device resume hook; clock control only */ ++static int imr_pm_resume(struct device *dev) ++{ ++ struct imr_device *imr = dev_get_drvdata(dev); ++ ++ WARN_ON(mutex_is_locked(&imr->mutex)); ++ ++ if (imr->refcount == 0) ++ return 0; ++ ++ clk_prepare_enable(imr->clock); ++ ++ return 0; ++} ++ ++#endif /* CONFIG_PM_SLEEP */ ++ ++/* ...power management callbacks */ ++static const struct dev_pm_ops imr_pm_ops = { ++ SET_SYSTEM_SLEEP_PM_OPS(imr_pm_suspend, imr_pm_resume) ++}; ++ ++/* ...device table */ ++static const struct of_device_id imr_of_match[] = { ++ { .compatible = "renesas,imr-lx4" }, ++ { }, ++}; ++ ++/* ...platform driver interface */ ++static struct platform_driver imr_platform_driver = { ++ .probe = imr_probe, ++ .remove = imr_remove, ++ .driver = { ++ .owner = THIS_MODULE, ++ .name = "imr", ++ .pm = &imr_pm_ops, ++ .of_match_table = imr_of_match, ++ }, ++}; ++ ++module_platform_driver(imr_platform_driver); ++ ++MODULE_ALIAS("imr"); ++MODULE_AUTHOR("Cogent Embedded Inc. <sources@cogentembedded.com>"); ++MODULE_DESCRIPTION("Renesas IMR-LX4 Driver"); ++MODULE_LICENSE("GPL"); +diff --git a/include/uapi/linux/rcar-imr.h b/include/uapi/linux/rcar-imr.h +new file mode 100644 +index 0000000..d02082f +--- /dev/null ++++ b/include/uapi/linux/rcar-imr.h +@@ -0,0 +1,98 @@ ++/* ++ * imr.h -- R-Car IMR-LX4 Driver UAPI ++ * ++ * Copyright (C) 2016 Cogent Embedded, Inc. <source@cogentembedded.com> ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ */ ++ ++#ifndef RCAR_IMR_USER_H ++#define RCAR_IMR_USER_H ++ ++#include <linux/videodev2.h> ++ ++/******************************************************************************* ++ * Mapping specification descriptor ++ ******************************************************************************/ ++ ++struct imr_map_desc { ++ /* ...mapping types */ ++ u32 type; ++ ++ /* ...total size of the mesh structure */ ++ u32 size; ++ ++ /* ...map-specific user-pointer */ ++ void *data; ++ ++} __attribute__((packed)); ++ ++/* ...regular mesh specification */ ++#define IMR_MAP_MESH (1 << 0) ++ ++/* ...auto-generated source coordinates */ ++#define IMR_MAP_AUTODG (1 << 1) ++ ++/* ...auto-generated destination coordinates */ ++#define IMR_MAP_AUTOSG (1 << 2) ++ ++/* ...luminance correction flag */ ++#define IMR_MAP_LUCE (1 << 3) ++ ++/* ...chromacity correction flag */ ++#define IMR_MAP_CLCE (1 << 4) ++ ++/* ...vertex clockwise-mode order */ ++#define IMR_MAP_TCM (1 << 5) ++ ++/* ...source coordinate decimal point position bit index */ ++#define __IMR_MAP_UVDPOR_SHIFT 8 ++#define __IMR_MAP_UVDPOR(v) (((v) >> __IMR_MAP_UVDPOR_SHIFT) & 0x7) ++#define IMR_MAP_UVDPOR(n) ((n & 0x7) << __IMR_MAP_UVDPOR_SHIFT) ++ ++/* ...destination coordinate sub-pixel mode */ ++#define IMR_MAP_DDP (1 << 11) ++ ++/* ...luminance correction offset decimal point position */ ++#define __IMR_MAP_YLDPO_SHIFT 12 ++#define __IMR_MAP_YLDPO(v) (((v) >> __IMR_MAP_YLDPO_SHIFT) & 0x7) ++#define IMR_MAP_YLDPO(n) ((n & 0x7) << __IMR_MAP_YLDPO_SHIFT) ++ ++/* ...chromacity (U) correction offset decimal point position */ ++#define __IMR_MAP_UBDPO_SHIFT 15 ++#define __IMR_MAP_UBDPO(v) (((v) >> __IMR_MAP_UBDPO_SHIFT) & 0x7) ++#define IMR_MAP_UBDPO(n) ((n & 0x7) << __IMR_MAP_UBDPO_SHIFT) ++ ++/* ...chromacity (V) correction offset decimal point position */ ++#define __IMR_MAP_VRDPO_SHIFT 18 ++#define __IMR_MAP_VRDPO(v) (((v) >> __IMR_MAP_VRDPO_SHIFT) & 0x7) ++#define IMR_MAP_VRDPO(n) ((n & 0x7) << __IMR_MAP_VRDPO_SHIFT) ++ ++/* ...regular mesh specification */ ++struct imr_mesh { ++ /* ...rectangular mesh size */ ++ u16 rows, columns; ++ ++ /* ...mesh parameters */ ++ u16 x0, y0, dx, dy; ++ ++} __attribute__((packed)); ++ ++/* ...VBO descriptor */ ++struct imr_vbo { ++ /* ...number of triangles */ ++ u16 num; ++ ++} __attribute__((packed)); ++ ++ ++/******************************************************************************* ++ * Private IOCTL codes ++ ******************************************************************************/ ++ ++#define VIDIOC_IMR_MESH _IOW('V', BASE_VIDIOC_PRIVATE + 0, struct imr_map_desc) ++ ++#endif /* RCAR_IMR_USER_H */ +-- +1.9.1 + |