嵌入式Linux下tc358743的驱动设计

tc358743HDMI 输入流转换为 MIPI CSI-2 TX 的芯片。

先来看tc358743的特性介绍介绍:

 可以看出tc358743的功能还是很强大的。

下面看看如何实现tc358743的驱动。

1.修改设备树。

可以参考官方提供的文档。

Documentation/devicetree/bindings/media/video-interfaces.txt。

tc358743包含以下属性:

必要属性:

  • compatible:值应为"toshiba,tc358743"
  • clocks,clock-names:应包含指向参考时钟源的phandle链接,输入时钟命名为"refclk"。

可选属性:

  • reset-gpios:连接到复位引脚的GPIO phandle GPIO
  • interrupts:连接到中断引脚的GPIO
  • data-lanes:对于四通道操作,应为<1 2 3 4>,对于双通道操作,应为<1 2>
  • clock-lanes:应为<0>
  • clock-noncontinuous:具有此布尔属性表示MIPI CSI-2时钟是否连续或非连续。
  • link-frequencies:允许的链路频率列表(单位:赫兹)。每个频率表示为64位大端整数。由于DDR传输,频率是每条传输线的bps的一半。

示例:

tc358743@f {
    compatible = "toshiba,tc358743";
    reg = <0x0f>;
    clocks = <&hdmi_osc>;
    clock-names = "refclk";
    reset-gpios = <&gpio6 9 GPIO_ACTIVE_LOW>;
    /*interrupt-parent = <&gpio2>;*/
    /*interrupts = <5 IRQ_TYPE_LEVEL_HIGH>;*/

    port {
        tc358743_out: endpoint {
            remote-endpoint = <&mipi_csi2_in>;
            data-lanes = <1 2 3 4>;
            clock-lanes = <0>;
            clock-noncontinuous;
            link-frequencies = /bits/ 64 <297000000>;
        };
    };
};

注释:上述代码段描述了使用Toshiba TC358743的设备节点。它指定了设备的兼容性、寄存器地址、时钟源、复位引脚和中断引脚等属性。其中包含了一个端口部分,指定了数据通道、时钟通道、时钟连续性和链路频率。

因为要使用i2c来配置,所以一般还需要对应配置i2c。这里的中断配置一般不用。

2.内核的驱动配置

因为内核已经有了这个官方给的驱动,不需要再折腾,所有直接menuconfig配置上就行了。

 3.重新编译,烧写DTB和内核后应该就可以启动tc358743驱动了。

tc358743驱动代码实现

其实tc358743和tc35874X是一个东西,tc35874X是为了兼容老版本的tc358749。设备树配置的名字得和这个驱动名称一致,就是compatible属性tc358743还是tc358749。

驱动程序入口和出口

static int __init tc35874x_driver_init(void)
{
	return i2c_add_driver(&tc35874x_driver);
}

static void __exit tc35874x_driver_exit(void)
{
	i2c_del_driver(&tc35874x_driver);
}

device_initcall_sync(tc35874x_driver_init);
module_exit(tc35874x_driver_exit);

i2c驱动

static struct i2c_driver tc35874x_driver = {
	.driver = {
		.name = TC35874X_NAME,
		.of_match_table = of_match_ptr(tc35874x_of_match),
	},
	.probe = tc35874x_probe,
	.remove = tc35874x_remove,
	.id_table = tc35874x_id,
};

这里有个重要的结构体tc35874x_state。它包含了一系列成员变量和控件,用于管理并存储TC35874X设备的状态。具体成员变量包括pdatabussdpadhdl等,其中一些成员变量与设备的配置、控制、信号处理等相关。这个结构体还包含了一些指针变量,如i2c_clientreset_gpio,用于与其他设备进行交互。同时,它还定义了一些常量和枚举类型,如module_indexmodule_facingcur_mode,以及对应的字符串指针变量。 总之,这个结构体用于组织和管理TC35874X设备的各种状态信息和控制参数。

struct tc35874x_state {
	struct tc35874x_platform_data pdata;
	struct v4l2_fwnode_bus_mipi_csi2 bus;
	struct v4l2_subdev sd;
	struct media_pad pad;
	struct v4l2_ctrl_handler hdl;
	struct i2c_client *i2c_client;
	/* CONFCTL is modified in ops and tc35874x_hdmi_sys_int_handler */
	struct mutex confctl_mutex;

	/* controls */
	struct v4l2_ctrl *detect_tx_5v_ctrl;
	struct v4l2_ctrl *audio_sampling_rate_ctrl;
	struct v4l2_ctrl *audio_present_ctrl;

	struct delayed_work delayed_work_enable_hotplug;

	struct timer_list timer;
	struct work_struct work_i2c_poll;

	/* edid  */
	u8 edid_blocks_written;

	struct v4l2_dv_timings timings;
	u32 mbus_fmt_code;
	u8 csi_lanes_in_use;

	struct gpio_desc *reset_gpio;
	struct cec_adapter *cec_adap;

	u32 module_index;
	const char *module_facing;
	const char *module_name;
	const char *len_name;
	struct v4l2_ctrl *link_freq;
	struct v4l2_ctrl *pixel_rate;
	const struct tc35874x_mode *cur_mode;
};

最重要的就是probe函数了,基本的初始化配置都是在这里实现的。

static int tc35874x_probe(struct i2c_client *client,
			  const struct i2c_device_id *id)
{
	// Define a default timing configuration
	static struct v4l2_dv_timings default_timing = V4L2_DV_BT_CEA_640X480P59_94;
	
	// Declare variables
	struct tc35874x_state *state;
	struct tc35874x_platform_data *pdata = client->dev.platform_data;
	struct v4l2_subdev *sd;
	struct v4l2_subdev_edid def_edid;
	struct device *dev = &client->dev;
	struct device_node *node = dev->of_node;
	char facing[2];
	int err, data;
	
	// Print driver version
	dev_info(dev, "driver version: %02x.%02x.%02x",
		DRIVER_VERSION >> 16,
		(DRIVER_VERSION & 0xff00) >> 8,
		DRIVER_VERSION & 0x00ff);
	
	// Check I2C functionality
	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
		return -EIO;
	v4l_dbg(1, debug, client, "chip found @ 0x%x (%s)\n",
		client->addr << 1, client->adapter->name);
	
	// Allocate and initialize the driver state
	state = devm_kzalloc(&client->dev, sizeof(struct tc35874x_state),
			GFP_KERNEL);
	if (!state)
		return -ENOMEM;
	
	// Read module information from the device tree
	err = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX,
				   &state->module_index);
	err |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING,
				       &state->module_facing);
	err |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME,
				       &state->module_name);
	err |= of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME,
				       &state->len_name);
	if (err) {
		dev_err(dev, "could not get module information!\n");
		return -EINVAL;
	}
	
	// Set the I2C client and current mode in the driver state
	state->i2c_client = client;
	state->cur_mode = &supported_modes[0];
	
	// Initialize platform data or read from device tree
	if (pdata) {
		state->pdata = *pdata;
		state->bus.flags = V4L2_MBUS_CSI2_CONTINUOUS_CLOCK;
	} else {
		err = tc35874x_probe_of(state);
		if (err == -ENODEV)
			v4l_err(client, "No platform data!\n");
		if (err)
			return err;
	}
	
	// Initialize the V4L2 subdevice
	sd = &state->sd;
	v4l2_i2c_subdev_init(sd, client, &tc35874x_ops);
	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
	
	// Check the chip ID
	data = i2c_rd16(sd, CHIPID) & MASK_CHIPID;
	switch (data) {
	case 0x0000:
	case 0x4700:
		break;
	default:
		v4l2_info(sd, "not a tc35874x on address 0x%x\n",
			  client->addr << 1);
		return -ENODEV;
	}
	
	// Initialize control handlers
	v4l2_ctrl_handler_init(&state->hdl, 4);
	
	state->link_freq = v4l2_ctrl_new_int_menu(&state->hdl, NULL,
		V4L2_CID_LINK_FREQ, 0, 0, link_freq_menu_items);
	
	state->pixel_rate = v4l2_ctrl_new_std(&state->hdl, NULL,
		V4L2_CID_PIXEL_RATE, 0, TC35874X_PIXEL_RATE_310M, 1,
		TC35874X_PIXEL_RATE_310M);
		
	state->detect_tx_5v_ctrl = v4l2_ctrl_new_std(&state->hdl,
			&tc35874x_ctrl_ops, V4L2_CID_DV_RX_POWER_PRESENT,
			0, 1, 0, 0);
	
	if (state->detect_tx_5v_ctrl)
		state->detect_tx_5v_ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
	
	// Custom controls
	state->audio_sampling_rate_ctrl = v4l2_ctrl_new_custom(&state->hdl,
			&tc35874x_ctrl_audio_sampling_rate, NULL);
	if (state->audio_sampling_rate_ctrl)
		state->audio_sampling_rate_ctrl->flags |=
			V4L2_CTRL_FLAG_VOLATILE;
			
	state->audio_present_ctrl = v4l2_ctrl_new_custom(&state->hdl,
			&tc35874x_ctrl_audio_present, NULL);
	
	sd->ctrl_handler = &state->hdl;
	if (state->hdl.error) {
		err = state->hdl.error;
		goto err_hdl;
	}
	
	if (tc35874x_update_controls(sd)) {
		err = -ENODEV;
		goto err_hdl;
	}
	
	// Initialize media entity pads
	state->pad.flags = MEDIA_PAD_FL_SOURCE;
	sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
	err = media_entity_pads_init(&sd->entity, 1, &state->pad);
	if (err < 0)
		goto err_hdl;
	
	state->mbus_fmt_code = MEDIA_BUS_FMT_UYVY8_2X8;
	
	sd->dev = &client->dev;
	memset(facing, 0, sizeof(facing));
	if (strcmp(state->module_facing, "back") == 0)
		facing[0] = 'b';
	else
		facing[0] = 'f';
	
	snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s",
		 state->module_index, facing,
		 TC35874X_NAME, dev_name(sd->dev));
	err = v4l2_async_register_subdev(sd);
	if (err < 0)
		goto err_hdl;
		
	mutex_init(&state->confctl_mutex);
	
	INIT_DELAYED_WORK(&state->delayed_work_enable_hotplug,
			tc35874x_delayed_work_enable_hotplug);
	
	tc35874x_initial_setup(sd);
	
	tc35874x_s_dv_timings(sd, &default_timing);
	
	tc35874x_set_csi_color_space(sd);
	
	def_edid.pad = 0;
	def_edid.start_block = 0;
	def_edid.blocks = 1;
	def_edid.edid = EDID_1920x1080_60;
	
	tc35874x_s_edid(sd, &def_edid);
	
	tc35874x_init_interrupts(sd);
	
	if (state->i2c_client->irq) {
		err = devm_request_threaded_irq(&client->dev,
						state->i2c_client->irq,
						NULL, tc35874x_irq_handler,
						IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
						"tc35874x", state);
		if (err)
			goto err_work_queues;
	} else {
		INIT_WORK(&state->work_i2c_poll,
			  tc35874x_work_i2c_poll);
		timer_setup(&state->timer, tc35874x_irq_poll_timer, 0);
		state->timer.expires = jiffies +
				       msecs_to_jiffies(POLL_INTERVAL_MS);
		add_timer(&state->timer);
	}
	
	tc35874x_enable_interrupts(sd, tx_5v_power_present(sd));
	i2c_wr16(sd, INTMASK, ~(MASK_HDMI_MSK | MASK_CSI_MSK) & 0xffff);
	
	err = v4l2_ctrl_handler_setup(sd->ctrl_handler);
	if (err)
		goto err_work_queues;
	
	v4l2_info(sd, "%s found @ 0x%x (%s)\n", client->name,
		  client->addr << 1, client->adapter->name);
	
	return 0;
}

这里有个tc358743_probe_of函数,用于从设备树读取属性进行配置。

猜你喜欢

转载自blog.csdn.net/huntenganwei/article/details/131591229
今日推荐