Linux 驱动入门-platform框架+gpio+中断

Linux platform框架下的驱动编程

Platform框架是Linux中极为重要的驱动框架,绝大部分驱动都可以按照platform来加载/调试。
gpio、pinctl和中断是不同的子系统,不同soc厂商,实现方法可能不相同,所以尽可能地按照原有的dts描述来写dts。

先写好设备树

直接在根目录下添加"key"节点:

    key {
        compatible = "alientek,key";	#这个是匹配属性,驱动就是根据这个字符串来决定加载哪个驱动
        status = "okay";
        key-gpio = <&gpiog 3 GPIO_ACTIVE_LOW>;	# key-gpio自定义的,等会用of函数来解析。但后面的<>内的值是固定格式,表示:使用GPIO的G3,默认为低电平
        #很奇怪为啥没有pinctl(一种专门用来配置管脚电气属性的子系统)?因为stm32mp在设置gpio子系统的时候会自动设置pinctl
        interrupt-parent = <&gpiog>;	#中断子系统,interrupt-parent和下面的interrupts是专有字符串。
        interrupts = <3 IRQ_TYPE_EDGE_BOTH>; #上升沿和下降沿都会触发。
    };
确认设备树正确加载
$ ls /proc/device-tree/key/*
声明此驱动是platform框架

在驱动最尾端添加module_platform_driver宏。让它按照key_diver加载。

module_platform_driver(key_driver);
MODULE_LICENSE("GPL");

接着实现key_driver结构体:

static const struct of_device_id key_of_match[] = {
	{ .compatible = "alientek,key" },	/*对应于设备树中的compatible */
	{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, key_of_match);
static struct platform_driver key_driver = {
	.driver		= {
		.name	= "key_drive_name",			/* 驱动名字,用于和设备匹配 */
		.of_match_table	= key_of_match, /* 设备树匹配表 		 */
	},
	.probe		= key_probe,	/*指定probe函数,当compatible匹配上,status="okay"时就会自动运行*/
	.remove		= key_remove,   /*rmmod时的动作*/
};

probe函数

先看看描述key这个驱动的结构体:

struct key_dev_t {
	int status;
	dev_t devid;
	struct cdev cdev; /* cdev */
	struct class *class; /* 类 */
	struct device *device; /* 设备 */
	struct device_node *nd; /* 设备节点 */
	int key_gpio; /* key 所使用的 GPIO 编号*/
	struct timer_list timer; /* 按键值 */
	int irq_num; /* 中断号 */
	spinlock_t spinlock; /* 自旋锁 */
};

不要使用全局变量去定义这个结构体!
不要使用全局变量去定义这个结构体!
不要使用全局变量去定义这个结构体!在probe函数里用malloc的方式申请一块这个结构体的内存,然后整个驱动的生存周期内,所有函数都通过指针来共享(读写)这块内存。这样就避免了全局变量。

看看probe函数,就是设备树的compatible属性驱动里的compatible匹配的时候自动运行的函数:

static int key_probe(struct platform_device *pdev)
{
	int ret;
	struct key_dev_t *key_dev;	//申明一个指针
	printk("key driver and device was matched!\r\n");
	key_dev = kzalloc(sizeof(*key_dev), GFP_KERNEL);   //申请一段空间,这段空间会在整个驱动周期内一直存在,只有remove函数会释放。这样就避免了全局变量。

	spin_lock_init(&key_dev->spinlock);	//初始化自旋锁

	/* 初始化 LED */
	ret = key_gpio_init(pdev->dev.of_node, key_dev);  //初始化LED,包括获取gpio,从dts解析中断号之类的。
	if (ret < 0)
		return ret;

	/* 1、设置设备号 */
	ret = alloc_chrdev_region(&key_dev->devid, 0, KET_CNT, KEY_NAME);
	if(ret < 0) {
		pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", KEY_NAME, ret);
	}
	/* 2、初始化cdev  */
	key_dev->cdev.owner = THIS_MODULE;
	cdev_init(&key_dev->cdev, &key_fops);
	/* 3、添加一个cdev */
	ret = cdev_add(&key_dev->cdev, key_dev->devid, KET_CNT);
	if(ret < 0)
		goto del_unregister;
	/* 4、创建类      */
	key_dev->class = class_create(THIS_MODULE, KEY_NAME);
	if (IS_ERR(key_dev->class)) {
		goto del_cdev;
	}
	/* 5、创建设备 */
	key_dev->device = device_create(key_dev->class, NULL, key_dev->devid, NULL, KEY_NAME);
	if (IS_ERR(key_dev->device)) {
		goto destroy_class;
	}
//这5个步骤下来,/dev下面就有KEY_NAME这个节点了

	platform_set_drvdata(pdev, key_dev); //这句话非常重要,因为没有全局变量,key_dev_t的实现是通过kzalloc来的,保存这个指针,后续就可以通过platform_get_drvdata来获取这个变量指针。
	return 0;
destroy_class:
	class_destroy(key_dev->class);
del_cdev:
	cdev_del(&key_dev->cdev);
del_unregister:
	unregister_chrdev_region(key_dev->devid, KET_CNT);
	kfree(key_dev);
	return -EIO;
}

总结:probe函数需要补充key_gpio_init函数。剩下的1-5都是固定步骤,走完了/dev下就有节点了。最后一定要platform_set_drvdata保存第一步申请的空间,这样其他函数才能使用这个变量。

static int key_gpio_init(struct device_node *nd, struct key_dev_t *dev)
{
	int ret;
	u32 irq_flags;
	struct key_dev_t *key_dev = dev;
	key_dev->key_gpio = of_get_named_gpio(nd, "key-gpio", 0); //从dts里面获取gpio信息,"key-gpio"就是之前定义的节点属性。
	if (key_dev->key_gpio < 0) {
		printk("cannot get key gpio\n");
		return -EINVAL;
    }
    key_dev->irq_num = irq_of_parse_and_map(nd, 0); //获取irq号,在/proc/interrupt下可以看到。实际中断号需要减去32
    if(!key_dev->irq_num)
	    return -EINVAL;
    printk("key_dev->key_gpio=%d,irq=%d\n", key_dev->key_gpio, key_dev->irq_num);

    ret = gpio_request(key_dev->key_gpio, "KEY0");	//申请gpio
    if (ret) {
	    printk(KERN_ERR "failed to gpio_request,ret=%d\n", ret);
	    return -EINVAL;
    }
    gpio_direction_input(key_dev->key_gpio);	//gpio方向
    irq_flags = irq_get_trigger_type(key_dev->irq_num);
    if(irq_flags == IRQF_TRIGGER_NONE){
	    printk("irq_flags = IRQF_TRIGGER_NONE\n");
	    return -EINVAL;
    }

    ret = request_irq(key_dev->irq_num, key_interrupt_isr_func, irq_flags,
		      "KEY0_IRQ", key_dev);		//申请中断,中断服务函数是key_interrupt_isr_func,传入的参数是key_dev

    return 0;
}

中断服务函数:

static irqreturn_t key_interrupt_isr_func(int irq, void *data)
{
	struct key_dev_t *key_dev = (struct key_dev_t *)data;
	printk(KERN_ERR "key_interrupt_isr!!!\n");
	if(gpio_get_value(key_dev->key_gpio))
		key_dev->status = KEY_PRESS;
	else
		key_dev->status = KEY_RELASE;
	return IRQ_HANDLED;
}
用户态操作接口
static struct file_operations key_fops = {
	.owner = THIS_MODULE,
	.open = key_open,
	.write = key_write,
	.read = key_read,
	.release = key_release,
};
static int key_open(struct inode *inode, struct file *filp)
{
	struct key_dev_t *key_dev;
	key_dev = container_of(inode->i_cdev, struct key_dev_t, cdev);
	printk("key open!\n");
	return 0;
}
static ssize_t key_read(struct file *filp, char __user *buf,size_t cnt,loff_t *offt)
{
	unsigned long flags;
	int ret;
	struct key_dev_t *key_dev;
	struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
	key_dev = container_of(cdev, struct key_dev_t, cdev);
	spin_lock_irqsave(&key_dev->spinlock, flags);

	ret = copy_to_user(buf, &key_dev->status, sizeof(key_dev->status));
	spin_unlock_irqrestore(&key_dev->spinlock, flags);
	return ret;
}
static int key_release(struct inode *inode, struct file *filp)
{

	return 0;
}
static ssize_t key_write(struct file *filp, const char __user *buf,size_t cnt,loff_t *offt)
{
	return 0;
}

用户态测试APP

#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(int argc, char *argv[])
{
	int fd, ret;
	int key_val;
	int tmp;
	fd = open(argv[1], O_RDONLY);
	if (fd < 0) {
		printf("cannot open %s\n", argv[1]);
		return -1;
    }
    for (;;){
	    read(fd, &key_val, sizeof(key_val));
        if(key_val != tmp) {
		printf("key res=%d\n", key_val);
		tmp = key_val;
	    }   
    }
    close(fd);
    return 0;
}

Makefile

KERNELDIR := $(BUILD_OUTPUT_PATH)
CURRENT_PATH := $(shell pwd)

obj-m := key.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules -j${N}
	$(CROSS_COMPILE)gcc app.c -o app
	rm *.mod*
	rm *.o
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

你可能感兴趣的:(嵌入式,linux,驱动开发)