diff options
Diffstat (limited to 'drivers/media/platform')
90 files changed, 17790 insertions, 1332 deletions
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index 041cb80a26b1..1313cd533436 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -74,6 +74,13 @@ config VIDEO_M32R_AR_M64278 To compile this driver as a module, choose M here: the module will be called arv. +config VIDEO_MUX + tristate "Video Multiplexer" + depends on OF && VIDEO_V4L2_SUBDEV_API && MEDIA_CONTROLLER + select REGMAP + help + This driver provides support for N:1 video bus multiplexers. + config VIDEO_OMAP3 tristate "OMAP 3 Camera support" depends on VIDEO_V4L2 && I2C && VIDEO_V4L2_SUBDEV_API && ARCH_OMAP3 @@ -82,6 +89,7 @@ config VIDEO_OMAP3 select ARM_DMA_USE_IOMMU select VIDEOBUF2_DMA_CONTIG select MFD_SYSCON + select V4L2_FWNODE ---help--- Driver for an OMAP 3 camera controller. @@ -97,6 +105,7 @@ config VIDEO_PXA27x depends on PXA27x || COMPILE_TEST select VIDEOBUF2_DMA_SG select SG_SPLIT + select V4L2_FWNODE ---help--- This is a v4l2 driver for the PXA27x Quick Capture Interface @@ -114,6 +123,19 @@ config VIDEO_S3C_CAMIF To compile this driver as a module, choose M here: the module will be called s3c-camif. +config VIDEO_STM32_DCMI + tristate "STM32 Digital Camera Memory Interface (DCMI) support" + depends on VIDEO_V4L2 && OF && HAS_DMA + depends on ARCH_STM32 || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + ---help--- + This module makes the STM32 Digital Camera Memory Interface (DCMI) + available as a v4l2 device. + + To compile this driver as a module, choose M here: the module + will be called stm32-dcmi. + source "drivers/media/platform/soc_camera/Kconfig" source "drivers/media/platform/exynos4-is/Kconfig" source "drivers/media/platform/am437x/Kconfig" @@ -127,6 +149,7 @@ config VIDEO_TI_CAL depends on SOC_DRA7XX || COMPILE_TEST depends on HAS_DMA select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE default n ---help--- Support for the TI CAL (Camera Adaptation Layer) block @@ -448,6 +471,20 @@ config VIDEO_TI_VPE_DEBUG ---help--- Enable debug messages on VPE driver. +config VIDEO_QCOM_VENUS + tristate "Qualcomm Venus V4L2 encoder/decoder driver" + depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA + depends on (ARCH_QCOM && IOMMU_DMA) || COMPILE_TEST + select QCOM_MDT_LOADER if (ARM || ARM64) + select QCOM_SCM if (ARM || ARM64) + select VIDEOBUF2_DMA_SG + select V4L2_MEM2MEM_DEV + ---help--- + This is a V4L2 driver for Qualcomm Venus video accelerator + hardware. It accelerates encoding and decoding operations + on various Qualcomm SoCs. + To compile this driver as a module choose m here. + endif # V4L_MEM2MEM_DRIVERS # TI VIDEO PORT Helper Modules @@ -521,4 +558,41 @@ config VIDEO_STI_HDMI_CEC CEC bus is present in the HDMI connector and enables communication between compatible devices. +config VIDEO_STM32_HDMI_CEC + tristate "STMicroelectronics STM32 HDMI CEC driver" + depends on ARCH_STM32 || COMPILE_TEST + select REGMAP + select REGMAP_MMIO + select CEC_CORE + ---help--- + This is a driver for STM32 interface. It uses the + generic CEC framework interface. + CEC bus is present in the HDMI connector and enables communication + between compatible devices. + endif #CEC_PLATFORM_DRIVERS + +menuconfig SDR_PLATFORM_DRIVERS + bool "SDR platform devices" + depends on MEDIA_SDR_SUPPORT + default n + ---help--- + Say Y here to enable support for platform-specific SDR Drivers. + +if SDR_PLATFORM_DRIVERS + +config VIDEO_RCAR_DRIF + tristate "Renesas Digitial Radio Interface (DRIF)" + depends on VIDEO_V4L2 && HAS_DMA + depends on ARCH_RENESAS || COMPILE_TEST + select VIDEOBUF2_VMALLOC + ---help--- + Say Y if you want to enable R-Car Gen3 DRIF support. DRIF is Digital + Radio Interface that interfaces with an RF front end chip. It is a + receiver of digital data which uses DMA to transfer received data to + a configured location for an application to use. + + To compile this driver as a module, choose M here; the module + will be called rcar_drif. + +endif # SDR_PLATFORM_DRIVERS diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 63303d63c64c..9beadc760467 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -28,6 +28,8 @@ obj-$(CONFIG_VIDEO_SH_VEU) += sh_veu.o obj-$(CONFIG_VIDEO_MEM2MEM_DEINTERLACE) += m2m-deinterlace.o +obj-$(CONFIG_VIDEO_MUX) += video-mux.o + obj-$(CONFIG_VIDEO_S3C_CAMIF) += s3c-camif/ obj-$(CONFIG_VIDEO_SAMSUNG_EXYNOS4_IS) += exynos4-is/ obj-$(CONFIG_VIDEO_SAMSUNG_S5P_JPEG) += s5p-jpeg/ @@ -44,14 +46,17 @@ obj-$(CONFIG_VIDEO_STI_HDMI_CEC) += sti/cec/ obj-$(CONFIG_VIDEO_STI_DELTA) += sti/delta/ -obj-$(CONFIG_BLACKFIN) += blackfin/ +obj-y += stm32/ + +obj-y += blackfin/ -obj-$(CONFIG_ARCH_DAVINCI) += davinci/ +obj-y += davinci/ obj-$(CONFIG_VIDEO_SH_VOU) += sh_vou.o obj-$(CONFIG_SOC_CAMERA) += soc_camera/ +obj-$(CONFIG_VIDEO_RCAR_DRIF) += rcar_drif.o obj-$(CONFIG_VIDEO_RENESAS_FCP) += rcar-fcp.o obj-$(CONFIG_VIDEO_RENESAS_FDP1) += rcar_fdp1.o obj-$(CONFIG_VIDEO_RENESAS_JPU) += rcar_jpu.o @@ -68,6 +73,8 @@ obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin/ obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel/ obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel/ +obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32/ + ccflags-y += -I$(srctree)/drivers/media/i2c obj-$(CONFIG_VIDEO_MEDIATEK_VPU) += mtk-vpu/ @@ -77,3 +84,5 @@ obj-$(CONFIG_VIDEO_MEDIATEK_VCODEC) += mtk-vcodec/ obj-$(CONFIG_VIDEO_MEDIATEK_MDP) += mtk-mdp/ obj-$(CONFIG_VIDEO_MEDIATEK_JPEG) += mtk-jpeg/ + +obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/ diff --git a/drivers/media/platform/am437x/Kconfig b/drivers/media/platform/am437x/Kconfig index 42d9c186710a..160e77e9a0fb 100644 --- a/drivers/media/platform/am437x/Kconfig +++ b/drivers/media/platform/am437x/Kconfig @@ -3,6 +3,7 @@ config VIDEO_AM437X_VPFE depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && HAS_DMA depends on SOC_AM43XX || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE help Support for AM437x Video Processing Front End based Video Capture Driver. diff --git a/drivers/media/platform/am437x/am437x-vpfe.c b/drivers/media/platform/am437x/am437x-vpfe.c index 05489a401c5c..466aba8b0e00 100644 --- a/drivers/media/platform/am437x/am437x-vpfe.c +++ b/drivers/media/platform/am437x/am437x-vpfe.c @@ -26,6 +26,7 @@ #include <linux/interrupt.h> #include <linux/io.h> #include <linux/module.h> +#include <linux/of_graph.h> #include <linux/pinctrl/consumer.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> @@ -36,7 +37,7 @@ #include <media/v4l2-common.h> #include <media/v4l2-ctrls.h> #include <media/v4l2-event.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include "am437x-vpfe.h" @@ -2303,7 +2304,8 @@ vpfe_async_bound(struct v4l2_async_notifier *notifier, vpfe_dbg(1, vpfe, "vpfe_async_bound\n"); for (i = 0; i < ARRAY_SIZE(vpfe->cfg->asd); i++) { - if (vpfe->cfg->asd[i]->match.of.node == asd[i].match.of.node) { + if (vpfe->cfg->asd[i]->match.fwnode.fwnode == + asd[i].match.fwnode.fwnode) { sdinfo = &vpfe->cfg->sub_devs[i]; vpfe->sd[i] = subdev; vpfe->sd[i]->grp_id = sdinfo->grp_id; @@ -2419,7 +2421,7 @@ static struct vpfe_config * vpfe_get_pdata(struct platform_device *pdev) { struct device_node *endpoint = NULL; - struct v4l2_of_endpoint bus_cfg; + struct v4l2_fwnode_endpoint bus_cfg; struct vpfe_subdev_info *sdinfo; struct vpfe_config *pdata; unsigned int flags; @@ -2463,7 +2465,8 @@ vpfe_get_pdata(struct platform_device *pdev) sdinfo->vpfe_param.if_type = VPFE_RAW_BAYER; } - err = v4l2_of_parse_endpoint(endpoint, &bus_cfg); + err = v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), + &bus_cfg); if (err) { dev_err(&pdev->dev, "Could not parse the endpoint\n"); goto done; @@ -2501,8 +2504,8 @@ vpfe_get_pdata(struct platform_device *pdev) goto done; } - pdata->asd[i]->match_type = V4L2_ASYNC_MATCH_OF; - pdata->asd[i]->match.of.node = rem; + pdata->asd[i]->match_type = V4L2_ASYNC_MATCH_FWNODE; + pdata->asd[i]->match.fwnode.fwnode = of_fwnode_handle(rem); of_node_put(rem); } diff --git a/drivers/media/platform/atmel/Kconfig b/drivers/media/platform/atmel/Kconfig index 9bd0f19b127f..55de751e5f51 100644 --- a/drivers/media/platform/atmel/Kconfig +++ b/drivers/media/platform/atmel/Kconfig @@ -4,6 +4,7 @@ config VIDEO_ATMEL_ISC depends on ARCH_AT91 || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG select REGMAP_MMIO + select V4L2_FWNODE help This module makes the ATMEL Image Sensor Controller available as a v4l2 device. @@ -13,6 +14,7 @@ config VIDEO_ATMEL_ISI depends on VIDEO_V4L2 && OF && HAS_DMA depends on ARCH_AT91 || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE ---help--- This module makes the ATMEL Image Sensor Interface available as a v4l2 device. diff --git a/drivers/media/platform/atmel/atmel-isc.c b/drivers/media/platform/atmel/atmel-isc.c index c4b2115559a5..d6534252cdcd 100644 --- a/drivers/media/platform/atmel/atmel-isc.c +++ b/drivers/media/platform/atmel/atmel-isc.c @@ -32,6 +32,7 @@ #include <linux/math64.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/of_graph.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/regmap.h> @@ -42,7 +43,7 @@ #include <media/v4l2-event.h> #include <media/v4l2-image-sizes.h> #include <media/v4l2-ioctl.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-subdev.h> #include <media/videobuf2-dma-contig.h> @@ -239,13 +240,11 @@ static struct isc_format isc_formats[] = { { V4L2_PIX_FMT_YUV420, 0x0, 12, ISC_PFE_CFG0_BPS_EIGHT, ISC_BAY_CFG_BGBG, ISC_RLP_CFG_MODE_YYCC, - ISC_DCFG_IMODE_YC420P | ISC_DCFG_YMBSIZE_BEATS8 | - ISC_DCFG_CMBSIZE_BEATS8, ISC_DCTRL_DVIEW_PLANAR, 0x7fb, + ISC_DCFG_IMODE_YC420P, ISC_DCTRL_DVIEW_PLANAR, 0x7fb, false, false }, { V4L2_PIX_FMT_YUV422P, 0x0, 16, ISC_PFE_CFG0_BPS_EIGHT, ISC_BAY_CFG_BGBG, ISC_RLP_CFG_MODE_YYCC, - ISC_DCFG_IMODE_YC422P | ISC_DCFG_YMBSIZE_BEATS8 | - ISC_DCFG_CMBSIZE_BEATS8, ISC_DCTRL_DVIEW_PLANAR, 0x3fb, + ISC_DCFG_IMODE_YC422P, ISC_DCTRL_DVIEW_PLANAR, 0x3fb, false, false }, { V4L2_PIX_FMT_RGB565, MEDIA_BUS_FMT_RGB565_2X8_LE, 16, ISC_PFE_CFG0_BPS_EIGHT, ISC_BAY_CFG_BGBG, ISC_RLP_CFG_MODE_RGB565, @@ -700,8 +699,10 @@ static void isc_set_histogram(struct isc_device *isc) } static inline void isc_get_param(const struct isc_format *fmt, - u32 *rlp_mode, u32 *dcfg_imode) + u32 *rlp_mode, u32 *dcfg) { + *dcfg = ISC_DCFG_YMBSIZE_BEATS8; + switch (fmt->fourcc) { case V4L2_PIX_FMT_SBGGR10: case V4L2_PIX_FMT_SGBRG10: @@ -712,11 +713,11 @@ static inline void isc_get_param(const struct isc_format *fmt, case V4L2_PIX_FMT_SGRBG12: case V4L2_PIX_FMT_SRGGB12: *rlp_mode = fmt->reg_rlp_mode; - *dcfg_imode = fmt->reg_dcfg_imode; + *dcfg |= fmt->reg_dcfg_imode; break; default: *rlp_mode = ISC_RLP_CFG_MODE_DAT8; - *dcfg_imode = ISC_DCFG_IMODE_PACKED8; + *dcfg |= ISC_DCFG_IMODE_PACKED8; break; } } @@ -726,18 +727,19 @@ static int isc_configure(struct isc_device *isc) struct regmap *regmap = isc->regmap; const struct isc_format *current_fmt = isc->current_fmt; struct isc_subdev_entity *subdev = isc->current_subdev; - u32 pfe_cfg0, rlp_mode, dcfg_imode, mask, pipeline; + u32 pfe_cfg0, rlp_mode, dcfg, mask, pipeline; if (sensor_is_preferred(current_fmt)) { pfe_cfg0 = current_fmt->reg_bps; pipeline = 0x0; - isc_get_param(current_fmt, &rlp_mode, &dcfg_imode); + isc_get_param(current_fmt, &rlp_mode, &dcfg); isc->ctrls.hist_stat = HIST_INIT; } else { pfe_cfg0 = isc->raw_fmt->reg_bps; pipeline = current_fmt->pipeline; rlp_mode = current_fmt->reg_rlp_mode; - dcfg_imode = current_fmt->reg_dcfg_imode; + dcfg = current_fmt->reg_dcfg_imode | ISC_DCFG_YMBSIZE_BEATS8 | + ISC_DCFG_CMBSIZE_BEATS8; } pfe_cfg0 |= subdev->pfe_cfg0 | ISC_PFE_CFG0_MODE_PROGRESSIVE; @@ -750,7 +752,7 @@ static int isc_configure(struct isc_device *isc) regmap_update_bits(regmap, ISC_RLP_CFG, ISC_RLP_CFG_MODE_MASK, rlp_mode); - regmap_update_bits(regmap, ISC_DCFG, ISC_DCFG_IMODE_MASK, dcfg_imode); + regmap_write(regmap, ISC_DCFG, dcfg); /* Set the pipeline */ isc_set_pipeline(isc, pipeline); @@ -1684,7 +1686,7 @@ static int isc_parse_dt(struct device *dev, struct isc_device *isc) { struct device_node *np = dev->of_node; struct device_node *epn = NULL, *rem; - struct v4l2_of_endpoint v4l2_epn; + struct v4l2_fwnode_endpoint v4l2_epn; struct isc_subdev_entity *subdev_entity; unsigned int flags; int ret; @@ -1703,7 +1705,8 @@ static int isc_parse_dt(struct device *dev, struct isc_device *isc) continue; } - ret = v4l2_of_parse_endpoint(epn, &v4l2_epn); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(epn), + &v4l2_epn); if (ret) { of_node_put(rem); ret = -EINVAL; @@ -1738,8 +1741,9 @@ static int isc_parse_dt(struct device *dev, struct isc_device *isc) if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_PPOL_LOW; - subdev_entity->asd->match_type = V4L2_ASYNC_MATCH_OF; - subdev_entity->asd->match.of.node = rem; + subdev_entity->asd->match_type = V4L2_ASYNC_MATCH_FWNODE; + subdev_entity->asd->match.fwnode.fwnode = + of_fwnode_handle(rem); list_add_tail(&subdev_entity->list, &isc->subdev_entities); } diff --git a/drivers/media/platform/atmel/atmel-isi.c b/drivers/media/platform/atmel/atmel-isi.c index e4867f84514c..891fa2505efa 100644 --- a/drivers/media/platform/atmel/atmel-isi.c +++ b/drivers/media/platform/atmel/atmel-isi.c @@ -19,6 +19,7 @@ #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/of_graph.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/slab.h> @@ -30,14 +31,14 @@ #include <media/v4l2-dev.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-event.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/videobuf2-dma-contig.h> #include <media/v4l2-image-sizes.h> #include "atmel-isi.h" -#define MAX_SUPPORT_WIDTH 2048 -#define MAX_SUPPORT_HEIGHT 2048 +#define MAX_SUPPORT_WIDTH 2048U +#define MAX_SUPPORT_HEIGHT 2048U #define MIN_FRAME_RATE 15 #define FRAME_INTERVAL_MILLI_SEC (1000 / MIN_FRAME_RATE) @@ -424,6 +425,8 @@ static int start_streaming(struct vb2_queue *vq, unsigned int count) struct frame_buffer *buf, *node; int ret; + pm_runtime_get_sync(isi->dev); + /* Enable stream on the sub device */ ret = v4l2_subdev_call(isi->entity.subdev, video, s_stream, 1); if (ret && ret != -ENOIOCTLCMD) { @@ -431,8 +434,6 @@ static int start_streaming(struct vb2_queue *vq, unsigned int count) goto err_start_stream; } - pm_runtime_get_sync(isi->dev); - /* Reset ISI */ ret = atmel_isi_wait_status(isi, WAIT_ISI_RESET); if (ret < 0) { @@ -455,10 +456,11 @@ static int start_streaming(struct vb2_queue *vq, unsigned int count) return 0; err_reset: - pm_runtime_put(isi->dev); v4l2_subdev_call(isi->entity.subdev, video, s_stream, 0); err_start_stream: + pm_runtime_put(isi->dev); + spin_lock_irq(&isi->irqlock); isi->active = NULL; /* Release all active buffers */ @@ -566,20 +568,15 @@ static int isi_try_fmt(struct atmel_isi *isi, struct v4l2_format *f, }; int ret; - if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) - return -EINVAL; - isi_fmt = find_format_by_fourcc(isi, pixfmt->pixelformat); if (!isi_fmt) { isi_fmt = isi->user_formats[isi->num_user_formats - 1]; pixfmt->pixelformat = isi_fmt->fourcc; } - /* Limit to Atmel ISC hardware capabilities */ - if (pixfmt->width > MAX_SUPPORT_WIDTH) - pixfmt->width = MAX_SUPPORT_WIDTH; - if (pixfmt->height > MAX_SUPPORT_HEIGHT) - pixfmt->height = MAX_SUPPORT_HEIGHT; + /* Limit to Atmel ISI hardware capabilities */ + pixfmt->width = clamp(pixfmt->width, 0U, MAX_SUPPORT_WIDTH); + pixfmt->height = clamp(pixfmt->height, 0U, MAX_SUPPORT_HEIGHT); v4l2_fill_mbus_format(&format.format, pixfmt, isi_fmt->mbus_code); ret = v4l2_subdev_call(isi->entity.subdev, pad, set_fmt, @@ -801,7 +798,7 @@ static int atmel_isi_parse_dt(struct atmel_isi *isi, struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; - struct v4l2_of_endpoint ep; + struct v4l2_fwnode_endpoint ep; int err; /* Default settings for ISI */ @@ -814,7 +811,7 @@ static int atmel_isi_parse_dt(struct atmel_isi *isi, return -EINVAL; } - err = v4l2_of_parse_endpoint(np, &ep); + err = v4l2_fwnode_endpoint_parse(of_fwnode_handle(np), &ep); of_node_put(np); if (err) { dev_err(&pdev->dev, "Could not parse the endpoint\n"); @@ -1058,7 +1055,7 @@ static int isi_graph_notify_complete(struct v4l2_async_notifier *notifier) struct atmel_isi *isi = notifier_to_isi(notifier); int ret; - isi->vdev->ctrl_handler = isi->entity.subdev->ctrl_handler; + isi->vdev->ctrl_handler = isi->entity.subdev->ctrl_handler; ret = isi_formats_init(isi); if (ret) { dev_err(isi->dev, "No supported mediabus format found\n"); @@ -1126,8 +1123,8 @@ static int isi_graph_parse(struct atmel_isi *isi, struct device_node *node) /* Remote node to connect */ isi->entity.node = remote; - isi->entity.asd.match_type = V4L2_ASYNC_MATCH_OF; - isi->entity.asd.match.of.node = remote; + isi->entity.asd.match_type = V4L2_ASYNC_MATCH_FWNODE; + isi->entity.asd.match.fwnode.fwnode = of_fwnode_handle(remote); return 0; } } diff --git a/drivers/media/platform/coda/coda-bit.c b/drivers/media/platform/coda/coda-bit.c index 403214e00e95..25cbf9e5ac5a 100644 --- a/drivers/media/platform/coda/coda-bit.c +++ b/drivers/media/platform/coda/coda-bit.c @@ -427,14 +427,16 @@ static int coda_alloc_framebuffers(struct coda_ctx *ctx, /* Register frame buffers in the parameter buffer */ for (i = 0; i < ctx->num_internal_frames; i++) { - u32 y, cb, cr; + u32 y, cb, cr, mvcol; /* Start addresses of Y, Cb, Cr planes */ y = ctx->internal_frames[i].paddr; cb = y + ysize; cr = y + ysize + ysize/4; + mvcol = y + ysize + ysize/4 + ysize/4; if (ctx->tiled_map_type == GDI_TILED_FRAME_MB_RASTER_MAP) { cb = round_up(cb, 4096); + mvcol = cb + ysize/2; cr = 0; /* Packed 20-bit MSB of base addresses */ /* YYYYYCCC, CCyyyyyc, cccc.... */ @@ -448,9 +450,7 @@ static int coda_alloc_framebuffers(struct coda_ctx *ctx, /* mvcol buffer for h.264 */ if (ctx->codec->src_fourcc == V4L2_PIX_FMT_H264 && dev->devtype->product != CODA_DX6) - coda_parabuf_write(ctx, 96 + i, - ctx->internal_frames[i].paddr + - ysize + ysize/4 + ysize/4); + coda_parabuf_write(ctx, 96 + i, mvcol); } /* mvcol buffer for mpeg4 */ @@ -1247,12 +1247,18 @@ static int coda_prepare_encode(struct coda_ctx *ctx) dst_buf->sequence = ctx->osequence; ctx->osequence++; + force_ipicture = ctx->params.force_ipicture; + if (force_ipicture) + ctx->params.force_ipicture = false; + else if ((src_buf->sequence % ctx->params.gop_size) == 0) + force_ipicture = 1; + /* * Workaround coda firmware BUG that only marks the first * frame as IDR. This is a problem for some decoders that can't * recover when a frame is lost. */ - if (src_buf->sequence % ctx->params.gop_size) { + if (!force_ipicture) { src_buf->flags |= V4L2_BUF_FLAG_PFRAME; src_buf->flags &= ~V4L2_BUF_FLAG_KEYFRAME; } else { @@ -1264,10 +1270,10 @@ static int coda_prepare_encode(struct coda_ctx *ctx) coda_set_gdi_regs(ctx); /* - * Copy headers at the beginning of the first frame for H.264 only. - * In MPEG4 they are already copied by the coda. + * Copy headers in front of the first frame and forced I frames for + * H.264 only. In MPEG4 they are already copied by the CODA. */ - if (src_buf->sequence == 0) { + if (src_buf->sequence == 0 || force_ipicture) { pic_stream_buffer_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0) + ctx->vpu_header_size[0] + @@ -1291,8 +1297,7 @@ static int coda_prepare_encode(struct coda_ctx *ctx) pic_stream_buffer_size = q_data_dst->sizeimage; } - if (src_buf->flags & V4L2_BUF_FLAG_KEYFRAME) { - force_ipicture = 1; + if (force_ipicture) { switch (dst_fourcc) { case V4L2_PIX_FMT_H264: quant_param = ctx->params.h264_intra_qp; @@ -1309,7 +1314,6 @@ static int coda_prepare_encode(struct coda_ctx *ctx) break; } } else { - force_ipicture = 0; switch (dst_fourcc) { case V4L2_PIX_FMT_H264: quant_param = ctx->params.h264_inter_qp; @@ -1382,7 +1386,8 @@ static void coda_finish_encode(struct coda_ctx *ctx) wr_ptr = coda_read(dev, CODA_REG_BIT_WR_PTR(ctx->reg_idx)); /* Calculate bytesused field */ - if (dst_buf->sequence == 0) { + if (dst_buf->sequence == 0 || + src_buf->flags & V4L2_BUF_FLAG_KEYFRAME) { vb2_set_plane_payload(&dst_buf->vb2_buf, 0, wr_ptr - start_ptr + ctx->vpu_header_size[0] + ctx->vpu_header_size[1] + @@ -2193,12 +2198,32 @@ static void coda_finish_decode(struct coda_ctx *ctx) ctx->display_idx = display_idx; } +static void coda_error_decode(struct coda_ctx *ctx) +{ + struct vb2_v4l2_buffer *dst_buf; + + /* + * For now this only handles the case where we would deadlock with + * userspace, i.e. userspace issued DEC_CMD_STOP and waits for EOS, + * but after a failed decode run we would hold the context and wait for + * userspace to queue more buffers. + */ + if (!(ctx->bit_stream_param & CODA_BIT_STREAM_END_FLAG)) + return; + + dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + dst_buf->sequence = ctx->qsequence - 1; + + coda_m2m_buf_done(ctx, dst_buf, VB2_BUF_STATE_ERROR); +} + const struct coda_context_ops coda_bit_decode_ops = { .queue_init = coda_decoder_queue_init, .reqbufs = coda_decoder_reqbufs, .start_streaming = coda_start_decoding, .prepare_run = coda_prepare_decode, .finish_run = coda_finish_decode, + .error_run = coda_error_decode, .seq_end_work = coda_seq_end_work, .release = coda_bit_release, }; diff --git a/drivers/media/platform/coda/coda-common.c b/drivers/media/platform/coda/coda-common.c index d523e990d509..f92cc7df58fb 100644 --- a/drivers/media/platform/coda/coda-common.c +++ b/drivers/media/platform/coda/coda-common.c @@ -430,10 +430,10 @@ static int coda_g_fmt(struct file *file, void *priv, f->fmt.pix.bytesperline = q_data->bytesperline; f->fmt.pix.sizeimage = q_data->sizeimage; - if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_JPEG) - f->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG; - else - f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + f->fmt.pix.ycbcr_enc = ctx->ycbcr_enc; + f->fmt.pix.quantization = ctx->quantization; return 0; } @@ -599,6 +599,9 @@ static int coda_try_fmt_vid_cap(struct file *file, void *priv, } f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + f->fmt.pix.ycbcr_enc = ctx->ycbcr_enc; + f->fmt.pix.quantization = ctx->quantization; q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); codec = coda_find_codec(ctx->dev, q_data_src->fourcc, @@ -612,7 +615,6 @@ static int coda_try_fmt_vid_cap(struct file *file, void *priv, /* The h.264 decoder only returns complete 16x16 macroblocks */ if (codec && codec->src_fourcc == V4L2_PIX_FMT_H264) { - f->fmt.pix.width = f->fmt.pix.width; f->fmt.pix.height = round_up(f->fmt.pix.height, 16); f->fmt.pix.bytesperline = round_up(f->fmt.pix.width, 16); f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * @@ -635,6 +637,23 @@ static int coda_try_fmt_vid_cap(struct file *file, void *priv, return 0; } +static void coda_set_default_colorspace(struct v4l2_pix_format *fmt) +{ + enum v4l2_colorspace colorspace; + + if (fmt->pixelformat == V4L2_PIX_FMT_JPEG) + colorspace = V4L2_COLORSPACE_JPEG; + else if (fmt->width <= 720 && fmt->height <= 576) + colorspace = V4L2_COLORSPACE_SMPTE170M; + else + colorspace = V4L2_COLORSPACE_REC709; + + fmt->colorspace = colorspace; + fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->quantization = V4L2_QUANTIZATION_DEFAULT; +} + static int coda_try_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f) { @@ -648,16 +667,8 @@ static int coda_try_fmt_vid_out(struct file *file, void *priv, if (ret < 0) return ret; - switch (f->fmt.pix.colorspace) { - case V4L2_COLORSPACE_REC709: - case V4L2_COLORSPACE_JPEG: - break; - default: - if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_JPEG) - f->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG; - else - f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; - } + if (f->fmt.pix.colorspace == V4L2_COLORSPACE_DEFAULT) + coda_set_default_colorspace(&f->fmt.pix); q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); codec = coda_find_codec(dev, f->fmt.pix.pixelformat, q_data_dst->fourcc); @@ -772,6 +783,9 @@ static int coda_s_fmt_vid_out(struct file *file, void *priv, return ret; ctx->colorspace = f->fmt.pix.colorspace; + ctx->xfer_func = f->fmt.pix.xfer_func; + ctx->ycbcr_enc = f->fmt.pix.ycbcr_enc; + ctx->quantization = f->fmt.pix.quantization; memset(&f_cap, 0, sizeof(f_cap)); f_cap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; @@ -1149,6 +1163,9 @@ static void coda_pic_run_work(struct work_struct *work) ctx->hold = true; coda_hw_reset(ctx); + + if (ctx->ops->error_run) + ctx->ops->error_run(ctx); } else if (!ctx->aborting) { ctx->ops->finish_run(ctx); } @@ -1282,7 +1299,13 @@ static void set_default_params(struct coda_ctx *ctx) csize = coda_estimate_sizeimage(ctx, usize, max_w, max_h); ctx->params.codec_mode = ctx->codec->mode; - ctx->colorspace = V4L2_COLORSPACE_REC709; + if (ctx->cvd->src_formats[0] == V4L2_PIX_FMT_JPEG) + ctx->colorspace = V4L2_COLORSPACE_JPEG; + else + ctx->colorspace = V4L2_COLORSPACE_REC709; + ctx->xfer_func = V4L2_XFER_FUNC_DEFAULT; + ctx->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + ctx->quantization = V4L2_QUANTIZATION_DEFAULT; ctx->params.framerate = 30; /* Default formats for output and input queues */ @@ -1680,6 +1703,9 @@ static int coda_s_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB: ctx->params.intra_refresh = ctrl->val; break; + case V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME: + ctx->params.force_ipicture = true; + break; case V4L2_CID_JPEG_COMPRESSION_QUALITY: coda_set_jpeg_compression_quality(ctx, ctrl->val); break; @@ -2063,8 +2089,7 @@ static int coda_hw_init(struct coda_dev *dev) if (ret) goto err_clk_ahb; - if (dev->rstc) - reset_control_reset(dev->rstc); + reset_control_reset(dev->rstc); /* * Copy the first CODA_ISRAM_SIZE in the internal SRAM. @@ -2448,13 +2473,8 @@ static int coda_probe(struct platform_device *pdev) dev->rstc = devm_reset_control_get_optional(&pdev->dev, NULL); if (IS_ERR(dev->rstc)) { ret = PTR_ERR(dev->rstc); - if (ret == -ENOENT || ret == -ENOTSUPP) { - dev->rstc = NULL; - } else { - dev_err(&pdev->dev, "failed get reset control: %d\n", - ret); - return ret; - } + dev_err(&pdev->dev, "failed get reset control: %d\n", ret); + return ret; } /* Get IRAM pool from device tree or platform data */ diff --git a/drivers/media/platform/coda/coda.h b/drivers/media/platform/coda/coda.h index 20222befb9b2..40fe22f0d757 100644 --- a/drivers/media/platform/coda/coda.h +++ b/drivers/media/platform/coda/coda.h @@ -135,6 +135,7 @@ struct coda_params { u32 vbv_size; u32 slice_max_bits; u32 slice_max_mb; + bool force_ipicture; }; struct coda_buffer_meta { @@ -182,6 +183,7 @@ struct coda_context_ops { int (*start_streaming)(struct coda_ctx *ctx); int (*prepare_run)(struct coda_ctx *ctx); void (*finish_run)(struct coda_ctx *ctx); + void (*error_run)(struct coda_ctx *ctx); void (*seq_end_work)(struct work_struct *work); void (*release)(struct coda_ctx *ctx); }; @@ -206,6 +208,9 @@ struct coda_ctx { enum coda_inst_type inst_type; const struct coda_codec *codec; enum v4l2_colorspace colorspace; + enum v4l2_xfer_func xfer_func; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quantization; struct coda_params params; struct v4l2_ctrl_handler ctrls; struct v4l2_fh fh; diff --git a/drivers/media/platform/coda/imx-vdoa.c b/drivers/media/platform/coda/imx-vdoa.c index 669a4c82f1ff..df9b71621420 100644 --- a/drivers/media/platform/coda/imx-vdoa.c +++ b/drivers/media/platform/coda/imx-vdoa.c @@ -101,6 +101,8 @@ struct vdoa_ctx { struct vdoa_data *vdoa; struct completion completion; struct vdoa_q_data q_data[2]; + unsigned int submitted_job; + unsigned int completed_job; }; static irqreturn_t vdoa_irq_handler(int irq, void *data) @@ -114,7 +116,7 @@ static irqreturn_t vdoa_irq_handler(int irq, void *data) curr_ctx = vdoa->curr_ctx; if (!curr_ctx) { - dev_dbg(vdoa->dev, + dev_warn(vdoa->dev, "Instance released before the end of transaction\n"); return IRQ_HANDLED; } @@ -127,19 +129,44 @@ static irqreturn_t vdoa_irq_handler(int irq, void *data) } else if (!(val & VDOAIST_EOT)) { dev_warn(vdoa->dev, "Spurious interrupt\n"); } + curr_ctx->completed_job++; complete(&curr_ctx->completion); return IRQ_HANDLED; } +int vdoa_wait_for_completion(struct vdoa_ctx *ctx) +{ + struct vdoa_data *vdoa = ctx->vdoa; + + if (ctx->submitted_job == ctx->completed_job) + return 0; + + if (!wait_for_completion_timeout(&ctx->completion, + msecs_to_jiffies(300))) { + dev_err(vdoa->dev, + "Timeout waiting for transfer result\n"); + return -ETIMEDOUT; + } + + return 0; +} +EXPORT_SYMBOL(vdoa_wait_for_completion); + void vdoa_device_run(struct vdoa_ctx *ctx, dma_addr_t dst, dma_addr_t src) { struct vdoa_q_data *src_q_data, *dst_q_data; struct vdoa_data *vdoa = ctx->vdoa; u32 val; + if (vdoa->curr_ctx) + vdoa_wait_for_completion(vdoa->curr_ctx); + vdoa->curr_ctx = ctx; + reinit_completion(&ctx->completion); + ctx->submitted_job++; + src_q_data = &ctx->q_data[V4L2_M2M_SRC]; dst_q_data = &ctx->q_data[V4L2_M2M_DST]; @@ -177,21 +204,6 @@ void vdoa_device_run(struct vdoa_ctx *ctx, dma_addr_t dst, dma_addr_t src) } EXPORT_SYMBOL(vdoa_device_run); -int vdoa_wait_for_completion(struct vdoa_ctx *ctx) -{ - struct vdoa_data *vdoa = ctx->vdoa; - - if (!wait_for_completion_timeout(&ctx->completion, - msecs_to_jiffies(300))) { - dev_err(vdoa->dev, - "Timeout waiting for transfer result\n"); - return -ETIMEDOUT; - } - - return 0; -} -EXPORT_SYMBOL(vdoa_wait_for_completion); - struct vdoa_ctx *vdoa_context_create(struct vdoa_data *vdoa) { struct vdoa_ctx *ctx; @@ -218,6 +230,11 @@ void vdoa_context_destroy(struct vdoa_ctx *ctx) { struct vdoa_data *vdoa = ctx->vdoa; + if (vdoa->curr_ctx == ctx) { + vdoa_wait_for_completion(vdoa->curr_ctx); + vdoa->curr_ctx = NULL; + } + clk_disable_unprepare(vdoa->vdoa_clk); kfree(ctx); } diff --git a/drivers/media/platform/davinci/Kconfig b/drivers/media/platform/davinci/Kconfig index 554e710de487..55982e681d77 100644 --- a/drivers/media/platform/davinci/Kconfig +++ b/drivers/media/platform/davinci/Kconfig @@ -22,6 +22,7 @@ config VIDEO_DAVINCI_VPIF_CAPTURE depends on HAS_DMA depends on I2C select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE help Enables Davinci VPIF module used for capture devices. This module is used for capture on TI DM6467/DA850/OMAPL138 diff --git a/drivers/media/platform/davinci/vpif.c b/drivers/media/platform/davinci/vpif.c index 1b02a6363f77..07e89a4985a6 100644 --- a/drivers/media/platform/davinci/vpif.c +++ b/drivers/media/platform/davinci/vpif.c @@ -26,6 +26,7 @@ #include <linux/pm_runtime.h> #include <linux/spinlock.h> #include <linux/v4l2-dv-timings.h> +#include <linux/of_graph.h> #include "vpif.h" @@ -423,7 +424,9 @@ EXPORT_SYMBOL(vpif_channel_getfid); static int vpif_probe(struct platform_device *pdev) { - static struct resource *res; + static struct resource *res, *res_irq; + struct platform_device *pdev_capture, *pdev_display; + struct device_node *endpoint = NULL; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); vpif_base = devm_ioremap_resource(&pdev->dev, res); @@ -435,6 +438,58 @@ static int vpif_probe(struct platform_device *pdev) spin_lock_init(&vpif_lock); dev_info(&pdev->dev, "vpif probe success\n"); + + /* + * If VPIF Node has endpoints, assume "new" DT support, + * where capture and display drivers don't have DT nodes + * so their devices need to be registered manually here + * for their legacy platform_drivers to work. + */ + endpoint = of_graph_get_next_endpoint(pdev->dev.of_node, + endpoint); + if (!endpoint) + return 0; + + /* + * For DT platforms, manually create platform_devices for + * capture/display drivers. + */ + res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res_irq) { + dev_warn(&pdev->dev, "Missing IRQ resource.\n"); + return -EINVAL; + } + + pdev_capture = devm_kzalloc(&pdev->dev, sizeof(*pdev_capture), + GFP_KERNEL); + if (pdev_capture) { + pdev_capture->name = "vpif_capture"; + pdev_capture->id = -1; + pdev_capture->resource = res_irq; + pdev_capture->num_resources = 1; + pdev_capture->dev.dma_mask = pdev->dev.dma_mask; + pdev_capture->dev.coherent_dma_mask = pdev->dev.coherent_dma_mask; + pdev_capture->dev.parent = &pdev->dev; + platform_device_register(pdev_capture); + } else { + dev_warn(&pdev->dev, "Unable to allocate memory for pdev_capture.\n"); + } + + pdev_display = devm_kzalloc(&pdev->dev, sizeof(*pdev_display), + GFP_KERNEL); + if (pdev_display) { + pdev_display->name = "vpif_display"; + pdev_display->id = -1; + pdev_display->resource = res_irq; + pdev_display->num_resources = 1; + pdev_display->dev.dma_mask = pdev->dev.dma_mask; + pdev_display->dev.coherent_dma_mask = pdev->dev.coherent_dma_mask; + pdev_display->dev.parent = &pdev->dev; + platform_device_register(pdev_display); + } else { + dev_warn(&pdev->dev, "Unable to allocate memory for pdev_display.\n"); + } + return 0; } diff --git a/drivers/media/platform/davinci/vpif_capture.c b/drivers/media/platform/davinci/vpif_capture.c index 44f702752d3a..d78580f9e431 100644 --- a/drivers/media/platform/davinci/vpif_capture.c +++ b/drivers/media/platform/davinci/vpif_capture.c @@ -18,10 +18,16 @@ #include <linux/module.h> #include <linux/interrupt.h> +#include <linux/of_graph.h> #include <linux/platform_device.h> #include <linux/slab.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-ioctl.h> +#include <media/i2c/tvp514x.h> +#include <media/v4l2-mediabus.h> + +#include <linux/videodev2.h> #include "vpif.h" #include "vpif_capture.h" @@ -385,7 +391,8 @@ static irqreturn_t vpif_channel_isr(int irq, void *dev_id) common = &ch->common[i]; /* skip If streaming is not started in this channel */ /* Check the field format */ - if (1 == ch->vpifparams.std_info.frm_fmt) { + if (1 == ch->vpifparams.std_info.frm_fmt || + common->fmt.fmt.pix.field == V4L2_FIELD_NONE) { /* Progressive mode */ spin_lock(&common->irqlock); if (list_empty(&common->dma_queue)) { @@ -466,9 +473,38 @@ static int vpif_update_std_info(struct channel_obj *ch) struct vpif_channel_config_params *std_info = &vpifparams->std_info; struct video_obj *vid_ch = &ch->video; int index; + struct v4l2_pix_format *pixfmt = &common->fmt.fmt.pix; vpif_dbg(2, debug, "vpif_update_std_info\n"); + /* + * if called after try_fmt or g_fmt, there will already be a size + * so use that by default. + */ + if (pixfmt->width && pixfmt->height) { + if (pixfmt->field == V4L2_FIELD_ANY || + pixfmt->field == V4L2_FIELD_NONE) + pixfmt->field = V4L2_FIELD_NONE; + + vpifparams->iface.if_type = VPIF_IF_BT656; + if (pixfmt->pixelformat == V4L2_PIX_FMT_SGRBG10 || + pixfmt->pixelformat == V4L2_PIX_FMT_SBGGR8) + vpifparams->iface.if_type = VPIF_IF_RAW_BAYER; + + if (pixfmt->pixelformat == V4L2_PIX_FMT_SGRBG10) + vpifparams->params.data_sz = 1; /* 10 bits/pixel. */ + + /* + * For raw formats from camera sensors, we don't need + * the std_info from table lookup, so nothing else to do here. + */ + if (vpifparams->iface.if_type == VPIF_IF_RAW_BAYER) { + memset(std_info, 0, sizeof(struct vpif_channel_config_params)); + vpifparams->std_info.capture_format = 1; /* CCD/raw mode */ + return 0; + } + } + for (index = 0; index < vpif_ch_params_count; index++) { config = &vpif_ch_params[index]; if (config->hd_sd == 0) { @@ -513,7 +549,7 @@ static int vpif_update_std_info(struct channel_obj *ch) if (ch->vpifparams.iface.if_type == VPIF_IF_RAW_BAYER) common->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_SBGGR8; else - common->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV422P; + common->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV16; common->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; @@ -655,7 +691,7 @@ static int vpif_input_to_subdev( /* loop through the sub device list to get the sub device info */ for (i = 0; i < vpif_cfg->subdev_count; i++) { subdev_info = &vpif_cfg->subdev_info[i]; - if (!strcmp(subdev_info->name, subdev_name)) + if (subdev_info && !strcmp(subdev_info->name, subdev_name)) return i; } return -1; @@ -917,8 +953,8 @@ static int vpif_enum_fmt_vid_cap(struct file *file, void *priv, fmt->pixelformat = V4L2_PIX_FMT_SBGGR8; } else { fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - strcpy(fmt->description, "YCbCr4:2:2 YC Planar"); - fmt->pixelformat = V4L2_PIX_FMT_YUV422P; + strcpy(fmt->description, "YCbCr4:2:2 Semi-Planar"); + fmt->pixelformat = V4L2_PIX_FMT_NV16; } return 0; } @@ -936,22 +972,8 @@ static int vpif_try_fmt_vid_cap(struct file *file, void *priv, struct channel_obj *ch = video_get_drvdata(vdev); struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; struct common_obj *common = &(ch->common[VPIF_VIDEO_INDEX]); - struct vpif_params *vpif_params = &ch->vpifparams; - - /* - * to supress v4l-compliance warnings silently correct - * the pixelformat - */ - if (vpif_params->iface.if_type == VPIF_IF_RAW_BAYER) { - if (pixfmt->pixelformat != V4L2_PIX_FMT_SBGGR8) - pixfmt->pixelformat = V4L2_PIX_FMT_SBGGR8; - } else { - if (pixfmt->pixelformat != V4L2_PIX_FMT_YUV422P) - pixfmt->pixelformat = V4L2_PIX_FMT_YUV422P; - } - - common->fmt.fmt.pix.pixelformat = pixfmt->pixelformat; + common->fmt = *fmt; vpif_update_std_info(ch); pixfmt->field = common->fmt.fmt.pix.field; @@ -960,8 +982,17 @@ static int vpif_try_fmt_vid_cap(struct file *file, void *priv, pixfmt->width = common->fmt.fmt.pix.width; pixfmt->height = common->fmt.fmt.pix.height; pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height * 2; + if (pixfmt->pixelformat == V4L2_PIX_FMT_SGRBG10) { + pixfmt->bytesperline = common->fmt.fmt.pix.width * 2; + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; + } pixfmt->priv = 0; + dev_dbg(vpif_dev, "%s: %d x %d; pitch=%d pixelformat=0x%08x, field=%d, size=%d\n", __func__, + pixfmt->width, pixfmt->height, + pixfmt->bytesperline, pixfmt->pixelformat, + pixfmt->field, pixfmt->sizeimage); + return 0; } @@ -978,13 +1009,47 @@ static int vpif_g_fmt_vid_cap(struct file *file, void *priv, struct video_device *vdev = video_devdata(file); struct channel_obj *ch = video_get_drvdata(vdev); struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct v4l2_pix_format *pix_fmt = &fmt->fmt.pix; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_mbus_framefmt *mbus_fmt = &format.format; + int ret; /* Check the validity of the buffer type */ if (common->fmt.type != fmt->type) return -EINVAL; - /* Fill in the information about format */ + /* By default, use currently set fmt */ *fmt = common->fmt; + + /* If subdev has get_fmt, use that to override */ + ret = v4l2_subdev_call(ch->sd, pad, get_fmt, NULL, &format); + if (!ret && mbus_fmt->code) { + v4l2_fill_pix_format(pix_fmt, mbus_fmt); + pix_fmt->bytesperline = pix_fmt->width; + if (mbus_fmt->code == MEDIA_BUS_FMT_SGRBG10_1X10) { + /* e.g. mt9v032 */ + pix_fmt->pixelformat = V4L2_PIX_FMT_SGRBG10; + pix_fmt->bytesperline = pix_fmt->width * 2; + } else if (mbus_fmt->code == MEDIA_BUS_FMT_UYVY8_2X8) { + /* e.g. tvp514x */ + pix_fmt->pixelformat = V4L2_PIX_FMT_NV16; + pix_fmt->bytesperline = pix_fmt->width * 2; + } else { + dev_warn(vpif_dev, "%s: Unhandled media-bus format 0x%x\n", + __func__, mbus_fmt->code); + } + pix_fmt->sizeimage = pix_fmt->bytesperline * pix_fmt->height; + dev_dbg(vpif_dev, "%s: %d x %d; pitch=%d, pixelformat=0x%08x, code=0x%x, field=%d, size=%d\n", __func__, + pix_fmt->width, pix_fmt->height, + pix_fmt->bytesperline, pix_fmt->pixelformat, + mbus_fmt->code, pix_fmt->field, pix_fmt->sizeimage); + + common->fmt = *fmt; + vpif_update_std_info(ch); + } + return 0; } @@ -1323,6 +1388,22 @@ static int vpif_async_bound(struct v4l2_async_notifier *notifier, { int i; + for (i = 0; i < vpif_obj.config->asd_sizes[0]; i++) { + struct v4l2_async_subdev *_asd = vpif_obj.config->asd[i]; + const struct fwnode_handle *fwnode = _asd->match.fwnode.fwnode; + + if (fwnode == subdev->fwnode) { + vpif_obj.sd[i] = subdev; + vpif_obj.config->chan_config->inputs[i].subdev_name = + (char *)to_of_node(subdev->fwnode)->full_name; + vpif_dbg(2, debug, + "%s: setting input %d subdev_name = %s\n", + __func__, i, + to_of_node(subdev->fwnode)->full_name); + return 0; + } + } + for (i = 0; i < vpif_obj.config->subdev_count; i++) if (!strcmp(vpif_obj.config->subdev_info[i].name, subdev->name)) { @@ -1356,6 +1437,7 @@ static int vpif_probe_complete(void) /* set initial format */ ch->video.stdid = V4L2_STD_525_60; memset(&ch->video.dv_timings, 0, sizeof(ch->video.dv_timings)); + common->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vpif_update_std_info(ch); /* Initialize vb2 queue */ @@ -1418,6 +1500,106 @@ static int vpif_async_complete(struct v4l2_async_notifier *notifier) return vpif_probe_complete(); } +static struct vpif_capture_config * +vpif_capture_get_pdata(struct platform_device *pdev) +{ + struct device_node *endpoint = NULL; + struct v4l2_fwnode_endpoint bus_cfg; + struct vpif_capture_config *pdata; + struct vpif_subdev_info *sdinfo; + struct vpif_capture_chan_config *chan; + unsigned int i; + + /* + * DT boot: OF node from parent device contains + * video ports & endpoints data. + */ + if (pdev->dev.parent && pdev->dev.parent->of_node) + pdev->dev.of_node = pdev->dev.parent->of_node; + if (!IS_ENABLED(CONFIG_OF) || !pdev->dev.of_node) + return pdev->dev.platform_data; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + pdata->subdev_info = + devm_kzalloc(&pdev->dev, sizeof(*pdata->subdev_info) * + VPIF_CAPTURE_NUM_CHANNELS, GFP_KERNEL); + + if (!pdata->subdev_info) + return NULL; + + for (i = 0; i < VPIF_CAPTURE_NUM_CHANNELS; i++) { + struct device_node *rem; + unsigned int flags; + int err; + + endpoint = of_graph_get_next_endpoint(pdev->dev.of_node, + endpoint); + if (!endpoint) + break; + + sdinfo = &pdata->subdev_info[i]; + chan = &pdata->chan_config[i]; + chan->inputs = devm_kzalloc(&pdev->dev, + sizeof(*chan->inputs) * + VPIF_CAPTURE_NUM_CHANNELS, + GFP_KERNEL); + + chan->input_count++; + chan->inputs[i].input.type = V4L2_INPUT_TYPE_CAMERA; + chan->inputs[i].input.std = V4L2_STD_ALL; + chan->inputs[i].input.capabilities = V4L2_IN_CAP_STD; + + err = v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), + &bus_cfg); + if (err) { + dev_err(&pdev->dev, "Could not parse the endpoint\n"); + goto done; + } + dev_dbg(&pdev->dev, "Endpoint %s, bus_width = %d\n", + endpoint->full_name, bus_cfg.bus.parallel.bus_width); + flags = bus_cfg.bus.parallel.flags; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + chan->vpif_if.hd_pol = 1; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + chan->vpif_if.vd_pol = 1; + + rem = of_graph_get_remote_port_parent(endpoint); + if (!rem) { + dev_dbg(&pdev->dev, "Remote device at %s not found\n", + endpoint->full_name); + goto done; + } + + dev_dbg(&pdev->dev, "Remote device %s, %s found\n", + rem->name, rem->full_name); + sdinfo->name = rem->full_name; + + pdata->asd[i] = devm_kzalloc(&pdev->dev, + sizeof(struct v4l2_async_subdev), + GFP_KERNEL); + if (!pdata->asd[i]) { + of_node_put(rem); + pdata = NULL; + goto done; + } + + pdata->asd[i]->match_type = V4L2_ASYNC_MATCH_FWNODE; + pdata->asd[i]->match.fwnode.fwnode = of_fwnode_handle(rem); + of_node_put(rem); + } + +done: + pdata->asd_sizes[0] = i; + pdata->subdev_count = i; + pdata->card_name = "DA850/OMAP-L138 Video Capture"; + + return pdata; +} + /** * vpif_probe : This function probes the vpif capture driver * @pdev: platform device pointer @@ -1434,6 +1616,12 @@ static __init int vpif_probe(struct platform_device *pdev) int res_idx = 0; int i, err; + pdev->dev.platform_data = vpif_capture_get_pdata(pdev); + if (!pdev->dev.platform_data) { + dev_warn(&pdev->dev, "Missing platform data. Giving up.\n"); + return -EINVAL; + } + if (!pdev->dev.platform_data) { dev_warn(&pdev->dev, "Missing platform data. Giving up.\n"); return -EINVAL; @@ -1474,7 +1662,7 @@ static __init int vpif_probe(struct platform_device *pdev) goto vpif_unregister; } - if (!vpif_obj.config->asd_sizes) { + if (!vpif_obj.config->asd_sizes[0]) { int i2c_id = vpif_obj.config->i2c_adapter_id; i2c_adap = i2c_get_adapter(i2c_id); diff --git a/drivers/media/platform/davinci/vpif_display.c b/drivers/media/platform/davinci/vpif_display.c index 7e5cf9923c8d..b5ac6ce626b3 100644 --- a/drivers/media/platform/davinci/vpif_display.c +++ b/drivers/media/platform/davinci/vpif_display.c @@ -1250,6 +1250,11 @@ static __init int vpif_probe(struct platform_device *pdev) return -EINVAL; } + if (!pdev->dev.platform_data) { + dev_warn(&pdev->dev, "Missing platform data. Giving up.\n"); + return -EINVAL; + } + vpif_dev = &pdev->dev; err = initialize_vpif(); diff --git a/drivers/media/platform/exynos-gsc/gsc-core.c b/drivers/media/platform/exynos-gsc/gsc-core.c index 59a634201830..43801509dabb 100644 --- a/drivers/media/platform/exynos-gsc/gsc-core.c +++ b/drivers/media/platform/exynos-gsc/gsc-core.c @@ -454,6 +454,7 @@ int gsc_try_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f) } else { min_w = variant->pix_min->target_rot_dis_w; min_h = variant->pix_min->target_rot_dis_h; + pix_mp->colorspace = ctx->out_colorspace; } pr_debug("mod_x: %d, mod_y: %d, max_w: %d, max_h = %d", @@ -472,10 +473,8 @@ int gsc_try_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f) pix_mp->num_planes = fmt->num_planes; - if (pix_mp->width >= 1280) /* HD */ - pix_mp->colorspace = V4L2_COLORSPACE_REC709; - else /* SD */ - pix_mp->colorspace = V4L2_COLORSPACE_SMPTE170M; + if (V4L2_TYPE_IS_OUTPUT(f->type)) + ctx->out_colorspace = pix_mp->colorspace; for (i = 0; i < pix_mp->num_planes; ++i) { struct v4l2_plane_pix_format *plane_fmt = &pix_mp->plane_fmt[i]; @@ -519,8 +518,8 @@ int gsc_g_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f) pix_mp->height = frame->f_height; pix_mp->field = V4L2_FIELD_NONE; pix_mp->pixelformat = frame->fmt->pixelformat; - pix_mp->colorspace = V4L2_COLORSPACE_REC709; pix_mp->num_planes = frame->fmt->num_planes; + pix_mp->colorspace = ctx->out_colorspace; for (i = 0; i < pix_mp->num_planes; ++i) { pix_mp->plane_fmt[i].bytesperline = (frame->f_width * @@ -569,9 +568,9 @@ int gsc_try_crop(struct gsc_ctx *ctx, struct v4l2_crop *cr) } pr_debug("user put w: %d, h: %d", cr->c.width, cr->c.height); - if (cr->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + if (cr->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) f = &ctx->d_frame; - else if (cr->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + else if (cr->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) f = &ctx->s_frame; else return -EINVAL; diff --git a/drivers/media/platform/exynos-gsc/gsc-core.h b/drivers/media/platform/exynos-gsc/gsc-core.h index 696217e9af66..715d9c9d8d30 100644 --- a/drivers/media/platform/exynos-gsc/gsc-core.h +++ b/drivers/media/platform/exynos-gsc/gsc-core.h @@ -376,6 +376,7 @@ struct gsc_ctx { struct v4l2_ctrl_handler ctrl_handler; struct gsc_ctrls gsc_ctrls; bool ctrls_rdy; + enum v4l2_colorspace out_colorspace; }; void gsc_set_prefbuf(struct gsc_dev *gsc, struct gsc_frame *frm); diff --git a/drivers/media/platform/exynos-gsc/gsc-m2m.c b/drivers/media/platform/exynos-gsc/gsc-m2m.c index 82505025d96c..33611a46ce35 100644 --- a/drivers/media/platform/exynos-gsc/gsc-m2m.c +++ b/drivers/media/platform/exynos-gsc/gsc-m2m.c @@ -460,8 +460,8 @@ static int gsc_m2m_g_selection(struct file *file, void *fh, struct gsc_frame *frame; struct gsc_ctx *ctx = fh_to_ctx(fh); - if ((s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) && - (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)) + if ((s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && + (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)) return -EINVAL; frame = ctx_get_frame(ctx, s->type); @@ -503,8 +503,8 @@ static int gsc_m2m_s_selection(struct file *file, void *fh, cr.type = s->type; cr.c = s->r; - if ((s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) && - (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)) + if ((s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && + (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)) return -EINVAL; ret = gsc_try_crop(ctx, &cr); diff --git a/drivers/media/platform/exynos4-is/Kconfig b/drivers/media/platform/exynos4-is/Kconfig index 57d42c6172c5..c480efb755f5 100644 --- a/drivers/media/platform/exynos4-is/Kconfig +++ b/drivers/media/platform/exynos4-is/Kconfig @@ -4,6 +4,7 @@ config VIDEO_SAMSUNG_EXYNOS4_IS depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST depends on OF && COMMON_CLK + select V4L2_FWNODE help Say Y here to enable camera host interface devices for Samsung S5P and EXYNOS SoC series. @@ -32,6 +33,7 @@ config VIDEO_S5P_MIPI_CSIS tristate "S5P/EXYNOS MIPI-CSI2 receiver (MIPI-CSIS) driver" depends on REGULATOR select GENERIC_PHY + select V4L2_FWNODE help This is a V4L2 driver for Samsung S5P and EXYNOS4 SoC MIPI-CSI2 receiver (MIPI-CSIS) devices. diff --git a/drivers/media/platform/exynos4-is/fimc-capture.c b/drivers/media/platform/exynos4-is/fimc-capture.c index 8a7cd07dbe28..948fe01f6c96 100644 --- a/drivers/media/platform/exynos4-is/fimc-capture.c +++ b/drivers/media/platform/exynos4-is/fimc-capture.c @@ -1270,13 +1270,14 @@ static int fimc_cap_g_selection(struct file *file, void *fh, struct fimc_ctx *ctx = fimc->vid_cap.ctx; struct fimc_frame *f = &ctx->s_frame; - if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; switch (s->target) { case V4L2_SEL_TGT_COMPOSE_DEFAULT: case V4L2_SEL_TGT_COMPOSE_BOUNDS: f = &ctx->d_frame; + /* fall through */ case V4L2_SEL_TGT_CROP_BOUNDS: case V4L2_SEL_TGT_CROP_DEFAULT: s->r.left = 0; @@ -1287,6 +1288,7 @@ static int fimc_cap_g_selection(struct file *file, void *fh, case V4L2_SEL_TGT_COMPOSE: f = &ctx->d_frame; + /* fall through */ case V4L2_SEL_TGT_CROP: s->r.left = f->offs_h; s->r.top = f->offs_v; @@ -1320,7 +1322,7 @@ static int fimc_cap_s_selection(struct file *file, void *fh, struct fimc_frame *f; unsigned long flags; - if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; if (s->target == V4L2_SEL_TGT_COMPOSE) @@ -1610,6 +1612,7 @@ static int fimc_subdev_get_selection(struct v4l2_subdev *sd, switch (sel->target) { case V4L2_SEL_TGT_COMPOSE_BOUNDS: f = &ctx->d_frame; + /* fall through */ case V4L2_SEL_TGT_CROP_BOUNDS: r->width = f->o_width; r->height = f->o_height; diff --git a/drivers/media/platform/exynos4-is/fimc-is.c b/drivers/media/platform/exynos4-is/fimc-is.c index 7f92144a1de3..340d906db370 100644 --- a/drivers/media/platform/exynos4-is/fimc-is.c +++ b/drivers/media/platform/exynos4-is/fimc-is.c @@ -854,7 +854,7 @@ static int fimc_is_probe(struct platform_device *pdev) vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32)); - ret = of_platform_populate(dev->of_node, NULL, NULL, dev); + ret = devm_of_platform_populate(dev); if (ret < 0) goto err_pm; @@ -864,7 +864,7 @@ static int fimc_is_probe(struct platform_device *pdev) */ ret = fimc_is_register_subdevs(is); if (ret < 0) - goto err_of_dep; + goto err_pm; ret = fimc_is_debugfs_create(is); if (ret < 0) @@ -883,8 +883,6 @@ err_dfs: fimc_is_debugfs_remove(is); err_sd: fimc_is_unregister_subdevs(is); -err_of_dep: - of_platform_depopulate(dev); err_pm: if (!pm_runtime_enabled(dev)) fimc_is_runtime_suspend(dev); @@ -946,7 +944,6 @@ static int fimc_is_remove(struct platform_device *pdev) if (!pm_runtime_status_suspended(dev)) fimc_is_runtime_suspend(dev); free_irq(is->irq, is); - of_platform_depopulate(dev); fimc_is_unregister_subdevs(is); vb2_dma_contig_clear_max_seg_size(dev); fimc_is_put_clocks(is); diff --git a/drivers/media/platform/exynos4-is/fimc-lite.c b/drivers/media/platform/exynos4-is/fimc-lite.c index b4c4a33784c4..7d3ec5cc6608 100644 --- a/drivers/media/platform/exynos4-is/fimc-lite.c +++ b/drivers/media/platform/exynos4-is/fimc-lite.c @@ -901,7 +901,7 @@ static int fimc_lite_g_selection(struct file *file, void *fh, struct fimc_lite *fimc = video_drvdata(file); struct flite_frame *f = &fimc->out_frame; - if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; switch (sel->target) { @@ -929,7 +929,7 @@ static int fimc_lite_s_selection(struct file *file, void *fh, struct v4l2_rect rect = sel->r; unsigned long flags; - if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || sel->target != V4L2_SEL_TGT_COMPOSE) return -EINVAL; diff --git a/drivers/media/platform/exynos4-is/media-dev.c b/drivers/media/platform/exynos4-is/media-dev.c index e82450e90a67..7d1cf78846c4 100644 --- a/drivers/media/platform/exynos4-is/media-dev.c +++ b/drivers/media/platform/exynos4-is/media-dev.c @@ -29,7 +29,7 @@ #include <linux/slab.h> #include <media/v4l2-async.h> #include <media/v4l2-ctrls.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/media-device.h> #include <media/drv-intf/exynos-fimc.h> @@ -388,7 +388,7 @@ static int fimc_md_parse_port_node(struct fimc_md *fmd, { struct fimc_source_info *pd = &fmd->sensor[index].pdata; struct device_node *rem, *ep, *np; - struct v4l2_of_endpoint endpoint; + struct v4l2_fwnode_endpoint endpoint; int ret; /* Assume here a port node can have only one endpoint node. */ @@ -396,7 +396,7 @@ static int fimc_md_parse_port_node(struct fimc_md *fmd, if (!ep) return 0; - ret = v4l2_of_parse_endpoint(ep, &endpoint); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &endpoint); if (ret) { of_node_put(ep); return ret; @@ -453,8 +453,8 @@ static int fimc_md_parse_port_node(struct fimc_md *fmd, return -EINVAL; } - fmd->sensor[index].asd.match_type = V4L2_ASYNC_MATCH_OF; - fmd->sensor[index].asd.match.of.node = rem; + fmd->sensor[index].asd.match_type = V4L2_ASYNC_MATCH_FWNODE; + fmd->sensor[index].asd.match.fwnode.fwnode = of_fwnode_handle(rem); fmd->async_subdevs[index] = &fmd->sensor[index].asd; fmd->num_sensors++; @@ -1361,7 +1361,8 @@ static int subdev_notifier_bound(struct v4l2_async_notifier *notifier, /* Find platform data for this sensor subdev */ for (i = 0; i < ARRAY_SIZE(fmd->sensor); i++) - if (fmd->sensor[i].asd.match.of.node == subdev->dev->of_node) + if (fmd->sensor[i].asd.match.fwnode.fwnode == + of_fwnode_handle(subdev->dev->of_node)) si = &fmd->sensor[i]; if (si == NULL) diff --git a/drivers/media/platform/exynos4-is/mipi-csis.c b/drivers/media/platform/exynos4-is/mipi-csis.c index f819b29efc38..98c89873c2dc 100644 --- a/drivers/media/platform/exynos4-is/mipi-csis.c +++ b/drivers/media/platform/exynos4-is/mipi-csis.c @@ -30,7 +30,7 @@ #include <linux/spinlock.h> #include <linux/videodev2.h> #include <media/drv-intf/exynos-fimc.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-subdev.h> #include "mipi-csis.h" @@ -718,7 +718,7 @@ static int s5pcsis_parse_dt(struct platform_device *pdev, struct csis_state *state) { struct device_node *node = pdev->dev.of_node; - struct v4l2_of_endpoint endpoint; + struct v4l2_fwnode_endpoint endpoint; int ret; if (of_property_read_u32(node, "clock-frequency", @@ -735,7 +735,7 @@ static int s5pcsis_parse_dt(struct platform_device *pdev, return -EINVAL; } /* Get port node and validate MIPI-CSI channel id. */ - ret = v4l2_of_parse_endpoint(node, &endpoint); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &endpoint); if (ret) goto err; diff --git a/drivers/media/platform/marvell-ccic/mcam-core.c b/drivers/media/platform/marvell-ccic/mcam-core.c index a8bda6679422..8cac2f202099 100644 --- a/drivers/media/platform/marvell-ccic/mcam-core.c +++ b/drivers/media/platform/marvell-ccic/mcam-core.c @@ -393,6 +393,7 @@ static int mcam_alloc_dma_bufs(struct mcam_camera *cam, int loadtime) dma_free_coherent(cam->dev, cam->dma_buf_size, cam->dma_bufs[0], cam->dma_handles[0]); cam->nbufs = 0; + /* fall-through */ case 0: cam_err(cam, "Insufficient DMA buffers, cannot operate\n"); return -ENOMEM; diff --git a/drivers/media/platform/mtk-mdp/mtk_mdp_core.c b/drivers/media/platform/mtk-mdp/mtk_mdp_core.c index 9e4eb7dcc424..81347558b24a 100644 --- a/drivers/media/platform/mtk-mdp/mtk_mdp_core.c +++ b/drivers/media/platform/mtk-mdp/mtk_mdp_core.c @@ -103,7 +103,7 @@ static int mtk_mdp_probe(struct platform_device *pdev) { struct mtk_mdp_dev *mdp; struct device *dev = &pdev->dev; - struct device_node *node; + struct device_node *node, *parent; int i, ret = 0; mdp = devm_kzalloc(dev, sizeof(*mdp), GFP_KERNEL); @@ -117,8 +117,16 @@ static int mtk_mdp_probe(struct platform_device *pdev) mutex_init(&mdp->lock); mutex_init(&mdp->vpulock); + /* Old dts had the components as child nodes */ + if (of_get_next_child(dev->of_node, NULL)) { + parent = dev->of_node; + dev_warn(dev, "device tree is out of date\n"); + } else { + parent = dev->of_node->parent; + } + /* Iterate over sibling MDP function blocks */ - for_each_child_of_node(dev->of_node, node) { + for_each_child_of_node(parent, node) { const struct of_device_id *of_id; enum mtk_mdp_comp_type comp_type; int comp_id; diff --git a/drivers/media/platform/mtk-vcodec/mtk_vcodec_dec.c b/drivers/media/platform/mtk-vcodec/mtk_vcodec_dec.c index a60b538686ea..843510979ad8 100644 --- a/drivers/media/platform/mtk-vcodec/mtk_vcodec_dec.c +++ b/drivers/media/platform/mtk-vcodec/mtk_vcodec_dec.c @@ -278,7 +278,7 @@ static void mtk_vdec_flush_decoder(struct mtk_vcodec_ctx *ctx) clean_free_buffer(ctx); } -static void mtk_vdec_pic_info_update(struct mtk_vcodec_ctx *ctx) +static int mtk_vdec_pic_info_update(struct mtk_vcodec_ctx *ctx) { unsigned int dpbsize = 0; int ret; @@ -288,7 +288,7 @@ static void mtk_vdec_pic_info_update(struct mtk_vcodec_ctx *ctx) &ctx->last_decoded_picinfo)) { mtk_v4l2_err("[%d]Error!! Cannot get param : GET_PARAM_PICTURE_INFO ERR", ctx->id); - return; + return -EINVAL; } if (ctx->last_decoded_picinfo.pic_w == 0 || @@ -296,12 +296,12 @@ static void mtk_vdec_pic_info_update(struct mtk_vcodec_ctx *ctx) ctx->last_decoded_picinfo.buf_w == 0 || ctx->last_decoded_picinfo.buf_h == 0) { mtk_v4l2_err("Cannot get correct pic info"); - return; + return -EINVAL; } if ((ctx->last_decoded_picinfo.pic_w == ctx->picinfo.pic_w) || (ctx->last_decoded_picinfo.pic_h == ctx->picinfo.pic_h)) - return; + return 0; mtk_v4l2_debug(1, "[%d]-> new(%d,%d), old(%d,%d), real(%d,%d)", @@ -316,6 +316,8 @@ static void mtk_vdec_pic_info_update(struct mtk_vcodec_ctx *ctx) mtk_v4l2_err("Incorrect dpb size, ret=%d", ret); ctx->dpb_size = dpbsize; + + return ret; } static void mtk_vdec_worker(struct work_struct *work) diff --git a/drivers/media/platform/mtk-vcodec/mtk_vcodec_util.h b/drivers/media/platform/mtk-vcodec/mtk_vcodec_util.h index 237e144c194f..06c254f5c171 100644 --- a/drivers/media/platform/mtk-vcodec/mtk_vcodec_util.h +++ b/drivers/media/platform/mtk-vcodec/mtk_vcodec_util.h @@ -32,6 +32,15 @@ extern int mtk_v4l2_dbg_level; extern bool mtk_vcodec_dbg; +#define mtk_v4l2_err(fmt, args...) \ + pr_err("[MTK_V4L2][ERROR] %s:%d: " fmt "\n", __func__, __LINE__, \ + ##args) + +#define mtk_vcodec_err(h, fmt, args...) \ + pr_err("[MTK_VCODEC][ERROR][%d]: %s() " fmt "\n", \ + ((struct mtk_vcodec_ctx *)h->ctx)->id, __func__, ##args) + + #if defined(DEBUG) #define mtk_v4l2_debug(level, fmt, args...) \ @@ -41,11 +50,6 @@ extern bool mtk_vcodec_dbg; level, __func__, __LINE__, ##args); \ } while (0) -#define mtk_v4l2_err(fmt, args...) \ - pr_err("[MTK_V4L2][ERROR] %s:%d: " fmt "\n", __func__, __LINE__, \ - ##args) - - #define mtk_v4l2_debug_enter() mtk_v4l2_debug(3, "+") #define mtk_v4l2_debug_leave() mtk_v4l2_debug(3, "-") @@ -57,22 +61,16 @@ extern bool mtk_vcodec_dbg; __func__, ##args); \ } while (0) -#define mtk_vcodec_err(h, fmt, args...) \ - pr_err("[MTK_VCODEC][ERROR][%d]: %s() " fmt "\n", \ - ((struct mtk_vcodec_ctx *)h->ctx)->id, __func__, ##args) - #define mtk_vcodec_debug_enter(h) mtk_vcodec_debug(h, "+") #define mtk_vcodec_debug_leave(h) mtk_vcodec_debug(h, "-") #else #define mtk_v4l2_debug(level, fmt, args...) {} -#define mtk_v4l2_err(fmt, args...) {} #define mtk_v4l2_debug_enter() {} #define mtk_v4l2_debug_leave() {} #define mtk_vcodec_debug(h, fmt, args...) {} -#define mtk_vcodec_err(h, fmt, args...) {} #define mtk_vcodec_debug_enter(h) {} #define mtk_vcodec_debug_leave(h) {} diff --git a/drivers/media/platform/omap3isp/isp.c b/drivers/media/platform/omap3isp/isp.c index 0d984a28a003..9df64c189883 100644 --- a/drivers/media/platform/omap3isp/isp.c +++ b/drivers/media/platform/omap3isp/isp.c @@ -55,6 +55,7 @@ #include <linux/module.h> #include <linux/omap-iommu.h> #include <linux/platform_device.h> +#include <linux/property.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> #include <linux/sched.h> @@ -63,9 +64,9 @@ #include <asm/dma-iommu.h> #include <media/v4l2-common.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-device.h> #include <media/v4l2-mc.h> -#include <media/v4l2-of.h> #include "isp.h" #include "ispreg.h" @@ -2007,20 +2008,20 @@ enum isp_of_phy { ISP_OF_PHY_CSIPHY2, }; -static int isp_of_parse_node(struct device *dev, struct device_node *node, - struct isp_async_subdev *isd) +static int isp_fwnode_parse(struct device *dev, struct fwnode_handle *fwnode, + struct isp_async_subdev *isd) { struct isp_bus_cfg *buscfg = &isd->bus; - struct v4l2_of_endpoint vep; + struct v4l2_fwnode_endpoint vep; unsigned int i; int ret; - ret = v4l2_of_parse_endpoint(node, &vep); + ret = v4l2_fwnode_endpoint_parse(fwnode, &vep); if (ret) return ret; - dev_dbg(dev, "parsing endpoint %s, interface %u\n", node->full_name, - vep.base.port); + dev_dbg(dev, "parsing endpoint %s, interface %u\n", + to_of_node(fwnode)->full_name, vep.base.port); switch (vep.base.port) { case ISP_OF_PHY_PARALLEL: @@ -2077,18 +2078,18 @@ static int isp_of_parse_node(struct device *dev, struct device_node *node, break; default: - dev_warn(dev, "%s: invalid interface %u\n", node->full_name, - vep.base.port); + dev_warn(dev, "%s: invalid interface %u\n", + to_of_node(fwnode)->full_name, vep.base.port); break; } return 0; } -static int isp_of_parse_nodes(struct device *dev, - struct v4l2_async_notifier *notifier) +static int isp_fwnodes_parse(struct device *dev, + struct v4l2_async_notifier *notifier) { - struct device_node *node = NULL; + struct fwnode_handle *fwnode = NULL; notifier->subdevs = devm_kcalloc( dev, ISP_MAX_SUBDEVS, sizeof(*notifier->subdevs), GFP_KERNEL); @@ -2096,7 +2097,8 @@ static int isp_of_parse_nodes(struct device *dev, return -ENOMEM; while (notifier->num_subdevs < ISP_MAX_SUBDEVS && - (node = of_graph_get_next_endpoint(dev->of_node, node))) { + (fwnode = fwnode_graph_get_next_endpoint( + of_fwnode_handle(dev->of_node), fwnode))) { struct isp_async_subdev *isd; isd = devm_kzalloc(dev, sizeof(*isd), GFP_KERNEL); @@ -2105,23 +2107,24 @@ static int isp_of_parse_nodes(struct device *dev, notifier->subdevs[notifier->num_subdevs] = &isd->asd; - if (isp_of_parse_node(dev, node, isd)) + if (isp_fwnode_parse(dev, fwnode, isd)) goto error; - isd->asd.match.of.node = of_graph_get_remote_port_parent(node); - if (!isd->asd.match.of.node) { + isd->asd.match.fwnode.fwnode = + fwnode_graph_get_remote_port_parent(fwnode); + if (!isd->asd.match.fwnode.fwnode) { dev_warn(dev, "bad remote port parent\n"); goto error; } - isd->asd.match_type = V4L2_ASYNC_MATCH_OF; + isd->asd.match_type = V4L2_ASYNC_MATCH_FWNODE; notifier->num_subdevs++; } return notifier->num_subdevs; error: - of_node_put(node); + fwnode_handle_put(fwnode); return -EINVAL; } @@ -2192,8 +2195,8 @@ static int isp_probe(struct platform_device *pdev) return -ENOMEM; } - ret = of_property_read_u32(pdev->dev.of_node, "ti,phy-type", - &isp->phy_type); + ret = fwnode_property_read_u32(of_fwnode_handle(pdev->dev.of_node), + "ti,phy-type", &isp->phy_type); if (ret) return ret; @@ -2202,12 +2205,12 @@ static int isp_probe(struct platform_device *pdev) if (IS_ERR(isp->syscon)) return PTR_ERR(isp->syscon); - ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 1, - &isp->syscon_offset); + ret = of_property_read_u32_index(pdev->dev.of_node, + "syscon", 1, &isp->syscon_offset); if (ret) return ret; - ret = isp_of_parse_nodes(&pdev->dev, &isp->notifier); + ret = isp_fwnodes_parse(&pdev->dev, &isp->notifier); if (ret < 0) return ret; diff --git a/drivers/media/platform/pxa_camera.c b/drivers/media/platform/pxa_camera.c index 929006f65cc7..399095170b6e 100644 --- a/drivers/media/platform/pxa_camera.c +++ b/drivers/media/platform/pxa_camera.c @@ -25,6 +25,7 @@ #include <linux/mm.h> #include <linux/moduleparam.h> #include <linux/of.h> +#include <linux/of_graph.h> #include <linux/time.h> #include <linux/platform_device.h> #include <linux/clk.h> @@ -37,9 +38,11 @@ #include <media/v4l2-async.h> #include <media/v4l2-clk.h> #include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> #include <media/v4l2-device.h> +#include <media/v4l2-event.h> #include <media/v4l2-ioctl.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/videobuf2-dma-sg.h> @@ -345,6 +348,36 @@ static const struct pxa_mbus_lookup mbus_fmt[] = { .layout = PXA_MBUS_LAYOUT_PACKED, }, }, { + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .name = "Bayer 8 GBRG", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_NONE, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .name = "Bayer 8 GRBG", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_NONE, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .name = "Bayer 8 RGGB", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_NONE, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { .code = MEDIA_BUS_FMT_SBGGR10_1X10, .fmt = { .fourcc = V4L2_PIX_FMT_SBGGR10, @@ -445,16 +478,6 @@ static const struct pxa_mbus_lookup mbus_fmt[] = { .layout = PXA_MBUS_LAYOUT_PACKED, }, }, { - .code = MEDIA_BUS_FMT_SGRBG8_1X8, - .fmt = { - .fourcc = V4L2_PIX_FMT_SGRBG8, - .name = "Bayer 8 GRBG", - .bits_per_sample = 8, - .packing = PXA_MBUS_PACKING_NONE, - .order = PXA_MBUS_ORDER_LE, - .layout = PXA_MBUS_LAYOUT_PACKED, - }, -}, { .code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, .fmt = { .fourcc = V4L2_PIX_FMT_SGRBG10DPCM8, @@ -555,6 +578,9 @@ static s32 pxa_mbus_bytes_per_line(u32 width, const struct pxa_mbus_pixelfmt *mf static s32 pxa_mbus_image_size(const struct pxa_mbus_pixelfmt *mf, u32 bytes_per_line, u32 height) { + if (mf->layout == PXA_MBUS_LAYOUT_PACKED) + return bytes_per_line * height; + switch (mf->packing) { case PXA_MBUS_PACKING_2X8_PADHI: return bytes_per_line * height * 2; @@ -1099,7 +1125,7 @@ static u32 mclk_get_divisor(struct platform_device *pdev, /* mclk <= ciclk / 4 (27.4.2) */ if (mclk > lcdclk / 4) { mclk = lcdclk / 4; - dev_warn(pcdev_to_dev(pcdev), + dev_warn(&pdev->dev, "Limiting master clock to %lu\n", mclk); } @@ -1110,7 +1136,7 @@ static u32 mclk_get_divisor(struct platform_device *pdev, if (pcdev->platform_flags & PXA_CAMERA_MCLK_EN) pcdev->mclk = lcdclk / (2 * (div + 1)); - dev_dbg(pcdev_to_dev(pcdev), "LCD clock %luHz, target freq %luHz, divisor %u\n", + dev_dbg(&pdev->dev, "LCD clock %luHz, target freq %luHz, divisor %u\n", lcdclk, mclk, div); return div; @@ -1291,6 +1317,7 @@ static void pxa_camera_setup_cicr(struct pxa_camera_dev *pcdev, * transformation. Note that UYVY is the only format that * should be used if pxa framebuffer Overlay2 is used. */ + /* fall through */ case V4L2_PIX_FMT_UYVY: case V4L2_PIX_FMT_VYUY: case V4L2_PIX_FMT_YUYV: @@ -2066,6 +2093,8 @@ static const struct v4l2_ioctl_ops pxa_camera_ioctl_ops = { .vidioc_g_register = pxac_vidioc_g_register, .vidioc_s_register = pxac_vidioc_s_register, #endif + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; static struct v4l2_clk_ops pxa_camera_mclk_ops = { @@ -2177,6 +2206,12 @@ static void pxa_camera_sensor_unbind(struct v4l2_async_notifier *notifier, pxa_dma_stop_channels(pcdev); pxa_camera_destroy_formats(pcdev); + + if (pcdev->mclk_clk) { + v4l2_clk_unregister(pcdev->mclk_clk); + pcdev->mclk_clk = NULL; + } + video_unregister_device(&pcdev->vdev); pcdev->sensor = NULL; @@ -2236,7 +2271,7 @@ static int pxa_camera_pdata_from_dt(struct device *dev, { u32 mclk_rate; struct device_node *remote, *np = dev->of_node; - struct v4l2_of_endpoint ep; + struct v4l2_fwnode_endpoint ep; int err = of_property_read_u32(np, "clock-frequency", &mclk_rate); if (!err) { @@ -2250,7 +2285,7 @@ static int pxa_camera_pdata_from_dt(struct device *dev, return -EINVAL; } - err = v4l2_of_parse_endpoint(np, &ep); + err = v4l2_fwnode_endpoint_parse(of_fwnode_handle(np), &ep); if (err) { dev_err(dev, "could not parse endpoint\n"); goto out; @@ -2287,10 +2322,10 @@ static int pxa_camera_pdata_from_dt(struct device *dev, if (ep.bus.parallel.flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) pcdev->platform_flags |= PXA_CAMERA_PCLK_EN; - asd->match_type = V4L2_ASYNC_MATCH_OF; + asd->match_type = V4L2_ASYNC_MATCH_FWNODE; remote = of_graph_get_remote_port(np); if (remote) { - asd->match.of.node = remote; + asd->match.fwnode.fwnode = of_fwnode_handle(remote); of_node_put(remote); } else { dev_notice(dev, "no remote for %s\n", of_node_full_name(np)); @@ -2501,7 +2536,13 @@ static int pxa_camera_remove(struct platform_device *pdev) dma_release_channel(pcdev->dma_chans[1]); dma_release_channel(pcdev->dma_chans[2]); - v4l2_clk_unregister(pcdev->mclk_clk); + v4l2_async_notifier_unregister(&pcdev->notifier); + + if (pcdev->mclk_clk) { + v4l2_clk_unregister(pcdev->mclk_clk); + pcdev->mclk_clk = NULL; + } + v4l2_device_unregister(&pcdev->v4l2_dev); dev_info(&pdev->dev, "PXA Camera driver unloaded\n"); diff --git a/drivers/media/platform/qcom/venus/Makefile b/drivers/media/platform/qcom/venus/Makefile new file mode 100644 index 000000000000..0fe9afb83697 --- /dev/null +++ b/drivers/media/platform/qcom/venus/Makefile @@ -0,0 +1,11 @@ +# Makefile for Qualcomm Venus driver + +venus-core-objs += core.o helpers.o firmware.o \ + hfi_venus.o hfi_msgs.o hfi_cmds.o hfi.o + +venus-dec-objs += vdec.o vdec_ctrls.o +venus-enc-objs += venc.o venc_ctrls.o + +obj-$(CONFIG_VIDEO_QCOM_VENUS) += venus-core.o +obj-$(CONFIG_VIDEO_QCOM_VENUS) += venus-dec.o +obj-$(CONFIG_VIDEO_QCOM_VENUS) += venus-enc.o diff --git a/drivers/media/platform/qcom/venus/core.c b/drivers/media/platform/qcom/venus/core.c new file mode 100644 index 000000000000..776d2bae6979 --- /dev/null +++ b/drivers/media/platform/qcom/venus/core.c @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/clk.h> +#include <linux/init.h> +#include <linux/ioctl.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/pm_runtime.h> +#include <media/videobuf2-v4l2.h> +#include <media/v4l2-mem2mem.h> +#include <media/v4l2-ioctl.h> + +#include "core.h" +#include "vdec.h" +#include "venc.h" +#include "firmware.h" + +static void venus_event_notify(struct venus_core *core, u32 event) +{ + struct venus_inst *inst; + + switch (event) { + case EVT_SYS_WATCHDOG_TIMEOUT: + case EVT_SYS_ERROR: + break; + default: + return; + } + + mutex_lock(&core->lock); + core->sys_error = true; + list_for_each_entry(inst, &core->instances, list) + inst->ops->event_notify(inst, EVT_SESSION_ERROR, NULL); + mutex_unlock(&core->lock); + + disable_irq_nosync(core->irq); + + /* + * Delay recovery to ensure venus has completed any pending cache + * operations. Without this sleep, we see device reset when firmware is + * unloaded after a system error. + */ + schedule_delayed_work(&core->work, msecs_to_jiffies(100)); +} + +static const struct hfi_core_ops venus_core_ops = { + .event_notify = venus_event_notify, +}; + +static void venus_sys_error_handler(struct work_struct *work) +{ + struct venus_core *core = + container_of(work, struct venus_core, work.work); + int ret = 0; + + dev_warn(core->dev, "system error has occurred, starting recovery!\n"); + + pm_runtime_get_sync(core->dev); + + hfi_core_deinit(core, true); + hfi_destroy(core); + mutex_lock(&core->lock); + venus_shutdown(&core->dev_fw); + + pm_runtime_put_sync(core->dev); + + ret |= hfi_create(core, &venus_core_ops); + + pm_runtime_get_sync(core->dev); + + ret |= venus_boot(core->dev, &core->dev_fw, core->res->fwname); + + ret |= hfi_core_resume(core, true); + + enable_irq(core->irq); + + mutex_unlock(&core->lock); + + ret |= hfi_core_init(core); + + pm_runtime_put_sync(core->dev); + + if (ret) { + disable_irq_nosync(core->irq); + dev_warn(core->dev, "recovery failed (%d)\n", ret); + schedule_delayed_work(&core->work, msecs_to_jiffies(10)); + return; + } + + mutex_lock(&core->lock); + core->sys_error = false; + mutex_unlock(&core->lock); +} + +static int venus_clks_get(struct venus_core *core) +{ + const struct venus_resources *res = core->res; + struct device *dev = core->dev; + unsigned int i; + + for (i = 0; i < res->clks_num; i++) { + core->clks[i] = devm_clk_get(dev, res->clks[i]); + if (IS_ERR(core->clks[i])) + return PTR_ERR(core->clks[i]); + } + + return 0; +} + +static int venus_clks_enable(struct venus_core *core) +{ + const struct venus_resources *res = core->res; + unsigned int i; + int ret; + + for (i = 0; i < res->clks_num; i++) { + ret = clk_prepare_enable(core->clks[i]); + if (ret) + goto err; + } + + return 0; +err: + while (--i) + clk_disable_unprepare(core->clks[i]); + + return ret; +} + +static void venus_clks_disable(struct venus_core *core) +{ + const struct venus_resources *res = core->res; + unsigned int i = res->clks_num; + + while (i--) + clk_disable_unprepare(core->clks[i]); +} + +static int venus_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct venus_core *core; + struct resource *r; + int ret; + + core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); + if (!core) + return -ENOMEM; + + core->dev = dev; + platform_set_drvdata(pdev, core); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + core->base = devm_ioremap_resource(dev, r); + if (IS_ERR(core->base)) + return PTR_ERR(core->base); + + core->irq = platform_get_irq(pdev, 0); + if (core->irq < 0) + return core->irq; + + core->res = of_device_get_match_data(dev); + if (!core->res) + return -ENODEV; + + ret = venus_clks_get(core); + if (ret) + return ret; + + ret = dma_set_mask_and_coherent(dev, core->res->dma_mask); + if (ret) + return ret; + + INIT_LIST_HEAD(&core->instances); + mutex_init(&core->lock); + INIT_DELAYED_WORK(&core->work, venus_sys_error_handler); + + ret = devm_request_threaded_irq(dev, core->irq, hfi_isr, hfi_isr_thread, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "venus", core); + if (ret) + return ret; + + ret = hfi_create(core, &venus_core_ops); + if (ret) + return ret; + + pm_runtime_enable(dev); + + ret = pm_runtime_get_sync(dev); + if (ret < 0) + goto err_runtime_disable; + + ret = venus_boot(dev, &core->dev_fw, core->res->fwname); + if (ret) + goto err_runtime_disable; + + ret = hfi_core_resume(core, true); + if (ret) + goto err_venus_shutdown; + + ret = hfi_core_init(core); + if (ret) + goto err_venus_shutdown; + + ret = v4l2_device_register(dev, &core->v4l2_dev); + if (ret) + goto err_core_deinit; + + ret = of_platform_populate(dev->of_node, NULL, NULL, dev); + if (ret) + goto err_dev_unregister; + + ret = pm_runtime_put_sync(dev); + if (ret) + goto err_dev_unregister; + + return 0; + +err_dev_unregister: + v4l2_device_unregister(&core->v4l2_dev); +err_core_deinit: + hfi_core_deinit(core, false); +err_venus_shutdown: + venus_shutdown(&core->dev_fw); +err_runtime_disable: + pm_runtime_set_suspended(dev); + pm_runtime_disable(dev); + hfi_destroy(core); + return ret; +} + +static int venus_remove(struct platform_device *pdev) +{ + struct venus_core *core = platform_get_drvdata(pdev); + struct device *dev = core->dev; + int ret; + + ret = pm_runtime_get_sync(dev); + WARN_ON(ret < 0); + + ret = hfi_core_deinit(core, true); + WARN_ON(ret); + + hfi_destroy(core); + venus_shutdown(&core->dev_fw); + of_platform_depopulate(dev); + + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + v4l2_device_unregister(&core->v4l2_dev); + + return ret; +} + +#ifdef CONFIG_PM +static int venus_runtime_suspend(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + int ret; + + ret = hfi_core_suspend(core); + + venus_clks_disable(core); + + return ret; +} + +static int venus_runtime_resume(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + int ret; + + ret = venus_clks_enable(core); + if (ret) + return ret; + + ret = hfi_core_resume(core, false); + if (ret) + goto err_clks_disable; + + return 0; + +err_clks_disable: + venus_clks_disable(core); + return ret; +} +#endif + +static const struct dev_pm_ops venus_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(venus_runtime_suspend, venus_runtime_resume, NULL) +}; + +static const struct freq_tbl msm8916_freq_table[] = { + { 352800, 228570000 }, /* 1920x1088 @ 30 + 1280x720 @ 30 */ + { 244800, 160000000 }, /* 1920x1088 @ 30 */ + { 108000, 100000000 }, /* 1280x720 @ 30 */ +}; + +static const struct reg_val msm8916_reg_preset[] = { + { 0xe0020, 0x05555556 }, + { 0xe0024, 0x05555556 }, + { 0x80124, 0x00000003 }, +}; + +static const struct venus_resources msm8916_res = { + .freq_tbl = msm8916_freq_table, + .freq_tbl_size = ARRAY_SIZE(msm8916_freq_table), + .reg_tbl = msm8916_reg_preset, + .reg_tbl_size = ARRAY_SIZE(msm8916_reg_preset), + .clks = { "core", "iface", "bus", }, + .clks_num = 3, + .max_load = 352800, /* 720p@30 + 1080p@30 */ + .hfi_version = HFI_VERSION_1XX, + .vmem_id = VIDC_RESOURCE_NONE, + .vmem_size = 0, + .vmem_addr = 0, + .dma_mask = 0xddc00000 - 1, + .fwname = "qcom/venus-1.8/venus.mdt", +}; + +static const struct freq_tbl msm8996_freq_table[] = { + { 1944000, 490000000 }, /* 4k UHD @ 60 */ + { 972000, 320000000 }, /* 4k UHD @ 30 */ + { 489600, 150000000 }, /* 1080p @ 60 */ + { 244800, 75000000 }, /* 1080p @ 30 */ +}; + +static const struct reg_val msm8996_reg_preset[] = { + { 0x80010, 0xffffffff }, + { 0x80018, 0x00001556 }, + { 0x8001C, 0x00001556 }, +}; + +static const struct venus_resources msm8996_res = { + .freq_tbl = msm8996_freq_table, + .freq_tbl_size = ARRAY_SIZE(msm8996_freq_table), + .reg_tbl = msm8996_reg_preset, + .reg_tbl_size = ARRAY_SIZE(msm8996_reg_preset), + .clks = {"core", "iface", "bus", "mbus" }, + .clks_num = 4, + .max_load = 2563200, + .hfi_version = HFI_VERSION_3XX, + .vmem_id = VIDC_RESOURCE_NONE, + .vmem_size = 0, + .vmem_addr = 0, + .dma_mask = 0xddc00000 - 1, + .fwname = "qcom/venus-4.2/venus.mdt", +}; + +static const struct of_device_id venus_dt_match[] = { + { .compatible = "qcom,msm8916-venus", .data = &msm8916_res, }, + { .compatible = "qcom,msm8996-venus", .data = &msm8996_res, }, + { } +}; +MODULE_DEVICE_TABLE(of, venus_dt_match); + +static struct platform_driver qcom_venus_driver = { + .probe = venus_probe, + .remove = venus_remove, + .driver = { + .name = "qcom-venus", + .of_match_table = venus_dt_match, + .pm = &venus_pm_ops, + }, +}; +module_platform_driver(qcom_venus_driver); + +MODULE_ALIAS("platform:qcom-venus"); +MODULE_DESCRIPTION("Qualcomm Venus video encoder and decoder driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/qcom/venus/core.h b/drivers/media/platform/qcom/venus/core.h new file mode 100644 index 000000000000..e542700eee32 --- /dev/null +++ b/drivers/media/platform/qcom/venus/core.h @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __VENUS_CORE_H_ +#define __VENUS_CORE_H_ + +#include <linux/list.h> +#include <media/videobuf2-v4l2.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> + +#include "hfi.h" + +#define VIDC_CLKS_NUM_MAX 4 + +struct freq_tbl { + unsigned int load; + unsigned long freq; +}; + +struct reg_val { + u32 reg; + u32 value; +}; + +struct venus_resources { + u64 dma_mask; + const struct freq_tbl *freq_tbl; + unsigned int freq_tbl_size; + const struct reg_val *reg_tbl; + unsigned int reg_tbl_size; + const char * const clks[VIDC_CLKS_NUM_MAX]; + unsigned int clks_num; + enum hfi_version hfi_version; + u32 max_load; + unsigned int vmem_id; + u32 vmem_size; + u32 vmem_addr; + const char *fwname; +}; + +struct venus_format { + u32 pixfmt; + unsigned int num_planes; + u32 type; +}; + +/** + * struct venus_core - holds core parameters valid for all instances + * + * @base: IO memory base address + * @irq: Venus irq + * @clks: an array of struct clk pointers + * @core0_clk: a struct clk pointer for core0 + * @core1_clk: a struct clk pointer for core1 + * @vdev_dec: a reference to video device structure for decoder instances + * @vdev_enc: a reference to video device structure for encoder instances + * @v4l2_dev: a holder for v4l2 device structure + * @res: a reference to venus resources structure + * @dev: convenience struct device pointer + * @dev_dec: convenience struct device pointer for decoder device + * @dev_enc: convenience struct device pointer for encoder device + * @lock: a lock for this strucure + * @instances: a list_head of all instances + * @insts_count: num of instances + * @state: the state of the venus core + * @done: a completion for sync HFI operations + * @error: an error returned during last HFI sync operations + * @sys_error: an error flag that signal system error event + * @core_ops: the core operations + * @enc_codecs: encoders supported by this core + * @dec_codecs: decoders supported by this core + * @max_sessions_supported: holds the maximum number of sessions + * @core_caps: core capabilities + * @priv: a private filed for HFI operations + * @ops: the core HFI operations + * @work: a delayed work for handling system fatal error + */ +struct venus_core { + void __iomem *base; + int irq; + struct clk *clks[VIDC_CLKS_NUM_MAX]; + struct clk *core0_clk; + struct clk *core1_clk; + struct video_device *vdev_dec; + struct video_device *vdev_enc; + struct v4l2_device v4l2_dev; + const struct venus_resources *res; + struct device *dev; + struct device *dev_dec; + struct device *dev_enc; + struct device dev_fw; + struct mutex lock; + struct list_head instances; + atomic_t insts_count; + unsigned int state; + struct completion done; + unsigned int error; + bool sys_error; + const struct hfi_core_ops *core_ops; + u32 enc_codecs; + u32 dec_codecs; + unsigned int max_sessions_supported; +#define ENC_ROTATION_CAPABILITY 0x1 +#define ENC_SCALING_CAPABILITY 0x2 +#define ENC_DEINTERLACE_CAPABILITY 0x4 +#define DEC_MULTI_STREAM_CAPABILITY 0x8 + unsigned int core_caps; + void *priv; + const struct hfi_ops *ops; + struct delayed_work work; +}; + +struct vdec_controls { + u32 post_loop_deb_mode; + u32 profile; + u32 level; +}; + +struct venc_controls { + u16 gop_size; + u32 num_p_frames; + u32 num_b_frames; + u32 bitrate_mode; + u32 bitrate; + u32 bitrate_peak; + + u32 h264_i_period; + u32 h264_entropy_mode; + u32 h264_i_qp; + u32 h264_p_qp; + u32 h264_b_qp; + u32 h264_min_qp; + u32 h264_max_qp; + u32 h264_loop_filter_mode; + u32 h264_loop_filter_alpha; + u32 h264_loop_filter_beta; + + u32 vp8_min_qp; + u32 vp8_max_qp; + + u32 multi_slice_mode; + u32 multi_slice_max_bytes; + u32 multi_slice_max_mb; + + u32 header_mode; + + struct { + u32 mpeg4; + u32 h264; + u32 vpx; + } profile; + struct { + u32 mpeg4; + u32 h264; + } level; +}; + +struct venus_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; + dma_addr_t dma_addr; + u32 size; + struct list_head reg_list; + u32 flags; + struct list_head ref_list; +}; + +#define to_venus_buffer(ptr) container_of(ptr, struct venus_buffer, vb) + +/** + * struct venus_inst - holds per instance paramerters + * + * @list: used for attach an instance to the core + * @lock: instance lock + * @core: a reference to the core struct + * @internalbufs: a list of internal bufferes + * @registeredbufs: a list of registered capture bufferes + * @delayed_process a list of delayed buffers + * @delayed_process_work: a work_struct for process delayed buffers + * @ctrl_handler: v4l control handler + * @controls: a union of decoder and encoder control parameters + * @fh: a holder of v4l file handle structure + * @streamon_cap: stream on flag for capture queue + * @streamon_out: stream on flag for output queue + * @cmd_stop: a flag to signal encoder/decoder commands + * @width: current capture width + * @height: current capture height + * @out_width: current output width + * @out_height: current output height + * @colorspace: current color space + * @quantization: current quantization + * @xfer_func: current xfer function + * @fps: holds current FPS + * @timeperframe: holds current time per frame structure + * @fmt_out: a reference to output format structure + * @fmt_cap: a reference to capture format structure + * @num_input_bufs: holds number of input buffers + * @num_output_bufs: holds number of output buffers + * @input_buf_size holds input buffer size + * @output_buf_size: holds output buffer size + * @reconfig: a flag raised by decoder when the stream resolution changed + * @reconfig_width: holds the new width + * @reconfig_height: holds the new height + * @sequence_cap: a sequence counter for capture queue + * @sequence_out: a sequence counter for output queue + * @m2m_dev: a reference to m2m device structure + * @m2m_ctx: a reference to m2m context structure + * @state: current state of the instance + * @done: a completion for sync HFI operation + * @error: an error returned during last HFI sync operation + * @session_error: a flag rised by HFI interface in case of session error + * @ops: HFI operations + * @priv: a private for HFI operations callbacks + * @session_type: the type of the session (decoder or encoder) + * @hprop: a union used as a holder by get property + * @cap_width: width capability + * @cap_height: height capability + * @cap_mbs_per_frame: macroblocks per frame capability + * @cap_mbs_per_sec: macroblocks per second capability + * @cap_framerate: framerate capability + * @cap_scale_x: horizontal scaling capability + * @cap_scale_y: vertical scaling capability + * @cap_bitrate: bitrate capability + * @cap_hier_p: hier capability + * @cap_ltr_count: LTR count capability + * @cap_secure_output2_threshold: secure OUTPUT2 threshold capability + * @cap_bufs_mode_static: buffers allocation mode capability + * @cap_bufs_mode_dynamic: buffers allocation mode capability + * @pl_count: count of supported profiles/levels + * @pl: supported profiles/levels + * @bufreq: holds buffer requirements + */ +struct venus_inst { + struct list_head list; + struct mutex lock; + struct venus_core *core; + struct list_head internalbufs; + struct list_head registeredbufs; + struct list_head delayed_process; + struct work_struct delayed_process_work; + + struct v4l2_ctrl_handler ctrl_handler; + union { + struct vdec_controls dec; + struct venc_controls enc; + } controls; + struct v4l2_fh fh; + unsigned int streamon_cap, streamon_out; + bool cmd_stop; + u32 width; + u32 height; + u32 out_width; + u32 out_height; + u32 colorspace; + u8 ycbcr_enc; + u8 quantization; + u8 xfer_func; + u64 fps; + struct v4l2_fract timeperframe; + const struct venus_format *fmt_out; + const struct venus_format *fmt_cap; + unsigned int num_input_bufs; + unsigned int num_output_bufs; + unsigned int input_buf_size; + unsigned int output_buf_size; + bool reconfig; + u32 reconfig_width; + u32 reconfig_height; + u32 sequence_cap; + u32 sequence_out; + struct v4l2_m2m_dev *m2m_dev; + struct v4l2_m2m_ctx *m2m_ctx; + unsigned int state; + struct completion done; + unsigned int error; + bool session_error; + const struct hfi_inst_ops *ops; + u32 session_type; + union hfi_get_property hprop; + struct hfi_capability cap_width; + struct hfi_capability cap_height; + struct hfi_capability cap_mbs_per_frame; + struct hfi_capability cap_mbs_per_sec; + struct hfi_capability cap_framerate; + struct hfi_capability cap_scale_x; + struct hfi_capability cap_scale_y; + struct hfi_capability cap_bitrate; + struct hfi_capability cap_hier_p; + struct hfi_capability cap_ltr_count; + struct hfi_capability cap_secure_output2_threshold; + bool cap_bufs_mode_static; + bool cap_bufs_mode_dynamic; + unsigned int pl_count; + struct hfi_profile_level pl[HFI_MAX_PROFILE_COUNT]; + struct hfi_buffer_requirements bufreq[HFI_BUFFER_TYPE_MAX]; +}; + +#define ctrl_to_inst(ctrl) \ + container_of((ctrl)->handler, struct venus_inst, ctrl_handler) + +static inline struct venus_inst *to_inst(struct file *filp) +{ + return container_of(filp->private_data, struct venus_inst, fh); +} + +static inline void *to_hfi_priv(struct venus_core *core) +{ + return core->priv; +} + +#endif diff --git a/drivers/media/platform/qcom/venus/firmware.c b/drivers/media/platform/qcom/venus/firmware.c new file mode 100644 index 000000000000..1b1a4f355918 --- /dev/null +++ b/drivers/media/platform/qcom/venus/firmware.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/dma-mapping.h> +#include <linux/firmware.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_reserved_mem.h> +#include <linux/slab.h> +#include <linux/qcom_scm.h> +#include <linux/soc/qcom/mdt_loader.h> + +#include "firmware.h" + +#define VENUS_PAS_ID 9 +#define VENUS_FW_MEM_SIZE SZ_8M + +static void device_release_dummy(struct device *dev) +{ + of_reserved_mem_device_release(dev); +} + +int venus_boot(struct device *parent, struct device *fw_dev, const char *fwname) +{ + const struct firmware *mdt; + phys_addr_t mem_phys; + ssize_t fw_size; + size_t mem_size; + void *mem_va; + int ret; + + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + fw_dev->parent = parent; + fw_dev->release = device_release_dummy; + + ret = dev_set_name(fw_dev, "%s:%s", dev_name(parent), "firmware"); + if (ret) + return ret; + + ret = device_register(fw_dev); + if (ret < 0) + return ret; + + ret = of_reserved_mem_device_init_by_idx(fw_dev, parent->of_node, 0); + if (ret) + goto err_unreg_device; + + mem_size = VENUS_FW_MEM_SIZE; + + mem_va = dmam_alloc_coherent(fw_dev, mem_size, &mem_phys, GFP_KERNEL); + if (!mem_va) { + ret = -ENOMEM; + goto err_unreg_device; + } + + ret = request_firmware(&mdt, fwname, fw_dev); + if (ret < 0) + goto err_unreg_device; + + fw_size = qcom_mdt_get_size(mdt); + if (fw_size < 0) { + ret = fw_size; + release_firmware(mdt); + goto err_unreg_device; + } + + ret = qcom_mdt_load(fw_dev, mdt, fwname, VENUS_PAS_ID, mem_va, mem_phys, + mem_size); + + release_firmware(mdt); + + if (ret) + goto err_unreg_device; + + ret = qcom_scm_pas_auth_and_reset(VENUS_PAS_ID); + if (ret) + goto err_unreg_device; + + return 0; + +err_unreg_device: + device_unregister(fw_dev); + return ret; +} + +int venus_shutdown(struct device *fw_dev) +{ + int ret; + + ret = qcom_scm_pas_shutdown(VENUS_PAS_ID); + device_unregister(fw_dev); + memset(fw_dev, 0, sizeof(*fw_dev)); + + return ret; +} diff --git a/drivers/media/platform/qcom/venus/firmware.h b/drivers/media/platform/qcom/venus/firmware.h new file mode 100644 index 000000000000..f81a98979798 --- /dev/null +++ b/drivers/media/platform/qcom/venus/firmware.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __VENUS_FIRMWARE_H__ +#define __VENUS_FIRMWARE_H__ + +struct device; + +int venus_boot(struct device *parent, struct device *fw_dev, + const char *fwname); +int venus_shutdown(struct device *fw_dev); + +#endif diff --git a/drivers/media/platform/qcom/venus/helpers.c b/drivers/media/platform/qcom/venus/helpers.c new file mode 100644 index 000000000000..5f4434c0a8f1 --- /dev/null +++ b/drivers/media/platform/qcom/venus/helpers.c @@ -0,0 +1,725 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/clk.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <media/videobuf2-dma-sg.h> +#include <media/v4l2-mem2mem.h> +#include <asm/div64.h> + +#include "core.h" +#include "helpers.h" +#include "hfi_helper.h" + +struct intbuf { + struct list_head list; + u32 type; + size_t size; + void *va; + dma_addr_t da; + unsigned long attrs; +}; + +static int intbufs_set_buffer(struct venus_inst *inst, u32 type) +{ + struct venus_core *core = inst->core; + struct device *dev = core->dev; + struct hfi_buffer_requirements bufreq; + struct hfi_buffer_desc bd; + struct intbuf *buf; + unsigned int i; + int ret; + + ret = venus_helper_get_bufreq(inst, type, &bufreq); + if (ret) + return 0; + + if (!bufreq.size) + return 0; + + for (i = 0; i < bufreq.count_actual; i++) { + buf = kzalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto fail; + } + + buf->type = bufreq.type; + buf->size = bufreq.size; + buf->attrs = DMA_ATTR_WRITE_COMBINE | + DMA_ATTR_NO_KERNEL_MAPPING; + buf->va = dma_alloc_attrs(dev, buf->size, &buf->da, GFP_KERNEL, + buf->attrs); + if (!buf->va) { + ret = -ENOMEM; + goto fail; + } + + memset(&bd, 0, sizeof(bd)); + bd.buffer_size = buf->size; + bd.buffer_type = buf->type; + bd.num_buffers = 1; + bd.device_addr = buf->da; + + ret = hfi_session_set_buffers(inst, &bd); + if (ret) { + dev_err(dev, "set session buffers failed\n"); + goto dma_free; + } + + list_add_tail(&buf->list, &inst->internalbufs); + } + + return 0; + +dma_free: + dma_free_attrs(dev, buf->size, buf->va, buf->da, buf->attrs); +fail: + kfree(buf); + return ret; +} + +static int intbufs_unset_buffers(struct venus_inst *inst) +{ + struct hfi_buffer_desc bd = {0}; + struct intbuf *buf, *n; + int ret = 0; + + list_for_each_entry_safe(buf, n, &inst->internalbufs, list) { + bd.buffer_size = buf->size; + bd.buffer_type = buf->type; + bd.num_buffers = 1; + bd.device_addr = buf->da; + bd.response_required = true; + + ret = hfi_session_unset_buffers(inst, &bd); + + list_del_init(&buf->list); + dma_free_attrs(inst->core->dev, buf->size, buf->va, buf->da, + buf->attrs); + kfree(buf); + } + + return ret; +} + +static const unsigned int intbuf_types[] = { + HFI_BUFFER_INTERNAL_SCRATCH, + HFI_BUFFER_INTERNAL_SCRATCH_1, + HFI_BUFFER_INTERNAL_SCRATCH_2, + HFI_BUFFER_INTERNAL_PERSIST, + HFI_BUFFER_INTERNAL_PERSIST_1, +}; + +static int intbufs_alloc(struct venus_inst *inst) +{ + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(intbuf_types); i++) { + ret = intbufs_set_buffer(inst, intbuf_types[i]); + if (ret) + goto error; + } + + return 0; + +error: + intbufs_unset_buffers(inst); + return ret; +} + +static int intbufs_free(struct venus_inst *inst) +{ + return intbufs_unset_buffers(inst); +} + +static u32 load_per_instance(struct venus_inst *inst) +{ + u32 mbs; + + if (!inst || !(inst->state >= INST_INIT && inst->state < INST_STOP)) + return 0; + + mbs = (ALIGN(inst->width, 16) / 16) * (ALIGN(inst->height, 16) / 16); + + return mbs * inst->fps; +} + +static u32 load_per_type(struct venus_core *core, u32 session_type) +{ + struct venus_inst *inst = NULL; + u32 mbs_per_sec = 0; + + mutex_lock(&core->lock); + list_for_each_entry(inst, &core->instances, list) { + if (inst->session_type != session_type) + continue; + + mbs_per_sec += load_per_instance(inst); + } + mutex_unlock(&core->lock); + + return mbs_per_sec; +} + +static int load_scale_clocks(struct venus_core *core) +{ + const struct freq_tbl *table = core->res->freq_tbl; + unsigned int num_rows = core->res->freq_tbl_size; + unsigned long freq = table[0].freq; + struct clk *clk = core->clks[0]; + struct device *dev = core->dev; + u32 mbs_per_sec; + unsigned int i; + int ret; + + mbs_per_sec = load_per_type(core, VIDC_SESSION_TYPE_ENC) + + load_per_type(core, VIDC_SESSION_TYPE_DEC); + + if (mbs_per_sec > core->res->max_load) + dev_warn(dev, "HW is overloaded, needed: %d max: %d\n", + mbs_per_sec, core->res->max_load); + + if (!mbs_per_sec && num_rows > 1) { + freq = table[num_rows - 1].freq; + goto set_freq; + } + + for (i = 0; i < num_rows; i++) { + if (mbs_per_sec > table[i].load) + break; + freq = table[i].freq; + } + +set_freq: + + if (core->res->hfi_version == HFI_VERSION_3XX) { + ret = clk_set_rate(clk, freq); + ret |= clk_set_rate(core->core0_clk, freq); + ret |= clk_set_rate(core->core1_clk, freq); + } else { + ret = clk_set_rate(clk, freq); + } + + if (ret) { + dev_err(dev, "failed to set clock rate %lu (%d)\n", freq, ret); + return ret; + } + + return 0; +} + +static void fill_buffer_desc(const struct venus_buffer *buf, + struct hfi_buffer_desc *bd, bool response) +{ + memset(bd, 0, sizeof(*bd)); + bd->buffer_type = HFI_BUFFER_OUTPUT; + bd->buffer_size = buf->size; + bd->num_buffers = 1; + bd->device_addr = buf->dma_addr; + bd->response_required = response; +} + +static void return_buf_error(struct venus_inst *inst, + struct vb2_v4l2_buffer *vbuf) +{ + struct v4l2_m2m_ctx *m2m_ctx = inst->m2m_ctx; + + if (vbuf->vb2_buf.type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + v4l2_m2m_src_buf_remove_by_buf(m2m_ctx, vbuf); + else + v4l2_m2m_src_buf_remove_by_buf(m2m_ctx, vbuf); + + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); +} + +static int +session_process_buf(struct venus_inst *inst, struct vb2_v4l2_buffer *vbuf) +{ + struct venus_buffer *buf = to_venus_buffer(vbuf); + struct vb2_buffer *vb = &vbuf->vb2_buf; + unsigned int type = vb->type; + struct hfi_frame_data fdata; + int ret; + + memset(&fdata, 0, sizeof(fdata)); + fdata.alloc_len = buf->size; + fdata.device_addr = buf->dma_addr; + fdata.timestamp = vb->timestamp; + do_div(fdata.timestamp, NSEC_PER_USEC); + fdata.flags = 0; + fdata.clnt_data = vbuf->vb2_buf.index; + + if (!fdata.timestamp) + fdata.flags |= HFI_BUFFERFLAG_TIMESTAMPINVALID; + + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + fdata.buffer_type = HFI_BUFFER_INPUT; + fdata.filled_len = vb2_get_plane_payload(vb, 0); + fdata.offset = vb->planes[0].data_offset; + + if (vbuf->flags & V4L2_BUF_FLAG_LAST || !fdata.filled_len) + fdata.flags |= HFI_BUFFERFLAG_EOS; + } else if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + fdata.buffer_type = HFI_BUFFER_OUTPUT; + fdata.filled_len = 0; + fdata.offset = 0; + } + + ret = hfi_session_process_buf(inst, &fdata); + if (ret) + return ret; + + return 0; +} + +static inline int is_reg_unreg_needed(struct venus_inst *inst) +{ + if (inst->session_type == VIDC_SESSION_TYPE_DEC && + inst->core->res->hfi_version == HFI_VERSION_3XX) + return 0; + + if (inst->session_type == VIDC_SESSION_TYPE_DEC && + inst->cap_bufs_mode_dynamic && + inst->core->res->hfi_version == HFI_VERSION_1XX) + return 0; + + return 1; +} + +static int session_unregister_bufs(struct venus_inst *inst) +{ + struct venus_buffer *buf, *n; + struct hfi_buffer_desc bd; + int ret = 0; + + if (!is_reg_unreg_needed(inst)) + return 0; + + list_for_each_entry_safe(buf, n, &inst->registeredbufs, reg_list) { + fill_buffer_desc(buf, &bd, true); + ret = hfi_session_unset_buffers(inst, &bd); + list_del_init(&buf->reg_list); + } + + return ret; +} + +static int session_register_bufs(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + struct device *dev = core->dev; + struct hfi_buffer_desc bd; + struct venus_buffer *buf; + int ret = 0; + + if (!is_reg_unreg_needed(inst)) + return 0; + + list_for_each_entry(buf, &inst->registeredbufs, reg_list) { + fill_buffer_desc(buf, &bd, false); + ret = hfi_session_set_buffers(inst, &bd); + if (ret) { + dev_err(dev, "%s: set buffer failed\n", __func__); + break; + } + } + + return ret; +} + +int venus_helper_get_bufreq(struct venus_inst *inst, u32 type, + struct hfi_buffer_requirements *req) +{ + u32 ptype = HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS; + union hfi_get_property hprop; + unsigned int i; + int ret; + + if (req) + memset(req, 0, sizeof(*req)); + + ret = hfi_session_get_property(inst, ptype, &hprop); + if (ret) + return ret; + + ret = -EINVAL; + + for (i = 0; i < HFI_BUFFER_TYPE_MAX; i++) { + if (hprop.bufreq[i].type != type) + continue; + + if (req) + memcpy(req, &hprop.bufreq[i], sizeof(*req)); + ret = 0; + break; + } + + return ret; +} +EXPORT_SYMBOL_GPL(venus_helper_get_bufreq); + +int venus_helper_set_input_resolution(struct venus_inst *inst, + unsigned int width, unsigned int height) +{ + u32 ptype = HFI_PROPERTY_PARAM_FRAME_SIZE; + struct hfi_framesize fs; + + fs.buffer_type = HFI_BUFFER_INPUT; + fs.width = width; + fs.height = height; + + return hfi_session_set_property(inst, ptype, &fs); +} +EXPORT_SYMBOL_GPL(venus_helper_set_input_resolution); + +int venus_helper_set_output_resolution(struct venus_inst *inst, + unsigned int width, unsigned int height) +{ + u32 ptype = HFI_PROPERTY_PARAM_FRAME_SIZE; + struct hfi_framesize fs; + + fs.buffer_type = HFI_BUFFER_OUTPUT; + fs.width = width; + fs.height = height; + + return hfi_session_set_property(inst, ptype, &fs); +} +EXPORT_SYMBOL_GPL(venus_helper_set_output_resolution); + +int venus_helper_set_num_bufs(struct venus_inst *inst, unsigned int input_bufs, + unsigned int output_bufs) +{ + u32 ptype = HFI_PROPERTY_PARAM_BUFFER_COUNT_ACTUAL; + struct hfi_buffer_count_actual buf_count; + int ret; + + buf_count.type = HFI_BUFFER_INPUT; + buf_count.count_actual = input_bufs; + + ret = hfi_session_set_property(inst, ptype, &buf_count); + if (ret) + return ret; + + buf_count.type = HFI_BUFFER_OUTPUT; + buf_count.count_actual = output_bufs; + + return hfi_session_set_property(inst, ptype, &buf_count); +} +EXPORT_SYMBOL_GPL(venus_helper_set_num_bufs); + +int venus_helper_set_color_format(struct venus_inst *inst, u32 pixfmt) +{ + struct hfi_uncompressed_format_select fmt; + u32 ptype = HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SELECT; + int ret; + + if (inst->session_type == VIDC_SESSION_TYPE_DEC) + fmt.buffer_type = HFI_BUFFER_OUTPUT; + else if (inst->session_type == VIDC_SESSION_TYPE_ENC) + fmt.buffer_type = HFI_BUFFER_INPUT; + else + return -EINVAL; + + switch (pixfmt) { + case V4L2_PIX_FMT_NV12: + fmt.format = HFI_COLOR_FORMAT_NV12; + break; + case V4L2_PIX_FMT_NV21: + fmt.format = HFI_COLOR_FORMAT_NV21; + break; + default: + return -EINVAL; + } + + ret = hfi_session_set_property(inst, ptype, &fmt); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(venus_helper_set_color_format); + +static void delayed_process_buf_func(struct work_struct *work) +{ + struct venus_buffer *buf, *n; + struct venus_inst *inst; + int ret; + + inst = container_of(work, struct venus_inst, delayed_process_work); + + mutex_lock(&inst->lock); + + if (!(inst->streamon_out & inst->streamon_cap)) + goto unlock; + + list_for_each_entry_safe(buf, n, &inst->delayed_process, ref_list) { + if (buf->flags & HFI_BUFFERFLAG_READONLY) + continue; + + ret = session_process_buf(inst, &buf->vb); + if (ret) + return_buf_error(inst, &buf->vb); + + list_del_init(&buf->ref_list); + } +unlock: + mutex_unlock(&inst->lock); +} + +void venus_helper_release_buf_ref(struct venus_inst *inst, unsigned int idx) +{ + struct venus_buffer *buf; + + list_for_each_entry(buf, &inst->registeredbufs, reg_list) { + if (buf->vb.vb2_buf.index == idx) { + buf->flags &= ~HFI_BUFFERFLAG_READONLY; + schedule_work(&inst->delayed_process_work); + break; + } + } +} +EXPORT_SYMBOL_GPL(venus_helper_release_buf_ref); + +void venus_helper_acquire_buf_ref(struct vb2_v4l2_buffer *vbuf) +{ + struct venus_buffer *buf = to_venus_buffer(vbuf); + + buf->flags |= HFI_BUFFERFLAG_READONLY; +} +EXPORT_SYMBOL_GPL(venus_helper_acquire_buf_ref); + +static int is_buf_refed(struct venus_inst *inst, struct vb2_v4l2_buffer *vbuf) +{ + struct venus_buffer *buf = to_venus_buffer(vbuf); + + if (buf->flags & HFI_BUFFERFLAG_READONLY) { + list_add_tail(&buf->ref_list, &inst->delayed_process); + schedule_work(&inst->delayed_process_work); + return 1; + } + + return 0; +} + +struct vb2_v4l2_buffer * +venus_helper_find_buf(struct venus_inst *inst, unsigned int type, u32 idx) +{ + struct v4l2_m2m_ctx *m2m_ctx = inst->m2m_ctx; + + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return v4l2_m2m_src_buf_remove_by_idx(m2m_ctx, idx); + else + return v4l2_m2m_dst_buf_remove_by_idx(m2m_ctx, idx); +} +EXPORT_SYMBOL_GPL(venus_helper_find_buf); + +int venus_helper_vb2_buf_init(struct vb2_buffer *vb) +{ + struct venus_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct venus_buffer *buf = to_venus_buffer(vbuf); + struct sg_table *sgt; + + sgt = vb2_dma_sg_plane_desc(vb, 0); + if (!sgt) + return -EFAULT; + + buf->size = vb2_plane_size(vb, 0); + buf->dma_addr = sg_dma_address(sgt->sgl); + + if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + list_add_tail(&buf->reg_list, &inst->registeredbufs); + + return 0; +} +EXPORT_SYMBOL_GPL(venus_helper_vb2_buf_init); + +int venus_helper_vb2_buf_prepare(struct vb2_buffer *vb) +{ + struct venus_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + + if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + vb2_plane_size(vb, 0) < inst->output_buf_size) + return -EINVAL; + if (vb->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + vb2_plane_size(vb, 0) < inst->input_buf_size) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_GPL(venus_helper_vb2_buf_prepare); + +void venus_helper_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct venus_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + struct v4l2_m2m_ctx *m2m_ctx = inst->m2m_ctx; + int ret; + + mutex_lock(&inst->lock); + + if (inst->cmd_stop) { + vbuf->flags |= V4L2_BUF_FLAG_LAST; + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE); + inst->cmd_stop = false; + goto unlock; + } + + v4l2_m2m_buf_queue(m2m_ctx, vbuf); + + if (!(inst->streamon_out & inst->streamon_cap)) + goto unlock; + + ret = is_buf_refed(inst, vbuf); + if (ret) + goto unlock; + + ret = session_process_buf(inst, vbuf); + if (ret) + return_buf_error(inst, vbuf); + +unlock: + mutex_unlock(&inst->lock); +} +EXPORT_SYMBOL_GPL(venus_helper_vb2_buf_queue); + +void venus_helper_buffers_done(struct venus_inst *inst, + enum vb2_buffer_state state) +{ + struct vb2_v4l2_buffer *buf; + + while ((buf = v4l2_m2m_src_buf_remove(inst->m2m_ctx))) + v4l2_m2m_buf_done(buf, state); + while ((buf = v4l2_m2m_dst_buf_remove(inst->m2m_ctx))) + v4l2_m2m_buf_done(buf, state); +} +EXPORT_SYMBOL_GPL(venus_helper_buffers_done); + +void venus_helper_vb2_stop_streaming(struct vb2_queue *q) +{ + struct venus_inst *inst = vb2_get_drv_priv(q); + struct venus_core *core = inst->core; + int ret; + + mutex_lock(&inst->lock); + + if (inst->streamon_out & inst->streamon_cap) { + ret = hfi_session_stop(inst); + ret |= hfi_session_unload_res(inst); + ret |= session_unregister_bufs(inst); + ret |= intbufs_free(inst); + ret |= hfi_session_deinit(inst); + + if (inst->session_error || core->sys_error) + ret = -EIO; + + if (ret) + hfi_session_abort(inst); + + load_scale_clocks(core); + } + + venus_helper_buffers_done(inst, VB2_BUF_STATE_ERROR); + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + inst->streamon_out = 0; + else + inst->streamon_cap = 0; + + mutex_unlock(&inst->lock); +} +EXPORT_SYMBOL_GPL(venus_helper_vb2_stop_streaming); + +int venus_helper_vb2_start_streaming(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + int ret; + + ret = intbufs_alloc(inst); + if (ret) + return ret; + + ret = session_register_bufs(inst); + if (ret) + goto err_bufs_free; + + load_scale_clocks(core); + + ret = hfi_session_load_res(inst); + if (ret) + goto err_unreg_bufs; + + ret = hfi_session_start(inst); + if (ret) + goto err_unload_res; + + return 0; + +err_unload_res: + hfi_session_unload_res(inst); +err_unreg_bufs: + session_unregister_bufs(inst); +err_bufs_free: + intbufs_free(inst); + return ret; +} +EXPORT_SYMBOL_GPL(venus_helper_vb2_start_streaming); + +void venus_helper_m2m_device_run(void *priv) +{ + struct venus_inst *inst = priv; + struct v4l2_m2m_ctx *m2m_ctx = inst->m2m_ctx; + struct v4l2_m2m_buffer *buf, *n; + int ret; + + mutex_lock(&inst->lock); + + v4l2_m2m_for_each_dst_buf_safe(m2m_ctx, buf, n) { + ret = session_process_buf(inst, &buf->vb); + if (ret) + return_buf_error(inst, &buf->vb); + } + + v4l2_m2m_for_each_src_buf_safe(m2m_ctx, buf, n) { + ret = session_process_buf(inst, &buf->vb); + if (ret) + return_buf_error(inst, &buf->vb); + } + + mutex_unlock(&inst->lock); +} +EXPORT_SYMBOL_GPL(venus_helper_m2m_device_run); + +void venus_helper_m2m_job_abort(void *priv) +{ + struct venus_inst *inst = priv; + + v4l2_m2m_job_finish(inst->m2m_dev, inst->m2m_ctx); +} +EXPORT_SYMBOL_GPL(venus_helper_m2m_job_abort); + +void venus_helper_init_instance(struct venus_inst *inst) +{ + if (inst->session_type == VIDC_SESSION_TYPE_DEC) { + INIT_LIST_HEAD(&inst->delayed_process); + INIT_WORK(&inst->delayed_process_work, + delayed_process_buf_func); + } +} +EXPORT_SYMBOL_GPL(venus_helper_init_instance); diff --git a/drivers/media/platform/qcom/venus/helpers.h b/drivers/media/platform/qcom/venus/helpers.h new file mode 100644 index 000000000000..6a061b417a93 --- /dev/null +++ b/drivers/media/platform/qcom/venus/helpers.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __VENUS_HELPERS_H__ +#define __VENUS_HELPERS_H__ + +#include <media/videobuf2-v4l2.h> + +struct venus_inst; + +struct vb2_v4l2_buffer *venus_helper_find_buf(struct venus_inst *inst, + unsigned int type, u32 idx); +void venus_helper_buffers_done(struct venus_inst *inst, + enum vb2_buffer_state state); +int venus_helper_vb2_buf_init(struct vb2_buffer *vb); +int venus_helper_vb2_buf_prepare(struct vb2_buffer *vb); +void venus_helper_vb2_buf_queue(struct vb2_buffer *vb); +void venus_helper_vb2_stop_streaming(struct vb2_queue *q); +int venus_helper_vb2_start_streaming(struct venus_inst *inst); +void venus_helper_m2m_device_run(void *priv); +void venus_helper_m2m_job_abort(void *priv); +int venus_helper_get_bufreq(struct venus_inst *inst, u32 type, + struct hfi_buffer_requirements *req); +int venus_helper_set_input_resolution(struct venus_inst *inst, + unsigned int width, unsigned int height); +int venus_helper_set_output_resolution(struct venus_inst *inst, + unsigned int width, unsigned int height); +int venus_helper_set_num_bufs(struct venus_inst *inst, unsigned int input_bufs, + unsigned int output_bufs); +int venus_helper_set_color_format(struct venus_inst *inst, u32 fmt); +void venus_helper_acquire_buf_ref(struct vb2_v4l2_buffer *vbuf); +void venus_helper_release_buf_ref(struct venus_inst *inst, unsigned int idx); +void venus_helper_init_instance(struct venus_inst *inst); +#endif diff --git a/drivers/media/platform/qcom/venus/hfi.c b/drivers/media/platform/qcom/venus/hfi.c new file mode 100644 index 000000000000..c09490876516 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi.c @@ -0,0 +1,522 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/list.h> +#include <linux/completion.h> +#include <linux/platform_device.h> +#include <linux/videodev2.h> + +#include "core.h" +#include "hfi.h" +#include "hfi_cmds.h" +#include "hfi_venus.h" + +#define TIMEOUT msecs_to_jiffies(1000) + +static u32 to_codec_type(u32 pixfmt) +{ + switch (pixfmt) { + case V4L2_PIX_FMT_H264: + case V4L2_PIX_FMT_H264_NO_SC: + return HFI_VIDEO_CODEC_H264; + case V4L2_PIX_FMT_H263: + return HFI_VIDEO_CODEC_H263; + case V4L2_PIX_FMT_MPEG1: + return HFI_VIDEO_CODEC_MPEG1; + case V4L2_PIX_FMT_MPEG2: + return HFI_VIDEO_CODEC_MPEG2; + case V4L2_PIX_FMT_MPEG4: + return HFI_VIDEO_CODEC_MPEG4; + case V4L2_PIX_FMT_VC1_ANNEX_G: + case V4L2_PIX_FMT_VC1_ANNEX_L: + return HFI_VIDEO_CODEC_VC1; + case V4L2_PIX_FMT_VP8: + return HFI_VIDEO_CODEC_VP8; + case V4L2_PIX_FMT_VP9: + return HFI_VIDEO_CODEC_VP9; + case V4L2_PIX_FMT_XVID: + return HFI_VIDEO_CODEC_DIVX; + default: + return 0; + } +} + +int hfi_core_init(struct venus_core *core) +{ + int ret = 0; + + mutex_lock(&core->lock); + + if (core->state >= CORE_INIT) + goto unlock; + + reinit_completion(&core->done); + + ret = core->ops->core_init(core); + if (ret) + goto unlock; + + ret = wait_for_completion_timeout(&core->done, TIMEOUT); + if (!ret) { + ret = -ETIMEDOUT; + goto unlock; + } + + ret = 0; + + if (core->error != HFI_ERR_NONE) { + ret = -EIO; + goto unlock; + } + + core->state = CORE_INIT; +unlock: + mutex_unlock(&core->lock); + return ret; +} + +static int core_deinit_wait_atomic_t(atomic_t *p) +{ + schedule(); + return 0; +} + +int hfi_core_deinit(struct venus_core *core, bool blocking) +{ + int ret = 0, empty; + + mutex_lock(&core->lock); + + if (core->state == CORE_UNINIT) + goto unlock; + + empty = list_empty(&core->instances); + + if (!empty && !blocking) { + ret = -EBUSY; + goto unlock; + } + + if (!empty) { + mutex_unlock(&core->lock); + wait_on_atomic_t(&core->insts_count, core_deinit_wait_atomic_t, + TASK_UNINTERRUPTIBLE); + mutex_lock(&core->lock); + } + + ret = core->ops->core_deinit(core); + + if (!ret) + core->state = CORE_UNINIT; + +unlock: + mutex_unlock(&core->lock); + return ret; +} + +int hfi_core_suspend(struct venus_core *core) +{ + if (core->state != CORE_INIT) + return 0; + + return core->ops->suspend(core); +} + +int hfi_core_resume(struct venus_core *core, bool force) +{ + if (!force && core->state != CORE_INIT) + return 0; + + return core->ops->resume(core); +} + +int hfi_core_trigger_ssr(struct venus_core *core, u32 type) +{ + return core->ops->core_trigger_ssr(core, type); +} + +int hfi_core_ping(struct venus_core *core) +{ + int ret; + + mutex_lock(&core->lock); + + ret = core->ops->core_ping(core, 0xbeef); + if (ret) + goto unlock; + + ret = wait_for_completion_timeout(&core->done, TIMEOUT); + if (!ret) { + ret = -ETIMEDOUT; + goto unlock; + } + ret = 0; + if (core->error != HFI_ERR_NONE) + ret = -ENODEV; +unlock: + mutex_unlock(&core->lock); + return ret; +} + +static int wait_session_msg(struct venus_inst *inst) +{ + int ret; + + ret = wait_for_completion_timeout(&inst->done, TIMEOUT); + if (!ret) + return -ETIMEDOUT; + + if (inst->error != HFI_ERR_NONE) + return -EIO; + + return 0; +} + +int hfi_session_create(struct venus_inst *inst, const struct hfi_inst_ops *ops) +{ + struct venus_core *core = inst->core; + + if (!ops) + return -EINVAL; + + inst->state = INST_UNINIT; + init_completion(&inst->done); + inst->ops = ops; + + mutex_lock(&core->lock); + list_add_tail(&inst->list, &core->instances); + atomic_inc(&core->insts_count); + mutex_unlock(&core->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(hfi_session_create); + +int hfi_session_init(struct venus_inst *inst, u32 pixfmt) +{ + struct venus_core *core = inst->core; + const struct hfi_ops *ops = core->ops; + u32 codec; + int ret; + + codec = to_codec_type(pixfmt); + reinit_completion(&inst->done); + + ret = ops->session_init(inst, inst->session_type, codec); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + inst->state = INST_INIT; + + return 0; +} +EXPORT_SYMBOL_GPL(hfi_session_init); + +void hfi_session_destroy(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + + mutex_lock(&core->lock); + list_del_init(&inst->list); + atomic_dec(&core->insts_count); + wake_up_atomic_t(&core->insts_count); + mutex_unlock(&core->lock); +} +EXPORT_SYMBOL_GPL(hfi_session_destroy); + +int hfi_session_deinit(struct venus_inst *inst) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + if (inst->state == INST_UNINIT) + return 0; + + if (inst->state < INST_INIT) + return -EINVAL; + + reinit_completion(&inst->done); + + ret = ops->session_end(inst); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + inst->state = INST_UNINIT; + + return 0; +} +EXPORT_SYMBOL_GPL(hfi_session_deinit); + +int hfi_session_start(struct venus_inst *inst) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + if (inst->state != INST_LOAD_RESOURCES) + return -EINVAL; + + reinit_completion(&inst->done); + + ret = ops->session_start(inst); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + inst->state = INST_START; + + return 0; +} + +int hfi_session_stop(struct venus_inst *inst) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + if (inst->state != INST_START) + return -EINVAL; + + reinit_completion(&inst->done); + + ret = ops->session_stop(inst); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + inst->state = INST_STOP; + + return 0; +} + +int hfi_session_continue(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + + if (core->res->hfi_version != HFI_VERSION_3XX) + return 0; + + return core->ops->session_continue(inst); +} +EXPORT_SYMBOL_GPL(hfi_session_continue); + +int hfi_session_abort(struct venus_inst *inst) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + reinit_completion(&inst->done); + + ret = ops->session_abort(inst); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + return 0; +} + +int hfi_session_load_res(struct venus_inst *inst) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + if (inst->state != INST_INIT) + return -EINVAL; + + reinit_completion(&inst->done); + + ret = ops->session_load_res(inst); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + inst->state = INST_LOAD_RESOURCES; + + return 0; +} + +int hfi_session_unload_res(struct venus_inst *inst) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + if (inst->state != INST_STOP) + return -EINVAL; + + reinit_completion(&inst->done); + + ret = ops->session_release_res(inst); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + inst->state = INST_RELEASE_RESOURCES; + + return 0; +} + +int hfi_session_flush(struct venus_inst *inst) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + reinit_completion(&inst->done); + + ret = ops->session_flush(inst, HFI_FLUSH_ALL); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(hfi_session_flush); + +int hfi_session_set_buffers(struct venus_inst *inst, struct hfi_buffer_desc *bd) +{ + const struct hfi_ops *ops = inst->core->ops; + + return ops->session_set_buffers(inst, bd); +} + +int hfi_session_unset_buffers(struct venus_inst *inst, + struct hfi_buffer_desc *bd) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + reinit_completion(&inst->done); + + ret = ops->session_unset_buffers(inst, bd); + if (ret) + return ret; + + if (!bd->response_required) + return 0; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + return 0; +} + +int hfi_session_get_property(struct venus_inst *inst, u32 ptype, + union hfi_get_property *hprop) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + if (inst->state < INST_INIT || inst->state >= INST_STOP) + return -EINVAL; + + reinit_completion(&inst->done); + + ret = ops->session_get_property(inst, ptype); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + *hprop = inst->hprop; + + return 0; +} +EXPORT_SYMBOL_GPL(hfi_session_get_property); + +int hfi_session_set_property(struct venus_inst *inst, u32 ptype, void *pdata) +{ + const struct hfi_ops *ops = inst->core->ops; + + if (inst->state < INST_INIT || inst->state >= INST_STOP) + return -EINVAL; + + return ops->session_set_property(inst, ptype, pdata); +} +EXPORT_SYMBOL_GPL(hfi_session_set_property); + +int hfi_session_process_buf(struct venus_inst *inst, struct hfi_frame_data *fd) +{ + const struct hfi_ops *ops = inst->core->ops; + + if (fd->buffer_type == HFI_BUFFER_INPUT) + return ops->session_etb(inst, fd); + else if (fd->buffer_type == HFI_BUFFER_OUTPUT) + return ops->session_ftb(inst, fd); + + return -EINVAL; +} + +irqreturn_t hfi_isr_thread(int irq, void *dev_id) +{ + struct venus_core *core = dev_id; + + return core->ops->isr_thread(core); +} + +irqreturn_t hfi_isr(int irq, void *dev) +{ + struct venus_core *core = dev; + + return core->ops->isr(core); +} + +int hfi_create(struct venus_core *core, const struct hfi_core_ops *ops) +{ + int ret; + + if (!ops) + return -EINVAL; + + atomic_set(&core->insts_count, 0); + core->core_ops = ops; + core->state = CORE_UNINIT; + init_completion(&core->done); + pkt_set_version(core->res->hfi_version); + ret = venus_hfi_create(core); + + return ret; +} + +void hfi_destroy(struct venus_core *core) +{ + venus_hfi_destroy(core); +} diff --git a/drivers/media/platform/qcom/venus/hfi.h b/drivers/media/platform/qcom/venus/hfi.h new file mode 100644 index 000000000000..5466b7d60dd0 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi.h @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __HFI_H__ +#define __HFI_H__ + +#include <linux/interrupt.h> + +#include "hfi_helper.h" + +#define VIDC_SESSION_TYPE_VPE 0 +#define VIDC_SESSION_TYPE_ENC 1 +#define VIDC_SESSION_TYPE_DEC 2 + +#define VIDC_RESOURCE_NONE 0 +#define VIDC_RESOURCE_OCMEM 1 +#define VIDC_RESOURCE_VMEM 2 + +struct hfi_buffer_desc { + u32 buffer_type; + u32 buffer_size; + u32 num_buffers; + u32 device_addr; + u32 extradata_addr; + u32 extradata_size; + u32 response_required; +}; + +struct hfi_frame_data { + u32 buffer_type; + u32 device_addr; + u32 extradata_addr; + u64 timestamp; + u32 flags; + u32 offset; + u32 alloc_len; + u32 filled_len; + u32 mark_target; + u32 mark_data; + u32 clnt_data; + u32 extradata_size; +}; + +union hfi_get_property { + struct hfi_profile_level profile_level; + struct hfi_buffer_requirements bufreq[HFI_BUFFER_TYPE_MAX]; +}; + +/* HFI events */ +#define EVT_SYS_EVENT_CHANGE 1 +#define EVT_SYS_WATCHDOG_TIMEOUT 2 +#define EVT_SYS_ERROR 3 +#define EVT_SESSION_ERROR 4 + +/* HFI event callback structure */ +struct hfi_event_data { + u32 error; + u32 height; + u32 width; + u32 event_type; + u32 packet_buffer; + u32 extradata_buffer; + u32 tag; + u32 profile; + u32 level; +}; + +/* define core states */ +#define CORE_UNINIT 0 +#define CORE_INIT 1 + +/* define instance states */ +#define INST_UNINIT 2 +#define INST_INIT 3 +#define INST_LOAD_RESOURCES 4 +#define INST_START 5 +#define INST_STOP 6 +#define INST_RELEASE_RESOURCES 7 + +struct venus_core; +struct venus_inst; + +struct hfi_core_ops { + void (*event_notify)(struct venus_core *core, u32 event); +}; + +struct hfi_inst_ops { + void (*buf_done)(struct venus_inst *inst, unsigned int buf_type, + u32 tag, u32 bytesused, u32 data_offset, u32 flags, + u32 hfi_flags, u64 timestamp_us); + void (*event_notify)(struct venus_inst *inst, u32 event, + struct hfi_event_data *data); +}; + +struct hfi_ops { + int (*core_init)(struct venus_core *core); + int (*core_deinit)(struct venus_core *core); + int (*core_ping)(struct venus_core *core, u32 cookie); + int (*core_trigger_ssr)(struct venus_core *core, u32 trigger_type); + + int (*session_init)(struct venus_inst *inst, u32 session_type, + u32 codec); + int (*session_end)(struct venus_inst *inst); + int (*session_abort)(struct venus_inst *inst); + int (*session_flush)(struct venus_inst *inst, u32 flush_mode); + int (*session_start)(struct venus_inst *inst); + int (*session_stop)(struct venus_inst *inst); + int (*session_continue)(struct venus_inst *inst); + int (*session_etb)(struct venus_inst *inst, struct hfi_frame_data *fd); + int (*session_ftb)(struct venus_inst *inst, struct hfi_frame_data *fd); + int (*session_set_buffers)(struct venus_inst *inst, + struct hfi_buffer_desc *bd); + int (*session_unset_buffers)(struct venus_inst *inst, + struct hfi_buffer_desc *bd); + int (*session_load_res)(struct venus_inst *inst); + int (*session_release_res)(struct venus_inst *inst); + int (*session_parse_seq_hdr)(struct venus_inst *inst, u32 seq_hdr, + u32 seq_hdr_len); + int (*session_get_seq_hdr)(struct venus_inst *inst, u32 seq_hdr, + u32 seq_hdr_len); + int (*session_set_property)(struct venus_inst *inst, u32 ptype, + void *pdata); + int (*session_get_property)(struct venus_inst *inst, u32 ptype); + + int (*resume)(struct venus_core *core); + int (*suspend)(struct venus_core *core); + + /* interrupt operations */ + irqreturn_t (*isr)(struct venus_core *core); + irqreturn_t (*isr_thread)(struct venus_core *core); +}; + +int hfi_create(struct venus_core *core, const struct hfi_core_ops *ops); +void hfi_destroy(struct venus_core *core); + +int hfi_core_init(struct venus_core *core); +int hfi_core_deinit(struct venus_core *core, bool blocking); +int hfi_core_suspend(struct venus_core *core); +int hfi_core_resume(struct venus_core *core, bool force); +int hfi_core_trigger_ssr(struct venus_core *core, u32 type); +int hfi_core_ping(struct venus_core *core); +int hfi_session_create(struct venus_inst *inst, const struct hfi_inst_ops *ops); +void hfi_session_destroy(struct venus_inst *inst); +int hfi_session_init(struct venus_inst *inst, u32 pixfmt); +int hfi_session_deinit(struct venus_inst *inst); +int hfi_session_start(struct venus_inst *inst); +int hfi_session_stop(struct venus_inst *inst); +int hfi_session_continue(struct venus_inst *inst); +int hfi_session_abort(struct venus_inst *inst); +int hfi_session_load_res(struct venus_inst *inst); +int hfi_session_unload_res(struct venus_inst *inst); +int hfi_session_flush(struct venus_inst *inst); +int hfi_session_set_buffers(struct venus_inst *inst, + struct hfi_buffer_desc *bd); +int hfi_session_unset_buffers(struct venus_inst *inst, + struct hfi_buffer_desc *bd); +int hfi_session_get_property(struct venus_inst *inst, u32 ptype, + union hfi_get_property *hprop); +int hfi_session_set_property(struct venus_inst *inst, u32 ptype, void *pdata); +int hfi_session_process_buf(struct venus_inst *inst, struct hfi_frame_data *f); +irqreturn_t hfi_isr_thread(int irq, void *dev_id); +irqreturn_t hfi_isr(int irq, void *dev); + +#endif diff --git a/drivers/media/platform/qcom/venus/hfi_cmds.c b/drivers/media/platform/qcom/venus/hfi_cmds.c new file mode 100644 index 000000000000..b83c5b8ddccb --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_cmds.c @@ -0,0 +1,1259 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/errno.h> +#include <linux/hash.h> + +#include "hfi_cmds.h" + +static enum hfi_version hfi_ver; + +void pkt_sys_init(struct hfi_sys_init_pkt *pkt, u32 arch_type) +{ + pkt->hdr.size = sizeof(*pkt); + pkt->hdr.pkt_type = HFI_CMD_SYS_INIT; + pkt->arch_type = arch_type; +} + +void pkt_sys_pc_prep(struct hfi_sys_pc_prep_pkt *pkt) +{ + pkt->hdr.size = sizeof(*pkt); + pkt->hdr.pkt_type = HFI_CMD_SYS_PC_PREP; +} + +void pkt_sys_idle_indicator(struct hfi_sys_set_property_pkt *pkt, u32 enable) +{ + struct hfi_enable *hfi = (struct hfi_enable *)&pkt->data[1]; + + pkt->hdr.size = sizeof(*pkt) + sizeof(*hfi) + sizeof(u32); + pkt->hdr.pkt_type = HFI_CMD_SYS_SET_PROPERTY; + pkt->num_properties = 1; + pkt->data[0] = HFI_PROPERTY_SYS_IDLE_INDICATOR; + hfi->enable = enable; +} + +void pkt_sys_debug_config(struct hfi_sys_set_property_pkt *pkt, u32 mode, + u32 config) +{ + struct hfi_debug_config *hfi; + + pkt->hdr.size = sizeof(*pkt) + sizeof(*hfi) + sizeof(u32); + pkt->hdr.pkt_type = HFI_CMD_SYS_SET_PROPERTY; + pkt->num_properties = 1; + pkt->data[0] = HFI_PROPERTY_SYS_DEBUG_CONFIG; + hfi = (struct hfi_debug_config *)&pkt->data[1]; + hfi->config = config; + hfi->mode = mode; +} + +void pkt_sys_coverage_config(struct hfi_sys_set_property_pkt *pkt, u32 mode) +{ + pkt->hdr.size = sizeof(*pkt) + sizeof(u32); + pkt->hdr.pkt_type = HFI_CMD_SYS_SET_PROPERTY; + pkt->num_properties = 1; + pkt->data[0] = HFI_PROPERTY_SYS_CONFIG_COVERAGE; + pkt->data[1] = mode; +} + +int pkt_sys_set_resource(struct hfi_sys_set_resource_pkt *pkt, u32 id, u32 size, + u32 addr, void *cookie) +{ + pkt->hdr.size = sizeof(*pkt); + pkt->hdr.pkt_type = HFI_CMD_SYS_SET_RESOURCE; + pkt->resource_handle = hash32_ptr(cookie); + + switch (id) { + case VIDC_RESOURCE_OCMEM: + case VIDC_RESOURCE_VMEM: { + struct hfi_resource_ocmem *res = + (struct hfi_resource_ocmem *)&pkt->resource_data[0]; + + res->size = size; + res->mem = addr; + pkt->resource_type = HFI_RESOURCE_OCMEM; + pkt->hdr.size += sizeof(*res) - sizeof(u32); + break; + } + case VIDC_RESOURCE_NONE: + default: + return -ENOTSUPP; + } + + return 0; +} + +int pkt_sys_unset_resource(struct hfi_sys_release_resource_pkt *pkt, u32 id, + u32 size, void *cookie) +{ + pkt->hdr.size = sizeof(*pkt); + pkt->hdr.pkt_type = HFI_CMD_SYS_RELEASE_RESOURCE; + pkt->resource_handle = hash32_ptr(cookie); + + switch (id) { + case VIDC_RESOURCE_OCMEM: + case VIDC_RESOURCE_VMEM: + pkt->resource_type = HFI_RESOURCE_OCMEM; + break; + case VIDC_RESOURCE_NONE: + break; + default: + return -ENOTSUPP; + } + + return 0; +} + +void pkt_sys_ping(struct hfi_sys_ping_pkt *pkt, u32 cookie) +{ + pkt->hdr.size = sizeof(*pkt); + pkt->hdr.pkt_type = HFI_CMD_SYS_PING; + pkt->client_data = cookie; +} + +void pkt_sys_power_control(struct hfi_sys_set_property_pkt *pkt, u32 enable) +{ + struct hfi_enable *hfi = (struct hfi_enable *)&pkt->data[1]; + + pkt->hdr.size = sizeof(*pkt) + sizeof(*hfi) + sizeof(u32); + pkt->hdr.pkt_type = HFI_CMD_SYS_SET_PROPERTY; + pkt->num_properties = 1; + pkt->data[0] = HFI_PROPERTY_SYS_CODEC_POWER_PLANE_CTRL; + hfi->enable = enable; +} + +int pkt_sys_ssr_cmd(struct hfi_sys_test_ssr_pkt *pkt, u32 trigger_type) +{ + switch (trigger_type) { + case HFI_TEST_SSR_SW_ERR_FATAL: + case HFI_TEST_SSR_SW_DIV_BY_ZERO: + case HFI_TEST_SSR_HW_WDOG_IRQ: + break; + default: + return -EINVAL; + } + + pkt->hdr.size = sizeof(*pkt); + pkt->hdr.pkt_type = HFI_CMD_SYS_TEST_SSR; + pkt->trigger_type = trigger_type; + + return 0; +} + +void pkt_sys_image_version(struct hfi_sys_get_property_pkt *pkt) +{ + pkt->hdr.size = sizeof(*pkt); + pkt->hdr.pkt_type = HFI_CMD_SYS_GET_PROPERTY; + pkt->num_properties = 1; + pkt->data[0] = HFI_PROPERTY_SYS_IMAGE_VERSION; +} + +int pkt_session_init(struct hfi_session_init_pkt *pkt, void *cookie, + u32 session_type, u32 codec) +{ + if (!pkt || !cookie || !codec) + return -EINVAL; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SYS_SESSION_INIT; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->session_domain = session_type; + pkt->session_codec = codec; + + return 0; +} + +void pkt_session_cmd(struct hfi_session_pkt *pkt, u32 pkt_type, void *cookie) +{ + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = pkt_type; + pkt->shdr.session_id = hash32_ptr(cookie); +} + +int pkt_session_set_buffers(struct hfi_session_set_buffers_pkt *pkt, + void *cookie, struct hfi_buffer_desc *bd) +{ + unsigned int i; + + if (!cookie || !pkt || !bd) + return -EINVAL; + + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_SET_BUFFERS; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->buffer_size = bd->buffer_size; + pkt->min_buffer_size = bd->buffer_size; + pkt->num_buffers = bd->num_buffers; + + if (bd->buffer_type == HFI_BUFFER_OUTPUT || + bd->buffer_type == HFI_BUFFER_OUTPUT2) { + struct hfi_buffer_info *bi; + + pkt->extradata_size = bd->extradata_size; + pkt->shdr.hdr.size = sizeof(*pkt) - sizeof(u32) + + (bd->num_buffers * sizeof(*bi)); + bi = (struct hfi_buffer_info *)pkt->buffer_info; + for (i = 0; i < pkt->num_buffers; i++) { + bi->buffer_addr = bd->device_addr; + bi->extradata_addr = bd->extradata_addr; + } + } else { + pkt->extradata_size = 0; + pkt->shdr.hdr.size = sizeof(*pkt) + + ((bd->num_buffers - 1) * sizeof(u32)); + for (i = 0; i < pkt->num_buffers; i++) + pkt->buffer_info[i] = bd->device_addr; + } + + pkt->buffer_type = bd->buffer_type; + + return 0; +} + +int pkt_session_unset_buffers(struct hfi_session_release_buffer_pkt *pkt, + void *cookie, struct hfi_buffer_desc *bd) +{ + unsigned int i; + + if (!cookie || !pkt || !bd) + return -EINVAL; + + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_RELEASE_BUFFERS; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->buffer_size = bd->buffer_size; + pkt->num_buffers = bd->num_buffers; + + if (bd->buffer_type == HFI_BUFFER_OUTPUT || + bd->buffer_type == HFI_BUFFER_OUTPUT2) { + struct hfi_buffer_info *bi; + + bi = (struct hfi_buffer_info *)pkt->buffer_info; + for (i = 0; i < pkt->num_buffers; i++) { + bi->buffer_addr = bd->device_addr; + bi->extradata_addr = bd->extradata_addr; + } + pkt->shdr.hdr.size = + sizeof(struct hfi_session_set_buffers_pkt) - + sizeof(u32) + (bd->num_buffers * sizeof(*bi)); + } else { + for (i = 0; i < pkt->num_buffers; i++) + pkt->buffer_info[i] = bd->device_addr; + + pkt->extradata_size = 0; + pkt->shdr.hdr.size = + sizeof(struct hfi_session_set_buffers_pkt) + + ((bd->num_buffers - 1) * sizeof(u32)); + } + + pkt->response_req = bd->response_required; + pkt->buffer_type = bd->buffer_type; + + return 0; +} + +int pkt_session_etb_decoder(struct hfi_session_empty_buffer_compressed_pkt *pkt, + void *cookie, struct hfi_frame_data *in_frame) +{ + if (!cookie || !in_frame->device_addr) + return -EINVAL; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_EMPTY_BUFFER; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->time_stamp_hi = upper_32_bits(in_frame->timestamp); + pkt->time_stamp_lo = lower_32_bits(in_frame->timestamp); + pkt->flags = in_frame->flags; + pkt->mark_target = in_frame->mark_target; + pkt->mark_data = in_frame->mark_data; + pkt->offset = in_frame->offset; + pkt->alloc_len = in_frame->alloc_len; + pkt->filled_len = in_frame->filled_len; + pkt->input_tag = in_frame->clnt_data; + pkt->packet_buffer = in_frame->device_addr; + + return 0; +} + +int pkt_session_etb_encoder( + struct hfi_session_empty_buffer_uncompressed_plane0_pkt *pkt, + void *cookie, struct hfi_frame_data *in_frame) +{ + if (!cookie || !in_frame->device_addr) + return -EINVAL; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_EMPTY_BUFFER; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->view_id = 0; + pkt->time_stamp_hi = upper_32_bits(in_frame->timestamp); + pkt->time_stamp_lo = lower_32_bits(in_frame->timestamp); + pkt->flags = in_frame->flags; + pkt->mark_target = in_frame->mark_target; + pkt->mark_data = in_frame->mark_data; + pkt->offset = in_frame->offset; + pkt->alloc_len = in_frame->alloc_len; + pkt->filled_len = in_frame->filled_len; + pkt->input_tag = in_frame->clnt_data; + pkt->packet_buffer = in_frame->device_addr; + pkt->extradata_buffer = in_frame->extradata_addr; + + return 0; +} + +int pkt_session_ftb(struct hfi_session_fill_buffer_pkt *pkt, void *cookie, + struct hfi_frame_data *out_frame) +{ + if (!cookie || !out_frame || !out_frame->device_addr) + return -EINVAL; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_FILL_BUFFER; + pkt->shdr.session_id = hash32_ptr(cookie); + + if (out_frame->buffer_type == HFI_BUFFER_OUTPUT) + pkt->stream_id = 0; + else if (out_frame->buffer_type == HFI_BUFFER_OUTPUT2) + pkt->stream_id = 1; + + pkt->output_tag = out_frame->clnt_data; + pkt->packet_buffer = out_frame->device_addr; + pkt->extradata_buffer = out_frame->extradata_addr; + pkt->alloc_len = out_frame->alloc_len; + pkt->filled_len = out_frame->filled_len; + pkt->offset = out_frame->offset; + pkt->data[0] = out_frame->extradata_size; + + return 0; +} + +int pkt_session_parse_seq_header( + struct hfi_session_parse_sequence_header_pkt *pkt, + void *cookie, u32 seq_hdr, u32 seq_hdr_len) +{ + if (!cookie || !seq_hdr || !seq_hdr_len) + return -EINVAL; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_PARSE_SEQUENCE_HEADER; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->header_len = seq_hdr_len; + pkt->packet_buffer = seq_hdr; + + return 0; +} + +int pkt_session_get_seq_hdr(struct hfi_session_get_sequence_header_pkt *pkt, + void *cookie, u32 seq_hdr, u32 seq_hdr_len) +{ + if (!cookie || !seq_hdr || !seq_hdr_len) + return -EINVAL; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_GET_SEQUENCE_HEADER; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->buffer_len = seq_hdr_len; + pkt->packet_buffer = seq_hdr; + + return 0; +} + +int pkt_session_flush(struct hfi_session_flush_pkt *pkt, void *cookie, u32 type) +{ + switch (type) { + case HFI_FLUSH_INPUT: + case HFI_FLUSH_OUTPUT: + case HFI_FLUSH_OUTPUT2: + case HFI_FLUSH_ALL: + break; + default: + return -EINVAL; + } + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_FLUSH; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->flush_type = type; + + return 0; +} + +static int pkt_session_get_property_1x(struct hfi_session_get_property_pkt *pkt, + void *cookie, u32 ptype) +{ + switch (ptype) { + case HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT: + case HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS: + break; + default: + return -EINVAL; + } + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_GET_PROPERTY; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->num_properties = 1; + pkt->data[0] = ptype; + + return 0; +} + +static int pkt_session_set_property_1x(struct hfi_session_set_property_pkt *pkt, + void *cookie, u32 ptype, void *pdata) +{ + void *prop_data; + int ret = 0; + + if (!pkt || !cookie || !pdata) + return -EINVAL; + + prop_data = &pkt->data[1]; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_SET_PROPERTY; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->num_properties = 1; + + switch (ptype) { + case HFI_PROPERTY_CONFIG_FRAME_RATE: { + struct hfi_framerate *in = pdata, *frate = prop_data; + + pkt->data[0] = HFI_PROPERTY_CONFIG_FRAME_RATE; + frate->buffer_type = in->buffer_type; + frate->framerate = in->framerate; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*frate); + break; + } + case HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SELECT: { + struct hfi_uncompressed_format_select *in = pdata; + struct hfi_uncompressed_format_select *hfi = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SELECT; + hfi->buffer_type = in->buffer_type; + hfi->format = in->format; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*hfi); + break; + } + case HFI_PROPERTY_PARAM_FRAME_SIZE: { + struct hfi_framesize *in = pdata, *fsize = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_FRAME_SIZE; + fsize->buffer_type = in->buffer_type; + fsize->height = in->height; + fsize->width = in->width; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*fsize); + break; + } + case HFI_PROPERTY_CONFIG_REALTIME: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = HFI_PROPERTY_CONFIG_REALTIME; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_PARAM_BUFFER_COUNT_ACTUAL: { + struct hfi_buffer_count_actual *in = pdata, *count = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_BUFFER_COUNT_ACTUAL; + count->count_actual = in->count_actual; + count->type = in->type; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*count); + break; + } + case HFI_PROPERTY_PARAM_BUFFER_SIZE_ACTUAL: { + struct hfi_buffer_size_actual *in = pdata, *sz = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_BUFFER_SIZE_ACTUAL; + sz->size = in->size; + sz->type = in->type; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*sz); + break; + } + case HFI_PROPERTY_PARAM_BUFFER_DISPLAY_HOLD_COUNT_ACTUAL: { + struct hfi_buffer_display_hold_count_actual *in = pdata; + struct hfi_buffer_display_hold_count_actual *count = prop_data; + + pkt->data[0] = + HFI_PROPERTY_PARAM_BUFFER_DISPLAY_HOLD_COUNT_ACTUAL; + count->hold_count = in->hold_count; + count->type = in->type; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*count); + break; + } + case HFI_PROPERTY_PARAM_NAL_STREAM_FORMAT_SELECT: { + struct hfi_nal_stream_format_select *in = pdata; + struct hfi_nal_stream_format_select *fmt = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_NAL_STREAM_FORMAT_SELECT; + fmt->format = in->format; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*fmt); + break; + } + case HFI_PROPERTY_PARAM_VDEC_OUTPUT_ORDER: { + u32 *in = pdata; + + switch (*in) { + case HFI_OUTPUT_ORDER_DECODE: + case HFI_OUTPUT_ORDER_DISPLAY: + break; + default: + ret = -EINVAL; + break; + } + + pkt->data[0] = HFI_PROPERTY_PARAM_VDEC_OUTPUT_ORDER; + pkt->data[1] = *in; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_PARAM_VDEC_PICTURE_TYPE_DECODE: { + struct hfi_enable_picture *in = pdata, *en = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VDEC_PICTURE_TYPE_DECODE; + en->picture_type = in->picture_type; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VDEC_OUTPUT2_KEEP_ASPECT_RATIO: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = + HFI_PROPERTY_PARAM_VDEC_OUTPUT2_KEEP_ASPECT_RATIO; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_CONFIG_VDEC_POST_LOOP_DEBLOCKER: { + struct hfi_enable *in = pdata; + struct hfi_enable *en = prop_data; + + pkt->data[0] = HFI_PROPERTY_CONFIG_VDEC_POST_LOOP_DEBLOCKER; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VDEC_MULTI_STREAM: { + struct hfi_multi_stream *in = pdata, *multi = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VDEC_MULTI_STREAM; + multi->buffer_type = in->buffer_type; + multi->enable = in->enable; + multi->width = in->width; + multi->height = in->height; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*multi); + break; + } + case HFI_PROPERTY_PARAM_VDEC_DISPLAY_PICTURE_BUFFER_COUNT: { + struct hfi_display_picture_buffer_count *in = pdata; + struct hfi_display_picture_buffer_count *count = prop_data; + + pkt->data[0] = + HFI_PROPERTY_PARAM_VDEC_DISPLAY_PICTURE_BUFFER_COUNT; + count->count = in->count; + count->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*count); + break; + } + case HFI_PROPERTY_PARAM_DIVX_FORMAT: { + u32 *in = pdata; + + switch (*in) { + case HFI_DIVX_FORMAT_4: + case HFI_DIVX_FORMAT_5: + case HFI_DIVX_FORMAT_6: + break; + default: + ret = -EINVAL; + break; + } + + pkt->data[0] = HFI_PROPERTY_PARAM_DIVX_FORMAT; + pkt->data[1] = *in; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_CONFIG_VDEC_MB_ERROR_MAP_REPORTING: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = HFI_PROPERTY_CONFIG_VDEC_MB_ERROR_MAP_REPORTING; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VDEC_CONTINUE_DATA_TRANSFER: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VDEC_CONTINUE_DATA_TRANSFER; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VDEC_THUMBNAIL_MODE: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VDEC_THUMBNAIL_MODE; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_CONFIG_VENC_SYNC_FRAME_SEQUENCE_HEADER: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = + HFI_PROPERTY_CONFIG_VENC_SYNC_FRAME_SEQUENCE_HEADER; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_CONFIG_VENC_REQUEST_SYNC_FRAME: + pkt->data[0] = HFI_PROPERTY_CONFIG_VENC_REQUEST_SYNC_FRAME; + pkt->shdr.hdr.size += sizeof(u32); + break; + case HFI_PROPERTY_PARAM_VENC_MPEG4_SHORT_HEADER: + break; + case HFI_PROPERTY_PARAM_VENC_MPEG4_AC_PREDICTION: + break; + case HFI_PROPERTY_CONFIG_VENC_TARGET_BITRATE: { + struct hfi_bitrate *in = pdata, *brate = prop_data; + + pkt->data[0] = HFI_PROPERTY_CONFIG_VENC_TARGET_BITRATE; + brate->bitrate = in->bitrate; + brate->layer_id = in->layer_id; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*brate); + break; + } + case HFI_PROPERTY_CONFIG_VENC_MAX_BITRATE: { + struct hfi_bitrate *in = pdata, *hfi = prop_data; + + pkt->data[0] = HFI_PROPERTY_CONFIG_VENC_MAX_BITRATE; + hfi->bitrate = in->bitrate; + hfi->layer_id = in->layer_id; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*hfi); + break; + } + case HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT: { + struct hfi_profile_level *in = pdata, *pl = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT; + pl->level = in->level; + pl->profile = in->profile; + if (pl->profile <= 0) + /* Profile not supported, falling back to high */ + pl->profile = HFI_H264_PROFILE_HIGH; + + if (!pl->level) + /* Level not supported, falling back to 1 */ + pl->level = 1; + + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*pl); + break; + } + case HFI_PROPERTY_PARAM_VENC_H264_ENTROPY_CONTROL: { + struct hfi_h264_entropy_control *in = pdata, *hfi = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_H264_ENTROPY_CONTROL; + hfi->entropy_mode = in->entropy_mode; + if (hfi->entropy_mode == HFI_H264_ENTROPY_CABAC) + hfi->cabac_model = in->cabac_model; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*hfi); + break; + } + case HFI_PROPERTY_PARAM_VENC_RATE_CONTROL: { + u32 *in = pdata; + + switch (*in) { + case HFI_RATE_CONTROL_OFF: + case HFI_RATE_CONTROL_CBR_CFR: + case HFI_RATE_CONTROL_CBR_VFR: + case HFI_RATE_CONTROL_VBR_CFR: + case HFI_RATE_CONTROL_VBR_VFR: + break; + default: + ret = -EINVAL; + break; + } + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_RATE_CONTROL; + pkt->data[1] = *in; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_PARAM_VENC_MPEG4_TIME_RESOLUTION: { + struct hfi_mpeg4_time_resolution *in = pdata, *res = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_MPEG4_TIME_RESOLUTION; + res->time_increment_resolution = in->time_increment_resolution; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*res); + break; + } + case HFI_PROPERTY_PARAM_VENC_MPEG4_HEADER_EXTENSION: { + struct hfi_mpeg4_header_extension *in = pdata, *ext = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_MPEG4_HEADER_EXTENSION; + ext->header_extension = in->header_extension; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*ext); + break; + } + case HFI_PROPERTY_PARAM_VENC_H264_DEBLOCK_CONTROL: { + struct hfi_h264_db_control *in = pdata, *db = prop_data; + + switch (in->mode) { + case HFI_H264_DB_MODE_DISABLE: + case HFI_H264_DB_MODE_SKIP_SLICE_BOUNDARY: + case HFI_H264_DB_MODE_ALL_BOUNDARY: + break; + default: + ret = -EINVAL; + break; + } + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_H264_DEBLOCK_CONTROL; + db->mode = in->mode; + db->slice_alpha_offset = in->slice_alpha_offset; + db->slice_beta_offset = in->slice_beta_offset; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*db); + break; + } + case HFI_PROPERTY_PARAM_VENC_SESSION_QP: { + struct hfi_quantization *in = pdata, *quant = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_SESSION_QP; + quant->qp_i = in->qp_i; + quant->qp_p = in->qp_p; + quant->qp_b = in->qp_b; + quant->layer_id = in->layer_id; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*quant); + break; + } + case HFI_PROPERTY_PARAM_VENC_SESSION_QP_RANGE: { + struct hfi_quantization_range *in = pdata, *range = prop_data; + u32 min_qp, max_qp; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_SESSION_QP_RANGE; + min_qp = in->min_qp; + max_qp = in->max_qp; + + /* We'll be packing in the qp, so make sure we + * won't be losing data when masking + */ + if (min_qp > 0xff || max_qp > 0xff) { + ret = -ERANGE; + break; + } + + /* When creating the packet, pack the qp value as + * 0xiippbb, where ii = qp range for I-frames, + * pp = qp range for P-frames, etc. + */ + range->min_qp = min_qp | min_qp << 8 | min_qp << 16; + range->max_qp = max_qp | max_qp << 8 | max_qp << 16; + range->layer_id = in->layer_id; + + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*range); + break; + } + case HFI_PROPERTY_PARAM_VENC_VC1_PERF_CFG: { + struct hfi_vc1e_perf_cfg_type *in = pdata, *perf = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_VC1_PERF_CFG; + + memcpy(perf->search_range_x_subsampled, + in->search_range_x_subsampled, + sizeof(perf->search_range_x_subsampled)); + memcpy(perf->search_range_y_subsampled, + in->search_range_y_subsampled, + sizeof(perf->search_range_y_subsampled)); + + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*perf); + break; + } + case HFI_PROPERTY_PARAM_VENC_MAX_NUM_B_FRAMES: { + struct hfi_max_num_b_frames *bframes = prop_data; + u32 *in = pdata; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_MAX_NUM_B_FRAMES; + bframes->max_num_b_frames = *in; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*bframes); + break; + } + case HFI_PROPERTY_CONFIG_VENC_INTRA_PERIOD: { + struct hfi_intra_period *in = pdata, *intra = prop_data; + + pkt->data[0] = HFI_PROPERTY_CONFIG_VENC_INTRA_PERIOD; + intra->pframes = in->pframes; + intra->bframes = in->bframes; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*intra); + break; + } + case HFI_PROPERTY_CONFIG_VENC_IDR_PERIOD: { + struct hfi_idr_period *in = pdata, *idr = prop_data; + + pkt->data[0] = HFI_PROPERTY_CONFIG_VENC_IDR_PERIOD; + idr->idr_period = in->idr_period; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*idr); + break; + } + case HFI_PROPERTY_PARAM_VDEC_CONCEAL_COLOR: { + struct hfi_conceal_color *color = prop_data; + u32 *in = pdata; + + pkt->data[0] = HFI_PROPERTY_PARAM_VDEC_CONCEAL_COLOR; + color->conceal_color = *in; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*color); + break; + } + case HFI_PROPERTY_CONFIG_VPE_OPERATIONS: { + struct hfi_operations_type *in = pdata, *ops = prop_data; + + switch (in->rotation) { + case HFI_ROTATE_NONE: + case HFI_ROTATE_90: + case HFI_ROTATE_180: + case HFI_ROTATE_270: + break; + default: + ret = -EINVAL; + break; + } + + switch (in->flip) { + case HFI_FLIP_NONE: + case HFI_FLIP_HORIZONTAL: + case HFI_FLIP_VERTICAL: + break; + default: + ret = -EINVAL; + break; + } + + pkt->data[0] = HFI_PROPERTY_CONFIG_VPE_OPERATIONS; + ops->rotation = in->rotation; + ops->flip = in->flip; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*ops); + break; + } + case HFI_PROPERTY_PARAM_VENC_INTRA_REFRESH: { + struct hfi_intra_refresh *in = pdata, *intra = prop_data; + + switch (in->mode) { + case HFI_INTRA_REFRESH_NONE: + case HFI_INTRA_REFRESH_ADAPTIVE: + case HFI_INTRA_REFRESH_CYCLIC: + case HFI_INTRA_REFRESH_CYCLIC_ADAPTIVE: + case HFI_INTRA_REFRESH_RANDOM: + break; + default: + ret = -EINVAL; + break; + } + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_INTRA_REFRESH; + intra->mode = in->mode; + intra->air_mbs = in->air_mbs; + intra->air_ref = in->air_ref; + intra->cir_mbs = in->cir_mbs; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*intra); + break; + } + case HFI_PROPERTY_PARAM_VENC_MULTI_SLICE_CONTROL: { + struct hfi_multi_slice_control *in = pdata, *multi = prop_data; + + switch (in->multi_slice) { + case HFI_MULTI_SLICE_OFF: + case HFI_MULTI_SLICE_GOB: + case HFI_MULTI_SLICE_BY_MB_COUNT: + case HFI_MULTI_SLICE_BY_BYTE_COUNT: + break; + default: + ret = -EINVAL; + break; + } + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_MULTI_SLICE_CONTROL; + multi->multi_slice = in->multi_slice; + multi->slice_size = in->slice_size; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*multi); + break; + } + case HFI_PROPERTY_PARAM_VENC_SLICE_DELIVERY_MODE: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_SLICE_DELIVERY_MODE; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VENC_H264_VUI_TIMING_INFO: { + struct hfi_h264_vui_timing_info *in = pdata, *vui = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_H264_VUI_TIMING_INFO; + vui->enable = in->enable; + vui->fixed_framerate = in->fixed_framerate; + vui->time_scale = in->time_scale; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*vui); + break; + } + case HFI_PROPERTY_CONFIG_VPE_DEINTERLACE: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = HFI_PROPERTY_CONFIG_VPE_DEINTERLACE; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VENC_H264_GENERATE_AUDNAL: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_H264_GENERATE_AUDNAL; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_BUFFER_ALLOC_MODE: { + struct hfi_buffer_alloc_mode *in = pdata, *mode = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_BUFFER_ALLOC_MODE; + mode->type = in->type; + mode->mode = in->mode; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*mode); + break; + } + case HFI_PROPERTY_PARAM_VDEC_FRAME_ASSEMBLY: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VDEC_FRAME_ASSEMBLY; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VENC_H264_VUI_BITSTREAM_RESTRC: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = + HFI_PROPERTY_PARAM_VENC_H264_VUI_BITSTREAM_RESTRC; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VENC_PRESERVE_TEXT_QUALITY: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_PRESERVE_TEXT_QUALITY; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VDEC_SCS_THRESHOLD: { + struct hfi_scs_threshold *thres = prop_data; + u32 *in = pdata; + + pkt->data[0] = HFI_PROPERTY_PARAM_VDEC_SCS_THRESHOLD; + thres->threshold_value = *in; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*thres); + break; + } + case HFI_PROPERTY_PARAM_MVC_BUFFER_LAYOUT: { + struct hfi_mvc_buffer_layout_descp_type *in = pdata; + struct hfi_mvc_buffer_layout_descp_type *mvc = prop_data; + + switch (in->layout_type) { + case HFI_MVC_BUFFER_LAYOUT_TOP_BOTTOM: + case HFI_MVC_BUFFER_LAYOUT_SEQ: + break; + default: + ret = -EINVAL; + break; + } + + pkt->data[0] = HFI_PROPERTY_PARAM_MVC_BUFFER_LAYOUT; + mvc->layout_type = in->layout_type; + mvc->bright_view_first = in->bright_view_first; + mvc->ngap = in->ngap; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*mvc); + break; + } + case HFI_PROPERTY_PARAM_VENC_LTRMODE: { + struct hfi_ltr_mode *in = pdata, *ltr = prop_data; + + switch (in->ltr_mode) { + case HFI_LTR_MODE_DISABLE: + case HFI_LTR_MODE_MANUAL: + case HFI_LTR_MODE_PERIODIC: + break; + default: + ret = -EINVAL; + break; + } + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_LTRMODE; + ltr->ltr_mode = in->ltr_mode; + ltr->ltr_count = in->ltr_count; + ltr->trust_mode = in->trust_mode; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*ltr); + break; + } + case HFI_PROPERTY_CONFIG_VENC_USELTRFRAME: { + struct hfi_ltr_use *in = pdata, *ltr_use = prop_data; + + pkt->data[0] = HFI_PROPERTY_CONFIG_VENC_USELTRFRAME; + ltr_use->frames = in->frames; + ltr_use->ref_ltr = in->ref_ltr; + ltr_use->use_constrnt = in->use_constrnt; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*ltr_use); + break; + } + case HFI_PROPERTY_CONFIG_VENC_MARKLTRFRAME: { + struct hfi_ltr_mark *in = pdata, *ltr_mark = prop_data; + + pkt->data[0] = HFI_PROPERTY_CONFIG_VENC_MARKLTRFRAME; + ltr_mark->mark_frame = in->mark_frame; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*ltr_mark); + break; + } + case HFI_PROPERTY_PARAM_VENC_HIER_P_MAX_NUM_ENH_LAYER: { + u32 *in = pdata; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_HIER_P_MAX_NUM_ENH_LAYER; + pkt->data[1] = *in; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_CONFIG_VENC_HIER_P_ENH_LAYER: { + u32 *in = pdata; + + pkt->data[0] = HFI_PROPERTY_CONFIG_VENC_HIER_P_ENH_LAYER; + pkt->data[1] = *in; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_PARAM_VENC_DISABLE_RC_TIMESTAMP: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_DISABLE_RC_TIMESTAMP; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VENC_INITIAL_QP: { + struct hfi_initial_quantization *in = pdata, *quant = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_INITIAL_QP; + quant->init_qp_enable = in->init_qp_enable; + quant->qp_i = in->qp_i; + quant->qp_p = in->qp_p; + quant->qp_b = in->qp_b; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*quant); + break; + } + case HFI_PROPERTY_PARAM_VPE_COLOR_SPACE_CONVERSION: { + struct hfi_vpe_color_space_conversion *in = pdata; + struct hfi_vpe_color_space_conversion *csc = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VPE_COLOR_SPACE_CONVERSION; + memcpy(csc->csc_matrix, in->csc_matrix, + sizeof(csc->csc_matrix)); + memcpy(csc->csc_bias, in->csc_bias, sizeof(csc->csc_bias)); + memcpy(csc->csc_limit, in->csc_limit, sizeof(csc->csc_limit)); + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*csc); + break; + } + case HFI_PROPERTY_PARAM_VENC_VPX_ERROR_RESILIENCE_MODE: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = + HFI_PROPERTY_PARAM_VENC_VPX_ERROR_RESILIENCE_MODE; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VENC_H264_NAL_SVC_EXT: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_H264_NAL_SVC_EXT; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_CONFIG_VENC_PERF_MODE: { + u32 *in = pdata; + + pkt->data[0] = HFI_PROPERTY_CONFIG_VENC_PERF_MODE; + pkt->data[1] = *in; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_PARAM_VENC_HIER_B_MAX_NUM_ENH_LAYER: { + u32 *in = pdata; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_HIER_B_MAX_NUM_ENH_LAYER; + pkt->data[1] = *in; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_PARAM_VDEC_NONCP_OUTPUT2: { + struct hfi_enable *in = pdata, *en = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VDEC_NONCP_OUTPUT2; + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VENC_HIER_P_HYBRID_MODE: { + struct hfi_hybrid_hierp *in = pdata, *hierp = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_HIER_P_HYBRID_MODE; + hierp->layers = in->layers; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*hierp); + break; + } + + /* FOLLOWING PROPERTIES ARE NOT IMPLEMENTED IN CORE YET */ + case HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS: + case HFI_PROPERTY_CONFIG_PRIORITY: + case HFI_PROPERTY_CONFIG_BATCH_INFO: + case HFI_PROPERTY_SYS_IDLE_INDICATOR: + case HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SUPPORTED: + case HFI_PROPERTY_PARAM_INTERLACE_FORMAT_SUPPORTED: + case HFI_PROPERTY_PARAM_CHROMA_SITE: + case HFI_PROPERTY_PARAM_PROPERTIES_SUPPORTED: + case HFI_PROPERTY_PARAM_PROFILE_LEVEL_SUPPORTED: + case HFI_PROPERTY_PARAM_CAPABILITY_SUPPORTED: + case HFI_PROPERTY_PARAM_NAL_STREAM_FORMAT_SUPPORTED: + case HFI_PROPERTY_PARAM_MULTI_VIEW_FORMAT: + case HFI_PROPERTY_PARAM_MAX_SEQUENCE_HEADER_SIZE: + case HFI_PROPERTY_PARAM_CODEC_SUPPORTED: + case HFI_PROPERTY_PARAM_VDEC_MULTI_VIEW_SELECT: + case HFI_PROPERTY_PARAM_VDEC_MB_QUANTIZATION: + case HFI_PROPERTY_PARAM_VDEC_NUM_CONCEALED_MB: + case HFI_PROPERTY_PARAM_VDEC_H264_ENTROPY_SWITCHING: + case HFI_PROPERTY_PARAM_VENC_MULTI_SLICE_INFO: + default: + return -EINVAL; + } + + return ret; +} + +static int +pkt_session_get_property_3xx(struct hfi_session_get_property_pkt *pkt, + void *cookie, u32 ptype) +{ + int ret = 0; + + if (!pkt || !cookie) + return -EINVAL; + + pkt->shdr.hdr.size = sizeof(struct hfi_session_get_property_pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_GET_PROPERTY; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->num_properties = 1; + + switch (ptype) { + case HFI_PROPERTY_CONFIG_VDEC_ENTROPY: + pkt->data[0] = HFI_PROPERTY_CONFIG_VDEC_ENTROPY; + break; + default: + ret = pkt_session_get_property_1x(pkt, cookie, ptype); + break; + } + + return ret; +} + +static int +pkt_session_set_property_3xx(struct hfi_session_set_property_pkt *pkt, + void *cookie, u32 ptype, void *pdata) +{ + void *prop_data; + int ret = 0; + + if (!pkt || !cookie || !pdata) + return -EINVAL; + + prop_data = &pkt->data[1]; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_SET_PROPERTY; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->num_properties = 1; + + /* + * Any session set property which is different in 3XX packetization + * should be added as a new case below. All unchanged session set + * properties will be handled in the default case. + */ + switch (ptype) { + case HFI_PROPERTY_PARAM_VDEC_MULTI_STREAM: { + struct hfi_multi_stream *in = pdata; + struct hfi_multi_stream_3x *multi = prop_data; + + pkt->data[0] = HFI_PROPERTY_PARAM_VDEC_MULTI_STREAM; + multi->buffer_type = in->buffer_type; + multi->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*multi); + break; + } + case HFI_PROPERTY_PARAM_VENC_INTRA_REFRESH: { + struct hfi_intra_refresh *in = pdata; + struct hfi_intra_refresh_3x *intra = prop_data; + + switch (in->mode) { + case HFI_INTRA_REFRESH_NONE: + case HFI_INTRA_REFRESH_ADAPTIVE: + case HFI_INTRA_REFRESH_CYCLIC: + case HFI_INTRA_REFRESH_CYCLIC_ADAPTIVE: + case HFI_INTRA_REFRESH_RANDOM: + break; + default: + ret = -EINVAL; + break; + } + + pkt->data[0] = HFI_PROPERTY_PARAM_VENC_INTRA_REFRESH; + intra->mode = in->mode; + intra->mbs = in->cir_mbs; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*intra); + break; + } + case HFI_PROPERTY_PARAM_VDEC_CONTINUE_DATA_TRANSFER: + /* for 3xx fw version session_continue is used */ + break; + default: + ret = pkt_session_set_property_1x(pkt, cookie, ptype, pdata); + break; + } + + return ret; +} + +int pkt_session_get_property(struct hfi_session_get_property_pkt *pkt, + void *cookie, u32 ptype) +{ + if (hfi_ver == HFI_VERSION_1XX) + return pkt_session_get_property_1x(pkt, cookie, ptype); + + return pkt_session_get_property_3xx(pkt, cookie, ptype); +} + +int pkt_session_set_property(struct hfi_session_set_property_pkt *pkt, + void *cookie, u32 ptype, void *pdata) +{ + if (hfi_ver == HFI_VERSION_1XX) + return pkt_session_set_property_1x(pkt, cookie, ptype, pdata); + + return pkt_session_set_property_3xx(pkt, cookie, ptype, pdata); +} + +void pkt_set_version(enum hfi_version version) +{ + hfi_ver = version; +} diff --git a/drivers/media/platform/qcom/venus/hfi_cmds.h b/drivers/media/platform/qcom/venus/hfi_cmds.h new file mode 100644 index 000000000000..f7617cf59914 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_cmds.h @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __VENUS_HFI_CMDS_H__ +#define __VENUS_HFI_CMDS_H__ + +#include "hfi.h" + +/* commands */ +#define HFI_CMD_SYS_INIT 0x10001 +#define HFI_CMD_SYS_PC_PREP 0x10002 +#define HFI_CMD_SYS_SET_RESOURCE 0x10003 +#define HFI_CMD_SYS_RELEASE_RESOURCE 0x10004 +#define HFI_CMD_SYS_SET_PROPERTY 0x10005 +#define HFI_CMD_SYS_GET_PROPERTY 0x10006 +#define HFI_CMD_SYS_SESSION_INIT 0x10007 +#define HFI_CMD_SYS_SESSION_END 0x10008 +#define HFI_CMD_SYS_SET_BUFFERS 0x10009 +#define HFI_CMD_SYS_TEST_SSR 0x10101 + +#define HFI_CMD_SESSION_SET_PROPERTY 0x11001 +#define HFI_CMD_SESSION_SET_BUFFERS 0x11002 +#define HFI_CMD_SESSION_GET_SEQUENCE_HEADER 0x11003 + +#define HFI_CMD_SYS_SESSION_ABORT 0x210001 +#define HFI_CMD_SYS_PING 0x210002 + +#define HFI_CMD_SESSION_LOAD_RESOURCES 0x211001 +#define HFI_CMD_SESSION_START 0x211002 +#define HFI_CMD_SESSION_STOP 0x211003 +#define HFI_CMD_SESSION_EMPTY_BUFFER 0x211004 +#define HFI_CMD_SESSION_FILL_BUFFER 0x211005 +#define HFI_CMD_SESSION_SUSPEND 0x211006 +#define HFI_CMD_SESSION_RESUME 0x211007 +#define HFI_CMD_SESSION_FLUSH 0x211008 +#define HFI_CMD_SESSION_GET_PROPERTY 0x211009 +#define HFI_CMD_SESSION_PARSE_SEQUENCE_HEADER 0x21100a +#define HFI_CMD_SESSION_RELEASE_BUFFERS 0x21100b +#define HFI_CMD_SESSION_RELEASE_RESOURCES 0x21100c +#define HFI_CMD_SESSION_CONTINUE 0x21100d +#define HFI_CMD_SESSION_SYNC 0x21100e + +/* command packets */ +struct hfi_sys_init_pkt { + struct hfi_pkt_hdr hdr; + u32 arch_type; +}; + +struct hfi_sys_pc_prep_pkt { + struct hfi_pkt_hdr hdr; +}; + +struct hfi_sys_set_resource_pkt { + struct hfi_pkt_hdr hdr; + u32 resource_handle; + u32 resource_type; + u32 resource_data[1]; +}; + +struct hfi_sys_release_resource_pkt { + struct hfi_pkt_hdr hdr; + u32 resource_type; + u32 resource_handle; +}; + +struct hfi_sys_set_property_pkt { + struct hfi_pkt_hdr hdr; + u32 num_properties; + u32 data[1]; +}; + +struct hfi_sys_get_property_pkt { + struct hfi_pkt_hdr hdr; + u32 num_properties; + u32 data[1]; +}; + +struct hfi_sys_set_buffers_pkt { + struct hfi_pkt_hdr hdr; + u32 buffer_type; + u32 buffer_size; + u32 num_buffers; + u32 buffer_addr[1]; +}; + +struct hfi_sys_ping_pkt { + struct hfi_pkt_hdr hdr; + u32 client_data; +}; + +struct hfi_session_init_pkt { + struct hfi_session_hdr_pkt shdr; + u32 session_domain; + u32 session_codec; +}; + +struct hfi_session_end_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_abort_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_set_property_pkt { + struct hfi_session_hdr_pkt shdr; + u32 num_properties; + u32 data[0]; +}; + +struct hfi_session_set_buffers_pkt { + struct hfi_session_hdr_pkt shdr; + u32 buffer_type; + u32 buffer_size; + u32 extradata_size; + u32 min_buffer_size; + u32 num_buffers; + u32 buffer_info[1]; +}; + +struct hfi_session_get_sequence_header_pkt { + struct hfi_session_hdr_pkt shdr; + u32 buffer_len; + u32 packet_buffer; +}; + +struct hfi_session_load_resources_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_start_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_stop_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_empty_buffer_compressed_pkt { + struct hfi_session_hdr_pkt shdr; + u32 time_stamp_hi; + u32 time_stamp_lo; + u32 flags; + u32 mark_target; + u32 mark_data; + u32 offset; + u32 alloc_len; + u32 filled_len; + u32 input_tag; + u32 packet_buffer; + u32 extradata_buffer; + u32 data[1]; +}; + +struct hfi_session_empty_buffer_uncompressed_plane0_pkt { + struct hfi_session_hdr_pkt shdr; + u32 view_id; + u32 time_stamp_hi; + u32 time_stamp_lo; + u32 flags; + u32 mark_target; + u32 mark_data; + u32 alloc_len; + u32 filled_len; + u32 offset; + u32 input_tag; + u32 packet_buffer; + u32 extradata_buffer; + u32 data[1]; +}; + +struct hfi_session_empty_buffer_uncompressed_plane1_pkt { + u32 flags; + u32 alloc_len; + u32 filled_len; + u32 offset; + u32 packet_buffer2; + u32 data[1]; +}; + +struct hfi_session_empty_buffer_uncompressed_plane2_pkt { + u32 flags; + u32 alloc_len; + u32 filled_len; + u32 offset; + u32 packet_buffer3; + u32 data[1]; +}; + +struct hfi_session_fill_buffer_pkt { + struct hfi_session_hdr_pkt shdr; + u32 stream_id; + u32 offset; + u32 alloc_len; + u32 filled_len; + u32 output_tag; + u32 packet_buffer; + u32 extradata_buffer; + u32 data[1]; +}; + +struct hfi_session_flush_pkt { + struct hfi_session_hdr_pkt shdr; + u32 flush_type; +}; + +struct hfi_session_suspend_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_resume_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_get_property_pkt { + struct hfi_session_hdr_pkt shdr; + u32 num_properties; + u32 data[1]; +}; + +struct hfi_session_release_buffer_pkt { + struct hfi_session_hdr_pkt shdr; + u32 buffer_type; + u32 buffer_size; + u32 extradata_size; + u32 response_req; + u32 num_buffers; + u32 buffer_info[1]; +}; + +struct hfi_session_release_resources_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_parse_sequence_header_pkt { + struct hfi_session_hdr_pkt shdr; + u32 header_len; + u32 packet_buffer; +}; + +struct hfi_sfr { + u32 buf_size; + u8 data[1]; +}; + +struct hfi_sys_test_ssr_pkt { + struct hfi_pkt_hdr hdr; + u32 trigger_type; +}; + +void pkt_set_version(enum hfi_version version); + +void pkt_sys_init(struct hfi_sys_init_pkt *pkt, u32 arch_type); +void pkt_sys_pc_prep(struct hfi_sys_pc_prep_pkt *pkt); +void pkt_sys_idle_indicator(struct hfi_sys_set_property_pkt *pkt, u32 enable); +void pkt_sys_power_control(struct hfi_sys_set_property_pkt *pkt, u32 enable); +int pkt_sys_set_resource(struct hfi_sys_set_resource_pkt *pkt, u32 id, u32 size, + u32 addr, void *cookie); +int pkt_sys_unset_resource(struct hfi_sys_release_resource_pkt *pkt, u32 id, + u32 size, void *cookie); +void pkt_sys_debug_config(struct hfi_sys_set_property_pkt *pkt, u32 mode, + u32 config); +void pkt_sys_coverage_config(struct hfi_sys_set_property_pkt *pkt, u32 mode); +void pkt_sys_ping(struct hfi_sys_ping_pkt *pkt, u32 cookie); +void pkt_sys_image_version(struct hfi_sys_get_property_pkt *pkt); +int pkt_sys_ssr_cmd(struct hfi_sys_test_ssr_pkt *pkt, u32 trigger_type); +int pkt_session_init(struct hfi_session_init_pkt *pkt, void *cookie, + u32 session_type, u32 codec); +void pkt_session_cmd(struct hfi_session_pkt *pkt, u32 pkt_type, void *cookie); +int pkt_session_set_buffers(struct hfi_session_set_buffers_pkt *pkt, + void *cookie, struct hfi_buffer_desc *bd); +int pkt_session_unset_buffers(struct hfi_session_release_buffer_pkt *pkt, + void *cookie, struct hfi_buffer_desc *bd); +int pkt_session_etb_decoder(struct hfi_session_empty_buffer_compressed_pkt *pkt, + void *cookie, struct hfi_frame_data *input_frame); +int pkt_session_etb_encoder( + struct hfi_session_empty_buffer_uncompressed_plane0_pkt *pkt, + void *cookie, struct hfi_frame_data *input_frame); +int pkt_session_ftb(struct hfi_session_fill_buffer_pkt *pkt, + void *cookie, struct hfi_frame_data *output_frame); +int pkt_session_parse_seq_header( + struct hfi_session_parse_sequence_header_pkt *pkt, + void *cookie, u32 seq_hdr, u32 seq_hdr_len); +int pkt_session_get_seq_hdr(struct hfi_session_get_sequence_header_pkt *pkt, + void *cookie, u32 seq_hdr, u32 seq_hdr_len); +int pkt_session_flush(struct hfi_session_flush_pkt *pkt, void *cookie, + u32 flush_mode); +int pkt_session_get_property(struct hfi_session_get_property_pkt *pkt, + void *cookie, u32 ptype); +int pkt_session_set_property(struct hfi_session_set_property_pkt *pkt, + void *cookie, u32 ptype, void *pdata); + +#endif diff --git a/drivers/media/platform/qcom/venus/hfi_helper.h b/drivers/media/platform/qcom/venus/hfi_helper.h new file mode 100644 index 000000000000..8d282dba9e57 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_helper.h @@ -0,0 +1,1050 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __VENUS_HFI_HELPER_H__ +#define __VENUS_HFI_HELPER_H__ + +#define HFI_DOMAIN_BASE_COMMON 0 + +#define HFI_DOMAIN_BASE_VDEC 0x1000000 +#define HFI_DOMAIN_BASE_VENC 0x2000000 +#define HFI_DOMAIN_BASE_VPE 0x3000000 + +#define HFI_VIDEO_ARCH_OX 0x1 + +#define HFI_ARCH_COMMON_OFFSET 0 +#define HFI_ARCH_OX_OFFSET 0x200000 + +#define HFI_OX_BASE 0x1000000 + +#define HFI_CMD_START_OFFSET 0x10000 +#define HFI_MSG_START_OFFSET 0x20000 + +#define HFI_ERR_NONE 0x0 +#define HFI_ERR_SYS_FATAL 0x1 +#define HFI_ERR_SYS_INVALID_PARAMETER 0x2 +#define HFI_ERR_SYS_VERSION_MISMATCH 0x3 +#define HFI_ERR_SYS_INSUFFICIENT_RESOURCES 0x4 +#define HFI_ERR_SYS_MAX_SESSIONS_REACHED 0x5 +#define HFI_ERR_SYS_UNSUPPORTED_CODEC 0x6 +#define HFI_ERR_SYS_SESSION_IN_USE 0x7 +#define HFI_ERR_SYS_SESSION_ID_OUT_OF_RANGE 0x8 +#define HFI_ERR_SYS_UNSUPPORTED_DOMAIN 0x9 + +#define HFI_ERR_SESSION_FATAL 0x1001 +#define HFI_ERR_SESSION_INVALID_PARAMETER 0x1002 +#define HFI_ERR_SESSION_BAD_POINTER 0x1003 +#define HFI_ERR_SESSION_INVALID_SESSION_ID 0x1004 +#define HFI_ERR_SESSION_INVALID_STREAM_ID 0x1005 +#define HFI_ERR_SESSION_INCORRECT_STATE_OPERATION 0x1006 +#define HFI_ERR_SESSION_UNSUPPORTED_PROPERTY 0x1007 +#define HFI_ERR_SESSION_UNSUPPORTED_SETTING 0x1008 +#define HFI_ERR_SESSION_INSUFFICIENT_RESOURCES 0x1009 +#define HFI_ERR_SESSION_STREAM_CORRUPT_OUTPUT_STALLED 0x100a +#define HFI_ERR_SESSION_STREAM_CORRUPT 0x100b +#define HFI_ERR_SESSION_ENC_OVERFLOW 0x100c +#define HFI_ERR_SESSION_UNSUPPORTED_STREAM 0x100d +#define HFI_ERR_SESSION_CMDSIZE 0x100e +#define HFI_ERR_SESSION_UNSUPPORT_CMD 0x100f +#define HFI_ERR_SESSION_UNSUPPORT_BUFFERTYPE 0x1010 +#define HFI_ERR_SESSION_BUFFERCOUNT_TOOSMALL 0x1011 +#define HFI_ERR_SESSION_INVALID_SCALE_FACTOR 0x1012 +#define HFI_ERR_SESSION_UPSCALE_NOT_SUPPORTED 0x1013 + +#define HFI_EVENT_SYS_ERROR 0x1 +#define HFI_EVENT_SESSION_ERROR 0x2 + +#define HFI_EVENT_DATA_SEQUENCE_CHANGED_SUFFICIENT_BUF_RESOURCES 0x1000001 +#define HFI_EVENT_DATA_SEQUENCE_CHANGED_INSUFFICIENT_BUF_RESOURCES 0x1000002 +#define HFI_EVENT_SESSION_SEQUENCE_CHANGED 0x1000003 +#define HFI_EVENT_SESSION_PROPERTY_CHANGED 0x1000004 +#define HFI_EVENT_SESSION_LTRUSE_FAILED 0x1000005 +#define HFI_EVENT_RELEASE_BUFFER_REFERENCE 0x1000006 + +#define HFI_BUFFERFLAG_EOS 0x00000001 +#define HFI_BUFFERFLAG_STARTTIME 0x00000002 +#define HFI_BUFFERFLAG_DECODEONLY 0x00000004 +#define HFI_BUFFERFLAG_DATACORRUPT 0x00000008 +#define HFI_BUFFERFLAG_ENDOFFRAME 0x00000010 +#define HFI_BUFFERFLAG_SYNCFRAME 0x00000020 +#define HFI_BUFFERFLAG_EXTRADATA 0x00000040 +#define HFI_BUFFERFLAG_CODECCONFIG 0x00000080 +#define HFI_BUFFERFLAG_TIMESTAMPINVALID 0x00000100 +#define HFI_BUFFERFLAG_READONLY 0x00000200 +#define HFI_BUFFERFLAG_ENDOFSUBFRAME 0x00000400 +#define HFI_BUFFERFLAG_EOSEQ 0x00200000 +#define HFI_BUFFERFLAG_MBAFF 0x08000000 +#define HFI_BUFFERFLAG_VPE_YUV_601_709_CSC_CLAMP 0x10000000 +#define HFI_BUFFERFLAG_DROP_FRAME 0x20000000 +#define HFI_BUFFERFLAG_TEI 0x40000000 +#define HFI_BUFFERFLAG_DISCONTINUITY 0x80000000 + +#define HFI_ERR_SESSION_EMPTY_BUFFER_DONE_OUTPUT_PENDING 0x1001001 +#define HFI_ERR_SESSION_SAME_STATE_OPERATION 0x1001002 +#define HFI_ERR_SESSION_SYNC_FRAME_NOT_DETECTED 0x1001003 +#define HFI_ERR_SESSION_START_CODE_NOT_FOUND 0x1001004 + +#define HFI_FLUSH_INPUT 0x1000001 +#define HFI_FLUSH_OUTPUT 0x1000002 +#define HFI_FLUSH_OUTPUT2 0x1000003 +#define HFI_FLUSH_ALL 0x1000004 + +#define HFI_EXTRADATA_NONE 0x00000000 +#define HFI_EXTRADATA_MB_QUANTIZATION 0x00000001 +#define HFI_EXTRADATA_INTERLACE_VIDEO 0x00000002 +#define HFI_EXTRADATA_VC1_FRAMEDISP 0x00000003 +#define HFI_EXTRADATA_VC1_SEQDISP 0x00000004 +#define HFI_EXTRADATA_TIMESTAMP 0x00000005 +#define HFI_EXTRADATA_S3D_FRAME_PACKING 0x00000006 +#define HFI_EXTRADATA_FRAME_RATE 0x00000007 +#define HFI_EXTRADATA_PANSCAN_WINDOW 0x00000008 +#define HFI_EXTRADATA_RECOVERY_POINT_SEI 0x00000009 +#define HFI_EXTRADATA_MPEG2_SEQDISP 0x0000000d +#define HFI_EXTRADATA_STREAM_USERDATA 0x0000000e +#define HFI_EXTRADATA_FRAME_QP 0x0000000f +#define HFI_EXTRADATA_FRAME_BITS_INFO 0x00000010 +#define HFI_EXTRADATA_MULTISLICE_INFO 0x7f100000 +#define HFI_EXTRADATA_NUM_CONCEALED_MB 0x7f100001 +#define HFI_EXTRADATA_INDEX 0x7f100002 +#define HFI_EXTRADATA_METADATA_LTR 0x7f100004 +#define HFI_EXTRADATA_METADATA_FILLER 0x7fe00002 + +#define HFI_INDEX_EXTRADATA_INPUT_CROP 0x0700000e +#define HFI_INDEX_EXTRADATA_DIGITAL_ZOOM 0x07000010 +#define HFI_INDEX_EXTRADATA_ASPECT_RATIO 0x7f100003 + +#define HFI_INTERLACE_FRAME_PROGRESSIVE 0x01 +#define HFI_INTERLACE_INTERLEAVE_FRAME_TOPFIELDFIRST 0x02 +#define HFI_INTERLACE_INTERLEAVE_FRAME_BOTTOMFIELDFIRST 0x04 +#define HFI_INTERLACE_FRAME_TOPFIELDFIRST 0x08 +#define HFI_INTERLACE_FRAME_BOTTOMFIELDFIRST 0x10 + +/* + * HFI_PROPERTY_PARAM_OX_START + * HFI_DOMAIN_BASE_COMMON + HFI_ARCH_OX_OFFSET + 0x1000 + */ +#define HFI_PROPERTY_PARAM_BUFFER_COUNT_ACTUAL 0x201001 +#define HFI_PROPERTY_PARAM_UNCOMPRESSED_PLANE_ACTUAL_CONSTRAINTS_INFO 0x201002 +#define HFI_PROPERTY_PARAM_INTERLACE_FORMAT_SUPPORTED 0x201003 +#define HFI_PROPERTY_PARAM_CHROMA_SITE 0x201004 +#define HFI_PROPERTY_PARAM_EXTRA_DATA_HEADER_CONFIG 0x201005 +#define HFI_PROPERTY_PARAM_INDEX_EXTRADATA 0x201006 +#define HFI_PROPERTY_PARAM_DIVX_FORMAT 0x201007 +#define HFI_PROPERTY_PARAM_BUFFER_ALLOC_MODE 0x201008 +#define HFI_PROPERTY_PARAM_S3D_FRAME_PACKING_EXTRADATA 0x201009 +#define HFI_PROPERTY_PARAM_ERR_DETECTION_CODE_EXTRADATA 0x20100a +#define HFI_PROPERTY_PARAM_BUFFER_ALLOC_MODE_SUPPORTED 0x20100b +#define HFI_PROPERTY_PARAM_BUFFER_SIZE_ACTUAL 0x20100c +#define HFI_PROPERTY_PARAM_BUFFER_DISPLAY_HOLD_COUNT_ACTUAL 0x20100d + +/* + * HFI_PROPERTY_CONFIG_OX_START + * HFI_DOMAIN_BASE_COMMON + HFI_ARCH_OX_OFFSET + 0x2000 + */ +#define HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS 0x202001 +#define HFI_PROPERTY_CONFIG_REALTIME 0x202002 +#define HFI_PROPERTY_CONFIG_PRIORITY 0x202003 +#define HFI_PROPERTY_CONFIG_BATCH_INFO 0x202004 + +/* + * HFI_PROPERTY_PARAM_VDEC_OX_START \ + * HFI_DOMAIN_BASE_VDEC + HFI_ARCH_OX_OFFSET + 0x3000 + */ +#define HFI_PROPERTY_PARAM_VDEC_CONTINUE_DATA_TRANSFER 0x1203001 +#define HFI_PROPERTY_PARAM_VDEC_DISPLAY_PICTURE_BUFFER_COUNT 0x1203002 +#define HFI_PROPERTY_PARAM_VDEC_MULTI_VIEW_SELECT 0x1203003 +#define HFI_PROPERTY_PARAM_VDEC_PICTURE_TYPE_DECODE 0x1203004 +#define HFI_PROPERTY_PARAM_VDEC_OUTPUT_ORDER 0x1203005 +#define HFI_PROPERTY_PARAM_VDEC_MB_QUANTIZATION 0x1203006 +#define HFI_PROPERTY_PARAM_VDEC_NUM_CONCEALED_MB 0x1203007 +#define HFI_PROPERTY_PARAM_VDEC_H264_ENTROPY_SWITCHING 0x1203008 +#define HFI_PROPERTY_PARAM_VDEC_OUTPUT2_KEEP_ASPECT_RATIO 0x1203009 +#define HFI_PROPERTY_PARAM_VDEC_FRAME_RATE_EXTRADATA 0x120300a +#define HFI_PROPERTY_PARAM_VDEC_PANSCAN_WNDW_EXTRADATA 0x120300b +#define HFI_PROPERTY_PARAM_VDEC_RECOVERY_POINT_SEI_EXTRADATA 0x120300c +#define HFI_PROPERTY_PARAM_VDEC_THUMBNAIL_MODE 0x120300d +#define HFI_PROPERTY_PARAM_VDEC_FRAME_ASSEMBLY 0x120300e +#define HFI_PROPERTY_PARAM_VDEC_VC1_FRAMEDISP_EXTRADATA 0x1203011 +#define HFI_PROPERTY_PARAM_VDEC_VC1_SEQDISP_EXTRADATA 0x1203012 +#define HFI_PROPERTY_PARAM_VDEC_TIMESTAMP_EXTRADATA 0x1203013 +#define HFI_PROPERTY_PARAM_VDEC_INTERLACE_VIDEO_EXTRADATA 0x1203014 +#define HFI_PROPERTY_PARAM_VDEC_AVC_SESSION_SELECT 0x1203015 +#define HFI_PROPERTY_PARAM_VDEC_MPEG2_SEQDISP_EXTRADATA 0x1203016 +#define HFI_PROPERTY_PARAM_VDEC_STREAM_USERDATA_EXTRADATA 0x1203017 +#define HFI_PROPERTY_PARAM_VDEC_FRAME_QP_EXTRADATA 0x1203018 +#define HFI_PROPERTY_PARAM_VDEC_FRAME_BITS_INFO_EXTRADATA 0x1203019 +#define HFI_PROPERTY_PARAM_VDEC_SCS_THRESHOLD 0x120301a + +/* + * HFI_PROPERTY_CONFIG_VDEC_OX_START + * HFI_DOMAIN_BASE_VDEC + HFI_ARCH_OX_OFFSET + 0x0000 + */ +#define HFI_PROPERTY_CONFIG_VDEC_POST_LOOP_DEBLOCKER 0x1200001 +#define HFI_PROPERTY_CONFIG_VDEC_MB_ERROR_MAP_REPORTING 0x1200002 +#define HFI_PROPERTY_CONFIG_VDEC_MB_ERROR_MAP 0x1200003 + +#define HFI_PROPERTY_CONFIG_VDEC_ENTROPY 0x1204004 + +/* + * HFI_PROPERTY_PARAM_VENC_OX_START + * HFI_DOMAIN_BASE_VENC + HFI_ARCH_OX_OFFSET + 0x5000 + */ +#define HFI_PROPERTY_PARAM_VENC_MULTI_SLICE_INFO 0x2205001 +#define HFI_PROPERTY_PARAM_VENC_H264_IDR_S3D_FRAME_PACKING_NAL 0x2205002 +#define HFI_PROPERTY_PARAM_VENC_LTR_INFO 0x2205003 +#define HFI_PROPERTY_PARAM_VENC_MBI_DUMPING 0x2205005 + +/* + * HFI_PROPERTY_CONFIG_VENC_OX_START + * HFI_DOMAIN_BASE_VENC + HFI_ARCH_OX_OFFSET + 0x6000 + */ +#define HFI_PROPERTY_CONFIG_VENC_FRAME_QP 0x2206001 + +/* + * HFI_PROPERTY_PARAM_VPE_OX_START + * HFI_DOMAIN_BASE_VPE + HFI_ARCH_OX_OFFSET + 0x7000 + */ +#define HFI_PROPERTY_PARAM_VPE_COLOR_SPACE_CONVERSION 0x3207001 + +#define HFI_PROPERTY_CONFIG_VPE_OX_START \ + (HFI_DOMAIN_BASE_VPE + HFI_ARCH_OX_OFFSET + 0x8000) + +#define HFI_CHROMA_SITE_0 0x1000001 +#define HFI_CHROMA_SITE_1 0x1000002 +#define HFI_CHROMA_SITE_2 0x1000003 +#define HFI_CHROMA_SITE_3 0x1000004 +#define HFI_CHROMA_SITE_4 0x1000005 +#define HFI_CHROMA_SITE_5 0x1000006 + +#define HFI_PRIORITY_LOW 10 +#define HFI_PRIOIRTY_MEDIUM 20 +#define HFI_PRIORITY_HIGH 30 + +#define HFI_OUTPUT_ORDER_DISPLAY 0x1000001 +#define HFI_OUTPUT_ORDER_DECODE 0x1000002 + +#define HFI_RATE_CONTROL_OFF 0x1000001 +#define HFI_RATE_CONTROL_VBR_VFR 0x1000002 +#define HFI_RATE_CONTROL_VBR_CFR 0x1000003 +#define HFI_RATE_CONTROL_CBR_VFR 0x1000004 +#define HFI_RATE_CONTROL_CBR_CFR 0x1000005 + +#define HFI_VIDEO_CODEC_H264 0x00000002 +#define HFI_VIDEO_CODEC_H263 0x00000004 +#define HFI_VIDEO_CODEC_MPEG1 0x00000008 +#define HFI_VIDEO_CODEC_MPEG2 0x00000010 +#define HFI_VIDEO_CODEC_MPEG4 0x00000020 +#define HFI_VIDEO_CODEC_DIVX_311 0x00000040 +#define HFI_VIDEO_CODEC_DIVX 0x00000080 +#define HFI_VIDEO_CODEC_VC1 0x00000100 +#define HFI_VIDEO_CODEC_SPARK 0x00000200 +#define HFI_VIDEO_CODEC_VP8 0x00001000 +#define HFI_VIDEO_CODEC_HEVC 0x00002000 +#define HFI_VIDEO_CODEC_VP9 0x00004000 +#define HFI_VIDEO_CODEC_HEVC_HYBRID 0x80000000 + +#define HFI_H264_PROFILE_BASELINE 0x00000001 +#define HFI_H264_PROFILE_MAIN 0x00000002 +#define HFI_H264_PROFILE_HIGH 0x00000004 +#define HFI_H264_PROFILE_STEREO_HIGH 0x00000008 +#define HFI_H264_PROFILE_MULTIVIEW_HIGH 0x00000010 +#define HFI_H264_PROFILE_CONSTRAINED_BASE 0x00000020 +#define HFI_H264_PROFILE_CONSTRAINED_HIGH 0x00000040 + +#define HFI_H264_LEVEL_1 0x00000001 +#define HFI_H264_LEVEL_1b 0x00000002 +#define HFI_H264_LEVEL_11 0x00000004 +#define HFI_H264_LEVEL_12 0x00000008 +#define HFI_H264_LEVEL_13 0x00000010 +#define HFI_H264_LEVEL_2 0x00000020 +#define HFI_H264_LEVEL_21 0x00000040 +#define HFI_H264_LEVEL_22 0x00000080 +#define HFI_H264_LEVEL_3 0x00000100 +#define HFI_H264_LEVEL_31 0x00000200 +#define HFI_H264_LEVEL_32 0x00000400 +#define HFI_H264_LEVEL_4 0x00000800 +#define HFI_H264_LEVEL_41 0x00001000 +#define HFI_H264_LEVEL_42 0x00002000 +#define HFI_H264_LEVEL_5 0x00004000 +#define HFI_H264_LEVEL_51 0x00008000 +#define HFI_H264_LEVEL_52 0x00010000 + +#define HFI_H263_PROFILE_BASELINE 0x00000001 + +#define HFI_H263_LEVEL_10 0x00000001 +#define HFI_H263_LEVEL_20 0x00000002 +#define HFI_H263_LEVEL_30 0x00000004 +#define HFI_H263_LEVEL_40 0x00000008 +#define HFI_H263_LEVEL_45 0x00000010 +#define HFI_H263_LEVEL_50 0x00000020 +#define HFI_H263_LEVEL_60 0x00000040 +#define HFI_H263_LEVEL_70 0x00000080 + +#define HFI_MPEG2_PROFILE_SIMPLE 0x00000001 +#define HFI_MPEG2_PROFILE_MAIN 0x00000002 +#define HFI_MPEG2_PROFILE_422 0x00000004 +#define HFI_MPEG2_PROFILE_SNR 0x00000008 +#define HFI_MPEG2_PROFILE_SPATIAL 0x00000010 +#define HFI_MPEG2_PROFILE_HIGH 0x00000020 + +#define HFI_MPEG2_LEVEL_LL 0x00000001 +#define HFI_MPEG2_LEVEL_ML 0x00000002 +#define HFI_MPEG2_LEVEL_H14 0x00000004 +#define HFI_MPEG2_LEVEL_HL 0x00000008 + +#define HFI_MPEG4_PROFILE_SIMPLE 0x00000001 +#define HFI_MPEG4_PROFILE_ADVANCEDSIMPLE 0x00000002 + +#define HFI_MPEG4_LEVEL_0 0x00000001 +#define HFI_MPEG4_LEVEL_0b 0x00000002 +#define HFI_MPEG4_LEVEL_1 0x00000004 +#define HFI_MPEG4_LEVEL_2 0x00000008 +#define HFI_MPEG4_LEVEL_3 0x00000010 +#define HFI_MPEG4_LEVEL_4 0x00000020 +#define HFI_MPEG4_LEVEL_4a 0x00000040 +#define HFI_MPEG4_LEVEL_5 0x00000080 +#define HFI_MPEG4_LEVEL_6 0x00000100 +#define HFI_MPEG4_LEVEL_7 0x00000200 +#define HFI_MPEG4_LEVEL_8 0x00000400 +#define HFI_MPEG4_LEVEL_9 0x00000800 +#define HFI_MPEG4_LEVEL_3b 0x00001000 + +#define HFI_VC1_PROFILE_SIMPLE 0x00000001 +#define HFI_VC1_PROFILE_MAIN 0x00000002 +#define HFI_VC1_PROFILE_ADVANCED 0x00000004 + +#define HFI_VC1_LEVEL_LOW 0x00000001 +#define HFI_VC1_LEVEL_MEDIUM 0x00000002 +#define HFI_VC1_LEVEL_HIGH 0x00000004 +#define HFI_VC1_LEVEL_0 0x00000008 +#define HFI_VC1_LEVEL_1 0x00000010 +#define HFI_VC1_LEVEL_2 0x00000020 +#define HFI_VC1_LEVEL_3 0x00000040 +#define HFI_VC1_LEVEL_4 0x00000080 + +#define HFI_VPX_PROFILE_SIMPLE 0x00000001 +#define HFI_VPX_PROFILE_ADVANCED 0x00000002 +#define HFI_VPX_PROFILE_VERSION_0 0x00000004 +#define HFI_VPX_PROFILE_VERSION_1 0x00000008 +#define HFI_VPX_PROFILE_VERSION_2 0x00000010 +#define HFI_VPX_PROFILE_VERSION_3 0x00000020 + +#define HFI_DIVX_FORMAT_4 0x1 +#define HFI_DIVX_FORMAT_5 0x2 +#define HFI_DIVX_FORMAT_6 0x3 + +#define HFI_DIVX_PROFILE_QMOBILE 0x00000001 +#define HFI_DIVX_PROFILE_MOBILE 0x00000002 +#define HFI_DIVX_PROFILE_MT 0x00000004 +#define HFI_DIVX_PROFILE_HT 0x00000008 +#define HFI_DIVX_PROFILE_HD 0x00000010 + +#define HFI_HEVC_PROFILE_MAIN 0x00000001 +#define HFI_HEVC_PROFILE_MAIN10 0x00000002 +#define HFI_HEVC_PROFILE_MAIN_STILL_PIC 0x00000004 + +#define HFI_HEVC_LEVEL_1 0x00000001 +#define HFI_HEVC_LEVEL_2 0x00000002 +#define HFI_HEVC_LEVEL_21 0x00000004 +#define HFI_HEVC_LEVEL_3 0x00000008 +#define HFI_HEVC_LEVEL_31 0x00000010 +#define HFI_HEVC_LEVEL_4 0x00000020 +#define HFI_HEVC_LEVEL_41 0x00000040 +#define HFI_HEVC_LEVEL_5 0x00000080 +#define HFI_HEVC_LEVEL_51 0x00000100 +#define HFI_HEVC_LEVEL_52 0x00000200 +#define HFI_HEVC_LEVEL_6 0x00000400 +#define HFI_HEVC_LEVEL_61 0x00000800 +#define HFI_HEVC_LEVEL_62 0x00001000 + +#define HFI_HEVC_TIER_MAIN 0x1 +#define HFI_HEVC_TIER_HIGH0 0x2 + +#define HFI_BUFFER_INPUT 0x1 +#define HFI_BUFFER_OUTPUT 0x2 +#define HFI_BUFFER_OUTPUT2 0x3 +#define HFI_BUFFER_INTERNAL_PERSIST 0x4 +#define HFI_BUFFER_INTERNAL_PERSIST_1 0x5 +#define HFI_BUFFER_INTERNAL_SCRATCH 0x1000001 +#define HFI_BUFFER_EXTRADATA_INPUT 0x1000002 +#define HFI_BUFFER_EXTRADATA_OUTPUT 0x1000003 +#define HFI_BUFFER_EXTRADATA_OUTPUT2 0x1000004 +#define HFI_BUFFER_INTERNAL_SCRATCH_1 0x1000005 +#define HFI_BUFFER_INTERNAL_SCRATCH_2 0x1000006 + +#define HFI_BUFFER_TYPE_MAX 11 + +#define HFI_BUFFER_MODE_STATIC 0x1000001 +#define HFI_BUFFER_MODE_RING 0x1000002 +#define HFI_BUFFER_MODE_DYNAMIC 0x1000003 + +#define HFI_VENC_PERFMODE_MAX_QUALITY 0x1 +#define HFI_VENC_PERFMODE_POWER_SAVE 0x2 + +/* + * HFI_PROPERTY_SYS_COMMON_START + * HFI_DOMAIN_BASE_COMMON + HFI_ARCH_COMMON_OFFSET + 0x0000 + */ +#define HFI_PROPERTY_SYS_DEBUG_CONFIG 0x1 +#define HFI_PROPERTY_SYS_RESOURCE_OCMEM_REQUIREMENT_INFO 0x2 +#define HFI_PROPERTY_SYS_CONFIG_VCODEC_CLKFREQ 0x3 +#define HFI_PROPERTY_SYS_IDLE_INDICATOR 0x4 +#define HFI_PROPERTY_SYS_CODEC_POWER_PLANE_CTRL 0x5 +#define HFI_PROPERTY_SYS_IMAGE_VERSION 0x6 +#define HFI_PROPERTY_SYS_CONFIG_COVERAGE 0x7 + +/* + * HFI_PROPERTY_PARAM_COMMON_START + * HFI_DOMAIN_BASE_COMMON + HFI_ARCH_COMMON_OFFSET + 0x1000 + */ +#define HFI_PROPERTY_PARAM_FRAME_SIZE 0x1001 +#define HFI_PROPERTY_PARAM_UNCOMPRESSED_PLANE_ACTUAL_INFO 0x1002 +#define HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SELECT 0x1003 +#define HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SUPPORTED 0x1004 +#define HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT 0x1005 +#define HFI_PROPERTY_PARAM_PROFILE_LEVEL_SUPPORTED 0x1006 +#define HFI_PROPERTY_PARAM_CAPABILITY_SUPPORTED 0x1007 +#define HFI_PROPERTY_PARAM_PROPERTIES_SUPPORTED 0x1008 +#define HFI_PROPERTY_PARAM_CODEC_SUPPORTED 0x1009 +#define HFI_PROPERTY_PARAM_NAL_STREAM_FORMAT_SUPPORTED 0x100a +#define HFI_PROPERTY_PARAM_NAL_STREAM_FORMAT_SELECT 0x100b +#define HFI_PROPERTY_PARAM_MULTI_VIEW_FORMAT 0x100c +#define HFI_PROPERTY_PARAM_MAX_SEQUENCE_HEADER_SIZE 0x100d +#define HFI_PROPERTY_PARAM_CODEC_MASK_SUPPORTED 0x100e +#define HFI_PROPERTY_PARAM_MVC_BUFFER_LAYOUT 0x100f +#define HFI_PROPERTY_PARAM_MAX_SESSIONS_SUPPORTED 0x1010 + +/* + * HFI_PROPERTY_CONFIG_COMMON_START + * HFI_DOMAIN_BASE_COMMON + HFI_ARCH_COMMON_OFFSET + 0x2000 + */ +#define HFI_PROPERTY_CONFIG_FRAME_RATE 0x2001 + +/* + * HFI_PROPERTY_PARAM_VDEC_COMMON_START + * HFI_DOMAIN_BASE_VDEC + HFI_ARCH_COMMON_OFFSET + 0x3000 + */ +#define HFI_PROPERTY_PARAM_VDEC_MULTI_STREAM 0x1003001 +#define HFI_PROPERTY_PARAM_VDEC_CONCEAL_COLOR 0x1003002 +#define HFI_PROPERTY_PARAM_VDEC_NONCP_OUTPUT2 0x1003003 + +/* + * HFI_PROPERTY_CONFIG_VDEC_COMMON_START + * HFI_DOMAIN_BASE_VDEC + HFI_ARCH_COMMON_OFFSET + 0x4000 + */ + +/* + * HFI_PROPERTY_PARAM_VENC_COMMON_START + * HFI_DOMAIN_BASE_VENC + HFI_ARCH_COMMON_OFFSET + 0x5000 + */ +#define HFI_PROPERTY_PARAM_VENC_SLICE_DELIVERY_MODE 0x2005001 +#define HFI_PROPERTY_PARAM_VENC_H264_ENTROPY_CONTROL 0x2005002 +#define HFI_PROPERTY_PARAM_VENC_H264_DEBLOCK_CONTROL 0x2005003 +#define HFI_PROPERTY_PARAM_VENC_RATE_CONTROL 0x2005004 +#define HFI_PROPERTY_PARAM_VENC_H264_PICORDER_CNT_TYPE 0x2005005 +#define HFI_PROPERTY_PARAM_VENC_SESSION_QP 0x2005006 +#define HFI_PROPERTY_PARAM_VENC_MPEG4_AC_PREDICTION 0x2005007 +#define HFI_PROPERTY_PARAM_VENC_SESSION_QP_RANGE 0x2005008 +#define HFI_PROPERTY_PARAM_VENC_MPEG4_TIME_RESOLUTION 0x2005009 +#define HFI_PROPERTY_PARAM_VENC_MPEG4_SHORT_HEADER 0x200500a +#define HFI_PROPERTY_PARAM_VENC_MPEG4_HEADER_EXTENSION 0x200500b +#define HFI_PROPERTY_PARAM_VENC_OPEN_GOP 0x200500c +#define HFI_PROPERTY_PARAM_VENC_INTRA_REFRESH 0x200500d +#define HFI_PROPERTY_PARAM_VENC_MULTI_SLICE_CONTROL 0x200500e +#define HFI_PROPERTY_PARAM_VENC_VBV_HRD_BUF_SIZE 0x200500f +#define HFI_PROPERTY_PARAM_VENC_QUALITY_VS_SPEED 0x2005010 +#define HFI_PROPERTY_PARAM_VENC_ADVANCED 0x2005012 +#define HFI_PROPERTY_PARAM_VENC_H264_SPS_ID 0x2005014 +#define HFI_PROPERTY_PARAM_VENC_H264_PPS_ID 0x2005015 +#define HFI_PROPERTY_PARAM_VENC_H264_GENERATE_AUDNAL 0x2005016 +#define HFI_PROPERTY_PARAM_VENC_ASPECT_RATIO 0x2005017 +#define HFI_PROPERTY_PARAM_VENC_NUMREF 0x2005018 +#define HFI_PROPERTY_PARAM_VENC_MULTIREF_P 0x2005019 +#define HFI_PROPERTY_PARAM_VENC_H264_NAL_SVC_EXT 0x200501b +#define HFI_PROPERTY_PARAM_VENC_LTRMODE 0x200501c +#define HFI_PROPERTY_PARAM_VENC_VIDEO_FULL_RANGE 0x200501d +#define HFI_PROPERTY_PARAM_VENC_H264_VUI_TIMING_INFO 0x200501e +#define HFI_PROPERTY_PARAM_VENC_VC1_PERF_CFG 0x200501f +#define HFI_PROPERTY_PARAM_VENC_MAX_NUM_B_FRAMES 0x2005020 +#define HFI_PROPERTY_PARAM_VENC_H264_VUI_BITSTREAM_RESTRC 0x2005021 +#define HFI_PROPERTY_PARAM_VENC_PRESERVE_TEXT_QUALITY 0x2005023 +#define HFI_PROPERTY_PARAM_VENC_HIER_P_MAX_NUM_ENH_LAYER 0x2005026 +#define HFI_PROPERTY_PARAM_VENC_DISABLE_RC_TIMESTAMP 0x2005027 +#define HFI_PROPERTY_PARAM_VENC_INITIAL_QP 0x2005028 +#define HFI_PROPERTY_PARAM_VENC_VPX_ERROR_RESILIENCE_MODE 0x2005029 +#define HFI_PROPERTY_PARAM_VENC_HIER_B_MAX_NUM_ENH_LAYER 0x200502c +#define HFI_PROPERTY_PARAM_VENC_HIER_P_HYBRID_MODE 0x200502f + +/* + * HFI_PROPERTY_CONFIG_VENC_COMMON_START + * HFI_DOMAIN_BASE_VENC + HFI_ARCH_COMMON_OFFSET + 0x6000 + */ +#define HFI_PROPERTY_CONFIG_VENC_TARGET_BITRATE 0x2006001 +#define HFI_PROPERTY_CONFIG_VENC_IDR_PERIOD 0x2006002 +#define HFI_PROPERTY_CONFIG_VENC_INTRA_PERIOD 0x2006003 +#define HFI_PROPERTY_CONFIG_VENC_REQUEST_SYNC_FRAME 0x2006004 +#define HFI_PROPERTY_CONFIG_VENC_SLICE_SIZE 0x2006005 +#define HFI_PROPERTY_CONFIG_VENC_MAX_BITRATE 0x2006007 +#define HFI_PROPERTY_CONFIG_VENC_SYNC_FRAME_SEQUENCE_HEADER 0x2006008 +#define HFI_PROPERTY_CONFIG_VENC_MARKLTRFRAME 0x2006009 +#define HFI_PROPERTY_CONFIG_VENC_USELTRFRAME 0x200600a +#define HFI_PROPERTY_CONFIG_VENC_HIER_P_ENH_LAYER 0x200600b +#define HFI_PROPERTY_CONFIG_VENC_LTRPERIOD 0x200600c +#define HFI_PROPERTY_CONFIG_VENC_PERF_MODE 0x200600e + +/* + * HFI_PROPERTY_PARAM_VPE_COMMON_START + * HFI_DOMAIN_BASE_VPE + HFI_ARCH_COMMON_OFFSET + 0x7000 + */ + +/* + * HFI_PROPERTY_CONFIG_VPE_COMMON_START + * HFI_DOMAIN_BASE_VPE + HFI_ARCH_COMMON_OFFSET + 0x8000 + */ +#define HFI_PROPERTY_CONFIG_VPE_DEINTERLACE 0x3008001 +#define HFI_PROPERTY_CONFIG_VPE_OPERATIONS 0x3008002 + +enum hfi_version { + HFI_VERSION_1XX, + HFI_VERSION_3XX, +}; + +struct hfi_buffer_info { + u32 buffer_addr; + u32 extradata_addr; +}; + +struct hfi_bitrate { + u32 bitrate; + u32 layer_id; +}; + +#define HFI_CAPABILITY_FRAME_WIDTH 0x01 +#define HFI_CAPABILITY_FRAME_HEIGHT 0x02 +#define HFI_CAPABILITY_MBS_PER_FRAME 0x03 +#define HFI_CAPABILITY_MBS_PER_SECOND 0x04 +#define HFI_CAPABILITY_FRAMERATE 0x05 +#define HFI_CAPABILITY_SCALE_X 0x06 +#define HFI_CAPABILITY_SCALE_Y 0x07 +#define HFI_CAPABILITY_BITRATE 0x08 +#define HFI_CAPABILITY_BFRAME 0x09 +#define HFI_CAPABILITY_PEAKBITRATE 0x0a +#define HFI_CAPABILITY_HIER_P_NUM_ENH_LAYERS 0x10 +#define HFI_CAPABILITY_ENC_LTR_COUNT 0x11 +#define HFI_CAPABILITY_CP_OUTPUT2_THRESH 0x12 +#define HFI_CAPABILITY_HIER_B_NUM_ENH_LAYERS 0x13 +#define HFI_CAPABILITY_LCU_SIZE 0x14 +#define HFI_CAPABILITY_HIER_P_HYBRID_NUM_ENH_LAYERS 0x15 +#define HFI_CAPABILITY_MBS_PER_SECOND_POWERSAVE 0x16 + +struct hfi_capability { + u32 capability_type; + u32 min; + u32 max; + u32 step_size; +}; + +struct hfi_capabilities { + u32 num_capabilities; + struct hfi_capability data[1]; +}; + +#define HFI_DEBUG_MSG_LOW 0x01 +#define HFI_DEBUG_MSG_MEDIUM 0x02 +#define HFI_DEBUG_MSG_HIGH 0x04 +#define HFI_DEBUG_MSG_ERROR 0x08 +#define HFI_DEBUG_MSG_FATAL 0x10 +#define HFI_DEBUG_MSG_PERF 0x20 + +#define HFI_DEBUG_MODE_QUEUE 0x01 +#define HFI_DEBUG_MODE_QDSS 0x02 + +struct hfi_debug_config { + u32 config; + u32 mode; +}; + +struct hfi_enable { + u32 enable; +}; + +#define HFI_H264_DB_MODE_DISABLE 0x1 +#define HFI_H264_DB_MODE_SKIP_SLICE_BOUNDARY 0x2 +#define HFI_H264_DB_MODE_ALL_BOUNDARY 0x3 + +struct hfi_h264_db_control { + u32 mode; + u32 slice_alpha_offset; + u32 slice_beta_offset; +}; + +#define HFI_H264_ENTROPY_CAVLC 0x1 +#define HFI_H264_ENTROPY_CABAC 0x2 + +#define HFI_H264_CABAC_MODEL_0 0x1 +#define HFI_H264_CABAC_MODEL_1 0x2 +#define HFI_H264_CABAC_MODEL_2 0x3 + +struct hfi_h264_entropy_control { + u32 entropy_mode; + u32 cabac_model; +}; + +struct hfi_framerate { + u32 buffer_type; + u32 framerate; +}; + +#define HFI_INTRA_REFRESH_NONE 0x1 +#define HFI_INTRA_REFRESH_CYCLIC 0x2 +#define HFI_INTRA_REFRESH_ADAPTIVE 0x3 +#define HFI_INTRA_REFRESH_CYCLIC_ADAPTIVE 0x4 +#define HFI_INTRA_REFRESH_RANDOM 0x5 + +struct hfi_intra_refresh { + u32 mode; + u32 air_mbs; + u32 air_ref; + u32 cir_mbs; +}; + +struct hfi_intra_refresh_3x { + u32 mode; + u32 mbs; +}; + +struct hfi_idr_period { + u32 idr_period; +}; + +struct hfi_operations_type { + u32 rotation; + u32 flip; +}; + +struct hfi_max_num_b_frames { + u32 max_num_b_frames; +}; + +struct hfi_vc1e_perf_cfg_type { + u32 search_range_x_subsampled[3]; + u32 search_range_y_subsampled[3]; +}; + +struct hfi_conceal_color { + u32 conceal_color; +}; + +struct hfi_intra_period { + u32 pframes; + u32 bframes; +}; + +struct hfi_mpeg4_header_extension { + u32 header_extension; +}; + +struct hfi_mpeg4_time_resolution { + u32 time_increment_resolution; +}; + +struct hfi_multi_stream { + u32 buffer_type; + u32 enable; + u32 width; + u32 height; +}; + +struct hfi_multi_stream_3x { + u32 buffer_type; + u32 enable; +}; + +struct hfi_multi_view_format { + u32 views; + u32 view_order[1]; +}; + +#define HFI_MULTI_SLICE_OFF 0x1 +#define HFI_MULTI_SLICE_BY_MB_COUNT 0x2 +#define HFI_MULTI_SLICE_BY_BYTE_COUNT 0x3 +#define HFI_MULTI_SLICE_GOB 0x4 + +struct hfi_multi_slice_control { + u32 multi_slice; + u32 slice_size; +}; + +#define HFI_NAL_FORMAT_STARTCODES 0x01 +#define HFI_NAL_FORMAT_ONE_NAL_PER_BUFFER 0x02 +#define HFI_NAL_FORMAT_ONE_BYTE_LENGTH 0x04 +#define HFI_NAL_FORMAT_TWO_BYTE_LENGTH 0x08 +#define HFI_NAL_FORMAT_FOUR_BYTE_LENGTH 0x10 + +struct hfi_nal_stream_format { + u32 format; +}; + +struct hfi_nal_stream_format_select { + u32 format; +}; + +#define HFI_PICTURE_TYPE_I 0x01 +#define HFI_PICTURE_TYPE_P 0x02 +#define HFI_PICTURE_TYPE_B 0x04 +#define HFI_PICTURE_TYPE_IDR 0x08 + +struct hfi_profile_level { + u32 profile; + u32 level; +}; + +#define HFI_MAX_PROFILE_COUNT 16 + +struct hfi_profile_level_supported { + u32 profile_count; + struct hfi_profile_level profile_level[1]; +}; + +struct hfi_quality_vs_speed { + u32 quality_vs_speed; +}; + +struct hfi_quantization { + u32 qp_i; + u32 qp_p; + u32 qp_b; + u32 layer_id; +}; + +struct hfi_initial_quantization { + u32 qp_i; + u32 qp_p; + u32 qp_b; + u32 init_qp_enable; +}; + +struct hfi_quantization_range { + u32 min_qp; + u32 max_qp; + u32 layer_id; +}; + +#define HFI_LTR_MODE_DISABLE 0x0 +#define HFI_LTR_MODE_MANUAL 0x1 +#define HFI_LTR_MODE_PERIODIC 0x2 + +struct hfi_ltr_mode { + u32 ltr_mode; + u32 ltr_count; + u32 trust_mode; +}; + +struct hfi_ltr_use { + u32 ref_ltr; + u32 use_constrnt; + u32 frames; +}; + +struct hfi_ltr_mark { + u32 mark_frame; +}; + +struct hfi_framesize { + u32 buffer_type; + u32 width; + u32 height; +}; + +struct hfi_h264_vui_timing_info { + u32 enable; + u32 fixed_framerate; + u32 time_scale; +}; + +#define HFI_COLOR_FORMAT_MONOCHROME 0x01 +#define HFI_COLOR_FORMAT_NV12 0x02 +#define HFI_COLOR_FORMAT_NV21 0x03 +#define HFI_COLOR_FORMAT_NV12_4x4TILE 0x04 +#define HFI_COLOR_FORMAT_NV21_4x4TILE 0x05 +#define HFI_COLOR_FORMAT_YUYV 0x06 +#define HFI_COLOR_FORMAT_YVYU 0x07 +#define HFI_COLOR_FORMAT_UYVY 0x08 +#define HFI_COLOR_FORMAT_VYUY 0x09 +#define HFI_COLOR_FORMAT_RGB565 0x0a +#define HFI_COLOR_FORMAT_BGR565 0x0b +#define HFI_COLOR_FORMAT_RGB888 0x0c +#define HFI_COLOR_FORMAT_BGR888 0x0d +#define HFI_COLOR_FORMAT_YUV444 0x0e +#define HFI_COLOR_FORMAT_RGBA8888 0x10 + +#define HFI_COLOR_FORMAT_UBWC_BASE 0x8000 +#define HFI_COLOR_FORMAT_10_BIT_BASE 0x4000 + +#define HFI_COLOR_FORMAT_YUV420_TP10 0x4002 +#define HFI_COLOR_FORMAT_NV12_UBWC 0x8002 +#define HFI_COLOR_FORMAT_YUV420_TP10_UBWC 0xc002 +#define HFI_COLOR_FORMAT_RGBA8888_UBWC 0x8010 + +struct hfi_uncompressed_format_select { + u32 buffer_type; + u32 format; +}; + +struct hfi_uncompressed_format_supported { + u32 buffer_type; + u32 format_entries; + u32 format_info[1]; +}; + +struct hfi_uncompressed_plane_actual { + int actual_stride; + u32 actual_plane_buffer_height; +}; + +struct hfi_uncompressed_plane_actual_info { + u32 buffer_type; + u32 num_planes; + struct hfi_uncompressed_plane_actual plane_format[1]; +}; + +struct hfi_uncompressed_plane_constraints { + u32 stride_multiples; + u32 max_stride; + u32 min_plane_buffer_height_multiple; + u32 buffer_alignment; +}; + +struct hfi_uncompressed_plane_info { + u32 format; + u32 num_planes; + struct hfi_uncompressed_plane_constraints plane_format[1]; +}; + +struct hfi_uncompressed_plane_actual_constraints_info { + u32 buffer_type; + u32 num_planes; + struct hfi_uncompressed_plane_constraints plane_format[1]; +}; + +struct hfi_codec_supported { + u32 dec_codecs; + u32 enc_codecs; +}; + +struct hfi_properties_supported { + u32 num_properties; + u32 properties[1]; +}; + +struct hfi_max_sessions_supported { + u32 max_sessions; +}; + +#define HFI_MAX_MATRIX_COEFFS 9 +#define HFI_MAX_BIAS_COEFFS 3 +#define HFI_MAX_LIMIT_COEFFS 6 + +struct hfi_vpe_color_space_conversion { + u32 csc_matrix[HFI_MAX_MATRIX_COEFFS]; + u32 csc_bias[HFI_MAX_BIAS_COEFFS]; + u32 csc_limit[HFI_MAX_LIMIT_COEFFS]; +}; + +#define HFI_ROTATE_NONE 0x1 +#define HFI_ROTATE_90 0x2 +#define HFI_ROTATE_180 0x3 +#define HFI_ROTATE_270 0x4 + +#define HFI_FLIP_NONE 0x1 +#define HFI_FLIP_HORIZONTAL 0x2 +#define HFI_FLIP_VERTICAL 0x3 + +struct hfi_operations { + u32 rotate; + u32 flip; +}; + +#define HFI_RESOURCE_OCMEM 0x1 + +struct hfi_resource_ocmem { + u32 size; + u32 mem; +}; + +struct hfi_resource_ocmem_requirement { + u32 session_domain; + u32 width; + u32 height; + u32 size; +}; + +struct hfi_resource_ocmem_requirement_info { + u32 num_entries; + struct hfi_resource_ocmem_requirement requirements[1]; +}; + +struct hfi_property_sys_image_version_info_type { + u32 string_size; + u8 str_image_version[1]; +}; + +struct hfi_codec_mask_supported { + u32 codecs; + u32 video_domains; +}; + +struct hfi_seq_header_info { + u32 max_hader_len; +}; + +struct hfi_aspect_ratio { + u32 aspect_width; + u32 aspect_height; +}; + +#define HFI_MVC_BUFFER_LAYOUT_TOP_BOTTOM 0 +#define HFI_MVC_BUFFER_LAYOUT_SIDEBYSIDE 1 +#define HFI_MVC_BUFFER_LAYOUT_SEQ 2 + +struct hfi_mvc_buffer_layout_descp_type { + u32 layout_type; + u32 bright_view_first; + u32 ngap; +}; + +struct hfi_scs_threshold { + u32 threshold_value; +}; + +#define HFI_TEST_SSR_SW_ERR_FATAL 0x1 +#define HFI_TEST_SSR_SW_DIV_BY_ZERO 0x2 +#define HFI_TEST_SSR_HW_WDOG_IRQ 0x3 + +struct hfi_buffer_alloc_mode { + u32 type; + u32 mode; +}; + +struct hfi_index_extradata_config { + u32 enable; + u32 index_extra_data_id; +}; + +struct hfi_extradata_header { + u32 size; + u32 version; + u32 port_index; + u32 type; + u32 data_size; + u8 data[1]; +}; + +struct hfi_batch_info { + u32 input_batch_count; + u32 output_batch_count; +}; + +struct hfi_buffer_count_actual { + u32 type; + u32 count_actual; +}; + +struct hfi_buffer_size_actual { + u32 type; + u32 size; +}; + +struct hfi_buffer_display_hold_count_actual { + u32 type; + u32 hold_count; +}; + +struct hfi_buffer_requirements { + u32 type; + u32 size; + u32 region_size; + u32 hold_count; + u32 count_min; + u32 count_actual; + u32 contiguous; + u32 alignment; +}; + +struct hfi_data_payload { + u32 size; + u8 data[1]; +}; + +struct hfi_enable_picture { + u32 picture_type; +}; + +struct hfi_display_picture_buffer_count { + int enable; + u32 count; +}; + +struct hfi_extra_data_header_config { + u32 type; + u32 buffer_type; + u32 version; + u32 port_index; + u32 client_extra_data_id; +}; + +struct hfi_interlace_format_supported { + u32 buffer_type; + u32 format; +}; + +struct hfi_buffer_alloc_mode_supported { + u32 buffer_type; + u32 num_entries; + u32 data[1]; +}; + +struct hfi_mb_error_map { + u32 error_map_size; + u8 error_map[1]; +}; + +struct hfi_metadata_pass_through { + int enable; + u32 size; +}; + +struct hfi_multi_view_select { + u32 view_index; +}; + +struct hfi_hybrid_hierp { + u32 layers; +}; + +struct hfi_pkt_hdr { + u32 size; + u32 pkt_type; +}; + +struct hfi_session_hdr_pkt { + struct hfi_pkt_hdr hdr; + u32 session_id; +}; + +struct hfi_session_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +#endif diff --git a/drivers/media/platform/qcom/venus/hfi_msgs.c b/drivers/media/platform/qcom/venus/hfi_msgs.c new file mode 100644 index 000000000000..f8841713e417 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_msgs.c @@ -0,0 +1,1052 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/hash.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <media/videobuf2-v4l2.h> + +#include "core.h" +#include "hfi.h" +#include "hfi_helper.h" +#include "hfi_msgs.h" + +static void event_seq_changed(struct venus_core *core, struct venus_inst *inst, + struct hfi_msg_event_notify_pkt *pkt) +{ + struct hfi_event_data event = {0}; + int num_properties_changed; + struct hfi_framesize *frame_sz; + struct hfi_profile_level *profile_level; + u8 *data_ptr; + u32 ptype; + + inst->error = HFI_ERR_NONE; + + switch (pkt->event_data1) { + case HFI_EVENT_DATA_SEQUENCE_CHANGED_SUFFICIENT_BUF_RESOURCES: + case HFI_EVENT_DATA_SEQUENCE_CHANGED_INSUFFICIENT_BUF_RESOURCES: + break; + default: + inst->error = HFI_ERR_SESSION_INVALID_PARAMETER; + goto done; + } + + event.event_type = pkt->event_data1; + + num_properties_changed = pkt->event_data2; + if (!num_properties_changed) { + inst->error = HFI_ERR_SESSION_INSUFFICIENT_RESOURCES; + goto done; + } + + data_ptr = (u8 *)&pkt->ext_event_data[0]; + do { + ptype = *((u32 *)data_ptr); + switch (ptype) { + case HFI_PROPERTY_PARAM_FRAME_SIZE: + data_ptr += sizeof(u32); + frame_sz = (struct hfi_framesize *)data_ptr; + event.width = frame_sz->width; + event.height = frame_sz->height; + data_ptr += sizeof(frame_sz); + break; + case HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT: + data_ptr += sizeof(u32); + profile_level = (struct hfi_profile_level *)data_ptr; + event.profile = profile_level->profile; + event.level = profile_level->level; + data_ptr += sizeof(profile_level); + break; + default: + break; + } + num_properties_changed--; + } while (num_properties_changed > 0); + +done: + inst->ops->event_notify(inst, EVT_SYS_EVENT_CHANGE, &event); +} + +static void event_release_buffer_ref(struct venus_core *core, + struct venus_inst *inst, + struct hfi_msg_event_notify_pkt *pkt) +{ + struct hfi_event_data event = {0}; + struct hfi_msg_event_release_buffer_ref_pkt *data; + + data = (struct hfi_msg_event_release_buffer_ref_pkt *) + pkt->ext_event_data; + + event.event_type = HFI_EVENT_RELEASE_BUFFER_REFERENCE; + event.packet_buffer = data->packet_buffer; + event.extradata_buffer = data->extradata_buffer; + event.tag = data->output_tag; + + inst->error = HFI_ERR_NONE; + inst->ops->event_notify(inst, EVT_SYS_EVENT_CHANGE, &event); +} + +static void event_sys_error(struct venus_core *core, u32 event, + struct hfi_msg_event_notify_pkt *pkt) +{ + if (pkt) + dev_dbg(core->dev, + "sys error (session id:%x, data1:%x, data2:%x)\n", + pkt->shdr.session_id, pkt->event_data1, + pkt->event_data2); + + core->core_ops->event_notify(core, event); +} + +static void +event_session_error(struct venus_core *core, struct venus_inst *inst, + struct hfi_msg_event_notify_pkt *pkt) +{ + struct device *dev = core->dev; + + dev_dbg(dev, "session error: event id:%x, session id:%x\n", + pkt->event_data1, pkt->shdr.session_id); + + if (!inst) + return; + + switch (pkt->event_data1) { + /* non fatal session errors */ + case HFI_ERR_SESSION_INVALID_SCALE_FACTOR: + case HFI_ERR_SESSION_UNSUPPORT_BUFFERTYPE: + case HFI_ERR_SESSION_UNSUPPORTED_SETTING: + case HFI_ERR_SESSION_UPSCALE_NOT_SUPPORTED: + inst->error = HFI_ERR_NONE; + break; + default: + dev_err(dev, "session error: event id:%x (%x), session id:%x\n", + pkt->event_data1, pkt->event_data2, + pkt->shdr.session_id); + + inst->error = pkt->event_data1; + inst->ops->event_notify(inst, EVT_SESSION_ERROR, NULL); + break; + } +} + +static void hfi_event_notify(struct venus_core *core, struct venus_inst *inst, + void *packet) +{ + struct hfi_msg_event_notify_pkt *pkt = packet; + + if (!packet) + return; + + switch (pkt->event_id) { + case HFI_EVENT_SYS_ERROR: + event_sys_error(core, EVT_SYS_ERROR, pkt); + break; + case HFI_EVENT_SESSION_ERROR: + event_session_error(core, inst, pkt); + break; + case HFI_EVENT_SESSION_SEQUENCE_CHANGED: + event_seq_changed(core, inst, pkt); + break; + case HFI_EVENT_RELEASE_BUFFER_REFERENCE: + event_release_buffer_ref(core, inst, pkt); + break; + case HFI_EVENT_SESSION_PROPERTY_CHANGED: + break; + default: + break; + } +} + +static void hfi_sys_init_done(struct venus_core *core, struct venus_inst *inst, + void *packet) +{ + struct hfi_msg_sys_init_done_pkt *pkt = packet; + u32 rem_bytes, read_bytes = 0, num_properties; + u32 error, ptype; + u8 *data; + + error = pkt->error_type; + if (error != HFI_ERR_NONE) + goto err_no_prop; + + num_properties = pkt->num_properties; + + if (!num_properties) { + error = HFI_ERR_SYS_INVALID_PARAMETER; + goto err_no_prop; + } + + rem_bytes = pkt->hdr.size - sizeof(*pkt) + sizeof(u32); + + if (!rem_bytes) { + /* missing property data */ + error = HFI_ERR_SYS_INSUFFICIENT_RESOURCES; + goto err_no_prop; + } + + data = (u8 *)&pkt->data[0]; + + if (core->res->hfi_version == HFI_VERSION_3XX) + goto err_no_prop; + + while (num_properties && rem_bytes >= sizeof(u32)) { + ptype = *((u32 *)data); + data += sizeof(u32); + + switch (ptype) { + case HFI_PROPERTY_PARAM_CODEC_SUPPORTED: { + struct hfi_codec_supported *prop; + + prop = (struct hfi_codec_supported *)data; + + if (rem_bytes < sizeof(*prop)) { + error = HFI_ERR_SYS_INSUFFICIENT_RESOURCES; + break; + } + + read_bytes += sizeof(*prop) + sizeof(u32); + core->dec_codecs = prop->dec_codecs; + core->enc_codecs = prop->enc_codecs; + break; + } + case HFI_PROPERTY_PARAM_MAX_SESSIONS_SUPPORTED: { + struct hfi_max_sessions_supported *prop; + + if (rem_bytes < sizeof(*prop)) { + error = HFI_ERR_SYS_INSUFFICIENT_RESOURCES; + break; + } + + prop = (struct hfi_max_sessions_supported *)data; + read_bytes += sizeof(*prop) + sizeof(u32); + core->max_sessions_supported = prop->max_sessions; + break; + } + default: + error = HFI_ERR_SYS_INVALID_PARAMETER; + break; + } + + if (!error) { + rem_bytes -= read_bytes; + data += read_bytes; + num_properties--; + } + } + +err_no_prop: + core->error = error; + complete(&core->done); +} + +static void +sys_get_prop_image_version(struct device *dev, + struct hfi_msg_sys_property_info_pkt *pkt) +{ + int req_bytes; + + req_bytes = pkt->hdr.size - sizeof(*pkt); + + if (req_bytes < 128 || !pkt->data[1] || pkt->num_properties > 1) + /* bad packet */ + return; + + dev_dbg(dev, "F/W version: %s\n", (u8 *)&pkt->data[1]); +} + +static void hfi_sys_property_info(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_sys_property_info_pkt *pkt = packet; + struct device *dev = core->dev; + + if (!pkt->num_properties) { + dev_dbg(dev, "%s: no properties\n", __func__); + return; + } + + switch (pkt->data[0]) { + case HFI_PROPERTY_SYS_IMAGE_VERSION: + sys_get_prop_image_version(dev, pkt); + break; + default: + dev_dbg(dev, "%s: unknown property data\n", __func__); + break; + } +} + +static void hfi_sys_rel_resource_done(struct venus_core *core, + struct venus_inst *inst, + void *packet) +{ + struct hfi_msg_sys_release_resource_done_pkt *pkt = packet; + + core->error = pkt->error_type; + complete(&core->done); +} + +static void hfi_sys_ping_done(struct venus_core *core, struct venus_inst *inst, + void *packet) +{ + struct hfi_msg_sys_ping_ack_pkt *pkt = packet; + + core->error = HFI_ERR_NONE; + + if (pkt->client_data != 0xbeef) + core->error = HFI_ERR_SYS_FATAL; + + complete(&core->done); +} + +static void hfi_sys_idle_done(struct venus_core *core, struct venus_inst *inst, + void *packet) +{ + dev_dbg(core->dev, "sys idle\n"); +} + +static void hfi_sys_pc_prepare_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_sys_pc_prep_done_pkt *pkt = packet; + + dev_dbg(core->dev, "pc prepare done (error %x)\n", pkt->error_type); +} + +static void +hfi_copy_cap_prop(struct hfi_capability *in, struct venus_inst *inst) +{ + if (!in || !inst) + return; + + switch (in->capability_type) { + case HFI_CAPABILITY_FRAME_WIDTH: + inst->cap_width = *in; + break; + case HFI_CAPABILITY_FRAME_HEIGHT: + inst->cap_height = *in; + break; + case HFI_CAPABILITY_MBS_PER_FRAME: + inst->cap_mbs_per_frame = *in; + break; + case HFI_CAPABILITY_MBS_PER_SECOND: + inst->cap_mbs_per_sec = *in; + break; + case HFI_CAPABILITY_FRAMERATE: + inst->cap_framerate = *in; + break; + case HFI_CAPABILITY_SCALE_X: + inst->cap_scale_x = *in; + break; + case HFI_CAPABILITY_SCALE_Y: + inst->cap_scale_y = *in; + break; + case HFI_CAPABILITY_BITRATE: + inst->cap_bitrate = *in; + break; + case HFI_CAPABILITY_HIER_P_NUM_ENH_LAYERS: + inst->cap_hier_p = *in; + break; + case HFI_CAPABILITY_ENC_LTR_COUNT: + inst->cap_ltr_count = *in; + break; + case HFI_CAPABILITY_CP_OUTPUT2_THRESH: + inst->cap_secure_output2_threshold = *in; + break; + default: + break; + } +} + +static unsigned int +session_get_prop_profile_level(struct hfi_msg_session_property_info_pkt *pkt, + struct hfi_profile_level *profile_level) +{ + struct hfi_profile_level *hfi; + u32 req_bytes; + + req_bytes = pkt->shdr.hdr.size - sizeof(*pkt); + + if (!req_bytes || req_bytes % sizeof(struct hfi_profile_level)) + /* bad packet */ + return HFI_ERR_SESSION_INVALID_PARAMETER; + + hfi = (struct hfi_profile_level *)&pkt->data[1]; + profile_level->profile = hfi->profile; + profile_level->level = hfi->level; + + return HFI_ERR_NONE; +} + +static unsigned int +session_get_prop_buf_req(struct hfi_msg_session_property_info_pkt *pkt, + struct hfi_buffer_requirements *bufreq) +{ + struct hfi_buffer_requirements *buf_req; + u32 req_bytes; + unsigned int idx = 0; + + req_bytes = pkt->shdr.hdr.size - sizeof(*pkt); + + if (!req_bytes || req_bytes % sizeof(*buf_req) || !pkt->data[1]) + /* bad packet */ + return HFI_ERR_SESSION_INVALID_PARAMETER; + + buf_req = (struct hfi_buffer_requirements *)&pkt->data[1]; + if (!buf_req) + return HFI_ERR_SESSION_INVALID_PARAMETER; + + while (req_bytes) { + memcpy(&bufreq[idx], buf_req, sizeof(*bufreq)); + idx++; + + if (idx > HFI_BUFFER_TYPE_MAX) + return HFI_ERR_SESSION_INVALID_PARAMETER; + + req_bytes -= sizeof(struct hfi_buffer_requirements); + buf_req++; + } + + return HFI_ERR_NONE; +} + +static void hfi_session_prop_info(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_property_info_pkt *pkt = packet; + struct device *dev = core->dev; + union hfi_get_property *hprop = &inst->hprop; + unsigned int error = HFI_ERR_NONE; + + if (!pkt->num_properties) { + error = HFI_ERR_SESSION_INVALID_PARAMETER; + dev_err(dev, "%s: no properties\n", __func__); + goto done; + } + + switch (pkt->data[0]) { + case HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS: + memset(hprop->bufreq, 0, sizeof(hprop->bufreq)); + error = session_get_prop_buf_req(pkt, hprop->bufreq); + break; + case HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT: + memset(&hprop->profile_level, 0, sizeof(hprop->profile_level)); + error = session_get_prop_profile_level(pkt, + &hprop->profile_level); + break; + case HFI_PROPERTY_CONFIG_VDEC_ENTROPY: + break; + default: + dev_dbg(dev, "%s: unknown property id:%x\n", __func__, + pkt->data[0]); + return; + } + +done: + inst->error = error; + complete(&inst->done); +} + +static u32 init_done_read_prop(struct venus_core *core, struct venus_inst *inst, + struct hfi_msg_session_init_done_pkt *pkt) +{ + struct device *dev = core->dev; + u32 rem_bytes, num_props; + u32 ptype, next_offset = 0; + u32 err; + u8 *data; + + rem_bytes = pkt->shdr.hdr.size - sizeof(*pkt) + sizeof(u32); + if (!rem_bytes) { + dev_err(dev, "%s: missing property info\n", __func__); + return HFI_ERR_SESSION_INSUFFICIENT_RESOURCES; + } + + err = pkt->error_type; + if (err) + return err; + + data = (u8 *)&pkt->data[0]; + num_props = pkt->num_properties; + + while (err == HFI_ERR_NONE && num_props && rem_bytes >= sizeof(u32)) { + ptype = *((u32 *)data); + next_offset = sizeof(u32); + + switch (ptype) { + case HFI_PROPERTY_PARAM_CODEC_MASK_SUPPORTED: { + struct hfi_codec_mask_supported *masks = + (struct hfi_codec_mask_supported *) + (data + next_offset); + + next_offset += sizeof(*masks); + num_props--; + break; + } + case HFI_PROPERTY_PARAM_CAPABILITY_SUPPORTED: { + struct hfi_capabilities *caps; + struct hfi_capability *cap; + u32 num_caps; + + if ((rem_bytes - next_offset) < sizeof(*cap)) { + err = HFI_ERR_SESSION_INVALID_PARAMETER; + break; + } + + caps = (struct hfi_capabilities *)(data + next_offset); + + num_caps = caps->num_capabilities; + cap = &caps->data[0]; + next_offset += sizeof(u32); + + while (num_caps && + (rem_bytes - next_offset) >= sizeof(u32)) { + hfi_copy_cap_prop(cap, inst); + cap++; + next_offset += sizeof(*cap); + num_caps--; + } + num_props--; + break; + } + case HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SUPPORTED: { + struct hfi_uncompressed_format_supported *prop = + (struct hfi_uncompressed_format_supported *) + (data + next_offset); + u32 num_fmt_entries; + u8 *fmt; + struct hfi_uncompressed_plane_info *inf; + + if ((rem_bytes - next_offset) < sizeof(*prop)) { + err = HFI_ERR_SESSION_INVALID_PARAMETER; + break; + } + + num_fmt_entries = prop->format_entries; + next_offset = sizeof(*prop) - sizeof(u32); + fmt = (u8 *)&prop->format_info[0]; + + dev_dbg(dev, "uncomm format support num entries:%u\n", + num_fmt_entries); + + while (num_fmt_entries) { + struct hfi_uncompressed_plane_constraints *cnts; + u32 bytes_to_skip; + + inf = (struct hfi_uncompressed_plane_info *)fmt; + + if ((rem_bytes - next_offset) < sizeof(*inf)) { + err = HFI_ERR_SESSION_INVALID_PARAMETER; + break; + } + + dev_dbg(dev, "plane info: fmt:%x, planes:%x\n", + inf->format, inf->num_planes); + + cnts = &inf->plane_format[0]; + dev_dbg(dev, "%u %u %u %u\n", + cnts->stride_multiples, + cnts->max_stride, + cnts->min_plane_buffer_height_multiple, + cnts->buffer_alignment); + + bytes_to_skip = sizeof(*inf) - sizeof(*cnts) + + inf->num_planes * sizeof(*cnts); + + fmt += bytes_to_skip; + next_offset += bytes_to_skip; + num_fmt_entries--; + } + num_props--; + break; + } + case HFI_PROPERTY_PARAM_PROPERTIES_SUPPORTED: { + struct hfi_properties_supported *prop = + (struct hfi_properties_supported *) + (data + next_offset); + + next_offset += sizeof(*prop) - sizeof(u32) + + prop->num_properties * sizeof(u32); + num_props--; + break; + } + case HFI_PROPERTY_PARAM_PROFILE_LEVEL_SUPPORTED: { + struct hfi_profile_level_supported *prop = + (struct hfi_profile_level_supported *) + (data + next_offset); + struct hfi_profile_level *pl; + unsigned int prop_count = 0; + unsigned int count = 0; + u8 *ptr; + + ptr = (u8 *)&prop->profile_level[0]; + prop_count = prop->profile_count; + + if (prop_count > HFI_MAX_PROFILE_COUNT) + prop_count = HFI_MAX_PROFILE_COUNT; + + while (prop_count) { + ptr++; + pl = (struct hfi_profile_level *)ptr; + + inst->pl[count].profile = pl->profile; + inst->pl[count].level = pl->level; + prop_count--; + count++; + ptr += sizeof(*pl) / sizeof(u32); + } + + inst->pl_count = count; + next_offset += sizeof(*prop) - sizeof(*pl) + + prop->profile_count * sizeof(*pl); + + num_props--; + break; + } + case HFI_PROPERTY_PARAM_INTERLACE_FORMAT_SUPPORTED: { + next_offset += + sizeof(struct hfi_interlace_format_supported); + num_props--; + break; + } + case HFI_PROPERTY_PARAM_NAL_STREAM_FORMAT_SUPPORTED: { + struct hfi_nal_stream_format *nal = + (struct hfi_nal_stream_format *) + (data + next_offset); + dev_dbg(dev, "NAL format: %x\n", nal->format); + next_offset += sizeof(*nal); + num_props--; + break; + } + case HFI_PROPERTY_PARAM_NAL_STREAM_FORMAT_SELECT: { + next_offset += sizeof(u32); + num_props--; + break; + } + case HFI_PROPERTY_PARAM_MAX_SEQUENCE_HEADER_SIZE: { + u32 *max_seq_sz = (u32 *)(data + next_offset); + + dev_dbg(dev, "max seq header sz: %x\n", *max_seq_sz); + next_offset += sizeof(u32); + num_props--; + break; + } + case HFI_PROPERTY_PARAM_VENC_INTRA_REFRESH: { + next_offset += sizeof(struct hfi_intra_refresh); + num_props--; + break; + } + case HFI_PROPERTY_PARAM_BUFFER_ALLOC_MODE_SUPPORTED: { + struct hfi_buffer_alloc_mode_supported *prop = + (struct hfi_buffer_alloc_mode_supported *) + (data + next_offset); + unsigned int i; + + for (i = 0; i < prop->num_entries; i++) { + if (prop->buffer_type == HFI_BUFFER_OUTPUT || + prop->buffer_type == HFI_BUFFER_OUTPUT2) { + switch (prop->data[i]) { + case HFI_BUFFER_MODE_STATIC: + inst->cap_bufs_mode_static = 1; + break; + case HFI_BUFFER_MODE_DYNAMIC: + inst->cap_bufs_mode_dynamic = 1; + break; + default: + break; + } + } + } + next_offset += sizeof(*prop) - + sizeof(u32) + prop->num_entries * sizeof(u32); + num_props--; + break; + } + default: + dev_dbg(dev, "%s: default case %#x\n", __func__, ptype); + break; + } + + rem_bytes -= next_offset; + data += next_offset; + } + + return err; +} + +static void hfi_session_init_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_init_done_pkt *pkt = packet; + unsigned int error; + + error = pkt->error_type; + if (error != HFI_ERR_NONE) + goto done; + + if (core->res->hfi_version != HFI_VERSION_1XX) + goto done; + + error = init_done_read_prop(core, inst, pkt); + +done: + inst->error = error; + complete(&inst->done); +} + +static void hfi_session_load_res_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_load_resources_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +static void hfi_session_flush_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_flush_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +static void hfi_session_etb_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_empty_buffer_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + inst->ops->buf_done(inst, HFI_BUFFER_INPUT, pkt->input_tag, + pkt->filled_len, pkt->offset, 0, 0, 0); +} + +static void hfi_session_ftb_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + u32 session_type = inst->session_type; + u64 timestamp_us = 0; + u32 timestamp_hi = 0, timestamp_lo = 0; + unsigned int error; + u32 flags = 0, hfi_flags = 0, offset = 0, filled_len = 0; + u32 pic_type = 0, buffer_type = 0, output_tag = -1; + + if (session_type == VIDC_SESSION_TYPE_ENC) { + struct hfi_msg_session_fbd_compressed_pkt *pkt = packet; + + timestamp_hi = pkt->time_stamp_hi; + timestamp_lo = pkt->time_stamp_lo; + hfi_flags = pkt->flags; + offset = pkt->offset; + filled_len = pkt->filled_len; + pic_type = pkt->picture_type; + output_tag = pkt->output_tag; + buffer_type = HFI_BUFFER_OUTPUT; + + error = pkt->error_type; + } else if (session_type == VIDC_SESSION_TYPE_DEC) { + struct hfi_msg_session_fbd_uncompressed_plane0_pkt *pkt = + packet; + + timestamp_hi = pkt->time_stamp_hi; + timestamp_lo = pkt->time_stamp_lo; + hfi_flags = pkt->flags; + offset = pkt->offset; + filled_len = pkt->filled_len; + pic_type = pkt->picture_type; + output_tag = pkt->output_tag; + + if (pkt->stream_id == 0) + buffer_type = HFI_BUFFER_OUTPUT; + else if (pkt->stream_id == 1) + buffer_type = HFI_BUFFER_OUTPUT2; + + error = pkt->error_type; + } else { + error = HFI_ERR_SESSION_INVALID_PARAMETER; + } + + if (buffer_type != HFI_BUFFER_OUTPUT) + goto done; + + if (hfi_flags & HFI_BUFFERFLAG_EOS) + flags |= V4L2_BUF_FLAG_LAST; + + switch (pic_type) { + case HFI_PICTURE_IDR: + case HFI_PICTURE_I: + flags |= V4L2_BUF_FLAG_KEYFRAME; + break; + case HFI_PICTURE_P: + flags |= V4L2_BUF_FLAG_PFRAME; + break; + case HFI_PICTURE_B: + flags |= V4L2_BUF_FLAG_BFRAME; + break; + case HFI_FRAME_NOTCODED: + case HFI_UNUSED_PICT: + case HFI_FRAME_YUV: + default: + break; + } + + if (!(hfi_flags & HFI_BUFFERFLAG_TIMESTAMPINVALID) && filled_len) { + timestamp_us = timestamp_hi; + timestamp_us = (timestamp_us << 32) | timestamp_lo; + } + +done: + inst->error = error; + inst->ops->buf_done(inst, buffer_type, output_tag, filled_len, + offset, flags, hfi_flags, timestamp_us); +} + +static void hfi_session_start_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_start_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +static void hfi_session_stop_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_stop_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +static void hfi_session_rel_res_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_release_resources_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +static void hfi_session_rel_buf_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_release_buffers_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +static void hfi_session_end_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_end_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +static void hfi_session_abort_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_sys_session_abort_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +static void hfi_session_get_seq_hdr_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_get_sequence_hdr_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +struct hfi_done_handler { + u32 pkt; + u32 pkt_sz; + u32 pkt_sz2; + void (*done)(struct venus_core *, struct venus_inst *, void *); + bool is_sys_pkt; +}; + +static const struct hfi_done_handler handlers[] = { + {.pkt = HFI_MSG_EVENT_NOTIFY, + .pkt_sz = sizeof(struct hfi_msg_event_notify_pkt), + .done = hfi_event_notify, + }, + {.pkt = HFI_MSG_SYS_INIT, + .pkt_sz = sizeof(struct hfi_msg_sys_init_done_pkt), + .done = hfi_sys_init_done, + .is_sys_pkt = true, + }, + {.pkt = HFI_MSG_SYS_PROPERTY_INFO, + .pkt_sz = sizeof(struct hfi_msg_sys_property_info_pkt), + .done = hfi_sys_property_info, + .is_sys_pkt = true, + }, + {.pkt = HFI_MSG_SYS_RELEASE_RESOURCE, + .pkt_sz = sizeof(struct hfi_msg_sys_release_resource_done_pkt), + .done = hfi_sys_rel_resource_done, + .is_sys_pkt = true, + }, + {.pkt = HFI_MSG_SYS_PING_ACK, + .pkt_sz = sizeof(struct hfi_msg_sys_ping_ack_pkt), + .done = hfi_sys_ping_done, + .is_sys_pkt = true, + }, + {.pkt = HFI_MSG_SYS_IDLE, + .pkt_sz = sizeof(struct hfi_msg_sys_idle_pkt), + .done = hfi_sys_idle_done, + .is_sys_pkt = true, + }, + {.pkt = HFI_MSG_SYS_PC_PREP, + .pkt_sz = sizeof(struct hfi_msg_sys_pc_prep_done_pkt), + .done = hfi_sys_pc_prepare_done, + .is_sys_pkt = true, + }, + {.pkt = HFI_MSG_SYS_SESSION_INIT, + .pkt_sz = sizeof(struct hfi_msg_session_init_done_pkt), + .done = hfi_session_init_done, + }, + {.pkt = HFI_MSG_SYS_SESSION_END, + .pkt_sz = sizeof(struct hfi_msg_session_end_done_pkt), + .done = hfi_session_end_done, + }, + {.pkt = HFI_MSG_SESSION_LOAD_RESOURCES, + .pkt_sz = sizeof(struct hfi_msg_session_load_resources_done_pkt), + .done = hfi_session_load_res_done, + }, + {.pkt = HFI_MSG_SESSION_START, + .pkt_sz = sizeof(struct hfi_msg_session_start_done_pkt), + .done = hfi_session_start_done, + }, + {.pkt = HFI_MSG_SESSION_STOP, + .pkt_sz = sizeof(struct hfi_msg_session_stop_done_pkt), + .done = hfi_session_stop_done, + }, + {.pkt = HFI_MSG_SYS_SESSION_ABORT, + .pkt_sz = sizeof(struct hfi_msg_sys_session_abort_done_pkt), + .done = hfi_session_abort_done, + }, + {.pkt = HFI_MSG_SESSION_EMPTY_BUFFER, + .pkt_sz = sizeof(struct hfi_msg_session_empty_buffer_done_pkt), + .done = hfi_session_etb_done, + }, + {.pkt = HFI_MSG_SESSION_FILL_BUFFER, + .pkt_sz = sizeof(struct hfi_msg_session_fbd_uncompressed_plane0_pkt), + .pkt_sz2 = sizeof(struct hfi_msg_session_fbd_compressed_pkt), + .done = hfi_session_ftb_done, + }, + {.pkt = HFI_MSG_SESSION_FLUSH, + .pkt_sz = sizeof(struct hfi_msg_session_flush_done_pkt), + .done = hfi_session_flush_done, + }, + {.pkt = HFI_MSG_SESSION_PROPERTY_INFO, + .pkt_sz = sizeof(struct hfi_msg_session_property_info_pkt), + .done = hfi_session_prop_info, + }, + {.pkt = HFI_MSG_SESSION_RELEASE_RESOURCES, + .pkt_sz = sizeof(struct hfi_msg_session_release_resources_done_pkt), + .done = hfi_session_rel_res_done, + }, + {.pkt = HFI_MSG_SESSION_GET_SEQUENCE_HEADER, + .pkt_sz = sizeof(struct hfi_msg_session_get_sequence_hdr_done_pkt), + .done = hfi_session_get_seq_hdr_done, + }, + {.pkt = HFI_MSG_SESSION_RELEASE_BUFFERS, + .pkt_sz = sizeof(struct hfi_msg_session_release_buffers_done_pkt), + .done = hfi_session_rel_buf_done, + }, +}; + +void hfi_process_watchdog_timeout(struct venus_core *core) +{ + event_sys_error(core, EVT_SYS_WATCHDOG_TIMEOUT, NULL); +} + +static struct venus_inst *to_instance(struct venus_core *core, u32 session_id) +{ + struct venus_inst *inst; + + mutex_lock(&core->lock); + list_for_each_entry(inst, &core->instances, list) + if (hash32_ptr(inst) == session_id) { + mutex_unlock(&core->lock); + return inst; + } + mutex_unlock(&core->lock); + + return NULL; +} + +u32 hfi_process_msg_packet(struct venus_core *core, struct hfi_pkt_hdr *hdr) +{ + const struct hfi_done_handler *handler; + struct device *dev = core->dev; + struct venus_inst *inst; + bool found = false; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(handlers); i++) { + handler = &handlers[i]; + if (handler->pkt != hdr->pkt_type) + continue; + found = true; + break; + } + + if (!found) + return hdr->pkt_type; + + if (hdr->size && hdr->size < handler->pkt_sz && + hdr->size < handler->pkt_sz2) { + dev_err(dev, "bad packet size (%d should be %d, pkt type:%x)\n", + hdr->size, handler->pkt_sz, hdr->pkt_type); + + return hdr->pkt_type; + } + + if (handler->is_sys_pkt) { + inst = NULL; + } else { + struct hfi_session_pkt *pkt; + + pkt = (struct hfi_session_pkt *)hdr; + inst = to_instance(core, pkt->shdr.session_id); + + if (!inst) + dev_warn(dev, "no valid instance(pkt session_id:%x, pkt:%x)\n", + pkt->shdr.session_id, + handler ? handler->pkt : 0); + + /* + * Event of type HFI_EVENT_SYS_ERROR will not have any session + * associated with it + */ + if (!inst && hdr->pkt_type != HFI_MSG_EVENT_NOTIFY) { + dev_err(dev, "got invalid session id:%x\n", + pkt->shdr.session_id); + goto invalid_session; + } + } + + handler->done(core, inst, hdr); + +invalid_session: + return hdr->pkt_type; +} diff --git a/drivers/media/platform/qcom/venus/hfi_msgs.h b/drivers/media/platform/qcom/venus/hfi_msgs.h new file mode 100644 index 000000000000..14d9a3979b14 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_msgs.h @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __VENUS_HFI_MSGS_H__ +#define __VENUS_HFI_MSGS_H__ + +/* message calls */ +#define HFI_MSG_SYS_INIT 0x20001 +#define HFI_MSG_SYS_PC_PREP 0x20002 +#define HFI_MSG_SYS_RELEASE_RESOURCE 0x20003 +#define HFI_MSG_SYS_DEBUG 0x20004 +#define HFI_MSG_SYS_SESSION_INIT 0x20006 +#define HFI_MSG_SYS_SESSION_END 0x20007 +#define HFI_MSG_SYS_IDLE 0x20008 +#define HFI_MSG_SYS_COV 0x20009 +#define HFI_MSG_SYS_PROPERTY_INFO 0x2000a + +#define HFI_MSG_EVENT_NOTIFY 0x21001 +#define HFI_MSG_SESSION_GET_SEQUENCE_HEADER 0x21002 + +#define HFI_MSG_SYS_PING_ACK 0x220002 +#define HFI_MSG_SYS_SESSION_ABORT 0x220004 + +#define HFI_MSG_SESSION_LOAD_RESOURCES 0x221001 +#define HFI_MSG_SESSION_START 0x221002 +#define HFI_MSG_SESSION_STOP 0x221003 +#define HFI_MSG_SESSION_SUSPEND 0x221004 +#define HFI_MSG_SESSION_RESUME 0x221005 +#define HFI_MSG_SESSION_FLUSH 0x221006 +#define HFI_MSG_SESSION_EMPTY_BUFFER 0x221007 +#define HFI_MSG_SESSION_FILL_BUFFER 0x221008 +#define HFI_MSG_SESSION_PROPERTY_INFO 0x221009 +#define HFI_MSG_SESSION_RELEASE_RESOURCES 0x22100a +#define HFI_MSG_SESSION_PARSE_SEQUENCE_HEADER 0x22100b +#define HFI_MSG_SESSION_RELEASE_BUFFERS 0x22100c + +#define HFI_PICTURE_I 0x00000001 +#define HFI_PICTURE_P 0x00000002 +#define HFI_PICTURE_B 0x00000004 +#define HFI_PICTURE_IDR 0x00000008 +#define HFI_FRAME_NOTCODED 0x7f002000 +#define HFI_FRAME_YUV 0x7f004000 +#define HFI_UNUSED_PICT 0x10000000 + +/* message packets */ +struct hfi_msg_event_notify_pkt { + struct hfi_session_hdr_pkt shdr; + u32 event_id; + u32 event_data1; + u32 event_data2; + u32 ext_event_data[1]; +}; + +struct hfi_msg_event_release_buffer_ref_pkt { + u32 packet_buffer; + u32 extradata_buffer; + u32 output_tag; +}; + +struct hfi_msg_sys_init_done_pkt { + struct hfi_pkt_hdr hdr; + u32 error_type; + u32 num_properties; + u32 data[1]; +}; + +struct hfi_msg_sys_pc_prep_done_pkt { + struct hfi_pkt_hdr hdr; + u32 error_type; +}; + +struct hfi_msg_sys_release_resource_done_pkt { + struct hfi_pkt_hdr hdr; + u32 resource_handle; + u32 error_type; +}; + +struct hfi_msg_session_init_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; + u32 num_properties; + u32 data[1]; +}; + +struct hfi_msg_session_end_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_session_get_sequence_hdr_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; + u32 header_len; + u32 sequence_header; +}; + +struct hfi_msg_sys_session_abort_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_sys_idle_pkt { + struct hfi_pkt_hdr hdr; +}; + +struct hfi_msg_sys_ping_ack_pkt { + struct hfi_pkt_hdr hdr; + u32 client_data; +}; + +struct hfi_msg_sys_property_info_pkt { + struct hfi_pkt_hdr hdr; + u32 num_properties; + u32 data[1]; +}; + +struct hfi_msg_session_load_resources_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_session_start_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_session_stop_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_session_suspend_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_session_resume_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_session_flush_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; + u32 flush_type; +}; + +struct hfi_msg_session_empty_buffer_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; + u32 offset; + u32 filled_len; + u32 input_tag; + u32 packet_buffer; + u32 extradata_buffer; + u32 data[0]; +}; + +struct hfi_msg_session_fbd_compressed_pkt { + struct hfi_session_hdr_pkt shdr; + u32 time_stamp_hi; + u32 time_stamp_lo; + u32 error_type; + u32 flags; + u32 mark_target; + u32 mark_data; + u32 stats; + u32 offset; + u32 alloc_len; + u32 filled_len; + u32 input_tag; + u32 output_tag; + u32 picture_type; + u32 packet_buffer; + u32 extradata_buffer; + u32 data[0]; +}; + +struct hfi_msg_session_fbd_uncompressed_plane0_pkt { + struct hfi_session_hdr_pkt shdr; + u32 stream_id; + u32 view_id; + u32 error_type; + u32 time_stamp_hi; + u32 time_stamp_lo; + u32 flags; + u32 mark_target; + u32 mark_data; + u32 stats; + u32 alloc_len; + u32 filled_len; + u32 offset; + u32 frame_width; + u32 frame_height; + u32 start_x_coord; + u32 start_y_coord; + u32 input_tag; + u32 input_tag2; + u32 output_tag; + u32 picture_type; + u32 packet_buffer; + u32 extradata_buffer; + u32 data[0]; +}; + +struct hfi_msg_session_fbd_uncompressed_plane1_pkt { + u32 flags; + u32 alloc_len; + u32 filled_len; + u32 offset; + u32 packet_buffer2; + u32 data[0]; +}; + +struct hfi_msg_session_fbd_uncompressed_plane2_pkt { + u32 flags; + u32 alloc_len; + u32 filled_len; + u32 offset; + u32 packet_buffer3; + u32 data[0]; +}; + +struct hfi_msg_session_parse_sequence_header_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; + u32 num_properties; + u32 data[1]; +}; + +struct hfi_msg_session_property_info_pkt { + struct hfi_session_hdr_pkt shdr; + u32 num_properties; + u32 data[1]; +}; + +struct hfi_msg_session_release_resources_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_session_release_buffers_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; + u32 num_buffers; + u32 buffer_info[1]; +}; + +struct hfi_msg_sys_debug_pkt { + struct hfi_pkt_hdr hdr; + u32 msg_type; + u32 msg_size; + u32 time_stamp_hi; + u32 time_stamp_lo; + u8 msg_data[1]; +}; + +struct hfi_msg_sys_coverage_pkt { + struct hfi_pkt_hdr hdr; + u32 msg_size; + u32 time_stamp_hi; + u32 time_stamp_lo; + u8 msg_data[1]; +}; + +struct venus_core; +struct hfi_pkt_hdr; + +void hfi_process_watchdog_timeout(struct venus_core *core); +u32 hfi_process_msg_packet(struct venus_core *core, struct hfi_pkt_hdr *hdr); + +#endif diff --git a/drivers/media/platform/qcom/venus/hfi_venus.c b/drivers/media/platform/qcom/venus/hfi_venus.c new file mode 100644 index 000000000000..1caae8feaa36 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_venus.c @@ -0,0 +1,1572 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/qcom_scm.h> +#include <linux/slab.h> + +#include "core.h" +#include "hfi_cmds.h" +#include "hfi_msgs.h" +#include "hfi_venus.h" +#include "hfi_venus_io.h" + +#define HFI_MASK_QHDR_TX_TYPE 0xff000000 +#define HFI_MASK_QHDR_RX_TYPE 0x00ff0000 +#define HFI_MASK_QHDR_PRI_TYPE 0x0000ff00 +#define HFI_MASK_QHDR_ID_TYPE 0x000000ff + +#define HFI_HOST_TO_CTRL_CMD_Q 0 +#define HFI_CTRL_TO_HOST_MSG_Q 1 +#define HFI_CTRL_TO_HOST_DBG_Q 2 +#define HFI_MASK_QHDR_STATUS 0x000000ff + +#define IFACEQ_NUM 3 +#define IFACEQ_CMD_IDX 0 +#define IFACEQ_MSG_IDX 1 +#define IFACEQ_DBG_IDX 2 +#define IFACEQ_MAX_BUF_COUNT 50 +#define IFACEQ_MAX_PARALLEL_CLNTS 16 +#define IFACEQ_DFLT_QHDR 0x01010000 + +#define POLL_INTERVAL_US 50 + +#define IFACEQ_MAX_PKT_SIZE 1024 +#define IFACEQ_MED_PKT_SIZE 768 +#define IFACEQ_MIN_PKT_SIZE 8 +#define IFACEQ_VAR_SMALL_PKT_SIZE 100 +#define IFACEQ_VAR_LARGE_PKT_SIZE 512 +#define IFACEQ_VAR_HUGE_PKT_SIZE (1024 * 12) + +enum tzbsp_video_state { + TZBSP_VIDEO_STATE_SUSPEND = 0, + TZBSP_VIDEO_STATE_RESUME +}; + +struct hfi_queue_table_header { + u32 version; + u32 size; + u32 qhdr0_offset; + u32 qhdr_size; + u32 num_q; + u32 num_active_q; +}; + +struct hfi_queue_header { + u32 status; + u32 start_addr; + u32 type; + u32 q_size; + u32 pkt_size; + u32 pkt_drop_cnt; + u32 rx_wm; + u32 tx_wm; + u32 rx_req; + u32 tx_req; + u32 rx_irq_status; + u32 tx_irq_status; + u32 read_idx; + u32 write_idx; +}; + +#define IFACEQ_TABLE_SIZE \ + (sizeof(struct hfi_queue_table_header) + \ + sizeof(struct hfi_queue_header) * IFACEQ_NUM) + +#define IFACEQ_QUEUE_SIZE (IFACEQ_MAX_PKT_SIZE * \ + IFACEQ_MAX_BUF_COUNT * IFACEQ_MAX_PARALLEL_CLNTS) + +#define IFACEQ_GET_QHDR_START_ADDR(ptr, i) \ + (void *)(((ptr) + sizeof(struct hfi_queue_table_header)) + \ + ((i) * sizeof(struct hfi_queue_header))) + +#define QDSS_SIZE SZ_4K +#define SFR_SIZE SZ_4K +#define QUEUE_SIZE \ + (IFACEQ_TABLE_SIZE + (IFACEQ_QUEUE_SIZE * IFACEQ_NUM)) + +#define ALIGNED_QDSS_SIZE ALIGN(QDSS_SIZE, SZ_4K) +#define ALIGNED_SFR_SIZE ALIGN(SFR_SIZE, SZ_4K) +#define ALIGNED_QUEUE_SIZE ALIGN(QUEUE_SIZE, SZ_4K) +#define SHARED_QSIZE ALIGN(ALIGNED_SFR_SIZE + ALIGNED_QUEUE_SIZE + \ + ALIGNED_QDSS_SIZE, SZ_1M) + +struct mem_desc { + dma_addr_t da; /* device address */ + void *kva; /* kernel virtual address */ + u32 size; + unsigned long attrs; +}; + +struct iface_queue { + struct hfi_queue_header *qhdr; + struct mem_desc qmem; +}; + +enum venus_state { + VENUS_STATE_DEINIT = 1, + VENUS_STATE_INIT, +}; + +struct venus_hfi_device { + struct venus_core *core; + u32 irq_status; + u32 last_packet_type; + bool power_enabled; + bool suspended; + enum venus_state state; + /* serialize read / write to the shared memory */ + struct mutex lock; + struct completion pwr_collapse_prep; + struct completion release_resource; + struct mem_desc ifaceq_table; + struct mem_desc sfr; + struct iface_queue queues[IFACEQ_NUM]; + u8 pkt_buf[IFACEQ_VAR_HUGE_PKT_SIZE]; + u8 dbg_buf[IFACEQ_VAR_HUGE_PKT_SIZE]; +}; + +static bool venus_pkt_debug; +static int venus_fw_debug = HFI_DEBUG_MSG_ERROR | HFI_DEBUG_MSG_FATAL; +static bool venus_sys_idle_indicator; +static bool venus_fw_low_power_mode = true; +static int venus_hw_rsp_timeout = 1000; +static bool venus_fw_coverage; + +static void venus_set_state(struct venus_hfi_device *hdev, + enum venus_state state) +{ + mutex_lock(&hdev->lock); + hdev->state = state; + mutex_unlock(&hdev->lock); +} + +static bool venus_is_valid_state(struct venus_hfi_device *hdev) +{ + return hdev->state != VENUS_STATE_DEINIT; +} + +static void venus_dump_packet(struct venus_hfi_device *hdev, const void *packet) +{ + size_t pkt_size = *(u32 *)packet; + + if (!venus_pkt_debug) + return; + + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 16, 1, packet, + pkt_size, true); +} + +static int venus_write_queue(struct venus_hfi_device *hdev, + struct iface_queue *queue, + void *packet, u32 *rx_req) +{ + struct hfi_queue_header *qhdr; + u32 dwords, new_wr_idx; + u32 empty_space, rd_idx, wr_idx, qsize; + u32 *wr_ptr; + + if (!queue->qmem.kva) + return -EINVAL; + + qhdr = queue->qhdr; + if (!qhdr) + return -EINVAL; + + venus_dump_packet(hdev, packet); + + dwords = (*(u32 *)packet) >> 2; + if (!dwords) + return -EINVAL; + + rd_idx = qhdr->read_idx; + wr_idx = qhdr->write_idx; + qsize = qhdr->q_size; + /* ensure rd/wr indices's are read from memory */ + rmb(); + + if (wr_idx >= rd_idx) + empty_space = qsize - (wr_idx - rd_idx); + else + empty_space = rd_idx - wr_idx; + + if (empty_space <= dwords) { + qhdr->tx_req = 1; + /* ensure tx_req is updated in memory */ + wmb(); + return -ENOSPC; + } + + qhdr->tx_req = 0; + /* ensure tx_req is updated in memory */ + wmb(); + + new_wr_idx = wr_idx + dwords; + wr_ptr = (u32 *)(queue->qmem.kva + (wr_idx << 2)); + if (new_wr_idx < qsize) { + memcpy(wr_ptr, packet, dwords << 2); + } else { + size_t len; + + new_wr_idx -= qsize; + len = (dwords - new_wr_idx) << 2; + memcpy(wr_ptr, packet, len); + memcpy(queue->qmem.kva, packet + len, new_wr_idx << 2); + } + + /* make sure packet is written before updating the write index */ + wmb(); + + qhdr->write_idx = new_wr_idx; + *rx_req = qhdr->rx_req ? 1 : 0; + + /* make sure write index is updated before an interrupt is raised */ + mb(); + + return 0; +} + +static int venus_read_queue(struct venus_hfi_device *hdev, + struct iface_queue *queue, void *pkt, u32 *tx_req) +{ + struct hfi_queue_header *qhdr; + u32 dwords, new_rd_idx; + u32 rd_idx, wr_idx, type, qsize; + u32 *rd_ptr; + u32 recv_request = 0; + int ret = 0; + + if (!queue->qmem.kva) + return -EINVAL; + + qhdr = queue->qhdr; + if (!qhdr) + return -EINVAL; + + type = qhdr->type; + rd_idx = qhdr->read_idx; + wr_idx = qhdr->write_idx; + qsize = qhdr->q_size; + + /* make sure data is valid before using it */ + rmb(); + + /* + * Do not set receive request for debug queue, if set, Venus generates + * interrupt for debug messages even when there is no response message + * available. In general debug queue will not become full as it is being + * emptied out for every interrupt from Venus. Venus will anyway + * generates interrupt if it is full. + */ + if (type & HFI_CTRL_TO_HOST_MSG_Q) + recv_request = 1; + + if (rd_idx == wr_idx) { + qhdr->rx_req = recv_request; + *tx_req = 0; + /* update rx_req field in memory */ + wmb(); + return -ENODATA; + } + + rd_ptr = (u32 *)(queue->qmem.kva + (rd_idx << 2)); + dwords = *rd_ptr >> 2; + if (!dwords) + return -EINVAL; + + new_rd_idx = rd_idx + dwords; + if (((dwords << 2) <= IFACEQ_VAR_HUGE_PKT_SIZE) && rd_idx <= qsize) { + if (new_rd_idx < qsize) { + memcpy(pkt, rd_ptr, dwords << 2); + } else { + size_t len; + + new_rd_idx -= qsize; + len = (dwords - new_rd_idx) << 2; + memcpy(pkt, rd_ptr, len); + memcpy(pkt + len, queue->qmem.kva, new_rd_idx << 2); + } + } else { + /* bad packet received, dropping */ + new_rd_idx = qhdr->write_idx; + ret = -EBADMSG; + } + + /* ensure the packet is read before updating read index */ + rmb(); + + qhdr->read_idx = new_rd_idx; + /* ensure updating read index */ + wmb(); + + rd_idx = qhdr->read_idx; + wr_idx = qhdr->write_idx; + /* ensure rd/wr indices are read from memory */ + rmb(); + + if (rd_idx != wr_idx) + qhdr->rx_req = 0; + else + qhdr->rx_req = recv_request; + + *tx_req = qhdr->tx_req ? 1 : 0; + + /* ensure rx_req is stored to memory and tx_req is loaded from memory */ + mb(); + + venus_dump_packet(hdev, pkt); + + return ret; +} + +static int venus_alloc(struct venus_hfi_device *hdev, struct mem_desc *desc, + u32 size) +{ + struct device *dev = hdev->core->dev; + + desc->attrs = DMA_ATTR_WRITE_COMBINE; + desc->size = ALIGN(size, SZ_4K); + + desc->kva = dma_alloc_attrs(dev, size, &desc->da, GFP_KERNEL, + desc->attrs); + if (!desc->kva) + return -ENOMEM; + + return 0; +} + +static void venus_free(struct venus_hfi_device *hdev, struct mem_desc *mem) +{ + struct device *dev = hdev->core->dev; + + dma_free_attrs(dev, mem->size, mem->kva, mem->da, mem->attrs); +} + +static void venus_writel(struct venus_hfi_device *hdev, u32 reg, u32 value) +{ + writel(value, hdev->core->base + reg); +} + +static u32 venus_readl(struct venus_hfi_device *hdev, u32 reg) +{ + return readl(hdev->core->base + reg); +} + +static void venus_set_registers(struct venus_hfi_device *hdev) +{ + const struct venus_resources *res = hdev->core->res; + const struct reg_val *tbl = res->reg_tbl; + unsigned int count = res->reg_tbl_size; + unsigned int i; + + for (i = 0; i < count; i++) + venus_writel(hdev, tbl[i].reg, tbl[i].value); +} + +static void venus_soft_int(struct venus_hfi_device *hdev) +{ + venus_writel(hdev, CPU_IC_SOFTINT, BIT(CPU_IC_SOFTINT_H2A_SHIFT)); +} + +static int venus_iface_cmdq_write_nolock(struct venus_hfi_device *hdev, + void *pkt) +{ + struct device *dev = hdev->core->dev; + struct hfi_pkt_hdr *cmd_packet; + struct iface_queue *queue; + u32 rx_req; + int ret; + + if (!venus_is_valid_state(hdev)) + return -EINVAL; + + cmd_packet = (struct hfi_pkt_hdr *)pkt; + hdev->last_packet_type = cmd_packet->pkt_type; + + queue = &hdev->queues[IFACEQ_CMD_IDX]; + + ret = venus_write_queue(hdev, queue, pkt, &rx_req); + if (ret) { + dev_err(dev, "write to iface cmd queue failed (%d)\n", ret); + return ret; + } + + if (rx_req) + venus_soft_int(hdev); + + return 0; +} + +static int venus_iface_cmdq_write(struct venus_hfi_device *hdev, void *pkt) +{ + int ret; + + mutex_lock(&hdev->lock); + ret = venus_iface_cmdq_write_nolock(hdev, pkt); + mutex_unlock(&hdev->lock); + + return ret; +} + +static int venus_hfi_core_set_resource(struct venus_core *core, u32 id, + u32 size, u32 addr, void *cookie) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + struct hfi_sys_set_resource_pkt *pkt; + u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE]; + int ret; + + if (id == VIDC_RESOURCE_NONE) + return 0; + + pkt = (struct hfi_sys_set_resource_pkt *)packet; + + ret = pkt_sys_set_resource(pkt, id, size, addr, cookie); + if (ret) + return ret; + + ret = venus_iface_cmdq_write(hdev, pkt); + if (ret) + return ret; + + return 0; +} + +static int venus_boot_core(struct venus_hfi_device *hdev) +{ + struct device *dev = hdev->core->dev; + static const unsigned int max_tries = 100; + u32 ctrl_status = 0; + unsigned int count = 0; + int ret = 0; + + venus_writel(hdev, VIDC_CTRL_INIT, BIT(VIDC_CTRL_INIT_CTRL_SHIFT)); + venus_writel(hdev, WRAPPER_INTR_MASK, WRAPPER_INTR_MASK_A2HVCODEC_MASK); + venus_writel(hdev, CPU_CS_SCIACMDARG3, 1); + + while (!ctrl_status && count < max_tries) { + ctrl_status = venus_readl(hdev, CPU_CS_SCIACMDARG0); + if ((ctrl_status & CPU_CS_SCIACMDARG0_ERROR_STATUS_MASK) == 4) { + dev_err(dev, "invalid setting for UC_REGION\n"); + ret = -EINVAL; + break; + } + + usleep_range(500, 1000); + count++; + } + + if (count >= max_tries) + ret = -ETIMEDOUT; + + return ret; +} + +static u32 venus_hwversion(struct venus_hfi_device *hdev) +{ + struct device *dev = hdev->core->dev; + u32 ver = venus_readl(hdev, WRAPPER_HW_VERSION); + u32 major, minor, step; + + major = ver & WRAPPER_HW_VERSION_MAJOR_VERSION_MASK; + major = major >> WRAPPER_HW_VERSION_MAJOR_VERSION_SHIFT; + minor = ver & WRAPPER_HW_VERSION_MINOR_VERSION_MASK; + minor = minor >> WRAPPER_HW_VERSION_MINOR_VERSION_SHIFT; + step = ver & WRAPPER_HW_VERSION_STEP_VERSION_MASK; + + dev_dbg(dev, "venus hw version %x.%x.%x\n", major, minor, step); + + return major; +} + +static int venus_run(struct venus_hfi_device *hdev) +{ + struct device *dev = hdev->core->dev; + int ret; + + /* + * Re-program all of the registers that get reset as a result of + * regulator_disable() and _enable() + */ + venus_set_registers(hdev); + + venus_writel(hdev, UC_REGION_ADDR, hdev->ifaceq_table.da); + venus_writel(hdev, UC_REGION_SIZE, SHARED_QSIZE); + venus_writel(hdev, CPU_CS_SCIACMDARG2, hdev->ifaceq_table.da); + venus_writel(hdev, CPU_CS_SCIACMDARG1, 0x01); + if (hdev->sfr.da) + venus_writel(hdev, SFR_ADDR, hdev->sfr.da); + + ret = venus_boot_core(hdev); + if (ret) { + dev_err(dev, "failed to reset venus core\n"); + return ret; + } + + venus_hwversion(hdev); + + return 0; +} + +static int venus_halt_axi(struct venus_hfi_device *hdev) +{ + void __iomem *base = hdev->core->base; + struct device *dev = hdev->core->dev; + u32 val; + int ret; + + /* Halt AXI and AXI IMEM VBIF Access */ + val = venus_readl(hdev, VBIF_AXI_HALT_CTRL0); + val |= VBIF_AXI_HALT_CTRL0_HALT_REQ; + venus_writel(hdev, VBIF_AXI_HALT_CTRL0, val); + + /* Request for AXI bus port halt */ + ret = readl_poll_timeout(base + VBIF_AXI_HALT_CTRL1, val, + val & VBIF_AXI_HALT_CTRL1_HALT_ACK, + POLL_INTERVAL_US, + VBIF_AXI_HALT_ACK_TIMEOUT_US); + if (ret) { + dev_err(dev, "AXI bus port halt timeout\n"); + return ret; + } + + return 0; +} + +static int venus_power_off(struct venus_hfi_device *hdev) +{ + int ret; + + if (!hdev->power_enabled) + return 0; + + ret = qcom_scm_set_remote_state(TZBSP_VIDEO_STATE_SUSPEND, 0); + if (ret) + return ret; + + ret = venus_halt_axi(hdev); + if (ret) + return ret; + + hdev->power_enabled = false; + + return 0; +} + +static int venus_power_on(struct venus_hfi_device *hdev) +{ + int ret; + + if (hdev->power_enabled) + return 0; + + ret = qcom_scm_set_remote_state(TZBSP_VIDEO_STATE_RESUME, 0); + if (ret) + goto err; + + ret = venus_run(hdev); + if (ret) + goto err_suspend; + + hdev->power_enabled = true; + + return 0; + +err_suspend: + qcom_scm_set_remote_state(TZBSP_VIDEO_STATE_SUSPEND, 0); +err: + hdev->power_enabled = false; + return ret; +} + +static int venus_iface_msgq_read_nolock(struct venus_hfi_device *hdev, + void *pkt) +{ + struct iface_queue *queue; + u32 tx_req; + int ret; + + if (!venus_is_valid_state(hdev)) + return -EINVAL; + + queue = &hdev->queues[IFACEQ_MSG_IDX]; + + ret = venus_read_queue(hdev, queue, pkt, &tx_req); + if (ret) + return ret; + + if (tx_req) + venus_soft_int(hdev); + + return 0; +} + +static int venus_iface_msgq_read(struct venus_hfi_device *hdev, void *pkt) +{ + int ret; + + mutex_lock(&hdev->lock); + ret = venus_iface_msgq_read_nolock(hdev, pkt); + mutex_unlock(&hdev->lock); + + return ret; +} + +static int venus_iface_dbgq_read_nolock(struct venus_hfi_device *hdev, + void *pkt) +{ + struct iface_queue *queue; + u32 tx_req; + int ret; + + ret = venus_is_valid_state(hdev); + if (!ret) + return -EINVAL; + + queue = &hdev->queues[IFACEQ_DBG_IDX]; + + ret = venus_read_queue(hdev, queue, pkt, &tx_req); + if (ret) + return ret; + + if (tx_req) + venus_soft_int(hdev); + + return 0; +} + +static int venus_iface_dbgq_read(struct venus_hfi_device *hdev, void *pkt) +{ + int ret; + + if (!pkt) + return -EINVAL; + + mutex_lock(&hdev->lock); + ret = venus_iface_dbgq_read_nolock(hdev, pkt); + mutex_unlock(&hdev->lock); + + return ret; +} + +static void venus_set_qhdr_defaults(struct hfi_queue_header *qhdr) +{ + qhdr->status = 1; + qhdr->type = IFACEQ_DFLT_QHDR; + qhdr->q_size = IFACEQ_QUEUE_SIZE / 4; + qhdr->pkt_size = 0; + qhdr->rx_wm = 1; + qhdr->tx_wm = 1; + qhdr->rx_req = 1; + qhdr->tx_req = 0; + qhdr->rx_irq_status = 0; + qhdr->tx_irq_status = 0; + qhdr->read_idx = 0; + qhdr->write_idx = 0; +} + +static void venus_interface_queues_release(struct venus_hfi_device *hdev) +{ + mutex_lock(&hdev->lock); + + venus_free(hdev, &hdev->ifaceq_table); + venus_free(hdev, &hdev->sfr); + + memset(hdev->queues, 0, sizeof(hdev->queues)); + memset(&hdev->ifaceq_table, 0, sizeof(hdev->ifaceq_table)); + memset(&hdev->sfr, 0, sizeof(hdev->sfr)); + + mutex_unlock(&hdev->lock); +} + +static int venus_interface_queues_init(struct venus_hfi_device *hdev) +{ + struct hfi_queue_table_header *tbl_hdr; + struct iface_queue *queue; + struct hfi_sfr *sfr; + struct mem_desc desc = {0}; + unsigned int offset; + unsigned int i; + int ret; + + ret = venus_alloc(hdev, &desc, ALIGNED_QUEUE_SIZE); + if (ret) + return ret; + + hdev->ifaceq_table.kva = desc.kva; + hdev->ifaceq_table.da = desc.da; + hdev->ifaceq_table.size = IFACEQ_TABLE_SIZE; + offset = hdev->ifaceq_table.size; + + for (i = 0; i < IFACEQ_NUM; i++) { + queue = &hdev->queues[i]; + queue->qmem.da = desc.da + offset; + queue->qmem.kva = desc.kva + offset; + queue->qmem.size = IFACEQ_QUEUE_SIZE; + offset += queue->qmem.size; + queue->qhdr = + IFACEQ_GET_QHDR_START_ADDR(hdev->ifaceq_table.kva, i); + + venus_set_qhdr_defaults(queue->qhdr); + + queue->qhdr->start_addr = queue->qmem.da; + + if (i == IFACEQ_CMD_IDX) + queue->qhdr->type |= HFI_HOST_TO_CTRL_CMD_Q; + else if (i == IFACEQ_MSG_IDX) + queue->qhdr->type |= HFI_CTRL_TO_HOST_MSG_Q; + else if (i == IFACEQ_DBG_IDX) + queue->qhdr->type |= HFI_CTRL_TO_HOST_DBG_Q; + } + + tbl_hdr = hdev->ifaceq_table.kva; + tbl_hdr->version = 0; + tbl_hdr->size = IFACEQ_TABLE_SIZE; + tbl_hdr->qhdr0_offset = sizeof(struct hfi_queue_table_header); + tbl_hdr->qhdr_size = sizeof(struct hfi_queue_header); + tbl_hdr->num_q = IFACEQ_NUM; + tbl_hdr->num_active_q = IFACEQ_NUM; + + /* + * Set receive request to zero on debug queue as there is no + * need of interrupt from video hardware for debug messages + */ + queue = &hdev->queues[IFACEQ_DBG_IDX]; + queue->qhdr->rx_req = 0; + + ret = venus_alloc(hdev, &desc, ALIGNED_SFR_SIZE); + if (ret) { + hdev->sfr.da = 0; + } else { + hdev->sfr.da = desc.da; + hdev->sfr.kva = desc.kva; + hdev->sfr.size = ALIGNED_SFR_SIZE; + sfr = hdev->sfr.kva; + sfr->buf_size = ALIGNED_SFR_SIZE; + } + + /* ensure table and queue header structs are settled in memory */ + wmb(); + + return 0; +} + +static int venus_sys_set_debug(struct venus_hfi_device *hdev, u32 debug) +{ + struct hfi_sys_set_property_pkt *pkt; + u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE]; + int ret; + + pkt = (struct hfi_sys_set_property_pkt *)packet; + + pkt_sys_debug_config(pkt, HFI_DEBUG_MODE_QUEUE, debug); + + ret = venus_iface_cmdq_write(hdev, pkt); + if (ret) + return ret; + + return 0; +} + +static int venus_sys_set_coverage(struct venus_hfi_device *hdev, u32 mode) +{ + struct hfi_sys_set_property_pkt *pkt; + u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE]; + int ret; + + pkt = (struct hfi_sys_set_property_pkt *)packet; + + pkt_sys_coverage_config(pkt, mode); + + ret = venus_iface_cmdq_write(hdev, pkt); + if (ret) + return ret; + + return 0; +} + +static int venus_sys_set_idle_message(struct venus_hfi_device *hdev, + bool enable) +{ + struct hfi_sys_set_property_pkt *pkt; + u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE]; + int ret; + + if (!enable) + return 0; + + pkt = (struct hfi_sys_set_property_pkt *)packet; + + pkt_sys_idle_indicator(pkt, enable); + + ret = venus_iface_cmdq_write(hdev, pkt); + if (ret) + return ret; + + return 0; +} + +static int venus_sys_set_power_control(struct venus_hfi_device *hdev, + bool enable) +{ + struct hfi_sys_set_property_pkt *pkt; + u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE]; + int ret; + + pkt = (struct hfi_sys_set_property_pkt *)packet; + + pkt_sys_power_control(pkt, enable); + + ret = venus_iface_cmdq_write(hdev, pkt); + if (ret) + return ret; + + return 0; +} + +static int venus_get_queue_size(struct venus_hfi_device *hdev, + unsigned int index) +{ + struct hfi_queue_header *qhdr; + + if (index >= IFACEQ_NUM) + return -EINVAL; + + qhdr = hdev->queues[index].qhdr; + if (!qhdr) + return -EINVAL; + + return abs(qhdr->read_idx - qhdr->write_idx); +} + +static int venus_sys_set_default_properties(struct venus_hfi_device *hdev) +{ + struct device *dev = hdev->core->dev; + int ret; + + ret = venus_sys_set_debug(hdev, venus_fw_debug); + if (ret) + dev_warn(dev, "setting fw debug msg ON failed (%d)\n", ret); + + ret = venus_sys_set_idle_message(hdev, venus_sys_idle_indicator); + if (ret) + dev_warn(dev, "setting idle response ON failed (%d)\n", ret); + + ret = venus_sys_set_power_control(hdev, venus_fw_low_power_mode); + if (ret) + dev_warn(dev, "setting hw power collapse ON failed (%d)\n", + ret); + + return ret; +} + +static int venus_session_cmd(struct venus_inst *inst, u32 pkt_type) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_pkt pkt; + + pkt_session_cmd(&pkt, pkt_type, inst); + + return venus_iface_cmdq_write(hdev, &pkt); +} + +static void venus_flush_debug_queue(struct venus_hfi_device *hdev) +{ + struct device *dev = hdev->core->dev; + void *packet = hdev->dbg_buf; + + while (!venus_iface_dbgq_read(hdev, packet)) { + struct hfi_msg_sys_coverage_pkt *pkt = packet; + + if (pkt->hdr.pkt_type != HFI_MSG_SYS_COV) { + struct hfi_msg_sys_debug_pkt *pkt = packet; + + dev_dbg(dev, "%s", pkt->msg_data); + } + } +} + +static int venus_prepare_power_collapse(struct venus_hfi_device *hdev, + bool wait) +{ + unsigned long timeout = msecs_to_jiffies(venus_hw_rsp_timeout); + struct hfi_sys_pc_prep_pkt pkt; + int ret; + + init_completion(&hdev->pwr_collapse_prep); + + pkt_sys_pc_prep(&pkt); + + ret = venus_iface_cmdq_write(hdev, &pkt); + if (ret) + return ret; + + if (!wait) + return 0; + + ret = wait_for_completion_timeout(&hdev->pwr_collapse_prep, timeout); + if (!ret) { + venus_flush_debug_queue(hdev); + return -ETIMEDOUT; + } + + return 0; +} + +static int venus_are_queues_empty(struct venus_hfi_device *hdev) +{ + int ret1, ret2; + + ret1 = venus_get_queue_size(hdev, IFACEQ_MSG_IDX); + if (ret1 < 0) + return ret1; + + ret2 = venus_get_queue_size(hdev, IFACEQ_CMD_IDX); + if (ret2 < 0) + return ret2; + + if (!ret1 && !ret2) + return 1; + + return 0; +} + +static void venus_sfr_print(struct venus_hfi_device *hdev) +{ + struct device *dev = hdev->core->dev; + struct hfi_sfr *sfr = hdev->sfr.kva; + void *p; + + if (!sfr) + return; + + p = memchr(sfr->data, '\0', sfr->buf_size); + /* + * SFR isn't guaranteed to be NULL terminated since SYS_ERROR indicates + * that Venus is in the process of crashing. + */ + if (!p) + sfr->data[sfr->buf_size - 1] = '\0'; + + dev_err_ratelimited(dev, "SFR message from FW: %s\n", sfr->data); +} + +static void venus_process_msg_sys_error(struct venus_hfi_device *hdev, + void *packet) +{ + struct hfi_msg_event_notify_pkt *event_pkt = packet; + + if (event_pkt->event_id != HFI_EVENT_SYS_ERROR) + return; + + venus_set_state(hdev, VENUS_STATE_DEINIT); + + /* + * Once SYS_ERROR received from HW, it is safe to halt the AXI. + * With SYS_ERROR, Venus FW may have crashed and HW might be + * active and causing unnecessary transactions. Hence it is + * safe to stop all AXI transactions from venus subsystem. + */ + venus_halt_axi(hdev); + venus_sfr_print(hdev); +} + +static irqreturn_t venus_isr_thread(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + const struct venus_resources *res; + void *pkt; + u32 msg_ret; + + if (!hdev) + return IRQ_NONE; + + res = hdev->core->res; + pkt = hdev->pkt_buf; + + if (hdev->irq_status & WRAPPER_INTR_STATUS_A2HWD_MASK) { + venus_sfr_print(hdev); + hfi_process_watchdog_timeout(core); + } + + while (!venus_iface_msgq_read(hdev, pkt)) { + msg_ret = hfi_process_msg_packet(core, pkt); + switch (msg_ret) { + case HFI_MSG_EVENT_NOTIFY: + venus_process_msg_sys_error(hdev, pkt); + break; + case HFI_MSG_SYS_INIT: + venus_hfi_core_set_resource(core, res->vmem_id, + res->vmem_size, + res->vmem_addr, + hdev); + break; + case HFI_MSG_SYS_RELEASE_RESOURCE: + complete(&hdev->release_resource); + break; + case HFI_MSG_SYS_PC_PREP: + complete(&hdev->pwr_collapse_prep); + break; + default: + break; + } + } + + venus_flush_debug_queue(hdev); + + return IRQ_HANDLED; +} + +static irqreturn_t venus_isr(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + u32 status; + + if (!hdev) + return IRQ_NONE; + + status = venus_readl(hdev, WRAPPER_INTR_STATUS); + + if (status & WRAPPER_INTR_STATUS_A2H_MASK || + status & WRAPPER_INTR_STATUS_A2HWD_MASK || + status & CPU_CS_SCIACMDARG0_INIT_IDLE_MSG_MASK) + hdev->irq_status = status; + + venus_writel(hdev, CPU_CS_A2HSOFTINTCLR, 1); + venus_writel(hdev, WRAPPER_INTR_CLEAR, status); + + return IRQ_WAKE_THREAD; +} + +static int venus_core_init(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + struct device *dev = core->dev; + struct hfi_sys_get_property_pkt version_pkt; + struct hfi_sys_init_pkt pkt; + int ret; + + pkt_sys_init(&pkt, HFI_VIDEO_ARCH_OX); + + venus_set_state(hdev, VENUS_STATE_INIT); + + ret = venus_iface_cmdq_write(hdev, &pkt); + if (ret) + return ret; + + pkt_sys_image_version(&version_pkt); + + ret = venus_iface_cmdq_write(hdev, &version_pkt); + if (ret) + dev_warn(dev, "failed to send image version pkt to fw\n"); + + return 0; +} + +static int venus_core_deinit(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + + venus_set_state(hdev, VENUS_STATE_DEINIT); + hdev->suspended = true; + hdev->power_enabled = false; + + return 0; +} + +static int venus_core_ping(struct venus_core *core, u32 cookie) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + struct hfi_sys_ping_pkt pkt; + + pkt_sys_ping(&pkt, cookie); + + return venus_iface_cmdq_write(hdev, &pkt); +} + +static int venus_core_trigger_ssr(struct venus_core *core, u32 trigger_type) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + struct hfi_sys_test_ssr_pkt pkt; + int ret; + + ret = pkt_sys_ssr_cmd(&pkt, trigger_type); + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, &pkt); +} + +static int venus_session_init(struct venus_inst *inst, u32 session_type, + u32 codec) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_init_pkt pkt; + int ret; + + ret = venus_sys_set_default_properties(hdev); + if (ret) + return ret; + + ret = pkt_session_init(&pkt, inst, session_type, codec); + if (ret) + goto err; + + ret = venus_iface_cmdq_write(hdev, &pkt); + if (ret) + goto err; + + return 0; + +err: + venus_flush_debug_queue(hdev); + return ret; +} + +static int venus_session_end(struct venus_inst *inst) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct device *dev = hdev->core->dev; + + if (venus_fw_coverage) { + if (venus_sys_set_coverage(hdev, venus_fw_coverage)) + dev_warn(dev, "fw coverage msg ON failed\n"); + } + + return venus_session_cmd(inst, HFI_CMD_SYS_SESSION_END); +} + +static int venus_session_abort(struct venus_inst *inst) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + + venus_flush_debug_queue(hdev); + + return venus_session_cmd(inst, HFI_CMD_SYS_SESSION_ABORT); +} + +static int venus_session_flush(struct venus_inst *inst, u32 flush_mode) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_flush_pkt pkt; + int ret; + + ret = pkt_session_flush(&pkt, inst, flush_mode); + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, &pkt); +} + +static int venus_session_start(struct venus_inst *inst) +{ + return venus_session_cmd(inst, HFI_CMD_SESSION_START); +} + +static int venus_session_stop(struct venus_inst *inst) +{ + return venus_session_cmd(inst, HFI_CMD_SESSION_STOP); +} + +static int venus_session_continue(struct venus_inst *inst) +{ + return venus_session_cmd(inst, HFI_CMD_SESSION_CONTINUE); +} + +static int venus_session_etb(struct venus_inst *inst, + struct hfi_frame_data *in_frame) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + u32 session_type = inst->session_type; + int ret; + + if (session_type == VIDC_SESSION_TYPE_DEC) { + struct hfi_session_empty_buffer_compressed_pkt pkt; + + ret = pkt_session_etb_decoder(&pkt, inst, in_frame); + if (ret) + return ret; + + ret = venus_iface_cmdq_write(hdev, &pkt); + } else if (session_type == VIDC_SESSION_TYPE_ENC) { + struct hfi_session_empty_buffer_uncompressed_plane0_pkt pkt; + + ret = pkt_session_etb_encoder(&pkt, inst, in_frame); + if (ret) + return ret; + + ret = venus_iface_cmdq_write(hdev, &pkt); + } else { + ret = -EINVAL; + } + + return ret; +} + +static int venus_session_ftb(struct venus_inst *inst, + struct hfi_frame_data *out_frame) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_fill_buffer_pkt pkt; + int ret; + + ret = pkt_session_ftb(&pkt, inst, out_frame); + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, &pkt); +} + +static int venus_session_set_buffers(struct venus_inst *inst, + struct hfi_buffer_desc *bd) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_set_buffers_pkt *pkt; + u8 packet[IFACEQ_VAR_LARGE_PKT_SIZE]; + int ret; + + if (bd->buffer_type == HFI_BUFFER_INPUT) + return 0; + + pkt = (struct hfi_session_set_buffers_pkt *)packet; + + ret = pkt_session_set_buffers(pkt, inst, bd); + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, pkt); +} + +static int venus_session_unset_buffers(struct venus_inst *inst, + struct hfi_buffer_desc *bd) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_release_buffer_pkt *pkt; + u8 packet[IFACEQ_VAR_LARGE_PKT_SIZE]; + int ret; + + if (bd->buffer_type == HFI_BUFFER_INPUT) + return 0; + + pkt = (struct hfi_session_release_buffer_pkt *)packet; + + ret = pkt_session_unset_buffers(pkt, inst, bd); + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, pkt); +} + +static int venus_session_load_res(struct venus_inst *inst) +{ + return venus_session_cmd(inst, HFI_CMD_SESSION_LOAD_RESOURCES); +} + +static int venus_session_release_res(struct venus_inst *inst) +{ + return venus_session_cmd(inst, HFI_CMD_SESSION_RELEASE_RESOURCES); +} + +static int venus_session_parse_seq_hdr(struct venus_inst *inst, u32 seq_hdr, + u32 seq_hdr_len) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_parse_sequence_header_pkt *pkt; + u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE]; + int ret; + + pkt = (struct hfi_session_parse_sequence_header_pkt *)packet; + + ret = pkt_session_parse_seq_header(pkt, inst, seq_hdr, seq_hdr_len); + if (ret) + return ret; + + ret = venus_iface_cmdq_write(hdev, pkt); + if (ret) + return ret; + + return 0; +} + +static int venus_session_get_seq_hdr(struct venus_inst *inst, u32 seq_hdr, + u32 seq_hdr_len) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_get_sequence_header_pkt *pkt; + u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE]; + int ret; + + pkt = (struct hfi_session_get_sequence_header_pkt *)packet; + + ret = pkt_session_get_seq_hdr(pkt, inst, seq_hdr, seq_hdr_len); + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, pkt); +} + +static int venus_session_set_property(struct venus_inst *inst, u32 ptype, + void *pdata) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_set_property_pkt *pkt; + u8 packet[IFACEQ_VAR_LARGE_PKT_SIZE]; + int ret; + + pkt = (struct hfi_session_set_property_pkt *)packet; + + ret = pkt_session_set_property(pkt, inst, ptype, pdata); + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, pkt); +} + +static int venus_session_get_property(struct venus_inst *inst, u32 ptype) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_get_property_pkt pkt; + int ret; + + ret = pkt_session_get_property(&pkt, inst, ptype); + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, &pkt); +} + +static int venus_resume(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + int ret = 0; + + mutex_lock(&hdev->lock); + + if (!hdev->suspended) + goto unlock; + + ret = venus_power_on(hdev); + +unlock: + if (!ret) + hdev->suspended = false; + + mutex_unlock(&hdev->lock); + + return ret; +} + +static int venus_suspend_1xx(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + struct device *dev = core->dev; + u32 ctrl_status; + int ret; + + if (!hdev->power_enabled || hdev->suspended) + return 0; + + mutex_lock(&hdev->lock); + ret = venus_is_valid_state(hdev); + mutex_unlock(&hdev->lock); + + if (!ret) { + dev_err(dev, "bad state, cannot suspend\n"); + return -EINVAL; + } + + ret = venus_prepare_power_collapse(hdev, true); + if (ret) { + dev_err(dev, "prepare for power collapse fail (%d)\n", ret); + return ret; + } + + mutex_lock(&hdev->lock); + + if (hdev->last_packet_type != HFI_CMD_SYS_PC_PREP) { + mutex_unlock(&hdev->lock); + return -EINVAL; + } + + ret = venus_are_queues_empty(hdev); + if (ret < 0 || !ret) { + mutex_unlock(&hdev->lock); + return -EINVAL; + } + + ctrl_status = venus_readl(hdev, CPU_CS_SCIACMDARG0); + if (!(ctrl_status & CPU_CS_SCIACMDARG0_PC_READY)) { + mutex_unlock(&hdev->lock); + return -EINVAL; + } + + ret = venus_power_off(hdev); + if (ret) { + mutex_unlock(&hdev->lock); + return ret; + } + + hdev->suspended = true; + + mutex_unlock(&hdev->lock); + + return 0; +} + +static int venus_suspend_3xx(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + struct device *dev = core->dev; + u32 ctrl_status, wfi_status; + int ret; + int cnt = 100; + + if (!hdev->power_enabled || hdev->suspended) + return 0; + + mutex_lock(&hdev->lock); + ret = venus_is_valid_state(hdev); + mutex_unlock(&hdev->lock); + + if (!ret) { + dev_err(dev, "bad state, cannot suspend\n"); + return -EINVAL; + } + + ctrl_status = venus_readl(hdev, CPU_CS_SCIACMDARG0); + if (!(ctrl_status & CPU_CS_SCIACMDARG0_PC_READY)) { + wfi_status = venus_readl(hdev, WRAPPER_CPU_STATUS); + ctrl_status = venus_readl(hdev, CPU_CS_SCIACMDARG0); + + ret = venus_prepare_power_collapse(hdev, false); + if (ret) { + dev_err(dev, "prepare for power collapse fail (%d)\n", + ret); + return ret; + } + + cnt = 100; + while (cnt--) { + wfi_status = venus_readl(hdev, WRAPPER_CPU_STATUS); + ctrl_status = venus_readl(hdev, CPU_CS_SCIACMDARG0); + if (ctrl_status & CPU_CS_SCIACMDARG0_PC_READY && + wfi_status & BIT(0)) + break; + usleep_range(1000, 1500); + } + } + + mutex_lock(&hdev->lock); + + ret = venus_power_off(hdev); + if (ret) { + dev_err(dev, "venus_power_off (%d)\n", ret); + mutex_unlock(&hdev->lock); + return ret; + } + + hdev->suspended = true; + + mutex_unlock(&hdev->lock); + + return 0; +} + +static int venus_suspend(struct venus_core *core) +{ + if (core->res->hfi_version == HFI_VERSION_3XX) + return venus_suspend_3xx(core); + + return venus_suspend_1xx(core); +} + +static const struct hfi_ops venus_hfi_ops = { + .core_init = venus_core_init, + .core_deinit = venus_core_deinit, + .core_ping = venus_core_ping, + .core_trigger_ssr = venus_core_trigger_ssr, + + .session_init = venus_session_init, + .session_end = venus_session_end, + .session_abort = venus_session_abort, + .session_flush = venus_session_flush, + .session_start = venus_session_start, + .session_stop = venus_session_stop, + .session_continue = venus_session_continue, + .session_etb = venus_session_etb, + .session_ftb = venus_session_ftb, + .session_set_buffers = venus_session_set_buffers, + .session_unset_buffers = venus_session_unset_buffers, + .session_load_res = venus_session_load_res, + .session_release_res = venus_session_release_res, + .session_parse_seq_hdr = venus_session_parse_seq_hdr, + .session_get_seq_hdr = venus_session_get_seq_hdr, + .session_set_property = venus_session_set_property, + .session_get_property = venus_session_get_property, + + .resume = venus_resume, + .suspend = venus_suspend, + + .isr = venus_isr, + .isr_thread = venus_isr_thread, +}; + +void venus_hfi_destroy(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + + venus_interface_queues_release(hdev); + mutex_destroy(&hdev->lock); + kfree(hdev); + core->priv = NULL; + core->ops = NULL; +} + +int venus_hfi_create(struct venus_core *core) +{ + struct venus_hfi_device *hdev; + int ret; + + hdev = kzalloc(sizeof(*hdev), GFP_KERNEL); + if (!hdev) + return -ENOMEM; + + mutex_init(&hdev->lock); + + hdev->core = core; + hdev->suspended = true; + core->priv = hdev; + core->ops = &venus_hfi_ops; + core->core_caps = ENC_ROTATION_CAPABILITY | ENC_SCALING_CAPABILITY | + ENC_DEINTERLACE_CAPABILITY | + DEC_MULTI_STREAM_CAPABILITY; + + ret = venus_interface_queues_init(hdev); + if (ret) + goto err_kfree; + + return 0; + +err_kfree: + kfree(hdev); + core->priv = NULL; + core->ops = NULL; + return ret; +} diff --git a/drivers/media/platform/qcom/venus/hfi_venus.h b/drivers/media/platform/qcom/venus/hfi_venus.h new file mode 100644 index 000000000000..885923354033 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_venus.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __VENUS_HFI_VENUS_H__ +#define __VENUS_HFI_VENUS_H__ + +struct venus_core; + +void venus_hfi_destroy(struct venus_core *core); +int venus_hfi_create(struct venus_core *core); + +#endif diff --git a/drivers/media/platform/qcom/venus/hfi_venus_io.h b/drivers/media/platform/qcom/venus/hfi_venus_io.h new file mode 100644 index 000000000000..98cc350113ab --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_venus_io.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __VENUS_HFI_VENUS_IO_H__ +#define __VENUS_HFI_VENUS_IO_H__ + +#define VBIF_BASE 0x80000 + +#define VBIF_AXI_HALT_CTRL0 (VBIF_BASE + 0x208) +#define VBIF_AXI_HALT_CTRL1 (VBIF_BASE + 0x20c) + +#define VBIF_AXI_HALT_CTRL0_HALT_REQ BIT(0) +#define VBIF_AXI_HALT_CTRL1_HALT_ACK BIT(0) +#define VBIF_AXI_HALT_ACK_TIMEOUT_US 500000 + +#define CPU_BASE 0xc0000 +#define CPU_CS_BASE (CPU_BASE + 0x12000) +#define CPU_IC_BASE (CPU_BASE + 0x1f000) + +#define CPU_CS_A2HSOFTINTCLR (CPU_CS_BASE + 0x1c) + +#define VIDC_CTRL_INIT (CPU_CS_BASE + 0x48) +#define VIDC_CTRL_INIT_RESERVED_BITS31_1_MASK 0xfffffffe +#define VIDC_CTRL_INIT_RESERVED_BITS31_1_SHIFT 1 +#define VIDC_CTRL_INIT_CTRL_MASK 0x1 +#define VIDC_CTRL_INIT_CTRL_SHIFT 0 + +/* HFI control status */ +#define CPU_CS_SCIACMDARG0 (CPU_CS_BASE + 0x4c) +#define CPU_CS_SCIACMDARG0_MASK 0xff +#define CPU_CS_SCIACMDARG0_SHIFT 0x0 +#define CPU_CS_SCIACMDARG0_ERROR_STATUS_MASK 0xfe +#define CPU_CS_SCIACMDARG0_ERROR_STATUS_SHIFT 0x1 +#define CPU_CS_SCIACMDARG0_INIT_STATUS_MASK 0x1 +#define CPU_CS_SCIACMDARG0_INIT_STATUS_SHIFT 0x0 +#define CPU_CS_SCIACMDARG0_PC_READY BIT(8) +#define CPU_CS_SCIACMDARG0_INIT_IDLE_MSG_MASK BIT(30) + +/* HFI queue table info */ +#define CPU_CS_SCIACMDARG1 (CPU_CS_BASE + 0x50) + +/* HFI queue table address */ +#define CPU_CS_SCIACMDARG2 (CPU_CS_BASE + 0x54) + +/* Venus cpu */ +#define CPU_CS_SCIACMDARG3 (CPU_CS_BASE + 0x58) + +#define SFR_ADDR (CPU_CS_BASE + 0x5c) +#define MMAP_ADDR (CPU_CS_BASE + 0x60) +#define UC_REGION_ADDR (CPU_CS_BASE + 0x64) +#define UC_REGION_SIZE (CPU_CS_BASE + 0x68) + +#define CPU_IC_SOFTINT (CPU_IC_BASE + 0x18) +#define CPU_IC_SOFTINT_H2A_MASK 0x8000 +#define CPU_IC_SOFTINT_H2A_SHIFT 0xf + +/* Venus wrapper */ +#define WRAPPER_BASE 0x000e0000 + +#define WRAPPER_HW_VERSION (WRAPPER_BASE + 0x00) +#define WRAPPER_HW_VERSION_MAJOR_VERSION_MASK 0x78000000 +#define WRAPPER_HW_VERSION_MAJOR_VERSION_SHIFT 28 +#define WRAPPER_HW_VERSION_MINOR_VERSION_MASK 0xfff0000 +#define WRAPPER_HW_VERSION_MINOR_VERSION_SHIFT 16 +#define WRAPPER_HW_VERSION_STEP_VERSION_MASK 0xffff + +#define WRAPPER_CLOCK_CONFIG (WRAPPER_BASE + 0x04) + +#define WRAPPER_INTR_STATUS (WRAPPER_BASE + 0x0c) +#define WRAPPER_INTR_STATUS_A2HWD_MASK 0x10 +#define WRAPPER_INTR_STATUS_A2HWD_SHIFT 0x4 +#define WRAPPER_INTR_STATUS_A2H_MASK 0x4 +#define WRAPPER_INTR_STATUS_A2H_SHIFT 0x2 + +#define WRAPPER_INTR_MASK (WRAPPER_BASE + 0x10) +#define WRAPPER_INTR_MASK_A2HWD_BASK 0x10 +#define WRAPPER_INTR_MASK_A2HWD_SHIFT 0x4 +#define WRAPPER_INTR_MASK_A2HVCODEC_MASK 0x8 +#define WRAPPER_INTR_MASK_A2HVCODEC_SHIFT 0x3 +#define WRAPPER_INTR_MASK_A2HCPU_MASK 0x4 +#define WRAPPER_INTR_MASK_A2HCPU_SHIFT 0x2 + +#define WRAPPER_INTR_CLEAR (WRAPPER_BASE + 0x14) +#define WRAPPER_INTR_CLEAR_A2HWD_MASK 0x10 +#define WRAPPER_INTR_CLEAR_A2HWD_SHIFT 0x4 +#define WRAPPER_INTR_CLEAR_A2H_MASK 0x4 +#define WRAPPER_INTR_CLEAR_A2H_SHIFT 0x2 + +#define WRAPPER_POWER_STATUS (WRAPPER_BASE + 0x44) +#define WRAPPER_VDEC_VCODEC_POWER_CONTROL (WRAPPER_BASE + 0x48) +#define WRAPPER_VENC_VCODEC_POWER_CONTROL (WRAPPER_BASE + 0x4c) +#define WRAPPER_VDEC_VENC_AHB_BRIDGE_SYNC_RESET (WRAPPER_BASE + 0x64) + +#define WRAPPER_CPU_CLOCK_CONFIG (WRAPPER_BASE + 0x2000) +#define WRAPPER_CPU_AXI_HALT (WRAPPER_BASE + 0x2008) +#define WRAPPER_CPU_AXI_HALT_STATUS (WRAPPER_BASE + 0x200c) + +#define WRAPPER_CPU_CGC_DIS (WRAPPER_BASE + 0x2010) +#define WRAPPER_CPU_STATUS (WRAPPER_BASE + 0x2014) +#define WRAPPER_SW_RESET (WRAPPER_BASE + 0x3000) + +#endif diff --git a/drivers/media/platform/qcom/venus/vdec.c b/drivers/media/platform/qcom/venus/vdec.c new file mode 100644 index 000000000000..eb0c1c51cfef --- /dev/null +++ b/drivers/media/platform/qcom/venus/vdec.c @@ -0,0 +1,1162 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-mem2mem.h> +#include <media/videobuf2-dma-sg.h> + +#include "hfi_venus_io.h" +#include "core.h" +#include "helpers.h" +#include "vdec.h" + +static u32 get_framesize_uncompressed(unsigned int plane, u32 width, u32 height) +{ + u32 y_stride, uv_stride, y_plane; + u32 y_sclines, uv_sclines, uv_plane; + u32 size; + + y_stride = ALIGN(width, 128); + uv_stride = ALIGN(width, 128); + y_sclines = ALIGN(height, 32); + uv_sclines = ALIGN(((height + 1) >> 1), 16); + + y_plane = y_stride * y_sclines; + uv_plane = uv_stride * uv_sclines + SZ_4K; + size = y_plane + uv_plane + SZ_8K; + + return ALIGN(size, SZ_4K); +} + +static u32 get_framesize_compressed(unsigned int width, unsigned int height) +{ + return ((width * height * 3 / 2) / 2) + 128; +} + +/* + * Three resons to keep MPLANE formats (despite that the number of planes + * currently is one): + * - the MPLANE formats allow only one plane to be used + * - the downstream driver use MPLANE formats too + * - future firmware versions could add support for >1 planes + */ +static const struct venus_format vdec_formats[] = { + { + .pixfmt = V4L2_PIX_FMT_NV12, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG4, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG2, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_H263, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_VC1_ANNEX_G, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_VC1_ANNEX_L, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_H264, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_VP8, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_VP9, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_XVID, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, +}; + +static const struct venus_format *find_format(u32 pixfmt, u32 type) +{ + const struct venus_format *fmt = vdec_formats; + unsigned int size = ARRAY_SIZE(vdec_formats); + unsigned int i; + + for (i = 0; i < size; i++) { + if (fmt[i].pixfmt == pixfmt) + break; + } + + if (i == size || fmt[i].type != type) + return NULL; + + return &fmt[i]; +} + +static const struct venus_format * +find_format_by_index(unsigned int index, u32 type) +{ + const struct venus_format *fmt = vdec_formats; + unsigned int size = ARRAY_SIZE(vdec_formats); + unsigned int i, k = 0; + + if (index > size) + return NULL; + + for (i = 0; i < size; i++) { + if (fmt[i].type != type) + continue; + if (k == index) + break; + k++; + } + + if (i == size) + return NULL; + + return &fmt[i]; +} + +static const struct venus_format * +vdec_try_fmt_common(struct venus_inst *inst, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + struct v4l2_plane_pix_format *pfmt = pixmp->plane_fmt; + const struct venus_format *fmt; + unsigned int p; + + memset(pfmt[0].reserved, 0, sizeof(pfmt[0].reserved)); + memset(pixmp->reserved, 0, sizeof(pixmp->reserved)); + + fmt = find_format(pixmp->pixelformat, f->type); + if (!fmt) { + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + pixmp->pixelformat = V4L2_PIX_FMT_NV12; + else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + pixmp->pixelformat = V4L2_PIX_FMT_H264; + else + return NULL; + fmt = find_format(pixmp->pixelformat, f->type); + pixmp->width = 1280; + pixmp->height = 720; + } + + pixmp->width = clamp(pixmp->width, inst->cap_width.min, + inst->cap_width.max); + pixmp->height = clamp(pixmp->height, inst->cap_height.min, + inst->cap_height.max); + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + pixmp->height = ALIGN(pixmp->height, 32); + + if (pixmp->field == V4L2_FIELD_ANY) + pixmp->field = V4L2_FIELD_NONE; + pixmp->num_planes = fmt->num_planes; + pixmp->flags = 0; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + for (p = 0; p < pixmp->num_planes; p++) { + pfmt[p].sizeimage = + get_framesize_uncompressed(p, pixmp->width, + pixmp->height); + pfmt[p].bytesperline = ALIGN(pixmp->width, 128); + } + } else { + pfmt[0].sizeimage = get_framesize_compressed(pixmp->width, + pixmp->height); + pfmt[0].bytesperline = 0; + } + + return fmt; +} + +static int vdec_try_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct venus_inst *inst = to_inst(file); + + vdec_try_fmt_common(inst, f); + + return 0; +} + +static int vdec_g_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct venus_inst *inst = to_inst(file); + const struct venus_format *fmt = NULL; + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + fmt = inst->fmt_cap; + else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + fmt = inst->fmt_out; + + if (inst->reconfig) { + struct v4l2_format format = {}; + + inst->out_width = inst->reconfig_width; + inst->out_height = inst->reconfig_height; + inst->reconfig = false; + + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + format.fmt.pix_mp.pixelformat = inst->fmt_cap->pixfmt; + format.fmt.pix_mp.width = inst->out_width; + format.fmt.pix_mp.height = inst->out_height; + + vdec_try_fmt_common(inst, &format); + + inst->width = format.fmt.pix_mp.width; + inst->height = format.fmt.pix_mp.height; + } + + pixmp->pixelformat = fmt->pixfmt; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + pixmp->width = inst->width; + pixmp->height = inst->height; + pixmp->colorspace = inst->colorspace; + pixmp->ycbcr_enc = inst->ycbcr_enc; + pixmp->quantization = inst->quantization; + pixmp->xfer_func = inst->xfer_func; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + pixmp->width = inst->out_width; + pixmp->height = inst->out_height; + } + + vdec_try_fmt_common(inst, f); + + return 0; +} + +static int vdec_s_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct venus_inst *inst = to_inst(file); + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + struct v4l2_pix_format_mplane orig_pixmp; + const struct venus_format *fmt; + struct v4l2_format format; + u32 pixfmt_out = 0, pixfmt_cap = 0; + + orig_pixmp = *pixmp; + + fmt = vdec_try_fmt_common(inst, f); + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + pixfmt_out = pixmp->pixelformat; + pixfmt_cap = inst->fmt_cap->pixfmt; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + pixfmt_cap = pixmp->pixelformat; + pixfmt_out = inst->fmt_out->pixfmt; + } + + memset(&format, 0, sizeof(format)); + + format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + format.fmt.pix_mp.pixelformat = pixfmt_out; + format.fmt.pix_mp.width = orig_pixmp.width; + format.fmt.pix_mp.height = orig_pixmp.height; + vdec_try_fmt_common(inst, &format); + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + inst->out_width = format.fmt.pix_mp.width; + inst->out_height = format.fmt.pix_mp.height; + inst->colorspace = pixmp->colorspace; + inst->ycbcr_enc = pixmp->ycbcr_enc; + inst->quantization = pixmp->quantization; + inst->xfer_func = pixmp->xfer_func; + } + + memset(&format, 0, sizeof(format)); + + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + format.fmt.pix_mp.pixelformat = pixfmt_cap; + format.fmt.pix_mp.width = orig_pixmp.width; + format.fmt.pix_mp.height = orig_pixmp.height; + vdec_try_fmt_common(inst, &format); + + inst->width = format.fmt.pix_mp.width; + inst->height = format.fmt.pix_mp.height; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + inst->fmt_out = fmt; + else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + inst->fmt_cap = fmt; + + return 0; +} + +static int +vdec_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct venus_inst *inst = to_inst(file); + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP: + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + s->r.width = inst->out_width; + s->r.height = inst->out_height; + break; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_PADDED: + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + s->r.width = inst->width; + s->r.height = inst->height; + break; + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE: + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + s->r.width = inst->out_width; + s->r.height = inst->out_height; + break; + default: + return -EINVAL; + } + + s->r.top = 0; + s->r.left = 0; + + return 0; +} + +static int +vdec_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + strlcpy(cap->driver, "qcom-venus", sizeof(cap->driver)); + strlcpy(cap->card, "Qualcomm Venus video decoder", sizeof(cap->card)); + strlcpy(cap->bus_info, "platform:qcom-venus", sizeof(cap->bus_info)); + + return 0; +} + +static int vdec_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + const struct venus_format *fmt; + + memset(f->reserved, 0, sizeof(f->reserved)); + + fmt = find_format_by_index(f->index, f->type); + if (!fmt) + return -EINVAL; + + f->pixelformat = fmt->pixfmt; + + return 0; +} + +static int vdec_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct venus_inst *inst = to_inst(file); + struct v4l2_captureparm *cap = &a->parm.capture; + struct v4l2_fract *timeperframe = &cap->timeperframe; + u64 us_per_frame, fps; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + memset(cap->reserved, 0, sizeof(cap->reserved)); + if (!timeperframe->denominator) + timeperframe->denominator = inst->timeperframe.denominator; + if (!timeperframe->numerator) + timeperframe->numerator = inst->timeperframe.numerator; + cap->readbuffers = 0; + cap->extendedmode = 0; + cap->capability = V4L2_CAP_TIMEPERFRAME; + us_per_frame = timeperframe->numerator * (u64)USEC_PER_SEC; + do_div(us_per_frame, timeperframe->denominator); + + if (!us_per_frame) + return -EINVAL; + + fps = (u64)USEC_PER_SEC; + do_div(fps, us_per_frame); + + inst->fps = fps; + inst->timeperframe = *timeperframe; + + return 0; +} + +static int vdec_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct venus_inst *inst = to_inst(file); + const struct venus_format *fmt; + + fmt = find_format(fsize->pixel_format, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (!fmt) { + fmt = find_format(fsize->pixel_format, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + if (!fmt) + return -EINVAL; + } + + if (fsize->index) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + + fsize->stepwise.min_width = inst->cap_width.min; + fsize->stepwise.max_width = inst->cap_width.max; + fsize->stepwise.step_width = inst->cap_width.step_size; + fsize->stepwise.min_height = inst->cap_height.min; + fsize->stepwise.max_height = inst->cap_height.max; + fsize->stepwise.step_height = inst->cap_height.step_size; + + return 0; +} + +static int vdec_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 2, NULL); + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subscribe_event(fh, sub); + default: + return -EINVAL; + } +} + +static int +vdec_try_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *cmd) +{ + if (cmd->cmd != V4L2_DEC_CMD_STOP) + return -EINVAL; + + return 0; +} + +static int +vdec_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *cmd) +{ + struct venus_inst *inst = to_inst(file); + int ret; + + ret = vdec_try_decoder_cmd(file, fh, cmd); + if (ret) + return ret; + + mutex_lock(&inst->lock); + inst->cmd_stop = true; + mutex_unlock(&inst->lock); + + hfi_session_flush(inst); + + return 0; +} + +static const struct v4l2_ioctl_ops vdec_ioctl_ops = { + .vidioc_querycap = vdec_querycap, + .vidioc_enum_fmt_vid_cap_mplane = vdec_enum_fmt, + .vidioc_enum_fmt_vid_out_mplane = vdec_enum_fmt, + .vidioc_s_fmt_vid_cap_mplane = vdec_s_fmt, + .vidioc_s_fmt_vid_out_mplane = vdec_s_fmt, + .vidioc_g_fmt_vid_cap_mplane = vdec_g_fmt, + .vidioc_g_fmt_vid_out_mplane = vdec_g_fmt, + .vidioc_try_fmt_vid_cap_mplane = vdec_try_fmt, + .vidioc_try_fmt_vid_out_mplane = vdec_try_fmt, + .vidioc_g_selection = vdec_g_selection, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + .vidioc_s_parm = vdec_s_parm, + .vidioc_enum_framesizes = vdec_enum_framesizes, + .vidioc_subscribe_event = vdec_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_try_decoder_cmd = vdec_try_decoder_cmd, + .vidioc_decoder_cmd = vdec_decoder_cmd, +}; + +static int vdec_set_properties(struct venus_inst *inst) +{ + struct vdec_controls *ctr = &inst->controls.dec; + struct venus_core *core = inst->core; + struct hfi_enable en = { .enable = 1 }; + u32 ptype; + int ret; + + if (core->res->hfi_version == HFI_VERSION_1XX) { + ptype = HFI_PROPERTY_PARAM_VDEC_CONTINUE_DATA_TRANSFER; + ret = hfi_session_set_property(inst, ptype, &en); + if (ret) + return ret; + } + + if (core->res->hfi_version == HFI_VERSION_3XX || + inst->cap_bufs_mode_dynamic) { + struct hfi_buffer_alloc_mode mode; + + ptype = HFI_PROPERTY_PARAM_BUFFER_ALLOC_MODE; + mode.type = HFI_BUFFER_OUTPUT; + mode.mode = HFI_BUFFER_MODE_DYNAMIC; + + ret = hfi_session_set_property(inst, ptype, &mode); + if (ret) + return ret; + } + + if (ctr->post_loop_deb_mode) { + ptype = HFI_PROPERTY_CONFIG_VDEC_POST_LOOP_DEBLOCKER; + en.enable = 1; + ret = hfi_session_set_property(inst, ptype, &en); + if (ret) + return ret; + } + + return 0; +} + +static int vdec_init_session(struct venus_inst *inst) +{ + int ret; + + ret = hfi_session_init(inst, inst->fmt_out->pixfmt); + if (ret) + return ret; + + ret = venus_helper_set_input_resolution(inst, inst->out_width, + inst->out_height); + if (ret) + goto deinit; + + ret = venus_helper_set_color_format(inst, inst->fmt_cap->pixfmt); + if (ret) + goto deinit; + + return 0; +deinit: + hfi_session_deinit(inst); + return ret; +} + +static int vdec_cap_num_buffers(struct venus_inst *inst, unsigned int *num) +{ + struct hfi_buffer_requirements bufreq; + int ret; + + ret = vdec_init_session(inst); + if (ret) + return ret; + + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_OUTPUT, &bufreq); + + *num = bufreq.count_actual; + + hfi_session_deinit(inst); + + return ret; +} + +static int vdec_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct venus_inst *inst = vb2_get_drv_priv(q); + unsigned int p, num; + int ret = 0; + + if (*num_planes) { + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + *num_planes != inst->fmt_out->num_planes) + return -EINVAL; + + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + *num_planes != inst->fmt_cap->num_planes) + return -EINVAL; + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + sizes[0] < inst->input_buf_size) + return -EINVAL; + + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + sizes[0] < inst->output_buf_size) + return -EINVAL; + + return 0; + } + + switch (q->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + *num_planes = inst->fmt_out->num_planes; + sizes[0] = get_framesize_compressed(inst->out_width, + inst->out_height); + inst->input_buf_size = sizes[0]; + inst->num_input_bufs = *num_buffers; + + ret = vdec_cap_num_buffers(inst, &num); + if (ret) + break; + + inst->num_output_bufs = num; + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + *num_planes = inst->fmt_cap->num_planes; + + ret = vdec_cap_num_buffers(inst, &num); + if (ret) + break; + + *num_buffers = max(*num_buffers, num); + + for (p = 0; p < *num_planes; p++) + sizes[p] = get_framesize_uncompressed(p, inst->width, + inst->height); + + inst->num_output_bufs = *num_buffers; + inst->output_buf_size = sizes[0]; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int vdec_verify_conf(struct venus_inst *inst) +{ + struct hfi_buffer_requirements bufreq; + int ret; + + if (!inst->num_input_bufs || !inst->num_output_bufs) + return -EINVAL; + + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_OUTPUT, &bufreq); + if (ret) + return ret; + + if (inst->num_output_bufs < bufreq.count_actual || + inst->num_output_bufs < bufreq.count_min) + return -EINVAL; + + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_INPUT, &bufreq); + if (ret) + return ret; + + if (inst->num_input_bufs < bufreq.count_min) + return -EINVAL; + + return 0; +} + +static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct venus_inst *inst = vb2_get_drv_priv(q); + struct venus_core *core = inst->core; + u32 ptype; + int ret; + + mutex_lock(&inst->lock); + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + inst->streamon_out = 1; + else + inst->streamon_cap = 1; + + if (!(inst->streamon_out & inst->streamon_cap)) { + mutex_unlock(&inst->lock); + return 0; + } + + venus_helper_init_instance(inst); + + inst->reconfig = false; + inst->sequence_cap = 0; + inst->sequence_out = 0; + inst->cmd_stop = false; + + ret = vdec_init_session(inst); + if (ret) + goto bufs_done; + + ret = vdec_set_properties(inst); + if (ret) + goto deinit_sess; + + if (core->res->hfi_version == HFI_VERSION_3XX) { + struct hfi_buffer_size_actual buf_sz; + + ptype = HFI_PROPERTY_PARAM_BUFFER_SIZE_ACTUAL; + buf_sz.type = HFI_BUFFER_OUTPUT; + buf_sz.size = inst->output_buf_size; + + ret = hfi_session_set_property(inst, ptype, &buf_sz); + if (ret) + goto deinit_sess; + } + + ret = vdec_verify_conf(inst); + if (ret) + goto deinit_sess; + + ret = venus_helper_set_num_bufs(inst, inst->num_input_bufs, + VB2_MAX_FRAME); + if (ret) + goto deinit_sess; + + ret = venus_helper_vb2_start_streaming(inst); + if (ret) + goto deinit_sess; + + mutex_unlock(&inst->lock); + + return 0; + +deinit_sess: + hfi_session_deinit(inst); +bufs_done: + venus_helper_buffers_done(inst, VB2_BUF_STATE_QUEUED); + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + inst->streamon_out = 0; + else + inst->streamon_cap = 0; + mutex_unlock(&inst->lock); + return ret; +} + +static const struct vb2_ops vdec_vb2_ops = { + .queue_setup = vdec_queue_setup, + .buf_init = venus_helper_vb2_buf_init, + .buf_prepare = venus_helper_vb2_buf_prepare, + .start_streaming = vdec_start_streaming, + .stop_streaming = venus_helper_vb2_stop_streaming, + .buf_queue = venus_helper_vb2_buf_queue, +}; + +static void vdec_buf_done(struct venus_inst *inst, unsigned int buf_type, + u32 tag, u32 bytesused, u32 data_offset, u32 flags, + u32 hfi_flags, u64 timestamp_us) +{ + enum vb2_buffer_state state = VB2_BUF_STATE_DONE; + struct vb2_v4l2_buffer *vbuf; + struct vb2_buffer *vb; + unsigned int type; + + if (buf_type == HFI_BUFFER_INPUT) + type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + else + type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + + vbuf = venus_helper_find_buf(inst, type, tag); + if (!vbuf) + return; + + vbuf->flags = flags; + vbuf->field = V4L2_FIELD_NONE; + + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + vb = &vbuf->vb2_buf; + vb->planes[0].bytesused = + max_t(unsigned int, inst->output_buf_size, bytesused); + vb->planes[0].data_offset = data_offset; + vb->timestamp = timestamp_us * NSEC_PER_USEC; + vbuf->sequence = inst->sequence_cap++; + + if (inst->cmd_stop) { + vbuf->flags |= V4L2_BUF_FLAG_LAST; + inst->cmd_stop = false; + } + + if (vbuf->flags & V4L2_BUF_FLAG_LAST) { + const struct v4l2_event ev = { .type = V4L2_EVENT_EOS }; + + v4l2_event_queue_fh(&inst->fh, &ev); + } + } else { + vbuf->sequence = inst->sequence_out++; + } + + if (hfi_flags & HFI_BUFFERFLAG_READONLY) + venus_helper_acquire_buf_ref(vbuf); + + if (hfi_flags & HFI_BUFFERFLAG_DATACORRUPT) + state = VB2_BUF_STATE_ERROR; + + v4l2_m2m_buf_done(vbuf, state); +} + +static void vdec_event_notify(struct venus_inst *inst, u32 event, + struct hfi_event_data *data) +{ + struct venus_core *core = inst->core; + struct device *dev = core->dev_dec; + static const struct v4l2_event ev = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION }; + + switch (event) { + case EVT_SESSION_ERROR: + inst->session_error = true; + dev_err(dev, "dec: event session error %x\n", inst->error); + break; + case EVT_SYS_EVENT_CHANGE: + switch (data->event_type) { + case HFI_EVENT_DATA_SEQUENCE_CHANGED_SUFFICIENT_BUF_RESOURCES: + hfi_session_continue(inst); + dev_dbg(dev, "event sufficient resources\n"); + break; + case HFI_EVENT_DATA_SEQUENCE_CHANGED_INSUFFICIENT_BUF_RESOURCES: + inst->reconfig_height = data->height; + inst->reconfig_width = data->width; + inst->reconfig = true; + + v4l2_event_queue_fh(&inst->fh, &ev); + + dev_dbg(dev, "event not sufficient resources (%ux%u)\n", + data->width, data->height); + break; + case HFI_EVENT_RELEASE_BUFFER_REFERENCE: + venus_helper_release_buf_ref(inst, data->tag); + break; + default: + break; + } + break; + default: + break; + } +} + +static const struct hfi_inst_ops vdec_hfi_ops = { + .buf_done = vdec_buf_done, + .event_notify = vdec_event_notify, +}; + +static void vdec_inst_init(struct venus_inst *inst) +{ + inst->fmt_out = &vdec_formats[6]; + inst->fmt_cap = &vdec_formats[0]; + inst->width = 1280; + inst->height = ALIGN(720, 32); + inst->out_width = 1280; + inst->out_height = 720; + inst->fps = 30; + inst->timeperframe.numerator = 1; + inst->timeperframe.denominator = 30; + + inst->cap_width.min = 64; + inst->cap_width.max = 1920; + if (inst->core->res->hfi_version == HFI_VERSION_3XX) + inst->cap_width.max = 3840; + inst->cap_width.step_size = 1; + inst->cap_height.min = 64; + inst->cap_height.max = ALIGN(1080, 32); + if (inst->core->res->hfi_version == HFI_VERSION_3XX) + inst->cap_height.max = ALIGN(2160, 32); + inst->cap_height.step_size = 1; + inst->cap_framerate.min = 1; + inst->cap_framerate.max = 30; + inst->cap_framerate.step_size = 1; + inst->cap_mbs_per_frame.min = 16; + inst->cap_mbs_per_frame.max = 8160; +} + +static const struct v4l2_m2m_ops vdec_m2m_ops = { + .device_run = venus_helper_m2m_device_run, + .job_abort = venus_helper_m2m_job_abort, +}; + +static int m2m_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct venus_inst *inst = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->ops = &vdec_vb2_ops; + src_vq->mem_ops = &vb2_dma_sg_memops; + src_vq->drv_priv = inst; + src_vq->buf_struct_size = sizeof(struct venus_buffer); + src_vq->allow_zero_bytesused = 1; + src_vq->min_buffers_needed = 1; + src_vq->dev = inst->core->dev; + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->ops = &vdec_vb2_ops; + dst_vq->mem_ops = &vb2_dma_sg_memops; + dst_vq->drv_priv = inst; + dst_vq->buf_struct_size = sizeof(struct venus_buffer); + dst_vq->allow_zero_bytesused = 1; + dst_vq->min_buffers_needed = 1; + dst_vq->dev = inst->core->dev; + ret = vb2_queue_init(dst_vq); + if (ret) { + vb2_queue_release(src_vq); + return ret; + } + + return 0; +} + +static int vdec_open(struct file *file) +{ + struct venus_core *core = video_drvdata(file); + struct venus_inst *inst; + int ret; + + inst = kzalloc(sizeof(*inst), GFP_KERNEL); + if (!inst) + return -ENOMEM; + + INIT_LIST_HEAD(&inst->registeredbufs); + INIT_LIST_HEAD(&inst->internalbufs); + INIT_LIST_HEAD(&inst->list); + mutex_init(&inst->lock); + + inst->core = core; + inst->session_type = VIDC_SESSION_TYPE_DEC; + inst->num_output_bufs = 1; + + venus_helper_init_instance(inst); + + ret = pm_runtime_get_sync(core->dev_dec); + if (ret < 0) + goto err_free_inst; + + ret = vdec_ctrl_init(inst); + if (ret) + goto err_put_sync; + + ret = hfi_session_create(inst, &vdec_hfi_ops); + if (ret) + goto err_ctrl_deinit; + + vdec_inst_init(inst); + + /* + * create m2m device for every instance, the m2m context scheduling + * is made by firmware side so we do not need to care about. + */ + inst->m2m_dev = v4l2_m2m_init(&vdec_m2m_ops); + if (IS_ERR(inst->m2m_dev)) { + ret = PTR_ERR(inst->m2m_dev); + goto err_session_destroy; + } + + inst->m2m_ctx = v4l2_m2m_ctx_init(inst->m2m_dev, inst, m2m_queue_init); + if (IS_ERR(inst->m2m_ctx)) { + ret = PTR_ERR(inst->m2m_ctx); + goto err_m2m_release; + } + + v4l2_fh_init(&inst->fh, core->vdev_dec); + + inst->fh.ctrl_handler = &inst->ctrl_handler; + v4l2_fh_add(&inst->fh); + inst->fh.m2m_ctx = inst->m2m_ctx; + file->private_data = &inst->fh; + + return 0; + +err_m2m_release: + v4l2_m2m_release(inst->m2m_dev); +err_session_destroy: + hfi_session_destroy(inst); +err_ctrl_deinit: + vdec_ctrl_deinit(inst); +err_put_sync: + pm_runtime_put_sync(core->dev_dec); +err_free_inst: + kfree(inst); + return ret; +} + +static int vdec_close(struct file *file) +{ + struct venus_inst *inst = to_inst(file); + + v4l2_m2m_ctx_release(inst->m2m_ctx); + v4l2_m2m_release(inst->m2m_dev); + vdec_ctrl_deinit(inst); + hfi_session_destroy(inst); + mutex_destroy(&inst->lock); + v4l2_fh_del(&inst->fh); + v4l2_fh_exit(&inst->fh); + + pm_runtime_put_sync(inst->core->dev_dec); + + kfree(inst); + return 0; +} + +static const struct v4l2_file_operations vdec_fops = { + .owner = THIS_MODULE, + .open = vdec_open, + .release = vdec_close, + .unlocked_ioctl = video_ioctl2, + .poll = v4l2_m2m_fop_poll, + .mmap = v4l2_m2m_fop_mmap, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = v4l2_compat_ioctl32, +#endif +}; + +static int vdec_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct video_device *vdev; + struct venus_core *core; + int ret; + + if (!dev->parent) + return -EPROBE_DEFER; + + core = dev_get_drvdata(dev->parent); + if (!core) + return -EPROBE_DEFER; + + if (core->res->hfi_version == HFI_VERSION_3XX) { + core->core0_clk = devm_clk_get(dev, "core"); + if (IS_ERR(core->core0_clk)) + return PTR_ERR(core->core0_clk); + } + + platform_set_drvdata(pdev, core); + + vdev = video_device_alloc(); + if (!vdev) + return -ENOMEM; + + vdev->release = video_device_release; + vdev->fops = &vdec_fops; + vdev->ioctl_ops = &vdec_ioctl_ops; + vdev->vfl_dir = VFL_DIR_M2M; + vdev->v4l2_dev = &core->v4l2_dev; + vdev->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; + + ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + if (ret) + goto err_vdev_release; + + core->vdev_dec = vdev; + core->dev_dec = dev; + + video_set_drvdata(vdev, core); + pm_runtime_enable(dev); + + return 0; + +err_vdev_release: + video_device_release(vdev); + return ret; +} + +static int vdec_remove(struct platform_device *pdev) +{ + struct venus_core *core = dev_get_drvdata(pdev->dev.parent); + + video_unregister_device(core->vdev_dec); + pm_runtime_disable(core->dev_dec); + + return 0; +} + +#ifdef CONFIG_PM +static int vdec_runtime_suspend(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + + if (core->res->hfi_version == HFI_VERSION_1XX) + return 0; + + writel(0, core->base + WRAPPER_VDEC_VCODEC_POWER_CONTROL); + clk_disable_unprepare(core->core0_clk); + writel(1, core->base + WRAPPER_VDEC_VCODEC_POWER_CONTROL); + + return 0; +} + +static int vdec_runtime_resume(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + int ret; + + if (core->res->hfi_version == HFI_VERSION_1XX) + return 0; + + writel(0, core->base + WRAPPER_VDEC_VCODEC_POWER_CONTROL); + ret = clk_prepare_enable(core->core0_clk); + writel(1, core->base + WRAPPER_VDEC_VCODEC_POWER_CONTROL); + + return ret; +} +#endif + +static const struct dev_pm_ops vdec_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(vdec_runtime_suspend, vdec_runtime_resume, NULL) +}; + +static const struct of_device_id vdec_dt_match[] = { + { .compatible = "venus-decoder" }, + { } +}; +MODULE_DEVICE_TABLE(of, vdec_dt_match); + +static struct platform_driver qcom_venus_dec_driver = { + .probe = vdec_probe, + .remove = vdec_remove, + .driver = { + .name = "qcom-venus-decoder", + .of_match_table = vdec_dt_match, + .pm = &vdec_pm_ops, + }, +}; +module_platform_driver(qcom_venus_dec_driver); + +MODULE_ALIAS("platform:qcom-venus-decoder"); +MODULE_DESCRIPTION("Qualcomm Venus video decoder driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/qcom/venus/vdec.h b/drivers/media/platform/qcom/venus/vdec.h new file mode 100644 index 000000000000..84b672c54d02 --- /dev/null +++ b/drivers/media/platform/qcom/venus/vdec.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __VENUS_VDEC_H__ +#define __VENUS_VDEC_H__ + +struct venus_inst; + +int vdec_ctrl_init(struct venus_inst *inst); +void vdec_ctrl_deinit(struct venus_inst *inst); + +#endif diff --git a/drivers/media/platform/qcom/venus/vdec_ctrls.c b/drivers/media/platform/qcom/venus/vdec_ctrls.c new file mode 100644 index 000000000000..032839bbc967 --- /dev/null +++ b/drivers/media/platform/qcom/venus/vdec_ctrls.c @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/types.h> +#include <media/v4l2-ctrls.h> + +#include "core.h" +#include "vdec.h" + +static int vdec_op_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct venus_inst *inst = ctrl_to_inst(ctrl); + struct vdec_controls *ctr = &inst->controls.dec; + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER: + ctr->post_loop_deb_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: + case V4L2_CID_MPEG_VIDEO_VPX_PROFILE: + ctr->profile = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL: + ctr->level = ctrl->val; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vdec_op_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct venus_inst *inst = ctrl_to_inst(ctrl); + struct vdec_controls *ctr = &inst->controls.dec; + union hfi_get_property hprop; + u32 ptype = HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT; + int ret; + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: + case V4L2_CID_MPEG_VIDEO_VPX_PROFILE: + ret = hfi_session_get_property(inst, ptype, &hprop); + if (!ret) + ctr->profile = hprop.profile_level.profile; + ctrl->val = ctr->profile; + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL: + ret = hfi_session_get_property(inst, ptype, &hprop); + if (!ret) + ctr->level = hprop.profile_level.level; + ctrl->val = ctr->level; + break; + case V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER: + ctrl->val = ctr->post_loop_deb_mode; + break; + case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: + ctrl->val = inst->num_output_bufs; + break; + default: + return -EINVAL; + }; + + return 0; +} + +static const struct v4l2_ctrl_ops vdec_ctrl_ops = { + .s_ctrl = vdec_op_s_ctrl, + .g_volatile_ctrl = vdec_op_g_volatile_ctrl, +}; + +int vdec_ctrl_init(struct venus_inst *inst) +{ + struct v4l2_ctrl *ctrl; + int ret; + + ret = v4l2_ctrl_handler_init(&inst->ctrl_handler, 7); + if (ret) + return ret; + + ctrl = v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE, + V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_CODING_EFFICIENCY, + ~((1 << V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE) | + (1 << V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE)), + V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + ctrl = v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL, + V4L2_MPEG_VIDEO_MPEG4_LEVEL_5, + 0, V4L2_MPEG_VIDEO_MPEG4_LEVEL_0); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + ctrl = v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH, + ~((1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH)), + V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + ctrl = v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LEVEL, + V4L2_MPEG_VIDEO_H264_LEVEL_5_1, + 0, V4L2_MPEG_VIDEO_H264_LEVEL_1_0); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + ctrl = v4l2_ctrl_new_std(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_VPX_PROFILE, 0, 3, 1, 0); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + v4l2_ctrl_new_std(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER, 0, 1, 1, 0); + + ctrl = v4l2_ctrl_new_std(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 1, 32, 1, 1); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + ret = inst->ctrl_handler.error; + if (ret) { + v4l2_ctrl_handler_free(&inst->ctrl_handler); + return ret; + } + + return 0; +} + +void vdec_ctrl_deinit(struct venus_inst *inst) +{ + v4l2_ctrl_handler_free(&inst->ctrl_handler); +} diff --git a/drivers/media/platform/qcom/venus/venc.c b/drivers/media/platform/qcom/venus/venc.c new file mode 100644 index 000000000000..39748e7a08e4 --- /dev/null +++ b/drivers/media/platform/qcom/venus/venc.c @@ -0,0 +1,1283 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <media/v4l2-mem2mem.h> +#include <media/videobuf2-dma-sg.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ctrls.h> + +#include "hfi_venus_io.h" +#include "core.h" +#include "helpers.h" +#include "venc.h" + +#define NUM_B_FRAMES_MAX 4 + +static u32 get_framesize_uncompressed(unsigned int plane, u32 width, u32 height) +{ + u32 y_stride, uv_stride, y_plane; + u32 y_sclines, uv_sclines, uv_plane; + u32 size; + + y_stride = ALIGN(width, 128); + uv_stride = ALIGN(width, 128); + y_sclines = ALIGN(height, 32); + uv_sclines = ALIGN(((height + 1) >> 1), 16); + + y_plane = y_stride * y_sclines; + uv_plane = uv_stride * uv_sclines + SZ_4K; + size = y_plane + uv_plane + SZ_8K; + size = ALIGN(size, SZ_4K); + + return size; +} + +static u32 get_framesize_compressed(u32 width, u32 height) +{ + u32 sz = ALIGN(height, 32) * ALIGN(width, 32) * 3 / 2 / 2; + + return ALIGN(sz, SZ_4K); +} + +/* + * Three resons to keep MPLANE formats (despite that the number of planes + * currently is one): + * - the MPLANE formats allow only one plane to be used + * - the downstream driver use MPLANE formats too + * - future firmware versions could add support for >1 planes + */ +static const struct venus_format venc_formats[] = { + { + .pixfmt = V4L2_PIX_FMT_NV12, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG4, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_H263, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_H264, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_VP8, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_VP9, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, +}; + +static const struct venus_format *find_format(u32 pixfmt, u32 type) +{ + const struct venus_format *fmt = venc_formats; + unsigned int size = ARRAY_SIZE(venc_formats); + unsigned int i; + + for (i = 0; i < size; i++) { + if (fmt[i].pixfmt == pixfmt) + break; + } + + if (i == size || fmt[i].type != type) + return NULL; + + return &fmt[i]; +} + +static const struct venus_format * +find_format_by_index(unsigned int index, u32 type) +{ + const struct venus_format *fmt = venc_formats; + unsigned int size = ARRAY_SIZE(venc_formats); + unsigned int i, k = 0; + + if (index > size) + return NULL; + + for (i = 0; i < size; i++) { + if (fmt[i].type != type) + continue; + if (k == index) + break; + k++; + } + + if (i == size) + return NULL; + + return &fmt[i]; +} + +static int venc_v4l2_to_hfi(int id, int value) +{ + switch (id) { + case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL: + switch (value) { + case V4L2_MPEG_VIDEO_MPEG4_LEVEL_0: + default: + return HFI_MPEG4_LEVEL_0; + case V4L2_MPEG_VIDEO_MPEG4_LEVEL_0B: + return HFI_MPEG4_LEVEL_0b; + case V4L2_MPEG_VIDEO_MPEG4_LEVEL_1: + return HFI_MPEG4_LEVEL_1; + case V4L2_MPEG_VIDEO_MPEG4_LEVEL_2: + return HFI_MPEG4_LEVEL_2; + case V4L2_MPEG_VIDEO_MPEG4_LEVEL_3: + return HFI_MPEG4_LEVEL_3; + case V4L2_MPEG_VIDEO_MPEG4_LEVEL_4: + return HFI_MPEG4_LEVEL_4; + case V4L2_MPEG_VIDEO_MPEG4_LEVEL_5: + return HFI_MPEG4_LEVEL_5; + } + case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: + switch (value) { + case V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE: + default: + return HFI_MPEG4_PROFILE_SIMPLE; + case V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE: + return HFI_MPEG4_PROFILE_ADVANCEDSIMPLE; + } + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + switch (value) { + case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE: + return HFI_H264_PROFILE_BASELINE; + case V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE: + return HFI_H264_PROFILE_CONSTRAINED_BASE; + case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN: + return HFI_H264_PROFILE_MAIN; + case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH: + default: + return HFI_H264_PROFILE_HIGH; + } + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + switch (value) { + case V4L2_MPEG_VIDEO_H264_LEVEL_1_0: + return HFI_H264_LEVEL_1; + case V4L2_MPEG_VIDEO_H264_LEVEL_1B: + return HFI_H264_LEVEL_1b; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_1: + return HFI_H264_LEVEL_11; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_2: + return HFI_H264_LEVEL_12; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_3: + return HFI_H264_LEVEL_13; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_0: + return HFI_H264_LEVEL_2; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_1: + return HFI_H264_LEVEL_21; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_2: + return HFI_H264_LEVEL_22; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_0: + return HFI_H264_LEVEL_3; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_1: + return HFI_H264_LEVEL_31; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_2: + return HFI_H264_LEVEL_32; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_0: + return HFI_H264_LEVEL_4; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_1: + return HFI_H264_LEVEL_41; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_2: + return HFI_H264_LEVEL_42; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_0: + default: + return HFI_H264_LEVEL_5; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_1: + return HFI_H264_LEVEL_51; + } + case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE: + switch (value) { + case V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC: + default: + return HFI_H264_ENTROPY_CAVLC; + case V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC: + return HFI_H264_ENTROPY_CABAC; + } + case V4L2_CID_MPEG_VIDEO_VPX_PROFILE: + switch (value) { + case 0: + default: + return HFI_VPX_PROFILE_VERSION_0; + case 1: + return HFI_VPX_PROFILE_VERSION_1; + case 2: + return HFI_VPX_PROFILE_VERSION_2; + case 3: + return HFI_VPX_PROFILE_VERSION_3; + } + } + + return 0; +} + +static int +venc_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + strlcpy(cap->driver, "qcom-venus", sizeof(cap->driver)); + strlcpy(cap->card, "Qualcomm Venus video encoder", sizeof(cap->card)); + strlcpy(cap->bus_info, "platform:qcom-venus", sizeof(cap->bus_info)); + + return 0; +} + +static int venc_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + const struct venus_format *fmt; + + fmt = find_format_by_index(f->index, f->type); + + memset(f->reserved, 0, sizeof(f->reserved)); + + if (!fmt) + return -EINVAL; + + f->pixelformat = fmt->pixfmt; + + return 0; +} + +static const struct venus_format * +venc_try_fmt_common(struct venus_inst *inst, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + struct v4l2_plane_pix_format *pfmt = pixmp->plane_fmt; + const struct venus_format *fmt; + unsigned int p; + + memset(pfmt[0].reserved, 0, sizeof(pfmt[0].reserved)); + memset(pixmp->reserved, 0, sizeof(pixmp->reserved)); + + fmt = find_format(pixmp->pixelformat, f->type); + if (!fmt) { + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + pixmp->pixelformat = V4L2_PIX_FMT_H264; + else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + pixmp->pixelformat = V4L2_PIX_FMT_NV12; + else + return NULL; + fmt = find_format(pixmp->pixelformat, f->type); + pixmp->width = 1280; + pixmp->height = 720; + } + + pixmp->width = clamp(pixmp->width, inst->cap_width.min, + inst->cap_width.max); + pixmp->height = clamp(pixmp->height, inst->cap_height.min, + inst->cap_height.max); + + if (inst->core->res->hfi_version == HFI_VERSION_1XX) + pixmp->height = ALIGN(pixmp->height, 32); + + pixmp->width = ALIGN(pixmp->width, 2); + pixmp->height = ALIGN(pixmp->height, 2); + + if (pixmp->field == V4L2_FIELD_ANY) + pixmp->field = V4L2_FIELD_NONE; + pixmp->num_planes = fmt->num_planes; + pixmp->flags = 0; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + for (p = 0; p < pixmp->num_planes; p++) { + pfmt[p].sizeimage = + get_framesize_uncompressed(p, pixmp->width, + pixmp->height); + + pfmt[p].bytesperline = ALIGN(pixmp->width, 128); + } + } else { + pfmt[0].sizeimage = get_framesize_compressed(pixmp->width, + pixmp->height); + pfmt[0].bytesperline = 0; + } + + return fmt; +} + +static int venc_try_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct venus_inst *inst = to_inst(file); + + venc_try_fmt_common(inst, f); + + return 0; +} + +static int venc_s_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct venus_inst *inst = to_inst(file); + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + struct v4l2_pix_format_mplane orig_pixmp; + const struct venus_format *fmt; + struct v4l2_format format; + u32 pixfmt_out = 0, pixfmt_cap = 0; + + orig_pixmp = *pixmp; + + fmt = venc_try_fmt_common(inst, f); + if (!fmt) + return -EINVAL; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + pixfmt_out = pixmp->pixelformat; + pixfmt_cap = inst->fmt_cap->pixfmt; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + pixfmt_cap = pixmp->pixelformat; + pixfmt_out = inst->fmt_out->pixfmt; + } + + memset(&format, 0, sizeof(format)); + + format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + format.fmt.pix_mp.pixelformat = pixfmt_out; + format.fmt.pix_mp.width = orig_pixmp.width; + format.fmt.pix_mp.height = orig_pixmp.height; + venc_try_fmt_common(inst, &format); + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + inst->out_width = format.fmt.pix_mp.width; + inst->out_height = format.fmt.pix_mp.height; + inst->colorspace = pixmp->colorspace; + inst->ycbcr_enc = pixmp->ycbcr_enc; + inst->quantization = pixmp->quantization; + inst->xfer_func = pixmp->xfer_func; + } + + memset(&format, 0, sizeof(format)); + + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + format.fmt.pix_mp.pixelformat = pixfmt_cap; + format.fmt.pix_mp.width = orig_pixmp.width; + format.fmt.pix_mp.height = orig_pixmp.height; + venc_try_fmt_common(inst, &format); + + inst->width = format.fmt.pix_mp.width; + inst->height = format.fmt.pix_mp.height; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + inst->fmt_out = fmt; + else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + inst->fmt_cap = fmt; + + return 0; +} + +static int venc_g_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + struct venus_inst *inst = to_inst(file); + const struct venus_format *fmt; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + fmt = inst->fmt_cap; + else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + fmt = inst->fmt_out; + else + return -EINVAL; + + pixmp->pixelformat = fmt->pixfmt; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + pixmp->width = inst->width; + pixmp->height = inst->height; + pixmp->colorspace = inst->colorspace; + pixmp->ycbcr_enc = inst->ycbcr_enc; + pixmp->quantization = inst->quantization; + pixmp->xfer_func = inst->xfer_func; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + pixmp->width = inst->out_width; + pixmp->height = inst->out_height; + } + + venc_try_fmt_common(inst, f); + + return 0; +} + +static int +venc_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct venus_inst *inst = to_inst(file); + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + s->r.width = inst->width; + s->r.height = inst->height; + break; + case V4L2_SEL_TGT_CROP: + s->r.width = inst->out_width; + s->r.height = inst->out_height; + break; + default: + return -EINVAL; + } + + s->r.top = 0; + s->r.left = 0; + + return 0; +} + +static int +venc_s_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct venus_inst *inst = to_inst(file); + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + if (s->r.width != inst->out_width || + s->r.height != inst->out_height || + s->r.top != 0 || s->r.left != 0) + return -EINVAL; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int venc_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct venus_inst *inst = to_inst(file); + struct v4l2_outputparm *out = &a->parm.output; + struct v4l2_fract *timeperframe = &out->timeperframe; + u64 us_per_frame, fps; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + memset(out->reserved, 0, sizeof(out->reserved)); + + if (!timeperframe->denominator) + timeperframe->denominator = inst->timeperframe.denominator; + if (!timeperframe->numerator) + timeperframe->numerator = inst->timeperframe.numerator; + + out->capability = V4L2_CAP_TIMEPERFRAME; + + us_per_frame = timeperframe->numerator * (u64)USEC_PER_SEC; + do_div(us_per_frame, timeperframe->denominator); + + if (!us_per_frame) + return -EINVAL; + + fps = (u64)USEC_PER_SEC; + do_div(fps, us_per_frame); + + inst->timeperframe = *timeperframe; + inst->fps = fps; + + return 0; +} + +static int venc_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct venus_inst *inst = to_inst(file); + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + a->parm.output.capability |= V4L2_CAP_TIMEPERFRAME; + a->parm.output.timeperframe = inst->timeperframe; + + return 0; +} + +static int venc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct venus_inst *inst = to_inst(file); + const struct venus_format *fmt; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + + fmt = find_format(fsize->pixel_format, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (!fmt) { + fmt = find_format(fsize->pixel_format, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + if (!fmt) + return -EINVAL; + } + + if (fsize->index) + return -EINVAL; + + fsize->stepwise.min_width = inst->cap_width.min; + fsize->stepwise.max_width = inst->cap_width.max; + fsize->stepwise.step_width = inst->cap_width.step_size; + fsize->stepwise.min_height = inst->cap_height.min; + fsize->stepwise.max_height = inst->cap_height.max; + fsize->stepwise.step_height = inst->cap_height.step_size; + + return 0; +} + +static int venc_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *fival) +{ + struct venus_inst *inst = to_inst(file); + const struct venus_format *fmt; + + fival->type = V4L2_FRMIVAL_TYPE_STEPWISE; + + fmt = find_format(fival->pixel_format, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (!fmt) { + fmt = find_format(fival->pixel_format, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + if (!fmt) + return -EINVAL; + } + + if (fival->index) + return -EINVAL; + + if (!fival->width || !fival->height) + return -EINVAL; + + if (fival->width > inst->cap_width.max || + fival->width < inst->cap_width.min || + fival->height > inst->cap_height.max || + fival->height < inst->cap_height.min) + return -EINVAL; + + fival->stepwise.min.numerator = 1; + fival->stepwise.min.denominator = inst->cap_framerate.max; + fival->stepwise.max.numerator = 1; + fival->stepwise.max.denominator = inst->cap_framerate.min; + fival->stepwise.step.numerator = 1; + fival->stepwise.step.denominator = inst->cap_framerate.max; + + return 0; +} + +static const struct v4l2_ioctl_ops venc_ioctl_ops = { + .vidioc_querycap = venc_querycap, + .vidioc_enum_fmt_vid_cap_mplane = venc_enum_fmt, + .vidioc_enum_fmt_vid_out_mplane = venc_enum_fmt, + .vidioc_s_fmt_vid_cap_mplane = venc_s_fmt, + .vidioc_s_fmt_vid_out_mplane = venc_s_fmt, + .vidioc_g_fmt_vid_cap_mplane = venc_g_fmt, + .vidioc_g_fmt_vid_out_mplane = venc_g_fmt, + .vidioc_try_fmt_vid_cap_mplane = venc_try_fmt, + .vidioc_try_fmt_vid_out_mplane = venc_try_fmt, + .vidioc_g_selection = venc_g_selection, + .vidioc_s_selection = venc_s_selection, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + .vidioc_s_parm = venc_s_parm, + .vidioc_g_parm = venc_g_parm, + .vidioc_enum_framesizes = venc_enum_framesizes, + .vidioc_enum_frameintervals = venc_enum_frameintervals, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int venc_set_properties(struct venus_inst *inst) +{ + struct venc_controls *ctr = &inst->controls.enc; + struct hfi_intra_period intra_period; + struct hfi_profile_level pl; + struct hfi_framerate frate; + struct hfi_bitrate brate; + struct hfi_idr_period idrp; + u32 ptype, rate_control, bitrate, profile = 0, level = 0; + int ret; + + ptype = HFI_PROPERTY_CONFIG_FRAME_RATE; + frate.buffer_type = HFI_BUFFER_OUTPUT; + frate.framerate = inst->fps * (1 << 16); + + ret = hfi_session_set_property(inst, ptype, &frate); + if (ret) + return ret; + + if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_H264) { + struct hfi_h264_vui_timing_info info; + + ptype = HFI_PROPERTY_PARAM_VENC_H264_VUI_TIMING_INFO; + info.enable = 1; + info.fixed_framerate = 1; + info.time_scale = NSEC_PER_SEC; + + ret = hfi_session_set_property(inst, ptype, &info); + if (ret) + return ret; + } + + ptype = HFI_PROPERTY_CONFIG_VENC_IDR_PERIOD; + idrp.idr_period = ctr->gop_size; + ret = hfi_session_set_property(inst, ptype, &idrp); + if (ret) + return ret; + + if (ctr->num_b_frames) { + u32 max_num_b_frames = NUM_B_FRAMES_MAX; + + ptype = HFI_PROPERTY_PARAM_VENC_MAX_NUM_B_FRAMES; + ret = hfi_session_set_property(inst, ptype, &max_num_b_frames); + if (ret) + return ret; + } + + /* intra_period = pframes + bframes + 1 */ + if (!ctr->num_p_frames) + ctr->num_p_frames = 2 * 15 - 1, + + ptype = HFI_PROPERTY_CONFIG_VENC_INTRA_PERIOD; + intra_period.pframes = ctr->num_p_frames; + intra_period.bframes = ctr->num_b_frames; + + ret = hfi_session_set_property(inst, ptype, &intra_period); + if (ret) + return ret; + + if (ctr->bitrate_mode == V4L2_MPEG_VIDEO_BITRATE_MODE_VBR) + rate_control = HFI_RATE_CONTROL_VBR_CFR; + else + rate_control = HFI_RATE_CONTROL_CBR_CFR; + + ptype = HFI_PROPERTY_PARAM_VENC_RATE_CONTROL; + ret = hfi_session_set_property(inst, ptype, &rate_control); + if (ret) + return ret; + + if (!ctr->bitrate) + bitrate = 64000; + else + bitrate = ctr->bitrate; + + ptype = HFI_PROPERTY_CONFIG_VENC_TARGET_BITRATE; + brate.bitrate = bitrate; + brate.layer_id = 0; + + ret = hfi_session_set_property(inst, ptype, &brate); + if (ret) + return ret; + + if (!ctr->bitrate_peak) + bitrate *= 2; + else + bitrate = ctr->bitrate_peak; + + ptype = HFI_PROPERTY_CONFIG_VENC_MAX_BITRATE; + brate.bitrate = bitrate; + brate.layer_id = 0; + + ret = hfi_session_set_property(inst, ptype, &brate); + if (ret) + return ret; + + if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_H264) { + profile = venc_v4l2_to_hfi(V4L2_CID_MPEG_VIDEO_H264_PROFILE, + ctr->profile.h264); + level = venc_v4l2_to_hfi(V4L2_CID_MPEG_VIDEO_H264_LEVEL, + ctr->level.h264); + } else if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_VP8) { + profile = venc_v4l2_to_hfi(V4L2_CID_MPEG_VIDEO_VPX_PROFILE, + ctr->profile.vpx); + level = 0; + } else if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_MPEG4) { + profile = venc_v4l2_to_hfi(V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE, + ctr->profile.mpeg4); + level = venc_v4l2_to_hfi(V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL, + ctr->level.mpeg4); + } else if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_H263) { + profile = 0; + level = 0; + } + + ptype = HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT; + pl.profile = profile; + pl.level = level; + + ret = hfi_session_set_property(inst, ptype, &pl); + if (ret) + return ret; + + return 0; +} + +static int venc_init_session(struct venus_inst *inst) +{ + int ret; + + ret = hfi_session_init(inst, inst->fmt_cap->pixfmt); + if (ret) + return ret; + + ret = venus_helper_set_input_resolution(inst, inst->out_width, + inst->out_height); + if (ret) + goto deinit; + + ret = venus_helper_set_output_resolution(inst, inst->width, + inst->height); + if (ret) + goto deinit; + + ret = venus_helper_set_color_format(inst, inst->fmt_out->pixfmt); + if (ret) + goto deinit; + + return 0; +deinit: + hfi_session_deinit(inst); + return ret; +} + +static int venc_out_num_buffers(struct venus_inst *inst, unsigned int *num) +{ + struct hfi_buffer_requirements bufreq; + int ret; + + ret = venc_init_session(inst); + if (ret) + return ret; + + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_INPUT, &bufreq); + + *num = bufreq.count_actual; + + hfi_session_deinit(inst); + + return ret; +} + +static int venc_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct venus_inst *inst = vb2_get_drv_priv(q); + unsigned int p, num, min = 4; + int ret = 0; + + if (*num_planes) { + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + *num_planes != inst->fmt_out->num_planes) + return -EINVAL; + + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + *num_planes != inst->fmt_cap->num_planes) + return -EINVAL; + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + sizes[0] < inst->input_buf_size) + return -EINVAL; + + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + sizes[0] < inst->output_buf_size) + return -EINVAL; + + return 0; + } + + switch (q->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + *num_planes = inst->fmt_out->num_planes; + + ret = venc_out_num_buffers(inst, &num); + if (ret) + break; + + num = max(num, min); + *num_buffers = max(*num_buffers, num); + inst->num_input_bufs = *num_buffers; + + for (p = 0; p < *num_planes; ++p) + sizes[p] = get_framesize_uncompressed(p, inst->width, + inst->height); + inst->input_buf_size = sizes[0]; + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + *num_planes = inst->fmt_cap->num_planes; + *num_buffers = max(*num_buffers, min); + inst->num_output_bufs = *num_buffers; + sizes[0] = get_framesize_compressed(inst->width, inst->height); + inst->output_buf_size = sizes[0]; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int venc_verify_conf(struct venus_inst *inst) +{ + struct hfi_buffer_requirements bufreq; + int ret; + + if (!inst->num_input_bufs || !inst->num_output_bufs) + return -EINVAL; + + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_OUTPUT, &bufreq); + if (ret) + return ret; + + if (inst->num_output_bufs < bufreq.count_actual || + inst->num_output_bufs < bufreq.count_min) + return -EINVAL; + + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_INPUT, &bufreq); + if (ret) + return ret; + + if (inst->num_input_bufs < bufreq.count_actual || + inst->num_input_bufs < bufreq.count_min) + return -EINVAL; + + return 0; +} + +static int venc_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct venus_inst *inst = vb2_get_drv_priv(q); + int ret; + + mutex_lock(&inst->lock); + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + inst->streamon_out = 1; + else + inst->streamon_cap = 1; + + if (!(inst->streamon_out & inst->streamon_cap)) { + mutex_unlock(&inst->lock); + return 0; + } + + venus_helper_init_instance(inst); + + inst->sequence_cap = 0; + inst->sequence_out = 0; + + ret = venc_init_session(inst); + if (ret) + goto bufs_done; + + ret = venc_set_properties(inst); + if (ret) + goto deinit_sess; + + ret = venc_verify_conf(inst); + if (ret) + goto deinit_sess; + + ret = venus_helper_set_num_bufs(inst, inst->num_input_bufs, + inst->num_output_bufs); + if (ret) + goto deinit_sess; + + ret = venus_helper_vb2_start_streaming(inst); + if (ret) + goto deinit_sess; + + mutex_unlock(&inst->lock); + + return 0; + +deinit_sess: + hfi_session_deinit(inst); +bufs_done: + venus_helper_buffers_done(inst, VB2_BUF_STATE_QUEUED); + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + inst->streamon_out = 0; + else + inst->streamon_cap = 0; + mutex_unlock(&inst->lock); + return ret; +} + +static const struct vb2_ops venc_vb2_ops = { + .queue_setup = venc_queue_setup, + .buf_init = venus_helper_vb2_buf_init, + .buf_prepare = venus_helper_vb2_buf_prepare, + .start_streaming = venc_start_streaming, + .stop_streaming = venus_helper_vb2_stop_streaming, + .buf_queue = venus_helper_vb2_buf_queue, +}; + +static void venc_buf_done(struct venus_inst *inst, unsigned int buf_type, + u32 tag, u32 bytesused, u32 data_offset, u32 flags, + u32 hfi_flags, u64 timestamp_us) +{ + struct vb2_v4l2_buffer *vbuf; + struct vb2_buffer *vb; + unsigned int type; + + if (buf_type == HFI_BUFFER_INPUT) + type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + else + type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + + vbuf = venus_helper_find_buf(inst, type, tag); + if (!vbuf) + return; + + vb = &vbuf->vb2_buf; + vb->planes[0].bytesused = bytesused; + vb->planes[0].data_offset = data_offset; + + vbuf->flags = flags; + + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + vb->timestamp = timestamp_us * NSEC_PER_USEC; + vbuf->sequence = inst->sequence_cap++; + } else { + vbuf->sequence = inst->sequence_out++; + } + + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE); +} + +static void venc_event_notify(struct venus_inst *inst, u32 event, + struct hfi_event_data *data) +{ + struct device *dev = inst->core->dev_enc; + + if (event == EVT_SESSION_ERROR) { + inst->session_error = true; + dev_err(dev, "enc: event session error %x\n", inst->error); + } +} + +static const struct hfi_inst_ops venc_hfi_ops = { + .buf_done = venc_buf_done, + .event_notify = venc_event_notify, +}; + +static const struct v4l2_m2m_ops venc_m2m_ops = { + .device_run = venus_helper_m2m_device_run, + .job_abort = venus_helper_m2m_job_abort, +}; + +static int m2m_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct venus_inst *inst = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->ops = &venc_vb2_ops; + src_vq->mem_ops = &vb2_dma_sg_memops; + src_vq->drv_priv = inst; + src_vq->buf_struct_size = sizeof(struct venus_buffer); + src_vq->allow_zero_bytesused = 1; + src_vq->min_buffers_needed = 1; + src_vq->dev = inst->core->dev; + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->ops = &venc_vb2_ops; + dst_vq->mem_ops = &vb2_dma_sg_memops; + dst_vq->drv_priv = inst; + dst_vq->buf_struct_size = sizeof(struct venus_buffer); + dst_vq->allow_zero_bytesused = 1; + dst_vq->min_buffers_needed = 1; + dst_vq->dev = inst->core->dev; + ret = vb2_queue_init(dst_vq); + if (ret) { + vb2_queue_release(src_vq); + return ret; + } + + return 0; +} + +static void venc_inst_init(struct venus_inst *inst) +{ + inst->fmt_cap = &venc_formats[2]; + inst->fmt_out = &venc_formats[0]; + inst->width = 1280; + inst->height = ALIGN(720, 32); + inst->out_width = 1280; + inst->out_height = 720; + inst->fps = 15; + inst->timeperframe.numerator = 1; + inst->timeperframe.denominator = 15; + + inst->cap_width.min = 96; + inst->cap_width.max = 1920; + if (inst->core->res->hfi_version == HFI_VERSION_3XX) + inst->cap_width.max = 3840; + inst->cap_width.step_size = 2; + inst->cap_height.min = 64; + inst->cap_height.max = ALIGN(1080, 32); + if (inst->core->res->hfi_version == HFI_VERSION_3XX) + inst->cap_height.max = ALIGN(2160, 32); + inst->cap_height.step_size = 2; + inst->cap_framerate.min = 1; + inst->cap_framerate.max = 30; + inst->cap_framerate.step_size = 1; + inst->cap_mbs_per_frame.min = 24; + inst->cap_mbs_per_frame.max = 8160; +} + +static int venc_open(struct file *file) +{ + struct venus_core *core = video_drvdata(file); + struct venus_inst *inst; + int ret; + + inst = kzalloc(sizeof(*inst), GFP_KERNEL); + if (!inst) + return -ENOMEM; + + INIT_LIST_HEAD(&inst->registeredbufs); + INIT_LIST_HEAD(&inst->internalbufs); + INIT_LIST_HEAD(&inst->list); + mutex_init(&inst->lock); + + inst->core = core; + inst->session_type = VIDC_SESSION_TYPE_ENC; + + venus_helper_init_instance(inst); + + ret = pm_runtime_get_sync(core->dev_enc); + if (ret < 0) + goto err_free_inst; + + ret = venc_ctrl_init(inst); + if (ret) + goto err_put_sync; + + ret = hfi_session_create(inst, &venc_hfi_ops); + if (ret) + goto err_ctrl_deinit; + + venc_inst_init(inst); + + /* + * create m2m device for every instance, the m2m context scheduling + * is made by firmware side so we do not need to care about. + */ + inst->m2m_dev = v4l2_m2m_init(&venc_m2m_ops); + if (IS_ERR(inst->m2m_dev)) { + ret = PTR_ERR(inst->m2m_dev); + goto err_session_destroy; + } + + inst->m2m_ctx = v4l2_m2m_ctx_init(inst->m2m_dev, inst, m2m_queue_init); + if (IS_ERR(inst->m2m_ctx)) { + ret = PTR_ERR(inst->m2m_ctx); + goto err_m2m_release; + } + + v4l2_fh_init(&inst->fh, core->vdev_enc); + + inst->fh.ctrl_handler = &inst->ctrl_handler; + v4l2_fh_add(&inst->fh); + inst->fh.m2m_ctx = inst->m2m_ctx; + file->private_data = &inst->fh; + + return 0; + +err_m2m_release: + v4l2_m2m_release(inst->m2m_dev); +err_session_destroy: + hfi_session_destroy(inst); +err_ctrl_deinit: + venc_ctrl_deinit(inst); +err_put_sync: + pm_runtime_put_sync(core->dev_enc); +err_free_inst: + kfree(inst); + return ret; +} + +static int venc_close(struct file *file) +{ + struct venus_inst *inst = to_inst(file); + + v4l2_m2m_ctx_release(inst->m2m_ctx); + v4l2_m2m_release(inst->m2m_dev); + venc_ctrl_deinit(inst); + hfi_session_destroy(inst); + mutex_destroy(&inst->lock); + v4l2_fh_del(&inst->fh); + v4l2_fh_exit(&inst->fh); + + pm_runtime_put_sync(inst->core->dev_enc); + + kfree(inst); + return 0; +} + +static const struct v4l2_file_operations venc_fops = { + .owner = THIS_MODULE, + .open = venc_open, + .release = venc_close, + .unlocked_ioctl = video_ioctl2, + .poll = v4l2_m2m_fop_poll, + .mmap = v4l2_m2m_fop_mmap, +#ifdef CONFIG_COMPAT + .compat_ioctl32 = v4l2_compat_ioctl32, +#endif +}; + +static int venc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct video_device *vdev; + struct venus_core *core; + int ret; + + if (!dev->parent) + return -EPROBE_DEFER; + + core = dev_get_drvdata(dev->parent); + if (!core) + return -EPROBE_DEFER; + + if (core->res->hfi_version == HFI_VERSION_3XX) { + core->core1_clk = devm_clk_get(dev, "core"); + if (IS_ERR(core->core1_clk)) + return PTR_ERR(core->core1_clk); + } + + platform_set_drvdata(pdev, core); + + vdev = video_device_alloc(); + if (!vdev) + return -ENOMEM; + + vdev->release = video_device_release; + vdev->fops = &venc_fops; + vdev->ioctl_ops = &venc_ioctl_ops; + vdev->vfl_dir = VFL_DIR_M2M; + vdev->v4l2_dev = &core->v4l2_dev; + vdev->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; + + ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + if (ret) + goto err_vdev_release; + + core->vdev_enc = vdev; + core->dev_enc = dev; + + video_set_drvdata(vdev, core); + pm_runtime_enable(dev); + + return 0; + +err_vdev_release: + video_device_release(vdev); + return ret; +} + +static int venc_remove(struct platform_device *pdev) +{ + struct venus_core *core = dev_get_drvdata(pdev->dev.parent); + + video_unregister_device(core->vdev_enc); + pm_runtime_disable(core->dev_enc); + + return 0; +} + +#ifdef CONFIG_PM +static int venc_runtime_suspend(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + + if (core->res->hfi_version == HFI_VERSION_1XX) + return 0; + + writel(0, core->base + WRAPPER_VENC_VCODEC_POWER_CONTROL); + clk_disable_unprepare(core->core1_clk); + writel(1, core->base + WRAPPER_VENC_VCODEC_POWER_CONTROL); + + return 0; +} + +static int venc_runtime_resume(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + int ret; + + if (core->res->hfi_version == HFI_VERSION_1XX) + return 0; + + writel(0, core->base + WRAPPER_VENC_VCODEC_POWER_CONTROL); + ret = clk_prepare_enable(core->core1_clk); + writel(1, core->base + WRAPPER_VENC_VCODEC_POWER_CONTROL); + + return ret; +} +#endif + +static const struct dev_pm_ops venc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(venc_runtime_suspend, venc_runtime_resume, NULL) +}; + +static const struct of_device_id venc_dt_match[] = { + { .compatible = "venus-encoder" }, + { } +}; +MODULE_DEVICE_TABLE(of, venc_dt_match); + +static struct platform_driver qcom_venus_enc_driver = { + .probe = venc_probe, + .remove = venc_remove, + .driver = { + .name = "qcom-venus-encoder", + .of_match_table = venc_dt_match, + .pm = &venc_pm_ops, + }, +}; +module_platform_driver(qcom_venus_enc_driver); + +MODULE_ALIAS("platform:qcom-venus-encoder"); +MODULE_DESCRIPTION("Qualcomm Venus video encoder driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/qcom/venus/venc.h b/drivers/media/platform/qcom/venus/venc.h new file mode 100644 index 000000000000..9daca669f307 --- /dev/null +++ b/drivers/media/platform/qcom/venus/venc.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __VENUS_VENC_H__ +#define __VENUS_VENC_H__ + +struct venus_inst; + +int venc_ctrl_init(struct venus_inst *inst); +void venc_ctrl_deinit(struct venus_inst *inst); + +#endif diff --git a/drivers/media/platform/qcom/venus/venc_ctrls.c b/drivers/media/platform/qcom/venus/venc_ctrls.c new file mode 100644 index 000000000000..ab0fe51ff0f7 --- /dev/null +++ b/drivers/media/platform/qcom/venus/venc_ctrls.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/types.h> +#include <media/v4l2-ctrls.h> + +#include "core.h" +#include "venc.h" + +#define BITRATE_MIN 32000 +#define BITRATE_MAX 160000000 +#define BITRATE_DEFAULT 1000000 +#define BITRATE_DEFAULT_PEAK (BITRATE_DEFAULT * 2) +#define BITRATE_STEP 100 +#define SLICE_BYTE_SIZE_MAX 1024 +#define SLICE_BYTE_SIZE_MIN 1024 +#define SLICE_MB_SIZE_MAX 300 +#define INTRA_REFRESH_MBS_MAX 300 +#define AT_SLICE_BOUNDARY \ + V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY + +static int venc_op_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct venus_inst *inst = ctrl_to_inst(ctrl); + struct venc_controls *ctr = &inst->controls.enc; + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + ctr->bitrate_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE: + ctr->bitrate = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE_PEAK: + ctr->bitrate_peak = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE: + ctr->h264_entropy_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: + ctr->profile.mpeg4 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + ctr->profile.h264 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_PROFILE: + ctr->profile.vpx = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL: + ctr->level.mpeg4 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + ctr->level.h264 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: + ctr->h264_i_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: + ctr->h264_p_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP: + ctr->h264_b_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_MIN_QP: + ctr->h264_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_MAX_QP: + ctr->h264_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE: + ctr->multi_slice_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES: + ctr->multi_slice_max_bytes = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB: + ctr->multi_slice_max_mb = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA: + ctr->h264_loop_filter_alpha = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA: + ctr->h264_loop_filter_beta = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE: + ctr->h264_loop_filter_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEADER_MODE: + ctr->header_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB: + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + ctr->gop_size = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD: + ctr->h264_i_period = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_MIN_QP: + ctr->vp8_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_MAX_QP: + ctr->vp8_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_B_FRAMES: + ctr->num_b_frames = ctrl->val; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops venc_ctrl_ops = { + .s_ctrl = venc_op_s_ctrl, +}; + +int venc_ctrl_init(struct venus_inst *inst) +{ + int ret; + + ret = v4l2_ctrl_handler_init(&inst->ctrl_handler, 27); + if (ret) + return ret; + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE_MODE, + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, + ~((1 << V4L2_MPEG_VIDEO_BITRATE_MODE_VBR) | + (1 << V4L2_MPEG_VIDEO_BITRATE_MODE_CBR)), + V4L2_MPEG_VIDEO_BITRATE_MODE_VBR); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE, + V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC, + 0, V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE, + V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_CODING_EFFICIENCY, + ~((1 << V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE) | + (1 << V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE)), + V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL, + V4L2_MPEG_VIDEO_MPEG4_LEVEL_5, + 0, V4L2_MPEG_VIDEO_MPEG4_LEVEL_0); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH, + ~((1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH)), + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LEVEL, + V4L2_MPEG_VIDEO_H264_LEVEL_5_1, + 0, V4L2_MPEG_VIDEO_H264_LEVEL_1_0); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE, + AT_SLICE_BOUNDARY, + 0, V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEADER_MODE, + V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME, + 1 << V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME, + V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE, + V4L2_MPEG_VIDEO_MULTI_SICE_MODE_MAX_BYTES, + 0, V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE, BITRATE_MIN, BITRATE_MAX, + BITRATE_STEP, BITRATE_DEFAULT); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, BITRATE_MIN, BITRATE_MAX, + BITRATE_STEP, BITRATE_DEFAULT_PEAK); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_VPX_PROFILE, 0, 3, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP, 1, 51, 1, 26); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP, 1, 51, 1, 28); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP, 1, 51, 1, 30); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MIN_QP, 1, 51, 1, 1); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MAX_QP, 1, 51, 1, 51); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES, SLICE_BYTE_SIZE_MIN, + SLICE_BYTE_SIZE_MAX, 1, SLICE_BYTE_SIZE_MIN); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB, 1, + SLICE_MB_SIZE_MAX, 1, 1); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA, -6, 6, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA, -6, 6, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB, + 0, INTRA_REFRESH_MBS_MAX, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, 0, (1 << 16) - 1, 1, 12); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_VPX_MIN_QP, 1, 128, 1, 1); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_VPX_MAX_QP, 1, 128, 1, 128); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_B_FRAMES, 0, 4, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, 0, (1 << 16) - 1, 1, 0); + + ret = inst->ctrl_handler.error; + if (ret) + goto err; + + ret = v4l2_ctrl_handler_setup(&inst->ctrl_handler); + if (ret) + goto err; + + return 0; +err: + v4l2_ctrl_handler_free(&inst->ctrl_handler); + return ret; +} + +void venc_ctrl_deinit(struct venus_inst *inst) +{ + v4l2_ctrl_handler_free(&inst->ctrl_handler); +} diff --git a/drivers/media/platform/rcar-vin/Kconfig b/drivers/media/platform/rcar-vin/Kconfig index 111d2a151f6a..af4c98b44d2e 100644 --- a/drivers/media/platform/rcar-vin/Kconfig +++ b/drivers/media/platform/rcar-vin/Kconfig @@ -3,6 +3,7 @@ config VIDEO_RCAR_VIN depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && OF && HAS_DMA && MEDIA_CONTROLLER depends on ARCH_RENESAS || COMPILE_TEST select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE ---help--- Support for Renesas R-Car Video Input (VIN) driver. Supports R-Car Gen2 SoCs. diff --git a/drivers/media/platform/rcar-vin/rcar-core.c b/drivers/media/platform/rcar-vin/rcar-core.c index 098a0b1cc10a..77dff047c41c 100644 --- a/drivers/media/platform/rcar-vin/rcar-core.c +++ b/drivers/media/platform/rcar-vin/rcar-core.c @@ -21,7 +21,7 @@ #include <linux/platform_device.h> #include <linux/pm_runtime.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include "rcar-vin.h" @@ -31,6 +31,20 @@ #define notifier_to_vin(n) container_of(n, struct rvin_dev, notifier) +static int rvin_find_pad(struct v4l2_subdev *sd, int direction) +{ + unsigned int pad; + + if (sd->entity.num_pads <= 1) + return 0; + + for (pad = 0; pad < sd->entity.num_pads; pad++) + if (sd->entity.pads[pad].flags & direction) + return pad; + + return -EINVAL; +} + static bool rvin_mbus_supported(struct rvin_graph_entity *entity) { struct v4l2_subdev *sd = entity->subdev; @@ -39,6 +53,7 @@ static bool rvin_mbus_supported(struct rvin_graph_entity *entity) }; code.index = 0; + code.pad = entity->source_pad; while (!v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code)) { code.index++; switch (code.code) { @@ -86,14 +101,9 @@ static void rvin_digital_notify_unbind(struct v4l2_async_notifier *notifier, { struct rvin_dev *vin = notifier_to_vin(notifier); - if (vin->digital.subdev == subdev) { - vin_dbg(vin, "unbind digital subdev %s\n", subdev->name); - rvin_v4l2_remove(vin); - vin->digital.subdev = NULL; - return; - } - - vin_err(vin, "no entity for subdev %s to unbind\n", subdev->name); + vin_dbg(vin, "unbind digital subdev %s\n", subdev->name); + rvin_v4l2_remove(vin); + vin->digital.subdev = NULL; } static int rvin_digital_notify_bound(struct v4l2_async_notifier *notifier, @@ -101,27 +111,37 @@ static int rvin_digital_notify_bound(struct v4l2_async_notifier *notifier, struct v4l2_async_subdev *asd) { struct rvin_dev *vin = notifier_to_vin(notifier); + int ret; v4l2_set_subdev_hostdata(subdev, vin); - if (vin->digital.asd.match.of.node == subdev->dev->of_node) { - vin_dbg(vin, "bound digital subdev %s\n", subdev->name); - vin->digital.subdev = subdev; - return 0; - } + /* Find source and sink pad of remote subdevice */ - vin_err(vin, "no entity for subdev %s to bind\n", subdev->name); - return -EINVAL; + ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SOURCE); + if (ret < 0) + return ret; + vin->digital.source_pad = ret; + + ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SINK); + vin->digital.sink_pad = ret < 0 ? 0 : ret; + + vin->digital.subdev = subdev; + + vin_dbg(vin, "bound subdev %s source pad: %u sink pad: %u\n", + subdev->name, vin->digital.source_pad, + vin->digital.sink_pad); + + return 0; } static int rvin_digitial_parse_v4l2(struct rvin_dev *vin, struct device_node *ep, struct v4l2_mbus_config *mbus_cfg) { - struct v4l2_of_endpoint v4l2_ep; + struct v4l2_fwnode_endpoint v4l2_ep; int ret; - ret = v4l2_of_parse_endpoint(ep, &v4l2_ep); + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &v4l2_ep); if (ret) { vin_err(vin, "Could not parse v4l2 endpoint\n"); return -EINVAL; @@ -151,7 +171,7 @@ static int rvin_digital_graph_parse(struct rvin_dev *vin) struct device_node *ep, *np; int ret; - vin->digital.asd.match.of.node = NULL; + vin->digital.asd.match.fwnode.fwnode = NULL; vin->digital.subdev = NULL; /* @@ -175,8 +195,8 @@ static int rvin_digital_graph_parse(struct rvin_dev *vin) if (ret) return ret; - vin->digital.asd.match.of.node = np; - vin->digital.asd.match_type = V4L2_ASYNC_MATCH_OF; + vin->digital.asd.match.fwnode.fwnode = of_fwnode_handle(np); + vin->digital.asd.match_type = V4L2_ASYNC_MATCH_FWNODE; return 0; } @@ -190,7 +210,7 @@ static int rvin_digital_graph_init(struct rvin_dev *vin) if (ret) return ret; - if (!vin->digital.asd.match.of.node) { + if (!vin->digital.asd.match.fwnode.fwnode) { vin_dbg(vin, "No digital subdevice found\n"); return -ENODEV; } @@ -203,7 +223,7 @@ static int rvin_digital_graph_init(struct rvin_dev *vin) subdevs[0] = &vin->digital.asd; vin_dbg(vin, "Found digital subdevice %s\n", - of_node_full_name(subdevs[0]->match.of.node)); + of_node_full_name(to_of_node(subdevs[0]->match.fwnode.fwnode))); vin->notifier.num_subdevs = 1; vin->notifier.subdevs = subdevs; diff --git a/drivers/media/platform/rcar-vin/rcar-dma.c b/drivers/media/platform/rcar-vin/rcar-dma.c index 9ccd5ff55e19..b136844499f6 100644 --- a/drivers/media/platform/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/rcar-vin/rcar-dma.c @@ -119,6 +119,15 @@ #define VNDMR2_FTEV (1 << 17) #define VNDMR2_VLV(n) ((n & 0xf) << 12) +struct rvin_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \ + struct rvin_buffer, \ + vb)->list) + static void rvin_write(struct rvin_dev *vin, u32 value, u32 offset) { iowrite32(value, vin->base + offset); @@ -269,48 +278,6 @@ static int rvin_setup(struct rvin_dev *vin) return 0; } -static void rvin_capture_on(struct rvin_dev *vin) -{ - vin_dbg(vin, "Capture on in %s mode\n", - vin->continuous ? "continuous" : "single"); - - if (vin->continuous) - /* Continuous Frame Capture Mode */ - rvin_write(vin, VNFC_C_FRAME, VNFC_REG); - else - /* Single Frame Capture Mode */ - rvin_write(vin, VNFC_S_FRAME, VNFC_REG); -} - -static void rvin_capture_off(struct rvin_dev *vin) -{ - /* Set continuous & single transfer off */ - rvin_write(vin, 0, VNFC_REG); -} - -static int rvin_capture_start(struct rvin_dev *vin) -{ - int ret; - - rvin_crop_scale_comp(vin); - - ret = rvin_setup(vin); - if (ret) - return ret; - - rvin_capture_on(vin); - - return 0; -} - -static void rvin_capture_stop(struct rvin_dev *vin) -{ - rvin_capture_off(vin); - - /* Disable module */ - rvin_write(vin, rvin_read(vin, VNMC_REG) & ~VNMC_ME, VNMC_REG); -} - static void rvin_disable_interrupts(struct rvin_dev *vin) { rvin_write(vin, 0, VNIE_REG); @@ -377,6 +344,99 @@ static void rvin_set_slot_addr(struct rvin_dev *vin, int slot, dma_addr_t addr) rvin_write(vin, offset, VNMB_REG(slot)); } +/* Moves a buffer from the queue to the HW slots */ +static bool rvin_fill_hw_slot(struct rvin_dev *vin, int slot) +{ + struct rvin_buffer *buf; + struct vb2_v4l2_buffer *vbuf; + dma_addr_t phys_addr_top; + + if (vin->queue_buf[slot] != NULL) + return true; + + if (list_empty(&vin->buf_list)) + return false; + + vin_dbg(vin, "Filling HW slot: %d\n", slot); + + /* Keep track of buffer we give to HW */ + buf = list_entry(vin->buf_list.next, struct rvin_buffer, list); + vbuf = &buf->vb; + list_del_init(to_buf_list(vbuf)); + vin->queue_buf[slot] = vbuf; + + /* Setup DMA */ + phys_addr_top = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); + rvin_set_slot_addr(vin, slot, phys_addr_top); + + return true; +} + +static bool rvin_fill_hw(struct rvin_dev *vin) +{ + int slot, limit; + + limit = vin->continuous ? HW_BUFFER_NUM : 1; + + for (slot = 0; slot < limit; slot++) + if (!rvin_fill_hw_slot(vin, slot)) + return false; + return true; +} + +static void rvin_capture_on(struct rvin_dev *vin) +{ + vin_dbg(vin, "Capture on in %s mode\n", + vin->continuous ? "continuous" : "single"); + + if (vin->continuous) + /* Continuous Frame Capture Mode */ + rvin_write(vin, VNFC_C_FRAME, VNFC_REG); + else + /* Single Frame Capture Mode */ + rvin_write(vin, VNFC_S_FRAME, VNFC_REG); +} + +static int rvin_capture_start(struct rvin_dev *vin) +{ + struct rvin_buffer *buf, *node; + int bufs, ret; + + /* Count number of free buffers */ + bufs = 0; + list_for_each_entry_safe(buf, node, &vin->buf_list, list) + bufs++; + + /* Continuous capture requires more buffers then there are HW slots */ + vin->continuous = bufs > HW_BUFFER_NUM; + + if (!rvin_fill_hw(vin)) { + vin_err(vin, "HW not ready to start, not enough buffers available\n"); + return -EINVAL; + } + + rvin_crop_scale_comp(vin); + + ret = rvin_setup(vin); + if (ret) + return ret; + + rvin_capture_on(vin); + + vin->state = RUNNING; + + return 0; +} + +static void rvin_capture_stop(struct rvin_dev *vin) +{ + /* Set continuous & single transfer off */ + rvin_write(vin, 0, VNFC_REG); + + /* Disable module */ + rvin_write(vin, rvin_read(vin, VNMC_REG) & ~VNMC_ME, VNMC_REG); +} + /* ----------------------------------------------------------------------------- * Crop and Scaling Gen2 */ @@ -839,61 +899,12 @@ void rvin_scale_try(struct rvin_dev *vin, struct v4l2_pix_format *pix, #define RVIN_TIMEOUT_MS 100 #define RVIN_RETRIES 10 -struct rvin_buffer { - struct vb2_v4l2_buffer vb; - struct list_head list; -}; - -#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \ - struct rvin_buffer, \ - vb)->list) - -/* Moves a buffer from the queue to the HW slots */ -static bool rvin_fill_hw_slot(struct rvin_dev *vin, int slot) -{ - struct rvin_buffer *buf; - struct vb2_v4l2_buffer *vbuf; - dma_addr_t phys_addr_top; - - if (vin->queue_buf[slot] != NULL) - return true; - - if (list_empty(&vin->buf_list)) - return false; - - vin_dbg(vin, "Filling HW slot: %d\n", slot); - - /* Keep track of buffer we give to HW */ - buf = list_entry(vin->buf_list.next, struct rvin_buffer, list); - vbuf = &buf->vb; - list_del_init(to_buf_list(vbuf)); - vin->queue_buf[slot] = vbuf; - - /* Setup DMA */ - phys_addr_top = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); - rvin_set_slot_addr(vin, slot, phys_addr_top); - - return true; -} - -static bool rvin_fill_hw(struct rvin_dev *vin) -{ - int slot, limit; - - limit = vin->continuous ? HW_BUFFER_NUM : 1; - - for (slot = 0; slot < limit; slot++) - if (!rvin_fill_hw_slot(vin, slot)) - return false; - return true; -} - static irqreturn_t rvin_irq(int irq, void *data) { struct rvin_dev *vin = data; u32 int_status, vnms; int slot; - unsigned int sequence, handled = 0; + unsigned int i, sequence, handled = 0; unsigned long flags; spin_lock_irqsave(&vin->qlock, flags); @@ -955,8 +966,20 @@ static irqreturn_t rvin_irq(int irq, void *data) * the VnMBm registers. */ if (vin->continuous) { - rvin_capture_off(vin); + rvin_capture_stop(vin); vin_dbg(vin, "IRQ %02d: hw not ready stop\n", sequence); + + /* Maybe we can continue in single capture mode */ + for (i = 0; i < HW_BUFFER_NUM; i++) { + if (vin->queue_buf[i]) { + list_add(to_buf_list(vin->queue_buf[i]), + &vin->buf_list); + vin->queue_buf[i] = NULL; + } + } + + if (!list_empty(&vin->buf_list)) + rvin_capture_start(vin); } } else { /* @@ -1041,8 +1064,7 @@ static void rvin_buffer_queue(struct vb2_buffer *vb) * capturing if HW is ready to continue. */ if (vin->state == STALLED) - if (rvin_fill_hw(vin)) - rvin_capture_on(vin); + rvin_capture_start(vin); spin_unlock_irqrestore(&vin->qlock, flags); } @@ -1059,25 +1081,9 @@ static int rvin_start_streaming(struct vb2_queue *vq, unsigned int count) spin_lock_irqsave(&vin->qlock, flags); - vin->state = RUNNING; vin->sequence = 0; - /* Continuous capture requires more buffers then there are HW slots */ - vin->continuous = count > HW_BUFFER_NUM; - - /* - * This should never happen but if we don't have enough - * buffers for HW bail out - */ - if (!rvin_fill_hw(vin)) { - vin_err(vin, "HW not ready to start, not enough buffers available\n"); - ret = -EINVAL; - goto out; - } - ret = rvin_capture_start(vin); -out: - /* Return all buffers if something went wrong */ if (ret) { return_all_buffers(vin, VB2_BUF_STATE_QUEUED); v4l2_subdev_call(sd, video, s_stream, 0); @@ -1183,7 +1189,7 @@ int rvin_dma_probe(struct rvin_dev *vin, int irq) q->ops = &rvin_qops; q->mem_ops = &vb2_dma_contig_memops; q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; - q->min_buffers_needed = 2; + q->min_buffers_needed = 1; q->dev = vin->dev; ret = vb2_queue_init(q); diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c b/drivers/media/platform/rcar-vin/rcar-v4l2.c index 2bbe6d495fa6..dd37ea811680 100644 --- a/drivers/media/platform/rcar-vin/rcar-v4l2.c +++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c @@ -111,7 +111,7 @@ static int rvin_reset_format(struct rvin_dev *vin) struct v4l2_mbus_framefmt *mf = &fmt.format; int ret; - fmt.pad = vin->src_pad_idx; + fmt.pad = vin->digital.source_pad; ret = v4l2_subdev_call(vin_to_source(vin), pad, get_fmt, NULL, &fmt); if (ret) @@ -151,6 +151,9 @@ static int rvin_reset_format(struct rvin_dev *vin) rvin_reset_crop_compose(vin); + vin->format.bytesperline = rvin_format_bytesperline(&vin->format); + vin->format.sizeimage = rvin_format_sizeimage(&vin->format); + return 0; } @@ -175,7 +178,7 @@ static int __rvin_try_format_source(struct rvin_dev *vin, if (pad_cfg == NULL) return -ENOMEM; - format.pad = vin->src_pad_idx; + format.pad = vin->digital.source_pad; field = pix->field; @@ -203,8 +206,8 @@ static int __rvin_try_format(struct rvin_dev *vin, struct v4l2_pix_format *pix, struct rvin_source_fmt *source) { - const struct rvin_video_format *info; u32 rwidth, rheight, walign; + int ret; /* Requested */ rwidth = pix->width; @@ -214,17 +217,11 @@ static int __rvin_try_format(struct rvin_dev *vin, if (pix->field == V4L2_FIELD_ANY) pix->field = vin->format.field; - /* - * Retrieve format information and select the current format if the - * requested format isn't supported. - */ - info = rvin_format_from_pixel(pix->pixelformat); - if (!info) { - vin_dbg(vin, "Format %x not found, keeping %x\n", - pix->pixelformat, vin->format.pixelformat); - *pix = vin->format; - pix->width = rwidth; - pix->height = rheight; + /* If requested format is not supported fallback to the default */ + if (!rvin_format_from_pixel(pix->pixelformat)) { + vin_dbg(vin, "Format 0x%x not found, using default 0x%x\n", + pix->pixelformat, RVIN_DEFAULT_FORMAT); + pix->pixelformat = RVIN_DEFAULT_FORMAT; } /* Always recalculate */ @@ -232,7 +229,9 @@ static int __rvin_try_format(struct rvin_dev *vin, pix->sizeimage = 0; /* Limit to source capabilities */ - __rvin_try_format_source(vin, which, pix, source); + ret = __rvin_try_format_source(vin, which, pix, source); + if (ret) + return ret; switch (pix->field) { case V4L2_FIELD_TOP: @@ -480,10 +479,14 @@ static int rvin_enum_input(struct file *file, void *priv, return ret; i->type = V4L2_INPUT_TYPE_CAMERA; - i->std = vin->vdev.tvnorms; - if (v4l2_subdev_has_op(sd, pad, dv_timings_cap)) + if (v4l2_subdev_has_op(sd, pad, dv_timings_cap)) { i->capabilities = V4L2_IN_CAP_DV_TIMINGS; + i->std = 0; + } else { + i->capabilities = V4L2_IN_CAP_STD; + i->std = vin->vdev.tvnorms; + } strlcpy(i->name, "Camera", sizeof(i->name)); @@ -547,14 +550,16 @@ static int rvin_enum_dv_timings(struct file *file, void *priv_fh, { struct rvin_dev *vin = video_drvdata(file); struct v4l2_subdev *sd = vin_to_source(vin); - int pad, ret; + int ret; + + if (timings->pad) + return -EINVAL; - pad = timings->pad; - timings->pad = vin->sink_pad_idx; + timings->pad = vin->digital.sink_pad; ret = v4l2_subdev_call(sd, pad, enum_dv_timings, timings); - timings->pad = pad; + timings->pad = 0; return ret; } @@ -570,12 +575,8 @@ static int rvin_s_dv_timings(struct file *file, void *priv_fh, if (ret) return ret; - vin->source.width = timings->bt.width; - vin->source.height = timings->bt.height; - vin->format.width = timings->bt.width; - vin->format.height = timings->bt.height; - - return 0; + /* Changing the timings will change the width/height */ + return rvin_reset_format(vin); } static int rvin_g_dv_timings(struct file *file, void *priv_fh, @@ -601,14 +602,16 @@ static int rvin_dv_timings_cap(struct file *file, void *priv_fh, { struct rvin_dev *vin = video_drvdata(file); struct v4l2_subdev *sd = vin_to_source(vin); - int pad, ret; + int ret; + + if (cap->pad) + return -EINVAL; - pad = cap->pad; - cap->pad = vin->sink_pad_idx; + cap->pad = vin->digital.sink_pad; ret = v4l2_subdev_call(sd, pad, dv_timings_cap, cap); - cap->pad = pad; + cap->pad = 0; return ret; } @@ -617,17 +620,16 @@ static int rvin_g_edid(struct file *file, void *fh, struct v4l2_edid *edid) { struct rvin_dev *vin = video_drvdata(file); struct v4l2_subdev *sd = vin_to_source(vin); - int input, ret; + int ret; if (edid->pad) return -EINVAL; - input = edid->pad; - edid->pad = vin->sink_pad_idx; + edid->pad = vin->digital.sink_pad; ret = v4l2_subdev_call(sd, pad, get_edid, edid); - edid->pad = input; + edid->pad = 0; return ret; } @@ -636,17 +638,16 @@ static int rvin_s_edid(struct file *file, void *fh, struct v4l2_edid *edid) { struct rvin_dev *vin = video_drvdata(file); struct v4l2_subdev *sd = vin_to_source(vin); - int input, ret; + int ret; if (edid->pad) return -EINVAL; - input = edid->pad; - edid->pad = vin->sink_pad_idx; + edid->pad = vin->digital.sink_pad; ret = v4l2_subdev_call(sd, pad, set_edid, edid); - edid->pad = input; + edid->pad = 0; return ret; } @@ -869,7 +870,7 @@ int rvin_v4l2_probe(struct rvin_dev *vin) { struct video_device *vdev = &vin->vdev; struct v4l2_subdev *sd = vin_to_source(vin); - int pad_idx, ret; + int ret; v4l2_set_subdev_hostdata(sd, vin); @@ -915,22 +916,6 @@ int rvin_v4l2_probe(struct rvin_dev *vin) vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; - vin->src_pad_idx = 0; - for (pad_idx = 0; pad_idx < sd->entity.num_pads; pad_idx++) - if (sd->entity.pads[pad_idx].flags == MEDIA_PAD_FL_SOURCE) - break; - if (pad_idx >= sd->entity.num_pads) - return -EINVAL; - - vin->src_pad_idx = pad_idx; - - vin->sink_pad_idx = 0; - for (pad_idx = 0; pad_idx < sd->entity.num_pads; pad_idx++) - if (sd->entity.pads[pad_idx].flags == MEDIA_PAD_FL_SINK) { - vin->sink_pad_idx = pad_idx; - break; - } - vin->format.pixelformat = RVIN_DEFAULT_FORMAT; rvin_reset_format(vin); diff --git a/drivers/media/platform/rcar-vin/rcar-vin.h b/drivers/media/platform/rcar-vin/rcar-vin.h index 727e215c0871..9bfb5a7c4dc4 100644 --- a/drivers/media/platform/rcar-vin/rcar-vin.h +++ b/drivers/media/platform/rcar-vin/rcar-vin.h @@ -74,6 +74,8 @@ struct rvin_video_format { * @subdev: subdevice matched using async framework * @code: Media bus format from source * @mbus_cfg: Media bus format from DT + * @source_pad: source pad of remote subdevice + * @sink_pad: sink pad of remote subdevice */ struct rvin_graph_entity { struct v4l2_async_subdev asd; @@ -81,6 +83,9 @@ struct rvin_graph_entity { u32 code; struct v4l2_mbus_config mbus_cfg; + + unsigned int source_pad; + unsigned int sink_pad; }; /** @@ -91,8 +96,6 @@ struct rvin_graph_entity { * * @vdev: V4L2 video device associated with VIN * @v4l2_dev: V4L2 device - * @src_pad_idx: source pad index for media controller drivers - * @sink_pad_idx: sink pad index for media controller drivers * @ctrl_handler: V4L2 control handler * @notifier: V4L2 asynchronous subdevs notifier * @digital: entity in the DT for local digital subdevice @@ -121,8 +124,6 @@ struct rvin_dev { struct video_device vdev; struct v4l2_device v4l2_dev; - int src_pad_idx; - int sink_pad_idx; struct v4l2_ctrl_handler ctrl_handler; struct v4l2_async_notifier notifier; struct rvin_graph_entity digital; diff --git a/drivers/media/platform/rcar_drif.c b/drivers/media/platform/rcar_drif.c new file mode 100644 index 000000000000..522364ff0d5d --- /dev/null +++ b/drivers/media/platform/rcar_drif.c @@ -0,0 +1,1498 @@ +/* + * R-Car Gen3 Digital Radio Interface (DRIF) driver + * + * Copyright (C) 2017 Renesas Electronics Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * The R-Car DRIF is a receive only MSIOF like controller with an + * external master device driving the SCK. It receives data into a FIFO, + * then this driver uses the SYS-DMAC engine to move the data from + * the device to memory. + * + * Each DRIF channel DRIFx (as per datasheet) contains two internal + * channels DRIFx0 & DRIFx1 within itself with each having its own resources + * like module clk, register set, irq and dma. These internal channels share + * common CLK & SYNC from master. The two data pins D0 & D1 shall be + * considered to represent the two internal channels. This internal split + * is not visible to the master device. + * + * Depending on the master device, a DRIF channel can use + * (1) both internal channels (D0 & D1) to receive data in parallel (or) + * (2) one internal channel (D0 or D1) to receive data + * + * The primary design goal of this controller is to act as a Digital Radio + * Interface that receives digital samples from a tuner device. Hence the + * driver exposes the device as a V4L2 SDR device. In order to qualify as + * a V4L2 SDR device, it should possess a tuner interface as mandated by the + * framework. This driver expects a tuner driver (sub-device) to bind + * asynchronously with this device and the combined drivers shall expose + * a V4L2 compliant SDR device. The DRIF driver is independent of the + * tuner vendor. + * + * The DRIF h/w can support I2S mode and Frame start synchronization pulse mode. + * This driver is tested for I2S mode only because of the availability of + * suitable master devices. Hence, not all configurable options of DRIF h/w + * like lsb/msb first, syncdl, dtdl etc. are exposed via DT and I2S defaults + * are used. These can be exposed later if needed after testing. + */ +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/ioctl.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of_graph.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <media/v4l2-async.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-v4l2.h> +#include <media/videobuf2-vmalloc.h> + +/* DRIF register offsets */ +#define RCAR_DRIF_SITMDR1 0x00 +#define RCAR_DRIF_SITMDR2 0x04 +#define RCAR_DRIF_SITMDR3 0x08 +#define RCAR_DRIF_SIRMDR1 0x10 +#define RCAR_DRIF_SIRMDR2 0x14 +#define RCAR_DRIF_SIRMDR3 0x18 +#define RCAR_DRIF_SICTR 0x28 +#define RCAR_DRIF_SIFCTR 0x30 +#define RCAR_DRIF_SISTR 0x40 +#define RCAR_DRIF_SIIER 0x44 +#define RCAR_DRIF_SIRFDR 0x60 + +#define RCAR_DRIF_RFOVF BIT(3) /* Receive FIFO overflow */ +#define RCAR_DRIF_RFUDF BIT(4) /* Receive FIFO underflow */ +#define RCAR_DRIF_RFSERR BIT(5) /* Receive frame sync error */ +#define RCAR_DRIF_REOF BIT(7) /* Frame reception end */ +#define RCAR_DRIF_RDREQ BIT(12) /* Receive data xfer req */ +#define RCAR_DRIF_RFFUL BIT(13) /* Receive FIFO full */ + +/* SIRMDR1 */ +#define RCAR_DRIF_SIRMDR1_SYNCMD_FRAME (0 << 28) +#define RCAR_DRIF_SIRMDR1_SYNCMD_LR (3 << 28) + +#define RCAR_DRIF_SIRMDR1_SYNCAC_POL_HIGH (0 << 25) +#define RCAR_DRIF_SIRMDR1_SYNCAC_POL_LOW (1 << 25) + +#define RCAR_DRIF_SIRMDR1_MSB_FIRST (0 << 24) +#define RCAR_DRIF_SIRMDR1_LSB_FIRST (1 << 24) + +#define RCAR_DRIF_SIRMDR1_DTDL_0 (0 << 20) +#define RCAR_DRIF_SIRMDR1_DTDL_1 (1 << 20) +#define RCAR_DRIF_SIRMDR1_DTDL_2 (2 << 20) +#define RCAR_DRIF_SIRMDR1_DTDL_0PT5 (5 << 20) +#define RCAR_DRIF_SIRMDR1_DTDL_1PT5 (6 << 20) + +#define RCAR_DRIF_SIRMDR1_SYNCDL_0 (0 << 20) +#define RCAR_DRIF_SIRMDR1_SYNCDL_1 (1 << 20) +#define RCAR_DRIF_SIRMDR1_SYNCDL_2 (2 << 20) +#define RCAR_DRIF_SIRMDR1_SYNCDL_3 (3 << 20) +#define RCAR_DRIF_SIRMDR1_SYNCDL_0PT5 (5 << 20) +#define RCAR_DRIF_SIRMDR1_SYNCDL_1PT5 (6 << 20) + +#define RCAR_DRIF_MDR_GRPCNT(n) (((n) - 1) << 30) +#define RCAR_DRIF_MDR_BITLEN(n) (((n) - 1) << 24) +#define RCAR_DRIF_MDR_WDCNT(n) (((n) - 1) << 16) + +/* Hidden Transmit register that controls CLK & SYNC */ +#define RCAR_DRIF_SITMDR1_PCON BIT(30) + +#define RCAR_DRIF_SICTR_RX_RISING_EDGE BIT(26) +#define RCAR_DRIF_SICTR_RX_EN BIT(8) +#define RCAR_DRIF_SICTR_RESET BIT(0) + +/* Constants */ +#define RCAR_DRIF_NUM_HWBUFS 32 +#define RCAR_DRIF_MAX_DEVS 4 +#define RCAR_DRIF_DEFAULT_NUM_HWBUFS 16 +#define RCAR_DRIF_DEFAULT_HWBUF_SIZE (4 * PAGE_SIZE) +#define RCAR_DRIF_MAX_CHANNEL 2 +#define RCAR_SDR_BUFFER_SIZE SZ_64K + +/* Internal buffer status flags */ +#define RCAR_DRIF_BUF_DONE BIT(0) /* DMA completed */ +#define RCAR_DRIF_BUF_OVERFLOW BIT(1) /* Overflow detected */ + +#define to_rcar_drif_buf_pair(sdr, ch_num, idx) \ + (&((sdr)->ch[!(ch_num)]->buf[(idx)])) + +#define for_each_rcar_drif_channel(ch, ch_mask) \ + for_each_set_bit(ch, ch_mask, RCAR_DRIF_MAX_CHANNEL) + +/* Debug */ +#define rdrif_dbg(sdr, fmt, arg...) \ + dev_dbg(sdr->v4l2_dev.dev, fmt, ## arg) + +#define rdrif_err(sdr, fmt, arg...) \ + dev_err(sdr->v4l2_dev.dev, fmt, ## arg) + +/* Stream formats */ +struct rcar_drif_format { + u32 pixelformat; + u32 buffersize; + u32 bitlen; + u32 wdcnt; + u32 num_ch; +}; + +/* Format descriptions for capture */ +static const struct rcar_drif_format formats[] = { + { + .pixelformat = V4L2_SDR_FMT_PCU16BE, + .buffersize = RCAR_SDR_BUFFER_SIZE, + .bitlen = 16, + .wdcnt = 1, + .num_ch = 2, + }, + { + .pixelformat = V4L2_SDR_FMT_PCU18BE, + .buffersize = RCAR_SDR_BUFFER_SIZE, + .bitlen = 18, + .wdcnt = 1, + .num_ch = 2, + }, + { + .pixelformat = V4L2_SDR_FMT_PCU20BE, + .buffersize = RCAR_SDR_BUFFER_SIZE, + .bitlen = 20, + .wdcnt = 1, + .num_ch = 2, + }, +}; + +/* Buffer for a received frame from one or both internal channels */ +struct rcar_drif_frame_buf { + /* Common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +/* OF graph endpoint's V4L2 async data */ +struct rcar_drif_graph_ep { + struct v4l2_subdev *subdev; /* Async matched subdev */ + struct v4l2_async_subdev asd; /* Async sub-device descriptor */ +}; + +/* DMA buffer */ +struct rcar_drif_hwbuf { + void *addr; /* CPU-side address */ + unsigned int status; /* Buffer status flags */ +}; + +/* Internal channel */ +struct rcar_drif { + struct rcar_drif_sdr *sdr; /* Group device */ + struct platform_device *pdev; /* Channel's pdev */ + void __iomem *base; /* Base register address */ + resource_size_t start; /* I/O resource offset */ + struct dma_chan *dmach; /* Reserved DMA channel */ + struct clk *clk; /* Module clock */ + struct rcar_drif_hwbuf buf[RCAR_DRIF_NUM_HWBUFS]; /* H/W bufs */ + dma_addr_t dma_handle; /* Handle for all bufs */ + unsigned int num; /* Channel number */ + bool acting_sdr; /* Channel acting as SDR device */ +}; + +/* DRIF V4L2 SDR */ +struct rcar_drif_sdr { + struct device *dev; /* Platform device */ + struct video_device *vdev; /* V4L2 SDR device */ + struct v4l2_device v4l2_dev; /* V4L2 device */ + + /* Videobuf2 queue and queued buffers list */ + struct vb2_queue vb_queue; + struct list_head queued_bufs; + spinlock_t queued_bufs_lock; /* Protects queued_bufs */ + spinlock_t dma_lock; /* To serialize DMA cb of channels */ + + struct mutex v4l2_mutex; /* To serialize ioctls */ + struct mutex vb_queue_mutex; /* To serialize streaming ioctls */ + struct v4l2_ctrl_handler ctrl_hdl; /* SDR control handler */ + struct v4l2_async_notifier notifier; /* For subdev (tuner) */ + struct rcar_drif_graph_ep ep; /* Endpoint V4L2 async data */ + + /* Current V4L2 SDR format ptr */ + const struct rcar_drif_format *fmt; + + /* Device tree SYNC properties */ + u32 mdr1; + + /* Internals */ + struct rcar_drif *ch[RCAR_DRIF_MAX_CHANNEL]; /* DRIFx0,1 */ + unsigned long hw_ch_mask; /* Enabled channels per DT */ + unsigned long cur_ch_mask; /* Used channels for an SDR FMT */ + u32 num_hw_ch; /* Num of DT enabled channels */ + u32 num_cur_ch; /* Num of used channels */ + u32 hwbuf_size; /* Each DMA buffer size */ + u32 produced; /* Buffers produced by sdr dev */ +}; + +/* Register access functions */ +static void rcar_drif_write(struct rcar_drif *ch, u32 offset, u32 data) +{ + writel(data, ch->base + offset); +} + +static u32 rcar_drif_read(struct rcar_drif *ch, u32 offset) +{ + return readl(ch->base + offset); +} + +/* Release DMA channels */ +static void rcar_drif_release_dmachannels(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) + if (sdr->ch[i]->dmach) { + dma_release_channel(sdr->ch[i]->dmach); + sdr->ch[i]->dmach = NULL; + } +} + +/* Allocate DMA channels */ +static int rcar_drif_alloc_dmachannels(struct rcar_drif_sdr *sdr) +{ + struct dma_slave_config dma_cfg; + unsigned int i; + int ret = -ENODEV; + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + struct rcar_drif *ch = sdr->ch[i]; + + ch->dmach = dma_request_slave_channel(&ch->pdev->dev, "rx"); + if (!ch->dmach) { + rdrif_err(sdr, "ch%u: dma channel req failed\n", i); + goto dmach_error; + } + + /* Configure slave */ + memset(&dma_cfg, 0, sizeof(dma_cfg)); + dma_cfg.src_addr = (phys_addr_t)(ch->start + RCAR_DRIF_SIRFDR); + dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + ret = dmaengine_slave_config(ch->dmach, &dma_cfg); + if (ret) { + rdrif_err(sdr, "ch%u: dma slave config failed\n", i); + goto dmach_error; + } + } + return 0; + +dmach_error: + rcar_drif_release_dmachannels(sdr); + return ret; +} + +/* Release queued vb2 buffers */ +static void rcar_drif_release_queued_bufs(struct rcar_drif_sdr *sdr, + enum vb2_buffer_state state) +{ + struct rcar_drif_frame_buf *fbuf, *tmp; + unsigned long flags; + + spin_lock_irqsave(&sdr->queued_bufs_lock, flags); + list_for_each_entry_safe(fbuf, tmp, &sdr->queued_bufs, list) { + list_del(&fbuf->list); + vb2_buffer_done(&fbuf->vb.vb2_buf, state); + } + spin_unlock_irqrestore(&sdr->queued_bufs_lock, flags); +} + +/* Set MDR defaults */ +static inline void rcar_drif_set_mdr1(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + /* Set defaults for enabled internal channels */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + /* Refer MSIOF section in manual for this register setting */ + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SITMDR1, + RCAR_DRIF_SITMDR1_PCON); + + /* Setup MDR1 value */ + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SIRMDR1, sdr->mdr1); + + rdrif_dbg(sdr, "ch%u: mdr1 = 0x%08x", + i, rcar_drif_read(sdr->ch[i], RCAR_DRIF_SIRMDR1)); + } +} + +/* Set DRIF receive format */ +static int rcar_drif_set_format(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + rdrif_dbg(sdr, "setfmt: bitlen %u wdcnt %u num_ch %u\n", + sdr->fmt->bitlen, sdr->fmt->wdcnt, sdr->fmt->num_ch); + + /* Sanity check */ + if (sdr->fmt->num_ch > sdr->num_cur_ch) { + rdrif_err(sdr, "fmt num_ch %u cur_ch %u mismatch\n", + sdr->fmt->num_ch, sdr->num_cur_ch); + return -EINVAL; + } + + /* Setup group, bitlen & wdcnt */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + u32 mdr; + + /* Two groups */ + mdr = RCAR_DRIF_MDR_GRPCNT(2) | + RCAR_DRIF_MDR_BITLEN(sdr->fmt->bitlen) | + RCAR_DRIF_MDR_WDCNT(sdr->fmt->wdcnt); + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SIRMDR2, mdr); + + mdr = RCAR_DRIF_MDR_BITLEN(sdr->fmt->bitlen) | + RCAR_DRIF_MDR_WDCNT(sdr->fmt->wdcnt); + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SIRMDR3, mdr); + + rdrif_dbg(sdr, "ch%u: new mdr[2,3] = 0x%08x, 0x%08x\n", + i, rcar_drif_read(sdr->ch[i], RCAR_DRIF_SIRMDR2), + rcar_drif_read(sdr->ch[i], RCAR_DRIF_SIRMDR3)); + } + return 0; +} + +/* Release DMA buffers */ +static void rcar_drif_release_buf(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + struct rcar_drif *ch = sdr->ch[i]; + + /* First entry contains the dma buf ptr */ + if (ch->buf[0].addr) { + dma_free_coherent(&ch->pdev->dev, + sdr->hwbuf_size * RCAR_DRIF_NUM_HWBUFS, + ch->buf[0].addr, ch->dma_handle); + ch->buf[0].addr = NULL; + } + } +} + +/* Request DMA buffers */ +static int rcar_drif_request_buf(struct rcar_drif_sdr *sdr) +{ + int ret = -ENOMEM; + unsigned int i, j; + void *addr; + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + struct rcar_drif *ch = sdr->ch[i]; + + /* Allocate DMA buffers */ + addr = dma_alloc_coherent(&ch->pdev->dev, + sdr->hwbuf_size * RCAR_DRIF_NUM_HWBUFS, + &ch->dma_handle, GFP_KERNEL); + if (!addr) { + rdrif_err(sdr, + "ch%u: dma alloc failed. num hwbufs %u size %u\n", + i, RCAR_DRIF_NUM_HWBUFS, sdr->hwbuf_size); + goto error; + } + + /* Split the chunk and populate bufctxt */ + for (j = 0; j < RCAR_DRIF_NUM_HWBUFS; j++) { + ch->buf[j].addr = addr + (j * sdr->hwbuf_size); + ch->buf[j].status = 0; + } + } + return 0; +error: + return ret; +} + +/* Setup vb_queue minimum buffer requirements */ +static int rcar_drif_queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct rcar_drif_sdr *sdr = vb2_get_drv_priv(vq); + + /* Need at least 16 buffers */ + if (vq->num_buffers + *num_buffers < 16) + *num_buffers = 16 - vq->num_buffers; + + *num_planes = 1; + sizes[0] = PAGE_ALIGN(sdr->fmt->buffersize); + rdrif_dbg(sdr, "num_bufs %d sizes[0] %d\n", *num_buffers, sizes[0]); + + return 0; +} + +/* Enqueue buffer */ +static void rcar_drif_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct rcar_drif_sdr *sdr = vb2_get_drv_priv(vb->vb2_queue); + struct rcar_drif_frame_buf *fbuf = + container_of(vbuf, struct rcar_drif_frame_buf, vb); + unsigned long flags; + + rdrif_dbg(sdr, "buf_queue idx %u\n", vb->index); + spin_lock_irqsave(&sdr->queued_bufs_lock, flags); + list_add_tail(&fbuf->list, &sdr->queued_bufs); + spin_unlock_irqrestore(&sdr->queued_bufs_lock, flags); +} + +/* Get a frame buf from list */ +static struct rcar_drif_frame_buf * +rcar_drif_get_fbuf(struct rcar_drif_sdr *sdr) +{ + struct rcar_drif_frame_buf *fbuf; + unsigned long flags; + + spin_lock_irqsave(&sdr->queued_bufs_lock, flags); + fbuf = list_first_entry_or_null(&sdr->queued_bufs, struct + rcar_drif_frame_buf, list); + if (!fbuf) { + /* + * App is late in enqueing buffers. Samples lost & there will + * be a gap in sequence number when app recovers + */ + rdrif_dbg(sdr, "\napp late: prod %u\n", sdr->produced); + spin_unlock_irqrestore(&sdr->queued_bufs_lock, flags); + return NULL; + } + list_del(&fbuf->list); + spin_unlock_irqrestore(&sdr->queued_bufs_lock, flags); + + return fbuf; +} + +/* Helpers to set/clear buf pair status */ +static inline bool rcar_drif_bufs_done(struct rcar_drif_hwbuf **buf) +{ + return (buf[0]->status & buf[1]->status & RCAR_DRIF_BUF_DONE); +} + +static inline bool rcar_drif_bufs_overflow(struct rcar_drif_hwbuf **buf) +{ + return ((buf[0]->status | buf[1]->status) & RCAR_DRIF_BUF_OVERFLOW); +} + +static inline void rcar_drif_bufs_clear(struct rcar_drif_hwbuf **buf, + unsigned int bit) +{ + unsigned int i; + + for (i = 0; i < RCAR_DRIF_MAX_CHANNEL; i++) + buf[i]->status &= ~bit; +} + +/* Channel DMA complete */ +static void rcar_drif_channel_complete(struct rcar_drif *ch, u32 idx) +{ + u32 str; + + ch->buf[idx].status |= RCAR_DRIF_BUF_DONE; + + /* Check for DRIF errors */ + str = rcar_drif_read(ch, RCAR_DRIF_SISTR); + if (unlikely(str & RCAR_DRIF_RFOVF)) { + /* Writing the same clears it */ + rcar_drif_write(ch, RCAR_DRIF_SISTR, str); + + /* Overflow: some samples are lost */ + ch->buf[idx].status |= RCAR_DRIF_BUF_OVERFLOW; + } +} + +/* DMA callback for each stage */ +static void rcar_drif_dma_complete(void *dma_async_param) +{ + struct rcar_drif *ch = dma_async_param; + struct rcar_drif_sdr *sdr = ch->sdr; + struct rcar_drif_hwbuf *buf[RCAR_DRIF_MAX_CHANNEL]; + struct rcar_drif_frame_buf *fbuf; + bool overflow = false; + u32 idx, produced; + unsigned int i; + + spin_lock(&sdr->dma_lock); + + /* DMA can be terminated while the callback was waiting on lock */ + if (!vb2_is_streaming(&sdr->vb_queue)) { + spin_unlock(&sdr->dma_lock); + return; + } + + idx = sdr->produced % RCAR_DRIF_NUM_HWBUFS; + rcar_drif_channel_complete(ch, idx); + + if (sdr->num_cur_ch == RCAR_DRIF_MAX_CHANNEL) { + buf[0] = ch->num ? to_rcar_drif_buf_pair(sdr, ch->num, idx) : + &ch->buf[idx]; + buf[1] = ch->num ? &ch->buf[idx] : + to_rcar_drif_buf_pair(sdr, ch->num, idx); + + /* Check if both DMA buffers are done */ + if (!rcar_drif_bufs_done(buf)) { + spin_unlock(&sdr->dma_lock); + return; + } + + /* Clear buf done status */ + rcar_drif_bufs_clear(buf, RCAR_DRIF_BUF_DONE); + + if (rcar_drif_bufs_overflow(buf)) { + overflow = true; + /* Clear the flag in status */ + rcar_drif_bufs_clear(buf, RCAR_DRIF_BUF_OVERFLOW); + } + } else { + buf[0] = &ch->buf[idx]; + if (buf[0]->status & RCAR_DRIF_BUF_OVERFLOW) { + overflow = true; + /* Clear the flag in status */ + buf[0]->status &= ~RCAR_DRIF_BUF_OVERFLOW; + } + } + + /* Buffer produced for consumption */ + produced = sdr->produced++; + spin_unlock(&sdr->dma_lock); + + rdrif_dbg(sdr, "ch%u: prod %u\n", ch->num, produced); + + /* Get fbuf */ + fbuf = rcar_drif_get_fbuf(sdr); + if (!fbuf) + return; + + for (i = 0; i < RCAR_DRIF_MAX_CHANNEL; i++) + memcpy(vb2_plane_vaddr(&fbuf->vb.vb2_buf, 0) + + i * sdr->hwbuf_size, buf[i]->addr, sdr->hwbuf_size); + + fbuf->vb.field = V4L2_FIELD_NONE; + fbuf->vb.sequence = produced; + fbuf->vb.vb2_buf.timestamp = ktime_get_ns(); + vb2_set_plane_payload(&fbuf->vb.vb2_buf, 0, sdr->fmt->buffersize); + + /* Set error state on overflow */ + vb2_buffer_done(&fbuf->vb.vb2_buf, + overflow ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); +} + +static int rcar_drif_qbuf(struct rcar_drif *ch) +{ + struct rcar_drif_sdr *sdr = ch->sdr; + dma_addr_t addr = ch->dma_handle; + struct dma_async_tx_descriptor *rxd; + dma_cookie_t cookie; + int ret = -EIO; + + /* Setup cyclic DMA with given buffers */ + rxd = dmaengine_prep_dma_cyclic(ch->dmach, addr, + sdr->hwbuf_size * RCAR_DRIF_NUM_HWBUFS, + sdr->hwbuf_size, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!rxd) { + rdrif_err(sdr, "ch%u: prep dma cyclic failed\n", ch->num); + return ret; + } + + /* Submit descriptor */ + rxd->callback = rcar_drif_dma_complete; + rxd->callback_param = ch; + cookie = dmaengine_submit(rxd); + if (dma_submit_error(cookie)) { + rdrif_err(sdr, "ch%u: dma submit failed\n", ch->num); + return ret; + } + + dma_async_issue_pending(ch->dmach); + return 0; +} + +/* Enable reception */ +static int rcar_drif_enable_rx(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + u32 ctr; + int ret; + + /* + * When both internal channels are enabled, they can be synchronized + * only by the master + */ + + /* Enable receive */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ctr = rcar_drif_read(sdr->ch[i], RCAR_DRIF_SICTR); + ctr |= (RCAR_DRIF_SICTR_RX_RISING_EDGE | + RCAR_DRIF_SICTR_RX_EN); + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SICTR, ctr); + } + + /* Check receive enabled */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ret = readl_poll_timeout(sdr->ch[i]->base + RCAR_DRIF_SICTR, + ctr, ctr & RCAR_DRIF_SICTR_RX_EN, 7, 100000); + if (ret) { + rdrif_err(sdr, "ch%u: rx en failed. ctr 0x%08x\n", i, + rcar_drif_read(sdr->ch[i], RCAR_DRIF_SICTR)); + break; + } + } + return ret; +} + +/* Disable reception */ +static void rcar_drif_disable_rx(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + u32 ctr; + int ret; + + /* Disable receive */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ctr = rcar_drif_read(sdr->ch[i], RCAR_DRIF_SICTR); + ctr &= ~RCAR_DRIF_SICTR_RX_EN; + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SICTR, ctr); + } + + /* Check receive disabled */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ret = readl_poll_timeout(sdr->ch[i]->base + RCAR_DRIF_SICTR, + ctr, !(ctr & RCAR_DRIF_SICTR_RX_EN), 7, 100000); + if (ret) + dev_warn(&sdr->vdev->dev, + "ch%u: failed to disable rx. ctr 0x%08x\n", + i, rcar_drif_read(sdr->ch[i], RCAR_DRIF_SICTR)); + } +} + +/* Stop channel */ +static void rcar_drif_stop_channel(struct rcar_drif *ch) +{ + /* Disable DMA receive interrupt */ + rcar_drif_write(ch, RCAR_DRIF_SIIER, 0x00000000); + + /* Terminate all DMA transfers */ + dmaengine_terminate_sync(ch->dmach); +} + +/* Stop receive operation */ +static void rcar_drif_stop(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + /* Disable Rx */ + rcar_drif_disable_rx(sdr); + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) + rcar_drif_stop_channel(sdr->ch[i]); +} + +/* Start channel */ +static int rcar_drif_start_channel(struct rcar_drif *ch) +{ + struct rcar_drif_sdr *sdr = ch->sdr; + u32 ctr, str; + int ret; + + /* Reset receive */ + rcar_drif_write(ch, RCAR_DRIF_SICTR, RCAR_DRIF_SICTR_RESET); + ret = readl_poll_timeout(ch->base + RCAR_DRIF_SICTR, ctr, + !(ctr & RCAR_DRIF_SICTR_RESET), 7, 100000); + if (ret) { + rdrif_err(sdr, "ch%u: failed to reset rx. ctr 0x%08x\n", + ch->num, rcar_drif_read(ch, RCAR_DRIF_SICTR)); + return ret; + } + + /* Queue buffers for DMA */ + ret = rcar_drif_qbuf(ch); + if (ret) + return ret; + + /* Clear status register flags */ + str = RCAR_DRIF_RFFUL | RCAR_DRIF_REOF | RCAR_DRIF_RFSERR | + RCAR_DRIF_RFUDF | RCAR_DRIF_RFOVF; + rcar_drif_write(ch, RCAR_DRIF_SISTR, str); + + /* Enable DMA receive interrupt */ + rcar_drif_write(ch, RCAR_DRIF_SIIER, 0x00009000); + + return ret; +} + +/* Start receive operation */ +static int rcar_drif_start(struct rcar_drif_sdr *sdr) +{ + unsigned long enabled = 0; + unsigned int i; + int ret; + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ret = rcar_drif_start_channel(sdr->ch[i]); + if (ret) + goto start_error; + enabled |= BIT(i); + } + + ret = rcar_drif_enable_rx(sdr); + if (ret) + goto enable_error; + + sdr->produced = 0; + return ret; + +enable_error: + rcar_drif_disable_rx(sdr); +start_error: + for_each_rcar_drif_channel(i, &enabled) + rcar_drif_stop_channel(sdr->ch[i]); + + return ret; +} + +/* Start streaming */ +static int rcar_drif_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct rcar_drif_sdr *sdr = vb2_get_drv_priv(vq); + unsigned long enabled = 0; + unsigned int i; + int ret; + + mutex_lock(&sdr->v4l2_mutex); + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ret = clk_prepare_enable(sdr->ch[i]->clk); + if (ret) + goto error; + enabled |= BIT(i); + } + + /* Set default MDRx settings */ + rcar_drif_set_mdr1(sdr); + + /* Set new format */ + ret = rcar_drif_set_format(sdr); + if (ret) + goto error; + + if (sdr->num_cur_ch == RCAR_DRIF_MAX_CHANNEL) + sdr->hwbuf_size = sdr->fmt->buffersize / RCAR_DRIF_MAX_CHANNEL; + else + sdr->hwbuf_size = sdr->fmt->buffersize; + + rdrif_dbg(sdr, "num hwbufs %u, hwbuf_size %u\n", + RCAR_DRIF_NUM_HWBUFS, sdr->hwbuf_size); + + /* Alloc DMA channel */ + ret = rcar_drif_alloc_dmachannels(sdr); + if (ret) + goto error; + + /* Request buffers */ + ret = rcar_drif_request_buf(sdr); + if (ret) + goto error; + + /* Start Rx */ + ret = rcar_drif_start(sdr); + if (ret) + goto error; + + mutex_unlock(&sdr->v4l2_mutex); + + return ret; + +error: + rcar_drif_release_queued_bufs(sdr, VB2_BUF_STATE_QUEUED); + rcar_drif_release_buf(sdr); + rcar_drif_release_dmachannels(sdr); + for_each_rcar_drif_channel(i, &enabled) + clk_disable_unprepare(sdr->ch[i]->clk); + + mutex_unlock(&sdr->v4l2_mutex); + + return ret; +} + +/* Stop streaming */ +static void rcar_drif_stop_streaming(struct vb2_queue *vq) +{ + struct rcar_drif_sdr *sdr = vb2_get_drv_priv(vq); + unsigned int i; + + mutex_lock(&sdr->v4l2_mutex); + + /* Stop hardware streaming */ + rcar_drif_stop(sdr); + + /* Return all queued buffers to vb2 */ + rcar_drif_release_queued_bufs(sdr, VB2_BUF_STATE_ERROR); + + /* Release buf */ + rcar_drif_release_buf(sdr); + + /* Release DMA channel resources */ + rcar_drif_release_dmachannels(sdr); + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) + clk_disable_unprepare(sdr->ch[i]->clk); + + mutex_unlock(&sdr->v4l2_mutex); +} + +/* Vb2 ops */ +static const struct vb2_ops rcar_drif_vb2_ops = { + .queue_setup = rcar_drif_queue_setup, + .buf_queue = rcar_drif_buf_queue, + .start_streaming = rcar_drif_start_streaming, + .stop_streaming = rcar_drif_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int rcar_drif_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strlcpy(cap->card, sdr->vdev->name, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + sdr->vdev->name); + + return 0; +} + +static int rcar_drif_set_default_format(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + /* Matching fmt based on required channels is set as default */ + if (sdr->num_hw_ch == formats[i].num_ch) { + sdr->fmt = &formats[i]; + sdr->cur_ch_mask = sdr->hw_ch_mask; + sdr->num_cur_ch = sdr->num_hw_ch; + dev_dbg(sdr->dev, "default fmt[%u]: mask %lu num %u\n", + i, sdr->cur_ch_mask, sdr->num_cur_ch); + return 0; + } + } + return -EINVAL; +} + +static int rcar_drif_enum_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(formats)) + return -EINVAL; + + f->pixelformat = formats[f->index].pixelformat; + + return 0; +} + +static int rcar_drif_g_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + f->fmt.sdr.pixelformat = sdr->fmt->pixelformat; + f->fmt.sdr.buffersize = sdr->fmt->buffersize; + + return 0; +} + +static int rcar_drif_s_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + struct vb2_queue *q = &sdr->vb_queue; + unsigned int i; + + if (vb2_is_busy(q)) + return -EBUSY; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].pixelformat == f->fmt.sdr.pixelformat) + break; + } + + if (i == ARRAY_SIZE(formats)) + i = 0; /* Set the 1st format as default on no match */ + + sdr->fmt = &formats[i]; + f->fmt.sdr.pixelformat = sdr->fmt->pixelformat; + f->fmt.sdr.buffersize = formats[i].buffersize; + memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved)); + + /* + * If a format demands one channel only out of two + * enabled channels, pick the 0th channel. + */ + if (formats[i].num_ch < sdr->num_hw_ch) { + sdr->cur_ch_mask = BIT(0); + sdr->num_cur_ch = formats[i].num_ch; + } else { + sdr->cur_ch_mask = sdr->hw_ch_mask; + sdr->num_cur_ch = sdr->num_hw_ch; + } + + rdrif_dbg(sdr, "cur: idx %u mask %lu num %u\n", + i, sdr->cur_ch_mask, sdr->num_cur_ch); + + return 0; +} + +static int rcar_drif_try_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].pixelformat == f->fmt.sdr.pixelformat) { + f->fmt.sdr.buffersize = formats[i].buffersize; + return 0; + } + } + + f->fmt.sdr.pixelformat = formats[0].pixelformat; + f->fmt.sdr.buffersize = formats[0].buffersize; + memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved)); + + return 0; +} + +/* Tuner subdev ioctls */ +static int rcar_drif_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + return v4l2_subdev_call(sdr->ep.subdev, tuner, enum_freq_bands, band); +} + +static int rcar_drif_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + return v4l2_subdev_call(sdr->ep.subdev, tuner, g_frequency, f); +} + +static int rcar_drif_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + return v4l2_subdev_call(sdr->ep.subdev, tuner, s_frequency, f); +} + +static int rcar_drif_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *vt) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + return v4l2_subdev_call(sdr->ep.subdev, tuner, g_tuner, vt); +} + +static int rcar_drif_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *vt) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + return v4l2_subdev_call(sdr->ep.subdev, tuner, s_tuner, vt); +} + +static const struct v4l2_ioctl_ops rcar_drif_ioctl_ops = { + .vidioc_querycap = rcar_drif_querycap, + + .vidioc_enum_fmt_sdr_cap = rcar_drif_enum_fmt_sdr_cap, + .vidioc_g_fmt_sdr_cap = rcar_drif_g_fmt_sdr_cap, + .vidioc_s_fmt_sdr_cap = rcar_drif_s_fmt_sdr_cap, + .vidioc_try_fmt_sdr_cap = rcar_drif_try_fmt_sdr_cap, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_s_frequency = rcar_drif_s_frequency, + .vidioc_g_frequency = rcar_drif_g_frequency, + .vidioc_s_tuner = rcar_drif_s_tuner, + .vidioc_g_tuner = rcar_drif_g_tuner, + .vidioc_enum_freq_bands = rcar_drif_enum_freq_bands, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_log_status = v4l2_ctrl_log_status, +}; + +static const struct v4l2_file_operations rcar_drif_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static int rcar_drif_sdr_register(struct rcar_drif_sdr *sdr) +{ + int ret; + + /* Init video_device structure */ + sdr->vdev = video_device_alloc(); + if (!sdr->vdev) + return -ENOMEM; + + snprintf(sdr->vdev->name, sizeof(sdr->vdev->name), "R-Car DRIF"); + sdr->vdev->fops = &rcar_drif_fops; + sdr->vdev->ioctl_ops = &rcar_drif_ioctl_ops; + sdr->vdev->release = video_device_release; + sdr->vdev->lock = &sdr->v4l2_mutex; + sdr->vdev->queue = &sdr->vb_queue; + sdr->vdev->queue->lock = &sdr->vb_queue_mutex; + sdr->vdev->ctrl_handler = &sdr->ctrl_hdl; + sdr->vdev->v4l2_dev = &sdr->v4l2_dev; + sdr->vdev->device_caps = V4L2_CAP_SDR_CAPTURE | V4L2_CAP_TUNER | + V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; + video_set_drvdata(sdr->vdev, sdr); + + /* Register V4L2 SDR device */ + ret = video_register_device(sdr->vdev, VFL_TYPE_SDR, -1); + if (ret) { + video_device_release(sdr->vdev); + sdr->vdev = NULL; + dev_err(sdr->dev, "failed video_register_device (%d)\n", ret); + } + + return ret; +} + +static void rcar_drif_sdr_unregister(struct rcar_drif_sdr *sdr) +{ + video_unregister_device(sdr->vdev); + sdr->vdev = NULL; +} + +/* Sub-device bound callback */ +static int rcar_drif_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rcar_drif_sdr *sdr = + container_of(notifier, struct rcar_drif_sdr, notifier); + + if (sdr->ep.asd.match.fwnode.fwnode != + of_fwnode_handle(subdev->dev->of_node)) { + rdrif_err(sdr, "subdev %s cannot bind\n", subdev->name); + return -EINVAL; + } + + v4l2_set_subdev_hostdata(subdev, sdr); + sdr->ep.subdev = subdev; + rdrif_dbg(sdr, "bound asd %s\n", subdev->name); + + return 0; +} + +/* Sub-device unbind callback */ +static void rcar_drif_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rcar_drif_sdr *sdr = + container_of(notifier, struct rcar_drif_sdr, notifier); + + if (sdr->ep.subdev != subdev) { + rdrif_err(sdr, "subdev %s is not bound\n", subdev->name); + return; + } + + /* Free ctrl handler if initialized */ + v4l2_ctrl_handler_free(&sdr->ctrl_hdl); + sdr->v4l2_dev.ctrl_handler = NULL; + sdr->ep.subdev = NULL; + + rcar_drif_sdr_unregister(sdr); + rdrif_dbg(sdr, "unbind asd %s\n", subdev->name); +} + +/* Sub-device registered notification callback */ +static int rcar_drif_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct rcar_drif_sdr *sdr = + container_of(notifier, struct rcar_drif_sdr, notifier); + int ret; + + /* + * The subdev tested at this point uses 4 controls. Using 10 as a worst + * case scenario hint. When less controls are needed there will be some + * unused memory and when more controls are needed the framework uses + * hash to manage controls within this number. + */ + ret = v4l2_ctrl_handler_init(&sdr->ctrl_hdl, 10); + if (ret) + return -ENOMEM; + + sdr->v4l2_dev.ctrl_handler = &sdr->ctrl_hdl; + ret = v4l2_device_register_subdev_nodes(&sdr->v4l2_dev); + if (ret) { + rdrif_err(sdr, "failed: register subdev nodes ret %d\n", ret); + goto error; + } + + ret = v4l2_ctrl_add_handler(&sdr->ctrl_hdl, + sdr->ep.subdev->ctrl_handler, NULL); + if (ret) { + rdrif_err(sdr, "failed: ctrl add hdlr ret %d\n", ret); + goto error; + } + + ret = rcar_drif_sdr_register(sdr); + if (ret) + goto error; + + return ret; + +error: + v4l2_ctrl_handler_free(&sdr->ctrl_hdl); + + return ret; +} + +/* Read endpoint properties */ +static void rcar_drif_get_ep_properties(struct rcar_drif_sdr *sdr, + struct fwnode_handle *fwnode) +{ + u32 val; + + /* Set the I2S defaults for SIRMDR1*/ + sdr->mdr1 = RCAR_DRIF_SIRMDR1_SYNCMD_LR | RCAR_DRIF_SIRMDR1_MSB_FIRST | + RCAR_DRIF_SIRMDR1_DTDL_1 | RCAR_DRIF_SIRMDR1_SYNCDL_0; + + /* Parse sync polarity from endpoint */ + if (!fwnode_property_read_u32(fwnode, "sync-active", &val)) + sdr->mdr1 |= val ? RCAR_DRIF_SIRMDR1_SYNCAC_POL_HIGH : + RCAR_DRIF_SIRMDR1_SYNCAC_POL_LOW; + else + sdr->mdr1 |= RCAR_DRIF_SIRMDR1_SYNCAC_POL_HIGH; /* default */ + + dev_dbg(sdr->dev, "mdr1 0x%08x\n", sdr->mdr1); +} + +/* Parse sub-devs (tuner) to find a matching device */ +static int rcar_drif_parse_subdevs(struct rcar_drif_sdr *sdr) +{ + struct v4l2_async_notifier *notifier = &sdr->notifier; + struct fwnode_handle *fwnode, *ep; + + notifier->subdevs = devm_kzalloc(sdr->dev, sizeof(*notifier->subdevs), + GFP_KERNEL); + if (!notifier->subdevs) + return -ENOMEM; + + ep = fwnode_graph_get_next_endpoint(of_fwnode_handle(sdr->dev->of_node), + NULL); + if (!ep) + return 0; + + notifier->subdevs[notifier->num_subdevs] = &sdr->ep.asd; + fwnode = fwnode_graph_get_remote_port_parent(ep); + if (!fwnode) { + dev_warn(sdr->dev, "bad remote port parent\n"); + fwnode_handle_put(ep); + return -EINVAL; + } + + sdr->ep.asd.match.fwnode.fwnode = fwnode; + sdr->ep.asd.match_type = V4L2_ASYNC_MATCH_FWNODE; + notifier->num_subdevs++; + + /* Get the endpoint properties */ + rcar_drif_get_ep_properties(sdr, ep); + + fwnode_handle_put(fwnode); + fwnode_handle_put(ep); + + return 0; +} + +/* Check if the given device is the primary bond */ +static bool rcar_drif_primary_bond(struct platform_device *pdev) +{ + return of_property_read_bool(pdev->dev.of_node, "renesas,primary-bond"); +} + +/* Check if both devices of the bond are enabled */ +static struct device_node *rcar_drif_bond_enabled(struct platform_device *p) +{ + struct device_node *np; + + np = of_parse_phandle(p->dev.of_node, "renesas,bonding", 0); + if (np && of_device_is_available(np)) + return np; + + return NULL; +} + +/* Check if the bonded device is probed */ +static int rcar_drif_bond_available(struct rcar_drif_sdr *sdr, + struct device_node *np) +{ + struct platform_device *pdev; + struct rcar_drif *ch; + int ret = 0; + + pdev = of_find_device_by_node(np); + if (!pdev) { + dev_err(sdr->dev, "failed to get bonded device from node\n"); + return -ENODEV; + } + + device_lock(&pdev->dev); + ch = platform_get_drvdata(pdev); + if (ch) { + /* Update sdr data in the bonded device */ + ch->sdr = sdr; + + /* Update sdr with bonded device data */ + sdr->ch[ch->num] = ch; + sdr->hw_ch_mask |= BIT(ch->num); + } else { + /* Defer */ + dev_info(sdr->dev, "defer probe\n"); + ret = -EPROBE_DEFER; + } + device_unlock(&pdev->dev); + + put_device(&pdev->dev); + + return ret; +} + +/* V4L2 SDR device probe */ +static int rcar_drif_sdr_probe(struct rcar_drif_sdr *sdr) +{ + int ret; + + /* Validate any supported format for enabled channels */ + ret = rcar_drif_set_default_format(sdr); + if (ret) { + dev_err(sdr->dev, "failed to set default format\n"); + return ret; + } + + /* Set defaults */ + sdr->hwbuf_size = RCAR_DRIF_DEFAULT_HWBUF_SIZE; + + mutex_init(&sdr->v4l2_mutex); + mutex_init(&sdr->vb_queue_mutex); + spin_lock_init(&sdr->queued_bufs_lock); + spin_lock_init(&sdr->dma_lock); + INIT_LIST_HEAD(&sdr->queued_bufs); + + /* Init videobuf2 queue structure */ + sdr->vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE; + sdr->vb_queue.io_modes = VB2_READ | VB2_MMAP | VB2_DMABUF; + sdr->vb_queue.drv_priv = sdr; + sdr->vb_queue.buf_struct_size = sizeof(struct rcar_drif_frame_buf); + sdr->vb_queue.ops = &rcar_drif_vb2_ops; + sdr->vb_queue.mem_ops = &vb2_vmalloc_memops; + sdr->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + + /* Init videobuf2 queue */ + ret = vb2_queue_init(&sdr->vb_queue); + if (ret) { + dev_err(sdr->dev, "failed: vb2_queue_init ret %d\n", ret); + return ret; + } + + /* Register the v4l2_device */ + ret = v4l2_device_register(sdr->dev, &sdr->v4l2_dev); + if (ret) { + dev_err(sdr->dev, "failed: v4l2_device_register ret %d\n", ret); + return ret; + } + + /* + * Parse subdevs after v4l2_device_register because if the subdev + * is already probed, bound and complete will be called immediately + */ + ret = rcar_drif_parse_subdevs(sdr); + if (ret) + goto error; + + sdr->notifier.bound = rcar_drif_notify_bound; + sdr->notifier.unbind = rcar_drif_notify_unbind; + sdr->notifier.complete = rcar_drif_notify_complete; + + /* Register notifier */ + ret = v4l2_async_notifier_register(&sdr->v4l2_dev, &sdr->notifier); + if (ret < 0) { + dev_err(sdr->dev, "failed: notifier register ret %d\n", ret); + goto error; + } + + return ret; + +error: + v4l2_device_unregister(&sdr->v4l2_dev); + + return ret; +} + +/* V4L2 SDR device remove */ +static void rcar_drif_sdr_remove(struct rcar_drif_sdr *sdr) +{ + v4l2_async_notifier_unregister(&sdr->notifier); + v4l2_device_unregister(&sdr->v4l2_dev); +} + +/* DRIF channel probe */ +static int rcar_drif_probe(struct platform_device *pdev) +{ + struct rcar_drif_sdr *sdr; + struct device_node *np; + struct rcar_drif *ch; + struct resource *res; + int ret; + + /* Reserve memory for enabled channel */ + ch = devm_kzalloc(&pdev->dev, sizeof(*ch), GFP_KERNEL); + if (!ch) + return -ENOMEM; + + ch->pdev = pdev; + + /* Module clock */ + ch->clk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(ch->clk)) { + ret = PTR_ERR(ch->clk); + dev_err(&pdev->dev, "clk get failed (%d)\n", ret); + return ret; + } + + /* Register map */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ch->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ch->base)) { + ret = PTR_ERR(ch->base); + dev_err(&pdev->dev, "ioremap failed (%d)\n", ret); + return ret; + } + ch->start = res->start; + platform_set_drvdata(pdev, ch); + + /* Check if both channels of the bond are enabled */ + np = rcar_drif_bond_enabled(pdev); + if (np) { + /* Check if current channel acting as primary-bond */ + if (!rcar_drif_primary_bond(pdev)) { + ch->num = 1; /* Primary bond is channel 0 always */ + of_node_put(np); + return 0; + } + } + + /* Reserve memory for SDR structure */ + sdr = devm_kzalloc(&pdev->dev, sizeof(*sdr), GFP_KERNEL); + if (!sdr) { + of_node_put(np); + return -ENOMEM; + } + ch->sdr = sdr; + sdr->dev = &pdev->dev; + + /* Establish links between SDR and channel(s) */ + sdr->ch[ch->num] = ch; + sdr->hw_ch_mask = BIT(ch->num); + if (np) { + /* Check if bonded device is ready */ + ret = rcar_drif_bond_available(sdr, np); + of_node_put(np); + if (ret) + return ret; + } + sdr->num_hw_ch = hweight_long(sdr->hw_ch_mask); + + return rcar_drif_sdr_probe(sdr); +} + +/* DRIF channel remove */ +static int rcar_drif_remove(struct platform_device *pdev) +{ + struct rcar_drif *ch = platform_get_drvdata(pdev); + struct rcar_drif_sdr *sdr = ch->sdr; + + /* Channel 0 will be the SDR instance */ + if (ch->num) + return 0; + + /* SDR instance */ + rcar_drif_sdr_remove(sdr); + + return 0; +} + +/* FIXME: Implement suspend/resume support */ +static int __maybe_unused rcar_drif_suspend(struct device *dev) +{ + return 0; +} + +static int __maybe_unused rcar_drif_resume(struct device *dev) +{ + return 0; +} + +static SIMPLE_DEV_PM_OPS(rcar_drif_pm_ops, rcar_drif_suspend, + rcar_drif_resume); + +static const struct of_device_id rcar_drif_of_table[] = { + { .compatible = "renesas,rcar-gen3-drif" }, + { } +}; +MODULE_DEVICE_TABLE(of, rcar_drif_of_table); + +#define RCAR_DRIF_DRV_NAME "rcar_drif" +static struct platform_driver rcar_drif_driver = { + .driver = { + .name = RCAR_DRIF_DRV_NAME, + .of_match_table = of_match_ptr(rcar_drif_of_table), + .pm = &rcar_drif_pm_ops, + }, + .probe = rcar_drif_probe, + .remove = rcar_drif_remove, +}; + +module_platform_driver(rcar_drif_driver); + +MODULE_DESCRIPTION("Renesas R-Car Gen3 DRIF driver"); +MODULE_ALIAS("platform:" RCAR_DRIF_DRV_NAME); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ramesh Shanmugasundaram <ramesh.shanmugasundaram@bp.renesas.com>"); diff --git a/drivers/media/platform/rcar_fdp1.c b/drivers/media/platform/rcar_fdp1.c index 42f25d241edd..3ee51fc3bb50 100644 --- a/drivers/media/platform/rcar_fdp1.c +++ b/drivers/media/platform/rcar_fdp1.c @@ -1,5 +1,5 @@ /* - * Renesas RCar Fine Display Processor + * Renesas R-Car Fine Display Processor * * Video format converter and frame deinterlacer device. * @@ -258,8 +258,9 @@ MODULE_PARM_DESC(debug, "activate debug info"); /* Internal Data (HW Version) */ #define FD1_IP_INTDATA 0x0800 -#define FD1_IP_H3 0x02010101 +#define FD1_IP_H3_ES1 0x02010101 #define FD1_IP_M3W 0x02010202 +#define FD1_IP_H3 0x02010203 /* LUTs */ #define FD1_LUT_DIF_ADJ 0x1000 @@ -2359,12 +2360,15 @@ static int fdp1_probe(struct platform_device *pdev) hw_version = fdp1_read(fdp1, FD1_IP_INTDATA); switch (hw_version) { - case FD1_IP_H3: - dprintk(fdp1, "FDP1 Version R-Car H3\n"); + case FD1_IP_H3_ES1: + dprintk(fdp1, "FDP1 Version R-Car H3 ES1\n"); break; case FD1_IP_M3W: dprintk(fdp1, "FDP1 Version R-Car M3-W\n"); break; + case FD1_IP_H3: + dprintk(fdp1, "FDP1 Version R-Car H3\n"); + break; default: dev_err(fdp1->dev, "FDP1 Unidentifiable (0x%08x)\n", hw_version); diff --git a/drivers/media/platform/s3c-camif/camif-capture.c b/drivers/media/platform/s3c-camif/camif-capture.c index 1b30be72f4f9..25c7a7d42292 100644 --- a/drivers/media/platform/s3c-camif/camif-capture.c +++ b/drivers/media/platform/s3c-camif/camif-capture.c @@ -80,7 +80,7 @@ static int s3c_camif_hw_init(struct camif_dev *camif, struct camif_vp *vp) camif_hw_set_test_pattern(camif, camif->test_pattern); if (variant->has_img_effect) camif_hw_set_effect(camif, camif->colorfx, - camif->colorfx_cb, camif->colorfx_cr); + camif->colorfx_cr, camif->colorfx_cb); if (variant->ip_revision == S3C6410_CAMIF_IP_REV) camif_hw_set_input_path(vp); camif_cfg_video_path(vp); @@ -364,7 +364,7 @@ irqreturn_t s3c_camif_irq_handler(int irq, void *priv) camif_hw_set_test_pattern(camif, camif->test_pattern); if (camif->variant->has_img_effect) camif_hw_set_effect(camif, camif->colorfx, - camif->colorfx_cb, camif->colorfx_cr); + camif->colorfx_cr, camif->colorfx_cb); vp->state &= ~ST_VP_CONFIG; } unlock: diff --git a/drivers/media/platform/s5p-cec/s5p_cec.c b/drivers/media/platform/s5p-cec/s5p_cec.c index 664937b61fa4..8e06071a7977 100644 --- a/drivers/media/platform/s5p-cec/s5p_cec.c +++ b/drivers/media/platform/s5p-cec/s5p_cec.c @@ -173,6 +173,7 @@ static int s5p_cec_probe(struct platform_device *pdev) struct platform_device *hdmi_dev; struct resource *res; struct s5p_cec_dev *cec; + bool needs_hpd = of_property_read_bool(pdev->dev.of_node, "needs-hpd"); int ret; np = of_parse_phandle(pdev->dev.of_node, "hdmi-phandle", 0); @@ -221,7 +222,8 @@ static int s5p_cec_probe(struct platform_device *pdev) cec->adap = cec_allocate_adapter(&s5p_cec_adap_ops, cec, CEC_NAME, CEC_CAP_LOG_ADDRS | CEC_CAP_TRANSMIT | - CEC_CAP_PASSTHROUGH | CEC_CAP_RC, 1); + CEC_CAP_PASSTHROUGH | CEC_CAP_RC | + (needs_hpd ? CEC_CAP_NEEDS_HPD : 0), 1); ret = PTR_ERR_OR_ZERO(cec->adap); if (ret) return ret; @@ -235,7 +237,7 @@ static int s5p_cec_probe(struct platform_device *pdev) platform_set_drvdata(pdev, cec); pm_runtime_enable(dev); - dev_dbg(dev, "successfuly probed\n"); + dev_dbg(dev, "successfully probed\n"); return 0; err_delete_adapter: diff --git a/drivers/media/platform/s5p-cec/s5p_cec.h b/drivers/media/platform/s5p-cec/s5p_cec.h index 7015845c1caa..8bcd8dc1aeb9 100644 --- a/drivers/media/platform/s5p-cec/s5p_cec.h +++ b/drivers/media/platform/s5p-cec/s5p_cec.h @@ -22,7 +22,6 @@ #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/timer.h> -#include <linux/version.h> #include <linux/workqueue.h> #include <media/cec.h> diff --git a/drivers/media/platform/s5p-jpeg/jpeg-core.c b/drivers/media/platform/s5p-jpeg/jpeg-core.c index 52dc7941db65..d1e3ebb22577 100644 --- a/drivers/media/platform/s5p-jpeg/jpeg-core.c +++ b/drivers/media/platform/s5p-jpeg/jpeg-core.c @@ -1099,10 +1099,10 @@ static bool s5p_jpeg_parse_hdr(struct s5p_jpeg_q_data *result, struct s5p_jpeg_ctx *ctx) { int c, components = 0, notfound, n_dht = 0, n_dqt = 0; - unsigned int height, width, word, subsampling = 0, sos = 0, sof = 0, - sof_len = 0; - unsigned int dht[S5P_JPEG_MAX_MARKER], dht_len[S5P_JPEG_MAX_MARKER], - dqt[S5P_JPEG_MAX_MARKER], dqt_len[S5P_JPEG_MAX_MARKER]; + unsigned int height = 0, width = 0, word, subsampling = 0; + unsigned int sos = 0, sof = 0, sof_len = 0; + unsigned int dht[S5P_JPEG_MAX_MARKER], dht_len[S5P_JPEG_MAX_MARKER]; + unsigned int dqt[S5P_JPEG_MAX_MARKER], dqt_len[S5P_JPEG_MAX_MARKER]; long length; struct s5p_jpeg_buffer jpeg_buffer; @@ -2642,13 +2642,13 @@ static irqreturn_t s5p_jpeg_irq(int irq, void *dev_id) if (curr_ctx->mode == S5P_JPEG_ENCODE) vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload_size); v4l2_m2m_buf_done(dst_buf, state); - v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); curr_ctx->subsampling = s5p_jpeg_get_subsampling_mode(jpeg->regs); spin_unlock(&jpeg->slock); s5p_jpeg_clear_int(jpeg->regs); + v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); return IRQ_HANDLED; } @@ -2707,11 +2707,12 @@ static irqreturn_t exynos4_jpeg_irq(int irq, void *priv) v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_ERROR); } - v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); if (jpeg->variant->version == SJPEG_EXYNOS4) curr_ctx->subsampling = exynos4_jpeg_get_frame_fmt(jpeg->regs); spin_unlock(&jpeg->slock); + + v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); return IRQ_HANDLED; } @@ -2770,10 +2771,15 @@ static irqreturn_t exynos3250_jpeg_irq(int irq, void *dev_id) if (curr_ctx->mode == S5P_JPEG_ENCODE) vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload_size); v4l2_m2m_buf_done(dst_buf, state); - v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); curr_ctx->subsampling = exynos3250_jpeg_get_subsampling_mode(jpeg->regs); + + spin_unlock(&jpeg->slock); + + v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); + return IRQ_HANDLED; + exit_unlock: spin_unlock(&jpeg->slock); return IRQ_HANDLED; diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v5.c b/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v5.c index b41ee608c171..0913881219ff 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v5.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v5.c @@ -1293,7 +1293,7 @@ static int s5p_mfc_run_init_dec_buffers(struct s5p_mfc_ctx *ctx) * First set the output frame buffers */ if (ctx->capture_state != QUEUE_BUFS_MMAPED) { - mfc_err("It seems that not all destionation buffers were mmaped\nMFC requires that all destination are mmaped before starting processing\n"); + mfc_err("It seems that not all destination buffers were mmaped\nMFC requires that all destination are mmaped before starting processing\n"); return -EAGAIN; } if (list_empty(&ctx->src_queue)) { diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c b/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c index 85880e9106be..88dbb9c341ec 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_opr_v6.c @@ -1650,7 +1650,7 @@ static inline int s5p_mfc_run_init_dec_buffers(struct s5p_mfc_ctx *ctx) * s5p_mfc_alloc_dec_buffers(ctx); */ if (ctx->capture_state != QUEUE_BUFS_MMAPED) { - mfc_err("It seems that not all destionation buffers were\n" + mfc_err("It seems that not all destination buffers were\n" "mmaped.MFC requires that all destination are mmaped\n" "before starting processing.\n"); return -EAGAIN; diff --git a/drivers/media/platform/sh_vou.c b/drivers/media/platform/sh_vou.c index 992d61a8b961..871da2a2a91c 100644 --- a/drivers/media/platform/sh_vou.c +++ b/drivers/media/platform/sh_vou.c @@ -229,6 +229,7 @@ static void sh_vou_stream_config(struct sh_vou_device *vou_dev) break; case V4L2_PIX_FMT_RGB565: dataswap ^= 1; + /* fall through */ case V4L2_PIX_FMT_RGB565X: row_coeff = 2; break; @@ -815,6 +816,7 @@ static u32 sh_vou_ntsc_mode(enum sh_vou_bus_fmt bus_fmt) default: pr_warn("%s(): Invalid bus-format code %d, using default 8-bit\n", __func__, bus_fmt); + /* fall through */ case SH_VOU_BUS_8BIT: return 1; case SH_VOU_BUS_16BIT: diff --git a/drivers/media/platform/soc_camera/soc_camera.c b/drivers/media/platform/soc_camera/soc_camera.c index 3c9421f4d8e3..45a0429d75bb 100644 --- a/drivers/media/platform/soc_camera/soc_camera.c +++ b/drivers/media/platform/soc_camera/soc_camera.c @@ -23,6 +23,7 @@ #include <linux/list.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/of_graph.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> @@ -36,7 +37,7 @@ #include <media/v4l2-common.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-dev.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/videobuf2-v4l2.h> /* Default to VGA resolution */ @@ -1512,8 +1513,8 @@ static int soc_of_bind(struct soc_camera_host *ici, if (!info) return -ENOMEM; - info->sasd.asd.match.of.node = remote; - info->sasd.asd.match_type = V4L2_ASYNC_MATCH_OF; + info->sasd.asd.match.fwnode.fwnode = of_fwnode_handle(remote); + info->sasd.asd.match_type = V4L2_ASYNC_MATCH_FWNODE; info->subdev = &info->sasd.asd; /* Or shall this be managed by the soc-camera device? */ diff --git a/drivers/media/platform/soc_camera/soc_mediabus.c b/drivers/media/platform/soc_camera/soc_mediabus.c index e3e665e1c503..57581f626f4c 100644 --- a/drivers/media/platform/soc_camera/soc_mediabus.c +++ b/drivers/media/platform/soc_camera/soc_mediabus.c @@ -494,6 +494,7 @@ unsigned int soc_mbus_config_compatible(const struct v4l2_mbus_config *cfg, V4L2_MBUS_HSYNC_ACTIVE_LOW); vsync = common_flags & (V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_LOW); + /* fall through */ case V4L2_MBUS_BT656: pclk = common_flags & (V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_PCLK_SAMPLE_FALLING); diff --git a/drivers/media/platform/sti/cec/stih-cec.c b/drivers/media/platform/sti/cec/stih-cec.c index 39ff55145a82..dccbdaebb7a8 100644 --- a/drivers/media/platform/sti/cec/stih-cec.c +++ b/drivers/media/platform/sti/cec/stih-cec.c @@ -1,6 +1,6 @@ /* * STIH4xx CEC driver - * Copyright (C) STMicroelectronic SA 2016 + * Copyright (C) STMicroelectronics SA 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -213,7 +213,8 @@ static int stih_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, for (i = 0; i < msg->len; i++) writeb(msg->msg[i], cec->regs + CEC_TX_DATA_BASE + i); - /* Start transmission, configure hardware to add start and stop bits + /* + * Start transmission, configure hardware to add start and stop bits * Signal free time is handled by the hardware */ writel(CEC_TX_AUTO_SOM_EN | CEC_TX_AUTO_EOM_EN | CEC_TX_START | @@ -225,22 +226,21 @@ static int stih_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, static void stih_tx_done(struct stih_cec *cec, u32 status) { if (status & CEC_TX_ERROR) { - cec_transmit_done(cec->adap, CEC_TX_STATUS_ERROR, 0, 0, 0, 1); + cec_transmit_attempt_done(cec->adap, CEC_TX_STATUS_ERROR); return; } if (status & CEC_TX_ARB_ERROR) { - cec_transmit_done(cec->adap, - CEC_TX_STATUS_ARB_LOST, 1, 0, 0, 0); + cec_transmit_attempt_done(cec->adap, CEC_TX_STATUS_ARB_LOST); return; } if (!(status & CEC_TX_ACK_GET_STS)) { - cec_transmit_done(cec->adap, CEC_TX_STATUS_NACK, 0, 1, 0, 0); + cec_transmit_attempt_done(cec->adap, CEC_TX_STATUS_NACK); return; } - cec_transmit_done(cec->adap, CEC_TX_STATUS_OK, 0, 0, 0, 0); + cec_transmit_attempt_done(cec->adap, CEC_TX_STATUS_OK); } static void stih_rx_done(struct stih_cec *cec, u32 status) @@ -353,7 +353,7 @@ static int stih_cec_probe(struct platform_device *pdev) cec->adap = cec_allocate_adapter(&sti_cec_adap_ops, cec, CEC_NAME, CEC_CAP_LOG_ADDRS | CEC_CAP_PASSTHROUGH | - CEC_CAP_TRANSMIT, 1); + CEC_CAP_TRANSMIT, CEC_MAX_LOG_ADDRS); ret = PTR_ERR_OR_ZERO(cec->adap); if (ret) return ret; diff --git a/drivers/media/platform/stm32/Makefile b/drivers/media/platform/stm32/Makefile new file mode 100644 index 000000000000..07355091376b --- /dev/null +++ b/drivers/media/platform/stm32/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32-dcmi.o +obj-$(CONFIG_VIDEO_STM32_HDMI_CEC) += stm32-cec.o diff --git a/drivers/media/platform/stm32/stm32-cec.c b/drivers/media/platform/stm32/stm32-cec.c new file mode 100644 index 000000000000..9ab896b01ee8 --- /dev/null +++ b/drivers/media/platform/stm32/stm32-cec.c @@ -0,0 +1,362 @@ +/* + * STM32 CEC driver + * Copyright (C) STMicroelectronics SA 2017 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include <media/cec.h> + +#define CEC_NAME "stm32-cec" + +/* CEC registers */ +#define CEC_CR 0x0000 /* Control Register */ +#define CEC_CFGR 0x0004 /* ConFiGuration Register */ +#define CEC_TXDR 0x0008 /* Rx data Register */ +#define CEC_RXDR 0x000C /* Rx data Register */ +#define CEC_ISR 0x0010 /* Interrupt and status Register */ +#define CEC_IER 0x0014 /* Interrupt enable Register */ + +#define TXEOM BIT(2) +#define TXSOM BIT(1) +#define CECEN BIT(0) + +#define LSTN BIT(31) +#define OAR GENMASK(30, 16) +#define SFTOP BIT(8) +#define BRDNOGEN BIT(7) +#define LBPEGEN BIT(6) +#define BREGEN BIT(5) +#define BRESTP BIT(4) +#define RXTOL BIT(3) +#define SFT GENMASK(2, 0) +#define FULL_CFG (LSTN | SFTOP | BRDNOGEN | LBPEGEN | BREGEN | BRESTP \ + | RXTOL) + +#define TXACKE BIT(12) +#define TXERR BIT(11) +#define TXUDR BIT(10) +#define TXEND BIT(9) +#define TXBR BIT(8) +#define ARBLST BIT(7) +#define RXACKE BIT(6) +#define RXOVR BIT(2) +#define RXEND BIT(1) +#define RXBR BIT(0) + +#define ALL_TX_IT (TXEND | TXBR | TXACKE | TXERR | TXUDR | ARBLST) +#define ALL_RX_IT (RXEND | RXBR | RXACKE | RXOVR) + +struct stm32_cec { + struct cec_adapter *adap; + struct device *dev; + struct clk *clk_cec; + struct clk *clk_hdmi_cec; + struct reset_control *rstc; + struct regmap *regmap; + int irq; + u32 irq_status; + struct cec_msg rx_msg; + struct cec_msg tx_msg; + int tx_cnt; +}; + +static void cec_hw_init(struct stm32_cec *cec) +{ + regmap_update_bits(cec->regmap, CEC_CR, TXEOM | TXSOM | CECEN, 0); + + regmap_update_bits(cec->regmap, CEC_IER, ALL_TX_IT | ALL_RX_IT, + ALL_TX_IT | ALL_RX_IT); + + regmap_update_bits(cec->regmap, CEC_CFGR, FULL_CFG, FULL_CFG); +} + +static void stm32_tx_done(struct stm32_cec *cec, u32 status) +{ + if (status & (TXERR | TXUDR)) { + cec_transmit_done(cec->adap, CEC_TX_STATUS_ERROR, + 0, 0, 0, 1); + return; + } + + if (status & ARBLST) { + cec_transmit_done(cec->adap, CEC_TX_STATUS_ARB_LOST, + 1, 0, 0, 0); + return; + } + + if (status & TXACKE) { + cec_transmit_done(cec->adap, CEC_TX_STATUS_NACK, + 0, 1, 0, 0); + return; + } + + if (cec->irq_status & TXBR) { + /* send next byte */ + if (cec->tx_cnt < cec->tx_msg.len) + regmap_write(cec->regmap, CEC_TXDR, + cec->tx_msg.msg[cec->tx_cnt++]); + + /* TXEOM is set to command transmission of the last byte */ + if (cec->tx_cnt == cec->tx_msg.len) + regmap_update_bits(cec->regmap, CEC_CR, TXEOM, TXEOM); + } + + if (cec->irq_status & TXEND) + cec_transmit_done(cec->adap, CEC_TX_STATUS_OK, 0, 0, 0, 0); +} + +static void stm32_rx_done(struct stm32_cec *cec, u32 status) +{ + if (cec->irq_status & (RXACKE | RXOVR)) { + cec->rx_msg.len = 0; + return; + } + + if (cec->irq_status & RXBR) { + u32 val; + + regmap_read(cec->regmap, CEC_RXDR, &val); + cec->rx_msg.msg[cec->rx_msg.len++] = val & 0xFF; + } + + if (cec->irq_status & RXEND) { + cec_received_msg(cec->adap, &cec->rx_msg); + cec->rx_msg.len = 0; + } +} + +static irqreturn_t stm32_cec_irq_thread(int irq, void *arg) +{ + struct stm32_cec *cec = arg; + + if (cec->irq_status & ALL_TX_IT) + stm32_tx_done(cec, cec->irq_status); + + if (cec->irq_status & ALL_RX_IT) + stm32_rx_done(cec, cec->irq_status); + + cec->irq_status = 0; + + return IRQ_HANDLED; +} + +static irqreturn_t stm32_cec_irq_handler(int irq, void *arg) +{ + struct stm32_cec *cec = arg; + + regmap_read(cec->regmap, CEC_ISR, &cec->irq_status); + + regmap_update_bits(cec->regmap, CEC_ISR, + ALL_TX_IT | ALL_RX_IT, + ALL_TX_IT | ALL_RX_IT); + + return IRQ_WAKE_THREAD; +} + +static int stm32_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct stm32_cec *cec = adap->priv; + int ret = 0; + + if (enable) { + ret = clk_enable(cec->clk_cec); + if (ret) + dev_err(cec->dev, "fail to enable cec clock\n"); + + clk_enable(cec->clk_hdmi_cec); + regmap_update_bits(cec->regmap, CEC_CR, CECEN, CECEN); + } else { + clk_disable(cec->clk_cec); + clk_disable(cec->clk_hdmi_cec); + regmap_update_bits(cec->regmap, CEC_CR, CECEN, 0); + } + + return ret; +} + +static int stm32_cec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr) +{ + struct stm32_cec *cec = adap->priv; + u32 oar = (1 << logical_addr) << 16; + + regmap_update_bits(cec->regmap, CEC_CR, CECEN, 0); + + if (logical_addr == CEC_LOG_ADDR_INVALID) + regmap_update_bits(cec->regmap, CEC_CFGR, OAR, 0); + else + regmap_update_bits(cec->regmap, CEC_CFGR, oar, oar); + + regmap_update_bits(cec->regmap, CEC_CR, CECEN, CECEN); + + return 0; +} + +static int stm32_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct stm32_cec *cec = adap->priv; + + /* Copy message */ + cec->tx_msg = *msg; + cec->tx_cnt = 0; + + /* + * If the CEC message consists of only one byte, + * TXEOM must be set before of TXSOM. + */ + if (cec->tx_msg.len == 1) + regmap_update_bits(cec->regmap, CEC_CR, TXEOM, TXEOM); + + /* TXSOM is set to command transmission of the first byte */ + regmap_update_bits(cec->regmap, CEC_CR, TXSOM, TXSOM); + + /* Write the header (first byte of message) */ + regmap_write(cec->regmap, CEC_TXDR, cec->tx_msg.msg[0]); + cec->tx_cnt++; + + return 0; +} + +static const struct cec_adap_ops stm32_cec_adap_ops = { + .adap_enable = stm32_cec_adap_enable, + .adap_log_addr = stm32_cec_adap_log_addr, + .adap_transmit = stm32_cec_adap_transmit, +}; + +static const struct regmap_config stm32_cec_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = sizeof(u32), + .max_register = 0x14, + .fast_io = true, +}; + +static int stm32_cec_probe(struct platform_device *pdev) +{ + u32 caps = CEC_CAP_LOG_ADDRS | CEC_CAP_PASSTHROUGH | + CEC_CAP_TRANSMIT | CEC_CAP_RC | CEC_CAP_PHYS_ADDR | + CEC_MODE_MONITOR_ALL; + struct resource *res; + struct stm32_cec *cec; + void __iomem *mmio; + int ret; + + cec = devm_kzalloc(&pdev->dev, sizeof(*cec), GFP_KERNEL); + if (!cec) + return -ENOMEM; + + cec->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mmio = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mmio)) + return PTR_ERR(mmio); + + cec->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "cec", mmio, + &stm32_cec_regmap_cfg); + + if (IS_ERR(cec->regmap)) + return PTR_ERR(cec->regmap); + + cec->irq = platform_get_irq(pdev, 0); + if (cec->irq < 0) + return cec->irq; + + ret = devm_request_threaded_irq(&pdev->dev, cec->irq, + stm32_cec_irq_handler, + stm32_cec_irq_thread, + 0, + pdev->name, cec); + if (ret) + return ret; + + cec->clk_cec = devm_clk_get(&pdev->dev, "cec"); + if (IS_ERR(cec->clk_cec)) { + dev_err(&pdev->dev, "Cannot get cec clock\n"); + return PTR_ERR(cec->clk_cec); + } + + ret = clk_prepare(cec->clk_cec); + if (ret) { + dev_err(&pdev->dev, "Unable to prepare cec clock\n"); + return ret; + } + + cec->clk_hdmi_cec = devm_clk_get(&pdev->dev, "hdmi-cec"); + if (!IS_ERR(cec->clk_hdmi_cec)) { + ret = clk_prepare(cec->clk_hdmi_cec); + if (ret) { + dev_err(&pdev->dev, "Unable to prepare hdmi-cec clock\n"); + return ret; + } + } + + /* + * CEC_CAP_PHYS_ADDR caps should be removed when a cec notifier is + * available for example when a drm driver can provide edid + */ + cec->adap = cec_allocate_adapter(&stm32_cec_adap_ops, cec, + CEC_NAME, caps, CEC_MAX_LOG_ADDRS); + ret = PTR_ERR_OR_ZERO(cec->adap); + if (ret) + return ret; + + ret = cec_register_adapter(cec->adap, &pdev->dev); + if (ret) { + cec_delete_adapter(cec->adap); + return ret; + } + + cec_hw_init(cec); + + platform_set_drvdata(pdev, cec); + + return 0; +} + +static int stm32_cec_remove(struct platform_device *pdev) +{ + struct stm32_cec *cec = platform_get_drvdata(pdev); + + clk_unprepare(cec->clk_cec); + clk_unprepare(cec->clk_hdmi_cec); + + cec_unregister_adapter(cec->adap); + + return 0; +} + +static const struct of_device_id stm32_cec_of_match[] = { + { .compatible = "st,stm32-cec" }, + { /* end node */ } +}; +MODULE_DEVICE_TABLE(of, stm32_cec_of_match); + +static struct platform_driver stm32_cec_driver = { + .probe = stm32_cec_probe, + .remove = stm32_cec_remove, + .driver = { + .name = CEC_NAME, + .of_match_table = stm32_cec_of_match, + }, +}; + +module_platform_driver(stm32_cec_driver); + +MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>"); +MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics STM32 Consumer Electronics Control"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/stm32/stm32-dcmi.c b/drivers/media/platform/stm32/stm32-dcmi.c new file mode 100644 index 000000000000..83d32a5d0f40 --- /dev/null +++ b/drivers/media/platform/stm32/stm32-dcmi.c @@ -0,0 +1,1404 @@ +/* + * Driver for STM32 Digital Camera Memory Interface + * + * Copyright (C) STMicroelectronics SA 2017 + * Authors: Yannick Fertre <yannick.fertre@st.com> + * Hugues Fruchet <hugues.fruchet@st.com> + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + * + * This driver is based on atmel_isi.c + * + */ + +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/reset.h> +#include <linux/videodev2.h> + +#include <media/v4l2-ctrls.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-image-sizes.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-dma-contig.h> + +#define DRV_NAME "stm32-dcmi" + +/* Registers offset for DCMI */ +#define DCMI_CR 0x00 /* Control Register */ +#define DCMI_SR 0x04 /* Status Register */ +#define DCMI_RIS 0x08 /* Raw Interrupt Status register */ +#define DCMI_IER 0x0C /* Interrupt Enable Register */ +#define DCMI_MIS 0x10 /* Masked Interrupt Status register */ +#define DCMI_ICR 0x14 /* Interrupt Clear Register */ +#define DCMI_ESCR 0x18 /* Embedded Synchronization Code Register */ +#define DCMI_ESUR 0x1C /* Embedded Synchronization Unmask Register */ +#define DCMI_CWSTRT 0x20 /* Crop Window STaRT */ +#define DCMI_CWSIZE 0x24 /* Crop Window SIZE */ +#define DCMI_DR 0x28 /* Data Register */ +#define DCMI_IDR 0x2C /* IDentifier Register */ + +/* Bits definition for control register (DCMI_CR) */ +#define CR_CAPTURE BIT(0) +#define CR_CM BIT(1) +#define CR_CROP BIT(2) +#define CR_JPEG BIT(3) +#define CR_ESS BIT(4) +#define CR_PCKPOL BIT(5) +#define CR_HSPOL BIT(6) +#define CR_VSPOL BIT(7) +#define CR_FCRC_0 BIT(8) +#define CR_FCRC_1 BIT(9) +#define CR_EDM_0 BIT(10) +#define CR_EDM_1 BIT(11) +#define CR_ENABLE BIT(14) + +/* Bits definition for status register (DCMI_SR) */ +#define SR_HSYNC BIT(0) +#define SR_VSYNC BIT(1) +#define SR_FNE BIT(2) + +/* + * Bits definition for interrupt registers + * (DCMI_RIS, DCMI_IER, DCMI_MIS, DCMI_ICR) + */ +#define IT_FRAME BIT(0) +#define IT_OVR BIT(1) +#define IT_ERR BIT(2) +#define IT_VSYNC BIT(3) +#define IT_LINE BIT(4) + +enum state { + STOPPED = 0, + RUNNING, + STOPPING, +}; + +#define MIN_WIDTH 16U +#define MAX_WIDTH 2048U +#define MIN_HEIGHT 16U +#define MAX_HEIGHT 2048U + +#define TIMEOUT_MS 1000 + +struct dcmi_graph_entity { + struct device_node *node; + + struct v4l2_async_subdev asd; + struct v4l2_subdev *subdev; +}; + +struct dcmi_format { + u32 fourcc; + u32 mbus_code; + u8 bpp; +}; + +struct dcmi_buf { + struct vb2_v4l2_buffer vb; + bool prepared; + dma_addr_t paddr; + size_t size; + struct list_head list; +}; + +struct stm32_dcmi { + /* Protects the access of variables shared within the interrupt */ + spinlock_t irqlock; + struct device *dev; + void __iomem *regs; + struct resource *res; + struct reset_control *rstc; + int sequence; + struct list_head buffers; + struct dcmi_buf *active; + + struct v4l2_device v4l2_dev; + struct video_device *vdev; + struct v4l2_async_notifier notifier; + struct dcmi_graph_entity entity; + struct v4l2_format fmt; + + const struct dcmi_format **user_formats; + unsigned int num_user_formats; + const struct dcmi_format *current_fmt; + + /* Protect this data structure */ + struct mutex lock; + struct vb2_queue queue; + + struct v4l2_fwnode_bus_parallel bus; + struct completion complete; + struct clk *mclk; + enum state state; + struct dma_chan *dma_chan; + dma_cookie_t dma_cookie; + u32 misr; + int errors_count; + int buffers_count; +}; + +static inline struct stm32_dcmi *notifier_to_dcmi(struct v4l2_async_notifier *n) +{ + return container_of(n, struct stm32_dcmi, notifier); +} + +static inline u32 reg_read(void __iomem *base, u32 reg) +{ + return readl_relaxed(base + reg); +} + +static inline void reg_write(void __iomem *base, u32 reg, u32 val) +{ + writel_relaxed(val, base + reg); +} + +static inline void reg_set(void __iomem *base, u32 reg, u32 mask) +{ + reg_write(base, reg, reg_read(base, reg) | mask); +} + +static inline void reg_clear(void __iomem *base, u32 reg, u32 mask) +{ + reg_write(base, reg, reg_read(base, reg) & ~mask); +} + +static int dcmi_start_capture(struct stm32_dcmi *dcmi); + +static void dcmi_dma_callback(void *param) +{ + struct stm32_dcmi *dcmi = (struct stm32_dcmi *)param; + struct dma_chan *chan = dcmi->dma_chan; + struct dma_tx_state state; + enum dma_status status; + + spin_lock(&dcmi->irqlock); + + /* Check DMA status */ + status = dmaengine_tx_status(chan, dcmi->dma_cookie, &state); + + switch (status) { + case DMA_IN_PROGRESS: + dev_dbg(dcmi->dev, "%s: Received DMA_IN_PROGRESS\n", __func__); + break; + case DMA_PAUSED: + dev_err(dcmi->dev, "%s: Received DMA_PAUSED\n", __func__); + break; + case DMA_ERROR: + dev_err(dcmi->dev, "%s: Received DMA_ERROR\n", __func__); + break; + case DMA_COMPLETE: + dev_dbg(dcmi->dev, "%s: Received DMA_COMPLETE\n", __func__); + + if (dcmi->active) { + struct dcmi_buf *buf = dcmi->active; + struct vb2_v4l2_buffer *vbuf = &dcmi->active->vb; + + vbuf->sequence = dcmi->sequence++; + vbuf->field = V4L2_FIELD_NONE; + vbuf->vb2_buf.timestamp = ktime_get_ns(); + vb2_set_plane_payload(&vbuf->vb2_buf, 0, buf->size); + vb2_buffer_done(&vbuf->vb2_buf, VB2_BUF_STATE_DONE); + dev_dbg(dcmi->dev, "buffer[%d] done seq=%d\n", + vbuf->vb2_buf.index, vbuf->sequence); + + dcmi->buffers_count++; + dcmi->active = NULL; + } + + /* Restart a new DMA transfer with next buffer */ + if (dcmi->state == RUNNING) { + if (list_empty(&dcmi->buffers)) { + dev_err(dcmi->dev, "%s: No more buffer queued, cannot capture buffer", + __func__); + dcmi->errors_count++; + dcmi->active = NULL; + + spin_unlock(&dcmi->irqlock); + return; + } + + dcmi->active = list_entry(dcmi->buffers.next, + struct dcmi_buf, list); + + list_del_init(&dcmi->active->list); + + if (dcmi_start_capture(dcmi)) { + dev_err(dcmi->dev, "%s: Cannot restart capture on DMA complete", + __func__); + + spin_unlock(&dcmi->irqlock); + return; + } + + /* Enable capture */ + reg_set(dcmi->regs, DCMI_CR, CR_CAPTURE); + } + + break; + default: + dev_err(dcmi->dev, "%s: Received unknown status\n", __func__); + break; + } + + spin_unlock(&dcmi->irqlock); +} + +static int dcmi_start_dma(struct stm32_dcmi *dcmi, + struct dcmi_buf *buf) +{ + struct dma_async_tx_descriptor *desc = NULL; + struct dma_slave_config config; + int ret; + + memset(&config, 0, sizeof(config)); + + config.src_addr = (dma_addr_t)dcmi->res->start + DCMI_DR; + config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + config.dst_maxburst = 4; + + /* Configure DMA channel */ + ret = dmaengine_slave_config(dcmi->dma_chan, &config); + if (ret < 0) { + dev_err(dcmi->dev, "%s: DMA channel config failed (%d)\n", + __func__, ret); + return ret; + } + + /* Prepare a DMA transaction */ + desc = dmaengine_prep_slave_single(dcmi->dma_chan, buf->paddr, + buf->size, + DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); + if (!desc) { + dev_err(dcmi->dev, "%s: DMA dmaengine_prep_slave_single failed for buffer size %zu\n", + __func__, buf->size); + return -EINVAL; + } + + /* Set completion callback routine for notification */ + desc->callback = dcmi_dma_callback; + desc->callback_param = dcmi; + + /* Push current DMA transaction in the pending queue */ + dcmi->dma_cookie = dmaengine_submit(desc); + + dma_async_issue_pending(dcmi->dma_chan); + + return 0; +} + +static int dcmi_start_capture(struct stm32_dcmi *dcmi) +{ + int ret; + struct dcmi_buf *buf = dcmi->active; + + if (!buf) + return -EINVAL; + + ret = dcmi_start_dma(dcmi, buf); + if (ret) { + dcmi->errors_count++; + return ret; + } + + /* Enable capture */ + reg_set(dcmi->regs, DCMI_CR, CR_CAPTURE); + + return 0; +} + +static irqreturn_t dcmi_irq_thread(int irq, void *arg) +{ + struct stm32_dcmi *dcmi = arg; + + spin_lock(&dcmi->irqlock); + + /* Stop capture is required */ + if (dcmi->state == STOPPING) { + reg_clear(dcmi->regs, DCMI_IER, IT_FRAME | IT_OVR | IT_ERR); + + dcmi->state = STOPPED; + + complete(&dcmi->complete); + + spin_unlock(&dcmi->irqlock); + return IRQ_HANDLED; + } + + if ((dcmi->misr & IT_OVR) || (dcmi->misr & IT_ERR)) { + /* + * An overflow or an error has been detected, + * stop current DMA transfert & restart it + */ + dev_warn(dcmi->dev, "%s: Overflow or error detected\n", + __func__); + + dcmi->errors_count++; + dmaengine_terminate_all(dcmi->dma_chan); + + reg_set(dcmi->regs, DCMI_ICR, IT_FRAME | IT_OVR | IT_ERR); + + dev_dbg(dcmi->dev, "Restarting capture after DCMI error\n"); + + if (dcmi_start_capture(dcmi)) { + dev_err(dcmi->dev, "%s: Cannot restart capture on overflow or error\n", + __func__); + + spin_unlock(&dcmi->irqlock); + return IRQ_HANDLED; + } + } + + spin_unlock(&dcmi->irqlock); + return IRQ_HANDLED; +} + +static irqreturn_t dcmi_irq_callback(int irq, void *arg) +{ + struct stm32_dcmi *dcmi = arg; + + spin_lock(&dcmi->irqlock); + + dcmi->misr = reg_read(dcmi->regs, DCMI_MIS); + + /* Clear interrupt */ + reg_set(dcmi->regs, DCMI_ICR, IT_FRAME | IT_OVR | IT_ERR); + + spin_unlock(&dcmi->irqlock); + + return IRQ_WAKE_THREAD; +} + +static int dcmi_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vq); + unsigned int size; + + size = dcmi->fmt.fmt.pix.sizeimage; + + /* Make sure the image size is large enough */ + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + + dcmi->active = NULL; + + dev_dbg(dcmi->dev, "Setup queue, count=%d, size=%d\n", + *nbuffers, size); + + return 0; +} + +static int dcmi_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dcmi_buf *buf = container_of(vbuf, struct dcmi_buf, vb); + + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static int dcmi_buf_prepare(struct vb2_buffer *vb) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dcmi_buf *buf = container_of(vbuf, struct dcmi_buf, vb); + unsigned long size; + + size = dcmi->fmt.fmt.pix.sizeimage; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(dcmi->dev, "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + + if (!buf->prepared) { + /* Get memory addresses */ + buf->paddr = + vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + buf->size = vb2_plane_size(&buf->vb.vb2_buf, 0); + buf->prepared = true; + + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->size); + + dev_dbg(dcmi->dev, "buffer[%d] phy=0x%pad size=%zu\n", + vb->index, &buf->paddr, buf->size); + } + + return 0; +} + +static void dcmi_buf_queue(struct vb2_buffer *vb) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dcmi_buf *buf = container_of(vbuf, struct dcmi_buf, vb); + unsigned long flags = 0; + + spin_lock_irqsave(&dcmi->irqlock, flags); + + if ((dcmi->state == RUNNING) && (!dcmi->active)) { + dcmi->active = buf; + + dev_dbg(dcmi->dev, "Starting capture on buffer[%d] queued\n", + buf->vb.vb2_buf.index); + + if (dcmi_start_capture(dcmi)) { + dev_err(dcmi->dev, "%s: Cannot restart capture on overflow or error\n", + __func__); + + spin_unlock_irqrestore(&dcmi->irqlock, flags); + return; + } + } else { + /* Enqueue to video buffers list */ + list_add_tail(&buf->list, &dcmi->buffers); + } + + spin_unlock_irqrestore(&dcmi->irqlock, flags); +} + +static int dcmi_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vq); + struct dcmi_buf *buf, *node; + u32 val; + int ret; + + ret = clk_enable(dcmi->mclk); + if (ret) { + dev_err(dcmi->dev, "%s: Failed to start streaming, cannot enable clock", + __func__); + goto err_release_buffers; + } + + /* Enable stream on the sub device */ + ret = v4l2_subdev_call(dcmi->entity.subdev, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) { + dev_err(dcmi->dev, "%s: Failed to start streaming, subdev streamon error", + __func__); + goto err_disable_clock; + } + + spin_lock_irq(&dcmi->irqlock); + + val = reg_read(dcmi->regs, DCMI_CR); + + val &= ~(CR_PCKPOL | CR_HSPOL | CR_VSPOL | + CR_EDM_0 | CR_EDM_1 | CR_FCRC_0 | + CR_FCRC_1 | CR_JPEG | CR_ESS); + + /* Set bus width */ + switch (dcmi->bus.bus_width) { + case 14: + val &= CR_EDM_0 + CR_EDM_1; + break; + case 12: + val &= CR_EDM_1; + break; + case 10: + val &= CR_EDM_0; + break; + default: + /* Set bus width to 8 bits by default */ + break; + } + + /* Set vertical synchronization polarity */ + if (dcmi->bus.flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + val |= CR_VSPOL; + + /* Set horizontal synchronization polarity */ + if (dcmi->bus.flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + val |= CR_HSPOL; + + /* Set pixel clock polarity */ + if (dcmi->bus.flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + val |= CR_PCKPOL; + + reg_write(dcmi->regs, DCMI_CR, val); + + /* Enable dcmi */ + reg_set(dcmi->regs, DCMI_CR, CR_ENABLE); + + dcmi->state = RUNNING; + + dcmi->sequence = 0; + dcmi->errors_count = 0; + dcmi->buffers_count = 0; + dcmi->active = NULL; + + /* + * Start transfer if at least one buffer has been queued, + * otherwise transfer is deferred at buffer queueing + */ + if (list_empty(&dcmi->buffers)) { + dev_dbg(dcmi->dev, "Start streaming is deferred to next buffer queueing\n"); + spin_unlock_irq(&dcmi->irqlock); + return 0; + } + + dcmi->active = list_entry(dcmi->buffers.next, struct dcmi_buf, list); + list_del_init(&dcmi->active->list); + + dev_dbg(dcmi->dev, "Start streaming, starting capture\n"); + + ret = dcmi_start_capture(dcmi); + if (ret) { + dev_err(dcmi->dev, "%s: Start streaming failed, cannot start capture", + __func__); + + spin_unlock_irq(&dcmi->irqlock); + goto err_subdev_streamoff; + } + + /* Enable interruptions */ + reg_set(dcmi->regs, DCMI_IER, IT_FRAME | IT_OVR | IT_ERR); + + spin_unlock_irq(&dcmi->irqlock); + + return 0; + +err_subdev_streamoff: + v4l2_subdev_call(dcmi->entity.subdev, video, s_stream, 0); + +err_disable_clock: + clk_disable(dcmi->mclk); + +err_release_buffers: + spin_lock_irq(&dcmi->irqlock); + /* + * Return all buffers to vb2 in QUEUED state. + * This will give ownership back to userspace + */ + if (dcmi->active) { + buf = dcmi->active; + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); + dcmi->active = NULL; + } + list_for_each_entry_safe(buf, node, &dcmi->buffers, list) { + list_del_init(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); + } + spin_unlock_irq(&dcmi->irqlock); + + return ret; +} + +static void dcmi_stop_streaming(struct vb2_queue *vq) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vq); + struct dcmi_buf *buf, *node; + unsigned long time_ms = msecs_to_jiffies(TIMEOUT_MS); + long timeout; + int ret; + + /* Disable stream on the sub device */ + ret = v4l2_subdev_call(dcmi->entity.subdev, video, s_stream, 0); + if (ret && ret != -ENOIOCTLCMD) + dev_err(dcmi->dev, "stream off failed in subdev\n"); + + dcmi->state = STOPPING; + + timeout = wait_for_completion_interruptible_timeout(&dcmi->complete, + time_ms); + + spin_lock_irq(&dcmi->irqlock); + + /* Disable interruptions */ + reg_clear(dcmi->regs, DCMI_IER, IT_FRAME | IT_OVR | IT_ERR); + + /* Disable DCMI */ + reg_clear(dcmi->regs, DCMI_CR, CR_ENABLE); + + if (!timeout) { + dev_err(dcmi->dev, "Timeout during stop streaming\n"); + dcmi->state = STOPPED; + } + + /* Return all queued buffers to vb2 in ERROR state */ + if (dcmi->active) { + buf = dcmi->active; + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + dcmi->active = NULL; + } + list_for_each_entry_safe(buf, node, &dcmi->buffers, list) { + list_del_init(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + + spin_unlock_irq(&dcmi->irqlock); + + /* Stop all pending DMA operations */ + dmaengine_terminate_all(dcmi->dma_chan); + + clk_disable(dcmi->mclk); + + dev_dbg(dcmi->dev, "Stop streaming, errors=%d buffers=%d\n", + dcmi->errors_count, dcmi->buffers_count); +} + +static struct vb2_ops dcmi_video_qops = { + .queue_setup = dcmi_queue_setup, + .buf_init = dcmi_buf_init, + .buf_prepare = dcmi_buf_prepare, + .buf_queue = dcmi_buf_queue, + .start_streaming = dcmi_start_streaming, + .stop_streaming = dcmi_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int dcmi_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + + *fmt = dcmi->fmt; + + return 0; +} + +static const struct dcmi_format *find_format_by_fourcc(struct stm32_dcmi *dcmi, + unsigned int fourcc) +{ + unsigned int num_formats = dcmi->num_user_formats; + const struct dcmi_format *fmt; + unsigned int i; + + for (i = 0; i < num_formats; i++) { + fmt = dcmi->user_formats[i]; + if (fmt->fourcc == fourcc) + return fmt; + } + + return NULL; +} + +static int dcmi_try_fmt(struct stm32_dcmi *dcmi, struct v4l2_format *f, + const struct dcmi_format **current_fmt) +{ + const struct dcmi_format *dcmi_fmt; + struct v4l2_pix_format *pixfmt = &f->fmt.pix; + struct v4l2_subdev_pad_config pad_cfg; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_TRY, + }; + int ret; + + dcmi_fmt = find_format_by_fourcc(dcmi, pixfmt->pixelformat); + if (!dcmi_fmt) { + dcmi_fmt = dcmi->user_formats[dcmi->num_user_formats - 1]; + pixfmt->pixelformat = dcmi_fmt->fourcc; + } + + /* Limit to hardware capabilities */ + pixfmt->width = clamp(pixfmt->width, MIN_WIDTH, MAX_WIDTH); + pixfmt->height = clamp(pixfmt->height, MIN_HEIGHT, MAX_HEIGHT); + + v4l2_fill_mbus_format(&format.format, pixfmt, dcmi_fmt->mbus_code); + ret = v4l2_subdev_call(dcmi->entity.subdev, pad, set_fmt, + &pad_cfg, &format); + if (ret < 0) + return ret; + + v4l2_fill_pix_format(pixfmt, &format.format); + + pixfmt->field = V4L2_FIELD_NONE; + pixfmt->bytesperline = pixfmt->width * dcmi_fmt->bpp; + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; + + if (current_fmt) + *current_fmt = dcmi_fmt; + + return 0; +} + +static int dcmi_set_fmt(struct stm32_dcmi *dcmi, struct v4l2_format *f) +{ + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + const struct dcmi_format *current_fmt; + int ret; + + ret = dcmi_try_fmt(dcmi, f, ¤t_fmt); + if (ret) + return ret; + + v4l2_fill_mbus_format(&format.format, &f->fmt.pix, + current_fmt->mbus_code); + ret = v4l2_subdev_call(dcmi->entity.subdev, pad, + set_fmt, NULL, &format); + if (ret < 0) + return ret; + + dcmi->fmt = *f; + dcmi->current_fmt = current_fmt; + + return 0; +} + +static int dcmi_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + + if (vb2_is_streaming(&dcmi->queue)) + return -EBUSY; + + return dcmi_set_fmt(dcmi, f); +} + +static int dcmi_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + + return dcmi_try_fmt(dcmi, f, NULL); +} + +static int dcmi_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + + if (f->index >= dcmi->num_user_formats) + return -EINVAL; + + f->pixelformat = dcmi->user_formats[f->index]->fourcc; + return 0; +} + +static int dcmi_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strlcpy(cap->driver, DRV_NAME, sizeof(cap->driver)); + strlcpy(cap->card, "STM32 Camera Memory Interface", + sizeof(cap->card)); + strlcpy(cap->bus_info, "platform:dcmi", sizeof(cap->bus_info)); + return 0; +} + +static int dcmi_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index != 0) + return -EINVAL; + + i->type = V4L2_INPUT_TYPE_CAMERA; + strlcpy(i->name, "Camera", sizeof(i->name)); + return 0; +} + +static int dcmi_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int dcmi_s_input(struct file *file, void *priv, unsigned int i) +{ + if (i > 0) + return -EINVAL; + return 0; +} + +static int dcmi_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + const struct dcmi_format *dcmi_fmt; + struct v4l2_subdev_frame_size_enum fse = { + .index = fsize->index, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + dcmi_fmt = find_format_by_fourcc(dcmi, fsize->pixel_format); + if (!dcmi_fmt) + return -EINVAL; + + fse.code = dcmi_fmt->mbus_code; + + ret = v4l2_subdev_call(dcmi->entity.subdev, pad, enum_frame_size, + NULL, &fse); + if (ret) + return ret; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = fse.max_width; + fsize->discrete.height = fse.max_height; + + return 0; +} + +static int dcmi_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *fival) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + const struct dcmi_format *dcmi_fmt; + struct v4l2_subdev_frame_interval_enum fie = { + .index = fival->index, + .width = fival->width, + .height = fival->height, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + dcmi_fmt = find_format_by_fourcc(dcmi, fival->pixel_format); + if (!dcmi_fmt) + return -EINVAL; + + fie.code = dcmi_fmt->mbus_code; + + ret = v4l2_subdev_call(dcmi->entity.subdev, pad, + enum_frame_interval, NULL, &fie); + if (ret) + return ret; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete = fie.interval; + + return 0; +} + +static const struct of_device_id stm32_dcmi_of_match[] = { + { .compatible = "st,stm32-dcmi"}, + { /* end node */ }, +}; +MODULE_DEVICE_TABLE(of, stm32_dcmi_of_match); + +static int dcmi_open(struct file *file) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + struct v4l2_subdev *sd = dcmi->entity.subdev; + int ret; + + if (mutex_lock_interruptible(&dcmi->lock)) + return -ERESTARTSYS; + + ret = v4l2_fh_open(file); + if (ret < 0) + goto unlock; + + if (!v4l2_fh_is_singular_file(file)) + goto fh_rel; + + ret = v4l2_subdev_call(sd, core, s_power, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) + goto fh_rel; + + ret = dcmi_set_fmt(dcmi, &dcmi->fmt); + if (ret) + v4l2_subdev_call(sd, core, s_power, 0); +fh_rel: + if (ret) + v4l2_fh_release(file); +unlock: + mutex_unlock(&dcmi->lock); + return ret; +} + +static int dcmi_release(struct file *file) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + struct v4l2_subdev *sd = dcmi->entity.subdev; + bool fh_singular; + int ret; + + mutex_lock(&dcmi->lock); + + fh_singular = v4l2_fh_is_singular_file(file); + + ret = _vb2_fop_release(file, NULL); + + if (fh_singular) + v4l2_subdev_call(sd, core, s_power, 0); + + mutex_unlock(&dcmi->lock); + + return ret; +} + +static const struct v4l2_ioctl_ops dcmi_ioctl_ops = { + .vidioc_querycap = dcmi_querycap, + + .vidioc_try_fmt_vid_cap = dcmi_try_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = dcmi_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = dcmi_s_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = dcmi_enum_fmt_vid_cap, + + .vidioc_enum_input = dcmi_enum_input, + .vidioc_g_input = dcmi_g_input, + .vidioc_s_input = dcmi_s_input, + + .vidioc_enum_framesizes = dcmi_enum_framesizes, + .vidioc_enum_frameintervals = dcmi_enum_frameintervals, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_file_operations dcmi_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = dcmi_open, + .release = dcmi_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, +#ifndef CONFIG_MMU + .get_unmapped_area = vb2_fop_get_unmapped_area, +#endif + .read = vb2_fop_read, +}; + +static int dcmi_set_default_fmt(struct stm32_dcmi *dcmi) +{ + struct v4l2_format f = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .fmt.pix = { + .width = CIF_WIDTH, + .height = CIF_HEIGHT, + .field = V4L2_FIELD_NONE, + .pixelformat = dcmi->user_formats[0]->fourcc, + }, + }; + int ret; + + ret = dcmi_try_fmt(dcmi, &f, NULL); + if (ret) + return ret; + dcmi->current_fmt = dcmi->user_formats[0]; + dcmi->fmt = f; + return 0; +} + +static const struct dcmi_format dcmi_formats[] = { + { + .fourcc = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .bpp = 2, + }, +}; + +static int dcmi_formats_init(struct stm32_dcmi *dcmi) +{ + const struct dcmi_format *dcmi_fmts[ARRAY_SIZE(dcmi_formats)]; + unsigned int num_fmts = 0, i, j; + struct v4l2_subdev *subdev = dcmi->entity.subdev; + struct v4l2_subdev_mbus_code_enum mbus_code = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + while (!v4l2_subdev_call(subdev, pad, enum_mbus_code, + NULL, &mbus_code)) { + for (i = 0; i < ARRAY_SIZE(dcmi_formats); i++) { + if (dcmi_formats[i].mbus_code != mbus_code.code) + continue; + + /* Code supported, have we got this fourcc yet? */ + for (j = 0; j < num_fmts; j++) + if (dcmi_fmts[j]->fourcc == + dcmi_formats[i].fourcc) + /* Already available */ + break; + if (j == num_fmts) + /* New */ + dcmi_fmts[num_fmts++] = dcmi_formats + i; + } + mbus_code.index++; + } + + if (!num_fmts) + return -ENXIO; + + dcmi->num_user_formats = num_fmts; + dcmi->user_formats = devm_kcalloc(dcmi->dev, + num_fmts, sizeof(struct dcmi_format *), + GFP_KERNEL); + if (!dcmi->user_formats) { + dev_err(dcmi->dev, "could not allocate memory\n"); + return -ENOMEM; + } + + memcpy(dcmi->user_formats, dcmi_fmts, + num_fmts * sizeof(struct dcmi_format *)); + dcmi->current_fmt = dcmi->user_formats[0]; + + return 0; +} + +static int dcmi_graph_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct stm32_dcmi *dcmi = notifier_to_dcmi(notifier); + int ret; + + dcmi->vdev->ctrl_handler = dcmi->entity.subdev->ctrl_handler; + ret = dcmi_formats_init(dcmi); + if (ret) { + dev_err(dcmi->dev, "No supported mediabus format found\n"); + return ret; + } + + ret = dcmi_set_default_fmt(dcmi); + if (ret) { + dev_err(dcmi->dev, "Could not set default format\n"); + return ret; + } + + ret = video_register_device(dcmi->vdev, VFL_TYPE_GRABBER, -1); + if (ret) { + dev_err(dcmi->dev, "Failed to register video device\n"); + return ret; + } + + dev_dbg(dcmi->dev, "Device registered as %s\n", + video_device_node_name(dcmi->vdev)); + return 0; +} + +static void dcmi_graph_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct stm32_dcmi *dcmi = notifier_to_dcmi(notifier); + + dev_dbg(dcmi->dev, "Removing %s\n", video_device_node_name(dcmi->vdev)); + + /* Checks internaly if vdev has been init or not */ + video_unregister_device(dcmi->vdev); +} + +static int dcmi_graph_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct stm32_dcmi *dcmi = notifier_to_dcmi(notifier); + + dev_dbg(dcmi->dev, "Subdev %s bound\n", subdev->name); + + dcmi->entity.subdev = subdev; + + return 0; +} + +static int dcmi_graph_parse(struct stm32_dcmi *dcmi, struct device_node *node) +{ + struct device_node *ep = NULL; + struct device_node *remote; + + while (1) { + ep = of_graph_get_next_endpoint(node, ep); + if (!ep) + return -EINVAL; + + remote = of_graph_get_remote_port_parent(ep); + if (!remote) { + of_node_put(ep); + return -EINVAL; + } + + /* Remote node to connect */ + dcmi->entity.node = remote; + dcmi->entity.asd.match_type = V4L2_ASYNC_MATCH_FWNODE; + dcmi->entity.asd.match.fwnode.fwnode = of_fwnode_handle(remote); + return 0; + } +} + +static int dcmi_graph_init(struct stm32_dcmi *dcmi) +{ + struct v4l2_async_subdev **subdevs = NULL; + int ret; + + /* Parse the graph to extract a list of subdevice DT nodes. */ + ret = dcmi_graph_parse(dcmi, dcmi->dev->of_node); + if (ret < 0) { + dev_err(dcmi->dev, "Graph parsing failed\n"); + return ret; + } + + /* Register the subdevices notifier. */ + subdevs = devm_kzalloc(dcmi->dev, sizeof(*subdevs), GFP_KERNEL); + if (!subdevs) { + of_node_put(dcmi->entity.node); + return -ENOMEM; + } + + subdevs[0] = &dcmi->entity.asd; + + dcmi->notifier.subdevs = subdevs; + dcmi->notifier.num_subdevs = 1; + dcmi->notifier.bound = dcmi_graph_notify_bound; + dcmi->notifier.unbind = dcmi_graph_notify_unbind; + dcmi->notifier.complete = dcmi_graph_notify_complete; + + ret = v4l2_async_notifier_register(&dcmi->v4l2_dev, &dcmi->notifier); + if (ret < 0) { + dev_err(dcmi->dev, "Notifier registration failed\n"); + of_node_put(dcmi->entity.node); + return ret; + } + + return 0; +} + +static int dcmi_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match = NULL; + struct v4l2_fwnode_endpoint ep; + struct stm32_dcmi *dcmi; + struct vb2_queue *q; + struct dma_chan *chan; + struct clk *mclk; + int irq; + int ret = 0; + + match = of_match_device(of_match_ptr(stm32_dcmi_of_match), &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Could not find a match in devicetree\n"); + return -ENODEV; + } + + dcmi = devm_kzalloc(&pdev->dev, sizeof(struct stm32_dcmi), GFP_KERNEL); + if (!dcmi) + return -ENOMEM; + + dcmi->rstc = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(dcmi->rstc)) { + dev_err(&pdev->dev, "Could not get reset control\n"); + return -ENODEV; + } + + /* Get bus characteristics from devicetree */ + np = of_graph_get_next_endpoint(np, NULL); + if (!np) { + dev_err(&pdev->dev, "Could not find the endpoint\n"); + of_node_put(np); + return -ENODEV; + } + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(np), &ep); + if (ret) { + dev_err(&pdev->dev, "Could not parse the endpoint\n"); + of_node_put(np); + return -ENODEV; + } + + if (ep.bus_type == V4L2_MBUS_CSI2) { + dev_err(&pdev->dev, "CSI bus not supported\n"); + of_node_put(np); + return -ENODEV; + } + dcmi->bus.flags = ep.bus.parallel.flags; + dcmi->bus.bus_width = ep.bus.parallel.bus_width; + dcmi->bus.data_shift = ep.bus.parallel.data_shift; + + of_node_put(np); + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(&pdev->dev, "Could not get irq\n"); + return -ENODEV; + } + + dcmi->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!dcmi->res) { + dev_err(&pdev->dev, "Could not get resource\n"); + return -ENODEV; + } + + dcmi->regs = devm_ioremap_resource(&pdev->dev, dcmi->res); + if (IS_ERR(dcmi->regs)) { + dev_err(&pdev->dev, "Could not map registers\n"); + return PTR_ERR(dcmi->regs); + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, dcmi_irq_callback, + dcmi_irq_thread, IRQF_ONESHOT, + dev_name(&pdev->dev), dcmi); + if (ret) { + dev_err(&pdev->dev, "Unable to request irq %d\n", irq); + return -ENODEV; + } + + mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(mclk)) { + dev_err(&pdev->dev, "Unable to get mclk\n"); + return PTR_ERR(mclk); + } + + chan = dma_request_slave_channel(&pdev->dev, "tx"); + if (!chan) { + dev_info(&pdev->dev, "Unable to request DMA channel, defer probing\n"); + return -EPROBE_DEFER; + } + + ret = clk_prepare(mclk); + if (ret) { + dev_err(&pdev->dev, "Unable to prepare mclk %p\n", mclk); + goto err_dma_release; + } + + spin_lock_init(&dcmi->irqlock); + mutex_init(&dcmi->lock); + init_completion(&dcmi->complete); + INIT_LIST_HEAD(&dcmi->buffers); + + dcmi->dev = &pdev->dev; + dcmi->mclk = mclk; + dcmi->state = STOPPED; + dcmi->dma_chan = chan; + + q = &dcmi->queue; + + /* Initialize the top-level structure */ + ret = v4l2_device_register(&pdev->dev, &dcmi->v4l2_dev); + if (ret) + goto err_clk_unprepare; + + dcmi->vdev = video_device_alloc(); + if (!dcmi->vdev) { + ret = -ENOMEM; + goto err_device_unregister; + } + + /* Video node */ + dcmi->vdev->fops = &dcmi_fops; + dcmi->vdev->v4l2_dev = &dcmi->v4l2_dev; + dcmi->vdev->queue = &dcmi->queue; + strlcpy(dcmi->vdev->name, KBUILD_MODNAME, sizeof(dcmi->vdev->name)); + dcmi->vdev->release = video_device_release; + dcmi->vdev->ioctl_ops = &dcmi_ioctl_ops; + dcmi->vdev->lock = &dcmi->lock; + dcmi->vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + video_set_drvdata(dcmi->vdev, dcmi); + + /* Buffer queue */ + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; + q->lock = &dcmi->lock; + q->drv_priv = dcmi; + q->buf_struct_size = sizeof(struct dcmi_buf); + q->ops = &dcmi_video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 2; + q->dev = &pdev->dev; + + ret = vb2_queue_init(q); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to initialize vb2 queue\n"); + goto err_device_release; + } + + ret = dcmi_graph_init(dcmi); + if (ret < 0) + goto err_device_release; + + /* Reset device */ + ret = reset_control_assert(dcmi->rstc); + if (ret) { + dev_err(&pdev->dev, "Failed to assert the reset line\n"); + goto err_device_release; + } + + usleep_range(3000, 5000); + + ret = reset_control_deassert(dcmi->rstc); + if (ret) { + dev_err(&pdev->dev, "Failed to deassert the reset line\n"); + goto err_device_release; + } + + dev_info(&pdev->dev, "Probe done\n"); + + platform_set_drvdata(pdev, dcmi); + return 0; + +err_device_release: + video_device_release(dcmi->vdev); +err_device_unregister: + v4l2_device_unregister(&dcmi->v4l2_dev); +err_clk_unprepare: + clk_unprepare(dcmi->mclk); +err_dma_release: + dma_release_channel(dcmi->dma_chan); + + return ret; +} + +static int dcmi_remove(struct platform_device *pdev) +{ + struct stm32_dcmi *dcmi = platform_get_drvdata(pdev); + + v4l2_async_notifier_unregister(&dcmi->notifier); + v4l2_device_unregister(&dcmi->v4l2_dev); + clk_unprepare(dcmi->mclk); + dma_release_channel(dcmi->dma_chan); + + return 0; +} + +static struct platform_driver stm32_dcmi_driver = { + .probe = dcmi_probe, + .remove = dcmi_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(stm32_dcmi_of_match), + }, +}; + +module_platform_driver(stm32_dcmi_driver); + +MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>"); +MODULE_AUTHOR("Hugues Fruchet <hugues.fruchet@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics STM32 Digital Camera Memory Interface driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); diff --git a/drivers/media/platform/ti-vpe/cal.c b/drivers/media/platform/ti-vpe/cal.c index 7a058b6e03d0..177faa36bc16 100644 --- a/drivers/media/platform/ti-vpe/cal.c +++ b/drivers/media/platform/ti-vpe/cal.c @@ -21,7 +21,7 @@ #include <linux/of_device.h> #include <linux/of_graph.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include <media/v4l2-async.h> #include <media/v4l2-common.h> #include <media/v4l2-ctrls.h> @@ -270,7 +270,7 @@ struct cal_ctx { struct video_device vdev; struct v4l2_async_notifier notifier; struct v4l2_subdev *sensor; - struct v4l2_of_endpoint endpoint; + struct v4l2_fwnode_endpoint endpoint; struct v4l2_async_subdev asd; struct v4l2_async_subdev *asd_list[1]; @@ -608,7 +608,8 @@ static void csi2_lane_config(struct cal_ctx *ctx) u32 val = reg_read(ctx->dev, CAL_CSI2_COMPLEXIO_CFG(ctx->csi2_port)); u32 lane_mask = CAL_CSI2_COMPLEXIO_CFG_CLOCK_POSITION_MASK; u32 polarity_mask = CAL_CSI2_COMPLEXIO_CFG_CLOCK_POL_MASK; - struct v4l2_of_bus_mipi_csi2 *mipi_csi2 = &ctx->endpoint.bus.mipi_csi2; + struct v4l2_fwnode_bus_mipi_csi2 *mipi_csi2 = + &ctx->endpoint.bus.mipi_csi2; int lane; set_field(&val, mipi_csi2->clock_lane + 1, lane_mask); @@ -1643,7 +1644,7 @@ static int of_cal_create_instance(struct cal_ctx *ctx, int inst) struct platform_device *pdev = ctx->dev->pdev; struct device_node *ep_node, *port, *remote_ep, *sensor_node, *parent; - struct v4l2_of_endpoint *endpoint; + struct v4l2_fwnode_endpoint *endpoint; struct v4l2_async_subdev *asd; u32 regval = 0; int ret, index, found_port = 0, lane; @@ -1698,15 +1699,15 @@ static int of_cal_create_instance(struct cal_ctx *ctx, int inst) ctx_dbg(3, ctx, "can't get remote parent\n"); goto cleanup_exit; } - asd->match_type = V4L2_ASYNC_MATCH_OF; - asd->match.of.node = sensor_node; + asd->match_type = V4L2_ASYNC_MATCH_FWNODE; + asd->match.fwnode.fwnode = of_fwnode_handle(sensor_node); remote_ep = of_parse_phandle(ep_node, "remote-endpoint", 0); if (!remote_ep) { ctx_dbg(3, ctx, "can't get remote-endpoint\n"); goto cleanup_exit; } - v4l2_of_parse_endpoint(remote_ep, endpoint); + v4l2_fwnode_endpoint_parse(of_fwnode_handle(remote_ep), endpoint); if (endpoint->bus_type != V4L2_MBUS_CSI2) { ctx_err(ctx, "Port:%d sub-device %s is not a CSI2 device\n", diff --git a/drivers/media/platform/video-mux.c b/drivers/media/platform/video-mux.c new file mode 100644 index 000000000000..665744716f73 --- /dev/null +++ b/drivers/media/platform/video-mux.c @@ -0,0 +1,334 @@ +/* + * video stream multiplexer controlled via mux control + * + * Copyright (C) 2013 Pengutronix, Sascha Hauer <kernel@pengutronix.de> + * Copyright (C) 2016-2017 Pengutronix, Philipp Zabel <kernel@pengutronix.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <media/v4l2-async.h> +#include <media/v4l2-device.h> +#include <media/v4l2-subdev.h> + +struct video_mux { + struct v4l2_subdev subdev; + struct media_pad *pads; + struct v4l2_mbus_framefmt *format_mbus; + struct regmap_field *field; + struct mutex lock; + int active; +}; + +static inline struct video_mux *v4l2_subdev_to_video_mux(struct v4l2_subdev *sd) +{ + return container_of(sd, struct video_mux, subdev); +} + +static int video_mux_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); + int ret = 0; + + /* + * The mux state is determined by the enabled sink pad link. + * Enabling or disabling the source pad link has no effect. + */ + if (local->flags & MEDIA_PAD_FL_SOURCE) + return 0; + + dev_dbg(sd->dev, "link setup '%s':%d->'%s':%d[%d]", + remote->entity->name, remote->index, local->entity->name, + local->index, flags & MEDIA_LNK_FL_ENABLED); + + mutex_lock(&vmux->lock); + + if (flags & MEDIA_LNK_FL_ENABLED) { + if (vmux->active == local->index) + goto out; + + if (vmux->active >= 0) { + ret = -EBUSY; + goto out; + } + + dev_dbg(sd->dev, "setting %d active\n", local->index); + ret = regmap_field_write(vmux->field, local->index); + if (ret < 0) + goto out; + vmux->active = local->index; + } else { + if (vmux->active != local->index) + goto out; + + dev_dbg(sd->dev, "going inactive\n"); + vmux->active = -1; + } + +out: + mutex_unlock(&vmux->lock); + return ret; +} + +static const struct media_entity_operations video_mux_ops = { + .link_setup = video_mux_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +static int video_mux_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); + struct v4l2_subdev *upstream_sd; + struct media_pad *pad; + + if (vmux->active == -1) { + dev_err(sd->dev, "Can not start streaming on inactive mux\n"); + return -EINVAL; + } + + pad = media_entity_remote_pad(&sd->entity.pads[vmux->active]); + if (!pad) { + dev_err(sd->dev, "Failed to find remote source pad\n"); + return -ENOLINK; + } + + if (!is_media_entity_v4l2_subdev(pad->entity)) { + dev_err(sd->dev, "Upstream entity is not a v4l2 subdev\n"); + return -ENODEV; + } + + upstream_sd = media_entity_to_v4l2_subdev(pad->entity); + + return v4l2_subdev_call(upstream_sd, video, s_stream, enable); +} + +static const struct v4l2_subdev_video_ops video_mux_subdev_video_ops = { + .s_stream = video_mux_s_stream, +}; + +static struct v4l2_mbus_framefmt * +__video_mux_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, u32 which) +{ + struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); + + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(sd, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &vmux->format_mbus[pad]; + default: + return NULL; + } +} + +static int video_mux_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); + + mutex_lock(&vmux->lock); + + sdformat->format = *__video_mux_get_pad_format(sd, cfg, sdformat->pad, + sdformat->which); + + mutex_unlock(&vmux->lock); + + return 0; +} + +static int video_mux_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); + struct v4l2_mbus_framefmt *mbusformat; + struct media_pad *pad = &vmux->pads[sdformat->pad]; + + mbusformat = __video_mux_get_pad_format(sd, cfg, sdformat->pad, + sdformat->which); + if (!mbusformat) + return -EINVAL; + + mutex_lock(&vmux->lock); + + /* Source pad mirrors active sink pad, no limitations on sink pads */ + if ((pad->flags & MEDIA_PAD_FL_SOURCE) && vmux->active >= 0) + sdformat->format = vmux->format_mbus[vmux->active]; + + *mbusformat = sdformat->format; + + mutex_unlock(&vmux->lock); + + return 0; +} + +static const struct v4l2_subdev_pad_ops video_mux_pad_ops = { + .get_fmt = video_mux_get_format, + .set_fmt = video_mux_set_format, +}; + +static const struct v4l2_subdev_ops video_mux_subdev_ops = { + .pad = &video_mux_pad_ops, + .video = &video_mux_subdev_video_ops, +}; + +static int video_mux_probe_mmio_mux(struct video_mux *vmux) +{ + struct device *dev = vmux->subdev.dev; + struct of_phandle_args args; + struct reg_field field; + struct regmap *regmap; + u32 reg, mask; + int ret; + + ret = of_parse_phandle_with_args(dev->of_node, "mux-controls", + "#mux-control-cells", 0, &args); + if (ret) + return ret; + + if (!of_device_is_compatible(args.np, "mmio-mux")) + return -EINVAL; + + regmap = syscon_node_to_regmap(args.np->parent); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + ret = of_property_read_u32_index(args.np, "mux-reg-masks", + 2 * args.args[0], ®); + if (!ret) + ret = of_property_read_u32_index(args.np, "mux-reg-masks", + 2 * args.args[0] + 1, &mask); + if (ret < 0) + return ret; + + field.reg = reg; + field.msb = fls(mask) - 1; + field.lsb = ffs(mask) - 1; + + vmux->field = devm_regmap_field_alloc(dev, regmap, field); + if (IS_ERR(vmux->field)) + return PTR_ERR(vmux->field); + + return 0; +} + +static int video_mux_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct device_node *ep; + struct video_mux *vmux; + unsigned int num_pads = 0; + int ret; + int i; + + vmux = devm_kzalloc(dev, sizeof(*vmux), GFP_KERNEL); + if (!vmux) + return -ENOMEM; + + platform_set_drvdata(pdev, vmux); + + v4l2_subdev_init(&vmux->subdev, &video_mux_subdev_ops); + snprintf(vmux->subdev.name, sizeof(vmux->subdev.name), "%s", np->name); + vmux->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + vmux->subdev.dev = dev; + + /* + * The largest numbered port is the output port. It determines + * total number of pads. + */ + for_each_endpoint_of_node(np, ep) { + struct of_endpoint endpoint; + + of_graph_parse_endpoint(ep, &endpoint); + num_pads = max(num_pads, endpoint.port + 1); + } + + if (num_pads < 2) { + dev_err(dev, "Not enough ports %d\n", num_pads); + return -EINVAL; + } + + ret = video_mux_probe_mmio_mux(vmux); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get mux: %d\n", ret); + return ret; + } + + mutex_init(&vmux->lock); + vmux->active = -1; + vmux->pads = devm_kcalloc(dev, num_pads, sizeof(*vmux->pads), + GFP_KERNEL); + vmux->format_mbus = devm_kcalloc(dev, num_pads, + sizeof(*vmux->format_mbus), + GFP_KERNEL); + + for (i = 0; i < num_pads - 1; i++) + vmux->pads[i].flags = MEDIA_PAD_FL_SINK; + vmux->pads[num_pads - 1].flags = MEDIA_PAD_FL_SOURCE; + + vmux->subdev.entity.function = MEDIA_ENT_F_VID_MUX; + ret = media_entity_pads_init(&vmux->subdev.entity, num_pads, + vmux->pads); + if (ret < 0) + return ret; + + vmux->subdev.entity.ops = &video_mux_ops; + + return v4l2_async_register_subdev(&vmux->subdev); +} + +static int video_mux_remove(struct platform_device *pdev) +{ + struct video_mux *vmux = platform_get_drvdata(pdev); + struct v4l2_subdev *sd = &vmux->subdev; + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + + return 0; +} + +static const struct of_device_id video_mux_dt_ids[] = { + { .compatible = "video-mux", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, video_mux_dt_ids); + +static struct platform_driver video_mux_driver = { + .probe = video_mux_probe, + .remove = video_mux_remove, + .driver = { + .of_match_table = video_mux_dt_ids, + .name = "video-mux", + }, +}; + +module_platform_driver(video_mux_driver); + +MODULE_DESCRIPTION("video stream multiplexer"); +MODULE_AUTHOR("Sascha Hauer, Pengutronix"); +MODULE_AUTHOR("Philipp Zabel, Pengutronix"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/vimc/Kconfig b/drivers/media/platform/vimc/Kconfig index a18f6352c422..71c9fe7d3370 100644 --- a/drivers/media/platform/vimc/Kconfig +++ b/drivers/media/platform/vimc/Kconfig @@ -2,6 +2,7 @@ config VIDEO_VIMC tristate "Virtual Media Controller Driver (VIMC)" depends on VIDEO_DEV && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API select VIDEOBUF2_VMALLOC + select VIDEO_V4L2_TPG default n ---help--- Skeleton driver for Virtual Media Controller diff --git a/drivers/media/platform/vimc/Makefile b/drivers/media/platform/vimc/Makefile index c45195e5e05c..68c5d9804c11 100644 --- a/drivers/media/platform/vimc/Makefile +++ b/drivers/media/platform/vimc/Makefile @@ -1,3 +1,9 @@ -vimc-objs := vimc-core.o vimc-capture.o vimc-sensor.o +vimc-objs := vimc-core.o +vimc_capture-objs := vimc-capture.o +vimc_common-objs := vimc-common.o +vimc_debayer-objs := vimc-debayer.o +vimc_scaler-objs := vimc-scaler.o +vimc_sensor-objs := vimc-sensor.o -obj-$(CONFIG_VIDEO_VIMC) += vimc.o +obj-$(CONFIG_VIDEO_VIMC) += vimc.o vimc_capture.o vimc_common.o vimc-debayer.o \ + vimc_scaler.o vimc_sensor.o diff --git a/drivers/media/platform/vimc/vimc-capture.c b/drivers/media/platform/vimc/vimc-capture.c index 9adb06d7e13d..14cb32e21130 100644 --- a/drivers/media/platform/vimc/vimc-capture.c +++ b/drivers/media/platform/vimc/vimc-capture.c @@ -15,15 +15,21 @@ * */ +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> #include <media/v4l2-ioctl.h> #include <media/videobuf2-core.h> #include <media/videobuf2-vmalloc.h> -#include "vimc-capture.h" +#include "vimc-common.h" + +#define VIMC_CAP_DRV_NAME "vimc-capture" struct vimc_cap_device { struct vimc_ent_device ved; struct video_device vdev; + struct device *dev; struct v4l2_pix_format format; struct vb2_queue queue; struct list_head buf_list; @@ -40,6 +46,14 @@ struct vimc_cap_device { struct media_pipeline pipe; }; +static const struct v4l2_pix_format fmt_default = { + .width = 640, + .height = 480, + .pixelformat = V4L2_PIX_FMT_RGB24, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_DEFAULT, +}; + struct vimc_cap_buffer { /* * struct vb2_v4l2_buffer must be the first element @@ -64,7 +78,16 @@ static int vimc_cap_querycap(struct file *file, void *priv, return 0; } -static int vimc_cap_fmt_vid_cap(struct file *file, void *priv, +static void vimc_cap_get_format(struct vimc_ent_device *ved, + struct v4l2_pix_format *fmt) +{ + struct vimc_cap_device *vcap = container_of(ved, struct vimc_cap_device, + ved); + + *fmt = vcap->format; +} + +static int vimc_cap_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct vimc_cap_device *vcap = video_drvdata(file); @@ -74,16 +97,98 @@ static int vimc_cap_fmt_vid_cap(struct file *file, void *priv, return 0; } +static int vimc_cap_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_pix_format *format = &f->fmt.pix; + const struct vimc_pix_map *vpix; + + format->width = clamp_t(u32, format->width, VIMC_FRAME_MIN_WIDTH, + VIMC_FRAME_MAX_WIDTH) & ~1; + format->height = clamp_t(u32, format->height, VIMC_FRAME_MIN_HEIGHT, + VIMC_FRAME_MAX_HEIGHT) & ~1; + + /* Don't accept a pixelformat that is not on the table */ + vpix = vimc_pix_map_by_pixelformat(format->pixelformat); + if (!vpix) { + format->pixelformat = fmt_default.pixelformat; + vpix = vimc_pix_map_by_pixelformat(format->pixelformat); + } + /* TODO: Add support for custom bytesperline values */ + format->bytesperline = format->width * vpix->bpp; + format->sizeimage = format->bytesperline * format->height; + + if (format->field == V4L2_FIELD_ANY) + format->field = fmt_default.field; + + vimc_colorimetry_clamp(format); + + return 0; +} + +static int vimc_cap_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct vimc_cap_device *vcap = video_drvdata(file); + + /* Do not change the format while stream is on */ + if (vb2_is_busy(&vcap->queue)) + return -EBUSY; + + vimc_cap_try_fmt_vid_cap(file, priv, f); + + dev_dbg(vcap->dev, "%s: format update: " + "old:%dx%d (0x%x, %d, %d, %d, %d) " + "new:%dx%d (0x%x, %d, %d, %d, %d)\n", vcap->vdev.name, + /* old */ + vcap->format.width, vcap->format.height, + vcap->format.pixelformat, vcap->format.colorspace, + vcap->format.quantization, vcap->format.xfer_func, + vcap->format.ycbcr_enc, + /* new */ + f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.pixelformat, f->fmt.pix.colorspace, + f->fmt.pix.quantization, f->fmt.pix.xfer_func, + f->fmt.pix.ycbcr_enc); + + vcap->format = f->fmt.pix; + + return 0; +} + static int vimc_cap_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) { - struct vimc_cap_device *vcap = video_drvdata(file); + const struct vimc_pix_map *vpix = vimc_pix_map_by_index(f->index); + + if (!vpix) + return -EINVAL; + + f->pixelformat = vpix->pixelformat; + + return 0; +} + +static int vimc_cap_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + const struct vimc_pix_map *vpix; + + if (fsize->index) + return -EINVAL; - if (f->index > 0) + /* Only accept code in the pix map table */ + vpix = vimc_pix_map_by_code(fsize->pixel_format); + if (!vpix) return -EINVAL; - /* We only support one format for now */ - f->pixelformat = vcap->format.pixelformat; + fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + fsize->stepwise.min_width = VIMC_FRAME_MIN_WIDTH; + fsize->stepwise.max_width = VIMC_FRAME_MAX_WIDTH; + fsize->stepwise.min_height = VIMC_FRAME_MIN_HEIGHT; + fsize->stepwise.max_height = VIMC_FRAME_MAX_HEIGHT; + fsize->stepwise.step_width = 2; + fsize->stepwise.step_height = 2; return 0; } @@ -101,10 +206,11 @@ static const struct v4l2_file_operations vimc_cap_fops = { static const struct v4l2_ioctl_ops vimc_cap_ioctl_ops = { .vidioc_querycap = vimc_cap_querycap, - .vidioc_g_fmt_vid_cap = vimc_cap_fmt_vid_cap, - .vidioc_s_fmt_vid_cap = vimc_cap_fmt_vid_cap, - .vidioc_try_fmt_vid_cap = vimc_cap_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vimc_cap_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vimc_cap_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vimc_cap_try_fmt_vid_cap, .vidioc_enum_fmt_vid_cap = vimc_cap_enum_fmt_vid_cap, + .vidioc_enum_framesizes = vimc_cap_enum_framesizes, .vidioc_reqbufs = vb2_ioctl_reqbufs, .vidioc_create_bufs = vb2_ioctl_create_bufs, @@ -132,31 +238,6 @@ static void vimc_cap_return_all_buffers(struct vimc_cap_device *vcap, spin_unlock(&vcap->qlock); } -static int vimc_cap_pipeline_s_stream(struct vimc_cap_device *vcap, int enable) -{ - struct v4l2_subdev *sd; - struct media_pad *pad; - int ret; - - /* Start the stream in the subdevice direct connected */ - pad = media_entity_remote_pad(&vcap->vdev.entity.pads[0]); - - /* - * if it is a raw node from vimc-core, there is nothing to activate - * TODO: remove this when there are no more raw nodes in the - * core and return error instead - */ - if (pad->entity->obj_type == MEDIA_ENTITY_TYPE_BASE) - return 0; - - sd = media_entity_to_v4l2_subdev(pad->entity); - ret = v4l2_subdev_call(sd, video, s_stream, enable); - if (ret && ret != -ENOIOCTLCMD) - return ret; - - return 0; -} - static int vimc_cap_start_streaming(struct vb2_queue *vq, unsigned int count) { struct vimc_cap_device *vcap = vb2_get_drv_priv(vq); @@ -173,7 +254,7 @@ static int vimc_cap_start_streaming(struct vb2_queue *vq, unsigned int count) } /* Enable streaming from the pipe */ - ret = vimc_cap_pipeline_s_stream(vcap, 1); + ret = vimc_pipeline_s_stream(&vcap->vdev.entity, 1); if (ret) { media_pipeline_stop(entity); vimc_cap_return_all_buffers(vcap, VB2_BUF_STATE_QUEUED); @@ -192,7 +273,7 @@ static void vimc_cap_stop_streaming(struct vb2_queue *vq) struct vimc_cap_device *vcap = vb2_get_drv_priv(vq); /* Disable streaming from the pipe */ - vimc_cap_pipeline_s_stream(vcap, 0); + vimc_pipeline_s_stream(&vcap->vdev.entity, 0); /* Stop the media pipeline */ media_pipeline_stop(&vcap->vdev.entity); @@ -234,8 +315,7 @@ static int vimc_cap_buffer_prepare(struct vb2_buffer *vb) unsigned long size = vcap->format.sizeimage; if (vb2_plane_size(vb, 0) < size) { - dev_err(vcap->vdev.v4l2_dev->dev, - "%s: buffer too small (%lu < %lu)\n", + dev_err(vcap->dev, "%s: buffer too small (%lu < %lu)\n", vcap->vdev.name, vb2_plane_size(vb, 0), size); return -EINVAL; } @@ -256,78 +336,14 @@ static const struct vb2_ops vimc_cap_qops = { .wait_finish = vb2_ops_wait_finish, }; -/* - * NOTE: this function is a copy of v4l2_subdev_link_validate_get_format - * maybe the v4l2 function should be public - */ -static int vimc_cap_v4l2_subdev_link_validate_get_format(struct media_pad *pad, - struct v4l2_subdev_format *fmt) -{ - struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(pad->entity); - - fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; - fmt->pad = pad->index; - - return v4l2_subdev_call(sd, pad, get_fmt, NULL, fmt); -} - -static int vimc_cap_link_validate(struct media_link *link) -{ - struct v4l2_subdev_format source_fmt; - const struct vimc_pix_map *vpix; - struct vimc_cap_device *vcap = container_of(link->sink->entity, - struct vimc_cap_device, - vdev.entity); - struct v4l2_pix_format *sink_fmt = &vcap->format; - int ret; - - /* - * if it is a raw node from vimc-core, ignore the link for now - * TODO: remove this when there are no more raw nodes in the - * core and return error instead - */ - if (link->source->entity->obj_type == MEDIA_ENTITY_TYPE_BASE) - return 0; - - /* Get the the format of the subdev */ - ret = vimc_cap_v4l2_subdev_link_validate_get_format(link->source, - &source_fmt); - if (ret) - return ret; - - dev_dbg(vcap->vdev.v4l2_dev->dev, - "%s: link validate formats src:%dx%d %d sink:%dx%d %d\n", - vcap->vdev.name, - source_fmt.format.width, source_fmt.format.height, - source_fmt.format.code, - sink_fmt->width, sink_fmt->height, - sink_fmt->pixelformat); - - /* The width, height and code must match. */ - vpix = vimc_pix_map_by_pixelformat(sink_fmt->pixelformat); - if (source_fmt.format.width != sink_fmt->width - || source_fmt.format.height != sink_fmt->height - || vpix->code != source_fmt.format.code) - return -EPIPE; - - /* - * The field order must match, or the sink field order must be NONE - * to support interlaced hardware connected to bridges that support - * progressive formats only. - */ - if (source_fmt.format.field != sink_fmt->field && - sink_fmt->field != V4L2_FIELD_NONE) - return -EPIPE; - - return 0; -} - static const struct media_entity_operations vimc_cap_mops = { - .link_validate = vimc_cap_link_validate, + .link_validate = vimc_link_validate, }; -static void vimc_cap_destroy(struct vimc_ent_device *ved) +static void vimc_cap_comp_unbind(struct device *comp, struct device *master, + void *master_data) { + struct vimc_ent_device *ved = dev_get_drvdata(comp); struct vimc_cap_device *vcap = container_of(ved, struct vimc_cap_device, ved); @@ -376,42 +392,35 @@ static void vimc_cap_process_frame(struct vimc_ent_device *ved, vb2_buffer_done(&vimc_buf->vb2.vb2_buf, VB2_BUF_STATE_DONE); } -struct vimc_ent_device *vimc_cap_create(struct v4l2_device *v4l2_dev, - const char *const name, - u16 num_pads, - const unsigned long *pads_flag) +static int vimc_cap_comp_bind(struct device *comp, struct device *master, + void *master_data) { + struct v4l2_device *v4l2_dev = master_data; + struct vimc_platform_data *pdata = comp->platform_data; const struct vimc_pix_map *vpix; struct vimc_cap_device *vcap; struct video_device *vdev; struct vb2_queue *q; int ret; - /* - * Check entity configuration params - * NOTE: we only support a single sink pad - */ - if (!name || num_pads != 1 || !pads_flag || - !(pads_flag[0] & MEDIA_PAD_FL_SINK)) - return ERR_PTR(-EINVAL); - /* Allocate the vimc_cap_device struct */ vcap = kzalloc(sizeof(*vcap), GFP_KERNEL); if (!vcap) - return ERR_PTR(-ENOMEM); + return -ENOMEM; /* Allocate the pads */ - vcap->ved.pads = vimc_pads_init(num_pads, pads_flag); + vcap->ved.pads = + vimc_pads_init(1, (const unsigned long[1]) {MEDIA_PAD_FL_SINK}); if (IS_ERR(vcap->ved.pads)) { ret = PTR_ERR(vcap->ved.pads); goto err_free_vcap; } /* Initialize the media entity */ - vcap->vdev.entity.name = name; + vcap->vdev.entity.name = pdata->entity_name; vcap->vdev.entity.function = MEDIA_ENT_F_IO_V4L; ret = media_entity_pads_init(&vcap->vdev.entity, - num_pads, vcap->ved.pads); + 1, vcap->ved.pads); if (ret) goto err_clean_pads; @@ -432,9 +441,8 @@ struct vimc_ent_device *vimc_cap_create(struct v4l2_device *v4l2_dev, ret = vb2_queue_init(q); if (ret) { - dev_err(vcap->vdev.v4l2_dev->dev, - "%s: vb2 queue init failed (err=%d)\n", - vcap->vdev.name, ret); + dev_err(comp, "%s: vb2 queue init failed (err=%d)\n", + pdata->entity_name, ret); goto err_clean_m_ent; } @@ -442,23 +450,19 @@ struct vimc_ent_device *vimc_cap_create(struct v4l2_device *v4l2_dev, INIT_LIST_HEAD(&vcap->buf_list); spin_lock_init(&vcap->qlock); - /* Set the frame format (this is hardcoded for now) */ - vcap->format.width = 640; - vcap->format.height = 480; - vcap->format.pixelformat = V4L2_PIX_FMT_RGB24; - vcap->format.field = V4L2_FIELD_NONE; - vcap->format.colorspace = V4L2_COLORSPACE_SRGB; - + /* Set default frame format */ + vcap->format = fmt_default; vpix = vimc_pix_map_by_pixelformat(vcap->format.pixelformat); - vcap->format.bytesperline = vcap->format.width * vpix->bpp; vcap->format.sizeimage = vcap->format.bytesperline * vcap->format.height; /* Fill the vimc_ent_device struct */ - vcap->ved.destroy = vimc_cap_destroy; vcap->ved.ent = &vcap->vdev.entity; vcap->ved.process_frame = vimc_cap_process_frame; + vcap->ved.vdev_get_format = vimc_cap_get_format; + dev_set_drvdata(comp, &vcap->ved); + vcap->dev = comp; /* Initialize the video_device struct */ vdev = &vcap->vdev; @@ -471,19 +475,18 @@ struct vimc_ent_device *vimc_cap_create(struct v4l2_device *v4l2_dev, vdev->queue = q; vdev->v4l2_dev = v4l2_dev; vdev->vfl_dir = VFL_DIR_RX; - strlcpy(vdev->name, name, sizeof(vdev->name)); + strlcpy(vdev->name, pdata->entity_name, sizeof(vdev->name)); video_set_drvdata(vdev, &vcap->ved); /* Register the video_device with the v4l2 and the media framework */ ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); if (ret) { - dev_err(vcap->vdev.v4l2_dev->dev, - "%s: video register failed (err=%d)\n", + dev_err(comp, "%s: video register failed (err=%d)\n", vcap->vdev.name, ret); goto err_release_queue; } - return &vcap->ved; + return 0; err_release_queue: vb2_queue_release(q); @@ -494,5 +497,45 @@ err_clean_pads: err_free_vcap: kfree(vcap); - return ERR_PTR(ret); + return ret; +} + +static const struct component_ops vimc_cap_comp_ops = { + .bind = vimc_cap_comp_bind, + .unbind = vimc_cap_comp_unbind, +}; + +static int vimc_cap_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &vimc_cap_comp_ops); } + +static int vimc_cap_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vimc_cap_comp_ops); + + return 0; +} + +static struct platform_driver vimc_cap_pdrv = { + .probe = vimc_cap_probe, + .remove = vimc_cap_remove, + .driver = { + .name = VIMC_CAP_DRV_NAME, + }, +}; + +static const struct platform_device_id vimc_cap_driver_ids[] = { + { + .name = VIMC_CAP_DRV_NAME, + }, + { } +}; + +module_platform_driver(vimc_cap_pdrv); + +MODULE_DEVICE_TABLE(platform, vimc_cap_driver_ids); + +MODULE_DESCRIPTION("Virtual Media Controller Driver (VIMC) Capture"); +MODULE_AUTHOR("Helen Mae Koike Fornazier <helen.fornazier@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/vimc/vimc-capture.h b/drivers/media/platform/vimc/vimc-capture.h deleted file mode 100644 index 581a813abdf1..000000000000 --- a/drivers/media/platform/vimc/vimc-capture.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * vimc-capture.h Virtual Media Controller Driver - * - * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef _VIMC_CAPTURE_H_ -#define _VIMC_CAPTURE_H_ - -#include "vimc-core.h" - -struct vimc_ent_device *vimc_cap_create(struct v4l2_device *v4l2_dev, - const char *const name, - u16 num_pads, - const unsigned long *pads_flag); - -#endif diff --git a/drivers/media/platform/vimc/vimc-common.c b/drivers/media/platform/vimc/vimc-common.c new file mode 100644 index 000000000000..9d63c84a9876 --- /dev/null +++ b/drivers/media/platform/vimc/vimc-common.c @@ -0,0 +1,473 @@ +/* + * vimc-common.c Virtual Media Controller Driver + * + * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> +#include <linux/module.h> + +#include "vimc-common.h" + +/* + * NOTE: non-bayer formats need to come first (necessary for enum_mbus_code + * in the scaler) + */ +static const struct vimc_pix_map vimc_pix_map_list[] = { + /* TODO: add all missing formats */ + + /* RGB formats */ + { + .code = MEDIA_BUS_FMT_BGR888_1X24, + .pixelformat = V4L2_PIX_FMT_BGR24, + .bpp = 3, + .bayer = false, + }, + { + .code = MEDIA_BUS_FMT_RGB888_1X24, + .pixelformat = V4L2_PIX_FMT_RGB24, + .bpp = 3, + .bayer = false, + }, + { + .code = MEDIA_BUS_FMT_ARGB8888_1X32, + .pixelformat = V4L2_PIX_FMT_ARGB32, + .bpp = 4, + .bayer = false, + }, + + /* Bayer formats */ + { + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .pixelformat = V4L2_PIX_FMT_SGBRG8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .pixelformat = V4L2_PIX_FMT_SGRBG8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .pixelformat = V4L2_PIX_FMT_SRGGB8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .pixelformat = V4L2_PIX_FMT_SBGGR10, + .bpp = 2, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .pixelformat = V4L2_PIX_FMT_SGBRG10, + .bpp = 2, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .pixelformat = V4L2_PIX_FMT_SGRBG10, + .bpp = 2, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .pixelformat = V4L2_PIX_FMT_SRGGB10, + .bpp = 2, + .bayer = true, + }, + + /* 10bit raw bayer a-law compressed to 8 bits */ + { + .code = MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8, + .pixelformat = V4L2_PIX_FMT_SBGGR10ALAW8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8, + .pixelformat = V4L2_PIX_FMT_SGBRG10ALAW8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8, + .pixelformat = V4L2_PIX_FMT_SGRBG10ALAW8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8, + .pixelformat = V4L2_PIX_FMT_SRGGB10ALAW8, + .bpp = 1, + .bayer = true, + }, + + /* 10bit raw bayer DPCM compressed to 8 bits */ + { + .code = MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8, + .pixelformat = V4L2_PIX_FMT_SBGGR10DPCM8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8, + .pixelformat = V4L2_PIX_FMT_SGBRG10DPCM8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, + .pixelformat = V4L2_PIX_FMT_SGRBG10DPCM8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8, + .pixelformat = V4L2_PIX_FMT_SRGGB10DPCM8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .pixelformat = V4L2_PIX_FMT_SBGGR12, + .bpp = 2, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .pixelformat = V4L2_PIX_FMT_SGBRG12, + .bpp = 2, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .pixelformat = V4L2_PIX_FMT_SGRBG12, + .bpp = 2, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .pixelformat = V4L2_PIX_FMT_SRGGB12, + .bpp = 2, + .bayer = true, + }, +}; + +const struct vimc_pix_map *vimc_pix_map_by_index(unsigned int i) +{ + if (i >= ARRAY_SIZE(vimc_pix_map_list)) + return NULL; + + return &vimc_pix_map_list[i]; +} +EXPORT_SYMBOL_GPL(vimc_pix_map_by_index); + +const struct vimc_pix_map *vimc_pix_map_by_code(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vimc_pix_map_list); i++) { + if (vimc_pix_map_list[i].code == code) + return &vimc_pix_map_list[i]; + } + return NULL; +} +EXPORT_SYMBOL_GPL(vimc_pix_map_by_code); + +const struct vimc_pix_map *vimc_pix_map_by_pixelformat(u32 pixelformat) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vimc_pix_map_list); i++) { + if (vimc_pix_map_list[i].pixelformat == pixelformat) + return &vimc_pix_map_list[i]; + } + return NULL; +} +EXPORT_SYMBOL_GPL(vimc_pix_map_by_pixelformat); + +int vimc_propagate_frame(struct media_pad *src, const void *frame) +{ + struct media_link *link; + + if (!(src->flags & MEDIA_PAD_FL_SOURCE)) + return -EINVAL; + + /* Send this frame to all sink pads that are direct linked */ + list_for_each_entry(link, &src->entity->links, list) { + if (link->source == src && + (link->flags & MEDIA_LNK_FL_ENABLED)) { + struct vimc_ent_device *ved = NULL; + struct media_entity *entity = link->sink->entity; + + if (is_media_entity_v4l2_subdev(entity)) { + struct v4l2_subdev *sd = + container_of(entity, struct v4l2_subdev, + entity); + ved = v4l2_get_subdevdata(sd); + } else if (is_media_entity_v4l2_video_device(entity)) { + struct video_device *vdev = + container_of(entity, + struct video_device, + entity); + ved = video_get_drvdata(vdev); + } + if (ved && ved->process_frame) + ved->process_frame(ved, link->sink, frame); + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(vimc_propagate_frame); + +/* Helper function to allocate and initialize pads */ +struct media_pad *vimc_pads_init(u16 num_pads, const unsigned long *pads_flag) +{ + struct media_pad *pads; + unsigned int i; + + /* Allocate memory for the pads */ + pads = kcalloc(num_pads, sizeof(*pads), GFP_KERNEL); + if (!pads) + return ERR_PTR(-ENOMEM); + + /* Initialize the pads */ + for (i = 0; i < num_pads; i++) { + pads[i].index = i; + pads[i].flags = pads_flag[i]; + } + + return pads; +} +EXPORT_SYMBOL_GPL(vimc_pads_init); + +int vimc_pipeline_s_stream(struct media_entity *ent, int enable) +{ + struct v4l2_subdev *sd; + struct media_pad *pad; + unsigned int i; + int ret; + + for (i = 0; i < ent->num_pads; i++) { + if (ent->pads[i].flags & MEDIA_PAD_FL_SOURCE) + continue; + + /* Start the stream in the subdevice direct connected */ + pad = media_entity_remote_pad(&ent->pads[i]); + + if (!is_media_entity_v4l2_subdev(pad->entity)) + return -EINVAL; + + sd = media_entity_to_v4l2_subdev(pad->entity); + ret = v4l2_subdev_call(sd, video, s_stream, enable); + if (ret && ret != -ENOIOCTLCMD) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(vimc_pipeline_s_stream); + +static int vimc_get_mbus_format(struct media_pad *pad, + struct v4l2_subdev_format *fmt) +{ + if (is_media_entity_v4l2_subdev(pad->entity)) { + struct v4l2_subdev *sd = + media_entity_to_v4l2_subdev(pad->entity); + int ret; + + fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt->pad = pad->index; + + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, fmt); + if (ret) + return ret; + + } else if (is_media_entity_v4l2_video_device(pad->entity)) { + struct video_device *vdev = container_of(pad->entity, + struct video_device, + entity); + struct vimc_ent_device *ved = video_get_drvdata(vdev); + const struct vimc_pix_map *vpix; + struct v4l2_pix_format vdev_fmt; + + if (!ved->vdev_get_format) + return -ENOIOCTLCMD; + + ved->vdev_get_format(ved, &vdev_fmt); + vpix = vimc_pix_map_by_pixelformat(vdev_fmt.pixelformat); + v4l2_fill_mbus_format(&fmt->format, &vdev_fmt, vpix->code); + } else { + return -EINVAL; + } + + return 0; +} + +int vimc_link_validate(struct media_link *link) +{ + struct v4l2_subdev_format source_fmt, sink_fmt; + int ret; + + ret = vimc_get_mbus_format(link->source, &source_fmt); + if (ret) + return ret; + + ret = vimc_get_mbus_format(link->sink, &sink_fmt); + if (ret) + return ret; + + pr_info("vimc link validate: " + "%s:src:%dx%d (0x%x, %d, %d, %d, %d) " + "%s:snk:%dx%d (0x%x, %d, %d, %d, %d)\n", + /* src */ + link->source->entity->name, + source_fmt.format.width, source_fmt.format.height, + source_fmt.format.code, source_fmt.format.colorspace, + source_fmt.format.quantization, source_fmt.format.xfer_func, + source_fmt.format.ycbcr_enc, + /* sink */ + link->sink->entity->name, + sink_fmt.format.width, sink_fmt.format.height, + sink_fmt.format.code, sink_fmt.format.colorspace, + sink_fmt.format.quantization, sink_fmt.format.xfer_func, + sink_fmt.format.ycbcr_enc); + + /* The width, height and code must match. */ + if (source_fmt.format.width != sink_fmt.format.width + || source_fmt.format.height != sink_fmt.format.height + || source_fmt.format.code != sink_fmt.format.code) + return -EPIPE; + + /* + * The field order must match, or the sink field order must be NONE + * to support interlaced hardware connected to bridges that support + * progressive formats only. + */ + if (source_fmt.format.field != sink_fmt.format.field && + sink_fmt.format.field != V4L2_FIELD_NONE) + return -EPIPE; + + /* + * If colorspace is DEFAULT, then assume all the colorimetry is also + * DEFAULT, return 0 to skip comparing the other colorimetry parameters + */ + if (source_fmt.format.colorspace == V4L2_COLORSPACE_DEFAULT + || sink_fmt.format.colorspace == V4L2_COLORSPACE_DEFAULT) + return 0; + + /* Colorspace must match. */ + if (source_fmt.format.colorspace != sink_fmt.format.colorspace) + return -EPIPE; + + /* Colorimetry must match if they are not set to DEFAULT */ + if (source_fmt.format.ycbcr_enc != V4L2_YCBCR_ENC_DEFAULT + && sink_fmt.format.ycbcr_enc != V4L2_YCBCR_ENC_DEFAULT + && source_fmt.format.ycbcr_enc != sink_fmt.format.ycbcr_enc) + return -EPIPE; + + if (source_fmt.format.quantization != V4L2_QUANTIZATION_DEFAULT + && sink_fmt.format.quantization != V4L2_QUANTIZATION_DEFAULT + && source_fmt.format.quantization != sink_fmt.format.quantization) + return -EPIPE; + + if (source_fmt.format.xfer_func != V4L2_XFER_FUNC_DEFAULT + && sink_fmt.format.xfer_func != V4L2_XFER_FUNC_DEFAULT + && source_fmt.format.xfer_func != sink_fmt.format.xfer_func) + return -EPIPE; + + return 0; +} +EXPORT_SYMBOL_GPL(vimc_link_validate); + +static const struct media_entity_operations vimc_ent_sd_mops = { + .link_validate = vimc_link_validate, +}; + +int vimc_ent_sd_register(struct vimc_ent_device *ved, + struct v4l2_subdev *sd, + struct v4l2_device *v4l2_dev, + const char *const name, + u32 function, + u16 num_pads, + const unsigned long *pads_flag, + const struct v4l2_subdev_ops *sd_ops) +{ + int ret; + + /* Allocate the pads */ + ved->pads = vimc_pads_init(num_pads, pads_flag); + if (IS_ERR(ved->pads)) + return PTR_ERR(ved->pads); + + /* Fill the vimc_ent_device struct */ + ved->ent = &sd->entity; + + /* Initialize the subdev */ + v4l2_subdev_init(sd, sd_ops); + sd->entity.function = function; + sd->entity.ops = &vimc_ent_sd_mops; + sd->owner = THIS_MODULE; + strlcpy(sd->name, name, sizeof(sd->name)); + v4l2_set_subdevdata(sd, ved); + + /* Expose this subdev to user space */ + sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + /* Initialize the media entity */ + ret = media_entity_pads_init(&sd->entity, num_pads, ved->pads); + if (ret) + goto err_clean_pads; + + /* Register the subdev with the v4l2 and the media framework */ + ret = v4l2_device_register_subdev(v4l2_dev, sd); + if (ret) { + dev_err(v4l2_dev->dev, + "%s: subdev register failed (err=%d)\n", + name, ret); + goto err_clean_m_ent; + } + + return 0; + +err_clean_m_ent: + media_entity_cleanup(&sd->entity); +err_clean_pads: + vimc_pads_cleanup(ved->pads); + return ret; +} +EXPORT_SYMBOL_GPL(vimc_ent_sd_register); + +void vimc_ent_sd_unregister(struct vimc_ent_device *ved, struct v4l2_subdev *sd) +{ + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(ved->ent); + vimc_pads_cleanup(ved->pads); +} +EXPORT_SYMBOL_GPL(vimc_ent_sd_unregister); + +MODULE_DESCRIPTION("Virtual Media Controller Driver (VIMC) Common"); +MODULE_AUTHOR("Helen Koike <helen.fornazier@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/vimc/vimc-common.h b/drivers/media/platform/vimc/vimc-common.h new file mode 100644 index 000000000000..dca528a316e7 --- /dev/null +++ b/drivers/media/platform/vimc/vimc-common.h @@ -0,0 +1,229 @@ +/* + * vimc-common.h Virtual Media Controller Driver + * + * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VIMC_COMMON_H_ +#define _VIMC_COMMON_H_ + +#include <linux/slab.h> +#include <media/media-device.h> +#include <media/v4l2-device.h> + +#define VIMC_FRAME_MAX_WIDTH 4096 +#define VIMC_FRAME_MAX_HEIGHT 2160 +#define VIMC_FRAME_MIN_WIDTH 16 +#define VIMC_FRAME_MIN_HEIGHT 16 + +#define VIMC_FRAME_INDEX(lin, col, width, bpp) ((lin * width + col) * bpp) + +/** + * struct vimc_colorimetry_clamp - Adjust colorimetry parameters + * + * @fmt: the pointer to struct v4l2_pix_format or + * struct v4l2_mbus_framefmt + * + * Entities must check if colorimetry given by the userspace is valid, if not + * then set them as DEFAULT + */ +#define vimc_colorimetry_clamp(fmt) \ +do { \ + if ((fmt)->colorspace == V4L2_COLORSPACE_DEFAULT \ + || (fmt)->colorspace > V4L2_COLORSPACE_DCI_P3) { \ + (fmt)->colorspace = V4L2_COLORSPACE_DEFAULT; \ + (fmt)->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; \ + (fmt)->quantization = V4L2_QUANTIZATION_DEFAULT; \ + (fmt)->xfer_func = V4L2_XFER_FUNC_DEFAULT; \ + } \ + if ((fmt)->ycbcr_enc > V4L2_YCBCR_ENC_SMPTE240M) \ + (fmt)->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; \ + if ((fmt)->quantization > V4L2_QUANTIZATION_LIM_RANGE) \ + (fmt)->quantization = V4L2_QUANTIZATION_DEFAULT; \ + if ((fmt)->xfer_func > V4L2_XFER_FUNC_SMPTE2084) \ + (fmt)->xfer_func = V4L2_XFER_FUNC_DEFAULT; \ +} while (0) + +/** + * struct vimc_platform_data - platform data to components + * + * @entity_name: The name of the entity to be created + * + * Board setup code will often provide additional information using the device's + * platform_data field to hold additional information. + * When injecting a new platform_device in the component system the core needs + * to provide to the corresponding submodules the name of the entity that should + * be used when registering the subdevice in the Media Controller system. + */ +struct vimc_platform_data { + char entity_name[32]; +}; + +/** + * struct vimc_pix_map - maps media bus code with v4l2 pixel format + * + * @code: media bus format code defined by MEDIA_BUS_FMT_* macros + * @bbp: number of bytes each pixel occupies + * @pixelformat: pixel format devined by V4L2_PIX_FMT_* macros + * + * Struct which matches the MEDIA_BUS_FMT_* codes with the corresponding + * V4L2_PIX_FMT_* fourcc pixelformat and its bytes per pixel (bpp) + */ +struct vimc_pix_map { + unsigned int code; + unsigned int bpp; + u32 pixelformat; + bool bayer; +}; + +/** + * struct vimc_ent_device - core struct that represents a node in the topology + * + * @ent: the pointer to struct media_entity for the node + * @pads: the list of pads of the node + * @process_frame: callback send a frame to that node + * @vdev_get_format: callback that returns the current format a pad, used + * only when is_media_entity_v4l2_video_device(ent) returns + * true + * + * Each node of the topology must create a vimc_ent_device struct. Depending on + * the node it will be of an instance of v4l2_subdev or video_device struct + * where both contains a struct media_entity. + * Those structures should embedded the vimc_ent_device struct through + * v4l2_set_subdevdata() and video_set_drvdata() respectivaly, allowing the + * vimc_ent_device struct to be retrieved from the corresponding struct + * media_entity + */ +struct vimc_ent_device { + struct media_entity *ent; + struct media_pad *pads; + void (*process_frame)(struct vimc_ent_device *ved, + struct media_pad *sink, const void *frame); + void (*vdev_get_format)(struct vimc_ent_device *ved, + struct v4l2_pix_format *fmt); +}; + +/** + * vimc_propagate_frame - propagate a frame through the topology + * + * @src: the source pad where the frame is being originated + * @frame: the frame to be propagated + * + * This function will call the process_frame callback from the vimc_ent_device + * struct of the nodes directly connected to the @src pad + */ +int vimc_propagate_frame(struct media_pad *src, const void *frame); + +/** + * vimc_pads_init - initialize pads + * + * @num_pads: number of pads to initialize + * @pads_flags: flags to use in each pad + * + * Helper functions to allocate/initialize pads + */ +struct media_pad *vimc_pads_init(u16 num_pads, + const unsigned long *pads_flag); + +/** + * vimc_pads_cleanup - free pads + * + * @pads: pointer to the pads + * + * Helper function to free the pads initialized with vimc_pads_init + */ +static inline void vimc_pads_cleanup(struct media_pad *pads) +{ + kfree(pads); +} + +/** + * vimc_pipeline_s_stream - start stream through the pipeline + * + * @ent: the pointer to struct media_entity for the node + * @enable: 1 to start the stream and 0 to stop + * + * Helper function to call the s_stream of the subdevices connected + * in all the sink pads of the entity + */ +int vimc_pipeline_s_stream(struct media_entity *ent, int enable); + +/** + * vimc_pix_map_by_index - get vimc_pix_map struct by its index + * + * @i: index of the vimc_pix_map struct in vimc_pix_map_list + */ +const struct vimc_pix_map *vimc_pix_map_by_index(unsigned int i); + +/** + * vimc_pix_map_by_code - get vimc_pix_map struct by media bus code + * + * @code: media bus format code defined by MEDIA_BUS_FMT_* macros + */ +const struct vimc_pix_map *vimc_pix_map_by_code(u32 code); + +/** + * vimc_pix_map_by_pixelformat - get vimc_pix_map struct by v4l2 pixel format + * + * @pixelformat: pixel format devined by V4L2_PIX_FMT_* macros + */ +const struct vimc_pix_map *vimc_pix_map_by_pixelformat(u32 pixelformat); + +/** + * vimc_ent_sd_register - initialize and register a subdev node + * + * @ved: the vimc_ent_device struct to be initialize + * @sd: the v4l2_subdev struct to be initialize and registered + * @v4l2_dev: the v4l2 device to register the v4l2_subdev + * @name: name of the sub-device. Please notice that the name must be + * unique. + * @function: media entity function defined by MEDIA_ENT_F_* macros + * @num_pads: number of pads to initialize + * @pads_flag: flags to use in each pad + * @sd_ops: pointer to &struct v4l2_subdev_ops. + * + * Helper function initialize and register the struct vimc_ent_device and struct + * v4l2_subdev which represents a subdev node in the topology + */ +int vimc_ent_sd_register(struct vimc_ent_device *ved, + struct v4l2_subdev *sd, + struct v4l2_device *v4l2_dev, + const char *const name, + u32 function, + u16 num_pads, + const unsigned long *pads_flag, + const struct v4l2_subdev_ops *sd_ops); + +/** + * vimc_ent_sd_unregister - cleanup and unregister a subdev node + * + * @ved: the vimc_ent_device struct to be cleaned up + * @sd: the v4l2_subdev struct to be unregistered + * + * Helper function cleanup and unregister the struct vimc_ent_device and struct + * v4l2_subdev which represents a subdev node in the topology + */ +void vimc_ent_sd_unregister(struct vimc_ent_device *ved, + struct v4l2_subdev *sd); + +/** + * vimc_link_validate - validates a media link + * + * @link: pointer to &struct media_link + * + * This function calls validates if a media link is valid for streaming. + */ +int vimc_link_validate(struct media_link *link); + +#endif diff --git a/drivers/media/platform/vimc/vimc-core.c b/drivers/media/platform/vimc/vimc-core.c index bc107da8fbd5..51c0eee61ca6 100644 --- a/drivers/media/platform/vimc/vimc-core.c +++ b/drivers/media/platform/vimc/vimc-core.c @@ -15,15 +15,14 @@ * */ +#include <linux/component.h> #include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <media/media-device.h> #include <media/v4l2-device.h> -#include "vimc-capture.h" -#include "vimc-core.h" -#include "vimc-sensor.h" +#include "vimc-common.h" #define VIMC_PDEV_NAME "vimc" #define VIMC_MDEV_MODEL_NAME "VIMC MDEV" @@ -37,10 +36,10 @@ } struct vimc_device { - /* - * The pipeline configuration - * (filled before calling vimc_device_register) - */ + /* The platform device */ + struct platform_device pdev; + + /* The pipeline configuration */ const struct vimc_pipeline_config *pipe_cfg; /* The Associated media_device parent */ @@ -49,43 +48,14 @@ struct vimc_device { /* Internal v4l2 parent device*/ struct v4l2_device v4l2_dev; - /* Internal topology */ - struct vimc_ent_device **ved; -}; - -/** - * enum vimc_ent_node - Select the functionality of a node in the topology - * @VIMC_ENT_NODE_SENSOR: A node of type SENSOR simulates a camera sensor - * generating internal images in bayer format and - * propagating those images through the pipeline - * @VIMC_ENT_NODE_CAPTURE: A node of type CAPTURE is a v4l2 video_device - * that exposes the received image from the - * pipeline to the user space - * @VIMC_ENT_NODE_INPUT: A node of type INPUT is a v4l2 video_device that - * receives images from the user space and - * propagates them through the pipeline - * @VIMC_ENT_NODE_DEBAYER: A node type DEBAYER expects to receive a frame - * in bayer format converts it to RGB - * @VIMC_ENT_NODE_SCALER: A node of type SCALER scales the received image - * by a given multiplier - * - * This enum is used in the entity configuration struct to allow the definition - * of a custom topology specifying the role of each node on it. - */ -enum vimc_ent_node { - VIMC_ENT_NODE_SENSOR, - VIMC_ENT_NODE_CAPTURE, - VIMC_ENT_NODE_INPUT, - VIMC_ENT_NODE_DEBAYER, - VIMC_ENT_NODE_SCALER, + /* Subdevices */ + struct platform_device **subdevs; }; /* Structure which describes individual configuration for each entity */ struct vimc_ent_config { const char *name; - size_t pads_qty; - const unsigned long *pads_flag; - enum vimc_ent_node node; + const char *drv; }; /* Structure which describes links between entities */ @@ -112,60 +82,40 @@ struct vimc_pipeline_config { static const struct vimc_ent_config ent_config[] = { { .name = "Sensor A", - .pads_qty = 1, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SOURCE}, - .node = VIMC_ENT_NODE_SENSOR, + .drv = "vimc-sensor", }, { .name = "Sensor B", - .pads_qty = 1, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SOURCE}, - .node = VIMC_ENT_NODE_SENSOR, + .drv = "vimc-sensor", }, { .name = "Debayer A", - .pads_qty = 2, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SINK, - MEDIA_PAD_FL_SOURCE}, - .node = VIMC_ENT_NODE_DEBAYER, + .drv = "vimc-debayer", }, { .name = "Debayer B", - .pads_qty = 2, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SINK, - MEDIA_PAD_FL_SOURCE}, - .node = VIMC_ENT_NODE_DEBAYER, + .drv = "vimc-debayer", }, { .name = "Raw Capture 0", - .pads_qty = 1, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SINK}, - .node = VIMC_ENT_NODE_CAPTURE, + .drv = "vimc-capture", }, { .name = "Raw Capture 1", - .pads_qty = 1, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SINK}, - .node = VIMC_ENT_NODE_CAPTURE, + .drv = "vimc-capture", }, { .name = "RGB/YUV Input", - .pads_qty = 1, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SOURCE}, - .node = VIMC_ENT_NODE_INPUT, + /* TODO: change this to vimc-input when it is implemented */ + .drv = "vimc-sensor", }, { .name = "Scaler", - .pads_qty = 2, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SINK, - MEDIA_PAD_FL_SOURCE}, - .node = VIMC_ENT_NODE_SCALER, + .drv = "vimc-scaler", }, { .name = "RGB/YUV Capture", - .pads_qty = 1, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SINK}, - .node = VIMC_ENT_NODE_CAPTURE, + .drv = "vimc-capture", }, }; @@ -197,314 +147,40 @@ static const struct vimc_pipeline_config pipe_cfg = { /* -------------------------------------------------------------------------- */ -static const struct vimc_pix_map vimc_pix_map_list[] = { - /* TODO: add all missing formats */ - - /* RGB formats */ - { - .code = MEDIA_BUS_FMT_BGR888_1X24, - .pixelformat = V4L2_PIX_FMT_BGR24, - .bpp = 3, - }, - { - .code = MEDIA_BUS_FMT_RGB888_1X24, - .pixelformat = V4L2_PIX_FMT_RGB24, - .bpp = 3, - }, - { - .code = MEDIA_BUS_FMT_ARGB8888_1X32, - .pixelformat = V4L2_PIX_FMT_ARGB32, - .bpp = 4, - }, - - /* Bayer formats */ - { - .code = MEDIA_BUS_FMT_SBGGR8_1X8, - .pixelformat = V4L2_PIX_FMT_SBGGR8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SGBRG8_1X8, - .pixelformat = V4L2_PIX_FMT_SGBRG8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SGRBG8_1X8, - .pixelformat = V4L2_PIX_FMT_SGRBG8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SRGGB8_1X8, - .pixelformat = V4L2_PIX_FMT_SRGGB8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SBGGR10_1X10, - .pixelformat = V4L2_PIX_FMT_SBGGR10, - .bpp = 2, - }, - { - .code = MEDIA_BUS_FMT_SGBRG10_1X10, - .pixelformat = V4L2_PIX_FMT_SGBRG10, - .bpp = 2, - }, - { - .code = MEDIA_BUS_FMT_SGRBG10_1X10, - .pixelformat = V4L2_PIX_FMT_SGRBG10, - .bpp = 2, - }, - { - .code = MEDIA_BUS_FMT_SRGGB10_1X10, - .pixelformat = V4L2_PIX_FMT_SRGGB10, - .bpp = 2, - }, - - /* 10bit raw bayer a-law compressed to 8 bits */ - { - .code = MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8, - .pixelformat = V4L2_PIX_FMT_SBGGR10ALAW8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8, - .pixelformat = V4L2_PIX_FMT_SGBRG10ALAW8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8, - .pixelformat = V4L2_PIX_FMT_SGRBG10ALAW8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8, - .pixelformat = V4L2_PIX_FMT_SRGGB10ALAW8, - .bpp = 1, - }, - - /* 10bit raw bayer DPCM compressed to 8 bits */ - { - .code = MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8, - .pixelformat = V4L2_PIX_FMT_SBGGR10DPCM8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8, - .pixelformat = V4L2_PIX_FMT_SGBRG10DPCM8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, - .pixelformat = V4L2_PIX_FMT_SGRBG10DPCM8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8, - .pixelformat = V4L2_PIX_FMT_SRGGB10DPCM8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SBGGR12_1X12, - .pixelformat = V4L2_PIX_FMT_SBGGR12, - .bpp = 2, - }, - { - .code = MEDIA_BUS_FMT_SGBRG12_1X12, - .pixelformat = V4L2_PIX_FMT_SGBRG12, - .bpp = 2, - }, - { - .code = MEDIA_BUS_FMT_SGRBG12_1X12, - .pixelformat = V4L2_PIX_FMT_SGRBG12, - .bpp = 2, - }, - { - .code = MEDIA_BUS_FMT_SRGGB12_1X12, - .pixelformat = V4L2_PIX_FMT_SRGGB12, - .bpp = 2, - }, -}; - -const struct vimc_pix_map *vimc_pix_map_by_code(u32 code) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(vimc_pix_map_list); i++) { - if (vimc_pix_map_list[i].code == code) - return &vimc_pix_map_list[i]; - } - return NULL; -} - -const struct vimc_pix_map *vimc_pix_map_by_pixelformat(u32 pixelformat) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(vimc_pix_map_list); i++) { - if (vimc_pix_map_list[i].pixelformat == pixelformat) - return &vimc_pix_map_list[i]; - } - return NULL; -} - -int vimc_propagate_frame(struct media_pad *src, const void *frame) -{ - struct media_link *link; - - if (!(src->flags & MEDIA_PAD_FL_SOURCE)) - return -EINVAL; - - /* Send this frame to all sink pads that are direct linked */ - list_for_each_entry(link, &src->entity->links, list) { - if (link->source == src && - (link->flags & MEDIA_LNK_FL_ENABLED)) { - struct vimc_ent_device *ved = NULL; - struct media_entity *entity = link->sink->entity; - - if (is_media_entity_v4l2_subdev(entity)) { - struct v4l2_subdev *sd = - container_of(entity, struct v4l2_subdev, - entity); - ved = v4l2_get_subdevdata(sd); - } else if (is_media_entity_v4l2_video_device(entity)) { - struct video_device *vdev = - container_of(entity, - struct video_device, - entity); - ved = video_get_drvdata(vdev); - } - if (ved && ved->process_frame) - ved->process_frame(ved, link->sink, frame); - } - } - - return 0; -} - -static void vimc_device_unregister(struct vimc_device *vimc) -{ - unsigned int i; - - media_device_unregister(&vimc->mdev); - /* Cleanup (only initialized) entities */ - for (i = 0; i < vimc->pipe_cfg->num_ents; i++) { - if (vimc->ved[i] && vimc->ved[i]->destroy) - vimc->ved[i]->destroy(vimc->ved[i]); - - vimc->ved[i] = NULL; - } - v4l2_device_unregister(&vimc->v4l2_dev); - media_device_cleanup(&vimc->mdev); -} - -/* Helper function to allocate and initialize pads */ -struct media_pad *vimc_pads_init(u16 num_pads, const unsigned long *pads_flag) +static int vimc_create_links(struct vimc_device *vimc) { - struct media_pad *pads; unsigned int i; - - /* Allocate memory for the pads */ - pads = kcalloc(num_pads, sizeof(*pads), GFP_KERNEL); - if (!pads) - return ERR_PTR(-ENOMEM); - - /* Initialize the pads */ - for (i = 0; i < num_pads; i++) { - pads[i].index = i; - pads[i].flags = pads_flag[i]; - } - - return pads; -} - -/* - * TODO: remove this function when all the - * entities specific code are implemented - */ -static void vimc_raw_destroy(struct vimc_ent_device *ved) -{ - media_device_unregister_entity(ved->ent); - - media_entity_cleanup(ved->ent); - - vimc_pads_cleanup(ved->pads); - - kfree(ved->ent); - - kfree(ved); -} - -/* - * TODO: remove this function when all the - * entities specific code are implemented - */ -static struct vimc_ent_device *vimc_raw_create(struct v4l2_device *v4l2_dev, - const char *const name, - u16 num_pads, - const unsigned long *pads_flag) -{ - struct vimc_ent_device *ved; int ret; - /* Allocate the main ved struct */ - ved = kzalloc(sizeof(*ved), GFP_KERNEL); - if (!ved) - return ERR_PTR(-ENOMEM); - - /* Allocate the media entity */ - ved->ent = kzalloc(sizeof(*ved->ent), GFP_KERNEL); - if (!ved->ent) { - ret = -ENOMEM; - goto err_free_ved; - } - - /* Allocate the pads */ - ved->pads = vimc_pads_init(num_pads, pads_flag); - if (IS_ERR(ved->pads)) { - ret = PTR_ERR(ved->pads); - goto err_free_ent; + /* Initialize the links between entities */ + for (i = 0; i < vimc->pipe_cfg->num_links; i++) { + const struct vimc_ent_link *link = &vimc->pipe_cfg->links[i]; + /* + * TODO: Check another way of retrieving ved struct without + * relying on platform_get_drvdata + */ + struct vimc_ent_device *ved_src = + platform_get_drvdata(vimc->subdevs[link->src_ent]); + struct vimc_ent_device *ved_sink = + platform_get_drvdata(vimc->subdevs[link->sink_ent]); + + ret = media_create_pad_link(ved_src->ent, link->src_pad, + ved_sink->ent, link->sink_pad, + link->flags); + if (ret) + return ret; } - /* Initialize the media entity */ - ved->ent->name = name; - ved->ent->function = MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN; - ret = media_entity_pads_init(ved->ent, num_pads, ved->pads); - if (ret) - goto err_cleanup_pads; - - /* Register the media entity */ - ret = media_device_register_entity(v4l2_dev->mdev, ved->ent); - if (ret) - goto err_cleanup_entity; - - /* Fill out the destroy function and return */ - ved->destroy = vimc_raw_destroy; - return ved; - -err_cleanup_entity: - media_entity_cleanup(ved->ent); -err_cleanup_pads: - vimc_pads_cleanup(ved->pads); -err_free_ent: - kfree(ved->ent); -err_free_ved: - kfree(ved); - - return ERR_PTR(ret); + return 0; } -static int vimc_device_register(struct vimc_device *vimc) +static int vimc_comp_bind(struct device *master) { - unsigned int i; + struct vimc_device *vimc = container_of(to_platform_device(master), + struct vimc_device, pdev); int ret; - /* Allocate memory for the vimc_ent_devices pointers */ - vimc->ved = devm_kcalloc(vimc->mdev.dev, vimc->pipe_cfg->num_ents, - sizeof(*vimc->ved), GFP_KERNEL); - if (!vimc->ved) - return -ENOMEM; - - /* Link the media device within the v4l2_device */ - vimc->v4l2_dev.mdev = &vimc->mdev; + dev_dbg(master, "bind"); /* Register the v4l2 struct */ ret = v4l2_device_register(vimc->mdev.dev, &vimc->v4l2_dev); @@ -514,66 +190,22 @@ static int vimc_device_register(struct vimc_device *vimc) return ret; } - /* Initialize entities */ - for (i = 0; i < vimc->pipe_cfg->num_ents; i++) { - struct vimc_ent_device *(*create_func)(struct v4l2_device *, - const char *const, - u16, - const unsigned long *); - - /* Register the specific node */ - switch (vimc->pipe_cfg->ents[i].node) { - case VIMC_ENT_NODE_SENSOR: - create_func = vimc_sen_create; - break; - - case VIMC_ENT_NODE_CAPTURE: - create_func = vimc_cap_create; - break; - - /* TODO: Instantiate the specific topology node */ - case VIMC_ENT_NODE_INPUT: - case VIMC_ENT_NODE_DEBAYER: - case VIMC_ENT_NODE_SCALER: - default: - /* - * TODO: remove this when all the entities specific - * code are implemented - */ - create_func = vimc_raw_create; - break; - } - - vimc->ved[i] = create_func(&vimc->v4l2_dev, - vimc->pipe_cfg->ents[i].name, - vimc->pipe_cfg->ents[i].pads_qty, - vimc->pipe_cfg->ents[i].pads_flag); - if (IS_ERR(vimc->ved[i])) { - ret = PTR_ERR(vimc->ved[i]); - vimc->ved[i] = NULL; - goto err; - } - } - - /* Initialize the links between entities */ - for (i = 0; i < vimc->pipe_cfg->num_links; i++) { - const struct vimc_ent_link *link = &vimc->pipe_cfg->links[i]; + /* Bind subdevices */ + ret = component_bind_all(master, &vimc->v4l2_dev); + if (ret) + goto err_v4l2_unregister; - ret = media_create_pad_link(vimc->ved[link->src_ent]->ent, - link->src_pad, - vimc->ved[link->sink_ent]->ent, - link->sink_pad, - link->flags); - if (ret) - goto err; - } + /* Initialize links */ + ret = vimc_create_links(vimc); + if (ret) + goto err_comp_unbind_all; /* Register the media device */ ret = media_device_register(&vimc->mdev); if (ret) { dev_err(vimc->mdev.dev, "media device register failed (err=%d)\n", ret); - return ret; + goto err_comp_unbind_all; } /* Expose all subdev's nodes*/ @@ -582,32 +214,106 @@ static int vimc_device_register(struct vimc_device *vimc) dev_err(vimc->mdev.dev, "vimc subdev nodes registration failed (err=%d)\n", ret); - goto err; + goto err_mdev_unregister; } return 0; -err: - /* Destroy the so far created topology */ - vimc_device_unregister(vimc); +err_mdev_unregister: + media_device_unregister(&vimc->mdev); +err_comp_unbind_all: + component_unbind_all(master, NULL); +err_v4l2_unregister: + v4l2_device_unregister(&vimc->v4l2_dev); return ret; } +static void vimc_comp_unbind(struct device *master) +{ + struct vimc_device *vimc = container_of(to_platform_device(master), + struct vimc_device, pdev); + + dev_dbg(master, "unbind"); + + media_device_unregister(&vimc->mdev); + component_unbind_all(master, NULL); + v4l2_device_unregister(&vimc->v4l2_dev); +} + +static int vimc_comp_compare(struct device *comp, void *data) +{ + const struct platform_device *pdev = to_platform_device(comp); + const char *name = data; + + return !strcmp(pdev->dev.platform_data, name); +} + +static struct component_match *vimc_add_subdevs(struct vimc_device *vimc) +{ + struct component_match *match = NULL; + struct vimc_platform_data pdata; + int i; + + for (i = 0; i < vimc->pipe_cfg->num_ents; i++) { + dev_dbg(&vimc->pdev.dev, "new pdev for %s\n", + vimc->pipe_cfg->ents[i].drv); + + strlcpy(pdata.entity_name, vimc->pipe_cfg->ents[i].name, + sizeof(pdata.entity_name)); + + vimc->subdevs[i] = platform_device_register_data(&vimc->pdev.dev, + vimc->pipe_cfg->ents[i].drv, + PLATFORM_DEVID_AUTO, + &pdata, + sizeof(pdata)); + if (!vimc->subdevs[i]) { + while (--i >= 0) + platform_device_unregister(vimc->subdevs[i]); + + return ERR_PTR(-ENOMEM); + } + + component_match_add(&vimc->pdev.dev, &match, vimc_comp_compare, + (void *)vimc->pipe_cfg->ents[i].name); + } + + return match; +} + +static void vimc_rm_subdevs(struct vimc_device *vimc) +{ + unsigned int i; + + for (i = 0; i < vimc->pipe_cfg->num_ents; i++) + platform_device_unregister(vimc->subdevs[i]); +} + +static const struct component_master_ops vimc_comp_ops = { + .bind = vimc_comp_bind, + .unbind = vimc_comp_unbind, +}; + static int vimc_probe(struct platform_device *pdev) { - struct vimc_device *vimc; + struct vimc_device *vimc = container_of(pdev, struct vimc_device, pdev); + struct component_match *match = NULL; int ret; - /* Prepare the vimc topology structure */ + dev_dbg(&pdev->dev, "probe"); - /* Allocate memory for the vimc structure */ - vimc = kzalloc(sizeof(*vimc), GFP_KERNEL); - if (!vimc) + /* Create platform_device for each entity in the topology*/ + vimc->subdevs = devm_kcalloc(&vimc->pdev.dev, vimc->pipe_cfg->num_ents, + sizeof(*vimc->subdevs), GFP_KERNEL); + if (!vimc->subdevs) return -ENOMEM; - /* Set the pipeline configuration struct */ - vimc->pipe_cfg = &pipe_cfg; + match = vimc_add_subdevs(vimc); + if (IS_ERR(match)) + return PTR_ERR(match); + + /* Link the media device within the v4l2_device */ + vimc->v4l2_dev.mdev = &vimc->mdev; /* Initialize media device */ strlcpy(vimc->mdev.model, VIMC_MDEV_MODEL_NAME, @@ -615,28 +321,27 @@ static int vimc_probe(struct platform_device *pdev) vimc->mdev.dev = &pdev->dev; media_device_init(&vimc->mdev); - /* Create vimc topology */ - ret = vimc_device_register(vimc); + /* Add self to the component system */ + ret = component_master_add_with_match(&pdev->dev, &vimc_comp_ops, + match); if (ret) { - dev_err(vimc->mdev.dev, - "vimc device registration failed (err=%d)\n", ret); + media_device_cleanup(&vimc->mdev); + vimc_rm_subdevs(vimc); kfree(vimc); return ret; } - /* Link the topology object with the platform device object */ - platform_set_drvdata(pdev, vimc); - return 0; } static int vimc_remove(struct platform_device *pdev) { - struct vimc_device *vimc = platform_get_drvdata(pdev); + struct vimc_device *vimc = container_of(pdev, struct vimc_device, pdev); + + dev_dbg(&pdev->dev, "remove"); - /* Destroy all the topology */ - vimc_device_unregister(vimc); - kfree(vimc); + component_master_del(&pdev->dev, &vimc_comp_ops); + vimc_rm_subdevs(vimc); return 0; } @@ -645,9 +350,12 @@ static void vimc_dev_release(struct device *dev) { } -static struct platform_device vimc_pdev = { - .name = VIMC_PDEV_NAME, - .dev.release = vimc_dev_release, +static struct vimc_device vimc_dev = { + .pipe_cfg = &pipe_cfg, + .pdev = { + .name = VIMC_PDEV_NAME, + .dev.release = vimc_dev_release, + } }; static struct platform_driver vimc_pdrv = { @@ -662,29 +370,29 @@ static int __init vimc_init(void) { int ret; - ret = platform_device_register(&vimc_pdev); + ret = platform_device_register(&vimc_dev.pdev); if (ret) { - dev_err(&vimc_pdev.dev, + dev_err(&vimc_dev.pdev.dev, "platform device registration failed (err=%d)\n", ret); return ret; } ret = platform_driver_register(&vimc_pdrv); if (ret) { - dev_err(&vimc_pdev.dev, + dev_err(&vimc_dev.pdev.dev, "platform driver registration failed (err=%d)\n", ret); - - platform_device_unregister(&vimc_pdev); + platform_driver_unregister(&vimc_pdrv); + return ret; } - return ret; + return 0; } static void __exit vimc_exit(void) { platform_driver_unregister(&vimc_pdrv); - platform_device_unregister(&vimc_pdev); + platform_device_unregister(&vimc_dev.pdev); } module_init(vimc_init); diff --git a/drivers/media/platform/vimc/vimc-core.h b/drivers/media/platform/vimc/vimc-core.h deleted file mode 100644 index 4525d23211ca..000000000000 --- a/drivers/media/platform/vimc/vimc-core.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - * vimc-core.h Virtual Media Controller Driver - * - * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef _VIMC_CORE_H_ -#define _VIMC_CORE_H_ - -#include <linux/slab.h> -#include <media/v4l2-device.h> - -/** - * struct vimc_pix_map - maps media bus code with v4l2 pixel format - * - * @code: media bus format code defined by MEDIA_BUS_FMT_* macros - * @bbp: number of bytes each pixel occupies - * @pixelformat: pixel format devined by V4L2_PIX_FMT_* macros - * - * Struct which matches the MEDIA_BUS_FMT_* codes with the corresponding - * V4L2_PIX_FMT_* fourcc pixelformat and its bytes per pixel (bpp) - */ -struct vimc_pix_map { - unsigned int code; - unsigned int bpp; - u32 pixelformat; -}; - -/** - * struct vimc_ent_device - core struct that represents a node in the topology - * - * @ent: the pointer to struct media_entity for the node - * @pads: the list of pads of the node - * @destroy: callback to destroy the node - * @process_frame: callback send a frame to that node - * - * Each node of the topology must create a vimc_ent_device struct. Depending on - * the node it will be of an instance of v4l2_subdev or video_device struct - * where both contains a struct media_entity. - * Those structures should embedded the vimc_ent_device struct through - * v4l2_set_subdevdata() and video_set_drvdata() respectivaly, allowing the - * vimc_ent_device struct to be retrieved from the corresponding struct - * media_entity - */ -struct vimc_ent_device { - struct media_entity *ent; - struct media_pad *pads; - void (*destroy)(struct vimc_ent_device *); - void (*process_frame)(struct vimc_ent_device *ved, - struct media_pad *sink, const void *frame); -}; - -/** - * vimc_propagate_frame - propagate a frame through the topology - * - * @src: the source pad where the frame is being originated - * @frame: the frame to be propagated - * - * This function will call the process_frame callback from the vimc_ent_device - * struct of the nodes directly connected to the @src pad - */ -int vimc_propagate_frame(struct media_pad *src, const void *frame); - -/** - * vimc_pads_init - initialize pads - * - * @num_pads: number of pads to initialize - * @pads_flags: flags to use in each pad - * - * Helper functions to allocate/initialize pads - */ -struct media_pad *vimc_pads_init(u16 num_pads, - const unsigned long *pads_flag); - -/** - * vimc_pads_cleanup - free pads - * - * @pads: pointer to the pads - * - * Helper function to free the pads initialized with vimc_pads_init - */ -static inline void vimc_pads_cleanup(struct media_pad *pads) -{ - kfree(pads); -} - -/** - * vimc_pix_map_by_code - get vimc_pix_map struct by media bus code - * - * @code: media bus format code defined by MEDIA_BUS_FMT_* macros - */ -const struct vimc_pix_map *vimc_pix_map_by_code(u32 code); - -/** - * vimc_pix_map_by_pixelformat - get vimc_pix_map struct by v4l2 pixel format - * - * @pixelformat: pixel format devined by V4L2_PIX_FMT_* macros - */ -const struct vimc_pix_map *vimc_pix_map_by_pixelformat(u32 pixelformat); - -#endif diff --git a/drivers/media/platform/vimc/vimc-debayer.c b/drivers/media/platform/vimc/vimc-debayer.c new file mode 100644 index 000000000000..35b15bd4d61d --- /dev/null +++ b/drivers/media/platform/vimc/vimc-debayer.c @@ -0,0 +1,601 @@ +/* + * vimc-debayer.c Virtual Media Controller Driver + * + * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/vmalloc.h> +#include <linux/v4l2-mediabus.h> +#include <media/v4l2-subdev.h> + +#include "vimc-common.h" + +#define VIMC_DEB_DRV_NAME "vimc-debayer" + +static unsigned int deb_mean_win_size = 3; +module_param(deb_mean_win_size, uint, 0000); +MODULE_PARM_DESC(deb_mean_win_size, " the window size to calculate the mean.\n" + "NOTE: the window size need to be an odd number, as the main pixel " + "stays in the center of the window, otherwise the next odd number " + "is considered"); + +#define IS_SINK(pad) (!pad) +#define IS_SRC(pad) (pad) + +enum vimc_deb_rgb_colors { + VIMC_DEB_RED = 0, + VIMC_DEB_GREEN = 1, + VIMC_DEB_BLUE = 2, +}; + +struct vimc_deb_pix_map { + u32 code; + enum vimc_deb_rgb_colors order[2][2]; +}; + +struct vimc_deb_device { + struct vimc_ent_device ved; + struct v4l2_subdev sd; + struct device *dev; + /* The active format */ + struct v4l2_mbus_framefmt sink_fmt; + u32 src_code; + void (*set_rgb_src)(struct vimc_deb_device *vdeb, unsigned int lin, + unsigned int col, unsigned int rgb[3]); + /* Values calculated when the stream starts */ + u8 *src_frame; + const struct vimc_deb_pix_map *sink_pix_map; + unsigned int sink_bpp; +}; + +static const struct v4l2_mbus_framefmt sink_fmt_default = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_RGB888_1X24, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_DEFAULT, +}; + +static const struct vimc_deb_pix_map vimc_deb_pix_map_list[] = { + { + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .order = { { VIMC_DEB_BLUE, VIMC_DEB_GREEN }, + { VIMC_DEB_GREEN, VIMC_DEB_RED } } + }, + { + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .order = { { VIMC_DEB_GREEN, VIMC_DEB_BLUE }, + { VIMC_DEB_RED, VIMC_DEB_GREEN } } + }, + { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .order = { { VIMC_DEB_GREEN, VIMC_DEB_RED }, + { VIMC_DEB_BLUE, VIMC_DEB_GREEN } } + }, + { + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .order = { { VIMC_DEB_RED, VIMC_DEB_GREEN }, + { VIMC_DEB_GREEN, VIMC_DEB_BLUE } } + }, + { + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .order = { { VIMC_DEB_BLUE, VIMC_DEB_GREEN }, + { VIMC_DEB_GREEN, VIMC_DEB_RED } } + }, + { + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .order = { { VIMC_DEB_GREEN, VIMC_DEB_BLUE }, + { VIMC_DEB_RED, VIMC_DEB_GREEN } } + }, + { + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .order = { { VIMC_DEB_GREEN, VIMC_DEB_RED }, + { VIMC_DEB_BLUE, VIMC_DEB_GREEN } } + }, + { + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .order = { { VIMC_DEB_RED, VIMC_DEB_GREEN }, + { VIMC_DEB_GREEN, VIMC_DEB_BLUE } } + }, + { + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .order = { { VIMC_DEB_BLUE, VIMC_DEB_GREEN }, + { VIMC_DEB_GREEN, VIMC_DEB_RED } } + }, + { + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .order = { { VIMC_DEB_GREEN, VIMC_DEB_BLUE }, + { VIMC_DEB_RED, VIMC_DEB_GREEN } } + }, + { + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .order = { { VIMC_DEB_GREEN, VIMC_DEB_RED }, + { VIMC_DEB_BLUE, VIMC_DEB_GREEN } } + }, + { + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .order = { { VIMC_DEB_RED, VIMC_DEB_GREEN }, + { VIMC_DEB_GREEN, VIMC_DEB_BLUE } } + }, +}; + +static const struct vimc_deb_pix_map *vimc_deb_pix_map_by_code(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vimc_deb_pix_map_list); i++) + if (vimc_deb_pix_map_list[i].code == code) + return &vimc_deb_pix_map_list[i]; + + return NULL; +} + +static int vimc_deb_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +{ + struct vimc_deb_device *vdeb = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *mf; + unsigned int i; + + mf = v4l2_subdev_get_try_format(sd, cfg, 0); + *mf = sink_fmt_default; + + for (i = 1; i < sd->entity.num_pads; i++) { + mf = v4l2_subdev_get_try_format(sd, cfg, i); + *mf = sink_fmt_default; + mf->code = vdeb->src_code; + } + + return 0; +} + +static int vimc_deb_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* We only support one format for source pads */ + if (IS_SRC(code->pad)) { + struct vimc_deb_device *vdeb = v4l2_get_subdevdata(sd); + + if (code->index) + return -EINVAL; + + code->code = vdeb->src_code; + } else { + if (code->index >= ARRAY_SIZE(vimc_deb_pix_map_list)) + return -EINVAL; + + code->code = vimc_deb_pix_map_list[code->index].code; + } + + return 0; +} + +static int vimc_deb_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vimc_deb_device *vdeb = v4l2_get_subdevdata(sd); + + if (fse->index) + return -EINVAL; + + if (IS_SINK(fse->pad)) { + const struct vimc_deb_pix_map *vpix = + vimc_deb_pix_map_by_code(fse->code); + + if (!vpix) + return -EINVAL; + } else if (fse->code != vdeb->src_code) { + return -EINVAL; + } + + fse->min_width = VIMC_FRAME_MIN_WIDTH; + fse->max_width = VIMC_FRAME_MAX_WIDTH; + fse->min_height = VIMC_FRAME_MIN_HEIGHT; + fse->max_height = VIMC_FRAME_MAX_HEIGHT; + + return 0; +} + +static int vimc_deb_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct vimc_deb_device *vdeb = v4l2_get_subdevdata(sd); + + /* Get the current sink format */ + fmt->format = fmt->which == V4L2_SUBDEV_FORMAT_TRY ? + *v4l2_subdev_get_try_format(sd, cfg, 0) : + vdeb->sink_fmt; + + /* Set the right code for the source pad */ + if (IS_SRC(fmt->pad)) + fmt->format.code = vdeb->src_code; + + return 0; +} + +static void vimc_deb_adjust_sink_fmt(struct v4l2_mbus_framefmt *fmt) +{ + const struct vimc_deb_pix_map *vpix; + + /* Don't accept a code that is not on the debayer table */ + vpix = vimc_deb_pix_map_by_code(fmt->code); + if (!vpix) + fmt->code = sink_fmt_default.code; + + fmt->width = clamp_t(u32, fmt->width, VIMC_FRAME_MIN_WIDTH, + VIMC_FRAME_MAX_WIDTH) & ~1; + fmt->height = clamp_t(u32, fmt->height, VIMC_FRAME_MIN_HEIGHT, + VIMC_FRAME_MAX_HEIGHT) & ~1; + + if (fmt->field == V4L2_FIELD_ANY) + fmt->field = sink_fmt_default.field; + + vimc_colorimetry_clamp(fmt); +} + +static int vimc_deb_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct vimc_deb_device *vdeb = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *sink_fmt; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + /* Do not change the format while stream is on */ + if (vdeb->src_frame) + return -EBUSY; + + sink_fmt = &vdeb->sink_fmt; + } else { + sink_fmt = v4l2_subdev_get_try_format(sd, cfg, 0); + } + + /* + * Do not change the format of the source pad, + * it is propagated from the sink + */ + if (IS_SRC(fmt->pad)) { + fmt->format = *sink_fmt; + /* TODO: Add support for other formats */ + fmt->format.code = vdeb->src_code; + } else { + /* Set the new format in the sink pad */ + vimc_deb_adjust_sink_fmt(&fmt->format); + + dev_dbg(vdeb->dev, "%s: sink format update: " + "old:%dx%d (0x%x, %d, %d, %d, %d) " + "new:%dx%d (0x%x, %d, %d, %d, %d)\n", vdeb->sd.name, + /* old */ + sink_fmt->width, sink_fmt->height, sink_fmt->code, + sink_fmt->colorspace, sink_fmt->quantization, + sink_fmt->xfer_func, sink_fmt->ycbcr_enc, + /* new */ + fmt->format.width, fmt->format.height, fmt->format.code, + fmt->format.colorspace, fmt->format.quantization, + fmt->format.xfer_func, fmt->format.ycbcr_enc); + + *sink_fmt = fmt->format; + } + + return 0; +} + +static const struct v4l2_subdev_pad_ops vimc_deb_pad_ops = { + .init_cfg = vimc_deb_init_cfg, + .enum_mbus_code = vimc_deb_enum_mbus_code, + .enum_frame_size = vimc_deb_enum_frame_size, + .get_fmt = vimc_deb_get_fmt, + .set_fmt = vimc_deb_set_fmt, +}; + +static void vimc_deb_set_rgb_mbus_fmt_rgb888_1x24(struct vimc_deb_device *vdeb, + unsigned int lin, + unsigned int col, + unsigned int rgb[3]) +{ + unsigned int i, index; + + index = VIMC_FRAME_INDEX(lin, col, vdeb->sink_fmt.width, 3); + for (i = 0; i < 3; i++) + vdeb->src_frame[index + i] = rgb[i]; +} + +static int vimc_deb_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct vimc_deb_device *vdeb = v4l2_get_subdevdata(sd); + int ret; + + if (enable) { + const struct vimc_pix_map *vpix; + unsigned int frame_size; + + if (vdeb->src_frame) + return 0; + + /* Calculate the frame size of the source pad */ + vpix = vimc_pix_map_by_code(vdeb->src_code); + frame_size = vdeb->sink_fmt.width * vdeb->sink_fmt.height * + vpix->bpp; + + /* Save the bytes per pixel of the sink */ + vpix = vimc_pix_map_by_code(vdeb->sink_fmt.code); + vdeb->sink_bpp = vpix->bpp; + + /* Get the corresponding pixel map from the table */ + vdeb->sink_pix_map = + vimc_deb_pix_map_by_code(vdeb->sink_fmt.code); + + /* + * Allocate the frame buffer. Use vmalloc to be able to + * allocate a large amount of memory + */ + vdeb->src_frame = vmalloc(frame_size); + if (!vdeb->src_frame) + return -ENOMEM; + + /* Turn the stream on in the subdevices directly connected */ + ret = vimc_pipeline_s_stream(&vdeb->sd.entity, 1); + if (ret) { + vfree(vdeb->src_frame); + vdeb->src_frame = NULL; + return ret; + } + } else { + if (!vdeb->src_frame) + return 0; + + /* Disable streaming from the pipe */ + ret = vimc_pipeline_s_stream(&vdeb->sd.entity, 0); + if (ret) + return ret; + + vfree(vdeb->src_frame); + vdeb->src_frame = NULL; + } + + return 0; +} + +static struct v4l2_subdev_video_ops vimc_deb_video_ops = { + .s_stream = vimc_deb_s_stream, +}; + +static const struct v4l2_subdev_ops vimc_deb_ops = { + .pad = &vimc_deb_pad_ops, + .video = &vimc_deb_video_ops, +}; + +static unsigned int vimc_deb_get_val(const u8 *bytes, + const unsigned int n_bytes) +{ + unsigned int i; + unsigned int acc = 0; + + for (i = 0; i < n_bytes; i++) + acc = acc + (bytes[i] << (8 * i)); + + return acc; +} + +static void vimc_deb_calc_rgb_sink(struct vimc_deb_device *vdeb, + const u8 *frame, + const unsigned int lin, + const unsigned int col, + unsigned int rgb[3]) +{ + unsigned int i, seek, wlin, wcol; + unsigned int n_rgb[3] = {0, 0, 0}; + + for (i = 0; i < 3; i++) + rgb[i] = 0; + + /* + * Calculate how many we need to subtract to get to the pixel in + * the top left corner of the mean window (considering the current + * pixel as the center) + */ + seek = deb_mean_win_size / 2; + + /* Sum the values of the colors in the mean window */ + + dev_dbg(vdeb->dev, + "deb: %s: --- Calc pixel %dx%d, window mean %d, seek %d ---\n", + vdeb->sd.name, lin, col, vdeb->sink_fmt.height, seek); + + /* + * Iterate through all the lines in the mean window, start + * with zero if the pixel is outside the frame and don't pass + * the height when the pixel is in the bottom border of the + * frame + */ + for (wlin = seek > lin ? 0 : lin - seek; + wlin < lin + seek + 1 && wlin < vdeb->sink_fmt.height; + wlin++) { + + /* + * Iterate through all the columns in the mean window, start + * with zero if the pixel is outside the frame and don't pass + * the width when the pixel is in the right border of the + * frame + */ + for (wcol = seek > col ? 0 : col - seek; + wcol < col + seek + 1 && wcol < vdeb->sink_fmt.width; + wcol++) { + enum vimc_deb_rgb_colors color; + unsigned int index; + + /* Check which color this pixel is */ + color = vdeb->sink_pix_map->order[wlin % 2][wcol % 2]; + + index = VIMC_FRAME_INDEX(wlin, wcol, + vdeb->sink_fmt.width, + vdeb->sink_bpp); + + dev_dbg(vdeb->dev, + "deb: %s: RGB CALC: frame index %d, win pos %dx%d, color %d\n", + vdeb->sd.name, index, wlin, wcol, color); + + /* Get its value */ + rgb[color] = rgb[color] + + vimc_deb_get_val(&frame[index], vdeb->sink_bpp); + + /* Save how many values we already added */ + n_rgb[color]++; + + dev_dbg(vdeb->dev, "deb: %s: RGB CALC: val %d, n %d\n", + vdeb->sd.name, rgb[color], n_rgb[color]); + } + } + + /* Calculate the mean */ + for (i = 0; i < 3; i++) { + dev_dbg(vdeb->dev, + "deb: %s: PRE CALC: %dx%d Color %d, val %d, n %d\n", + vdeb->sd.name, lin, col, i, rgb[i], n_rgb[i]); + + if (n_rgb[i]) + rgb[i] = rgb[i] / n_rgb[i]; + + dev_dbg(vdeb->dev, + "deb: %s: FINAL CALC: %dx%d Color %d, val %d\n", + vdeb->sd.name, lin, col, i, rgb[i]); + } +} + +static void vimc_deb_process_frame(struct vimc_ent_device *ved, + struct media_pad *sink, + const void *sink_frame) +{ + struct vimc_deb_device *vdeb = container_of(ved, struct vimc_deb_device, + ved); + unsigned int rgb[3]; + unsigned int i, j; + + /* If the stream in this node is not active, just return */ + if (!vdeb->src_frame) + return; + + for (i = 0; i < vdeb->sink_fmt.height; i++) + for (j = 0; j < vdeb->sink_fmt.width; j++) { + vimc_deb_calc_rgb_sink(vdeb, sink_frame, i, j, rgb); + vdeb->set_rgb_src(vdeb, i, j, rgb); + } + + /* Propagate the frame through all source pads */ + for (i = 1; i < vdeb->sd.entity.num_pads; i++) { + struct media_pad *pad = &vdeb->sd.entity.pads[i]; + + vimc_propagate_frame(pad, vdeb->src_frame); + } +} + +static void vimc_deb_comp_unbind(struct device *comp, struct device *master, + void *master_data) +{ + struct vimc_ent_device *ved = dev_get_drvdata(comp); + struct vimc_deb_device *vdeb = container_of(ved, struct vimc_deb_device, + ved); + + vimc_ent_sd_unregister(ved, &vdeb->sd); + kfree(vdeb); +} + +static int vimc_deb_comp_bind(struct device *comp, struct device *master, + void *master_data) +{ + struct v4l2_device *v4l2_dev = master_data; + struct vimc_platform_data *pdata = comp->platform_data; + struct vimc_deb_device *vdeb; + int ret; + + /* Allocate the vdeb struct */ + vdeb = kzalloc(sizeof(*vdeb), GFP_KERNEL); + if (!vdeb) + return -ENOMEM; + + /* Initialize ved and sd */ + ret = vimc_ent_sd_register(&vdeb->ved, &vdeb->sd, v4l2_dev, + pdata->entity_name, + MEDIA_ENT_F_ATV_DECODER, 2, + (const unsigned long[2]) {MEDIA_PAD_FL_SINK, + MEDIA_PAD_FL_SOURCE}, + &vimc_deb_ops); + if (ret) { + kfree(vdeb); + return ret; + } + + vdeb->ved.process_frame = vimc_deb_process_frame; + dev_set_drvdata(comp, &vdeb->ved); + vdeb->dev = comp; + + /* Initialize the frame format */ + vdeb->sink_fmt = sink_fmt_default; + /* + * TODO: Add support for more output formats, we only support + * RGB888 for now + * NOTE: the src format is always the same as the sink, except + * for the code + */ + vdeb->src_code = MEDIA_BUS_FMT_RGB888_1X24; + vdeb->set_rgb_src = vimc_deb_set_rgb_mbus_fmt_rgb888_1x24; + + return 0; +} + +static const struct component_ops vimc_deb_comp_ops = { + .bind = vimc_deb_comp_bind, + .unbind = vimc_deb_comp_unbind, +}; + +static int vimc_deb_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &vimc_deb_comp_ops); +} + +static int vimc_deb_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vimc_deb_comp_ops); + + return 0; +} + +static struct platform_driver vimc_deb_pdrv = { + .probe = vimc_deb_probe, + .remove = vimc_deb_remove, + .driver = { + .name = VIMC_DEB_DRV_NAME, + }, +}; + +static const struct platform_device_id vimc_deb_driver_ids[] = { + { + .name = VIMC_DEB_DRV_NAME, + }, + { } +}; + +module_platform_driver(vimc_deb_pdrv); + +MODULE_DEVICE_TABLE(platform, vimc_deb_driver_ids); + +MODULE_DESCRIPTION("Virtual Media Controller Driver (VIMC) Debayer"); +MODULE_AUTHOR("Helen Mae Koike Fornazier <helen.fornazier@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/vimc/vimc-scaler.c b/drivers/media/platform/vimc/vimc-scaler.c new file mode 100644 index 000000000000..fe77505d2679 --- /dev/null +++ b/drivers/media/platform/vimc/vimc-scaler.c @@ -0,0 +1,455 @@ +/* + * vimc-scaler.c Virtual Media Controller Driver + * + * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/vmalloc.h> +#include <linux/v4l2-mediabus.h> +#include <media/v4l2-subdev.h> + +#include "vimc-common.h" + +#define VIMC_SCA_DRV_NAME "vimc-scaler" + +static unsigned int sca_mult = 3; +module_param(sca_mult, uint, 0000); +MODULE_PARM_DESC(sca_mult, " the image size multiplier"); + +#define IS_SINK(pad) (!pad) +#define IS_SRC(pad) (pad) +#define MAX_ZOOM 8 + +struct vimc_sca_device { + struct vimc_ent_device ved; + struct v4l2_subdev sd; + struct device *dev; + /* NOTE: the source fmt is the same as the sink + * with the width and hight multiplied by mult + */ + struct v4l2_mbus_framefmt sink_fmt; + /* Values calculated when the stream starts */ + u8 *src_frame; + unsigned int src_line_size; + unsigned int bpp; +}; + +static const struct v4l2_mbus_framefmt sink_fmt_default = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_RGB888_1X24, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_DEFAULT, +}; + +static int vimc_sca_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +{ + struct v4l2_mbus_framefmt *mf; + unsigned int i; + + mf = v4l2_subdev_get_try_format(sd, cfg, 0); + *mf = sink_fmt_default; + + for (i = 1; i < sd->entity.num_pads; i++) { + mf = v4l2_subdev_get_try_format(sd, cfg, i); + *mf = sink_fmt_default; + mf->width = mf->width * sca_mult; + mf->height = mf->height * sca_mult; + } + + return 0; +} + +static int vimc_sca_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + const struct vimc_pix_map *vpix = vimc_pix_map_by_index(code->index); + + /* We don't support bayer format */ + if (!vpix || vpix->bayer) + return -EINVAL; + + code->code = vpix->code; + + return 0; +} + +static int vimc_sca_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + const struct vimc_pix_map *vpix; + + if (fse->index) + return -EINVAL; + + /* Only accept code in the pix map table in non bayer format */ + vpix = vimc_pix_map_by_code(fse->code); + if (!vpix || vpix->bayer) + return -EINVAL; + + fse->min_width = VIMC_FRAME_MIN_WIDTH; + fse->min_height = VIMC_FRAME_MIN_HEIGHT; + + if (IS_SINK(fse->pad)) { + fse->max_width = VIMC_FRAME_MAX_WIDTH; + fse->max_height = VIMC_FRAME_MAX_HEIGHT; + } else { + fse->max_width = VIMC_FRAME_MAX_WIDTH * MAX_ZOOM; + fse->max_height = VIMC_FRAME_MAX_HEIGHT * MAX_ZOOM; + } + + return 0; +} + +static int vimc_sca_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct vimc_sca_device *vsca = v4l2_get_subdevdata(sd); + + /* Get the current sink format */ + format->format = (format->which == V4L2_SUBDEV_FORMAT_TRY) ? + *v4l2_subdev_get_try_format(sd, cfg, 0) : + vsca->sink_fmt; + + /* Scale the frame size for the source pad */ + if (IS_SRC(format->pad)) { + format->format.width = vsca->sink_fmt.width * sca_mult; + format->format.height = vsca->sink_fmt.height * sca_mult; + } + + return 0; +} + +static void vimc_sca_adjust_sink_fmt(struct v4l2_mbus_framefmt *fmt) +{ + const struct vimc_pix_map *vpix; + + /* Only accept code in the pix map table in non bayer format */ + vpix = vimc_pix_map_by_code(fmt->code); + if (!vpix || vpix->bayer) + fmt->code = sink_fmt_default.code; + + fmt->width = clamp_t(u32, fmt->width, VIMC_FRAME_MIN_WIDTH, + VIMC_FRAME_MAX_WIDTH) & ~1; + fmt->height = clamp_t(u32, fmt->height, VIMC_FRAME_MIN_HEIGHT, + VIMC_FRAME_MAX_HEIGHT) & ~1; + + if (fmt->field == V4L2_FIELD_ANY) + fmt->field = sink_fmt_default.field; + + vimc_colorimetry_clamp(fmt); +} + +static int vimc_sca_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct vimc_sca_device *vsca = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *sink_fmt; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + /* Do not change the format while stream is on */ + if (vsca->src_frame) + return -EBUSY; + + sink_fmt = &vsca->sink_fmt; + } else { + sink_fmt = v4l2_subdev_get_try_format(sd, cfg, 0); + } + + /* + * Do not change the format of the source pad, + * it is propagated from the sink + */ + if (IS_SRC(fmt->pad)) { + fmt->format = *sink_fmt; + fmt->format.width = sink_fmt->width * sca_mult; + fmt->format.height = sink_fmt->height * sca_mult; + } else { + /* Set the new format in the sink pad */ + vimc_sca_adjust_sink_fmt(&fmt->format); + + dev_dbg(vsca->dev, "%s: sink format update: " + "old:%dx%d (0x%x, %d, %d, %d, %d) " + "new:%dx%d (0x%x, %d, %d, %d, %d)\n", vsca->sd.name, + /* old */ + sink_fmt->width, sink_fmt->height, sink_fmt->code, + sink_fmt->colorspace, sink_fmt->quantization, + sink_fmt->xfer_func, sink_fmt->ycbcr_enc, + /* new */ + fmt->format.width, fmt->format.height, fmt->format.code, + fmt->format.colorspace, fmt->format.quantization, + fmt->format.xfer_func, fmt->format.ycbcr_enc); + + *sink_fmt = fmt->format; + } + + return 0; +} + +static const struct v4l2_subdev_pad_ops vimc_sca_pad_ops = { + .init_cfg = vimc_sca_init_cfg, + .enum_mbus_code = vimc_sca_enum_mbus_code, + .enum_frame_size = vimc_sca_enum_frame_size, + .get_fmt = vimc_sca_get_fmt, + .set_fmt = vimc_sca_set_fmt, +}; + +static int vimc_sca_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct vimc_sca_device *vsca = v4l2_get_subdevdata(sd); + int ret; + + if (enable) { + const struct vimc_pix_map *vpix; + unsigned int frame_size; + + if (vsca->src_frame) + return 0; + + /* Save the bytes per pixel of the sink */ + vpix = vimc_pix_map_by_code(vsca->sink_fmt.code); + vsca->bpp = vpix->bpp; + + /* Calculate the width in bytes of the src frame */ + vsca->src_line_size = vsca->sink_fmt.width * + sca_mult * vsca->bpp; + + /* Calculate the frame size of the source pad */ + frame_size = vsca->src_line_size * vsca->sink_fmt.height * + sca_mult; + + /* Allocate the frame buffer. Use vmalloc to be able to + * allocate a large amount of memory + */ + vsca->src_frame = vmalloc(frame_size); + if (!vsca->src_frame) + return -ENOMEM; + + /* Turn the stream on in the subdevices directly connected */ + ret = vimc_pipeline_s_stream(&vsca->sd.entity, 1); + if (ret) { + vfree(vsca->src_frame); + vsca->src_frame = NULL; + return ret; + } + } else { + if (!vsca->src_frame) + return 0; + + /* Disable streaming from the pipe */ + ret = vimc_pipeline_s_stream(&vsca->sd.entity, 0); + if (ret) + return ret; + + vfree(vsca->src_frame); + vsca->src_frame = NULL; + } + + return 0; +} + +static struct v4l2_subdev_video_ops vimc_sca_video_ops = { + .s_stream = vimc_sca_s_stream, +}; + +static const struct v4l2_subdev_ops vimc_sca_ops = { + .pad = &vimc_sca_pad_ops, + .video = &vimc_sca_video_ops, +}; + +static void vimc_sca_fill_pix(u8 *const ptr, + const u8 *const pixel, + const unsigned int bpp) +{ + unsigned int i; + + /* copy the pixel to the pointer */ + for (i = 0; i < bpp; i++) + ptr[i] = pixel[i]; +} + +static void vimc_sca_scale_pix(const struct vimc_sca_device *const vsca, + const unsigned int lin, const unsigned int col, + const u8 *const sink_frame) +{ + unsigned int i, j, index; + const u8 *pixel; + + /* Point to the pixel value in position (lin, col) in the sink frame */ + index = VIMC_FRAME_INDEX(lin, col, + vsca->sink_fmt.width, + vsca->bpp); + pixel = &sink_frame[index]; + + dev_dbg(vsca->dev, + "sca: %s: --- scale_pix sink pos %dx%d, index %d ---\n", + vsca->sd.name, lin, col, index); + + /* point to the place we are going to put the first pixel + * in the scaled src frame + */ + index = VIMC_FRAME_INDEX(lin * sca_mult, col * sca_mult, + vsca->sink_fmt.width * sca_mult, vsca->bpp); + + dev_dbg(vsca->dev, "sca: %s: scale_pix src pos %dx%d, index %d\n", + vsca->sd.name, lin * sca_mult, col * sca_mult, index); + + /* Repeat this pixel mult times */ + for (i = 0; i < sca_mult; i++) { + /* Iterate through each beginning of a + * pixel repetition in a line + */ + for (j = 0; j < sca_mult * vsca->bpp; j += vsca->bpp) { + dev_dbg(vsca->dev, + "sca: %s: sca: scale_pix src pos %d\n", + vsca->sd.name, index + j); + + /* copy the pixel to the position index + j */ + vimc_sca_fill_pix(&vsca->src_frame[index + j], + pixel, vsca->bpp); + } + + /* move the index to the next line */ + index += vsca->src_line_size; + } +} + +static void vimc_sca_fill_src_frame(const struct vimc_sca_device *const vsca, + const u8 *const sink_frame) +{ + unsigned int i, j; + + /* Scale each pixel from the original sink frame */ + /* TODO: implement scale down, only scale up is supported for now */ + for (i = 0; i < vsca->sink_fmt.height; i++) + for (j = 0; j < vsca->sink_fmt.width; j++) + vimc_sca_scale_pix(vsca, i, j, sink_frame); +} + +static void vimc_sca_process_frame(struct vimc_ent_device *ved, + struct media_pad *sink, + const void *sink_frame) +{ + struct vimc_sca_device *vsca = container_of(ved, struct vimc_sca_device, + ved); + unsigned int i; + + /* If the stream in this node is not active, just return */ + if (!vsca->src_frame) + return; + + vimc_sca_fill_src_frame(vsca, sink_frame); + + /* Propagate the frame through all source pads */ + for (i = 1; i < vsca->sd.entity.num_pads; i++) { + struct media_pad *pad = &vsca->sd.entity.pads[i]; + + vimc_propagate_frame(pad, vsca->src_frame); + } +}; + +static void vimc_sca_comp_unbind(struct device *comp, struct device *master, + void *master_data) +{ + struct vimc_ent_device *ved = dev_get_drvdata(comp); + struct vimc_sca_device *vsca = container_of(ved, struct vimc_sca_device, + ved); + + vimc_ent_sd_unregister(ved, &vsca->sd); + kfree(vsca); +} + + +static int vimc_sca_comp_bind(struct device *comp, struct device *master, + void *master_data) +{ + struct v4l2_device *v4l2_dev = master_data; + struct vimc_platform_data *pdata = comp->platform_data; + struct vimc_sca_device *vsca; + int ret; + + /* Allocate the vsca struct */ + vsca = kzalloc(sizeof(*vsca), GFP_KERNEL); + if (!vsca) + return -ENOMEM; + + /* Initialize ved and sd */ + ret = vimc_ent_sd_register(&vsca->ved, &vsca->sd, v4l2_dev, + pdata->entity_name, + MEDIA_ENT_F_ATV_DECODER, 2, + (const unsigned long[2]) {MEDIA_PAD_FL_SINK, + MEDIA_PAD_FL_SOURCE}, + &vimc_sca_ops); + if (ret) { + kfree(vsca); + return ret; + } + + vsca->ved.process_frame = vimc_sca_process_frame; + dev_set_drvdata(comp, &vsca->ved); + vsca->dev = comp; + + /* Initialize the frame format */ + vsca->sink_fmt = sink_fmt_default; + + return 0; +} + +static const struct component_ops vimc_sca_comp_ops = { + .bind = vimc_sca_comp_bind, + .unbind = vimc_sca_comp_unbind, +}; + +static int vimc_sca_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &vimc_sca_comp_ops); +} + +static int vimc_sca_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vimc_sca_comp_ops); + + return 0; +} + +static struct platform_driver vimc_sca_pdrv = { + .probe = vimc_sca_probe, + .remove = vimc_sca_remove, + .driver = { + .name = VIMC_SCA_DRV_NAME, + }, +}; + +static const struct platform_device_id vimc_sca_driver_ids[] = { + { + .name = VIMC_SCA_DRV_NAME, + }, + { } +}; + +module_platform_driver(vimc_sca_pdrv); + +MODULE_DEVICE_TABLE(platform, vimc_sca_driver_ids); + +MODULE_DESCRIPTION("Virtual Media Controller Driver (VIMC) Scaler"); +MODULE_AUTHOR("Helen Mae Koike Fornazier <helen.fornazier@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/vimc/vimc-sensor.c b/drivers/media/platform/vimc/vimc-sensor.c index 591f6a4f8bd3..ebdbbe8c05ed 100644 --- a/drivers/media/platform/vimc/vimc-sensor.c +++ b/drivers/media/platform/vimc/vimc-sensor.c @@ -15,36 +15,64 @@ * */ +#include <linux/component.h> #include <linux/freezer.h> #include <linux/kthread.h> +#include <linux/module.h> +#include <linux/platform_device.h> #include <linux/v4l2-mediabus.h> #include <linux/vmalloc.h> #include <media/v4l2-subdev.h> +#include <media/v4l2-tpg.h> -#include "vimc-sensor.h" +#include "vimc-common.h" + +#define VIMC_SEN_DRV_NAME "vimc-sensor" struct vimc_sen_device { struct vimc_ent_device ved; struct v4l2_subdev sd; + struct device *dev; + struct tpg_data tpg; struct task_struct *kthread_sen; u8 *frame; /* The active format */ struct v4l2_mbus_framefmt mbus_format; - int frame_size; }; +static const struct v4l2_mbus_framefmt fmt_default = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_RGB888_1X24, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_DEFAULT, +}; + +static int vimc_sen_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +{ + unsigned int i; + + for (i = 0; i < sd->entity.num_pads; i++) { + struct v4l2_mbus_framefmt *mf; + + mf = v4l2_subdev_get_try_format(sd, cfg, i); + *mf = fmt_default; + } + + return 0; +} + static int vimc_sen_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_mbus_code_enum *code) { - struct vimc_sen_device *vsen = - container_of(sd, struct vimc_sen_device, sd); + const struct vimc_pix_map *vpix = vimc_pix_map_by_index(code->index); - /* TODO: Add support for other codes */ - if (code->index) + if (!vpix) return -EINVAL; - code->code = vsen->mbus_format.code; + code->code = vpix->code; return 0; } @@ -53,51 +81,123 @@ static int vimc_sen_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_frame_size_enum *fse) { - struct vimc_sen_device *vsen = - container_of(sd, struct vimc_sen_device, sd); + const struct vimc_pix_map *vpix; - /* TODO: Add support to other formats */ if (fse->index) return -EINVAL; - /* TODO: Add support for other codes */ - if (fse->code != vsen->mbus_format.code) + /* Only accept code in the pix map table */ + vpix = vimc_pix_map_by_code(fse->code); + if (!vpix) return -EINVAL; - fse->min_width = vsen->mbus_format.width; - fse->max_width = vsen->mbus_format.width; - fse->min_height = vsen->mbus_format.height; - fse->max_height = vsen->mbus_format.height; + fse->min_width = VIMC_FRAME_MIN_WIDTH; + fse->max_width = VIMC_FRAME_MAX_WIDTH; + fse->min_height = VIMC_FRAME_MIN_HEIGHT; + fse->max_height = VIMC_FRAME_MAX_HEIGHT; return 0; } static int vimc_sen_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *format) + struct v4l2_subdev_format *fmt) { struct vimc_sen_device *vsen = container_of(sd, struct vimc_sen_device, sd); - format->format = vsen->mbus_format; + fmt->format = fmt->which == V4L2_SUBDEV_FORMAT_TRY ? + *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) : + vsen->mbus_format; + + return 0; +} + +static void vimc_sen_tpg_s_format(struct vimc_sen_device *vsen) +{ + const struct vimc_pix_map *vpix = + vimc_pix_map_by_code(vsen->mbus_format.code); + + tpg_reset_source(&vsen->tpg, vsen->mbus_format.width, + vsen->mbus_format.height, vsen->mbus_format.field); + tpg_s_bytesperline(&vsen->tpg, 0, vsen->mbus_format.width * vpix->bpp); + tpg_s_buf_height(&vsen->tpg, vsen->mbus_format.height); + tpg_s_fourcc(&vsen->tpg, vpix->pixelformat); + /* TODO: add support for V4L2_FIELD_ALTERNATE */ + tpg_s_field(&vsen->tpg, vsen->mbus_format.field, false); + tpg_s_colorspace(&vsen->tpg, vsen->mbus_format.colorspace); + tpg_s_ycbcr_enc(&vsen->tpg, vsen->mbus_format.ycbcr_enc); + tpg_s_quantization(&vsen->tpg, vsen->mbus_format.quantization); + tpg_s_xfer_func(&vsen->tpg, vsen->mbus_format.xfer_func); +} + +static void vimc_sen_adjust_fmt(struct v4l2_mbus_framefmt *fmt) +{ + const struct vimc_pix_map *vpix; + + /* Only accept code in the pix map table */ + vpix = vimc_pix_map_by_code(fmt->code); + if (!vpix) + fmt->code = fmt_default.code; + + fmt->width = clamp_t(u32, fmt->width, VIMC_FRAME_MIN_WIDTH, + VIMC_FRAME_MAX_WIDTH) & ~1; + fmt->height = clamp_t(u32, fmt->height, VIMC_FRAME_MIN_HEIGHT, + VIMC_FRAME_MAX_HEIGHT) & ~1; + + /* TODO: add support for V4L2_FIELD_ALTERNATE */ + if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE) + fmt->field = fmt_default.field; + + vimc_colorimetry_clamp(fmt); +} + +static int vimc_sen_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct vimc_sen_device *vsen = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *mf; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + /* Do not change the format while stream is on */ + if (vsen->frame) + return -EBUSY; + + mf = &vsen->mbus_format; + } else { + mf = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + } + + /* Set the new format */ + vimc_sen_adjust_fmt(&fmt->format); + + dev_dbg(vsen->dev, "%s: format update: " + "old:%dx%d (0x%x, %d, %d, %d, %d) " + "new:%dx%d (0x%x, %d, %d, %d, %d)\n", vsen->sd.name, + /* old */ + mf->width, mf->height, mf->code, + mf->colorspace, mf->quantization, + mf->xfer_func, mf->ycbcr_enc, + /* new */ + fmt->format.width, fmt->format.height, fmt->format.code, + fmt->format.colorspace, fmt->format.quantization, + fmt->format.xfer_func, fmt->format.ycbcr_enc); + + *mf = fmt->format; return 0; } static const struct v4l2_subdev_pad_ops vimc_sen_pad_ops = { + .init_cfg = vimc_sen_init_cfg, .enum_mbus_code = vimc_sen_enum_mbus_code, .enum_frame_size = vimc_sen_enum_frame_size, .get_fmt = vimc_sen_get_fmt, - /* TODO: Add support to other formats */ - .set_fmt = vimc_sen_get_fmt, + .set_fmt = vimc_sen_set_fmt, }; -/* media operations */ -static const struct media_entity_operations vimc_sen_mops = { - .link_validate = v4l2_subdev_link_validate, -}; - -static int vimc_thread_sen(void *data) +static int vimc_sen_tpg_thread(void *data) { struct vimc_sen_device *vsen = data; unsigned int i; @@ -110,7 +210,7 @@ static int vimc_thread_sen(void *data) if (kthread_should_stop()) break; - memset(vsen->frame, 100, vsen->frame_size); + tpg_fill_plane_buffer(&vsen->tpg, 0, 0, vsen->frame); /* Send the frame to all source pads */ for (i = 0; i < vsen->sd.entity.num_pads; i++) @@ -132,50 +232,57 @@ static int vimc_sen_s_stream(struct v4l2_subdev *sd, int enable) if (enable) { const struct vimc_pix_map *vpix; + unsigned int frame_size; if (vsen->kthread_sen) - return -EINVAL; + /* tpg is already executing */ + return 0; /* Calculate the frame size */ vpix = vimc_pix_map_by_code(vsen->mbus_format.code); - vsen->frame_size = vsen->mbus_format.width * vpix->bpp * - vsen->mbus_format.height; + frame_size = vsen->mbus_format.width * vpix->bpp * + vsen->mbus_format.height; /* * Allocate the frame buffer. Use vmalloc to be able to * allocate a large amount of memory */ - vsen->frame = vmalloc(vsen->frame_size); + vsen->frame = vmalloc(frame_size); if (!vsen->frame) return -ENOMEM; + /* configure the test pattern generator */ + vimc_sen_tpg_s_format(vsen); + /* Initialize the image generator thread */ - vsen->kthread_sen = kthread_run(vimc_thread_sen, vsen, "%s-sen", - vsen->sd.v4l2_dev->name); + vsen->kthread_sen = kthread_run(vimc_sen_tpg_thread, vsen, + "%s-sen", vsen->sd.v4l2_dev->name); if (IS_ERR(vsen->kthread_sen)) { - dev_err(vsen->sd.v4l2_dev->dev, - "%s: kernel_thread() failed\n", vsen->sd.name); + dev_err(vsen->dev, "%s: kernel_thread() failed\n", + vsen->sd.name); vfree(vsen->frame); vsen->frame = NULL; return PTR_ERR(vsen->kthread_sen); } } else { if (!vsen->kthread_sen) - return -EINVAL; + return 0; /* Stop image generator */ ret = kthread_stop(vsen->kthread_sen); - vsen->kthread_sen = NULL; + if (ret) + return ret; + vsen->kthread_sen = NULL; vfree(vsen->frame); vsen->frame = NULL; - return ret; + return 0; } return 0; } -struct v4l2_subdev_video_ops vimc_sen_video_ops = { +static struct v4l2_subdev_video_ops vimc_sen_video_ops = { .s_stream = vimc_sen_s_stream, }; @@ -184,93 +291,99 @@ static const struct v4l2_subdev_ops vimc_sen_ops = { .video = &vimc_sen_video_ops, }; -static void vimc_sen_destroy(struct vimc_ent_device *ved) +static void vimc_sen_comp_unbind(struct device *comp, struct device *master, + void *master_data) { + struct vimc_ent_device *ved = dev_get_drvdata(comp); struct vimc_sen_device *vsen = container_of(ved, struct vimc_sen_device, ved); - v4l2_device_unregister_subdev(&vsen->sd); - media_entity_cleanup(ved->ent); + vimc_ent_sd_unregister(ved, &vsen->sd); + tpg_free(&vsen->tpg); kfree(vsen); } -struct vimc_ent_device *vimc_sen_create(struct v4l2_device *v4l2_dev, - const char *const name, - u16 num_pads, - const unsigned long *pads_flag) +static int vimc_sen_comp_bind(struct device *comp, struct device *master, + void *master_data) { + struct v4l2_device *v4l2_dev = master_data; + struct vimc_platform_data *pdata = comp->platform_data; struct vimc_sen_device *vsen; - unsigned int i; int ret; - /* NOTE: a sensor node may be created with more then one pad */ - if (!name || !num_pads || !pads_flag) - return ERR_PTR(-EINVAL); - - /* check if all pads are sources */ - for (i = 0; i < num_pads; i++) - if (!(pads_flag[i] & MEDIA_PAD_FL_SOURCE)) - return ERR_PTR(-EINVAL); - /* Allocate the vsen struct */ vsen = kzalloc(sizeof(*vsen), GFP_KERNEL); if (!vsen) - return ERR_PTR(-ENOMEM); - - /* Allocate the pads */ - vsen->ved.pads = vimc_pads_init(num_pads, pads_flag); - if (IS_ERR(vsen->ved.pads)) { - ret = PTR_ERR(vsen->ved.pads); + return -ENOMEM; + + /* Initialize ved and sd */ + ret = vimc_ent_sd_register(&vsen->ved, &vsen->sd, v4l2_dev, + pdata->entity_name, + MEDIA_ENT_F_ATV_DECODER, 1, + (const unsigned long[1]) {MEDIA_PAD_FL_SOURCE}, + &vimc_sen_ops); + if (ret) goto err_free_vsen; - } - - /* Fill the vimc_ent_device struct */ - vsen->ved.destroy = vimc_sen_destroy; - vsen->ved.ent = &vsen->sd.entity; - /* Initialize the subdev */ - v4l2_subdev_init(&vsen->sd, &vimc_sen_ops); - vsen->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; - vsen->sd.entity.ops = &vimc_sen_mops; - vsen->sd.owner = THIS_MODULE; - strlcpy(vsen->sd.name, name, sizeof(vsen->sd.name)); - v4l2_set_subdevdata(&vsen->sd, &vsen->ved); + dev_set_drvdata(comp, &vsen->ved); + vsen->dev = comp; - /* Expose this subdev to user space */ - vsen->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + /* Initialize the frame format */ + vsen->mbus_format = fmt_default; - /* Initialize the media entity */ - ret = media_entity_pads_init(&vsen->sd.entity, - num_pads, vsen->ved.pads); + /* Initialize the test pattern generator */ + tpg_init(&vsen->tpg, vsen->mbus_format.width, + vsen->mbus_format.height); + ret = tpg_alloc(&vsen->tpg, VIMC_FRAME_MAX_WIDTH); if (ret) - goto err_clean_pads; - - /* Set the active frame format (this is hardcoded for now) */ - vsen->mbus_format.width = 640; - vsen->mbus_format.height = 480; - vsen->mbus_format.code = MEDIA_BUS_FMT_RGB888_1X24; - vsen->mbus_format.field = V4L2_FIELD_NONE; - vsen->mbus_format.colorspace = V4L2_COLORSPACE_SRGB; - vsen->mbus_format.quantization = V4L2_QUANTIZATION_FULL_RANGE; - vsen->mbus_format.xfer_func = V4L2_XFER_FUNC_SRGB; - - /* Register the subdev with the v4l2 and the media framework */ - ret = v4l2_device_register_subdev(v4l2_dev, &vsen->sd); - if (ret) { - dev_err(vsen->sd.v4l2_dev->dev, - "%s: subdev register failed (err=%d)\n", - vsen->sd.name, ret); - goto err_clean_m_ent; - } + goto err_unregister_ent_sd; - return &vsen->ved; + return 0; -err_clean_m_ent: - media_entity_cleanup(&vsen->sd.entity); -err_clean_pads: - vimc_pads_cleanup(vsen->ved.pads); +err_unregister_ent_sd: + vimc_ent_sd_unregister(&vsen->ved, &vsen->sd); err_free_vsen: kfree(vsen); - return ERR_PTR(ret); + return ret; } + +static const struct component_ops vimc_sen_comp_ops = { + .bind = vimc_sen_comp_bind, + .unbind = vimc_sen_comp_unbind, +}; + +static int vimc_sen_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &vimc_sen_comp_ops); +} + +static int vimc_sen_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vimc_sen_comp_ops); + + return 0; +} + +static struct platform_driver vimc_sen_pdrv = { + .probe = vimc_sen_probe, + .remove = vimc_sen_remove, + .driver = { + .name = VIMC_SEN_DRV_NAME, + }, +}; + +static const struct platform_device_id vimc_sen_driver_ids[] = { + { + .name = VIMC_SEN_DRV_NAME, + }, + { } +}; + +module_platform_driver(vimc_sen_pdrv); + +MODULE_DEVICE_TABLE(platform, vimc_sen_driver_ids); + +MODULE_DESCRIPTION("Virtual Media Controller Driver (VIMC) Sensor"); +MODULE_AUTHOR("Helen Mae Koike Fornazier <helen.fornazier@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/vimc/vimc-sensor.h b/drivers/media/platform/vimc/vimc-sensor.h deleted file mode 100644 index 505310e8aeb7..000000000000 --- a/drivers/media/platform/vimc/vimc-sensor.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * vimc-sensor.h Virtual Media Controller Driver - * - * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef _VIMC_SENSOR_H_ -#define _VIMC_SENSOR_H_ - -#include "vimc-core.h" - -struct vimc_ent_device *vimc_sen_create(struct v4l2_device *v4l2_dev, - const char *const name, - u16 num_pads, - const unsigned long *pads_flag); - -#endif diff --git a/drivers/media/platform/vivid/vivid-cec.c b/drivers/media/platform/vivid/vivid-cec.c index 653f4099f737..e15705758969 100644 --- a/drivers/media/platform/vivid/vivid-cec.c +++ b/drivers/media/platform/vivid/vivid-cec.c @@ -34,7 +34,7 @@ void vivid_cec_bus_free_work(struct vivid_dev *dev) cancel_delayed_work_sync(&cw->work); spin_lock(&dev->cec_slock); list_del(&cw->list); - cec_transmit_done(cw->adap, CEC_TX_STATUS_LOW_DRIVE, 0, 0, 1, 0); + cec_transmit_attempt_done(cw->adap, CEC_TX_STATUS_LOW_DRIVE); kfree(cw); } spin_unlock(&dev->cec_slock); @@ -84,7 +84,7 @@ static void vivid_cec_xfer_done_worker(struct work_struct *work) dev->cec_xfer_start_jiffies = 0; list_del(&cw->list); spin_unlock(&dev->cec_slock); - cec_transmit_done(cw->adap, cw->tx_status, 0, valid_dest ? 0 : 1, 0, 0); + cec_transmit_attempt_done(cw->adap, cw->tx_status); /* Broadcast message */ if (adap != dev->cec_rx_adap) @@ -105,7 +105,7 @@ static void vivid_cec_xfer_try_worker(struct work_struct *work) if (dev->cec_xfer_time_jiffies) { list_del(&cw->list); spin_unlock(&dev->cec_slock); - cec_transmit_done(cw->adap, CEC_TX_STATUS_ARB_LOST, 1, 0, 0, 0); + cec_transmit_attempt_done(cw->adap, CEC_TX_STATUS_ARB_LOST); kfree(cw); } else { INIT_DELAYED_WORK(&cw->work, vivid_cec_xfer_done_worker); diff --git a/drivers/media/platform/xilinx/Kconfig b/drivers/media/platform/xilinx/Kconfig index 84bae795b70d..a5d21b7c6e0b 100644 --- a/drivers/media/platform/xilinx/Kconfig +++ b/drivers/media/platform/xilinx/Kconfig @@ -2,6 +2,7 @@ config VIDEO_XILINX tristate "Xilinx Video IP (EXPERIMENTAL)" depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API && OF && HAS_DMA select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE ---help--- Driver for Xilinx Video IP Pipelines diff --git a/drivers/media/platform/xilinx/xilinx-vipp.c b/drivers/media/platform/xilinx/xilinx-vipp.c index feb3b2f1d874..ac4704388920 100644 --- a/drivers/media/platform/xilinx/xilinx-vipp.c +++ b/drivers/media/platform/xilinx/xilinx-vipp.c @@ -22,7 +22,7 @@ #include <media/v4l2-async.h> #include <media/v4l2-common.h> #include <media/v4l2-device.h> -#include <media/v4l2-of.h> +#include <media/v4l2-fwnode.h> #include "xilinx-dma.h" #include "xilinx-vipp.h" @@ -74,7 +74,7 @@ static int xvip_graph_build_one(struct xvip_composite_device *xdev, struct media_pad *local_pad; struct media_pad *remote_pad; struct xvip_graph_entity *ent; - struct v4l2_of_link link; + struct v4l2_fwnode_link link; struct device_node *ep = NULL; struct device_node *next; int ret = 0; @@ -92,7 +92,7 @@ static int xvip_graph_build_one(struct xvip_composite_device *xdev, dev_dbg(xdev->dev, "processing endpoint %s\n", ep->full_name); - ret = v4l2_of_parse_link(ep, &link); + ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link); if (ret < 0) { dev_err(xdev->dev, "failed to parse link for %s\n", ep->full_name); @@ -103,9 +103,10 @@ static int xvip_graph_build_one(struct xvip_composite_device *xdev, * the link. */ if (link.local_port >= local->num_pads) { - dev_err(xdev->dev, "invalid port number %u on %s\n", - link.local_port, link.local_node->full_name); - v4l2_of_put_link(&link); + dev_err(xdev->dev, "invalid port number %u for %s\n", + link.local_port, + to_of_node(link.local_node)->full_name); + v4l2_fwnode_put_link(&link); ret = -EINVAL; break; } @@ -114,25 +115,28 @@ static int xvip_graph_build_one(struct xvip_composite_device *xdev, if (local_pad->flags & MEDIA_PAD_FL_SINK) { dev_dbg(xdev->dev, "skipping sink port %s:%u\n", - link.local_node->full_name, link.local_port); - v4l2_of_put_link(&link); + to_of_node(link.local_node)->full_name, + link.local_port); + v4l2_fwnode_put_link(&link); continue; } /* Skip DMA engines, they will be processed separately. */ - if (link.remote_node == xdev->dev->of_node) { + if (link.remote_node == of_fwnode_handle(xdev->dev->of_node)) { dev_dbg(xdev->dev, "skipping DMA port %s:%u\n", - link.local_node->full_name, link.local_port); - v4l2_of_put_link(&link); + to_of_node(link.local_node)->full_name, + link.local_port); + v4l2_fwnode_put_link(&link); continue; } /* Find the remote entity. */ - ent = xvip_graph_find_entity(xdev, link.remote_node); + ent = xvip_graph_find_entity(xdev, + to_of_node(link.remote_node)); if (ent == NULL) { dev_err(xdev->dev, "no entity found for %s\n", - link.remote_node->full_name); - v4l2_of_put_link(&link); + to_of_node(link.remote_node)->full_name); + v4l2_fwnode_put_link(&link); ret = -ENODEV; break; } @@ -141,15 +145,16 @@ static int xvip_graph_build_one(struct xvip_composite_device *xdev, if (link.remote_port >= remote->num_pads) { dev_err(xdev->dev, "invalid port number %u on %s\n", - link.remote_port, link.remote_node->full_name); - v4l2_of_put_link(&link); + link.remote_port, + to_of_node(link.remote_node)->full_name); + v4l2_fwnode_put_link(&link); ret = -EINVAL; break; } remote_pad = &remote->pads[link.remote_port]; - v4l2_of_put_link(&link); + v4l2_fwnode_put_link(&link); /* Create the media link. */ dev_dbg(xdev->dev, "creating %s:%u -> %s:%u link\n", @@ -194,7 +199,7 @@ static int xvip_graph_build_dma(struct xvip_composite_device *xdev) struct media_pad *source_pad; struct media_pad *sink_pad; struct xvip_graph_entity *ent; - struct v4l2_of_link link; + struct v4l2_fwnode_link link; struct device_node *ep = NULL; struct device_node *next; struct xvip_dma *dma; @@ -213,7 +218,7 @@ static int xvip_graph_build_dma(struct xvip_composite_device *xdev) dev_dbg(xdev->dev, "processing endpoint %s\n", ep->full_name); - ret = v4l2_of_parse_link(ep, &link); + ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link); if (ret < 0) { dev_err(xdev->dev, "failed to parse link for %s\n", ep->full_name); @@ -225,7 +230,7 @@ static int xvip_graph_build_dma(struct xvip_composite_device *xdev) if (dma == NULL) { dev_err(xdev->dev, "no DMA engine found for port %u\n", link.local_port); - v4l2_of_put_link(&link); + v4l2_fwnode_put_link(&link); ret = -EINVAL; break; } @@ -234,19 +239,21 @@ static int xvip_graph_build_dma(struct xvip_composite_device *xdev) dma->video.name); /* Find the remote entity. */ - ent = xvip_graph_find_entity(xdev, link.remote_node); + ent = xvip_graph_find_entity(xdev, + to_of_node(link.remote_node)); if (ent == NULL) { dev_err(xdev->dev, "no entity found for %s\n", - link.remote_node->full_name); - v4l2_of_put_link(&link); + to_of_node(link.remote_node)->full_name); + 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 %s\n", - link.remote_port, link.remote_node->full_name); - v4l2_of_put_link(&link); + link.remote_port, + to_of_node(link.remote_node)->full_name); + v4l2_fwnode_put_link(&link); ret = -EINVAL; break; } @@ -263,7 +270,7 @@ static int xvip_graph_build_dma(struct xvip_composite_device *xdev) sink_pad = &dma->pad; } - v4l2_of_put_link(&link); + v4l2_fwnode_put_link(&link); /* Create the media link. */ dev_dbg(xdev->dev, "creating %s:%u -> %s:%u link\n", @@ -383,8 +390,8 @@ static int xvip_graph_parse_one(struct xvip_composite_device *xdev, } entity->node = remote; - entity->asd.match_type = V4L2_ASYNC_MATCH_OF; - entity->asd.match.of.node = remote; + entity->asd.match_type = V4L2_ASYNC_MATCH_FWNODE; + entity->asd.match.fwnode.fwnode = of_fwnode_handle(remote); list_add_tail(&entity->list, &xdev->entities); xdev->num_subdevs++; } |