Embedded LINUX driver learning 4. Character device driver programming (2) Kernel driver implementation code

@ TOC kernel driver implementation code)

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/gpio.h>
#include <mach/platform.h>
#include <linux/uaccess.h>
static dev_t file_dev_num;//设备号
static struct cdev file_cdev;
/*B.1硬件资源配置*/
struct led_source {
    
    //定义一个LED灯信息的结构体,包含名称和GPIO口两个成员变量;
    char *name;
    int gpio;
};
static struct led_source led_info[] = {
    
    
    {
    
    
        .name = "LED1",  //LED灯名字,可以随便取;
        .gpio = PAD_GPIO_C +12     //PAD_GPIO_C,不同芯片厂家,名称不同,根据芯片厂家提供的Linux内核中的宏编写;
    }, 
    {
    
    
        .name = "LED2",
        .gpio = PAD_GPIO_C +11
    },
    {
    
    
        .name = "LED3",
        .gpio = PAD_GPIO_C +7
    },
    {
    
    
        .name = "LED4",
        .gpio = PAD_GPIO_B +26
    }
};
/*D.2内核空间组用户空间提供的读操作接口函数*/
ssize_t mychar_dev_read(struct file * file,char __user * buf,\
                        size_t count,loff_t * ppos){
    
    
    int kbuf[4];
    int i =0;
    int ret_copy = 0 ;
    //读取当前GPIO的数值保存到kbuf数组中,
    //通过copy_to_user()函数将数据 从内核空间传递到用户空间
    for(;i < ARRAY_SIZE(led_info);i++){
    
    
        kbuf[i] = gpio_get_value(led_info[i].gpio);
    }
    ret_copy = copy_to_user(buf,kbuf,sizeof(int)*(i+1));
    return ret_copy;
}
/*D.3.1当用户空间向内核空间写操作接口写入开灯命令时,执行如下函数*/
void led_on(struct file * file){
    
    
    int i = 0;
    char file_name[64];//保存打开的文件名
    strcpy(file_name,file->f_path.dentry->d_iname);
    //将文件名的最后一个字符做为灯的编号;
    i = simple_strtoul(&file_name[strlen(file_name)-1],NULL,0);
    //设置GPIO为低电平 ,即:开灯
    gpio_set_value(led_info[i].gpio,0);
}
/*d.3.2当用户空间向内核空间写操作接口写入关灯命令时,执行如下函数*/
void led_off(struct file * file){
    
    
    int i = 0;
    char file_name[64];
    strcpy(file_name,file->f_path.dentry->d_iname);
    i = simple_strtoul(&file_name[strlen(file_name)-1],NULL,0);
    //设置GPIO为高电平 ,即:关灯
    gpio_set_value(led_info[i].gpio,1);
}
/*D.3内核空间组用户空间提供的写操作接口函数*/
ssize_t mychar_dev_write(struct file * file ,const char __user * buf,\
                         size_t count, loff_t *ppos)
{
    
    
    int  kbuf;
    int ret_copy = 0;
    ret_copy = copy_from_user(&kbuf,buf,4);
    switch(kbuf){
    
    
        case 0:
            led_on(file);//执行开灯操作
            break;
        case 1:
            led_off(file);//执行关灯操作
            break;
        default :
            break;
    }
    return ret_copy;
}
/*D.1给字符设备接口赋值,即:提供读写操作接口
static struct file_operations mychar_dev_fops = {
    .owner = THIS_MODULE,
    .read = mychar_dev_read,
    .write = mychar_dev_write,
};
/*A.0驱动模块加载时执行的函数*/
static int mychar_dev_init(void){
    
    
    //B.2初始化GPIO
    int i =0 ;
    int ret_chrdev;
    for(;i<ARRAY_SIZE(led_info);i++){
    
    
        gpio_request(led_info[i].gpio,led_info[i].name);//申请GPIO
        gpio_direction_output(led_info[i].gpio,1);//配置GPIO为输出口,高电平
    }
    //C.1 申请字符设备号,初始化、注册字符设备
    //申请了一个主设备号和4个次设备号,注册和释放时也要注册和释放4个;
    ret_chrdev = alloc_chrdev_region(&file_dev_num,0,4,"myfile_cdev");
    cdev_init(&file_cdev,&mychar_dev_fops);
    cdev_add(&file_cdev,file_dev_num,4);
    return 0;
}
/*A.0驱动模块卸载时执行的函数*/
static void mychar_dev_exit(void){
    
    
//B.3释放GPIO资源
    int i = 0;
    for(;i<ARRAY_SIZE(led_info);i++){
    
    
        gpio_set_value(led_info[i].gpio,1);设置GPIO为高电平
        gpio_free(led_info[i].gpio);//释放GPIO资源
    }
    //C.2释放字符设备号,包含4个次设备号,删除字符设备
    unregister_chrdev_region(file_dev_num,4);
    cdev_del(&file_cdev);
    printk("模块卸载完成\n");
}
module_init(mychar_dev_init);//A.0定义驱动模块加载时执行的函数
module_exit(mychar_dev_exit);//A.0定义驱动模块卸载时执行的函数
MODULE_LICENSE("GPL");//A.0定义驱动模块支持GPL协义

Guess you like

Origin blog.csdn.net/weixin_47273317/article/details/107821927