目录
一、前言
前面用一个虚拟的chrdevbase设备做了一个字符设备的开发,现在编写一个真正的linux字符设备-led。重点即是linux下引脚控制驱动。在此先了解一下MMU(内存管理单元),
MMU的主要功能:
①、完成虚拟空间到物理空间的映射;
②、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性;
linux内核启动后会初始化MMU,设置好内存映射,设置好后CPU访问的都是虚拟地址。获取实际硬件寄存器物理地址在linux里面对应的虚拟地址,进行操作。这里涉及到物理地址和虚拟地址之间的转换,需要用到两个函数:ioremap和iounmap
1.1、地址映射
1.1.1、ioremap
用于获取指定物理地址空间对应的虚拟地址空间。
#define ioremap(cookie,size) __arm_ioremap((cookie), (size),
MT_DEVICE)
/*
@Decription:获取指定物理地址对应的虚拟地址
@phys_addr:要映射的物理起始地址
@size:要映射的内存空间大小
@return:__iomem类型指针,指向映射后的虚拟空间首地址
*/
void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size,
unsigned int mtype)
{
return arch_ioremap_caller(phys_addr, size, mtype,
__builtin_return_address(0));
}
示例如下:
有个寄存器SW_MUX_GPIO1_IO03_BASE物理地址为0x020E0068,获取对应的虚拟地址的方法如下:
//寄存器物理地址
#define SW_MUX_GPIO1_IO03_BASE 0x020E0068
//定义一个__iomem类型指针
static void __iomem* SW_MUX_GPIO1_IO03;
//通过ioremap获取对应的虚拟地址
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
1.1.2、iounmap
卸载驱动的时候释放ioremap函数所做的映射。
/*
@Decription:释放映射后的地址
@addr:要取消映射的虚拟空间起始地址
*/
void iounmap (volatile void __iomem *addr)
示例:
要取消SW_MUX_GPIO1_IO03_BASE寄存器的地址映射:
iounmap(SW_MUX_GPIO1_IO03);
1.2、IO内存访问函数
使用ioremap函数将寄存器物理地址映射到虚拟地址以后,就可以通过指针访问这些地址了,推荐用linux提供的函数进行内存读写操作。
1.2.1、读操作
//8bit
u8 readb(const volatile void __iomem *addr)
//16bit
u16 readw(const volatile void __iomem *addr)
//32bit
u32 readl(const volatile void __iomem *addr)
1.2.2、写操作
void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)
二、驱动程序编写
主要是操作gpio相关的寄存器。
①、先通过ioremap获得对应的虚拟地址
②、然后使用库函数进行读写操作。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#define LED_MAJOR 200
#define LED_NAME "led"
/* 寄存器物理地址 */
#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;
#define LEDOFF 0
#define LEDON 1
void led_switch(unsigned char sta)
{
unsigned int val = 0;
if (sta == LEDOFF){
val = readl(GPIO1_DR);
val |= 1 << 3; /* bit3置1,关闭LED */
writel(val, GPIO1_DR);
} else if (sta == LEDON){
val = readl(GPIO1_DR);
val &= ~(1 << 3); /* bit3清零,打开LED */
writel(val, GPIO1_DR);
}
}
static int led_open(struct inode *inode, struct file *filp)
{
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
int retvalue;
unsigned char databuf[1];
retvalue = copy_from_user(databuf, buf ,count);
if (retvalue < 0){
printk("kernel write failed!\r\n");
return -EFAULT;
}
/*开灯关灯判断*/
led_switch(databuf[0]);
return 0;
}
/*字符设备操作集*/
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.write = led_write,
.open = led_open,
.release = led_release,
};
/*入口*/
static int __init led_init(void)
{
int ret = 0;
unsigned int val = 0;
/* 1、初始化LED灯,地址映射*/
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);
/* 2、初始化 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); /* 先清除以前的配置bit26,27 */
val |= 3 << 26; /* bit26,27置1 */
writel(val, IMX6U_CCM_CCGR1);
writel(0x05, SW_MUX_GPIO1_IO03); /* 设置复用 */
writel(0X10B0, SW_PAD_GPIO1_IO03); /* 设置电气属性 */
val = readl(GPIO1_GDIR);
val |= 1 << 3; /* bit3置1,设置为输出 */
writel(val, GPIO1_GDIR);
val = readl(GPIO1_DR);
val &= ~(1 << 3); /* bit3清零,打开LED */
writel(val, GPIO1_DR);
/*注册字符设备*/
ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
if (ret < 0) {
printk("register chrdev failed!\r\n");
return -EIO;
}
printk("led_init\r\n");
return 0;
}
/*出口*/
static void __exit led_exit(void)
{
unsigned int val = 0;
val = readl(GPIO1_DR);
val |= 1 << 3; /* bit3置1,关闭LED */
writel(val, GPIO1_DR);
/* 1、取消地址映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/*注销字符设备*/
unregister_chrdev(LED_MAJOR, LED_NAME);
printk("led_exit\r\n");
}
/*注册驱动加载和卸载*/
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zk");
三、测试程序编写
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/*
@argc:应用程序参数个数
@argv[]:具体的参数内容,字符串形式
@ ./ledAPP <filename> <0:1> 0关灯,1开灯
./ledAPP /dev/led 0 关灯
./ledAPP /dev/led 1 开灯
*/
int main(int argc, char *argv[])
{
int ret = 0;
int fd = 0;
int retvalue = 0;
char *filename;
unsigned char databuf[1];
if (argc != 3){
printf("Error usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0){
printf("can't open file\r\n", filename);
return -1;
}
databuf[0] = atoi(argv[2]);
retvalue = write(fd, databuf, sizeof(databuf));
if (retvalue < 0) {
printf("LED control failed!\r\n");
close(fd);
return -1;
}
/*close*/
ret = close(fd);
if (ret < 0){
printf("close file %s failed\r\n", filename);
}
return 0;
}
四、测试
- 将编译出的led.ko和ledApp放在文件系统路径里
- 利用modprobe加载驱动模块
- 利用mknod新建设备节点
- 执行./ledApp /dev/led 1点灯
- 执行./ledApp /dev/led 0关灯