tc358743将 HDMI 输入流转换为 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设备的状态。具体成员变量包括pdata
、bus
、sd
、pad
、hdl
等,其中一些成员变量与设备的配置、控制、信号处理等相关。这个结构体还包含了一些指针变量,如i2c_client
和reset_gpio
,用于与其他设备进行交互。同时,它还定义了一些常量和枚举类型,如module_index
、module_facing
和cur_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函数,用于从设备树读取属性进行配置。