字符设备驱动学习之LED

前面讲述了驱动的一些基本概念,以及最基本的一个Helloword模块,本篇主要讲解LED驱动及给出一些能帮助我们学习的注释。

我们的板子是FL2440,内核版本为3.0.54。

首先给出LED驱动代码及一些注释:

/*********************************************************************************
  2  *      Copyright:  (C) 2017 minda
  3  *                  All rights reserved.
  4  *
  5  *       Filename:  led.c
  6  *    Description:  This file 
  7  *                 
  8  *        Version:  1.0.0(03/29/2017)
  9  *         Author:  tangyanjun <519656780@qq.com>
 10  *      ChangeLog:  1, Release initial version on "03/29/2017 01:58:32 PM"
 11  *                 
 12  ********************************************************************************/
 13 #include <linux/module.h>   /* Every Linux kernel module must include this head */
 14 #include <linux/init.h>     /* Every Linux kernel module must include this head */
 15 #include <linux/kernel.h>   /* printk() */
 16 #include <linux/fs.h>       /* struct fops */
 17 #include <linux/errno.h>    /* error codes */
 18 #include <linux/cdev.h>     /* cdev_alloc()  */
 19 #include <asm/io.h>         /* ioremap()  */
 20 #include <linux/ioport.h>   /* request_mem_region() */
 21 
 22 #include <asm/ioctl.h>      /* Linux kernel space head file for macro _IO() to generate ioctl command  */
 23 #ifndef __KERNEL__
 24 #include <sys/ioctl.h>      /* User space head file for macro _IO() to generate ioctl command */
 25 #endif
 26 //#include <linux/printk.h> /* Define log level KERN_DEBUG, no need include here */
 27 
 28 
 29 #define DRV_AUTHOR                "Tang Yanjun <519656780@qq.com>"
 30 #define DRV_DESC                  "S3C24XX LED driver"
 31 
 32 #define DEV_NAME                  "led"      //定义设备名称
 33 #define LED_NUM                   4        //定义设备数量
 34 
 35 /* Set the LED dev major number */
 36 //#define LED_MAJOR                 79
 37 #ifndef LED_MAJOR
 38 #define LED_MAJOR                 0    //这里是一个宏判断,默认主设备号为0     
 39 #endif
 40 
 41 #define DRV_MAJOR_VER             1
 42 #define DRV_MINOR_VER             0
 43 #define DRV_REVER_VER             0

 45 #define DISABLE                   0
 46 #define ENABLE                    1
 47 
 48 #define GPIO_INPUT                0x00   //GPIO输入模式为00
 49 #define GPIO_OUTPUT               0x01   //GPIO输出模式为01
 50 
 51 
 52 #define PLATDRV_MAGIC             0x60    //定义魔数
 53 #define LED_OFF                   _IO (PLATDRV_MAGIC, 0x18)
 54 #define LED_ON                    _IO (PLATDRV_MAGIC, 0x19)
 55 
 56 #define S3C_GPB_BASE              0x56000010  //LED引脚控制寄存器基址
 57 #define GPBCON_OFFSET             0    //定义GPBCON偏移地址 (GPBCON是用来选定引脚并设置输入或者输出模式)
 58 #define GPBDAT_OFFSET             4   // 定义GPBDAT偏移地址,GPBDAT寄存器用于读/写 引脚数据;当引脚被设为输入时,读此寄存器可知相应引脚的电    平状态是高还是低;当引脚被设为输出时,写此寄存器相应位可以令此引脚输出高电平或是低电平。
 59 #define GPBUP_OFFSET              8   // 定义GPBUP偏移地址,某位为1时,相应引脚无内部上拉电阻;为0时,相应引脚使用内部上拉电阻。上拉电阻的作用在于:当GPIO引脚处于第三态(即不是输出高电平,也不是输出低电平,而是呈高阻态,即相当于没接芯片)时,它的电平状态由上拉电阻、下拉电阻确定。
 60 #define S3C_GPB_LEN               0x10        /* 0x56000010~0x56000020  */
 61 
 62 int led[LED_NUM] = {5,6,8,10};  /* Four LEDs use GPB5,GPB6,GPB8,GPB10 */
 63 
 64 static void __iomem *s3c_gpb_membase;

 67 #define s3c_gpio_write(val, reg) __raw_writel((val), (reg)+s3c_gpb_membase)
 68 #define s3c_gpio_read(reg)       __raw_readl((reg)+s3c_gpb_membase)                 //读写操作寄存器
 69 
 70 
 71 int dev_count = ARRAY_SIZE(led);
 72 int dev_major = LED_MAJOR;
 73 int dev_minor = 0;
 74 int debug = DISABLE;
 75 
 76 static struct cdev      *led_cdev;
 77 
 78 static int s3c_hw_init(void)
 79 {
 80     int          i;
 81     volatile unsigned long  gpb_con, gpb_dat, gpb_up;
 82 
 83     if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN, "s3c2440 led"))
 84     {
 85         return -EBUSY;
 86     }
 87 
 88     if( !(s3c_gpb_membase=ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) )
 89       {
 90         release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);           //                                                                 主要是检查传入地址的合法性,建立页表(包括访问权限),完成物理地址到虚拟地址的转换
 91         return -ENOMEM;
 92     }
 93 
 94     for(i=0; i<dev_count; i++)
 95     {
 96         /* Set GPBCON register, set correspond GPIO port as input or output mode  */
 97         gpb_con = s3c_gpio_read(GPBCON_OFFSET);
 98         gpb_con &= ~(0x3<<(2*led[i]));   /* Clear the currespond LED GPIO configure register */
 99         gpb_con |= GPIO_OUTPUT<<(2*led[i]); /* Set the currespond LED GPIO as output mode */
100         s3c_gpio_write(gpb_con, GPBCON_OFFSET);
101 
102         /* Set GPBUP register, set correspond GPIO port pull up resister as enable or disable  */
103         gpb_up = s3c_gpio_read(GPBUP_OFFSET);
104         //gpb_up &= ~(0x1<<led[i]); /* Enable pull up resister */
105         gpb_up |= (0x1<<led[i]);  /* Disable pull up resister */
106         s3c_gpio_write(gpb_up, GPBUP_OFFSET);
107 
108         /* Set GPBDAT register, set correspond GPIO port power level as high level or low level */
109         gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
110        //gpb_dat &= ~(0x1<<led[i]); /* This port set to low level, then turn LED on */
111         gpb_dat |= (0x1<<led[i]);  /* This port set to high level, then turn LED off */
112         s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
113     }
114 
115     return 0;
116 }
117 
118 
119 static void turn_led(int which, unsigned int cmd)   //由ioctl调用,控制单个LED的亮灭
120 {
121     volatile unsigned long  gpb_dat;
122 
123     gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);  //设置gpb_dat变量来存储GPBDAT的在内存中的虚拟地址
124 
125     if(LED_ON == cmd)
126     {
127         gpb_dat &= ~(0x1<<led[which]); /*  Turn LED On */
128     }
129     else if(LED_OFF == cmd)
130     {
131         gpb_dat |= (0x1<<led[which]);  /*  Turn LED off */
132     }
133 
134     s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
135 }
136 
137 static void s3c_hw_term(void)   //调用LED结束后释放所占内存资源及设置相应GPIO引脚关闭LED
138 {
139     int                     i;
140     volatile unsigned long  gpb_dat;
141 
142     for(i=0; i<dev_count; i++)
143     {
144         gpb_dat = s3c_gpio_read(GPBDAT_OFFSET);
145         gpb_dat |= (0x1<<led[i]);  /* Turn LED off */
146         s3c_gpio_write(gpb_dat, GPBDAT_OFFSET);
147     }
148 
149     release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN);
150     iounmap(s3c_gpb_membase);
151 }
154 static int led_open(struct inode *inode, struct file *file)  //驱动功能函数open
155 {
156     int minor = iminor(inode);
157 
158     file->private_data = (void *)minor;   //将次设备号保存到private_data中
    /*private_data用于在系统调用期间保存各种状态信息*/
159 
160     printk(KERN_DEBUG "/dev/led%d opened.\n", minor);
161     return 0;
162 }
163 
164 static int led_release(struct inode *inode, struct file *file)  //驱动功能函数
165 {
166     printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode));
167 
168     return 0;
169 } //内核中用inode结构表示具体的文件,而用file结构表示打开的文件描述符。struct file代表一个打开的文件,在执行file_operation中的open操作时被创       建,这里需要注意的是与用户空间file指针的区别,一个在内核,而file指针在用户空间,由c库来定义。struct inode被内核用来代表一个文件,注意和struct       file的区别,struct inode一个是代表文件,struct file一个是代表打开的文件。

170 static void print_help(void)
172 {
173     printk("Follow is the ioctl() commands for %s driver:\n", DEV_NAME);
174     //printk("Enable Driver debug command: %u\n", SET_DRV_DEBUG);
175     printk("Turn LED on command  : %u\n", LED_ON);
176     printk("Turn LED off command : %u\n", LED_OFF);
177 
178     return;
179 }
180 
181 static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
182 {
183     int which = (int)file->private_data;
184 
185     switch (cmd)            //cmd为命令,这里即为实现led的亮灭
186     {
187         case LED_ON:
188 
189             turn_led(which, LED_ON);
190             break;
192         case LED_OFF:
193             turn_led(which, LED_OFF);
194             break;
195 
196         default:
197             printk(KERN_ERR "%s driver don't support ioctl command=%d\n", DEV_NAME, cmd);
198             print_help();
199             break;
200     }
201 
202     return 0;
203 }
204 
205 
206 static struct file_operations led_fops =       //这个结构体是用来连接驱动与设备的关键东东
207 {
208     .owner = THIS_MODULE,
209     .open = led_open,
210     .release = led_release,
211     .unlocked_ioctl = led_ioctl,
212 };
214    static int __init s3c_led_init(void)    //初始化函数
215 {
216     int                    result;
217     dev_t                  devno;      //保存设备编号
218 
219     if( 0 != s3c_hw_init() )
220     {
221         printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n");
222         return -ENODEV;
223     }
224 
225     /*  Alloc the device for driver */
226     if (0 != dev_major) /*  Static */
227     {
228         devno = MKDEV(dev_major, 0);
229         result = register_chrdev_region (devno, dev_count, DEV_NAME);//获得设备编号,成功时为0
230     }
231     else
232     {
233         result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME);       //动态分配
234         dev_major = MAJOR(devno);
235     }
236 
237     /*  Alloc for device major failure */
238     if (result < 0)
239     {
240         printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major);
241         return -ENODEV;
242     }
243     printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major);
244 
245     if(NULL == (led_cdev=cdev_alloc()) )
246     {
247         printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME);
248         unregister_chrdev_region(devno, dev_count);     //释放设备编号
249         return -ENOMEM;   //超出内存
250     }
251 
252     led_cdev->owner = THIS_MODULE;
253     cdev_init(led_cdev, &led_fops);
254 
255     result = cdev_add(led_cdev, devno, dev_count);
256     if (0 != result)
257     {
258         printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result);
259         goto ERROR;
260     }
261 
262 
263     printk(KERN_ERR "S3C %s driver[major=%d] version %d.%d.%d installed successfully!\n",
264             DEV_NAME, dev_major, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER);
265     return 0;
266 
267 
268 ERROR:
269     printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME);
270     cdev_del(led_cdev);                                 //注销
271     unregister_chrdev_region(devno, dev_count);        //释放设备编号
272     return result;
273 }
274 
275 static void __exit s3c_led_exit(void)    //清除函数
276 {
277     dev_t devno = MKDEV(dev_major, dev_minor);          //由主/次设备号构造一个dev_t数据项
278
279     s3c_hw_term();
280 
281     cdev_del(led_cdev);                                 //注销
282     unregister_chrdev_region(devno, dev_count);        //释放设备编号
283 
284     printk(KERN_ERR "S3C %s driver version %d.%d.%d removed!\n",
285             DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER);
286 
287     return ;
288 }
289 
290 
291 
292 /* These two functions defined in <linux/init.h> */
293 module_init(s3c_led_init);
294 module_exit(s3c_led_exit);
295 
296 module_param(debug, int, S_IRUGO);
297 module_param(dev_major, int, S_IRUGO);
298 
299 MODULE_AUTHOR(DRV_AUTHOR);
300 MODULE_DESCRIPTION(DRV_DESC);
301 MODULE_LICENSE("GPL");
302 

因为正在尝试驱动的入门,所以很多东西也还是一知半解,下面是我对led驱动的一些理解:
1.驱动分三个部分:
   第一个部分为关于设备的一些基本设置,如初始化硬件设置,寄存器,以及物理地址与虚拟地址的映射。而本人对硬件方面的知识还有待提高,所以硬件这一部分深入学习以后再详谈;
   第二个部分即是*fops,这一部分用来存放操作设备的指针,通过指针操作设备,将驱动与设备联系起来;
   第三个部分是驱动的基本套路,像前面的helloword模块一样,分配主次设备号,定义cdev结构体,最后再声明驱动的基本信息,作者,许可声明等等。
2.驱动的一些重要信息:
   data:用来存放信息的,比如led的开关状态信息;
   open、read、write、iocol等函数的调用起着关键作用;
   如果我们的驱动程序达不到你想要的某些功能,你也可以自己发挥才干,在驱动中添加一些函数调用。
下面是一个基本流程:
硬件初始化 -> 申请主次设备号 -> 定义fops(file_operations)结构体 -> 申请cdev结构体,并把fops结构体嵌入cdev结构体中,与之绑定 -> cdev_add字符设备注册 。
其中file_operations结构体中的成员是加载驱动后提供的对设备进行各种操作的函数的指针,在这里你可以将你想对自己设备要进行的操作赋值结构体中相关的函数指针。比如open,read,write等。而cdev结构体则是用来描述字符设备,每一个字符设备都会有一个相对应的cdev来描述。还要值得注意的是,在linux内核中,所有的设备都是以文件的形式存在,即我们都是在对文件进行操作,都存在/dev目录下。而inode则是设备索引节点,每一个文件产生后都会有相应的inode来标识。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值