// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: Copyright (c) 2018-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
/*
 * ar0234.c - ar0234 sensor driver
 */

#include <nvidia/conftest.h>

#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>

#include <media/max9295.h>
#include <media/max9296.h>

#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

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);

#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,
};


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

// Coefficients as per distortion model being used
struct polynomial_lens_distortion_coeff {
	// 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];
};

/*
 * Stereo Eeprom Data
 */
struct camera_intrinsics {
	// 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 {
		struct polynomial_lens_distortion_coeff poly;
		struct fisheye_lens_distortion_coeff fisheye;
	} dist_coeff;
};

/*
 * Extrinsic parameters shared by camera and IMU.
 * All rotation + translation with respect to the same reference point
 */
struct camera_extrinsics {
	/*
	 * 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;
};

/*
 * 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.
 */
struct imu_params_v1 {
	// 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
	struct camera_extrinsics extr;
};

struct imu_params_noise_m {
	/*
	 * 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;
};

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

struct radial_lsc_params {
	// 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;
};

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

	// Extrinsic structure for camera device
	struct camera_extrinsics cam_extr;

	// Flag for IMU availability
	u8 imu_present;

	// Intrinsic structure for IMU
	struct imu_params_v2 imu;

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

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

struct LiEeprom_Content_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
	 */
	struct camera_intrinsics left_cam_intr;
	struct camera_intrinsics right_cam_intr;
	/**
	 * Extrinsic structure for camera device
	 */
	struct camera_extrinsics cam_extr;
	/**
	 * Flag for IMU 0-absent, 1-present
	 */
	u8 imu_present;
	/**
	 * Intrinsic structure for IMU
	 */
	struct imu_params_v1 imu;

	u8 tmp[16];

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

	struct imu_params_noise_m nm;

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

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;
	struct	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;
}

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;
}

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;
			}

			if (0x301a == table[i].addr || 0x3060 == table[i].addr)
				msleep(100);
		} else {
			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);
		}
		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 (pw->reset_gpio > 0)
		gpio_set_value(pw->reset_gpio, 1);

	usleep_range(1000, 2000);
	if (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 {
			err = clk_set_parent(pw->mclk, parent);
			if (err < 0)
				dev_dbg(dev,
					"%s failed to set parent clock %d\n",
					__func__, err);
		}
	}
	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
		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);
		priv->frame_length = 0xC20;
	} else if (val == 60000000) {//60fps full resolution
		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);
		priv->frame_length = 0x610;
	} else if (val == 120000000) {//120fps binning resolution
		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);
		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;

	if (mode->image_properties.line_length == 0 ||
		mode->control_properties.exposure_factor == 0)
		return -EINVAL;

	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;
	struct LiEeprom_Content_Struct *tmp;
	u32 test = 0;

	switch (ctrl->id) {
	case TEGRA_CAMERA_CID_STEREO_EEPROM:
		if (AR0234_EEPROM_SIZE >
			sizeof(struct LiEeprom_Content_Struct))
			return -EINVAL;

		tmp = kmalloc(sizeof(struct LiEeprom_Content_Struct),
				GFP_KERNEL);
		if (tmp == NULL)
			return -ENOMEM;

		memset(&(priv->EepromCalib), 0,
				sizeof(struct NvCamSyncSensorCalibData));
		memset(ctrl->p_new.p, 0,
				sizeof(struct NvCamSyncSensorCalibData));
		memcpy(tmp, priv->eeprom_buf, AR0234_EEPROM_SIZE);

		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(struct NvCamSyncSensorCalibData));

		kfree(tmp);
		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(struct NvCamSyncSensorCalibData), sizeof(struct alternating_exposure_cfg)},
	.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;

		priv->eeprom[i].i2c_client = i2c_new_client_device(
				priv->eeprom[i].adap, &priv->eeprom[i].brd);
		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);
	}

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



#if defined(NV_I2C_DRIVER_STRUCT_PROBE_WITHOUT_I2C_DEVICE_ID_ARG) /* Linux 6.3 */
static int ar0234_probe(struct i2c_client *client)
#else
static int ar0234_probe(struct i2c_client *client,
		const struct i2c_device_id *id)
#endif
{
	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)
		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 (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;
		}
		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;
		}
		dev_info(&client->dev, "single detect success\n");
	} else if (ar0234_hawk_link_check(priv) == 3) {
		;
	} else {
		return -1;
	}

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

	msleep(100);
	err = ar0234_board_setup(priv);
	if (err) {
		dev_err(dev, "board setup failed\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;
}

#if defined(NV_I2C_DRIVER_STRUCT_REMOVE_RETURN_TYPE_INT) /* Linux 6.1 */
static int ar0234_remove(struct i2c_client *client)
#else
static void ar0234_remove(struct i2c_client *client)
#endif
{
	struct camera_common_data *s_data = to_camera_common_data(&client->dev);
	struct ar0234 *priv;

	if (!s_data)
#if defined(NV_I2C_DRIVER_STRUCT_REMOVE_RETURN_TYPE_INT) /* Linux 6.1 */
		return -EINVAL;
#else
		return;
#endif

	priv = (struct ar0234 *)s_data->priv;
	tegracam_v4l2subdev_unregister(priv->tc_dev);
	tegracam_device_unregister(priv->tc_dev);
	ar0234_eeprom_device_release(priv);
#if defined(NV_I2C_DRIVER_STRUCT_REMOVE_RETURN_TYPE_INT) /* Linux 6.1 */
	return 0;
#endif
}

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");
