media: nxp: Add i.MX8 ISI driver
The Image Sensing Interface (ISI) combines image processing pipelines with DMA engines to process and capture frames originating from a variety of sources. The inputs to the ISI go through Pixel Link interfaces, and their number and nature is SoC-dependent. They cover both capture interfaces (MIPI CSI-2 RX, HDMI RX) and memory inputs. Signed-off-by: Christian Hemp <c.hemp@phytec.de> Signed-off-by: Dong Aisheng <aisheng.dong@nxp.com> Signed-off-by: Guoniu Zhou <guoniu.zhou@nxp.com> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Stefan Riedmueller <s.riedmueller@phytec.de> Tested-by: Adam Ford <aford173@gmail.com> #imx8mn-beacon Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
This commit is contained in:
parent
b28e241802
commit
cf21f328fc
@ -14931,6 +14931,13 @@ F: Documentation/devicetree/bindings/clock/imx*
|
||||
F: drivers/clk/imx/
|
||||
F: include/dt-bindings/clock/imx*
|
||||
|
||||
NXP i.MX 8M ISI DRIVER
|
||||
M: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
L: linux-media@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/media/nxp,imx8-isi.yaml
|
||||
F: drivers/media/platform/nxp/imx8-isi/
|
||||
|
||||
NXP i.MX 8MQ DCSS DRIVER
|
||||
M: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
|
||||
R: Lucas Stach <l.stach@pengutronix.de>
|
||||
|
@ -28,6 +28,8 @@ config VIDEO_IMX_MIPI_CSIS
|
||||
Video4Linux2 sub-device driver for the MIPI CSI-2 CSIS receiver
|
||||
v3.3/v3.6.3 found on some i.MX7 and i.MX8 SoCs.
|
||||
|
||||
source "drivers/media/platform/nxp/imx8-isi/Kconfig"
|
||||
|
||||
# mem2mem drivers
|
||||
|
||||
config VIDEO_IMX_PXP
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
obj-y += dw100/
|
||||
obj-y += imx-jpeg/
|
||||
obj-y += imx8-isi/
|
||||
|
||||
obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o
|
||||
obj-$(CONFIG_VIDEO_IMX_MIPI_CSIS) += imx-mipi-csis.o
|
||||
|
22
drivers/media/platform/nxp/imx8-isi/Kconfig
Normal file
22
drivers/media/platform/nxp/imx8-isi/Kconfig
Normal file
@ -0,0 +1,22 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
config VIDEO_IMX8_ISI
|
||||
tristate "i.MX8 Image Sensor Interface (ISI) driver"
|
||||
depends on ARCH_MXC || COMPILE_TEST
|
||||
depends on HAS_DMA && PM
|
||||
depends on VIDEO_DEV
|
||||
select MEDIA_CONTROLLER
|
||||
select V4L2_FWNODE
|
||||
select V4L2_MEM2MEM_DEV if VIDEO_IMX8_ISI_M2M
|
||||
select VIDEO_V4L2_SUBDEV_API
|
||||
select VIDEOBUF2_DMA_CONTIG
|
||||
help
|
||||
V4L2 driver for the Image Sensor Interface (ISI) found in various
|
||||
i.MX8 SoCs.
|
||||
|
||||
config VIDEO_IMX8_ISI_M2M
|
||||
bool "i.MX8 Image Sensor Interface (ISI) memory-to-memory support"
|
||||
depends on VIDEO_IMX8_ISI
|
||||
help
|
||||
Select 'yes' here to enable support for memory-to-memory processing
|
||||
in the ISI driver.
|
8
drivers/media/platform/nxp/imx8-isi/Makefile
Normal file
8
drivers/media/platform/nxp/imx8-isi/Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
imx8-isi-y := imx8-isi-core.o imx8-isi-crossbar.o imx8-isi-hw.o \
|
||||
imx8-isi-pipe.o imx8-isi-video.o
|
||||
imx8-isi-$(CONFIG_DEBUG_FS) += imx8-isi-debug.o
|
||||
imx8-isi-$(CONFIG_VIDEO_IMX8_ISI_M2M) += imx8-isi-m2m.o
|
||||
|
||||
obj-$(CONFIG_VIDEO_IMX8_ISI) += imx8-isi.o
|
645
drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c
Normal file
645
drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c
Normal file
@ -0,0 +1,645 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright 2019-2020 NXP
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/sys_soc.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <media/media-device.h>
|
||||
#include <media/v4l2-async.h>
|
||||
#include <media/v4l2-device.h>
|
||||
#include <media/v4l2-mc.h>
|
||||
|
||||
#include "imx8-isi-core.h"
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* V4L2 async subdevs
|
||||
*/
|
||||
|
||||
struct mxc_isi_async_subdev {
|
||||
struct v4l2_async_subdev asd;
|
||||
unsigned int port;
|
||||
};
|
||||
|
||||
static inline struct mxc_isi_async_subdev *
|
||||
asd_to_mxc_isi_async_subdev(struct v4l2_async_subdev *asd)
|
||||
{
|
||||
return container_of(asd, struct mxc_isi_async_subdev, asd);
|
||||
};
|
||||
|
||||
static inline struct mxc_isi_dev *
|
||||
notifier_to_mxc_isi_dev(struct v4l2_async_notifier *n)
|
||||
{
|
||||
return container_of(n, struct mxc_isi_dev, notifier);
|
||||
};
|
||||
|
||||
static int mxc_isi_async_notifier_bound(struct v4l2_async_notifier *notifier,
|
||||
struct v4l2_subdev *sd,
|
||||
struct v4l2_async_subdev *asd)
|
||||
{
|
||||
const unsigned int link_flags = MEDIA_LNK_FL_IMMUTABLE
|
||||
| MEDIA_LNK_FL_ENABLED;
|
||||
struct mxc_isi_dev *isi = notifier_to_mxc_isi_dev(notifier);
|
||||
struct mxc_isi_async_subdev *masd = asd_to_mxc_isi_async_subdev(asd);
|
||||
struct media_pad *pad = &isi->crossbar.pads[masd->port];
|
||||
struct device_link *link;
|
||||
|
||||
dev_dbg(isi->dev, "Bound subdev %s to crossbar input %u\n", sd->name,
|
||||
masd->port);
|
||||
|
||||
/*
|
||||
* Enforce suspend/resume ordering between the source (supplier) and
|
||||
* the ISI (consumer). The source will be suspended before and resume
|
||||
* after the ISI.
|
||||
*/
|
||||
link = device_link_add(isi->dev, sd->dev, DL_FLAG_STATELESS);
|
||||
if (!link) {
|
||||
dev_err(isi->dev,
|
||||
"Failed to create device link to source %s\n", sd->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return v4l2_create_fwnode_links_to_pad(sd, pad, link_flags);
|
||||
}
|
||||
|
||||
static int mxc_isi_async_notifier_complete(struct v4l2_async_notifier *notifier)
|
||||
{
|
||||
struct mxc_isi_dev *isi = notifier_to_mxc_isi_dev(notifier);
|
||||
int ret;
|
||||
|
||||
dev_dbg(isi->dev, "All subdevs bound\n");
|
||||
|
||||
ret = v4l2_device_register_subdev_nodes(&isi->v4l2_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(isi->dev,
|
||||
"Failed to register subdev nodes: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return media_device_register(&isi->media_dev);
|
||||
}
|
||||
|
||||
static const struct v4l2_async_notifier_operations mxc_isi_async_notifier_ops = {
|
||||
.bound = mxc_isi_async_notifier_bound,
|
||||
.complete = mxc_isi_async_notifier_complete,
|
||||
};
|
||||
|
||||
static int mxc_isi_pipe_register(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = v4l2_device_register_subdev(&pipe->isi->v4l2_dev, &pipe->sd);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return mxc_isi_video_register(pipe, &pipe->isi->v4l2_dev);
|
||||
}
|
||||
|
||||
static void mxc_isi_pipe_unregister(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
mxc_isi_video_unregister(pipe);
|
||||
}
|
||||
|
||||
static int mxc_isi_v4l2_init(struct mxc_isi_dev *isi)
|
||||
{
|
||||
struct fwnode_handle *node = dev_fwnode(isi->dev);
|
||||
struct media_device *media_dev = &isi->media_dev;
|
||||
struct v4l2_device *v4l2_dev = &isi->v4l2_dev;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
/* Initialize the media device. */
|
||||
strscpy(media_dev->model, "FSL Capture Media Device",
|
||||
sizeof(media_dev->model));
|
||||
media_dev->dev = isi->dev;
|
||||
|
||||
media_device_init(media_dev);
|
||||
|
||||
/* Initialize and register the V4L2 device. */
|
||||
v4l2_dev->mdev = media_dev;
|
||||
strscpy(v4l2_dev->name, "mx8-img-md", sizeof(v4l2_dev->name));
|
||||
|
||||
ret = v4l2_device_register(isi->dev, v4l2_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(isi->dev,
|
||||
"Failed to register V4L2 device: %d\n", ret);
|
||||
goto err_media;
|
||||
}
|
||||
|
||||
/* Register the crossbar switch subdev. */
|
||||
ret = mxc_isi_crossbar_register(&isi->crossbar);
|
||||
if (ret < 0) {
|
||||
dev_err(isi->dev, "Failed to register crossbar: %d\n", ret);
|
||||
goto err_v4l2;
|
||||
}
|
||||
|
||||
/* Register the pipeline subdevs and link them to the crossbar switch. */
|
||||
for (i = 0; i < isi->pdata->num_channels; ++i) {
|
||||
struct mxc_isi_pipe *pipe = &isi->pipes[i];
|
||||
|
||||
ret = mxc_isi_pipe_register(pipe);
|
||||
if (ret < 0) {
|
||||
dev_err(isi->dev, "Failed to register pipe%u: %d\n", i,
|
||||
ret);
|
||||
goto err_v4l2;
|
||||
}
|
||||
|
||||
ret = media_create_pad_link(&isi->crossbar.sd.entity,
|
||||
isi->crossbar.num_sinks + i,
|
||||
&pipe->sd.entity,
|
||||
MXC_ISI_PIPE_PAD_SINK,
|
||||
MEDIA_LNK_FL_IMMUTABLE |
|
||||
MEDIA_LNK_FL_ENABLED);
|
||||
if (ret < 0)
|
||||
goto err_v4l2;
|
||||
}
|
||||
|
||||
/* Register the M2M device. */
|
||||
ret = mxc_isi_m2m_register(isi, v4l2_dev);
|
||||
if (ret < 0) {
|
||||
dev_err(isi->dev, "Failed to register M2M device: %d\n", ret);
|
||||
goto err_v4l2;
|
||||
}
|
||||
|
||||
/* Initialize, fill and register the async notifier. */
|
||||
v4l2_async_nf_init(&isi->notifier);
|
||||
isi->notifier.ops = &mxc_isi_async_notifier_ops;
|
||||
|
||||
for (i = 0; i < isi->pdata->num_ports; ++i) {
|
||||
struct mxc_isi_async_subdev *masd;
|
||||
struct fwnode_handle *ep;
|
||||
|
||||
ep = fwnode_graph_get_endpoint_by_id(node, i, 0,
|
||||
FWNODE_GRAPH_ENDPOINT_NEXT);
|
||||
|
||||
if (!ep)
|
||||
continue;
|
||||
|
||||
masd = v4l2_async_nf_add_fwnode_remote(&isi->notifier, ep,
|
||||
struct mxc_isi_async_subdev);
|
||||
fwnode_handle_put(ep);
|
||||
|
||||
if (IS_ERR(masd)) {
|
||||
ret = PTR_ERR(masd);
|
||||
goto err_m2m;
|
||||
}
|
||||
|
||||
masd->port = i;
|
||||
}
|
||||
|
||||
ret = v4l2_async_nf_register(v4l2_dev, &isi->notifier);
|
||||
if (ret < 0) {
|
||||
dev_err(isi->dev,
|
||||
"Failed to register async notifier: %d\n", ret);
|
||||
goto err_m2m;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_m2m:
|
||||
mxc_isi_m2m_unregister(isi);
|
||||
v4l2_async_nf_cleanup(&isi->notifier);
|
||||
err_v4l2:
|
||||
v4l2_device_unregister(v4l2_dev);
|
||||
err_media:
|
||||
media_device_cleanup(media_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mxc_isi_v4l2_cleanup(struct mxc_isi_dev *isi)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
v4l2_async_nf_unregister(&isi->notifier);
|
||||
v4l2_async_nf_cleanup(&isi->notifier);
|
||||
|
||||
v4l2_device_unregister(&isi->v4l2_dev);
|
||||
media_device_unregister(&isi->media_dev);
|
||||
|
||||
mxc_isi_m2m_unregister(isi);
|
||||
|
||||
for (i = 0; i < isi->pdata->num_channels; ++i)
|
||||
mxc_isi_pipe_unregister(&isi->pipes[i]);
|
||||
|
||||
mxc_isi_crossbar_unregister(&isi->crossbar);
|
||||
|
||||
media_device_cleanup(&isi->media_dev);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Device information
|
||||
*/
|
||||
|
||||
/* For i.MX8QM/QXP B0 ISI IER version */
|
||||
static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v0 = {
|
||||
.oflw_y_buf_en = { .offset = 16, .mask = 0x10000 },
|
||||
.oflw_u_buf_en = { .offset = 19, .mask = 0x80000 },
|
||||
.oflw_v_buf_en = { .offset = 22, .mask = 0x400000 },
|
||||
|
||||
.excs_oflw_y_buf_en = { .offset = 17, .mask = 0x20000 },
|
||||
.excs_oflw_u_buf_en = { .offset = 20, .mask = 0x100000 },
|
||||
.excs_oflw_v_buf_en = { .offset = 23, .mask = 0x800000 },
|
||||
|
||||
.panic_y_buf_en = {.offset = 18, .mask = 0x40000 },
|
||||
.panic_u_buf_en = {.offset = 21, .mask = 0x200000 },
|
||||
.panic_v_buf_en = {.offset = 24, .mask = 0x1000000 },
|
||||
};
|
||||
|
||||
/* Panic will assert when the buffers are 50% full */
|
||||
static const struct mxc_isi_set_thd mxc_imx8_isi_thd_v0 = {
|
||||
.panic_set_thd_y = { .mask = 0x03, .offset = 0, .threshold = 0x2 },
|
||||
.panic_set_thd_u = { .mask = 0x18, .offset = 3, .threshold = 0x2 },
|
||||
.panic_set_thd_v = { .mask = 0xc0, .offset = 6, .threshold = 0x2 },
|
||||
};
|
||||
|
||||
/* For i.MX8QXP C0 and i.MX8MN ISI IER version */
|
||||
static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v1 = {
|
||||
.oflw_y_buf_en = { .offset = 19, .mask = 0x80000 },
|
||||
.oflw_u_buf_en = { .offset = 21, .mask = 0x200000 },
|
||||
.oflw_v_buf_en = { .offset = 23, .mask = 0x800000 },
|
||||
|
||||
.panic_y_buf_en = {.offset = 20, .mask = 0x100000 },
|
||||
.panic_u_buf_en = {.offset = 22, .mask = 0x400000 },
|
||||
.panic_v_buf_en = {.offset = 24, .mask = 0x1000000 },
|
||||
};
|
||||
|
||||
/* For i.MX8MP ISI IER version */
|
||||
static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v2 = {
|
||||
.oflw_y_buf_en = { .offset = 18, .mask = 0x40000 },
|
||||
.oflw_u_buf_en = { .offset = 20, .mask = 0x100000 },
|
||||
.oflw_v_buf_en = { .offset = 22, .mask = 0x400000 },
|
||||
|
||||
.panic_y_buf_en = {.offset = 19, .mask = 0x80000 },
|
||||
.panic_u_buf_en = {.offset = 21, .mask = 0x200000 },
|
||||
.panic_v_buf_en = {.offset = 23, .mask = 0x800000 },
|
||||
};
|
||||
|
||||
/* Panic will assert when the buffers are 50% full */
|
||||
static const struct mxc_isi_set_thd mxc_imx8_isi_thd_v1 = {
|
||||
.panic_set_thd_y = { .mask = 0x0000f, .offset = 0, .threshold = 0x7 },
|
||||
.panic_set_thd_u = { .mask = 0x00f00, .offset = 8, .threshold = 0x7 },
|
||||
.panic_set_thd_v = { .mask = 0xf0000, .offset = 16, .threshold = 0x7 },
|
||||
};
|
||||
|
||||
static const struct clk_bulk_data mxc_imx8_clks[] = {
|
||||
{ .id = NULL },
|
||||
};
|
||||
|
||||
/* Chip C0 */
|
||||
static const struct mxc_isi_plat_data mxc_imx8_data_v0 = {
|
||||
.model = MXC_ISI_IMX8,
|
||||
.num_ports = 5,
|
||||
.num_channels = 8,
|
||||
.reg_offset = 0x10000,
|
||||
.ier_reg = &mxc_imx8_isi_ier_v0,
|
||||
.set_thd = &mxc_imx8_isi_thd_v0,
|
||||
.clks = mxc_imx8_clks,
|
||||
.num_clks = ARRAY_SIZE(mxc_imx8_clks),
|
||||
.buf_active_reverse = false,
|
||||
.has_gasket = false,
|
||||
.has_36bit_dma = false,
|
||||
};
|
||||
|
||||
static const struct mxc_isi_plat_data mxc_imx8_data_v1 = {
|
||||
.model = MXC_ISI_IMX8,
|
||||
.num_ports = 5,
|
||||
.num_channels = 8,
|
||||
.reg_offset = 0x10000,
|
||||
.ier_reg = &mxc_imx8_isi_ier_v1,
|
||||
.set_thd = &mxc_imx8_isi_thd_v1,
|
||||
.clks = mxc_imx8_clks,
|
||||
.num_clks = ARRAY_SIZE(mxc_imx8_clks),
|
||||
.buf_active_reverse = true,
|
||||
.has_gasket = false,
|
||||
.has_36bit_dma = false,
|
||||
};
|
||||
|
||||
static const struct clk_bulk_data mxc_imx8mn_clks[] = {
|
||||
{ .id = "axi" },
|
||||
{ .id = "apb" },
|
||||
};
|
||||
|
||||
static const struct mxc_isi_plat_data mxc_imx8mn_data = {
|
||||
.model = MXC_ISI_IMX8MN,
|
||||
.num_ports = 1,
|
||||
.num_channels = 1,
|
||||
.reg_offset = 0,
|
||||
.ier_reg = &mxc_imx8_isi_ier_v1,
|
||||
.set_thd = &mxc_imx8_isi_thd_v1,
|
||||
.clks = mxc_imx8mn_clks,
|
||||
.num_clks = ARRAY_SIZE(mxc_imx8mn_clks),
|
||||
.buf_active_reverse = false,
|
||||
.has_gasket = true,
|
||||
.has_36bit_dma = false,
|
||||
};
|
||||
|
||||
static const struct mxc_isi_plat_data mxc_imx8mp_data = {
|
||||
.model = MXC_ISI_IMX8MP,
|
||||
.num_ports = 2,
|
||||
.num_channels = 2,
|
||||
.reg_offset = 0x2000,
|
||||
.ier_reg = &mxc_imx8_isi_ier_v2,
|
||||
.set_thd = &mxc_imx8_isi_thd_v1,
|
||||
.clks = mxc_imx8mn_clks,
|
||||
.num_clks = ARRAY_SIZE(mxc_imx8mn_clks),
|
||||
.buf_active_reverse = true,
|
||||
.has_gasket = true,
|
||||
.has_36bit_dma = true,
|
||||
};
|
||||
|
||||
static const struct soc_device_attribute imx8_soc[] = {
|
||||
{
|
||||
.soc_id = "i.MX8QXP",
|
||||
.revision = "1.0",
|
||||
.data = &mxc_imx8_data_v0,
|
||||
}, {
|
||||
.soc_id = "i.MX8QXP",
|
||||
.revision = "1.1",
|
||||
.data = &mxc_imx8_data_v0,
|
||||
}, {
|
||||
.soc_id = "i.MX8QXP",
|
||||
.revision = "1.2",
|
||||
}, {
|
||||
.soc_id = "i.MX8QM",
|
||||
.revision = "1.0",
|
||||
.data = &mxc_imx8_data_v0,
|
||||
}, {
|
||||
.soc_id = "i.MX8QM",
|
||||
.revision = "1.1",
|
||||
.data = &mxc_imx8_data_v0,
|
||||
}, {
|
||||
.soc_id = "i.MX8MN",
|
||||
.revision = "1.0",
|
||||
}, {
|
||||
.soc_id = "i.MX8MP",
|
||||
}, {
|
||||
/* sentinel */
|
||||
}
|
||||
};
|
||||
|
||||
static int mxc_isi_get_platform_data(struct mxc_isi_dev *isi)
|
||||
|
||||
{
|
||||
const struct soc_device_attribute *match;
|
||||
|
||||
isi->pdata = of_device_get_match_data(isi->dev);
|
||||
|
||||
match = soc_device_match(imx8_soc);
|
||||
if (!match)
|
||||
return -EINVAL;
|
||||
|
||||
if (match->data)
|
||||
isi->pdata = match->data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Power management
|
||||
*/
|
||||
|
||||
static int mxc_isi_pm_suspend(struct device *dev)
|
||||
{
|
||||
struct mxc_isi_dev *isi = dev_get_drvdata(dev);
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < isi->pdata->num_channels; ++i) {
|
||||
struct mxc_isi_pipe *pipe = &isi->pipes[i];
|
||||
|
||||
mxc_isi_video_suspend(pipe);
|
||||
}
|
||||
|
||||
return pm_runtime_force_suspend(dev);
|
||||
}
|
||||
|
||||
static int mxc_isi_pm_resume(struct device *dev)
|
||||
{
|
||||
struct mxc_isi_dev *isi = dev_get_drvdata(dev);
|
||||
unsigned int i;
|
||||
int err = 0;
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_force_resume(dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < isi->pdata->num_channels; ++i) {
|
||||
struct mxc_isi_pipe *pipe = &isi->pipes[i];
|
||||
|
||||
ret = mxc_isi_video_resume(pipe);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to resume pipeline %u (%d)\n", i,
|
||||
ret);
|
||||
/*
|
||||
* Record the last error as it's as meaningful as any,
|
||||
* and continue resuming the other pipelines.
|
||||
*/
|
||||
err = ret;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int mxc_isi_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct mxc_isi_dev *isi = dev_get_drvdata(dev);
|
||||
|
||||
clk_bulk_disable_unprepare(isi->pdata->num_clks, isi->clks);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxc_isi_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct mxc_isi_dev *isi = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = clk_bulk_prepare_enable(isi->pdata->num_clks, isi->clks);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to enable clocks (%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops mxc_isi_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(mxc_isi_pm_suspend, mxc_isi_pm_resume)
|
||||
SET_RUNTIME_PM_OPS(mxc_isi_runtime_suspend, mxc_isi_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Probe, remove & driver
|
||||
*/
|
||||
|
||||
static int mxc_isi_clk_get(struct mxc_isi_dev *isi)
|
||||
{
|
||||
unsigned int size = isi->pdata->num_clks
|
||||
* sizeof(*isi->clks);
|
||||
int ret;
|
||||
|
||||
isi->clks = devm_kmalloc(isi->dev, size, GFP_KERNEL);
|
||||
if (!isi->clks)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(isi->clks, isi->pdata->clks, size);
|
||||
|
||||
ret = devm_clk_bulk_get(isi->dev, isi->pdata->num_clks,
|
||||
isi->clks);
|
||||
if (ret < 0) {
|
||||
dev_err(isi->dev, "Failed to acquire clocks: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxc_isi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct mxc_isi_dev *isi;
|
||||
unsigned int dma_size;
|
||||
unsigned int i;
|
||||
int ret = 0;
|
||||
|
||||
isi = devm_kzalloc(dev, sizeof(*isi), GFP_KERNEL);
|
||||
if (!isi)
|
||||
return -ENOMEM;
|
||||
|
||||
isi->dev = dev;
|
||||
platform_set_drvdata(pdev, isi);
|
||||
|
||||
ret = mxc_isi_get_platform_data(isi);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Can't get platform device data\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
isi->pipes = kcalloc(isi->pdata->num_channels, sizeof(isi->pipes[0]),
|
||||
GFP_KERNEL);
|
||||
if (!isi->pipes)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = mxc_isi_clk_get(isi);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to get clocks\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
isi->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(isi->regs)) {
|
||||
dev_err(dev, "Failed to get ISI register map\n");
|
||||
return PTR_ERR(isi->regs);
|
||||
}
|
||||
|
||||
if (isi->pdata->has_gasket) {
|
||||
isi->gasket = syscon_regmap_lookup_by_phandle(dev->of_node,
|
||||
"fsl,blk-ctrl");
|
||||
if (IS_ERR(isi->gasket)) {
|
||||
ret = PTR_ERR(isi->gasket);
|
||||
dev_err(dev, "failed to get gasket: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
dma_size = isi->pdata->has_36bit_dma ? 36 : 32;
|
||||
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_size));
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set DMA mask\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
ret = mxc_isi_crossbar_init(isi);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to initialize crossbar: %d\n", ret);
|
||||
goto err_pm;
|
||||
}
|
||||
|
||||
for (i = 0; i < isi->pdata->num_channels; ++i) {
|
||||
ret = mxc_isi_pipe_init(isi, i);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to initialize pipe%u: %d\n", i,
|
||||
ret);
|
||||
goto err_xbar;
|
||||
}
|
||||
}
|
||||
|
||||
ret = mxc_isi_v4l2_init(isi);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to initialize V4L2: %d\n", ret);
|
||||
goto err_xbar;
|
||||
}
|
||||
|
||||
mxc_isi_debug_init(isi);
|
||||
|
||||
return 0;
|
||||
|
||||
err_xbar:
|
||||
mxc_isi_crossbar_cleanup(&isi->crossbar);
|
||||
err_pm:
|
||||
pm_runtime_disable(isi->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mxc_isi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mxc_isi_dev *isi = platform_get_drvdata(pdev);
|
||||
unsigned int i;
|
||||
|
||||
mxc_isi_debug_cleanup(isi);
|
||||
|
||||
for (i = 0; i < isi->pdata->num_channels; ++i) {
|
||||
struct mxc_isi_pipe *pipe = &isi->pipes[i];
|
||||
|
||||
mxc_isi_pipe_cleanup(pipe);
|
||||
}
|
||||
|
||||
mxc_isi_crossbar_cleanup(&isi->crossbar);
|
||||
mxc_isi_v4l2_cleanup(isi);
|
||||
|
||||
pm_runtime_disable(isi->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id mxc_isi_of_match[] = {
|
||||
{ .compatible = "fsl,imx8-isi", .data = &mxc_imx8_data_v1 },
|
||||
{ .compatible = "fsl,imx8mn-isi", .data = &mxc_imx8mn_data },
|
||||
{ .compatible = "fsl,imx8mp-isi", .data = &mxc_imx8mp_data },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, mxc_isi_of_match);
|
||||
|
||||
static struct platform_driver mxc_isi_driver = {
|
||||
.probe = mxc_isi_probe,
|
||||
.remove = mxc_isi_remove,
|
||||
.driver = {
|
||||
.of_match_table = mxc_isi_of_match,
|
||||
.name = MXC_ISI_DRIVER_NAME,
|
||||
.pm = &mxc_isi_pm_ops,
|
||||
}
|
||||
};
|
||||
module_platform_driver(mxc_isi_driver);
|
||||
|
||||
MODULE_ALIAS("ISI");
|
||||
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
||||
MODULE_DESCRIPTION("IMX8 Image Sensing Interface driver");
|
||||
MODULE_LICENSE("GPL");
|
395
drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h
Normal file
395
drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h
Normal file
@ -0,0 +1,395 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* V4L2 Capture ISI subdev for i.MX8QXP/QM platform
|
||||
*
|
||||
* ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which
|
||||
* used to process image from camera sensor to memory or DC
|
||||
* Copyright 2019-2020 NXP
|
||||
*/
|
||||
|
||||
#ifndef __MXC_ISI_CORE_H__
|
||||
#define __MXC_ISI_CORE_H__
|
||||
|
||||
#include <linux/list.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include <media/media-device.h>
|
||||
#include <media/media-entity.h>
|
||||
#include <media/v4l2-async.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
#include <media/v4l2-dev.h>
|
||||
#include <media/v4l2-device.h>
|
||||
#include <media/v4l2-subdev.h>
|
||||
#include <media/videobuf2-core.h>
|
||||
#include <media/videobuf2-v4l2.h>
|
||||
|
||||
struct clk_bulk_data;
|
||||
struct dentry;
|
||||
struct device;
|
||||
struct media_intf_devnode;
|
||||
struct regmap;
|
||||
struct v4l2_m2m_dev;
|
||||
|
||||
/* Pipeline pads */
|
||||
#define MXC_ISI_PIPE_PAD_SINK 0
|
||||
#define MXC_ISI_PIPE_PAD_SOURCE 1
|
||||
#define MXC_ISI_PIPE_PADS_NUM 2
|
||||
|
||||
#define MXC_ISI_MIN_WIDTH 1U
|
||||
#define MXC_ISI_MIN_HEIGHT 1U
|
||||
#define MXC_ISI_MAX_WIDTH_UNCHAINED 2048U
|
||||
#define MXC_ISI_MAX_WIDTH_CHAINED 4096U
|
||||
#define MXC_ISI_MAX_HEIGHT 8191U
|
||||
|
||||
#define MXC_ISI_DEF_WIDTH 1920U
|
||||
#define MXC_ISI_DEF_HEIGHT 1080U
|
||||
#define MXC_ISI_DEF_MBUS_CODE_SINK MEDIA_BUS_FMT_UYVY8_1X16
|
||||
#define MXC_ISI_DEF_MBUS_CODE_SOURCE MEDIA_BUS_FMT_YUV8_1X24
|
||||
#define MXC_ISI_DEF_PIXEL_FORMAT V4L2_PIX_FMT_YUYV
|
||||
#define MXC_ISI_DEF_COLOR_SPACE V4L2_COLORSPACE_SRGB
|
||||
#define MXC_ISI_DEF_YCBCR_ENC V4L2_YCBCR_ENC_601
|
||||
#define MXC_ISI_DEF_QUANTIZATION V4L2_QUANTIZATION_LIM_RANGE
|
||||
#define MXC_ISI_DEF_XFER_FUNC V4L2_XFER_FUNC_SRGB
|
||||
|
||||
#define MXC_ISI_DRIVER_NAME "mxc-isi"
|
||||
#define MXC_ISI_CAPTURE "mxc-isi-cap"
|
||||
#define MXC_ISI_M2M "mxc-isi-m2m"
|
||||
#define MXC_MAX_PLANES 3
|
||||
|
||||
struct mxc_isi_dev;
|
||||
struct mxc_isi_m2m_ctx;
|
||||
|
||||
enum mxc_isi_buf_id {
|
||||
MXC_ISI_BUF1 = 0x0,
|
||||
MXC_ISI_BUF2,
|
||||
};
|
||||
|
||||
enum mxc_isi_encoding {
|
||||
MXC_ISI_ENC_RAW,
|
||||
MXC_ISI_ENC_RGB,
|
||||
MXC_ISI_ENC_YUV,
|
||||
};
|
||||
|
||||
enum mxc_isi_input_id {
|
||||
/* Inputs from the crossbar switch range from 0 to 15 */
|
||||
MXC_ISI_INPUT_MEM = 16,
|
||||
};
|
||||
|
||||
enum mxc_isi_video_type {
|
||||
MXC_ISI_VIDEO_CAP = BIT(0),
|
||||
MXC_ISI_VIDEO_M2M_OUT = BIT(1),
|
||||
MXC_ISI_VIDEO_M2M_CAP = BIT(2),
|
||||
};
|
||||
|
||||
struct mxc_isi_format_info {
|
||||
u32 mbus_code;
|
||||
u32 fourcc;
|
||||
enum mxc_isi_video_type type;
|
||||
u32 isi_in_format;
|
||||
u32 isi_out_format;
|
||||
u8 mem_planes;
|
||||
u8 color_planes;
|
||||
u8 depth[MXC_MAX_PLANES];
|
||||
u8 hsub;
|
||||
u8 vsub;
|
||||
enum mxc_isi_encoding encoding;
|
||||
};
|
||||
|
||||
struct mxc_isi_bus_format_info {
|
||||
u32 mbus_code;
|
||||
u32 output;
|
||||
u32 pads;
|
||||
enum mxc_isi_encoding encoding;
|
||||
};
|
||||
|
||||
struct mxc_isi_buffer {
|
||||
struct vb2_v4l2_buffer v4l2_buf;
|
||||
struct list_head list;
|
||||
dma_addr_t dma_addrs[3];
|
||||
enum mxc_isi_buf_id id;
|
||||
bool discard;
|
||||
};
|
||||
|
||||
struct mxc_isi_reg {
|
||||
u32 offset;
|
||||
u32 mask;
|
||||
};
|
||||
|
||||
struct mxc_isi_ier_reg {
|
||||
/* Overflow Y/U/V trigger enable*/
|
||||
struct mxc_isi_reg oflw_y_buf_en;
|
||||
struct mxc_isi_reg oflw_u_buf_en;
|
||||
struct mxc_isi_reg oflw_v_buf_en;
|
||||
|
||||
/* Excess overflow Y/U/V trigger enable*/
|
||||
struct mxc_isi_reg excs_oflw_y_buf_en;
|
||||
struct mxc_isi_reg excs_oflw_u_buf_en;
|
||||
struct mxc_isi_reg excs_oflw_v_buf_en;
|
||||
|
||||
/* Panic Y/U/V trigger enable*/
|
||||
struct mxc_isi_reg panic_y_buf_en;
|
||||
struct mxc_isi_reg panic_v_buf_en;
|
||||
struct mxc_isi_reg panic_u_buf_en;
|
||||
};
|
||||
|
||||
struct mxc_isi_panic_thd {
|
||||
u32 mask;
|
||||
u32 offset;
|
||||
u32 threshold;
|
||||
};
|
||||
|
||||
struct mxc_isi_set_thd {
|
||||
struct mxc_isi_panic_thd panic_set_thd_y;
|
||||
struct mxc_isi_panic_thd panic_set_thd_u;
|
||||
struct mxc_isi_panic_thd panic_set_thd_v;
|
||||
};
|
||||
|
||||
enum model {
|
||||
MXC_ISI_IMX8,
|
||||
MXC_ISI_IMX8MN,
|
||||
MXC_ISI_IMX8MP,
|
||||
};
|
||||
|
||||
struct mxc_isi_plat_data {
|
||||
enum model model;
|
||||
unsigned int num_ports;
|
||||
unsigned int num_channels;
|
||||
unsigned int reg_offset;
|
||||
const struct mxc_isi_ier_reg *ier_reg;
|
||||
const struct mxc_isi_set_thd *set_thd;
|
||||
const struct clk_bulk_data *clks;
|
||||
unsigned int num_clks;
|
||||
bool buf_active_reverse;
|
||||
bool has_gasket;
|
||||
bool has_36bit_dma;
|
||||
};
|
||||
|
||||
struct mxc_isi_dma_buffer {
|
||||
size_t size;
|
||||
void *addr;
|
||||
dma_addr_t dma;
|
||||
};
|
||||
|
||||
struct mxc_isi_input {
|
||||
unsigned int enable_count;
|
||||
};
|
||||
|
||||
struct mxc_isi_crossbar {
|
||||
struct mxc_isi_dev *isi;
|
||||
|
||||
unsigned int num_sinks;
|
||||
unsigned int num_sources;
|
||||
struct mxc_isi_input *inputs;
|
||||
|
||||
struct v4l2_subdev sd;
|
||||
struct media_pad *pads;
|
||||
};
|
||||
|
||||
struct mxc_isi_video {
|
||||
struct mxc_isi_pipe *pipe;
|
||||
|
||||
struct video_device vdev;
|
||||
struct media_pad pad;
|
||||
|
||||
/* Protects is_streaming, and the vdev and vb2_q operations */
|
||||
struct mutex lock;
|
||||
bool is_streaming;
|
||||
|
||||
struct v4l2_pix_format_mplane pix;
|
||||
const struct mxc_isi_format_info *fmtinfo;
|
||||
|
||||
struct {
|
||||
struct v4l2_ctrl_handler handler;
|
||||
unsigned int alpha;
|
||||
bool hflip;
|
||||
bool vflip;
|
||||
} ctrls;
|
||||
|
||||
struct vb2_queue vb2_q;
|
||||
struct mxc_isi_buffer buf_discard[3];
|
||||
struct list_head out_pending;
|
||||
struct list_head out_active;
|
||||
struct list_head out_discard;
|
||||
u32 frame_count;
|
||||
/* Protects out_pending, out_active, out_discard and frame_count */
|
||||
spinlock_t buf_lock;
|
||||
|
||||
struct mxc_isi_dma_buffer discard_buffer[MXC_MAX_PLANES];
|
||||
};
|
||||
|
||||
typedef void(*mxc_isi_pipe_irq_t)(struct mxc_isi_pipe *, u32);
|
||||
|
||||
struct mxc_isi_pipe {
|
||||
struct mxc_isi_dev *isi;
|
||||
u32 id;
|
||||
void __iomem *regs;
|
||||
|
||||
struct media_pipeline pipe;
|
||||
|
||||
struct v4l2_subdev sd;
|
||||
struct media_pad pads[MXC_ISI_PIPE_PADS_NUM];
|
||||
|
||||
struct mxc_isi_video video;
|
||||
|
||||
/*
|
||||
* Protects use_count, irq_handler, res_available, res_acquired,
|
||||
* chained_res, and the CHNL_CTRL register.
|
||||
*/
|
||||
struct mutex lock;
|
||||
unsigned int use_count;
|
||||
mxc_isi_pipe_irq_t irq_handler;
|
||||
|
||||
#define MXC_ISI_CHANNEL_RES_LINE_BUF BIT(0)
|
||||
#define MXC_ISI_CHANNEL_RES_OUTPUT_BUF BIT(1)
|
||||
u8 available_res;
|
||||
u8 acquired_res;
|
||||
u8 chained_res;
|
||||
bool chained;
|
||||
};
|
||||
|
||||
struct mxc_isi_m2m {
|
||||
struct mxc_isi_dev *isi;
|
||||
struct mxc_isi_pipe *pipe;
|
||||
|
||||
struct media_pad pad;
|
||||
struct video_device vdev;
|
||||
struct media_intf_devnode *intf;
|
||||
struct v4l2_m2m_dev *m2m_dev;
|
||||
|
||||
/* Protects last_ctx, usage_count and chained_count */
|
||||
struct mutex lock;
|
||||
|
||||
struct mxc_isi_m2m_ctx *last_ctx;
|
||||
int usage_count;
|
||||
int chained_count;
|
||||
};
|
||||
|
||||
struct mxc_isi_dev {
|
||||
struct device *dev;
|
||||
|
||||
const struct mxc_isi_plat_data *pdata;
|
||||
|
||||
void __iomem *regs;
|
||||
struct clk_bulk_data *clks;
|
||||
struct regmap *gasket;
|
||||
|
||||
struct mxc_isi_crossbar crossbar;
|
||||
struct mxc_isi_pipe *pipes;
|
||||
struct mxc_isi_m2m m2m;
|
||||
|
||||
struct media_device media_dev;
|
||||
struct v4l2_device v4l2_dev;
|
||||
struct v4l2_async_notifier notifier;
|
||||
|
||||
struct dentry *debugfs_root;
|
||||
};
|
||||
|
||||
int mxc_isi_crossbar_init(struct mxc_isi_dev *isi);
|
||||
void mxc_isi_crossbar_cleanup(struct mxc_isi_crossbar *xbar);
|
||||
int mxc_isi_crossbar_register(struct mxc_isi_crossbar *xbar);
|
||||
void mxc_isi_crossbar_unregister(struct mxc_isi_crossbar *xbar);
|
||||
|
||||
const struct mxc_isi_bus_format_info *
|
||||
mxc_isi_bus_format_by_code(u32 code, unsigned int pad);
|
||||
const struct mxc_isi_bus_format_info *
|
||||
mxc_isi_bus_format_by_index(unsigned int index, unsigned int pad);
|
||||
const struct mxc_isi_format_info *
|
||||
mxc_isi_format_by_fourcc(u32 fourcc, enum mxc_isi_video_type type);
|
||||
const struct mxc_isi_format_info *
|
||||
mxc_isi_format_enum(unsigned int index, enum mxc_isi_video_type type);
|
||||
const struct mxc_isi_format_info *
|
||||
mxc_isi_format_try(struct mxc_isi_pipe *pipe, struct v4l2_pix_format_mplane *pix,
|
||||
enum mxc_isi_video_type type);
|
||||
|
||||
int mxc_isi_pipe_init(struct mxc_isi_dev *isi, unsigned int id);
|
||||
void mxc_isi_pipe_cleanup(struct mxc_isi_pipe *pipe);
|
||||
int mxc_isi_pipe_acquire(struct mxc_isi_pipe *pipe,
|
||||
mxc_isi_pipe_irq_t irq_handler);
|
||||
void mxc_isi_pipe_release(struct mxc_isi_pipe *pipe);
|
||||
int mxc_isi_pipe_enable(struct mxc_isi_pipe *pipe);
|
||||
void mxc_isi_pipe_disable(struct mxc_isi_pipe *pipe);
|
||||
|
||||
int mxc_isi_video_register(struct mxc_isi_pipe *pipe,
|
||||
struct v4l2_device *v4l2_dev);
|
||||
void mxc_isi_video_unregister(struct mxc_isi_pipe *pipe);
|
||||
void mxc_isi_video_suspend(struct mxc_isi_pipe *pipe);
|
||||
int mxc_isi_video_resume(struct mxc_isi_pipe *pipe);
|
||||
int mxc_isi_video_queue_setup(const struct v4l2_pix_format_mplane *format,
|
||||
const struct mxc_isi_format_info *info,
|
||||
unsigned int *num_buffers,
|
||||
unsigned int *num_planes, unsigned int sizes[]);
|
||||
void mxc_isi_video_buffer_init(struct vb2_buffer *vb2, dma_addr_t dma_addrs[3],
|
||||
const struct mxc_isi_format_info *info,
|
||||
const struct v4l2_pix_format_mplane *pix);
|
||||
int mxc_isi_video_buffer_prepare(struct mxc_isi_dev *isi, struct vb2_buffer *vb2,
|
||||
const struct mxc_isi_format_info *info,
|
||||
const struct v4l2_pix_format_mplane *pix);
|
||||
|
||||
#ifdef CONFIG_VIDEO_IMX8_ISI_M2M
|
||||
int mxc_isi_m2m_register(struct mxc_isi_dev *isi, struct v4l2_device *v4l2_dev);
|
||||
int mxc_isi_m2m_unregister(struct mxc_isi_dev *isi);
|
||||
#else
|
||||
static inline int mxc_isi_m2m_register(struct mxc_isi_dev *isi,
|
||||
struct v4l2_device *v4l2_dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline int mxc_isi_m2m_unregister(struct mxc_isi_dev *isi)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int mxc_isi_channel_acquire(struct mxc_isi_pipe *pipe,
|
||||
mxc_isi_pipe_irq_t irq_handler, bool bypass);
|
||||
void mxc_isi_channel_release(struct mxc_isi_pipe *pipe);
|
||||
void mxc_isi_channel_get(struct mxc_isi_pipe *pipe);
|
||||
void mxc_isi_channel_put(struct mxc_isi_pipe *pipe);
|
||||
void mxc_isi_channel_enable(struct mxc_isi_pipe *pipe);
|
||||
void mxc_isi_channel_disable(struct mxc_isi_pipe *pipe);
|
||||
int mxc_isi_channel_chain(struct mxc_isi_pipe *pipe, bool bypass);
|
||||
void mxc_isi_channel_unchain(struct mxc_isi_pipe *pipe);
|
||||
|
||||
void mxc_isi_channel_config(struct mxc_isi_pipe *pipe,
|
||||
enum mxc_isi_input_id input,
|
||||
const struct v4l2_area *in_size,
|
||||
const struct v4l2_area *scale,
|
||||
const struct v4l2_rect *crop,
|
||||
enum mxc_isi_encoding in_encoding,
|
||||
enum mxc_isi_encoding out_encoding);
|
||||
|
||||
void mxc_isi_channel_set_input_format(struct mxc_isi_pipe *pipe,
|
||||
const struct mxc_isi_format_info *info,
|
||||
const struct v4l2_pix_format_mplane *format);
|
||||
void mxc_isi_channel_set_output_format(struct mxc_isi_pipe *pipe,
|
||||
const struct mxc_isi_format_info *info,
|
||||
struct v4l2_pix_format_mplane *format);
|
||||
void mxc_isi_channel_m2m_start(struct mxc_isi_pipe *pipe);
|
||||
|
||||
void mxc_isi_channel_set_alpha(struct mxc_isi_pipe *pipe, u8 alpha);
|
||||
void mxc_isi_channel_set_flip(struct mxc_isi_pipe *pipe, bool hflip, bool vflip);
|
||||
|
||||
void mxc_isi_channel_set_inbuf(struct mxc_isi_pipe *pipe, dma_addr_t dma_addr);
|
||||
void mxc_isi_channel_set_outbuf(struct mxc_isi_pipe *pipe,
|
||||
const dma_addr_t dma_addrs[3],
|
||||
enum mxc_isi_buf_id buf_id);
|
||||
|
||||
u32 mxc_isi_channel_irq_status(struct mxc_isi_pipe *pipe, bool clear);
|
||||
void mxc_isi_channel_irq_clear(struct mxc_isi_pipe *pipe);
|
||||
|
||||
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
||||
void mxc_isi_debug_init(struct mxc_isi_dev *isi);
|
||||
void mxc_isi_debug_cleanup(struct mxc_isi_dev *isi);
|
||||
#else
|
||||
static inline void mxc_isi_debug_init(struct mxc_isi_dev *isi)
|
||||
{
|
||||
}
|
||||
static inline void mxc_isi_debug_cleanup(struct mxc_isi_dev *isi)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __MXC_ISI_CORE_H__ */
|
529
drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c
Normal file
529
drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c
Normal file
@ -0,0 +1,529 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* i.MX8 ISI - Input crossbar switch
|
||||
*
|
||||
* Copyright (c) 2022 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <media/media-entity.h>
|
||||
#include <media/mipi-csi2.h>
|
||||
#include <media/v4l2-subdev.h>
|
||||
|
||||
#include "imx8-isi-core.h"
|
||||
|
||||
static inline struct mxc_isi_crossbar *to_isi_crossbar(struct v4l2_subdev *sd)
|
||||
{
|
||||
return container_of(sd, struct mxc_isi_crossbar, sd);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Media block control (i.MX8MN and i.MX8MP only)
|
||||
*/
|
||||
#define GASKET_BASE(n) (0x0060 + (n) * 0x30)
|
||||
|
||||
#define GASKET_CTRL 0x0000
|
||||
#define GASKET_CTRL_DATA_TYPE(dt) ((dt) << 8)
|
||||
#define GASKET_CTRL_DATA_TYPE_MASK (0x3f << 8)
|
||||
#define GASKET_CTRL_DUAL_COMP_ENABLE BIT(1)
|
||||
#define GASKET_CTRL_ENABLE BIT(0)
|
||||
|
||||
#define GASKET_HSIZE 0x0004
|
||||
#define GASKET_VSIZE 0x0008
|
||||
|
||||
static int mxc_isi_crossbar_gasket_enable(struct mxc_isi_crossbar *xbar,
|
||||
struct v4l2_subdev_state *state,
|
||||
struct v4l2_subdev *remote_sd,
|
||||
u32 remote_pad, unsigned int port)
|
||||
{
|
||||
struct mxc_isi_dev *isi = xbar->isi;
|
||||
const struct v4l2_mbus_framefmt *fmt;
|
||||
struct v4l2_mbus_frame_desc fd;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (!isi->pdata->has_gasket)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Configure and enable the gasket with the frame size and CSI-2 data
|
||||
* type. For YUV422 8-bit, enable dual component mode unconditionally,
|
||||
* to match the configuration of the CSIS.
|
||||
*/
|
||||
|
||||
ret = v4l2_subdev_call(remote_sd, pad, get_frame_desc, remote_pad, &fd);
|
||||
if (ret) {
|
||||
dev_err(isi->dev,
|
||||
"failed to get frame descriptor from '%s':%u: %d\n",
|
||||
remote_sd->name, remote_pad, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (fd.num_entries != 1) {
|
||||
dev_err(isi->dev, "invalid frame descriptor for '%s':%u\n",
|
||||
remote_sd->name, remote_pad);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
fmt = v4l2_subdev_state_get_stream_format(state, port, 0);
|
||||
if (!fmt)
|
||||
return -EINVAL;
|
||||
|
||||
regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_HSIZE, fmt->width);
|
||||
regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_VSIZE, fmt->height);
|
||||
|
||||
val = GASKET_CTRL_DATA_TYPE(fd.entry[0].bus.csi2.dt)
|
||||
| GASKET_CTRL_ENABLE;
|
||||
|
||||
if (fd.entry[0].bus.csi2.dt == MIPI_CSI2_DT_YUV422_8B)
|
||||
val |= GASKET_CTRL_DUAL_COMP_ENABLE;
|
||||
|
||||
regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_CTRL, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mxc_isi_crossbar_gasket_disable(struct mxc_isi_crossbar *xbar,
|
||||
unsigned int port)
|
||||
{
|
||||
struct mxc_isi_dev *isi = xbar->isi;
|
||||
|
||||
if (!isi->pdata->has_gasket)
|
||||
return;
|
||||
|
||||
regmap_write(isi->gasket, GASKET_BASE(port) + GASKET_CTRL, 0);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* V4L2 subdev operations
|
||||
*/
|
||||
|
||||
static const struct v4l2_mbus_framefmt mxc_isi_crossbar_default_format = {
|
||||
.code = MXC_ISI_DEF_MBUS_CODE_SINK,
|
||||
.width = MXC_ISI_DEF_WIDTH,
|
||||
.height = MXC_ISI_DEF_HEIGHT,
|
||||
.field = V4L2_FIELD_NONE,
|
||||
.colorspace = MXC_ISI_DEF_COLOR_SPACE,
|
||||
.ycbcr_enc = MXC_ISI_DEF_YCBCR_ENC,
|
||||
.quantization = MXC_ISI_DEF_QUANTIZATION,
|
||||
.xfer_func = MXC_ISI_DEF_XFER_FUNC,
|
||||
};
|
||||
|
||||
static int __mxc_isi_crossbar_set_routing(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
struct v4l2_subdev_krouting *routing)
|
||||
{
|
||||
struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
|
||||
struct v4l2_subdev_route *route;
|
||||
int ret;
|
||||
|
||||
ret = v4l2_subdev_routing_validate(sd, routing,
|
||||
V4L2_SUBDEV_ROUTING_NO_N_TO_1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* The memory input can be routed to the first pipeline only. */
|
||||
for_each_active_route(&state->routing, route) {
|
||||
if (route->sink_pad == xbar->num_sinks - 1 &&
|
||||
route->source_pad != xbar->num_sinks) {
|
||||
dev_dbg(xbar->isi->dev,
|
||||
"invalid route from memory input (%u) to pipe %u\n",
|
||||
route->sink_pad,
|
||||
route->source_pad - xbar->num_sinks);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return v4l2_subdev_set_routing_with_fmt(sd, state, routing,
|
||||
&mxc_isi_crossbar_default_format);
|
||||
}
|
||||
|
||||
static struct v4l2_subdev *
|
||||
mxc_isi_crossbar_xlate_streams(struct mxc_isi_crossbar *xbar,
|
||||
struct v4l2_subdev_state *state,
|
||||
u32 source_pad, u64 source_streams,
|
||||
u32 *__sink_pad, u64 *__sink_streams,
|
||||
u32 *remote_pad)
|
||||
{
|
||||
struct v4l2_subdev_route *route;
|
||||
struct v4l2_subdev *sd;
|
||||
struct media_pad *pad;
|
||||
u64 sink_streams = 0;
|
||||
int sink_pad = -1;
|
||||
|
||||
/*
|
||||
* Translate the source pad and streams to the sink side. The routing
|
||||
* validation forbids stream merging, so all matching entries in the
|
||||
* routing table are guaranteed to have the same sink pad.
|
||||
*
|
||||
* TODO: This is likely worth a helper function, it could perhaps be
|
||||
* supported by v4l2_subdev_state_xlate_streams() with pad1 set to -1.
|
||||
*/
|
||||
for_each_active_route(&state->routing, route) {
|
||||
if (route->source_pad != source_pad ||
|
||||
!(source_streams & BIT(route->source_stream)))
|
||||
continue;
|
||||
|
||||
sink_streams |= BIT(route->sink_stream);
|
||||
sink_pad = route->sink_pad;
|
||||
}
|
||||
|
||||
if (sink_pad < 0) {
|
||||
dev_dbg(xbar->isi->dev,
|
||||
"no stream connected to pipeline %u\n",
|
||||
source_pad - xbar->num_sinks);
|
||||
return ERR_PTR(-EPIPE);
|
||||
}
|
||||
|
||||
pad = media_pad_remote_pad_first(&xbar->pads[sink_pad]);
|
||||
sd = media_entity_to_v4l2_subdev(pad->entity);
|
||||
|
||||
if (!sd) {
|
||||
dev_dbg(xbar->isi->dev,
|
||||
"no entity connected to crossbar input %u\n",
|
||||
sink_pad);
|
||||
return ERR_PTR(-EPIPE);
|
||||
}
|
||||
|
||||
*__sink_pad = sink_pad;
|
||||
*__sink_streams = sink_streams;
|
||||
*remote_pad = pad->index;
|
||||
|
||||
return sd;
|
||||
}
|
||||
|
||||
static int mxc_isi_crossbar_init_cfg(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state)
|
||||
{
|
||||
struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
|
||||
struct v4l2_subdev_krouting routing = { };
|
||||
struct v4l2_subdev_route *routes;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Create a 1:1 mapping between pixel link inputs and outputs to
|
||||
* pipelines by default.
|
||||
*/
|
||||
routes = kcalloc(xbar->num_sources, sizeof(*routes), GFP_KERNEL);
|
||||
if (!routes)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < xbar->num_sources; ++i) {
|
||||
struct v4l2_subdev_route *route = &routes[i];
|
||||
|
||||
route->sink_pad = i;
|
||||
route->source_pad = i + xbar->num_sinks;
|
||||
route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
|
||||
};
|
||||
|
||||
routing.num_routes = xbar->num_sources;
|
||||
routing.routes = routes;
|
||||
|
||||
ret = __mxc_isi_crossbar_set_routing(sd, state, &routing);
|
||||
|
||||
kfree(routes);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mxc_isi_crossbar_enum_mbus_code(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
struct v4l2_subdev_mbus_code_enum *code)
|
||||
{
|
||||
struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
|
||||
const struct mxc_isi_bus_format_info *info;
|
||||
|
||||
if (code->pad >= xbar->num_sinks) {
|
||||
const struct v4l2_mbus_framefmt *format;
|
||||
|
||||
/*
|
||||
* The media bus code on source pads is identical to the
|
||||
* connected sink pad.
|
||||
*/
|
||||
if (code->index > 0)
|
||||
return -EINVAL;
|
||||
|
||||
format = v4l2_subdev_state_get_opposite_stream_format(state,
|
||||
code->pad,
|
||||
code->stream);
|
||||
if (!format)
|
||||
return -EINVAL;
|
||||
|
||||
code->code = format->code;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
info = mxc_isi_bus_format_by_index(code->index, MXC_ISI_PIPE_PAD_SINK);
|
||||
if (!info)
|
||||
return -EINVAL;
|
||||
|
||||
code->code = info->mbus_code;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxc_isi_crossbar_set_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
struct v4l2_subdev_format *fmt)
|
||||
{
|
||||
struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
|
||||
struct v4l2_mbus_framefmt *sink_fmt;
|
||||
struct v4l2_subdev_route *route;
|
||||
|
||||
if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
|
||||
media_pad_is_streaming(&xbar->pads[fmt->pad]))
|
||||
return -EBUSY;
|
||||
|
||||
/*
|
||||
* The source pad format is always identical to the sink pad format and
|
||||
* can't be modified.
|
||||
*/
|
||||
if (fmt->pad >= xbar->num_sinks)
|
||||
return v4l2_subdev_get_fmt(sd, state, fmt);
|
||||
|
||||
/* Validate the requested format. */
|
||||
if (!mxc_isi_bus_format_by_code(fmt->format.code, MXC_ISI_PIPE_PAD_SINK))
|
||||
fmt->format.code = MXC_ISI_DEF_MBUS_CODE_SINK;
|
||||
|
||||
fmt->format.width = clamp_t(unsigned int, fmt->format.width,
|
||||
MXC_ISI_MIN_WIDTH, MXC_ISI_MAX_WIDTH_CHAINED);
|
||||
fmt->format.height = clamp_t(unsigned int, fmt->format.height,
|
||||
MXC_ISI_MIN_HEIGHT, MXC_ISI_MAX_HEIGHT);
|
||||
fmt->format.field = V4L2_FIELD_NONE;
|
||||
|
||||
/*
|
||||
* Set the format on the sink stream and propagate it to the source
|
||||
* streams.
|
||||
*/
|
||||
sink_fmt = v4l2_subdev_state_get_stream_format(state, fmt->pad,
|
||||
fmt->stream);
|
||||
if (!sink_fmt)
|
||||
return -EINVAL;
|
||||
|
||||
*sink_fmt = fmt->format;
|
||||
|
||||
/* TODO: A format propagation helper would be useful. */
|
||||
for_each_active_route(&state->routing, route) {
|
||||
struct v4l2_mbus_framefmt *source_fmt;
|
||||
|
||||
if (route->sink_pad != fmt->pad ||
|
||||
route->sink_stream != fmt->stream)
|
||||
continue;
|
||||
|
||||
source_fmt = v4l2_subdev_state_get_stream_format(state, route->source_pad,
|
||||
route->source_stream);
|
||||
if (!source_fmt)
|
||||
return -EINVAL;
|
||||
|
||||
*source_fmt = fmt->format;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxc_isi_crossbar_set_routing(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
enum v4l2_subdev_format_whence which,
|
||||
struct v4l2_subdev_krouting *routing)
|
||||
{
|
||||
if (which == V4L2_SUBDEV_FORMAT_ACTIVE &&
|
||||
media_entity_is_streaming(&sd->entity))
|
||||
return -EBUSY;
|
||||
|
||||
return __mxc_isi_crossbar_set_routing(sd, state, routing);
|
||||
}
|
||||
|
||||
static int mxc_isi_crossbar_enable_streams(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
u32 pad, u64 streams_mask)
|
||||
{
|
||||
struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
|
||||
struct v4l2_subdev *remote_sd;
|
||||
struct mxc_isi_input *input;
|
||||
u64 sink_streams;
|
||||
u32 sink_pad;
|
||||
u32 remote_pad;
|
||||
int ret;
|
||||
|
||||
remote_sd = mxc_isi_crossbar_xlate_streams(xbar, state, pad, streams_mask,
|
||||
&sink_pad, &sink_streams,
|
||||
&remote_pad);
|
||||
if (IS_ERR(remote_sd))
|
||||
return PTR_ERR(remote_sd);
|
||||
|
||||
input = &xbar->inputs[sink_pad];
|
||||
|
||||
/*
|
||||
* TODO: Track per-stream enable counts to support multiplexed
|
||||
* streams.
|
||||
*/
|
||||
if (!input->enable_count) {
|
||||
ret = mxc_isi_crossbar_gasket_enable(xbar, state, remote_sd,
|
||||
remote_pad, sink_pad);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = v4l2_subdev_enable_streams(remote_sd, remote_pad,
|
||||
sink_streams);
|
||||
if (ret) {
|
||||
dev_err(xbar->isi->dev,
|
||||
"failed to %s streams 0x%llx on '%s':%u: %d\n",
|
||||
"enable", sink_streams, remote_sd->name,
|
||||
remote_pad, ret);
|
||||
mxc_isi_crossbar_gasket_disable(xbar, sink_pad);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
input->enable_count++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxc_isi_crossbar_disable_streams(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
u32 pad, u64 streams_mask)
|
||||
{
|
||||
struct mxc_isi_crossbar *xbar = to_isi_crossbar(sd);
|
||||
struct v4l2_subdev *remote_sd;
|
||||
struct mxc_isi_input *input;
|
||||
u64 sink_streams;
|
||||
u32 sink_pad;
|
||||
u32 remote_pad;
|
||||
int ret = 0;
|
||||
|
||||
remote_sd = mxc_isi_crossbar_xlate_streams(xbar, state, pad, streams_mask,
|
||||
&sink_pad, &sink_streams,
|
||||
&remote_pad);
|
||||
if (IS_ERR(remote_sd))
|
||||
return PTR_ERR(remote_sd);
|
||||
|
||||
input = &xbar->inputs[sink_pad];
|
||||
|
||||
input->enable_count--;
|
||||
|
||||
if (!input->enable_count) {
|
||||
ret = v4l2_subdev_disable_streams(remote_sd, remote_pad,
|
||||
sink_streams);
|
||||
if (ret)
|
||||
dev_err(xbar->isi->dev,
|
||||
"failed to %s streams 0x%llx on '%s':%u: %d\n",
|
||||
"disable", sink_streams, remote_sd->name,
|
||||
remote_pad, ret);
|
||||
|
||||
mxc_isi_crossbar_gasket_disable(xbar, sink_pad);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct v4l2_subdev_pad_ops mxc_isi_crossbar_subdev_pad_ops = {
|
||||
.init_cfg = mxc_isi_crossbar_init_cfg,
|
||||
.enum_mbus_code = mxc_isi_crossbar_enum_mbus_code,
|
||||
.get_fmt = v4l2_subdev_get_fmt,
|
||||
.set_fmt = mxc_isi_crossbar_set_fmt,
|
||||
.set_routing = mxc_isi_crossbar_set_routing,
|
||||
.enable_streams = mxc_isi_crossbar_enable_streams,
|
||||
.disable_streams = mxc_isi_crossbar_disable_streams,
|
||||
};
|
||||
|
||||
static const struct v4l2_subdev_ops mxc_isi_crossbar_subdev_ops = {
|
||||
.pad = &mxc_isi_crossbar_subdev_pad_ops,
|
||||
};
|
||||
|
||||
static const struct media_entity_operations mxc_isi_cross_entity_ops = {
|
||||
.get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
|
||||
.link_validate = v4l2_subdev_link_validate,
|
||||
.has_pad_interdep = v4l2_subdev_has_pad_interdep,
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Init & cleanup
|
||||
*/
|
||||
|
||||
int mxc_isi_crossbar_init(struct mxc_isi_dev *isi)
|
||||
{
|
||||
struct mxc_isi_crossbar *xbar = &isi->crossbar;
|
||||
struct v4l2_subdev *sd = &xbar->sd;
|
||||
unsigned int num_pads;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
xbar->isi = isi;
|
||||
|
||||
v4l2_subdev_init(sd, &mxc_isi_crossbar_subdev_ops);
|
||||
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
|
||||
strscpy(sd->name, "crossbar", sizeof(sd->name));
|
||||
sd->dev = isi->dev;
|
||||
|
||||
sd->entity.function = MEDIA_ENT_F_VID_MUX;
|
||||
sd->entity.ops = &mxc_isi_cross_entity_ops;
|
||||
|
||||
/*
|
||||
* The subdev has one sink and one source per port, plus one sink for
|
||||
* the memory input.
|
||||
*/
|
||||
xbar->num_sinks = isi->pdata->num_ports + 1;
|
||||
xbar->num_sources = isi->pdata->num_ports;
|
||||
num_pads = xbar->num_sinks + xbar->num_sources;
|
||||
|
||||
xbar->pads = kcalloc(num_pads, sizeof(*xbar->pads), GFP_KERNEL);
|
||||
if (!xbar->pads)
|
||||
return -ENOMEM;
|
||||
|
||||
xbar->inputs = kcalloc(xbar->num_sinks, sizeof(*xbar->inputs),
|
||||
GFP_KERNEL);
|
||||
if (!xbar->pads) {
|
||||
ret = -ENOMEM;
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
for (i = 0; i < xbar->num_sinks; ++i)
|
||||
xbar->pads[i].flags = MEDIA_PAD_FL_SINK;
|
||||
for (i = 0; i < xbar->num_sources; ++i)
|
||||
xbar->pads[i + xbar->num_sinks].flags = MEDIA_PAD_FL_SOURCE;
|
||||
|
||||
ret = media_entity_pads_init(&sd->entity, num_pads, xbar->pads);
|
||||
if (ret)
|
||||
goto err_free;
|
||||
|
||||
ret = v4l2_subdev_init_finalize(sd);
|
||||
if (ret < 0)
|
||||
goto err_entity;
|
||||
|
||||
return 0;
|
||||
|
||||
err_entity:
|
||||
media_entity_cleanup(&sd->entity);
|
||||
err_free:
|
||||
kfree(xbar->pads);
|
||||
kfree(xbar->inputs);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mxc_isi_crossbar_cleanup(struct mxc_isi_crossbar *xbar)
|
||||
{
|
||||
media_entity_cleanup(&xbar->sd.entity);
|
||||
kfree(xbar->pads);
|
||||
kfree(xbar->inputs);
|
||||
}
|
||||
|
||||
int mxc_isi_crossbar_register(struct mxc_isi_crossbar *xbar)
|
||||
{
|
||||
return v4l2_device_register_subdev(&xbar->isi->v4l2_dev, &xbar->sd);
|
||||
}
|
||||
|
||||
void mxc_isi_crossbar_unregister(struct mxc_isi_crossbar *xbar)
|
||||
{
|
||||
}
|
109
drivers/media/platform/nxp/imx8-isi/imx8-isi-debug.c
Normal file
109
drivers/media/platform/nxp/imx8-isi/imx8-isi-debug.c
Normal file
@ -0,0 +1,109 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright 2019-2020 NXP
|
||||
*/
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "imx8-isi-core.h"
|
||||
#include "imx8-isi-regs.h"
|
||||
|
||||
static inline u32 mxc_isi_read(struct mxc_isi_pipe *pipe, u32 reg)
|
||||
{
|
||||
return readl(pipe->regs + reg);
|
||||
}
|
||||
|
||||
static int mxc_isi_debug_dump_regs_show(struct seq_file *m, void *p)
|
||||
{
|
||||
#define MXC_ISI_DEBUG_REG(name) { name, #name }
|
||||
static const struct {
|
||||
u32 offset;
|
||||
const char * const name;
|
||||
} registers[] = {
|
||||
MXC_ISI_DEBUG_REG(CHNL_CTRL),
|
||||
MXC_ISI_DEBUG_REG(CHNL_IMG_CTRL),
|
||||
MXC_ISI_DEBUG_REG(CHNL_OUT_BUF_CTRL),
|
||||
MXC_ISI_DEBUG_REG(CHNL_IMG_CFG),
|
||||
MXC_ISI_DEBUG_REG(CHNL_IER),
|
||||
MXC_ISI_DEBUG_REG(CHNL_STS),
|
||||
MXC_ISI_DEBUG_REG(CHNL_SCALE_FACTOR),
|
||||
MXC_ISI_DEBUG_REG(CHNL_SCALE_OFFSET),
|
||||
MXC_ISI_DEBUG_REG(CHNL_CROP_ULC),
|
||||
MXC_ISI_DEBUG_REG(CHNL_CROP_LRC),
|
||||
MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF0),
|
||||
MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF1),
|
||||
MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF2),
|
||||
MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF3),
|
||||
MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF4),
|
||||
MXC_ISI_DEBUG_REG(CHNL_CSC_COEFF5),
|
||||
MXC_ISI_DEBUG_REG(CHNL_ROI_0_ALPHA),
|
||||
MXC_ISI_DEBUG_REG(CHNL_ROI_0_ULC),
|
||||
MXC_ISI_DEBUG_REG(CHNL_ROI_0_LRC),
|
||||
MXC_ISI_DEBUG_REG(CHNL_ROI_1_ALPHA),
|
||||
MXC_ISI_DEBUG_REG(CHNL_ROI_1_ULC),
|
||||
MXC_ISI_DEBUG_REG(CHNL_ROI_1_LRC),
|
||||
MXC_ISI_DEBUG_REG(CHNL_ROI_2_ALPHA),
|
||||
MXC_ISI_DEBUG_REG(CHNL_ROI_2_ULC),
|
||||
MXC_ISI_DEBUG_REG(CHNL_ROI_2_LRC),
|
||||
MXC_ISI_DEBUG_REG(CHNL_ROI_3_ALPHA),
|
||||
MXC_ISI_DEBUG_REG(CHNL_ROI_3_ULC),
|
||||
MXC_ISI_DEBUG_REG(CHNL_ROI_3_LRC),
|
||||
MXC_ISI_DEBUG_REG(CHNL_OUT_BUF1_ADDR_Y),
|
||||
MXC_ISI_DEBUG_REG(CHNL_OUT_BUF1_ADDR_U),
|
||||
MXC_ISI_DEBUG_REG(CHNL_OUT_BUF1_ADDR_V),
|
||||
MXC_ISI_DEBUG_REG(CHNL_OUT_BUF_PITCH),
|
||||
MXC_ISI_DEBUG_REG(CHNL_IN_BUF_ADDR),
|
||||
MXC_ISI_DEBUG_REG(CHNL_IN_BUF_PITCH),
|
||||
MXC_ISI_DEBUG_REG(CHNL_MEM_RD_CTRL),
|
||||
MXC_ISI_DEBUG_REG(CHNL_OUT_BUF2_ADDR_Y),
|
||||
MXC_ISI_DEBUG_REG(CHNL_OUT_BUF2_ADDR_U),
|
||||
MXC_ISI_DEBUG_REG(CHNL_OUT_BUF2_ADDR_V),
|
||||
MXC_ISI_DEBUG_REG(CHNL_SCL_IMG_CFG),
|
||||
MXC_ISI_DEBUG_REG(CHNL_FLOW_CTRL),
|
||||
};
|
||||
|
||||
struct mxc_isi_pipe *pipe = m->private;
|
||||
unsigned int i;
|
||||
|
||||
if (!pm_runtime_get_if_in_use(pipe->isi->dev))
|
||||
return 0;
|
||||
|
||||
seq_printf(m, "--- ISI pipe %u registers ---\n", pipe->id);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(registers); ++i)
|
||||
seq_printf(m, "%20s[0x%02x]: 0x%08x\n",
|
||||
registers[i].name, registers[i].offset,
|
||||
mxc_isi_read(pipe, registers[i].offset));
|
||||
|
||||
pm_runtime_put(pipe->isi->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(mxc_isi_debug_dump_regs);
|
||||
|
||||
void mxc_isi_debug_init(struct mxc_isi_dev *isi)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
isi->debugfs_root = debugfs_create_dir(dev_name(isi->dev), NULL);
|
||||
|
||||
for (i = 0; i < isi->pdata->num_channels; ++i) {
|
||||
struct mxc_isi_pipe *pipe = &isi->pipes[i];
|
||||
char name[8];
|
||||
|
||||
sprintf(name, "pipe%u", pipe->id);
|
||||
debugfs_create_file(name, 0444, isi->debugfs_root, pipe,
|
||||
&mxc_isi_debug_dump_regs_fops);
|
||||
}
|
||||
}
|
||||
|
||||
void mxc_isi_debug_cleanup(struct mxc_isi_dev *isi)
|
||||
{
|
||||
debugfs_remove_recursive(isi->debugfs_root);
|
||||
}
|
651
drivers/media/platform/nxp/imx8-isi/imx8-isi-hw.c
Normal file
651
drivers/media/platform/nxp/imx8-isi/imx8-isi-hw.c
Normal file
@ -0,0 +1,651 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright 2019-2020 NXP
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "imx8-isi-core.h"
|
||||
#include "imx8-isi-regs.h"
|
||||
|
||||
#define ISI_DOWNSCALE_THRESHOLD 0x4000
|
||||
|
||||
static inline u32 mxc_isi_read(struct mxc_isi_pipe *pipe, u32 reg)
|
||||
{
|
||||
return readl(pipe->regs + reg);
|
||||
}
|
||||
|
||||
static inline void mxc_isi_write(struct mxc_isi_pipe *pipe, u32 reg, u32 val)
|
||||
{
|
||||
writel(val, pipe->regs + reg);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Buffers & M2M operation
|
||||
*/
|
||||
|
||||
void mxc_isi_channel_set_inbuf(struct mxc_isi_pipe *pipe, dma_addr_t dma_addr)
|
||||
{
|
||||
mxc_isi_write(pipe, CHNL_IN_BUF_ADDR, dma_addr);
|
||||
#if CONFIG_ARCH_DMA_ADDR_T_64BIT
|
||||
if (pipe->isi->pdata->has_36bit_dma)
|
||||
mxc_isi_write(pipe, CHNL_IN_BUF_XTND_ADDR, dma_addr >> 32);
|
||||
#endif
|
||||
}
|
||||
|
||||
void mxc_isi_channel_set_outbuf(struct mxc_isi_pipe *pipe,
|
||||
const dma_addr_t dma_addrs[3],
|
||||
enum mxc_isi_buf_id buf_id)
|
||||
{
|
||||
int val;
|
||||
|
||||
val = mxc_isi_read(pipe, CHNL_OUT_BUF_CTRL);
|
||||
|
||||
if (buf_id == MXC_ISI_BUF1) {
|
||||
mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_Y, dma_addrs[0]);
|
||||
mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_U, dma_addrs[1]);
|
||||
mxc_isi_write(pipe, CHNL_OUT_BUF1_ADDR_V, dma_addrs[2]);
|
||||
#if CONFIG_ARCH_DMA_ADDR_T_64BIT
|
||||
if (pipe->isi->pdata->has_36bit_dma) {
|
||||
mxc_isi_write(pipe, CHNL_Y_BUF1_XTND_ADDR,
|
||||
dma_addrs[0] >> 32);
|
||||
mxc_isi_write(pipe, CHNL_U_BUF1_XTND_ADDR,
|
||||
dma_addrs[1] >> 32);
|
||||
mxc_isi_write(pipe, CHNL_V_BUF1_XTND_ADDR,
|
||||
dma_addrs[2] >> 32);
|
||||
}
|
||||
#endif
|
||||
val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR;
|
||||
} else {
|
||||
mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_Y, dma_addrs[0]);
|
||||
mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_U, dma_addrs[1]);
|
||||
mxc_isi_write(pipe, CHNL_OUT_BUF2_ADDR_V, dma_addrs[2]);
|
||||
#if CONFIG_ARCH_DMA_ADDR_T_64BIT
|
||||
if (pipe->isi->pdata->has_36bit_dma) {
|
||||
mxc_isi_write(pipe, CHNL_Y_BUF2_XTND_ADDR,
|
||||
dma_addrs[0] >> 32);
|
||||
mxc_isi_write(pipe, CHNL_U_BUF2_XTND_ADDR,
|
||||
dma_addrs[1] >> 32);
|
||||
mxc_isi_write(pipe, CHNL_V_BUF2_XTND_ADDR,
|
||||
dma_addrs[2] >> 32);
|
||||
}
|
||||
#endif
|
||||
val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR;
|
||||
}
|
||||
|
||||
mxc_isi_write(pipe, CHNL_OUT_BUF_CTRL, val);
|
||||
}
|
||||
|
||||
void mxc_isi_channel_m2m_start(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = mxc_isi_read(pipe, CHNL_MEM_RD_CTRL);
|
||||
val &= ~CHNL_MEM_RD_CTRL_READ_MEM;
|
||||
mxc_isi_write(pipe, CHNL_MEM_RD_CTRL, val);
|
||||
udelay(300);
|
||||
|
||||
val |= CHNL_MEM_RD_CTRL_READ_MEM;
|
||||
mxc_isi_write(pipe, CHNL_MEM_RD_CTRL, val);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Pipeline configuration
|
||||
*/
|
||||
|
||||
static u32 mxc_isi_channel_scaling_ratio(unsigned int from, unsigned int to,
|
||||
u32 *dec)
|
||||
{
|
||||
unsigned int ratio = from / to;
|
||||
|
||||
if (ratio < 2)
|
||||
*dec = 1;
|
||||
else if (ratio < 4)
|
||||
*dec = 2;
|
||||
else if (ratio < 8)
|
||||
*dec = 4;
|
||||
else
|
||||
*dec = 8;
|
||||
|
||||
return min_t(u32, from * 0x1000 / (to * *dec), ISI_DOWNSCALE_THRESHOLD);
|
||||
}
|
||||
|
||||
static void mxc_isi_channel_set_scaling(struct mxc_isi_pipe *pipe,
|
||||
enum mxc_isi_encoding encoding,
|
||||
const struct v4l2_area *in_size,
|
||||
const struct v4l2_area *out_size,
|
||||
bool *bypass)
|
||||
{
|
||||
u32 xscale, yscale;
|
||||
u32 decx, decy;
|
||||
u32 val;
|
||||
|
||||
dev_dbg(pipe->isi->dev, "input %ux%u, output %ux%u\n",
|
||||
in_size->width, in_size->height,
|
||||
out_size->width, out_size->height);
|
||||
|
||||
xscale = mxc_isi_channel_scaling_ratio(in_size->width, out_size->width,
|
||||
&decx);
|
||||
yscale = mxc_isi_channel_scaling_ratio(in_size->height, out_size->height,
|
||||
&decy);
|
||||
|
||||
val = mxc_isi_read(pipe, CHNL_IMG_CTRL);
|
||||
val &= ~(CHNL_IMG_CTRL_DEC_X_MASK | CHNL_IMG_CTRL_DEC_Y_MASK |
|
||||
CHNL_IMG_CTRL_YCBCR_MODE);
|
||||
|
||||
val |= CHNL_IMG_CTRL_DEC_X(ilog2(decx))
|
||||
| CHNL_IMG_CTRL_DEC_Y(ilog2(decy));
|
||||
|
||||
/*
|
||||
* Contrary to what the documentation states, YCBCR_MODE does not
|
||||
* control conversion between YCbCr and RGB, but whether the scaler
|
||||
* operates in YUV mode or in RGB mode. It must be set when the scaler
|
||||
* input is YUV.
|
||||
*/
|
||||
if (encoding == MXC_ISI_ENC_YUV)
|
||||
val |= CHNL_IMG_CTRL_YCBCR_MODE;
|
||||
|
||||
mxc_isi_write(pipe, CHNL_IMG_CTRL, val);
|
||||
|
||||
mxc_isi_write(pipe, CHNL_SCALE_FACTOR,
|
||||
CHNL_SCALE_FACTOR_Y_SCALE(yscale) |
|
||||
CHNL_SCALE_FACTOR_X_SCALE(xscale));
|
||||
|
||||
mxc_isi_write(pipe, CHNL_SCALE_OFFSET, 0);
|
||||
|
||||
mxc_isi_write(pipe, CHNL_SCL_IMG_CFG,
|
||||
CHNL_SCL_IMG_CFG_HEIGHT(out_size->height) |
|
||||
CHNL_SCL_IMG_CFG_WIDTH(out_size->width));
|
||||
|
||||
*bypass = in_size->height == out_size->height &&
|
||||
in_size->width == out_size->width;
|
||||
}
|
||||
|
||||
static void mxc_isi_channel_set_crop(struct mxc_isi_pipe *pipe,
|
||||
const struct v4l2_area *src,
|
||||
const struct v4l2_rect *dst)
|
||||
{
|
||||
u32 val, val0, val1;
|
||||
|
||||
val = mxc_isi_read(pipe, CHNL_IMG_CTRL);
|
||||
val &= ~CHNL_IMG_CTRL_CROP_EN;
|
||||
|
||||
if (src->height == dst->height && src->width == dst->width) {
|
||||
mxc_isi_write(pipe, CHNL_IMG_CTRL, val);
|
||||
return;
|
||||
}
|
||||
|
||||
val |= CHNL_IMG_CTRL_CROP_EN;
|
||||
val0 = CHNL_CROP_ULC_X(dst->left) | CHNL_CROP_ULC_Y(dst->top);
|
||||
val1 = CHNL_CROP_LRC_X(dst->width) | CHNL_CROP_LRC_Y(dst->height);
|
||||
|
||||
mxc_isi_write(pipe, CHNL_CROP_ULC, val0);
|
||||
mxc_isi_write(pipe, CHNL_CROP_LRC, val1 + val0);
|
||||
mxc_isi_write(pipe, CHNL_IMG_CTRL, val);
|
||||
}
|
||||
|
||||
/*
|
||||
* A2,A1, B1, A3, B3, B2,
|
||||
* C2, C1, D1, C3, D3, D2
|
||||
*/
|
||||
static const u32 mxc_isi_yuv2rgb_coeffs[6] = {
|
||||
/* YUV -> RGB */
|
||||
0x0000012a, 0x012a0198, 0x0730079c,
|
||||
0x0204012a, 0x01f00000, 0x01800180
|
||||
};
|
||||
|
||||
static const u32 mxc_isi_rgb2yuv_coeffs[6] = {
|
||||
/* RGB->YUV */
|
||||
0x00810041, 0x07db0019, 0x007007b6,
|
||||
0x07a20070, 0x001007ee, 0x00800080
|
||||
};
|
||||
|
||||
static void mxc_isi_channel_set_csc(struct mxc_isi_pipe *pipe,
|
||||
enum mxc_isi_encoding in_encoding,
|
||||
enum mxc_isi_encoding out_encoding,
|
||||
bool *bypass)
|
||||
{
|
||||
static const char * const encodings[] = {
|
||||
[MXC_ISI_ENC_RAW] = "RAW",
|
||||
[MXC_ISI_ENC_RGB] = "RGB",
|
||||
[MXC_ISI_ENC_YUV] = "YUV",
|
||||
};
|
||||
const u32 *coeffs;
|
||||
bool cscen = true;
|
||||
u32 val;
|
||||
|
||||
val = mxc_isi_read(pipe, CHNL_IMG_CTRL);
|
||||
val &= ~(CHNL_IMG_CTRL_CSC_BYPASS | CHNL_IMG_CTRL_CSC_MODE_MASK);
|
||||
|
||||
if (in_encoding == MXC_ISI_ENC_YUV &&
|
||||
out_encoding == MXC_ISI_ENC_RGB) {
|
||||
/* YUV2RGB */
|
||||
coeffs = mxc_isi_yuv2rgb_coeffs;
|
||||
/* YCbCr enable??? */
|
||||
val |= CHNL_IMG_CTRL_CSC_MODE(CHNL_IMG_CTRL_CSC_MODE_YCBCR2RGB);
|
||||
} else if (in_encoding == MXC_ISI_ENC_RGB &&
|
||||
out_encoding == MXC_ISI_ENC_YUV) {
|
||||
/* RGB2YUV */
|
||||
coeffs = mxc_isi_rgb2yuv_coeffs;
|
||||
val |= CHNL_IMG_CTRL_CSC_MODE(CHNL_IMG_CTRL_CSC_MODE_RGB2YCBCR);
|
||||
} else {
|
||||
/* Bypass CSC */
|
||||
cscen = false;
|
||||
val |= CHNL_IMG_CTRL_CSC_BYPASS;
|
||||
}
|
||||
|
||||
dev_dbg(pipe->isi->dev, "CSC: %s -> %s\n",
|
||||
encodings[in_encoding], encodings[out_encoding]);
|
||||
|
||||
if (cscen) {
|
||||
mxc_isi_write(pipe, CHNL_CSC_COEFF0, coeffs[0]);
|
||||
mxc_isi_write(pipe, CHNL_CSC_COEFF1, coeffs[1]);
|
||||
mxc_isi_write(pipe, CHNL_CSC_COEFF2, coeffs[2]);
|
||||
mxc_isi_write(pipe, CHNL_CSC_COEFF3, coeffs[3]);
|
||||
mxc_isi_write(pipe, CHNL_CSC_COEFF4, coeffs[4]);
|
||||
mxc_isi_write(pipe, CHNL_CSC_COEFF5, coeffs[5]);
|
||||
}
|
||||
|
||||
mxc_isi_write(pipe, CHNL_IMG_CTRL, val);
|
||||
|
||||
*bypass = !cscen;
|
||||
}
|
||||
|
||||
void mxc_isi_channel_set_alpha(struct mxc_isi_pipe *pipe, u8 alpha)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = mxc_isi_read(pipe, CHNL_IMG_CTRL);
|
||||
val &= ~CHNL_IMG_CTRL_GBL_ALPHA_VAL_MASK;
|
||||
val |= CHNL_IMG_CTRL_GBL_ALPHA_VAL(alpha) |
|
||||
CHNL_IMG_CTRL_GBL_ALPHA_EN;
|
||||
mxc_isi_write(pipe, CHNL_IMG_CTRL, val);
|
||||
}
|
||||
|
||||
void mxc_isi_channel_set_flip(struct mxc_isi_pipe *pipe, bool hflip, bool vflip)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = mxc_isi_read(pipe, CHNL_IMG_CTRL);
|
||||
val &= ~(CHNL_IMG_CTRL_VFLIP_EN | CHNL_IMG_CTRL_HFLIP_EN);
|
||||
|
||||
if (vflip)
|
||||
val |= CHNL_IMG_CTRL_VFLIP_EN;
|
||||
if (hflip)
|
||||
val |= CHNL_IMG_CTRL_HFLIP_EN;
|
||||
|
||||
mxc_isi_write(pipe, CHNL_IMG_CTRL, val);
|
||||
}
|
||||
|
||||
static void mxc_isi_channel_set_panic_threshold(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
const struct mxc_isi_set_thd *set_thd = pipe->isi->pdata->set_thd;
|
||||
u32 val;
|
||||
|
||||
val = mxc_isi_read(pipe, CHNL_OUT_BUF_CTRL);
|
||||
|
||||
val &= ~(set_thd->panic_set_thd_y.mask);
|
||||
val |= set_thd->panic_set_thd_y.threshold << set_thd->panic_set_thd_y.offset;
|
||||
|
||||
val &= ~(set_thd->panic_set_thd_u.mask);
|
||||
val |= set_thd->panic_set_thd_u.threshold << set_thd->panic_set_thd_u.offset;
|
||||
|
||||
val &= ~(set_thd->panic_set_thd_v.mask);
|
||||
val |= set_thd->panic_set_thd_v.threshold << set_thd->panic_set_thd_v.offset;
|
||||
|
||||
mxc_isi_write(pipe, CHNL_OUT_BUF_CTRL, val);
|
||||
}
|
||||
|
||||
static void mxc_isi_channel_set_control(struct mxc_isi_pipe *pipe,
|
||||
enum mxc_isi_input_id input,
|
||||
bool bypass)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
mutex_lock(&pipe->lock);
|
||||
|
||||
val = mxc_isi_read(pipe, CHNL_CTRL);
|
||||
val &= ~(CHNL_CTRL_CHNL_BYPASS | CHNL_CTRL_CHAIN_BUF_MASK |
|
||||
CHNL_CTRL_BLANK_PXL_MASK | CHNL_CTRL_SRC_TYPE_MASK |
|
||||
CHNL_CTRL_MIPI_VC_ID_MASK | CHNL_CTRL_SRC_INPUT_MASK);
|
||||
|
||||
/*
|
||||
* If no scaling or color space conversion is needed, bypass the
|
||||
* channel.
|
||||
*/
|
||||
if (bypass)
|
||||
val |= CHNL_CTRL_CHNL_BYPASS;
|
||||
|
||||
/* Chain line buffers if needed. */
|
||||
if (pipe->chained)
|
||||
val |= CHNL_CTRL_CHAIN_BUF(CHNL_CTRL_CHAIN_BUF_2_CHAIN);
|
||||
|
||||
val |= CHNL_CTRL_BLANK_PXL(0xff);
|
||||
|
||||
/* Input source (including VC configuration for CSI-2) */
|
||||
if (input == MXC_ISI_INPUT_MEM) {
|
||||
/*
|
||||
* The memory input is connected to the last port of the
|
||||
* crossbar switch, after all pixel link inputs. The SRC_INPUT
|
||||
* field controls the input selection and must be set
|
||||
* accordingly, despite being documented as ignored when using
|
||||
* the memory input in the i.MX8MP reference manual, and
|
||||
* reserved in the i.MX8MN reference manual.
|
||||
*/
|
||||
val |= CHNL_CTRL_SRC_TYPE(CHNL_CTRL_SRC_TYPE_MEMORY);
|
||||
val |= CHNL_CTRL_SRC_INPUT(pipe->isi->pdata->num_ports);
|
||||
} else {
|
||||
val |= CHNL_CTRL_SRC_TYPE(CHNL_CTRL_SRC_TYPE_DEVICE);
|
||||
val |= CHNL_CTRL_SRC_INPUT(input);
|
||||
val |= CHNL_CTRL_MIPI_VC_ID(0); /* FIXME: For CSI-2 only */
|
||||
}
|
||||
|
||||
mxc_isi_write(pipe, CHNL_CTRL, val);
|
||||
|
||||
mutex_unlock(&pipe->lock);
|
||||
}
|
||||
|
||||
void mxc_isi_channel_config(struct mxc_isi_pipe *pipe,
|
||||
enum mxc_isi_input_id input,
|
||||
const struct v4l2_area *in_size,
|
||||
const struct v4l2_area *scale,
|
||||
const struct v4l2_rect *crop,
|
||||
enum mxc_isi_encoding in_encoding,
|
||||
enum mxc_isi_encoding out_encoding)
|
||||
{
|
||||
bool csc_bypass;
|
||||
bool scaler_bypass;
|
||||
|
||||
/* Input frame size */
|
||||
mxc_isi_write(pipe, CHNL_IMG_CFG,
|
||||
CHNL_IMG_CFG_HEIGHT(in_size->height) |
|
||||
CHNL_IMG_CFG_WIDTH(in_size->width));
|
||||
|
||||
/* Scaling */
|
||||
mxc_isi_channel_set_scaling(pipe, in_encoding, in_size, scale,
|
||||
&scaler_bypass);
|
||||
mxc_isi_channel_set_crop(pipe, scale, crop);
|
||||
|
||||
/* CSC */
|
||||
mxc_isi_channel_set_csc(pipe, in_encoding, out_encoding, &csc_bypass);
|
||||
|
||||
/* Output buffer management */
|
||||
mxc_isi_channel_set_panic_threshold(pipe);
|
||||
|
||||
/* Channel control */
|
||||
mxc_isi_channel_set_control(pipe, input, csc_bypass && scaler_bypass);
|
||||
}
|
||||
|
||||
void mxc_isi_channel_set_input_format(struct mxc_isi_pipe *pipe,
|
||||
const struct mxc_isi_format_info *info,
|
||||
const struct v4l2_pix_format_mplane *format)
|
||||
{
|
||||
unsigned int bpl = format->plane_fmt[0].bytesperline;
|
||||
|
||||
mxc_isi_write(pipe, CHNL_MEM_RD_CTRL,
|
||||
CHNL_MEM_RD_CTRL_IMG_TYPE(info->isi_in_format));
|
||||
mxc_isi_write(pipe, CHNL_IN_BUF_PITCH,
|
||||
CHNL_IN_BUF_PITCH_LINE_PITCH(bpl));
|
||||
}
|
||||
|
||||
void mxc_isi_channel_set_output_format(struct mxc_isi_pipe *pipe,
|
||||
const struct mxc_isi_format_info *info,
|
||||
struct v4l2_pix_format_mplane *format)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* set outbuf format */
|
||||
dev_dbg(pipe->isi->dev, "output format %p4cc", &format->pixelformat);
|
||||
|
||||
val = mxc_isi_read(pipe, CHNL_IMG_CTRL);
|
||||
val &= ~CHNL_IMG_CTRL_FORMAT_MASK;
|
||||
val |= CHNL_IMG_CTRL_FORMAT(info->isi_out_format);
|
||||
mxc_isi_write(pipe, CHNL_IMG_CTRL, val);
|
||||
|
||||
/* line pitch */
|
||||
mxc_isi_write(pipe, CHNL_OUT_BUF_PITCH,
|
||||
format->plane_fmt[0].bytesperline);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* IRQ
|
||||
*/
|
||||
|
||||
u32 mxc_isi_channel_irq_status(struct mxc_isi_pipe *pipe, bool clear)
|
||||
{
|
||||
u32 status;
|
||||
|
||||
status = mxc_isi_read(pipe, CHNL_STS);
|
||||
if (clear)
|
||||
mxc_isi_write(pipe, CHNL_STS, status);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void mxc_isi_channel_irq_clear(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
mxc_isi_write(pipe, CHNL_STS, 0xffffffff);
|
||||
}
|
||||
|
||||
static void mxc_isi_channel_irq_enable(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
const struct mxc_isi_ier_reg *ier_reg = pipe->isi->pdata->ier_reg;
|
||||
u32 val;
|
||||
|
||||
val = CHNL_IER_FRM_RCVD_EN |
|
||||
CHNL_IER_AXI_WR_ERR_U_EN |
|
||||
CHNL_IER_AXI_WR_ERR_V_EN |
|
||||
CHNL_IER_AXI_WR_ERR_Y_EN;
|
||||
|
||||
/* Y/U/V overflow enable */
|
||||
val |= ier_reg->oflw_y_buf_en.mask |
|
||||
ier_reg->oflw_u_buf_en.mask |
|
||||
ier_reg->oflw_v_buf_en.mask;
|
||||
|
||||
/* Y/U/V excess overflow enable */
|
||||
val |= ier_reg->excs_oflw_y_buf_en.mask |
|
||||
ier_reg->excs_oflw_u_buf_en.mask |
|
||||
ier_reg->excs_oflw_v_buf_en.mask;
|
||||
|
||||
/* Y/U/V panic enable */
|
||||
val |= ier_reg->panic_y_buf_en.mask |
|
||||
ier_reg->panic_u_buf_en.mask |
|
||||
ier_reg->panic_v_buf_en.mask;
|
||||
|
||||
mxc_isi_channel_irq_clear(pipe);
|
||||
mxc_isi_write(pipe, CHNL_IER, val);
|
||||
}
|
||||
|
||||
static void mxc_isi_channel_irq_disable(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
mxc_isi_write(pipe, CHNL_IER, 0);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Init, deinit, enable, disable
|
||||
*/
|
||||
|
||||
static void mxc_isi_channel_sw_reset(struct mxc_isi_pipe *pipe, bool enable_clk)
|
||||
{
|
||||
mxc_isi_write(pipe, CHNL_CTRL, CHNL_CTRL_SW_RST);
|
||||
mdelay(5);
|
||||
mxc_isi_write(pipe, CHNL_CTRL, enable_clk ? CHNL_CTRL_CLK_EN : 0);
|
||||
}
|
||||
|
||||
static void __mxc_isi_channel_get(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
if (!pipe->use_count++)
|
||||
mxc_isi_channel_sw_reset(pipe, true);
|
||||
}
|
||||
|
||||
void mxc_isi_channel_get(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
mutex_lock(&pipe->lock);
|
||||
__mxc_isi_channel_get(pipe);
|
||||
mutex_unlock(&pipe->lock);
|
||||
}
|
||||
|
||||
static void __mxc_isi_channel_put(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
if (!--pipe->use_count)
|
||||
mxc_isi_channel_sw_reset(pipe, false);
|
||||
}
|
||||
|
||||
void mxc_isi_channel_put(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
mutex_lock(&pipe->lock);
|
||||
__mxc_isi_channel_put(pipe);
|
||||
mutex_unlock(&pipe->lock);
|
||||
}
|
||||
|
||||
void mxc_isi_channel_enable(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
mxc_isi_channel_irq_enable(pipe);
|
||||
|
||||
mutex_lock(&pipe->lock);
|
||||
|
||||
val = mxc_isi_read(pipe, CHNL_CTRL);
|
||||
val |= CHNL_CTRL_CHNL_EN;
|
||||
mxc_isi_write(pipe, CHNL_CTRL, val);
|
||||
|
||||
mutex_unlock(&pipe->lock);
|
||||
|
||||
msleep(300);
|
||||
}
|
||||
|
||||
void mxc_isi_channel_disable(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
mxc_isi_channel_irq_disable(pipe);
|
||||
|
||||
mutex_lock(&pipe->lock);
|
||||
|
||||
val = mxc_isi_read(pipe, CHNL_CTRL);
|
||||
val &= ~CHNL_CTRL_CHNL_EN;
|
||||
mxc_isi_write(pipe, CHNL_CTRL, val);
|
||||
|
||||
mutex_unlock(&pipe->lock);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Resource management & chaining
|
||||
*/
|
||||
int mxc_isi_channel_acquire(struct mxc_isi_pipe *pipe,
|
||||
mxc_isi_pipe_irq_t irq_handler, bool bypass)
|
||||
{
|
||||
u8 resources;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&pipe->lock);
|
||||
|
||||
if (pipe->irq_handler) {
|
||||
ret = -EBUSY;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure the resources we need are available. The output buffer is
|
||||
* always needed to operate the channel, the line buffer is needed only
|
||||
* when the channel isn't in bypass mode.
|
||||
*/
|
||||
resources = MXC_ISI_CHANNEL_RES_OUTPUT_BUF
|
||||
| (!bypass ? MXC_ISI_CHANNEL_RES_LINE_BUF : 0);
|
||||
if ((pipe->available_res & resources) != resources) {
|
||||
ret = -EBUSY;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/* Acquire the channel resources. */
|
||||
pipe->acquired_res = resources;
|
||||
pipe->available_res &= ~resources;
|
||||
pipe->irq_handler = irq_handler;
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&pipe->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mxc_isi_channel_release(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
mutex_lock(&pipe->lock);
|
||||
|
||||
pipe->irq_handler = NULL;
|
||||
pipe->available_res |= pipe->acquired_res;
|
||||
pipe->acquired_res = 0;
|
||||
|
||||
mutex_unlock(&pipe->lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* We currently support line buffer chaining only, for handling images with a
|
||||
* width larger than 2048 pixels.
|
||||
*
|
||||
* TODO: Support secondary line buffer for downscaling YUV420 images.
|
||||
*/
|
||||
int mxc_isi_channel_chain(struct mxc_isi_pipe *pipe, bool bypass)
|
||||
{
|
||||
/* Channel chaining requires both line and output buffer. */
|
||||
const u8 resources = MXC_ISI_CHANNEL_RES_OUTPUT_BUF
|
||||
| MXC_ISI_CHANNEL_RES_LINE_BUF;
|
||||
struct mxc_isi_pipe *chained_pipe = pipe + 1;
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* If buffer chaining is required, make sure this channel is not the
|
||||
* last one, otherwise there's no 'next' channel to chain with. This
|
||||
* should be prevented by checks in the set format handlers, but let's
|
||||
* be defensive.
|
||||
*/
|
||||
if (WARN_ON(pipe->id == pipe->isi->pdata->num_channels - 1))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&chained_pipe->lock);
|
||||
|
||||
/* Safety checks. */
|
||||
if (WARN_ON(pipe->chained || chained_pipe->chained_res)) {
|
||||
ret = -EINVAL;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if ((chained_pipe->available_res & resources) != resources) {
|
||||
ret = -EBUSY;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
pipe->chained = true;
|
||||
chained_pipe->chained_res |= resources;
|
||||
chained_pipe->available_res &= ~resources;
|
||||
|
||||
__mxc_isi_channel_get(chained_pipe);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&chained_pipe->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mxc_isi_channel_unchain(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
struct mxc_isi_pipe *chained_pipe = pipe + 1;
|
||||
|
||||
if (!pipe->chained)
|
||||
return;
|
||||
|
||||
pipe->chained = false;
|
||||
|
||||
mutex_lock(&chained_pipe->lock);
|
||||
|
||||
chained_pipe->available_res |= chained_pipe->chained_res;
|
||||
chained_pipe->chained_res = 0;
|
||||
|
||||
__mxc_isi_channel_put(chained_pipe);
|
||||
|
||||
mutex_unlock(&chained_pipe->lock);
|
||||
}
|
858
drivers/media/platform/nxp/imx8-isi/imx8-isi-m2m.c
Normal file
858
drivers/media/platform/nxp/imx8-isi/imx8-isi-m2m.c
Normal file
@ -0,0 +1,858 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* ISI V4L2 memory to memory driver for i.MX8QXP/QM platform
|
||||
*
|
||||
* ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which
|
||||
* used to process image from camera sensor or memory to memory or DC
|
||||
*
|
||||
* Copyright (c) 2019 NXP Semiconductor
|
||||
*/
|
||||
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include <media/media-entity.h>
|
||||
#include <media/v4l2-ctrls.h>
|
||||
#include <media/v4l2-device.h>
|
||||
#include <media/v4l2-event.h>
|
||||
#include <media/v4l2-fh.h>
|
||||
#include <media/v4l2-ioctl.h>
|
||||
#include <media/v4l2-mem2mem.h>
|
||||
#include <media/videobuf2-core.h>
|
||||
#include <media/videobuf2-dma-contig.h>
|
||||
|
||||
#include "imx8-isi-core.h"
|
||||
|
||||
struct mxc_isi_m2m_buffer {
|
||||
struct v4l2_m2m_buffer buf;
|
||||
dma_addr_t dma_addrs[3];
|
||||
};
|
||||
|
||||
struct mxc_isi_m2m_ctx_queue_data {
|
||||
struct v4l2_pix_format_mplane format;
|
||||
const struct mxc_isi_format_info *info;
|
||||
u32 sequence;
|
||||
};
|
||||
|
||||
struct mxc_isi_m2m_ctx {
|
||||
struct v4l2_fh fh;
|
||||
struct mxc_isi_m2m *m2m;
|
||||
|
||||
/* Protects the m2m vb2 queues */
|
||||
struct mutex vb2_lock;
|
||||
|
||||
struct {
|
||||
struct mxc_isi_m2m_ctx_queue_data out;
|
||||
struct mxc_isi_m2m_ctx_queue_data cap;
|
||||
} queues;
|
||||
|
||||
struct {
|
||||
struct v4l2_ctrl_handler handler;
|
||||
unsigned int alpha;
|
||||
bool hflip;
|
||||
bool vflip;
|
||||
} ctrls;
|
||||
|
||||
bool chained;
|
||||
};
|
||||
|
||||
static inline struct mxc_isi_m2m_buffer *
|
||||
to_isi_m2m_buffer(struct vb2_v4l2_buffer *buf)
|
||||
{
|
||||
return container_of(buf, struct mxc_isi_m2m_buffer, buf.vb);
|
||||
}
|
||||
|
||||
static inline struct mxc_isi_m2m_ctx *to_isi_m2m_ctx(struct v4l2_fh *fh)
|
||||
{
|
||||
return container_of(fh, struct mxc_isi_m2m_ctx, fh);
|
||||
}
|
||||
|
||||
static inline struct mxc_isi_m2m_ctx_queue_data *
|
||||
mxc_isi_m2m_ctx_qdata(struct mxc_isi_m2m_ctx *ctx, enum v4l2_buf_type type)
|
||||
{
|
||||
if (V4L2_TYPE_IS_OUTPUT(type))
|
||||
return &ctx->queues.out;
|
||||
else
|
||||
return &ctx->queues.cap;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* V4L2 M2M device operations
|
||||
*/
|
||||
|
||||
static void mxc_isi_m2m_frame_write_done(struct mxc_isi_pipe *pipe, u32 status)
|
||||
{
|
||||
struct mxc_isi_m2m *m2m = &pipe->isi->m2m;
|
||||
struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf;
|
||||
struct mxc_isi_m2m_ctx *ctx;
|
||||
|
||||
ctx = v4l2_m2m_get_curr_priv(m2m->m2m_dev);
|
||||
if (!ctx) {
|
||||
dev_err(m2m->isi->dev,
|
||||
"Instance released before the end of transaction\n");
|
||||
return;
|
||||
}
|
||||
|
||||
src_vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
|
||||
dst_vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
|
||||
|
||||
v4l2_m2m_buf_copy_metadata(src_vbuf, dst_vbuf, false);
|
||||
|
||||
src_vbuf->sequence = ctx->queues.out.sequence++;
|
||||
dst_vbuf->sequence = ctx->queues.cap.sequence++;
|
||||
|
||||
v4l2_m2m_buf_done(src_vbuf, VB2_BUF_STATE_DONE);
|
||||
v4l2_m2m_buf_done(dst_vbuf, VB2_BUF_STATE_DONE);
|
||||
|
||||
v4l2_m2m_job_finish(m2m->m2m_dev, ctx->fh.m2m_ctx);
|
||||
}
|
||||
|
||||
static void mxc_isi_m2m_device_run(void *priv)
|
||||
{
|
||||
struct mxc_isi_m2m_ctx *ctx = priv;
|
||||
struct mxc_isi_m2m *m2m = ctx->m2m;
|
||||
struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf;
|
||||
struct mxc_isi_m2m_buffer *src_buf, *dst_buf;
|
||||
|
||||
mxc_isi_channel_disable(m2m->pipe);
|
||||
|
||||
mutex_lock(&m2m->lock);
|
||||
|
||||
/* If the context has changed, reconfigure the channel. */
|
||||
if (m2m->last_ctx != ctx) {
|
||||
const struct v4l2_area in_size = {
|
||||
.width = ctx->queues.out.format.width,
|
||||
.height = ctx->queues.out.format.height,
|
||||
};
|
||||
const struct v4l2_area scale = {
|
||||
.width = ctx->queues.cap.format.width,
|
||||
.height = ctx->queues.cap.format.height,
|
||||
};
|
||||
const struct v4l2_rect crop = {
|
||||
.width = ctx->queues.cap.format.width,
|
||||
.height = ctx->queues.cap.format.height,
|
||||
};
|
||||
|
||||
mxc_isi_channel_config(m2m->pipe, MXC_ISI_INPUT_MEM,
|
||||
&in_size, &scale, &crop,
|
||||
ctx->queues.out.info->encoding,
|
||||
ctx->queues.cap.info->encoding);
|
||||
mxc_isi_channel_set_input_format(m2m->pipe,
|
||||
ctx->queues.out.info,
|
||||
&ctx->queues.out.format);
|
||||
mxc_isi_channel_set_output_format(m2m->pipe,
|
||||
ctx->queues.cap.info,
|
||||
&ctx->queues.cap.format);
|
||||
|
||||
m2m->last_ctx = ctx;
|
||||
}
|
||||
|
||||
mutex_unlock(&m2m->lock);
|
||||
|
||||
mutex_lock(ctx->ctrls.handler.lock);
|
||||
mxc_isi_channel_set_alpha(m2m->pipe, ctx->ctrls.alpha);
|
||||
mxc_isi_channel_set_flip(m2m->pipe, ctx->ctrls.hflip, ctx->ctrls.vflip);
|
||||
mutex_unlock(ctx->ctrls.handler.lock);
|
||||
|
||||
src_vbuf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
|
||||
dst_vbuf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
|
||||
|
||||
src_buf = to_isi_m2m_buffer(src_vbuf);
|
||||
dst_buf = to_isi_m2m_buffer(dst_vbuf);
|
||||
|
||||
mxc_isi_channel_set_inbuf(m2m->pipe, src_buf->dma_addrs[0]);
|
||||
mxc_isi_channel_set_outbuf(m2m->pipe, dst_buf->dma_addrs, MXC_ISI_BUF1);
|
||||
mxc_isi_channel_set_outbuf(m2m->pipe, dst_buf->dma_addrs, MXC_ISI_BUF2);
|
||||
|
||||
mxc_isi_channel_enable(m2m->pipe);
|
||||
|
||||
mxc_isi_channel_m2m_start(m2m->pipe);
|
||||
}
|
||||
|
||||
static const struct v4l2_m2m_ops mxc_isi_m2m_ops = {
|
||||
.device_run = mxc_isi_m2m_device_run,
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* videobuf2 queue operations
|
||||
*/
|
||||
|
||||
static int mxc_isi_m2m_vb2_queue_setup(struct vb2_queue *q,
|
||||
unsigned int *num_buffers,
|
||||
unsigned int *num_planes,
|
||||
unsigned int sizes[],
|
||||
struct device *alloc_devs[])
|
||||
{
|
||||
struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q);
|
||||
const struct mxc_isi_m2m_ctx_queue_data *qdata =
|
||||
mxc_isi_m2m_ctx_qdata(ctx, q->type);
|
||||
|
||||
return mxc_isi_video_queue_setup(&qdata->format, qdata->info,
|
||||
num_buffers, num_planes, sizes);
|
||||
}
|
||||
|
||||
static int mxc_isi_m2m_vb2_buffer_init(struct vb2_buffer *vb2)
|
||||
{
|
||||
struct vb2_queue *vq = vb2->vb2_queue;
|
||||
struct mxc_isi_m2m_buffer *buf = to_isi_m2m_buffer(to_vb2_v4l2_buffer(vb2));
|
||||
struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(vb2->vb2_queue);
|
||||
const struct mxc_isi_m2m_ctx_queue_data *qdata =
|
||||
mxc_isi_m2m_ctx_qdata(ctx, vq->type);
|
||||
|
||||
mxc_isi_video_buffer_init(vb2, buf->dma_addrs, qdata->info,
|
||||
&qdata->format);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxc_isi_m2m_vb2_buffer_prepare(struct vb2_buffer *vb2)
|
||||
{
|
||||
struct vb2_queue *vq = vb2->vb2_queue;
|
||||
struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(vq);
|
||||
const struct mxc_isi_m2m_ctx_queue_data *qdata =
|
||||
mxc_isi_m2m_ctx_qdata(ctx, vq->type);
|
||||
|
||||
return mxc_isi_video_buffer_prepare(ctx->m2m->isi, vb2, qdata->info,
|
||||
&qdata->format);
|
||||
}
|
||||
|
||||
static void mxc_isi_m2m_vb2_buffer_queue(struct vb2_buffer *vb2)
|
||||
{
|
||||
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2);
|
||||
struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(vb2->vb2_queue);
|
||||
|
||||
v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
|
||||
}
|
||||
|
||||
static int mxc_isi_m2m_vb2_start_streaming(struct vb2_queue *q,
|
||||
unsigned int count)
|
||||
{
|
||||
struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q);
|
||||
struct mxc_isi_m2m_ctx_queue_data *qdata =
|
||||
mxc_isi_m2m_ctx_qdata(ctx, q->type);
|
||||
|
||||
qdata->sequence = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mxc_isi_m2m_vb2_stop_streaming(struct vb2_queue *q)
|
||||
{
|
||||
struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q);
|
||||
struct vb2_v4l2_buffer *vbuf;
|
||||
|
||||
for (;;) {
|
||||
if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
|
||||
vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
|
||||
else
|
||||
vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
|
||||
if (!vbuf)
|
||||
break;
|
||||
|
||||
v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct vb2_ops mxc_isi_m2m_vb2_qops = {
|
||||
.queue_setup = mxc_isi_m2m_vb2_queue_setup,
|
||||
.buf_init = mxc_isi_m2m_vb2_buffer_init,
|
||||
.buf_prepare = mxc_isi_m2m_vb2_buffer_prepare,
|
||||
.buf_queue = mxc_isi_m2m_vb2_buffer_queue,
|
||||
.wait_prepare = vb2_ops_wait_prepare,
|
||||
.wait_finish = vb2_ops_wait_finish,
|
||||
.start_streaming = mxc_isi_m2m_vb2_start_streaming,
|
||||
.stop_streaming = mxc_isi_m2m_vb2_stop_streaming,
|
||||
};
|
||||
|
||||
static int mxc_isi_m2m_queue_init(void *priv, struct vb2_queue *src_vq,
|
||||
struct vb2_queue *dst_vq)
|
||||
{
|
||||
struct mxc_isi_m2m_ctx *ctx = priv;
|
||||
struct mxc_isi_m2m *m2m = ctx->m2m;
|
||||
int ret;
|
||||
|
||||
src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
|
||||
src_vq->drv_priv = ctx;
|
||||
src_vq->buf_struct_size = sizeof(struct mxc_isi_m2m_buffer);
|
||||
src_vq->ops = &mxc_isi_m2m_vb2_qops;
|
||||
src_vq->mem_ops = &vb2_dma_contig_memops;
|
||||
src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
|
||||
src_vq->lock = &ctx->vb2_lock;
|
||||
src_vq->dev = m2m->isi->dev;
|
||||
|
||||
ret = vb2_queue_init(src_vq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
|
||||
dst_vq->drv_priv = ctx;
|
||||
dst_vq->buf_struct_size = sizeof(struct mxc_isi_m2m_buffer);
|
||||
dst_vq->ops = &mxc_isi_m2m_vb2_qops;
|
||||
dst_vq->mem_ops = &vb2_dma_contig_memops;
|
||||
dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
|
||||
dst_vq->lock = &ctx->vb2_lock;
|
||||
dst_vq->dev = m2m->isi->dev;
|
||||
|
||||
return vb2_queue_init(dst_vq);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* V4L2 controls
|
||||
*/
|
||||
|
||||
static inline struct mxc_isi_m2m_ctx *
|
||||
ctrl_to_mxc_isi_m2m_ctx(struct v4l2_ctrl *ctrl)
|
||||
{
|
||||
return container_of(ctrl->handler, struct mxc_isi_m2m_ctx, ctrls.handler);
|
||||
}
|
||||
|
||||
static int mxc_isi_m2m_ctx_s_ctrl(struct v4l2_ctrl *ctrl)
|
||||
{
|
||||
struct mxc_isi_m2m_ctx *ctx = ctrl_to_mxc_isi_m2m_ctx(ctrl);
|
||||
|
||||
switch (ctrl->id) {
|
||||
case V4L2_CID_HFLIP:
|
||||
ctx->ctrls.hflip = ctrl->val;
|
||||
break;
|
||||
|
||||
case V4L2_CID_VFLIP:
|
||||
ctx->ctrls.vflip = ctrl->val;
|
||||
break;
|
||||
|
||||
case V4L2_CID_ALPHA_COMPONENT:
|
||||
ctx->ctrls.alpha = ctrl->val;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct v4l2_ctrl_ops mxc_isi_m2m_ctx_ctrl_ops = {
|
||||
.s_ctrl = mxc_isi_m2m_ctx_s_ctrl,
|
||||
};
|
||||
|
||||
static int mxc_isi_m2m_ctx_ctrls_create(struct mxc_isi_m2m_ctx *ctx)
|
||||
{
|
||||
struct v4l2_ctrl_handler *handler = &ctx->ctrls.handler;
|
||||
int ret;
|
||||
|
||||
v4l2_ctrl_handler_init(handler, 3);
|
||||
|
||||
v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctx_ctrl_ops,
|
||||
V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 0);
|
||||
v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctx_ctrl_ops,
|
||||
V4L2_CID_HFLIP, 0, 1, 1, 0);
|
||||
v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctx_ctrl_ops,
|
||||
V4L2_CID_VFLIP, 0, 1, 1, 0);
|
||||
|
||||
if (handler->error) {
|
||||
ret = handler->error;
|
||||
v4l2_ctrl_handler_free(handler);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ctx->fh.ctrl_handler = handler;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mxc_isi_m2m_ctx_ctrls_delete(struct mxc_isi_m2m_ctx *ctx)
|
||||
{
|
||||
v4l2_ctrl_handler_free(&ctx->ctrls.handler);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* V4L2 ioctls
|
||||
*/
|
||||
|
||||
static int mxc_isi_m2m_querycap(struct file *file, void *fh,
|
||||
struct v4l2_capability *cap)
|
||||
{
|
||||
strscpy(cap->driver, MXC_ISI_DRIVER_NAME, sizeof(cap->driver));
|
||||
strscpy(cap->card, MXC_ISI_M2M, sizeof(cap->card));
|
||||
cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
|
||||
cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxc_isi_m2m_enum_fmt_vid(struct file *file, void *fh,
|
||||
struct v4l2_fmtdesc *f)
|
||||
{
|
||||
const enum mxc_isi_video_type type =
|
||||
f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ?
|
||||
MXC_ISI_VIDEO_M2M_OUT : MXC_ISI_VIDEO_M2M_CAP;
|
||||
const struct mxc_isi_format_info *info;
|
||||
|
||||
info = mxc_isi_format_enum(f->index, type);
|
||||
if (!info)
|
||||
return -EINVAL;
|
||||
|
||||
f->pixelformat = info->fourcc;
|
||||
f->flags |= V4L2_FMT_FLAG_CSC_COLORSPACE | V4L2_FMT_FLAG_CSC_YCBCR_ENC
|
||||
| V4L2_FMT_FLAG_CSC_QUANTIZATION | V4L2_FMT_FLAG_CSC_XFER_FUNC;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mxc_isi_format_info *
|
||||
__mxc_isi_m2m_try_fmt_vid(struct mxc_isi_m2m_ctx *ctx,
|
||||
struct v4l2_pix_format_mplane *pix,
|
||||
const enum mxc_isi_video_type type)
|
||||
{
|
||||
if (type == MXC_ISI_VIDEO_M2M_CAP) {
|
||||
/* Downscaling only */
|
||||
pix->width = min(pix->width, ctx->queues.out.format.width);
|
||||
pix->height = min(pix->height, ctx->queues.out.format.height);
|
||||
}
|
||||
|
||||
return mxc_isi_format_try(ctx->m2m->pipe, pix, type);
|
||||
}
|
||||
|
||||
static int mxc_isi_m2m_try_fmt_vid(struct file *file, void *fh,
|
||||
struct v4l2_format *f)
|
||||
{
|
||||
const enum mxc_isi_video_type type =
|
||||
f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ?
|
||||
MXC_ISI_VIDEO_M2M_OUT : MXC_ISI_VIDEO_M2M_CAP;
|
||||
struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh);
|
||||
|
||||
__mxc_isi_m2m_try_fmt_vid(ctx, &f->fmt.pix_mp, type);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxc_isi_m2m_g_fmt_vid(struct file *file, void *fh,
|
||||
struct v4l2_format *f)
|
||||
{
|
||||
struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh);
|
||||
const struct mxc_isi_m2m_ctx_queue_data *qdata =
|
||||
mxc_isi_m2m_ctx_qdata(ctx, f->type);
|
||||
|
||||
f->fmt.pix_mp = qdata->format;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxc_isi_m2m_s_fmt_vid(struct file *file, void *fh,
|
||||
struct v4l2_format *f)
|
||||
{
|
||||
const enum mxc_isi_video_type type =
|
||||
f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ?
|
||||
MXC_ISI_VIDEO_M2M_OUT : MXC_ISI_VIDEO_M2M_CAP;
|
||||
struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh);
|
||||
struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
|
||||
const struct mxc_isi_format_info *info;
|
||||
struct vb2_queue *vq;
|
||||
|
||||
vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
|
||||
if (!vq)
|
||||
return -EINVAL;
|
||||
|
||||
if (vb2_is_busy(vq))
|
||||
return -EBUSY;
|
||||
|
||||
info = __mxc_isi_m2m_try_fmt_vid(ctx, pix, type);
|
||||
|
||||
if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
|
||||
ctx->queues.out.format = *pix;
|
||||
ctx->queues.out.info = info;
|
||||
}
|
||||
|
||||
/*
|
||||
* Always set the format on the capture side, due to either format
|
||||
* propagation or direct setting.
|
||||
*/
|
||||
ctx->queues.cap.format = *pix;
|
||||
ctx->queues.cap.info = info;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxc_isi_m2m_streamon(struct file *file, void *fh,
|
||||
enum v4l2_buf_type type)
|
||||
{
|
||||
struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh);
|
||||
const struct v4l2_pix_format_mplane *out_pix = &ctx->queues.out.format;
|
||||
const struct v4l2_pix_format_mplane *cap_pix = &ctx->queues.cap.format;
|
||||
const struct mxc_isi_format_info *cap_info = ctx->queues.cap.info;
|
||||
const struct mxc_isi_format_info *out_info = ctx->queues.out.info;
|
||||
struct mxc_isi_m2m *m2m = ctx->m2m;
|
||||
bool bypass;
|
||||
|
||||
int ret;
|
||||
|
||||
mutex_lock(&m2m->lock);
|
||||
|
||||
if (m2m->usage_count == INT_MAX) {
|
||||
ret = -EOVERFLOW;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
bypass = cap_pix->width == out_pix->width &&
|
||||
cap_pix->height == out_pix->height &&
|
||||
cap_info->encoding == out_info->encoding;
|
||||
|
||||
/*
|
||||
* Acquire the pipe and initialize the channel with the first user of
|
||||
* the M2M device.
|
||||
*/
|
||||
if (m2m->usage_count == 0) {
|
||||
ret = mxc_isi_channel_acquire(m2m->pipe,
|
||||
&mxc_isi_m2m_frame_write_done,
|
||||
bypass);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
mxc_isi_channel_get(m2m->pipe);
|
||||
}
|
||||
|
||||
m2m->usage_count++;
|
||||
|
||||
/*
|
||||
* Allocate resources for the channel, counting how many users require
|
||||
* buffer chaining.
|
||||
*/
|
||||
if (!ctx->chained && out_pix->width > MXC_ISI_MAX_WIDTH_UNCHAINED) {
|
||||
ret = mxc_isi_channel_chain(m2m->pipe, bypass);
|
||||
if (ret)
|
||||
goto deinit;
|
||||
|
||||
m2m->chained_count++;
|
||||
ctx->chained = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Drop the lock to start the stream, as the .device_run() operation
|
||||
* needs to acquire it.
|
||||
*/
|
||||
mutex_unlock(&m2m->lock);
|
||||
ret = v4l2_m2m_ioctl_streamon(file, fh, type);
|
||||
if (ret) {
|
||||
/* Reacquire the lock for the cleanup path. */
|
||||
mutex_lock(&m2m->lock);
|
||||
goto unchain;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
unchain:
|
||||
if (ctx->chained && --m2m->chained_count == 0)
|
||||
mxc_isi_channel_unchain(m2m->pipe);
|
||||
ctx->chained = false;
|
||||
|
||||
deinit:
|
||||
if (--m2m->usage_count == 0) {
|
||||
mxc_isi_channel_put(m2m->pipe);
|
||||
mxc_isi_channel_release(m2m->pipe);
|
||||
}
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&m2m->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mxc_isi_m2m_streamoff(struct file *file, void *fh,
|
||||
enum v4l2_buf_type type)
|
||||
{
|
||||
struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh);
|
||||
struct mxc_isi_m2m *m2m = ctx->m2m;
|
||||
|
||||
v4l2_m2m_ioctl_streamoff(file, fh, type);
|
||||
|
||||
mutex_lock(&m2m->lock);
|
||||
|
||||
/*
|
||||
* If the last context is this one, reset it to make sure the device
|
||||
* will be reconfigured when streaming is restarted.
|
||||
*/
|
||||
if (m2m->last_ctx == ctx)
|
||||
m2m->last_ctx = NULL;
|
||||
|
||||
/* Free the channel resources if this is the last chained context. */
|
||||
if (ctx->chained && --m2m->chained_count == 0)
|
||||
mxc_isi_channel_unchain(m2m->pipe);
|
||||
ctx->chained = false;
|
||||
|
||||
/* Turn off the light with the last user. */
|
||||
if (--m2m->usage_count == 0) {
|
||||
mxc_isi_channel_disable(m2m->pipe);
|
||||
mxc_isi_channel_put(m2m->pipe);
|
||||
mxc_isi_channel_release(m2m->pipe);
|
||||
}
|
||||
|
||||
WARN_ON(m2m->usage_count < 0);
|
||||
|
||||
mutex_unlock(&m2m->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct v4l2_ioctl_ops mxc_isi_m2m_ioctl_ops = {
|
||||
.vidioc_querycap = mxc_isi_m2m_querycap,
|
||||
|
||||
.vidioc_enum_fmt_vid_cap = mxc_isi_m2m_enum_fmt_vid,
|
||||
.vidioc_enum_fmt_vid_out = mxc_isi_m2m_enum_fmt_vid,
|
||||
.vidioc_g_fmt_vid_cap_mplane = mxc_isi_m2m_g_fmt_vid,
|
||||
.vidioc_g_fmt_vid_out_mplane = mxc_isi_m2m_g_fmt_vid,
|
||||
.vidioc_s_fmt_vid_cap_mplane = mxc_isi_m2m_s_fmt_vid,
|
||||
.vidioc_s_fmt_vid_out_mplane = mxc_isi_m2m_s_fmt_vid,
|
||||
.vidioc_try_fmt_vid_cap_mplane = mxc_isi_m2m_try_fmt_vid,
|
||||
.vidioc_try_fmt_vid_out_mplane = mxc_isi_m2m_try_fmt_vid,
|
||||
|
||||
.vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
|
||||
.vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
|
||||
.vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
|
||||
.vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
|
||||
.vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
|
||||
.vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
|
||||
.vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
|
||||
|
||||
.vidioc_streamon = mxc_isi_m2m_streamon,
|
||||
.vidioc_streamoff = mxc_isi_m2m_streamoff,
|
||||
|
||||
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
|
||||
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Video device file operations
|
||||
*/
|
||||
|
||||
static void mxc_isi_m2m_init_format(struct mxc_isi_m2m_ctx *ctx,
|
||||
struct mxc_isi_m2m_ctx_queue_data *qdata,
|
||||
enum mxc_isi_video_type type)
|
||||
{
|
||||
qdata->format.width = MXC_ISI_DEF_WIDTH;
|
||||
qdata->format.height = MXC_ISI_DEF_HEIGHT;
|
||||
qdata->format.pixelformat = MXC_ISI_DEF_PIXEL_FORMAT;
|
||||
|
||||
qdata->info = mxc_isi_format_try(ctx->m2m->pipe, &qdata->format, type);
|
||||
}
|
||||
|
||||
static int mxc_isi_m2m_open(struct file *file)
|
||||
{
|
||||
struct video_device *vdev = video_devdata(file);
|
||||
struct mxc_isi_m2m *m2m = video_drvdata(file);
|
||||
struct mxc_isi_m2m_ctx *ctx;
|
||||
int ret;
|
||||
|
||||
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
ctx->m2m = m2m;
|
||||
mutex_init(&ctx->vb2_lock);
|
||||
|
||||
v4l2_fh_init(&ctx->fh, vdev);
|
||||
file->private_data = &ctx->fh;
|
||||
|
||||
ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(m2m->m2m_dev, ctx,
|
||||
&mxc_isi_m2m_queue_init);
|
||||
if (IS_ERR(ctx->fh.m2m_ctx)) {
|
||||
ret = PTR_ERR(ctx->fh.m2m_ctx);
|
||||
ctx->fh.m2m_ctx = NULL;
|
||||
goto err_fh;
|
||||
}
|
||||
|
||||
mxc_isi_m2m_init_format(ctx, &ctx->queues.out, MXC_ISI_VIDEO_M2M_OUT);
|
||||
mxc_isi_m2m_init_format(ctx, &ctx->queues.cap, MXC_ISI_VIDEO_M2M_CAP);
|
||||
|
||||
ret = mxc_isi_m2m_ctx_ctrls_create(ctx);
|
||||
if (ret)
|
||||
goto err_ctx;
|
||||
|
||||
ret = pm_runtime_resume_and_get(m2m->isi->dev);
|
||||
if (ret)
|
||||
goto err_ctrls;
|
||||
|
||||
v4l2_fh_add(&ctx->fh);
|
||||
|
||||
return 0;
|
||||
|
||||
err_ctrls:
|
||||
mxc_isi_m2m_ctx_ctrls_delete(ctx);
|
||||
err_ctx:
|
||||
v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
|
||||
err_fh:
|
||||
v4l2_fh_exit(&ctx->fh);
|
||||
mutex_destroy(&ctx->vb2_lock);
|
||||
kfree(ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mxc_isi_m2m_release(struct file *file)
|
||||
{
|
||||
struct mxc_isi_m2m *m2m = video_drvdata(file);
|
||||
struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(file->private_data);
|
||||
|
||||
v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
|
||||
mxc_isi_m2m_ctx_ctrls_delete(ctx);
|
||||
|
||||
v4l2_fh_del(&ctx->fh);
|
||||
v4l2_fh_exit(&ctx->fh);
|
||||
|
||||
mutex_destroy(&ctx->vb2_lock);
|
||||
kfree(ctx);
|
||||
|
||||
pm_runtime_put(m2m->isi->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct v4l2_file_operations mxc_isi_m2m_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = mxc_isi_m2m_open,
|
||||
.release = mxc_isi_m2m_release,
|
||||
.poll = v4l2_m2m_fop_poll,
|
||||
.unlocked_ioctl = video_ioctl2,
|
||||
.mmap = v4l2_m2m_fop_mmap,
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Registration
|
||||
*/
|
||||
|
||||
int mxc_isi_m2m_register(struct mxc_isi_dev *isi, struct v4l2_device *v4l2_dev)
|
||||
{
|
||||
struct mxc_isi_m2m *m2m = &isi->m2m;
|
||||
struct video_device *vdev = &m2m->vdev;
|
||||
struct media_link *link;
|
||||
int ret;
|
||||
|
||||
m2m->isi = isi;
|
||||
m2m->pipe = &isi->pipes[0];
|
||||
|
||||
mutex_init(&m2m->lock);
|
||||
|
||||
/* Initialize the video device and create controls. */
|
||||
snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.m2m");
|
||||
|
||||
vdev->fops = &mxc_isi_m2m_fops;
|
||||
vdev->ioctl_ops = &mxc_isi_m2m_ioctl_ops;
|
||||
vdev->v4l2_dev = v4l2_dev;
|
||||
vdev->minor = -1;
|
||||
vdev->release = video_device_release_empty;
|
||||
vdev->vfl_dir = VFL_DIR_M2M;
|
||||
|
||||
vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
|
||||
video_set_drvdata(vdev, m2m);
|
||||
|
||||
/* Create the M2M device. */
|
||||
m2m->m2m_dev = v4l2_m2m_init(&mxc_isi_m2m_ops);
|
||||
if (IS_ERR(m2m->m2m_dev)) {
|
||||
dev_err(isi->dev, "failed to initialize m2m device\n");
|
||||
ret = PTR_ERR(m2m->m2m_dev);
|
||||
goto err_mutex;
|
||||
}
|
||||
|
||||
/* Register the video device. */
|
||||
ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
|
||||
if (ret < 0) {
|
||||
dev_err(isi->dev, "failed to register m2m device\n");
|
||||
goto err_m2m;
|
||||
}
|
||||
|
||||
/*
|
||||
* Populate the media graph. We can't use the mem2mem helper
|
||||
* v4l2_m2m_register_media_controller() as the M2M interface needs to
|
||||
* be connected to the existing entities in the graph, so we have to
|
||||
* wire things up manually:
|
||||
*
|
||||
* - The entity in the video_device, which isn't touched by the V4L2
|
||||
* core for M2M devices, is used as the source I/O entity in the
|
||||
* graph, connected to the crossbar switch.
|
||||
*
|
||||
* - The video device at the end of the pipeline provides the sink
|
||||
* entity, and is already wired up in the graph.
|
||||
*
|
||||
* - A new interface is created, pointing at both entities. The sink
|
||||
* entity will thus have two interfaces pointing to it.
|
||||
*/
|
||||
m2m->pad.flags = MEDIA_PAD_FL_SOURCE;
|
||||
vdev->entity.name = "mxc_isi.output";
|
||||
vdev->entity.function = MEDIA_ENT_F_IO_V4L;
|
||||
ret = media_entity_pads_init(&vdev->entity, 1, &m2m->pad);
|
||||
if (ret)
|
||||
goto err_video;
|
||||
|
||||
ret = media_device_register_entity(v4l2_dev->mdev, &vdev->entity);
|
||||
if (ret)
|
||||
goto err_entity_cleanup;
|
||||
|
||||
ret = media_create_pad_link(&vdev->entity, 0,
|
||||
&m2m->isi->crossbar.sd.entity,
|
||||
m2m->isi->crossbar.num_sinks - 1,
|
||||
MEDIA_LNK_FL_IMMUTABLE |
|
||||
MEDIA_LNK_FL_ENABLED);
|
||||
if (ret)
|
||||
goto err_entity_unreg;
|
||||
|
||||
m2m->intf = media_devnode_create(v4l2_dev->mdev, MEDIA_INTF_T_V4L_VIDEO,
|
||||
0, VIDEO_MAJOR, vdev->minor);
|
||||
if (!m2m->intf) {
|
||||
ret = -ENOMEM;
|
||||
goto err_entity_unreg;
|
||||
}
|
||||
|
||||
link = media_create_intf_link(&vdev->entity, &m2m->intf->intf,
|
||||
MEDIA_LNK_FL_IMMUTABLE |
|
||||
MEDIA_LNK_FL_ENABLED);
|
||||
if (!link) {
|
||||
ret = -ENOMEM;
|
||||
goto err_devnode;
|
||||
}
|
||||
|
||||
link = media_create_intf_link(&m2m->pipe->video.vdev.entity,
|
||||
&m2m->intf->intf,
|
||||
MEDIA_LNK_FL_IMMUTABLE |
|
||||
MEDIA_LNK_FL_ENABLED);
|
||||
if (!link) {
|
||||
ret = -ENOMEM;
|
||||
goto err_devnode;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_devnode:
|
||||
media_devnode_remove(m2m->intf);
|
||||
err_entity_unreg:
|
||||
media_device_unregister_entity(&vdev->entity);
|
||||
err_entity_cleanup:
|
||||
media_entity_cleanup(&vdev->entity);
|
||||
err_video:
|
||||
video_unregister_device(vdev);
|
||||
err_m2m:
|
||||
v4l2_m2m_release(m2m->m2m_dev);
|
||||
err_mutex:
|
||||
mutex_destroy(&m2m->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mxc_isi_m2m_unregister(struct mxc_isi_dev *isi)
|
||||
{
|
||||
struct mxc_isi_m2m *m2m = &isi->m2m;
|
||||
struct video_device *vdev = &m2m->vdev;
|
||||
|
||||
video_unregister_device(vdev);
|
||||
|
||||
v4l2_m2m_release(m2m->m2m_dev);
|
||||
media_devnode_remove(m2m->intf);
|
||||
media_entity_cleanup(&vdev->entity);
|
||||
mutex_destroy(&m2m->lock);
|
||||
|
||||
return 0;
|
||||
}
|
867
drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c
Normal file
867
drivers/media/platform/nxp/imx8-isi/imx8-isi-pipe.c
Normal file
@ -0,0 +1,867 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* V4L2 Capture ISI subdev driver for i.MX8QXP/QM platform
|
||||
*
|
||||
* ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which
|
||||
* used to process image from camera sensor to memory or DC
|
||||
*
|
||||
* Copyright (c) 2019 NXP Semiconductor
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include <media/media-entity.h>
|
||||
#include <media/v4l2-subdev.h>
|
||||
#include <media/videobuf2-v4l2.h>
|
||||
|
||||
#include "imx8-isi-core.h"
|
||||
#include "imx8-isi-regs.h"
|
||||
|
||||
/*
|
||||
* While the ISI receives data from the gasket on a 3x12-bit bus, the pipeline
|
||||
* subdev conceptually includes the gasket in order to avoid exposing an extra
|
||||
* subdev between the CSIS and the ISI. We thus need to expose media bus codes
|
||||
* corresponding to the CSIS output, which is narrower.
|
||||
*/
|
||||
static const struct mxc_isi_bus_format_info mxc_isi_bus_formats[] = {
|
||||
/* YUV formats */
|
||||
{
|
||||
.mbus_code = MEDIA_BUS_FMT_UYVY8_1X16,
|
||||
.output = MEDIA_BUS_FMT_YUV8_1X24,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK),
|
||||
.encoding = MXC_ISI_ENC_YUV,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
|
||||
.output = MEDIA_BUS_FMT_YUV8_1X24,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_YUV,
|
||||
},
|
||||
/* RGB formats */
|
||||
{
|
||||
.mbus_code = MEDIA_BUS_FMT_RGB565_1X16,
|
||||
.output = MEDIA_BUS_FMT_RGB888_1X24,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK),
|
||||
.encoding = MXC_ISI_ENC_RGB,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
|
||||
.output = MEDIA_BUS_FMT_RGB888_1X24,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RGB,
|
||||
},
|
||||
/* RAW formats */
|
||||
{
|
||||
.mbus_code = MEDIA_BUS_FMT_Y8_1X8,
|
||||
.output = MEDIA_BUS_FMT_Y8_1X8,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_Y10_1X10,
|
||||
.output = MEDIA_BUS_FMT_Y10_1X10,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_Y12_1X12,
|
||||
.output = MEDIA_BUS_FMT_Y12_1X12,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_Y14_1X14,
|
||||
.output = MEDIA_BUS_FMT_Y14_1X14,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
|
||||
.output = MEDIA_BUS_FMT_SBGGR8_1X8,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
|
||||
.output = MEDIA_BUS_FMT_SGBRG8_1X8,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
|
||||
.output = MEDIA_BUS_FMT_SGRBG8_1X8,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
|
||||
.output = MEDIA_BUS_FMT_SRGGB8_1X8,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
|
||||
.output = MEDIA_BUS_FMT_SBGGR10_1X10,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
|
||||
.output = MEDIA_BUS_FMT_SGBRG10_1X10,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
|
||||
.output = MEDIA_BUS_FMT_SGRBG10_1X10,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
|
||||
.output = MEDIA_BUS_FMT_SRGGB10_1X10,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
|
||||
.output = MEDIA_BUS_FMT_SBGGR12_1X12,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12,
|
||||
.output = MEDIA_BUS_FMT_SGBRG12_1X12,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
|
||||
.output = MEDIA_BUS_FMT_SGRBG12_1X12,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12,
|
||||
.output = MEDIA_BUS_FMT_SRGGB12_1X12,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SBGGR14_1X14,
|
||||
.output = MEDIA_BUS_FMT_SBGGR14_1X14,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SGBRG14_1X14,
|
||||
.output = MEDIA_BUS_FMT_SGBRG14_1X14,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SGRBG14_1X14,
|
||||
.output = MEDIA_BUS_FMT_SGRBG14_1X14,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}, {
|
||||
.mbus_code = MEDIA_BUS_FMT_SRGGB14_1X14,
|
||||
.output = MEDIA_BUS_FMT_SRGGB14_1X14,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
},
|
||||
/* JPEG */
|
||||
{
|
||||
.mbus_code = MEDIA_BUS_FMT_JPEG_1X8,
|
||||
.output = MEDIA_BUS_FMT_JPEG_1X8,
|
||||
.pads = BIT(MXC_ISI_PIPE_PAD_SINK)
|
||||
| BIT(MXC_ISI_PIPE_PAD_SOURCE),
|
||||
.encoding = MXC_ISI_ENC_RAW,
|
||||
}
|
||||
};
|
||||
|
||||
const struct mxc_isi_bus_format_info *
|
||||
mxc_isi_bus_format_by_code(u32 code, unsigned int pad)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mxc_isi_bus_formats); i++) {
|
||||
const struct mxc_isi_bus_format_info *info =
|
||||
&mxc_isi_bus_formats[i];
|
||||
|
||||
if (info->mbus_code == code && info->pads & BIT(pad))
|
||||
return info;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct mxc_isi_bus_format_info *
|
||||
mxc_isi_bus_format_by_index(unsigned int index, unsigned int pad)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mxc_isi_bus_formats); i++) {
|
||||
const struct mxc_isi_bus_format_info *info =
|
||||
&mxc_isi_bus_formats[i];
|
||||
|
||||
if (!(info->pads & BIT(pad)))
|
||||
continue;
|
||||
|
||||
if (!index)
|
||||
return info;
|
||||
|
||||
index--;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline struct mxc_isi_pipe *to_isi_pipe(struct v4l2_subdev *sd)
|
||||
{
|
||||
return container_of(sd, struct mxc_isi_pipe, sd);
|
||||
}
|
||||
|
||||
int mxc_isi_pipe_enable(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
struct mxc_isi_crossbar *xbar = &pipe->isi->crossbar;
|
||||
const struct mxc_isi_bus_format_info *sink_info;
|
||||
const struct mxc_isi_bus_format_info *src_info;
|
||||
const struct v4l2_mbus_framefmt *sink_fmt;
|
||||
const struct v4l2_mbus_framefmt *src_fmt;
|
||||
const struct v4l2_rect *compose;
|
||||
struct v4l2_subdev_state *state;
|
||||
struct v4l2_subdev *sd = &pipe->sd;
|
||||
struct v4l2_area in_size, scale;
|
||||
struct v4l2_rect crop;
|
||||
u32 input;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Find the connected input by inspecting the crossbar switch routing
|
||||
* table.
|
||||
*/
|
||||
state = v4l2_subdev_lock_and_get_active_state(&xbar->sd);
|
||||
ret = v4l2_subdev_routing_find_opposite_end(&state->routing,
|
||||
xbar->num_sinks + pipe->id,
|
||||
0, &input, NULL);
|
||||
v4l2_subdev_unlock_state(state);
|
||||
|
||||
if (ret)
|
||||
return -EPIPE;
|
||||
|
||||
/* Configure the pipeline. */
|
||||
state = v4l2_subdev_lock_and_get_active_state(sd);
|
||||
|
||||
sink_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SINK);
|
||||
src_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SOURCE);
|
||||
compose = v4l2_subdev_get_try_compose(sd, state, MXC_ISI_PIPE_PAD_SINK);
|
||||
crop = *v4l2_subdev_get_try_crop(sd, state, MXC_ISI_PIPE_PAD_SOURCE);
|
||||
|
||||
sink_info = mxc_isi_bus_format_by_code(sink_fmt->code,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
src_info = mxc_isi_bus_format_by_code(src_fmt->code,
|
||||
MXC_ISI_PIPE_PAD_SOURCE);
|
||||
|
||||
in_size.width = sink_fmt->width;
|
||||
in_size.height = sink_fmt->height;
|
||||
scale.width = compose->width;
|
||||
scale.height = compose->height;
|
||||
|
||||
v4l2_subdev_unlock_state(state);
|
||||
|
||||
/* Configure the ISI channel. */
|
||||
mxc_isi_channel_config(pipe, input, &in_size, &scale, &crop,
|
||||
sink_info->encoding, src_info->encoding);
|
||||
|
||||
mxc_isi_channel_enable(pipe);
|
||||
|
||||
/* Enable streams on the crossbar switch. */
|
||||
ret = v4l2_subdev_enable_streams(&xbar->sd, xbar->num_sinks + pipe->id,
|
||||
BIT(0));
|
||||
if (ret) {
|
||||
mxc_isi_channel_disable(pipe);
|
||||
dev_err(pipe->isi->dev, "Failed to enable pipe %u\n",
|
||||
pipe->id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mxc_isi_pipe_disable(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
struct mxc_isi_crossbar *xbar = &pipe->isi->crossbar;
|
||||
int ret;
|
||||
|
||||
ret = v4l2_subdev_disable_streams(&xbar->sd, xbar->num_sinks + pipe->id,
|
||||
BIT(0));
|
||||
if (ret)
|
||||
dev_err(pipe->isi->dev, "Failed to disable pipe %u\n",
|
||||
pipe->id);
|
||||
|
||||
mxc_isi_channel_disable(pipe);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* V4L2 subdev operations
|
||||
*/
|
||||
|
||||
static struct v4l2_mbus_framefmt *
|
||||
mxc_isi_pipe_get_pad_format(struct mxc_isi_pipe *pipe,
|
||||
struct v4l2_subdev_state *state,
|
||||
unsigned int pad)
|
||||
{
|
||||
return v4l2_subdev_get_try_format(&pipe->sd, state, pad);
|
||||
}
|
||||
|
||||
static struct v4l2_rect *
|
||||
mxc_isi_pipe_get_pad_crop(struct mxc_isi_pipe *pipe,
|
||||
struct v4l2_subdev_state *state,
|
||||
unsigned int pad)
|
||||
{
|
||||
return v4l2_subdev_get_try_crop(&pipe->sd, state, pad);
|
||||
}
|
||||
|
||||
static struct v4l2_rect *
|
||||
mxc_isi_pipe_get_pad_compose(struct mxc_isi_pipe *pipe,
|
||||
struct v4l2_subdev_state *state,
|
||||
unsigned int pad)
|
||||
{
|
||||
return v4l2_subdev_get_try_compose(&pipe->sd, state, pad);
|
||||
}
|
||||
|
||||
static int mxc_isi_pipe_init_cfg(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state)
|
||||
{
|
||||
struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
|
||||
struct v4l2_mbus_framefmt *fmt_source;
|
||||
struct v4l2_mbus_framefmt *fmt_sink;
|
||||
struct v4l2_rect *compose;
|
||||
struct v4l2_rect *crop;
|
||||
|
||||
fmt_sink = mxc_isi_pipe_get_pad_format(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
fmt_source = mxc_isi_pipe_get_pad_format(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SOURCE);
|
||||
|
||||
fmt_sink->width = MXC_ISI_DEF_WIDTH;
|
||||
fmt_sink->height = MXC_ISI_DEF_HEIGHT;
|
||||
fmt_sink->code = MXC_ISI_DEF_MBUS_CODE_SINK;
|
||||
fmt_sink->field = V4L2_FIELD_NONE;
|
||||
fmt_sink->colorspace = V4L2_COLORSPACE_JPEG;
|
||||
fmt_sink->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt_sink->colorspace);
|
||||
fmt_sink->quantization =
|
||||
V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt_sink->colorspace,
|
||||
fmt_sink->ycbcr_enc);
|
||||
fmt_sink->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt_sink->colorspace);
|
||||
|
||||
*fmt_source = *fmt_sink;
|
||||
fmt_source->code = MXC_ISI_DEF_MBUS_CODE_SOURCE;
|
||||
|
||||
compose = mxc_isi_pipe_get_pad_compose(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
crop = mxc_isi_pipe_get_pad_crop(pipe, state, MXC_ISI_PIPE_PAD_SOURCE);
|
||||
|
||||
compose->left = 0;
|
||||
compose->top = 0;
|
||||
compose->width = MXC_ISI_DEF_WIDTH;
|
||||
compose->height = MXC_ISI_DEF_HEIGHT;
|
||||
|
||||
*crop = *compose;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxc_isi_pipe_enum_mbus_code(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
struct v4l2_subdev_mbus_code_enum *code)
|
||||
{
|
||||
static const u32 output_codes[] = {
|
||||
MEDIA_BUS_FMT_YUV8_1X24,
|
||||
MEDIA_BUS_FMT_RGB888_1X24,
|
||||
};
|
||||
struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
|
||||
const struct mxc_isi_bus_format_info *info;
|
||||
unsigned int index;
|
||||
unsigned int i;
|
||||
|
||||
if (code->pad == MXC_ISI_PIPE_PAD_SOURCE) {
|
||||
const struct v4l2_mbus_framefmt *format;
|
||||
|
||||
format = mxc_isi_pipe_get_pad_format(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
info = mxc_isi_bus_format_by_code(format->code,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
|
||||
if (info->encoding == MXC_ISI_ENC_RAW) {
|
||||
/*
|
||||
* For RAW formats, the sink and source media bus codes
|
||||
* must match.
|
||||
*/
|
||||
if (code->index)
|
||||
return -EINVAL;
|
||||
|
||||
code->code = info->output;
|
||||
} else {
|
||||
/*
|
||||
* For RGB or YUV formats, the ISI supports format
|
||||
* conversion. Either of the two output formats can be
|
||||
* used regardless of the input.
|
||||
*/
|
||||
if (code->index > 1)
|
||||
return -EINVAL;
|
||||
|
||||
code->code = output_codes[code->index];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
index = code->index;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mxc_isi_bus_formats); ++i) {
|
||||
info = &mxc_isi_bus_formats[i];
|
||||
|
||||
if (!(info->pads & BIT(MXC_ISI_PIPE_PAD_SINK)))
|
||||
continue;
|
||||
|
||||
if (index == 0) {
|
||||
code->code = info->mbus_code;
|
||||
return 0;
|
||||
}
|
||||
|
||||
index--;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int mxc_isi_pipe_set_fmt(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
struct v4l2_subdev_format *fmt)
|
||||
{
|
||||
struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
|
||||
struct v4l2_mbus_framefmt *mf = &fmt->format;
|
||||
const struct mxc_isi_bus_format_info *info;
|
||||
struct v4l2_mbus_framefmt *format;
|
||||
struct v4l2_rect *rect;
|
||||
|
||||
if (vb2_is_busy(&pipe->video.vb2_q))
|
||||
return -EBUSY;
|
||||
|
||||
if (fmt->pad == MXC_ISI_PIPE_PAD_SINK) {
|
||||
unsigned int max_width;
|
||||
|
||||
info = mxc_isi_bus_format_by_code(mf->code,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
if (!info)
|
||||
info = mxc_isi_bus_format_by_code(MXC_ISI_DEF_MBUS_CODE_SINK,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
|
||||
/*
|
||||
* Limit the max line length if there's no adjacent pipe to
|
||||
* chain with.
|
||||
*/
|
||||
max_width = pipe->id == pipe->isi->pdata->num_channels - 1
|
||||
? MXC_ISI_MAX_WIDTH_UNCHAINED
|
||||
: MXC_ISI_MAX_WIDTH_CHAINED;
|
||||
|
||||
mf->code = info->mbus_code;
|
||||
mf->width = clamp(mf->width, MXC_ISI_MIN_WIDTH, max_width);
|
||||
mf->height = clamp(mf->height, MXC_ISI_MIN_HEIGHT,
|
||||
MXC_ISI_MAX_HEIGHT);
|
||||
|
||||
/* Propagate the format to the source pad. */
|
||||
rect = mxc_isi_pipe_get_pad_compose(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
rect->width = mf->width;
|
||||
rect->height = mf->height;
|
||||
|
||||
rect = mxc_isi_pipe_get_pad_crop(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SOURCE);
|
||||
rect->left = 0;
|
||||
rect->top = 0;
|
||||
rect->width = mf->width;
|
||||
rect->height = mf->height;
|
||||
|
||||
format = mxc_isi_pipe_get_pad_format(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SOURCE);
|
||||
format->code = info->output;
|
||||
format->width = mf->width;
|
||||
format->height = mf->height;
|
||||
} else {
|
||||
/*
|
||||
* For RGB or YUV formats, the ISI supports RGB <-> YUV format
|
||||
* conversion. For RAW formats, the sink and source media bus
|
||||
* codes must match.
|
||||
*/
|
||||
format = mxc_isi_pipe_get_pad_format(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
info = mxc_isi_bus_format_by_code(format->code,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
|
||||
if (info->encoding != MXC_ISI_ENC_RAW) {
|
||||
if (mf->code != MEDIA_BUS_FMT_YUV8_1X24 &&
|
||||
mf->code != MEDIA_BUS_FMT_RGB888_1X24)
|
||||
mf->code = info->output;
|
||||
|
||||
info = mxc_isi_bus_format_by_code(mf->code,
|
||||
MXC_ISI_PIPE_PAD_SOURCE);
|
||||
}
|
||||
|
||||
mf->code = info->output;
|
||||
|
||||
/*
|
||||
* The width and height on the source can't be changed, they
|
||||
* must match the crop rectangle size.
|
||||
*/
|
||||
rect = mxc_isi_pipe_get_pad_crop(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SOURCE);
|
||||
|
||||
mf->width = rect->width;
|
||||
mf->height = rect->height;
|
||||
}
|
||||
|
||||
format = mxc_isi_pipe_get_pad_format(pipe, state, fmt->pad);
|
||||
*format = *mf;
|
||||
|
||||
dev_dbg(pipe->isi->dev, "pad%u: code: 0x%04x, %ux%u",
|
||||
fmt->pad, mf->code, mf->width, mf->height);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxc_isi_pipe_get_selection(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
struct v4l2_subdev_selection *sel)
|
||||
{
|
||||
struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
|
||||
const struct v4l2_mbus_framefmt *format;
|
||||
const struct v4l2_rect *rect;
|
||||
|
||||
switch (sel->target) {
|
||||
case V4L2_SEL_TGT_COMPOSE_BOUNDS:
|
||||
if (sel->pad != MXC_ISI_PIPE_PAD_SINK)
|
||||
/* No compose rectangle on source pad. */
|
||||
return -EINVAL;
|
||||
|
||||
/* The sink compose is bound by the sink format. */
|
||||
format = mxc_isi_pipe_get_pad_format(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
sel->r.left = 0;
|
||||
sel->r.top = 0;
|
||||
sel->r.width = format->width;
|
||||
sel->r.height = format->height;
|
||||
break;
|
||||
|
||||
case V4L2_SEL_TGT_CROP_BOUNDS:
|
||||
if (sel->pad != MXC_ISI_PIPE_PAD_SOURCE)
|
||||
/* No crop rectangle on sink pad. */
|
||||
return -EINVAL;
|
||||
|
||||
/* The source crop is bound by the sink compose. */
|
||||
rect = mxc_isi_pipe_get_pad_compose(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
sel->r = *rect;
|
||||
break;
|
||||
|
||||
case V4L2_SEL_TGT_CROP:
|
||||
if (sel->pad != MXC_ISI_PIPE_PAD_SOURCE)
|
||||
/* No crop rectangle on sink pad. */
|
||||
return -EINVAL;
|
||||
|
||||
rect = mxc_isi_pipe_get_pad_crop(pipe, state, sel->pad);
|
||||
sel->r = *rect;
|
||||
break;
|
||||
|
||||
case V4L2_SEL_TGT_COMPOSE:
|
||||
if (sel->pad != MXC_ISI_PIPE_PAD_SINK)
|
||||
/* No compose rectangle on source pad. */
|
||||
return -EINVAL;
|
||||
|
||||
rect = mxc_isi_pipe_get_pad_compose(pipe, state, sel->pad);
|
||||
sel->r = *rect;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxc_isi_pipe_set_selection(struct v4l2_subdev *sd,
|
||||
struct v4l2_subdev_state *state,
|
||||
struct v4l2_subdev_selection *sel)
|
||||
{
|
||||
struct mxc_isi_pipe *pipe = to_isi_pipe(sd);
|
||||
struct v4l2_mbus_framefmt *format;
|
||||
struct v4l2_rect *rect;
|
||||
|
||||
switch (sel->target) {
|
||||
case V4L2_SEL_TGT_CROP:
|
||||
if (sel->pad != MXC_ISI_PIPE_PAD_SOURCE)
|
||||
/* The pipeline support cropping on the source only. */
|
||||
return -EINVAL;
|
||||
|
||||
/* The source crop is bound by the sink compose. */
|
||||
rect = mxc_isi_pipe_get_pad_compose(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
sel->r.left = clamp_t(s32, sel->r.left, 0, rect->width - 1);
|
||||
sel->r.top = clamp_t(s32, sel->r.top, 0, rect->height - 1);
|
||||
sel->r.width = clamp(sel->r.width, MXC_ISI_MIN_WIDTH,
|
||||
rect->width - sel->r.left);
|
||||
sel->r.height = clamp(sel->r.height, MXC_ISI_MIN_HEIGHT,
|
||||
rect->height - sel->r.top);
|
||||
|
||||
rect = mxc_isi_pipe_get_pad_crop(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SOURCE);
|
||||
*rect = sel->r;
|
||||
|
||||
/* Propagate the crop rectangle to the source pad. */
|
||||
format = mxc_isi_pipe_get_pad_format(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SOURCE);
|
||||
format->width = sel->r.width;
|
||||
format->height = sel->r.height;
|
||||
break;
|
||||
|
||||
case V4L2_SEL_TGT_COMPOSE:
|
||||
if (sel->pad != MXC_ISI_PIPE_PAD_SINK)
|
||||
/* Composing is supported on the sink only. */
|
||||
return -EINVAL;
|
||||
|
||||
/* The sink crop is bound by the sink format downscaling only). */
|
||||
format = mxc_isi_pipe_get_pad_format(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
|
||||
sel->r.left = 0;
|
||||
sel->r.top = 0;
|
||||
sel->r.width = clamp(sel->r.width, MXC_ISI_MIN_WIDTH,
|
||||
format->width);
|
||||
sel->r.height = clamp(sel->r.height, MXC_ISI_MIN_HEIGHT,
|
||||
format->height);
|
||||
|
||||
rect = mxc_isi_pipe_get_pad_compose(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
*rect = sel->r;
|
||||
|
||||
/* Propagate the compose rectangle to the source pad. */
|
||||
rect = mxc_isi_pipe_get_pad_crop(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SOURCE);
|
||||
rect->left = 0;
|
||||
rect->top = 0;
|
||||
rect->width = sel->r.width;
|
||||
rect->height = sel->r.height;
|
||||
|
||||
format = mxc_isi_pipe_get_pad_format(pipe, state,
|
||||
MXC_ISI_PIPE_PAD_SOURCE);
|
||||
format->width = sel->r.width;
|
||||
format->height = sel->r.height;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_dbg(pipe->isi->dev, "%s, target %#x: (%d,%d)/%dx%d", __func__,
|
||||
sel->target, sel->r.left, sel->r.top, sel->r.width,
|
||||
sel->r.height);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct v4l2_subdev_pad_ops mxc_isi_pipe_subdev_pad_ops = {
|
||||
.init_cfg = mxc_isi_pipe_init_cfg,
|
||||
.enum_mbus_code = mxc_isi_pipe_enum_mbus_code,
|
||||
.get_fmt = v4l2_subdev_get_fmt,
|
||||
.set_fmt = mxc_isi_pipe_set_fmt,
|
||||
.get_selection = mxc_isi_pipe_get_selection,
|
||||
.set_selection = mxc_isi_pipe_set_selection,
|
||||
};
|
||||
|
||||
static const struct v4l2_subdev_ops mxc_isi_pipe_subdev_ops = {
|
||||
.pad = &mxc_isi_pipe_subdev_pad_ops,
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* IRQ handling
|
||||
*/
|
||||
|
||||
static irqreturn_t mxc_isi_pipe_irq_handler(int irq, void *priv)
|
||||
{
|
||||
struct mxc_isi_pipe *pipe = priv;
|
||||
const struct mxc_isi_ier_reg *ier_reg = pipe->isi->pdata->ier_reg;
|
||||
u32 status;
|
||||
|
||||
status = mxc_isi_channel_irq_status(pipe, true);
|
||||
|
||||
if (status & CHNL_STS_FRM_STRD) {
|
||||
if (!WARN_ON(!pipe->irq_handler))
|
||||
pipe->irq_handler(pipe, status);
|
||||
}
|
||||
|
||||
if (status & (CHNL_STS_AXI_WR_ERR_Y |
|
||||
CHNL_STS_AXI_WR_ERR_U |
|
||||
CHNL_STS_AXI_WR_ERR_V))
|
||||
dev_dbg(pipe->isi->dev, "%s: IRQ AXI Error stat=0x%X\n",
|
||||
__func__, status);
|
||||
|
||||
if (status & (ier_reg->panic_y_buf_en.mask |
|
||||
ier_reg->panic_u_buf_en.mask |
|
||||
ier_reg->panic_v_buf_en.mask))
|
||||
dev_dbg(pipe->isi->dev, "%s: IRQ Panic OFLW Error stat=0x%X\n",
|
||||
__func__, status);
|
||||
|
||||
if (status & (ier_reg->oflw_y_buf_en.mask |
|
||||
ier_reg->oflw_u_buf_en.mask |
|
||||
ier_reg->oflw_v_buf_en.mask))
|
||||
dev_dbg(pipe->isi->dev, "%s: IRQ OFLW Error stat=0x%X\n",
|
||||
__func__, status);
|
||||
|
||||
if (status & (ier_reg->excs_oflw_y_buf_en.mask |
|
||||
ier_reg->excs_oflw_u_buf_en.mask |
|
||||
ier_reg->excs_oflw_v_buf_en.mask))
|
||||
dev_dbg(pipe->isi->dev, "%s: IRQ EXCS OFLW Error stat=0x%X\n",
|
||||
__func__, status);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Init & cleanup
|
||||
*/
|
||||
|
||||
static const struct media_entity_operations mxc_isi_pipe_entity_ops = {
|
||||
.link_validate = v4l2_subdev_link_validate,
|
||||
};
|
||||
|
||||
int mxc_isi_pipe_init(struct mxc_isi_dev *isi, unsigned int id)
|
||||
{
|
||||
struct mxc_isi_pipe *pipe = &isi->pipes[id];
|
||||
struct v4l2_subdev *sd;
|
||||
int irq;
|
||||
int ret;
|
||||
|
||||
pipe->id = id;
|
||||
pipe->isi = isi;
|
||||
pipe->regs = isi->regs + id * isi->pdata->reg_offset;
|
||||
|
||||
mutex_init(&pipe->lock);
|
||||
|
||||
pipe->available_res = MXC_ISI_CHANNEL_RES_LINE_BUF
|
||||
| MXC_ISI_CHANNEL_RES_OUTPUT_BUF;
|
||||
pipe->acquired_res = 0;
|
||||
pipe->chained_res = 0;
|
||||
pipe->chained = false;
|
||||
|
||||
sd = &pipe->sd;
|
||||
v4l2_subdev_init(sd, &mxc_isi_pipe_subdev_ops);
|
||||
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
|
||||
snprintf(sd->name, sizeof(sd->name), "mxc_isi.%d", pipe->id);
|
||||
sd->dev = isi->dev;
|
||||
|
||||
sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
|
||||
sd->entity.ops = &mxc_isi_pipe_entity_ops;
|
||||
|
||||
pipe->pads[MXC_ISI_PIPE_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
|
||||
pipe->pads[MXC_ISI_PIPE_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
|
||||
|
||||
ret = media_entity_pads_init(&sd->entity, MXC_ISI_PIPE_PADS_NUM,
|
||||
pipe->pads);
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
ret = v4l2_subdev_init_finalize(sd);
|
||||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
/* Register IRQ handler. */
|
||||
mxc_isi_channel_irq_clear(pipe);
|
||||
|
||||
irq = platform_get_irq(to_platform_device(isi->dev), id);
|
||||
if (irq < 0) {
|
||||
dev_err(pipe->isi->dev, "Failed to get IRQ (%d)\n", irq);
|
||||
ret = irq;
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(isi->dev, irq, mxc_isi_pipe_irq_handler,
|
||||
0, dev_name(isi->dev), pipe);
|
||||
if (ret < 0) {
|
||||
dev_err(isi->dev, "failed to request IRQ (%d)\n", ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
media_entity_cleanup(&sd->entity);
|
||||
mutex_destroy(&pipe->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mxc_isi_pipe_cleanup(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
struct v4l2_subdev *sd = &pipe->sd;
|
||||
|
||||
media_entity_cleanup(&sd->entity);
|
||||
mutex_destroy(&pipe->lock);
|
||||
}
|
||||
|
||||
int mxc_isi_pipe_acquire(struct mxc_isi_pipe *pipe,
|
||||
mxc_isi_pipe_irq_t irq_handler)
|
||||
{
|
||||
const struct mxc_isi_bus_format_info *sink_info;
|
||||
const struct mxc_isi_bus_format_info *src_info;
|
||||
struct v4l2_mbus_framefmt *sink_fmt;
|
||||
const struct v4l2_mbus_framefmt *src_fmt;
|
||||
struct v4l2_subdev *sd = &pipe->sd;
|
||||
struct v4l2_subdev_state *state;
|
||||
bool bypass;
|
||||
int ret;
|
||||
|
||||
state = v4l2_subdev_lock_and_get_active_state(sd);
|
||||
sink_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SINK);
|
||||
src_fmt = v4l2_subdev_get_try_format(sd, state, MXC_ISI_PIPE_PAD_SOURCE);
|
||||
v4l2_subdev_unlock_state(state);
|
||||
|
||||
sink_info = mxc_isi_bus_format_by_code(sink_fmt->code,
|
||||
MXC_ISI_PIPE_PAD_SINK);
|
||||
src_info = mxc_isi_bus_format_by_code(src_fmt->code,
|
||||
MXC_ISI_PIPE_PAD_SOURCE);
|
||||
|
||||
bypass = sink_fmt->width == src_fmt->width &&
|
||||
sink_fmt->height == src_fmt->height &&
|
||||
sink_info->encoding == src_info->encoding;
|
||||
|
||||
ret = mxc_isi_channel_acquire(pipe, irq_handler, bypass);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Chain the channel if needed for wide resolutions. */
|
||||
if (sink_fmt->width > MXC_ISI_MAX_WIDTH_UNCHAINED) {
|
||||
ret = mxc_isi_channel_chain(pipe, bypass);
|
||||
if (ret)
|
||||
mxc_isi_channel_release(pipe);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mxc_isi_pipe_release(struct mxc_isi_pipe *pipe)
|
||||
{
|
||||
mxc_isi_channel_release(pipe);
|
||||
mxc_isi_channel_unchain(pipe);
|
||||
}
|
418
drivers/media/platform/nxp/imx8-isi/imx8-isi-regs.h
Normal file
418
drivers/media/platform/nxp/imx8-isi/imx8-isi-regs.h
Normal file
@ -0,0 +1,418 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright 2019-2020 NXP
|
||||
*/
|
||||
|
||||
#ifndef __IMX8_ISI_REGS_H__
|
||||
#define __IMX8_ISI_REGS_H__
|
||||
|
||||
#include <linux/bits.h>
|
||||
|
||||
/* ISI Registers Define */
|
||||
/* Channel Control Register */
|
||||
#define CHNL_CTRL 0x0000
|
||||
#define CHNL_CTRL_CHNL_EN BIT(31)
|
||||
#define CHNL_CTRL_CLK_EN BIT(30)
|
||||
#define CHNL_CTRL_CHNL_BYPASS BIT(29)
|
||||
#define CHNL_CTRL_CHAIN_BUF(n) ((n) << 25)
|
||||
#define CHNL_CTRL_CHAIN_BUF_MASK GENMASK(26, 25)
|
||||
#define CHNL_CTRL_CHAIN_BUF_NO_CHAIN 0
|
||||
#define CHNL_CTRL_CHAIN_BUF_2_CHAIN 1
|
||||
#define CHNL_CTRL_SW_RST BIT(24)
|
||||
#define CHNL_CTRL_BLANK_PXL(n) ((n) << 16)
|
||||
#define CHNL_CTRL_BLANK_PXL_MASK GENMASK(23, 16)
|
||||
#define CHNL_CTRL_MIPI_VC_ID(n) ((n) << 6)
|
||||
#define CHNL_CTRL_MIPI_VC_ID_MASK GENMASK(7, 6)
|
||||
#define CHNL_CTRL_SRC_TYPE(n) ((n) << 4)
|
||||
#define CHNL_CTRL_SRC_TYPE_MASK BIT(4)
|
||||
#define CHNL_CTRL_SRC_TYPE_DEVICE 0
|
||||
#define CHNL_CTRL_SRC_TYPE_MEMORY 1
|
||||
#define CHNL_CTRL_SRC_INPUT(n) ((n) << 0)
|
||||
#define CHNL_CTRL_SRC_INPUT_MASK GENMASK(2, 0)
|
||||
|
||||
/* Channel Image Control Register */
|
||||
#define CHNL_IMG_CTRL 0x0004
|
||||
#define CHNL_IMG_CTRL_FORMAT(n) ((n) << 24)
|
||||
#define CHNL_IMG_CTRL_FORMAT_MASK GENMASK(29, 24)
|
||||
#define CHNL_IMG_CTRL_FORMAT_RGBA8888 0x00
|
||||
#define CHNL_IMG_CTRL_FORMAT_ABGR8888 0x01
|
||||
#define CHNL_IMG_CTRL_FORMAT_ARGB8888 0x02
|
||||
#define CHNL_IMG_CTRL_FORMAT_RGBX888 0x03
|
||||
#define CHNL_IMG_CTRL_FORMAT_XBGR888 0x04
|
||||
#define CHNL_IMG_CTRL_FORMAT_XRGB888 0x05
|
||||
#define CHNL_IMG_CTRL_FORMAT_RGB888P 0x06
|
||||
#define CHNL_IMG_CTRL_FORMAT_BGR888P 0x07
|
||||
#define CHNL_IMG_CTRL_FORMAT_A2BGR10 0x08
|
||||
#define CHNL_IMG_CTRL_FORMAT_A2RGB10 0x09
|
||||
#define CHNL_IMG_CTRL_FORMAT_RGB565 0x0a
|
||||
#define CHNL_IMG_CTRL_FORMAT_RAW8 0x0b
|
||||
#define CHNL_IMG_CTRL_FORMAT_RAW10 0x0c
|
||||
#define CHNL_IMG_CTRL_FORMAT_RAW10P 0x0d
|
||||
#define CHNL_IMG_CTRL_FORMAT_RAW12 0x0e
|
||||
#define CHNL_IMG_CTRL_FORMAT_RAW16 0x0f
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV444_1P8P 0x10
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV444_2P8P 0x11
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV444_3P8P 0x12
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV444_1P8 0x13
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV444_1P10 0x14
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV444_2P10 0x15
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV444_3P10 0x16
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV444_1P10P 0x18
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV444_2P10P 0x19
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV444_3P10P 0x1a
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV444_1P12 0x1c
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV444_2P12 0x1d
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV444_3P12 0x1e
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV422_1P8P 0x20
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV422_2P8P 0x21
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV422_3P8P 0x22
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV422_1P10 0x24
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV422_2P10 0x25
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV422_3P10 0x26
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV422_1P10P 0x28
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV422_2P10P 0x29
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV422_3P10P 0x2a
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV422_1P12 0x2c
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV422_2P12 0x2d
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV422_3P12 0x2e
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV420_2P8P 0x31
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV420_3P8P 0x32
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV420_2P10 0x35
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV420_3P10 0x36
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV420_2P10P 0x39
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV420_3P10P 0x3a
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV420_2P12 0x3d
|
||||
#define CHNL_IMG_CTRL_FORMAT_YUV420_3P12 0x3e
|
||||
#define CHNL_IMG_CTRL_GBL_ALPHA_VAL(n) ((n) << 16)
|
||||
#define CHNL_IMG_CTRL_GBL_ALPHA_VAL_MASK GENMASK(23, 16)
|
||||
#define CHNL_IMG_CTRL_GBL_ALPHA_EN BIT(15)
|
||||
#define CHNL_IMG_CTRL_DEINT(n) ((n) << 12)
|
||||
#define CHNL_IMG_CTRL_DEINT_MASK GENMASK(14, 12)
|
||||
#define CHNL_IMG_CTRL_DEINT_WEAVE_ODD_EVEN 2
|
||||
#define CHNL_IMG_CTRL_DEINT_WEAVE_EVEN_ODD 3
|
||||
#define CHNL_IMG_CTRL_DEINT_BLEND_ODD_EVEN 4
|
||||
#define CHNL_IMG_CTRL_DEINT_BLEND_EVEN_ODD 5
|
||||
#define CHNL_IMG_CTRL_DEINT_LDOUBLE_ODD_EVEN 6
|
||||
#define CHNL_IMG_CTRL_DEINT_LDOUBLE_EVEN_ODD 7
|
||||
#define CHNL_IMG_CTRL_DEC_X(n) ((n) << 10)
|
||||
#define CHNL_IMG_CTRL_DEC_X_MASK GENMASK(11, 10)
|
||||
#define CHNL_IMG_CTRL_DEC_Y(n) ((n) << 8)
|
||||
#define CHNL_IMG_CTRL_DEC_Y_MASK GENMASK(9, 8)
|
||||
#define CHNL_IMG_CTRL_CROP_EN BIT(7)
|
||||
#define CHNL_IMG_CTRL_VFLIP_EN BIT(6)
|
||||
#define CHNL_IMG_CTRL_HFLIP_EN BIT(5)
|
||||
#define CHNL_IMG_CTRL_YCBCR_MODE BIT(3)
|
||||
#define CHNL_IMG_CTRL_CSC_MODE(n) ((n) << 1)
|
||||
#define CHNL_IMG_CTRL_CSC_MODE_MASK GENMASK(2, 1)
|
||||
#define CHNL_IMG_CTRL_CSC_MODE_YUV2RGB 0
|
||||
#define CHNL_IMG_CTRL_CSC_MODE_YCBCR2RGB 1
|
||||
#define CHNL_IMG_CTRL_CSC_MODE_RGB2YUV 2
|
||||
#define CHNL_IMG_CTRL_CSC_MODE_RGB2YCBCR 3
|
||||
#define CHNL_IMG_CTRL_CSC_BYPASS BIT(0)
|
||||
|
||||
/* Channel Output Buffer Control Register */
|
||||
#define CHNL_OUT_BUF_CTRL 0x0008
|
||||
#define CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR BIT(15)
|
||||
#define CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR BIT(14)
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V(n) ((n) << 6)
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_MASK GENMASK(7, 6)
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_NO_PANIC 0
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_PANIC_25 1
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_PANIC_50 2
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_PANIC_75 3
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U(n) ((n) << 3)
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_MASK GENMASK(4, 3)
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_NO_PANIC 0
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_PANIC_25 1
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_PANIC_50 2
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_PANIC_75 3
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y(n) ((n) << 0)
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_MASK GENMASK(1, 0)
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_NO_PANIC 0
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_PANIC_25 1
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_PANIC_50 2
|
||||
#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_PANIC_75 3
|
||||
|
||||
/* Channel Image Configuration */
|
||||
#define CHNL_IMG_CFG 0x000c
|
||||
#define CHNL_IMG_CFG_HEIGHT(n) ((n) << 16)
|
||||
#define CHNL_IMG_CFG_HEIGHT_MASK GENMASK(28, 16)
|
||||
#define CHNL_IMG_CFG_WIDTH(n) ((n) << 0)
|
||||
#define CHNL_IMG_CFG_WIDTH_MASK GENMASK(12, 0)
|
||||
|
||||
/* Channel Interrupt Enable Register */
|
||||
#define CHNL_IER 0x0010
|
||||
#define CHNL_IER_MEM_RD_DONE_EN BIT(31)
|
||||
#define CHNL_IER_LINE_RCVD_EN BIT(30)
|
||||
#define CHNL_IER_FRM_RCVD_EN BIT(29)
|
||||
#define CHNL_IER_AXI_WR_ERR_V_EN BIT(28)
|
||||
#define CHNL_IER_AXI_WR_ERR_U_EN BIT(27)
|
||||
#define CHNL_IER_AXI_WR_ERR_Y_EN BIT(26)
|
||||
#define CHNL_IER_AXI_RD_ERR_EN BIT(25)
|
||||
|
||||
/* Channel Status Register */
|
||||
#define CHNL_STS 0x0014
|
||||
#define CHNL_STS_MEM_RD_DONE BIT(31)
|
||||
#define CHNL_STS_LINE_STRD BIT(30)
|
||||
#define CHNL_STS_FRM_STRD BIT(29)
|
||||
#define CHNL_STS_AXI_WR_ERR_V BIT(28)
|
||||
#define CHNL_STS_AXI_WR_ERR_U BIT(27)
|
||||
#define CHNL_STS_AXI_WR_ERR_Y BIT(26)
|
||||
#define CHNL_STS_AXI_RD_ERR BIT(25)
|
||||
#define CHNL_STS_OFLW_PANIC_V_BUF BIT(24)
|
||||
#define CHNL_STS_EXCS_OFLW_V_BUF BIT(23)
|
||||
#define CHNL_STS_OFLW_V_BUF BIT(22)
|
||||
#define CHNL_STS_OFLW_PANIC_U_BUF BIT(21)
|
||||
#define CHNL_STS_EXCS_OFLW_U_BUF BIT(20)
|
||||
#define CHNL_STS_OFLW_U_BUF BIT(19)
|
||||
#define CHNL_STS_OFLW_PANIC_Y_BUF BIT(18)
|
||||
#define CHNL_STS_EXCS_OFLW_Y_BUF BIT(17)
|
||||
#define CHNL_STS_OFLW_Y_BUF BIT(16)
|
||||
#define CHNL_STS_EARLY_VSYNC_ERR BIT(15)
|
||||
#define CHNL_STS_LATE_VSYNC_ERR BIT(14)
|
||||
#define CHNL_STS_MEM_RD_OFLOW BIT(10)
|
||||
#define CHNL_STS_BUF2_ACTIVE BIT(9)
|
||||
#define CHNL_STS_BUF1_ACTIVE BIT(8)
|
||||
#define CHNL_STS_OFLW_BYTES(n) ((n) << 0)
|
||||
#define CHNL_STS_OFLW_BYTES_MASK GENMASK(7, 0)
|
||||
|
||||
/* Channel Scale Factor Register */
|
||||
#define CHNL_SCALE_FACTOR 0x0018
|
||||
#define CHNL_SCALE_FACTOR_Y_SCALE(n) ((n) << 16)
|
||||
#define CHNL_SCALE_FACTOR_Y_SCALE_MASK GENMASK(29, 16)
|
||||
#define CHNL_SCALE_FACTOR_X_SCALE(n) ((n) << 0)
|
||||
#define CHNL_SCALE_FACTOR_X_SCALE_MASK GENMASK(13, 0)
|
||||
|
||||
/* Channel Scale Offset Register */
|
||||
#define CHNL_SCALE_OFFSET 0x001c
|
||||
#define CHNL_SCALE_OFFSET_Y_SCALE(n) ((n) << 16)
|
||||
#define CHNL_SCALE_OFFSET_Y_SCALE_MASK GENMASK(27, 16)
|
||||
#define CHNL_SCALE_OFFSET_X_SCALE(n) ((n) << 0)
|
||||
#define CHNL_SCALE_OFFSET_X_SCALE_MASK GENMASK(11, 0)
|
||||
|
||||
/* Channel Crop Upper Left Corner Coordinate Register */
|
||||
#define CHNL_CROP_ULC 0x0020
|
||||
#define CHNL_CROP_ULC_X(n) ((n) << 16)
|
||||
#define CHNL_CROP_ULC_X_MASK GENMASK(27, 16)
|
||||
#define CHNL_CROP_ULC_Y(n) ((n) << 0)
|
||||
#define CHNL_CROP_ULC_Y_MASK GENMASK(11, 0)
|
||||
|
||||
/* Channel Crop Lower Right Corner Coordinate Register */
|
||||
#define CHNL_CROP_LRC 0x0024
|
||||
#define CHNL_CROP_LRC_X(n) ((n) << 16)
|
||||
#define CHNL_CROP_LRC_X_MASK GENMASK(27, 16)
|
||||
#define CHNL_CROP_LRC_Y(n) ((n) << 0)
|
||||
#define CHNL_CROP_LRC_Y_MASK GENMASK(11, 0)
|
||||
|
||||
/* Channel Color Space Conversion Coefficient Register 0 */
|
||||
#define CHNL_CSC_COEFF0 0x0028
|
||||
#define CHNL_CSC_COEFF0_A2(n) ((n) << 16)
|
||||
#define CHNL_CSC_COEFF0_A2_MASK GENMASK(26, 16)
|
||||
#define CHNL_CSC_COEFF0_A1(n) ((n) << 0)
|
||||
#define CHNL_CSC_COEFF0_A1_MASK GENMASK(10, 0)
|
||||
|
||||
/* Channel Color Space Conversion Coefficient Register 1 */
|
||||
#define CHNL_CSC_COEFF1 0x002c
|
||||
#define CHNL_CSC_COEFF1_B1(n) ((n) << 16)
|
||||
#define CHNL_CSC_COEFF1_B1_MASK GENMASK(26, 16)
|
||||
#define CHNL_CSC_COEFF1_A3(n) ((n) << 0)
|
||||
#define CHNL_CSC_COEFF1_A3_MASK GENMASK(10, 0)
|
||||
|
||||
/* Channel Color Space Conversion Coefficient Register 2 */
|
||||
#define CHNL_CSC_COEFF2 0x0030
|
||||
#define CHNL_CSC_COEFF2_B3(n) ((n) << 16)
|
||||
#define CHNL_CSC_COEFF2_B3_MASK GENMASK(26, 16)
|
||||
#define CHNL_CSC_COEFF2_B2(n) ((n) << 0)
|
||||
#define CHNL_CSC_COEFF2_B2_MASK GENMASK(10, 0)
|
||||
|
||||
/* Channel Color Space Conversion Coefficient Register 3 */
|
||||
#define CHNL_CSC_COEFF3 0x0034
|
||||
#define CHNL_CSC_COEFF3_C2(n) ((n) << 16)
|
||||
#define CHNL_CSC_COEFF3_C2_MASK GENMASK(26, 16)
|
||||
#define CHNL_CSC_COEFF3_C1(n) ((n) << 0)
|
||||
#define CHNL_CSC_COEFF3_C1_MASK GENMASK(10, 0)
|
||||
|
||||
/* Channel Color Space Conversion Coefficient Register 4 */
|
||||
#define CHNL_CSC_COEFF4 0x0038
|
||||
#define CHNL_CSC_COEFF4_D1(n) ((n) << 16)
|
||||
#define CHNL_CSC_COEFF4_D1_MASK GENMASK(24, 16)
|
||||
#define CHNL_CSC_COEFF4_C3(n) ((n) << 0)
|
||||
#define CHNL_CSC_COEFF4_C3_MASK GENMASK(10, 0)
|
||||
|
||||
/* Channel Color Space Conversion Coefficient Register 5 */
|
||||
#define CHNL_CSC_COEFF5 0x003c
|
||||
#define CHNL_CSC_COEFF5_D3(n) ((n) << 16)
|
||||
#define CHNL_CSC_COEFF5_D3_MASK GENMASK(24, 16)
|
||||
#define CHNL_CSC_COEFF5_D2(n) ((n) << 0)
|
||||
#define CHNL_CSC_COEFF5_D2_MASK GENMASK(8, 0)
|
||||
|
||||
/* Channel Alpha Value Register for ROI 0 */
|
||||
#define CHNL_ROI_0_ALPHA 0x0040
|
||||
#define CHNL_ROI_0_ALPHA_VAL(n) ((n) << 24)
|
||||
#define CHNL_ROI_0_ALPHA_MASK GENMASK(31, 24)
|
||||
#define CHNL_ROI_0_ALPHA_EN BIT(16)
|
||||
|
||||
/* Channel Upper Left Coordinate Register for ROI 0 */
|
||||
#define CHNL_ROI_0_ULC 0x0044
|
||||
#define CHNL_ROI_0_ULC_X(n) ((n) << 16)
|
||||
#define CHNL_ROI_0_ULC_X_MASK GENMASK(27, 16)
|
||||
#define CHNL_ROI_0_ULC_Y(n) ((n) << 0)
|
||||
#define CHNL_ROI_0_ULC_Y_MASK GENMASK(11, 0)
|
||||
|
||||
/* Channel Lower Right Coordinate Register for ROI 0 */
|
||||
#define CHNL_ROI_0_LRC 0x0048
|
||||
#define CHNL_ROI_0_LRC_X(n) ((n) << 16)
|
||||
#define CHNL_ROI_0_LRC_X_MASK GENMASK(27, 16)
|
||||
#define CHNL_ROI_0_LRC_Y(n) ((n) << 0)
|
||||
#define CHNL_ROI_0_LRC_Y_MASK GENMASK(11, 0)
|
||||
|
||||
/* Channel Alpha Value Register for ROI 1 */
|
||||
#define CHNL_ROI_1_ALPHA 0x004c
|
||||
#define CHNL_ROI_1_ALPHA_VAL(n) ((n) << 24)
|
||||
#define CHNL_ROI_1_ALPHA_MASK GENMASK(31, 24)
|
||||
#define CHNL_ROI_1_ALPHA_EN BIT(16)
|
||||
|
||||
/* Channel Upper Left Coordinate Register for ROI 1 */
|
||||
#define CHNL_ROI_1_ULC 0x0050
|
||||
#define CHNL_ROI_1_ULC_X(n) ((n) << 16)
|
||||
#define CHNL_ROI_1_ULC_X_MASK GENMASK(27, 16)
|
||||
#define CHNL_ROI_1_ULC_Y(n) ((n) << 0)
|
||||
#define CHNL_ROI_1_ULC_Y_MASK GENMASK(11, 0)
|
||||
|
||||
/* Channel Lower Right Coordinate Register for ROI 1 */
|
||||
#define CHNL_ROI_1_LRC 0x0054
|
||||
#define CHNL_ROI_1_LRC_X(n) ((n) << 16)
|
||||
#define CHNL_ROI_1_LRC_X_MASK GENMASK(27, 16)
|
||||
#define CHNL_ROI_1_LRC_Y(n) ((n) << 0)
|
||||
#define CHNL_ROI_1_LRC_Y_MASK GENMASK(11, 0)
|
||||
|
||||
/* Channel Alpha Value Register for ROI 2 */
|
||||
#define CHNL_ROI_2_ALPHA 0x0058
|
||||
#define CHNL_ROI_2_ALPHA_VAL(n) ((n) << 24)
|
||||
#define CHNL_ROI_2_ALPHA_MASK GENMASK(31, 24)
|
||||
#define CHNL_ROI_2_ALPHA_EN BIT(16)
|
||||
|
||||
/* Channel Upper Left Coordinate Register for ROI 2 */
|
||||
#define CHNL_ROI_2_ULC 0x005c
|
||||
#define CHNL_ROI_2_ULC_X(n) ((n) << 16)
|
||||
#define CHNL_ROI_2_ULC_X_MASK GENMASK(27, 16)
|
||||
#define CHNL_ROI_2_ULC_Y(n) ((n) << 0)
|
||||
#define CHNL_ROI_2_ULC_Y_MASK GENMASK(11, 0)
|
||||
|
||||
/* Channel Lower Right Coordinate Register for ROI 2 */
|
||||
#define CHNL_ROI_2_LRC 0x0060
|
||||
#define CHNL_ROI_2_LRC_X(n) ((n) << 16)
|
||||
#define CHNL_ROI_2_LRC_X_MASK GENMASK(27, 16)
|
||||
#define CHNL_ROI_2_LRC_Y(n) ((n) << 0)
|
||||
#define CHNL_ROI_2_LRC_Y_MASK GENMASK(11, 0)
|
||||
|
||||
/* Channel Alpha Value Register for ROI 3 */
|
||||
#define CHNL_ROI_3_ALPHA 0x0064
|
||||
#define CHNL_ROI_3_ALPHA_VAL(n) ((n) << 24)
|
||||
#define CHNL_ROI_3_ALPHA_MASK GENMASK(31, 24)
|
||||
#define CHNL_ROI_3_ALPHA_EN BIT(16)
|
||||
|
||||
/* Channel Upper Left Coordinate Register for ROI 3 */
|
||||
#define CHNL_ROI_3_ULC 0x0068
|
||||
#define CHNL_ROI_3_ULC_X(n) ((n) << 16)
|
||||
#define CHNL_ROI_3_ULC_X_MASK GENMASK(27, 16)
|
||||
#define CHNL_ROI_3_ULC_Y(n) ((n) << 0)
|
||||
#define CHNL_ROI_3_ULC_Y_MASK GENMASK(11, 0)
|
||||
|
||||
/* Channel Lower Right Coordinate Register for ROI 3 */
|
||||
#define CHNL_ROI_3_LRC 0x006c
|
||||
#define CHNL_ROI_3_LRC_X(n) ((n) << 16)
|
||||
#define CHNL_ROI_3_LRC_X_MASK GENMASK(27, 16)
|
||||
#define CHNL_ROI_3_LRC_Y(n) ((n) << 0)
|
||||
#define CHNL_ROI_3_LRC_Y_MASK GENMASK(11, 0)
|
||||
/* Channel RGB or Luma (Y) Output Buffer 1 Address */
|
||||
#define CHNL_OUT_BUF1_ADDR_Y 0x0070
|
||||
|
||||
/* Channel Chroma (U/Cb/UV/CbCr) Output Buffer 1 Address */
|
||||
#define CHNL_OUT_BUF1_ADDR_U 0x0074
|
||||
|
||||
/* Channel Chroma (V/Cr) Output Buffer 1 Address */
|
||||
#define CHNL_OUT_BUF1_ADDR_V 0x0078
|
||||
|
||||
/* Channel Output Buffer Pitch */
|
||||
#define CHNL_OUT_BUF_PITCH 0x007c
|
||||
#define CHNL_OUT_BUF_PITCH_LINE_PITCH(n) ((n) << 0)
|
||||
#define CHNL_OUT_BUF_PITCH_LINE_PITCH_MASK GENMASK(15, 0)
|
||||
|
||||
/* Channel Input Buffer Address */
|
||||
#define CHNL_IN_BUF_ADDR 0x0080
|
||||
|
||||
/* Channel Input Buffer Pitch */
|
||||
#define CHNL_IN_BUF_PITCH 0x0084
|
||||
#define CHNL_IN_BUF_PITCH_FRM_PITCH(n) ((n) << 16)
|
||||
#define CHNL_IN_BUF_PITCH_FRM_PITCH_MASK GENMASK(31, 16)
|
||||
#define CHNL_IN_BUF_PITCH_LINE_PITCH(n) ((n) << 0)
|
||||
#define CHNL_IN_BUF_PITCH_LINE_PITCH_MASK GENMASK(15, 0)
|
||||
|
||||
/* Channel Memory Read Control */
|
||||
#define CHNL_MEM_RD_CTRL 0x0088
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE(n) ((n) << 28)
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_MASK GENMASK(31, 28)
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_BGR8P 0x00
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_RGB8P 0x01
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_XRGB8 0x02
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_RGBX8 0x03
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_XBGR8 0x04
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_RGB565 0x05
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_A2BGR10 0x06
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_A2RGB10 0x07
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV444_1P8P 0x08
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV444_1P10 0x09
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV444_1P10P 0x0a
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV444_1P12 0x0b
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV444_1P8 0x0c
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV422_1P8P 0x0d
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV422_1P10 0x0e
|
||||
#define CHNL_MEM_RD_CTRL_IMG_TYPE_YUV422_1P12 0x0f
|
||||
#define CHNL_MEM_RD_CTRL_READ_MEM BIT(0)
|
||||
|
||||
/* Channel RGB or Luma (Y) Output Buffer 2 Address */
|
||||
#define CHNL_OUT_BUF2_ADDR_Y 0x008c
|
||||
|
||||
/* Channel Chroma (U/Cb/UV/CbCr) Output Buffer 2 Address */
|
||||
#define CHNL_OUT_BUF2_ADDR_U 0x0090
|
||||
|
||||
/* Channel Chroma (V/Cr) Output Buffer 2 Address */
|
||||
#define CHNL_OUT_BUF2_ADDR_V 0x0094
|
||||
|
||||
/* Channel scale image config */
|
||||
#define CHNL_SCL_IMG_CFG 0x0098
|
||||
#define CHNL_SCL_IMG_CFG_HEIGHT(n) ((n) << 16)
|
||||
#define CHNL_SCL_IMG_CFG_HEIGHT_MASK GENMASK(28, 16)
|
||||
#define CHNL_SCL_IMG_CFG_WIDTH(n) ((n) << 0)
|
||||
#define CHNL_SCL_IMG_CFG_WIDTH_MASK GENMASK(12, 0)
|
||||
|
||||
/* Channel Flow Control Register */
|
||||
#define CHNL_FLOW_CTRL 0x009c
|
||||
#define CHNL_FLOW_CTRL_FC_DENOM_MASK GENMASK(7, 0)
|
||||
#define CHNL_FLOW_CTRL_FC_DENOM(n) ((n) << 0)
|
||||
#define CHNL_FLOW_CTRL_FC_NUMER_MASK GENMASK(23, 16)
|
||||
#define CHNL_FLOW_CTRL_FC_NUMER(n) ((n) << 0)
|
||||
|
||||
/* Channel Output Y-Buffer 1 Extended Address Bits */
|
||||
#define CHNL_Y_BUF1_XTND_ADDR 0x00a0
|
||||
|
||||
/* Channel Output U-Buffer 1 Extended Address Bits */
|
||||
#define CHNL_U_BUF1_XTND_ADDR 0x00a4
|
||||
|
||||
/* Channel Output V-Buffer 1 Extended Address Bits */
|
||||
#define CHNL_V_BUF1_XTND_ADDR 0x00a8
|
||||
|
||||
/* Channel Output Y-Buffer 2 Extended Address Bits */
|
||||
#define CHNL_Y_BUF2_XTND_ADDR 0x00ac
|
||||
|
||||
/* Channel Output U-Buffer 2 Extended Address Bits */
|
||||
#define CHNL_U_BUF2_XTND_ADDR 0x00b0
|
||||
|
||||
/* Channel Output V-Buffer 2 Extended Address Bits */
|
||||
#define CHNL_V_BUF2_XTND_ADDR 0x00b4
|
||||
|
||||
/* Channel Input Buffer Extended Address Bits */
|
||||
#define CHNL_IN_BUF_XTND_ADDR 0x00b8
|
||||
|
||||
#endif /* __IMX8_ISI_REGS_H__ */
|
1512
drivers/media/platform/nxp/imx8-isi/imx8-isi-video.c
Normal file
1512
drivers/media/platform/nxp/imx8-isi/imx8-isi-video.c
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user