mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-25 08:21:14 +00:00
cddd459140
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>
19996 lines
539 KiB
Diff
19996 lines
539 KiB
Diff
From a7782c6e5e37b2b406221827b177c2bfcc8825cd Mon Sep 17 00:00:00 2001
|
|
From: Liu Ying <victor.liu@nxp.com>
|
|
Date: Tue, 22 Jan 2019 17:08:01 +0800
|
|
Subject: [PATCH] gpu: Move ipu-v3 to imx folder
|
|
|
|
The new imx folder may contain ipu-v3 and dpu common drivers.
|
|
|
|
Signed-off-by: Liu Ying <victor.liu@nxp.com>
|
|
[ Aisheng: fix source path ]
|
|
Signed-off-by: Dong Aisheng <aisheng.dong@nxp.com>
|
|
---
|
|
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, 9898 insertions(+), 9896 deletions(-)
|
|
create mode 100644 drivers/gpu/imx/Kconfig
|
|
create mode 100644 drivers/gpu/imx/Makefile
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/Kconfig
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/Makefile
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/ipu-common.c
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/ipu-cpmem.c
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/ipu-csi.c
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/ipu-dc.c
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/ipu-di.c
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/ipu-dmfc.c
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/ipu-dp.c
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/ipu-ic.c
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/ipu-image-convert.c
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/ipu-pre.c
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/ipu-prg.c
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/ipu-prv.h
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/ipu-smfc.c
|
|
create mode 100644 drivers/gpu/imx/ipu-v3/ipu-vdi.c
|
|
delete mode 100644 drivers/gpu/ipu-v3/Kconfig
|
|
delete mode 100644 drivers/gpu/ipu-v3/Makefile
|
|
delete mode 100644 drivers/gpu/ipu-v3/ipu-common.c
|
|
delete mode 100644 drivers/gpu/ipu-v3/ipu-cpmem.c
|
|
delete mode 100644 drivers/gpu/ipu-v3/ipu-csi.c
|
|
delete mode 100644 drivers/gpu/ipu-v3/ipu-dc.c
|
|
delete mode 100644 drivers/gpu/ipu-v3/ipu-di.c
|
|
delete mode 100644 drivers/gpu/ipu-v3/ipu-dmfc.c
|
|
delete mode 100644 drivers/gpu/ipu-v3/ipu-dp.c
|
|
delete mode 100644 drivers/gpu/ipu-v3/ipu-ic.c
|
|
delete mode 100644 drivers/gpu/ipu-v3/ipu-image-convert.c
|
|
delete mode 100644 drivers/gpu/ipu-v3/ipu-pre.c
|
|
delete mode 100644 drivers/gpu/ipu-v3/ipu-prg.c
|
|
delete mode 100644 drivers/gpu/ipu-v3/ipu-prv.h
|
|
delete mode 100644 drivers/gpu/ipu-v3/ipu-smfc.c
|
|
delete 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/
|
|
--- /dev/null
|
|
+++ b/drivers/gpu/imx/Kconfig
|
|
@@ -0,0 +1 @@
|
|
+source "drivers/gpu/imx/ipu-v3/Kconfig"
|
|
--- /dev/null
|
|
+++ b/drivers/gpu/imx/Makefile
|
|
@@ -0,0 +1 @@
|
|
+obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/
|
|
--- /dev/null
|
|
+++ b/drivers/gpu/imx/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/imx/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/imx/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/imx/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/imx/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/imx/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/imx/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/imx/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/imx/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/imx/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/imx/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/imx/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/imx/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/imx/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/imx/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/imx/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/gpu/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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)
|
|
-{
|
|
-}
|
|
--- 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/ipu-v3/Kconfig"
|
|
+source "drivers/gpu/imx/Kconfig"
|
|
|
|
source "drivers/gpu/drm/Kconfig"
|
|
|