基于led框架及gpiolib管理的驱动编写

发布于:2025-07-05 ⋅ 阅读:(15) ⋅ 点赞:(0)

从led驱动来了解驱动框架

驱动框架:
1、内核中驱动部分维护者针对每个种类的驱动设计一套成熟的、标准的、典型的驱动实现,然后把不同厂家的同类硬件驱动中相同的部分抽出来自己实现好,再把不同部分留出接口给具体的驱动开发工程师来实现,这就叫驱动框架。
2、内核维护者在内核中设计了一些统一管控系统资源的体系,这些体系让内核能够对资源在各个驱动之间的使用统一协调和分配,保证整个内核的稳定健康运行。
譬如系统中所有的GPIO就属于系统资源,每个驱动模块如果要使用某个GPIO就要先调用特殊的接口先申请,申请到后使用,使用完后要释放。又譬如中断号也是一种资源,驱动在使用前也必须去申请。这也是驱动框架的组成部分。
3、一些特定的接口函数、一些特定的数据结构,这些是驱动框架的直接表现


内核驱动框架中LED的基本情况
1、
驱动led的路径:
drivers\kernel\drivers\leds
这个目录就是驱动框架规定的led这种硬件驱动放的地方
drivers这个文件夹中存放的都是各个硬件的驱动

2、
其中,核心文件代码
led-class.c  、 led-core.c
这两个文件共同构成了led驱动的第一部分。这两个文件由内核开发者提供
他们描述的是内核中所有厂家的不同LED硬件的相同部分的逻辑。

leds-xxxx.c,这个文件是LED驱动框架的第2部分,是由不同厂商的驱动工程师编写添加的,厂商驱动工程师结合自己公司的硬件的不同情况来对LED进行操作,使用第一部分提供的接口来和驱动框架进行交互,最终实现驱动的功能

*****************
现在,我们用的是jiuding的开发板,有一个情况就是,其未使用这个内核推荐的这个led驱动框架
路径:
drivers\char\led\x210-led.c
写好了这个驱动的现象:
rootfs中,找到/sys/devices/platform/x210-led/
进入这个目录,会led1、led2...
直接输入:
echo 1 -> led1
对应的led就会亮
echo 0 -> led1
对应的led就会亮


*************************
写驱动的一个要点就是:
知道内核开发者提供了什么,我们要自己利用它要提供什么

例如:
leds-s3c24xx.c中,
led_classdev_register这一个函数,
就是调用的是drivers\kernel\drivers\leds\led-class.c

所以我们就用过合理调用内核开发者在驱动中提供的接口来进一步实现自己要完成的驱动工作
做产品的厂商的驱动工程师以SoC厂商提供的驱动源码为基础,来做移植和调试


************************
代码分析:
subsys_initcall(leds_init);
subsys_initcall是一个宏定义,在/linux/init.h
subsys_initcall
#define subsys_initcall(fn)        __define_initcall("4",fn,4)

#define __define_initcall(level,fn,id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" level ".init"))) = fn

section__(".initcall" level ".init")
这个宏的功能是将这个声明的函数放到一个特定的段:.initcall4.init


为什么要设置这样的一个段?
原因就是内核在启动过程中,需要顺序去做的事有很多,如何去处理这个顺序初始操作。
解决的方法就是:
给内核启动时要顺序执行的这些函数归类,然后每个类按一定的顺序去调用执行
这个分类名就是:.initcallx.init  这个x的值就是0-7
内核开发者在处理这些函数时,只需要将这些函数设置合适的级别,这些函数就会被连接的时候放进特定的段,内核启动的时候按照这些段的次序去依次执行即可

#define module_init(x)    __initcall(x);
#define __initcall(fn) device_initcall(fn);
#define device_initcall(fn)        __define_initcall("6",fn,6)

subsys_initcall
module_init
实质上其实都是差不多的,只不过执行级别不一样而已

***************************
leds_init
    class_create 创建一个类 
    led_suspend: 
    在系统挂起时被调用,用于关闭LED或将其设置为低功耗状态。

    led_resume: 
    在系统恢复时被调用,用于重新开启LED或恢复到正常工作状态。
(嵌入式系统或设备驱动中常见的电源管理函数,主要用于控制 LED 设备在系统低功耗状态(如休眠、待机)下的行为)
    static struct device_attribute led_class_attrs[] = {
    __ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
    __ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
#ifdef CONFIG_LEDS_TRIGGERS
    __ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
#endif
    __ATTR_NULL,
};


device_attribute的定义:
    struct device_attribute {
        struct attribute    attr;
        ssize_t (*show)(struct device *dev, struct device_attribute *attr,
                char *buf);
        ssize_t (*store)(struct device *dev, struct device_attribute *attr,
                 const char *buf, size_t count);
    };


这里涉及到的是attribute 、 led_brightness_show 、led_brightness_store这三个函数
attribute:
1、对应的是将来/sys/class/leds目录的内容,一般是文件和文件夹。
这些文件实质就是sys开放给应用层的一些操作接口(类似于/dev/目录下的那些设备文件)
2、作用:
让应用程序可以通过/sys/class/leds目录下的是随性文件来操作驱动进而操作硬件设备
3、attribute实质上是另一条驱动实现的路线。区别于之前的fos


************************************
led_classdev_register
    device_create
    设备驱动的创建(class中的文件)

led_classdev_register:
这个函数实质上就是led驱动框架中内核开发者提供给soc厂家驱动开发者的一个注册驱动的接口

当我们使用led驱动框架去编写驱动的时候,这个led_classdev_register函数的作用类似于我们之前使用file_operations方式去注册字符设备驱动时的register_chrdev函数。


************************
去掉jiuding写的led驱动,自己写一个
1、首先去掉九鼎移植的驱动在应用层的接口在/sys/devices/platform/x210-led/目录下,有led1、led2、led3、led4四个设备文件,各自管理一个led。
2、要去掉九鼎自己移植的led驱动,要在make menucofig中去掉选择项,然后重新make得到zImage,然后重启时启动这个新的zImage即可。
3、新的内核启动后,如果/sys/devices/platform/目录下已经没有了x210-led这个目录,就说明我们去掉这个驱动成功了。


**********************
参考leds-s3c24xx写一个简单的led框架的驱动
用到函数:
module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

static int __init s5pv210_led_init(void)
static void __exit s5pv210_led_exit(void)

用到的注册函数:
led_classdev_register();

led_classdev_unregister();

其中,注册函数用到的参数:
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)

一个涉及到父级的
一个就是基于struct led_classdev结构体的

所以使用这个函数,要对此结构体实例化:
这次用到的
struct led_classdev {
    const char        *name;
    int             brightness;
    brightness_set

一个是创建的驱动的名字
brightness一个是调节led驱动的亮度
max_brightness 最大的亮度255

mydev.brightness_set = s5pv210_led_set;
    mydev.brightness = 255;
    mydev.name = "s5_led1";
    
ret = led_classdev_register(0 &mydev);//创建一个类    

s5pv210_led_set led的实现硬件驱动函数
static void s3c24xx_led_set(struct led_classdev *led_cdev,
                enum led_brightness value)

enum led_brightness {
    LED_OFF        = 0,
    LED_HALF    = 127,
    LED_FULL    = 255,
};

static void s5pv210_led_set(struct led_classdev *led_cdev,
                enum led_brightness value)
{
    
    printk(KERN_INFO "s5pv210_led_set\n");
    
    if(value == LED_OFF)
    {
        writel(0x11111111,GPJ0CON);
        writel(((1<<3) | (1<<4) | (1<<5)),GPJ0DAT);
    }
    else 
    {
        writel(0x11111111,GPJ0CON);
        writel(((0<<3) | (0<<4) | (0<<5)),GPJ0DAT);
    }
    
}

这就是一个简单的基于led驱动框架编写需要用到的函数

不过基本在驱动中,应该是设置每一个led对应的驱动程序

所以,对应结构体也要增加

static struct led_classdev mydev1;
static struct led_classdev mydev2;


对应操作函数要增加
s5pv210_led1_set()
s5pv210_led2_set()

初始化(实例化)mydev1 mydev2

mydev1.name = "s5_led1";
mydev1.brightness = 255;
mydev1.brightness_set = s5pv210_led1_set;

mydev2.name = "s5_led2";
mydev2.brightness = 255;
mydev2.brightness_set = s5pv210_led2_set;

**********************************
gpiolib:
在使用中,由于很多硬件的都会用到GPIO,难免会遇到GPIO被复用的情况,
然后当这种情况出现时,就是同一个io被2个驱动同时控制了,就会有bug出现,
所以,内核提供gpiolib来同意管理系统中的所有gpio,
gpiolib本身属于驱动框架中的一部分

学习gpiolib:
明白它的建立过程
使用方法:申请、使用、释放
涉及目录及文件
分析:
整个gpiolib分为三部分

目标函数:
/arch/arch/mach-s5pv210/Mach-smdkc110.c
smdkc110_map_io
    s5pv210_gpiolib_init //这个函数就是我们gpiolib初始化函数
    

/arch/arch/mach-s5pv210/gpiolib.c
s5pv210_gpiolib_init


************************

第一部分:厂商驱动工程师负责的这一部分

1、:

struct s3c_gpio_chip *chip = s5pv210_gpio_4bit;

使用了s3c_gpio_chip这个结构体定义了里面一个chip的结构体
struct s3c_gpio_chip {
    struct gpio_chip    chip;
    struct s3c_gpio_cfg    *config;
    struct s3c_gpio_pm    *pm;
    void __iomem        *base;
    int            eint_offset;
    spinlock_t         lock;
#ifdef CONFIG_PM
    u32            pm_save[7];
#endif
};

gpio_chip 结构体里面是一个gpio的抽象,这些结构体的变量就可以
描述一个io端口(注意是端口)
端口和io口是不一样的,端口指的是一个大类,(指将s5pv210中160多个io口分为N个端口),
如GPA0,GPB0...
而每个端口又包含M个io口,如GPA0_0、GPA0_1..

而且,内核中为每个gpio分配了一个编号,编号是一个数字,表明每一个io口


struct gpio_chip {
    const char        *label;
    struct device        *dev;
    struct module        *owner;

    int            (*request)(struct gpio_chip *chip,
                        unsigned offset);
    void            (*free)(struct gpio_chip *chip,
                        unsigned offset);

    int            (*direction_input)(struct gpio_chip *chip,
                        unsigned offset);
    int            (*get)(struct gpio_chip *chip,
                        unsigned offset);
    int            (*direction_output)(struct gpio_chip *chip,
                        unsigned offset, int value);
    int            (*set_debounce)(struct gpio_chip *chip,
                        unsigned offset, unsigned debounce);

    void            (*set)(struct gpio_chip *chip,
                        unsigned offset, int value);

    int            (*to_irq)(struct gpio_chip *chip,
                        unsigned offset);

    void            (*dbg_show)(struct seq_file *s,
                        struct gpio_chip *chip);
    int            base;
    u16            ngpio;
    const char        *const *names;
    unsigned        can_sleep:1;
    unsigned        exported:1;
};


s5pv210_gpio_4bit
这个结构体中,是一个结构体数组,分别代表不同的io端口
base:每一个端口的首地址
ngpio:
label:首个端口及io口名字
to_irq:中断部分用的

.base    = S5PV210_GPA0(0):
S5PV210_GPIO_A0_START    = 0,

_nr 的本质
_nr 表示 GPIO控制器内部的引脚偏移量(从0开始)

它标识的是单个GPIO控制器内部的物理引脚编号(如GPA0端口的第0脚、第1脚等)

#define S5PV210_GPA0(_nr)    (S5PV210_GPIO_A0_START + (_nr))

ngpio这个表示的是有几个io口
.ngpio    = S5PV210_GPIO_A0_NR,
#define S5PV210_GPIO_A0_NR    (8)

端口名
.label    = "GPA0",

static struct s3c_gpio_chip s5pv210_gpio_4bit[] = {
    {
        .chip    = {
            .base    = S5PV210_GPA0(0),
            .ngpio    = S5PV210_GPIO_A0_NR,
            .label    = "GPA0",
            .to_irq = s5p_gpiolib_gpioint_to_irq,
        },
    }, {
        .chip    = {
            .base    = S5PV210_GPA1(0),
            .ngpio    = S5PV210_GPIO_A1_NR,
            .label    = "GPA1",
            .to_irq = s5p_gpiolib_gpioint_to_irq,
        },


****************************
2、:
s5pv210_gpiolib_init函数中:

int nr_chips = ARRAY_SIZE(s5pv210_gpio_4bit);
    int i = 0;

    for (i = 0; i < nr_chips; i++, chip++) {
        if (chip->config == NULL)
            chip->config = &gpio_cfg;
        if (chip->base == NULL)
            chip->base = S5PV210_BANK_BASE(i);
    }

这段代码就是先获取端口的数量,然后遍历

先查看是否对config有定义,不然就选择默认操作
就是对gpio的初始化,下拉模式
两个函数其实差不多,只是定义数据类型的不同
gpio_cfg

static struct s3c_gpio_cfg gpio_cfg = {
    .set_config    = s3c_gpio_setcfg_s3c64xx_4bit,
    .set_pull    = s3c_gpio_setpull_updown,
    .get_pull    = s3c_gpio_getpull_updown,
};

static struct s3c_gpio_cfg gpio_cfg_noint = {
    .set_config    = s3c_gpio_setcfg_s3c64xx_4bit,
    .set_pull    = s3c_gpio_setpull_updown,
    .get_pull    = s3c_gpio_getpull_updown,
};

if (chip->base == NULL)
            chip->base = S5PV210_BANK_BASE(i);
这里的话,就是判断有无定义,如果为空,就自动赋值,
赋值方式:
#define S5PV210_BANK_BASE(bank_nr)    (S5P_VA_GPIO + ((bank_nr) * 0x20))

#define S3C_ADDR_BASE    (0xFD000000)
#define S5P_VA_GPIO            S3C_ADDR(0x00500000)
这里的是一个虚拟地址,就是0xfd500000
后面的地址偏移量就是每个端口的基地址偏移量,都是相差0x20


***********************************
3、:
arch/arm/plat-Samsung/gpiolib.c

samsung_gpiolib_add_4bit_chips(s5pv210_gpio_4bit, nr_chips);
    samsung_gpiolib_add_4bit(chip);
        chip->chip.direction_input = samsung_gpiolib_4bit_input;
        chip->chip.direction_output = samsung_gpiolib_4bit_output;
        chip->pm = __gpio_pm(&s3c_gpio_pm_4bit);
    
    s3c_gpiolib_add(chip);
    
这个函数没有对gpiolib的注册工作,只是进行了相关的进一步的填充,
填充的是每一个gpio设置为输入输出操作方法
    
****
这里的函数有两个,其中不同的只是bit不同,
这是对应的CON寄存器对应的io口的操作不同,
我们现在用的是4bit对应一个io口,
有的是使用2bit对应一个io口

    
void __init samsung_gpiolib_add_4bit_chips(struct s3c_gpio_chip *chip,
void __init samsung_gpiolib_add_4bit2_chips(struct s3c_gpio_chip *chip,

***********
这个函数在arch/arm/plat-Samsung/gpio.c

上面说的进行的是填充工作,指的是表面的函数内部的工作
追根到s3c_gpiolib_add函数内部,实际这里进行了相关的注册
__init void s3c_gpiolib_add(struct s3c_gpio_chip *chip)

函数里面首先是读取
struct gpio_chip *gc = &chip->chip;
这个结构体的chip,就是相应的端口
然后进行检测并完善下面的这几个函数
在结构体中就是一个函数指针
if (!gc->direction_input)
        gc->direction_input = s3c_gpiolib_input;
    if (!gc->direction_output)
        gc->direction_output = s3c_gpiolib_output;
    if (!gc->set)
        gc->set = s3c_gpiolib_set;
    if (!gc->get)
        gc->get = s3c_gpiolib_get;

函数定义:
int            (*direction_input)(struct gpio_chip *chip,
                        unsigned offset);
int            (*direction_output)(struct gpio_chip *chip,
                        unsigned offset, int value);
void            (*set)(struct gpio_chip *chip,
                        unsigned offset, int value);
int            (*get)(struct gpio_chip *chip,
                        unsigned offset);


函数底部进行真正的注册:
/drivers/gpio/gpiolib.c
ret = gpiochip_add(gc);

函数内部:
1、
int        base = chip->base;
获取结构体的相关信息,就是相关gpio端口端口的全部信息

2、
if ((!gpio_is_valid(base) || !gpio_is_valid(base + chip->ngpio - 1))
进行判断
基地址或者io口错误,就会直接报错

3、
进行一个base判断
if (base < 0) {
        base = gpiochip_find_base(chip->ngpio);
        if (base < 0) {
            status = base;
            goto unlock;
        }
        chip->base = base;
    }

这个函数的作用就是关于热插拔相关的,使用动态分配gpio,找到其相关base address
/* dynamic allocation of GPIOs, e.g. on a hotplugged device */
static int gpiochip_find_base(int ngpio)


4、
把我们封装好的gpio端口的所有的信息(chip结构体中)挂接到
内核gpiolib模块定义的一个gpio_desc数组的某一个格子中
static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];

struct gpio_desc {
    struct gpio_chip    *chip;
    unsigned long        flags;
/* flag symbols are bit numbers */
#define FLAG_REQUESTED    0
#define FLAG_IS_OUT    1
#define FLAG_RESERVED    2
#define FLAG_EXPORT    3    /* protected by sysfs_lock */
#define FLAG_SYSFS    4    /* exported via /sys/class/gpio/control */
#define FLAG_TRIG_FALL    5    /* trigger on falling edge */
#define FLAG_TRIG_RISE    6    /* trigger on rising edge */
#define FLAG_ACTIVE_LOW    7    /* sysfs value has active low */

#define PDESC_ID_SHIFT    16    /* add new flags before this one */

#define GPIO_FLAGS_MASK        ((1 << PDESC_ID_SHIFT) - 1)
#define GPIO_TRIGGER_MASK    (BIT(FLAG_TRIG_FALL) | BIT(FLAG_TRIG_RISE))

#ifdef CONFIG_DEBUG_FS
    const char        *label;
#endif
};


*******************
现在到注册的这一步的话,就是s5pv210_gpiolib_init的主要
而函数内部完成的,主要是结构体信息(gpio端口)的填充及通过这个
结构体信息的获取到内核的gpiolib模块中的过程,最后挂载在desc这个结构体,
最后在s3c_gpiolib_add这个函数中调用gpiochip_add(gc)这个函数完成注册,
就是把信息放到gpio_desc[id].chip


*************************
第二部分:
内核开发者要提供的驱动框架的这一部分

整个drivers/gpio/gpiolib.c

整个文件分为两类:
1、写给其它驱动并且用到了gpiolib的人使用的
int gpiochip_add(struct gpio_chip *chip)
是框架开出来的接口,给厂商驱动工程师使用,用于向内核注册我们的gpiolib

int gpio_request(unsigned gpio, const char *label)
是框架开出来的接口,给使用gpiolib来编写自己的驱动的工程师用的,
驱动中要向使用一个gpio,就必须先调用gpio_request接口来向内核的
gpiolib部分申请,得到允许后才可以使用这个gpio

void gpio_free(unsigned gpio)
对于free,是用来释放申请后用完了的gpio

int gpio_request_one(unsigned gpio, unsigned long flags, const char *label)
是用来申请单个gpio,和上面的申请一样,不过是多了一个flag

int gpio_request_array(struct gpio *array, size_t num)
这个是用来解决单个申请的复杂性的申请接口,可以将多个gpio放到
一个数组中,依次申请多个gpio

void gpio_free_array(struct gpio *array, size_t num)
这个就是用来释放 申请了一个数组的gpio


const char *gpiochip_is_requested(struct gpio_chip *chip, unsigned offset)
这个是用来检测某个gpio是否已经被申请

int gpio_direction_input(unsigned gpio)
int gpio_direction_output(unsigned gpio, int value)
这个就接口用来设置gpio为输入/输出模式,函数内部并没有对硬件进行操作,
只是通过chip结构体变量的函数指针调用了将来soc厂商的驱动工程师写的真正的
操作硬件实现gpio设置为输出模式那个函数
status = chip->direction_output(chip, gpio, value);

int gpio_set_debounce(unsigned gpio, unsigned debounce)
这个函数是用来消抖的

int __gpio_get_value(unsigned gpio)
void __gpio_set_value(unsigned gpio, int value)
这两个函数是用来设置/获取gpio的值的,就是电平
这个实质上是给内核内部使用的,我们调用的是它的宏定义的那个

在arch/arm/mach-s5pv210/include/mach/gpio.h
#include <mach/gpio.h>
#define gpio_get_value    __gpio_get_value
#define gpio_set_value    __gpio_set_value

************
2、:
第二类就是剩下的那些函数,这类函数是gpiolib内部自己的一些功能实现的
代码


这两个函数,是在attribute那里被定义的一个属性,show、store,
是可以进行读/写操作,在类中写的那个驱动函数中,show xx 就是把里面的
硬件操作属性读出来
如 show brightness

store 就是写
在那里输出  echo 1/0 > brightness
实际上执行的就是store
 

static ssize_t gpio_direction_show(struct device *dev,
        struct device_attribute *attr, char *buf)

static ssize_t gpio_direction_store(struct device *dev,
        struct device_attribute *attr, const char *buf, size_t size)

*****************************
gpiolib的attribute部分
在.config文件中配置选择y
CONFIG_GPIO_SYSFS=y

所以文件中的#ifdef CONFIG_GPIO_SYSFS这个条件内容才能实现
注意有两处地方涉及到一个是一个结构体,
一个是函数,主要是那几个attribute的实现

下面讲到有一个class的注册和devices_group的create
那么在rootfs中的实现就是
[root@sugar /root]# cd /sys/class/gpio/
[root@sugar gpio]# ls
export       gpiochip164  gpiochip235  gpiochip316  gpiochip396  gpiochip56
gpiochip0    gpiochip172  gpiochip244  gpiochip325  gpiochip40   gpiochip62
gpiochip104  gpiochip181  gpiochip253  gpiochip334  gpiochip405  gpiochip71
gpiochip112  gpiochip188  gpiochip262  gpiochip343  gpiochip414  gpiochip80
gpiochip120  gpiochip197  gpiochip271  gpiochip35   gpiochip423  gpiochip89
gpiochip128  gpiochip206  gpiochip280  gpiochip351  gpiochip431  gpiochip9
gpiochip137  gpiochip212  gpiochip289  gpiochip360  gpiochip438  gpiochip96
gpiochip14   gpiochip221  gpiochip29   gpiochip369  gpiochip447  unexport
gpiochip146  gpiochip226  gpiochip298  gpiochip378  gpiochip456
gpiochip155  gpiochip23   gpiochip307  gpiochip387  gpiochip47


static int __init gpiolib_sysfs_init(void)
    status = class_register(&gpio_class);
        static struct class gpio_class = {
        .name =        "gpio",
        .owner =    THIS_MODULE,

        .class_attrs =    gpio_class_attrs,
        };
        这是注册了一个gpio的class
        
    status = gpiochip_export(chip);
        dev = device_create(&gpio_class, chip->dev, MKDEV(0, 0), chip,
                "gpiochip%d", chip->base);
        函数中注册了设备名字
    status = sysfs_create_group(&dev->kobj,
                &gpiochip_attr_group);
        
        return internal_create_group(kobj, 0, grp);


在class中还有export、unexport这两个,它的作用就是将某一个io的信息打印出来
eg:
root@sugar gpio]# echo 47 > export 
[root@sugar gpio]# ls
export       gpiochip155  gpiochip23   gpiochip307  gpiochip387  gpiochip47
gpio47       gpiochip164  gpiochip235  gpiochip316  gpiochip396  gpiochip56

gpio47就是导出来的那个io的全部信息
[root@sugar gpio]# cd gpio47/
[root@sugar gpio47]# ls
active_low  direction   edge        power       subsystem   uevent      value
[root@sugar gpio47]# cat edge
none
[root@sugar gpio47]# cat value 
1
[root@sugar gpio47]# cat active_low 
0

当查看完毕后,
[root@sugar gpio]# echo 47 >unexport 
[root@sugar gpio]# ls
export       gpiochip164  gpiochip235  gpiochip316  gpiochip396  gpiochip56
gpiochip0    gpiochip172  gpiochip244  gpiochip325  gpiochip40   gpiochip62


这两个export、unexport在代码中的实现:
static struct class_attribute gpio_class_attrs[] = {
    __ATTR(export, 0200, NULL, export_store),
    __ATTR(unexport, 0200, NULL, unexport_store),
    __ATTR_NULL,
};

在class中填充,然后在class 注册时一起注册

代码在:
在gpiochip_export中使用
dev = device_create(&gpio_class, chip->dev, MKDEV(0, 0), chip,
                "gpiochip%d", chip->base);

******
进入到某个gpiochip,
这里包含有这个端口的信息:
[root@sugar gpio]# cd gpiochip0
[root@sugar gpiochip0]# ls
base       label      ngpio      power      subsystem  uevent
[root@sugar gpiochip0]# cat base 
0
[root@sugar gpiochip0]# cat label 
GPA0
[root@sugar gpiochip0]# cat ngpio 
8

代码中的实现:
static const struct attribute *gpiochip_attrs[] = {
    &dev_attr_base.attr,
    &dev_attr_label.attr,
    &dev_attr_ngpio.attr,
    NULL,
};

static const struct attribute_group gpiochip_attr_group = {
    .attrs = (struct attribute **) gpiochip_attrs,
};


在gpiochip_export中使用
if (!IS_ERR(dev)) {
        status = sysfs_create_group(&dev->kobj,
                &gpiochip_attr_group);


还有一个value
static const DEVICE_ATTR(value, 0644,
        gpio_value_show, gpio_value_store);
这是在devices中某个io的操作


***********************************

至此,gpiolib分析完毕


**********************************
使用gpiolib完成led驱动

驱动实现流程:
1、使用int gpio_request(unsigned gpio, const char *label)
申请要操作的gpio
2、设置gpio输入/输出模式
int gpio_direction_input(unsigned gpio)
    int gpio_direction_output(unsigned gpio, int value)
3、设置gpio操作电平
获取电平:int __gpio_get_value(unsigned gpio)
设置电平:void __gpio_set_value(unsigned gpio, int value)


代码:

首先申请
ret = gpio_request(GPIO_LED1, "GPH0");
    if(ret)
        printk(KERN_ERR "gpio_request failed\n");

申请成功设置输入输出模式:
gpio_direction_output(GPIO_LED1, 1);


然后注册:
led_cdev1.brightness_set =s5pv210_led1_set;
    led_cdev1.brightness = 0;
    led_cdev1.name = "led1";
    
    ret = led_classdev_register(NULL, &led_cdev1);
    if (ret < 0) {
        printk(KERN_ERR "led_classdev_register failed\n");
        return ret;
    }


对应函数:注销和释放
led_classdev_unregister(&led_cdev1);
    gpio_free(GPIO_LED1);


操作函数:
结构体对应的
s5pv210_led1_set

操作:
if( value== LED_OFF )
    {
        gpio_set_value(GPIO_LED1,X210_LED_OFF);        
        printk(KERN_INFO "X210_LED_OFF\n");
    }
    else
    {
        gpio_set_value(GPIO_LED1,X210_LED_ON);    
        printk(KERN_INFO "X210_LED_ON\n");
    }


这是一个利用gpiolib来进行管理注册驱动并操作相应硬件的文件


*************************
利用debugfs查看gpio使用信息
[root@sugar gpiochip172]# cat label 
GPJ0
[root@sugar gpiochip172]# 

[root@sugar /sys]# mount -t debugfs debugfs /tmp
[root@sugar /tmp]# ls
apanic          binder          ieee80211       mmc2            pmstats
asoc            gpio            mmc0            mmc3            sched_features
bdi             hid             mmc1            pmem_gpu1       usb
[root@sugar /tmp]# cat gpio

GPIOs 172-179, GPJ0:
 gpio-175 (GPH0                ) out hi

使用完成后:
[root@sugar /]# umount /tmp


*****************************
将驱动加载到内核

主要是三个

1、将编写的.c文件复制到drivers/leds中,就是写的是什么类型的
驱动就放到什么位置


2、修改makefile,按类型补充到对应位置
添加的config将来会在.config文件中

3、kconfig文件,如果缺少这里的配置文件,那么在make menuconfig
操作中就不会出现这个配置项


**************************
整个框架学习总共就是两部分:
1、参考别人的驱动led框架编写
2、补充gpiolib框架,主要就是管理gpio


 


网站公告

今日签到

点亮在社区的每一天
去签到