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/*
在驱动最尾端添加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时的动作*/
};
先看看描述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;
}
#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;
}
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