驱动程序
一.查看芯片手册
根据芯片手册找到3个条件
1.根据芯片手册找到对应端口,并对相应端口组使能,而IMX6ULL使能是默认的
2.找到对应引脚的模式,设置为GPIO模式或者其他串口模式
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址:0x02290000 + 0x14 设置引脚模式的地址
3.并在GPIO模式下,设置引脚是输入模式或者是输出模式
GPIO5_GDIR地址:0x020AC0 设置输入输出模式的地址
4.对引脚的数据寄存器进行数据的写入
GPIO5_DR地址:0x020AC000设置数据的地址
二.用source insight打开liunx内核源码
参照内核驱动程序编写LED的驱动程序
三.编写LED驱动
1.设置LED入口函数和出口函数
a.入口函数
//入口函数 static int __init led_init(void) { printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__); major = register_chrdev(0, "100ask_led", &led_fops); //ioremap 映射寄存器地址,实际地址到虚拟地址 //IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址:0x02290000 + 0x14 IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3=ioremap(0x02290000 + 0x14, 4); //映射大小为4但是是一个页为4K //GPIO5_GDIR地址:0x020AC0 GPIO5_GDIR = ioremap(0x020AC0,4); //GPIO5_DR地址:0x020AC000 GPIO5_DR = ioremap(0x020AC000,4); led_class = class_create(THIS_MODULE, "myled"); device_create(led_class, NULL, MKDEV(major,0),NULL,"myled");//前面两行系统就会自己创建名为myled的设备节点,就不需要手动创建 return 0; } module_init(led_init);
入口函数做的事情如下:
1.调用register_chrdev函数向内核注册驱动程序,而驱动程序写在file_operations结构体的led_fops中。并分配主设备号。
2.将实际的物理地址通过ioremap函数映射为机器的虚拟地址,通过对虚拟地址指针的操作就可以操作物理地址。
3.创建两个类class_create和device_create创建这两个类的目的是使机器自动创建设备节点。
4.调用module_init函数将led_init告诉内核为led驱动程序的入口函数
b.出口函数
static void __exit led_exit(void) { //IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址:0x02290000 + 0x14 iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3); //GPIO5_GDIR地址:0x020AC0 iounmap(GPIO5_GDIR); //GPIO5_DR地址:0x020AC000 iounmap(GPIO5_DR); class_destroy(led_class); device_destroy(led_class, MKDEV(major,0)); unregister_chrdev(major, "100ask_led"); } module_exit(led_exit);
出口函数做的事情如下:
1.对入口函数内部做的ioremap销毁
2.对绕口令函数内部做的class_create和device_create这两个类进行销毁
3.将注册函数也进行销毁
4.module_exit告诉内核led_exit为出口函数
2.编写驱动程序
static const struct file_operations led_fops = { .owner = THIS_MODULE, .write = led_write, .open = led_open, };
这里声明了驱动程序里面有那些函数:led_write和;led_open函数
a.led_open函数
static int led_open(struct inode *inode, struct file *filp) { //enable gpio已经默认设置 //configure pin as gpio5_3 *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf; *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5; //configure gpio as output *GPIO5_GDIR |= (1<<3); return 0; }
led_open函数作用是:打开引脚驱动,打开的时候证明将要用这个驱动。所有设置这个驱动的配置。也就是上面芯片手册所查到的物理地址设置成相应的值。
b.led_write函数
static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { char val; int ret; // copy_from_user : get data from app ret = copy_from_user(&val, buf, 1); // to set gpio register : out 1/0 if(val) { *GPIO5_DR &= ~(1<<3); } else { *GPIO5_DR |= (1<<3); } return 1; }
通过用户层传来的命令向驱动引脚写数据,这里要注意用户层和核心层之间的数据通信是通过copy_from_user来通信的。然后对引脚的数据寄存器进行数据的填写。
完整代码
#include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/delay.h> #include <linux/poll.h> #include <linux/mutex.h> #include <linux/wait.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/device.h> static int major; static struct class *led_class; //IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址:0x02290000 + 0x14 static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3; //GPIO5_GDIR地址:0x020AC0 static volatile unsigned int *GPIO5_GDIR; //GPIO5_DR地址:0x020AC000 static volatile unsigned int *GPIO5_DR; static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { char val; int ret; // copy_from_user : get data from app ret = copy_from_user(&val, buf, 1); // to set gpio register : out 1/0 if(val) { *GPIO5_DR &= ~(1<<3); } else { *GPIO5_DR |= (1<<3); } return 1; } static int led_open(struct inode *inode, struct file *filp) { //enable gpio已经默认设置 //configure pin as gpio5_3 *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf; *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5; //configure gpio as output *GPIO5_GDIR |= (1<<3); return 0; } static const struct file_operations led_fops = { .owner = THIS_MODULE, .write = led_write, .open = led_open, }; //入口函数 static int __init led_init(void) { printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__); major = register_chrdev(0, "100ask_led", &led_fops); //ioremap 映射寄存器地址,实际地址到虚拟地址 //IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址:0x02290000 + 0x14 IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3=ioremap(0x02290000 + 0x14, 4); //映射大小为4但是是一个页为4K //GPIO5_GDIR地址:0x020AC0 GPIO5_GDIR = ioremap(0x020AC0,4); //GPIO5_DR地址:0x020AC000 GPIO5_DR = ioremap(0x020AC000,4); led_class = class_create(THIS_MODULE, "myled"); device_create(led_class, NULL, MKDEV(major,0),NULL,"myled");//前面两行系统就会自己创建名为myled的设备节点,就不需要手动创建 return 0; } //出口函数 static void __exit led_exit(void) { //IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址:0x02290000 + 0x14 iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3); //GPIO5_GDIR地址:0x020AC0 iounmap(GPIO5_GDIR); //GPIO5_DR地址:0x020AC000 iounmap(GPIO5_DR); class_destroy(led_class); device_destroy(led_class, MKDEV(major,0)); unregister_chrdev(major, "100ask_led"); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL");