从零开始之驱动发开、linux驱动(七、字符驱动之led最终版)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/82468500

我们上一接用的gpiolib非常杂乱,使用了两种

1.各硬件厂商实现的,如s3c_gpio_cfgpin(S5PV210_GPJ0(5), S3C_GPIO_SFN(1))

2.系统公共的,如gpio_set_value(S5PV210_GPJ0(4),val)

其实公共的也是对各硬件厂商的封装,用那个都可以,个人建议用系统公共的,应为这样换芯片厂商,不影响任何使用。

2.错误处理基本没做,甚至很烂,这节修改。

代码如下。

#include <linux/fs.h>       /* 包含file_operation结构体 */
#include <linux/init.h>     /* 包含module_init module_exit */
#include <linux/module.h>   /* 包含LICENSE的宏 */
#include <asm/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <asm/gpio.h>
#include <linux/gfp.h>



static unsigned int major;
static struct class *leds_class;
static struct device *led_dev[4];



/* open函数 */
static int leds_drv_open(struct inode *inode, struct file *file)
{
    /* 得到要操作的次设备号,进行对应的操作 */
    int minor = MINOR(inode->i_rdev);

   /* 因为在申请使用gpiolib时已经默认初始化为输出高电平了,这里只是展示有单独的操纵gpio配置的函数 */
    switch(minor)
    {   
        case 0:
        /* 初始化gpj0_3.4.5为输出 */
        /* 默认关闭led灯 */
        gpio_direction_output(S5PV210_GPJ0(3), 1);
        gpio_direction_output(S5PV210_GPJ0(4), 1);
        gpio_direction_output(S5PV210_GPJ0(5), 1);
        break;

        case 1:
        /* 初始化gpj0_3为输出 */
        /* 默认关闭led灯 */
        gpio_direction_output(S5PV210_GPJ0(3), 1);
        break;

        case 2:
        /* 初始化gpj0_4为输出 */
        /* 默认关闭led灯 */
        gpio_direction_output(S5PV210_GPJ0(4), 1);
        break;

        case 3:
        /* 初始化gpj0_5为输出 */
        /* 默认关闭led灯 */
        gpio_direction_output(S5PV210_GPJ0(5), 1);
        break;

        default:
        break;
    }

    return 0;
}

static ssize_t leds_drv_write(struct file *file, const char __user * from, size_t len, loff_t *ppos)
{
    char buf[10];
    int ret, val;

    /* 得到要操作的次设备号,进行对应的操作 */
    int minor = MINOR(file->f_inode->i_rdev);

    memset(buf, 0,10);

    /* 拷贝用户空间的数据到内核空间 */
    ret = copy_from_user(buf, from, 3);
    if(ret)
    {
        printk("copy_from_user fail\n");
    }
    /* 比较用户空间传的参数,来控制io状态 */
    if(strcmp("on", buf))
    {
        val = 1;
    }
    else
    {
        val = 0;
    }

     /* 根据参数设置led的输出 */
    switch(minor)
    {
        case 0:
            if(val)
            {
                gpio_set_value(S5PV210_GPJ0(3), val);
                gpio_set_value(S5PV210_GPJ0(4), val);
                gpio_set_value(S5PV210_GPJ0(5), val);
            }
            else
            {
                gpio_set_value(S5PV210_GPJ0(3), val);
                gpio_set_value(S5PV210_GPJ0(4), val);
                gpio_set_value(S5PV210_GPJ0(5), val);
            }
        break;

        case 1:
            gpio_set_value(S5PV210_GPJ0(3),val);
        break;

        case 2:
            gpio_set_value(S5PV210_GPJ0(4),val);
        break;

        case 3:
            gpio_set_value(S5PV210_GPJ0(5),val);
        break;

        default:
        break;
    }


    return 0;
}


static const struct file_operations leds_drv_file_operation = {
    .owner = THIS_MODULE,
    .open  = leds_drv_open,
    .write = leds_drv_write,
};

/* 要申请的gpio,默认初始化为输出高电平 */
static const struct gpio leds_gpio[] = {
    {S5PV210_GPJ0(3), GPIOF_OUT_INIT_HIGH, "led1-gpj0-3"},
    {S5PV210_GPJ0(4), GPIOF_OUT_INIT_HIGH, "led2-gpj0-4"},
    {S5PV210_GPJ0(5), GPIOF_OUT_INIT_HIGH, "led3-gpj0-5"},
};



static int __init leds_drv_init(void)
{
    int i;

    /* 获取一个自动的主设备号 */
    major =  register_chrdev(0,"leds_drv",&leds_drv_file_operation);
    if(major < 0)
    {
        printk("register_chrdev leds_drv fail \n");
        goto err_register_chrdev;
    }

    /* 创建一个类 */
    leds_class = class_create(THIS_MODULE, "leds_class");
    if(!leds_class)
    {
        printk("class_create leds fail\n");
        goto err_class_create;
    }

    /* 创建从属这个类的设备 */
    led_dev[0] = device_create(leds_class,NULL,MKDEV(major, 0), NULL, "leds");
    if(!led_dev[0])
    {
        goto err_device_create_led0;
    }



    /* 创建其它三个设备 */
    for(i = 1; i < 4; i++)
    {
        led_dev[i] = device_create(leds_class,NULL,MKDEV(major, i), NULL, "led%d",i);
        if(!led_dev[i])
        {
            goto err_device_create_leds;
        }
    }

    /* 使用gpiolib申请gpio */
    if(gpio_request_array(leds_gpio, 3))
    {
        goto err_gpio_request_array;
    }

    return 0;


/* 倒影式错误处理机制 */
err_gpio_request_array:
    for(i-- ; i > 0; i--)
    {
        device_unregister(led_dev[i]);
    }
err_device_create_leds:
    device_unregister(led_dev[0]);
err_device_create_led0:
    class_destroy(leds_class);
err_class_create:
    unregister_chrdev(major,"leds_drv");
err_register_chrdev:

    return -EIO;

}




static void __exit leds_drv_exit(void)
{
    int i;


    /* 注销类里面的设备 */
    for(i = 0; i < 4; i++)
    {
        device_unregister(led_dev[i]);
    }
    /* 注销字符设备 */
    unregister_chrdev(major,"leds_drv");
    /* 注销类 */
    class_destroy(leds_class);
    /* 释放申请的gpio */
    gpio_free_array(leds_gpio, 3);
}

module_init(leds_drv_init);
module_exit(leds_drv_exit);
MODULE_LICENSE("GPL");
      

关于错误处理:

写内核一定要细心,驱动注册中,一个错误,整个驱动都不能用了,为了让不能使用的驱动不再占用资源,要注销掉错误之前已经申请的资源。

可以看我的这篇博客。

https://blog.csdn.net/qq_16777851/article/details/80868787

关于注销函数:

以上面的注销函数为例

static void __exit leds_drv_exit(void)
{
    int i;

    /* 释放申请的gpio */
    gpio_free_array(leds_gpio, 3);

    /* 注销类里面的设备 */
    for(i = 0; i < 4; i++)
    {
        device_unregister(led_dev[i]);
    }
   
    /* 注销类 */
    class_destroy(leds_class);

    /* 注销字符设备 */
    unregister_chrdev(major,"leds_drv");
}

通常也要满足倒影式的注销,比如device_unregister注销的设备,从属某个类,从属某个主设备类型。必须先注销设备设备,再注销管理设备的类和主设备类型。

关于gpiolib,调试

第一种:/sys/class/中添加gpio接口

make menuconfig

Device Drivers  --->   
    -*- GPIO Support  --->     

查看目前gpio的使用情况

包括它属于哪个组,该组有多少个gpio,以及编号

第二种:debugfs

make meunconfig

Kernel hacking  --->    
    Compile-time checks and compiler options  --->   

debugfs的使用

安装我们的驱动后

可以看到我们驱动程序申请时的命令以及默认输出高电平都是一致的。

执行亮三个led灯操作

可以发现电平已经是低电平了。

debugfs中的gpio可以很好的调试一些具体的不容易测量的io的电平情况。

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/82468500