0%

Linux驱动学习日记(21) 设备树下的 platform 驱动编写

  在第 17 篇学习日志中,我们学习了 Linux 下的驱动分离和分层,以及总线、设备和驱动这样的驱动框架。基于总线、设备、和驱动这样的驱动框架,Linux 内核 提出了 platform 这个虚拟总线,相应的也有 platform 设备和 platform 驱动。本次我们学习一下如何在设备树下编写 platform 驱动。

设备属下的 platform 驱动简介

  platform 驱动框架分为总线、设备和驱动,其中总线不需要我们管理,这个是 Linux 内核提供的,我们在编写驱动时只需要关注与设备和驱动的具体实现即可。在使用设备树的时候,设备的描述被放在了设备树中,因此 platform_device 就不需要我们去编写了,我们只需要实现 platform_driver 即可。在编写设备树的 platform 驱动的时候需要我们注意以下几点:

  1、在设备树中创建节点
  我们需要先在设备树中创建设备节点来描述设备信息,重点是要设置好 compatible 属性的值,因为 platform 总线需要通过设备节点的 compatible 属性值来匹配驱动。比如,我们可以编写如下所示的设备节点来描述我们即将使用到的 LED 这个设备:

1
2
3
4
5
6
7
8
9
gpioled{
#address-cells = <1>;
#size-cells = <1>;
compatible = "hcw-gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};

  这里我们将 compatible 属性值设置为 “hcw-gpioled”,因此一会儿在编写 platform 驱动的时候 of_match_table 属性表中要有 “hcw-gpioled”。

  2、编写 platform 驱动的时候要注意兼容属性
  在使用设备树的时候 platform 驱动会通过 of_match_table 来保存兼容性值,也就是表明此驱动兼容哪些设备。所以,of_match_table 会很重要。比如在本次学习中,platform 驱动里的 platform_driver 可以按照如下代码设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static const struct of_device_id leds_of_match[] = {
{.compatible = "hcw-gpioled"},
{/* Sentinel */}
};

MODULE_DEVICE_TABLE(of,leds_of_match);

static struct platform_driver leds_platform_driverj = {
.driver = {
.name = "imx6ul-led",
.of_match_table = leds_of_match,
}
.probe = leds_probe,
.remove = leds_remove,
};

  其中,of_device_id 表是驱动的兼容表,是一个数组,每个数组元素为 of_device_if 类型。每个数组元素都是一个兼容属性,表示兼容的设备,一个驱动可以跟多个设备进行匹配,这里我们仅仅匹配了一个设备,那就是上文创建的 gpioled 这个设备。**注意!在编写 of_device_id 的时候最后一个元素一定要为空!
  然后我们需要通过 MODULE_DEVICE_TABLE 来声明 leds_of_match 这个设备匹配表。最后,我们设置 platform_driver 中的 of_match_table 匹配表为上面创建的 leds_of_match,至此我们就设置好 platform 驱动的匹配表了。

驱动程序编写

修改设备树文件

  首先修改设备树文件,加上我们需要的设备信息。本章我们只使用了一个 gpio 灯,因此可以直接使用之前写过的 gpioled 子节点。

platform 驱动程序编写

  新建文件夹,创建 C 文件,写入以下代码

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
/*
* @Author: 胡城玮
* @FilePath: gpioled.c
* @Date: 2026-03-11
* @Description:
* @Version: 0.1
*/

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/file.h>
#include <linux/platform_device.h>

#define LEDDEV_CNT 1
#define LEDDEV_NAME "dtsplatled"
#define LEDOFF 0
#define LEDON 1

struct leddev_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备信息 */
int major; /* 主设备号 */
struct device_node *node; /* LED 设备节点 */
int led0; /* LED 灯 GPIO 标号 */
};

struct leddev_dev leddev; /* LED 设备 */

/**
* @description: LED 切换状态
* @param {u8} sta LED 状态
* @return {*}
*/
void led0_switch(u8 sta)
{
if (sta == LEDON)
gpio_set_value(leddev.led0, 0);
else if (sta == LEDOFF)
gpio_set_value(leddev.led0, 1);
}

/**
* @description: 打开设备
* @param {inode} *inode 传递给设备的 inode
* @param {file} *filp 设备文件
* @return {*} 0 成功 其他失败
*/
static int led_open(struct inode *inode, struct file *filp){
filp->private_data = &leddev;
return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[2];
unsigned char ledstat;

retvalue = copy_from_user(databuf,buf,cnt);
if(retvalue < 0)
{
printk("kernel write failed!\r\n");
return -EFAULT;
}

ledstat = databuf[0];
if (ledstat == LEDON)
{
led0_switch(LEDON);
}
else if (ledstat == LEDOFF)
{
led0_switch(LEDOFF);
}

return 0;
}

static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};

static int led_probe(struct platform_device *dev)
{
printk("led driver and device was matched!\r\n");

if(leddev.major){
leddev.devid = MKDEV(leddev.major, 0);
register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
}
else
{
alloc_chrdev_region(&leddev.devid, 0,LEDDEV_CNT,LEDDEV_NAME);
leddev.major = MAJOR(leddev.devid);
}

cdev_init(&leddev.cdev, &led_fops);
cdev_add(&leddev.cdev,leddev.devid,LEDDEV_CNT);

leddev.class = class_create(THIS_MODULE,LEDDEV_NAME);
if(IS_ERR(leddev.class))
{
return PTR_ERR(leddev.class);
}

leddev.device = device_create(leddev.class,NULL,leddev.devid,NULL,LEDDEV_NAME);
if(IS_ERR(leddev.device))
{
return PTR_ERR(leddev.device);
}

leddev.node = of_find_node_by_path("/gpioled");
if(leddev.node == NULL)
{
printk("gpioled does not found\r\n");
return -EINVAL;
}

leddev.led0 = of_get_named_gpio(leddev.node,"led-gpio",0);
if(leddev.led0 < 0)
{
printk("cant get led-gpio\r\n");
return -EINVAL;
}

gpio_request(leddev.led0, "led0");
gpio_direction_output(leddev.led0,1);
return 0;
}

static int led_remove(struct platform_device *dev)
{
gpio_set_value(leddev.led0, 1);

cdev_del(&leddev.cdev);

unregister_chrdev_region(leddev.devid,LEDDEV_CNT);
device_destroy(leddev.class, leddev.devid);
class_destroy(leddev.class);
return 0;
}

static const struct of_device_id led_of_match[] =
{
{.compatible = "hcw-gpioled"},
{}
};

static struct platform_driver led_driver =
{
.driver = {
.name = "imx6u-led",
.of_match_table = led_of_match
},
.probe = led_probe,
.remove = led_remove,
};

static int __init leddrv_init(void)
{
return platform_driver_register(&led_driver);
}

static void __exit leddrv_exit(void)
{
platform_driver_unregister(&led_driver);
}

module_init(leddrv_init);
module_exit(leddrv_exit);

MODULE_AUTHOR("hcw");
MODULE_LICENSE("GPL");

编译后装载

  复制到根文件系统后,通过 insmod 命令装载,显示匹配成功。运行测试程序,可以正常点亮或熄灭。

图 1 成功匹配