1.什么是gpiolib?
在平常的硬件驱动过程中,很多的硬件都要用到GPIO,GPIO会复用,如果同一时刻GPIO被两个驱动同时控制了,那么就会出现bug,所以内核提供了gpiolib来统一管理系统中所有的GPIO,gpiolib本身就是属于驱动框架的一部分!
在这部分的学习过程中,我们会分析gpiolib应该如何去学习,如何编程实现gpiolib,并且通过上一节中对LED的程序进行扩展,增加gpiolib控制来实现灯的变化!
2.如何学习gpiolib
gpiolib的架构(建立)
在内核启动,静态映射自动建立的时候,有一个函数xxx_gpiolib_init()会被自动加载!
然后进入到xxx_gpiolib_init()这个函数中,依次进行分析!
接下来就顺着主线分析s3c_gpio_chip这个结构体,首先可以看出来,这个结构体是一个gpio端口的抽象,这个结构体的一个变量就可以完全的描述一个IO端口!
这里区分端口和IO口: 一个Soc有很多的IO口,这些IO口首先被分成N个端口,然后每个端口有包含了M个IO口。譬如GPA0是一个端口,里面包含了8个IO口,我们一般记作GPA0_0(GPA0.0)、GPA0_1;
这个东西是一个结构体数组,数组里包含了很多个struct s3c_gpio_chip类型的变量!一个变量对应一个端口,重点是描述.chip这个元素!
其中.base 、.ngpio、.label成了GPIO中的一些属性:
通过上面的分析,我们已经大概知道了如何分析一个gpiolib的建立,并且在init函数的最后一句,我们有向内核建立gpiolib的函数。
这个函数才是具体进行gpiolib的注册的。这个函数接受的参数是我们当前文件定义好的结构体数组xxx_gpio_4bit(其实两个参数分别是数组名和数组元素个数),这个数组包含了当前系统中所有IO端口的信息。
gpiolib的使用方法
在这里我们不考虑,内核部分是如何实现的,只考虑如果我们作为厂商的驱动工程师,我们应该怎样使用gpiolib!也是比较重要的一部分!
gpiochip_add:是框架开出来的接口,给厂商驱动工程师用,用于向内核注册我们的gpiolib。
gpio_request:是框架开出来的接口,给使用gpiolib来编写自己的驱动的驱动工程师用的,驱动中想使用某一个gpio,就必须先调用gpio_request接口来向内核的gpiolib部分申请,得到允许后才能使用这个gpio。
gpio_free:对应gpio_request,用来释放申请后用完了的gpio。
gpio_request_one/gpio_request_array:这两个是gpio_request的变种。
gpiochip_is_requested:接口用来判断某一个gpio是否已经被申请了。
gpio_direction_input/gpio_direction_output:接口用来设置GPIO为输入/输出模式,注意该函数内部实际并没有对硬件进行操作,只是通过chip结构体变量的函数指针调用了将来Soc厂商的驱动工程师真正写的操作gpio设置成输出模式的那个函数!
3.通过编程实践测试gpiolib
第一步:使用gpio_request申请要是用的一个GPIO。
第二步:gpio_direction_input/gpio_direction_output设置成输入/输出模式。
第三步:设置输出值gpio_set_value,获取gpio_get_value.
通过以上三步和上一章节LED驱动框架编写LED的驱动程序!
#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>
///****此驱动使用的是led框架和gpiolib框架进行编写***
#define GPIO_LED1 S5PV210_GPJ0(3)
#define GPIO_LED2 S5PV210_GPJ0(4)
#define GPIO_LED3 S5PV210_GPJ0(5)
#define X210_LED_OFF 1 // X210中LED是正极接电源,负极节GPIO
#define X210_LED_ON 0 // 所以1是灭,0是亮
static struct led_classdev mydev1; // 定义结构体变量
static struct led_classdev mydev2; // 定义结构体变量
static struct led_classdev mydev3; // 定义结构体变量
// 这个函数就是要去完成具体的硬件读写任务的
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
if (value == LED_OFF)
{
// 用户给了个0,希望LED灭
//writel(0x11111111, GPJ0CON);
// 读改写三部曲
//writel((readl(GPJ0DAT) | (1<<3)), GPJ0DAT);
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 void s5pv210_led2_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
printk(KERN_INFO "s5pv2102_led_set\n");
//writel(0x11111111, GPJ0CON);
// 在这里根据用户设置的值来操作硬件
// 用户设置的值就是value
if (value == LED_OFF)
{
// 用户给了个0,希望LED灭
//writel(0x11111111, GPJ0CON);
// 读改写三部曲
//writel((readl(GPJ0DAT) | (1<<4)), GPJ0DAT);
}
else
{
// 用户给的是非0,希望LED亮
//writel(0x11111111, GPJ0CON);
//writel((readl(GPJ0DAT) & ~(1<<4)), GPJ0DAT);
}
}
static void s5pv210_led3_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
printk(KERN_INFO "s5pv210_led3_set\n");
//writel(0x11111111, GPJ0CON);
// 在这里根据用户设置的值来操作硬件
// 用户设置的值就是value
if (value == LED_OFF)
{
// 用户给了个0,希望LED灭
//writel(0x11111111, GPJ0CON);
// 读改写三部曲
//writel((readl(GPJ0DAT) | (1<<5)), GPJ0DAT);
}
else
{
// 用户给的是非0,希望LED亮
//writel(0x11111111, GPJ0CON);
//writel((readl(GPJ0DAT) & ~(1<<5)), GPJ0DAT);
}
}
static int __init s5pv210_led_init(void)
{
// 用户insmod安装驱动模块时会调用该函数
// 该函数的主要任务就是去使用led驱动框架提供的设备注册函数来注册一个设备
int ret = -1;
// 在这里去申请驱动用到的各种资源,当前驱动中就是GPIO资源
if (gpio_request(GPIO_LED1, "led1_gpj0.3")) //申请内存,如果成功返回0,失败返回1 如果返回0 说明成功,直接跳到else执行
{
printk(KERN_ERR "gpio_request failed\n");//返回1 说明失败,打印此句
}
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);//led_classdev_register就是注册设备
if (ret < 0) {
printk(KERN_ERR "led_classdev_register failed\n");
return ret;
}
// led2
mydev2.name = "led2";
mydev2.brightness = 0;
mydev2.brightness_set = s5pv210_led2_set;
ret = led_classdev_register(NULL, &mydev2);
if (ret < 0) {
printk(KERN_ERR "led_classdev_register failed\n");
return ret;
}
// led3
mydev3.name = "led3";
mydev3.brightness = 0;
mydev3.brightness_set = s5pv210_led3_set;
ret = led_classdev_register(NULL, &mydev3);
if (ret < 0) {
printk(KERN_ERR "led_classdev_register failed\n");
return ret;
}
return 0;
}
static void __exit s5pv210_led_exit(void)
{
led_classdev_unregister(&mydev1);
led_classdev_unregister(&mydev2);
led_classdev_unregister(&mydev3);
gpio_free(GPIO_LED1);
}
module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston <1264671872@qq.com>"); // 描述模块的作者
MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模块的介绍信息
MODULE_ALIAS("s5pv210_led"); // 描述模块的别名信息