分享

Linux下Led&Button设备驱动PCA9555

 XeonGate 2020-01-09
  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中作为下次按键被按下之前的值. 最后退出中断处理程序。

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

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多