From 04dd17822334871b23ea2862f7798fb0e0007777 Mon Sep 17 00:00:00 2001
From: hc <hc@nodka.com>
Date: Sat, 11 May 2024 08:53:19 +0000
Subject: [PATCH] change otg to host mode

---
 kernel/drivers/media/i2c/lt6911uxc.c |  445 ++++++++++++++++++++++++++++++++++++++++++-------------
 1 files changed, 339 insertions(+), 106 deletions(-)

diff --git a/kernel/drivers/media/i2c/lt6911uxc.c b/kernel/drivers/media/i2c/lt6911uxc.c
index 88ce0ca..dc30a14 100644
--- a/kernel/drivers/media/i2c/lt6911uxc.c
+++ b/kernel/drivers/media/i2c/lt6911uxc.c
@@ -3,6 +3,11 @@
  * Copyright (c) 2021 Rockchip Electronics Co. Ltd.
  *
  * Author: Dingxian Wen <shawn.wen@rock-chips.com>
+ * V0.0X01.0X00 first version.
+ * V0.0X01.0X01 fix if plugin_gpio was not used.
+ * V0.0X01.0X02 modify driver init level to late_initcall.
+ * V0.0X01.0X03 add 4K60 dual mipi support
+ *
  */
 
 #include <linux/clk.h>
@@ -15,12 +20,14 @@
 #include <linux/module.h>
 #include <linux/of_graph.h>
 #include <linux/rk-camera-module.h>
+#include <linux/rk_hdmirx_class.h>
 #include <linux/slab.h>
 #include <linux/timer.h>
 #include <linux/v4l2-dv-timings.h>
 #include <linux/version.h>
 #include <linux/videodev2.h>
 #include <linux/workqueue.h>
+#include <linux/compat.h>
 #include <media/v4l2-controls_rockchip.h>
 #include <media/v4l2-ctrls.h>
 #include <media/v4l2-device.h>
@@ -29,22 +36,36 @@
 #include <media/v4l2-fwnode.h>
 #include "lt6911uxc.h"
 
-#define DRIVER_VERSION			KERNEL_VERSION(0, 0x01, 0x0)
+#define DRIVER_VERSION			KERNEL_VERSION(0, 0x01, 0x3)
 #define LT6911UXC_NAME			"LT6911UXC"
 
-#define LT6911UXC_LINK_FREQ_HIGH	400000000
-#define LT6911UXC_LINK_FREQ_LOW		200000000
-#define LT6911UXC_PIXEL_RATE		400000000
+#define LT6911UXC_LINK_FREQ_650M	650000000
+#define LT6911UXC_LINK_FREQ_400M	400000000
+#define LT6911UXC_LINK_FREQ_300M	300000000
+#define LT6911UXC_LINK_FREQ_200M	200000000
+#define LT6911UXC_LINK_FREQ_100M	100000000
+#define LT6911UXC_LINK_FREQ_60M		60000000
+#define LT6911UXC_PIXEL_RATE		600000000
 
 #define I2C_MAX_XFER_SIZE		128
+
+#ifdef LT6911UXC_OUT_RGB
+#define LT6911UXC_MEDIA_BUS_FMT		MEDIA_BUS_FMT_BGR888_1X24
+#else
+#define LT6911UXC_MEDIA_BUS_FMT		MEDIA_BUS_FMT_UYVY8_2X8
+#endif
 
 static int debug;
 module_param(debug, int, 0644);
 MODULE_PARM_DESC(debug, "debug level (0-2)");
 
 static const s64 link_freq_menu_items[] = {
-	LT6911UXC_LINK_FREQ_HIGH,
-	LT6911UXC_LINK_FREQ_LOW,
+	LT6911UXC_LINK_FREQ_650M,
+	LT6911UXC_LINK_FREQ_400M,
+	LT6911UXC_LINK_FREQ_300M,
+	LT6911UXC_LINK_FREQ_200M,
+	LT6911UXC_LINK_FREQ_100M,
+	LT6911UXC_LINK_FREQ_60M,
 };
 
 struct lt6911uxc {
@@ -67,6 +88,7 @@
 	struct v4l2_dv_timings timings;
 	struct v4l2_fwnode_bus_mipi_csi2 bus;
 	struct v4l2_subdev sd;
+	struct rkmodule_multi_dev_info multi_dev_info;
 	const char *len_name;
 	const char *module_facing;
 	const char *module_name;
@@ -79,6 +101,7 @@
 	u32 module_index;
 	u32 csi_lanes_in_use;
 	u32 audio_sampling_rate;
+	struct device *classdev;
 };
 
 struct lt6911uxc_mode {
@@ -88,13 +111,25 @@
 	u32 hts_def;
 	u32 vts_def;
 	u32 exp_def;
+	u32 mipi_freq_idx;
+};
+
+static struct rkmodule_csi_dphy_param rk3588_dcphy_param = {
+	.vendor = PHY_VENDOR_SAMSUNG,
+	.lp_vol_ref = 3,
+	.lp_hys_sw = {3, 0, 3, 0},
+	.lp_escclk_pol_sel = {1, 1, 0, 0},
+	.skew_data_cal_clk = {0, 0, 0, 0},
+	.clk_hs_term_sel = 2,
+	.data_hs_term_sel = {2, 2, 2, 2},
+	.reserved = {0},
 };
 
 static const struct v4l2_dv_timings_cap lt6911uxc_timings_cap = {
 	.type = V4L2_DV_BT_656_1120,
 	/* keep this initialization for compatibility with GCC < 4.4.6 */
 	.reserved = { 0 },
-	V4L2_INIT_BT_TIMINGS(1, 10000, 1, 10000, 0, 400000000,
+	V4L2_INIT_BT_TIMINGS(1, 10000, 1, 10000, 0, 600000000,
 			V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT |
 			V4L2_DV_BT_STD_GTF | V4L2_DV_BT_STD_CVT,
 			V4L2_DV_BT_CAP_PROGRESSIVE |
@@ -109,10 +144,21 @@
 		.height = 2160,
 		.max_fps = {
 			.numerator = 10000,
+			.denominator = 600000,
+		},
+		.hts_def = 4400,
+		.vts_def = 2250,
+		.mipi_freq_idx = 0,
+	}, {
+		.width = 3840,
+		.height = 2160,
+		.max_fps = {
+			.numerator = 10000,
 			.denominator = 300000,
 		},
 		.hts_def = 4400,
 		.vts_def = 2250,
+		.mipi_freq_idx = 0,
 	}, {
 		.width = 1920,
 		.height = 1080,
@@ -122,6 +168,7 @@
 		},
 		.hts_def = 2200,
 		.vts_def = 1125,
+		.mipi_freq_idx = 2,
 	}, {
 		.width = 1920,
 		.height = 540,
@@ -129,6 +176,7 @@
 			.numerator = 10000,
 			.denominator = 600000,
 		},
+		.mipi_freq_idx = 3,
 	}, {
 		.width = 1440,
 		.height = 240,
@@ -136,6 +184,7 @@
 			.numerator = 10000,
 			.denominator = 600000,
 		},
+		.mipi_freq_idx = 4,
 	}, {
 		.width = 1440,
 		.height = 288,
@@ -143,6 +192,7 @@
 			.numerator = 10000,
 			.denominator = 500000,
 		},
+		.mipi_freq_idx = 4,
 	}, {
 		.width = 1280,
 		.height = 720,
@@ -152,6 +202,7 @@
 		},
 		.hts_def = 1650,
 		.vts_def = 750,
+		.mipi_freq_idx = 3,
 	}, {
 		.width = 720,
 		.height = 576,
@@ -161,6 +212,7 @@
 		},
 		.hts_def = 864,
 		.vts_def = 625,
+		.mipi_freq_idx = 5,
 	}, {
 		.width = 720,
 		.height = 480,
@@ -170,6 +222,7 @@
 		},
 		.hts_def = 858,
 		.vts_def = 525,
+		.mipi_freq_idx = 5,
 	},
 };
 
@@ -286,14 +339,27 @@
 
 static inline bool tx_5v_power_present(struct v4l2_subdev *sd)
 {
-	int val;
+	bool ret;
+	int val, i, cnt;
 	struct lt6911uxc *lt6911uxc = to_state(sd);
 
-	val = gpiod_get_value(lt6911uxc->plugin_det_gpio);
-	v4l2_dbg(1, debug, sd, "%s plug det: %s!\n", __func__,
-			(val > 0) ? "int" : "out");
+	/* if not use plugin det gpio */
+	if (!lt6911uxc->plugin_det_gpio)
+		return true;
 
-	return  (val > 0);
+	cnt = 0;
+	for (i = 0; i < 5; i++) {
+		val = gpiod_get_value(lt6911uxc->plugin_det_gpio);
+
+		if (val > 0)
+			cnt++;
+		usleep_range(500, 600);
+	}
+
+	ret = (cnt >= 3) ? true : false;
+	v4l2_dbg(1, debug, sd, "%s: %d\n", __func__, ret);
+
+	return ret;
 }
 
 static inline bool no_signal(struct v4l2_subdev *sd)
@@ -365,6 +431,7 @@
 	u8 value, val_h, val_l;
 	u32 fw_ver, mipi_byte_clk, mipi_bitrate;
 	u8 fw_a, fw_b, fw_c, fw_d, lanes;
+	u8 video_fmt;
 	int ret;
 
 	memset(timings, 0, sizeof(struct v4l2_dv_timings));
@@ -396,13 +463,15 @@
 
 	i2c_rd8(sd, MIPI_LANES, &lanes);
 	lt6911uxc->csi_lanes_in_use = lanes;
+	if (lt6911uxc->csi_lanes_in_use == 8)
+		v4l2_info(sd, "get 8 lane in use, set dual mipi mode\n");
 	i2c_wr8(sd, FM1_DET_CLK_SRC_SEL, AD_LMTX_WRITE_CLK);
 	i2c_rd8(sd, FREQ_METER_H, &clk_h);
 	i2c_rd8(sd, FREQ_METER_M, &clk_m);
 	i2c_rd8(sd, FREQ_METER_L, &clk_l);
 	mipi_byte_clk = (((clk_h & 0xf) << 16) | (clk_m << 8) | clk_l);
 	mipi_bitrate = mipi_byte_clk * 8 / 1000;
-	v4l2_info(sd, "MIPI Byte clk: %dKHz, MIPI bitrate: %dMbps, lanes:%d\n",
+	v4l2_info(sd, "MIPI Byte clk: %uKHz, MIPI bitrate: %uMbps, lanes:%d\n",
 			mipi_byte_clk, mipi_bitrate, lanes);
 
 	i2c_rd8(sd, HTOTAL_H, &val_h);
@@ -432,7 +501,15 @@
 	hbp = ((val_h << 8) | val_l) * 2;
 	i2c_rd8(sd, VBP, &value);
 	vbp = value;
+	i2c_rd8(sd, COLOR_FMT_STATUS, &video_fmt);
+	video_fmt = (video_fmt & GENMASK(6, 5)) >> 5;
 	lt6911uxc_i2c_disable(sd);
+
+	if (video_fmt == 0x3) {
+		lt6911uxc->nosignal = true;
+		v4l2_err(sd, "%s ERROR: HDMI input YUV420, don't support YUV420!\n", __func__);
+		return -EINVAL;
+	}
 
 	if (!lt6911uxc_rcv_supported_res(sd, hact, vact)) {
 		lt6911uxc->nosignal = true;
@@ -505,6 +582,8 @@
 	struct v4l2_subdev *sd = &lt6911uxc->sd;
 
 	v4l2_dbg(2, debug, sd, "%s:\n", __func__);
+
+	v4l2_ctrl_s_ctrl(lt6911uxc->detect_tx_5v_ctrl, tx_5v_power_present(sd));
 	lt6911uxc_config_hpd(sd);
 }
 
@@ -589,21 +668,6 @@
 		v4l2_subdev_notify_event(sd, &lt6911uxc_ev_fmt);
 }
 
-static int lt6911uxc_get_ctrl(struct v4l2_ctrl *ctrl)
-{
-	int ret = -1;
-	struct lt6911uxc *lt6911uxc = container_of(ctrl->handler,
-			struct lt6911uxc, hdl);
-	struct v4l2_subdev *sd = &(lt6911uxc->sd);
-
-	if (ctrl->id == V4L2_CID_DV_RX_POWER_PRESENT) {
-		ret = tx_5v_power_present(sd);
-		*ctrl->p_new.p_s32 = ret;
-	}
-
-	return ret;
-}
-
 static int lt6911uxc_isr(struct v4l2_subdev *sd, u32 status, bool *handled)
 {
 	struct lt6911uxc *lt6911uxc = to_state(sd);
@@ -682,7 +746,6 @@
 	}
 
 	lt6911uxc->timings = *timings;
-
 	enable_stream(sd, false);
 
 	return 0;
@@ -738,12 +801,12 @@
 	return 0;
 }
 
-static int lt6911uxc_g_mbus_config(struct v4l2_subdev *sd,
+static int lt6911uxc_g_mbus_config(struct v4l2_subdev *sd, unsigned int pad,
 			     struct v4l2_mbus_config *cfg)
 {
 	struct lt6911uxc *lt6911uxc = to_state(sd);
 
-	cfg->type = V4L2_MBUS_CSI2;
+	cfg->type = V4L2_MBUS_CSI2_DPHY;
 	cfg->flags = V4L2_MBUS_CSI2_CONTINUOUS_CLOCK | V4L2_MBUS_CSI2_CHANNEL_0;
 
 	switch (lt6911uxc->csi_lanes_in_use) {
@@ -759,7 +822,9 @@
 	case 4:
 		cfg->flags |= V4L2_MBUS_CSI2_4_LANE;
 		break;
-
+	case 8:
+		cfg->flags |= V4L2_MBUS_CSI2_4_LANE;
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -780,7 +845,7 @@
 {
 	switch (code->index) {
 	case 0:
-		code->code = MEDIA_BUS_FMT_UYVY8_2X8;
+		code->code = LT6911UXC_MEDIA_BUS_FMT;
 		break;
 
 	default:
@@ -797,7 +862,7 @@
 	if (fse->index >= ARRAY_SIZE(supported_modes))
 		return -EINVAL;
 
-	if (fse->code != MEDIA_BUS_FMT_UYVY8_2X8)
+	if (fse->code != LT6911UXC_MEDIA_BUS_FMT)
 		return -EINVAL;
 
 	fse->min_width  = supported_modes[fse->index].width;
@@ -815,8 +880,7 @@
 	if (fie->index >= ARRAY_SIZE(supported_modes))
 		return -EINVAL;
 
-	if (fie->code != MEDIA_BUS_FMT_UYVY8_2X8)
-		return -EINVAL;
+	fie->code = LT6911UXC_MEDIA_BUS_FMT;
 
 	fie->width = supported_modes[fie->index].width;
 	fie->height = supported_modes[fie->index].height;
@@ -825,11 +889,50 @@
 	return 0;
 }
 
+static int lt6911uxc_get_reso_dist(const struct lt6911uxc_mode *mode,
+		struct v4l2_dv_timings *timings)
+{
+	struct v4l2_bt_timings *bt = &timings->bt;
+	u32 cur_fps, dist_fps;
+
+	cur_fps = fps_calc(bt);
+	dist_fps = DIV_ROUND_CLOSEST(mode->max_fps.denominator, mode->max_fps.numerator);
+
+	return abs(mode->width - bt->width) +
+		abs(mode->height - bt->height) + abs(dist_fps - cur_fps);
+}
+
+static const struct lt6911uxc_mode *
+lt6911uxc_find_best_fit(struct lt6911uxc *lt6911uxc)
+{
+	int dist;
+	int cur_best_fit = 0;
+	int cur_best_fit_dist = -1;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(supported_modes); i++) {
+		dist = lt6911uxc_get_reso_dist(&supported_modes[i], &lt6911uxc->timings);
+		if (cur_best_fit_dist == -1 || dist < cur_best_fit_dist) {
+			cur_best_fit_dist = dist;
+			cur_best_fit = i;
+		}
+	}
+	dev_info(&lt6911uxc->i2c_client->dev,
+		"find current mode: support_mode[%d], %dx%d@%dfps\n",
+		cur_best_fit, supported_modes[cur_best_fit].width,
+		supported_modes[cur_best_fit].height,
+		DIV_ROUND_CLOSEST(supported_modes[cur_best_fit].max_fps.denominator,
+		supported_modes[cur_best_fit].max_fps.numerator));
+
+	return &supported_modes[cur_best_fit];
+}
+
 static int lt6911uxc_get_fmt(struct v4l2_subdev *sd,
 		struct v4l2_subdev_pad_config *cfg,
 		struct v4l2_subdev_format *format)
 {
 	struct lt6911uxc *lt6911uxc = to_state(sd);
+	const struct lt6911uxc_mode *mode;
 
 	mutex_lock(&lt6911uxc->confctl_mutex);
 	format->format.code = lt6911uxc->mbus_fmt_code;
@@ -839,6 +942,14 @@
 		lt6911uxc->timings.bt.interlaced ?
 		V4L2_FIELD_INTERLACED : V4L2_FIELD_NONE;
 	format->format.colorspace = V4L2_COLORSPACE_SRGB;
+
+	mode = lt6911uxc_find_best_fit(lt6911uxc);
+	lt6911uxc->cur_mode = mode;
+	__v4l2_ctrl_s_ctrl_int64(lt6911uxc->pixel_rate,
+				LT6911UXC_PIXEL_RATE);
+	__v4l2_ctrl_s_ctrl(lt6911uxc->link_freq,
+				mode->mipi_freq_idx);
+
 	mutex_unlock(&lt6911uxc->confctl_mutex);
 
 	v4l2_dbg(1, debug, sd, "%s: fmt code:%d, w:%d, h:%d, field mode:%s\n",
@@ -848,40 +959,12 @@
 	return 0;
 }
 
-static int lt6911uxc_get_reso_dist(const struct lt6911uxc_mode *mode,
-		struct v4l2_mbus_framefmt *framefmt)
-{
-	return abs(mode->width - framefmt->width) +
-	       abs(mode->height - framefmt->height);
-}
-
-static const struct lt6911uxc_mode *
-lt6911uxc_find_best_fit(struct v4l2_subdev_format *fmt)
-{
-	struct v4l2_mbus_framefmt *framefmt = &fmt->format;
-	int dist;
-	int cur_best_fit = 0;
-	int cur_best_fit_dist = -1;
-	unsigned int i;
-
-	for (i = 0; i < ARRAY_SIZE(supported_modes); i++) {
-		dist = lt6911uxc_get_reso_dist(&supported_modes[i], framefmt);
-		if (cur_best_fit_dist == -1 || dist < cur_best_fit_dist) {
-			cur_best_fit_dist = dist;
-			cur_best_fit = i;
-		}
-	}
-
-	return &supported_modes[cur_best_fit];
-}
-
 static int lt6911uxc_set_fmt(struct v4l2_subdev *sd,
 		struct v4l2_subdev_pad_config *cfg,
 		struct v4l2_subdev_format *format)
 {
 	struct lt6911uxc *lt6911uxc = to_state(sd);
 	const struct lt6911uxc_mode *mode;
-	int index;
 
 	/* is overwritten by get_fmt */
 	u32 code = format->format.code;
@@ -893,7 +976,7 @@
 		return ret;
 
 	switch (code) {
-	case MEDIA_BUS_FMT_UYVY8_2X8:
+	case LT6911UXC_MEDIA_BUS_FMT:
 		break;
 
 	default:
@@ -904,19 +987,9 @@
 		return 0;
 
 	lt6911uxc->mbus_fmt_code = format->format.code;
-	mode = lt6911uxc_find_best_fit(format);
+	mode = lt6911uxc_find_best_fit(lt6911uxc);
 	lt6911uxc->cur_mode = mode;
 	enable_stream(sd, false);
-
-	if (((mode->width == 720) && (mode->height == 576)) ||
-	    ((mode->width == 720) && (mode->height == 480)))
-		index = 1;
-	else
-		index = 0;
-
-	__v4l2_ctrl_s_ctrl(lt6911uxc->link_freq, index);
-	v4l2_dbg(1, debug, sd, "%s res wxh:%dx%d, link freq:%llu", __func__,
-			mode->width, mode->height, link_freq_menu_items[index]);
 
 	return 0;
 }
@@ -946,11 +1019,40 @@
 static long lt6911uxc_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
 {
 	struct lt6911uxc *lt6911uxc = to_state(sd);
+	struct device *dev = &lt6911uxc->i2c_client->dev;
 	long ret = 0;
+	struct rkmodule_csi_dphy_param *dphy_param;
+	struct rkmodule_capture_info  *capture_info;
 
 	switch (cmd) {
 	case RKMODULE_GET_MODULE_INFO:
 		lt6911uxc_get_module_inf(lt6911uxc, (struct rkmodule_inf *)arg);
+		break;
+	case RKMODULE_GET_HDMI_MODE:
+		*(int *)arg = RKMODULE_HDMIIN_MODE;
+		break;
+	case RKMODULE_SET_CSI_DPHY_PARAM:
+		dphy_param = (struct rkmodule_csi_dphy_param *)arg;
+		if (dphy_param->vendor == PHY_VENDOR_SAMSUNG)
+			rk3588_dcphy_param = *dphy_param;
+		dev_dbg(&lt6911uxc->i2c_client->dev,
+			"sensor set dphy param\n");
+		break;
+	case RKMODULE_GET_CSI_DPHY_PARAM:
+		dphy_param = (struct rkmodule_csi_dphy_param *)arg;
+		*dphy_param = rk3588_dcphy_param;
+		dev_dbg(&lt6911uxc->i2c_client->dev,
+			"sensor get dphy param\n");
+		break;
+	case RKMODULE_GET_CAPTURE_MODE:
+		capture_info = (struct rkmodule_capture_info *)arg;
+		if (lt6911uxc->csi_lanes_in_use == 8) {
+			dev_info(dev, "8 lanes in use, set dual mipi mode\n");
+			capture_info->mode = RKMODULE_MULTI_DEV_COMBINE_ONE;
+			capture_info->multi_dev = lt6911uxc->multi_dev_info;
+		} else {
+			capture_info->mode = 0;
+		}
 		break;
 	default:
 		ret = -ENOIOCTLCMD;
@@ -967,6 +1069,9 @@
 	void __user *up = compat_ptr(arg);
 	struct rkmodule_inf *inf;
 	long ret;
+	int *seq;
+	struct rkmodule_csi_dphy_param *dphy_param;
+	struct rkmodule_capture_info  *capture_info;
 
 	switch (cmd) {
 	case RKMODULE_GET_MODULE_INFO:
@@ -984,7 +1089,65 @@
 		}
 		kfree(inf);
 		break;
+	case RKMODULE_GET_HDMI_MODE:
+		seq = kzalloc(sizeof(*seq), GFP_KERNEL);
+		if (!seq) {
+			ret = -ENOMEM;
+			return ret;
+		}
 
+		ret = lt6911uxc_ioctl(sd, cmd, seq);
+		if (!ret) {
+			ret = copy_to_user(up, seq, sizeof(*seq));
+			if (ret)
+				ret = -EFAULT;
+		}
+		kfree(seq);
+		break;
+	case RKMODULE_SET_CSI_DPHY_PARAM:
+		dphy_param = kzalloc(sizeof(*dphy_param), GFP_KERNEL);
+		if (!dphy_param) {
+			ret = -ENOMEM;
+			return ret;
+		}
+
+		ret = copy_from_user(dphy_param, up, sizeof(*dphy_param));
+		if (!ret)
+			ret = lt6911uxc_ioctl(sd, cmd, dphy_param);
+		else
+			ret = -EFAULT;
+		kfree(dphy_param);
+		break;
+	case RKMODULE_GET_CSI_DPHY_PARAM:
+		dphy_param = kzalloc(sizeof(*dphy_param), GFP_KERNEL);
+		if (!dphy_param) {
+			ret = -ENOMEM;
+			return ret;
+		}
+
+		ret = lt6911uxc_ioctl(sd, cmd, dphy_param);
+		if (!ret) {
+			ret = copy_to_user(up, dphy_param, sizeof(*dphy_param));
+			if (ret)
+				ret = -EFAULT;
+		}
+		kfree(dphy_param);
+		break;
+	case RKMODULE_GET_CAPTURE_MODE:
+		capture_info = kzalloc(sizeof(*capture_info), GFP_KERNEL);
+		if (!capture_info) {
+			ret = -ENOMEM;
+			return ret;
+		}
+
+		ret = lt6911uxc_ioctl(sd, cmd, capture_info);
+		if (!ret) {
+			ret = copy_to_user(up, capture_info, sizeof(*capture_info));
+			if (ret)
+				ret = -EFAULT;
+		}
+		kfree(capture_info);
+		break;
 	default:
 		ret = -ENOIOCTLCMD;
 		break;
@@ -993,10 +1156,6 @@
 	return ret;
 }
 #endif
-
-static const struct v4l2_ctrl_ops lt6911uxc_ctrl_ops = {
-	.g_volatile_ctrl = lt6911uxc_get_ctrl,
-};
 
 static const struct v4l2_subdev_core_ops lt6911uxc_core_ops = {
 	.interrupt_service_routine = lt6911uxc_isr,
@@ -1013,7 +1172,6 @@
 	.s_dv_timings = lt6911uxc_s_dv_timings,
 	.g_dv_timings = lt6911uxc_g_dv_timings,
 	.query_dv_timings = lt6911uxc_query_dv_timings,
-	.g_mbus_config = lt6911uxc_g_mbus_config,
 	.s_stream = lt6911uxc_s_stream,
 	.g_frame_interval = lt6911uxc_g_frame_interval,
 };
@@ -1026,6 +1184,7 @@
 	.get_fmt = lt6911uxc_get_fmt,
 	.enum_dv_timings = lt6911uxc_enum_dv_timings,
 	.dv_timings_cap = lt6911uxc_dv_timings_cap,
+	.get_mbus_config = lt6911uxc_g_mbus_config,
 };
 
 static const struct v4l2_subdev_ops lt6911uxc_ops = {
@@ -1068,9 +1227,11 @@
 
 static int lt6911uxc_init_v4l2_ctrls(struct lt6911uxc *lt6911uxc)
 {
+	const struct lt6911uxc_mode *mode;
 	struct v4l2_subdev *sd;
 	int ret;
 
+	mode = lt6911uxc->cur_mode;
 	sd = &lt6911uxc->sd;
 	ret = v4l2_ctrl_handler_init(&lt6911uxc->hdl, 5);
 	if (ret)
@@ -1080,14 +1241,13 @@
 			V4L2_CID_LINK_FREQ,
 			ARRAY_SIZE(link_freq_menu_items) - 1, 0,
 			link_freq_menu_items);
-	v4l2_ctrl_new_std(&lt6911uxc->hdl, NULL, V4L2_CID_PIXEL_RATE,
-			  0, LT6911UXC_PIXEL_RATE, 1, LT6911UXC_PIXEL_RATE);
+	lt6911uxc->pixel_rate = v4l2_ctrl_new_std(&lt6911uxc->hdl, NULL,
+			V4L2_CID_PIXEL_RATE,
+			0, LT6911UXC_PIXEL_RATE, 1, LT6911UXC_PIXEL_RATE);
 
 	lt6911uxc->detect_tx_5v_ctrl = v4l2_ctrl_new_std(&lt6911uxc->hdl,
-			&lt6911uxc_ctrl_ops, V4L2_CID_DV_RX_POWER_PRESENT,
+			NULL, V4L2_CID_DV_RX_POWER_PRESENT,
 			0, 1, 0, 0);
-	if (lt6911uxc->detect_tx_5v_ctrl)
-		lt6911uxc->detect_tx_5v_ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
 
 	lt6911uxc->audio_sampling_rate_ctrl =
 		v4l2_ctrl_new_custom(&lt6911uxc->hdl,
@@ -1101,6 +1261,9 @@
 		v4l2_err(sd, "cfg v4l2 ctrls failed! ret:%d\n", ret);
 		return ret;
 	}
+
+	__v4l2_ctrl_s_ctrl(lt6911uxc->link_freq, mode->mipi_freq_idx);
+	__v4l2_ctrl_s_ctrl_int64(lt6911uxc->pixel_rate, LT6911UXC_PIXEL_RATE);
 
 	if (lt6911uxc_update_controls(sd)) {
 		ret = -ENODEV;
@@ -1153,7 +1316,7 @@
 {
 	struct device *dev = &lt6911uxc->i2c_client->dev;
 	struct device_node *node = dev->of_node;
-	struct v4l2_fwnode_endpoint *endpoint;
+	struct v4l2_fwnode_endpoint endpoint = { .bus_type = 0 };
 	struct device_node *ep;
 	int ret;
 
@@ -1209,15 +1372,14 @@
 		return ret;
 	}
 
-	endpoint = v4l2_fwnode_endpoint_alloc_parse(of_fwnode_handle(ep));
-	if (IS_ERR(endpoint)) {
+	ret = v4l2_fwnode_endpoint_alloc_parse(of_fwnode_handle(ep), &endpoint);
+	if (ret) {
 		dev_err(dev, "failed to parse endpoint\n");
-		ret = PTR_ERR(endpoint);
-		return ret;
+		goto put_node;
 	}
 
-	if (endpoint->bus_type != V4L2_MBUS_CSI2 ||
-			endpoint->bus.mipi_csi2.num_data_lanes == 0) {
+	if (endpoint.bus_type != V4L2_MBUS_CSI2_DPHY ||
+			endpoint.bus.mipi_csi2.num_data_lanes == 0) {
 		dev_err(dev, "missing CSI-2 properties in endpoint\n");
 		ret = -EINVAL;
 		goto free_endpoint;
@@ -1236,8 +1398,8 @@
 		goto free_endpoint;
 	}
 
-	lt6911uxc->csi_lanes_in_use = endpoint->bus.mipi_csi2.num_data_lanes;
-	lt6911uxc->bus = endpoint->bus.mipi_csi2;
+	lt6911uxc->csi_lanes_in_use = endpoint.bus.mipi_csi2.num_data_lanes;
+	lt6911uxc->bus = endpoint.bus.mipi_csi2;
 	lt6911uxc->enable_hdcp = false;
 
 	gpiod_set_value(lt6911uxc->hpd_ctl_gpio, 0);
@@ -1247,7 +1409,9 @@
 	ret = 0;
 
 free_endpoint:
-	v4l2_fwnode_endpoint_free(endpoint);
+	v4l2_fwnode_endpoint_free(&endpoint);
+put_node:
+	of_node_put(ep);
 	return ret;
 }
 #else
@@ -1257,9 +1421,69 @@
 }
 #endif
 
+static ssize_t audio_rate_show(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct lt6911uxc *lt6911uxc = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE, "%d", lt6911uxc->audio_sampling_rate);
+}
+
+static ssize_t audio_present_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct lt6911uxc *lt6911uxc = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE, "%d",
+			tx_5v_power_present(&lt6911uxc->sd) ?
+			lt6911uxc->is_audio_present : 0);
+}
+
+static DEVICE_ATTR_RO(audio_rate);
+static DEVICE_ATTR_RO(audio_present);
+
+static struct attribute *lt6911_attrs[] = {
+	&dev_attr_audio_rate.attr,
+	&dev_attr_audio_present.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(lt6911);
+
+static int lt6911uxc_get_multi_dev_info(struct lt6911uxc *lt6911uxc)
+{
+	struct device *dev = &lt6911uxc->i2c_client->dev;
+	struct device_node *node = dev->of_node;
+	struct device_node *multi_info_np;
+
+	multi_info_np = of_get_child_by_name(node, "multi-dev-info");
+	if (!multi_info_np) {
+		dev_info(dev, "failed to get multi dev info\n");
+		return -EINVAL;
+	}
+
+	of_property_read_u32(multi_info_np, "dev-idx-l",
+			&lt6911uxc->multi_dev_info.dev_idx[0]);
+	of_property_read_u32(multi_info_np, "dev-idx-r",
+			&lt6911uxc->multi_dev_info.dev_idx[1]);
+	of_property_read_u32(multi_info_np, "combine-idx",
+			&lt6911uxc->multi_dev_info.combine_idx[0]);
+	of_property_read_u32(multi_info_np, "pixel-offset",
+			&lt6911uxc->multi_dev_info.pixel_offset);
+	of_property_read_u32(multi_info_np, "dev-num",
+			&lt6911uxc->multi_dev_info.dev_num);
+	dev_info(dev,
+		"multi dev left: mipi%d, multi dev right: mipi%d, combile mipi%d, dev num: %d\n",
+		lt6911uxc->multi_dev_info.dev_idx[0], lt6911uxc->multi_dev_info.dev_idx[1],
+		lt6911uxc->multi_dev_info.combine_idx[0], lt6911uxc->multi_dev_info.dev_num);
+
+	return 0;
+}
+
 static int lt6911uxc_probe(struct i2c_client *client,
 		const struct i2c_device_id *id)
 {
+	struct v4l2_dv_timings default_timing =
+				V4L2_DV_BT_CEA_640X480P59_94;
 	struct lt6911uxc *lt6911uxc;
 	struct v4l2_subdev *sd;
 	struct device *dev = &client->dev;
@@ -1277,14 +1501,19 @@
 
 	sd = &lt6911uxc->sd;
 	lt6911uxc->i2c_client = client;
+	lt6911uxc->timings = default_timing;
 	lt6911uxc->cur_mode = &supported_modes[0];
-	lt6911uxc->mbus_fmt_code = MEDIA_BUS_FMT_UYVY8_2X8;
+	lt6911uxc->mbus_fmt_code = LT6911UXC_MEDIA_BUS_FMT;
 
 	err = lt6911uxc_parse_of(lt6911uxc);
 	if (err) {
 		v4l2_err(sd, "lt6911uxc_parse_of failed! err:%d\n", err);
 		return err;
 	}
+
+	err = lt6911uxc_get_multi_dev_info(lt6911uxc);
+	if (err)
+		v4l2_info(sd, "get multi dev info failed, not use dual mipi mode\n");
 
 	err = lt6911uxc_check_chip_id(lt6911uxc);
 	if (err < 0)
@@ -1330,6 +1559,14 @@
 		goto err_clean_entity;
 	}
 
+	lt6911uxc->classdev = device_create_with_groups(rk_hdmirx_class(),
+							dev, MKDEV(0, 0),
+							lt6911uxc,
+							lt6911_groups,
+							"lt6911");
+	if (IS_ERR(lt6911uxc->classdev))
+		goto err_clean_entity;
+
 	INIT_DELAYED_WORK(&lt6911uxc->delayed_work_enable_hotplug,
 			lt6911uxc_delayed_work_enable_hotplug);
 	INIT_DELAYED_WORK(&lt6911uxc->delayed_work_res_change,
@@ -1353,20 +1590,15 @@
 	}
 
 	lt6911uxc->plugin_irq = gpiod_to_irq(lt6911uxc->plugin_det_gpio);
-	if (lt6911uxc->plugin_irq < 0) {
-		dev_err(dev, "failed to get plugin det irq\n");
-		err = lt6911uxc->plugin_irq;
-		goto err_work_queues;
-	}
+	if (lt6911uxc->plugin_irq < 0)
+		dev_err(dev, "failed to get plugin det irq, maybe no use\n");
 
 	err = devm_request_threaded_irq(dev, lt6911uxc->plugin_irq, NULL,
 			plugin_detect_irq_handler, IRQF_TRIGGER_FALLING |
 			IRQF_TRIGGER_RISING | IRQF_ONESHOT, "lt6911uxc",
 			lt6911uxc);
-	if (err) {
-		dev_err(dev, "failed to register plugin det irq (%d)\n", err);
-		goto err_work_queues;
-	}
+	if (err)
+		dev_err(dev, "failed to register plugin det irq (%d), maybe no use\n", err);
 
 	err = v4l2_ctrl_handler_setup(sd->ctrl_handler);
 	if (err) {
@@ -1444,4 +1676,5 @@
 
 MODULE_DESCRIPTION("Lontium LT6911UXC HDMI to MIPI CSI-2 bridge driver");
 MODULE_AUTHOR("Dingxian Wen <shawn.wen@rock-chips.com>");
+MODULE_AUTHOR("Jianwei Fan <jianwei.fan@rock-chips.com>");
 MODULE_LICENSE("GPL v2");

--
Gitblit v1.6.2