linux 查看led设备,Linux下Led&Button设备驱动——详细设计

数据结构

点击(此处)折叠或打开

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

点击(此处)折叠或打开

static struct pca9555_platform_data imx233_lbtn = {

.leds = {

{

.id = 0;

.name = "led0";

.state = PCA9555_OFF;

},

……

{

id = 11;

.name = "led4"

.state = PCA9555_OFF;

},

}

.btns = {

{

.irq = 17;

.name = "btn1";

.id = 2;

.keycode = KEY_1;

}, ……

{

.irq = 17;

.name = "btn8";

.id = 15;

.keycode = KEY_8;

},

}

};

static struct i2c_board_info __initdata stmp3xxx_i2c_devices[] = {

{ I2C_BOARD_INFO("stfm1000", 0xc0), .flags = I2C_M_TEN },

{ I2C_BOARD_INFO("pca9555",0x20),

.platform_data = &imx233_lbtn,

},

};

static void __init stmp378x_devb_init(void)

{

struct fsl_usb2_platform_data *udata;

stmp3xxx_init();

i2c_register_board_info(0, stmp3xxx_i2c_devices, ARRAY_SIZE(stmp3xxx_i2c_devices));

stmp3xxx_set_mmc_data(&stmp3xxx_mmc.dev);

..

}       其中红色部分为注册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引脚功能

点击(此处)折叠或打开

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

for(i=0;i<2;i++) {

i2c_smbus_write_byte_data(client,PCA9555_REG_PINVERSION(i),0X0);

i2c_smbus_write_byte_data(client,PCA9555_REG_CONF(i),con[i]);

}

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

点击(此处)折叠或打开

for(i=0;i<5;i++) {

struct pca9555_led *led = &data->leds[i];

struct pca9555_led *pled = &pdata->leds[i];

led->client = client;

led->id = pled->id;

led->state = pled->state;

led->name = pled->name;

led->ldev.name = led->name;

led->ldev.brightness_set = pca9555_set_brightness;

err = led_classdev_register(&client->dev,&led->ldev);

if(err<0) {

dev_err(&client->dev,

"couldn't register LED %s \n",led->name);

return -1;

}

}

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

点击(此处)折叠或打开

for( i =0;i < 8; i++) {

struct pca9555_btn *pbtn = &pdata->btns[i];

btns[i] = &data->btns[i];

btns[i]->client = client;

btns[i]->name = pbtn->name;

btns[i]->id = pbtn->id;

btns[i]->irq = pbtn->irq;

btns[i]->keycode = pbtn->keycode;;

btns[i]->idev.name = btns[i]->name;

btns[i]->idev.evbit[0] = BIT_MASK(EV_KEY);

set_bit(btns[i]->keycode,btns[i]->idev.keybit);

if(request_irq(btns[i]->irq,button_key_event, IRQF_SHARED,btns[i]->name,&btns[i])) {

printk("button can not be allocate irq");

return -EBUSY;

}

printk(KERN_INFO"%s successfully loaded\n",btns[i]->name);

ret = input_register_device(&btns[i]->idev);

if(ret) {

input_free_device(&btns[i]->idev);

return ret;

}

}

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

点击(此处)折叠或打开

pca9555_set_brightness

static void pca9555_set_brightness(struct led_classdev *led_cdev,

enum led_brightness value)

{

struct pca9555_led *led = ldev_to_led(led_cdev);

if(value)

led->state = PCA9555_ON;

else

led->state = PCA9555_OFF;

pca9555_setled(led);}

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

点击(此处)折叠或打开

static void pca9555_setled(struct pca9555_led *led)

{

struct i2c_client *client = led->client;

struct pca9555_data *data = i2c_get_clientdata(client);

char reg;

mutex_lock(&data->update_lock);

reg = i2c_smbus_read_byte_data(client,LED_REG(led->id));

if(led->state)

reg = reg & ~(0x1 << ((led->id) % 8));

else

reg = reg | (0x1 <id) %8));

i2c_smbus_write_byte_data(client,LED_REG(led->id),reg);

mutex_unlock(&data->update_lock);

}

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

点击(此处)折叠或打开

static irqreturn_t button_key_event(int irq,void *dev_id)

{

//struct pca9555_btn *button = (struct pca9555_btn *)dev_id;

struct i2c_client *client = btns[0]->client;

char linreg1,linreg2;

linreg1 = i2c_smbus_read_byte_data(client,PCA9555_REG_INPUT(0));

linreg2 = i2c_smbus_read_byte_data(client,PCA9555_REG_INPUT(1));

switch((int)inreg1^linreg1) {

case 2:

input_report_key(&btns[0]->idev,btns[0]->keycode,linreg1);

input_sync(&btns[0]->idev);

break;

......

}

switch((int)inreg2^linreg2) {

case 2:

input_report_key(&btns[3]->idev,btns[3]->keycode,linreg2);

input_sync(&btns[3]->idev);

break;

......

}

inreg1 = linreg1;

inreg2 = linreg2;

return IRQ_HANDLED;

}

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值