框架:
字符设备驱动,参照上文
主要分析作为驱动开发者,中断资源的获取的来龙去脉
以key为例。
硬件信息:
首先打开开发板PCB,芯片数据手册收集一波硬件信息
例如我这次要动手的是开发板上的Home键
在底板PCB搜索关键词“Home”
可见,它是连在UART_RING这个网络标签上,继续追查看它连着核心板的哪里
一查就看到了,得知了这个按键的gpio是GPX1_1,还有中断号是XEINT9
好好好,最后,只要得到GPX1的寄存器基地址就好了
在芯片手册查关键词“GPX1CON”,可以得到关键信息,地址是0x11000000+0x0c20=0x11000C20
这就是GPX1寄存器的物理地址了,
再瞄一眼,按顺序是CON,DAT,PUD,DRV寄存器,也就是说~基地址+0是CON,基地址+1是DAT,基地址+2是PUD,基地址+3是DRV。(以unsigned int*为基准)
把gpio对应位配置为0x0,输入模式。默认就是输入模式,不想写这部分的代码也行。
现在硬件信息都知道了:
//key GM_INT9--GPX1_1--XEINT(9)--0x11000C20
下面分传统方式和设备树方式写硬件部分的代码:
传统方式:
// 4.1直接从原理图得到硬件信息
//key GM_INT9--GPX1_1--0x11000C20
key_dev->regmap_size = 4;
key_dev->phyreg_base = 0x11000C20 ; //寄存器基地址
//+0是CON +1是DAT +2是PUD +3是DRV
key_dev->virreg_base = ioremap(key_dev->phyreg_base,4*key_dev->regmap_size);//映射4个寄存器
if(key_dev->virreg_base==NULL){
printk(KERN_EMERG "ioremap error\n");
ret = -ENOMEM;
goto err_4;
}
//配置GPX1-1为输入 注:这些不写也行,写了更好
*(key_dev->virreg_base) &= (~(0xF<<4)); //清除4-7位
*(key_dev->virreg_base) |=((0x0)<<4); //将4-7位置为0x0 输入模式
//初始化GPX1-1为高电平,即和原理图一样
*(key_dev->virreg_base+1) &= (~(0x1<<1)); //清除第1位
*(key_dev->virreg_base+1) |=((0x1)<<1); //将第1位置为0x1,即高电平
//初始化GPX1-1为上拉,即和原理图一样
*(key_dev->virreg_base+2) &= (~(0x3<<2)); //清除【3:2】位
*(key_dev->virreg_base+2) |=((0x3)<<2); //将其置为0x3,即上拉
//申请中断
request_irq(IRQ_EINT(9),keyirq_handler,IRQ_TYPE_EDGE_FALLING,"keyirq",NULL);
设备树方式:
其实设备树和传统方式是差不太多的,反正都是为了获得中断号,然后向linux系统注册一个中断
设备树节点编写:
在/kernel/arch/arm/boot/dts/exynos4412-itop-elite.dts中
mykey {
//节点名字
compatible = "gpio-key"; //没有匹配,随便写
gpios = <&gpx1 1 GPIO_PULL_UP>; //指明gpio是gpx1的第1个
interrupt-parent = <&gpx1>; //继承gpx1中断控制器
interrupts = <1 IRQ_TYPE_EDGE_BOTH>;//继承过来第1个中断,后面那个参数无所谓
//代码里可以再配置
};
字符设备驱动中“硬”的部分代码:
// 4.2设备树方式获得硬件信息
// /mykey compatible = "gpio-key"
/* 定位到mykey节点 */
key_dev->mynode = of_find_node_by_name(NULL,"mykey");
if(key_dev->mynode==NULL){
printk(KERN_EMERG "of_find_node_by_name error\n");
ret = -ENOMEM;
goto err_4;
}
/*
在这个节点里拿到gpio信息,返回gpio号
有了这个gpiO号就可以对此gpio配置了,上拉下拉,输入输出,高电平低电平等
*/
key_dev->mygpio = of_get_named_gpio_flags(key_dev->mynode,"gpios",0,NULL);
if (!gpio_is_valid(key_dev->mygpio))
printk("gpio isn't valid\n");
else printk("gpio num=%d",key_dev->mygpio);
/* 向系统申请这个gpio的使用权 */
ret= gpio_request(key_dev->mygpio, "gpios");
if(ret!=0){
printk(KERN_EMERG "gpio_request error\n");
ret = -ENOMEM;
goto err_5;
}
/* 这个是配置gpio模式的,通过gpio号就能配置 这里配置为输入 */
gpio_direction_input(key_dev->mygpio);//初始化为输入模式
/* 从这个节点拿到中断号 */
//获取中断号--申请中断
key_dev->irqno = of_irq_get(key_dev->mynode,0);
ret = request_irq(key_dev->irqno,keyirq_handler,IRQ_TYPE_EDGE_BOTH,"keyirq",NULL);
if(ret==0){ //返回0说明正确
printk(KERN_EMERG "key_dev->irqno is %d\n",key_dev->irqno);
}
完整代码:
驱动代码:
hw_charDev_describe.h
//#include <linux/device.h>
#include <linux/cdev.h>
struct key_event{
int code; //表示按键的内涵:home/esc/Q/W/E/R/T/ENTER
int value; ///按下1,抬起0
};
struct hw_describe{
char *dev_name; //外设名字
unsigned int dev_major; //外设设备号
struct class *hw_class; //外设设备类型
struct device *dev; //外设设备节点
struct cdev mycdev; //注册cdev到系统
struct device_node *mynode; //设备节点
unsigned int phyreg_base; //外设寄存器物理基地址
volatile unsigned int *virreg_base; //外设寄存器虚拟基地址
unsigned int regmap_size; //ioremap的长度,一个寄存器是4,即4Byte,32位
int mygpio; //gpio号
int irqno; //中断号
//外设类型
struct key_event mykey_event;
};
5th_charDev_addirq.c
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("LCH");
#include "hw_charDev_describe.h"
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/uaccess.h>
#include <uapi/linux/input-event-codes.h>
struct hw_describe *key_dev;//用于描述硬件外设
unsigned char key_state = 0;//0表示没有按下,1表示有按下
/*打开操作*/
static int chardevnode_open(struct inode *inode, struct file *file){
printk(KERN_EMERG "chardevnode_open is success!\n");
return 0;
}
/*关闭操作*/
static int chardevnode_release(struct inode *inode, struct file *file){
printk(KERN_EMERG "chardevnode_release is success!\n");
return 0;
}
/*IO操作*/
static long chardevnode_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
// printk(KERN_EMERG "chardevnode_ioctl is success! cmd is %ld,arg is %ld \n",cmd,arg);
return 0;
}
ssize_t chardevnode_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops){
//用户要读,ret = copy_to_user(buf用户的,给用户数据的指针,count);ret!=0是不正常的
int ret;
if(key_state==1){
ret = copy_to_user(buf,&key_dev->mykey_event,count);
if(ret>0){
printk(KERN_EMERG "copy_to_user error!\n");
return -EFAULT;
}
memset(&key_dev->mykey_event,0,sizeof(key_dev->mykey_event));
key_state=0;
}
return 0;
}
ssize_t chardevnode_write(struct file *file, const char __user *buf, size_t count, loff_t *f_ops){
//用户写进来,ret = copy_from_user(存数据的指针,buf用户写进来的,count);ret!=0是不正常的
return 0;
}
struct file_operations fops ={
.open = chardevnode_open,
.release = chardevnode_release,
.unlocked_ioctl = chardevnode_ioctl,
.read = chardevnode_read,
.write = chardevnode_write,
};
static irqreturn_t keyirq_handler(int irqno,void *devid){
int value;
key_state = 1;
printk("receive a interrupt 9!\n");
// 4.1非设备树方式
// value = *(key_dev->virreg_base+1)&(0x1<<1);
// 4.2设备树方式
value = gpio_get_value(key_dev->mygpio);
if(value==1){ //没按下 == key_event.value = 0
key_dev->mykey_event.code = KEY_ENTER;
key_dev->mykey_event.value = 0;
printk("key up!\n");
}
else if(value==0){ //按键按下
key_dev->mykey_event.code = KEY_ENTER;
key_dev->mykey_event.value = 1;
printk("key down!\n");
}
return IRQ_HANDLED;
}
static int __init charDev_init(void)
{
int ret =0 ;
printk(KERN_EMERG "charDev_init\n");
//0.给外设对象分配空间
key_dev = kmalloc(sizeof(struct hw_describe),GFP_KERNEL);
if(key_dev ==NULL){
printk(KERN_EMERG "kmallock error\n");
return(-ENOMEM);
}
key_dev->dev_name = "mykey";
//1.申请主设备号 设备号是MKDEV(key_dev->dev_major,自选)
key_dev->dev_major=register_chrdev(0,key_dev->dev_name,&fops);
if(key_dev->dev_major<0)
{
printk(KERN_EMERG "register_chrdev error\n");
ret = -ENODEV;
goto err_0;
}
else
{
printk(KERN_EMERG "dev_major is %d\n",key_dev->dev_major);
}
//2.注册设备到系统 /proc/devices
cdev_init(&(key_dev->mycdev),&fops);
(key_dev->mycdev).owner = THIS_MODULE;
(key_dev->mycdev).ops = &fops;
ret = cdev_add(
&(key_dev->mycdev),MKDEV(key_dev->dev_major,0),1);
if(ret){
printk(KERN_EMERG "cdev_add is fail! %d\n",ret);
goto err_1;
}else{
printk(KERN_EMERG "cdev_add is success!\n");
}
//3.生成设备节点
key_dev->hw_class = class_create(THIS_MODULE,"charDev_class");
if(IS_ERR(key_dev->hw_class))
{
printk(KERN_EMERG "class_create error\n");
ret = PTR_ERR(key_dev->hw_class);
goto err_2;
}
key_dev->dev=device_create(key_dev->hw_class,NULL,MKDEV(key_dev->dev_major,0),NULL,key_dev->dev_name);//设备节点名字
if(IS_ERR(key_dev->dev))
{
printk(KERN_EMERG "device_create error\n");
ret = PTR_ERR(key_dev->dev);
goto err_3;
}
else
{
printk(KERN_EMERG "dev_name is %s\n",key_dev->dev_name);
}
//4.获取硬件资源
/*
// 4.1直接从原理图得到硬件信息
//key GM_INT9--GPX1_1--0x11000C20
key_dev->regmap_size = 4;
key_dev->phyreg_base = 0x11000C20 ; //寄存器基地址
//+0是CON +1是DAT +2是PUD +3是DRV
key_dev->virreg_base = ioremap(key_dev->phyreg_base,4*key_dev->regmap_size);//映射4个寄存器
if(key_dev->virreg_base==NULL){
printk(KERN_EMERG "ioremap error\n");
ret = -ENOMEM;
goto err_4;
}
//配置GPX1-1为输入
*(key_dev->virreg_base) &= (~(0xF<<4)); //清除4-7位
*(key_dev->virreg_base) |=((0x0)<<4); //将4-7位置为0x1
//初始化GPX1-1为高电平,即和原理图一样
*(key_dev->virreg_base+1) &= (~(0x1<<1)); //清除第1位
*(key_dev->virreg_base+1) |=((0x1)<<1); //将第1位置为0x1,即高电平
//初始化GPX1-1为上拉,即和原理图一样
*(key_dev->virreg_base+2) &= (~(0x3<<2)); //清除【3:2】位
*(key_dev->virreg_base+2) |=((0x3)<<2); //将其置为0x3,即上拉
*/
// 4.2设备树方式获得硬件信息
// /mykey compatible = "gpio-key"
key_dev->mynode = of_find_node_by_name(NULL,"mykey");
if(key_dev->mynode==NULL){
printk(KERN_EMERG "of_find_node_by_name error\n");
ret = -ENOMEM;
goto err_4;
}
key_dev->mygpio = of_get_named_gpio_flags(key_dev->mynode,"gpios",0,NULL);
if (!gpio_is_valid(key_dev->mygpio))
printk("gpio isn't valid\n");
else printk("gpio num=%d",key_dev->mygpio);
ret= gpio_request(key_dev->mygpio, "gpios");
if(ret!=0){
printk(KERN_EMERG "gpio_request error\n");
ret = -ENOMEM;
goto err_5;
}
gpio_direction_input(key_dev->mygpio);//初始化为输入模式
//获取中断号--申请中断
key_dev->irqno = of_irq_get(key_dev->mynode,0);
ret = request_irq(key_dev->irqno,keyirq_handler,IRQ_TYPE_EDGE_BOTH,"keyirq",NULL);
if(ret==0){ //返回0说明正确
printk(KERN_EMERG "key_dev->irqno is %d\n",key_dev->irqno);
}
return 0;
err_5:
gpio_free(key_dev->mygpio);
err_4:
device_destroy(key_dev->hw_class,MKDEV(key_dev->dev_major,0));
err_3:
class_destroy(key_dev->hw_class);
err_2:
unregister_chrdev(key_dev->dev_major,key_dev->dev_name);
err_1:
cdev_del(&(key_dev->mycdev));
err_0:
kfree(key_dev);
return(ret);
}
static void __exit charDev_exit(void)
{
free_irq(key_dev->irqno,NULL);
gpio_free(key_dev->mygpio);
//iounmap(key_dev->virreg_base);
device_destroy(key_dev->hw_class,MKDEV(key_dev->dev_major,0));
class_destroy(key_dev->hw_class);
cdev_del(&(key_dev->mycdev));
unregister_chrdev(key_dev->dev_major,key_dev->dev_name);
kfree(key_dev);
}
module_init(charDev_init);
module_exit(charDev_exit);
应用层代码:
app_readkey.c
(阻塞io)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <unistd.h>
struct key_event{
int code; //表示按键的内涵:home/esc/Q/W/E/R/T/ENTER
int value; ///按下1,抬起0
};
//打开设备节点 传入cmd ,
int main(int argc ,char **argv){
int fd,res,digital,readNum;
struct key_event mykey;
char *readSWDigitalNode="/dev/mykey";
unsigned char readBuffer;
//打开节点
fd = open(readSWDigitalNode,O_RDWR);
if(fd>0)printf("app open readSWDigitalNode success\n");
else {
printf("app open readSWDigitalNode err\n");
return -1;
}
while(1){
readNum = read(fd, (void *)(&mykey),sizeof(struct key_event));//读取1B的数据
if(mykey.value==1){ //按下1,抬起0
printf("app key down\n");
}
else if(mykey.value==0){
printf("app key up\n");
}
}
close(fd);
return ;
}
运行效果:
加载驱动:
运行app:
查看注册的irq:
cat /proc/interrupts
错误分析:
如果遇到这种错误:
说明申请gpio失败/申请中断失败
原因是有别的驱动已经占用了这个gpio
有两种方法解决
1.在设备树中将有关这个gpio的注释掉
在/kernel/arch/arm/boot/dts/exynos4412-itop-elite.dts中搜索关键词”gpx1“或者“Home”"key"
可见果然有别的地方占用了,把home里面的内容都注释掉即可,我们自己的驱动就不会报错了
2.在内核配置make menuconfig中找出用到了这个gpio资源的驱动,去掉它。这个比较麻烦,不熟悉板级驱动的还真不太好搞,这个可以去参考讯为的手册,我记得他有讲过。