diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/media/platform/xilinx | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/media/platform/xilinx')
-rw-r--r-- | drivers/media/platform/xilinx/Kconfig | 35 | ||||
-rw-r--r-- | drivers/media/platform/xilinx/Makefile | 8 | ||||
-rw-r--r-- | drivers/media/platform/xilinx/xilinx-csi2rxss.c | 1098 | ||||
-rw-r--r-- | drivers/media/platform/xilinx/xilinx-dma.c | 759 | ||||
-rw-r--r-- | drivers/media/platform/xilinx/xilinx-dma.h | 109 | ||||
-rw-r--r-- | drivers/media/platform/xilinx/xilinx-tpg.c | 933 | ||||
-rw-r--r-- | drivers/media/platform/xilinx/xilinx-vip.c | 320 | ||||
-rw-r--r-- | drivers/media/platform/xilinx/xilinx-vip.h | 234 | ||||
-rw-r--r-- | drivers/media/platform/xilinx/xilinx-vipp.c | 649 | ||||
-rw-r--r-- | drivers/media/platform/xilinx/xilinx-vipp.h | 42 | ||||
-rw-r--r-- | drivers/media/platform/xilinx/xilinx-vtc.c | 377 | ||||
-rw-r--r-- | drivers/media/platform/xilinx/xilinx-vtc.h | 39 |
12 files changed, 4603 insertions, 0 deletions
diff --git a/drivers/media/platform/xilinx/Kconfig b/drivers/media/platform/xilinx/Kconfig new file mode 100644 index 000000000..93ef78bf6 --- /dev/null +++ b/drivers/media/platform/xilinx/Kconfig @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0 + +comment "Xilinx media platform drivers" + +config VIDEO_XILINX + tristate "Xilinx Video IP (EXPERIMENTAL)" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && OF && HAS_DMA + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + help + Driver for Xilinx Video IP Pipelines + +config VIDEO_XILINX_CSI2RXSS + depends on VIDEO_XILINX + tristate "Xilinx CSI-2 Rx Subsystem" + help + Driver for Xilinx MIPI CSI-2 Rx Subsystem. This is a V4L sub-device + based driver that takes input from CSI-2 Tx source and converts + it into an AXI4-Stream. + +config VIDEO_XILINX_TPG + tristate "Xilinx Video Test Pattern Generator" + depends on VIDEO_XILINX + select VIDEO_XILINX_VTC + help + Driver for the Xilinx Video Test Pattern Generator + +config VIDEO_XILINX_VTC + tristate "Xilinx Video Timing Controller" + depends on VIDEO_XILINX + help + Driver for the Xilinx Video Timing Controller diff --git a/drivers/media/platform/xilinx/Makefile b/drivers/media/platform/xilinx/Makefile new file mode 100644 index 000000000..6119a34f3 --- /dev/null +++ b/drivers/media/platform/xilinx/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 + +xilinx-video-objs += xilinx-dma.o xilinx-vip.o xilinx-vipp.o + +obj-$(CONFIG_VIDEO_XILINX) += xilinx-video.o +obj-$(CONFIG_VIDEO_XILINX_CSI2RXSS) += xilinx-csi2rxss.o +obj-$(CONFIG_VIDEO_XILINX_TPG) += xilinx-tpg.o +obj-$(CONFIG_VIDEO_XILINX_VTC) += xilinx-vtc.o diff --git a/drivers/media/platform/xilinx/xilinx-csi2rxss.c b/drivers/media/platform/xilinx/xilinx-csi2rxss.c new file mode 100644 index 000000000..29b53febc --- /dev/null +++ b/drivers/media/platform/xilinx/xilinx-csi2rxss.c @@ -0,0 +1,1098 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Xilinx MIPI CSI-2 Rx Subsystem + * + * Copyright (C) 2016 - 2020 Xilinx, Inc. + * + * Contacts: Vishal Sagar <vishal.sagar@xilinx.com> + * + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/v4l2-subdev.h> +#include <media/media-entity.h> +#include <media/mipi-csi2.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-subdev.h> +#include "xilinx-vip.h" + +/* Register register map */ +#define XCSI_CCR_OFFSET 0x00 +#define XCSI_CCR_SOFTRESET BIT(1) +#define XCSI_CCR_ENABLE BIT(0) + +#define XCSI_PCR_OFFSET 0x04 +#define XCSI_PCR_MAXLANES_MASK GENMASK(4, 3) +#define XCSI_PCR_ACTLANES_MASK GENMASK(1, 0) + +#define XCSI_CSR_OFFSET 0x10 +#define XCSI_CSR_PKTCNT GENMASK(31, 16) +#define XCSI_CSR_SPFIFOFULL BIT(3) +#define XCSI_CSR_SPFIFONE BIT(2) +#define XCSI_CSR_SLBF BIT(1) +#define XCSI_CSR_RIPCD BIT(0) + +#define XCSI_GIER_OFFSET 0x20 +#define XCSI_GIER_GIE BIT(0) + +#define XCSI_ISR_OFFSET 0x24 +#define XCSI_IER_OFFSET 0x28 + +#define XCSI_ISR_FR BIT(31) +#define XCSI_ISR_VCXFE BIT(30) +#define XCSI_ISR_WCC BIT(22) +#define XCSI_ISR_ILC BIT(21) +#define XCSI_ISR_SPFIFOF BIT(20) +#define XCSI_ISR_SPFIFONE BIT(19) +#define XCSI_ISR_SLBF BIT(18) +#define XCSI_ISR_STOP BIT(17) +#define XCSI_ISR_SOTERR BIT(13) +#define XCSI_ISR_SOTSYNCERR BIT(12) +#define XCSI_ISR_ECC2BERR BIT(11) +#define XCSI_ISR_ECC1BERR BIT(10) +#define XCSI_ISR_CRCERR BIT(9) +#define XCSI_ISR_DATAIDERR BIT(8) +#define XCSI_ISR_VC3FSYNCERR BIT(7) +#define XCSI_ISR_VC3FLVLERR BIT(6) +#define XCSI_ISR_VC2FSYNCERR BIT(5) +#define XCSI_ISR_VC2FLVLERR BIT(4) +#define XCSI_ISR_VC1FSYNCERR BIT(3) +#define XCSI_ISR_VC1FLVLERR BIT(2) +#define XCSI_ISR_VC0FSYNCERR BIT(1) +#define XCSI_ISR_VC0FLVLERR BIT(0) + +#define XCSI_ISR_ALLINTR_MASK (0xc07e3fff) + +/* + * Removed VCXFE mask as it doesn't exist in IER + * Removed STOP state irq as this will keep driver in irq handler only + */ +#define XCSI_IER_INTR_MASK (XCSI_ISR_ALLINTR_MASK &\ + ~(XCSI_ISR_STOP | XCSI_ISR_VCXFE)) + +#define XCSI_SPKTR_OFFSET 0x30 +#define XCSI_SPKTR_DATA GENMASK(23, 8) +#define XCSI_SPKTR_VC GENMASK(7, 6) +#define XCSI_SPKTR_DT GENMASK(5, 0) +#define XCSI_SPKT_FIFO_DEPTH 31 + +#define XCSI_VCXR_OFFSET 0x34 +#define XCSI_VCXR_VCERR GENMASK(23, 0) +#define XCSI_VCXR_FSYNCERR BIT(1) +#define XCSI_VCXR_FLVLERR BIT(0) + +#define XCSI_CLKINFR_OFFSET 0x3C +#define XCSI_CLKINFR_STOP BIT(1) + +#define XCSI_DLXINFR_OFFSET 0x40 +#define XCSI_DLXINFR_STOP BIT(5) +#define XCSI_DLXINFR_SOTERR BIT(1) +#define XCSI_DLXINFR_SOTSYNCERR BIT(0) +#define XCSI_MAXDL_COUNT 0x4 + +#define XCSI_VCXINF1R_OFFSET 0x60 +#define XCSI_VCXINF1R_LINECOUNT GENMASK(31, 16) +#define XCSI_VCXINF1R_LINECOUNT_SHIFT 16 +#define XCSI_VCXINF1R_BYTECOUNT GENMASK(15, 0) + +#define XCSI_VCXINF2R_OFFSET 0x64 +#define XCSI_VCXINF2R_DT GENMASK(5, 0) +#define XCSI_MAXVCX_COUNT 16 + +/* + * Sink pad connected to sensor source pad. + * Source pad connected to next module like demosaic. + */ +#define XCSI_MEDIA_PADS 2 +#define XCSI_DEFAULT_WIDTH 1920 +#define XCSI_DEFAULT_HEIGHT 1080 + +#define XCSI_VCX_START 4 +#define XCSI_MAX_VC 4 +#define XCSI_MAX_VCX 16 + +#define XCSI_NEXTREG_OFFSET 4 + +/* There are 2 events frame sync and frame level error per VC */ +#define XCSI_VCX_NUM_EVENTS ((XCSI_MAX_VCX - XCSI_MAX_VC) * 2) + +/** + * struct xcsi2rxss_event - Event log structure + * @mask: Event mask + * @name: Name of the event + */ +struct xcsi2rxss_event { + u32 mask; + const char *name; +}; + +static const struct xcsi2rxss_event xcsi2rxss_events[] = { + { XCSI_ISR_FR, "Frame Received" }, + { XCSI_ISR_VCXFE, "VCX Frame Errors" }, + { XCSI_ISR_WCC, "Word Count Errors" }, + { XCSI_ISR_ILC, "Invalid Lane Count Error" }, + { XCSI_ISR_SPFIFOF, "Short Packet FIFO OverFlow Error" }, + { XCSI_ISR_SPFIFONE, "Short Packet FIFO Not Empty" }, + { XCSI_ISR_SLBF, "Streamline Buffer Full Error" }, + { XCSI_ISR_STOP, "Lane Stop State" }, + { XCSI_ISR_SOTERR, "SOT Error" }, + { XCSI_ISR_SOTSYNCERR, "SOT Sync Error" }, + { XCSI_ISR_ECC2BERR, "2 Bit ECC Unrecoverable Error" }, + { XCSI_ISR_ECC1BERR, "1 Bit ECC Recoverable Error" }, + { XCSI_ISR_CRCERR, "CRC Error" }, + { XCSI_ISR_DATAIDERR, "Data Id Error" }, + { XCSI_ISR_VC3FSYNCERR, "Virtual Channel 3 Frame Sync Error" }, + { XCSI_ISR_VC3FLVLERR, "Virtual Channel 3 Frame Level Error" }, + { XCSI_ISR_VC2FSYNCERR, "Virtual Channel 2 Frame Sync Error" }, + { XCSI_ISR_VC2FLVLERR, "Virtual Channel 2 Frame Level Error" }, + { XCSI_ISR_VC1FSYNCERR, "Virtual Channel 1 Frame Sync Error" }, + { XCSI_ISR_VC1FLVLERR, "Virtual Channel 1 Frame Level Error" }, + { XCSI_ISR_VC0FSYNCERR, "Virtual Channel 0 Frame Sync Error" }, + { XCSI_ISR_VC0FLVLERR, "Virtual Channel 0 Frame Level Error" } +}; + +#define XCSI_NUM_EVENTS ARRAY_SIZE(xcsi2rxss_events) + +/* + * This table provides a mapping between CSI-2 Data type + * and media bus formats + */ +static const u32 xcsi2dt_mbus_lut[][2] = { + { MIPI_CSI2_DT_YUV422_8B, MEDIA_BUS_FMT_UYVY8_1X16 }, + { MIPI_CSI2_DT_YUV422_10B, MEDIA_BUS_FMT_UYVY10_1X20 }, + { MIPI_CSI2_DT_RGB444, 0 }, + { MIPI_CSI2_DT_RGB555, 0 }, + { MIPI_CSI2_DT_RGB565, 0 }, + { MIPI_CSI2_DT_RGB666, 0 }, + { MIPI_CSI2_DT_RGB888, MEDIA_BUS_FMT_RBG888_1X24 }, + { MIPI_CSI2_DT_RAW6, 0 }, + { MIPI_CSI2_DT_RAW7, 0 }, + { MIPI_CSI2_DT_RAW8, MEDIA_BUS_FMT_SRGGB8_1X8 }, + { MIPI_CSI2_DT_RAW8, MEDIA_BUS_FMT_SBGGR8_1X8 }, + { MIPI_CSI2_DT_RAW8, MEDIA_BUS_FMT_SGBRG8_1X8 }, + { MIPI_CSI2_DT_RAW8, MEDIA_BUS_FMT_SGRBG8_1X8 }, + { MIPI_CSI2_DT_RAW10, MEDIA_BUS_FMT_SRGGB10_1X10 }, + { MIPI_CSI2_DT_RAW10, MEDIA_BUS_FMT_SBGGR10_1X10 }, + { MIPI_CSI2_DT_RAW10, MEDIA_BUS_FMT_SGBRG10_1X10 }, + { MIPI_CSI2_DT_RAW10, MEDIA_BUS_FMT_SGRBG10_1X10 }, + { MIPI_CSI2_DT_RAW12, MEDIA_BUS_FMT_SRGGB12_1X12 }, + { MIPI_CSI2_DT_RAW12, MEDIA_BUS_FMT_SBGGR12_1X12 }, + { MIPI_CSI2_DT_RAW12, MEDIA_BUS_FMT_SGBRG12_1X12 }, + { MIPI_CSI2_DT_RAW12, MEDIA_BUS_FMT_SGRBG12_1X12 }, + { MIPI_CSI2_DT_RAW12, MEDIA_BUS_FMT_Y12_1X12 }, + { MIPI_CSI2_DT_RAW16, MEDIA_BUS_FMT_SRGGB16_1X16 }, + { MIPI_CSI2_DT_RAW16, MEDIA_BUS_FMT_SBGGR16_1X16 }, + { MIPI_CSI2_DT_RAW16, MEDIA_BUS_FMT_SGBRG16_1X16 }, + { MIPI_CSI2_DT_RAW16, MEDIA_BUS_FMT_SGRBG16_1X16 }, + { MIPI_CSI2_DT_RAW20, 0 }, +}; + +/** + * struct xcsi2rxss_state - CSI-2 Rx Subsystem device structure + * @subdev: The v4l2 subdev structure + * @format: Active V4L2 formats on each pad + * @default_format: Default V4L2 format + * @events: counter for events + * @vcx_events: counter for vcx_events + * @dev: Platform structure + * @rsubdev: Remote subdev connected to sink pad + * @rst_gpio: reset to video_aresetn + * @clks: array of clocks + * @iomem: Base address of subsystem + * @max_num_lanes: Maximum number of lanes present + * @datatype: Data type filter + * @lock: mutex for accessing this structure + * @pads: media pads + * @streaming: Flag for storing streaming state + * @enable_active_lanes: If number of active lanes can be modified + * @en_vcx: If more than 4 VC are enabled + * + * This structure contains the device driver related parameters + */ +struct xcsi2rxss_state { + struct v4l2_subdev subdev; + struct v4l2_mbus_framefmt format; + struct v4l2_mbus_framefmt default_format; + u32 events[XCSI_NUM_EVENTS]; + u32 vcx_events[XCSI_VCX_NUM_EVENTS]; + struct device *dev; + struct v4l2_subdev *rsubdev; + struct gpio_desc *rst_gpio; + struct clk_bulk_data *clks; + void __iomem *iomem; + u32 max_num_lanes; + u32 datatype; + /* used to protect access to this struct */ + struct mutex lock; + struct media_pad pads[XCSI_MEDIA_PADS]; + bool streaming; + bool enable_active_lanes; + bool en_vcx; +}; + +static const struct clk_bulk_data xcsi2rxss_clks[] = { + { .id = "lite_aclk" }, + { .id = "video_aclk" }, +}; + +static inline struct xcsi2rxss_state * +to_xcsi2rxssstate(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct xcsi2rxss_state, subdev); +} + +/* + * Register related operations + */ +static inline u32 xcsi2rxss_read(struct xcsi2rxss_state *xcsi2rxss, u32 addr) +{ + return ioread32(xcsi2rxss->iomem + addr); +} + +static inline void xcsi2rxss_write(struct xcsi2rxss_state *xcsi2rxss, u32 addr, + u32 value) +{ + iowrite32(value, xcsi2rxss->iomem + addr); +} + +static inline void xcsi2rxss_clr(struct xcsi2rxss_state *xcsi2rxss, u32 addr, + u32 clr) +{ + xcsi2rxss_write(xcsi2rxss, addr, + xcsi2rxss_read(xcsi2rxss, addr) & ~clr); +} + +static inline void xcsi2rxss_set(struct xcsi2rxss_state *xcsi2rxss, u32 addr, + u32 set) +{ + xcsi2rxss_write(xcsi2rxss, addr, xcsi2rxss_read(xcsi2rxss, addr) | set); +} + +/* + * This function returns the nth mbus for a data type. + * In case of error, mbus code returned is 0. + */ +static u32 xcsi2rxss_get_nth_mbus(u32 dt, u32 n) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(xcsi2dt_mbus_lut); i++) { + if (xcsi2dt_mbus_lut[i][0] == dt) { + if (n-- == 0) + return xcsi2dt_mbus_lut[i][1]; + } + } + + return 0; +} + +/* This returns the data type for a media bus format else 0 */ +static u32 xcsi2rxss_get_dt(u32 mbus) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(xcsi2dt_mbus_lut); i++) { + if (xcsi2dt_mbus_lut[i][1] == mbus) + return xcsi2dt_mbus_lut[i][0]; + } + + return 0; +} + +/** + * xcsi2rxss_soft_reset - Does a soft reset of the MIPI CSI-2 Rx Subsystem + * @state: Xilinx CSI-2 Rx Subsystem structure pointer + * + * Core takes less than 100 video clock cycles to reset. + * So a larger timeout value is chosen for margin. + * + * Return: 0 - on success OR -ETIME if reset times out + */ +static int xcsi2rxss_soft_reset(struct xcsi2rxss_state *state) +{ + u32 timeout = 1000; /* us */ + + xcsi2rxss_set(state, XCSI_CCR_OFFSET, XCSI_CCR_SOFTRESET); + + while (xcsi2rxss_read(state, XCSI_CSR_OFFSET) & XCSI_CSR_RIPCD) { + if (timeout == 0) { + dev_err(state->dev, "soft reset timed out!\n"); + return -ETIME; + } + + timeout--; + udelay(1); + } + + xcsi2rxss_clr(state, XCSI_CCR_OFFSET, XCSI_CCR_SOFTRESET); + return 0; +} + +static void xcsi2rxss_hard_reset(struct xcsi2rxss_state *state) +{ + if (!state->rst_gpio) + return; + + /* minimum of 40 dphy_clk_200M cycles */ + gpiod_set_value_cansleep(state->rst_gpio, 1); + usleep_range(1, 2); + gpiod_set_value_cansleep(state->rst_gpio, 0); +} + +static void xcsi2rxss_reset_event_counters(struct xcsi2rxss_state *state) +{ + unsigned int i; + + for (i = 0; i < XCSI_NUM_EVENTS; i++) + state->events[i] = 0; + + for (i = 0; i < XCSI_VCX_NUM_EVENTS; i++) + state->vcx_events[i] = 0; +} + +/* Print event counters */ +static void xcsi2rxss_log_counters(struct xcsi2rxss_state *state) +{ + struct device *dev = state->dev; + unsigned int i; + + for (i = 0; i < XCSI_NUM_EVENTS; i++) { + if (state->events[i] > 0) { + dev_info(dev, "%s events: %d\n", + xcsi2rxss_events[i].name, + state->events[i]); + } + } + + if (state->en_vcx) { + for (i = 0; i < XCSI_VCX_NUM_EVENTS; i++) { + if (state->vcx_events[i] > 0) { + dev_info(dev, + "VC %d Frame %s err vcx events: %d\n", + (i / 2) + XCSI_VCX_START, + i & 1 ? "Sync" : "Level", + state->vcx_events[i]); + } + } + } +} + +/** + * xcsi2rxss_log_status - Logs the status of the CSI-2 Receiver + * @sd: Pointer to V4L2 subdevice structure + * + * This function prints the current status of Xilinx MIPI CSI-2 + * + * Return: 0 on success + */ +static int xcsi2rxss_log_status(struct v4l2_subdev *sd) +{ + struct xcsi2rxss_state *xcsi2rxss = to_xcsi2rxssstate(sd); + struct device *dev = xcsi2rxss->dev; + u32 reg, data; + unsigned int i, max_vc; + + mutex_lock(&xcsi2rxss->lock); + + xcsi2rxss_log_counters(xcsi2rxss); + + dev_info(dev, "***** Core Status *****\n"); + data = xcsi2rxss_read(xcsi2rxss, XCSI_CSR_OFFSET); + dev_info(dev, "Short Packet FIFO Full = %s\n", + data & XCSI_CSR_SPFIFOFULL ? "true" : "false"); + dev_info(dev, "Short Packet FIFO Not Empty = %s\n", + data & XCSI_CSR_SPFIFONE ? "true" : "false"); + dev_info(dev, "Stream line buffer full = %s\n", + data & XCSI_CSR_SLBF ? "true" : "false"); + dev_info(dev, "Soft reset/Core disable in progress = %s\n", + data & XCSI_CSR_RIPCD ? "true" : "false"); + + /* Clk & Lane Info */ + dev_info(dev, "******** Clock Lane Info *********\n"); + data = xcsi2rxss_read(xcsi2rxss, XCSI_CLKINFR_OFFSET); + dev_info(dev, "Clock Lane in Stop State = %s\n", + data & XCSI_CLKINFR_STOP ? "true" : "false"); + + dev_info(dev, "******** Data Lane Info *********\n"); + dev_info(dev, "Lane\tSoT Error\tSoT Sync Error\tStop State\n"); + reg = XCSI_DLXINFR_OFFSET; + for (i = 0; i < XCSI_MAXDL_COUNT; i++) { + data = xcsi2rxss_read(xcsi2rxss, reg); + + dev_info(dev, "%d\t%s\t\t%s\t\t%s\n", i, + data & XCSI_DLXINFR_SOTERR ? "true" : "false", + data & XCSI_DLXINFR_SOTSYNCERR ? "true" : "false", + data & XCSI_DLXINFR_STOP ? "true" : "false"); + + reg += XCSI_NEXTREG_OFFSET; + } + + /* Virtual Channel Image Information */ + dev_info(dev, "********** Virtual Channel Info ************\n"); + dev_info(dev, "VC\tLine Count\tByte Count\tData Type\n"); + if (xcsi2rxss->en_vcx) + max_vc = XCSI_MAX_VCX; + else + max_vc = XCSI_MAX_VC; + + reg = XCSI_VCXINF1R_OFFSET; + for (i = 0; i < max_vc; i++) { + u32 line_count, byte_count, data_type; + + /* Get line and byte count from VCXINFR1 Register */ + data = xcsi2rxss_read(xcsi2rxss, reg); + byte_count = data & XCSI_VCXINF1R_BYTECOUNT; + line_count = data & XCSI_VCXINF1R_LINECOUNT; + line_count >>= XCSI_VCXINF1R_LINECOUNT_SHIFT; + + /* Get data type from VCXINFR2 Register */ + reg += XCSI_NEXTREG_OFFSET; + data = xcsi2rxss_read(xcsi2rxss, reg); + data_type = data & XCSI_VCXINF2R_DT; + + dev_info(dev, "%d\t%d\t\t%d\t\t0x%x\n", i, line_count, + byte_count, data_type); + + /* Move to next pair of VC Info registers */ + reg += XCSI_NEXTREG_OFFSET; + } + + mutex_unlock(&xcsi2rxss->lock); + + return 0; +} + +static struct v4l2_subdev *xcsi2rxss_get_remote_subdev(struct media_pad *local) +{ + struct media_pad *remote; + + remote = media_pad_remote_pad_first(local); + if (!remote || !is_media_entity_v4l2_subdev(remote->entity)) + return NULL; + + return media_entity_to_v4l2_subdev(remote->entity); +} + +static int xcsi2rxss_start_stream(struct xcsi2rxss_state *state) +{ + int ret = 0; + + /* enable core */ + xcsi2rxss_set(state, XCSI_CCR_OFFSET, XCSI_CCR_ENABLE); + + ret = xcsi2rxss_soft_reset(state); + if (ret) { + state->streaming = false; + return ret; + } + + /* enable interrupts */ + xcsi2rxss_clr(state, XCSI_GIER_OFFSET, XCSI_GIER_GIE); + xcsi2rxss_write(state, XCSI_IER_OFFSET, XCSI_IER_INTR_MASK); + xcsi2rxss_set(state, XCSI_GIER_OFFSET, XCSI_GIER_GIE); + + state->streaming = true; + + state->rsubdev = + xcsi2rxss_get_remote_subdev(&state->pads[XVIP_PAD_SINK]); + + ret = v4l2_subdev_call(state->rsubdev, video, s_stream, 1); + if (ret) { + /* disable interrupts */ + xcsi2rxss_clr(state, XCSI_IER_OFFSET, XCSI_IER_INTR_MASK); + xcsi2rxss_clr(state, XCSI_GIER_OFFSET, XCSI_GIER_GIE); + + /* disable core */ + xcsi2rxss_clr(state, XCSI_CCR_OFFSET, XCSI_CCR_ENABLE); + state->streaming = false; + } + + return ret; +} + +static void xcsi2rxss_stop_stream(struct xcsi2rxss_state *state) +{ + v4l2_subdev_call(state->rsubdev, video, s_stream, 0); + + /* disable interrupts */ + xcsi2rxss_clr(state, XCSI_IER_OFFSET, XCSI_IER_INTR_MASK); + xcsi2rxss_clr(state, XCSI_GIER_OFFSET, XCSI_GIER_GIE); + + /* disable core */ + xcsi2rxss_clr(state, XCSI_CCR_OFFSET, XCSI_CCR_ENABLE); + state->streaming = false; +} + +/** + * xcsi2rxss_irq_handler - Interrupt handler for CSI-2 + * @irq: IRQ number + * @data: Pointer to device state + * + * In the interrupt handler, a list of event counters are updated for + * corresponding interrupts. This is useful to get status / debug. + * + * Return: IRQ_HANDLED after handling interrupts + */ +static irqreturn_t xcsi2rxss_irq_handler(int irq, void *data) +{ + struct xcsi2rxss_state *state = (struct xcsi2rxss_state *)data; + struct device *dev = state->dev; + u32 status; + + status = xcsi2rxss_read(state, XCSI_ISR_OFFSET) & XCSI_ISR_ALLINTR_MASK; + xcsi2rxss_write(state, XCSI_ISR_OFFSET, status); + + /* Received a short packet */ + if (status & XCSI_ISR_SPFIFONE) { + u32 count = 0; + + /* + * Drain generic short packet FIFO by reading max 31 + * (fifo depth) short packets from fifo or till fifo is empty. + */ + for (count = 0; count < XCSI_SPKT_FIFO_DEPTH; ++count) { + u32 spfifostat, spkt; + + spkt = xcsi2rxss_read(state, XCSI_SPKTR_OFFSET); + dev_dbg(dev, "Short packet = 0x%08x\n", spkt); + spfifostat = xcsi2rxss_read(state, XCSI_ISR_OFFSET); + spfifostat &= XCSI_ISR_SPFIFONE; + if (!spfifostat) + break; + xcsi2rxss_write(state, XCSI_ISR_OFFSET, spfifostat); + } + } + + /* Short packet FIFO overflow */ + if (status & XCSI_ISR_SPFIFOF) + dev_dbg_ratelimited(dev, "Short packet FIFO overflowed\n"); + + /* + * Stream line buffer full + * This means there is a backpressure from downstream IP + */ + if (status & XCSI_ISR_SLBF) { + dev_alert_ratelimited(dev, "Stream Line Buffer Full!\n"); + + /* disable interrupts */ + xcsi2rxss_clr(state, XCSI_IER_OFFSET, XCSI_IER_INTR_MASK); + xcsi2rxss_clr(state, XCSI_GIER_OFFSET, XCSI_GIER_GIE); + + /* disable core */ + xcsi2rxss_clr(state, XCSI_CCR_OFFSET, XCSI_CCR_ENABLE); + + /* + * The IP needs to be hard reset before it can be used now. + * This will be done in streamoff. + */ + + /* + * TODO: Notify the whole pipeline with v4l2_subdev_notify() to + * inform userspace. + */ + } + + /* Increment event counters */ + if (status & XCSI_ISR_ALLINTR_MASK) { + unsigned int i; + + for (i = 0; i < XCSI_NUM_EVENTS; i++) { + if (!(status & xcsi2rxss_events[i].mask)) + continue; + state->events[i]++; + dev_dbg_ratelimited(dev, "%s: %u\n", + xcsi2rxss_events[i].name, + state->events[i]); + } + + if (status & XCSI_ISR_VCXFE && state->en_vcx) { + u32 vcxstatus; + + vcxstatus = xcsi2rxss_read(state, XCSI_VCXR_OFFSET); + vcxstatus &= XCSI_VCXR_VCERR; + for (i = 0; i < XCSI_VCX_NUM_EVENTS; i++) { + if (!(vcxstatus & BIT(i))) + continue; + state->vcx_events[i]++; + } + xcsi2rxss_write(state, XCSI_VCXR_OFFSET, vcxstatus); + } + } + + return IRQ_HANDLED; +} + +/** + * xcsi2rxss_s_stream - It is used to start/stop the streaming. + * @sd: V4L2 Sub device + * @enable: Flag (True / False) + * + * This function controls the start or stop of streaming for the + * Xilinx MIPI CSI-2 Rx Subsystem. + * + * Return: 0 on success, errors otherwise + */ +static int xcsi2rxss_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct xcsi2rxss_state *xcsi2rxss = to_xcsi2rxssstate(sd); + int ret = 0; + + mutex_lock(&xcsi2rxss->lock); + + if (enable == xcsi2rxss->streaming) + goto stream_done; + + if (enable) { + xcsi2rxss_reset_event_counters(xcsi2rxss); + ret = xcsi2rxss_start_stream(xcsi2rxss); + } else { + xcsi2rxss_stop_stream(xcsi2rxss); + xcsi2rxss_hard_reset(xcsi2rxss); + } + +stream_done: + mutex_unlock(&xcsi2rxss->lock); + return ret; +} + +static struct v4l2_mbus_framefmt * +__xcsi2rxss_get_pad_format(struct xcsi2rxss_state *xcsi2rxss, + struct v4l2_subdev_state *sd_state, + unsigned int pad, u32 which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(&xcsi2rxss->subdev, + sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &xcsi2rxss->format; + default: + return NULL; + } +} + +/** + * xcsi2rxss_init_cfg - Initialise the pad format config to default + * @sd: Pointer to V4L2 Sub device structure + * @sd_state: Pointer to sub device state structure + * + * This function is used to initialize the pad format with the default + * values. + * + * Return: 0 on success + */ +static int xcsi2rxss_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct xcsi2rxss_state *xcsi2rxss = to_xcsi2rxssstate(sd); + struct v4l2_mbus_framefmt *format; + unsigned int i; + + mutex_lock(&xcsi2rxss->lock); + for (i = 0; i < XCSI_MEDIA_PADS; i++) { + format = v4l2_subdev_get_try_format(sd, sd_state, i); + *format = xcsi2rxss->default_format; + } + mutex_unlock(&xcsi2rxss->lock); + + return 0; +} + +/** + * xcsi2rxss_get_format - Get the pad format + * @sd: Pointer to V4L2 Sub device structure + * @sd_state: Pointer to sub device state structure + * @fmt: Pointer to pad level media bus format + * + * This function is used to get the pad format information. + * + * Return: 0 on success + */ +static int xcsi2rxss_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct xcsi2rxss_state *xcsi2rxss = to_xcsi2rxssstate(sd); + + mutex_lock(&xcsi2rxss->lock); + fmt->format = *__xcsi2rxss_get_pad_format(xcsi2rxss, sd_state, + fmt->pad, + fmt->which); + mutex_unlock(&xcsi2rxss->lock); + + return 0; +} + +/** + * xcsi2rxss_set_format - This is used to set the pad format + * @sd: Pointer to V4L2 Sub device structure + * @sd_state: Pointer to sub device state structure + * @fmt: Pointer to pad level media bus format + * + * This function is used to set the pad format. Since the pad format is fixed + * in hardware, it can't be modified on run time. So when a format set is + * requested by application, all parameters except the format type is saved + * for the pad and the original pad format is sent back to the application. + * + * Return: 0 on success + */ +static int xcsi2rxss_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct xcsi2rxss_state *xcsi2rxss = to_xcsi2rxssstate(sd); + struct v4l2_mbus_framefmt *__format; + u32 dt; + + mutex_lock(&xcsi2rxss->lock); + + /* + * Only the format->code parameter matters for CSI as the + * CSI format cannot be changed at runtime. + * Ensure that format to set is copied to over to CSI pad format + */ + __format = __xcsi2rxss_get_pad_format(xcsi2rxss, sd_state, + fmt->pad, fmt->which); + + /* only sink pad format can be updated */ + if (fmt->pad == XVIP_PAD_SOURCE) { + fmt->format = *__format; + mutex_unlock(&xcsi2rxss->lock); + return 0; + } + + /* + * RAW8 is supported in all datatypes. So if requested media bus format + * is of RAW8 type, then allow to be set. In case core is configured to + * other RAW, YUV422 8/10 or RGB888, set appropriate media bus format. + */ + dt = xcsi2rxss_get_dt(fmt->format.code); + if (dt != xcsi2rxss->datatype && dt != MIPI_CSI2_DT_RAW8) { + dev_dbg(xcsi2rxss->dev, "Unsupported media bus format"); + /* set the default format for the data type */ + fmt->format.code = xcsi2rxss_get_nth_mbus(xcsi2rxss->datatype, + 0); + } + + *__format = fmt->format; + mutex_unlock(&xcsi2rxss->lock); + + return 0; +} + +/* + * xcsi2rxss_enum_mbus_code - Handle pixel format enumeration + * @sd: pointer to v4l2 subdev structure + * @cfg: V4L2 subdev pad configuration + * @code: pointer to v4l2_subdev_mbus_code_enum structure + * + * Return: -EINVAL or zero on success + */ +static int xcsi2rxss_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct xcsi2rxss_state *state = to_xcsi2rxssstate(sd); + u32 dt, n; + int ret = 0; + + /* RAW8 dt packets are available in all DT configurations */ + if (code->index < 4) { + n = code->index; + dt = MIPI_CSI2_DT_RAW8; + } else if (state->datatype != MIPI_CSI2_DT_RAW8) { + n = code->index - 4; + dt = state->datatype; + } else { + return -EINVAL; + } + + code->code = xcsi2rxss_get_nth_mbus(dt, n); + if (!code->code) + ret = -EINVAL; + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Media Operations + */ + +static const struct media_entity_operations xcsi2rxss_media_ops = { + .link_validate = v4l2_subdev_link_validate +}; + +static const struct v4l2_subdev_core_ops xcsi2rxss_core_ops = { + .log_status = xcsi2rxss_log_status, +}; + +static const struct v4l2_subdev_video_ops xcsi2rxss_video_ops = { + .s_stream = xcsi2rxss_s_stream +}; + +static const struct v4l2_subdev_pad_ops xcsi2rxss_pad_ops = { + .init_cfg = xcsi2rxss_init_cfg, + .get_fmt = xcsi2rxss_get_format, + .set_fmt = xcsi2rxss_set_format, + .enum_mbus_code = xcsi2rxss_enum_mbus_code, + .link_validate = v4l2_subdev_link_validate_default, +}; + +static const struct v4l2_subdev_ops xcsi2rxss_ops = { + .core = &xcsi2rxss_core_ops, + .video = &xcsi2rxss_video_ops, + .pad = &xcsi2rxss_pad_ops +}; + +static int xcsi2rxss_parse_of(struct xcsi2rxss_state *xcsi2rxss) +{ + struct device *dev = xcsi2rxss->dev; + struct device_node *node = dev->of_node; + + struct fwnode_handle *ep; + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + bool en_csi_v20, vfb; + int ret; + + en_csi_v20 = of_property_read_bool(node, "xlnx,en-csi-v2-0"); + if (en_csi_v20) + xcsi2rxss->en_vcx = of_property_read_bool(node, "xlnx,en-vcx"); + + xcsi2rxss->enable_active_lanes = + of_property_read_bool(node, "xlnx,en-active-lanes"); + + ret = of_property_read_u32(node, "xlnx,csi-pxl-format", + &xcsi2rxss->datatype); + if (ret < 0) { + dev_err(dev, "missing xlnx,csi-pxl-format property\n"); + return ret; + } + + switch (xcsi2rxss->datatype) { + case MIPI_CSI2_DT_YUV422_8B: + case MIPI_CSI2_DT_RGB444: + case MIPI_CSI2_DT_RGB555: + case MIPI_CSI2_DT_RGB565: + case MIPI_CSI2_DT_RGB666: + case MIPI_CSI2_DT_RGB888: + case MIPI_CSI2_DT_RAW6: + case MIPI_CSI2_DT_RAW7: + case MIPI_CSI2_DT_RAW8: + case MIPI_CSI2_DT_RAW10: + case MIPI_CSI2_DT_RAW12: + case MIPI_CSI2_DT_RAW14: + break; + case MIPI_CSI2_DT_YUV422_10B: + case MIPI_CSI2_DT_RAW16: + case MIPI_CSI2_DT_RAW20: + if (!en_csi_v20) { + ret = -EINVAL; + dev_dbg(dev, "enable csi v2 for this pixel format"); + } + break; + default: + ret = -EINVAL; + } + if (ret < 0) { + dev_err(dev, "invalid csi-pxl-format property!\n"); + return ret; + } + + vfb = of_property_read_bool(node, "xlnx,vfb"); + if (!vfb) { + dev_err(dev, "operation without VFB is not supported\n"); + return -EINVAL; + } + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + XVIP_PAD_SINK, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) { + dev_err(dev, "no sink port found"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + fwnode_handle_put(ep); + if (ret) { + dev_err(dev, "error parsing sink port"); + return ret; + } + + dev_dbg(dev, "mipi number lanes = %d\n", + vep.bus.mipi_csi2.num_data_lanes); + + xcsi2rxss->max_num_lanes = vep.bus.mipi_csi2.num_data_lanes; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + XVIP_PAD_SOURCE, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) { + dev_err(dev, "no source port found"); + return -EINVAL; + } + + fwnode_handle_put(ep); + + dev_dbg(dev, "vcx %s, %u data lanes (%s), data type 0x%02x\n", + xcsi2rxss->en_vcx ? "enabled" : "disabled", + xcsi2rxss->max_num_lanes, + xcsi2rxss->enable_active_lanes ? "dynamic" : "static", + xcsi2rxss->datatype); + + return 0; +} + +static int xcsi2rxss_probe(struct platform_device *pdev) +{ + struct v4l2_subdev *subdev; + struct xcsi2rxss_state *xcsi2rxss; + int num_clks = ARRAY_SIZE(xcsi2rxss_clks); + struct device *dev = &pdev->dev; + int irq, ret; + + xcsi2rxss = devm_kzalloc(dev, sizeof(*xcsi2rxss), GFP_KERNEL); + if (!xcsi2rxss) + return -ENOMEM; + + xcsi2rxss->dev = dev; + + xcsi2rxss->clks = devm_kmemdup(dev, xcsi2rxss_clks, + sizeof(xcsi2rxss_clks), GFP_KERNEL); + if (!xcsi2rxss->clks) + return -ENOMEM; + + /* Reset GPIO */ + xcsi2rxss->rst_gpio = devm_gpiod_get_optional(dev, "video-reset", + GPIOD_OUT_HIGH); + if (IS_ERR(xcsi2rxss->rst_gpio)) { + if (PTR_ERR(xcsi2rxss->rst_gpio) != -EPROBE_DEFER) + dev_err(dev, "Video Reset GPIO not setup in DT"); + return PTR_ERR(xcsi2rxss->rst_gpio); + } + + ret = xcsi2rxss_parse_of(xcsi2rxss); + if (ret < 0) + return ret; + + xcsi2rxss->iomem = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(xcsi2rxss->iomem)) + return PTR_ERR(xcsi2rxss->iomem); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, irq, NULL, + xcsi2rxss_irq_handler, IRQF_ONESHOT, + dev_name(dev), xcsi2rxss); + if (ret) { + dev_err(dev, "Err = %d Interrupt handler reg failed!\n", ret); + return ret; + } + + ret = clk_bulk_get(dev, num_clks, xcsi2rxss->clks); + if (ret) + return ret; + + /* TODO: Enable/disable clocks at stream on/off time. */ + ret = clk_bulk_prepare_enable(num_clks, xcsi2rxss->clks); + if (ret) + goto err_clk_put; + + mutex_init(&xcsi2rxss->lock); + + xcsi2rxss_hard_reset(xcsi2rxss); + xcsi2rxss_soft_reset(xcsi2rxss); + + /* Initialize V4L2 subdevice and media entity */ + xcsi2rxss->pads[XVIP_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + xcsi2rxss->pads[XVIP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + /* Initialize the default format */ + xcsi2rxss->default_format.code = + xcsi2rxss_get_nth_mbus(xcsi2rxss->datatype, 0); + xcsi2rxss->default_format.field = V4L2_FIELD_NONE; + xcsi2rxss->default_format.colorspace = V4L2_COLORSPACE_SRGB; + xcsi2rxss->default_format.width = XCSI_DEFAULT_WIDTH; + xcsi2rxss->default_format.height = XCSI_DEFAULT_HEIGHT; + xcsi2rxss->format = xcsi2rxss->default_format; + + /* Initialize V4L2 subdevice and media entity */ + subdev = &xcsi2rxss->subdev; + v4l2_subdev_init(subdev, &xcsi2rxss_ops); + subdev->dev = dev; + strscpy(subdev->name, dev_name(dev), sizeof(subdev->name)); + subdev->flags |= V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_HAS_DEVNODE; + subdev->entity.ops = &xcsi2rxss_media_ops; + v4l2_set_subdevdata(subdev, xcsi2rxss); + + ret = media_entity_pads_init(&subdev->entity, XCSI_MEDIA_PADS, + xcsi2rxss->pads); + if (ret < 0) + goto error; + + platform_set_drvdata(pdev, xcsi2rxss); + + ret = v4l2_async_register_subdev(subdev); + if (ret < 0) { + dev_err(dev, "failed to register subdev\n"); + goto error; + } + + return 0; +error: + media_entity_cleanup(&subdev->entity); + mutex_destroy(&xcsi2rxss->lock); + clk_bulk_disable_unprepare(num_clks, xcsi2rxss->clks); +err_clk_put: + clk_bulk_put(num_clks, xcsi2rxss->clks); + return ret; +} + +static int xcsi2rxss_remove(struct platform_device *pdev) +{ + struct xcsi2rxss_state *xcsi2rxss = platform_get_drvdata(pdev); + struct v4l2_subdev *subdev = &xcsi2rxss->subdev; + int num_clks = ARRAY_SIZE(xcsi2rxss_clks); + + v4l2_async_unregister_subdev(subdev); + media_entity_cleanup(&subdev->entity); + mutex_destroy(&xcsi2rxss->lock); + clk_bulk_disable_unprepare(num_clks, xcsi2rxss->clks); + clk_bulk_put(num_clks, xcsi2rxss->clks); + + return 0; +} + +static const struct of_device_id xcsi2rxss_of_id_table[] = { + { .compatible = "xlnx,mipi-csi2-rx-subsystem-5.0", }, + { } +}; +MODULE_DEVICE_TABLE(of, xcsi2rxss_of_id_table); + +static struct platform_driver xcsi2rxss_driver = { + .driver = { + .name = "xilinx-csi2rxss", + .of_match_table = xcsi2rxss_of_id_table, + }, + .probe = xcsi2rxss_probe, + .remove = xcsi2rxss_remove, +}; + +module_platform_driver(xcsi2rxss_driver); + +MODULE_AUTHOR("Vishal Sagar <vsagar@xilinx.com>"); +MODULE_DESCRIPTION("Xilinx MIPI CSI-2 Rx Subsystem Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/xilinx/xilinx-dma.c b/drivers/media/platform/xilinx/xilinx-dma.c new file mode 100644 index 000000000..0a7fd8642 --- /dev/null +++ b/drivers/media/platform/xilinx/xilinx-dma.c @@ -0,0 +1,759 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx Video DMA + * + * Copyright (C) 2013-2015 Ideas on Board + * Copyright (C) 2013-2015 Xilinx, Inc. + * + * Contacts: Hyun Kwon <hyun.kwon@xilinx.com> + * Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#include <linux/dma/xilinx_dma.h> +#include <linux/lcm.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> + +#include <media/v4l2-dev.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-v4l2.h> +#include <media/videobuf2-dma-contig.h> + +#include "xilinx-dma.h" +#include "xilinx-vip.h" +#include "xilinx-vipp.h" + +#define XVIP_DMA_DEF_WIDTH 1920 +#define XVIP_DMA_DEF_HEIGHT 1080 + +/* Minimum and maximum widths are expressed in bytes */ +#define XVIP_DMA_MIN_WIDTH 1U +#define XVIP_DMA_MAX_WIDTH 65535U +#define XVIP_DMA_MIN_HEIGHT 1U +#define XVIP_DMA_MAX_HEIGHT 8191U + +/* ----------------------------------------------------------------------------- + * Helper functions + */ + +static struct v4l2_subdev * +xvip_dma_remote_subdev(struct media_pad *local, u32 *pad) +{ + struct media_pad *remote; + + remote = media_pad_remote_pad_first(local); + if (!remote || !is_media_entity_v4l2_subdev(remote->entity)) + return NULL; + + if (pad) + *pad = remote->index; + + return media_entity_to_v4l2_subdev(remote->entity); +} + +static int xvip_dma_verify_format(struct xvip_dma *dma) +{ + struct v4l2_subdev_format fmt; + struct v4l2_subdev *subdev; + int ret; + + subdev = xvip_dma_remote_subdev(&dma->pad, &fmt.pad); + if (subdev == NULL) + return -EPIPE; + + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); + if (ret < 0) + return ret == -ENOIOCTLCMD ? -EINVAL : ret; + + if (dma->fmtinfo->code != fmt.format.code || + dma->format.height != fmt.format.height || + dma->format.width != fmt.format.width || + dma->format.colorspace != fmt.format.colorspace) + return -EINVAL; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Pipeline Stream Management + */ + +/** + * xvip_pipeline_start_stop - Start ot stop streaming on a pipeline + * @pipe: The pipeline + * @start: Start (when true) or stop (when false) the pipeline + * + * Walk the entities chain starting at the pipeline output video node and start + * or stop all of them. + * + * Return: 0 if successful, or the return value of the failed video::s_stream + * operation otherwise. + */ +static int xvip_pipeline_start_stop(struct xvip_pipeline *pipe, bool start) +{ + struct xvip_dma *dma = pipe->output; + struct media_entity *entity; + struct media_pad *pad; + struct v4l2_subdev *subdev; + int ret; + + entity = &dma->video.entity; + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad = media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + entity = pad->entity; + subdev = media_entity_to_v4l2_subdev(entity); + + ret = v4l2_subdev_call(subdev, video, s_stream, start); + if (start && ret < 0 && ret != -ENOIOCTLCMD) + return ret; + } + + return 0; +} + +/** + * xvip_pipeline_set_stream - Enable/disable streaming on a pipeline + * @pipe: The pipeline + * @on: Turn the stream on when true or off when false + * + * The pipeline is shared between all DMA engines connect at its input and + * output. While the stream state of DMA engines can be controlled + * independently, pipelines have a shared stream state that enable or disable + * all entities in the pipeline. For this reason the pipeline uses a streaming + * counter that tracks the number of DMA engines that have requested the stream + * to be enabled. + * + * When called with the @on argument set to true, this function will increment + * the pipeline streaming count. If the streaming count reaches the number of + * DMA engines in the pipeline it will enable all entities that belong to the + * pipeline. + * + * Similarly, when called with the @on argument set to false, this function will + * decrement the pipeline streaming count and disable all entities in the + * pipeline when the streaming count reaches zero. + * + * Return: 0 if successful, or the return value of the failed video::s_stream + * operation otherwise. Stopping the pipeline never fails. The pipeline state is + * not updated when the operation fails. + */ +static int xvip_pipeline_set_stream(struct xvip_pipeline *pipe, bool on) +{ + int ret = 0; + + mutex_lock(&pipe->lock); + + if (on) { + if (pipe->stream_count == pipe->num_dmas - 1) { + ret = xvip_pipeline_start_stop(pipe, true); + if (ret < 0) + goto done; + } + pipe->stream_count++; + } else { + if (--pipe->stream_count == 0) + xvip_pipeline_start_stop(pipe, false); + } + +done: + mutex_unlock(&pipe->lock); + return ret; +} + +static int xvip_pipeline_validate(struct xvip_pipeline *pipe, + struct xvip_dma *start) +{ + struct media_graph graph; + struct media_entity *entity = &start->video.entity; + struct media_device *mdev = entity->graph_obj.mdev; + unsigned int num_inputs = 0; + unsigned int num_outputs = 0; + int ret; + + mutex_lock(&mdev->graph_mutex); + + /* Walk the graph to locate the video nodes. */ + ret = media_graph_walk_init(&graph, mdev); + if (ret) { + mutex_unlock(&mdev->graph_mutex); + return ret; + } + + media_graph_walk_start(&graph, entity); + + while ((entity = media_graph_walk_next(&graph))) { + struct xvip_dma *dma; + + if (entity->function != MEDIA_ENT_F_IO_V4L) + continue; + + dma = to_xvip_dma(media_entity_to_video_device(entity)); + + if (dma->pad.flags & MEDIA_PAD_FL_SINK) { + pipe->output = dma; + num_outputs++; + } else { + num_inputs++; + } + } + + mutex_unlock(&mdev->graph_mutex); + + media_graph_walk_cleanup(&graph); + + /* We need exactly one output and zero or one input. */ + if (num_outputs != 1 || num_inputs > 1) + return -EPIPE; + + pipe->num_dmas = num_inputs + num_outputs; + + return 0; +} + +static void __xvip_pipeline_cleanup(struct xvip_pipeline *pipe) +{ + pipe->num_dmas = 0; + pipe->output = NULL; +} + +/** + * xvip_pipeline_cleanup - Cleanup the pipeline after streaming + * @pipe: the pipeline + * + * Decrease the pipeline use count and clean it up if we were the last user. + */ +static void xvip_pipeline_cleanup(struct xvip_pipeline *pipe) +{ + mutex_lock(&pipe->lock); + + /* If we're the last user clean up the pipeline. */ + if (--pipe->use_count == 0) + __xvip_pipeline_cleanup(pipe); + + mutex_unlock(&pipe->lock); +} + +/** + * xvip_pipeline_prepare - Prepare the pipeline for streaming + * @pipe: the pipeline + * @dma: DMA engine at one end of the pipeline + * + * Validate the pipeline if no user exists yet, otherwise just increase the use + * count. + * + * Return: 0 if successful or -EPIPE if the pipeline is not valid. + */ +static int xvip_pipeline_prepare(struct xvip_pipeline *pipe, + struct xvip_dma *dma) +{ + int ret; + + mutex_lock(&pipe->lock); + + /* If we're the first user validate and initialize the pipeline. */ + if (pipe->use_count == 0) { + ret = xvip_pipeline_validate(pipe, dma); + if (ret < 0) { + __xvip_pipeline_cleanup(pipe); + goto done; + } + } + + pipe->use_count++; + ret = 0; + +done: + mutex_unlock(&pipe->lock); + return ret; +} + +/* ----------------------------------------------------------------------------- + * videobuf2 queue operations + */ + +/** + * struct xvip_dma_buffer - Video DMA buffer + * @buf: vb2 buffer base object + * @queue: buffer list entry in the DMA engine queued buffers list + * @dma: DMA channel that uses the buffer + */ +struct xvip_dma_buffer { + struct vb2_v4l2_buffer buf; + struct list_head queue; + struct xvip_dma *dma; +}; + +#define to_xvip_dma_buffer(vb) container_of(vb, struct xvip_dma_buffer, buf) + +static void xvip_dma_complete(void *param) +{ + struct xvip_dma_buffer *buf = param; + struct xvip_dma *dma = buf->dma; + + spin_lock(&dma->queued_lock); + list_del(&buf->queue); + spin_unlock(&dma->queued_lock); + + buf->buf.field = V4L2_FIELD_NONE; + buf->buf.sequence = dma->sequence++; + buf->buf.vb2_buf.timestamp = ktime_get_ns(); + vb2_set_plane_payload(&buf->buf.vb2_buf, 0, dma->format.sizeimage); + vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_DONE); +} + +static int +xvip_dma_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct xvip_dma *dma = vb2_get_drv_priv(vq); + + /* Make sure the image size is large enough. */ + if (*nplanes) + return sizes[0] < dma->format.sizeimage ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = dma->format.sizeimage; + + return 0; +} + +static int xvip_dma_buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct xvip_dma *dma = vb2_get_drv_priv(vb->vb2_queue); + struct xvip_dma_buffer *buf = to_xvip_dma_buffer(vbuf); + + buf->dma = dma; + + return 0; +} + +static void xvip_dma_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct xvip_dma *dma = vb2_get_drv_priv(vb->vb2_queue); + struct xvip_dma_buffer *buf = to_xvip_dma_buffer(vbuf); + struct dma_async_tx_descriptor *desc; + dma_addr_t addr = vb2_dma_contig_plane_dma_addr(vb, 0); + u32 flags; + + if (dma->queue.type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + flags = DMA_PREP_INTERRUPT | DMA_CTRL_ACK; + dma->xt.dir = DMA_DEV_TO_MEM; + dma->xt.src_sgl = false; + dma->xt.dst_sgl = true; + dma->xt.dst_start = addr; + } else { + flags = DMA_PREP_INTERRUPT | DMA_CTRL_ACK; + dma->xt.dir = DMA_MEM_TO_DEV; + dma->xt.src_sgl = true; + dma->xt.dst_sgl = false; + dma->xt.src_start = addr; + } + + dma->xt.frame_size = 1; + dma->sgl[0].size = dma->format.width * dma->fmtinfo->bpp; + dma->sgl[0].icg = dma->format.bytesperline - dma->sgl[0].size; + dma->xt.numf = dma->format.height; + + desc = dmaengine_prep_interleaved_dma(dma->dma, &dma->xt, flags); + if (!desc) { + dev_err(dma->xdev->dev, "Failed to prepare DMA transfer\n"); + vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_ERROR); + return; + } + desc->callback = xvip_dma_complete; + desc->callback_param = buf; + + spin_lock_irq(&dma->queued_lock); + list_add_tail(&buf->queue, &dma->queued_bufs); + spin_unlock_irq(&dma->queued_lock); + + dmaengine_submit(desc); + + if (vb2_is_streaming(&dma->queue)) + dma_async_issue_pending(dma->dma); +} + +static int xvip_dma_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct xvip_dma *dma = vb2_get_drv_priv(vq); + struct xvip_dma_buffer *buf, *nbuf; + struct xvip_pipeline *pipe; + int ret; + + dma->sequence = 0; + + /* + * Start streaming on the pipeline. No link touching an entity in the + * pipeline can be activated or deactivated once streaming is started. + * + * Use the pipeline object embedded in the first DMA object that starts + * streaming. + */ + pipe = to_xvip_pipeline(&dma->video) ? : &dma->pipe; + + ret = video_device_pipeline_start(&dma->video, &pipe->pipe); + if (ret < 0) + goto error; + + /* Verify that the configured format matches the output of the + * connected subdev. + */ + ret = xvip_dma_verify_format(dma); + if (ret < 0) + goto error_stop; + + ret = xvip_pipeline_prepare(pipe, dma); + if (ret < 0) + goto error_stop; + + /* Start the DMA engine. This must be done before starting the blocks + * in the pipeline to avoid DMA synchronization issues. + */ + dma_async_issue_pending(dma->dma); + + /* Start the pipeline. */ + xvip_pipeline_set_stream(pipe, true); + + return 0; + +error_stop: + video_device_pipeline_stop(&dma->video); + +error: + /* Give back all queued buffers to videobuf2. */ + spin_lock_irq(&dma->queued_lock); + list_for_each_entry_safe(buf, nbuf, &dma->queued_bufs, queue) { + vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_QUEUED); + list_del(&buf->queue); + } + spin_unlock_irq(&dma->queued_lock); + + return ret; +} + +static void xvip_dma_stop_streaming(struct vb2_queue *vq) +{ + struct xvip_dma *dma = vb2_get_drv_priv(vq); + struct xvip_pipeline *pipe = to_xvip_pipeline(&dma->video); + struct xvip_dma_buffer *buf, *nbuf; + + /* Stop the pipeline. */ + xvip_pipeline_set_stream(pipe, false); + + /* Stop and reset the DMA engine. */ + dmaengine_terminate_all(dma->dma); + + /* Cleanup the pipeline and mark it as being stopped. */ + xvip_pipeline_cleanup(pipe); + video_device_pipeline_stop(&dma->video); + + /* Give back all queued buffers to videobuf2. */ + spin_lock_irq(&dma->queued_lock); + list_for_each_entry_safe(buf, nbuf, &dma->queued_bufs, queue) { + vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_ERROR); + list_del(&buf->queue); + } + spin_unlock_irq(&dma->queued_lock); +} + +static const struct vb2_ops xvip_dma_queue_qops = { + .queue_setup = xvip_dma_queue_setup, + .buf_prepare = xvip_dma_buffer_prepare, + .buf_queue = xvip_dma_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = xvip_dma_start_streaming, + .stop_streaming = xvip_dma_stop_streaming, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 ioctls + */ + +static int +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 = dma->xdev->v4l2_caps | V4L2_CAP_STREAMING | + V4L2_CAP_DEVICE_CAPS; + + strscpy(cap->driver, "xilinx-vipp", sizeof(cap->driver)); + strscpy(cap->card, dma->video.name, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%pOFn:%u", + dma->xdev->dev->of_node, dma->port); + + return 0; +} + +/* FIXME: without this callback function, some applications are not configured + * with correct formats, and it results in frames in wrong format. Whether this + * callback needs to be required is not clearly defined, so it should be + * clarified through the mailing list. + */ +static int +xvip_dma_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + struct v4l2_fh *vfh = file->private_data; + struct xvip_dma *dma = to_xvip_dma(vfh->vdev); + + if (f->index > 0) + return -EINVAL; + + f->pixelformat = dma->format.pixelformat; + + return 0; +} + +static int +xvip_dma_get_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct v4l2_fh *vfh = file->private_data; + struct xvip_dma *dma = to_xvip_dma(vfh->vdev); + + format->fmt.pix = dma->format; + + return 0; +} + +static void +__xvip_dma_try_format(struct xvip_dma *dma, struct v4l2_pix_format *pix, + const struct xvip_video_format **fmtinfo) +{ + const struct xvip_video_format *info; + unsigned int min_width; + unsigned int max_width; + unsigned int min_bpl; + unsigned int max_bpl; + unsigned int width; + unsigned int align; + unsigned int bpl; + + /* Retrieve format information and select the default format if the + * requested format isn't supported. + */ + info = xvip_get_format_by_fourcc(pix->pixelformat); + + pix->pixelformat = info->fourcc; + pix->field = V4L2_FIELD_NONE; + + /* The transfer alignment requirements are expressed in bytes. Compute + * the minimum and maximum values, clamp the requested width and convert + * it back to pixels. + */ + align = lcm(dma->align, info->bpp); + min_width = roundup(XVIP_DMA_MIN_WIDTH, align); + max_width = rounddown(XVIP_DMA_MAX_WIDTH, align); + width = rounddown(pix->width * info->bpp, align); + + pix->width = clamp(width, min_width, max_width) / info->bpp; + pix->height = clamp(pix->height, XVIP_DMA_MIN_HEIGHT, + XVIP_DMA_MAX_HEIGHT); + + /* Clamp the requested bytes per line value. If the maximum bytes per + * line value is zero, the module doesn't support user configurable line + * sizes. Override the requested value with the minimum in that case. + */ + min_bpl = pix->width * info->bpp; + max_bpl = rounddown(XVIP_DMA_MAX_WIDTH, dma->align); + bpl = rounddown(pix->bytesperline, dma->align); + + pix->bytesperline = clamp(bpl, min_bpl, max_bpl); + pix->sizeimage = pix->bytesperline * pix->height; + + if (fmtinfo) + *fmtinfo = info; +} + +static int +xvip_dma_try_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct v4l2_fh *vfh = file->private_data; + struct xvip_dma *dma = to_xvip_dma(vfh->vdev); + + __xvip_dma_try_format(dma, &format->fmt.pix, NULL); + return 0; +} + +static int +xvip_dma_set_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct v4l2_fh *vfh = file->private_data; + struct xvip_dma *dma = to_xvip_dma(vfh->vdev); + const struct xvip_video_format *info; + + __xvip_dma_try_format(dma, &format->fmt.pix, &info); + + if (vb2_is_busy(&dma->queue)) + return -EBUSY; + + dma->format = format->fmt.pix; + dma->fmtinfo = info; + + return 0; +} + +static const struct v4l2_ioctl_ops xvip_dma_ioctl_ops = { + .vidioc_querycap = xvip_dma_querycap, + .vidioc_enum_fmt_vid_cap = xvip_dma_enum_format, + .vidioc_g_fmt_vid_cap = xvip_dma_get_format, + .vidioc_g_fmt_vid_out = xvip_dma_get_format, + .vidioc_s_fmt_vid_cap = xvip_dma_set_format, + .vidioc_s_fmt_vid_out = xvip_dma_set_format, + .vidioc_try_fmt_vid_cap = xvip_dma_try_format, + .vidioc_try_fmt_vid_out = xvip_dma_try_format, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 file operations + */ + +static const struct v4l2_file_operations xvip_dma_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, +}; + +/* ----------------------------------------------------------------------------- + * Xilinx Video DMA Core + */ + +int xvip_dma_init(struct xvip_composite_device *xdev, struct xvip_dma *dma, + enum v4l2_buf_type type, unsigned int port) +{ + char name[16]; + int ret; + + dma->xdev = xdev; + dma->port = port; + mutex_init(&dma->lock); + mutex_init(&dma->pipe.lock); + INIT_LIST_HEAD(&dma->queued_bufs); + spin_lock_init(&dma->queued_lock); + + dma->fmtinfo = xvip_get_format_by_fourcc(V4L2_PIX_FMT_YUYV); + dma->format.pixelformat = dma->fmtinfo->fourcc; + dma->format.colorspace = V4L2_COLORSPACE_SRGB; + dma->format.field = V4L2_FIELD_NONE; + dma->format.width = XVIP_DMA_DEF_WIDTH; + dma->format.height = XVIP_DMA_DEF_HEIGHT; + dma->format.bytesperline = dma->format.width * dma->fmtinfo->bpp; + dma->format.sizeimage = dma->format.bytesperline * dma->format.height; + + /* Initialize the media entity... */ + dma->pad.flags = type == V4L2_BUF_TYPE_VIDEO_CAPTURE + ? MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&dma->video.entity, 1, &dma->pad); + if (ret < 0) + goto error; + + /* ... and the video node... */ + dma->video.fops = &xvip_dma_fops; + dma->video.v4l2_dev = &xdev->v4l2_dev; + dma->video.queue = &dma->queue; + snprintf(dma->video.name, sizeof(dma->video.name), "%pOFn %s %u", + xdev->dev->of_node, + type == V4L2_BUF_TYPE_VIDEO_CAPTURE ? "output" : "input", + port); + dma->video.vfl_type = VFL_TYPE_VIDEO; + dma->video.vfl_dir = type == V4L2_BUF_TYPE_VIDEO_CAPTURE + ? VFL_DIR_RX : VFL_DIR_TX; + dma->video.release = video_device_release_empty; + dma->video.ioctl_ops = &xvip_dma_ioctl_ops; + dma->video.lock = &dma->lock; + dma->video.device_caps = V4L2_CAP_STREAMING; + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + dma->video.device_caps |= V4L2_CAP_VIDEO_CAPTURE; + else + dma->video.device_caps |= V4L2_CAP_VIDEO_OUTPUT; + + video_set_drvdata(&dma->video, dma); + + /* ... and the buffers queue... */ + /* Don't enable VB2_READ and VB2_WRITE, as using the read() and write() + * V4L2 APIs would be inefficient. Testing on the command line with a + * 'cat /dev/video?' thus won't be possible, but given that the driver + * anyway requires a test tool to setup the pipeline before any video + * stream can be started, requiring a specific V4L2 test tool as well + * instead of 'cat' isn't really a drawback. + */ + dma->queue.type = type; + dma->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + dma->queue.lock = &dma->lock; + dma->queue.drv_priv = dma; + dma->queue.buf_struct_size = sizeof(struct xvip_dma_buffer); + dma->queue.ops = &xvip_dma_queue_qops; + dma->queue.mem_ops = &vb2_dma_contig_memops; + dma->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC + | V4L2_BUF_FLAG_TSTAMP_SRC_EOF; + dma->queue.dev = dma->xdev->dev; + ret = vb2_queue_init(&dma->queue); + if (ret < 0) { + dev_err(dma->xdev->dev, "failed to initialize VB2 queue\n"); + goto error; + } + + /* ... and the DMA channel. */ + snprintf(name, sizeof(name), "port%u", port); + dma->dma = dma_request_chan(dma->xdev->dev, name); + if (IS_ERR(dma->dma)) { + ret = PTR_ERR(dma->dma); + if (ret != -EPROBE_DEFER) + dev_err(dma->xdev->dev, "no VDMA channel found\n"); + goto error; + } + + dma->align = 1 << dma->dma->device->copy_align; + + ret = video_register_device(&dma->video, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + dev_err(dma->xdev->dev, "failed to register video device\n"); + goto error; + } + + return 0; + +error: + xvip_dma_cleanup(dma); + return ret; +} + +void xvip_dma_cleanup(struct xvip_dma *dma) +{ + if (video_is_registered(&dma->video)) + video_unregister_device(&dma->video); + + if (!IS_ERR_OR_NULL(dma->dma)) + dma_release_channel(dma->dma); + + media_entity_cleanup(&dma->video.entity); + + mutex_destroy(&dma->lock); + mutex_destroy(&dma->pipe.lock); +} diff --git a/drivers/media/platform/xilinx/xilinx-dma.h b/drivers/media/platform/xilinx/xilinx-dma.h new file mode 100644 index 000000000..9c6d4c18d --- /dev/null +++ b/drivers/media/platform/xilinx/xilinx-dma.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Xilinx Video DMA + * + * Copyright (C) 2013-2015 Ideas on Board + * Copyright (C) 2013-2015 Xilinx, Inc. + * + * Contacts: Hyun Kwon <hyun.kwon@xilinx.com> + * Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#ifndef __XILINX_VIP_DMA_H__ +#define __XILINX_VIP_DMA_H__ + +#include <linux/dmaengine.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/videodev2.h> + +#include <media/media-entity.h> +#include <media/v4l2-dev.h> +#include <media/videobuf2-v4l2.h> + +struct dma_chan; +struct xvip_composite_device; +struct xvip_video_format; + +/** + * struct xvip_pipeline - Xilinx Video IP pipeline structure + * @pipe: media pipeline + * @lock: protects the pipeline @stream_count + * @use_count: number of DMA engines using the pipeline + * @stream_count: number of DMA engines currently streaming + * @num_dmas: number of DMA engines in the pipeline + * @output: DMA engine at the output of the pipeline + */ +struct xvip_pipeline { + struct media_pipeline pipe; + + struct mutex lock; + unsigned int use_count; + unsigned int stream_count; + + unsigned int num_dmas; + struct xvip_dma *output; +}; + +static inline struct xvip_pipeline *to_xvip_pipeline(struct video_device *vdev) +{ + struct media_pipeline *pipe = video_device_pipeline(vdev); + + if (!pipe) + return NULL; + + return container_of(pipe, struct xvip_pipeline, pipe); +} + +/** + * struct xvip_dma - Video DMA channel + * @list: list entry in a composite device dmas list + * @video: V4L2 video device associated with the DMA channel + * @pad: media pad for the video device entity + * @xdev: composite device the DMA channel belongs to + * @pipe: pipeline belonging to the DMA channel + * @port: composite device DT node port number for the DMA channel + * @lock: protects the @format, @fmtinfo and @queue fields + * @format: active V4L2 pixel format + * @fmtinfo: format information corresponding to the active @format + * @queue: vb2 buffers queue + * @sequence: V4L2 buffers sequence number + * @queued_bufs: list of queued buffers + * @queued_lock: protects the buf_queued list + * @dma: DMA engine channel + * @align: transfer alignment required by the DMA channel (in bytes) + * @xt: dma interleaved template for dma configuration + * @sgl: data chunk structure for dma_interleaved_template + */ +struct xvip_dma { + struct list_head list; + struct video_device video; + struct media_pad pad; + + struct xvip_composite_device *xdev; + struct xvip_pipeline pipe; + unsigned int port; + + struct mutex lock; + struct v4l2_pix_format format; + const struct xvip_video_format *fmtinfo; + + struct vb2_queue queue; + unsigned int sequence; + + struct list_head queued_bufs; + spinlock_t queued_lock; + + struct dma_chan *dma; + unsigned int align; + struct dma_interleaved_template xt; + struct data_chunk sgl[1]; +}; + +#define to_xvip_dma(vdev) container_of(vdev, struct xvip_dma, video) + +int xvip_dma_init(struct xvip_composite_device *xdev, struct xvip_dma *dma, + enum v4l2_buf_type type, unsigned int port); +void xvip_dma_cleanup(struct xvip_dma *dma); + +#endif /* __XILINX_VIP_DMA_H__ */ diff --git a/drivers/media/platform/xilinx/xilinx-tpg.c b/drivers/media/platform/xilinx/xilinx-tpg.c new file mode 100644 index 000000000..0f2d5a0ed --- /dev/null +++ b/drivers/media/platform/xilinx/xilinx-tpg.c @@ -0,0 +1,933 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx Test Pattern Generator + * + * Copyright (C) 2013-2015 Ideas on Board + * Copyright (C) 2013-2015 Xilinx, Inc. + * + * Contacts: Hyun Kwon <hyun.kwon@xilinx.com> + * Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/xilinx-v4l2-controls.h> + +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> + +#include "xilinx-vip.h" +#include "xilinx-vtc.h" + +#define XTPG_CTRL_STATUS_SLAVE_ERROR (1 << 16) +#define XTPG_CTRL_IRQ_SLAVE_ERROR (1 << 16) + +#define XTPG_PATTERN_CONTROL 0x0100 +#define XTPG_PATTERN_MASK (0xf << 0) +#define XTPG_PATTERN_CONTROL_CROSS_HAIRS (1 << 4) +#define XTPG_PATTERN_CONTROL_MOVING_BOX (1 << 5) +#define XTPG_PATTERN_CONTROL_COLOR_MASK_SHIFT 6 +#define XTPG_PATTERN_CONTROL_COLOR_MASK_MASK (0xf << 6) +#define XTPG_PATTERN_CONTROL_STUCK_PIXEL (1 << 9) +#define XTPG_PATTERN_CONTROL_NOISE (1 << 10) +#define XTPG_PATTERN_CONTROL_MOTION (1 << 12) +#define XTPG_MOTION_SPEED 0x0104 +#define XTPG_CROSS_HAIRS 0x0108 +#define XTPG_CROSS_HAIRS_ROW_SHIFT 0 +#define XTPG_CROSS_HAIRS_ROW_MASK (0xfff << 0) +#define XTPG_CROSS_HAIRS_COLUMN_SHIFT 16 +#define XTPG_CROSS_HAIRS_COLUMN_MASK (0xfff << 16) +#define XTPG_ZPLATE_HOR_CONTROL 0x010c +#define XTPG_ZPLATE_VER_CONTROL 0x0110 +#define XTPG_ZPLATE_START_SHIFT 0 +#define XTPG_ZPLATE_START_MASK (0xffff << 0) +#define XTPG_ZPLATE_SPEED_SHIFT 16 +#define XTPG_ZPLATE_SPEED_MASK (0xffff << 16) +#define XTPG_BOX_SIZE 0x0114 +#define XTPG_BOX_COLOR 0x0118 +#define XTPG_STUCK_PIXEL_THRESH 0x011c +#define XTPG_NOISE_GAIN 0x0120 +#define XTPG_BAYER_PHASE 0x0124 +#define XTPG_BAYER_PHASE_RGGB 0 +#define XTPG_BAYER_PHASE_GRBG 1 +#define XTPG_BAYER_PHASE_GBRG 2 +#define XTPG_BAYER_PHASE_BGGR 3 +#define XTPG_BAYER_PHASE_OFF 4 + +/* + * The minimum blanking value is one clock cycle for the front porch, one clock + * cycle for the sync pulse and one clock cycle for the back porch. + */ +#define XTPG_MIN_HBLANK 3 +#define XTPG_MAX_HBLANK (XVTC_MAX_HSIZE - XVIP_MIN_WIDTH) +#define XTPG_MIN_VBLANK 3 +#define XTPG_MAX_VBLANK (XVTC_MAX_VSIZE - XVIP_MIN_HEIGHT) + +/** + * struct xtpg_device - Xilinx Test Pattern Generator device structure + * @xvip: Xilinx Video IP device + * @pads: media pads + * @npads: number of pads (1 or 2) + * @has_input: whether an input is connected to the sink pad + * @formats: active V4L2 media bus format for each pad + * @default_format: default V4L2 media bus format + * @vip_format: format information corresponding to the active format + * @bayer: boolean flag if TPG is set to any bayer format + * @ctrl_handler: control handler + * @hblank: horizontal blanking control + * @vblank: vertical blanking control + * @pattern: test pattern control + * @streaming: is the video stream active + * @vtc: video timing controller + * @vtmux_gpio: video timing mux GPIO + */ +struct xtpg_device { + struct xvip_device xvip; + + struct media_pad pads[2]; + unsigned int npads; + bool has_input; + + struct v4l2_mbus_framefmt formats[2]; + struct v4l2_mbus_framefmt default_format; + const struct xvip_video_format *vip_format; + bool bayer; + + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *pattern; + bool streaming; + + struct xvtc_device *vtc; + struct gpio_desc *vtmux_gpio; +}; + +static inline struct xtpg_device *to_tpg(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct xtpg_device, xvip.subdev); +} + +static u32 xtpg_get_bayer_phase(unsigned int code) +{ + switch (code) { + case MEDIA_BUS_FMT_SRGGB8_1X8: + return XTPG_BAYER_PHASE_RGGB; + case MEDIA_BUS_FMT_SGRBG8_1X8: + return XTPG_BAYER_PHASE_GRBG; + case MEDIA_BUS_FMT_SGBRG8_1X8: + return XTPG_BAYER_PHASE_GBRG; + case MEDIA_BUS_FMT_SBGGR8_1X8: + return XTPG_BAYER_PHASE_BGGR; + default: + return XTPG_BAYER_PHASE_OFF; + } +} + +static void __xtpg_update_pattern_control(struct xtpg_device *xtpg, + bool passthrough, bool pattern) +{ + u32 pattern_mask = (1 << (xtpg->pattern->maximum + 1)) - 1; + + /* + * If the TPG has no sink pad or no input connected to its sink pad + * passthrough mode can't be enabled. + */ + if (xtpg->npads == 1 || !xtpg->has_input) + passthrough = false; + + /* If passthrough mode is allowed unmask bit 0. */ + if (passthrough) + pattern_mask &= ~1; + + /* If test pattern mode is allowed unmask all other bits. */ + if (pattern) + pattern_mask &= 1; + + __v4l2_ctrl_modify_range(xtpg->pattern, 0, xtpg->pattern->maximum, + pattern_mask, pattern ? 9 : 0); +} + +static void xtpg_update_pattern_control(struct xtpg_device *xtpg, + bool passthrough, bool pattern) +{ + mutex_lock(xtpg->ctrl_handler.lock); + __xtpg_update_pattern_control(xtpg, passthrough, pattern); + mutex_unlock(xtpg->ctrl_handler.lock); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Video Operations + */ + +static int xtpg_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct xtpg_device *xtpg = to_tpg(subdev); + unsigned int width = xtpg->formats[0].width; + unsigned int height = xtpg->formats[0].height; + bool passthrough; + u32 bayer_phase; + + if (!enable) { + xvip_stop(&xtpg->xvip); + if (xtpg->vtc) + xvtc_generator_stop(xtpg->vtc); + + xtpg_update_pattern_control(xtpg, true, true); + xtpg->streaming = false; + return 0; + } + + xvip_set_frame_size(&xtpg->xvip, &xtpg->formats[0]); + + if (xtpg->vtc) { + struct xvtc_config config = { + .hblank_start = width, + .hsync_start = width + 1, + .vblank_start = height, + .vsync_start = height + 1, + }; + unsigned int htotal; + unsigned int vtotal; + + htotal = min_t(unsigned int, XVTC_MAX_HSIZE, + v4l2_ctrl_g_ctrl(xtpg->hblank) + width); + vtotal = min_t(unsigned int, XVTC_MAX_VSIZE, + v4l2_ctrl_g_ctrl(xtpg->vblank) + height); + + config.hsync_end = htotal - 1; + config.hsize = htotal; + config.vsync_end = vtotal - 1; + config.vsize = vtotal; + + xvtc_generator_start(xtpg->vtc, &config); + } + + /* + * Configure the bayer phase and video timing mux based on the + * operation mode (passthrough or test pattern generation). The test + * pattern can be modified by the control set handler, we thus need to + * take the control lock here to avoid races. + */ + mutex_lock(xtpg->ctrl_handler.lock); + + xvip_clr_and_set(&xtpg->xvip, XTPG_PATTERN_CONTROL, + XTPG_PATTERN_MASK, xtpg->pattern->cur.val); + + /* + * Switching between passthrough and test pattern generation modes isn't + * allowed during streaming, update the control range accordingly. + */ + passthrough = xtpg->pattern->cur.val == 0; + __xtpg_update_pattern_control(xtpg, passthrough, !passthrough); + + xtpg->streaming = true; + + mutex_unlock(xtpg->ctrl_handler.lock); + + /* + * For TPG v5.0, the bayer phase needs to be off for the pass through + * mode, otherwise the external input would be subsampled. + */ + bayer_phase = passthrough ? XTPG_BAYER_PHASE_OFF + : xtpg_get_bayer_phase(xtpg->formats[0].code); + xvip_write(&xtpg->xvip, XTPG_BAYER_PHASE, bayer_phase); + + if (xtpg->vtmux_gpio) + gpiod_set_value_cansleep(xtpg->vtmux_gpio, !passthrough); + + xvip_start(&xtpg->xvip); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +static struct v4l2_mbus_framefmt * +__xtpg_get_pad_format(struct xtpg_device *xtpg, + struct v4l2_subdev_state *sd_state, + unsigned int pad, u32 which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(&xtpg->xvip.subdev, + sd_state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &xtpg->formats[pad]; + default: + return NULL; + } +} + +static int xtpg_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct xtpg_device *xtpg = to_tpg(subdev); + + fmt->format = *__xtpg_get_pad_format(xtpg, sd_state, fmt->pad, + fmt->which); + + return 0; +} + +static int xtpg_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct xtpg_device *xtpg = to_tpg(subdev); + struct v4l2_mbus_framefmt *__format; + u32 bayer_phase; + + __format = __xtpg_get_pad_format(xtpg, sd_state, fmt->pad, fmt->which); + + /* In two pads mode the source pad format is always identical to the + * sink pad format. + */ + if (xtpg->npads == 2 && fmt->pad == 1) { + fmt->format = *__format; + return 0; + } + + /* Bayer phase is configurable at runtime */ + if (xtpg->bayer) { + bayer_phase = xtpg_get_bayer_phase(fmt->format.code); + if (bayer_phase != XTPG_BAYER_PHASE_OFF) + __format->code = fmt->format.code; + } + + xvip_set_format_size(__format, fmt); + + fmt->format = *__format; + + /* Propagate the format to the source pad. */ + if (xtpg->npads == 2) { + __format = __xtpg_get_pad_format(xtpg, sd_state, 1, + fmt->which); + *__format = fmt->format; + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static int xtpg_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_get_try_format(subdev, sd_state, fse->pad); + + if (fse->index || fse->code != format->code) + return -EINVAL; + + /* Min / max values for pad 0 is always fixed in both one and two pads + * modes. In two pads mode, the source pad(= 1) size is identical to + * the sink pad size */ + if (fse->pad == 0) { + fse->min_width = XVIP_MIN_WIDTH; + fse->max_width = XVIP_MAX_WIDTH; + fse->min_height = XVIP_MIN_HEIGHT; + fse->max_height = XVIP_MAX_HEIGHT; + } else { + fse->min_width = format->width; + fse->max_width = format->width; + fse->min_height = format->height; + fse->max_height = format->height; + } + + return 0; +} + +static int xtpg_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + struct xtpg_device *xtpg = to_tpg(subdev); + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_get_try_format(subdev, fh->state, 0); + *format = xtpg->default_format; + + if (xtpg->npads == 2) { + format = v4l2_subdev_get_try_format(subdev, fh->state, 1); + *format = xtpg->default_format; + } + + return 0; +} + +static int xtpg_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + return 0; +} + +static int xtpg_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct xtpg_device *xtpg = container_of(ctrl->handler, + struct xtpg_device, + ctrl_handler); + switch (ctrl->id) { + case V4L2_CID_TEST_PATTERN: + xvip_clr_and_set(&xtpg->xvip, XTPG_PATTERN_CONTROL, + XTPG_PATTERN_MASK, ctrl->val); + return 0; + case V4L2_CID_XILINX_TPG_CROSS_HAIRS: + xvip_clr_or_set(&xtpg->xvip, XTPG_PATTERN_CONTROL, + XTPG_PATTERN_CONTROL_CROSS_HAIRS, ctrl->val); + return 0; + case V4L2_CID_XILINX_TPG_MOVING_BOX: + xvip_clr_or_set(&xtpg->xvip, XTPG_PATTERN_CONTROL, + XTPG_PATTERN_CONTROL_MOVING_BOX, ctrl->val); + return 0; + case V4L2_CID_XILINX_TPG_COLOR_MASK: + xvip_clr_and_set(&xtpg->xvip, XTPG_PATTERN_CONTROL, + XTPG_PATTERN_CONTROL_COLOR_MASK_MASK, + ctrl->val << + XTPG_PATTERN_CONTROL_COLOR_MASK_SHIFT); + return 0; + case V4L2_CID_XILINX_TPG_STUCK_PIXEL: + xvip_clr_or_set(&xtpg->xvip, XTPG_PATTERN_CONTROL, + XTPG_PATTERN_CONTROL_STUCK_PIXEL, ctrl->val); + return 0; + case V4L2_CID_XILINX_TPG_NOISE: + xvip_clr_or_set(&xtpg->xvip, XTPG_PATTERN_CONTROL, + XTPG_PATTERN_CONTROL_NOISE, ctrl->val); + return 0; + case V4L2_CID_XILINX_TPG_MOTION: + xvip_clr_or_set(&xtpg->xvip, XTPG_PATTERN_CONTROL, + XTPG_PATTERN_CONTROL_MOTION, ctrl->val); + return 0; + case V4L2_CID_XILINX_TPG_MOTION_SPEED: + xvip_write(&xtpg->xvip, XTPG_MOTION_SPEED, ctrl->val); + return 0; + case V4L2_CID_XILINX_TPG_CROSS_HAIR_ROW: + xvip_clr_and_set(&xtpg->xvip, XTPG_CROSS_HAIRS, + XTPG_CROSS_HAIRS_ROW_MASK, + ctrl->val << XTPG_CROSS_HAIRS_ROW_SHIFT); + return 0; + case V4L2_CID_XILINX_TPG_CROSS_HAIR_COLUMN: + xvip_clr_and_set(&xtpg->xvip, XTPG_CROSS_HAIRS, + XTPG_CROSS_HAIRS_COLUMN_MASK, + ctrl->val << XTPG_CROSS_HAIRS_COLUMN_SHIFT); + return 0; + case V4L2_CID_XILINX_TPG_ZPLATE_HOR_START: + xvip_clr_and_set(&xtpg->xvip, XTPG_ZPLATE_HOR_CONTROL, + XTPG_ZPLATE_START_MASK, + ctrl->val << XTPG_ZPLATE_START_SHIFT); + return 0; + case V4L2_CID_XILINX_TPG_ZPLATE_HOR_SPEED: + xvip_clr_and_set(&xtpg->xvip, XTPG_ZPLATE_HOR_CONTROL, + XTPG_ZPLATE_SPEED_MASK, + ctrl->val << XTPG_ZPLATE_SPEED_SHIFT); + return 0; + case V4L2_CID_XILINX_TPG_ZPLATE_VER_START: + xvip_clr_and_set(&xtpg->xvip, XTPG_ZPLATE_VER_CONTROL, + XTPG_ZPLATE_START_MASK, + ctrl->val << XTPG_ZPLATE_START_SHIFT); + return 0; + case V4L2_CID_XILINX_TPG_ZPLATE_VER_SPEED: + xvip_clr_and_set(&xtpg->xvip, XTPG_ZPLATE_VER_CONTROL, + XTPG_ZPLATE_SPEED_MASK, + ctrl->val << XTPG_ZPLATE_SPEED_SHIFT); + return 0; + case V4L2_CID_XILINX_TPG_BOX_SIZE: + xvip_write(&xtpg->xvip, XTPG_BOX_SIZE, ctrl->val); + return 0; + case V4L2_CID_XILINX_TPG_BOX_COLOR: + xvip_write(&xtpg->xvip, XTPG_BOX_COLOR, ctrl->val); + return 0; + case V4L2_CID_XILINX_TPG_STUCK_PIXEL_THRESH: + xvip_write(&xtpg->xvip, XTPG_STUCK_PIXEL_THRESH, ctrl->val); + return 0; + case V4L2_CID_XILINX_TPG_NOISE_GAIN: + xvip_write(&xtpg->xvip, XTPG_NOISE_GAIN, ctrl->val); + return 0; + } + + return 0; +} + +static const struct v4l2_ctrl_ops xtpg_ctrl_ops = { + .s_ctrl = xtpg_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops xtpg_core_ops = { +}; + +static const struct v4l2_subdev_video_ops xtpg_video_ops = { + .s_stream = xtpg_s_stream, +}; + +static const struct v4l2_subdev_pad_ops xtpg_pad_ops = { + .enum_mbus_code = xvip_enum_mbus_code, + .enum_frame_size = xtpg_enum_frame_size, + .get_fmt = xtpg_get_format, + .set_fmt = xtpg_set_format, +}; + +static const struct v4l2_subdev_ops xtpg_ops = { + .core = &xtpg_core_ops, + .video = &xtpg_video_ops, + .pad = &xtpg_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops xtpg_internal_ops = { + .open = xtpg_open, + .close = xtpg_close, +}; + +/* + * Control Config + */ + +static const char *const xtpg_pattern_strings[] = { + "Passthrough", + "Horizontal Ramp", + "Vertical Ramp", + "Temporal Ramp", + "Solid Red", + "Solid Green", + "Solid Blue", + "Solid Black", + "Solid White", + "Color Bars", + "Zone Plate", + "Tartan Color Bars", + "Cross Hatch", + "None", + "Vertical/Horizontal Ramps", + "Black/White Checker Board", +}; + +static struct v4l2_ctrl_config xtpg_ctrls[] = { + { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_CROSS_HAIRS, + .name = "Test Pattern: Cross Hairs", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = false, + .max = true, + .step = 1, + .def = 0, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_MOVING_BOX, + .name = "Test Pattern: Moving Box", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = false, + .max = true, + .step = 1, + .def = 0, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_COLOR_MASK, + .name = "Test Pattern: Color Mask", + .type = V4L2_CTRL_TYPE_BITMASK, + .min = 0, + .max = 0xf, + .def = 0, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_STUCK_PIXEL, + .name = "Test Pattern: Stuck Pixel", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = false, + .max = true, + .step = 1, + .def = 0, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_NOISE, + .name = "Test Pattern: Noise", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = false, + .max = true, + .step = 1, + .def = 0, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_MOTION, + .name = "Test Pattern: Motion", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = false, + .max = true, + .step = 1, + .def = 0, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_MOTION_SPEED, + .name = "Test Pattern: Motion Speed", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 8) - 1, + .step = 1, + .def = 4, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_CROSS_HAIR_ROW, + .name = "Test Pattern: Cross Hairs Row", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 12) - 1, + .step = 1, + .def = 0x64, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_CROSS_HAIR_COLUMN, + .name = "Test Pattern: Cross Hairs Column", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 12) - 1, + .step = 1, + .def = 0x64, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_ZPLATE_HOR_START, + .name = "Test Pattern: Zplate Horizontal Start Pos", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 16) - 1, + .step = 1, + .def = 0x1e, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_ZPLATE_HOR_SPEED, + .name = "Test Pattern: Zplate Horizontal Speed", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 16) - 1, + .step = 1, + .def = 0, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_ZPLATE_VER_START, + .name = "Test Pattern: Zplate Vertical Start Pos", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 16) - 1, + .step = 1, + .def = 1, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_ZPLATE_VER_SPEED, + .name = "Test Pattern: Zplate Vertical Speed", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 16) - 1, + .step = 1, + .def = 0, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_BOX_SIZE, + .name = "Test Pattern: Box Size", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 12) - 1, + .step = 1, + .def = 0x32, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_BOX_COLOR, + .name = "Test Pattern: Box Color(RGB)", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 24) - 1, + .step = 1, + .def = 0, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_STUCK_PIXEL_THRESH, + .name = "Test Pattern: Stuck Pixel threshold", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 16) - 1, + .step = 1, + .def = 0, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, { + .ops = &xtpg_ctrl_ops, + .id = V4L2_CID_XILINX_TPG_NOISE_GAIN, + .name = "Test Pattern: Noise Gain", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = (1 << 8) - 1, + .step = 1, + .def = 0, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, +}; + +/* ----------------------------------------------------------------------------- + * Media Operations + */ + +static const struct media_entity_operations xtpg_media_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* ----------------------------------------------------------------------------- + * Power Management + */ + +static int __maybe_unused xtpg_pm_suspend(struct device *dev) +{ + struct xtpg_device *xtpg = dev_get_drvdata(dev); + + xvip_suspend(&xtpg->xvip); + + return 0; +} + +static int __maybe_unused xtpg_pm_resume(struct device *dev) +{ + struct xtpg_device *xtpg = dev_get_drvdata(dev); + + xvip_resume(&xtpg->xvip); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Platform Device Driver + */ + +static int xtpg_parse_of(struct xtpg_device *xtpg) +{ + struct device *dev = xtpg->xvip.dev; + struct device_node *node = xtpg->xvip.dev->of_node; + struct device_node *ports; + struct device_node *port; + unsigned int nports = 0; + bool has_endpoint = false; + + ports = of_get_child_by_name(node, "ports"); + if (ports == NULL) + ports = node; + + for_each_child_of_node(ports, port) { + const struct xvip_video_format *format; + struct device_node *endpoint; + + if (!of_node_name_eq(port, "port")) + continue; + + format = xvip_of_get_format(port); + if (IS_ERR(format)) { + dev_err(dev, "invalid format in DT"); + of_node_put(port); + return PTR_ERR(format); + } + + /* Get and check the format description */ + if (!xtpg->vip_format) { + xtpg->vip_format = format; + } else if (xtpg->vip_format != format) { + dev_err(dev, "in/out format mismatch in DT"); + of_node_put(port); + return -EINVAL; + } + + if (nports == 0) { + endpoint = of_get_next_child(port, NULL); + if (endpoint) + has_endpoint = true; + of_node_put(endpoint); + } + + /* Count the number of ports. */ + nports++; + } + + if (nports != 1 && nports != 2) { + dev_err(dev, "invalid number of ports %u\n", nports); + return -EINVAL; + } + + xtpg->npads = nports; + if (nports == 2 && has_endpoint) + xtpg->has_input = true; + + return 0; +} + +static int xtpg_probe(struct platform_device *pdev) +{ + struct v4l2_subdev *subdev; + struct xtpg_device *xtpg; + u32 i, bayer_phase; + int ret; + + xtpg = devm_kzalloc(&pdev->dev, sizeof(*xtpg), GFP_KERNEL); + if (!xtpg) + return -ENOMEM; + + xtpg->xvip.dev = &pdev->dev; + + ret = xtpg_parse_of(xtpg); + if (ret < 0) + return ret; + + ret = xvip_init_resources(&xtpg->xvip); + if (ret < 0) + return ret; + + xtpg->vtmux_gpio = devm_gpiod_get_optional(&pdev->dev, "timing", + GPIOD_OUT_HIGH); + if (IS_ERR(xtpg->vtmux_gpio)) { + ret = PTR_ERR(xtpg->vtmux_gpio); + goto error_resource; + } + + xtpg->vtc = xvtc_of_get(pdev->dev.of_node); + if (IS_ERR(xtpg->vtc)) { + ret = PTR_ERR(xtpg->vtc); + goto error_resource; + } + + /* Reset and initialize the core */ + xvip_reset(&xtpg->xvip); + + /* Initialize V4L2 subdevice and media entity. Pad numbers depend on the + * number of pads. + */ + if (xtpg->npads == 2) { + xtpg->pads[0].flags = MEDIA_PAD_FL_SINK; + xtpg->pads[1].flags = MEDIA_PAD_FL_SOURCE; + } else { + xtpg->pads[0].flags = MEDIA_PAD_FL_SOURCE; + } + + /* Initialize the default format */ + xtpg->default_format.code = xtpg->vip_format->code; + xtpg->default_format.field = V4L2_FIELD_NONE; + xtpg->default_format.colorspace = V4L2_COLORSPACE_SRGB; + xvip_get_frame_size(&xtpg->xvip, &xtpg->default_format); + + bayer_phase = xtpg_get_bayer_phase(xtpg->vip_format->code); + if (bayer_phase != XTPG_BAYER_PHASE_OFF) + xtpg->bayer = true; + + xtpg->formats[0] = xtpg->default_format; + if (xtpg->npads == 2) + xtpg->formats[1] = xtpg->default_format; + + /* Initialize V4L2 subdevice and media entity */ + subdev = &xtpg->xvip.subdev; + v4l2_subdev_init(subdev, &xtpg_ops); + subdev->dev = &pdev->dev; + subdev->internal_ops = &xtpg_internal_ops; + strscpy(subdev->name, dev_name(&pdev->dev), sizeof(subdev->name)); + v4l2_set_subdevdata(subdev, xtpg); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + subdev->entity.ops = &xtpg_media_ops; + + ret = media_entity_pads_init(&subdev->entity, xtpg->npads, xtpg->pads); + if (ret < 0) + goto error; + + v4l2_ctrl_handler_init(&xtpg->ctrl_handler, 3 + ARRAY_SIZE(xtpg_ctrls)); + + xtpg->vblank = v4l2_ctrl_new_std(&xtpg->ctrl_handler, &xtpg_ctrl_ops, + V4L2_CID_VBLANK, XTPG_MIN_VBLANK, + XTPG_MAX_VBLANK, 1, 100); + xtpg->hblank = v4l2_ctrl_new_std(&xtpg->ctrl_handler, &xtpg_ctrl_ops, + V4L2_CID_HBLANK, XTPG_MIN_HBLANK, + XTPG_MAX_HBLANK, 1, 100); + xtpg->pattern = v4l2_ctrl_new_std_menu_items(&xtpg->ctrl_handler, + &xtpg_ctrl_ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(xtpg_pattern_strings) - 1, + 1, 9, xtpg_pattern_strings); + + for (i = 0; i < ARRAY_SIZE(xtpg_ctrls); i++) + v4l2_ctrl_new_custom(&xtpg->ctrl_handler, &xtpg_ctrls[i], NULL); + + if (xtpg->ctrl_handler.error) { + dev_err(&pdev->dev, "failed to add controls\n"); + ret = xtpg->ctrl_handler.error; + goto error; + } + subdev->ctrl_handler = &xtpg->ctrl_handler; + + xtpg_update_pattern_control(xtpg, true, true); + + ret = v4l2_ctrl_handler_setup(&xtpg->ctrl_handler); + if (ret < 0) { + dev_err(&pdev->dev, "failed to set controls\n"); + goto error; + } + + platform_set_drvdata(pdev, xtpg); + + xvip_print_version(&xtpg->xvip); + + ret = v4l2_async_register_subdev(subdev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register subdev\n"); + goto error; + } + + return 0; + +error: + v4l2_ctrl_handler_free(&xtpg->ctrl_handler); + media_entity_cleanup(&subdev->entity); + xvtc_put(xtpg->vtc); +error_resource: + xvip_cleanup_resources(&xtpg->xvip); + return ret; +} + +static int xtpg_remove(struct platform_device *pdev) +{ + struct xtpg_device *xtpg = platform_get_drvdata(pdev); + struct v4l2_subdev *subdev = &xtpg->xvip.subdev; + + v4l2_async_unregister_subdev(subdev); + v4l2_ctrl_handler_free(&xtpg->ctrl_handler); + media_entity_cleanup(&subdev->entity); + + xvip_cleanup_resources(&xtpg->xvip); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(xtpg_pm_ops, xtpg_pm_suspend, xtpg_pm_resume); + +static const struct of_device_id xtpg_of_id_table[] = { + { .compatible = "xlnx,v-tpg-5.0" }, + { } +}; +MODULE_DEVICE_TABLE(of, xtpg_of_id_table); + +static struct platform_driver xtpg_driver = { + .driver = { + .name = "xilinx-tpg", + .pm = &xtpg_pm_ops, + .of_match_table = xtpg_of_id_table, + }, + .probe = xtpg_probe, + .remove = xtpg_remove, +}; + +module_platform_driver(xtpg_driver); + +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_DESCRIPTION("Xilinx Test Pattern Generator Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/xilinx/xilinx-vip.c b/drivers/media/platform/xilinx/xilinx-vip.c new file mode 100644 index 000000000..5b214bf7f --- /dev/null +++ b/drivers/media/platform/xilinx/xilinx-vip.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx Video IP Core + * + * Copyright (C) 2013-2015 Ideas on Board + * Copyright (C) 2013-2015 Xilinx, Inc. + * + * Contacts: Hyun Kwon <hyun.kwon@xilinx.com> + * Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#include <linux/clk.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include <dt-bindings/media/xilinx-vip.h> + +#include "xilinx-vip.h" + +/* ----------------------------------------------------------------------------- + * Helper functions + */ + +static const struct xvip_video_format xvip_video_formats[] = { + { XVIP_VF_YUV_422, 8, NULL, MEDIA_BUS_FMT_UYVY8_1X16, + 2, V4L2_PIX_FMT_YUYV }, + { XVIP_VF_YUV_444, 8, NULL, MEDIA_BUS_FMT_VUY8_1X24, + 3, V4L2_PIX_FMT_YUV444 }, + { XVIP_VF_RBG, 8, NULL, MEDIA_BUS_FMT_RBG888_1X24, + 3, 0 }, + { XVIP_VF_MONO_SENSOR, 8, "mono", MEDIA_BUS_FMT_Y8_1X8, + 1, V4L2_PIX_FMT_GREY }, + { XVIP_VF_MONO_SENSOR, 8, "rggb", MEDIA_BUS_FMT_SRGGB8_1X8, + 1, V4L2_PIX_FMT_SRGGB8 }, + { XVIP_VF_MONO_SENSOR, 8, "grbg", MEDIA_BUS_FMT_SGRBG8_1X8, + 1, V4L2_PIX_FMT_SGRBG8 }, + { XVIP_VF_MONO_SENSOR, 8, "gbrg", MEDIA_BUS_FMT_SGBRG8_1X8, + 1, V4L2_PIX_FMT_SGBRG8 }, + { XVIP_VF_MONO_SENSOR, 8, "bggr", MEDIA_BUS_FMT_SBGGR8_1X8, + 1, V4L2_PIX_FMT_SBGGR8 }, + { XVIP_VF_MONO_SENSOR, 12, "mono", MEDIA_BUS_FMT_Y12_1X12, + 2, V4L2_PIX_FMT_Y12 }, +}; + +/** + * xvip_get_format_by_code - Retrieve format information for a media bus code + * @code: the format media bus code + * + * Return: a pointer to the format information structure corresponding to the + * given V4L2 media bus format @code, or ERR_PTR if no corresponding format can + * be found. + */ +const struct xvip_video_format *xvip_get_format_by_code(unsigned int code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(xvip_video_formats); ++i) { + const struct xvip_video_format *format = &xvip_video_formats[i]; + + if (format->code == code) + return format; + } + + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL_GPL(xvip_get_format_by_code); + +/** + * xvip_get_format_by_fourcc - Retrieve format information for a 4CC + * @fourcc: the format 4CC + * + * Return: a pointer to the format information structure corresponding to the + * given V4L2 format @fourcc. If not found, return a pointer to the first + * available format (V4L2_PIX_FMT_YUYV). + */ +const struct xvip_video_format *xvip_get_format_by_fourcc(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(xvip_video_formats); ++i) { + const struct xvip_video_format *format = &xvip_video_formats[i]; + + if (format->fourcc == fourcc) + return format; + } + + return &xvip_video_formats[0]; +} +EXPORT_SYMBOL_GPL(xvip_get_format_by_fourcc); + +/** + * xvip_of_get_format - Parse a device tree node and return format information + * @node: the device tree node + * + * Read the xlnx,video-format, xlnx,video-width and xlnx,cfa-pattern properties + * from the device tree @node passed as an argument and return the corresponding + * format information. + * + * Return: a pointer to the format information structure corresponding to the + * format name and width, or ERR_PTR if no corresponding format can be found. + */ +const struct xvip_video_format *xvip_of_get_format(struct device_node *node) +{ + const char *pattern = "mono"; + unsigned int vf_code; + unsigned int i; + u32 width; + int ret; + + ret = of_property_read_u32(node, "xlnx,video-format", &vf_code); + if (ret < 0) + return ERR_PTR(ret); + + ret = of_property_read_u32(node, "xlnx,video-width", &width); + if (ret < 0) + return ERR_PTR(ret); + + if (vf_code == XVIP_VF_MONO_SENSOR) + of_property_read_string(node, "xlnx,cfa-pattern", &pattern); + + for (i = 0; i < ARRAY_SIZE(xvip_video_formats); ++i) { + const struct xvip_video_format *format = &xvip_video_formats[i]; + + if (format->vf_code != vf_code || format->width != width) + continue; + + if (vf_code == XVIP_VF_MONO_SENSOR && + strcmp(pattern, format->pattern)) + continue; + + return format; + } + + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL_GPL(xvip_of_get_format); + +/** + * xvip_set_format_size - Set the media bus frame format size + * @format: V4L2 frame format on media bus + * @fmt: media bus format + * + * Set the media bus frame format size. The width / height from the subdevice + * format are set to the given media bus format. The new format size is stored + * in @format. The width and height are clamped using default min / max values. + */ +void xvip_set_format_size(struct v4l2_mbus_framefmt *format, + const struct v4l2_subdev_format *fmt) +{ + format->width = clamp_t(unsigned int, fmt->format.width, + XVIP_MIN_WIDTH, XVIP_MAX_WIDTH); + format->height = clamp_t(unsigned int, fmt->format.height, + XVIP_MIN_HEIGHT, XVIP_MAX_HEIGHT); +} +EXPORT_SYMBOL_GPL(xvip_set_format_size); + +/** + * xvip_clr_or_set - Clear or set the register with a bitmask + * @xvip: Xilinx Video IP device + * @addr: address of register + * @mask: bitmask to be set or cleared + * @set: boolean flag indicating whether to set or clear + * + * Clear or set the register at address @addr with a bitmask @mask depending on + * the boolean flag @set. When the flag @set is true, the bitmask is set in + * the register, otherwise the bitmask is cleared from the register + * when the flag @set is false. + * + * Fox example, this function can be used to set a control with a boolean value + * requested by users. If the caller knows whether to set or clear in the first + * place, the caller should call xvip_clr() or xvip_set() directly instead of + * using this function. + */ +void xvip_clr_or_set(struct xvip_device *xvip, u32 addr, u32 mask, bool set) +{ + u32 reg; + + reg = xvip_read(xvip, addr); + reg = set ? reg | mask : reg & ~mask; + xvip_write(xvip, addr, reg); +} +EXPORT_SYMBOL_GPL(xvip_clr_or_set); + +/** + * xvip_clr_and_set - Clear and set the register with a bitmask + * @xvip: Xilinx Video IP device + * @addr: address of register + * @clr: bitmask to be cleared + * @set: bitmask to be set + * + * Clear a bit(s) of mask @clr in the register at address @addr, then set + * a bit(s) of mask @set in the register after. + */ +void xvip_clr_and_set(struct xvip_device *xvip, u32 addr, u32 clr, u32 set) +{ + u32 reg; + + reg = xvip_read(xvip, addr); + reg &= ~clr; + reg |= set; + xvip_write(xvip, addr, reg); +} +EXPORT_SYMBOL_GPL(xvip_clr_and_set); + +int xvip_init_resources(struct xvip_device *xvip) +{ + struct platform_device *pdev = to_platform_device(xvip->dev); + + xvip->iomem = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(xvip->iomem)) + return PTR_ERR(xvip->iomem); + + xvip->clk = devm_clk_get(xvip->dev, NULL); + if (IS_ERR(xvip->clk)) + return PTR_ERR(xvip->clk); + + clk_prepare_enable(xvip->clk); + return 0; +} +EXPORT_SYMBOL_GPL(xvip_init_resources); + +void xvip_cleanup_resources(struct xvip_device *xvip) +{ + clk_disable_unprepare(xvip->clk); +} +EXPORT_SYMBOL_GPL(xvip_cleanup_resources); + +/* ----------------------------------------------------------------------------- + * Subdev operations handlers + */ + +/** + * xvip_enum_mbus_code - Enumerate the media format code + * @subdev: V4L2 subdevice + * @sd_state: V4L2 subdev state + * @code: returning media bus code + * + * Enumerate the media bus code of the subdevice. Return the corresponding + * pad format code. This function only works for subdevices with fixed format + * on all pads. Subdevices with multiple format should have their own + * function to enumerate mbus codes. + * + * Return: 0 if the media bus code is found, or -EINVAL if the format index + * is not valid. + */ +int xvip_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct v4l2_mbus_framefmt *format; + + /* Enumerating frame sizes based on the active configuration isn't + * supported yet. + */ + if (code->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + if (code->index) + return -EINVAL; + + format = v4l2_subdev_get_try_format(subdev, sd_state, code->pad); + + code->code = format->code; + + return 0; +} +EXPORT_SYMBOL_GPL(xvip_enum_mbus_code); + +/** + * xvip_enum_frame_size - Enumerate the media bus frame size + * @subdev: V4L2 subdevice + * @sd_state: V4L2 subdev state + * @fse: returning media bus frame size + * + * This function is a drop-in implementation of the subdev enum_frame_size pad + * operation. It assumes that the subdevice has one sink pad and one source + * pad, and that the format on the source pad is always identical to the + * format on the sink pad. Entities with different requirements need to + * implement their own enum_frame_size handlers. + * + * Return: 0 if the media bus frame size is found, or -EINVAL + * if the index or the code is not valid. + */ +int xvip_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct v4l2_mbus_framefmt *format; + + /* Enumerating frame sizes based on the active configuration isn't + * supported yet. + */ + if (fse->which == V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + format = v4l2_subdev_get_try_format(subdev, sd_state, fse->pad); + + if (fse->index || fse->code != format->code) + return -EINVAL; + + if (fse->pad == XVIP_PAD_SINK) { + fse->min_width = XVIP_MIN_WIDTH; + fse->max_width = XVIP_MAX_WIDTH; + fse->min_height = XVIP_MIN_HEIGHT; + fse->max_height = XVIP_MAX_HEIGHT; + } else { + /* The size on the source pad is fixed and always identical to + * the size on the sink pad. + */ + fse->min_width = format->width; + fse->max_width = format->width; + fse->min_height = format->height; + fse->max_height = format->height; + } + + return 0; +} +EXPORT_SYMBOL_GPL(xvip_enum_frame_size); diff --git a/drivers/media/platform/xilinx/xilinx-vip.h b/drivers/media/platform/xilinx/xilinx-vip.h new file mode 100644 index 000000000..48fe229c5 --- /dev/null +++ b/drivers/media/platform/xilinx/xilinx-vip.h @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Xilinx Video IP Core + * + * Copyright (C) 2013-2015 Ideas on Board + * Copyright (C) 2013-2015 Xilinx, Inc. + * + * Contacts: Hyun Kwon <hyun.kwon@xilinx.com> + * Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#ifndef __XILINX_VIP_H__ +#define __XILINX_VIP_H__ + +#include <linux/bitops.h> +#include <linux/io.h> +#include <media/v4l2-subdev.h> + +struct clk; + +/* + * Minimum and maximum width and height common to most video IP cores. IP + * cores with different requirements must define their own values. + */ +#define XVIP_MIN_WIDTH 32 +#define XVIP_MAX_WIDTH 7680 +#define XVIP_MIN_HEIGHT 32 +#define XVIP_MAX_HEIGHT 7680 + +/* + * Pad IDs. IP cores with multiple inputs or outputs should define their own + * values. + */ +#define XVIP_PAD_SINK 0 +#define XVIP_PAD_SOURCE 1 + +/* Xilinx Video IP Control Registers */ +#define XVIP_CTRL_CONTROL 0x0000 +#define XVIP_CTRL_CONTROL_SW_ENABLE BIT(0) +#define XVIP_CTRL_CONTROL_REG_UPDATE BIT(1) +#define XVIP_CTRL_CONTROL_BYPASS BIT(4) +#define XVIP_CTRL_CONTROL_TEST_PATTERN BIT(5) +#define XVIP_CTRL_CONTROL_FRAME_SYNC_RESET BIT(30) +#define XVIP_CTRL_CONTROL_SW_RESET BIT(31) +#define XVIP_CTRL_STATUS 0x0004 +#define XVIP_CTRL_STATUS_PROC_STARTED BIT(0) +#define XVIP_CTRL_STATUS_EOF BIT(1) +#define XVIP_CTRL_ERROR 0x0008 +#define XVIP_CTRL_ERROR_SLAVE_EOL_EARLY BIT(0) +#define XVIP_CTRL_ERROR_SLAVE_EOL_LATE BIT(1) +#define XVIP_CTRL_ERROR_SLAVE_SOF_EARLY BIT(2) +#define XVIP_CTRL_ERROR_SLAVE_SOF_LATE BIT(3) +#define XVIP_CTRL_IRQ_ENABLE 0x000c +#define XVIP_CTRL_IRQ_ENABLE_PROC_STARTED BIT(0) +#define XVIP_CTRL_IRQ_EOF BIT(1) +#define XVIP_CTRL_VERSION 0x0010 +#define XVIP_CTRL_VERSION_MAJOR_MASK (0xff << 24) +#define XVIP_CTRL_VERSION_MAJOR_SHIFT 24 +#define XVIP_CTRL_VERSION_MINOR_MASK (0xff << 16) +#define XVIP_CTRL_VERSION_MINOR_SHIFT 16 +#define XVIP_CTRL_VERSION_REVISION_MASK (0xf << 12) +#define XVIP_CTRL_VERSION_REVISION_SHIFT 12 +#define XVIP_CTRL_VERSION_PATCH_MASK (0xf << 8) +#define XVIP_CTRL_VERSION_PATCH_SHIFT 8 +#define XVIP_CTRL_VERSION_INTERNAL_MASK (0xff << 0) +#define XVIP_CTRL_VERSION_INTERNAL_SHIFT 0 + +/* Xilinx Video IP Timing Registers */ +#define XVIP_ACTIVE_SIZE 0x0020 +#define XVIP_ACTIVE_VSIZE_MASK (0x7ff << 16) +#define XVIP_ACTIVE_VSIZE_SHIFT 16 +#define XVIP_ACTIVE_HSIZE_MASK (0x7ff << 0) +#define XVIP_ACTIVE_HSIZE_SHIFT 0 +#define XVIP_ENCODING 0x0028 +#define XVIP_ENCODING_NBITS_8 (0 << 4) +#define XVIP_ENCODING_NBITS_10 (1 << 4) +#define XVIP_ENCODING_NBITS_12 (2 << 4) +#define XVIP_ENCODING_NBITS_16 (3 << 4) +#define XVIP_ENCODING_NBITS_MASK (3 << 4) +#define XVIP_ENCODING_NBITS_SHIFT 4 +#define XVIP_ENCODING_VIDEO_FORMAT_YUV422 (0 << 0) +#define XVIP_ENCODING_VIDEO_FORMAT_YUV444 (1 << 0) +#define XVIP_ENCODING_VIDEO_FORMAT_RGB (2 << 0) +#define XVIP_ENCODING_VIDEO_FORMAT_YUV420 (3 << 0) +#define XVIP_ENCODING_VIDEO_FORMAT_MASK (3 << 0) +#define XVIP_ENCODING_VIDEO_FORMAT_SHIFT 0 + +/** + * struct xvip_device - Xilinx Video IP device structure + * @subdev: V4L2 subdevice + * @dev: (OF) device + * @iomem: device I/O register space remapped to kernel virtual memory + * @clk: video core clock + * @saved_ctrl: saved control register for resume / suspend + */ +struct xvip_device { + struct v4l2_subdev subdev; + struct device *dev; + void __iomem *iomem; + struct clk *clk; + u32 saved_ctrl; +}; + +/** + * struct xvip_video_format - Xilinx Video IP video format description + * @vf_code: AXI4 video format code + * @width: AXI4 format width in bits per component + * @pattern: CFA pattern for Mono/Sensor formats + * @code: media bus format code + * @bpp: bytes per pixel (when stored in memory) + * @fourcc: V4L2 pixel format FCC identifier + */ +struct xvip_video_format { + unsigned int vf_code; + unsigned int width; + const char *pattern; + unsigned int code; + unsigned int bpp; + u32 fourcc; +}; + +const struct xvip_video_format *xvip_get_format_by_code(unsigned int code); +const struct xvip_video_format *xvip_get_format_by_fourcc(u32 fourcc); +const struct xvip_video_format *xvip_of_get_format(struct device_node *node); +void xvip_set_format_size(struct v4l2_mbus_framefmt *format, + const struct v4l2_subdev_format *fmt); +int xvip_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code); +int xvip_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse); + +static inline u32 xvip_read(struct xvip_device *xvip, u32 addr) +{ + return ioread32(xvip->iomem + addr); +} + +static inline void xvip_write(struct xvip_device *xvip, u32 addr, u32 value) +{ + iowrite32(value, xvip->iomem + addr); +} + +static inline void xvip_clr(struct xvip_device *xvip, u32 addr, u32 clr) +{ + xvip_write(xvip, addr, xvip_read(xvip, addr) & ~clr); +} + +static inline void xvip_set(struct xvip_device *xvip, u32 addr, u32 set) +{ + xvip_write(xvip, addr, xvip_read(xvip, addr) | set); +} + +void xvip_clr_or_set(struct xvip_device *xvip, u32 addr, u32 mask, bool set); +void xvip_clr_and_set(struct xvip_device *xvip, u32 addr, u32 clr, u32 set); + +int xvip_init_resources(struct xvip_device *xvip); +void xvip_cleanup_resources(struct xvip_device *xvip); + +static inline void xvip_reset(struct xvip_device *xvip) +{ + xvip_write(xvip, XVIP_CTRL_CONTROL, XVIP_CTRL_CONTROL_SW_RESET); +} + +static inline void xvip_start(struct xvip_device *xvip) +{ + xvip_set(xvip, XVIP_CTRL_CONTROL, + XVIP_CTRL_CONTROL_SW_ENABLE | XVIP_CTRL_CONTROL_REG_UPDATE); +} + +static inline void xvip_stop(struct xvip_device *xvip) +{ + xvip_clr(xvip, XVIP_CTRL_CONTROL, XVIP_CTRL_CONTROL_SW_ENABLE); +} + +static inline void xvip_resume(struct xvip_device *xvip) +{ + xvip_write(xvip, XVIP_CTRL_CONTROL, + xvip->saved_ctrl | XVIP_CTRL_CONTROL_SW_ENABLE); +} + +static inline void xvip_suspend(struct xvip_device *xvip) +{ + xvip->saved_ctrl = xvip_read(xvip, XVIP_CTRL_CONTROL); + xvip_write(xvip, XVIP_CTRL_CONTROL, + xvip->saved_ctrl & ~XVIP_CTRL_CONTROL_SW_ENABLE); +} + +static inline void xvip_set_frame_size(struct xvip_device *xvip, + const struct v4l2_mbus_framefmt *format) +{ + xvip_write(xvip, XVIP_ACTIVE_SIZE, + (format->height << XVIP_ACTIVE_VSIZE_SHIFT) | + (format->width << XVIP_ACTIVE_HSIZE_SHIFT)); +} + +static inline void xvip_get_frame_size(struct xvip_device *xvip, + struct v4l2_mbus_framefmt *format) +{ + u32 reg; + + reg = xvip_read(xvip, XVIP_ACTIVE_SIZE); + format->width = (reg & XVIP_ACTIVE_HSIZE_MASK) >> + XVIP_ACTIVE_HSIZE_SHIFT; + format->height = (reg & XVIP_ACTIVE_VSIZE_MASK) >> + XVIP_ACTIVE_VSIZE_SHIFT; +} + +static inline void xvip_enable_reg_update(struct xvip_device *xvip) +{ + xvip_set(xvip, XVIP_CTRL_CONTROL, XVIP_CTRL_CONTROL_REG_UPDATE); +} + +static inline void xvip_disable_reg_update(struct xvip_device *xvip) +{ + xvip_clr(xvip, XVIP_CTRL_CONTROL, XVIP_CTRL_CONTROL_REG_UPDATE); +} + +static inline void xvip_print_version(struct xvip_device *xvip) +{ + u32 version; + + version = xvip_read(xvip, XVIP_CTRL_VERSION); + + dev_info(xvip->dev, "device found, version %u.%02x%x\n", + ((version & XVIP_CTRL_VERSION_MAJOR_MASK) >> + XVIP_CTRL_VERSION_MAJOR_SHIFT), + ((version & XVIP_CTRL_VERSION_MINOR_MASK) >> + XVIP_CTRL_VERSION_MINOR_SHIFT), + ((version & XVIP_CTRL_VERSION_REVISION_MASK) >> + XVIP_CTRL_VERSION_REVISION_SHIFT)); +} + +#endif /* __XILINX_VIP_H__ */ diff --git a/drivers/media/platform/xilinx/xilinx-vipp.c b/drivers/media/platform/xilinx/xilinx-vipp.c new file mode 100644 index 000000000..0a16c218a --- /dev/null +++ b/drivers/media/platform/xilinx/xilinx-vipp.c @@ -0,0 +1,649 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx Video IP Composite Device + * + * Copyright (C) 2013-2015 Ideas on Board + * Copyright (C) 2013-2015 Xilinx, Inc. + * + * Contacts: Hyun Kwon <hyun.kwon@xilinx.com> + * Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#include <linux/list.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <media/v4l2-async.h> +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#include "xilinx-dma.h" +#include "xilinx-vipp.h" + +#define XVIPP_DMA_S2MM 0 +#define XVIPP_DMA_MM2S 1 + +/** + * struct xvip_graph_entity - Entity in the video graph + * @asd: subdev asynchronous registration information + * @entity: media entity, from the corresponding V4L2 subdev + * @subdev: V4L2 subdev + */ +struct xvip_graph_entity { + struct v4l2_async_subdev asd; /* must be first */ + struct media_entity *entity; + struct v4l2_subdev *subdev; +}; + +static inline struct xvip_graph_entity * +to_xvip_entity(struct v4l2_async_subdev *asd) +{ + return container_of(asd, struct xvip_graph_entity, asd); +} + +/* ----------------------------------------------------------------------------- + * Graph Management + */ + +static struct xvip_graph_entity * +xvip_graph_find_entity(struct xvip_composite_device *xdev, + const struct fwnode_handle *fwnode) +{ + struct xvip_graph_entity *entity; + struct v4l2_async_subdev *asd; + + list_for_each_entry(asd, &xdev->notifier.asd_list, asd_list) { + entity = to_xvip_entity(asd); + if (entity->asd.match.fwnode == fwnode) + return entity; + } + + return NULL; +} + +static int xvip_graph_build_one(struct xvip_composite_device *xdev, + struct xvip_graph_entity *entity) +{ + u32 link_flags = MEDIA_LNK_FL_ENABLED; + struct media_entity *local = entity->entity; + struct media_entity *remote; + struct media_pad *local_pad; + struct media_pad *remote_pad; + struct xvip_graph_entity *ent; + struct v4l2_fwnode_link link; + struct fwnode_handle *ep = NULL; + int ret = 0; + + dev_dbg(xdev->dev, "creating links for entity %s\n", local->name); + + while (1) { + /* Get the next endpoint and parse its link. */ + ep = fwnode_graph_get_next_endpoint(entity->asd.match.fwnode, + ep); + if (ep == NULL) + break; + + dev_dbg(xdev->dev, "processing endpoint %p\n", ep); + + ret = v4l2_fwnode_parse_link(ep, &link); + if (ret < 0) { + dev_err(xdev->dev, "failed to parse link for %p\n", + ep); + continue; + } + + /* Skip sink ports, they will be processed from the other end of + * the link. + */ + if (link.local_port >= local->num_pads) { + dev_err(xdev->dev, "invalid port number %u for %p\n", + link.local_port, link.local_node); + v4l2_fwnode_put_link(&link); + ret = -EINVAL; + break; + } + + local_pad = &local->pads[link.local_port]; + + if (local_pad->flags & MEDIA_PAD_FL_SINK) { + dev_dbg(xdev->dev, "skipping sink port %p:%u\n", + link.local_node, link.local_port); + v4l2_fwnode_put_link(&link); + continue; + } + + /* Skip DMA engines, they will be processed separately. */ + if (link.remote_node == of_fwnode_handle(xdev->dev->of_node)) { + dev_dbg(xdev->dev, "skipping DMA port %p:%u\n", + link.local_node, link.local_port); + v4l2_fwnode_put_link(&link); + continue; + } + + /* Find the remote entity. */ + ent = xvip_graph_find_entity(xdev, link.remote_node); + if (ent == NULL) { + dev_err(xdev->dev, "no entity found for %p\n", + link.remote_node); + v4l2_fwnode_put_link(&link); + ret = -ENODEV; + break; + } + + remote = ent->entity; + + if (link.remote_port >= remote->num_pads) { + dev_err(xdev->dev, "invalid port number %u on %p\n", + link.remote_port, link.remote_node); + v4l2_fwnode_put_link(&link); + ret = -EINVAL; + break; + } + + remote_pad = &remote->pads[link.remote_port]; + + v4l2_fwnode_put_link(&link); + + /* Create the media link. */ + dev_dbg(xdev->dev, "creating %s:%u -> %s:%u link\n", + local->name, local_pad->index, + remote->name, remote_pad->index); + + ret = media_create_pad_link(local, local_pad->index, + remote, remote_pad->index, + link_flags); + if (ret < 0) { + dev_err(xdev->dev, + "failed to create %s:%u -> %s:%u link\n", + local->name, local_pad->index, + remote->name, remote_pad->index); + break; + } + } + + fwnode_handle_put(ep); + return ret; +} + +static struct xvip_dma * +xvip_graph_find_dma(struct xvip_composite_device *xdev, unsigned int port) +{ + struct xvip_dma *dma; + + list_for_each_entry(dma, &xdev->dmas, list) { + if (dma->port == port) + return dma; + } + + return NULL; +} + +static int xvip_graph_build_dma(struct xvip_composite_device *xdev) +{ + u32 link_flags = MEDIA_LNK_FL_ENABLED; + struct device_node *node = xdev->dev->of_node; + struct media_entity *source; + struct media_entity *sink; + struct media_pad *source_pad; + struct media_pad *sink_pad; + struct xvip_graph_entity *ent; + struct v4l2_fwnode_link link; + struct device_node *ep = NULL; + struct xvip_dma *dma; + int ret = 0; + + dev_dbg(xdev->dev, "creating links for DMA engines\n"); + + while (1) { + /* Get the next endpoint and parse its link. */ + ep = of_graph_get_next_endpoint(node, ep); + if (ep == NULL) + break; + + dev_dbg(xdev->dev, "processing endpoint %pOF\n", ep); + + ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link); + if (ret < 0) { + dev_err(xdev->dev, "failed to parse link for %pOF\n", + ep); + continue; + } + + /* Find the DMA engine. */ + dma = xvip_graph_find_dma(xdev, link.local_port); + if (dma == NULL) { + dev_err(xdev->dev, "no DMA engine found for port %u\n", + link.local_port); + v4l2_fwnode_put_link(&link); + ret = -EINVAL; + break; + } + + dev_dbg(xdev->dev, "creating link for DMA engine %s\n", + dma->video.name); + + /* Find the remote entity. */ + ent = xvip_graph_find_entity(xdev, link.remote_node); + if (ent == NULL) { + dev_err(xdev->dev, "no entity found for %pOF\n", + to_of_node(link.remote_node)); + v4l2_fwnode_put_link(&link); + ret = -ENODEV; + break; + } + + if (link.remote_port >= ent->entity->num_pads) { + dev_err(xdev->dev, "invalid port number %u on %pOF\n", + link.remote_port, + to_of_node(link.remote_node)); + v4l2_fwnode_put_link(&link); + ret = -EINVAL; + break; + } + + if (dma->pad.flags & MEDIA_PAD_FL_SOURCE) { + source = &dma->video.entity; + source_pad = &dma->pad; + sink = ent->entity; + sink_pad = &sink->pads[link.remote_port]; + } else { + source = ent->entity; + source_pad = &source->pads[link.remote_port]; + sink = &dma->video.entity; + sink_pad = &dma->pad; + } + + v4l2_fwnode_put_link(&link); + + /* Create the media link. */ + dev_dbg(xdev->dev, "creating %s:%u -> %s:%u link\n", + source->name, source_pad->index, + sink->name, sink_pad->index); + + ret = media_create_pad_link(source, source_pad->index, + sink, sink_pad->index, + link_flags); + if (ret < 0) { + dev_err(xdev->dev, + "failed to create %s:%u -> %s:%u link\n", + source->name, source_pad->index, + sink->name, sink_pad->index); + break; + } + } + + of_node_put(ep); + return ret; +} + +static int xvip_graph_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct xvip_composite_device *xdev = + container_of(notifier, struct xvip_composite_device, notifier); + struct xvip_graph_entity *entity; + struct v4l2_async_subdev *asd; + int ret; + + dev_dbg(xdev->dev, "notify complete, all subdevs registered\n"); + + /* Create links for every entity. */ + list_for_each_entry(asd, &xdev->notifier.asd_list, asd_list) { + entity = to_xvip_entity(asd); + ret = xvip_graph_build_one(xdev, entity); + if (ret < 0) + return ret; + } + + /* Create links for DMA channels. */ + ret = xvip_graph_build_dma(xdev); + if (ret < 0) + return ret; + + ret = v4l2_device_register_subdev_nodes(&xdev->v4l2_dev); + if (ret < 0) + dev_err(xdev->dev, "failed to register subdev nodes\n"); + + return media_device_register(&xdev->media_dev); +} + +static int xvip_graph_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *unused) +{ + struct xvip_composite_device *xdev = + container_of(notifier, struct xvip_composite_device, notifier); + struct xvip_graph_entity *entity; + struct v4l2_async_subdev *asd; + + /* Locate the entity corresponding to the bound subdev and store the + * subdev pointer. + */ + list_for_each_entry(asd, &xdev->notifier.asd_list, asd_list) { + entity = to_xvip_entity(asd); + + if (entity->asd.match.fwnode != subdev->fwnode) + continue; + + if (entity->subdev) { + dev_err(xdev->dev, "duplicate subdev for node %p\n", + entity->asd.match.fwnode); + return -EINVAL; + } + + dev_dbg(xdev->dev, "subdev %s bound\n", subdev->name); + entity->entity = &subdev->entity; + entity->subdev = subdev; + return 0; + } + + dev_err(xdev->dev, "no entity for subdev %s\n", subdev->name); + return -EINVAL; +} + +static const struct v4l2_async_notifier_operations xvip_graph_notify_ops = { + .bound = xvip_graph_notify_bound, + .complete = xvip_graph_notify_complete, +}; + +static int xvip_graph_parse_one(struct xvip_composite_device *xdev, + struct fwnode_handle *fwnode) +{ + struct fwnode_handle *remote; + struct fwnode_handle *ep = NULL; + int ret = 0; + + dev_dbg(xdev->dev, "parsing node %p\n", fwnode); + + while (1) { + struct xvip_graph_entity *xge; + + ep = fwnode_graph_get_next_endpoint(fwnode, ep); + if (ep == NULL) + break; + + dev_dbg(xdev->dev, "handling endpoint %p\n", ep); + + remote = fwnode_graph_get_remote_port_parent(ep); + if (remote == NULL) { + ret = -EINVAL; + goto err_notifier_cleanup; + } + + fwnode_handle_put(ep); + + /* Skip entities that we have already processed. */ + if (remote == of_fwnode_handle(xdev->dev->of_node) || + xvip_graph_find_entity(xdev, remote)) { + fwnode_handle_put(remote); + continue; + } + + xge = v4l2_async_nf_add_fwnode(&xdev->notifier, remote, + struct xvip_graph_entity); + fwnode_handle_put(remote); + if (IS_ERR(xge)) { + ret = PTR_ERR(xge); + goto err_notifier_cleanup; + } + } + + return 0; + +err_notifier_cleanup: + v4l2_async_nf_cleanup(&xdev->notifier); + fwnode_handle_put(ep); + return ret; +} + +static int xvip_graph_parse(struct xvip_composite_device *xdev) +{ + struct xvip_graph_entity *entity; + struct v4l2_async_subdev *asd; + int ret; + + /* + * Walk the links to parse the full graph. Start by parsing the + * composite node and then parse entities in turn. The list_for_each + * loop will handle entities added at the end of the list while walking + * the links. + */ + ret = xvip_graph_parse_one(xdev, of_fwnode_handle(xdev->dev->of_node)); + if (ret < 0) + return 0; + + list_for_each_entry(asd, &xdev->notifier.asd_list, asd_list) { + entity = to_xvip_entity(asd); + ret = xvip_graph_parse_one(xdev, entity->asd.match.fwnode); + if (ret < 0) { + v4l2_async_nf_cleanup(&xdev->notifier); + break; + } + } + + return ret; +} + +static int xvip_graph_dma_init_one(struct xvip_composite_device *xdev, + struct device_node *node) +{ + struct xvip_dma *dma; + enum v4l2_buf_type type; + const char *direction; + unsigned int index; + int ret; + + ret = of_property_read_string(node, "direction", &direction); + if (ret < 0) + return ret; + + if (strcmp(direction, "input") == 0) + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + else if (strcmp(direction, "output") == 0) + type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + else + return -EINVAL; + + of_property_read_u32(node, "reg", &index); + + dma = devm_kzalloc(xdev->dev, sizeof(*dma), GFP_KERNEL); + if (dma == NULL) + return -ENOMEM; + + ret = xvip_dma_init(xdev, dma, type, index); + if (ret < 0) { + dev_err(xdev->dev, "%pOF initialization failed\n", node); + return ret; + } + + list_add_tail(&dma->list, &xdev->dmas); + + xdev->v4l2_caps |= type == V4L2_BUF_TYPE_VIDEO_CAPTURE + ? V4L2_CAP_VIDEO_CAPTURE : V4L2_CAP_VIDEO_OUTPUT; + + return 0; +} + +static int xvip_graph_dma_init(struct xvip_composite_device *xdev) +{ + struct device_node *ports; + struct device_node *port; + int ret = 0; + + ports = of_get_child_by_name(xdev->dev->of_node, "ports"); + if (ports == NULL) { + dev_err(xdev->dev, "ports node not present\n"); + return -EINVAL; + } + + for_each_child_of_node(ports, port) { + ret = xvip_graph_dma_init_one(xdev, port); + if (ret) { + of_node_put(port); + break; + } + } + + of_node_put(ports); + return ret; +} + +static void xvip_graph_cleanup(struct xvip_composite_device *xdev) +{ + struct xvip_dma *dmap; + struct xvip_dma *dma; + + v4l2_async_nf_unregister(&xdev->notifier); + v4l2_async_nf_cleanup(&xdev->notifier); + + list_for_each_entry_safe(dma, dmap, &xdev->dmas, list) { + xvip_dma_cleanup(dma); + list_del(&dma->list); + } +} + +static int xvip_graph_init(struct xvip_composite_device *xdev) +{ + int ret; + + /* Init the DMA channels. */ + ret = xvip_graph_dma_init(xdev); + if (ret < 0) { + dev_err(xdev->dev, "DMA initialization failed\n"); + goto done; + } + + /* Parse the graph to extract a list of subdevice DT nodes. */ + ret = xvip_graph_parse(xdev); + if (ret < 0) { + dev_err(xdev->dev, "graph parsing failed\n"); + goto done; + } + + if (list_empty(&xdev->notifier.asd_list)) { + dev_err(xdev->dev, "no subdev found in graph\n"); + ret = -ENOENT; + goto done; + } + + /* Register the subdevices notifier. */ + xdev->notifier.ops = &xvip_graph_notify_ops; + + ret = v4l2_async_nf_register(&xdev->v4l2_dev, &xdev->notifier); + if (ret < 0) { + dev_err(xdev->dev, "notifier registration failed\n"); + goto done; + } + + ret = 0; + +done: + if (ret < 0) + xvip_graph_cleanup(xdev); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Media Controller and V4L2 + */ + +static void xvip_composite_v4l2_cleanup(struct xvip_composite_device *xdev) +{ + v4l2_device_unregister(&xdev->v4l2_dev); + media_device_unregister(&xdev->media_dev); + media_device_cleanup(&xdev->media_dev); +} + +static int xvip_composite_v4l2_init(struct xvip_composite_device *xdev) +{ + int ret; + + xdev->media_dev.dev = xdev->dev; + strscpy(xdev->media_dev.model, "Xilinx Video Composite Device", + sizeof(xdev->media_dev.model)); + xdev->media_dev.hw_revision = 0; + + media_device_init(&xdev->media_dev); + + xdev->v4l2_dev.mdev = &xdev->media_dev; + ret = v4l2_device_register(xdev->dev, &xdev->v4l2_dev); + if (ret < 0) { + dev_err(xdev->dev, "V4L2 device registration failed (%d)\n", + ret); + media_device_cleanup(&xdev->media_dev); + return ret; + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Platform Device Driver + */ + +static int xvip_composite_probe(struct platform_device *pdev) +{ + struct xvip_composite_device *xdev; + int ret; + + xdev = devm_kzalloc(&pdev->dev, sizeof(*xdev), GFP_KERNEL); + if (!xdev) + return -ENOMEM; + + xdev->dev = &pdev->dev; + INIT_LIST_HEAD(&xdev->dmas); + v4l2_async_nf_init(&xdev->notifier); + + ret = xvip_composite_v4l2_init(xdev); + if (ret < 0) + return ret; + + ret = xvip_graph_init(xdev); + if (ret < 0) + goto error; + + platform_set_drvdata(pdev, xdev); + + dev_info(xdev->dev, "device registered\n"); + + return 0; + +error: + xvip_composite_v4l2_cleanup(xdev); + return ret; +} + +static int xvip_composite_remove(struct platform_device *pdev) +{ + struct xvip_composite_device *xdev = platform_get_drvdata(pdev); + + xvip_graph_cleanup(xdev); + xvip_composite_v4l2_cleanup(xdev); + + return 0; +} + +static const struct of_device_id xvip_composite_of_id_table[] = { + { .compatible = "xlnx,video" }, + { } +}; +MODULE_DEVICE_TABLE(of, xvip_composite_of_id_table); + +static struct platform_driver xvip_composite_driver = { + .driver = { + .name = "xilinx-video", + .of_match_table = xvip_composite_of_id_table, + }, + .probe = xvip_composite_probe, + .remove = xvip_composite_remove, +}; + +module_platform_driver(xvip_composite_driver); + +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_DESCRIPTION("Xilinx Video IP Composite Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/xilinx/xilinx-vipp.h b/drivers/media/platform/xilinx/xilinx-vipp.h new file mode 100644 index 000000000..cc52c1854 --- /dev/null +++ b/drivers/media/platform/xilinx/xilinx-vipp.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Xilinx Video IP Composite Device + * + * Copyright (C) 2013-2015 Ideas on Board + * Copyright (C) 2013-2015 Xilinx, Inc. + * + * Contacts: Hyun Kwon <hyun.kwon@xilinx.com> + * Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#ifndef __XILINX_VIPP_H__ +#define __XILINX_VIPP_H__ + +#include <linux/list.h> +#include <linux/mutex.h> +#include <media/media-device.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +/** + * struct xvip_composite_device - Xilinx Video IP device structure + * @v4l2_dev: V4L2 device + * @media_dev: media device + * @dev: (OF) device + * @notifier: V4L2 asynchronous subdevs notifier + * @dmas: list of DMA channels at the pipeline output and input + * @v4l2_caps: V4L2 capabilities of the whole device (see VIDIOC_QUERYCAP) + */ +struct xvip_composite_device { + struct v4l2_device v4l2_dev; + struct media_device media_dev; + struct device *dev; + + struct v4l2_async_notifier notifier; + + struct list_head dmas; + u32 v4l2_caps; +}; + +#endif /* __XILINX_VIPP_H__ */ diff --git a/drivers/media/platform/xilinx/xilinx-vtc.c b/drivers/media/platform/xilinx/xilinx-vtc.c new file mode 100644 index 000000000..0ae0208d7 --- /dev/null +++ b/drivers/media/platform/xilinx/xilinx-vtc.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx Video Timing Controller + * + * Copyright (C) 2013-2015 Ideas on Board + * Copyright (C) 2013-2015 Xilinx, Inc. + * + * Contacts: Hyun Kwon <hyun.kwon@xilinx.com> + * Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include "xilinx-vip.h" +#include "xilinx-vtc.h" + +#define XVTC_CONTROL_FIELD_ID_POL_SRC (1 << 26) +#define XVTC_CONTROL_ACTIVE_CHROMA_POL_SRC (1 << 25) +#define XVTC_CONTROL_ACTIVE_VIDEO_POL_SRC (1 << 24) +#define XVTC_CONTROL_HSYNC_POL_SRC (1 << 23) +#define XVTC_CONTROL_VSYNC_POL_SRC (1 << 22) +#define XVTC_CONTROL_HBLANK_POL_SRC (1 << 21) +#define XVTC_CONTROL_VBLANK_POL_SRC (1 << 20) +#define XVTC_CONTROL_CHROMA_SRC (1 << 18) +#define XVTC_CONTROL_VBLANK_HOFF_SRC (1 << 17) +#define XVTC_CONTROL_VSYNC_END_SRC (1 << 16) +#define XVTC_CONTROL_VSYNC_START_SRC (1 << 15) +#define XVTC_CONTROL_ACTIVE_VSIZE_SRC (1 << 14) +#define XVTC_CONTROL_FRAME_VSIZE_SRC (1 << 13) +#define XVTC_CONTROL_HSYNC_END_SRC (1 << 11) +#define XVTC_CONTROL_HSYNC_START_SRC (1 << 10) +#define XVTC_CONTROL_ACTIVE_HSIZE_SRC (1 << 9) +#define XVTC_CONTROL_FRAME_HSIZE_SRC (1 << 8) +#define XVTC_CONTROL_SYNC_ENABLE (1 << 5) +#define XVTC_CONTROL_DET_ENABLE (1 << 3) +#define XVTC_CONTROL_GEN_ENABLE (1 << 2) + +#define XVTC_STATUS_FSYNC(n) ((n) << 16) +#define XVTC_STATUS_GEN_ACTIVE_VIDEO (1 << 13) +#define XVTC_STATUS_GEN_VBLANK (1 << 12) +#define XVTC_STATUS_DET_ACTIVE_VIDEO (1 << 11) +#define XVTC_STATUS_DET_VBLANK (1 << 10) +#define XVTC_STATUS_LOCK_LOSS (1 << 9) +#define XVTC_STATUS_LOCK (1 << 8) + +#define XVTC_ERROR_ACTIVE_CHROMA_LOCK (1 << 21) +#define XVTC_ERROR_ACTIVE_VIDEO_LOCK (1 << 20) +#define XVTC_ERROR_HSYNC_LOCK (1 << 19) +#define XVTC_ERROR_VSYNC_LOCK (1 << 18) +#define XVTC_ERROR_HBLANK_LOCK (1 << 17) +#define XVTC_ERROR_VBLANK_LOCK (1 << 16) + +#define XVTC_IRQ_ENABLE_FSYNC(n) ((n) << 16) +#define XVTC_IRQ_ENABLE_GEN_ACTIVE_VIDEO (1 << 13) +#define XVTC_IRQ_ENABLE_GEN_VBLANK (1 << 12) +#define XVTC_IRQ_ENABLE_DET_ACTIVE_VIDEO (1 << 11) +#define XVTC_IRQ_ENABLE_DET_VBLANK (1 << 10) +#define XVTC_IRQ_ENABLE_LOCK_LOSS (1 << 9) +#define XVTC_IRQ_ENABLE_LOCK (1 << 8) + +/* + * The following registers exist in two blocks, one at 0x0020 for the detector + * and one at 0x0060 for the generator. + */ + +#define XVTC_DETECTOR_OFFSET 0x0020 +#define XVTC_GENERATOR_OFFSET 0x0060 + +#define XVTC_ACTIVE_SIZE 0x0000 +#define XVTC_ACTIVE_VSIZE_SHIFT 16 +#define XVTC_ACTIVE_VSIZE_MASK (0x1fff << 16) +#define XVTC_ACTIVE_HSIZE_SHIFT 0 +#define XVTC_ACTIVE_HSIZE_MASK (0x1fff << 0) + +#define XVTC_TIMING_STATUS 0x0004 +#define XVTC_TIMING_STATUS_ACTIVE_VIDEO (1 << 2) +#define XVTC_TIMING_STATUS_VBLANK (1 << 1) +#define XVTC_TIMING_STATUS_LOCKED (1 << 0) + +#define XVTC_ENCODING 0x0008 +#define XVTC_ENCODING_CHROMA_PARITY_SHIFT 8 +#define XVTC_ENCODING_CHROMA_PARITY_MASK (3 << 8) +#define XVTC_ENCODING_CHROMA_PARITY_EVEN_ALL (0 << 8) +#define XVTC_ENCODING_CHROMA_PARITY_ODD_ALL (1 << 8) +#define XVTC_ENCODING_CHROMA_PARITY_EVEN_EVEN (2 << 8) +#define XVTC_ENCODING_CHROMA_PARITY_ODD_EVEN (3 << 8) +#define XVTC_ENCODING_VIDEO_FORMAT_SHIFT 0 +#define XVTC_ENCODING_VIDEO_FORMAT_MASK (0xf << 0) +#define XVTC_ENCODING_VIDEO_FORMAT_YUV422 (0 << 0) +#define XVTC_ENCODING_VIDEO_FORMAT_YUV444 (1 << 0) +#define XVTC_ENCODING_VIDEO_FORMAT_RGB (2 << 0) +#define XVTC_ENCODING_VIDEO_FORMAT_YUV420 (3 << 0) + +#define XVTC_POLARITY 0x000c +#define XVTC_POLARITY_ACTIVE_CHROMA_POL (1 << 5) +#define XVTC_POLARITY_ACTIVE_VIDEO_POL (1 << 4) +#define XVTC_POLARITY_HSYNC_POL (1 << 3) +#define XVTC_POLARITY_VSYNC_POL (1 << 2) +#define XVTC_POLARITY_HBLANK_POL (1 << 1) +#define XVTC_POLARITY_VBLANK_POL (1 << 0) + +#define XVTC_HSIZE 0x0010 +#define XVTC_HSIZE_MASK (0x1fff << 0) + +#define XVTC_VSIZE 0x0014 +#define XVTC_VSIZE_MASK (0x1fff << 0) + +#define XVTC_HSYNC 0x0018 +#define XVTC_HSYNC_END_SHIFT 16 +#define XVTC_HSYNC_END_MASK (0x1fff << 16) +#define XVTC_HSYNC_START_SHIFT 0 +#define XVTC_HSYNC_START_MASK (0x1fff << 0) + +#define XVTC_F0_VBLANK_H 0x001c +#define XVTC_F0_VBLANK_HEND_SHIFT 16 +#define XVTC_F0_VBLANK_HEND_MASK (0x1fff << 16) +#define XVTC_F0_VBLANK_HSTART_SHIFT 0 +#define XVTC_F0_VBLANK_HSTART_MASK (0x1fff << 0) + +#define XVTC_F0_VSYNC_V 0x0020 +#define XVTC_F0_VSYNC_VEND_SHIFT 16 +#define XVTC_F0_VSYNC_VEND_MASK (0x1fff << 16) +#define XVTC_F0_VSYNC_VSTART_SHIFT 0 +#define XVTC_F0_VSYNC_VSTART_MASK (0x1fff << 0) + +#define XVTC_F0_VSYNC_H 0x0024 +#define XVTC_F0_VSYNC_HEND_SHIFT 16 +#define XVTC_F0_VSYNC_HEND_MASK (0x1fff << 16) +#define XVTC_F0_VSYNC_HSTART_SHIFT 0 +#define XVTC_F0_VSYNC_HSTART_MASK (0x1fff << 0) + +#define XVTC_FRAME_SYNC_CONFIG(n) (0x0100 + 4 * (n)) +#define XVTC_FRAME_SYNC_V_START_SHIFT 16 +#define XVTC_FRAME_SYNC_V_START_MASK (0x1fff << 16) +#define XVTC_FRAME_SYNC_H_START_SHIFT 0 +#define XVTC_FRAME_SYNC_H_START_MASK (0x1fff << 0) + +#define XVTC_GENERATOR_GLOBAL_DELAY 0x0104 + +/** + * struct xvtc_device - Xilinx Video Timing Controller device structure + * @xvip: Xilinx Video IP device + * @list: entry in the global VTC list + * @has_detector: the VTC has a timing detector + * @has_generator: the VTC has a timing generator + * @config: generator timings configuration + */ +struct xvtc_device { + struct xvip_device xvip; + struct list_head list; + + bool has_detector; + bool has_generator; + + struct xvtc_config config; +}; + +static LIST_HEAD(xvtc_list); +static DEFINE_MUTEX(xvtc_lock); + +static inline void xvtc_gen_write(struct xvtc_device *xvtc, u32 addr, u32 value) +{ + xvip_write(&xvtc->xvip, XVTC_GENERATOR_OFFSET + addr, value); +} + +/* ----------------------------------------------------------------------------- + * Generator Operations + */ + +int xvtc_generator_start(struct xvtc_device *xvtc, + const struct xvtc_config *config) +{ + int ret; + + if (!xvtc->has_generator) + return -ENXIO; + + ret = clk_prepare_enable(xvtc->xvip.clk); + if (ret < 0) + return ret; + + /* We don't care about the chroma active signal, encoding parameters are + * not important for now. + */ + xvtc_gen_write(xvtc, XVTC_POLARITY, + XVTC_POLARITY_ACTIVE_CHROMA_POL | + XVTC_POLARITY_ACTIVE_VIDEO_POL | + XVTC_POLARITY_HSYNC_POL | XVTC_POLARITY_VSYNC_POL | + XVTC_POLARITY_HBLANK_POL | XVTC_POLARITY_VBLANK_POL); + + /* Hardcode the polarity to active high, as required by the video in to + * AXI4-stream core. + */ + xvtc_gen_write(xvtc, XVTC_ENCODING, 0); + + /* Configure the timings. The VBLANK and VSYNC signals assertion and + * deassertion are hardcoded to the first pixel of the line. + */ + xvtc_gen_write(xvtc, XVTC_ACTIVE_SIZE, + (config->vblank_start << XVTC_ACTIVE_VSIZE_SHIFT) | + (config->hblank_start << XVTC_ACTIVE_HSIZE_SHIFT)); + xvtc_gen_write(xvtc, XVTC_HSIZE, config->hsize); + xvtc_gen_write(xvtc, XVTC_VSIZE, config->vsize); + xvtc_gen_write(xvtc, XVTC_HSYNC, + (config->hsync_end << XVTC_HSYNC_END_SHIFT) | + (config->hsync_start << XVTC_HSYNC_START_SHIFT)); + xvtc_gen_write(xvtc, XVTC_F0_VBLANK_H, 0); + xvtc_gen_write(xvtc, XVTC_F0_VSYNC_V, + (config->vsync_end << XVTC_F0_VSYNC_VEND_SHIFT) | + (config->vsync_start << XVTC_F0_VSYNC_VSTART_SHIFT)); + xvtc_gen_write(xvtc, XVTC_F0_VSYNC_H, 0); + + /* Enable the generator. Set the source of all generator parameters to + * generator registers. + */ + xvip_write(&xvtc->xvip, XVIP_CTRL_CONTROL, + XVTC_CONTROL_ACTIVE_CHROMA_POL_SRC | + XVTC_CONTROL_ACTIVE_VIDEO_POL_SRC | + XVTC_CONTROL_HSYNC_POL_SRC | XVTC_CONTROL_VSYNC_POL_SRC | + XVTC_CONTROL_HBLANK_POL_SRC | XVTC_CONTROL_VBLANK_POL_SRC | + XVTC_CONTROL_CHROMA_SRC | XVTC_CONTROL_VBLANK_HOFF_SRC | + XVTC_CONTROL_VSYNC_END_SRC | XVTC_CONTROL_VSYNC_START_SRC | + XVTC_CONTROL_ACTIVE_VSIZE_SRC | + XVTC_CONTROL_FRAME_VSIZE_SRC | XVTC_CONTROL_HSYNC_END_SRC | + XVTC_CONTROL_HSYNC_START_SRC | + XVTC_CONTROL_ACTIVE_HSIZE_SRC | + XVTC_CONTROL_FRAME_HSIZE_SRC | XVTC_CONTROL_GEN_ENABLE | + XVIP_CTRL_CONTROL_REG_UPDATE); + + return 0; +} +EXPORT_SYMBOL_GPL(xvtc_generator_start); + +int xvtc_generator_stop(struct xvtc_device *xvtc) +{ + if (!xvtc->has_generator) + return -ENXIO; + + xvip_write(&xvtc->xvip, XVIP_CTRL_CONTROL, 0); + + clk_disable_unprepare(xvtc->xvip.clk); + + return 0; +} +EXPORT_SYMBOL_GPL(xvtc_generator_stop); + +struct xvtc_device *xvtc_of_get(struct device_node *np) +{ + struct device_node *xvtc_node; + struct xvtc_device *found = NULL; + struct xvtc_device *xvtc; + + if (!of_find_property(np, "xlnx,vtc", NULL)) + return NULL; + + xvtc_node = of_parse_phandle(np, "xlnx,vtc", 0); + if (xvtc_node == NULL) + return ERR_PTR(-EINVAL); + + mutex_lock(&xvtc_lock); + list_for_each_entry(xvtc, &xvtc_list, list) { + if (xvtc->xvip.dev->of_node == xvtc_node) { + found = xvtc; + break; + } + } + mutex_unlock(&xvtc_lock); + + of_node_put(xvtc_node); + + if (!found) + return ERR_PTR(-EPROBE_DEFER); + + return found; +} +EXPORT_SYMBOL_GPL(xvtc_of_get); + +void xvtc_put(struct xvtc_device *xvtc) +{ +} +EXPORT_SYMBOL_GPL(xvtc_put); + +/* ----------------------------------------------------------------------------- + * Registration and Unregistration + */ + +static void xvtc_register_device(struct xvtc_device *xvtc) +{ + mutex_lock(&xvtc_lock); + list_add_tail(&xvtc->list, &xvtc_list); + mutex_unlock(&xvtc_lock); +} + +static void xvtc_unregister_device(struct xvtc_device *xvtc) +{ + mutex_lock(&xvtc_lock); + list_del(&xvtc->list); + mutex_unlock(&xvtc_lock); +} + +/* ----------------------------------------------------------------------------- + * Platform Device Driver + */ + +static int xvtc_parse_of(struct xvtc_device *xvtc) +{ + struct device_node *node = xvtc->xvip.dev->of_node; + + xvtc->has_detector = of_property_read_bool(node, "xlnx,detector"); + xvtc->has_generator = of_property_read_bool(node, "xlnx,generator"); + + return 0; +} + +static int xvtc_probe(struct platform_device *pdev) +{ + struct xvtc_device *xvtc; + int ret; + + xvtc = devm_kzalloc(&pdev->dev, sizeof(*xvtc), GFP_KERNEL); + if (!xvtc) + return -ENOMEM; + + xvtc->xvip.dev = &pdev->dev; + + ret = xvtc_parse_of(xvtc); + if (ret < 0) + return ret; + + ret = xvip_init_resources(&xvtc->xvip); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, xvtc); + + xvip_print_version(&xvtc->xvip); + + xvtc_register_device(xvtc); + + return 0; +} + +static int xvtc_remove(struct platform_device *pdev) +{ + struct xvtc_device *xvtc = platform_get_drvdata(pdev); + + xvtc_unregister_device(xvtc); + + xvip_cleanup_resources(&xvtc->xvip); + + return 0; +} + +static const struct of_device_id xvtc_of_id_table[] = { + { .compatible = "xlnx,v-tc-6.1" }, + { } +}; +MODULE_DEVICE_TABLE(of, xvtc_of_id_table); + +static struct platform_driver xvtc_driver = { + .driver = { + .name = "xilinx-vtc", + .of_match_table = xvtc_of_id_table, + }, + .probe = xvtc_probe, + .remove = xvtc_remove, +}; + +module_platform_driver(xvtc_driver); + +MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); +MODULE_DESCRIPTION("Xilinx Video Timing Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/xilinx/xilinx-vtc.h b/drivers/media/platform/xilinx/xilinx-vtc.h new file mode 100644 index 000000000..855845911 --- /dev/null +++ b/drivers/media/platform/xilinx/xilinx-vtc.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Xilinx Video Timing Controller + * + * Copyright (C) 2013-2015 Ideas on Board + * Copyright (C) 2013-2015 Xilinx, Inc. + * + * Contacts: Hyun Kwon <hyun.kwon@xilinx.com> + * Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#ifndef __XILINX_VTC_H__ +#define __XILINX_VTC_H__ + +struct device_node; +struct xvtc_device; + +#define XVTC_MAX_HSIZE 8191 +#define XVTC_MAX_VSIZE 8191 + +struct xvtc_config { + unsigned int hblank_start; + unsigned int hsync_start; + unsigned int hsync_end; + unsigned int hsize; + unsigned int vblank_start; + unsigned int vsync_start; + unsigned int vsync_end; + unsigned int vsize; +}; + +struct xvtc_device *xvtc_of_get(struct device_node *np); +void xvtc_put(struct xvtc_device *xvtc); + +int xvtc_generator_start(struct xvtc_device *xvtc, + const struct xvtc_config *config); +int xvtc_generator_stop(struct xvtc_device *xvtc); + +#endif /* __XILINX_VTC_H__ */ |