Enable MCLK for custom sensor module?

Hi Forum,

I am bringing up a custom AR0234 camera module (without any oscillator) making use the <jetpack5.1.2-r35.4.1-bsp>/kernel/nvidia/drivers/media/i2c/nv_ar0234.c. Below is the dts :

/dts-v1/;
#include "tegra234-orin-nx-toto-VIO-WASN002.dts"
#include "../toto_camera/tegra234-orin-nx-toto-VIO-WASN002-AR0234-2LANES.dtsi"

/ {
	nvidia,dtsfilename = "tegra234-orin-nx-toto-VIO-WASN002-AR0234-2LANES.dts";
	nvidia,dtbbuildtime = __DATE__, __TIME__;
};

#include "dt-bindings/gpio/tegra234-gpio.h"
#include "dt-bindings/clock/tegra234-clock.h"

#define CAM0_RST_L	TEGRA234_MAIN_GPIO(H, 3)
#define CAM0_PWDN	TEGRA234_MAIN_GPIO(H, 6)
#define CAM1_RST_L	TEGRA234_MAIN_GPIO(AC, 1)
#define CAM1_PWDN	TEGRA234_MAIN_GPIO(AC, 0)
#define PWR_EN      	TEGRA234_MAIN_GPIO(AC, 7)
#define CAM_I2C_MUX 	TEGRA234_AON_GPIO(CC, 3)

/ {
	cam_i2cmux{
		status = "okay";
		compatible = "i2c-mux-gpio";
		#address-cells = <1>;
		#size-cells = <0>;
		i2c-parent = <&cam_i2c>;
		mux-gpios = <&tegra_aon_gpio CAM_I2C_MUX GPIO_ACTIVE_HIGH>;
		i2c@0 {
			status = "okay";
			reg = <0>;
			#address-cells = <1>;
			#size-cells = <0>;
            ar0234_a: ar0234_a@10 {
                status = "okay";
				/* Define any required hw resources needed by driver */
				/* ie. clocks, io pins, power sources */
				clocks = <&bpmp_clks TEGRA234_CLK_EXTPERIPH1>,
						<&bpmp_clks TEGRA234_CLK_EXTPERIPH1>;
				clock-names = "extperiph1", "pllp_grtba";
				clock-frequency = <24000000>;
				mclk = "extperiph1";
				channel = "a";
				reset-gpios = <&tegra_main_gpio CAM0_RST_L GPIO_ACTIVE_HIGH>;
				pwdn-gpios = <&tegra_main_gpio CAM0_PWDN GPIO_ACTIVE_HIGH>;
				pwr-gpios = <&tegra_main_gpio PWR_EN GPIO_ACTIVE_HIGH>;
            };
		};
		i2c@1 {
			status = "okay";
			reg = <1>;
			#address-cells = <1>;
			#size-cells = <0>;
            ar0234_c: ar0234_c@10 {
                status = "okay";
				/* Define any required hw resources needed by driver */
				/* ie. clocks, io pins, power sources */
				clocks = <&bpmp_clks TEGRA234_CLK_EXTPERIPH1>,
						<&bpmp_clks TEGRA234_CLK_EXTPERIPH1>;
				clock-names = "extperiph1", "pllp_grtba";
				clock-frequency = <24000000>;
				mclk = "extperiph1";
				channel = "a";
				reset-gpios = <&tegra_main_gpio CAM1_RST_L GPIO_ACTIVE_HIGH>;
				pwdn-gpios = <&tegra_main_gpio CAM1_PWDN GPIO_ACTIVE_HIGH>;
				pwr-gpios = <&tegra_main_gpio PWR_EN GPIO_ACTIVE_HIGH>;
            };
		};
	};

	gpio@2200000 {
		camera-control-output-low {
			gpio-hog;
			output-low;
			gpios = <CAM0_PWDN 0 CAM1_PWDN 0>;
			label = "cam0-pwdn","cam1-pwdn";
		};
	};
};

And the .dtsi :

#include <dt-bindings/media/camera.h>

/ {
	tegra-capture-vi  {
		num-channels = <2>;
		ports {
			#address-cells = <1>;
			#size-cells = <0>;
			vi_port0: port@0 {
				reg = <0>;
				vio_vi_in0: endpoint {
					port-index = <0>;
					bus-width = <2>;
					remote-endpoint = <&vio_csi_out0>;
				};
			};
			vi_port1: port@1 {
				reg = <1>;
				vio_vi_in1: endpoint {
					port-index = <2>;
					bus-width = <2>;
					remote-endpoint = <&vio_csi_out1>;
				};
			};
		};
	};

	host1x@13e00000 {
		nvcsi@15a00000 {
			num-channels = <2>;
			#address-cells = <1>;
			#size-cells = <0>;
			csi_chan0: channel@0 {
				reg = <0>;
				ports {
					#address-cells = <1>;
					#size-cells = <0>;
					csi_chan0_port0: port@0 {
						reg = <0>;
						vio_csi_in0: endpoint@0 {
							port-index = <0>;
							bus-width = <2>;
							remote-endpoint = <&vio_cam_out0>;
						};
					};
					csi_chan0_port1: port@1 {
						reg = <1>;
						vio_csi_out0: endpoint@1 {
							remote-endpoint = <&vio_vi_in0>;
						};
					};
				};
			};
			csi_chan1: channel@1 {
				reg = <1>;
				ports {
					#address-cells = <1>;
					#size-cells = <0>;
					csi_chan1_port0: port@0 {
						reg = <0>;
						vio_csi_in1: endpoint@2 {
							port-index = <2>;
							bus-width = <2>;
							remote-endpoint = <&vio_cam_out1>;
						};
					};
					csi_chan1_port1: port@1 {
						reg = <1>;
						vio_csi_out1: endpoint@3 {
							remote-endpoint = <&vio_vi_in1>;
						};
					};
				};
			};
		};
	};

	cam_i2cmux {
		i2c_0:i2c@0 {
			ar0234_a@10 {
				compatible = "onsemi,ar0234";

				reg = <0x10>;

				devnode = "video0";

				/* Physical dimensions of sensor */
				physical_w = "15.0";
				physical_h = "12.5";

				sensor_model ="ar0234";

				supports-alt-exp = "true";

				post_crop_frame_drop = "0";

				/* Convert Gain to unit of dB (decibel) befor passing to kernel driver */
				//use_decibel_gain = "true";

				/* enable CID_SENSOR_MODE_ID for sensor modes selection */
				use_sensor_mode_id = "true";

				mode0 {/*mode IMX424_MODE_3840X1080_CROP_30FPS*/
					mclk_khz = "24000";
					num_lanes = "2";
					tegra_sinterface = "serial_a";
					vc_id = "1";
					discontinuous_clk = "no";
					dpcm_enable = "false";
					cil_settletime = "0";
					dynamic_pixel_bit_depth = "10";
					csi_pixel_bit_depth = "10";
					mode_type = "bayer";
					pixel_phase = "grbg";

					active_w = "1920";
					active_h = "1200";
					readout_orientation = "0";
					line_length = "2448";
					inherent_gain = "1";
					mclk_multiplier = "3.01";
					pix_clk_hz = "134400000";
					serdes_pix_clk_hz = "299000000";

					gain_factor = "100";
					min_gain_val = "100"; /* dB */
					max_gain_val = "1600"; /* dB */
					step_gain_val = "1"; /* 0.1 */
					default_gain = "100";
					min_hdr_ratio = "1";
					max_hdr_ratio = "1";
					framerate_factor = "1000000";
					min_framerate = "30000000";
					max_framerate = "30000000";
					step_framerate = "30000000";
					default_framerate = "30000000";
					exposure_factor = "1000000";
					min_exp_time = "28"; /*us, 2 lines*/
					max_exp_time = "22000";
					step_exp_time = "1";
					default_exp_time = "22000";/* us */
					embedded_metadata_height = "0";
				};

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

					port@0 {
						reg = <0>;
						vio_cam_out0: endpoint {
							port-index = <0>;  /* CSI A */
							bus-width = <2>;
							remote-endpoint = <&vio_csi_in0>;
						};
					};
				};
			};
		};

		i2c_1: i2c@1 {
			ar0234_c@10 {
				compatible = "onsemi,ar0234";

				reg = <0x10>;

				devnode = "video1";

				/* Physical dimensions of sensor */
				physical_w = "15.0";
				physical_h = "12.5";

				sensor_model ="ar0234";

				supports-alt-exp = "true";
				post_crop_frame_drop = "0";

				/* Convert Gain to unit of dB (decibel) befor passing to kernel driver */
				//use_decibel_gain = "true";

				/* enable CID_SENSOR_MODE_ID for sensor modes selection */
				use_sensor_mode_id = "true";

				mode0 {/*mode IMX424_MODE_3840X1080_CROP_30FPS*/
					mclk_khz = "24000";
					num_lanes = "2";
					tegra_sinterface = "serial_c";
					vc_id = "1";
					discontinuous_clk = "no";
					dpcm_enable = "false";
					cil_settletime = "0";
					dynamic_pixel_bit_depth = "10";
					csi_pixel_bit_depth = "10";
					mode_type = "bayer";
					pixel_phase = "grbg";

					active_w = "1920";
					active_h = "1200";
					readout_orientation = "0";
					line_length = "2448";
					inherent_gain = "1";
					mclk_multiplier = "3.01";
					pix_clk_hz = "134400000";
					serdes_pix_clk_hz = "299000000";

					gain_factor = "100";
					min_gain_val = "100"; /* dB */
					max_gain_val = "1600"; /* dB */
					step_gain_val = "1"; /* 0.1 */
					default_gain = "100";
					min_hdr_ratio = "1";
					max_hdr_ratio = "1";
					framerate_factor = "1000000";
					min_framerate = "30000000";
					max_framerate = "30000000";
					step_framerate = "30000000";
					default_framerate = "30000000";
					exposure_factor = "1000000";
					min_exp_time = "28"; /*us, 2 lines*/
					max_exp_time = "22000";
					step_exp_time = "1";
					default_exp_time = "22000";/* us */
					embedded_metadata_height = "0";
				};

				ports {
					#address-cells = <1>;
					#size-cells = <0>;
					port@0 {
						reg = <0>;
						vio_cam_out1: endpoint {
							status = "okay";
							port-index = <2>;
							bus-width = <2>;
							remote-endpoint = <&vio_csi_in1>;
						};
					};
				};
			};
		};
	};
};

/ {
	tcp: tegra-camera-platform {
		compatible = "nvidia, tegra-camera-platform";
		/**
		* Physical settings to calculate max ISO BW
		*
		* num_csi_lanes = <>;
		* Total number of CSI lanes when all cameras are active
		*
		* max_lane_speed = <>;
		* Max lane speed in Kbit/s
		*
		* min_bits_per_pixel = <>;
		* Min bits per pixel
		*
		* vi_peak_byte_per_pixel = <>;
		* Max byte per pixel for the VI ISO case
		*
		* vi_bw_margin_pct = <>;
		* Vi bandwidth margin in percentage
		*
		* max_pixel_rate = <>;
		* Max pixel rate in Kpixel/s for the ISP ISO case
		*
		* isp_peak_byte_per_pixel = <>;
		* Max byte per pixel for the ISP ISO case
		*
		* isp_bw_margin_pct = <>;
		* Isp bandwidth margin in percentage
		*/
		num_csi_lanes = <4>;
		max_lane_speed = <1500000>;
		min_bits_per_pixel = <10>;
		vi_peak_byte_per_pixel = <2>;
		vi_bw_margin_pct = <25>;
		max_pixel_rate = <240000>;
		isp_peak_byte_per_pixel = <5>;
		isp_bw_margin_pct = <25>;

		/**
		 * The general guideline for naming badge_info contains 3 parts, and is as follows,
		 * The first part is the camera_board_id for the module; if the module is in a FFD
		 * platform, then use the platform name for this part.
		 * The second part contains the position of the module, ex. "rear" or "front".
		 * The third part contains the last 6 characters of a part number which is found
		 * in the module's specsheet from the vendor.
		 */
		modules {
			cam_module0: module0 {
				badge = "li_ar0234_front";
				position = "front";
				orientation = "1";
				cam_module0_drivernode0: drivernode0 {
					pcl_id = "v4l2_sensor";
					devname = "ar0234 9-0010";
					proc-device-tree = "/proc/device-tree/cam_i2cmux/i2c@0/ar0234_a@10";
				};
			};
			cam_module1: module1 {
				badge = "li_ar0234_rear";
				position = "rear";
				orientation = "1";
				cam_module1_drivernode0: drivernode0 {
					pcl_id = "v4l2_sensor";
					devname = "ar0234 10-0010";
					proc-device-tree = "/proc/device-tree/cam_i2cmux/i2c@1/ar0234_c@10";
				};
			};
		};
	};
};

The modified source file in which I dropped gmsl, used i2c only. I also moved the ar0234_board_setup() function on the top of any writting / reading to the I2C register(s) to make sure that the clocks are enabled (via camera_common_mclk_enable() as suggested in How to force mclk enabled for cmos sensor driver - #3 by yawei.yang ) :

/*
 * ar0234.c - ar0234 sensor driver
 *
 * Copyright (c) 2018-2023, NVIDIA CORPORATION.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#define DEBUG 1
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/module.h>

#include <linux/seq_file.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>

#if 0
#include <media/max9295.h>
#include <media/max9296.h>
#endif

#include <media/tegracam_core.h>
#include "ar0234_mode_tbls.h"

#define CHANNEL_N 13
#define MAX_RADIAL_COEFFICIENTS         6
#define MAX_TANGENTIAL_COEFFICIENTS     2
#define MAX_FISHEYE_COEFFICIENTS        6
#define CAMERA_MAX_SN_LENGTH 32
#define LEOP_CAMERA_MAX_SN_LENGTH 10

#define MAX_RLS_COLOR_CHANNELS 4
#define MAX_RLS_BREAKPOINTS 6

#if 0
extern int max96712_write_reg_Dser(int slaveAddr,int channel,
		u16 addr, u8 val);

extern int max96712_read_reg_Dser(int slaveAddr,int channel,
		                u16 addr, unsigned int *val);
#endif

#define AR0234_MIN_GAIN         (1)
#define AR0234_MAX_GAIN         (8)
#define AR0234_MAX_GAIN_REG     (0x40)
#define AR0234_DEFAULT_FRAME_LENGTH    (1224)
#define AR0234_COARSE_TIME_SHS1_ADDR    0x3012
#define AR0234_CTX_CONTROL_REG_ADDR	0x3034
#define AR0234_CTX_WR_DATA_REG_ADDR	0x3066
#define AR0234_ANALOG_GAIN    0x3060

const struct of_device_id ar0234_of_match[] = {
	{.compatible = "onsemi,ar0234",},
	{ },
};
MODULE_DEVICE_TABLE(of, ar0234_of_match);

static const u32 ctrl_cid_list[] = {
	TEGRA_CAMERA_CID_GAIN,
	TEGRA_CAMERA_CID_EXPOSURE,
	TEGRA_CAMERA_CID_EXPOSURE_SHORT,
	TEGRA_CAMERA_CID_FRAME_RATE,
	TEGRA_CAMERA_CID_EEPROM_DATA,
	TEGRA_CAMERA_CID_HDR_EN,
	TEGRA_CAMERA_CID_SENSOR_MODE_ID,
	TEGRA_CAMERA_CID_STEREO_EEPROM,
	TEGRA_CAMERA_CID_ALTERNATING_EXPOSURE,
};

const u16 alternating_exposure_cfg_size = sizeof(struct alternating_exposure_cfg);

// Coefficients as per distortion model (wide FOV) being used
typedef struct
{
	// Radial coefficients count
	u32 coeff_count;
	// Radial coefficients
	float k[MAX_FISHEYE_COEFFICIENTS];
	// 0 -> equidistant, 1 -> equisolid, 2 -> orthographic, 3 -> stereographic
	u32 mapping_type;
} fisheye_lens_distortion_coeff;

// Coefficients as per distortion model being used
typedef struct
{
	// Radial coefficients count
	u32 radial_coeff_count;
	// Radial coefficients
	float k[MAX_RADIAL_COEFFICIENTS];
	// Tangential coefficients count
	u32 tangential_coeff_count;
	// Tangential coefficients
	float p[MAX_TANGENTIAL_COEFFICIENTS];
} polynomial_lens_distortion_coeff;

/*
 * Stereo Eeprom Data
 */
typedef struct
{
	// Width and height of image in pixels
	u32 width, height;
	// Focal length in pixels
	float fx, fy;
	float skew;
	// Principal point (optical center) in pixels
	float cx, cy;
	/*
	 * Structure for distortion coefficients as per the model being used
	 * 0: pinhole, assuming polynomial distortion
	 * 1: fisheye, assuming fisheye distortion)
	 * 2: ocam (omini-directional)
	 */
	u32 distortion_type;
	union distortion_coefficients {
		polynomial_lens_distortion_coeff poly;
		fisheye_lens_distortion_coeff fisheye;
	} dist_coeff;
} camera_intrinsics;

/*
 * Extrinsic parameters shared by camera and IMU.
 * All rotation + translation with respect to the same reference point
 */
typedef struct
{
	/*
	 * Rotation parameter expressed in Rodrigues notation
	 * angle = sqrt(rx^2+ry^2+rz^2)
	 * unit axis = [rx,ry,rz]/angle
	 */
	float rx, ry, rz;
	// Translation parameter from one camera to another parameter
	float tx, ty, tz;
} camera_extrinsics;

/*
 * IMU parameters used by HAWK 1.0. HAWK 1.0 did not have IMU noise model parameters
 * in EEPROM. To preserve backward compatibility with HAWK 1.0, the EEPROM data is arranged
 * in a certain way which requires tracking the imu noise model parameters in a
 * separate structure.
 */
typedef struct
{
	// 3D vector to add to accelerometer readings
	float linear_acceleration_bias[3];
	// 3D vector to add to gyroscope readings
	float angular_velocity_bias[3];
	// gravity acceleration
	float gravity_acceleration[3];
	// Extrinsic structure for IMU device
	camera_extrinsics extr;

} imu_params_v1;

typedef struct {
	/*
	 *Noise model parameters
	 */

	float update_rate;
	float linear_acceleration_noise_density;
	float linear_acceleration_random_walk;
	float angular_velocity_noise_density;
	float angular_velocity_random_walk;

} imu_params_noise_m;

/*
 * Combined IMU calibration data structure
 */
typedef struct {
	imu_params_v1 imu_data_v1;
	imu_params_noise_m nm;
} imu_params_v2;

typedef struct {
	// Image height
	u16 image_height;
	// Image width
	u16 image_width;
	// Number of color channels
	u8 n_channels;
	// Coordinate x of center point
	float rls_x0[MAX_RLS_COLOR_CHANNELS];
	// Coordinate y of center point
	float rls_y0[MAX_RLS_COLOR_CHANNELS];
	// Ellipse xx parameter
	double ekxx[MAX_RLS_COLOR_CHANNELS];
	// Ellipse xy parameter
	double ekxy[MAX_RLS_COLOR_CHANNELS];
	// Ellipse yx parameter
	double ekyx[MAX_RLS_COLOR_CHANNELS];
	// Ellipse yy parameter
	double ekyy[MAX_RLS_COLOR_CHANNELS];
	// Number of breakpoints in LSC radial transfer function
	u8 rls_n_points;
	// LSC radial transfer function X
	float rls_rad_tf_x[MAX_RLS_COLOR_CHANNELS][MAX_RLS_BREAKPOINTS];
	// LSC radial transfer function Y
	float rls_rad_tf_y[MAX_RLS_COLOR_CHANNELS][MAX_RLS_BREAKPOINTS];
	// LSC radial transfer function slope
	float rls_rad_tf_slope[MAX_RLS_COLOR_CHANNELS][MAX_RLS_BREAKPOINTS];
	// rScale parameter
	u8 r_scale;
} radial_lsc_params;

typedef struct
{
	// Intrinsic structure for  camera device
	camera_intrinsics cam_intr;

	// Extrinsic structure for camera device
	camera_extrinsics cam_extr;

	// Flag for IMU availability
	u8 imu_present;

	// Intrinsic structure for IMU
	imu_params_v2 imu;

	// HAWK module serial number
	u8 serial_number[CAMERA_MAX_SN_LENGTH];

	// Radial Lens Shading Correction parameters
	radial_lsc_params rls;
} NvCamSyncSensorCalibData;

typedef struct
{
	/**
	 * EEPROM layout version
	 */
	u32 version;
	/**
	 * Factory Blob Flag, to set when factory flashed and reset to 0
	 * when user modified
	 */
	u32 factory_data;
	/**
	 * Intrinsic structure for camera device
	 */
	camera_intrinsics left_cam_intr;
	camera_intrinsics right_cam_intr;
	/**
	 * Extrinsic structure for camera device
	 */
	camera_extrinsics cam_extr;
	/**
	 * Flag for IMU 0-absent, 1-present
	 */
	u8 imu_present;
	/**
	 * Intrinsic structure for IMU
	 */
	imu_params_v1 imu;

	u8 tmp[16];

	// HAWK module serial number
	u8 serial_number[LEOP_CAMERA_MAX_SN_LENGTH];

	imu_params_noise_m nm;

	// Radial Lens Shading Correction parameters
	radial_lsc_params left_rls;
	radial_lsc_params right_rls;
} LiEeprom_Content_Struct;

struct ar0234 {
	struct camera_common_eeprom_data eeprom[AR0234_EEPROM_NUM_BLOCKS];
	u8                              eeprom_buf[AR0234_EEPROM_SIZE];
	struct i2c_client	*i2c_client;
	const struct i2c_device_id *id;
	struct v4l2_subdev	*subdev;
	u32	frame_length;
	struct camera_common_data	*s_data;
	struct tegracam_device		*tc_dev;
	u32                             channel;
	u32 	sync_sensor_index;
	NvCamSyncSensorCalibData EepromCalib;
};

static const struct regmap_config sensor_regmap_config = {
	.reg_bits = 16,
	.val_bits = 16,
	.cache_type = REGCACHE_RBTREE,
};

static inline void ar0234_get_coarse_time_regs_shs1(ar0234_reg *regs,
		u16 coarse_time)
{
	regs->addr = AR0234_COARSE_TIME_SHS1_ADDR;
	regs->val = (coarse_time) & 0xffff;

}

static inline void ar0234_get_gain_reg(ar0234_reg *regs,
		u16 gain)
{
	regs->addr = AR0234_ANALOG_GAIN;
	regs->val = (gain) & 0xffff;

}

static inline void ar0234_compute_gain_reg(u16 *gain_reg,
		u16 *gain, s64 val)
{
	if (val < 200) {
		*gain_reg = (32 * (1000 - (100000 / *gain)))/1000;
	} else if (val < 400 && val >= 200) {
		*gain = *gain / 2;
		*gain_reg = (16 * (1000 - (100000 / *gain)))/1000 * 2;
		*gain_reg = *gain_reg + 0x10;
	} else if (val < 800 && val >= 400) {
		*gain = *gain / 4;
		*gain_reg = (32 * (1000 - (100000 / *gain)))/1000;
		*gain_reg = *gain_reg + 0x20;
	} else if (val < 1600 && val >= 800) {
		*gain = *gain / 8;
		*gain_reg = (16 * (1000 - (100000 / *gain)))/1000 * 2;
		*gain_reg = *gain_reg + 0x30;
	} else if (val >= 1600) {
		*gain_reg = 0x40;
	}

	if (*gain_reg > AR0234_MAX_GAIN_REG)
		*gain_reg = AR0234_MAX_GAIN_REG;
}

static int test_mode;
module_param(test_mode, int, 0644);

static inline int ar0234_read_reg(struct camera_common_data *s_data,
		u16 addr, u16 *val)
{
	int err = 0;
	u32 reg_val = 0;

	err = regmap_read(s_data->regmap, addr, &reg_val);
	*val = reg_val & 0xFFFF;

	return err;
}

static int ar0234_write_reg(struct camera_common_data *s_data,
		u16 addr, u16 val)
{
	int err;
	struct device *dev = s_data->dev;

	err = regmap_write(s_data->regmap, addr, val);
	if (err)
		dev_err(dev, "%s:i2c write failed, 0x%x = %x\n",
				__func__, addr, val);

	return err;
}

#if 0
static int ar0234_hawk_link_check(struct ar0234 *priv)
{
	struct tegracam_device *tc_dev = priv->tc_dev;
	struct device *dev = tc_dev->dev;
	unsigned int linkA = 0;
	unsigned int linkB = 0;

	dev_dbg(dev, "%s: channel %d, ", __func__, priv->channel);

	if (priv->channel == CHANNEL_N)
		return 3;

	max96712_read_reg_Dser(0x52, priv->channel, 0x1A, &linkA);
	max96712_read_reg_Dser(0x52, priv->channel, 0x0A, &linkB);

	dev_dbg(dev, "linA=%x, linB=%x\n", linkA, linkB);
	if ((linkB & 0x8) && (linkA & 0x8)) {
		return 2;
	} else if (linkA & 0x8) {
		return 1;
	} else {
		return 0;
	}

}
#endif

static int ar0234_write_table(struct ar0234 *priv,
		const struct index_reg_8 table[])
{
	struct tegracam_device *tc_dev = priv->tc_dev;
	struct device *dev = tc_dev->dev;
	int i = 0;
	int ret = 0;
	int retry = 5;

	dev_dbg(dev, "%s: channel %d, ", __func__, priv->channel);
	while (table[i].source != 0x00) {
		if (table[i].source == 0x06) {
			retry = 1;

			if (table[i].addr == AR0234_TABLE_WAIT_MS)
			{
				dev_dbg(dev, "%s: sleep 500\n", __func__);
				msleep(table[i].val);
				i++;
				continue;
			}
retry_sensor:
			ret = ar0234_write_reg(priv->s_data, table[i].addr, table[i].val);
			if (ret) {
				retry--;
				if (retry > 0) {
					dev_warn(dev, "ar0234_write_reg: try %d\n", retry);
					msleep(4);
					goto retry_sensor;
				}
				return -1;
			} else {
				if (0x301a == table[i].addr || 0x3060 == table[i].addr)
					msleep(100);
			}
		} else {
#if 0
			retry = 5;

			if (priv->channel == CHANNEL_N)
			{
				i++;
				continue;
			}
retry_serdes:
			ret = max96712_write_reg_Dser(table[i].source, priv->channel, table[i].addr, (u8)table[i].val);
			if (ret && (table[i].addr != 0x0000))
			{
				retry--;
				if (retry > 0) {
					dev_warn(dev, "max96712_write_reg_Dser: try %d\n", retry);
					msleep(4);
					goto retry_serdes;
				}
				return -1;
			}
			if (0x0010 == table[i].addr || 0x0000 == table[i].addr || 0x0006 == table[i].addr || 0x0018 == table[i].addr)
				msleep(300);
			else
				msleep(10);
#endif
		}
		i++;
	}
	return 0;
}

static int ar0234_power_on(struct camera_common_data *s_data)
{
	int err = 0;
	struct camera_common_power_rail *pw = s_data->power;
	struct camera_common_pdata *pdata = s_data->pdata;
	struct device *dev = s_data->dev;

	dev_dbg(dev, "%s: power on\n", __func__);
	if (pdata && pdata->power_on) {
		err = pdata->power_on(pw);
		if (err)
			dev_err(dev, "%s failed.\n", __func__);
		else
			pw->state = SWITCH_ON;
		return err;
	}

	if (gpio_is_valid(pw->reset_gpio > 0))
		gpio_set_value(pw->reset_gpio, 1);

	usleep_range(1000, 2000);
	if (gpio_is_valid(pw->reset_gpio > 0))
		gpio_set_value(pw->reset_gpio, 1);

	usleep_range(10000, 20000);
	pw->state = SWITCH_ON;

	return 0;
}

static int ar0234_power_off(struct camera_common_data *s_data)
{
	int err = 0;
	struct camera_common_power_rail *pw = s_data->power;
	struct camera_common_pdata *pdata = s_data->pdata;
	struct device *dev = s_data->dev;

	dev_dbg(dev, "%s:\n", __func__);
	if (pdata && pdata->power_off) {
		err = pdata->power_off(pw);
		if (!err)
			goto power_off_done;
		else
			dev_err(dev, "%s failed.\n", __func__);
		return err;
	}

power_off_done:
	pw->state = SWITCH_OFF;

	return 0;
}

static int ar0234_power_get(struct tegracam_device *tc_dev)
{
	struct device *dev = tc_dev->dev;
	struct camera_common_data *s_data = tc_dev->s_data;
	struct camera_common_power_rail *pw = s_data->power;
	struct camera_common_pdata *pdata = s_data->pdata;
	const char *mclk_name;
	const char *parentclk_name;
	struct clk *parent;
	int err = 0;

	mclk_name = pdata->mclk_name ?
		pdata->mclk_name : "cam_mclk1";
	pw->mclk = devm_clk_get(dev, mclk_name);
	if (IS_ERR(pw->mclk)) {
		dev_err(dev, "unable to get clock %s\n", mclk_name);
		return PTR_ERR(pw->mclk);
	}

	parentclk_name = pdata->parentclk_name;
	if (parentclk_name) {
		parent = devm_clk_get(dev, parentclk_name);
		if (IS_ERR(parent)) {
			dev_err(dev, "unable to get parent clcok %s",
					parentclk_name);
		} else
			clk_set_parent(pw->mclk, parent);
	}
	if (!err) {
		pw->reset_gpio = pdata->reset_gpio;
		pw->af_gpio = pdata->af_gpio;
		pw->pwdn_gpio = pdata->pwdn_gpio;
	}

	pw->state = SWITCH_OFF;

	return err;
}

static int ar0234_power_put(struct tegracam_device *tc_dev)
{
	struct camera_common_data *s_data = tc_dev->s_data;
	struct camera_common_power_rail *pw = s_data->power;

	if (unlikely(!pw))
		return -EFAULT;

	return 0;
}

static int ar0234_set_group_hold(struct tegracam_device *tc_dev, bool val)
{
	struct device *dev = tc_dev->dev;
	int err = 0;

	if (err) {
		dev_dbg(dev,
				"%s: Group hold control error\n", __func__);
		return err;
	}

	return 0;
}

static int ar0234_set_gain(struct tegracam_device *tc_dev, s64 val)
{
	struct camera_common_data *s_data = tc_dev->s_data;
	struct device *dev = tc_dev->dev;
	ar0234_reg reg_list[1];
	int err;
	u16 gain = (u16)val;
	u16 gain_reg = 0;

	ar0234_compute_gain_reg(&gain_reg, &gain, val);

	ar0234_get_gain_reg(reg_list, gain_reg);
	err = ar0234_write_reg(s_data, reg_list[0].addr,
			reg_list[0].val);
	if (err)
		goto fail;
	return 0;

fail:
	dev_info(dev, "%s: GAIN control error\n", __func__);
	return err;
}

static int ar0234_set_frame_rate(struct tegracam_device *tc_dev, s64 val)
{
	struct ar0234 *priv = (struct ar0234 *)tegracam_get_privdata(tc_dev);

	if (val == 30000000) {  //30fps full resolution
#if 0
		max96712_write_reg_Dser(0x52, priv->channel, 0x04A5, 0x35);
		max96712_write_reg_Dser(0x52, priv->channel, 0x04A6, 0xB7);
		max96712_write_reg_Dser(0x52, priv->channel, 0x04A7, 0x0C);
#endif
		priv->frame_length = 0xC20;
	} else if (val == 60000000) {//60fps full resolution
#if 0
		max96712_write_reg_Dser(0x52, priv->channel, 0x04A5, 0x9A);
		max96712_write_reg_Dser(0x52, priv->channel, 0x04A6, 0x5B);
		max96712_write_reg_Dser(0x52, priv->channel, 0x04A7, 0x06);
#endif
		priv->frame_length = 0x610;
	} else if (val == 120000000) {//120fps binning resolution
#if 0
		max96712_write_reg_Dser(0x52, priv->channel, 0x04A5, 0xCD);
		max96712_write_reg_Dser(0x52, priv->channel, 0x04A6, 0x2D);
		max96712_write_reg_Dser(0x52, priv->channel, 0x04A7, 0x03);
#endif
		priv->frame_length = 0x308;
	}

	return 0;
}

static int ar0234_set_exposure(struct tegracam_device *tc_dev, s64 val)
{
	struct ar0234 *priv = (struct ar0234 *)tegracam_get_privdata(tc_dev);
	struct camera_common_data *s_data = tc_dev->s_data;
	const struct sensor_mode_properties *mode =
		&s_data->sensor_props.sensor_modes[s_data->mode];
	ar0234_reg reg_list[1];
	int err;
	u32 coarse_time;
	u32 shs1;

	if (priv->frame_length == 0)
		priv->frame_length = AR0234_DEFAULT_FRAME_LENGTH;


	coarse_time = mode->signal_properties.pixel_clock.val *
		val / mode->image_properties.line_length /
		mode->control_properties.exposure_factor;

	if (coarse_time > priv->frame_length)
		coarse_time = priv->frame_length;
	shs1 = coarse_time;
	/* 0 and 1 are prohibited */
	if (shs1 < 2)
		shs1 = 2;

	ar0234_get_coarse_time_regs_shs1(reg_list, shs1);
	err = ar0234_write_reg(priv->s_data, reg_list[0].addr,
			reg_list[0].val);
	if (err)
		goto fail;

	return 0;

fail:
	dev_dbg(&priv->i2c_client->dev,
			"%s: set coarse time error\n", __func__);
	return err;
}

static int ar0234_fill_string_ctrl(struct tegracam_device *tc_dev,
		struct v4l2_ctrl *ctrl)
{
	struct ar0234 *priv = tc_dev->priv;
	int i, ret;

	switch (ctrl->id) {
		case TEGRA_CAMERA_CID_EEPROM_DATA:
			for (i = 0; i < AR0234_EEPROM_SIZE; i++) {
				ret = sprintf(&ctrl->p_new.p_char[i*2], "%02x",
						priv->eeprom_buf[i]);
				if (ret < 0)
					return -EINVAL;
			}
			break;
		default:
			return -EINVAL;
	}
	ctrl->p_cur.p_char = ctrl->p_new.p_char;

	return 0;
}

static int ar0234_fill_eeprom(struct tegracam_device *tc_dev,
				struct v4l2_ctrl *ctrl)
{
	struct ar0234 *priv = tc_dev->priv;
	LiEeprom_Content_Struct tmp;
	u32 test = 0;

	switch (ctrl->id) {
		case TEGRA_CAMERA_CID_STEREO_EEPROM:
			memset(&(priv->EepromCalib), 0, sizeof(NvCamSyncSensorCalibData));
			memset(ctrl->p_new.p, 0, sizeof(NvCamSyncSensorCalibData));
			memcpy(&tmp, priv->eeprom_buf, sizeof(LiEeprom_Content_Struct));

			if (priv->sync_sensor_index == 1) {
				priv->EepromCalib.cam_intr =  tmp.left_cam_intr;
			} else if (priv->sync_sensor_index == 2) {
				priv->EepromCalib.cam_intr =  tmp.right_cam_intr;
			} else {
				priv->EepromCalib.cam_intr =  tmp.left_cam_intr;
			}
			priv->EepromCalib.cam_extr = tmp.cam_extr;
			priv->EepromCalib.imu_present = tmp.imu_present;
			priv->EepromCalib.imu.imu_data_v1 = tmp.imu;
			priv->EepromCalib.imu.nm = tmp.nm;
			memcpy(priv->EepromCalib.serial_number, tmp.serial_number, 8);
			if (priv->sync_sensor_index == 1)
				priv->EepromCalib.rls = tmp.left_rls;
			else if (priv->sync_sensor_index == 2)
				priv->EepromCalib.rls = tmp.right_rls;
			else
				priv->EepromCalib.rls = tmp.left_rls;

			memcpy(ctrl->p_new.p, (u8 *)&(priv->EepromCalib), sizeof(NvCamSyncSensorCalibData));
			break;
		default:
			return -EINVAL;
	}

	memcpy(&test, &(priv->EepromCalib.cam_intr.fx), 4);

	ctrl->p_cur.p = ctrl->p_new.p;

	return 0;
}

static int ar0234_set_alternating_exposure(struct tegracam_device *tc_dev,
	struct alternating_exposure_cfg *cfg)
{
	struct ar0234 *priv = (struct ar0234 *)tegracam_get_privdata(tc_dev);
	struct camera_common_data *s_data = tc_dev->s_data;
	const struct sensor_mode_properties *mode =
		&s_data->sensor_props.sensor_modes[s_data->mode];
	int err, ii;
	const int num_contexts = 2;
	u32 coarse_time_values[2];
	u16 gain_reg_values[2];
	u16 gain_values[2];

	struct index_reg_8 table_alt_exp_enable[] = {
		{0x06, AR0234_CTX_CONTROL_REG_ADDR, 0x0000}, // reset address pointer
		// set 1 (1+1) contexts in bits 7-4, set MSB 0x3 of CIT register in bits 3-0
		{0x06, AR0234_CTX_WR_DATA_REG_ADDR, 0xF813},
		{0x06, AR0234_CTX_WR_DATA_REG_ADDR, 0x0809}, // set address x3012, CIT
		{0x06, AR0234_CTX_WR_DATA_REG_ADDR, 0x0100}, // context 0 exposure time
		{0x06, AR0234_CTX_WR_DATA_REG_ADDR, 0x0200}, // context 1 exposure time
		// set 1 (1+1) contexts in bits 7-4, set MSB 0x3 of analog gain register in bits 3-0
		{0x06, AR0234_CTX_WR_DATA_REG_ADDR, 0xF813},
		{0x06, AR0234_CTX_WR_DATA_REG_ADDR, 0x0830}, // set address x3060, analog gain
		{0x06, AR0234_CTX_WR_DATA_REG_ADDR, 0x0010}, // context 0 gain
		{0x06, AR0234_CTX_WR_DATA_REG_ADDR, 0x0010}, // context 1 gain
		{0x06, AR0234_CTX_WR_DATA_REG_ADDR, 0x0000}, // end of code

		{0x06, AR0234_CTX_CONTROL_REG_ADDR, 0x0220}, // stop auto cycling
		{0x06, AR0234_TABLE_WAIT_MS, 100},
		{0x06, AR0234_CTX_CONTROL_REG_ADDR, 0x02A0}, // start auto cycling two contexts

		{0x00, AR0234_TABLE_END, 0x00},
	};

	struct index_reg_8 table_alt_exp_disable[] = {
		{0x06, AR0234_CTX_CONTROL_REG_ADDR, 0x0000}, // reset address pointer
		{0x06, AR0234_CTX_CONTROL_REG_ADDR, 0x0220}, // stop auto cycling
		{0x06, AR0234_TABLE_WAIT_MS, 100},

		{0x00, AR0234_TABLE_END, 0x00},
	};

	if (priv->frame_length == 0)
		priv->frame_length = AR0234_DEFAULT_FRAME_LENGTH;

	coarse_time_values[0] = mode->signal_properties.pixel_clock.val *
		cfg->exp_time_0 / mode->image_properties.line_length /
		mode->control_properties.exposure_factor;
	coarse_time_values[1] = mode->signal_properties.pixel_clock.val *
		cfg->exp_time_1 / mode->image_properties.line_length /
		mode->control_properties.exposure_factor;

	/* 0 and 1 are prohibited */
	for (ii = 0; ii < num_contexts; ii++)
		if (coarse_time_values[ii] < 2)
			coarse_time_values[ii] = 2;

	gain_values[0] = (u16)(cfg->analog_gain_0);
	gain_values[1] = (u16)(cfg->analog_gain_1);
	for (ii = 0; ii < 2; ii++)
		gain_reg_values[ii] = 0;

	ar0234_compute_gain_reg(&gain_reg_values[0], &gain_values[0], cfg->analog_gain_0);
	ar0234_compute_gain_reg(&gain_reg_values[1], &gain_values[1], cfg->analog_gain_1);

	table_alt_exp_enable[3].val = coarse_time_values[0] & 0xffff;
	table_alt_exp_enable[4].val = coarse_time_values[1] & 0xffff;
	table_alt_exp_enable[7].val = gain_reg_values[0] & 0xffff;
	table_alt_exp_enable[8].val = gain_reg_values[1] & 0xffff;

	if (cfg->enable == false)
		err = ar0234_write_table(priv, table_alt_exp_disable);
	else
		err = ar0234_write_table(priv, table_alt_exp_enable);
	if (err)
		goto fail;

	return 0;

fail:
	dev_dbg(&priv->i2c_client->dev,
			"%s: set alternating exposure error\n", __func__);
	return err;
}

static struct tegracam_ctrl_ops ar0234_ctrl_ops = {
	.numctrls = ARRAY_SIZE(ctrl_cid_list),
	.ctrl_cid_list = ctrl_cid_list,
	.string_ctrl_size = {AR0234_EEPROM_STR_SIZE},
	.compound_ctrl_size = {sizeof(NvCamSyncSensorCalibData), alternating_exposure_cfg_size},
	.set_gain = ar0234_set_gain,
	.set_exposure = ar0234_set_exposure,
	.set_exposure_short = ar0234_set_exposure,
	.set_frame_rate = ar0234_set_frame_rate,
	.set_group_hold = ar0234_set_group_hold,
	.set_alternating_exposure = ar0234_set_alternating_exposure,
	.fill_string_ctrl = ar0234_fill_string_ctrl,
	.fill_compound_ctrl = ar0234_fill_eeprom,
};

static struct camera_common_pdata *ar0234_parse_dt(struct tegracam_device *tc_dev)
{
	struct device *dev = tc_dev->dev;
	struct device_node *node = dev->of_node;
	struct camera_common_pdata *board_priv_pdata;
	const struct of_device_id *match;
	int err;
	int gpio = 0;

	if (!node)
		return NULL;

	match = of_match_device(ar0234_of_match, dev);
	if (!match) {
		dev_err(dev, "Failed to find matching dt id\n");
		return NULL;
	}

	board_priv_pdata = devm_kzalloc(dev, sizeof(*board_priv_pdata), GFP_KERNEL);

	err = of_property_read_string(node, "mclk",
			&board_priv_pdata->mclk_name);
	if (err)
		dev_err(dev, "mclk not in DT\n");

	board_priv_pdata->reset_gpio = of_get_named_gpio(node,
			"reset-gpios", 0);
	gpio_direction_output(board_priv_pdata->reset_gpio, 1);

	gpio = of_get_named_gpio(node,
			"pwdn-gpios", 0);

	gpio_direction_output(gpio, 1);
	gpio = of_get_named_gpio(node,
			"pwr-gpios", 0);

	gpio_direction_output(gpio, 1);

	board_priv_pdata->has_eeprom =
		of_property_read_bool(node, "has-eeprom");
	return board_priv_pdata;
}

static int ar0234_set_mode(struct tegracam_device *tc_dev)
{
	struct ar0234 *priv = (struct ar0234 *)tegracam_get_privdata(tc_dev);
	struct camera_common_data *s_data = tc_dev->s_data;
	struct device *dev = tc_dev->dev;
	const struct of_device_id *match;
	int err;

	match = of_match_device(ar0234_of_match, dev);
	if (!match) {
		dev_err(dev, "Failed to find matching dt id\n");
		return -EINVAL;
	}

	err = ar0234_write_table(priv, mode_table[AR0234_MODE_STOP_STREAM]);
	if (err)
		return err;

	if (s_data->mode_prop_idx < 0)
		return -EINVAL;
	dev_dbg(dev, "%s: mode index:%d\n", __func__,s_data->mode_prop_idx);
	err = ar0234_write_table(priv, mode_table[s_data->mode_prop_idx]);
	if (err)
		return err;

	return 0;
}

static int ar0234_start_streaming(struct tegracam_device *tc_dev)
{
	struct ar0234 *priv = (struct ar0234 *)tegracam_get_privdata(tc_dev);
	int err;

	if (test_mode) {
		err = ar0234_write_table(priv,
				mode_table[AR0234_MODE_TEST_PATTERN]);
		if (err)
			return err;
	}


	err = ar0234_write_table(priv,
			mode_table[AR0234_MODE_START_STREAM]);
	if (err)
		return err;

	return 0;

}

static int ar0234_stop_streaming(struct tegracam_device *tc_dev)
{
	struct ar0234 *priv = (struct ar0234 *)tegracam_get_privdata(tc_dev);
	int err;

	err = ar0234_write_table(priv, mode_table[AR0234_MODE_STOP_STREAM]);
	if (err)
		return err;

	return 0;
}

static struct camera_common_sensor_ops ar0234_common_ops = {
	.numfrmfmts = ARRAY_SIZE(ar0234_frmfmt),
	.frmfmt_table = ar0234_frmfmt,
	.power_on = ar0234_power_on,
	.power_off = ar0234_power_off,
	.parse_dt = ar0234_parse_dt,
	.power_get = ar0234_power_get,
	.power_put = ar0234_power_put,
	.set_mode = ar0234_set_mode,
	.start_streaming = ar0234_start_streaming,
	.stop_streaming = ar0234_stop_streaming,
};

static int ar0234_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
{
	struct i2c_client *client = v4l2_get_subdevdata(sd);

	dev_dbg(&client->dev, "%s:\n", __func__);

	return 0;
}

static int ar0234_eeprom_device_release(struct ar0234 *priv)
{
	int i;

	for (i = 0; i < AR0234_EEPROM_NUM_BLOCKS; i++) {
		if (priv->eeprom[i].i2c_client != NULL) {
			i2c_unregister_device(priv->eeprom[i].i2c_client);
			priv->eeprom[i].i2c_client = NULL;
		}
	}

	return 0;
}

static const struct v4l2_subdev_internal_ops ar0234_subdev_internal_ops = {
	.open = ar0234_open,
};


static int ar0234_eeprom_device_init(struct ar0234 *priv)
{
	struct camera_common_pdata *pdata =  priv->s_data->pdata;
	char *dev_name = "eeprom_ar0234";
	static struct regmap_config eeprom_regmap_config = {
		.reg_bits = 8,
		.val_bits = 8
	};
	int i;
	int err;

	if (!pdata->has_eeprom)
		return -EINVAL;

	for (i = 0; i < AR0234_EEPROM_NUM_BLOCKS; i++) {
		priv->eeprom[i].adap = i2c_get_adapter(
				priv->i2c_client->adapter->nr);
		memset(&priv->eeprom[i].brd, 0, sizeof(priv->eeprom[i].brd));
		strncpy(priv->eeprom[i].brd.type, dev_name,
				sizeof(priv->eeprom[i].brd.type));

		if (priv->sync_sensor_index == 1)
			priv->eeprom[i].brd.addr = AR0234_EEPROM_ADDRESS + i;
		else if (priv->sync_sensor_index == 2)
			priv->eeprom[i].brd.addr = AR0234_EEPROM_ADDRESS_R + i;

#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)
		priv->eeprom[i].i2c_client = i2c_new_device(
				priv->eeprom[i].adap, &priv->eeprom[i].brd);
#else
		priv->eeprom[i].i2c_client = i2c_new_client_device(
				priv->eeprom[i].adap, &priv->eeprom[i].brd);
#endif
		priv->eeprom[i].regmap = devm_regmap_init_i2c(
				priv->eeprom[i].i2c_client, &eeprom_regmap_config);
		if (IS_ERR(priv->eeprom[i].regmap)) {
			err = PTR_ERR(priv->eeprom[i].regmap);
			ar0234_eeprom_device_release(priv);
			return err;
		}
	}

	return 0;
}

static int ar0234_read_eeprom(struct ar0234 *priv)
{
	int err, i;

	for (i = 0; i < AR0234_EEPROM_NUM_BLOCKS; i++) {
		err = regmap_bulk_read(priv->eeprom[i].regmap, 0,
				&priv->eeprom_buf[i * AR0234_EEPROM_BLOCK_SIZE],
				AR0234_EEPROM_BLOCK_SIZE);
		if (err)
			return err;
	}

	return 0;
}

static int ar0234_board_setup(struct ar0234 *priv)
{
	struct camera_common_data *s_data = priv->s_data;
	struct device *dev = s_data->dev;
	bool eeprom_ctrl = 0;
	int err = 0;

	dev_dbg(dev, "%s++\n", __func__);

	/* eeprom interface */
	err = ar0234_eeprom_device_init(priv);
	if (err && s_data->pdata->has_eeprom)
		dev_err(dev,
				"Failed to allocate eeprom reg map: %d\n", err);
	eeprom_ctrl = !err;

	err = camera_common_mclk_enable(s_data);
	if (err) {
		dev_err(dev,
				"Error %d turning on mclk\n", err);
		return err;
	}

	err = ar0234_power_on(s_data);
	if (err) {
		dev_err(dev,
				"Error %d during power on sensor\n", err);
		return err;
	}
	if (eeprom_ctrl) {
		err = ar0234_read_eeprom(priv);
		if (err) {
			dev_err(dev,
					"Error %d reading eeprom\n", err);
			goto error;
		}
	}

error:
	ar0234_power_off(s_data);
	camera_common_mclk_disable(s_data);
	return err;
}



static int ar0234_probe(struct i2c_client *client,
		const struct i2c_device_id *id)
{
	struct device *dev = &client->dev;
	struct device_node *node = dev->of_node;
	struct tegracam_device *tc_dev;
	struct ar0234 *priv;
	const char *str;
	int err;

	dev_info(dev, "probing v4l2 sensor.\n");

	if (!IS_ENABLED(CONFIG_OF) || !node)
		return -EINVAL;

	priv = devm_kzalloc(dev, sizeof(struct ar0234), GFP_KERNEL);
	if (!priv) {
		dev_err(dev, "unable to allocate memory!\n");
		return -ENOMEM;
	}
	tc_dev = devm_kzalloc(dev,
			sizeof(struct tegracam_device), GFP_KERNEL);
	if (!tc_dev)
		return -ENOMEM;

	err = of_property_read_string(node, "channel", &str);
	if (err)
		dev_err(dev, "channel not found\n");
	priv->channel = str[0] - 'a';
	dev_dbg(dev, "%s: channel %d\n", __func__, priv->channel);


	err = of_property_read_u32(node, "sync_sensor_index",
			&priv->sync_sensor_index);
	if (err)
		 dev_err(dev, "sync name index not in DT\n");

	priv->i2c_client = tc_dev->client = client;
	tc_dev->dev = dev;
	strncpy(tc_dev->name, "ar0234", sizeof(tc_dev->name));
	tc_dev->dev_regmap_config = &sensor_regmap_config;
	tc_dev->sensor_ops = &ar0234_common_ops;
	tc_dev->v4l2sd_internal_ops = &ar0234_subdev_internal_ops;
	tc_dev->tcctrl_ops = &ar0234_ctrl_ops;

	err = tegracam_device_register(tc_dev);
	if (err) {
		dev_err(dev, "tegra camera driver registration failed\n");
		return err;
	}
	priv->tc_dev = tc_dev;
	priv->s_data = tc_dev->s_data;
	priv->subdev = &tc_dev->s_data->subdev;
	tegracam_set_privdata(tc_dev, (void *)priv);

	ar0234_power_on(tc_dev->s_data);
	msleep(100);
#if 0
	if (ar0234_hawk_link_check(priv) == 2) {
		err = ar0234_write_table(priv, mode_table[AR0234_MODE_Dser_Ser]);
		if (err) {
			dev_info(&client->dev,"dual camera detect error\n");
			return err;
		} else {
			dev_info(&client->dev,"dual camera detect success\n");
		}
	} else if (ar0234_hawk_link_check(priv) == 1) {
		err = ar0234_write_table(priv, mode_table[AR0234_MODE_Single_Dser_Ser]);
		if (err) {
			dev_info(&client->dev,"single detect error\n");
			return err;
		} else {
			dev_info(&client->dev,"single detect success\n");
		}
	} else if (ar0234_hawk_link_check(priv) == 3) {
		;
	} else {
		return -1;
	}
#endif

	msleep(100);
	err = ar0234_board_setup(priv);
	if (err) {
		dev_err(dev, "board setup failed\n");
		return err;
	}

	err = ar0234_write_table(priv, mode_table[AR0234_MODE_STOP_STREAM]);
	if (err) {
		dev_info(&client->dev,"ar0234 detect error\n");
		return err;
	}

	err = tegracam_v4l2subdev_register(tc_dev, true);
	if (err) {
		dev_err(dev, "tegra camera subdev registration failed\n");
		return err;
	}

	dev_info(&client->dev, "Detected AR0234 sensor\n");

	return 0;
}

static int ar0234_remove(struct i2c_client *client)
{
	struct camera_common_data *s_data = to_camera_common_data(&client->dev);
	struct ar0234 *priv = (struct ar0234 *)s_data->priv;

	tegracam_v4l2subdev_unregister(priv->tc_dev);
	tegracam_device_unregister(priv->tc_dev);
	ar0234_eeprom_device_release(priv);

	return 0;
}

static const struct i2c_device_id ar0234_id[] = {
	{ "ar0234", 0 },
	{ }
};

MODULE_DEVICE_TABLE(i2c, ar0234_id);

static struct i2c_driver ar0234_i2c_driver = {
	.driver = {
		.name = "ar0234",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(ar0234_of_match),
	},
	.probe = ar0234_probe,
	.remove = ar0234_remove,
	.id_table = ar0234_id,
};

module_i2c_driver(ar0234_i2c_driver);

MODULE_DESCRIPTION("Media Controller driver for Sony AR0234");
MODULE_AUTHOR("NVIDIA Corporation");
MODULE_AUTHOR("Sudhir Vyas <svyas@nvidia.com");
MODULE_LICENSE("GPL v2");

However, I cannot probe any waveform from the MCLK pin.

Most of the available examples have the similar clock configuration but are for camera modules with on-board oscillator such as the Raspberry Pi IMX219, IMX477, …

I also found another discussion about similar issue : Enabling extperiph1/2 output - #5 by maxe777

Could you tell what was the missing configuration for generating 24MHz MCLK so that the camera sensor can work and communicate via I2C channel, please?

Thanks in advance and best regards,
Khang

Hello,

I did not check in details, but I spotted in your dts:

clocks = <&bpmp_clks TEGRA234_CLK_EXTPERIPH1>,
						<&bpmp_clks TEGRA234_CLK_EXTPERIPH1>;

while for the second clock, the name is pllp_grtba.

I’m not sure, but I would say the second clock should be the parent clock, and not repeat the EXTPERIPH1.

You can also check outside of your drive by using sysfs command under:

/sys/kernel/debug/bpmp/debug/clk/extperiph1/

You need to set the rate, the parent clock and the state to 1. The clock should come out.
The sysfs entry may help you to check if you face a HW issue or a driver/dts problem.

1 Like

Hi @maxe777,

I copied that clock configuration from the <jetpack5.1.2-r35.4.1-bsp>/hardware/nvidia/platform/t23x/concord/kernel-dts/cvb/tegra234-p3737-0000-camera-ar0234-a00.dtsi which seems to be the config of the Leopard Imaging 's LI-AR0234CS-STEREO-GMSL2-30 camera.

Anyway, I will replace the second (parent) clock with TEGRA234_CLK_PLLP_OUT0 for example.

Best,
Khang

hello khang.l4es,

IMX477 skips mclk enable as this camera has an internal oscillator.
however, it’s defined in pinmux,
as you can see… $public_sources/kernel_src/hardware/nvidia/platform/t23x/p3768/bct/pinmux/tegra234-mb1-bct-pinmux-p3767-dp-a03.dtsi

                        extperiph1_clk_pp0 {
                                nvidia,pins = "extperiph1_clk_pp0";
                                nvidia,function = "extperiph1";

                        extperiph2_clk_pp1 {
                                nvidia,pins = "extperiph2_clk_pp1";
                                nvidia,function = "extperiph2";

Hi @maxe777 and @JerryChang ,

I checked both cases :

  1. Keep TEGRA234_CLK_EXTPERIPH1 for pllp_grtba :
            ar0234_a: ar0234_a@10 {
                status = "okay";
				/* Define any required hw resources needed by driver */
				/* ie. clocks, io pins, power sources */
				clocks = <&bpmp_clks TEGRA234_CLK_EXTPERIPH1>,
						<&bpmp_clks TEGRA234_CLK_EXTPERIPH1>;
				clock-names = "extperiph1", "pllp_grtba";
				clock-frequency = <24000000>;
				mclk = "extperiph1";
				...
            };
  1. Replace TEGRA234_CLK_EXTPERIPH1 with TEGRA234_CLK_PLLP_OUT0 for pllp_grtba
            ar0234_a: ar0234_a@10 {
                status = "okay";
				/* Define any required hw resources needed by driver */
				/* ie. clocks, io pins, power sources */
				clocks = <&bpmp_clks TEGRA234_CLK_EXTPERIPH1>,
						<&bpmp_clks TEGRA234_CLK_PLLP_OUT0>;
				clock-names = "extperiph1", "pllp_grtba";
				clock-frequency = <24000000>;
				mclk = "extperiph1";
				...
            };

In both cases, I could not see the 24MHz clock on the relevant MCLK pin unless I enabled the state with :

cat /sys/kernel/debug/bpmp/debug/clk/extperiph1/state 
0
echo 1 > /sys/kernel/debug/bpmp/debug/clk/extperiph1/state 

I do not understand with the driver does not enable the clock with the calling of camera_common_mclk_enable() within ar0234_board_setup()ar0234_probe()
Best,
Khang

Hi again,

I investigated a little bit further and found that above clock & mclk properties are parsed in <public_sources/kernel_src>/kernel/nvidia/drivers/media/platform/tegra/camera/camera_common.c::camera_common_parse_clocks() which is in turn called uniquely in <public_sources/kernel_src>/kernel/nvidia/drivers/media/i2c/nv_ov5693.c::ov5693_parse_dt()

I modified the <public_sources/kernel_src>/kernel/nvidia/drivers/media/i2c/nv_ar0234.c::ar0234_parse_dt() similarly :

@ -837,6 +850,7 @@ static struct camera_common_pdata *ar0234_parse_dt(struct tegracam_device *tc_de
        const struct of_device_id *match;
        int err;
        int gpio = 0;
 
        if (!node)
                return NULL;
@@ -848,28 +862,62 @@ static struct camera_common_pdata *ar0234_parse_dt(struct tegracam_device *tc_de
        }
 
        board_priv_pdata = devm_kzalloc(dev, sizeof(*board_priv_pdata), GFP_KERNEL);
+       if (!board_priv_pdata)
+               return NULL;
 
-       err = of_property_read_string(node, "mclk",
-                       &board_priv_pdata->mclk_name);
-       if (err)
-               dev_err(dev, "mclk not in DT\n");
+       err = camera_common_parse_clocks(dev,
+                       board_priv_pdata);
+       if (err) {
+               dev_err(dev, "Failed to find clocks\n");
+               goto error;
+       }

I also modified my .dtsi in inspiring the <public_sources/kernel_src>/hardware/nvidia/platform/t23x/prometheus/kernel-dts/cvb/tegra234-p3740-0000-camera-e3333-a00.dtsi :

 ar0234_a: ar0234_a@10 {
                status = "okay";
				/* Define any required hw resources needed by driver */
				/* ie. clocks, io pins, power sources */
				clocks = <&bpmp_clks TEGRA234_CLK_EXTPERIPH1>,
						<&bpmp_clks TEGRA234_CLK_PLLP_OUT0>;
				clock-names = "extperiph1", "pllp_grtba";
				clock-frequency = <24000000>;
				mclk = "extperiph1";
				...
            };
            ...
            ar0234_c: ar0234_c@10 {
                status = "okay";
				/* Define any required hw resources needed by driver */
				/* ie. clocks, io pins, power sources */
				clocks = <&bpmp_clks TEGRA234_CLK_EXTPERIPH2>,
						<&bpmp_clks TEGRA234_CLK_PLLP_OUT0>;
				clock-names = "extperiph2", "pllp_grtba";
				clock-frequency = <24000000>;
				mclk = "extperiph2";
				...
            };

but it was still the same, unfortunately.

Khang.

hello khang.l4es,

reference camera boards they’re using I2C Mux.
taking IMX274 as an exmaple,
IMX274/Cam-0 → CSI-A/B → I2C@0x1a → I2C Mux by TCA9546@0x70 → MCLK02_CAM0_MCLK as mclk.

hence…
its device tree settings looks like below.

	i2c@3180000 {
 		tca9546@70 {
 			i2c@0 {
 				imx274_a@1a {
 					/* Define any required hw resources needed by driver */
 					/* ie. clocks, io pins, power sources */
 					clocks = <&bpmp_clks TEGRA234_CLK_EXTPERIPH1>,
 							 <&bpmp_clks TEGRA234_CLK_EXTPERIPH1>;
 					clock-names = "extperiph1", "pllp_grtba";
 					mclk = "extperiph1";

you may also note that, those clock property settings were coming from…
$public_sources/kernel_src/hardware/nvidia/soc/t23x/kernel-include/dt-bindings/clock/tegra234-clk.h


anyways, is the camera works by manual control?
did you tried enable regulators and control mclk manually to enable camera stream?
for instance,
To turn-on/off mclk:
# echo 1 > /sys/kernel/debug/bpmp/debug/clk/extperiph1/state
# echo 0 > /sys/kernel/debug/bpmp/debug/clk/extperiph1/state
To power-on/off 2v8 regulator:
# echo enable > /sys/kernel/debug/regulator/avdd-cam-2v8/regulator/state
# echo disable > /sys/kernel/debug/regulator/avdd-cam-2v8/regulator/state

Hi @JerryChang ,

I’m confused as I found MCLK02_CAM0_MCLK nowhere. But in the following device-trees of imx274 :
hardware/nvidia/platform/t23x/concord/kernel-dts/cvb/tegra234-p3737-0000-camera-imx274-dual.dtsi
hardware/nvidia/platform/t23x/prometheus/kernel-dts/cvb/tegra234-p3740-0000-camera-imx274-dual.dtsi

same clock configuration applied for both cameras and similar to my initial configuration :

                                        /* Define any required hw resources needed by driver */
                                        /* ie. clocks, io pins, power sources */
                                        clocks = <&bpmp_clks TEGRA234_CLK_EXTPERIPH1>,
                                                         <&bpmp_clks TEGRA234_CLK_EXTPERIPH1>;
                                        clock-names = "extperiph1", "pllp_grtba";
                                        mclk = "extperiph1";

anyways, is the camera works by manual control?
did you tried enable regulators and control mclk manually to enable camera stream?

With the manual control of mclk ( echo 1 > /sys/kernel/debug/bpmp/debug/clk/extperiph1/state), the 24MHz waveform appeared and the camera was probed successfully as it passed the very first I2C register writing (stream-off) in order to confirm the communication.

I haven’t tested the streaming yet,

Regards,
Khang