media: raspberrypi: Add support for RP1-CFE

Add support for Raspberry Pi CFE. The CFE is a hardware block that
contains:

- MIPI D-PHY
- MIPI CSI-2 receiver
- Front End ISP (FE)

The driver has been upported from the Raspberry Pi kernel commit
88a681df9623 ("ARM: dts: bcm2712-rpi: Add i2c<n>_pins labels").

Co-developed-by: Naushir Patuck <naush@raspberrypi.com>
Signed-off-by: Naushir Patuck <naush@raspberrypi.com>
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
This commit is contained in:
Tomi Valkeinen 2024-10-03 13:31:12 +03:00 committed by Mauro Carvalho Chehab
parent 1358bb5239
commit 6edb685abb
17 changed files with 4989 additions and 0 deletions

View File

@ -19372,6 +19372,13 @@ F: Documentation/devicetree/bindings/media/raspberrypi,pispbe.yaml
F: drivers/media/platform/raspberrypi/pisp_be/
F: include/uapi/linux/media/raspberrypi/
RASPBERRY PI PISP CAMERA FRONT END
M: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
M: Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
S: Maintained
F: Documentation/devicetree/bindings/media/raspberrypi,rp1-cfe.yaml
F: drivers/media/platform/raspberrypi/rp1-cfe/
RC-CORE / LIRC FRAMEWORK
M: Sean Young <sean@mess.org>
L: linux-media@vger.kernel.org

View File

@ -3,3 +3,4 @@
comment "Raspberry Pi media platform drivers"
source "drivers/media/platform/raspberrypi/pisp_be/Kconfig"
source "drivers/media/platform/raspberrypi/rp1-cfe/Kconfig"

View File

@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-y += pisp_be/
obj-y += rp1-cfe/

View File

@ -0,0 +1,15 @@
# RP1 V4L2 camera support
config VIDEO_RP1_CFE
tristate "Raspberry Pi RP1 Camera Front End (CFE) video capture driver"
depends on VIDEO_DEV
depends on PM
select VIDEO_V4L2_SUBDEV_API
select MEDIA_CONTROLLER
select VIDEOBUF2_DMA_CONTIG
select V4L2_FWNODE
help
Say Y here to enable support for the Raspberry Pi RP1 Camera Front End.
To compile this driver as a module, choose M here. The module will be
called rp1-cfe.

View File

@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for RP1 Camera Front End driver
#
rp1-cfe-objs := cfe.o csi2.o pisp-fe.o dphy.o
obj-$(CONFIG_VIDEO_RP1_CFE) += rp1-cfe.o

View File

@ -0,0 +1,332 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* RP1 Camera Front End formats definition
*
* Copyright (C) 2021-2024 - Raspberry Pi Ltd.
*/
#ifndef _CFE_FMTS_H_
#define _CFE_FMTS_H_
#include "cfe.h"
#include <media/mipi-csi2.h>
static const struct cfe_fmt formats[] = {
/* YUV Formats */
{
.fourcc = V4L2_PIX_FMT_YUYV,
.code = MEDIA_BUS_FMT_YUYV8_1X16,
.depth = 16,
.csi_dt = MIPI_CSI2_DT_YUV422_8B,
},
{
.fourcc = V4L2_PIX_FMT_UYVY,
.code = MEDIA_BUS_FMT_UYVY8_1X16,
.depth = 16,
.csi_dt = MIPI_CSI2_DT_YUV422_8B,
},
{
.fourcc = V4L2_PIX_FMT_YVYU,
.code = MEDIA_BUS_FMT_YVYU8_1X16,
.depth = 16,
.csi_dt = MIPI_CSI2_DT_YUV422_8B,
},
{
.fourcc = V4L2_PIX_FMT_VYUY,
.code = MEDIA_BUS_FMT_VYUY8_1X16,
.depth = 16,
.csi_dt = MIPI_CSI2_DT_YUV422_8B,
},
{
/* RGB Formats */
.fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */
.code = MEDIA_BUS_FMT_RGB565_2X8_LE,
.depth = 16,
.csi_dt = MIPI_CSI2_DT_RGB565,
},
{ .fourcc = V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */
.code = MEDIA_BUS_FMT_RGB565_2X8_BE,
.depth = 16,
.csi_dt = MIPI_CSI2_DT_RGB565,
},
{
.fourcc = V4L2_PIX_FMT_RGB555, /* gggbbbbb arrrrrgg */
.code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE,
.depth = 16,
.csi_dt = MIPI_CSI2_DT_RGB555,
},
{
.fourcc = V4L2_PIX_FMT_RGB555X, /* arrrrrgg gggbbbbb */
.code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE,
.depth = 16,
.csi_dt = MIPI_CSI2_DT_RGB555,
},
{
.fourcc = V4L2_PIX_FMT_RGB24, /* rgb */
.code = MEDIA_BUS_FMT_RGB888_1X24,
.depth = 24,
.csi_dt = MIPI_CSI2_DT_RGB888,
},
{
.fourcc = V4L2_PIX_FMT_BGR24, /* bgr */
.code = MEDIA_BUS_FMT_BGR888_1X24,
.depth = 24,
.csi_dt = MIPI_CSI2_DT_RGB888,
},
{
.fourcc = V4L2_PIX_FMT_RGB32, /* argb */
.code = MEDIA_BUS_FMT_ARGB8888_1X32,
.depth = 32,
.csi_dt = 0x0,
},
/* Bayer Formats */
{
.fourcc = V4L2_PIX_FMT_SBGGR8,
.code = MEDIA_BUS_FMT_SBGGR8_1X8,
.depth = 8,
.csi_dt = MIPI_CSI2_DT_RAW8,
.remap = { V4L2_PIX_FMT_SBGGR16, V4L2_PIX_FMT_PISP_COMP1_BGGR },
},
{
.fourcc = V4L2_PIX_FMT_SGBRG8,
.code = MEDIA_BUS_FMT_SGBRG8_1X8,
.depth = 8,
.csi_dt = MIPI_CSI2_DT_RAW8,
.remap = { V4L2_PIX_FMT_SGBRG16, V4L2_PIX_FMT_PISP_COMP1_GBRG },
},
{
.fourcc = V4L2_PIX_FMT_SGRBG8,
.code = MEDIA_BUS_FMT_SGRBG8_1X8,
.depth = 8,
.csi_dt = MIPI_CSI2_DT_RAW8,
.remap = { V4L2_PIX_FMT_SGRBG16, V4L2_PIX_FMT_PISP_COMP1_GRBG },
},
{
.fourcc = V4L2_PIX_FMT_SRGGB8,
.code = MEDIA_BUS_FMT_SRGGB8_1X8,
.depth = 8,
.csi_dt = MIPI_CSI2_DT_RAW8,
.remap = { V4L2_PIX_FMT_SRGGB16, V4L2_PIX_FMT_PISP_COMP1_RGGB },
},
{
.fourcc = V4L2_PIX_FMT_SBGGR10P,
.code = MEDIA_BUS_FMT_SBGGR10_1X10,
.depth = 10,
.csi_dt = MIPI_CSI2_DT_RAW10,
.remap = { V4L2_PIX_FMT_SBGGR16, V4L2_PIX_FMT_PISP_COMP1_BGGR },
},
{
.fourcc = V4L2_PIX_FMT_SGBRG10P,
.code = MEDIA_BUS_FMT_SGBRG10_1X10,
.depth = 10,
.csi_dt = MIPI_CSI2_DT_RAW10,
.remap = { V4L2_PIX_FMT_SGBRG16, V4L2_PIX_FMT_PISP_COMP1_GBRG },
},
{
.fourcc = V4L2_PIX_FMT_SGRBG10P,
.code = MEDIA_BUS_FMT_SGRBG10_1X10,
.depth = 10,
.csi_dt = MIPI_CSI2_DT_RAW10,
.remap = { V4L2_PIX_FMT_SGRBG16, V4L2_PIX_FMT_PISP_COMP1_GRBG },
},
{
.fourcc = V4L2_PIX_FMT_SRGGB10P,
.code = MEDIA_BUS_FMT_SRGGB10_1X10,
.depth = 10,
.csi_dt = MIPI_CSI2_DT_RAW10,
.remap = { V4L2_PIX_FMT_SRGGB16, V4L2_PIX_FMT_PISP_COMP1_RGGB },
},
{
.fourcc = V4L2_PIX_FMT_SBGGR12P,
.code = MEDIA_BUS_FMT_SBGGR12_1X12,
.depth = 12,
.csi_dt = MIPI_CSI2_DT_RAW12,
.remap = { V4L2_PIX_FMT_SBGGR16, V4L2_PIX_FMT_PISP_COMP1_BGGR },
},
{
.fourcc = V4L2_PIX_FMT_SGBRG12P,
.code = MEDIA_BUS_FMT_SGBRG12_1X12,
.depth = 12,
.csi_dt = MIPI_CSI2_DT_RAW12,
.remap = { V4L2_PIX_FMT_SGBRG16, V4L2_PIX_FMT_PISP_COMP1_GBRG },
},
{
.fourcc = V4L2_PIX_FMT_SGRBG12P,
.code = MEDIA_BUS_FMT_SGRBG12_1X12,
.depth = 12,
.csi_dt = MIPI_CSI2_DT_RAW12,
.remap = { V4L2_PIX_FMT_SGRBG16, V4L2_PIX_FMT_PISP_COMP1_GRBG },
},
{
.fourcc = V4L2_PIX_FMT_SRGGB12P,
.code = MEDIA_BUS_FMT_SRGGB12_1X12,
.depth = 12,
.csi_dt = MIPI_CSI2_DT_RAW12,
.remap = { V4L2_PIX_FMT_SRGGB16, V4L2_PIX_FMT_PISP_COMP1_RGGB },
},
{
.fourcc = V4L2_PIX_FMT_SBGGR14P,
.code = MEDIA_BUS_FMT_SBGGR14_1X14,
.depth = 14,
.csi_dt = MIPI_CSI2_DT_RAW14,
.remap = { V4L2_PIX_FMT_SBGGR16, V4L2_PIX_FMT_PISP_COMP1_BGGR },
},
{
.fourcc = V4L2_PIX_FMT_SGBRG14P,
.code = MEDIA_BUS_FMT_SGBRG14_1X14,
.depth = 14,
.csi_dt = MIPI_CSI2_DT_RAW14,
.remap = { V4L2_PIX_FMT_SGBRG16, V4L2_PIX_FMT_PISP_COMP1_GBRG },
},
{
.fourcc = V4L2_PIX_FMT_SGRBG14P,
.code = MEDIA_BUS_FMT_SGRBG14_1X14,
.depth = 14,
.csi_dt = MIPI_CSI2_DT_RAW14,
.remap = { V4L2_PIX_FMT_SGRBG16, V4L2_PIX_FMT_PISP_COMP1_GRBG },
},
{
.fourcc = V4L2_PIX_FMT_SRGGB14P,
.code = MEDIA_BUS_FMT_SRGGB14_1X14,
.depth = 14,
.csi_dt = MIPI_CSI2_DT_RAW14,
.remap = { V4L2_PIX_FMT_SRGGB16, V4L2_PIX_FMT_PISP_COMP1_RGGB },
},
{
.fourcc = V4L2_PIX_FMT_SBGGR16,
.code = MEDIA_BUS_FMT_SBGGR16_1X16,
.depth = 16,
.csi_dt = MIPI_CSI2_DT_RAW16,
.flags = CFE_FORMAT_FLAG_FE_OUT,
.remap = { V4L2_PIX_FMT_SBGGR16, V4L2_PIX_FMT_PISP_COMP1_BGGR },
},
{
.fourcc = V4L2_PIX_FMT_SGBRG16,
.code = MEDIA_BUS_FMT_SGBRG16_1X16,
.depth = 16,
.csi_dt = MIPI_CSI2_DT_RAW16,
.flags = CFE_FORMAT_FLAG_FE_OUT,
.remap = { V4L2_PIX_FMT_SGBRG16, V4L2_PIX_FMT_PISP_COMP1_GBRG },
},
{
.fourcc = V4L2_PIX_FMT_SGRBG16,
.code = MEDIA_BUS_FMT_SGRBG16_1X16,
.depth = 16,
.csi_dt = MIPI_CSI2_DT_RAW16,
.flags = CFE_FORMAT_FLAG_FE_OUT,
.remap = { V4L2_PIX_FMT_SGRBG16, V4L2_PIX_FMT_PISP_COMP1_GRBG },
},
{
.fourcc = V4L2_PIX_FMT_SRGGB16,
.code = MEDIA_BUS_FMT_SRGGB16_1X16,
.depth = 16,
.csi_dt = MIPI_CSI2_DT_RAW16,
.flags = CFE_FORMAT_FLAG_FE_OUT,
.remap = { V4L2_PIX_FMT_SRGGB16, V4L2_PIX_FMT_PISP_COMP1_RGGB },
},
/* PiSP Compressed Mode 1 */
{
.fourcc = V4L2_PIX_FMT_PISP_COMP1_RGGB,
.code = MEDIA_BUS_FMT_SRGGB16_1X16,
.depth = 8,
.flags = CFE_FORMAT_FLAG_FE_OUT,
},
{
.fourcc = V4L2_PIX_FMT_PISP_COMP1_BGGR,
.code = MEDIA_BUS_FMT_SBGGR16_1X16,
.depth = 8,
.flags = CFE_FORMAT_FLAG_FE_OUT,
},
{
.fourcc = V4L2_PIX_FMT_PISP_COMP1_GBRG,
.code = MEDIA_BUS_FMT_SGBRG16_1X16,
.depth = 8,
.flags = CFE_FORMAT_FLAG_FE_OUT,
},
{
.fourcc = V4L2_PIX_FMT_PISP_COMP1_GRBG,
.code = MEDIA_BUS_FMT_SGRBG16_1X16,
.depth = 8,
.flags = CFE_FORMAT_FLAG_FE_OUT,
},
/* Greyscale format */
{
.fourcc = V4L2_PIX_FMT_GREY,
.code = MEDIA_BUS_FMT_Y8_1X8,
.depth = 8,
.csi_dt = MIPI_CSI2_DT_RAW8,
.remap = { V4L2_PIX_FMT_Y16, V4L2_PIX_FMT_PISP_COMP1_MONO },
},
{
.fourcc = V4L2_PIX_FMT_Y10P,
.code = MEDIA_BUS_FMT_Y10_1X10,
.depth = 10,
.csi_dt = MIPI_CSI2_DT_RAW10,
.remap = { V4L2_PIX_FMT_Y16, V4L2_PIX_FMT_PISP_COMP1_MONO },
},
{
.fourcc = V4L2_PIX_FMT_Y12P,
.code = MEDIA_BUS_FMT_Y12_1X12,
.depth = 12,
.csi_dt = MIPI_CSI2_DT_RAW12,
.remap = { V4L2_PIX_FMT_Y16, V4L2_PIX_FMT_PISP_COMP1_MONO },
},
{
.fourcc = V4L2_PIX_FMT_Y14P,
.code = MEDIA_BUS_FMT_Y14_1X14,
.depth = 14,
.csi_dt = MIPI_CSI2_DT_RAW14,
.remap = { V4L2_PIX_FMT_Y16, V4L2_PIX_FMT_PISP_COMP1_MONO },
},
{
.fourcc = V4L2_PIX_FMT_Y16,
.code = MEDIA_BUS_FMT_Y16_1X16,
.depth = 16,
.csi_dt = MIPI_CSI2_DT_RAW16,
.flags = CFE_FORMAT_FLAG_FE_OUT,
.remap = { V4L2_PIX_FMT_Y16, V4L2_PIX_FMT_PISP_COMP1_MONO },
},
{
.fourcc = V4L2_PIX_FMT_PISP_COMP1_MONO,
.code = MEDIA_BUS_FMT_Y16_1X16,
.depth = 8,
.flags = CFE_FORMAT_FLAG_FE_OUT,
},
/* Embedded data formats */
{
.fourcc = V4L2_META_FMT_GENERIC_8,
.code = MEDIA_BUS_FMT_META_8,
.depth = 8,
.csi_dt = MIPI_CSI2_DT_EMBEDDED_8B,
.flags = CFE_FORMAT_FLAG_META_CAP,
},
{
.fourcc = V4L2_META_FMT_GENERIC_CSI2_10,
.code = MEDIA_BUS_FMT_META_10,
.depth = 10,
.csi_dt = MIPI_CSI2_DT_EMBEDDED_8B,
.flags = CFE_FORMAT_FLAG_META_CAP,
},
{
.fourcc = V4L2_META_FMT_GENERIC_CSI2_12,
.code = MEDIA_BUS_FMT_META_12,
.depth = 12,
.csi_dt = MIPI_CSI2_DT_EMBEDDED_8B,
.flags = CFE_FORMAT_FLAG_META_CAP,
},
/* Frontend formats */
{
.fourcc = V4L2_META_FMT_RPI_FE_CFG,
.code = MEDIA_BUS_FMT_FIXED,
.flags = CFE_FORMAT_FLAG_META_OUT,
},
{
.fourcc = V4L2_META_FMT_RPI_FE_STATS,
.code = MEDIA_BUS_FMT_FIXED,
.flags = CFE_FORMAT_FLAG_META_CAP,
},
};
#endif /* _CFE_FMTS_H_ */

View File

@ -0,0 +1,202 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2024 Raspberry Pi Ltd.
* Copyright (c) 2024 Ideas on Board Oy
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM cfe
#if !defined(_CFE_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define _CFE_TRACE_H
#include <linux/tracepoint.h>
#include <media/videobuf2-v4l2.h>
TRACE_EVENT(cfe_return_buffer,
TP_PROTO(u32 node_id, u32 buf_idx, u32 queue_id),
TP_ARGS(node_id, buf_idx, queue_id),
TP_STRUCT__entry(
__field(u32, node_id)
__field(u32, buf_idx)
__field(u32, queue_id)
),
TP_fast_assign(
__entry->node_id = node_id;
__entry->buf_idx = buf_idx;
__entry->queue_id = queue_id;
),
TP_printk("node=%u buf=%u, queue=%u", __entry->node_id,
__entry->buf_idx, __entry->queue_id)
);
DECLARE_EVENT_CLASS(cfe_buffer_template,
TP_PROTO(u32 node_id, struct vb2_buffer *buf),
TP_ARGS(node_id, buf),
TP_STRUCT__entry(
__field(u32, node_id)
__field(u32, buf_idx)
),
TP_fast_assign(
__entry->node_id = node_id;
__entry->buf_idx = buf->index;
),
TP_printk("node=%u buf=%u", __entry->node_id, __entry->buf_idx)
);
DEFINE_EVENT(cfe_buffer_template, cfe_buffer_prepare,
TP_PROTO(u32 node_id, struct vb2_buffer *buf),
TP_ARGS(node_id, buf));
TRACE_EVENT(cfe_buffer_queue,
TP_PROTO(u32 node_id, struct vb2_buffer *buf, bool schedule_now),
TP_ARGS(node_id, buf, schedule_now),
TP_STRUCT__entry(
__field(u32, node_id)
__field(u32, buf_idx)
__field(bool, schedule_now)
),
TP_fast_assign(
__entry->node_id = node_id;
__entry->buf_idx = buf->index;
__entry->schedule_now = schedule_now;
),
TP_printk("node=%u buf=%u%s", __entry->node_id, __entry->buf_idx,
__entry->schedule_now ? " schedule immediately" : "")
);
DEFINE_EVENT(cfe_buffer_template, cfe_csi2_schedule,
TP_PROTO(u32 node_id, struct vb2_buffer *buf),
TP_ARGS(node_id, buf));
DEFINE_EVENT(cfe_buffer_template, cfe_fe_schedule,
TP_PROTO(u32 node_id, struct vb2_buffer *buf),
TP_ARGS(node_id, buf));
TRACE_EVENT(cfe_buffer_complete,
TP_PROTO(u32 node_id, struct vb2_v4l2_buffer *buf),
TP_ARGS(node_id, buf),
TP_STRUCT__entry(
__field(u32, node_id)
__field(u32, buf_idx)
__field(u32, seq)
__field(u64, ts)
),
TP_fast_assign(
__entry->node_id = node_id;
__entry->buf_idx = buf->vb2_buf.index;
__entry->seq = buf->sequence;
__entry->ts = buf->vb2_buf.timestamp;
),
TP_printk("node=%u buf=%u seq=%u ts=%llu", __entry->node_id,
__entry->buf_idx, __entry->seq, __entry->ts)
);
TRACE_EVENT(cfe_frame_start,
TP_PROTO(u32 node_id, u32 fs_count),
TP_ARGS(node_id, fs_count),
TP_STRUCT__entry(
__field(u32, node_id)
__field(u32, fs_count)
),
TP_fast_assign(
__entry->node_id = node_id;
__entry->fs_count = fs_count;
),
TP_printk("node=%u fs_count=%u", __entry->node_id, __entry->fs_count)
);
TRACE_EVENT(cfe_frame_end,
TP_PROTO(u32 node_id, u32 fs_count),
TP_ARGS(node_id, fs_count),
TP_STRUCT__entry(
__field(u32, node_id)
__field(u32, fs_count)
),
TP_fast_assign(
__entry->node_id = node_id;
__entry->fs_count = fs_count;
),
TP_printk("node=%u fs_count=%u", __entry->node_id, __entry->fs_count)
);
TRACE_EVENT(cfe_prepare_next_job,
TP_PROTO(bool fe_enabled),
TP_ARGS(fe_enabled),
TP_STRUCT__entry(
__field(bool, fe_enabled)
),
TP_fast_assign(
__entry->fe_enabled = fe_enabled;
),
TP_printk("fe_enabled=%u", __entry->fe_enabled)
);
/* These are copied from csi2.c */
#define CSI2_STATUS_IRQ_FS(x) (BIT(0) << (x))
#define CSI2_STATUS_IRQ_FE(x) (BIT(4) << (x))
#define CSI2_STATUS_IRQ_FE_ACK(x) (BIT(8) << (x))
#define CSI2_STATUS_IRQ_LE(x) (BIT(12) << (x))
#define CSI2_STATUS_IRQ_LE_ACK(x) (BIT(16) << (x))
TRACE_EVENT(csi2_irq,
TP_PROTO(u32 channel, u32 status, u32 dbg),
TP_ARGS(channel, status, dbg),
TP_STRUCT__entry(
__field(u32, channel)
__field(u32, status)
__field(u32, dbg)
),
TP_fast_assign(
__entry->channel = channel;
__entry->status = status;
__entry->dbg = dbg;
),
TP_printk("ch=%u flags=[ %s%s%s%s%s] frame=%u line=%u\n",
__entry->channel,
(__entry->status & CSI2_STATUS_IRQ_FS(__entry->channel)) ?
"FS " : "",
(__entry->status & CSI2_STATUS_IRQ_FE(__entry->channel)) ?
"FE " : "",
(__entry->status & CSI2_STATUS_IRQ_FE_ACK(__entry->channel)) ?
"FE_ACK " : "",
(__entry->status & CSI2_STATUS_IRQ_LE(__entry->channel)) ?
"LE " : "",
(__entry->status & CSI2_STATUS_IRQ_LE_ACK(__entry->channel)) ?
"LE_ACK " : "",
__entry->dbg >> 16, __entry->dbg & 0xffff)
);
TRACE_EVENT(fe_irq,
TP_PROTO(u32 status, u32 output_status, u32 frame_status,
u32 error_status, u32 int_status),
TP_ARGS(status, output_status, frame_status, error_status, int_status),
TP_STRUCT__entry(
__field(u32, status)
__field(u32, output_status)
__field(u32, frame_status)
__field(u32, error_status)
__field(u32, int_status)
),
TP_fast_assign(
__entry->status = status;
__entry->output_status = output_status;
__entry->frame_status = frame_status;
__entry->error_status = error_status;
__entry->int_status = int_status;
),
TP_printk("status 0x%x out_status 0x%x frame_status 0x%x error_status 0x%x int_status 0x%x",
__entry->status,
__entry->output_status,
__entry->frame_status,
__entry->error_status,
__entry->int_status)
);
#endif /* _CFE_TRACE_H */
/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#define TRACE_INCLUDE_FILE ../../drivers/media/platform/raspberrypi/rp1-cfe/cfe-trace
#include <trace/define_trace.h>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* RP1 CFE Driver
*
* Copyright (c) 2021-2024 Raspberry Pi Ltd.
* Copyright (c) 2023-2024 Ideas on Board Oy
*/
#ifndef _RP1_CFE_
#define _RP1_CFE_
#include <linux/media-bus-format.h>
#include <linux/types.h>
#include <linux/videodev2.h>
extern bool cfe_debug_verbose;
enum cfe_remap_types {
CFE_REMAP_16BIT,
CFE_REMAP_COMPRESSED,
CFE_NUM_REMAP,
};
#define CFE_FORMAT_FLAG_META_OUT BIT(0)
#define CFE_FORMAT_FLAG_META_CAP BIT(1)
#define CFE_FORMAT_FLAG_FE_OUT BIT(2)
struct cfe_fmt {
u32 fourcc;
u32 code;
u8 depth;
u8 csi_dt;
u32 remap[CFE_NUM_REMAP];
u32 flags;
};
extern const struct v4l2_mbus_framefmt cfe_default_format;
const struct cfe_fmt *find_format_by_code(u32 code);
const struct cfe_fmt *find_format_by_pix(u32 pixelformat);
u32 cfe_find_16bit_code(u32 code);
u32 cfe_find_compressed_code(u32 code);
#endif

View File

@ -0,0 +1,586 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* RP1 CSI-2 Driver
*
* Copyright (c) 2021-2024 Raspberry Pi Ltd.
* Copyright (c) 2023-2024 Ideas on Board Oy
*/
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/pm_runtime.h>
#include <linux/seq_file.h>
#include <media/videobuf2-dma-contig.h>
#include "cfe.h"
#include "csi2.h"
#include "cfe-trace.h"
static bool csi2_track_errors;
module_param_named(track_csi2_errors, csi2_track_errors, bool, 0);
MODULE_PARM_DESC(track_csi2_errors, "track csi-2 errors");
#define csi2_dbg(csi2, fmt, arg...) dev_dbg((csi2)->v4l2_dev->dev, fmt, ##arg)
#define csi2_err(csi2, fmt, arg...) dev_err((csi2)->v4l2_dev->dev, fmt, ##arg)
/* CSI2-DMA registers */
#define CSI2_STATUS 0x000
#define CSI2_QOS 0x004
#define CSI2_DISCARDS_OVERFLOW 0x008
#define CSI2_DISCARDS_INACTIVE 0x00c
#define CSI2_DISCARDS_UNMATCHED 0x010
#define CSI2_DISCARDS_LEN_LIMIT 0x014
#define CSI2_DISCARDS_AMOUNT_SHIFT 0
#define CSI2_DISCARDS_AMOUNT_MASK GENMASK(23, 0)
#define CSI2_DISCARDS_DT_SHIFT 24
#define CSI2_DISCARDS_DT_MASK GENMASK(29, 24)
#define CSI2_DISCARDS_VC_SHIFT 30
#define CSI2_DISCARDS_VC_MASK GENMASK(31, 30)
#define CSI2_LLEV_PANICS 0x018
#define CSI2_ULEV_PANICS 0x01c
#define CSI2_IRQ_MASK 0x020
#define CSI2_IRQ_MASK_IRQ_OVERFLOW BIT(0)
#define CSI2_IRQ_MASK_IRQ_DISCARD_OVERFLOW BIT(1)
#define CSI2_IRQ_MASK_IRQ_DISCARD_LENGTH_LIMIT BIT(2)
#define CSI2_IRQ_MASK_IRQ_DISCARD_UNMATCHED BIT(3)
#define CSI2_IRQ_MASK_IRQ_DISCARD_INACTIVE BIT(4)
#define CSI2_IRQ_MASK_IRQ_ALL \
(CSI2_IRQ_MASK_IRQ_OVERFLOW | CSI2_IRQ_MASK_IRQ_DISCARD_OVERFLOW | \
CSI2_IRQ_MASK_IRQ_DISCARD_LENGTH_LIMIT | \
CSI2_IRQ_MASK_IRQ_DISCARD_UNMATCHED | \
CSI2_IRQ_MASK_IRQ_DISCARD_INACTIVE)
#define CSI2_CTRL 0x024
#define CSI2_CH_CTRL(x) ((x) * 0x40 + 0x28)
#define CSI2_CH_ADDR0(x) ((x) * 0x40 + 0x2c)
#define CSI2_CH_ADDR1(x) ((x) * 0x40 + 0x3c)
#define CSI2_CH_STRIDE(x) ((x) * 0x40 + 0x30)
#define CSI2_CH_LENGTH(x) ((x) * 0x40 + 0x34)
#define CSI2_CH_DEBUG(x) ((x) * 0x40 + 0x38)
#define CSI2_CH_FRAME_SIZE(x) ((x) * 0x40 + 0x40)
#define CSI2_CH_COMP_CTRL(x) ((x) * 0x40 + 0x44)
#define CSI2_CH_FE_FRAME_ID(x) ((x) * 0x40 + 0x48)
/* CSI2_STATUS */
#define CSI2_STATUS_IRQ_FS(x) (BIT(0) << (x))
#define CSI2_STATUS_IRQ_FE(x) (BIT(4) << (x))
#define CSI2_STATUS_IRQ_FE_ACK(x) (BIT(8) << (x))
#define CSI2_STATUS_IRQ_LE(x) (BIT(12) << (x))
#define CSI2_STATUS_IRQ_LE_ACK(x) (BIT(16) << (x))
#define CSI2_STATUS_IRQ_CH_MASK(x) \
(CSI2_STATUS_IRQ_FS(x) | CSI2_STATUS_IRQ_FE(x) | \
CSI2_STATUS_IRQ_FE_ACK(x) | CSI2_STATUS_IRQ_LE(x) | \
CSI2_STATUS_IRQ_LE_ACK(x))
#define CSI2_STATUS_IRQ_OVERFLOW BIT(20)
#define CSI2_STATUS_IRQ_DISCARD_OVERFLOW BIT(21)
#define CSI2_STATUS_IRQ_DISCARD_LEN_LIMIT BIT(22)
#define CSI2_STATUS_IRQ_DISCARD_UNMATCHED BIT(23)
#define CSI2_STATUS_IRQ_DISCARD_INACTIVE BIT(24)
/* CSI2_CTRL */
#define CSI2_CTRL_EOP_IS_EOL BIT(0)
/* CSI2_CH_CTRL */
#define CSI2_CH_CTRL_DMA_EN BIT(0)
#define CSI2_CH_CTRL_FORCE BIT(3)
#define CSI2_CH_CTRL_AUTO_ARM BIT(4)
#define CSI2_CH_CTRL_IRQ_EN_FS BIT(13)
#define CSI2_CH_CTRL_IRQ_EN_FE BIT(14)
#define CSI2_CH_CTRL_IRQ_EN_FE_ACK BIT(15)
#define CSI2_CH_CTRL_IRQ_EN_LE BIT(16)
#define CSI2_CH_CTRL_IRQ_EN_LE_ACK BIT(17)
#define CSI2_CH_CTRL_FLUSH_FE BIT(28)
#define CSI2_CH_CTRL_PACK_LINE BIT(29)
#define CSI2_CH_CTRL_PACK_BYTES BIT(30)
#define CSI2_CH_CTRL_CH_MODE_MASK GENMASK(2, 1)
#define CSI2_CH_CTRL_VC_MASK GENMASK(6, 5)
#define CSI2_CH_CTRL_DT_MASK GENMASK(12, 7)
#define CSI2_CH_CTRL_LC_MASK GENMASK(27, 18)
/* CHx_COMPRESSION_CONTROL */
#define CSI2_CH_COMP_CTRL_OFFSET_MASK GENMASK(15, 0)
#define CSI2_CH_COMP_CTRL_SHIFT_MASK GENMASK(19, 16)
#define CSI2_CH_COMP_CTRL_MODE_MASK GENMASK(25, 24)
static inline u32 csi2_reg_read(struct csi2_device *csi2, u32 offset)
{
return readl(csi2->base + offset);
}
static inline void csi2_reg_write(struct csi2_device *csi2, u32 offset, u32 val)
{
writel(val, csi2->base + offset);
}
static inline void set_field(u32 *valp, u32 field, u32 mask)
{
u32 val = *valp;
val &= ~mask;
val |= (field << __ffs(mask)) & mask;
*valp = val;
}
static int csi2_regs_show(struct seq_file *s, void *data)
{
struct csi2_device *csi2 = s->private;
int ret;
ret = pm_runtime_resume_and_get(csi2->v4l2_dev->dev);
if (ret)
return ret;
#define DUMP(reg) seq_printf(s, #reg " \t0x%08x\n", csi2_reg_read(csi2, reg))
#define DUMP_CH(idx, reg) seq_printf(s, #reg "(%u) \t0x%08x\n", idx, \
csi2_reg_read(csi2, reg(idx)))
DUMP(CSI2_STATUS);
DUMP(CSI2_DISCARDS_OVERFLOW);
DUMP(CSI2_DISCARDS_INACTIVE);
DUMP(CSI2_DISCARDS_UNMATCHED);
DUMP(CSI2_DISCARDS_LEN_LIMIT);
DUMP(CSI2_LLEV_PANICS);
DUMP(CSI2_ULEV_PANICS);
DUMP(CSI2_IRQ_MASK);
DUMP(CSI2_CTRL);
for (unsigned int i = 0; i < CSI2_NUM_CHANNELS; ++i) {
DUMP_CH(i, CSI2_CH_CTRL);
DUMP_CH(i, CSI2_CH_ADDR0);
DUMP_CH(i, CSI2_CH_ADDR1);
DUMP_CH(i, CSI2_CH_STRIDE);
DUMP_CH(i, CSI2_CH_LENGTH);
DUMP_CH(i, CSI2_CH_DEBUG);
DUMP_CH(i, CSI2_CH_FRAME_SIZE);
DUMP_CH(i, CSI2_CH_COMP_CTRL);
DUMP_CH(i, CSI2_CH_FE_FRAME_ID);
}
#undef DUMP
#undef DUMP_CH
pm_runtime_put(csi2->v4l2_dev->dev);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(csi2_regs);
static int csi2_errors_show(struct seq_file *s, void *data)
{
struct csi2_device *csi2 = s->private;
unsigned long flags;
u32 discards_table[DISCARDS_TABLE_NUM_VCS][DISCARDS_TABLE_NUM_ENTRIES];
u32 discards_dt_table[DISCARDS_TABLE_NUM_ENTRIES];
u32 overflows;
spin_lock_irqsave(&csi2->errors_lock, flags);
memcpy(discards_table, csi2->discards_table, sizeof(discards_table));
memcpy(discards_dt_table, csi2->discards_dt_table,
sizeof(discards_dt_table));
overflows = csi2->overflows;
csi2->overflows = 0;
memset(csi2->discards_table, 0, sizeof(discards_table));
memset(csi2->discards_dt_table, 0, sizeof(discards_dt_table));
spin_unlock_irqrestore(&csi2->errors_lock, flags);
seq_printf(s, "Overflows %u\n", overflows);
seq_puts(s, "Discards:\n");
seq_puts(s, "VC OVLF LEN UNMATCHED INACTIVE\n");
for (unsigned int vc = 0; vc < DISCARDS_TABLE_NUM_VCS; ++vc) {
seq_printf(s, "%u %10u %10u %10u %10u\n", vc,
discards_table[vc][DISCARDS_TABLE_OVERFLOW],
discards_table[vc][DISCARDS_TABLE_LENGTH_LIMIT],
discards_table[vc][DISCARDS_TABLE_UNMATCHED],
discards_table[vc][DISCARDS_TABLE_INACTIVE]);
}
seq_printf(s, "Last DT %10u %10u %10u %10u\n",
discards_dt_table[DISCARDS_TABLE_OVERFLOW],
discards_dt_table[DISCARDS_TABLE_LENGTH_LIMIT],
discards_dt_table[DISCARDS_TABLE_UNMATCHED],
discards_dt_table[DISCARDS_TABLE_INACTIVE]);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(csi2_errors);
static void csi2_isr_handle_errors(struct csi2_device *csi2, u32 status)
{
spin_lock(&csi2->errors_lock);
if (status & CSI2_STATUS_IRQ_OVERFLOW)
csi2->overflows++;
for (unsigned int i = 0; i < DISCARDS_TABLE_NUM_ENTRIES; ++i) {
static const u32 discard_bits[] = {
CSI2_STATUS_IRQ_DISCARD_OVERFLOW,
CSI2_STATUS_IRQ_DISCARD_LEN_LIMIT,
CSI2_STATUS_IRQ_DISCARD_UNMATCHED,
CSI2_STATUS_IRQ_DISCARD_INACTIVE,
};
static const u8 discard_regs[] = {
CSI2_DISCARDS_OVERFLOW,
CSI2_DISCARDS_LEN_LIMIT,
CSI2_DISCARDS_UNMATCHED,
CSI2_DISCARDS_INACTIVE,
};
u32 amount;
u8 dt, vc;
u32 v;
if (!(status & discard_bits[i]))
continue;
v = csi2_reg_read(csi2, discard_regs[i]);
csi2_reg_write(csi2, discard_regs[i], 0);
amount = (v & CSI2_DISCARDS_AMOUNT_MASK) >>
CSI2_DISCARDS_AMOUNT_SHIFT;
dt = (v & CSI2_DISCARDS_DT_MASK) >> CSI2_DISCARDS_DT_SHIFT;
vc = (v & CSI2_DISCARDS_VC_MASK) >> CSI2_DISCARDS_VC_SHIFT;
csi2->discards_table[vc][i] += amount;
csi2->discards_dt_table[i] = dt;
}
spin_unlock(&csi2->errors_lock);
}
void csi2_isr(struct csi2_device *csi2, bool *sof, bool *eof)
{
u32 status;
status = csi2_reg_read(csi2, CSI2_STATUS);
/* Write value back to clear the interrupts */
csi2_reg_write(csi2, CSI2_STATUS, status);
for (unsigned int i = 0; i < CSI2_NUM_CHANNELS; i++) {
u32 dbg;
if ((status & CSI2_STATUS_IRQ_CH_MASK(i)) == 0)
continue;
dbg = csi2_reg_read(csi2, CSI2_CH_DEBUG(i));
trace_csi2_irq(i, status, dbg);
sof[i] = !!(status & CSI2_STATUS_IRQ_FS(i));
eof[i] = !!(status & CSI2_STATUS_IRQ_FE_ACK(i));
}
if (csi2_track_errors)
csi2_isr_handle_errors(csi2, status);
}
void csi2_set_buffer(struct csi2_device *csi2, unsigned int channel,
dma_addr_t dmaaddr, unsigned int stride, unsigned int size)
{
u64 addr = dmaaddr;
/*
* ADDRESS0 must be written last as it triggers the double buffering
* mechanism for all buffer registers within the hardware.
*/
addr >>= 4;
csi2_reg_write(csi2, CSI2_CH_LENGTH(channel), size >> 4);
csi2_reg_write(csi2, CSI2_CH_STRIDE(channel), stride >> 4);
csi2_reg_write(csi2, CSI2_CH_ADDR1(channel), addr >> 32);
csi2_reg_write(csi2, CSI2_CH_ADDR0(channel), addr & 0xffffffff);
}
void csi2_set_compression(struct csi2_device *csi2, unsigned int channel,
enum csi2_compression_mode mode, unsigned int shift,
unsigned int offset)
{
u32 compression = 0;
set_field(&compression, CSI2_CH_COMP_CTRL_OFFSET_MASK, offset);
set_field(&compression, CSI2_CH_COMP_CTRL_SHIFT_MASK, shift);
set_field(&compression, CSI2_CH_COMP_CTRL_MODE_MASK, mode);
csi2_reg_write(csi2, CSI2_CH_COMP_CTRL(channel), compression);
}
void csi2_start_channel(struct csi2_device *csi2, unsigned int channel,
enum csi2_mode mode, bool auto_arm, bool pack_bytes,
unsigned int width, unsigned int height,
u8 vc, u8 dt)
{
u32 ctrl;
csi2_dbg(csi2, "%s [%u]\n", __func__, channel);
csi2_reg_write(csi2, CSI2_CH_CTRL(channel), 0);
csi2_reg_write(csi2, CSI2_CH_DEBUG(channel), 0);
csi2_reg_write(csi2, CSI2_STATUS, CSI2_STATUS_IRQ_CH_MASK(channel));
/* Enable channel and FS/FE interrupts. */
ctrl = CSI2_CH_CTRL_DMA_EN | CSI2_CH_CTRL_IRQ_EN_FS |
CSI2_CH_CTRL_IRQ_EN_FE_ACK | CSI2_CH_CTRL_PACK_LINE;
/* PACK_BYTES ensures no striding for embedded data. */
if (pack_bytes)
ctrl |= CSI2_CH_CTRL_PACK_BYTES;
if (auto_arm)
ctrl |= CSI2_CH_CTRL_AUTO_ARM;
if (width && height) {
set_field(&ctrl, mode, CSI2_CH_CTRL_CH_MODE_MASK);
csi2_reg_write(csi2, CSI2_CH_FRAME_SIZE(channel),
(height << 16) | width);
} else {
set_field(&ctrl, 0x0, CSI2_CH_CTRL_CH_MODE_MASK);
csi2_reg_write(csi2, CSI2_CH_FRAME_SIZE(channel), 0);
}
set_field(&ctrl, vc, CSI2_CH_CTRL_VC_MASK);
set_field(&ctrl, dt, CSI2_CH_CTRL_DT_MASK);
csi2_reg_write(csi2, CSI2_CH_CTRL(channel), ctrl);
csi2->num_lines[channel] = height;
}
void csi2_stop_channel(struct csi2_device *csi2, unsigned int channel)
{
csi2_dbg(csi2, "%s [%u]\n", __func__, channel);
/* Channel disable. Use FORCE to allow stopping mid-frame. */
csi2_reg_write(csi2, CSI2_CH_CTRL(channel), CSI2_CH_CTRL_FORCE);
/* Latch the above change by writing to the ADDR0 register. */
csi2_reg_write(csi2, CSI2_CH_ADDR0(channel), 0);
/* Write this again, the HW needs it! */
csi2_reg_write(csi2, CSI2_CH_ADDR0(channel), 0);
}
void csi2_open_rx(struct csi2_device *csi2)
{
csi2_reg_write(csi2, CSI2_IRQ_MASK,
csi2_track_errors ? CSI2_IRQ_MASK_IRQ_ALL : 0);
dphy_start(&csi2->dphy);
csi2_reg_write(csi2, CSI2_CTRL, CSI2_CTRL_EOP_IS_EOL);
}
void csi2_close_rx(struct csi2_device *csi2)
{
dphy_stop(&csi2->dphy);
csi2_reg_write(csi2, CSI2_IRQ_MASK, 0);
}
static int csi2_init_state(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state)
{
struct v4l2_subdev_route routes[] = { {
.sink_pad = CSI2_PAD_SINK,
.sink_stream = 0,
.source_pad = CSI2_PAD_FIRST_SOURCE,
.source_stream = 0,
.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
} };
struct v4l2_subdev_krouting routing = {
.num_routes = ARRAY_SIZE(routes),
.routes = routes,
};
int ret;
ret = v4l2_subdev_set_routing_with_fmt(sd, state, &routing,
&cfe_default_format);
if (ret)
return ret;
return 0;
}
static int csi2_pad_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_format *format)
{
if (format->pad == CSI2_PAD_SINK) {
/* Store the sink format and propagate it to the source. */
const struct cfe_fmt *cfe_fmt;
cfe_fmt = find_format_by_code(format->format.code);
if (!cfe_fmt) {
cfe_fmt = find_format_by_code(MEDIA_BUS_FMT_SRGGB10_1X10);
format->format.code = cfe_fmt->code;
}
struct v4l2_mbus_framefmt *fmt;
fmt = v4l2_subdev_state_get_format(state, format->pad,
format->stream);
if (!fmt)
return -EINVAL;
*fmt = format->format;
fmt = v4l2_subdev_state_get_opposite_stream_format(state,
format->pad,
format->stream);
if (!fmt)
return -EINVAL;
format->format.field = V4L2_FIELD_NONE;
*fmt = format->format;
} else {
/* Only allow changing the source pad mbus code. */
struct v4l2_mbus_framefmt *sink_fmt, *source_fmt;
u32 sink_code;
u32 code;
sink_fmt = v4l2_subdev_state_get_opposite_stream_format(state,
format->pad,
format->stream);
if (!sink_fmt)
return -EINVAL;
source_fmt = v4l2_subdev_state_get_format(state, format->pad,
format->stream);
if (!source_fmt)
return -EINVAL;
sink_code = sink_fmt->code;
code = format->format.code;
/*
* Only allow changing the mbus code to:
* - The sink's mbus code
* - The 16-bit version of the sink's mbus code
* - The compressed version of the sink's mbus code
*/
if (code == sink_code ||
code == cfe_find_16bit_code(sink_code) ||
code == cfe_find_compressed_code(sink_code))
source_fmt->code = code;
format->format.code = source_fmt->code;
}
return 0;
}
static int csi2_set_routing(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
enum v4l2_subdev_format_whence which,
struct v4l2_subdev_krouting *routing)
{
int ret;
ret = v4l2_subdev_routing_validate(sd, routing,
V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 |
V4L2_SUBDEV_ROUTING_NO_SOURCE_MULTIPLEXING);
if (ret)
return ret;
/* Only stream ID 0 allowed on source pads */
for (unsigned int i = 0; i < routing->num_routes; ++i) {
const struct v4l2_subdev_route *route = &routing->routes[i];
if (route->source_stream != 0)
return -EINVAL;
}
ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing,
&cfe_default_format);
if (ret)
return ret;
return 0;
}
static const struct v4l2_subdev_pad_ops csi2_subdev_pad_ops = {
.get_fmt = v4l2_subdev_get_fmt,
.set_fmt = csi2_pad_set_fmt,
.set_routing = csi2_set_routing,
.link_validate = v4l2_subdev_link_validate_default,
};
static const struct media_entity_operations csi2_entity_ops = {
.link_validate = v4l2_subdev_link_validate,
.has_pad_interdep = v4l2_subdev_has_pad_interdep,
};
static const struct v4l2_subdev_ops csi2_subdev_ops = {
.pad = &csi2_subdev_pad_ops,
};
static const struct v4l2_subdev_internal_ops csi2_internal_ops = {
.init_state = csi2_init_state,
};
int csi2_init(struct csi2_device *csi2, struct dentry *debugfs)
{
unsigned int ret;
spin_lock_init(&csi2->errors_lock);
csi2->dphy.dev = csi2->v4l2_dev->dev;
dphy_probe(&csi2->dphy);
debugfs_create_file("csi2_regs", 0440, debugfs, csi2, &csi2_regs_fops);
if (csi2_track_errors)
debugfs_create_file("csi2_errors", 0440, debugfs, csi2,
&csi2_errors_fops);
csi2->pad[CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
for (unsigned int i = CSI2_PAD_FIRST_SOURCE;
i < CSI2_PAD_FIRST_SOURCE + CSI2_PAD_NUM_SOURCES; i++)
csi2->pad[i].flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&csi2->sd.entity, ARRAY_SIZE(csi2->pad),
csi2->pad);
if (ret)
return ret;
/* Initialize subdev */
v4l2_subdev_init(&csi2->sd, &csi2_subdev_ops);
csi2->sd.internal_ops = &csi2_internal_ops;
csi2->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
csi2->sd.entity.ops = &csi2_entity_ops;
csi2->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
csi2->sd.owner = THIS_MODULE;
snprintf(csi2->sd.name, sizeof(csi2->sd.name), "csi2");
ret = v4l2_subdev_init_finalize(&csi2->sd);
if (ret)
goto err_entity_cleanup;
ret = v4l2_device_register_subdev(csi2->v4l2_dev, &csi2->sd);
if (ret) {
csi2_err(csi2, "Failed register csi2 subdev (%d)\n", ret);
goto err_subdev_cleanup;
}
return 0;
err_subdev_cleanup:
v4l2_subdev_cleanup(&csi2->sd);
err_entity_cleanup:
media_entity_cleanup(&csi2->sd.entity);
return ret;
}
void csi2_uninit(struct csi2_device *csi2)
{
v4l2_device_unregister_subdev(&csi2->sd);
v4l2_subdev_cleanup(&csi2->sd);
media_entity_cleanup(&csi2->sd.entity);
}

View File

@ -0,0 +1,89 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* RP1 CSI-2 Driver
*
* Copyright (c) 2021-2024 Raspberry Pi Ltd.
* Copyright (c) 2023-2024 Ideas on Board Oy
*/
#ifndef _RP1_CSI2_
#define _RP1_CSI2_
#include <linux/debugfs.h>
#include <linux/io.h>
#include <linux/types.h>
#include <media/v4l2-device.h>
#include <media/v4l2-subdev.h>
#include "dphy.h"
#define CSI2_NUM_CHANNELS 4
#define CSI2_PAD_SINK 0
#define CSI2_PAD_FIRST_SOURCE 1
#define CSI2_PAD_NUM_SOURCES 4
#define CSI2_NUM_PADS 5
#define DISCARDS_TABLE_NUM_VCS 4
enum csi2_mode {
CSI2_MODE_NORMAL = 0,
CSI2_MODE_REMAP = 1,
CSI2_MODE_COMPRESSED = 2,
CSI2_MODE_FE_STREAMING = 3,
};
enum csi2_compression_mode {
CSI2_COMPRESSION_DELTA = 1,
CSI2_COMPRESSION_SIMPLE = 2,
CSI2_COMPRESSION_COMBINED = 3,
};
enum discards_table_index {
DISCARDS_TABLE_OVERFLOW = 0,
DISCARDS_TABLE_LENGTH_LIMIT,
DISCARDS_TABLE_UNMATCHED,
DISCARDS_TABLE_INACTIVE,
DISCARDS_TABLE_NUM_ENTRIES,
};
struct csi2_device {
/* Parent V4l2 device */
struct v4l2_device *v4l2_dev;
void __iomem *base;
struct dphy_data dphy;
enum v4l2_mbus_type bus_type;
unsigned int bus_flags;
unsigned int num_lines[CSI2_NUM_CHANNELS];
struct media_pad pad[CSI2_NUM_PADS];
struct v4l2_subdev sd;
/* lock for csi2 errors counters */
spinlock_t errors_lock;
u32 overflows;
u32 discards_table[DISCARDS_TABLE_NUM_VCS][DISCARDS_TABLE_NUM_ENTRIES];
u32 discards_dt_table[DISCARDS_TABLE_NUM_ENTRIES];
};
void csi2_isr(struct csi2_device *csi2, bool *sof, bool *eof);
void csi2_set_buffer(struct csi2_device *csi2, unsigned int channel,
dma_addr_t dmaaddr, unsigned int stride,
unsigned int size);
void csi2_set_compression(struct csi2_device *csi2, unsigned int channel,
enum csi2_compression_mode mode, unsigned int shift,
unsigned int offset);
void csi2_start_channel(struct csi2_device *csi2, unsigned int channel,
enum csi2_mode mode, bool auto_arm,
bool pack_bytes, unsigned int width,
unsigned int height, u8 vc, u8 dt);
void csi2_stop_channel(struct csi2_device *csi2, unsigned int channel);
void csi2_open_rx(struct csi2_device *csi2);
void csi2_close_rx(struct csi2_device *csi2);
int csi2_init(struct csi2_device *csi2, struct dentry *debugfs);
void csi2_uninit(struct csi2_device *csi2);
#endif

View File

@ -0,0 +1,181 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* RP1 CSI-2 Driver
*
* Copyright (c) 2021-2024 Raspberry Pi Ltd.
* Copyright (c) 2023-2024 Ideas on Board Oy
*/
#include <linux/delay.h>
#include <linux/pm_runtime.h>
#include "dphy.h"
#define dphy_dbg(dphy, fmt, arg...) dev_dbg((dphy)->dev, fmt, ##arg)
#define dphy_err(dphy, fmt, arg...) dev_err((dphy)->dev, fmt, ##arg)
/* DW dphy Host registers */
#define DPHY_VERSION 0x000
#define DPHY_N_LANES 0x004
#define DPHY_RESETN 0x008
#define DPHY_PHY_SHUTDOWNZ 0x040
#define DPHY_PHY_RSTZ 0x044
#define DPHY_PHY_RX 0x048
#define DPHY_PHY_STOPSTATE 0x04c
#define DPHY_PHY_TST_CTRL0 0x050
#define DPHY_PHY_TST_CTRL1 0x054
#define DPHY_PHY2_TST_CTRL0 0x058
#define DPHY_PHY2_TST_CTRL1 0x05c
/* DW dphy Host Transactions */
#define DPHY_HS_RX_CTRL_LANE0_OFFSET 0x44
#define DPHY_PLL_INPUT_DIV_OFFSET 0x17
#define DPHY_PLL_LOOP_DIV_OFFSET 0x18
#define DPHY_PLL_DIV_CTRL_OFFSET 0x19
static u32 dw_csi2_host_read(struct dphy_data *dphy, u32 offset)
{
return readl(dphy->base + offset);
}
static void dw_csi2_host_write(struct dphy_data *dphy, u32 offset, u32 data)
{
writel(data, dphy->base + offset);
}
static void set_tstclr(struct dphy_data *dphy, u32 val)
{
u32 ctrl0 = dw_csi2_host_read(dphy, DPHY_PHY_TST_CTRL0);
dw_csi2_host_write(dphy, DPHY_PHY_TST_CTRL0, (ctrl0 & ~1) | val);
}
static void set_tstclk(struct dphy_data *dphy, u32 val)
{
u32 ctrl0 = dw_csi2_host_read(dphy, DPHY_PHY_TST_CTRL0);
dw_csi2_host_write(dphy, DPHY_PHY_TST_CTRL0, (ctrl0 & ~2) | (val << 1));
}
static uint8_t get_tstdout(struct dphy_data *dphy)
{
u32 ctrl1 = dw_csi2_host_read(dphy, DPHY_PHY_TST_CTRL1);
return ((ctrl1 >> 8) & 0xff);
}
static void set_testen(struct dphy_data *dphy, u32 val)
{
u32 ctrl1 = dw_csi2_host_read(dphy, DPHY_PHY_TST_CTRL1);
dw_csi2_host_write(dphy, DPHY_PHY_TST_CTRL1,
(ctrl1 & ~(1 << 16)) | (val << 16));
}
static void set_testdin(struct dphy_data *dphy, u32 val)
{
u32 ctrl1 = dw_csi2_host_read(dphy, DPHY_PHY_TST_CTRL1);
dw_csi2_host_write(dphy, DPHY_PHY_TST_CTRL1, (ctrl1 & ~0xff) | val);
}
static uint8_t dphy_transaction(struct dphy_data *dphy, u8 test_code,
uint8_t test_data)
{
/* See page 101 of the MIPI DPHY databook. */
set_tstclk(dphy, 1);
set_testen(dphy, 0);
set_testdin(dphy, test_code);
set_testen(dphy, 1);
set_tstclk(dphy, 0);
set_testen(dphy, 0);
set_testdin(dphy, test_data);
set_tstclk(dphy, 1);
return get_tstdout(dphy);
}
static void dphy_set_hsfreqrange(struct dphy_data *dphy, uint32_t mbps)
{
/* See Table 5-1 on page 65 of dphy databook */
static const u16 hsfreqrange_table[][2] = {
{ 89, 0b000000 }, { 99, 0b010000 }, { 109, 0b100000 },
{ 129, 0b000001 }, { 139, 0b010001 }, { 149, 0b100001 },
{ 169, 0b000010 }, { 179, 0b010010 }, { 199, 0b100010 },
{ 219, 0b000011 }, { 239, 0b010011 }, { 249, 0b100011 },
{ 269, 0b000100 }, { 299, 0b010100 }, { 329, 0b000101 },
{ 359, 0b010101 }, { 399, 0b100101 }, { 449, 0b000110 },
{ 499, 0b010110 }, { 549, 0b000111 }, { 599, 0b010111 },
{ 649, 0b001000 }, { 699, 0b011000 }, { 749, 0b001001 },
{ 799, 0b011001 }, { 849, 0b101001 }, { 899, 0b111001 },
{ 949, 0b001010 }, { 999, 0b011010 }, { 1049, 0b101010 },
{ 1099, 0b111010 }, { 1149, 0b001011 }, { 1199, 0b011011 },
{ 1249, 0b101011 }, { 1299, 0b111011 }, { 1349, 0b001100 },
{ 1399, 0b011100 }, { 1449, 0b101100 }, { 1500, 0b111100 },
};
unsigned int i;
if (mbps < 80 || mbps > 1500)
dphy_err(dphy, "DPHY: Datarate %u Mbps out of range\n", mbps);
for (i = 0; i < ARRAY_SIZE(hsfreqrange_table) - 1; i++) {
if (mbps <= hsfreqrange_table[i][0])
break;
}
dphy_transaction(dphy, DPHY_HS_RX_CTRL_LANE0_OFFSET,
hsfreqrange_table[i][1] << 1);
}
static void dphy_init(struct dphy_data *dphy)
{
dw_csi2_host_write(dphy, DPHY_PHY_RSTZ, 0);
dw_csi2_host_write(dphy, DPHY_PHY_SHUTDOWNZ, 0);
set_tstclk(dphy, 1);
set_testen(dphy, 0);
set_tstclr(dphy, 1);
usleep_range(15, 20);
set_tstclr(dphy, 0);
usleep_range(15, 20);
dphy_set_hsfreqrange(dphy, dphy->dphy_rate);
usleep_range(5, 10);
dw_csi2_host_write(dphy, DPHY_PHY_SHUTDOWNZ, 1);
usleep_range(5, 10);
dw_csi2_host_write(dphy, DPHY_PHY_RSTZ, 1);
}
void dphy_start(struct dphy_data *dphy)
{
dphy_dbg(dphy, "%s: Link rate %u Mbps, %u data lanes\n", __func__,
dphy->dphy_rate, dphy->active_lanes);
dw_csi2_host_write(dphy, DPHY_N_LANES, (dphy->active_lanes - 1));
dphy_init(dphy);
dw_csi2_host_write(dphy, DPHY_RESETN, 0xffffffff);
usleep_range(10, 50);
}
void dphy_stop(struct dphy_data *dphy)
{
dphy_dbg(dphy, "%s\n", __func__);
/* Set only one lane (lane 0) as active (ON) */
dw_csi2_host_write(dphy, DPHY_N_LANES, 0);
dw_csi2_host_write(dphy, DPHY_RESETN, 0);
}
void dphy_probe(struct dphy_data *dphy)
{
u32 host_ver;
u8 host_ver_major, host_ver_minor;
host_ver = dw_csi2_host_read(dphy, DPHY_VERSION);
host_ver_major = (u8)((host_ver >> 24) - '0');
host_ver_minor = (u8)((host_ver >> 16) - '0');
host_ver_minor = host_ver_minor * 10;
host_ver_minor += (u8)((host_ver >> 8) - '0');
dphy_dbg(dphy, "DW dphy Host HW v%u.%u\n", host_ver_major,
host_ver_minor);
}

View File

@ -0,0 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2021-2024 Raspberry Pi Ltd.
* Copyright (c) 2023-2024 Ideas on Board Oy
*/
#ifndef _RP1_DPHY_
#define _RP1_DPHY_
#include <linux/io.h>
#include <linux/types.h>
struct dphy_data {
struct device *dev;
void __iomem *base;
u32 dphy_rate;
u32 max_lanes;
u32 active_lanes;
};
void dphy_probe(struct dphy_data *dphy);
void dphy_start(struct dphy_data *dphy);
void dphy_stop(struct dphy_data *dphy);
#endif

View File

@ -0,0 +1,605 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PiSP Front End Driver
*
* Copyright (c) 2021-2024 Raspberry Pi Ltd.
*/
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/pm_runtime.h>
#include <linux/seq_file.h>
#include <media/videobuf2-dma-contig.h>
#include "cfe.h"
#include "pisp-fe.h"
#include "cfe-trace.h"
#define FE_VERSION 0x000
#define FE_CONTROL 0x004
#define FE_STATUS 0x008
#define FE_FRAME_STATUS 0x00c
#define FE_ERROR_STATUS 0x010
#define FE_OUTPUT_STATUS 0x014
#define FE_INT_EN 0x018
#define FE_INT_STATUS 0x01c
/* CONTROL */
#define FE_CONTROL_QUEUE BIT(0)
#define FE_CONTROL_ABORT BIT(1)
#define FE_CONTROL_RESET BIT(2)
#define FE_CONTROL_LATCH_REGS BIT(3)
/* INT_EN / INT_STATUS */
#define FE_INT_EOF BIT(0)
#define FE_INT_SOF BIT(1)
#define FE_INT_LINES0 BIT(8)
#define FE_INT_LINES1 BIT(9)
#define FE_INT_STATS BIT(16)
#define FE_INT_QREADY BIT(24)
/* STATUS */
#define FE_STATUS_QUEUED BIT(0)
#define FE_STATUS_WAITING BIT(1)
#define FE_STATUS_ACTIVE BIT(2)
#define PISP_FE_CONFIG_BASE_OFFSET 0x0040
#define PISP_FE_ENABLE_STATS_CLUSTER \
(PISP_FE_ENABLE_STATS_CROP | PISP_FE_ENABLE_DECIMATE | \
PISP_FE_ENABLE_BLC | PISP_FE_ENABLE_CDAF_STATS | \
PISP_FE_ENABLE_AWB_STATS | PISP_FE_ENABLE_RGBY | \
PISP_FE_ENABLE_LSC | PISP_FE_ENABLE_AGC_STATS)
#define PISP_FE_ENABLE_OUTPUT_CLUSTER(i) \
((PISP_FE_ENABLE_CROP0 | PISP_FE_ENABLE_DOWNSCALE0 | \
PISP_FE_ENABLE_COMPRESS0 | PISP_FE_ENABLE_OUTPUT0) << (4 * (i)))
struct pisp_fe_config_param {
u32 dirty_flags;
u32 dirty_flags_extra;
size_t offset;
size_t size;
};
static const struct pisp_fe_config_param pisp_fe_config_map[] = {
/* *_dirty_flag_extra types */
{ 0, PISP_FE_DIRTY_GLOBAL,
offsetof(struct pisp_fe_config, global),
sizeof(struct pisp_fe_global_config) },
{ 0, PISP_FE_DIRTY_FLOATING,
offsetof(struct pisp_fe_config, floating_stats),
sizeof(struct pisp_fe_floating_stats_config) },
{ 0, PISP_FE_DIRTY_OUTPUT_AXI,
offsetof(struct pisp_fe_config, output_axi),
sizeof(struct pisp_fe_output_axi_config) },
/* *_dirty_flag types */
{ PISP_FE_ENABLE_INPUT, 0,
offsetof(struct pisp_fe_config, input),
sizeof(struct pisp_fe_input_config) },
{ PISP_FE_ENABLE_DECOMPRESS, 0,
offsetof(struct pisp_fe_config, decompress),
sizeof(struct pisp_decompress_config) },
{ PISP_FE_ENABLE_DECOMPAND, 0,
offsetof(struct pisp_fe_config, decompand),
sizeof(struct pisp_fe_decompand_config) },
{ PISP_FE_ENABLE_BLA, 0,
offsetof(struct pisp_fe_config, bla),
sizeof(struct pisp_bla_config) },
{ PISP_FE_ENABLE_DPC, 0,
offsetof(struct pisp_fe_config, dpc),
sizeof(struct pisp_fe_dpc_config) },
{ PISP_FE_ENABLE_STATS_CROP, 0,
offsetof(struct pisp_fe_config, stats_crop),
sizeof(struct pisp_fe_crop_config) },
{ PISP_FE_ENABLE_BLC, 0,
offsetof(struct pisp_fe_config, blc),
sizeof(struct pisp_bla_config) },
{ PISP_FE_ENABLE_CDAF_STATS, 0,
offsetof(struct pisp_fe_config, cdaf_stats),
sizeof(struct pisp_fe_cdaf_stats_config) },
{ PISP_FE_ENABLE_AWB_STATS, 0,
offsetof(struct pisp_fe_config, awb_stats),
sizeof(struct pisp_fe_awb_stats_config) },
{ PISP_FE_ENABLE_RGBY, 0,
offsetof(struct pisp_fe_config, rgby),
sizeof(struct pisp_fe_rgby_config) },
{ PISP_FE_ENABLE_LSC, 0,
offsetof(struct pisp_fe_config, lsc),
sizeof(struct pisp_fe_lsc_config) },
{ PISP_FE_ENABLE_AGC_STATS, 0,
offsetof(struct pisp_fe_config, agc_stats),
sizeof(struct pisp_agc_statistics) },
{ PISP_FE_ENABLE_CROP0, 0,
offsetof(struct pisp_fe_config, ch[0].crop),
sizeof(struct pisp_fe_crop_config) },
{ PISP_FE_ENABLE_DOWNSCALE0, 0,
offsetof(struct pisp_fe_config, ch[0].downscale),
sizeof(struct pisp_fe_downscale_config) },
{ PISP_FE_ENABLE_COMPRESS0, 0,
offsetof(struct pisp_fe_config, ch[0].compress),
sizeof(struct pisp_compress_config) },
{ PISP_FE_ENABLE_OUTPUT0, 0,
offsetof(struct pisp_fe_config, ch[0].output),
sizeof(struct pisp_fe_output_config) },
{ PISP_FE_ENABLE_CROP1, 0,
offsetof(struct pisp_fe_config, ch[1].crop),
sizeof(struct pisp_fe_crop_config) },
{ PISP_FE_ENABLE_DOWNSCALE1, 0,
offsetof(struct pisp_fe_config, ch[1].downscale),
sizeof(struct pisp_fe_downscale_config) },
{ PISP_FE_ENABLE_COMPRESS1, 0,
offsetof(struct pisp_fe_config, ch[1].compress),
sizeof(struct pisp_compress_config) },
{ PISP_FE_ENABLE_OUTPUT1, 0,
offsetof(struct pisp_fe_config, ch[1].output),
sizeof(struct pisp_fe_output_config) },
};
#define pisp_fe_dbg(fe, fmt, arg...) dev_dbg((fe)->v4l2_dev->dev, fmt, ##arg)
#define pisp_fe_info(fe, fmt, arg...) dev_info((fe)->v4l2_dev->dev, fmt, ##arg)
#define pisp_fe_err(fe, fmt, arg...) dev_err((fe)->v4l2_dev->dev, fmt, ##arg)
static inline u32 pisp_fe_reg_read(struct pisp_fe_device *fe, u32 offset)
{
return readl(fe->base + offset);
}
static inline void pisp_fe_reg_write(struct pisp_fe_device *fe, u32 offset,
u32 val)
{
writel(val, fe->base + offset);
}
static inline void pisp_fe_reg_write_relaxed(struct pisp_fe_device *fe,
u32 offset, u32 val)
{
writel_relaxed(val, fe->base + offset);
}
static int pisp_fe_regs_show(struct seq_file *s, void *data)
{
struct pisp_fe_device *fe = s->private;
int ret;
ret = pm_runtime_resume_and_get(fe->v4l2_dev->dev);
if (ret)
return ret;
pisp_fe_reg_write(fe, FE_CONTROL, FE_CONTROL_LATCH_REGS);
#define DUMP(reg) seq_printf(s, #reg " \t0x%08x\n", pisp_fe_reg_read(fe, reg))
DUMP(FE_VERSION);
DUMP(FE_CONTROL);
DUMP(FE_STATUS);
DUMP(FE_FRAME_STATUS);
DUMP(FE_ERROR_STATUS);
DUMP(FE_OUTPUT_STATUS);
DUMP(FE_INT_EN);
DUMP(FE_INT_STATUS);
#undef DUMP
pm_runtime_put(fe->v4l2_dev->dev);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(pisp_fe_regs);
static void pisp_fe_config_write(struct pisp_fe_device *fe,
struct pisp_fe_config *config,
unsigned int start_offset, unsigned int size)
{
const unsigned int max_offset =
offsetof(struct pisp_fe_config, ch[PISP_FE_NUM_OUTPUTS]);
unsigned int end_offset;
u32 *cfg = (u32 *)config;
start_offset = min(start_offset, max_offset);
end_offset = min(start_offset + size, max_offset);
cfg += start_offset >> 2;
for (unsigned int i = start_offset; i < end_offset; i += 4, cfg++)
pisp_fe_reg_write_relaxed(fe, PISP_FE_CONFIG_BASE_OFFSET + i,
*cfg);
}
void pisp_fe_isr(struct pisp_fe_device *fe, bool *sof, bool *eof)
{
u32 status, int_status, out_status, frame_status, error_status;
pisp_fe_reg_write(fe, FE_CONTROL, FE_CONTROL_LATCH_REGS);
status = pisp_fe_reg_read(fe, FE_STATUS);
out_status = pisp_fe_reg_read(fe, FE_OUTPUT_STATUS);
frame_status = pisp_fe_reg_read(fe, FE_FRAME_STATUS);
error_status = pisp_fe_reg_read(fe, FE_ERROR_STATUS);
int_status = pisp_fe_reg_read(fe, FE_INT_STATUS);
pisp_fe_reg_write(fe, FE_INT_STATUS, int_status);
trace_fe_irq(status, out_status, frame_status, error_status,
int_status);
/* We do not report interrupts for the input/stream pad. */
for (unsigned int i = 0; i < FE_NUM_PADS - 1; i++) {
sof[i] = !!(int_status & FE_INT_SOF);
eof[i] = !!(int_status & FE_INT_EOF);
}
}
static bool pisp_fe_validate_output(struct pisp_fe_config const *cfg,
unsigned int c, struct v4l2_format const *f)
{
unsigned int wbytes;
wbytes = cfg->ch[c].output.format.width;
if (cfg->ch[c].output.format.format & PISP_IMAGE_FORMAT_BPS_MASK)
wbytes *= 2;
/* Check output image dimensions are nonzero and not too big */
if (cfg->ch[c].output.format.width < 2 ||
cfg->ch[c].output.format.height < 2 ||
cfg->ch[c].output.format.height > f->fmt.pix.height ||
cfg->ch[c].output.format.stride > f->fmt.pix.bytesperline ||
wbytes > f->fmt.pix.bytesperline)
return false;
/* Check for zero-sized crops, which could cause lockup */
if ((cfg->global.enables & PISP_FE_ENABLE_CROP(c)) &&
((cfg->ch[c].crop.offset_x >= (cfg->input.format.width & ~1) ||
cfg->ch[c].crop.offset_y >= cfg->input.format.height ||
cfg->ch[c].crop.width < 2 || cfg->ch[c].crop.height < 2)))
return false;
if ((cfg->global.enables & PISP_FE_ENABLE_DOWNSCALE(c)) &&
(cfg->ch[c].downscale.output_width < 2 ||
cfg->ch[c].downscale.output_height < 2))
return false;
return true;
}
static bool pisp_fe_validate_stats(struct pisp_fe_config const *cfg)
{
/* Check for zero-sized crop, which could cause lockup */
return (!(cfg->global.enables & PISP_FE_ENABLE_STATS_CROP) ||
(cfg->stats_crop.offset_x < (cfg->input.format.width & ~1) &&
cfg->stats_crop.offset_y < cfg->input.format.height &&
cfg->stats_crop.width >= 2 && cfg->stats_crop.height >= 2));
}
int pisp_fe_validate_config(struct pisp_fe_device *fe,
struct pisp_fe_config *cfg,
struct v4l2_format const *f0,
struct v4l2_format const *f1)
{
/*
* Check the input is enabled, streaming and has nonzero size;
* to avoid cases where the hardware might lock up or try to
* read inputs from memory (which this driver doesn't support).
*/
if (!(cfg->global.enables & PISP_FE_ENABLE_INPUT) ||
cfg->input.streaming != 1 || cfg->input.format.width < 2 ||
cfg->input.format.height < 2) {
pisp_fe_err(fe, "%s: Input config not valid", __func__);
return -EINVAL;
}
for (unsigned int i = 0; i < PISP_FE_NUM_OUTPUTS; i++) {
if (!(cfg->global.enables & PISP_FE_ENABLE_OUTPUT(i))) {
if (cfg->global.enables &
PISP_FE_ENABLE_OUTPUT_CLUSTER(i)) {
pisp_fe_err(fe, "%s: Output %u not valid",
__func__, i);
return -EINVAL;
}
continue;
}
if (!pisp_fe_validate_output(cfg, i, i ? f1 : f0))
return -EINVAL;
}
if ((cfg->global.enables & PISP_FE_ENABLE_STATS_CLUSTER) &&
!pisp_fe_validate_stats(cfg)) {
pisp_fe_err(fe, "%s: Stats config not valid", __func__);
return -EINVAL;
}
return 0;
}
void pisp_fe_submit_job(struct pisp_fe_device *fe, struct vb2_buffer **vb2_bufs,
struct pisp_fe_config *cfg)
{
u64 addr;
u32 status;
/*
* Check output buffers exist and outputs are correctly configured.
* If valid, set the buffer's DMA address; otherwise disable.
*/
for (unsigned int i = 0; i < PISP_FE_NUM_OUTPUTS; i++) {
struct vb2_buffer *buf = vb2_bufs[FE_OUTPUT0_PAD + i];
if (!(cfg->global.enables & PISP_FE_ENABLE_OUTPUT(i)))
continue;
addr = vb2_dma_contig_plane_dma_addr(buf, 0);
cfg->output_buffer[i].addr_lo = addr & 0xffffffff;
cfg->output_buffer[i].addr_hi = addr >> 32;
}
if (vb2_bufs[FE_STATS_PAD]) {
addr = vb2_dma_contig_plane_dma_addr(vb2_bufs[FE_STATS_PAD], 0);
cfg->stats_buffer.addr_lo = addr & 0xffffffff;
cfg->stats_buffer.addr_hi = addr >> 32;
}
/* Set up ILINES interrupts 3/4 of the way down each output */
cfg->ch[0].output.ilines =
max(0x80u, (3u * cfg->ch[0].output.format.height) >> 2);
cfg->ch[1].output.ilines =
max(0x80u, (3u * cfg->ch[1].output.format.height) >> 2);
/*
* The hardware must have consumed the previous config by now.
* This read of status also serves as a memory barrier before the
* sequence of relaxed writes which follow.
*/
status = pisp_fe_reg_read(fe, FE_STATUS);
if (WARN_ON(status & FE_STATUS_QUEUED))
return;
/*
* Unconditionally write buffers, global and input parameters.
* Write cropping and output parameters whenever they are enabled.
* Selectively write other parameters that have been marked as
* changed through the dirty flags.
*/
pisp_fe_config_write(fe, cfg, 0,
offsetof(struct pisp_fe_config, decompress));
cfg->dirty_flags_extra &= ~PISP_FE_DIRTY_GLOBAL;
cfg->dirty_flags &= ~PISP_FE_ENABLE_INPUT;
cfg->dirty_flags |= (cfg->global.enables &
(PISP_FE_ENABLE_STATS_CROP |
PISP_FE_ENABLE_OUTPUT_CLUSTER(0) |
PISP_FE_ENABLE_OUTPUT_CLUSTER(1)));
for (unsigned int i = 0; i < ARRAY_SIZE(pisp_fe_config_map); i++) {
const struct pisp_fe_config_param *p = &pisp_fe_config_map[i];
if (cfg->dirty_flags & p->dirty_flags ||
cfg->dirty_flags_extra & p->dirty_flags_extra)
pisp_fe_config_write(fe, cfg, p->offset, p->size);
}
/* This final non-relaxed write serves as a memory barrier */
pisp_fe_reg_write(fe, FE_CONTROL, FE_CONTROL_QUEUE);
}
void pisp_fe_start(struct pisp_fe_device *fe)
{
pisp_fe_reg_write(fe, FE_CONTROL, FE_CONTROL_RESET);
pisp_fe_reg_write(fe, FE_INT_STATUS, ~0);
pisp_fe_reg_write(fe, FE_INT_EN, FE_INT_EOF | FE_INT_SOF |
FE_INT_LINES0 | FE_INT_LINES1);
fe->inframe_count = 0;
}
void pisp_fe_stop(struct pisp_fe_device *fe)
{
pisp_fe_reg_write(fe, FE_INT_EN, 0);
pisp_fe_reg_write(fe, FE_CONTROL, FE_CONTROL_ABORT);
usleep_range(1000, 2000);
WARN_ON(pisp_fe_reg_read(fe, FE_STATUS));
pisp_fe_reg_write(fe, FE_INT_STATUS, ~0);
}
static int pisp_fe_init_state(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state)
{
struct v4l2_mbus_framefmt *fmt;
fmt = v4l2_subdev_state_get_format(state, FE_STREAM_PAD);
*fmt = cfe_default_format;
fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16;
fmt = v4l2_subdev_state_get_format(state, FE_CONFIG_PAD);
fmt->code = MEDIA_BUS_FMT_FIXED;
fmt->width = sizeof(struct pisp_fe_config);
fmt->height = 1;
fmt = v4l2_subdev_state_get_format(state, FE_OUTPUT0_PAD);
*fmt = cfe_default_format;
fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16;
fmt = v4l2_subdev_state_get_format(state, FE_OUTPUT1_PAD);
*fmt = cfe_default_format;
fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16;
fmt = v4l2_subdev_state_get_format(state, FE_STATS_PAD);
fmt->code = MEDIA_BUS_FMT_FIXED;
fmt->width = sizeof(struct pisp_statistics);
fmt->height = 1;
return 0;
}
static int pisp_fe_pad_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_format *format)
{
struct v4l2_mbus_framefmt *fmt;
const struct cfe_fmt *cfe_fmt;
/* TODO: format propagation to source pads */
/* TODO: format validation */
switch (format->pad) {
case FE_STREAM_PAD:
cfe_fmt = find_format_by_code(format->format.code);
if (!cfe_fmt || !(cfe_fmt->flags & CFE_FORMAT_FLAG_FE_OUT))
cfe_fmt = find_format_by_code(MEDIA_BUS_FMT_SRGGB16_1X16);
format->format.code = cfe_fmt->code;
format->format.field = V4L2_FIELD_NONE;
fmt = v4l2_subdev_state_get_format(state, FE_STREAM_PAD);
*fmt = format->format;
fmt = v4l2_subdev_state_get_format(state, FE_OUTPUT0_PAD);
*fmt = format->format;
fmt = v4l2_subdev_state_get_format(state, FE_OUTPUT1_PAD);
*fmt = format->format;
return 0;
case FE_OUTPUT0_PAD:
case FE_OUTPUT1_PAD: {
/*
* TODO: we should allow scaling and cropping by allowing the
* user to set the size here.
*/
struct v4l2_mbus_framefmt *sink_fmt, *source_fmt;
u32 sink_code;
u32 code;
cfe_fmt = find_format_by_code(format->format.code);
if (!cfe_fmt || !(cfe_fmt->flags & CFE_FORMAT_FLAG_FE_OUT))
cfe_fmt = find_format_by_code(MEDIA_BUS_FMT_SRGGB16_1X16);
format->format.code = cfe_fmt->code;
sink_fmt = v4l2_subdev_state_get_format(state, FE_STREAM_PAD);
if (!sink_fmt)
return -EINVAL;
source_fmt = v4l2_subdev_state_get_format(state, format->pad);
if (!source_fmt)
return -EINVAL;
sink_code = sink_fmt->code;
code = format->format.code;
/*
* If the source code from the user does not match the code in
* the sink pad, check that the source code matches the
* compressed version of the sink code.
*/
if (code != sink_code &&
code == cfe_find_compressed_code(sink_code))
source_fmt->code = code;
return 0;
}
case FE_CONFIG_PAD:
case FE_STATS_PAD:
default:
return v4l2_subdev_get_fmt(sd, state, format);
}
}
static const struct v4l2_subdev_pad_ops pisp_fe_subdev_pad_ops = {
.get_fmt = v4l2_subdev_get_fmt,
.set_fmt = pisp_fe_pad_set_fmt,
.link_validate = v4l2_subdev_link_validate_default,
};
static int pisp_fe_link_validate(struct media_link *link)
{
struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(link->sink->entity);
struct pisp_fe_device *fe = container_of(sd, struct pisp_fe_device, sd);
pisp_fe_dbg(fe, "%s: link \"%s\":%u -> \"%s\":%u\n", __func__,
link->source->entity->name, link->source->index,
link->sink->entity->name, link->sink->index);
if (link->sink->index == FE_STREAM_PAD)
return v4l2_subdev_link_validate(link);
if (link->sink->index == FE_CONFIG_PAD)
return 0;
return -EINVAL;
}
static const struct media_entity_operations pisp_fe_entity_ops = {
.link_validate = pisp_fe_link_validate,
};
static const struct v4l2_subdev_ops pisp_fe_subdev_ops = {
.pad = &pisp_fe_subdev_pad_ops,
};
static const struct v4l2_subdev_internal_ops pisp_fe_internal_ops = {
.init_state = pisp_fe_init_state,
};
int pisp_fe_init(struct pisp_fe_device *fe, struct dentry *debugfs)
{
int ret;
debugfs_create_file("fe_regs", 0440, debugfs, fe, &pisp_fe_regs_fops);
fe->hw_revision = pisp_fe_reg_read(fe, FE_VERSION);
pisp_fe_info(fe, "PiSP FE HW v%u.%u\n",
(fe->hw_revision >> 24) & 0xff,
(fe->hw_revision >> 20) & 0x0f);
fe->pad[FE_STREAM_PAD].flags =
MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
fe->pad[FE_CONFIG_PAD].flags = MEDIA_PAD_FL_SINK;
fe->pad[FE_OUTPUT0_PAD].flags = MEDIA_PAD_FL_SOURCE;
fe->pad[FE_OUTPUT1_PAD].flags = MEDIA_PAD_FL_SOURCE;
fe->pad[FE_STATS_PAD].flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&fe->sd.entity, ARRAY_SIZE(fe->pad),
fe->pad);
if (ret)
return ret;
/* Initialize subdev */
v4l2_subdev_init(&fe->sd, &pisp_fe_subdev_ops);
fe->sd.internal_ops = &pisp_fe_internal_ops;
fe->sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
fe->sd.entity.ops = &pisp_fe_entity_ops;
fe->sd.entity.name = "pisp-fe";
fe->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
fe->sd.owner = THIS_MODULE;
snprintf(fe->sd.name, sizeof(fe->sd.name), "pisp-fe");
ret = v4l2_subdev_init_finalize(&fe->sd);
if (ret)
goto err_entity_cleanup;
ret = v4l2_device_register_subdev(fe->v4l2_dev, &fe->sd);
if (ret) {
pisp_fe_err(fe, "Failed register pisp fe subdev (%d)\n", ret);
goto err_subdev_cleanup;
}
/* Must be in IDLE state (STATUS == 0) here. */
WARN_ON(pisp_fe_reg_read(fe, FE_STATUS));
return 0;
err_subdev_cleanup:
v4l2_subdev_cleanup(&fe->sd);
err_entity_cleanup:
media_entity_cleanup(&fe->sd.entity);
return ret;
}
void pisp_fe_uninit(struct pisp_fe_device *fe)
{
v4l2_device_unregister_subdev(&fe->sd);
v4l2_subdev_cleanup(&fe->sd);
media_entity_cleanup(&fe->sd.entity);
}

View File

@ -0,0 +1,53 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* PiSP Front End Driver
*
* Copyright (c) 2021-2024 Raspberry Pi Ltd.
*/
#ifndef _PISP_FE_H_
#define _PISP_FE_H_
#include <linux/debugfs.h>
#include <linux/io.h>
#include <linux/types.h>
#include <linux/videodev2.h>
#include <media/media-device.h>
#include <media/v4l2-device.h>
#include <media/v4l2-subdev.h>
#include <linux/media/raspberrypi/pisp_fe_config.h>
enum pisp_fe_pads {
FE_STREAM_PAD,
FE_CONFIG_PAD,
FE_OUTPUT0_PAD,
FE_OUTPUT1_PAD,
FE_STATS_PAD,
FE_NUM_PADS
};
struct pisp_fe_device {
/* Parent V4l2 device */
struct v4l2_device *v4l2_dev;
void __iomem *base;
u32 hw_revision;
u16 inframe_count;
struct media_pad pad[FE_NUM_PADS];
struct v4l2_subdev sd;
};
void pisp_fe_isr(struct pisp_fe_device *fe, bool *sof, bool *eof);
int pisp_fe_validate_config(struct pisp_fe_device *fe,
struct pisp_fe_config *cfg,
struct v4l2_format const *f0,
struct v4l2_format const *f1);
void pisp_fe_submit_job(struct pisp_fe_device *fe, struct vb2_buffer **vb2_bufs,
struct pisp_fe_config *cfg);
void pisp_fe_start(struct pisp_fe_device *fe);
void pisp_fe_stop(struct pisp_fe_device *fe);
int pisp_fe_init(struct pisp_fe_device *fe, struct dentry *debugfs);
void pisp_fe_uninit(struct pisp_fe_device *fe);
#endif

View File

@ -0,0 +1,273 @@
/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
/*
* RP1 PiSP Front End Driver Configuration structures
*
* Copyright (C) 2021 - Raspberry Pi Ltd.
*
*/
#ifndef _UAPI_PISP_FE_CONFIG_
#define _UAPI_PISP_FE_CONFIG_
#include <linux/types.h>
#include "pisp_common.h"
#include "pisp_fe_statistics.h"
#define PISP_FE_NUM_OUTPUTS 2
enum pisp_fe_enable {
PISP_FE_ENABLE_INPUT = 0x000001,
PISP_FE_ENABLE_DECOMPRESS = 0x000002,
PISP_FE_ENABLE_DECOMPAND = 0x000004,
PISP_FE_ENABLE_BLA = 0x000008,
PISP_FE_ENABLE_DPC = 0x000010,
PISP_FE_ENABLE_STATS_CROP = 0x000020,
PISP_FE_ENABLE_DECIMATE = 0x000040,
PISP_FE_ENABLE_BLC = 0x000080,
PISP_FE_ENABLE_CDAF_STATS = 0x000100,
PISP_FE_ENABLE_AWB_STATS = 0x000200,
PISP_FE_ENABLE_RGBY = 0x000400,
PISP_FE_ENABLE_LSC = 0x000800,
PISP_FE_ENABLE_AGC_STATS = 0x001000,
PISP_FE_ENABLE_CROP0 = 0x010000,
PISP_FE_ENABLE_DOWNSCALE0 = 0x020000,
PISP_FE_ENABLE_COMPRESS0 = 0x040000,
PISP_FE_ENABLE_OUTPUT0 = 0x080000,
PISP_FE_ENABLE_CROP1 = 0x100000,
PISP_FE_ENABLE_DOWNSCALE1 = 0x200000,
PISP_FE_ENABLE_COMPRESS1 = 0x400000,
PISP_FE_ENABLE_OUTPUT1 = 0x800000
};
#define PISP_FE_ENABLE_CROP(i) (PISP_FE_ENABLE_CROP0 << (4 * (i)))
#define PISP_FE_ENABLE_DOWNSCALE(i) (PISP_FE_ENABLE_DOWNSCALE0 << (4 * (i)))
#define PISP_FE_ENABLE_COMPRESS(i) (PISP_FE_ENABLE_COMPRESS0 << (4 * (i)))
#define PISP_FE_ENABLE_OUTPUT(i) (PISP_FE_ENABLE_OUTPUT0 << (4 * (i)))
/*
* We use the enable flags to show when blocks are "dirty", but we need some
* extra ones too.
*/
enum pisp_fe_dirty {
PISP_FE_DIRTY_GLOBAL = 0x0001,
PISP_FE_DIRTY_FLOATING = 0x0002,
PISP_FE_DIRTY_OUTPUT_AXI = 0x0004
};
struct pisp_fe_global_config {
__u32 enables;
__u8 bayer_order;
__u8 pad[3];
} __attribute__((packed));
struct pisp_fe_input_axi_config {
/* burst length minus one, in the range 0..15; OR'd with flags */
__u8 maxlen_flags;
/* { prot[2:0], cache[3:0] } fields */
__u8 cache_prot;
/* QoS (only 4 LS bits are used) */
__u16 qos;
} __attribute__((packed));
struct pisp_fe_output_axi_config {
/* burst length minus one, in the range 0..15; OR'd with flags */
__u8 maxlen_flags;
/* { prot[2:0], cache[3:0] } fields */
__u8 cache_prot;
/* QoS (4 bitfields of 4 bits each for different panic levels) */
__u16 qos;
/* For Panic mode: Output FIFO panic threshold */
__u16 thresh;
/* For Panic mode: Output FIFO statistics throttle threshold */
__u16 throttle;
} __attribute__((packed));
struct pisp_fe_input_config {
__u8 streaming;
__u8 pad[3];
struct pisp_image_format_config format;
struct pisp_fe_input_axi_config axi;
/* Extra cycles delay before issuing each burst request */
__u8 holdoff;
__u8 pad2[3];
} __attribute__((packed));
struct pisp_fe_output_config {
struct pisp_image_format_config format;
__u16 ilines;
__u8 pad[2];
} __attribute__((packed));
struct pisp_fe_input_buffer_config {
__u32 addr_lo;
__u32 addr_hi;
__u16 frame_id;
__u16 pad;
} __attribute__((packed));
#define PISP_FE_DECOMPAND_LUT_SIZE 65
struct pisp_fe_decompand_config {
__u16 lut[PISP_FE_DECOMPAND_LUT_SIZE];
__u16 pad;
} __attribute__((packed));
struct pisp_fe_dpc_config {
__u8 coeff_level;
__u8 coeff_range;
__u8 coeff_range2;
#define PISP_FE_DPC_FLAG_FOLDBACK 1
#define PISP_FE_DPC_FLAG_VFLAG 2
__u8 flags;
} __attribute__((packed));
#define PISP_FE_LSC_LUT_SIZE 16
struct pisp_fe_lsc_config {
__u8 shift;
__u8 pad0;
__u16 scale;
__u16 centre_x;
__u16 centre_y;
__u16 lut[PISP_FE_LSC_LUT_SIZE];
} __attribute__((packed));
struct pisp_fe_rgby_config {
__u16 gain_r;
__u16 gain_g;
__u16 gain_b;
__u8 maxflag;
__u8 pad;
} __attribute__((packed));
struct pisp_fe_agc_stats_config {
__u16 offset_x;
__u16 offset_y;
__u16 size_x;
__u16 size_y;
/* each weight only 4 bits */
__u8 weights[PISP_AGC_STATS_NUM_ZONES / 2];
__u16 row_offset_x;
__u16 row_offset_y;
__u16 row_size_x;
__u16 row_size_y;
__u8 row_shift;
__u8 float_shift;
__u8 pad1[2];
} __attribute__((packed));
struct pisp_fe_awb_stats_config {
__u16 offset_x;
__u16 offset_y;
__u16 size_x;
__u16 size_y;
__u8 shift;
__u8 pad[3];
__u16 r_lo;
__u16 r_hi;
__u16 g_lo;
__u16 g_hi;
__u16 b_lo;
__u16 b_hi;
} __attribute__((packed));
struct pisp_fe_floating_stats_region {
__u16 offset_x;
__u16 offset_y;
__u16 size_x;
__u16 size_y;
} __attribute__((packed));
struct pisp_fe_floating_stats_config {
struct pisp_fe_floating_stats_region
regions[PISP_FLOATING_STATS_NUM_ZONES];
} __attribute__((packed));
#define PISP_FE_CDAF_NUM_WEIGHTS 8
struct pisp_fe_cdaf_stats_config {
__u16 noise_constant;
__u16 noise_slope;
__u16 offset_x;
__u16 offset_y;
__u16 size_x;
__u16 size_y;
__u16 skip_x;
__u16 skip_y;
__u32 mode;
} __attribute__((packed));
struct pisp_fe_stats_buffer_config {
__u32 addr_lo;
__u32 addr_hi;
} __attribute__((packed));
struct pisp_fe_crop_config {
__u16 offset_x;
__u16 offset_y;
__u16 width;
__u16 height;
} __attribute__((packed));
enum pisp_fe_downscale_flags {
/* downscale the four Bayer components independently... */
DOWNSCALE_BAYER = 1,
/* ...without trying to preserve their spatial relationship */
DOWNSCALE_BIN = 2,
};
struct pisp_fe_downscale_config {
__u8 xin;
__u8 xout;
__u8 yin;
__u8 yout;
__u8 flags; /* enum pisp_fe_downscale_flags */
__u8 pad[3];
__u16 output_width;
__u16 output_height;
} __attribute__((packed));
struct pisp_fe_output_buffer_config {
__u32 addr_lo;
__u32 addr_hi;
} __attribute__((packed));
/* Each of the two output channels/branches: */
struct pisp_fe_output_branch_config {
struct pisp_fe_crop_config crop;
struct pisp_fe_downscale_config downscale;
struct pisp_compress_config compress;
struct pisp_fe_output_config output;
__u32 pad;
} __attribute__((packed));
/* And finally one to rule them all: */
struct pisp_fe_config {
/* I/O configuration: */
struct pisp_fe_stats_buffer_config stats_buffer;
struct pisp_fe_output_buffer_config output_buffer[PISP_FE_NUM_OUTPUTS];
struct pisp_fe_input_buffer_config input_buffer;
/* processing configuration: */
struct pisp_fe_global_config global;
struct pisp_fe_input_config input;
struct pisp_decompress_config decompress;
struct pisp_fe_decompand_config decompand;
struct pisp_bla_config bla;
struct pisp_fe_dpc_config dpc;
struct pisp_fe_crop_config stats_crop;
__u32 spare1; /* placeholder for future decimate configuration */
struct pisp_bla_config blc;
struct pisp_fe_rgby_config rgby;
struct pisp_fe_lsc_config lsc;
struct pisp_fe_agc_stats_config agc_stats;
struct pisp_fe_awb_stats_config awb_stats;
struct pisp_fe_cdaf_stats_config cdaf_stats;
struct pisp_fe_floating_stats_config floating_stats;
struct pisp_fe_output_axi_config output_axi;
struct pisp_fe_output_branch_config ch[PISP_FE_NUM_OUTPUTS];
/* non-register fields: */
__u32 dirty_flags; /* these use pisp_fe_enable */
__u32 dirty_flags_extra; /* these use pisp_fe_dirty */
} __attribute__((packed));
#endif /* _UAPI_PISP_FE_CONFIG_ */

View File

@ -0,0 +1,64 @@
/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
/*
* RP1 PiSP Front End statistics definitions
*
* Copyright (C) 2021 - Raspberry Pi Ltd.
*
*/
#ifndef _UAPI_PISP_FE_STATISTICS_H_
#define _UAPI_PISP_FE_STATISTICS_H_
#include <linux/types.h>
#define PISP_FLOATING_STATS_NUM_ZONES 4
#define PISP_AGC_STATS_NUM_BINS 1024
#define PISP_AGC_STATS_SIZE 16
#define PISP_AGC_STATS_NUM_ZONES (PISP_AGC_STATS_SIZE * PISP_AGC_STATS_SIZE)
#define PISP_AGC_STATS_NUM_ROW_SUMS 512
struct pisp_agc_statistics_zone {
__u64 Y_sum;
__u32 counted;
__u32 pad;
} __attribute__((packed));
struct pisp_agc_statistics {
__u32 row_sums[PISP_AGC_STATS_NUM_ROW_SUMS];
/*
* 32-bits per bin means an image (just less than) 16384x16384 pixels
* in size can weight every pixel from 0 to 15.
*/
__u32 histogram[PISP_AGC_STATS_NUM_BINS];
struct pisp_agc_statistics_zone floating[PISP_FLOATING_STATS_NUM_ZONES];
} __attribute__((packed));
#define PISP_AWB_STATS_SIZE 32
#define PISP_AWB_STATS_NUM_ZONES (PISP_AWB_STATS_SIZE * PISP_AWB_STATS_SIZE)
struct pisp_awb_statistics_zone {
__u32 R_sum;
__u32 G_sum;
__u32 B_sum;
__u32 counted;
} __attribute__((packed));
struct pisp_awb_statistics {
struct pisp_awb_statistics_zone zones[PISP_AWB_STATS_NUM_ZONES];
struct pisp_awb_statistics_zone floating[PISP_FLOATING_STATS_NUM_ZONES];
} __attribute__((packed));
#define PISP_CDAF_STATS_SIZE 8
#define PISP_CDAF_STATS_NUM_FOMS (PISP_CDAF_STATS_SIZE * PISP_CDAF_STATS_SIZE)
struct pisp_cdaf_statistics {
__u64 foms[PISP_CDAF_STATS_NUM_FOMS];
__u64 floating[PISP_FLOATING_STATS_NUM_ZONES];
} __attribute__((packed));
struct pisp_statistics {
struct pisp_awb_statistics awb;
struct pisp_agc_statistics agc;
struct pisp_cdaf_statistics cdaf;
} __attribute__((packed));
#endif /* _UAPI_PISP_FE_STATISTICS_H_ */