Linux驱动—LED第一个IO控制,从点灯开始
控制一个LED,就是对IO的控制,首先初始化配置,设置IO复用,配置IO的电器属性,配置IO的输入输出等。但是这些操作是在驱动程序中实现的。
字符设备的开发流程:应用程序调用open函数打开一个设备文件(LED也是一个设备),这个设备文件是在驱动程序加载完成之后产生的,存放于/dev/目录下,应用程序的open与驱动的open对应,应用层有相应的打开,读写操作函数,驱动层也有与之对应的读写函数,在Linux内核文件中include/linux/fs.h中,有一个结构体类型专门存放内核驱动操作函数集(函数指针集)——struct file_operations;
因此:字符驱动的编写主要是驱动对应的open write close ,其实就是对struct file_operations 结构体的成员变量的实现
(1)模块的加载与卸载注册函数
头文件:
#include <linux/module.h>
#include <linux/init.h>
module_init(xxxx_init)向Linux内核注册一个模块加载函数module_exit(xxxx_exit);注册一个模块卸载函数
MODULE_LICENSE(“GPL”); 模块的许可证信息,必须添加!
MODULE_AUTHOR(“Liupeng); 模块的作者
模块的加载:驱动程序编译为.ko文件后,需要手动加载驱动程序,加载方式有:insmod xxx.ko 和 modprobe xxx.ko两种
insmod:
不能解决模块之间的依赖关系,
例如,该模块调用另外一个模块则首先需要加载被调用模块
modprobe:
会自动确定模块之间的依赖关系,将有依赖关系的驱动程序全部加载,一般使用该方式加载驱动.ko
在使用modprobe加载一个新的驱动之前需要使用depmod命令一次加载出来三个新的文件.deb .alias .sysbols,且需要加载的驱动对应的.ko文件必须在 /lib/modules/4.1.15 这个目录下,因为modprobe执行时会在该路径下查找对应的.ko文件调用其中一个加载命令,就会执行驱动程序中注册好的模块加载函数,该模块加载函数的主要功能就是字符设备的加载,设备的配置(也可以在驱动的open中实现设备的配置)。
模块的卸载:rmmod xxx.ko该命令执行之后,会调module_exit(xxxx_exit);注册的卸载函数,清理工作。
(2)字符设备的加载和卸载
我们要在注册好的字符设备加载函数中完成初始化工作,完成后进行设备的加载,实现设备的读写操作,在注册的字符设备卸载函数中完成设备的卸载工作
头文件:
#include <linux/fs.h>
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)static inline void unregister_chrdev(unsigned int major, const char *name)
major : 主设备号
name:设备名
fops:结构体file_operations类型指针,指向设备的操作函数集合变量;
file_operations所在头文件:#include <linux/fs.h>
register_chrdev 函数一般在函数xxx_init()中进行
unregister_chrdev函数一般在函数xxx_exit()中进行
设备号:
unsigned int 32位数据 dev_t
头文件 include/linux/types.h
高12位为主设备号 范围:0—4095低20位为次设备号 范围:0 –
一个主设备号代表了一类设备,一类设备下面包含了其他的设备,其他的设备用次设备号来区分
在Linux下查看所有设备的设备号:cat /proc/devices
设备号的操作函数或宏:
静态分配设备号:
头文件: include/linux/kdev_t.h
从dev_t 获取主设备号和次设备号:dev_t为总的设备号MAJOR(dev_t)
MINOR(dev_t)
也可以使用主设备号和次设备号构成dev_t.,
MKDEV(major,minor)
(3)实现具体的读写操作
地址映射MMU:内存管理单元
①、完成虚拟空间到物理空间的映射。
②、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。
Linux内核启动之后,会初始化MMU,设置好之后CPU访问的都是虚拟地址,比如(imx6uLL)GPIO1_IO03 引 脚 的 复 用 寄 存 器 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 地址为 0X020E0068
。如果没有开启 MMU 的话直接向 0X020E0068 这个寄存器 地址写入数据就可以配置 GPIO1_IO03 的复用功能。现在开启了 MMU,设置了内存映射,因此就不能直接向 0X020E0068物理地址写入数据了。我们必须得到 0X020E0068 物理地址对应的虚拟地址。
(1)ioremap()
头文件: #include <linux/io.h>
原型:
#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)
假设需要获取0X020E0068物理地址对应的虚拟地址xxx(代表地址),xxx=ioremap(0X020E0068,4);
4代表内存长度字节,寄存器是4字节的。
iounmap()void iounmap (volatile void __iomem *addr)
卸载驱动的时候需要释放掉ioremap函数所做的映射
addr:要取消掉的虚拟空间首地址对映射后的虚拟内存操作
写操作:
void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)
value:需要写入的数据
addr:地址
读操作:
u8 readb (const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)
addr:地址
返回值:读到的数据
用户空间与内核空间的数据传递应用
空间的读写操作,是对内核空间的一种数据访问与获取,它们之间的数据交换获取方式:
copy_from_user();
static inline long copy_from_user(void __user *to, const void *from, unsigned long n)
用户空间向内核空间的数据传递
copy_to_user();
static inline long copy_to_user(void __user *to, const void *from, unsigned long n)
to:目标
from:依赖
n:字节数。由用户程序决定
返回值:0 成功, 其他值失败,返回未传输成功的字节个数内核空间向内核空间的数据传递
通过以上基本框架的建立,再根据实际的寄存器地址,通过对虚拟地址的读写,实现对寄存器的配置,在驱动层的读写中对寄存器的读写,即实现了应用层对Led的控制。具体的代码实现:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#define NEWCHRLED_CNT 1
#define NEWCHRLED_NAME "newchrled"
#define LEDON 1
#define LEDOFF 0
#define CCM_CCGR1_BASE (0x020c406c)
#define SW_MUX_GPIO1_IO03_BASE (0x020e0068)
#define SW_PAD_GPIO1_IO03_BASE (0x020e02f4)
#define GPIO1_DR_BASE (0x0209c000)
#define GPIO1_GDIR_BASE (0x0209c004)
static void __iomem *IMX6U_CCM_CCGR1 ;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
struct newchrled_dev{
dev_t devid ; /*设备号*/
struct cdev cdev;
struct class *class;
struct device *devices;
int major;
int minor;
};
struct newchrled_dev newchrled;
void led_switch(u8 sta)
{
u32 val=0;
if(sta==LEDON){
val=readl(GPIO1_DR);
val&=~(1<<3);
writel(val,GPIO1_DR);
}else {
val=readl(GPIO1_DR);
val|=(1<<3);
writel(val,GPIO1_DR);
}
}
static int led_open(struct inode *inode,struct file *filp)
{
filp->private_data = &newchrled;/*设置私有数据*/
return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
static ssize_t led_write(struct file*filp, const char __user *buf,
size_t cnt,loff_t *offt)
{
int retvalue;
u8 databuf[1];
u8 ledstat;
retvalue=copy_from_user(databuf,buf,cnt);
if(retvalue<0){
printk("kernel write error");
return 1;
}
ledstat=databuf[0];
if(ledstat==LEDON)
led_switch(LEDON);
else if(ledstat==LEDOFF){
led_switch(LEDOFF);
}
return 0;
}
static int led_release(struct inode *inode,struct file *filp)
{
return 0;
}
static struct file_operations newchrled_fops={
.owner= THIS_MODULE,
.open = led_open,
.read = led_read,
.write= led_write,
.release= led_release
};
static int __init led_init(void)
{
u32 val=0;
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE,4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE,4);
GPIO1_DR = ioremap(GPIO1_DR_BASE,4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE,4);
val = readl(IMX6U_CCM_CCGR1);
val&= ~(3<<26);
val|= (3<<26);
writel(val,IMX6U_CCM_CCGR1);
writel(5,SW_MUX_GPIO1_IO03);
writel(0x10b0,SW_PAD_GPIO1_IO03);
val = readl(GPIO1_GDIR);
val|= (1<<3);
writel(val,GPIO1_GDIR);
val = readl(GPIO1_DR);
val|= (1<<3);
writel(val,GPIO1_DR);
/*创建设备号*/
if(newchrled.major){ /*如果定义了设备号*/
newchrled.devid = MKDEV(newchrled.major,0); //确定设备号
register_chrdev_region(newchrled.devid,NEWCHRLED_CNT,
NEWCHRLED_NAME); /*注册设备号*/
}else {
alloc_chrdev_region(&newchrled.devid,0,NEWCHRLED_CNT,
NEWCHRLED_NAME); /*申请设备号*/
newchrled.major = MAJOR(newchrled.devid);
newchrled.minor = MINOR(newchrled.devid);
}
printk("newchrled major = %d, minor = %d \r\n",
newchrled.major,newchrled.minor);
newchrled.cdev.owner = THIS_MODULE; /*cdev结构体初始化*/
cdev_init(&newchrled.cdev,&newchrled_fops);
cdev_add( &newchrled.cdev,newchrled.devid,NEWCHRLED_CNT); /*添加字符设备*/
/*创建设备节点*/
newchrled.class = class_create(THIS_MODULE,NEWCHRLED_NAME);/*创建类*/
if(IS_ERR(newchrled.class)){
return PTR_ERR(newchrled.class);
}
newchrled.devices = device_create(newchrled.class,NULL , /*创建设备*/
newchrled.devid,NULL,NEWCHRLED_NAME);
if(IS_ERR(newchrled.devices)){
return PTR_ERR(newchrled.devices);
}
return 0;
}
static void __exit led_exit(void)
{
iounmap(IMX6U_CCM_CCGR1) ;
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
unregister_chrdev_region(newchrled.devid,1);//释放掉设备号
cdev_del(&newchrled.cdev);
class_destroy(newchrled.class);
device_destroy(newchrled.class,newchrled.devid);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Liupeng Peng");