Chinaunix首页 | 论坛 | 博客
  • 博客访问: 918576
  • 博文数量: 453
  • 博客积分: 7865
  • 博客等级: 少将
  • 技术积分: 5673
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-29 16:21
个人简介

时光荏苒..

文章分类
文章存档

2015年(46)

2014年(22)

2013年(68)

2012年(218)

2011年(99)

分类: LINUX

2012-03-12 15:38:57

在介绍了Linux下Led&Button设备驱动的框架及整体设计之后,接下来进行详细设计
数据结构 

点击(此处)折叠或打开

  1. struct pca9555_led {
  2.         u8 id;
  3.         struct i2c_client *client;
  4.         char *name;
  5.         struct led_classdev ldev;
  6.         struct work_struct work;
  7.         enum pca9555_state state;
  8. };

  9. struct pca9555_btn {
  10.         int irq;
  11.         char *name;
  12.         u8 id;
  13.         int keycode;
  14.         struct input_dev idev;
  15.         struct i2c_client *client;

  16. };

  17. struct pca9555_platform_data {
  18.         struct pca9555_led leds[5];
  19.         struct pca9555_btn btns[8];
  20. };

       上面的结构体定义在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,并且包含中断号,按键码等信息。

点击(此处)折叠或打开

  1. static struct i2c_driver pca9555_driver = {
  2.         .driver = {
  3.                 .name = "pca9555",
  4.         },
  5.                 .probe = pca9555_probe, //当有i2c_client与i2c_driver匹配时调用
  6.                 .remove = pca9555_remove, //注销时调用
  7.                 .id_table = pca9555_id, //根据id进行匹配
  8. }

  9. struct pca9555_data{
  10.         struct i2c_client *client;
  11.         struct pca9555_led leds[5];
  12.         struct pca9555_btn btns[8];
  13.         struct mutex update_lock;

  14. };

        这两个结构体定义在驱动文件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中,在完成设备结构体的部分初始化后将会调用该接口完成注册。相关代码如下:

点击(此处)折叠或打开

  1. static struct pca9555_platform_data imx233_lbtn = {
  2.         .leds = {
  3.                 {
  4.                         .id = 0;
  5.                         .name = "led0";
  6.                         .state = PCA9555_OFF;
  7.                 },
  8.                 ……
  9.                 {
  10.                         id = 11;
  11.                         .name = "led4"
  12.                         .state = PCA9555_OFF;
  13.                 },
  14.         }
  15.         .btns = {
  16.                 {
  17.                         .irq = 17;
  18.                         .name = "btn1";
  19.                         .id = 2;
  20.                         .keycode = KEY_1;
  21.                 }, ……
  22.                 {
  23.                         .irq = 17;
  24.                         .name = "btn8";
  25.                         .id = 15;
  26.                         .keycode = KEY_8;
  27.                 },
  28.         }
  29. };

  30. static struct i2c_board_info __initdata stmp3xxx_i2c_devices[] = {
  31.         { I2C_BOARD_INFO("stfm1000", 0xc0), .flags = I2C_M_TEN },
  32.         { I2C_BOARD_INFO("pca9555",0x20),
  33.                 .platform_data = &imx233_lbtn,
  34.         },
  35. };

  36. static void __init stmp378x_devb_init(void)
  37. {
  38.         struct fsl_usb2_platform_data *udata;
  39.         stmp3xxx_init();
  40.         i2c_register_board_info(0, stmp3xxx_i2c_devices, ARRAY_SIZE(stmp3xxx_i2c_devices));

  41.         stmp3xxx_set_mmc_data(&stmp3xxx_mmc.dev);
  42.   ..
  43. }
       其中红色部分为注册pca9555所添加的,0×20为i2c中从设备pca9555的地址。leds和btns中的id值根据引脚编号得到,在驱动中led和button访问对应引脚值时用到。另外btns中的irq为中断号。9555中的8个按键共享一个GPIO中断,其中断号为17。btns中的keycode定义了按键码,将会上报到输入子系统中,当中断产生时产生相应的输出。

       完成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()函数执行。

pca9555_configure(client, data, pca9555_pdata);

Configure

在pca9555_configure()中所做的工作主要分三个部分:

配置PCA9555引脚功能

点击(此处)折叠或打开

  1. u8 con[2] = { 0x54,0xf7};

  2.         for(i=0;i<2;i++) {
  3.            i2c_smbus_write_byte_data(client,PCA9555_REG_PINVERSION(i),0X0);
  4.            i2c_smbus_write_byte_data(client,PCA9555_REG_CONF(i),con[i]);
  5.         }

       将极性反转寄存器配置为全0,然后通过9555的两个8位控制寄存器配置I/O引脚的输入输出功能。保证接led的引脚为输出引脚,连有按键的为输入引脚。PCA9555_REG_PINVERSION(i)、PCA9555_REG_CONF(i)定义了操作pca9555特定寄存器的命令字节的值,在后面出现时将不解释。

led设备注册 

点击(此处)折叠或打开

  1. for(i=0;i<5;i++) {
  2.                 struct pca9555_led *led = &data->leds[i];
  3.                 struct pca9555_led *pled = &pdata->leds[i];
  4.                 led->client = client;
  5.                 led->id = pled->id;
  6.                 led->state = pled->state;
  7.                 led->name = pled->name;
  8.                 led->ldev.name = led->name;
  9.                 led->ldev.brightness_set = pca9555_set_brightness;
  10.                 err = led_classdev_register(&client->dev,&led->ldev);
  11.                 if(err<0) {
  12.                         dev_err(&client->dev,
  13.                                         "couldn't register LED %s \n",led->name);
  14.                         return -1;

  15.                 }
  16.      }

       分别初始化5个led设备,并定义了回调函数pca9555_set_brightness(),然后将led设备注册到led-class中, 在前面led-class中已经介绍。剩下的工作就是实现回调函数pca9555_set_brightness()来控制led的亮灭。将在后面具体分析。btn设备注册 

点击(此处)折叠或打开

  1. for( i =0;i < 8; i++) {
  2.                 struct pca9555_btn *pbtn = &pdata->btns[i];
  3.                 btns[i] = &data->btns[i];
  4.                 btns[i]->client = client;
  5.                 btns[i]->name = pbtn->name;
  6.                 btns[i]->id = pbtn->id;
  7.                 btns[i]->irq = pbtn->irq;
  8.                 btns[i]->keycode = pbtn->keycode;;
  9.                 btns[i]->idev.name = btns[i]->name;
  10.                 btns[i]->idev.evbit[0] = BIT_MASK(EV_KEY);
  11.                 set_bit(btns[i]->keycode,btns[i]->idev.keybit);
  12.                 if(request_irq(btns[i]->irq,button_key_event, IRQF_SHARED,btns[i]->name,&btns[i])) {
  13.                         printk("button can not be allocate irq");
  14.                         return -EBUSY;
  15.                 }
  16.                 printk(KERN_INFO"%s successfully loaded\n",btns[i]->name);

  17.                 ret = input_register_device(&btns[i]->idev);
  18.                 if(ret) {
  19.                         input_free_device(&btns[i]->idev);
  20.                         return ret;
  21.                 }
  22.         }

       分别初始化8个按键所对应的变量,并设置idev域,使其支持按键事件并设置对应的按键码。在前面的Linux输入子系统部分有介绍。接着申请中断,由于这里的8个按键共享一个GPIO中断,中断类型设置为IRQF_SHARED,且中断处理函数为button_key_event,当中断触发时将会回调执行该函数。将会在后面详细分析该函数。最后注册输入设备到Linux输入子系统。

回调函数

点击(此处)折叠或打开

  1. pca9555_set_brightness
  2. static void pca9555_set_brightness(struct led_classdev *led_cdev,
  3.                 enum led_brightness value)
  4. {
  5.         struct pca9555_led *led = ldev_to_led(led_cdev);

  6.         if(value)
  7.                 led->state = PCA9555_ON;
  8.         else
  9.                 led->state = PCA9555_OFF;
  10.         pca9555_setled(led);}


       在/sys/class/leds/中相应的led设备目录下,通过echo写入brightness文件的值(0~255)将会传递到value 中,根据写入的值是非为0设置state域,然后调用pca9555_setled(led)来点亮或熄灭led设备。

点击(此处)折叠或打开

  1. static void pca9555_setled(struct pca9555_led *led)
  2. {
  3.         struct i2c_client *client = led->client;
  4.         struct pca9555_data *data = i2c_get_clientdata(client);
  5.         char reg;

  6.         mutex_lock(&data->update_lock);
  7.         reg = i2c_smbus_read_byte_data(client,LED_REG(led->id));
  8.         if(led->state)
  9.                 reg = reg & ~(0x1 << ((led->id) % 8));
  10.         else
  11.                 reg = reg | (0x1 <<((led->id) %8));
  12.         i2c_smbus_write_byte_data(client,LED_REG(led->id),reg);
  13.         mutex_unlock(&data->update_lock);

  14. }

     在前面提到的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()函数代码如下:


点击(此处)折叠或打开

  1. static irqreturn_t button_key_event(int irq,void *dev_id)
  2. {
  3.         //struct pca9555_btn *button = (struct pca9555_btn *)dev_id;
  4.         struct i2c_client *client = btns[0]->client;
  5.         char linreg1,linreg2;

  6.         linreg1 = i2c_smbus_read_byte_data(client,PCA9555_REG_INPUT(0));
  7.         linreg2 = i2c_smbus_read_byte_data(client,PCA9555_REG_INPUT(1));
  8.         switch((int)inreg1^linreg1) {

  9.                 case 2:
  10.                         input_report_key(&btns[0]->idev,btns[0]->keycode,linreg1);
  11.                         input_sync(&btns[0]->idev);
  12.                         break;
  13.                 ......
  14.                 }
  15.         switch((int)inreg2^linreg2) {
  16.                 case 2:
  17.                         input_report_key(&btns[3]->idev,btns[3]->keycode,linreg2);
  18.                         input_sync(&btns[3]->idev);
  19.                         break;
  20.                 ......
  21.          }
  22.         inreg1 = linreg1;
  23.         inreg2 = linreg2;

  24.         return IRQ_HANDLED;

  25. }


       当中断发生后,在中断处理程序中首先读取按键被按下后pca9555中两个输入寄存器中的值,然后与被按下前寄存器中的值inreg1、inreg2比较,确定电位发生变化的引脚所对应的按键,然后通过input_report_key向输入子系统上报对应的按键事件。接着将这次按键被按下后寄存器的值保存到inreg1、inreg2中作为下次按键被按下之前的值. 最后退出中断处理程序。

       对于驱动的设计先写到这,在后面调试过程中发现问题再补充和完善。

 
 
 
 
 
阅读(3425) | 评论(0) | 转发(0) |
0

上一篇:iMX233下Led & Button工作原理

下一篇:SMBus

给主人留下些什么吧!~~