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
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 = <®_cam_1v8>;
ovdd-supply = <®_cam_1v8>;
avdd-supply = <®_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");

