最近学习到了Linux驱动章节的课程,对设备的对应驱动的注册有些困惑,看了下发现是把设备的所有操作方法封装到结构体 file_operations 中,这个结构体为所有的设备文件都提供了统一的操作函数接口。然后把这个结构体连同设备的主设备号、名字(没啥用)一起,通过函数 register_chrdev(0, "first_drv", &first_drv_fops) 注册驱动模块到内核的字符设备数组chrdev的第xx项,具体注册和使用方法和裸机LCD及LCD控制器的一模一样!
1. 驱动程序使用如下:
1 /*
2 2018-10-18
3 功能: 第一个驱动程序;
4 */
5
6 #include <linux/module.h>
7 #include <linux/kernel.h>
8 #include <linux/fs.h>
9 #include <linux/init.h>
10 #include <linux/delay.h>
11 #include <asm/uaccess.h>
12 #include <asm/irq.h>
13 #include <asm/io.h>
14 #include <asm/arch/regs-gpio.h>
15 #include <asm/hardware.h>
16
17 static int first_drv_open(struct inode * inode, struct file *file)
18 {
19 printk("first_drv_open\n");
20 }
21
22 static ssize_t first_drv_write(struct file *file, const char __usr *buf, size_t count, loff_t * ppos)
23 {
24 printk("first_drv_write\n");
25 }
26
27 static struct file_operations first_drv_fops =
28 {
29 .owner = THIS_MODULE,
30 .open = first_drv_open,
31 .write = first_drv_write
32 };
33
34 int major;
35 static int first_drv_init(void)
36 {
37 major = register_chrdev(0, "first_drv", &first_drv_fops);
38 firstdrv_class = class_create(THIS_MODULE, "firstdrv");
39 first_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz");
40 gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
41 gpfdat = gpfcon + 1;
42 return 0;
43 }
44
45 static void first_drv_exit(void)
46 {
47 unregister_chrdev(major, "first_drv");
48 class_device_unregister(firstdrv_class_dev);
49 class_destroy(firstdrv_class);
50 }
51
52 module_init(first_drv_init);
53 module_exit(first_drv_exit);
54 MODULE_LICENSE("GPL");
2.文件lcd.h如下:
1 #ifndef _LCD_H
2 #define _LCD_H
3
4
5 enum {
6 NORMAL = 0,
7 INVERT = 1,
8 };
9
10 /* NORMAL : 正常极性
11 * INVERT : 反转极性
12 */
13 typedef struct pins_polarity {
14 int de; /* normal: 高电平时可以传输数据 */
15 int pwren; /* normal: 高电平有效 */
16 int vclk; /* normal: 在下降沿获取数据 */
17 int rgb; /* normal: 高电平表示1 */
18 int hsync; /* normal: 高脉冲 */
19 int vsync; /* normal: 高脉冲 */
20 }pins_polarity, *p_pins_polarity;
21
22 typedef struct time_sequence {
23 /* 垂直方向 */
24 int tvp; /* vysnc脉冲宽度 */
25 int tvb; /* 上边黑框, Vertical Back porch */
26 int tvf; /* 下边黑框, Vertical Front porch */
27
28 /* 水平方向 */
29 int thp; /* hsync脉冲宽度 */
30 int thb; /* 左边黑框, Horizontal Back porch */
31 int thf; /* 右边黑框, Horizontal Front porch */
32
33 int vclk;
34 }time_sequence, *p_time_sequence;
35
36
37 typedef struct lcd_params {
38 char *name;
39
40 /* 引脚极性 */
41 pins_polarity pins_pol;
42
43 /* 时序 */
44 time_sequence time_seq;
45
46 /* 分辨率, bpp */
47 int xres;
48 int yres;
49 int bpp;
50
51 /* framebuffer的地址 */
52 unsigned int fb_base;
53 }lcd_params, *p_lcd_params;
54
55 void get_lcd_params(unsigned int *fb_base, int *xres, int *yres, int *bpp);
56
57 #endif /* _LCD_H */
3.文件lcd_4.3.c如下:
#include "lcd.h"
#define LCD_FB_BASE 0x33c00000
lcd_params lcd_4_3_params = {
.name = "lcd_4.3",
.pins_pol = {
.de = NORMAL, /* normal: 高电平时可以传输数据 */
.pwren = NORMAL, /* normal: 高电平有效 */
.vclk = NORMAL, /* normal: 在下降沿获取数据 */
.rgb = NORMAL, /* normal: 高电平表示1 */
.hsync = INVERT, /* normal: 高脉冲 */
.vsync = INVERT, /* normal: 高脉冲 */
},
.time_seq = {
/* 垂直方向 */
.tvp= 10, /* vysnc脉冲宽度 */
.tvb= 2, /* 上边黑框, Vertical Back porch */
.tvf= 2, /* 下边黑框, Vertical Front porch */
/* 水平方向 */
.thp= 41, /* hsync脉冲宽度 */
.thb= 2, /* 左边黑框, Horizontal Back porch */
.thf= 2, /* 右边黑框, Horizontal Front porch */
.vclk= 9, /* MHz */
},
.xres = 480,
.yres = 272,
.bpp = 32, /* 16, no 24bpp */
.fb_base = LCD_FB_BASE,
};
void lcd_4_3_add(void)
{
register_lcd(&lcd_4_3_params);
}
《代码大全》建议在变量定义的时候进行初始化,但是很多人,特别是新人对结构体或者结构体数组定义是一般不会初始化,或者不知道怎么初始化。
1、初始化
typedef struct _TEST_T {
int i;
char c[10];
}TEST_T;
TEST_T gst = {1, “12345”};//可以初始化,设置i为1,s为一个字符串.
TEST_T gst = {1};//初始化个数少于实际个数时,只初始化前面的成员。
TEST_Tgst = {.c=“12345”};//有选择的初始化成员。
2、复合字面量。
gst = (TEST_T){122, "1256"};//这是一个赋值语句,也可以作为初始化。可以出现在程序的任何地方。
当然也可以使用复合字面量来初始化:
gst = (TEST_T){.i=122, .c="123"};
3、结构体数组
可以用多个大括号括起来:
TEST_T gst[10] = {{},{},{},{}}
也可以初始化其中的一个元素:
TEST_T gst[10] = {[2]={}, [3]={}}
也可以使用复合字面量:
TEST_T gst[10] = {[2].i=0, [3].i={}}
为什么要初始化:
1、对局部变量初始化可以防止随机值产生的危害。
2、对全局变量初始化可以告诉编译器,这是一个定义,而不是一个声明。(如果两个c中有相同的全局变量定义,且没有初始化,编译器会认为第二个是声明而不是定义。)