前言
驱动开发框架由内核开发人员提供,SOC开发人员根据内核提供的驱动框架编写适合的硬件驱动,开发板厂商负责移植驱动到自己生产的开发板上,三者分工不同。
1 框架源码
文件路径: x210_kernel\drivers\leds
上面两个C源码文件由内核开发人员提供,简化了驱动开发的过程,例如:mknod /dev/名字 c 200 0
创建设备绑定主次设备号,这个步骤已经被包含在了框架里面,不需要自己在命令行中手动绑定。
框架中还提供了更加简便的字符设备注册函数,对注册的步骤进行了封装。
使用框架可以快速的进行驱动开发,和不使用框架开发实现的功能是相同的。
2 驱动源码
源码位置: kernel/drivers/leds/leds-s5pv210.c
平台总线式驱动注册
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <linux/platform_device.h>
#define GPIO_LED1 S5PV210_GPJ0(3) //虚拟地址
#define X210_LED_OFF 1 // X210中LED是正极接电源,负极节GPIO
#define X210_LED_ON 0 // 所以1是灭,0是亮
//框架中的函数,在两个.C文件的其中之一中
static struct led_classdev mydev1; // 定义结构体变量
// 这个函数就是要去完成具体的硬件读写任务的
static void s5pv210_led1_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
printk(KERN_INFO "s5pv210_led1_set\n");
//writel(0x11111111, GPJ0CON);
// 在这里根据用户设置的值来操作硬件
// 用户设置的值就是value
//框架中使用枚举对0和255和非0且非255进行了枚举类型转换
if (value == LED_OFF) //对应0
{
// 用户给了个0,希望LED灭
//writel(0x11111111, GPJ0CON);
// 读改写三部曲
//writel((readl(GPJ0DAT) | (1<<3)), GPJ0DAT);
//使用gpio库进行io寄存器设置
gpio_set_value(GPIO_LED1, X210_LED_OFF);
}
else
{
// 用户给的是非0,希望LED亮
//writel(0x11111111, GPJ0CON);
//writel((readl(GPJ0DAT) & ~(1<<3)), GPJ0DAT);
gpio_set_value(GPIO_LED1, X210_LED_ON);
}
}
static int s5pv210_led_probe(struct platform_device *dev)
{
// 用户insmod安装驱动模块时会调用该函数
// 该函数的主要任务就是去使用led驱动框架提供的设备注册函数来注册一个设备
int ret = -1;
// 在这里去申请驱动用到的各种资源,当前驱动中就是GPIO资源
if (gpio_request(GPIO_LED1, "led1_gpj0.3"))
{
printk(KERN_ERR "gpio_request failed\n");
}
else
{
// 设置为输出模式,并且默认输出1让LED灯灭
gpio_direction_output(GPIO_LED1, 1);
}
// led1
mydev1.name = "led1";
mydev1.brightness = 0;
mydev1.brightness_set = s5pv210_led1_set;
//框架封装的注册函数
ret = led_classdev_register(NULL, &mydev1);
if (ret < 0) {
printk(KERN_ERR "led_classdev_register failed\n");
return ret;
}
return 0;
}
static int s5pv210_led_remove(struct platform_device *dev)
{
led_classdev_unregister(&mydev1);
gpio_free(GPIO_LED1);
return 0;
}
static struct platform_driver s5pv210_led_driver = {
.probe = s5pv210_led_probe,
.remove = s5pv210_led_remove,
.driver = {
//这个名字要和注册的device名字形同
.name = "s5pv210_led",
.owner = THIS_MODULE,
},
};
static int __init s5pv210_led_init(void)
{
/*使用平台总线的形式进行驱动注册,当前内核中注册了平台总线
设备才能成功的注册平台总线驱动,两者注册的顺序没有现后之分,
两者注册的时候,都会寻找对方进行匹配*/
return platform_driver_register(&s5pv210_led_driver);
}
static void __exit s5pv210_led_exit(void)
{
platform_driver_unregister(&s5pv210_led_driver);
}
module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
这个驱动中使用了gpio_request
这个IO口申请函数,且九鼎自己实现的led驱动,也使用了这个函数,所以需要使用menuconfig取消对leds-x210.c这个文件的编译。一个IO口只能被申请一次,内核会进行记录。
上述驱动源码注册的设备地址:/sys/class/leds/led/led1,led1就是注册时候的名字。可以使用echo进行写入操作,控制LED的亮灭。