驱动实例 — GPIO驱动 485调试 设备树修改
应用场景:使用的是3399pro,控制GPIO1_B5(RS485)的高低电平。来控制uart0的收发。
http://wiki.t-firefly.com/AIO-3399C/driver_gpio.html 有关于3399详细GPIO使用说明
http://www.wowotech.net/device_model/429.html GPIO调试相关
1.调试485确认硬件没问题
查看哪些引脚被占用: cat /sys/kernel/debug/gpio
查看当前开发板哪些引脚被占用。可以看到uart0的引脚被蓝牙占用。(因此到时候要在设备树中将蓝牙disabled了,没用到)
cd /sys/class/gpio echo 45 > export 将gpio 45暴露给用户层 这样在gpio目录下就有一个gpio45的文件,就可以直接对gpio45进行操作。 echo out > gpio45/direction echo 0 > gpio45/value echo 1 > gpio45/value
这样直接echo 111 > /dev/ttyS0。可以在PC上的串口助手看到有输出。
但是我用cat /dev/ttyS0发现数据一直不能接收进来。
后面必须要编写串口程序配置串口才能接收。
2.修改设备树
2.1 禁用蓝牙,因为蓝牙占用了uart0
将status = "okay";修改成disabled。
以下设备树配置有问题:
3.编写GPIO驱动
#include <linux/kernel.h> #include <linux/gpio.h> #include <linux/init.h> #include <linux/module.h> #include <linux/delay.h> #include <linux/of.h> #include <linux/of_gpio.h> #include <linux/of_platform.h> #include <linux/version.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/sched.h> #include <linux/pm.h> #include <linux/sysctl.h> #include <linux/proc_fs.h> #include <asm/uaccess.h> #include <asm/io.h> #define GPIO_LOW 0 #define GPIO_HIGH 1 int gpio; int major; static struct class *cls; static int rs485_ctrl_open(struct inode *inode, struct file *file) { printk(KERN_EMERG "%s-%d: enter\n",__FUNCTION__,__LINE__); return 0; } static ssize_t rs485_ctrl_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val; int ret; printk(KERN_EMERG "%s-%d: enter\n",__FUNCTION__,__LINE__); ret = copy_from_user(&val, buf, count); //copy_to_user(); if (val == 1) { gpio_set_value(gpio,GPIO_HIGH); } else { gpio_set_value(gpio,GPIO_LOW); } return 0; } static long rs485_ctrl_ioctl( struct file *files, unsigned int cmd, unsigned long arg){ //printk("cmd is %d,arg is %d\n",cmd,arg); if(cmd > 1){ printk(KERN_EMERG "rs485 control gpio cmd is 0 or 1\n"); } if(arg > 1){ printk(KERN_EMERG "rs485 control gpio arg is only 1\n"); } gpio_set_value(gpio,cmd); return 0; } static struct file_operations rs485_ctrl_fops = { .owner = THIS_MODULE, .open = rs485_ctrl_open, .write = rs485_ctrl_write, .unlocked_ioctl = rs485_ctrl_ioctl, }; static int rs485_ctrl_probe(struct platform_device *pdev) { int ret ; enum of_gpio_flags flag; //设备节点结构体 struct device_node *rs485_ctrl_node = pdev->dev.of_node; printk(KERN_EMERG "rs485 control gpio %s-%d: enter\n",__FUNCTION__,__LINE__); //of_get_named_gpio_flags 从设备树中读取 rs485_ctrl_gpio 的 GPIO 配置编号和标志 gpio = of_get_named_gpio_flags(rs485_ctrl_node,"rs485_ctrl_gpio", 0,&flag); //gpio_is_valid 判断该 GPIO 编号是否有效。 if (!gpio_is_valid(gpio)){ printk(KERN_INFO "hello: invalid gpio : %d\n",gpio); return -1; } //gpio_request 则申请占用该 GPIO。如果初始化过程出错,需要调用 gpio_free 来释放之前申请过且成功的 GPIO 。 ret = gpio_request(gpio, "rs485_ctrl-gpio"); if (ret) { gpio_free(gpio); return -EIO; } //调用 gpio_direction_output 就可以设置输出高还是低电平 gpio_direction_output(gpio, GPIO_HIGH); major = register_chrdev(0, "rs485_ctrl_dev", &rs485_ctrl_fops); cls = class_create(THIS_MODULE, "rs485_ctrl_dev"); device_create(cls, NULL, MKDEV(major, 0), NULL, "rs485_ctrl_drv"); gpio_set_value(gpio, GPIO_HIGH); printk(KERN_INFO "rs485 control gpio %s-%d: exit\n", __FUNCTION__,__LINE__); return 0; } static int rs485_ctrl_remove(struct platform_device *pdev) { printk(KERN_INFO "rs485 control gpio %s\n", __FUNCTION__); gpio_free(gpio); device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); unregister_chrdev(major, "rs485_ctrl_dev"); return 0; } static const struct of_device_id of_rs485_ctrl_match[] = { { .compatible = "rs485_ctrl" }, { /* Sentinel */ } }; static struct platform_driver rs485_ctrl_driver = { .probe = rs485_ctrl_probe, .remove = rs485_ctrl_remove, .driver = { .name = "rs485_ctrl_drv", .owner = THIS_MODULE, .of_match_table = of_rs485_ctrl_match, }, }; static int __init rs485_ctrl_init(void) { printk(KERN_INFO "rs485 control gpio init %s\n", __FUNCTION__); return platform_driver_register(&rs485_ctrl_driver); } static void __exit rs485_ctrl_exit(void) { platform_driver_unregister(&rs485_ctrl_driver); printk(KERN_INFO "rs485 control gpio exit!\n"); } module_init(rs485_ctrl_init); module_exit(rs485_ctrl_exit); MODULE_LICENSE("GPL");
4.将GPIO驱动编译进内核
要给linux内核添加模块(驱动)有如下两种方式:
(1)动态方式:采用insmod命令来给运行中的linux加载模块。
(2)静态方式:修改linux的配置菜单,添加模块相关文件到源码对应目录,然后把模块直接编译进内核。
内核的配置系统一般由以下几部分组成:
(1)Makefile:分布在Linux内核源代码中的Makefile,定义Linux内核的编译规则。
(2)配置文件(Kconfig):给用户提供配置选项,修改该文件来改变配置菜单选项。
(3)配置工具(make menuconfig):包括配置命令解释器(对配置脚本中使用的配置命令进行解释),配置用户界面(提供字符界面和图形界面)。这些配置工具都是使用脚本语言编写的,如Tcl/TK、Perl等。
配置工具(make menuconfig)根据kconfig配置脚本产生配置菜单,然后根据配置菜单的配置情况生成顶层目录下的.config(只有一个.config),在.config里定义了配置选择的配置宏定义,如下所示:
可以看到这里配置了很多CONFIG_XXX相关的宏定义。
打开/drivers/char/Makefile:
所以.config里面的配置项也就是应用到了Makefile里面。
流程就是:Kconfig(修改该文件来改变配置菜单选项) ----> Make menuconfig(配置菜单) ----> .config(配置生成) ----> Makefile(根据.config里面的配置项确定要编译哪些驱动)
不创建新的驱动文件夹:
(1)把我们的驱动源文件(rs485_driver.c)放到对应目录下,具体放到哪里需要根据驱动的类型和特点。这里假设我们放到./driver/char下。
(2)然后我们修改./driver/char下的Kconfig文件
注意这里的RS485_DRIVER这个名字可以随便写,他并不需要跟驱动源文件保持一致,但最好保持一致,等下我们在修改Makefile时会用到这个名字,他将会变成CONFIG_RS485_DRIVER,那个名字必须与这个名字对应。如上所示,tristate定义了这个配置选项的可选项有几个。(具体查看Kconfig语法规则)
(3)修改./driver/char下的Makefile文件,如下所示:
Makefile的CONFIG_RS485_DRIVER需要和Kconfig中的RS485_DRIVER对应起来。因为Kconfig中的RS485_DRIVER在经过make menuconfig配置过,会生成.config。在.config中就会变成CONFIG_RS485_DRIVER。
这样配置就完成了。只需要再顶层目录make menuconfig。
然后选择配置。再make一下就可以了。
创建新的驱动文件夹:
(1)在源码的对应目录下建立自己的目录(rs485),这里假设为/drivers/char/rs485。
(2) 把驱动源码放到新建的rs485目录下,并在此目录下新建Kconfig和Makefile文件。然后给新建的Kconfig和Makefile添加内容。
(3)在drivers/char/Kconfig中加入:source “drivers/char/rs485/Kconfig”
在drivers/char/Makefile中加入:obj-$(CONFIG_RS485_DRIVER) += rs485/ (这边直接指定我们创建的目录,Makefile就会去文件rs485下的Makefile找并编译)
然后make menuconfig选择编译:
在.config就可以看到相关配置信息