字符设备驱动:LED传统实现方式

1. 环境:

1.1 开发板:正点原子 I.MX6U ALPHA V2.2

1.2 开发PC:Ubuntu20.04

1.3 U-boot:uboot-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2

1.4 LInux内核:linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2

1.5 rootfs:busybox-1.29.0.tar.bz2制作

1.6 交叉编译工具链:gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz

 

2. 硬件控制说明

2.1 由GPIO1 PIN3控制,高---熄灭, 低---点亮

 

 3. 驱动代码

  1 #include <linux/init.h>
  2 #include <linux/module.h>
  3 #include <linux/fs.h>           //文件操作函数,file_operarions
  4 #include <linux/device.h>       //设备申请函数,device_create
  5 #include <linux/slab.h>         //内核空间申请函数件,kmalloc
  6 #include <asm/uaccess.h>        //内核与用户空间消息拷贝函数,copy_to_usser  &  copy_from_user
  7 #include <asm/io.h>             //内存地址映射函数,ioremap
  8 #include <linux/cdev.h>
  9 
 10 //GPIO寄存器地址,参考"i.MX 6ULL Applications Processor Reference Manual"
 11 #define LED_GPIO1_3_BASE        0x0209c000
 12 #define GPIO_DR                 0x0
 13 #define GPIO_GDIR               0x4
 14 #define GPIO_PSR                0x8         //本驱动未使用到
 15 #define GPIO_ICR1               0xc         //本驱动未使用到
 16 #define GPIO_ICR2               0x10        //本驱动未使用到
 17 #define GPIO_IMR                0x14        //本驱动未使用到
 18 #define GPIO_ISR                0x18        //本驱动未使用到
 19 #define GPIO_EDGE_SEL           0x1c        //本驱动未使用到
 20 
 21 #define LED_GPIO1_3_MUX         0X020E0068      //GPIO复用功能选择寄存器地址
 22 #define LED_GPIO1_3_PAD         0X020E02F4      //GPIO驱动方式选择寄存器地址
 23 #define LED_GPIO1_3_CCM         0X020C406C      //GPIO时钟寄存器地址
 24 
 25 //led控制
 26 #define LED_ON                  1
 27 #define LED_OFF                 0
 28 
 29 //自定义一个结构体,将一些需要的变量放在其中,以便管理
 30 struct led_device
 31 {
 32     struct class *led_class;
 33     struct device *led_device;
 34     struct cdev led_cdev;
 35     
 36 };
 37 
 38 
 39 dev_t devno;            //很奇怪,必须另外定义dev_t,直接使用led_cdev中的dev_t,device_create函数看不到其设备文件,不解
 40 u32 dev_major = 0;      //用于保存主设备号
 41 
 42 static  struct led_device *imx_led_gpio1_3;
 43 static  void __iomem *led_dr = NULL;        //保存GPIO数据寄存器内存映射后的地址
 44 static  void __iomem *led_gdir = NULL;      //保存GPIO输入输出方向选择寄存器内存映射后的地址
 45 static  void __iomem *led_pd = NULL;        //保存GPIO驱动方式选择寄存器内存映射后的地址
 46 static  void __iomem *led_mux = NULL;       //保存GPIO复用功能选择寄存器内存映射后的地址
 47 static  void __iomem *led_ccm = NULL;       //保存GPIO时钟寄存器内存映射后的地址
 48 
 49 
 50 //设备文件的open操作函数
 51 static  int led_open(struct inode *inode, struct file *file)
 52 {
 53 
 54     return 0;
 55 }
 56 
 57 //设备文件的close操作函数
 58 static int led_close(struct inode *inode, struct file *file)
 59 {
 60     return 0;
 61 }
 62 
 63 //设备文件的read操作函数
 64 static ssize_t led_read(struct file *file, char __user *buf, size_t size, loff_t *f_ops)
 65 {
 66     return 0;
 67 }
 68 
 69 //设备文件的write操作函数
 70 static ssize_t led_write(struct file *file, const char __user *buf, size_t size, loff_t *f_ops)
 71 {
 72     int ret;
 73     unsigned char cmd[1];
 74     u32 val = 0;
 75 
 76     ret = copy_from_user(cmd, buf, size);
 77     if(ret < 0) 
 78     {
 79         printk("kernel write failed!\r\n");
 80         return -EFAULT;
 81     }
 82 
 83 
 84    if(*cmd == LED_ON)
 85    {
 86         val = readl(led_dr);
 87         val &= ~(1 << 3);    
 88         writel(val, led_dr);
 89    }
 90    else if(*cmd == LED_OFF)
 91    {
 92         val = readl(led_dr);
 93         val|= (1 << 3);    
 94         writel(val, led_dr);
 95    }
 96    else
 97    {
 98        printk("led command is invalid!\n");
 99        return -2;
100    } 
101 
102     return 0;
103 }
104 
105 static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
106 {
107     u32 val = 0;
108     switch (cmd)
109     {
110      case LED_OFF:
111                 val = readl(led_dr);
112                 val|= (1 << 3);    
113                 writel(val, led_dr);
114                 break;
115     case LED_ON:
116                 val = readl(led_dr);
117                 val &= ~(1 << 3);    
118                 writel(val, led_dr);
119                 break;
120     default:
121                 val = readl(led_dr);
122                 val|= (1 << 3);    
123                 writel(val, led_dr);
124                 break;
125     }
126 
127 
128     return 0;
129 }
130 
131 
132 
133 static  const struct file_operations led_fops = {
134     .owner               =   THIS_MODULE,
135     .open                =   led_open,
136     .release             =   led_close,
137     .write               =   led_write,
138     .read                =   led_read,
139     .unlocked_ioctl      =   led_ioctl,
140 };
141 
142 
143 static int __init led_init(void)
144 {
145     int ret;
146     u32 val = 0;
147 
148     //为自定义机构提申请内核内存
149     imx_led_gpio1_3 = kmalloc(sizeof(struct led_device), GFP_KERNEL);
150     if(imx_led_gpio1_3 == NULL)
151     {
152         printk(KERN_ERR "kmalloc fail!\n");
153         return -ENOMEM;
154     }
155 
156     //ret = register_chrdev(LED_MAJOR, "led", &led_fops);
157     //如果使用动态申请,则必须使用cdev_init() & cdev_add()这两个函数;静态则不需要
158     ret = alloc_chrdev_region(&devno, 0, 1, "led");
159     if(ret < 0)
160     {
161         printk(KERN_ERR "register major failed!\n");
162         ret = -EINVAL;
163         goto err1;
164     }
165 
166     dev_major = MAJOR(devno);
167 
168     //将设备与文件操作函数关联
169     cdev_init(&(imx_led_gpio1_3->led_cdev), &led_fops);
170 
171     //注册设备
172     cdev_add(&(imx_led_gpio1_3->led_cdev), MKDEV(dev_major, 0), 1);
173 
174     //创建设备类
175     imx_led_gpio1_3->led_class = class_create(THIS_MODULE, "led_class");
176     if(IS_ERR(imx_led_gpio1_3->led_class))
177     {
178         printk(KERN_ERR "failed to create class!\n");
179         ret = PTR_ERR(imx_led_gpio1_3->led_class);
180         goto err2;
181     }
182 
183     
184     //创建设备文件,此函数最后一个参数led,其实就是设备名,应用程序open打开的也是这个文件名
185     imx_led_gpio1_3->led_device = device_create(imx_led_gpio1_3->led_class, 0, MKDEV(dev_major, 0), 0, "led");
186     if(IS_ERR(imx_led_gpio1_3->led_device))
187     {
188         printk(KERN_ERR "failed to create device!\n");
189         ret = PTR_ERR(imx_led_gpio1_3->led_device);
190         goto err3;
191     }
192 
193     //对物理地址进行内存映射,以便后面操作
194     led_dr = ioremap(LED_GPIO1_3_BASE, 4);
195     if(led_dr == NULL)
196     {
197         printk(KERN_ERR "led_dr ioremap fail!\n");
198         ret = -ENOMEM;
199         goto err4;
200     }
201 
202     led_gdir = ioremap(LED_GPIO1_3_BASE + GPIO_GDIR, 4);
203     if(led_gdir == NULL)
204     {
205         printk(KERN_ERR "led_gdir ioremap fail!\n");
206         ret = -ENOMEM;
207         goto err4;
208     }
209 
210     led_pd = ioremap(LED_GPIO1_3_PAD, 4);
211     if(led_pd == NULL)
212     {
213         printk(KERN_ERR "led_pd ioremap fail!\n");
214         ret = -ENOMEM;
215         goto err4;
216     }    
217 
218     led_ccm = ioremap(LED_GPIO1_3_CCM, 4);
219     if(led_ccm == NULL)
220     {
221         printk(KERN_ERR "led_ccm ioremap fail!\n");
222         ret = -ENOMEM;
223         goto err4;
224     }
225 
226 
227     led_mux = ioremap(LED_GPIO1_3_MUX, 4);
228     if(led_mux == NULL)
229     {
230         printk(KERN_ERR "led_mux ioremap fail!\n");
231         ret = -ENOMEM;
232         goto err4;
233     }
234 
235     // 使能GPIO1时钟 
236     val = readl(led_ccm);
237     val &= ~(3 << 26);    //清掉旧值
238     val |= (3 << 26);    // 设置新值
239     writel(val, led_ccm);
240 
241     // 将GPIO1_IO03复用功能设置为GPIO
242     writel(5, led_mux);
243 
244     // 设置GPIO的电流驱动能力
245     writel(0x10B0, led_pd);
246 
247    // 设置GPIO的输入输出方向 
248     val = readl(led_gdir);
249     val &= ~(1 << 3);    // 清除以前的设置
250     val |= (1 << 3);    // 设置为输出 
251     writel(val, led_gdir);
252 
253 
254     //设置LED的默认状态:LED熄灭 
255     val = readl(led_dr);
256     val |= (1 << 3);    
257     writel(val, led_dr);
258 
259     return 0;
260 
261 //注销设备节点
262 err4:
263     device_destroy(imx_led_gpio1_3->led_class, MKDEV(dev_major, 0));
264 
265 //注销设备类
266 err3:
267     class_destroy(imx_led_gpio1_3->led_class);
268 
269 //注销设备号
270 err2:
271     unregister_chrdev(dev_major, "led");
272 
273 //释放由kmalloc申请的内存
274 err1:
275     kfree(imx_led_gpio1_3);
276     return ret;
277 
278 }
279 
280 static void __exit led_exit(void)
281 {   
282     device_destroy(imx_led_gpio1_3->led_class, MKDEV(dev_major, 0));
283     class_destroy(imx_led_gpio1_3->led_class);
284     unregister_chrdev(dev_major, "led");
285     iounmap(led_dr);
286     iounmap(led_gdir);
287     iounmap(led_pd);
288     iounmap(led_mux);
289     iounmap(led_ccm);
290     kfree(imx_led_gpio1_3);
291 }
292 
293 
294 module_init(led_init);
295 module_exit(led_exit);
296 MODULE_LICENSE("Dual BSD/GPL");
View Code

 

4. 测试代码

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <sys/types.h>
 4 #include <sys/stat.h>
 5 #include <fcntl.h>
 6 
 7 #define LED_ON                  1
 8 #define LED_OFF                 0
 9 
10 
11 int main(int argc, char **argv)
12 {
13     int fd;
14     int buf = 0;
15 
16     fd = open("/dev/led", O_RDWR);
17 
18     printf("fd = %d\n", fd);
19     if(fd < 0)
20     {
21         perror("open fail!\n");
22        
23         exit(1);
24     }
25 
26 #if 0
27 
28     if(strcmp(argv[1], "on") == 0)
29     {
30         if(ioctl(fd, LED_ON) < 0)
31             perror("ioctrl fail:led on\n");
32         else
33             printf("ioctl ok:led on\n");
34     }
35     else if(strcmp(argv[1], "off") == 0)
36     {
37         if(ioctl(fd, LED_OFF) < 0)
38              perror("ioctrl fail:led off\n");
39         else
40             printf("ioctl ok:led off\n");
41     }
42     else
43     {
44         perror("command is invalid!\n");
45     }
46 
47 
48 #else
49      if(strcmp(argv[1], "on") == 0)
50      {
51          buf = 1;
52         if(write(fd, &buf, sizeof(buf)) < 0)
53         {
54             perror("write fail:led on!\n");
55             exit(1); 
56         }
57         else
58         {
59             printf("write ok:led on!\n");         
60         }
61      }
62      else if(strcmp(argv[1], "off") == 0)
63      {
64          buf = 0;
65         if(write(fd, &buf, sizeof(buf)) < 0)
66         {
67             perror("write fail:led off!\n");
68             exit(1);
69         }
70         else
71         {
72             printf("write ok:led off!\n");
73         }
74      }
75      else
76     {
77         perror("command is invalid!\n");
78     }
79 
80 
81 #endif
82 
83     close(fd);
84     return 0;
85 }
View Code

 

总结:

1. I.MX6ULL GPIO涉及到数据、方向、复用、驱动方式、时钟五个寄存器的设置

2. 设备号如果使用动态申请(alloc_chrdev_region),则必须使用cdev_init() & cdev_add()这两个函数;静态则不需要

3. 驱动的主要步骤,大致为:1. 申请内核空间(kmalloc)-->2. 申请设备号(alloc_chrdev_region)-->3. 建立设备与file_operations的连接(cdev_init)-->4. 向内核注册设备(cdev_add)-->5. 创建设备类(led_class)-->6. 创建设备文件(device_create)

 

posted @ 2021-09-24 00:35  秋水寒林  阅读(59)  评论(0编辑  收藏  举报