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