linux 下任何 外设驱动都是 配置相应的寄存器
MMU(内存管理单元) 作用- 完成虚拟空间到物理空间的映射
- 内存保护,设置寄存器的范围权限、设置虚拟存储空间的缓冲特性
1. ioremap 函数:获取指定物理地址对应的虚拟地址
为宏 实际函数原型如下:
void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype)
2、 iounmap函数: 释放掉 ioremap函数所做的映射 (卸载驱动的时候需要调用此函数)
为宏 实际函数原型如下:
void iounmap (volatile void __iomem *addr)
I/O 内存范围函数:使用内存管理单元映射后,可直接操作虚拟地址,但是不建议如此,而是通过一下几个函数操作内存。
读:
u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *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)
实际代码 旧字符设备 led灯驱动代码编写: 指明 固定 设备号 以及子设备号#include#include #include #include #include #include #include #include #include #include #include #define LED_MAJOR 200 #define LED_NAME "led" #define LEDOFF 0 #define LEDON 1 #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; void led_switch(u8 sta) { u32 val = 0; if(sta == LEDON) { val = readl(GPIO1_DR); val &= ~(1 << 3); writel(val, GPIO1_DR); }else if(sta == LEDOFF) { val = readl(GPIO1_DR); val|= (1 << 3); writel(val, GPIO1_DR); } } static int led_open(struct inode *inode, struct file *filp) { 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; unsigned char databuf[1]; unsigned char ledstat; retvalue = copy_from_user(databuf, buf, cnt); if(retvalue < 0) { printk("kernel write failed!rn"); return -EFAULT; } 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 led_fops = { .owner = THIS_MODULE, .open = led_open, .read = led_read, .write = led_write, .release = led_release, }; static int __init led_init(void) { int retvalue = 0; 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); val |= (1 << 3); writel(val, GPIO1_GDIR); val = readl(GPIO1_DR); val |= (1 << 3); writel(val, GPIO1_DR); retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops); if(retvalue < 0){ printk("register chrdev failed!rn"); return -EIO; } 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(LED_MAJOR, LED_NAME); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ali");
代码功能
实现
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
加载驱动 led_init
流程:
(1)寄存器地址映射
(2) 使能GPIO时钟
(3)设置GPIO复用功能 电气属性
(4)设置输出
(5)默认关闭led
(6)注册字符设备驱动
卸载驱动 led_exit
流程:
1:取消虚拟地址映射
2:卸载字符设备驱动
应用程序编辑
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if(argc != 3){
printf("Error Usage!rn");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!rn", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]);
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0){
printf("LED Control Failed!rn");
close(fd);
return -1;
}
retvalue = close(fd);
if(retvalue < 0){
printf("file %s close failed!rn", argv[1]);
return -1;
}
return 0;
}
运行测试
在makefile
KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek CURRENT_PATH := $(shell pwd) obj-m := led.o build: kernel_modules kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
make -j32 即可 编译出 led.o 驱动文件
通过下面命令 编译出 应用文件
arm-linux-gnueabihf-gcc ledApp.c -o ledApp
运行测试depmod //第一次加载驱动的时候需要运行此命令
modprobe led.ko //加载驱动
mknod /dev/led c 200 0 // 创建节点
./ledApp /dev/led 1 //打开 LED灯
./ledApp /dev/led 0 //关闭 LED灯
rmmod led.ko //卸载驱动
新字符设备 led灯驱动代码2 编写:区别于旧字符设备:
通过以下函数 自动获取 设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
通过以下函数 释放获取的设备号
void unregister_chrdev_region(dev_t from, unsigned count)
修改原理代码 新代码如下:
#include#include #include #include #include #include #include #include #include #include #include #include #include #define NEWCHRLED_CNT 1 #define NEWCHRLED_NAME "newchrled" #define LEDOFF 0 #define LEDON 1 #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 *device; 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 if(sta == LEDOFF) { 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; unsigned char databuf[1]; unsigned char ledstat; retvalue = copy_from_user(databuf, buf, cnt); if(retvalue < 0) { printk("kernel write failed!rn"); return -EFAULT; } 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); 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("newcheled major=%d,minor=%drn",newchrled.major, newchrled.minor); newchrled.cdev.owner = THIS_MODULE; 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.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME); if (IS_ERR(newchrled.device)) { return PTR_ERR(newchrled.device); } 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); cdev_del(&newchrled.cdev); unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); device_destroy(newchrled.class, newchrled.devid); class_destroy(newchrled.class); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");
区别与旧字符设备:
使用cdev 结构体 表示一个字符设备
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
成员变量 ops 为字符设备操作函数集合
成员变量 dev 设备号
驱动 加载函数 led_init(void) 中 的 注册字符设备过程 分为以下4 小步
(1)创建设备号
register_chrdev_region 或 alloc_chrdev_region
(2)初始化字符设备
cdev_init(struct cdev *cdev, const struct file_operations *fops)
(3)向linux 系统添加一个字符设备
(4)创建一个类 ()
class_create(THIS_MODULE, NEWCHRLED_NAME);
(5)创建设备
device_create(struct class *class,
struct device *parent,
dev_t devt,
void *drvdata, const char *fmt, ...)
通过(4)(5) 2步骤 可以自动创建设备 节点 省去在modprobe 后通过mknod 这步。
驱动卸载函数
注销字符设备过程 分为以下4 小步
(1)cdev_del(&newchrled.cdev); 删除字符设备
(2)unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); 注销设备号
(3)device_destroy 析构 设备 对象
(4)class_destroy 析构 类对象
运行测试相对于旧字符设备 不需要在 手动创建节点
modprobe newchrled.ko //加载驱动
./ledApp /dev/newchrled 1 // 打开led灯
./ledApp /dev/newchrled 1 // 关闭led灯
rmmod newchrled.ko 卸载驱动



