HDMI通过DDC驱动实现了一个标准的I2C驱动。相关驱动源码路径如下: - kernel/arch/arm/mach-x4412.c
- kernel/drivers/media/video/samsung/tvout/hw_if/hdcp.c
复制代码在mach-x4412.c中定义了HDMI对应I2C设备的名称和设备地址: - static struct i2c_board_info i2c_devs2[] __initdata = {
- #ifdef CONFIG_VIDEO_TVOUT
- {
- I2C_BOARD_INFO("s5p_ddc", (0x74 >> 1)),
- },
- #endif
- };
复制代码 在smdk4x12_machine_init函数中,通过i2c_register_board_info函数注册该设备: - s3c_i2c2_set_platdata(NULL);
- i2c_register_board_info(2, i2c_devs2, ARRAY_SIZE(i2c_devs2));
复制代码 在hdcp.c中,实际上包含两个驱动,一个是DDC驱动,另一个是HDCP驱动。在DDC驱动的s5p_ddc_init初始化函数中通过i2c_add_driver函数添加一个名为s5p_ddc的i2c驱动,很明显它和mach-x4412.c中添加的i2c设备名称一致,探测函数s5p_ddc_probe将会执行。该函数仅仅是将探测函数引入的i2c_client结构体赋值给一个全局结构体ddc_port,用于I2C读写函数。 s5p_ddc_read函数用于I2C寄存器的批量读函数,其原型如下: - static int s5p_ddc_read(u8 reg, int bytes, u8 *dest)
- {
- struct i2c_client *i2c = ddc_port;
- u8 addr = reg;
- int ret, cnt = 0;
-
- struct i2c_msg msg[] = {
- [0] = {
- .addr = i2c->addr,
- .flags = 0,//先写地址
- .len = 1,
- .buf = &addr
- },
- [1] = {
- .addr = i2c->addr,
- .flags = I2C_M_RD,//再读bytes个字节的长度到dest
- .len = bytes,
- .buf = dest
- }
- };
-
- do {
- if (!s5p_hdmi_reg_get_hpd_status() ||
- s5p_hdmi_ctrl_status() == false || on_stop_process)
- goto ddc_read_err;
-
- ret = i2c_transfer(i2c->adapter, msg, 2);//开始读
-
- if (ret < 0 || ret != 2)
- tvout_dbg("ddc : can't read data, retry %d\n", cnt);
- else//如果读成功,则退出while循环,否则读取DDC_RETRY_CNT次,直到成功为止
- break;
-
- if (hdcp_info.auth_status == FIRST_AUTHENTICATION_DONE
- || hdcp_info.auth_status == SECOND_AUTHENTICATION_DONE)
- goto ddc_read_err;
-
- msleep(DDC_DELAY);
- cnt++;
- } while (cnt < DDC_RETRY_CNT);
-
- if (cnt == DDC_RETRY_CNT)
- goto ddc_read_err;
-
- tvout_dbg("ddc : read data ok\n");
-
- return 0;
- ddc_read_err:
- tvout_err("ddc : can't read data, timeout\n");
- return -1;
- }
复制代码 传入的形参reg为需要读取的寄存器群的首地址,bytes表示需要读取的寄存器的字节数,dest用于存储读取到的寄存器值。该函数将标准的I2C读写函数i2c_transfer封装起来,同时加入了读失败了反复读取的机制,其核心和前面的I2C读写并无多大区别。 s5p_ddc_write函数用于批量写寄存器,其函数原型如下: - static int s5p_ddc_write(u8 reg, int bytes, u8 *src)
- {
- struct i2c_client *i2c = ddc_port;
- u8 msg[bytes + 1];
- int ret, cnt = 0;
-
- msg[0] = reg;
- memcpy(&msg[1], src, bytes);
-
- do {
- if (!s5p_hdmi_reg_get_hpd_status() ||
- s5p_hdmi_ctrl_status() == false || on_stop_process)
- goto ddc_write_err;
-
- ret = i2c_master_send(i2c, msg, bytes + 1);//一次性写入bytes个字节的数据,在写入字节之前需要先写寄存器地址
-
- if (ret < 0 || ret < bytes + 1)
- tvout_dbg("ddc : can't write data, retry %d\n", cnt);
- else
- break;
-
- msleep(DDC_DELAY);
- cnt++;
- } while (cnt < DDC_RETRY_CNT);
-
- if (cnt == DDC_RETRY_CNT)
- goto ddc_write_err;
-
- tvout_dbg("ddc : write data ok\n");
- return 0;
- ddc_write_err:
- tvout_err("ddc : can't write data, timeout\n");
- return -1;
- }
复制代码 同样,传入的形参reg为要写入的寄存器群的起始地址,bytes表示要写入的寄存器的字节数,src指向要写入的寄存器的值的地址。所不同的是,这里调用的是i2c_master_send函数,而不是i2c_transfer函数。其实i2c_master_send函数的本质也就是封装了i2c_transfer函数,其函数原型如下: - int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
- {
- int ret;
- struct i2c_adapter *adap = client->adapter;
- struct i2c_msg msg;
-
- msg.addr = client->addr;
- msg.flags = client->flags & I2C_M_TEN;
- msg.len = count;
- msg.buf = (char *)buf;
-
- ret = i2c_transfer(adap, &msg, 1);
-
- /* If everything went ok (i.e. 1 msg transmitted), return #bytes
- transmitted, else error code. */
- return (ret == 1) ? count : ret;
- }
复制代码 它本质上还是和s5p_ddc_read函数调用了相同的I2C函数。这两个函数被包装起来给HDMI的HDCP等驱动调用,完成HDMI的相关通讯。
|