diff options
author | Yangbo Lu <yangbo.lu@nxp.com> | 2020-04-10 10:47:05 +0800 |
---|---|---|
committer | Petr Štetiar <ynezz@true.cz> | 2020-05-07 12:53:06 +0200 |
commit | cddd4591404fb4c53dc0b3c0b15b942cdbed4356 (patch) | |
tree | 392c1179de46b0f804e3789edca19069b64e6b44 /target/linux/layerscape/patches-5.4/805-display-0049-Revert-gpu-Move-ipu-v3-to-imx-folder.patch | |
parent | d1d2c0b5579ea4f69a42246c9318539d61ba1999 (diff) | |
download | upstream-cddd4591404fb4c53dc0b3c0b15b942cdbed4356.tar.gz upstream-cddd4591404fb4c53dc0b3c0b15b942cdbed4356.tar.bz2 upstream-cddd4591404fb4c53dc0b3c0b15b942cdbed4356.zip |
layerscape: add patches-5.4
Add patches for linux-5.4. The patches are from NXP LSDK-20.04 release
which was tagged LSDK-20.04-V5.4.
https://source.codeaurora.org/external/qoriq/qoriq-components/linux/
For boards LS1021A-IOT, and Traverse-LS1043 which are not involved in
LSDK, port the dts patches from 4.14.
The patches are sorted into the following categories:
301-arch-xxxx
302-dts-xxxx
303-core-xxxx
701-net-xxxx
801-audio-xxxx
802-can-xxxx
803-clock-xxxx
804-crypto-xxxx
805-display-xxxx
806-dma-xxxx
807-gpio-xxxx
808-i2c-xxxx
809-jailhouse-xxxx
810-keys-xxxx
811-kvm-xxxx
812-pcie-xxxx
813-pm-xxxx
814-qe-xxxx
815-sata-xxxx
816-sdhc-xxxx
817-spi-xxxx
818-thermal-xxxx
819-uart-xxxx
820-usb-xxxx
821-vfio-xxxx
Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com>
Diffstat (limited to 'target/linux/layerscape/patches-5.4/805-display-0049-Revert-gpu-Move-ipu-v3-to-imx-folder.patch')
-rw-r--r-- | target/linux/layerscape/patches-5.4/805-display-0049-Revert-gpu-Move-ipu-v3-to-imx-folder.patch | 19991 |
1 files changed, 19991 insertions, 0 deletions
diff --git a/target/linux/layerscape/patches-5.4/805-display-0049-Revert-gpu-Move-ipu-v3-to-imx-folder.patch b/target/linux/layerscape/patches-5.4/805-display-0049-Revert-gpu-Move-ipu-v3-to-imx-folder.patch new file mode 100644 index 0000000000..1c1886b4f9 --- /dev/null +++ b/target/linux/layerscape/patches-5.4/805-display-0049-Revert-gpu-Move-ipu-v3-to-imx-folder.patch @@ -0,0 +1,19991 @@ +From 9a941f832d8b88fb29b40e4b3f9c2b42a8a9b000 Mon Sep 17 00:00:00 2001 +From: Yangbo Lu <yangbo.lu@nxp.com> +Date: Mon, 2 Mar 2020 13:46:13 +0800 +Subject: [PATCH] Revert "gpu: Move ipu-v3 to imx folder" + +This reverts commit 9da4beb1c9ce90f97c8d3b5c6254da576746d2cd. +--- + drivers/gpu/Makefile | 2 +- + drivers/gpu/imx/Kconfig | 1 - + drivers/gpu/imx/Makefile | 1 - + drivers/gpu/imx/ipu-v3/Kconfig | 11 - + drivers/gpu/imx/ipu-v3/Makefile | 10 - + drivers/gpu/imx/ipu-v3/ipu-common.c | 1565 ------------------ + drivers/gpu/imx/ipu-v3/ipu-cpmem.c | 976 ----------- + drivers/gpu/imx/ipu-v3/ipu-csi.c | 821 --------- + drivers/gpu/imx/ipu-v3/ipu-dc.c | 420 ----- + drivers/gpu/imx/ipu-v3/ipu-di.c | 745 --------- + drivers/gpu/imx/ipu-v3/ipu-dmfc.c | 214 --- + drivers/gpu/imx/ipu-v3/ipu-dp.c | 357 ---- + drivers/gpu/imx/ipu-v3/ipu-ic.c | 761 --------- + drivers/gpu/imx/ipu-v3/ipu-image-convert.c | 2475 ---------------------------- + drivers/gpu/imx/ipu-v3/ipu-pre.c | 346 ---- + drivers/gpu/imx/ipu-v3/ipu-prg.c | 483 ------ + drivers/gpu/imx/ipu-v3/ipu-prv.h | 274 --- + drivers/gpu/imx/ipu-v3/ipu-smfc.c | 202 --- + drivers/gpu/imx/ipu-v3/ipu-vdi.c | 234 --- + drivers/gpu/ipu-v3/Kconfig | 11 + + drivers/gpu/ipu-v3/Makefile | 10 + + drivers/gpu/ipu-v3/ipu-common.c | 1565 ++++++++++++++++++ + drivers/gpu/ipu-v3/ipu-cpmem.c | 976 +++++++++++ + drivers/gpu/ipu-v3/ipu-csi.c | 821 +++++++++ + drivers/gpu/ipu-v3/ipu-dc.c | 420 +++++ + drivers/gpu/ipu-v3/ipu-di.c | 745 +++++++++ + drivers/gpu/ipu-v3/ipu-dmfc.c | 214 +++ + drivers/gpu/ipu-v3/ipu-dp.c | 357 ++++ + drivers/gpu/ipu-v3/ipu-ic.c | 761 +++++++++ + drivers/gpu/ipu-v3/ipu-image-convert.c | 2475 ++++++++++++++++++++++++++++ + drivers/gpu/ipu-v3/ipu-pre.c | 346 ++++ + drivers/gpu/ipu-v3/ipu-prg.c | 483 ++++++ + drivers/gpu/ipu-v3/ipu-prv.h | 274 +++ + drivers/gpu/ipu-v3/ipu-smfc.c | 202 +++ + drivers/gpu/ipu-v3/ipu-vdi.c | 234 +++ + drivers/video/Kconfig | 2 +- + 36 files changed, 9896 insertions(+), 9898 deletions(-) + delete mode 100644 drivers/gpu/imx/Kconfig + delete mode 100644 drivers/gpu/imx/Makefile + delete mode 100644 drivers/gpu/imx/ipu-v3/Kconfig + delete mode 100644 drivers/gpu/imx/ipu-v3/Makefile + delete mode 100644 drivers/gpu/imx/ipu-v3/ipu-common.c + delete mode 100644 drivers/gpu/imx/ipu-v3/ipu-cpmem.c + delete mode 100644 drivers/gpu/imx/ipu-v3/ipu-csi.c + delete mode 100644 drivers/gpu/imx/ipu-v3/ipu-dc.c + delete mode 100644 drivers/gpu/imx/ipu-v3/ipu-di.c + delete mode 100644 drivers/gpu/imx/ipu-v3/ipu-dmfc.c + delete mode 100644 drivers/gpu/imx/ipu-v3/ipu-dp.c + delete mode 100644 drivers/gpu/imx/ipu-v3/ipu-ic.c + delete mode 100644 drivers/gpu/imx/ipu-v3/ipu-image-convert.c + delete mode 100644 drivers/gpu/imx/ipu-v3/ipu-pre.c + delete mode 100644 drivers/gpu/imx/ipu-v3/ipu-prg.c + delete mode 100644 drivers/gpu/imx/ipu-v3/ipu-prv.h + delete mode 100644 drivers/gpu/imx/ipu-v3/ipu-smfc.c + delete mode 100644 drivers/gpu/imx/ipu-v3/ipu-vdi.c + create mode 100644 drivers/gpu/ipu-v3/Kconfig + create mode 100644 drivers/gpu/ipu-v3/Makefile + create mode 100644 drivers/gpu/ipu-v3/ipu-common.c + create mode 100644 drivers/gpu/ipu-v3/ipu-cpmem.c + create mode 100644 drivers/gpu/ipu-v3/ipu-csi.c + create mode 100644 drivers/gpu/ipu-v3/ipu-dc.c + create mode 100644 drivers/gpu/ipu-v3/ipu-di.c + create mode 100644 drivers/gpu/ipu-v3/ipu-dmfc.c + create mode 100644 drivers/gpu/ipu-v3/ipu-dp.c + create mode 100644 drivers/gpu/ipu-v3/ipu-ic.c + create mode 100644 drivers/gpu/ipu-v3/ipu-image-convert.c + create mode 100644 drivers/gpu/ipu-v3/ipu-pre.c + create mode 100644 drivers/gpu/ipu-v3/ipu-prg.c + create mode 100644 drivers/gpu/ipu-v3/ipu-prv.h + create mode 100644 drivers/gpu/ipu-v3/ipu-smfc.c + create mode 100644 drivers/gpu/ipu-v3/ipu-vdi.c + +--- a/drivers/gpu/Makefile ++++ b/drivers/gpu/Makefile +@@ -3,5 +3,5 @@ + # taken to initialize them in the correct order. Link order is the only way + # to ensure this currently. + obj-$(CONFIG_TEGRA_HOST1X) += host1x/ +-obj-y += imx/ + obj-y += drm/ vga/ ++obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/ +--- a/drivers/gpu/imx/Kconfig ++++ /dev/null +@@ -1 +0,0 @@ +-source "drivers/gpu/imx/ipu-v3/Kconfig" +--- a/drivers/gpu/imx/Makefile ++++ /dev/null +@@ -1 +0,0 @@ +-obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/ +--- a/drivers/gpu/imx/ipu-v3/Kconfig ++++ /dev/null +@@ -1,11 +0,0 @@ +-# SPDX-License-Identifier: GPL-2.0-only +-config IMX_IPUV3_CORE +- tristate "IPUv3 core support" +- depends on SOC_IMX5 || SOC_IMX6Q || ARCH_MULTIPLATFORM || COMPILE_TEST +- depends on DRM || !DRM # if DRM=m, this can't be 'y' +- select BITREVERSE +- select GENERIC_ALLOCATOR if DRM +- select GENERIC_IRQ_CHIP +- help +- Choose this if you have a i.MX5/6 system and want to use the Image +- Processing Unit. This option only enables IPU base support. +--- a/drivers/gpu/imx/ipu-v3/Makefile ++++ /dev/null +@@ -1,10 +0,0 @@ +-# SPDX-License-Identifier: GPL-2.0 +-obj-$(CONFIG_IMX_IPUV3_CORE) += imx-ipu-v3.o +- +-imx-ipu-v3-objs := ipu-common.o ipu-cpmem.o ipu-csi.o ipu-dc.o ipu-di.o \ +- ipu-dp.o ipu-dmfc.o ipu-ic.o ipu-ic-csc.o \ +- ipu-image-convert.o ipu-smfc.o ipu-vdi.o +- +-ifdef CONFIG_DRM +- imx-ipu-v3-objs += ipu-pre.o ipu-prg.o +-endif +--- a/drivers/gpu/imx/ipu-v3/ipu-common.c ++++ /dev/null +@@ -1,1565 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-or-later +-/* +- * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> +- * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. +- */ +-#include <linux/module.h> +-#include <linux/export.h> +-#include <linux/types.h> +-#include <linux/reset.h> +-#include <linux/platform_device.h> +-#include <linux/err.h> +-#include <linux/spinlock.h> +-#include <linux/delay.h> +-#include <linux/interrupt.h> +-#include <linux/io.h> +-#include <linux/clk.h> +-#include <linux/list.h> +-#include <linux/irq.h> +-#include <linux/irqchip/chained_irq.h> +-#include <linux/irqdomain.h> +-#include <linux/of_device.h> +-#include <linux/of_graph.h> +- +-#include <drm/drm_fourcc.h> +- +-#include <video/imx-ipu-v3.h> +-#include "ipu-prv.h" +- +-static inline u32 ipu_cm_read(struct ipu_soc *ipu, unsigned offset) +-{ +- return readl(ipu->cm_reg + offset); +-} +- +-static inline void ipu_cm_write(struct ipu_soc *ipu, u32 value, unsigned offset) +-{ +- writel(value, ipu->cm_reg + offset); +-} +- +-int ipu_get_num(struct ipu_soc *ipu) +-{ +- return ipu->id; +-} +-EXPORT_SYMBOL_GPL(ipu_get_num); +- +-void ipu_srm_dp_update(struct ipu_soc *ipu, bool sync) +-{ +- u32 val; +- +- val = ipu_cm_read(ipu, IPU_SRM_PRI2); +- val &= ~DP_S_SRM_MODE_MASK; +- val |= sync ? DP_S_SRM_MODE_NEXT_FRAME : +- DP_S_SRM_MODE_NOW; +- ipu_cm_write(ipu, val, IPU_SRM_PRI2); +-} +-EXPORT_SYMBOL_GPL(ipu_srm_dp_update); +- +-enum ipu_color_space ipu_drm_fourcc_to_colorspace(u32 drm_fourcc) +-{ +- switch (drm_fourcc) { +- case DRM_FORMAT_ARGB1555: +- case DRM_FORMAT_ABGR1555: +- case DRM_FORMAT_RGBA5551: +- case DRM_FORMAT_BGRA5551: +- case DRM_FORMAT_RGB565: +- case DRM_FORMAT_BGR565: +- case DRM_FORMAT_RGB888: +- case DRM_FORMAT_BGR888: +- case DRM_FORMAT_ARGB4444: +- case DRM_FORMAT_XRGB8888: +- case DRM_FORMAT_XBGR8888: +- case DRM_FORMAT_RGBX8888: +- case DRM_FORMAT_BGRX8888: +- case DRM_FORMAT_ARGB8888: +- case DRM_FORMAT_ABGR8888: +- case DRM_FORMAT_RGBA8888: +- case DRM_FORMAT_BGRA8888: +- case DRM_FORMAT_RGB565_A8: +- case DRM_FORMAT_BGR565_A8: +- case DRM_FORMAT_RGB888_A8: +- case DRM_FORMAT_BGR888_A8: +- case DRM_FORMAT_RGBX8888_A8: +- case DRM_FORMAT_BGRX8888_A8: +- return IPUV3_COLORSPACE_RGB; +- case DRM_FORMAT_YUYV: +- case DRM_FORMAT_UYVY: +- case DRM_FORMAT_YUV420: +- case DRM_FORMAT_YVU420: +- case DRM_FORMAT_YUV422: +- case DRM_FORMAT_YVU422: +- case DRM_FORMAT_YUV444: +- case DRM_FORMAT_YVU444: +- case DRM_FORMAT_NV12: +- case DRM_FORMAT_NV21: +- case DRM_FORMAT_NV16: +- case DRM_FORMAT_NV61: +- return IPUV3_COLORSPACE_YUV; +- default: +- return IPUV3_COLORSPACE_UNKNOWN; +- } +-} +-EXPORT_SYMBOL_GPL(ipu_drm_fourcc_to_colorspace); +- +-enum ipu_color_space ipu_pixelformat_to_colorspace(u32 pixelformat) +-{ +- switch (pixelformat) { +- case V4L2_PIX_FMT_YUV420: +- case V4L2_PIX_FMT_YVU420: +- case V4L2_PIX_FMT_YUV422P: +- case V4L2_PIX_FMT_UYVY: +- case V4L2_PIX_FMT_YUYV: +- case V4L2_PIX_FMT_NV12: +- case V4L2_PIX_FMT_NV21: +- case V4L2_PIX_FMT_NV16: +- case V4L2_PIX_FMT_NV61: +- return IPUV3_COLORSPACE_YUV; +- case V4L2_PIX_FMT_RGB565: +- case V4L2_PIX_FMT_BGR24: +- case V4L2_PIX_FMT_RGB24: +- case V4L2_PIX_FMT_ABGR32: +- case V4L2_PIX_FMT_XBGR32: +- case V4L2_PIX_FMT_BGRA32: +- case V4L2_PIX_FMT_BGRX32: +- case V4L2_PIX_FMT_RGBA32: +- case V4L2_PIX_FMT_RGBX32: +- case V4L2_PIX_FMT_ARGB32: +- case V4L2_PIX_FMT_XRGB32: +- return IPUV3_COLORSPACE_RGB; +- default: +- return IPUV3_COLORSPACE_UNKNOWN; +- } +-} +-EXPORT_SYMBOL_GPL(ipu_pixelformat_to_colorspace); +- +-bool ipu_pixelformat_is_planar(u32 pixelformat) +-{ +- switch (pixelformat) { +- case V4L2_PIX_FMT_YUV420: +- case V4L2_PIX_FMT_YVU420: +- case V4L2_PIX_FMT_YUV422P: +- case V4L2_PIX_FMT_NV12: +- case V4L2_PIX_FMT_NV21: +- case V4L2_PIX_FMT_NV16: +- case V4L2_PIX_FMT_NV61: +- return true; +- } +- +- return false; +-} +-EXPORT_SYMBOL_GPL(ipu_pixelformat_is_planar); +- +-enum ipu_color_space ipu_mbus_code_to_colorspace(u32 mbus_code) +-{ +- switch (mbus_code & 0xf000) { +- case 0x1000: +- return IPUV3_COLORSPACE_RGB; +- case 0x2000: +- return IPUV3_COLORSPACE_YUV; +- default: +- return IPUV3_COLORSPACE_UNKNOWN; +- } +-} +-EXPORT_SYMBOL_GPL(ipu_mbus_code_to_colorspace); +- +-int ipu_stride_to_bytes(u32 pixel_stride, u32 pixelformat) +-{ +- switch (pixelformat) { +- case V4L2_PIX_FMT_YUV420: +- case V4L2_PIX_FMT_YVU420: +- case V4L2_PIX_FMT_YUV422P: +- case V4L2_PIX_FMT_NV12: +- case V4L2_PIX_FMT_NV21: +- case V4L2_PIX_FMT_NV16: +- case V4L2_PIX_FMT_NV61: +- /* +- * for the planar YUV formats, the stride passed to +- * cpmem must be the stride in bytes of the Y plane. +- * And all the planar YUV formats have an 8-bit +- * Y component. +- */ +- return (8 * pixel_stride) >> 3; +- case V4L2_PIX_FMT_RGB565: +- case V4L2_PIX_FMT_YUYV: +- case V4L2_PIX_FMT_UYVY: +- return (16 * pixel_stride) >> 3; +- case V4L2_PIX_FMT_BGR24: +- case V4L2_PIX_FMT_RGB24: +- return (24 * pixel_stride) >> 3; +- case V4L2_PIX_FMT_BGR32: +- case V4L2_PIX_FMT_RGB32: +- case V4L2_PIX_FMT_XBGR32: +- case V4L2_PIX_FMT_XRGB32: +- return (32 * pixel_stride) >> 3; +- default: +- break; +- } +- +- return -EINVAL; +-} +-EXPORT_SYMBOL_GPL(ipu_stride_to_bytes); +- +-int ipu_degrees_to_rot_mode(enum ipu_rotate_mode *mode, int degrees, +- bool hflip, bool vflip) +-{ +- u32 r90, vf, hf; +- +- switch (degrees) { +- case 0: +- vf = hf = r90 = 0; +- break; +- case 90: +- vf = hf = 0; +- r90 = 1; +- break; +- case 180: +- vf = hf = 1; +- r90 = 0; +- break; +- case 270: +- vf = hf = r90 = 1; +- break; +- default: +- return -EINVAL; +- } +- +- hf ^= (u32)hflip; +- vf ^= (u32)vflip; +- +- *mode = (enum ipu_rotate_mode)((r90 << 2) | (hf << 1) | vf); +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_degrees_to_rot_mode); +- +-int ipu_rot_mode_to_degrees(int *degrees, enum ipu_rotate_mode mode, +- bool hflip, bool vflip) +-{ +- u32 r90, vf, hf; +- +- r90 = ((u32)mode >> 2) & 0x1; +- hf = ((u32)mode >> 1) & 0x1; +- vf = ((u32)mode >> 0) & 0x1; +- hf ^= (u32)hflip; +- vf ^= (u32)vflip; +- +- switch ((enum ipu_rotate_mode)((r90 << 2) | (hf << 1) | vf)) { +- case IPU_ROTATE_NONE: +- *degrees = 0; +- break; +- case IPU_ROTATE_90_RIGHT: +- *degrees = 90; +- break; +- case IPU_ROTATE_180: +- *degrees = 180; +- break; +- case IPU_ROTATE_90_LEFT: +- *degrees = 270; +- break; +- default: +- return -EINVAL; +- } +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_rot_mode_to_degrees); +- +-struct ipuv3_channel *ipu_idmac_get(struct ipu_soc *ipu, unsigned num) +-{ +- struct ipuv3_channel *channel; +- +- dev_dbg(ipu->dev, "%s %d\n", __func__, num); +- +- if (num > 63) +- return ERR_PTR(-ENODEV); +- +- mutex_lock(&ipu->channel_lock); +- +- list_for_each_entry(channel, &ipu->channels, list) { +- if (channel->num == num) { +- channel = ERR_PTR(-EBUSY); +- goto out; +- } +- } +- +- channel = kzalloc(sizeof(*channel), GFP_KERNEL); +- if (!channel) { +- channel = ERR_PTR(-ENOMEM); +- goto out; +- } +- +- channel->num = num; +- channel->ipu = ipu; +- list_add(&channel->list, &ipu->channels); +- +-out: +- mutex_unlock(&ipu->channel_lock); +- +- return channel; +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_get); +- +-void ipu_idmac_put(struct ipuv3_channel *channel) +-{ +- struct ipu_soc *ipu = channel->ipu; +- +- dev_dbg(ipu->dev, "%s %d\n", __func__, channel->num); +- +- mutex_lock(&ipu->channel_lock); +- +- list_del(&channel->list); +- kfree(channel); +- +- mutex_unlock(&ipu->channel_lock); +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_put); +- +-#define idma_mask(ch) (1 << ((ch) & 0x1f)) +- +-/* +- * This is an undocumented feature, a write one to a channel bit in +- * IPU_CHA_CUR_BUF and IPU_CHA_TRIPLE_CUR_BUF will reset the channel's +- * internal current buffer pointer so that transfers start from buffer +- * 0 on the next channel enable (that's the theory anyway, the imx6 TRM +- * only says these are read-only registers). This operation is required +- * for channel linking to work correctly, for instance video capture +- * pipelines that carry out image rotations will fail after the first +- * streaming unless this function is called for each channel before +- * re-enabling the channels. +- */ +-static void __ipu_idmac_reset_current_buffer(struct ipuv3_channel *channel) +-{ +- struct ipu_soc *ipu = channel->ipu; +- unsigned int chno = channel->num; +- +- ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_CUR_BUF(chno)); +-} +- +-void ipu_idmac_set_double_buffer(struct ipuv3_channel *channel, +- bool doublebuffer) +-{ +- struct ipu_soc *ipu = channel->ipu; +- unsigned long flags; +- u32 reg; +- +- spin_lock_irqsave(&ipu->lock, flags); +- +- reg = ipu_cm_read(ipu, IPU_CHA_DB_MODE_SEL(channel->num)); +- if (doublebuffer) +- reg |= idma_mask(channel->num); +- else +- reg &= ~idma_mask(channel->num); +- ipu_cm_write(ipu, reg, IPU_CHA_DB_MODE_SEL(channel->num)); +- +- __ipu_idmac_reset_current_buffer(channel); +- +- spin_unlock_irqrestore(&ipu->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_set_double_buffer); +- +-static const struct { +- int chnum; +- u32 reg; +- int shift; +-} idmac_lock_en_info[] = { +- { .chnum = 5, .reg = IDMAC_CH_LOCK_EN_1, .shift = 0, }, +- { .chnum = 11, .reg = IDMAC_CH_LOCK_EN_1, .shift = 2, }, +- { .chnum = 12, .reg = IDMAC_CH_LOCK_EN_1, .shift = 4, }, +- { .chnum = 14, .reg = IDMAC_CH_LOCK_EN_1, .shift = 6, }, +- { .chnum = 15, .reg = IDMAC_CH_LOCK_EN_1, .shift = 8, }, +- { .chnum = 20, .reg = IDMAC_CH_LOCK_EN_1, .shift = 10, }, +- { .chnum = 21, .reg = IDMAC_CH_LOCK_EN_1, .shift = 12, }, +- { .chnum = 22, .reg = IDMAC_CH_LOCK_EN_1, .shift = 14, }, +- { .chnum = 23, .reg = IDMAC_CH_LOCK_EN_1, .shift = 16, }, +- { .chnum = 27, .reg = IDMAC_CH_LOCK_EN_1, .shift = 18, }, +- { .chnum = 28, .reg = IDMAC_CH_LOCK_EN_1, .shift = 20, }, +- { .chnum = 45, .reg = IDMAC_CH_LOCK_EN_2, .shift = 0, }, +- { .chnum = 46, .reg = IDMAC_CH_LOCK_EN_2, .shift = 2, }, +- { .chnum = 47, .reg = IDMAC_CH_LOCK_EN_2, .shift = 4, }, +- { .chnum = 48, .reg = IDMAC_CH_LOCK_EN_2, .shift = 6, }, +- { .chnum = 49, .reg = IDMAC_CH_LOCK_EN_2, .shift = 8, }, +- { .chnum = 50, .reg = IDMAC_CH_LOCK_EN_2, .shift = 10, }, +-}; +- +-int ipu_idmac_lock_enable(struct ipuv3_channel *channel, int num_bursts) +-{ +- struct ipu_soc *ipu = channel->ipu; +- unsigned long flags; +- u32 bursts, regval; +- int i; +- +- switch (num_bursts) { +- case 0: +- case 1: +- bursts = 0x00; /* locking disabled */ +- break; +- case 2: +- bursts = 0x01; +- break; +- case 4: +- bursts = 0x02; +- break; +- case 8: +- bursts = 0x03; +- break; +- default: +- return -EINVAL; +- } +- +- /* +- * IPUv3EX / i.MX51 has a different register layout, and on IPUv3M / +- * i.MX53 channel arbitration locking doesn't seem to work properly. +- * Allow enabling the lock feature on IPUv3H / i.MX6 only. +- */ +- if (bursts && ipu->ipu_type != IPUV3H) +- return -EINVAL; +- +- for (i = 0; i < ARRAY_SIZE(idmac_lock_en_info); i++) { +- if (channel->num == idmac_lock_en_info[i].chnum) +- break; +- } +- if (i >= ARRAY_SIZE(idmac_lock_en_info)) +- return -EINVAL; +- +- spin_lock_irqsave(&ipu->lock, flags); +- +- regval = ipu_idmac_read(ipu, idmac_lock_en_info[i].reg); +- regval &= ~(0x03 << idmac_lock_en_info[i].shift); +- regval |= (bursts << idmac_lock_en_info[i].shift); +- ipu_idmac_write(ipu, regval, idmac_lock_en_info[i].reg); +- +- spin_unlock_irqrestore(&ipu->lock, flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_lock_enable); +- +-int ipu_module_enable(struct ipu_soc *ipu, u32 mask) +-{ +- unsigned long lock_flags; +- u32 val; +- +- spin_lock_irqsave(&ipu->lock, lock_flags); +- +- val = ipu_cm_read(ipu, IPU_DISP_GEN); +- +- if (mask & IPU_CONF_DI0_EN) +- val |= IPU_DI0_COUNTER_RELEASE; +- if (mask & IPU_CONF_DI1_EN) +- val |= IPU_DI1_COUNTER_RELEASE; +- +- ipu_cm_write(ipu, val, IPU_DISP_GEN); +- +- val = ipu_cm_read(ipu, IPU_CONF); +- val |= mask; +- ipu_cm_write(ipu, val, IPU_CONF); +- +- spin_unlock_irqrestore(&ipu->lock, lock_flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_module_enable); +- +-int ipu_module_disable(struct ipu_soc *ipu, u32 mask) +-{ +- unsigned long lock_flags; +- u32 val; +- +- spin_lock_irqsave(&ipu->lock, lock_flags); +- +- val = ipu_cm_read(ipu, IPU_CONF); +- val &= ~mask; +- ipu_cm_write(ipu, val, IPU_CONF); +- +- val = ipu_cm_read(ipu, IPU_DISP_GEN); +- +- if (mask & IPU_CONF_DI0_EN) +- val &= ~IPU_DI0_COUNTER_RELEASE; +- if (mask & IPU_CONF_DI1_EN) +- val &= ~IPU_DI1_COUNTER_RELEASE; +- +- ipu_cm_write(ipu, val, IPU_DISP_GEN); +- +- spin_unlock_irqrestore(&ipu->lock, lock_flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_module_disable); +- +-int ipu_idmac_get_current_buffer(struct ipuv3_channel *channel) +-{ +- struct ipu_soc *ipu = channel->ipu; +- unsigned int chno = channel->num; +- +- return (ipu_cm_read(ipu, IPU_CHA_CUR_BUF(chno)) & idma_mask(chno)) ? 1 : 0; +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_get_current_buffer); +- +-bool ipu_idmac_buffer_is_ready(struct ipuv3_channel *channel, u32 buf_num) +-{ +- struct ipu_soc *ipu = channel->ipu; +- unsigned long flags; +- u32 reg = 0; +- +- spin_lock_irqsave(&ipu->lock, flags); +- switch (buf_num) { +- case 0: +- reg = ipu_cm_read(ipu, IPU_CHA_BUF0_RDY(channel->num)); +- break; +- case 1: +- reg = ipu_cm_read(ipu, IPU_CHA_BUF1_RDY(channel->num)); +- break; +- case 2: +- reg = ipu_cm_read(ipu, IPU_CHA_BUF2_RDY(channel->num)); +- break; +- } +- spin_unlock_irqrestore(&ipu->lock, flags); +- +- return ((reg & idma_mask(channel->num)) != 0); +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_buffer_is_ready); +- +-void ipu_idmac_select_buffer(struct ipuv3_channel *channel, u32 buf_num) +-{ +- struct ipu_soc *ipu = channel->ipu; +- unsigned int chno = channel->num; +- unsigned long flags; +- +- spin_lock_irqsave(&ipu->lock, flags); +- +- /* Mark buffer as ready. */ +- if (buf_num == 0) +- ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_BUF0_RDY(chno)); +- else +- ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_BUF1_RDY(chno)); +- +- spin_unlock_irqrestore(&ipu->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_select_buffer); +- +-void ipu_idmac_clear_buffer(struct ipuv3_channel *channel, u32 buf_num) +-{ +- struct ipu_soc *ipu = channel->ipu; +- unsigned int chno = channel->num; +- unsigned long flags; +- +- spin_lock_irqsave(&ipu->lock, flags); +- +- ipu_cm_write(ipu, 0xF0300000, IPU_GPR); /* write one to clear */ +- switch (buf_num) { +- case 0: +- ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_BUF0_RDY(chno)); +- break; +- case 1: +- ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_BUF1_RDY(chno)); +- break; +- case 2: +- ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_BUF2_RDY(chno)); +- break; +- default: +- break; +- } +- ipu_cm_write(ipu, 0x0, IPU_GPR); /* write one to set */ +- +- spin_unlock_irqrestore(&ipu->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_clear_buffer); +- +-int ipu_idmac_enable_channel(struct ipuv3_channel *channel) +-{ +- struct ipu_soc *ipu = channel->ipu; +- u32 val; +- unsigned long flags; +- +- spin_lock_irqsave(&ipu->lock, flags); +- +- val = ipu_idmac_read(ipu, IDMAC_CHA_EN(channel->num)); +- val |= idma_mask(channel->num); +- ipu_idmac_write(ipu, val, IDMAC_CHA_EN(channel->num)); +- +- spin_unlock_irqrestore(&ipu->lock, flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_enable_channel); +- +-bool ipu_idmac_channel_busy(struct ipu_soc *ipu, unsigned int chno) +-{ +- return (ipu_idmac_read(ipu, IDMAC_CHA_BUSY(chno)) & idma_mask(chno)); +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_channel_busy); +- +-int ipu_idmac_wait_busy(struct ipuv3_channel *channel, int ms) +-{ +- struct ipu_soc *ipu = channel->ipu; +- unsigned long timeout; +- +- timeout = jiffies + msecs_to_jiffies(ms); +- while (ipu_idmac_read(ipu, IDMAC_CHA_BUSY(channel->num)) & +- idma_mask(channel->num)) { +- if (time_after(jiffies, timeout)) +- return -ETIMEDOUT; +- cpu_relax(); +- } +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_wait_busy); +- +-int ipu_idmac_disable_channel(struct ipuv3_channel *channel) +-{ +- struct ipu_soc *ipu = channel->ipu; +- u32 val; +- unsigned long flags; +- +- spin_lock_irqsave(&ipu->lock, flags); +- +- /* Disable DMA channel(s) */ +- val = ipu_idmac_read(ipu, IDMAC_CHA_EN(channel->num)); +- val &= ~idma_mask(channel->num); +- ipu_idmac_write(ipu, val, IDMAC_CHA_EN(channel->num)); +- +- __ipu_idmac_reset_current_buffer(channel); +- +- /* Set channel buffers NOT to be ready */ +- ipu_cm_write(ipu, 0xf0000000, IPU_GPR); /* write one to clear */ +- +- if (ipu_cm_read(ipu, IPU_CHA_BUF0_RDY(channel->num)) & +- idma_mask(channel->num)) { +- ipu_cm_write(ipu, idma_mask(channel->num), +- IPU_CHA_BUF0_RDY(channel->num)); +- } +- +- if (ipu_cm_read(ipu, IPU_CHA_BUF1_RDY(channel->num)) & +- idma_mask(channel->num)) { +- ipu_cm_write(ipu, idma_mask(channel->num), +- IPU_CHA_BUF1_RDY(channel->num)); +- } +- +- ipu_cm_write(ipu, 0x0, IPU_GPR); /* write one to set */ +- +- /* Reset the double buffer */ +- val = ipu_cm_read(ipu, IPU_CHA_DB_MODE_SEL(channel->num)); +- val &= ~idma_mask(channel->num); +- ipu_cm_write(ipu, val, IPU_CHA_DB_MODE_SEL(channel->num)); +- +- spin_unlock_irqrestore(&ipu->lock, flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_disable_channel); +- +-/* +- * The imx6 rev. D TRM says that enabling the WM feature will increase +- * a channel's priority. Refer to Table 36-8 Calculated priority value. +- * The sub-module that is the sink or source for the channel must enable +- * watermark signal for this to take effect (SMFC_WM for instance). +- */ +-void ipu_idmac_enable_watermark(struct ipuv3_channel *channel, bool enable) +-{ +- struct ipu_soc *ipu = channel->ipu; +- unsigned long flags; +- u32 val; +- +- spin_lock_irqsave(&ipu->lock, flags); +- +- val = ipu_idmac_read(ipu, IDMAC_WM_EN(channel->num)); +- if (enable) +- val |= 1 << (channel->num % 32); +- else +- val &= ~(1 << (channel->num % 32)); +- ipu_idmac_write(ipu, val, IDMAC_WM_EN(channel->num)); +- +- spin_unlock_irqrestore(&ipu->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_enable_watermark); +- +-static int ipu_memory_reset(struct ipu_soc *ipu) +-{ +- unsigned long timeout; +- +- ipu_cm_write(ipu, 0x807FFFFF, IPU_MEM_RST); +- +- timeout = jiffies + msecs_to_jiffies(1000); +- while (ipu_cm_read(ipu, IPU_MEM_RST) & 0x80000000) { +- if (time_after(jiffies, timeout)) +- return -ETIME; +- cpu_relax(); +- } +- +- return 0; +-} +- +-/* +- * Set the source mux for the given CSI. Selects either parallel or +- * MIPI CSI2 sources. +- */ +-void ipu_set_csi_src_mux(struct ipu_soc *ipu, int csi_id, bool mipi_csi2) +-{ +- unsigned long flags; +- u32 val, mask; +- +- mask = (csi_id == 1) ? IPU_CONF_CSI1_DATA_SOURCE : +- IPU_CONF_CSI0_DATA_SOURCE; +- +- spin_lock_irqsave(&ipu->lock, flags); +- +- val = ipu_cm_read(ipu, IPU_CONF); +- if (mipi_csi2) +- val |= mask; +- else +- val &= ~mask; +- ipu_cm_write(ipu, val, IPU_CONF); +- +- spin_unlock_irqrestore(&ipu->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_set_csi_src_mux); +- +-/* +- * Set the source mux for the IC. Selects either CSI[01] or the VDI. +- */ +-void ipu_set_ic_src_mux(struct ipu_soc *ipu, int csi_id, bool vdi) +-{ +- unsigned long flags; +- u32 val; +- +- spin_lock_irqsave(&ipu->lock, flags); +- +- val = ipu_cm_read(ipu, IPU_CONF); +- if (vdi) +- val |= IPU_CONF_IC_INPUT; +- else +- val &= ~IPU_CONF_IC_INPUT; +- +- if (csi_id == 1) +- val |= IPU_CONF_CSI_SEL; +- else +- val &= ~IPU_CONF_CSI_SEL; +- +- ipu_cm_write(ipu, val, IPU_CONF); +- +- spin_unlock_irqrestore(&ipu->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_set_ic_src_mux); +- +- +-/* Frame Synchronization Unit Channel Linking */ +- +-struct fsu_link_reg_info { +- int chno; +- u32 reg; +- u32 mask; +- u32 val; +-}; +- +-struct fsu_link_info { +- struct fsu_link_reg_info src; +- struct fsu_link_reg_info sink; +-}; +- +-static const struct fsu_link_info fsu_link_info[] = { +- { +- .src = { IPUV3_CHANNEL_IC_PRP_ENC_MEM, IPU_FS_PROC_FLOW2, +- FS_PRP_ENC_DEST_SEL_MASK, FS_PRP_ENC_DEST_SEL_IRT_ENC }, +- .sink = { IPUV3_CHANNEL_MEM_ROT_ENC, IPU_FS_PROC_FLOW1, +- FS_PRPENC_ROT_SRC_SEL_MASK, FS_PRPENC_ROT_SRC_SEL_ENC }, +- }, { +- .src = { IPUV3_CHANNEL_IC_PRP_VF_MEM, IPU_FS_PROC_FLOW2, +- FS_PRPVF_DEST_SEL_MASK, FS_PRPVF_DEST_SEL_IRT_VF }, +- .sink = { IPUV3_CHANNEL_MEM_ROT_VF, IPU_FS_PROC_FLOW1, +- FS_PRPVF_ROT_SRC_SEL_MASK, FS_PRPVF_ROT_SRC_SEL_VF }, +- }, { +- .src = { IPUV3_CHANNEL_IC_PP_MEM, IPU_FS_PROC_FLOW2, +- FS_PP_DEST_SEL_MASK, FS_PP_DEST_SEL_IRT_PP }, +- .sink = { IPUV3_CHANNEL_MEM_ROT_PP, IPU_FS_PROC_FLOW1, +- FS_PP_ROT_SRC_SEL_MASK, FS_PP_ROT_SRC_SEL_PP }, +- }, { +- .src = { IPUV3_CHANNEL_CSI_DIRECT, 0 }, +- .sink = { IPUV3_CHANNEL_CSI_VDI_PREV, IPU_FS_PROC_FLOW1, +- FS_VDI_SRC_SEL_MASK, FS_VDI_SRC_SEL_CSI_DIRECT }, +- }, +-}; +- +-static const struct fsu_link_info *find_fsu_link_info(int src, int sink) +-{ +- int i; +- +- for (i = 0; i < ARRAY_SIZE(fsu_link_info); i++) { +- if (src == fsu_link_info[i].src.chno && +- sink == fsu_link_info[i].sink.chno) +- return &fsu_link_info[i]; +- } +- +- return NULL; +-} +- +-/* +- * Links a source channel to a sink channel in the FSU. +- */ +-int ipu_fsu_link(struct ipu_soc *ipu, int src_ch, int sink_ch) +-{ +- const struct fsu_link_info *link; +- u32 src_reg, sink_reg; +- unsigned long flags; +- +- link = find_fsu_link_info(src_ch, sink_ch); +- if (!link) +- return -EINVAL; +- +- spin_lock_irqsave(&ipu->lock, flags); +- +- if (link->src.mask) { +- src_reg = ipu_cm_read(ipu, link->src.reg); +- src_reg &= ~link->src.mask; +- src_reg |= link->src.val; +- ipu_cm_write(ipu, src_reg, link->src.reg); +- } +- +- if (link->sink.mask) { +- sink_reg = ipu_cm_read(ipu, link->sink.reg); +- sink_reg &= ~link->sink.mask; +- sink_reg |= link->sink.val; +- ipu_cm_write(ipu, sink_reg, link->sink.reg); +- } +- +- spin_unlock_irqrestore(&ipu->lock, flags); +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_fsu_link); +- +-/* +- * Unlinks source and sink channels in the FSU. +- */ +-int ipu_fsu_unlink(struct ipu_soc *ipu, int src_ch, int sink_ch) +-{ +- const struct fsu_link_info *link; +- u32 src_reg, sink_reg; +- unsigned long flags; +- +- link = find_fsu_link_info(src_ch, sink_ch); +- if (!link) +- return -EINVAL; +- +- spin_lock_irqsave(&ipu->lock, flags); +- +- if (link->src.mask) { +- src_reg = ipu_cm_read(ipu, link->src.reg); +- src_reg &= ~link->src.mask; +- ipu_cm_write(ipu, src_reg, link->src.reg); +- } +- +- if (link->sink.mask) { +- sink_reg = ipu_cm_read(ipu, link->sink.reg); +- sink_reg &= ~link->sink.mask; +- ipu_cm_write(ipu, sink_reg, link->sink.reg); +- } +- +- spin_unlock_irqrestore(&ipu->lock, flags); +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_fsu_unlink); +- +-/* Link IDMAC channels in the FSU */ +-int ipu_idmac_link(struct ipuv3_channel *src, struct ipuv3_channel *sink) +-{ +- return ipu_fsu_link(src->ipu, src->num, sink->num); +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_link); +- +-/* Unlink IDMAC channels in the FSU */ +-int ipu_idmac_unlink(struct ipuv3_channel *src, struct ipuv3_channel *sink) +-{ +- return ipu_fsu_unlink(src->ipu, src->num, sink->num); +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_unlink); +- +-struct ipu_devtype { +- const char *name; +- unsigned long cm_ofs; +- unsigned long cpmem_ofs; +- unsigned long srm_ofs; +- unsigned long tpm_ofs; +- unsigned long csi0_ofs; +- unsigned long csi1_ofs; +- unsigned long ic_ofs; +- unsigned long disp0_ofs; +- unsigned long disp1_ofs; +- unsigned long dc_tmpl_ofs; +- unsigned long vdi_ofs; +- enum ipuv3_type type; +-}; +- +-static struct ipu_devtype ipu_type_imx51 = { +- .name = "IPUv3EX", +- .cm_ofs = 0x1e000000, +- .cpmem_ofs = 0x1f000000, +- .srm_ofs = 0x1f040000, +- .tpm_ofs = 0x1f060000, +- .csi0_ofs = 0x1e030000, +- .csi1_ofs = 0x1e038000, +- .ic_ofs = 0x1e020000, +- .disp0_ofs = 0x1e040000, +- .disp1_ofs = 0x1e048000, +- .dc_tmpl_ofs = 0x1f080000, +- .vdi_ofs = 0x1e068000, +- .type = IPUV3EX, +-}; +- +-static struct ipu_devtype ipu_type_imx53 = { +- .name = "IPUv3M", +- .cm_ofs = 0x06000000, +- .cpmem_ofs = 0x07000000, +- .srm_ofs = 0x07040000, +- .tpm_ofs = 0x07060000, +- .csi0_ofs = 0x06030000, +- .csi1_ofs = 0x06038000, +- .ic_ofs = 0x06020000, +- .disp0_ofs = 0x06040000, +- .disp1_ofs = 0x06048000, +- .dc_tmpl_ofs = 0x07080000, +- .vdi_ofs = 0x06068000, +- .type = IPUV3M, +-}; +- +-static struct ipu_devtype ipu_type_imx6q = { +- .name = "IPUv3H", +- .cm_ofs = 0x00200000, +- .cpmem_ofs = 0x00300000, +- .srm_ofs = 0x00340000, +- .tpm_ofs = 0x00360000, +- .csi0_ofs = 0x00230000, +- .csi1_ofs = 0x00238000, +- .ic_ofs = 0x00220000, +- .disp0_ofs = 0x00240000, +- .disp1_ofs = 0x00248000, +- .dc_tmpl_ofs = 0x00380000, +- .vdi_ofs = 0x00268000, +- .type = IPUV3H, +-}; +- +-static const struct of_device_id imx_ipu_dt_ids[] = { +- { .compatible = "fsl,imx51-ipu", .data = &ipu_type_imx51, }, +- { .compatible = "fsl,imx53-ipu", .data = &ipu_type_imx53, }, +- { .compatible = "fsl,imx6q-ipu", .data = &ipu_type_imx6q, }, +- { .compatible = "fsl,imx6qp-ipu", .data = &ipu_type_imx6q, }, +- { /* sentinel */ } +-}; +-MODULE_DEVICE_TABLE(of, imx_ipu_dt_ids); +- +-static int ipu_submodules_init(struct ipu_soc *ipu, +- struct platform_device *pdev, unsigned long ipu_base, +- struct clk *ipu_clk) +-{ +- char *unit; +- int ret; +- struct device *dev = &pdev->dev; +- const struct ipu_devtype *devtype = ipu->devtype; +- +- ret = ipu_cpmem_init(ipu, dev, ipu_base + devtype->cpmem_ofs); +- if (ret) { +- unit = "cpmem"; +- goto err_cpmem; +- } +- +- ret = ipu_csi_init(ipu, dev, 0, ipu_base + devtype->csi0_ofs, +- IPU_CONF_CSI0_EN, ipu_clk); +- if (ret) { +- unit = "csi0"; +- goto err_csi_0; +- } +- +- ret = ipu_csi_init(ipu, dev, 1, ipu_base + devtype->csi1_ofs, +- IPU_CONF_CSI1_EN, ipu_clk); +- if (ret) { +- unit = "csi1"; +- goto err_csi_1; +- } +- +- ret = ipu_ic_init(ipu, dev, +- ipu_base + devtype->ic_ofs, +- ipu_base + devtype->tpm_ofs); +- if (ret) { +- unit = "ic"; +- goto err_ic; +- } +- +- ret = ipu_vdi_init(ipu, dev, ipu_base + devtype->vdi_ofs, +- IPU_CONF_VDI_EN | IPU_CONF_ISP_EN | +- IPU_CONF_IC_INPUT); +- if (ret) { +- unit = "vdi"; +- goto err_vdi; +- } +- +- ret = ipu_image_convert_init(ipu, dev); +- if (ret) { +- unit = "image_convert"; +- goto err_image_convert; +- } +- +- ret = ipu_di_init(ipu, dev, 0, ipu_base + devtype->disp0_ofs, +- IPU_CONF_DI0_EN, ipu_clk); +- if (ret) { +- unit = "di0"; +- goto err_di_0; +- } +- +- ret = ipu_di_init(ipu, dev, 1, ipu_base + devtype->disp1_ofs, +- IPU_CONF_DI1_EN, ipu_clk); +- if (ret) { +- unit = "di1"; +- goto err_di_1; +- } +- +- ret = ipu_dc_init(ipu, dev, ipu_base + devtype->cm_ofs + +- IPU_CM_DC_REG_OFS, ipu_base + devtype->dc_tmpl_ofs); +- if (ret) { +- unit = "dc_template"; +- goto err_dc; +- } +- +- ret = ipu_dmfc_init(ipu, dev, ipu_base + +- devtype->cm_ofs + IPU_CM_DMFC_REG_OFS, ipu_clk); +- if (ret) { +- unit = "dmfc"; +- goto err_dmfc; +- } +- +- ret = ipu_dp_init(ipu, dev, ipu_base + devtype->srm_ofs); +- if (ret) { +- unit = "dp"; +- goto err_dp; +- } +- +- ret = ipu_smfc_init(ipu, dev, ipu_base + +- devtype->cm_ofs + IPU_CM_SMFC_REG_OFS); +- if (ret) { +- unit = "smfc"; +- goto err_smfc; +- } +- +- return 0; +- +-err_smfc: +- ipu_dp_exit(ipu); +-err_dp: +- ipu_dmfc_exit(ipu); +-err_dmfc: +- ipu_dc_exit(ipu); +-err_dc: +- ipu_di_exit(ipu, 1); +-err_di_1: +- ipu_di_exit(ipu, 0); +-err_di_0: +- ipu_image_convert_exit(ipu); +-err_image_convert: +- ipu_vdi_exit(ipu); +-err_vdi: +- ipu_ic_exit(ipu); +-err_ic: +- ipu_csi_exit(ipu, 1); +-err_csi_1: +- ipu_csi_exit(ipu, 0); +-err_csi_0: +- ipu_cpmem_exit(ipu); +-err_cpmem: +- dev_err(&pdev->dev, "init %s failed with %d\n", unit, ret); +- return ret; +-} +- +-static void ipu_irq_handle(struct ipu_soc *ipu, const int *regs, int num_regs) +-{ +- unsigned long status; +- int i, bit, irq; +- +- for (i = 0; i < num_regs; i++) { +- +- status = ipu_cm_read(ipu, IPU_INT_STAT(regs[i])); +- status &= ipu_cm_read(ipu, IPU_INT_CTRL(regs[i])); +- +- for_each_set_bit(bit, &status, 32) { +- irq = irq_linear_revmap(ipu->domain, +- regs[i] * 32 + bit); +- if (irq) +- generic_handle_irq(irq); +- } +- } +-} +- +-static void ipu_irq_handler(struct irq_desc *desc) +-{ +- struct ipu_soc *ipu = irq_desc_get_handler_data(desc); +- struct irq_chip *chip = irq_desc_get_chip(desc); +- static const int int_reg[] = { 0, 1, 2, 3, 10, 11, 12, 13, 14}; +- +- chained_irq_enter(chip, desc); +- +- ipu_irq_handle(ipu, int_reg, ARRAY_SIZE(int_reg)); +- +- chained_irq_exit(chip, desc); +-} +- +-static void ipu_err_irq_handler(struct irq_desc *desc) +-{ +- struct ipu_soc *ipu = irq_desc_get_handler_data(desc); +- struct irq_chip *chip = irq_desc_get_chip(desc); +- static const int int_reg[] = { 4, 5, 8, 9}; +- +- chained_irq_enter(chip, desc); +- +- ipu_irq_handle(ipu, int_reg, ARRAY_SIZE(int_reg)); +- +- chained_irq_exit(chip, desc); +-} +- +-int ipu_map_irq(struct ipu_soc *ipu, int irq) +-{ +- int virq; +- +- virq = irq_linear_revmap(ipu->domain, irq); +- if (!virq) +- virq = irq_create_mapping(ipu->domain, irq); +- +- return virq; +-} +-EXPORT_SYMBOL_GPL(ipu_map_irq); +- +-int ipu_idmac_channel_irq(struct ipu_soc *ipu, struct ipuv3_channel *channel, +- enum ipu_channel_irq irq_type) +-{ +- return ipu_map_irq(ipu, irq_type + channel->num); +-} +-EXPORT_SYMBOL_GPL(ipu_idmac_channel_irq); +- +-static void ipu_submodules_exit(struct ipu_soc *ipu) +-{ +- ipu_smfc_exit(ipu); +- ipu_dp_exit(ipu); +- ipu_dmfc_exit(ipu); +- ipu_dc_exit(ipu); +- ipu_di_exit(ipu, 1); +- ipu_di_exit(ipu, 0); +- ipu_image_convert_exit(ipu); +- ipu_vdi_exit(ipu); +- ipu_ic_exit(ipu); +- ipu_csi_exit(ipu, 1); +- ipu_csi_exit(ipu, 0); +- ipu_cpmem_exit(ipu); +-} +- +-static int platform_remove_devices_fn(struct device *dev, void *unused) +-{ +- struct platform_device *pdev = to_platform_device(dev); +- +- platform_device_unregister(pdev); +- +- return 0; +-} +- +-static void platform_device_unregister_children(struct platform_device *pdev) +-{ +- device_for_each_child(&pdev->dev, NULL, platform_remove_devices_fn); +-} +- +-struct ipu_platform_reg { +- struct ipu_client_platformdata pdata; +- const char *name; +-}; +- +-/* These must be in the order of the corresponding device tree port nodes */ +-static struct ipu_platform_reg client_reg[] = { +- { +- .pdata = { +- .csi = 0, +- .dma[0] = IPUV3_CHANNEL_CSI0, +- .dma[1] = -EINVAL, +- }, +- .name = "imx-ipuv3-csi", +- }, { +- .pdata = { +- .csi = 1, +- .dma[0] = IPUV3_CHANNEL_CSI1, +- .dma[1] = -EINVAL, +- }, +- .name = "imx-ipuv3-csi", +- }, { +- .pdata = { +- .di = 0, +- .dc = 5, +- .dp = IPU_DP_FLOW_SYNC_BG, +- .dma[0] = IPUV3_CHANNEL_MEM_BG_SYNC, +- .dma[1] = IPUV3_CHANNEL_MEM_FG_SYNC, +- }, +- .name = "imx-ipuv3-crtc", +- }, { +- .pdata = { +- .di = 1, +- .dc = 1, +- .dp = -EINVAL, +- .dma[0] = IPUV3_CHANNEL_MEM_DC_SYNC, +- .dma[1] = -EINVAL, +- }, +- .name = "imx-ipuv3-crtc", +- }, +-}; +- +-static DEFINE_MUTEX(ipu_client_id_mutex); +-static int ipu_client_id; +- +-static int ipu_add_client_devices(struct ipu_soc *ipu, unsigned long ipu_base) +-{ +- struct device *dev = ipu->dev; +- unsigned i; +- int id, ret; +- +- mutex_lock(&ipu_client_id_mutex); +- id = ipu_client_id; +- ipu_client_id += ARRAY_SIZE(client_reg); +- mutex_unlock(&ipu_client_id_mutex); +- +- for (i = 0; i < ARRAY_SIZE(client_reg); i++) { +- struct ipu_platform_reg *reg = &client_reg[i]; +- struct platform_device *pdev; +- struct device_node *of_node; +- +- /* Associate subdevice with the corresponding port node */ +- of_node = of_graph_get_port_by_id(dev->of_node, i); +- if (!of_node) { +- dev_info(dev, +- "no port@%d node in %pOF, not using %s%d\n", +- i, dev->of_node, +- (i / 2) ? "DI" : "CSI", i % 2); +- continue; +- } +- +- pdev = platform_device_alloc(reg->name, id++); +- if (!pdev) { +- ret = -ENOMEM; +- goto err_register; +- } +- +- pdev->dev.parent = dev; +- +- reg->pdata.of_node = of_node; +- ret = platform_device_add_data(pdev, ®->pdata, +- sizeof(reg->pdata)); +- if (!ret) +- ret = platform_device_add(pdev); +- if (ret) { +- platform_device_put(pdev); +- goto err_register; +- } +- } +- +- return 0; +- +-err_register: +- platform_device_unregister_children(to_platform_device(dev)); +- +- return ret; +-} +- +- +-static int ipu_irq_init(struct ipu_soc *ipu) +-{ +- struct irq_chip_generic *gc; +- struct irq_chip_type *ct; +- unsigned long unused[IPU_NUM_IRQS / 32] = { +- 0x400100d0, 0xffe000fd, +- 0x400100d0, 0xffe000fd, +- 0x400100d0, 0xffe000fd, +- 0x4077ffff, 0xffe7e1fd, +- 0x23fffffe, 0x8880fff0, +- 0xf98fe7d0, 0xfff81fff, +- 0x400100d0, 0xffe000fd, +- 0x00000000, +- }; +- int ret, i; +- +- ipu->domain = irq_domain_add_linear(ipu->dev->of_node, IPU_NUM_IRQS, +- &irq_generic_chip_ops, ipu); +- if (!ipu->domain) { +- dev_err(ipu->dev, "failed to add irq domain\n"); +- return -ENODEV; +- } +- +- ret = irq_alloc_domain_generic_chips(ipu->domain, 32, 1, "IPU", +- handle_level_irq, 0, 0, 0); +- if (ret < 0) { +- dev_err(ipu->dev, "failed to alloc generic irq chips\n"); +- irq_domain_remove(ipu->domain); +- return ret; +- } +- +- /* Mask and clear all interrupts */ +- for (i = 0; i < IPU_NUM_IRQS; i += 32) { +- ipu_cm_write(ipu, 0, IPU_INT_CTRL(i / 32)); +- ipu_cm_write(ipu, ~unused[i / 32], IPU_INT_STAT(i / 32)); +- } +- +- for (i = 0; i < IPU_NUM_IRQS; i += 32) { +- gc = irq_get_domain_generic_chip(ipu->domain, i); +- gc->reg_base = ipu->cm_reg; +- gc->unused = unused[i / 32]; +- ct = gc->chip_types; +- ct->chip.irq_ack = irq_gc_ack_set_bit; +- ct->chip.irq_mask = irq_gc_mask_clr_bit; +- ct->chip.irq_unmask = irq_gc_mask_set_bit; +- ct->regs.ack = IPU_INT_STAT(i / 32); +- ct->regs.mask = IPU_INT_CTRL(i / 32); +- } +- +- irq_set_chained_handler_and_data(ipu->irq_sync, ipu_irq_handler, ipu); +- irq_set_chained_handler_and_data(ipu->irq_err, ipu_err_irq_handler, +- ipu); +- +- return 0; +-} +- +-static void ipu_irq_exit(struct ipu_soc *ipu) +-{ +- int i, irq; +- +- irq_set_chained_handler_and_data(ipu->irq_err, NULL, NULL); +- irq_set_chained_handler_and_data(ipu->irq_sync, NULL, NULL); +- +- /* TODO: remove irq_domain_generic_chips */ +- +- for (i = 0; i < IPU_NUM_IRQS; i++) { +- irq = irq_linear_revmap(ipu->domain, i); +- if (irq) +- irq_dispose_mapping(irq); +- } +- +- irq_domain_remove(ipu->domain); +-} +- +-void ipu_dump(struct ipu_soc *ipu) +-{ +- int i; +- +- dev_dbg(ipu->dev, "IPU_CONF = \t0x%08X\n", +- ipu_cm_read(ipu, IPU_CONF)); +- dev_dbg(ipu->dev, "IDMAC_CONF = \t0x%08X\n", +- ipu_idmac_read(ipu, IDMAC_CONF)); +- dev_dbg(ipu->dev, "IDMAC_CHA_EN1 = \t0x%08X\n", +- ipu_idmac_read(ipu, IDMAC_CHA_EN(0))); +- dev_dbg(ipu->dev, "IDMAC_CHA_EN2 = \t0x%08X\n", +- ipu_idmac_read(ipu, IDMAC_CHA_EN(32))); +- dev_dbg(ipu->dev, "IDMAC_CHA_PRI1 = \t0x%08X\n", +- ipu_idmac_read(ipu, IDMAC_CHA_PRI(0))); +- dev_dbg(ipu->dev, "IDMAC_CHA_PRI2 = \t0x%08X\n", +- ipu_idmac_read(ipu, IDMAC_CHA_PRI(32))); +- dev_dbg(ipu->dev, "IDMAC_BAND_EN1 = \t0x%08X\n", +- ipu_idmac_read(ipu, IDMAC_BAND_EN(0))); +- dev_dbg(ipu->dev, "IDMAC_BAND_EN2 = \t0x%08X\n", +- ipu_idmac_read(ipu, IDMAC_BAND_EN(32))); +- dev_dbg(ipu->dev, "IPU_CHA_DB_MODE_SEL0 = \t0x%08X\n", +- ipu_cm_read(ipu, IPU_CHA_DB_MODE_SEL(0))); +- dev_dbg(ipu->dev, "IPU_CHA_DB_MODE_SEL1 = \t0x%08X\n", +- ipu_cm_read(ipu, IPU_CHA_DB_MODE_SEL(32))); +- dev_dbg(ipu->dev, "IPU_FS_PROC_FLOW1 = \t0x%08X\n", +- ipu_cm_read(ipu, IPU_FS_PROC_FLOW1)); +- dev_dbg(ipu->dev, "IPU_FS_PROC_FLOW2 = \t0x%08X\n", +- ipu_cm_read(ipu, IPU_FS_PROC_FLOW2)); +- dev_dbg(ipu->dev, "IPU_FS_PROC_FLOW3 = \t0x%08X\n", +- ipu_cm_read(ipu, IPU_FS_PROC_FLOW3)); +- dev_dbg(ipu->dev, "IPU_FS_DISP_FLOW1 = \t0x%08X\n", +- ipu_cm_read(ipu, IPU_FS_DISP_FLOW1)); +- for (i = 0; i < 15; i++) +- dev_dbg(ipu->dev, "IPU_INT_CTRL(%d) = \t%08X\n", i, +- ipu_cm_read(ipu, IPU_INT_CTRL(i))); +-} +-EXPORT_SYMBOL_GPL(ipu_dump); +- +-static int ipu_probe(struct platform_device *pdev) +-{ +- struct device_node *np = pdev->dev.of_node; +- struct ipu_soc *ipu; +- struct resource *res; +- unsigned long ipu_base; +- int ret, irq_sync, irq_err; +- const struct ipu_devtype *devtype; +- +- devtype = of_device_get_match_data(&pdev->dev); +- if (!devtype) +- return -EINVAL; +- +- irq_sync = platform_get_irq(pdev, 0); +- irq_err = platform_get_irq(pdev, 1); +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- +- dev_dbg(&pdev->dev, "irq_sync: %d irq_err: %d\n", +- irq_sync, irq_err); +- +- if (!res || irq_sync < 0 || irq_err < 0) +- return -ENODEV; +- +- ipu_base = res->start; +- +- ipu = devm_kzalloc(&pdev->dev, sizeof(*ipu), GFP_KERNEL); +- if (!ipu) +- return -ENODEV; +- +- ipu->id = of_alias_get_id(np, "ipu"); +- if (ipu->id < 0) +- ipu->id = 0; +- +- if (of_device_is_compatible(np, "fsl,imx6qp-ipu") && +- IS_ENABLED(CONFIG_DRM)) { +- ipu->prg_priv = ipu_prg_lookup_by_phandle(&pdev->dev, +- "fsl,prg", ipu->id); +- if (!ipu->prg_priv) +- return -EPROBE_DEFER; +- } +- +- ipu->devtype = devtype; +- ipu->ipu_type = devtype->type; +- +- spin_lock_init(&ipu->lock); +- mutex_init(&ipu->channel_lock); +- INIT_LIST_HEAD(&ipu->channels); +- +- dev_dbg(&pdev->dev, "cm_reg: 0x%08lx\n", +- ipu_base + devtype->cm_ofs); +- dev_dbg(&pdev->dev, "idmac: 0x%08lx\n", +- ipu_base + devtype->cm_ofs + IPU_CM_IDMAC_REG_OFS); +- dev_dbg(&pdev->dev, "cpmem: 0x%08lx\n", +- ipu_base + devtype->cpmem_ofs); +- dev_dbg(&pdev->dev, "csi0: 0x%08lx\n", +- ipu_base + devtype->csi0_ofs); +- dev_dbg(&pdev->dev, "csi1: 0x%08lx\n", +- ipu_base + devtype->csi1_ofs); +- dev_dbg(&pdev->dev, "ic: 0x%08lx\n", +- ipu_base + devtype->ic_ofs); +- dev_dbg(&pdev->dev, "disp0: 0x%08lx\n", +- ipu_base + devtype->disp0_ofs); +- dev_dbg(&pdev->dev, "disp1: 0x%08lx\n", +- ipu_base + devtype->disp1_ofs); +- dev_dbg(&pdev->dev, "srm: 0x%08lx\n", +- ipu_base + devtype->srm_ofs); +- dev_dbg(&pdev->dev, "tpm: 0x%08lx\n", +- ipu_base + devtype->tpm_ofs); +- dev_dbg(&pdev->dev, "dc: 0x%08lx\n", +- ipu_base + devtype->cm_ofs + IPU_CM_DC_REG_OFS); +- dev_dbg(&pdev->dev, "ic: 0x%08lx\n", +- ipu_base + devtype->cm_ofs + IPU_CM_IC_REG_OFS); +- dev_dbg(&pdev->dev, "dmfc: 0x%08lx\n", +- ipu_base + devtype->cm_ofs + IPU_CM_DMFC_REG_OFS); +- dev_dbg(&pdev->dev, "vdi: 0x%08lx\n", +- ipu_base + devtype->vdi_ofs); +- +- ipu->cm_reg = devm_ioremap(&pdev->dev, +- ipu_base + devtype->cm_ofs, PAGE_SIZE); +- ipu->idmac_reg = devm_ioremap(&pdev->dev, +- ipu_base + devtype->cm_ofs + IPU_CM_IDMAC_REG_OFS, +- PAGE_SIZE); +- +- if (!ipu->cm_reg || !ipu->idmac_reg) +- return -ENOMEM; +- +- ipu->clk = devm_clk_get(&pdev->dev, "bus"); +- if (IS_ERR(ipu->clk)) { +- ret = PTR_ERR(ipu->clk); +- dev_err(&pdev->dev, "clk_get failed with %d", ret); +- return ret; +- } +- +- platform_set_drvdata(pdev, ipu); +- +- ret = clk_prepare_enable(ipu->clk); +- if (ret) { +- dev_err(&pdev->dev, "clk_prepare_enable failed: %d\n", ret); +- return ret; +- } +- +- ipu->dev = &pdev->dev; +- ipu->irq_sync = irq_sync; +- ipu->irq_err = irq_err; +- +- ret = device_reset(&pdev->dev); +- if (ret) { +- dev_err(&pdev->dev, "failed to reset: %d\n", ret); +- goto out_failed_reset; +- } +- ret = ipu_memory_reset(ipu); +- if (ret) +- goto out_failed_reset; +- +- ret = ipu_irq_init(ipu); +- if (ret) +- goto out_failed_irq; +- +- /* Set MCU_T to divide MCU access window into 2 */ +- ipu_cm_write(ipu, 0x00400000L | (IPU_MCU_T_DEFAULT << 18), +- IPU_DISP_GEN); +- +- ret = ipu_submodules_init(ipu, pdev, ipu_base, ipu->clk); +- if (ret) +- goto failed_submodules_init; +- +- ret = ipu_add_client_devices(ipu, ipu_base); +- if (ret) { +- dev_err(&pdev->dev, "adding client devices failed with %d\n", +- ret); +- goto failed_add_clients; +- } +- +- dev_info(&pdev->dev, "%s probed\n", devtype->name); +- +- return 0; +- +-failed_add_clients: +- ipu_submodules_exit(ipu); +-failed_submodules_init: +- ipu_irq_exit(ipu); +-out_failed_irq: +-out_failed_reset: +- clk_disable_unprepare(ipu->clk); +- return ret; +-} +- +-static int ipu_remove(struct platform_device *pdev) +-{ +- struct ipu_soc *ipu = platform_get_drvdata(pdev); +- +- platform_device_unregister_children(pdev); +- ipu_submodules_exit(ipu); +- ipu_irq_exit(ipu); +- +- clk_disable_unprepare(ipu->clk); +- +- return 0; +-} +- +-static struct platform_driver imx_ipu_driver = { +- .driver = { +- .name = "imx-ipuv3", +- .of_match_table = imx_ipu_dt_ids, +- }, +- .probe = ipu_probe, +- .remove = ipu_remove, +-}; +- +-static struct platform_driver * const drivers[] = { +-#if IS_ENABLED(CONFIG_DRM) +- &ipu_pre_drv, +- &ipu_prg_drv, +-#endif +- &imx_ipu_driver, +-}; +- +-static int __init imx_ipu_init(void) +-{ +- return platform_register_drivers(drivers, ARRAY_SIZE(drivers)); +-} +-module_init(imx_ipu_init); +- +-static void __exit imx_ipu_exit(void) +-{ +- platform_unregister_drivers(drivers, ARRAY_SIZE(drivers)); +-} +-module_exit(imx_ipu_exit); +- +-MODULE_ALIAS("platform:imx-ipuv3"); +-MODULE_DESCRIPTION("i.MX IPU v3 driver"); +-MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); +-MODULE_LICENSE("GPL"); +--- a/drivers/gpu/imx/ipu-v3/ipu-cpmem.c ++++ /dev/null +@@ -1,976 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-or-later +-/* +- * Copyright (C) 2012 Mentor Graphics Inc. +- * Copyright 2005-2012 Freescale Semiconductor, Inc. All Rights Reserved. +- */ +-#include <linux/types.h> +-#include <linux/bitrev.h> +-#include <linux/io.h> +-#include <linux/sizes.h> +-#include <drm/drm_fourcc.h> +-#include "ipu-prv.h" +- +-struct ipu_cpmem_word { +- u32 data[5]; +- u32 res[3]; +-}; +- +-struct ipu_ch_param { +- struct ipu_cpmem_word word[2]; +-}; +- +-struct ipu_cpmem { +- struct ipu_ch_param __iomem *base; +- u32 module; +- spinlock_t lock; +- int use_count; +- struct ipu_soc *ipu; +-}; +- +-#define IPU_CPMEM_WORD(word, ofs, size) ((((word) * 160 + (ofs)) << 8) | (size)) +- +-#define IPU_FIELD_UBO IPU_CPMEM_WORD(0, 46, 22) +-#define IPU_FIELD_VBO IPU_CPMEM_WORD(0, 68, 22) +-#define IPU_FIELD_IOX IPU_CPMEM_WORD(0, 90, 4) +-#define IPU_FIELD_RDRW IPU_CPMEM_WORD(0, 94, 1) +-#define IPU_FIELD_SO IPU_CPMEM_WORD(0, 113, 1) +-#define IPU_FIELD_SLY IPU_CPMEM_WORD(1, 102, 14) +-#define IPU_FIELD_SLUV IPU_CPMEM_WORD(1, 128, 14) +- +-#define IPU_FIELD_XV IPU_CPMEM_WORD(0, 0, 10) +-#define IPU_FIELD_YV IPU_CPMEM_WORD(0, 10, 9) +-#define IPU_FIELD_XB IPU_CPMEM_WORD(0, 19, 13) +-#define IPU_FIELD_YB IPU_CPMEM_WORD(0, 32, 12) +-#define IPU_FIELD_NSB_B IPU_CPMEM_WORD(0, 44, 1) +-#define IPU_FIELD_CF IPU_CPMEM_WORD(0, 45, 1) +-#define IPU_FIELD_SX IPU_CPMEM_WORD(0, 46, 12) +-#define IPU_FIELD_SY IPU_CPMEM_WORD(0, 58, 11) +-#define IPU_FIELD_NS IPU_CPMEM_WORD(0, 69, 10) +-#define IPU_FIELD_SDX IPU_CPMEM_WORD(0, 79, 7) +-#define IPU_FIELD_SM IPU_CPMEM_WORD(0, 86, 10) +-#define IPU_FIELD_SCC IPU_CPMEM_WORD(0, 96, 1) +-#define IPU_FIELD_SCE IPU_CPMEM_WORD(0, 97, 1) +-#define IPU_FIELD_SDY IPU_CPMEM_WORD(0, 98, 7) +-#define IPU_FIELD_SDRX IPU_CPMEM_WORD(0, 105, 1) +-#define IPU_FIELD_SDRY IPU_CPMEM_WORD(0, 106, 1) +-#define IPU_FIELD_BPP IPU_CPMEM_WORD(0, 107, 3) +-#define IPU_FIELD_DEC_SEL IPU_CPMEM_WORD(0, 110, 2) +-#define IPU_FIELD_DIM IPU_CPMEM_WORD(0, 112, 1) +-#define IPU_FIELD_BNDM IPU_CPMEM_WORD(0, 114, 3) +-#define IPU_FIELD_BM IPU_CPMEM_WORD(0, 117, 2) +-#define IPU_FIELD_ROT IPU_CPMEM_WORD(0, 119, 1) +-#define IPU_FIELD_ROT_HF_VF IPU_CPMEM_WORD(0, 119, 3) +-#define IPU_FIELD_HF IPU_CPMEM_WORD(0, 120, 1) +-#define IPU_FIELD_VF IPU_CPMEM_WORD(0, 121, 1) +-#define IPU_FIELD_THE IPU_CPMEM_WORD(0, 122, 1) +-#define IPU_FIELD_CAP IPU_CPMEM_WORD(0, 123, 1) +-#define IPU_FIELD_CAE IPU_CPMEM_WORD(0, 124, 1) +-#define IPU_FIELD_FW IPU_CPMEM_WORD(0, 125, 13) +-#define IPU_FIELD_FH IPU_CPMEM_WORD(0, 138, 12) +-#define IPU_FIELD_EBA0 IPU_CPMEM_WORD(1, 0, 29) +-#define IPU_FIELD_EBA1 IPU_CPMEM_WORD(1, 29, 29) +-#define IPU_FIELD_ILO IPU_CPMEM_WORD(1, 58, 20) +-#define IPU_FIELD_NPB IPU_CPMEM_WORD(1, 78, 7) +-#define IPU_FIELD_PFS IPU_CPMEM_WORD(1, 85, 4) +-#define IPU_FIELD_ALU IPU_CPMEM_WORD(1, 89, 1) +-#define IPU_FIELD_ALBM IPU_CPMEM_WORD(1, 90, 3) +-#define IPU_FIELD_ID IPU_CPMEM_WORD(1, 93, 2) +-#define IPU_FIELD_TH IPU_CPMEM_WORD(1, 95, 7) +-#define IPU_FIELD_SL IPU_CPMEM_WORD(1, 102, 14) +-#define IPU_FIELD_WID0 IPU_CPMEM_WORD(1, 116, 3) +-#define IPU_FIELD_WID1 IPU_CPMEM_WORD(1, 119, 3) +-#define IPU_FIELD_WID2 IPU_CPMEM_WORD(1, 122, 3) +-#define IPU_FIELD_WID3 IPU_CPMEM_WORD(1, 125, 3) +-#define IPU_FIELD_OFS0 IPU_CPMEM_WORD(1, 128, 5) +-#define IPU_FIELD_OFS1 IPU_CPMEM_WORD(1, 133, 5) +-#define IPU_FIELD_OFS2 IPU_CPMEM_WORD(1, 138, 5) +-#define IPU_FIELD_OFS3 IPU_CPMEM_WORD(1, 143, 5) +-#define IPU_FIELD_SXYS IPU_CPMEM_WORD(1, 148, 1) +-#define IPU_FIELD_CRE IPU_CPMEM_WORD(1, 149, 1) +-#define IPU_FIELD_DEC_SEL2 IPU_CPMEM_WORD(1, 150, 1) +- +-static inline struct ipu_ch_param __iomem * +-ipu_get_cpmem(struct ipuv3_channel *ch) +-{ +- struct ipu_cpmem *cpmem = ch->ipu->cpmem_priv; +- +- return cpmem->base + ch->num; +-} +- +-static void ipu_ch_param_write_field(struct ipuv3_channel *ch, u32 wbs, u32 v) +-{ +- struct ipu_ch_param __iomem *base = ipu_get_cpmem(ch); +- u32 bit = (wbs >> 8) % 160; +- u32 size = wbs & 0xff; +- u32 word = (wbs >> 8) / 160; +- u32 i = bit / 32; +- u32 ofs = bit % 32; +- u32 mask = (1 << size) - 1; +- u32 val; +- +- pr_debug("%s %d %d %d\n", __func__, word, bit , size); +- +- val = readl(&base->word[word].data[i]); +- val &= ~(mask << ofs); +- val |= v << ofs; +- writel(val, &base->word[word].data[i]); +- +- if ((bit + size - 1) / 32 > i) { +- val = readl(&base->word[word].data[i + 1]); +- val &= ~(mask >> (ofs ? (32 - ofs) : 0)); +- val |= v >> (ofs ? (32 - ofs) : 0); +- writel(val, &base->word[word].data[i + 1]); +- } +-} +- +-static u32 ipu_ch_param_read_field(struct ipuv3_channel *ch, u32 wbs) +-{ +- struct ipu_ch_param __iomem *base = ipu_get_cpmem(ch); +- u32 bit = (wbs >> 8) % 160; +- u32 size = wbs & 0xff; +- u32 word = (wbs >> 8) / 160; +- u32 i = bit / 32; +- u32 ofs = bit % 32; +- u32 mask = (1 << size) - 1; +- u32 val = 0; +- +- pr_debug("%s %d %d %d\n", __func__, word, bit , size); +- +- val = (readl(&base->word[word].data[i]) >> ofs) & mask; +- +- if ((bit + size - 1) / 32 > i) { +- u32 tmp; +- +- tmp = readl(&base->word[word].data[i + 1]); +- tmp &= mask >> (ofs ? (32 - ofs) : 0); +- val |= tmp << (ofs ? (32 - ofs) : 0); +- } +- +- return val; +-} +- +-/* +- * The V4L2 spec defines packed RGB formats in memory byte order, which from +- * point of view of the IPU corresponds to little-endian words with the first +- * component in the least significant bits. +- * The DRM pixel formats and IPU internal representation are ordered the other +- * way around, with the first named component ordered at the most significant +- * bits. Further, V4L2 formats are not well defined: +- * https://linuxtv.org/downloads/v4l-dvb-apis/packed-rgb.html +- * We choose the interpretation which matches GStreamer behavior. +- */ +-static int v4l2_pix_fmt_to_drm_fourcc(u32 pixelformat) +-{ +- switch (pixelformat) { +- case V4L2_PIX_FMT_RGB565: +- /* +- * Here we choose the 'corrected' interpretation of RGBP, a +- * little-endian 16-bit word with the red component at the most +- * significant bits: +- * g[2:0]b[4:0] r[4:0]g[5:3] <=> [16:0] R:G:B +- */ +- return DRM_FORMAT_RGB565; +- case V4L2_PIX_FMT_BGR24: +- /* B G R <=> [24:0] R:G:B */ +- return DRM_FORMAT_RGB888; +- case V4L2_PIX_FMT_RGB24: +- /* R G B <=> [24:0] B:G:R */ +- return DRM_FORMAT_BGR888; +- case V4L2_PIX_FMT_BGR32: +- /* B G R A <=> [32:0] A:B:G:R */ +- return DRM_FORMAT_XRGB8888; +- case V4L2_PIX_FMT_RGB32: +- /* R G B A <=> [32:0] A:B:G:R */ +- return DRM_FORMAT_XBGR8888; +- case V4L2_PIX_FMT_ABGR32: +- /* B G R A <=> [32:0] A:R:G:B */ +- return DRM_FORMAT_ARGB8888; +- case V4L2_PIX_FMT_XBGR32: +- /* B G R X <=> [32:0] X:R:G:B */ +- return DRM_FORMAT_XRGB8888; +- case V4L2_PIX_FMT_BGRA32: +- /* A B G R <=> [32:0] R:G:B:A */ +- return DRM_FORMAT_RGBA8888; +- case V4L2_PIX_FMT_BGRX32: +- /* X B G R <=> [32:0] R:G:B:X */ +- return DRM_FORMAT_RGBX8888; +- case V4L2_PIX_FMT_RGBA32: +- /* R G B A <=> [32:0] A:B:G:R */ +- return DRM_FORMAT_ABGR8888; +- case V4L2_PIX_FMT_RGBX32: +- /* R G B X <=> [32:0] X:B:G:R */ +- return DRM_FORMAT_XBGR8888; +- case V4L2_PIX_FMT_ARGB32: +- /* A R G B <=> [32:0] B:G:R:A */ +- return DRM_FORMAT_BGRA8888; +- case V4L2_PIX_FMT_XRGB32: +- /* X R G B <=> [32:0] B:G:R:X */ +- return DRM_FORMAT_BGRX8888; +- case V4L2_PIX_FMT_UYVY: +- return DRM_FORMAT_UYVY; +- case V4L2_PIX_FMT_YUYV: +- return DRM_FORMAT_YUYV; +- case V4L2_PIX_FMT_YUV420: +- return DRM_FORMAT_YUV420; +- case V4L2_PIX_FMT_YUV422P: +- return DRM_FORMAT_YUV422; +- case V4L2_PIX_FMT_YVU420: +- return DRM_FORMAT_YVU420; +- case V4L2_PIX_FMT_NV12: +- return DRM_FORMAT_NV12; +- case V4L2_PIX_FMT_NV16: +- return DRM_FORMAT_NV16; +- } +- +- return -EINVAL; +-} +- +-void ipu_cpmem_zero(struct ipuv3_channel *ch) +-{ +- struct ipu_ch_param __iomem *p = ipu_get_cpmem(ch); +- void __iomem *base = p; +- int i; +- +- for (i = 0; i < sizeof(*p) / sizeof(u32); i++) +- writel(0, base + i * sizeof(u32)); +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_zero); +- +-void ipu_cpmem_set_resolution(struct ipuv3_channel *ch, int xres, int yres) +-{ +- ipu_ch_param_write_field(ch, IPU_FIELD_FW, xres - 1); +- ipu_ch_param_write_field(ch, IPU_FIELD_FH, yres - 1); +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_set_resolution); +- +-void ipu_cpmem_skip_odd_chroma_rows(struct ipuv3_channel *ch) +-{ +- ipu_ch_param_write_field(ch, IPU_FIELD_RDRW, 1); +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_skip_odd_chroma_rows); +- +-void ipu_cpmem_set_stride(struct ipuv3_channel *ch, int stride) +-{ +- ipu_ch_param_write_field(ch, IPU_FIELD_SLY, stride - 1); +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_set_stride); +- +-void ipu_cpmem_set_high_priority(struct ipuv3_channel *ch) +-{ +- struct ipu_soc *ipu = ch->ipu; +- u32 val; +- +- if (ipu->ipu_type == IPUV3EX) +- ipu_ch_param_write_field(ch, IPU_FIELD_ID, 1); +- +- val = ipu_idmac_read(ipu, IDMAC_CHA_PRI(ch->num)); +- val |= 1 << (ch->num % 32); +- ipu_idmac_write(ipu, val, IDMAC_CHA_PRI(ch->num)); +-}; +-EXPORT_SYMBOL_GPL(ipu_cpmem_set_high_priority); +- +-void ipu_cpmem_set_buffer(struct ipuv3_channel *ch, int bufnum, dma_addr_t buf) +-{ +- WARN_ON_ONCE(buf & 0x7); +- +- if (bufnum) +- ipu_ch_param_write_field(ch, IPU_FIELD_EBA1, buf >> 3); +- else +- ipu_ch_param_write_field(ch, IPU_FIELD_EBA0, buf >> 3); +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_set_buffer); +- +-void ipu_cpmem_set_uv_offset(struct ipuv3_channel *ch, u32 u_off, u32 v_off) +-{ +- WARN_ON_ONCE((u_off & 0x7) || (v_off & 0x7)); +- +- ipu_ch_param_write_field(ch, IPU_FIELD_UBO, u_off / 8); +- ipu_ch_param_write_field(ch, IPU_FIELD_VBO, v_off / 8); +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_set_uv_offset); +- +-void ipu_cpmem_interlaced_scan(struct ipuv3_channel *ch, int stride, +- u32 pixelformat) +-{ +- u32 ilo, sly, sluv; +- +- if (stride < 0) { +- stride = -stride; +- ilo = 0x100000 - (stride / 8); +- } else { +- ilo = stride / 8; +- } +- +- sly = (stride * 2) - 1; +- +- switch (pixelformat) { +- case V4L2_PIX_FMT_YUV420: +- case V4L2_PIX_FMT_YVU420: +- sluv = stride / 2 - 1; +- break; +- case V4L2_PIX_FMT_NV12: +- sluv = stride - 1; +- break; +- case V4L2_PIX_FMT_YUV422P: +- sluv = stride - 1; +- break; +- case V4L2_PIX_FMT_NV16: +- sluv = stride * 2 - 1; +- break; +- default: +- sluv = 0; +- break; +- } +- +- ipu_ch_param_write_field(ch, IPU_FIELD_SO, 1); +- ipu_ch_param_write_field(ch, IPU_FIELD_ILO, ilo); +- ipu_ch_param_write_field(ch, IPU_FIELD_SLY, sly); +- if (sluv) +- ipu_ch_param_write_field(ch, IPU_FIELD_SLUV, sluv); +-}; +-EXPORT_SYMBOL_GPL(ipu_cpmem_interlaced_scan); +- +-void ipu_cpmem_set_axi_id(struct ipuv3_channel *ch, u32 id) +-{ +- id &= 0x3; +- ipu_ch_param_write_field(ch, IPU_FIELD_ID, id); +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_set_axi_id); +- +-int ipu_cpmem_get_burstsize(struct ipuv3_channel *ch) +-{ +- return ipu_ch_param_read_field(ch, IPU_FIELD_NPB) + 1; +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_get_burstsize); +- +-void ipu_cpmem_set_burstsize(struct ipuv3_channel *ch, int burstsize) +-{ +- ipu_ch_param_write_field(ch, IPU_FIELD_NPB, burstsize - 1); +-}; +-EXPORT_SYMBOL_GPL(ipu_cpmem_set_burstsize); +- +-void ipu_cpmem_set_block_mode(struct ipuv3_channel *ch) +-{ +- ipu_ch_param_write_field(ch, IPU_FIELD_BM, 1); +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_set_block_mode); +- +-void ipu_cpmem_set_rotation(struct ipuv3_channel *ch, +- enum ipu_rotate_mode rot) +-{ +- u32 temp_rot = bitrev8(rot) >> 5; +- +- ipu_ch_param_write_field(ch, IPU_FIELD_ROT_HF_VF, temp_rot); +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_set_rotation); +- +-int ipu_cpmem_set_format_rgb(struct ipuv3_channel *ch, +- const struct ipu_rgb *rgb) +-{ +- int bpp = 0, npb = 0, ro, go, bo, to; +- +- ro = rgb->bits_per_pixel - rgb->red.length - rgb->red.offset; +- go = rgb->bits_per_pixel - rgb->green.length - rgb->green.offset; +- bo = rgb->bits_per_pixel - rgb->blue.length - rgb->blue.offset; +- to = rgb->bits_per_pixel - rgb->transp.length - rgb->transp.offset; +- +- ipu_ch_param_write_field(ch, IPU_FIELD_WID0, rgb->red.length - 1); +- ipu_ch_param_write_field(ch, IPU_FIELD_OFS0, ro); +- ipu_ch_param_write_field(ch, IPU_FIELD_WID1, rgb->green.length - 1); +- ipu_ch_param_write_field(ch, IPU_FIELD_OFS1, go); +- ipu_ch_param_write_field(ch, IPU_FIELD_WID2, rgb->blue.length - 1); +- ipu_ch_param_write_field(ch, IPU_FIELD_OFS2, bo); +- +- if (rgb->transp.length) { +- ipu_ch_param_write_field(ch, IPU_FIELD_WID3, +- rgb->transp.length - 1); +- ipu_ch_param_write_field(ch, IPU_FIELD_OFS3, to); +- } else { +- ipu_ch_param_write_field(ch, IPU_FIELD_WID3, 7); +- ipu_ch_param_write_field(ch, IPU_FIELD_OFS3, +- rgb->bits_per_pixel); +- } +- +- switch (rgb->bits_per_pixel) { +- case 32: +- bpp = 0; +- npb = 15; +- break; +- case 24: +- bpp = 1; +- npb = 19; +- break; +- case 16: +- bpp = 3; +- npb = 31; +- break; +- case 8: +- bpp = 5; +- npb = 63; +- break; +- default: +- return -EINVAL; +- } +- ipu_ch_param_write_field(ch, IPU_FIELD_BPP, bpp); +- ipu_ch_param_write_field(ch, IPU_FIELD_NPB, npb); +- ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 7); /* rgb mode */ +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_set_format_rgb); +- +-int ipu_cpmem_set_format_passthrough(struct ipuv3_channel *ch, int width) +-{ +- int bpp = 0, npb = 0; +- +- switch (width) { +- case 32: +- bpp = 0; +- npb = 15; +- break; +- case 24: +- bpp = 1; +- npb = 19; +- break; +- case 16: +- bpp = 3; +- npb = 31; +- break; +- case 8: +- bpp = 5; +- npb = 63; +- break; +- default: +- return -EINVAL; +- } +- +- ipu_ch_param_write_field(ch, IPU_FIELD_BPP, bpp); +- ipu_ch_param_write_field(ch, IPU_FIELD_NPB, npb); +- ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 6); /* raw mode */ +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_set_format_passthrough); +- +-void ipu_cpmem_set_yuv_interleaved(struct ipuv3_channel *ch, u32 pixel_format) +-{ +- switch (pixel_format) { +- case V4L2_PIX_FMT_UYVY: +- ipu_ch_param_write_field(ch, IPU_FIELD_BPP, 3); /* bits/pixel */ +- ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 0xA);/* pix fmt */ +- ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31);/* burst size */ +- break; +- case V4L2_PIX_FMT_YUYV: +- ipu_ch_param_write_field(ch, IPU_FIELD_BPP, 3); /* bits/pixel */ +- ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 0x8);/* pix fmt */ +- ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31);/* burst size */ +- break; +- } +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_set_yuv_interleaved); +- +-void ipu_cpmem_set_yuv_planar_full(struct ipuv3_channel *ch, +- unsigned int uv_stride, +- unsigned int u_offset, unsigned int v_offset) +-{ +- WARN_ON_ONCE((u_offset & 0x7) || (v_offset & 0x7)); +- +- ipu_ch_param_write_field(ch, IPU_FIELD_SLUV, uv_stride - 1); +- ipu_ch_param_write_field(ch, IPU_FIELD_UBO, u_offset / 8); +- ipu_ch_param_write_field(ch, IPU_FIELD_VBO, v_offset / 8); +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_set_yuv_planar_full); +- +-static const struct ipu_rgb def_xrgb_32 = { +- .red = { .offset = 16, .length = 8, }, +- .green = { .offset = 8, .length = 8, }, +- .blue = { .offset = 0, .length = 8, }, +- .transp = { .offset = 24, .length = 8, }, +- .bits_per_pixel = 32, +-}; +- +-static const struct ipu_rgb def_xbgr_32 = { +- .red = { .offset = 0, .length = 8, }, +- .green = { .offset = 8, .length = 8, }, +- .blue = { .offset = 16, .length = 8, }, +- .transp = { .offset = 24, .length = 8, }, +- .bits_per_pixel = 32, +-}; +- +-static const struct ipu_rgb def_rgbx_32 = { +- .red = { .offset = 24, .length = 8, }, +- .green = { .offset = 16, .length = 8, }, +- .blue = { .offset = 8, .length = 8, }, +- .transp = { .offset = 0, .length = 8, }, +- .bits_per_pixel = 32, +-}; +- +-static const struct ipu_rgb def_bgrx_32 = { +- .red = { .offset = 8, .length = 8, }, +- .green = { .offset = 16, .length = 8, }, +- .blue = { .offset = 24, .length = 8, }, +- .transp = { .offset = 0, .length = 8, }, +- .bits_per_pixel = 32, +-}; +- +-static const struct ipu_rgb def_rgb_24 = { +- .red = { .offset = 16, .length = 8, }, +- .green = { .offset = 8, .length = 8, }, +- .blue = { .offset = 0, .length = 8, }, +- .transp = { .offset = 0, .length = 0, }, +- .bits_per_pixel = 24, +-}; +- +-static const struct ipu_rgb def_bgr_24 = { +- .red = { .offset = 0, .length = 8, }, +- .green = { .offset = 8, .length = 8, }, +- .blue = { .offset = 16, .length = 8, }, +- .transp = { .offset = 0, .length = 0, }, +- .bits_per_pixel = 24, +-}; +- +-static const struct ipu_rgb def_rgb_16 = { +- .red = { .offset = 11, .length = 5, }, +- .green = { .offset = 5, .length = 6, }, +- .blue = { .offset = 0, .length = 5, }, +- .transp = { .offset = 0, .length = 0, }, +- .bits_per_pixel = 16, +-}; +- +-static const struct ipu_rgb def_bgr_16 = { +- .red = { .offset = 0, .length = 5, }, +- .green = { .offset = 5, .length = 6, }, +- .blue = { .offset = 11, .length = 5, }, +- .transp = { .offset = 0, .length = 0, }, +- .bits_per_pixel = 16, +-}; +- +-static const struct ipu_rgb def_argb_16 = { +- .red = { .offset = 10, .length = 5, }, +- .green = { .offset = 5, .length = 5, }, +- .blue = { .offset = 0, .length = 5, }, +- .transp = { .offset = 15, .length = 1, }, +- .bits_per_pixel = 16, +-}; +- +-static const struct ipu_rgb def_argb_16_4444 = { +- .red = { .offset = 8, .length = 4, }, +- .green = { .offset = 4, .length = 4, }, +- .blue = { .offset = 0, .length = 4, }, +- .transp = { .offset = 12, .length = 4, }, +- .bits_per_pixel = 16, +-}; +- +-static const struct ipu_rgb def_abgr_16 = { +- .red = { .offset = 0, .length = 5, }, +- .green = { .offset = 5, .length = 5, }, +- .blue = { .offset = 10, .length = 5, }, +- .transp = { .offset = 15, .length = 1, }, +- .bits_per_pixel = 16, +-}; +- +-static const struct ipu_rgb def_rgba_16 = { +- .red = { .offset = 11, .length = 5, }, +- .green = { .offset = 6, .length = 5, }, +- .blue = { .offset = 1, .length = 5, }, +- .transp = { .offset = 0, .length = 1, }, +- .bits_per_pixel = 16, +-}; +- +-static const struct ipu_rgb def_bgra_16 = { +- .red = { .offset = 1, .length = 5, }, +- .green = { .offset = 6, .length = 5, }, +- .blue = { .offset = 11, .length = 5, }, +- .transp = { .offset = 0, .length = 1, }, +- .bits_per_pixel = 16, +-}; +- +-#define Y_OFFSET(pix, x, y) ((x) + pix->width * (y)) +-#define U_OFFSET(pix, x, y) ((pix->width * pix->height) + \ +- (pix->width * ((y) / 2) / 2) + (x) / 2) +-#define V_OFFSET(pix, x, y) ((pix->width * pix->height) + \ +- (pix->width * pix->height / 4) + \ +- (pix->width * ((y) / 2) / 2) + (x) / 2) +-#define U2_OFFSET(pix, x, y) ((pix->width * pix->height) + \ +- (pix->width * (y) / 2) + (x) / 2) +-#define V2_OFFSET(pix, x, y) ((pix->width * pix->height) + \ +- (pix->width * pix->height / 2) + \ +- (pix->width * (y) / 2) + (x) / 2) +-#define UV_OFFSET(pix, x, y) ((pix->width * pix->height) + \ +- (pix->width * ((y) / 2)) + (x)) +-#define UV2_OFFSET(pix, x, y) ((pix->width * pix->height) + \ +- (pix->width * y) + (x)) +- +-#define NUM_ALPHA_CHANNELS 7 +- +-/* See Table 37-12. Alpha channels mapping. */ +-static int ipu_channel_albm(int ch_num) +-{ +- switch (ch_num) { +- case IPUV3_CHANNEL_G_MEM_IC_PRP_VF: return 0; +- case IPUV3_CHANNEL_G_MEM_IC_PP: return 1; +- case IPUV3_CHANNEL_MEM_FG_SYNC: return 2; +- case IPUV3_CHANNEL_MEM_FG_ASYNC: return 3; +- case IPUV3_CHANNEL_MEM_BG_SYNC: return 4; +- case IPUV3_CHANNEL_MEM_BG_ASYNC: return 5; +- case IPUV3_CHANNEL_MEM_VDI_PLANE1_COMB: return 6; +- default: +- return -EINVAL; +- } +-} +- +-static void ipu_cpmem_set_separate_alpha(struct ipuv3_channel *ch) +-{ +- struct ipu_soc *ipu = ch->ipu; +- int albm; +- u32 val; +- +- albm = ipu_channel_albm(ch->num); +- if (albm < 0) +- return; +- +- ipu_ch_param_write_field(ch, IPU_FIELD_ALU, 1); +- ipu_ch_param_write_field(ch, IPU_FIELD_ALBM, albm); +- ipu_ch_param_write_field(ch, IPU_FIELD_CRE, 1); +- +- val = ipu_idmac_read(ipu, IDMAC_SEP_ALPHA); +- val |= BIT(ch->num); +- ipu_idmac_write(ipu, val, IDMAC_SEP_ALPHA); +-} +- +-int ipu_cpmem_set_fmt(struct ipuv3_channel *ch, u32 drm_fourcc) +-{ +- switch (drm_fourcc) { +- case DRM_FORMAT_YUV420: +- case DRM_FORMAT_YVU420: +- /* pix format */ +- ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 2); +- /* burst size */ +- ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31); +- break; +- case DRM_FORMAT_YUV422: +- case DRM_FORMAT_YVU422: +- /* pix format */ +- ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 1); +- /* burst size */ +- ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31); +- break; +- case DRM_FORMAT_YUV444: +- case DRM_FORMAT_YVU444: +- /* pix format */ +- ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 0); +- /* burst size */ +- ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31); +- break; +- case DRM_FORMAT_NV12: +- /* pix format */ +- ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 4); +- /* burst size */ +- ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31); +- break; +- case DRM_FORMAT_NV16: +- /* pix format */ +- ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 3); +- /* burst size */ +- ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31); +- break; +- case DRM_FORMAT_UYVY: +- /* bits/pixel */ +- ipu_ch_param_write_field(ch, IPU_FIELD_BPP, 3); +- /* pix format */ +- ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 0xA); +- /* burst size */ +- ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31); +- break; +- case DRM_FORMAT_YUYV: +- /* bits/pixel */ +- ipu_ch_param_write_field(ch, IPU_FIELD_BPP, 3); +- /* pix format */ +- ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 0x8); +- /* burst size */ +- ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31); +- break; +- case DRM_FORMAT_ABGR8888: +- case DRM_FORMAT_XBGR8888: +- ipu_cpmem_set_format_rgb(ch, &def_xbgr_32); +- break; +- case DRM_FORMAT_ARGB8888: +- case DRM_FORMAT_XRGB8888: +- ipu_cpmem_set_format_rgb(ch, &def_xrgb_32); +- break; +- case DRM_FORMAT_RGBA8888: +- case DRM_FORMAT_RGBX8888: +- case DRM_FORMAT_RGBX8888_A8: +- ipu_cpmem_set_format_rgb(ch, &def_rgbx_32); +- break; +- case DRM_FORMAT_BGRA8888: +- case DRM_FORMAT_BGRX8888: +- case DRM_FORMAT_BGRX8888_A8: +- ipu_cpmem_set_format_rgb(ch, &def_bgrx_32); +- break; +- case DRM_FORMAT_BGR888: +- case DRM_FORMAT_BGR888_A8: +- ipu_cpmem_set_format_rgb(ch, &def_bgr_24); +- break; +- case DRM_FORMAT_RGB888: +- case DRM_FORMAT_RGB888_A8: +- ipu_cpmem_set_format_rgb(ch, &def_rgb_24); +- break; +- case DRM_FORMAT_RGB565: +- case DRM_FORMAT_RGB565_A8: +- ipu_cpmem_set_format_rgb(ch, &def_rgb_16); +- break; +- case DRM_FORMAT_BGR565: +- case DRM_FORMAT_BGR565_A8: +- ipu_cpmem_set_format_rgb(ch, &def_bgr_16); +- break; +- case DRM_FORMAT_ARGB1555: +- ipu_cpmem_set_format_rgb(ch, &def_argb_16); +- break; +- case DRM_FORMAT_ABGR1555: +- ipu_cpmem_set_format_rgb(ch, &def_abgr_16); +- break; +- case DRM_FORMAT_RGBA5551: +- ipu_cpmem_set_format_rgb(ch, &def_rgba_16); +- break; +- case DRM_FORMAT_BGRA5551: +- ipu_cpmem_set_format_rgb(ch, &def_bgra_16); +- break; +- case DRM_FORMAT_ARGB4444: +- ipu_cpmem_set_format_rgb(ch, &def_argb_16_4444); +- break; +- default: +- return -EINVAL; +- } +- +- switch (drm_fourcc) { +- case DRM_FORMAT_RGB565_A8: +- case DRM_FORMAT_BGR565_A8: +- case DRM_FORMAT_RGB888_A8: +- case DRM_FORMAT_BGR888_A8: +- case DRM_FORMAT_RGBX8888_A8: +- case DRM_FORMAT_BGRX8888_A8: +- ipu_ch_param_write_field(ch, IPU_FIELD_WID3, 7); +- ipu_cpmem_set_separate_alpha(ch); +- break; +- default: +- break; +- } +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_set_fmt); +- +-int ipu_cpmem_set_image(struct ipuv3_channel *ch, struct ipu_image *image) +-{ +- struct v4l2_pix_format *pix = &image->pix; +- int offset, u_offset, v_offset; +- int ret = 0; +- +- pr_debug("%s: resolution: %dx%d stride: %d\n", +- __func__, pix->width, pix->height, +- pix->bytesperline); +- +- ipu_cpmem_set_resolution(ch, image->rect.width, image->rect.height); +- ipu_cpmem_set_stride(ch, pix->bytesperline); +- +- ipu_cpmem_set_fmt(ch, v4l2_pix_fmt_to_drm_fourcc(pix->pixelformat)); +- +- switch (pix->pixelformat) { +- case V4L2_PIX_FMT_YUV420: +- offset = Y_OFFSET(pix, image->rect.left, image->rect.top); +- u_offset = image->u_offset ? +- image->u_offset : U_OFFSET(pix, image->rect.left, +- image->rect.top) - offset; +- v_offset = image->v_offset ? +- image->v_offset : V_OFFSET(pix, image->rect.left, +- image->rect.top) - offset; +- +- ipu_cpmem_set_yuv_planar_full(ch, pix->bytesperline / 2, +- u_offset, v_offset); +- break; +- case V4L2_PIX_FMT_YVU420: +- offset = Y_OFFSET(pix, image->rect.left, image->rect.top); +- u_offset = image->u_offset ? +- image->u_offset : V_OFFSET(pix, image->rect.left, +- image->rect.top) - offset; +- v_offset = image->v_offset ? +- image->v_offset : U_OFFSET(pix, image->rect.left, +- image->rect.top) - offset; +- +- ipu_cpmem_set_yuv_planar_full(ch, pix->bytesperline / 2, +- u_offset, v_offset); +- break; +- case V4L2_PIX_FMT_YUV422P: +- offset = Y_OFFSET(pix, image->rect.left, image->rect.top); +- u_offset = image->u_offset ? +- image->u_offset : U2_OFFSET(pix, image->rect.left, +- image->rect.top) - offset; +- v_offset = image->v_offset ? +- image->v_offset : V2_OFFSET(pix, image->rect.left, +- image->rect.top) - offset; +- +- ipu_cpmem_set_yuv_planar_full(ch, pix->bytesperline / 2, +- u_offset, v_offset); +- break; +- case V4L2_PIX_FMT_NV12: +- offset = Y_OFFSET(pix, image->rect.left, image->rect.top); +- u_offset = image->u_offset ? +- image->u_offset : UV_OFFSET(pix, image->rect.left, +- image->rect.top) - offset; +- v_offset = image->v_offset ? image->v_offset : 0; +- +- ipu_cpmem_set_yuv_planar_full(ch, pix->bytesperline, +- u_offset, v_offset); +- break; +- case V4L2_PIX_FMT_NV16: +- offset = Y_OFFSET(pix, image->rect.left, image->rect.top); +- u_offset = image->u_offset ? +- image->u_offset : UV2_OFFSET(pix, image->rect.left, +- image->rect.top) - offset; +- v_offset = image->v_offset ? image->v_offset : 0; +- +- ipu_cpmem_set_yuv_planar_full(ch, pix->bytesperline, +- u_offset, v_offset); +- break; +- case V4L2_PIX_FMT_UYVY: +- case V4L2_PIX_FMT_YUYV: +- case V4L2_PIX_FMT_RGB565: +- offset = image->rect.left * 2 + +- image->rect.top * pix->bytesperline; +- break; +- case V4L2_PIX_FMT_RGB32: +- case V4L2_PIX_FMT_BGR32: +- case V4L2_PIX_FMT_ABGR32: +- case V4L2_PIX_FMT_XBGR32: +- case V4L2_PIX_FMT_BGRA32: +- case V4L2_PIX_FMT_BGRX32: +- case V4L2_PIX_FMT_RGBA32: +- case V4L2_PIX_FMT_RGBX32: +- case V4L2_PIX_FMT_ARGB32: +- case V4L2_PIX_FMT_XRGB32: +- offset = image->rect.left * 4 + +- image->rect.top * pix->bytesperline; +- break; +- case V4L2_PIX_FMT_RGB24: +- case V4L2_PIX_FMT_BGR24: +- offset = image->rect.left * 3 + +- image->rect.top * pix->bytesperline; +- break; +- case V4L2_PIX_FMT_SBGGR8: +- case V4L2_PIX_FMT_SGBRG8: +- case V4L2_PIX_FMT_SGRBG8: +- case V4L2_PIX_FMT_SRGGB8: +- case V4L2_PIX_FMT_GREY: +- offset = image->rect.left + image->rect.top * pix->bytesperline; +- break; +- case V4L2_PIX_FMT_SBGGR16: +- case V4L2_PIX_FMT_SGBRG16: +- case V4L2_PIX_FMT_SGRBG16: +- case V4L2_PIX_FMT_SRGGB16: +- case V4L2_PIX_FMT_Y16: +- offset = image->rect.left * 2 + +- image->rect.top * pix->bytesperline; +- break; +- default: +- /* This should not happen */ +- WARN_ON(1); +- offset = 0; +- ret = -EINVAL; +- } +- +- ipu_cpmem_set_buffer(ch, 0, image->phys0 + offset); +- ipu_cpmem_set_buffer(ch, 1, image->phys1 + offset); +- +- return ret; +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_set_image); +- +-void ipu_cpmem_dump(struct ipuv3_channel *ch) +-{ +- struct ipu_ch_param __iomem *p = ipu_get_cpmem(ch); +- struct ipu_soc *ipu = ch->ipu; +- int chno = ch->num; +- +- dev_dbg(ipu->dev, "ch %d word 0 - %08X %08X %08X %08X %08X\n", chno, +- readl(&p->word[0].data[0]), +- readl(&p->word[0].data[1]), +- readl(&p->word[0].data[2]), +- readl(&p->word[0].data[3]), +- readl(&p->word[0].data[4])); +- dev_dbg(ipu->dev, "ch %d word 1 - %08X %08X %08X %08X %08X\n", chno, +- readl(&p->word[1].data[0]), +- readl(&p->word[1].data[1]), +- readl(&p->word[1].data[2]), +- readl(&p->word[1].data[3]), +- readl(&p->word[1].data[4])); +- dev_dbg(ipu->dev, "PFS 0x%x, ", +- ipu_ch_param_read_field(ch, IPU_FIELD_PFS)); +- dev_dbg(ipu->dev, "BPP 0x%x, ", +- ipu_ch_param_read_field(ch, IPU_FIELD_BPP)); +- dev_dbg(ipu->dev, "NPB 0x%x\n", +- ipu_ch_param_read_field(ch, IPU_FIELD_NPB)); +- +- dev_dbg(ipu->dev, "FW %d, ", +- ipu_ch_param_read_field(ch, IPU_FIELD_FW)); +- dev_dbg(ipu->dev, "FH %d, ", +- ipu_ch_param_read_field(ch, IPU_FIELD_FH)); +- dev_dbg(ipu->dev, "EBA0 0x%x\n", +- ipu_ch_param_read_field(ch, IPU_FIELD_EBA0) << 3); +- dev_dbg(ipu->dev, "EBA1 0x%x\n", +- ipu_ch_param_read_field(ch, IPU_FIELD_EBA1) << 3); +- dev_dbg(ipu->dev, "Stride %d\n", +- ipu_ch_param_read_field(ch, IPU_FIELD_SL)); +- dev_dbg(ipu->dev, "scan_order %d\n", +- ipu_ch_param_read_field(ch, IPU_FIELD_SO)); +- dev_dbg(ipu->dev, "uv_stride %d\n", +- ipu_ch_param_read_field(ch, IPU_FIELD_SLUV)); +- dev_dbg(ipu->dev, "u_offset 0x%x\n", +- ipu_ch_param_read_field(ch, IPU_FIELD_UBO) << 3); +- dev_dbg(ipu->dev, "v_offset 0x%x\n", +- ipu_ch_param_read_field(ch, IPU_FIELD_VBO) << 3); +- +- dev_dbg(ipu->dev, "Width0 %d+1, ", +- ipu_ch_param_read_field(ch, IPU_FIELD_WID0)); +- dev_dbg(ipu->dev, "Width1 %d+1, ", +- ipu_ch_param_read_field(ch, IPU_FIELD_WID1)); +- dev_dbg(ipu->dev, "Width2 %d+1, ", +- ipu_ch_param_read_field(ch, IPU_FIELD_WID2)); +- dev_dbg(ipu->dev, "Width3 %d+1, ", +- ipu_ch_param_read_field(ch, IPU_FIELD_WID3)); +- dev_dbg(ipu->dev, "Offset0 %d, ", +- ipu_ch_param_read_field(ch, IPU_FIELD_OFS0)); +- dev_dbg(ipu->dev, "Offset1 %d, ", +- ipu_ch_param_read_field(ch, IPU_FIELD_OFS1)); +- dev_dbg(ipu->dev, "Offset2 %d, ", +- ipu_ch_param_read_field(ch, IPU_FIELD_OFS2)); +- dev_dbg(ipu->dev, "Offset3 %d\n", +- ipu_ch_param_read_field(ch, IPU_FIELD_OFS3)); +-} +-EXPORT_SYMBOL_GPL(ipu_cpmem_dump); +- +-int ipu_cpmem_init(struct ipu_soc *ipu, struct device *dev, unsigned long base) +-{ +- struct ipu_cpmem *cpmem; +- +- cpmem = devm_kzalloc(dev, sizeof(*cpmem), GFP_KERNEL); +- if (!cpmem) +- return -ENOMEM; +- +- ipu->cpmem_priv = cpmem; +- +- spin_lock_init(&cpmem->lock); +- cpmem->base = devm_ioremap(dev, base, SZ_128K); +- if (!cpmem->base) +- return -ENOMEM; +- +- dev_dbg(dev, "CPMEM base: 0x%08lx remapped to %p\n", +- base, cpmem->base); +- cpmem->ipu = ipu; +- +- return 0; +-} +- +-void ipu_cpmem_exit(struct ipu_soc *ipu) +-{ +-} +--- a/drivers/gpu/imx/ipu-v3/ipu-csi.c ++++ /dev/null +@@ -1,821 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-or-later +-/* +- * Copyright (C) 2012-2014 Mentor Graphics Inc. +- * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. +- */ +-#include <linux/export.h> +-#include <linux/module.h> +-#include <linux/types.h> +-#include <linux/errno.h> +-#include <linux/delay.h> +-#include <linux/io.h> +-#include <linux/err.h> +-#include <linux/platform_device.h> +-#include <linux/videodev2.h> +-#include <uapi/linux/v4l2-mediabus.h> +-#include <linux/clk.h> +-#include <linux/clk-provider.h> +-#include <linux/clkdev.h> +- +-#include "ipu-prv.h" +- +-struct ipu_csi { +- void __iomem *base; +- int id; +- u32 module; +- struct clk *clk_ipu; /* IPU bus clock */ +- spinlock_t lock; +- bool inuse; +- struct ipu_soc *ipu; +-}; +- +-/* CSI Register Offsets */ +-#define CSI_SENS_CONF 0x0000 +-#define CSI_SENS_FRM_SIZE 0x0004 +-#define CSI_ACT_FRM_SIZE 0x0008 +-#define CSI_OUT_FRM_CTRL 0x000c +-#define CSI_TST_CTRL 0x0010 +-#define CSI_CCIR_CODE_1 0x0014 +-#define CSI_CCIR_CODE_2 0x0018 +-#define CSI_CCIR_CODE_3 0x001c +-#define CSI_MIPI_DI 0x0020 +-#define CSI_SKIP 0x0024 +-#define CSI_CPD_CTRL 0x0028 +-#define CSI_CPD_RC(n) (0x002c + ((n)*4)) +-#define CSI_CPD_RS(n) (0x004c + ((n)*4)) +-#define CSI_CPD_GRC(n) (0x005c + ((n)*4)) +-#define CSI_CPD_GRS(n) (0x007c + ((n)*4)) +-#define CSI_CPD_GBC(n) (0x008c + ((n)*4)) +-#define CSI_CPD_GBS(n) (0x00Ac + ((n)*4)) +-#define CSI_CPD_BC(n) (0x00Bc + ((n)*4)) +-#define CSI_CPD_BS(n) (0x00Dc + ((n)*4)) +-#define CSI_CPD_OFFSET1 0x00ec +-#define CSI_CPD_OFFSET2 0x00f0 +- +-/* CSI Register Fields */ +-#define CSI_SENS_CONF_DATA_FMT_SHIFT 8 +-#define CSI_SENS_CONF_DATA_FMT_MASK 0x00000700 +-#define CSI_SENS_CONF_DATA_FMT_RGB_YUV444 0L +-#define CSI_SENS_CONF_DATA_FMT_YUV422_YUYV 1L +-#define CSI_SENS_CONF_DATA_FMT_YUV422_UYVY 2L +-#define CSI_SENS_CONF_DATA_FMT_BAYER 3L +-#define CSI_SENS_CONF_DATA_FMT_RGB565 4L +-#define CSI_SENS_CONF_DATA_FMT_RGB555 5L +-#define CSI_SENS_CONF_DATA_FMT_RGB444 6L +-#define CSI_SENS_CONF_DATA_FMT_JPEG 7L +- +-#define CSI_SENS_CONF_VSYNC_POL_SHIFT 0 +-#define CSI_SENS_CONF_HSYNC_POL_SHIFT 1 +-#define CSI_SENS_CONF_DATA_POL_SHIFT 2 +-#define CSI_SENS_CONF_PIX_CLK_POL_SHIFT 3 +-#define CSI_SENS_CONF_SENS_PRTCL_MASK 0x00000070 +-#define CSI_SENS_CONF_SENS_PRTCL_SHIFT 4 +-#define CSI_SENS_CONF_PACK_TIGHT_SHIFT 7 +-#define CSI_SENS_CONF_DATA_WIDTH_SHIFT 11 +-#define CSI_SENS_CONF_EXT_VSYNC_SHIFT 15 +-#define CSI_SENS_CONF_DIVRATIO_SHIFT 16 +- +-#define CSI_SENS_CONF_DIVRATIO_MASK 0x00ff0000 +-#define CSI_SENS_CONF_DATA_DEST_SHIFT 24 +-#define CSI_SENS_CONF_DATA_DEST_MASK 0x07000000 +-#define CSI_SENS_CONF_JPEG8_EN_SHIFT 27 +-#define CSI_SENS_CONF_JPEG_EN_SHIFT 28 +-#define CSI_SENS_CONF_FORCE_EOF_SHIFT 29 +-#define CSI_SENS_CONF_DATA_EN_POL_SHIFT 31 +- +-#define CSI_DATA_DEST_IC 2 +-#define CSI_DATA_DEST_IDMAC 4 +- +-#define CSI_CCIR_ERR_DET_EN 0x01000000 +-#define CSI_HORI_DOWNSIZE_EN 0x80000000 +-#define CSI_VERT_DOWNSIZE_EN 0x40000000 +-#define CSI_TEST_GEN_MODE_EN 0x01000000 +- +-#define CSI_HSC_MASK 0x1fff0000 +-#define CSI_HSC_SHIFT 16 +-#define CSI_VSC_MASK 0x00000fff +-#define CSI_VSC_SHIFT 0 +- +-#define CSI_TEST_GEN_R_MASK 0x000000ff +-#define CSI_TEST_GEN_R_SHIFT 0 +-#define CSI_TEST_GEN_G_MASK 0x0000ff00 +-#define CSI_TEST_GEN_G_SHIFT 8 +-#define CSI_TEST_GEN_B_MASK 0x00ff0000 +-#define CSI_TEST_GEN_B_SHIFT 16 +- +-#define CSI_MAX_RATIO_SKIP_SMFC_MASK 0x00000007 +-#define CSI_MAX_RATIO_SKIP_SMFC_SHIFT 0 +-#define CSI_SKIP_SMFC_MASK 0x000000f8 +-#define CSI_SKIP_SMFC_SHIFT 3 +-#define CSI_ID_2_SKIP_MASK 0x00000300 +-#define CSI_ID_2_SKIP_SHIFT 8 +- +-#define CSI_COLOR_FIRST_ROW_MASK 0x00000002 +-#define CSI_COLOR_FIRST_COMP_MASK 0x00000001 +- +-/* MIPI CSI-2 data types */ +-#define MIPI_DT_YUV420 0x18 /* YYY.../UYVY.... */ +-#define MIPI_DT_YUV420_LEGACY 0x1a /* UYY.../VYY... */ +-#define MIPI_DT_YUV422 0x1e /* UYVY... */ +-#define MIPI_DT_RGB444 0x20 +-#define MIPI_DT_RGB555 0x21 +-#define MIPI_DT_RGB565 0x22 +-#define MIPI_DT_RGB666 0x23 +-#define MIPI_DT_RGB888 0x24 +-#define MIPI_DT_RAW6 0x28 +-#define MIPI_DT_RAW7 0x29 +-#define MIPI_DT_RAW8 0x2a +-#define MIPI_DT_RAW10 0x2b +-#define MIPI_DT_RAW12 0x2c +-#define MIPI_DT_RAW14 0x2d +- +-/* +- * Bitfield of CSI bus signal polarities and modes. +- */ +-struct ipu_csi_bus_config { +- unsigned data_width:4; +- unsigned clk_mode:3; +- unsigned ext_vsync:1; +- unsigned vsync_pol:1; +- unsigned hsync_pol:1; +- unsigned pixclk_pol:1; +- unsigned data_pol:1; +- unsigned sens_clksrc:1; +- unsigned pack_tight:1; +- unsigned force_eof:1; +- unsigned data_en_pol:1; +- +- unsigned data_fmt; +- unsigned mipi_dt; +-}; +- +-/* +- * Enumeration of CSI data bus widths. +- */ +-enum ipu_csi_data_width { +- IPU_CSI_DATA_WIDTH_4 = 0, +- IPU_CSI_DATA_WIDTH_8 = 1, +- IPU_CSI_DATA_WIDTH_10 = 3, +- IPU_CSI_DATA_WIDTH_12 = 5, +- IPU_CSI_DATA_WIDTH_16 = 9, +-}; +- +-/* +- * Enumeration of CSI clock modes. +- */ +-enum ipu_csi_clk_mode { +- IPU_CSI_CLK_MODE_GATED_CLK, +- IPU_CSI_CLK_MODE_NONGATED_CLK, +- IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE, +- IPU_CSI_CLK_MODE_CCIR656_INTERLACED, +- IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR, +- IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR, +- IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR, +- IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR, +-}; +- +-static inline u32 ipu_csi_read(struct ipu_csi *csi, unsigned offset) +-{ +- return readl(csi->base + offset); +-} +- +-static inline void ipu_csi_write(struct ipu_csi *csi, u32 value, +- unsigned offset) +-{ +- writel(value, csi->base + offset); +-} +- +-/* +- * Set mclk division ratio for generating test mode mclk. Only used +- * for test generator. +- */ +-static int ipu_csi_set_testgen_mclk(struct ipu_csi *csi, u32 pixel_clk, +- u32 ipu_clk) +-{ +- u32 temp; +- int div_ratio; +- +- div_ratio = (ipu_clk / pixel_clk) - 1; +- +- if (div_ratio > 0xFF || div_ratio < 0) { +- dev_err(csi->ipu->dev, +- "value of pixel_clk extends normal range\n"); +- return -EINVAL; +- } +- +- temp = ipu_csi_read(csi, CSI_SENS_CONF); +- temp &= ~CSI_SENS_CONF_DIVRATIO_MASK; +- ipu_csi_write(csi, temp | (div_ratio << CSI_SENS_CONF_DIVRATIO_SHIFT), +- CSI_SENS_CONF); +- +- return 0; +-} +- +-/* +- * Find the CSI data format and data width for the given V4L2 media +- * bus pixel format code. +- */ +-static int mbus_code_to_bus_cfg(struct ipu_csi_bus_config *cfg, u32 mbus_code, +- enum v4l2_mbus_type mbus_type) +-{ +- switch (mbus_code) { +- case MEDIA_BUS_FMT_BGR565_2X8_BE: +- case MEDIA_BUS_FMT_BGR565_2X8_LE: +- case MEDIA_BUS_FMT_RGB565_2X8_BE: +- case MEDIA_BUS_FMT_RGB565_2X8_LE: +- if (mbus_type == V4L2_MBUS_CSI2_DPHY) +- cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_RGB565; +- else +- cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; +- cfg->mipi_dt = MIPI_DT_RGB565; +- cfg->data_width = IPU_CSI_DATA_WIDTH_8; +- break; +- case MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE: +- case MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE: +- cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_RGB444; +- cfg->mipi_dt = MIPI_DT_RGB444; +- cfg->data_width = IPU_CSI_DATA_WIDTH_8; +- break; +- case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE: +- case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE: +- cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_RGB555; +- cfg->mipi_dt = MIPI_DT_RGB555; +- cfg->data_width = IPU_CSI_DATA_WIDTH_8; +- break; +- case MEDIA_BUS_FMT_RGB888_1X24: +- case MEDIA_BUS_FMT_BGR888_1X24: +- cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_RGB_YUV444; +- cfg->mipi_dt = MIPI_DT_RGB888; +- cfg->data_width = IPU_CSI_DATA_WIDTH_8; +- break; +- case MEDIA_BUS_FMT_UYVY8_2X8: +- cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_UYVY; +- cfg->mipi_dt = MIPI_DT_YUV422; +- cfg->data_width = IPU_CSI_DATA_WIDTH_8; +- break; +- case MEDIA_BUS_FMT_YUYV8_2X8: +- cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_YUYV; +- cfg->mipi_dt = MIPI_DT_YUV422; +- cfg->data_width = IPU_CSI_DATA_WIDTH_8; +- break; +- case MEDIA_BUS_FMT_UYVY8_1X16: +- case MEDIA_BUS_FMT_YUYV8_1X16: +- cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; +- cfg->mipi_dt = MIPI_DT_YUV422; +- cfg->data_width = IPU_CSI_DATA_WIDTH_16; +- break; +- case MEDIA_BUS_FMT_SBGGR8_1X8: +- case MEDIA_BUS_FMT_SGBRG8_1X8: +- case MEDIA_BUS_FMT_SGRBG8_1X8: +- case MEDIA_BUS_FMT_SRGGB8_1X8: +- case MEDIA_BUS_FMT_Y8_1X8: +- cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; +- cfg->mipi_dt = MIPI_DT_RAW8; +- cfg->data_width = IPU_CSI_DATA_WIDTH_8; +- break; +- case MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8: +- case MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8: +- case MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8: +- case MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8: +- case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE: +- case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE: +- case MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_BE: +- case MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_LE: +- cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; +- cfg->mipi_dt = MIPI_DT_RAW10; +- cfg->data_width = IPU_CSI_DATA_WIDTH_8; +- break; +- case MEDIA_BUS_FMT_SBGGR10_1X10: +- case MEDIA_BUS_FMT_SGBRG10_1X10: +- case MEDIA_BUS_FMT_SGRBG10_1X10: +- case MEDIA_BUS_FMT_SRGGB10_1X10: +- case MEDIA_BUS_FMT_Y10_1X10: +- cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; +- cfg->mipi_dt = MIPI_DT_RAW10; +- cfg->data_width = IPU_CSI_DATA_WIDTH_10; +- break; +- case MEDIA_BUS_FMT_SBGGR12_1X12: +- case MEDIA_BUS_FMT_SGBRG12_1X12: +- case MEDIA_BUS_FMT_SGRBG12_1X12: +- case MEDIA_BUS_FMT_SRGGB12_1X12: +- case MEDIA_BUS_FMT_Y12_1X12: +- cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; +- cfg->mipi_dt = MIPI_DT_RAW12; +- cfg->data_width = IPU_CSI_DATA_WIDTH_12; +- break; +- case MEDIA_BUS_FMT_JPEG_1X8: +- /* TODO */ +- cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_JPEG; +- cfg->mipi_dt = MIPI_DT_RAW8; +- cfg->data_width = IPU_CSI_DATA_WIDTH_8; +- break; +- default: +- return -EINVAL; +- } +- +- return 0; +-} +- +-/* translate alternate field mode based on given standard */ +-static inline enum v4l2_field +-ipu_csi_translate_field(enum v4l2_field field, v4l2_std_id std) +-{ +- return (field != V4L2_FIELD_ALTERNATE) ? field : +- ((std & V4L2_STD_525_60) ? +- V4L2_FIELD_SEQ_BT : V4L2_FIELD_SEQ_TB); +-} +- +-/* +- * Fill a CSI bus config struct from mbus_config and mbus_framefmt. +- */ +-static int fill_csi_bus_cfg(struct ipu_csi_bus_config *csicfg, +- const struct v4l2_mbus_config *mbus_cfg, +- const struct v4l2_mbus_framefmt *mbus_fmt) +-{ +- int ret; +- +- memset(csicfg, 0, sizeof(*csicfg)); +- +- ret = mbus_code_to_bus_cfg(csicfg, mbus_fmt->code, mbus_cfg->type); +- if (ret < 0) +- return ret; +- +- switch (mbus_cfg->type) { +- case V4L2_MBUS_PARALLEL: +- csicfg->ext_vsync = 1; +- csicfg->vsync_pol = (mbus_cfg->flags & +- V4L2_MBUS_VSYNC_ACTIVE_LOW) ? 1 : 0; +- csicfg->hsync_pol = (mbus_cfg->flags & +- V4L2_MBUS_HSYNC_ACTIVE_LOW) ? 1 : 0; +- csicfg->pixclk_pol = (mbus_cfg->flags & +- V4L2_MBUS_PCLK_SAMPLE_FALLING) ? 1 : 0; +- csicfg->clk_mode = IPU_CSI_CLK_MODE_GATED_CLK; +- break; +- case V4L2_MBUS_BT656: +- csicfg->ext_vsync = 0; +- if (V4L2_FIELD_HAS_BOTH(mbus_fmt->field) || +- mbus_fmt->field == V4L2_FIELD_ALTERNATE) +- csicfg->clk_mode = IPU_CSI_CLK_MODE_CCIR656_INTERLACED; +- else +- csicfg->clk_mode = IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE; +- break; +- case V4L2_MBUS_CSI2_DPHY: +- /* +- * MIPI CSI-2 requires non gated clock mode, all other +- * parameters are not applicable for MIPI CSI-2 bus. +- */ +- csicfg->clk_mode = IPU_CSI_CLK_MODE_NONGATED_CLK; +- break; +- default: +- /* will never get here, keep compiler quiet */ +- break; +- } +- +- return 0; +-} +- +-static int +-ipu_csi_set_bt_interlaced_codes(struct ipu_csi *csi, +- const struct v4l2_mbus_framefmt *infmt, +- const struct v4l2_mbus_framefmt *outfmt, +- v4l2_std_id std) +-{ +- enum v4l2_field infield, outfield; +- bool swap_fields; +- +- /* get translated field type of input and output */ +- infield = ipu_csi_translate_field(infmt->field, std); +- outfield = ipu_csi_translate_field(outfmt->field, std); +- +- /* +- * Write the H-V-F codes the CSI will match against the +- * incoming data for start/end of active and blanking +- * field intervals. If input and output field types are +- * sequential but not the same (one is SEQ_BT and the other +- * is SEQ_TB), swap the F-bit so that the CSI will capture +- * field 1 lines before field 0 lines. +- */ +- swap_fields = (V4L2_FIELD_IS_SEQUENTIAL(infield) && +- V4L2_FIELD_IS_SEQUENTIAL(outfield) && +- infield != outfield); +- +- if (!swap_fields) { +- /* +- * Field0BlankEnd = 110, Field0BlankStart = 010 +- * Field0ActiveEnd = 100, Field0ActiveStart = 000 +- * Field1BlankEnd = 111, Field1BlankStart = 011 +- * Field1ActiveEnd = 101, Field1ActiveStart = 001 +- */ +- ipu_csi_write(csi, 0x40596 | CSI_CCIR_ERR_DET_EN, +- CSI_CCIR_CODE_1); +- ipu_csi_write(csi, 0xD07DF, CSI_CCIR_CODE_2); +- } else { +- dev_dbg(csi->ipu->dev, "capture field swap\n"); +- +- /* same as above but with F-bit inverted */ +- ipu_csi_write(csi, 0xD07DF | CSI_CCIR_ERR_DET_EN, +- CSI_CCIR_CODE_1); +- ipu_csi_write(csi, 0x40596, CSI_CCIR_CODE_2); +- } +- +- ipu_csi_write(csi, 0xFF0000, CSI_CCIR_CODE_3); +- +- return 0; +-} +- +- +-int ipu_csi_init_interface(struct ipu_csi *csi, +- const struct v4l2_mbus_config *mbus_cfg, +- const struct v4l2_mbus_framefmt *infmt, +- const struct v4l2_mbus_framefmt *outfmt) +-{ +- struct ipu_csi_bus_config cfg; +- unsigned long flags; +- u32 width, height, data = 0; +- v4l2_std_id std; +- int ret; +- +- ret = fill_csi_bus_cfg(&cfg, mbus_cfg, infmt); +- if (ret < 0) +- return ret; +- +- /* set default sensor frame width and height */ +- width = infmt->width; +- height = infmt->height; +- if (infmt->field == V4L2_FIELD_ALTERNATE) +- height *= 2; +- +- /* Set the CSI_SENS_CONF register remaining fields */ +- data |= cfg.data_width << CSI_SENS_CONF_DATA_WIDTH_SHIFT | +- cfg.data_fmt << CSI_SENS_CONF_DATA_FMT_SHIFT | +- cfg.data_pol << CSI_SENS_CONF_DATA_POL_SHIFT | +- cfg.vsync_pol << CSI_SENS_CONF_VSYNC_POL_SHIFT | +- cfg.hsync_pol << CSI_SENS_CONF_HSYNC_POL_SHIFT | +- cfg.pixclk_pol << CSI_SENS_CONF_PIX_CLK_POL_SHIFT | +- cfg.ext_vsync << CSI_SENS_CONF_EXT_VSYNC_SHIFT | +- cfg.clk_mode << CSI_SENS_CONF_SENS_PRTCL_SHIFT | +- cfg.pack_tight << CSI_SENS_CONF_PACK_TIGHT_SHIFT | +- cfg.force_eof << CSI_SENS_CONF_FORCE_EOF_SHIFT | +- cfg.data_en_pol << CSI_SENS_CONF_DATA_EN_POL_SHIFT; +- +- spin_lock_irqsave(&csi->lock, flags); +- +- ipu_csi_write(csi, data, CSI_SENS_CONF); +- +- /* Set CCIR registers */ +- +- switch (cfg.clk_mode) { +- case IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE: +- ipu_csi_write(csi, 0x40030, CSI_CCIR_CODE_1); +- ipu_csi_write(csi, 0xFF0000, CSI_CCIR_CODE_3); +- break; +- case IPU_CSI_CLK_MODE_CCIR656_INTERLACED: +- if (width == 720 && height == 480) { +- std = V4L2_STD_NTSC; +- height = 525; +- } else if (width == 720 && height == 576) { +- std = V4L2_STD_PAL; +- height = 625; +- } else { +- dev_err(csi->ipu->dev, +- "Unsupported interlaced video mode\n"); +- ret = -EINVAL; +- goto out_unlock; +- } +- +- ret = ipu_csi_set_bt_interlaced_codes(csi, infmt, outfmt, std); +- if (ret) +- goto out_unlock; +- break; +- case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR: +- case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR: +- case IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR: +- case IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR: +- ipu_csi_write(csi, 0x40030 | CSI_CCIR_ERR_DET_EN, +- CSI_CCIR_CODE_1); +- ipu_csi_write(csi, 0xFF0000, CSI_CCIR_CODE_3); +- break; +- case IPU_CSI_CLK_MODE_GATED_CLK: +- case IPU_CSI_CLK_MODE_NONGATED_CLK: +- ipu_csi_write(csi, 0, CSI_CCIR_CODE_1); +- break; +- } +- +- /* Setup sensor frame size */ +- ipu_csi_write(csi, (width - 1) | ((height - 1) << 16), +- CSI_SENS_FRM_SIZE); +- +- dev_dbg(csi->ipu->dev, "CSI_SENS_CONF = 0x%08X\n", +- ipu_csi_read(csi, CSI_SENS_CONF)); +- dev_dbg(csi->ipu->dev, "CSI_ACT_FRM_SIZE = 0x%08X\n", +- ipu_csi_read(csi, CSI_ACT_FRM_SIZE)); +- +-out_unlock: +- spin_unlock_irqrestore(&csi->lock, flags); +- +- return ret; +-} +-EXPORT_SYMBOL_GPL(ipu_csi_init_interface); +- +-bool ipu_csi_is_interlaced(struct ipu_csi *csi) +-{ +- unsigned long flags; +- u32 sensor_protocol; +- +- spin_lock_irqsave(&csi->lock, flags); +- sensor_protocol = +- (ipu_csi_read(csi, CSI_SENS_CONF) & +- CSI_SENS_CONF_SENS_PRTCL_MASK) >> +- CSI_SENS_CONF_SENS_PRTCL_SHIFT; +- spin_unlock_irqrestore(&csi->lock, flags); +- +- switch (sensor_protocol) { +- case IPU_CSI_CLK_MODE_GATED_CLK: +- case IPU_CSI_CLK_MODE_NONGATED_CLK: +- case IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE: +- case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR: +- case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR: +- return false; +- case IPU_CSI_CLK_MODE_CCIR656_INTERLACED: +- case IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR: +- case IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR: +- return true; +- default: +- dev_err(csi->ipu->dev, +- "CSI %d sensor protocol unsupported\n", csi->id); +- return false; +- } +-} +-EXPORT_SYMBOL_GPL(ipu_csi_is_interlaced); +- +-void ipu_csi_get_window(struct ipu_csi *csi, struct v4l2_rect *w) +-{ +- unsigned long flags; +- u32 reg; +- +- spin_lock_irqsave(&csi->lock, flags); +- +- reg = ipu_csi_read(csi, CSI_ACT_FRM_SIZE); +- w->width = (reg & 0xFFFF) + 1; +- w->height = (reg >> 16 & 0xFFFF) + 1; +- +- reg = ipu_csi_read(csi, CSI_OUT_FRM_CTRL); +- w->left = (reg & CSI_HSC_MASK) >> CSI_HSC_SHIFT; +- w->top = (reg & CSI_VSC_MASK) >> CSI_VSC_SHIFT; +- +- spin_unlock_irqrestore(&csi->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_csi_get_window); +- +-void ipu_csi_set_window(struct ipu_csi *csi, struct v4l2_rect *w) +-{ +- unsigned long flags; +- u32 reg; +- +- spin_lock_irqsave(&csi->lock, flags); +- +- ipu_csi_write(csi, (w->width - 1) | ((w->height - 1) << 16), +- CSI_ACT_FRM_SIZE); +- +- reg = ipu_csi_read(csi, CSI_OUT_FRM_CTRL); +- reg &= ~(CSI_HSC_MASK | CSI_VSC_MASK); +- reg |= ((w->top << CSI_VSC_SHIFT) | (w->left << CSI_HSC_SHIFT)); +- ipu_csi_write(csi, reg, CSI_OUT_FRM_CTRL); +- +- spin_unlock_irqrestore(&csi->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_csi_set_window); +- +-void ipu_csi_set_downsize(struct ipu_csi *csi, bool horiz, bool vert) +-{ +- unsigned long flags; +- u32 reg; +- +- spin_lock_irqsave(&csi->lock, flags); +- +- reg = ipu_csi_read(csi, CSI_OUT_FRM_CTRL); +- reg &= ~(CSI_HORI_DOWNSIZE_EN | CSI_VERT_DOWNSIZE_EN); +- reg |= (horiz ? CSI_HORI_DOWNSIZE_EN : 0) | +- (vert ? CSI_VERT_DOWNSIZE_EN : 0); +- ipu_csi_write(csi, reg, CSI_OUT_FRM_CTRL); +- +- spin_unlock_irqrestore(&csi->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_csi_set_downsize); +- +-void ipu_csi_set_test_generator(struct ipu_csi *csi, bool active, +- u32 r_value, u32 g_value, u32 b_value, +- u32 pix_clk) +-{ +- unsigned long flags; +- u32 ipu_clk = clk_get_rate(csi->clk_ipu); +- u32 temp; +- +- spin_lock_irqsave(&csi->lock, flags); +- +- temp = ipu_csi_read(csi, CSI_TST_CTRL); +- +- if (!active) { +- temp &= ~CSI_TEST_GEN_MODE_EN; +- ipu_csi_write(csi, temp, CSI_TST_CTRL); +- } else { +- /* Set sensb_mclk div_ratio */ +- ipu_csi_set_testgen_mclk(csi, pix_clk, ipu_clk); +- +- temp &= ~(CSI_TEST_GEN_R_MASK | CSI_TEST_GEN_G_MASK | +- CSI_TEST_GEN_B_MASK); +- temp |= CSI_TEST_GEN_MODE_EN; +- temp |= (r_value << CSI_TEST_GEN_R_SHIFT) | +- (g_value << CSI_TEST_GEN_G_SHIFT) | +- (b_value << CSI_TEST_GEN_B_SHIFT); +- ipu_csi_write(csi, temp, CSI_TST_CTRL); +- } +- +- spin_unlock_irqrestore(&csi->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_csi_set_test_generator); +- +-int ipu_csi_set_mipi_datatype(struct ipu_csi *csi, u32 vc, +- struct v4l2_mbus_framefmt *mbus_fmt) +-{ +- struct ipu_csi_bus_config cfg; +- unsigned long flags; +- u32 temp; +- int ret; +- +- if (vc > 3) +- return -EINVAL; +- +- ret = mbus_code_to_bus_cfg(&cfg, mbus_fmt->code, V4L2_MBUS_CSI2_DPHY); +- if (ret < 0) +- return ret; +- +- spin_lock_irqsave(&csi->lock, flags); +- +- temp = ipu_csi_read(csi, CSI_MIPI_DI); +- temp &= ~(0xff << (vc * 8)); +- temp |= (cfg.mipi_dt << (vc * 8)); +- ipu_csi_write(csi, temp, CSI_MIPI_DI); +- +- spin_unlock_irqrestore(&csi->lock, flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_csi_set_mipi_datatype); +- +-int ipu_csi_set_skip_smfc(struct ipu_csi *csi, u32 skip, +- u32 max_ratio, u32 id) +-{ +- unsigned long flags; +- u32 temp; +- +- if (max_ratio > 5 || id > 3) +- return -EINVAL; +- +- spin_lock_irqsave(&csi->lock, flags); +- +- temp = ipu_csi_read(csi, CSI_SKIP); +- temp &= ~(CSI_MAX_RATIO_SKIP_SMFC_MASK | CSI_ID_2_SKIP_MASK | +- CSI_SKIP_SMFC_MASK); +- temp |= (max_ratio << CSI_MAX_RATIO_SKIP_SMFC_SHIFT) | +- (id << CSI_ID_2_SKIP_SHIFT) | +- (skip << CSI_SKIP_SMFC_SHIFT); +- ipu_csi_write(csi, temp, CSI_SKIP); +- +- spin_unlock_irqrestore(&csi->lock, flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_csi_set_skip_smfc); +- +-int ipu_csi_set_dest(struct ipu_csi *csi, enum ipu_csi_dest csi_dest) +-{ +- unsigned long flags; +- u32 csi_sens_conf, dest; +- +- if (csi_dest == IPU_CSI_DEST_IDMAC) +- dest = CSI_DATA_DEST_IDMAC; +- else +- dest = CSI_DATA_DEST_IC; /* IC or VDIC */ +- +- spin_lock_irqsave(&csi->lock, flags); +- +- csi_sens_conf = ipu_csi_read(csi, CSI_SENS_CONF); +- csi_sens_conf &= ~CSI_SENS_CONF_DATA_DEST_MASK; +- csi_sens_conf |= (dest << CSI_SENS_CONF_DATA_DEST_SHIFT); +- ipu_csi_write(csi, csi_sens_conf, CSI_SENS_CONF); +- +- spin_unlock_irqrestore(&csi->lock, flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_csi_set_dest); +- +-int ipu_csi_enable(struct ipu_csi *csi) +-{ +- ipu_module_enable(csi->ipu, csi->module); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_csi_enable); +- +-int ipu_csi_disable(struct ipu_csi *csi) +-{ +- ipu_module_disable(csi->ipu, csi->module); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_csi_disable); +- +-struct ipu_csi *ipu_csi_get(struct ipu_soc *ipu, int id) +-{ +- unsigned long flags; +- struct ipu_csi *csi, *ret; +- +- if (id > 1) +- return ERR_PTR(-EINVAL); +- +- csi = ipu->csi_priv[id]; +- ret = csi; +- +- spin_lock_irqsave(&csi->lock, flags); +- +- if (csi->inuse) { +- ret = ERR_PTR(-EBUSY); +- goto unlock; +- } +- +- csi->inuse = true; +-unlock: +- spin_unlock_irqrestore(&csi->lock, flags); +- return ret; +-} +-EXPORT_SYMBOL_GPL(ipu_csi_get); +- +-void ipu_csi_put(struct ipu_csi *csi) +-{ +- unsigned long flags; +- +- spin_lock_irqsave(&csi->lock, flags); +- csi->inuse = false; +- spin_unlock_irqrestore(&csi->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_csi_put); +- +-int ipu_csi_init(struct ipu_soc *ipu, struct device *dev, int id, +- unsigned long base, u32 module, struct clk *clk_ipu) +-{ +- struct ipu_csi *csi; +- +- if (id > 1) +- return -ENODEV; +- +- csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL); +- if (!csi) +- return -ENOMEM; +- +- ipu->csi_priv[id] = csi; +- +- spin_lock_init(&csi->lock); +- csi->module = module; +- csi->id = id; +- csi->clk_ipu = clk_ipu; +- csi->base = devm_ioremap(dev, base, PAGE_SIZE); +- if (!csi->base) +- return -ENOMEM; +- +- dev_dbg(dev, "CSI%d base: 0x%08lx remapped to %p\n", +- id, base, csi->base); +- csi->ipu = ipu; +- +- return 0; +-} +- +-void ipu_csi_exit(struct ipu_soc *ipu, int id) +-{ +-} +- +-void ipu_csi_dump(struct ipu_csi *csi) +-{ +- dev_dbg(csi->ipu->dev, "CSI_SENS_CONF: %08x\n", +- ipu_csi_read(csi, CSI_SENS_CONF)); +- dev_dbg(csi->ipu->dev, "CSI_SENS_FRM_SIZE: %08x\n", +- ipu_csi_read(csi, CSI_SENS_FRM_SIZE)); +- dev_dbg(csi->ipu->dev, "CSI_ACT_FRM_SIZE: %08x\n", +- ipu_csi_read(csi, CSI_ACT_FRM_SIZE)); +- dev_dbg(csi->ipu->dev, "CSI_OUT_FRM_CTRL: %08x\n", +- ipu_csi_read(csi, CSI_OUT_FRM_CTRL)); +- dev_dbg(csi->ipu->dev, "CSI_TST_CTRL: %08x\n", +- ipu_csi_read(csi, CSI_TST_CTRL)); +- dev_dbg(csi->ipu->dev, "CSI_CCIR_CODE_1: %08x\n", +- ipu_csi_read(csi, CSI_CCIR_CODE_1)); +- dev_dbg(csi->ipu->dev, "CSI_CCIR_CODE_2: %08x\n", +- ipu_csi_read(csi, CSI_CCIR_CODE_2)); +- dev_dbg(csi->ipu->dev, "CSI_CCIR_CODE_3: %08x\n", +- ipu_csi_read(csi, CSI_CCIR_CODE_3)); +- dev_dbg(csi->ipu->dev, "CSI_MIPI_DI: %08x\n", +- ipu_csi_read(csi, CSI_MIPI_DI)); +- dev_dbg(csi->ipu->dev, "CSI_SKIP: %08x\n", +- ipu_csi_read(csi, CSI_SKIP)); +-} +-EXPORT_SYMBOL_GPL(ipu_csi_dump); +--- a/drivers/gpu/imx/ipu-v3/ipu-dc.c ++++ /dev/null +@@ -1,420 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-or-later +-/* +- * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> +- * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. +- */ +- +-#include <linux/export.h> +-#include <linux/module.h> +-#include <linux/types.h> +-#include <linux/errno.h> +-#include <linux/delay.h> +-#include <linux/interrupt.h> +-#include <linux/io.h> +- +-#include <video/imx-ipu-v3.h> +-#include "ipu-prv.h" +- +-#define DC_MAP_CONF_PTR(n) (0x108 + ((n) & ~0x1) * 2) +-#define DC_MAP_CONF_VAL(n) (0x144 + ((n) & ~0x1) * 2) +- +-#define DC_EVT_NF 0 +-#define DC_EVT_NL 1 +-#define DC_EVT_EOF 2 +-#define DC_EVT_NFIELD 3 +-#define DC_EVT_EOL 4 +-#define DC_EVT_EOFIELD 5 +-#define DC_EVT_NEW_ADDR 6 +-#define DC_EVT_NEW_CHAN 7 +-#define DC_EVT_NEW_DATA 8 +- +-#define DC_EVT_NEW_ADDR_W_0 0 +-#define DC_EVT_NEW_ADDR_W_1 1 +-#define DC_EVT_NEW_CHAN_W_0 2 +-#define DC_EVT_NEW_CHAN_W_1 3 +-#define DC_EVT_NEW_DATA_W_0 4 +-#define DC_EVT_NEW_DATA_W_1 5 +-#define DC_EVT_NEW_ADDR_R_0 6 +-#define DC_EVT_NEW_ADDR_R_1 7 +-#define DC_EVT_NEW_CHAN_R_0 8 +-#define DC_EVT_NEW_CHAN_R_1 9 +-#define DC_EVT_NEW_DATA_R_0 10 +-#define DC_EVT_NEW_DATA_R_1 11 +- +-#define DC_WR_CH_CONF 0x0 +-#define DC_WR_CH_ADDR 0x4 +-#define DC_RL_CH(evt) (8 + ((evt) & ~0x1) * 2) +- +-#define DC_GEN 0xd4 +-#define DC_DISP_CONF1(disp) (0xd8 + (disp) * 4) +-#define DC_DISP_CONF2(disp) (0xe8 + (disp) * 4) +-#define DC_STAT 0x1c8 +- +-#define WROD(lf) (0x18 | ((lf) << 1)) +-#define WRG 0x01 +-#define WCLK 0xc9 +- +-#define SYNC_WAVE 0 +-#define NULL_WAVE (-1) +- +-#define DC_GEN_SYNC_1_6_SYNC (2 << 1) +-#define DC_GEN_SYNC_PRIORITY_1 (1 << 7) +- +-#define DC_WR_CH_CONF_WORD_SIZE_8 (0 << 0) +-#define DC_WR_CH_CONF_WORD_SIZE_16 (1 << 0) +-#define DC_WR_CH_CONF_WORD_SIZE_24 (2 << 0) +-#define DC_WR_CH_CONF_WORD_SIZE_32 (3 << 0) +-#define DC_WR_CH_CONF_DISP_ID_PARALLEL(i) (((i) & 0x1) << 3) +-#define DC_WR_CH_CONF_DISP_ID_SERIAL (2 << 3) +-#define DC_WR_CH_CONF_DISP_ID_ASYNC (3 << 4) +-#define DC_WR_CH_CONF_FIELD_MODE (1 << 9) +-#define DC_WR_CH_CONF_PROG_TYPE_NORMAL (4 << 5) +-#define DC_WR_CH_CONF_PROG_TYPE_MASK (7 << 5) +-#define DC_WR_CH_CONF_PROG_DI_ID (1 << 2) +-#define DC_WR_CH_CONF_PROG_DISP_ID(i) (((i) & 0x1) << 3) +- +-#define IPU_DC_NUM_CHANNELS 10 +- +-struct ipu_dc_priv; +- +-enum ipu_dc_map { +- IPU_DC_MAP_RGB24, +- IPU_DC_MAP_RGB565, +- IPU_DC_MAP_GBR24, /* TVEv2 */ +- IPU_DC_MAP_BGR666, +- IPU_DC_MAP_LVDS666, +- IPU_DC_MAP_BGR24, +-}; +- +-struct ipu_dc { +- /* The display interface number assigned to this dc channel */ +- unsigned int di; +- void __iomem *base; +- struct ipu_dc_priv *priv; +- int chno; +- bool in_use; +-}; +- +-struct ipu_dc_priv { +- void __iomem *dc_reg; +- void __iomem *dc_tmpl_reg; +- struct ipu_soc *ipu; +- struct device *dev; +- struct ipu_dc channels[IPU_DC_NUM_CHANNELS]; +- struct mutex mutex; +- struct completion comp; +- int use_count; +-}; +- +-static void dc_link_event(struct ipu_dc *dc, int event, int addr, int priority) +-{ +- u32 reg; +- +- reg = readl(dc->base + DC_RL_CH(event)); +- reg &= ~(0xffff << (16 * (event & 0x1))); +- reg |= ((addr << 8) | priority) << (16 * (event & 0x1)); +- writel(reg, dc->base + DC_RL_CH(event)); +-} +- +-static void dc_write_tmpl(struct ipu_dc *dc, int word, u32 opcode, u32 operand, +- int map, int wave, int glue, int sync, int stop) +-{ +- struct ipu_dc_priv *priv = dc->priv; +- u32 reg1, reg2; +- +- if (opcode == WCLK) { +- reg1 = (operand << 20) & 0xfff00000; +- reg2 = operand >> 12 | opcode << 1 | stop << 9; +- } else if (opcode == WRG) { +- reg1 = sync | glue << 4 | ++wave << 11 | ((operand << 15) & 0xffff8000); +- reg2 = operand >> 17 | opcode << 7 | stop << 9; +- } else { +- reg1 = sync | glue << 4 | ++wave << 11 | ++map << 15 | ((operand << 20) & 0xfff00000); +- reg2 = operand >> 12 | opcode << 4 | stop << 9; +- } +- writel(reg1, priv->dc_tmpl_reg + word * 8); +- writel(reg2, priv->dc_tmpl_reg + word * 8 + 4); +-} +- +-static int ipu_bus_format_to_map(u32 fmt) +-{ +- switch (fmt) { +- default: +- WARN_ON(1); +- /* fall-through */ +- case MEDIA_BUS_FMT_RGB888_1X24: +- return IPU_DC_MAP_RGB24; +- case MEDIA_BUS_FMT_RGB565_1X16: +- return IPU_DC_MAP_RGB565; +- case MEDIA_BUS_FMT_GBR888_1X24: +- return IPU_DC_MAP_GBR24; +- case MEDIA_BUS_FMT_RGB666_1X18: +- return IPU_DC_MAP_BGR666; +- case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: +- return IPU_DC_MAP_LVDS666; +- case MEDIA_BUS_FMT_BGR888_1X24: +- return IPU_DC_MAP_BGR24; +- } +-} +- +-int ipu_dc_init_sync(struct ipu_dc *dc, struct ipu_di *di, bool interlaced, +- u32 bus_format, u32 width) +-{ +- struct ipu_dc_priv *priv = dc->priv; +- int addr, sync; +- u32 reg = 0; +- int map; +- +- dc->di = ipu_di_get_num(di); +- +- map = ipu_bus_format_to_map(bus_format); +- +- /* +- * In interlaced mode we need more counters to create the asymmetric +- * per-field VSYNC signals. The pixel active signal synchronising DC +- * to DI moves to signal generator #6 (see ipu-di.c). In progressive +- * mode counter #5 is used. +- */ +- sync = interlaced ? 6 : 5; +- +- /* Reserve 5 microcode template words for each DI */ +- if (dc->di) +- addr = 5; +- else +- addr = 0; +- +- if (interlaced) { +- dc_link_event(dc, DC_EVT_NL, addr, 3); +- dc_link_event(dc, DC_EVT_EOL, addr, 2); +- dc_link_event(dc, DC_EVT_NEW_DATA, addr, 1); +- +- /* Init template microcode */ +- dc_write_tmpl(dc, addr, WROD(0), 0, map, SYNC_WAVE, 0, sync, 1); +- } else { +- dc_link_event(dc, DC_EVT_NL, addr + 2, 3); +- dc_link_event(dc, DC_EVT_EOL, addr + 3, 2); +- dc_link_event(dc, DC_EVT_NEW_DATA, addr + 1, 1); +- +- /* Init template microcode */ +- dc_write_tmpl(dc, addr + 2, WROD(0), 0, map, SYNC_WAVE, 8, sync, 1); +- dc_write_tmpl(dc, addr + 3, WROD(0), 0, map, SYNC_WAVE, 4, sync, 0); +- dc_write_tmpl(dc, addr + 4, WRG, 0, map, NULL_WAVE, 0, 0, 1); +- dc_write_tmpl(dc, addr + 1, WROD(0), 0, map, SYNC_WAVE, 0, sync, 1); +- } +- +- dc_link_event(dc, DC_EVT_NF, 0, 0); +- dc_link_event(dc, DC_EVT_NFIELD, 0, 0); +- dc_link_event(dc, DC_EVT_EOF, 0, 0); +- dc_link_event(dc, DC_EVT_EOFIELD, 0, 0); +- dc_link_event(dc, DC_EVT_NEW_CHAN, 0, 0); +- dc_link_event(dc, DC_EVT_NEW_ADDR, 0, 0); +- +- reg = readl(dc->base + DC_WR_CH_CONF); +- if (interlaced) +- reg |= DC_WR_CH_CONF_FIELD_MODE; +- else +- reg &= ~DC_WR_CH_CONF_FIELD_MODE; +- writel(reg, dc->base + DC_WR_CH_CONF); +- +- writel(0x0, dc->base + DC_WR_CH_ADDR); +- writel(width, priv->dc_reg + DC_DISP_CONF2(dc->di)); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_dc_init_sync); +- +-void ipu_dc_enable(struct ipu_soc *ipu) +-{ +- struct ipu_dc_priv *priv = ipu->dc_priv; +- +- mutex_lock(&priv->mutex); +- +- if (!priv->use_count) +- ipu_module_enable(priv->ipu, IPU_CONF_DC_EN); +- +- priv->use_count++; +- +- mutex_unlock(&priv->mutex); +-} +-EXPORT_SYMBOL_GPL(ipu_dc_enable); +- +-void ipu_dc_enable_channel(struct ipu_dc *dc) +-{ +- u32 reg; +- +- reg = readl(dc->base + DC_WR_CH_CONF); +- reg |= DC_WR_CH_CONF_PROG_TYPE_NORMAL; +- writel(reg, dc->base + DC_WR_CH_CONF); +-} +-EXPORT_SYMBOL_GPL(ipu_dc_enable_channel); +- +-void ipu_dc_disable_channel(struct ipu_dc *dc) +-{ +- u32 val; +- +- val = readl(dc->base + DC_WR_CH_CONF); +- val &= ~DC_WR_CH_CONF_PROG_TYPE_MASK; +- writel(val, dc->base + DC_WR_CH_CONF); +-} +-EXPORT_SYMBOL_GPL(ipu_dc_disable_channel); +- +-void ipu_dc_disable(struct ipu_soc *ipu) +-{ +- struct ipu_dc_priv *priv = ipu->dc_priv; +- +- mutex_lock(&priv->mutex); +- +- priv->use_count--; +- if (!priv->use_count) +- ipu_module_disable(priv->ipu, IPU_CONF_DC_EN); +- +- if (priv->use_count < 0) +- priv->use_count = 0; +- +- mutex_unlock(&priv->mutex); +-} +-EXPORT_SYMBOL_GPL(ipu_dc_disable); +- +-static void ipu_dc_map_config(struct ipu_dc_priv *priv, enum ipu_dc_map map, +- int byte_num, int offset, int mask) +-{ +- int ptr = map * 3 + byte_num; +- u32 reg; +- +- reg = readl(priv->dc_reg + DC_MAP_CONF_VAL(ptr)); +- reg &= ~(0xffff << (16 * (ptr & 0x1))); +- reg |= ((offset << 8) | mask) << (16 * (ptr & 0x1)); +- writel(reg, priv->dc_reg + DC_MAP_CONF_VAL(ptr)); +- +- reg = readl(priv->dc_reg + DC_MAP_CONF_PTR(map)); +- reg &= ~(0x1f << ((16 * (map & 0x1)) + (5 * byte_num))); +- reg |= ptr << ((16 * (map & 0x1)) + (5 * byte_num)); +- writel(reg, priv->dc_reg + DC_MAP_CONF_PTR(map)); +-} +- +-static void ipu_dc_map_clear(struct ipu_dc_priv *priv, int map) +-{ +- u32 reg = readl(priv->dc_reg + DC_MAP_CONF_PTR(map)); +- +- writel(reg & ~(0xffff << (16 * (map & 0x1))), +- priv->dc_reg + DC_MAP_CONF_PTR(map)); +-} +- +-struct ipu_dc *ipu_dc_get(struct ipu_soc *ipu, int channel) +-{ +- struct ipu_dc_priv *priv = ipu->dc_priv; +- struct ipu_dc *dc; +- +- if (channel >= IPU_DC_NUM_CHANNELS) +- return ERR_PTR(-ENODEV); +- +- dc = &priv->channels[channel]; +- +- mutex_lock(&priv->mutex); +- +- if (dc->in_use) { +- mutex_unlock(&priv->mutex); +- return ERR_PTR(-EBUSY); +- } +- +- dc->in_use = true; +- +- mutex_unlock(&priv->mutex); +- +- return dc; +-} +-EXPORT_SYMBOL_GPL(ipu_dc_get); +- +-void ipu_dc_put(struct ipu_dc *dc) +-{ +- struct ipu_dc_priv *priv = dc->priv; +- +- mutex_lock(&priv->mutex); +- dc->in_use = false; +- mutex_unlock(&priv->mutex); +-} +-EXPORT_SYMBOL_GPL(ipu_dc_put); +- +-int ipu_dc_init(struct ipu_soc *ipu, struct device *dev, +- unsigned long base, unsigned long template_base) +-{ +- struct ipu_dc_priv *priv; +- static int channel_offsets[] = { 0, 0x1c, 0x38, 0x54, 0x58, 0x5c, +- 0x78, 0, 0x94, 0xb4}; +- int i; +- +- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); +- if (!priv) +- return -ENOMEM; +- +- mutex_init(&priv->mutex); +- +- priv->dev = dev; +- priv->ipu = ipu; +- priv->dc_reg = devm_ioremap(dev, base, PAGE_SIZE); +- priv->dc_tmpl_reg = devm_ioremap(dev, template_base, PAGE_SIZE); +- if (!priv->dc_reg || !priv->dc_tmpl_reg) +- return -ENOMEM; +- +- for (i = 0; i < IPU_DC_NUM_CHANNELS; i++) { +- priv->channels[i].chno = i; +- priv->channels[i].priv = priv; +- priv->channels[i].base = priv->dc_reg + channel_offsets[i]; +- } +- +- writel(DC_WR_CH_CONF_WORD_SIZE_24 | DC_WR_CH_CONF_DISP_ID_PARALLEL(1) | +- DC_WR_CH_CONF_PROG_DI_ID, +- priv->channels[1].base + DC_WR_CH_CONF); +- writel(DC_WR_CH_CONF_WORD_SIZE_24 | DC_WR_CH_CONF_DISP_ID_PARALLEL(0), +- priv->channels[5].base + DC_WR_CH_CONF); +- +- writel(DC_GEN_SYNC_1_6_SYNC | DC_GEN_SYNC_PRIORITY_1, +- priv->dc_reg + DC_GEN); +- +- ipu->dc_priv = priv; +- +- dev_dbg(dev, "DC base: 0x%08lx template base: 0x%08lx\n", +- base, template_base); +- +- /* rgb24 */ +- ipu_dc_map_clear(priv, IPU_DC_MAP_RGB24); +- ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 0, 7, 0xff); /* blue */ +- ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 1, 15, 0xff); /* green */ +- ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 2, 23, 0xff); /* red */ +- +- /* rgb565 */ +- ipu_dc_map_clear(priv, IPU_DC_MAP_RGB565); +- ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 0, 4, 0xf8); /* blue */ +- ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 1, 10, 0xfc); /* green */ +- ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 2, 15, 0xf8); /* red */ +- +- /* gbr24 */ +- ipu_dc_map_clear(priv, IPU_DC_MAP_GBR24); +- ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 2, 15, 0xff); /* green */ +- ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 1, 7, 0xff); /* blue */ +- ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 0, 23, 0xff); /* red */ +- +- /* bgr666 */ +- ipu_dc_map_clear(priv, IPU_DC_MAP_BGR666); +- ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 0, 5, 0xfc); /* blue */ +- ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 1, 11, 0xfc); /* green */ +- ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 2, 17, 0xfc); /* red */ +- +- /* lvds666 */ +- ipu_dc_map_clear(priv, IPU_DC_MAP_LVDS666); +- ipu_dc_map_config(priv, IPU_DC_MAP_LVDS666, 0, 5, 0xfc); /* blue */ +- ipu_dc_map_config(priv, IPU_DC_MAP_LVDS666, 1, 13, 0xfc); /* green */ +- ipu_dc_map_config(priv, IPU_DC_MAP_LVDS666, 2, 21, 0xfc); /* red */ +- +- /* bgr24 */ +- ipu_dc_map_clear(priv, IPU_DC_MAP_BGR24); +- ipu_dc_map_config(priv, IPU_DC_MAP_BGR24, 2, 7, 0xff); /* red */ +- ipu_dc_map_config(priv, IPU_DC_MAP_BGR24, 1, 15, 0xff); /* green */ +- ipu_dc_map_config(priv, IPU_DC_MAP_BGR24, 0, 23, 0xff); /* blue */ +- +- return 0; +-} +- +-void ipu_dc_exit(struct ipu_soc *ipu) +-{ +-} +--- a/drivers/gpu/imx/ipu-v3/ipu-di.c ++++ /dev/null +@@ -1,745 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-or-later +-/* +- * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> +- * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. +- */ +-#include <linux/export.h> +-#include <linux/module.h> +-#include <linux/types.h> +-#include <linux/errno.h> +-#include <linux/io.h> +-#include <linux/err.h> +-#include <linux/platform_device.h> +- +-#include <video/imx-ipu-v3.h> +-#include "ipu-prv.h" +- +-struct ipu_di { +- void __iomem *base; +- int id; +- u32 module; +- struct clk *clk_di; /* display input clock */ +- struct clk *clk_ipu; /* IPU bus clock */ +- struct clk *clk_di_pixel; /* resulting pixel clock */ +- bool inuse; +- struct ipu_soc *ipu; +-}; +- +-static DEFINE_MUTEX(di_mutex); +- +-struct di_sync_config { +- int run_count; +- int run_src; +- int offset_count; +- int offset_src; +- int repeat_count; +- int cnt_clr_src; +- int cnt_polarity_gen_en; +- int cnt_polarity_clr_src; +- int cnt_polarity_trigger_src; +- int cnt_up; +- int cnt_down; +-}; +- +-enum di_pins { +- DI_PIN11 = 0, +- DI_PIN12 = 1, +- DI_PIN13 = 2, +- DI_PIN14 = 3, +- DI_PIN15 = 4, +- DI_PIN16 = 5, +- DI_PIN17 = 6, +- DI_PIN_CS = 7, +- +- DI_PIN_SER_CLK = 0, +- DI_PIN_SER_RS = 1, +-}; +- +-enum di_sync_wave { +- DI_SYNC_NONE = 0, +- DI_SYNC_CLK = 1, +- DI_SYNC_INT_HSYNC = 2, +- DI_SYNC_HSYNC = 3, +- DI_SYNC_VSYNC = 4, +- DI_SYNC_DE = 6, +- +- DI_SYNC_CNT1 = 2, /* counter >= 2 only */ +- DI_SYNC_CNT4 = 5, /* counter >= 5 only */ +- DI_SYNC_CNT5 = 6, /* counter >= 6 only */ +-}; +- +-#define SYNC_WAVE 0 +- +-#define DI_GENERAL 0x0000 +-#define DI_BS_CLKGEN0 0x0004 +-#define DI_BS_CLKGEN1 0x0008 +-#define DI_SW_GEN0(gen) (0x000c + 4 * ((gen) - 1)) +-#define DI_SW_GEN1(gen) (0x0030 + 4 * ((gen) - 1)) +-#define DI_STP_REP(gen) (0x0148 + 4 * (((gen) - 1)/2)) +-#define DI_SYNC_AS_GEN 0x0054 +-#define DI_DW_GEN(gen) (0x0058 + 4 * (gen)) +-#define DI_DW_SET(gen, set) (0x0088 + 4 * ((gen) + 0xc * (set))) +-#define DI_SER_CONF 0x015c +-#define DI_SSC 0x0160 +-#define DI_POL 0x0164 +-#define DI_AW0 0x0168 +-#define DI_AW1 0x016c +-#define DI_SCR_CONF 0x0170 +-#define DI_STAT 0x0174 +- +-#define DI_SW_GEN0_RUN_COUNT(x) ((x) << 19) +-#define DI_SW_GEN0_RUN_SRC(x) ((x) << 16) +-#define DI_SW_GEN0_OFFSET_COUNT(x) ((x) << 3) +-#define DI_SW_GEN0_OFFSET_SRC(x) ((x) << 0) +- +-#define DI_SW_GEN1_CNT_POL_GEN_EN(x) ((x) << 29) +-#define DI_SW_GEN1_CNT_CLR_SRC(x) ((x) << 25) +-#define DI_SW_GEN1_CNT_POL_TRIGGER_SRC(x) ((x) << 12) +-#define DI_SW_GEN1_CNT_POL_CLR_SRC(x) ((x) << 9) +-#define DI_SW_GEN1_CNT_DOWN(x) ((x) << 16) +-#define DI_SW_GEN1_CNT_UP(x) (x) +-#define DI_SW_GEN1_AUTO_RELOAD (0x10000000) +- +-#define DI_DW_GEN_ACCESS_SIZE_OFFSET 24 +-#define DI_DW_GEN_COMPONENT_SIZE_OFFSET 16 +- +-#define DI_GEN_POLARITY_1 (1 << 0) +-#define DI_GEN_POLARITY_2 (1 << 1) +-#define DI_GEN_POLARITY_3 (1 << 2) +-#define DI_GEN_POLARITY_4 (1 << 3) +-#define DI_GEN_POLARITY_5 (1 << 4) +-#define DI_GEN_POLARITY_6 (1 << 5) +-#define DI_GEN_POLARITY_7 (1 << 6) +-#define DI_GEN_POLARITY_8 (1 << 7) +-#define DI_GEN_POLARITY_DISP_CLK (1 << 17) +-#define DI_GEN_DI_CLK_EXT (1 << 20) +-#define DI_GEN_DI_VSYNC_EXT (1 << 21) +- +-#define DI_POL_DRDY_DATA_POLARITY (1 << 7) +-#define DI_POL_DRDY_POLARITY_15 (1 << 4) +- +-#define DI_VSYNC_SEL_OFFSET 13 +- +-static inline u32 ipu_di_read(struct ipu_di *di, unsigned offset) +-{ +- return readl(di->base + offset); +-} +- +-static inline void ipu_di_write(struct ipu_di *di, u32 value, unsigned offset) +-{ +- writel(value, di->base + offset); +-} +- +-static void ipu_di_data_wave_config(struct ipu_di *di, +- int wave_gen, +- int access_size, int component_size) +-{ +- u32 reg; +- reg = (access_size << DI_DW_GEN_ACCESS_SIZE_OFFSET) | +- (component_size << DI_DW_GEN_COMPONENT_SIZE_OFFSET); +- ipu_di_write(di, reg, DI_DW_GEN(wave_gen)); +-} +- +-static void ipu_di_data_pin_config(struct ipu_di *di, int wave_gen, int di_pin, +- int set, int up, int down) +-{ +- u32 reg; +- +- reg = ipu_di_read(di, DI_DW_GEN(wave_gen)); +- reg &= ~(0x3 << (di_pin * 2)); +- reg |= set << (di_pin * 2); +- ipu_di_write(di, reg, DI_DW_GEN(wave_gen)); +- +- ipu_di_write(di, (down << 16) | up, DI_DW_SET(wave_gen, set)); +-} +- +-static void ipu_di_sync_config(struct ipu_di *di, struct di_sync_config *config, +- int start, int count) +-{ +- u32 reg; +- int i; +- +- for (i = 0; i < count; i++) { +- struct di_sync_config *c = &config[i]; +- int wave_gen = start + i + 1; +- +- if ((c->run_count >= 0x1000) || (c->offset_count >= 0x1000) || +- (c->repeat_count >= 0x1000) || +- (c->cnt_up >= 0x400) || +- (c->cnt_down >= 0x400)) { +- dev_err(di->ipu->dev, "DI%d counters out of range.\n", +- di->id); +- return; +- } +- +- reg = DI_SW_GEN0_RUN_COUNT(c->run_count) | +- DI_SW_GEN0_RUN_SRC(c->run_src) | +- DI_SW_GEN0_OFFSET_COUNT(c->offset_count) | +- DI_SW_GEN0_OFFSET_SRC(c->offset_src); +- ipu_di_write(di, reg, DI_SW_GEN0(wave_gen)); +- +- reg = DI_SW_GEN1_CNT_POL_GEN_EN(c->cnt_polarity_gen_en) | +- DI_SW_GEN1_CNT_CLR_SRC(c->cnt_clr_src) | +- DI_SW_GEN1_CNT_POL_TRIGGER_SRC( +- c->cnt_polarity_trigger_src) | +- DI_SW_GEN1_CNT_POL_CLR_SRC(c->cnt_polarity_clr_src) | +- DI_SW_GEN1_CNT_DOWN(c->cnt_down) | +- DI_SW_GEN1_CNT_UP(c->cnt_up); +- +- /* Enable auto reload */ +- if (c->repeat_count == 0) +- reg |= DI_SW_GEN1_AUTO_RELOAD; +- +- ipu_di_write(di, reg, DI_SW_GEN1(wave_gen)); +- +- reg = ipu_di_read(di, DI_STP_REP(wave_gen)); +- reg &= ~(0xffff << (16 * ((wave_gen - 1) & 0x1))); +- reg |= c->repeat_count << (16 * ((wave_gen - 1) & 0x1)); +- ipu_di_write(di, reg, DI_STP_REP(wave_gen)); +- } +-} +- +-static void ipu_di_sync_config_interlaced(struct ipu_di *di, +- struct ipu_di_signal_cfg *sig) +-{ +- u32 h_total = sig->mode.hactive + sig->mode.hsync_len + +- sig->mode.hback_porch + sig->mode.hfront_porch; +- u32 v_total = sig->mode.vactive + sig->mode.vsync_len + +- sig->mode.vback_porch + sig->mode.vfront_porch; +- struct di_sync_config cfg[] = { +- { +- /* 1: internal VSYNC for each frame */ +- .run_count = v_total * 2 - 1, +- .run_src = 3, /* == counter 7 */ +- }, { +- /* PIN2: HSYNC waveform */ +- .run_count = h_total - 1, +- .run_src = DI_SYNC_CLK, +- .cnt_polarity_gen_en = 1, +- .cnt_polarity_trigger_src = DI_SYNC_CLK, +- .cnt_down = sig->mode.hsync_len * 2, +- }, { +- /* PIN3: VSYNC waveform */ +- .run_count = v_total - 1, +- .run_src = 4, /* == counter 7 */ +- .cnt_polarity_gen_en = 1, +- .cnt_polarity_trigger_src = 4, /* == counter 7 */ +- .cnt_down = sig->mode.vsync_len * 2, +- .cnt_clr_src = DI_SYNC_CNT1, +- }, { +- /* 4: Field */ +- .run_count = v_total / 2, +- .run_src = DI_SYNC_HSYNC, +- .offset_count = h_total / 2, +- .offset_src = DI_SYNC_CLK, +- .repeat_count = 2, +- .cnt_clr_src = DI_SYNC_CNT1, +- }, { +- /* 5: Active lines */ +- .run_src = DI_SYNC_HSYNC, +- .offset_count = (sig->mode.vsync_len + +- sig->mode.vback_porch) / 2, +- .offset_src = DI_SYNC_HSYNC, +- .repeat_count = sig->mode.vactive / 2, +- .cnt_clr_src = DI_SYNC_CNT4, +- }, { +- /* 6: Active pixel, referenced by DC */ +- .run_src = DI_SYNC_CLK, +- .offset_count = sig->mode.hsync_len + +- sig->mode.hback_porch, +- .offset_src = DI_SYNC_CLK, +- .repeat_count = sig->mode.hactive, +- .cnt_clr_src = DI_SYNC_CNT5, +- }, { +- /* 7: Half line HSYNC */ +- .run_count = h_total / 2 - 1, +- .run_src = DI_SYNC_CLK, +- } +- }; +- +- ipu_di_sync_config(di, cfg, 0, ARRAY_SIZE(cfg)); +- +- ipu_di_write(di, v_total / 2 - 1, DI_SCR_CONF); +-} +- +-static void ipu_di_sync_config_noninterlaced(struct ipu_di *di, +- struct ipu_di_signal_cfg *sig, int div) +-{ +- u32 h_total = sig->mode.hactive + sig->mode.hsync_len + +- sig->mode.hback_porch + sig->mode.hfront_porch; +- u32 v_total = sig->mode.vactive + sig->mode.vsync_len + +- sig->mode.vback_porch + sig->mode.vfront_porch; +- struct di_sync_config cfg[] = { +- { +- /* 1: INT_HSYNC */ +- .run_count = h_total - 1, +- .run_src = DI_SYNC_CLK, +- } , { +- /* PIN2: HSYNC */ +- .run_count = h_total - 1, +- .run_src = DI_SYNC_CLK, +- .offset_count = div * sig->v_to_h_sync, +- .offset_src = DI_SYNC_CLK, +- .cnt_polarity_gen_en = 1, +- .cnt_polarity_trigger_src = DI_SYNC_CLK, +- .cnt_down = sig->mode.hsync_len * 2, +- } , { +- /* PIN3: VSYNC */ +- .run_count = v_total - 1, +- .run_src = DI_SYNC_INT_HSYNC, +- .cnt_polarity_gen_en = 1, +- .cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC, +- .cnt_down = sig->mode.vsync_len * 2, +- } , { +- /* 4: Line Active */ +- .run_src = DI_SYNC_HSYNC, +- .offset_count = sig->mode.vsync_len + +- sig->mode.vback_porch, +- .offset_src = DI_SYNC_HSYNC, +- .repeat_count = sig->mode.vactive, +- .cnt_clr_src = DI_SYNC_VSYNC, +- } , { +- /* 5: Pixel Active, referenced by DC */ +- .run_src = DI_SYNC_CLK, +- .offset_count = sig->mode.hsync_len + +- sig->mode.hback_porch, +- .offset_src = DI_SYNC_CLK, +- .repeat_count = sig->mode.hactive, +- .cnt_clr_src = 5, /* Line Active */ +- } , { +- /* unused */ +- } , { +- /* unused */ +- } , { +- /* unused */ +- } , { +- /* unused */ +- }, +- }; +- /* can't use #7 and #8 for line active and pixel active counters */ +- struct di_sync_config cfg_vga[] = { +- { +- /* 1: INT_HSYNC */ +- .run_count = h_total - 1, +- .run_src = DI_SYNC_CLK, +- } , { +- /* 2: VSYNC */ +- .run_count = v_total - 1, +- .run_src = DI_SYNC_INT_HSYNC, +- } , { +- /* 3: Line Active */ +- .run_src = DI_SYNC_INT_HSYNC, +- .offset_count = sig->mode.vsync_len + +- sig->mode.vback_porch, +- .offset_src = DI_SYNC_INT_HSYNC, +- .repeat_count = sig->mode.vactive, +- .cnt_clr_src = 3 /* VSYNC */, +- } , { +- /* PIN4: HSYNC for VGA via TVEv2 on TQ MBa53 */ +- .run_count = h_total - 1, +- .run_src = DI_SYNC_CLK, +- .offset_count = div * sig->v_to_h_sync + 18, /* magic value from Freescale TVE driver */ +- .offset_src = DI_SYNC_CLK, +- .cnt_polarity_gen_en = 1, +- .cnt_polarity_trigger_src = DI_SYNC_CLK, +- .cnt_down = sig->mode.hsync_len * 2, +- } , { +- /* 5: Pixel Active signal to DC */ +- .run_src = DI_SYNC_CLK, +- .offset_count = sig->mode.hsync_len + +- sig->mode.hback_porch, +- .offset_src = DI_SYNC_CLK, +- .repeat_count = sig->mode.hactive, +- .cnt_clr_src = 4, /* Line Active */ +- } , { +- /* PIN6: VSYNC for VGA via TVEv2 on TQ MBa53 */ +- .run_count = v_total - 1, +- .run_src = DI_SYNC_INT_HSYNC, +- .offset_count = 1, /* magic value from Freescale TVE driver */ +- .offset_src = DI_SYNC_INT_HSYNC, +- .cnt_polarity_gen_en = 1, +- .cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC, +- .cnt_down = sig->mode.vsync_len * 2, +- } , { +- /* PIN4: HSYNC for VGA via TVEv2 on i.MX53-QSB */ +- .run_count = h_total - 1, +- .run_src = DI_SYNC_CLK, +- .offset_count = div * sig->v_to_h_sync + 18, /* magic value from Freescale TVE driver */ +- .offset_src = DI_SYNC_CLK, +- .cnt_polarity_gen_en = 1, +- .cnt_polarity_trigger_src = DI_SYNC_CLK, +- .cnt_down = sig->mode.hsync_len * 2, +- } , { +- /* PIN6: VSYNC for VGA via TVEv2 on i.MX53-QSB */ +- .run_count = v_total - 1, +- .run_src = DI_SYNC_INT_HSYNC, +- .offset_count = 1, /* magic value from Freescale TVE driver */ +- .offset_src = DI_SYNC_INT_HSYNC, +- .cnt_polarity_gen_en = 1, +- .cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC, +- .cnt_down = sig->mode.vsync_len * 2, +- } , { +- /* unused */ +- }, +- }; +- +- ipu_di_write(di, v_total - 1, DI_SCR_CONF); +- if (sig->hsync_pin == 2 && sig->vsync_pin == 3) +- ipu_di_sync_config(di, cfg, 0, ARRAY_SIZE(cfg)); +- else +- ipu_di_sync_config(di, cfg_vga, 0, ARRAY_SIZE(cfg_vga)); +-} +- +-static void ipu_di_config_clock(struct ipu_di *di, +- const struct ipu_di_signal_cfg *sig) +-{ +- struct clk *clk; +- unsigned clkgen0; +- uint32_t val; +- +- if (sig->clkflags & IPU_DI_CLKMODE_EXT) { +- /* +- * CLKMODE_EXT means we must use the DI clock: this is +- * needed for things like LVDS which needs to feed the +- * DI and LDB with the same pixel clock. +- */ +- clk = di->clk_di; +- +- if (sig->clkflags & IPU_DI_CLKMODE_SYNC) { +- /* +- * CLKMODE_SYNC means that we want the DI to be +- * clocked at the same rate as the parent clock. +- * This is needed (eg) for LDB which needs to be +- * fed with the same pixel clock. We assume that +- * the LDB clock has already been set correctly. +- */ +- clkgen0 = 1 << 4; +- } else { +- /* +- * We can use the divider. We should really have +- * a flag here indicating whether the bridge can +- * cope with a fractional divider or not. For the +- * time being, let's go for simplicitly and +- * reliability. +- */ +- unsigned long in_rate; +- unsigned div; +- +- clk_set_rate(clk, sig->mode.pixelclock); +- +- in_rate = clk_get_rate(clk); +- div = DIV_ROUND_CLOSEST(in_rate, sig->mode.pixelclock); +- div = clamp(div, 1U, 255U); +- +- clkgen0 = div << 4; +- } +- } else { +- /* +- * For other interfaces, we can arbitarily select between +- * the DI specific clock and the internal IPU clock. See +- * DI_GENERAL bit 20. We select the IPU clock if it can +- * give us a clock rate within 1% of the requested frequency, +- * otherwise we use the DI clock. +- */ +- unsigned long rate, clkrate; +- unsigned div, error; +- +- clkrate = clk_get_rate(di->clk_ipu); +- div = DIV_ROUND_CLOSEST(clkrate, sig->mode.pixelclock); +- div = clamp(div, 1U, 255U); +- rate = clkrate / div; +- +- error = rate / (sig->mode.pixelclock / 1000); +- +- dev_dbg(di->ipu->dev, " IPU clock can give %lu with divider %u, error %d.%u%%\n", +- rate, div, (signed)(error - 1000) / 10, error % 10); +- +- /* Allow a 1% error */ +- if (error < 1010 && error >= 990) { +- clk = di->clk_ipu; +- +- clkgen0 = div << 4; +- } else { +- unsigned long in_rate; +- unsigned div; +- +- clk = di->clk_di; +- +- clk_set_rate(clk, sig->mode.pixelclock); +- +- in_rate = clk_get_rate(clk); +- div = DIV_ROUND_CLOSEST(in_rate, sig->mode.pixelclock); +- div = clamp(div, 1U, 255U); +- +- clkgen0 = div << 4; +- } +- } +- +- di->clk_di_pixel = clk; +- +- /* Set the divider */ +- ipu_di_write(di, clkgen0, DI_BS_CLKGEN0); +- +- /* +- * Set the high/low periods. Bits 24:16 give us the falling edge, +- * and bits 8:0 give the rising edge. LSB is fraction, and is +- * based on the divider above. We want a 50% duty cycle, so set +- * the falling edge to be half the divider. +- */ +- ipu_di_write(di, (clkgen0 >> 4) << 16, DI_BS_CLKGEN1); +- +- /* Finally select the input clock */ +- val = ipu_di_read(di, DI_GENERAL) & ~DI_GEN_DI_CLK_EXT; +- if (clk == di->clk_di) +- val |= DI_GEN_DI_CLK_EXT; +- ipu_di_write(di, val, DI_GENERAL); +- +- dev_dbg(di->ipu->dev, "Want %luHz IPU %luHz DI %luHz using %s, %luHz\n", +- sig->mode.pixelclock, +- clk_get_rate(di->clk_ipu), +- clk_get_rate(di->clk_di), +- clk == di->clk_di ? "DI" : "IPU", +- clk_get_rate(di->clk_di_pixel) / (clkgen0 >> 4)); +-} +- +-/* +- * This function is called to adjust a video mode to IPU restrictions. +- * It is meant to be called from drm crtc mode_fixup() methods. +- */ +-int ipu_di_adjust_videomode(struct ipu_di *di, struct videomode *mode) +-{ +- u32 diff; +- +- if (mode->vfront_porch >= 2) +- return 0; +- +- diff = 2 - mode->vfront_porch; +- +- if (mode->vback_porch >= diff) { +- mode->vfront_porch = 2; +- mode->vback_porch -= diff; +- } else if (mode->vsync_len > diff) { +- mode->vfront_porch = 2; +- mode->vsync_len = mode->vsync_len - diff; +- } else { +- dev_warn(di->ipu->dev, "failed to adjust videomode\n"); +- return -EINVAL; +- } +- +- dev_dbg(di->ipu->dev, "videomode adapted for IPU restrictions\n"); +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_di_adjust_videomode); +- +-static u32 ipu_di_gen_polarity(int pin) +-{ +- switch (pin) { +- case 1: +- return DI_GEN_POLARITY_1; +- case 2: +- return DI_GEN_POLARITY_2; +- case 3: +- return DI_GEN_POLARITY_3; +- case 4: +- return DI_GEN_POLARITY_4; +- case 5: +- return DI_GEN_POLARITY_5; +- case 6: +- return DI_GEN_POLARITY_6; +- case 7: +- return DI_GEN_POLARITY_7; +- case 8: +- return DI_GEN_POLARITY_8; +- } +- return 0; +-} +- +-int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig) +-{ +- u32 reg; +- u32 di_gen, vsync_cnt; +- u32 div; +- +- dev_dbg(di->ipu->dev, "disp %d: panel size = %d x %d\n", +- di->id, sig->mode.hactive, sig->mode.vactive); +- +- dev_dbg(di->ipu->dev, "Clocks: IPU %luHz DI %luHz Needed %luHz\n", +- clk_get_rate(di->clk_ipu), +- clk_get_rate(di->clk_di), +- sig->mode.pixelclock); +- +- mutex_lock(&di_mutex); +- +- ipu_di_config_clock(di, sig); +- +- div = ipu_di_read(di, DI_BS_CLKGEN0) & 0xfff; +- div = div / 16; /* Now divider is integer portion */ +- +- /* Setup pixel clock timing */ +- /* Down time is half of period */ +- ipu_di_write(di, (div << 16), DI_BS_CLKGEN1); +- +- ipu_di_data_wave_config(di, SYNC_WAVE, div - 1, div - 1); +- ipu_di_data_pin_config(di, SYNC_WAVE, DI_PIN15, 3, 0, div * 2); +- +- di_gen = ipu_di_read(di, DI_GENERAL) & DI_GEN_DI_CLK_EXT; +- di_gen |= DI_GEN_DI_VSYNC_EXT; +- +- if (sig->mode.flags & DISPLAY_FLAGS_INTERLACED) { +- ipu_di_sync_config_interlaced(di, sig); +- +- /* set y_sel = 1 */ +- di_gen |= 0x10000000; +- +- vsync_cnt = 3; +- } else { +- ipu_di_sync_config_noninterlaced(di, sig, div); +- +- vsync_cnt = 3; +- if (di->id == 1) +- /* +- * TODO: change only for TVEv2, parallel display +- * uses pin 2 / 3 +- */ +- if (!(sig->hsync_pin == 2 && sig->vsync_pin == 3)) +- vsync_cnt = 6; +- } +- +- if (sig->mode.flags & DISPLAY_FLAGS_HSYNC_HIGH) +- di_gen |= ipu_di_gen_polarity(sig->hsync_pin); +- if (sig->mode.flags & DISPLAY_FLAGS_VSYNC_HIGH) +- di_gen |= ipu_di_gen_polarity(sig->vsync_pin); +- +- if (sig->clk_pol) +- di_gen |= DI_GEN_POLARITY_DISP_CLK; +- +- ipu_di_write(di, di_gen, DI_GENERAL); +- +- ipu_di_write(di, (--vsync_cnt << DI_VSYNC_SEL_OFFSET) | 0x00000002, +- DI_SYNC_AS_GEN); +- +- reg = ipu_di_read(di, DI_POL); +- reg &= ~(DI_POL_DRDY_DATA_POLARITY | DI_POL_DRDY_POLARITY_15); +- +- if (sig->enable_pol) +- reg |= DI_POL_DRDY_POLARITY_15; +- if (sig->data_pol) +- reg |= DI_POL_DRDY_DATA_POLARITY; +- +- ipu_di_write(di, reg, DI_POL); +- +- mutex_unlock(&di_mutex); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_di_init_sync_panel); +- +-int ipu_di_enable(struct ipu_di *di) +-{ +- int ret; +- +- WARN_ON(IS_ERR(di->clk_di_pixel)); +- +- ret = clk_prepare_enable(di->clk_di_pixel); +- if (ret) +- return ret; +- +- ipu_module_enable(di->ipu, di->module); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_di_enable); +- +-int ipu_di_disable(struct ipu_di *di) +-{ +- WARN_ON(IS_ERR(di->clk_di_pixel)); +- +- ipu_module_disable(di->ipu, di->module); +- +- clk_disable_unprepare(di->clk_di_pixel); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_di_disable); +- +-int ipu_di_get_num(struct ipu_di *di) +-{ +- return di->id; +-} +-EXPORT_SYMBOL_GPL(ipu_di_get_num); +- +-static DEFINE_MUTEX(ipu_di_lock); +- +-struct ipu_di *ipu_di_get(struct ipu_soc *ipu, int disp) +-{ +- struct ipu_di *di; +- +- if (disp > 1) +- return ERR_PTR(-EINVAL); +- +- di = ipu->di_priv[disp]; +- +- mutex_lock(&ipu_di_lock); +- +- if (di->inuse) { +- di = ERR_PTR(-EBUSY); +- goto out; +- } +- +- di->inuse = true; +-out: +- mutex_unlock(&ipu_di_lock); +- +- return di; +-} +-EXPORT_SYMBOL_GPL(ipu_di_get); +- +-void ipu_di_put(struct ipu_di *di) +-{ +- mutex_lock(&ipu_di_lock); +- +- di->inuse = false; +- +- mutex_unlock(&ipu_di_lock); +-} +-EXPORT_SYMBOL_GPL(ipu_di_put); +- +-int ipu_di_init(struct ipu_soc *ipu, struct device *dev, int id, +- unsigned long base, +- u32 module, struct clk *clk_ipu) +-{ +- struct ipu_di *di; +- +- if (id > 1) +- return -ENODEV; +- +- di = devm_kzalloc(dev, sizeof(*di), GFP_KERNEL); +- if (!di) +- return -ENOMEM; +- +- ipu->di_priv[id] = di; +- +- di->clk_di = devm_clk_get(dev, id ? "di1" : "di0"); +- if (IS_ERR(di->clk_di)) +- return PTR_ERR(di->clk_di); +- +- di->module = module; +- di->id = id; +- di->clk_ipu = clk_ipu; +- di->base = devm_ioremap(dev, base, PAGE_SIZE); +- if (!di->base) +- return -ENOMEM; +- +- ipu_di_write(di, 0x10, DI_BS_CLKGEN0); +- +- dev_dbg(dev, "DI%d base: 0x%08lx remapped to %p\n", +- id, base, di->base); +- di->inuse = false; +- di->ipu = ipu; +- +- return 0; +-} +- +-void ipu_di_exit(struct ipu_soc *ipu, int id) +-{ +-} +--- a/drivers/gpu/imx/ipu-v3/ipu-dmfc.c ++++ /dev/null +@@ -1,214 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-or-later +-/* +- * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> +- * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. +- */ +-#include <linux/export.h> +-#include <linux/types.h> +-#include <linux/errno.h> +-#include <linux/io.h> +- +-#include <video/imx-ipu-v3.h> +-#include "ipu-prv.h" +- +-#define DMFC_RD_CHAN 0x0000 +-#define DMFC_WR_CHAN 0x0004 +-#define DMFC_WR_CHAN_DEF 0x0008 +-#define DMFC_DP_CHAN 0x000c +-#define DMFC_DP_CHAN_DEF 0x0010 +-#define DMFC_GENERAL1 0x0014 +-#define DMFC_GENERAL2 0x0018 +-#define DMFC_IC_CTRL 0x001c +-#define DMFC_WR_CHAN_ALT 0x0020 +-#define DMFC_WR_CHAN_DEF_ALT 0x0024 +-#define DMFC_DP_CHAN_ALT 0x0028 +-#define DMFC_DP_CHAN_DEF_ALT 0x002c +-#define DMFC_GENERAL1_ALT 0x0030 +-#define DMFC_STAT 0x0034 +- +-#define DMFC_WR_CHAN_1_28 0 +-#define DMFC_WR_CHAN_2_41 8 +-#define DMFC_WR_CHAN_1C_42 16 +-#define DMFC_WR_CHAN_2C_43 24 +- +-#define DMFC_DP_CHAN_5B_23 0 +-#define DMFC_DP_CHAN_5F_27 8 +-#define DMFC_DP_CHAN_6B_24 16 +-#define DMFC_DP_CHAN_6F_29 24 +- +-struct dmfc_channel_data { +- int ipu_channel; +- unsigned long channel_reg; +- unsigned long shift; +- unsigned eot_shift; +- unsigned max_fifo_lines; +-}; +- +-static const struct dmfc_channel_data dmfcdata[] = { +- { +- .ipu_channel = IPUV3_CHANNEL_MEM_BG_SYNC, +- .channel_reg = DMFC_DP_CHAN, +- .shift = DMFC_DP_CHAN_5B_23, +- .eot_shift = 20, +- .max_fifo_lines = 3, +- }, { +- .ipu_channel = 24, +- .channel_reg = DMFC_DP_CHAN, +- .shift = DMFC_DP_CHAN_6B_24, +- .eot_shift = 22, +- .max_fifo_lines = 1, +- }, { +- .ipu_channel = IPUV3_CHANNEL_MEM_FG_SYNC, +- .channel_reg = DMFC_DP_CHAN, +- .shift = DMFC_DP_CHAN_5F_27, +- .eot_shift = 21, +- .max_fifo_lines = 2, +- }, { +- .ipu_channel = IPUV3_CHANNEL_MEM_DC_SYNC, +- .channel_reg = DMFC_WR_CHAN, +- .shift = DMFC_WR_CHAN_1_28, +- .eot_shift = 16, +- .max_fifo_lines = 2, +- }, { +- .ipu_channel = 29, +- .channel_reg = DMFC_DP_CHAN, +- .shift = DMFC_DP_CHAN_6F_29, +- .eot_shift = 23, +- .max_fifo_lines = 1, +- }, +-}; +- +-#define DMFC_NUM_CHANNELS ARRAY_SIZE(dmfcdata) +- +-struct ipu_dmfc_priv; +- +-struct dmfc_channel { +- unsigned slots; +- struct ipu_soc *ipu; +- struct ipu_dmfc_priv *priv; +- const struct dmfc_channel_data *data; +-}; +- +-struct ipu_dmfc_priv { +- struct ipu_soc *ipu; +- struct device *dev; +- struct dmfc_channel channels[DMFC_NUM_CHANNELS]; +- struct mutex mutex; +- void __iomem *base; +- int use_count; +-}; +- +-int ipu_dmfc_enable_channel(struct dmfc_channel *dmfc) +-{ +- struct ipu_dmfc_priv *priv = dmfc->priv; +- mutex_lock(&priv->mutex); +- +- if (!priv->use_count) +- ipu_module_enable(priv->ipu, IPU_CONF_DMFC_EN); +- +- priv->use_count++; +- +- mutex_unlock(&priv->mutex); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_dmfc_enable_channel); +- +-void ipu_dmfc_disable_channel(struct dmfc_channel *dmfc) +-{ +- struct ipu_dmfc_priv *priv = dmfc->priv; +- +- mutex_lock(&priv->mutex); +- +- priv->use_count--; +- +- if (!priv->use_count) +- ipu_module_disable(priv->ipu, IPU_CONF_DMFC_EN); +- +- if (priv->use_count < 0) +- priv->use_count = 0; +- +- mutex_unlock(&priv->mutex); +-} +-EXPORT_SYMBOL_GPL(ipu_dmfc_disable_channel); +- +-void ipu_dmfc_config_wait4eot(struct dmfc_channel *dmfc, int width) +-{ +- struct ipu_dmfc_priv *priv = dmfc->priv; +- u32 dmfc_gen1; +- +- mutex_lock(&priv->mutex); +- +- dmfc_gen1 = readl(priv->base + DMFC_GENERAL1); +- +- if ((dmfc->slots * 64 * 4) / width > dmfc->data->max_fifo_lines) +- dmfc_gen1 |= 1 << dmfc->data->eot_shift; +- else +- dmfc_gen1 &= ~(1 << dmfc->data->eot_shift); +- +- writel(dmfc_gen1, priv->base + DMFC_GENERAL1); +- +- mutex_unlock(&priv->mutex); +-} +-EXPORT_SYMBOL_GPL(ipu_dmfc_config_wait4eot); +- +-struct dmfc_channel *ipu_dmfc_get(struct ipu_soc *ipu, int ipu_channel) +-{ +- struct ipu_dmfc_priv *priv = ipu->dmfc_priv; +- int i; +- +- for (i = 0; i < DMFC_NUM_CHANNELS; i++) +- if (dmfcdata[i].ipu_channel == ipu_channel) +- return &priv->channels[i]; +- return ERR_PTR(-ENODEV); +-} +-EXPORT_SYMBOL_GPL(ipu_dmfc_get); +- +-void ipu_dmfc_put(struct dmfc_channel *dmfc) +-{ +-} +-EXPORT_SYMBOL_GPL(ipu_dmfc_put); +- +-int ipu_dmfc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base, +- struct clk *ipu_clk) +-{ +- struct ipu_dmfc_priv *priv; +- int i; +- +- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); +- if (!priv) +- return -ENOMEM; +- +- priv->base = devm_ioremap(dev, base, PAGE_SIZE); +- if (!priv->base) +- return -ENOMEM; +- +- priv->dev = dev; +- priv->ipu = ipu; +- mutex_init(&priv->mutex); +- +- ipu->dmfc_priv = priv; +- +- for (i = 0; i < DMFC_NUM_CHANNELS; i++) { +- priv->channels[i].priv = priv; +- priv->channels[i].ipu = ipu; +- priv->channels[i].data = &dmfcdata[i]; +- +- if (dmfcdata[i].ipu_channel == IPUV3_CHANNEL_MEM_BG_SYNC || +- dmfcdata[i].ipu_channel == IPUV3_CHANNEL_MEM_FG_SYNC || +- dmfcdata[i].ipu_channel == IPUV3_CHANNEL_MEM_DC_SYNC) +- priv->channels[i].slots = 2; +- } +- +- writel(0x00000050, priv->base + DMFC_WR_CHAN); +- writel(0x00005654, priv->base + DMFC_DP_CHAN); +- writel(0x202020f6, priv->base + DMFC_WR_CHAN_DEF); +- writel(0x2020f6f6, priv->base + DMFC_DP_CHAN_DEF); +- writel(0x00000003, priv->base + DMFC_GENERAL1); +- +- return 0; +-} +- +-void ipu_dmfc_exit(struct ipu_soc *ipu) +-{ +-} +--- a/drivers/gpu/imx/ipu-v3/ipu-dp.c ++++ /dev/null +@@ -1,357 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-or-later +-/* +- * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> +- * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. +- */ +-#include <linux/export.h> +-#include <linux/kernel.h> +-#include <linux/types.h> +-#include <linux/errno.h> +-#include <linux/io.h> +-#include <linux/err.h> +- +-#include <video/imx-ipu-v3.h> +-#include "ipu-prv.h" +- +-#define DP_SYNC 0 +-#define DP_ASYNC0 0x60 +-#define DP_ASYNC1 0xBC +- +-#define DP_COM_CONF 0x0 +-#define DP_GRAPH_WIND_CTRL 0x0004 +-#define DP_FG_POS 0x0008 +-#define DP_CSC_A_0 0x0044 +-#define DP_CSC_A_1 0x0048 +-#define DP_CSC_A_2 0x004C +-#define DP_CSC_A_3 0x0050 +-#define DP_CSC_0 0x0054 +-#define DP_CSC_1 0x0058 +- +-#define DP_COM_CONF_FG_EN (1 << 0) +-#define DP_COM_CONF_GWSEL (1 << 1) +-#define DP_COM_CONF_GWAM (1 << 2) +-#define DP_COM_CONF_GWCKE (1 << 3) +-#define DP_COM_CONF_CSC_DEF_MASK (3 << 8) +-#define DP_COM_CONF_CSC_DEF_OFFSET 8 +-#define DP_COM_CONF_CSC_DEF_FG (3 << 8) +-#define DP_COM_CONF_CSC_DEF_BG (2 << 8) +-#define DP_COM_CONF_CSC_DEF_BOTH (1 << 8) +- +-#define IPUV3_NUM_FLOWS 3 +- +-struct ipu_dp_priv; +- +-struct ipu_dp { +- u32 flow; +- bool in_use; +- bool foreground; +- enum ipu_color_space in_cs; +-}; +- +-struct ipu_flow { +- struct ipu_dp foreground; +- struct ipu_dp background; +- enum ipu_color_space out_cs; +- void __iomem *base; +- struct ipu_dp_priv *priv; +-}; +- +-struct ipu_dp_priv { +- struct ipu_soc *ipu; +- struct device *dev; +- void __iomem *base; +- struct ipu_flow flow[IPUV3_NUM_FLOWS]; +- struct mutex mutex; +- int use_count; +-}; +- +-static u32 ipu_dp_flow_base[] = {DP_SYNC, DP_ASYNC0, DP_ASYNC1}; +- +-static inline struct ipu_flow *to_flow(struct ipu_dp *dp) +-{ +- if (dp->foreground) +- return container_of(dp, struct ipu_flow, foreground); +- else +- return container_of(dp, struct ipu_flow, background); +-} +- +-int ipu_dp_set_global_alpha(struct ipu_dp *dp, bool enable, +- u8 alpha, bool bg_chan) +-{ +- struct ipu_flow *flow = to_flow(dp); +- struct ipu_dp_priv *priv = flow->priv; +- u32 reg; +- +- mutex_lock(&priv->mutex); +- +- reg = readl(flow->base + DP_COM_CONF); +- if (bg_chan) +- reg &= ~DP_COM_CONF_GWSEL; +- else +- reg |= DP_COM_CONF_GWSEL; +- writel(reg, flow->base + DP_COM_CONF); +- +- if (enable) { +- reg = readl(flow->base + DP_GRAPH_WIND_CTRL) & 0x00FFFFFFL; +- writel(reg | ((u32) alpha << 24), +- flow->base + DP_GRAPH_WIND_CTRL); +- +- reg = readl(flow->base + DP_COM_CONF); +- writel(reg | DP_COM_CONF_GWAM, flow->base + DP_COM_CONF); +- } else { +- reg = readl(flow->base + DP_COM_CONF); +- writel(reg & ~DP_COM_CONF_GWAM, flow->base + DP_COM_CONF); +- } +- +- ipu_srm_dp_update(priv->ipu, true); +- +- mutex_unlock(&priv->mutex); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_dp_set_global_alpha); +- +-int ipu_dp_set_window_pos(struct ipu_dp *dp, u16 x_pos, u16 y_pos) +-{ +- struct ipu_flow *flow = to_flow(dp); +- struct ipu_dp_priv *priv = flow->priv; +- +- writel((x_pos << 16) | y_pos, flow->base + DP_FG_POS); +- +- ipu_srm_dp_update(priv->ipu, true); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_dp_set_window_pos); +- +-static void ipu_dp_csc_init(struct ipu_flow *flow, +- enum ipu_color_space in, +- enum ipu_color_space out, +- u32 place) +-{ +- u32 reg; +- +- reg = readl(flow->base + DP_COM_CONF); +- reg &= ~DP_COM_CONF_CSC_DEF_MASK; +- +- if (in == out) { +- writel(reg, flow->base + DP_COM_CONF); +- return; +- } +- +- if (in == IPUV3_COLORSPACE_RGB && out == IPUV3_COLORSPACE_YUV) { +- writel(0x099 | (0x12d << 16), flow->base + DP_CSC_A_0); +- writel(0x03a | (0x3a9 << 16), flow->base + DP_CSC_A_1); +- writel(0x356 | (0x100 << 16), flow->base + DP_CSC_A_2); +- writel(0x100 | (0x329 << 16), flow->base + DP_CSC_A_3); +- writel(0x3d6 | (0x0000 << 16) | (2 << 30), +- flow->base + DP_CSC_0); +- writel(0x200 | (2 << 14) | (0x200 << 16) | (2 << 30), +- flow->base + DP_CSC_1); +- } else { +- writel(0x095 | (0x000 << 16), flow->base + DP_CSC_A_0); +- writel(0x0cc | (0x095 << 16), flow->base + DP_CSC_A_1); +- writel(0x3ce | (0x398 << 16), flow->base + DP_CSC_A_2); +- writel(0x095 | (0x0ff << 16), flow->base + DP_CSC_A_3); +- writel(0x000 | (0x3e42 << 16) | (1 << 30), +- flow->base + DP_CSC_0); +- writel(0x10a | (1 << 14) | (0x3dd6 << 16) | (1 << 30), +- flow->base + DP_CSC_1); +- } +- +- reg |= place; +- +- writel(reg, flow->base + DP_COM_CONF); +-} +- +-int ipu_dp_setup_channel(struct ipu_dp *dp, +- enum ipu_color_space in, +- enum ipu_color_space out) +-{ +- struct ipu_flow *flow = to_flow(dp); +- struct ipu_dp_priv *priv = flow->priv; +- +- mutex_lock(&priv->mutex); +- +- dp->in_cs = in; +- +- if (!dp->foreground) +- flow->out_cs = out; +- +- if (flow->foreground.in_cs == flow->background.in_cs) { +- /* +- * foreground and background are of same colorspace, put +- * colorspace converter after combining unit. +- */ +- ipu_dp_csc_init(flow, flow->foreground.in_cs, flow->out_cs, +- DP_COM_CONF_CSC_DEF_BOTH); +- } else { +- if (flow->foreground.in_cs == IPUV3_COLORSPACE_UNKNOWN || +- flow->foreground.in_cs == flow->out_cs) +- /* +- * foreground identical to output, apply color +- * conversion on background +- */ +- ipu_dp_csc_init(flow, flow->background.in_cs, +- flow->out_cs, DP_COM_CONF_CSC_DEF_BG); +- else +- ipu_dp_csc_init(flow, flow->foreground.in_cs, +- flow->out_cs, DP_COM_CONF_CSC_DEF_FG); +- } +- +- ipu_srm_dp_update(priv->ipu, true); +- +- mutex_unlock(&priv->mutex); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_dp_setup_channel); +- +-int ipu_dp_enable(struct ipu_soc *ipu) +-{ +- struct ipu_dp_priv *priv = ipu->dp_priv; +- +- mutex_lock(&priv->mutex); +- +- if (!priv->use_count) +- ipu_module_enable(priv->ipu, IPU_CONF_DP_EN); +- +- priv->use_count++; +- +- mutex_unlock(&priv->mutex); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_dp_enable); +- +-int ipu_dp_enable_channel(struct ipu_dp *dp) +-{ +- struct ipu_flow *flow = to_flow(dp); +- struct ipu_dp_priv *priv = flow->priv; +- u32 reg; +- +- if (!dp->foreground) +- return 0; +- +- mutex_lock(&priv->mutex); +- +- reg = readl(flow->base + DP_COM_CONF); +- reg |= DP_COM_CONF_FG_EN; +- writel(reg, flow->base + DP_COM_CONF); +- +- ipu_srm_dp_update(priv->ipu, true); +- +- mutex_unlock(&priv->mutex); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_dp_enable_channel); +- +-void ipu_dp_disable_channel(struct ipu_dp *dp, bool sync) +-{ +- struct ipu_flow *flow = to_flow(dp); +- struct ipu_dp_priv *priv = flow->priv; +- u32 reg, csc; +- +- dp->in_cs = IPUV3_COLORSPACE_UNKNOWN; +- +- if (!dp->foreground) +- return; +- +- mutex_lock(&priv->mutex); +- +- reg = readl(flow->base + DP_COM_CONF); +- csc = reg & DP_COM_CONF_CSC_DEF_MASK; +- reg &= ~DP_COM_CONF_CSC_DEF_MASK; +- if (csc == DP_COM_CONF_CSC_DEF_BOTH || csc == DP_COM_CONF_CSC_DEF_BG) +- reg |= DP_COM_CONF_CSC_DEF_BG; +- +- reg &= ~DP_COM_CONF_FG_EN; +- writel(reg, flow->base + DP_COM_CONF); +- +- writel(0, flow->base + DP_FG_POS); +- ipu_srm_dp_update(priv->ipu, sync); +- +- mutex_unlock(&priv->mutex); +-} +-EXPORT_SYMBOL_GPL(ipu_dp_disable_channel); +- +-void ipu_dp_disable(struct ipu_soc *ipu) +-{ +- struct ipu_dp_priv *priv = ipu->dp_priv; +- +- mutex_lock(&priv->mutex); +- +- priv->use_count--; +- +- if (!priv->use_count) +- ipu_module_disable(priv->ipu, IPU_CONF_DP_EN); +- +- if (priv->use_count < 0) +- priv->use_count = 0; +- +- mutex_unlock(&priv->mutex); +-} +-EXPORT_SYMBOL_GPL(ipu_dp_disable); +- +-struct ipu_dp *ipu_dp_get(struct ipu_soc *ipu, unsigned int flow) +-{ +- struct ipu_dp_priv *priv = ipu->dp_priv; +- struct ipu_dp *dp; +- +- if ((flow >> 1) >= IPUV3_NUM_FLOWS) +- return ERR_PTR(-EINVAL); +- +- if (flow & 1) +- dp = &priv->flow[flow >> 1].foreground; +- else +- dp = &priv->flow[flow >> 1].background; +- +- if (dp->in_use) +- return ERR_PTR(-EBUSY); +- +- dp->in_use = true; +- +- return dp; +-} +-EXPORT_SYMBOL_GPL(ipu_dp_get); +- +-void ipu_dp_put(struct ipu_dp *dp) +-{ +- dp->in_use = false; +-} +-EXPORT_SYMBOL_GPL(ipu_dp_put); +- +-int ipu_dp_init(struct ipu_soc *ipu, struct device *dev, unsigned long base) +-{ +- struct ipu_dp_priv *priv; +- int i; +- +- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); +- if (!priv) +- return -ENOMEM; +- priv->dev = dev; +- priv->ipu = ipu; +- +- ipu->dp_priv = priv; +- +- priv->base = devm_ioremap(dev, base, PAGE_SIZE); +- if (!priv->base) +- return -ENOMEM; +- +- mutex_init(&priv->mutex); +- +- for (i = 0; i < IPUV3_NUM_FLOWS; i++) { +- priv->flow[i].background.in_cs = IPUV3_COLORSPACE_UNKNOWN; +- priv->flow[i].foreground.in_cs = IPUV3_COLORSPACE_UNKNOWN; +- priv->flow[i].foreground.foreground = true; +- priv->flow[i].base = priv->base + ipu_dp_flow_base[i]; +- priv->flow[i].priv = priv; +- } +- +- return 0; +-} +- +-void ipu_dp_exit(struct ipu_soc *ipu) +-{ +-} +--- a/drivers/gpu/imx/ipu-v3/ipu-ic.c ++++ /dev/null +@@ -1,761 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-or-later +-/* +- * Copyright (C) 2012-2014 Mentor Graphics Inc. +- * Copyright 2005-2012 Freescale Semiconductor, Inc. All Rights Reserved. +- */ +- +-#include <linux/types.h> +-#include <linux/init.h> +-#include <linux/errno.h> +-#include <linux/spinlock.h> +-#include <linux/bitrev.h> +-#include <linux/io.h> +-#include <linux/err.h> +-#include <linux/sizes.h> +-#include "ipu-prv.h" +- +-/* IC Register Offsets */ +-#define IC_CONF 0x0000 +-#define IC_PRP_ENC_RSC 0x0004 +-#define IC_PRP_VF_RSC 0x0008 +-#define IC_PP_RSC 0x000C +-#define IC_CMBP_1 0x0010 +-#define IC_CMBP_2 0x0014 +-#define IC_IDMAC_1 0x0018 +-#define IC_IDMAC_2 0x001C +-#define IC_IDMAC_3 0x0020 +-#define IC_IDMAC_4 0x0024 +- +-/* IC Register Fields */ +-#define IC_CONF_PRPENC_EN (1 << 0) +-#define IC_CONF_PRPENC_CSC1 (1 << 1) +-#define IC_CONF_PRPENC_ROT_EN (1 << 2) +-#define IC_CONF_PRPVF_EN (1 << 8) +-#define IC_CONF_PRPVF_CSC1 (1 << 9) +-#define IC_CONF_PRPVF_CSC2 (1 << 10) +-#define IC_CONF_PRPVF_CMB (1 << 11) +-#define IC_CONF_PRPVF_ROT_EN (1 << 12) +-#define IC_CONF_PP_EN (1 << 16) +-#define IC_CONF_PP_CSC1 (1 << 17) +-#define IC_CONF_PP_CSC2 (1 << 18) +-#define IC_CONF_PP_CMB (1 << 19) +-#define IC_CONF_PP_ROT_EN (1 << 20) +-#define IC_CONF_IC_GLB_LOC_A (1 << 28) +-#define IC_CONF_KEY_COLOR_EN (1 << 29) +-#define IC_CONF_RWS_EN (1 << 30) +-#define IC_CONF_CSI_MEM_WR_EN (1 << 31) +- +-#define IC_IDMAC_1_CB0_BURST_16 (1 << 0) +-#define IC_IDMAC_1_CB1_BURST_16 (1 << 1) +-#define IC_IDMAC_1_CB2_BURST_16 (1 << 2) +-#define IC_IDMAC_1_CB3_BURST_16 (1 << 3) +-#define IC_IDMAC_1_CB4_BURST_16 (1 << 4) +-#define IC_IDMAC_1_CB5_BURST_16 (1 << 5) +-#define IC_IDMAC_1_CB6_BURST_16 (1 << 6) +-#define IC_IDMAC_1_CB7_BURST_16 (1 << 7) +-#define IC_IDMAC_1_PRPENC_ROT_MASK (0x7 << 11) +-#define IC_IDMAC_1_PRPENC_ROT_OFFSET 11 +-#define IC_IDMAC_1_PRPVF_ROT_MASK (0x7 << 14) +-#define IC_IDMAC_1_PRPVF_ROT_OFFSET 14 +-#define IC_IDMAC_1_PP_ROT_MASK (0x7 << 17) +-#define IC_IDMAC_1_PP_ROT_OFFSET 17 +-#define IC_IDMAC_1_PP_FLIP_RS (1 << 22) +-#define IC_IDMAC_1_PRPVF_FLIP_RS (1 << 21) +-#define IC_IDMAC_1_PRPENC_FLIP_RS (1 << 20) +- +-#define IC_IDMAC_2_PRPENC_HEIGHT_MASK (0x3ff << 0) +-#define IC_IDMAC_2_PRPENC_HEIGHT_OFFSET 0 +-#define IC_IDMAC_2_PRPVF_HEIGHT_MASK (0x3ff << 10) +-#define IC_IDMAC_2_PRPVF_HEIGHT_OFFSET 10 +-#define IC_IDMAC_2_PP_HEIGHT_MASK (0x3ff << 20) +-#define IC_IDMAC_2_PP_HEIGHT_OFFSET 20 +- +-#define IC_IDMAC_3_PRPENC_WIDTH_MASK (0x3ff << 0) +-#define IC_IDMAC_3_PRPENC_WIDTH_OFFSET 0 +-#define IC_IDMAC_3_PRPVF_WIDTH_MASK (0x3ff << 10) +-#define IC_IDMAC_3_PRPVF_WIDTH_OFFSET 10 +-#define IC_IDMAC_3_PP_WIDTH_MASK (0x3ff << 20) +-#define IC_IDMAC_3_PP_WIDTH_OFFSET 20 +- +-struct ic_task_regoffs { +- u32 rsc; +- u32 tpmem_csc[2]; +-}; +- +-struct ic_task_bitfields { +- u32 ic_conf_en; +- u32 ic_conf_rot_en; +- u32 ic_conf_cmb_en; +- u32 ic_conf_csc1_en; +- u32 ic_conf_csc2_en; +- u32 ic_cmb_galpha_bit; +-}; +- +-static const struct ic_task_regoffs ic_task_reg[IC_NUM_TASKS] = { +- [IC_TASK_ENCODER] = { +- .rsc = IC_PRP_ENC_RSC, +- .tpmem_csc = {0x2008, 0}, +- }, +- [IC_TASK_VIEWFINDER] = { +- .rsc = IC_PRP_VF_RSC, +- .tpmem_csc = {0x4028, 0x4040}, +- }, +- [IC_TASK_POST_PROCESSOR] = { +- .rsc = IC_PP_RSC, +- .tpmem_csc = {0x6060, 0x6078}, +- }, +-}; +- +-static const struct ic_task_bitfields ic_task_bit[IC_NUM_TASKS] = { +- [IC_TASK_ENCODER] = { +- .ic_conf_en = IC_CONF_PRPENC_EN, +- .ic_conf_rot_en = IC_CONF_PRPENC_ROT_EN, +- .ic_conf_cmb_en = 0, /* NA */ +- .ic_conf_csc1_en = IC_CONF_PRPENC_CSC1, +- .ic_conf_csc2_en = 0, /* NA */ +- .ic_cmb_galpha_bit = 0, /* NA */ +- }, +- [IC_TASK_VIEWFINDER] = { +- .ic_conf_en = IC_CONF_PRPVF_EN, +- .ic_conf_rot_en = IC_CONF_PRPVF_ROT_EN, +- .ic_conf_cmb_en = IC_CONF_PRPVF_CMB, +- .ic_conf_csc1_en = IC_CONF_PRPVF_CSC1, +- .ic_conf_csc2_en = IC_CONF_PRPVF_CSC2, +- .ic_cmb_galpha_bit = 0, +- }, +- [IC_TASK_POST_PROCESSOR] = { +- .ic_conf_en = IC_CONF_PP_EN, +- .ic_conf_rot_en = IC_CONF_PP_ROT_EN, +- .ic_conf_cmb_en = IC_CONF_PP_CMB, +- .ic_conf_csc1_en = IC_CONF_PP_CSC1, +- .ic_conf_csc2_en = IC_CONF_PP_CSC2, +- .ic_cmb_galpha_bit = 8, +- }, +-}; +- +-struct ipu_ic_priv; +- +-struct ipu_ic { +- enum ipu_ic_task task; +- const struct ic_task_regoffs *reg; +- const struct ic_task_bitfields *bit; +- +- struct ipu_ic_colorspace in_cs; +- struct ipu_ic_colorspace g_in_cs; +- struct ipu_ic_colorspace out_cs; +- +- bool graphics; +- bool rotation; +- bool in_use; +- +- struct ipu_ic_priv *priv; +-}; +- +-struct ipu_ic_priv { +- void __iomem *base; +- void __iomem *tpmem_base; +- spinlock_t lock; +- struct ipu_soc *ipu; +- int use_count; +- int irt_use_count; +- struct ipu_ic task[IC_NUM_TASKS]; +-}; +- +-static inline u32 ipu_ic_read(struct ipu_ic *ic, unsigned offset) +-{ +- return readl(ic->priv->base + offset); +-} +- +-static inline void ipu_ic_write(struct ipu_ic *ic, u32 value, unsigned offset) +-{ +- writel(value, ic->priv->base + offset); +-} +- +-static int init_csc(struct ipu_ic *ic, +- const struct ipu_ic_csc *csc, +- int csc_index) +-{ +- struct ipu_ic_priv *priv = ic->priv; +- u32 __iomem *base; +- const u16 (*c)[3]; +- const u16 *a; +- u32 param; +- +- base = (u32 __iomem *) +- (priv->tpmem_base + ic->reg->tpmem_csc[csc_index]); +- +- /* Cast to unsigned */ +- c = (const u16 (*)[3])csc->params.coeff; +- a = (const u16 *)csc->params.offset; +- +- param = ((a[0] & 0x1f) << 27) | ((c[0][0] & 0x1ff) << 18) | +- ((c[1][1] & 0x1ff) << 9) | (c[2][2] & 0x1ff); +- writel(param, base++); +- +- param = ((a[0] & 0x1fe0) >> 5) | (csc->params.scale << 8) | +- (csc->params.sat << 10); +- writel(param, base++); +- +- param = ((a[1] & 0x1f) << 27) | ((c[0][1] & 0x1ff) << 18) | +- ((c[1][0] & 0x1ff) << 9) | (c[2][0] & 0x1ff); +- writel(param, base++); +- +- param = ((a[1] & 0x1fe0) >> 5); +- writel(param, base++); +- +- param = ((a[2] & 0x1f) << 27) | ((c[0][2] & 0x1ff) << 18) | +- ((c[1][2] & 0x1ff) << 9) | (c[2][1] & 0x1ff); +- writel(param, base++); +- +- param = ((a[2] & 0x1fe0) >> 5); +- writel(param, base++); +- +- return 0; +-} +- +-static int calc_resize_coeffs(struct ipu_ic *ic, +- u32 in_size, u32 out_size, +- u32 *resize_coeff, +- u32 *downsize_coeff) +-{ +- struct ipu_ic_priv *priv = ic->priv; +- struct ipu_soc *ipu = priv->ipu; +- u32 temp_size, temp_downsize; +- +- /* +- * Input size cannot be more than 4096, and output size cannot +- * be more than 1024 +- */ +- if (in_size > 4096) { +- dev_err(ipu->dev, "Unsupported resize (in_size > 4096)\n"); +- return -EINVAL; +- } +- if (out_size > 1024) { +- dev_err(ipu->dev, "Unsupported resize (out_size > 1024)\n"); +- return -EINVAL; +- } +- +- /* Cannot downsize more than 4:1 */ +- if ((out_size << 2) < in_size) { +- dev_err(ipu->dev, "Unsupported downsize\n"); +- return -EINVAL; +- } +- +- /* Compute downsizing coefficient */ +- temp_downsize = 0; +- temp_size = in_size; +- while (((temp_size > 1024) || (temp_size >= out_size * 2)) && +- (temp_downsize < 2)) { +- temp_size >>= 1; +- temp_downsize++; +- } +- *downsize_coeff = temp_downsize; +- +- /* +- * compute resizing coefficient using the following equation: +- * resize_coeff = M * (SI - 1) / (SO - 1) +- * where M = 2^13, SI = input size, SO = output size +- */ +- *resize_coeff = (8192L * (temp_size - 1)) / (out_size - 1); +- if (*resize_coeff >= 16384L) { +- dev_err(ipu->dev, "Warning! Overflow on resize coeff.\n"); +- *resize_coeff = 0x3FFF; +- } +- +- return 0; +-} +- +-void ipu_ic_task_enable(struct ipu_ic *ic) +-{ +- struct ipu_ic_priv *priv = ic->priv; +- unsigned long flags; +- u32 ic_conf; +- +- spin_lock_irqsave(&priv->lock, flags); +- +- ic_conf = ipu_ic_read(ic, IC_CONF); +- +- ic_conf |= ic->bit->ic_conf_en; +- +- if (ic->rotation) +- ic_conf |= ic->bit->ic_conf_rot_en; +- +- if (ic->in_cs.cs != ic->out_cs.cs) +- ic_conf |= ic->bit->ic_conf_csc1_en; +- +- if (ic->graphics) { +- ic_conf |= ic->bit->ic_conf_cmb_en; +- ic_conf |= ic->bit->ic_conf_csc1_en; +- +- if (ic->g_in_cs.cs != ic->out_cs.cs) +- ic_conf |= ic->bit->ic_conf_csc2_en; +- } +- +- ipu_ic_write(ic, ic_conf, IC_CONF); +- +- spin_unlock_irqrestore(&priv->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_ic_task_enable); +- +-void ipu_ic_task_disable(struct ipu_ic *ic) +-{ +- struct ipu_ic_priv *priv = ic->priv; +- unsigned long flags; +- u32 ic_conf; +- +- spin_lock_irqsave(&priv->lock, flags); +- +- ic_conf = ipu_ic_read(ic, IC_CONF); +- +- ic_conf &= ~(ic->bit->ic_conf_en | +- ic->bit->ic_conf_csc1_en | +- ic->bit->ic_conf_rot_en); +- if (ic->bit->ic_conf_csc2_en) +- ic_conf &= ~ic->bit->ic_conf_csc2_en; +- if (ic->bit->ic_conf_cmb_en) +- ic_conf &= ~ic->bit->ic_conf_cmb_en; +- +- ipu_ic_write(ic, ic_conf, IC_CONF); +- +- spin_unlock_irqrestore(&priv->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_ic_task_disable); +- +-int ipu_ic_task_graphics_init(struct ipu_ic *ic, +- const struct ipu_ic_colorspace *g_in_cs, +- bool galpha_en, u32 galpha, +- bool colorkey_en, u32 colorkey) +-{ +- struct ipu_ic_priv *priv = ic->priv; +- struct ipu_ic_csc csc2; +- unsigned long flags; +- u32 reg, ic_conf; +- int ret = 0; +- +- if (ic->task == IC_TASK_ENCODER) +- return -EINVAL; +- +- spin_lock_irqsave(&priv->lock, flags); +- +- ic_conf = ipu_ic_read(ic, IC_CONF); +- +- if (!(ic_conf & ic->bit->ic_conf_csc1_en)) { +- struct ipu_ic_csc csc1; +- +- ret = ipu_ic_calc_csc(&csc1, +- V4L2_YCBCR_ENC_601, +- V4L2_QUANTIZATION_FULL_RANGE, +- IPUV3_COLORSPACE_RGB, +- V4L2_YCBCR_ENC_601, +- V4L2_QUANTIZATION_FULL_RANGE, +- IPUV3_COLORSPACE_RGB); +- if (ret) +- goto unlock; +- +- /* need transparent CSC1 conversion */ +- ret = init_csc(ic, &csc1, 0); +- if (ret) +- goto unlock; +- } +- +- ic->g_in_cs = *g_in_cs; +- csc2.in_cs = ic->g_in_cs; +- csc2.out_cs = ic->out_cs; +- +- ret = __ipu_ic_calc_csc(&csc2); +- if (ret) +- goto unlock; +- +- ret = init_csc(ic, &csc2, 1); +- if (ret) +- goto unlock; +- +- if (galpha_en) { +- ic_conf |= IC_CONF_IC_GLB_LOC_A; +- reg = ipu_ic_read(ic, IC_CMBP_1); +- reg &= ~(0xff << ic->bit->ic_cmb_galpha_bit); +- reg |= (galpha << ic->bit->ic_cmb_galpha_bit); +- ipu_ic_write(ic, reg, IC_CMBP_1); +- } else +- ic_conf &= ~IC_CONF_IC_GLB_LOC_A; +- +- if (colorkey_en) { +- ic_conf |= IC_CONF_KEY_COLOR_EN; +- ipu_ic_write(ic, colorkey, IC_CMBP_2); +- } else +- ic_conf &= ~IC_CONF_KEY_COLOR_EN; +- +- ipu_ic_write(ic, ic_conf, IC_CONF); +- +- ic->graphics = true; +-unlock: +- spin_unlock_irqrestore(&priv->lock, flags); +- return ret; +-} +-EXPORT_SYMBOL_GPL(ipu_ic_task_graphics_init); +- +-int ipu_ic_task_init_rsc(struct ipu_ic *ic, +- const struct ipu_ic_csc *csc, +- int in_width, int in_height, +- int out_width, int out_height, +- u32 rsc) +-{ +- struct ipu_ic_priv *priv = ic->priv; +- u32 downsize_coeff, resize_coeff; +- unsigned long flags; +- int ret = 0; +- +- if (!rsc) { +- /* Setup vertical resizing */ +- +- ret = calc_resize_coeffs(ic, in_height, out_height, +- &resize_coeff, &downsize_coeff); +- if (ret) +- return ret; +- +- rsc = (downsize_coeff << 30) | (resize_coeff << 16); +- +- /* Setup horizontal resizing */ +- ret = calc_resize_coeffs(ic, in_width, out_width, +- &resize_coeff, &downsize_coeff); +- if (ret) +- return ret; +- +- rsc |= (downsize_coeff << 14) | resize_coeff; +- } +- +- spin_lock_irqsave(&priv->lock, flags); +- +- ipu_ic_write(ic, rsc, ic->reg->rsc); +- +- /* Setup color space conversion */ +- ic->in_cs = csc->in_cs; +- ic->out_cs = csc->out_cs; +- +- ret = init_csc(ic, csc, 0); +- +- spin_unlock_irqrestore(&priv->lock, flags); +- return ret; +-} +- +-int ipu_ic_task_init(struct ipu_ic *ic, +- const struct ipu_ic_csc *csc, +- int in_width, int in_height, +- int out_width, int out_height) +-{ +- return ipu_ic_task_init_rsc(ic, csc, +- in_width, in_height, +- out_width, out_height, 0); +-} +-EXPORT_SYMBOL_GPL(ipu_ic_task_init); +- +-int ipu_ic_task_idma_init(struct ipu_ic *ic, struct ipuv3_channel *channel, +- u32 width, u32 height, int burst_size, +- enum ipu_rotate_mode rot) +-{ +- struct ipu_ic_priv *priv = ic->priv; +- struct ipu_soc *ipu = priv->ipu; +- u32 ic_idmac_1, ic_idmac_2, ic_idmac_3; +- u32 temp_rot = bitrev8(rot) >> 5; +- bool need_hor_flip = false; +- unsigned long flags; +- int ret = 0; +- +- if ((burst_size != 8) && (burst_size != 16)) { +- dev_err(ipu->dev, "Illegal burst length for IC\n"); +- return -EINVAL; +- } +- +- width--; +- height--; +- +- if (temp_rot & 0x2) /* Need horizontal flip */ +- need_hor_flip = true; +- +- spin_lock_irqsave(&priv->lock, flags); +- +- ic_idmac_1 = ipu_ic_read(ic, IC_IDMAC_1); +- ic_idmac_2 = ipu_ic_read(ic, IC_IDMAC_2); +- ic_idmac_3 = ipu_ic_read(ic, IC_IDMAC_3); +- +- switch (channel->num) { +- case IPUV3_CHANNEL_IC_PP_MEM: +- if (burst_size == 16) +- ic_idmac_1 |= IC_IDMAC_1_CB2_BURST_16; +- else +- ic_idmac_1 &= ~IC_IDMAC_1_CB2_BURST_16; +- +- if (need_hor_flip) +- ic_idmac_1 |= IC_IDMAC_1_PP_FLIP_RS; +- else +- ic_idmac_1 &= ~IC_IDMAC_1_PP_FLIP_RS; +- +- ic_idmac_2 &= ~IC_IDMAC_2_PP_HEIGHT_MASK; +- ic_idmac_2 |= height << IC_IDMAC_2_PP_HEIGHT_OFFSET; +- +- ic_idmac_3 &= ~IC_IDMAC_3_PP_WIDTH_MASK; +- ic_idmac_3 |= width << IC_IDMAC_3_PP_WIDTH_OFFSET; +- break; +- case IPUV3_CHANNEL_MEM_IC_PP: +- if (burst_size == 16) +- ic_idmac_1 |= IC_IDMAC_1_CB5_BURST_16; +- else +- ic_idmac_1 &= ~IC_IDMAC_1_CB5_BURST_16; +- break; +- case IPUV3_CHANNEL_MEM_ROT_PP: +- ic_idmac_1 &= ~IC_IDMAC_1_PP_ROT_MASK; +- ic_idmac_1 |= temp_rot << IC_IDMAC_1_PP_ROT_OFFSET; +- break; +- case IPUV3_CHANNEL_MEM_IC_PRP_VF: +- if (burst_size == 16) +- ic_idmac_1 |= IC_IDMAC_1_CB6_BURST_16; +- else +- ic_idmac_1 &= ~IC_IDMAC_1_CB6_BURST_16; +- break; +- case IPUV3_CHANNEL_IC_PRP_ENC_MEM: +- if (burst_size == 16) +- ic_idmac_1 |= IC_IDMAC_1_CB0_BURST_16; +- else +- ic_idmac_1 &= ~IC_IDMAC_1_CB0_BURST_16; +- +- if (need_hor_flip) +- ic_idmac_1 |= IC_IDMAC_1_PRPENC_FLIP_RS; +- else +- ic_idmac_1 &= ~IC_IDMAC_1_PRPENC_FLIP_RS; +- +- ic_idmac_2 &= ~IC_IDMAC_2_PRPENC_HEIGHT_MASK; +- ic_idmac_2 |= height << IC_IDMAC_2_PRPENC_HEIGHT_OFFSET; +- +- ic_idmac_3 &= ~IC_IDMAC_3_PRPENC_WIDTH_MASK; +- ic_idmac_3 |= width << IC_IDMAC_3_PRPENC_WIDTH_OFFSET; +- break; +- case IPUV3_CHANNEL_MEM_ROT_ENC: +- ic_idmac_1 &= ~IC_IDMAC_1_PRPENC_ROT_MASK; +- ic_idmac_1 |= temp_rot << IC_IDMAC_1_PRPENC_ROT_OFFSET; +- break; +- case IPUV3_CHANNEL_IC_PRP_VF_MEM: +- if (burst_size == 16) +- ic_idmac_1 |= IC_IDMAC_1_CB1_BURST_16; +- else +- ic_idmac_1 &= ~IC_IDMAC_1_CB1_BURST_16; +- +- if (need_hor_flip) +- ic_idmac_1 |= IC_IDMAC_1_PRPVF_FLIP_RS; +- else +- ic_idmac_1 &= ~IC_IDMAC_1_PRPVF_FLIP_RS; +- +- ic_idmac_2 &= ~IC_IDMAC_2_PRPVF_HEIGHT_MASK; +- ic_idmac_2 |= height << IC_IDMAC_2_PRPVF_HEIGHT_OFFSET; +- +- ic_idmac_3 &= ~IC_IDMAC_3_PRPVF_WIDTH_MASK; +- ic_idmac_3 |= width << IC_IDMAC_3_PRPVF_WIDTH_OFFSET; +- break; +- case IPUV3_CHANNEL_MEM_ROT_VF: +- ic_idmac_1 &= ~IC_IDMAC_1_PRPVF_ROT_MASK; +- ic_idmac_1 |= temp_rot << IC_IDMAC_1_PRPVF_ROT_OFFSET; +- break; +- case IPUV3_CHANNEL_G_MEM_IC_PRP_VF: +- if (burst_size == 16) +- ic_idmac_1 |= IC_IDMAC_1_CB3_BURST_16; +- else +- ic_idmac_1 &= ~IC_IDMAC_1_CB3_BURST_16; +- break; +- case IPUV3_CHANNEL_G_MEM_IC_PP: +- if (burst_size == 16) +- ic_idmac_1 |= IC_IDMAC_1_CB4_BURST_16; +- else +- ic_idmac_1 &= ~IC_IDMAC_1_CB4_BURST_16; +- break; +- case IPUV3_CHANNEL_VDI_MEM_IC_VF: +- if (burst_size == 16) +- ic_idmac_1 |= IC_IDMAC_1_CB7_BURST_16; +- else +- ic_idmac_1 &= ~IC_IDMAC_1_CB7_BURST_16; +- break; +- default: +- goto unlock; +- } +- +- ipu_ic_write(ic, ic_idmac_1, IC_IDMAC_1); +- ipu_ic_write(ic, ic_idmac_2, IC_IDMAC_2); +- ipu_ic_write(ic, ic_idmac_3, IC_IDMAC_3); +- +- if (ipu_rot_mode_is_irt(rot)) +- ic->rotation = true; +- +-unlock: +- spin_unlock_irqrestore(&priv->lock, flags); +- return ret; +-} +-EXPORT_SYMBOL_GPL(ipu_ic_task_idma_init); +- +-static void ipu_irt_enable(struct ipu_ic *ic) +-{ +- struct ipu_ic_priv *priv = ic->priv; +- +- if (!priv->irt_use_count) +- ipu_module_enable(priv->ipu, IPU_CONF_ROT_EN); +- +- priv->irt_use_count++; +-} +- +-static void ipu_irt_disable(struct ipu_ic *ic) +-{ +- struct ipu_ic_priv *priv = ic->priv; +- +- if (priv->irt_use_count) { +- if (!--priv->irt_use_count) +- ipu_module_disable(priv->ipu, IPU_CONF_ROT_EN); +- } +-} +- +-int ipu_ic_enable(struct ipu_ic *ic) +-{ +- struct ipu_ic_priv *priv = ic->priv; +- unsigned long flags; +- +- spin_lock_irqsave(&priv->lock, flags); +- +- if (!priv->use_count) +- ipu_module_enable(priv->ipu, IPU_CONF_IC_EN); +- +- priv->use_count++; +- +- if (ic->rotation) +- ipu_irt_enable(ic); +- +- spin_unlock_irqrestore(&priv->lock, flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_ic_enable); +- +-int ipu_ic_disable(struct ipu_ic *ic) +-{ +- struct ipu_ic_priv *priv = ic->priv; +- unsigned long flags; +- +- spin_lock_irqsave(&priv->lock, flags); +- +- priv->use_count--; +- +- if (!priv->use_count) +- ipu_module_disable(priv->ipu, IPU_CONF_IC_EN); +- +- if (priv->use_count < 0) +- priv->use_count = 0; +- +- if (ic->rotation) +- ipu_irt_disable(ic); +- +- ic->rotation = ic->graphics = false; +- +- spin_unlock_irqrestore(&priv->lock, flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_ic_disable); +- +-struct ipu_ic *ipu_ic_get(struct ipu_soc *ipu, enum ipu_ic_task task) +-{ +- struct ipu_ic_priv *priv = ipu->ic_priv; +- unsigned long flags; +- struct ipu_ic *ic, *ret; +- +- if (task >= IC_NUM_TASKS) +- return ERR_PTR(-EINVAL); +- +- ic = &priv->task[task]; +- +- spin_lock_irqsave(&priv->lock, flags); +- +- if (ic->in_use) { +- ret = ERR_PTR(-EBUSY); +- goto unlock; +- } +- +- ic->in_use = true; +- ret = ic; +- +-unlock: +- spin_unlock_irqrestore(&priv->lock, flags); +- return ret; +-} +-EXPORT_SYMBOL_GPL(ipu_ic_get); +- +-void ipu_ic_put(struct ipu_ic *ic) +-{ +- struct ipu_ic_priv *priv = ic->priv; +- unsigned long flags; +- +- spin_lock_irqsave(&priv->lock, flags); +- ic->in_use = false; +- spin_unlock_irqrestore(&priv->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_ic_put); +- +-int ipu_ic_init(struct ipu_soc *ipu, struct device *dev, +- unsigned long base, unsigned long tpmem_base) +-{ +- struct ipu_ic_priv *priv; +- int i; +- +- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); +- if (!priv) +- return -ENOMEM; +- +- ipu->ic_priv = priv; +- +- spin_lock_init(&priv->lock); +- priv->base = devm_ioremap(dev, base, PAGE_SIZE); +- if (!priv->base) +- return -ENOMEM; +- priv->tpmem_base = devm_ioremap(dev, tpmem_base, SZ_64K); +- if (!priv->tpmem_base) +- return -ENOMEM; +- +- dev_dbg(dev, "IC base: 0x%08lx remapped to %p\n", base, priv->base); +- +- priv->ipu = ipu; +- +- for (i = 0; i < IC_NUM_TASKS; i++) { +- priv->task[i].task = i; +- priv->task[i].priv = priv; +- priv->task[i].reg = &ic_task_reg[i]; +- priv->task[i].bit = &ic_task_bit[i]; +- } +- +- return 0; +-} +- +-void ipu_ic_exit(struct ipu_soc *ipu) +-{ +-} +- +-void ipu_ic_dump(struct ipu_ic *ic) +-{ +- struct ipu_ic_priv *priv = ic->priv; +- struct ipu_soc *ipu = priv->ipu; +- +- dev_dbg(ipu->dev, "IC_CONF = \t0x%08X\n", +- ipu_ic_read(ic, IC_CONF)); +- dev_dbg(ipu->dev, "IC_PRP_ENC_RSC = \t0x%08X\n", +- ipu_ic_read(ic, IC_PRP_ENC_RSC)); +- dev_dbg(ipu->dev, "IC_PRP_VF_RSC = \t0x%08X\n", +- ipu_ic_read(ic, IC_PRP_VF_RSC)); +- dev_dbg(ipu->dev, "IC_PP_RSC = \t0x%08X\n", +- ipu_ic_read(ic, IC_PP_RSC)); +- dev_dbg(ipu->dev, "IC_CMBP_1 = \t0x%08X\n", +- ipu_ic_read(ic, IC_CMBP_1)); +- dev_dbg(ipu->dev, "IC_CMBP_2 = \t0x%08X\n", +- ipu_ic_read(ic, IC_CMBP_2)); +- dev_dbg(ipu->dev, "IC_IDMAC_1 = \t0x%08X\n", +- ipu_ic_read(ic, IC_IDMAC_1)); +- dev_dbg(ipu->dev, "IC_IDMAC_2 = \t0x%08X\n", +- ipu_ic_read(ic, IC_IDMAC_2)); +- dev_dbg(ipu->dev, "IC_IDMAC_3 = \t0x%08X\n", +- ipu_ic_read(ic, IC_IDMAC_3)); +- dev_dbg(ipu->dev, "IC_IDMAC_4 = \t0x%08X\n", +- ipu_ic_read(ic, IC_IDMAC_4)); +-} +-EXPORT_SYMBOL_GPL(ipu_ic_dump); +--- a/drivers/gpu/imx/ipu-v3/ipu-image-convert.c ++++ /dev/null +@@ -1,2475 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-or-later +-/* +- * Copyright (C) 2012-2016 Mentor Graphics Inc. +- * +- * Queued image conversion support, with tiling and rotation. +- */ +- +-#include <linux/interrupt.h> +-#include <linux/dma-mapping.h> +-#include <video/imx-ipu-image-convert.h> +-#include "ipu-prv.h" +- +-/* +- * The IC Resizer has a restriction that the output frame from the +- * resizer must be 1024 or less in both width (pixels) and height +- * (lines). +- * +- * The image converter attempts to split up a conversion when +- * the desired output (converted) frame resolution exceeds the +- * IC resizer limit of 1024 in either dimension. +- * +- * If either dimension of the output frame exceeds the limit, the +- * dimension is split into 1, 2, or 4 equal stripes, for a maximum +- * of 4*4 or 16 tiles. A conversion is then carried out for each +- * tile (but taking care to pass the full frame stride length to +- * the DMA channel's parameter memory!). IDMA double-buffering is used +- * to convert each tile back-to-back when possible (see note below +- * when double_buffering boolean is set). +- * +- * Note that the input frame must be split up into the same number +- * of tiles as the output frame: +- * +- * +---------+-----+ +- * +-----+---+ | A | B | +- * | A | B | | | | +- * +-----+---+ --> +---------+-----+ +- * | C | D | | C | D | +- * +-----+---+ | | | +- * +---------+-----+ +- * +- * Clockwise 90° rotations are handled by first rescaling into a +- * reusable temporary tile buffer and then rotating with the 8x8 +- * block rotator, writing to the correct destination: +- * +- * +-----+-----+ +- * | | | +- * +-----+---+ +---------+ | C | A | +- * | A | B | | A,B, | | | | | +- * +-----+---+ --> | C,D | | --> | | | +- * | C | D | +---------+ +-----+-----+ +- * +-----+---+ | D | B | +- * | | | +- * +-----+-----+ +- * +- * If the 8x8 block rotator is used, horizontal or vertical flipping +- * is done during the rotation step, otherwise flipping is done +- * during the scaling step. +- * With rotation or flipping, tile order changes between input and +- * output image. Tiles are numbered row major from top left to bottom +- * right for both input and output image. +- */ +- +-#define MAX_STRIPES_W 4 +-#define MAX_STRIPES_H 4 +-#define MAX_TILES (MAX_STRIPES_W * MAX_STRIPES_H) +- +-#define MIN_W 16 +-#define MIN_H 8 +-#define MAX_W 4096 +-#define MAX_H 4096 +- +-enum ipu_image_convert_type { +- IMAGE_CONVERT_IN = 0, +- IMAGE_CONVERT_OUT, +-}; +- +-struct ipu_image_convert_dma_buf { +- void *virt; +- dma_addr_t phys; +- unsigned long len; +-}; +- +-struct ipu_image_convert_dma_chan { +- int in; +- int out; +- int rot_in; +- int rot_out; +- int vdi_in_p; +- int vdi_in; +- int vdi_in_n; +-}; +- +-/* dimensions of one tile */ +-struct ipu_image_tile { +- u32 width; +- u32 height; +- u32 left; +- u32 top; +- /* size and strides are in bytes */ +- u32 size; +- u32 stride; +- u32 rot_stride; +- /* start Y or packed offset of this tile */ +- u32 offset; +- /* offset from start to tile in U plane, for planar formats */ +- u32 u_off; +- /* offset from start to tile in V plane, for planar formats */ +- u32 v_off; +-}; +- +-struct ipu_image_convert_image { +- struct ipu_image base; +- enum ipu_image_convert_type type; +- +- const struct ipu_image_pixfmt *fmt; +- unsigned int stride; +- +- /* # of rows (horizontal stripes) if dest height is > 1024 */ +- unsigned int num_rows; +- /* # of columns (vertical stripes) if dest width is > 1024 */ +- unsigned int num_cols; +- +- struct ipu_image_tile tile[MAX_TILES]; +-}; +- +-struct ipu_image_pixfmt { +- u32 fourcc; /* V4L2 fourcc */ +- int bpp; /* total bpp */ +- int uv_width_dec; /* decimation in width for U/V planes */ +- int uv_height_dec; /* decimation in height for U/V planes */ +- bool planar; /* planar format */ +- bool uv_swapped; /* U and V planes are swapped */ +- bool uv_packed; /* partial planar (U and V in same plane) */ +-}; +- +-struct ipu_image_convert_ctx; +-struct ipu_image_convert_chan; +-struct ipu_image_convert_priv; +- +-struct ipu_image_convert_ctx { +- struct ipu_image_convert_chan *chan; +- +- ipu_image_convert_cb_t complete; +- void *complete_context; +- +- /* Source/destination image data and rotation mode */ +- struct ipu_image_convert_image in; +- struct ipu_image_convert_image out; +- struct ipu_ic_csc csc; +- enum ipu_rotate_mode rot_mode; +- u32 downsize_coeff_h; +- u32 downsize_coeff_v; +- u32 image_resize_coeff_h; +- u32 image_resize_coeff_v; +- u32 resize_coeffs_h[MAX_STRIPES_W]; +- u32 resize_coeffs_v[MAX_STRIPES_H]; +- +- /* intermediate buffer for rotation */ +- struct ipu_image_convert_dma_buf rot_intermediate[2]; +- +- /* current buffer number for double buffering */ +- int cur_buf_num; +- +- bool aborting; +- struct completion aborted; +- +- /* can we use double-buffering for this conversion operation? */ +- bool double_buffering; +- /* num_rows * num_cols */ +- unsigned int num_tiles; +- /* next tile to process */ +- unsigned int next_tile; +- /* where to place converted tile in dest image */ +- unsigned int out_tile_map[MAX_TILES]; +- +- struct list_head list; +-}; +- +-struct ipu_image_convert_chan { +- struct ipu_image_convert_priv *priv; +- +- enum ipu_ic_task ic_task; +- const struct ipu_image_convert_dma_chan *dma_ch; +- +- struct ipu_ic *ic; +- struct ipuv3_channel *in_chan; +- struct ipuv3_channel *out_chan; +- struct ipuv3_channel *rotation_in_chan; +- struct ipuv3_channel *rotation_out_chan; +- +- /* the IPU end-of-frame irqs */ +- int out_eof_irq; +- int rot_out_eof_irq; +- +- spinlock_t irqlock; +- +- /* list of convert contexts */ +- struct list_head ctx_list; +- /* queue of conversion runs */ +- struct list_head pending_q; +- /* queue of completed runs */ +- struct list_head done_q; +- +- /* the current conversion run */ +- struct ipu_image_convert_run *current_run; +-}; +- +-struct ipu_image_convert_priv { +- struct ipu_image_convert_chan chan[IC_NUM_TASKS]; +- struct ipu_soc *ipu; +-}; +- +-static const struct ipu_image_convert_dma_chan +-image_convert_dma_chan[IC_NUM_TASKS] = { +- [IC_TASK_VIEWFINDER] = { +- .in = IPUV3_CHANNEL_MEM_IC_PRP_VF, +- .out = IPUV3_CHANNEL_IC_PRP_VF_MEM, +- .rot_in = IPUV3_CHANNEL_MEM_ROT_VF, +- .rot_out = IPUV3_CHANNEL_ROT_VF_MEM, +- .vdi_in_p = IPUV3_CHANNEL_MEM_VDI_PREV, +- .vdi_in = IPUV3_CHANNEL_MEM_VDI_CUR, +- .vdi_in_n = IPUV3_CHANNEL_MEM_VDI_NEXT, +- }, +- [IC_TASK_POST_PROCESSOR] = { +- .in = IPUV3_CHANNEL_MEM_IC_PP, +- .out = IPUV3_CHANNEL_IC_PP_MEM, +- .rot_in = IPUV3_CHANNEL_MEM_ROT_PP, +- .rot_out = IPUV3_CHANNEL_ROT_PP_MEM, +- }, +-}; +- +-static const struct ipu_image_pixfmt image_convert_formats[] = { +- { +- .fourcc = V4L2_PIX_FMT_RGB565, +- .bpp = 16, +- }, { +- .fourcc = V4L2_PIX_FMT_RGB24, +- .bpp = 24, +- }, { +- .fourcc = V4L2_PIX_FMT_BGR24, +- .bpp = 24, +- }, { +- .fourcc = V4L2_PIX_FMT_RGB32, +- .bpp = 32, +- }, { +- .fourcc = V4L2_PIX_FMT_BGR32, +- .bpp = 32, +- }, { +- .fourcc = V4L2_PIX_FMT_XRGB32, +- .bpp = 32, +- }, { +- .fourcc = V4L2_PIX_FMT_XBGR32, +- .bpp = 32, +- }, { +- .fourcc = V4L2_PIX_FMT_BGRX32, +- .bpp = 32, +- }, { +- .fourcc = V4L2_PIX_FMT_RGBX32, +- .bpp = 32, +- }, { +- .fourcc = V4L2_PIX_FMT_YUYV, +- .bpp = 16, +- .uv_width_dec = 2, +- .uv_height_dec = 1, +- }, { +- .fourcc = V4L2_PIX_FMT_UYVY, +- .bpp = 16, +- .uv_width_dec = 2, +- .uv_height_dec = 1, +- }, { +- .fourcc = V4L2_PIX_FMT_YUV420, +- .bpp = 12, +- .planar = true, +- .uv_width_dec = 2, +- .uv_height_dec = 2, +- }, { +- .fourcc = V4L2_PIX_FMT_YVU420, +- .bpp = 12, +- .planar = true, +- .uv_width_dec = 2, +- .uv_height_dec = 2, +- .uv_swapped = true, +- }, { +- .fourcc = V4L2_PIX_FMT_NV12, +- .bpp = 12, +- .planar = true, +- .uv_width_dec = 2, +- .uv_height_dec = 2, +- .uv_packed = true, +- }, { +- .fourcc = V4L2_PIX_FMT_YUV422P, +- .bpp = 16, +- .planar = true, +- .uv_width_dec = 2, +- .uv_height_dec = 1, +- }, { +- .fourcc = V4L2_PIX_FMT_NV16, +- .bpp = 16, +- .planar = true, +- .uv_width_dec = 2, +- .uv_height_dec = 1, +- .uv_packed = true, +- }, +-}; +- +-static const struct ipu_image_pixfmt *get_format(u32 fourcc) +-{ +- const struct ipu_image_pixfmt *ret = NULL; +- unsigned int i; +- +- for (i = 0; i < ARRAY_SIZE(image_convert_formats); i++) { +- if (image_convert_formats[i].fourcc == fourcc) { +- ret = &image_convert_formats[i]; +- break; +- } +- } +- +- return ret; +-} +- +-static void dump_format(struct ipu_image_convert_ctx *ctx, +- struct ipu_image_convert_image *ic_image) +-{ +- struct ipu_image_convert_chan *chan = ctx->chan; +- struct ipu_image_convert_priv *priv = chan->priv; +- +- dev_dbg(priv->ipu->dev, +- "task %u: ctx %p: %s format: %dx%d (%dx%d tiles), %c%c%c%c\n", +- chan->ic_task, ctx, +- ic_image->type == IMAGE_CONVERT_OUT ? "Output" : "Input", +- ic_image->base.pix.width, ic_image->base.pix.height, +- ic_image->num_cols, ic_image->num_rows, +- ic_image->fmt->fourcc & 0xff, +- (ic_image->fmt->fourcc >> 8) & 0xff, +- (ic_image->fmt->fourcc >> 16) & 0xff, +- (ic_image->fmt->fourcc >> 24) & 0xff); +-} +- +-int ipu_image_convert_enum_format(int index, u32 *fourcc) +-{ +- const struct ipu_image_pixfmt *fmt; +- +- if (index >= (int)ARRAY_SIZE(image_convert_formats)) +- return -EINVAL; +- +- /* Format found */ +- fmt = &image_convert_formats[index]; +- *fourcc = fmt->fourcc; +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_image_convert_enum_format); +- +-static void free_dma_buf(struct ipu_image_convert_priv *priv, +- struct ipu_image_convert_dma_buf *buf) +-{ +- if (buf->virt) +- dma_free_coherent(priv->ipu->dev, +- buf->len, buf->virt, buf->phys); +- buf->virt = NULL; +- buf->phys = 0; +-} +- +-static int alloc_dma_buf(struct ipu_image_convert_priv *priv, +- struct ipu_image_convert_dma_buf *buf, +- int size) +-{ +- buf->len = PAGE_ALIGN(size); +- buf->virt = dma_alloc_coherent(priv->ipu->dev, buf->len, &buf->phys, +- GFP_DMA | GFP_KERNEL); +- if (!buf->virt) { +- dev_err(priv->ipu->dev, "failed to alloc dma buffer\n"); +- return -ENOMEM; +- } +- +- return 0; +-} +- +-static inline int num_stripes(int dim) +-{ +- return (dim - 1) / 1024 + 1; +-} +- +-/* +- * Calculate downsizing coefficients, which are the same for all tiles, +- * and initial bilinear resizing coefficients, which are used to find the +- * best seam positions. +- * Also determine the number of tiles necessary to guarantee that no tile +- * is larger than 1024 pixels in either dimension at the output and between +- * IC downsizing and main processing sections. +- */ +-static int calc_image_resize_coefficients(struct ipu_image_convert_ctx *ctx, +- struct ipu_image *in, +- struct ipu_image *out) +-{ +- u32 downsized_width = in->rect.width; +- u32 downsized_height = in->rect.height; +- u32 downsize_coeff_v = 0; +- u32 downsize_coeff_h = 0; +- u32 resized_width = out->rect.width; +- u32 resized_height = out->rect.height; +- u32 resize_coeff_h; +- u32 resize_coeff_v; +- u32 cols; +- u32 rows; +- +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) { +- resized_width = out->rect.height; +- resized_height = out->rect.width; +- } +- +- /* Do not let invalid input lead to an endless loop below */ +- if (WARN_ON(resized_width == 0 || resized_height == 0)) +- return -EINVAL; +- +- while (downsized_width >= resized_width * 2) { +- downsized_width >>= 1; +- downsize_coeff_h++; +- } +- +- while (downsized_height >= resized_height * 2) { +- downsized_height >>= 1; +- downsize_coeff_v++; +- } +- +- /* +- * Calculate the bilinear resizing coefficients that could be used if +- * we were converting with a single tile. The bottom right output pixel +- * should sample as close as possible to the bottom right input pixel +- * out of the decimator, but not overshoot it: +- */ +- resize_coeff_h = 8192 * (downsized_width - 1) / (resized_width - 1); +- resize_coeff_v = 8192 * (downsized_height - 1) / (resized_height - 1); +- +- /* +- * Both the output of the IC downsizing section before being passed to +- * the IC main processing section and the final output of the IC main +- * processing section must be <= 1024 pixels in both dimensions. +- */ +- cols = num_stripes(max_t(u32, downsized_width, resized_width)); +- rows = num_stripes(max_t(u32, downsized_height, resized_height)); +- +- dev_dbg(ctx->chan->priv->ipu->dev, +- "%s: hscale: >>%u, *8192/%u vscale: >>%u, *8192/%u, %ux%u tiles\n", +- __func__, downsize_coeff_h, resize_coeff_h, downsize_coeff_v, +- resize_coeff_v, cols, rows); +- +- if (downsize_coeff_h > 2 || downsize_coeff_v > 2 || +- resize_coeff_h > 0x3fff || resize_coeff_v > 0x3fff) +- return -EINVAL; +- +- ctx->downsize_coeff_h = downsize_coeff_h; +- ctx->downsize_coeff_v = downsize_coeff_v; +- ctx->image_resize_coeff_h = resize_coeff_h; +- ctx->image_resize_coeff_v = resize_coeff_v; +- ctx->in.num_cols = cols; +- ctx->in.num_rows = rows; +- +- return 0; +-} +- +-#define round_closest(x, y) round_down((x) + (y)/2, (y)) +- +-/* +- * Find the best aligned seam position for the given column / row index. +- * Rotation and image offsets are out of scope. +- * +- * @index: column / row index, used to calculate valid interval +- * @in_edge: input right / bottom edge +- * @out_edge: output right / bottom edge +- * @in_align: input alignment, either horizontal 8-byte line start address +- * alignment, or pixel alignment due to image format +- * @out_align: output alignment, either horizontal 8-byte line start address +- * alignment, or pixel alignment due to image format or rotator +- * block size +- * @in_burst: horizontal input burst size in case of horizontal flip +- * @out_burst: horizontal output burst size or rotator block size +- * @downsize_coeff: downsizing section coefficient +- * @resize_coeff: main processing section resizing coefficient +- * @_in_seam: aligned input seam position return value +- * @_out_seam: aligned output seam position return value +- */ +-static void find_best_seam(struct ipu_image_convert_ctx *ctx, +- unsigned int index, +- unsigned int in_edge, +- unsigned int out_edge, +- unsigned int in_align, +- unsigned int out_align, +- unsigned int in_burst, +- unsigned int out_burst, +- unsigned int downsize_coeff, +- unsigned int resize_coeff, +- u32 *_in_seam, +- u32 *_out_seam) +-{ +- struct device *dev = ctx->chan->priv->ipu->dev; +- unsigned int out_pos; +- /* Input / output seam position candidates */ +- unsigned int out_seam = 0; +- unsigned int in_seam = 0; +- unsigned int min_diff = UINT_MAX; +- unsigned int out_start; +- unsigned int out_end; +- unsigned int in_start; +- unsigned int in_end; +- +- /* Start within 1024 pixels of the right / bottom edge */ +- out_start = max_t(int, index * out_align, out_edge - 1024); +- /* End before having to add more columns to the left / rows above */ +- out_end = min_t(unsigned int, out_edge, index * 1024 + 1); +- +- /* +- * Limit input seam position to make sure that the downsized input tile +- * to the right or bottom does not exceed 1024 pixels. +- */ +- in_start = max_t(int, index * in_align, +- in_edge - (1024 << downsize_coeff)); +- in_end = min_t(unsigned int, in_edge, +- index * (1024 << downsize_coeff) + 1); +- +- /* +- * Output tiles must start at a multiple of 8 bytes horizontally and +- * possibly at an even line horizontally depending on the pixel format. +- * Only consider output aligned positions for the seam. +- */ +- out_start = round_up(out_start, out_align); +- for (out_pos = out_start; out_pos < out_end; out_pos += out_align) { +- unsigned int in_pos; +- unsigned int in_pos_aligned; +- unsigned int in_pos_rounded; +- unsigned int abs_diff; +- +- /* +- * Tiles in the right row / bottom column may not be allowed to +- * overshoot horizontally / vertically. out_burst may be the +- * actual DMA burst size, or the rotator block size. +- */ +- if ((out_burst > 1) && (out_edge - out_pos) % out_burst) +- continue; +- +- /* +- * Input sample position, corresponding to out_pos, 19.13 fixed +- * point. +- */ +- in_pos = (out_pos * resize_coeff) << downsize_coeff; +- /* +- * The closest input sample position that we could actually +- * start the input tile at, 19.13 fixed point. +- */ +- in_pos_aligned = round_closest(in_pos, 8192U * in_align); +- /* Convert 19.13 fixed point to integer */ +- in_pos_rounded = in_pos_aligned / 8192U; +- +- if (in_pos_rounded < in_start) +- continue; +- if (in_pos_rounded >= in_end) +- break; +- +- if ((in_burst > 1) && +- (in_edge - in_pos_rounded) % in_burst) +- continue; +- +- if (in_pos < in_pos_aligned) +- abs_diff = in_pos_aligned - in_pos; +- else +- abs_diff = in_pos - in_pos_aligned; +- +- if (abs_diff < min_diff) { +- in_seam = in_pos_rounded; +- out_seam = out_pos; +- min_diff = abs_diff; +- } +- } +- +- *_out_seam = out_seam; +- *_in_seam = in_seam; +- +- dev_dbg(dev, "%s: out_seam %u(%u) in [%u, %u], in_seam %u(%u) in [%u, %u] diff %u.%03u\n", +- __func__, out_seam, out_align, out_start, out_end, +- in_seam, in_align, in_start, in_end, min_diff / 8192, +- DIV_ROUND_CLOSEST(min_diff % 8192 * 1000, 8192)); +-} +- +-/* +- * Tile left edges are required to be aligned to multiples of 8 bytes +- * by the IDMAC. +- */ +-static inline u32 tile_left_align(const struct ipu_image_pixfmt *fmt) +-{ +- if (fmt->planar) +- return fmt->uv_packed ? 8 : 8 * fmt->uv_width_dec; +- else +- return fmt->bpp == 32 ? 2 : fmt->bpp == 16 ? 4 : 8; +-} +- +-/* +- * Tile top edge alignment is only limited by chroma subsampling. +- */ +-static inline u32 tile_top_align(const struct ipu_image_pixfmt *fmt) +-{ +- return fmt->uv_height_dec > 1 ? 2 : 1; +-} +- +-static inline u32 tile_width_align(enum ipu_image_convert_type type, +- const struct ipu_image_pixfmt *fmt, +- enum ipu_rotate_mode rot_mode) +-{ +- if (type == IMAGE_CONVERT_IN) { +- /* +- * The IC burst reads 8 pixels at a time. Reading beyond the +- * end of the line is usually acceptable. Those pixels are +- * ignored, unless the IC has to write the scaled line in +- * reverse. +- */ +- return (!ipu_rot_mode_is_irt(rot_mode) && +- (rot_mode & IPU_ROT_BIT_HFLIP)) ? 8 : 2; +- } +- +- /* +- * Align to 16x16 pixel blocks for planar 4:2:0 chroma subsampled +- * formats to guarantee 8-byte aligned line start addresses in the +- * chroma planes when IRT is used. Align to 8x8 pixel IRT block size +- * for all other formats. +- */ +- return (ipu_rot_mode_is_irt(rot_mode) && +- fmt->planar && !fmt->uv_packed) ? +- 8 * fmt->uv_width_dec : 8; +-} +- +-static inline u32 tile_height_align(enum ipu_image_convert_type type, +- const struct ipu_image_pixfmt *fmt, +- enum ipu_rotate_mode rot_mode) +-{ +- if (type == IMAGE_CONVERT_IN || !ipu_rot_mode_is_irt(rot_mode)) +- return 2; +- +- /* +- * Align to 16x16 pixel blocks for planar 4:2:0 chroma subsampled +- * formats to guarantee 8-byte aligned line start addresses in the +- * chroma planes when IRT is used. Align to 8x8 pixel IRT block size +- * for all other formats. +- */ +- return (fmt->planar && !fmt->uv_packed) ? 8 * fmt->uv_width_dec : 8; +-} +- +-/* +- * Fill in left position and width and for all tiles in an input column, and +- * for all corresponding output tiles. If the 90° rotator is used, the output +- * tiles are in a row, and output tile top position and height are set. +- */ +-static void fill_tile_column(struct ipu_image_convert_ctx *ctx, +- unsigned int col, +- struct ipu_image_convert_image *in, +- unsigned int in_left, unsigned int in_width, +- struct ipu_image_convert_image *out, +- unsigned int out_left, unsigned int out_width) +-{ +- unsigned int row, tile_idx; +- struct ipu_image_tile *in_tile, *out_tile; +- +- for (row = 0; row < in->num_rows; row++) { +- tile_idx = in->num_cols * row + col; +- in_tile = &in->tile[tile_idx]; +- out_tile = &out->tile[ctx->out_tile_map[tile_idx]]; +- +- in_tile->left = in_left; +- in_tile->width = in_width; +- +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) { +- out_tile->top = out_left; +- out_tile->height = out_width; +- } else { +- out_tile->left = out_left; +- out_tile->width = out_width; +- } +- } +-} +- +-/* +- * Fill in top position and height and for all tiles in an input row, and +- * for all corresponding output tiles. If the 90° rotator is used, the output +- * tiles are in a column, and output tile left position and width are set. +- */ +-static void fill_tile_row(struct ipu_image_convert_ctx *ctx, unsigned int row, +- struct ipu_image_convert_image *in, +- unsigned int in_top, unsigned int in_height, +- struct ipu_image_convert_image *out, +- unsigned int out_top, unsigned int out_height) +-{ +- unsigned int col, tile_idx; +- struct ipu_image_tile *in_tile, *out_tile; +- +- for (col = 0; col < in->num_cols; col++) { +- tile_idx = in->num_cols * row + col; +- in_tile = &in->tile[tile_idx]; +- out_tile = &out->tile[ctx->out_tile_map[tile_idx]]; +- +- in_tile->top = in_top; +- in_tile->height = in_height; +- +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) { +- out_tile->left = out_top; +- out_tile->width = out_height; +- } else { +- out_tile->top = out_top; +- out_tile->height = out_height; +- } +- } +-} +- +-/* +- * Find the best horizontal and vertical seam positions to split into tiles. +- * Minimize the fractional part of the input sampling position for the +- * top / left pixels of each tile. +- */ +-static void find_seams(struct ipu_image_convert_ctx *ctx, +- struct ipu_image_convert_image *in, +- struct ipu_image_convert_image *out) +-{ +- struct device *dev = ctx->chan->priv->ipu->dev; +- unsigned int resized_width = out->base.rect.width; +- unsigned int resized_height = out->base.rect.height; +- unsigned int col; +- unsigned int row; +- unsigned int in_left_align = tile_left_align(in->fmt); +- unsigned int in_top_align = tile_top_align(in->fmt); +- unsigned int out_left_align = tile_left_align(out->fmt); +- unsigned int out_top_align = tile_top_align(out->fmt); +- unsigned int out_width_align = tile_width_align(out->type, out->fmt, +- ctx->rot_mode); +- unsigned int out_height_align = tile_height_align(out->type, out->fmt, +- ctx->rot_mode); +- unsigned int in_right = in->base.rect.width; +- unsigned int in_bottom = in->base.rect.height; +- unsigned int out_right = out->base.rect.width; +- unsigned int out_bottom = out->base.rect.height; +- unsigned int flipped_out_left; +- unsigned int flipped_out_top; +- +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) { +- /* Switch width/height and align top left to IRT block size */ +- resized_width = out->base.rect.height; +- resized_height = out->base.rect.width; +- out_left_align = out_height_align; +- out_top_align = out_width_align; +- out_width_align = out_left_align; +- out_height_align = out_top_align; +- out_right = out->base.rect.height; +- out_bottom = out->base.rect.width; +- } +- +- for (col = in->num_cols - 1; col > 0; col--) { +- bool allow_in_overshoot = ipu_rot_mode_is_irt(ctx->rot_mode) || +- !(ctx->rot_mode & IPU_ROT_BIT_HFLIP); +- bool allow_out_overshoot = (col < in->num_cols - 1) && +- !(ctx->rot_mode & IPU_ROT_BIT_HFLIP); +- unsigned int in_left; +- unsigned int out_left; +- +- /* +- * Align input width to burst length if the scaling step flips +- * horizontally. +- */ +- +- find_best_seam(ctx, col, +- in_right, out_right, +- in_left_align, out_left_align, +- allow_in_overshoot ? 1 : 8 /* burst length */, +- allow_out_overshoot ? 1 : out_width_align, +- ctx->downsize_coeff_h, ctx->image_resize_coeff_h, +- &in_left, &out_left); +- +- if (ctx->rot_mode & IPU_ROT_BIT_HFLIP) +- flipped_out_left = resized_width - out_right; +- else +- flipped_out_left = out_left; +- +- fill_tile_column(ctx, col, in, in_left, in_right - in_left, +- out, flipped_out_left, out_right - out_left); +- +- dev_dbg(dev, "%s: col %u: %u, %u -> %u, %u\n", __func__, col, +- in_left, in_right - in_left, +- flipped_out_left, out_right - out_left); +- +- in_right = in_left; +- out_right = out_left; +- } +- +- flipped_out_left = (ctx->rot_mode & IPU_ROT_BIT_HFLIP) ? +- resized_width - out_right : 0; +- +- fill_tile_column(ctx, 0, in, 0, in_right, +- out, flipped_out_left, out_right); +- +- dev_dbg(dev, "%s: col 0: 0, %u -> %u, %u\n", __func__, +- in_right, flipped_out_left, out_right); +- +- for (row = in->num_rows - 1; row > 0; row--) { +- bool allow_overshoot = row < in->num_rows - 1; +- unsigned int in_top; +- unsigned int out_top; +- +- find_best_seam(ctx, row, +- in_bottom, out_bottom, +- in_top_align, out_top_align, +- 1, allow_overshoot ? 1 : out_height_align, +- ctx->downsize_coeff_v, ctx->image_resize_coeff_v, +- &in_top, &out_top); +- +- if ((ctx->rot_mode & IPU_ROT_BIT_VFLIP) ^ +- ipu_rot_mode_is_irt(ctx->rot_mode)) +- flipped_out_top = resized_height - out_bottom; +- else +- flipped_out_top = out_top; +- +- fill_tile_row(ctx, row, in, in_top, in_bottom - in_top, +- out, flipped_out_top, out_bottom - out_top); +- +- dev_dbg(dev, "%s: row %u: %u, %u -> %u, %u\n", __func__, row, +- in_top, in_bottom - in_top, +- flipped_out_top, out_bottom - out_top); +- +- in_bottom = in_top; +- out_bottom = out_top; +- } +- +- if ((ctx->rot_mode & IPU_ROT_BIT_VFLIP) ^ +- ipu_rot_mode_is_irt(ctx->rot_mode)) +- flipped_out_top = resized_height - out_bottom; +- else +- flipped_out_top = 0; +- +- fill_tile_row(ctx, 0, in, 0, in_bottom, +- out, flipped_out_top, out_bottom); +- +- dev_dbg(dev, "%s: row 0: 0, %u -> %u, %u\n", __func__, +- in_bottom, flipped_out_top, out_bottom); +-} +- +-static int calc_tile_dimensions(struct ipu_image_convert_ctx *ctx, +- struct ipu_image_convert_image *image) +-{ +- struct ipu_image_convert_chan *chan = ctx->chan; +- struct ipu_image_convert_priv *priv = chan->priv; +- unsigned int max_width = 1024; +- unsigned int max_height = 1024; +- unsigned int i; +- +- if (image->type == IMAGE_CONVERT_IN) { +- /* Up to 4096x4096 input tile size */ +- max_width <<= ctx->downsize_coeff_h; +- max_height <<= ctx->downsize_coeff_v; +- } +- +- for (i = 0; i < ctx->num_tiles; i++) { +- struct ipu_image_tile *tile; +- const unsigned int row = i / image->num_cols; +- const unsigned int col = i % image->num_cols; +- +- if (image->type == IMAGE_CONVERT_OUT) +- tile = &image->tile[ctx->out_tile_map[i]]; +- else +- tile = &image->tile[i]; +- +- tile->size = ((tile->height * image->fmt->bpp) >> 3) * +- tile->width; +- +- if (image->fmt->planar) { +- tile->stride = tile->width; +- tile->rot_stride = tile->height; +- } else { +- tile->stride = +- (image->fmt->bpp * tile->width) >> 3; +- tile->rot_stride = +- (image->fmt->bpp * tile->height) >> 3; +- } +- +- dev_dbg(priv->ipu->dev, +- "task %u: ctx %p: %s@[%u,%u]: %ux%u@%u,%u\n", +- chan->ic_task, ctx, +- image->type == IMAGE_CONVERT_IN ? "Input" : "Output", +- row, col, +- tile->width, tile->height, tile->left, tile->top); +- +- if (!tile->width || tile->width > max_width || +- !tile->height || tile->height > max_height) { +- dev_err(priv->ipu->dev, "invalid %s tile size: %ux%u\n", +- image->type == IMAGE_CONVERT_IN ? "input" : +- "output", tile->width, tile->height); +- return -EINVAL; +- } +- } +- +- return 0; +-} +- +-/* +- * Use the rotation transformation to find the tile coordinates +- * (row, col) of a tile in the destination frame that corresponds +- * to the given tile coordinates of a source frame. The destination +- * coordinate is then converted to a tile index. +- */ +-static int transform_tile_index(struct ipu_image_convert_ctx *ctx, +- int src_row, int src_col) +-{ +- struct ipu_image_convert_chan *chan = ctx->chan; +- struct ipu_image_convert_priv *priv = chan->priv; +- struct ipu_image_convert_image *s_image = &ctx->in; +- struct ipu_image_convert_image *d_image = &ctx->out; +- int dst_row, dst_col; +- +- /* with no rotation it's a 1:1 mapping */ +- if (ctx->rot_mode == IPU_ROTATE_NONE) +- return src_row * s_image->num_cols + src_col; +- +- /* +- * before doing the transform, first we have to translate +- * source row,col for an origin in the center of s_image +- */ +- src_row = src_row * 2 - (s_image->num_rows - 1); +- src_col = src_col * 2 - (s_image->num_cols - 1); +- +- /* do the rotation transform */ +- if (ctx->rot_mode & IPU_ROT_BIT_90) { +- dst_col = -src_row; +- dst_row = src_col; +- } else { +- dst_col = src_col; +- dst_row = src_row; +- } +- +- /* apply flip */ +- if (ctx->rot_mode & IPU_ROT_BIT_HFLIP) +- dst_col = -dst_col; +- if (ctx->rot_mode & IPU_ROT_BIT_VFLIP) +- dst_row = -dst_row; +- +- dev_dbg(priv->ipu->dev, "task %u: ctx %p: [%d,%d] --> [%d,%d]\n", +- chan->ic_task, ctx, src_col, src_row, dst_col, dst_row); +- +- /* +- * finally translate dest row,col using an origin in upper +- * left of d_image +- */ +- dst_row += d_image->num_rows - 1; +- dst_col += d_image->num_cols - 1; +- dst_row /= 2; +- dst_col /= 2; +- +- return dst_row * d_image->num_cols + dst_col; +-} +- +-/* +- * Fill the out_tile_map[] with transformed destination tile indeces. +- */ +-static void calc_out_tile_map(struct ipu_image_convert_ctx *ctx) +-{ +- struct ipu_image_convert_image *s_image = &ctx->in; +- unsigned int row, col, tile = 0; +- +- for (row = 0; row < s_image->num_rows; row++) { +- for (col = 0; col < s_image->num_cols; col++) { +- ctx->out_tile_map[tile] = +- transform_tile_index(ctx, row, col); +- tile++; +- } +- } +-} +- +-static int calc_tile_offsets_planar(struct ipu_image_convert_ctx *ctx, +- struct ipu_image_convert_image *image) +-{ +- struct ipu_image_convert_chan *chan = ctx->chan; +- struct ipu_image_convert_priv *priv = chan->priv; +- const struct ipu_image_pixfmt *fmt = image->fmt; +- unsigned int row, col, tile = 0; +- u32 H, top, y_stride, uv_stride; +- u32 uv_row_off, uv_col_off, uv_off, u_off, v_off, tmp; +- u32 y_row_off, y_col_off, y_off; +- u32 y_size, uv_size; +- +- /* setup some convenience vars */ +- H = image->base.pix.height; +- +- y_stride = image->stride; +- uv_stride = y_stride / fmt->uv_width_dec; +- if (fmt->uv_packed) +- uv_stride *= 2; +- +- y_size = H * y_stride; +- uv_size = y_size / (fmt->uv_width_dec * fmt->uv_height_dec); +- +- for (row = 0; row < image->num_rows; row++) { +- top = image->tile[tile].top; +- y_row_off = top * y_stride; +- uv_row_off = (top * uv_stride) / fmt->uv_height_dec; +- +- for (col = 0; col < image->num_cols; col++) { +- y_col_off = image->tile[tile].left; +- uv_col_off = y_col_off / fmt->uv_width_dec; +- if (fmt->uv_packed) +- uv_col_off *= 2; +- +- y_off = y_row_off + y_col_off; +- uv_off = uv_row_off + uv_col_off; +- +- u_off = y_size - y_off + uv_off; +- v_off = (fmt->uv_packed) ? 0 : u_off + uv_size; +- if (fmt->uv_swapped) { +- tmp = u_off; +- u_off = v_off; +- v_off = tmp; +- } +- +- image->tile[tile].offset = y_off; +- image->tile[tile].u_off = u_off; +- image->tile[tile++].v_off = v_off; +- +- if ((y_off & 0x7) || (u_off & 0x7) || (v_off & 0x7)) { +- dev_err(priv->ipu->dev, +- "task %u: ctx %p: %s@[%d,%d]: " +- "y_off %08x, u_off %08x, v_off %08x\n", +- chan->ic_task, ctx, +- image->type == IMAGE_CONVERT_IN ? +- "Input" : "Output", row, col, +- y_off, u_off, v_off); +- return -EINVAL; +- } +- } +- } +- +- return 0; +-} +- +-static int calc_tile_offsets_packed(struct ipu_image_convert_ctx *ctx, +- struct ipu_image_convert_image *image) +-{ +- struct ipu_image_convert_chan *chan = ctx->chan; +- struct ipu_image_convert_priv *priv = chan->priv; +- const struct ipu_image_pixfmt *fmt = image->fmt; +- unsigned int row, col, tile = 0; +- u32 bpp, stride, offset; +- u32 row_off, col_off; +- +- /* setup some convenience vars */ +- stride = image->stride; +- bpp = fmt->bpp; +- +- for (row = 0; row < image->num_rows; row++) { +- row_off = image->tile[tile].top * stride; +- +- for (col = 0; col < image->num_cols; col++) { +- col_off = (image->tile[tile].left * bpp) >> 3; +- +- offset = row_off + col_off; +- +- image->tile[tile].offset = offset; +- image->tile[tile].u_off = 0; +- image->tile[tile++].v_off = 0; +- +- if (offset & 0x7) { +- dev_err(priv->ipu->dev, +- "task %u: ctx %p: %s@[%d,%d]: " +- "phys %08x\n", +- chan->ic_task, ctx, +- image->type == IMAGE_CONVERT_IN ? +- "Input" : "Output", row, col, +- row_off + col_off); +- return -EINVAL; +- } +- } +- } +- +- return 0; +-} +- +-static int calc_tile_offsets(struct ipu_image_convert_ctx *ctx, +- struct ipu_image_convert_image *image) +-{ +- if (image->fmt->planar) +- return calc_tile_offsets_planar(ctx, image); +- +- return calc_tile_offsets_packed(ctx, image); +-} +- +-/* +- * Calculate the resizing ratio for the IC main processing section given input +- * size, fixed downsizing coefficient, and output size. +- * Either round to closest for the next tile's first pixel to minimize seams +- * and distortion (for all but right column / bottom row), or round down to +- * avoid sampling beyond the edges of the input image for this tile's last +- * pixel. +- * Returns the resizing coefficient, resizing ratio is 8192.0 / resize_coeff. +- */ +-static u32 calc_resize_coeff(u32 input_size, u32 downsize_coeff, +- u32 output_size, bool allow_overshoot) +-{ +- u32 downsized = input_size >> downsize_coeff; +- +- if (allow_overshoot) +- return DIV_ROUND_CLOSEST(8192 * downsized, output_size); +- else +- return 8192 * (downsized - 1) / (output_size - 1); +-} +- +-/* +- * Slightly modify resize coefficients per tile to hide the bilinear +- * interpolator reset at tile borders, shifting the right / bottom edge +- * by up to a half input pixel. This removes noticeable seams between +- * tiles at higher upscaling factors. +- */ +-static void calc_tile_resize_coefficients(struct ipu_image_convert_ctx *ctx) +-{ +- struct ipu_image_convert_chan *chan = ctx->chan; +- struct ipu_image_convert_priv *priv = chan->priv; +- struct ipu_image_tile *in_tile, *out_tile; +- unsigned int col, row, tile_idx; +- unsigned int last_output; +- +- for (col = 0; col < ctx->in.num_cols; col++) { +- bool closest = (col < ctx->in.num_cols - 1) && +- !(ctx->rot_mode & IPU_ROT_BIT_HFLIP); +- u32 resized_width; +- u32 resize_coeff_h; +- u32 in_width; +- +- tile_idx = col; +- in_tile = &ctx->in.tile[tile_idx]; +- out_tile = &ctx->out.tile[ctx->out_tile_map[tile_idx]]; +- +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) +- resized_width = out_tile->height; +- else +- resized_width = out_tile->width; +- +- resize_coeff_h = calc_resize_coeff(in_tile->width, +- ctx->downsize_coeff_h, +- resized_width, closest); +- +- dev_dbg(priv->ipu->dev, "%s: column %u hscale: *8192/%u\n", +- __func__, col, resize_coeff_h); +- +- /* +- * With the horizontal scaling factor known, round up resized +- * width (output width or height) to burst size. +- */ +- resized_width = round_up(resized_width, 8); +- +- /* +- * Calculate input width from the last accessed input pixel +- * given resized width and scaling coefficients. Round up to +- * burst size. +- */ +- last_output = resized_width - 1; +- if (closest && ((last_output * resize_coeff_h) % 8192)) +- last_output++; +- in_width = round_up( +- (DIV_ROUND_UP(last_output * resize_coeff_h, 8192) + 1) +- << ctx->downsize_coeff_h, 8); +- +- for (row = 0; row < ctx->in.num_rows; row++) { +- tile_idx = row * ctx->in.num_cols + col; +- in_tile = &ctx->in.tile[tile_idx]; +- out_tile = &ctx->out.tile[ctx->out_tile_map[tile_idx]]; +- +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) +- out_tile->height = resized_width; +- else +- out_tile->width = resized_width; +- +- in_tile->width = in_width; +- } +- +- ctx->resize_coeffs_h[col] = resize_coeff_h; +- } +- +- for (row = 0; row < ctx->in.num_rows; row++) { +- bool closest = (row < ctx->in.num_rows - 1) && +- !(ctx->rot_mode & IPU_ROT_BIT_VFLIP); +- u32 resized_height; +- u32 resize_coeff_v; +- u32 in_height; +- +- tile_idx = row * ctx->in.num_cols; +- in_tile = &ctx->in.tile[tile_idx]; +- out_tile = &ctx->out.tile[ctx->out_tile_map[tile_idx]]; +- +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) +- resized_height = out_tile->width; +- else +- resized_height = out_tile->height; +- +- resize_coeff_v = calc_resize_coeff(in_tile->height, +- ctx->downsize_coeff_v, +- resized_height, closest); +- +- dev_dbg(priv->ipu->dev, "%s: row %u vscale: *8192/%u\n", +- __func__, row, resize_coeff_v); +- +- /* +- * With the vertical scaling factor known, round up resized +- * height (output width or height) to IDMAC limitations. +- */ +- resized_height = round_up(resized_height, 2); +- +- /* +- * Calculate input width from the last accessed input pixel +- * given resized height and scaling coefficients. Align to +- * IDMAC restrictions. +- */ +- last_output = resized_height - 1; +- if (closest && ((last_output * resize_coeff_v) % 8192)) +- last_output++; +- in_height = round_up( +- (DIV_ROUND_UP(last_output * resize_coeff_v, 8192) + 1) +- << ctx->downsize_coeff_v, 2); +- +- for (col = 0; col < ctx->in.num_cols; col++) { +- tile_idx = row * ctx->in.num_cols + col; +- in_tile = &ctx->in.tile[tile_idx]; +- out_tile = &ctx->out.tile[ctx->out_tile_map[tile_idx]]; +- +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) +- out_tile->width = resized_height; +- else +- out_tile->height = resized_height; +- +- in_tile->height = in_height; +- } +- +- ctx->resize_coeffs_v[row] = resize_coeff_v; +- } +-} +- +-/* +- * return the number of runs in given queue (pending_q or done_q) +- * for this context. hold irqlock when calling. +- */ +-static int get_run_count(struct ipu_image_convert_ctx *ctx, +- struct list_head *q) +-{ +- struct ipu_image_convert_run *run; +- int count = 0; +- +- lockdep_assert_held(&ctx->chan->irqlock); +- +- list_for_each_entry(run, q, list) { +- if (run->ctx == ctx) +- count++; +- } +- +- return count; +-} +- +-static void convert_stop(struct ipu_image_convert_run *run) +-{ +- struct ipu_image_convert_ctx *ctx = run->ctx; +- struct ipu_image_convert_chan *chan = ctx->chan; +- struct ipu_image_convert_priv *priv = chan->priv; +- +- dev_dbg(priv->ipu->dev, "%s: task %u: stopping ctx %p run %p\n", +- __func__, chan->ic_task, ctx, run); +- +- /* disable IC tasks and the channels */ +- ipu_ic_task_disable(chan->ic); +- ipu_idmac_disable_channel(chan->in_chan); +- ipu_idmac_disable_channel(chan->out_chan); +- +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) { +- ipu_idmac_disable_channel(chan->rotation_in_chan); +- ipu_idmac_disable_channel(chan->rotation_out_chan); +- ipu_idmac_unlink(chan->out_chan, chan->rotation_in_chan); +- } +- +- ipu_ic_disable(chan->ic); +-} +- +-static void init_idmac_channel(struct ipu_image_convert_ctx *ctx, +- struct ipuv3_channel *channel, +- struct ipu_image_convert_image *image, +- enum ipu_rotate_mode rot_mode, +- bool rot_swap_width_height, +- unsigned int tile) +-{ +- struct ipu_image_convert_chan *chan = ctx->chan; +- unsigned int burst_size; +- u32 width, height, stride; +- dma_addr_t addr0, addr1 = 0; +- struct ipu_image tile_image; +- unsigned int tile_idx[2]; +- +- if (image->type == IMAGE_CONVERT_OUT) { +- tile_idx[0] = ctx->out_tile_map[tile]; +- tile_idx[1] = ctx->out_tile_map[1]; +- } else { +- tile_idx[0] = tile; +- tile_idx[1] = 1; +- } +- +- if (rot_swap_width_height) { +- width = image->tile[tile_idx[0]].height; +- height = image->tile[tile_idx[0]].width; +- stride = image->tile[tile_idx[0]].rot_stride; +- addr0 = ctx->rot_intermediate[0].phys; +- if (ctx->double_buffering) +- addr1 = ctx->rot_intermediate[1].phys; +- } else { +- width = image->tile[tile_idx[0]].width; +- height = image->tile[tile_idx[0]].height; +- stride = image->stride; +- addr0 = image->base.phys0 + +- image->tile[tile_idx[0]].offset; +- if (ctx->double_buffering) +- addr1 = image->base.phys0 + +- image->tile[tile_idx[1]].offset; +- } +- +- ipu_cpmem_zero(channel); +- +- memset(&tile_image, 0, sizeof(tile_image)); +- tile_image.pix.width = tile_image.rect.width = width; +- tile_image.pix.height = tile_image.rect.height = height; +- tile_image.pix.bytesperline = stride; +- tile_image.pix.pixelformat = image->fmt->fourcc; +- tile_image.phys0 = addr0; +- tile_image.phys1 = addr1; +- if (image->fmt->planar && !rot_swap_width_height) { +- tile_image.u_offset = image->tile[tile_idx[0]].u_off; +- tile_image.v_offset = image->tile[tile_idx[0]].v_off; +- } +- +- ipu_cpmem_set_image(channel, &tile_image); +- +- if (rot_mode) +- ipu_cpmem_set_rotation(channel, rot_mode); +- +- /* +- * Skip writing U and V components to odd rows in the output +- * channels for planar 4:2:0. +- */ +- if ((channel == chan->out_chan || +- channel == chan->rotation_out_chan) && +- image->fmt->planar && image->fmt->uv_height_dec == 2) +- ipu_cpmem_skip_odd_chroma_rows(channel); +- +- if (channel == chan->rotation_in_chan || +- channel == chan->rotation_out_chan) { +- burst_size = 8; +- ipu_cpmem_set_block_mode(channel); +- } else +- burst_size = (width % 16) ? 8 : 16; +- +- ipu_cpmem_set_burstsize(channel, burst_size); +- +- ipu_ic_task_idma_init(chan->ic, channel, width, height, +- burst_size, rot_mode); +- +- /* +- * Setting a non-zero AXI ID collides with the PRG AXI snooping, so +- * only do this when there is no PRG present. +- */ +- if (!channel->ipu->prg_priv) +- ipu_cpmem_set_axi_id(channel, 1); +- +- ipu_idmac_set_double_buffer(channel, ctx->double_buffering); +-} +- +-static int convert_start(struct ipu_image_convert_run *run, unsigned int tile) +-{ +- struct ipu_image_convert_ctx *ctx = run->ctx; +- struct ipu_image_convert_chan *chan = ctx->chan; +- struct ipu_image_convert_priv *priv = chan->priv; +- struct ipu_image_convert_image *s_image = &ctx->in; +- struct ipu_image_convert_image *d_image = &ctx->out; +- unsigned int dst_tile = ctx->out_tile_map[tile]; +- unsigned int dest_width, dest_height; +- unsigned int col, row; +- u32 rsc; +- int ret; +- +- dev_dbg(priv->ipu->dev, "%s: task %u: starting ctx %p run %p tile %u -> %u\n", +- __func__, chan->ic_task, ctx, run, tile, dst_tile); +- +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) { +- /* swap width/height for resizer */ +- dest_width = d_image->tile[dst_tile].height; +- dest_height = d_image->tile[dst_tile].width; +- } else { +- dest_width = d_image->tile[dst_tile].width; +- dest_height = d_image->tile[dst_tile].height; +- } +- +- row = tile / s_image->num_cols; +- col = tile % s_image->num_cols; +- +- rsc = (ctx->downsize_coeff_v << 30) | +- (ctx->resize_coeffs_v[row] << 16) | +- (ctx->downsize_coeff_h << 14) | +- (ctx->resize_coeffs_h[col]); +- +- dev_dbg(priv->ipu->dev, "%s: %ux%u -> %ux%u (rsc = 0x%x)\n", +- __func__, s_image->tile[tile].width, +- s_image->tile[tile].height, dest_width, dest_height, rsc); +- +- /* setup the IC resizer and CSC */ +- ret = ipu_ic_task_init_rsc(chan->ic, &ctx->csc, +- s_image->tile[tile].width, +- s_image->tile[tile].height, +- dest_width, +- dest_height, +- rsc); +- if (ret) { +- dev_err(priv->ipu->dev, "ipu_ic_task_init failed, %d\n", ret); +- return ret; +- } +- +- /* init the source MEM-->IC PP IDMAC channel */ +- init_idmac_channel(ctx, chan->in_chan, s_image, +- IPU_ROTATE_NONE, false, tile); +- +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) { +- /* init the IC PP-->MEM IDMAC channel */ +- init_idmac_channel(ctx, chan->out_chan, d_image, +- IPU_ROTATE_NONE, true, tile); +- +- /* init the MEM-->IC PP ROT IDMAC channel */ +- init_idmac_channel(ctx, chan->rotation_in_chan, d_image, +- ctx->rot_mode, true, tile); +- +- /* init the destination IC PP ROT-->MEM IDMAC channel */ +- init_idmac_channel(ctx, chan->rotation_out_chan, d_image, +- IPU_ROTATE_NONE, false, tile); +- +- /* now link IC PP-->MEM to MEM-->IC PP ROT */ +- ipu_idmac_link(chan->out_chan, chan->rotation_in_chan); +- } else { +- /* init the destination IC PP-->MEM IDMAC channel */ +- init_idmac_channel(ctx, chan->out_chan, d_image, +- ctx->rot_mode, false, tile); +- } +- +- /* enable the IC */ +- ipu_ic_enable(chan->ic); +- +- /* set buffers ready */ +- ipu_idmac_select_buffer(chan->in_chan, 0); +- ipu_idmac_select_buffer(chan->out_chan, 0); +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) +- ipu_idmac_select_buffer(chan->rotation_out_chan, 0); +- if (ctx->double_buffering) { +- ipu_idmac_select_buffer(chan->in_chan, 1); +- ipu_idmac_select_buffer(chan->out_chan, 1); +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) +- ipu_idmac_select_buffer(chan->rotation_out_chan, 1); +- } +- +- /* enable the channels! */ +- ipu_idmac_enable_channel(chan->in_chan); +- ipu_idmac_enable_channel(chan->out_chan); +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) { +- ipu_idmac_enable_channel(chan->rotation_in_chan); +- ipu_idmac_enable_channel(chan->rotation_out_chan); +- } +- +- ipu_ic_task_enable(chan->ic); +- +- ipu_cpmem_dump(chan->in_chan); +- ipu_cpmem_dump(chan->out_chan); +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) { +- ipu_cpmem_dump(chan->rotation_in_chan); +- ipu_cpmem_dump(chan->rotation_out_chan); +- } +- +- ipu_dump(priv->ipu); +- +- return 0; +-} +- +-/* hold irqlock when calling */ +-static int do_run(struct ipu_image_convert_run *run) +-{ +- struct ipu_image_convert_ctx *ctx = run->ctx; +- struct ipu_image_convert_chan *chan = ctx->chan; +- +- lockdep_assert_held(&chan->irqlock); +- +- ctx->in.base.phys0 = run->in_phys; +- ctx->out.base.phys0 = run->out_phys; +- +- ctx->cur_buf_num = 0; +- ctx->next_tile = 1; +- +- /* remove run from pending_q and set as current */ +- list_del(&run->list); +- chan->current_run = run; +- +- return convert_start(run, 0); +-} +- +-/* hold irqlock when calling */ +-static void run_next(struct ipu_image_convert_chan *chan) +-{ +- struct ipu_image_convert_priv *priv = chan->priv; +- struct ipu_image_convert_run *run, *tmp; +- int ret; +- +- lockdep_assert_held(&chan->irqlock); +- +- list_for_each_entry_safe(run, tmp, &chan->pending_q, list) { +- /* skip contexts that are aborting */ +- if (run->ctx->aborting) { +- dev_dbg(priv->ipu->dev, +- "%s: task %u: skipping aborting ctx %p run %p\n", +- __func__, chan->ic_task, run->ctx, run); +- continue; +- } +- +- ret = do_run(run); +- if (!ret) +- break; +- +- /* +- * something went wrong with start, add the run +- * to done q and continue to the next run in the +- * pending q. +- */ +- run->status = ret; +- list_add_tail(&run->list, &chan->done_q); +- chan->current_run = NULL; +- } +-} +- +-static void empty_done_q(struct ipu_image_convert_chan *chan) +-{ +- struct ipu_image_convert_priv *priv = chan->priv; +- struct ipu_image_convert_run *run; +- unsigned long flags; +- +- spin_lock_irqsave(&chan->irqlock, flags); +- +- while (!list_empty(&chan->done_q)) { +- run = list_entry(chan->done_q.next, +- struct ipu_image_convert_run, +- list); +- +- list_del(&run->list); +- +- dev_dbg(priv->ipu->dev, +- "%s: task %u: completing ctx %p run %p with %d\n", +- __func__, chan->ic_task, run->ctx, run, run->status); +- +- /* call the completion callback and free the run */ +- spin_unlock_irqrestore(&chan->irqlock, flags); +- run->ctx->complete(run, run->ctx->complete_context); +- spin_lock_irqsave(&chan->irqlock, flags); +- } +- +- spin_unlock_irqrestore(&chan->irqlock, flags); +-} +- +-/* +- * the bottom half thread clears out the done_q, calling the +- * completion handler for each. +- */ +-static irqreturn_t do_bh(int irq, void *dev_id) +-{ +- struct ipu_image_convert_chan *chan = dev_id; +- struct ipu_image_convert_priv *priv = chan->priv; +- struct ipu_image_convert_ctx *ctx; +- unsigned long flags; +- +- dev_dbg(priv->ipu->dev, "%s: task %u: enter\n", __func__, +- chan->ic_task); +- +- empty_done_q(chan); +- +- spin_lock_irqsave(&chan->irqlock, flags); +- +- /* +- * the done_q is cleared out, signal any contexts +- * that are aborting that abort can complete. +- */ +- list_for_each_entry(ctx, &chan->ctx_list, list) { +- if (ctx->aborting) { +- dev_dbg(priv->ipu->dev, +- "%s: task %u: signaling abort for ctx %p\n", +- __func__, chan->ic_task, ctx); +- complete_all(&ctx->aborted); +- } +- } +- +- spin_unlock_irqrestore(&chan->irqlock, flags); +- +- dev_dbg(priv->ipu->dev, "%s: task %u: exit\n", __func__, +- chan->ic_task); +- +- return IRQ_HANDLED; +-} +- +-static bool ic_settings_changed(struct ipu_image_convert_ctx *ctx) +-{ +- unsigned int cur_tile = ctx->next_tile - 1; +- unsigned int next_tile = ctx->next_tile; +- +- if (ctx->resize_coeffs_h[cur_tile % ctx->in.num_cols] != +- ctx->resize_coeffs_h[next_tile % ctx->in.num_cols] || +- ctx->resize_coeffs_v[cur_tile / ctx->in.num_cols] != +- ctx->resize_coeffs_v[next_tile / ctx->in.num_cols] || +- ctx->in.tile[cur_tile].width != ctx->in.tile[next_tile].width || +- ctx->in.tile[cur_tile].height != ctx->in.tile[next_tile].height || +- ctx->out.tile[cur_tile].width != ctx->out.tile[next_tile].width || +- ctx->out.tile[cur_tile].height != ctx->out.tile[next_tile].height) +- return true; +- +- return false; +-} +- +-/* hold irqlock when calling */ +-static irqreturn_t do_irq(struct ipu_image_convert_run *run) +-{ +- struct ipu_image_convert_ctx *ctx = run->ctx; +- struct ipu_image_convert_chan *chan = ctx->chan; +- struct ipu_image_tile *src_tile, *dst_tile; +- struct ipu_image_convert_image *s_image = &ctx->in; +- struct ipu_image_convert_image *d_image = &ctx->out; +- struct ipuv3_channel *outch; +- unsigned int dst_idx; +- +- lockdep_assert_held(&chan->irqlock); +- +- outch = ipu_rot_mode_is_irt(ctx->rot_mode) ? +- chan->rotation_out_chan : chan->out_chan; +- +- /* +- * It is difficult to stop the channel DMA before the channels +- * enter the paused state. Without double-buffering the channels +- * are always in a paused state when the EOF irq occurs, so it +- * is safe to stop the channels now. For double-buffering we +- * just ignore the abort until the operation completes, when it +- * is safe to shut down. +- */ +- if (ctx->aborting && !ctx->double_buffering) { +- convert_stop(run); +- run->status = -EIO; +- goto done; +- } +- +- if (ctx->next_tile == ctx->num_tiles) { +- /* +- * the conversion is complete +- */ +- convert_stop(run); +- run->status = 0; +- goto done; +- } +- +- /* +- * not done, place the next tile buffers. +- */ +- if (!ctx->double_buffering) { +- if (ic_settings_changed(ctx)) { +- convert_stop(run); +- convert_start(run, ctx->next_tile); +- } else { +- src_tile = &s_image->tile[ctx->next_tile]; +- dst_idx = ctx->out_tile_map[ctx->next_tile]; +- dst_tile = &d_image->tile[dst_idx]; +- +- ipu_cpmem_set_buffer(chan->in_chan, 0, +- s_image->base.phys0 + +- src_tile->offset); +- ipu_cpmem_set_buffer(outch, 0, +- d_image->base.phys0 + +- dst_tile->offset); +- if (s_image->fmt->planar) +- ipu_cpmem_set_uv_offset(chan->in_chan, +- src_tile->u_off, +- src_tile->v_off); +- if (d_image->fmt->planar) +- ipu_cpmem_set_uv_offset(outch, +- dst_tile->u_off, +- dst_tile->v_off); +- +- ipu_idmac_select_buffer(chan->in_chan, 0); +- ipu_idmac_select_buffer(outch, 0); +- } +- } else if (ctx->next_tile < ctx->num_tiles - 1) { +- +- src_tile = &s_image->tile[ctx->next_tile + 1]; +- dst_idx = ctx->out_tile_map[ctx->next_tile + 1]; +- dst_tile = &d_image->tile[dst_idx]; +- +- ipu_cpmem_set_buffer(chan->in_chan, ctx->cur_buf_num, +- s_image->base.phys0 + src_tile->offset); +- ipu_cpmem_set_buffer(outch, ctx->cur_buf_num, +- d_image->base.phys0 + dst_tile->offset); +- +- ipu_idmac_select_buffer(chan->in_chan, ctx->cur_buf_num); +- ipu_idmac_select_buffer(outch, ctx->cur_buf_num); +- +- ctx->cur_buf_num ^= 1; +- } +- +- ctx->next_tile++; +- return IRQ_HANDLED; +-done: +- list_add_tail(&run->list, &chan->done_q); +- chan->current_run = NULL; +- run_next(chan); +- return IRQ_WAKE_THREAD; +-} +- +-static irqreturn_t norotate_irq(int irq, void *data) +-{ +- struct ipu_image_convert_chan *chan = data; +- struct ipu_image_convert_ctx *ctx; +- struct ipu_image_convert_run *run; +- unsigned long flags; +- irqreturn_t ret; +- +- spin_lock_irqsave(&chan->irqlock, flags); +- +- /* get current run and its context */ +- run = chan->current_run; +- if (!run) { +- ret = IRQ_NONE; +- goto out; +- } +- +- ctx = run->ctx; +- +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) { +- /* this is a rotation operation, just ignore */ +- spin_unlock_irqrestore(&chan->irqlock, flags); +- return IRQ_HANDLED; +- } +- +- ret = do_irq(run); +-out: +- spin_unlock_irqrestore(&chan->irqlock, flags); +- return ret; +-} +- +-static irqreturn_t rotate_irq(int irq, void *data) +-{ +- struct ipu_image_convert_chan *chan = data; +- struct ipu_image_convert_priv *priv = chan->priv; +- struct ipu_image_convert_ctx *ctx; +- struct ipu_image_convert_run *run; +- unsigned long flags; +- irqreturn_t ret; +- +- spin_lock_irqsave(&chan->irqlock, flags); +- +- /* get current run and its context */ +- run = chan->current_run; +- if (!run) { +- ret = IRQ_NONE; +- goto out; +- } +- +- ctx = run->ctx; +- +- if (!ipu_rot_mode_is_irt(ctx->rot_mode)) { +- /* this was NOT a rotation operation, shouldn't happen */ +- dev_err(priv->ipu->dev, "Unexpected rotation interrupt\n"); +- spin_unlock_irqrestore(&chan->irqlock, flags); +- return IRQ_HANDLED; +- } +- +- ret = do_irq(run); +-out: +- spin_unlock_irqrestore(&chan->irqlock, flags); +- return ret; +-} +- +-/* +- * try to force the completion of runs for this ctx. Called when +- * abort wait times out in ipu_image_convert_abort(). +- */ +-static void force_abort(struct ipu_image_convert_ctx *ctx) +-{ +- struct ipu_image_convert_chan *chan = ctx->chan; +- struct ipu_image_convert_run *run; +- unsigned long flags; +- +- spin_lock_irqsave(&chan->irqlock, flags); +- +- run = chan->current_run; +- if (run && run->ctx == ctx) { +- convert_stop(run); +- run->status = -EIO; +- list_add_tail(&run->list, &chan->done_q); +- chan->current_run = NULL; +- run_next(chan); +- } +- +- spin_unlock_irqrestore(&chan->irqlock, flags); +- +- empty_done_q(chan); +-} +- +-static void release_ipu_resources(struct ipu_image_convert_chan *chan) +-{ +- if (chan->out_eof_irq >= 0) +- free_irq(chan->out_eof_irq, chan); +- if (chan->rot_out_eof_irq >= 0) +- free_irq(chan->rot_out_eof_irq, chan); +- +- if (!IS_ERR_OR_NULL(chan->in_chan)) +- ipu_idmac_put(chan->in_chan); +- if (!IS_ERR_OR_NULL(chan->out_chan)) +- ipu_idmac_put(chan->out_chan); +- if (!IS_ERR_OR_NULL(chan->rotation_in_chan)) +- ipu_idmac_put(chan->rotation_in_chan); +- if (!IS_ERR_OR_NULL(chan->rotation_out_chan)) +- ipu_idmac_put(chan->rotation_out_chan); +- if (!IS_ERR_OR_NULL(chan->ic)) +- ipu_ic_put(chan->ic); +- +- chan->in_chan = chan->out_chan = chan->rotation_in_chan = +- chan->rotation_out_chan = NULL; +- chan->out_eof_irq = chan->rot_out_eof_irq = -1; +-} +- +-static int get_ipu_resources(struct ipu_image_convert_chan *chan) +-{ +- const struct ipu_image_convert_dma_chan *dma = chan->dma_ch; +- struct ipu_image_convert_priv *priv = chan->priv; +- int ret; +- +- /* get IC */ +- chan->ic = ipu_ic_get(priv->ipu, chan->ic_task); +- if (IS_ERR(chan->ic)) { +- dev_err(priv->ipu->dev, "could not acquire IC\n"); +- ret = PTR_ERR(chan->ic); +- goto err; +- } +- +- /* get IDMAC channels */ +- chan->in_chan = ipu_idmac_get(priv->ipu, dma->in); +- chan->out_chan = ipu_idmac_get(priv->ipu, dma->out); +- if (IS_ERR(chan->in_chan) || IS_ERR(chan->out_chan)) { +- dev_err(priv->ipu->dev, "could not acquire idmac channels\n"); +- ret = -EBUSY; +- goto err; +- } +- +- chan->rotation_in_chan = ipu_idmac_get(priv->ipu, dma->rot_in); +- chan->rotation_out_chan = ipu_idmac_get(priv->ipu, dma->rot_out); +- if (IS_ERR(chan->rotation_in_chan) || IS_ERR(chan->rotation_out_chan)) { +- dev_err(priv->ipu->dev, +- "could not acquire idmac rotation channels\n"); +- ret = -EBUSY; +- goto err; +- } +- +- /* acquire the EOF interrupts */ +- chan->out_eof_irq = ipu_idmac_channel_irq(priv->ipu, +- chan->out_chan, +- IPU_IRQ_EOF); +- +- ret = request_threaded_irq(chan->out_eof_irq, norotate_irq, do_bh, +- 0, "ipu-ic", chan); +- if (ret < 0) { +- dev_err(priv->ipu->dev, "could not acquire irq %d\n", +- chan->out_eof_irq); +- chan->out_eof_irq = -1; +- goto err; +- } +- +- chan->rot_out_eof_irq = ipu_idmac_channel_irq(priv->ipu, +- chan->rotation_out_chan, +- IPU_IRQ_EOF); +- +- ret = request_threaded_irq(chan->rot_out_eof_irq, rotate_irq, do_bh, +- 0, "ipu-ic", chan); +- if (ret < 0) { +- dev_err(priv->ipu->dev, "could not acquire irq %d\n", +- chan->rot_out_eof_irq); +- chan->rot_out_eof_irq = -1; +- goto err; +- } +- +- return 0; +-err: +- release_ipu_resources(chan); +- return ret; +-} +- +-static int fill_image(struct ipu_image_convert_ctx *ctx, +- struct ipu_image_convert_image *ic_image, +- struct ipu_image *image, +- enum ipu_image_convert_type type) +-{ +- struct ipu_image_convert_priv *priv = ctx->chan->priv; +- +- ic_image->base = *image; +- ic_image->type = type; +- +- ic_image->fmt = get_format(image->pix.pixelformat); +- if (!ic_image->fmt) { +- dev_err(priv->ipu->dev, "pixelformat not supported for %s\n", +- type == IMAGE_CONVERT_OUT ? "Output" : "Input"); +- return -EINVAL; +- } +- +- if (ic_image->fmt->planar) +- ic_image->stride = ic_image->base.pix.width; +- else +- ic_image->stride = ic_image->base.pix.bytesperline; +- +- return 0; +-} +- +-/* borrowed from drivers/media/v4l2-core/v4l2-common.c */ +-static unsigned int clamp_align(unsigned int x, unsigned int min, +- unsigned int max, unsigned int align) +-{ +- /* Bits that must be zero to be aligned */ +- unsigned int mask = ~((1 << align) - 1); +- +- /* Clamp to aligned min and max */ +- x = clamp(x, (min + ~mask) & mask, max & mask); +- +- /* Round to nearest aligned value */ +- if (align) +- x = (x + (1 << (align - 1))) & mask; +- +- return x; +-} +- +-/* Adjusts input/output images to IPU restrictions */ +-void ipu_image_convert_adjust(struct ipu_image *in, struct ipu_image *out, +- enum ipu_rotate_mode rot_mode) +-{ +- const struct ipu_image_pixfmt *infmt, *outfmt; +- u32 w_align_out, h_align_out; +- u32 w_align_in, h_align_in; +- +- infmt = get_format(in->pix.pixelformat); +- outfmt = get_format(out->pix.pixelformat); +- +- /* set some default pixel formats if needed */ +- if (!infmt) { +- in->pix.pixelformat = V4L2_PIX_FMT_RGB24; +- infmt = get_format(V4L2_PIX_FMT_RGB24); +- } +- if (!outfmt) { +- out->pix.pixelformat = V4L2_PIX_FMT_RGB24; +- outfmt = get_format(V4L2_PIX_FMT_RGB24); +- } +- +- /* image converter does not handle fields */ +- in->pix.field = out->pix.field = V4L2_FIELD_NONE; +- +- /* resizer cannot downsize more than 4:1 */ +- if (ipu_rot_mode_is_irt(rot_mode)) { +- out->pix.height = max_t(__u32, out->pix.height, +- in->pix.width / 4); +- out->pix.width = max_t(__u32, out->pix.width, +- in->pix.height / 4); +- } else { +- out->pix.width = max_t(__u32, out->pix.width, +- in->pix.width / 4); +- out->pix.height = max_t(__u32, out->pix.height, +- in->pix.height / 4); +- } +- +- /* align input width/height */ +- w_align_in = ilog2(tile_width_align(IMAGE_CONVERT_IN, infmt, +- rot_mode)); +- h_align_in = ilog2(tile_height_align(IMAGE_CONVERT_IN, infmt, +- rot_mode)); +- in->pix.width = clamp_align(in->pix.width, MIN_W, MAX_W, +- w_align_in); +- in->pix.height = clamp_align(in->pix.height, MIN_H, MAX_H, +- h_align_in); +- +- /* align output width/height */ +- w_align_out = ilog2(tile_width_align(IMAGE_CONVERT_OUT, outfmt, +- rot_mode)); +- h_align_out = ilog2(tile_height_align(IMAGE_CONVERT_OUT, outfmt, +- rot_mode)); +- out->pix.width = clamp_align(out->pix.width, MIN_W, MAX_W, +- w_align_out); +- out->pix.height = clamp_align(out->pix.height, MIN_H, MAX_H, +- h_align_out); +- +- /* set input/output strides and image sizes */ +- in->pix.bytesperline = infmt->planar ? +- clamp_align(in->pix.width, 2 << w_align_in, MAX_W, +- w_align_in) : +- clamp_align((in->pix.width * infmt->bpp) >> 3, +- ((2 << w_align_in) * infmt->bpp) >> 3, +- (MAX_W * infmt->bpp) >> 3, +- w_align_in); +- in->pix.sizeimage = infmt->planar ? +- (in->pix.height * in->pix.bytesperline * infmt->bpp) >> 3 : +- in->pix.height * in->pix.bytesperline; +- out->pix.bytesperline = outfmt->planar ? out->pix.width : +- (out->pix.width * outfmt->bpp) >> 3; +- out->pix.sizeimage = outfmt->planar ? +- (out->pix.height * out->pix.bytesperline * outfmt->bpp) >> 3 : +- out->pix.height * out->pix.bytesperline; +-} +-EXPORT_SYMBOL_GPL(ipu_image_convert_adjust); +- +-/* +- * this is used by ipu_image_convert_prepare() to verify set input and +- * output images are valid before starting the conversion. Clients can +- * also call it before calling ipu_image_convert_prepare(). +- */ +-int ipu_image_convert_verify(struct ipu_image *in, struct ipu_image *out, +- enum ipu_rotate_mode rot_mode) +-{ +- struct ipu_image testin, testout; +- +- testin = *in; +- testout = *out; +- +- ipu_image_convert_adjust(&testin, &testout, rot_mode); +- +- if (testin.pix.width != in->pix.width || +- testin.pix.height != in->pix.height || +- testout.pix.width != out->pix.width || +- testout.pix.height != out->pix.height) +- return -EINVAL; +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_image_convert_verify); +- +-/* +- * Call ipu_image_convert_prepare() to prepare for the conversion of +- * given images and rotation mode. Returns a new conversion context. +- */ +-struct ipu_image_convert_ctx * +-ipu_image_convert_prepare(struct ipu_soc *ipu, enum ipu_ic_task ic_task, +- struct ipu_image *in, struct ipu_image *out, +- enum ipu_rotate_mode rot_mode, +- ipu_image_convert_cb_t complete, +- void *complete_context) +-{ +- struct ipu_image_convert_priv *priv = ipu->image_convert_priv; +- struct ipu_image_convert_image *s_image, *d_image; +- struct ipu_image_convert_chan *chan; +- struct ipu_image_convert_ctx *ctx; +- unsigned long flags; +- unsigned int i; +- bool get_res; +- int ret; +- +- if (!in || !out || !complete || +- (ic_task != IC_TASK_VIEWFINDER && +- ic_task != IC_TASK_POST_PROCESSOR)) +- return ERR_PTR(-EINVAL); +- +- /* verify the in/out images before continuing */ +- ret = ipu_image_convert_verify(in, out, rot_mode); +- if (ret) { +- dev_err(priv->ipu->dev, "%s: in/out formats invalid\n", +- __func__); +- return ERR_PTR(ret); +- } +- +- chan = &priv->chan[ic_task]; +- +- ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); +- if (!ctx) +- return ERR_PTR(-ENOMEM); +- +- dev_dbg(priv->ipu->dev, "%s: task %u: ctx %p\n", __func__, +- chan->ic_task, ctx); +- +- ctx->chan = chan; +- init_completion(&ctx->aborted); +- +- ctx->rot_mode = rot_mode; +- +- /* Sets ctx->in.num_rows/cols as well */ +- ret = calc_image_resize_coefficients(ctx, in, out); +- if (ret) +- goto out_free; +- +- s_image = &ctx->in; +- d_image = &ctx->out; +- +- /* set tiling and rotation */ +- if (ipu_rot_mode_is_irt(rot_mode)) { +- d_image->num_rows = s_image->num_cols; +- d_image->num_cols = s_image->num_rows; +- } else { +- d_image->num_rows = s_image->num_rows; +- d_image->num_cols = s_image->num_cols; +- } +- +- ctx->num_tiles = d_image->num_cols * d_image->num_rows; +- +- ret = fill_image(ctx, s_image, in, IMAGE_CONVERT_IN); +- if (ret) +- goto out_free; +- ret = fill_image(ctx, d_image, out, IMAGE_CONVERT_OUT); +- if (ret) +- goto out_free; +- +- calc_out_tile_map(ctx); +- +- find_seams(ctx, s_image, d_image); +- +- ret = calc_tile_dimensions(ctx, s_image); +- if (ret) +- goto out_free; +- +- ret = calc_tile_offsets(ctx, s_image); +- if (ret) +- goto out_free; +- +- calc_tile_dimensions(ctx, d_image); +- ret = calc_tile_offsets(ctx, d_image); +- if (ret) +- goto out_free; +- +- calc_tile_resize_coefficients(ctx); +- +- ret = ipu_ic_calc_csc(&ctx->csc, +- s_image->base.pix.ycbcr_enc, +- s_image->base.pix.quantization, +- ipu_pixelformat_to_colorspace(s_image->fmt->fourcc), +- d_image->base.pix.ycbcr_enc, +- d_image->base.pix.quantization, +- ipu_pixelformat_to_colorspace(d_image->fmt->fourcc)); +- if (ret) +- goto out_free; +- +- dump_format(ctx, s_image); +- dump_format(ctx, d_image); +- +- ctx->complete = complete; +- ctx->complete_context = complete_context; +- +- /* +- * Can we use double-buffering for this operation? If there is +- * only one tile (the whole image can be converted in a single +- * operation) there's no point in using double-buffering. Also, +- * the IPU's IDMAC channels allow only a single U and V plane +- * offset shared between both buffers, but these offsets change +- * for every tile, and therefore would have to be updated for +- * each buffer which is not possible. So double-buffering is +- * impossible when either the source or destination images are +- * a planar format (YUV420, YUV422P, etc.). Further, differently +- * sized tiles or different resizing coefficients per tile +- * prevent double-buffering as well. +- */ +- ctx->double_buffering = (ctx->num_tiles > 1 && +- !s_image->fmt->planar && +- !d_image->fmt->planar); +- for (i = 1; i < ctx->num_tiles; i++) { +- if (ctx->in.tile[i].width != ctx->in.tile[0].width || +- ctx->in.tile[i].height != ctx->in.tile[0].height || +- ctx->out.tile[i].width != ctx->out.tile[0].width || +- ctx->out.tile[i].height != ctx->out.tile[0].height) { +- ctx->double_buffering = false; +- break; +- } +- } +- for (i = 1; i < ctx->in.num_cols; i++) { +- if (ctx->resize_coeffs_h[i] != ctx->resize_coeffs_h[0]) { +- ctx->double_buffering = false; +- break; +- } +- } +- for (i = 1; i < ctx->in.num_rows; i++) { +- if (ctx->resize_coeffs_v[i] != ctx->resize_coeffs_v[0]) { +- ctx->double_buffering = false; +- break; +- } +- } +- +- if (ipu_rot_mode_is_irt(ctx->rot_mode)) { +- unsigned long intermediate_size = d_image->tile[0].size; +- +- for (i = 1; i < ctx->num_tiles; i++) { +- if (d_image->tile[i].size > intermediate_size) +- intermediate_size = d_image->tile[i].size; +- } +- +- ret = alloc_dma_buf(priv, &ctx->rot_intermediate[0], +- intermediate_size); +- if (ret) +- goto out_free; +- if (ctx->double_buffering) { +- ret = alloc_dma_buf(priv, +- &ctx->rot_intermediate[1], +- intermediate_size); +- if (ret) +- goto out_free_dmabuf0; +- } +- } +- +- spin_lock_irqsave(&chan->irqlock, flags); +- +- get_res = list_empty(&chan->ctx_list); +- +- list_add_tail(&ctx->list, &chan->ctx_list); +- +- spin_unlock_irqrestore(&chan->irqlock, flags); +- +- if (get_res) { +- ret = get_ipu_resources(chan); +- if (ret) +- goto out_free_dmabuf1; +- } +- +- return ctx; +- +-out_free_dmabuf1: +- free_dma_buf(priv, &ctx->rot_intermediate[1]); +- spin_lock_irqsave(&chan->irqlock, flags); +- list_del(&ctx->list); +- spin_unlock_irqrestore(&chan->irqlock, flags); +-out_free_dmabuf0: +- free_dma_buf(priv, &ctx->rot_intermediate[0]); +-out_free: +- kfree(ctx); +- return ERR_PTR(ret); +-} +-EXPORT_SYMBOL_GPL(ipu_image_convert_prepare); +- +-/* +- * Carry out a single image conversion run. Only the physaddr's of the input +- * and output image buffers are needed. The conversion context must have +- * been created previously with ipu_image_convert_prepare(). +- */ +-int ipu_image_convert_queue(struct ipu_image_convert_run *run) +-{ +- struct ipu_image_convert_chan *chan; +- struct ipu_image_convert_priv *priv; +- struct ipu_image_convert_ctx *ctx; +- unsigned long flags; +- int ret = 0; +- +- if (!run || !run->ctx || !run->in_phys || !run->out_phys) +- return -EINVAL; +- +- ctx = run->ctx; +- chan = ctx->chan; +- priv = chan->priv; +- +- dev_dbg(priv->ipu->dev, "%s: task %u: ctx %p run %p\n", __func__, +- chan->ic_task, ctx, run); +- +- INIT_LIST_HEAD(&run->list); +- +- spin_lock_irqsave(&chan->irqlock, flags); +- +- if (ctx->aborting) { +- ret = -EIO; +- goto unlock; +- } +- +- list_add_tail(&run->list, &chan->pending_q); +- +- if (!chan->current_run) { +- ret = do_run(run); +- if (ret) +- chan->current_run = NULL; +- } +-unlock: +- spin_unlock_irqrestore(&chan->irqlock, flags); +- return ret; +-} +-EXPORT_SYMBOL_GPL(ipu_image_convert_queue); +- +-/* Abort any active or pending conversions for this context */ +-static void __ipu_image_convert_abort(struct ipu_image_convert_ctx *ctx) +-{ +- struct ipu_image_convert_chan *chan = ctx->chan; +- struct ipu_image_convert_priv *priv = chan->priv; +- struct ipu_image_convert_run *run, *active_run, *tmp; +- unsigned long flags; +- int run_count, ret; +- +- spin_lock_irqsave(&chan->irqlock, flags); +- +- /* move all remaining pending runs in this context to done_q */ +- list_for_each_entry_safe(run, tmp, &chan->pending_q, list) { +- if (run->ctx != ctx) +- continue; +- run->status = -EIO; +- list_move_tail(&run->list, &chan->done_q); +- } +- +- run_count = get_run_count(ctx, &chan->done_q); +- active_run = (chan->current_run && chan->current_run->ctx == ctx) ? +- chan->current_run : NULL; +- +- if (active_run) +- reinit_completion(&ctx->aborted); +- +- ctx->aborting = true; +- +- spin_unlock_irqrestore(&chan->irqlock, flags); +- +- if (!run_count && !active_run) { +- dev_dbg(priv->ipu->dev, +- "%s: task %u: no abort needed for ctx %p\n", +- __func__, chan->ic_task, ctx); +- return; +- } +- +- if (!active_run) { +- empty_done_q(chan); +- return; +- } +- +- dev_dbg(priv->ipu->dev, +- "%s: task %u: wait for completion: %d runs\n", +- __func__, chan->ic_task, run_count); +- +- ret = wait_for_completion_timeout(&ctx->aborted, +- msecs_to_jiffies(10000)); +- if (ret == 0) { +- dev_warn(priv->ipu->dev, "%s: timeout\n", __func__); +- force_abort(ctx); +- } +-} +- +-void ipu_image_convert_abort(struct ipu_image_convert_ctx *ctx) +-{ +- __ipu_image_convert_abort(ctx); +- ctx->aborting = false; +-} +-EXPORT_SYMBOL_GPL(ipu_image_convert_abort); +- +-/* Unprepare image conversion context */ +-void ipu_image_convert_unprepare(struct ipu_image_convert_ctx *ctx) +-{ +- struct ipu_image_convert_chan *chan = ctx->chan; +- struct ipu_image_convert_priv *priv = chan->priv; +- unsigned long flags; +- bool put_res; +- +- /* make sure no runs are hanging around */ +- __ipu_image_convert_abort(ctx); +- +- dev_dbg(priv->ipu->dev, "%s: task %u: removing ctx %p\n", __func__, +- chan->ic_task, ctx); +- +- spin_lock_irqsave(&chan->irqlock, flags); +- +- list_del(&ctx->list); +- +- put_res = list_empty(&chan->ctx_list); +- +- spin_unlock_irqrestore(&chan->irqlock, flags); +- +- if (put_res) +- release_ipu_resources(chan); +- +- free_dma_buf(priv, &ctx->rot_intermediate[1]); +- free_dma_buf(priv, &ctx->rot_intermediate[0]); +- +- kfree(ctx); +-} +-EXPORT_SYMBOL_GPL(ipu_image_convert_unprepare); +- +-/* +- * "Canned" asynchronous single image conversion. Allocates and returns +- * a new conversion run. On successful return the caller must free the +- * run and call ipu_image_convert_unprepare() after conversion completes. +- */ +-struct ipu_image_convert_run * +-ipu_image_convert(struct ipu_soc *ipu, enum ipu_ic_task ic_task, +- struct ipu_image *in, struct ipu_image *out, +- enum ipu_rotate_mode rot_mode, +- ipu_image_convert_cb_t complete, +- void *complete_context) +-{ +- struct ipu_image_convert_ctx *ctx; +- struct ipu_image_convert_run *run; +- int ret; +- +- ctx = ipu_image_convert_prepare(ipu, ic_task, in, out, rot_mode, +- complete, complete_context); +- if (IS_ERR(ctx)) +- return ERR_CAST(ctx); +- +- run = kzalloc(sizeof(*run), GFP_KERNEL); +- if (!run) { +- ipu_image_convert_unprepare(ctx); +- return ERR_PTR(-ENOMEM); +- } +- +- run->ctx = ctx; +- run->in_phys = in->phys0; +- run->out_phys = out->phys0; +- +- ret = ipu_image_convert_queue(run); +- if (ret) { +- ipu_image_convert_unprepare(ctx); +- kfree(run); +- return ERR_PTR(ret); +- } +- +- return run; +-} +-EXPORT_SYMBOL_GPL(ipu_image_convert); +- +-/* "Canned" synchronous single image conversion */ +-static void image_convert_sync_complete(struct ipu_image_convert_run *run, +- void *data) +-{ +- struct completion *comp = data; +- +- complete(comp); +-} +- +-int ipu_image_convert_sync(struct ipu_soc *ipu, enum ipu_ic_task ic_task, +- struct ipu_image *in, struct ipu_image *out, +- enum ipu_rotate_mode rot_mode) +-{ +- struct ipu_image_convert_run *run; +- struct completion comp; +- int ret; +- +- init_completion(&comp); +- +- run = ipu_image_convert(ipu, ic_task, in, out, rot_mode, +- image_convert_sync_complete, &comp); +- if (IS_ERR(run)) +- return PTR_ERR(run); +- +- ret = wait_for_completion_timeout(&comp, msecs_to_jiffies(10000)); +- ret = (ret == 0) ? -ETIMEDOUT : 0; +- +- ipu_image_convert_unprepare(run->ctx); +- kfree(run); +- +- return ret; +-} +-EXPORT_SYMBOL_GPL(ipu_image_convert_sync); +- +-int ipu_image_convert_init(struct ipu_soc *ipu, struct device *dev) +-{ +- struct ipu_image_convert_priv *priv; +- int i; +- +- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); +- if (!priv) +- return -ENOMEM; +- +- ipu->image_convert_priv = priv; +- priv->ipu = ipu; +- +- for (i = 0; i < IC_NUM_TASKS; i++) { +- struct ipu_image_convert_chan *chan = &priv->chan[i]; +- +- chan->ic_task = i; +- chan->priv = priv; +- chan->dma_ch = &image_convert_dma_chan[i]; +- chan->out_eof_irq = -1; +- chan->rot_out_eof_irq = -1; +- +- spin_lock_init(&chan->irqlock); +- INIT_LIST_HEAD(&chan->ctx_list); +- INIT_LIST_HEAD(&chan->pending_q); +- INIT_LIST_HEAD(&chan->done_q); +- } +- +- return 0; +-} +- +-void ipu_image_convert_exit(struct ipu_soc *ipu) +-{ +-} +--- a/drivers/gpu/imx/ipu-v3/ipu-pre.c ++++ /dev/null +@@ -1,346 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-only +-/* +- * Copyright (c) 2017 Lucas Stach, Pengutronix +- */ +- +-#include <drm/drm_fourcc.h> +-#include <linux/clk.h> +-#include <linux/err.h> +-#include <linux/genalloc.h> +-#include <linux/module.h> +-#include <linux/of.h> +-#include <linux/platform_device.h> +-#include <video/imx-ipu-v3.h> +- +-#include "ipu-prv.h" +- +-#define IPU_PRE_MAX_WIDTH 2048 +-#define IPU_PRE_NUM_SCANLINES 8 +- +-#define IPU_PRE_CTRL 0x000 +-#define IPU_PRE_CTRL_SET 0x004 +-#define IPU_PRE_CTRL_ENABLE (1 << 0) +-#define IPU_PRE_CTRL_BLOCK_EN (1 << 1) +-#define IPU_PRE_CTRL_BLOCK_16 (1 << 2) +-#define IPU_PRE_CTRL_SDW_UPDATE (1 << 4) +-#define IPU_PRE_CTRL_VFLIP (1 << 5) +-#define IPU_PRE_CTRL_SO (1 << 6) +-#define IPU_PRE_CTRL_INTERLACED_FIELD (1 << 7) +-#define IPU_PRE_CTRL_HANDSHAKE_EN (1 << 8) +-#define IPU_PRE_CTRL_HANDSHAKE_LINE_NUM(v) ((v & 0x3) << 9) +-#define IPU_PRE_CTRL_HANDSHAKE_ABORT_SKIP_EN (1 << 11) +-#define IPU_PRE_CTRL_EN_REPEAT (1 << 28) +-#define IPU_PRE_CTRL_TPR_REST_SEL (1 << 29) +-#define IPU_PRE_CTRL_CLKGATE (1 << 30) +-#define IPU_PRE_CTRL_SFTRST (1 << 31) +- +-#define IPU_PRE_CUR_BUF 0x030 +- +-#define IPU_PRE_NEXT_BUF 0x040 +- +-#define IPU_PRE_TPR_CTRL 0x070 +-#define IPU_PRE_TPR_CTRL_TILE_FORMAT(v) ((v & 0xff) << 0) +-#define IPU_PRE_TPR_CTRL_TILE_FORMAT_MASK 0xff +-#define IPU_PRE_TPR_CTRL_TILE_FORMAT_16_BIT (1 << 0) +-#define IPU_PRE_TPR_CTRL_TILE_FORMAT_SPLIT_BUF (1 << 4) +-#define IPU_PRE_TPR_CTRL_TILE_FORMAT_SINGLE_BUF (1 << 5) +-#define IPU_PRE_TPR_CTRL_TILE_FORMAT_SUPER_TILED (1 << 6) +- +-#define IPU_PRE_PREFETCH_ENG_CTRL 0x080 +-#define IPU_PRE_PREF_ENG_CTRL_PREFETCH_EN (1 << 0) +-#define IPU_PRE_PREF_ENG_CTRL_RD_NUM_BYTES(v) ((v & 0x7) << 1) +-#define IPU_PRE_PREF_ENG_CTRL_INPUT_ACTIVE_BPP(v) ((v & 0x3) << 4) +-#define IPU_PRE_PREF_ENG_CTRL_INPUT_PIXEL_FORMAT(v) ((v & 0x7) << 8) +-#define IPU_PRE_PREF_ENG_CTRL_SHIFT_BYPASS (1 << 11) +-#define IPU_PRE_PREF_ENG_CTRL_FIELD_INVERSE (1 << 12) +-#define IPU_PRE_PREF_ENG_CTRL_PARTIAL_UV_SWAP (1 << 14) +-#define IPU_PRE_PREF_ENG_CTRL_TPR_COOR_OFFSET_EN (1 << 15) +- +-#define IPU_PRE_PREFETCH_ENG_INPUT_SIZE 0x0a0 +-#define IPU_PRE_PREFETCH_ENG_INPUT_SIZE_WIDTH(v) ((v & 0xffff) << 0) +-#define IPU_PRE_PREFETCH_ENG_INPUT_SIZE_HEIGHT(v) ((v & 0xffff) << 16) +- +-#define IPU_PRE_PREFETCH_ENG_PITCH 0x0d0 +-#define IPU_PRE_PREFETCH_ENG_PITCH_Y(v) ((v & 0xffff) << 0) +-#define IPU_PRE_PREFETCH_ENG_PITCH_UV(v) ((v & 0xffff) << 16) +- +-#define IPU_PRE_STORE_ENG_CTRL 0x110 +-#define IPU_PRE_STORE_ENG_CTRL_STORE_EN (1 << 0) +-#define IPU_PRE_STORE_ENG_CTRL_WR_NUM_BYTES(v) ((v & 0x7) << 1) +-#define IPU_PRE_STORE_ENG_CTRL_OUTPUT_ACTIVE_BPP(v) ((v & 0x3) << 4) +- +-#define IPU_PRE_STORE_ENG_STATUS 0x120 +-#define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_X_MASK 0xffff +-#define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_X_SHIFT 0 +-#define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_MASK 0x3fff +-#define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_SHIFT 16 +-#define IPU_PRE_STORE_ENG_STATUS_STORE_FIFO_FULL (1 << 30) +-#define IPU_PRE_STORE_ENG_STATUS_STORE_FIELD (1 << 31) +- +-#define IPU_PRE_STORE_ENG_SIZE 0x130 +-#define IPU_PRE_STORE_ENG_SIZE_INPUT_WIDTH(v) ((v & 0xffff) << 0) +-#define IPU_PRE_STORE_ENG_SIZE_INPUT_HEIGHT(v) ((v & 0xffff) << 16) +- +-#define IPU_PRE_STORE_ENG_PITCH 0x140 +-#define IPU_PRE_STORE_ENG_PITCH_OUT_PITCH(v) ((v & 0xffff) << 0) +- +-#define IPU_PRE_STORE_ENG_ADDR 0x150 +- +-struct ipu_pre { +- struct list_head list; +- struct device *dev; +- +- void __iomem *regs; +- struct clk *clk_axi; +- struct gen_pool *iram; +- +- dma_addr_t buffer_paddr; +- void *buffer_virt; +- bool in_use; +- unsigned int safe_window_end; +- unsigned int last_bufaddr; +-}; +- +-static DEFINE_MUTEX(ipu_pre_list_mutex); +-static LIST_HEAD(ipu_pre_list); +-static int available_pres; +- +-int ipu_pre_get_available_count(void) +-{ +- return available_pres; +-} +- +-struct ipu_pre * +-ipu_pre_lookup_by_phandle(struct device *dev, const char *name, int index) +-{ +- struct device_node *pre_node = of_parse_phandle(dev->of_node, +- name, index); +- struct ipu_pre *pre; +- +- mutex_lock(&ipu_pre_list_mutex); +- list_for_each_entry(pre, &ipu_pre_list, list) { +- if (pre_node == pre->dev->of_node) { +- mutex_unlock(&ipu_pre_list_mutex); +- device_link_add(dev, pre->dev, +- DL_FLAG_AUTOREMOVE_CONSUMER); +- of_node_put(pre_node); +- return pre; +- } +- } +- mutex_unlock(&ipu_pre_list_mutex); +- +- of_node_put(pre_node); +- +- return NULL; +-} +- +-int ipu_pre_get(struct ipu_pre *pre) +-{ +- u32 val; +- +- if (pre->in_use) +- return -EBUSY; +- +- /* first get the engine out of reset and remove clock gating */ +- writel(0, pre->regs + IPU_PRE_CTRL); +- +- /* init defaults that should be applied to all streams */ +- val = IPU_PRE_CTRL_HANDSHAKE_ABORT_SKIP_EN | +- IPU_PRE_CTRL_HANDSHAKE_EN | +- IPU_PRE_CTRL_TPR_REST_SEL | +- IPU_PRE_CTRL_SDW_UPDATE; +- writel(val, pre->regs + IPU_PRE_CTRL); +- +- pre->in_use = true; +- return 0; +-} +- +-void ipu_pre_put(struct ipu_pre *pre) +-{ +- writel(IPU_PRE_CTRL_SFTRST, pre->regs + IPU_PRE_CTRL); +- +- pre->in_use = false; +-} +- +-void ipu_pre_configure(struct ipu_pre *pre, unsigned int width, +- unsigned int height, unsigned int stride, u32 format, +- uint64_t modifier, unsigned int bufaddr) +-{ +- const struct drm_format_info *info = drm_format_info(format); +- u32 active_bpp = info->cpp[0] >> 1; +- u32 val; +- +- /* calculate safe window for ctrl register updates */ +- if (modifier == DRM_FORMAT_MOD_LINEAR) +- pre->safe_window_end = height - 2; +- else +- pre->safe_window_end = DIV_ROUND_UP(height, 4) - 1; +- +- writel(bufaddr, pre->regs + IPU_PRE_CUR_BUF); +- writel(bufaddr, pre->regs + IPU_PRE_NEXT_BUF); +- pre->last_bufaddr = bufaddr; +- +- val = IPU_PRE_PREF_ENG_CTRL_INPUT_PIXEL_FORMAT(0) | +- IPU_PRE_PREF_ENG_CTRL_INPUT_ACTIVE_BPP(active_bpp) | +- IPU_PRE_PREF_ENG_CTRL_RD_NUM_BYTES(4) | +- IPU_PRE_PREF_ENG_CTRL_SHIFT_BYPASS | +- IPU_PRE_PREF_ENG_CTRL_PREFETCH_EN; +- writel(val, pre->regs + IPU_PRE_PREFETCH_ENG_CTRL); +- +- val = IPU_PRE_PREFETCH_ENG_INPUT_SIZE_WIDTH(width) | +- IPU_PRE_PREFETCH_ENG_INPUT_SIZE_HEIGHT(height); +- writel(val, pre->regs + IPU_PRE_PREFETCH_ENG_INPUT_SIZE); +- +- val = IPU_PRE_PREFETCH_ENG_PITCH_Y(stride); +- writel(val, pre->regs + IPU_PRE_PREFETCH_ENG_PITCH); +- +- val = IPU_PRE_STORE_ENG_CTRL_OUTPUT_ACTIVE_BPP(active_bpp) | +- IPU_PRE_STORE_ENG_CTRL_WR_NUM_BYTES(4) | +- IPU_PRE_STORE_ENG_CTRL_STORE_EN; +- writel(val, pre->regs + IPU_PRE_STORE_ENG_CTRL); +- +- val = IPU_PRE_STORE_ENG_SIZE_INPUT_WIDTH(width) | +- IPU_PRE_STORE_ENG_SIZE_INPUT_HEIGHT(height); +- writel(val, pre->regs + IPU_PRE_STORE_ENG_SIZE); +- +- val = IPU_PRE_STORE_ENG_PITCH_OUT_PITCH(stride); +- writel(val, pre->regs + IPU_PRE_STORE_ENG_PITCH); +- +- writel(pre->buffer_paddr, pre->regs + IPU_PRE_STORE_ENG_ADDR); +- +- val = readl(pre->regs + IPU_PRE_TPR_CTRL); +- val &= ~IPU_PRE_TPR_CTRL_TILE_FORMAT_MASK; +- if (modifier != DRM_FORMAT_MOD_LINEAR) { +- /* only support single buffer formats for now */ +- val |= IPU_PRE_TPR_CTRL_TILE_FORMAT_SINGLE_BUF; +- if (modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED) +- val |= IPU_PRE_TPR_CTRL_TILE_FORMAT_SUPER_TILED; +- if (info->cpp[0] == 2) +- val |= IPU_PRE_TPR_CTRL_TILE_FORMAT_16_BIT; +- } +- writel(val, pre->regs + IPU_PRE_TPR_CTRL); +- +- val = readl(pre->regs + IPU_PRE_CTRL); +- val |= IPU_PRE_CTRL_EN_REPEAT | IPU_PRE_CTRL_ENABLE | +- IPU_PRE_CTRL_SDW_UPDATE; +- if (modifier == DRM_FORMAT_MOD_LINEAR) +- val &= ~IPU_PRE_CTRL_BLOCK_EN; +- else +- val |= IPU_PRE_CTRL_BLOCK_EN; +- writel(val, pre->regs + IPU_PRE_CTRL); +-} +- +-void ipu_pre_update(struct ipu_pre *pre, unsigned int bufaddr) +-{ +- unsigned long timeout = jiffies + msecs_to_jiffies(5); +- unsigned short current_yblock; +- u32 val; +- +- if (bufaddr == pre->last_bufaddr) +- return; +- +- writel(bufaddr, pre->regs + IPU_PRE_NEXT_BUF); +- pre->last_bufaddr = bufaddr; +- +- do { +- if (time_after(jiffies, timeout)) { +- dev_warn(pre->dev, "timeout waiting for PRE safe window\n"); +- return; +- } +- +- val = readl(pre->regs + IPU_PRE_STORE_ENG_STATUS); +- current_yblock = +- (val >> IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_SHIFT) & +- IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_MASK; +- } while (current_yblock == 0 || current_yblock >= pre->safe_window_end); +- +- writel(IPU_PRE_CTRL_SDW_UPDATE, pre->regs + IPU_PRE_CTRL_SET); +-} +- +-bool ipu_pre_update_pending(struct ipu_pre *pre) +-{ +- return !!(readl_relaxed(pre->regs + IPU_PRE_CTRL) & +- IPU_PRE_CTRL_SDW_UPDATE); +-} +- +-u32 ipu_pre_get_baddr(struct ipu_pre *pre) +-{ +- return (u32)pre->buffer_paddr; +-} +- +-static int ipu_pre_probe(struct platform_device *pdev) +-{ +- struct device *dev = &pdev->dev; +- struct resource *res; +- struct ipu_pre *pre; +- +- pre = devm_kzalloc(dev, sizeof(*pre), GFP_KERNEL); +- if (!pre) +- return -ENOMEM; +- +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- pre->regs = devm_ioremap_resource(&pdev->dev, res); +- if (IS_ERR(pre->regs)) +- return PTR_ERR(pre->regs); +- +- pre->clk_axi = devm_clk_get(dev, "axi"); +- if (IS_ERR(pre->clk_axi)) +- return PTR_ERR(pre->clk_axi); +- +- pre->iram = of_gen_pool_get(dev->of_node, "fsl,iram", 0); +- if (!pre->iram) +- return -EPROBE_DEFER; +- +- /* +- * Allocate IRAM buffer with maximum size. This could be made dynamic, +- * but as there is no other user of this IRAM region and we can fit all +- * max sized buffers into it, there is no need yet. +- */ +- pre->buffer_virt = gen_pool_dma_alloc(pre->iram, IPU_PRE_MAX_WIDTH * +- IPU_PRE_NUM_SCANLINES * 4, +- &pre->buffer_paddr); +- if (!pre->buffer_virt) +- return -ENOMEM; +- +- clk_prepare_enable(pre->clk_axi); +- +- pre->dev = dev; +- platform_set_drvdata(pdev, pre); +- mutex_lock(&ipu_pre_list_mutex); +- list_add(&pre->list, &ipu_pre_list); +- available_pres++; +- mutex_unlock(&ipu_pre_list_mutex); +- +- return 0; +-} +- +-static int ipu_pre_remove(struct platform_device *pdev) +-{ +- struct ipu_pre *pre = platform_get_drvdata(pdev); +- +- mutex_lock(&ipu_pre_list_mutex); +- list_del(&pre->list); +- available_pres--; +- mutex_unlock(&ipu_pre_list_mutex); +- +- clk_disable_unprepare(pre->clk_axi); +- +- if (pre->buffer_virt) +- gen_pool_free(pre->iram, (unsigned long)pre->buffer_virt, +- IPU_PRE_MAX_WIDTH * IPU_PRE_NUM_SCANLINES * 4); +- return 0; +-} +- +-static const struct of_device_id ipu_pre_dt_ids[] = { +- { .compatible = "fsl,imx6qp-pre", }, +- { /* sentinel */ }, +-}; +- +-struct platform_driver ipu_pre_drv = { +- .probe = ipu_pre_probe, +- .remove = ipu_pre_remove, +- .driver = { +- .name = "imx-ipu-pre", +- .of_match_table = ipu_pre_dt_ids, +- }, +-}; +--- a/drivers/gpu/imx/ipu-v3/ipu-prg.c ++++ /dev/null +@@ -1,483 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-only +-/* +- * Copyright (c) 2016-2017 Lucas Stach, Pengutronix +- */ +- +-#include <drm/drm_fourcc.h> +-#include <linux/clk.h> +-#include <linux/err.h> +-#include <linux/iopoll.h> +-#include <linux/mfd/syscon.h> +-#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +-#include <linux/module.h> +-#include <linux/of.h> +-#include <linux/platform_device.h> +-#include <linux/pm_runtime.h> +-#include <linux/regmap.h> +-#include <video/imx-ipu-v3.h> +- +-#include "ipu-prv.h" +- +-#define IPU_PRG_CTL 0x00 +-#define IPU_PRG_CTL_BYPASS(i) (1 << (0 + i)) +-#define IPU_PRG_CTL_SOFT_ARID_MASK 0x3 +-#define IPU_PRG_CTL_SOFT_ARID_SHIFT(i) (8 + i * 2) +-#define IPU_PRG_CTL_SOFT_ARID(i, v) ((v & 0x3) << (8 + 2 * i)) +-#define IPU_PRG_CTL_SO(i) (1 << (16 + i)) +-#define IPU_PRG_CTL_VFLIP(i) (1 << (19 + i)) +-#define IPU_PRG_CTL_BLOCK_MODE(i) (1 << (22 + i)) +-#define IPU_PRG_CTL_CNT_LOAD_EN(i) (1 << (25 + i)) +-#define IPU_PRG_CTL_SOFTRST (1 << 30) +-#define IPU_PRG_CTL_SHADOW_EN (1 << 31) +- +-#define IPU_PRG_STATUS 0x04 +-#define IPU_PRG_STATUS_BUFFER0_READY(i) (1 << (0 + i * 2)) +-#define IPU_PRG_STATUS_BUFFER1_READY(i) (1 << (1 + i * 2)) +- +-#define IPU_PRG_QOS 0x08 +-#define IPU_PRG_QOS_ARID_MASK 0xf +-#define IPU_PRG_QOS_ARID_SHIFT(i) (0 + i * 4) +- +-#define IPU_PRG_REG_UPDATE 0x0c +-#define IPU_PRG_REG_UPDATE_REG_UPDATE (1 << 0) +- +-#define IPU_PRG_STRIDE(i) (0x10 + i * 0x4) +-#define IPU_PRG_STRIDE_STRIDE_MASK 0x3fff +- +-#define IPU_PRG_CROP_LINE 0x1c +- +-#define IPU_PRG_THD 0x20 +- +-#define IPU_PRG_BADDR(i) (0x24 + i * 0x4) +- +-#define IPU_PRG_OFFSET(i) (0x30 + i * 0x4) +- +-#define IPU_PRG_ILO(i) (0x3c + i * 0x4) +- +-#define IPU_PRG_HEIGHT(i) (0x48 + i * 0x4) +-#define IPU_PRG_HEIGHT_PRE_HEIGHT_MASK 0xfff +-#define IPU_PRG_HEIGHT_PRE_HEIGHT_SHIFT 0 +-#define IPU_PRG_HEIGHT_IPU_HEIGHT_MASK 0xfff +-#define IPU_PRG_HEIGHT_IPU_HEIGHT_SHIFT 16 +- +-struct ipu_prg_channel { +- bool enabled; +- int used_pre; +-}; +- +-struct ipu_prg { +- struct list_head list; +- struct device *dev; +- int id; +- +- void __iomem *regs; +- struct clk *clk_ipg, *clk_axi; +- struct regmap *iomuxc_gpr; +- struct ipu_pre *pres[3]; +- +- struct ipu_prg_channel chan[3]; +-}; +- +-static DEFINE_MUTEX(ipu_prg_list_mutex); +-static LIST_HEAD(ipu_prg_list); +- +-struct ipu_prg * +-ipu_prg_lookup_by_phandle(struct device *dev, const char *name, int ipu_id) +-{ +- struct device_node *prg_node = of_parse_phandle(dev->of_node, +- name, 0); +- struct ipu_prg *prg; +- +- mutex_lock(&ipu_prg_list_mutex); +- list_for_each_entry(prg, &ipu_prg_list, list) { +- if (prg_node == prg->dev->of_node) { +- mutex_unlock(&ipu_prg_list_mutex); +- device_link_add(dev, prg->dev, +- DL_FLAG_AUTOREMOVE_CONSUMER); +- prg->id = ipu_id; +- of_node_put(prg_node); +- return prg; +- } +- } +- mutex_unlock(&ipu_prg_list_mutex); +- +- of_node_put(prg_node); +- +- return NULL; +-} +- +-int ipu_prg_max_active_channels(void) +-{ +- return ipu_pre_get_available_count(); +-} +-EXPORT_SYMBOL_GPL(ipu_prg_max_active_channels); +- +-bool ipu_prg_present(struct ipu_soc *ipu) +-{ +- if (ipu->prg_priv) +- return true; +- +- return false; +-} +-EXPORT_SYMBOL_GPL(ipu_prg_present); +- +-bool ipu_prg_format_supported(struct ipu_soc *ipu, uint32_t format, +- uint64_t modifier) +-{ +- const struct drm_format_info *info = drm_format_info(format); +- +- if (info->num_planes != 1) +- return false; +- +- switch (modifier) { +- case DRM_FORMAT_MOD_LINEAR: +- case DRM_FORMAT_MOD_VIVANTE_TILED: +- case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED: +- return true; +- default: +- return false; +- } +-} +-EXPORT_SYMBOL_GPL(ipu_prg_format_supported); +- +-int ipu_prg_enable(struct ipu_soc *ipu) +-{ +- struct ipu_prg *prg = ipu->prg_priv; +- +- if (!prg) +- return 0; +- +- return pm_runtime_get_sync(prg->dev); +-} +-EXPORT_SYMBOL_GPL(ipu_prg_enable); +- +-void ipu_prg_disable(struct ipu_soc *ipu) +-{ +- struct ipu_prg *prg = ipu->prg_priv; +- +- if (!prg) +- return; +- +- pm_runtime_put(prg->dev); +-} +-EXPORT_SYMBOL_GPL(ipu_prg_disable); +- +-/* +- * The channel configuartion functions below are not thread safe, as they +- * must be only called from the atomic commit path in the DRM driver, which +- * is properly serialized. +- */ +-static int ipu_prg_ipu_to_prg_chan(int ipu_chan) +-{ +- /* +- * This isn't clearly documented in the RM, but IPU to PRG channel +- * assignment is fixed, as only with this mapping the control signals +- * match up. +- */ +- switch (ipu_chan) { +- case IPUV3_CHANNEL_MEM_BG_SYNC: +- return 0; +- case IPUV3_CHANNEL_MEM_FG_SYNC: +- return 1; +- case IPUV3_CHANNEL_MEM_DC_SYNC: +- return 2; +- default: +- return -EINVAL; +- } +-} +- +-static int ipu_prg_get_pre(struct ipu_prg *prg, int prg_chan) +-{ +- int i, ret; +- +- /* channel 0 is special as it is hardwired to one of the PREs */ +- if (prg_chan == 0) { +- ret = ipu_pre_get(prg->pres[0]); +- if (ret) +- goto fail; +- prg->chan[prg_chan].used_pre = 0; +- return 0; +- } +- +- for (i = 1; i < 3; i++) { +- ret = ipu_pre_get(prg->pres[i]); +- if (!ret) { +- u32 val, mux; +- int shift; +- +- prg->chan[prg_chan].used_pre = i; +- +- /* configure the PRE to PRG channel mux */ +- shift = (i == 1) ? 12 : 14; +- mux = (prg->id << 1) | (prg_chan - 1); +- regmap_update_bits(prg->iomuxc_gpr, IOMUXC_GPR5, +- 0x3 << shift, mux << shift); +- +- /* check other mux, must not point to same channel */ +- shift = (i == 1) ? 14 : 12; +- regmap_read(prg->iomuxc_gpr, IOMUXC_GPR5, &val); +- if (((val >> shift) & 0x3) == mux) { +- regmap_update_bits(prg->iomuxc_gpr, IOMUXC_GPR5, +- 0x3 << shift, +- (mux ^ 0x1) << shift); +- } +- +- return 0; +- } +- } +- +-fail: +- dev_err(prg->dev, "could not get PRE for PRG chan %d", prg_chan); +- return ret; +-} +- +-static void ipu_prg_put_pre(struct ipu_prg *prg, int prg_chan) +-{ +- struct ipu_prg_channel *chan = &prg->chan[prg_chan]; +- +- ipu_pre_put(prg->pres[chan->used_pre]); +- chan->used_pre = -1; +-} +- +-void ipu_prg_channel_disable(struct ipuv3_channel *ipu_chan) +-{ +- int prg_chan = ipu_prg_ipu_to_prg_chan(ipu_chan->num); +- struct ipu_prg *prg = ipu_chan->ipu->prg_priv; +- struct ipu_prg_channel *chan; +- u32 val; +- +- if (prg_chan < 0) +- return; +- +- chan = &prg->chan[prg_chan]; +- if (!chan->enabled) +- return; +- +- pm_runtime_get_sync(prg->dev); +- +- val = readl(prg->regs + IPU_PRG_CTL); +- val |= IPU_PRG_CTL_BYPASS(prg_chan); +- writel(val, prg->regs + IPU_PRG_CTL); +- +- val = IPU_PRG_REG_UPDATE_REG_UPDATE; +- writel(val, prg->regs + IPU_PRG_REG_UPDATE); +- +- pm_runtime_put(prg->dev); +- +- ipu_prg_put_pre(prg, prg_chan); +- +- chan->enabled = false; +-} +-EXPORT_SYMBOL_GPL(ipu_prg_channel_disable); +- +-int ipu_prg_channel_configure(struct ipuv3_channel *ipu_chan, +- unsigned int axi_id, unsigned int width, +- unsigned int height, unsigned int stride, +- u32 format, uint64_t modifier, unsigned long *eba) +-{ +- int prg_chan = ipu_prg_ipu_to_prg_chan(ipu_chan->num); +- struct ipu_prg *prg = ipu_chan->ipu->prg_priv; +- struct ipu_prg_channel *chan; +- u32 val; +- int ret; +- +- if (prg_chan < 0) +- return prg_chan; +- +- chan = &prg->chan[prg_chan]; +- +- if (chan->enabled) { +- ipu_pre_update(prg->pres[chan->used_pre], *eba); +- return 0; +- } +- +- ret = ipu_prg_get_pre(prg, prg_chan); +- if (ret) +- return ret; +- +- ipu_pre_configure(prg->pres[chan->used_pre], +- width, height, stride, format, modifier, *eba); +- +- +- pm_runtime_get_sync(prg->dev); +- +- val = (stride - 1) & IPU_PRG_STRIDE_STRIDE_MASK; +- writel(val, prg->regs + IPU_PRG_STRIDE(prg_chan)); +- +- val = ((height & IPU_PRG_HEIGHT_PRE_HEIGHT_MASK) << +- IPU_PRG_HEIGHT_PRE_HEIGHT_SHIFT) | +- ((height & IPU_PRG_HEIGHT_IPU_HEIGHT_MASK) << +- IPU_PRG_HEIGHT_IPU_HEIGHT_SHIFT); +- writel(val, prg->regs + IPU_PRG_HEIGHT(prg_chan)); +- +- val = ipu_pre_get_baddr(prg->pres[chan->used_pre]); +- *eba = val; +- writel(val, prg->regs + IPU_PRG_BADDR(prg_chan)); +- +- val = readl(prg->regs + IPU_PRG_CTL); +- /* config AXI ID */ +- val &= ~(IPU_PRG_CTL_SOFT_ARID_MASK << +- IPU_PRG_CTL_SOFT_ARID_SHIFT(prg_chan)); +- val |= IPU_PRG_CTL_SOFT_ARID(prg_chan, axi_id); +- /* enable channel */ +- val &= ~IPU_PRG_CTL_BYPASS(prg_chan); +- writel(val, prg->regs + IPU_PRG_CTL); +- +- val = IPU_PRG_REG_UPDATE_REG_UPDATE; +- writel(val, prg->regs + IPU_PRG_REG_UPDATE); +- +- /* wait for both double buffers to be filled */ +- readl_poll_timeout(prg->regs + IPU_PRG_STATUS, val, +- (val & IPU_PRG_STATUS_BUFFER0_READY(prg_chan)) && +- (val & IPU_PRG_STATUS_BUFFER1_READY(prg_chan)), +- 5, 1000); +- +- pm_runtime_put(prg->dev); +- +- chan->enabled = true; +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_prg_channel_configure); +- +-bool ipu_prg_channel_configure_pending(struct ipuv3_channel *ipu_chan) +-{ +- int prg_chan = ipu_prg_ipu_to_prg_chan(ipu_chan->num); +- struct ipu_prg *prg = ipu_chan->ipu->prg_priv; +- struct ipu_prg_channel *chan; +- +- if (prg_chan < 0) +- return false; +- +- chan = &prg->chan[prg_chan]; +- WARN_ON(!chan->enabled); +- +- return ipu_pre_update_pending(prg->pres[chan->used_pre]); +-} +-EXPORT_SYMBOL_GPL(ipu_prg_channel_configure_pending); +- +-static int ipu_prg_probe(struct platform_device *pdev) +-{ +- struct device *dev = &pdev->dev; +- struct resource *res; +- struct ipu_prg *prg; +- u32 val; +- int i, ret; +- +- prg = devm_kzalloc(dev, sizeof(*prg), GFP_KERNEL); +- if (!prg) +- return -ENOMEM; +- +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- prg->regs = devm_ioremap_resource(&pdev->dev, res); +- if (IS_ERR(prg->regs)) +- return PTR_ERR(prg->regs); +- +- +- prg->clk_ipg = devm_clk_get(dev, "ipg"); +- if (IS_ERR(prg->clk_ipg)) +- return PTR_ERR(prg->clk_ipg); +- +- prg->clk_axi = devm_clk_get(dev, "axi"); +- if (IS_ERR(prg->clk_axi)) +- return PTR_ERR(prg->clk_axi); +- +- prg->iomuxc_gpr = +- syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); +- if (IS_ERR(prg->iomuxc_gpr)) +- return PTR_ERR(prg->iomuxc_gpr); +- +- for (i = 0; i < 3; i++) { +- prg->pres[i] = ipu_pre_lookup_by_phandle(dev, "fsl,pres", i); +- if (!prg->pres[i]) +- return -EPROBE_DEFER; +- } +- +- ret = clk_prepare_enable(prg->clk_ipg); +- if (ret) +- return ret; +- +- ret = clk_prepare_enable(prg->clk_axi); +- if (ret) { +- clk_disable_unprepare(prg->clk_ipg); +- return ret; +- } +- +- /* init to free running mode */ +- val = readl(prg->regs + IPU_PRG_CTL); +- val |= IPU_PRG_CTL_SHADOW_EN; +- writel(val, prg->regs + IPU_PRG_CTL); +- +- /* disable address threshold */ +- writel(0xffffffff, prg->regs + IPU_PRG_THD); +- +- pm_runtime_set_active(dev); +- pm_runtime_enable(dev); +- +- prg->dev = dev; +- platform_set_drvdata(pdev, prg); +- mutex_lock(&ipu_prg_list_mutex); +- list_add(&prg->list, &ipu_prg_list); +- mutex_unlock(&ipu_prg_list_mutex); +- +- return 0; +-} +- +-static int ipu_prg_remove(struct platform_device *pdev) +-{ +- struct ipu_prg *prg = platform_get_drvdata(pdev); +- +- mutex_lock(&ipu_prg_list_mutex); +- list_del(&prg->list); +- mutex_unlock(&ipu_prg_list_mutex); +- +- return 0; +-} +- +-#ifdef CONFIG_PM +-static int prg_suspend(struct device *dev) +-{ +- struct ipu_prg *prg = dev_get_drvdata(dev); +- +- clk_disable_unprepare(prg->clk_axi); +- clk_disable_unprepare(prg->clk_ipg); +- +- return 0; +-} +- +-static int prg_resume(struct device *dev) +-{ +- struct ipu_prg *prg = dev_get_drvdata(dev); +- int ret; +- +- ret = clk_prepare_enable(prg->clk_ipg); +- if (ret) +- return ret; +- +- ret = clk_prepare_enable(prg->clk_axi); +- if (ret) { +- clk_disable_unprepare(prg->clk_ipg); +- return ret; +- } +- +- return 0; +-} +-#endif +- +-static const struct dev_pm_ops prg_pm_ops = { +- SET_RUNTIME_PM_OPS(prg_suspend, prg_resume, NULL) +-}; +- +-static const struct of_device_id ipu_prg_dt_ids[] = { +- { .compatible = "fsl,imx6qp-prg", }, +- { /* sentinel */ }, +-}; +- +-struct platform_driver ipu_prg_drv = { +- .probe = ipu_prg_probe, +- .remove = ipu_prg_remove, +- .driver = { +- .name = "imx-ipu-prg", +- .pm = &prg_pm_ops, +- .of_match_table = ipu_prg_dt_ids, +- }, +-}; +--- a/drivers/gpu/imx/ipu-v3/ipu-prv.h ++++ /dev/null +@@ -1,274 +0,0 @@ +-/* SPDX-License-Identifier: GPL-2.0-or-later */ +-/* +- * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> +- * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. +- */ +-#ifndef __IPU_PRV_H__ +-#define __IPU_PRV_H__ +- +-struct ipu_soc; +- +-#include <linux/types.h> +-#include <linux/device.h> +-#include <linux/clk.h> +-#include <linux/platform_device.h> +- +-#include <video/imx-ipu-v3.h> +- +-#define IPU_MCU_T_DEFAULT 8 +-#define IPU_CM_IDMAC_REG_OFS 0x00008000 +-#define IPU_CM_IC_REG_OFS 0x00020000 +-#define IPU_CM_IRT_REG_OFS 0x00028000 +-#define IPU_CM_CSI0_REG_OFS 0x00030000 +-#define IPU_CM_CSI1_REG_OFS 0x00038000 +-#define IPU_CM_SMFC_REG_OFS 0x00050000 +-#define IPU_CM_DC_REG_OFS 0x00058000 +-#define IPU_CM_DMFC_REG_OFS 0x00060000 +- +-/* Register addresses */ +-/* IPU Common registers */ +-#define IPU_CM_REG(offset) (offset) +- +-#define IPU_CONF IPU_CM_REG(0) +- +-#define IPU_SRM_PRI1 IPU_CM_REG(0x00a0) +-#define IPU_SRM_PRI2 IPU_CM_REG(0x00a4) +-#define IPU_FS_PROC_FLOW1 IPU_CM_REG(0x00a8) +-#define IPU_FS_PROC_FLOW2 IPU_CM_REG(0x00ac) +-#define IPU_FS_PROC_FLOW3 IPU_CM_REG(0x00b0) +-#define IPU_FS_DISP_FLOW1 IPU_CM_REG(0x00b4) +-#define IPU_FS_DISP_FLOW2 IPU_CM_REG(0x00b8) +-#define IPU_SKIP IPU_CM_REG(0x00bc) +-#define IPU_DISP_ALT_CONF IPU_CM_REG(0x00c0) +-#define IPU_DISP_GEN IPU_CM_REG(0x00c4) +-#define IPU_DISP_ALT1 IPU_CM_REG(0x00c8) +-#define IPU_DISP_ALT2 IPU_CM_REG(0x00cc) +-#define IPU_DISP_ALT3 IPU_CM_REG(0x00d0) +-#define IPU_DISP_ALT4 IPU_CM_REG(0x00d4) +-#define IPU_SNOOP IPU_CM_REG(0x00d8) +-#define IPU_MEM_RST IPU_CM_REG(0x00dc) +-#define IPU_PM IPU_CM_REG(0x00e0) +-#define IPU_GPR IPU_CM_REG(0x00e4) +-#define IPU_CHA_DB_MODE_SEL(ch) IPU_CM_REG(0x0150 + 4 * ((ch) / 32)) +-#define IPU_ALT_CHA_DB_MODE_SEL(ch) IPU_CM_REG(0x0168 + 4 * ((ch) / 32)) +-#define IPU_CHA_CUR_BUF(ch) IPU_CM_REG(0x023C + 4 * ((ch) / 32)) +-#define IPU_ALT_CUR_BUF0 IPU_CM_REG(0x0244) +-#define IPU_ALT_CUR_BUF1 IPU_CM_REG(0x0248) +-#define IPU_SRM_STAT IPU_CM_REG(0x024C) +-#define IPU_PROC_TASK_STAT IPU_CM_REG(0x0250) +-#define IPU_DISP_TASK_STAT IPU_CM_REG(0x0254) +-#define IPU_CHA_BUF0_RDY(ch) IPU_CM_REG(0x0268 + 4 * ((ch) / 32)) +-#define IPU_CHA_BUF1_RDY(ch) IPU_CM_REG(0x0270 + 4 * ((ch) / 32)) +-#define IPU_CHA_BUF2_RDY(ch) IPU_CM_REG(0x0288 + 4 * ((ch) / 32)) +-#define IPU_ALT_CHA_BUF0_RDY(ch) IPU_CM_REG(0x0278 + 4 * ((ch) / 32)) +-#define IPU_ALT_CHA_BUF1_RDY(ch) IPU_CM_REG(0x0280 + 4 * ((ch) / 32)) +- +-#define IPU_INT_CTRL(n) IPU_CM_REG(0x003C + 4 * (n)) +-#define IPU_INT_STAT(n) IPU_CM_REG(0x0200 + 4 * (n)) +- +-/* SRM_PRI2 */ +-#define DP_S_SRM_MODE_MASK (0x3 << 3) +-#define DP_S_SRM_MODE_NOW (0x3 << 3) +-#define DP_S_SRM_MODE_NEXT_FRAME (0x1 << 3) +- +-/* FS_PROC_FLOW1 */ +-#define FS_PRPENC_ROT_SRC_SEL_MASK (0xf << 0) +-#define FS_PRPENC_ROT_SRC_SEL_ENC (0x7 << 0) +-#define FS_PRPVF_ROT_SRC_SEL_MASK (0xf << 8) +-#define FS_PRPVF_ROT_SRC_SEL_VF (0x8 << 8) +-#define FS_PP_SRC_SEL_MASK (0xf << 12) +-#define FS_PP_ROT_SRC_SEL_MASK (0xf << 16) +-#define FS_PP_ROT_SRC_SEL_PP (0x5 << 16) +-#define FS_VDI1_SRC_SEL_MASK (0x3 << 20) +-#define FS_VDI3_SRC_SEL_MASK (0x3 << 20) +-#define FS_PRP_SRC_SEL_MASK (0xf << 24) +-#define FS_VDI_SRC_SEL_MASK (0x3 << 28) +-#define FS_VDI_SRC_SEL_CSI_DIRECT (0x1 << 28) +-#define FS_VDI_SRC_SEL_VDOA (0x2 << 28) +- +-/* FS_PROC_FLOW2 */ +-#define FS_PRP_ENC_DEST_SEL_MASK (0xf << 0) +-#define FS_PRP_ENC_DEST_SEL_IRT_ENC (0x1 << 0) +-#define FS_PRPVF_DEST_SEL_MASK (0xf << 4) +-#define FS_PRPVF_DEST_SEL_IRT_VF (0x1 << 4) +-#define FS_PRPVF_ROT_DEST_SEL_MASK (0xf << 8) +-#define FS_PP_DEST_SEL_MASK (0xf << 12) +-#define FS_PP_DEST_SEL_IRT_PP (0x3 << 12) +-#define FS_PP_ROT_DEST_SEL_MASK (0xf << 16) +-#define FS_PRPENC_ROT_DEST_SEL_MASK (0xf << 20) +-#define FS_PRP_DEST_SEL_MASK (0xf << 24) +- +-#define IPU_DI0_COUNTER_RELEASE (1 << 24) +-#define IPU_DI1_COUNTER_RELEASE (1 << 25) +- +-#define IPU_IDMAC_REG(offset) (offset) +- +-#define IDMAC_CONF IPU_IDMAC_REG(0x0000) +-#define IDMAC_CHA_EN(ch) IPU_IDMAC_REG(0x0004 + 4 * ((ch) / 32)) +-#define IDMAC_SEP_ALPHA IPU_IDMAC_REG(0x000c) +-#define IDMAC_ALT_SEP_ALPHA IPU_IDMAC_REG(0x0010) +-#define IDMAC_CHA_PRI(ch) IPU_IDMAC_REG(0x0014 + 4 * ((ch) / 32)) +-#define IDMAC_WM_EN(ch) IPU_IDMAC_REG(0x001c + 4 * ((ch) / 32)) +-#define IDMAC_CH_LOCK_EN_1 IPU_IDMAC_REG(0x0024) +-#define IDMAC_CH_LOCK_EN_2 IPU_IDMAC_REG(0x0028) +-#define IDMAC_SUB_ADDR_0 IPU_IDMAC_REG(0x002c) +-#define IDMAC_SUB_ADDR_1 IPU_IDMAC_REG(0x0030) +-#define IDMAC_SUB_ADDR_2 IPU_IDMAC_REG(0x0034) +-#define IDMAC_BAND_EN(ch) IPU_IDMAC_REG(0x0040 + 4 * ((ch) / 32)) +-#define IDMAC_CHA_BUSY(ch) IPU_IDMAC_REG(0x0100 + 4 * ((ch) / 32)) +- +-#define IPU_NUM_IRQS (32 * 15) +- +-enum ipu_modules { +- IPU_CONF_CSI0_EN = (1 << 0), +- IPU_CONF_CSI1_EN = (1 << 1), +- IPU_CONF_IC_EN = (1 << 2), +- IPU_CONF_ROT_EN = (1 << 3), +- IPU_CONF_ISP_EN = (1 << 4), +- IPU_CONF_DP_EN = (1 << 5), +- IPU_CONF_DI0_EN = (1 << 6), +- IPU_CONF_DI1_EN = (1 << 7), +- IPU_CONF_SMFC_EN = (1 << 8), +- IPU_CONF_DC_EN = (1 << 9), +- IPU_CONF_DMFC_EN = (1 << 10), +- +- IPU_CONF_VDI_EN = (1 << 12), +- +- IPU_CONF_IDMAC_DIS = (1 << 22), +- +- IPU_CONF_IC_DMFC_SEL = (1 << 25), +- IPU_CONF_IC_DMFC_SYNC = (1 << 26), +- IPU_CONF_VDI_DMFC_SYNC = (1 << 27), +- +- IPU_CONF_CSI0_DATA_SOURCE = (1 << 28), +- IPU_CONF_CSI1_DATA_SOURCE = (1 << 29), +- IPU_CONF_IC_INPUT = (1 << 30), +- IPU_CONF_CSI_SEL = (1 << 31), +-}; +- +-struct ipuv3_channel { +- unsigned int num; +- struct ipu_soc *ipu; +- struct list_head list; +-}; +- +-struct ipu_cpmem; +-struct ipu_csi; +-struct ipu_dc_priv; +-struct ipu_dmfc_priv; +-struct ipu_di; +-struct ipu_ic_priv; +-struct ipu_vdi; +-struct ipu_image_convert_priv; +-struct ipu_smfc_priv; +-struct ipu_pre; +-struct ipu_prg; +- +-struct ipu_devtype; +- +-struct ipu_soc { +- struct device *dev; +- const struct ipu_devtype *devtype; +- enum ipuv3_type ipu_type; +- spinlock_t lock; +- struct mutex channel_lock; +- struct list_head channels; +- +- void __iomem *cm_reg; +- void __iomem *idmac_reg; +- +- int id; +- int usecount; +- +- struct clk *clk; +- +- int irq_sync; +- int irq_err; +- struct irq_domain *domain; +- +- struct ipu_cpmem *cpmem_priv; +- struct ipu_dc_priv *dc_priv; +- struct ipu_dp_priv *dp_priv; +- struct ipu_dmfc_priv *dmfc_priv; +- struct ipu_di *di_priv[2]; +- struct ipu_csi *csi_priv[2]; +- struct ipu_ic_priv *ic_priv; +- struct ipu_vdi *vdi_priv; +- struct ipu_image_convert_priv *image_convert_priv; +- struct ipu_smfc_priv *smfc_priv; +- struct ipu_prg *prg_priv; +-}; +- +-static inline u32 ipu_idmac_read(struct ipu_soc *ipu, unsigned offset) +-{ +- return readl(ipu->idmac_reg + offset); +-} +- +-static inline void ipu_idmac_write(struct ipu_soc *ipu, u32 value, +- unsigned offset) +-{ +- writel(value, ipu->idmac_reg + offset); +-} +- +-void ipu_srm_dp_update(struct ipu_soc *ipu, bool sync); +- +-int ipu_module_enable(struct ipu_soc *ipu, u32 mask); +-int ipu_module_disable(struct ipu_soc *ipu, u32 mask); +- +-bool ipu_idmac_channel_busy(struct ipu_soc *ipu, unsigned int chno); +- +-int ipu_csi_init(struct ipu_soc *ipu, struct device *dev, int id, +- unsigned long base, u32 module, struct clk *clk_ipu); +-void ipu_csi_exit(struct ipu_soc *ipu, int id); +- +-int ipu_ic_init(struct ipu_soc *ipu, struct device *dev, +- unsigned long base, unsigned long tpmem_base); +-void ipu_ic_exit(struct ipu_soc *ipu); +- +-int ipu_vdi_init(struct ipu_soc *ipu, struct device *dev, +- unsigned long base, u32 module); +-void ipu_vdi_exit(struct ipu_soc *ipu); +- +-int ipu_image_convert_init(struct ipu_soc *ipu, struct device *dev); +-void ipu_image_convert_exit(struct ipu_soc *ipu); +- +-int ipu_di_init(struct ipu_soc *ipu, struct device *dev, int id, +- unsigned long base, u32 module, struct clk *ipu_clk); +-void ipu_di_exit(struct ipu_soc *ipu, int id); +- +-int ipu_dmfc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base, +- struct clk *ipu_clk); +-void ipu_dmfc_exit(struct ipu_soc *ipu); +- +-int ipu_dp_init(struct ipu_soc *ipu, struct device *dev, unsigned long base); +-void ipu_dp_exit(struct ipu_soc *ipu); +- +-int ipu_dc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base, +- unsigned long template_base); +-void ipu_dc_exit(struct ipu_soc *ipu); +- +-int ipu_cpmem_init(struct ipu_soc *ipu, struct device *dev, unsigned long base); +-void ipu_cpmem_exit(struct ipu_soc *ipu); +- +-int ipu_smfc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base); +-void ipu_smfc_exit(struct ipu_soc *ipu); +- +-struct ipu_pre *ipu_pre_lookup_by_phandle(struct device *dev, const char *name, +- int index); +-int ipu_pre_get_available_count(void); +-int ipu_pre_get(struct ipu_pre *pre); +-void ipu_pre_put(struct ipu_pre *pre); +-u32 ipu_pre_get_baddr(struct ipu_pre *pre); +-void ipu_pre_configure(struct ipu_pre *pre, unsigned int width, +- unsigned int height, unsigned int stride, u32 format, +- uint64_t modifier, unsigned int bufaddr); +-void ipu_pre_update(struct ipu_pre *pre, unsigned int bufaddr); +-bool ipu_pre_update_pending(struct ipu_pre *pre); +- +-struct ipu_prg *ipu_prg_lookup_by_phandle(struct device *dev, const char *name, +- int ipu_id); +- +-extern struct platform_driver ipu_pre_drv; +-extern struct platform_driver ipu_prg_drv; +- +-#endif /* __IPU_PRV_H__ */ +--- a/drivers/gpu/imx/ipu-v3/ipu-smfc.c ++++ /dev/null +@@ -1,202 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-or-later +-/* +- * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved. +- */ +-#include <linux/export.h> +-#include <linux/types.h> +-#include <linux/init.h> +-#include <linux/io.h> +-#include <linux/errno.h> +-#include <linux/spinlock.h> +-#include <linux/delay.h> +-#include <linux/clk.h> +-#include <video/imx-ipu-v3.h> +- +-#include "ipu-prv.h" +- +-struct ipu_smfc { +- struct ipu_smfc_priv *priv; +- int chno; +- bool inuse; +-}; +- +-struct ipu_smfc_priv { +- void __iomem *base; +- spinlock_t lock; +- struct ipu_soc *ipu; +- struct ipu_smfc channel[4]; +- int use_count; +-}; +- +-/*SMFC Registers */ +-#define SMFC_MAP 0x0000 +-#define SMFC_WMC 0x0004 +-#define SMFC_BS 0x0008 +- +-int ipu_smfc_set_burstsize(struct ipu_smfc *smfc, int burstsize) +-{ +- struct ipu_smfc_priv *priv = smfc->priv; +- unsigned long flags; +- u32 val, shift; +- +- spin_lock_irqsave(&priv->lock, flags); +- +- shift = smfc->chno * 4; +- val = readl(priv->base + SMFC_BS); +- val &= ~(0xf << shift); +- val |= burstsize << shift; +- writel(val, priv->base + SMFC_BS); +- +- spin_unlock_irqrestore(&priv->lock, flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_smfc_set_burstsize); +- +-int ipu_smfc_map_channel(struct ipu_smfc *smfc, int csi_id, int mipi_id) +-{ +- struct ipu_smfc_priv *priv = smfc->priv; +- unsigned long flags; +- u32 val, shift; +- +- spin_lock_irqsave(&priv->lock, flags); +- +- shift = smfc->chno * 3; +- val = readl(priv->base + SMFC_MAP); +- val &= ~(0x7 << shift); +- val |= ((csi_id << 2) | mipi_id) << shift; +- writel(val, priv->base + SMFC_MAP); +- +- spin_unlock_irqrestore(&priv->lock, flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_smfc_map_channel); +- +-int ipu_smfc_set_watermark(struct ipu_smfc *smfc, u32 set_level, u32 clr_level) +-{ +- struct ipu_smfc_priv *priv = smfc->priv; +- unsigned long flags; +- u32 val, shift; +- +- spin_lock_irqsave(&priv->lock, flags); +- +- shift = smfc->chno * 6 + (smfc->chno > 1 ? 4 : 0); +- val = readl(priv->base + SMFC_WMC); +- val &= ~(0x3f << shift); +- val |= ((clr_level << 3) | set_level) << shift; +- writel(val, priv->base + SMFC_WMC); +- +- spin_unlock_irqrestore(&priv->lock, flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_smfc_set_watermark); +- +-int ipu_smfc_enable(struct ipu_smfc *smfc) +-{ +- struct ipu_smfc_priv *priv = smfc->priv; +- unsigned long flags; +- +- spin_lock_irqsave(&priv->lock, flags); +- +- if (!priv->use_count) +- ipu_module_enable(priv->ipu, IPU_CONF_SMFC_EN); +- +- priv->use_count++; +- +- spin_unlock_irqrestore(&priv->lock, flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_smfc_enable); +- +-int ipu_smfc_disable(struct ipu_smfc *smfc) +-{ +- struct ipu_smfc_priv *priv = smfc->priv; +- unsigned long flags; +- +- spin_lock_irqsave(&priv->lock, flags); +- +- priv->use_count--; +- +- if (!priv->use_count) +- ipu_module_disable(priv->ipu, IPU_CONF_SMFC_EN); +- +- if (priv->use_count < 0) +- priv->use_count = 0; +- +- spin_unlock_irqrestore(&priv->lock, flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_smfc_disable); +- +-struct ipu_smfc *ipu_smfc_get(struct ipu_soc *ipu, unsigned int chno) +-{ +- struct ipu_smfc_priv *priv = ipu->smfc_priv; +- struct ipu_smfc *smfc, *ret; +- unsigned long flags; +- +- if (chno >= 4) +- return ERR_PTR(-EINVAL); +- +- smfc = &priv->channel[chno]; +- ret = smfc; +- +- spin_lock_irqsave(&priv->lock, flags); +- +- if (smfc->inuse) { +- ret = ERR_PTR(-EBUSY); +- goto unlock; +- } +- +- smfc->inuse = true; +-unlock: +- spin_unlock_irqrestore(&priv->lock, flags); +- return ret; +-} +-EXPORT_SYMBOL_GPL(ipu_smfc_get); +- +-void ipu_smfc_put(struct ipu_smfc *smfc) +-{ +- struct ipu_smfc_priv *priv = smfc->priv; +- unsigned long flags; +- +- spin_lock_irqsave(&priv->lock, flags); +- smfc->inuse = false; +- spin_unlock_irqrestore(&priv->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_smfc_put); +- +-int ipu_smfc_init(struct ipu_soc *ipu, struct device *dev, +- unsigned long base) +-{ +- struct ipu_smfc_priv *priv; +- int i; +- +- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); +- if (!priv) +- return -ENOMEM; +- +- ipu->smfc_priv = priv; +- spin_lock_init(&priv->lock); +- priv->ipu = ipu; +- +- priv->base = devm_ioremap(dev, base, PAGE_SIZE); +- if (!priv->base) +- return -ENOMEM; +- +- for (i = 0; i < 4; i++) { +- priv->channel[i].priv = priv; +- priv->channel[i].chno = i; +- } +- +- pr_debug("%s: ioremap 0x%08lx -> %p\n", __func__, base, priv->base); +- +- return 0; +-} +- +-void ipu_smfc_exit(struct ipu_soc *ipu) +-{ +-} +--- a/drivers/gpu/imx/ipu-v3/ipu-vdi.c ++++ /dev/null +@@ -1,234 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0-or-later +-/* +- * Copyright (C) 2012-2016 Mentor Graphics Inc. +- * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. +- */ +-#include <linux/io.h> +-#include "ipu-prv.h" +- +-struct ipu_vdi { +- void __iomem *base; +- u32 module; +- spinlock_t lock; +- int use_count; +- struct ipu_soc *ipu; +-}; +- +- +-/* VDI Register Offsets */ +-#define VDI_FSIZE 0x0000 +-#define VDI_C 0x0004 +- +-/* VDI Register Fields */ +-#define VDI_C_CH_420 (0 << 1) +-#define VDI_C_CH_422 (1 << 1) +-#define VDI_C_MOT_SEL_MASK (0x3 << 2) +-#define VDI_C_MOT_SEL_FULL (2 << 2) +-#define VDI_C_MOT_SEL_LOW (1 << 2) +-#define VDI_C_MOT_SEL_MED (0 << 2) +-#define VDI_C_BURST_SIZE1_4 (3 << 4) +-#define VDI_C_BURST_SIZE2_4 (3 << 8) +-#define VDI_C_BURST_SIZE3_4 (3 << 12) +-#define VDI_C_BURST_SIZE_MASK 0xF +-#define VDI_C_BURST_SIZE1_OFFSET 4 +-#define VDI_C_BURST_SIZE2_OFFSET 8 +-#define VDI_C_BURST_SIZE3_OFFSET 12 +-#define VDI_C_VWM1_SET_1 (0 << 16) +-#define VDI_C_VWM1_SET_2 (1 << 16) +-#define VDI_C_VWM1_CLR_2 (1 << 19) +-#define VDI_C_VWM3_SET_1 (0 << 22) +-#define VDI_C_VWM3_SET_2 (1 << 22) +-#define VDI_C_VWM3_CLR_2 (1 << 25) +-#define VDI_C_TOP_FIELD_MAN_1 (1 << 30) +-#define VDI_C_TOP_FIELD_AUTO_1 (1 << 31) +- +-static inline u32 ipu_vdi_read(struct ipu_vdi *vdi, unsigned int offset) +-{ +- return readl(vdi->base + offset); +-} +- +-static inline void ipu_vdi_write(struct ipu_vdi *vdi, u32 value, +- unsigned int offset) +-{ +- writel(value, vdi->base + offset); +-} +- +-void ipu_vdi_set_field_order(struct ipu_vdi *vdi, v4l2_std_id std, u32 field) +-{ +- bool top_field_0 = false; +- unsigned long flags; +- u32 reg; +- +- switch (field) { +- case V4L2_FIELD_INTERLACED_TB: +- case V4L2_FIELD_SEQ_TB: +- case V4L2_FIELD_TOP: +- top_field_0 = true; +- break; +- case V4L2_FIELD_INTERLACED_BT: +- case V4L2_FIELD_SEQ_BT: +- case V4L2_FIELD_BOTTOM: +- top_field_0 = false; +- break; +- default: +- top_field_0 = (std & V4L2_STD_525_60) ? true : false; +- break; +- } +- +- spin_lock_irqsave(&vdi->lock, flags); +- +- reg = ipu_vdi_read(vdi, VDI_C); +- if (top_field_0) +- reg &= ~(VDI_C_TOP_FIELD_MAN_1 | VDI_C_TOP_FIELD_AUTO_1); +- else +- reg |= VDI_C_TOP_FIELD_MAN_1 | VDI_C_TOP_FIELD_AUTO_1; +- ipu_vdi_write(vdi, reg, VDI_C); +- +- spin_unlock_irqrestore(&vdi->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_vdi_set_field_order); +- +-void ipu_vdi_set_motion(struct ipu_vdi *vdi, enum ipu_motion_sel motion_sel) +-{ +- unsigned long flags; +- u32 reg; +- +- spin_lock_irqsave(&vdi->lock, flags); +- +- reg = ipu_vdi_read(vdi, VDI_C); +- +- reg &= ~VDI_C_MOT_SEL_MASK; +- +- switch (motion_sel) { +- case MED_MOTION: +- reg |= VDI_C_MOT_SEL_MED; +- break; +- case HIGH_MOTION: +- reg |= VDI_C_MOT_SEL_FULL; +- break; +- default: +- reg |= VDI_C_MOT_SEL_LOW; +- break; +- } +- +- ipu_vdi_write(vdi, reg, VDI_C); +- +- spin_unlock_irqrestore(&vdi->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_vdi_set_motion); +- +-void ipu_vdi_setup(struct ipu_vdi *vdi, u32 code, int xres, int yres) +-{ +- unsigned long flags; +- u32 pixel_fmt, reg; +- +- spin_lock_irqsave(&vdi->lock, flags); +- +- reg = ((yres - 1) << 16) | (xres - 1); +- ipu_vdi_write(vdi, reg, VDI_FSIZE); +- +- /* +- * Full motion, only vertical filter is used. +- * Burst size is 4 accesses +- */ +- if (code == MEDIA_BUS_FMT_UYVY8_2X8 || +- code == MEDIA_BUS_FMT_UYVY8_1X16 || +- code == MEDIA_BUS_FMT_YUYV8_2X8 || +- code == MEDIA_BUS_FMT_YUYV8_1X16) +- pixel_fmt = VDI_C_CH_422; +- else +- pixel_fmt = VDI_C_CH_420; +- +- reg = ipu_vdi_read(vdi, VDI_C); +- reg |= pixel_fmt; +- reg |= VDI_C_BURST_SIZE2_4; +- reg |= VDI_C_BURST_SIZE1_4 | VDI_C_VWM1_CLR_2; +- reg |= VDI_C_BURST_SIZE3_4 | VDI_C_VWM3_CLR_2; +- ipu_vdi_write(vdi, reg, VDI_C); +- +- spin_unlock_irqrestore(&vdi->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_vdi_setup); +- +-void ipu_vdi_unsetup(struct ipu_vdi *vdi) +-{ +- unsigned long flags; +- +- spin_lock_irqsave(&vdi->lock, flags); +- ipu_vdi_write(vdi, 0, VDI_FSIZE); +- ipu_vdi_write(vdi, 0, VDI_C); +- spin_unlock_irqrestore(&vdi->lock, flags); +-} +-EXPORT_SYMBOL_GPL(ipu_vdi_unsetup); +- +-int ipu_vdi_enable(struct ipu_vdi *vdi) +-{ +- unsigned long flags; +- +- spin_lock_irqsave(&vdi->lock, flags); +- +- if (!vdi->use_count) +- ipu_module_enable(vdi->ipu, vdi->module); +- +- vdi->use_count++; +- +- spin_unlock_irqrestore(&vdi->lock, flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_vdi_enable); +- +-int ipu_vdi_disable(struct ipu_vdi *vdi) +-{ +- unsigned long flags; +- +- spin_lock_irqsave(&vdi->lock, flags); +- +- if (vdi->use_count) { +- if (!--vdi->use_count) +- ipu_module_disable(vdi->ipu, vdi->module); +- } +- +- spin_unlock_irqrestore(&vdi->lock, flags); +- +- return 0; +-} +-EXPORT_SYMBOL_GPL(ipu_vdi_disable); +- +-struct ipu_vdi *ipu_vdi_get(struct ipu_soc *ipu) +-{ +- return ipu->vdi_priv; +-} +-EXPORT_SYMBOL_GPL(ipu_vdi_get); +- +-void ipu_vdi_put(struct ipu_vdi *vdi) +-{ +-} +-EXPORT_SYMBOL_GPL(ipu_vdi_put); +- +-int ipu_vdi_init(struct ipu_soc *ipu, struct device *dev, +- unsigned long base, u32 module) +-{ +- struct ipu_vdi *vdi; +- +- vdi = devm_kzalloc(dev, sizeof(*vdi), GFP_KERNEL); +- if (!vdi) +- return -ENOMEM; +- +- ipu->vdi_priv = vdi; +- +- spin_lock_init(&vdi->lock); +- vdi->module = module; +- vdi->base = devm_ioremap(dev, base, PAGE_SIZE); +- if (!vdi->base) +- return -ENOMEM; +- +- dev_dbg(dev, "VDI base: 0x%08lx remapped to %p\n", base, vdi->base); +- vdi->ipu = ipu; +- +- return 0; +-} +- +-void ipu_vdi_exit(struct ipu_soc *ipu) +-{ +-} +--- /dev/null ++++ b/drivers/gpu/ipu-v3/Kconfig +@@ -0,0 +1,11 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++config IMX_IPUV3_CORE ++ tristate "IPUv3 core support" ++ depends on SOC_IMX5 || SOC_IMX6Q || ARCH_MULTIPLATFORM || COMPILE_TEST ++ depends on DRM || !DRM # if DRM=m, this can't be 'y' ++ select BITREVERSE ++ select GENERIC_ALLOCATOR if DRM ++ select GENERIC_IRQ_CHIP ++ help ++ Choose this if you have a i.MX5/6 system and want to use the Image ++ Processing Unit. This option only enables IPU base support. +--- /dev/null ++++ b/drivers/gpu/ipu-v3/Makefile +@@ -0,0 +1,10 @@ ++# SPDX-License-Identifier: GPL-2.0 ++obj-$(CONFIG_IMX_IPUV3_CORE) += imx-ipu-v3.o ++ ++imx-ipu-v3-objs := ipu-common.o ipu-cpmem.o ipu-csi.o ipu-dc.o ipu-di.o \ ++ ipu-dp.o ipu-dmfc.o ipu-ic.o ipu-ic-csc.o \ ++ ipu-image-convert.o ipu-smfc.o ipu-vdi.o ++ ++ifdef CONFIG_DRM ++ imx-ipu-v3-objs += ipu-pre.o ipu-prg.o ++endif +--- /dev/null ++++ b/drivers/gpu/ipu-v3/ipu-common.c +@@ -0,0 +1,1565 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> ++ * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. ++ */ ++#include <linux/module.h> ++#include <linux/export.h> ++#include <linux/types.h> ++#include <linux/reset.h> ++#include <linux/platform_device.h> ++#include <linux/err.h> ++#include <linux/spinlock.h> ++#include <linux/delay.h> ++#include <linux/interrupt.h> ++#include <linux/io.h> ++#include <linux/clk.h> ++#include <linux/list.h> ++#include <linux/irq.h> ++#include <linux/irqchip/chained_irq.h> ++#include <linux/irqdomain.h> ++#include <linux/of_device.h> ++#include <linux/of_graph.h> ++ ++#include <drm/drm_fourcc.h> ++ ++#include <video/imx-ipu-v3.h> ++#include "ipu-prv.h" ++ ++static inline u32 ipu_cm_read(struct ipu_soc *ipu, unsigned offset) ++{ ++ return readl(ipu->cm_reg + offset); ++} ++ ++static inline void ipu_cm_write(struct ipu_soc *ipu, u32 value, unsigned offset) ++{ ++ writel(value, ipu->cm_reg + offset); ++} ++ ++int ipu_get_num(struct ipu_soc *ipu) ++{ ++ return ipu->id; ++} ++EXPORT_SYMBOL_GPL(ipu_get_num); ++ ++void ipu_srm_dp_update(struct ipu_soc *ipu, bool sync) ++{ ++ u32 val; ++ ++ val = ipu_cm_read(ipu, IPU_SRM_PRI2); ++ val &= ~DP_S_SRM_MODE_MASK; ++ val |= sync ? DP_S_SRM_MODE_NEXT_FRAME : ++ DP_S_SRM_MODE_NOW; ++ ipu_cm_write(ipu, val, IPU_SRM_PRI2); ++} ++EXPORT_SYMBOL_GPL(ipu_srm_dp_update); ++ ++enum ipu_color_space ipu_drm_fourcc_to_colorspace(u32 drm_fourcc) ++{ ++ switch (drm_fourcc) { ++ case DRM_FORMAT_ARGB1555: ++ case DRM_FORMAT_ABGR1555: ++ case DRM_FORMAT_RGBA5551: ++ case DRM_FORMAT_BGRA5551: ++ case DRM_FORMAT_RGB565: ++ case DRM_FORMAT_BGR565: ++ case DRM_FORMAT_RGB888: ++ case DRM_FORMAT_BGR888: ++ case DRM_FORMAT_ARGB4444: ++ case DRM_FORMAT_XRGB8888: ++ case DRM_FORMAT_XBGR8888: ++ case DRM_FORMAT_RGBX8888: ++ case DRM_FORMAT_BGRX8888: ++ case DRM_FORMAT_ARGB8888: ++ case DRM_FORMAT_ABGR8888: ++ case DRM_FORMAT_RGBA8888: ++ case DRM_FORMAT_BGRA8888: ++ case DRM_FORMAT_RGB565_A8: ++ case DRM_FORMAT_BGR565_A8: ++ case DRM_FORMAT_RGB888_A8: ++ case DRM_FORMAT_BGR888_A8: ++ case DRM_FORMAT_RGBX8888_A8: ++ case DRM_FORMAT_BGRX8888_A8: ++ return IPUV3_COLORSPACE_RGB; ++ case DRM_FORMAT_YUYV: ++ case DRM_FORMAT_UYVY: ++ case DRM_FORMAT_YUV420: ++ case DRM_FORMAT_YVU420: ++ case DRM_FORMAT_YUV422: ++ case DRM_FORMAT_YVU422: ++ case DRM_FORMAT_YUV444: ++ case DRM_FORMAT_YVU444: ++ case DRM_FORMAT_NV12: ++ case DRM_FORMAT_NV21: ++ case DRM_FORMAT_NV16: ++ case DRM_FORMAT_NV61: ++ return IPUV3_COLORSPACE_YUV; ++ default: ++ return IPUV3_COLORSPACE_UNKNOWN; ++ } ++} ++EXPORT_SYMBOL_GPL(ipu_drm_fourcc_to_colorspace); ++ ++enum ipu_color_space ipu_pixelformat_to_colorspace(u32 pixelformat) ++{ ++ switch (pixelformat) { ++ case V4L2_PIX_FMT_YUV420: ++ case V4L2_PIX_FMT_YVU420: ++ case V4L2_PIX_FMT_YUV422P: ++ case V4L2_PIX_FMT_UYVY: ++ case V4L2_PIX_FMT_YUYV: ++ case V4L2_PIX_FMT_NV12: ++ case V4L2_PIX_FMT_NV21: ++ case V4L2_PIX_FMT_NV16: ++ case V4L2_PIX_FMT_NV61: ++ return IPUV3_COLORSPACE_YUV; ++ case V4L2_PIX_FMT_RGB565: ++ case V4L2_PIX_FMT_BGR24: ++ case V4L2_PIX_FMT_RGB24: ++ case V4L2_PIX_FMT_ABGR32: ++ case V4L2_PIX_FMT_XBGR32: ++ case V4L2_PIX_FMT_BGRA32: ++ case V4L2_PIX_FMT_BGRX32: ++ case V4L2_PIX_FMT_RGBA32: ++ case V4L2_PIX_FMT_RGBX32: ++ case V4L2_PIX_FMT_ARGB32: ++ case V4L2_PIX_FMT_XRGB32: ++ return IPUV3_COLORSPACE_RGB; ++ default: ++ return IPUV3_COLORSPACE_UNKNOWN; ++ } ++} ++EXPORT_SYMBOL_GPL(ipu_pixelformat_to_colorspace); ++ ++bool ipu_pixelformat_is_planar(u32 pixelformat) ++{ ++ switch (pixelformat) { ++ case V4L2_PIX_FMT_YUV420: ++ case V4L2_PIX_FMT_YVU420: ++ case V4L2_PIX_FMT_YUV422P: ++ case V4L2_PIX_FMT_NV12: ++ case V4L2_PIX_FMT_NV21: ++ case V4L2_PIX_FMT_NV16: ++ case V4L2_PIX_FMT_NV61: ++ return true; ++ } ++ ++ return false; ++} ++EXPORT_SYMBOL_GPL(ipu_pixelformat_is_planar); ++ ++enum ipu_color_space ipu_mbus_code_to_colorspace(u32 mbus_code) ++{ ++ switch (mbus_code & 0xf000) { ++ case 0x1000: ++ return IPUV3_COLORSPACE_RGB; ++ case 0x2000: ++ return IPUV3_COLORSPACE_YUV; ++ default: ++ return IPUV3_COLORSPACE_UNKNOWN; ++ } ++} ++EXPORT_SYMBOL_GPL(ipu_mbus_code_to_colorspace); ++ ++int ipu_stride_to_bytes(u32 pixel_stride, u32 pixelformat) ++{ ++ switch (pixelformat) { ++ case V4L2_PIX_FMT_YUV420: ++ case V4L2_PIX_FMT_YVU420: ++ case V4L2_PIX_FMT_YUV422P: ++ case V4L2_PIX_FMT_NV12: ++ case V4L2_PIX_FMT_NV21: ++ case V4L2_PIX_FMT_NV16: ++ case V4L2_PIX_FMT_NV61: ++ /* ++ * for the planar YUV formats, the stride passed to ++ * cpmem must be the stride in bytes of the Y plane. ++ * And all the planar YUV formats have an 8-bit ++ * Y component. ++ */ ++ return (8 * pixel_stride) >> 3; ++ case V4L2_PIX_FMT_RGB565: ++ case V4L2_PIX_FMT_YUYV: ++ case V4L2_PIX_FMT_UYVY: ++ return (16 * pixel_stride) >> 3; ++ case V4L2_PIX_FMT_BGR24: ++ case V4L2_PIX_FMT_RGB24: ++ return (24 * pixel_stride) >> 3; ++ case V4L2_PIX_FMT_BGR32: ++ case V4L2_PIX_FMT_RGB32: ++ case V4L2_PIX_FMT_XBGR32: ++ case V4L2_PIX_FMT_XRGB32: ++ return (32 * pixel_stride) >> 3; ++ default: ++ break; ++ } ++ ++ return -EINVAL; ++} ++EXPORT_SYMBOL_GPL(ipu_stride_to_bytes); ++ ++int ipu_degrees_to_rot_mode(enum ipu_rotate_mode *mode, int degrees, ++ bool hflip, bool vflip) ++{ ++ u32 r90, vf, hf; ++ ++ switch (degrees) { ++ case 0: ++ vf = hf = r90 = 0; ++ break; ++ case 90: ++ vf = hf = 0; ++ r90 = 1; ++ break; ++ case 180: ++ vf = hf = 1; ++ r90 = 0; ++ break; ++ case 270: ++ vf = hf = r90 = 1; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ hf ^= (u32)hflip; ++ vf ^= (u32)vflip; ++ ++ *mode = (enum ipu_rotate_mode)((r90 << 2) | (hf << 1) | vf); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_degrees_to_rot_mode); ++ ++int ipu_rot_mode_to_degrees(int *degrees, enum ipu_rotate_mode mode, ++ bool hflip, bool vflip) ++{ ++ u32 r90, vf, hf; ++ ++ r90 = ((u32)mode >> 2) & 0x1; ++ hf = ((u32)mode >> 1) & 0x1; ++ vf = ((u32)mode >> 0) & 0x1; ++ hf ^= (u32)hflip; ++ vf ^= (u32)vflip; ++ ++ switch ((enum ipu_rotate_mode)((r90 << 2) | (hf << 1) | vf)) { ++ case IPU_ROTATE_NONE: ++ *degrees = 0; ++ break; ++ case IPU_ROTATE_90_RIGHT: ++ *degrees = 90; ++ break; ++ case IPU_ROTATE_180: ++ *degrees = 180; ++ break; ++ case IPU_ROTATE_90_LEFT: ++ *degrees = 270; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_rot_mode_to_degrees); ++ ++struct ipuv3_channel *ipu_idmac_get(struct ipu_soc *ipu, unsigned num) ++{ ++ struct ipuv3_channel *channel; ++ ++ dev_dbg(ipu->dev, "%s %d\n", __func__, num); ++ ++ if (num > 63) ++ return ERR_PTR(-ENODEV); ++ ++ mutex_lock(&ipu->channel_lock); ++ ++ list_for_each_entry(channel, &ipu->channels, list) { ++ if (channel->num == num) { ++ channel = ERR_PTR(-EBUSY); ++ goto out; ++ } ++ } ++ ++ channel = kzalloc(sizeof(*channel), GFP_KERNEL); ++ if (!channel) { ++ channel = ERR_PTR(-ENOMEM); ++ goto out; ++ } ++ ++ channel->num = num; ++ channel->ipu = ipu; ++ list_add(&channel->list, &ipu->channels); ++ ++out: ++ mutex_unlock(&ipu->channel_lock); ++ ++ return channel; ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_get); ++ ++void ipu_idmac_put(struct ipuv3_channel *channel) ++{ ++ struct ipu_soc *ipu = channel->ipu; ++ ++ dev_dbg(ipu->dev, "%s %d\n", __func__, channel->num); ++ ++ mutex_lock(&ipu->channel_lock); ++ ++ list_del(&channel->list); ++ kfree(channel); ++ ++ mutex_unlock(&ipu->channel_lock); ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_put); ++ ++#define idma_mask(ch) (1 << ((ch) & 0x1f)) ++ ++/* ++ * This is an undocumented feature, a write one to a channel bit in ++ * IPU_CHA_CUR_BUF and IPU_CHA_TRIPLE_CUR_BUF will reset the channel's ++ * internal current buffer pointer so that transfers start from buffer ++ * 0 on the next channel enable (that's the theory anyway, the imx6 TRM ++ * only says these are read-only registers). This operation is required ++ * for channel linking to work correctly, for instance video capture ++ * pipelines that carry out image rotations will fail after the first ++ * streaming unless this function is called for each channel before ++ * re-enabling the channels. ++ */ ++static void __ipu_idmac_reset_current_buffer(struct ipuv3_channel *channel) ++{ ++ struct ipu_soc *ipu = channel->ipu; ++ unsigned int chno = channel->num; ++ ++ ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_CUR_BUF(chno)); ++} ++ ++void ipu_idmac_set_double_buffer(struct ipuv3_channel *channel, ++ bool doublebuffer) ++{ ++ struct ipu_soc *ipu = channel->ipu; ++ unsigned long flags; ++ u32 reg; ++ ++ spin_lock_irqsave(&ipu->lock, flags); ++ ++ reg = ipu_cm_read(ipu, IPU_CHA_DB_MODE_SEL(channel->num)); ++ if (doublebuffer) ++ reg |= idma_mask(channel->num); ++ else ++ reg &= ~idma_mask(channel->num); ++ ipu_cm_write(ipu, reg, IPU_CHA_DB_MODE_SEL(channel->num)); ++ ++ __ipu_idmac_reset_current_buffer(channel); ++ ++ spin_unlock_irqrestore(&ipu->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_set_double_buffer); ++ ++static const struct { ++ int chnum; ++ u32 reg; ++ int shift; ++} idmac_lock_en_info[] = { ++ { .chnum = 5, .reg = IDMAC_CH_LOCK_EN_1, .shift = 0, }, ++ { .chnum = 11, .reg = IDMAC_CH_LOCK_EN_1, .shift = 2, }, ++ { .chnum = 12, .reg = IDMAC_CH_LOCK_EN_1, .shift = 4, }, ++ { .chnum = 14, .reg = IDMAC_CH_LOCK_EN_1, .shift = 6, }, ++ { .chnum = 15, .reg = IDMAC_CH_LOCK_EN_1, .shift = 8, }, ++ { .chnum = 20, .reg = IDMAC_CH_LOCK_EN_1, .shift = 10, }, ++ { .chnum = 21, .reg = IDMAC_CH_LOCK_EN_1, .shift = 12, }, ++ { .chnum = 22, .reg = IDMAC_CH_LOCK_EN_1, .shift = 14, }, ++ { .chnum = 23, .reg = IDMAC_CH_LOCK_EN_1, .shift = 16, }, ++ { .chnum = 27, .reg = IDMAC_CH_LOCK_EN_1, .shift = 18, }, ++ { .chnum = 28, .reg = IDMAC_CH_LOCK_EN_1, .shift = 20, }, ++ { .chnum = 45, .reg = IDMAC_CH_LOCK_EN_2, .shift = 0, }, ++ { .chnum = 46, .reg = IDMAC_CH_LOCK_EN_2, .shift = 2, }, ++ { .chnum = 47, .reg = IDMAC_CH_LOCK_EN_2, .shift = 4, }, ++ { .chnum = 48, .reg = IDMAC_CH_LOCK_EN_2, .shift = 6, }, ++ { .chnum = 49, .reg = IDMAC_CH_LOCK_EN_2, .shift = 8, }, ++ { .chnum = 50, .reg = IDMAC_CH_LOCK_EN_2, .shift = 10, }, ++}; ++ ++int ipu_idmac_lock_enable(struct ipuv3_channel *channel, int num_bursts) ++{ ++ struct ipu_soc *ipu = channel->ipu; ++ unsigned long flags; ++ u32 bursts, regval; ++ int i; ++ ++ switch (num_bursts) { ++ case 0: ++ case 1: ++ bursts = 0x00; /* locking disabled */ ++ break; ++ case 2: ++ bursts = 0x01; ++ break; ++ case 4: ++ bursts = 0x02; ++ break; ++ case 8: ++ bursts = 0x03; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ /* ++ * IPUv3EX / i.MX51 has a different register layout, and on IPUv3M / ++ * i.MX53 channel arbitration locking doesn't seem to work properly. ++ * Allow enabling the lock feature on IPUv3H / i.MX6 only. ++ */ ++ if (bursts && ipu->ipu_type != IPUV3H) ++ return -EINVAL; ++ ++ for (i = 0; i < ARRAY_SIZE(idmac_lock_en_info); i++) { ++ if (channel->num == idmac_lock_en_info[i].chnum) ++ break; ++ } ++ if (i >= ARRAY_SIZE(idmac_lock_en_info)) ++ return -EINVAL; ++ ++ spin_lock_irqsave(&ipu->lock, flags); ++ ++ regval = ipu_idmac_read(ipu, idmac_lock_en_info[i].reg); ++ regval &= ~(0x03 << idmac_lock_en_info[i].shift); ++ regval |= (bursts << idmac_lock_en_info[i].shift); ++ ipu_idmac_write(ipu, regval, idmac_lock_en_info[i].reg); ++ ++ spin_unlock_irqrestore(&ipu->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_lock_enable); ++ ++int ipu_module_enable(struct ipu_soc *ipu, u32 mask) ++{ ++ unsigned long lock_flags; ++ u32 val; ++ ++ spin_lock_irqsave(&ipu->lock, lock_flags); ++ ++ val = ipu_cm_read(ipu, IPU_DISP_GEN); ++ ++ if (mask & IPU_CONF_DI0_EN) ++ val |= IPU_DI0_COUNTER_RELEASE; ++ if (mask & IPU_CONF_DI1_EN) ++ val |= IPU_DI1_COUNTER_RELEASE; ++ ++ ipu_cm_write(ipu, val, IPU_DISP_GEN); ++ ++ val = ipu_cm_read(ipu, IPU_CONF); ++ val |= mask; ++ ipu_cm_write(ipu, val, IPU_CONF); ++ ++ spin_unlock_irqrestore(&ipu->lock, lock_flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_module_enable); ++ ++int ipu_module_disable(struct ipu_soc *ipu, u32 mask) ++{ ++ unsigned long lock_flags; ++ u32 val; ++ ++ spin_lock_irqsave(&ipu->lock, lock_flags); ++ ++ val = ipu_cm_read(ipu, IPU_CONF); ++ val &= ~mask; ++ ipu_cm_write(ipu, val, IPU_CONF); ++ ++ val = ipu_cm_read(ipu, IPU_DISP_GEN); ++ ++ if (mask & IPU_CONF_DI0_EN) ++ val &= ~IPU_DI0_COUNTER_RELEASE; ++ if (mask & IPU_CONF_DI1_EN) ++ val &= ~IPU_DI1_COUNTER_RELEASE; ++ ++ ipu_cm_write(ipu, val, IPU_DISP_GEN); ++ ++ spin_unlock_irqrestore(&ipu->lock, lock_flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_module_disable); ++ ++int ipu_idmac_get_current_buffer(struct ipuv3_channel *channel) ++{ ++ struct ipu_soc *ipu = channel->ipu; ++ unsigned int chno = channel->num; ++ ++ return (ipu_cm_read(ipu, IPU_CHA_CUR_BUF(chno)) & idma_mask(chno)) ? 1 : 0; ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_get_current_buffer); ++ ++bool ipu_idmac_buffer_is_ready(struct ipuv3_channel *channel, u32 buf_num) ++{ ++ struct ipu_soc *ipu = channel->ipu; ++ unsigned long flags; ++ u32 reg = 0; ++ ++ spin_lock_irqsave(&ipu->lock, flags); ++ switch (buf_num) { ++ case 0: ++ reg = ipu_cm_read(ipu, IPU_CHA_BUF0_RDY(channel->num)); ++ break; ++ case 1: ++ reg = ipu_cm_read(ipu, IPU_CHA_BUF1_RDY(channel->num)); ++ break; ++ case 2: ++ reg = ipu_cm_read(ipu, IPU_CHA_BUF2_RDY(channel->num)); ++ break; ++ } ++ spin_unlock_irqrestore(&ipu->lock, flags); ++ ++ return ((reg & idma_mask(channel->num)) != 0); ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_buffer_is_ready); ++ ++void ipu_idmac_select_buffer(struct ipuv3_channel *channel, u32 buf_num) ++{ ++ struct ipu_soc *ipu = channel->ipu; ++ unsigned int chno = channel->num; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&ipu->lock, flags); ++ ++ /* Mark buffer as ready. */ ++ if (buf_num == 0) ++ ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_BUF0_RDY(chno)); ++ else ++ ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_BUF1_RDY(chno)); ++ ++ spin_unlock_irqrestore(&ipu->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_select_buffer); ++ ++void ipu_idmac_clear_buffer(struct ipuv3_channel *channel, u32 buf_num) ++{ ++ struct ipu_soc *ipu = channel->ipu; ++ unsigned int chno = channel->num; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&ipu->lock, flags); ++ ++ ipu_cm_write(ipu, 0xF0300000, IPU_GPR); /* write one to clear */ ++ switch (buf_num) { ++ case 0: ++ ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_BUF0_RDY(chno)); ++ break; ++ case 1: ++ ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_BUF1_RDY(chno)); ++ break; ++ case 2: ++ ipu_cm_write(ipu, idma_mask(chno), IPU_CHA_BUF2_RDY(chno)); ++ break; ++ default: ++ break; ++ } ++ ipu_cm_write(ipu, 0x0, IPU_GPR); /* write one to set */ ++ ++ spin_unlock_irqrestore(&ipu->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_clear_buffer); ++ ++int ipu_idmac_enable_channel(struct ipuv3_channel *channel) ++{ ++ struct ipu_soc *ipu = channel->ipu; ++ u32 val; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&ipu->lock, flags); ++ ++ val = ipu_idmac_read(ipu, IDMAC_CHA_EN(channel->num)); ++ val |= idma_mask(channel->num); ++ ipu_idmac_write(ipu, val, IDMAC_CHA_EN(channel->num)); ++ ++ spin_unlock_irqrestore(&ipu->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_enable_channel); ++ ++bool ipu_idmac_channel_busy(struct ipu_soc *ipu, unsigned int chno) ++{ ++ return (ipu_idmac_read(ipu, IDMAC_CHA_BUSY(chno)) & idma_mask(chno)); ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_channel_busy); ++ ++int ipu_idmac_wait_busy(struct ipuv3_channel *channel, int ms) ++{ ++ struct ipu_soc *ipu = channel->ipu; ++ unsigned long timeout; ++ ++ timeout = jiffies + msecs_to_jiffies(ms); ++ while (ipu_idmac_read(ipu, IDMAC_CHA_BUSY(channel->num)) & ++ idma_mask(channel->num)) { ++ if (time_after(jiffies, timeout)) ++ return -ETIMEDOUT; ++ cpu_relax(); ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_wait_busy); ++ ++int ipu_idmac_disable_channel(struct ipuv3_channel *channel) ++{ ++ struct ipu_soc *ipu = channel->ipu; ++ u32 val; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&ipu->lock, flags); ++ ++ /* Disable DMA channel(s) */ ++ val = ipu_idmac_read(ipu, IDMAC_CHA_EN(channel->num)); ++ val &= ~idma_mask(channel->num); ++ ipu_idmac_write(ipu, val, IDMAC_CHA_EN(channel->num)); ++ ++ __ipu_idmac_reset_current_buffer(channel); ++ ++ /* Set channel buffers NOT to be ready */ ++ ipu_cm_write(ipu, 0xf0000000, IPU_GPR); /* write one to clear */ ++ ++ if (ipu_cm_read(ipu, IPU_CHA_BUF0_RDY(channel->num)) & ++ idma_mask(channel->num)) { ++ ipu_cm_write(ipu, idma_mask(channel->num), ++ IPU_CHA_BUF0_RDY(channel->num)); ++ } ++ ++ if (ipu_cm_read(ipu, IPU_CHA_BUF1_RDY(channel->num)) & ++ idma_mask(channel->num)) { ++ ipu_cm_write(ipu, idma_mask(channel->num), ++ IPU_CHA_BUF1_RDY(channel->num)); ++ } ++ ++ ipu_cm_write(ipu, 0x0, IPU_GPR); /* write one to set */ ++ ++ /* Reset the double buffer */ ++ val = ipu_cm_read(ipu, IPU_CHA_DB_MODE_SEL(channel->num)); ++ val &= ~idma_mask(channel->num); ++ ipu_cm_write(ipu, val, IPU_CHA_DB_MODE_SEL(channel->num)); ++ ++ spin_unlock_irqrestore(&ipu->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_disable_channel); ++ ++/* ++ * The imx6 rev. D TRM says that enabling the WM feature will increase ++ * a channel's priority. Refer to Table 36-8 Calculated priority value. ++ * The sub-module that is the sink or source for the channel must enable ++ * watermark signal for this to take effect (SMFC_WM for instance). ++ */ ++void ipu_idmac_enable_watermark(struct ipuv3_channel *channel, bool enable) ++{ ++ struct ipu_soc *ipu = channel->ipu; ++ unsigned long flags; ++ u32 val; ++ ++ spin_lock_irqsave(&ipu->lock, flags); ++ ++ val = ipu_idmac_read(ipu, IDMAC_WM_EN(channel->num)); ++ if (enable) ++ val |= 1 << (channel->num % 32); ++ else ++ val &= ~(1 << (channel->num % 32)); ++ ipu_idmac_write(ipu, val, IDMAC_WM_EN(channel->num)); ++ ++ spin_unlock_irqrestore(&ipu->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_enable_watermark); ++ ++static int ipu_memory_reset(struct ipu_soc *ipu) ++{ ++ unsigned long timeout; ++ ++ ipu_cm_write(ipu, 0x807FFFFF, IPU_MEM_RST); ++ ++ timeout = jiffies + msecs_to_jiffies(1000); ++ while (ipu_cm_read(ipu, IPU_MEM_RST) & 0x80000000) { ++ if (time_after(jiffies, timeout)) ++ return -ETIME; ++ cpu_relax(); ++ } ++ ++ return 0; ++} ++ ++/* ++ * Set the source mux for the given CSI. Selects either parallel or ++ * MIPI CSI2 sources. ++ */ ++void ipu_set_csi_src_mux(struct ipu_soc *ipu, int csi_id, bool mipi_csi2) ++{ ++ unsigned long flags; ++ u32 val, mask; ++ ++ mask = (csi_id == 1) ? IPU_CONF_CSI1_DATA_SOURCE : ++ IPU_CONF_CSI0_DATA_SOURCE; ++ ++ spin_lock_irqsave(&ipu->lock, flags); ++ ++ val = ipu_cm_read(ipu, IPU_CONF); ++ if (mipi_csi2) ++ val |= mask; ++ else ++ val &= ~mask; ++ ipu_cm_write(ipu, val, IPU_CONF); ++ ++ spin_unlock_irqrestore(&ipu->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_set_csi_src_mux); ++ ++/* ++ * Set the source mux for the IC. Selects either CSI[01] or the VDI. ++ */ ++void ipu_set_ic_src_mux(struct ipu_soc *ipu, int csi_id, bool vdi) ++{ ++ unsigned long flags; ++ u32 val; ++ ++ spin_lock_irqsave(&ipu->lock, flags); ++ ++ val = ipu_cm_read(ipu, IPU_CONF); ++ if (vdi) ++ val |= IPU_CONF_IC_INPUT; ++ else ++ val &= ~IPU_CONF_IC_INPUT; ++ ++ if (csi_id == 1) ++ val |= IPU_CONF_CSI_SEL; ++ else ++ val &= ~IPU_CONF_CSI_SEL; ++ ++ ipu_cm_write(ipu, val, IPU_CONF); ++ ++ spin_unlock_irqrestore(&ipu->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_set_ic_src_mux); ++ ++ ++/* Frame Synchronization Unit Channel Linking */ ++ ++struct fsu_link_reg_info { ++ int chno; ++ u32 reg; ++ u32 mask; ++ u32 val; ++}; ++ ++struct fsu_link_info { ++ struct fsu_link_reg_info src; ++ struct fsu_link_reg_info sink; ++}; ++ ++static const struct fsu_link_info fsu_link_info[] = { ++ { ++ .src = { IPUV3_CHANNEL_IC_PRP_ENC_MEM, IPU_FS_PROC_FLOW2, ++ FS_PRP_ENC_DEST_SEL_MASK, FS_PRP_ENC_DEST_SEL_IRT_ENC }, ++ .sink = { IPUV3_CHANNEL_MEM_ROT_ENC, IPU_FS_PROC_FLOW1, ++ FS_PRPENC_ROT_SRC_SEL_MASK, FS_PRPENC_ROT_SRC_SEL_ENC }, ++ }, { ++ .src = { IPUV3_CHANNEL_IC_PRP_VF_MEM, IPU_FS_PROC_FLOW2, ++ FS_PRPVF_DEST_SEL_MASK, FS_PRPVF_DEST_SEL_IRT_VF }, ++ .sink = { IPUV3_CHANNEL_MEM_ROT_VF, IPU_FS_PROC_FLOW1, ++ FS_PRPVF_ROT_SRC_SEL_MASK, FS_PRPVF_ROT_SRC_SEL_VF }, ++ }, { ++ .src = { IPUV3_CHANNEL_IC_PP_MEM, IPU_FS_PROC_FLOW2, ++ FS_PP_DEST_SEL_MASK, FS_PP_DEST_SEL_IRT_PP }, ++ .sink = { IPUV3_CHANNEL_MEM_ROT_PP, IPU_FS_PROC_FLOW1, ++ FS_PP_ROT_SRC_SEL_MASK, FS_PP_ROT_SRC_SEL_PP }, ++ }, { ++ .src = { IPUV3_CHANNEL_CSI_DIRECT, 0 }, ++ .sink = { IPUV3_CHANNEL_CSI_VDI_PREV, IPU_FS_PROC_FLOW1, ++ FS_VDI_SRC_SEL_MASK, FS_VDI_SRC_SEL_CSI_DIRECT }, ++ }, ++}; ++ ++static const struct fsu_link_info *find_fsu_link_info(int src, int sink) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(fsu_link_info); i++) { ++ if (src == fsu_link_info[i].src.chno && ++ sink == fsu_link_info[i].sink.chno) ++ return &fsu_link_info[i]; ++ } ++ ++ return NULL; ++} ++ ++/* ++ * Links a source channel to a sink channel in the FSU. ++ */ ++int ipu_fsu_link(struct ipu_soc *ipu, int src_ch, int sink_ch) ++{ ++ const struct fsu_link_info *link; ++ u32 src_reg, sink_reg; ++ unsigned long flags; ++ ++ link = find_fsu_link_info(src_ch, sink_ch); ++ if (!link) ++ return -EINVAL; ++ ++ spin_lock_irqsave(&ipu->lock, flags); ++ ++ if (link->src.mask) { ++ src_reg = ipu_cm_read(ipu, link->src.reg); ++ src_reg &= ~link->src.mask; ++ src_reg |= link->src.val; ++ ipu_cm_write(ipu, src_reg, link->src.reg); ++ } ++ ++ if (link->sink.mask) { ++ sink_reg = ipu_cm_read(ipu, link->sink.reg); ++ sink_reg &= ~link->sink.mask; ++ sink_reg |= link->sink.val; ++ ipu_cm_write(ipu, sink_reg, link->sink.reg); ++ } ++ ++ spin_unlock_irqrestore(&ipu->lock, flags); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_fsu_link); ++ ++/* ++ * Unlinks source and sink channels in the FSU. ++ */ ++int ipu_fsu_unlink(struct ipu_soc *ipu, int src_ch, int sink_ch) ++{ ++ const struct fsu_link_info *link; ++ u32 src_reg, sink_reg; ++ unsigned long flags; ++ ++ link = find_fsu_link_info(src_ch, sink_ch); ++ if (!link) ++ return -EINVAL; ++ ++ spin_lock_irqsave(&ipu->lock, flags); ++ ++ if (link->src.mask) { ++ src_reg = ipu_cm_read(ipu, link->src.reg); ++ src_reg &= ~link->src.mask; ++ ipu_cm_write(ipu, src_reg, link->src.reg); ++ } ++ ++ if (link->sink.mask) { ++ sink_reg = ipu_cm_read(ipu, link->sink.reg); ++ sink_reg &= ~link->sink.mask; ++ ipu_cm_write(ipu, sink_reg, link->sink.reg); ++ } ++ ++ spin_unlock_irqrestore(&ipu->lock, flags); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_fsu_unlink); ++ ++/* Link IDMAC channels in the FSU */ ++int ipu_idmac_link(struct ipuv3_channel *src, struct ipuv3_channel *sink) ++{ ++ return ipu_fsu_link(src->ipu, src->num, sink->num); ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_link); ++ ++/* Unlink IDMAC channels in the FSU */ ++int ipu_idmac_unlink(struct ipuv3_channel *src, struct ipuv3_channel *sink) ++{ ++ return ipu_fsu_unlink(src->ipu, src->num, sink->num); ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_unlink); ++ ++struct ipu_devtype { ++ const char *name; ++ unsigned long cm_ofs; ++ unsigned long cpmem_ofs; ++ unsigned long srm_ofs; ++ unsigned long tpm_ofs; ++ unsigned long csi0_ofs; ++ unsigned long csi1_ofs; ++ unsigned long ic_ofs; ++ unsigned long disp0_ofs; ++ unsigned long disp1_ofs; ++ unsigned long dc_tmpl_ofs; ++ unsigned long vdi_ofs; ++ enum ipuv3_type type; ++}; ++ ++static struct ipu_devtype ipu_type_imx51 = { ++ .name = "IPUv3EX", ++ .cm_ofs = 0x1e000000, ++ .cpmem_ofs = 0x1f000000, ++ .srm_ofs = 0x1f040000, ++ .tpm_ofs = 0x1f060000, ++ .csi0_ofs = 0x1e030000, ++ .csi1_ofs = 0x1e038000, ++ .ic_ofs = 0x1e020000, ++ .disp0_ofs = 0x1e040000, ++ .disp1_ofs = 0x1e048000, ++ .dc_tmpl_ofs = 0x1f080000, ++ .vdi_ofs = 0x1e068000, ++ .type = IPUV3EX, ++}; ++ ++static struct ipu_devtype ipu_type_imx53 = { ++ .name = "IPUv3M", ++ .cm_ofs = 0x06000000, ++ .cpmem_ofs = 0x07000000, ++ .srm_ofs = 0x07040000, ++ .tpm_ofs = 0x07060000, ++ .csi0_ofs = 0x06030000, ++ .csi1_ofs = 0x06038000, ++ .ic_ofs = 0x06020000, ++ .disp0_ofs = 0x06040000, ++ .disp1_ofs = 0x06048000, ++ .dc_tmpl_ofs = 0x07080000, ++ .vdi_ofs = 0x06068000, ++ .type = IPUV3M, ++}; ++ ++static struct ipu_devtype ipu_type_imx6q = { ++ .name = "IPUv3H", ++ .cm_ofs = 0x00200000, ++ .cpmem_ofs = 0x00300000, ++ .srm_ofs = 0x00340000, ++ .tpm_ofs = 0x00360000, ++ .csi0_ofs = 0x00230000, ++ .csi1_ofs = 0x00238000, ++ .ic_ofs = 0x00220000, ++ .disp0_ofs = 0x00240000, ++ .disp1_ofs = 0x00248000, ++ .dc_tmpl_ofs = 0x00380000, ++ .vdi_ofs = 0x00268000, ++ .type = IPUV3H, ++}; ++ ++static const struct of_device_id imx_ipu_dt_ids[] = { ++ { .compatible = "fsl,imx51-ipu", .data = &ipu_type_imx51, }, ++ { .compatible = "fsl,imx53-ipu", .data = &ipu_type_imx53, }, ++ { .compatible = "fsl,imx6q-ipu", .data = &ipu_type_imx6q, }, ++ { .compatible = "fsl,imx6qp-ipu", .data = &ipu_type_imx6q, }, ++ { /* sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, imx_ipu_dt_ids); ++ ++static int ipu_submodules_init(struct ipu_soc *ipu, ++ struct platform_device *pdev, unsigned long ipu_base, ++ struct clk *ipu_clk) ++{ ++ char *unit; ++ int ret; ++ struct device *dev = &pdev->dev; ++ const struct ipu_devtype *devtype = ipu->devtype; ++ ++ ret = ipu_cpmem_init(ipu, dev, ipu_base + devtype->cpmem_ofs); ++ if (ret) { ++ unit = "cpmem"; ++ goto err_cpmem; ++ } ++ ++ ret = ipu_csi_init(ipu, dev, 0, ipu_base + devtype->csi0_ofs, ++ IPU_CONF_CSI0_EN, ipu_clk); ++ if (ret) { ++ unit = "csi0"; ++ goto err_csi_0; ++ } ++ ++ ret = ipu_csi_init(ipu, dev, 1, ipu_base + devtype->csi1_ofs, ++ IPU_CONF_CSI1_EN, ipu_clk); ++ if (ret) { ++ unit = "csi1"; ++ goto err_csi_1; ++ } ++ ++ ret = ipu_ic_init(ipu, dev, ++ ipu_base + devtype->ic_ofs, ++ ipu_base + devtype->tpm_ofs); ++ if (ret) { ++ unit = "ic"; ++ goto err_ic; ++ } ++ ++ ret = ipu_vdi_init(ipu, dev, ipu_base + devtype->vdi_ofs, ++ IPU_CONF_VDI_EN | IPU_CONF_ISP_EN | ++ IPU_CONF_IC_INPUT); ++ if (ret) { ++ unit = "vdi"; ++ goto err_vdi; ++ } ++ ++ ret = ipu_image_convert_init(ipu, dev); ++ if (ret) { ++ unit = "image_convert"; ++ goto err_image_convert; ++ } ++ ++ ret = ipu_di_init(ipu, dev, 0, ipu_base + devtype->disp0_ofs, ++ IPU_CONF_DI0_EN, ipu_clk); ++ if (ret) { ++ unit = "di0"; ++ goto err_di_0; ++ } ++ ++ ret = ipu_di_init(ipu, dev, 1, ipu_base + devtype->disp1_ofs, ++ IPU_CONF_DI1_EN, ipu_clk); ++ if (ret) { ++ unit = "di1"; ++ goto err_di_1; ++ } ++ ++ ret = ipu_dc_init(ipu, dev, ipu_base + devtype->cm_ofs + ++ IPU_CM_DC_REG_OFS, ipu_base + devtype->dc_tmpl_ofs); ++ if (ret) { ++ unit = "dc_template"; ++ goto err_dc; ++ } ++ ++ ret = ipu_dmfc_init(ipu, dev, ipu_base + ++ devtype->cm_ofs + IPU_CM_DMFC_REG_OFS, ipu_clk); ++ if (ret) { ++ unit = "dmfc"; ++ goto err_dmfc; ++ } ++ ++ ret = ipu_dp_init(ipu, dev, ipu_base + devtype->srm_ofs); ++ if (ret) { ++ unit = "dp"; ++ goto err_dp; ++ } ++ ++ ret = ipu_smfc_init(ipu, dev, ipu_base + ++ devtype->cm_ofs + IPU_CM_SMFC_REG_OFS); ++ if (ret) { ++ unit = "smfc"; ++ goto err_smfc; ++ } ++ ++ return 0; ++ ++err_smfc: ++ ipu_dp_exit(ipu); ++err_dp: ++ ipu_dmfc_exit(ipu); ++err_dmfc: ++ ipu_dc_exit(ipu); ++err_dc: ++ ipu_di_exit(ipu, 1); ++err_di_1: ++ ipu_di_exit(ipu, 0); ++err_di_0: ++ ipu_image_convert_exit(ipu); ++err_image_convert: ++ ipu_vdi_exit(ipu); ++err_vdi: ++ ipu_ic_exit(ipu); ++err_ic: ++ ipu_csi_exit(ipu, 1); ++err_csi_1: ++ ipu_csi_exit(ipu, 0); ++err_csi_0: ++ ipu_cpmem_exit(ipu); ++err_cpmem: ++ dev_err(&pdev->dev, "init %s failed with %d\n", unit, ret); ++ return ret; ++} ++ ++static void ipu_irq_handle(struct ipu_soc *ipu, const int *regs, int num_regs) ++{ ++ unsigned long status; ++ int i, bit, irq; ++ ++ for (i = 0; i < num_regs; i++) { ++ ++ status = ipu_cm_read(ipu, IPU_INT_STAT(regs[i])); ++ status &= ipu_cm_read(ipu, IPU_INT_CTRL(regs[i])); ++ ++ for_each_set_bit(bit, &status, 32) { ++ irq = irq_linear_revmap(ipu->domain, ++ regs[i] * 32 + bit); ++ if (irq) ++ generic_handle_irq(irq); ++ } ++ } ++} ++ ++static void ipu_irq_handler(struct irq_desc *desc) ++{ ++ struct ipu_soc *ipu = irq_desc_get_handler_data(desc); ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ static const int int_reg[] = { 0, 1, 2, 3, 10, 11, 12, 13, 14}; ++ ++ chained_irq_enter(chip, desc); ++ ++ ipu_irq_handle(ipu, int_reg, ARRAY_SIZE(int_reg)); ++ ++ chained_irq_exit(chip, desc); ++} ++ ++static void ipu_err_irq_handler(struct irq_desc *desc) ++{ ++ struct ipu_soc *ipu = irq_desc_get_handler_data(desc); ++ struct irq_chip *chip = irq_desc_get_chip(desc); ++ static const int int_reg[] = { 4, 5, 8, 9}; ++ ++ chained_irq_enter(chip, desc); ++ ++ ipu_irq_handle(ipu, int_reg, ARRAY_SIZE(int_reg)); ++ ++ chained_irq_exit(chip, desc); ++} ++ ++int ipu_map_irq(struct ipu_soc *ipu, int irq) ++{ ++ int virq; ++ ++ virq = irq_linear_revmap(ipu->domain, irq); ++ if (!virq) ++ virq = irq_create_mapping(ipu->domain, irq); ++ ++ return virq; ++} ++EXPORT_SYMBOL_GPL(ipu_map_irq); ++ ++int ipu_idmac_channel_irq(struct ipu_soc *ipu, struct ipuv3_channel *channel, ++ enum ipu_channel_irq irq_type) ++{ ++ return ipu_map_irq(ipu, irq_type + channel->num); ++} ++EXPORT_SYMBOL_GPL(ipu_idmac_channel_irq); ++ ++static void ipu_submodules_exit(struct ipu_soc *ipu) ++{ ++ ipu_smfc_exit(ipu); ++ ipu_dp_exit(ipu); ++ ipu_dmfc_exit(ipu); ++ ipu_dc_exit(ipu); ++ ipu_di_exit(ipu, 1); ++ ipu_di_exit(ipu, 0); ++ ipu_image_convert_exit(ipu); ++ ipu_vdi_exit(ipu); ++ ipu_ic_exit(ipu); ++ ipu_csi_exit(ipu, 1); ++ ipu_csi_exit(ipu, 0); ++ ipu_cpmem_exit(ipu); ++} ++ ++static int platform_remove_devices_fn(struct device *dev, void *unused) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ ++ platform_device_unregister(pdev); ++ ++ return 0; ++} ++ ++static void platform_device_unregister_children(struct platform_device *pdev) ++{ ++ device_for_each_child(&pdev->dev, NULL, platform_remove_devices_fn); ++} ++ ++struct ipu_platform_reg { ++ struct ipu_client_platformdata pdata; ++ const char *name; ++}; ++ ++/* These must be in the order of the corresponding device tree port nodes */ ++static struct ipu_platform_reg client_reg[] = { ++ { ++ .pdata = { ++ .csi = 0, ++ .dma[0] = IPUV3_CHANNEL_CSI0, ++ .dma[1] = -EINVAL, ++ }, ++ .name = "imx-ipuv3-csi", ++ }, { ++ .pdata = { ++ .csi = 1, ++ .dma[0] = IPUV3_CHANNEL_CSI1, ++ .dma[1] = -EINVAL, ++ }, ++ .name = "imx-ipuv3-csi", ++ }, { ++ .pdata = { ++ .di = 0, ++ .dc = 5, ++ .dp = IPU_DP_FLOW_SYNC_BG, ++ .dma[0] = IPUV3_CHANNEL_MEM_BG_SYNC, ++ .dma[1] = IPUV3_CHANNEL_MEM_FG_SYNC, ++ }, ++ .name = "imx-ipuv3-crtc", ++ }, { ++ .pdata = { ++ .di = 1, ++ .dc = 1, ++ .dp = -EINVAL, ++ .dma[0] = IPUV3_CHANNEL_MEM_DC_SYNC, ++ .dma[1] = -EINVAL, ++ }, ++ .name = "imx-ipuv3-crtc", ++ }, ++}; ++ ++static DEFINE_MUTEX(ipu_client_id_mutex); ++static int ipu_client_id; ++ ++static int ipu_add_client_devices(struct ipu_soc *ipu, unsigned long ipu_base) ++{ ++ struct device *dev = ipu->dev; ++ unsigned i; ++ int id, ret; ++ ++ mutex_lock(&ipu_client_id_mutex); ++ id = ipu_client_id; ++ ipu_client_id += ARRAY_SIZE(client_reg); ++ mutex_unlock(&ipu_client_id_mutex); ++ ++ for (i = 0; i < ARRAY_SIZE(client_reg); i++) { ++ struct ipu_platform_reg *reg = &client_reg[i]; ++ struct platform_device *pdev; ++ struct device_node *of_node; ++ ++ /* Associate subdevice with the corresponding port node */ ++ of_node = of_graph_get_port_by_id(dev->of_node, i); ++ if (!of_node) { ++ dev_info(dev, ++ "no port@%d node in %pOF, not using %s%d\n", ++ i, dev->of_node, ++ (i / 2) ? "DI" : "CSI", i % 2); ++ continue; ++ } ++ ++ pdev = platform_device_alloc(reg->name, id++); ++ if (!pdev) { ++ ret = -ENOMEM; ++ goto err_register; ++ } ++ ++ pdev->dev.parent = dev; ++ ++ reg->pdata.of_node = of_node; ++ ret = platform_device_add_data(pdev, ®->pdata, ++ sizeof(reg->pdata)); ++ if (!ret) ++ ret = platform_device_add(pdev); ++ if (ret) { ++ platform_device_put(pdev); ++ goto err_register; ++ } ++ } ++ ++ return 0; ++ ++err_register: ++ platform_device_unregister_children(to_platform_device(dev)); ++ ++ return ret; ++} ++ ++ ++static int ipu_irq_init(struct ipu_soc *ipu) ++{ ++ struct irq_chip_generic *gc; ++ struct irq_chip_type *ct; ++ unsigned long unused[IPU_NUM_IRQS / 32] = { ++ 0x400100d0, 0xffe000fd, ++ 0x400100d0, 0xffe000fd, ++ 0x400100d0, 0xffe000fd, ++ 0x4077ffff, 0xffe7e1fd, ++ 0x23fffffe, 0x8880fff0, ++ 0xf98fe7d0, 0xfff81fff, ++ 0x400100d0, 0xffe000fd, ++ 0x00000000, ++ }; ++ int ret, i; ++ ++ ipu->domain = irq_domain_add_linear(ipu->dev->of_node, IPU_NUM_IRQS, ++ &irq_generic_chip_ops, ipu); ++ if (!ipu->domain) { ++ dev_err(ipu->dev, "failed to add irq domain\n"); ++ return -ENODEV; ++ } ++ ++ ret = irq_alloc_domain_generic_chips(ipu->domain, 32, 1, "IPU", ++ handle_level_irq, 0, 0, 0); ++ if (ret < 0) { ++ dev_err(ipu->dev, "failed to alloc generic irq chips\n"); ++ irq_domain_remove(ipu->domain); ++ return ret; ++ } ++ ++ /* Mask and clear all interrupts */ ++ for (i = 0; i < IPU_NUM_IRQS; i += 32) { ++ ipu_cm_write(ipu, 0, IPU_INT_CTRL(i / 32)); ++ ipu_cm_write(ipu, ~unused[i / 32], IPU_INT_STAT(i / 32)); ++ } ++ ++ for (i = 0; i < IPU_NUM_IRQS; i += 32) { ++ gc = irq_get_domain_generic_chip(ipu->domain, i); ++ gc->reg_base = ipu->cm_reg; ++ gc->unused = unused[i / 32]; ++ ct = gc->chip_types; ++ ct->chip.irq_ack = irq_gc_ack_set_bit; ++ ct->chip.irq_mask = irq_gc_mask_clr_bit; ++ ct->chip.irq_unmask = irq_gc_mask_set_bit; ++ ct->regs.ack = IPU_INT_STAT(i / 32); ++ ct->regs.mask = IPU_INT_CTRL(i / 32); ++ } ++ ++ irq_set_chained_handler_and_data(ipu->irq_sync, ipu_irq_handler, ipu); ++ irq_set_chained_handler_and_data(ipu->irq_err, ipu_err_irq_handler, ++ ipu); ++ ++ return 0; ++} ++ ++static void ipu_irq_exit(struct ipu_soc *ipu) ++{ ++ int i, irq; ++ ++ irq_set_chained_handler_and_data(ipu->irq_err, NULL, NULL); ++ irq_set_chained_handler_and_data(ipu->irq_sync, NULL, NULL); ++ ++ /* TODO: remove irq_domain_generic_chips */ ++ ++ for (i = 0; i < IPU_NUM_IRQS; i++) { ++ irq = irq_linear_revmap(ipu->domain, i); ++ if (irq) ++ irq_dispose_mapping(irq); ++ } ++ ++ irq_domain_remove(ipu->domain); ++} ++ ++void ipu_dump(struct ipu_soc *ipu) ++{ ++ int i; ++ ++ dev_dbg(ipu->dev, "IPU_CONF = \t0x%08X\n", ++ ipu_cm_read(ipu, IPU_CONF)); ++ dev_dbg(ipu->dev, "IDMAC_CONF = \t0x%08X\n", ++ ipu_idmac_read(ipu, IDMAC_CONF)); ++ dev_dbg(ipu->dev, "IDMAC_CHA_EN1 = \t0x%08X\n", ++ ipu_idmac_read(ipu, IDMAC_CHA_EN(0))); ++ dev_dbg(ipu->dev, "IDMAC_CHA_EN2 = \t0x%08X\n", ++ ipu_idmac_read(ipu, IDMAC_CHA_EN(32))); ++ dev_dbg(ipu->dev, "IDMAC_CHA_PRI1 = \t0x%08X\n", ++ ipu_idmac_read(ipu, IDMAC_CHA_PRI(0))); ++ dev_dbg(ipu->dev, "IDMAC_CHA_PRI2 = \t0x%08X\n", ++ ipu_idmac_read(ipu, IDMAC_CHA_PRI(32))); ++ dev_dbg(ipu->dev, "IDMAC_BAND_EN1 = \t0x%08X\n", ++ ipu_idmac_read(ipu, IDMAC_BAND_EN(0))); ++ dev_dbg(ipu->dev, "IDMAC_BAND_EN2 = \t0x%08X\n", ++ ipu_idmac_read(ipu, IDMAC_BAND_EN(32))); ++ dev_dbg(ipu->dev, "IPU_CHA_DB_MODE_SEL0 = \t0x%08X\n", ++ ipu_cm_read(ipu, IPU_CHA_DB_MODE_SEL(0))); ++ dev_dbg(ipu->dev, "IPU_CHA_DB_MODE_SEL1 = \t0x%08X\n", ++ ipu_cm_read(ipu, IPU_CHA_DB_MODE_SEL(32))); ++ dev_dbg(ipu->dev, "IPU_FS_PROC_FLOW1 = \t0x%08X\n", ++ ipu_cm_read(ipu, IPU_FS_PROC_FLOW1)); ++ dev_dbg(ipu->dev, "IPU_FS_PROC_FLOW2 = \t0x%08X\n", ++ ipu_cm_read(ipu, IPU_FS_PROC_FLOW2)); ++ dev_dbg(ipu->dev, "IPU_FS_PROC_FLOW3 = \t0x%08X\n", ++ ipu_cm_read(ipu, IPU_FS_PROC_FLOW3)); ++ dev_dbg(ipu->dev, "IPU_FS_DISP_FLOW1 = \t0x%08X\n", ++ ipu_cm_read(ipu, IPU_FS_DISP_FLOW1)); ++ for (i = 0; i < 15; i++) ++ dev_dbg(ipu->dev, "IPU_INT_CTRL(%d) = \t%08X\n", i, ++ ipu_cm_read(ipu, IPU_INT_CTRL(i))); ++} ++EXPORT_SYMBOL_GPL(ipu_dump); ++ ++static int ipu_probe(struct platform_device *pdev) ++{ ++ struct device_node *np = pdev->dev.of_node; ++ struct ipu_soc *ipu; ++ struct resource *res; ++ unsigned long ipu_base; ++ int ret, irq_sync, irq_err; ++ const struct ipu_devtype *devtype; ++ ++ devtype = of_device_get_match_data(&pdev->dev); ++ if (!devtype) ++ return -EINVAL; ++ ++ irq_sync = platform_get_irq(pdev, 0); ++ irq_err = platform_get_irq(pdev, 1); ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ ++ dev_dbg(&pdev->dev, "irq_sync: %d irq_err: %d\n", ++ irq_sync, irq_err); ++ ++ if (!res || irq_sync < 0 || irq_err < 0) ++ return -ENODEV; ++ ++ ipu_base = res->start; ++ ++ ipu = devm_kzalloc(&pdev->dev, sizeof(*ipu), GFP_KERNEL); ++ if (!ipu) ++ return -ENODEV; ++ ++ ipu->id = of_alias_get_id(np, "ipu"); ++ if (ipu->id < 0) ++ ipu->id = 0; ++ ++ if (of_device_is_compatible(np, "fsl,imx6qp-ipu") && ++ IS_ENABLED(CONFIG_DRM)) { ++ ipu->prg_priv = ipu_prg_lookup_by_phandle(&pdev->dev, ++ "fsl,prg", ipu->id); ++ if (!ipu->prg_priv) ++ return -EPROBE_DEFER; ++ } ++ ++ ipu->devtype = devtype; ++ ipu->ipu_type = devtype->type; ++ ++ spin_lock_init(&ipu->lock); ++ mutex_init(&ipu->channel_lock); ++ INIT_LIST_HEAD(&ipu->channels); ++ ++ dev_dbg(&pdev->dev, "cm_reg: 0x%08lx\n", ++ ipu_base + devtype->cm_ofs); ++ dev_dbg(&pdev->dev, "idmac: 0x%08lx\n", ++ ipu_base + devtype->cm_ofs + IPU_CM_IDMAC_REG_OFS); ++ dev_dbg(&pdev->dev, "cpmem: 0x%08lx\n", ++ ipu_base + devtype->cpmem_ofs); ++ dev_dbg(&pdev->dev, "csi0: 0x%08lx\n", ++ ipu_base + devtype->csi0_ofs); ++ dev_dbg(&pdev->dev, "csi1: 0x%08lx\n", ++ ipu_base + devtype->csi1_ofs); ++ dev_dbg(&pdev->dev, "ic: 0x%08lx\n", ++ ipu_base + devtype->ic_ofs); ++ dev_dbg(&pdev->dev, "disp0: 0x%08lx\n", ++ ipu_base + devtype->disp0_ofs); ++ dev_dbg(&pdev->dev, "disp1: 0x%08lx\n", ++ ipu_base + devtype->disp1_ofs); ++ dev_dbg(&pdev->dev, "srm: 0x%08lx\n", ++ ipu_base + devtype->srm_ofs); ++ dev_dbg(&pdev->dev, "tpm: 0x%08lx\n", ++ ipu_base + devtype->tpm_ofs); ++ dev_dbg(&pdev->dev, "dc: 0x%08lx\n", ++ ipu_base + devtype->cm_ofs + IPU_CM_DC_REG_OFS); ++ dev_dbg(&pdev->dev, "ic: 0x%08lx\n", ++ ipu_base + devtype->cm_ofs + IPU_CM_IC_REG_OFS); ++ dev_dbg(&pdev->dev, "dmfc: 0x%08lx\n", ++ ipu_base + devtype->cm_ofs + IPU_CM_DMFC_REG_OFS); ++ dev_dbg(&pdev->dev, "vdi: 0x%08lx\n", ++ ipu_base + devtype->vdi_ofs); ++ ++ ipu->cm_reg = devm_ioremap(&pdev->dev, ++ ipu_base + devtype->cm_ofs, PAGE_SIZE); ++ ipu->idmac_reg = devm_ioremap(&pdev->dev, ++ ipu_base + devtype->cm_ofs + IPU_CM_IDMAC_REG_OFS, ++ PAGE_SIZE); ++ ++ if (!ipu->cm_reg || !ipu->idmac_reg) ++ return -ENOMEM; ++ ++ ipu->clk = devm_clk_get(&pdev->dev, "bus"); ++ if (IS_ERR(ipu->clk)) { ++ ret = PTR_ERR(ipu->clk); ++ dev_err(&pdev->dev, "clk_get failed with %d", ret); ++ return ret; ++ } ++ ++ platform_set_drvdata(pdev, ipu); ++ ++ ret = clk_prepare_enable(ipu->clk); ++ if (ret) { ++ dev_err(&pdev->dev, "clk_prepare_enable failed: %d\n", ret); ++ return ret; ++ } ++ ++ ipu->dev = &pdev->dev; ++ ipu->irq_sync = irq_sync; ++ ipu->irq_err = irq_err; ++ ++ ret = device_reset(&pdev->dev); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to reset: %d\n", ret); ++ goto out_failed_reset; ++ } ++ ret = ipu_memory_reset(ipu); ++ if (ret) ++ goto out_failed_reset; ++ ++ ret = ipu_irq_init(ipu); ++ if (ret) ++ goto out_failed_irq; ++ ++ /* Set MCU_T to divide MCU access window into 2 */ ++ ipu_cm_write(ipu, 0x00400000L | (IPU_MCU_T_DEFAULT << 18), ++ IPU_DISP_GEN); ++ ++ ret = ipu_submodules_init(ipu, pdev, ipu_base, ipu->clk); ++ if (ret) ++ goto failed_submodules_init; ++ ++ ret = ipu_add_client_devices(ipu, ipu_base); ++ if (ret) { ++ dev_err(&pdev->dev, "adding client devices failed with %d\n", ++ ret); ++ goto failed_add_clients; ++ } ++ ++ dev_info(&pdev->dev, "%s probed\n", devtype->name); ++ ++ return 0; ++ ++failed_add_clients: ++ ipu_submodules_exit(ipu); ++failed_submodules_init: ++ ipu_irq_exit(ipu); ++out_failed_irq: ++out_failed_reset: ++ clk_disable_unprepare(ipu->clk); ++ return ret; ++} ++ ++static int ipu_remove(struct platform_device *pdev) ++{ ++ struct ipu_soc *ipu = platform_get_drvdata(pdev); ++ ++ platform_device_unregister_children(pdev); ++ ipu_submodules_exit(ipu); ++ ipu_irq_exit(ipu); ++ ++ clk_disable_unprepare(ipu->clk); ++ ++ return 0; ++} ++ ++static struct platform_driver imx_ipu_driver = { ++ .driver = { ++ .name = "imx-ipuv3", ++ .of_match_table = imx_ipu_dt_ids, ++ }, ++ .probe = ipu_probe, ++ .remove = ipu_remove, ++}; ++ ++static struct platform_driver * const drivers[] = { ++#if IS_ENABLED(CONFIG_DRM) ++ &ipu_pre_drv, ++ &ipu_prg_drv, ++#endif ++ &imx_ipu_driver, ++}; ++ ++static int __init imx_ipu_init(void) ++{ ++ return platform_register_drivers(drivers, ARRAY_SIZE(drivers)); ++} ++module_init(imx_ipu_init); ++ ++static void __exit imx_ipu_exit(void) ++{ ++ platform_unregister_drivers(drivers, ARRAY_SIZE(drivers)); ++} ++module_exit(imx_ipu_exit); ++ ++MODULE_ALIAS("platform:imx-ipuv3"); ++MODULE_DESCRIPTION("i.MX IPU v3 driver"); ++MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); ++MODULE_LICENSE("GPL"); +--- /dev/null ++++ b/drivers/gpu/ipu-v3/ipu-cpmem.c +@@ -0,0 +1,976 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2012 Mentor Graphics Inc. ++ * Copyright 2005-2012 Freescale Semiconductor, Inc. All Rights Reserved. ++ */ ++#include <linux/types.h> ++#include <linux/bitrev.h> ++#include <linux/io.h> ++#include <linux/sizes.h> ++#include <drm/drm_fourcc.h> ++#include "ipu-prv.h" ++ ++struct ipu_cpmem_word { ++ u32 data[5]; ++ u32 res[3]; ++}; ++ ++struct ipu_ch_param { ++ struct ipu_cpmem_word word[2]; ++}; ++ ++struct ipu_cpmem { ++ struct ipu_ch_param __iomem *base; ++ u32 module; ++ spinlock_t lock; ++ int use_count; ++ struct ipu_soc *ipu; ++}; ++ ++#define IPU_CPMEM_WORD(word, ofs, size) ((((word) * 160 + (ofs)) << 8) | (size)) ++ ++#define IPU_FIELD_UBO IPU_CPMEM_WORD(0, 46, 22) ++#define IPU_FIELD_VBO IPU_CPMEM_WORD(0, 68, 22) ++#define IPU_FIELD_IOX IPU_CPMEM_WORD(0, 90, 4) ++#define IPU_FIELD_RDRW IPU_CPMEM_WORD(0, 94, 1) ++#define IPU_FIELD_SO IPU_CPMEM_WORD(0, 113, 1) ++#define IPU_FIELD_SLY IPU_CPMEM_WORD(1, 102, 14) ++#define IPU_FIELD_SLUV IPU_CPMEM_WORD(1, 128, 14) ++ ++#define IPU_FIELD_XV IPU_CPMEM_WORD(0, 0, 10) ++#define IPU_FIELD_YV IPU_CPMEM_WORD(0, 10, 9) ++#define IPU_FIELD_XB IPU_CPMEM_WORD(0, 19, 13) ++#define IPU_FIELD_YB IPU_CPMEM_WORD(0, 32, 12) ++#define IPU_FIELD_NSB_B IPU_CPMEM_WORD(0, 44, 1) ++#define IPU_FIELD_CF IPU_CPMEM_WORD(0, 45, 1) ++#define IPU_FIELD_SX IPU_CPMEM_WORD(0, 46, 12) ++#define IPU_FIELD_SY IPU_CPMEM_WORD(0, 58, 11) ++#define IPU_FIELD_NS IPU_CPMEM_WORD(0, 69, 10) ++#define IPU_FIELD_SDX IPU_CPMEM_WORD(0, 79, 7) ++#define IPU_FIELD_SM IPU_CPMEM_WORD(0, 86, 10) ++#define IPU_FIELD_SCC IPU_CPMEM_WORD(0, 96, 1) ++#define IPU_FIELD_SCE IPU_CPMEM_WORD(0, 97, 1) ++#define IPU_FIELD_SDY IPU_CPMEM_WORD(0, 98, 7) ++#define IPU_FIELD_SDRX IPU_CPMEM_WORD(0, 105, 1) ++#define IPU_FIELD_SDRY IPU_CPMEM_WORD(0, 106, 1) ++#define IPU_FIELD_BPP IPU_CPMEM_WORD(0, 107, 3) ++#define IPU_FIELD_DEC_SEL IPU_CPMEM_WORD(0, 110, 2) ++#define IPU_FIELD_DIM IPU_CPMEM_WORD(0, 112, 1) ++#define IPU_FIELD_BNDM IPU_CPMEM_WORD(0, 114, 3) ++#define IPU_FIELD_BM IPU_CPMEM_WORD(0, 117, 2) ++#define IPU_FIELD_ROT IPU_CPMEM_WORD(0, 119, 1) ++#define IPU_FIELD_ROT_HF_VF IPU_CPMEM_WORD(0, 119, 3) ++#define IPU_FIELD_HF IPU_CPMEM_WORD(0, 120, 1) ++#define IPU_FIELD_VF IPU_CPMEM_WORD(0, 121, 1) ++#define IPU_FIELD_THE IPU_CPMEM_WORD(0, 122, 1) ++#define IPU_FIELD_CAP IPU_CPMEM_WORD(0, 123, 1) ++#define IPU_FIELD_CAE IPU_CPMEM_WORD(0, 124, 1) ++#define IPU_FIELD_FW IPU_CPMEM_WORD(0, 125, 13) ++#define IPU_FIELD_FH IPU_CPMEM_WORD(0, 138, 12) ++#define IPU_FIELD_EBA0 IPU_CPMEM_WORD(1, 0, 29) ++#define IPU_FIELD_EBA1 IPU_CPMEM_WORD(1, 29, 29) ++#define IPU_FIELD_ILO IPU_CPMEM_WORD(1, 58, 20) ++#define IPU_FIELD_NPB IPU_CPMEM_WORD(1, 78, 7) ++#define IPU_FIELD_PFS IPU_CPMEM_WORD(1, 85, 4) ++#define IPU_FIELD_ALU IPU_CPMEM_WORD(1, 89, 1) ++#define IPU_FIELD_ALBM IPU_CPMEM_WORD(1, 90, 3) ++#define IPU_FIELD_ID IPU_CPMEM_WORD(1, 93, 2) ++#define IPU_FIELD_TH IPU_CPMEM_WORD(1, 95, 7) ++#define IPU_FIELD_SL IPU_CPMEM_WORD(1, 102, 14) ++#define IPU_FIELD_WID0 IPU_CPMEM_WORD(1, 116, 3) ++#define IPU_FIELD_WID1 IPU_CPMEM_WORD(1, 119, 3) ++#define IPU_FIELD_WID2 IPU_CPMEM_WORD(1, 122, 3) ++#define IPU_FIELD_WID3 IPU_CPMEM_WORD(1, 125, 3) ++#define IPU_FIELD_OFS0 IPU_CPMEM_WORD(1, 128, 5) ++#define IPU_FIELD_OFS1 IPU_CPMEM_WORD(1, 133, 5) ++#define IPU_FIELD_OFS2 IPU_CPMEM_WORD(1, 138, 5) ++#define IPU_FIELD_OFS3 IPU_CPMEM_WORD(1, 143, 5) ++#define IPU_FIELD_SXYS IPU_CPMEM_WORD(1, 148, 1) ++#define IPU_FIELD_CRE IPU_CPMEM_WORD(1, 149, 1) ++#define IPU_FIELD_DEC_SEL2 IPU_CPMEM_WORD(1, 150, 1) ++ ++static inline struct ipu_ch_param __iomem * ++ipu_get_cpmem(struct ipuv3_channel *ch) ++{ ++ struct ipu_cpmem *cpmem = ch->ipu->cpmem_priv; ++ ++ return cpmem->base + ch->num; ++} ++ ++static void ipu_ch_param_write_field(struct ipuv3_channel *ch, u32 wbs, u32 v) ++{ ++ struct ipu_ch_param __iomem *base = ipu_get_cpmem(ch); ++ u32 bit = (wbs >> 8) % 160; ++ u32 size = wbs & 0xff; ++ u32 word = (wbs >> 8) / 160; ++ u32 i = bit / 32; ++ u32 ofs = bit % 32; ++ u32 mask = (1 << size) - 1; ++ u32 val; ++ ++ pr_debug("%s %d %d %d\n", __func__, word, bit , size); ++ ++ val = readl(&base->word[word].data[i]); ++ val &= ~(mask << ofs); ++ val |= v << ofs; ++ writel(val, &base->word[word].data[i]); ++ ++ if ((bit + size - 1) / 32 > i) { ++ val = readl(&base->word[word].data[i + 1]); ++ val &= ~(mask >> (ofs ? (32 - ofs) : 0)); ++ val |= v >> (ofs ? (32 - ofs) : 0); ++ writel(val, &base->word[word].data[i + 1]); ++ } ++} ++ ++static u32 ipu_ch_param_read_field(struct ipuv3_channel *ch, u32 wbs) ++{ ++ struct ipu_ch_param __iomem *base = ipu_get_cpmem(ch); ++ u32 bit = (wbs >> 8) % 160; ++ u32 size = wbs & 0xff; ++ u32 word = (wbs >> 8) / 160; ++ u32 i = bit / 32; ++ u32 ofs = bit % 32; ++ u32 mask = (1 << size) - 1; ++ u32 val = 0; ++ ++ pr_debug("%s %d %d %d\n", __func__, word, bit , size); ++ ++ val = (readl(&base->word[word].data[i]) >> ofs) & mask; ++ ++ if ((bit + size - 1) / 32 > i) { ++ u32 tmp; ++ ++ tmp = readl(&base->word[word].data[i + 1]); ++ tmp &= mask >> (ofs ? (32 - ofs) : 0); ++ val |= tmp << (ofs ? (32 - ofs) : 0); ++ } ++ ++ return val; ++} ++ ++/* ++ * The V4L2 spec defines packed RGB formats in memory byte order, which from ++ * point of view of the IPU corresponds to little-endian words with the first ++ * component in the least significant bits. ++ * The DRM pixel formats and IPU internal representation are ordered the other ++ * way around, with the first named component ordered at the most significant ++ * bits. Further, V4L2 formats are not well defined: ++ * https://linuxtv.org/downloads/v4l-dvb-apis/packed-rgb.html ++ * We choose the interpretation which matches GStreamer behavior. ++ */ ++static int v4l2_pix_fmt_to_drm_fourcc(u32 pixelformat) ++{ ++ switch (pixelformat) { ++ case V4L2_PIX_FMT_RGB565: ++ /* ++ * Here we choose the 'corrected' interpretation of RGBP, a ++ * little-endian 16-bit word with the red component at the most ++ * significant bits: ++ * g[2:0]b[4:0] r[4:0]g[5:3] <=> [16:0] R:G:B ++ */ ++ return DRM_FORMAT_RGB565; ++ case V4L2_PIX_FMT_BGR24: ++ /* B G R <=> [24:0] R:G:B */ ++ return DRM_FORMAT_RGB888; ++ case V4L2_PIX_FMT_RGB24: ++ /* R G B <=> [24:0] B:G:R */ ++ return DRM_FORMAT_BGR888; ++ case V4L2_PIX_FMT_BGR32: ++ /* B G R A <=> [32:0] A:B:G:R */ ++ return DRM_FORMAT_XRGB8888; ++ case V4L2_PIX_FMT_RGB32: ++ /* R G B A <=> [32:0] A:B:G:R */ ++ return DRM_FORMAT_XBGR8888; ++ case V4L2_PIX_FMT_ABGR32: ++ /* B G R A <=> [32:0] A:R:G:B */ ++ return DRM_FORMAT_ARGB8888; ++ case V4L2_PIX_FMT_XBGR32: ++ /* B G R X <=> [32:0] X:R:G:B */ ++ return DRM_FORMAT_XRGB8888; ++ case V4L2_PIX_FMT_BGRA32: ++ /* A B G R <=> [32:0] R:G:B:A */ ++ return DRM_FORMAT_RGBA8888; ++ case V4L2_PIX_FMT_BGRX32: ++ /* X B G R <=> [32:0] R:G:B:X */ ++ return DRM_FORMAT_RGBX8888; ++ case V4L2_PIX_FMT_RGBA32: ++ /* R G B A <=> [32:0] A:B:G:R */ ++ return DRM_FORMAT_ABGR8888; ++ case V4L2_PIX_FMT_RGBX32: ++ /* R G B X <=> [32:0] X:B:G:R */ ++ return DRM_FORMAT_XBGR8888; ++ case V4L2_PIX_FMT_ARGB32: ++ /* A R G B <=> [32:0] B:G:R:A */ ++ return DRM_FORMAT_BGRA8888; ++ case V4L2_PIX_FMT_XRGB32: ++ /* X R G B <=> [32:0] B:G:R:X */ ++ return DRM_FORMAT_BGRX8888; ++ case V4L2_PIX_FMT_UYVY: ++ return DRM_FORMAT_UYVY; ++ case V4L2_PIX_FMT_YUYV: ++ return DRM_FORMAT_YUYV; ++ case V4L2_PIX_FMT_YUV420: ++ return DRM_FORMAT_YUV420; ++ case V4L2_PIX_FMT_YUV422P: ++ return DRM_FORMAT_YUV422; ++ case V4L2_PIX_FMT_YVU420: ++ return DRM_FORMAT_YVU420; ++ case V4L2_PIX_FMT_NV12: ++ return DRM_FORMAT_NV12; ++ case V4L2_PIX_FMT_NV16: ++ return DRM_FORMAT_NV16; ++ } ++ ++ return -EINVAL; ++} ++ ++void ipu_cpmem_zero(struct ipuv3_channel *ch) ++{ ++ struct ipu_ch_param __iomem *p = ipu_get_cpmem(ch); ++ void __iomem *base = p; ++ int i; ++ ++ for (i = 0; i < sizeof(*p) / sizeof(u32); i++) ++ writel(0, base + i * sizeof(u32)); ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_zero); ++ ++void ipu_cpmem_set_resolution(struct ipuv3_channel *ch, int xres, int yres) ++{ ++ ipu_ch_param_write_field(ch, IPU_FIELD_FW, xres - 1); ++ ipu_ch_param_write_field(ch, IPU_FIELD_FH, yres - 1); ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_set_resolution); ++ ++void ipu_cpmem_skip_odd_chroma_rows(struct ipuv3_channel *ch) ++{ ++ ipu_ch_param_write_field(ch, IPU_FIELD_RDRW, 1); ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_skip_odd_chroma_rows); ++ ++void ipu_cpmem_set_stride(struct ipuv3_channel *ch, int stride) ++{ ++ ipu_ch_param_write_field(ch, IPU_FIELD_SLY, stride - 1); ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_set_stride); ++ ++void ipu_cpmem_set_high_priority(struct ipuv3_channel *ch) ++{ ++ struct ipu_soc *ipu = ch->ipu; ++ u32 val; ++ ++ if (ipu->ipu_type == IPUV3EX) ++ ipu_ch_param_write_field(ch, IPU_FIELD_ID, 1); ++ ++ val = ipu_idmac_read(ipu, IDMAC_CHA_PRI(ch->num)); ++ val |= 1 << (ch->num % 32); ++ ipu_idmac_write(ipu, val, IDMAC_CHA_PRI(ch->num)); ++}; ++EXPORT_SYMBOL_GPL(ipu_cpmem_set_high_priority); ++ ++void ipu_cpmem_set_buffer(struct ipuv3_channel *ch, int bufnum, dma_addr_t buf) ++{ ++ WARN_ON_ONCE(buf & 0x7); ++ ++ if (bufnum) ++ ipu_ch_param_write_field(ch, IPU_FIELD_EBA1, buf >> 3); ++ else ++ ipu_ch_param_write_field(ch, IPU_FIELD_EBA0, buf >> 3); ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_set_buffer); ++ ++void ipu_cpmem_set_uv_offset(struct ipuv3_channel *ch, u32 u_off, u32 v_off) ++{ ++ WARN_ON_ONCE((u_off & 0x7) || (v_off & 0x7)); ++ ++ ipu_ch_param_write_field(ch, IPU_FIELD_UBO, u_off / 8); ++ ipu_ch_param_write_field(ch, IPU_FIELD_VBO, v_off / 8); ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_set_uv_offset); ++ ++void ipu_cpmem_interlaced_scan(struct ipuv3_channel *ch, int stride, ++ u32 pixelformat) ++{ ++ u32 ilo, sly, sluv; ++ ++ if (stride < 0) { ++ stride = -stride; ++ ilo = 0x100000 - (stride / 8); ++ } else { ++ ilo = stride / 8; ++ } ++ ++ sly = (stride * 2) - 1; ++ ++ switch (pixelformat) { ++ case V4L2_PIX_FMT_YUV420: ++ case V4L2_PIX_FMT_YVU420: ++ sluv = stride / 2 - 1; ++ break; ++ case V4L2_PIX_FMT_NV12: ++ sluv = stride - 1; ++ break; ++ case V4L2_PIX_FMT_YUV422P: ++ sluv = stride - 1; ++ break; ++ case V4L2_PIX_FMT_NV16: ++ sluv = stride * 2 - 1; ++ break; ++ default: ++ sluv = 0; ++ break; ++ } ++ ++ ipu_ch_param_write_field(ch, IPU_FIELD_SO, 1); ++ ipu_ch_param_write_field(ch, IPU_FIELD_ILO, ilo); ++ ipu_ch_param_write_field(ch, IPU_FIELD_SLY, sly); ++ if (sluv) ++ ipu_ch_param_write_field(ch, IPU_FIELD_SLUV, sluv); ++}; ++EXPORT_SYMBOL_GPL(ipu_cpmem_interlaced_scan); ++ ++void ipu_cpmem_set_axi_id(struct ipuv3_channel *ch, u32 id) ++{ ++ id &= 0x3; ++ ipu_ch_param_write_field(ch, IPU_FIELD_ID, id); ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_set_axi_id); ++ ++int ipu_cpmem_get_burstsize(struct ipuv3_channel *ch) ++{ ++ return ipu_ch_param_read_field(ch, IPU_FIELD_NPB) + 1; ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_get_burstsize); ++ ++void ipu_cpmem_set_burstsize(struct ipuv3_channel *ch, int burstsize) ++{ ++ ipu_ch_param_write_field(ch, IPU_FIELD_NPB, burstsize - 1); ++}; ++EXPORT_SYMBOL_GPL(ipu_cpmem_set_burstsize); ++ ++void ipu_cpmem_set_block_mode(struct ipuv3_channel *ch) ++{ ++ ipu_ch_param_write_field(ch, IPU_FIELD_BM, 1); ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_set_block_mode); ++ ++void ipu_cpmem_set_rotation(struct ipuv3_channel *ch, ++ enum ipu_rotate_mode rot) ++{ ++ u32 temp_rot = bitrev8(rot) >> 5; ++ ++ ipu_ch_param_write_field(ch, IPU_FIELD_ROT_HF_VF, temp_rot); ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_set_rotation); ++ ++int ipu_cpmem_set_format_rgb(struct ipuv3_channel *ch, ++ const struct ipu_rgb *rgb) ++{ ++ int bpp = 0, npb = 0, ro, go, bo, to; ++ ++ ro = rgb->bits_per_pixel - rgb->red.length - rgb->red.offset; ++ go = rgb->bits_per_pixel - rgb->green.length - rgb->green.offset; ++ bo = rgb->bits_per_pixel - rgb->blue.length - rgb->blue.offset; ++ to = rgb->bits_per_pixel - rgb->transp.length - rgb->transp.offset; ++ ++ ipu_ch_param_write_field(ch, IPU_FIELD_WID0, rgb->red.length - 1); ++ ipu_ch_param_write_field(ch, IPU_FIELD_OFS0, ro); ++ ipu_ch_param_write_field(ch, IPU_FIELD_WID1, rgb->green.length - 1); ++ ipu_ch_param_write_field(ch, IPU_FIELD_OFS1, go); ++ ipu_ch_param_write_field(ch, IPU_FIELD_WID2, rgb->blue.length - 1); ++ ipu_ch_param_write_field(ch, IPU_FIELD_OFS2, bo); ++ ++ if (rgb->transp.length) { ++ ipu_ch_param_write_field(ch, IPU_FIELD_WID3, ++ rgb->transp.length - 1); ++ ipu_ch_param_write_field(ch, IPU_FIELD_OFS3, to); ++ } else { ++ ipu_ch_param_write_field(ch, IPU_FIELD_WID3, 7); ++ ipu_ch_param_write_field(ch, IPU_FIELD_OFS3, ++ rgb->bits_per_pixel); ++ } ++ ++ switch (rgb->bits_per_pixel) { ++ case 32: ++ bpp = 0; ++ npb = 15; ++ break; ++ case 24: ++ bpp = 1; ++ npb = 19; ++ break; ++ case 16: ++ bpp = 3; ++ npb = 31; ++ break; ++ case 8: ++ bpp = 5; ++ npb = 63; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ipu_ch_param_write_field(ch, IPU_FIELD_BPP, bpp); ++ ipu_ch_param_write_field(ch, IPU_FIELD_NPB, npb); ++ ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 7); /* rgb mode */ ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_set_format_rgb); ++ ++int ipu_cpmem_set_format_passthrough(struct ipuv3_channel *ch, int width) ++{ ++ int bpp = 0, npb = 0; ++ ++ switch (width) { ++ case 32: ++ bpp = 0; ++ npb = 15; ++ break; ++ case 24: ++ bpp = 1; ++ npb = 19; ++ break; ++ case 16: ++ bpp = 3; ++ npb = 31; ++ break; ++ case 8: ++ bpp = 5; ++ npb = 63; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ ipu_ch_param_write_field(ch, IPU_FIELD_BPP, bpp); ++ ipu_ch_param_write_field(ch, IPU_FIELD_NPB, npb); ++ ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 6); /* raw mode */ ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_set_format_passthrough); ++ ++void ipu_cpmem_set_yuv_interleaved(struct ipuv3_channel *ch, u32 pixel_format) ++{ ++ switch (pixel_format) { ++ case V4L2_PIX_FMT_UYVY: ++ ipu_ch_param_write_field(ch, IPU_FIELD_BPP, 3); /* bits/pixel */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 0xA);/* pix fmt */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31);/* burst size */ ++ break; ++ case V4L2_PIX_FMT_YUYV: ++ ipu_ch_param_write_field(ch, IPU_FIELD_BPP, 3); /* bits/pixel */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 0x8);/* pix fmt */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31);/* burst size */ ++ break; ++ } ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_set_yuv_interleaved); ++ ++void ipu_cpmem_set_yuv_planar_full(struct ipuv3_channel *ch, ++ unsigned int uv_stride, ++ unsigned int u_offset, unsigned int v_offset) ++{ ++ WARN_ON_ONCE((u_offset & 0x7) || (v_offset & 0x7)); ++ ++ ipu_ch_param_write_field(ch, IPU_FIELD_SLUV, uv_stride - 1); ++ ipu_ch_param_write_field(ch, IPU_FIELD_UBO, u_offset / 8); ++ ipu_ch_param_write_field(ch, IPU_FIELD_VBO, v_offset / 8); ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_set_yuv_planar_full); ++ ++static const struct ipu_rgb def_xrgb_32 = { ++ .red = { .offset = 16, .length = 8, }, ++ .green = { .offset = 8, .length = 8, }, ++ .blue = { .offset = 0, .length = 8, }, ++ .transp = { .offset = 24, .length = 8, }, ++ .bits_per_pixel = 32, ++}; ++ ++static const struct ipu_rgb def_xbgr_32 = { ++ .red = { .offset = 0, .length = 8, }, ++ .green = { .offset = 8, .length = 8, }, ++ .blue = { .offset = 16, .length = 8, }, ++ .transp = { .offset = 24, .length = 8, }, ++ .bits_per_pixel = 32, ++}; ++ ++static const struct ipu_rgb def_rgbx_32 = { ++ .red = { .offset = 24, .length = 8, }, ++ .green = { .offset = 16, .length = 8, }, ++ .blue = { .offset = 8, .length = 8, }, ++ .transp = { .offset = 0, .length = 8, }, ++ .bits_per_pixel = 32, ++}; ++ ++static const struct ipu_rgb def_bgrx_32 = { ++ .red = { .offset = 8, .length = 8, }, ++ .green = { .offset = 16, .length = 8, }, ++ .blue = { .offset = 24, .length = 8, }, ++ .transp = { .offset = 0, .length = 8, }, ++ .bits_per_pixel = 32, ++}; ++ ++static const struct ipu_rgb def_rgb_24 = { ++ .red = { .offset = 16, .length = 8, }, ++ .green = { .offset = 8, .length = 8, }, ++ .blue = { .offset = 0, .length = 8, }, ++ .transp = { .offset = 0, .length = 0, }, ++ .bits_per_pixel = 24, ++}; ++ ++static const struct ipu_rgb def_bgr_24 = { ++ .red = { .offset = 0, .length = 8, }, ++ .green = { .offset = 8, .length = 8, }, ++ .blue = { .offset = 16, .length = 8, }, ++ .transp = { .offset = 0, .length = 0, }, ++ .bits_per_pixel = 24, ++}; ++ ++static const struct ipu_rgb def_rgb_16 = { ++ .red = { .offset = 11, .length = 5, }, ++ .green = { .offset = 5, .length = 6, }, ++ .blue = { .offset = 0, .length = 5, }, ++ .transp = { .offset = 0, .length = 0, }, ++ .bits_per_pixel = 16, ++}; ++ ++static const struct ipu_rgb def_bgr_16 = { ++ .red = { .offset = 0, .length = 5, }, ++ .green = { .offset = 5, .length = 6, }, ++ .blue = { .offset = 11, .length = 5, }, ++ .transp = { .offset = 0, .length = 0, }, ++ .bits_per_pixel = 16, ++}; ++ ++static const struct ipu_rgb def_argb_16 = { ++ .red = { .offset = 10, .length = 5, }, ++ .green = { .offset = 5, .length = 5, }, ++ .blue = { .offset = 0, .length = 5, }, ++ .transp = { .offset = 15, .length = 1, }, ++ .bits_per_pixel = 16, ++}; ++ ++static const struct ipu_rgb def_argb_16_4444 = { ++ .red = { .offset = 8, .length = 4, }, ++ .green = { .offset = 4, .length = 4, }, ++ .blue = { .offset = 0, .length = 4, }, ++ .transp = { .offset = 12, .length = 4, }, ++ .bits_per_pixel = 16, ++}; ++ ++static const struct ipu_rgb def_abgr_16 = { ++ .red = { .offset = 0, .length = 5, }, ++ .green = { .offset = 5, .length = 5, }, ++ .blue = { .offset = 10, .length = 5, }, ++ .transp = { .offset = 15, .length = 1, }, ++ .bits_per_pixel = 16, ++}; ++ ++static const struct ipu_rgb def_rgba_16 = { ++ .red = { .offset = 11, .length = 5, }, ++ .green = { .offset = 6, .length = 5, }, ++ .blue = { .offset = 1, .length = 5, }, ++ .transp = { .offset = 0, .length = 1, }, ++ .bits_per_pixel = 16, ++}; ++ ++static const struct ipu_rgb def_bgra_16 = { ++ .red = { .offset = 1, .length = 5, }, ++ .green = { .offset = 6, .length = 5, }, ++ .blue = { .offset = 11, .length = 5, }, ++ .transp = { .offset = 0, .length = 1, }, ++ .bits_per_pixel = 16, ++}; ++ ++#define Y_OFFSET(pix, x, y) ((x) + pix->width * (y)) ++#define U_OFFSET(pix, x, y) ((pix->width * pix->height) + \ ++ (pix->width * ((y) / 2) / 2) + (x) / 2) ++#define V_OFFSET(pix, x, y) ((pix->width * pix->height) + \ ++ (pix->width * pix->height / 4) + \ ++ (pix->width * ((y) / 2) / 2) + (x) / 2) ++#define U2_OFFSET(pix, x, y) ((pix->width * pix->height) + \ ++ (pix->width * (y) / 2) + (x) / 2) ++#define V2_OFFSET(pix, x, y) ((pix->width * pix->height) + \ ++ (pix->width * pix->height / 2) + \ ++ (pix->width * (y) / 2) + (x) / 2) ++#define UV_OFFSET(pix, x, y) ((pix->width * pix->height) + \ ++ (pix->width * ((y) / 2)) + (x)) ++#define UV2_OFFSET(pix, x, y) ((pix->width * pix->height) + \ ++ (pix->width * y) + (x)) ++ ++#define NUM_ALPHA_CHANNELS 7 ++ ++/* See Table 37-12. Alpha channels mapping. */ ++static int ipu_channel_albm(int ch_num) ++{ ++ switch (ch_num) { ++ case IPUV3_CHANNEL_G_MEM_IC_PRP_VF: return 0; ++ case IPUV3_CHANNEL_G_MEM_IC_PP: return 1; ++ case IPUV3_CHANNEL_MEM_FG_SYNC: return 2; ++ case IPUV3_CHANNEL_MEM_FG_ASYNC: return 3; ++ case IPUV3_CHANNEL_MEM_BG_SYNC: return 4; ++ case IPUV3_CHANNEL_MEM_BG_ASYNC: return 5; ++ case IPUV3_CHANNEL_MEM_VDI_PLANE1_COMB: return 6; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static void ipu_cpmem_set_separate_alpha(struct ipuv3_channel *ch) ++{ ++ struct ipu_soc *ipu = ch->ipu; ++ int albm; ++ u32 val; ++ ++ albm = ipu_channel_albm(ch->num); ++ if (albm < 0) ++ return; ++ ++ ipu_ch_param_write_field(ch, IPU_FIELD_ALU, 1); ++ ipu_ch_param_write_field(ch, IPU_FIELD_ALBM, albm); ++ ipu_ch_param_write_field(ch, IPU_FIELD_CRE, 1); ++ ++ val = ipu_idmac_read(ipu, IDMAC_SEP_ALPHA); ++ val |= BIT(ch->num); ++ ipu_idmac_write(ipu, val, IDMAC_SEP_ALPHA); ++} ++ ++int ipu_cpmem_set_fmt(struct ipuv3_channel *ch, u32 drm_fourcc) ++{ ++ switch (drm_fourcc) { ++ case DRM_FORMAT_YUV420: ++ case DRM_FORMAT_YVU420: ++ /* pix format */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 2); ++ /* burst size */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31); ++ break; ++ case DRM_FORMAT_YUV422: ++ case DRM_FORMAT_YVU422: ++ /* pix format */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 1); ++ /* burst size */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31); ++ break; ++ case DRM_FORMAT_YUV444: ++ case DRM_FORMAT_YVU444: ++ /* pix format */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 0); ++ /* burst size */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31); ++ break; ++ case DRM_FORMAT_NV12: ++ /* pix format */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 4); ++ /* burst size */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31); ++ break; ++ case DRM_FORMAT_NV16: ++ /* pix format */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 3); ++ /* burst size */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31); ++ break; ++ case DRM_FORMAT_UYVY: ++ /* bits/pixel */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_BPP, 3); ++ /* pix format */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 0xA); ++ /* burst size */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31); ++ break; ++ case DRM_FORMAT_YUYV: ++ /* bits/pixel */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_BPP, 3); ++ /* pix format */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_PFS, 0x8); ++ /* burst size */ ++ ipu_ch_param_write_field(ch, IPU_FIELD_NPB, 31); ++ break; ++ case DRM_FORMAT_ABGR8888: ++ case DRM_FORMAT_XBGR8888: ++ ipu_cpmem_set_format_rgb(ch, &def_xbgr_32); ++ break; ++ case DRM_FORMAT_ARGB8888: ++ case DRM_FORMAT_XRGB8888: ++ ipu_cpmem_set_format_rgb(ch, &def_xrgb_32); ++ break; ++ case DRM_FORMAT_RGBA8888: ++ case DRM_FORMAT_RGBX8888: ++ case DRM_FORMAT_RGBX8888_A8: ++ ipu_cpmem_set_format_rgb(ch, &def_rgbx_32); ++ break; ++ case DRM_FORMAT_BGRA8888: ++ case DRM_FORMAT_BGRX8888: ++ case DRM_FORMAT_BGRX8888_A8: ++ ipu_cpmem_set_format_rgb(ch, &def_bgrx_32); ++ break; ++ case DRM_FORMAT_BGR888: ++ case DRM_FORMAT_BGR888_A8: ++ ipu_cpmem_set_format_rgb(ch, &def_bgr_24); ++ break; ++ case DRM_FORMAT_RGB888: ++ case DRM_FORMAT_RGB888_A8: ++ ipu_cpmem_set_format_rgb(ch, &def_rgb_24); ++ break; ++ case DRM_FORMAT_RGB565: ++ case DRM_FORMAT_RGB565_A8: ++ ipu_cpmem_set_format_rgb(ch, &def_rgb_16); ++ break; ++ case DRM_FORMAT_BGR565: ++ case DRM_FORMAT_BGR565_A8: ++ ipu_cpmem_set_format_rgb(ch, &def_bgr_16); ++ break; ++ case DRM_FORMAT_ARGB1555: ++ ipu_cpmem_set_format_rgb(ch, &def_argb_16); ++ break; ++ case DRM_FORMAT_ABGR1555: ++ ipu_cpmem_set_format_rgb(ch, &def_abgr_16); ++ break; ++ case DRM_FORMAT_RGBA5551: ++ ipu_cpmem_set_format_rgb(ch, &def_rgba_16); ++ break; ++ case DRM_FORMAT_BGRA5551: ++ ipu_cpmem_set_format_rgb(ch, &def_bgra_16); ++ break; ++ case DRM_FORMAT_ARGB4444: ++ ipu_cpmem_set_format_rgb(ch, &def_argb_16_4444); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ switch (drm_fourcc) { ++ case DRM_FORMAT_RGB565_A8: ++ case DRM_FORMAT_BGR565_A8: ++ case DRM_FORMAT_RGB888_A8: ++ case DRM_FORMAT_BGR888_A8: ++ case DRM_FORMAT_RGBX8888_A8: ++ case DRM_FORMAT_BGRX8888_A8: ++ ipu_ch_param_write_field(ch, IPU_FIELD_WID3, 7); ++ ipu_cpmem_set_separate_alpha(ch); ++ break; ++ default: ++ break; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_set_fmt); ++ ++int ipu_cpmem_set_image(struct ipuv3_channel *ch, struct ipu_image *image) ++{ ++ struct v4l2_pix_format *pix = &image->pix; ++ int offset, u_offset, v_offset; ++ int ret = 0; ++ ++ pr_debug("%s: resolution: %dx%d stride: %d\n", ++ __func__, pix->width, pix->height, ++ pix->bytesperline); ++ ++ ipu_cpmem_set_resolution(ch, image->rect.width, image->rect.height); ++ ipu_cpmem_set_stride(ch, pix->bytesperline); ++ ++ ipu_cpmem_set_fmt(ch, v4l2_pix_fmt_to_drm_fourcc(pix->pixelformat)); ++ ++ switch (pix->pixelformat) { ++ case V4L2_PIX_FMT_YUV420: ++ offset = Y_OFFSET(pix, image->rect.left, image->rect.top); ++ u_offset = image->u_offset ? ++ image->u_offset : U_OFFSET(pix, image->rect.left, ++ image->rect.top) - offset; ++ v_offset = image->v_offset ? ++ image->v_offset : V_OFFSET(pix, image->rect.left, ++ image->rect.top) - offset; ++ ++ ipu_cpmem_set_yuv_planar_full(ch, pix->bytesperline / 2, ++ u_offset, v_offset); ++ break; ++ case V4L2_PIX_FMT_YVU420: ++ offset = Y_OFFSET(pix, image->rect.left, image->rect.top); ++ u_offset = image->u_offset ? ++ image->u_offset : V_OFFSET(pix, image->rect.left, ++ image->rect.top) - offset; ++ v_offset = image->v_offset ? ++ image->v_offset : U_OFFSET(pix, image->rect.left, ++ image->rect.top) - offset; ++ ++ ipu_cpmem_set_yuv_planar_full(ch, pix->bytesperline / 2, ++ u_offset, v_offset); ++ break; ++ case V4L2_PIX_FMT_YUV422P: ++ offset = Y_OFFSET(pix, image->rect.left, image->rect.top); ++ u_offset = image->u_offset ? ++ image->u_offset : U2_OFFSET(pix, image->rect.left, ++ image->rect.top) - offset; ++ v_offset = image->v_offset ? ++ image->v_offset : V2_OFFSET(pix, image->rect.left, ++ image->rect.top) - offset; ++ ++ ipu_cpmem_set_yuv_planar_full(ch, pix->bytesperline / 2, ++ u_offset, v_offset); ++ break; ++ case V4L2_PIX_FMT_NV12: ++ offset = Y_OFFSET(pix, image->rect.left, image->rect.top); ++ u_offset = image->u_offset ? ++ image->u_offset : UV_OFFSET(pix, image->rect.left, ++ image->rect.top) - offset; ++ v_offset = image->v_offset ? image->v_offset : 0; ++ ++ ipu_cpmem_set_yuv_planar_full(ch, pix->bytesperline, ++ u_offset, v_offset); ++ break; ++ case V4L2_PIX_FMT_NV16: ++ offset = Y_OFFSET(pix, image->rect.left, image->rect.top); ++ u_offset = image->u_offset ? ++ image->u_offset : UV2_OFFSET(pix, image->rect.left, ++ image->rect.top) - offset; ++ v_offset = image->v_offset ? image->v_offset : 0; ++ ++ ipu_cpmem_set_yuv_planar_full(ch, pix->bytesperline, ++ u_offset, v_offset); ++ break; ++ case V4L2_PIX_FMT_UYVY: ++ case V4L2_PIX_FMT_YUYV: ++ case V4L2_PIX_FMT_RGB565: ++ offset = image->rect.left * 2 + ++ image->rect.top * pix->bytesperline; ++ break; ++ case V4L2_PIX_FMT_RGB32: ++ case V4L2_PIX_FMT_BGR32: ++ case V4L2_PIX_FMT_ABGR32: ++ case V4L2_PIX_FMT_XBGR32: ++ case V4L2_PIX_FMT_BGRA32: ++ case V4L2_PIX_FMT_BGRX32: ++ case V4L2_PIX_FMT_RGBA32: ++ case V4L2_PIX_FMT_RGBX32: ++ case V4L2_PIX_FMT_ARGB32: ++ case V4L2_PIX_FMT_XRGB32: ++ offset = image->rect.left * 4 + ++ image->rect.top * pix->bytesperline; ++ break; ++ case V4L2_PIX_FMT_RGB24: ++ case V4L2_PIX_FMT_BGR24: ++ offset = image->rect.left * 3 + ++ image->rect.top * pix->bytesperline; ++ break; ++ case V4L2_PIX_FMT_SBGGR8: ++ case V4L2_PIX_FMT_SGBRG8: ++ case V4L2_PIX_FMT_SGRBG8: ++ case V4L2_PIX_FMT_SRGGB8: ++ case V4L2_PIX_FMT_GREY: ++ offset = image->rect.left + image->rect.top * pix->bytesperline; ++ break; ++ case V4L2_PIX_FMT_SBGGR16: ++ case V4L2_PIX_FMT_SGBRG16: ++ case V4L2_PIX_FMT_SGRBG16: ++ case V4L2_PIX_FMT_SRGGB16: ++ case V4L2_PIX_FMT_Y16: ++ offset = image->rect.left * 2 + ++ image->rect.top * pix->bytesperline; ++ break; ++ default: ++ /* This should not happen */ ++ WARN_ON(1); ++ offset = 0; ++ ret = -EINVAL; ++ } ++ ++ ipu_cpmem_set_buffer(ch, 0, image->phys0 + offset); ++ ipu_cpmem_set_buffer(ch, 1, image->phys1 + offset); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_set_image); ++ ++void ipu_cpmem_dump(struct ipuv3_channel *ch) ++{ ++ struct ipu_ch_param __iomem *p = ipu_get_cpmem(ch); ++ struct ipu_soc *ipu = ch->ipu; ++ int chno = ch->num; ++ ++ dev_dbg(ipu->dev, "ch %d word 0 - %08X %08X %08X %08X %08X\n", chno, ++ readl(&p->word[0].data[0]), ++ readl(&p->word[0].data[1]), ++ readl(&p->word[0].data[2]), ++ readl(&p->word[0].data[3]), ++ readl(&p->word[0].data[4])); ++ dev_dbg(ipu->dev, "ch %d word 1 - %08X %08X %08X %08X %08X\n", chno, ++ readl(&p->word[1].data[0]), ++ readl(&p->word[1].data[1]), ++ readl(&p->word[1].data[2]), ++ readl(&p->word[1].data[3]), ++ readl(&p->word[1].data[4])); ++ dev_dbg(ipu->dev, "PFS 0x%x, ", ++ ipu_ch_param_read_field(ch, IPU_FIELD_PFS)); ++ dev_dbg(ipu->dev, "BPP 0x%x, ", ++ ipu_ch_param_read_field(ch, IPU_FIELD_BPP)); ++ dev_dbg(ipu->dev, "NPB 0x%x\n", ++ ipu_ch_param_read_field(ch, IPU_FIELD_NPB)); ++ ++ dev_dbg(ipu->dev, "FW %d, ", ++ ipu_ch_param_read_field(ch, IPU_FIELD_FW)); ++ dev_dbg(ipu->dev, "FH %d, ", ++ ipu_ch_param_read_field(ch, IPU_FIELD_FH)); ++ dev_dbg(ipu->dev, "EBA0 0x%x\n", ++ ipu_ch_param_read_field(ch, IPU_FIELD_EBA0) << 3); ++ dev_dbg(ipu->dev, "EBA1 0x%x\n", ++ ipu_ch_param_read_field(ch, IPU_FIELD_EBA1) << 3); ++ dev_dbg(ipu->dev, "Stride %d\n", ++ ipu_ch_param_read_field(ch, IPU_FIELD_SL)); ++ dev_dbg(ipu->dev, "scan_order %d\n", ++ ipu_ch_param_read_field(ch, IPU_FIELD_SO)); ++ dev_dbg(ipu->dev, "uv_stride %d\n", ++ ipu_ch_param_read_field(ch, IPU_FIELD_SLUV)); ++ dev_dbg(ipu->dev, "u_offset 0x%x\n", ++ ipu_ch_param_read_field(ch, IPU_FIELD_UBO) << 3); ++ dev_dbg(ipu->dev, "v_offset 0x%x\n", ++ ipu_ch_param_read_field(ch, IPU_FIELD_VBO) << 3); ++ ++ dev_dbg(ipu->dev, "Width0 %d+1, ", ++ ipu_ch_param_read_field(ch, IPU_FIELD_WID0)); ++ dev_dbg(ipu->dev, "Width1 %d+1, ", ++ ipu_ch_param_read_field(ch, IPU_FIELD_WID1)); ++ dev_dbg(ipu->dev, "Width2 %d+1, ", ++ ipu_ch_param_read_field(ch, IPU_FIELD_WID2)); ++ dev_dbg(ipu->dev, "Width3 %d+1, ", ++ ipu_ch_param_read_field(ch, IPU_FIELD_WID3)); ++ dev_dbg(ipu->dev, "Offset0 %d, ", ++ ipu_ch_param_read_field(ch, IPU_FIELD_OFS0)); ++ dev_dbg(ipu->dev, "Offset1 %d, ", ++ ipu_ch_param_read_field(ch, IPU_FIELD_OFS1)); ++ dev_dbg(ipu->dev, "Offset2 %d, ", ++ ipu_ch_param_read_field(ch, IPU_FIELD_OFS2)); ++ dev_dbg(ipu->dev, "Offset3 %d\n", ++ ipu_ch_param_read_field(ch, IPU_FIELD_OFS3)); ++} ++EXPORT_SYMBOL_GPL(ipu_cpmem_dump); ++ ++int ipu_cpmem_init(struct ipu_soc *ipu, struct device *dev, unsigned long base) ++{ ++ struct ipu_cpmem *cpmem; ++ ++ cpmem = devm_kzalloc(dev, sizeof(*cpmem), GFP_KERNEL); ++ if (!cpmem) ++ return -ENOMEM; ++ ++ ipu->cpmem_priv = cpmem; ++ ++ spin_lock_init(&cpmem->lock); ++ cpmem->base = devm_ioremap(dev, base, SZ_128K); ++ if (!cpmem->base) ++ return -ENOMEM; ++ ++ dev_dbg(dev, "CPMEM base: 0x%08lx remapped to %p\n", ++ base, cpmem->base); ++ cpmem->ipu = ipu; ++ ++ return 0; ++} ++ ++void ipu_cpmem_exit(struct ipu_soc *ipu) ++{ ++} +--- /dev/null ++++ b/drivers/gpu/ipu-v3/ipu-csi.c +@@ -0,0 +1,821 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2012-2014 Mentor Graphics Inc. ++ * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. ++ */ ++#include <linux/export.h> ++#include <linux/module.h> ++#include <linux/types.h> ++#include <linux/errno.h> ++#include <linux/delay.h> ++#include <linux/io.h> ++#include <linux/err.h> ++#include <linux/platform_device.h> ++#include <linux/videodev2.h> ++#include <uapi/linux/v4l2-mediabus.h> ++#include <linux/clk.h> ++#include <linux/clk-provider.h> ++#include <linux/clkdev.h> ++ ++#include "ipu-prv.h" ++ ++struct ipu_csi { ++ void __iomem *base; ++ int id; ++ u32 module; ++ struct clk *clk_ipu; /* IPU bus clock */ ++ spinlock_t lock; ++ bool inuse; ++ struct ipu_soc *ipu; ++}; ++ ++/* CSI Register Offsets */ ++#define CSI_SENS_CONF 0x0000 ++#define CSI_SENS_FRM_SIZE 0x0004 ++#define CSI_ACT_FRM_SIZE 0x0008 ++#define CSI_OUT_FRM_CTRL 0x000c ++#define CSI_TST_CTRL 0x0010 ++#define CSI_CCIR_CODE_1 0x0014 ++#define CSI_CCIR_CODE_2 0x0018 ++#define CSI_CCIR_CODE_3 0x001c ++#define CSI_MIPI_DI 0x0020 ++#define CSI_SKIP 0x0024 ++#define CSI_CPD_CTRL 0x0028 ++#define CSI_CPD_RC(n) (0x002c + ((n)*4)) ++#define CSI_CPD_RS(n) (0x004c + ((n)*4)) ++#define CSI_CPD_GRC(n) (0x005c + ((n)*4)) ++#define CSI_CPD_GRS(n) (0x007c + ((n)*4)) ++#define CSI_CPD_GBC(n) (0x008c + ((n)*4)) ++#define CSI_CPD_GBS(n) (0x00Ac + ((n)*4)) ++#define CSI_CPD_BC(n) (0x00Bc + ((n)*4)) ++#define CSI_CPD_BS(n) (0x00Dc + ((n)*4)) ++#define CSI_CPD_OFFSET1 0x00ec ++#define CSI_CPD_OFFSET2 0x00f0 ++ ++/* CSI Register Fields */ ++#define CSI_SENS_CONF_DATA_FMT_SHIFT 8 ++#define CSI_SENS_CONF_DATA_FMT_MASK 0x00000700 ++#define CSI_SENS_CONF_DATA_FMT_RGB_YUV444 0L ++#define CSI_SENS_CONF_DATA_FMT_YUV422_YUYV 1L ++#define CSI_SENS_CONF_DATA_FMT_YUV422_UYVY 2L ++#define CSI_SENS_CONF_DATA_FMT_BAYER 3L ++#define CSI_SENS_CONF_DATA_FMT_RGB565 4L ++#define CSI_SENS_CONF_DATA_FMT_RGB555 5L ++#define CSI_SENS_CONF_DATA_FMT_RGB444 6L ++#define CSI_SENS_CONF_DATA_FMT_JPEG 7L ++ ++#define CSI_SENS_CONF_VSYNC_POL_SHIFT 0 ++#define CSI_SENS_CONF_HSYNC_POL_SHIFT 1 ++#define CSI_SENS_CONF_DATA_POL_SHIFT 2 ++#define CSI_SENS_CONF_PIX_CLK_POL_SHIFT 3 ++#define CSI_SENS_CONF_SENS_PRTCL_MASK 0x00000070 ++#define CSI_SENS_CONF_SENS_PRTCL_SHIFT 4 ++#define CSI_SENS_CONF_PACK_TIGHT_SHIFT 7 ++#define CSI_SENS_CONF_DATA_WIDTH_SHIFT 11 ++#define CSI_SENS_CONF_EXT_VSYNC_SHIFT 15 ++#define CSI_SENS_CONF_DIVRATIO_SHIFT 16 ++ ++#define CSI_SENS_CONF_DIVRATIO_MASK 0x00ff0000 ++#define CSI_SENS_CONF_DATA_DEST_SHIFT 24 ++#define CSI_SENS_CONF_DATA_DEST_MASK 0x07000000 ++#define CSI_SENS_CONF_JPEG8_EN_SHIFT 27 ++#define CSI_SENS_CONF_JPEG_EN_SHIFT 28 ++#define CSI_SENS_CONF_FORCE_EOF_SHIFT 29 ++#define CSI_SENS_CONF_DATA_EN_POL_SHIFT 31 ++ ++#define CSI_DATA_DEST_IC 2 ++#define CSI_DATA_DEST_IDMAC 4 ++ ++#define CSI_CCIR_ERR_DET_EN 0x01000000 ++#define CSI_HORI_DOWNSIZE_EN 0x80000000 ++#define CSI_VERT_DOWNSIZE_EN 0x40000000 ++#define CSI_TEST_GEN_MODE_EN 0x01000000 ++ ++#define CSI_HSC_MASK 0x1fff0000 ++#define CSI_HSC_SHIFT 16 ++#define CSI_VSC_MASK 0x00000fff ++#define CSI_VSC_SHIFT 0 ++ ++#define CSI_TEST_GEN_R_MASK 0x000000ff ++#define CSI_TEST_GEN_R_SHIFT 0 ++#define CSI_TEST_GEN_G_MASK 0x0000ff00 ++#define CSI_TEST_GEN_G_SHIFT 8 ++#define CSI_TEST_GEN_B_MASK 0x00ff0000 ++#define CSI_TEST_GEN_B_SHIFT 16 ++ ++#define CSI_MAX_RATIO_SKIP_SMFC_MASK 0x00000007 ++#define CSI_MAX_RATIO_SKIP_SMFC_SHIFT 0 ++#define CSI_SKIP_SMFC_MASK 0x000000f8 ++#define CSI_SKIP_SMFC_SHIFT 3 ++#define CSI_ID_2_SKIP_MASK 0x00000300 ++#define CSI_ID_2_SKIP_SHIFT 8 ++ ++#define CSI_COLOR_FIRST_ROW_MASK 0x00000002 ++#define CSI_COLOR_FIRST_COMP_MASK 0x00000001 ++ ++/* MIPI CSI-2 data types */ ++#define MIPI_DT_YUV420 0x18 /* YYY.../UYVY.... */ ++#define MIPI_DT_YUV420_LEGACY 0x1a /* UYY.../VYY... */ ++#define MIPI_DT_YUV422 0x1e /* UYVY... */ ++#define MIPI_DT_RGB444 0x20 ++#define MIPI_DT_RGB555 0x21 ++#define MIPI_DT_RGB565 0x22 ++#define MIPI_DT_RGB666 0x23 ++#define MIPI_DT_RGB888 0x24 ++#define MIPI_DT_RAW6 0x28 ++#define MIPI_DT_RAW7 0x29 ++#define MIPI_DT_RAW8 0x2a ++#define MIPI_DT_RAW10 0x2b ++#define MIPI_DT_RAW12 0x2c ++#define MIPI_DT_RAW14 0x2d ++ ++/* ++ * Bitfield of CSI bus signal polarities and modes. ++ */ ++struct ipu_csi_bus_config { ++ unsigned data_width:4; ++ unsigned clk_mode:3; ++ unsigned ext_vsync:1; ++ unsigned vsync_pol:1; ++ unsigned hsync_pol:1; ++ unsigned pixclk_pol:1; ++ unsigned data_pol:1; ++ unsigned sens_clksrc:1; ++ unsigned pack_tight:1; ++ unsigned force_eof:1; ++ unsigned data_en_pol:1; ++ ++ unsigned data_fmt; ++ unsigned mipi_dt; ++}; ++ ++/* ++ * Enumeration of CSI data bus widths. ++ */ ++enum ipu_csi_data_width { ++ IPU_CSI_DATA_WIDTH_4 = 0, ++ IPU_CSI_DATA_WIDTH_8 = 1, ++ IPU_CSI_DATA_WIDTH_10 = 3, ++ IPU_CSI_DATA_WIDTH_12 = 5, ++ IPU_CSI_DATA_WIDTH_16 = 9, ++}; ++ ++/* ++ * Enumeration of CSI clock modes. ++ */ ++enum ipu_csi_clk_mode { ++ IPU_CSI_CLK_MODE_GATED_CLK, ++ IPU_CSI_CLK_MODE_NONGATED_CLK, ++ IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE, ++ IPU_CSI_CLK_MODE_CCIR656_INTERLACED, ++ IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR, ++ IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR, ++ IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR, ++ IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR, ++}; ++ ++static inline u32 ipu_csi_read(struct ipu_csi *csi, unsigned offset) ++{ ++ return readl(csi->base + offset); ++} ++ ++static inline void ipu_csi_write(struct ipu_csi *csi, u32 value, ++ unsigned offset) ++{ ++ writel(value, csi->base + offset); ++} ++ ++/* ++ * Set mclk division ratio for generating test mode mclk. Only used ++ * for test generator. ++ */ ++static int ipu_csi_set_testgen_mclk(struct ipu_csi *csi, u32 pixel_clk, ++ u32 ipu_clk) ++{ ++ u32 temp; ++ int div_ratio; ++ ++ div_ratio = (ipu_clk / pixel_clk) - 1; ++ ++ if (div_ratio > 0xFF || div_ratio < 0) { ++ dev_err(csi->ipu->dev, ++ "value of pixel_clk extends normal range\n"); ++ return -EINVAL; ++ } ++ ++ temp = ipu_csi_read(csi, CSI_SENS_CONF); ++ temp &= ~CSI_SENS_CONF_DIVRATIO_MASK; ++ ipu_csi_write(csi, temp | (div_ratio << CSI_SENS_CONF_DIVRATIO_SHIFT), ++ CSI_SENS_CONF); ++ ++ return 0; ++} ++ ++/* ++ * Find the CSI data format and data width for the given V4L2 media ++ * bus pixel format code. ++ */ ++static int mbus_code_to_bus_cfg(struct ipu_csi_bus_config *cfg, u32 mbus_code, ++ enum v4l2_mbus_type mbus_type) ++{ ++ switch (mbus_code) { ++ case MEDIA_BUS_FMT_BGR565_2X8_BE: ++ case MEDIA_BUS_FMT_BGR565_2X8_LE: ++ case MEDIA_BUS_FMT_RGB565_2X8_BE: ++ case MEDIA_BUS_FMT_RGB565_2X8_LE: ++ if (mbus_type == V4L2_MBUS_CSI2_DPHY) ++ cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_RGB565; ++ else ++ cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; ++ cfg->mipi_dt = MIPI_DT_RGB565; ++ cfg->data_width = IPU_CSI_DATA_WIDTH_8; ++ break; ++ case MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE: ++ case MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE: ++ cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_RGB444; ++ cfg->mipi_dt = MIPI_DT_RGB444; ++ cfg->data_width = IPU_CSI_DATA_WIDTH_8; ++ break; ++ case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE: ++ case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE: ++ cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_RGB555; ++ cfg->mipi_dt = MIPI_DT_RGB555; ++ cfg->data_width = IPU_CSI_DATA_WIDTH_8; ++ break; ++ case MEDIA_BUS_FMT_RGB888_1X24: ++ case MEDIA_BUS_FMT_BGR888_1X24: ++ cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_RGB_YUV444; ++ cfg->mipi_dt = MIPI_DT_RGB888; ++ cfg->data_width = IPU_CSI_DATA_WIDTH_8; ++ break; ++ case MEDIA_BUS_FMT_UYVY8_2X8: ++ cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_UYVY; ++ cfg->mipi_dt = MIPI_DT_YUV422; ++ cfg->data_width = IPU_CSI_DATA_WIDTH_8; ++ break; ++ case MEDIA_BUS_FMT_YUYV8_2X8: ++ cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_YUYV; ++ cfg->mipi_dt = MIPI_DT_YUV422; ++ cfg->data_width = IPU_CSI_DATA_WIDTH_8; ++ break; ++ case MEDIA_BUS_FMT_UYVY8_1X16: ++ case MEDIA_BUS_FMT_YUYV8_1X16: ++ cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; ++ cfg->mipi_dt = MIPI_DT_YUV422; ++ cfg->data_width = IPU_CSI_DATA_WIDTH_16; ++ break; ++ case MEDIA_BUS_FMT_SBGGR8_1X8: ++ case MEDIA_BUS_FMT_SGBRG8_1X8: ++ case MEDIA_BUS_FMT_SGRBG8_1X8: ++ case MEDIA_BUS_FMT_SRGGB8_1X8: ++ case MEDIA_BUS_FMT_Y8_1X8: ++ cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; ++ cfg->mipi_dt = MIPI_DT_RAW8; ++ cfg->data_width = IPU_CSI_DATA_WIDTH_8; ++ break; ++ case MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8: ++ case MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8: ++ case MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8: ++ case MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8: ++ case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE: ++ case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE: ++ case MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_BE: ++ case MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_LE: ++ cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; ++ cfg->mipi_dt = MIPI_DT_RAW10; ++ cfg->data_width = IPU_CSI_DATA_WIDTH_8; ++ break; ++ case MEDIA_BUS_FMT_SBGGR10_1X10: ++ case MEDIA_BUS_FMT_SGBRG10_1X10: ++ case MEDIA_BUS_FMT_SGRBG10_1X10: ++ case MEDIA_BUS_FMT_SRGGB10_1X10: ++ case MEDIA_BUS_FMT_Y10_1X10: ++ cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; ++ cfg->mipi_dt = MIPI_DT_RAW10; ++ cfg->data_width = IPU_CSI_DATA_WIDTH_10; ++ break; ++ case MEDIA_BUS_FMT_SBGGR12_1X12: ++ case MEDIA_BUS_FMT_SGBRG12_1X12: ++ case MEDIA_BUS_FMT_SGRBG12_1X12: ++ case MEDIA_BUS_FMT_SRGGB12_1X12: ++ case MEDIA_BUS_FMT_Y12_1X12: ++ cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; ++ cfg->mipi_dt = MIPI_DT_RAW12; ++ cfg->data_width = IPU_CSI_DATA_WIDTH_12; ++ break; ++ case MEDIA_BUS_FMT_JPEG_1X8: ++ /* TODO */ ++ cfg->data_fmt = CSI_SENS_CONF_DATA_FMT_JPEG; ++ cfg->mipi_dt = MIPI_DT_RAW8; ++ cfg->data_width = IPU_CSI_DATA_WIDTH_8; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++/* translate alternate field mode based on given standard */ ++static inline enum v4l2_field ++ipu_csi_translate_field(enum v4l2_field field, v4l2_std_id std) ++{ ++ return (field != V4L2_FIELD_ALTERNATE) ? field : ++ ((std & V4L2_STD_525_60) ? ++ V4L2_FIELD_SEQ_BT : V4L2_FIELD_SEQ_TB); ++} ++ ++/* ++ * Fill a CSI bus config struct from mbus_config and mbus_framefmt. ++ */ ++static int fill_csi_bus_cfg(struct ipu_csi_bus_config *csicfg, ++ const struct v4l2_mbus_config *mbus_cfg, ++ const struct v4l2_mbus_framefmt *mbus_fmt) ++{ ++ int ret; ++ ++ memset(csicfg, 0, sizeof(*csicfg)); ++ ++ ret = mbus_code_to_bus_cfg(csicfg, mbus_fmt->code, mbus_cfg->type); ++ if (ret < 0) ++ return ret; ++ ++ switch (mbus_cfg->type) { ++ case V4L2_MBUS_PARALLEL: ++ csicfg->ext_vsync = 1; ++ csicfg->vsync_pol = (mbus_cfg->flags & ++ V4L2_MBUS_VSYNC_ACTIVE_LOW) ? 1 : 0; ++ csicfg->hsync_pol = (mbus_cfg->flags & ++ V4L2_MBUS_HSYNC_ACTIVE_LOW) ? 1 : 0; ++ csicfg->pixclk_pol = (mbus_cfg->flags & ++ V4L2_MBUS_PCLK_SAMPLE_FALLING) ? 1 : 0; ++ csicfg->clk_mode = IPU_CSI_CLK_MODE_GATED_CLK; ++ break; ++ case V4L2_MBUS_BT656: ++ csicfg->ext_vsync = 0; ++ if (V4L2_FIELD_HAS_BOTH(mbus_fmt->field) || ++ mbus_fmt->field == V4L2_FIELD_ALTERNATE) ++ csicfg->clk_mode = IPU_CSI_CLK_MODE_CCIR656_INTERLACED; ++ else ++ csicfg->clk_mode = IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE; ++ break; ++ case V4L2_MBUS_CSI2_DPHY: ++ /* ++ * MIPI CSI-2 requires non gated clock mode, all other ++ * parameters are not applicable for MIPI CSI-2 bus. ++ */ ++ csicfg->clk_mode = IPU_CSI_CLK_MODE_NONGATED_CLK; ++ break; ++ default: ++ /* will never get here, keep compiler quiet */ ++ break; ++ } ++ ++ return 0; ++} ++ ++static int ++ipu_csi_set_bt_interlaced_codes(struct ipu_csi *csi, ++ const struct v4l2_mbus_framefmt *infmt, ++ const struct v4l2_mbus_framefmt *outfmt, ++ v4l2_std_id std) ++{ ++ enum v4l2_field infield, outfield; ++ bool swap_fields; ++ ++ /* get translated field type of input and output */ ++ infield = ipu_csi_translate_field(infmt->field, std); ++ outfield = ipu_csi_translate_field(outfmt->field, std); ++ ++ /* ++ * Write the H-V-F codes the CSI will match against the ++ * incoming data for start/end of active and blanking ++ * field intervals. If input and output field types are ++ * sequential but not the same (one is SEQ_BT and the other ++ * is SEQ_TB), swap the F-bit so that the CSI will capture ++ * field 1 lines before field 0 lines. ++ */ ++ swap_fields = (V4L2_FIELD_IS_SEQUENTIAL(infield) && ++ V4L2_FIELD_IS_SEQUENTIAL(outfield) && ++ infield != outfield); ++ ++ if (!swap_fields) { ++ /* ++ * Field0BlankEnd = 110, Field0BlankStart = 010 ++ * Field0ActiveEnd = 100, Field0ActiveStart = 000 ++ * Field1BlankEnd = 111, Field1BlankStart = 011 ++ * Field1ActiveEnd = 101, Field1ActiveStart = 001 ++ */ ++ ipu_csi_write(csi, 0x40596 | CSI_CCIR_ERR_DET_EN, ++ CSI_CCIR_CODE_1); ++ ipu_csi_write(csi, 0xD07DF, CSI_CCIR_CODE_2); ++ } else { ++ dev_dbg(csi->ipu->dev, "capture field swap\n"); ++ ++ /* same as above but with F-bit inverted */ ++ ipu_csi_write(csi, 0xD07DF | CSI_CCIR_ERR_DET_EN, ++ CSI_CCIR_CODE_1); ++ ipu_csi_write(csi, 0x40596, CSI_CCIR_CODE_2); ++ } ++ ++ ipu_csi_write(csi, 0xFF0000, CSI_CCIR_CODE_3); ++ ++ return 0; ++} ++ ++ ++int ipu_csi_init_interface(struct ipu_csi *csi, ++ const struct v4l2_mbus_config *mbus_cfg, ++ const struct v4l2_mbus_framefmt *infmt, ++ const struct v4l2_mbus_framefmt *outfmt) ++{ ++ struct ipu_csi_bus_config cfg; ++ unsigned long flags; ++ u32 width, height, data = 0; ++ v4l2_std_id std; ++ int ret; ++ ++ ret = fill_csi_bus_cfg(&cfg, mbus_cfg, infmt); ++ if (ret < 0) ++ return ret; ++ ++ /* set default sensor frame width and height */ ++ width = infmt->width; ++ height = infmt->height; ++ if (infmt->field == V4L2_FIELD_ALTERNATE) ++ height *= 2; ++ ++ /* Set the CSI_SENS_CONF register remaining fields */ ++ data |= cfg.data_width << CSI_SENS_CONF_DATA_WIDTH_SHIFT | ++ cfg.data_fmt << CSI_SENS_CONF_DATA_FMT_SHIFT | ++ cfg.data_pol << CSI_SENS_CONF_DATA_POL_SHIFT | ++ cfg.vsync_pol << CSI_SENS_CONF_VSYNC_POL_SHIFT | ++ cfg.hsync_pol << CSI_SENS_CONF_HSYNC_POL_SHIFT | ++ cfg.pixclk_pol << CSI_SENS_CONF_PIX_CLK_POL_SHIFT | ++ cfg.ext_vsync << CSI_SENS_CONF_EXT_VSYNC_SHIFT | ++ cfg.clk_mode << CSI_SENS_CONF_SENS_PRTCL_SHIFT | ++ cfg.pack_tight << CSI_SENS_CONF_PACK_TIGHT_SHIFT | ++ cfg.force_eof << CSI_SENS_CONF_FORCE_EOF_SHIFT | ++ cfg.data_en_pol << CSI_SENS_CONF_DATA_EN_POL_SHIFT; ++ ++ spin_lock_irqsave(&csi->lock, flags); ++ ++ ipu_csi_write(csi, data, CSI_SENS_CONF); ++ ++ /* Set CCIR registers */ ++ ++ switch (cfg.clk_mode) { ++ case IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE: ++ ipu_csi_write(csi, 0x40030, CSI_CCIR_CODE_1); ++ ipu_csi_write(csi, 0xFF0000, CSI_CCIR_CODE_3); ++ break; ++ case IPU_CSI_CLK_MODE_CCIR656_INTERLACED: ++ if (width == 720 && height == 480) { ++ std = V4L2_STD_NTSC; ++ height = 525; ++ } else if (width == 720 && height == 576) { ++ std = V4L2_STD_PAL; ++ height = 625; ++ } else { ++ dev_err(csi->ipu->dev, ++ "Unsupported interlaced video mode\n"); ++ ret = -EINVAL; ++ goto out_unlock; ++ } ++ ++ ret = ipu_csi_set_bt_interlaced_codes(csi, infmt, outfmt, std); ++ if (ret) ++ goto out_unlock; ++ break; ++ case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR: ++ case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR: ++ case IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR: ++ case IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR: ++ ipu_csi_write(csi, 0x40030 | CSI_CCIR_ERR_DET_EN, ++ CSI_CCIR_CODE_1); ++ ipu_csi_write(csi, 0xFF0000, CSI_CCIR_CODE_3); ++ break; ++ case IPU_CSI_CLK_MODE_GATED_CLK: ++ case IPU_CSI_CLK_MODE_NONGATED_CLK: ++ ipu_csi_write(csi, 0, CSI_CCIR_CODE_1); ++ break; ++ } ++ ++ /* Setup sensor frame size */ ++ ipu_csi_write(csi, (width - 1) | ((height - 1) << 16), ++ CSI_SENS_FRM_SIZE); ++ ++ dev_dbg(csi->ipu->dev, "CSI_SENS_CONF = 0x%08X\n", ++ ipu_csi_read(csi, CSI_SENS_CONF)); ++ dev_dbg(csi->ipu->dev, "CSI_ACT_FRM_SIZE = 0x%08X\n", ++ ipu_csi_read(csi, CSI_ACT_FRM_SIZE)); ++ ++out_unlock: ++ spin_unlock_irqrestore(&csi->lock, flags); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(ipu_csi_init_interface); ++ ++bool ipu_csi_is_interlaced(struct ipu_csi *csi) ++{ ++ unsigned long flags; ++ u32 sensor_protocol; ++ ++ spin_lock_irqsave(&csi->lock, flags); ++ sensor_protocol = ++ (ipu_csi_read(csi, CSI_SENS_CONF) & ++ CSI_SENS_CONF_SENS_PRTCL_MASK) >> ++ CSI_SENS_CONF_SENS_PRTCL_SHIFT; ++ spin_unlock_irqrestore(&csi->lock, flags); ++ ++ switch (sensor_protocol) { ++ case IPU_CSI_CLK_MODE_GATED_CLK: ++ case IPU_CSI_CLK_MODE_NONGATED_CLK: ++ case IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE: ++ case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR: ++ case IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR: ++ return false; ++ case IPU_CSI_CLK_MODE_CCIR656_INTERLACED: ++ case IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR: ++ case IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR: ++ return true; ++ default: ++ dev_err(csi->ipu->dev, ++ "CSI %d sensor protocol unsupported\n", csi->id); ++ return false; ++ } ++} ++EXPORT_SYMBOL_GPL(ipu_csi_is_interlaced); ++ ++void ipu_csi_get_window(struct ipu_csi *csi, struct v4l2_rect *w) ++{ ++ unsigned long flags; ++ u32 reg; ++ ++ spin_lock_irqsave(&csi->lock, flags); ++ ++ reg = ipu_csi_read(csi, CSI_ACT_FRM_SIZE); ++ w->width = (reg & 0xFFFF) + 1; ++ w->height = (reg >> 16 & 0xFFFF) + 1; ++ ++ reg = ipu_csi_read(csi, CSI_OUT_FRM_CTRL); ++ w->left = (reg & CSI_HSC_MASK) >> CSI_HSC_SHIFT; ++ w->top = (reg & CSI_VSC_MASK) >> CSI_VSC_SHIFT; ++ ++ spin_unlock_irqrestore(&csi->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_csi_get_window); ++ ++void ipu_csi_set_window(struct ipu_csi *csi, struct v4l2_rect *w) ++{ ++ unsigned long flags; ++ u32 reg; ++ ++ spin_lock_irqsave(&csi->lock, flags); ++ ++ ipu_csi_write(csi, (w->width - 1) | ((w->height - 1) << 16), ++ CSI_ACT_FRM_SIZE); ++ ++ reg = ipu_csi_read(csi, CSI_OUT_FRM_CTRL); ++ reg &= ~(CSI_HSC_MASK | CSI_VSC_MASK); ++ reg |= ((w->top << CSI_VSC_SHIFT) | (w->left << CSI_HSC_SHIFT)); ++ ipu_csi_write(csi, reg, CSI_OUT_FRM_CTRL); ++ ++ spin_unlock_irqrestore(&csi->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_csi_set_window); ++ ++void ipu_csi_set_downsize(struct ipu_csi *csi, bool horiz, bool vert) ++{ ++ unsigned long flags; ++ u32 reg; ++ ++ spin_lock_irqsave(&csi->lock, flags); ++ ++ reg = ipu_csi_read(csi, CSI_OUT_FRM_CTRL); ++ reg &= ~(CSI_HORI_DOWNSIZE_EN | CSI_VERT_DOWNSIZE_EN); ++ reg |= (horiz ? CSI_HORI_DOWNSIZE_EN : 0) | ++ (vert ? CSI_VERT_DOWNSIZE_EN : 0); ++ ipu_csi_write(csi, reg, CSI_OUT_FRM_CTRL); ++ ++ spin_unlock_irqrestore(&csi->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_csi_set_downsize); ++ ++void ipu_csi_set_test_generator(struct ipu_csi *csi, bool active, ++ u32 r_value, u32 g_value, u32 b_value, ++ u32 pix_clk) ++{ ++ unsigned long flags; ++ u32 ipu_clk = clk_get_rate(csi->clk_ipu); ++ u32 temp; ++ ++ spin_lock_irqsave(&csi->lock, flags); ++ ++ temp = ipu_csi_read(csi, CSI_TST_CTRL); ++ ++ if (!active) { ++ temp &= ~CSI_TEST_GEN_MODE_EN; ++ ipu_csi_write(csi, temp, CSI_TST_CTRL); ++ } else { ++ /* Set sensb_mclk div_ratio */ ++ ipu_csi_set_testgen_mclk(csi, pix_clk, ipu_clk); ++ ++ temp &= ~(CSI_TEST_GEN_R_MASK | CSI_TEST_GEN_G_MASK | ++ CSI_TEST_GEN_B_MASK); ++ temp |= CSI_TEST_GEN_MODE_EN; ++ temp |= (r_value << CSI_TEST_GEN_R_SHIFT) | ++ (g_value << CSI_TEST_GEN_G_SHIFT) | ++ (b_value << CSI_TEST_GEN_B_SHIFT); ++ ipu_csi_write(csi, temp, CSI_TST_CTRL); ++ } ++ ++ spin_unlock_irqrestore(&csi->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_csi_set_test_generator); ++ ++int ipu_csi_set_mipi_datatype(struct ipu_csi *csi, u32 vc, ++ struct v4l2_mbus_framefmt *mbus_fmt) ++{ ++ struct ipu_csi_bus_config cfg; ++ unsigned long flags; ++ u32 temp; ++ int ret; ++ ++ if (vc > 3) ++ return -EINVAL; ++ ++ ret = mbus_code_to_bus_cfg(&cfg, mbus_fmt->code, V4L2_MBUS_CSI2_DPHY); ++ if (ret < 0) ++ return ret; ++ ++ spin_lock_irqsave(&csi->lock, flags); ++ ++ temp = ipu_csi_read(csi, CSI_MIPI_DI); ++ temp &= ~(0xff << (vc * 8)); ++ temp |= (cfg.mipi_dt << (vc * 8)); ++ ipu_csi_write(csi, temp, CSI_MIPI_DI); ++ ++ spin_unlock_irqrestore(&csi->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_csi_set_mipi_datatype); ++ ++int ipu_csi_set_skip_smfc(struct ipu_csi *csi, u32 skip, ++ u32 max_ratio, u32 id) ++{ ++ unsigned long flags; ++ u32 temp; ++ ++ if (max_ratio > 5 || id > 3) ++ return -EINVAL; ++ ++ spin_lock_irqsave(&csi->lock, flags); ++ ++ temp = ipu_csi_read(csi, CSI_SKIP); ++ temp &= ~(CSI_MAX_RATIO_SKIP_SMFC_MASK | CSI_ID_2_SKIP_MASK | ++ CSI_SKIP_SMFC_MASK); ++ temp |= (max_ratio << CSI_MAX_RATIO_SKIP_SMFC_SHIFT) | ++ (id << CSI_ID_2_SKIP_SHIFT) | ++ (skip << CSI_SKIP_SMFC_SHIFT); ++ ipu_csi_write(csi, temp, CSI_SKIP); ++ ++ spin_unlock_irqrestore(&csi->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_csi_set_skip_smfc); ++ ++int ipu_csi_set_dest(struct ipu_csi *csi, enum ipu_csi_dest csi_dest) ++{ ++ unsigned long flags; ++ u32 csi_sens_conf, dest; ++ ++ if (csi_dest == IPU_CSI_DEST_IDMAC) ++ dest = CSI_DATA_DEST_IDMAC; ++ else ++ dest = CSI_DATA_DEST_IC; /* IC or VDIC */ ++ ++ spin_lock_irqsave(&csi->lock, flags); ++ ++ csi_sens_conf = ipu_csi_read(csi, CSI_SENS_CONF); ++ csi_sens_conf &= ~CSI_SENS_CONF_DATA_DEST_MASK; ++ csi_sens_conf |= (dest << CSI_SENS_CONF_DATA_DEST_SHIFT); ++ ipu_csi_write(csi, csi_sens_conf, CSI_SENS_CONF); ++ ++ spin_unlock_irqrestore(&csi->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_csi_set_dest); ++ ++int ipu_csi_enable(struct ipu_csi *csi) ++{ ++ ipu_module_enable(csi->ipu, csi->module); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_csi_enable); ++ ++int ipu_csi_disable(struct ipu_csi *csi) ++{ ++ ipu_module_disable(csi->ipu, csi->module); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_csi_disable); ++ ++struct ipu_csi *ipu_csi_get(struct ipu_soc *ipu, int id) ++{ ++ unsigned long flags; ++ struct ipu_csi *csi, *ret; ++ ++ if (id > 1) ++ return ERR_PTR(-EINVAL); ++ ++ csi = ipu->csi_priv[id]; ++ ret = csi; ++ ++ spin_lock_irqsave(&csi->lock, flags); ++ ++ if (csi->inuse) { ++ ret = ERR_PTR(-EBUSY); ++ goto unlock; ++ } ++ ++ csi->inuse = true; ++unlock: ++ spin_unlock_irqrestore(&csi->lock, flags); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(ipu_csi_get); ++ ++void ipu_csi_put(struct ipu_csi *csi) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&csi->lock, flags); ++ csi->inuse = false; ++ spin_unlock_irqrestore(&csi->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_csi_put); ++ ++int ipu_csi_init(struct ipu_soc *ipu, struct device *dev, int id, ++ unsigned long base, u32 module, struct clk *clk_ipu) ++{ ++ struct ipu_csi *csi; ++ ++ if (id > 1) ++ return -ENODEV; ++ ++ csi = devm_kzalloc(dev, sizeof(*csi), GFP_KERNEL); ++ if (!csi) ++ return -ENOMEM; ++ ++ ipu->csi_priv[id] = csi; ++ ++ spin_lock_init(&csi->lock); ++ csi->module = module; ++ csi->id = id; ++ csi->clk_ipu = clk_ipu; ++ csi->base = devm_ioremap(dev, base, PAGE_SIZE); ++ if (!csi->base) ++ return -ENOMEM; ++ ++ dev_dbg(dev, "CSI%d base: 0x%08lx remapped to %p\n", ++ id, base, csi->base); ++ csi->ipu = ipu; ++ ++ return 0; ++} ++ ++void ipu_csi_exit(struct ipu_soc *ipu, int id) ++{ ++} ++ ++void ipu_csi_dump(struct ipu_csi *csi) ++{ ++ dev_dbg(csi->ipu->dev, "CSI_SENS_CONF: %08x\n", ++ ipu_csi_read(csi, CSI_SENS_CONF)); ++ dev_dbg(csi->ipu->dev, "CSI_SENS_FRM_SIZE: %08x\n", ++ ipu_csi_read(csi, CSI_SENS_FRM_SIZE)); ++ dev_dbg(csi->ipu->dev, "CSI_ACT_FRM_SIZE: %08x\n", ++ ipu_csi_read(csi, CSI_ACT_FRM_SIZE)); ++ dev_dbg(csi->ipu->dev, "CSI_OUT_FRM_CTRL: %08x\n", ++ ipu_csi_read(csi, CSI_OUT_FRM_CTRL)); ++ dev_dbg(csi->ipu->dev, "CSI_TST_CTRL: %08x\n", ++ ipu_csi_read(csi, CSI_TST_CTRL)); ++ dev_dbg(csi->ipu->dev, "CSI_CCIR_CODE_1: %08x\n", ++ ipu_csi_read(csi, CSI_CCIR_CODE_1)); ++ dev_dbg(csi->ipu->dev, "CSI_CCIR_CODE_2: %08x\n", ++ ipu_csi_read(csi, CSI_CCIR_CODE_2)); ++ dev_dbg(csi->ipu->dev, "CSI_CCIR_CODE_3: %08x\n", ++ ipu_csi_read(csi, CSI_CCIR_CODE_3)); ++ dev_dbg(csi->ipu->dev, "CSI_MIPI_DI: %08x\n", ++ ipu_csi_read(csi, CSI_MIPI_DI)); ++ dev_dbg(csi->ipu->dev, "CSI_SKIP: %08x\n", ++ ipu_csi_read(csi, CSI_SKIP)); ++} ++EXPORT_SYMBOL_GPL(ipu_csi_dump); +--- /dev/null ++++ b/drivers/gpu/ipu-v3/ipu-dc.c +@@ -0,0 +1,420 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> ++ * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. ++ */ ++ ++#include <linux/export.h> ++#include <linux/module.h> ++#include <linux/types.h> ++#include <linux/errno.h> ++#include <linux/delay.h> ++#include <linux/interrupt.h> ++#include <linux/io.h> ++ ++#include <video/imx-ipu-v3.h> ++#include "ipu-prv.h" ++ ++#define DC_MAP_CONF_PTR(n) (0x108 + ((n) & ~0x1) * 2) ++#define DC_MAP_CONF_VAL(n) (0x144 + ((n) & ~0x1) * 2) ++ ++#define DC_EVT_NF 0 ++#define DC_EVT_NL 1 ++#define DC_EVT_EOF 2 ++#define DC_EVT_NFIELD 3 ++#define DC_EVT_EOL 4 ++#define DC_EVT_EOFIELD 5 ++#define DC_EVT_NEW_ADDR 6 ++#define DC_EVT_NEW_CHAN 7 ++#define DC_EVT_NEW_DATA 8 ++ ++#define DC_EVT_NEW_ADDR_W_0 0 ++#define DC_EVT_NEW_ADDR_W_1 1 ++#define DC_EVT_NEW_CHAN_W_0 2 ++#define DC_EVT_NEW_CHAN_W_1 3 ++#define DC_EVT_NEW_DATA_W_0 4 ++#define DC_EVT_NEW_DATA_W_1 5 ++#define DC_EVT_NEW_ADDR_R_0 6 ++#define DC_EVT_NEW_ADDR_R_1 7 ++#define DC_EVT_NEW_CHAN_R_0 8 ++#define DC_EVT_NEW_CHAN_R_1 9 ++#define DC_EVT_NEW_DATA_R_0 10 ++#define DC_EVT_NEW_DATA_R_1 11 ++ ++#define DC_WR_CH_CONF 0x0 ++#define DC_WR_CH_ADDR 0x4 ++#define DC_RL_CH(evt) (8 + ((evt) & ~0x1) * 2) ++ ++#define DC_GEN 0xd4 ++#define DC_DISP_CONF1(disp) (0xd8 + (disp) * 4) ++#define DC_DISP_CONF2(disp) (0xe8 + (disp) * 4) ++#define DC_STAT 0x1c8 ++ ++#define WROD(lf) (0x18 | ((lf) << 1)) ++#define WRG 0x01 ++#define WCLK 0xc9 ++ ++#define SYNC_WAVE 0 ++#define NULL_WAVE (-1) ++ ++#define DC_GEN_SYNC_1_6_SYNC (2 << 1) ++#define DC_GEN_SYNC_PRIORITY_1 (1 << 7) ++ ++#define DC_WR_CH_CONF_WORD_SIZE_8 (0 << 0) ++#define DC_WR_CH_CONF_WORD_SIZE_16 (1 << 0) ++#define DC_WR_CH_CONF_WORD_SIZE_24 (2 << 0) ++#define DC_WR_CH_CONF_WORD_SIZE_32 (3 << 0) ++#define DC_WR_CH_CONF_DISP_ID_PARALLEL(i) (((i) & 0x1) << 3) ++#define DC_WR_CH_CONF_DISP_ID_SERIAL (2 << 3) ++#define DC_WR_CH_CONF_DISP_ID_ASYNC (3 << 4) ++#define DC_WR_CH_CONF_FIELD_MODE (1 << 9) ++#define DC_WR_CH_CONF_PROG_TYPE_NORMAL (4 << 5) ++#define DC_WR_CH_CONF_PROG_TYPE_MASK (7 << 5) ++#define DC_WR_CH_CONF_PROG_DI_ID (1 << 2) ++#define DC_WR_CH_CONF_PROG_DISP_ID(i) (((i) & 0x1) << 3) ++ ++#define IPU_DC_NUM_CHANNELS 10 ++ ++struct ipu_dc_priv; ++ ++enum ipu_dc_map { ++ IPU_DC_MAP_RGB24, ++ IPU_DC_MAP_RGB565, ++ IPU_DC_MAP_GBR24, /* TVEv2 */ ++ IPU_DC_MAP_BGR666, ++ IPU_DC_MAP_LVDS666, ++ IPU_DC_MAP_BGR24, ++}; ++ ++struct ipu_dc { ++ /* The display interface number assigned to this dc channel */ ++ unsigned int di; ++ void __iomem *base; ++ struct ipu_dc_priv *priv; ++ int chno; ++ bool in_use; ++}; ++ ++struct ipu_dc_priv { ++ void __iomem *dc_reg; ++ void __iomem *dc_tmpl_reg; ++ struct ipu_soc *ipu; ++ struct device *dev; ++ struct ipu_dc channels[IPU_DC_NUM_CHANNELS]; ++ struct mutex mutex; ++ struct completion comp; ++ int use_count; ++}; ++ ++static void dc_link_event(struct ipu_dc *dc, int event, int addr, int priority) ++{ ++ u32 reg; ++ ++ reg = readl(dc->base + DC_RL_CH(event)); ++ reg &= ~(0xffff << (16 * (event & 0x1))); ++ reg |= ((addr << 8) | priority) << (16 * (event & 0x1)); ++ writel(reg, dc->base + DC_RL_CH(event)); ++} ++ ++static void dc_write_tmpl(struct ipu_dc *dc, int word, u32 opcode, u32 operand, ++ int map, int wave, int glue, int sync, int stop) ++{ ++ struct ipu_dc_priv *priv = dc->priv; ++ u32 reg1, reg2; ++ ++ if (opcode == WCLK) { ++ reg1 = (operand << 20) & 0xfff00000; ++ reg2 = operand >> 12 | opcode << 1 | stop << 9; ++ } else if (opcode == WRG) { ++ reg1 = sync | glue << 4 | ++wave << 11 | ((operand << 15) & 0xffff8000); ++ reg2 = operand >> 17 | opcode << 7 | stop << 9; ++ } else { ++ reg1 = sync | glue << 4 | ++wave << 11 | ++map << 15 | ((operand << 20) & 0xfff00000); ++ reg2 = operand >> 12 | opcode << 4 | stop << 9; ++ } ++ writel(reg1, priv->dc_tmpl_reg + word * 8); ++ writel(reg2, priv->dc_tmpl_reg + word * 8 + 4); ++} ++ ++static int ipu_bus_format_to_map(u32 fmt) ++{ ++ switch (fmt) { ++ default: ++ WARN_ON(1); ++ /* fall-through */ ++ case MEDIA_BUS_FMT_RGB888_1X24: ++ return IPU_DC_MAP_RGB24; ++ case MEDIA_BUS_FMT_RGB565_1X16: ++ return IPU_DC_MAP_RGB565; ++ case MEDIA_BUS_FMT_GBR888_1X24: ++ return IPU_DC_MAP_GBR24; ++ case MEDIA_BUS_FMT_RGB666_1X18: ++ return IPU_DC_MAP_BGR666; ++ case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: ++ return IPU_DC_MAP_LVDS666; ++ case MEDIA_BUS_FMT_BGR888_1X24: ++ return IPU_DC_MAP_BGR24; ++ } ++} ++ ++int ipu_dc_init_sync(struct ipu_dc *dc, struct ipu_di *di, bool interlaced, ++ u32 bus_format, u32 width) ++{ ++ struct ipu_dc_priv *priv = dc->priv; ++ int addr, sync; ++ u32 reg = 0; ++ int map; ++ ++ dc->di = ipu_di_get_num(di); ++ ++ map = ipu_bus_format_to_map(bus_format); ++ ++ /* ++ * In interlaced mode we need more counters to create the asymmetric ++ * per-field VSYNC signals. The pixel active signal synchronising DC ++ * to DI moves to signal generator #6 (see ipu-di.c). In progressive ++ * mode counter #5 is used. ++ */ ++ sync = interlaced ? 6 : 5; ++ ++ /* Reserve 5 microcode template words for each DI */ ++ if (dc->di) ++ addr = 5; ++ else ++ addr = 0; ++ ++ if (interlaced) { ++ dc_link_event(dc, DC_EVT_NL, addr, 3); ++ dc_link_event(dc, DC_EVT_EOL, addr, 2); ++ dc_link_event(dc, DC_EVT_NEW_DATA, addr, 1); ++ ++ /* Init template microcode */ ++ dc_write_tmpl(dc, addr, WROD(0), 0, map, SYNC_WAVE, 0, sync, 1); ++ } else { ++ dc_link_event(dc, DC_EVT_NL, addr + 2, 3); ++ dc_link_event(dc, DC_EVT_EOL, addr + 3, 2); ++ dc_link_event(dc, DC_EVT_NEW_DATA, addr + 1, 1); ++ ++ /* Init template microcode */ ++ dc_write_tmpl(dc, addr + 2, WROD(0), 0, map, SYNC_WAVE, 8, sync, 1); ++ dc_write_tmpl(dc, addr + 3, WROD(0), 0, map, SYNC_WAVE, 4, sync, 0); ++ dc_write_tmpl(dc, addr + 4, WRG, 0, map, NULL_WAVE, 0, 0, 1); ++ dc_write_tmpl(dc, addr + 1, WROD(0), 0, map, SYNC_WAVE, 0, sync, 1); ++ } ++ ++ dc_link_event(dc, DC_EVT_NF, 0, 0); ++ dc_link_event(dc, DC_EVT_NFIELD, 0, 0); ++ dc_link_event(dc, DC_EVT_EOF, 0, 0); ++ dc_link_event(dc, DC_EVT_EOFIELD, 0, 0); ++ dc_link_event(dc, DC_EVT_NEW_CHAN, 0, 0); ++ dc_link_event(dc, DC_EVT_NEW_ADDR, 0, 0); ++ ++ reg = readl(dc->base + DC_WR_CH_CONF); ++ if (interlaced) ++ reg |= DC_WR_CH_CONF_FIELD_MODE; ++ else ++ reg &= ~DC_WR_CH_CONF_FIELD_MODE; ++ writel(reg, dc->base + DC_WR_CH_CONF); ++ ++ writel(0x0, dc->base + DC_WR_CH_ADDR); ++ writel(width, priv->dc_reg + DC_DISP_CONF2(dc->di)); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_dc_init_sync); ++ ++void ipu_dc_enable(struct ipu_soc *ipu) ++{ ++ struct ipu_dc_priv *priv = ipu->dc_priv; ++ ++ mutex_lock(&priv->mutex); ++ ++ if (!priv->use_count) ++ ipu_module_enable(priv->ipu, IPU_CONF_DC_EN); ++ ++ priv->use_count++; ++ ++ mutex_unlock(&priv->mutex); ++} ++EXPORT_SYMBOL_GPL(ipu_dc_enable); ++ ++void ipu_dc_enable_channel(struct ipu_dc *dc) ++{ ++ u32 reg; ++ ++ reg = readl(dc->base + DC_WR_CH_CONF); ++ reg |= DC_WR_CH_CONF_PROG_TYPE_NORMAL; ++ writel(reg, dc->base + DC_WR_CH_CONF); ++} ++EXPORT_SYMBOL_GPL(ipu_dc_enable_channel); ++ ++void ipu_dc_disable_channel(struct ipu_dc *dc) ++{ ++ u32 val; ++ ++ val = readl(dc->base + DC_WR_CH_CONF); ++ val &= ~DC_WR_CH_CONF_PROG_TYPE_MASK; ++ writel(val, dc->base + DC_WR_CH_CONF); ++} ++EXPORT_SYMBOL_GPL(ipu_dc_disable_channel); ++ ++void ipu_dc_disable(struct ipu_soc *ipu) ++{ ++ struct ipu_dc_priv *priv = ipu->dc_priv; ++ ++ mutex_lock(&priv->mutex); ++ ++ priv->use_count--; ++ if (!priv->use_count) ++ ipu_module_disable(priv->ipu, IPU_CONF_DC_EN); ++ ++ if (priv->use_count < 0) ++ priv->use_count = 0; ++ ++ mutex_unlock(&priv->mutex); ++} ++EXPORT_SYMBOL_GPL(ipu_dc_disable); ++ ++static void ipu_dc_map_config(struct ipu_dc_priv *priv, enum ipu_dc_map map, ++ int byte_num, int offset, int mask) ++{ ++ int ptr = map * 3 + byte_num; ++ u32 reg; ++ ++ reg = readl(priv->dc_reg + DC_MAP_CONF_VAL(ptr)); ++ reg &= ~(0xffff << (16 * (ptr & 0x1))); ++ reg |= ((offset << 8) | mask) << (16 * (ptr & 0x1)); ++ writel(reg, priv->dc_reg + DC_MAP_CONF_VAL(ptr)); ++ ++ reg = readl(priv->dc_reg + DC_MAP_CONF_PTR(map)); ++ reg &= ~(0x1f << ((16 * (map & 0x1)) + (5 * byte_num))); ++ reg |= ptr << ((16 * (map & 0x1)) + (5 * byte_num)); ++ writel(reg, priv->dc_reg + DC_MAP_CONF_PTR(map)); ++} ++ ++static void ipu_dc_map_clear(struct ipu_dc_priv *priv, int map) ++{ ++ u32 reg = readl(priv->dc_reg + DC_MAP_CONF_PTR(map)); ++ ++ writel(reg & ~(0xffff << (16 * (map & 0x1))), ++ priv->dc_reg + DC_MAP_CONF_PTR(map)); ++} ++ ++struct ipu_dc *ipu_dc_get(struct ipu_soc *ipu, int channel) ++{ ++ struct ipu_dc_priv *priv = ipu->dc_priv; ++ struct ipu_dc *dc; ++ ++ if (channel >= IPU_DC_NUM_CHANNELS) ++ return ERR_PTR(-ENODEV); ++ ++ dc = &priv->channels[channel]; ++ ++ mutex_lock(&priv->mutex); ++ ++ if (dc->in_use) { ++ mutex_unlock(&priv->mutex); ++ return ERR_PTR(-EBUSY); ++ } ++ ++ dc->in_use = true; ++ ++ mutex_unlock(&priv->mutex); ++ ++ return dc; ++} ++EXPORT_SYMBOL_GPL(ipu_dc_get); ++ ++void ipu_dc_put(struct ipu_dc *dc) ++{ ++ struct ipu_dc_priv *priv = dc->priv; ++ ++ mutex_lock(&priv->mutex); ++ dc->in_use = false; ++ mutex_unlock(&priv->mutex); ++} ++EXPORT_SYMBOL_GPL(ipu_dc_put); ++ ++int ipu_dc_init(struct ipu_soc *ipu, struct device *dev, ++ unsigned long base, unsigned long template_base) ++{ ++ struct ipu_dc_priv *priv; ++ static int channel_offsets[] = { 0, 0x1c, 0x38, 0x54, 0x58, 0x5c, ++ 0x78, 0, 0x94, 0xb4}; ++ int i; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ mutex_init(&priv->mutex); ++ ++ priv->dev = dev; ++ priv->ipu = ipu; ++ priv->dc_reg = devm_ioremap(dev, base, PAGE_SIZE); ++ priv->dc_tmpl_reg = devm_ioremap(dev, template_base, PAGE_SIZE); ++ if (!priv->dc_reg || !priv->dc_tmpl_reg) ++ return -ENOMEM; ++ ++ for (i = 0; i < IPU_DC_NUM_CHANNELS; i++) { ++ priv->channels[i].chno = i; ++ priv->channels[i].priv = priv; ++ priv->channels[i].base = priv->dc_reg + channel_offsets[i]; ++ } ++ ++ writel(DC_WR_CH_CONF_WORD_SIZE_24 | DC_WR_CH_CONF_DISP_ID_PARALLEL(1) | ++ DC_WR_CH_CONF_PROG_DI_ID, ++ priv->channels[1].base + DC_WR_CH_CONF); ++ writel(DC_WR_CH_CONF_WORD_SIZE_24 | DC_WR_CH_CONF_DISP_ID_PARALLEL(0), ++ priv->channels[5].base + DC_WR_CH_CONF); ++ ++ writel(DC_GEN_SYNC_1_6_SYNC | DC_GEN_SYNC_PRIORITY_1, ++ priv->dc_reg + DC_GEN); ++ ++ ipu->dc_priv = priv; ++ ++ dev_dbg(dev, "DC base: 0x%08lx template base: 0x%08lx\n", ++ base, template_base); ++ ++ /* rgb24 */ ++ ipu_dc_map_clear(priv, IPU_DC_MAP_RGB24); ++ ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 0, 7, 0xff); /* blue */ ++ ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 1, 15, 0xff); /* green */ ++ ipu_dc_map_config(priv, IPU_DC_MAP_RGB24, 2, 23, 0xff); /* red */ ++ ++ /* rgb565 */ ++ ipu_dc_map_clear(priv, IPU_DC_MAP_RGB565); ++ ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 0, 4, 0xf8); /* blue */ ++ ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 1, 10, 0xfc); /* green */ ++ ipu_dc_map_config(priv, IPU_DC_MAP_RGB565, 2, 15, 0xf8); /* red */ ++ ++ /* gbr24 */ ++ ipu_dc_map_clear(priv, IPU_DC_MAP_GBR24); ++ ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 2, 15, 0xff); /* green */ ++ ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 1, 7, 0xff); /* blue */ ++ ipu_dc_map_config(priv, IPU_DC_MAP_GBR24, 0, 23, 0xff); /* red */ ++ ++ /* bgr666 */ ++ ipu_dc_map_clear(priv, IPU_DC_MAP_BGR666); ++ ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 0, 5, 0xfc); /* blue */ ++ ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 1, 11, 0xfc); /* green */ ++ ipu_dc_map_config(priv, IPU_DC_MAP_BGR666, 2, 17, 0xfc); /* red */ ++ ++ /* lvds666 */ ++ ipu_dc_map_clear(priv, IPU_DC_MAP_LVDS666); ++ ipu_dc_map_config(priv, IPU_DC_MAP_LVDS666, 0, 5, 0xfc); /* blue */ ++ ipu_dc_map_config(priv, IPU_DC_MAP_LVDS666, 1, 13, 0xfc); /* green */ ++ ipu_dc_map_config(priv, IPU_DC_MAP_LVDS666, 2, 21, 0xfc); /* red */ ++ ++ /* bgr24 */ ++ ipu_dc_map_clear(priv, IPU_DC_MAP_BGR24); ++ ipu_dc_map_config(priv, IPU_DC_MAP_BGR24, 2, 7, 0xff); /* red */ ++ ipu_dc_map_config(priv, IPU_DC_MAP_BGR24, 1, 15, 0xff); /* green */ ++ ipu_dc_map_config(priv, IPU_DC_MAP_BGR24, 0, 23, 0xff); /* blue */ ++ ++ return 0; ++} ++ ++void ipu_dc_exit(struct ipu_soc *ipu) ++{ ++} +--- /dev/null ++++ b/drivers/gpu/ipu-v3/ipu-di.c +@@ -0,0 +1,745 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> ++ * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. ++ */ ++#include <linux/export.h> ++#include <linux/module.h> ++#include <linux/types.h> ++#include <linux/errno.h> ++#include <linux/io.h> ++#include <linux/err.h> ++#include <linux/platform_device.h> ++ ++#include <video/imx-ipu-v3.h> ++#include "ipu-prv.h" ++ ++struct ipu_di { ++ void __iomem *base; ++ int id; ++ u32 module; ++ struct clk *clk_di; /* display input clock */ ++ struct clk *clk_ipu; /* IPU bus clock */ ++ struct clk *clk_di_pixel; /* resulting pixel clock */ ++ bool inuse; ++ struct ipu_soc *ipu; ++}; ++ ++static DEFINE_MUTEX(di_mutex); ++ ++struct di_sync_config { ++ int run_count; ++ int run_src; ++ int offset_count; ++ int offset_src; ++ int repeat_count; ++ int cnt_clr_src; ++ int cnt_polarity_gen_en; ++ int cnt_polarity_clr_src; ++ int cnt_polarity_trigger_src; ++ int cnt_up; ++ int cnt_down; ++}; ++ ++enum di_pins { ++ DI_PIN11 = 0, ++ DI_PIN12 = 1, ++ DI_PIN13 = 2, ++ DI_PIN14 = 3, ++ DI_PIN15 = 4, ++ DI_PIN16 = 5, ++ DI_PIN17 = 6, ++ DI_PIN_CS = 7, ++ ++ DI_PIN_SER_CLK = 0, ++ DI_PIN_SER_RS = 1, ++}; ++ ++enum di_sync_wave { ++ DI_SYNC_NONE = 0, ++ DI_SYNC_CLK = 1, ++ DI_SYNC_INT_HSYNC = 2, ++ DI_SYNC_HSYNC = 3, ++ DI_SYNC_VSYNC = 4, ++ DI_SYNC_DE = 6, ++ ++ DI_SYNC_CNT1 = 2, /* counter >= 2 only */ ++ DI_SYNC_CNT4 = 5, /* counter >= 5 only */ ++ DI_SYNC_CNT5 = 6, /* counter >= 6 only */ ++}; ++ ++#define SYNC_WAVE 0 ++ ++#define DI_GENERAL 0x0000 ++#define DI_BS_CLKGEN0 0x0004 ++#define DI_BS_CLKGEN1 0x0008 ++#define DI_SW_GEN0(gen) (0x000c + 4 * ((gen) - 1)) ++#define DI_SW_GEN1(gen) (0x0030 + 4 * ((gen) - 1)) ++#define DI_STP_REP(gen) (0x0148 + 4 * (((gen) - 1)/2)) ++#define DI_SYNC_AS_GEN 0x0054 ++#define DI_DW_GEN(gen) (0x0058 + 4 * (gen)) ++#define DI_DW_SET(gen, set) (0x0088 + 4 * ((gen) + 0xc * (set))) ++#define DI_SER_CONF 0x015c ++#define DI_SSC 0x0160 ++#define DI_POL 0x0164 ++#define DI_AW0 0x0168 ++#define DI_AW1 0x016c ++#define DI_SCR_CONF 0x0170 ++#define DI_STAT 0x0174 ++ ++#define DI_SW_GEN0_RUN_COUNT(x) ((x) << 19) ++#define DI_SW_GEN0_RUN_SRC(x) ((x) << 16) ++#define DI_SW_GEN0_OFFSET_COUNT(x) ((x) << 3) ++#define DI_SW_GEN0_OFFSET_SRC(x) ((x) << 0) ++ ++#define DI_SW_GEN1_CNT_POL_GEN_EN(x) ((x) << 29) ++#define DI_SW_GEN1_CNT_CLR_SRC(x) ((x) << 25) ++#define DI_SW_GEN1_CNT_POL_TRIGGER_SRC(x) ((x) << 12) ++#define DI_SW_GEN1_CNT_POL_CLR_SRC(x) ((x) << 9) ++#define DI_SW_GEN1_CNT_DOWN(x) ((x) << 16) ++#define DI_SW_GEN1_CNT_UP(x) (x) ++#define DI_SW_GEN1_AUTO_RELOAD (0x10000000) ++ ++#define DI_DW_GEN_ACCESS_SIZE_OFFSET 24 ++#define DI_DW_GEN_COMPONENT_SIZE_OFFSET 16 ++ ++#define DI_GEN_POLARITY_1 (1 << 0) ++#define DI_GEN_POLARITY_2 (1 << 1) ++#define DI_GEN_POLARITY_3 (1 << 2) ++#define DI_GEN_POLARITY_4 (1 << 3) ++#define DI_GEN_POLARITY_5 (1 << 4) ++#define DI_GEN_POLARITY_6 (1 << 5) ++#define DI_GEN_POLARITY_7 (1 << 6) ++#define DI_GEN_POLARITY_8 (1 << 7) ++#define DI_GEN_POLARITY_DISP_CLK (1 << 17) ++#define DI_GEN_DI_CLK_EXT (1 << 20) ++#define DI_GEN_DI_VSYNC_EXT (1 << 21) ++ ++#define DI_POL_DRDY_DATA_POLARITY (1 << 7) ++#define DI_POL_DRDY_POLARITY_15 (1 << 4) ++ ++#define DI_VSYNC_SEL_OFFSET 13 ++ ++static inline u32 ipu_di_read(struct ipu_di *di, unsigned offset) ++{ ++ return readl(di->base + offset); ++} ++ ++static inline void ipu_di_write(struct ipu_di *di, u32 value, unsigned offset) ++{ ++ writel(value, di->base + offset); ++} ++ ++static void ipu_di_data_wave_config(struct ipu_di *di, ++ int wave_gen, ++ int access_size, int component_size) ++{ ++ u32 reg; ++ reg = (access_size << DI_DW_GEN_ACCESS_SIZE_OFFSET) | ++ (component_size << DI_DW_GEN_COMPONENT_SIZE_OFFSET); ++ ipu_di_write(di, reg, DI_DW_GEN(wave_gen)); ++} ++ ++static void ipu_di_data_pin_config(struct ipu_di *di, int wave_gen, int di_pin, ++ int set, int up, int down) ++{ ++ u32 reg; ++ ++ reg = ipu_di_read(di, DI_DW_GEN(wave_gen)); ++ reg &= ~(0x3 << (di_pin * 2)); ++ reg |= set << (di_pin * 2); ++ ipu_di_write(di, reg, DI_DW_GEN(wave_gen)); ++ ++ ipu_di_write(di, (down << 16) | up, DI_DW_SET(wave_gen, set)); ++} ++ ++static void ipu_di_sync_config(struct ipu_di *di, struct di_sync_config *config, ++ int start, int count) ++{ ++ u32 reg; ++ int i; ++ ++ for (i = 0; i < count; i++) { ++ struct di_sync_config *c = &config[i]; ++ int wave_gen = start + i + 1; ++ ++ if ((c->run_count >= 0x1000) || (c->offset_count >= 0x1000) || ++ (c->repeat_count >= 0x1000) || ++ (c->cnt_up >= 0x400) || ++ (c->cnt_down >= 0x400)) { ++ dev_err(di->ipu->dev, "DI%d counters out of range.\n", ++ di->id); ++ return; ++ } ++ ++ reg = DI_SW_GEN0_RUN_COUNT(c->run_count) | ++ DI_SW_GEN0_RUN_SRC(c->run_src) | ++ DI_SW_GEN0_OFFSET_COUNT(c->offset_count) | ++ DI_SW_GEN0_OFFSET_SRC(c->offset_src); ++ ipu_di_write(di, reg, DI_SW_GEN0(wave_gen)); ++ ++ reg = DI_SW_GEN1_CNT_POL_GEN_EN(c->cnt_polarity_gen_en) | ++ DI_SW_GEN1_CNT_CLR_SRC(c->cnt_clr_src) | ++ DI_SW_GEN1_CNT_POL_TRIGGER_SRC( ++ c->cnt_polarity_trigger_src) | ++ DI_SW_GEN1_CNT_POL_CLR_SRC(c->cnt_polarity_clr_src) | ++ DI_SW_GEN1_CNT_DOWN(c->cnt_down) | ++ DI_SW_GEN1_CNT_UP(c->cnt_up); ++ ++ /* Enable auto reload */ ++ if (c->repeat_count == 0) ++ reg |= DI_SW_GEN1_AUTO_RELOAD; ++ ++ ipu_di_write(di, reg, DI_SW_GEN1(wave_gen)); ++ ++ reg = ipu_di_read(di, DI_STP_REP(wave_gen)); ++ reg &= ~(0xffff << (16 * ((wave_gen - 1) & 0x1))); ++ reg |= c->repeat_count << (16 * ((wave_gen - 1) & 0x1)); ++ ipu_di_write(di, reg, DI_STP_REP(wave_gen)); ++ } ++} ++ ++static void ipu_di_sync_config_interlaced(struct ipu_di *di, ++ struct ipu_di_signal_cfg *sig) ++{ ++ u32 h_total = sig->mode.hactive + sig->mode.hsync_len + ++ sig->mode.hback_porch + sig->mode.hfront_porch; ++ u32 v_total = sig->mode.vactive + sig->mode.vsync_len + ++ sig->mode.vback_porch + sig->mode.vfront_porch; ++ struct di_sync_config cfg[] = { ++ { ++ /* 1: internal VSYNC for each frame */ ++ .run_count = v_total * 2 - 1, ++ .run_src = 3, /* == counter 7 */ ++ }, { ++ /* PIN2: HSYNC waveform */ ++ .run_count = h_total - 1, ++ .run_src = DI_SYNC_CLK, ++ .cnt_polarity_gen_en = 1, ++ .cnt_polarity_trigger_src = DI_SYNC_CLK, ++ .cnt_down = sig->mode.hsync_len * 2, ++ }, { ++ /* PIN3: VSYNC waveform */ ++ .run_count = v_total - 1, ++ .run_src = 4, /* == counter 7 */ ++ .cnt_polarity_gen_en = 1, ++ .cnt_polarity_trigger_src = 4, /* == counter 7 */ ++ .cnt_down = sig->mode.vsync_len * 2, ++ .cnt_clr_src = DI_SYNC_CNT1, ++ }, { ++ /* 4: Field */ ++ .run_count = v_total / 2, ++ .run_src = DI_SYNC_HSYNC, ++ .offset_count = h_total / 2, ++ .offset_src = DI_SYNC_CLK, ++ .repeat_count = 2, ++ .cnt_clr_src = DI_SYNC_CNT1, ++ }, { ++ /* 5: Active lines */ ++ .run_src = DI_SYNC_HSYNC, ++ .offset_count = (sig->mode.vsync_len + ++ sig->mode.vback_porch) / 2, ++ .offset_src = DI_SYNC_HSYNC, ++ .repeat_count = sig->mode.vactive / 2, ++ .cnt_clr_src = DI_SYNC_CNT4, ++ }, { ++ /* 6: Active pixel, referenced by DC */ ++ .run_src = DI_SYNC_CLK, ++ .offset_count = sig->mode.hsync_len + ++ sig->mode.hback_porch, ++ .offset_src = DI_SYNC_CLK, ++ .repeat_count = sig->mode.hactive, ++ .cnt_clr_src = DI_SYNC_CNT5, ++ }, { ++ /* 7: Half line HSYNC */ ++ .run_count = h_total / 2 - 1, ++ .run_src = DI_SYNC_CLK, ++ } ++ }; ++ ++ ipu_di_sync_config(di, cfg, 0, ARRAY_SIZE(cfg)); ++ ++ ipu_di_write(di, v_total / 2 - 1, DI_SCR_CONF); ++} ++ ++static void ipu_di_sync_config_noninterlaced(struct ipu_di *di, ++ struct ipu_di_signal_cfg *sig, int div) ++{ ++ u32 h_total = sig->mode.hactive + sig->mode.hsync_len + ++ sig->mode.hback_porch + sig->mode.hfront_porch; ++ u32 v_total = sig->mode.vactive + sig->mode.vsync_len + ++ sig->mode.vback_porch + sig->mode.vfront_porch; ++ struct di_sync_config cfg[] = { ++ { ++ /* 1: INT_HSYNC */ ++ .run_count = h_total - 1, ++ .run_src = DI_SYNC_CLK, ++ } , { ++ /* PIN2: HSYNC */ ++ .run_count = h_total - 1, ++ .run_src = DI_SYNC_CLK, ++ .offset_count = div * sig->v_to_h_sync, ++ .offset_src = DI_SYNC_CLK, ++ .cnt_polarity_gen_en = 1, ++ .cnt_polarity_trigger_src = DI_SYNC_CLK, ++ .cnt_down = sig->mode.hsync_len * 2, ++ } , { ++ /* PIN3: VSYNC */ ++ .run_count = v_total - 1, ++ .run_src = DI_SYNC_INT_HSYNC, ++ .cnt_polarity_gen_en = 1, ++ .cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC, ++ .cnt_down = sig->mode.vsync_len * 2, ++ } , { ++ /* 4: Line Active */ ++ .run_src = DI_SYNC_HSYNC, ++ .offset_count = sig->mode.vsync_len + ++ sig->mode.vback_porch, ++ .offset_src = DI_SYNC_HSYNC, ++ .repeat_count = sig->mode.vactive, ++ .cnt_clr_src = DI_SYNC_VSYNC, ++ } , { ++ /* 5: Pixel Active, referenced by DC */ ++ .run_src = DI_SYNC_CLK, ++ .offset_count = sig->mode.hsync_len + ++ sig->mode.hback_porch, ++ .offset_src = DI_SYNC_CLK, ++ .repeat_count = sig->mode.hactive, ++ .cnt_clr_src = 5, /* Line Active */ ++ } , { ++ /* unused */ ++ } , { ++ /* unused */ ++ } , { ++ /* unused */ ++ } , { ++ /* unused */ ++ }, ++ }; ++ /* can't use #7 and #8 for line active and pixel active counters */ ++ struct di_sync_config cfg_vga[] = { ++ { ++ /* 1: INT_HSYNC */ ++ .run_count = h_total - 1, ++ .run_src = DI_SYNC_CLK, ++ } , { ++ /* 2: VSYNC */ ++ .run_count = v_total - 1, ++ .run_src = DI_SYNC_INT_HSYNC, ++ } , { ++ /* 3: Line Active */ ++ .run_src = DI_SYNC_INT_HSYNC, ++ .offset_count = sig->mode.vsync_len + ++ sig->mode.vback_porch, ++ .offset_src = DI_SYNC_INT_HSYNC, ++ .repeat_count = sig->mode.vactive, ++ .cnt_clr_src = 3 /* VSYNC */, ++ } , { ++ /* PIN4: HSYNC for VGA via TVEv2 on TQ MBa53 */ ++ .run_count = h_total - 1, ++ .run_src = DI_SYNC_CLK, ++ .offset_count = div * sig->v_to_h_sync + 18, /* magic value from Freescale TVE driver */ ++ .offset_src = DI_SYNC_CLK, ++ .cnt_polarity_gen_en = 1, ++ .cnt_polarity_trigger_src = DI_SYNC_CLK, ++ .cnt_down = sig->mode.hsync_len * 2, ++ } , { ++ /* 5: Pixel Active signal to DC */ ++ .run_src = DI_SYNC_CLK, ++ .offset_count = sig->mode.hsync_len + ++ sig->mode.hback_porch, ++ .offset_src = DI_SYNC_CLK, ++ .repeat_count = sig->mode.hactive, ++ .cnt_clr_src = 4, /* Line Active */ ++ } , { ++ /* PIN6: VSYNC for VGA via TVEv2 on TQ MBa53 */ ++ .run_count = v_total - 1, ++ .run_src = DI_SYNC_INT_HSYNC, ++ .offset_count = 1, /* magic value from Freescale TVE driver */ ++ .offset_src = DI_SYNC_INT_HSYNC, ++ .cnt_polarity_gen_en = 1, ++ .cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC, ++ .cnt_down = sig->mode.vsync_len * 2, ++ } , { ++ /* PIN4: HSYNC for VGA via TVEv2 on i.MX53-QSB */ ++ .run_count = h_total - 1, ++ .run_src = DI_SYNC_CLK, ++ .offset_count = div * sig->v_to_h_sync + 18, /* magic value from Freescale TVE driver */ ++ .offset_src = DI_SYNC_CLK, ++ .cnt_polarity_gen_en = 1, ++ .cnt_polarity_trigger_src = DI_SYNC_CLK, ++ .cnt_down = sig->mode.hsync_len * 2, ++ } , { ++ /* PIN6: VSYNC for VGA via TVEv2 on i.MX53-QSB */ ++ .run_count = v_total - 1, ++ .run_src = DI_SYNC_INT_HSYNC, ++ .offset_count = 1, /* magic value from Freescale TVE driver */ ++ .offset_src = DI_SYNC_INT_HSYNC, ++ .cnt_polarity_gen_en = 1, ++ .cnt_polarity_trigger_src = DI_SYNC_INT_HSYNC, ++ .cnt_down = sig->mode.vsync_len * 2, ++ } , { ++ /* unused */ ++ }, ++ }; ++ ++ ipu_di_write(di, v_total - 1, DI_SCR_CONF); ++ if (sig->hsync_pin == 2 && sig->vsync_pin == 3) ++ ipu_di_sync_config(di, cfg, 0, ARRAY_SIZE(cfg)); ++ else ++ ipu_di_sync_config(di, cfg_vga, 0, ARRAY_SIZE(cfg_vga)); ++} ++ ++static void ipu_di_config_clock(struct ipu_di *di, ++ const struct ipu_di_signal_cfg *sig) ++{ ++ struct clk *clk; ++ unsigned clkgen0; ++ uint32_t val; ++ ++ if (sig->clkflags & IPU_DI_CLKMODE_EXT) { ++ /* ++ * CLKMODE_EXT means we must use the DI clock: this is ++ * needed for things like LVDS which needs to feed the ++ * DI and LDB with the same pixel clock. ++ */ ++ clk = di->clk_di; ++ ++ if (sig->clkflags & IPU_DI_CLKMODE_SYNC) { ++ /* ++ * CLKMODE_SYNC means that we want the DI to be ++ * clocked at the same rate as the parent clock. ++ * This is needed (eg) for LDB which needs to be ++ * fed with the same pixel clock. We assume that ++ * the LDB clock has already been set correctly. ++ */ ++ clkgen0 = 1 << 4; ++ } else { ++ /* ++ * We can use the divider. We should really have ++ * a flag here indicating whether the bridge can ++ * cope with a fractional divider or not. For the ++ * time being, let's go for simplicitly and ++ * reliability. ++ */ ++ unsigned long in_rate; ++ unsigned div; ++ ++ clk_set_rate(clk, sig->mode.pixelclock); ++ ++ in_rate = clk_get_rate(clk); ++ div = DIV_ROUND_CLOSEST(in_rate, sig->mode.pixelclock); ++ div = clamp(div, 1U, 255U); ++ ++ clkgen0 = div << 4; ++ } ++ } else { ++ /* ++ * For other interfaces, we can arbitarily select between ++ * the DI specific clock and the internal IPU clock. See ++ * DI_GENERAL bit 20. We select the IPU clock if it can ++ * give us a clock rate within 1% of the requested frequency, ++ * otherwise we use the DI clock. ++ */ ++ unsigned long rate, clkrate; ++ unsigned div, error; ++ ++ clkrate = clk_get_rate(di->clk_ipu); ++ div = DIV_ROUND_CLOSEST(clkrate, sig->mode.pixelclock); ++ div = clamp(div, 1U, 255U); ++ rate = clkrate / div; ++ ++ error = rate / (sig->mode.pixelclock / 1000); ++ ++ dev_dbg(di->ipu->dev, " IPU clock can give %lu with divider %u, error %d.%u%%\n", ++ rate, div, (signed)(error - 1000) / 10, error % 10); ++ ++ /* Allow a 1% error */ ++ if (error < 1010 && error >= 990) { ++ clk = di->clk_ipu; ++ ++ clkgen0 = div << 4; ++ } else { ++ unsigned long in_rate; ++ unsigned div; ++ ++ clk = di->clk_di; ++ ++ clk_set_rate(clk, sig->mode.pixelclock); ++ ++ in_rate = clk_get_rate(clk); ++ div = DIV_ROUND_CLOSEST(in_rate, sig->mode.pixelclock); ++ div = clamp(div, 1U, 255U); ++ ++ clkgen0 = div << 4; ++ } ++ } ++ ++ di->clk_di_pixel = clk; ++ ++ /* Set the divider */ ++ ipu_di_write(di, clkgen0, DI_BS_CLKGEN0); ++ ++ /* ++ * Set the high/low periods. Bits 24:16 give us the falling edge, ++ * and bits 8:0 give the rising edge. LSB is fraction, and is ++ * based on the divider above. We want a 50% duty cycle, so set ++ * the falling edge to be half the divider. ++ */ ++ ipu_di_write(di, (clkgen0 >> 4) << 16, DI_BS_CLKGEN1); ++ ++ /* Finally select the input clock */ ++ val = ipu_di_read(di, DI_GENERAL) & ~DI_GEN_DI_CLK_EXT; ++ if (clk == di->clk_di) ++ val |= DI_GEN_DI_CLK_EXT; ++ ipu_di_write(di, val, DI_GENERAL); ++ ++ dev_dbg(di->ipu->dev, "Want %luHz IPU %luHz DI %luHz using %s, %luHz\n", ++ sig->mode.pixelclock, ++ clk_get_rate(di->clk_ipu), ++ clk_get_rate(di->clk_di), ++ clk == di->clk_di ? "DI" : "IPU", ++ clk_get_rate(di->clk_di_pixel) / (clkgen0 >> 4)); ++} ++ ++/* ++ * This function is called to adjust a video mode to IPU restrictions. ++ * It is meant to be called from drm crtc mode_fixup() methods. ++ */ ++int ipu_di_adjust_videomode(struct ipu_di *di, struct videomode *mode) ++{ ++ u32 diff; ++ ++ if (mode->vfront_porch >= 2) ++ return 0; ++ ++ diff = 2 - mode->vfront_porch; ++ ++ if (mode->vback_porch >= diff) { ++ mode->vfront_porch = 2; ++ mode->vback_porch -= diff; ++ } else if (mode->vsync_len > diff) { ++ mode->vfront_porch = 2; ++ mode->vsync_len = mode->vsync_len - diff; ++ } else { ++ dev_warn(di->ipu->dev, "failed to adjust videomode\n"); ++ return -EINVAL; ++ } ++ ++ dev_dbg(di->ipu->dev, "videomode adapted for IPU restrictions\n"); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_di_adjust_videomode); ++ ++static u32 ipu_di_gen_polarity(int pin) ++{ ++ switch (pin) { ++ case 1: ++ return DI_GEN_POLARITY_1; ++ case 2: ++ return DI_GEN_POLARITY_2; ++ case 3: ++ return DI_GEN_POLARITY_3; ++ case 4: ++ return DI_GEN_POLARITY_4; ++ case 5: ++ return DI_GEN_POLARITY_5; ++ case 6: ++ return DI_GEN_POLARITY_6; ++ case 7: ++ return DI_GEN_POLARITY_7; ++ case 8: ++ return DI_GEN_POLARITY_8; ++ } ++ return 0; ++} ++ ++int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig) ++{ ++ u32 reg; ++ u32 di_gen, vsync_cnt; ++ u32 div; ++ ++ dev_dbg(di->ipu->dev, "disp %d: panel size = %d x %d\n", ++ di->id, sig->mode.hactive, sig->mode.vactive); ++ ++ dev_dbg(di->ipu->dev, "Clocks: IPU %luHz DI %luHz Needed %luHz\n", ++ clk_get_rate(di->clk_ipu), ++ clk_get_rate(di->clk_di), ++ sig->mode.pixelclock); ++ ++ mutex_lock(&di_mutex); ++ ++ ipu_di_config_clock(di, sig); ++ ++ div = ipu_di_read(di, DI_BS_CLKGEN0) & 0xfff; ++ div = div / 16; /* Now divider is integer portion */ ++ ++ /* Setup pixel clock timing */ ++ /* Down time is half of period */ ++ ipu_di_write(di, (div << 16), DI_BS_CLKGEN1); ++ ++ ipu_di_data_wave_config(di, SYNC_WAVE, div - 1, div - 1); ++ ipu_di_data_pin_config(di, SYNC_WAVE, DI_PIN15, 3, 0, div * 2); ++ ++ di_gen = ipu_di_read(di, DI_GENERAL) & DI_GEN_DI_CLK_EXT; ++ di_gen |= DI_GEN_DI_VSYNC_EXT; ++ ++ if (sig->mode.flags & DISPLAY_FLAGS_INTERLACED) { ++ ipu_di_sync_config_interlaced(di, sig); ++ ++ /* set y_sel = 1 */ ++ di_gen |= 0x10000000; ++ ++ vsync_cnt = 3; ++ } else { ++ ipu_di_sync_config_noninterlaced(di, sig, div); ++ ++ vsync_cnt = 3; ++ if (di->id == 1) ++ /* ++ * TODO: change only for TVEv2, parallel display ++ * uses pin 2 / 3 ++ */ ++ if (!(sig->hsync_pin == 2 && sig->vsync_pin == 3)) ++ vsync_cnt = 6; ++ } ++ ++ if (sig->mode.flags & DISPLAY_FLAGS_HSYNC_HIGH) ++ di_gen |= ipu_di_gen_polarity(sig->hsync_pin); ++ if (sig->mode.flags & DISPLAY_FLAGS_VSYNC_HIGH) ++ di_gen |= ipu_di_gen_polarity(sig->vsync_pin); ++ ++ if (sig->clk_pol) ++ di_gen |= DI_GEN_POLARITY_DISP_CLK; ++ ++ ipu_di_write(di, di_gen, DI_GENERAL); ++ ++ ipu_di_write(di, (--vsync_cnt << DI_VSYNC_SEL_OFFSET) | 0x00000002, ++ DI_SYNC_AS_GEN); ++ ++ reg = ipu_di_read(di, DI_POL); ++ reg &= ~(DI_POL_DRDY_DATA_POLARITY | DI_POL_DRDY_POLARITY_15); ++ ++ if (sig->enable_pol) ++ reg |= DI_POL_DRDY_POLARITY_15; ++ if (sig->data_pol) ++ reg |= DI_POL_DRDY_DATA_POLARITY; ++ ++ ipu_di_write(di, reg, DI_POL); ++ ++ mutex_unlock(&di_mutex); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_di_init_sync_panel); ++ ++int ipu_di_enable(struct ipu_di *di) ++{ ++ int ret; ++ ++ WARN_ON(IS_ERR(di->clk_di_pixel)); ++ ++ ret = clk_prepare_enable(di->clk_di_pixel); ++ if (ret) ++ return ret; ++ ++ ipu_module_enable(di->ipu, di->module); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_di_enable); ++ ++int ipu_di_disable(struct ipu_di *di) ++{ ++ WARN_ON(IS_ERR(di->clk_di_pixel)); ++ ++ ipu_module_disable(di->ipu, di->module); ++ ++ clk_disable_unprepare(di->clk_di_pixel); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_di_disable); ++ ++int ipu_di_get_num(struct ipu_di *di) ++{ ++ return di->id; ++} ++EXPORT_SYMBOL_GPL(ipu_di_get_num); ++ ++static DEFINE_MUTEX(ipu_di_lock); ++ ++struct ipu_di *ipu_di_get(struct ipu_soc *ipu, int disp) ++{ ++ struct ipu_di *di; ++ ++ if (disp > 1) ++ return ERR_PTR(-EINVAL); ++ ++ di = ipu->di_priv[disp]; ++ ++ mutex_lock(&ipu_di_lock); ++ ++ if (di->inuse) { ++ di = ERR_PTR(-EBUSY); ++ goto out; ++ } ++ ++ di->inuse = true; ++out: ++ mutex_unlock(&ipu_di_lock); ++ ++ return di; ++} ++EXPORT_SYMBOL_GPL(ipu_di_get); ++ ++void ipu_di_put(struct ipu_di *di) ++{ ++ mutex_lock(&ipu_di_lock); ++ ++ di->inuse = false; ++ ++ mutex_unlock(&ipu_di_lock); ++} ++EXPORT_SYMBOL_GPL(ipu_di_put); ++ ++int ipu_di_init(struct ipu_soc *ipu, struct device *dev, int id, ++ unsigned long base, ++ u32 module, struct clk *clk_ipu) ++{ ++ struct ipu_di *di; ++ ++ if (id > 1) ++ return -ENODEV; ++ ++ di = devm_kzalloc(dev, sizeof(*di), GFP_KERNEL); ++ if (!di) ++ return -ENOMEM; ++ ++ ipu->di_priv[id] = di; ++ ++ di->clk_di = devm_clk_get(dev, id ? "di1" : "di0"); ++ if (IS_ERR(di->clk_di)) ++ return PTR_ERR(di->clk_di); ++ ++ di->module = module; ++ di->id = id; ++ di->clk_ipu = clk_ipu; ++ di->base = devm_ioremap(dev, base, PAGE_SIZE); ++ if (!di->base) ++ return -ENOMEM; ++ ++ ipu_di_write(di, 0x10, DI_BS_CLKGEN0); ++ ++ dev_dbg(dev, "DI%d base: 0x%08lx remapped to %p\n", ++ id, base, di->base); ++ di->inuse = false; ++ di->ipu = ipu; ++ ++ return 0; ++} ++ ++void ipu_di_exit(struct ipu_soc *ipu, int id) ++{ ++} +--- /dev/null ++++ b/drivers/gpu/ipu-v3/ipu-dmfc.c +@@ -0,0 +1,214 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> ++ * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. ++ */ ++#include <linux/export.h> ++#include <linux/types.h> ++#include <linux/errno.h> ++#include <linux/io.h> ++ ++#include <video/imx-ipu-v3.h> ++#include "ipu-prv.h" ++ ++#define DMFC_RD_CHAN 0x0000 ++#define DMFC_WR_CHAN 0x0004 ++#define DMFC_WR_CHAN_DEF 0x0008 ++#define DMFC_DP_CHAN 0x000c ++#define DMFC_DP_CHAN_DEF 0x0010 ++#define DMFC_GENERAL1 0x0014 ++#define DMFC_GENERAL2 0x0018 ++#define DMFC_IC_CTRL 0x001c ++#define DMFC_WR_CHAN_ALT 0x0020 ++#define DMFC_WR_CHAN_DEF_ALT 0x0024 ++#define DMFC_DP_CHAN_ALT 0x0028 ++#define DMFC_DP_CHAN_DEF_ALT 0x002c ++#define DMFC_GENERAL1_ALT 0x0030 ++#define DMFC_STAT 0x0034 ++ ++#define DMFC_WR_CHAN_1_28 0 ++#define DMFC_WR_CHAN_2_41 8 ++#define DMFC_WR_CHAN_1C_42 16 ++#define DMFC_WR_CHAN_2C_43 24 ++ ++#define DMFC_DP_CHAN_5B_23 0 ++#define DMFC_DP_CHAN_5F_27 8 ++#define DMFC_DP_CHAN_6B_24 16 ++#define DMFC_DP_CHAN_6F_29 24 ++ ++struct dmfc_channel_data { ++ int ipu_channel; ++ unsigned long channel_reg; ++ unsigned long shift; ++ unsigned eot_shift; ++ unsigned max_fifo_lines; ++}; ++ ++static const struct dmfc_channel_data dmfcdata[] = { ++ { ++ .ipu_channel = IPUV3_CHANNEL_MEM_BG_SYNC, ++ .channel_reg = DMFC_DP_CHAN, ++ .shift = DMFC_DP_CHAN_5B_23, ++ .eot_shift = 20, ++ .max_fifo_lines = 3, ++ }, { ++ .ipu_channel = 24, ++ .channel_reg = DMFC_DP_CHAN, ++ .shift = DMFC_DP_CHAN_6B_24, ++ .eot_shift = 22, ++ .max_fifo_lines = 1, ++ }, { ++ .ipu_channel = IPUV3_CHANNEL_MEM_FG_SYNC, ++ .channel_reg = DMFC_DP_CHAN, ++ .shift = DMFC_DP_CHAN_5F_27, ++ .eot_shift = 21, ++ .max_fifo_lines = 2, ++ }, { ++ .ipu_channel = IPUV3_CHANNEL_MEM_DC_SYNC, ++ .channel_reg = DMFC_WR_CHAN, ++ .shift = DMFC_WR_CHAN_1_28, ++ .eot_shift = 16, ++ .max_fifo_lines = 2, ++ }, { ++ .ipu_channel = 29, ++ .channel_reg = DMFC_DP_CHAN, ++ .shift = DMFC_DP_CHAN_6F_29, ++ .eot_shift = 23, ++ .max_fifo_lines = 1, ++ }, ++}; ++ ++#define DMFC_NUM_CHANNELS ARRAY_SIZE(dmfcdata) ++ ++struct ipu_dmfc_priv; ++ ++struct dmfc_channel { ++ unsigned slots; ++ struct ipu_soc *ipu; ++ struct ipu_dmfc_priv *priv; ++ const struct dmfc_channel_data *data; ++}; ++ ++struct ipu_dmfc_priv { ++ struct ipu_soc *ipu; ++ struct device *dev; ++ struct dmfc_channel channels[DMFC_NUM_CHANNELS]; ++ struct mutex mutex; ++ void __iomem *base; ++ int use_count; ++}; ++ ++int ipu_dmfc_enable_channel(struct dmfc_channel *dmfc) ++{ ++ struct ipu_dmfc_priv *priv = dmfc->priv; ++ mutex_lock(&priv->mutex); ++ ++ if (!priv->use_count) ++ ipu_module_enable(priv->ipu, IPU_CONF_DMFC_EN); ++ ++ priv->use_count++; ++ ++ mutex_unlock(&priv->mutex); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_dmfc_enable_channel); ++ ++void ipu_dmfc_disable_channel(struct dmfc_channel *dmfc) ++{ ++ struct ipu_dmfc_priv *priv = dmfc->priv; ++ ++ mutex_lock(&priv->mutex); ++ ++ priv->use_count--; ++ ++ if (!priv->use_count) ++ ipu_module_disable(priv->ipu, IPU_CONF_DMFC_EN); ++ ++ if (priv->use_count < 0) ++ priv->use_count = 0; ++ ++ mutex_unlock(&priv->mutex); ++} ++EXPORT_SYMBOL_GPL(ipu_dmfc_disable_channel); ++ ++void ipu_dmfc_config_wait4eot(struct dmfc_channel *dmfc, int width) ++{ ++ struct ipu_dmfc_priv *priv = dmfc->priv; ++ u32 dmfc_gen1; ++ ++ mutex_lock(&priv->mutex); ++ ++ dmfc_gen1 = readl(priv->base + DMFC_GENERAL1); ++ ++ if ((dmfc->slots * 64 * 4) / width > dmfc->data->max_fifo_lines) ++ dmfc_gen1 |= 1 << dmfc->data->eot_shift; ++ else ++ dmfc_gen1 &= ~(1 << dmfc->data->eot_shift); ++ ++ writel(dmfc_gen1, priv->base + DMFC_GENERAL1); ++ ++ mutex_unlock(&priv->mutex); ++} ++EXPORT_SYMBOL_GPL(ipu_dmfc_config_wait4eot); ++ ++struct dmfc_channel *ipu_dmfc_get(struct ipu_soc *ipu, int ipu_channel) ++{ ++ struct ipu_dmfc_priv *priv = ipu->dmfc_priv; ++ int i; ++ ++ for (i = 0; i < DMFC_NUM_CHANNELS; i++) ++ if (dmfcdata[i].ipu_channel == ipu_channel) ++ return &priv->channels[i]; ++ return ERR_PTR(-ENODEV); ++} ++EXPORT_SYMBOL_GPL(ipu_dmfc_get); ++ ++void ipu_dmfc_put(struct dmfc_channel *dmfc) ++{ ++} ++EXPORT_SYMBOL_GPL(ipu_dmfc_put); ++ ++int ipu_dmfc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base, ++ struct clk *ipu_clk) ++{ ++ struct ipu_dmfc_priv *priv; ++ int i; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ priv->base = devm_ioremap(dev, base, PAGE_SIZE); ++ if (!priv->base) ++ return -ENOMEM; ++ ++ priv->dev = dev; ++ priv->ipu = ipu; ++ mutex_init(&priv->mutex); ++ ++ ipu->dmfc_priv = priv; ++ ++ for (i = 0; i < DMFC_NUM_CHANNELS; i++) { ++ priv->channels[i].priv = priv; ++ priv->channels[i].ipu = ipu; ++ priv->channels[i].data = &dmfcdata[i]; ++ ++ if (dmfcdata[i].ipu_channel == IPUV3_CHANNEL_MEM_BG_SYNC || ++ dmfcdata[i].ipu_channel == IPUV3_CHANNEL_MEM_FG_SYNC || ++ dmfcdata[i].ipu_channel == IPUV3_CHANNEL_MEM_DC_SYNC) ++ priv->channels[i].slots = 2; ++ } ++ ++ writel(0x00000050, priv->base + DMFC_WR_CHAN); ++ writel(0x00005654, priv->base + DMFC_DP_CHAN); ++ writel(0x202020f6, priv->base + DMFC_WR_CHAN_DEF); ++ writel(0x2020f6f6, priv->base + DMFC_DP_CHAN_DEF); ++ writel(0x00000003, priv->base + DMFC_GENERAL1); ++ ++ return 0; ++} ++ ++void ipu_dmfc_exit(struct ipu_soc *ipu) ++{ ++} +--- /dev/null ++++ b/drivers/gpu/ipu-v3/ipu-dp.c +@@ -0,0 +1,357 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> ++ * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. ++ */ ++#include <linux/export.h> ++#include <linux/kernel.h> ++#include <linux/types.h> ++#include <linux/errno.h> ++#include <linux/io.h> ++#include <linux/err.h> ++ ++#include <video/imx-ipu-v3.h> ++#include "ipu-prv.h" ++ ++#define DP_SYNC 0 ++#define DP_ASYNC0 0x60 ++#define DP_ASYNC1 0xBC ++ ++#define DP_COM_CONF 0x0 ++#define DP_GRAPH_WIND_CTRL 0x0004 ++#define DP_FG_POS 0x0008 ++#define DP_CSC_A_0 0x0044 ++#define DP_CSC_A_1 0x0048 ++#define DP_CSC_A_2 0x004C ++#define DP_CSC_A_3 0x0050 ++#define DP_CSC_0 0x0054 ++#define DP_CSC_1 0x0058 ++ ++#define DP_COM_CONF_FG_EN (1 << 0) ++#define DP_COM_CONF_GWSEL (1 << 1) ++#define DP_COM_CONF_GWAM (1 << 2) ++#define DP_COM_CONF_GWCKE (1 << 3) ++#define DP_COM_CONF_CSC_DEF_MASK (3 << 8) ++#define DP_COM_CONF_CSC_DEF_OFFSET 8 ++#define DP_COM_CONF_CSC_DEF_FG (3 << 8) ++#define DP_COM_CONF_CSC_DEF_BG (2 << 8) ++#define DP_COM_CONF_CSC_DEF_BOTH (1 << 8) ++ ++#define IPUV3_NUM_FLOWS 3 ++ ++struct ipu_dp_priv; ++ ++struct ipu_dp { ++ u32 flow; ++ bool in_use; ++ bool foreground; ++ enum ipu_color_space in_cs; ++}; ++ ++struct ipu_flow { ++ struct ipu_dp foreground; ++ struct ipu_dp background; ++ enum ipu_color_space out_cs; ++ void __iomem *base; ++ struct ipu_dp_priv *priv; ++}; ++ ++struct ipu_dp_priv { ++ struct ipu_soc *ipu; ++ struct device *dev; ++ void __iomem *base; ++ struct ipu_flow flow[IPUV3_NUM_FLOWS]; ++ struct mutex mutex; ++ int use_count; ++}; ++ ++static u32 ipu_dp_flow_base[] = {DP_SYNC, DP_ASYNC0, DP_ASYNC1}; ++ ++static inline struct ipu_flow *to_flow(struct ipu_dp *dp) ++{ ++ if (dp->foreground) ++ return container_of(dp, struct ipu_flow, foreground); ++ else ++ return container_of(dp, struct ipu_flow, background); ++} ++ ++int ipu_dp_set_global_alpha(struct ipu_dp *dp, bool enable, ++ u8 alpha, bool bg_chan) ++{ ++ struct ipu_flow *flow = to_flow(dp); ++ struct ipu_dp_priv *priv = flow->priv; ++ u32 reg; ++ ++ mutex_lock(&priv->mutex); ++ ++ reg = readl(flow->base + DP_COM_CONF); ++ if (bg_chan) ++ reg &= ~DP_COM_CONF_GWSEL; ++ else ++ reg |= DP_COM_CONF_GWSEL; ++ writel(reg, flow->base + DP_COM_CONF); ++ ++ if (enable) { ++ reg = readl(flow->base + DP_GRAPH_WIND_CTRL) & 0x00FFFFFFL; ++ writel(reg | ((u32) alpha << 24), ++ flow->base + DP_GRAPH_WIND_CTRL); ++ ++ reg = readl(flow->base + DP_COM_CONF); ++ writel(reg | DP_COM_CONF_GWAM, flow->base + DP_COM_CONF); ++ } else { ++ reg = readl(flow->base + DP_COM_CONF); ++ writel(reg & ~DP_COM_CONF_GWAM, flow->base + DP_COM_CONF); ++ } ++ ++ ipu_srm_dp_update(priv->ipu, true); ++ ++ mutex_unlock(&priv->mutex); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_dp_set_global_alpha); ++ ++int ipu_dp_set_window_pos(struct ipu_dp *dp, u16 x_pos, u16 y_pos) ++{ ++ struct ipu_flow *flow = to_flow(dp); ++ struct ipu_dp_priv *priv = flow->priv; ++ ++ writel((x_pos << 16) | y_pos, flow->base + DP_FG_POS); ++ ++ ipu_srm_dp_update(priv->ipu, true); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_dp_set_window_pos); ++ ++static void ipu_dp_csc_init(struct ipu_flow *flow, ++ enum ipu_color_space in, ++ enum ipu_color_space out, ++ u32 place) ++{ ++ u32 reg; ++ ++ reg = readl(flow->base + DP_COM_CONF); ++ reg &= ~DP_COM_CONF_CSC_DEF_MASK; ++ ++ if (in == out) { ++ writel(reg, flow->base + DP_COM_CONF); ++ return; ++ } ++ ++ if (in == IPUV3_COLORSPACE_RGB && out == IPUV3_COLORSPACE_YUV) { ++ writel(0x099 | (0x12d << 16), flow->base + DP_CSC_A_0); ++ writel(0x03a | (0x3a9 << 16), flow->base + DP_CSC_A_1); ++ writel(0x356 | (0x100 << 16), flow->base + DP_CSC_A_2); ++ writel(0x100 | (0x329 << 16), flow->base + DP_CSC_A_3); ++ writel(0x3d6 | (0x0000 << 16) | (2 << 30), ++ flow->base + DP_CSC_0); ++ writel(0x200 | (2 << 14) | (0x200 << 16) | (2 << 30), ++ flow->base + DP_CSC_1); ++ } else { ++ writel(0x095 | (0x000 << 16), flow->base + DP_CSC_A_0); ++ writel(0x0cc | (0x095 << 16), flow->base + DP_CSC_A_1); ++ writel(0x3ce | (0x398 << 16), flow->base + DP_CSC_A_2); ++ writel(0x095 | (0x0ff << 16), flow->base + DP_CSC_A_3); ++ writel(0x000 | (0x3e42 << 16) | (1 << 30), ++ flow->base + DP_CSC_0); ++ writel(0x10a | (1 << 14) | (0x3dd6 << 16) | (1 << 30), ++ flow->base + DP_CSC_1); ++ } ++ ++ reg |= place; ++ ++ writel(reg, flow->base + DP_COM_CONF); ++} ++ ++int ipu_dp_setup_channel(struct ipu_dp *dp, ++ enum ipu_color_space in, ++ enum ipu_color_space out) ++{ ++ struct ipu_flow *flow = to_flow(dp); ++ struct ipu_dp_priv *priv = flow->priv; ++ ++ mutex_lock(&priv->mutex); ++ ++ dp->in_cs = in; ++ ++ if (!dp->foreground) ++ flow->out_cs = out; ++ ++ if (flow->foreground.in_cs == flow->background.in_cs) { ++ /* ++ * foreground and background are of same colorspace, put ++ * colorspace converter after combining unit. ++ */ ++ ipu_dp_csc_init(flow, flow->foreground.in_cs, flow->out_cs, ++ DP_COM_CONF_CSC_DEF_BOTH); ++ } else { ++ if (flow->foreground.in_cs == IPUV3_COLORSPACE_UNKNOWN || ++ flow->foreground.in_cs == flow->out_cs) ++ /* ++ * foreground identical to output, apply color ++ * conversion on background ++ */ ++ ipu_dp_csc_init(flow, flow->background.in_cs, ++ flow->out_cs, DP_COM_CONF_CSC_DEF_BG); ++ else ++ ipu_dp_csc_init(flow, flow->foreground.in_cs, ++ flow->out_cs, DP_COM_CONF_CSC_DEF_FG); ++ } ++ ++ ipu_srm_dp_update(priv->ipu, true); ++ ++ mutex_unlock(&priv->mutex); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_dp_setup_channel); ++ ++int ipu_dp_enable(struct ipu_soc *ipu) ++{ ++ struct ipu_dp_priv *priv = ipu->dp_priv; ++ ++ mutex_lock(&priv->mutex); ++ ++ if (!priv->use_count) ++ ipu_module_enable(priv->ipu, IPU_CONF_DP_EN); ++ ++ priv->use_count++; ++ ++ mutex_unlock(&priv->mutex); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_dp_enable); ++ ++int ipu_dp_enable_channel(struct ipu_dp *dp) ++{ ++ struct ipu_flow *flow = to_flow(dp); ++ struct ipu_dp_priv *priv = flow->priv; ++ u32 reg; ++ ++ if (!dp->foreground) ++ return 0; ++ ++ mutex_lock(&priv->mutex); ++ ++ reg = readl(flow->base + DP_COM_CONF); ++ reg |= DP_COM_CONF_FG_EN; ++ writel(reg, flow->base + DP_COM_CONF); ++ ++ ipu_srm_dp_update(priv->ipu, true); ++ ++ mutex_unlock(&priv->mutex); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_dp_enable_channel); ++ ++void ipu_dp_disable_channel(struct ipu_dp *dp, bool sync) ++{ ++ struct ipu_flow *flow = to_flow(dp); ++ struct ipu_dp_priv *priv = flow->priv; ++ u32 reg, csc; ++ ++ dp->in_cs = IPUV3_COLORSPACE_UNKNOWN; ++ ++ if (!dp->foreground) ++ return; ++ ++ mutex_lock(&priv->mutex); ++ ++ reg = readl(flow->base + DP_COM_CONF); ++ csc = reg & DP_COM_CONF_CSC_DEF_MASK; ++ reg &= ~DP_COM_CONF_CSC_DEF_MASK; ++ if (csc == DP_COM_CONF_CSC_DEF_BOTH || csc == DP_COM_CONF_CSC_DEF_BG) ++ reg |= DP_COM_CONF_CSC_DEF_BG; ++ ++ reg &= ~DP_COM_CONF_FG_EN; ++ writel(reg, flow->base + DP_COM_CONF); ++ ++ writel(0, flow->base + DP_FG_POS); ++ ipu_srm_dp_update(priv->ipu, sync); ++ ++ mutex_unlock(&priv->mutex); ++} ++EXPORT_SYMBOL_GPL(ipu_dp_disable_channel); ++ ++void ipu_dp_disable(struct ipu_soc *ipu) ++{ ++ struct ipu_dp_priv *priv = ipu->dp_priv; ++ ++ mutex_lock(&priv->mutex); ++ ++ priv->use_count--; ++ ++ if (!priv->use_count) ++ ipu_module_disable(priv->ipu, IPU_CONF_DP_EN); ++ ++ if (priv->use_count < 0) ++ priv->use_count = 0; ++ ++ mutex_unlock(&priv->mutex); ++} ++EXPORT_SYMBOL_GPL(ipu_dp_disable); ++ ++struct ipu_dp *ipu_dp_get(struct ipu_soc *ipu, unsigned int flow) ++{ ++ struct ipu_dp_priv *priv = ipu->dp_priv; ++ struct ipu_dp *dp; ++ ++ if ((flow >> 1) >= IPUV3_NUM_FLOWS) ++ return ERR_PTR(-EINVAL); ++ ++ if (flow & 1) ++ dp = &priv->flow[flow >> 1].foreground; ++ else ++ dp = &priv->flow[flow >> 1].background; ++ ++ if (dp->in_use) ++ return ERR_PTR(-EBUSY); ++ ++ dp->in_use = true; ++ ++ return dp; ++} ++EXPORT_SYMBOL_GPL(ipu_dp_get); ++ ++void ipu_dp_put(struct ipu_dp *dp) ++{ ++ dp->in_use = false; ++} ++EXPORT_SYMBOL_GPL(ipu_dp_put); ++ ++int ipu_dp_init(struct ipu_soc *ipu, struct device *dev, unsigned long base) ++{ ++ struct ipu_dp_priv *priv; ++ int i; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ priv->dev = dev; ++ priv->ipu = ipu; ++ ++ ipu->dp_priv = priv; ++ ++ priv->base = devm_ioremap(dev, base, PAGE_SIZE); ++ if (!priv->base) ++ return -ENOMEM; ++ ++ mutex_init(&priv->mutex); ++ ++ for (i = 0; i < IPUV3_NUM_FLOWS; i++) { ++ priv->flow[i].background.in_cs = IPUV3_COLORSPACE_UNKNOWN; ++ priv->flow[i].foreground.in_cs = IPUV3_COLORSPACE_UNKNOWN; ++ priv->flow[i].foreground.foreground = true; ++ priv->flow[i].base = priv->base + ipu_dp_flow_base[i]; ++ priv->flow[i].priv = priv; ++ } ++ ++ return 0; ++} ++ ++void ipu_dp_exit(struct ipu_soc *ipu) ++{ ++} +--- /dev/null ++++ b/drivers/gpu/ipu-v3/ipu-ic.c +@@ -0,0 +1,761 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2012-2014 Mentor Graphics Inc. ++ * Copyright 2005-2012 Freescale Semiconductor, Inc. All Rights Reserved. ++ */ ++ ++#include <linux/types.h> ++#include <linux/init.h> ++#include <linux/errno.h> ++#include <linux/spinlock.h> ++#include <linux/bitrev.h> ++#include <linux/io.h> ++#include <linux/err.h> ++#include <linux/sizes.h> ++#include "ipu-prv.h" ++ ++/* IC Register Offsets */ ++#define IC_CONF 0x0000 ++#define IC_PRP_ENC_RSC 0x0004 ++#define IC_PRP_VF_RSC 0x0008 ++#define IC_PP_RSC 0x000C ++#define IC_CMBP_1 0x0010 ++#define IC_CMBP_2 0x0014 ++#define IC_IDMAC_1 0x0018 ++#define IC_IDMAC_2 0x001C ++#define IC_IDMAC_3 0x0020 ++#define IC_IDMAC_4 0x0024 ++ ++/* IC Register Fields */ ++#define IC_CONF_PRPENC_EN (1 << 0) ++#define IC_CONF_PRPENC_CSC1 (1 << 1) ++#define IC_CONF_PRPENC_ROT_EN (1 << 2) ++#define IC_CONF_PRPVF_EN (1 << 8) ++#define IC_CONF_PRPVF_CSC1 (1 << 9) ++#define IC_CONF_PRPVF_CSC2 (1 << 10) ++#define IC_CONF_PRPVF_CMB (1 << 11) ++#define IC_CONF_PRPVF_ROT_EN (1 << 12) ++#define IC_CONF_PP_EN (1 << 16) ++#define IC_CONF_PP_CSC1 (1 << 17) ++#define IC_CONF_PP_CSC2 (1 << 18) ++#define IC_CONF_PP_CMB (1 << 19) ++#define IC_CONF_PP_ROT_EN (1 << 20) ++#define IC_CONF_IC_GLB_LOC_A (1 << 28) ++#define IC_CONF_KEY_COLOR_EN (1 << 29) ++#define IC_CONF_RWS_EN (1 << 30) ++#define IC_CONF_CSI_MEM_WR_EN (1 << 31) ++ ++#define IC_IDMAC_1_CB0_BURST_16 (1 << 0) ++#define IC_IDMAC_1_CB1_BURST_16 (1 << 1) ++#define IC_IDMAC_1_CB2_BURST_16 (1 << 2) ++#define IC_IDMAC_1_CB3_BURST_16 (1 << 3) ++#define IC_IDMAC_1_CB4_BURST_16 (1 << 4) ++#define IC_IDMAC_1_CB5_BURST_16 (1 << 5) ++#define IC_IDMAC_1_CB6_BURST_16 (1 << 6) ++#define IC_IDMAC_1_CB7_BURST_16 (1 << 7) ++#define IC_IDMAC_1_PRPENC_ROT_MASK (0x7 << 11) ++#define IC_IDMAC_1_PRPENC_ROT_OFFSET 11 ++#define IC_IDMAC_1_PRPVF_ROT_MASK (0x7 << 14) ++#define IC_IDMAC_1_PRPVF_ROT_OFFSET 14 ++#define IC_IDMAC_1_PP_ROT_MASK (0x7 << 17) ++#define IC_IDMAC_1_PP_ROT_OFFSET 17 ++#define IC_IDMAC_1_PP_FLIP_RS (1 << 22) ++#define IC_IDMAC_1_PRPVF_FLIP_RS (1 << 21) ++#define IC_IDMAC_1_PRPENC_FLIP_RS (1 << 20) ++ ++#define IC_IDMAC_2_PRPENC_HEIGHT_MASK (0x3ff << 0) ++#define IC_IDMAC_2_PRPENC_HEIGHT_OFFSET 0 ++#define IC_IDMAC_2_PRPVF_HEIGHT_MASK (0x3ff << 10) ++#define IC_IDMAC_2_PRPVF_HEIGHT_OFFSET 10 ++#define IC_IDMAC_2_PP_HEIGHT_MASK (0x3ff << 20) ++#define IC_IDMAC_2_PP_HEIGHT_OFFSET 20 ++ ++#define IC_IDMAC_3_PRPENC_WIDTH_MASK (0x3ff << 0) ++#define IC_IDMAC_3_PRPENC_WIDTH_OFFSET 0 ++#define IC_IDMAC_3_PRPVF_WIDTH_MASK (0x3ff << 10) ++#define IC_IDMAC_3_PRPVF_WIDTH_OFFSET 10 ++#define IC_IDMAC_3_PP_WIDTH_MASK (0x3ff << 20) ++#define IC_IDMAC_3_PP_WIDTH_OFFSET 20 ++ ++struct ic_task_regoffs { ++ u32 rsc; ++ u32 tpmem_csc[2]; ++}; ++ ++struct ic_task_bitfields { ++ u32 ic_conf_en; ++ u32 ic_conf_rot_en; ++ u32 ic_conf_cmb_en; ++ u32 ic_conf_csc1_en; ++ u32 ic_conf_csc2_en; ++ u32 ic_cmb_galpha_bit; ++}; ++ ++static const struct ic_task_regoffs ic_task_reg[IC_NUM_TASKS] = { ++ [IC_TASK_ENCODER] = { ++ .rsc = IC_PRP_ENC_RSC, ++ .tpmem_csc = {0x2008, 0}, ++ }, ++ [IC_TASK_VIEWFINDER] = { ++ .rsc = IC_PRP_VF_RSC, ++ .tpmem_csc = {0x4028, 0x4040}, ++ }, ++ [IC_TASK_POST_PROCESSOR] = { ++ .rsc = IC_PP_RSC, ++ .tpmem_csc = {0x6060, 0x6078}, ++ }, ++}; ++ ++static const struct ic_task_bitfields ic_task_bit[IC_NUM_TASKS] = { ++ [IC_TASK_ENCODER] = { ++ .ic_conf_en = IC_CONF_PRPENC_EN, ++ .ic_conf_rot_en = IC_CONF_PRPENC_ROT_EN, ++ .ic_conf_cmb_en = 0, /* NA */ ++ .ic_conf_csc1_en = IC_CONF_PRPENC_CSC1, ++ .ic_conf_csc2_en = 0, /* NA */ ++ .ic_cmb_galpha_bit = 0, /* NA */ ++ }, ++ [IC_TASK_VIEWFINDER] = { ++ .ic_conf_en = IC_CONF_PRPVF_EN, ++ .ic_conf_rot_en = IC_CONF_PRPVF_ROT_EN, ++ .ic_conf_cmb_en = IC_CONF_PRPVF_CMB, ++ .ic_conf_csc1_en = IC_CONF_PRPVF_CSC1, ++ .ic_conf_csc2_en = IC_CONF_PRPVF_CSC2, ++ .ic_cmb_galpha_bit = 0, ++ }, ++ [IC_TASK_POST_PROCESSOR] = { ++ .ic_conf_en = IC_CONF_PP_EN, ++ .ic_conf_rot_en = IC_CONF_PP_ROT_EN, ++ .ic_conf_cmb_en = IC_CONF_PP_CMB, ++ .ic_conf_csc1_en = IC_CONF_PP_CSC1, ++ .ic_conf_csc2_en = IC_CONF_PP_CSC2, ++ .ic_cmb_galpha_bit = 8, ++ }, ++}; ++ ++struct ipu_ic_priv; ++ ++struct ipu_ic { ++ enum ipu_ic_task task; ++ const struct ic_task_regoffs *reg; ++ const struct ic_task_bitfields *bit; ++ ++ struct ipu_ic_colorspace in_cs; ++ struct ipu_ic_colorspace g_in_cs; ++ struct ipu_ic_colorspace out_cs; ++ ++ bool graphics; ++ bool rotation; ++ bool in_use; ++ ++ struct ipu_ic_priv *priv; ++}; ++ ++struct ipu_ic_priv { ++ void __iomem *base; ++ void __iomem *tpmem_base; ++ spinlock_t lock; ++ struct ipu_soc *ipu; ++ int use_count; ++ int irt_use_count; ++ struct ipu_ic task[IC_NUM_TASKS]; ++}; ++ ++static inline u32 ipu_ic_read(struct ipu_ic *ic, unsigned offset) ++{ ++ return readl(ic->priv->base + offset); ++} ++ ++static inline void ipu_ic_write(struct ipu_ic *ic, u32 value, unsigned offset) ++{ ++ writel(value, ic->priv->base + offset); ++} ++ ++static int init_csc(struct ipu_ic *ic, ++ const struct ipu_ic_csc *csc, ++ int csc_index) ++{ ++ struct ipu_ic_priv *priv = ic->priv; ++ u32 __iomem *base; ++ const u16 (*c)[3]; ++ const u16 *a; ++ u32 param; ++ ++ base = (u32 __iomem *) ++ (priv->tpmem_base + ic->reg->tpmem_csc[csc_index]); ++ ++ /* Cast to unsigned */ ++ c = (const u16 (*)[3])csc->params.coeff; ++ a = (const u16 *)csc->params.offset; ++ ++ param = ((a[0] & 0x1f) << 27) | ((c[0][0] & 0x1ff) << 18) | ++ ((c[1][1] & 0x1ff) << 9) | (c[2][2] & 0x1ff); ++ writel(param, base++); ++ ++ param = ((a[0] & 0x1fe0) >> 5) | (csc->params.scale << 8) | ++ (csc->params.sat << 10); ++ writel(param, base++); ++ ++ param = ((a[1] & 0x1f) << 27) | ((c[0][1] & 0x1ff) << 18) | ++ ((c[1][0] & 0x1ff) << 9) | (c[2][0] & 0x1ff); ++ writel(param, base++); ++ ++ param = ((a[1] & 0x1fe0) >> 5); ++ writel(param, base++); ++ ++ param = ((a[2] & 0x1f) << 27) | ((c[0][2] & 0x1ff) << 18) | ++ ((c[1][2] & 0x1ff) << 9) | (c[2][1] & 0x1ff); ++ writel(param, base++); ++ ++ param = ((a[2] & 0x1fe0) >> 5); ++ writel(param, base++); ++ ++ return 0; ++} ++ ++static int calc_resize_coeffs(struct ipu_ic *ic, ++ u32 in_size, u32 out_size, ++ u32 *resize_coeff, ++ u32 *downsize_coeff) ++{ ++ struct ipu_ic_priv *priv = ic->priv; ++ struct ipu_soc *ipu = priv->ipu; ++ u32 temp_size, temp_downsize; ++ ++ /* ++ * Input size cannot be more than 4096, and output size cannot ++ * be more than 1024 ++ */ ++ if (in_size > 4096) { ++ dev_err(ipu->dev, "Unsupported resize (in_size > 4096)\n"); ++ return -EINVAL; ++ } ++ if (out_size > 1024) { ++ dev_err(ipu->dev, "Unsupported resize (out_size > 1024)\n"); ++ return -EINVAL; ++ } ++ ++ /* Cannot downsize more than 4:1 */ ++ if ((out_size << 2) < in_size) { ++ dev_err(ipu->dev, "Unsupported downsize\n"); ++ return -EINVAL; ++ } ++ ++ /* Compute downsizing coefficient */ ++ temp_downsize = 0; ++ temp_size = in_size; ++ while (((temp_size > 1024) || (temp_size >= out_size * 2)) && ++ (temp_downsize < 2)) { ++ temp_size >>= 1; ++ temp_downsize++; ++ } ++ *downsize_coeff = temp_downsize; ++ ++ /* ++ * compute resizing coefficient using the following equation: ++ * resize_coeff = M * (SI - 1) / (SO - 1) ++ * where M = 2^13, SI = input size, SO = output size ++ */ ++ *resize_coeff = (8192L * (temp_size - 1)) / (out_size - 1); ++ if (*resize_coeff >= 16384L) { ++ dev_err(ipu->dev, "Warning! Overflow on resize coeff.\n"); ++ *resize_coeff = 0x3FFF; ++ } ++ ++ return 0; ++} ++ ++void ipu_ic_task_enable(struct ipu_ic *ic) ++{ ++ struct ipu_ic_priv *priv = ic->priv; ++ unsigned long flags; ++ u32 ic_conf; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ ic_conf = ipu_ic_read(ic, IC_CONF); ++ ++ ic_conf |= ic->bit->ic_conf_en; ++ ++ if (ic->rotation) ++ ic_conf |= ic->bit->ic_conf_rot_en; ++ ++ if (ic->in_cs.cs != ic->out_cs.cs) ++ ic_conf |= ic->bit->ic_conf_csc1_en; ++ ++ if (ic->graphics) { ++ ic_conf |= ic->bit->ic_conf_cmb_en; ++ ic_conf |= ic->bit->ic_conf_csc1_en; ++ ++ if (ic->g_in_cs.cs != ic->out_cs.cs) ++ ic_conf |= ic->bit->ic_conf_csc2_en; ++ } ++ ++ ipu_ic_write(ic, ic_conf, IC_CONF); ++ ++ spin_unlock_irqrestore(&priv->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_ic_task_enable); ++ ++void ipu_ic_task_disable(struct ipu_ic *ic) ++{ ++ struct ipu_ic_priv *priv = ic->priv; ++ unsigned long flags; ++ u32 ic_conf; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ ic_conf = ipu_ic_read(ic, IC_CONF); ++ ++ ic_conf &= ~(ic->bit->ic_conf_en | ++ ic->bit->ic_conf_csc1_en | ++ ic->bit->ic_conf_rot_en); ++ if (ic->bit->ic_conf_csc2_en) ++ ic_conf &= ~ic->bit->ic_conf_csc2_en; ++ if (ic->bit->ic_conf_cmb_en) ++ ic_conf &= ~ic->bit->ic_conf_cmb_en; ++ ++ ipu_ic_write(ic, ic_conf, IC_CONF); ++ ++ spin_unlock_irqrestore(&priv->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_ic_task_disable); ++ ++int ipu_ic_task_graphics_init(struct ipu_ic *ic, ++ const struct ipu_ic_colorspace *g_in_cs, ++ bool galpha_en, u32 galpha, ++ bool colorkey_en, u32 colorkey) ++{ ++ struct ipu_ic_priv *priv = ic->priv; ++ struct ipu_ic_csc csc2; ++ unsigned long flags; ++ u32 reg, ic_conf; ++ int ret = 0; ++ ++ if (ic->task == IC_TASK_ENCODER) ++ return -EINVAL; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ ic_conf = ipu_ic_read(ic, IC_CONF); ++ ++ if (!(ic_conf & ic->bit->ic_conf_csc1_en)) { ++ struct ipu_ic_csc csc1; ++ ++ ret = ipu_ic_calc_csc(&csc1, ++ V4L2_YCBCR_ENC_601, ++ V4L2_QUANTIZATION_FULL_RANGE, ++ IPUV3_COLORSPACE_RGB, ++ V4L2_YCBCR_ENC_601, ++ V4L2_QUANTIZATION_FULL_RANGE, ++ IPUV3_COLORSPACE_RGB); ++ if (ret) ++ goto unlock; ++ ++ /* need transparent CSC1 conversion */ ++ ret = init_csc(ic, &csc1, 0); ++ if (ret) ++ goto unlock; ++ } ++ ++ ic->g_in_cs = *g_in_cs; ++ csc2.in_cs = ic->g_in_cs; ++ csc2.out_cs = ic->out_cs; ++ ++ ret = __ipu_ic_calc_csc(&csc2); ++ if (ret) ++ goto unlock; ++ ++ ret = init_csc(ic, &csc2, 1); ++ if (ret) ++ goto unlock; ++ ++ if (galpha_en) { ++ ic_conf |= IC_CONF_IC_GLB_LOC_A; ++ reg = ipu_ic_read(ic, IC_CMBP_1); ++ reg &= ~(0xff << ic->bit->ic_cmb_galpha_bit); ++ reg |= (galpha << ic->bit->ic_cmb_galpha_bit); ++ ipu_ic_write(ic, reg, IC_CMBP_1); ++ } else ++ ic_conf &= ~IC_CONF_IC_GLB_LOC_A; ++ ++ if (colorkey_en) { ++ ic_conf |= IC_CONF_KEY_COLOR_EN; ++ ipu_ic_write(ic, colorkey, IC_CMBP_2); ++ } else ++ ic_conf &= ~IC_CONF_KEY_COLOR_EN; ++ ++ ipu_ic_write(ic, ic_conf, IC_CONF); ++ ++ ic->graphics = true; ++unlock: ++ spin_unlock_irqrestore(&priv->lock, flags); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(ipu_ic_task_graphics_init); ++ ++int ipu_ic_task_init_rsc(struct ipu_ic *ic, ++ const struct ipu_ic_csc *csc, ++ int in_width, int in_height, ++ int out_width, int out_height, ++ u32 rsc) ++{ ++ struct ipu_ic_priv *priv = ic->priv; ++ u32 downsize_coeff, resize_coeff; ++ unsigned long flags; ++ int ret = 0; ++ ++ if (!rsc) { ++ /* Setup vertical resizing */ ++ ++ ret = calc_resize_coeffs(ic, in_height, out_height, ++ &resize_coeff, &downsize_coeff); ++ if (ret) ++ return ret; ++ ++ rsc = (downsize_coeff << 30) | (resize_coeff << 16); ++ ++ /* Setup horizontal resizing */ ++ ret = calc_resize_coeffs(ic, in_width, out_width, ++ &resize_coeff, &downsize_coeff); ++ if (ret) ++ return ret; ++ ++ rsc |= (downsize_coeff << 14) | resize_coeff; ++ } ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ ipu_ic_write(ic, rsc, ic->reg->rsc); ++ ++ /* Setup color space conversion */ ++ ic->in_cs = csc->in_cs; ++ ic->out_cs = csc->out_cs; ++ ++ ret = init_csc(ic, csc, 0); ++ ++ spin_unlock_irqrestore(&priv->lock, flags); ++ return ret; ++} ++ ++int ipu_ic_task_init(struct ipu_ic *ic, ++ const struct ipu_ic_csc *csc, ++ int in_width, int in_height, ++ int out_width, int out_height) ++{ ++ return ipu_ic_task_init_rsc(ic, csc, ++ in_width, in_height, ++ out_width, out_height, 0); ++} ++EXPORT_SYMBOL_GPL(ipu_ic_task_init); ++ ++int ipu_ic_task_idma_init(struct ipu_ic *ic, struct ipuv3_channel *channel, ++ u32 width, u32 height, int burst_size, ++ enum ipu_rotate_mode rot) ++{ ++ struct ipu_ic_priv *priv = ic->priv; ++ struct ipu_soc *ipu = priv->ipu; ++ u32 ic_idmac_1, ic_idmac_2, ic_idmac_3; ++ u32 temp_rot = bitrev8(rot) >> 5; ++ bool need_hor_flip = false; ++ unsigned long flags; ++ int ret = 0; ++ ++ if ((burst_size != 8) && (burst_size != 16)) { ++ dev_err(ipu->dev, "Illegal burst length for IC\n"); ++ return -EINVAL; ++ } ++ ++ width--; ++ height--; ++ ++ if (temp_rot & 0x2) /* Need horizontal flip */ ++ need_hor_flip = true; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ ic_idmac_1 = ipu_ic_read(ic, IC_IDMAC_1); ++ ic_idmac_2 = ipu_ic_read(ic, IC_IDMAC_2); ++ ic_idmac_3 = ipu_ic_read(ic, IC_IDMAC_3); ++ ++ switch (channel->num) { ++ case IPUV3_CHANNEL_IC_PP_MEM: ++ if (burst_size == 16) ++ ic_idmac_1 |= IC_IDMAC_1_CB2_BURST_16; ++ else ++ ic_idmac_1 &= ~IC_IDMAC_1_CB2_BURST_16; ++ ++ if (need_hor_flip) ++ ic_idmac_1 |= IC_IDMAC_1_PP_FLIP_RS; ++ else ++ ic_idmac_1 &= ~IC_IDMAC_1_PP_FLIP_RS; ++ ++ ic_idmac_2 &= ~IC_IDMAC_2_PP_HEIGHT_MASK; ++ ic_idmac_2 |= height << IC_IDMAC_2_PP_HEIGHT_OFFSET; ++ ++ ic_idmac_3 &= ~IC_IDMAC_3_PP_WIDTH_MASK; ++ ic_idmac_3 |= width << IC_IDMAC_3_PP_WIDTH_OFFSET; ++ break; ++ case IPUV3_CHANNEL_MEM_IC_PP: ++ if (burst_size == 16) ++ ic_idmac_1 |= IC_IDMAC_1_CB5_BURST_16; ++ else ++ ic_idmac_1 &= ~IC_IDMAC_1_CB5_BURST_16; ++ break; ++ case IPUV3_CHANNEL_MEM_ROT_PP: ++ ic_idmac_1 &= ~IC_IDMAC_1_PP_ROT_MASK; ++ ic_idmac_1 |= temp_rot << IC_IDMAC_1_PP_ROT_OFFSET; ++ break; ++ case IPUV3_CHANNEL_MEM_IC_PRP_VF: ++ if (burst_size == 16) ++ ic_idmac_1 |= IC_IDMAC_1_CB6_BURST_16; ++ else ++ ic_idmac_1 &= ~IC_IDMAC_1_CB6_BURST_16; ++ break; ++ case IPUV3_CHANNEL_IC_PRP_ENC_MEM: ++ if (burst_size == 16) ++ ic_idmac_1 |= IC_IDMAC_1_CB0_BURST_16; ++ else ++ ic_idmac_1 &= ~IC_IDMAC_1_CB0_BURST_16; ++ ++ if (need_hor_flip) ++ ic_idmac_1 |= IC_IDMAC_1_PRPENC_FLIP_RS; ++ else ++ ic_idmac_1 &= ~IC_IDMAC_1_PRPENC_FLIP_RS; ++ ++ ic_idmac_2 &= ~IC_IDMAC_2_PRPENC_HEIGHT_MASK; ++ ic_idmac_2 |= height << IC_IDMAC_2_PRPENC_HEIGHT_OFFSET; ++ ++ ic_idmac_3 &= ~IC_IDMAC_3_PRPENC_WIDTH_MASK; ++ ic_idmac_3 |= width << IC_IDMAC_3_PRPENC_WIDTH_OFFSET; ++ break; ++ case IPUV3_CHANNEL_MEM_ROT_ENC: ++ ic_idmac_1 &= ~IC_IDMAC_1_PRPENC_ROT_MASK; ++ ic_idmac_1 |= temp_rot << IC_IDMAC_1_PRPENC_ROT_OFFSET; ++ break; ++ case IPUV3_CHANNEL_IC_PRP_VF_MEM: ++ if (burst_size == 16) ++ ic_idmac_1 |= IC_IDMAC_1_CB1_BURST_16; ++ else ++ ic_idmac_1 &= ~IC_IDMAC_1_CB1_BURST_16; ++ ++ if (need_hor_flip) ++ ic_idmac_1 |= IC_IDMAC_1_PRPVF_FLIP_RS; ++ else ++ ic_idmac_1 &= ~IC_IDMAC_1_PRPVF_FLIP_RS; ++ ++ ic_idmac_2 &= ~IC_IDMAC_2_PRPVF_HEIGHT_MASK; ++ ic_idmac_2 |= height << IC_IDMAC_2_PRPVF_HEIGHT_OFFSET; ++ ++ ic_idmac_3 &= ~IC_IDMAC_3_PRPVF_WIDTH_MASK; ++ ic_idmac_3 |= width << IC_IDMAC_3_PRPVF_WIDTH_OFFSET; ++ break; ++ case IPUV3_CHANNEL_MEM_ROT_VF: ++ ic_idmac_1 &= ~IC_IDMAC_1_PRPVF_ROT_MASK; ++ ic_idmac_1 |= temp_rot << IC_IDMAC_1_PRPVF_ROT_OFFSET; ++ break; ++ case IPUV3_CHANNEL_G_MEM_IC_PRP_VF: ++ if (burst_size == 16) ++ ic_idmac_1 |= IC_IDMAC_1_CB3_BURST_16; ++ else ++ ic_idmac_1 &= ~IC_IDMAC_1_CB3_BURST_16; ++ break; ++ case IPUV3_CHANNEL_G_MEM_IC_PP: ++ if (burst_size == 16) ++ ic_idmac_1 |= IC_IDMAC_1_CB4_BURST_16; ++ else ++ ic_idmac_1 &= ~IC_IDMAC_1_CB4_BURST_16; ++ break; ++ case IPUV3_CHANNEL_VDI_MEM_IC_VF: ++ if (burst_size == 16) ++ ic_idmac_1 |= IC_IDMAC_1_CB7_BURST_16; ++ else ++ ic_idmac_1 &= ~IC_IDMAC_1_CB7_BURST_16; ++ break; ++ default: ++ goto unlock; ++ } ++ ++ ipu_ic_write(ic, ic_idmac_1, IC_IDMAC_1); ++ ipu_ic_write(ic, ic_idmac_2, IC_IDMAC_2); ++ ipu_ic_write(ic, ic_idmac_3, IC_IDMAC_3); ++ ++ if (ipu_rot_mode_is_irt(rot)) ++ ic->rotation = true; ++ ++unlock: ++ spin_unlock_irqrestore(&priv->lock, flags); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(ipu_ic_task_idma_init); ++ ++static void ipu_irt_enable(struct ipu_ic *ic) ++{ ++ struct ipu_ic_priv *priv = ic->priv; ++ ++ if (!priv->irt_use_count) ++ ipu_module_enable(priv->ipu, IPU_CONF_ROT_EN); ++ ++ priv->irt_use_count++; ++} ++ ++static void ipu_irt_disable(struct ipu_ic *ic) ++{ ++ struct ipu_ic_priv *priv = ic->priv; ++ ++ if (priv->irt_use_count) { ++ if (!--priv->irt_use_count) ++ ipu_module_disable(priv->ipu, IPU_CONF_ROT_EN); ++ } ++} ++ ++int ipu_ic_enable(struct ipu_ic *ic) ++{ ++ struct ipu_ic_priv *priv = ic->priv; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ if (!priv->use_count) ++ ipu_module_enable(priv->ipu, IPU_CONF_IC_EN); ++ ++ priv->use_count++; ++ ++ if (ic->rotation) ++ ipu_irt_enable(ic); ++ ++ spin_unlock_irqrestore(&priv->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_ic_enable); ++ ++int ipu_ic_disable(struct ipu_ic *ic) ++{ ++ struct ipu_ic_priv *priv = ic->priv; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ priv->use_count--; ++ ++ if (!priv->use_count) ++ ipu_module_disable(priv->ipu, IPU_CONF_IC_EN); ++ ++ if (priv->use_count < 0) ++ priv->use_count = 0; ++ ++ if (ic->rotation) ++ ipu_irt_disable(ic); ++ ++ ic->rotation = ic->graphics = false; ++ ++ spin_unlock_irqrestore(&priv->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_ic_disable); ++ ++struct ipu_ic *ipu_ic_get(struct ipu_soc *ipu, enum ipu_ic_task task) ++{ ++ struct ipu_ic_priv *priv = ipu->ic_priv; ++ unsigned long flags; ++ struct ipu_ic *ic, *ret; ++ ++ if (task >= IC_NUM_TASKS) ++ return ERR_PTR(-EINVAL); ++ ++ ic = &priv->task[task]; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ if (ic->in_use) { ++ ret = ERR_PTR(-EBUSY); ++ goto unlock; ++ } ++ ++ ic->in_use = true; ++ ret = ic; ++ ++unlock: ++ spin_unlock_irqrestore(&priv->lock, flags); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(ipu_ic_get); ++ ++void ipu_ic_put(struct ipu_ic *ic) ++{ ++ struct ipu_ic_priv *priv = ic->priv; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ic->in_use = false; ++ spin_unlock_irqrestore(&priv->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_ic_put); ++ ++int ipu_ic_init(struct ipu_soc *ipu, struct device *dev, ++ unsigned long base, unsigned long tpmem_base) ++{ ++ struct ipu_ic_priv *priv; ++ int i; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ ipu->ic_priv = priv; ++ ++ spin_lock_init(&priv->lock); ++ priv->base = devm_ioremap(dev, base, PAGE_SIZE); ++ if (!priv->base) ++ return -ENOMEM; ++ priv->tpmem_base = devm_ioremap(dev, tpmem_base, SZ_64K); ++ if (!priv->tpmem_base) ++ return -ENOMEM; ++ ++ dev_dbg(dev, "IC base: 0x%08lx remapped to %p\n", base, priv->base); ++ ++ priv->ipu = ipu; ++ ++ for (i = 0; i < IC_NUM_TASKS; i++) { ++ priv->task[i].task = i; ++ priv->task[i].priv = priv; ++ priv->task[i].reg = &ic_task_reg[i]; ++ priv->task[i].bit = &ic_task_bit[i]; ++ } ++ ++ return 0; ++} ++ ++void ipu_ic_exit(struct ipu_soc *ipu) ++{ ++} ++ ++void ipu_ic_dump(struct ipu_ic *ic) ++{ ++ struct ipu_ic_priv *priv = ic->priv; ++ struct ipu_soc *ipu = priv->ipu; ++ ++ dev_dbg(ipu->dev, "IC_CONF = \t0x%08X\n", ++ ipu_ic_read(ic, IC_CONF)); ++ dev_dbg(ipu->dev, "IC_PRP_ENC_RSC = \t0x%08X\n", ++ ipu_ic_read(ic, IC_PRP_ENC_RSC)); ++ dev_dbg(ipu->dev, "IC_PRP_VF_RSC = \t0x%08X\n", ++ ipu_ic_read(ic, IC_PRP_VF_RSC)); ++ dev_dbg(ipu->dev, "IC_PP_RSC = \t0x%08X\n", ++ ipu_ic_read(ic, IC_PP_RSC)); ++ dev_dbg(ipu->dev, "IC_CMBP_1 = \t0x%08X\n", ++ ipu_ic_read(ic, IC_CMBP_1)); ++ dev_dbg(ipu->dev, "IC_CMBP_2 = \t0x%08X\n", ++ ipu_ic_read(ic, IC_CMBP_2)); ++ dev_dbg(ipu->dev, "IC_IDMAC_1 = \t0x%08X\n", ++ ipu_ic_read(ic, IC_IDMAC_1)); ++ dev_dbg(ipu->dev, "IC_IDMAC_2 = \t0x%08X\n", ++ ipu_ic_read(ic, IC_IDMAC_2)); ++ dev_dbg(ipu->dev, "IC_IDMAC_3 = \t0x%08X\n", ++ ipu_ic_read(ic, IC_IDMAC_3)); ++ dev_dbg(ipu->dev, "IC_IDMAC_4 = \t0x%08X\n", ++ ipu_ic_read(ic, IC_IDMAC_4)); ++} ++EXPORT_SYMBOL_GPL(ipu_ic_dump); +--- /dev/null ++++ b/drivers/gpu/ipu-v3/ipu-image-convert.c +@@ -0,0 +1,2475 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2012-2016 Mentor Graphics Inc. ++ * ++ * Queued image conversion support, with tiling and rotation. ++ */ ++ ++#include <linux/interrupt.h> ++#include <linux/dma-mapping.h> ++#include <video/imx-ipu-image-convert.h> ++#include "ipu-prv.h" ++ ++/* ++ * The IC Resizer has a restriction that the output frame from the ++ * resizer must be 1024 or less in both width (pixels) and height ++ * (lines). ++ * ++ * The image converter attempts to split up a conversion when ++ * the desired output (converted) frame resolution exceeds the ++ * IC resizer limit of 1024 in either dimension. ++ * ++ * If either dimension of the output frame exceeds the limit, the ++ * dimension is split into 1, 2, or 4 equal stripes, for a maximum ++ * of 4*4 or 16 tiles. A conversion is then carried out for each ++ * tile (but taking care to pass the full frame stride length to ++ * the DMA channel's parameter memory!). IDMA double-buffering is used ++ * to convert each tile back-to-back when possible (see note below ++ * when double_buffering boolean is set). ++ * ++ * Note that the input frame must be split up into the same number ++ * of tiles as the output frame: ++ * ++ * +---------+-----+ ++ * +-----+---+ | A | B | ++ * | A | B | | | | ++ * +-----+---+ --> +---------+-----+ ++ * | C | D | | C | D | ++ * +-----+---+ | | | ++ * +---------+-----+ ++ * ++ * Clockwise 90° rotations are handled by first rescaling into a ++ * reusable temporary tile buffer and then rotating with the 8x8 ++ * block rotator, writing to the correct destination: ++ * ++ * +-----+-----+ ++ * | | | ++ * +-----+---+ +---------+ | C | A | ++ * | A | B | | A,B, | | | | | ++ * +-----+---+ --> | C,D | | --> | | | ++ * | C | D | +---------+ +-----+-----+ ++ * +-----+---+ | D | B | ++ * | | | ++ * +-----+-----+ ++ * ++ * If the 8x8 block rotator is used, horizontal or vertical flipping ++ * is done during the rotation step, otherwise flipping is done ++ * during the scaling step. ++ * With rotation or flipping, tile order changes between input and ++ * output image. Tiles are numbered row major from top left to bottom ++ * right for both input and output image. ++ */ ++ ++#define MAX_STRIPES_W 4 ++#define MAX_STRIPES_H 4 ++#define MAX_TILES (MAX_STRIPES_W * MAX_STRIPES_H) ++ ++#define MIN_W 16 ++#define MIN_H 8 ++#define MAX_W 4096 ++#define MAX_H 4096 ++ ++enum ipu_image_convert_type { ++ IMAGE_CONVERT_IN = 0, ++ IMAGE_CONVERT_OUT, ++}; ++ ++struct ipu_image_convert_dma_buf { ++ void *virt; ++ dma_addr_t phys; ++ unsigned long len; ++}; ++ ++struct ipu_image_convert_dma_chan { ++ int in; ++ int out; ++ int rot_in; ++ int rot_out; ++ int vdi_in_p; ++ int vdi_in; ++ int vdi_in_n; ++}; ++ ++/* dimensions of one tile */ ++struct ipu_image_tile { ++ u32 width; ++ u32 height; ++ u32 left; ++ u32 top; ++ /* size and strides are in bytes */ ++ u32 size; ++ u32 stride; ++ u32 rot_stride; ++ /* start Y or packed offset of this tile */ ++ u32 offset; ++ /* offset from start to tile in U plane, for planar formats */ ++ u32 u_off; ++ /* offset from start to tile in V plane, for planar formats */ ++ u32 v_off; ++}; ++ ++struct ipu_image_convert_image { ++ struct ipu_image base; ++ enum ipu_image_convert_type type; ++ ++ const struct ipu_image_pixfmt *fmt; ++ unsigned int stride; ++ ++ /* # of rows (horizontal stripes) if dest height is > 1024 */ ++ unsigned int num_rows; ++ /* # of columns (vertical stripes) if dest width is > 1024 */ ++ unsigned int num_cols; ++ ++ struct ipu_image_tile tile[MAX_TILES]; ++}; ++ ++struct ipu_image_pixfmt { ++ u32 fourcc; /* V4L2 fourcc */ ++ int bpp; /* total bpp */ ++ int uv_width_dec; /* decimation in width for U/V planes */ ++ int uv_height_dec; /* decimation in height for U/V planes */ ++ bool planar; /* planar format */ ++ bool uv_swapped; /* U and V planes are swapped */ ++ bool uv_packed; /* partial planar (U and V in same plane) */ ++}; ++ ++struct ipu_image_convert_ctx; ++struct ipu_image_convert_chan; ++struct ipu_image_convert_priv; ++ ++struct ipu_image_convert_ctx { ++ struct ipu_image_convert_chan *chan; ++ ++ ipu_image_convert_cb_t complete; ++ void *complete_context; ++ ++ /* Source/destination image data and rotation mode */ ++ struct ipu_image_convert_image in; ++ struct ipu_image_convert_image out; ++ struct ipu_ic_csc csc; ++ enum ipu_rotate_mode rot_mode; ++ u32 downsize_coeff_h; ++ u32 downsize_coeff_v; ++ u32 image_resize_coeff_h; ++ u32 image_resize_coeff_v; ++ u32 resize_coeffs_h[MAX_STRIPES_W]; ++ u32 resize_coeffs_v[MAX_STRIPES_H]; ++ ++ /* intermediate buffer for rotation */ ++ struct ipu_image_convert_dma_buf rot_intermediate[2]; ++ ++ /* current buffer number for double buffering */ ++ int cur_buf_num; ++ ++ bool aborting; ++ struct completion aborted; ++ ++ /* can we use double-buffering for this conversion operation? */ ++ bool double_buffering; ++ /* num_rows * num_cols */ ++ unsigned int num_tiles; ++ /* next tile to process */ ++ unsigned int next_tile; ++ /* where to place converted tile in dest image */ ++ unsigned int out_tile_map[MAX_TILES]; ++ ++ struct list_head list; ++}; ++ ++struct ipu_image_convert_chan { ++ struct ipu_image_convert_priv *priv; ++ ++ enum ipu_ic_task ic_task; ++ const struct ipu_image_convert_dma_chan *dma_ch; ++ ++ struct ipu_ic *ic; ++ struct ipuv3_channel *in_chan; ++ struct ipuv3_channel *out_chan; ++ struct ipuv3_channel *rotation_in_chan; ++ struct ipuv3_channel *rotation_out_chan; ++ ++ /* the IPU end-of-frame irqs */ ++ int out_eof_irq; ++ int rot_out_eof_irq; ++ ++ spinlock_t irqlock; ++ ++ /* list of convert contexts */ ++ struct list_head ctx_list; ++ /* queue of conversion runs */ ++ struct list_head pending_q; ++ /* queue of completed runs */ ++ struct list_head done_q; ++ ++ /* the current conversion run */ ++ struct ipu_image_convert_run *current_run; ++}; ++ ++struct ipu_image_convert_priv { ++ struct ipu_image_convert_chan chan[IC_NUM_TASKS]; ++ struct ipu_soc *ipu; ++}; ++ ++static const struct ipu_image_convert_dma_chan ++image_convert_dma_chan[IC_NUM_TASKS] = { ++ [IC_TASK_VIEWFINDER] = { ++ .in = IPUV3_CHANNEL_MEM_IC_PRP_VF, ++ .out = IPUV3_CHANNEL_IC_PRP_VF_MEM, ++ .rot_in = IPUV3_CHANNEL_MEM_ROT_VF, ++ .rot_out = IPUV3_CHANNEL_ROT_VF_MEM, ++ .vdi_in_p = IPUV3_CHANNEL_MEM_VDI_PREV, ++ .vdi_in = IPUV3_CHANNEL_MEM_VDI_CUR, ++ .vdi_in_n = IPUV3_CHANNEL_MEM_VDI_NEXT, ++ }, ++ [IC_TASK_POST_PROCESSOR] = { ++ .in = IPUV3_CHANNEL_MEM_IC_PP, ++ .out = IPUV3_CHANNEL_IC_PP_MEM, ++ .rot_in = IPUV3_CHANNEL_MEM_ROT_PP, ++ .rot_out = IPUV3_CHANNEL_ROT_PP_MEM, ++ }, ++}; ++ ++static const struct ipu_image_pixfmt image_convert_formats[] = { ++ { ++ .fourcc = V4L2_PIX_FMT_RGB565, ++ .bpp = 16, ++ }, { ++ .fourcc = V4L2_PIX_FMT_RGB24, ++ .bpp = 24, ++ }, { ++ .fourcc = V4L2_PIX_FMT_BGR24, ++ .bpp = 24, ++ }, { ++ .fourcc = V4L2_PIX_FMT_RGB32, ++ .bpp = 32, ++ }, { ++ .fourcc = V4L2_PIX_FMT_BGR32, ++ .bpp = 32, ++ }, { ++ .fourcc = V4L2_PIX_FMT_XRGB32, ++ .bpp = 32, ++ }, { ++ .fourcc = V4L2_PIX_FMT_XBGR32, ++ .bpp = 32, ++ }, { ++ .fourcc = V4L2_PIX_FMT_BGRX32, ++ .bpp = 32, ++ }, { ++ .fourcc = V4L2_PIX_FMT_RGBX32, ++ .bpp = 32, ++ }, { ++ .fourcc = V4L2_PIX_FMT_YUYV, ++ .bpp = 16, ++ .uv_width_dec = 2, ++ .uv_height_dec = 1, ++ }, { ++ .fourcc = V4L2_PIX_FMT_UYVY, ++ .bpp = 16, ++ .uv_width_dec = 2, ++ .uv_height_dec = 1, ++ }, { ++ .fourcc = V4L2_PIX_FMT_YUV420, ++ .bpp = 12, ++ .planar = true, ++ .uv_width_dec = 2, ++ .uv_height_dec = 2, ++ }, { ++ .fourcc = V4L2_PIX_FMT_YVU420, ++ .bpp = 12, ++ .planar = true, ++ .uv_width_dec = 2, ++ .uv_height_dec = 2, ++ .uv_swapped = true, ++ }, { ++ .fourcc = V4L2_PIX_FMT_NV12, ++ .bpp = 12, ++ .planar = true, ++ .uv_width_dec = 2, ++ .uv_height_dec = 2, ++ .uv_packed = true, ++ }, { ++ .fourcc = V4L2_PIX_FMT_YUV422P, ++ .bpp = 16, ++ .planar = true, ++ .uv_width_dec = 2, ++ .uv_height_dec = 1, ++ }, { ++ .fourcc = V4L2_PIX_FMT_NV16, ++ .bpp = 16, ++ .planar = true, ++ .uv_width_dec = 2, ++ .uv_height_dec = 1, ++ .uv_packed = true, ++ }, ++}; ++ ++static const struct ipu_image_pixfmt *get_format(u32 fourcc) ++{ ++ const struct ipu_image_pixfmt *ret = NULL; ++ unsigned int i; ++ ++ for (i = 0; i < ARRAY_SIZE(image_convert_formats); i++) { ++ if (image_convert_formats[i].fourcc == fourcc) { ++ ret = &image_convert_formats[i]; ++ break; ++ } ++ } ++ ++ return ret; ++} ++ ++static void dump_format(struct ipu_image_convert_ctx *ctx, ++ struct ipu_image_convert_image *ic_image) ++{ ++ struct ipu_image_convert_chan *chan = ctx->chan; ++ struct ipu_image_convert_priv *priv = chan->priv; ++ ++ dev_dbg(priv->ipu->dev, ++ "task %u: ctx %p: %s format: %dx%d (%dx%d tiles), %c%c%c%c\n", ++ chan->ic_task, ctx, ++ ic_image->type == IMAGE_CONVERT_OUT ? "Output" : "Input", ++ ic_image->base.pix.width, ic_image->base.pix.height, ++ ic_image->num_cols, ic_image->num_rows, ++ ic_image->fmt->fourcc & 0xff, ++ (ic_image->fmt->fourcc >> 8) & 0xff, ++ (ic_image->fmt->fourcc >> 16) & 0xff, ++ (ic_image->fmt->fourcc >> 24) & 0xff); ++} ++ ++int ipu_image_convert_enum_format(int index, u32 *fourcc) ++{ ++ const struct ipu_image_pixfmt *fmt; ++ ++ if (index >= (int)ARRAY_SIZE(image_convert_formats)) ++ return -EINVAL; ++ ++ /* Format found */ ++ fmt = &image_convert_formats[index]; ++ *fourcc = fmt->fourcc; ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_image_convert_enum_format); ++ ++static void free_dma_buf(struct ipu_image_convert_priv *priv, ++ struct ipu_image_convert_dma_buf *buf) ++{ ++ if (buf->virt) ++ dma_free_coherent(priv->ipu->dev, ++ buf->len, buf->virt, buf->phys); ++ buf->virt = NULL; ++ buf->phys = 0; ++} ++ ++static int alloc_dma_buf(struct ipu_image_convert_priv *priv, ++ struct ipu_image_convert_dma_buf *buf, ++ int size) ++{ ++ buf->len = PAGE_ALIGN(size); ++ buf->virt = dma_alloc_coherent(priv->ipu->dev, buf->len, &buf->phys, ++ GFP_DMA | GFP_KERNEL); ++ if (!buf->virt) { ++ dev_err(priv->ipu->dev, "failed to alloc dma buffer\n"); ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static inline int num_stripes(int dim) ++{ ++ return (dim - 1) / 1024 + 1; ++} ++ ++/* ++ * Calculate downsizing coefficients, which are the same for all tiles, ++ * and initial bilinear resizing coefficients, which are used to find the ++ * best seam positions. ++ * Also determine the number of tiles necessary to guarantee that no tile ++ * is larger than 1024 pixels in either dimension at the output and between ++ * IC downsizing and main processing sections. ++ */ ++static int calc_image_resize_coefficients(struct ipu_image_convert_ctx *ctx, ++ struct ipu_image *in, ++ struct ipu_image *out) ++{ ++ u32 downsized_width = in->rect.width; ++ u32 downsized_height = in->rect.height; ++ u32 downsize_coeff_v = 0; ++ u32 downsize_coeff_h = 0; ++ u32 resized_width = out->rect.width; ++ u32 resized_height = out->rect.height; ++ u32 resize_coeff_h; ++ u32 resize_coeff_v; ++ u32 cols; ++ u32 rows; ++ ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) { ++ resized_width = out->rect.height; ++ resized_height = out->rect.width; ++ } ++ ++ /* Do not let invalid input lead to an endless loop below */ ++ if (WARN_ON(resized_width == 0 || resized_height == 0)) ++ return -EINVAL; ++ ++ while (downsized_width >= resized_width * 2) { ++ downsized_width >>= 1; ++ downsize_coeff_h++; ++ } ++ ++ while (downsized_height >= resized_height * 2) { ++ downsized_height >>= 1; ++ downsize_coeff_v++; ++ } ++ ++ /* ++ * Calculate the bilinear resizing coefficients that could be used if ++ * we were converting with a single tile. The bottom right output pixel ++ * should sample as close as possible to the bottom right input pixel ++ * out of the decimator, but not overshoot it: ++ */ ++ resize_coeff_h = 8192 * (downsized_width - 1) / (resized_width - 1); ++ resize_coeff_v = 8192 * (downsized_height - 1) / (resized_height - 1); ++ ++ /* ++ * Both the output of the IC downsizing section before being passed to ++ * the IC main processing section and the final output of the IC main ++ * processing section must be <= 1024 pixels in both dimensions. ++ */ ++ cols = num_stripes(max_t(u32, downsized_width, resized_width)); ++ rows = num_stripes(max_t(u32, downsized_height, resized_height)); ++ ++ dev_dbg(ctx->chan->priv->ipu->dev, ++ "%s: hscale: >>%u, *8192/%u vscale: >>%u, *8192/%u, %ux%u tiles\n", ++ __func__, downsize_coeff_h, resize_coeff_h, downsize_coeff_v, ++ resize_coeff_v, cols, rows); ++ ++ if (downsize_coeff_h > 2 || downsize_coeff_v > 2 || ++ resize_coeff_h > 0x3fff || resize_coeff_v > 0x3fff) ++ return -EINVAL; ++ ++ ctx->downsize_coeff_h = downsize_coeff_h; ++ ctx->downsize_coeff_v = downsize_coeff_v; ++ ctx->image_resize_coeff_h = resize_coeff_h; ++ ctx->image_resize_coeff_v = resize_coeff_v; ++ ctx->in.num_cols = cols; ++ ctx->in.num_rows = rows; ++ ++ return 0; ++} ++ ++#define round_closest(x, y) round_down((x) + (y)/2, (y)) ++ ++/* ++ * Find the best aligned seam position for the given column / row index. ++ * Rotation and image offsets are out of scope. ++ * ++ * @index: column / row index, used to calculate valid interval ++ * @in_edge: input right / bottom edge ++ * @out_edge: output right / bottom edge ++ * @in_align: input alignment, either horizontal 8-byte line start address ++ * alignment, or pixel alignment due to image format ++ * @out_align: output alignment, either horizontal 8-byte line start address ++ * alignment, or pixel alignment due to image format or rotator ++ * block size ++ * @in_burst: horizontal input burst size in case of horizontal flip ++ * @out_burst: horizontal output burst size or rotator block size ++ * @downsize_coeff: downsizing section coefficient ++ * @resize_coeff: main processing section resizing coefficient ++ * @_in_seam: aligned input seam position return value ++ * @_out_seam: aligned output seam position return value ++ */ ++static void find_best_seam(struct ipu_image_convert_ctx *ctx, ++ unsigned int index, ++ unsigned int in_edge, ++ unsigned int out_edge, ++ unsigned int in_align, ++ unsigned int out_align, ++ unsigned int in_burst, ++ unsigned int out_burst, ++ unsigned int downsize_coeff, ++ unsigned int resize_coeff, ++ u32 *_in_seam, ++ u32 *_out_seam) ++{ ++ struct device *dev = ctx->chan->priv->ipu->dev; ++ unsigned int out_pos; ++ /* Input / output seam position candidates */ ++ unsigned int out_seam = 0; ++ unsigned int in_seam = 0; ++ unsigned int min_diff = UINT_MAX; ++ unsigned int out_start; ++ unsigned int out_end; ++ unsigned int in_start; ++ unsigned int in_end; ++ ++ /* Start within 1024 pixels of the right / bottom edge */ ++ out_start = max_t(int, index * out_align, out_edge - 1024); ++ /* End before having to add more columns to the left / rows above */ ++ out_end = min_t(unsigned int, out_edge, index * 1024 + 1); ++ ++ /* ++ * Limit input seam position to make sure that the downsized input tile ++ * to the right or bottom does not exceed 1024 pixels. ++ */ ++ in_start = max_t(int, index * in_align, ++ in_edge - (1024 << downsize_coeff)); ++ in_end = min_t(unsigned int, in_edge, ++ index * (1024 << downsize_coeff) + 1); ++ ++ /* ++ * Output tiles must start at a multiple of 8 bytes horizontally and ++ * possibly at an even line horizontally depending on the pixel format. ++ * Only consider output aligned positions for the seam. ++ */ ++ out_start = round_up(out_start, out_align); ++ for (out_pos = out_start; out_pos < out_end; out_pos += out_align) { ++ unsigned int in_pos; ++ unsigned int in_pos_aligned; ++ unsigned int in_pos_rounded; ++ unsigned int abs_diff; ++ ++ /* ++ * Tiles in the right row / bottom column may not be allowed to ++ * overshoot horizontally / vertically. out_burst may be the ++ * actual DMA burst size, or the rotator block size. ++ */ ++ if ((out_burst > 1) && (out_edge - out_pos) % out_burst) ++ continue; ++ ++ /* ++ * Input sample position, corresponding to out_pos, 19.13 fixed ++ * point. ++ */ ++ in_pos = (out_pos * resize_coeff) << downsize_coeff; ++ /* ++ * The closest input sample position that we could actually ++ * start the input tile at, 19.13 fixed point. ++ */ ++ in_pos_aligned = round_closest(in_pos, 8192U * in_align); ++ /* Convert 19.13 fixed point to integer */ ++ in_pos_rounded = in_pos_aligned / 8192U; ++ ++ if (in_pos_rounded < in_start) ++ continue; ++ if (in_pos_rounded >= in_end) ++ break; ++ ++ if ((in_burst > 1) && ++ (in_edge - in_pos_rounded) % in_burst) ++ continue; ++ ++ if (in_pos < in_pos_aligned) ++ abs_diff = in_pos_aligned - in_pos; ++ else ++ abs_diff = in_pos - in_pos_aligned; ++ ++ if (abs_diff < min_diff) { ++ in_seam = in_pos_rounded; ++ out_seam = out_pos; ++ min_diff = abs_diff; ++ } ++ } ++ ++ *_out_seam = out_seam; ++ *_in_seam = in_seam; ++ ++ dev_dbg(dev, "%s: out_seam %u(%u) in [%u, %u], in_seam %u(%u) in [%u, %u] diff %u.%03u\n", ++ __func__, out_seam, out_align, out_start, out_end, ++ in_seam, in_align, in_start, in_end, min_diff / 8192, ++ DIV_ROUND_CLOSEST(min_diff % 8192 * 1000, 8192)); ++} ++ ++/* ++ * Tile left edges are required to be aligned to multiples of 8 bytes ++ * by the IDMAC. ++ */ ++static inline u32 tile_left_align(const struct ipu_image_pixfmt *fmt) ++{ ++ if (fmt->planar) ++ return fmt->uv_packed ? 8 : 8 * fmt->uv_width_dec; ++ else ++ return fmt->bpp == 32 ? 2 : fmt->bpp == 16 ? 4 : 8; ++} ++ ++/* ++ * Tile top edge alignment is only limited by chroma subsampling. ++ */ ++static inline u32 tile_top_align(const struct ipu_image_pixfmt *fmt) ++{ ++ return fmt->uv_height_dec > 1 ? 2 : 1; ++} ++ ++static inline u32 tile_width_align(enum ipu_image_convert_type type, ++ const struct ipu_image_pixfmt *fmt, ++ enum ipu_rotate_mode rot_mode) ++{ ++ if (type == IMAGE_CONVERT_IN) { ++ /* ++ * The IC burst reads 8 pixels at a time. Reading beyond the ++ * end of the line is usually acceptable. Those pixels are ++ * ignored, unless the IC has to write the scaled line in ++ * reverse. ++ */ ++ return (!ipu_rot_mode_is_irt(rot_mode) && ++ (rot_mode & IPU_ROT_BIT_HFLIP)) ? 8 : 2; ++ } ++ ++ /* ++ * Align to 16x16 pixel blocks for planar 4:2:0 chroma subsampled ++ * formats to guarantee 8-byte aligned line start addresses in the ++ * chroma planes when IRT is used. Align to 8x8 pixel IRT block size ++ * for all other formats. ++ */ ++ return (ipu_rot_mode_is_irt(rot_mode) && ++ fmt->planar && !fmt->uv_packed) ? ++ 8 * fmt->uv_width_dec : 8; ++} ++ ++static inline u32 tile_height_align(enum ipu_image_convert_type type, ++ const struct ipu_image_pixfmt *fmt, ++ enum ipu_rotate_mode rot_mode) ++{ ++ if (type == IMAGE_CONVERT_IN || !ipu_rot_mode_is_irt(rot_mode)) ++ return 2; ++ ++ /* ++ * Align to 16x16 pixel blocks for planar 4:2:0 chroma subsampled ++ * formats to guarantee 8-byte aligned line start addresses in the ++ * chroma planes when IRT is used. Align to 8x8 pixel IRT block size ++ * for all other formats. ++ */ ++ return (fmt->planar && !fmt->uv_packed) ? 8 * fmt->uv_width_dec : 8; ++} ++ ++/* ++ * Fill in left position and width and for all tiles in an input column, and ++ * for all corresponding output tiles. If the 90° rotator is used, the output ++ * tiles are in a row, and output tile top position and height are set. ++ */ ++static void fill_tile_column(struct ipu_image_convert_ctx *ctx, ++ unsigned int col, ++ struct ipu_image_convert_image *in, ++ unsigned int in_left, unsigned int in_width, ++ struct ipu_image_convert_image *out, ++ unsigned int out_left, unsigned int out_width) ++{ ++ unsigned int row, tile_idx; ++ struct ipu_image_tile *in_tile, *out_tile; ++ ++ for (row = 0; row < in->num_rows; row++) { ++ tile_idx = in->num_cols * row + col; ++ in_tile = &in->tile[tile_idx]; ++ out_tile = &out->tile[ctx->out_tile_map[tile_idx]]; ++ ++ in_tile->left = in_left; ++ in_tile->width = in_width; ++ ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) { ++ out_tile->top = out_left; ++ out_tile->height = out_width; ++ } else { ++ out_tile->left = out_left; ++ out_tile->width = out_width; ++ } ++ } ++} ++ ++/* ++ * Fill in top position and height and for all tiles in an input row, and ++ * for all corresponding output tiles. If the 90° rotator is used, the output ++ * tiles are in a column, and output tile left position and width are set. ++ */ ++static void fill_tile_row(struct ipu_image_convert_ctx *ctx, unsigned int row, ++ struct ipu_image_convert_image *in, ++ unsigned int in_top, unsigned int in_height, ++ struct ipu_image_convert_image *out, ++ unsigned int out_top, unsigned int out_height) ++{ ++ unsigned int col, tile_idx; ++ struct ipu_image_tile *in_tile, *out_tile; ++ ++ for (col = 0; col < in->num_cols; col++) { ++ tile_idx = in->num_cols * row + col; ++ in_tile = &in->tile[tile_idx]; ++ out_tile = &out->tile[ctx->out_tile_map[tile_idx]]; ++ ++ in_tile->top = in_top; ++ in_tile->height = in_height; ++ ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) { ++ out_tile->left = out_top; ++ out_tile->width = out_height; ++ } else { ++ out_tile->top = out_top; ++ out_tile->height = out_height; ++ } ++ } ++} ++ ++/* ++ * Find the best horizontal and vertical seam positions to split into tiles. ++ * Minimize the fractional part of the input sampling position for the ++ * top / left pixels of each tile. ++ */ ++static void find_seams(struct ipu_image_convert_ctx *ctx, ++ struct ipu_image_convert_image *in, ++ struct ipu_image_convert_image *out) ++{ ++ struct device *dev = ctx->chan->priv->ipu->dev; ++ unsigned int resized_width = out->base.rect.width; ++ unsigned int resized_height = out->base.rect.height; ++ unsigned int col; ++ unsigned int row; ++ unsigned int in_left_align = tile_left_align(in->fmt); ++ unsigned int in_top_align = tile_top_align(in->fmt); ++ unsigned int out_left_align = tile_left_align(out->fmt); ++ unsigned int out_top_align = tile_top_align(out->fmt); ++ unsigned int out_width_align = tile_width_align(out->type, out->fmt, ++ ctx->rot_mode); ++ unsigned int out_height_align = tile_height_align(out->type, out->fmt, ++ ctx->rot_mode); ++ unsigned int in_right = in->base.rect.width; ++ unsigned int in_bottom = in->base.rect.height; ++ unsigned int out_right = out->base.rect.width; ++ unsigned int out_bottom = out->base.rect.height; ++ unsigned int flipped_out_left; ++ unsigned int flipped_out_top; ++ ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) { ++ /* Switch width/height and align top left to IRT block size */ ++ resized_width = out->base.rect.height; ++ resized_height = out->base.rect.width; ++ out_left_align = out_height_align; ++ out_top_align = out_width_align; ++ out_width_align = out_left_align; ++ out_height_align = out_top_align; ++ out_right = out->base.rect.height; ++ out_bottom = out->base.rect.width; ++ } ++ ++ for (col = in->num_cols - 1; col > 0; col--) { ++ bool allow_in_overshoot = ipu_rot_mode_is_irt(ctx->rot_mode) || ++ !(ctx->rot_mode & IPU_ROT_BIT_HFLIP); ++ bool allow_out_overshoot = (col < in->num_cols - 1) && ++ !(ctx->rot_mode & IPU_ROT_BIT_HFLIP); ++ unsigned int in_left; ++ unsigned int out_left; ++ ++ /* ++ * Align input width to burst length if the scaling step flips ++ * horizontally. ++ */ ++ ++ find_best_seam(ctx, col, ++ in_right, out_right, ++ in_left_align, out_left_align, ++ allow_in_overshoot ? 1 : 8 /* burst length */, ++ allow_out_overshoot ? 1 : out_width_align, ++ ctx->downsize_coeff_h, ctx->image_resize_coeff_h, ++ &in_left, &out_left); ++ ++ if (ctx->rot_mode & IPU_ROT_BIT_HFLIP) ++ flipped_out_left = resized_width - out_right; ++ else ++ flipped_out_left = out_left; ++ ++ fill_tile_column(ctx, col, in, in_left, in_right - in_left, ++ out, flipped_out_left, out_right - out_left); ++ ++ dev_dbg(dev, "%s: col %u: %u, %u -> %u, %u\n", __func__, col, ++ in_left, in_right - in_left, ++ flipped_out_left, out_right - out_left); ++ ++ in_right = in_left; ++ out_right = out_left; ++ } ++ ++ flipped_out_left = (ctx->rot_mode & IPU_ROT_BIT_HFLIP) ? ++ resized_width - out_right : 0; ++ ++ fill_tile_column(ctx, 0, in, 0, in_right, ++ out, flipped_out_left, out_right); ++ ++ dev_dbg(dev, "%s: col 0: 0, %u -> %u, %u\n", __func__, ++ in_right, flipped_out_left, out_right); ++ ++ for (row = in->num_rows - 1; row > 0; row--) { ++ bool allow_overshoot = row < in->num_rows - 1; ++ unsigned int in_top; ++ unsigned int out_top; ++ ++ find_best_seam(ctx, row, ++ in_bottom, out_bottom, ++ in_top_align, out_top_align, ++ 1, allow_overshoot ? 1 : out_height_align, ++ ctx->downsize_coeff_v, ctx->image_resize_coeff_v, ++ &in_top, &out_top); ++ ++ if ((ctx->rot_mode & IPU_ROT_BIT_VFLIP) ^ ++ ipu_rot_mode_is_irt(ctx->rot_mode)) ++ flipped_out_top = resized_height - out_bottom; ++ else ++ flipped_out_top = out_top; ++ ++ fill_tile_row(ctx, row, in, in_top, in_bottom - in_top, ++ out, flipped_out_top, out_bottom - out_top); ++ ++ dev_dbg(dev, "%s: row %u: %u, %u -> %u, %u\n", __func__, row, ++ in_top, in_bottom - in_top, ++ flipped_out_top, out_bottom - out_top); ++ ++ in_bottom = in_top; ++ out_bottom = out_top; ++ } ++ ++ if ((ctx->rot_mode & IPU_ROT_BIT_VFLIP) ^ ++ ipu_rot_mode_is_irt(ctx->rot_mode)) ++ flipped_out_top = resized_height - out_bottom; ++ else ++ flipped_out_top = 0; ++ ++ fill_tile_row(ctx, 0, in, 0, in_bottom, ++ out, flipped_out_top, out_bottom); ++ ++ dev_dbg(dev, "%s: row 0: 0, %u -> %u, %u\n", __func__, ++ in_bottom, flipped_out_top, out_bottom); ++} ++ ++static int calc_tile_dimensions(struct ipu_image_convert_ctx *ctx, ++ struct ipu_image_convert_image *image) ++{ ++ struct ipu_image_convert_chan *chan = ctx->chan; ++ struct ipu_image_convert_priv *priv = chan->priv; ++ unsigned int max_width = 1024; ++ unsigned int max_height = 1024; ++ unsigned int i; ++ ++ if (image->type == IMAGE_CONVERT_IN) { ++ /* Up to 4096x4096 input tile size */ ++ max_width <<= ctx->downsize_coeff_h; ++ max_height <<= ctx->downsize_coeff_v; ++ } ++ ++ for (i = 0; i < ctx->num_tiles; i++) { ++ struct ipu_image_tile *tile; ++ const unsigned int row = i / image->num_cols; ++ const unsigned int col = i % image->num_cols; ++ ++ if (image->type == IMAGE_CONVERT_OUT) ++ tile = &image->tile[ctx->out_tile_map[i]]; ++ else ++ tile = &image->tile[i]; ++ ++ tile->size = ((tile->height * image->fmt->bpp) >> 3) * ++ tile->width; ++ ++ if (image->fmt->planar) { ++ tile->stride = tile->width; ++ tile->rot_stride = tile->height; ++ } else { ++ tile->stride = ++ (image->fmt->bpp * tile->width) >> 3; ++ tile->rot_stride = ++ (image->fmt->bpp * tile->height) >> 3; ++ } ++ ++ dev_dbg(priv->ipu->dev, ++ "task %u: ctx %p: %s@[%u,%u]: %ux%u@%u,%u\n", ++ chan->ic_task, ctx, ++ image->type == IMAGE_CONVERT_IN ? "Input" : "Output", ++ row, col, ++ tile->width, tile->height, tile->left, tile->top); ++ ++ if (!tile->width || tile->width > max_width || ++ !tile->height || tile->height > max_height) { ++ dev_err(priv->ipu->dev, "invalid %s tile size: %ux%u\n", ++ image->type == IMAGE_CONVERT_IN ? "input" : ++ "output", tile->width, tile->height); ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++} ++ ++/* ++ * Use the rotation transformation to find the tile coordinates ++ * (row, col) of a tile in the destination frame that corresponds ++ * to the given tile coordinates of a source frame. The destination ++ * coordinate is then converted to a tile index. ++ */ ++static int transform_tile_index(struct ipu_image_convert_ctx *ctx, ++ int src_row, int src_col) ++{ ++ struct ipu_image_convert_chan *chan = ctx->chan; ++ struct ipu_image_convert_priv *priv = chan->priv; ++ struct ipu_image_convert_image *s_image = &ctx->in; ++ struct ipu_image_convert_image *d_image = &ctx->out; ++ int dst_row, dst_col; ++ ++ /* with no rotation it's a 1:1 mapping */ ++ if (ctx->rot_mode == IPU_ROTATE_NONE) ++ return src_row * s_image->num_cols + src_col; ++ ++ /* ++ * before doing the transform, first we have to translate ++ * source row,col for an origin in the center of s_image ++ */ ++ src_row = src_row * 2 - (s_image->num_rows - 1); ++ src_col = src_col * 2 - (s_image->num_cols - 1); ++ ++ /* do the rotation transform */ ++ if (ctx->rot_mode & IPU_ROT_BIT_90) { ++ dst_col = -src_row; ++ dst_row = src_col; ++ } else { ++ dst_col = src_col; ++ dst_row = src_row; ++ } ++ ++ /* apply flip */ ++ if (ctx->rot_mode & IPU_ROT_BIT_HFLIP) ++ dst_col = -dst_col; ++ if (ctx->rot_mode & IPU_ROT_BIT_VFLIP) ++ dst_row = -dst_row; ++ ++ dev_dbg(priv->ipu->dev, "task %u: ctx %p: [%d,%d] --> [%d,%d]\n", ++ chan->ic_task, ctx, src_col, src_row, dst_col, dst_row); ++ ++ /* ++ * finally translate dest row,col using an origin in upper ++ * left of d_image ++ */ ++ dst_row += d_image->num_rows - 1; ++ dst_col += d_image->num_cols - 1; ++ dst_row /= 2; ++ dst_col /= 2; ++ ++ return dst_row * d_image->num_cols + dst_col; ++} ++ ++/* ++ * Fill the out_tile_map[] with transformed destination tile indeces. ++ */ ++static void calc_out_tile_map(struct ipu_image_convert_ctx *ctx) ++{ ++ struct ipu_image_convert_image *s_image = &ctx->in; ++ unsigned int row, col, tile = 0; ++ ++ for (row = 0; row < s_image->num_rows; row++) { ++ for (col = 0; col < s_image->num_cols; col++) { ++ ctx->out_tile_map[tile] = ++ transform_tile_index(ctx, row, col); ++ tile++; ++ } ++ } ++} ++ ++static int calc_tile_offsets_planar(struct ipu_image_convert_ctx *ctx, ++ struct ipu_image_convert_image *image) ++{ ++ struct ipu_image_convert_chan *chan = ctx->chan; ++ struct ipu_image_convert_priv *priv = chan->priv; ++ const struct ipu_image_pixfmt *fmt = image->fmt; ++ unsigned int row, col, tile = 0; ++ u32 H, top, y_stride, uv_stride; ++ u32 uv_row_off, uv_col_off, uv_off, u_off, v_off, tmp; ++ u32 y_row_off, y_col_off, y_off; ++ u32 y_size, uv_size; ++ ++ /* setup some convenience vars */ ++ H = image->base.pix.height; ++ ++ y_stride = image->stride; ++ uv_stride = y_stride / fmt->uv_width_dec; ++ if (fmt->uv_packed) ++ uv_stride *= 2; ++ ++ y_size = H * y_stride; ++ uv_size = y_size / (fmt->uv_width_dec * fmt->uv_height_dec); ++ ++ for (row = 0; row < image->num_rows; row++) { ++ top = image->tile[tile].top; ++ y_row_off = top * y_stride; ++ uv_row_off = (top * uv_stride) / fmt->uv_height_dec; ++ ++ for (col = 0; col < image->num_cols; col++) { ++ y_col_off = image->tile[tile].left; ++ uv_col_off = y_col_off / fmt->uv_width_dec; ++ if (fmt->uv_packed) ++ uv_col_off *= 2; ++ ++ y_off = y_row_off + y_col_off; ++ uv_off = uv_row_off + uv_col_off; ++ ++ u_off = y_size - y_off + uv_off; ++ v_off = (fmt->uv_packed) ? 0 : u_off + uv_size; ++ if (fmt->uv_swapped) { ++ tmp = u_off; ++ u_off = v_off; ++ v_off = tmp; ++ } ++ ++ image->tile[tile].offset = y_off; ++ image->tile[tile].u_off = u_off; ++ image->tile[tile++].v_off = v_off; ++ ++ if ((y_off & 0x7) || (u_off & 0x7) || (v_off & 0x7)) { ++ dev_err(priv->ipu->dev, ++ "task %u: ctx %p: %s@[%d,%d]: " ++ "y_off %08x, u_off %08x, v_off %08x\n", ++ chan->ic_task, ctx, ++ image->type == IMAGE_CONVERT_IN ? ++ "Input" : "Output", row, col, ++ y_off, u_off, v_off); ++ return -EINVAL; ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++static int calc_tile_offsets_packed(struct ipu_image_convert_ctx *ctx, ++ struct ipu_image_convert_image *image) ++{ ++ struct ipu_image_convert_chan *chan = ctx->chan; ++ struct ipu_image_convert_priv *priv = chan->priv; ++ const struct ipu_image_pixfmt *fmt = image->fmt; ++ unsigned int row, col, tile = 0; ++ u32 bpp, stride, offset; ++ u32 row_off, col_off; ++ ++ /* setup some convenience vars */ ++ stride = image->stride; ++ bpp = fmt->bpp; ++ ++ for (row = 0; row < image->num_rows; row++) { ++ row_off = image->tile[tile].top * stride; ++ ++ for (col = 0; col < image->num_cols; col++) { ++ col_off = (image->tile[tile].left * bpp) >> 3; ++ ++ offset = row_off + col_off; ++ ++ image->tile[tile].offset = offset; ++ image->tile[tile].u_off = 0; ++ image->tile[tile++].v_off = 0; ++ ++ if (offset & 0x7) { ++ dev_err(priv->ipu->dev, ++ "task %u: ctx %p: %s@[%d,%d]: " ++ "phys %08x\n", ++ chan->ic_task, ctx, ++ image->type == IMAGE_CONVERT_IN ? ++ "Input" : "Output", row, col, ++ row_off + col_off); ++ return -EINVAL; ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++static int calc_tile_offsets(struct ipu_image_convert_ctx *ctx, ++ struct ipu_image_convert_image *image) ++{ ++ if (image->fmt->planar) ++ return calc_tile_offsets_planar(ctx, image); ++ ++ return calc_tile_offsets_packed(ctx, image); ++} ++ ++/* ++ * Calculate the resizing ratio for the IC main processing section given input ++ * size, fixed downsizing coefficient, and output size. ++ * Either round to closest for the next tile's first pixel to minimize seams ++ * and distortion (for all but right column / bottom row), or round down to ++ * avoid sampling beyond the edges of the input image for this tile's last ++ * pixel. ++ * Returns the resizing coefficient, resizing ratio is 8192.0 / resize_coeff. ++ */ ++static u32 calc_resize_coeff(u32 input_size, u32 downsize_coeff, ++ u32 output_size, bool allow_overshoot) ++{ ++ u32 downsized = input_size >> downsize_coeff; ++ ++ if (allow_overshoot) ++ return DIV_ROUND_CLOSEST(8192 * downsized, output_size); ++ else ++ return 8192 * (downsized - 1) / (output_size - 1); ++} ++ ++/* ++ * Slightly modify resize coefficients per tile to hide the bilinear ++ * interpolator reset at tile borders, shifting the right / bottom edge ++ * by up to a half input pixel. This removes noticeable seams between ++ * tiles at higher upscaling factors. ++ */ ++static void calc_tile_resize_coefficients(struct ipu_image_convert_ctx *ctx) ++{ ++ struct ipu_image_convert_chan *chan = ctx->chan; ++ struct ipu_image_convert_priv *priv = chan->priv; ++ struct ipu_image_tile *in_tile, *out_tile; ++ unsigned int col, row, tile_idx; ++ unsigned int last_output; ++ ++ for (col = 0; col < ctx->in.num_cols; col++) { ++ bool closest = (col < ctx->in.num_cols - 1) && ++ !(ctx->rot_mode & IPU_ROT_BIT_HFLIP); ++ u32 resized_width; ++ u32 resize_coeff_h; ++ u32 in_width; ++ ++ tile_idx = col; ++ in_tile = &ctx->in.tile[tile_idx]; ++ out_tile = &ctx->out.tile[ctx->out_tile_map[tile_idx]]; ++ ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) ++ resized_width = out_tile->height; ++ else ++ resized_width = out_tile->width; ++ ++ resize_coeff_h = calc_resize_coeff(in_tile->width, ++ ctx->downsize_coeff_h, ++ resized_width, closest); ++ ++ dev_dbg(priv->ipu->dev, "%s: column %u hscale: *8192/%u\n", ++ __func__, col, resize_coeff_h); ++ ++ /* ++ * With the horizontal scaling factor known, round up resized ++ * width (output width or height) to burst size. ++ */ ++ resized_width = round_up(resized_width, 8); ++ ++ /* ++ * Calculate input width from the last accessed input pixel ++ * given resized width and scaling coefficients. Round up to ++ * burst size. ++ */ ++ last_output = resized_width - 1; ++ if (closest && ((last_output * resize_coeff_h) % 8192)) ++ last_output++; ++ in_width = round_up( ++ (DIV_ROUND_UP(last_output * resize_coeff_h, 8192) + 1) ++ << ctx->downsize_coeff_h, 8); ++ ++ for (row = 0; row < ctx->in.num_rows; row++) { ++ tile_idx = row * ctx->in.num_cols + col; ++ in_tile = &ctx->in.tile[tile_idx]; ++ out_tile = &ctx->out.tile[ctx->out_tile_map[tile_idx]]; ++ ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) ++ out_tile->height = resized_width; ++ else ++ out_tile->width = resized_width; ++ ++ in_tile->width = in_width; ++ } ++ ++ ctx->resize_coeffs_h[col] = resize_coeff_h; ++ } ++ ++ for (row = 0; row < ctx->in.num_rows; row++) { ++ bool closest = (row < ctx->in.num_rows - 1) && ++ !(ctx->rot_mode & IPU_ROT_BIT_VFLIP); ++ u32 resized_height; ++ u32 resize_coeff_v; ++ u32 in_height; ++ ++ tile_idx = row * ctx->in.num_cols; ++ in_tile = &ctx->in.tile[tile_idx]; ++ out_tile = &ctx->out.tile[ctx->out_tile_map[tile_idx]]; ++ ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) ++ resized_height = out_tile->width; ++ else ++ resized_height = out_tile->height; ++ ++ resize_coeff_v = calc_resize_coeff(in_tile->height, ++ ctx->downsize_coeff_v, ++ resized_height, closest); ++ ++ dev_dbg(priv->ipu->dev, "%s: row %u vscale: *8192/%u\n", ++ __func__, row, resize_coeff_v); ++ ++ /* ++ * With the vertical scaling factor known, round up resized ++ * height (output width or height) to IDMAC limitations. ++ */ ++ resized_height = round_up(resized_height, 2); ++ ++ /* ++ * Calculate input width from the last accessed input pixel ++ * given resized height and scaling coefficients. Align to ++ * IDMAC restrictions. ++ */ ++ last_output = resized_height - 1; ++ if (closest && ((last_output * resize_coeff_v) % 8192)) ++ last_output++; ++ in_height = round_up( ++ (DIV_ROUND_UP(last_output * resize_coeff_v, 8192) + 1) ++ << ctx->downsize_coeff_v, 2); ++ ++ for (col = 0; col < ctx->in.num_cols; col++) { ++ tile_idx = row * ctx->in.num_cols + col; ++ in_tile = &ctx->in.tile[tile_idx]; ++ out_tile = &ctx->out.tile[ctx->out_tile_map[tile_idx]]; ++ ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) ++ out_tile->width = resized_height; ++ else ++ out_tile->height = resized_height; ++ ++ in_tile->height = in_height; ++ } ++ ++ ctx->resize_coeffs_v[row] = resize_coeff_v; ++ } ++} ++ ++/* ++ * return the number of runs in given queue (pending_q or done_q) ++ * for this context. hold irqlock when calling. ++ */ ++static int get_run_count(struct ipu_image_convert_ctx *ctx, ++ struct list_head *q) ++{ ++ struct ipu_image_convert_run *run; ++ int count = 0; ++ ++ lockdep_assert_held(&ctx->chan->irqlock); ++ ++ list_for_each_entry(run, q, list) { ++ if (run->ctx == ctx) ++ count++; ++ } ++ ++ return count; ++} ++ ++static void convert_stop(struct ipu_image_convert_run *run) ++{ ++ struct ipu_image_convert_ctx *ctx = run->ctx; ++ struct ipu_image_convert_chan *chan = ctx->chan; ++ struct ipu_image_convert_priv *priv = chan->priv; ++ ++ dev_dbg(priv->ipu->dev, "%s: task %u: stopping ctx %p run %p\n", ++ __func__, chan->ic_task, ctx, run); ++ ++ /* disable IC tasks and the channels */ ++ ipu_ic_task_disable(chan->ic); ++ ipu_idmac_disable_channel(chan->in_chan); ++ ipu_idmac_disable_channel(chan->out_chan); ++ ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) { ++ ipu_idmac_disable_channel(chan->rotation_in_chan); ++ ipu_idmac_disable_channel(chan->rotation_out_chan); ++ ipu_idmac_unlink(chan->out_chan, chan->rotation_in_chan); ++ } ++ ++ ipu_ic_disable(chan->ic); ++} ++ ++static void init_idmac_channel(struct ipu_image_convert_ctx *ctx, ++ struct ipuv3_channel *channel, ++ struct ipu_image_convert_image *image, ++ enum ipu_rotate_mode rot_mode, ++ bool rot_swap_width_height, ++ unsigned int tile) ++{ ++ struct ipu_image_convert_chan *chan = ctx->chan; ++ unsigned int burst_size; ++ u32 width, height, stride; ++ dma_addr_t addr0, addr1 = 0; ++ struct ipu_image tile_image; ++ unsigned int tile_idx[2]; ++ ++ if (image->type == IMAGE_CONVERT_OUT) { ++ tile_idx[0] = ctx->out_tile_map[tile]; ++ tile_idx[1] = ctx->out_tile_map[1]; ++ } else { ++ tile_idx[0] = tile; ++ tile_idx[1] = 1; ++ } ++ ++ if (rot_swap_width_height) { ++ width = image->tile[tile_idx[0]].height; ++ height = image->tile[tile_idx[0]].width; ++ stride = image->tile[tile_idx[0]].rot_stride; ++ addr0 = ctx->rot_intermediate[0].phys; ++ if (ctx->double_buffering) ++ addr1 = ctx->rot_intermediate[1].phys; ++ } else { ++ width = image->tile[tile_idx[0]].width; ++ height = image->tile[tile_idx[0]].height; ++ stride = image->stride; ++ addr0 = image->base.phys0 + ++ image->tile[tile_idx[0]].offset; ++ if (ctx->double_buffering) ++ addr1 = image->base.phys0 + ++ image->tile[tile_idx[1]].offset; ++ } ++ ++ ipu_cpmem_zero(channel); ++ ++ memset(&tile_image, 0, sizeof(tile_image)); ++ tile_image.pix.width = tile_image.rect.width = width; ++ tile_image.pix.height = tile_image.rect.height = height; ++ tile_image.pix.bytesperline = stride; ++ tile_image.pix.pixelformat = image->fmt->fourcc; ++ tile_image.phys0 = addr0; ++ tile_image.phys1 = addr1; ++ if (image->fmt->planar && !rot_swap_width_height) { ++ tile_image.u_offset = image->tile[tile_idx[0]].u_off; ++ tile_image.v_offset = image->tile[tile_idx[0]].v_off; ++ } ++ ++ ipu_cpmem_set_image(channel, &tile_image); ++ ++ if (rot_mode) ++ ipu_cpmem_set_rotation(channel, rot_mode); ++ ++ /* ++ * Skip writing U and V components to odd rows in the output ++ * channels for planar 4:2:0. ++ */ ++ if ((channel == chan->out_chan || ++ channel == chan->rotation_out_chan) && ++ image->fmt->planar && image->fmt->uv_height_dec == 2) ++ ipu_cpmem_skip_odd_chroma_rows(channel); ++ ++ if (channel == chan->rotation_in_chan || ++ channel == chan->rotation_out_chan) { ++ burst_size = 8; ++ ipu_cpmem_set_block_mode(channel); ++ } else ++ burst_size = (width % 16) ? 8 : 16; ++ ++ ipu_cpmem_set_burstsize(channel, burst_size); ++ ++ ipu_ic_task_idma_init(chan->ic, channel, width, height, ++ burst_size, rot_mode); ++ ++ /* ++ * Setting a non-zero AXI ID collides with the PRG AXI snooping, so ++ * only do this when there is no PRG present. ++ */ ++ if (!channel->ipu->prg_priv) ++ ipu_cpmem_set_axi_id(channel, 1); ++ ++ ipu_idmac_set_double_buffer(channel, ctx->double_buffering); ++} ++ ++static int convert_start(struct ipu_image_convert_run *run, unsigned int tile) ++{ ++ struct ipu_image_convert_ctx *ctx = run->ctx; ++ struct ipu_image_convert_chan *chan = ctx->chan; ++ struct ipu_image_convert_priv *priv = chan->priv; ++ struct ipu_image_convert_image *s_image = &ctx->in; ++ struct ipu_image_convert_image *d_image = &ctx->out; ++ unsigned int dst_tile = ctx->out_tile_map[tile]; ++ unsigned int dest_width, dest_height; ++ unsigned int col, row; ++ u32 rsc; ++ int ret; ++ ++ dev_dbg(priv->ipu->dev, "%s: task %u: starting ctx %p run %p tile %u -> %u\n", ++ __func__, chan->ic_task, ctx, run, tile, dst_tile); ++ ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) { ++ /* swap width/height for resizer */ ++ dest_width = d_image->tile[dst_tile].height; ++ dest_height = d_image->tile[dst_tile].width; ++ } else { ++ dest_width = d_image->tile[dst_tile].width; ++ dest_height = d_image->tile[dst_tile].height; ++ } ++ ++ row = tile / s_image->num_cols; ++ col = tile % s_image->num_cols; ++ ++ rsc = (ctx->downsize_coeff_v << 30) | ++ (ctx->resize_coeffs_v[row] << 16) | ++ (ctx->downsize_coeff_h << 14) | ++ (ctx->resize_coeffs_h[col]); ++ ++ dev_dbg(priv->ipu->dev, "%s: %ux%u -> %ux%u (rsc = 0x%x)\n", ++ __func__, s_image->tile[tile].width, ++ s_image->tile[tile].height, dest_width, dest_height, rsc); ++ ++ /* setup the IC resizer and CSC */ ++ ret = ipu_ic_task_init_rsc(chan->ic, &ctx->csc, ++ s_image->tile[tile].width, ++ s_image->tile[tile].height, ++ dest_width, ++ dest_height, ++ rsc); ++ if (ret) { ++ dev_err(priv->ipu->dev, "ipu_ic_task_init failed, %d\n", ret); ++ return ret; ++ } ++ ++ /* init the source MEM-->IC PP IDMAC channel */ ++ init_idmac_channel(ctx, chan->in_chan, s_image, ++ IPU_ROTATE_NONE, false, tile); ++ ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) { ++ /* init the IC PP-->MEM IDMAC channel */ ++ init_idmac_channel(ctx, chan->out_chan, d_image, ++ IPU_ROTATE_NONE, true, tile); ++ ++ /* init the MEM-->IC PP ROT IDMAC channel */ ++ init_idmac_channel(ctx, chan->rotation_in_chan, d_image, ++ ctx->rot_mode, true, tile); ++ ++ /* init the destination IC PP ROT-->MEM IDMAC channel */ ++ init_idmac_channel(ctx, chan->rotation_out_chan, d_image, ++ IPU_ROTATE_NONE, false, tile); ++ ++ /* now link IC PP-->MEM to MEM-->IC PP ROT */ ++ ipu_idmac_link(chan->out_chan, chan->rotation_in_chan); ++ } else { ++ /* init the destination IC PP-->MEM IDMAC channel */ ++ init_idmac_channel(ctx, chan->out_chan, d_image, ++ ctx->rot_mode, false, tile); ++ } ++ ++ /* enable the IC */ ++ ipu_ic_enable(chan->ic); ++ ++ /* set buffers ready */ ++ ipu_idmac_select_buffer(chan->in_chan, 0); ++ ipu_idmac_select_buffer(chan->out_chan, 0); ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) ++ ipu_idmac_select_buffer(chan->rotation_out_chan, 0); ++ if (ctx->double_buffering) { ++ ipu_idmac_select_buffer(chan->in_chan, 1); ++ ipu_idmac_select_buffer(chan->out_chan, 1); ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) ++ ipu_idmac_select_buffer(chan->rotation_out_chan, 1); ++ } ++ ++ /* enable the channels! */ ++ ipu_idmac_enable_channel(chan->in_chan); ++ ipu_idmac_enable_channel(chan->out_chan); ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) { ++ ipu_idmac_enable_channel(chan->rotation_in_chan); ++ ipu_idmac_enable_channel(chan->rotation_out_chan); ++ } ++ ++ ipu_ic_task_enable(chan->ic); ++ ++ ipu_cpmem_dump(chan->in_chan); ++ ipu_cpmem_dump(chan->out_chan); ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) { ++ ipu_cpmem_dump(chan->rotation_in_chan); ++ ipu_cpmem_dump(chan->rotation_out_chan); ++ } ++ ++ ipu_dump(priv->ipu); ++ ++ return 0; ++} ++ ++/* hold irqlock when calling */ ++static int do_run(struct ipu_image_convert_run *run) ++{ ++ struct ipu_image_convert_ctx *ctx = run->ctx; ++ struct ipu_image_convert_chan *chan = ctx->chan; ++ ++ lockdep_assert_held(&chan->irqlock); ++ ++ ctx->in.base.phys0 = run->in_phys; ++ ctx->out.base.phys0 = run->out_phys; ++ ++ ctx->cur_buf_num = 0; ++ ctx->next_tile = 1; ++ ++ /* remove run from pending_q and set as current */ ++ list_del(&run->list); ++ chan->current_run = run; ++ ++ return convert_start(run, 0); ++} ++ ++/* hold irqlock when calling */ ++static void run_next(struct ipu_image_convert_chan *chan) ++{ ++ struct ipu_image_convert_priv *priv = chan->priv; ++ struct ipu_image_convert_run *run, *tmp; ++ int ret; ++ ++ lockdep_assert_held(&chan->irqlock); ++ ++ list_for_each_entry_safe(run, tmp, &chan->pending_q, list) { ++ /* skip contexts that are aborting */ ++ if (run->ctx->aborting) { ++ dev_dbg(priv->ipu->dev, ++ "%s: task %u: skipping aborting ctx %p run %p\n", ++ __func__, chan->ic_task, run->ctx, run); ++ continue; ++ } ++ ++ ret = do_run(run); ++ if (!ret) ++ break; ++ ++ /* ++ * something went wrong with start, add the run ++ * to done q and continue to the next run in the ++ * pending q. ++ */ ++ run->status = ret; ++ list_add_tail(&run->list, &chan->done_q); ++ chan->current_run = NULL; ++ } ++} ++ ++static void empty_done_q(struct ipu_image_convert_chan *chan) ++{ ++ struct ipu_image_convert_priv *priv = chan->priv; ++ struct ipu_image_convert_run *run; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&chan->irqlock, flags); ++ ++ while (!list_empty(&chan->done_q)) { ++ run = list_entry(chan->done_q.next, ++ struct ipu_image_convert_run, ++ list); ++ ++ list_del(&run->list); ++ ++ dev_dbg(priv->ipu->dev, ++ "%s: task %u: completing ctx %p run %p with %d\n", ++ __func__, chan->ic_task, run->ctx, run, run->status); ++ ++ /* call the completion callback and free the run */ ++ spin_unlock_irqrestore(&chan->irqlock, flags); ++ run->ctx->complete(run, run->ctx->complete_context); ++ spin_lock_irqsave(&chan->irqlock, flags); ++ } ++ ++ spin_unlock_irqrestore(&chan->irqlock, flags); ++} ++ ++/* ++ * the bottom half thread clears out the done_q, calling the ++ * completion handler for each. ++ */ ++static irqreturn_t do_bh(int irq, void *dev_id) ++{ ++ struct ipu_image_convert_chan *chan = dev_id; ++ struct ipu_image_convert_priv *priv = chan->priv; ++ struct ipu_image_convert_ctx *ctx; ++ unsigned long flags; ++ ++ dev_dbg(priv->ipu->dev, "%s: task %u: enter\n", __func__, ++ chan->ic_task); ++ ++ empty_done_q(chan); ++ ++ spin_lock_irqsave(&chan->irqlock, flags); ++ ++ /* ++ * the done_q is cleared out, signal any contexts ++ * that are aborting that abort can complete. ++ */ ++ list_for_each_entry(ctx, &chan->ctx_list, list) { ++ if (ctx->aborting) { ++ dev_dbg(priv->ipu->dev, ++ "%s: task %u: signaling abort for ctx %p\n", ++ __func__, chan->ic_task, ctx); ++ complete_all(&ctx->aborted); ++ } ++ } ++ ++ spin_unlock_irqrestore(&chan->irqlock, flags); ++ ++ dev_dbg(priv->ipu->dev, "%s: task %u: exit\n", __func__, ++ chan->ic_task); ++ ++ return IRQ_HANDLED; ++} ++ ++static bool ic_settings_changed(struct ipu_image_convert_ctx *ctx) ++{ ++ unsigned int cur_tile = ctx->next_tile - 1; ++ unsigned int next_tile = ctx->next_tile; ++ ++ if (ctx->resize_coeffs_h[cur_tile % ctx->in.num_cols] != ++ ctx->resize_coeffs_h[next_tile % ctx->in.num_cols] || ++ ctx->resize_coeffs_v[cur_tile / ctx->in.num_cols] != ++ ctx->resize_coeffs_v[next_tile / ctx->in.num_cols] || ++ ctx->in.tile[cur_tile].width != ctx->in.tile[next_tile].width || ++ ctx->in.tile[cur_tile].height != ctx->in.tile[next_tile].height || ++ ctx->out.tile[cur_tile].width != ctx->out.tile[next_tile].width || ++ ctx->out.tile[cur_tile].height != ctx->out.tile[next_tile].height) ++ return true; ++ ++ return false; ++} ++ ++/* hold irqlock when calling */ ++static irqreturn_t do_irq(struct ipu_image_convert_run *run) ++{ ++ struct ipu_image_convert_ctx *ctx = run->ctx; ++ struct ipu_image_convert_chan *chan = ctx->chan; ++ struct ipu_image_tile *src_tile, *dst_tile; ++ struct ipu_image_convert_image *s_image = &ctx->in; ++ struct ipu_image_convert_image *d_image = &ctx->out; ++ struct ipuv3_channel *outch; ++ unsigned int dst_idx; ++ ++ lockdep_assert_held(&chan->irqlock); ++ ++ outch = ipu_rot_mode_is_irt(ctx->rot_mode) ? ++ chan->rotation_out_chan : chan->out_chan; ++ ++ /* ++ * It is difficult to stop the channel DMA before the channels ++ * enter the paused state. Without double-buffering the channels ++ * are always in a paused state when the EOF irq occurs, so it ++ * is safe to stop the channels now. For double-buffering we ++ * just ignore the abort until the operation completes, when it ++ * is safe to shut down. ++ */ ++ if (ctx->aborting && !ctx->double_buffering) { ++ convert_stop(run); ++ run->status = -EIO; ++ goto done; ++ } ++ ++ if (ctx->next_tile == ctx->num_tiles) { ++ /* ++ * the conversion is complete ++ */ ++ convert_stop(run); ++ run->status = 0; ++ goto done; ++ } ++ ++ /* ++ * not done, place the next tile buffers. ++ */ ++ if (!ctx->double_buffering) { ++ if (ic_settings_changed(ctx)) { ++ convert_stop(run); ++ convert_start(run, ctx->next_tile); ++ } else { ++ src_tile = &s_image->tile[ctx->next_tile]; ++ dst_idx = ctx->out_tile_map[ctx->next_tile]; ++ dst_tile = &d_image->tile[dst_idx]; ++ ++ ipu_cpmem_set_buffer(chan->in_chan, 0, ++ s_image->base.phys0 + ++ src_tile->offset); ++ ipu_cpmem_set_buffer(outch, 0, ++ d_image->base.phys0 + ++ dst_tile->offset); ++ if (s_image->fmt->planar) ++ ipu_cpmem_set_uv_offset(chan->in_chan, ++ src_tile->u_off, ++ src_tile->v_off); ++ if (d_image->fmt->planar) ++ ipu_cpmem_set_uv_offset(outch, ++ dst_tile->u_off, ++ dst_tile->v_off); ++ ++ ipu_idmac_select_buffer(chan->in_chan, 0); ++ ipu_idmac_select_buffer(outch, 0); ++ } ++ } else if (ctx->next_tile < ctx->num_tiles - 1) { ++ ++ src_tile = &s_image->tile[ctx->next_tile + 1]; ++ dst_idx = ctx->out_tile_map[ctx->next_tile + 1]; ++ dst_tile = &d_image->tile[dst_idx]; ++ ++ ipu_cpmem_set_buffer(chan->in_chan, ctx->cur_buf_num, ++ s_image->base.phys0 + src_tile->offset); ++ ipu_cpmem_set_buffer(outch, ctx->cur_buf_num, ++ d_image->base.phys0 + dst_tile->offset); ++ ++ ipu_idmac_select_buffer(chan->in_chan, ctx->cur_buf_num); ++ ipu_idmac_select_buffer(outch, ctx->cur_buf_num); ++ ++ ctx->cur_buf_num ^= 1; ++ } ++ ++ ctx->next_tile++; ++ return IRQ_HANDLED; ++done: ++ list_add_tail(&run->list, &chan->done_q); ++ chan->current_run = NULL; ++ run_next(chan); ++ return IRQ_WAKE_THREAD; ++} ++ ++static irqreturn_t norotate_irq(int irq, void *data) ++{ ++ struct ipu_image_convert_chan *chan = data; ++ struct ipu_image_convert_ctx *ctx; ++ struct ipu_image_convert_run *run; ++ unsigned long flags; ++ irqreturn_t ret; ++ ++ spin_lock_irqsave(&chan->irqlock, flags); ++ ++ /* get current run and its context */ ++ run = chan->current_run; ++ if (!run) { ++ ret = IRQ_NONE; ++ goto out; ++ } ++ ++ ctx = run->ctx; ++ ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) { ++ /* this is a rotation operation, just ignore */ ++ spin_unlock_irqrestore(&chan->irqlock, flags); ++ return IRQ_HANDLED; ++ } ++ ++ ret = do_irq(run); ++out: ++ spin_unlock_irqrestore(&chan->irqlock, flags); ++ return ret; ++} ++ ++static irqreturn_t rotate_irq(int irq, void *data) ++{ ++ struct ipu_image_convert_chan *chan = data; ++ struct ipu_image_convert_priv *priv = chan->priv; ++ struct ipu_image_convert_ctx *ctx; ++ struct ipu_image_convert_run *run; ++ unsigned long flags; ++ irqreturn_t ret; ++ ++ spin_lock_irqsave(&chan->irqlock, flags); ++ ++ /* get current run and its context */ ++ run = chan->current_run; ++ if (!run) { ++ ret = IRQ_NONE; ++ goto out; ++ } ++ ++ ctx = run->ctx; ++ ++ if (!ipu_rot_mode_is_irt(ctx->rot_mode)) { ++ /* this was NOT a rotation operation, shouldn't happen */ ++ dev_err(priv->ipu->dev, "Unexpected rotation interrupt\n"); ++ spin_unlock_irqrestore(&chan->irqlock, flags); ++ return IRQ_HANDLED; ++ } ++ ++ ret = do_irq(run); ++out: ++ spin_unlock_irqrestore(&chan->irqlock, flags); ++ return ret; ++} ++ ++/* ++ * try to force the completion of runs for this ctx. Called when ++ * abort wait times out in ipu_image_convert_abort(). ++ */ ++static void force_abort(struct ipu_image_convert_ctx *ctx) ++{ ++ struct ipu_image_convert_chan *chan = ctx->chan; ++ struct ipu_image_convert_run *run; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&chan->irqlock, flags); ++ ++ run = chan->current_run; ++ if (run && run->ctx == ctx) { ++ convert_stop(run); ++ run->status = -EIO; ++ list_add_tail(&run->list, &chan->done_q); ++ chan->current_run = NULL; ++ run_next(chan); ++ } ++ ++ spin_unlock_irqrestore(&chan->irqlock, flags); ++ ++ empty_done_q(chan); ++} ++ ++static void release_ipu_resources(struct ipu_image_convert_chan *chan) ++{ ++ if (chan->out_eof_irq >= 0) ++ free_irq(chan->out_eof_irq, chan); ++ if (chan->rot_out_eof_irq >= 0) ++ free_irq(chan->rot_out_eof_irq, chan); ++ ++ if (!IS_ERR_OR_NULL(chan->in_chan)) ++ ipu_idmac_put(chan->in_chan); ++ if (!IS_ERR_OR_NULL(chan->out_chan)) ++ ipu_idmac_put(chan->out_chan); ++ if (!IS_ERR_OR_NULL(chan->rotation_in_chan)) ++ ipu_idmac_put(chan->rotation_in_chan); ++ if (!IS_ERR_OR_NULL(chan->rotation_out_chan)) ++ ipu_idmac_put(chan->rotation_out_chan); ++ if (!IS_ERR_OR_NULL(chan->ic)) ++ ipu_ic_put(chan->ic); ++ ++ chan->in_chan = chan->out_chan = chan->rotation_in_chan = ++ chan->rotation_out_chan = NULL; ++ chan->out_eof_irq = chan->rot_out_eof_irq = -1; ++} ++ ++static int get_ipu_resources(struct ipu_image_convert_chan *chan) ++{ ++ const struct ipu_image_convert_dma_chan *dma = chan->dma_ch; ++ struct ipu_image_convert_priv *priv = chan->priv; ++ int ret; ++ ++ /* get IC */ ++ chan->ic = ipu_ic_get(priv->ipu, chan->ic_task); ++ if (IS_ERR(chan->ic)) { ++ dev_err(priv->ipu->dev, "could not acquire IC\n"); ++ ret = PTR_ERR(chan->ic); ++ goto err; ++ } ++ ++ /* get IDMAC channels */ ++ chan->in_chan = ipu_idmac_get(priv->ipu, dma->in); ++ chan->out_chan = ipu_idmac_get(priv->ipu, dma->out); ++ if (IS_ERR(chan->in_chan) || IS_ERR(chan->out_chan)) { ++ dev_err(priv->ipu->dev, "could not acquire idmac channels\n"); ++ ret = -EBUSY; ++ goto err; ++ } ++ ++ chan->rotation_in_chan = ipu_idmac_get(priv->ipu, dma->rot_in); ++ chan->rotation_out_chan = ipu_idmac_get(priv->ipu, dma->rot_out); ++ if (IS_ERR(chan->rotation_in_chan) || IS_ERR(chan->rotation_out_chan)) { ++ dev_err(priv->ipu->dev, ++ "could not acquire idmac rotation channels\n"); ++ ret = -EBUSY; ++ goto err; ++ } ++ ++ /* acquire the EOF interrupts */ ++ chan->out_eof_irq = ipu_idmac_channel_irq(priv->ipu, ++ chan->out_chan, ++ IPU_IRQ_EOF); ++ ++ ret = request_threaded_irq(chan->out_eof_irq, norotate_irq, do_bh, ++ 0, "ipu-ic", chan); ++ if (ret < 0) { ++ dev_err(priv->ipu->dev, "could not acquire irq %d\n", ++ chan->out_eof_irq); ++ chan->out_eof_irq = -1; ++ goto err; ++ } ++ ++ chan->rot_out_eof_irq = ipu_idmac_channel_irq(priv->ipu, ++ chan->rotation_out_chan, ++ IPU_IRQ_EOF); ++ ++ ret = request_threaded_irq(chan->rot_out_eof_irq, rotate_irq, do_bh, ++ 0, "ipu-ic", chan); ++ if (ret < 0) { ++ dev_err(priv->ipu->dev, "could not acquire irq %d\n", ++ chan->rot_out_eof_irq); ++ chan->rot_out_eof_irq = -1; ++ goto err; ++ } ++ ++ return 0; ++err: ++ release_ipu_resources(chan); ++ return ret; ++} ++ ++static int fill_image(struct ipu_image_convert_ctx *ctx, ++ struct ipu_image_convert_image *ic_image, ++ struct ipu_image *image, ++ enum ipu_image_convert_type type) ++{ ++ struct ipu_image_convert_priv *priv = ctx->chan->priv; ++ ++ ic_image->base = *image; ++ ic_image->type = type; ++ ++ ic_image->fmt = get_format(image->pix.pixelformat); ++ if (!ic_image->fmt) { ++ dev_err(priv->ipu->dev, "pixelformat not supported for %s\n", ++ type == IMAGE_CONVERT_OUT ? "Output" : "Input"); ++ return -EINVAL; ++ } ++ ++ if (ic_image->fmt->planar) ++ ic_image->stride = ic_image->base.pix.width; ++ else ++ ic_image->stride = ic_image->base.pix.bytesperline; ++ ++ return 0; ++} ++ ++/* borrowed from drivers/media/v4l2-core/v4l2-common.c */ ++static unsigned int clamp_align(unsigned int x, unsigned int min, ++ unsigned int max, unsigned int align) ++{ ++ /* Bits that must be zero to be aligned */ ++ unsigned int mask = ~((1 << align) - 1); ++ ++ /* Clamp to aligned min and max */ ++ x = clamp(x, (min + ~mask) & mask, max & mask); ++ ++ /* Round to nearest aligned value */ ++ if (align) ++ x = (x + (1 << (align - 1))) & mask; ++ ++ return x; ++} ++ ++/* Adjusts input/output images to IPU restrictions */ ++void ipu_image_convert_adjust(struct ipu_image *in, struct ipu_image *out, ++ enum ipu_rotate_mode rot_mode) ++{ ++ const struct ipu_image_pixfmt *infmt, *outfmt; ++ u32 w_align_out, h_align_out; ++ u32 w_align_in, h_align_in; ++ ++ infmt = get_format(in->pix.pixelformat); ++ outfmt = get_format(out->pix.pixelformat); ++ ++ /* set some default pixel formats if needed */ ++ if (!infmt) { ++ in->pix.pixelformat = V4L2_PIX_FMT_RGB24; ++ infmt = get_format(V4L2_PIX_FMT_RGB24); ++ } ++ if (!outfmt) { ++ out->pix.pixelformat = V4L2_PIX_FMT_RGB24; ++ outfmt = get_format(V4L2_PIX_FMT_RGB24); ++ } ++ ++ /* image converter does not handle fields */ ++ in->pix.field = out->pix.field = V4L2_FIELD_NONE; ++ ++ /* resizer cannot downsize more than 4:1 */ ++ if (ipu_rot_mode_is_irt(rot_mode)) { ++ out->pix.height = max_t(__u32, out->pix.height, ++ in->pix.width / 4); ++ out->pix.width = max_t(__u32, out->pix.width, ++ in->pix.height / 4); ++ } else { ++ out->pix.width = max_t(__u32, out->pix.width, ++ in->pix.width / 4); ++ out->pix.height = max_t(__u32, out->pix.height, ++ in->pix.height / 4); ++ } ++ ++ /* align input width/height */ ++ w_align_in = ilog2(tile_width_align(IMAGE_CONVERT_IN, infmt, ++ rot_mode)); ++ h_align_in = ilog2(tile_height_align(IMAGE_CONVERT_IN, infmt, ++ rot_mode)); ++ in->pix.width = clamp_align(in->pix.width, MIN_W, MAX_W, ++ w_align_in); ++ in->pix.height = clamp_align(in->pix.height, MIN_H, MAX_H, ++ h_align_in); ++ ++ /* align output width/height */ ++ w_align_out = ilog2(tile_width_align(IMAGE_CONVERT_OUT, outfmt, ++ rot_mode)); ++ h_align_out = ilog2(tile_height_align(IMAGE_CONVERT_OUT, outfmt, ++ rot_mode)); ++ out->pix.width = clamp_align(out->pix.width, MIN_W, MAX_W, ++ w_align_out); ++ out->pix.height = clamp_align(out->pix.height, MIN_H, MAX_H, ++ h_align_out); ++ ++ /* set input/output strides and image sizes */ ++ in->pix.bytesperline = infmt->planar ? ++ clamp_align(in->pix.width, 2 << w_align_in, MAX_W, ++ w_align_in) : ++ clamp_align((in->pix.width * infmt->bpp) >> 3, ++ ((2 << w_align_in) * infmt->bpp) >> 3, ++ (MAX_W * infmt->bpp) >> 3, ++ w_align_in); ++ in->pix.sizeimage = infmt->planar ? ++ (in->pix.height * in->pix.bytesperline * infmt->bpp) >> 3 : ++ in->pix.height * in->pix.bytesperline; ++ out->pix.bytesperline = outfmt->planar ? out->pix.width : ++ (out->pix.width * outfmt->bpp) >> 3; ++ out->pix.sizeimage = outfmt->planar ? ++ (out->pix.height * out->pix.bytesperline * outfmt->bpp) >> 3 : ++ out->pix.height * out->pix.bytesperline; ++} ++EXPORT_SYMBOL_GPL(ipu_image_convert_adjust); ++ ++/* ++ * this is used by ipu_image_convert_prepare() to verify set input and ++ * output images are valid before starting the conversion. Clients can ++ * also call it before calling ipu_image_convert_prepare(). ++ */ ++int ipu_image_convert_verify(struct ipu_image *in, struct ipu_image *out, ++ enum ipu_rotate_mode rot_mode) ++{ ++ struct ipu_image testin, testout; ++ ++ testin = *in; ++ testout = *out; ++ ++ ipu_image_convert_adjust(&testin, &testout, rot_mode); ++ ++ if (testin.pix.width != in->pix.width || ++ testin.pix.height != in->pix.height || ++ testout.pix.width != out->pix.width || ++ testout.pix.height != out->pix.height) ++ return -EINVAL; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_image_convert_verify); ++ ++/* ++ * Call ipu_image_convert_prepare() to prepare for the conversion of ++ * given images and rotation mode. Returns a new conversion context. ++ */ ++struct ipu_image_convert_ctx * ++ipu_image_convert_prepare(struct ipu_soc *ipu, enum ipu_ic_task ic_task, ++ struct ipu_image *in, struct ipu_image *out, ++ enum ipu_rotate_mode rot_mode, ++ ipu_image_convert_cb_t complete, ++ void *complete_context) ++{ ++ struct ipu_image_convert_priv *priv = ipu->image_convert_priv; ++ struct ipu_image_convert_image *s_image, *d_image; ++ struct ipu_image_convert_chan *chan; ++ struct ipu_image_convert_ctx *ctx; ++ unsigned long flags; ++ unsigned int i; ++ bool get_res; ++ int ret; ++ ++ if (!in || !out || !complete || ++ (ic_task != IC_TASK_VIEWFINDER && ++ ic_task != IC_TASK_POST_PROCESSOR)) ++ return ERR_PTR(-EINVAL); ++ ++ /* verify the in/out images before continuing */ ++ ret = ipu_image_convert_verify(in, out, rot_mode); ++ if (ret) { ++ dev_err(priv->ipu->dev, "%s: in/out formats invalid\n", ++ __func__); ++ return ERR_PTR(ret); ++ } ++ ++ chan = &priv->chan[ic_task]; ++ ++ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); ++ if (!ctx) ++ return ERR_PTR(-ENOMEM); ++ ++ dev_dbg(priv->ipu->dev, "%s: task %u: ctx %p\n", __func__, ++ chan->ic_task, ctx); ++ ++ ctx->chan = chan; ++ init_completion(&ctx->aborted); ++ ++ ctx->rot_mode = rot_mode; ++ ++ /* Sets ctx->in.num_rows/cols as well */ ++ ret = calc_image_resize_coefficients(ctx, in, out); ++ if (ret) ++ goto out_free; ++ ++ s_image = &ctx->in; ++ d_image = &ctx->out; ++ ++ /* set tiling and rotation */ ++ if (ipu_rot_mode_is_irt(rot_mode)) { ++ d_image->num_rows = s_image->num_cols; ++ d_image->num_cols = s_image->num_rows; ++ } else { ++ d_image->num_rows = s_image->num_rows; ++ d_image->num_cols = s_image->num_cols; ++ } ++ ++ ctx->num_tiles = d_image->num_cols * d_image->num_rows; ++ ++ ret = fill_image(ctx, s_image, in, IMAGE_CONVERT_IN); ++ if (ret) ++ goto out_free; ++ ret = fill_image(ctx, d_image, out, IMAGE_CONVERT_OUT); ++ if (ret) ++ goto out_free; ++ ++ calc_out_tile_map(ctx); ++ ++ find_seams(ctx, s_image, d_image); ++ ++ ret = calc_tile_dimensions(ctx, s_image); ++ if (ret) ++ goto out_free; ++ ++ ret = calc_tile_offsets(ctx, s_image); ++ if (ret) ++ goto out_free; ++ ++ calc_tile_dimensions(ctx, d_image); ++ ret = calc_tile_offsets(ctx, d_image); ++ if (ret) ++ goto out_free; ++ ++ calc_tile_resize_coefficients(ctx); ++ ++ ret = ipu_ic_calc_csc(&ctx->csc, ++ s_image->base.pix.ycbcr_enc, ++ s_image->base.pix.quantization, ++ ipu_pixelformat_to_colorspace(s_image->fmt->fourcc), ++ d_image->base.pix.ycbcr_enc, ++ d_image->base.pix.quantization, ++ ipu_pixelformat_to_colorspace(d_image->fmt->fourcc)); ++ if (ret) ++ goto out_free; ++ ++ dump_format(ctx, s_image); ++ dump_format(ctx, d_image); ++ ++ ctx->complete = complete; ++ ctx->complete_context = complete_context; ++ ++ /* ++ * Can we use double-buffering for this operation? If there is ++ * only one tile (the whole image can be converted in a single ++ * operation) there's no point in using double-buffering. Also, ++ * the IPU's IDMAC channels allow only a single U and V plane ++ * offset shared between both buffers, but these offsets change ++ * for every tile, and therefore would have to be updated for ++ * each buffer which is not possible. So double-buffering is ++ * impossible when either the source or destination images are ++ * a planar format (YUV420, YUV422P, etc.). Further, differently ++ * sized tiles or different resizing coefficients per tile ++ * prevent double-buffering as well. ++ */ ++ ctx->double_buffering = (ctx->num_tiles > 1 && ++ !s_image->fmt->planar && ++ !d_image->fmt->planar); ++ for (i = 1; i < ctx->num_tiles; i++) { ++ if (ctx->in.tile[i].width != ctx->in.tile[0].width || ++ ctx->in.tile[i].height != ctx->in.tile[0].height || ++ ctx->out.tile[i].width != ctx->out.tile[0].width || ++ ctx->out.tile[i].height != ctx->out.tile[0].height) { ++ ctx->double_buffering = false; ++ break; ++ } ++ } ++ for (i = 1; i < ctx->in.num_cols; i++) { ++ if (ctx->resize_coeffs_h[i] != ctx->resize_coeffs_h[0]) { ++ ctx->double_buffering = false; ++ break; ++ } ++ } ++ for (i = 1; i < ctx->in.num_rows; i++) { ++ if (ctx->resize_coeffs_v[i] != ctx->resize_coeffs_v[0]) { ++ ctx->double_buffering = false; ++ break; ++ } ++ } ++ ++ if (ipu_rot_mode_is_irt(ctx->rot_mode)) { ++ unsigned long intermediate_size = d_image->tile[0].size; ++ ++ for (i = 1; i < ctx->num_tiles; i++) { ++ if (d_image->tile[i].size > intermediate_size) ++ intermediate_size = d_image->tile[i].size; ++ } ++ ++ ret = alloc_dma_buf(priv, &ctx->rot_intermediate[0], ++ intermediate_size); ++ if (ret) ++ goto out_free; ++ if (ctx->double_buffering) { ++ ret = alloc_dma_buf(priv, ++ &ctx->rot_intermediate[1], ++ intermediate_size); ++ if (ret) ++ goto out_free_dmabuf0; ++ } ++ } ++ ++ spin_lock_irqsave(&chan->irqlock, flags); ++ ++ get_res = list_empty(&chan->ctx_list); ++ ++ list_add_tail(&ctx->list, &chan->ctx_list); ++ ++ spin_unlock_irqrestore(&chan->irqlock, flags); ++ ++ if (get_res) { ++ ret = get_ipu_resources(chan); ++ if (ret) ++ goto out_free_dmabuf1; ++ } ++ ++ return ctx; ++ ++out_free_dmabuf1: ++ free_dma_buf(priv, &ctx->rot_intermediate[1]); ++ spin_lock_irqsave(&chan->irqlock, flags); ++ list_del(&ctx->list); ++ spin_unlock_irqrestore(&chan->irqlock, flags); ++out_free_dmabuf0: ++ free_dma_buf(priv, &ctx->rot_intermediate[0]); ++out_free: ++ kfree(ctx); ++ return ERR_PTR(ret); ++} ++EXPORT_SYMBOL_GPL(ipu_image_convert_prepare); ++ ++/* ++ * Carry out a single image conversion run. Only the physaddr's of the input ++ * and output image buffers are needed. The conversion context must have ++ * been created previously with ipu_image_convert_prepare(). ++ */ ++int ipu_image_convert_queue(struct ipu_image_convert_run *run) ++{ ++ struct ipu_image_convert_chan *chan; ++ struct ipu_image_convert_priv *priv; ++ struct ipu_image_convert_ctx *ctx; ++ unsigned long flags; ++ int ret = 0; ++ ++ if (!run || !run->ctx || !run->in_phys || !run->out_phys) ++ return -EINVAL; ++ ++ ctx = run->ctx; ++ chan = ctx->chan; ++ priv = chan->priv; ++ ++ dev_dbg(priv->ipu->dev, "%s: task %u: ctx %p run %p\n", __func__, ++ chan->ic_task, ctx, run); ++ ++ INIT_LIST_HEAD(&run->list); ++ ++ spin_lock_irqsave(&chan->irqlock, flags); ++ ++ if (ctx->aborting) { ++ ret = -EIO; ++ goto unlock; ++ } ++ ++ list_add_tail(&run->list, &chan->pending_q); ++ ++ if (!chan->current_run) { ++ ret = do_run(run); ++ if (ret) ++ chan->current_run = NULL; ++ } ++unlock: ++ spin_unlock_irqrestore(&chan->irqlock, flags); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(ipu_image_convert_queue); ++ ++/* Abort any active or pending conversions for this context */ ++static void __ipu_image_convert_abort(struct ipu_image_convert_ctx *ctx) ++{ ++ struct ipu_image_convert_chan *chan = ctx->chan; ++ struct ipu_image_convert_priv *priv = chan->priv; ++ struct ipu_image_convert_run *run, *active_run, *tmp; ++ unsigned long flags; ++ int run_count, ret; ++ ++ spin_lock_irqsave(&chan->irqlock, flags); ++ ++ /* move all remaining pending runs in this context to done_q */ ++ list_for_each_entry_safe(run, tmp, &chan->pending_q, list) { ++ if (run->ctx != ctx) ++ continue; ++ run->status = -EIO; ++ list_move_tail(&run->list, &chan->done_q); ++ } ++ ++ run_count = get_run_count(ctx, &chan->done_q); ++ active_run = (chan->current_run && chan->current_run->ctx == ctx) ? ++ chan->current_run : NULL; ++ ++ if (active_run) ++ reinit_completion(&ctx->aborted); ++ ++ ctx->aborting = true; ++ ++ spin_unlock_irqrestore(&chan->irqlock, flags); ++ ++ if (!run_count && !active_run) { ++ dev_dbg(priv->ipu->dev, ++ "%s: task %u: no abort needed for ctx %p\n", ++ __func__, chan->ic_task, ctx); ++ return; ++ } ++ ++ if (!active_run) { ++ empty_done_q(chan); ++ return; ++ } ++ ++ dev_dbg(priv->ipu->dev, ++ "%s: task %u: wait for completion: %d runs\n", ++ __func__, chan->ic_task, run_count); ++ ++ ret = wait_for_completion_timeout(&ctx->aborted, ++ msecs_to_jiffies(10000)); ++ if (ret == 0) { ++ dev_warn(priv->ipu->dev, "%s: timeout\n", __func__); ++ force_abort(ctx); ++ } ++} ++ ++void ipu_image_convert_abort(struct ipu_image_convert_ctx *ctx) ++{ ++ __ipu_image_convert_abort(ctx); ++ ctx->aborting = false; ++} ++EXPORT_SYMBOL_GPL(ipu_image_convert_abort); ++ ++/* Unprepare image conversion context */ ++void ipu_image_convert_unprepare(struct ipu_image_convert_ctx *ctx) ++{ ++ struct ipu_image_convert_chan *chan = ctx->chan; ++ struct ipu_image_convert_priv *priv = chan->priv; ++ unsigned long flags; ++ bool put_res; ++ ++ /* make sure no runs are hanging around */ ++ __ipu_image_convert_abort(ctx); ++ ++ dev_dbg(priv->ipu->dev, "%s: task %u: removing ctx %p\n", __func__, ++ chan->ic_task, ctx); ++ ++ spin_lock_irqsave(&chan->irqlock, flags); ++ ++ list_del(&ctx->list); ++ ++ put_res = list_empty(&chan->ctx_list); ++ ++ spin_unlock_irqrestore(&chan->irqlock, flags); ++ ++ if (put_res) ++ release_ipu_resources(chan); ++ ++ free_dma_buf(priv, &ctx->rot_intermediate[1]); ++ free_dma_buf(priv, &ctx->rot_intermediate[0]); ++ ++ kfree(ctx); ++} ++EXPORT_SYMBOL_GPL(ipu_image_convert_unprepare); ++ ++/* ++ * "Canned" asynchronous single image conversion. Allocates and returns ++ * a new conversion run. On successful return the caller must free the ++ * run and call ipu_image_convert_unprepare() after conversion completes. ++ */ ++struct ipu_image_convert_run * ++ipu_image_convert(struct ipu_soc *ipu, enum ipu_ic_task ic_task, ++ struct ipu_image *in, struct ipu_image *out, ++ enum ipu_rotate_mode rot_mode, ++ ipu_image_convert_cb_t complete, ++ void *complete_context) ++{ ++ struct ipu_image_convert_ctx *ctx; ++ struct ipu_image_convert_run *run; ++ int ret; ++ ++ ctx = ipu_image_convert_prepare(ipu, ic_task, in, out, rot_mode, ++ complete, complete_context); ++ if (IS_ERR(ctx)) ++ return ERR_CAST(ctx); ++ ++ run = kzalloc(sizeof(*run), GFP_KERNEL); ++ if (!run) { ++ ipu_image_convert_unprepare(ctx); ++ return ERR_PTR(-ENOMEM); ++ } ++ ++ run->ctx = ctx; ++ run->in_phys = in->phys0; ++ run->out_phys = out->phys0; ++ ++ ret = ipu_image_convert_queue(run); ++ if (ret) { ++ ipu_image_convert_unprepare(ctx); ++ kfree(run); ++ return ERR_PTR(ret); ++ } ++ ++ return run; ++} ++EXPORT_SYMBOL_GPL(ipu_image_convert); ++ ++/* "Canned" synchronous single image conversion */ ++static void image_convert_sync_complete(struct ipu_image_convert_run *run, ++ void *data) ++{ ++ struct completion *comp = data; ++ ++ complete(comp); ++} ++ ++int ipu_image_convert_sync(struct ipu_soc *ipu, enum ipu_ic_task ic_task, ++ struct ipu_image *in, struct ipu_image *out, ++ enum ipu_rotate_mode rot_mode) ++{ ++ struct ipu_image_convert_run *run; ++ struct completion comp; ++ int ret; ++ ++ init_completion(&comp); ++ ++ run = ipu_image_convert(ipu, ic_task, in, out, rot_mode, ++ image_convert_sync_complete, &comp); ++ if (IS_ERR(run)) ++ return PTR_ERR(run); ++ ++ ret = wait_for_completion_timeout(&comp, msecs_to_jiffies(10000)); ++ ret = (ret == 0) ? -ETIMEDOUT : 0; ++ ++ ipu_image_convert_unprepare(run->ctx); ++ kfree(run); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(ipu_image_convert_sync); ++ ++int ipu_image_convert_init(struct ipu_soc *ipu, struct device *dev) ++{ ++ struct ipu_image_convert_priv *priv; ++ int i; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ ipu->image_convert_priv = priv; ++ priv->ipu = ipu; ++ ++ for (i = 0; i < IC_NUM_TASKS; i++) { ++ struct ipu_image_convert_chan *chan = &priv->chan[i]; ++ ++ chan->ic_task = i; ++ chan->priv = priv; ++ chan->dma_ch = &image_convert_dma_chan[i]; ++ chan->out_eof_irq = -1; ++ chan->rot_out_eof_irq = -1; ++ ++ spin_lock_init(&chan->irqlock); ++ INIT_LIST_HEAD(&chan->ctx_list); ++ INIT_LIST_HEAD(&chan->pending_q); ++ INIT_LIST_HEAD(&chan->done_q); ++ } ++ ++ return 0; ++} ++ ++void ipu_image_convert_exit(struct ipu_soc *ipu) ++{ ++} +--- /dev/null ++++ b/drivers/gpu/ipu-v3/ipu-pre.c +@@ -0,0 +1,346 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (c) 2017 Lucas Stach, Pengutronix ++ */ ++ ++#include <drm/drm_fourcc.h> ++#include <linux/clk.h> ++#include <linux/err.h> ++#include <linux/genalloc.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/platform_device.h> ++#include <video/imx-ipu-v3.h> ++ ++#include "ipu-prv.h" ++ ++#define IPU_PRE_MAX_WIDTH 2048 ++#define IPU_PRE_NUM_SCANLINES 8 ++ ++#define IPU_PRE_CTRL 0x000 ++#define IPU_PRE_CTRL_SET 0x004 ++#define IPU_PRE_CTRL_ENABLE (1 << 0) ++#define IPU_PRE_CTRL_BLOCK_EN (1 << 1) ++#define IPU_PRE_CTRL_BLOCK_16 (1 << 2) ++#define IPU_PRE_CTRL_SDW_UPDATE (1 << 4) ++#define IPU_PRE_CTRL_VFLIP (1 << 5) ++#define IPU_PRE_CTRL_SO (1 << 6) ++#define IPU_PRE_CTRL_INTERLACED_FIELD (1 << 7) ++#define IPU_PRE_CTRL_HANDSHAKE_EN (1 << 8) ++#define IPU_PRE_CTRL_HANDSHAKE_LINE_NUM(v) ((v & 0x3) << 9) ++#define IPU_PRE_CTRL_HANDSHAKE_ABORT_SKIP_EN (1 << 11) ++#define IPU_PRE_CTRL_EN_REPEAT (1 << 28) ++#define IPU_PRE_CTRL_TPR_REST_SEL (1 << 29) ++#define IPU_PRE_CTRL_CLKGATE (1 << 30) ++#define IPU_PRE_CTRL_SFTRST (1 << 31) ++ ++#define IPU_PRE_CUR_BUF 0x030 ++ ++#define IPU_PRE_NEXT_BUF 0x040 ++ ++#define IPU_PRE_TPR_CTRL 0x070 ++#define IPU_PRE_TPR_CTRL_TILE_FORMAT(v) ((v & 0xff) << 0) ++#define IPU_PRE_TPR_CTRL_TILE_FORMAT_MASK 0xff ++#define IPU_PRE_TPR_CTRL_TILE_FORMAT_16_BIT (1 << 0) ++#define IPU_PRE_TPR_CTRL_TILE_FORMAT_SPLIT_BUF (1 << 4) ++#define IPU_PRE_TPR_CTRL_TILE_FORMAT_SINGLE_BUF (1 << 5) ++#define IPU_PRE_TPR_CTRL_TILE_FORMAT_SUPER_TILED (1 << 6) ++ ++#define IPU_PRE_PREFETCH_ENG_CTRL 0x080 ++#define IPU_PRE_PREF_ENG_CTRL_PREFETCH_EN (1 << 0) ++#define IPU_PRE_PREF_ENG_CTRL_RD_NUM_BYTES(v) ((v & 0x7) << 1) ++#define IPU_PRE_PREF_ENG_CTRL_INPUT_ACTIVE_BPP(v) ((v & 0x3) << 4) ++#define IPU_PRE_PREF_ENG_CTRL_INPUT_PIXEL_FORMAT(v) ((v & 0x7) << 8) ++#define IPU_PRE_PREF_ENG_CTRL_SHIFT_BYPASS (1 << 11) ++#define IPU_PRE_PREF_ENG_CTRL_FIELD_INVERSE (1 << 12) ++#define IPU_PRE_PREF_ENG_CTRL_PARTIAL_UV_SWAP (1 << 14) ++#define IPU_PRE_PREF_ENG_CTRL_TPR_COOR_OFFSET_EN (1 << 15) ++ ++#define IPU_PRE_PREFETCH_ENG_INPUT_SIZE 0x0a0 ++#define IPU_PRE_PREFETCH_ENG_INPUT_SIZE_WIDTH(v) ((v & 0xffff) << 0) ++#define IPU_PRE_PREFETCH_ENG_INPUT_SIZE_HEIGHT(v) ((v & 0xffff) << 16) ++ ++#define IPU_PRE_PREFETCH_ENG_PITCH 0x0d0 ++#define IPU_PRE_PREFETCH_ENG_PITCH_Y(v) ((v & 0xffff) << 0) ++#define IPU_PRE_PREFETCH_ENG_PITCH_UV(v) ((v & 0xffff) << 16) ++ ++#define IPU_PRE_STORE_ENG_CTRL 0x110 ++#define IPU_PRE_STORE_ENG_CTRL_STORE_EN (1 << 0) ++#define IPU_PRE_STORE_ENG_CTRL_WR_NUM_BYTES(v) ((v & 0x7) << 1) ++#define IPU_PRE_STORE_ENG_CTRL_OUTPUT_ACTIVE_BPP(v) ((v & 0x3) << 4) ++ ++#define IPU_PRE_STORE_ENG_STATUS 0x120 ++#define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_X_MASK 0xffff ++#define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_X_SHIFT 0 ++#define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_MASK 0x3fff ++#define IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_SHIFT 16 ++#define IPU_PRE_STORE_ENG_STATUS_STORE_FIFO_FULL (1 << 30) ++#define IPU_PRE_STORE_ENG_STATUS_STORE_FIELD (1 << 31) ++ ++#define IPU_PRE_STORE_ENG_SIZE 0x130 ++#define IPU_PRE_STORE_ENG_SIZE_INPUT_WIDTH(v) ((v & 0xffff) << 0) ++#define IPU_PRE_STORE_ENG_SIZE_INPUT_HEIGHT(v) ((v & 0xffff) << 16) ++ ++#define IPU_PRE_STORE_ENG_PITCH 0x140 ++#define IPU_PRE_STORE_ENG_PITCH_OUT_PITCH(v) ((v & 0xffff) << 0) ++ ++#define IPU_PRE_STORE_ENG_ADDR 0x150 ++ ++struct ipu_pre { ++ struct list_head list; ++ struct device *dev; ++ ++ void __iomem *regs; ++ struct clk *clk_axi; ++ struct gen_pool *iram; ++ ++ dma_addr_t buffer_paddr; ++ void *buffer_virt; ++ bool in_use; ++ unsigned int safe_window_end; ++ unsigned int last_bufaddr; ++}; ++ ++static DEFINE_MUTEX(ipu_pre_list_mutex); ++static LIST_HEAD(ipu_pre_list); ++static int available_pres; ++ ++int ipu_pre_get_available_count(void) ++{ ++ return available_pres; ++} ++ ++struct ipu_pre * ++ipu_pre_lookup_by_phandle(struct device *dev, const char *name, int index) ++{ ++ struct device_node *pre_node = of_parse_phandle(dev->of_node, ++ name, index); ++ struct ipu_pre *pre; ++ ++ mutex_lock(&ipu_pre_list_mutex); ++ list_for_each_entry(pre, &ipu_pre_list, list) { ++ if (pre_node == pre->dev->of_node) { ++ mutex_unlock(&ipu_pre_list_mutex); ++ device_link_add(dev, pre->dev, ++ DL_FLAG_AUTOREMOVE_CONSUMER); ++ of_node_put(pre_node); ++ return pre; ++ } ++ } ++ mutex_unlock(&ipu_pre_list_mutex); ++ ++ of_node_put(pre_node); ++ ++ return NULL; ++} ++ ++int ipu_pre_get(struct ipu_pre *pre) ++{ ++ u32 val; ++ ++ if (pre->in_use) ++ return -EBUSY; ++ ++ /* first get the engine out of reset and remove clock gating */ ++ writel(0, pre->regs + IPU_PRE_CTRL); ++ ++ /* init defaults that should be applied to all streams */ ++ val = IPU_PRE_CTRL_HANDSHAKE_ABORT_SKIP_EN | ++ IPU_PRE_CTRL_HANDSHAKE_EN | ++ IPU_PRE_CTRL_TPR_REST_SEL | ++ IPU_PRE_CTRL_SDW_UPDATE; ++ writel(val, pre->regs + IPU_PRE_CTRL); ++ ++ pre->in_use = true; ++ return 0; ++} ++ ++void ipu_pre_put(struct ipu_pre *pre) ++{ ++ writel(IPU_PRE_CTRL_SFTRST, pre->regs + IPU_PRE_CTRL); ++ ++ pre->in_use = false; ++} ++ ++void ipu_pre_configure(struct ipu_pre *pre, unsigned int width, ++ unsigned int height, unsigned int stride, u32 format, ++ uint64_t modifier, unsigned int bufaddr) ++{ ++ const struct drm_format_info *info = drm_format_info(format); ++ u32 active_bpp = info->cpp[0] >> 1; ++ u32 val; ++ ++ /* calculate safe window for ctrl register updates */ ++ if (modifier == DRM_FORMAT_MOD_LINEAR) ++ pre->safe_window_end = height - 2; ++ else ++ pre->safe_window_end = DIV_ROUND_UP(height, 4) - 1; ++ ++ writel(bufaddr, pre->regs + IPU_PRE_CUR_BUF); ++ writel(bufaddr, pre->regs + IPU_PRE_NEXT_BUF); ++ pre->last_bufaddr = bufaddr; ++ ++ val = IPU_PRE_PREF_ENG_CTRL_INPUT_PIXEL_FORMAT(0) | ++ IPU_PRE_PREF_ENG_CTRL_INPUT_ACTIVE_BPP(active_bpp) | ++ IPU_PRE_PREF_ENG_CTRL_RD_NUM_BYTES(4) | ++ IPU_PRE_PREF_ENG_CTRL_SHIFT_BYPASS | ++ IPU_PRE_PREF_ENG_CTRL_PREFETCH_EN; ++ writel(val, pre->regs + IPU_PRE_PREFETCH_ENG_CTRL); ++ ++ val = IPU_PRE_PREFETCH_ENG_INPUT_SIZE_WIDTH(width) | ++ IPU_PRE_PREFETCH_ENG_INPUT_SIZE_HEIGHT(height); ++ writel(val, pre->regs + IPU_PRE_PREFETCH_ENG_INPUT_SIZE); ++ ++ val = IPU_PRE_PREFETCH_ENG_PITCH_Y(stride); ++ writel(val, pre->regs + IPU_PRE_PREFETCH_ENG_PITCH); ++ ++ val = IPU_PRE_STORE_ENG_CTRL_OUTPUT_ACTIVE_BPP(active_bpp) | ++ IPU_PRE_STORE_ENG_CTRL_WR_NUM_BYTES(4) | ++ IPU_PRE_STORE_ENG_CTRL_STORE_EN; ++ writel(val, pre->regs + IPU_PRE_STORE_ENG_CTRL); ++ ++ val = IPU_PRE_STORE_ENG_SIZE_INPUT_WIDTH(width) | ++ IPU_PRE_STORE_ENG_SIZE_INPUT_HEIGHT(height); ++ writel(val, pre->regs + IPU_PRE_STORE_ENG_SIZE); ++ ++ val = IPU_PRE_STORE_ENG_PITCH_OUT_PITCH(stride); ++ writel(val, pre->regs + IPU_PRE_STORE_ENG_PITCH); ++ ++ writel(pre->buffer_paddr, pre->regs + IPU_PRE_STORE_ENG_ADDR); ++ ++ val = readl(pre->regs + IPU_PRE_TPR_CTRL); ++ val &= ~IPU_PRE_TPR_CTRL_TILE_FORMAT_MASK; ++ if (modifier != DRM_FORMAT_MOD_LINEAR) { ++ /* only support single buffer formats for now */ ++ val |= IPU_PRE_TPR_CTRL_TILE_FORMAT_SINGLE_BUF; ++ if (modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED) ++ val |= IPU_PRE_TPR_CTRL_TILE_FORMAT_SUPER_TILED; ++ if (info->cpp[0] == 2) ++ val |= IPU_PRE_TPR_CTRL_TILE_FORMAT_16_BIT; ++ } ++ writel(val, pre->regs + IPU_PRE_TPR_CTRL); ++ ++ val = readl(pre->regs + IPU_PRE_CTRL); ++ val |= IPU_PRE_CTRL_EN_REPEAT | IPU_PRE_CTRL_ENABLE | ++ IPU_PRE_CTRL_SDW_UPDATE; ++ if (modifier == DRM_FORMAT_MOD_LINEAR) ++ val &= ~IPU_PRE_CTRL_BLOCK_EN; ++ else ++ val |= IPU_PRE_CTRL_BLOCK_EN; ++ writel(val, pre->regs + IPU_PRE_CTRL); ++} ++ ++void ipu_pre_update(struct ipu_pre *pre, unsigned int bufaddr) ++{ ++ unsigned long timeout = jiffies + msecs_to_jiffies(5); ++ unsigned short current_yblock; ++ u32 val; ++ ++ if (bufaddr == pre->last_bufaddr) ++ return; ++ ++ writel(bufaddr, pre->regs + IPU_PRE_NEXT_BUF); ++ pre->last_bufaddr = bufaddr; ++ ++ do { ++ if (time_after(jiffies, timeout)) { ++ dev_warn(pre->dev, "timeout waiting for PRE safe window\n"); ++ return; ++ } ++ ++ val = readl(pre->regs + IPU_PRE_STORE_ENG_STATUS); ++ current_yblock = ++ (val >> IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_SHIFT) & ++ IPU_PRE_STORE_ENG_STATUS_STORE_BLOCK_Y_MASK; ++ } while (current_yblock == 0 || current_yblock >= pre->safe_window_end); ++ ++ writel(IPU_PRE_CTRL_SDW_UPDATE, pre->regs + IPU_PRE_CTRL_SET); ++} ++ ++bool ipu_pre_update_pending(struct ipu_pre *pre) ++{ ++ return !!(readl_relaxed(pre->regs + IPU_PRE_CTRL) & ++ IPU_PRE_CTRL_SDW_UPDATE); ++} ++ ++u32 ipu_pre_get_baddr(struct ipu_pre *pre) ++{ ++ return (u32)pre->buffer_paddr; ++} ++ ++static int ipu_pre_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct resource *res; ++ struct ipu_pre *pre; ++ ++ pre = devm_kzalloc(dev, sizeof(*pre), GFP_KERNEL); ++ if (!pre) ++ return -ENOMEM; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ pre->regs = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(pre->regs)) ++ return PTR_ERR(pre->regs); ++ ++ pre->clk_axi = devm_clk_get(dev, "axi"); ++ if (IS_ERR(pre->clk_axi)) ++ return PTR_ERR(pre->clk_axi); ++ ++ pre->iram = of_gen_pool_get(dev->of_node, "fsl,iram", 0); ++ if (!pre->iram) ++ return -EPROBE_DEFER; ++ ++ /* ++ * Allocate IRAM buffer with maximum size. This could be made dynamic, ++ * but as there is no other user of this IRAM region and we can fit all ++ * max sized buffers into it, there is no need yet. ++ */ ++ pre->buffer_virt = gen_pool_dma_alloc(pre->iram, IPU_PRE_MAX_WIDTH * ++ IPU_PRE_NUM_SCANLINES * 4, ++ &pre->buffer_paddr); ++ if (!pre->buffer_virt) ++ return -ENOMEM; ++ ++ clk_prepare_enable(pre->clk_axi); ++ ++ pre->dev = dev; ++ platform_set_drvdata(pdev, pre); ++ mutex_lock(&ipu_pre_list_mutex); ++ list_add(&pre->list, &ipu_pre_list); ++ available_pres++; ++ mutex_unlock(&ipu_pre_list_mutex); ++ ++ return 0; ++} ++ ++static int ipu_pre_remove(struct platform_device *pdev) ++{ ++ struct ipu_pre *pre = platform_get_drvdata(pdev); ++ ++ mutex_lock(&ipu_pre_list_mutex); ++ list_del(&pre->list); ++ available_pres--; ++ mutex_unlock(&ipu_pre_list_mutex); ++ ++ clk_disable_unprepare(pre->clk_axi); ++ ++ if (pre->buffer_virt) ++ gen_pool_free(pre->iram, (unsigned long)pre->buffer_virt, ++ IPU_PRE_MAX_WIDTH * IPU_PRE_NUM_SCANLINES * 4); ++ return 0; ++} ++ ++static const struct of_device_id ipu_pre_dt_ids[] = { ++ { .compatible = "fsl,imx6qp-pre", }, ++ { /* sentinel */ }, ++}; ++ ++struct platform_driver ipu_pre_drv = { ++ .probe = ipu_pre_probe, ++ .remove = ipu_pre_remove, ++ .driver = { ++ .name = "imx-ipu-pre", ++ .of_match_table = ipu_pre_dt_ids, ++ }, ++}; +--- /dev/null ++++ b/drivers/gpu/ipu-v3/ipu-prg.c +@@ -0,0 +1,483 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (c) 2016-2017 Lucas Stach, Pengutronix ++ */ ++ ++#include <drm/drm_fourcc.h> ++#include <linux/clk.h> ++#include <linux/err.h> ++#include <linux/iopoll.h> ++#include <linux/mfd/syscon.h> ++#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> ++#include <linux/module.h> ++#include <linux/of.h> ++#include <linux/platform_device.h> ++#include <linux/pm_runtime.h> ++#include <linux/regmap.h> ++#include <video/imx-ipu-v3.h> ++ ++#include "ipu-prv.h" ++ ++#define IPU_PRG_CTL 0x00 ++#define IPU_PRG_CTL_BYPASS(i) (1 << (0 + i)) ++#define IPU_PRG_CTL_SOFT_ARID_MASK 0x3 ++#define IPU_PRG_CTL_SOFT_ARID_SHIFT(i) (8 + i * 2) ++#define IPU_PRG_CTL_SOFT_ARID(i, v) ((v & 0x3) << (8 + 2 * i)) ++#define IPU_PRG_CTL_SO(i) (1 << (16 + i)) ++#define IPU_PRG_CTL_VFLIP(i) (1 << (19 + i)) ++#define IPU_PRG_CTL_BLOCK_MODE(i) (1 << (22 + i)) ++#define IPU_PRG_CTL_CNT_LOAD_EN(i) (1 << (25 + i)) ++#define IPU_PRG_CTL_SOFTRST (1 << 30) ++#define IPU_PRG_CTL_SHADOW_EN (1 << 31) ++ ++#define IPU_PRG_STATUS 0x04 ++#define IPU_PRG_STATUS_BUFFER0_READY(i) (1 << (0 + i * 2)) ++#define IPU_PRG_STATUS_BUFFER1_READY(i) (1 << (1 + i * 2)) ++ ++#define IPU_PRG_QOS 0x08 ++#define IPU_PRG_QOS_ARID_MASK 0xf ++#define IPU_PRG_QOS_ARID_SHIFT(i) (0 + i * 4) ++ ++#define IPU_PRG_REG_UPDATE 0x0c ++#define IPU_PRG_REG_UPDATE_REG_UPDATE (1 << 0) ++ ++#define IPU_PRG_STRIDE(i) (0x10 + i * 0x4) ++#define IPU_PRG_STRIDE_STRIDE_MASK 0x3fff ++ ++#define IPU_PRG_CROP_LINE 0x1c ++ ++#define IPU_PRG_THD 0x20 ++ ++#define IPU_PRG_BADDR(i) (0x24 + i * 0x4) ++ ++#define IPU_PRG_OFFSET(i) (0x30 + i * 0x4) ++ ++#define IPU_PRG_ILO(i) (0x3c + i * 0x4) ++ ++#define IPU_PRG_HEIGHT(i) (0x48 + i * 0x4) ++#define IPU_PRG_HEIGHT_PRE_HEIGHT_MASK 0xfff ++#define IPU_PRG_HEIGHT_PRE_HEIGHT_SHIFT 0 ++#define IPU_PRG_HEIGHT_IPU_HEIGHT_MASK 0xfff ++#define IPU_PRG_HEIGHT_IPU_HEIGHT_SHIFT 16 ++ ++struct ipu_prg_channel { ++ bool enabled; ++ int used_pre; ++}; ++ ++struct ipu_prg { ++ struct list_head list; ++ struct device *dev; ++ int id; ++ ++ void __iomem *regs; ++ struct clk *clk_ipg, *clk_axi; ++ struct regmap *iomuxc_gpr; ++ struct ipu_pre *pres[3]; ++ ++ struct ipu_prg_channel chan[3]; ++}; ++ ++static DEFINE_MUTEX(ipu_prg_list_mutex); ++static LIST_HEAD(ipu_prg_list); ++ ++struct ipu_prg * ++ipu_prg_lookup_by_phandle(struct device *dev, const char *name, int ipu_id) ++{ ++ struct device_node *prg_node = of_parse_phandle(dev->of_node, ++ name, 0); ++ struct ipu_prg *prg; ++ ++ mutex_lock(&ipu_prg_list_mutex); ++ list_for_each_entry(prg, &ipu_prg_list, list) { ++ if (prg_node == prg->dev->of_node) { ++ mutex_unlock(&ipu_prg_list_mutex); ++ device_link_add(dev, prg->dev, ++ DL_FLAG_AUTOREMOVE_CONSUMER); ++ prg->id = ipu_id; ++ of_node_put(prg_node); ++ return prg; ++ } ++ } ++ mutex_unlock(&ipu_prg_list_mutex); ++ ++ of_node_put(prg_node); ++ ++ return NULL; ++} ++ ++int ipu_prg_max_active_channels(void) ++{ ++ return ipu_pre_get_available_count(); ++} ++EXPORT_SYMBOL_GPL(ipu_prg_max_active_channels); ++ ++bool ipu_prg_present(struct ipu_soc *ipu) ++{ ++ if (ipu->prg_priv) ++ return true; ++ ++ return false; ++} ++EXPORT_SYMBOL_GPL(ipu_prg_present); ++ ++bool ipu_prg_format_supported(struct ipu_soc *ipu, uint32_t format, ++ uint64_t modifier) ++{ ++ const struct drm_format_info *info = drm_format_info(format); ++ ++ if (info->num_planes != 1) ++ return false; ++ ++ switch (modifier) { ++ case DRM_FORMAT_MOD_LINEAR: ++ case DRM_FORMAT_MOD_VIVANTE_TILED: ++ case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED: ++ return true; ++ default: ++ return false; ++ } ++} ++EXPORT_SYMBOL_GPL(ipu_prg_format_supported); ++ ++int ipu_prg_enable(struct ipu_soc *ipu) ++{ ++ struct ipu_prg *prg = ipu->prg_priv; ++ ++ if (!prg) ++ return 0; ++ ++ return pm_runtime_get_sync(prg->dev); ++} ++EXPORT_SYMBOL_GPL(ipu_prg_enable); ++ ++void ipu_prg_disable(struct ipu_soc *ipu) ++{ ++ struct ipu_prg *prg = ipu->prg_priv; ++ ++ if (!prg) ++ return; ++ ++ pm_runtime_put(prg->dev); ++} ++EXPORT_SYMBOL_GPL(ipu_prg_disable); ++ ++/* ++ * The channel configuartion functions below are not thread safe, as they ++ * must be only called from the atomic commit path in the DRM driver, which ++ * is properly serialized. ++ */ ++static int ipu_prg_ipu_to_prg_chan(int ipu_chan) ++{ ++ /* ++ * This isn't clearly documented in the RM, but IPU to PRG channel ++ * assignment is fixed, as only with this mapping the control signals ++ * match up. ++ */ ++ switch (ipu_chan) { ++ case IPUV3_CHANNEL_MEM_BG_SYNC: ++ return 0; ++ case IPUV3_CHANNEL_MEM_FG_SYNC: ++ return 1; ++ case IPUV3_CHANNEL_MEM_DC_SYNC: ++ return 2; ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int ipu_prg_get_pre(struct ipu_prg *prg, int prg_chan) ++{ ++ int i, ret; ++ ++ /* channel 0 is special as it is hardwired to one of the PREs */ ++ if (prg_chan == 0) { ++ ret = ipu_pre_get(prg->pres[0]); ++ if (ret) ++ goto fail; ++ prg->chan[prg_chan].used_pre = 0; ++ return 0; ++ } ++ ++ for (i = 1; i < 3; i++) { ++ ret = ipu_pre_get(prg->pres[i]); ++ if (!ret) { ++ u32 val, mux; ++ int shift; ++ ++ prg->chan[prg_chan].used_pre = i; ++ ++ /* configure the PRE to PRG channel mux */ ++ shift = (i == 1) ? 12 : 14; ++ mux = (prg->id << 1) | (prg_chan - 1); ++ regmap_update_bits(prg->iomuxc_gpr, IOMUXC_GPR5, ++ 0x3 << shift, mux << shift); ++ ++ /* check other mux, must not point to same channel */ ++ shift = (i == 1) ? 14 : 12; ++ regmap_read(prg->iomuxc_gpr, IOMUXC_GPR5, &val); ++ if (((val >> shift) & 0x3) == mux) { ++ regmap_update_bits(prg->iomuxc_gpr, IOMUXC_GPR5, ++ 0x3 << shift, ++ (mux ^ 0x1) << shift); ++ } ++ ++ return 0; ++ } ++ } ++ ++fail: ++ dev_err(prg->dev, "could not get PRE for PRG chan %d", prg_chan); ++ return ret; ++} ++ ++static void ipu_prg_put_pre(struct ipu_prg *prg, int prg_chan) ++{ ++ struct ipu_prg_channel *chan = &prg->chan[prg_chan]; ++ ++ ipu_pre_put(prg->pres[chan->used_pre]); ++ chan->used_pre = -1; ++} ++ ++void ipu_prg_channel_disable(struct ipuv3_channel *ipu_chan) ++{ ++ int prg_chan = ipu_prg_ipu_to_prg_chan(ipu_chan->num); ++ struct ipu_prg *prg = ipu_chan->ipu->prg_priv; ++ struct ipu_prg_channel *chan; ++ u32 val; ++ ++ if (prg_chan < 0) ++ return; ++ ++ chan = &prg->chan[prg_chan]; ++ if (!chan->enabled) ++ return; ++ ++ pm_runtime_get_sync(prg->dev); ++ ++ val = readl(prg->regs + IPU_PRG_CTL); ++ val |= IPU_PRG_CTL_BYPASS(prg_chan); ++ writel(val, prg->regs + IPU_PRG_CTL); ++ ++ val = IPU_PRG_REG_UPDATE_REG_UPDATE; ++ writel(val, prg->regs + IPU_PRG_REG_UPDATE); ++ ++ pm_runtime_put(prg->dev); ++ ++ ipu_prg_put_pre(prg, prg_chan); ++ ++ chan->enabled = false; ++} ++EXPORT_SYMBOL_GPL(ipu_prg_channel_disable); ++ ++int ipu_prg_channel_configure(struct ipuv3_channel *ipu_chan, ++ unsigned int axi_id, unsigned int width, ++ unsigned int height, unsigned int stride, ++ u32 format, uint64_t modifier, unsigned long *eba) ++{ ++ int prg_chan = ipu_prg_ipu_to_prg_chan(ipu_chan->num); ++ struct ipu_prg *prg = ipu_chan->ipu->prg_priv; ++ struct ipu_prg_channel *chan; ++ u32 val; ++ int ret; ++ ++ if (prg_chan < 0) ++ return prg_chan; ++ ++ chan = &prg->chan[prg_chan]; ++ ++ if (chan->enabled) { ++ ipu_pre_update(prg->pres[chan->used_pre], *eba); ++ return 0; ++ } ++ ++ ret = ipu_prg_get_pre(prg, prg_chan); ++ if (ret) ++ return ret; ++ ++ ipu_pre_configure(prg->pres[chan->used_pre], ++ width, height, stride, format, modifier, *eba); ++ ++ ++ pm_runtime_get_sync(prg->dev); ++ ++ val = (stride - 1) & IPU_PRG_STRIDE_STRIDE_MASK; ++ writel(val, prg->regs + IPU_PRG_STRIDE(prg_chan)); ++ ++ val = ((height & IPU_PRG_HEIGHT_PRE_HEIGHT_MASK) << ++ IPU_PRG_HEIGHT_PRE_HEIGHT_SHIFT) | ++ ((height & IPU_PRG_HEIGHT_IPU_HEIGHT_MASK) << ++ IPU_PRG_HEIGHT_IPU_HEIGHT_SHIFT); ++ writel(val, prg->regs + IPU_PRG_HEIGHT(prg_chan)); ++ ++ val = ipu_pre_get_baddr(prg->pres[chan->used_pre]); ++ *eba = val; ++ writel(val, prg->regs + IPU_PRG_BADDR(prg_chan)); ++ ++ val = readl(prg->regs + IPU_PRG_CTL); ++ /* config AXI ID */ ++ val &= ~(IPU_PRG_CTL_SOFT_ARID_MASK << ++ IPU_PRG_CTL_SOFT_ARID_SHIFT(prg_chan)); ++ val |= IPU_PRG_CTL_SOFT_ARID(prg_chan, axi_id); ++ /* enable channel */ ++ val &= ~IPU_PRG_CTL_BYPASS(prg_chan); ++ writel(val, prg->regs + IPU_PRG_CTL); ++ ++ val = IPU_PRG_REG_UPDATE_REG_UPDATE; ++ writel(val, prg->regs + IPU_PRG_REG_UPDATE); ++ ++ /* wait for both double buffers to be filled */ ++ readl_poll_timeout(prg->regs + IPU_PRG_STATUS, val, ++ (val & IPU_PRG_STATUS_BUFFER0_READY(prg_chan)) && ++ (val & IPU_PRG_STATUS_BUFFER1_READY(prg_chan)), ++ 5, 1000); ++ ++ pm_runtime_put(prg->dev); ++ ++ chan->enabled = true; ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_prg_channel_configure); ++ ++bool ipu_prg_channel_configure_pending(struct ipuv3_channel *ipu_chan) ++{ ++ int prg_chan = ipu_prg_ipu_to_prg_chan(ipu_chan->num); ++ struct ipu_prg *prg = ipu_chan->ipu->prg_priv; ++ struct ipu_prg_channel *chan; ++ ++ if (prg_chan < 0) ++ return false; ++ ++ chan = &prg->chan[prg_chan]; ++ WARN_ON(!chan->enabled); ++ ++ return ipu_pre_update_pending(prg->pres[chan->used_pre]); ++} ++EXPORT_SYMBOL_GPL(ipu_prg_channel_configure_pending); ++ ++static int ipu_prg_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct resource *res; ++ struct ipu_prg *prg; ++ u32 val; ++ int i, ret; ++ ++ prg = devm_kzalloc(dev, sizeof(*prg), GFP_KERNEL); ++ if (!prg) ++ return -ENOMEM; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ prg->regs = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(prg->regs)) ++ return PTR_ERR(prg->regs); ++ ++ ++ prg->clk_ipg = devm_clk_get(dev, "ipg"); ++ if (IS_ERR(prg->clk_ipg)) ++ return PTR_ERR(prg->clk_ipg); ++ ++ prg->clk_axi = devm_clk_get(dev, "axi"); ++ if (IS_ERR(prg->clk_axi)) ++ return PTR_ERR(prg->clk_axi); ++ ++ prg->iomuxc_gpr = ++ syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); ++ if (IS_ERR(prg->iomuxc_gpr)) ++ return PTR_ERR(prg->iomuxc_gpr); ++ ++ for (i = 0; i < 3; i++) { ++ prg->pres[i] = ipu_pre_lookup_by_phandle(dev, "fsl,pres", i); ++ if (!prg->pres[i]) ++ return -EPROBE_DEFER; ++ } ++ ++ ret = clk_prepare_enable(prg->clk_ipg); ++ if (ret) ++ return ret; ++ ++ ret = clk_prepare_enable(prg->clk_axi); ++ if (ret) { ++ clk_disable_unprepare(prg->clk_ipg); ++ return ret; ++ } ++ ++ /* init to free running mode */ ++ val = readl(prg->regs + IPU_PRG_CTL); ++ val |= IPU_PRG_CTL_SHADOW_EN; ++ writel(val, prg->regs + IPU_PRG_CTL); ++ ++ /* disable address threshold */ ++ writel(0xffffffff, prg->regs + IPU_PRG_THD); ++ ++ pm_runtime_set_active(dev); ++ pm_runtime_enable(dev); ++ ++ prg->dev = dev; ++ platform_set_drvdata(pdev, prg); ++ mutex_lock(&ipu_prg_list_mutex); ++ list_add(&prg->list, &ipu_prg_list); ++ mutex_unlock(&ipu_prg_list_mutex); ++ ++ return 0; ++} ++ ++static int ipu_prg_remove(struct platform_device *pdev) ++{ ++ struct ipu_prg *prg = platform_get_drvdata(pdev); ++ ++ mutex_lock(&ipu_prg_list_mutex); ++ list_del(&prg->list); ++ mutex_unlock(&ipu_prg_list_mutex); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_PM ++static int prg_suspend(struct device *dev) ++{ ++ struct ipu_prg *prg = dev_get_drvdata(dev); ++ ++ clk_disable_unprepare(prg->clk_axi); ++ clk_disable_unprepare(prg->clk_ipg); ++ ++ return 0; ++} ++ ++static int prg_resume(struct device *dev) ++{ ++ struct ipu_prg *prg = dev_get_drvdata(dev); ++ int ret; ++ ++ ret = clk_prepare_enable(prg->clk_ipg); ++ if (ret) ++ return ret; ++ ++ ret = clk_prepare_enable(prg->clk_axi); ++ if (ret) { ++ clk_disable_unprepare(prg->clk_ipg); ++ return ret; ++ } ++ ++ return 0; ++} ++#endif ++ ++static const struct dev_pm_ops prg_pm_ops = { ++ SET_RUNTIME_PM_OPS(prg_suspend, prg_resume, NULL) ++}; ++ ++static const struct of_device_id ipu_prg_dt_ids[] = { ++ { .compatible = "fsl,imx6qp-prg", }, ++ { /* sentinel */ }, ++}; ++ ++struct platform_driver ipu_prg_drv = { ++ .probe = ipu_prg_probe, ++ .remove = ipu_prg_remove, ++ .driver = { ++ .name = "imx-ipu-prg", ++ .pm = &prg_pm_ops, ++ .of_match_table = ipu_prg_dt_ids, ++ }, ++}; +--- /dev/null ++++ b/drivers/gpu/ipu-v3/ipu-prv.h +@@ -0,0 +1,274 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2010 Sascha Hauer <s.hauer@pengutronix.de> ++ * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. ++ */ ++#ifndef __IPU_PRV_H__ ++#define __IPU_PRV_H__ ++ ++struct ipu_soc; ++ ++#include <linux/types.h> ++#include <linux/device.h> ++#include <linux/clk.h> ++#include <linux/platform_device.h> ++ ++#include <video/imx-ipu-v3.h> ++ ++#define IPU_MCU_T_DEFAULT 8 ++#define IPU_CM_IDMAC_REG_OFS 0x00008000 ++#define IPU_CM_IC_REG_OFS 0x00020000 ++#define IPU_CM_IRT_REG_OFS 0x00028000 ++#define IPU_CM_CSI0_REG_OFS 0x00030000 ++#define IPU_CM_CSI1_REG_OFS 0x00038000 ++#define IPU_CM_SMFC_REG_OFS 0x00050000 ++#define IPU_CM_DC_REG_OFS 0x00058000 ++#define IPU_CM_DMFC_REG_OFS 0x00060000 ++ ++/* Register addresses */ ++/* IPU Common registers */ ++#define IPU_CM_REG(offset) (offset) ++ ++#define IPU_CONF IPU_CM_REG(0) ++ ++#define IPU_SRM_PRI1 IPU_CM_REG(0x00a0) ++#define IPU_SRM_PRI2 IPU_CM_REG(0x00a4) ++#define IPU_FS_PROC_FLOW1 IPU_CM_REG(0x00a8) ++#define IPU_FS_PROC_FLOW2 IPU_CM_REG(0x00ac) ++#define IPU_FS_PROC_FLOW3 IPU_CM_REG(0x00b0) ++#define IPU_FS_DISP_FLOW1 IPU_CM_REG(0x00b4) ++#define IPU_FS_DISP_FLOW2 IPU_CM_REG(0x00b8) ++#define IPU_SKIP IPU_CM_REG(0x00bc) ++#define IPU_DISP_ALT_CONF IPU_CM_REG(0x00c0) ++#define IPU_DISP_GEN IPU_CM_REG(0x00c4) ++#define IPU_DISP_ALT1 IPU_CM_REG(0x00c8) ++#define IPU_DISP_ALT2 IPU_CM_REG(0x00cc) ++#define IPU_DISP_ALT3 IPU_CM_REG(0x00d0) ++#define IPU_DISP_ALT4 IPU_CM_REG(0x00d4) ++#define IPU_SNOOP IPU_CM_REG(0x00d8) ++#define IPU_MEM_RST IPU_CM_REG(0x00dc) ++#define IPU_PM IPU_CM_REG(0x00e0) ++#define IPU_GPR IPU_CM_REG(0x00e4) ++#define IPU_CHA_DB_MODE_SEL(ch) IPU_CM_REG(0x0150 + 4 * ((ch) / 32)) ++#define IPU_ALT_CHA_DB_MODE_SEL(ch) IPU_CM_REG(0x0168 + 4 * ((ch) / 32)) ++#define IPU_CHA_CUR_BUF(ch) IPU_CM_REG(0x023C + 4 * ((ch) / 32)) ++#define IPU_ALT_CUR_BUF0 IPU_CM_REG(0x0244) ++#define IPU_ALT_CUR_BUF1 IPU_CM_REG(0x0248) ++#define IPU_SRM_STAT IPU_CM_REG(0x024C) ++#define IPU_PROC_TASK_STAT IPU_CM_REG(0x0250) ++#define IPU_DISP_TASK_STAT IPU_CM_REG(0x0254) ++#define IPU_CHA_BUF0_RDY(ch) IPU_CM_REG(0x0268 + 4 * ((ch) / 32)) ++#define IPU_CHA_BUF1_RDY(ch) IPU_CM_REG(0x0270 + 4 * ((ch) / 32)) ++#define IPU_CHA_BUF2_RDY(ch) IPU_CM_REG(0x0288 + 4 * ((ch) / 32)) ++#define IPU_ALT_CHA_BUF0_RDY(ch) IPU_CM_REG(0x0278 + 4 * ((ch) / 32)) ++#define IPU_ALT_CHA_BUF1_RDY(ch) IPU_CM_REG(0x0280 + 4 * ((ch) / 32)) ++ ++#define IPU_INT_CTRL(n) IPU_CM_REG(0x003C + 4 * (n)) ++#define IPU_INT_STAT(n) IPU_CM_REG(0x0200 + 4 * (n)) ++ ++/* SRM_PRI2 */ ++#define DP_S_SRM_MODE_MASK (0x3 << 3) ++#define DP_S_SRM_MODE_NOW (0x3 << 3) ++#define DP_S_SRM_MODE_NEXT_FRAME (0x1 << 3) ++ ++/* FS_PROC_FLOW1 */ ++#define FS_PRPENC_ROT_SRC_SEL_MASK (0xf << 0) ++#define FS_PRPENC_ROT_SRC_SEL_ENC (0x7 << 0) ++#define FS_PRPVF_ROT_SRC_SEL_MASK (0xf << 8) ++#define FS_PRPVF_ROT_SRC_SEL_VF (0x8 << 8) ++#define FS_PP_SRC_SEL_MASK (0xf << 12) ++#define FS_PP_ROT_SRC_SEL_MASK (0xf << 16) ++#define FS_PP_ROT_SRC_SEL_PP (0x5 << 16) ++#define FS_VDI1_SRC_SEL_MASK (0x3 << 20) ++#define FS_VDI3_SRC_SEL_MASK (0x3 << 20) ++#define FS_PRP_SRC_SEL_MASK (0xf << 24) ++#define FS_VDI_SRC_SEL_MASK (0x3 << 28) ++#define FS_VDI_SRC_SEL_CSI_DIRECT (0x1 << 28) ++#define FS_VDI_SRC_SEL_VDOA (0x2 << 28) ++ ++/* FS_PROC_FLOW2 */ ++#define FS_PRP_ENC_DEST_SEL_MASK (0xf << 0) ++#define FS_PRP_ENC_DEST_SEL_IRT_ENC (0x1 << 0) ++#define FS_PRPVF_DEST_SEL_MASK (0xf << 4) ++#define FS_PRPVF_DEST_SEL_IRT_VF (0x1 << 4) ++#define FS_PRPVF_ROT_DEST_SEL_MASK (0xf << 8) ++#define FS_PP_DEST_SEL_MASK (0xf << 12) ++#define FS_PP_DEST_SEL_IRT_PP (0x3 << 12) ++#define FS_PP_ROT_DEST_SEL_MASK (0xf << 16) ++#define FS_PRPENC_ROT_DEST_SEL_MASK (0xf << 20) ++#define FS_PRP_DEST_SEL_MASK (0xf << 24) ++ ++#define IPU_DI0_COUNTER_RELEASE (1 << 24) ++#define IPU_DI1_COUNTER_RELEASE (1 << 25) ++ ++#define IPU_IDMAC_REG(offset) (offset) ++ ++#define IDMAC_CONF IPU_IDMAC_REG(0x0000) ++#define IDMAC_CHA_EN(ch) IPU_IDMAC_REG(0x0004 + 4 * ((ch) / 32)) ++#define IDMAC_SEP_ALPHA IPU_IDMAC_REG(0x000c) ++#define IDMAC_ALT_SEP_ALPHA IPU_IDMAC_REG(0x0010) ++#define IDMAC_CHA_PRI(ch) IPU_IDMAC_REG(0x0014 + 4 * ((ch) / 32)) ++#define IDMAC_WM_EN(ch) IPU_IDMAC_REG(0x001c + 4 * ((ch) / 32)) ++#define IDMAC_CH_LOCK_EN_1 IPU_IDMAC_REG(0x0024) ++#define IDMAC_CH_LOCK_EN_2 IPU_IDMAC_REG(0x0028) ++#define IDMAC_SUB_ADDR_0 IPU_IDMAC_REG(0x002c) ++#define IDMAC_SUB_ADDR_1 IPU_IDMAC_REG(0x0030) ++#define IDMAC_SUB_ADDR_2 IPU_IDMAC_REG(0x0034) ++#define IDMAC_BAND_EN(ch) IPU_IDMAC_REG(0x0040 + 4 * ((ch) / 32)) ++#define IDMAC_CHA_BUSY(ch) IPU_IDMAC_REG(0x0100 + 4 * ((ch) / 32)) ++ ++#define IPU_NUM_IRQS (32 * 15) ++ ++enum ipu_modules { ++ IPU_CONF_CSI0_EN = (1 << 0), ++ IPU_CONF_CSI1_EN = (1 << 1), ++ IPU_CONF_IC_EN = (1 << 2), ++ IPU_CONF_ROT_EN = (1 << 3), ++ IPU_CONF_ISP_EN = (1 << 4), ++ IPU_CONF_DP_EN = (1 << 5), ++ IPU_CONF_DI0_EN = (1 << 6), ++ IPU_CONF_DI1_EN = (1 << 7), ++ IPU_CONF_SMFC_EN = (1 << 8), ++ IPU_CONF_DC_EN = (1 << 9), ++ IPU_CONF_DMFC_EN = (1 << 10), ++ ++ IPU_CONF_VDI_EN = (1 << 12), ++ ++ IPU_CONF_IDMAC_DIS = (1 << 22), ++ ++ IPU_CONF_IC_DMFC_SEL = (1 << 25), ++ IPU_CONF_IC_DMFC_SYNC = (1 << 26), ++ IPU_CONF_VDI_DMFC_SYNC = (1 << 27), ++ ++ IPU_CONF_CSI0_DATA_SOURCE = (1 << 28), ++ IPU_CONF_CSI1_DATA_SOURCE = (1 << 29), ++ IPU_CONF_IC_INPUT = (1 << 30), ++ IPU_CONF_CSI_SEL = (1 << 31), ++}; ++ ++struct ipuv3_channel { ++ unsigned int num; ++ struct ipu_soc *ipu; ++ struct list_head list; ++}; ++ ++struct ipu_cpmem; ++struct ipu_csi; ++struct ipu_dc_priv; ++struct ipu_dmfc_priv; ++struct ipu_di; ++struct ipu_ic_priv; ++struct ipu_vdi; ++struct ipu_image_convert_priv; ++struct ipu_smfc_priv; ++struct ipu_pre; ++struct ipu_prg; ++ ++struct ipu_devtype; ++ ++struct ipu_soc { ++ struct device *dev; ++ const struct ipu_devtype *devtype; ++ enum ipuv3_type ipu_type; ++ spinlock_t lock; ++ struct mutex channel_lock; ++ struct list_head channels; ++ ++ void __iomem *cm_reg; ++ void __iomem *idmac_reg; ++ ++ int id; ++ int usecount; ++ ++ struct clk *clk; ++ ++ int irq_sync; ++ int irq_err; ++ struct irq_domain *domain; ++ ++ struct ipu_cpmem *cpmem_priv; ++ struct ipu_dc_priv *dc_priv; ++ struct ipu_dp_priv *dp_priv; ++ struct ipu_dmfc_priv *dmfc_priv; ++ struct ipu_di *di_priv[2]; ++ struct ipu_csi *csi_priv[2]; ++ struct ipu_ic_priv *ic_priv; ++ struct ipu_vdi *vdi_priv; ++ struct ipu_image_convert_priv *image_convert_priv; ++ struct ipu_smfc_priv *smfc_priv; ++ struct ipu_prg *prg_priv; ++}; ++ ++static inline u32 ipu_idmac_read(struct ipu_soc *ipu, unsigned offset) ++{ ++ return readl(ipu->idmac_reg + offset); ++} ++ ++static inline void ipu_idmac_write(struct ipu_soc *ipu, u32 value, ++ unsigned offset) ++{ ++ writel(value, ipu->idmac_reg + offset); ++} ++ ++void ipu_srm_dp_update(struct ipu_soc *ipu, bool sync); ++ ++int ipu_module_enable(struct ipu_soc *ipu, u32 mask); ++int ipu_module_disable(struct ipu_soc *ipu, u32 mask); ++ ++bool ipu_idmac_channel_busy(struct ipu_soc *ipu, unsigned int chno); ++ ++int ipu_csi_init(struct ipu_soc *ipu, struct device *dev, int id, ++ unsigned long base, u32 module, struct clk *clk_ipu); ++void ipu_csi_exit(struct ipu_soc *ipu, int id); ++ ++int ipu_ic_init(struct ipu_soc *ipu, struct device *dev, ++ unsigned long base, unsigned long tpmem_base); ++void ipu_ic_exit(struct ipu_soc *ipu); ++ ++int ipu_vdi_init(struct ipu_soc *ipu, struct device *dev, ++ unsigned long base, u32 module); ++void ipu_vdi_exit(struct ipu_soc *ipu); ++ ++int ipu_image_convert_init(struct ipu_soc *ipu, struct device *dev); ++void ipu_image_convert_exit(struct ipu_soc *ipu); ++ ++int ipu_di_init(struct ipu_soc *ipu, struct device *dev, int id, ++ unsigned long base, u32 module, struct clk *ipu_clk); ++void ipu_di_exit(struct ipu_soc *ipu, int id); ++ ++int ipu_dmfc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base, ++ struct clk *ipu_clk); ++void ipu_dmfc_exit(struct ipu_soc *ipu); ++ ++int ipu_dp_init(struct ipu_soc *ipu, struct device *dev, unsigned long base); ++void ipu_dp_exit(struct ipu_soc *ipu); ++ ++int ipu_dc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base, ++ unsigned long template_base); ++void ipu_dc_exit(struct ipu_soc *ipu); ++ ++int ipu_cpmem_init(struct ipu_soc *ipu, struct device *dev, unsigned long base); ++void ipu_cpmem_exit(struct ipu_soc *ipu); ++ ++int ipu_smfc_init(struct ipu_soc *ipu, struct device *dev, unsigned long base); ++void ipu_smfc_exit(struct ipu_soc *ipu); ++ ++struct ipu_pre *ipu_pre_lookup_by_phandle(struct device *dev, const char *name, ++ int index); ++int ipu_pre_get_available_count(void); ++int ipu_pre_get(struct ipu_pre *pre); ++void ipu_pre_put(struct ipu_pre *pre); ++u32 ipu_pre_get_baddr(struct ipu_pre *pre); ++void ipu_pre_configure(struct ipu_pre *pre, unsigned int width, ++ unsigned int height, unsigned int stride, u32 format, ++ uint64_t modifier, unsigned int bufaddr); ++void ipu_pre_update(struct ipu_pre *pre, unsigned int bufaddr); ++bool ipu_pre_update_pending(struct ipu_pre *pre); ++ ++struct ipu_prg *ipu_prg_lookup_by_phandle(struct device *dev, const char *name, ++ int ipu_id); ++ ++extern struct platform_driver ipu_pre_drv; ++extern struct platform_driver ipu_prg_drv; ++ ++#endif /* __IPU_PRV_H__ */ +--- /dev/null ++++ b/drivers/gpu/ipu-v3/ipu-smfc.c +@@ -0,0 +1,202 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved. ++ */ ++#include <linux/export.h> ++#include <linux/types.h> ++#include <linux/init.h> ++#include <linux/io.h> ++#include <linux/errno.h> ++#include <linux/spinlock.h> ++#include <linux/delay.h> ++#include <linux/clk.h> ++#include <video/imx-ipu-v3.h> ++ ++#include "ipu-prv.h" ++ ++struct ipu_smfc { ++ struct ipu_smfc_priv *priv; ++ int chno; ++ bool inuse; ++}; ++ ++struct ipu_smfc_priv { ++ void __iomem *base; ++ spinlock_t lock; ++ struct ipu_soc *ipu; ++ struct ipu_smfc channel[4]; ++ int use_count; ++}; ++ ++/*SMFC Registers */ ++#define SMFC_MAP 0x0000 ++#define SMFC_WMC 0x0004 ++#define SMFC_BS 0x0008 ++ ++int ipu_smfc_set_burstsize(struct ipu_smfc *smfc, int burstsize) ++{ ++ struct ipu_smfc_priv *priv = smfc->priv; ++ unsigned long flags; ++ u32 val, shift; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ shift = smfc->chno * 4; ++ val = readl(priv->base + SMFC_BS); ++ val &= ~(0xf << shift); ++ val |= burstsize << shift; ++ writel(val, priv->base + SMFC_BS); ++ ++ spin_unlock_irqrestore(&priv->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_smfc_set_burstsize); ++ ++int ipu_smfc_map_channel(struct ipu_smfc *smfc, int csi_id, int mipi_id) ++{ ++ struct ipu_smfc_priv *priv = smfc->priv; ++ unsigned long flags; ++ u32 val, shift; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ shift = smfc->chno * 3; ++ val = readl(priv->base + SMFC_MAP); ++ val &= ~(0x7 << shift); ++ val |= ((csi_id << 2) | mipi_id) << shift; ++ writel(val, priv->base + SMFC_MAP); ++ ++ spin_unlock_irqrestore(&priv->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_smfc_map_channel); ++ ++int ipu_smfc_set_watermark(struct ipu_smfc *smfc, u32 set_level, u32 clr_level) ++{ ++ struct ipu_smfc_priv *priv = smfc->priv; ++ unsigned long flags; ++ u32 val, shift; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ shift = smfc->chno * 6 + (smfc->chno > 1 ? 4 : 0); ++ val = readl(priv->base + SMFC_WMC); ++ val &= ~(0x3f << shift); ++ val |= ((clr_level << 3) | set_level) << shift; ++ writel(val, priv->base + SMFC_WMC); ++ ++ spin_unlock_irqrestore(&priv->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_smfc_set_watermark); ++ ++int ipu_smfc_enable(struct ipu_smfc *smfc) ++{ ++ struct ipu_smfc_priv *priv = smfc->priv; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ if (!priv->use_count) ++ ipu_module_enable(priv->ipu, IPU_CONF_SMFC_EN); ++ ++ priv->use_count++; ++ ++ spin_unlock_irqrestore(&priv->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_smfc_enable); ++ ++int ipu_smfc_disable(struct ipu_smfc *smfc) ++{ ++ struct ipu_smfc_priv *priv = smfc->priv; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ priv->use_count--; ++ ++ if (!priv->use_count) ++ ipu_module_disable(priv->ipu, IPU_CONF_SMFC_EN); ++ ++ if (priv->use_count < 0) ++ priv->use_count = 0; ++ ++ spin_unlock_irqrestore(&priv->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_smfc_disable); ++ ++struct ipu_smfc *ipu_smfc_get(struct ipu_soc *ipu, unsigned int chno) ++{ ++ struct ipu_smfc_priv *priv = ipu->smfc_priv; ++ struct ipu_smfc *smfc, *ret; ++ unsigned long flags; ++ ++ if (chno >= 4) ++ return ERR_PTR(-EINVAL); ++ ++ smfc = &priv->channel[chno]; ++ ret = smfc; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ if (smfc->inuse) { ++ ret = ERR_PTR(-EBUSY); ++ goto unlock; ++ } ++ ++ smfc->inuse = true; ++unlock: ++ spin_unlock_irqrestore(&priv->lock, flags); ++ return ret; ++} ++EXPORT_SYMBOL_GPL(ipu_smfc_get); ++ ++void ipu_smfc_put(struct ipu_smfc *smfc) ++{ ++ struct ipu_smfc_priv *priv = smfc->priv; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ smfc->inuse = false; ++ spin_unlock_irqrestore(&priv->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_smfc_put); ++ ++int ipu_smfc_init(struct ipu_soc *ipu, struct device *dev, ++ unsigned long base) ++{ ++ struct ipu_smfc_priv *priv; ++ int i; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ ipu->smfc_priv = priv; ++ spin_lock_init(&priv->lock); ++ priv->ipu = ipu; ++ ++ priv->base = devm_ioremap(dev, base, PAGE_SIZE); ++ if (!priv->base) ++ return -ENOMEM; ++ ++ for (i = 0; i < 4; i++) { ++ priv->channel[i].priv = priv; ++ priv->channel[i].chno = i; ++ } ++ ++ pr_debug("%s: ioremap 0x%08lx -> %p\n", __func__, base, priv->base); ++ ++ return 0; ++} ++ ++void ipu_smfc_exit(struct ipu_soc *ipu) ++{ ++} +--- /dev/null ++++ b/drivers/gpu/ipu-v3/ipu-vdi.c +@@ -0,0 +1,234 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (C) 2012-2016 Mentor Graphics Inc. ++ * Copyright (C) 2005-2009 Freescale Semiconductor, Inc. ++ */ ++#include <linux/io.h> ++#include "ipu-prv.h" ++ ++struct ipu_vdi { ++ void __iomem *base; ++ u32 module; ++ spinlock_t lock; ++ int use_count; ++ struct ipu_soc *ipu; ++}; ++ ++ ++/* VDI Register Offsets */ ++#define VDI_FSIZE 0x0000 ++#define VDI_C 0x0004 ++ ++/* VDI Register Fields */ ++#define VDI_C_CH_420 (0 << 1) ++#define VDI_C_CH_422 (1 << 1) ++#define VDI_C_MOT_SEL_MASK (0x3 << 2) ++#define VDI_C_MOT_SEL_FULL (2 << 2) ++#define VDI_C_MOT_SEL_LOW (1 << 2) ++#define VDI_C_MOT_SEL_MED (0 << 2) ++#define VDI_C_BURST_SIZE1_4 (3 << 4) ++#define VDI_C_BURST_SIZE2_4 (3 << 8) ++#define VDI_C_BURST_SIZE3_4 (3 << 12) ++#define VDI_C_BURST_SIZE_MASK 0xF ++#define VDI_C_BURST_SIZE1_OFFSET 4 ++#define VDI_C_BURST_SIZE2_OFFSET 8 ++#define VDI_C_BURST_SIZE3_OFFSET 12 ++#define VDI_C_VWM1_SET_1 (0 << 16) ++#define VDI_C_VWM1_SET_2 (1 << 16) ++#define VDI_C_VWM1_CLR_2 (1 << 19) ++#define VDI_C_VWM3_SET_1 (0 << 22) ++#define VDI_C_VWM3_SET_2 (1 << 22) ++#define VDI_C_VWM3_CLR_2 (1 << 25) ++#define VDI_C_TOP_FIELD_MAN_1 (1 << 30) ++#define VDI_C_TOP_FIELD_AUTO_1 (1 << 31) ++ ++static inline u32 ipu_vdi_read(struct ipu_vdi *vdi, unsigned int offset) ++{ ++ return readl(vdi->base + offset); ++} ++ ++static inline void ipu_vdi_write(struct ipu_vdi *vdi, u32 value, ++ unsigned int offset) ++{ ++ writel(value, vdi->base + offset); ++} ++ ++void ipu_vdi_set_field_order(struct ipu_vdi *vdi, v4l2_std_id std, u32 field) ++{ ++ bool top_field_0 = false; ++ unsigned long flags; ++ u32 reg; ++ ++ switch (field) { ++ case V4L2_FIELD_INTERLACED_TB: ++ case V4L2_FIELD_SEQ_TB: ++ case V4L2_FIELD_TOP: ++ top_field_0 = true; ++ break; ++ case V4L2_FIELD_INTERLACED_BT: ++ case V4L2_FIELD_SEQ_BT: ++ case V4L2_FIELD_BOTTOM: ++ top_field_0 = false; ++ break; ++ default: ++ top_field_0 = (std & V4L2_STD_525_60) ? true : false; ++ break; ++ } ++ ++ spin_lock_irqsave(&vdi->lock, flags); ++ ++ reg = ipu_vdi_read(vdi, VDI_C); ++ if (top_field_0) ++ reg &= ~(VDI_C_TOP_FIELD_MAN_1 | VDI_C_TOP_FIELD_AUTO_1); ++ else ++ reg |= VDI_C_TOP_FIELD_MAN_1 | VDI_C_TOP_FIELD_AUTO_1; ++ ipu_vdi_write(vdi, reg, VDI_C); ++ ++ spin_unlock_irqrestore(&vdi->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_vdi_set_field_order); ++ ++void ipu_vdi_set_motion(struct ipu_vdi *vdi, enum ipu_motion_sel motion_sel) ++{ ++ unsigned long flags; ++ u32 reg; ++ ++ spin_lock_irqsave(&vdi->lock, flags); ++ ++ reg = ipu_vdi_read(vdi, VDI_C); ++ ++ reg &= ~VDI_C_MOT_SEL_MASK; ++ ++ switch (motion_sel) { ++ case MED_MOTION: ++ reg |= VDI_C_MOT_SEL_MED; ++ break; ++ case HIGH_MOTION: ++ reg |= VDI_C_MOT_SEL_FULL; ++ break; ++ default: ++ reg |= VDI_C_MOT_SEL_LOW; ++ break; ++ } ++ ++ ipu_vdi_write(vdi, reg, VDI_C); ++ ++ spin_unlock_irqrestore(&vdi->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_vdi_set_motion); ++ ++void ipu_vdi_setup(struct ipu_vdi *vdi, u32 code, int xres, int yres) ++{ ++ unsigned long flags; ++ u32 pixel_fmt, reg; ++ ++ spin_lock_irqsave(&vdi->lock, flags); ++ ++ reg = ((yres - 1) << 16) | (xres - 1); ++ ipu_vdi_write(vdi, reg, VDI_FSIZE); ++ ++ /* ++ * Full motion, only vertical filter is used. ++ * Burst size is 4 accesses ++ */ ++ if (code == MEDIA_BUS_FMT_UYVY8_2X8 || ++ code == MEDIA_BUS_FMT_UYVY8_1X16 || ++ code == MEDIA_BUS_FMT_YUYV8_2X8 || ++ code == MEDIA_BUS_FMT_YUYV8_1X16) ++ pixel_fmt = VDI_C_CH_422; ++ else ++ pixel_fmt = VDI_C_CH_420; ++ ++ reg = ipu_vdi_read(vdi, VDI_C); ++ reg |= pixel_fmt; ++ reg |= VDI_C_BURST_SIZE2_4; ++ reg |= VDI_C_BURST_SIZE1_4 | VDI_C_VWM1_CLR_2; ++ reg |= VDI_C_BURST_SIZE3_4 | VDI_C_VWM3_CLR_2; ++ ipu_vdi_write(vdi, reg, VDI_C); ++ ++ spin_unlock_irqrestore(&vdi->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_vdi_setup); ++ ++void ipu_vdi_unsetup(struct ipu_vdi *vdi) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&vdi->lock, flags); ++ ipu_vdi_write(vdi, 0, VDI_FSIZE); ++ ipu_vdi_write(vdi, 0, VDI_C); ++ spin_unlock_irqrestore(&vdi->lock, flags); ++} ++EXPORT_SYMBOL_GPL(ipu_vdi_unsetup); ++ ++int ipu_vdi_enable(struct ipu_vdi *vdi) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&vdi->lock, flags); ++ ++ if (!vdi->use_count) ++ ipu_module_enable(vdi->ipu, vdi->module); ++ ++ vdi->use_count++; ++ ++ spin_unlock_irqrestore(&vdi->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_vdi_enable); ++ ++int ipu_vdi_disable(struct ipu_vdi *vdi) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&vdi->lock, flags); ++ ++ if (vdi->use_count) { ++ if (!--vdi->use_count) ++ ipu_module_disable(vdi->ipu, vdi->module); ++ } ++ ++ spin_unlock_irqrestore(&vdi->lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ipu_vdi_disable); ++ ++struct ipu_vdi *ipu_vdi_get(struct ipu_soc *ipu) ++{ ++ return ipu->vdi_priv; ++} ++EXPORT_SYMBOL_GPL(ipu_vdi_get); ++ ++void ipu_vdi_put(struct ipu_vdi *vdi) ++{ ++} ++EXPORT_SYMBOL_GPL(ipu_vdi_put); ++ ++int ipu_vdi_init(struct ipu_soc *ipu, struct device *dev, ++ unsigned long base, u32 module) ++{ ++ struct ipu_vdi *vdi; ++ ++ vdi = devm_kzalloc(dev, sizeof(*vdi), GFP_KERNEL); ++ if (!vdi) ++ return -ENOMEM; ++ ++ ipu->vdi_priv = vdi; ++ ++ spin_lock_init(&vdi->lock); ++ vdi->module = module; ++ vdi->base = devm_ioremap(dev, base, PAGE_SIZE); ++ if (!vdi->base) ++ return -ENOMEM; ++ ++ dev_dbg(dev, "VDI base: 0x%08lx remapped to %p\n", base, vdi->base); ++ vdi->ipu = ipu; ++ ++ return 0; ++} ++ ++void ipu_vdi_exit(struct ipu_soc *ipu) ++{ ++} +--- a/drivers/video/Kconfig ++++ b/drivers/video/Kconfig +@@ -15,7 +15,7 @@ source "drivers/char/agp/Kconfig" + source "drivers/gpu/vga/Kconfig" + + source "drivers/gpu/host1x/Kconfig" +-source "drivers/gpu/imx/Kconfig" ++source "drivers/gpu/ipu-v3/Kconfig" + + source "drivers/gpu/drm/Kconfig" + |