概述
前面讲述了驱动的一些基本概念,以及最基本的一个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: %un", SET_DRV_DEBUG);
175 printk("Turn LED on command : %un", LED_ON);
176 printk("Turn LED off command : %un", 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=%dn", 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 %dn", DEV_NAME, dev_major);
241 return -ENODEV;
242 }
243 printk(KERN_DEBUG "S3C %s driver use major %dn", 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=%dn", 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来标识。
最后
以上就是健忘黑夜为你收集整理的字符设备驱动学习之LED的全部内容,希望文章能够帮你解决字符设备驱动学习之LED所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复