[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_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 时调用的函数。

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。