[Linux] I2C设备读写及文件节点创建
Linux Kernel Version:3.0.35
Platform:Freescale DSA2L
通过I2C读取VGA屏的EDID信息(主要是分辨率),解析后喂给CH7036芯片(LVDS转VGA,DVI,HDMI芯片),提供文件节点给User Space。
代码流程
由于EDID协议规定I2C的读取Slave地址为0x50,所以先添加Device注册代码:
i2c.h
#define I2C_BOARD_INFO(dev_type, dev_addr) \ .type = dev_type, .addr = (dev_addr)
board-mx6q_dsa2l.c
static struct i2c_board_info mxc_i2c2_board_info[] __initdata = { { I2C_BOARD_INFO("mxc_edid_i2c", 0x50), .platform_data = NULL, .irq = DSA2L_VGA_CABLE_IN, //not irq just a gpio }, }; i2c_register_board_info(2, mxc_i2c2_board_info, ARRAY_SIZE(mxc_i2c2_board_info)); //注册i2c设备
编写驱动文件mxcfb_ch7036_edid.c
添加I2C驱动:
static const struct i2c_device_id mxc_edid_i2c_id[] = { { "mxc_edid_i2c", 0 }, {}, };
MODULE_DEVICE_TABLE(i2c, mxc_edid_i2c_id); static struct i2c_driver mxc_edid_i2c_driver = { .driver = { .name = "mxc_edid_i2c", }, .probe = mxc_edid_i2c_probe, .remove = mxc_edid_i2c_remove, .id_table = mxc_edid_i2c_id, }; static int __init mxc_edid_i2c_init(void) { return i2c_add_driver(&mxc_edid_i2c_driver); } static void __exit mxc_edid_i2c_exit(void) { i2c_del_driver(&mxc_edid_i2c_driver); } module_init(mxc_edid_i2c_init); module_exit(mxc_edid_i2c_exit);
probe函数,创建了文件节点给用户空间读取:
static int __devinit mxc_edid_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id){ int ret; edid_vga_client = client; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C)) return -ENODEV; edid_all_buf = kmalloc(edid_buf_size, GFP_KERNEL); edid_vga_class = class_create(THIS_MODULE, "mxc_edid_class"); edid_vga_dev = device_create(edid_vga_class, NULL, MKDEV(I2C_MAJOR, 50), NULL, "mxc_edid_dev"); ret = device_create_file(edid_vga_dev, &dev_attr_all_register_value); if (ret < 0){ dev_warn(&client->dev, "cound not create sys node for dev_attr_all_register_value\n"); } ret = device_create_file(edid_vga_dev, &dev_attr_plug_state); if (ret < 0){ dev_warn(&client->dev, "cound not create sys node for dev_attr_plug_state\n"); } ret = device_create_file(edid_vga_dev, &dev_attr_timing); if (ret < 0){ dev_warn(&client->dev, "cound not create sys node for dev_attr_timing\n"); } return 0; }
DEVICE_ATTR,列举一个:
static DEVICE_ATTR(all_register_value, S_IRUGO, mxc_edid_show_state, NULL);
读取文件节点对应的处理函数:
static ssize_t mxc_edid_show_state(struct device *dev, struct device_attribute *attr, char *buf) { int ret, i; ret = mxc_edid_edidread(edid_vga_client); if (ret <= 0) return ret; for(i = 0; i < edid_buf_size; i++){ if(i!=0 && i%10 == 0){ printk("\n"); printk("%02X ", edid_all_buf[i]); }else{ printk("%02X ", edid_all_buf[i]); } } printk("\n"); return strlen(buf); }
通过I2C读取EDID:
static inline int edid_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num){ int ret = 0; ret = gpio_get_value(edid_vga_client->irq); // 读取vga_detect pin,低电平表示接入vga if(!ret){ ret = i2c_transfer(adap, msgs, num); if (ret != num) { return -EIO; } } return ret; } static int mxc_edid_edidread(struct i2c_client *client){ int ret = 0; unsigned char offset = 0; struct i2c_adapter *adp = client->adapter; struct i2c_msg msg[2] = { { .addr = edid_addr, .flags = 0, .len = 1, .buf = &offset, }, { .addr = edid_addr, .flags = I2C_M_RD, .len = edid_buf_size, .buf = edid_all_buf, }, }; ret = edid_i2c_transfer(adp, msg, ARRAY_SIZE(msg)); return ret; }
知识细节
I2C时序图
所以前面 i2c_msg 数组定义有两个msg,先是写一个寄存器地址,然后是读取指定长度。
i2c 在没有数据传输的时候,SCL和SDA都处于高电平;
在SCL高电平时,SDA负跳变表示START,SDA正跳变表示STOP,数据的高低变换应该在SCL低电平时进行。
i2c标准模式的速率100kbit/s,SCL为100khz,高速模式400kbit/s,SCL为400khz。
i2c_transfer 和 i2c_smbus_write_byte_data
smbus 是 i2c 的子集,是轻量级的i2c,最大传输速度比i2c慢,常见于电源管理。
追踪代码可以发现 i2c_smbus_write_byte_data 其实本质就是对 i2c_transfer 的封装,先不做深究。
关于/dev/i2c-x
用户空间通过操作/dev/i2c-x这些i2c适配器(比如ioctl),也可以对i2c设备进行操作。TODO:研究此部分
关于文件节点的操作
代码流程:
struct device *edid_vga_dev; static struct class *edid_vga_class; static DEVICE_ATTR(all_register_value, S_IRUGO, mxc_edid_show_state, NULL); edid_vga_class = class_create(THIS_MODULE, "mxc_edid_class"); edid_vga_dev = device_create(edid_vga_class, NULL, MKDEV(I2C_MAJOR, 50), NULL, "mxc_edid_dev"); ret = device_create_file(edid_vga_dev, &dev_attr_all_register_value);
class_create:创建一个class,如“mxc_edid_class”,这样,会在对应的设备目录下创建class目录,比如:/sys/devices/virtual/mxc_edid_class
device_create:创建一个device,第一个参数:class,参数二:parent device,参数三,设备号,参数四:相关连的device,参数五:device name
device_create_file:创建sysfs attribute file
关于DEVICE_ATTR,device.h:
#define DEVICE_ATTR(_name, _mode, _show, _store) \ struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
sysfs.h:
#define __ATTR(_name,_mode,_show,_store) { \ .attr = {.name = __stringify(_name), .mode = _mode }, \ .show = _show, \ .store = _store, \ }
所以,第一个参数为名字,第二个参数是访问权限,第三个参数是用户空间 cat 时调用的函数,第四个参数是用户空间 echo 时调用的函数。