new file mode 100644
@@ -0,0 +1,579 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (C) 2013 - 2023 Intel Corporation
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+
+#include <media/media-entity.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+
+#include "ipu6.h"
+#include "ipu6-bus.h"
+#include "ipu6-buttress.h"
+#include "ipu6-isys.h"
+#include "ipu6-isys-csi2.h"
+#include "ipu6-isys-phy.h"
+#include "ipu6-isys-subdev.h"
+#include "ipu6-isys-video.h"
+#include "ipu6-platform-buttress-regs.h"
+#include "ipu6-platform-isys-csi2-reg.h"
+#include "ipu6-platform-regs.h"
+
+static const u32 csi2_supported_codes[] = {
+ MEDIA_BUS_FMT_RGB565_1X16,
+ MEDIA_BUS_FMT_RGB888_1X24,
+ MEDIA_BUS_FMT_UYVY8_1X16,
+ MEDIA_BUS_FMT_YUYV8_1X16,
+ MEDIA_BUS_FMT_SBGGR10_1X10,
+ MEDIA_BUS_FMT_SGBRG10_1X10,
+ MEDIA_BUS_FMT_SGRBG10_1X10,
+ MEDIA_BUS_FMT_SRGGB10_1X10,
+ MEDIA_BUS_FMT_SBGGR12_1X12,
+ MEDIA_BUS_FMT_SGBRG12_1X12,
+ MEDIA_BUS_FMT_SGRBG12_1X12,
+ MEDIA_BUS_FMT_SRGGB12_1X12,
+ MEDIA_BUS_FMT_SBGGR8_1X8,
+ MEDIA_BUS_FMT_SGBRG8_1X8,
+ MEDIA_BUS_FMT_SGRBG8_1X8,
+ MEDIA_BUS_FMT_SRGGB8_1X8,
+ 0,
+};
+
+/*
+ * Strings corresponding to CSI-2 receiver errors are here.
+ * Corresponding macros are defined in the header file.
+ */
+static struct ipu6_csi2_error dphy_rx_errors[] = {
+ {"Single packet header error corrected", true},
+ {"Multiple packet header errors detected", true},
+ {"Payload checksum (CRC) error", true},
+ {"Transfer FIFO overflow", false},
+ {"Reserved short packet data type detected", true},
+ {"Reserved long packet data type detected", true},
+ {"Incomplete long packet detected", false},
+ {"Frame sync error", false},
+ {"Line sync error", false},
+ {"DPHY recoverable synchronization error", true},
+ {"DPHY fatal error", false},
+ {"DPHY elastic FIFO overflow", false},
+ {"Inter-frame short packet discarded", true},
+ {"Inter-frame long packet discarded", true},
+ {"MIPI pktgen overflow", false},
+ {"MIPI pktgen data loss", false},
+ {"FIFO overflow", false},
+ {"Lane deskew", false},
+ {"SOT sync error", false},
+ {"HSIDLE detected", false}
+};
+
+int ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2, s64 *link_freq)
+{
+ struct media_pad *src_pad;
+ struct v4l2_subdev *ext_sd;
+ unsigned int bpp, lanes;
+ struct device *dev;
+ s64 ret;
+
+ if (!csi2 || !link_freq)
+ return -EINVAL;
+
+ dev = &csi2->isys->adev->dev;
+ src_pad = media_entity_remote_source_pad_unique(&csi2->asd.sd.entity);
+ if (IS_ERR_OR_NULL(src_pad)) {
+ dev_err(dev, "can't get source pad of %s\n", csi2->asd.sd.name);
+ return -ENOLINK;
+ }
+
+ ext_sd = media_entity_to_v4l2_subdev(src_pad->entity);
+ if (WARN(!ext_sd, "Failed to get subdev for %s\n", csi2->asd.sd.name))
+ return -ENODEV;
+
+ bpp = ipu6_isys_mbus_code_to_bpp(csi2->asd.ffmt->code);
+ lanes = csi2->nlanes;
+
+ ret = v4l2_get_link_freq(ext_sd->ctrl_handler, bpp, lanes * 2);
+ if (ret < 0) {
+ dev_err(dev, "can't get link frequency (%lld)\n", ret);
+ return ret;
+ }
+
+ dev_dbg(dev, "link freq of %s is %lld\n", ext_sd->name, ret);
+ *link_freq = ret;
+
+ return 0;
+}
+
+static int csi2_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
+ struct v4l2_event_subscription *sub)
+{
+ struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(sd);
+
+ dev_dbg(&csi2->isys->adev->dev, "csi2 subscribe event(type %u id %u)\n",
+ sub->type, sub->id);
+
+ switch (sub->type) {
+ case V4L2_EVENT_FRAME_SYNC:
+ return v4l2_event_subscribe(fh, sub, 10, NULL);
+ case V4L2_EVENT_CTRL:
+ return v4l2_ctrl_subscribe_event(fh, sub);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct v4l2_subdev_core_ops csi2_sd_core_ops = {
+ .subscribe_event = csi2_subscribe_event,
+ .unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+/*
+ * The input system CSI2+ receiver has several
+ * parameters affecting the receiver timings. These depend
+ * on the MIPI bus frequency F in Hz (sensor transmitter rate)
+ * as follows:
+ * register value = (A/1e9 + B * UI) / COUNT_ACC
+ * where
+ * UI = 1 / (2 * F) in seconds
+ * COUNT_ACC = counter accuracy in seconds
+ * COUNT_ACC = 0.125 ns = 1 / 8 ns, ACCINV = 8.
+ *
+ * A and B are coefficients from the table below,
+ * depending whether the register minimum or maximum value is
+ * calculated.
+ * Minimum Maximum
+ * Clock lane A B A B
+ * reg_rx_csi_dly_cnt_termen_clane 0 0 38 0
+ * reg_rx_csi_dly_cnt_settle_clane 95 -8 300 -16
+ * Data lanes
+ * reg_rx_csi_dly_cnt_termen_dlane0 0 0 35 4
+ * reg_rx_csi_dly_cnt_settle_dlane0 85 -2 145 -6
+ * reg_rx_csi_dly_cnt_termen_dlane1 0 0 35 4
+ * reg_rx_csi_dly_cnt_settle_dlane1 85 -2 145 -6
+ * reg_rx_csi_dly_cnt_termen_dlane2 0 0 35 4
+ * reg_rx_csi_dly_cnt_settle_dlane2 85 -2 145 -6
+ * reg_rx_csi_dly_cnt_termen_dlane3 0 0 35 4
+ * reg_rx_csi_dly_cnt_settle_dlane3 85 -2 145 -6
+ *
+ * We use the minimum values of both A and B.
+ */
+
+#define DIV_SHIFT 8
+#define CSI2_ACCINV 8
+
+static u32 calc_timing(s32 a, s32 b, s64 link_freq, u32 accinv)
+{
+ return accinv * a + (accinv * b * (500000000 >> DIV_SHIFT)
+ / (int32_t)(link_freq >> DIV_SHIFT));
+}
+
+static int
+ipu6_isys_csi2_calc_timing(struct ipu6_isys_csi2 *csi2,
+ struct ipu6_isys_csi2_timing *timing, u32 accinv)
+{
+ s64 link_freq;
+ int ret;
+
+ ret = ipu6_isys_csi2_get_link_freq(csi2, &link_freq);
+ if (ret)
+ return ret;
+
+ timing->ctermen = calc_timing(CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_A,
+ CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_B,
+ link_freq, accinv);
+ timing->csettle = calc_timing(CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_A,
+ CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_B,
+ link_freq, accinv);
+ dev_dbg(&csi2->isys->adev->dev, "ctermen %u\n", timing->ctermen);
+ dev_dbg(&csi2->isys->adev->dev, "csettle %u\n", timing->csettle);
+
+ timing->dtermen = calc_timing(CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_A,
+ CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_B,
+ link_freq, accinv);
+ timing->dsettle = calc_timing(CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_A,
+ CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_B,
+ link_freq, accinv);
+ dev_dbg(&csi2->isys->adev->dev, "dtermen %u\n", timing->dtermen);
+ dev_dbg(&csi2->isys->adev->dev, "dsettle %u\n", timing->dsettle);
+
+ return 0;
+}
+
+void ipu6_isys_register_errors(struct ipu6_isys_csi2 *csi2)
+{
+ u32 irq = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
+ CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET);
+ struct ipu6_isys *isys = csi2->isys;
+ u32 mask;
+
+ mask = isys->pdata->ipdata->csi2.irq_mask;
+ writel(irq & mask, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
+ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
+ csi2->receiver_errors |= irq & mask;
+}
+
+void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2)
+{
+ struct ipu6_csi2_error *errors;
+ u32 status;
+ u32 i;
+
+ /* register errors once more in case of interrupts are disabled */
+ ipu6_isys_register_errors(csi2);
+ status = csi2->receiver_errors;
+ csi2->receiver_errors = 0;
+ errors = dphy_rx_errors;
+
+ for (i = 0; i < CSI_RX_NUM_ERRORS_IN_IRQ; i++) {
+ if (status & BIT(i))
+ dev_err_ratelimited(&csi2->isys->adev->dev,
+ "csi2-%i error: %s\n",
+ csi2->port,
+ errors[i].error_string);
+ }
+}
+
+static int ipu6_isys_csi2_set_stream(struct v4l2_subdev *sd,
+ const struct ipu6_isys_csi2_timing *timing,
+ unsigned int nlanes, int enable)
+{
+ struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(sd);
+ struct ipu6_isys *isys = csi2->isys;
+ struct ipu6_isys_stream *stream;
+ struct ipu6_isys_csi2_config cfg;
+ unsigned int port, nports;
+ int ret = 0;
+ u32 mask = 0;
+ u32 i;
+
+ stream = ipu6_isys_query_stream_by_source(isys, csi2->asd.source);
+ if (!stream) {
+ dev_err(&isys->adev->dev, "no available stream\n");
+ return -EINVAL;
+ }
+
+ ipu6_isys_put_stream(stream);
+
+ port = csi2->port;
+ dev_dbg(&isys->adev->dev, "for port %u with %u lanes\n", port, nlanes);
+
+ cfg.port = port;
+ cfg.nlanes = nlanes;
+
+ mask = isys->pdata->ipdata->csi2.irq_mask;
+ nports = isys->pdata->ipdata->csi2.nports;
+
+ if (!enable) {
+ writel(0, csi2->base + CSI_REG_CSI_FE_ENABLE);
+ writel(0, csi2->base + CSI_REG_PPI2CSI_ENABLE);
+
+ writel(0,
+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
+ CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
+ writel(mask,
+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
+ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
+ writel(0,
+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
+ CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
+ writel(0xffffffff,
+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
+ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
+
+ isys->phy_set_power(isys, &cfg, timing, false);
+
+ writel(0, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT
+ (isys->pdata->ipdata->csi2.fw_access_port_ofs, port));
+ writel(0, isys->pdata->base +
+ CSI_REG_HUB_DRV_ACCESS_PORT(port));
+
+ return ret;
+ }
+
+ /* reset port reset */
+ writel(0x1, csi2->base + CSI_REG_PORT_GPREG_SRST);
+ usleep_range(100, 200);
+ writel(0x0, csi2->base + CSI_REG_PORT_GPREG_SRST);
+
+ /* enable port clock */
+ for (i = 0; i < nports; i++) {
+ writel(1, isys->pdata->base + CSI_REG_HUB_DRV_ACCESS_PORT(i));
+ writel(1, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT
+ (isys->pdata->ipdata->csi2.fw_access_port_ofs, i));
+ }
+
+ /* enable all error related irq */
+ writel(mask,
+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
+ CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET);
+ writel(mask,
+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
+ CSI_PORT_REG_BASE_IRQ_MASK_OFFSET);
+ writel(mask,
+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
+ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
+ writel(mask,
+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
+ CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET);
+ writel(mask,
+ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
+ CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
+
+ /*
+ * Using event from firmware instead of irq to handle CSI2 sync event
+ * which can reduce system wakeups. If CSI2 sync irq enabled, we need
+ * disable the firmware CSI2 sync event to avoid duplicate handling.
+ */
+ writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
+ CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET);
+ writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
+ CSI_PORT_REG_BASE_IRQ_MASK_OFFSET);
+ writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
+ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
+ writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
+ CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET);
+ writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
+ CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
+
+ /* configure to enable FE and PPI2CSI */
+ writel(0, csi2->base + CSI_REG_CSI_FE_MODE);
+ writel(CSI_SENSOR_INPUT, csi2->base + CSI_REG_CSI_FE_MUX_CTRL);
+ writel(CSI_CNTR_SENSOR_LINE_ID | CSI_CNTR_SENSOR_FRAME_ID,
+ csi2->base + CSI_REG_CSI_FE_SYNC_CNTR_SEL);
+ writel(FIELD_PREP(PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK, nlanes - 1),
+ csi2->base + CSI_REG_PPI2CSI_CONFIG_PPI_INTF);
+
+ writel(1, csi2->base + CSI_REG_PPI2CSI_ENABLE);
+ writel(1, csi2->base + CSI_REG_CSI_FE_ENABLE);
+
+ ret = isys->phy_set_power(isys, &cfg, timing, true);
+ if (ret) {
+ dev_err(&isys->adev->dev, "csi-%d phy power up failed %d\n",
+ port, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int set_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(sd);
+ struct ipu6_isys_csi2_timing timing = {0};
+ struct ipu6_isys_stream *stream;
+ unsigned int nlanes;
+ int ret;
+
+ dev_dbg(&csi2->isys->adev->dev, "csi2 s_stream %d\n", enable);
+
+ stream = ipu6_isys_query_stream_by_source(csi2->isys, csi2->asd.source);
+ if (!stream) {
+ dev_err(&csi2->isys->adev->dev, "no available stream\n");
+ return -ENODEV;
+ }
+
+ if (!stream->source_entity) {
+ dev_err(&csi2->isys->adev->dev, "source_entity is NULL\n");
+ return -ENODEV;
+ }
+
+ ipu6_isys_put_stream(stream);
+
+ if (!enable) {
+ ipu6_isys_csi2_set_stream(sd, &timing, 0, enable);
+ return 0;
+ }
+
+ nlanes = csi2->nlanes;
+ dev_dbg(&csi2->isys->adev->dev, "lane nr %d.\n", nlanes);
+
+ ret = ipu6_isys_csi2_calc_timing(csi2, &timing, CSI2_ACCINV);
+ if (ret)
+ return ret;
+
+ return ipu6_isys_csi2_set_stream(sd, &timing, nlanes, enable);
+}
+
+static int ipu6_isys_csi2_set_sel(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
+ struct v4l2_mbus_framefmt *sink_ffmt =
+ ipu6_isys_get_ffmt(sd, state, CSI2_PAD_SINK, sel->which);
+ struct v4l2_mbus_framefmt *src_ffmt =
+ ipu6_isys_get_ffmt(sd, state, sel->pad, sel->which);
+
+ if (sel->pad == CSI2_PAD_SINK || sel->target != V4L2_SEL_TGT_CROP)
+ return -EINVAL;
+
+ mutex_lock(&asd->mutex);
+ /* Only vertical cropping is supported */
+ sel->r.left = 0;
+ sel->r.width = sink_ffmt->width;
+ /* Non-bayer formats can't be single line cropped */
+ if (!ipu6_isys_is_bayer_format(sink_ffmt->code))
+ sel->r.top &= ~1;
+ sel->r.height = clamp(sel->r.height & ~1, IPU6_ISYS_MIN_HEIGHT,
+ sink_ffmt->height - sel->r.top);
+ *ipu6_isys_get_crop(sd, state, sel->pad, sel->which) = sel->r;
+
+ /* update source pad format */
+ src_ffmt->width = sel->r.width;
+ src_ffmt->height = sel->r.height;
+ if (ipu6_isys_is_bayer_format(sink_ffmt->code))
+ src_ffmt->code = ipu6_isys_convert_bayer_order(sink_ffmt->code,
+ sel->r.left,
+ sel->r.top);
+ dev_dbg(&asd->isys->adev->dev,
+ "set crop for %s, sel: %d,%d,%d,%d code: 0x%x\n", sd->name,
+ sel->r.left, sel->r.top, sel->r.width, sel->r.height,
+ src_ffmt->code);
+ mutex_unlock(&asd->mutex);
+
+ return 0;
+}
+
+static int ipu6_isys_csi2_get_sel(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
+ struct v4l2_mbus_framefmt *sink_ffmt =
+ ipu6_isys_get_ffmt(sd, state, CSI2_PAD_SINK, sel->which);
+ struct v4l2_rect *crop =
+ ipu6_isys_get_crop(sd, state, sel->pad, sel->which);
+ int ret = 0;
+
+ if (sd->entity.pads[sel->pad].flags & MEDIA_PAD_FL_SINK)
+ return -EINVAL;
+
+ mutex_lock(&asd->mutex);
+ switch (sel->target) {
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ sel->r.left = 0;
+ sel->r.top = 0;
+ sel->r.width = sink_ffmt->width;
+ sel->r.height = sink_ffmt->height;
+ break;
+ case V4L2_SEL_TGT_CROP:
+ sel->r = *crop;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ mutex_unlock(&asd->mutex);
+
+ return ret;
+}
+
+static const struct v4l2_subdev_video_ops csi2_sd_video_ops = {
+ .s_stream = set_stream,
+};
+
+static const struct v4l2_subdev_pad_ops csi2_sd_pad_ops = {
+ .init_cfg = ipu6_isys_subdev_init_cfg,
+ .link_validate = v4l2_subdev_link_validate_default,
+ .get_fmt = ipu6_isys_subdev_get_fmt,
+ .set_fmt = ipu6_isys_subdev_set_fmt,
+ .get_selection = ipu6_isys_csi2_get_sel,
+ .set_selection = ipu6_isys_csi2_set_sel,
+ .enum_mbus_code = ipu6_isys_subdev_enum_mbus_code,
+};
+
+static const struct v4l2_subdev_ops csi2_sd_ops = {
+ .core = &csi2_sd_core_ops,
+ .video = &csi2_sd_video_ops,
+ .pad = &csi2_sd_pad_ops,
+};
+
+static struct media_entity_operations csi2_entity_ops = {
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+void ipu6_isys_csi2_cleanup(struct ipu6_isys_csi2 *csi2)
+{
+ if (!csi2->isys)
+ return;
+
+ v4l2_device_unregister_subdev(&csi2->asd.sd);
+ ipu6_isys_subdev_cleanup(&csi2->asd);
+ csi2->isys = NULL;
+}
+
+int ipu6_isys_csi2_init(struct ipu6_isys_csi2 *csi2,
+ struct ipu6_isys *isys,
+ void __iomem *base, unsigned int index)
+{
+ struct v4l2_subdev_format fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ .pad = CSI2_PAD_SINK,
+ .format = {
+ .width = 4096,
+ .height = 3072,
+ .code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ },
+ };
+ int ret;
+
+ csi2->isys = isys;
+ csi2->base = base;
+ csi2->port = index;
+
+ csi2->asd.sd.entity.ops = &csi2_entity_ops;
+ csi2->asd.isys = isys;
+ ret = ipu6_isys_subdev_init(&csi2->asd, &csi2_sd_ops, 0,
+ NR_OF_CSI2_PADS);
+ if (ret)
+ goto fail;
+
+ csi2->asd.pad[CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK
+ | MEDIA_PAD_FL_MUST_CONNECT;
+ csi2->asd.pad[CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+ csi2->asd.source = IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT0 + index;
+ csi2->asd.supported_codes = csi2_supported_codes;
+ ipu6_isys_subdev_set_fmt(&csi2->asd.sd, NULL, &fmt);
+ snprintf(csi2->asd.sd.name, sizeof(csi2->asd.sd.name),
+ IPU6_ISYS_ENTITY_PREFIX " CSI2 %u", index);
+ v4l2_set_subdevdata(&csi2->asd.sd, &csi2->asd);
+
+ ret = v4l2_device_register_subdev(&isys->v4l2_dev, &csi2->asd.sd);
+ if (ret) {
+ dev_info(&isys->adev->dev, "can't register v4l2 subdev\n");
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ ipu6_isys_csi2_cleanup(csi2);
+
+ return ret;
+}
+
+void ipu6_isys_csi2_sof_event_by_stream(struct ipu6_isys_stream *stream)
+{
+ struct video_device *vdev = stream->csi2->asd.sd.devnode;
+ struct v4l2_event ev = {
+ .type = V4L2_EVENT_FRAME_SYNC,
+ };
+
+ ev.u.frame_sync.frame_sequence = atomic_inc_return(&stream->sequence);
+
+ v4l2_event_queue(vdev, &ev);
+ dev_dbg(&stream->isys->adev->dev,
+ "sof_event::csi2-%i sequence: %i\n",
+ stream->csi2->port, ev.u.frame_sync.frame_sequence);
+}
+
+void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream)
+{
+ u32 frame_sequence = atomic_read(&stream->sequence);
+
+ dev_dbg(&stream->isys->adev->dev, "eof_event::csi2-%i sequence: %i\n",
+ stream->csi2->port, frame_sequence);
+}
new file mode 100644
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (C) 2013 - 2023 Intel Corporation */
+
+#ifndef IPU6_ISYS_CSI2_H
+#define IPU6_ISYS_CSI2_H
+
+#include <media/media-entity.h>
+#include <media/v4l2-device.h>
+
+#include "ipu6-isys-subdev.h"
+
+struct ipu6_isys;
+struct ipu6_isys_csi2_pdata;
+struct ipu6_isys_csi2_timing;
+struct ipu6_isys_stream;
+
+#define NR_OF_CSI2_SINK_PADS 1
+#define CSI2_PAD_SINK 0
+#define NR_OF_CSI2_SOURCE_PADS 1
+#define CSI2_PAD_SOURCE 1
+#define NR_OF_CSI2_PADS (NR_OF_CSI2_SINK_PADS + NR_OF_CSI2_SOURCE_PADS)
+
+#define CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_A 0
+#define CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_B 0
+#define CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_A 95
+#define CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_B -8
+
+#define CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_A 0
+#define CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_B 0
+#define CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_A 85
+#define CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_B -2
+
+#define IPU6_EOF_TIMEOUT 300
+#define IPU6_EOF_TIMEOUT_JIFFIES msecs_to_jiffies(IPU6_EOF_TIMEOUT)
+
+#define CSI2_CROP_HOR BIT(0)
+#define CSI2_CROP_VER BIT(1)
+#define CSI2_CROP_MASK (CSI2_CROP_VER | CSI2_CROP_HOR)
+
+struct ipu6_isys_csi2 {
+ struct ipu6_isys_csi2_pdata *pdata;
+ struct ipu6_isys *isys;
+ struct ipu6_isys_subdev asd;
+
+ void __iomem *base;
+ u32 receiver_errors;
+ unsigned int nlanes;
+ unsigned int port;
+};
+
+struct ipu6_isys_csi2_timing {
+ u32 ctermen;
+ u32 csettle;
+ u32 dtermen;
+ u32 dsettle;
+};
+
+struct ipu6_csi2_error {
+ const char *error_string;
+ bool is_info_only;
+};
+
+#define to_ipu6_isys_csi2(sd) container_of(to_ipu6_isys_subdev(sd), \
+ struct ipu6_isys_csi2, asd)
+
+int ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2, s64 *link_freq);
+int ipu6_isys_csi2_init(struct ipu6_isys_csi2 *csi2, struct ipu6_isys *isys,
+ void __iomem *base, unsigned int index);
+void ipu6_isys_csi2_cleanup(struct ipu6_isys_csi2 *csi2);
+void ipu6_isys_csi2_sof_event_by_stream(struct ipu6_isys_stream *stream);
+void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream);
+void ipu6_isys_register_errors(struct ipu6_isys_csi2 *csi2);
+void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2);
+
+#endif /* IPU6_ISYS_CSI2_H */
new file mode 100644
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (C) 2013 - 2023 Intel Corporation
+
+#include <linux/media-bus-format.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <media/media-entity.h>
+
+#include "ipu6.h"
+#include "ipu6-bus.h"
+#include "ipu6-isys.h"
+#include "ipu6-isys-subdev.h"
+#include "ipu6-isys-video.h"
+
+unsigned int ipu6_isys_mbus_code_to_bpp(u32 code)
+{
+ switch (code) {
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ return 24;
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ return 16;
+ case MEDIA_BUS_FMT_SBGGR12_1X12:
+ case MEDIA_BUS_FMT_SGBRG12_1X12:
+ case MEDIA_BUS_FMT_SGRBG12_1X12:
+ case MEDIA_BUS_FMT_SRGGB12_1X12:
+ return 12;
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
+ case MEDIA_BUS_FMT_SGBRG10_1X10:
+ case MEDIA_BUS_FMT_SGRBG10_1X10:
+ case MEDIA_BUS_FMT_SRGGB10_1X10:
+ return 10;
+ case MEDIA_BUS_FMT_SBGGR8_1X8:
+ case MEDIA_BUS_FMT_SGBRG8_1X8:
+ case MEDIA_BUS_FMT_SGRBG8_1X8:
+ case MEDIA_BUS_FMT_SRGGB8_1X8:
+ return 8;
+ default:
+ WARN_ON(1);
+ return 8;
+ }
+}
+
+unsigned int ipu6_isys_mbus_code_to_mipi(u32 code)
+{
+ switch (code) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ return MIPI_CSI2_DT_RGB565;
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ return MIPI_CSI2_DT_RGB888;
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ return MIPI_CSI2_DT_YUV422_8B;
+ case MEDIA_BUS_FMT_SBGGR12_1X12:
+ case MEDIA_BUS_FMT_SGBRG12_1X12:
+ case MEDIA_BUS_FMT_SGRBG12_1X12:
+ case MEDIA_BUS_FMT_SRGGB12_1X12:
+ return MIPI_CSI2_DT_RAW12;
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
+ case MEDIA_BUS_FMT_SGBRG10_1X10:
+ case MEDIA_BUS_FMT_SGRBG10_1X10:
+ case MEDIA_BUS_FMT_SRGGB10_1X10:
+ return MIPI_CSI2_DT_RAW10;
+ case MEDIA_BUS_FMT_SBGGR8_1X8:
+ case MEDIA_BUS_FMT_SGBRG8_1X8:
+ case MEDIA_BUS_FMT_SGRBG8_1X8:
+ case MEDIA_BUS_FMT_SRGGB8_1X8:
+ return MIPI_CSI2_DT_RAW8;
+ default:
+ /* return unavailable MIPI data type - 0x3f */
+ WARN_ON(1);
+ return 0x3f;
+ }
+}
+
+bool ipu6_isys_is_bayer_format(u32 code)
+{
+ switch (ipu6_isys_mbus_code_to_mipi(code)) {
+ case MIPI_CSI2_DT_RAW8:
+ case MIPI_CSI2_DT_RAW10:
+ case MIPI_CSI2_DT_RAW12:
+ return true;
+ }
+ return false;
+}
+
+u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y)
+{
+ static const u32 code_map[] = {
+ MEDIA_BUS_FMT_SRGGB8_1X8,
+ MEDIA_BUS_FMT_SGRBG8_1X8,
+ MEDIA_BUS_FMT_SGBRG8_1X8,
+ MEDIA_BUS_FMT_SBGGR8_1X8,
+ MEDIA_BUS_FMT_SRGGB10_1X10,
+ MEDIA_BUS_FMT_SGRBG10_1X10,
+ MEDIA_BUS_FMT_SGBRG10_1X10,
+ MEDIA_BUS_FMT_SBGGR10_1X10,
+ MEDIA_BUS_FMT_SRGGB12_1X12,
+ MEDIA_BUS_FMT_SGRBG12_1X12,
+ MEDIA_BUS_FMT_SGBRG12_1X12,
+ MEDIA_BUS_FMT_SBGGR12_1X12,
+ };
+ u32 i;
+
+ for (i = 0; i < ARRAY_SIZE(code_map); i++)
+ if (code_map[i] == code)
+ break;
+
+ if (i == ARRAY_SIZE(code_map)) {
+ WARN_ON(1);
+ return code;
+ }
+
+ return code_map[i ^ (((y & 1) << 1) | (x & 1))];
+}
+
+struct v4l2_mbus_framefmt *ipu6_isys_get_ffmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ unsigned int pad,
+ unsigned int which)
+{
+ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
+
+ if (which == V4L2_SUBDEV_FORMAT_ACTIVE)
+ return &asd->ffmt[pad];
+ else
+ return v4l2_subdev_get_try_format(sd, state, pad);
+}
+
+struct v4l2_rect *ipu6_isys_get_crop(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ unsigned int pad,
+ unsigned int which)
+{
+ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
+
+ if (which == V4L2_SUBDEV_FORMAT_ACTIVE)
+ return &asd->crop;
+ else
+ return v4l2_subdev_get_try_crop(sd, state, pad);
+}
+
+int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
+ struct v4l2_mbus_framefmt *ffmt =
+ ipu6_isys_get_ffmt(sd, state, fmt->pad, fmt->which);
+ u32 code = asd->supported_codes[0];
+ unsigned int i;
+
+ mutex_lock(&asd->mutex);
+ if ((sd->entity.pads[fmt->pad].flags & MEDIA_PAD_FL_SOURCE) &&
+ sd->entity.num_pads > 1) {
+ fmt->format = *ffmt;
+ mutex_unlock(&asd->mutex);
+ return 0;
+ }
+ fmt->format.width = clamp(fmt->format.width, IPU6_ISYS_MIN_WIDTH,
+ IPU6_ISYS_MAX_WIDTH);
+ fmt->format.height = clamp(fmt->format.height,
+ IPU6_ISYS_MIN_HEIGHT, IPU6_ISYS_MAX_HEIGHT);
+ for (i = 0; asd->supported_codes[i]; i++) {
+ if (asd->supported_codes[i] == fmt->format.code) {
+ code = asd->supported_codes[i];
+ break;
+ }
+ }
+ fmt->format.code = code;
+ fmt->format.field = V4L2_FIELD_NONE;
+ *ffmt = fmt->format;
+ if (sd->entity.pads[fmt->pad].flags & MEDIA_PAD_FL_SINK) {
+ /* propagate format to following source pad */
+ struct v4l2_rect *crop =
+ ipu6_isys_get_crop(sd, state, fmt->pad + 1, fmt->which);
+
+ *ipu6_isys_get_ffmt(sd, state, fmt->pad + 1, fmt->which) =
+ fmt->format;
+ /* reset crop */
+ crop->left = 0;
+ crop->top = 0;
+ crop->width = ffmt->width;
+ crop->height = ffmt->height;
+ }
+ mutex_unlock(&asd->mutex);
+
+ return 0;
+}
+
+int ipu6_isys_subdev_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
+
+ mutex_lock(&asd->mutex);
+ fmt->format = *ipu6_isys_get_ffmt(sd, state, fmt->pad, fmt->which);
+ mutex_unlock(&asd->mutex);
+
+ return 0;
+}
+
+int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
+ const u32 *supported_codes = asd->supported_codes;
+ u32 index;
+
+ for (index = 0; supported_codes[index]; index++) {
+ if (index == code->index) {
+ code->code = supported_codes[index];
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+int ipu6_isys_subdev_init_cfg(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state)
+{
+ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
+ unsigned int i;
+
+ mutex_lock(&asd->mutex);
+
+ for (i = 0; i < asd->sd.entity.num_pads; i++) {
+ struct v4l2_mbus_framefmt *try_fmt =
+ v4l2_subdev_get_try_format(sd, state, i);
+ struct v4l2_rect *try_crop =
+ v4l2_subdev_get_try_crop(sd, state, i);
+
+ *try_fmt = asd->ffmt[i];
+ *try_crop = asd->crop;
+ }
+
+ mutex_unlock(&asd->mutex);
+
+ return 0;
+}
+
+int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd,
+ const struct v4l2_subdev_ops *ops,
+ unsigned int nr_ctrls,
+ unsigned int num_pads)
+{
+ int ret;
+
+ v4l2_subdev_init(&asd->sd, ops);
+
+ asd->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
+ asd->sd.owner = THIS_MODULE;
+ asd->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+
+ asd->pad = devm_kcalloc(&asd->isys->adev->dev, num_pads,
+ sizeof(*asd->pad), GFP_KERNEL);
+
+ asd->ffmt = devm_kcalloc(&asd->isys->adev->dev, num_pads,
+ sizeof(*asd->ffmt), GFP_KERNEL);
+
+ if (!asd->pad || !asd->ffmt)
+ return -ENOMEM;
+
+ mutex_init(&asd->mutex);
+
+ ret = media_entity_pads_init(&asd->sd.entity, num_pads, asd->pad);
+ if (ret)
+ goto out_mutex_destroy;
+
+ if (asd->ctrl_init) {
+ ret = v4l2_ctrl_handler_init(&asd->ctrl_handler, nr_ctrls);
+ if (ret)
+ goto out_media_entity_cleanup;
+
+ asd->ctrl_init(&asd->sd);
+ if (asd->ctrl_handler.error) {
+ ret = asd->ctrl_handler.error;
+ goto out_v4l2_ctrl_handler_free;
+ }
+
+ asd->sd.ctrl_handler = &asd->ctrl_handler;
+ }
+
+ asd->source = -1;
+
+ return 0;
+
+out_v4l2_ctrl_handler_free:
+ v4l2_ctrl_handler_free(&asd->ctrl_handler);
+
+out_media_entity_cleanup:
+ media_entity_cleanup(&asd->sd.entity);
+
+out_mutex_destroy:
+ mutex_destroy(&asd->mutex);
+
+ return ret;
+}
+
+void ipu6_isys_subdev_cleanup(struct ipu6_isys_subdev *asd)
+{
+ media_entity_cleanup(&asd->sd.entity);
+ v4l2_ctrl_handler_free(&asd->ctrl_handler);
+ mutex_destroy(&asd->mutex);
+}
new file mode 100644
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (C) 2013 - 2023 Intel Corporation */
+
+#ifndef IPU6_ISYS_SUBDEV_H
+#define IPU6_ISYS_SUBDEV_H
+
+#include <linux/mutex.h>
+
+#include <media/media-entity.h>
+#include <media/mipi-csi2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+
+#define FMT_ENTRY (struct ipu6_isys_fmt_entry [])
+
+struct ipu6_isys;
+
+struct ipu6_isys_subdev {
+ /* Serialise access to any other field in the struct */
+ struct mutex mutex;
+ struct v4l2_subdev sd;
+ struct ipu6_isys *isys;
+ u32 const *supported_codes;
+ struct media_pad *pad;
+ struct v4l2_mbus_framefmt *ffmt;
+ struct v4l2_rect crop;
+ struct v4l2_ctrl_handler ctrl_handler;
+ void (*ctrl_init)(struct v4l2_subdev *sd);
+ int source; /* SSI stream source; -1 if unset */
+};
+
+#define to_ipu6_isys_subdev(__sd) \
+ container_of(__sd, struct ipu6_isys_subdev, sd)
+
+unsigned int ipu6_isys_mbus_code_to_bpp(u32 code);
+unsigned int ipu6_isys_mbus_code_to_mipi(u32 code);
+bool ipu6_isys_is_bayer_format(u32 code);
+u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y);
+
+int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_format *fmt);
+int ipu6_isys_subdev_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_format *fmt);
+int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_mbus_code_enum
+ *code);
+int ipu6_isys_subdev_link_validate(struct v4l2_subdev *sd,
+ struct media_link *link,
+ struct v4l2_subdev_format *source_fmt,
+ struct v4l2_subdev_format *sink_fmt);
+struct v4l2_mbus_framefmt *ipu6_isys_get_ffmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ unsigned int pad,
+ unsigned int which);
+struct v4l2_rect *ipu6_isys_get_crop(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ unsigned int pad,
+ unsigned int which);
+
+int ipu6_isys_subdev_init_cfg(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state);
+int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd,
+ const struct v4l2_subdev_ops *ops,
+ unsigned int nr_ctrls,
+ unsigned int num_pads);
+void ipu6_isys_subdev_cleanup(struct ipu6_isys_subdev *asd);
+#endif /* IPU6_ISYS_SUBDEV_H */
new file mode 100644
@@ -0,0 +1,187 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (C) 2023 Intel Corporation */
+
+#ifndef IPU6_PLATFORM_ISYS_CSI2_REG_H
+#define IPU6_PLATFORM_ISYS_CSI2_REG_H
+
+#define CSI_REG_BASE 0x220000
+#define CSI_REG_BASE_PORT(id) ((id) * 0x1000)
+
+#define IPU6_CSI_PORT_A_ADDR_OFFSET \
+ (CSI_REG_BASE + CSI_REG_BASE_PORT(0))
+#define IPU6_CSI_PORT_B_ADDR_OFFSET \
+ (CSI_REG_BASE + CSI_REG_BASE_PORT(1))
+#define IPU6_CSI_PORT_C_ADDR_OFFSET \
+ (CSI_REG_BASE + CSI_REG_BASE_PORT(2))
+#define IPU6_CSI_PORT_D_ADDR_OFFSET \
+ (CSI_REG_BASE + CSI_REG_BASE_PORT(3))
+#define IPU6_CSI_PORT_E_ADDR_OFFSET \
+ (CSI_REG_BASE + CSI_REG_BASE_PORT(4))
+#define IPU6_CSI_PORT_F_ADDR_OFFSET \
+ (CSI_REG_BASE + CSI_REG_BASE_PORT(5))
+#define IPU6_CSI_PORT_G_ADDR_OFFSET \
+ (CSI_REG_BASE + CSI_REG_BASE_PORT(6))
+#define IPU6_CSI_PORT_H_ADDR_OFFSET \
+ (CSI_REG_BASE + CSI_REG_BASE_PORT(7))
+
+/* CSI Port Genral Purpose Registers */
+#define CSI_REG_PORT_GPREG_SRST 0x0
+#define CSI_REG_PORT_GPREG_CSI2_SLV_REG_SRST 0x4
+#define CSI_REG_PORT_GPREG_CSI2_PORT_CONTROL 0x8
+
+/*
+ * Port IRQs mapping events:
+ * IRQ0 - CSI_FE event
+ * IRQ1 - CSI_SYNC
+ * IRQ2 - S2M_SIDS0TO7
+ * IRQ3 - S2M_SIDS8TO15
+ */
+#define CSI_PORT_REG_BASE_IRQ_CSI 0x80
+#define CSI_PORT_REG_BASE_IRQ_CSI_SYNC 0xA0
+#define CSI_PORT_REG_BASE_IRQ_S2M_SIDS0TOS7 0xC0
+#define CSI_PORT_REG_BASE_IRQ_S2M_SIDS8TOS15 0xE0
+
+#define CSI_PORT_REG_BASE_IRQ_EDGE_OFFSET 0x0
+#define CSI_PORT_REG_BASE_IRQ_MASK_OFFSET 0x4
+#define CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET 0x8
+#define CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET 0xc
+#define CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET 0x10
+#define CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET 0x14
+
+#define IPU6SE_CSI_RX_ERROR_IRQ_MASK GENMASK(18, 0)
+#define IPU6_CSI_RX_ERROR_IRQ_MASK GENMASK(19, 0)
+
+#define CSI_RX_NUM_ERRORS_IN_IRQ 20
+#define CSI_RX_NUM_IRQ 32
+
+#define IPU6_CSI_RX_IRQ_FS_VC 1
+#define IPU6_CSI_RX_IRQ_FE_VC 2
+
+/* PPI2CSI */
+#define CSI_REG_PPI2CSI_ENABLE 0x200
+#define CSI_REG_PPI2CSI_CONFIG_PPI_INTF 0x204
+#define PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK GENMASK(4, 3)
+#define CSI_REG_PPI2CSI_CONFIG_CSI_FEATURE 0x208
+
+enum CSI_PPI2CSI_CTRL {
+ CSI_PPI2CSI_DISABLE = 0,
+ CSI_PPI2CSI_ENABLE = 1,
+};
+
+/* CSI_FE */
+#define CSI_REG_CSI_FE_ENABLE 0x280
+#define CSI_REG_CSI_FE_MODE 0x284
+#define CSI_REG_CSI_FE_MUX_CTRL 0x288
+#define CSI_REG_CSI_FE_SYNC_CNTR_SEL 0x290
+
+enum CSI_FE_ENABLE_TYPE {
+ CSI_FE_DISABLE = 0,
+ CSI_FE_ENABLE = 1,
+};
+
+enum CSI_FE_MODE_TYPE {
+ CSI_FE_DPHY_MODE = 0,
+ CSI_FE_CPHY_MODE = 1,
+};
+
+enum CSI_FE_INPUT_SELECTOR {
+ CSI_SENSOR_INPUT = 0,
+ CSI_MIPIGEN_INPUT = 1,
+};
+
+enum CSI_FE_SYNC_CNTR_SEL_TYPE {
+ CSI_CNTR_SENSOR_LINE_ID = BIT(0),
+ CSI_CNTR_INT_LINE_PKT_ID = ~CSI_CNTR_SENSOR_LINE_ID,
+ CSI_CNTR_SENSOR_FRAME_ID = BIT(1),
+ CSI_CNTR_INT_FRAME_PKT_ID = ~CSI_CNTR_SENSOR_FRAME_ID,
+};
+
+/* CSI HUB General Purpose Registers */
+#define CSI_REG_HUB_GPREG_SRST (CSI_REG_BASE + 0x18000)
+#define CSI_REG_HUB_GPREG_SLV_REG_SRST (CSI_REG_BASE + 0x18004)
+
+#define CSI_REG_HUB_DRV_ACCESS_PORT(id) (CSI_REG_BASE + 0x18018 + (id) * 4)
+#define CSI_REG_HUB_FW_ACCESS_PORT_OFS 0x17000
+#define CSI_REG_HUB_FW_ACCESS_PORT_V6OFS 0x16000
+#define CSI_REG_HUB_FW_ACCESS_PORT(ofs, id) (CSI_REG_BASE + (ofs) + \
+ (id) * 4)
+
+enum CSI_PORT_CLK_GATING_SWITCH {
+ CSI_PORT_CLK_GATING_OFF = 0,
+ CSI_PORT_CLK_GATING_ON = 1,
+};
+
+#define CSI_REG_BASE_HUB_IRQ 0x18200
+
+#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE 0x238200
+#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK 0x238204
+#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS 0x238208
+#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR 0x23820c
+#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE 0x238210
+#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE 0x238214
+
+#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_EDGE 0x238220
+#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_MASK 0x238224
+#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_STATUS 0x238228
+#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_CLEAR 0x23822c
+#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_ENABLE 0x238230
+#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_LEVEL_NOT_PULSE 0x238234
+
+/* MTL IPU6V6 irq ctrl0 & ctrl1 */
+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE 0x238700
+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK 0x238704
+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS 0x238708
+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR 0x23870c
+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE 0x238710
+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE 0x238714
+
+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_EDGE 0x238720
+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_MASK 0x238724
+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_STATUS 0x238728
+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_CLEAR 0x23872c
+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_ENABLE 0x238730
+#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_LEVEL_NOT_PULSE 0x238734
+
+/*
+ * 3:0 CSI_PORT.irq_out[3:0] CSI_PORT_CTRL0 IRQ outputs (4bits)
+ * [0] CSI_PORT.IRQ_CTRL0_csi
+ * [1] CSI_PORT.IRQ_CTRL1_csi_sync
+ * [2] CSI_PORT.IRQ_CTRL2_s2m_sids0to7
+ * [3] CSI_PORT.IRQ_CTRL3_s2m_sids8to15
+ */
+#define IPU6_ISYS_UNISPART_IRQ_CSI2(port) \
+ (0x3 << ((port) * IPU6_CSI_IRQ_NUM_PER_PIPE))
+
+/*
+ * ipu6se support 2 front ends, 2 port per front end, 4 ports 0..3
+ * sip0 - 0, 1
+ * sip1 - 2, 3
+ * 0 and 2 support 4 data lanes, 1 and 3 support 2 data lanes
+ * all offset are base from isys base address
+ */
+
+#define CSI2_HUB_GPREG_SIP_SRST(sip) (0x238038 + (sip) * 4)
+#define CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip) (0x238050 + (sip) * 4)
+
+#define CSI2_HUB_GPREG_DPHY_TIMER_INCR (0x238040)
+#define CSI2_HUB_GPREG_HPLL_FREQ (0x238044)
+#define CSI2_HUB_GPREG_IS_CLK_RATIO (0x238048)
+#define CSI2_HUB_GPREG_HPLL_FREQ_ISCLK_RATE_OVERRIDE (0x23804c)
+#define CSI2_HUB_GPREG_PORT_CLKGATING_DISABLE (0x238058)
+#define CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL (0x23805c)
+#define CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL (0x238088)
+#define CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL (0x2380a4)
+#define CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL (0x2380d0)
+
+#define CSI2_SIP_TOP_CSI_RX_BASE(sip) (0x23805c + (sip) * 0x48)
+#define CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port) (0x23805c + ((port) / 2) * 0x48)
+#define CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port) (0x238088 + ((port) / 2) * 0x48)
+
+/* offset from port base */
+#define CSI2_SIP_TOP_CSI_RX_PORT_CONTROL (0x0)
+#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE (0x4)
+#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE (0x8)
+#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(lane) (0xc + (lane) * 8)
+#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(lane) (0x10 + (lane) * 8)
+
+#endif /* IPU6_ISYS_CSI2_REG_H */