目标:编写一个platporm架构的LED驱动
参考知识:
在Linux字符设备驱动编程模型中,只要应用程序open()了相应的设备文件,就可以使用ioctl通过驱动程序来控制我们的硬件,这种模型直观,但是从软件设计的角度看,却是一种十分糟糕的方式,它有一个致命的问题,就是设备信息和驱动代码冗余在一起,一旦硬件信息发生改变甚至设备已经不在了,就必须要修改驱动源码,非常的麻烦,为了解决这种驱动代码和设备信息耦合的问题,Linux提出了platform bus(平台总线)的概念,即使用虚拟总线将设备信息和驱动程序进行分离,设备树的提出就是进一步深化这种思想,将设备信息进行更好的整理。平台总线会维护两条链表,分别管理设备和驱动,当一个设备被注册到总线上的时候,总线会根据其名字搜索对应的驱动,如果找到就将设备信息导入驱动程序并执行驱动;当一个驱动被注册到平台总线的时候,总线也会搜索设备。总之,平台总线负责将设备信息和驱动代码匹配,这样就可以做到驱动和设备信息的分离。
//include/linux/ioport.h
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
};
这个结构用来描述一个地址资源或中断资源,除了这个结构,内核还提供了一些宏来帮助我们快速的创建一个resource对象。
start表示资源开始的位置,如果是IO地址资源,就是起始物理地址,如果是中断资源,就是中断号;
end表示资源结束的位置,如果是IO地址地址,就是映射的最后一个物理地址,如果是中断资源,就不用填;
name就是这个资源的名字。
flags表示资源类型,提取函数在寻找资源的时候会对比自己传入的参数和这个成员,工程师应该使用内核提供的宏,这些宏也在"ioport.h"中进行了定义,比如IORESOURCE_MEM表示这个资源是地址资源,IORESOURCE_IRQ表示这个资源是中断资源...。
有了这几个属性,就可以完整的描述一个资源,但如果每个资源都需要单独管理而不是组成某种数据结构,显然是一种非常愚蠢的做法,所以内核的resource结构还提供了三个指针:parent,sibling,child(24),分别用来表示资源的父资源,兄弟资源,子资源,这样内核就可以使用树结构来高效的管理大量的系统资源,linux内核有两种树结构:iomem_resource,ioport_resource,进行板级开发的时候,通常将主板上的ROM资源放入iomem_resource树的一个节点,而将系统固有的I/O资源挂到ioport_resource树上。
Makefile的参考:
查看原理图:
要控制的LED,输出0就会亮,输出1就会灭
可以看到要控制LED就要通过PH2来控制
查看数据手册:
GPIO基址:
PH控制寄存器偏移地址:
PH2控制寄存器的位:
PH数据寄存器的偏移地址:
设备部分代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
//#define GPIO_LED_PIN_NUM 0x00FC //物理地址
// GPIO 0x0300B000
//PH_CFG0 Offset: 0x00FC ->0x0300B0FC
//PH2[31:0]: [10:8] = 111 -> 001 output
//PH_DAT[31:0]: [15:0] Offset: 0x010C ->0x0300B10C
//定义LED资源
static struct resource led_resources[] = {
[0] = {
.start = 0x0300B0FC, //资源的起始地址
.end = 0x0300B0FC + 16 - 1, //资源的结束地址
.flags = IORESOURCE_MEM, //资源的类型
},
[1] = {
.start = 0x0300B10C,
.end = 0x0300B10C + 16 - 1,
.flags = IORESOURCE_MEM,
},
};
//struct resource led_resources = DEFINE_RES_MEM(GPIO_LED_PIN_NUM, 4);
static void led_platform_release(struct device *dev) {
return;
}
//定义平台设备
static struct platform_device led_platform_device = {
.name = "myled", //platform_driver中的名字要一致
.id = -1, //表示只有一个设备
.num_resources = ARRAY_SIZE(led_resources), //资源数组大小
.resource = led_resources, //资源数组
.dev = {
.release = led_platform_release,
//.platform_data = NULL,
},
};
//注册平台设备
static int led_platform_init(void) {
printk("led_platform_init\n");
//platform_device_register(&led_platform_device);
int ret;
ret = platform_device_register(&led_platform_device);
if(ret < 0) {
platform_device_put(&led_platform_device);
return ret;
}
return 0;
}
static void led_platform_exit(void) {
printk("led_platform_exit\n");
platform_device_unregister(&led_platform_device);
}
module_init(led_platform_init);
module_exit(led_platform_exit);
MODULE_LICENSE("GPL v2");
驱动部分代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#define LED_ON 0
#define LED_OFF 1
#define LED_CON_PIN 8
#define LED_DAT_PIN 2
static int major; //主设备号
struct cdev *led_cedv;
static struct class *led_class;
unsigned long *gpio_con; //GPIO控制寄存器
unsigned long *gpio_dat; //GPIO数据寄存器
//unsigned long a = 0, b = 0;
#define DEVICE_NAME "myled"
static int led_open(struct inode *inode, struct file *file) {
printk("led_open\n");
*gpio_con &= ~(7<< LED_CON_PIN);
*gpio_con |= (1 << LED_CON_PIN);
//设置控制寄存器[10:8]为001,引脚输出
return 0;
}
static int led_release(struct inode *inode, struct file *file) {
printk("led_release\n");
//*gpio_con |= (7<< LED_CON_PIN); //tmdsb加了这句让我测了一天灯不变
return 0;
}
ssize_t led_read(struct file *filp, //其中参数filp是文件指针,
char __user *buf, //参数buff指向用户空间的缓冲区,这个缓冲区是一个存放新读入数据的空缓冲区;
size_t count, //参数count是请求传输的数据长度,
loff_t *f_pos) { //offp指明了用户在文件中进行存放操作的位置;
printk("read led\n");
int val = 0;
val = *gpio_dat & (0x01 << LED_DAT_PIN);
copy_to_user(buf, &val, 1); //把读取的数据传递到用户层
return 1; //读取成功返回读取的字节数
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
int val = 0;
if(count != 1) return -1;
copy_from_user(&val, buf, count);
printk("led_write %d\n", val);
if(val) { //1 | 0
*gpio_dat |= (0x01 << LED_DAT_PIN); //写入1
} else {
*gpio_dat &= ~(0x01 << LED_DAT_PIN); //写入0
}
return 1;
}
long led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
printk("led_ioctl %d\n", cmd);
switch (cmd)
{
case LED_ON:
printk("led_ioctl LED_ON\n");
*gpio_dat &= ~(0x01 << LED_DAT_PIN);
break;
case LED_OFF:
printk("led_ioctl LED_OFF\n");
*gpio_dat |= (0x01 << LED_DAT_PIN);
break;
default:
printk("led_ioctl default\n");
*gpio_dat &= ~(0x01 << LED_DAT_PIN);
break;
}
return 0;
}
struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
.unlocked_ioctl = led_ioctl,
};
//平台设备和平台驱动匹配成功后就会调用下面的函数
static int led_probe(struct platform_device *pdev) {
printk("led_probe\n");
int ret;
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取设备资源,拿出resource[0]资源
gpio_con = ioremap(res->start, res->end - res->start + 1); //把物理地址映射为虚拟地址
res = platform_get_resource(pdev, IORESOURCE_MEM, 1); //获取设备资源,拿出resource[1]资源
gpio_dat = ioremap(res->start, res->end - res->start + 1);
//gpio_con = &a;
//gpio_dat = &b;
//gpio_con = ioremap(0x0300B0FC, 16);
//gpio_dat = ioremap(0x0300B10C, 16);
major = register_chrdev(0, "myled", &led_fops); //申请设备号
led_class = class_create(THIS_MODULE, "myled"); //创建设备类
device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled0", NULL); //创建设备节点
return 0;
}
static int led_remove(struct platform_device *dev) {
printk("led_remove\n");
device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
unregister_chrdev(major, "mydev");
iounmap(gpio_con); //注销虚拟地址
iounmap(gpio_dat);
return 0;
}
//定义platform驱动
static struct platform_driver led_platform_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled", //与platform_device中的名称对应
.owner = THIS_MODULE,
},
};
static int led_init(void) {
printk("led_init\n");
//注册平台驱动的同时会匹配相应的平台设备
return platform_driver_register(&led_platform_driver);
}
static void led_exit(void) {
printk("led_exit\n");
platform_driver_unregister(&led_platform_driver);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL v2");
Makefile文件:
##CONFIG_MODULE_SIG=n
CROSS_COMPILE:=/home/e123/workspace/V536_CDR_v2.0/prebuilt/gcc/linux-x86/arm/toolchain-sunxi-musl/toolchain/bin/arm-openwrt-linux-
##ARCH:=arm
CC:= $(CROSS_COMPILE)gcc
LD:= $(CROSS_COMPILE)ld
##CC = /home/e123/workspace/V536_CDR_v2.0/out/v536-cdr/staging_dir/toolchain/bin/arm-openwrt-linux-muslgnueabi-gcc-6.4.1
obj-m := led_platform_driver.o led_platform_device.o
KVERS = /lib/modules/$(shell uname -r)/build
##KVERS = /home/e123/workspace/V536_CDR_v2.0/lichee/linux-4.9
build: kernel_modules
kernel_modules:
make ARCH=arm -C $(KVERS) M=$(CURDIR) modules
clean:
make ARCH=arm -C $(KVERS) M=$(CURDIR) clean
测试程序:
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(){
int fd = open("/dev/myled0", O_RDWR);
if(fd < 0) {
printf("open faild!");
return -1;
}
long a = 500;
int b = 1;
scanf(" %d", &a);
if(a < 0 || a > 123456789) a = 1000;
a = a * 1000;
while(1) {
usleep(a);
b ^= 1;
write(fd, &b, 1);
}
close(fd);
return 0;
}
现象:可以看到红色的LED按照输入的时间间隔(毫秒)闪烁。