diff options
Diffstat (limited to 'drivers/media/platform')
103 files changed, 6676 insertions, 2559 deletions
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index c7a1cf8a1b01..2728376b04b5 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -26,6 +26,7 @@ config VIDEO_VIA_CAMERA # # Platform multimedia device configuration # +source "drivers/media/platform/cadence/Kconfig" source "drivers/media/platform/davinci/Kconfig" @@ -34,7 +35,7 @@ source "drivers/media/platform/omap/Kconfig" config VIDEO_SH_VOU tristate "SuperH VOU video output driver" depends on MEDIA_CAMERA_SUPPORT - depends on VIDEO_DEV && I2C && HAS_DMA + depends on VIDEO_DEV && I2C depends on ARCH_SHMOBILE || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG help @@ -42,7 +43,7 @@ config VIDEO_SH_VOU config VIDEO_VIU tristate "Freescale VIU Video Driver" - depends on VIDEO_V4L2 && PPC_MPC512x + depends on VIDEO_V4L2 && (PPC_MPC512x || COMPILE_TEST) && I2C select VIDEOBUF_DMA_CONTIG default y ---help--- @@ -62,10 +63,10 @@ config VIDEO_MUX config VIDEO_OMAP3 tristate "OMAP 3 Camera support" - depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API && ARCH_OMAP3 - depends on HAS_DMA && OF - depends on OMAP_IOMMU - select ARM_DMA_USE_IOMMU + depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API + depends on (ARCH_OMAP3 && OMAP_IOMMU) || COMPILE_TEST + depends on COMMON_CLK && OF + select ARM_DMA_USE_IOMMU if OMAP_IOMMU select VIDEOBUF2_DMA_CONTIG select MFD_SYSCON select V4L2_FWNODE @@ -80,7 +81,7 @@ config VIDEO_OMAP3_DEBUG config VIDEO_PXA27x tristate "PXA27x Quick Capture Interface driver" - depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA + depends on VIDEO_DEV && VIDEO_V4L2 depends on PXA27x || COMPILE_TEST select VIDEOBUF2_DMA_SG select SG_SPLIT @@ -90,7 +91,7 @@ config VIDEO_PXA27x config VIDEO_QCOM_CAMSS tristate "Qualcomm 8x16 V4L2 Camera Subsystem driver" - depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && HAS_DMA + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API depends on (ARCH_QCOM && IOMMU_DMA) || COMPILE_TEST select VIDEOBUF2_DMA_SG select V4L2_FWNODE @@ -100,7 +101,6 @@ config VIDEO_S3C_CAMIF depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API depends on PM depends on ARCH_S3C64XX || PLAT_S3C24XX || COMPILE_TEST - depends on HAS_DMA select VIDEOBUF2_DMA_CONTIG ---help--- This is a v4l2 driver for s3c24xx and s3c64xx SoC series camera @@ -111,7 +111,7 @@ config VIDEO_S3C_CAMIF config VIDEO_STM32_DCMI tristate "STM32 Digital Camera Memory Interface (DCMI) support" - depends on VIDEO_V4L2 && OF && HAS_DMA + depends on VIDEO_V4L2 && OF depends on ARCH_STM32 || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG select V4L2_FWNODE @@ -124,7 +124,7 @@ config VIDEO_STM32_DCMI config VIDEO_RENESAS_CEU tristate "Renesas Capture Engine Unit (CEU) driver" - depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA + depends on VIDEO_DEV && VIDEO_V4L2 depends on ARCH_SHMOBILE || ARCH_R7S72100 || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG select V4L2_FWNODE @@ -142,7 +142,6 @@ config VIDEO_TI_CAL tristate "TI CAL (Camera Adaptation Layer) driver" depends on VIDEO_DEV && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API depends on SOC_DRA7XX || COMPILE_TEST - depends on HAS_DMA select VIDEOBUF2_DMA_CONTIG select V4L2_FWNODE default n @@ -170,7 +169,6 @@ if V4L_MEM2MEM_DRIVERS config VIDEO_CODA tristate "Chips&Media Coda multi-standard codec IP" depends on VIDEO_DEV && VIDEO_V4L2 && (ARCH_MXC || COMPILE_TEST) - depends on HAS_DMA select SRAM select VIDEOBUF2_DMA_CONTIG select VIDEOBUF2_VMALLOC @@ -188,7 +186,6 @@ config VIDEO_MEDIATEK_JPEG depends on MTK_IOMMU_V1 || COMPILE_TEST depends on VIDEO_DEV && VIDEO_V4L2 depends on ARCH_MEDIATEK || COMPILE_TEST - depends on HAS_DMA select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV ---help--- @@ -200,7 +197,7 @@ config VIDEO_MEDIATEK_JPEG config VIDEO_MEDIATEK_VPU tristate "Mediatek Video Processor Unit" - depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA + depends on VIDEO_DEV && VIDEO_V4L2 depends on ARCH_MEDIATEK || COMPILE_TEST ---help--- This driver provides downloading VPU firmware and @@ -216,7 +213,6 @@ config VIDEO_MEDIATEK_MDP depends on MTK_IOMMU || COMPILE_TEST depends on VIDEO_DEV && VIDEO_V4L2 depends on ARCH_MEDIATEK || COMPILE_TEST - depends on HAS_DMA select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV select VIDEO_MEDIATEK_VPU @@ -231,7 +227,7 @@ config VIDEO_MEDIATEK_MDP config VIDEO_MEDIATEK_VCODEC tristate "Mediatek Video Codec driver" depends on MTK_IOMMU || COMPILE_TEST - depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA + depends on VIDEO_DEV && VIDEO_V4L2 depends on ARCH_MEDIATEK || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV @@ -247,7 +243,7 @@ config VIDEO_MEDIATEK_VCODEC config VIDEO_MEM2MEM_DEINTERLACE tristate "Deinterlace support" - depends on VIDEO_DEV && VIDEO_V4L2 && DMA_ENGINE + depends on VIDEO_DEV && VIDEO_V4L2 depends on HAS_DMA select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV @@ -258,7 +254,6 @@ config VIDEO_SAMSUNG_S5P_G2D tristate "Samsung S5P and EXYNOS4 G2D 2d graphics accelerator driver" depends on VIDEO_DEV && VIDEO_V4L2 depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST - depends on HAS_DMA select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV default n @@ -270,7 +265,6 @@ config VIDEO_SAMSUNG_S5P_JPEG tristate "Samsung S5P/Exynos3250/Exynos4 JPEG codec driver" depends on VIDEO_DEV && VIDEO_V4L2 depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST - depends on HAS_DMA select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV ---help--- @@ -281,7 +275,6 @@ config VIDEO_SAMSUNG_S5P_MFC tristate "Samsung S5P MFC Video Codec" depends on VIDEO_DEV && VIDEO_V4L2 depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST - depends on HAS_DMA select VIDEOBUF2_DMA_CONTIG default n help @@ -291,7 +284,6 @@ config VIDEO_MX2_EMMAPRP tristate "MX2 eMMa-PrP support" depends on VIDEO_DEV && VIDEO_V4L2 depends on SOC_IMX27 || COMPILE_TEST - depends on HAS_DMA select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV help @@ -303,7 +295,6 @@ config VIDEO_SAMSUNG_EXYNOS_GSC tristate "Samsung Exynos G-Scaler driver" depends on VIDEO_DEV && VIDEO_V4L2 depends on ARCH_EXYNOS || COMPILE_TEST - depends on HAS_DMA select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV help @@ -312,7 +303,6 @@ config VIDEO_SAMSUNG_EXYNOS_GSC config VIDEO_STI_BDISP tristate "STMicroelectronics BDISP 2D blitter driver" depends on VIDEO_DEV && VIDEO_V4L2 - depends on HAS_DMA depends on ARCH_STI || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV @@ -322,7 +312,6 @@ config VIDEO_STI_BDISP config VIDEO_STI_HVA tristate "STMicroelectronics HVA multi-format video encoder V4L2 driver" depends on VIDEO_DEV && VIDEO_V4L2 - depends on HAS_DMA depends on ARCH_STI || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV @@ -349,7 +338,6 @@ config VIDEO_STI_DELTA tristate "STMicroelectronics DELTA multi-format video decoder V4L2 driver" depends on VIDEO_DEV && VIDEO_V4L2 depends on ARCH_STI || COMPILE_TEST - depends on HAS_DMA help This V4L2 driver enables DELTA multi-format video decoder of STMicroelectronics STiH4xx SoC series allowing hardware @@ -395,7 +383,7 @@ config VIDEO_SH_VEU config VIDEO_RENESAS_FDP1 tristate "Renesas Fine Display Processor" - depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA + depends on VIDEO_DEV && VIDEO_V4L2 depends on ARCH_SHMOBILE || COMPILE_TEST depends on (!ARCH_RENESAS && !VIDEO_RENESAS_FCP) || VIDEO_RENESAS_FCP select VIDEOBUF2_DMA_CONTIG @@ -409,7 +397,7 @@ config VIDEO_RENESAS_FDP1 config VIDEO_RENESAS_JPU tristate "Renesas JPEG Processing Unit" - depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA + depends on VIDEO_DEV && VIDEO_V4L2 depends on ARCH_RENESAS || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV @@ -434,8 +422,8 @@ config VIDEO_RENESAS_FCP config VIDEO_RENESAS_VSP1 tristate "Renesas VSP1 Video Processing Engine" - depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && HAS_DMA - depends on (ARCH_RENESAS && OF) || COMPILE_TEST + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + depends on ARCH_RENESAS || COMPILE_TEST depends on (!ARM64 && !VIDEO_RENESAS_FCP) || VIDEO_RENESAS_FCP select VIDEOBUF2_DMA_CONTIG select VIDEOBUF2_VMALLOC @@ -447,7 +435,7 @@ config VIDEO_RENESAS_VSP1 config VIDEO_ROCKCHIP_RGA tristate "Rockchip Raster 2d Graphic Acceleration Unit" - depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA + depends on VIDEO_DEV && VIDEO_V4L2 depends on ARCH_ROCKCHIP || COMPILE_TEST select VIDEOBUF2_DMA_SG select V4L2_MEM2MEM_DEV @@ -464,7 +452,6 @@ config VIDEO_TI_VPE tristate "TI VPE (Video Processing Engine) driver" depends on VIDEO_DEV && VIDEO_V4L2 depends on SOC_DRA7XX || COMPILE_TEST - depends on HAS_DMA select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV select VIDEO_TI_VPDMA @@ -483,7 +470,7 @@ config VIDEO_TI_VPE_DEBUG config VIDEO_QCOM_VENUS tristate "Qualcomm Venus V4L2 encoder/decoder driver" - depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA + depends on VIDEO_DEV && VIDEO_V4L2 depends on (ARCH_QCOM && IOMMU_DMA) || COMPILE_TEST select QCOM_MDT_LOADER if ARCH_QCOM select QCOM_SCM if ARCH_QCOM @@ -558,7 +545,7 @@ config VIDEO_MESON_AO_CEC config CEC_GPIO tristate "Generic GPIO-based CEC driver" - depends on PREEMPT + depends on PREEMPT || COMPILE_TEST select CEC_CORE select CEC_PIN select GPIOLIB @@ -625,7 +612,7 @@ if SDR_PLATFORM_DRIVERS config VIDEO_RCAR_DRIF tristate "Renesas Digitial Radio Interface (DRIF)" - depends on VIDEO_V4L2 && HAS_DMA + depends on VIDEO_V4L2 depends on ARCH_RENESAS || COMPILE_TEST select VIDEOBUF2_VMALLOC ---help--- diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 932515df4477..04bc1502a30e 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -3,6 +3,7 @@ # Makefile for the video capture/playback device drivers. # +obj-$(CONFIG_VIDEO_CADENCE) += cadence/ obj-$(CONFIG_VIDEO_VIA_CAMERA) += via-camera.o obj-$(CONFIG_VIDEO_CAFE_CCIC) += marvell-ccic/ obj-$(CONFIG_VIDEO_MMP_CAMERA) += marvell-ccic/ diff --git a/drivers/media/platform/am437x/Kconfig b/drivers/media/platform/am437x/Kconfig index 160e77e9a0fb..f4ce1176e4dc 100644 --- a/drivers/media/platform/am437x/Kconfig +++ b/drivers/media/platform/am437x/Kconfig @@ -1,6 +1,6 @@ config VIDEO_AM437X_VPFE tristate "TI AM437x VPFE video capture driver" - depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && HAS_DMA + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API depends on SOC_AM43XX || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG select V4L2_FWNODE diff --git a/drivers/media/platform/am437x/am437x-vpfe.c b/drivers/media/platform/am437x/am437x-vpfe.c index 601ae6487617..58ebc2220d0e 100644 --- a/drivers/media/platform/am437x/am437x-vpfe.c +++ b/drivers/media/platform/am437x/am437x-vpfe.c @@ -2662,8 +2662,7 @@ static void vpfe_save_context(struct vpfe_ccdc *ccdc) static int vpfe_suspend(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct vpfe_device *vpfe = platform_get_drvdata(pdev); + struct vpfe_device *vpfe = dev_get_drvdata(dev); struct vpfe_ccdc *ccdc = &vpfe->ccdc; /* if streaming has not started we don't care */ @@ -2720,8 +2719,7 @@ static void vpfe_restore_context(struct vpfe_ccdc *ccdc) static int vpfe_resume(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct vpfe_device *vpfe = platform_get_drvdata(pdev); + struct vpfe_device *vpfe = dev_get_drvdata(dev); struct vpfe_ccdc *ccdc = &vpfe->ccdc; /* if streaming has not started we don't care */ diff --git a/drivers/media/platform/atmel/Kconfig b/drivers/media/platform/atmel/Kconfig index 55de751e5f51..a211ef20f77e 100644 --- a/drivers/media/platform/atmel/Kconfig +++ b/drivers/media/platform/atmel/Kconfig @@ -1,6 +1,6 @@ config VIDEO_ATMEL_ISC tristate "ATMEL Image Sensor Controller (ISC) support" - depends on VIDEO_V4L2 && COMMON_CLK && VIDEO_V4L2_SUBDEV_API && HAS_DMA + depends on VIDEO_V4L2 && COMMON_CLK && VIDEO_V4L2_SUBDEV_API depends on ARCH_AT91 || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG select REGMAP_MMIO @@ -11,7 +11,7 @@ config VIDEO_ATMEL_ISC config VIDEO_ATMEL_ISI tristate "ATMEL Image Sensor Interface (ISI) support" - depends on VIDEO_V4L2 && OF && HAS_DMA + depends on VIDEO_V4L2 && OF depends on ARCH_AT91 || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG select V4L2_FWNODE diff --git a/drivers/media/platform/cadence/Kconfig b/drivers/media/platform/cadence/Kconfig new file mode 100644 index 000000000000..3bf0f2454384 --- /dev/null +++ b/drivers/media/platform/cadence/Kconfig @@ -0,0 +1,34 @@ +config VIDEO_CADENCE + bool "Cadence Video Devices" + help + If you have a media device designed by Cadence, say Y. + + Note that this option doesn't include new drivers in the kernel: + saying N will just cause Kconfig to skip all the questions about + Cadence media devices. + +if VIDEO_CADENCE + +config VIDEO_CADENCE_CSI2RX + tristate "Cadence MIPI-CSI2 RX Controller" + depends on MEDIA_CONTROLLER + depends on VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + Support for the Cadence MIPI CSI2 Receiver controller. + + To compile this driver as a module, choose M here: the module will be + called cdns-csi2rx. + +config VIDEO_CADENCE_CSI2TX + tristate "Cadence MIPI-CSI2 TX Controller" + depends on MEDIA_CONTROLLER + depends on VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + Support for the Cadence MIPI CSI2 Transceiver controller. + + To compile this driver as a module, choose M here: the module will be + called cdns-csi2tx. + +endif diff --git a/drivers/media/platform/cadence/Makefile b/drivers/media/platform/cadence/Makefile new file mode 100644 index 000000000000..be59a8728a01 --- /dev/null +++ b/drivers/media/platform/cadence/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_VIDEO_CADENCE_CSI2RX) += cdns-csi2rx.o +obj-$(CONFIG_VIDEO_CADENCE_CSI2TX) += cdns-csi2tx.o diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c new file mode 100644 index 000000000000..a0f02916006b --- /dev/null +++ b/drivers/media/platform/cadence/cdns-csi2rx.c @@ -0,0 +1,498 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Cadence MIPI-CSI2 RX Controller v1.3 + * + * Copyright (C) 2017 Cadence Design Systems Inc. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +#define CSI2RX_DEVICE_CFG_REG 0x000 + +#define CSI2RX_SOFT_RESET_REG 0x004 +#define CSI2RX_SOFT_RESET_PROTOCOL BIT(1) +#define CSI2RX_SOFT_RESET_FRONT BIT(0) + +#define CSI2RX_STATIC_CFG_REG 0x008 +#define CSI2RX_STATIC_CFG_DLANE_MAP(llane, plane) ((plane) << (16 + (llane) * 4)) +#define CSI2RX_STATIC_CFG_LANES_MASK GENMASK(11, 8) + +#define CSI2RX_STREAM_BASE(n) (((n) + 1) * 0x100) + +#define CSI2RX_STREAM_CTRL_REG(n) (CSI2RX_STREAM_BASE(n) + 0x000) +#define CSI2RX_STREAM_CTRL_START BIT(0) + +#define CSI2RX_STREAM_DATA_CFG_REG(n) (CSI2RX_STREAM_BASE(n) + 0x008) +#define CSI2RX_STREAM_DATA_CFG_EN_VC_SELECT BIT(31) +#define CSI2RX_STREAM_DATA_CFG_VC_SELECT(n) BIT((n) + 16) + +#define CSI2RX_STREAM_CFG_REG(n) (CSI2RX_STREAM_BASE(n) + 0x00c) +#define CSI2RX_STREAM_CFG_FIFO_MODE_LARGE_BUF (1 << 8) + +#define CSI2RX_LANES_MAX 4 +#define CSI2RX_STREAMS_MAX 4 + +enum csi2rx_pads { + CSI2RX_PAD_SINK, + CSI2RX_PAD_SOURCE_STREAM0, + CSI2RX_PAD_SOURCE_STREAM1, + CSI2RX_PAD_SOURCE_STREAM2, + CSI2RX_PAD_SOURCE_STREAM3, + CSI2RX_PAD_MAX, +}; + +struct csi2rx_priv { + struct device *dev; + unsigned int count; + + /* + * Used to prevent race conditions between multiple, + * concurrent calls to start and stop. + */ + struct mutex lock; + + void __iomem *base; + struct clk *sys_clk; + struct clk *p_clk; + struct clk *pixel_clk[CSI2RX_STREAMS_MAX]; + struct phy *dphy; + + u8 lanes[CSI2RX_LANES_MAX]; + u8 num_lanes; + u8 max_lanes; + u8 max_streams; + bool has_internal_dphy; + + struct v4l2_subdev subdev; + struct v4l2_async_notifier notifier; + struct media_pad pads[CSI2RX_PAD_MAX]; + + /* Remote source */ + struct v4l2_async_subdev asd; + struct v4l2_subdev *source_subdev; + int source_pad; +}; + +static inline +struct csi2rx_priv *v4l2_subdev_to_csi2rx(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct csi2rx_priv, subdev); +} + +static void csi2rx_reset(struct csi2rx_priv *csi2rx) +{ + writel(CSI2RX_SOFT_RESET_PROTOCOL | CSI2RX_SOFT_RESET_FRONT, + csi2rx->base + CSI2RX_SOFT_RESET_REG); + + udelay(10); + + writel(0, csi2rx->base + CSI2RX_SOFT_RESET_REG); +} + +static int csi2rx_start(struct csi2rx_priv *csi2rx) +{ + unsigned int i; + unsigned long lanes_used = 0; + u32 reg; + int ret; + + ret = clk_prepare_enable(csi2rx->p_clk); + if (ret) + return ret; + + csi2rx_reset(csi2rx); + + reg = csi2rx->num_lanes << 8; + for (i = 0; i < csi2rx->num_lanes; i++) { + reg |= CSI2RX_STATIC_CFG_DLANE_MAP(i, csi2rx->lanes[i]); + set_bit(csi2rx->lanes[i], &lanes_used); + } + + /* + * Even the unused lanes need to be mapped. In order to avoid + * to map twice to the same physical lane, keep the lanes used + * in the previous loop, and only map unused physical lanes to + * the rest of our logical lanes. + */ + for (i = csi2rx->num_lanes; i < csi2rx->max_lanes; i++) { + unsigned int idx = find_first_zero_bit(&lanes_used, + sizeof(lanes_used)); + set_bit(idx, &lanes_used); + reg |= CSI2RX_STATIC_CFG_DLANE_MAP(i, i + 1); + } + + writel(reg, csi2rx->base + CSI2RX_STATIC_CFG_REG); + + ret = v4l2_subdev_call(csi2rx->source_subdev, video, s_stream, true); + if (ret) + goto err_disable_pclk; + + /* + * Create a static mapping between the CSI virtual channels + * and the output stream. + * + * This should be enhanced, but v4l2 lacks the support for + * changing that mapping dynamically. + * + * We also cannot enable and disable independent streams here, + * hence the reference counting. + */ + for (i = 0; i < csi2rx->max_streams; i++) { + ret = clk_prepare_enable(csi2rx->pixel_clk[i]); + if (ret) + goto err_disable_pixclk; + + writel(CSI2RX_STREAM_CFG_FIFO_MODE_LARGE_BUF, + csi2rx->base + CSI2RX_STREAM_CFG_REG(i)); + + writel(CSI2RX_STREAM_DATA_CFG_EN_VC_SELECT | + CSI2RX_STREAM_DATA_CFG_VC_SELECT(i), + csi2rx->base + CSI2RX_STREAM_DATA_CFG_REG(i)); + + writel(CSI2RX_STREAM_CTRL_START, + csi2rx->base + CSI2RX_STREAM_CTRL_REG(i)); + } + + ret = clk_prepare_enable(csi2rx->sys_clk); + if (ret) + goto err_disable_pixclk; + + clk_disable_unprepare(csi2rx->p_clk); + + return 0; + +err_disable_pixclk: + for (; i > 0; i--) + clk_disable_unprepare(csi2rx->pixel_clk[i - 1]); + +err_disable_pclk: + clk_disable_unprepare(csi2rx->p_clk); + + return ret; +} + +static void csi2rx_stop(struct csi2rx_priv *csi2rx) +{ + unsigned int i; + + clk_prepare_enable(csi2rx->p_clk); + clk_disable_unprepare(csi2rx->sys_clk); + + for (i = 0; i < csi2rx->max_streams; i++) { + writel(0, csi2rx->base + CSI2RX_STREAM_CTRL_REG(i)); + + clk_disable_unprepare(csi2rx->pixel_clk[i]); + } + + clk_disable_unprepare(csi2rx->p_clk); + + if (v4l2_subdev_call(csi2rx->source_subdev, video, s_stream, false)) + dev_warn(csi2rx->dev, "Couldn't disable our subdev\n"); +} + +static int csi2rx_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev); + int ret = 0; + + mutex_lock(&csi2rx->lock); + + if (enable) { + /* + * If we're not the first users, there's no need to + * enable the whole controller. + */ + if (!csi2rx->count) { + ret = csi2rx_start(csi2rx); + if (ret) + goto out; + } + + csi2rx->count++; + } else { + csi2rx->count--; + + /* + * Let the last user turn off the lights. + */ + if (!csi2rx->count) + csi2rx_stop(csi2rx); + } + +out: + mutex_unlock(&csi2rx->lock); + return ret; +} + +static const struct v4l2_subdev_video_ops csi2rx_video_ops = { + .s_stream = csi2rx_s_stream, +}; + +static const struct v4l2_subdev_ops csi2rx_subdev_ops = { + .video = &csi2rx_video_ops, +}; + +static int csi2rx_async_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *s_subdev, + struct v4l2_async_subdev *asd) +{ + struct v4l2_subdev *subdev = notifier->sd; + struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev); + + csi2rx->source_pad = media_entity_get_fwnode_pad(&s_subdev->entity, + s_subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (csi2rx->source_pad < 0) { + dev_err(csi2rx->dev, "Couldn't find output pad for subdev %s\n", + s_subdev->name); + return csi2rx->source_pad; + } + + csi2rx->source_subdev = s_subdev; + + dev_dbg(csi2rx->dev, "Bound %s pad: %d\n", s_subdev->name, + csi2rx->source_pad); + + return media_create_pad_link(&csi2rx->source_subdev->entity, + csi2rx->source_pad, + &csi2rx->subdev.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static const struct v4l2_async_notifier_operations csi2rx_notifier_ops = { + .bound = csi2rx_async_bound, +}; + +static int csi2rx_get_resources(struct csi2rx_priv *csi2rx, + struct platform_device *pdev) +{ + struct resource *res; + unsigned char i; + u32 dev_cfg; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + csi2rx->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(csi2rx->base)) + return PTR_ERR(csi2rx->base); + + csi2rx->sys_clk = devm_clk_get(&pdev->dev, "sys_clk"); + if (IS_ERR(csi2rx->sys_clk)) { + dev_err(&pdev->dev, "Couldn't get sys clock\n"); + return PTR_ERR(csi2rx->sys_clk); + } + + csi2rx->p_clk = devm_clk_get(&pdev->dev, "p_clk"); + if (IS_ERR(csi2rx->p_clk)) { + dev_err(&pdev->dev, "Couldn't get P clock\n"); + return PTR_ERR(csi2rx->p_clk); + } + + csi2rx->dphy = devm_phy_optional_get(&pdev->dev, "dphy"); + if (IS_ERR(csi2rx->dphy)) { + dev_err(&pdev->dev, "Couldn't get external D-PHY\n"); + return PTR_ERR(csi2rx->dphy); + } + + /* + * FIXME: Once we'll have external D-PHY support, the check + * will need to be removed. + */ + if (csi2rx->dphy) { + dev_err(&pdev->dev, "External D-PHY not supported yet\n"); + return -EINVAL; + } + + clk_prepare_enable(csi2rx->p_clk); + dev_cfg = readl(csi2rx->base + CSI2RX_DEVICE_CFG_REG); + clk_disable_unprepare(csi2rx->p_clk); + + csi2rx->max_lanes = dev_cfg & 7; + if (csi2rx->max_lanes > CSI2RX_LANES_MAX) { + dev_err(&pdev->dev, "Invalid number of lanes: %u\n", + csi2rx->max_lanes); + return -EINVAL; + } + + csi2rx->max_streams = (dev_cfg >> 4) & 7; + if (csi2rx->max_streams > CSI2RX_STREAMS_MAX) { + dev_err(&pdev->dev, "Invalid number of streams: %u\n", + csi2rx->max_streams); + return -EINVAL; + } + + csi2rx->has_internal_dphy = dev_cfg & BIT(3) ? true : false; + + /* + * FIXME: Once we'll have internal D-PHY support, the check + * will need to be removed. + */ + if (csi2rx->has_internal_dphy) { + dev_err(&pdev->dev, "Internal D-PHY not supported yet\n"); + return -EINVAL; + } + + for (i = 0; i < csi2rx->max_streams; i++) { + char clk_name[16]; + + snprintf(clk_name, sizeof(clk_name), "pixel_if%u_clk", i); + csi2rx->pixel_clk[i] = devm_clk_get(&pdev->dev, clk_name); + if (IS_ERR(csi2rx->pixel_clk[i])) { + dev_err(&pdev->dev, "Couldn't get clock %s\n", clk_name); + return PTR_ERR(csi2rx->pixel_clk[i]); + } + } + + return 0; +} + +static int csi2rx_parse_dt(struct csi2rx_priv *csi2rx) +{ + struct v4l2_fwnode_endpoint v4l2_ep; + struct fwnode_handle *fwh; + struct device_node *ep; + int ret; + + ep = of_graph_get_endpoint_by_regs(csi2rx->dev->of_node, 0, 0); + if (!ep) + return -EINVAL; + + fwh = of_fwnode_handle(ep); + ret = v4l2_fwnode_endpoint_parse(fwh, &v4l2_ep); + if (ret) { + dev_err(csi2rx->dev, "Could not parse v4l2 endpoint\n"); + of_node_put(ep); + return ret; + } + + if (v4l2_ep.bus_type != V4L2_MBUS_CSI2) { + dev_err(csi2rx->dev, "Unsupported media bus type: 0x%x\n", + v4l2_ep.bus_type); + of_node_put(ep); + return -EINVAL; + } + + memcpy(csi2rx->lanes, v4l2_ep.bus.mipi_csi2.data_lanes, + sizeof(csi2rx->lanes)); + csi2rx->num_lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes; + if (csi2rx->num_lanes > csi2rx->max_lanes) { + dev_err(csi2rx->dev, "Unsupported number of data-lanes: %d\n", + csi2rx->num_lanes); + of_node_put(ep); + return -EINVAL; + } + + csi2rx->asd.match.fwnode = fwnode_graph_get_remote_port_parent(fwh); + csi2rx->asd.match_type = V4L2_ASYNC_MATCH_FWNODE; + of_node_put(ep); + + csi2rx->notifier.subdevs = devm_kzalloc(csi2rx->dev, + sizeof(*csi2rx->notifier.subdevs), + GFP_KERNEL); + if (!csi2rx->notifier.subdevs) + return -ENOMEM; + + csi2rx->notifier.subdevs[0] = &csi2rx->asd; + csi2rx->notifier.num_subdevs = 1; + csi2rx->notifier.ops = &csi2rx_notifier_ops; + + return v4l2_async_subdev_notifier_register(&csi2rx->subdev, + &csi2rx->notifier); +} + +static int csi2rx_probe(struct platform_device *pdev) +{ + struct csi2rx_priv *csi2rx; + unsigned int i; + int ret; + + csi2rx = kzalloc(sizeof(*csi2rx), GFP_KERNEL); + if (!csi2rx) + return -ENOMEM; + platform_set_drvdata(pdev, csi2rx); + csi2rx->dev = &pdev->dev; + mutex_init(&csi2rx->lock); + + ret = csi2rx_get_resources(csi2rx, pdev); + if (ret) + goto err_free_priv; + + ret = csi2rx_parse_dt(csi2rx); + if (ret) + goto err_free_priv; + + csi2rx->subdev.owner = THIS_MODULE; + csi2rx->subdev.dev = &pdev->dev; + v4l2_subdev_init(&csi2rx->subdev, &csi2rx_subdev_ops); + v4l2_set_subdevdata(&csi2rx->subdev, &pdev->dev); + snprintf(csi2rx->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s.%s", + KBUILD_MODNAME, dev_name(&pdev->dev)); + + /* Create our media pads */ + csi2rx->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + csi2rx->pads[CSI2RX_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + for (i = CSI2RX_PAD_SOURCE_STREAM0; i < CSI2RX_PAD_MAX; i++) + csi2rx->pads[i].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&csi2rx->subdev.entity, CSI2RX_PAD_MAX, + csi2rx->pads); + if (ret) + goto err_free_priv; + + ret = v4l2_async_register_subdev(&csi2rx->subdev); + if (ret < 0) + goto err_free_priv; + + dev_info(&pdev->dev, + "Probed CSI2RX with %u/%u lanes, %u streams, %s D-PHY\n", + csi2rx->num_lanes, csi2rx->max_lanes, csi2rx->max_streams, + csi2rx->has_internal_dphy ? "internal" : "no"); + + return 0; + +err_free_priv: + kfree(csi2rx); + return ret; +} + +static int csi2rx_remove(struct platform_device *pdev) +{ + struct csi2rx_priv *csi2rx = platform_get_drvdata(pdev); + + v4l2_async_unregister_subdev(&csi2rx->subdev); + kfree(csi2rx); + + return 0; +} + +static const struct of_device_id csi2rx_of_table[] = { + { .compatible = "cdns,csi2rx" }, + { }, +}; +MODULE_DEVICE_TABLE(of, csi2rx_of_table); + +static struct platform_driver csi2rx_driver = { + .probe = csi2rx_probe, + .remove = csi2rx_remove, + + .driver = { + .name = "cdns-csi2rx", + .of_match_table = csi2rx_of_table, + }, +}; +module_platform_driver(csi2rx_driver); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>"); +MODULE_DESCRIPTION("Cadence CSI2-RX controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/cadence/cdns-csi2tx.c b/drivers/media/platform/cadence/cdns-csi2tx.c new file mode 100644 index 000000000000..dfa1d88d955b --- /dev/null +++ b/drivers/media/platform/cadence/cdns-csi2tx.c @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Cadence MIPI-CSI2 TX Controller + * + * Copyright (C) 2017-2018 Cadence Design Systems Inc. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> + +#define CSI2TX_DEVICE_CONFIG_REG 0x00 +#define CSI2TX_DEVICE_CONFIG_STREAMS_MASK GENMASK(6, 4) +#define CSI2TX_DEVICE_CONFIG_HAS_DPHY BIT(3) +#define CSI2TX_DEVICE_CONFIG_LANES_MASK GENMASK(2, 0) + +#define CSI2TX_CONFIG_REG 0x20 +#define CSI2TX_CONFIG_CFG_REQ BIT(2) +#define CSI2TX_CONFIG_SRST_REQ BIT(1) + +#define CSI2TX_DPHY_CFG_REG 0x28 +#define CSI2TX_DPHY_CFG_CLK_RESET BIT(16) +#define CSI2TX_DPHY_CFG_LANE_RESET(n) BIT((n) + 12) +#define CSI2TX_DPHY_CFG_MODE_MASK GENMASK(9, 8) +#define CSI2TX_DPHY_CFG_MODE_LPDT (2 << 8) +#define CSI2TX_DPHY_CFG_MODE_HS (1 << 8) +#define CSI2TX_DPHY_CFG_MODE_ULPS (0 << 8) +#define CSI2TX_DPHY_CFG_CLK_ENABLE BIT(4) +#define CSI2TX_DPHY_CFG_LANE_ENABLE(n) BIT(n) + +#define CSI2TX_DPHY_CLK_WAKEUP_REG 0x2c +#define CSI2TX_DPHY_CLK_WAKEUP_ULPS_CYCLES(n) ((n) & 0xffff) + +#define CSI2TX_DT_CFG_REG(n) (0x80 + (n) * 8) +#define CSI2TX_DT_CFG_DT(n) (((n) & 0x3f) << 2) + +#define CSI2TX_DT_FORMAT_REG(n) (0x84 + (n) * 8) +#define CSI2TX_DT_FORMAT_BYTES_PER_LINE(n) (((n) & 0xffff) << 16) +#define CSI2TX_DT_FORMAT_MAX_LINE_NUM(n) ((n) & 0xffff) + +#define CSI2TX_STREAM_IF_CFG_REG(n) (0x100 + (n) * 4) +#define CSI2TX_STREAM_IF_CFG_FILL_LEVEL(n) ((n) & 0x1f) + +#define CSI2TX_LANES_MAX 4 +#define CSI2TX_STREAMS_MAX 4 + +enum csi2tx_pads { + CSI2TX_PAD_SOURCE, + CSI2TX_PAD_SINK_STREAM0, + CSI2TX_PAD_SINK_STREAM1, + CSI2TX_PAD_SINK_STREAM2, + CSI2TX_PAD_SINK_STREAM3, + CSI2TX_PAD_MAX, +}; + +struct csi2tx_fmt { + u32 mbus; + u32 dt; + u32 bpp; +}; + +struct csi2tx_priv { + struct device *dev; + unsigned int count; + + /* + * Used to prevent race conditions between multiple, + * concurrent calls to start and stop. + */ + struct mutex lock; + + void __iomem *base; + + struct clk *esc_clk; + struct clk *p_clk; + struct clk *pixel_clk[CSI2TX_STREAMS_MAX]; + + struct v4l2_subdev subdev; + struct media_pad pads[CSI2TX_PAD_MAX]; + struct v4l2_mbus_framefmt pad_fmts[CSI2TX_PAD_MAX]; + + bool has_internal_dphy; + u8 lanes[CSI2TX_LANES_MAX]; + unsigned int num_lanes; + unsigned int max_lanes; + unsigned int max_streams; +}; + +static const struct csi2tx_fmt csi2tx_formats[] = { + { + .mbus = MEDIA_BUS_FMT_UYVY8_1X16, + .bpp = 2, + .dt = 0x1e, + }, + { + .mbus = MEDIA_BUS_FMT_RGB888_1X24, + .bpp = 3, + .dt = 0x24, + }, +}; + +static const struct v4l2_mbus_framefmt fmt_default = { + .width = 1280, + .height = 720, + .code = MEDIA_BUS_FMT_RGB888_1X24, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_DEFAULT, +}; + +static inline +struct csi2tx_priv *v4l2_subdev_to_csi2tx(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct csi2tx_priv, subdev); +} + +static const struct csi2tx_fmt *csi2tx_get_fmt_from_mbus(u32 mbus) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(csi2tx_formats); i++) + if (csi2tx_formats[i].mbus == mbus) + return &csi2tx_formats[i]; + + return NULL; +} + +static int csi2tx_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index >= ARRAY_SIZE(csi2tx_formats)) + return -EINVAL; + + code->code = csi2tx_formats[code->index].mbus; + + return 0; +} + +static struct v4l2_mbus_framefmt * +__csi2tx_get_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct csi2tx_priv *csi2tx = v4l2_subdev_to_csi2tx(subdev); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(subdev, cfg, + fmt->pad); + + return &csi2tx->pad_fmts[fmt->pad]; +} + +static int csi2tx_get_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + const struct v4l2_mbus_framefmt *format; + + /* Multiplexed pad? */ + if (fmt->pad == CSI2TX_PAD_SOURCE) + return -EINVAL; + + format = __csi2tx_get_pad_format(subdev, cfg, fmt); + if (!format) + return -EINVAL; + + fmt->format = *format; + + return 0; +} + +static int csi2tx_set_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + const struct v4l2_mbus_framefmt *src_format = &fmt->format; + struct v4l2_mbus_framefmt *dst_format; + + /* Multiplexed pad? */ + if (fmt->pad == CSI2TX_PAD_SOURCE) + return -EINVAL; + + if (!csi2tx_get_fmt_from_mbus(fmt->format.code)) + src_format = &fmt_default; + + dst_format = __csi2tx_get_pad_format(subdev, cfg, fmt); + if (!dst_format) + return -EINVAL; + + *dst_format = *src_format; + + return 0; +} + +static const struct v4l2_subdev_pad_ops csi2tx_pad_ops = { + .enum_mbus_code = csi2tx_enum_mbus_code, + .get_fmt = csi2tx_get_pad_format, + .set_fmt = csi2tx_set_pad_format, +}; + +static void csi2tx_reset(struct csi2tx_priv *csi2tx) +{ + writel(CSI2TX_CONFIG_SRST_REQ, csi2tx->base + CSI2TX_CONFIG_REG); + + udelay(10); +} + +static int csi2tx_start(struct csi2tx_priv *csi2tx) +{ + struct media_entity *entity = &csi2tx->subdev.entity; + struct media_link *link; + unsigned int i; + u32 reg; + + csi2tx_reset(csi2tx); + + writel(CSI2TX_CONFIG_CFG_REQ, csi2tx->base + CSI2TX_CONFIG_REG); + + udelay(10); + + /* Configure our PPI interface with the D-PHY */ + writel(CSI2TX_DPHY_CLK_WAKEUP_ULPS_CYCLES(32), + csi2tx->base + CSI2TX_DPHY_CLK_WAKEUP_REG); + + /* Put our lanes (clock and data) out of reset */ + reg = CSI2TX_DPHY_CFG_CLK_RESET | CSI2TX_DPHY_CFG_MODE_LPDT; + for (i = 0; i < csi2tx->num_lanes; i++) + reg |= CSI2TX_DPHY_CFG_LANE_RESET(csi2tx->lanes[i]); + writel(reg, csi2tx->base + CSI2TX_DPHY_CFG_REG); + + udelay(10); + + /* Enable our (clock and data) lanes */ + reg |= CSI2TX_DPHY_CFG_CLK_ENABLE; + for (i = 0; i < csi2tx->num_lanes; i++) + reg |= CSI2TX_DPHY_CFG_LANE_ENABLE(csi2tx->lanes[i]); + writel(reg, csi2tx->base + CSI2TX_DPHY_CFG_REG); + + udelay(10); + + /* Switch to HS mode */ + reg &= ~CSI2TX_DPHY_CFG_MODE_MASK; + writel(reg | CSI2TX_DPHY_CFG_MODE_HS, + csi2tx->base + CSI2TX_DPHY_CFG_REG); + + udelay(10); + + /* + * Create a static mapping between the CSI virtual channels + * and the input streams. + * + * This should be enhanced, but v4l2 lacks the support for + * changing that mapping dynamically at the moment. + * + * We're protected from the userspace setting up links at the + * same time by the upper layer having called + * media_pipeline_start(). + */ + list_for_each_entry(link, &entity->links, list) { + struct v4l2_mbus_framefmt *mfmt; + const struct csi2tx_fmt *fmt; + unsigned int stream; + int pad_idx = -1; + + /* Only consider our enabled input pads */ + for (i = CSI2TX_PAD_SINK_STREAM0; i < CSI2TX_PAD_MAX; i++) { + struct media_pad *pad = &csi2tx->pads[i]; + + if ((pad == link->sink) && + (link->flags & MEDIA_LNK_FL_ENABLED)) { + pad_idx = i; + break; + } + } + + if (pad_idx < 0) + continue; + + mfmt = &csi2tx->pad_fmts[pad_idx]; + fmt = csi2tx_get_fmt_from_mbus(mfmt->code); + if (!fmt) + continue; + + stream = pad_idx - CSI2TX_PAD_SINK_STREAM0; + + /* + * We use the stream ID there, but it's wrong. + * + * A stream could very well send a data type that is + * not equal to its stream ID. We need to find a + * proper way to address it. + */ + writel(CSI2TX_DT_CFG_DT(fmt->dt), + csi2tx->base + CSI2TX_DT_CFG_REG(stream)); + + writel(CSI2TX_DT_FORMAT_BYTES_PER_LINE(mfmt->width * fmt->bpp) | + CSI2TX_DT_FORMAT_MAX_LINE_NUM(mfmt->height + 1), + csi2tx->base + CSI2TX_DT_FORMAT_REG(stream)); + + /* + * TODO: This needs to be calculated based on the + * output CSI2 clock rate. + */ + writel(CSI2TX_STREAM_IF_CFG_FILL_LEVEL(4), + csi2tx->base + CSI2TX_STREAM_IF_CFG_REG(stream)); + } + + /* Disable the configuration mode */ + writel(0, csi2tx->base + CSI2TX_CONFIG_REG); + + return 0; +} + +static void csi2tx_stop(struct csi2tx_priv *csi2tx) +{ + writel(CSI2TX_CONFIG_CFG_REQ | CSI2TX_CONFIG_SRST_REQ, + csi2tx->base + CSI2TX_CONFIG_REG); +} + +static int csi2tx_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct csi2tx_priv *csi2tx = v4l2_subdev_to_csi2tx(subdev); + int ret = 0; + + mutex_lock(&csi2tx->lock); + + if (enable) { + /* + * If we're not the first users, there's no need to + * enable the whole controller. + */ + if (!csi2tx->count) { + ret = csi2tx_start(csi2tx); + if (ret) + goto out; + } + + csi2tx->count++; + } else { + csi2tx->count--; + + /* + * Let the last user turn off the lights. + */ + if (!csi2tx->count) + csi2tx_stop(csi2tx); + } + +out: + mutex_unlock(&csi2tx->lock); + return ret; +} + +static const struct v4l2_subdev_video_ops csi2tx_video_ops = { + .s_stream = csi2tx_s_stream, +}; + +static const struct v4l2_subdev_ops csi2tx_subdev_ops = { + .pad = &csi2tx_pad_ops, + .video = &csi2tx_video_ops, +}; + +static int csi2tx_get_resources(struct csi2tx_priv *csi2tx, + struct platform_device *pdev) +{ + struct resource *res; + unsigned int i; + u32 dev_cfg; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + csi2tx->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(csi2tx->base)) + return PTR_ERR(csi2tx->base); + + csi2tx->p_clk = devm_clk_get(&pdev->dev, "p_clk"); + if (IS_ERR(csi2tx->p_clk)) { + dev_err(&pdev->dev, "Couldn't get p_clk\n"); + return PTR_ERR(csi2tx->p_clk); + } + + csi2tx->esc_clk = devm_clk_get(&pdev->dev, "esc_clk"); + if (IS_ERR(csi2tx->esc_clk)) { + dev_err(&pdev->dev, "Couldn't get the esc_clk\n"); + return PTR_ERR(csi2tx->esc_clk); + } + + clk_prepare_enable(csi2tx->p_clk); + dev_cfg = readl(csi2tx->base + CSI2TX_DEVICE_CONFIG_REG); + clk_disable_unprepare(csi2tx->p_clk); + + csi2tx->max_lanes = dev_cfg & CSI2TX_DEVICE_CONFIG_LANES_MASK; + if (csi2tx->max_lanes > CSI2TX_LANES_MAX) { + dev_err(&pdev->dev, "Invalid number of lanes: %u\n", + csi2tx->max_lanes); + return -EINVAL; + } + + csi2tx->max_streams = (dev_cfg & CSI2TX_DEVICE_CONFIG_STREAMS_MASK) >> 4; + if (csi2tx->max_streams > CSI2TX_STREAMS_MAX) { + dev_err(&pdev->dev, "Invalid number of streams: %u\n", + csi2tx->max_streams); + return -EINVAL; + } + + csi2tx->has_internal_dphy = !!(dev_cfg & CSI2TX_DEVICE_CONFIG_HAS_DPHY); + + for (i = 0; i < csi2tx->max_streams; i++) { + char clk_name[16]; + + snprintf(clk_name, sizeof(clk_name), "pixel_if%u_clk", i); + csi2tx->pixel_clk[i] = devm_clk_get(&pdev->dev, clk_name); + if (IS_ERR(csi2tx->pixel_clk[i])) { + dev_err(&pdev->dev, "Couldn't get clock %s\n", + clk_name); + return PTR_ERR(csi2tx->pixel_clk[i]); + } + } + + return 0; +} + +static int csi2tx_check_lanes(struct csi2tx_priv *csi2tx) +{ + struct v4l2_fwnode_endpoint v4l2_ep; + struct device_node *ep; + int ret; + + ep = of_graph_get_endpoint_by_regs(csi2tx->dev->of_node, 0, 0); + if (!ep) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &v4l2_ep); + if (ret) { + dev_err(csi2tx->dev, "Could not parse v4l2 endpoint\n"); + goto out; + } + + if (v4l2_ep.bus_type != V4L2_MBUS_CSI2) { + dev_err(csi2tx->dev, "Unsupported media bus type: 0x%x\n", + v4l2_ep.bus_type); + ret = -EINVAL; + goto out; + } + + csi2tx->num_lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes; + if (csi2tx->num_lanes > csi2tx->max_lanes) { + dev_err(csi2tx->dev, + "Current configuration uses more lanes than supported\n"); + ret = -EINVAL; + goto out; + } + + memcpy(csi2tx->lanes, v4l2_ep.bus.mipi_csi2.data_lanes, + sizeof(csi2tx->lanes)); + +out: + of_node_put(ep); + return ret; +} + +static int csi2tx_probe(struct platform_device *pdev) +{ + struct csi2tx_priv *csi2tx; + unsigned int i; + int ret; + + csi2tx = kzalloc(sizeof(*csi2tx), GFP_KERNEL); + if (!csi2tx) + return -ENOMEM; + platform_set_drvdata(pdev, csi2tx); + mutex_init(&csi2tx->lock); + csi2tx->dev = &pdev->dev; + + ret = csi2tx_get_resources(csi2tx, pdev); + if (ret) + goto err_free_priv; + + v4l2_subdev_init(&csi2tx->subdev, &csi2tx_subdev_ops); + csi2tx->subdev.owner = THIS_MODULE; + csi2tx->subdev.dev = &pdev->dev; + csi2tx->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(csi2tx->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s.%s", + KBUILD_MODNAME, dev_name(&pdev->dev)); + + ret = csi2tx_check_lanes(csi2tx); + if (ret) + goto err_free_priv; + + /* Create our media pads */ + csi2tx->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + csi2tx->pads[CSI2TX_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + for (i = CSI2TX_PAD_SINK_STREAM0; i < CSI2TX_PAD_MAX; i++) + csi2tx->pads[i].flags = MEDIA_PAD_FL_SINK; + + /* + * Only the input pads are considered to have a format at the + * moment. The CSI link can multiplex various streams with + * different formats, and we can't expose this in v4l2 right + * now. + */ + for (i = CSI2TX_PAD_SINK_STREAM0; i < CSI2TX_PAD_MAX; i++) + csi2tx->pad_fmts[i] = fmt_default; + + ret = media_entity_pads_init(&csi2tx->subdev.entity, CSI2TX_PAD_MAX, + csi2tx->pads); + if (ret) + goto err_free_priv; + + ret = v4l2_async_register_subdev(&csi2tx->subdev); + if (ret < 0) + goto err_free_priv; + + dev_info(&pdev->dev, + "Probed CSI2TX with %u/%u lanes, %u streams, %s D-PHY\n", + csi2tx->num_lanes, csi2tx->max_lanes, csi2tx->max_streams, + csi2tx->has_internal_dphy ? "internal" : "no"); + + return 0; + +err_free_priv: + kfree(csi2tx); + return ret; +} + +static int csi2tx_remove(struct platform_device *pdev) +{ + struct csi2tx_priv *csi2tx = platform_get_drvdata(pdev); + + v4l2_async_unregister_subdev(&csi2tx->subdev); + kfree(csi2tx); + + return 0; +} + +static const struct of_device_id csi2tx_of_table[] = { + { .compatible = "cdns,csi2tx" }, + { }, +}; +MODULE_DEVICE_TABLE(of, csi2tx_of_table); + +static struct platform_driver csi2tx_driver = { + .probe = csi2tx_probe, + .remove = csi2tx_remove, + + .driver = { + .name = "cdns-csi2tx", + .of_match_table = csi2tx_of_table, + }, +}; +module_platform_driver(csi2tx_driver); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>"); +MODULE_DESCRIPTION("Cadence CSI2-TX controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/cec-gpio/cec-gpio.c b/drivers/media/platform/cec-gpio/cec-gpio.c index f1f28cf5c751..69f8242209c2 100644 --- a/drivers/media/platform/cec-gpio/cec-gpio.c +++ b/drivers/media/platform/cec-gpio/cec-gpio.c @@ -158,7 +158,7 @@ static int cec_gpio_probe(struct platform_device *pdev) cec->dev = dev; - cec->cec_gpio = devm_gpiod_get(dev, "cec", GPIOD_IN); + cec->cec_gpio = devm_gpiod_get(dev, "cec", GPIOD_OUT_HIGH_OPEN_DRAIN); if (IS_ERR(cec->cec_gpio)) return PTR_ERR(cec->cec_gpio); cec->cec_irq = gpiod_to_irq(cec->cec_gpio); diff --git a/drivers/media/platform/coda/coda-common.c b/drivers/media/platform/coda/coda-common.c index 04e35d70ce2e..c7631e117dd3 100644 --- a/drivers/media/platform/coda/coda-common.c +++ b/drivers/media/platform/coda/coda-common.c @@ -779,16 +779,27 @@ static int coda_s_fmt_vid_cap(struct file *file, void *priv, r.width = q_data_src->width; r.height = q_data_src->height; - return coda_s_fmt(ctx, f, &r); + ret = coda_s_fmt(ctx, f, &r); + if (ret) + return ret; + + if (ctx->inst_type != CODA_INST_ENCODER) + return 0; + + ctx->colorspace = f->fmt.pix.colorspace; + ctx->xfer_func = f->fmt.pix.xfer_func; + ctx->ycbcr_enc = f->fmt.pix.ycbcr_enc; + ctx->quantization = f->fmt.pix.quantization; + + return 0; } static int coda_s_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f) { struct coda_ctx *ctx = fh_to_ctx(priv); - struct coda_q_data *q_data_src; struct v4l2_format f_cap; - struct v4l2_rect r; + struct vb2_queue *dst_vq; int ret; ret = coda_try_fmt_vid_out(file, priv, f); @@ -799,28 +810,34 @@ static int coda_s_fmt_vid_out(struct file *file, void *priv, if (ret) return ret; + if (ctx->inst_type != CODA_INST_DECODER) + return 0; + ctx->colorspace = f->fmt.pix.colorspace; ctx->xfer_func = f->fmt.pix.xfer_func; ctx->ycbcr_enc = f->fmt.pix.ycbcr_enc; ctx->quantization = f->fmt.pix.quantization; + dst_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (!dst_vq) + return -EINVAL; + + /* + * Setting the capture queue format is not possible while the capture + * queue is still busy. This is not an error, but the user will have to + * make sure themselves that the capture format is set correctly before + * starting the output queue again. + */ + if (vb2_is_busy(dst_vq)) + return 0; + memset(&f_cap, 0, sizeof(f_cap)); f_cap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; coda_g_fmt(file, priv, &f_cap); f_cap.fmt.pix.width = f->fmt.pix.width; f_cap.fmt.pix.height = f->fmt.pix.height; - ret = coda_try_fmt_vid_cap(file, priv, &f_cap); - if (ret) - return ret; - - q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); - r.left = 0; - r.top = 0; - r.width = q_data_src->width; - r.height = q_data_src->height; - - return coda_s_fmt(ctx, &f_cap, &r); + return coda_s_fmt_vid_cap(file, priv, &f_cap); } static int coda_reqbufs(struct file *file, void *priv, diff --git a/drivers/media/platform/davinci/Kconfig b/drivers/media/platform/davinci/Kconfig index 55982e681d77..06b5e581f25f 100644 --- a/drivers/media/platform/davinci/Kconfig +++ b/drivers/media/platform/davinci/Kconfig @@ -2,7 +2,6 @@ config VIDEO_DAVINCI_VPIF_DISPLAY tristate "TI DaVinci VPIF V4L2-Display driver" depends on VIDEO_V4L2 depends on ARCH_DAVINCI || COMPILE_TEST - depends on HAS_DMA depends on I2C select VIDEOBUF2_DMA_CONTIG select VIDEO_ADV7343 if MEDIA_SUBDRV_AUTOSELECT @@ -19,7 +18,6 @@ config VIDEO_DAVINCI_VPIF_CAPTURE tristate "TI DaVinci VPIF video capture driver" depends on VIDEO_V4L2 depends on ARCH_DAVINCI || COMPILE_TEST - depends on HAS_DMA depends on I2C select VIDEOBUF2_DMA_CONTIG select V4L2_FWNODE @@ -35,7 +33,6 @@ config VIDEO_DM6446_CCDC tristate "TI DM6446 CCDC video capture driver" depends on VIDEO_V4L2 depends on ARCH_DAVINCI || COMPILE_TEST - depends on HAS_DMA depends on I2C select VIDEOBUF_DMA_CONTIG help @@ -52,7 +49,6 @@ config VIDEO_DM355_CCDC tristate "TI DM355 CCDC video capture driver" depends on VIDEO_V4L2 depends on ARCH_DAVINCI || COMPILE_TEST - depends on HAS_DMA depends on I2C select VIDEOBUF_DMA_CONTIG help @@ -67,8 +63,8 @@ config VIDEO_DM355_CCDC config VIDEO_DM365_ISIF tristate "TI DM365 ISIF video capture driver" - depends on VIDEO_V4L2 && ARCH_DAVINCI - depends on HAS_DMA + depends on VIDEO_V4L2 + depends on ARCH_DAVINCI || COMPILE_TEST depends on I2C select VIDEOBUF_DMA_CONTIG help @@ -81,8 +77,8 @@ config VIDEO_DM365_ISIF config VIDEO_DAVINCI_VPBE_DISPLAY tristate "TI DaVinci VPBE V4L2-Display driver" - depends on VIDEO_V4L2 && ARCH_DAVINCI - depends on HAS_DMA + depends on VIDEO_V4L2 + depends on ARCH_DAVINCI || COMPILE_TEST depends on I2C select VIDEOBUF2_DMA_CONTIG help diff --git a/drivers/media/platform/davinci/isif.c b/drivers/media/platform/davinci/isif.c index d5ff58494c1e..f924e76e2fbf 100644 --- a/drivers/media/platform/davinci/isif.c +++ b/drivers/media/platform/davinci/isif.c @@ -31,8 +31,6 @@ #include <linux/err.h> #include <linux/module.h> -#include <mach/mux.h> - #include <media/davinci/isif.h> #include <media/davinci/vpss.h> @@ -1029,7 +1027,7 @@ static int isif_probe(struct platform_device *pdev) { void (*setup_pinmux)(void); struct resource *res; - void *__iomem addr; + void __iomem *addr; int status = 0, i; /* Platform data holds setup_pinmux function ptr */ diff --git a/drivers/media/platform/davinci/vpbe.c b/drivers/media/platform/davinci/vpbe.c index 7f6462562579..18c035ef84cf 100644 --- a/drivers/media/platform/davinci/vpbe.c +++ b/drivers/media/platform/davinci/vpbe.c @@ -51,7 +51,7 @@ MODULE_AUTHOR("Texas Instruments"); /** * vpbe_current_encoder_info - Get config info for current encoder - * @vpbe_dev - vpbe device ptr + * @vpbe_dev: vpbe device ptr * * Return ptr to current encoder config info */ @@ -68,8 +68,8 @@ vpbe_current_encoder_info(struct vpbe_device *vpbe_dev) /** * vpbe_find_encoder_sd_index - Given a name find encoder sd index * - * @vpbe_config - ptr to vpbe cfg - * @output_index - index used by application + * @cfg: ptr to vpbe cfg + * @index: index used by application * * Return sd index of the encoder */ @@ -94,8 +94,8 @@ static int vpbe_find_encoder_sd_index(struct vpbe_config *cfg, /** * vpbe_g_cropcap - Get crop capabilities of the display - * @vpbe_dev - vpbe device ptr - * @cropcap - cropcap is a ptr to struct v4l2_cropcap + * @vpbe_dev: vpbe device ptr + * @cropcap: cropcap is a ptr to struct v4l2_cropcap * * Update the crop capabilities in crop cap for current * mode @@ -116,8 +116,8 @@ static int vpbe_g_cropcap(struct vpbe_device *vpbe_dev, /** * vpbe_enum_outputs - enumerate outputs - * @vpbe_dev - vpbe device ptr - * @output - ptr to v4l2_output structure + * @vpbe_dev: vpbe device ptr + * @output: ptr to v4l2_output structure * * Enumerates the outputs available at the vpbe display * returns the status, -EINVAL if end of output list @@ -212,8 +212,8 @@ static int vpbe_get_std_info_by_name(struct vpbe_device *vpbe_dev, /** * vpbe_set_output - Set output - * @vpbe_dev - vpbe device ptr - * @index - index of output + * @vpbe_dev: vpbe device ptr + * @index: index of output * * Set vpbe output to the output specified by the index */ @@ -308,7 +308,7 @@ static int vpbe_set_default_output(struct vpbe_device *vpbe_dev) /** * vpbe_get_output - Get output - * @vpbe_dev - vpbe device ptr + * @vpbe_dev: vpbe device ptr * * return current vpbe output to the the index */ @@ -317,7 +317,7 @@ static unsigned int vpbe_get_output(struct vpbe_device *vpbe_dev) return vpbe_dev->current_out_index; } -/** +/* * vpbe_s_dv_timings - Set the given preset timings in the encoder * * Sets the timings if supported by the current encoder. Return the status. @@ -369,7 +369,7 @@ static int vpbe_s_dv_timings(struct vpbe_device *vpbe_dev, return ret; } -/** +/* * vpbe_g_dv_timings - Get the timings in the current encoder * * Get the timings in the current encoder. Return the status. 0 - success @@ -394,7 +394,7 @@ static int vpbe_g_dv_timings(struct vpbe_device *vpbe_dev, return -EINVAL; } -/** +/* * vpbe_enum_dv_timings - Enumerate the dv timings in the current encoder * * Get the timings in the current encoder. Return the status. 0 - success @@ -426,7 +426,7 @@ static int vpbe_enum_dv_timings(struct vpbe_device *vpbe_dev, return 0; } -/** +/* * vpbe_s_std - Set the given standard in the encoder * * Sets the standard if supported by the current encoder. Return the status. @@ -465,7 +465,7 @@ static int vpbe_s_std(struct vpbe_device *vpbe_dev, v4l2_std_id std_id) return ret; } -/** +/* * vpbe_g_std - Get the standard in the current encoder * * Get the standard in the current encoder. Return the status. 0 - success @@ -488,7 +488,7 @@ static int vpbe_g_std(struct vpbe_device *vpbe_dev, v4l2_std_id *std_id) return -EINVAL; } -/** +/* * vpbe_set_mode - Set mode in the current encoder using mode info * * Use the mode string to decide what timings to set in the encoder @@ -572,7 +572,8 @@ static int platform_device_get(struct device *dev, void *data) /** * vpbe_initialize() - Initialize the vpbe display controller - * @vpbe_dev - vpbe device ptr + * @dev: Master and slave device ptr + * @vpbe_dev: vpbe device ptr * * Master frame buffer device drivers calls this to initialize vpbe * display controller. This will then registers v4l2 device and the sub @@ -769,7 +770,8 @@ fail_mutex_unlock: /** * vpbe_deinitialize() - de-initialize the vpbe display controller - * @dev - Master and slave device ptr + * @dev: Master and slave device ptr + * @vpbe_dev: vpbe device ptr * * vpbe_master and slave frame buffer devices calls this to de-initialize * the display controller. It is called when master and slave device diff --git a/drivers/media/platform/davinci/vpbe_display.c b/drivers/media/platform/davinci/vpbe_display.c index 6aabd21fe69f..b0eb3d899eb4 100644 --- a/drivers/media/platform/davinci/vpbe_display.c +++ b/drivers/media/platform/davinci/vpbe_display.c @@ -26,7 +26,10 @@ #include <linux/slab.h> #include <asm/pgtable.h> + +#ifdef CONFIG_ARCH_DAVINCI #include <mach/cputype.h> +#endif #include <media/v4l2-dev.h> #include <media/v4l2-common.h> @@ -53,8 +56,7 @@ static int vpbe_set_osd_display_params(struct vpbe_display *disp_dev, static int venc_is_second_field(struct vpbe_display *disp_dev) { struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; - int ret; - int val; + int ret, val; ret = v4l2_subdev_call(vpbe_dev->venc, core, @@ -64,6 +66,7 @@ static int venc_is_second_field(struct vpbe_display *disp_dev) if (ret < 0) { v4l2_err(&vpbe_dev->v4l2_dev, "Error in getting Field ID 0\n"); + return 1; } return val; } @@ -282,7 +285,7 @@ static int vpbe_start_streaming(struct vb2_queue *vq, unsigned int count) struct osd_state *osd_device = layer->disp_dev->osd_device; int ret; - osd_device->ops.disable_layer(osd_device, layer->layer_info.id); + osd_device->ops.disable_layer(osd_device, layer->layer_info.id); /* Get the next frame from the buffer queue */ layer->next_frm = layer->cur_frm = list_entry(layer->dma_queue.next, @@ -564,7 +567,7 @@ static void vpbe_disp_check_window_params(struct vpbe_display *disp_dev, } -/** +/* * vpbe_try_format() * If user application provides width and height, and have bytesperline set * to zero, driver calculates bytesperline and sizeimage based on hardware @@ -929,7 +932,7 @@ static int vpbe_display_try_fmt(struct file *file, void *priv, } -/** +/* * vpbe_display_s_std - Set the given standard in the encoder * * Sets the standard if supported by the current encoder. Return the status. @@ -961,7 +964,7 @@ static int vpbe_display_s_std(struct file *file, void *priv, return 0; } -/** +/* * vpbe_display_g_std - Get the standard in the current encoder * * Get the standard in the current encoder. Return the status. 0 - success @@ -984,7 +987,7 @@ static int vpbe_display_g_std(struct file *file, void *priv, return -EINVAL; } -/** +/* * vpbe_display_enum_output - enumerate outputs * * Enumerates the outputs available at the vpbe display @@ -1013,7 +1016,7 @@ static int vpbe_display_enum_output(struct file *file, void *priv, return 0; } -/** +/* * vpbe_display_s_output - Set output to * the output specified by the index */ @@ -1042,7 +1045,7 @@ static int vpbe_display_s_output(struct file *file, void *priv, return 0; } -/** +/* * vpbe_display_g_output - Get output from subdevice * for a given by the index */ @@ -1059,7 +1062,7 @@ static int vpbe_display_g_output(struct file *file, void *priv, return 0; } -/** +/* * vpbe_display_enum_dv_timings - Enumerate the dv timings * * enum the timings in the current encoder. Return the status. 0 - success @@ -1089,7 +1092,7 @@ vpbe_display_enum_dv_timings(struct file *file, void *priv, return 0; } -/** +/* * vpbe_display_s_dv_timings - Set the dv timings * * Set the timings in the current encoder. Return the status. 0 - success @@ -1122,7 +1125,7 @@ vpbe_display_s_dv_timings(struct file *file, void *priv, return 0; } -/** +/* * vpbe_display_g_dv_timings - Set the dv timings * * Get the timings in the current encoder. Return the status. 0 - success @@ -1351,9 +1354,9 @@ static int register_device(struct vpbe_layer *vpbe_display_layer, v4l2_info(&disp_dev->vpbe_dev->v4l2_dev, "Trying to register VPBE display device.\n"); v4l2_info(&disp_dev->vpbe_dev->v4l2_dev, - "layer=%x,layer->video_dev=%x\n", - (int)vpbe_display_layer, - (int)&vpbe_display_layer->video_dev); + "layer=%p,layer->video_dev=%p\n", + vpbe_display_layer, + &vpbe_display_layer->video_dev); vpbe_display_layer->video_dev.queue = &vpbe_display_layer->buffer_queue; err = video_register_device(&vpbe_display_layer->video_dev, diff --git a/drivers/media/platform/davinci/vpbe_osd.c b/drivers/media/platform/davinci/vpbe_osd.c index 66449791c70c..7f610320426d 100644 --- a/drivers/media/platform/davinci/vpbe_osd.c +++ b/drivers/media/platform/davinci/vpbe_osd.c @@ -24,8 +24,10 @@ #include <linux/clk.h> #include <linux/slab.h> +#ifdef CONFIG_ARCH_DAVINCI #include <mach/cputype.h> #include <mach/hardware.h> +#endif #include <media/davinci/vpss.h> #include <media/v4l2-device.h> @@ -122,10 +124,10 @@ static inline u32 osd_modify(struct osd_state *sd, u32 mask, u32 val, /** * _osd_dm6446_vid0_pingpong() - field inversion fix for DM6446 - * @sd - ptr to struct osd_state - * @field_inversion - inversion flag - * @fb_base_phys - frame buffer address - * @lconfig - ptr to layer config + * @sd: ptr to struct osd_state + * @field_inversion: inversion flag + * @fb_base_phys: frame buffer address + * @lconfig: ptr to layer config * * This routine implements a workaround for the field signal inversion silicon * erratum described in Advisory 1.3.8 for the DM6446. The fb_base_phys and @@ -782,9 +784,9 @@ static void osd_get_layer_config(struct osd_state *sd, enum osd_layer layer, /** * try_layer_config() - Try a specific configuration for the layer - * @sd - ptr to struct osd_state - * @layer - layer to configure - * @lconfig - layer configuration to try + * @sd: ptr to struct osd_state + * @layer: layer to configure + * @lconfig: layer configuration to try * * If the requested lconfig is completely rejected and the value of lconfig on * exit is the current lconfig, then try_layer_config() returns 1. Otherwise, @@ -844,9 +846,10 @@ static int try_layer_config(struct osd_state *sd, enum osd_layer layer, /* DM6446: */ /* only one OSD window at a time can use RGB pixel formats */ - if ((osd->vpbe_type == VPBE_VERSION_1) && - is_osd_win(layer) && is_rgb_pixfmt(lconfig->pixfmt)) { + if ((osd->vpbe_type == VPBE_VERSION_1) && + is_osd_win(layer) && is_rgb_pixfmt(lconfig->pixfmt)) { enum osd_pix_format pixfmt; + if (layer == WIN_OSD0) pixfmt = osd->win[WIN_OSD1].lconfig.pixfmt; else diff --git a/drivers/media/platform/davinci/vpbe_venc.c b/drivers/media/platform/davinci/vpbe_venc.c index 3a4e78595149..ba157827192c 100644 --- a/drivers/media/platform/davinci/vpbe_venc.c +++ b/drivers/media/platform/davinci/vpbe_venc.c @@ -21,8 +21,11 @@ #include <linux/videodev2.h> #include <linux/slab.h> +#ifdef CONFIG_ARCH_DAVINCI #include <mach/hardware.h> #include <mach/mux.h> +#endif + #include <linux/platform_data/i2c-davinci.h> #include <linux/io.h> @@ -224,7 +227,6 @@ venc_enable_vpss_clock(int venc_type, */ static int venc_set_ntsc(struct v4l2_subdev *sd) { - u32 val; struct venc_state *venc = to_state(sd); struct venc_platform_data *pdata = venc->pdata; @@ -241,7 +243,7 @@ static int venc_set_ntsc(struct v4l2_subdev *sd) if (venc->venc_type == VPBE_VERSION_3) { venc_write(sd, VENC_CLKCTL, 0x01); venc_write(sd, VENC_VIDCTL, 0); - val = vdaccfg_write(sd, VDAC_CONFIG_SD_V3); + vdaccfg_write(sd, VDAC_CONFIG_SD_V3); } else if (venc->venc_type == VPBE_VERSION_2) { venc_write(sd, VENC_CLKCTL, 0x01); venc_write(sd, VENC_VIDCTL, 0); @@ -604,10 +606,9 @@ static int venc_device_get(struct device *dev, void *data) struct v4l2_subdev *venc_sub_dev_init(struct v4l2_device *v4l2_dev, const char *venc_name) { - struct venc_state *venc; - int err; + struct venc_state *venc = NULL; - err = bus_for_each_dev(&platform_bus_type, NULL, &venc, + bus_for_each_dev(&platform_bus_type, NULL, &venc, venc_device_get); if (venc == NULL) return NULL; diff --git a/drivers/media/platform/davinci/vpfe_capture.c b/drivers/media/platform/davinci/vpfe_capture.c index 6f44abf7fa31..8613358ed245 100644 --- a/drivers/media/platform/davinci/vpfe_capture.c +++ b/drivers/media/platform/davinci/vpfe_capture.c @@ -1509,7 +1509,7 @@ static int vpfe_streamon(struct file *file, void *priv, unlock_out: mutex_unlock(&vpfe_dev->lock); streamoff: - ret = videobuf_streamoff(&vpfe_dev->buffer_queue); + videobuf_streamoff(&vpfe_dev->buffer_queue); return ret; } diff --git a/drivers/media/platform/exynos4-is/Kconfig b/drivers/media/platform/exynos4-is/Kconfig index 7b2c49e5a592..c8e5ad8f8294 100644 --- a/drivers/media/platform/exynos4-is/Kconfig +++ b/drivers/media/platform/exynos4-is/Kconfig @@ -41,11 +41,10 @@ config VIDEO_S5P_MIPI_CSIS To compile this driver as a module, choose M here: the module will be called s5p-csis. -if SOC_EXYNOS4412 || SOC_EXYNOS5250 - config VIDEO_EXYNOS_FIMC_LITE tristate "EXYNOS FIMC-LITE camera interface driver" depends on I2C + depends on SOC_EXYNOS4412 || SOC_EXYNOS5250 || COMPILE_TEST depends on HAS_DMA select VIDEOBUF2_DMA_CONTIG select VIDEO_EXYNOS4_IS_COMMON @@ -55,7 +54,6 @@ config VIDEO_EXYNOS_FIMC_LITE To compile this driver as a module, choose M here: the module will be called exynos-fimc-lite. -endif config VIDEO_EXYNOS4_FIMC_IS tristate "EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver" diff --git a/drivers/media/platform/exynos4-is/fimc-lite-reg.c b/drivers/media/platform/exynos4-is/fimc-lite-reg.c index f0acc550d065..16565a0b4bf1 100644 --- a/drivers/media/platform/exynos4-is/fimc-lite-reg.c +++ b/drivers/media/platform/exynos4-is/fimc-lite-reg.c @@ -254,7 +254,7 @@ void flite_hw_set_dma_window(struct fimc_lite *dev, struct flite_frame *f) /* Maximum output pixel size */ cfg = readl(dev->regs + FLITE_REG_CIOCAN); cfg &= ~FLITE_REG_CIOCAN_MASK; - cfg = (f->f_height << 16) | f->f_width; + cfg |= (f->f_height << 16) | f->f_width; writel(cfg, dev->regs + FLITE_REG_CIOCAN); /* DMA offsets */ diff --git a/drivers/media/platform/fsl-viu.c b/drivers/media/platform/fsl-viu.c index 200c47c69a75..e41510ce69a4 100644 --- a/drivers/media/platform/fsl-viu.c +++ b/drivers/media/platform/fsl-viu.c @@ -36,6 +36,12 @@ #define DRV_NAME "fsl_viu" #define VIU_VERSION "0.5.1" +/* Allow building this driver with COMPILE_TEST */ +#ifndef CONFIG_PPC +#define out_be32(v, a) iowrite32be(a, (void __iomem *)v) +#define in_be32(a) ioread32be((void __iomem *)a) +#endif + #define BUFFER_TIMEOUT msecs_to_jiffies(500) /* 0.5 seconds */ #define VIU_VID_MEM_LIMIT 4 /* Video memory limit, in Mb */ @@ -128,7 +134,7 @@ struct viu_dev { int dma_done; /* Hardware register area */ - struct viu_reg *vr; + struct viu_reg __iomem *vr; /* Interrupt vector */ int irq; @@ -229,7 +235,7 @@ enum status_config { static irqreturn_t viu_intr(int irq, void *dev_id); -struct viu_fmt *format_by_fourcc(int fourcc) +static struct viu_fmt *format_by_fourcc(int fourcc) { int i; @@ -242,9 +248,9 @@ struct viu_fmt *format_by_fourcc(int fourcc) return NULL; } -void viu_start_dma(struct viu_dev *dev) +static void viu_start_dma(struct viu_dev *dev) { - struct viu_reg *vr = dev->vr; + struct viu_reg __iomem *vr = dev->vr; dev->field = 0; @@ -253,9 +259,9 @@ void viu_start_dma(struct viu_dev *dev) out_be32(&vr->status_cfg, INT_FIELD_EN); } -void viu_stop_dma(struct viu_dev *dev) +static void viu_stop_dma(struct viu_dev *dev) { - struct viu_reg *vr = dev->vr; + struct viu_reg __iomem *vr = dev->vr; int cnt = 100; u32 status_cfg; @@ -290,7 +296,7 @@ static int restart_video_queue(struct viu_dmaqueue *vidq) { struct viu_buf *buf, *prev; - dprintk(1, "%s vidq=0x%08lx\n", __func__, (unsigned long)vidq); + dprintk(1, "%s vidq=%p\n", __func__, vidq); if (!list_empty(&vidq->active)) { buf = list_entry(vidq->active.next, struct viu_buf, vb.queue); dprintk(2, "restart_queue [%p/%d]: restart dma\n", @@ -395,7 +401,7 @@ static void free_buffer(struct videobuf_queue *vq, struct viu_buf *buf) inline int buffer_activate(struct viu_dev *dev, struct viu_buf *buf) { - struct viu_reg *vr = dev->vr; + struct viu_reg __iomem *vr = dev->vr; int bpp; /* setup the DMA base address */ @@ -497,8 +503,7 @@ static void buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) struct viu_buf *prev; if (!list_empty(&vidq->queued)) { - dprintk(1, "adding vb queue=0x%08lx\n", - (unsigned long)&buf->vb.queue); + dprintk(1, "adding vb queue=%p\n", &buf->vb.queue); dprintk(1, "vidq pointer 0x%p, queued 0x%p\n", vidq, &vidq->queued); dprintk(1, "dev %p, queued: self %p, next %p, head %p\n", @@ -509,8 +514,7 @@ static void buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) dprintk(2, "[%p/%d] buffer_queue - append to queued\n", buf, buf->vb.i); } else if (list_empty(&vidq->active)) { - dprintk(1, "adding vb active=0x%08lx\n", - (unsigned long)&buf->vb.queue); + dprintk(1, "adding vb active=%p\n", &buf->vb.queue); list_add_tail(&buf->vb.queue, &vidq->active); buf->vb.state = VIDEOBUF_ACTIVE; mod_timer(&vidq->timeout, jiffies+BUFFER_TIMEOUT); @@ -519,8 +523,7 @@ static void buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) buffer_activate(dev, buf); } else { - dprintk(1, "adding vb queue2=0x%08lx\n", - (unsigned long)&buf->vb.queue); + dprintk(1, "adding vb queue2=%p\n", &buf->vb.queue); prev = list_entry(vidq->active.prev, struct viu_buf, vb.queue); if (prev->vb.width == buf->vb.width && prev->vb.height == buf->vb.height && @@ -703,10 +706,8 @@ static int verify_preview(struct viu_dev *dev, struct v4l2_window *win) return 0; } -inline void viu_activate_overlay(struct viu_reg *viu_reg) +inline void viu_activate_overlay(struct viu_reg __iomem *vr) { - struct viu_reg *vr = viu_reg; - out_be32(&vr->field_base_addr, reg_val.field_base_addr); out_be32(&vr->dma_inc, reg_val.dma_inc); out_be32(&vr->picture_count, reg_val.picture_count); @@ -749,7 +750,7 @@ static int viu_setup_preview(struct viu_dev *dev, struct viu_fh *fh) reg_val.status_cfg |= DMA_ACT | INT_DMA_END_EN | INT_FIELD_EN; /* setup the base address of the overlay buffer */ - reg_val.field_base_addr = (u32)dev->ovbuf.base; + reg_val.field_base_addr = (u32)(long)dev->ovbuf.base; return 0; } @@ -802,7 +803,7 @@ static int vidioc_overlay(struct file *file, void *priv, unsigned int on) return 0; } -int vidioc_g_fbuf(struct file *file, void *priv, struct v4l2_framebuffer *arg) +static int vidioc_g_fbuf(struct file *file, void *priv, struct v4l2_framebuffer *arg) { struct viu_fh *fh = priv; struct viu_dev *dev = fh->dev; @@ -813,7 +814,7 @@ int vidioc_g_fbuf(struct file *file, void *priv, struct v4l2_framebuffer *arg) return 0; } -int vidioc_s_fbuf(struct file *file, void *priv, const struct v4l2_framebuffer *arg) +static int vidioc_s_fbuf(struct file *file, void *priv, const struct v4l2_framebuffer *arg) { struct viu_fh *fh = priv; struct viu_dev *dev = fh->dev; @@ -985,10 +986,8 @@ inline void viu_activate_next_buf(struct viu_dev *dev, } } -inline void viu_default_settings(struct viu_reg *viu_reg) +inline void viu_default_settings(struct viu_reg __iomem *vr) { - struct viu_reg *vr = viu_reg; - out_be32(&vr->luminance, 0x9512A254); out_be32(&vr->chroma_r, 0x03310000); out_be32(&vr->chroma_g, 0x06600F38); @@ -1001,7 +1000,7 @@ inline void viu_default_settings(struct viu_reg *viu_reg) static void viu_overlay_intr(struct viu_dev *dev, u32 status) { - struct viu_reg *vr = dev->vr; + struct viu_reg __iomem *vr = dev->vr; if (status & INT_DMA_END_STATUS) dev->dma_done = 1; @@ -1032,7 +1031,7 @@ static void viu_overlay_intr(struct viu_dev *dev, u32 status) static void viu_capture_intr(struct viu_dev *dev, u32 status) { struct viu_dmaqueue *vidq = &dev->vidq; - struct viu_reg *vr = dev->vr; + struct viu_reg __iomem *vr = dev->vr; struct viu_buf *buf; int field_num; int need_two; @@ -1104,7 +1103,7 @@ static void viu_capture_intr(struct viu_dev *dev, u32 status) static irqreturn_t viu_intr(int irq, void *dev_id) { struct viu_dev *dev = (struct viu_dev *)dev_id; - struct viu_reg *vr = dev->vr; + struct viu_reg __iomem *vr = dev->vr; u32 status; u32 error; @@ -1169,7 +1168,7 @@ static int viu_open(struct file *file) struct video_device *vdev = video_devdata(file); struct viu_dev *dev = video_get_drvdata(vdev); struct viu_fh *fh; - struct viu_reg *vr; + struct viu_reg __iomem *vr; int minor = vdev->minor; u32 status_cfg; @@ -1210,9 +1209,7 @@ static int viu_open(struct file *file) dev->crop_current.width = fh->width; dev->crop_current.height = fh->height; - dprintk(1, "Open: fh=0x%08lx, dev=0x%08lx, dev->vidq=0x%08lx\n", - (unsigned long)fh, (unsigned long)dev, - (unsigned long)&dev->vidq); + dprintk(1, "Open: fh=%p, dev=%p, dev->vidq=%p\n", fh, dev, &dev->vidq); dprintk(1, "Open: list_empty queued=%d\n", list_empty(&dev->vidq.queued)); dprintk(1, "Open: list_empty active=%d\n", @@ -1305,7 +1302,7 @@ static int viu_release(struct file *file) return 0; } -void viu_reset(struct viu_reg *reg) +static void viu_reset(struct viu_reg __iomem *reg) { out_be32(®->status_cfg, 0); out_be32(®->luminance, 0x9512a254); @@ -1325,7 +1322,7 @@ static int viu_mmap(struct file *file, struct vm_area_struct *vma) struct viu_dev *dev = fh->dev; int ret; - dprintk(1, "mmap called, vma=0x%08lx\n", (unsigned long)vma); + dprintk(1, "mmap called, vma=%p\n", vma); if (mutex_lock_interruptible(&dev->lock)) return -ERESTARTSYS; @@ -1407,7 +1404,7 @@ static int viu_of_probe(struct platform_device *op) } viu_irq = irq_of_parse_and_map(op->dev.of_node, 0); - if (viu_irq == NO_IRQ) { + if (!viu_irq) { dev_err(&op->dev, "Error while mapping the irq\n"); return -EINVAL; } diff --git a/drivers/media/platform/marvell-ccic/Kconfig b/drivers/media/platform/marvell-ccic/Kconfig index 4bf5bd1e90d6..cf12e077203a 100644 --- a/drivers/media/platform/marvell-ccic/Kconfig +++ b/drivers/media/platform/marvell-ccic/Kconfig @@ -1,7 +1,6 @@ config VIDEO_CAFE_CCIC tristate "Marvell 88ALP01 (Cafe) CMOS Camera Controller support" depends on PCI && I2C && VIDEO_V4L2 - depends on HAS_DMA select VIDEO_OV7670 select VIDEOBUF2_VMALLOC select VIDEOBUF2_DMA_CONTIG @@ -13,10 +12,12 @@ config VIDEO_CAFE_CCIC config VIDEO_MMP_CAMERA tristate "Marvell Armada 610 integrated camera controller support" - depends on ARCH_MMP && I2C && VIDEO_V4L2 - depends on HAS_DMA && BROKEN + depends on I2C && VIDEO_V4L2 + depends on ARCH_MMP || COMPILE_TEST select VIDEO_OV7670 select I2C_GPIO + select VIDEOBUF2_VMALLOC + select VIDEOBUF2_DMA_CONTIG select VIDEOBUF2_DMA_SG ---help--- This is a Video4Linux2 driver for the integrated camera diff --git a/drivers/media/platform/marvell-ccic/Makefile b/drivers/media/platform/marvell-ccic/Makefile index 05a792c579a2..b3a4d0cdccb8 100644 --- a/drivers/media/platform/marvell-ccic/Makefile +++ b/drivers/media/platform/marvell-ccic/Makefile @@ -1,6 +1,5 @@ -obj-$(CONFIG_VIDEO_CAFE_CCIC) += cafe_ccic.o -cafe_ccic-y := cafe-driver.o mcam-core.o - -obj-$(CONFIG_VIDEO_MMP_CAMERA) += mmp_camera.o -mmp_camera-y := mmp-driver.o mcam-core.o +obj-$(CONFIG_VIDEO_CAFE_CCIC) += cafe_ccic.o mcam-core.o +cafe_ccic-y := cafe-driver.o +obj-$(CONFIG_VIDEO_MMP_CAMERA) += mmp_camera.o mcam-core.o +mmp_camera-y := mmp-driver.o diff --git a/drivers/media/platform/marvell-ccic/mcam-core.c b/drivers/media/platform/marvell-ccic/mcam-core.c index 80670eeee142..dfdbd4354b74 100644 --- a/drivers/media/platform/marvell-ccic/mcam-core.c +++ b/drivers/media/platform/marvell-ccic/mcam-core.c @@ -1720,6 +1720,7 @@ int mccic_irq(struct mcam_camera *cam, unsigned int irqs) } return handled; } +EXPORT_SYMBOL_GPL(mccic_irq); /* ---------------------------------------------------------------------- */ /* @@ -1830,7 +1831,7 @@ out_unregister: v4l2_device_unregister(&cam->v4l2_dev); return ret; } - +EXPORT_SYMBOL_GPL(mccic_register); void mccic_shutdown(struct mcam_camera *cam) { @@ -1850,6 +1851,7 @@ void mccic_shutdown(struct mcam_camera *cam) v4l2_ctrl_handler_free(&cam->ctrl_handler); v4l2_device_unregister(&cam->v4l2_dev); } +EXPORT_SYMBOL_GPL(mccic_shutdown); /* * Power management @@ -1868,6 +1870,7 @@ void mccic_suspend(struct mcam_camera *cam) } mutex_unlock(&cam->s_mutex); } +EXPORT_SYMBOL_GPL(mccic_suspend); int mccic_resume(struct mcam_camera *cam) { @@ -1898,4 +1901,8 @@ int mccic_resume(struct mcam_camera *cam) } return ret; } +EXPORT_SYMBOL_GPL(mccic_resume); #endif /* CONFIG_PM */ + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Jonathan Corbet <corbet@lwn.net>"); diff --git a/drivers/media/platform/marvell-ccic/mmp-driver.c b/drivers/media/platform/marvell-ccic/mmp-driver.c index 816f4b6a7b8e..3cf300072348 100644 --- a/drivers/media/platform/marvell-ccic/mmp-driver.c +++ b/drivers/media/platform/marvell-ccic/mmp-driver.c @@ -37,7 +37,7 @@ MODULE_LICENSE("GPL"); static char *mcam_clks[] = {"CCICAXICLK", "CCICFUNCLK", "CCICPHYCLK"}; struct mmp_camera { - void *power_regs; + void __iomem *power_regs; struct platform_device *pdev; struct mcam_camera mcam; struct list_head devlist; @@ -183,7 +183,7 @@ static void mmpcam_power_down(struct mcam_camera *mcam) mcam_clk_disable(mcam); } -void mcam_ctlr_reset(struct mcam_camera *mcam) +static void mcam_ctlr_reset(struct mcam_camera *mcam) { unsigned long val; struct mmp_camera *cam = mcam_to_cam(mcam); @@ -214,7 +214,7 @@ void mcam_ctlr_reset(struct mcam_camera *mcam) * CSI2_DPHY3 and CSI2_DPHY6 can be set with a default value * or be calculated dynamically */ -void mmpcam_calc_dphy(struct mcam_camera *mcam) +static void mmpcam_calc_dphy(struct mcam_camera *mcam) { struct mmp_camera *cam = mcam_to_cam(mcam); struct mmp_camera_platform_data *pdata = cam->pdev->dev.platform_data; diff --git a/drivers/media/platform/mtk-jpeg/mtk_jpeg_core.c b/drivers/media/platform/mtk-jpeg/mtk_jpeg_core.c index af17aaa21f58..328e8f650d9b 100644 --- a/drivers/media/platform/mtk-jpeg/mtk_jpeg_core.c +++ b/drivers/media/platform/mtk-jpeg/mtk_jpeg_core.c @@ -1084,10 +1084,7 @@ static int mtk_jpeg_clk_init(struct mtk_jpeg_dev *jpeg) return PTR_ERR(jpeg->clk_jdec); jpeg->clk_jdec_smi = devm_clk_get(jpeg->dev, "jpgdec-smi"); - if (IS_ERR(jpeg->clk_jdec_smi)) - return PTR_ERR(jpeg->clk_jdec_smi); - - return 0; + return PTR_ERR_OR_ZERO(jpeg->clk_jdec_smi); } static int mtk_jpeg_probe(struct platform_device *pdev) diff --git a/drivers/media/platform/omap/Kconfig b/drivers/media/platform/omap/Kconfig index e8e2db181a7a..d827b6c285a6 100644 --- a/drivers/media/platform/omap/Kconfig +++ b/drivers/media/platform/omap/Kconfig @@ -1,15 +1,16 @@ config VIDEO_OMAP2_VOUT_VRFB bool + default y + depends on VIDEO_OMAP2_VOUT && (OMAP2_VRFB || COMPILE_TEST) config VIDEO_OMAP2_VOUT tristate "OMAP2/OMAP3 V4L2-Display driver" depends on MMU - depends on ARCH_OMAP2 || ARCH_OMAP3 - depends on FB_OMAP2 + depends on FB_OMAP2 || (COMPILE_TEST && FB_OMAP2=n) + depends on ARCH_OMAP2 || ARCH_OMAP3 || COMPILE_TEST select VIDEOBUF_GEN select VIDEOBUF_DMA_CONTIG select OMAP2_VRFB if ARCH_OMAP2 || ARCH_OMAP3 - select VIDEO_OMAP2_VOUT_VRFB if VIDEO_OMAP2_VOUT && OMAP2_VRFB select FRAME_VECTOR default n ---help--- diff --git a/drivers/media/platform/omap/omap_vout.c b/drivers/media/platform/omap/omap_vout.c index a795a9fae899..5700b7818621 100644 --- a/drivers/media/platform/omap/omap_vout.c +++ b/drivers/media/platform/omap/omap_vout.c @@ -198,7 +198,7 @@ static int omap_vout_try_format(struct v4l2_pix_format *pix) * omap_vout_get_userptr: Convert user space virtual address to physical * address. */ -static int omap_vout_get_userptr(struct videobuf_buffer *vb, u32 virtp, +static int omap_vout_get_userptr(struct videobuf_buffer *vb, long virtp, u32 *physp) { struct frame_vector *vec; @@ -702,19 +702,18 @@ static int omap_vout_buffer_setup(struct videobuf_queue *q, unsigned int *count, virt_addr = omap_vout_alloc_buffer(vout->buffer_size, &phy_addr); if (!virt_addr) { - if (ovid->rotation_type == VOUT_ROT_NONE) { + if (ovid->rotation_type == VOUT_ROT_NONE) break; - } else { - if (!is_rotation_enabled(vout)) - break; + + if (!is_rotation_enabled(vout)) + break; + /* Free the VRFB buffers if no space for V4L2 buffers */ for (j = i; j < *count; j++) { - omap_vout_free_buffer( - vout->smsshado_virt_addr[j], - vout->smsshado_size); + omap_vout_free_buffer(vout->smsshado_virt_addr[j], + vout->smsshado_size); vout->smsshado_virt_addr[j] = 0; vout->smsshado_phy_addr[j] = 0; - } } } vout->buf_virt_addr[i] = virt_addr; diff --git a/drivers/media/platform/omap/omap_vout_vrfb.c b/drivers/media/platform/omap/omap_vout_vrfb.c index 1d8508237220..29e3f5da59c1 100644 --- a/drivers/media/platform/omap/omap_vout_vrfb.c +++ b/drivers/media/platform/omap/omap_vout_vrfb.c @@ -54,8 +54,8 @@ static int omap_vout_allocate_vrfb_buffers(struct omap_vout_device *vout, *count = 0; return -ENOMEM; } - memset((void *) vout->smsshado_virt_addr[i], 0, - vout->smsshado_size); + memset((void *)(long)vout->smsshado_virt_addr[i], 0, + vout->smsshado_size); } return 0; } diff --git a/drivers/media/platform/omap3isp/isp.c b/drivers/media/platform/omap3isp/isp.c index 8eb000e3d8fd..f22cf351e3ee 100644 --- a/drivers/media/platform/omap3isp/isp.c +++ b/drivers/media/platform/omap3isp/isp.c @@ -61,7 +61,9 @@ #include <linux/sched.h> #include <linux/vmalloc.h> +#ifdef CONFIG_ARM_DMA_USE_IOMMU #include <asm/dma-iommu.h> +#endif #include <media/v4l2-common.h> #include <media/v4l2-fwnode.h> @@ -284,13 +286,6 @@ static const struct clk_ops isp_xclk_ops = { static const char *isp_xclk_parent_name = "cam_mclk"; -static const struct clk_init_data isp_xclk_init_data = { - .name = "cam_xclk", - .ops = &isp_xclk_ops, - .parent_names = &isp_xclk_parent_name, - .num_parents = 1, -}; - static struct clk *isp_xclk_src_get(struct of_phandle_args *clkspec, void *data) { unsigned int idx = clkspec->args[0]; @@ -1945,12 +1940,16 @@ error_csi2: static void isp_detach_iommu(struct isp_device *isp) { +#ifdef CONFIG_ARM_DMA_USE_IOMMU + arm_iommu_detach_device(isp->dev); arm_iommu_release_mapping(isp->mapping); isp->mapping = NULL; +#endif } static int isp_attach_iommu(struct isp_device *isp) { +#ifdef CONFIG_ARM_DMA_USE_IOMMU struct dma_iommu_mapping *mapping; int ret; @@ -1961,8 +1960,7 @@ static int isp_attach_iommu(struct isp_device *isp) mapping = arm_iommu_create_mapping(&platform_bus_type, SZ_1G, SZ_2G); if (IS_ERR(mapping)) { dev_err(isp->dev, "failed to create ARM IOMMU mapping\n"); - ret = PTR_ERR(mapping); - goto error; + return PTR_ERR(mapping); } isp->mapping = mapping; @@ -1977,8 +1975,12 @@ static int isp_attach_iommu(struct isp_device *isp) return 0; error: - isp_detach_iommu(isp); + arm_iommu_release_mapping(isp->mapping); + isp->mapping = NULL; return ret; +#else + return -ENODEV; +#endif } /* diff --git a/drivers/media/platform/omap3isp/ispccdc.c b/drivers/media/platform/omap3isp/ispccdc.c index b66276ab5765..77b73e27a274 100644 --- a/drivers/media/platform/omap3isp/ispccdc.c +++ b/drivers/media/platform/omap3isp/ispccdc.c @@ -735,7 +735,7 @@ static int ccdc_config(struct isp_ccdc_device *ccdc, return -ENOMEM; if (copy_from_user(fpc_new.addr, - (__force void __user *)fpc.fpcaddr, + (__force void __user *)(long)fpc.fpcaddr, size)) { dma_free_coherent(isp->dev, size, fpc_new.addr, fpc_new.dma); diff --git a/drivers/media/platform/omap3isp/isph3a_aewb.c b/drivers/media/platform/omap3isp/isph3a_aewb.c index d44626f20ac6..3c82dea4d375 100644 --- a/drivers/media/platform/omap3isp/isph3a_aewb.c +++ b/drivers/media/platform/omap3isp/isph3a_aewb.c @@ -250,6 +250,8 @@ static long h3a_aewb_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) return omap3isp_stat_config(stat, arg); case VIDIOC_OMAP3ISP_STAT_REQ: return omap3isp_stat_request_statistics(stat, arg); + case VIDIOC_OMAP3ISP_STAT_REQ_TIME32: + return omap3isp_stat_request_statistics_time32(stat, arg); case VIDIOC_OMAP3ISP_STAT_EN: { unsigned long *en = arg; return omap3isp_stat_enable(stat, !!*en); diff --git a/drivers/media/platform/omap3isp/isph3a_af.c b/drivers/media/platform/omap3isp/isph3a_af.c index 99bd6cc21d86..4da25c84f0c6 100644 --- a/drivers/media/platform/omap3isp/isph3a_af.c +++ b/drivers/media/platform/omap3isp/isph3a_af.c @@ -314,6 +314,8 @@ static long h3a_af_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) return omap3isp_stat_config(stat, arg); case VIDIOC_OMAP3ISP_STAT_REQ: return omap3isp_stat_request_statistics(stat, arg); + case VIDIOC_OMAP3ISP_STAT_REQ_TIME32: + return omap3isp_stat_request_statistics_time32(stat, arg); case VIDIOC_OMAP3ISP_STAT_EN: { int *en = arg; return omap3isp_stat_enable(stat, !!*en); diff --git a/drivers/media/platform/omap3isp/isphist.c b/drivers/media/platform/omap3isp/isphist.c index a4ed5d140d48..d4be3d0e06f9 100644 --- a/drivers/media/platform/omap3isp/isphist.c +++ b/drivers/media/platform/omap3isp/isphist.c @@ -435,6 +435,8 @@ static long hist_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) return omap3isp_stat_config(stat, arg); case VIDIOC_OMAP3ISP_STAT_REQ: return omap3isp_stat_request_statistics(stat, arg); + case VIDIOC_OMAP3ISP_STAT_REQ_TIME32: + return omap3isp_stat_request_statistics_time32(stat, arg); case VIDIOC_OMAP3ISP_STAT_EN: { int *en = arg; return omap3isp_stat_enable(stat, !!*en); diff --git a/drivers/media/platform/omap3isp/isppreview.c b/drivers/media/platform/omap3isp/isppreview.c index ac30a0f83780..3195f7c8b8b7 100644 --- a/drivers/media/platform/omap3isp/isppreview.c +++ b/drivers/media/platform/omap3isp/isppreview.c @@ -25,7 +25,7 @@ #include "isppreview.h" /* Default values in Office Fluorescent Light for RGBtoRGB Blending */ -static struct omap3isp_prev_rgbtorgb flr_rgb2rgb = { +static const struct omap3isp_prev_rgbtorgb flr_rgb2rgb = { { /* RGB-RGB Matrix */ {0x01E2, 0x0F30, 0x0FEE}, {0x0F9B, 0x01AC, 0x0FB9}, @@ -35,7 +35,7 @@ static struct omap3isp_prev_rgbtorgb flr_rgb2rgb = { }; /* Default values in Office Fluorescent Light for RGB to YUV Conversion*/ -static struct omap3isp_prev_csc flr_prev_csc = { +static const struct omap3isp_prev_csc flr_prev_csc = { { /* CSC Coef Matrix */ {66, 129, 25}, {-38, -75, 112}, @@ -890,7 +890,7 @@ static int preview_config(struct isp_prev_device *prev, params = &prev->params.params[!!(active & bit)]; if (cfg->flag & bit) { - void __user *from = *(void * __user *) + void __user *from = *(void __user **) ((void *)cfg + attr->config_offset); void *to = (void *)params + attr->param_offset; size_t size = attr->param_size; diff --git a/drivers/media/platform/omap3isp/ispstat.c b/drivers/media/platform/omap3isp/ispstat.c index 47cbc7e3d825..47353fee26c3 100644 --- a/drivers/media/platform/omap3isp/ispstat.c +++ b/drivers/media/platform/omap3isp/ispstat.c @@ -17,6 +17,7 @@ #include <linux/dma-mapping.h> #include <linux/slab.h> +#include <linux/timekeeping.h> #include <linux/uaccess.h> #include "isp.h" @@ -237,7 +238,7 @@ static int isp_stat_buf_queue(struct ispstat *stat) if (!stat->active_buf) return STAT_NO_BUF; - v4l2_get_timestamp(&stat->active_buf->ts); + ktime_get_ts64(&stat->active_buf->ts); stat->active_buf->buf_size = stat->buf_size; if (isp_stat_buf_check_magic(stat, stat->active_buf)) { @@ -370,7 +371,7 @@ static int isp_stat_bufs_alloc_one(struct device *dev, int ret; buf->virt_addr = dma_alloc_coherent(dev, size, &buf->dma_addr, - GFP_KERNEL | GFP_DMA); + GFP_KERNEL); if (!buf->virt_addr) return -ENOMEM; @@ -449,10 +450,8 @@ static int isp_stat_bufs_alloc(struct ispstat *stat, u32 size) buf->empty = 1; dev_dbg(stat->isp->dev, - "%s: buffer[%u] allocated. dma=0x%08lx virt=0x%08lx", - stat->subdev.name, i, - (unsigned long)buf->dma_addr, - (unsigned long)buf->virt_addr); + "%s: buffer[%u] allocated. dma=%pad virt=%p", + stat->subdev.name, i, &buf->dma_addr, buf->virt_addr); } return 0; @@ -500,7 +499,8 @@ int omap3isp_stat_request_statistics(struct ispstat *stat, return PTR_ERR(buf); } - data->ts = buf->ts; + data->ts.tv_sec = buf->ts.tv_sec; + data->ts.tv_usec = buf->ts.tv_nsec / NSEC_PER_USEC; data->config_counter = buf->config_counter; data->frame_number = buf->frame_number; data->buf_size = buf->buf_size; @@ -512,6 +512,23 @@ int omap3isp_stat_request_statistics(struct ispstat *stat, return 0; } +int omap3isp_stat_request_statistics_time32(struct ispstat *stat, + struct omap3isp_stat_data_time32 *data) +{ + struct omap3isp_stat_data data64; + int ret; + + ret = omap3isp_stat_request_statistics(stat, &data64); + if (ret) + return ret; + + data->ts.tv_sec = data64.ts.tv_sec; + data->ts.tv_usec = data64.ts.tv_usec; + memcpy(&data->buf, &data64.buf, sizeof(*data) - sizeof(data->ts)); + + return 0; +} + /* * omap3isp_stat_config - Receives new statistic engine configuration. * @new_conf: Pointer to config structure. @@ -527,12 +544,6 @@ int omap3isp_stat_config(struct ispstat *stat, void *new_conf) struct ispstat_generic_config *user_cfg = new_conf; u32 buf_size = user_cfg->buf_size; - if (!new_conf) { - dev_dbg(stat->isp->dev, "%s: configuration is NULL\n", - stat->subdev.name); - return -EINVAL; - } - mutex_lock(&stat->ioctl_lock); dev_dbg(stat->isp->dev, diff --git a/drivers/media/platform/omap3isp/ispstat.h b/drivers/media/platform/omap3isp/ispstat.h index 6d9b0244f320..923b38cfc682 100644 --- a/drivers/media/platform/omap3isp/ispstat.h +++ b/drivers/media/platform/omap3isp/ispstat.h @@ -39,7 +39,7 @@ struct ispstat_buffer { struct sg_table sgt; void *virt_addr; dma_addr_t dma_addr; - struct timeval ts; + struct timespec64 ts; u32 buf_size; u32 frame_number; u16 config_counter; @@ -130,6 +130,8 @@ struct ispstat_generic_config { int omap3isp_stat_config(struct ispstat *stat, void *new_conf); int omap3isp_stat_request_statistics(struct ispstat *stat, struct omap3isp_stat_data *data); +int omap3isp_stat_request_statistics_time32(struct ispstat *stat, + struct omap3isp_stat_data_time32 *data); int omap3isp_stat_init(struct ispstat *stat, const char *name, const struct v4l2_subdev_ops *sd_ops); void omap3isp_stat_cleanup(struct ispstat *stat); diff --git a/drivers/media/platform/omap3isp/ispvideo.c b/drivers/media/platform/omap3isp/ispvideo.c index a751c89a3ea8..9d228eac24ea 100644 --- a/drivers/media/platform/omap3isp/ispvideo.c +++ b/drivers/media/platform/omap3isp/ispvideo.c @@ -1403,7 +1403,7 @@ static int isp_video_mmap(struct file *file, struct vm_area_struct *vma) return vb2_mmap(&vfh->queue, vma); } -static struct v4l2_file_operations isp_video_fops = { +static const struct v4l2_file_operations isp_video_fops = { .owner = THIS_MODULE, .unlocked_ioctl = video_ioctl2, .open = isp_video_open, diff --git a/drivers/media/platform/pxa_camera.c b/drivers/media/platform/pxa_camera.c index c71a00736541..4d5a26b4cdda 100644 --- a/drivers/media/platform/pxa_camera.c +++ b/drivers/media/platform/pxa_camera.c @@ -2030,6 +2030,22 @@ static int pxac_vidioc_s_input(struct file *file, void *priv, unsigned int i) return 0; } +static int pxac_sensor_set_power(struct pxa_camera_dev *pcdev, int on) +{ + int ret; + + ret = sensor_call(pcdev, core, s_power, on); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (ret) { + dev_warn(pcdev_to_dev(pcdev), + "Failed to put subdevice in %s mode: %d\n", + on ? "normal operation" : "power saving", ret); + } + + return ret; +} + static int pxac_fops_camera_open(struct file *filp) { struct pxa_camera_dev *pcdev = video_drvdata(filp); @@ -2040,7 +2056,10 @@ static int pxac_fops_camera_open(struct file *filp) if (ret < 0) goto out; - ret = sensor_call(pcdev, core, s_power, 1); + if (!v4l2_fh_is_singular_file(filp)) + goto out; + + ret = pxac_sensor_set_power(pcdev, 1); if (ret) v4l2_fh_release(filp); out: @@ -2052,13 +2071,17 @@ static int pxac_fops_camera_release(struct file *filp) { struct pxa_camera_dev *pcdev = video_drvdata(filp); int ret; - - ret = vb2_fop_release(filp); - if (ret < 0) - return ret; + bool fh_singular; mutex_lock(&pcdev->mlock); - ret = sensor_call(pcdev, core, s_power, 0); + + fh_singular = v4l2_fh_is_singular_file(filp); + + ret = _vb2_fop_release(filp, NULL); + + if (fh_singular) + ret = pxac_sensor_set_power(pcdev, 0); + mutex_unlock(&pcdev->mlock); return ret; @@ -2160,7 +2183,7 @@ static int pxa_camera_sensor_bound(struct v4l2_async_notifier *notifier, pix->pixelformat = pcdev->current_fmt->host_fmt->fourcc; v4l2_fill_mbus_format(mf, pix, pcdev->current_fmt->code); - err = sensor_call(pcdev, core, s_power, 1); + err = pxac_sensor_set_power(pcdev, 1); if (err) goto out; @@ -2187,7 +2210,7 @@ static int pxa_camera_sensor_bound(struct v4l2_async_notifier *notifier, } out_sensor_poweroff: - err = sensor_call(pcdev, core, s_power, 0); + err = pxac_sensor_set_power(pcdev, 0); out: mutex_unlock(&pcdev->mlock); return err; @@ -2242,11 +2265,8 @@ static int pxa_camera_suspend(struct device *dev) pcdev->save_cicr[i++] = __raw_readl(pcdev->base + CICR3); pcdev->save_cicr[i++] = __raw_readl(pcdev->base + CICR4); - if (pcdev->sensor) { - ret = sensor_call(pcdev, core, s_power, 0); - if (ret == -ENOIOCTLCMD) - ret = 0; - } + if (pcdev->sensor) + ret = pxac_sensor_set_power(pcdev, 0); return ret; } @@ -2263,9 +2283,7 @@ static int pxa_camera_resume(struct device *dev) __raw_writel(pcdev->save_cicr[i++], pcdev->base + CICR4); if (pcdev->sensor) { - ret = sensor_call(pcdev, core, s_power, 1); - if (ret == -ENOIOCTLCMD) - ret = 0; + ret = pxac_sensor_set_power(pcdev, 1); } /* Restart frame capture if active buffer exists */ diff --git a/drivers/media/platform/rcar-vin/Kconfig b/drivers/media/platform/rcar-vin/Kconfig index af4c98b44d2e..baf4eafff6e3 100644 --- a/drivers/media/platform/rcar-vin/Kconfig +++ b/drivers/media/platform/rcar-vin/Kconfig @@ -1,12 +1,24 @@ +config VIDEO_RCAR_CSI2 + tristate "R-Car MIPI CSI-2 Receiver" + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && OF + depends on ARCH_RENESAS || COMPILE_TEST + select V4L2_FWNODE + help + Support for Renesas R-Car MIPI CSI-2 receiver. + Supports R-Car Gen3 SoCs. + + To compile this driver as a module, choose M here: the + module will be called rcar-csi2. + config VIDEO_RCAR_VIN tristate "R-Car Video Input (VIN) Driver" - depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && OF && HAS_DMA && MEDIA_CONTROLLER + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && OF && MEDIA_CONTROLLER depends on ARCH_RENESAS || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG select V4L2_FWNODE ---help--- Support for Renesas R-Car Video Input (VIN) driver. - Supports R-Car Gen2 SoCs. + Supports R-Car Gen2 and Gen3 SoCs. To compile this driver as a module, choose M here: the module will be called rcar-vin. diff --git a/drivers/media/platform/rcar-vin/Makefile b/drivers/media/platform/rcar-vin/Makefile index 48c5632c21dc..5ab803d3e7c1 100644 --- a/drivers/media/platform/rcar-vin/Makefile +++ b/drivers/media/platform/rcar-vin/Makefile @@ -1,3 +1,4 @@ rcar-vin-objs = rcar-core.o rcar-dma.o rcar-v4l2.o +obj-$(CONFIG_VIDEO_RCAR_CSI2) += rcar-csi2.o obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin.o diff --git a/drivers/media/platform/rcar-vin/rcar-core.c b/drivers/media/platform/rcar-vin/rcar-core.c index f1fc7978d6d1..d3072e166a1c 100644 --- a/drivers/media/platform/rcar-vin/rcar-core.c +++ b/drivers/media/platform/rcar-vin/rcar-core.c @@ -20,12 +20,341 @@ #include <linux/of_graph.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/sys_soc.h> #include <media/v4l2-async.h> #include <media/v4l2-fwnode.h> +#include <media/v4l2-mc.h> #include "rcar-vin.h" +/* + * The companion CSI-2 receiver driver (rcar-csi2) is known + * and we know it has one source pad (pad 0) and four sink + * pads (pad 1-4). So to translate a pad on the remote + * CSI-2 receiver to/from the VIN internal channel number simply + * subtract/add one from the pad/channel number. + */ +#define rvin_group_csi_pad_to_channel(pad) ((pad) - 1) +#define rvin_group_csi_channel_to_pad(channel) ((channel) + 1) + +/* + * Not all VINs are created equal, master VINs control the + * routing for other VIN's. We can figure out which VIN is + * master by looking at a VINs id. + */ +#define rvin_group_id_to_master(vin) ((vin) < 4 ? 0 : 4) + +/* ----------------------------------------------------------------------------- + * Media Controller link notification + */ + +/* group lock should be held when calling this function. */ +static int rvin_group_entity_to_csi_id(struct rvin_group *group, + struct media_entity *entity) +{ + struct v4l2_subdev *sd; + unsigned int i; + + sd = media_entity_to_v4l2_subdev(entity); + + for (i = 0; i < RVIN_CSI_MAX; i++) + if (group->csi[i].subdev == sd) + return i; + + return -ENODEV; +} + +static unsigned int rvin_group_get_mask(struct rvin_dev *vin, + enum rvin_csi_id csi_id, + unsigned char channel) +{ + const struct rvin_group_route *route; + unsigned int mask = 0; + + for (route = vin->info->routes; route->mask; route++) { + if (route->vin == vin->id && + route->csi == csi_id && + route->channel == channel) { + vin_dbg(vin, + "Adding route: vin: %d csi: %d channel: %d\n", + route->vin, route->csi, route->channel); + mask |= route->mask; + } + } + + return mask; +} + +/* + * Link setup for the links between a VIN and a CSI-2 receiver is a bit + * complex. The reason for this is that the register controlling routing + * is not present in each VIN instance. There are special VINs which + * control routing for themselves and other VINs. There are not many + * different possible links combinations that can be enabled at the same + * time, therefor all already enabled links which are controlled by a + * master VIN need to be taken into account when making the decision + * if a new link can be enabled or not. + * + * 1. Find out which VIN the link the user tries to enable is connected to. + * 2. Lookup which master VIN controls the links for this VIN. + * 3. Start with a bitmask with all bits set. + * 4. For each previously enabled link from the master VIN bitwise AND its + * route mask (see documentation for mask in struct rvin_group_route) + * with the bitmask. + * 5. Bitwise AND the mask for the link the user tries to enable to the bitmask. + * 6. If the bitmask is not empty at this point the new link can be enabled + * while keeping all previous links enabled. Update the CHSEL value of the + * master VIN and inform the user that the link could be enabled. + * + * Please note that no link can be enabled if any VIN in the group is + * currently open. + */ +static int rvin_group_link_notify(struct media_link *link, u32 flags, + unsigned int notification) +{ + struct rvin_group *group = container_of(link->graph_obj.mdev, + struct rvin_group, mdev); + unsigned int master_id, channel, mask_new, i; + unsigned int mask = ~0; + struct media_entity *entity; + struct video_device *vdev; + struct media_pad *csi_pad; + struct rvin_dev *vin = NULL; + int csi_id, ret; + + ret = v4l2_pipeline_link_notify(link, flags, notification); + if (ret) + return ret; + + /* Only care about link enablement for VIN nodes. */ + if (!(flags & MEDIA_LNK_FL_ENABLED) || + !is_media_entity_v4l2_video_device(link->sink->entity)) + return 0; + + /* If any entity is in use don't allow link changes. */ + media_device_for_each_entity(entity, &group->mdev) + if (entity->use_count) + return -EBUSY; + + mutex_lock(&group->lock); + + /* Find the master VIN that controls the routes. */ + vdev = media_entity_to_video_device(link->sink->entity); + vin = container_of(vdev, struct rvin_dev, vdev); + master_id = rvin_group_id_to_master(vin->id); + + if (WARN_ON(!group->vin[master_id])) { + ret = -ENODEV; + goto out; + } + + /* Build a mask for already enabled links. */ + for (i = master_id; i < master_id + 4; i++) { + if (!group->vin[i]) + continue; + + /* Get remote CSI-2, if any. */ + csi_pad = media_entity_remote_pad( + &group->vin[i]->vdev.entity.pads[0]); + if (!csi_pad) + continue; + + csi_id = rvin_group_entity_to_csi_id(group, csi_pad->entity); + channel = rvin_group_csi_pad_to_channel(csi_pad->index); + + mask &= rvin_group_get_mask(group->vin[i], csi_id, channel); + } + + /* Add the new link to the existing mask and check if it works. */ + csi_id = rvin_group_entity_to_csi_id(group, link->source->entity); + channel = rvin_group_csi_pad_to_channel(link->source->index); + mask_new = mask & rvin_group_get_mask(vin, csi_id, channel); + + vin_dbg(vin, "Try link change mask: 0x%x new: 0x%x\n", mask, mask_new); + + if (!mask_new) { + ret = -EMLINK; + goto out; + } + + /* New valid CHSEL found, set the new value. */ + ret = rvin_set_channel_routing(group->vin[master_id], __ffs(mask_new)); +out: + mutex_unlock(&group->lock); + + return ret; +} + +static const struct media_device_ops rvin_media_ops = { + .link_notify = rvin_group_link_notify, +}; + +/* ----------------------------------------------------------------------------- + * Gen3 CSI2 Group Allocator + */ + +/* FIXME: This should if we find a system that supports more + * than one group for the whole system be replaced with a linked + * list of groups. And eventually all of this should be replaced + * with a global device allocator API. + * + * But for now this works as on all supported systems there will + * be only one group for all instances. + */ + +static DEFINE_MUTEX(rvin_group_lock); +static struct rvin_group *rvin_group_data; + +static void rvin_group_cleanup(struct rvin_group *group) +{ + media_device_unregister(&group->mdev); + media_device_cleanup(&group->mdev); + mutex_destroy(&group->lock); +} + +static int rvin_group_init(struct rvin_group *group, struct rvin_dev *vin) +{ + struct media_device *mdev = &group->mdev; + const struct of_device_id *match; + struct device_node *np; + int ret; + + mutex_init(&group->lock); + + /* Count number of VINs in the system */ + group->count = 0; + for_each_matching_node(np, vin->dev->driver->of_match_table) + if (of_device_is_available(np)) + group->count++; + + vin_dbg(vin, "found %u enabled VIN's in DT", group->count); + + mdev->dev = vin->dev; + mdev->ops = &rvin_media_ops; + + match = of_match_node(vin->dev->driver->of_match_table, + vin->dev->of_node); + + strlcpy(mdev->driver_name, KBUILD_MODNAME, sizeof(mdev->driver_name)); + strlcpy(mdev->model, match->compatible, sizeof(mdev->model)); + snprintf(mdev->bus_info, sizeof(mdev->bus_info), "platform:%s", + dev_name(mdev->dev)); + + media_device_init(mdev); + + ret = media_device_register(&group->mdev); + if (ret) + rvin_group_cleanup(group); + + return ret; +} + +static void rvin_group_release(struct kref *kref) +{ + struct rvin_group *group = + container_of(kref, struct rvin_group, refcount); + + mutex_lock(&rvin_group_lock); + + rvin_group_data = NULL; + + rvin_group_cleanup(group); + + kfree(group); + + mutex_unlock(&rvin_group_lock); +} + +static int rvin_group_get(struct rvin_dev *vin) +{ + struct rvin_group *group; + u32 id; + int ret; + + /* Make sure VIN id is present and sane */ + ret = of_property_read_u32(vin->dev->of_node, "renesas,id", &id); + if (ret) { + vin_err(vin, "%pOF: No renesas,id property found\n", + vin->dev->of_node); + return -EINVAL; + } + + if (id >= RCAR_VIN_NUM) { + vin_err(vin, "%pOF: Invalid renesas,id '%u'\n", + vin->dev->of_node, id); + return -EINVAL; + } + + /* Join or create a VIN group */ + mutex_lock(&rvin_group_lock); + if (rvin_group_data) { + group = rvin_group_data; + kref_get(&group->refcount); + } else { + group = kzalloc(sizeof(*group), GFP_KERNEL); + if (!group) { + ret = -ENOMEM; + goto err_group; + } + + ret = rvin_group_init(group, vin); + if (ret) { + kfree(group); + vin_err(vin, "Failed to initialize group\n"); + goto err_group; + } + + kref_init(&group->refcount); + + rvin_group_data = group; + } + mutex_unlock(&rvin_group_lock); + + /* Add VIN to group */ + mutex_lock(&group->lock); + + if (group->vin[id]) { + vin_err(vin, "Duplicate renesas,id property value %u\n", id); + mutex_unlock(&group->lock); + kref_put(&group->refcount, rvin_group_release); + return -EINVAL; + } + + group->vin[id] = vin; + + vin->id = id; + vin->group = group; + vin->v4l2_dev.mdev = &group->mdev; + + mutex_unlock(&group->lock); + + return 0; +err_group: + mutex_unlock(&rvin_group_lock); + return ret; +} + +static void rvin_group_put(struct rvin_dev *vin) +{ + struct rvin_group *group = vin->group; + + mutex_lock(&group->lock); + + vin->group = NULL; + vin->v4l2_dev.mdev = NULL; + + if (WARN_ON(group->vin[vin->id] != vin)) + goto out; + + group->vin[vin->id] = NULL; +out: + mutex_unlock(&group->lock); + + kref_put(&group->refcount, rvin_group_release); +} + /* ----------------------------------------------------------------------------- * Async notifier */ @@ -46,30 +375,93 @@ static int rvin_find_pad(struct v4l2_subdev *sd, int direction) return -EINVAL; } -static bool rvin_mbus_supported(struct rvin_graph_entity *entity) +/* ----------------------------------------------------------------------------- + * Digital async notifier + */ + +/* The vin lock should be held when calling the subdevice attach and detach */ +static int rvin_digital_subdevice_attach(struct rvin_dev *vin, + struct v4l2_subdev *subdev) { - struct v4l2_subdev *sd = entity->subdev; struct v4l2_subdev_mbus_code_enum code = { .which = V4L2_SUBDEV_FORMAT_ACTIVE, }; + int ret; + /* Find source and sink pad of remote subdevice */ + ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SOURCE); + if (ret < 0) + return ret; + vin->digital->source_pad = ret; + + ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SINK); + vin->digital->sink_pad = ret < 0 ? 0 : ret; + + /* Find compatible subdevices mbus format */ + vin->mbus_code = 0; code.index = 0; - code.pad = entity->source_pad; - while (!v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code)) { + code.pad = vin->digital->source_pad; + while (!vin->mbus_code && + !v4l2_subdev_call(subdev, pad, enum_mbus_code, NULL, &code)) { code.index++; switch (code.code) { case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_UYVY8_1X16: case MEDIA_BUS_FMT_UYVY8_2X8: case MEDIA_BUS_FMT_UYVY10_2X10: case MEDIA_BUS_FMT_RGB888_1X24: - entity->code = code.code; - return true; + vin->mbus_code = code.code; + vin_dbg(vin, "Found media bus format for %s: %d\n", + subdev->name, vin->mbus_code); + break; default: break; } } - return false; + if (!vin->mbus_code) { + vin_err(vin, "Unsupported media bus format for %s\n", + subdev->name); + return -EINVAL; + } + + /* Read tvnorms */ + ret = v4l2_subdev_call(subdev, video, g_tvnorms, &vin->vdev.tvnorms); + if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) + return ret; + + /* Read standard */ + vin->std = V4L2_STD_UNKNOWN; + ret = v4l2_subdev_call(subdev, video, g_std, &vin->std); + if (ret < 0 && ret != -ENOIOCTLCMD) + return ret; + + /* Add the controls */ + ret = v4l2_ctrl_handler_init(&vin->ctrl_handler, 16); + if (ret < 0) + return ret; + + ret = v4l2_ctrl_add_handler(&vin->ctrl_handler, subdev->ctrl_handler, + NULL); + if (ret < 0) { + v4l2_ctrl_handler_free(&vin->ctrl_handler); + return ret; + } + + vin->vdev.ctrl_handler = &vin->ctrl_handler; + + vin->digital->subdev = subdev; + + return 0; +} + +static void rvin_digital_subdevice_detach(struct rvin_dev *vin) +{ + rvin_v4l2_unregister(vin); + v4l2_ctrl_handler_free(&vin->ctrl_handler); + + vin->vdev.ctrl_handler = NULL; + vin->digital->subdev = NULL; } static int rvin_digital_notify_complete(struct v4l2_async_notifier *notifier) @@ -77,23 +469,13 @@ static int rvin_digital_notify_complete(struct v4l2_async_notifier *notifier) struct rvin_dev *vin = notifier_to_vin(notifier); int ret; - /* Verify subdevices mbus format */ - if (!rvin_mbus_supported(vin->digital)) { - vin_err(vin, "Unsupported media bus format for %s\n", - vin->digital->subdev->name); - return -EINVAL; - } - - vin_dbg(vin, "Found media bus format for %s: %d\n", - vin->digital->subdev->name, vin->digital->code); - ret = v4l2_device_register_subdev_nodes(&vin->v4l2_dev); if (ret < 0) { vin_err(vin, "Failed to register subdev nodes\n"); return ret; } - return rvin_v4l2_probe(vin); + return rvin_v4l2_register(vin); } static void rvin_digital_notify_unbind(struct v4l2_async_notifier *notifier, @@ -103,8 +485,10 @@ static void rvin_digital_notify_unbind(struct v4l2_async_notifier *notifier, struct rvin_dev *vin = notifier_to_vin(notifier); vin_dbg(vin, "unbind digital subdev %s\n", subdev->name); - rvin_v4l2_remove(vin); - vin->digital->subdev = NULL; + + mutex_lock(&vin->lock); + rvin_digital_subdevice_detach(vin); + mutex_unlock(&vin->lock); } static int rvin_digital_notify_bound(struct v4l2_async_notifier *notifier, @@ -114,19 +498,13 @@ static int rvin_digital_notify_bound(struct v4l2_async_notifier *notifier, struct rvin_dev *vin = notifier_to_vin(notifier); int ret; - v4l2_set_subdev_hostdata(subdev, vin); - - /* Find source and sink pad of remote subdevice */ - - ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SOURCE); - if (ret < 0) + mutex_lock(&vin->lock); + ret = rvin_digital_subdevice_attach(vin, subdev); + mutex_unlock(&vin->lock); + if (ret) return ret; - vin->digital->source_pad = ret; - ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SINK); - vin->digital->sink_pad = ret < 0 ? 0 : ret; - - vin->digital->subdev = subdev; + v4l2_set_subdev_hostdata(subdev, vin); vin_dbg(vin, "bound subdev %s source pad: %u sink pad: %u\n", subdev->name, vin->digital->source_pad, @@ -134,13 +512,13 @@ static int rvin_digital_notify_bound(struct v4l2_async_notifier *notifier, return 0; } + static const struct v4l2_async_notifier_operations rvin_digital_notify_ops = { .bound = rvin_digital_notify_bound, .unbind = rvin_digital_notify_unbind, .complete = rvin_digital_notify_complete, }; - static int rvin_digital_parse_v4l2(struct device *dev, struct v4l2_fwnode_endpoint *vep, struct v4l2_async_subdev *asd) @@ -152,16 +530,16 @@ static int rvin_digital_parse_v4l2(struct device *dev, if (vep->base.port || vep->base.id) return -ENOTCONN; - rvge->mbus_cfg.type = vep->bus_type; + vin->mbus_cfg.type = vep->bus_type; - switch (rvge->mbus_cfg.type) { + switch (vin->mbus_cfg.type) { case V4L2_MBUS_PARALLEL: vin_dbg(vin, "Found PARALLEL media bus\n"); - rvge->mbus_cfg.flags = vep->bus.parallel.flags; + vin->mbus_cfg.flags = vep->bus.parallel.flags; break; case V4L2_MBUS_BT656: vin_dbg(vin, "Found BT656 media bus\n"); - rvge->mbus_cfg.flags = 0; + vin->mbus_cfg.flags = 0; break; default: vin_err(vin, "Unknown media bus type\n"); @@ -200,24 +578,477 @@ static int rvin_digital_graph_init(struct rvin_dev *vin) } /* ----------------------------------------------------------------------------- + * Group async notifier + */ + +static int rvin_group_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct rvin_dev *vin = notifier_to_vin(notifier); + const struct rvin_group_route *route; + unsigned int i; + int ret; + + ret = v4l2_device_register_subdev_nodes(&vin->v4l2_dev); + if (ret) { + vin_err(vin, "Failed to register subdev nodes\n"); + return ret; + } + + /* Register all video nodes for the group. */ + for (i = 0; i < RCAR_VIN_NUM; i++) { + if (vin->group->vin[i]) { + ret = rvin_v4l2_register(vin->group->vin[i]); + if (ret) + return ret; + } + } + + /* Create all media device links between VINs and CSI-2's. */ + mutex_lock(&vin->group->lock); + for (route = vin->info->routes; route->mask; route++) { + struct media_pad *source_pad, *sink_pad; + struct media_entity *source, *sink; + unsigned int source_idx; + + /* Check that VIN is part of the group. */ + if (!vin->group->vin[route->vin]) + continue; + + /* Check that VIN' master is part of the group. */ + if (!vin->group->vin[rvin_group_id_to_master(route->vin)]) + continue; + + /* Check that CSI-2 is part of the group. */ + if (!vin->group->csi[route->csi].subdev) + continue; + + source = &vin->group->csi[route->csi].subdev->entity; + source_idx = rvin_group_csi_channel_to_pad(route->channel); + source_pad = &source->pads[source_idx]; + + sink = &vin->group->vin[route->vin]->vdev.entity; + sink_pad = &sink->pads[0]; + + /* Skip if link already exists. */ + if (media_entity_find_link(source_pad, sink_pad)) + continue; + + ret = media_create_pad_link(source, source_idx, sink, 0, 0); + if (ret) { + vin_err(vin, "Error adding link from %s to %s\n", + source->name, sink->name); + break; + } + } + mutex_unlock(&vin->group->lock); + + return ret; +} + +static void rvin_group_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rvin_dev *vin = notifier_to_vin(notifier); + unsigned int i; + + for (i = 0; i < RCAR_VIN_NUM; i++) + if (vin->group->vin[i]) + rvin_v4l2_unregister(vin->group->vin[i]); + + mutex_lock(&vin->group->lock); + + for (i = 0; i < RVIN_CSI_MAX; i++) { + if (vin->group->csi[i].fwnode != asd->match.fwnode) + continue; + vin->group->csi[i].subdev = NULL; + vin_dbg(vin, "Unbind CSI-2 %s from slot %u\n", subdev->name, i); + break; + } + + mutex_unlock(&vin->group->lock); +} + +static int rvin_group_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rvin_dev *vin = notifier_to_vin(notifier); + unsigned int i; + + mutex_lock(&vin->group->lock); + + for (i = 0; i < RVIN_CSI_MAX; i++) { + if (vin->group->csi[i].fwnode != asd->match.fwnode) + continue; + vin->group->csi[i].subdev = subdev; + vin_dbg(vin, "Bound CSI-2 %s to slot %u\n", subdev->name, i); + break; + } + + mutex_unlock(&vin->group->lock); + + return 0; +} + +static const struct v4l2_async_notifier_operations rvin_group_notify_ops = { + .bound = rvin_group_notify_bound, + .unbind = rvin_group_notify_unbind, + .complete = rvin_group_notify_complete, +}; + +static int rvin_mc_parse_of_endpoint(struct device *dev, + struct v4l2_fwnode_endpoint *vep, + struct v4l2_async_subdev *asd) +{ + struct rvin_dev *vin = dev_get_drvdata(dev); + + if (vep->base.port != 1 || vep->base.id >= RVIN_CSI_MAX) + return -EINVAL; + + if (!of_device_is_available(to_of_node(asd->match.fwnode))) { + + vin_dbg(vin, "OF device %pOF disabled, ignoring\n", + to_of_node(asd->match.fwnode)); + return -ENOTCONN; + + } + + if (vin->group->csi[vep->base.id].fwnode) { + vin_dbg(vin, "OF device %pOF already handled\n", + to_of_node(asd->match.fwnode)); + return -ENOTCONN; + } + + vin->group->csi[vep->base.id].fwnode = asd->match.fwnode; + + vin_dbg(vin, "Add group OF device %pOF to slot %u\n", + to_of_node(asd->match.fwnode), vep->base.id); + + return 0; +} + +static int rvin_mc_parse_of_graph(struct rvin_dev *vin) +{ + unsigned int count = 0; + unsigned int i; + int ret; + + mutex_lock(&vin->group->lock); + + /* If there already is a notifier something has gone wrong, bail out. */ + if (WARN_ON(vin->group->notifier)) { + mutex_unlock(&vin->group->lock); + return -EINVAL; + } + + /* If not all VIN's are registered don't register the notifier. */ + for (i = 0; i < RCAR_VIN_NUM; i++) + if (vin->group->vin[i]) + count++; + + if (vin->group->count != count) { + mutex_unlock(&vin->group->lock); + return 0; + } + + /* + * Have all VIN's look for subdevices. Some subdevices will overlap + * but the parser function can handle it, so each subdevice will + * only be registered once with the notifier. + */ + + vin->group->notifier = &vin->notifier; + + for (i = 0; i < RCAR_VIN_NUM; i++) { + if (!vin->group->vin[i]) + continue; + + ret = v4l2_async_notifier_parse_fwnode_endpoints_by_port( + vin->group->vin[i]->dev, vin->group->notifier, + sizeof(struct v4l2_async_subdev), 1, + rvin_mc_parse_of_endpoint); + if (ret) { + mutex_unlock(&vin->group->lock); + return ret; + } + } + + mutex_unlock(&vin->group->lock); + + vin->group->notifier->ops = &rvin_group_notify_ops; + + ret = v4l2_async_notifier_register(&vin->v4l2_dev, &vin->notifier); + if (ret < 0) { + vin_err(vin, "Notifier registration failed\n"); + return ret; + } + + return 0; +} + +static int rvin_mc_init(struct rvin_dev *vin) +{ + int ret; + + /* All our sources are CSI-2 */ + vin->mbus_cfg.type = V4L2_MBUS_CSI2; + vin->mbus_cfg.flags = 0; + + vin->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vin->vdev.entity, 1, &vin->pad); + if (ret) + return ret; + + ret = rvin_group_get(vin); + if (ret) + return ret; + + ret = rvin_mc_parse_of_graph(vin); + if (ret) + rvin_group_put(vin); + + return ret; +} + +/* ----------------------------------------------------------------------------- * Platform Device Driver */ +static const struct rvin_info rcar_info_h1 = { + .model = RCAR_H1, + .use_mc = false, + .max_width = 2048, + .max_height = 2048, +}; + +static const struct rvin_info rcar_info_m1 = { + .model = RCAR_M1, + .use_mc = false, + .max_width = 2048, + .max_height = 2048, +}; + +static const struct rvin_info rcar_info_gen2 = { + .model = RCAR_GEN2, + .use_mc = false, + .max_width = 2048, + .max_height = 2048, +}; + +static const struct rvin_group_route rcar_info_r8a7795_routes[] = { + { .csi = RVIN_CSI40, .channel = 0, .vin = 0, .mask = BIT(0) | BIT(3) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 0, .mask = BIT(1) | BIT(4) }, + { .csi = RVIN_CSI40, .channel = 1, .vin = 0, .mask = BIT(2) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 1, .mask = BIT(0) }, + { .csi = RVIN_CSI40, .channel = 1, .vin = 1, .mask = BIT(1) | BIT(3) }, + { .csi = RVIN_CSI40, .channel = 0, .vin = 1, .mask = BIT(2) }, + { .csi = RVIN_CSI20, .channel = 1, .vin = 1, .mask = BIT(4) }, + { .csi = RVIN_CSI20, .channel = 1, .vin = 2, .mask = BIT(0) }, + { .csi = RVIN_CSI40, .channel = 0, .vin = 2, .mask = BIT(1) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 2, .mask = BIT(2) }, + { .csi = RVIN_CSI40, .channel = 2, .vin = 2, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 2, .vin = 2, .mask = BIT(4) }, + { .csi = RVIN_CSI40, .channel = 1, .vin = 3, .mask = BIT(0) }, + { .csi = RVIN_CSI20, .channel = 1, .vin = 3, .mask = BIT(1) | BIT(2) }, + { .csi = RVIN_CSI40, .channel = 3, .vin = 3, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 3, .vin = 3, .mask = BIT(4) }, + { .csi = RVIN_CSI41, .channel = 0, .vin = 4, .mask = BIT(0) | BIT(3) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 4, .mask = BIT(1) | BIT(4) }, + { .csi = RVIN_CSI41, .channel = 1, .vin = 4, .mask = BIT(2) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 5, .mask = BIT(0) }, + { .csi = RVIN_CSI41, .channel = 1, .vin = 5, .mask = BIT(1) | BIT(3) }, + { .csi = RVIN_CSI41, .channel = 0, .vin = 5, .mask = BIT(2) }, + { .csi = RVIN_CSI20, .channel = 1, .vin = 5, .mask = BIT(4) }, + { .csi = RVIN_CSI20, .channel = 1, .vin = 6, .mask = BIT(0) }, + { .csi = RVIN_CSI41, .channel = 0, .vin = 6, .mask = BIT(1) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 6, .mask = BIT(2) }, + { .csi = RVIN_CSI41, .channel = 2, .vin = 6, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 2, .vin = 6, .mask = BIT(4) }, + { .csi = RVIN_CSI41, .channel = 1, .vin = 7, .mask = BIT(0) }, + { .csi = RVIN_CSI20, .channel = 1, .vin = 7, .mask = BIT(1) | BIT(2) }, + { .csi = RVIN_CSI41, .channel = 3, .vin = 7, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 3, .vin = 7, .mask = BIT(4) }, + { /* Sentinel */ } +}; + +static const struct rvin_info rcar_info_r8a7795 = { + .model = RCAR_GEN3, + .use_mc = true, + .max_width = 4096, + .max_height = 4096, + .routes = rcar_info_r8a7795_routes, +}; + +static const struct rvin_group_route rcar_info_r8a7795es1_routes[] = { + { .csi = RVIN_CSI40, .channel = 0, .vin = 0, .mask = BIT(0) | BIT(3) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 0, .mask = BIT(1) | BIT(4) }, + { .csi = RVIN_CSI21, .channel = 0, .vin = 0, .mask = BIT(2) | BIT(5) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 1, .mask = BIT(0) }, + { .csi = RVIN_CSI21, .channel = 0, .vin = 1, .mask = BIT(1) }, + { .csi = RVIN_CSI40, .channel = 0, .vin = 1, .mask = BIT(2) }, + { .csi = RVIN_CSI40, .channel = 1, .vin = 1, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 1, .vin = 1, .mask = BIT(4) }, + { .csi = RVIN_CSI21, .channel = 1, .vin = 1, .mask = BIT(5) }, + { .csi = RVIN_CSI21, .channel = 0, .vin = 2, .mask = BIT(0) }, + { .csi = RVIN_CSI40, .channel = 0, .vin = 2, .mask = BIT(1) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 2, .mask = BIT(2) }, + { .csi = RVIN_CSI40, .channel = 2, .vin = 2, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 2, .vin = 2, .mask = BIT(4) }, + { .csi = RVIN_CSI21, .channel = 2, .vin = 2, .mask = BIT(5) }, + { .csi = RVIN_CSI40, .channel = 1, .vin = 3, .mask = BIT(0) }, + { .csi = RVIN_CSI20, .channel = 1, .vin = 3, .mask = BIT(1) }, + { .csi = RVIN_CSI21, .channel = 1, .vin = 3, .mask = BIT(2) }, + { .csi = RVIN_CSI40, .channel = 3, .vin = 3, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 3, .vin = 3, .mask = BIT(4) }, + { .csi = RVIN_CSI21, .channel = 3, .vin = 3, .mask = BIT(5) }, + { .csi = RVIN_CSI41, .channel = 0, .vin = 4, .mask = BIT(0) | BIT(3) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 4, .mask = BIT(1) | BIT(4) }, + { .csi = RVIN_CSI21, .channel = 0, .vin = 4, .mask = BIT(2) | BIT(5) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 5, .mask = BIT(0) }, + { .csi = RVIN_CSI21, .channel = 0, .vin = 5, .mask = BIT(1) }, + { .csi = RVIN_CSI41, .channel = 0, .vin = 5, .mask = BIT(2) }, + { .csi = RVIN_CSI41, .channel = 1, .vin = 5, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 1, .vin = 5, .mask = BIT(4) }, + { .csi = RVIN_CSI21, .channel = 1, .vin = 5, .mask = BIT(5) }, + { .csi = RVIN_CSI21, .channel = 0, .vin = 6, .mask = BIT(0) }, + { .csi = RVIN_CSI41, .channel = 0, .vin = 6, .mask = BIT(1) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 6, .mask = BIT(2) }, + { .csi = RVIN_CSI41, .channel = 2, .vin = 6, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 2, .vin = 6, .mask = BIT(4) }, + { .csi = RVIN_CSI21, .channel = 2, .vin = 6, .mask = BIT(5) }, + { .csi = RVIN_CSI41, .channel = 1, .vin = 7, .mask = BIT(0) }, + { .csi = RVIN_CSI20, .channel = 1, .vin = 7, .mask = BIT(1) }, + { .csi = RVIN_CSI21, .channel = 1, .vin = 7, .mask = BIT(2) }, + { .csi = RVIN_CSI41, .channel = 3, .vin = 7, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 3, .vin = 7, .mask = BIT(4) }, + { .csi = RVIN_CSI21, .channel = 3, .vin = 7, .mask = BIT(5) }, + { /* Sentinel */ } +}; + +static const struct rvin_info rcar_info_r8a7795es1 = { + .model = RCAR_GEN3, + .use_mc = true, + .max_width = 4096, + .max_height = 4096, + .routes = rcar_info_r8a7795es1_routes, +}; + +static const struct rvin_group_route rcar_info_r8a7796_routes[] = { + { .csi = RVIN_CSI40, .channel = 0, .vin = 0, .mask = BIT(0) | BIT(3) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 0, .mask = BIT(1) | BIT(4) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 1, .mask = BIT(0) }, + { .csi = RVIN_CSI40, .channel = 0, .vin = 1, .mask = BIT(2) }, + { .csi = RVIN_CSI40, .channel = 1, .vin = 1, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 1, .vin = 1, .mask = BIT(4) }, + { .csi = RVIN_CSI40, .channel = 0, .vin = 2, .mask = BIT(1) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 2, .mask = BIT(2) }, + { .csi = RVIN_CSI40, .channel = 2, .vin = 2, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 2, .vin = 2, .mask = BIT(4) }, + { .csi = RVIN_CSI40, .channel = 1, .vin = 3, .mask = BIT(0) }, + { .csi = RVIN_CSI20, .channel = 1, .vin = 3, .mask = BIT(1) }, + { .csi = RVIN_CSI40, .channel = 3, .vin = 3, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 3, .vin = 3, .mask = BIT(4) }, + { .csi = RVIN_CSI40, .channel = 0, .vin = 4, .mask = BIT(0) | BIT(3) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 4, .mask = BIT(1) | BIT(4) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 5, .mask = BIT(0) }, + { .csi = RVIN_CSI40, .channel = 0, .vin = 5, .mask = BIT(2) }, + { .csi = RVIN_CSI40, .channel = 1, .vin = 5, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 1, .vin = 5, .mask = BIT(4) }, + { .csi = RVIN_CSI40, .channel = 0, .vin = 6, .mask = BIT(1) }, + { .csi = RVIN_CSI20, .channel = 0, .vin = 6, .mask = BIT(2) }, + { .csi = RVIN_CSI40, .channel = 2, .vin = 6, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 2, .vin = 6, .mask = BIT(4) }, + { .csi = RVIN_CSI40, .channel = 1, .vin = 7, .mask = BIT(0) }, + { .csi = RVIN_CSI20, .channel = 1, .vin = 7, .mask = BIT(1) }, + { .csi = RVIN_CSI40, .channel = 3, .vin = 7, .mask = BIT(3) }, + { .csi = RVIN_CSI20, .channel = 3, .vin = 7, .mask = BIT(4) }, + { /* Sentinel */ } +}; + +static const struct rvin_info rcar_info_r8a7796 = { + .model = RCAR_GEN3, + .use_mc = true, + .max_width = 4096, + .max_height = 4096, + .routes = rcar_info_r8a7796_routes, +}; + +static const struct rvin_group_route _rcar_info_r8a77970_routes[] = { + { .csi = RVIN_CSI40, .channel = 0, .vin = 0, .mask = BIT(0) | BIT(3) }, + { .csi = RVIN_CSI40, .channel = 0, .vin = 1, .mask = BIT(2) }, + { .csi = RVIN_CSI40, .channel = 1, .vin = 1, .mask = BIT(3) }, + { .csi = RVIN_CSI40, .channel = 0, .vin = 2, .mask = BIT(1) }, + { .csi = RVIN_CSI40, .channel = 2, .vin = 2, .mask = BIT(3) }, + { .csi = RVIN_CSI40, .channel = 1, .vin = 3, .mask = BIT(0) }, + { .csi = RVIN_CSI40, .channel = 3, .vin = 3, .mask = BIT(3) }, + { /* Sentinel */ } +}; + +static const struct rvin_info rcar_info_r8a77970 = { + .model = RCAR_GEN3, + .use_mc = true, + .max_width = 4096, + .max_height = 4096, + .routes = _rcar_info_r8a77970_routes, +}; + static const struct of_device_id rvin_of_id_table[] = { - { .compatible = "renesas,vin-r8a7794", .data = (void *)RCAR_GEN2 }, - { .compatible = "renesas,vin-r8a7793", .data = (void *)RCAR_GEN2 }, - { .compatible = "renesas,vin-r8a7791", .data = (void *)RCAR_GEN2 }, - { .compatible = "renesas,vin-r8a7790", .data = (void *)RCAR_GEN2 }, - { .compatible = "renesas,vin-r8a7779", .data = (void *)RCAR_H1 }, - { .compatible = "renesas,vin-r8a7778", .data = (void *)RCAR_M1 }, - { .compatible = "renesas,rcar-gen2-vin", .data = (void *)RCAR_GEN2 }, - { }, + { + .compatible = "renesas,vin-r8a7778", + .data = &rcar_info_m1, + }, + { + .compatible = "renesas,vin-r8a7779", + .data = &rcar_info_h1, + }, + { + .compatible = "renesas,vin-r8a7790", + .data = &rcar_info_gen2, + }, + { + .compatible = "renesas,vin-r8a7791", + .data = &rcar_info_gen2, + }, + { + .compatible = "renesas,vin-r8a7793", + .data = &rcar_info_gen2, + }, + { + .compatible = "renesas,vin-r8a7794", + .data = &rcar_info_gen2, + }, + { + .compatible = "renesas,rcar-gen2-vin", + .data = &rcar_info_gen2, + }, + { + .compatible = "renesas,vin-r8a7795", + .data = &rcar_info_r8a7795, + }, + { + .compatible = "renesas,vin-r8a7796", + .data = &rcar_info_r8a7796, + }, + { + .compatible = "renesas,vin-r8a77970", + .data = &rcar_info_r8a77970, + }, + { /* Sentinel */ }, }; MODULE_DEVICE_TABLE(of, rvin_of_id_table); +static const struct soc_device_attribute r8a7795es1[] = { + { + .soc_id = "r8a7795", .revision = "ES1.*", + .data = &rcar_info_r8a7795es1, + }, + { /* Sentinel */ } +}; + static int rcar_vin_probe(struct platform_device *pdev) { - const struct of_device_id *match; + const struct soc_device_attribute *attr; struct rvin_dev *vin; struct resource *mem; int irq, ret; @@ -226,12 +1057,16 @@ static int rcar_vin_probe(struct platform_device *pdev) if (!vin) return -ENOMEM; - match = of_match_device(of_match_ptr(rvin_of_id_table), &pdev->dev); - if (!match) - return -ENODEV; - vin->dev = &pdev->dev; - vin->chip = (enum chip_id)match->data; + vin->info = of_device_get_match_data(&pdev->dev); + + /* + * Special care is needed on r8a7795 ES1.x since it + * uses different routing than r8a7795 ES2.0. + */ + attr = soc_device_match(r8a7795es1); + if (attr) + vin->info = attr->data; mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (mem == NULL) @@ -245,13 +1080,15 @@ static int rcar_vin_probe(struct platform_device *pdev) if (irq < 0) return irq; - ret = rvin_dma_probe(vin, irq); + ret = rvin_dma_register(vin, irq); if (ret) return ret; platform_set_drvdata(pdev, vin); - - ret = rvin_digital_graph_init(vin); + if (vin->info->use_mc) + ret = rvin_mc_init(vin); + else + ret = rvin_digital_graph_init(vin); if (ret < 0) goto error; @@ -260,7 +1097,7 @@ static int rcar_vin_probe(struct platform_device *pdev) return 0; error: - rvin_dma_remove(vin); + rvin_dma_unregister(vin); v4l2_async_notifier_cleanup(&vin->notifier); return ret; @@ -272,10 +1109,22 @@ static int rcar_vin_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); + rvin_v4l2_unregister(vin); + v4l2_async_notifier_unregister(&vin->notifier); v4l2_async_notifier_cleanup(&vin->notifier); - rvin_dma_remove(vin); + if (vin->info->use_mc) { + mutex_lock(&vin->group->lock); + if (vin->group->notifier == &vin->notifier) + vin->group->notifier = NULL; + mutex_unlock(&vin->group->lock); + rvin_group_put(vin); + } else { + v4l2_ctrl_handler_free(&vin->ctrl_handler); + } + + rvin_dma_unregister(vin); return 0; } diff --git a/drivers/media/platform/rcar-vin/rcar-csi2.c b/drivers/media/platform/rcar-vin/rcar-csi2.c new file mode 100644 index 000000000000..daef72d410a3 --- /dev/null +++ b/drivers/media/platform/rcar-vin/rcar-csi2.c @@ -0,0 +1,1084 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Renesas R-Car MIPI CSI-2 Receiver + * + * Copyright (C) 2018 Renesas Electronics Corp. + */ + +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/sys_soc.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mc.h> +#include <media/v4l2-subdev.h> + +struct rcar_csi2; + +/* Register offsets and bits */ + +/* Control Timing Select */ +#define TREF_REG 0x00 +#define TREF_TREF BIT(0) + +/* Software Reset */ +#define SRST_REG 0x04 +#define SRST_SRST BIT(0) + +/* PHY Operation Control */ +#define PHYCNT_REG 0x08 +#define PHYCNT_SHUTDOWNZ BIT(17) +#define PHYCNT_RSTZ BIT(16) +#define PHYCNT_ENABLECLK BIT(4) +#define PHYCNT_ENABLE_3 BIT(3) +#define PHYCNT_ENABLE_2 BIT(2) +#define PHYCNT_ENABLE_1 BIT(1) +#define PHYCNT_ENABLE_0 BIT(0) + +/* Checksum Control */ +#define CHKSUM_REG 0x0c +#define CHKSUM_ECC_EN BIT(1) +#define CHKSUM_CRC_EN BIT(0) + +/* + * Channel Data Type Select + * VCDT[0-15]: Channel 1 VCDT[16-31]: Channel 2 + * VCDT2[0-15]: Channel 3 VCDT2[16-31]: Channel 4 + */ +#define VCDT_REG 0x10 +#define VCDT2_REG 0x14 +#define VCDT_VCDTN_EN BIT(15) +#define VCDT_SEL_VC(n) (((n) & 0x3) << 8) +#define VCDT_SEL_DTN_ON BIT(6) +#define VCDT_SEL_DT(n) (((n) & 0x3f) << 0) + +/* Frame Data Type Select */ +#define FRDT_REG 0x18 + +/* Field Detection Control */ +#define FLD_REG 0x1c +#define FLD_FLD_NUM(n) (((n) & 0xff) << 16) +#define FLD_FLD_EN4 BIT(3) +#define FLD_FLD_EN3 BIT(2) +#define FLD_FLD_EN2 BIT(1) +#define FLD_FLD_EN BIT(0) + +/* Automatic Standby Control */ +#define ASTBY_REG 0x20 + +/* Long Data Type Setting 0 */ +#define LNGDT0_REG 0x28 + +/* Long Data Type Setting 1 */ +#define LNGDT1_REG 0x2c + +/* Interrupt Enable */ +#define INTEN_REG 0x30 + +/* Interrupt Source Mask */ +#define INTCLOSE_REG 0x34 + +/* Interrupt Status Monitor */ +#define INTSTATE_REG 0x38 +#define INTSTATE_INT_ULPS_START BIT(7) +#define INTSTATE_INT_ULPS_END BIT(6) + +/* Interrupt Error Status Monitor */ +#define INTERRSTATE_REG 0x3c + +/* Short Packet Data */ +#define SHPDAT_REG 0x40 + +/* Short Packet Count */ +#define SHPCNT_REG 0x44 + +/* LINK Operation Control */ +#define LINKCNT_REG 0x48 +#define LINKCNT_MONITOR_EN BIT(31) +#define LINKCNT_REG_MONI_PACT_EN BIT(25) +#define LINKCNT_ICLK_NONSTOP BIT(24) + +/* Lane Swap */ +#define LSWAP_REG 0x4c +#define LSWAP_L3SEL(n) (((n) & 0x3) << 6) +#define LSWAP_L2SEL(n) (((n) & 0x3) << 4) +#define LSWAP_L1SEL(n) (((n) & 0x3) << 2) +#define LSWAP_L0SEL(n) (((n) & 0x3) << 0) + +/* PHY Test Interface Write Register */ +#define PHTW_REG 0x50 +#define PHTW_DWEN BIT(24) +#define PHTW_TESTDIN_DATA(n) (((n & 0xff)) << 16) +#define PHTW_CWEN BIT(8) +#define PHTW_TESTDIN_CODE(n) ((n & 0xff)) + +struct phtw_value { + u16 data; + u16 code; +}; + +struct rcsi2_mbps_reg { + u16 mbps; + u16 reg; +}; + +static const struct rcsi2_mbps_reg phtw_mbps_h3_v3h_m3n[] = { + { .mbps = 80, .reg = 0x86 }, + { .mbps = 90, .reg = 0x86 }, + { .mbps = 100, .reg = 0x87 }, + { .mbps = 110, .reg = 0x87 }, + { .mbps = 120, .reg = 0x88 }, + { .mbps = 130, .reg = 0x88 }, + { .mbps = 140, .reg = 0x89 }, + { .mbps = 150, .reg = 0x89 }, + { .mbps = 160, .reg = 0x8a }, + { .mbps = 170, .reg = 0x8a }, + { .mbps = 180, .reg = 0x8b }, + { .mbps = 190, .reg = 0x8b }, + { .mbps = 205, .reg = 0x8c }, + { .mbps = 220, .reg = 0x8d }, + { .mbps = 235, .reg = 0x8e }, + { .mbps = 250, .reg = 0x8e }, + { /* sentinel */ }, +}; + +static const struct rcsi2_mbps_reg phtw_mbps_v3m_e3[] = { + { .mbps = 80, .reg = 0x00 }, + { .mbps = 90, .reg = 0x20 }, + { .mbps = 100, .reg = 0x40 }, + { .mbps = 110, .reg = 0x02 }, + { .mbps = 130, .reg = 0x22 }, + { .mbps = 140, .reg = 0x42 }, + { .mbps = 150, .reg = 0x04 }, + { .mbps = 170, .reg = 0x24 }, + { .mbps = 180, .reg = 0x44 }, + { .mbps = 200, .reg = 0x06 }, + { .mbps = 220, .reg = 0x26 }, + { .mbps = 240, .reg = 0x46 }, + { .mbps = 250, .reg = 0x08 }, + { .mbps = 270, .reg = 0x28 }, + { .mbps = 300, .reg = 0x0a }, + { .mbps = 330, .reg = 0x2a }, + { .mbps = 360, .reg = 0x4a }, + { .mbps = 400, .reg = 0x0c }, + { .mbps = 450, .reg = 0x2c }, + { .mbps = 500, .reg = 0x0e }, + { .mbps = 550, .reg = 0x2e }, + { .mbps = 600, .reg = 0x10 }, + { .mbps = 650, .reg = 0x30 }, + { .mbps = 700, .reg = 0x12 }, + { .mbps = 750, .reg = 0x32 }, + { .mbps = 800, .reg = 0x52 }, + { .mbps = 850, .reg = 0x72 }, + { .mbps = 900, .reg = 0x14 }, + { .mbps = 950, .reg = 0x34 }, + { .mbps = 1000, .reg = 0x54 }, + { .mbps = 1050, .reg = 0x74 }, + { .mbps = 1125, .reg = 0x16 }, + { /* sentinel */ }, +}; + +/* PHY Test Interface Clear */ +#define PHTC_REG 0x58 +#define PHTC_TESTCLR BIT(0) + +/* PHY Frequency Control */ +#define PHYPLL_REG 0x68 +#define PHYPLL_HSFREQRANGE(n) ((n) << 16) + +static const struct rcsi2_mbps_reg hsfreqrange_h3_v3h_m3n[] = { + { .mbps = 80, .reg = 0x00 }, + { .mbps = 90, .reg = 0x10 }, + { .mbps = 100, .reg = 0x20 }, + { .mbps = 110, .reg = 0x30 }, + { .mbps = 120, .reg = 0x01 }, + { .mbps = 130, .reg = 0x11 }, + { .mbps = 140, .reg = 0x21 }, + { .mbps = 150, .reg = 0x31 }, + { .mbps = 160, .reg = 0x02 }, + { .mbps = 170, .reg = 0x12 }, + { .mbps = 180, .reg = 0x22 }, + { .mbps = 190, .reg = 0x32 }, + { .mbps = 205, .reg = 0x03 }, + { .mbps = 220, .reg = 0x13 }, + { .mbps = 235, .reg = 0x23 }, + { .mbps = 250, .reg = 0x33 }, + { .mbps = 275, .reg = 0x04 }, + { .mbps = 300, .reg = 0x14 }, + { .mbps = 325, .reg = 0x25 }, + { .mbps = 350, .reg = 0x35 }, + { .mbps = 400, .reg = 0x05 }, + { .mbps = 450, .reg = 0x16 }, + { .mbps = 500, .reg = 0x26 }, + { .mbps = 550, .reg = 0x37 }, + { .mbps = 600, .reg = 0x07 }, + { .mbps = 650, .reg = 0x18 }, + { .mbps = 700, .reg = 0x28 }, + { .mbps = 750, .reg = 0x39 }, + { .mbps = 800, .reg = 0x09 }, + { .mbps = 850, .reg = 0x19 }, + { .mbps = 900, .reg = 0x29 }, + { .mbps = 950, .reg = 0x3a }, + { .mbps = 1000, .reg = 0x0a }, + { .mbps = 1050, .reg = 0x1a }, + { .mbps = 1100, .reg = 0x2a }, + { .mbps = 1150, .reg = 0x3b }, + { .mbps = 1200, .reg = 0x0b }, + { .mbps = 1250, .reg = 0x1b }, + { .mbps = 1300, .reg = 0x2b }, + { .mbps = 1350, .reg = 0x3c }, + { .mbps = 1400, .reg = 0x0c }, + { .mbps = 1450, .reg = 0x1c }, + { .mbps = 1500, .reg = 0x2c }, + { /* sentinel */ }, +}; + +static const struct rcsi2_mbps_reg hsfreqrange_m3w_h3es1[] = { + { .mbps = 80, .reg = 0x00 }, + { .mbps = 90, .reg = 0x10 }, + { .mbps = 100, .reg = 0x20 }, + { .mbps = 110, .reg = 0x30 }, + { .mbps = 120, .reg = 0x01 }, + { .mbps = 130, .reg = 0x11 }, + { .mbps = 140, .reg = 0x21 }, + { .mbps = 150, .reg = 0x31 }, + { .mbps = 160, .reg = 0x02 }, + { .mbps = 170, .reg = 0x12 }, + { .mbps = 180, .reg = 0x22 }, + { .mbps = 190, .reg = 0x32 }, + { .mbps = 205, .reg = 0x03 }, + { .mbps = 220, .reg = 0x13 }, + { .mbps = 235, .reg = 0x23 }, + { .mbps = 250, .reg = 0x33 }, + { .mbps = 275, .reg = 0x04 }, + { .mbps = 300, .reg = 0x14 }, + { .mbps = 325, .reg = 0x05 }, + { .mbps = 350, .reg = 0x15 }, + { .mbps = 400, .reg = 0x25 }, + { .mbps = 450, .reg = 0x06 }, + { .mbps = 500, .reg = 0x16 }, + { .mbps = 550, .reg = 0x07 }, + { .mbps = 600, .reg = 0x17 }, + { .mbps = 650, .reg = 0x08 }, + { .mbps = 700, .reg = 0x18 }, + { .mbps = 750, .reg = 0x09 }, + { .mbps = 800, .reg = 0x19 }, + { .mbps = 850, .reg = 0x29 }, + { .mbps = 900, .reg = 0x39 }, + { .mbps = 950, .reg = 0x0a }, + { .mbps = 1000, .reg = 0x1a }, + { .mbps = 1050, .reg = 0x2a }, + { .mbps = 1100, .reg = 0x3a }, + { .mbps = 1150, .reg = 0x0b }, + { .mbps = 1200, .reg = 0x1b }, + { .mbps = 1250, .reg = 0x2b }, + { .mbps = 1300, .reg = 0x3b }, + { .mbps = 1350, .reg = 0x0c }, + { .mbps = 1400, .reg = 0x1c }, + { .mbps = 1450, .reg = 0x2c }, + { .mbps = 1500, .reg = 0x3c }, + { /* sentinel */ }, +}; + +/* PHY ESC Error Monitor */ +#define PHEERM_REG 0x74 + +/* PHY Clock Lane Monitor */ +#define PHCLM_REG 0x78 +#define PHCLM_STOPSTATECKL BIT(0) + +/* PHY Data Lane Monitor */ +#define PHDLM_REG 0x7c + +/* CSI0CLK Frequency Configuration Preset Register */ +#define CSI0CLKFCPR_REG 0x260 +#define CSI0CLKFREQRANGE(n) ((n & 0x3f) << 16) + +struct rcar_csi2_format { + u32 code; + unsigned int datatype; + unsigned int bpp; +}; + +static const struct rcar_csi2_format rcar_csi2_formats[] = { + { .code = MEDIA_BUS_FMT_RGB888_1X24, .datatype = 0x24, .bpp = 24 }, + { .code = MEDIA_BUS_FMT_UYVY8_1X16, .datatype = 0x1e, .bpp = 16 }, + { .code = MEDIA_BUS_FMT_YUYV8_1X16, .datatype = 0x1e, .bpp = 16 }, + { .code = MEDIA_BUS_FMT_UYVY8_2X8, .datatype = 0x1e, .bpp = 16 }, + { .code = MEDIA_BUS_FMT_YUYV10_2X10, .datatype = 0x1e, .bpp = 20 }, +}; + +static const struct rcar_csi2_format *rcsi2_code_to_fmt(unsigned int code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rcar_csi2_formats); i++) + if (rcar_csi2_formats[i].code == code) + return &rcar_csi2_formats[i]; + + return NULL; +} + +enum rcar_csi2_pads { + RCAR_CSI2_SINK, + RCAR_CSI2_SOURCE_VC0, + RCAR_CSI2_SOURCE_VC1, + RCAR_CSI2_SOURCE_VC2, + RCAR_CSI2_SOURCE_VC3, + NR_OF_RCAR_CSI2_PAD, +}; + +struct rcar_csi2_info { + int (*init_phtw)(struct rcar_csi2 *priv, unsigned int mbps); + const struct rcsi2_mbps_reg *hsfreqrange; + unsigned int csi0clkfreqrange; + bool clear_ulps; +}; + +struct rcar_csi2 { + struct device *dev; + void __iomem *base; + const struct rcar_csi2_info *info; + + struct v4l2_subdev subdev; + struct media_pad pads[NR_OF_RCAR_CSI2_PAD]; + + struct v4l2_async_notifier notifier; + struct v4l2_async_subdev asd; + struct v4l2_subdev *remote; + + struct v4l2_mbus_framefmt mf; + + struct mutex lock; + int stream_count; + + unsigned short lanes; + unsigned char lane_swap[4]; +}; + +static inline struct rcar_csi2 *sd_to_csi2(struct v4l2_subdev *sd) +{ + return container_of(sd, struct rcar_csi2, subdev); +} + +static inline struct rcar_csi2 *notifier_to_csi2(struct v4l2_async_notifier *n) +{ + return container_of(n, struct rcar_csi2, notifier); +} + +static u32 rcsi2_read(struct rcar_csi2 *priv, unsigned int reg) +{ + return ioread32(priv->base + reg); +} + +static void rcsi2_write(struct rcar_csi2 *priv, unsigned int reg, u32 data) +{ + iowrite32(data, priv->base + reg); +} + +static void rcsi2_reset(struct rcar_csi2 *priv) +{ + rcsi2_write(priv, SRST_REG, SRST_SRST); + usleep_range(100, 150); + rcsi2_write(priv, SRST_REG, 0); +} + +static int rcsi2_wait_phy_start(struct rcar_csi2 *priv) +{ + unsigned int timeout; + + /* Wait for the clock and data lanes to enter LP-11 state. */ + for (timeout = 0; timeout <= 20; timeout++) { + const u32 lane_mask = (1 << priv->lanes) - 1; + + if ((rcsi2_read(priv, PHCLM_REG) & PHCLM_STOPSTATECKL) && + (rcsi2_read(priv, PHDLM_REG) & lane_mask) == lane_mask) + return 0; + + usleep_range(1000, 2000); + } + + dev_err(priv->dev, "Timeout waiting for LP-11 state\n"); + + return -ETIMEDOUT; +} + +static int rcsi2_set_phypll(struct rcar_csi2 *priv, unsigned int mbps) +{ + const struct rcsi2_mbps_reg *hsfreq; + + for (hsfreq = priv->info->hsfreqrange; hsfreq->mbps != 0; hsfreq++) + if (hsfreq->mbps >= mbps) + break; + + if (!hsfreq->mbps) { + dev_err(priv->dev, "Unsupported PHY speed (%u Mbps)", mbps); + return -ERANGE; + } + + rcsi2_write(priv, PHYPLL_REG, PHYPLL_HSFREQRANGE(hsfreq->reg)); + + return 0; +} + +static int rcsi2_calc_mbps(struct rcar_csi2 *priv, unsigned int bpp) +{ + struct v4l2_subdev *source; + struct v4l2_ctrl *ctrl; + u64 mbps; + + if (!priv->remote) + return -ENODEV; + + source = priv->remote; + + /* Read the pixel rate control from remote. */ + ctrl = v4l2_ctrl_find(source->ctrl_handler, V4L2_CID_PIXEL_RATE); + if (!ctrl) { + dev_err(priv->dev, "no pixel rate control in subdev %s\n", + source->name); + return -EINVAL; + } + + /* + * Calculate the phypll in mbps. + * link_freq = (pixel_rate * bits_per_sample) / (2 * nr_of_lanes) + * bps = link_freq * 2 + */ + mbps = v4l2_ctrl_g_ctrl_int64(ctrl) * bpp; + do_div(mbps, priv->lanes * 1000000); + + return mbps; +} + +static int rcsi2_start(struct rcar_csi2 *priv) +{ + const struct rcar_csi2_format *format; + u32 phycnt, vcdt = 0, vcdt2 = 0; + unsigned int i; + int mbps, ret; + + dev_dbg(priv->dev, "Input size (%ux%u%c)\n", + priv->mf.width, priv->mf.height, + priv->mf.field == V4L2_FIELD_NONE ? 'p' : 'i'); + + /* Code is validated in set_fmt. */ + format = rcsi2_code_to_fmt(priv->mf.code); + + /* + * Enable all Virtual Channels. + * + * NOTE: It's not possible to get individual datatype for each + * source virtual channel. Once this is possible in V4L2 + * it should be used here. + */ + for (i = 0; i < 4; i++) { + u32 vcdt_part; + + vcdt_part = VCDT_SEL_VC(i) | VCDT_VCDTN_EN | VCDT_SEL_DTN_ON | + VCDT_SEL_DT(format->datatype); + + /* Store in correct reg and offset. */ + if (i < 2) + vcdt |= vcdt_part << ((i % 2) * 16); + else + vcdt2 |= vcdt_part << ((i % 2) * 16); + } + + phycnt = PHYCNT_ENABLECLK; + phycnt |= (1 << priv->lanes) - 1; + + mbps = rcsi2_calc_mbps(priv, format->bpp); + if (mbps < 0) + return mbps; + + /* Init */ + rcsi2_write(priv, TREF_REG, TREF_TREF); + rcsi2_reset(priv); + rcsi2_write(priv, PHTC_REG, 0); + + /* Configure */ + rcsi2_write(priv, FLD_REG, FLD_FLD_NUM(2) | FLD_FLD_EN4 | + FLD_FLD_EN3 | FLD_FLD_EN2 | FLD_FLD_EN); + rcsi2_write(priv, VCDT_REG, vcdt); + rcsi2_write(priv, VCDT2_REG, vcdt2); + /* Lanes are zero indexed. */ + rcsi2_write(priv, LSWAP_REG, + LSWAP_L0SEL(priv->lane_swap[0] - 1) | + LSWAP_L1SEL(priv->lane_swap[1] - 1) | + LSWAP_L2SEL(priv->lane_swap[2] - 1) | + LSWAP_L3SEL(priv->lane_swap[3] - 1)); + + /* Start */ + if (priv->info->init_phtw) { + ret = priv->info->init_phtw(priv, mbps); + if (ret) + return ret; + } + + if (priv->info->hsfreqrange) { + ret = rcsi2_set_phypll(priv, mbps); + if (ret) + return ret; + } + + if (priv->info->csi0clkfreqrange) + rcsi2_write(priv, CSI0CLKFCPR_REG, + CSI0CLKFREQRANGE(priv->info->csi0clkfreqrange)); + + rcsi2_write(priv, PHYCNT_REG, phycnt); + rcsi2_write(priv, LINKCNT_REG, LINKCNT_MONITOR_EN | + LINKCNT_REG_MONI_PACT_EN | LINKCNT_ICLK_NONSTOP); + rcsi2_write(priv, PHYCNT_REG, phycnt | PHYCNT_SHUTDOWNZ); + rcsi2_write(priv, PHYCNT_REG, phycnt | PHYCNT_SHUTDOWNZ | PHYCNT_RSTZ); + + ret = rcsi2_wait_phy_start(priv); + if (ret) + return ret; + + /* Clear Ultra Low Power interrupt. */ + if (priv->info->clear_ulps) + rcsi2_write(priv, INTSTATE_REG, + INTSTATE_INT_ULPS_START | + INTSTATE_INT_ULPS_END); + return 0; +} + +static void rcsi2_stop(struct rcar_csi2 *priv) +{ + rcsi2_write(priv, PHYCNT_REG, 0); + + rcsi2_reset(priv); + + rcsi2_write(priv, PHTC_REG, PHTC_TESTCLR); +} + +static int rcsi2_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct rcar_csi2 *priv = sd_to_csi2(sd); + struct v4l2_subdev *nextsd; + int ret = 0; + + mutex_lock(&priv->lock); + + if (!priv->remote) { + ret = -ENODEV; + goto out; + } + + nextsd = priv->remote; + + if (enable && priv->stream_count == 0) { + pm_runtime_get_sync(priv->dev); + + ret = rcsi2_start(priv); + if (ret) { + pm_runtime_put(priv->dev); + goto out; + } + + ret = v4l2_subdev_call(nextsd, video, s_stream, 1); + if (ret) { + rcsi2_stop(priv); + pm_runtime_put(priv->dev); + goto out; + } + } else if (!enable && priv->stream_count == 1) { + rcsi2_stop(priv); + v4l2_subdev_call(nextsd, video, s_stream, 0); + pm_runtime_put(priv->dev); + } + + priv->stream_count += enable ? 1 : -1; +out: + mutex_unlock(&priv->lock); + + return ret; +} + +static int rcsi2_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct rcar_csi2 *priv = sd_to_csi2(sd); + struct v4l2_mbus_framefmt *framefmt; + + if (!rcsi2_code_to_fmt(format->format.code)) + format->format.code = rcar_csi2_formats[0].code; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + priv->mf = format->format; + } else { + framefmt = v4l2_subdev_get_try_format(sd, cfg, 0); + *framefmt = format->format; + } + + return 0; +} + +static int rcsi2_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct rcar_csi2 *priv = sd_to_csi2(sd); + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + format->format = priv->mf; + else + format->format = *v4l2_subdev_get_try_format(sd, cfg, 0); + + return 0; +} + +static const struct v4l2_subdev_video_ops rcar_csi2_video_ops = { + .s_stream = rcsi2_s_stream, +}; + +static const struct v4l2_subdev_pad_ops rcar_csi2_pad_ops = { + .set_fmt = rcsi2_set_pad_format, + .get_fmt = rcsi2_get_pad_format, +}; + +static const struct v4l2_subdev_ops rcar_csi2_subdev_ops = { + .video = &rcar_csi2_video_ops, + .pad = &rcar_csi2_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Async handling and registration of subdevices and links. + */ + +static int rcsi2_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rcar_csi2 *priv = notifier_to_csi2(notifier); + int pad; + + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode, + MEDIA_PAD_FL_SOURCE); + if (pad < 0) { + dev_err(priv->dev, "Failed to find pad for %s\n", subdev->name); + return pad; + } + + priv->remote = subdev; + + dev_dbg(priv->dev, "Bound %s pad: %d\n", subdev->name, pad); + + return media_create_pad_link(&subdev->entity, pad, + &priv->subdev.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static void rcsi2_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rcar_csi2 *priv = notifier_to_csi2(notifier); + + priv->remote = NULL; + + dev_dbg(priv->dev, "Unbind %s\n", subdev->name); +} + +static const struct v4l2_async_notifier_operations rcar_csi2_notify_ops = { + .bound = rcsi2_notify_bound, + .unbind = rcsi2_notify_unbind, +}; + +static int rcsi2_parse_v4l2(struct rcar_csi2 *priv, + struct v4l2_fwnode_endpoint *vep) +{ + unsigned int i; + + /* Only port 0 endpoint 0 is valid. */ + if (vep->base.port || vep->base.id) + return -ENOTCONN; + + if (vep->bus_type != V4L2_MBUS_CSI2) { + dev_err(priv->dev, "Unsupported bus: %u\n", vep->bus_type); + return -EINVAL; + } + + priv->lanes = vep->bus.mipi_csi2.num_data_lanes; + if (priv->lanes != 1 && priv->lanes != 2 && priv->lanes != 4) { + dev_err(priv->dev, "Unsupported number of data-lanes: %u\n", + priv->lanes); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(priv->lane_swap); i++) { + priv->lane_swap[i] = i < priv->lanes ? + vep->bus.mipi_csi2.data_lanes[i] : i; + + /* Check for valid lane number. */ + if (priv->lane_swap[i] < 1 || priv->lane_swap[i] > 4) { + dev_err(priv->dev, "data-lanes must be in 1-4 range\n"); + return -EINVAL; + } + } + + return 0; +} + +static int rcsi2_parse_dt(struct rcar_csi2 *priv) +{ + struct device_node *ep; + struct v4l2_fwnode_endpoint v4l2_ep; + int ret; + + ep = of_graph_get_endpoint_by_regs(priv->dev->of_node, 0, 0); + if (!ep) { + dev_err(priv->dev, "Not connected to subdevice\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &v4l2_ep); + if (ret) { + dev_err(priv->dev, "Could not parse v4l2 endpoint\n"); + of_node_put(ep); + return -EINVAL; + } + + ret = rcsi2_parse_v4l2(priv, &v4l2_ep); + if (ret) { + of_node_put(ep); + return ret; + } + + priv->asd.match.fwnode = + fwnode_graph_get_remote_endpoint(of_fwnode_handle(ep)); + priv->asd.match_type = V4L2_ASYNC_MATCH_FWNODE; + + of_node_put(ep); + + priv->notifier.subdevs = devm_kzalloc(priv->dev, + sizeof(*priv->notifier.subdevs), + GFP_KERNEL); + if (!priv->notifier.subdevs) + return -ENOMEM; + + priv->notifier.num_subdevs = 1; + priv->notifier.subdevs[0] = &priv->asd; + priv->notifier.ops = &rcar_csi2_notify_ops; + + dev_dbg(priv->dev, "Found '%pOF'\n", + to_of_node(priv->asd.match.fwnode)); + + return v4l2_async_subdev_notifier_register(&priv->subdev, + &priv->notifier); +} + +/* ----------------------------------------------------------------------------- + * PHTW initialization sequences. + * + * NOTE: Magic values are from the datasheet and lack documentation. + */ + +static int rcsi2_phtw_write(struct rcar_csi2 *priv, u16 data, u16 code) +{ + unsigned int timeout; + + rcsi2_write(priv, PHTW_REG, + PHTW_DWEN | PHTW_TESTDIN_DATA(data) | + PHTW_CWEN | PHTW_TESTDIN_CODE(code)); + + /* Wait for DWEN and CWEN to be cleared by hardware. */ + for (timeout = 0; timeout <= 20; timeout++) { + if (!(rcsi2_read(priv, PHTW_REG) & (PHTW_DWEN | PHTW_CWEN))) + return 0; + + usleep_range(1000, 2000); + } + + dev_err(priv->dev, "Timeout waiting for PHTW_DWEN and/or PHTW_CWEN\n"); + + return -ETIMEDOUT; +} + +static int rcsi2_phtw_write_array(struct rcar_csi2 *priv, + const struct phtw_value *values) +{ + const struct phtw_value *value; + int ret; + + for (value = values; value->data || value->code; value++) { + ret = rcsi2_phtw_write(priv, value->data, value->code); + if (ret) + return ret; + } + + return 0; +} + +static int rcsi2_phtw_write_mbps(struct rcar_csi2 *priv, unsigned int mbps, + const struct rcsi2_mbps_reg *values, u16 code) +{ + const struct rcsi2_mbps_reg *value; + + for (value = values; value->mbps; value++) + if (value->mbps >= mbps) + break; + + if (!value->mbps) { + dev_err(priv->dev, "Unsupported PHY speed (%u Mbps)", mbps); + return -ERANGE; + } + + return rcsi2_phtw_write(priv, value->reg, code); +} + +static int rcsi2_init_phtw_h3_v3h_m3n(struct rcar_csi2 *priv, unsigned int mbps) +{ + static const struct phtw_value step1[] = { + { .data = 0xcc, .code = 0xe2 }, + { .data = 0x01, .code = 0xe3 }, + { .data = 0x11, .code = 0xe4 }, + { .data = 0x01, .code = 0xe5 }, + { .data = 0x10, .code = 0x04 }, + { /* sentinel */ }, + }; + + static const struct phtw_value step2[] = { + { .data = 0x38, .code = 0x08 }, + { .data = 0x01, .code = 0x00 }, + { .data = 0x4b, .code = 0xac }, + { .data = 0x03, .code = 0x00 }, + { .data = 0x80, .code = 0x07 }, + { /* sentinel */ }, + }; + + int ret; + + ret = rcsi2_phtw_write_array(priv, step1); + if (ret) + return ret; + + if (mbps <= 250) { + ret = rcsi2_phtw_write(priv, 0x39, 0x05); + if (ret) + return ret; + + ret = rcsi2_phtw_write_mbps(priv, mbps, phtw_mbps_h3_v3h_m3n, + 0xf1); + if (ret) + return ret; + } + + return rcsi2_phtw_write_array(priv, step2); +} + +static int rcsi2_init_phtw_v3m_e3(struct rcar_csi2 *priv, unsigned int mbps) +{ + static const struct phtw_value step1[] = { + { .data = 0xed, .code = 0x34 }, + { .data = 0xed, .code = 0x44 }, + { .data = 0xed, .code = 0x54 }, + { .data = 0xed, .code = 0x84 }, + { .data = 0xed, .code = 0x94 }, + { /* sentinel */ }, + }; + + int ret; + + ret = rcsi2_phtw_write_mbps(priv, mbps, phtw_mbps_v3m_e3, 0x44); + if (ret) + return ret; + + return rcsi2_phtw_write_array(priv, step1); +} + +/* ----------------------------------------------------------------------------- + * Platform Device Driver. + */ + +static const struct media_entity_operations rcar_csi2_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int rcsi2_probe_resources(struct rcar_csi2 *priv, + struct platform_device *pdev) +{ + struct resource *res; + int irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + return 0; +} + +static const struct rcar_csi2_info rcar_csi2_info_r8a7795 = { + .init_phtw = rcsi2_init_phtw_h3_v3h_m3n, + .hsfreqrange = hsfreqrange_h3_v3h_m3n, + .csi0clkfreqrange = 0x20, + .clear_ulps = true, +}; + +static const struct rcar_csi2_info rcar_csi2_info_r8a7795es1 = { + .hsfreqrange = hsfreqrange_m3w_h3es1, +}; + +static const struct rcar_csi2_info rcar_csi2_info_r8a7796 = { + .hsfreqrange = hsfreqrange_m3w_h3es1, +}; + +static const struct rcar_csi2_info rcar_csi2_info_r8a77965 = { + .init_phtw = rcsi2_init_phtw_h3_v3h_m3n, + .hsfreqrange = hsfreqrange_h3_v3h_m3n, + .csi0clkfreqrange = 0x20, + .clear_ulps = true, +}; + +static const struct rcar_csi2_info rcar_csi2_info_r8a77970 = { + .init_phtw = rcsi2_init_phtw_v3m_e3, +}; + +static const struct of_device_id rcar_csi2_of_table[] = { + { + .compatible = "renesas,r8a7795-csi2", + .data = &rcar_csi2_info_r8a7795, + }, + { + .compatible = "renesas,r8a7796-csi2", + .data = &rcar_csi2_info_r8a7796, + }, + { + .compatible = "renesas,r8a77965-csi2", + .data = &rcar_csi2_info_r8a77965, + }, + { + .compatible = "renesas,r8a77970-csi2", + .data = &rcar_csi2_info_r8a77970, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, rcar_csi2_of_table); + +static const struct soc_device_attribute r8a7795es1[] = { + { + .soc_id = "r8a7795", .revision = "ES1.*", + .data = &rcar_csi2_info_r8a7795es1, + }, + { /* sentinel */ }, +}; + +static int rcsi2_probe(struct platform_device *pdev) +{ + const struct soc_device_attribute *attr; + struct rcar_csi2 *priv; + unsigned int i; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->info = of_device_get_match_data(&pdev->dev); + + /* + * r8a7795 ES1.x behaves differently than the ES2.0+ but doesn't + * have it's own compatible string. + */ + attr = soc_device_match(r8a7795es1); + if (attr) + priv->info = attr->data; + + priv->dev = &pdev->dev; + + mutex_init(&priv->lock); + priv->stream_count = 0; + + ret = rcsi2_probe_resources(priv, pdev); + if (ret) { + dev_err(priv->dev, "Failed to get resources\n"); + return ret; + } + + platform_set_drvdata(pdev, priv); + + ret = rcsi2_parse_dt(priv); + if (ret) + return ret; + + priv->subdev.owner = THIS_MODULE; + priv->subdev.dev = &pdev->dev; + v4l2_subdev_init(&priv->subdev, &rcar_csi2_subdev_ops); + v4l2_set_subdevdata(&priv->subdev, &pdev->dev); + snprintf(priv->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s %s", + KBUILD_MODNAME, dev_name(&pdev->dev)); + priv->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + priv->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + priv->subdev.entity.ops = &rcar_csi2_entity_ops; + + priv->pads[RCAR_CSI2_SINK].flags = MEDIA_PAD_FL_SINK; + for (i = RCAR_CSI2_SOURCE_VC0; i < NR_OF_RCAR_CSI2_PAD; i++) + priv->pads[i].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&priv->subdev.entity, NR_OF_RCAR_CSI2_PAD, + priv->pads); + if (ret) + goto error; + + pm_runtime_enable(&pdev->dev); + + ret = v4l2_async_register_subdev(&priv->subdev); + if (ret < 0) + goto error; + + dev_info(priv->dev, "%d lanes found\n", priv->lanes); + + return 0; + +error: + v4l2_async_notifier_unregister(&priv->notifier); + v4l2_async_notifier_cleanup(&priv->notifier); + + return ret; +} + +static int rcsi2_remove(struct platform_device *pdev) +{ + struct rcar_csi2 *priv = platform_get_drvdata(pdev); + + v4l2_async_notifier_unregister(&priv->notifier); + v4l2_async_notifier_cleanup(&priv->notifier); + v4l2_async_unregister_subdev(&priv->subdev); + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static struct platform_driver rcar_csi2_pdrv = { + .remove = rcsi2_remove, + .probe = rcsi2_probe, + .driver = { + .name = "rcar-csi2", + .of_match_table = rcar_csi2_of_table, + }, +}; + +module_platform_driver(rcar_csi2_pdrv); + +MODULE_AUTHOR("Niklas Söderlund <niklas.soderlund@ragnatech.se>"); +MODULE_DESCRIPTION("Renesas R-Car MIPI CSI-2 receiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/rcar-vin/rcar-dma.c b/drivers/media/platform/rcar-vin/rcar-dma.c index 4a40e6ad1be7..ac07f99e3516 100644 --- a/drivers/media/platform/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/rcar-vin/rcar-dma.c @@ -16,6 +16,7 @@ #include <linux/delay.h> #include <linux/interrupt.h> +#include <linux/pm_runtime.h> #include <media/videobuf2-dma-contig.h> @@ -33,21 +34,23 @@ #define VNELPRC_REG 0x10 /* Video n End Line Pre-Clip Register */ #define VNSPPRC_REG 0x14 /* Video n Start Pixel Pre-Clip Register */ #define VNEPPRC_REG 0x18 /* Video n End Pixel Pre-Clip Register */ -#define VNSLPOC_REG 0x1C /* Video n Start Line Post-Clip Register */ -#define VNELPOC_REG 0x20 /* Video n End Line Post-Clip Register */ -#define VNSPPOC_REG 0x24 /* Video n Start Pixel Post-Clip Register */ -#define VNEPPOC_REG 0x28 /* Video n End Pixel Post-Clip Register */ #define VNIS_REG 0x2C /* Video n Image Stride Register */ #define VNMB_REG(m) (0x30 + ((m) << 2)) /* Video n Memory Base m Register */ #define VNIE_REG 0x40 /* Video n Interrupt Enable Register */ #define VNINTS_REG 0x44 /* Video n Interrupt Status Register */ #define VNSI_REG 0x48 /* Video n Scanline Interrupt Register */ #define VNMTC_REG 0x4C /* Video n Memory Transfer Control Register */ -#define VNYS_REG 0x50 /* Video n Y Scale Register */ -#define VNXS_REG 0x54 /* Video n X Scale Register */ #define VNDMR_REG 0x58 /* Video n Data Mode Register */ #define VNDMR2_REG 0x5C /* Video n Data Mode Register 2 */ #define VNUVAOF_REG 0x60 /* Video n UV Address Offset Register */ + +/* Register offsets specific for Gen2 */ +#define VNSLPOC_REG 0x1C /* Video n Start Line Post-Clip Register */ +#define VNELPOC_REG 0x20 /* Video n End Line Post-Clip Register */ +#define VNSPPOC_REG 0x24 /* Video n Start Pixel Post-Clip Register */ +#define VNEPPOC_REG 0x28 /* Video n End Pixel Post-Clip Register */ +#define VNYS_REG 0x50 /* Video n Y Scale Register */ +#define VNXS_REG 0x54 /* Video n X Scale Register */ #define VNC1A_REG 0x80 /* Video n Coefficient Set C1A Register */ #define VNC1B_REG 0x84 /* Video n Coefficient Set C1B Register */ #define VNC1C_REG 0x88 /* Video n Coefficient Set C1C Register */ @@ -73,9 +76,13 @@ #define VNC8B_REG 0xF4 /* Video n Coefficient Set C8B Register */ #define VNC8C_REG 0xF8 /* Video n Coefficient Set C8C Register */ +/* Register offsets specific for Gen3 */ +#define VNCSI_IFMD_REG 0x20 /* Video n CSI2 Interface Mode Register */ /* Register bit fields for R-Car VIN */ /* Video n Main Control Register bits */ +#define VNMC_DPINE (1 << 27) /* Gen3 specific */ +#define VNMC_SCLE (1 << 26) /* Gen3 specific */ #define VNMC_FOC (1 << 21) #define VNMC_YCAL (1 << 19) #define VNMC_INF_YUV8_BT656 (0 << 16) @@ -119,6 +126,12 @@ #define VNDMR2_FTEV (1 << 17) #define VNDMR2_VLV(n) ((n & 0xf) << 12) +/* Video n CSI2 Interface Mode Register (Gen3) */ +#define VNCSI_IFMD_DES1 (1 << 26) +#define VNCSI_IFMD_DES0 (1 << 25) +#define VNCSI_IFMD_CSI_CHSEL(n) (((n) & 0xf) << 0) +#define VNCSI_IFMD_CSI_CHSEL_MASK 0xf + struct rvin_buffer { struct vb2_v4l2_buffer vb; struct list_head list; @@ -138,267 +151,6 @@ static u32 rvin_read(struct rvin_dev *vin, u32 offset) return ioread32(vin->base + offset); } -static int rvin_setup(struct rvin_dev *vin) -{ - u32 vnmc, dmr, dmr2, interrupts; - v4l2_std_id std; - bool progressive = false, output_is_yuv = false, input_is_yuv = false; - - switch (vin->format.field) { - case V4L2_FIELD_TOP: - vnmc = VNMC_IM_ODD; - break; - case V4L2_FIELD_BOTTOM: - vnmc = VNMC_IM_EVEN; - break; - case V4L2_FIELD_INTERLACED: - /* Default to TB */ - vnmc = VNMC_IM_FULL; - /* Use BT if video standard can be read and is 60 Hz format */ - if (!v4l2_subdev_call(vin_to_source(vin), video, g_std, &std)) { - if (std & V4L2_STD_525_60) - vnmc = VNMC_IM_FULL | VNMC_FOC; - } - break; - case V4L2_FIELD_INTERLACED_TB: - vnmc = VNMC_IM_FULL; - break; - case V4L2_FIELD_INTERLACED_BT: - vnmc = VNMC_IM_FULL | VNMC_FOC; - break; - case V4L2_FIELD_ALTERNATE: - case V4L2_FIELD_NONE: - vnmc = VNMC_IM_ODD_EVEN; - progressive = true; - break; - default: - vnmc = VNMC_IM_ODD; - break; - } - - /* - * Input interface - */ - switch (vin->digital->code) { - case MEDIA_BUS_FMT_YUYV8_1X16: - /* BT.601/BT.1358 16bit YCbCr422 */ - vnmc |= VNMC_INF_YUV16; - input_is_yuv = true; - break; - case MEDIA_BUS_FMT_UYVY8_2X8: - /* BT.656 8bit YCbCr422 or BT.601 8bit YCbCr422 */ - vnmc |= vin->digital->mbus_cfg.type == V4L2_MBUS_BT656 ? - VNMC_INF_YUV8_BT656 : VNMC_INF_YUV8_BT601; - input_is_yuv = true; - break; - case MEDIA_BUS_FMT_RGB888_1X24: - vnmc |= VNMC_INF_RGB888; - break; - case MEDIA_BUS_FMT_UYVY10_2X10: - /* BT.656 10bit YCbCr422 or BT.601 10bit YCbCr422 */ - vnmc |= vin->digital->mbus_cfg.type == V4L2_MBUS_BT656 ? - VNMC_INF_YUV10_BT656 : VNMC_INF_YUV10_BT601; - input_is_yuv = true; - break; - default: - break; - } - - /* Enable VSYNC Field Toogle mode after one VSYNC input */ - dmr2 = VNDMR2_FTEV | VNDMR2_VLV(1); - - /* Hsync Signal Polarity Select */ - if (!(vin->digital->mbus_cfg.flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) - dmr2 |= VNDMR2_HPS; - - /* Vsync Signal Polarity Select */ - if (!(vin->digital->mbus_cfg.flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) - dmr2 |= VNDMR2_VPS; - - /* - * Output format - */ - switch (vin->format.pixelformat) { - case V4L2_PIX_FMT_NV16: - rvin_write(vin, - ALIGN(vin->format.width * vin->format.height, 0x80), - VNUVAOF_REG); - dmr = VNDMR_DTMD_YCSEP; - output_is_yuv = true; - break; - case V4L2_PIX_FMT_YUYV: - dmr = VNDMR_BPSM; - output_is_yuv = true; - break; - case V4L2_PIX_FMT_UYVY: - dmr = 0; - output_is_yuv = true; - break; - case V4L2_PIX_FMT_XRGB555: - dmr = VNDMR_DTMD_ARGB1555; - break; - case V4L2_PIX_FMT_RGB565: - dmr = 0; - break; - case V4L2_PIX_FMT_XBGR32: - /* Note: not supported on M1 */ - dmr = VNDMR_EXRGB; - break; - default: - vin_err(vin, "Invalid pixelformat (0x%x)\n", - vin->format.pixelformat); - return -EINVAL; - } - - /* Always update on field change */ - vnmc |= VNMC_VUP; - - /* If input and output use the same colorspace, use bypass mode */ - if (input_is_yuv == output_is_yuv) - vnmc |= VNMC_BPS; - - /* Progressive or interlaced mode */ - interrupts = progressive ? VNIE_FIE : VNIE_EFE; - - /* Ack interrupts */ - rvin_write(vin, interrupts, VNINTS_REG); - /* Enable interrupts */ - rvin_write(vin, interrupts, VNIE_REG); - /* Start capturing */ - rvin_write(vin, dmr, VNDMR_REG); - rvin_write(vin, dmr2, VNDMR2_REG); - - /* Enable module */ - rvin_write(vin, vnmc | VNMC_ME, VNMC_REG); - - return 0; -} - -static void rvin_disable_interrupts(struct rvin_dev *vin) -{ - rvin_write(vin, 0, VNIE_REG); -} - -static u32 rvin_get_interrupt_status(struct rvin_dev *vin) -{ - return rvin_read(vin, VNINTS_REG); -} - -static void rvin_ack_interrupt(struct rvin_dev *vin) -{ - rvin_write(vin, rvin_read(vin, VNINTS_REG), VNINTS_REG); -} - -static bool rvin_capture_active(struct rvin_dev *vin) -{ - return rvin_read(vin, VNMS_REG) & VNMS_CA; -} - -static enum v4l2_field rvin_get_active_field(struct rvin_dev *vin, u32 vnms) -{ - if (vin->format.field == V4L2_FIELD_ALTERNATE) { - /* If FS is set it's a Even field */ - if (vnms & VNMS_FS) - return V4L2_FIELD_BOTTOM; - return V4L2_FIELD_TOP; - } - - return vin->format.field; -} - -static void rvin_set_slot_addr(struct rvin_dev *vin, int slot, dma_addr_t addr) -{ - const struct rvin_video_format *fmt; - int offsetx, offsety; - dma_addr_t offset; - - fmt = rvin_format_from_pixel(vin->format.pixelformat); - - /* - * There is no HW support for composition do the beast we can - * by modifying the buffer offset - */ - offsetx = vin->compose.left * fmt->bpp; - offsety = vin->compose.top * vin->format.bytesperline; - offset = addr + offsetx + offsety; - - /* - * The address needs to be 128 bytes aligned. Driver should never accept - * settings that do not satisfy this in the first place... - */ - if (WARN_ON((offsetx | offsety | offset) & HW_BUFFER_MASK)) - return; - - rvin_write(vin, offset, VNMB_REG(slot)); -} - -/* - * Moves a buffer from the queue to the HW slot. If no buffer is - * available use the scratch buffer. The scratch buffer is never - * returned to userspace, its only function is to enable the capture - * loop to keep running. - */ -static void rvin_fill_hw_slot(struct rvin_dev *vin, int slot) -{ - struct rvin_buffer *buf; - struct vb2_v4l2_buffer *vbuf; - dma_addr_t phys_addr; - - /* A already populated slot shall never be overwritten. */ - if (WARN_ON(vin->queue_buf[slot] != NULL)) - return; - - vin_dbg(vin, "Filling HW slot: %d\n", slot); - - if (list_empty(&vin->buf_list)) { - vin->queue_buf[slot] = NULL; - phys_addr = vin->scratch_phys; - } else { - /* Keep track of buffer we give to HW */ - buf = list_entry(vin->buf_list.next, struct rvin_buffer, list); - vbuf = &buf->vb; - list_del_init(to_buf_list(vbuf)); - vin->queue_buf[slot] = vbuf; - - /* Setup DMA */ - phys_addr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); - } - - rvin_set_slot_addr(vin, slot, phys_addr); -} - -static int rvin_capture_start(struct rvin_dev *vin) -{ - int slot, ret; - - for (slot = 0; slot < HW_BUFFER_NUM; slot++) - rvin_fill_hw_slot(vin, slot); - - rvin_crop_scale_comp(vin); - - ret = rvin_setup(vin); - if (ret) - return ret; - - vin_dbg(vin, "Starting to capture\n"); - - /* Continuous Frame Capture Mode */ - rvin_write(vin, VNFC_C_FRAME, VNFC_REG); - - vin->state = RUNNING; - - return 0; -} - -static void rvin_capture_stop(struct rvin_dev *vin) -{ - /* Set continuous & single transfer off */ - rvin_write(vin, 0, VNFC_REG); - - /* Disable module */ - rvin_write(vin, rvin_read(vin, VNMC_REG) & ~VNMC_ME, VNMC_REG); -} - /* ----------------------------------------------------------------------------- * Crop and Scaling Gen2 */ @@ -775,28 +527,10 @@ static void rvin_set_coeff(struct rvin_dev *vin, unsigned short xs) rvin_write(vin, p_set->coeff_set[23], VNC8C_REG); } -void rvin_crop_scale_comp(struct rvin_dev *vin) +static void rvin_crop_scale_comp_gen2(struct rvin_dev *vin) { u32 xs, ys; - /* Set Start/End Pixel/Line Pre-Clip */ - rvin_write(vin, vin->crop.left, VNSPPRC_REG); - rvin_write(vin, vin->crop.left + vin->crop.width - 1, VNEPPRC_REG); - switch (vin->format.field) { - case V4L2_FIELD_INTERLACED: - case V4L2_FIELD_INTERLACED_TB: - case V4L2_FIELD_INTERLACED_BT: - rvin_write(vin, vin->crop.top / 2, VNSLPRC_REG); - rvin_write(vin, (vin->crop.top + vin->crop.height) / 2 - 1, - VNELPRC_REG); - break; - default: - rvin_write(vin, vin->crop.top, VNSLPRC_REG); - rvin_write(vin, vin->crop.top + vin->crop.height - 1, - VNELPRC_REG); - break; - } - /* Set scaling coefficient */ ys = 0; if (vin->crop.height != vin->compose.height) @@ -834,11 +568,6 @@ void rvin_crop_scale_comp(struct rvin_dev *vin) break; } - if (vin->format.pixelformat == V4L2_PIX_FMT_NV16) - rvin_write(vin, ALIGN(vin->format.width, 0x20), VNIS_REG); - else - rvin_write(vin, ALIGN(vin->format.width, 0x10), VNIS_REG); - vin_dbg(vin, "Pre-Clip: %ux%u@%u:%u YS: %d XS: %d Post-Clip: %ux%u@%u:%u\n", vin->crop.width, vin->crop.height, vin->crop.left, @@ -846,12 +575,299 @@ void rvin_crop_scale_comp(struct rvin_dev *vin) 0, 0); } -void rvin_scale_try(struct rvin_dev *vin, struct v4l2_pix_format *pix, - u32 width, u32 height) +void rvin_crop_scale_comp(struct rvin_dev *vin) { - /* All VIN channels on Gen2 have scalers */ - pix->width = width; - pix->height = height; + /* Set Start/End Pixel/Line Pre-Clip */ + rvin_write(vin, vin->crop.left, VNSPPRC_REG); + rvin_write(vin, vin->crop.left + vin->crop.width - 1, VNEPPRC_REG); + + switch (vin->format.field) { + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + rvin_write(vin, vin->crop.top / 2, VNSLPRC_REG); + rvin_write(vin, (vin->crop.top + vin->crop.height) / 2 - 1, + VNELPRC_REG); + break; + default: + rvin_write(vin, vin->crop.top, VNSLPRC_REG); + rvin_write(vin, vin->crop.top + vin->crop.height - 1, + VNELPRC_REG); + break; + } + + /* TODO: Add support for the UDS scaler. */ + if (vin->info->model != RCAR_GEN3) + rvin_crop_scale_comp_gen2(vin); + + if (vin->format.pixelformat == V4L2_PIX_FMT_NV16) + rvin_write(vin, ALIGN(vin->format.width, 0x20), VNIS_REG); + else + rvin_write(vin, ALIGN(vin->format.width, 0x10), VNIS_REG); +} + +/* ----------------------------------------------------------------------------- + * Hardware setup + */ + +static int rvin_setup(struct rvin_dev *vin) +{ + u32 vnmc, dmr, dmr2, interrupts; + bool progressive = false, output_is_yuv = false, input_is_yuv = false; + + switch (vin->format.field) { + case V4L2_FIELD_TOP: + vnmc = VNMC_IM_ODD; + break; + case V4L2_FIELD_BOTTOM: + vnmc = VNMC_IM_EVEN; + break; + case V4L2_FIELD_INTERLACED: + /* Default to TB */ + vnmc = VNMC_IM_FULL; + /* Use BT if video standard can be read and is 60 Hz format */ + if (!vin->info->use_mc && vin->std & V4L2_STD_525_60) + vnmc = VNMC_IM_FULL | VNMC_FOC; + break; + case V4L2_FIELD_INTERLACED_TB: + vnmc = VNMC_IM_FULL; + break; + case V4L2_FIELD_INTERLACED_BT: + vnmc = VNMC_IM_FULL | VNMC_FOC; + break; + case V4L2_FIELD_NONE: + vnmc = VNMC_IM_ODD_EVEN; + progressive = true; + break; + default: + vnmc = VNMC_IM_ODD; + break; + } + + /* + * Input interface + */ + switch (vin->mbus_code) { + case MEDIA_BUS_FMT_YUYV8_1X16: + /* BT.601/BT.1358 16bit YCbCr422 */ + vnmc |= VNMC_INF_YUV16; + input_is_yuv = true; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + vnmc |= VNMC_INF_YUV16 | VNMC_YCAL; + input_is_yuv = true; + break; + case MEDIA_BUS_FMT_UYVY8_2X8: + /* BT.656 8bit YCbCr422 or BT.601 8bit YCbCr422 */ + vnmc |= vin->mbus_cfg.type == V4L2_MBUS_BT656 ? + VNMC_INF_YUV8_BT656 : VNMC_INF_YUV8_BT601; + input_is_yuv = true; + break; + case MEDIA_BUS_FMT_RGB888_1X24: + vnmc |= VNMC_INF_RGB888; + break; + case MEDIA_BUS_FMT_UYVY10_2X10: + /* BT.656 10bit YCbCr422 or BT.601 10bit YCbCr422 */ + vnmc |= vin->mbus_cfg.type == V4L2_MBUS_BT656 ? + VNMC_INF_YUV10_BT656 : VNMC_INF_YUV10_BT601; + input_is_yuv = true; + break; + default: + break; + } + + /* Enable VSYNC Field Toogle mode after one VSYNC input */ + if (vin->info->model == RCAR_GEN3) + dmr2 = VNDMR2_FTEV; + else + dmr2 = VNDMR2_FTEV | VNDMR2_VLV(1); + + /* Hsync Signal Polarity Select */ + if (!(vin->mbus_cfg.flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) + dmr2 |= VNDMR2_HPS; + + /* Vsync Signal Polarity Select */ + if (!(vin->mbus_cfg.flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) + dmr2 |= VNDMR2_VPS; + + /* + * Output format + */ + switch (vin->format.pixelformat) { + case V4L2_PIX_FMT_NV16: + rvin_write(vin, + ALIGN(vin->format.width * vin->format.height, 0x80), + VNUVAOF_REG); + dmr = VNDMR_DTMD_YCSEP; + output_is_yuv = true; + break; + case V4L2_PIX_FMT_YUYV: + dmr = VNDMR_BPSM; + output_is_yuv = true; + break; + case V4L2_PIX_FMT_UYVY: + dmr = 0; + output_is_yuv = true; + break; + case V4L2_PIX_FMT_XRGB555: + dmr = VNDMR_DTMD_ARGB1555; + break; + case V4L2_PIX_FMT_RGB565: + dmr = 0; + break; + case V4L2_PIX_FMT_XBGR32: + /* Note: not supported on M1 */ + dmr = VNDMR_EXRGB; + break; + default: + vin_err(vin, "Invalid pixelformat (0x%x)\n", + vin->format.pixelformat); + return -EINVAL; + } + + /* Always update on field change */ + vnmc |= VNMC_VUP; + + /* If input and output use the same colorspace, use bypass mode */ + if (input_is_yuv == output_is_yuv) + vnmc |= VNMC_BPS; + + if (vin->info->model == RCAR_GEN3) { + /* Select between CSI-2 and Digital input */ + if (vin->mbus_cfg.type == V4L2_MBUS_CSI2) + vnmc &= ~VNMC_DPINE; + else + vnmc |= VNMC_DPINE; + } + + /* Progressive or interlaced mode */ + interrupts = progressive ? VNIE_FIE : VNIE_EFE; + + /* Ack interrupts */ + rvin_write(vin, interrupts, VNINTS_REG); + /* Enable interrupts */ + rvin_write(vin, interrupts, VNIE_REG); + /* Start capturing */ + rvin_write(vin, dmr, VNDMR_REG); + rvin_write(vin, dmr2, VNDMR2_REG); + + /* Enable module */ + rvin_write(vin, vnmc | VNMC_ME, VNMC_REG); + + return 0; +} + +static void rvin_disable_interrupts(struct rvin_dev *vin) +{ + rvin_write(vin, 0, VNIE_REG); +} + +static u32 rvin_get_interrupt_status(struct rvin_dev *vin) +{ + return rvin_read(vin, VNINTS_REG); +} + +static void rvin_ack_interrupt(struct rvin_dev *vin) +{ + rvin_write(vin, rvin_read(vin, VNINTS_REG), VNINTS_REG); +} + +static bool rvin_capture_active(struct rvin_dev *vin) +{ + return rvin_read(vin, VNMS_REG) & VNMS_CA; +} + +static void rvin_set_slot_addr(struct rvin_dev *vin, int slot, dma_addr_t addr) +{ + const struct rvin_video_format *fmt; + int offsetx, offsety; + dma_addr_t offset; + + fmt = rvin_format_from_pixel(vin->format.pixelformat); + + /* + * There is no HW support for composition do the beast we can + * by modifying the buffer offset + */ + offsetx = vin->compose.left * fmt->bpp; + offsety = vin->compose.top * vin->format.bytesperline; + offset = addr + offsetx + offsety; + + /* + * The address needs to be 128 bytes aligned. Driver should never accept + * settings that do not satisfy this in the first place... + */ + if (WARN_ON((offsetx | offsety | offset) & HW_BUFFER_MASK)) + return; + + rvin_write(vin, offset, VNMB_REG(slot)); +} + +/* + * Moves a buffer from the queue to the HW slot. If no buffer is + * available use the scratch buffer. The scratch buffer is never + * returned to userspace, its only function is to enable the capture + * loop to keep running. + */ +static void rvin_fill_hw_slot(struct rvin_dev *vin, int slot) +{ + struct rvin_buffer *buf; + struct vb2_v4l2_buffer *vbuf; + dma_addr_t phys_addr; + + /* A already populated slot shall never be overwritten. */ + if (WARN_ON(vin->queue_buf[slot] != NULL)) + return; + + vin_dbg(vin, "Filling HW slot: %d\n", slot); + + if (list_empty(&vin->buf_list)) { + vin->queue_buf[slot] = NULL; + phys_addr = vin->scratch_phys; + } else { + /* Keep track of buffer we give to HW */ + buf = list_entry(vin->buf_list.next, struct rvin_buffer, list); + vbuf = &buf->vb; + list_del_init(to_buf_list(vbuf)); + vin->queue_buf[slot] = vbuf; + + /* Setup DMA */ + phys_addr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); + } + + rvin_set_slot_addr(vin, slot, phys_addr); +} + +static int rvin_capture_start(struct rvin_dev *vin) +{ + int slot, ret; + + for (slot = 0; slot < HW_BUFFER_NUM; slot++) + rvin_fill_hw_slot(vin, slot); + + rvin_crop_scale_comp(vin); + + ret = rvin_setup(vin); + if (ret) + return ret; + + vin_dbg(vin, "Starting to capture\n"); + + /* Continuous Frame Capture Mode */ + rvin_write(vin, VNFC_C_FRAME, VNFC_REG); + + vin->state = RUNNING; + + return 0; +} + +static void rvin_capture_stop(struct rvin_dev *vin) +{ + /* Set continuous & single transfer off */ + rvin_write(vin, 0, VNFC_REG); + + /* Disable module */ + rvin_write(vin, rvin_read(vin, VNMC_REG) & ~VNMC_ME, VNMC_REG); } /* ----------------------------------------------------------------------------- @@ -896,7 +912,7 @@ static irqreturn_t rvin_irq(int irq, void *data) /* Capture frame */ if (vin->queue_buf[slot]) { - vin->queue_buf[slot]->field = rvin_get_active_field(vin, vnms); + vin->queue_buf[slot]->field = vin->format.field; vin->queue_buf[slot]->sequence = vin->sequence; vin->queue_buf[slot]->vb2_buf.timestamp = ktime_get_ns(); vb2_buffer_done(&vin->queue_buf[slot]->vb2_buf, @@ -984,10 +1000,127 @@ static void rvin_buffer_queue(struct vb2_buffer *vb) spin_unlock_irqrestore(&vin->qlock, flags); } +static int rvin_mc_validate_format(struct rvin_dev *vin, struct v4l2_subdev *sd, + struct media_pad *pad) +{ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + fmt.pad = pad->index; + if (v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt)) + return -EPIPE; + + switch (fmt.format.code) { + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_UYVY10_2X10: + case MEDIA_BUS_FMT_RGB888_1X24: + vin->mbus_code = fmt.format.code; + break; + default: + return -EPIPE; + } + + switch (fmt.format.field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_NONE: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + /* Supported natively */ + break; + case V4L2_FIELD_ALTERNATE: + switch (vin->format.field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_NONE: + break; + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + /* Use VIN hardware to combine the two fields */ + fmt.format.height *= 2; + break; + default: + return -EPIPE; + } + break; + default: + return -EPIPE; + } + + if (fmt.format.width != vin->format.width || + fmt.format.height != vin->format.height || + fmt.format.code != vin->mbus_code) + return -EPIPE; + + return 0; +} + +static int rvin_set_stream(struct rvin_dev *vin, int on) +{ + struct media_pipeline *pipe; + struct media_device *mdev; + struct v4l2_subdev *sd; + struct media_pad *pad; + int ret; + + /* No media controller used, simply pass operation to subdevice. */ + if (!vin->info->use_mc) { + ret = v4l2_subdev_call(vin->digital->subdev, video, s_stream, + on); + + return ret == -ENOIOCTLCMD ? 0 : ret; + } + + pad = media_entity_remote_pad(&vin->pad); + if (!pad) + return -EPIPE; + + sd = media_entity_to_v4l2_subdev(pad->entity); + + if (!on) { + media_pipeline_stop(&vin->vdev.entity); + return v4l2_subdev_call(sd, video, s_stream, 0); + } + + ret = rvin_mc_validate_format(vin, sd, pad); + if (ret) + return ret; + + /* + * The graph lock needs to be taken to protect concurrent + * starts of multiple VIN instances as they might share + * a common subdevice down the line and then should use + * the same pipe. + */ + mdev = vin->vdev.entity.graph_obj.mdev; + mutex_lock(&mdev->graph_mutex); + pipe = sd->entity.pipe ? sd->entity.pipe : &vin->vdev.pipe; + ret = __media_pipeline_start(&vin->vdev.entity, pipe); + mutex_unlock(&mdev->graph_mutex); + if (ret) + return ret; + + ret = v4l2_subdev_call(sd, video, s_stream, 1); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (ret) + media_pipeline_stop(&vin->vdev.entity); + + return ret; +} + static int rvin_start_streaming(struct vb2_queue *vq, unsigned int count) { struct rvin_dev *vin = vb2_get_drv_priv(vq); - struct v4l2_subdev *sd; unsigned long flags; int ret; @@ -1002,8 +1135,13 @@ static int rvin_start_streaming(struct vb2_queue *vq, unsigned int count) return -ENOMEM; } - sd = vin_to_source(vin); - v4l2_subdev_call(sd, video, s_stream, 1); + ret = rvin_set_stream(vin, 1); + if (ret) { + spin_lock_irqsave(&vin->qlock, flags); + return_all_buffers(vin, VB2_BUF_STATE_QUEUED); + spin_unlock_irqrestore(&vin->qlock, flags); + goto out; + } spin_lock_irqsave(&vin->qlock, flags); @@ -1012,11 +1150,11 @@ static int rvin_start_streaming(struct vb2_queue *vq, unsigned int count) ret = rvin_capture_start(vin); if (ret) { return_all_buffers(vin, VB2_BUF_STATE_QUEUED); - v4l2_subdev_call(sd, video, s_stream, 0); + rvin_set_stream(vin, 0); } spin_unlock_irqrestore(&vin->qlock, flags); - +out: if (ret) dma_free_coherent(vin->dev, vin->format.sizeimage, vin->scratch, vin->scratch_phys); @@ -1027,7 +1165,6 @@ static int rvin_start_streaming(struct vb2_queue *vq, unsigned int count) static void rvin_stop_streaming(struct vb2_queue *vq) { struct rvin_dev *vin = vb2_get_drv_priv(vq); - struct v4l2_subdev *sd; unsigned long flags; int retries = 0; @@ -1066,8 +1203,7 @@ static void rvin_stop_streaming(struct vb2_queue *vq) spin_unlock_irqrestore(&vin->qlock, flags); - sd = vin_to_source(vin); - v4l2_subdev_call(sd, video, s_stream, 0); + rvin_set_stream(vin, 0); /* disable interrupts */ rvin_disable_interrupts(vin); @@ -1087,14 +1223,14 @@ static const struct vb2_ops rvin_qops = { .wait_finish = vb2_ops_wait_finish, }; -void rvin_dma_remove(struct rvin_dev *vin) +void rvin_dma_unregister(struct rvin_dev *vin) { mutex_destroy(&vin->lock); v4l2_device_unregister(&vin->v4l2_dev); } -int rvin_dma_probe(struct rvin_dev *vin, int irq) +int rvin_dma_register(struct rvin_dev *vin, int irq) { struct vb2_queue *q = &vin->queue; int i, ret; @@ -1142,7 +1278,43 @@ int rvin_dma_probe(struct rvin_dev *vin, int irq) return 0; error: - rvin_dma_remove(vin); + rvin_dma_unregister(vin); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Gen3 CHSEL manipulation + */ + +/* + * There is no need to have locking around changing the routing + * as it's only possible to do so when no VIN in the group is + * streaming so nothing can race with the VNMC register. + */ +int rvin_set_channel_routing(struct rvin_dev *vin, u8 chsel) +{ + u32 ifmd, vnmc; + int ret; + + ret = pm_runtime_get_sync(vin->dev); + if (ret < 0) + return ret; + + /* Make register writes take effect immediately. */ + vnmc = rvin_read(vin, VNMC_REG); + rvin_write(vin, vnmc & ~VNMC_VUP, VNMC_REG); + + ifmd = VNCSI_IFMD_DES1 | VNCSI_IFMD_DES0 | VNCSI_IFMD_CSI_CHSEL(chsel); + + rvin_write(vin, ifmd, VNCSI_IFMD_REG); + + vin_dbg(vin, "Set IFMD 0x%x\n", ifmd); + + /* Restore VNMC. */ + rvin_write(vin, vnmc, VNMC_REG); + + pm_runtime_put(vin->dev); return ret; } diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c b/drivers/media/platform/rcar-vin/rcar-v4l2.c index b479b882da12..e78fba84d590 100644 --- a/drivers/media/platform/rcar-vin/rcar-v4l2.c +++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c @@ -18,13 +18,16 @@ #include <media/v4l2-event.h> #include <media/v4l2-ioctl.h> +#include <media/v4l2-mc.h> #include <media/v4l2-rect.h> #include "rcar-vin.h" #define RVIN_DEFAULT_FORMAT V4L2_PIX_FMT_YUYV -#define RVIN_MAX_WIDTH 2048 -#define RVIN_MAX_HEIGHT 2048 +#define RVIN_DEFAULT_WIDTH 800 +#define RVIN_DEFAULT_HEIGHT 600 +#define RVIN_DEFAULT_FIELD V4L2_FIELD_NONE +#define RVIN_DEFAULT_COLORSPACE V4L2_COLORSPACE_SRGB /* ----------------------------------------------------------------------------- * Format Conversions @@ -88,99 +91,111 @@ static u32 rvin_format_sizeimage(struct v4l2_pix_format *pix) return pix->bytesperline * pix->height; } -/* ----------------------------------------------------------------------------- - * V4L2 - */ - -static void rvin_reset_crop_compose(struct rvin_dev *vin) +static void rvin_format_align(struct rvin_dev *vin, struct v4l2_pix_format *pix) { - vin->crop.top = vin->crop.left = 0; - vin->crop.width = vin->source.width; - vin->crop.height = vin->source.height; + u32 walign; - vin->compose.top = vin->compose.left = 0; - vin->compose.width = vin->format.width; - vin->compose.height = vin->format.height; + if (!rvin_format_from_pixel(pix->pixelformat) || + (vin->info->model == RCAR_M1 && + pix->pixelformat == V4L2_PIX_FMT_XBGR32)) + pix->pixelformat = RVIN_DEFAULT_FORMAT; + + switch (pix->field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_NONE: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_INTERLACED: + break; + case V4L2_FIELD_ALTERNATE: + /* + * Driver does not (yet) support outputting ALTERNATE to a + * userspace. It does support outputting INTERLACED so use + * the VIN hardware to combine the two fields. + */ + pix->field = V4L2_FIELD_INTERLACED; + pix->height *= 2; + break; + default: + pix->field = RVIN_DEFAULT_FIELD; + break; + } + + /* HW limit width to a multiple of 32 (2^5) for NV16 else 2 (2^1) */ + walign = vin->format.pixelformat == V4L2_PIX_FMT_NV16 ? 5 : 1; + + /* Limit to VIN capabilities */ + v4l_bound_align_image(&pix->width, 2, vin->info->max_width, walign, + &pix->height, 4, vin->info->max_height, 2, 0); + + pix->bytesperline = rvin_format_bytesperline(pix); + pix->sizeimage = rvin_format_sizeimage(pix); + + vin_dbg(vin, "Format %ux%u bpl: %u size: %u\n", + pix->width, pix->height, pix->bytesperline, pix->sizeimage); } +/* ----------------------------------------------------------------------------- + * V4L2 + */ + static int rvin_reset_format(struct rvin_dev *vin) { struct v4l2_subdev_format fmt = { .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = vin->digital->source_pad, }; - struct v4l2_mbus_framefmt *mf = &fmt.format; int ret; - fmt.pad = vin->digital->source_pad; - ret = v4l2_subdev_call(vin_to_source(vin), pad, get_fmt, NULL, &fmt); if (ret) return ret; - vin->format.width = mf->width; - vin->format.height = mf->height; - vin->format.colorspace = mf->colorspace; - vin->format.field = mf->field; + v4l2_fill_pix_format(&vin->format, &fmt.format); - /* - * If the subdevice uses ALTERNATE field mode and G_STD is - * implemented use the VIN HW to combine the two fields to - * one INTERLACED frame. The ALTERNATE field mode can still - * be requested in S_FMT and be respected, this is just the - * default which is applied at probing or when S_STD is called. - */ - if (vin->format.field == V4L2_FIELD_ALTERNATE && - v4l2_subdev_has_op(vin_to_source(vin), video, g_std)) - vin->format.field = V4L2_FIELD_INTERLACED; + rvin_format_align(vin, &vin->format); - switch (vin->format.field) { - case V4L2_FIELD_TOP: - case V4L2_FIELD_BOTTOM: - case V4L2_FIELD_ALTERNATE: - vin->format.height /= 2; - break; - case V4L2_FIELD_NONE: - case V4L2_FIELD_INTERLACED_TB: - case V4L2_FIELD_INTERLACED_BT: - case V4L2_FIELD_INTERLACED: - break; - default: - vin->format.field = V4L2_FIELD_NONE; - break; - } - - rvin_reset_crop_compose(vin); + vin->source.top = 0; + vin->source.left = 0; + vin->source.width = vin->format.width; + vin->source.height = vin->format.height; - vin->format.bytesperline = rvin_format_bytesperline(&vin->format); - vin->format.sizeimage = rvin_format_sizeimage(&vin->format); + vin->crop = vin->source; + vin->compose = vin->source; return 0; } -static int __rvin_try_format_source(struct rvin_dev *vin, - u32 which, - struct v4l2_pix_format *pix, - struct rvin_source_fmt *source) +static int rvin_try_format(struct rvin_dev *vin, u32 which, + struct v4l2_pix_format *pix, + struct v4l2_rect *crop, struct v4l2_rect *compose) { - struct v4l2_subdev *sd; + struct v4l2_subdev *sd = vin_to_source(vin); struct v4l2_subdev_pad_config *pad_cfg; struct v4l2_subdev_format format = { .which = which, + .pad = vin->digital->source_pad, }; enum v4l2_field field; + u32 width, height; int ret; - sd = vin_to_source(vin); - - v4l2_fill_mbus_format(&format.format, pix, vin->digital->code); - pad_cfg = v4l2_subdev_alloc_pad_config(sd); if (pad_cfg == NULL) return -ENOMEM; - format.pad = vin->digital->source_pad; + if (!rvin_format_from_pixel(pix->pixelformat) || + (vin->info->model == RCAR_M1 && + pix->pixelformat == V4L2_PIX_FMT_XBGR32)) + pix->pixelformat = RVIN_DEFAULT_FORMAT; + + v4l2_fill_mbus_format(&format.format, pix, vin->mbus_code); + /* Allow the video device to override field and to scale */ field = pix->field; + width = pix->width; + height = pix->height; ret = v4l2_subdev_call(sd, pad, set_fmt, pad_cfg, &format); if (ret < 0 && ret != -ENOIOCTLCMD) @@ -188,92 +203,36 @@ static int __rvin_try_format_source(struct rvin_dev *vin, v4l2_fill_pix_format(pix, &format.format); - pix->field = field; - - source->width = pix->width; - source->height = pix->height; - - vin_dbg(vin, "Source resolution: %ux%u\n", source->width, - source->height); + if (crop) { + crop->top = 0; + crop->left = 0; + crop->width = pix->width; + crop->height = pix->height; -done: - v4l2_subdev_free_pad_config(pad_cfg); - return ret; -} - -static int __rvin_try_format(struct rvin_dev *vin, - u32 which, - struct v4l2_pix_format *pix, - struct rvin_source_fmt *source) -{ - u32 rwidth, rheight, walign; - int ret; - - /* Requested */ - rwidth = pix->width; - rheight = pix->height; - - /* Keep current field if no specific one is asked for */ - if (pix->field == V4L2_FIELD_ANY) - pix->field = vin->format.field; - - /* If requested format is not supported fallback to the default */ - if (!rvin_format_from_pixel(pix->pixelformat)) { - vin_dbg(vin, "Format 0x%x not found, using default 0x%x\n", - pix->pixelformat, RVIN_DEFAULT_FORMAT); - pix->pixelformat = RVIN_DEFAULT_FORMAT; + /* + * If source is ALTERNATE the driver will use the VIN hardware + * to INTERLACE it. The crop height then needs to be doubled. + */ + if (pix->field == V4L2_FIELD_ALTERNATE) + crop->height *= 2; } - /* Always recalculate */ - pix->bytesperline = 0; - pix->sizeimage = 0; + if (field != V4L2_FIELD_ANY) + pix->field = field; - /* Limit to source capabilities */ - ret = __rvin_try_format_source(vin, which, pix, source); - if (ret) - return ret; + pix->width = width; + pix->height = height; - switch (pix->field) { - case V4L2_FIELD_TOP: - case V4L2_FIELD_BOTTOM: - case V4L2_FIELD_ALTERNATE: - pix->height /= 2; - source->height /= 2; - break; - case V4L2_FIELD_NONE: - case V4L2_FIELD_INTERLACED_TB: - case V4L2_FIELD_INTERLACED_BT: - case V4L2_FIELD_INTERLACED: - break; - default: - pix->field = V4L2_FIELD_NONE; - break; - } - - /* If source can't match format try if VIN can scale */ - if (source->width != rwidth || source->height != rheight) - rvin_scale_try(vin, pix, rwidth, rheight); - - /* HW limit width to a multiple of 32 (2^5) for NV16 else 2 (2^1) */ - walign = vin->format.pixelformat == V4L2_PIX_FMT_NV16 ? 5 : 1; + rvin_format_align(vin, pix); - /* Limit to VIN capabilities */ - v4l_bound_align_image(&pix->width, 2, RVIN_MAX_WIDTH, walign, - &pix->height, 4, RVIN_MAX_HEIGHT, 2, 0); - - pix->bytesperline = max_t(u32, pix->bytesperline, - rvin_format_bytesperline(pix)); - pix->sizeimage = max_t(u32, pix->sizeimage, - rvin_format_sizeimage(pix)); - - if (vin->chip == RCAR_M1 && pix->pixelformat == V4L2_PIX_FMT_XBGR32) { - vin_err(vin, "pixel format XBGR32 not supported on M1\n"); - return -EINVAL; + if (compose) { + compose->top = 0; + compose->left = 0; + compose->width = pix->width; + compose->height = pix->height; } - - vin_dbg(vin, "Requested %ux%u Got %ux%u bpl: %d size: %d\n", - rwidth, rheight, pix->width, pix->height, - pix->bytesperline, pix->sizeimage); +done: + v4l2_subdev_free_pad_config(pad_cfg); return 0; } @@ -294,33 +253,30 @@ static int rvin_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct rvin_dev *vin = video_drvdata(file); - struct rvin_source_fmt source; - return __rvin_try_format(vin, V4L2_SUBDEV_FORMAT_TRY, &f->fmt.pix, - &source); + return rvin_try_format(vin, V4L2_SUBDEV_FORMAT_TRY, &f->fmt.pix, NULL, + NULL); } static int rvin_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct rvin_dev *vin = video_drvdata(file); - struct rvin_source_fmt source; + struct v4l2_rect crop, compose; int ret; if (vb2_is_busy(&vin->queue)) return -EBUSY; - ret = __rvin_try_format(vin, V4L2_SUBDEV_FORMAT_ACTIVE, &f->fmt.pix, - &source); + ret = rvin_try_format(vin, V4L2_SUBDEV_FORMAT_ACTIVE, &f->fmt.pix, + &crop, &compose); if (ret) return ret; - vin->source.width = source.width; - vin->source.height = source.height; - vin->format = f->fmt.pix; - - rvin_reset_crop_compose(vin); + vin->crop = crop; + vin->compose = compose; + vin->source = crop; return 0; } @@ -405,8 +361,8 @@ static int rvin_s_selection(struct file *file, void *fh, max_rect.height = vin->source.height; v4l2_rect_map_inside(&r, &max_rect); - v4l_bound_align_image(&r.width, 2, vin->source.width, 1, - &r.height, 4, vin->source.height, 2, 0); + v4l_bound_align_image(&r.width, 6, vin->source.width, 0, + &r.height, 2, vin->source.height, 0, 0); r.top = clamp_t(s32, r.top, 0, vin->source.height - r.height); r.left = clamp_t(s32, r.left, 0, vin->source.width - r.width); @@ -523,6 +479,8 @@ static int rvin_s_std(struct file *file, void *priv, v4l2_std_id a) if (ret < 0) return ret; + vin->std = a; + /* Changing the standard will change the width/height */ return rvin_reset_format(vin); } @@ -530,9 +488,13 @@ static int rvin_s_std(struct file *file, void *priv, v4l2_std_id a) static int rvin_g_std(struct file *file, void *priv, v4l2_std_id *a) { struct rvin_dev *vin = video_drvdata(file); - struct v4l2_subdev *sd = vin_to_source(vin); - return v4l2_subdev_call(sd, video, g_std, a); + if (v4l2_subdev_has_op(vin_to_source(vin), pad, dv_timings_cap)) + return -ENOIOCTLCMD; + + *a = vin->std; + + return 0; } static int rvin_subscribe_event(struct v4l2_fh *fh, @@ -697,6 +659,97 @@ static const struct v4l2_ioctl_ops rvin_ioctl_ops = { }; /* ----------------------------------------------------------------------------- + * V4L2 Media Controller + */ + +static void rvin_mc_try_format(struct rvin_dev *vin, + struct v4l2_pix_format *pix) +{ + /* + * The V4L2 specification clearly documents the colorspace fields + * as being set by drivers for capture devices. Using the values + * supplied by userspace thus wouldn't comply with the API. Until + * the API is updated force fixed vaules. + */ + pix->colorspace = RVIN_DEFAULT_COLORSPACE; + pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace); + pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); + pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, pix->colorspace, + pix->ycbcr_enc); + + rvin_format_align(vin, pix); +} + +static int rvin_mc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rvin_dev *vin = video_drvdata(file); + + rvin_mc_try_format(vin, &f->fmt.pix); + + return 0; +} + +static int rvin_mc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rvin_dev *vin = video_drvdata(file); + + if (vb2_is_busy(&vin->queue)) + return -EBUSY; + + rvin_mc_try_format(vin, &f->fmt.pix); + + vin->format = f->fmt.pix; + + vin->crop.top = 0; + vin->crop.left = 0; + vin->crop.width = vin->format.width; + vin->crop.height = vin->format.height; + vin->compose = vin->crop; + + return 0; +} + +static int rvin_mc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index != 0) + return -EINVAL; + + i->type = V4L2_INPUT_TYPE_CAMERA; + strlcpy(i->name, "Camera", sizeof(i->name)); + + return 0; +} + +static const struct v4l2_ioctl_ops rvin_mc_ioctl_ops = { + .vidioc_querycap = rvin_querycap, + .vidioc_try_fmt_vid_cap = rvin_mc_try_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = rvin_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = rvin_mc_s_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = rvin_enum_fmt_vid_cap, + + .vidioc_enum_input = rvin_mc_enum_input, + .vidioc_g_input = rvin_g_input, + .vidioc_s_input = rvin_s_input, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = rvin_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* ----------------------------------------------------------------------------- * File Operations */ @@ -839,14 +892,82 @@ static const struct v4l2_file_operations rvin_fops = { .read = vb2_fop_read, }; -void rvin_v4l2_remove(struct rvin_dev *vin) +/* ----------------------------------------------------------------------------- + * Media controller file operations + */ + +static int rvin_mc_open(struct file *file) { + struct rvin_dev *vin = video_drvdata(file); + int ret; + + ret = mutex_lock_interruptible(&vin->lock); + if (ret) + return ret; + + ret = pm_runtime_get_sync(vin->dev); + if (ret < 0) + goto err_unlock; + + ret = v4l2_pipeline_pm_use(&vin->vdev.entity, 1); + if (ret < 0) + goto err_pm; + + file->private_data = vin; + + ret = v4l2_fh_open(file); + if (ret) + goto err_v4l2pm; + + mutex_unlock(&vin->lock); + + return 0; +err_v4l2pm: + v4l2_pipeline_pm_use(&vin->vdev.entity, 0); +err_pm: + pm_runtime_put(vin->dev); +err_unlock: + mutex_unlock(&vin->lock); + + return ret; +} + +static int rvin_mc_release(struct file *file) +{ + struct rvin_dev *vin = video_drvdata(file); + int ret; + + mutex_lock(&vin->lock); + + /* the release helper will cleanup any on-going streaming. */ + ret = _vb2_fop_release(file, NULL); + + v4l2_pipeline_pm_use(&vin->vdev.entity, 0); + pm_runtime_put(vin->dev); + + mutex_unlock(&vin->lock); + + return ret; +} + +static const struct v4l2_file_operations rvin_mc_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = rvin_mc_open, + .release = rvin_mc_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .read = vb2_fop_read, +}; + +void rvin_v4l2_unregister(struct rvin_dev *vin) +{ + if (!video_is_registered(&vin->vdev)) + return; + v4l2_info(&vin->v4l2_dev, "Removing %s\n", video_device_node_name(&vin->vdev)); - /* Checks internaly if handlers have been init or not */ - v4l2_ctrl_handler_free(&vin->ctrl_handler); - /* Checks internaly if vdev have been init or not */ video_unregister_device(&vin->vdev); } @@ -866,58 +987,39 @@ static void rvin_notify(struct v4l2_subdev *sd, } } -int rvin_v4l2_probe(struct rvin_dev *vin) +int rvin_v4l2_register(struct rvin_dev *vin) { struct video_device *vdev = &vin->vdev; - struct v4l2_subdev *sd = vin_to_source(vin); int ret; - v4l2_set_subdev_hostdata(sd, vin); - vin->v4l2_dev.notify = rvin_notify; - ret = v4l2_subdev_call(sd, video, g_tvnorms, &vin->vdev.tvnorms); - if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) - return ret; - - if (vin->vdev.tvnorms == 0) { - /* Disable the STD API if there are no tvnorms defined */ - v4l2_disable_ioctl(&vin->vdev, VIDIOC_G_STD); - v4l2_disable_ioctl(&vin->vdev, VIDIOC_S_STD); - v4l2_disable_ioctl(&vin->vdev, VIDIOC_QUERYSTD); - v4l2_disable_ioctl(&vin->vdev, VIDIOC_ENUMSTD); - } - - /* Add the controls */ - /* - * Currently the subdev with the largest number of controls (13) is - * ov6550. So let's pick 16 as a hint for the control handler. Note - * that this is a hint only: too large and you waste some memory, too - * small and there is a (very) small performance hit when looking up - * controls in the internal hash. - */ - ret = v4l2_ctrl_handler_init(&vin->ctrl_handler, 16); - if (ret < 0) - return ret; - - ret = v4l2_ctrl_add_handler(&vin->ctrl_handler, sd->ctrl_handler, NULL); - if (ret < 0) - return ret; - /* video node */ - vdev->fops = &rvin_fops; vdev->v4l2_dev = &vin->v4l2_dev; vdev->queue = &vin->queue; - strlcpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name)); + snprintf(vdev->name, sizeof(vdev->name), "VIN%u output", vin->id); vdev->release = video_device_release_empty; - vdev->ioctl_ops = &rvin_ioctl_ops; vdev->lock = &vin->lock; - vdev->ctrl_handler = &vin->ctrl_handler; vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; + /* Set a default format */ vin->format.pixelformat = RVIN_DEFAULT_FORMAT; - rvin_reset_format(vin); + vin->format.width = RVIN_DEFAULT_WIDTH; + vin->format.height = RVIN_DEFAULT_HEIGHT; + vin->format.field = RVIN_DEFAULT_FIELD; + vin->format.colorspace = RVIN_DEFAULT_COLORSPACE; + + if (vin->info->use_mc) { + vdev->fops = &rvin_mc_fops; + vdev->ioctl_ops = &rvin_mc_ioctl_ops; + } else { + vdev->fops = &rvin_fops; + vdev->ioctl_ops = &rvin_ioctl_ops; + rvin_reset_format(vin); + } + + rvin_format_align(vin, &vin->format); ret = video_register_device(&vin->vdev, VFL_TYPE_GRABBER, -1); if (ret) { diff --git a/drivers/media/platform/rcar-vin/rcar-vin.h b/drivers/media/platform/rcar-vin/rcar-vin.h index 95897127cc41..c2aef789b491 100644 --- a/drivers/media/platform/rcar-vin/rcar-vin.h +++ b/drivers/media/platform/rcar-vin/rcar-vin.h @@ -17,6 +17,8 @@ #ifndef __RCAR_VIN__ #define __RCAR_VIN__ +#include <linux/kref.h> + #include <media/v4l2-async.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-dev.h> @@ -29,10 +31,24 @@ /* Address alignment mask for HW buffers */ #define HW_BUFFER_MASK 0x7f -enum chip_id { +/* Max number on VIN instances that can be in a system */ +#define RCAR_VIN_NUM 8 + +struct rvin_group; + +enum model_id { RCAR_H1, RCAR_M1, RCAR_GEN2, + RCAR_GEN3, +}; + +enum rvin_csi_id { + RVIN_CSI20, + RVIN_CSI21, + RVIN_CSI40, + RVIN_CSI41, + RVIN_CSI_MAX, }; /** @@ -47,16 +63,6 @@ enum rvin_dma_state { }; /** - * struct rvin_source_fmt - Source information - * @width: Width from source - * @height: Height from source - */ -struct rvin_source_fmt { - u32 width; - u32 height; -}; - -/** * struct rvin_video_format - Data format stored in memory * @fourcc: Pixelformat * @bpp: Bytes per pixel @@ -70,8 +76,6 @@ struct rvin_video_format { * struct rvin_graph_entity - Video endpoint from async framework * @asd: sub-device descriptor for async framework * @subdev: subdevice matched using async framework - * @code: Media bus format from source - * @mbus_cfg: Media bus format from DT * @source_pad: source pad of remote subdevice * @sink_pad: sink pad of remote subdevice */ @@ -79,18 +83,64 @@ struct rvin_graph_entity { struct v4l2_async_subdev asd; struct v4l2_subdev *subdev; - u32 code; - struct v4l2_mbus_config mbus_cfg; - unsigned int source_pad; unsigned int sink_pad; }; /** + * struct rvin_group_route - describes a route from a channel of a + * CSI-2 receiver to a VIN + * + * @csi: CSI-2 receiver ID. + * @channel: Output channel of the CSI-2 receiver. + * @vin: VIN ID. + * @mask: Bitmask of the different CHSEL register values that + * allow for a route from @csi + @chan to @vin. + * + * .. note:: + * Each R-Car CSI-2 receiver has four output channels facing the VIN + * devices, each channel can carry one CSI-2 Virtual Channel (VC). + * There is no correlation between channel number and CSI-2 VC. It's + * up to the CSI-2 receiver driver to configure which VC is output + * on which channel, the VIN devices only care about output channels. + * + * There are in some cases multiple CHSEL register settings which would + * allow for the same route from @csi + @channel to @vin. For example + * on R-Car H3 both the CHSEL values 0 and 3 allow for a route from + * CSI40/VC0 to VIN0. All possible CHSEL values for a route need to be + * recorded as a bitmask in @mask, in this example bit 0 and 3 should + * be set. + */ +struct rvin_group_route { + enum rvin_csi_id csi; + unsigned int channel; + unsigned int vin; + unsigned int mask; +}; + +/** + * struct rvin_info - Information about the particular VIN implementation + * @model: VIN model + * @use_mc: use media controller instead of controlling subdevice + * @max_width: max input width the VIN supports + * @max_height: max input height the VIN supports + * @routes: list of possible routes from the CSI-2 recivers to + * all VINs. The list mush be NULL terminated. + */ +struct rvin_info { + enum model_id model; + bool use_mc; + + unsigned int max_width; + unsigned int max_height; + const struct rvin_group_route *routes; +}; + +/** * struct rvin_dev - Renesas VIN device structure * @dev: (OF) device * @base: device I/O register space remapped to virtual memory - * @chip: type of VIN chip + * @info: info about VIN instance * * @vdev: V4L2 video device associated with VIN * @v4l2_dev: V4L2 device @@ -98,6 +148,10 @@ struct rvin_graph_entity { * @notifier: V4L2 asynchronous subdevs notifier * @digital: entity in the DT for local digital subdevice * + * @group: Gen3 CSI group + * @id: Gen3 group id for this VIN + * @pad: media pad for the video device entity + * * @lock: protects @queue * @queue: vb2 buffers queue * @scratch: cpu address for scratch buffer @@ -110,16 +164,19 @@ struct rvin_graph_entity { * @sequence: V4L2 buffers sequence number * @state: keeps track of operation state * - * @source: active format from the video source + * @mbus_cfg: media bus configuration from DT + * @mbus_code: media bus format code * @format: active V4L2 pixel format * * @crop: active cropping * @compose: active composing + * @source: active size of the video source + * @std: active video standard of the video source */ struct rvin_dev { struct device *dev; void __iomem *base; - enum chip_id chip; + const struct rvin_info *info; struct video_device vdev; struct v4l2_device v4l2_dev; @@ -127,6 +184,10 @@ struct rvin_dev { struct v4l2_async_notifier notifier; struct rvin_graph_entity *digital; + struct rvin_group *group; + unsigned int id; + struct media_pad pad; + struct mutex lock; struct vb2_queue queue; void *scratch; @@ -138,11 +199,14 @@ struct rvin_dev { unsigned int sequence; enum rvin_dma_state state; - struct rvin_source_fmt source; + struct v4l2_mbus_config mbus_cfg; + u32 mbus_code; struct v4l2_pix_format format; struct v4l2_rect crop; struct v4l2_rect compose; + struct v4l2_rect source; + v4l2_std_id std; }; #define vin_to_source(vin) ((vin)->digital->subdev) @@ -153,17 +217,47 @@ struct rvin_dev { #define vin_warn(d, fmt, arg...) dev_warn(d->dev, fmt, ##arg) #define vin_err(d, fmt, arg...) dev_err(d->dev, fmt, ##arg) -int rvin_dma_probe(struct rvin_dev *vin, int irq); -void rvin_dma_remove(struct rvin_dev *vin); +/** + * struct rvin_group - VIN CSI2 group information + * @refcount: number of VIN instances using the group + * + * @mdev: media device which represents the group + * + * @lock: protects the count, notifier, vin and csi members + * @count: number of enabled VIN instances found in DT + * @notifier: pointer to the notifier of a VIN which handles the + * groups async sub-devices. + * @vin: VIN instances which are part of the group + * @csi: array of pairs of fwnode and subdev pointers + * to all CSI-2 subdevices. + */ +struct rvin_group { + struct kref refcount; + + struct media_device mdev; -int rvin_v4l2_probe(struct rvin_dev *vin); -void rvin_v4l2_remove(struct rvin_dev *vin); + struct mutex lock; + unsigned int count; + struct v4l2_async_notifier *notifier; + struct rvin_dev *vin[RCAR_VIN_NUM]; + + struct { + struct fwnode_handle *fwnode; + struct v4l2_subdev *subdev; + } csi[RVIN_CSI_MAX]; +}; + +int rvin_dma_register(struct rvin_dev *vin, int irq); +void rvin_dma_unregister(struct rvin_dev *vin); + +int rvin_v4l2_register(struct rvin_dev *vin); +void rvin_v4l2_unregister(struct rvin_dev *vin); const struct rvin_video_format *rvin_format_from_pixel(u32 pixelformat); /* Cropping, composing and scaling */ -void rvin_scale_try(struct rvin_dev *vin, struct v4l2_pix_format *pix, - u32 width, u32 height); void rvin_crop_scale_comp(struct rvin_dev *vin); +int rvin_set_channel_routing(struct rvin_dev *vin, u8 chsel); + #endif diff --git a/drivers/media/platform/rcar_jpu.c b/drivers/media/platform/rcar_jpu.c index f6092ae45912..8b44a849ab41 100644 --- a/drivers/media/platform/rcar_jpu.c +++ b/drivers/media/platform/rcar_jpu.c @@ -1280,7 +1280,7 @@ static int jpu_open(struct file *file) /* ...issue software reset */ ret = jpu_reset(jpu); if (ret) - goto device_prepare_rollback; + goto jpu_reset_rollback; } jpu->ref_count++; @@ -1288,6 +1288,8 @@ static int jpu_open(struct file *file) mutex_unlock(&jpu->mutex); return 0; +jpu_reset_rollback: + clk_disable_unprepare(jpu->clk); device_prepare_rollback: mutex_unlock(&jpu->mutex); v4l_prepare_rollback: diff --git a/drivers/media/platform/renesas-ceu.c b/drivers/media/platform/renesas-ceu.c index 6599dba5ab84..fe4fe944592d 100644 --- a/drivers/media/platform/renesas-ceu.c +++ b/drivers/media/platform/renesas-ceu.c @@ -777,8 +777,15 @@ static int ceu_try_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt) const struct ceu_fmt *ceu_fmt; int ret; + /* + * Set format on sensor sub device: bus format used to produce memory + * format is selected at initialization time. + */ struct v4l2_subdev_format sd_format = { - .which = V4L2_SUBDEV_FORMAT_TRY, + .which = V4L2_SUBDEV_FORMAT_TRY, + .format = { + .code = ceu_sd->mbus_fmt.mbus_code, + }, }; switch (pix->pixelformat) { @@ -800,10 +807,6 @@ static int ceu_try_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt) v4l_bound_align_image(&pix->width, 2, CEU_MAX_WIDTH, 4, &pix->height, 4, CEU_MAX_HEIGHT, 4, 0); - /* - * Set format on sensor sub device: bus format used to produce memory - * format is selected at initialization time. - */ v4l2_fill_mbus_format_mplane(&sd_format.format, pix); ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, &pad_cfg, &sd_format); if (ret) @@ -827,8 +830,15 @@ static int ceu_set_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt) struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd; int ret; + /* + * Set format on sensor sub device: bus format used to produce memory + * format is selected at initialization time. + */ struct v4l2_subdev_format format = { .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .format = { + .code = ceu_sd->mbus_fmt.mbus_code, + }, }; ret = ceu_try_fmt(ceudev, v4l2_fmt); @@ -1365,7 +1375,7 @@ static int ceu_notify_complete(struct v4l2_async_notifier *notifier) return ret; /* Register the video device. */ - strncpy(vdev->name, DRIVER_NAME, strlen(DRIVER_NAME)); + strlcpy(vdev->name, DRIVER_NAME, sizeof(vdev->name)); vdev->v4l2_dev = v4l2_dev; vdev->lock = &ceudev->mlock; vdev->queue = &ceudev->vb2_vq; @@ -1545,6 +1555,7 @@ static const struct ceu_data ceu_data_sh4 = { #if IS_ENABLED(CONFIG_OF) static const struct of_device_id ceu_of_match[] = { { .compatible = "renesas,r7s72100-ceu", .data = &ceu_data_rz }, + { .compatible = "renesas,r8a7740-ceu", .data = &ceu_data_rz }, { } }; MODULE_DEVICE_TABLE(of, ceu_of_match); diff --git a/drivers/media/platform/s5p-jpeg/jpeg-hw-exynos3250.c b/drivers/media/platform/s5p-jpeg/jpeg-hw-exynos3250.c index 0974b9a7a584..0861842b2dfc 100644 --- a/drivers/media/platform/s5p-jpeg/jpeg-hw-exynos3250.c +++ b/drivers/media/platform/s5p-jpeg/jpeg-hw-exynos3250.c @@ -425,9 +425,9 @@ unsigned int exynos3250_jpeg_get_int_status(void __iomem *regs) } void exynos3250_jpeg_clear_int_status(void __iomem *regs, - unsigned int value) + unsigned int value) { - return writel(value, regs + EXYNOS3250_JPGINTST); + writel(value, regs + EXYNOS3250_JPGINTST); } unsigned int exynos3250_jpeg_operating(void __iomem *regs) diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_dec.c b/drivers/media/platform/s5p-mfc/s5p_mfc_dec.c index 5cf4d9921264..6a3cc4f86c5d 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_dec.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_dec.c @@ -271,8 +271,8 @@ static int vidioc_querycap(struct file *file, void *priv, { struct s5p_mfc_dev *dev = video_drvdata(file); - strncpy(cap->driver, S5P_MFC_NAME, sizeof(cap->driver) - 1); - strncpy(cap->card, dev->vfd_dec->name, sizeof(cap->card) - 1); + strlcpy(cap->driver, S5P_MFC_NAME, sizeof(cap->driver)); + strlcpy(cap->card, dev->vfd_dec->name, sizeof(cap->card)); snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", dev_name(&dev->plat_dev->dev)); /* diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c b/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c index 5c0462ca9993..570f391f2cfd 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_enc.c @@ -1313,8 +1313,8 @@ static int vidioc_querycap(struct file *file, void *priv, { struct s5p_mfc_dev *dev = video_drvdata(file); - strncpy(cap->driver, S5P_MFC_NAME, sizeof(cap->driver) - 1); - strncpy(cap->card, dev->vfd_enc->name, sizeof(cap->card) - 1); + strlcpy(cap->driver, S5P_MFC_NAME, sizeof(cap->driver)); + strlcpy(cap->card, dev->vfd_enc->name, sizeof(cap->card)); snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", dev_name(&dev->plat_dev->dev)); /* diff --git a/drivers/media/platform/soc_camera/Kconfig b/drivers/media/platform/soc_camera/Kconfig index f5979c12ad61..669d116b8f09 100644 --- a/drivers/media/platform/soc_camera/Kconfig +++ b/drivers/media/platform/soc_camera/Kconfig @@ -18,9 +18,8 @@ config SOC_CAMERA_PLATFORM config VIDEO_SH_MOBILE_CEU tristate "SuperH Mobile CEU Interface driver" - depends on VIDEO_DEV && SOC_CAMERA && HAS_DMA && HAVE_CLK + depends on VIDEO_DEV && SOC_CAMERA && HAVE_CLK depends on ARCH_SHMOBILE || COMPILE_TEST - depends on HAS_DMA select VIDEOBUF2_DMA_CONTIG select SOC_CAMERA_SCALE_CROP ---help--- diff --git a/drivers/media/platform/soc_camera/soc_camera_platform.c b/drivers/media/platform/soc_camera/soc_camera_platform.c index cb4986b8f798..ce00e90d4e3c 100644 --- a/drivers/media/platform/soc_camera/soc_camera_platform.c +++ b/drivers/media/platform/soc_camera/soc_camera_platform.c @@ -159,7 +159,8 @@ static int soc_camera_platform_probe(struct platform_device *pdev) v4l2_subdev_init(&priv->subdev, &platform_subdev_ops); v4l2_set_subdevdata(&priv->subdev, p); - strncpy(priv->subdev.name, dev_name(&pdev->dev), V4L2_SUBDEV_NAME_SIZE); + strlcpy(priv->subdev.name, dev_name(&pdev->dev), + sizeof(priv->subdev.name)); return v4l2_device_register_subdev(&ici->v4l2_dev, &priv->subdev); } diff --git a/drivers/media/platform/sti/bdisp/bdisp-hw.c b/drivers/media/platform/sti/bdisp/bdisp-hw.c index a5eb592e12c0..26d9fa7aeb5f 100644 --- a/drivers/media/platform/sti/bdisp/bdisp-hw.c +++ b/drivers/media/platform/sti/bdisp/bdisp-hw.c @@ -455,7 +455,7 @@ int bdisp_hw_alloc_nodes(struct bdisp_ctx *ctx) /* Allocate all the nodes within a single memory page */ base = dma_alloc_attrs(dev, node_size * MAX_NB_NODE, &paddr, - GFP_KERNEL | GFP_DMA, DMA_ATTR_WRITE_COMBINE); + GFP_KERNEL, DMA_ATTR_WRITE_COMBINE); if (!base) { dev_err(dev, "%s no mem\n", __func__); return -ENOMEM; diff --git a/drivers/media/platform/sti/bdisp/bdisp-v4l2.c b/drivers/media/platform/sti/bdisp/bdisp-v4l2.c index bf4ca16db440..66b64096f5de 100644 --- a/drivers/media/platform/sti/bdisp/bdisp-v4l2.c +++ b/drivers/media/platform/sti/bdisp/bdisp-v4l2.c @@ -1297,6 +1297,10 @@ static int bdisp_probe(struct platform_device *pdev) if (!bdisp) return -ENOMEM; + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + bdisp->pdev = pdev; bdisp->dev = dev; platform_set_drvdata(pdev, bdisp); diff --git a/drivers/media/platform/sti/c8sectpfe/Kconfig b/drivers/media/platform/sti/c8sectpfe/Kconfig index 740190f8a3b6..7420a50572d3 100644 --- a/drivers/media/platform/sti/c8sectpfe/Kconfig +++ b/drivers/media/platform/sti/c8sectpfe/Kconfig @@ -1,6 +1,6 @@ config DVB_C8SECTPFE tristate "STMicroelectronics C8SECTPFE DVB support" - depends on PINCTRL && DVB_CORE && I2C && HAS_DMA + depends on PINCTRL && DVB_CORE && I2C depends on ARCH_STI || ARCH_MULTIPLATFORM || COMPILE_TEST select FW_LOADER select DEBUG_FS diff --git a/drivers/media/platform/sti/hva/hva-mem.c b/drivers/media/platform/sti/hva/hva-mem.c index caf50cd4bb77..68047b60b66c 100644 --- a/drivers/media/platform/sti/hva/hva-mem.c +++ b/drivers/media/platform/sti/hva/hva-mem.c @@ -22,7 +22,7 @@ int hva_mem_alloc(struct hva_ctx *ctx, u32 size, const char *name, return -ENOMEM; } - base = dma_alloc_attrs(dev, size, &paddr, GFP_KERNEL | GFP_DMA, + base = dma_alloc_attrs(dev, size, &paddr, GFP_KERNEL, DMA_ATTR_WRITE_COMBINE); if (!base) { dev_err(dev, "%s %s : dma_alloc_attrs failed for %s (size=%d)\n", diff --git a/drivers/media/platform/sti/hva/hva-v4l2.c b/drivers/media/platform/sti/hva/hva-v4l2.c index 2ab0b5cc5c22..15080cb00fa7 100644 --- a/drivers/media/platform/sti/hva/hva-v4l2.c +++ b/drivers/media/platform/sti/hva/hva-v4l2.c @@ -1355,6 +1355,10 @@ static int hva_probe(struct platform_device *pdev) goto err; } + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + hva->dev = dev; hva->pdev = pdev; platform_set_drvdata(pdev, hva); diff --git a/drivers/media/platform/via-camera.c b/drivers/media/platform/via-camera.c index e9a02639554b..f01c3e813247 100644 --- a/drivers/media/platform/via-camera.c +++ b/drivers/media/platform/via-camera.c @@ -178,7 +178,7 @@ static int via_sensor_power_setup(struct via_camera *cam) cam->power_gpio = viafb_gpio_lookup("VGPIO3"); cam->reset_gpio = viafb_gpio_lookup("VGPIO2"); - if (cam->power_gpio < 0 || cam->reset_gpio < 0) { + if (!gpio_is_valid(cam->power_gpio) || !gpio_is_valid(cam->reset_gpio)) { dev_err(&cam->platdev->dev, "Unable to find GPIO lines\n"); return -EINVAL; } diff --git a/drivers/media/platform/video-mux.c b/drivers/media/platform/video-mux.c index ee89ad76bee2..1fb887293337 100644 --- a/drivers/media/platform/video-mux.c +++ b/drivers/media/platform/video-mux.c @@ -45,6 +45,7 @@ static int video_mux_link_setup(struct media_entity *entity, { struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); + u16 source_pad = entity->num_pads - 1; int ret = 0; /* @@ -74,6 +75,9 @@ static int video_mux_link_setup(struct media_entity *entity, if (ret < 0) goto out; vmux->active = local->index; + + /* Propagate the active format to the source */ + vmux->format_mbus[source_pad] = vmux->format_mbus[vmux->active]; } else { if (vmux->active != local->index) goto out; @@ -162,14 +166,20 @@ static int video_mux_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_format *sdformat) { struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); - struct v4l2_mbus_framefmt *mbusformat; + struct v4l2_mbus_framefmt *mbusformat, *source_mbusformat; struct media_pad *pad = &vmux->pads[sdformat->pad]; + u16 source_pad = sd->entity.num_pads - 1; mbusformat = __video_mux_get_pad_format(sd, cfg, sdformat->pad, sdformat->which); if (!mbusformat) return -EINVAL; + source_mbusformat = __video_mux_get_pad_format(sd, cfg, source_pad, + sdformat->which); + if (!source_mbusformat) + return -EINVAL; + mutex_lock(&vmux->lock); /* Source pad mirrors active sink pad, no limitations on sink pads */ @@ -178,6 +188,10 @@ static int video_mux_set_format(struct v4l2_subdev *sd, *mbusformat = sdformat->format; + /* Propagate the format from an active sink to source */ + if ((pad->flags & MEDIA_PAD_FL_SINK) && (pad->index == vmux->active)) + *source_mbusformat = sdformat->format; + mutex_unlock(&vmux->lock); return 0; diff --git a/drivers/media/platform/vivid/vivid-vid-common.c b/drivers/media/platform/vivid/vivid-vid-common.c index e5914be0e12d..be531caa2cdf 100644 --- a/drivers/media/platform/vivid/vivid-vid-common.c +++ b/drivers/media/platform/vivid/vivid-vid-common.c @@ -860,7 +860,7 @@ int vidioc_g_edid(struct file *file, void *_fh, return -ENODATA; if (edid->start_block >= dev->edid_blocks) return -EINVAL; - if (edid->start_block + edid->blocks > dev->edid_blocks) + if (edid->blocks > dev->edid_blocks - edid->start_block) edid->blocks = dev->edid_blocks - edid->start_block; if (adap) cec_set_edid_phys_addr(dev->edid, dev->edid_blocks * 128, adap->phys_addr); diff --git a/drivers/media/platform/vsp1/Makefile b/drivers/media/platform/vsp1/Makefile index f5cd6f0491cb..4bb4dcbef7b5 100644 --- a/drivers/media/platform/vsp1/Makefile +++ b/drivers/media/platform/vsp1/Makefile @@ -3,8 +3,8 @@ vsp1-y := vsp1_drv.o vsp1_entity.o vsp1_pipe.o vsp1-y += vsp1_dl.o vsp1_drm.o vsp1_video.o vsp1-y += vsp1_rpf.o vsp1_rwpf.o vsp1_wpf.o vsp1-y += vsp1_clu.o vsp1_hsit.o vsp1_lut.o -vsp1-y += vsp1_bru.o vsp1_sru.o vsp1_uds.o +vsp1-y += vsp1_brx.o vsp1_sru.o vsp1_uds.o vsp1-y += vsp1_hgo.o vsp1_hgt.o vsp1_histo.o -vsp1-y += vsp1_lif.o +vsp1-y += vsp1_lif.o vsp1_uif.o obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1.o diff --git a/drivers/media/platform/vsp1/vsp1.h b/drivers/media/platform/vsp1/vsp1.h index 78ef838416b3..33f632331474 100644 --- a/drivers/media/platform/vsp1/vsp1.h +++ b/drivers/media/platform/vsp1/vsp1.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1.h -- R-Car VSP1 Driver * * Copyright (C) 2013-2014 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_H__ #define __VSP1_H__ @@ -30,7 +26,7 @@ struct rcar_fcp_device; struct vsp1_drm; struct vsp1_entity; struct vsp1_platform_data; -struct vsp1_bru; +struct vsp1_brx; struct vsp1_clu; struct vsp1_hgo; struct vsp1_hgt; @@ -40,10 +36,12 @@ struct vsp1_lut; struct vsp1_rwpf; struct vsp1_sru; struct vsp1_uds; +struct vsp1_uif; #define VSP1_MAX_LIF 2 #define VSP1_MAX_RPF 5 #define VSP1_MAX_UDS 3 +#define VSP1_MAX_UIF 2 #define VSP1_MAX_WPF 4 #define VSP1_HAS_LUT (1 << 1) @@ -64,6 +62,7 @@ struct vsp1_device_info { unsigned int lif_count; unsigned int rpf_count; unsigned int uds_count; + unsigned int uif_count; unsigned int wpf_count; unsigned int num_bru_inputs; bool uapi; @@ -78,8 +77,8 @@ struct vsp1_device { struct rcar_fcp_device *fcp; struct device *bus_master; - struct vsp1_bru *brs; - struct vsp1_bru *bru; + struct vsp1_brx *brs; + struct vsp1_brx *bru; struct vsp1_clu *clu; struct vsp1_hgo *hgo; struct vsp1_hgt *hgt; @@ -90,6 +89,7 @@ struct vsp1_device { struct vsp1_rwpf *rpf[VSP1_MAX_RPF]; struct vsp1_sru *sru; struct vsp1_uds *uds[VSP1_MAX_UDS]; + struct vsp1_uif *uif[VSP1_MAX_UIF]; struct vsp1_rwpf *wpf[VSP1_MAX_WPF]; struct list_head entities; diff --git a/drivers/media/platform/vsp1/vsp1_bru.h b/drivers/media/platform/vsp1/vsp1_bru.h deleted file mode 100644 index c98ed96d8de6..000000000000 --- a/drivers/media/platform/vsp1/vsp1_bru.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * vsp1_bru.h -- R-Car VSP1 Blend ROP Unit - * - * Copyright (C) 2013 Renesas Corporation - * - * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_BRU_H__ -#define __VSP1_BRU_H__ - -#include <media/media-entity.h> -#include <media/v4l2-ctrls.h> -#include <media/v4l2-subdev.h> - -#include "vsp1_entity.h" - -struct vsp1_device; -struct vsp1_rwpf; - -#define BRU_PAD_SINK(n) (n) - -struct vsp1_bru { - struct vsp1_entity entity; - unsigned int base; - - struct v4l2_ctrl_handler ctrls; - - struct { - struct vsp1_rwpf *rpf; - } inputs[VSP1_MAX_RPF]; - - u32 bgcolor; -}; - -static inline struct vsp1_bru *to_bru(struct v4l2_subdev *subdev) -{ - return container_of(subdev, struct vsp1_bru, entity.subdev); -} - -struct vsp1_bru *vsp1_bru_create(struct vsp1_device *vsp1, - enum vsp1_entity_type type); - -#endif /* __VSP1_BRU_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_bru.c b/drivers/media/platform/vsp1/vsp1_brx.c index e8fd2ae3b3eb..359917b5d842 100644 --- a/drivers/media/platform/vsp1/vsp1_bru.c +++ b/drivers/media/platform/vsp1/vsp1_brx.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* - * vsp1_bru.c -- R-Car VSP1 Blend ROP Unit + * vsp1_brx.c -- R-Car VSP1 Blend ROP Unit (BRU and BRS) * * Copyright (C) 2013 Renesas Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/device.h> @@ -17,45 +13,45 @@ #include <media/v4l2-subdev.h> #include "vsp1.h" -#include "vsp1_bru.h" +#include "vsp1_brx.h" #include "vsp1_dl.h" #include "vsp1_pipe.h" #include "vsp1_rwpf.h" #include "vsp1_video.h" -#define BRU_MIN_SIZE 1U -#define BRU_MAX_SIZE 8190U +#define BRX_MIN_SIZE 1U +#define BRX_MAX_SIZE 8190U /* ----------------------------------------------------------------------------- * Device Access */ -static inline void vsp1_bru_write(struct vsp1_bru *bru, struct vsp1_dl_list *dl, - u32 reg, u32 data) +static inline void vsp1_brx_write(struct vsp1_brx *brx, + struct vsp1_dl_body *dlb, u32 reg, u32 data) { - vsp1_dl_list_write(dl, bru->base + reg, data); + vsp1_dl_body_write(dlb, brx->base + reg, data); } /* ----------------------------------------------------------------------------- * Controls */ -static int bru_s_ctrl(struct v4l2_ctrl *ctrl) +static int brx_s_ctrl(struct v4l2_ctrl *ctrl) { - struct vsp1_bru *bru = - container_of(ctrl->handler, struct vsp1_bru, ctrls); + struct vsp1_brx *brx = + container_of(ctrl->handler, struct vsp1_brx, ctrls); switch (ctrl->id) { case V4L2_CID_BG_COLOR: - bru->bgcolor = ctrl->val; + brx->bgcolor = ctrl->val; break; } return 0; } -static const struct v4l2_ctrl_ops bru_ctrl_ops = { - .s_ctrl = bru_s_ctrl, +static const struct v4l2_ctrl_ops brx_ctrl_ops = { + .s_ctrl = brx_s_ctrl, }; /* ----------------------------------------------------------------------------- @@ -63,12 +59,12 @@ static const struct v4l2_ctrl_ops bru_ctrl_ops = { */ /* - * The BRU can't perform format conversion, all sink and source formats must be + * The BRx can't perform format conversion, all sink and source formats must be * identical. We pick the format on the first sink pad (pad 0) and propagate it * to all other pads. */ -static int bru_enum_mbus_code(struct v4l2_subdev *subdev, +static int brx_enum_mbus_code(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_mbus_code_enum *code) { @@ -81,7 +77,7 @@ static int bru_enum_mbus_code(struct v4l2_subdev *subdev, ARRAY_SIZE(codes)); } -static int bru_enum_frame_size(struct v4l2_subdev *subdev, +static int brx_enum_frame_size(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_frame_size_enum *fse) { @@ -92,29 +88,29 @@ static int bru_enum_frame_size(struct v4l2_subdev *subdev, fse->code != MEDIA_BUS_FMT_AYUV8_1X32) return -EINVAL; - fse->min_width = BRU_MIN_SIZE; - fse->max_width = BRU_MAX_SIZE; - fse->min_height = BRU_MIN_SIZE; - fse->max_height = BRU_MAX_SIZE; + fse->min_width = BRX_MIN_SIZE; + fse->max_width = BRX_MAX_SIZE; + fse->min_height = BRX_MIN_SIZE; + fse->max_height = BRX_MAX_SIZE; return 0; } -static struct v4l2_rect *bru_get_compose(struct vsp1_bru *bru, +static struct v4l2_rect *brx_get_compose(struct vsp1_brx *brx, struct v4l2_subdev_pad_config *cfg, unsigned int pad) { - return v4l2_subdev_get_try_compose(&bru->entity.subdev, cfg, pad); + return v4l2_subdev_get_try_compose(&brx->entity.subdev, cfg, pad); } -static void bru_try_format(struct vsp1_bru *bru, +static void brx_try_format(struct vsp1_brx *brx, struct v4l2_subdev_pad_config *config, unsigned int pad, struct v4l2_mbus_framefmt *fmt) { struct v4l2_mbus_framefmt *format; switch (pad) { - case BRU_PAD_SINK(0): + case BRX_PAD_SINK(0): /* Default to YUV if the requested format is not supported. */ if (fmt->code != MEDIA_BUS_FMT_ARGB8888_1X32 && fmt->code != MEDIA_BUS_FMT_AYUV8_1X32) @@ -122,46 +118,46 @@ static void bru_try_format(struct vsp1_bru *bru, break; default: - /* The BRU can't perform format conversion. */ - format = vsp1_entity_get_pad_format(&bru->entity, config, - BRU_PAD_SINK(0)); + /* The BRx can't perform format conversion. */ + format = vsp1_entity_get_pad_format(&brx->entity, config, + BRX_PAD_SINK(0)); fmt->code = format->code; break; } - fmt->width = clamp(fmt->width, BRU_MIN_SIZE, BRU_MAX_SIZE); - fmt->height = clamp(fmt->height, BRU_MIN_SIZE, BRU_MAX_SIZE); + fmt->width = clamp(fmt->width, BRX_MIN_SIZE, BRX_MAX_SIZE); + fmt->height = clamp(fmt->height, BRX_MIN_SIZE, BRX_MAX_SIZE); fmt->field = V4L2_FIELD_NONE; fmt->colorspace = V4L2_COLORSPACE_SRGB; } -static int bru_set_format(struct v4l2_subdev *subdev, +static int brx_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt) { - struct vsp1_bru *bru = to_bru(subdev); + struct vsp1_brx *brx = to_brx(subdev); struct v4l2_subdev_pad_config *config; struct v4l2_mbus_framefmt *format; int ret = 0; - mutex_lock(&bru->entity.lock); + mutex_lock(&brx->entity.lock); - config = vsp1_entity_get_pad_config(&bru->entity, cfg, fmt->which); + config = vsp1_entity_get_pad_config(&brx->entity, cfg, fmt->which); if (!config) { ret = -EINVAL; goto done; } - bru_try_format(bru, config, fmt->pad, &fmt->format); + brx_try_format(brx, config, fmt->pad, &fmt->format); - format = vsp1_entity_get_pad_format(&bru->entity, config, fmt->pad); + format = vsp1_entity_get_pad_format(&brx->entity, config, fmt->pad); *format = fmt->format; /* Reset the compose rectangle */ - if (fmt->pad != bru->entity.source_pad) { + if (fmt->pad != brx->entity.source_pad) { struct v4l2_rect *compose; - compose = bru_get_compose(bru, config, fmt->pad); + compose = brx_get_compose(brx, config, fmt->pad); compose->left = 0; compose->top = 0; compose->width = format->width; @@ -169,48 +165,48 @@ static int bru_set_format(struct v4l2_subdev *subdev, } /* Propagate the format code to all pads */ - if (fmt->pad == BRU_PAD_SINK(0)) { + if (fmt->pad == BRX_PAD_SINK(0)) { unsigned int i; - for (i = 0; i <= bru->entity.source_pad; ++i) { - format = vsp1_entity_get_pad_format(&bru->entity, + for (i = 0; i <= brx->entity.source_pad; ++i) { + format = vsp1_entity_get_pad_format(&brx->entity, config, i); format->code = fmt->format.code; } } done: - mutex_unlock(&bru->entity.lock); + mutex_unlock(&brx->entity.lock); return ret; } -static int bru_get_selection(struct v4l2_subdev *subdev, +static int brx_get_selection(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_selection *sel) { - struct vsp1_bru *bru = to_bru(subdev); + struct vsp1_brx *brx = to_brx(subdev); struct v4l2_subdev_pad_config *config; - if (sel->pad == bru->entity.source_pad) + if (sel->pad == brx->entity.source_pad) return -EINVAL; switch (sel->target) { case V4L2_SEL_TGT_COMPOSE_BOUNDS: sel->r.left = 0; sel->r.top = 0; - sel->r.width = BRU_MAX_SIZE; - sel->r.height = BRU_MAX_SIZE; + sel->r.width = BRX_MAX_SIZE; + sel->r.height = BRX_MAX_SIZE; return 0; case V4L2_SEL_TGT_COMPOSE: - config = vsp1_entity_get_pad_config(&bru->entity, cfg, + config = vsp1_entity_get_pad_config(&brx->entity, cfg, sel->which); if (!config) return -EINVAL; - mutex_lock(&bru->entity.lock); - sel->r = *bru_get_compose(bru, config, sel->pad); - mutex_unlock(&bru->entity.lock); + mutex_lock(&brx->entity.lock); + sel->r = *brx_get_compose(brx, config, sel->pad); + mutex_unlock(&brx->entity.lock); return 0; default: @@ -218,25 +214,25 @@ static int bru_get_selection(struct v4l2_subdev *subdev, } } -static int bru_set_selection(struct v4l2_subdev *subdev, +static int brx_set_selection(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_selection *sel) { - struct vsp1_bru *bru = to_bru(subdev); + struct vsp1_brx *brx = to_brx(subdev); struct v4l2_subdev_pad_config *config; struct v4l2_mbus_framefmt *format; struct v4l2_rect *compose; int ret = 0; - if (sel->pad == bru->entity.source_pad) + if (sel->pad == brx->entity.source_pad) return -EINVAL; if (sel->target != V4L2_SEL_TGT_COMPOSE) return -EINVAL; - mutex_lock(&bru->entity.lock); + mutex_lock(&brx->entity.lock); - config = vsp1_entity_get_pad_config(&bru->entity, cfg, sel->which); + config = vsp1_entity_get_pad_config(&brx->entity, cfg, sel->which); if (!config) { ret = -EINVAL; goto done; @@ -246,8 +242,8 @@ static int bru_set_selection(struct v4l2_subdev *subdev, * The compose rectangle top left corner must be inside the output * frame. */ - format = vsp1_entity_get_pad_format(&bru->entity, config, - bru->entity.source_pad); + format = vsp1_entity_get_pad_format(&brx->entity, config, + brx->entity.source_pad); sel->r.left = clamp_t(unsigned int, sel->r.left, 0, format->width - 1); sel->r.top = clamp_t(unsigned int, sel->r.top, 0, format->height - 1); @@ -255,51 +251,47 @@ static int bru_set_selection(struct v4l2_subdev *subdev, * Scaling isn't supported, the compose rectangle size must be identical * to the sink format size. */ - format = vsp1_entity_get_pad_format(&bru->entity, config, sel->pad); + format = vsp1_entity_get_pad_format(&brx->entity, config, sel->pad); sel->r.width = format->width; sel->r.height = format->height; - compose = bru_get_compose(bru, config, sel->pad); + compose = brx_get_compose(brx, config, sel->pad); *compose = sel->r; done: - mutex_unlock(&bru->entity.lock); + mutex_unlock(&brx->entity.lock); return ret; } -static const struct v4l2_subdev_pad_ops bru_pad_ops = { +static const struct v4l2_subdev_pad_ops brx_pad_ops = { .init_cfg = vsp1_entity_init_cfg, - .enum_mbus_code = bru_enum_mbus_code, - .enum_frame_size = bru_enum_frame_size, + .enum_mbus_code = brx_enum_mbus_code, + .enum_frame_size = brx_enum_frame_size, .get_fmt = vsp1_subdev_get_pad_format, - .set_fmt = bru_set_format, - .get_selection = bru_get_selection, - .set_selection = bru_set_selection, + .set_fmt = brx_set_format, + .get_selection = brx_get_selection, + .set_selection = brx_set_selection, }; -static const struct v4l2_subdev_ops bru_ops = { - .pad = &bru_pad_ops, +static const struct v4l2_subdev_ops brx_ops = { + .pad = &brx_pad_ops, }; /* ----------------------------------------------------------------------------- * VSP1 Entity Operations */ -static void bru_configure(struct vsp1_entity *entity, - struct vsp1_pipeline *pipe, - struct vsp1_dl_list *dl, - enum vsp1_entity_params params) +static void brx_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb) { - struct vsp1_bru *bru = to_bru(&entity->subdev); + struct vsp1_brx *brx = to_brx(&entity->subdev); struct v4l2_mbus_framefmt *format; unsigned int flags; unsigned int i; - if (params != VSP1_ENTITY_PARAMS_INIT) - return; - - format = vsp1_entity_get_pad_format(&bru->entity, bru->entity.config, - bru->entity.source_pad); + format = vsp1_entity_get_pad_format(&brx->entity, brx->entity.config, + brx->entity.source_pad); /* * The hardware is extremely flexible but we have no userspace API to @@ -313,7 +305,7 @@ static void bru_configure(struct vsp1_entity *entity, * format at the pipeline output is premultiplied. */ flags = pipe->output ? pipe->output->format.flags : 0; - vsp1_bru_write(bru, dl, VI6_BRU_INCTRL, + vsp1_brx_write(brx, dlb, VI6_BRU_INCTRL, flags & V4L2_PIX_FMT_FLAG_PREMUL_ALPHA ? 0 : VI6_BRU_INCTRL_NRM); @@ -321,12 +313,12 @@ static void bru_configure(struct vsp1_entity *entity, * Set the background position to cover the whole output image and * configure its color. */ - vsp1_bru_write(bru, dl, VI6_BRU_VIRRPF_SIZE, + vsp1_brx_write(brx, dlb, VI6_BRU_VIRRPF_SIZE, (format->width << VI6_BRU_VIRRPF_SIZE_HSIZE_SHIFT) | (format->height << VI6_BRU_VIRRPF_SIZE_VSIZE_SHIFT)); - vsp1_bru_write(bru, dl, VI6_BRU_VIRRPF_LOC, 0); + vsp1_brx_write(brx, dlb, VI6_BRU_VIRRPF_LOC, 0); - vsp1_bru_write(bru, dl, VI6_BRU_VIRRPF_COL, bru->bgcolor | + vsp1_brx_write(brx, dlb, VI6_BRU_VIRRPF_COL, brx->bgcolor | (0xff << VI6_BRU_VIRRPF_COL_A_SHIFT)); /* @@ -336,25 +328,25 @@ static void bru_configure(struct vsp1_entity *entity, * unit. */ if (entity->type == VSP1_ENTITY_BRU) - vsp1_bru_write(bru, dl, VI6_BRU_ROP, + vsp1_brx_write(brx, dlb, VI6_BRU_ROP, VI6_BRU_ROP_DSTSEL_BRUIN(1) | VI6_BRU_ROP_CROP(VI6_ROP_NOP) | VI6_BRU_ROP_AROP(VI6_ROP_NOP)); - for (i = 0; i < bru->entity.source_pad; ++i) { + for (i = 0; i < brx->entity.source_pad; ++i) { bool premultiplied = false; u32 ctrl = 0; /* - * Configure all Blend/ROP units corresponding to an enabled BRU + * Configure all Blend/ROP units corresponding to an enabled BRx * input for alpha blending. Blend/ROP units corresponding to - * disabled BRU inputs are used in ROP NOP mode to ignore the + * disabled BRx inputs are used in ROP NOP mode to ignore the * SRC input. */ - if (bru->inputs[i].rpf) { + if (brx->inputs[i].rpf) { ctrl |= VI6_BRU_CTRL_RBC; - premultiplied = bru->inputs[i].rpf->format.flags + premultiplied = brx->inputs[i].rpf->format.flags & V4L2_PIX_FMT_FLAG_PREMUL_ALPHA; } else { ctrl |= VI6_BRU_CTRL_CROP(VI6_ROP_NOP) @@ -378,7 +370,7 @@ static void bru_configure(struct vsp1_entity *entity, if (!(entity->type == VSP1_ENTITY_BRU && i == 1)) ctrl |= VI6_BRU_CTRL_SRCSEL_BRUIN(i); - vsp1_bru_write(bru, dl, VI6_BRU_CTRL(i), ctrl); + vsp1_brx_write(brx, dlb, VI6_BRU_CTRL(i), ctrl); /* * Harcode the blending formula to @@ -393,7 +385,7 @@ static void bru_configure(struct vsp1_entity *entity, * * otherwise. */ - vsp1_bru_write(bru, dl, VI6_BRU_BLD(i), + vsp1_brx_write(brx, dlb, VI6_BRU_BLD(i), VI6_BRU_BLD_CCMDX_255_SRC_A | (premultiplied ? VI6_BRU_BLD_CCMDY_COEFY : VI6_BRU_BLD_CCMDY_SRC_A) | @@ -403,29 +395,29 @@ static void bru_configure(struct vsp1_entity *entity, } } -static const struct vsp1_entity_operations bru_entity_ops = { - .configure = bru_configure, +static const struct vsp1_entity_operations brx_entity_ops = { + .configure_stream = brx_configure_stream, }; /* ----------------------------------------------------------------------------- * Initialization and Cleanup */ -struct vsp1_bru *vsp1_bru_create(struct vsp1_device *vsp1, +struct vsp1_brx *vsp1_brx_create(struct vsp1_device *vsp1, enum vsp1_entity_type type) { - struct vsp1_bru *bru; + struct vsp1_brx *brx; unsigned int num_pads; const char *name; int ret; - bru = devm_kzalloc(vsp1->dev, sizeof(*bru), GFP_KERNEL); - if (bru == NULL) + brx = devm_kzalloc(vsp1->dev, sizeof(*brx), GFP_KERNEL); + if (brx == NULL) return ERR_PTR(-ENOMEM); - bru->base = type == VSP1_ENTITY_BRU ? VI6_BRU_BASE : VI6_BRS_BASE; - bru->entity.ops = &bru_entity_ops; - bru->entity.type = type; + brx->base = type == VSP1_ENTITY_BRU ? VI6_BRU_BASE : VI6_BRS_BASE; + brx->entity.ops = &brx_entity_ops; + brx->entity.type = type; if (type == VSP1_ENTITY_BRU) { num_pads = vsp1->info->num_bru_inputs + 1; @@ -435,26 +427,26 @@ struct vsp1_bru *vsp1_bru_create(struct vsp1_device *vsp1, name = "brs"; } - ret = vsp1_entity_init(vsp1, &bru->entity, name, num_pads, &bru_ops, + ret = vsp1_entity_init(vsp1, &brx->entity, name, num_pads, &brx_ops, MEDIA_ENT_F_PROC_VIDEO_COMPOSER); if (ret < 0) return ERR_PTR(ret); /* Initialize the control handler. */ - v4l2_ctrl_handler_init(&bru->ctrls, 1); - v4l2_ctrl_new_std(&bru->ctrls, &bru_ctrl_ops, V4L2_CID_BG_COLOR, + v4l2_ctrl_handler_init(&brx->ctrls, 1); + v4l2_ctrl_new_std(&brx->ctrls, &brx_ctrl_ops, V4L2_CID_BG_COLOR, 0, 0xffffff, 1, 0); - bru->bgcolor = 0; + brx->bgcolor = 0; - bru->entity.subdev.ctrl_handler = &bru->ctrls; + brx->entity.subdev.ctrl_handler = &brx->ctrls; - if (bru->ctrls.error) { + if (brx->ctrls.error) { dev_err(vsp1->dev, "%s: failed to initialize controls\n", name); - ret = bru->ctrls.error; - vsp1_entity_destroy(&bru->entity); + ret = brx->ctrls.error; + vsp1_entity_destroy(&brx->entity); return ERR_PTR(ret); } - return bru; + return brx; } diff --git a/drivers/media/platform/vsp1/vsp1_brx.h b/drivers/media/platform/vsp1/vsp1_brx.h new file mode 100644 index 000000000000..6abbb8c3343c --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_brx.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_brx.h -- R-Car VSP1 Blend ROP Unit (BRU and BRS) + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_BRX_H__ +#define __VSP1_BRX_H__ + +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> + +#include "vsp1_entity.h" + +struct vsp1_device; +struct vsp1_rwpf; + +#define BRX_PAD_SINK(n) (n) + +struct vsp1_brx { + struct vsp1_entity entity; + unsigned int base; + + struct v4l2_ctrl_handler ctrls; + + struct { + struct vsp1_rwpf *rpf; + } inputs[VSP1_MAX_RPF]; + + u32 bgcolor; +}; + +static inline struct vsp1_brx *to_brx(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_brx, entity.subdev); +} + +struct vsp1_brx *vsp1_brx_create(struct vsp1_device *vsp1, + enum vsp1_entity_type type); + +#endif /* __VSP1_BRX_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_clu.c b/drivers/media/platform/vsp1/vsp1_clu.c index f2fb26e5ab4e..942fc14c19d1 100644 --- a/drivers/media/platform/vsp1/vsp1_clu.c +++ b/drivers/media/platform/vsp1/vsp1_clu.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_clu.c -- R-Car VSP1 Cubic Look-Up Table * * Copyright (C) 2015-2016 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/device.h> @@ -23,14 +19,16 @@ #define CLU_MIN_SIZE 4U #define CLU_MAX_SIZE 8190U +#define CLU_SIZE (17 * 17 * 17) + /* ----------------------------------------------------------------------------- * Device Access */ -static inline void vsp1_clu_write(struct vsp1_clu *clu, struct vsp1_dl_list *dl, - u32 reg, u32 data) +static inline void vsp1_clu_write(struct vsp1_clu *clu, + struct vsp1_dl_body *dlb, u32 reg, u32 data) { - vsp1_dl_list_write(dl, reg, data); + vsp1_dl_body_write(dlb, reg, data); } /* ----------------------------------------------------------------------------- @@ -47,19 +45,19 @@ static int clu_set_table(struct vsp1_clu *clu, struct v4l2_ctrl *ctrl) struct vsp1_dl_body *dlb; unsigned int i; - dlb = vsp1_dl_fragment_alloc(clu->entity.vsp1, 1 + 17 * 17 * 17); + dlb = vsp1_dl_body_get(clu->pool); if (!dlb) return -ENOMEM; - vsp1_dl_fragment_write(dlb, VI6_CLU_ADDR, 0); - for (i = 0; i < 17 * 17 * 17; ++i) - vsp1_dl_fragment_write(dlb, VI6_CLU_DATA, ctrl->p_new.p_u32[i]); + vsp1_dl_body_write(dlb, VI6_CLU_ADDR, 0); + for (i = 0; i < CLU_SIZE; ++i) + vsp1_dl_body_write(dlb, VI6_CLU_DATA, ctrl->p_new.p_u32[i]); spin_lock_irq(&clu->lock); swap(clu->clu, dlb); spin_unlock_irq(&clu->lock); - vsp1_dl_fragment_free(dlb); + vsp1_dl_body_put(dlb); return 0; } @@ -118,18 +116,18 @@ static const struct v4l2_ctrl_config clu_mode_control = { * V4L2 Subdevice Pad Operations */ +static const unsigned int clu_codes[] = { + MEDIA_BUS_FMT_ARGB8888_1X32, + MEDIA_BUS_FMT_AHSV8888_1X32, + MEDIA_BUS_FMT_AYUV8_1X32, +}; + static int clu_enum_mbus_code(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_mbus_code_enum *code) { - static const unsigned int codes[] = { - MEDIA_BUS_FMT_ARGB8888_1X32, - MEDIA_BUS_FMT_AHSV8888_1X32, - MEDIA_BUS_FMT_AYUV8_1X32, - }; - - return vsp1_subdev_enum_mbus_code(subdev, cfg, code, codes, - ARRAY_SIZE(codes)); + return vsp1_subdev_enum_mbus_code(subdev, cfg, code, clu_codes, + ARRAY_SIZE(clu_codes)); } static int clu_enum_frame_size(struct v4l2_subdev *subdev, @@ -145,51 +143,10 @@ static int clu_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt) { - struct vsp1_clu *clu = to_clu(subdev); - struct v4l2_subdev_pad_config *config; - struct v4l2_mbus_framefmt *format; - int ret = 0; - - mutex_lock(&clu->entity.lock); - - config = vsp1_entity_get_pad_config(&clu->entity, cfg, fmt->which); - if (!config) { - ret = -EINVAL; - goto done; - } - - /* Default to YUV if the requested format is not supported. */ - if (fmt->format.code != MEDIA_BUS_FMT_ARGB8888_1X32 && - fmt->format.code != MEDIA_BUS_FMT_AHSV8888_1X32 && - fmt->format.code != MEDIA_BUS_FMT_AYUV8_1X32) - fmt->format.code = MEDIA_BUS_FMT_AYUV8_1X32; - - format = vsp1_entity_get_pad_format(&clu->entity, config, fmt->pad); - - if (fmt->pad == CLU_PAD_SOURCE) { - /* The CLU output format can't be modified. */ - fmt->format = *format; - goto done; - } - - format->code = fmt->format.code; - format->width = clamp_t(unsigned int, fmt->format.width, - CLU_MIN_SIZE, CLU_MAX_SIZE); - format->height = clamp_t(unsigned int, fmt->format.height, - CLU_MIN_SIZE, CLU_MAX_SIZE); - format->field = V4L2_FIELD_NONE; - format->colorspace = V4L2_COLORSPACE_SRGB; - - fmt->format = *format; - - /* Propagate the format to the source pad. */ - format = vsp1_entity_get_pad_format(&clu->entity, config, - CLU_PAD_SOURCE); - *format = fmt->format; - -done: - mutex_unlock(&clu->entity.lock); - return ret; + return vsp1_subdev_set_pad_format(subdev, cfg, fmt, clu_codes, + ARRAY_SIZE(clu_codes), + CLU_MIN_SIZE, CLU_MIN_SIZE, + CLU_MAX_SIZE, CLU_MAX_SIZE); } /* ----------------------------------------------------------------------------- @@ -212,57 +169,65 @@ static const struct v4l2_subdev_ops clu_ops = { * VSP1 Entity Operations */ -static void clu_configure(struct vsp1_entity *entity, - struct vsp1_pipeline *pipe, - struct vsp1_dl_list *dl, - enum vsp1_entity_params params) +static void clu_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb) { struct vsp1_clu *clu = to_clu(&entity->subdev); - struct vsp1_dl_body *dlb; + struct v4l2_mbus_framefmt *format; + + /* + * The yuv_mode can't be changed during streaming. Cache it internally + * for future runtime configuration calls. + */ + format = vsp1_entity_get_pad_format(&clu->entity, + clu->entity.config, + CLU_PAD_SINK); + clu->yuv_mode = format->code == MEDIA_BUS_FMT_AYUV8_1X32; +} + +static void clu_configure_frame(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_clu *clu = to_clu(&entity->subdev); + struct vsp1_dl_body *clu_dlb; unsigned long flags; u32 ctrl = VI6_CLU_CTRL_AAI | VI6_CLU_CTRL_MVS | VI6_CLU_CTRL_EN; - switch (params) { - case VSP1_ENTITY_PARAMS_INIT: { - /* - * The format can't be changed during streaming, only verify it - * at setup time and store the information internally for future - * runtime configuration calls. - */ - struct v4l2_mbus_framefmt *format; - - format = vsp1_entity_get_pad_format(&clu->entity, - clu->entity.config, - CLU_PAD_SINK); - clu->yuv_mode = format->code == MEDIA_BUS_FMT_AYUV8_1X32; - break; - } - - case VSP1_ENTITY_PARAMS_PARTITION: - break; + /* 2D mode can only be used with the YCbCr pixel encoding. */ + if (clu->mode == V4L2_CID_VSP1_CLU_MODE_2D && clu->yuv_mode) + ctrl |= VI6_CLU_CTRL_AX1I_2D | VI6_CLU_CTRL_AX2I_2D + | VI6_CLU_CTRL_OS0_2D | VI6_CLU_CTRL_OS1_2D + | VI6_CLU_CTRL_OS2_2D | VI6_CLU_CTRL_M2D; - case VSP1_ENTITY_PARAMS_RUNTIME: - /* 2D mode can only be used with the YCbCr pixel encoding. */ - if (clu->mode == V4L2_CID_VSP1_CLU_MODE_2D && clu->yuv_mode) - ctrl |= VI6_CLU_CTRL_AX1I_2D | VI6_CLU_CTRL_AX2I_2D - | VI6_CLU_CTRL_OS0_2D | VI6_CLU_CTRL_OS1_2D - | VI6_CLU_CTRL_OS2_2D | VI6_CLU_CTRL_M2D; + vsp1_clu_write(clu, dlb, VI6_CLU_CTRL, ctrl); - vsp1_clu_write(clu, dl, VI6_CLU_CTRL, ctrl); + spin_lock_irqsave(&clu->lock, flags); + clu_dlb = clu->clu; + clu->clu = NULL; + spin_unlock_irqrestore(&clu->lock, flags); - spin_lock_irqsave(&clu->lock, flags); - dlb = clu->clu; - clu->clu = NULL; - spin_unlock_irqrestore(&clu->lock, flags); + if (clu_dlb) { + vsp1_dl_list_add_body(dl, clu_dlb); - if (dlb) - vsp1_dl_list_add_fragment(dl, dlb); - break; + /* Release our local reference. */ + vsp1_dl_body_put(clu_dlb); } } +static void clu_destroy(struct vsp1_entity *entity) +{ + struct vsp1_clu *clu = to_clu(&entity->subdev); + + vsp1_dl_body_pool_destroy(clu->pool); +} + static const struct vsp1_entity_operations clu_entity_ops = { - .configure = clu_configure, + .configure_stream = clu_configure_stream, + .configure_frame = clu_configure_frame, + .destroy = clu_destroy, }; /* ----------------------------------------------------------------------------- @@ -288,6 +253,17 @@ struct vsp1_clu *vsp1_clu_create(struct vsp1_device *vsp1) if (ret < 0) return ERR_PTR(ret); + /* + * Pre-allocate a body pool, with 3 bodies allowing a userspace update + * before the hardware has committed a previous set of tables, handling + * both the queued and pending dl entries. One extra entry is added to + * the CLU_SIZE to allow for the VI6_CLU_ADDR header. + */ + clu->pool = vsp1_dl_body_pool_create(clu->entity.vsp1, 3, CLU_SIZE + 1, + 0); + if (!clu->pool) + return ERR_PTR(-ENOMEM); + /* Initialize the control handler. */ v4l2_ctrl_handler_init(&clu->ctrls, 2); v4l2_ctrl_new_custom(&clu->ctrls, &clu_table_control, NULL); diff --git a/drivers/media/platform/vsp1/vsp1_clu.h b/drivers/media/platform/vsp1/vsp1_clu.h index 036e0a2f1a42..cef2f44481ba 100644 --- a/drivers/media/platform/vsp1/vsp1_clu.h +++ b/drivers/media/platform/vsp1/vsp1_clu.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1_clu.h -- R-Car VSP1 Cubic Look-Up Table * * Copyright (C) 2015 Renesas Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_CLU_H__ #define __VSP1_CLU_H__ @@ -36,6 +32,7 @@ struct vsp1_clu { spinlock_t lock; unsigned int mode; struct vsp1_dl_body *clu; + struct vsp1_dl_body_pool *pool; }; static inline struct vsp1_clu *to_clu(struct v4l2_subdev *subdev) diff --git a/drivers/media/platform/vsp1/vsp1_dl.c b/drivers/media/platform/vsp1/vsp1_dl.c index 0b86ed01e85d..d9b9cdd8fbe2 100644 --- a/drivers/media/platform/vsp1/vsp1_dl.c +++ b/drivers/media/platform/vsp1/vsp1_dl.c @@ -1,19 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0+ /* - * vsp1_dl.h -- R-Car VSP1 Display List + * vsp1_dl.c -- R-Car VSP1 Display List * * Copyright (C) 2015 Renesas Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/device.h> #include <linux/dma-mapping.h> #include <linux/gfp.h> +#include <linux/refcount.h> #include <linux/slab.h> #include <linux/workqueue.h> @@ -45,14 +42,22 @@ struct vsp1_dl_entry { /** * struct vsp1_dl_body - Display list body * @list: entry in the display list list of bodies + * @free: entry in the pool free body list + * @pool: pool to which this body belongs * @vsp1: the VSP1 device * @entries: array of entries * @dma: DMA address of the entries * @size: size of the DMA memory in bytes * @num_entries: number of stored entries + * @max_entries: number of entries available */ struct vsp1_dl_body { struct list_head list; + struct list_head free; + + refcount_t refcnt; + + struct vsp1_dl_body_pool *pool; struct vsp1_device *vsp1; struct vsp1_dl_entry *entries; @@ -60,6 +65,31 @@ struct vsp1_dl_body { size_t size; unsigned int num_entries; + unsigned int max_entries; +}; + +/** + * struct vsp1_dl_body_pool - display list body pool + * @dma: DMA address of the entries + * @size: size of the full DMA memory pool in bytes + * @mem: CPU memory pointer for the pool + * @bodies: Array of DLB structures for the pool + * @free: List of free DLB entries + * @lock: Protects the free list + * @vsp1: the VSP1 device + */ +struct vsp1_dl_body_pool { + /* DMA allocation */ + dma_addr_t dma; + size_t size; + void *mem; + + /* Body management */ + struct vsp1_dl_body *bodies; + struct list_head free; + spinlock_t lock; + + struct vsp1_device *vsp1; }; /** @@ -69,9 +99,10 @@ struct vsp1_dl_body { * @header: display list header, NULL for headerless lists * @dma: DMA address for the header * @body0: first display list body - * @fragments: list of extra display list bodies + * @bodies: list of extra display list bodies * @has_chain: if true, indicates that there's a partition chain * @chain: entry in the display list partition chain + * @internal: whether the display list is used for internal purpose */ struct vsp1_dl_list { struct list_head list; @@ -80,11 +111,13 @@ struct vsp1_dl_list { struct vsp1_dl_header *header; dma_addr_t dma; - struct vsp1_dl_body body0; - struct list_head fragments; + struct vsp1_dl_body *body0; + struct list_head bodies; bool has_chain; struct list_head chain; + + bool internal; }; enum vsp1_dl_mode { @@ -98,13 +131,12 @@ enum vsp1_dl_mode { * @mode: display list operation mode (header or headerless) * @singleshot: execute the display list in single-shot mode * @vsp1: the VSP1 device - * @lock: protects the free, active, queued, pending and gc_fragments lists + * @lock: protects the free, active, queued, and pending lists * @free: array of all free display lists * @active: list currently being processed (loaded) by hardware * @queued: list queued to the hardware (written to the DL registers) * @pending: list waiting to be queued to the hardware - * @gc_work: fragments garbage collector work struct - * @gc_fragments: array of display list fragments waiting to be freed + * @pool: body pool for the display list bodies */ struct vsp1_dl_manager { unsigned int index; @@ -118,109 +150,164 @@ struct vsp1_dl_manager { struct vsp1_dl_list *queued; struct vsp1_dl_list *pending; - struct work_struct gc_work; - struct list_head gc_fragments; + struct vsp1_dl_body_pool *pool; }; /* ----------------------------------------------------------------------------- * Display List Body Management */ -/* - * Initialize a display list body object and allocate DMA memory for the body - * data. The display list body object is expected to have been initialized to - * 0 when allocated. +/** + * vsp1_dl_body_pool_create - Create a pool of bodies from a single allocation + * @vsp1: The VSP1 device + * @num_bodies: The number of bodies to allocate + * @num_entries: The maximum number of entries that a body can contain + * @extra_size: Extra allocation provided for the bodies + * + * Allocate a pool of display list bodies each with enough memory to contain the + * requested number of entries plus the @extra_size. + * + * Return a pointer to a pool on success or NULL if memory can't be allocated. */ -static int vsp1_dl_body_init(struct vsp1_device *vsp1, - struct vsp1_dl_body *dlb, unsigned int num_entries, - size_t extra_size) +struct vsp1_dl_body_pool * +vsp1_dl_body_pool_create(struct vsp1_device *vsp1, unsigned int num_bodies, + unsigned int num_entries, size_t extra_size) { - size_t size = num_entries * sizeof(*dlb->entries) + extra_size; + struct vsp1_dl_body_pool *pool; + size_t dlb_size; + unsigned int i; - dlb->vsp1 = vsp1; - dlb->size = size; + pool = kzalloc(sizeof(*pool), GFP_KERNEL); + if (!pool) + return NULL; - dlb->entries = dma_alloc_wc(vsp1->bus_master, dlb->size, &dlb->dma, - GFP_KERNEL); - if (!dlb->entries) - return -ENOMEM; + pool->vsp1 = vsp1; - return 0; + /* + * TODO: 'extra_size' is only used by vsp1_dlm_create(), to allocate + * extra memory for the display list header. We need only one header per + * display list, not per display list body, thus this allocation is + * extraneous and should be reworked in the future. + */ + dlb_size = num_entries * sizeof(struct vsp1_dl_entry) + extra_size; + pool->size = dlb_size * num_bodies; + + pool->bodies = kcalloc(num_bodies, sizeof(*pool->bodies), GFP_KERNEL); + if (!pool->bodies) { + kfree(pool); + return NULL; + } + + pool->mem = dma_alloc_wc(vsp1->bus_master, pool->size, &pool->dma, + GFP_KERNEL); + if (!pool->mem) { + kfree(pool->bodies); + kfree(pool); + return NULL; + } + + spin_lock_init(&pool->lock); + INIT_LIST_HEAD(&pool->free); + + for (i = 0; i < num_bodies; ++i) { + struct vsp1_dl_body *dlb = &pool->bodies[i]; + + dlb->pool = pool; + dlb->max_entries = num_entries; + + dlb->dma = pool->dma + i * dlb_size; + dlb->entries = pool->mem + i * dlb_size; + + list_add_tail(&dlb->free, &pool->free); + } + + return pool; } -/* - * Cleanup a display list body and free allocated DMA memory allocated. +/** + * vsp1_dl_body_pool_destroy - Release a body pool + * @pool: The body pool + * + * Release all components of a pool allocation. */ -static void vsp1_dl_body_cleanup(struct vsp1_dl_body *dlb) +void vsp1_dl_body_pool_destroy(struct vsp1_dl_body_pool *pool) { - dma_free_wc(dlb->vsp1->bus_master, dlb->size, dlb->entries, dlb->dma); + if (!pool) + return; + + if (pool->mem) + dma_free_wc(pool->vsp1->bus_master, pool->size, pool->mem, + pool->dma); + + kfree(pool->bodies); + kfree(pool); } /** - * vsp1_dl_fragment_alloc - Allocate a display list fragment - * @vsp1: The VSP1 device - * @num_entries: The maximum number of entries that the fragment can contain + * vsp1_dl_body_get - Obtain a body from a pool + * @pool: The body pool * - * Allocate a display list fragment with enough memory to contain the requested - * number of entries. + * Obtain a body from the pool without blocking. * - * Return a pointer to a fragment on success or NULL if memory can't be - * allocated. + * Returns a display list body or NULL if there are none available. */ -struct vsp1_dl_body *vsp1_dl_fragment_alloc(struct vsp1_device *vsp1, - unsigned int num_entries) +struct vsp1_dl_body *vsp1_dl_body_get(struct vsp1_dl_body_pool *pool) { - struct vsp1_dl_body *dlb; - int ret; + struct vsp1_dl_body *dlb = NULL; + unsigned long flags; - dlb = kzalloc(sizeof(*dlb), GFP_KERNEL); - if (!dlb) - return NULL; + spin_lock_irqsave(&pool->lock, flags); - ret = vsp1_dl_body_init(vsp1, dlb, num_entries, 0); - if (ret < 0) { - kfree(dlb); - return NULL; + if (!list_empty(&pool->free)) { + dlb = list_first_entry(&pool->free, struct vsp1_dl_body, free); + list_del(&dlb->free); + refcount_set(&dlb->refcnt, 1); } + spin_unlock_irqrestore(&pool->lock, flags); + return dlb; } /** - * vsp1_dl_fragment_free - Free a display list fragment - * @dlb: The fragment - * - * Free the given display list fragment and the associated DMA memory. - * - * Fragments must only be freed explicitly if they are not added to a display - * list, as the display list will take ownership of them and free them - * otherwise. Manual free typically happens at cleanup time for fragments that - * have been allocated but not used. + * vsp1_dl_body_put - Return a body back to its pool + * @dlb: The display list body * - * Passing a NULL pointer to this function is safe, in that case no operation - * will be performed. + * Return a body back to the pool, and reset the num_entries to clear the list. */ -void vsp1_dl_fragment_free(struct vsp1_dl_body *dlb) +void vsp1_dl_body_put(struct vsp1_dl_body *dlb) { + unsigned long flags; + if (!dlb) return; - vsp1_dl_body_cleanup(dlb); - kfree(dlb); + if (!refcount_dec_and_test(&dlb->refcnt)) + return; + + dlb->num_entries = 0; + + spin_lock_irqsave(&dlb->pool->lock, flags); + list_add_tail(&dlb->free, &dlb->pool->free); + spin_unlock_irqrestore(&dlb->pool->lock, flags); } /** - * vsp1_dl_fragment_write - Write a register to a display list fragment - * @dlb: The fragment + * vsp1_dl_body_write - Write a register to a display list body + * @dlb: The body * @reg: The register address * @data: The register value * - * Write the given register and value to the display list fragment. The maximum - * number of entries that can be written in a fragment is specified when the - * fragment is allocated by vsp1_dl_fragment_alloc(). + * Write the given register and value to the display list body. The maximum + * number of entries that can be written in a body is specified when the body is + * allocated by vsp1_dl_body_alloc(). */ -void vsp1_dl_fragment_write(struct vsp1_dl_body *dlb, u32 reg, u32 data) +void vsp1_dl_body_write(struct vsp1_dl_body *dlb, u32 reg, u32 data) { + if (WARN_ONCE(dlb->num_entries >= dlb->max_entries, + "DLB size exceeded (max %u)", dlb->max_entries)) + return; + dlb->entries[dlb->num_entries].addr = reg; dlb->entries[dlb->num_entries].data = data; dlb->num_entries++; @@ -233,51 +320,47 @@ void vsp1_dl_fragment_write(struct vsp1_dl_body *dlb, u32 reg, u32 data) static struct vsp1_dl_list *vsp1_dl_list_alloc(struct vsp1_dl_manager *dlm) { struct vsp1_dl_list *dl; - size_t header_size; - int ret; dl = kzalloc(sizeof(*dl), GFP_KERNEL); if (!dl) return NULL; - INIT_LIST_HEAD(&dl->fragments); + INIT_LIST_HEAD(&dl->bodies); dl->dlm = dlm; - /* - * Initialize the display list body and allocate DMA memory for the body - * and the optional header. Both are allocated together to avoid memory - * fragmentation, with the header located right after the body in - * memory. - */ - header_size = dlm->mode == VSP1_DL_MODE_HEADER - ? ALIGN(sizeof(struct vsp1_dl_header), 8) - : 0; - - ret = vsp1_dl_body_init(dlm->vsp1, &dl->body0, VSP1_DL_NUM_ENTRIES, - header_size); - if (ret < 0) { - kfree(dl); + /* Get a default body for our list. */ + dl->body0 = vsp1_dl_body_get(dlm->pool); + if (!dl->body0) return NULL; - } - if (dlm->mode == VSP1_DL_MODE_HEADER) { - size_t header_offset = VSP1_DL_NUM_ENTRIES - * sizeof(*dl->body0.entries); + size_t header_offset = dl->body0->max_entries + * sizeof(*dl->body0->entries); - dl->header = ((void *)dl->body0.entries) + header_offset; - dl->dma = dl->body0.dma + header_offset; + dl->header = ((void *)dl->body0->entries) + header_offset; + dl->dma = dl->body0->dma + header_offset; memset(dl->header, 0, sizeof(*dl->header)); - dl->header->lists[0].addr = dl->body0.dma; + dl->header->lists[0].addr = dl->body0->dma; } return dl; } +static void vsp1_dl_list_bodies_put(struct vsp1_dl_list *dl) +{ + struct vsp1_dl_body *dlb, *tmp; + + list_for_each_entry_safe(dlb, tmp, &dl->bodies, list) { + list_del(&dlb->list); + vsp1_dl_body_put(dlb); + } +} + static void vsp1_dl_list_free(struct vsp1_dl_list *dl) { - vsp1_dl_body_cleanup(&dl->body0); - list_splice_init(&dl->fragments, &dl->dlm->gc_fragments); + vsp1_dl_body_put(dl->body0); + vsp1_dl_list_bodies_put(dl); + kfree(dl); } @@ -331,18 +414,13 @@ static void __vsp1_dl_list_put(struct vsp1_dl_list *dl) dl->has_chain = false; + vsp1_dl_list_bodies_put(dl); + /* - * We can't free fragments here as DMA memory can only be freed in - * interruptible context. Move all fragments to the display list - * manager's list of fragments to be freed, they will be - * garbage-collected by the work queue. + * body0 is reused as as an optimisation as presently every display list + * has at least one body, thus we reinitialise the entries list. */ - if (!list_empty(&dl->fragments)) { - list_splice_init(&dl->fragments, &dl->dlm->gc_fragments); - schedule_work(&dl->dlm->gc_work); - } - - dl->body0.num_entries = 0; + dl->body0->num_entries = 0; list_add_tail(&dl->list, &dl->dlm->free); } @@ -369,43 +447,46 @@ void vsp1_dl_list_put(struct vsp1_dl_list *dl) } /** - * vsp1_dl_list_write - Write a register to the display list + * vsp1_dl_list_get_body0 - Obtain the default body for the display list * @dl: The display list - * @reg: The register address - * @data: The register value * - * Write the given register and value to the display list. Up to 256 registers - * can be written per display list. + * Obtain a pointer to the internal display list body allowing this to be passed + * directly to configure operations. */ -void vsp1_dl_list_write(struct vsp1_dl_list *dl, u32 reg, u32 data) +struct vsp1_dl_body *vsp1_dl_list_get_body0(struct vsp1_dl_list *dl) { - vsp1_dl_fragment_write(&dl->body0, reg, data); + return dl->body0; } /** - * vsp1_dl_list_add_fragment - Add a fragment to the display list + * vsp1_dl_list_add_body - Add a body to the display list * @dl: The display list - * @dlb: The fragment + * @dlb: The body + * + * Add a display list body to a display list. Registers contained in bodies are + * processed after registers contained in the main display list, in the order in + * which bodies are added. * - * Add a display list body as a fragment to a display list. Registers contained - * in fragments are processed after registers contained in the main display - * list, in the order in which fragments are added. + * Adding a body to a display list passes ownership of the body to the list. The + * caller retains its reference to the fragment when adding it to the display + * list, but is not allowed to add new entries to the body. * - * Adding a fragment to a display list passes ownership of the fragment to the - * list. The caller must not touch the fragment after this call, and must not - * free it explicitly with vsp1_dl_fragment_free(). + * The reference must be explicitly released by a call to vsp1_dl_body_put() + * when the body isn't needed anymore. * - * Fragments are only usable for display lists in header mode. Attempt to - * add a fragment to a header-less display list will return an error. + * Additional bodies are only usable for display lists in header mode. + * Attempting to add a body to a header-less display list will return an error. */ -int vsp1_dl_list_add_fragment(struct vsp1_dl_list *dl, - struct vsp1_dl_body *dlb) +int vsp1_dl_list_add_body(struct vsp1_dl_list *dl, struct vsp1_dl_body *dlb) { /* Multi-body lists are only available in header mode. */ if (dl->dlm->mode != VSP1_DL_MODE_HEADER) return -EINVAL; - list_add_tail(&dlb->list, &dl->fragments); + refcount_inc(&dlb->refcnt); + + list_add_tail(&dlb->list, &dl->bodies); + return 0; } @@ -451,10 +532,10 @@ static void vsp1_dl_list_fill_header(struct vsp1_dl_list *dl, bool is_last) * list was allocated. */ - hdr->num_bytes = dl->body0.num_entries + hdr->num_bytes = dl->body0->num_entries * sizeof(*dl->header->lists); - list_for_each_entry(dlb, &dl->fragments, list) { + list_for_each_entry(dlb, &dl->bodies, list) { num_lists++; hdr++; @@ -525,9 +606,9 @@ static void vsp1_dl_list_hw_enqueue(struct vsp1_dl_list *dl) * bit will be cleared by the hardware when the display list * processing starts. */ - vsp1_write(vsp1, VI6_DL_HDR_ADDR(0), dl->body0.dma); + vsp1_write(vsp1, VI6_DL_HDR_ADDR(0), dl->body0->dma); vsp1_write(vsp1, VI6_DL_BODY_SIZE, VI6_DL_BODY_SIZE_UPD | - (dl->body0.num_entries * sizeof(*dl->header->lists))); + (dl->body0->num_entries * sizeof(*dl->header->lists))); } else { /* * In header mode, program the display list header address. If @@ -550,8 +631,16 @@ static void vsp1_dl_list_commit_continuous(struct vsp1_dl_list *dl) * case we can't replace the queued list by the new one, as we could * race with the hardware. We thus mark the update as pending, it will * be queued up to the hardware by the frame end interrupt handler. + * + * If a display list is already pending we simply drop it as the new + * display list is assumed to contain a more recent configuration. It is + * an error if the already pending list has the internal flag set, as + * there is then a process waiting for that list to complete. This + * shouldn't happen as the waiting process should perform proper + * locking, but warn just in case. */ if (vsp1_dl_list_hw_update_pending(dlm)) { + WARN_ON(dlm->pending && dlm->pending->internal); __vsp1_dl_list_put(dlm->pending); dlm->pending = dl; return; @@ -581,7 +670,7 @@ static void vsp1_dl_list_commit_singleshot(struct vsp1_dl_list *dl) dlm->active = dl; } -void vsp1_dl_list_commit(struct vsp1_dl_list *dl) +void vsp1_dl_list_commit(struct vsp1_dl_list *dl, bool internal) { struct vsp1_dl_manager *dlm = dl->dlm; struct vsp1_dl_list *dl_child; @@ -598,6 +687,8 @@ void vsp1_dl_list_commit(struct vsp1_dl_list *dl) } } + dl->internal = internal; + spin_lock_irqsave(&dlm->lock, flags); if (dlm->singleshot) @@ -616,14 +707,22 @@ void vsp1_dl_list_commit(struct vsp1_dl_list *dl) * vsp1_dlm_irq_frame_end - Display list handler for the frame end interrupt * @dlm: the display list manager * - * Return true if the previous display list has completed at frame end, or false - * if it has been delayed by one frame because the display list commit raced - * with the frame end interrupt. The function always returns true in header mode - * as display list processing is then not continuous and races never occur. + * Return a set of flags that indicates display list completion status. + * + * The VSP1_DL_FRAME_END_COMPLETED flag indicates that the previous display list + * has completed at frame end. If the flag is not returned display list + * completion has been delayed by one frame because the display list commit + * raced with the frame end interrupt. The function always returns with the flag + * set in header mode as display list processing is then not continuous and + * races never occur. + * + * The VSP1_DL_FRAME_END_INTERNAL flag indicates that the previous display list + * has completed and had been queued with the internal notification flag. + * Internal notification is only supported for continuous mode. */ -bool vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm) +unsigned int vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm) { - bool completed = false; + unsigned int flags = 0; spin_lock(&dlm->lock); @@ -634,7 +733,7 @@ bool vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm) if (dlm->singleshot) { __vsp1_dl_list_put(dlm->active); dlm->active = NULL; - completed = true; + flags |= VSP1_DL_FRAME_END_COMPLETED; goto done; } @@ -652,10 +751,14 @@ bool vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm) * frame end interrupt. The display list thus becomes active. */ if (dlm->queued) { + if (dlm->queued->internal) + flags |= VSP1_DL_FRAME_END_INTERNAL; + dlm->queued->internal = false; + __vsp1_dl_list_put(dlm->active); dlm->active = dlm->queued; dlm->queued = NULL; - completed = true; + flags |= VSP1_DL_FRAME_END_COMPLETED; } /* @@ -672,7 +775,7 @@ bool vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm) done: spin_unlock(&dlm->lock); - return completed; + return flags; } /* Hardware Setup */ @@ -710,38 +813,9 @@ void vsp1_dlm_reset(struct vsp1_dl_manager *dlm) dlm->pending = NULL; } -/* - * Free all fragments awaiting to be garbage-collected. - * - * This function must be called without the display list manager lock held. - */ -static void vsp1_dlm_fragments_free(struct vsp1_dl_manager *dlm) +struct vsp1_dl_body *vsp1_dlm_dl_body_get(struct vsp1_dl_manager *dlm) { - unsigned long flags; - - spin_lock_irqsave(&dlm->lock, flags); - - while (!list_empty(&dlm->gc_fragments)) { - struct vsp1_dl_body *dlb; - - dlb = list_first_entry(&dlm->gc_fragments, struct vsp1_dl_body, - list); - list_del(&dlb->list); - - spin_unlock_irqrestore(&dlm->lock, flags); - vsp1_dl_fragment_free(dlb); - spin_lock_irqsave(&dlm->lock, flags); - } - - spin_unlock_irqrestore(&dlm->lock, flags); -} - -static void vsp1_dlm_garbage_collect(struct work_struct *work) -{ - struct vsp1_dl_manager *dlm = - container_of(work, struct vsp1_dl_manager, gc_work); - - vsp1_dlm_fragments_free(dlm); + return vsp1_dl_body_get(dlm->pool); } struct vsp1_dl_manager *vsp1_dlm_create(struct vsp1_device *vsp1, @@ -749,6 +823,7 @@ struct vsp1_dl_manager *vsp1_dlm_create(struct vsp1_device *vsp1, unsigned int prealloc) { struct vsp1_dl_manager *dlm; + size_t header_size; unsigned int i; dlm = devm_kzalloc(vsp1->dev, sizeof(*dlm), GFP_KERNEL); @@ -763,8 +838,22 @@ struct vsp1_dl_manager *vsp1_dlm_create(struct vsp1_device *vsp1, spin_lock_init(&dlm->lock); INIT_LIST_HEAD(&dlm->free); - INIT_LIST_HEAD(&dlm->gc_fragments); - INIT_WORK(&dlm->gc_work, vsp1_dlm_garbage_collect); + + /* + * Initialize the display list body and allocate DMA memory for the body + * and the optional header. Both are allocated together to avoid memory + * fragmentation, with the header located right after the body in + * memory. An extra body is allocated on top of the prealloc to account + * for the cached body used by the vsp1_pipeline object. + */ + header_size = dlm->mode == VSP1_DL_MODE_HEADER + ? ALIGN(sizeof(struct vsp1_dl_header), 8) + : 0; + + dlm->pool = vsp1_dl_body_pool_create(vsp1, prealloc + 1, + VSP1_DL_NUM_ENTRIES, header_size); + if (!dlm->pool) + return NULL; for (i = 0; i < prealloc; ++i) { struct vsp1_dl_list *dl; @@ -786,12 +875,10 @@ void vsp1_dlm_destroy(struct vsp1_dl_manager *dlm) if (!dlm) return; - cancel_work_sync(&dlm->gc_work); - list_for_each_entry_safe(dl, next, &dlm->free, list) { list_del(&dl->list); vsp1_dl_list_free(dl); } - vsp1_dlm_fragments_free(dlm); + vsp1_dl_body_pool_destroy(dlm->pool); } diff --git a/drivers/media/platform/vsp1/vsp1_dl.h b/drivers/media/platform/vsp1/vsp1_dl.h index ee3508172f0a..7dba0469c92e 100644 --- a/drivers/media/platform/vsp1/vsp1_dl.h +++ b/drivers/media/platform/vsp1/vsp1_dl.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1_dl.h -- R-Car VSP1 Display List * * Copyright (C) 2015 Renesas Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_DL_H__ #define __VSP1_DL_H__ @@ -16,10 +12,14 @@ #include <linux/types.h> struct vsp1_device; -struct vsp1_dl_fragment; +struct vsp1_dl_body; +struct vsp1_dl_body_pool; struct vsp1_dl_list; struct vsp1_dl_manager; +#define VSP1_DL_FRAME_END_COMPLETED BIT(0) +#define VSP1_DL_FRAME_END_INTERNAL BIT(1) + void vsp1_dlm_setup(struct vsp1_device *vsp1); struct vsp1_dl_manager *vsp1_dlm_create(struct vsp1_device *vsp1, @@ -27,19 +27,23 @@ struct vsp1_dl_manager *vsp1_dlm_create(struct vsp1_device *vsp1, unsigned int prealloc); void vsp1_dlm_destroy(struct vsp1_dl_manager *dlm); void vsp1_dlm_reset(struct vsp1_dl_manager *dlm); -bool vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm); +unsigned int vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm); +struct vsp1_dl_body *vsp1_dlm_dl_body_get(struct vsp1_dl_manager *dlm); struct vsp1_dl_list *vsp1_dl_list_get(struct vsp1_dl_manager *dlm); void vsp1_dl_list_put(struct vsp1_dl_list *dl); -void vsp1_dl_list_write(struct vsp1_dl_list *dl, u32 reg, u32 data); -void vsp1_dl_list_commit(struct vsp1_dl_list *dl); - -struct vsp1_dl_body *vsp1_dl_fragment_alloc(struct vsp1_device *vsp1, - unsigned int num_entries); -void vsp1_dl_fragment_free(struct vsp1_dl_body *dlb); -void vsp1_dl_fragment_write(struct vsp1_dl_body *dlb, u32 reg, u32 data); -int vsp1_dl_list_add_fragment(struct vsp1_dl_list *dl, - struct vsp1_dl_body *dlb); +struct vsp1_dl_body *vsp1_dl_list_get_body0(struct vsp1_dl_list *dl); +void vsp1_dl_list_commit(struct vsp1_dl_list *dl, bool internal); + +struct vsp1_dl_body_pool * +vsp1_dl_body_pool_create(struct vsp1_device *vsp1, unsigned int num_bodies, + unsigned int num_entries, size_t extra_size); +void vsp1_dl_body_pool_destroy(struct vsp1_dl_body_pool *pool); +struct vsp1_dl_body *vsp1_dl_body_get(struct vsp1_dl_body_pool *pool); +void vsp1_dl_body_put(struct vsp1_dl_body *dlb); + +void vsp1_dl_body_write(struct vsp1_dl_body *dlb, u32 reg, u32 data); +int vsp1_dl_list_add_body(struct vsp1_dl_list *dl, struct vsp1_dl_body *dlb); int vsp1_dl_list_add_chain(struct vsp1_dl_list *head, struct vsp1_dl_list *dl); #endif /* __VSP1_DL_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_drm.c b/drivers/media/platform/vsp1/vsp1_drm.c index b8fee1834253..edb35a5c57ea 100644 --- a/drivers/media/platform/vsp1/vsp1_drm.c +++ b/drivers/media/platform/vsp1/vsp1_drm.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* - * vsp1_drm.c -- R-Car VSP1 DRM API + * vsp1_drm.c -- R-Car VSP1 DRM/KMS Interface * * Copyright (C) 2015 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/device.h> @@ -20,26 +16,550 @@ #include <media/vsp1.h> #include "vsp1.h" -#include "vsp1_bru.h" +#include "vsp1_brx.h" #include "vsp1_dl.h" #include "vsp1_drm.h" #include "vsp1_lif.h" #include "vsp1_pipe.h" #include "vsp1_rwpf.h" +#include "vsp1_uif.h" -#define BRU_NAME(e) (e)->type == VSP1_ENTITY_BRU ? "BRU" : "BRS" +#define BRX_NAME(e) (e)->type == VSP1_ENTITY_BRU ? "BRU" : "BRS" /* ----------------------------------------------------------------------------- * Interrupt Handling */ static void vsp1_du_pipeline_frame_end(struct vsp1_pipeline *pipe, - bool completed) + unsigned int completion) { struct vsp1_drm_pipeline *drm_pipe = to_vsp1_drm_pipeline(pipe); + bool complete = completion == VSP1_DL_FRAME_END_COMPLETED; + + if (drm_pipe->du_complete) { + struct vsp1_entity *uif = drm_pipe->uif; + u32 crc; - if (drm_pipe->du_complete) - drm_pipe->du_complete(drm_pipe->du_private, completed); + crc = uif ? vsp1_uif_get_crc(to_uif(&uif->subdev)) : 0; + drm_pipe->du_complete(drm_pipe->du_private, complete, crc); + } + + if (completion & VSP1_DL_FRAME_END_INTERNAL) { + drm_pipe->force_brx_release = false; + wake_up(&drm_pipe->wait_queue); + } +} + +/* ----------------------------------------------------------------------------- + * Pipeline Configuration + */ + +/* + * Insert the UIF in the pipeline between the prev and next entities. If no UIF + * is available connect the two entities directly. + */ +static int vsp1_du_insert_uif(struct vsp1_device *vsp1, + struct vsp1_pipeline *pipe, + struct vsp1_entity *uif, + struct vsp1_entity *prev, unsigned int prev_pad, + struct vsp1_entity *next, unsigned int next_pad) +{ + struct v4l2_subdev_format format; + int ret; + + if (!uif) { + /* + * If there's no UIF to be inserted, connect the previous and + * next entities directly. + */ + prev->sink = next; + prev->sink_pad = next_pad; + return 0; + } + + prev->sink = uif; + prev->sink_pad = UIF_PAD_SINK; + + memset(&format, 0, sizeof(format)); + format.which = V4L2_SUBDEV_FORMAT_ACTIVE; + format.pad = prev_pad; + + ret = v4l2_subdev_call(&prev->subdev, pad, get_fmt, NULL, &format); + if (ret < 0) + return ret; + + format.pad = UIF_PAD_SINK; + + ret = v4l2_subdev_call(&uif->subdev, pad, set_fmt, NULL, &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on UIF sink\n", + __func__, format.format.width, format.format.height, + format.format.code); + + /* + * The UIF doesn't mangle the format between its sink and source pads, + * so there is no need to retrieve the format on its source pad. + */ + + uif->sink = next; + uif->sink_pad = next_pad; + + return 0; +} + +/* Setup one RPF and the connected BRx sink pad. */ +static int vsp1_du_pipeline_setup_rpf(struct vsp1_device *vsp1, + struct vsp1_pipeline *pipe, + struct vsp1_rwpf *rpf, + struct vsp1_entity *uif, + unsigned int brx_input) +{ + struct v4l2_subdev_selection sel; + struct v4l2_subdev_format format; + const struct v4l2_rect *crop; + int ret; + + /* + * Configure the format on the RPF sink pad and propagate it up to the + * BRx sink pad. + */ + crop = &vsp1->drm->inputs[rpf->entity.index].crop; + + memset(&format, 0, sizeof(format)); + format.which = V4L2_SUBDEV_FORMAT_ACTIVE; + format.pad = RWPF_PAD_SINK; + format.format.width = crop->width + crop->left; + format.format.height = crop->height + crop->top; + format.format.code = rpf->fmtinfo->mbus; + format.format.field = V4L2_FIELD_NONE; + + ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_fmt, NULL, + &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, + "%s: set format %ux%u (%x) on RPF%u sink\n", + __func__, format.format.width, format.format.height, + format.format.code, rpf->entity.index); + + memset(&sel, 0, sizeof(sel)); + sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; + sel.pad = RWPF_PAD_SINK; + sel.target = V4L2_SEL_TGT_CROP; + sel.r = *crop; + + ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_selection, NULL, + &sel); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, + "%s: set selection (%u,%u)/%ux%u on RPF%u sink\n", + __func__, sel.r.left, sel.r.top, sel.r.width, sel.r.height, + rpf->entity.index); + + /* + * RPF source, hardcode the format to ARGB8888 to turn on format + * conversion if needed. + */ + format.pad = RWPF_PAD_SOURCE; + + ret = v4l2_subdev_call(&rpf->entity.subdev, pad, get_fmt, NULL, + &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, + "%s: got format %ux%u (%x) on RPF%u source\n", + __func__, format.format.width, format.format.height, + format.format.code, rpf->entity.index); + + format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32; + + ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_fmt, NULL, + &format); + if (ret < 0) + return ret; + + /* Insert and configure the UIF if available. */ + ret = vsp1_du_insert_uif(vsp1, pipe, uif, &rpf->entity, RWPF_PAD_SOURCE, + pipe->brx, brx_input); + if (ret < 0) + return ret; + + /* BRx sink, propagate the format from the RPF source. */ + format.pad = brx_input; + + ret = v4l2_subdev_call(&pipe->brx->subdev, pad, set_fmt, NULL, + &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on %s pad %u\n", + __func__, format.format.width, format.format.height, + format.format.code, BRX_NAME(pipe->brx), format.pad); + + sel.pad = brx_input; + sel.target = V4L2_SEL_TGT_COMPOSE; + sel.r = vsp1->drm->inputs[rpf->entity.index].compose; + + ret = v4l2_subdev_call(&pipe->brx->subdev, pad, set_selection, NULL, + &sel); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, "%s: set selection (%u,%u)/%ux%u on %s pad %u\n", + __func__, sel.r.left, sel.r.top, sel.r.width, sel.r.height, + BRX_NAME(pipe->brx), sel.pad); + + return 0; +} + +/* Setup the BRx source pad. */ +static int vsp1_du_pipeline_setup_inputs(struct vsp1_device *vsp1, + struct vsp1_pipeline *pipe); +static void vsp1_du_pipeline_configure(struct vsp1_pipeline *pipe); + +static int vsp1_du_pipeline_setup_brx(struct vsp1_device *vsp1, + struct vsp1_pipeline *pipe) +{ + struct vsp1_drm_pipeline *drm_pipe = to_vsp1_drm_pipeline(pipe); + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct vsp1_entity *brx; + int ret; + + /* + * Pick a BRx: + * - If we need more than two inputs, use the BRU. + * - Otherwise, if we are not forced to release our BRx, keep it. + * - Else, use any free BRx (randomly starting with the BRU). + */ + if (pipe->num_inputs > 2) + brx = &vsp1->bru->entity; + else if (pipe->brx && !drm_pipe->force_brx_release) + brx = pipe->brx; + else if (!vsp1->bru->entity.pipe) + brx = &vsp1->bru->entity; + else + brx = &vsp1->brs->entity; + + /* Switch BRx if needed. */ + if (brx != pipe->brx) { + struct vsp1_entity *released_brx = NULL; + + /* Release our BRx if we have one. */ + if (pipe->brx) { + dev_dbg(vsp1->dev, "%s: pipe %u: releasing %s\n", + __func__, pipe->lif->index, + BRX_NAME(pipe->brx)); + + /* + * The BRx might be acquired by the other pipeline in + * the next step. We must thus remove it from the list + * of entities for this pipeline. The other pipeline's + * hardware configuration will reconfigure the BRx + * routing. + * + * However, if the other pipeline doesn't acquire our + * BRx, we need to keep it in the list, otherwise the + * hardware configuration step won't disconnect it from + * the pipeline. To solve this, store the released BRx + * pointer to add it back to the list of entities later + * if it isn't acquired by the other pipeline. + */ + released_brx = pipe->brx; + + list_del(&pipe->brx->list_pipe); + pipe->brx->sink = NULL; + pipe->brx->pipe = NULL; + pipe->brx = NULL; + } + + /* + * If the BRx we need is in use, force the owner pipeline to + * switch to the other BRx and wait until the switch completes. + */ + if (brx->pipe) { + struct vsp1_drm_pipeline *owner_pipe; + + dev_dbg(vsp1->dev, "%s: pipe %u: waiting for %s\n", + __func__, pipe->lif->index, BRX_NAME(brx)); + + owner_pipe = to_vsp1_drm_pipeline(brx->pipe); + owner_pipe->force_brx_release = true; + + vsp1_du_pipeline_setup_inputs(vsp1, &owner_pipe->pipe); + vsp1_du_pipeline_configure(&owner_pipe->pipe); + + ret = wait_event_timeout(owner_pipe->wait_queue, + !owner_pipe->force_brx_release, + msecs_to_jiffies(500)); + if (ret == 0) + dev_warn(vsp1->dev, + "DRM pipeline %u reconfiguration timeout\n", + owner_pipe->pipe.lif->index); + } + + /* + * If the BRx we have released previously hasn't been acquired + * by the other pipeline, add it back to the entities list (with + * the pipe pointer NULL) to let vsp1_du_pipeline_configure() + * disconnect it from the hardware pipeline. + */ + if (released_brx && !released_brx->pipe) + list_add_tail(&released_brx->list_pipe, + &pipe->entities); + + /* Add the BRx to the pipeline. */ + dev_dbg(vsp1->dev, "%s: pipe %u: acquired %s\n", + __func__, pipe->lif->index, BRX_NAME(brx)); + + pipe->brx = brx; + pipe->brx->pipe = pipe; + pipe->brx->sink = &pipe->output->entity; + pipe->brx->sink_pad = 0; + + list_add_tail(&pipe->brx->list_pipe, &pipe->entities); + } + + /* + * Configure the format on the BRx source and verify that it matches the + * requested format. We don't set the media bus code as it is configured + * on the BRx sink pad 0 and propagated inside the entity, not on the + * source pad. + */ + format.pad = pipe->brx->source_pad; + format.format.width = drm_pipe->width; + format.format.height = drm_pipe->height; + format.format.field = V4L2_FIELD_NONE; + + ret = v4l2_subdev_call(&pipe->brx->subdev, pad, set_fmt, NULL, + &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on %s pad %u\n", + __func__, format.format.width, format.format.height, + format.format.code, BRX_NAME(pipe->brx), pipe->brx->source_pad); + + if (format.format.width != drm_pipe->width || + format.format.height != drm_pipe->height) { + dev_dbg(vsp1->dev, "%s: format mismatch\n", __func__); + return -EPIPE; + } + + return 0; +} + +static unsigned int rpf_zpos(struct vsp1_device *vsp1, struct vsp1_rwpf *rpf) +{ + return vsp1->drm->inputs[rpf->entity.index].zpos; +} + +/* Setup the input side of the pipeline (RPFs and BRx). */ +static int vsp1_du_pipeline_setup_inputs(struct vsp1_device *vsp1, + struct vsp1_pipeline *pipe) +{ + struct vsp1_drm_pipeline *drm_pipe = to_vsp1_drm_pipeline(pipe); + struct vsp1_rwpf *inputs[VSP1_MAX_RPF] = { NULL, }; + struct vsp1_entity *uif; + bool use_uif = false; + struct vsp1_brx *brx; + unsigned int i; + int ret; + + /* Count the number of enabled inputs and sort them by Z-order. */ + pipe->num_inputs = 0; + + for (i = 0; i < vsp1->info->rpf_count; ++i) { + struct vsp1_rwpf *rpf = vsp1->rpf[i]; + unsigned int j; + + if (!pipe->inputs[i]) + continue; + + /* Insert the RPF in the sorted RPFs array. */ + for (j = pipe->num_inputs++; j > 0; --j) { + if (rpf_zpos(vsp1, inputs[j-1]) <= rpf_zpos(vsp1, rpf)) + break; + inputs[j] = inputs[j-1]; + } + + inputs[j] = rpf; + } + + /* + * Setup the BRx. This must be done before setting up the RPF input + * pipelines as the BRx sink compose rectangles depend on the BRx source + * format. + */ + ret = vsp1_du_pipeline_setup_brx(vsp1, pipe); + if (ret < 0) { + dev_err(vsp1->dev, "%s: failed to setup %s source\n", __func__, + BRX_NAME(pipe->brx)); + return ret; + } + + brx = to_brx(&pipe->brx->subdev); + + /* Setup the RPF input pipeline for every enabled input. */ + for (i = 0; i < pipe->brx->source_pad; ++i) { + struct vsp1_rwpf *rpf = inputs[i]; + + if (!rpf) { + brx->inputs[i].rpf = NULL; + continue; + } + + if (!rpf->entity.pipe) { + rpf->entity.pipe = pipe; + list_add_tail(&rpf->entity.list_pipe, &pipe->entities); + } + + brx->inputs[i].rpf = rpf; + rpf->brx_input = i; + rpf->entity.sink = pipe->brx; + rpf->entity.sink_pad = i; + + dev_dbg(vsp1->dev, "%s: connecting RPF.%u to %s:%u\n", + __func__, rpf->entity.index, BRX_NAME(pipe->brx), i); + + uif = drm_pipe->crc.source == VSP1_DU_CRC_PLANE && + drm_pipe->crc.index == i ? drm_pipe->uif : NULL; + if (uif) + use_uif = true; + ret = vsp1_du_pipeline_setup_rpf(vsp1, pipe, rpf, uif, i); + if (ret < 0) { + dev_err(vsp1->dev, + "%s: failed to setup RPF.%u\n", + __func__, rpf->entity.index); + return ret; + } + } + + /* Insert and configure the UIF at the BRx output if available. */ + uif = drm_pipe->crc.source == VSP1_DU_CRC_OUTPUT ? drm_pipe->uif : NULL; + if (uif) + use_uif = true; + ret = vsp1_du_insert_uif(vsp1, pipe, uif, + pipe->brx, pipe->brx->source_pad, + &pipe->output->entity, 0); + if (ret < 0) + dev_err(vsp1->dev, "%s: failed to setup UIF after %s\n", + __func__, BRX_NAME(pipe->brx)); + + /* + * If the UIF is not in use schedule it for removal by setting its pipe + * pointer to NULL, vsp1_du_pipeline_configure() will remove it from the + * hardware pipeline and from the pipeline's list of entities. Otherwise + * make sure it is present in the pipeline's list of entities if it + * wasn't already. + */ + if (!use_uif) { + drm_pipe->uif->pipe = NULL; + } else if (!drm_pipe->uif->pipe) { + drm_pipe->uif->pipe = pipe; + list_add_tail(&drm_pipe->uif->list_pipe, &pipe->entities); + } + + return 0; +} + +/* Setup the output side of the pipeline (WPF and LIF). */ +static int vsp1_du_pipeline_setup_output(struct vsp1_device *vsp1, + struct vsp1_pipeline *pipe) +{ + struct vsp1_drm_pipeline *drm_pipe = to_vsp1_drm_pipeline(pipe); + struct v4l2_subdev_format format = { 0, }; + int ret; + + format.which = V4L2_SUBDEV_FORMAT_ACTIVE; + format.pad = RWPF_PAD_SINK; + format.format.width = drm_pipe->width; + format.format.height = drm_pipe->height; + format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32; + format.format.field = V4L2_FIELD_NONE; + + ret = v4l2_subdev_call(&pipe->output->entity.subdev, pad, set_fmt, NULL, + &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on WPF%u sink\n", + __func__, format.format.width, format.format.height, + format.format.code, pipe->output->entity.index); + + format.pad = RWPF_PAD_SOURCE; + ret = v4l2_subdev_call(&pipe->output->entity.subdev, pad, get_fmt, NULL, + &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, "%s: got format %ux%u (%x) on WPF%u source\n", + __func__, format.format.width, format.format.height, + format.format.code, pipe->output->entity.index); + + format.pad = LIF_PAD_SINK; + ret = v4l2_subdev_call(&pipe->lif->subdev, pad, set_fmt, NULL, + &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on LIF%u sink\n", + __func__, format.format.width, format.format.height, + format.format.code, pipe->lif->index); + + /* + * Verify that the format at the output of the pipeline matches the + * requested frame size and media bus code. + */ + if (format.format.width != drm_pipe->width || + format.format.height != drm_pipe->height || + format.format.code != MEDIA_BUS_FMT_ARGB8888_1X32) { + dev_dbg(vsp1->dev, "%s: format mismatch on LIF%u\n", __func__, + pipe->lif->index); + return -EPIPE; + } + + return 0; +} + +/* Configure all entities in the pipeline. */ +static void vsp1_du_pipeline_configure(struct vsp1_pipeline *pipe) +{ + struct vsp1_drm_pipeline *drm_pipe = to_vsp1_drm_pipeline(pipe); + struct vsp1_entity *entity; + struct vsp1_entity *next; + struct vsp1_dl_list *dl; + struct vsp1_dl_body *dlb; + + dl = vsp1_dl_list_get(pipe->output->dlm); + dlb = vsp1_dl_list_get_body0(dl); + + list_for_each_entry_safe(entity, next, &pipe->entities, list_pipe) { + /* Disconnect unused entities from the pipeline. */ + if (!entity->pipe) { + vsp1_dl_body_write(dlb, entity->route->reg, + VI6_DPR_NODE_UNUSED); + + entity->sink = NULL; + list_del(&entity->list_pipe); + + continue; + } + + vsp1_entity_route_setup(entity, pipe, dlb); + vsp1_entity_configure_stream(entity, pipe, dlb); + vsp1_entity_configure_frame(entity, pipe, dl, dlb); + vsp1_entity_configure_partition(entity, pipe, dl, dlb); + } + + vsp1_dl_list_commit(dl, drm_pipe->force_brx_release); } /* ----------------------------------------------------------------------------- @@ -64,8 +584,8 @@ EXPORT_SYMBOL_GPL(vsp1_du_init); * @cfg: the LIF configuration * * Configure the output part of VSP DRM pipeline for the given frame @cfg.width - * and @cfg.height. This sets up formats on the blend unit (BRU or BRS) source - * pad, the WPF sink and source pads, and the LIF sink pad. + * and @cfg.height. This sets up formats on the BRx source pad, the WPF sink and + * source pads, and the LIF sink pad. * * The @pipe_index argument selects which DRM pipeline to setup. The number of * available pipelines depend on the VSP instance. @@ -84,11 +604,6 @@ int vsp1_du_setup_lif(struct device *dev, unsigned int pipe_index, struct vsp1_device *vsp1 = dev_get_drvdata(dev); struct vsp1_drm_pipeline *drm_pipe; struct vsp1_pipeline *pipe; - struct vsp1_bru *bru; - struct vsp1_entity *entity; - struct vsp1_entity *next; - struct vsp1_dl_list *dl; - struct v4l2_subdev_format format; unsigned long flags; unsigned int i; int ret; @@ -98,9 +613,14 @@ int vsp1_du_setup_lif(struct device *dev, unsigned int pipe_index, drm_pipe = &vsp1->drm->pipe[pipe_index]; pipe = &drm_pipe->pipe; - bru = to_bru(&pipe->bru->subdev); if (!cfg) { + struct vsp1_brx *brx; + + mutex_lock(&vsp1->drm->lock); + + brx = to_brx(&pipe->brx->subdev); + /* * NULL configuration means the CRTC is being disabled, stop * the pipeline and turn the light off. @@ -109,8 +629,6 @@ int vsp1_du_setup_lif(struct device *dev, unsigned int pipe_index, if (ret == -ETIMEDOUT) dev_err(vsp1->dev, "DRM pipeline stop timeout\n"); - media_pipeline_stop(&pipe->output->entity.subdev.entity); - for (i = 0; i < ARRAY_SIZE(pipe->inputs); ++i) { struct vsp1_rwpf *rpf = pipe->inputs[i]; @@ -118,19 +636,30 @@ int vsp1_du_setup_lif(struct device *dev, unsigned int pipe_index, continue; /* - * Remove the RPF from the pipe and the list of BRU + * Remove the RPF from the pipe and the list of BRx * inputs. */ - WARN_ON(list_empty(&rpf->entity.list_pipe)); - list_del_init(&rpf->entity.list_pipe); + WARN_ON(!rpf->entity.pipe); + rpf->entity.pipe = NULL; + list_del(&rpf->entity.list_pipe); pipe->inputs[i] = NULL; - bru->inputs[rpf->bru_input].rpf = NULL; + brx->inputs[rpf->brx_input].rpf = NULL; } drm_pipe->du_complete = NULL; pipe->num_inputs = 0; + dev_dbg(vsp1->dev, "%s: pipe %u: releasing %s\n", + __func__, pipe->lif->index, + BRX_NAME(pipe->brx)); + + list_del(&pipe->brx->list_pipe); + pipe->brx->pipe = NULL; + pipe->brx = NULL; + + mutex_unlock(&vsp1->drm->lock); + vsp1_dlm_reset(pipe->output->dlm); vsp1_device_put(vsp1); @@ -139,100 +668,27 @@ int vsp1_du_setup_lif(struct device *dev, unsigned int pipe_index, return 0; } + drm_pipe->width = cfg->width; + drm_pipe->height = cfg->height; + dev_dbg(vsp1->dev, "%s: configuring LIF%u with format %ux%u\n", __func__, pipe_index, cfg->width, cfg->height); - /* - * Configure the format at the BRU sinks and propagate it through the - * pipeline. - */ - memset(&format, 0, sizeof(format)); - format.which = V4L2_SUBDEV_FORMAT_ACTIVE; - - for (i = 0; i < pipe->bru->source_pad; ++i) { - format.pad = i; - - format.format.width = cfg->width; - format.format.height = cfg->height; - format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32; - format.format.field = V4L2_FIELD_NONE; - - ret = v4l2_subdev_call(&pipe->bru->subdev, pad, - set_fmt, NULL, &format); - if (ret < 0) - return ret; - - dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on %s pad %u\n", - __func__, format.format.width, format.format.height, - format.format.code, BRU_NAME(pipe->bru), i); - } - - format.pad = pipe->bru->source_pad; - format.format.width = cfg->width; - format.format.height = cfg->height; - format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32; - format.format.field = V4L2_FIELD_NONE; - - ret = v4l2_subdev_call(&pipe->bru->subdev, pad, set_fmt, NULL, - &format); - if (ret < 0) - return ret; - - dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on %s pad %u\n", - __func__, format.format.width, format.format.height, - format.format.code, BRU_NAME(pipe->bru), i); - - format.pad = RWPF_PAD_SINK; - ret = v4l2_subdev_call(&pipe->output->entity.subdev, pad, set_fmt, NULL, - &format); - if (ret < 0) - return ret; - - dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on WPF%u sink\n", - __func__, format.format.width, format.format.height, - format.format.code, pipe->output->entity.index); + mutex_lock(&vsp1->drm->lock); - format.pad = RWPF_PAD_SOURCE; - ret = v4l2_subdev_call(&pipe->output->entity.subdev, pad, get_fmt, NULL, - &format); + /* Setup formats through the pipeline. */ + ret = vsp1_du_pipeline_setup_inputs(vsp1, pipe); if (ret < 0) - return ret; - - dev_dbg(vsp1->dev, "%s: got format %ux%u (%x) on WPF%u source\n", - __func__, format.format.width, format.format.height, - format.format.code, pipe->output->entity.index); + goto unlock; - format.pad = LIF_PAD_SINK; - ret = v4l2_subdev_call(&pipe->lif->subdev, pad, set_fmt, NULL, - &format); + ret = vsp1_du_pipeline_setup_output(vsp1, pipe); if (ret < 0) - return ret; - - dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on LIF%u sink\n", - __func__, format.format.width, format.format.height, - format.format.code, pipe_index); + goto unlock; - /* - * Verify that the format at the output of the pipeline matches the - * requested frame size and media bus code. - */ - if (format.format.width != cfg->width || - format.format.height != cfg->height || - format.format.code != MEDIA_BUS_FMT_ARGB8888_1X32) { - dev_dbg(vsp1->dev, "%s: format mismatch\n", __func__); - return -EPIPE; - } - - /* - * Mark the pipeline as streaming and enable the VSP1. This will store - * the pipeline pointer in all entities, which the s_stream handlers - * will need. We don't start the entities themselves right at this point - * as there's no plane configured yet, so we can't start processing - * buffers. - */ + /* Enable the VSP1. */ ret = vsp1_device_get(vsp1); if (ret < 0) - return ret; + goto unlock; /* * Register a callback to allow us to notify the DRM driver of frame @@ -241,35 +697,18 @@ int vsp1_du_setup_lif(struct device *dev, unsigned int pipe_index, drm_pipe->du_complete = cfg->callback; drm_pipe->du_private = cfg->callback_data; - ret = media_pipeline_start(&pipe->output->entity.subdev.entity, - &pipe->pipe); - if (ret < 0) { - dev_dbg(vsp1->dev, "%s: pipeline start failed\n", __func__); - vsp1_device_put(vsp1); - return ret; - } - /* Disable the display interrupts. */ vsp1_write(vsp1, VI6_DISP_IRQ_STA, 0); vsp1_write(vsp1, VI6_DISP_IRQ_ENB, 0); /* Configure all entities in the pipeline. */ - dl = vsp1_dl_list_get(pipe->output->dlm); + vsp1_du_pipeline_configure(pipe); - list_for_each_entry_safe(entity, next, &pipe->entities, list_pipe) { - vsp1_entity_route_setup(entity, pipe, dl); - - if (entity->ops->configure) { - entity->ops->configure(entity, pipe, dl, - VSP1_ENTITY_PARAMS_INIT); - entity->ops->configure(entity, pipe, dl, - VSP1_ENTITY_PARAMS_RUNTIME); - entity->ops->configure(entity, pipe, dl, - VSP1_ENTITY_PARAMS_PARTITION); - } - } +unlock: + mutex_unlock(&vsp1->drm->lock); - vsp1_dl_list_commit(dl); + if (ret < 0) + return ret; /* Start the pipeline. */ spin_lock_irqsave(&pipe->irqlock, flags); @@ -290,9 +729,8 @@ EXPORT_SYMBOL_GPL(vsp1_du_setup_lif); void vsp1_du_atomic_begin(struct device *dev, unsigned int pipe_index) { struct vsp1_device *vsp1 = dev_get_drvdata(dev); - struct vsp1_drm_pipeline *drm_pipe = &vsp1->drm->pipe[pipe_index]; - drm_pipe->enabled = drm_pipe->pipe.num_inputs != 0; + mutex_lock(&vsp1->drm->lock); } EXPORT_SYMBOL_GPL(vsp1_du_atomic_begin); @@ -345,10 +783,11 @@ int vsp1_du_atomic_update(struct device *dev, unsigned int pipe_index, rpf_index); /* - * Remove the RPF from the pipe's inputs. The atomic flush - * handler will disable the input and remove the entity from the - * pipe's entities list. + * Remove the RPF from the pipeline's inputs. Keep it in the + * pipeline's entity list to let vsp1_du_pipeline_configure() + * remove it from the hardware pipeline. */ + rpf->entity.pipe = NULL; drm_pipe->pipe.inputs[rpf_index] = NULL; return 0; } @@ -392,214 +831,24 @@ int vsp1_du_atomic_update(struct device *dev, unsigned int pipe_index, } EXPORT_SYMBOL_GPL(vsp1_du_atomic_update); -static int vsp1_du_setup_rpf_pipe(struct vsp1_device *vsp1, - struct vsp1_pipeline *pipe, - struct vsp1_rwpf *rpf, unsigned int bru_input) -{ - struct v4l2_subdev_selection sel; - struct v4l2_subdev_format format; - const struct v4l2_rect *crop; - int ret; - - /* - * Configure the format on the RPF sink pad and propagate it up to the - * BRU sink pad. - */ - crop = &vsp1->drm->inputs[rpf->entity.index].crop; - - memset(&format, 0, sizeof(format)); - format.which = V4L2_SUBDEV_FORMAT_ACTIVE; - format.pad = RWPF_PAD_SINK; - format.format.width = crop->width + crop->left; - format.format.height = crop->height + crop->top; - format.format.code = rpf->fmtinfo->mbus; - format.format.field = V4L2_FIELD_NONE; - - ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_fmt, NULL, - &format); - if (ret < 0) - return ret; - - dev_dbg(vsp1->dev, - "%s: set format %ux%u (%x) on RPF%u sink\n", - __func__, format.format.width, format.format.height, - format.format.code, rpf->entity.index); - - memset(&sel, 0, sizeof(sel)); - sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; - sel.pad = RWPF_PAD_SINK; - sel.target = V4L2_SEL_TGT_CROP; - sel.r = *crop; - - ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_selection, NULL, - &sel); - if (ret < 0) - return ret; - - dev_dbg(vsp1->dev, - "%s: set selection (%u,%u)/%ux%u on RPF%u sink\n", - __func__, sel.r.left, sel.r.top, sel.r.width, sel.r.height, - rpf->entity.index); - - /* - * RPF source, hardcode the format to ARGB8888 to turn on format - * conversion if needed. - */ - format.pad = RWPF_PAD_SOURCE; - - ret = v4l2_subdev_call(&rpf->entity.subdev, pad, get_fmt, NULL, - &format); - if (ret < 0) - return ret; - - dev_dbg(vsp1->dev, - "%s: got format %ux%u (%x) on RPF%u source\n", - __func__, format.format.width, format.format.height, - format.format.code, rpf->entity.index); - - format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32; - - ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_fmt, NULL, - &format); - if (ret < 0) - return ret; - - /* BRU sink, propagate the format from the RPF source. */ - format.pad = bru_input; - - ret = v4l2_subdev_call(&pipe->bru->subdev, pad, set_fmt, NULL, - &format); - if (ret < 0) - return ret; - - dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on %s pad %u\n", - __func__, format.format.width, format.format.height, - format.format.code, BRU_NAME(pipe->bru), format.pad); - - sel.pad = bru_input; - sel.target = V4L2_SEL_TGT_COMPOSE; - sel.r = vsp1->drm->inputs[rpf->entity.index].compose; - - ret = v4l2_subdev_call(&pipe->bru->subdev, pad, set_selection, NULL, - &sel); - if (ret < 0) - return ret; - - dev_dbg(vsp1->dev, "%s: set selection (%u,%u)/%ux%u on %s pad %u\n", - __func__, sel.r.left, sel.r.top, sel.r.width, sel.r.height, - BRU_NAME(pipe->bru), sel.pad); - - return 0; -} - -static unsigned int rpf_zpos(struct vsp1_device *vsp1, struct vsp1_rwpf *rpf) -{ - return vsp1->drm->inputs[rpf->entity.index].zpos; -} - /** * vsp1_du_atomic_flush - Commit an atomic update * @dev: the VSP device * @pipe_index: the DRM pipeline index + * @cfg: atomic pipe configuration */ -void vsp1_du_atomic_flush(struct device *dev, unsigned int pipe_index) +void vsp1_du_atomic_flush(struct device *dev, unsigned int pipe_index, + const struct vsp1_du_atomic_pipe_config *cfg) { struct vsp1_device *vsp1 = dev_get_drvdata(dev); struct vsp1_drm_pipeline *drm_pipe = &vsp1->drm->pipe[pipe_index]; struct vsp1_pipeline *pipe = &drm_pipe->pipe; - struct vsp1_rwpf *inputs[VSP1_MAX_RPF] = { NULL, }; - struct vsp1_bru *bru = to_bru(&pipe->bru->subdev); - struct vsp1_entity *entity; - struct vsp1_entity *next; - struct vsp1_dl_list *dl; - unsigned int i; - int ret; - - /* Prepare the display list. */ - dl = vsp1_dl_list_get(pipe->output->dlm); - - /* Count the number of enabled inputs and sort them by Z-order. */ - pipe->num_inputs = 0; - - for (i = 0; i < vsp1->info->rpf_count; ++i) { - struct vsp1_rwpf *rpf = vsp1->rpf[i]; - unsigned int j; - /* - * Make sure we don't accept more inputs than the hardware can - * handle. This is a temporary fix to avoid display stall, we - * need to instead allocate the BRU or BRS to display pipelines - * dynamically based on the number of planes they each use. - */ - if (pipe->num_inputs >= pipe->bru->source_pad) - pipe->inputs[i] = NULL; + drm_pipe->crc = cfg->crc; - if (!pipe->inputs[i]) - continue; - - /* Insert the RPF in the sorted RPFs array. */ - for (j = pipe->num_inputs++; j > 0; --j) { - if (rpf_zpos(vsp1, inputs[j-1]) <= rpf_zpos(vsp1, rpf)) - break; - inputs[j] = inputs[j-1]; - } - - inputs[j] = rpf; - } - - /* Setup the RPF input pipeline for every enabled input. */ - for (i = 0; i < pipe->bru->source_pad; ++i) { - struct vsp1_rwpf *rpf = inputs[i]; - - if (!rpf) { - bru->inputs[i].rpf = NULL; - continue; - } - - if (list_empty(&rpf->entity.list_pipe)) - list_add_tail(&rpf->entity.list_pipe, &pipe->entities); - - bru->inputs[i].rpf = rpf; - rpf->bru_input = i; - rpf->entity.sink = pipe->bru; - rpf->entity.sink_pad = i; - - dev_dbg(vsp1->dev, "%s: connecting RPF.%u to %s:%u\n", - __func__, rpf->entity.index, BRU_NAME(pipe->bru), i); - - ret = vsp1_du_setup_rpf_pipe(vsp1, pipe, rpf, i); - if (ret < 0) - dev_err(vsp1->dev, - "%s: failed to setup RPF.%u\n", - __func__, rpf->entity.index); - } - - /* Configure all entities in the pipeline. */ - list_for_each_entry_safe(entity, next, &pipe->entities, list_pipe) { - /* Disconnect unused RPFs from the pipeline. */ - if (entity->type == VSP1_ENTITY_RPF && - !pipe->inputs[entity->index]) { - vsp1_dl_list_write(dl, entity->route->reg, - VI6_DPR_NODE_UNUSED); - - list_del_init(&entity->list_pipe); - - continue; - } - - vsp1_entity_route_setup(entity, pipe, dl); - - if (entity->ops->configure) { - entity->ops->configure(entity, pipe, dl, - VSP1_ENTITY_PARAMS_INIT); - entity->ops->configure(entity, pipe, dl, - VSP1_ENTITY_PARAMS_RUNTIME); - entity->ops->configure(entity, pipe, dl, - VSP1_ENTITY_PARAMS_PARTITION); - } - } - - vsp1_dl_list_commit(dl); + vsp1_du_pipeline_setup_inputs(vsp1, pipe); + vsp1_du_pipeline_configure(pipe); + mutex_unlock(&vsp1->drm->lock); } EXPORT_SYMBOL_GPL(vsp1_du_atomic_flush); @@ -638,31 +887,40 @@ int vsp1_drm_init(struct vsp1_device *vsp1) if (!vsp1->drm) return -ENOMEM; + mutex_init(&vsp1->drm->lock); + /* Create one DRM pipeline per LIF. */ for (i = 0; i < vsp1->info->lif_count; ++i) { struct vsp1_drm_pipeline *drm_pipe = &vsp1->drm->pipe[i]; struct vsp1_pipeline *pipe = &drm_pipe->pipe; + init_waitqueue_head(&drm_pipe->wait_queue); + vsp1_pipeline_init(pipe); + pipe->frame_end = vsp1_du_pipeline_frame_end; + /* - * The DRM pipeline is static, add entities manually. The first - * pipeline uses the BRU and the second pipeline the BRS. + * The output side of the DRM pipeline is static, add the + * corresponding entities manually. */ - pipe->bru = i == 0 ? &vsp1->bru->entity : &vsp1->brs->entity; - pipe->lif = &vsp1->lif[i]->entity; pipe->output = vsp1->wpf[i]; - pipe->output->pipe = pipe; - pipe->frame_end = vsp1_du_pipeline_frame_end; + pipe->lif = &vsp1->lif[i]->entity; - pipe->bru->sink = &pipe->output->entity; - pipe->bru->sink_pad = 0; + pipe->output->entity.pipe = pipe; pipe->output->entity.sink = pipe->lif; pipe->output->entity.sink_pad = 0; + list_add_tail(&pipe->output->entity.list_pipe, &pipe->entities); - list_add_tail(&pipe->bru->list_pipe, &pipe->entities); + pipe->lif->pipe = pipe; list_add_tail(&pipe->lif->list_pipe, &pipe->entities); - list_add_tail(&pipe->output->entity.list_pipe, &pipe->entities); + + /* + * CRC computation is initially disabled, don't add the UIF to + * the pipeline. + */ + if (i < vsp1->info->uif_count) + drm_pipe->uif = &vsp1->uif[i]->entity; } /* Disable all RPFs initially. */ @@ -677,4 +935,5 @@ int vsp1_drm_init(struct vsp1_device *vsp1) void vsp1_drm_cleanup(struct vsp1_device *vsp1) { + mutex_destroy(&vsp1->drm->lock); } diff --git a/drivers/media/platform/vsp1/vsp1_drm.h b/drivers/media/platform/vsp1/vsp1_drm.h index 1cd9db785bf7..8dfd274a59e2 100644 --- a/drivers/media/platform/vsp1/vsp1_drm.h +++ b/drivers/media/platform/vsp1/vsp1_drm.h @@ -1,46 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1_drm.h -- R-Car VSP1 DRM/KMS Interface * * Copyright (C) 2015 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_DRM_H__ #define __VSP1_DRM_H__ +#include <linux/mutex.h> #include <linux/videodev2.h> +#include <linux/wait.h> + +#include <media/vsp1.h> #include "vsp1_pipe.h" /** * vsp1_drm_pipeline - State for the API exposed to the DRM driver * @pipe: the VSP1 pipeline used for display - * @enabled: pipeline state at the beginning of an update + * @width: output display width + * @height: output display height + * @force_brx_release: when set, release the BRx during the next reconfiguration + * @wait_queue: wait queue to wait for BRx release completion + * @uif: UIF entity if available for the pipeline + * @crc: CRC computation configuration * @du_complete: frame completion callback for the DU driver (optional) * @du_private: data to be passed to the du_complete callback */ struct vsp1_drm_pipeline { struct vsp1_pipeline pipe; - bool enabled; + + unsigned int width; + unsigned int height; + + bool force_brx_release; + wait_queue_head_t wait_queue; + + struct vsp1_entity *uif; + struct vsp1_du_crc_config crc; /* Frame synchronisation */ - void (*du_complete)(void *, bool); + void (*du_complete)(void *data, bool completed, u32 crc); void *du_private; }; /** * vsp1_drm - State for the API exposed to the DRM driver * @pipe: the VSP1 DRM pipeline used for display + * @lock: protects the BRU and BRS allocation * @inputs: source crop rectangle, destination compose rectangle and z-order * position for every input (indexed by RPF index) */ struct vsp1_drm { struct vsp1_drm_pipeline pipe[VSP1_MAX_LIF]; + struct mutex lock; struct { struct v4l2_rect crop; diff --git a/drivers/media/platform/vsp1/vsp1_drv.c b/drivers/media/platform/vsp1/vsp1_drv.c index eed9516e25e1..5d82f6ee56ea 100644 --- a/drivers/media/platform/vsp1/vsp1_drv.c +++ b/drivers/media/platform/vsp1/vsp1_drv.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_drv.c -- R-Car VSP1 Driver * * Copyright (C) 2013-2015 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/clk.h> @@ -26,7 +22,7 @@ #include <media/v4l2-subdev.h> #include "vsp1.h" -#include "vsp1_bru.h" +#include "vsp1_brx.h" #include "vsp1_clu.h" #include "vsp1_dl.h" #include "vsp1_drm.h" @@ -39,6 +35,7 @@ #include "vsp1_rwpf.h" #include "vsp1_sru.h" #include "vsp1_uds.h" +#include "vsp1_uif.h" #include "vsp1_video.h" /* ----------------------------------------------------------------------------- @@ -63,7 +60,7 @@ static irqreturn_t vsp1_irq_handler(int irq, void *data) vsp1_write(vsp1, VI6_WPF_IRQ_STA(i), ~status & mask); if (status & VI6_WFP_IRQ_STA_DFE) { - vsp1_pipeline_frame_end(wpf->pipe); + vsp1_pipeline_frame_end(wpf->entity.pipe); ret = IRQ_HANDLED; } } @@ -269,7 +266,7 @@ static int vsp1_create_entities(struct vsp1_device *vsp1) /* Instantiate all the entities. */ if (vsp1->info->features & VSP1_HAS_BRS) { - vsp1->brs = vsp1_bru_create(vsp1, VSP1_ENTITY_BRS); + vsp1->brs = vsp1_brx_create(vsp1, VSP1_ENTITY_BRS); if (IS_ERR(vsp1->brs)) { ret = PTR_ERR(vsp1->brs); goto done; @@ -279,7 +276,7 @@ static int vsp1_create_entities(struct vsp1_device *vsp1) } if (vsp1->info->features & VSP1_HAS_BRU) { - vsp1->bru = vsp1_bru_create(vsp1, VSP1_ENTITY_BRU); + vsp1->bru = vsp1_brx_create(vsp1, VSP1_ENTITY_BRU); if (IS_ERR(vsp1->bru)) { ret = PTR_ERR(vsp1->bru); goto done; @@ -413,6 +410,19 @@ static int vsp1_create_entities(struct vsp1_device *vsp1) list_add_tail(&uds->entity.list_dev, &vsp1->entities); } + for (i = 0; i < vsp1->info->uif_count; ++i) { + struct vsp1_uif *uif; + + uif = vsp1_uif_create(vsp1, i); + if (IS_ERR(uif)) { + ret = PTR_ERR(uif); + goto done; + } + + vsp1->uif[i] = uif; + list_add_tail(&uif->entity.list_dev, &vsp1->entities); + } + for (i = 0; i < vsp1->info->wpf_count; ++i) { struct vsp1_rwpf *wpf; @@ -517,6 +527,9 @@ static int vsp1_device_init(struct vsp1_device *vsp1) for (i = 0; i < vsp1->info->uds_count; ++i) vsp1_write(vsp1, VI6_DPR_UDS_ROUTE(i), VI6_DPR_NODE_UNUSED); + for (i = 0; i < vsp1->info->uif_count; ++i) + vsp1_write(vsp1, VI6_DPR_UIF_ROUTE(i), VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_SRU_ROUTE, VI6_DPR_NODE_UNUSED); vsp1_write(vsp1, VI6_DPR_LUT_ROUTE, VI6_DPR_NODE_UNUSED); vsp1_write(vsp1, VI6_DPR_CLU_ROUTE, VI6_DPR_NODE_UNUSED); @@ -576,7 +589,7 @@ static int __maybe_unused vsp1_pm_suspend(struct device *dev) * restarted explicitly by the DU. */ if (!vsp1->drm) - vsp1_pipelines_suspend(vsp1); + vsp1_video_suspend(vsp1); pm_runtime_force_suspend(vsp1->dev); @@ -594,7 +607,7 @@ static int __maybe_unused vsp1_pm_resume(struct device *dev) * restarted explicitly by the DU. */ if (!vsp1->drm) - vsp1_pipelines_resume(vsp1); + vsp1_video_resume(vsp1); return 0; } @@ -744,6 +757,7 @@ static const struct vsp1_device_info vsp1_device_infos[] = { .features = VSP1_HAS_BRU | VSP1_HAS_WPF_VFLIP, .lif_count = 1, .rpf_count = 5, + .uif_count = 1, .wpf_count = 2, .num_bru_inputs = 5, }, { @@ -753,6 +767,7 @@ static const struct vsp1_device_info vsp1_device_infos[] = { .features = VSP1_HAS_BRS | VSP1_HAS_BRU, .lif_count = 1, .rpf_count = 5, + .uif_count = 1, .wpf_count = 1, .num_bru_inputs = 5, }, { @@ -762,6 +777,7 @@ static const struct vsp1_device_info vsp1_device_infos[] = { .features = VSP1_HAS_BRS | VSP1_HAS_BRU, .lif_count = 2, .rpf_count = 5, + .uif_count = 2, .wpf_count = 2, .num_bru_inputs = 5, }, diff --git a/drivers/media/platform/vsp1/vsp1_entity.c b/drivers/media/platform/vsp1/vsp1_entity.c index 54de15095709..da276a85aa95 100644 --- a/drivers/media/platform/vsp1/vsp1_entity.c +++ b/drivers/media/platform/vsp1/vsp1_entity.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_entity.c -- R-Car VSP1 Base Entity * * Copyright (C) 2013-2014 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/device.h> @@ -26,7 +22,7 @@ void vsp1_entity_route_setup(struct vsp1_entity *entity, struct vsp1_pipeline *pipe, - struct vsp1_dl_list *dl) + struct vsp1_dl_body *dlb) { struct vsp1_entity *source; u32 route; @@ -42,7 +38,7 @@ void vsp1_entity_route_setup(struct vsp1_entity *entity, smppt = (pipe->output->entity.index << VI6_DPR_SMPPT_TGW_SHIFT) | (source->route->output << VI6_DPR_SMPPT_PT_SHIFT); - vsp1_dl_list_write(dl, VI6_DPR_HGO_SMPPT, smppt); + vsp1_dl_body_write(dlb, VI6_DPR_HGO_SMPPT, smppt); return; } else if (entity->type == VSP1_ENTITY_HGT) { u32 smppt; @@ -55,7 +51,7 @@ void vsp1_entity_route_setup(struct vsp1_entity *entity, smppt = (pipe->output->entity.index << VI6_DPR_SMPPT_TGW_SHIFT) | (source->route->output << VI6_DPR_SMPPT_PT_SHIFT); - vsp1_dl_list_write(dl, VI6_DPR_HGT_SMPPT, smppt); + vsp1_dl_body_write(dlb, VI6_DPR_HGT_SMPPT, smppt); return; } @@ -70,7 +66,33 @@ void vsp1_entity_route_setup(struct vsp1_entity *entity, */ if (source->type == VSP1_ENTITY_BRS) route |= VI6_DPR_ROUTE_BRSSEL; - vsp1_dl_list_write(dl, source->route->reg, route); + vsp1_dl_body_write(dlb, source->route->reg, route); +} + +void vsp1_entity_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb) +{ + if (entity->ops->configure_stream) + entity->ops->configure_stream(entity, pipe, dlb); +} + +void vsp1_entity_configure_frame(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + if (entity->ops->configure_frame) + entity->ops->configure_frame(entity, pipe, dl, dlb); +} + +void vsp1_entity_configure_partition(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + if (entity->ops->configure_partition) + entity->ops->configure_partition(entity, pipe, dl, dlb); } /* ----------------------------------------------------------------------------- @@ -311,6 +333,97 @@ done: return ret; } +/* + * vsp1_subdev_set_pad_format - Subdev pad set_fmt handler + * @subdev: V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: V4L2 subdev format + * @codes: Array of supported media bus codes + * @ncodes: Number of supported media bus codes + * @min_width: Minimum image width + * @min_height: Minimum image height + * @max_width: Maximum image width + * @max_height: Maximum image height + * + * This function implements the subdev set_fmt pad operation for entities that + * do not support scaling or cropping. It defaults to the first supplied media + * bus code if the requested code isn't supported, clamps the size to the + * supplied minimum and maximum, and propagates the sink pad format to the + * source pad. + */ +int vsp1_subdev_set_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt, + const unsigned int *codes, unsigned int ncodes, + unsigned int min_width, unsigned int min_height, + unsigned int max_width, unsigned int max_height) +{ + struct vsp1_entity *entity = to_vsp1_entity(subdev); + struct v4l2_subdev_pad_config *config; + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *selection; + unsigned int i; + int ret = 0; + + mutex_lock(&entity->lock); + + config = vsp1_entity_get_pad_config(entity, cfg, fmt->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + format = vsp1_entity_get_pad_format(entity, config, fmt->pad); + + if (fmt->pad == entity->source_pad) { + /* The output format can't be modified. */ + fmt->format = *format; + goto done; + } + + /* + * Default to the first media bus code if the requested format is not + * supported. + */ + for (i = 0; i < ncodes; ++i) { + if (fmt->format.code == codes[i]) + break; + } + + format->code = i < ncodes ? codes[i] : codes[0]; + format->width = clamp_t(unsigned int, fmt->format.width, + min_width, max_width); + format->height = clamp_t(unsigned int, fmt->format.height, + min_height, max_height); + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + fmt->format = *format; + + /* Propagate the format to the source pad. */ + format = vsp1_entity_get_pad_format(entity, config, entity->source_pad); + *format = fmt->format; + + /* Reset the crop and compose rectangles */ + selection = vsp1_entity_get_pad_selection(entity, config, fmt->pad, + V4L2_SEL_TGT_CROP); + selection->left = 0; + selection->top = 0; + selection->width = format->width; + selection->height = format->height; + + selection = vsp1_entity_get_pad_selection(entity, config, fmt->pad, + V4L2_SEL_TGT_COMPOSE); + selection->left = 0; + selection->top = 0; + selection->width = format->width; + selection->height = format->height; + +done: + mutex_unlock(&entity->lock); + return ret; +} + /* ----------------------------------------------------------------------------- * Media Operations */ @@ -452,6 +565,10 @@ struct media_pad *vsp1_entity_remote_pad(struct media_pad *pad) { VSP1_ENTITY_UDS, idx, VI6_DPR_UDS_ROUTE(idx), \ { VI6_DPR_NODE_UDS(idx) }, VI6_DPR_NODE_UDS(idx) } +#define VSP1_ENTITY_ROUTE_UIF(idx) \ + { VSP1_ENTITY_UIF, idx, VI6_DPR_UIF_ROUTE(idx), \ + { VI6_DPR_NODE_UIF(idx) }, VI6_DPR_NODE_UIF(idx) } + #define VSP1_ENTITY_ROUTE_WPF(idx) \ { VSP1_ENTITY_WPF, idx, 0, \ { VI6_DPR_NODE_WPF(idx) }, VI6_DPR_NODE_WPF(idx) } @@ -480,6 +597,8 @@ static const struct vsp1_route vsp1_routes[] = { VSP1_ENTITY_ROUTE_UDS(0), VSP1_ENTITY_ROUTE_UDS(1), VSP1_ENTITY_ROUTE_UDS(2), + VSP1_ENTITY_ROUTE_UIF(0), /* Named UIF4 in the documentation */ + VSP1_ENTITY_ROUTE_UIF(1), /* Named UIF5 in the documentation */ VSP1_ENTITY_ROUTE_WPF(0), VSP1_ENTITY_ROUTE_WPF(1), VSP1_ENTITY_ROUTE_WPF(2), diff --git a/drivers/media/platform/vsp1/vsp1_entity.h b/drivers/media/platform/vsp1/vsp1_entity.h index 408602ebeb97..97acb7795cf1 100644 --- a/drivers/media/platform/vsp1/vsp1_entity.h +++ b/drivers/media/platform/vsp1/vsp1_entity.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1_entity.h -- R-Car VSP1 Base Entity * * Copyright (C) 2013-2014 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_ENTITY_H__ #define __VSP1_ENTITY_H__ @@ -19,6 +15,7 @@ #include <media/v4l2-subdev.h> struct vsp1_device; +struct vsp1_dl_body; struct vsp1_dl_list; struct vsp1_pipeline; struct vsp1_partition; @@ -37,21 +34,10 @@ enum vsp1_entity_type { VSP1_ENTITY_RPF, VSP1_ENTITY_SRU, VSP1_ENTITY_UDS, + VSP1_ENTITY_UIF, VSP1_ENTITY_WPF, }; -/** - * enum vsp1_entity_params - Entity configuration parameters class - * @VSP1_ENTITY_PARAMS_INIT - Initial parameters - * @VSP1_ENTITY_PARAMS_PARTITION - Per-image partition parameters - * @VSP1_ENTITY_PARAMS_RUNTIME - Runtime-configurable parameters - */ -enum vsp1_entity_params { - VSP1_ENTITY_PARAMS_INIT, - VSP1_ENTITY_PARAMS_PARTITION, - VSP1_ENTITY_PARAMS_RUNTIME, -}; - #define VSP1_ENTITY_MAX_INPUTS 5 /* For the BRU */ /* @@ -80,8 +66,10 @@ struct vsp1_route { /** * struct vsp1_entity_operations - Entity operations * @destroy: Destroy the entity. - * @configure: Setup the hardware based on the entity state (pipeline, formats, - * selection rectangles, ...) + * @configure_stream: Setup the hardware parameters for the stream which do + * not vary between frames (pipeline, formats). + * @configure_frame: Configure the runtime parameters for each frame. + * @configure_partition: Configure partition specific parameters. * @max_width: Return the max supported width of data that the entity can * process in a single operation. * @partition: Process the partition construction based on this entity's @@ -89,8 +77,14 @@ struct vsp1_route { */ struct vsp1_entity_operations { void (*destroy)(struct vsp1_entity *); - void (*configure)(struct vsp1_entity *, struct vsp1_pipeline *, - struct vsp1_dl_list *, enum vsp1_entity_params); + void (*configure_stream)(struct vsp1_entity *, struct vsp1_pipeline *, + struct vsp1_dl_body *); + void (*configure_frame)(struct vsp1_entity *, struct vsp1_pipeline *, + struct vsp1_dl_list *, struct vsp1_dl_body *); + void (*configure_partition)(struct vsp1_entity *, + struct vsp1_pipeline *, + struct vsp1_dl_list *, + struct vsp1_dl_body *); unsigned int (*max_width)(struct vsp1_entity *, struct vsp1_pipeline *); void (*partition)(struct vsp1_entity *, struct vsp1_pipeline *, struct vsp1_partition *, unsigned int, @@ -106,6 +100,8 @@ struct vsp1_entity { unsigned int index; const struct vsp1_route *route; + struct vsp1_pipeline *pipe; + struct list_head list_dev; struct list_head list_pipe; @@ -155,13 +151,33 @@ int vsp1_entity_init_cfg(struct v4l2_subdev *subdev, void vsp1_entity_route_setup(struct vsp1_entity *entity, struct vsp1_pipeline *pipe, - struct vsp1_dl_list *dl); + struct vsp1_dl_body *dlb); + +void vsp1_entity_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb); + +void vsp1_entity_configure_frame(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb); + +void vsp1_entity_configure_partition(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb); struct media_pad *vsp1_entity_remote_pad(struct media_pad *pad); int vsp1_subdev_get_pad_format(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt); +int vsp1_subdev_set_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt, + const unsigned int *codes, unsigned int ncodes, + unsigned int min_width, unsigned int min_height, + unsigned int max_width, unsigned int max_height); int vsp1_subdev_enum_mbus_code(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_mbus_code_enum *code, diff --git a/drivers/media/platform/vsp1/vsp1_hgo.c b/drivers/media/platform/vsp1/vsp1_hgo.c index 50309c053b78..827373c25351 100644 --- a/drivers/media/platform/vsp1/vsp1_hgo.c +++ b/drivers/media/platform/vsp1/vsp1_hgo.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_hgo.c -- R-Car VSP1 Histogram Generator 1D * * Copyright (C) 2016 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/device.h> @@ -32,10 +28,10 @@ static inline u32 vsp1_hgo_read(struct vsp1_hgo *hgo, u32 reg) return vsp1_read(hgo->histo.entity.vsp1, reg); } -static inline void vsp1_hgo_write(struct vsp1_hgo *hgo, struct vsp1_dl_list *dl, - u32 reg, u32 data) +static inline void vsp1_hgo_write(struct vsp1_hgo *hgo, + struct vsp1_dl_body *dlb, u32 reg, u32 data) { - vsp1_dl_list_write(dl, reg, data); + vsp1_dl_body_write(dlb, reg, data); } /* ----------------------------------------------------------------------------- @@ -133,10 +129,9 @@ static const struct v4l2_ctrl_config hgo_num_bins_control = { * VSP1 Entity Operations */ -static void hgo_configure(struct vsp1_entity *entity, - struct vsp1_pipeline *pipe, - struct vsp1_dl_list *dl, - enum vsp1_entity_params params) +static void hgo_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb) { struct vsp1_hgo *hgo = to_hgo(&entity->subdev); struct v4l2_rect *compose; @@ -144,21 +139,18 @@ static void hgo_configure(struct vsp1_entity *entity, unsigned int hratio; unsigned int vratio; - if (params != VSP1_ENTITY_PARAMS_INIT) - return; - crop = vsp1_entity_get_pad_selection(entity, entity->config, HISTO_PAD_SINK, V4L2_SEL_TGT_CROP); compose = vsp1_entity_get_pad_selection(entity, entity->config, HISTO_PAD_SINK, V4L2_SEL_TGT_COMPOSE); - vsp1_hgo_write(hgo, dl, VI6_HGO_REGRST, VI6_HGO_REGRST_RCLEA); + vsp1_hgo_write(hgo, dlb, VI6_HGO_REGRST, VI6_HGO_REGRST_RCLEA); - vsp1_hgo_write(hgo, dl, VI6_HGO_OFFSET, + vsp1_hgo_write(hgo, dlb, VI6_HGO_OFFSET, (crop->left << VI6_HGO_OFFSET_HOFFSET_SHIFT) | (crop->top << VI6_HGO_OFFSET_VOFFSET_SHIFT)); - vsp1_hgo_write(hgo, dl, VI6_HGO_SIZE, + vsp1_hgo_write(hgo, dlb, VI6_HGO_SIZE, (crop->width << VI6_HGO_SIZE_HSIZE_SHIFT) | (crop->height << VI6_HGO_SIZE_VSIZE_SHIFT)); @@ -170,7 +162,7 @@ static void hgo_configure(struct vsp1_entity *entity, hratio = crop->width * 2 / compose->width / 3; vratio = crop->height * 2 / compose->height / 3; - vsp1_hgo_write(hgo, dl, VI6_HGO_MODE, + vsp1_hgo_write(hgo, dlb, VI6_HGO_MODE, (hgo->num_bins == 256 ? VI6_HGO_MODE_STEP : 0) | (hgo->max_rgb ? VI6_HGO_MODE_MAXRGB : 0) | (hratio << VI6_HGO_MODE_HRATIO_SHIFT) | @@ -178,7 +170,7 @@ static void hgo_configure(struct vsp1_entity *entity, } static const struct vsp1_entity_operations hgo_entity_ops = { - .configure = hgo_configure, + .configure_stream = hgo_configure_stream, .destroy = vsp1_histogram_destroy, }; diff --git a/drivers/media/platform/vsp1/vsp1_hgo.h b/drivers/media/platform/vsp1/vsp1_hgo.h index c6c0b7a80e0c..6b0c8580e1bf 100644 --- a/drivers/media/platform/vsp1/vsp1_hgo.h +++ b/drivers/media/platform/vsp1/vsp1_hgo.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1_hgo.h -- R-Car VSP1 Histogram Generator 1D * * Copyright (C) 2016 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_HGO_H__ #define __VSP1_HGO_H__ diff --git a/drivers/media/platform/vsp1/vsp1_hgt.c b/drivers/media/platform/vsp1/vsp1_hgt.c index b5ce305e3e6f..bb6ce6fdd5f4 100644 --- a/drivers/media/platform/vsp1/vsp1_hgt.c +++ b/drivers/media/platform/vsp1/vsp1_hgt.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_hgt.c -- R-Car VSP1 Histogram Generator 2D * * Copyright (C) 2016 Renesas Electronics Corporation * * Contact: Niklas Söderlund (niklas.soderlund@ragnatech.se) - * - * 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/device.h> @@ -32,10 +28,10 @@ static inline u32 vsp1_hgt_read(struct vsp1_hgt *hgt, u32 reg) return vsp1_read(hgt->histo.entity.vsp1, reg); } -static inline void vsp1_hgt_write(struct vsp1_hgt *hgt, struct vsp1_dl_list *dl, - u32 reg, u32 data) +static inline void vsp1_hgt_write(struct vsp1_hgt *hgt, + struct vsp1_dl_body *dlb, u32 reg, u32 data) { - vsp1_dl_list_write(dl, reg, data); + vsp1_dl_body_write(dlb, reg, data); } /* ----------------------------------------------------------------------------- @@ -129,10 +125,9 @@ static const struct v4l2_ctrl_config hgt_hue_areas = { * VSP1 Entity Operations */ -static void hgt_configure(struct vsp1_entity *entity, - struct vsp1_pipeline *pipe, - struct vsp1_dl_list *dl, - enum vsp1_entity_params params) +static void hgt_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb) { struct vsp1_hgt *hgt = to_hgt(&entity->subdev); struct v4l2_rect *compose; @@ -143,21 +138,18 @@ static void hgt_configure(struct vsp1_entity *entity, u8 upper; unsigned int i; - if (params != VSP1_ENTITY_PARAMS_INIT) - return; - crop = vsp1_entity_get_pad_selection(entity, entity->config, HISTO_PAD_SINK, V4L2_SEL_TGT_CROP); compose = vsp1_entity_get_pad_selection(entity, entity->config, HISTO_PAD_SINK, V4L2_SEL_TGT_COMPOSE); - vsp1_hgt_write(hgt, dl, VI6_HGT_REGRST, VI6_HGT_REGRST_RCLEA); + vsp1_hgt_write(hgt, dlb, VI6_HGT_REGRST, VI6_HGT_REGRST_RCLEA); - vsp1_hgt_write(hgt, dl, VI6_HGT_OFFSET, + vsp1_hgt_write(hgt, dlb, VI6_HGT_OFFSET, (crop->left << VI6_HGT_OFFSET_HOFFSET_SHIFT) | (crop->top << VI6_HGT_OFFSET_VOFFSET_SHIFT)); - vsp1_hgt_write(hgt, dl, VI6_HGT_SIZE, + vsp1_hgt_write(hgt, dlb, VI6_HGT_SIZE, (crop->width << VI6_HGT_SIZE_HSIZE_SHIFT) | (crop->height << VI6_HGT_SIZE_VSIZE_SHIFT)); @@ -165,7 +157,7 @@ static void hgt_configure(struct vsp1_entity *entity, for (i = 0; i < HGT_NUM_HUE_AREAS; ++i) { lower = hgt->hue_areas[i*2 + 0]; upper = hgt->hue_areas[i*2 + 1]; - vsp1_hgt_write(hgt, dl, VI6_HGT_HUE_AREA(i), + vsp1_hgt_write(hgt, dlb, VI6_HGT_HUE_AREA(i), (lower << VI6_HGT_HUE_AREA_LOWER_SHIFT) | (upper << VI6_HGT_HUE_AREA_UPPER_SHIFT)); } @@ -173,13 +165,13 @@ static void hgt_configure(struct vsp1_entity *entity, hratio = crop->width * 2 / compose->width / 3; vratio = crop->height * 2 / compose->height / 3; - vsp1_hgt_write(hgt, dl, VI6_HGT_MODE, + vsp1_hgt_write(hgt, dlb, VI6_HGT_MODE, (hratio << VI6_HGT_MODE_HRATIO_SHIFT) | (vratio << VI6_HGT_MODE_VRATIO_SHIFT)); } static const struct vsp1_entity_operations hgt_entity_ops = { - .configure = hgt_configure, + .configure_stream = hgt_configure_stream, .destroy = vsp1_histogram_destroy, }; diff --git a/drivers/media/platform/vsp1/vsp1_hgt.h b/drivers/media/platform/vsp1/vsp1_hgt.h index 83f2e130942a..38ec237bdd2d 100644 --- a/drivers/media/platform/vsp1/vsp1_hgt.h +++ b/drivers/media/platform/vsp1/vsp1_hgt.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1_hgt.h -- R-Car VSP1 Histogram Generator 2D * * Copyright (C) 2016 Renesas Electronics Corporation * * Contact: Niklas Söderlund (niklas.soderlund@ragnatech.se) - * - * 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 __VSP1_HGT_H__ #define __VSP1_HGT_H__ diff --git a/drivers/media/platform/vsp1/vsp1_histo.c b/drivers/media/platform/vsp1/vsp1_histo.c index afab77cf4fa5..5e15c8ff88d9 100644 --- a/drivers/media/platform/vsp1/vsp1_histo.c +++ b/drivers/media/platform/vsp1/vsp1_histo.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_histo.c -- R-Car VSP1 Histogram API * @@ -5,11 +6,6 @@ * Copyright (C) 2016 Laurent Pinchart * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/device.h> @@ -61,7 +57,7 @@ void vsp1_histogram_buffer_complete(struct vsp1_histogram *histo, struct vsp1_histogram_buffer *buf, size_t size) { - struct vsp1_pipeline *pipe = histo->pipe; + struct vsp1_pipeline *pipe = histo->entity.pipe; unsigned long flags; /* @@ -393,65 +389,14 @@ static int histo_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_format *fmt) { struct vsp1_histogram *histo = subdev_to_histo(subdev); - struct v4l2_subdev_pad_config *config; - struct v4l2_mbus_framefmt *format; - struct v4l2_rect *selection; - unsigned int i; - int ret = 0; if (fmt->pad != HISTO_PAD_SINK) return histo_get_format(subdev, cfg, fmt); - mutex_lock(&histo->entity.lock); - - config = vsp1_entity_get_pad_config(&histo->entity, cfg, fmt->which); - if (!config) { - ret = -EINVAL; - goto done; - } - - /* - * Default to the first format if the requested format is not - * supported. - */ - for (i = 0; i < histo->num_formats; ++i) { - if (fmt->format.code == histo->formats[i]) - break; - } - if (i == histo->num_formats) - fmt->format.code = histo->formats[0]; - - format = vsp1_entity_get_pad_format(&histo->entity, config, fmt->pad); - - format->code = fmt->format.code; - format->width = clamp_t(unsigned int, fmt->format.width, - HISTO_MIN_SIZE, HISTO_MAX_SIZE); - format->height = clamp_t(unsigned int, fmt->format.height, - HISTO_MIN_SIZE, HISTO_MAX_SIZE); - format->field = V4L2_FIELD_NONE; - format->colorspace = V4L2_COLORSPACE_SRGB; - - fmt->format = *format; - - /* Reset the crop and compose rectangles */ - selection = vsp1_entity_get_pad_selection(&histo->entity, config, - fmt->pad, V4L2_SEL_TGT_CROP); - selection->left = 0; - selection->top = 0; - selection->width = format->width; - selection->height = format->height; - - selection = vsp1_entity_get_pad_selection(&histo->entity, config, - fmt->pad, - V4L2_SEL_TGT_COMPOSE); - selection->left = 0; - selection->top = 0; - selection->width = format->width; - selection->height = format->height; - -done: - mutex_unlock(&histo->entity.lock); - return ret; + return vsp1_subdev_set_pad_format(subdev, cfg, fmt, + histo->formats, histo->num_formats, + HISTO_MIN_SIZE, HISTO_MIN_SIZE, + HISTO_MAX_SIZE, HISTO_MAX_SIZE); } static const struct v4l2_subdev_pad_ops histo_pad_ops = { diff --git a/drivers/media/platform/vsp1/vsp1_histo.h b/drivers/media/platform/vsp1/vsp1_histo.h index af2874f6031d..06f029846244 100644 --- a/drivers/media/platform/vsp1/vsp1_histo.h +++ b/drivers/media/platform/vsp1/vsp1_histo.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1_histo.h -- R-Car VSP1 Histogram API * @@ -5,11 +6,6 @@ * Copyright (C) 2016 Laurent Pinchart * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_HISTO_H__ #define __VSP1_HISTO_H__ @@ -25,7 +21,6 @@ #include "vsp1_entity.h" struct vsp1_device; -struct vsp1_pipeline; #define HISTO_PAD_SINK 0 #define HISTO_PAD_SOURCE 1 @@ -37,8 +32,6 @@ struct vsp1_histogram_buffer { }; struct vsp1_histogram { - struct vsp1_pipeline *pipe; - struct vsp1_entity entity; struct video_device video; struct media_pad pad; diff --git a/drivers/media/platform/vsp1/vsp1_hsit.c b/drivers/media/platform/vsp1/vsp1_hsit.c index 764d405345ee..39ab2e0c7c18 100644 --- a/drivers/media/platform/vsp1/vsp1_hsit.c +++ b/drivers/media/platform/vsp1/vsp1_hsit.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_hsit.c -- R-Car VSP1 Hue Saturation value (Inverse) Transform * * Copyright (C) 2013 Renesas Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/device.h> @@ -28,9 +24,9 @@ */ static inline void vsp1_hsit_write(struct vsp1_hsit *hsit, - struct vsp1_dl_list *dl, u32 reg, u32 data) + struct vsp1_dl_body *dlb, u32 reg, u32 data) { - vsp1_dl_list_write(dl, reg, data); + vsp1_dl_body_write(dlb, reg, data); } /* ----------------------------------------------------------------------------- @@ -131,24 +127,20 @@ static const struct v4l2_subdev_ops hsit_ops = { * VSP1 Entity Operations */ -static void hsit_configure(struct vsp1_entity *entity, - struct vsp1_pipeline *pipe, - struct vsp1_dl_list *dl, - enum vsp1_entity_params params) +static void hsit_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb) { struct vsp1_hsit *hsit = to_hsit(&entity->subdev); - if (params != VSP1_ENTITY_PARAMS_INIT) - return; - if (hsit->inverse) - vsp1_hsit_write(hsit, dl, VI6_HSI_CTRL, VI6_HSI_CTRL_EN); + vsp1_hsit_write(hsit, dlb, VI6_HSI_CTRL, VI6_HSI_CTRL_EN); else - vsp1_hsit_write(hsit, dl, VI6_HST_CTRL, VI6_HST_CTRL_EN); + vsp1_hsit_write(hsit, dlb, VI6_HST_CTRL, VI6_HST_CTRL_EN); } static const struct vsp1_entity_operations hsit_entity_ops = { - .configure = hsit_configure, + .configure_stream = hsit_configure_stream, }; /* ----------------------------------------------------------------------------- diff --git a/drivers/media/platform/vsp1/vsp1_hsit.h b/drivers/media/platform/vsp1/vsp1_hsit.h index 82f1c8426900..a658b1aa49e7 100644 --- a/drivers/media/platform/vsp1/vsp1_hsit.h +++ b/drivers/media/platform/vsp1/vsp1_hsit.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1_hsit.h -- R-Car VSP1 Hue Saturation value (Inverse) Transform * * Copyright (C) 2013 Renesas Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_HSIT_H__ #define __VSP1_HSIT_H__ diff --git a/drivers/media/platform/vsp1/vsp1_lif.c b/drivers/media/platform/vsp1/vsp1_lif.c index 704920753998..0cb63244b21a 100644 --- a/drivers/media/platform/vsp1/vsp1_lif.c +++ b/drivers/media/platform/vsp1/vsp1_lif.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_lif.c -- R-Car VSP1 LCD Controller Interface * * Copyright (C) 2013-2014 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/device.h> @@ -27,27 +23,28 @@ * Device Access */ -static inline void vsp1_lif_write(struct vsp1_lif *lif, struct vsp1_dl_list *dl, - u32 reg, u32 data) +static inline void vsp1_lif_write(struct vsp1_lif *lif, + struct vsp1_dl_body *dlb, u32 reg, u32 data) { - vsp1_dl_list_write(dl, reg + lif->entity.index * VI6_LIF_OFFSET, data); + vsp1_dl_body_write(dlb, reg + lif->entity.index * VI6_LIF_OFFSET, + data); } /* ----------------------------------------------------------------------------- * V4L2 Subdevice Operations */ +static const unsigned int lif_codes[] = { + MEDIA_BUS_FMT_ARGB8888_1X32, + MEDIA_BUS_FMT_AYUV8_1X32, +}; + static int lif_enum_mbus_code(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_mbus_code_enum *code) { - static const unsigned int codes[] = { - MEDIA_BUS_FMT_ARGB8888_1X32, - MEDIA_BUS_FMT_AYUV8_1X32, - }; - - return vsp1_subdev_enum_mbus_code(subdev, cfg, code, codes, - ARRAY_SIZE(codes)); + return vsp1_subdev_enum_mbus_code(subdev, cfg, code, lif_codes, + ARRAY_SIZE(lif_codes)); } static int lif_enum_frame_size(struct v4l2_subdev *subdev, @@ -63,53 +60,10 @@ static int lif_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt) { - struct vsp1_lif *lif = to_lif(subdev); - struct v4l2_subdev_pad_config *config; - struct v4l2_mbus_framefmt *format; - int ret = 0; - - mutex_lock(&lif->entity.lock); - - config = vsp1_entity_get_pad_config(&lif->entity, cfg, fmt->which); - if (!config) { - ret = -EINVAL; - goto done; - } - - /* Default to YUV if the requested format is not supported. */ - if (fmt->format.code != MEDIA_BUS_FMT_ARGB8888_1X32 && - fmt->format.code != MEDIA_BUS_FMT_AYUV8_1X32) - fmt->format.code = MEDIA_BUS_FMT_AYUV8_1X32; - - format = vsp1_entity_get_pad_format(&lif->entity, config, fmt->pad); - - if (fmt->pad == LIF_PAD_SOURCE) { - /* - * The LIF source format is always identical to its sink - * format. - */ - fmt->format = *format; - goto done; - } - - format->code = fmt->format.code; - format->width = clamp_t(unsigned int, fmt->format.width, - LIF_MIN_SIZE, LIF_MAX_SIZE); - format->height = clamp_t(unsigned int, fmt->format.height, - LIF_MIN_SIZE, LIF_MAX_SIZE); - format->field = V4L2_FIELD_NONE; - format->colorspace = V4L2_COLORSPACE_SRGB; - - fmt->format = *format; - - /* Propagate the format to the source pad. */ - format = vsp1_entity_get_pad_format(&lif->entity, config, - LIF_PAD_SOURCE); - *format = fmt->format; - -done: - mutex_unlock(&lif->entity.lock); - return ret; + return vsp1_subdev_set_pad_format(subdev, cfg, fmt, lif_codes, + ARRAY_SIZE(lif_codes), + LIF_MIN_SIZE, LIF_MIN_SIZE, + LIF_MAX_SIZE, LIF_MAX_SIZE); } static const struct v4l2_subdev_pad_ops lif_pad_ops = { @@ -128,10 +82,9 @@ static const struct v4l2_subdev_ops lif_ops = { * VSP1 Entity Operations */ -static void lif_configure(struct vsp1_entity *entity, - struct vsp1_pipeline *pipe, - struct vsp1_dl_list *dl, - enum vsp1_entity_params params) +static void lif_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb) { const struct v4l2_mbus_framefmt *format; struct vsp1_lif *lif = to_lif(&entity->subdev); @@ -139,19 +92,16 @@ static void lif_configure(struct vsp1_entity *entity, unsigned int obth = 400; unsigned int lbth = 200; - if (params != VSP1_ENTITY_PARAMS_INIT) - return; - format = vsp1_entity_get_pad_format(&lif->entity, lif->entity.config, LIF_PAD_SOURCE); obth = min(obth, (format->width + 1) / 2 * format->height - 4); - vsp1_lif_write(lif, dl, VI6_LIF_CSBTH, + vsp1_lif_write(lif, dlb, VI6_LIF_CSBTH, (hbth << VI6_LIF_CSBTH_HBTH_SHIFT) | (lbth << VI6_LIF_CSBTH_LBTH_SHIFT)); - vsp1_lif_write(lif, dl, VI6_LIF_CTRL, + vsp1_lif_write(lif, dlb, VI6_LIF_CTRL, (obth << VI6_LIF_CTRL_OBTH_SHIFT) | (format->code == 0 ? VI6_LIF_CTRL_CFMT : 0) | VI6_LIF_CTRL_REQSEL | VI6_LIF_CTRL_LIF_EN); @@ -164,13 +114,13 @@ static void lif_configure(struct vsp1_entity *entity, */ if ((entity->vsp1->version & VI6_IP_VERSION_MASK) == (VI6_IP_VERSION_MODEL_VSPD_V3 | VI6_IP_VERSION_SOC_V3M)) - vsp1_lif_write(lif, dl, VI6_LIF_LBA, + vsp1_lif_write(lif, dlb, VI6_LIF_LBA, VI6_LIF_LBA_LBA0 | (1536 << VI6_LIF_LBA_LBA1_SHIFT)); } static const struct vsp1_entity_operations lif_entity_ops = { - .configure = lif_configure, + .configure_stream = lif_configure_stream, }; /* ----------------------------------------------------------------------------- diff --git a/drivers/media/platform/vsp1/vsp1_lif.h b/drivers/media/platform/vsp1/vsp1_lif.h index 3417339379b1..71a4eda9c2b2 100644 --- a/drivers/media/platform/vsp1/vsp1_lif.h +++ b/drivers/media/platform/vsp1/vsp1_lif.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1_lif.h -- R-Car VSP1 LCD Controller Interface * * Copyright (C) 2013-2014 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_LIF_H__ #define __VSP1_LIF_H__ diff --git a/drivers/media/platform/vsp1/vsp1_lut.c b/drivers/media/platform/vsp1/vsp1_lut.c index c67cc60db0db..64c48d9459b0 100644 --- a/drivers/media/platform/vsp1/vsp1_lut.c +++ b/drivers/media/platform/vsp1/vsp1_lut.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_lut.c -- R-Car VSP1 Look-Up Table * * Copyright (C) 2013 Renesas Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/device.h> @@ -23,14 +19,16 @@ #define LUT_MIN_SIZE 4U #define LUT_MAX_SIZE 8190U +#define LUT_SIZE 256 + /* ----------------------------------------------------------------------------- * Device Access */ -static inline void vsp1_lut_write(struct vsp1_lut *lut, struct vsp1_dl_list *dl, - u32 reg, u32 data) +static inline void vsp1_lut_write(struct vsp1_lut *lut, + struct vsp1_dl_body *dlb, u32 reg, u32 data) { - vsp1_dl_list_write(dl, reg, data); + vsp1_dl_body_write(dlb, reg, data); } /* ----------------------------------------------------------------------------- @@ -44,19 +42,19 @@ static int lut_set_table(struct vsp1_lut *lut, struct v4l2_ctrl *ctrl) struct vsp1_dl_body *dlb; unsigned int i; - dlb = vsp1_dl_fragment_alloc(lut->entity.vsp1, 256); + dlb = vsp1_dl_body_get(lut->pool); if (!dlb) return -ENOMEM; - for (i = 0; i < 256; ++i) - vsp1_dl_fragment_write(dlb, VI6_LUT_TABLE + 4 * i, + for (i = 0; i < LUT_SIZE; ++i) + vsp1_dl_body_write(dlb, VI6_LUT_TABLE + 4 * i, ctrl->p_new.p_u32[i]); spin_lock_irq(&lut->lock); swap(lut->lut, dlb); spin_unlock_irq(&lut->lock); - vsp1_dl_fragment_free(dlb); + vsp1_dl_body_put(dlb); return 0; } @@ -87,25 +85,25 @@ static const struct v4l2_ctrl_config lut_table_control = { .max = 0x00ffffff, .step = 1, .def = 0, - .dims = { 256}, + .dims = { LUT_SIZE }, }; /* ----------------------------------------------------------------------------- * V4L2 Subdevice Pad Operations */ +static const unsigned int lut_codes[] = { + MEDIA_BUS_FMT_ARGB8888_1X32, + MEDIA_BUS_FMT_AHSV8888_1X32, + MEDIA_BUS_FMT_AYUV8_1X32, +}; + static int lut_enum_mbus_code(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_mbus_code_enum *code) { - static const unsigned int codes[] = { - MEDIA_BUS_FMT_ARGB8888_1X32, - MEDIA_BUS_FMT_AHSV8888_1X32, - MEDIA_BUS_FMT_AYUV8_1X32, - }; - - return vsp1_subdev_enum_mbus_code(subdev, cfg, code, codes, - ARRAY_SIZE(codes)); + return vsp1_subdev_enum_mbus_code(subdev, cfg, code, lut_codes, + ARRAY_SIZE(lut_codes)); } static int lut_enum_frame_size(struct v4l2_subdev *subdev, @@ -121,51 +119,10 @@ static int lut_set_format(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt) { - struct vsp1_lut *lut = to_lut(subdev); - struct v4l2_subdev_pad_config *config; - struct v4l2_mbus_framefmt *format; - int ret = 0; - - mutex_lock(&lut->entity.lock); - - config = vsp1_entity_get_pad_config(&lut->entity, cfg, fmt->which); - if (!config) { - ret = -EINVAL; - goto done; - } - - /* Default to YUV if the requested format is not supported. */ - if (fmt->format.code != MEDIA_BUS_FMT_ARGB8888_1X32 && - fmt->format.code != MEDIA_BUS_FMT_AHSV8888_1X32 && - fmt->format.code != MEDIA_BUS_FMT_AYUV8_1X32) - fmt->format.code = MEDIA_BUS_FMT_AYUV8_1X32; - - format = vsp1_entity_get_pad_format(&lut->entity, config, fmt->pad); - - if (fmt->pad == LUT_PAD_SOURCE) { - /* The LUT output format can't be modified. */ - fmt->format = *format; - goto done; - } - - format->code = fmt->format.code; - format->width = clamp_t(unsigned int, fmt->format.width, - LUT_MIN_SIZE, LUT_MAX_SIZE); - format->height = clamp_t(unsigned int, fmt->format.height, - LUT_MIN_SIZE, LUT_MAX_SIZE); - format->field = V4L2_FIELD_NONE; - format->colorspace = V4L2_COLORSPACE_SRGB; - - fmt->format = *format; - - /* Propagate the format to the source pad. */ - format = vsp1_entity_get_pad_format(&lut->entity, config, - LUT_PAD_SOURCE); - *format = fmt->format; - -done: - mutex_unlock(&lut->entity.lock); - return ret; + return vsp1_subdev_set_pad_format(subdev, cfg, fmt, lut_codes, + ARRAY_SIZE(lut_codes), + LUT_MIN_SIZE, LUT_MIN_SIZE, + LUT_MAX_SIZE, LUT_MAX_SIZE); } /* ----------------------------------------------------------------------------- @@ -188,37 +145,48 @@ static const struct v4l2_subdev_ops lut_ops = { * VSP1 Entity Operations */ -static void lut_configure(struct vsp1_entity *entity, - struct vsp1_pipeline *pipe, - struct vsp1_dl_list *dl, - enum vsp1_entity_params params) +static void lut_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb) { struct vsp1_lut *lut = to_lut(&entity->subdev); - struct vsp1_dl_body *dlb; - unsigned long flags; - switch (params) { - case VSP1_ENTITY_PARAMS_INIT: - vsp1_lut_write(lut, dl, VI6_LUT_CTRL, VI6_LUT_CTRL_EN); - break; + vsp1_lut_write(lut, dlb, VI6_LUT_CTRL, VI6_LUT_CTRL_EN); +} - case VSP1_ENTITY_PARAMS_PARTITION: - break; +static void lut_configure_frame(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_lut *lut = to_lut(&entity->subdev); + struct vsp1_dl_body *lut_dlb; + unsigned long flags; - case VSP1_ENTITY_PARAMS_RUNTIME: - spin_lock_irqsave(&lut->lock, flags); - dlb = lut->lut; - lut->lut = NULL; - spin_unlock_irqrestore(&lut->lock, flags); + spin_lock_irqsave(&lut->lock, flags); + lut_dlb = lut->lut; + lut->lut = NULL; + spin_unlock_irqrestore(&lut->lock, flags); - if (dlb) - vsp1_dl_list_add_fragment(dl, dlb); - break; + if (lut_dlb) { + vsp1_dl_list_add_body(dl, lut_dlb); + + /* Release our local reference. */ + vsp1_dl_body_put(lut_dlb); } } +static void lut_destroy(struct vsp1_entity *entity) +{ + struct vsp1_lut *lut = to_lut(&entity->subdev); + + vsp1_dl_body_pool_destroy(lut->pool); +} + static const struct vsp1_entity_operations lut_entity_ops = { - .configure = lut_configure, + .configure_stream = lut_configure_stream, + .configure_frame = lut_configure_frame, + .destroy = lut_destroy, }; /* ----------------------------------------------------------------------------- @@ -244,6 +212,15 @@ struct vsp1_lut *vsp1_lut_create(struct vsp1_device *vsp1) if (ret < 0) return ERR_PTR(ret); + /* + * Pre-allocate a body pool, with 3 bodies allowing a userspace update + * before the hardware has committed a previous set of tables, handling + * both the queued and pending dl entries. + */ + lut->pool = vsp1_dl_body_pool_create(vsp1, 3, LUT_SIZE, 0); + if (!lut->pool) + return ERR_PTR(-ENOMEM); + /* Initialize the control handler. */ v4l2_ctrl_handler_init(&lut->ctrls, 1); v4l2_ctrl_new_custom(&lut->ctrls, &lut_table_control, NULL); diff --git a/drivers/media/platform/vsp1/vsp1_lut.h b/drivers/media/platform/vsp1/vsp1_lut.h index f8c4e8f0a79d..8cb0df1b7e27 100644 --- a/drivers/media/platform/vsp1/vsp1_lut.h +++ b/drivers/media/platform/vsp1/vsp1_lut.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1_lut.h -- R-Car VSP1 Look-Up Table * * Copyright (C) 2013 Renesas Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_LUT_H__ #define __VSP1_LUT_H__ @@ -33,6 +29,7 @@ struct vsp1_lut { spinlock_t lock; struct vsp1_dl_body *lut; + struct vsp1_dl_body_pool *pool; }; static inline struct vsp1_lut *to_lut(struct v4l2_subdev *subdev) diff --git a/drivers/media/platform/vsp1/vsp1_pipe.c b/drivers/media/platform/vsp1/vsp1_pipe.c index 44944ac86d9b..54ff539ffea0 100644 --- a/drivers/media/platform/vsp1/vsp1_pipe.c +++ b/drivers/media/platform/vsp1/vsp1_pipe.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_pipe.c -- R-Car VSP1 Pipeline * * Copyright (C) 2013-2015 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/delay.h> @@ -20,7 +16,7 @@ #include <media/v4l2-subdev.h> #include "vsp1.h" -#include "vsp1_bru.h" +#include "vsp1_brx.h" #include "vsp1_dl.h" #include "vsp1_entity.h" #include "vsp1_hgo.h" @@ -185,44 +181,29 @@ const struct vsp1_format_info *vsp1_get_format_info(struct vsp1_device *vsp1, void vsp1_pipeline_reset(struct vsp1_pipeline *pipe) { + struct vsp1_entity *entity; unsigned int i; - if (pipe->bru) { - struct vsp1_bru *bru = to_bru(&pipe->bru->subdev); - - for (i = 0; i < ARRAY_SIZE(bru->inputs); ++i) - bru->inputs[i].rpf = NULL; - } - - for (i = 0; i < ARRAY_SIZE(pipe->inputs); ++i) { - if (pipe->inputs[i]) { - pipe->inputs[i]->pipe = NULL; - pipe->inputs[i] = NULL; - } - } + if (pipe->brx) { + struct vsp1_brx *brx = to_brx(&pipe->brx->subdev); - if (pipe->output) { - pipe->output->pipe = NULL; - pipe->output = NULL; + for (i = 0; i < ARRAY_SIZE(brx->inputs); ++i) + brx->inputs[i].rpf = NULL; } - if (pipe->hgo) { - struct vsp1_hgo *hgo = to_hgo(&pipe->hgo->subdev); - - hgo->histo.pipe = NULL; - } + for (i = 0; i < ARRAY_SIZE(pipe->inputs); ++i) + pipe->inputs[i] = NULL; - if (pipe->hgt) { - struct vsp1_hgt *hgt = to_hgt(&pipe->hgt->subdev); + pipe->output = NULL; - hgt->histo.pipe = NULL; - } + list_for_each_entry(entity, &pipe->entities, list_pipe) + entity->pipe = NULL; INIT_LIST_HEAD(&pipe->entities); pipe->state = VSP1_PIPELINE_STOPPED; pipe->buffers_ready = 0; pipe->num_inputs = 0; - pipe->bru = NULL; + pipe->brx = NULL; pipe->hgo = NULL; pipe->hgt = NULL; pipe->lif = NULL; @@ -330,17 +311,17 @@ bool vsp1_pipeline_ready(struct vsp1_pipeline *pipe) void vsp1_pipeline_frame_end(struct vsp1_pipeline *pipe) { - bool completed; + unsigned int flags; if (pipe == NULL) return; /* * If the DL commit raced with the frame end interrupt, the commit ends - * up being postponed by one frame. @completed represents whether the + * up being postponed by one frame. The returned flags tell whether the * active frame was finished or postponed. */ - completed = vsp1_dlm_irq_frame_end(pipe->output->dlm); + flags = vsp1_dlm_irq_frame_end(pipe->output->dlm); if (pipe->hgo) vsp1_hgo_frame_end(pipe->hgo); @@ -353,7 +334,7 @@ void vsp1_pipeline_frame_end(struct vsp1_pipeline *pipe) * frame_end to account for vblank events. */ if (pipe->frame_end) - pipe->frame_end(pipe, completed); + pipe->frame_end(pipe, flags); pipe->sequence++; } @@ -367,7 +348,7 @@ void vsp1_pipeline_frame_end(struct vsp1_pipeline *pipe) * from the input RPF alpha. */ void vsp1_pipeline_propagate_alpha(struct vsp1_pipeline *pipe, - struct vsp1_dl_list *dl, unsigned int alpha) + struct vsp1_dl_body *dlb, unsigned int alpha) { if (!pipe->uds) return; @@ -380,7 +361,7 @@ void vsp1_pipeline_propagate_alpha(struct vsp1_pipeline *pipe, pipe->uds_input->type == VSP1_ENTITY_BRS) alpha = 255; - vsp1_uds_set_alpha(pipe->uds, dl, alpha); + vsp1_uds_set_alpha(pipe->uds, dlb, alpha); } /* @@ -405,73 +386,3 @@ void vsp1_pipeline_propagate_partition(struct vsp1_pipeline *pipe, } } -void vsp1_pipelines_suspend(struct vsp1_device *vsp1) -{ - unsigned long flags; - unsigned int i; - int ret; - - /* - * To avoid increasing the system suspend time needlessly, loop over the - * pipelines twice, first to set them all to the stopping state, and - * then to wait for the stop to complete. - */ - for (i = 0; i < vsp1->info->wpf_count; ++i) { - struct vsp1_rwpf *wpf = vsp1->wpf[i]; - struct vsp1_pipeline *pipe; - - if (wpf == NULL) - continue; - - pipe = wpf->pipe; - if (pipe == NULL) - continue; - - spin_lock_irqsave(&pipe->irqlock, flags); - if (pipe->state == VSP1_PIPELINE_RUNNING) - pipe->state = VSP1_PIPELINE_STOPPING; - spin_unlock_irqrestore(&pipe->irqlock, flags); - } - - for (i = 0; i < vsp1->info->wpf_count; ++i) { - struct vsp1_rwpf *wpf = vsp1->wpf[i]; - struct vsp1_pipeline *pipe; - - if (wpf == NULL) - continue; - - pipe = wpf->pipe; - if (pipe == NULL) - continue; - - ret = wait_event_timeout(pipe->wq, vsp1_pipeline_stopped(pipe), - msecs_to_jiffies(500)); - if (ret == 0) - dev_warn(vsp1->dev, "pipeline %u stop timeout\n", - wpf->entity.index); - } -} - -void vsp1_pipelines_resume(struct vsp1_device *vsp1) -{ - unsigned long flags; - unsigned int i; - - /* Resume all running pipelines. */ - for (i = 0; i < vsp1->info->wpf_count; ++i) { - struct vsp1_rwpf *wpf = vsp1->wpf[i]; - struct vsp1_pipeline *pipe; - - if (wpf == NULL) - continue; - - pipe = wpf->pipe; - if (pipe == NULL) - continue; - - spin_lock_irqsave(&pipe->irqlock, flags); - if (vsp1_pipeline_ready(pipe)) - vsp1_pipeline_run(pipe); - spin_unlock_irqrestore(&pipe->irqlock, flags); - } -} diff --git a/drivers/media/platform/vsp1/vsp1_pipe.h b/drivers/media/platform/vsp1/vsp1_pipe.h index dfff9b5685fe..743d8f0db45c 100644 --- a/drivers/media/platform/vsp1/vsp1_pipe.h +++ b/drivers/media/platform/vsp1/vsp1_pipe.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1_pipe.h -- R-Car VSP1 Pipeline * * Copyright (C) 2013-2015 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_PIPE_H__ #define __VSP1_PIPE_H__ @@ -99,14 +95,15 @@ struct vsp1_partition { * @num_inputs: number of RPFs * @inputs: array of RPFs in the pipeline (indexed by RPF index) * @output: WPF at the output of the pipeline - * @bru: BRU entity, if present + * @brx: BRx entity, if present * @hgo: HGO entity, if present * @hgt: HGT entity, if present * @lif: LIF entity, if present * @uds: UDS entity, if present * @uds_input: entity at the input of the UDS, if the UDS is present * @entities: list of entities in the pipeline - * @dl: display list associated with the pipeline + * @stream_config: cached stream configuration for video pipelines + * @configured: when false the @stream_config shall be written to the hardware * @partitions: The number of partitions used to process one frame * @partition: The current partition for configuration to process * @part_table: The pre-calculated partitions used by the pipeline @@ -118,7 +115,7 @@ struct vsp1_pipeline { enum vsp1_pipeline_state state; wait_queue_head_t wq; - void (*frame_end)(struct vsp1_pipeline *pipe, bool completed); + void (*frame_end)(struct vsp1_pipeline *pipe, unsigned int completion); struct mutex lock; struct kref kref; @@ -129,7 +126,7 @@ struct vsp1_pipeline { unsigned int num_inputs; struct vsp1_rwpf *inputs[VSP1_MAX_RPF]; struct vsp1_rwpf *output; - struct vsp1_entity *bru; + struct vsp1_entity *brx; struct vsp1_entity *hgo; struct vsp1_entity *hgt; struct vsp1_entity *lif; @@ -143,7 +140,8 @@ struct vsp1_pipeline { */ struct list_head entities; - struct vsp1_dl_list *dl; + struct vsp1_dl_body *stream_config; + bool configured; unsigned int partitions; struct vsp1_partition *partition; @@ -161,16 +159,14 @@ bool vsp1_pipeline_ready(struct vsp1_pipeline *pipe); void vsp1_pipeline_frame_end(struct vsp1_pipeline *pipe); void vsp1_pipeline_propagate_alpha(struct vsp1_pipeline *pipe, - struct vsp1_dl_list *dl, unsigned int alpha); + struct vsp1_dl_body *dlb, + unsigned int alpha); void vsp1_pipeline_propagate_partition(struct vsp1_pipeline *pipe, struct vsp1_partition *partition, unsigned int index, struct vsp1_partition_window *window); -void vsp1_pipelines_suspend(struct vsp1_device *vsp1); -void vsp1_pipelines_resume(struct vsp1_device *vsp1); - const struct vsp1_format_info *vsp1_get_format_info(struct vsp1_device *vsp1, u32 fourcc); diff --git a/drivers/media/platform/vsp1/vsp1_regs.h b/drivers/media/platform/vsp1/vsp1_regs.h index dae0c1901297..0d249ff9f564 100644 --- a/drivers/media/platform/vsp1/vsp1_regs.h +++ b/drivers/media/platform/vsp1/vsp1_regs.h @@ -1,13 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * vsp1_regs.h -- R-Car VSP1 Registers Definitions * * Copyright (C) 2013 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. */ #ifndef __VSP1_REGS_H__ @@ -311,6 +308,44 @@ #define VI6_WPF_WRBCK_CTRL_WBMD (1 << 0) /* ----------------------------------------------------------------------------- + * UIF Control Registers + */ + +#define VI6_UIF_OFFSET 0x100 + +#define VI6_UIF_DISCOM_DOCMCR 0x1c00 +#define VI6_UIF_DISCOM_DOCMCR_CMPRU (1 << 16) +#define VI6_UIF_DISCOM_DOCMCR_CMPR (1 << 0) + +#define VI6_UIF_DISCOM_DOCMSTR 0x1c04 +#define VI6_UIF_DISCOM_DOCMSTR_CMPPRE (1 << 1) +#define VI6_UIF_DISCOM_DOCMSTR_CMPST (1 << 0) + +#define VI6_UIF_DISCOM_DOCMCLSTR 0x1c08 +#define VI6_UIF_DISCOM_DOCMCLSTR_CMPCLPRE (1 << 1) +#define VI6_UIF_DISCOM_DOCMCLSTR_CMPCLST (1 << 0) + +#define VI6_UIF_DISCOM_DOCMIENR 0x1c0c +#define VI6_UIF_DISCOM_DOCMIENR_CMPPREIEN (1 << 1) +#define VI6_UIF_DISCOM_DOCMIENR_CMPIEN (1 << 0) + +#define VI6_UIF_DISCOM_DOCMMDR 0x1c10 +#define VI6_UIF_DISCOM_DOCMMDR_INTHRH(n) ((n) << 16) + +#define VI6_UIF_DISCOM_DOCMPMR 0x1c14 +#define VI6_UIF_DISCOM_DOCMPMR_CMPDFF(n) ((n) << 17) +#define VI6_UIF_DISCOM_DOCMPMR_CMPDFA(n) ((n) << 8) +#define VI6_UIF_DISCOM_DOCMPMR_CMPDAUF (1 << 7) +#define VI6_UIF_DISCOM_DOCMPMR_SEL(n) ((n) << 0) + +#define VI6_UIF_DISCOM_DOCMECRCR 0x1c18 +#define VI6_UIF_DISCOM_DOCMCCRCR 0x1c1c +#define VI6_UIF_DISCOM_DOCMSPXR 0x1c20 +#define VI6_UIF_DISCOM_DOCMSPYR 0x1c24 +#define VI6_UIF_DISCOM_DOCMSZXR 0x1c28 +#define VI6_UIF_DISCOM_DOCMSZYR 0x1c2c + +/* ----------------------------------------------------------------------------- * DPR Control Registers */ @@ -342,7 +377,10 @@ #define VI6_DPR_SMPPT_PT_MASK (0x3f << 0) #define VI6_DPR_SMPPT_PT_SHIFT 0 +#define VI6_DPR_UIF_ROUTE(n) (0x2074 + (n) * 4) + #define VI6_DPR_NODE_RPF(n) (n) +#define VI6_DPR_NODE_UIF(n) (12 + (n)) #define VI6_DPR_NODE_SRU 16 #define VI6_DPR_NODE_UDS(n) (17 + (n)) #define VI6_DPR_NODE_LUT 22 diff --git a/drivers/media/platform/vsp1/vsp1_rpf.c b/drivers/media/platform/vsp1/vsp1_rpf.c index fe0633da5a5f..69e5fe6e6b50 100644 --- a/drivers/media/platform/vsp1/vsp1_rpf.c +++ b/drivers/media/platform/vsp1/vsp1_rpf.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_rpf.c -- R-Car VSP1 Read Pixel Formatter * * Copyright (C) 2013-2014 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/device.h> @@ -29,9 +25,10 @@ */ static inline void vsp1_rpf_write(struct vsp1_rwpf *rpf, - struct vsp1_dl_list *dl, u32 reg, u32 data) + struct vsp1_dl_body *dlb, u32 reg, u32 data) { - vsp1_dl_list_write(dl, reg + rpf->entity.index * VI6_RPF_OFFSET, data); + vsp1_dl_body_write(dlb, reg + rpf->entity.index * VI6_RPF_OFFSET, + data); } /* ----------------------------------------------------------------------------- @@ -46,10 +43,9 @@ static const struct v4l2_subdev_ops rpf_ops = { * VSP1 Entity Operations */ -static void rpf_configure(struct vsp1_entity *entity, - struct vsp1_pipeline *pipe, - struct vsp1_dl_list *dl, - enum vsp1_entity_params params) +static void rpf_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb) { struct vsp1_rwpf *rpf = to_rwpf(&entity->subdev); const struct vsp1_format_info *fmtinfo = rpf->fmtinfo; @@ -61,80 +57,6 @@ static void rpf_configure(struct vsp1_entity *entity, u32 pstride; u32 infmt; - if (params == VSP1_ENTITY_PARAMS_RUNTIME) { - vsp1_rpf_write(rpf, dl, VI6_RPF_VRTCOL_SET, - rpf->alpha << VI6_RPF_VRTCOL_SET_LAYA_SHIFT); - vsp1_rpf_write(rpf, dl, VI6_RPF_MULT_ALPHA, rpf->mult_alpha | - (rpf->alpha << VI6_RPF_MULT_ALPHA_RATIO_SHIFT)); - - vsp1_pipeline_propagate_alpha(pipe, dl, rpf->alpha); - return; - } - - if (params == VSP1_ENTITY_PARAMS_PARTITION) { - struct vsp1_device *vsp1 = rpf->entity.vsp1; - struct vsp1_rwpf_memory mem = rpf->mem; - struct v4l2_rect crop; - - /* - * Source size and crop offsets. - * - * The crop offsets correspond to the location of the crop - * rectangle top left corner in the plane buffer. Only two - * offsets are needed, as planes 2 and 3 always have identical - * strides. - */ - crop = *vsp1_rwpf_get_crop(rpf, rpf->entity.config); - - /* - * Partition Algorithm Control - * - * The partition algorithm can split this frame into multiple - * slices. We must scale our partition window based on the pipe - * configuration to match the destination partition window. - * To achieve this, we adjust our crop to provide a 'sub-crop' - * matching the expected partition window. Only 'left' and - * 'width' need to be adjusted. - */ - if (pipe->partitions > 1) { - crop.width = pipe->partition->rpf.width; - crop.left += pipe->partition->rpf.left; - } - - vsp1_rpf_write(rpf, dl, VI6_RPF_SRC_BSIZE, - (crop.width << VI6_RPF_SRC_BSIZE_BHSIZE_SHIFT) | - (crop.height << VI6_RPF_SRC_BSIZE_BVSIZE_SHIFT)); - vsp1_rpf_write(rpf, dl, VI6_RPF_SRC_ESIZE, - (crop.width << VI6_RPF_SRC_ESIZE_EHSIZE_SHIFT) | - (crop.height << VI6_RPF_SRC_ESIZE_EVSIZE_SHIFT)); - - mem.addr[0] += crop.top * format->plane_fmt[0].bytesperline - + crop.left * fmtinfo->bpp[0] / 8; - - if (format->num_planes > 1) { - unsigned int offset; - - offset = crop.top * format->plane_fmt[1].bytesperline - + crop.left / fmtinfo->hsub - * fmtinfo->bpp[1] / 8; - mem.addr[1] += offset; - mem.addr[2] += offset; - } - - /* - * On Gen3 hardware the SPUVS bit has no effect on 3-planar - * formats. Swap the U and V planes manually in that case. - */ - if (vsp1->info->gen == 3 && format->num_planes == 3 && - fmtinfo->swap_uv) - swap(mem.addr[1], mem.addr[2]); - - vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_Y, mem.addr[0]); - vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_C0, mem.addr[1]); - vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_C1, mem.addr[2]); - return; - } - /* Stride */ pstride = format->plane_fmt[0].bytesperline << VI6_RPF_SRCM_PSTRIDE_Y_SHIFT; @@ -142,7 +64,7 @@ static void rpf_configure(struct vsp1_entity *entity, pstride |= format->plane_fmt[1].bytesperline << VI6_RPF_SRCM_PSTRIDE_C_SHIFT; - vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_PSTRIDE, pstride); + vsp1_rpf_write(rpf, dlb, VI6_RPF_SRCM_PSTRIDE, pstride); /* Format */ sink_format = vsp1_entity_get_pad_format(&rpf->entity, @@ -163,22 +85,22 @@ static void rpf_configure(struct vsp1_entity *entity, if (sink_format->code != source_format->code) infmt |= VI6_RPF_INFMT_CSC; - vsp1_rpf_write(rpf, dl, VI6_RPF_INFMT, infmt); - vsp1_rpf_write(rpf, dl, VI6_RPF_DSWAP, fmtinfo->swap); + vsp1_rpf_write(rpf, dlb, VI6_RPF_INFMT, infmt); + vsp1_rpf_write(rpf, dlb, VI6_RPF_DSWAP, fmtinfo->swap); /* Output location */ - if (pipe->bru) { + if (pipe->brx) { const struct v4l2_rect *compose; - compose = vsp1_entity_get_pad_selection(pipe->bru, - pipe->bru->config, - rpf->bru_input, + compose = vsp1_entity_get_pad_selection(pipe->brx, + pipe->brx->config, + rpf->brx_input, V4L2_SEL_TGT_COMPOSE); left = compose->left; top = compose->top; } - vsp1_rpf_write(rpf, dl, VI6_RPF_LOC, + vsp1_rpf_write(rpf, dlb, VI6_RPF_LOC, (left << VI6_RPF_LOC_HCOORD_SHIFT) | (top << VI6_RPF_LOC_VCOORD_SHIFT)); @@ -191,10 +113,10 @@ static void rpf_configure(struct vsp1_entity *entity, * alpha channel by a fixed global alpha value, and multiply the pixel * components to convert the input to premultiplied alpha. * - * As alpha premultiplication is available in the BRU for both Gen2 and + * As alpha premultiplication is available in the BRx for both Gen2 and * Gen3 we handle it there and use the Gen3 alpha multiplier for global * alpha multiplication only. This however prevents conversion to - * premultiplied alpha if no BRU is present in the pipeline. If that use + * premultiplied alpha if no BRx is present in the pipeline. If that use * case turns out to be useful we will revisit the implementation (for * Gen3 only). * @@ -205,7 +127,7 @@ static void rpf_configure(struct vsp1_entity *entity, * * In all cases, disable color keying. */ - vsp1_rpf_write(rpf, dl, VI6_RPF_ALPH_SEL, VI6_RPF_ALPH_SEL_AEXT_EXT | + vsp1_rpf_write(rpf, dlb, VI6_RPF_ALPH_SEL, VI6_RPF_ALPH_SEL_AEXT_EXT | (fmtinfo->alpha ? VI6_RPF_ALPH_SEL_ASEL_PACKED : VI6_RPF_ALPH_SEL_ASEL_FIXED)); @@ -242,9 +164,94 @@ static void rpf_configure(struct vsp1_entity *entity, rpf->mult_alpha = mult; } - vsp1_rpf_write(rpf, dl, VI6_RPF_MSK_CTRL, 0); - vsp1_rpf_write(rpf, dl, VI6_RPF_CKEY_CTRL, 0); + vsp1_rpf_write(rpf, dlb, VI6_RPF_MSK_CTRL, 0); + vsp1_rpf_write(rpf, dlb, VI6_RPF_CKEY_CTRL, 0); + +} + +static void rpf_configure_frame(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_rwpf *rpf = to_rwpf(&entity->subdev); + + vsp1_rpf_write(rpf, dlb, VI6_RPF_VRTCOL_SET, + rpf->alpha << VI6_RPF_VRTCOL_SET_LAYA_SHIFT); + vsp1_rpf_write(rpf, dlb, VI6_RPF_MULT_ALPHA, rpf->mult_alpha | + (rpf->alpha << VI6_RPF_MULT_ALPHA_RATIO_SHIFT)); + + vsp1_pipeline_propagate_alpha(pipe, dlb, rpf->alpha); +} + +static void rpf_configure_partition(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_rwpf *rpf = to_rwpf(&entity->subdev); + struct vsp1_rwpf_memory mem = rpf->mem; + struct vsp1_device *vsp1 = rpf->entity.vsp1; + const struct vsp1_format_info *fmtinfo = rpf->fmtinfo; + const struct v4l2_pix_format_mplane *format = &rpf->format; + struct v4l2_rect crop; + + /* + * Source size and crop offsets. + * + * The crop offsets correspond to the location of the crop + * rectangle top left corner in the plane buffer. Only two + * offsets are needed, as planes 2 and 3 always have identical + * strides. + */ + crop = *vsp1_rwpf_get_crop(rpf, rpf->entity.config); + + /* + * Partition Algorithm Control + * + * The partition algorithm can split this frame into multiple + * slices. We must scale our partition window based on the pipe + * configuration to match the destination partition window. + * To achieve this, we adjust our crop to provide a 'sub-crop' + * matching the expected partition window. Only 'left' and + * 'width' need to be adjusted. + */ + if (pipe->partitions > 1) { + crop.width = pipe->partition->rpf.width; + crop.left += pipe->partition->rpf.left; + } + + vsp1_rpf_write(rpf, dlb, VI6_RPF_SRC_BSIZE, + (crop.width << VI6_RPF_SRC_BSIZE_BHSIZE_SHIFT) | + (crop.height << VI6_RPF_SRC_BSIZE_BVSIZE_SHIFT)); + vsp1_rpf_write(rpf, dlb, VI6_RPF_SRC_ESIZE, + (crop.width << VI6_RPF_SRC_ESIZE_EHSIZE_SHIFT) | + (crop.height << VI6_RPF_SRC_ESIZE_EVSIZE_SHIFT)); + + mem.addr[0] += crop.top * format->plane_fmt[0].bytesperline + + crop.left * fmtinfo->bpp[0] / 8; + + if (format->num_planes > 1) { + unsigned int offset; + + offset = crop.top * format->plane_fmt[1].bytesperline + + crop.left / fmtinfo->hsub + * fmtinfo->bpp[1] / 8; + mem.addr[1] += offset; + mem.addr[2] += offset; + } + + /* + * On Gen3 hardware the SPUVS bit has no effect on 3-planar + * formats. Swap the U and V planes manually in that case. + */ + if (vsp1->info->gen == 3 && format->num_planes == 3 && + fmtinfo->swap_uv) + swap(mem.addr[1], mem.addr[2]); + vsp1_rpf_write(rpf, dlb, VI6_RPF_SRCM_ADDR_Y, mem.addr[0]); + vsp1_rpf_write(rpf, dlb, VI6_RPF_SRCM_ADDR_C0, mem.addr[1]); + vsp1_rpf_write(rpf, dlb, VI6_RPF_SRCM_ADDR_C1, mem.addr[2]); } static void rpf_partition(struct vsp1_entity *entity, @@ -257,7 +264,9 @@ static void rpf_partition(struct vsp1_entity *entity, } static const struct vsp1_entity_operations rpf_entity_ops = { - .configure = rpf_configure, + .configure_stream = rpf_configure_stream, + .configure_frame = rpf_configure_frame, + .configure_partition = rpf_configure_partition, .partition = rpf_partition, }; diff --git a/drivers/media/platform/vsp1/vsp1_rwpf.c b/drivers/media/platform/vsp1/vsp1_rwpf.c index cfd8f1904fa6..049bdd958e56 100644 --- a/drivers/media/platform/vsp1/vsp1_rwpf.c +++ b/drivers/media/platform/vsp1/vsp1_rwpf.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_rwpf.c -- R-Car VSP1 Read and Write Pixel Formatters * * Copyright (C) 2013-2014 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 <media/v4l2-subdev.h> diff --git a/drivers/media/platform/vsp1/vsp1_rwpf.h b/drivers/media/platform/vsp1/vsp1_rwpf.h index 58215a7ab631..70742ecf766f 100644 --- a/drivers/media/platform/vsp1/vsp1_rwpf.h +++ b/drivers/media/platform/vsp1/vsp1_rwpf.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1_rwpf.h -- R-Car VSP1 Read and Write Pixel Formatters * * Copyright (C) 2013-2014 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_RWPF_H__ #define __VSP1_RWPF_H__ @@ -27,7 +23,6 @@ struct v4l2_ctrl; struct vsp1_dl_manager; -struct vsp1_pipeline; struct vsp1_rwpf; struct vsp1_video; @@ -39,7 +34,6 @@ struct vsp1_rwpf { struct vsp1_entity entity; struct v4l2_ctrl_handler ctrls; - struct vsp1_pipeline *pipe; struct vsp1_video *video; unsigned int max_width; @@ -47,7 +41,7 @@ struct vsp1_rwpf { struct v4l2_pix_format_mplane format; const struct vsp1_format_info *fmtinfo; - unsigned int bru_input; + unsigned int brx_input; unsigned int alpha; diff --git a/drivers/media/platform/vsp1/vsp1_sru.c b/drivers/media/platform/vsp1/vsp1_sru.c index 51e5691187c3..04e4e05af6ae 100644 --- a/drivers/media/platform/vsp1/vsp1_sru.c +++ b/drivers/media/platform/vsp1/vsp1_sru.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_sru.c -- R-Car VSP1 Super Resolution Unit * * Copyright (C) 2013 Renesas Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/device.h> @@ -28,10 +24,10 @@ * Device Access */ -static inline void vsp1_sru_write(struct vsp1_sru *sru, struct vsp1_dl_list *dl, - u32 reg, u32 data) +static inline void vsp1_sru_write(struct vsp1_sru *sru, + struct vsp1_dl_body *dlb, u32 reg, u32 data) { - vsp1_dl_list_write(dl, reg, data); + vsp1_dl_body_write(dlb, reg, data); } /* ----------------------------------------------------------------------------- @@ -271,10 +267,9 @@ static const struct v4l2_subdev_ops sru_ops = { * VSP1 Entity Operations */ -static void sru_configure(struct vsp1_entity *entity, - struct vsp1_pipeline *pipe, - struct vsp1_dl_list *dl, - enum vsp1_entity_params params) +static void sru_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb) { const struct vsp1_sru_param *param; struct vsp1_sru *sru = to_sru(&entity->subdev); @@ -282,9 +277,6 @@ static void sru_configure(struct vsp1_entity *entity, struct v4l2_mbus_framefmt *output; u32 ctrl0; - if (params != VSP1_ENTITY_PARAMS_INIT) - return; - input = vsp1_entity_get_pad_format(&sru->entity, sru->entity.config, SRU_PAD_SINK); output = vsp1_entity_get_pad_format(&sru->entity, sru->entity.config, @@ -303,9 +295,9 @@ static void sru_configure(struct vsp1_entity *entity, ctrl0 |= param->ctrl0; - vsp1_sru_write(sru, dl, VI6_SRU_CTRL0, ctrl0); - vsp1_sru_write(sru, dl, VI6_SRU_CTRL1, VI6_SRU_CTRL1_PARAM5); - vsp1_sru_write(sru, dl, VI6_SRU_CTRL2, param->ctrl2); + vsp1_sru_write(sru, dlb, VI6_SRU_CTRL0, ctrl0); + vsp1_sru_write(sru, dlb, VI6_SRU_CTRL1, VI6_SRU_CTRL1_PARAM5); + vsp1_sru_write(sru, dlb, VI6_SRU_CTRL2, param->ctrl2); } static unsigned int sru_max_width(struct vsp1_entity *entity, @@ -351,7 +343,7 @@ static void sru_partition(struct vsp1_entity *entity, } static const struct vsp1_entity_operations sru_entity_ops = { - .configure = sru_configure, + .configure_stream = sru_configure_stream, .max_width = sru_max_width, .partition = sru_partition, }; diff --git a/drivers/media/platform/vsp1/vsp1_sru.h b/drivers/media/platform/vsp1/vsp1_sru.h index 85e241457af2..ddb00eadd1ea 100644 --- a/drivers/media/platform/vsp1/vsp1_sru.h +++ b/drivers/media/platform/vsp1/vsp1_sru.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1_sru.h -- R-Car VSP1 Super Resolution Unit * * Copyright (C) 2013 Renesas Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_SRU_H__ #define __VSP1_SRU_H__ diff --git a/drivers/media/platform/vsp1/vsp1_uds.c b/drivers/media/platform/vsp1/vsp1_uds.c index 72f72a9d2152..c20c84b54936 100644 --- a/drivers/media/platform/vsp1/vsp1_uds.c +++ b/drivers/media/platform/vsp1/vsp1_uds.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_uds.c -- R-Car VSP1 Up and Down Scaler * * Copyright (C) 2013-2014 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/device.h> @@ -31,22 +27,22 @@ * Device Access */ -static inline void vsp1_uds_write(struct vsp1_uds *uds, struct vsp1_dl_list *dl, - u32 reg, u32 data) +static inline void vsp1_uds_write(struct vsp1_uds *uds, + struct vsp1_dl_body *dlb, u32 reg, u32 data) { - vsp1_dl_list_write(dl, reg + uds->entity.index * VI6_UDS_OFFSET, data); + vsp1_dl_body_write(dlb, reg + uds->entity.index * VI6_UDS_OFFSET, data); } /* ----------------------------------------------------------------------------- * Scaling Computation */ -void vsp1_uds_set_alpha(struct vsp1_entity *entity, struct vsp1_dl_list *dl, +void vsp1_uds_set_alpha(struct vsp1_entity *entity, struct vsp1_dl_body *dlb, unsigned int alpha) { struct vsp1_uds *uds = to_uds(&entity->subdev); - vsp1_uds_write(uds, dl, VI6_UDS_ALPVAL, + vsp1_uds_write(uds, dlb, VI6_UDS_ALPVAL, alpha << VI6_UDS_ALPVAL_VAL0_SHIFT); } @@ -259,10 +255,9 @@ static const struct v4l2_subdev_ops uds_ops = { * VSP1 Entity Operations */ -static void uds_configure(struct vsp1_entity *entity, - struct vsp1_pipeline *pipe, - struct vsp1_dl_list *dl, - enum vsp1_entity_params params) +static void uds_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb) { struct vsp1_uds *uds = to_uds(&entity->subdev); const struct v4l2_mbus_framefmt *output; @@ -276,27 +271,6 @@ static void uds_configure(struct vsp1_entity *entity, output = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, UDS_PAD_SOURCE); - if (params == VSP1_ENTITY_PARAMS_PARTITION) { - struct vsp1_partition *partition = pipe->partition; - - /* Input size clipping */ - vsp1_uds_write(uds, dl, VI6_UDS_HSZCLIP, VI6_UDS_HSZCLIP_HCEN | - (0 << VI6_UDS_HSZCLIP_HCL_OFST_SHIFT) | - (partition->uds_sink.width - << VI6_UDS_HSZCLIP_HCL_SIZE_SHIFT)); - - /* Output size clipping */ - vsp1_uds_write(uds, dl, VI6_UDS_CLIP_SIZE, - (partition->uds_source.width - << VI6_UDS_CLIP_SIZE_HSIZE_SHIFT) | - (output->height - << VI6_UDS_CLIP_SIZE_VSIZE_SHIFT)); - return; - } - - if (params != VSP1_ENTITY_PARAMS_INIT) - return; - hscale = uds_compute_ratio(input->width, output->width); vscale = uds_compute_ratio(input->height, output->height); @@ -312,22 +286,48 @@ static void uds_configure(struct vsp1_entity *entity, else multitap = true; - vsp1_uds_write(uds, dl, VI6_UDS_CTRL, + vsp1_uds_write(uds, dlb, VI6_UDS_CTRL, (uds->scale_alpha ? VI6_UDS_CTRL_AON : 0) | (multitap ? VI6_UDS_CTRL_BC : 0)); - vsp1_uds_write(uds, dl, VI6_UDS_PASS_BWIDTH, + vsp1_uds_write(uds, dlb, VI6_UDS_PASS_BWIDTH, (uds_passband_width(hscale) << VI6_UDS_PASS_BWIDTH_H_SHIFT) | (uds_passband_width(vscale) << VI6_UDS_PASS_BWIDTH_V_SHIFT)); /* Set the scaling ratios. */ - vsp1_uds_write(uds, dl, VI6_UDS_SCALE, + vsp1_uds_write(uds, dlb, VI6_UDS_SCALE, (hscale << VI6_UDS_SCALE_HFRAC_SHIFT) | (vscale << VI6_UDS_SCALE_VFRAC_SHIFT)); } +static void uds_configure_partition(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_uds *uds = to_uds(&entity->subdev); + struct vsp1_partition *partition = pipe->partition; + const struct v4l2_mbus_framefmt *output; + + output = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, + UDS_PAD_SOURCE); + + /* Input size clipping */ + vsp1_uds_write(uds, dlb, VI6_UDS_HSZCLIP, VI6_UDS_HSZCLIP_HCEN | + (0 << VI6_UDS_HSZCLIP_HCL_OFST_SHIFT) | + (partition->uds_sink.width + << VI6_UDS_HSZCLIP_HCL_SIZE_SHIFT)); + + /* Output size clipping */ + vsp1_uds_write(uds, dlb, VI6_UDS_CLIP_SIZE, + (partition->uds_source.width + << VI6_UDS_CLIP_SIZE_HSIZE_SHIFT) | + (output->height + << VI6_UDS_CLIP_SIZE_VSIZE_SHIFT)); +} + static unsigned int uds_max_width(struct vsp1_entity *entity, struct vsp1_pipeline *pipe) { @@ -384,7 +384,8 @@ static void uds_partition(struct vsp1_entity *entity, } static const struct vsp1_entity_operations uds_entity_ops = { - .configure = uds_configure, + .configure_stream = uds_configure_stream, + .configure_partition = uds_configure_partition, .max_width = uds_max_width, .partition = uds_partition, }; diff --git a/drivers/media/platform/vsp1/vsp1_uds.h b/drivers/media/platform/vsp1/vsp1_uds.h index 7bf3cdcffc65..c34f95a666d2 100644 --- a/drivers/media/platform/vsp1/vsp1_uds.h +++ b/drivers/media/platform/vsp1/vsp1_uds.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1_uds.h -- R-Car VSP1 Up and Down Scaler * * Copyright (C) 2013-2014 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_UDS_H__ #define __VSP1_UDS_H__ @@ -35,7 +31,7 @@ static inline struct vsp1_uds *to_uds(struct v4l2_subdev *subdev) struct vsp1_uds *vsp1_uds_create(struct vsp1_device *vsp1, unsigned int index); -void vsp1_uds_set_alpha(struct vsp1_entity *uds, struct vsp1_dl_list *dl, +void vsp1_uds_set_alpha(struct vsp1_entity *uds, struct vsp1_dl_body *dlb, unsigned int alpha); #endif /* __VSP1_UDS_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_uif.c b/drivers/media/platform/vsp1/vsp1_uif.c new file mode 100644 index 000000000000..4b58d51df231 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_uif.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_uif.c -- R-Car VSP1 User Logic Interface + * + * Copyright (C) 2017-2018 Laurent Pinchart + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include <linux/device.h> +#include <linux/gfp.h> +#include <linux/sys_soc.h> + +#include <media/media-entity.h> +#include <media/v4l2-subdev.h> + +#include "vsp1.h" +#include "vsp1_dl.h" +#include "vsp1_entity.h" +#include "vsp1_uif.h" + +#define UIF_MIN_SIZE 4U +#define UIF_MAX_SIZE 8190U + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_uif_read(struct vsp1_uif *uif, u32 reg) +{ + return vsp1_read(uif->entity.vsp1, + uif->entity.index * VI6_UIF_OFFSET + reg); +} + +static inline void vsp1_uif_write(struct vsp1_uif *uif, + struct vsp1_dl_body *dlb, u32 reg, u32 data) +{ + vsp1_dl_body_write(dlb, reg + uif->entity.index * VI6_UIF_OFFSET, data); +} + +u32 vsp1_uif_get_crc(struct vsp1_uif *uif) +{ + return vsp1_uif_read(uif, VI6_UIF_DISCOM_DOCMCCRCR); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +static const unsigned int uif_codes[] = { + MEDIA_BUS_FMT_ARGB8888_1X32, + MEDIA_BUS_FMT_AHSV8888_1X32, + MEDIA_BUS_FMT_AYUV8_1X32, +}; + +static int uif_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + return vsp1_subdev_enum_mbus_code(subdev, cfg, code, uif_codes, + ARRAY_SIZE(uif_codes)); +} + +static int uif_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + return vsp1_subdev_enum_frame_size(subdev, cfg, fse, UIF_MIN_SIZE, + UIF_MIN_SIZE, UIF_MAX_SIZE, + UIF_MAX_SIZE); +} + +static int uif_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + return vsp1_subdev_set_pad_format(subdev, cfg, fmt, uif_codes, + ARRAY_SIZE(uif_codes), + UIF_MIN_SIZE, UIF_MIN_SIZE, + UIF_MAX_SIZE, UIF_MAX_SIZE); +} + +static int uif_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_uif *uif = to_uif(subdev); + struct v4l2_subdev_pad_config *config; + struct v4l2_mbus_framefmt *format; + int ret = 0; + + if (sel->pad != UIF_PAD_SINK) + return -EINVAL; + + mutex_lock(&uif->entity.lock); + + config = vsp1_entity_get_pad_config(&uif->entity, cfg, sel->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + format = vsp1_entity_get_pad_format(&uif->entity, config, + UIF_PAD_SINK); + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = format->width; + sel->r.height = format->height; + break; + + case V4L2_SEL_TGT_CROP: + sel->r = *vsp1_entity_get_pad_selection(&uif->entity, config, + sel->pad, sel->target); + break; + + default: + ret = -EINVAL; + break; + } + +done: + mutex_unlock(&uif->entity.lock); + return ret; +} + +static int uif_set_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_uif *uif = to_uif(subdev); + struct v4l2_subdev_pad_config *config; + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *selection; + int ret = 0; + + if (sel->pad != UIF_PAD_SINK || + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + mutex_lock(&uif->entity.lock); + + config = vsp1_entity_get_pad_config(&uif->entity, cfg, sel->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + /* The crop rectangle must be inside the input frame. */ + format = vsp1_entity_get_pad_format(&uif->entity, config, UIF_PAD_SINK); + + sel->r.left = clamp_t(unsigned int, sel->r.left, 0, format->width - 1); + sel->r.top = clamp_t(unsigned int, sel->r.top, 0, format->height - 1); + sel->r.width = clamp_t(unsigned int, sel->r.width, UIF_MIN_SIZE, + format->width - sel->r.left); + sel->r.height = clamp_t(unsigned int, sel->r.height, UIF_MIN_SIZE, + format->height - sel->r.top); + + /* Store the crop rectangle. */ + selection = vsp1_entity_get_pad_selection(&uif->entity, config, + sel->pad, V4L2_SEL_TGT_CROP); + *selection = sel->r; + +done: + mutex_unlock(&uif->entity.lock); + return ret; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static const struct v4l2_subdev_pad_ops uif_pad_ops = { + .init_cfg = vsp1_entity_init_cfg, + .enum_mbus_code = uif_enum_mbus_code, + .enum_frame_size = uif_enum_frame_size, + .get_fmt = vsp1_subdev_get_pad_format, + .set_fmt = uif_set_format, + .get_selection = uif_get_selection, + .set_selection = uif_set_selection, +}; + +static const struct v4l2_subdev_ops uif_ops = { + .pad = &uif_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * VSP1 Entity Operations + */ + +static void uif_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb) +{ + struct vsp1_uif *uif = to_uif(&entity->subdev); + const struct v4l2_rect *crop; + unsigned int left; + unsigned int width; + + vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMPMR, + VI6_UIF_DISCOM_DOCMPMR_SEL(9)); + + crop = vsp1_entity_get_pad_selection(entity, entity->config, + UIF_PAD_SINK, V4L2_SEL_TGT_CROP); + + left = crop->left; + width = crop->width; + + /* On M3-W the horizontal coordinates are twice the register value. */ + if (uif->m3w_quirk) { + left /= 2; + width /= 2; + } + + vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMSPXR, left); + vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMSPYR, crop->top); + vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMSZXR, width); + vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMSZYR, crop->height); + + vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMCR, + VI6_UIF_DISCOM_DOCMCR_CMPR); +} + +static const struct vsp1_entity_operations uif_entity_ops = { + .configure_stream = uif_configure_stream, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +static const struct soc_device_attribute vsp1_r8a7796[] = { + { .soc_id = "r8a7796" }, + { /* sentinel */ } +}; + +struct vsp1_uif *vsp1_uif_create(struct vsp1_device *vsp1, unsigned int index) +{ + struct vsp1_uif *uif; + char name[6]; + int ret; + + uif = devm_kzalloc(vsp1->dev, sizeof(*uif), GFP_KERNEL); + if (!uif) + return ERR_PTR(-ENOMEM); + + if (soc_device_match(vsp1_r8a7796)) + uif->m3w_quirk = true; + + uif->entity.ops = &uif_entity_ops; + uif->entity.type = VSP1_ENTITY_UIF; + uif->entity.index = index; + + /* The datasheet names the two UIF instances UIF4 and UIF5. */ + sprintf(name, "uif.%u", index + 4); + ret = vsp1_entity_init(vsp1, &uif->entity, name, 2, &uif_ops, + MEDIA_ENT_F_PROC_VIDEO_STATISTICS); + if (ret < 0) + return ERR_PTR(ret); + + return uif; +} diff --git a/drivers/media/platform/vsp1/vsp1_uif.h b/drivers/media/platform/vsp1/vsp1_uif.h new file mode 100644 index 000000000000..c71ab5f6a6f8 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_uif.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_uif.h -- R-Car VSP1 User Logic Interface + * + * Copyright (C) 2017-2018 Laurent Pinchart + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_UIF_H__ +#define __VSP1_UIF_H__ + +#include "vsp1_entity.h" + +struct vsp1_device; + +#define UIF_PAD_SINK 0 +#define UIF_PAD_SOURCE 1 + +struct vsp1_uif { + struct vsp1_entity entity; + bool m3w_quirk; +}; + +static inline struct vsp1_uif *to_uif(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_uif, entity.subdev); +} + +struct vsp1_uif *vsp1_uif_create(struct vsp1_device *vsp1, unsigned int index); +u32 vsp1_uif_get_crc(struct vsp1_uif *uif); + +#endif /* __VSP1_UIF_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_video.c b/drivers/media/platform/vsp1/vsp1_video.c index c2d3b8f0f487..81d47a09d7bc 100644 --- a/drivers/media/platform/vsp1/vsp1_video.c +++ b/drivers/media/platform/vsp1/vsp1_video.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_video.c -- R-Car VSP1 Video Node * * Copyright (C) 2013-2015 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/list.h> @@ -28,7 +24,7 @@ #include <media/videobuf2-dma-contig.h> #include "vsp1.h" -#include "vsp1_bru.h" +#include "vsp1_brx.h" #include "vsp1_dl.h" #include "vsp1_entity.h" #include "vsp1_hgo.h" @@ -324,7 +320,7 @@ static int vsp1_video_pipeline_setup_partitions(struct vsp1_pipeline *pipe) static struct vsp1_vb2_buffer * vsp1_video_complete_buffer(struct vsp1_video *video) { - struct vsp1_pipeline *pipe = video->rwpf->pipe; + struct vsp1_pipeline *pipe = video->rwpf->entity.pipe; struct vsp1_vb2_buffer *next = NULL; struct vsp1_vb2_buffer *done; unsigned long flags; @@ -382,69 +378,71 @@ static void vsp1_video_pipeline_run_partition(struct vsp1_pipeline *pipe, struct vsp1_dl_list *dl, unsigned int partition) { + struct vsp1_dl_body *dlb = vsp1_dl_list_get_body0(dl); struct vsp1_entity *entity; pipe->partition = &pipe->part_table[partition]; - list_for_each_entry(entity, &pipe->entities, list_pipe) { - if (entity->ops->configure) - entity->ops->configure(entity, pipe, dl, - VSP1_ENTITY_PARAMS_PARTITION); - } + list_for_each_entry(entity, &pipe->entities, list_pipe) + vsp1_entity_configure_partition(entity, pipe, dl, dlb); } static void vsp1_video_pipeline_run(struct vsp1_pipeline *pipe) { struct vsp1_device *vsp1 = pipe->output->entity.vsp1; struct vsp1_entity *entity; + struct vsp1_dl_body *dlb; + struct vsp1_dl_list *dl; unsigned int partition; - if (!pipe->dl) - pipe->dl = vsp1_dl_list_get(pipe->output->dlm); + dl = vsp1_dl_list_get(pipe->output->dlm); /* - * Start with the runtime parameters as the configure operation can - * compute/cache information needed when configuring partitions. This - * is the case with flipping in the WPF. + * If the VSP hardware isn't configured yet (which occurs either when + * processing the first frame or after a system suspend/resume), add the + * cached stream configuration to the display list to perform a full + * initialisation. */ - list_for_each_entry(entity, &pipe->entities, list_pipe) { - if (entity->ops->configure) - entity->ops->configure(entity, pipe, pipe->dl, - VSP1_ENTITY_PARAMS_RUNTIME); - } + if (!pipe->configured) + vsp1_dl_list_add_body(dl, pipe->stream_config); + + dlb = vsp1_dl_list_get_body0(dl); - /* Run the first partition */ - vsp1_video_pipeline_run_partition(pipe, pipe->dl, 0); + list_for_each_entry(entity, &pipe->entities, list_pipe) + vsp1_entity_configure_frame(entity, pipe, dl, dlb); - /* Process consecutive partitions as necessary */ + /* Run the first partition. */ + vsp1_video_pipeline_run_partition(pipe, dl, 0); + + /* Process consecutive partitions as necessary. */ for (partition = 1; partition < pipe->partitions; ++partition) { - struct vsp1_dl_list *dl; + struct vsp1_dl_list *dl_next; - dl = vsp1_dl_list_get(pipe->output->dlm); + dl_next = vsp1_dl_list_get(pipe->output->dlm); /* * An incomplete chain will still function, but output only * the partitions that had a dl available. The frame end * interrupt will be marked on the last dl in the chain. */ - if (!dl) { + if (!dl_next) { dev_err(vsp1->dev, "Failed to obtain a dl list. Frame will be incomplete\n"); break; } - vsp1_video_pipeline_run_partition(pipe, dl, partition); - vsp1_dl_list_add_chain(pipe->dl, dl); + vsp1_video_pipeline_run_partition(pipe, dl_next, partition); + vsp1_dl_list_add_chain(dl, dl_next); } /* Complete, and commit the head display list. */ - vsp1_dl_list_commit(pipe->dl); - pipe->dl = NULL; + vsp1_dl_list_commit(dl, false); + pipe->configured = true; vsp1_pipeline_run(pipe); } static void vsp1_video_pipeline_frame_end(struct vsp1_pipeline *pipe, - bool completed) + unsigned int completion) { struct vsp1_device *vsp1 = pipe->output->entity.vsp1; enum vsp1_pipeline_state state; @@ -452,7 +450,7 @@ static void vsp1_video_pipeline_frame_end(struct vsp1_pipeline *pipe, unsigned int i; /* M2M Pipelines should never call here with an incomplete frame. */ - WARN_ON_ONCE(!completed); + WARN_ON_ONCE(!(completion & VSP1_DL_FRAME_END_COMPLETED)); spin_lock_irqsave(&pipe->irqlock, flags); @@ -488,7 +486,7 @@ static int vsp1_video_pipeline_build_branch(struct vsp1_pipeline *pipe, struct media_entity_enum ent_enum; struct vsp1_entity *entity; struct media_pad *pad; - struct vsp1_bru *bru = NULL; + struct vsp1_brx *brx = NULL; int ret; ret = media_entity_enum_init(&ent_enum, &input->entity.vsp1->media_dev); @@ -524,14 +522,14 @@ static int vsp1_video_pipeline_build_branch(struct vsp1_pipeline *pipe, if (entity->type == VSP1_ENTITY_BRU || entity->type == VSP1_ENTITY_BRS) { /* BRU and BRS can't be chained. */ - if (bru) { + if (brx) { ret = -EPIPE; goto out; } - bru = to_bru(&entity->subdev); - bru->inputs[pad->index].rpf = input; - input->bru_input = pad->index; + brx = to_brx(&entity->subdev); + brx->inputs[pad->index].rpf = input; + input->brx_input = pad->index; } /* We've reached the WPF, we're done. */ @@ -553,7 +551,7 @@ static int vsp1_video_pipeline_build_branch(struct vsp1_pipeline *pipe, } pipe->uds = entity; - pipe->uds_input = bru ? &bru->entity : &input->entity; + pipe->uds_input = brx ? &brx->entity : &input->entity; } /* Follow the source link, ignoring any HGO or HGT. */ @@ -598,20 +596,19 @@ static int vsp1_video_pipeline_build(struct vsp1_pipeline *pipe, subdev = media_entity_to_v4l2_subdev(entity); e = to_vsp1_entity(subdev); list_add_tail(&e->list_pipe, &pipe->entities); + e->pipe = pipe; switch (e->type) { case VSP1_ENTITY_RPF: rwpf = to_rwpf(subdev); pipe->inputs[rwpf->entity.index] = rwpf; rwpf->video->pipe_index = ++pipe->num_inputs; - rwpf->pipe = pipe; break; case VSP1_ENTITY_WPF: rwpf = to_rwpf(subdev); pipe->output = rwpf; rwpf->video->pipe_index = 0; - rwpf->pipe = pipe; break; case VSP1_ENTITY_LIF: @@ -620,17 +617,15 @@ static int vsp1_video_pipeline_build(struct vsp1_pipeline *pipe, case VSP1_ENTITY_BRU: case VSP1_ENTITY_BRS: - pipe->bru = e; + pipe->brx = e; break; case VSP1_ENTITY_HGO: pipe->hgo = e; - to_hgo(subdev)->histo.pipe = pipe; break; case VSP1_ENTITY_HGT: pipe->hgt = e; - to_hgt(subdev)->histo.pipe = pipe; break; default: @@ -682,7 +677,7 @@ static struct vsp1_pipeline *vsp1_video_pipeline_get(struct vsp1_video *video) * Otherwise allocate a new pipeline and initialize it, it will be freed * when the last reference is released. */ - if (!video->rwpf->pipe) { + if (!video->rwpf->entity.pipe) { pipe = kzalloc(sizeof(*pipe), GFP_KERNEL); if (!pipe) return ERR_PTR(-ENOMEM); @@ -694,7 +689,7 @@ static struct vsp1_pipeline *vsp1_video_pipeline_get(struct vsp1_video *video) return ERR_PTR(ret); } } else { - pipe = video->rwpf->pipe; + pipe = video->rwpf->entity.pipe; kref_get(&pipe->kref); } @@ -777,7 +772,7 @@ static void vsp1_video_buffer_queue(struct vb2_buffer *vb) { struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct vsp1_video *video = vb2_get_drv_priv(vb->vb2_queue); - struct vsp1_pipeline *pipe = video->rwpf->pipe; + struct vsp1_pipeline *pipe = video->rwpf->entity.pipe; struct vsp1_vb2_buffer *buf = to_vsp1_vb2_buffer(vbuf); unsigned long flags; bool empty; @@ -812,11 +807,6 @@ static int vsp1_video_setup_pipeline(struct vsp1_pipeline *pipe) if (ret < 0) return ret; - /* Prepare the display list. */ - pipe->dl = vsp1_dl_list_get(pipe->output->dlm); - if (!pipe->dl) - return -ENOMEM; - if (pipe->uds) { struct vsp1_uds *uds = to_uds(&pipe->uds->subdev); @@ -838,20 +828,25 @@ static int vsp1_video_setup_pipeline(struct vsp1_pipeline *pipe) } } - list_for_each_entry(entity, &pipe->entities, list_pipe) { - vsp1_entity_route_setup(entity, pipe, pipe->dl); + /* + * Compute and cache the stream configuration into a body. The cached + * body will be added to the display list by vsp1_video_pipeline_run() + * whenever the pipeline needs to be fully reconfigured. + */ + pipe->stream_config = vsp1_dlm_dl_body_get(pipe->output->dlm); + if (!pipe->stream_config) + return -ENOMEM; - if (entity->ops->configure) - entity->ops->configure(entity, pipe, pipe->dl, - VSP1_ENTITY_PARAMS_INIT); + list_for_each_entry(entity, &pipe->entities, list_pipe) { + vsp1_entity_route_setup(entity, pipe, pipe->stream_config); + vsp1_entity_configure_stream(entity, pipe, pipe->stream_config); } return 0; } -static void vsp1_video_cleanup_pipeline(struct vsp1_pipeline *pipe) +static void vsp1_video_release_buffers(struct vsp1_video *video) { - struct vsp1_video *video = pipe->output->video; struct vsp1_vb2_buffer *buffer; unsigned long flags; @@ -861,18 +856,26 @@ static void vsp1_video_cleanup_pipeline(struct vsp1_pipeline *pipe) vb2_buffer_done(&buffer->buf.vb2_buf, VB2_BUF_STATE_ERROR); INIT_LIST_HEAD(&video->irqqueue); spin_unlock_irqrestore(&video->irqlock, flags); +} + +static void vsp1_video_cleanup_pipeline(struct vsp1_pipeline *pipe) +{ + lockdep_assert_held(&pipe->lock); + + /* Release any cached configuration from our output video. */ + vsp1_dl_body_put(pipe->stream_config); + pipe->stream_config = NULL; + pipe->configured = false; /* Release our partition table allocation */ - mutex_lock(&pipe->lock); kfree(pipe->part_table); pipe->part_table = NULL; - mutex_unlock(&pipe->lock); } static int vsp1_video_start_streaming(struct vb2_queue *vq, unsigned int count) { struct vsp1_video *video = vb2_get_drv_priv(vq); - struct vsp1_pipeline *pipe = video->rwpf->pipe; + struct vsp1_pipeline *pipe = video->rwpf->entity.pipe; bool start_pipeline = false; unsigned long flags; int ret; @@ -881,8 +884,9 @@ static int vsp1_video_start_streaming(struct vb2_queue *vq, unsigned int count) if (pipe->stream_count == pipe->num_inputs) { ret = vsp1_video_setup_pipeline(pipe); if (ret < 0) { - mutex_unlock(&pipe->lock); + vsp1_video_release_buffers(video); vsp1_video_cleanup_pipeline(pipe); + mutex_unlock(&pipe->lock); return ret; } @@ -913,7 +917,7 @@ static int vsp1_video_start_streaming(struct vb2_queue *vq, unsigned int count) static void vsp1_video_stop_streaming(struct vb2_queue *vq) { struct vsp1_video *video = vb2_get_drv_priv(vq); - struct vsp1_pipeline *pipe = video->rwpf->pipe; + struct vsp1_pipeline *pipe = video->rwpf->entity.pipe; unsigned long flags; int ret; @@ -932,13 +936,12 @@ static void vsp1_video_stop_streaming(struct vb2_queue *vq) if (ret == -ETIMEDOUT) dev_err(video->vsp1->dev, "pipeline stop timeout\n"); - vsp1_dl_list_put(pipe->dl); - pipe->dl = NULL; + vsp1_video_cleanup_pipeline(pipe); } mutex_unlock(&pipe->lock); media_pipeline_stop(&video->video.entity); - vsp1_video_cleanup_pipeline(pipe); + vsp1_video_release_buffers(video); vsp1_video_pipeline_put(pipe); } @@ -1173,6 +1176,87 @@ static const struct v4l2_file_operations vsp1_video_fops = { }; /* ----------------------------------------------------------------------------- + * Suspend and Resume + */ + +void vsp1_video_suspend(struct vsp1_device *vsp1) +{ + unsigned long flags; + unsigned int i; + int ret; + + /* + * To avoid increasing the system suspend time needlessly, loop over the + * pipelines twice, first to set them all to the stopping state, and + * then to wait for the stop to complete. + */ + for (i = 0; i < vsp1->info->wpf_count; ++i) { + struct vsp1_rwpf *wpf = vsp1->wpf[i]; + struct vsp1_pipeline *pipe; + + if (wpf == NULL) + continue; + + pipe = wpf->entity.pipe; + if (pipe == NULL) + continue; + + spin_lock_irqsave(&pipe->irqlock, flags); + if (pipe->state == VSP1_PIPELINE_RUNNING) + pipe->state = VSP1_PIPELINE_STOPPING; + spin_unlock_irqrestore(&pipe->irqlock, flags); + } + + for (i = 0; i < vsp1->info->wpf_count; ++i) { + struct vsp1_rwpf *wpf = vsp1->wpf[i]; + struct vsp1_pipeline *pipe; + + if (wpf == NULL) + continue; + + pipe = wpf->entity.pipe; + if (pipe == NULL) + continue; + + ret = wait_event_timeout(pipe->wq, vsp1_pipeline_stopped(pipe), + msecs_to_jiffies(500)); + if (ret == 0) + dev_warn(vsp1->dev, "pipeline %u stop timeout\n", + wpf->entity.index); + } +} + +void vsp1_video_resume(struct vsp1_device *vsp1) +{ + unsigned long flags; + unsigned int i; + + /* Resume all running pipelines. */ + for (i = 0; i < vsp1->info->wpf_count; ++i) { + struct vsp1_rwpf *wpf = vsp1->wpf[i]; + struct vsp1_pipeline *pipe; + + if (wpf == NULL) + continue; + + pipe = wpf->entity.pipe; + if (pipe == NULL) + continue; + + /* + * The hardware may have been reset during a suspend and will + * need a full reconfiguration. + */ + pipe->configured = false; + + spin_lock_irqsave(&pipe->irqlock, flags); + if (vsp1_pipeline_ready(pipe)) + vsp1_video_pipeline_run(pipe); + spin_unlock_irqrestore(&pipe->irqlock, flags); + } +} + +/* ----------------------------------------------------------------------------- * Initialization and Cleanup */ diff --git a/drivers/media/platform/vsp1/vsp1_video.h b/drivers/media/platform/vsp1/vsp1_video.h index 50ea7f02205f..f3cf5e2fdf5a 100644 --- a/drivers/media/platform/vsp1/vsp1_video.h +++ b/drivers/media/platform/vsp1/vsp1_video.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * vsp1_video.h -- R-Car VSP1 Video Node * * Copyright (C) 2013-2015 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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 __VSP1_VIDEO_H__ #define __VSP1_VIDEO_H__ @@ -55,6 +51,9 @@ static inline struct vsp1_video *to_vsp1_video(struct video_device *vdev) return container_of(vdev, struct vsp1_video, video); } +void vsp1_video_suspend(struct vsp1_device *vsp1); +void vsp1_video_resume(struct vsp1_device *vsp1); + struct vsp1_video *vsp1_video_create(struct vsp1_device *vsp1, struct vsp1_rwpf *rwpf); void vsp1_video_cleanup(struct vsp1_video *video); diff --git a/drivers/media/platform/vsp1/vsp1_wpf.c b/drivers/media/platform/vsp1/vsp1_wpf.c index 8bd6b2f1af15..23c8f706b3f2 100644 --- a/drivers/media/platform/vsp1/vsp1_wpf.c +++ b/drivers/media/platform/vsp1/vsp1_wpf.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * vsp1_wpf.c -- R-Car VSP1 Write Pixel Formatter * * Copyright (C) 2013-2014 Renesas Electronics Corporation * * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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/device.h> @@ -31,9 +27,9 @@ */ static inline void vsp1_wpf_write(struct vsp1_rwpf *wpf, - struct vsp1_dl_list *dl, u32 reg, u32 data) + struct vsp1_dl_body *dlb, u32 reg, u32 data) { - vsp1_dl_list_write(dl, reg + wpf->entity.index * VI6_WPF_OFFSET, data); + vsp1_dl_body_write(dlb, reg + wpf->entity.index * VI6_WPF_OFFSET, data); } /* ----------------------------------------------------------------------------- @@ -236,10 +232,9 @@ static void vsp1_wpf_destroy(struct vsp1_entity *entity) vsp1_dlm_destroy(wpf->dlm); } -static void wpf_configure(struct vsp1_entity *entity, - struct vsp1_pipeline *pipe, - struct vsp1_dl_list *dl, - enum vsp1_entity_params params) +static void wpf_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb) { struct vsp1_rwpf *wpf = to_rwpf(&entity->subdev); struct vsp1_device *vsp1 = wpf->entity.vsp1; @@ -249,149 +244,12 @@ static void wpf_configure(struct vsp1_entity *entity, u32 outfmt = 0; u32 srcrpf = 0; - if (params == VSP1_ENTITY_PARAMS_RUNTIME) { - const unsigned int mask = BIT(WPF_CTRL_VFLIP) - | BIT(WPF_CTRL_HFLIP); - unsigned long flags; - - spin_lock_irqsave(&wpf->flip.lock, flags); - wpf->flip.active = (wpf->flip.active & ~mask) - | (wpf->flip.pending & mask); - spin_unlock_irqrestore(&wpf->flip.lock, flags); - - outfmt = (wpf->alpha << VI6_WPF_OUTFMT_PDV_SHIFT) | wpf->outfmt; - - if (wpf->flip.active & BIT(WPF_CTRL_VFLIP)) - outfmt |= VI6_WPF_OUTFMT_FLP; - if (wpf->flip.active & BIT(WPF_CTRL_HFLIP)) - outfmt |= VI6_WPF_OUTFMT_HFLP; - - vsp1_wpf_write(wpf, dl, VI6_WPF_OUTFMT, outfmt); - return; - } - sink_format = vsp1_entity_get_pad_format(&wpf->entity, wpf->entity.config, RWPF_PAD_SINK); source_format = vsp1_entity_get_pad_format(&wpf->entity, wpf->entity.config, RWPF_PAD_SOURCE); - - if (params == VSP1_ENTITY_PARAMS_PARTITION) { - const struct v4l2_pix_format_mplane *format = &wpf->format; - const struct vsp1_format_info *fmtinfo = wpf->fmtinfo; - struct vsp1_rwpf_memory mem = wpf->mem; - unsigned int flip = wpf->flip.active; - unsigned int width = sink_format->width; - unsigned int height = sink_format->height; - unsigned int offset; - - /* - * Cropping. The partition algorithm can split the image into - * multiple slices. - */ - if (pipe->partitions > 1) - width = pipe->partition->wpf.width; - - vsp1_wpf_write(wpf, dl, VI6_WPF_HSZCLIP, VI6_WPF_SZCLIP_EN | - (0 << VI6_WPF_SZCLIP_OFST_SHIFT) | - (width << VI6_WPF_SZCLIP_SIZE_SHIFT)); - vsp1_wpf_write(wpf, dl, VI6_WPF_VSZCLIP, VI6_WPF_SZCLIP_EN | - (0 << VI6_WPF_SZCLIP_OFST_SHIFT) | - (height << VI6_WPF_SZCLIP_SIZE_SHIFT)); - - if (pipe->lif) - return; - - /* - * Update the memory offsets based on flipping configuration. - * The destination addresses point to the locations where the - * VSP starts writing to memory, which can be any corner of the - * image depending on the combination of flipping and rotation. - */ - - /* - * First take the partition left coordinate into account. - * Compute the offset to order the partitions correctly on the - * output based on whether flipping is enabled. Consider - * horizontal flipping when rotation is disabled but vertical - * flipping when rotation is enabled, as rotating the image - * switches the horizontal and vertical directions. The offset - * is applied horizontally or vertically accordingly. - */ - if (flip & BIT(WPF_CTRL_HFLIP) && !wpf->flip.rotate) - offset = format->width - pipe->partition->wpf.left - - pipe->partition->wpf.width; - else if (flip & BIT(WPF_CTRL_VFLIP) && wpf->flip.rotate) - offset = format->height - pipe->partition->wpf.left - - pipe->partition->wpf.width; - else - offset = pipe->partition->wpf.left; - - for (i = 0; i < format->num_planes; ++i) { - unsigned int hsub = i > 0 ? fmtinfo->hsub : 1; - unsigned int vsub = i > 0 ? fmtinfo->vsub : 1; - - if (wpf->flip.rotate) - mem.addr[i] += offset / vsub - * format->plane_fmt[i].bytesperline; - else - mem.addr[i] += offset / hsub - * fmtinfo->bpp[i] / 8; - } - - if (flip & BIT(WPF_CTRL_VFLIP)) { - /* - * When rotating the output (after rotation) image - * height is equal to the partition width (before - * rotation). Otherwise it is equal to the output - * image height. - */ - if (wpf->flip.rotate) - height = pipe->partition->wpf.width; - else - height = format->height; - - mem.addr[0] += (height - 1) - * format->plane_fmt[0].bytesperline; - - if (format->num_planes > 1) { - offset = (height / fmtinfo->vsub - 1) - * format->plane_fmt[1].bytesperline; - mem.addr[1] += offset; - mem.addr[2] += offset; - } - } - - if (wpf->flip.rotate && !(flip & BIT(WPF_CTRL_HFLIP))) { - unsigned int hoffset = max(0, (int)format->width - 16); - - /* - * Compute the output coordinate. The partition - * horizontal (left) offset becomes a vertical offset. - */ - for (i = 0; i < format->num_planes; ++i) { - unsigned int hsub = i > 0 ? fmtinfo->hsub : 1; - - mem.addr[i] += hoffset / hsub - * fmtinfo->bpp[i] / 8; - } - } - - /* - * On Gen3 hardware the SPUVS bit has no effect on 3-planar - * formats. Swap the U and V planes manually in that case. - */ - if (vsp1->info->gen == 3 && format->num_planes == 3 && - fmtinfo->swap_uv) - swap(mem.addr[1], mem.addr[2]); - - vsp1_wpf_write(wpf, dl, VI6_WPF_DSTM_ADDR_Y, mem.addr[0]); - vsp1_wpf_write(wpf, dl, VI6_WPF_DSTM_ADDR_C0, mem.addr[1]); - vsp1_wpf_write(wpf, dl, VI6_WPF_DSTM_ADDR_C1, mem.addr[2]); - return; - } - /* Format */ if (!pipe->lif) { const struct v4l2_pix_format_mplane *format = &wpf->format; @@ -410,17 +268,17 @@ static void wpf_configure(struct vsp1_entity *entity, outfmt |= VI6_WPF_OUTFMT_SPUVS; /* Destination stride and byte swapping. */ - vsp1_wpf_write(wpf, dl, VI6_WPF_DSTM_STRIDE_Y, + vsp1_wpf_write(wpf, dlb, VI6_WPF_DSTM_STRIDE_Y, format->plane_fmt[0].bytesperline); if (format->num_planes > 1) - vsp1_wpf_write(wpf, dl, VI6_WPF_DSTM_STRIDE_C, + vsp1_wpf_write(wpf, dlb, VI6_WPF_DSTM_STRIDE_C, format->plane_fmt[1].bytesperline); - vsp1_wpf_write(wpf, dl, VI6_WPF_DSWAP, fmtinfo->swap); + vsp1_wpf_write(wpf, dlb, VI6_WPF_DSWAP, fmtinfo->swap); if (vsp1->info->features & VSP1_HAS_WPF_HFLIP && wpf->entity.index == 0) - vsp1_wpf_write(wpf, dl, VI6_WPF_ROT_CTRL, + vsp1_wpf_write(wpf, dlb, VI6_WPF_ROT_CTRL, VI6_WPF_ROT_CTRL_LN16 | (256 << VI6_WPF_ROT_CTRL_LMEM_WD_SHIFT)); } @@ -430,13 +288,13 @@ static void wpf_configure(struct vsp1_entity *entity, wpf->outfmt = outfmt; - vsp1_dl_list_write(dl, VI6_DPR_WPF_FPORCH(wpf->entity.index), + vsp1_dl_body_write(dlb, VI6_DPR_WPF_FPORCH(wpf->entity.index), VI6_DPR_WPF_FPORCH_FP_WPFN); - vsp1_dl_list_write(dl, VI6_WPF_WRBCK_CTRL, 0); + vsp1_dl_body_write(dlb, VI6_WPF_WRBCK_CTRL, 0); /* - * Sources. If the pipeline has a single input and BRU is not used, + * Sources. If the pipeline has a single input and BRx is not used, * configure it as the master layer. Otherwise configure all * inputs as sub-layers and select the virtual RPF as the master * layer. @@ -447,24 +305,180 @@ static void wpf_configure(struct vsp1_entity *entity, if (!input) continue; - srcrpf |= (!pipe->bru && pipe->num_inputs == 1) + srcrpf |= (!pipe->brx && pipe->num_inputs == 1) ? VI6_WPF_SRCRPF_RPF_ACT_MST(input->entity.index) : VI6_WPF_SRCRPF_RPF_ACT_SUB(input->entity.index); } - if (pipe->bru) - srcrpf |= pipe->bru->type == VSP1_ENTITY_BRU + if (pipe->brx) + srcrpf |= pipe->brx->type == VSP1_ENTITY_BRU ? VI6_WPF_SRCRPF_VIRACT_MST : VI6_WPF_SRCRPF_VIRACT2_MST; - vsp1_wpf_write(wpf, dl, VI6_WPF_SRCRPF, srcrpf); + vsp1_wpf_write(wpf, dlb, VI6_WPF_SRCRPF, srcrpf); /* Enable interrupts */ - vsp1_dl_list_write(dl, VI6_WPF_IRQ_STA(wpf->entity.index), 0); - vsp1_dl_list_write(dl, VI6_WPF_IRQ_ENB(wpf->entity.index), + vsp1_dl_body_write(dlb, VI6_WPF_IRQ_STA(wpf->entity.index), 0); + vsp1_dl_body_write(dlb, VI6_WPF_IRQ_ENB(wpf->entity.index), VI6_WFP_IRQ_ENB_DFEE); } +static void wpf_configure_frame(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + const unsigned int mask = BIT(WPF_CTRL_VFLIP) + | BIT(WPF_CTRL_HFLIP); + struct vsp1_rwpf *wpf = to_rwpf(&entity->subdev); + unsigned long flags; + u32 outfmt; + + spin_lock_irqsave(&wpf->flip.lock, flags); + wpf->flip.active = (wpf->flip.active & ~mask) + | (wpf->flip.pending & mask); + spin_unlock_irqrestore(&wpf->flip.lock, flags); + + outfmt = (wpf->alpha << VI6_WPF_OUTFMT_PDV_SHIFT) | wpf->outfmt; + + if (wpf->flip.active & BIT(WPF_CTRL_VFLIP)) + outfmt |= VI6_WPF_OUTFMT_FLP; + if (wpf->flip.active & BIT(WPF_CTRL_HFLIP)) + outfmt |= VI6_WPF_OUTFMT_HFLP; + + vsp1_wpf_write(wpf, dlb, VI6_WPF_OUTFMT, outfmt); +} + +static void wpf_configure_partition(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_rwpf *wpf = to_rwpf(&entity->subdev); + struct vsp1_device *vsp1 = wpf->entity.vsp1; + struct vsp1_rwpf_memory mem = wpf->mem; + const struct v4l2_mbus_framefmt *sink_format; + const struct v4l2_pix_format_mplane *format = &wpf->format; + const struct vsp1_format_info *fmtinfo = wpf->fmtinfo; + unsigned int width; + unsigned int height; + unsigned int offset; + unsigned int flip; + unsigned int i; + + sink_format = vsp1_entity_get_pad_format(&wpf->entity, + wpf->entity.config, + RWPF_PAD_SINK); + width = sink_format->width; + height = sink_format->height; + + /* + * Cropping. The partition algorithm can split the image into + * multiple slices. + */ + if (pipe->partitions > 1) + width = pipe->partition->wpf.width; + + vsp1_wpf_write(wpf, dlb, VI6_WPF_HSZCLIP, VI6_WPF_SZCLIP_EN | + (0 << VI6_WPF_SZCLIP_OFST_SHIFT) | + (width << VI6_WPF_SZCLIP_SIZE_SHIFT)); + vsp1_wpf_write(wpf, dlb, VI6_WPF_VSZCLIP, VI6_WPF_SZCLIP_EN | + (0 << VI6_WPF_SZCLIP_OFST_SHIFT) | + (height << VI6_WPF_SZCLIP_SIZE_SHIFT)); + + if (pipe->lif) + return; + + /* + * Update the memory offsets based on flipping configuration. + * The destination addresses point to the locations where the + * VSP starts writing to memory, which can be any corner of the + * image depending on the combination of flipping and rotation. + */ + + /* + * First take the partition left coordinate into account. + * Compute the offset to order the partitions correctly on the + * output based on whether flipping is enabled. Consider + * horizontal flipping when rotation is disabled but vertical + * flipping when rotation is enabled, as rotating the image + * switches the horizontal and vertical directions. The offset + * is applied horizontally or vertically accordingly. + */ + flip = wpf->flip.active; + + if (flip & BIT(WPF_CTRL_HFLIP) && !wpf->flip.rotate) + offset = format->width - pipe->partition->wpf.left + - pipe->partition->wpf.width; + else if (flip & BIT(WPF_CTRL_VFLIP) && wpf->flip.rotate) + offset = format->height - pipe->partition->wpf.left + - pipe->partition->wpf.width; + else + offset = pipe->partition->wpf.left; + + for (i = 0; i < format->num_planes; ++i) { + unsigned int hsub = i > 0 ? fmtinfo->hsub : 1; + unsigned int vsub = i > 0 ? fmtinfo->vsub : 1; + + if (wpf->flip.rotate) + mem.addr[i] += offset / vsub + * format->plane_fmt[i].bytesperline; + else + mem.addr[i] += offset / hsub + * fmtinfo->bpp[i] / 8; + } + + if (flip & BIT(WPF_CTRL_VFLIP)) { + /* + * When rotating the output (after rotation) image + * height is equal to the partition width (before + * rotation). Otherwise it is equal to the output + * image height. + */ + if (wpf->flip.rotate) + height = pipe->partition->wpf.width; + else + height = format->height; + + mem.addr[0] += (height - 1) + * format->plane_fmt[0].bytesperline; + + if (format->num_planes > 1) { + offset = (height / fmtinfo->vsub - 1) + * format->plane_fmt[1].bytesperline; + mem.addr[1] += offset; + mem.addr[2] += offset; + } + } + + if (wpf->flip.rotate && !(flip & BIT(WPF_CTRL_HFLIP))) { + unsigned int hoffset = max(0, (int)format->width - 16); + + /* + * Compute the output coordinate. The partition + * horizontal (left) offset becomes a vertical offset. + */ + for (i = 0; i < format->num_planes; ++i) { + unsigned int hsub = i > 0 ? fmtinfo->hsub : 1; + + mem.addr[i] += hoffset / hsub + * fmtinfo->bpp[i] / 8; + } + } + + /* + * On Gen3 hardware the SPUVS bit has no effect on 3-planar + * formats. Swap the U and V planes manually in that case. + */ + if (vsp1->info->gen == 3 && format->num_planes == 3 && + fmtinfo->swap_uv) + swap(mem.addr[1], mem.addr[2]); + + vsp1_wpf_write(wpf, dlb, VI6_WPF_DSTM_ADDR_Y, mem.addr[0]); + vsp1_wpf_write(wpf, dlb, VI6_WPF_DSTM_ADDR_C0, mem.addr[1]); + vsp1_wpf_write(wpf, dlb, VI6_WPF_DSTM_ADDR_C1, mem.addr[2]); +} + static unsigned int wpf_max_width(struct vsp1_entity *entity, struct vsp1_pipeline *pipe) { @@ -484,7 +498,9 @@ static void wpf_partition(struct vsp1_entity *entity, static const struct vsp1_entity_operations wpf_entity_ops = { .destroy = vsp1_wpf_destroy, - .configure = wpf_configure, + .configure_stream = wpf_configure_stream, + .configure_frame = wpf_configure_frame, + .configure_partition = wpf_configure_partition, .max_width = wpf_max_width, .partition = wpf_partition, }; diff --git a/drivers/media/platform/xilinx/xilinx-dma.c b/drivers/media/platform/xilinx/xilinx-dma.c index 522cdfdd3345..d041f94be832 100644 --- a/drivers/media/platform/xilinx/xilinx-dma.c +++ b/drivers/media/platform/xilinx/xilinx-dma.c @@ -494,13 +494,15 @@ xvip_dma_querycap(struct file *file, void *fh, struct v4l2_capability *cap) struct v4l2_fh *vfh = file->private_data; struct xvip_dma *dma = to_xvip_dma(vfh->vdev); - cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING - | dma->xdev->v4l2_caps; + cap->device_caps = V4L2_CAP_STREAMING; if (dma->queue.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) - cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + cap->device_caps |= V4L2_CAP_VIDEO_CAPTURE; else - cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + cap->device_caps |= V4L2_CAP_VIDEO_OUTPUT; + + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS + | dma->xdev->v4l2_caps; strlcpy(cap->driver, "xilinx-vipp", sizeof(cap->driver)); strlcpy(cap->card, dma->video.name, sizeof(cap->card)); |