数据结构

点击(此处)折叠或打开

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

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

linux 查看led设备,Linux下LedButton设备驱动——详细设计相关推荐

  1. Linux 设备树下的 platform 驱动示例

    1.简介 基于总线.设备和驱动这样的驱动框架,Linux 内核提出来 platform 这个虚拟总线,相应的也有 platform 设备和 platform 驱动. Linux 总线设备和驱动模式 2 ...

  2. 【正点原子MP157连载】第三十五章 设备树下的platform驱动编写-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  3. Linux 设备树下的 platform 驱动实验基于正点原子IMX6ULL开发板

    1 设备树下的 platform 驱动简介 platform 驱动框架分为总线.设备和驱动,其中总线不需要我们这些驱动程序员去管理,这个是 Linux 内核提供的,我们在编写驱动的时候只要关注于设备和 ...

  4. linux查看显示器名称命令,linux 查看显示器信息Linux下查看硬件信息命令大全

    /proc 虚拟的目录,是系统内存的映射.可直接访问这个目录来获取系统信息.其中也包含下面的信息: 主机CPU信息:cpuinfo 主机DMA通道信息:dma 文件系统信息:filesystems 主 ...

  5. 设备树下的platform 驱动编写

    目录 设备树下的platform 驱动简介 硬件原理图分析 实验程序编写 修改设备树文件 platform 驱动程序编写 编写测试APP 运行测试 编译驱动程序和测试APP 运行测试 上一章我们详细的 ...

  6. 设备树下的platform驱动编写

    文章目录 一.设备树下的platform驱动简介 1.在设备树中创建设备节点 2.编写 platform 驱动的时候要注意兼容属性 3.编写platform驱动 二.硬件原理图分析 三.实验程序编写 ...

  7. 设备树下字符设备驱动

    设备树下字符设备驱动 一.在设备树里添加自己的节点 二.驱动代码 三.makefile 四.应用层代码 运行测试 总结 一.在设备树里添加自己的节点 alphaled { 2 #address-cel ...

  8. 设备树下的 platform 驱动开发框架

    1. 设备树下的platform驱动开发 platform驱动框架分为总线.设备和驱动,其中总线是由Linux内核提供,在编写驱动时只要关注于设备和驱动的具体实现即可.Linux下的platform驱 ...

  9. linux查看显卡核心数,linux查看硬件信息,linux查看硬盘信息,linux查看CPU信息,linux查看显卡,硬件型号信息 | 帮助信息-动天数据...

    linux查看硬件信息,linux查看硬盘信息,linux查看CPU信息,linux查看显卡,硬件型号信息 作者:dthost | 时间:2015-09-30 | 8,325 次阅读 linux服务器 ...

  10. 通过命令查看linux 密码,linux查看用户密码(linux查看用户密码命令)

    linux查看用户密码(linux查看用户密码命令) 2020-05-15 13:18:30 共10个回答 1.用户名和密码的存储位置存储帐号的文件:/etc/passwd存储密码的文件:/etc/s ...

最新文章

  1. 今日头条字节跳动抖音小程序上线前后的搜索优化配置
  2. SpringMVC下的基本配置
  3. MySQL5.5.32编译安装
  4. 高德地图 amap 设置鼠标样式
  5. git 无法提交空目录
  6. C#基础笔记(第十天)
  7. 诡异的ie8堆栈溢出异常
  8. silverlight自定义安装客户端插件
  9. houdini大神自诉:为什么我要放弃maya I
  10. 无损音乐ape转wav图文教程
  11. iOS从零开始,用Swift:iOS上的数据持久性和沙箱
  12. mysql分组后为0也显示_C罗离开后,梅西6次参加国家德比的数据为0球0助!
  13. 【做项目】基于SpringBoot从零开发的个人博客 —— 从技术选型到部署实战(附学习路线)
  14. js 数字金额的转换 (转)
  15. http://localhost:8080/ 无法访问
  16. 锁定Mac电脑的8种方法
  17. 使用frp搭建自己的内网穿透
  18. 劝退员工,你问过劳动合同法了吗?
  19. 编译OpenCV:opencv/3rdparty/openjpeg/openjp2/opj_stdint.h:48:2: error: #error unsupported platform
  20. 微软将关闭安卓iOS平台Cortana语音助手大战惨遭失败

热门文章

  1. 网络通信程序做个瞬间的扫描判断
  2. 【飞鸽传书3.0】飞鸽传书3.0关键字布局
  3. 飞鸽传书2007用户需求就是做好需求处理
  4. 本周Web2.0小工具推荐[2008-09-13]
  5. SMTP协议原始命令码和工作原理
  6. 最简单的NamedPiep程序[秋镇菜]-初学者看看
  7. 难学的十大编程语言,C++位居第二,它才是第一名!
  8. java在枚举方法中调方法_java – 值方法如何在枚举中工作
  9. 推荐一些数据挖掘和生信友好的SCI!
  10. 提高篇 第四部分 数据结构 第2章 RMQ问题