时光荏苒..
全部博文(453)
分类: LINUX
2012-03-12 15:38:57
点击(此处)折叠或打开
- struct pca9555_led {
- u8 id;
- struct i2c_client *client;
- char *name;
- struct led_classdev ldev;
- struct work_struct work;
- enum pca9555_state state;
- };
- struct pca9555_btn {
- int irq;
- char *name;
- u8 id;
- int keycode;
- struct input_dev idev;
- struct i2c_client *client;
- };
- struct pca9555_platform_data {
- struct pca9555_led leds[5];
- struct pca9555_btn btns[8];
- };
上面的结构体定义在pca9555.h中,PCA9555有16个I/O,5个接led,8个接按键,结构体pca9555_platform_data描述了9555的使用情况。结构体类型pca9555_led和pca9555_btn分别用于描述led和Button,他们都属于i2c设备,因此都包括结构体指针变量struct i2c_client *client,led需要向led-class中注册,其注册的设备结构类型为led_classdev,Button为输入设备,在设备结构体中包含向input子系统注册的类型input_dev,并且包含中断号,按键码等信息。
点击(此处)折叠或打开
- static struct i2c_driver pca9555_driver = {
- .driver = {
- .name = "pca9555",
- },
- .probe = pca9555_probe, //当有i2c_client与i2c_driver匹配时调用
- .remove = pca9555_remove, //注销时调用
- .id_table = pca9555_id, //根据id进行匹配
- }
- struct pca9555_data{
- struct i2c_client *client;
- struct pca9555_led leds[5];
- struct pca9555_btn btns[8];
- struct mutex update_lock;
- };
这两个结构体定义在驱动文件pca9555.c中,pca9555_driver在i2c驱动注册时作为参数被调用。pca9555_data中除了定义leds、btns之外定义了互斥变量update_lock,在通过i2c总线读写设备时用到。
I2C设备的注册
在Linux2.6内核中支持两种编写i2c驱动程序的方式(这里所有内核版本为linux2.6.28):Adapter方式(LEGACY)和Probe方式(new style)。对于LEGACY方式的驱动设备部分在驱动运行的时候动态创建,新式的驱动(probe方式)倾向于向传统的Linux下设备驱动看齐,采用静态定义的方式来注册设备。使用接口为:
int __init i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
该函数定义在linux2.6.28/driver/i2c/i2c-boardinfo.c中。在平台代码中将会调用该函数完成i2c_board_info的注册。注册过程会根据info参数提供的设备信息封装一个devinfo的结构体,并添加到全局链表_i2c_board_list中。
对于i.MX233,在linux2.6.28\arch\arm\mach-stmp3xxx\stmp378x_devb.c中,在完成设备结构体的部分初始化后将会调用该接口完成注册。相关代码如下:
点击(此处)折叠或打开
完成i2c_board_info注册后,在I2C核心中会根据板级i2c设备配置信息,创建i2c客户端设备(i2c_client)添加到i2c子系统中。
i2c驱动接下来的部分全部在pca9555.c中实现。
Probe在数据结构中提到static struct i2c_driver pca9555_driver ,在加载驱动模块时将会将其注册到i2c子系统中。接口如下:
i2c_add_driver(&pca9555_driver);
在pca9555_driver中定义了一个probe和remove两个回调函数。当加载驱动模块后i2c_client和i2c_driver匹配时将会调用pca9555_probe()。
函数pca9555_probe()所做的工作很简单。主要是取得板级的设备信息,这里将其保存到变量pca9555_pdata中,然后申请pca9555_data类型变量data空间并完成相关指针传递。接着初始化互斥量update->lock,最后跳转到pca9555_configure()函数执行。
Configurepca9555_configure(client, data, pca9555_pdata);
在pca9555_configure()中所做的工作主要分三个部分:
配置PCA9555引脚功能点击(此处)折叠或打开
将极性反转寄存器配置为全0,然后通过9555的两个8位控制寄存器配置I/O引脚的输入输出功能。保证接led的引脚为输出引脚,连有按键的为输入引脚。PCA9555_REG_PINVERSION(i)、PCA9555_REG_CONF(i)定义了操作pca9555特定寄存器的命令字节的值,在后面出现时将不解释。
led设备注册点击(此处)折叠或打开
点击(此处)折叠或打开
分别初始化8个按键所对应的变量,并设置idev域,使其支持按键事件并设置对应的按键码。在前面的Linux输入子系统部分有介绍。接着申请中断,由于这里的8个按键共享一个GPIO中断,中断类型设置为IRQF_SHARED,且中断处理函数为button_key_event,当中断触发时将会回调执行该函数。将会在后面详细分析该函数。最后注册输入设备到Linux输入子系统。
回调函数点击(此处)折叠或打开
在/sys/class/leds/中相应的led设备目录下,通过echo写入brightness文件的值(0~255)将会传递到value 中,根据写入的值是非为0设置state域,然后调用pca9555_setled(led)来点亮或熄灭led设备。
点击(此处)折叠或打开
在前面提到的led的id值在这里被用到,通过宏LED_REG(led->id)确定控制该led所在引脚的寄存器字节,读取该寄存器中的值到reg中,然后根据先前设置的state域,设置led引脚所对应的位的值,最后将处理过的reg重新写入到相应的寄存器中。这样就达到改变led所在引脚的电位的目的,从而控制led的亮度。另外指出的是在读写的时候需要用到互斥机制。
button_key_event在configure中申请中断时,将8个按键共用了一个GPIO中断,当这些按键中的任意一个产生中断时都会跳转到中断处理函数button_key_event中执行,这是需要判断到底是哪个按键被按下,然后向输入子系统上报相应按键对应的事件。
为了确定被按下的按键,定义如下两个字符型的全局变量inreg1,inreg2,用于保存中断发生前反映pca9555输入引脚电位的两个输入寄存器的值。在发生第一次中断之前,在模块加载中先读取这连个寄存器的值。这里放在pca9555_configure()函数的最后,代码如下:
inreg1 = i2c_smbus_read_byte_data(client,PCA9555_REG_INPUT(0)); inreg2 = i2c_smbus_read_byte_data(client,PCA9555_REG_INPUT(1));中断处理程序button_key_event()函数代码如下:
点击(此处)折叠或打开
当中断发生后,在中断处理程序中首先读取按键被按下后pca9555中两个输入寄存器中的值,然后与被按下前寄存器中的值inreg1、inreg2比较,确定电位发生变化的引脚所对应的按键,然后通过input_report_key向输入子系统上报对应的按键事件。接着将这次按键被按下后寄存器的值保存到inreg1、inreg2中作为下次按键被按下之前的值. 最后退出中断处理程序。
对于驱动的设计先写到这,在后面调试过程中发现问题再补充和完善。