[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 時調用的函數。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM