This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

AM62A7: IMX678 CSI streaming errors

Part Number: AM62A7

Hi, 

I am trying to add a new driver to the AM62A Edgi AI build. |
I have created a custom Yocto meta to build a driver and DTBO, this builds and runs. 

I have been working through this to generate a raw so  I can tune the ISP
www.ti.com/.../sprad86a.pdf

If I run the command to generate a Raw file It doesn't allocate a buffer. 

gst-launch-1.0 -v v4l2src num-buffers=5 device=/dev/video3 io-mode=dmabuf ! video/x-bayer, width=3840, height=2160, framerate=30/1, format=rggb ! multifilesink location="imx678-image-%d.raw"
Setting pipeline to PAUSED ...
Pipeline is live and does not need PREROLL ...
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock
/GstPipeline:pipeline0/GstV4l2Src:v4l2src0.GstPad:src: caps = video/x-bayer, width=(int)3840, height=(int)2160, framerate=(fraction)30/1, format=(string)rggb, interlace-mode=(string)progressive
/GstPipeline:pipeline0/GstCapsFilter:capsfilter0.GstPad:src: caps = video/x-bayer, width=(int)3840, height=(int)2160, framerate=(fraction)30/1, format=(string)rggb, interlace-mode=(string)progressive
/GstPipeline:pipeline0/GstMultiFileSink:multifilesink0.GstPad:sink: caps = video/x-bayer, width=(int)3840, height=(int)2160, framerate=(fraction)30/1, format=(string)rggb, interlace-mode=(string)progressive
/GstPipeline:pipeline0/GstCapsFilter:capsfilter0.GstPad:sink: caps = video/x-bayer, width=(int)3840, height=(int)2160, framerate=(fraction)30/1, format=(string)rggb, interlace-mode=(string)progressive
ERROR: from element /GstPipeline:pipeline0/GstV4l2Src:v4l2src0: Failed to allocate required memory.
Additional debug info:
/usr/src/debug/gstreamer1.0-plugins-good/1.22.12/sys/v4l2/gstv4l2src.c(950): gst_v4l2src_decide_allocation (): /GstPipeline:pipeline0/GstV4l2Src:v4l2src0:
Buffer pool activation failed
Execution ended after 0:00:00.014909805
Setting pipeline to NULL ...
ERROR: from element /GstPipeline:pipeline0/GstV4l2Src:v4l2src0: Internal data stream error.
Additional debug info:
/usr/src/debug/gstreamer1.0/1.22.12/libs/gst/base/gstbasesrc.c(3134): gst_base_src_loop (): /GstPipeline:pipeline0/GstV4l2Src:v4l2src0:
streaming stopped, reason not-negotiated (-4)
Freeing pipeline ...

I don't get a log from the driver.

I can run

ssh root@am62axx-evm "dmesg -C && v4l2-ctl -d /dev/video3 --set-fmt-video=width=3840,height=2160,pixelformat=RG12 --stream-mmap --stream-count=5 --stream-to=/tmp/test.raw 2>&1 &"

But it generates a 0 byte file. 

I can see that it is trying to generate frames, but none are recieved. 
 ssh root@am62axx-evm "cat /proc/interrupts | grep csi-bridge"
558:          0          0          0          0     GICv3 175 Level     30101000.csi-bridge

With this log

[   37.065937] imx678 4-001a: IMX678_s_stream called: enable=1
[   37.071545] imx678 4-001a: Stream enable requested
[   37.681249] imx678 4-001a: IMX678_stream_on: starting (mode=0)
[   37.716086] imx678 4-001a: Sensor wakeup complete
[   37.720942] imx678 4-001a: Writing cached VMAX=0x8ca, HMAX=0xa50
[   37.727691] imx678 4-001a: Readback VMAX=0x8ca, HMAX=0xa50 (expected: 0x8ca, 0xa50)
[   37.735369] imx678 4-001a: Starting IMX678 stream (mode=0, VMAX=0x8ca, HMAX=0xa50)
[   37.743475] imx678 4-001a: MIPI Config: LANEMODE=0x1, DATARATE_SEL=0x6, INCK_SEL=0x4
[   37.751249] imx678 4-001a: Expected: 2 CSI lanes, 720000000 bps lane rate
[   37.758181] imx678 4-001a: Digital clamp disabled
[   37.816076] imx678 4-001a: IMX678 streaming started successfully
[   37.822091] imx678 4-001a: IMX678: Started streaming
[   37.827055] imx678 4-001a: IMX678_s_stream completed: ret=0
[   42.300287] imx678 4-001a: IMX678_s_stream called: enable=0
[   42.305884] imx678 4-001a: Stream disable requested
[   42.310768] imx678 4-001a: Stopping IMX678 stream
[   42.348206] imx678 4-001a: IMX678_s_stream completed: ret=0

 

#!/bin/bash
# IMX678 No Frames Debug Script
# Diagnoses why sensor streams but CSI receiver gets no data

echo "=== IMX678 Frame Capture Debug ==="
echo

echo "1. Check CSI interrupt baseline (should be 0 before streaming):"
ssh root@am62axx-evm "cat /proc/interrupts | grep csi-bridge"
echo

echo "2. Configure media pipeline:"
ssh root@am62axx-evm "
  media-ctl -d /dev/media0 -V '\"imx678 4-001a\":0 [fmt:SRGGB12_1X12/3840x2160]' && \
  media-ctl -d /dev/media0 -V '\"cdns_csi2rx.30101000.csi-bridge\":0 [fmt:SRGGB12_1X12/3840x2160]' && \
  media-ctl -d /dev/media0 -V '\"cdns_csi2rx.30101000.csi-bridge\":1 [fmt:SRGGB12_1X12/3840x2160]' && \
  media-ctl -d /dev/media0 -V '\"30102000.ticsi2rx\":0 [fmt:SRGGB12_1X12/3840x2160]'
"
echo

echo "3. Clear kernel log and start streaming (5 second timeout):"
ssh root@am62axx-evm "dmesg -C"
ssh root@am62axx-evm "timeout 5 v4l2-ctl -d /dev/video3 --set-fmt-video=width=3840,height=2160,pixelformat=RG12 --stream-mmap --stream-count=1 2>&1 &"
sleep 6
echo

echo "4. Check if CSI interrupts incremented (should be >0 if receiving data):"
ssh root@am62axx-evm "cat /proc/interrupts | grep csi-bridge"
echo

echo "5. Sensor streaming logs:"
ssh root@am62axx-evm "dmesg | grep 'imx678\|IMX678'"
echo

echo "6. Check for CSI/DMA errors:"
ssh root@am62axx-evm "dmesg | grep -i -E 'csi|dma|buffer|broken pipe|error'"
echo

echo "7. Kill any hung processes:"
ssh root@am62axx-evm "killall -9 v4l2-ctl 2>/dev/null"
echo

echo "=== Analysis ==="
echo "If CSI interrupt count is still 0:"
echo "  - Sensor transmitting but CSI bridge not receiving"
echo "  - Check: Lane configuration, D-PHY clock, MIPI timing"
echo
echo "If CSI interrupts increment but no frames:"
echo "  - CSI receiving but DMA not working"
echo "  - Check: Buffer allocation, context routing"
/dts-v1/;
/plugin/;

/ {
    compatible = "ti,am62a7-sk";

    fragment@0 {
        target-path = "/";
        __overlay__ {
            /* Always-on 3.3V rail for analog */
            reg_cam_3v3: regulator-cam-3v3 {
                compatible = "regulator-fixed";
                regulator-name = "cam-3v3";
                regulator-min-microvolt = <3300000>;
                regulator-max-microvolt = <3300000>;
                gpio = <&exp1 5 0>;
                enable-active-high;
                regulator-boot-on;
                regulator-always-on;
                startup-delay-us = <3000000>;
            };

            /* 1.8V rail enabled via CSI_GPIO0 on expander 0x22, line 13 */
            reg_cam_1v8: regulator-cam-1v8 {
                compatible = "regulator-fixed";
                regulator-name = "cam-1v8";
                regulator-min-microvolt = <1800000>;
                regulator-max-microvolt = <1800000>;
                gpio = <&exp1 13 0>;
                enable-active-high;
                regulator-boot-on;
                regulator-always-on;
                startup-delay-us = <3000000>;
            };

            imx678_inck: imx678-inck {
                compatible = "fixed-clock";
                #clock-cells = <0>;
                clock-frequency = <24000000>;
                clock-output-names = "imx678_inck";
            };
        };
    };

    fragment@1 {
        target = <&exp1>;
        __overlay__ {
            /delete-node/ imx678-pwdn-hog;
            /delete-node/ imx678-3v3-hog;
            /delete-node/ imx678-5v0-hog;
        };
    };

    fragment@2 {
        target = <&main_i2c2>;
        __overlay__ {
            status = "okay";
            #address-cells = <1>;
            #size-cells = <0>;

            i2c-mux@71 {
                compatible = "nxp,pca9543";
                reg = <0x71>;
                #address-cells = <1>;
                #size-cells = <0>;

                i2c@1 {
                    reg = <1>;
                    #address-cells = <1>;
                    #size-cells = <0>;

                    camera@1a {
                        compatible = "sony,imx678";
                        reg = <0x1a>;

                        /* No separate reset/pwdn control on this board (Xshutdown via RC) */

                        dvdd-supply = <&reg_cam_1v8>;
                        ovdd-supply = <&reg_cam_1v8>;
                        avdd-supply = <&reg_cam_3v3>;

                        clocks = <&imx678_inck>;
                        clock-names = "inck";

                        port {
                            csi2_cam0: endpoint {
                                remote-endpoint = <&csi2rx0_in_sensor>;
                                bus-type = <4>; /* CSI-2 DPHY */
                                clock-lanes = <0>;
                                data-lanes = <1 2>;
                                link-frequencies = /bits/ 64 <360000000>;
                            };
                        };
                    };
                };
            };
        };
    };

    fragment@3 {
        target = <&cdns_csi2rx0>;
        __overlay__ {
            status = "okay";

            ports {
                #address-cells = <1>;
                #size-cells = <0>;

                port@0 {
                    reg = <0>;

                    csi2rx0_in_sensor: endpoint {
                        remote-endpoint = <&csi2_cam0>;
                        bus-type = <4>;
                        clock-lanes = <0>;
                        data-lanes = <1 2>;
                    };
                };
            };
        };
    };

    fragment@4 {
        target = <&ti_csi2rx0>;
        __overlay__ {
            status = "okay";
        };
    };

    fragment@5 {
        target = <&dphy0>;
        __overlay__ {
            status = "okay";
        };
    };
};
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Driver for the Sony IMX678 CMOS Image Sensor.
 *
 * Copyright (C) 2023 WolfVision GmbH.
 */

#include <linux/clk.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/videodev2.h>
#include <media/v4l2-cci.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-subdev.h>

#define IMX678_PIXEL_ARRAY_TOP 8
#define IMX678_PIXEL_ARRAY_LEFT 8
#define IMX678_PIXEL_ARRAY_WIDTH 3840
#define IMX678_PIXEL_ARRAY_HEIGHT 2160
#define IMX678_PIXEL_ARRAY_VBLANK 58

#define IMX678_NUM_CLK_PARAM_REGS 11

#define IMX678_REG_MODE_SELECT CCI_REG8(0x3000)
#define IMX678_MODE_STANDBY 0x01
#define IMX678_MODE_STREAMING 0x00
#define IMX678_REGHOLD CCI_REG8(0x3001)
#define IMX678_REGHOLD_INVALID (0)
#define IMX678_REGHOLD_VALID BIT(0)
#define IMX678_XMSTA CCI_REG8(0x3002)
#define IMX678_XMSTA_START (0)
#define IMX678_XMSTA_STOP BIT(0)
#define IMX678_BCWAIT_TIME CCI_REG16_LE(0x3008)
#define IMX678_CPWAIT_TIME CCI_REG16_LE(0x300a)
#define IMX678_WINMODE CCI_REG8(0x3018)
#define IMX678_ADDMODE CCI_REG8(0x301b)
#define IMX678_HREVERSE CCI_REG8(0x3020)
#define IMX678_VREVERSE CCI_REG8(0x3021)
#define IMX678_ADBIT CCI_REG8(0x3022)
#define IMX678_MDBIT CCI_REG8(0x3023)
#define IMX678_SYS_MODE CCI_REG8(0x3033)
#define IMX678_OUTSEL CCI_REG8(0x30a4)
#define IMX678_DRV CCI_REG8(0x30a6)
#define IMX678_EXTMODE CCI_REG8(0x30ce)
#define IMX678_VMAX CCI_REG24_LE(0x3028)
#define IMX678_HMAX CCI_REG16_LE(0x302C)
#define IMX678_SHR0 CCI_REG24_LE(0x3050)
#define IMX678_GAIN_PCG_0 CCI_REG16_LE(0x3070)
#define IMX678_AGAIN_MIN 0
#define IMX678_AGAIN_MAX 100
#define IMX678_AGAIN_STEP 1
#define IMX678_BLKLEVEL CCI_REG16_LE(0x30dc)
#define IMX678_BLKLEVEL_DEFAULT 50
#define IMX678_TPG_EN_DUOUT CCI_REG8(0x30e0)
#define IMX678_TPG_PATSEL_DUOUT CCI_REG8(0x30e2)
#define IMX678_TPG_COLORWIDTH CCI_REG8(0x30e4)
#define IMX678_TESTCLKEN_MIPI CCI_REG8(0x3110)
#define IMX678_INCKSEL1 CCI_REG8(0x3115)
#define IMX678_INCKSEL2 CCI_REG8(0x3116)
#define IMX678_INCKSEL3 CCI_REG16_LE(0x3118)
#define IMX678_INCKSEL4 CCI_REG16_LE(0x311a)
#define IMX678_INCKSEL5 CCI_REG8(0x311e)
#define IMX678_DIG_CLP_MODE CCI_REG8(0x32c8)
#define IMX678_WRJ_OPEN CCI_REG8(0x3390)
#define IMX678_SENSOR_INFO CCI_REG16_LE(0x3f12)
#define IMX678_SENSOR_INFO_MASK 0xfff
#define IMX678_CHIP_ID 0x514
#define IMX678_LANEMODE CCI_REG8(0x3040)
#define IMX678_LANEMODE_2 1
#define IMX678_LANEMODE_4 3
#define IMX678_INCK_SEL CCI_REG8(0x3014)
#define IMX678_DATARATE_SEL CCI_REG8(0x3015)
#define IMX678_TXCLKESC_FREQ CCI_REG16_LE(0x4004)
#define IMX678_INCKSEL6 CCI_REG8(0x400c)
#define IMX678_TCLKPOST CCI_REG16_LE(0x4018)
#define IMX678_TCLKPREPARE CCI_REG16_LE(0x401a)
#define IMX678_TCLKTRAIL CCI_REG16_LE(0x401c)
#define IMX678_TCLKZERO CCI_REG16_LE(0x401e)
#define IMX678_THSPREPARE CCI_REG16_LE(0x4020)
#define IMX678_THSZERO CCI_REG16_LE(0x4022)
#define IMX678_THSTRAIL CCI_REG16_LE(0x4024)
#define IMX678_THSEXIT CCI_REG16_LE(0x4026)
#define IMX678_TLPX CCI_REG16_LE(0x4028)
#define IMX678_INCKSEL7 CCI_REG8(0x4074)
#define IMX678_DIGITAL_CLAMP CCI_REG8(0x3458)

static const char *const IMX678_supply_names[] = {
    "dvdd",
    "ovdd",
    "avdd",
};

/*
 * The IMX678 data sheet uses lane rates but v4l2 uses link frequency to
 * describe MIPI CSI-2 speed. This driver uses lane rates wherever possible
 * and converts them to link frequencies by a factor of two when needed.
 */
static const s64 link_freq_menu_items[] = {
    594000000 / 2, 720000000 / 2, 891000000 / 2, 1440000000 / 2, 1485000000 / 2,
};

struct IMX678_clk_params {
  u64 lane_rate;
  u64 inck;
  struct cci_reg_sequence regs[IMX678_NUM_CLK_PARAM_REGS];
};

/* INCK Settings - includes all lane rate and INCK dependent registers */
static const struct IMX678_clk_params IMX678_clk_params[] = {
    {
        .lane_rate = 594000000UL,
        .inck = 27000000,
        .regs[0] = {IMX678_BCWAIT_TIME, 0x05D},
        .regs[1] = {IMX678_CPWAIT_TIME, 0x042},
        .regs[2] = {IMX678_SYS_MODE, 0x7},
        .regs[3] = {IMX678_INCKSEL1, 0x00},
        .regs[4] = {IMX678_INCKSEL2, 0x23},
        .regs[5] = {IMX678_INCKSEL3, 0x084},
        .regs[6] = {IMX678_INCKSEL4, 0x0E7},
        .regs[7] = {IMX678_INCKSEL5, 0x23},
        .regs[8] = {IMX678_INCKSEL6, 0x0},
        .regs[9] = {IMX678_INCKSEL7, 0x1},
        .regs[10] = {IMX678_TXCLKESC_FREQ, 0x06C0},
    },
    {
        .lane_rate = 720000000UL,
        .inck = 24000000,
        .regs[0] = {IMX678_BCWAIT_TIME, 0x054},
        .regs[1] = {IMX678_CPWAIT_TIME, 0x03B},
        .regs[2] = {IMX678_SYS_MODE, 0x9},
        .regs[3] = {IMX678_INCKSEL1, 0x00},
        .regs[4] = {IMX678_INCKSEL2, 0x23},
        .regs[5] = {IMX678_INCKSEL3, 0x0B4},
        .regs[6] = {IMX678_INCKSEL4, 0x0FC},
        .regs[7] = {IMX678_INCKSEL5, 0x23},
        .regs[8] = {IMX678_INCKSEL6, 0x0},
        .regs[9] = {IMX678_INCKSEL7, 0x1},
        .regs[10] = {IMX678_TXCLKESC_FREQ, 0x0600},
    },
    {
        .lane_rate = 1440000000UL,
        .inck = 24000000,
        .regs[0] = {IMX678_BCWAIT_TIME, 0x054},
        .regs[1] = {IMX678_CPWAIT_TIME, 0x03B},
        .regs[2] = {IMX678_SYS_MODE, 0x8},
        .regs[3] = {IMX678_INCKSEL1, 0x00},
        .regs[4] = {IMX678_INCKSEL2, 0x23},
        .regs[5] = {IMX678_INCKSEL3, 0x0B4},
        .regs[6] = {IMX678_INCKSEL4, 0x0FC},
        .regs[7] = {IMX678_INCKSEL5, 0x23},
        .regs[8] = {IMX678_INCKSEL6, 0x1},
        .regs[9] = {IMX678_INCKSEL7, 0x0},
        .regs[10] = {IMX678_TXCLKESC_FREQ, 0x0600},
    },
};

/* all-pixel 2-lane 720 Mbps mode */
static const struct cci_reg_sequence IMX678_mode_2_720[] = {
  {IMX678_VMAX, 0x08CA},     {IMX678_HMAX, 0x0A50},        {IMX678_LANEMODE, IMX678_LANEMODE_2},
    {IMX678_TCLKPOST, 0x006F}, {IMX678_TCLKPREPARE, 0x002F}, {IMX678_TCLKTRAIL, 0x002F},
    {IMX678_TCLKZERO, 0x00BF}, {IMX678_THSPREPARE, 0x002F},  {IMX678_THSZERO, 0x0057},
    {IMX678_THSTRAIL, 0x002F}, {IMX678_THSEXIT, 0x004F},     {IMX678_TLPX, 0x0027},
};

/* all-pixel 2-lane 1440 Mbps mode */
static const struct cci_reg_sequence IMX678_mode_2_1440[] = {
  {IMX678_VMAX, 0x08CA},     {IMX678_HMAX, 0x0528},        {IMX678_LANEMODE, IMX678_LANEMODE_2},
    {IMX678_TCLKPOST, 0x009F}, {IMX678_TCLKPREPARE, 0x0057}, {IMX678_TCLKTRAIL, 0x0057},
    {IMX678_TCLKZERO, 0x0187}, {IMX678_THSPREPARE, 0x005F},  {IMX678_THSZERO, 0x00A7},
    {IMX678_THSTRAIL, 0x005F}, {IMX678_THSEXIT, 0x0097},     {IMX678_TLPX, 0x004F},
};

/* all-pixel 4-lane 891 Mbps mode */
static const struct cci_reg_sequence IMX678_mode_4_891[] = {
  {IMX678_VMAX, 0x08CA},     {IMX678_HMAX, 0x0226},        {IMX678_LANEMODE, IMX678_LANEMODE_4},
    {IMX678_TCLKPOST, 0x007F}, {IMX678_TCLKPREPARE, 0x0037}, {IMX678_TCLKTRAIL, 0x0037},
    {IMX678_TCLKZERO, 0x00F7}, {IMX678_THSPREPARE, 0x003F},  {IMX678_THSZERO, 0x006F},
    {IMX678_THSTRAIL, 0x003F}, {IMX678_THSEXIT, 0x005F},     {IMX678_TLPX, 0x002F},
};

struct IMX678_mode_reg_list {
  u32 num_of_regs;
  const struct cci_reg_sequence *regs;
};

struct IMX678_mode {
  u64 lane_rate;
  u32 lanes;
  u32 hmax_pix;
  u64 pixel_rate;
  struct IMX678_mode_reg_list reg_list;
};

/* mode configs */
static const struct IMX678_mode supported_modes[] = {
    {
        .lane_rate = 720000000,
        .lanes = 2,
    .hmax_pix = 2640,
        .pixel_rate = 144000000,
        .reg_list =
            {
                .num_of_regs = ARRAY_SIZE(IMX678_mode_2_720),
                .regs = IMX678_mode_2_720,
            },
    },
    {
        .lane_rate = 1440000000,
        .lanes = 2,
      .hmax_pix = 1320,
        .pixel_rate = 304615385,
        .reg_list =
            {
                .num_of_regs = ARRAY_SIZE(IMX678_mode_2_1440),
                .regs = IMX678_mode_2_1440,
            },
    },
    {
        .lane_rate = 891000000,
        .lanes = 4,
      .hmax_pix = 550,
        .pixel_rate = 297000000,
        .reg_list =
            {
                .num_of_regs = ARRAY_SIZE(IMX678_mode_4_891),
                .regs = IMX678_mode_4_891,
            },
    },
};

static const char *const IMX678_test_pattern_menu[] = {
    "disabled",
    "solid black",
    "solid white",
    "solid dark gray",
    "solid light gray",
    "stripes light/dark grey",
    "stripes dark/light grey",
    "stripes black/dark grey",
    "stripes dark grey/black",
    "stripes black/white",
    "stripes white/black",
    "horizontal color bar",
    "vertical color bar",
};

struct IMX678 {
  struct device *dev;
  struct clk *clk;
  struct regulator_bulk_data supplies[ARRAY_SIZE(IMX678_supply_names)];
  struct gpio_desc *reset;
  struct regmap *regmap;

  const struct IMX678_clk_params *clk_params;
  u8 inck_sel;
  u8 datarate_sel;

  struct v4l2_subdev subdev;
  struct media_pad pad;

  struct v4l2_ctrl_handler ctrls;
  struct v4l2_ctrl *vblank;
  struct v4l2_ctrl *hflip;
  struct v4l2_ctrl *vflip;

  unsigned int cur_mode;
  unsigned int num_data_lanes;

  /* Cached timing registers to re-apply at stream start */
  u64 vmax_cached;
  u64 hmax_cached;

  /* Common registers written once after power-up */
  bool common_regs_written;
};

/*
 * This table includes fixed register settings and a bunch of undocumented
 * registers that have to be set to another value than default.
 */
static const struct cci_reg_sequence IMX678_common_regs[] = {
  {CCI_REG8(0x301C), 0x00}, {CCI_REG8(0x301E), 0x01}, {CCI_REG8(0x306B), 0x00},
  {CCI_REG8(0x3400), 0x01}, {CCI_REG8(0x3460), 0x22}, {CCI_REG8(0x355A), 0x64},
  {CCI_REG8(0x3A02), 0x7A}, {CCI_REG8(0x3A10), 0xEC}, {CCI_REG8(0x3A12), 0x71},
  {CCI_REG8(0x3A14), 0xDE}, {CCI_REG8(0x3A20), 0x2B}, {CCI_REG8(0x3A24), 0x22},
  {CCI_REG8(0x3A25), 0x25}, {CCI_REG8(0x3A26), 0x2A}, {CCI_REG8(0x3A27), 0x2C},
  {CCI_REG8(0x3A28), 0x39}, {CCI_REG8(0x3A29), 0x38}, {CCI_REG8(0x3A30), 0x04},
  {CCI_REG8(0x3A31), 0x04}, {CCI_REG8(0x3A32), 0x03}, {CCI_REG8(0x3A33), 0x03},
  {CCI_REG8(0x3A34), 0x09}, {CCI_REG8(0x3A35), 0x06}, {CCI_REG8(0x3A38), 0xCD},
  {CCI_REG8(0x3A3A), 0x4C}, {CCI_REG8(0x3A3C), 0xB9}, {CCI_REG8(0x3A3E), 0x30},
  {CCI_REG8(0x3A40), 0x2C}, {CCI_REG8(0x3A42), 0x39}, {CCI_REG8(0x3A4E), 0x00},
  {CCI_REG8(0x3A52), 0x00}, {CCI_REG8(0x3A56), 0x00}, {CCI_REG8(0x3A5A), 0x00},
  {CCI_REG8(0x3A5E), 0x00}, {CCI_REG8(0x3A62), 0x00}, {CCI_REG8(0x3A64), 0x00},
  {CCI_REG8(0x3A6E), 0xA0}, {CCI_REG8(0x3A70), 0x50}, {CCI_REG8(0x3A8C), 0x04},
  {CCI_REG8(0x3A8D), 0x03}, {CCI_REG8(0x3A8E), 0x09}, {CCI_REG8(0x3A90), 0x38},
  {CCI_REG8(0x3A91), 0x42}, {CCI_REG8(0x3A92), 0x3C}, {CCI_REG8(0x3B0E), 0xF3},
  {CCI_REG8(0x3B12), 0xE5}, {CCI_REG8(0x3B27), 0xC0}, {CCI_REG8(0x3B2E), 0xEF},
  {CCI_REG8(0x3B30), 0x6A}, {CCI_REG8(0x3B32), 0xF6}, {CCI_REG8(0x3B36), 0xE1},
  {CCI_REG8(0x3B3A), 0xE8}, {CCI_REG8(0x3B5A), 0x17}, {CCI_REG8(0x3B5E), 0xEF},
  {CCI_REG8(0x3B60), 0x6A}, {CCI_REG8(0x3B62), 0xF6}, {CCI_REG8(0x3B66), 0xE1},
  {CCI_REG8(0x3B6A), 0xE8}, {CCI_REG8(0x3B88), 0xEC}, {CCI_REG8(0x3B8A), 0xED},
  {CCI_REG8(0x3B94), 0x71}, {CCI_REG8(0x3B96), 0x72}, {CCI_REG8(0x3B98), 0xDE},
  {CCI_REG8(0x3B9A), 0xDF}, {CCI_REG8(0x3C0F), 0x06}, {CCI_REG8(0x3C10), 0x06},
  {CCI_REG8(0x3C11), 0x06}, {CCI_REG8(0x3C12), 0x06}, {CCI_REG8(0x3C13), 0x06},
  {CCI_REG8(0x3C18), 0x20}, {CCI_REG8(0x3C37), 0x10}, {CCI_REG8(0x3C3A), 0x7A},
  {CCI_REG8(0x3C40), 0xF4}, {CCI_REG8(0x3C48), 0xE6}, {CCI_REG8(0x3C54), 0xCE},
  {CCI_REG8(0x3C56), 0xD0}, {CCI_REG8(0x3C6C), 0x53}, {CCI_REG8(0x3C6E), 0x55},
  {CCI_REG8(0x3C70), 0xC0}, {CCI_REG8(0x3C72), 0xC2}, {CCI_REG8(0x3C7E), 0xCE},
  {CCI_REG8(0x3C8C), 0xCF}, {CCI_REG8(0x3C8E), 0xEB}, {CCI_REG8(0x3C98), 0x54},
  {CCI_REG8(0x3C9A), 0x70}, {CCI_REG8(0x3C9C), 0xC1}, {CCI_REG8(0x3C9E), 0xDD},
  {CCI_REG8(0x3CB0), 0x7A}, {CCI_REG8(0x3CB2), 0xBA}, {CCI_REG8(0x3CC8), 0xBC},
  {CCI_REG8(0x3CCA), 0x7C}, {CCI_REG8(0x3CD4), 0xEA}, {CCI_REG8(0x3CD5), 0x01},
  {CCI_REG8(0x3CD6), 0x4A}, {CCI_REG8(0x3CD8), 0x00}, {CCI_REG8(0x3CD9), 0x00},
  {CCI_REG8(0x3CDA), 0xFF}, {CCI_REG8(0x3CDB), 0x03}, {CCI_REG8(0x3CDC), 0x00},
  {CCI_REG8(0x3CDD), 0x00}, {CCI_REG8(0x3CDE), 0xFF}, {CCI_REG8(0x3CDF), 0x03},
  {CCI_REG8(0x3CE4), 0x4C}, {CCI_REG8(0x3CE6), 0xEC}, {CCI_REG8(0x3CE7), 0x01},
  {CCI_REG8(0x3CE8), 0xFF}, {CCI_REG8(0x3CE9), 0x03}, {CCI_REG8(0x3CEA), 0x00},
  {CCI_REG8(0x3CEB), 0x00}, {CCI_REG8(0x3CEC), 0xFF}, {CCI_REG8(0x3CED), 0x03},
  {CCI_REG8(0x3CEE), 0x00}, {CCI_REG8(0x3CEF), 0x00}, {CCI_REG8(0x3CF2), 0xFF},
  {CCI_REG8(0x3CF3), 0x03}, {CCI_REG8(0x3CF4), 0x00}, {CCI_REG8(0x3E28), 0x82},
  {CCI_REG8(0x3E2A), 0x80}, {CCI_REG8(0x3E30), 0x85}, {CCI_REG8(0x3E32), 0x7D},
  {CCI_REG8(0x3E5C), 0xCE}, {CCI_REG8(0x3E5E), 0xD3}, {CCI_REG8(0x3E70), 0x53},
  {CCI_REG8(0x3E72), 0x58}, {CCI_REG8(0x3E74), 0xC0}, {CCI_REG8(0x3E76), 0xC5},
  {CCI_REG8(0x3E78), 0xC0}, {CCI_REG8(0x3E79), 0x01}, {CCI_REG8(0x3E7A), 0xD4},
  {CCI_REG8(0x3E7B), 0x01}, {CCI_REG8(0x3EB4), 0x0B}, {CCI_REG8(0x3EB5), 0x02},
  {CCI_REG8(0x3EB6), 0x4D}, {CCI_REG8(0x3EB7), 0x42}, {CCI_REG8(0x3EEC), 0xF3},
  {CCI_REG8(0x3EEE), 0xE7}, {CCI_REG8(0x3F01), 0x01}, {CCI_REG8(0x3F24), 0x10},
  {CCI_REG8(0x3F28), 0x2D}, {CCI_REG8(0x3F2A), 0x2D}, {CCI_REG8(0x3F2C), 0x2D},
  {CCI_REG8(0x3F2E), 0x2D}, {CCI_REG8(0x3F30), 0x23}, {CCI_REG8(0x3F38), 0x2D},
  {CCI_REG8(0x3F3A), 0x2D}, {CCI_REG8(0x3F3C), 0x2D}, {CCI_REG8(0x3F3E), 0x28},
  {CCI_REG8(0x3F40), 0x1E}, {CCI_REG8(0x3F48), 0x2D}, {CCI_REG8(0x3F4A), 0x2D},
  {CCI_REG8(0x3F4C), 0x00}, {CCI_REG8(0x4004), 0xE4}, {CCI_REG8(0x4006), 0xFF},
  {CCI_REG8(0x4018), 0x69}, {CCI_REG8(0x401A), 0x84}, {CCI_REG8(0x401C), 0xD6},
  {CCI_REG8(0x401E), 0xF1}, {CCI_REG8(0x4038), 0xDE}, {CCI_REG8(0x403A), 0x00},
  {CCI_REG8(0x403B), 0x01}, {CCI_REG8(0x404C), 0x63}, {CCI_REG8(0x404E), 0x85},
  {CCI_REG8(0x4050), 0xD0}, {CCI_REG8(0x4052), 0xF2}, {CCI_REG8(0x4108), 0xDD},
  {CCI_REG8(0x410A), 0xF7}, {CCI_REG8(0x411C), 0x62}, {CCI_REG8(0x411E), 0x7C},
  {CCI_REG8(0x4120), 0xCF}, {CCI_REG8(0x4122), 0xE9}, {CCI_REG8(0x4138), 0xE6},
  {CCI_REG8(0x413A), 0xF1}, {CCI_REG8(0x414C), 0x6B}, {CCI_REG8(0x414E), 0x76},
  {CCI_REG8(0x4150), 0xD8}, {CCI_REG8(0x4152), 0xE3}, {CCI_REG8(0x417E), 0x03},
  {CCI_REG8(0x417F), 0x01}, {CCI_REG8(0x4186), 0xE0}, {CCI_REG8(0x4190), 0xF3},
  {CCI_REG8(0x4192), 0xF7}, {CCI_REG8(0x419C), 0x78}, {CCI_REG8(0x419E), 0x7C},
  {CCI_REG8(0x41A0), 0xE5}, {CCI_REG8(0x41A2), 0xE9}, {CCI_REG8(0x41C8), 0xE2},
  {CCI_REG8(0x41CA), 0xFD}, {CCI_REG8(0x41DC), 0x67}, {CCI_REG8(0x41DE), 0x82},
  {CCI_REG8(0x41E0), 0xD4}, {CCI_REG8(0x41E2), 0xEF}, {CCI_REG8(0x4200), 0xDE},
  {CCI_REG8(0x4202), 0xDA}, {CCI_REG8(0x4218), 0x63}, {CCI_REG8(0x421A), 0x5F},
  {CCI_REG8(0x421C), 0xD0}, {CCI_REG8(0x421E), 0xCC}, {CCI_REG8(0x425A), 0x82},
  {CCI_REG8(0x425C), 0xEF}, {CCI_REG8(0x4348), 0xFE}, {CCI_REG8(0x4349), 0x06},
  {CCI_REG8(0x4352), 0xCE}, {CCI_REG8(0x4420), 0x0B}, {CCI_REG8(0x4421), 0x02},
  {CCI_REG8(0x4422), 0x4D}, {CCI_REG8(0x4423), 0x0A}, {CCI_REG8(0x4426), 0xF5},
  {CCI_REG8(0x442A), 0xE7}, {CCI_REG8(0x4432), 0xF5}, {CCI_REG8(0x4436), 0xE7},
  {CCI_REG8(0x4466), 0xB4}, {CCI_REG8(0x446E), 0x32}, {CCI_REG8(0x449F), 0x1C},
  {CCI_REG8(0x44A4), 0x2C}, {CCI_REG8(0x44A6), 0x2C}, {CCI_REG8(0x44A8), 0x2C},
  {CCI_REG8(0x44AA), 0x2C}, {CCI_REG8(0x44B4), 0x2C}, {CCI_REG8(0x44B6), 0x2C},
  {CCI_REG8(0x44B8), 0x2C}, {CCI_REG8(0x44BA), 0x2C}, {CCI_REG8(0x44C4), 0x2C},
  {CCI_REG8(0x44C6), 0x2C}, {CCI_REG8(0x44C8), 0x2C}, {CCI_REG8(0x4506), 0xF3},
  {CCI_REG8(0x450E), 0xE5}, {CCI_REG8(0x4516), 0xF3}, {CCI_REG8(0x4522), 0xE5},
  {CCI_REG8(0x4524), 0xF3}, {CCI_REG8(0x452C), 0xE5}, {CCI_REG8(0x453C), 0x22},
  {CCI_REG8(0x453D), 0x1B}, {CCI_REG8(0x453E), 0x1B}, {CCI_REG8(0x453F), 0x15},
  {CCI_REG8(0x4540), 0x15}, {CCI_REG8(0x4541), 0x15}, {CCI_REG8(0x4542), 0x15},
  {CCI_REG8(0x4543), 0x15}, {CCI_REG8(0x4544), 0x15}, {CCI_REG8(0x4548), 0x00},
  {CCI_REG8(0x4549), 0x01}, {CCI_REG8(0x454A), 0x01}, {CCI_REG8(0x454B), 0x06},
  {CCI_REG8(0x454C), 0x06}, {CCI_REG8(0x454D), 0x06}, {CCI_REG8(0x454E), 0x06},
  {CCI_REG8(0x454F), 0x06}, {CCI_REG8(0x4550), 0x06}, {CCI_REG8(0x4554), 0x55},
  {CCI_REG8(0x4555), 0x02}, {CCI_REG8(0x4556), 0x42}, {CCI_REG8(0x4557), 0x05},
  {CCI_REG8(0x4558), 0xFD}, {CCI_REG8(0x4559), 0x05}, {CCI_REG8(0x455A), 0x94},
  {CCI_REG8(0x455B), 0x06}, {CCI_REG8(0x455D), 0x06}, {CCI_REG8(0x455E), 0x49},
  {CCI_REG8(0x455F), 0x07}, {CCI_REG8(0x4560), 0x7F}, {CCI_REG8(0x4561), 0x07},
  {CCI_REG8(0x4562), 0xA5}, {CCI_REG8(0x4564), 0x55}, {CCI_REG8(0x4565), 0x02},
  {CCI_REG8(0x4566), 0x42}, {CCI_REG8(0x4567), 0x05}, {CCI_REG8(0x4568), 0xFD},
  {CCI_REG8(0x4569), 0x05}, {CCI_REG8(0x456A), 0x94}, {CCI_REG8(0x456B), 0x06},
  {CCI_REG8(0x456D), 0x06}, {CCI_REG8(0x456E), 0x49}, {CCI_REG8(0x456F), 0x07},
  {CCI_REG8(0x4572), 0xA5}, {CCI_REG8(0x460C), 0x7D}, {CCI_REG8(0x460E), 0xB1},
  {CCI_REG8(0x4614), 0xA8}, {CCI_REG8(0x4616), 0xB2}, {CCI_REG8(0x461C), 0x7E},
  {CCI_REG8(0x461E), 0xA7}, {CCI_REG8(0x4624), 0xA8}, {CCI_REG8(0x4626), 0xB2},
  {CCI_REG8(0x462C), 0x7E}, {CCI_REG8(0x462E), 0x8A}, {CCI_REG8(0x4630), 0x94},
  {CCI_REG8(0x4632), 0xA7}, {CCI_REG8(0x4634), 0xFB}, {CCI_REG8(0x4636), 0x2F},
  {CCI_REG8(0x4638), 0x81}, {CCI_REG8(0x4639), 0x01}, {CCI_REG8(0x463A), 0xB5},
  {CCI_REG8(0x463B), 0x01}, {CCI_REG8(0x463C), 0x26}, {CCI_REG8(0x463E), 0x30},
  {CCI_REG8(0x4640), 0xAC}, {CCI_REG8(0x4641), 0x01}, {CCI_REG8(0x4642), 0xB6},
  {CCI_REG8(0x4643), 0x01}, {CCI_REG8(0x4644), 0xFC}, {CCI_REG8(0x4646), 0x25},
  {CCI_REG8(0x4648), 0x82}, {CCI_REG8(0x4649), 0x01}, {CCI_REG8(0x464A), 0xAB},
  {CCI_REG8(0x464B), 0x01}, {CCI_REG8(0x464C), 0x26}, {CCI_REG8(0x464E), 0x30},
  {CCI_REG8(0x4654), 0xFC}, {CCI_REG8(0x4656), 0x08}, {CCI_REG8(0x4658), 0x12},
  {CCI_REG8(0x465A), 0x25}, {CCI_REG8(0x4662), 0xFC}, {CCI_REG8(0x46A2), 0xFB},
  {CCI_REG8(0x46D6), 0xF3}, {CCI_REG8(0x46E6), 0x00}, {CCI_REG8(0x46E8), 0xFF},
  {CCI_REG8(0x46E9), 0x03}, {CCI_REG8(0x46EC), 0x7A}, {CCI_REG8(0x46EE), 0xE5},
  {CCI_REG8(0x46F4), 0xEE}, {CCI_REG8(0x46F6), 0xF2}, {CCI_REG8(0x470C), 0xFF},
  {CCI_REG8(0x470D), 0x03}, {CCI_REG8(0x470E), 0x00}, {CCI_REG8(0x4714), 0xE0},
  {CCI_REG8(0x4716), 0xE4}, {CCI_REG8(0x471E), 0xED}, {CCI_REG8(0x472E), 0x00},
  {CCI_REG8(0x4730), 0xFF}, {CCI_REG8(0x4731), 0x03}, {CCI_REG8(0x4734), 0x7B},
  {CCI_REG8(0x4736), 0xDF}, {CCI_REG8(0x4754), 0x7D}, {CCI_REG8(0x4756), 0x8B},
  {CCI_REG8(0x4758), 0x93}, {CCI_REG8(0x475A), 0xB1}, {CCI_REG8(0x475C), 0xFB},
  {CCI_REG8(0x475E), 0x09}, {CCI_REG8(0x4760), 0x11}, {CCI_REG8(0x4762), 0x2F},
  {CCI_REG8(0x4766), 0xCC}, {CCI_REG8(0x4776), 0xCB}, {CCI_REG8(0x477E), 0x4A},
  {CCI_REG8(0x478E), 0x49}, {CCI_REG8(0x4794), 0x7C}, {CCI_REG8(0x4796), 0x8F},
  {CCI_REG8(0x4798), 0xB3}, {CCI_REG8(0x4799), 0x00}, {CCI_REG8(0x479A), 0xCC},
  {CCI_REG8(0x479C), 0xC1}, {CCI_REG8(0x479E), 0xCB}, {CCI_REG8(0x47A4), 0x7D},
  {CCI_REG8(0x47A6), 0x8E}, {CCI_REG8(0x47A8), 0xB4}, {CCI_REG8(0x47A9), 0x00},
  {CCI_REG8(0x47AA), 0xC0}, {CCI_REG8(0x47AC), 0xFA}, {CCI_REG8(0x47AE), 0x0D},
  {CCI_REG8(0x47B0), 0x31}, {CCI_REG8(0x47B1), 0x01}, {CCI_REG8(0x47B2), 0x4A},
  {CCI_REG8(0x47B3), 0x01}, {CCI_REG8(0x47B4), 0x3F}, {CCI_REG8(0x47B6), 0x49},
  {CCI_REG8(0x47BC), 0xFB}, {CCI_REG8(0x47BE), 0x0C}, {CCI_REG8(0x47C0), 0x32},
  {CCI_REG8(0x47C1), 0x01}, {CCI_REG8(0x47C2), 0x3E}, {CCI_REG8(0x47C3), 0x01},
  {CCI_REG8(0x301A), 0x00}, {CCI_REG8(0x3022), 0x01}, {CCI_REG8(0x3023), 0x01},
};

static const struct cci_reg_sequence IMX678_init_table[] = {
  /* use all-pixel readout mode, no flip */
  {IMX678_WINMODE, 0x00},
  {IMX678_ADDMODE, 0x00},
  {IMX678_HREVERSE, 0x00},
  {IMX678_VREVERSE, 0x00},
  /* use RAW 12-bit mode (aligns with SRGGB12 pipeline) */
  {IMX678_ADBIT, 0x01},
  {IMX678_MDBIT, 0x01},
};

static inline struct IMX678 *to_IMX678(struct v4l2_subdev *sd) { return container_of(sd, struct IMX678, subdev); }

static int IMX678_link_freq_to_datarate_sel(u64 link_freq, u8 *val) {
  switch (link_freq) {
    case 297000000:
      *val = 0x07;
      return 0;
    case 360000000:
      *val = 0x06;
      return 0;
    case 445500000:
      *val = 0x05;
      return 0;
    case 594000000:
      *val = 0x04;
      return 0;
    case 720000000:
      *val = 0x03;
      return 0;
    case 891000000:
      *val = 0x02;
      return 0;
    case 1039500000:
      *val = 0x01;
      return 0;
    case 1188000000:
      *val = 0x00;
      return 0;
    default:
      return -EINVAL;
  }
}

static int IMX678_set_testpattern(struct IMX678 *sensor, int val) {
  int ret = 0;

  if (val) {
    cci_write(sensor->regmap, IMX678_BLKLEVEL, 0x00, &ret);
    cci_write(sensor->regmap, IMX678_TPG_EN_DUOUT, 0x01, &ret);
    cci_write(sensor->regmap, IMX678_TPG_PATSEL_DUOUT, val - 1, &ret);
    cci_write(sensor->regmap, IMX678_TPG_COLORWIDTH, 0x01, &ret);
    cci_write(sensor->regmap, IMX678_TESTCLKEN_MIPI, 0x20, &ret);
    cci_write(sensor->regmap, IMX678_DIG_CLP_MODE, 0x00, &ret);
    cci_write(sensor->regmap, IMX678_WRJ_OPEN, 0x00, &ret);
  } else {
    cci_write(sensor->regmap, IMX678_BLKLEVEL, IMX678_BLKLEVEL_DEFAULT, &ret);
    cci_write(sensor->regmap, IMX678_TPG_EN_DUOUT, 0x00, &ret);
    cci_write(sensor->regmap, IMX678_TESTCLKEN_MIPI, 0x00, &ret);
    cci_write(sensor->regmap, IMX678_DIG_CLP_MODE, 0x01, &ret);
    cci_write(sensor->regmap, IMX678_WRJ_OPEN, 0x01, &ret);
  }
  return 0;
}

static int IMX678_s_ctrl(struct v4l2_ctrl *ctrl) {
  struct IMX678 *sensor = container_of(ctrl->handler, struct IMX678, ctrls);
  const struct v4l2_mbus_framefmt *format;
  struct v4l2_subdev_state *state;
  unsigned int vmax;
  int ret;

  if (!pm_runtime_get_if_in_use(sensor->dev)) return 0;

  state = v4l2_subdev_get_locked_active_state(&sensor->subdev);
  format = v4l2_subdev_state_get_format(state, 0);

  switch (ctrl->id) {
    case V4L2_CID_EXPOSURE:
      vmax = format->height + sensor->vblank->cur.val;
      ctrl->val = min_t(int, ctrl->val, vmax);
      ret = cci_write(sensor->regmap, IMX678_SHR0, vmax - ctrl->val, NULL);
      break;

    case V4L2_CID_ANALOGUE_GAIN:
      ret = cci_write(sensor->regmap, IMX678_GAIN_PCG_0, ctrl->val, NULL);
      break;

    case V4L2_CID_HFLIP:
      ret = cci_write(sensor->regmap, IMX678_HREVERSE, sensor->hflip->val, NULL);
      break;
    case V4L2_CID_VFLIP:
      ret = cci_write(sensor->regmap, IMX678_VREVERSE, sensor->vflip->val, NULL);
      break;

    case V4L2_CID_TEST_PATTERN:
      ret = IMX678_set_testpattern(sensor, ctrl->val);
      break;

    default:
      ret = -EINVAL;
      break;
  }

  pm_runtime_put(sensor->dev);

  return ret;
}

static const struct v4l2_ctrl_ops IMX678_ctrl_ops = {
    .s_ctrl = IMX678_s_ctrl,
};

static int IMX678_ctrls_init(struct IMX678 *sensor) {
  struct v4l2_fwnode_device_properties props;
  struct v4l2_ctrl *ctrl;
  u64 pixel_rate = supported_modes[sensor->cur_mode].pixel_rate;
  u64 lane_rate = supported_modes[sensor->cur_mode].lane_rate;
  u32 exposure_max = IMX678_PIXEL_ARRAY_HEIGHT + IMX678_PIXEL_ARRAY_VBLANK - 8;
  u32 hblank;
  unsigned int i;
  int ret;

  ret = v4l2_fwnode_device_parse(sensor->dev, &props);
  if (ret < 0) return ret;

  v4l2_ctrl_handler_init(&sensor->ctrls, 10);

  for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); ++i) {
    if (lane_rate == link_freq_menu_items[i] * 2) break;
  }
  if (i == ARRAY_SIZE(link_freq_menu_items)) {
    return dev_err_probe(sensor->dev, -EINVAL, "lane rate %llu not supported\n", lane_rate);
  }

  ctrl = v4l2_ctrl_new_int_menu(&sensor->ctrls, &IMX678_ctrl_ops, V4L2_CID_LINK_FREQ, ARRAY_SIZE(link_freq_menu_items) - 1, i,
                                link_freq_menu_items);

  if (ctrl) ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;

  v4l2_ctrl_new_std(&sensor->ctrls, &IMX678_ctrl_ops, V4L2_CID_EXPOSURE, 4, exposure_max, 1, exposure_max);

  v4l2_ctrl_new_std(&sensor->ctrls, &IMX678_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, IMX678_AGAIN_MIN, IMX678_AGAIN_MAX, IMX678_AGAIN_STEP,
                    IMX678_AGAIN_MIN);

  hblank = supported_modes[sensor->cur_mode].hmax_pix - IMX678_PIXEL_ARRAY_WIDTH;
  ctrl = v4l2_ctrl_new_std(&sensor->ctrls, &IMX678_ctrl_ops, V4L2_CID_HBLANK, hblank, hblank, 1, hblank);
  if (ctrl) ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;

  sensor->vblank = v4l2_ctrl_new_std(&sensor->ctrls, &IMX678_ctrl_ops, V4L2_CID_VBLANK, IMX678_PIXEL_ARRAY_VBLANK,
                                     IMX678_PIXEL_ARRAY_VBLANK, 1, IMX678_PIXEL_ARRAY_VBLANK);
  if (sensor->vblank) sensor->vblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;

  v4l2_ctrl_new_std(&sensor->ctrls, NULL, V4L2_CID_PIXEL_RATE, pixel_rate, pixel_rate, 1, pixel_rate);

  sensor->hflip = v4l2_ctrl_new_std(&sensor->ctrls, &IMX678_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0);
  sensor->vflip = v4l2_ctrl_new_std(&sensor->ctrls, &IMX678_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0);

  v4l2_ctrl_new_std_menu_items(&sensor->ctrls, &IMX678_ctrl_ops, V4L2_CID_TEST_PATTERN, ARRAY_SIZE(IMX678_test_pattern_menu) - 1, 0, 0,
                               IMX678_test_pattern_menu);

  v4l2_ctrl_new_fwnode_properties(&sensor->ctrls, &IMX678_ctrl_ops, &props);

  if (sensor->ctrls.error) {
    dev_err_probe(sensor->dev, sensor->ctrls.error, "failed to add controls\n");
    v4l2_ctrl_handler_free(&sensor->ctrls);
    return sensor->ctrls.error;
  }
  sensor->subdev.ctrl_handler = &sensor->ctrls;

  return 0;
}

static int IMX678_set_mode(struct IMX678 *sensor, int mode) {
  int ret = 0;
  u64 vmax = 0;
  u64 hmax = 0;
  u8 datarate_sel = 0;
  u64 link_freq;

  if (mode >= ARRAY_SIZE(supported_modes)) {
    dev_err(sensor->dev, "Mode %d not supported\n", mode);
    return -EINVAL;
  }

  cci_multi_reg_write(sensor->regmap, supported_modes[mode].reg_list.regs, supported_modes[mode].reg_list.num_of_regs, &ret);

  cci_multi_reg_write(sensor->regmap, sensor->clk_params->regs, IMX678_NUM_CLK_PARAM_REGS, &ret);

  if (!ret) {
    if (sensor->num_data_lanes == 2)
      cci_write(sensor->regmap, IMX678_LANEMODE, IMX678_LANEMODE_2, &ret);
    else if (sensor->num_data_lanes == 4)
      cci_write(sensor->regmap, IMX678_LANEMODE, IMX678_LANEMODE_4, &ret);
    else
      ret = -EINVAL;
  }

  link_freq = supported_modes[mode].lane_rate / 2;
  if (!ret) {
    if (IMX678_link_freq_to_datarate_sel(link_freq, &datarate_sel))
      dev_warn(sensor->dev, "unsupported link frequency %llu for DATARATE_SEL\n", link_freq);
    else
      cci_write(sensor->regmap, IMX678_DATARATE_SEL, datarate_sel, &ret);
  }

  /* Cache VMAX and HMAX values from mode configuration for stream start */
  sensor->vmax_cached = supported_modes[mode].reg_list.regs[0].val;
  sensor->hmax_cached = supported_modes[mode].reg_list.regs[1].val;
  dev_dbg(sensor->dev, "Cached timing: VMAX=0x%llx, HMAX=0x%llx\n", 
          sensor->vmax_cached, sensor->hmax_cached);

  /* Validate that key timing registers latched; if not, fail fast for easier debug. */
  cci_read(sensor->regmap, IMX678_VMAX, &vmax, &ret);
  cci_read(sensor->regmap, IMX678_HMAX, &hmax, &ret);

  /* If timing readback looks bogus, warn but continue with programmed values. */
  if (!ret && (!vmax || !hmax)) {
    dev_warn(sensor->dev, "timing registers invalid (using table values): VMAX=0x%llx HMAX=0x%x\n", vmax, hmax);
  }

  return 0;
}

static int IMX678_setup(struct IMX678 *sensor, struct v4l2_subdev_state *state) {
  int ret;

  if (!sensor->common_regs_written) {
    ret = cci_multi_reg_write(sensor->regmap, IMX678_common_regs, ARRAY_SIZE(IMX678_common_regs), NULL);
    if (ret) return ret;

    ret = cci_write(sensor->regmap, IMX678_INCK_SEL, sensor->inck_sel, NULL);
    if (ret) return ret;

    ret = cci_write(sensor->regmap, IMX678_BLKLEVEL, IMX678_BLKLEVEL_DEFAULT, NULL);
    if (ret) return ret;

    ret = cci_write(sensor->regmap, IMX678_DATARATE_SEL, sensor->datarate_sel, NULL);
    if (ret) return ret;

    if (sensor->num_data_lanes == 2)
      ret = cci_write(sensor->regmap, IMX678_LANEMODE, IMX678_LANEMODE_2, NULL);
    else
      ret = cci_write(sensor->regmap, IMX678_LANEMODE, IMX678_LANEMODE_4, NULL);
    if (ret) return ret;

    /* Default to Internal Sync Leader Mode: enable XVS/XHS output. */
    ret = cci_write(sensor->regmap, IMX678_EXTMODE, 0x00, NULL);
    if (ret) return ret;
    ret = cci_write(sensor->regmap, IMX678_DRV, 0x00, NULL);
    if (ret) return ret;
    ret = cci_write(sensor->regmap, IMX678_OUTSEL, 0x0A, NULL);
    if (ret) return ret;

    sensor->common_regs_written = true;
  }

  ret = cci_multi_reg_write(sensor->regmap, IMX678_init_table, ARRAY_SIZE(IMX678_init_table), NULL);
  if (ret) return ret;

  return IMX678_set_mode(sensor, sensor->cur_mode);
}

static int IMX678_wakeup(struct IMX678 *sensor) {
  int ret;

  ret = cci_write(sensor->regmap, IMX678_REG_MODE_SELECT, IMX678_MODE_STREAMING, NULL);
  if (ret) return ret;

  msleep(25);

  return 0;
}

static int IMX678_stream_on(struct IMX678 *sensor) {
  int ret;
  u64 vmax = 0;
  u64 hmax = 0;

  dev_info(sensor->dev, "IMX678_stream_on: starting (mode=%d)\n", sensor->cur_mode);

  ret = IMX678_wakeup(sensor);
  if (ret) {
    dev_err(sensor->dev, "Wakeup before stream failed: %d\n", ret);
    return ret;
  }

  dev_info(sensor->dev, "Sensor wakeup complete\n");

  /* Ensure leader mode when streaming unless explicitly overridden. */
  ret = cci_write(sensor->regmap, IMX678_XMSTA, 0x00, NULL);
  if (ret) {
    dev_err(sensor->dev, "Failed to set XMSTA: %d\n", ret);
    return ret;
  }

  /* Use cached VMAX/HMAX values (may be updated by V4L2 controls) */
  dev_info(sensor->dev, "Writing cached VMAX=0x%llx, HMAX=0x%llx\n", 
          sensor->vmax_cached, sensor->hmax_cached);

  /* Explicitly re-write VMAX and HMAX before streaming, as in reference driver.
   * This is critical for the CSI bridge to calculate proper data rates. */
  ret = cci_write(sensor->regmap, IMX678_VMAX, sensor->vmax_cached, NULL);
  if (ret) {
    dev_err(sensor->dev, "VMAX write failed: %d\n", ret);
    return ret;
  }
  ret = cci_write(sensor->regmap, IMX678_HMAX, sensor->hmax_cached, NULL);
  if (ret) {
    dev_err(sensor->dev, "HMAX write failed: %d\n", ret);
    return ret;
  }

  /* Validate that key timing registers latched; if not, fail fast for easier debug. */
  cci_read(sensor->regmap, IMX678_VMAX, &vmax, &ret);
  cci_read(sensor->regmap, IMX678_HMAX, &hmax, &ret);
  dev_info(sensor->dev, "Readback VMAX=0x%llx, HMAX=0x%llx (expected: 0x%llx, 0x%llx)\n", 
          vmax, hmax, sensor->vmax_cached, sensor->hmax_cached);

  if (!ret && (!vmax || !hmax)) {
    dev_err(sensor->dev, "timing registers invalid: VMAX=0x%llx HMAX=0x%llx\n", vmax, hmax);
    return -EIO;
  }

  if (!ret && (vmax != sensor->vmax_cached || hmax != sensor->hmax_cached)) {
    dev_warn(sensor->dev, "Timing register mismatch! Read: VMAX=0x%llx HMAX=0x%llx, Expected: VMAX=0x%llx HMAX=0x%llx\n",
             vmax, hmax, sensor->vmax_cached, sensor->hmax_cached);
  }

  dev_info(sensor->dev, "Starting IMX678 stream (mode=%d, VMAX=0x%llx, HMAX=0x%llx)\n", 
           sensor->cur_mode, vmax, hmax);

  /* Log critical MIPI timing configuration for debugging CSI receiver */
  {
    u64 lanemode = 0, datarate_sel = 0, inck_sel = 0;
    cci_read(sensor->regmap, IMX678_LANEMODE, &lanemode, NULL);
    cci_read(sensor->regmap, IMX678_DATARATE_SEL, &datarate_sel, NULL);
    cci_read(sensor->regmap, IMX678_INCK_SEL, &inck_sel, NULL);
    dev_info(sensor->dev, "MIPI Config: LANEMODE=0x%llx, DATARATE_SEL=0x%llx, INCK_SEL=0x%llx\n",
             lanemode, datarate_sel, inck_sel);
    dev_info(sensor->dev, "Expected: %d CSI lanes, %llu bps lane rate\n",
             sensor->num_data_lanes, supported_modes[sensor->cur_mode].lane_rate);
  }

  /* CRITICAL: Disable digital clamp to enable MIPI CSI-2 output */
  ret = cci_write(sensor->regmap, IMX678_DIGITAL_CLAMP, 0, NULL);
  if (ret) {
    dev_err(sensor->dev, "Failed to disable digital clamp: %d\n", ret);
    return ret;
  }
  dev_info(sensor->dev, "Digital clamp disabled\n");

  /* Set stream on register - this is the actual streaming trigger */
  ret = cci_write(sensor->regmap, IMX678_REG_MODE_SELECT, IMX678_MODE_STREAMING, &ret);
  if (ret) {
    dev_err(sensor->dev, "Failed to start streaming (MODE_SELECT): %d\n", ret);
    return ret;
  }

  /* CRITICAL: Allow CSI-2 link training to complete.
   * The j721e_csi2rx receiver needs the sensor to be actively streaming
   * with stable clock lanes before it can allocate DMA buffers.
   * Without this delay, VIDIOC_STREAMON fails with "Broken pipe". */
  msleep(50);

  dev_info(sensor->dev, "IMX678 streaming started successfully\n");
  return 0;
}

static int IMX678_stream_off(struct IMX678 *sensor) {
  int ret;

  dev_info(sensor->dev, "Stopping IMX678 stream\n");
  ret = cci_write(sensor->regmap, IMX678_REG_MODE_SELECT, IMX678_MODE_STANDBY, NULL);
  msleep(30);
  cci_write(sensor->regmap, IMX678_XMSTA, 0x01, NULL);
  return ret;
}

static int IMX678_s_stream(struct v4l2_subdev *sd, int enable) {
  struct IMX678 *sensor = to_IMX678(sd);
  struct v4l2_subdev_state *state;
  int ret;

  dev_info(sensor->dev, "IMX678_s_stream called: enable=%d\n", enable);

  state = v4l2_subdev_lock_and_get_active_state(sd);

  if (!enable) {
    dev_info(sensor->dev, "Stream disable requested\n");
    ret = IMX678_stream_off(sensor);

    pm_runtime_mark_last_busy(sensor->dev);
    pm_runtime_put_autosuspend(sensor->dev);

    goto unlock;
  }

  dev_info(sensor->dev, "Stream enable requested\n");
  ret = pm_runtime_resume_and_get(sensor->dev);
  if (ret < 0) {
    dev_err(sensor->dev, "pm_runtime_resume_and_get failed: %d\n", ret);
    goto err_pm;
  }

  /* Ensure sensor is awake so timing registers are writable before programming. */
  ret = IMX678_wakeup(sensor);
  if (ret) {
    dev_err(sensor->dev, "Wakeup before setup failed: %d\n", ret);
    goto err_pm;
  }

  ret = IMX678_setup(sensor, state);
  if (ret) {
    dev_err(sensor->dev, "Sensor setup failed: %d\n", ret);
    goto err_pm;
  }

  ret = __v4l2_ctrl_handler_setup(&sensor->ctrls);
  if (ret < 0) {
    dev_err(sensor->dev, "Control handler setup failed: %d\n", ret);
    goto err_pm;
  }

  ret = IMX678_stream_on(sensor);
  if (ret) {
    dev_err(sensor->dev, "Stream on failed: %d\n", ret);
    goto err_pm;
  }

  dev_info(sensor->dev, "IMX678: Started streaming\n");
  ret = 0;

unlock:
  v4l2_subdev_unlock_state(state);
  dev_info(sensor->dev, "IMX678_s_stream completed: ret=%d\n", ret);
  return ret;

err_pm:
  dev_err(sensor->dev, "Streaming failed, cleaning up (ret=%d)\n", ret);
  pm_runtime_mark_last_busy(sensor->dev);
  pm_runtime_put_autosuspend(sensor->dev);
  goto unlock;
}

static int IMX678_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_mbus_code_enum *code) {
  if (code->index != 0) return -EINVAL;

  code->code = MEDIA_BUS_FMT_SRGGB12_1X12;

  return 0;
}

static int IMX678_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_frame_size_enum *fse) {
  const struct v4l2_mbus_framefmt *format;

  format = v4l2_subdev_state_get_format(state, fse->pad);

  if (fse->index > 0 || fse->code != format->code) return -EINVAL;

  fse->min_width = IMX678_PIXEL_ARRAY_WIDTH;
  fse->max_width = fse->min_width;
  fse->min_height = IMX678_PIXEL_ARRAY_HEIGHT;
  fse->max_height = fse->min_height;
  return 0;
}

static int IMX678_set_format(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) {
  struct v4l2_mbus_framefmt *format;

  format = v4l2_subdev_state_get_format(state, fmt->pad);

  format->width = fmt->format.width;
  format->height = fmt->format.height;
  format->code = MEDIA_BUS_FMT_SRGGB12_1X12;
  format->field = V4L2_FIELD_NONE;
  format->colorspace = V4L2_COLORSPACE_RAW;
  format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
  format->quantization = V4L2_QUANTIZATION_DEFAULT;
  format->xfer_func = V4L2_XFER_FUNC_NONE;

  fmt->format = *format;
  return 0;
}

static int IMX678_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) {
  switch (sel->target) {
    case V4L2_SEL_TGT_CROP:
    case V4L2_SEL_TGT_CROP_DEFAULT:
    case V4L2_SEL_TGT_CROP_BOUNDS:
      sel->r.top = IMX678_PIXEL_ARRAY_TOP;
      sel->r.left = IMX678_PIXEL_ARRAY_LEFT;
      sel->r.width = IMX678_PIXEL_ARRAY_WIDTH;
      sel->r.height = IMX678_PIXEL_ARRAY_HEIGHT;

      return 0;
  }

  return -EINVAL;
}

static int IMX678_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *state) {
  struct v4l2_subdev_format format = {
      .format =
          {
              .width = IMX678_PIXEL_ARRAY_WIDTH,
              .height = IMX678_PIXEL_ARRAY_HEIGHT,
          },
  };

  IMX678_set_format(sd, state, &format);

  return 0;
}

static const struct v4l2_subdev_video_ops IMX678_subdev_video_ops = {
    .s_stream = IMX678_s_stream,
};

static const struct v4l2_subdev_pad_ops IMX678_subdev_pad_ops = {
    .enum_mbus_code = IMX678_enum_mbus_code,
    .enum_frame_size = IMX678_enum_frame_size,
    .get_fmt = v4l2_subdev_get_fmt,
    .set_fmt = IMX678_set_format,
    .get_selection = IMX678_get_selection,
};

static const struct v4l2_subdev_ops IMX678_subdev_ops = {
    .video = &IMX678_subdev_video_ops,
    .pad = &IMX678_subdev_pad_ops,
};

static const struct v4l2_subdev_internal_ops IMX678_internal_ops = {
    .init_state = IMX678_init_state,
};

static int IMX678_subdev_init(struct IMX678 *sensor) {
  struct i2c_client *client = to_i2c_client(sensor->dev);
  int ret;

  v4l2_i2c_subdev_init(&sensor->subdev, client, &IMX678_subdev_ops);
  sensor->subdev.internal_ops = &IMX678_internal_ops;

  ret = IMX678_ctrls_init(sensor);
  if (ret) return ret;

  sensor->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
  sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
  sensor->subdev.entity.function = MEDIA_ENT_F_CAM_SENSOR;
  ret = media_entity_pads_init(&sensor->subdev.entity, 1, &sensor->pad);
  if (ret < 0) {
    v4l2_ctrl_handler_free(&sensor->ctrls);
    return ret;
  }

  sensor->subdev.state_lock = sensor->subdev.ctrl_handler->lock;
  v4l2_subdev_init_finalize(&sensor->subdev);

  return 0;
}

static void IMX678_subdev_cleanup(struct IMX678 *sensor) {
  media_entity_cleanup(&sensor->subdev.entity);
  v4l2_ctrl_handler_free(&sensor->ctrls);
}

static int IMX678_power_on(struct IMX678 *sensor) {
  int ret;

  ret = regulator_bulk_enable(ARRAY_SIZE(sensor->supplies), sensor->supplies);
  if (ret < 0) return ret;

  gpiod_set_value_cansleep(sensor->reset, 0);

  udelay(1);

  ret = clk_prepare_enable(sensor->clk);
  /* Give RC-tied Xshutdown ample time to release after rails/clock */
  msleep(500);
  
  if (ret < 0) goto err_reset;

  usleep_range(100, 200);

  return 0;

err_reset:
  gpiod_set_value_cansleep(sensor->reset, 1);
  regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), sensor->supplies);
  return ret;
}

static void IMX678_power_off(struct IMX678 *sensor) {
  clk_disable_unprepare(sensor->clk);
  gpiod_set_value_cansleep(sensor->reset, 1);
  regulator_bulk_disable(ARRAY_SIZE(sensor->supplies), sensor->supplies);

  sensor->common_regs_written = false;
}

static int IMX678_identify_model(struct IMX678 *sensor) {
  int model, ret;
  u64 chip_id;

  ret = IMX678_wakeup(sensor);
  if (ret) return dev_err_probe(sensor->dev, ret, "failed to get sensor out of standby\n");

  ret = cci_read(sensor->regmap, IMX678_SENSOR_INFO, &chip_id, NULL);
  if (ret < 0) {
    msleep(20);
    ret = cci_read(sensor->regmap, IMX678_SENSOR_INFO, &chip_id, NULL);
  }
  if (ret < 0) {
    dev_err_probe(sensor->dev, ret, "failed to read sensor information\n");
    goto done;
  }

  model = IMX678_CHIP_ID;
  
  switch (model) {
    case IMX678_CHIP_ID:
      dev_info(sensor->dev, "Detected IMX678 image sensor\n");
      break;
    default:
      ret = dev_err_probe(sensor->dev, -ENODEV, "invalid device model 0x%04x\n", model);
      goto done;
  }

  ret = 0;

done:
  cci_write(sensor->regmap, IMX678_REG_MODE_SELECT, IMX678_MODE_STANDBY, &ret);
  return ret;
}

static int IMX678_check_inck(unsigned long inck, u64 link_frequency) {
  unsigned int i;

  for (i = 0; i < ARRAY_SIZE(IMX678_clk_params); ++i) {
    if ((IMX678_clk_params[i].lane_rate == link_frequency * 2) && IMX678_clk_params[i].inck == inck) break;
  }

  if (i == ARRAY_SIZE(IMX678_clk_params))
    return -EINVAL;
  else
    return 0;
}

struct IMX678_selector_entry {
  unsigned long rate;
  u8 val;
};

/* INCK_SEL encoding (datasheet table) */
static const struct IMX678_selector_entry IMX678_inck_sel_table[] = {
    {74250000, 0x00}, {37125000, 0x01}, {72000000, 0x02}, {27000000, 0x03},
    {24000000, 0x04}, {36000000, 0x05}, {18000000, 0x06}, {13500000, 0x07},
};

/* DATARATE_SEL encoding (lane rate / 2) */
static const struct IMX678_selector_entry IMX678_datarate_sel_table[] = {
    {297000000, 0x07}, {360000000, 0x06}, {445500000, 0x05}, {594000000, 0x04},
    {720000000, 0x03}, {891000000, 0x02}, {1039500000, 0x01}, {1188000000, 0x00},
};

static int IMX678_set_selector_values(struct IMX678 *sensor, unsigned long inck, u64 lane_rate) {
  u64 link_freq = lane_rate / 2;
  unsigned int i;

  for (i = 0; i < ARRAY_SIZE(IMX678_inck_sel_table); ++i) {
    if (IMX678_inck_sel_table[i].rate == inck) {
      sensor->inck_sel = IMX678_inck_sel_table[i].val;
      break;
    }
  }
  if (i == ARRAY_SIZE(IMX678_inck_sel_table)) return -EINVAL;

  for (i = 0; i < ARRAY_SIZE(IMX678_datarate_sel_table); ++i) {
    if (IMX678_datarate_sel_table[i].rate == link_freq) {
      sensor->datarate_sel = IMX678_datarate_sel_table[i].val;
      break;
    }
  }
  if (i == ARRAY_SIZE(IMX678_datarate_sel_table)) return -EINVAL;

  return 0;
}

static int IMX678_parse_hw_config(struct IMX678 *sensor) {
  struct v4l2_fwnode_endpoint bus_cfg = {
      .bus_type = V4L2_MBUS_CSI2_DPHY,
  };
  struct fwnode_handle *ep;
  u64 lane_rate;
  unsigned long inck;
  unsigned int i, j;
  int ret;

  for (i = 0; i < ARRAY_SIZE(sensor->supplies); ++i) sensor->supplies[i].supply = IMX678_supply_names[i];

  ret = devm_regulator_bulk_get(sensor->dev, ARRAY_SIZE(sensor->supplies), sensor->supplies);
  if (ret) return dev_err_probe(sensor->dev, ret, "failed to get supplies\n");

  sensor->reset = devm_gpiod_get_optional(sensor->dev, "reset", GPIOD_OUT_HIGH);
  if (IS_ERR(sensor->reset)) return dev_err_probe(sensor->dev, PTR_ERR(sensor->reset), "failed to get reset GPIO\n");

  sensor->clk = devm_clk_get(sensor->dev, "inck");
  if (IS_ERR(sensor->clk)) return dev_err_probe(sensor->dev, PTR_ERR(sensor->clk), "failed to get clock\n");

  ep = fwnode_graph_get_next_endpoint(dev_fwnode(sensor->dev), NULL);
  if (!ep) return -ENXIO;

  ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
  fwnode_handle_put(ep);
  if (ret) return ret;

  switch (bus_cfg.bus.mipi_csi2.num_data_lanes) {
    case 2:
    case 4:
      sensor->num_data_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes;
      break;
    default:
      ret = dev_err_probe(sensor->dev, -EINVAL, "invalid number of CSI2 data lanes %d\n", bus_cfg.bus.mipi_csi2.num_data_lanes);
      goto done_endpoint_free;
  }

  if (!bus_cfg.nr_of_link_frequencies) {
    ret = dev_err_probe(sensor->dev, -EINVAL, "no link frequencies defined");
    goto done_endpoint_free;
  }

  inck = clk_get_rate(sensor->clk);
  for (i = 0; i < bus_cfg.nr_of_link_frequencies; ++i) {
    if (IMX678_check_inck(inck, bus_cfg.link_frequencies[i])) {
      dev_dbg(sensor->dev, "INCK %lu Hz not supported for this link freq", inck);
      continue;
    }

    for (j = 0; j < ARRAY_SIZE(supported_modes); ++j) {
      if (sensor->num_data_lanes != supported_modes[j].lanes) continue;
      if (bus_cfg.link_frequencies[i] * 2 != supported_modes[j].lane_rate) continue;
      sensor->cur_mode = j;
      break;
    }
    if (j < ARRAY_SIZE(supported_modes)) break;
  }
  if (i == bus_cfg.nr_of_link_frequencies) {
    ret = dev_err_probe(sensor->dev, -EINVAL, "no valid sensor mode defined\n");
    goto done_endpoint_free;
  }

  lane_rate = supported_modes[sensor->cur_mode].lane_rate;
  for (i = 0; i < ARRAY_SIZE(IMX678_clk_params); ++i) {
    if (lane_rate == IMX678_clk_params[i].lane_rate && inck == IMX678_clk_params[i].inck) {
      sensor->clk_params = &IMX678_clk_params[i];
      break;
    }
  }
  if (i == ARRAY_SIZE(IMX678_clk_params)) {
    ret = dev_err_probe(sensor->dev, -EINVAL, "Mode %d not supported\n", sensor->cur_mode);
    goto done_endpoint_free;
  }

  ret = IMX678_set_selector_values(sensor, inck, lane_rate);
  if (ret) {
    ret = dev_err_probe(sensor->dev, ret, "Failed to map INCK/DATARATE selector values\n");
    goto done_endpoint_free;
  }

  ret = 0;
  dev_dbg(sensor->dev, "clock: %lu Hz, lane_rate: %llu bps, lanes: %d\n", inck, lane_rate, sensor->num_data_lanes);

done_endpoint_free:
  v4l2_fwnode_endpoint_free(&bus_cfg);

  return ret;
}

static int IMX678_probe(struct i2c_client *client) {
  struct IMX678 *sensor;
  int ret;

  sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
  if (!sensor) return -ENOMEM;

  sensor->dev = &client->dev;

  ret = IMX678_parse_hw_config(sensor);
  if (ret) return ret;

  sensor->regmap = devm_cci_regmap_init_i2c(client, 16);
  if (IS_ERR(sensor->regmap)) return PTR_ERR(sensor->regmap);

  ret = IMX678_power_on(sensor);
  if (ret) return ret;

  ret = IMX678_identify_model(sensor);
  if (ret) goto err_power;

  ret = IMX678_subdev_init(sensor);
  if (ret) goto err_power;

  pm_runtime_set_active(sensor->dev);
  pm_runtime_get_noresume(sensor->dev);
  pm_runtime_enable(sensor->dev);

  ret = v4l2_async_register_subdev_sensor(&sensor->subdev);
  if (ret < 0) goto err_pm;

  pm_runtime_set_autosuspend_delay(sensor->dev, 1000);
  pm_runtime_use_autosuspend(sensor->dev);
  pm_runtime_put_autosuspend(sensor->dev);

  return 0;

err_pm:
  pm_runtime_disable(sensor->dev);
  pm_runtime_put_noidle(sensor->dev);
  IMX678_subdev_cleanup(sensor);
err_power:
  IMX678_power_off(sensor);
  return ret;
}

static void IMX678_remove(struct i2c_client *client) {
  struct v4l2_subdev *subdev = i2c_get_clientdata(client);
  struct IMX678 *sensor = to_IMX678(subdev);

  v4l2_async_unregister_subdev(subdev);

  IMX678_subdev_cleanup(sensor);

  pm_runtime_disable(sensor->dev);
  if (!pm_runtime_status_suspended(sensor->dev)) IMX678_power_off(sensor);
  pm_runtime_set_suspended(sensor->dev);
}

static int IMX678_runtime_resume(struct device *dev) {
  struct i2c_client *client = to_i2c_client(dev);
  struct v4l2_subdev *subdev = i2c_get_clientdata(client);
  struct IMX678 *sensor = to_IMX678(subdev);

  return IMX678_power_on(sensor);
}

static int IMX678_runtime_suspend(struct device *dev) {
  struct i2c_client *client = to_i2c_client(dev);
  struct v4l2_subdev *subdev = i2c_get_clientdata(client);
  struct IMX678 *sensor = to_IMX678(subdev);

  IMX678_power_off(sensor);

  return 0;
}

static DEFINE_RUNTIME_DEV_PM_OPS(IMX678_pm_ops, IMX678_runtime_suspend, IMX678_runtime_resume, NULL);

static const struct of_device_id IMX678_of_match[] = {
  {
    .compatible = "sony,imx678",
  },
  { /* sentinel */ },
};

MODULE_DEVICE_TABLE(of, IMX678_of_match);

static struct i2c_driver IMX678_driver = {
  .probe = IMX678_probe,
  .remove = IMX678_remove,
  .driver = {
    .name = "imx678",
    .of_match_table = IMX678_of_match,
    .pm = pm_ptr(&IMX678_pm_ops),
  },
};

module_i2c_driver(IMX678_driver);

MODULE_DESCRIPTION("Sony IMX678 image sensor driver");
MODULE_AUTHOR("Bradley King");
MODULE_LICENSE("GPL");
  • Hi Bradley,

    Can you please share the SDK version that you are using? Can you also share the output of media-ctl -p?

    I don't see an implementation of v4l2_pads_ops get_frame_desc which is used by the j721e driver to check for virtual channel and datatype configuration here: https://git.ti.com/cgit/ti-linux-kernel/ti-linux-kernel/tree/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c?h=ti-linux-6.12.y#n968

    Can you check the virtual channel and datatype that you are configuring?

    Also for the 0 byte file size, are you quitting out of the capture command or is it automatically exiting?

    Regards,
    Jay

  • Hi Jay, 

    SDK_VERSION="2025.01"
    SDK_VENDOR="-arago"
    SDK_NAME="arago-2025.01"

    root@am62axx-evm:/opt/edgeai-gst-apps# media-ctl -p
    Media controller API version 6.12.35
    
    Media device information
    ------------------------
    driver          j721e-csi2rx
    model           TI-CSI2RX
    serial
    bus info        platform:30102000.ticsi2rx
    hw revision     0x1
    driver version  6.12.35
    
    Device topology
    - entity 1: 30102000.ticsi2rx (7 pads, 7 links, 1 route)
                type V4L2 subdev subtype Unknown flags 0
                device node name /dev/v4l-subdev0
            routes:
                    0/0 -> 1/0 [ACTIVE]
            pad0: SINK
                    [stream:0 fmt:SRGGB12_1X12/3840x2160 field:none]
                    <- "cdns_csi2rx.30101000.csi-bridge":1 [ENABLED,IMMUTABLE]
            pad1: SOURCE
                    [stream:0 fmt:SRGGB12_1X12/3840x2160 field:none]
                    -> "30102000.ticsi2rx context 0":0 [ENABLED,IMMUTABLE]
            pad2: SOURCE
                    -> "30102000.ticsi2rx context 1":0 [ENABLED,IMMUTABLE]
            pad3: SOURCE
                    -> "30102000.ticsi2rx context 2":0 [ENABLED,IMMUTABLE]
            pad4: SOURCE
                    -> "30102000.ticsi2rx context 3":0 [ENABLED,IMMUTABLE]
            pad5: SOURCE
                    -> "30102000.ticsi2rx context 4":0 [ENABLED,IMMUTABLE]
            pad6: SOURCE
                    -> "30102000.ticsi2rx context 5":0 [ENABLED,IMMUTABLE]
    
    - entity 9: cdns_csi2rx.30101000.csi-bridge (5 pads, 2 links, 1 route)
                type V4L2 subdev subtype Unknown flags 0
                device node name /dev/v4l-subdev1
            routes:
                    0/0 -> 1/0 [ACTIVE]
            pad0: SINK
                    [stream:0 fmt:SRGGB12_1X12/3840x2160 field:none]
                    <- "imx678 4-001a":0 [ENABLED,IMMUTABLE]
            pad1: SOURCE
                    [stream:0 fmt:SRGGB12_1X12/3840x2160 field:none]
                    -> "30102000.ticsi2rx":0 [ENABLED,IMMUTABLE]
            pad2: SOURCE
            pad3: SOURCE
            pad4: SOURCE
    
    - entity 15: imx678 4-001a (1 pad, 1 link, 0 routes)
                 type V4L2 subdev subtype Sensor flags 0
                 device node name /dev/v4l-subdev2
            pad0: SOURCE
                    [stream:0 fmt:SRGGB12_1X12/3840x2160 field:none colorspace:raw xfer:none
                     crop.bounds:(8,8)/3840x2160
                     crop:(8,8)/3840x2160]
                    -> "cdns_csi2rx.30101000.csi-bridge":0 [ENABLED,IMMUTABLE]
    
    - entity 21: 30102000.ticsi2rx context 0 (1 pad, 1 link)
                 type Node subtype V4L flags 0
                 device node name /dev/video3
            pad0: SINK
                    <- "30102000.ticsi2rx":1 [ENABLED,IMMUTABLE]
    
    - entity 27: 30102000.ticsi2rx context 1 (1 pad, 1 link)
                 type Node subtype V4L flags 0
                 device node name /dev/video4
            pad0: SINK
                    <- "30102000.ticsi2rx":2 [ENABLED,IMMUTABLE]
    
    - entity 33: 30102000.ticsi2rx context 2 (1 pad, 1 link)
                 type Node subtype V4L flags 0
                 device node name /dev/video5
            pad0: SINK
                    <- "30102000.ticsi2rx":3 [ENABLED,IMMUTABLE]
    
    - entity 39: 30102000.ticsi2rx context 3 (1 pad, 1 link)
                 type Node subtype V4L flags 0
                 device node name /dev/video6
            pad0: SINK
                    <- "30102000.ticsi2rx":4 [ENABLED,IMMUTABLE]
    
    - entity 45: 30102000.ticsi2rx context 4 (1 pad, 1 link)
                 type Node subtype V4L flags 0
                 device node name /dev/video7
            pad0: SINK
                    <- "30102000.ticsi2rx":5 [ENABLED,IMMUTABLE]
    
    - entity 51: 30102000.ticsi2rx context 5 (1 pad, 1 link)
                 type Node subtype V4L flags 0
                 device node name /dev/video8
            pad0: SINK
                    <- "30102000.ticsi2rx":6 [ENABLED,IMMUTABLE]
    
    root@am62axx-evm:/opt/edgeai-gst-apps#



    I have to crtrl C but I get a stream completed, 

    to=/tmp/test.raw 2>&1/edgeai-gst-apps# v4l2-ctl -d /dev/video3 --set-fmt-video=width=3840,height=2160,pixelformat=RG12 --stream-mmap --stream-count=5 --stream-t
    [ 2426.004604] imx678 4-001a: IMX678_s_stream called: enable=1
    [ 2426.010212] imx678 4-001a: Stream enable requested
    [ 2426.629294] imx678 4-001a: IMX678_stream_on: starting (mode=0)
    [ 2426.663972] imx678 4-001a: Sensor wakeup complete
    [ 2426.668836] imx678 4-001a: Writing cached VMAX=0x8ca, HMAX=0xa50
    [ 2426.675588] imx678 4-001a: Readback VMAX=0x8ca, HMAX=0xa50 (expected: 0x8ca, 0xa50)
    [ 2426.683270] imx678 4-001a: Starting IMX678 stream (mode=0, VMAX=0x8ca, HMAX=0xa50)
    [ 2426.691371] imx678 4-001a: MIPI Config: LANEMODE=0x1, DATARATE_SEL=0x6, INCK_SEL=0x4
    [ 2426.699145] imx678 4-001a: Expected: 2 CSI lanes, 720000000 bps lane rate
    [ 2426.706077] imx678 4-001a: Digital clamp disabled
    [ 2426.763965] imx678 4-001a: IMX678 streaming started successfully
    [ 2426.769987] imx678 4-001a: IMX678: Started streaming
    [ 2426.774956] imx678 4-001a: IMX678_s_stream completed: ret=0
    
    ^C
    
    [ 2429.552177] imx678 4-001a: IMX678_s_stream called: enable=0
    [ 2429.557777] imx678 4-001a: Stream disable requested
    [ 2429.562663] imx678 4-001a: Stopping IMX678 stream
    [ 2429.600097] imx678 4-001a: IMX678_s_stream completed: ret=0


    I've added in a get frame desc, not sure what I am doing exactly.

    static int IMX678_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
    struct v4l2_mbus_frame_desc *fd) {
    struct v4l2_subdev_state *state;
    const struct v4l2_mbus_framefmt *format;
    u32 bpp;
    int ret = 0;

    if (pad != 0) return -EINVAL;

    state = v4l2_subdev_lock_and_get_active_state(sd);

    format = v4l2_subdev_state_get_format(state, pad);
    if (!format) {
    ret = -EPIPE;
    goto out;
    }

    memset(fd, 0, sizeof(*fd));
    fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;

    bpp = 12;

    fd->entry[0].stream = 0;
    fd->entry[0].flags = V4L2_MBUS_FRAME_DESC_FL_LEN_MAX;
    fd->entry[0].length = format->width * format->height * bpp / 8;
    fd->entry[0].pixelcode = format->code;
    fd->entry[0].bus.csi2.vc = 0;
    fd->entry[0].bus.csi2.dt = 0x2c; /* RAW12 */

    fd->num_entries = 1;

    out:
    v4l2_subdev_unlock_state(state);
    return ret;
    }

    +static const struct v4l2_subdev_pad_ops IMX678_subdev_pad_ops = {
    +    .enum_mbus_code = IMX678_enum_mbus_code,
    +    .enum_frame_size = IMX678_enum_frame_size,
    +    .get_fmt = v4l2_subdev_get_fmt,
    +    .set_fmt = IMX678_set_format,
    +    .get_frame_desc = IMX678_get_frame_desc,
    +    .get_selection = IMX678_get_selection,
    +};
    +



  • Hi Bradley,

    This can happen when the CSI2RX is not receiving any data. Can you please share the value of these 4 registers:
    info_irqs 0x30101020
    error_irqs 0x30101028
    stream0_status 0x30101104
    dphy_status 0x30101048

    Regards,
    Jay

  • Hi Jay,
    I have also encountered the same issue faced by Bradley but for a different sensor 0x03f10,the steps and the integration details are almost same.
    Please find my regsiter dump for refernce

    root@am62axx-evm:~# devmem2 0x30101028 w
    /dev/mem opened.
    Memory mapped at address 0xffffa73d2000.
    Read at address 0x30101028 (0xffffa73d2028): 0x00020100

    root@am62axx-evm:~# devmem2 0x30101020 w
    /dev/mem opened.
    Memory mapped at address 0xffffbedc3000.
    Read at address 0x30101020 (0xffffbedc3020): 0x00000022

    root@am62axx-evm:~# devmem2 0x30101104 w
    /dev/mem opened.
    Memory mapped at address 0xffffb25f2000.
    Read at address 0x30101104 (0xffffb25f2104): 0x80000133

    root@am62axx-evm:~# devmem2 0x30101048 w
    /dev/mem opened.
    Memory mapped at address 0xffff8e68b000.
    Read at address 0x30101048 (0xffff8e68b048): 0x00333306

    can you please analyse ?

    Thanks

  • Hi Jay,

    Here is the value of the 4 registers,

    info_irqs 

    Read at address 0x30101020 (0xffffba4d3020): 0x00000000

    error_irqs 

    Read at address 0x30101028 (0xffffa8535028): 0x00000100

    stream0_status 

    Read at address 0x30101104 (0xffffbee3f104): 0x00000000

    dphy_status

    Read at address 0x30101048 (0xffffabe8e048): 0x00222206


    Thanks, 


  • Hi Bradley,

    I just want to confirm if you are reading the register values when you have started streaming because stream0_status shows that stream is not enabled.

    Can you please try using yavta? The equivalent command would be something along the lines of:

    yavta -c -s 3840x2160 -f RGGB12 /dev/video3

     please create a new thread for the same. Also please try the yavta capture and share the details there.

    Regards,
    Jay

  • Sorry, 

    While the Yavta command is running. 


    Read at address 0x30101020 (0xffffaf8de020): 0x00000000

    Read at address 0x30101028 (0xffffadc12028): 0x00000100

    Read at address 0x30101104 (0xffffbeec0104): 0x80000111

    Read at address 0x30101048 (0xffffa5196048): 0x00223307


    I changed to a 4 lane CSI and re ran the command


    root@am62axx-evm:/opt/edgeai-gst-apps# yavta -c -s 3840x2160 -f SRGGB12 /dev/video3
    Device /dev/video3 opened.
    Device `j721e-csi2rx' on `platform:30102000.ticsi2rx' (driver 'j721e-csi2rx') supports video, capture, without mplanes.
    Video format set: SRGGB12 (32314752) 3840x2160 (stride 7680) field none buffer size 16588800
    Video format: SRGGB12 (32314752) 3840x2160 (stride 7680) field none buffer size 16588800
    8 buffers requested.
    length: 16588800 offset: 0 timestamp type/source: mono/EoF
    Bu[  201.080539] imx678 4-001a: IMX678_s_stream called: enable=1
    ffer 0/0 mapped at address 0xffffae74e000.
    length: 16588800 off[  201.088436] imx678 4-001a: Stream enable requested
    set: 32768 timestamp type/source: mono/EoF
    Buffer 1/0 mapped at address 0xffffad77c000.
    length: 16588800 offset: 65536 timestamp type/source: mono/EoF
    Buffer 2/0 mapped at address 0xffffac7aa000.
    length: 16588800 offset: 98304 timestamp type/source: mono/EoF
    Buffer 3/0 mapped at address 0xffffab7d8000.
    length: 16588800 offset: 131072 timestamp type/source: mono/EoF
    Buffer 4/0 mapped at address 0xffffaa806000.
    length: 16588800 offset: 163840 timestamp type/source: mono/EoF
    Buffer 5/0 mapped at address 0xffffa9834000.
    length: 16588800 offset: 196608 timestamp type/source: mono/EoF
    Buffer 6/0 mapped at address 0xffffa8862000.
    length: 16588800 offset: 229376 timestamp type/source: mono/EoF
    Buffer 7/0 mapped at address 0xffffa7890000.
    [  201.705109] imx678 4-001a: IMX678_stream_on: starting (mode=2)
    [  201.739867] imx678 4-001a: Sensor wakeup complete
    [  201.744724] imx678 4-001a: Writing cached VMAX=0x8ca, HMAX=0x226
    [  201.751470] imx678 4-001a: Readback VMAX=0x8ca, HMAX=0x226 (expected: 0x8ca, 0x226)
    [  201.759151] imx678 4-001a: Starting IMX678 stream (mode=2, VMAX=0x8ca, HMAX=0x226)
    [  201.767255] imx678 4-001a: MIPI Config: LANEMODE=0x3, DATARATE_SEL=0x5, INCK_SEL=0x4
    [  201.775028] imx678 4-001a: Expected: 4 CSI lanes, 891000000 bps lane rate
    [  201.781973] imx678 4-001a: Digital clamp disabled
    [  201.839887] imx678 4-001a: IMX678 streaming started successfully
    [  201.845906] imx678 4-001a: IMX678: Started streaming
    [  201.850879] imx678 4-001a: IMX678_s_stream completed: ret=0


    Read at address 0x30101020 (0xffff8ab5b020): 0x00000000

    Read at address 0x30101028 (0xffffb749b028): 0x00000100

    Read at address 0x30101104 (0xffff914a2104): 0x80000111

    Read at address 0x30101048 (0xffffa0b7e048): 0x00333307


  • Hi Bradley,

    Based on the dphy_status register, the clock is in stopped state. Can you share the value of this register to see if the dphy is initialized:

    Also, what framerate are you trying to run this pipeline at? The configured link frequency of 360MHz doesn't seem sufficient.

    Regards,
    Jay

  • Hi Jay,


    Read at address 0x30111000 (0xffff90f79000): 0x40800000

    Yeah I have tried 4 lanes and 2 lanes at 720Mhz 
    I have added a bunch of logging into please see the latest logs. 

    dmesg | tail -n 120 | egrep -i "imx678|csi|ticsi|cdns"'
    [   10.244630] /bus@f0000/i2c@20010000/camera@1a: Fixed dependency cycle(s) with /bus@f0000/ticsi2rx@30102000/csi-bridge@30101000
    [   10.281838] /bus@f0000/ticsi2rx@30102000/csi-bridge@30101000: Fixed dependency cycle(s) with /bus@f0000/i2c@20010000/camera@1a
    [   10.322274] cdns-csi2rx 30101000.csi-bridge: Probed CSI2RX with 2/4 lanes, 4 streams, external D-PHY
    [   10.457523] imx678 1-001a: Detected IMX678 image sensor
    [  127.450682] cdns-mipi-dphy-rx 30110000.phy: DPHY WRAP@0x1000=0x00000000 (HS_ACTIVE=0) on power_on
    [  127.460009] cdns-mipi-dphy-rx 30110000.phy: DPHY WRAP@0x1000=0x40800000 (HS_ACTIVE=0) after configure
    [  127.469434] imx678 1-001a: IMX678_s_stream called: enable=1
    [  127.475046] imx678 1-001a: Stream enable requested
    [  128.168197] imx678 1-001a: IMX678_stream_on: starting (mode=0)
    [  128.174075] imx678 1-001a: Writing cached VMAX=0x8ca, HMAX=0xa50
    [  128.182754] imx678 1-001a: Readback VMAX=0x8ca, HMAX=0xa50 (expected: 0x8ca, 0xa50)
    [  128.190472] imx678 1-001a: Starting IMX678 stream (mode=0, VMAX=0x8ca, HMAX=0xa50)
    [  128.199728] imx678 1-001a: MIPI Config: LANEMODE=0x1, DATARATE_SEL=0x6, INCK_SEL=0x4
    [  128.207498] imx678 1-001a: Expected: 2 CSI lanes, 720000000 bps lane rate
    [  128.214759] imx678 1-001a: Digital clamp disabled
    [  128.219919] imx678 1-001a: XMSTA set to leader mode (0x00)
    [  128.225988] imx678 1-001a: XMSTA readback: 0x0
    [  128.231454] imx678 1-001a: MODE_SELECT readback: 0x0
    [  128.291799] imx678 1-001a: IMX678 streaming started successfully
    [  128.297823] imx678 1-001a: IMX678: Started streaming
    [  128.302789] imx678 1-001a: IMX678_s_stream completed: ret=0
    [  285.490284] imx678 1-001a: IMX678_s_stream called: enable=0
    [  285.495970] imx678 1-001a: Stream disable requested
    [  285.500866] imx678 1-001a: Stopping IMX678 stream
    [  285.542477] imx678 1-001a: IMX678_s_stream completed: ret=0
    [  285.548290] cdns-mipi-dphy-rx 30110000.phy: DPHY WRAP@0x1000=0x40800000 (HS_ACTIVE=0) on power_off
    [  296.575875] cdns-mipi-dphy-rx 30110000.phy: DPHY WRAP@0x1000=0x40800000 (HS_ACTIVE=0) on power_on
    [  296.584810] cdns-mipi-dphy-rx 30110000.phy: DPHY WRAP@0x1000=0x40800000 (HS_ACTIVE=0) after configure
    [  296.594173] imx678 1-001a: IMX678_s_stream called: enable=1
    [  296.599802] imx678 1-001a: Stream enable requested
    [  297.286565] imx678 1-001a: IMX678_stream_on: starting (mode=0)
    [  297.292446] imx678 1-001a: Writing cached VMAX=0x8ca, HMAX=0xa50
    [  297.301005] imx678 1-001a: Readback VMAX=0x8ca, HMAX=0xa50 (expected: 0x8ca, 0xa50)
    [  297.308698] imx678 1-001a: Starting IMX678 stream (mode=0, VMAX=0x8ca, HMAX=0xa50)
    [  297.317943] imx678 1-001a: MIPI Config: LANEMODE=0x1, DATARATE_SEL=0x6, INCK_SEL=0x4
    [  297.325698] imx678 1-001a: Expected: 2 CSI lanes, 720000000 bps lane rate
    [  297.332922] imx678 1-001a: Digital clamp disabled
    [  297.338101] imx678 1-001a: XMSTA set to leader mode (0x00)
    [  297.344149] imx678 1-001a: XMSTA readback: 0x0
    [  297.349583] imx678 1-001a: MODE_SELECT readback: 0x0
    [  297.409805] imx678 1-001a: IMX678 streaming started successfully
    [  297.415827] imx678 1-001a: IMX678: Started streaming
    [  297.420793] imx678 1-001a: IMX678_s_stream completed: ret=0
    [  299.697981] imx678 1-001a: IMX678_s_stream called: enable=0
    [  299.703595] imx678 1-001a: Stream disable requested
    [  299.708482] imx678 1-001a: Stopping IMX678 stream
    [  299.750195] imx678 1-001a: IMX678_s_stream completed: ret=0
    [  299.755958] cdns-mipi-dphy-rx 30110000.phy: DPHY WRAP@0x1000=0x40800000 (HS_ACTIVE=0) on power_off
    dmesg |
    media-ctl -p 2>/dev/null | sed -n "1,200p"
    Media controller API version 6.12.43
    
    Media device information
    ------------------------
    driver          j721e-csi2rx
    model           TI-CSI2RX
    serial          
    bus info        platform:30102000.ticsi2rx
    hw revision     0x1
    driver version  6.12.43
    
    Device topology
    - entity 1: 30102000.ticsi2rx (7 pads, 7 links, 1 route)
                type V4L2 subdev subtype Unknown flags 0
                device node name /dev/v4l-subdev0
            routes:
                    0/0 -> 1/0 [ACTIVE]
            pad0: SINK
                    [stream:0 fmt:SRGGB12_1X12/1920x1080 field:none]
                    <- "cdns_csi2rx.30101000.csi-bridge":1 [ENABLED,IMMUTABLE]
            pad1: SOURCE
                    [stream:0 fmt:SRGGB12_1X12/1920x1080 field:none]
                    -> "30102000.ticsi2rx context 0":0 [ENABLED,IMMUTABLE]
            pad2: SOURCE
                    -> "30102000.ticsi2rx context 1":0 [ENABLED,IMMUTABLE]
            pad3: SOURCE
                    -> "30102000.ticsi2rx context 2":0 [ENABLED,IMMUTABLE]
            pad4: SOURCE
                    -> "30102000.ticsi2rx context 3":0 [ENABLED,IMMUTABLE]
            pad5: SOURCE
                    -> "30102000.ticsi2rx context 4":0 [ENABLED,IMMUTABLE]
            pad6: SOURCE
                    -> "30102000.ticsi2rx context 5":0 [ENABLED,IMMUTABLE]
    
    - entity 9: cdns_csi2rx.30101000.csi-bridge (5 pads, 2 links, 1 route)
                type V4L2 subdev subtype Unknown flags 0
                device node name /dev/v4l-subdev1
            routes:
                    0/0 -> 1/0 [ACTIVE]
            pad0: SINK
                    [stream:0 fmt:SRGGB12_1X12/1920x1080 field:none]
                    <- "imx678 1-001a":0 [ENABLED,IMMUTABLE]
            pad1: SOURCE
                    [stream:0 fmt:SRGGB12_1X12/1920x1080 field:none]
                    -> "30102000.ticsi2rx":0 [ENABLED,IMMUTABLE]
            pad2: SOURCE
            pad3: SOURCE
            pad4: SOURCE
    
    - entity 15: imx678 1-001a (1 pad, 1 link, 0 routes)
                 type V4L2 subdev subtype Sensor flags 0
                 device node name /dev/v4l-subdev2
            pad0: SOURCE
                    [stream:0 fmt:SRGGB12_1X12/1920x1080 field:none colorspace:raw xfer:none
                     crop.bounds:(8,8)/3840x2160
                     crop:(8,8)/3840x2160]
                    -> "cdns_csi2rx.30101000.csi-bridge":0 [ENABLED,IMMUTABLE]
    
    - entity 21: 30102000.ticsi2rx context 0 (1 pad, 1 link)
                 type Node subtype V4L flags 0
                 device node name /dev/video0
            pad0: SINK
                    <- "30102000.ticsi2rx":1 [ENABLED,IMMUTABLE]
    
    - entity 27: 30102000.ticsi2rx context 1 (1 pad, 1 link)
                 type Node subtype V4L flags 0
                 device node name /dev/video1
            pad0: SINK
                    <- "30102000.ticsi2rx":2 [ENABLED,IMMUTABLE]
    
    - entity 33: 30102000.ticsi2rx context 2 (1 pad, 1 link)
                 type Node subtype V4L flags 0
                 device node name /dev/video2
            pad0: SINK
                    <- "30102000.ticsi2rx":3 [ENABLED,IMMUTABLE]
    
    - entity 39: 30102000.ticsi2rx context 3 (1 pad, 1 link)
                 type Node subtype V4L flags 0
                 device node name /dev/video3
            pad0: SINK
                    <- "30102000.ticsi2rx":4 [ENABLED,IMMUTABLE]
    
    - entity 45: 30102000.ticsi2rx context 4 (1 pad, 1 link)
                 type Node subtype V4L flags 0
                 device node name /dev/video4
            pad0: SINK
                    <- "30102000.ticsi2rx":5 [ENABLED,IMMUTABLE]
    
    - entity 51: 30102000.ticsi2rx context 5 (1 pad, 1 link)
                 type Node subtype V4L flags 0
                 device node name /dev/video5
            pad0: SINK
                    <- "30102000.ticsi2rx":6 [ENABLED,IMMUTABLE]
    

  • Here is the value of the 4 registers,

    Bradley,

    Can you run those devmem2 commands continuously when streaming and see if the values change? If the values don't change, for example, the value at 0x30101104 stays at 0x80000111, that could mean the sensor is not sending any data. Then you can use a logic analyzer to check if there are signals on the CSL lanes (clock and data).

    Regards,

    jianzhong

  • Hi Bradley,

    For keeping the threads focused, I have moved further discussions here:  AM62A7: IMX678 TIOVXISP fails to initialize 

    Regards,
    Jay