09、platform实验
LED 灯案例:GPIO0_C7 引脚
一、定义平台设备
C
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
//#define GPIO4_BASE (0xfe770000)
//#define GPIO4_DR (GPIO4_BASE + 0x0000)
//#define GPIO4_DDR (GPIO4_BASE + 0x0008)
#define GPIO0_BASE (0xfdd60000)
#define GPIO0_DR (GPIO0_BASE + 0x0004)
#define GPIO0_DDR (GPIO0_BASE + 0x000C)
static struct resource led_resource[] = {
[0] = DEFINE_RES_MEM(GPIO0_DR, 4), //數據
[1] = DEFINE_RES_MEM(GPIO0_DDR, 4), //方向
};
static void led_release(struct device *dev)
{
}
/* led hardware information */
unsigned int led_hwinfo[1] = { 7 }; //偏移
/* led device */
static struct platform_device led_pdev = {
.name = "led_pdev", //匹配
.id = 0,
.num_resources = ARRAY_SIZE(led_resource),
.resource = led_resource,
.dev = {
.release = led_release,
.platform_data = led_hwinfo,
},
};
static __init int led_pdev_init(void)
{
printk("pdev init\n");
platform_device_register(&led_pdev);
return 0;
}
module_init(led_pdev_init);
static __exit void led_pdev_exit(void)
{
printk("pdev exit\n");
platform_device_unregister(&led_pdev);
}
module_exit(led_pdev_exit);
MODULE_DESCRIPTION("Platform LED device");
MODULE_AUTHOR("embedfire");
MODULE_LICENSE("GPL");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
::: 在内核源码/include/linux/ioport.h 中,提供了宏定义 DEFINE_RES_MEM、 DEFINE_RES_IO、
DEFINE_RES_IRQ 和 DEFINE_RES_DMA, 用 来 定 义 所 需 要 的 资 源 类 型。 DEFINE_RES_MEM 用于定义 IORESOURCE_MEM 类型的资源,我们只需要传入两个参数,一个是寄存器地址,另一个是大小。 :::
从手册上看,可以得知一个寄存器都是 32 位的,
因此,这里我们选择需要 4 个字节大小的空间。 rled_resource 资源数组中,我们将所有的
MEM 资源进行了编号, 0 对应了 GPIOA_MODER, 1 对应了 GPIOA_OTYPER,驱动到时候就可以根据这些编号获得对应的寄存器地址。
二、定义平台驱动
我们驱动提供 id_table 的方式,来匹配设备。我们定义一个 platform_device_id 类型的变量led_pdev_ids,说明驱动支持哪些设备,这里我们只支持一个设备,名称为 led_pdev,要与平台设备提供的名称保持一致。
C
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#define DEV_MAJOR 243
#define DEV_NAME "led"
static struct class *led_test_class;
//结构体led_data来管理我们LED灯的硬件信息
struct led_data {
unsigned int led_pin;
unsigned int __iomem *va_DDR;
unsigned int __iomem *va_DR;
struct cdev led_cdev;
};
static int led_cdev_open(struct inode *inode, struct file *filp)
{
unsigned int val = 0;
struct led_data *cur_led = container_of(inode->i_cdev, struct led_data, led_cdev);
printk("led_cdev_open() \n");
// 设置引脚输出
val = readl(cur_led->va_DDR);
val |= ((unsigned int)0X1 << (cur_led->led_pin+16));
val |= ((unsigned int)0X1 << (cur_led->led_pin));
writel(val,cur_led->va_DDR);
//设置默认输出高电平
val = readl(cur_led->va_DR);
val |= ((unsigned int)0X1 << (cur_led->led_pin+16));
val |= ((unsigned int)0x1 << (cur_led->led_pin));
writel(val, cur_led->va_DR);
filp->private_data = cur_led;
return 0;
}
static int led_cdev_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t led_cdev_write(struct file *filp, const char __user * buf,
size_t count, loff_t * ppos)
{
unsigned long val = 0;
unsigned long ret = 0;
int tmp = count;
struct led_data *cur_led = (struct led_data *)filp->private_data;
val = kstrtoul_from_user(buf, tmp, 10, &ret);
val = readl(cur_led->va_DR);
if (ret == 0)
{
val |= ((unsigned int)0x1 << ((cur_led->led_pin)+16));
val &= ~((unsigned int)0X1 << (cur_led->led_pin));
}
else
{
val |= ((unsigned int)0x1 << (cur_led->led_pin+16));
val |= ((unsigned int)0X1 << (cur_led->led_pin));
}
writel(val, cur_led->va_DR);
*ppos += tmp;
return tmp;
}
static struct file_operations led_cdev_fops = {
.open = led_cdev_open,
.release = led_cdev_release,
.write = led_cdev_write,
};
//probe函数中,驱动需要去提取设备的资源,完成字符设备的注册等工作
static int led_pdrv_probe(struct platform_device *pdev)
{
struct led_data *cur_led;
unsigned int *led_hwinfo;
struct resource *mem_DR;
struct resource *mem_DDR;
dev_t cur_dev;
int ret = 0;
printk("led platform driver probe\n");
//第一步:提取平台设备提供的资源
//devm_kzalloc函数申请cur_led和led_hwinfo结构体内存大小
cur_led = devm_kzalloc(&pdev->dev, sizeof(struct led_data), GFP_KERNEL);
if(!cur_led)
return -ENOMEM;
led_hwinfo = devm_kzalloc(&pdev->dev, sizeof(unsigned int)*2, GFP_KERNEL);
if(!led_hwinfo)
return -ENOMEM;
/* get the pin for led and the reg's shift */
//dev_get_platdata函数获取私有数据,得到LED灯的寄存器偏移量,并赋值给cur_led->led_pin
led_hwinfo = dev_get_platdata(&pdev->dev); // 拿到GPIOX
cur_led->led_pin = led_hwinfo[0];
/* get platform resource */
//利用函数platform_get_resource可以获取到各个寄存器的地址
mem_DR = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 數據寄存器
mem_DDR = platform_get_resource(pdev, IORESOURCE_MEM, 1); //方向寄存器
//使用devm_ioremap将获取到的寄存器地址转化为虚拟地址
cur_led->va_DR = devm_ioremap(&pdev->dev, mem_DR->start, resource_size(mem_DR));
cur_led->va_DDR = devm_ioremap(&pdev->dev, mem_DDR->start, resource_size(mem_DDR));
//第二步:注册字符设备
cur_dev = MKDEV(DEV_MAJOR, pdev->id);
register_chrdev_region(cur_dev, 1, "led_cdev");
cdev_init(&cur_led->led_cdev, &led_cdev_fops);
ret = cdev_add(&cur_led->led_cdev, cur_dev, 1);
if(ret < 0)
{
printk("fail to add cdev\n");
goto add_err;
}
device_create(led_test_class, NULL, cur_dev, NULL, DEV_NAME "%d", pdev->id); // /dev/led0
/* save as drvdata */
//platform_set_drvdata函数,将LED数据信息存入在平台驱动结构体中pdev->dev->driver_data中
platform_set_drvdata(pdev, cur_led);
return 0;
add_err:
unregister_chrdev_region(cur_dev, 1);
return ret;
}
static int led_pdrv_remove(struct platform_device *pdev)
{
dev_t cur_dev;
//platform_get_drvdata,获取当前LED灯对应的结构体
struct led_data *cur_data = platform_get_drvdata(pdev);
printk("led platform driver remove\n");
cur_dev = MKDEV(DEV_MAJOR, pdev->id);
//cdev_del删除对应的字符设备
cdev_del(&cur_data->led_cdev);
//删除/dev目录下的设备
device_destroy(led_test_class, cur_dev);
//unregister_chrdev_region, 注销掉当前的字符设备编号
unregister_chrdev_region(cur_dev, 1);
return 0;
}
static struct platform_device_id led_pdev_ids[] = {
{.name = "led_pdev"}, //匹配
{}
};
MODULE_DEVICE_TABLE(platform, led_pdev_ids);
//平台总线匹配过程中 ,只会根据id_table中的name值进行匹配,若和平台设备的name值相等,则表示匹配成功; 反之,则匹配不成功,表明当前内核没有该驱动能够支持的设备。
static struct platform_driver led_pdrv = {
.probe = led_pdrv_probe,
.remove = led_pdrv_remove,
.driver.name = "led_pdev",
.id_table = led_pdev_ids,
};
static __init int led_pdrv_init(void)
{
printk("led platform driver init\n");
//class_create,来创建一个led类
led_test_class = class_create(THIS_MODULE, "test_leds");
//调用函数platform_driver_register,注册我们的平台驱动结构体,这样当加载该内核模块时, 就会有新的平台驱动加入到内核中。 第20-27行,注销
platform_driver_register(&led_pdrv);
return 0;
}
module_init(led_pdrv_init);
static __exit void led_pdrv_exit(void)
{
printk("led platform driver exit\n");
platform_driver_unregister(&led_pdrv);
class_destroy(led_test_class);
}
module_exit(led_pdrv_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("the example for platform driver");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
- 这块代码提供了驱动支持哪些设备
- MODULE_DEVICE_TABLE()。这个宏让驱动程序公开其 ID 表,该表描述它可以支持哪些设备,用于匹配设备。
这仅仅完成了第一个内容,这是总线进行匹配时所需要的内容。而在匹配成功之后,驱动需要去提取设备的资源,这部分工作都是在 probe 函数中完成。由于我们采用字符设备的框架,因此,在 probe 过程,还需要完成字符设备的注册等工作,关键接口:
- dev_get_platdata
- platform_get_resource
- devm_ioremap
三、Makefile
C
KERNEL_DIR=../../kernel/
ARCH=arm64
CROSS_COMPILE=aarch64-linux-gnu-
export ARCH CROSS_COMPILE
obj-m := led_pdev.o led_pdrv.o
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
modules clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12