In this chapter, we will implement a complete device tree + platform driver.
1. Device Tree Node Preparation
Add our device node in the device tree file:
/ {
mychardev: mychardev@0 {
compatible = "lckfb,mychardev"; // Driver matching key field
reg = <0x0 0xfec00000 0x0 0x1000>; // Register address range
buffer-len = <64>; // Custom property: buffer size
dev-id = <0xFFA8>; // Custom property: device ID
status = "okay"; // Device status
};
};2
3
4
5
6
7
8
9
Field description:
| Field | Description |
|---|---|
compatible | Driver matching field, must match of_match_table in driver |
reg | Register physical address (64-bit format): base address 0xfec00000, size 0x1000 (4KB) |
buffer-len | Custom property, buffer size 64 bytes |
dev-id | Custom property, device ID 0xFFA8 |
status | Device status, "okay" means enabled |
About the address range description of the reg field (RK3576 is an ARM64 SoC):
reg = <high 32 bits of address low 32 bits of address high 32 bits of size low 32 bits of size>;
└─────┬────┘ └─────┬────┘ └─────┬────┘ └─────┬────┘
Group 1 Group 2 Group 3 Group 4
└──────────┬──────────┘ └──────────┬──────────┘
64-bit address 64-bit size2
3
4
5
6
2. Compile Kernel and Flash
According to the Debian12 Kernel Compilation tutorial, recompile the kernel to generate boot.img, and flash the kernel image separately to replace the device tree.
After flashing is complete, boot the board and verify the device tree node:
View the unpacked device tree node
ls /sys/firmware/devicetree/base/mychardev@0Seeing compatible/reg/buffer-len/dev-id/status fields indicates they have taken effect!
3. Write Platform Driver
1. Create Project Directory
mkdir 12_devicetree_platform2. Write Driver
Enter the 12_devicetree_platform/ directory, create the platform_chardev.c file, and write the following code:
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
// Device name and class name
#define DEV_NAME "mychardev"
#define CLASS_NAME "class_mychardev"
// Global variables
static dev_t dev_num; // Device number
static struct cdev cdev_test; // Character device structure
static struct class *class_test; // Device class pointer
// ==================== Character Device Operations ====================
static int chrdev_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "mychardev: chrdev_open called\n");
return 0;
}
static ssize_t chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
printk(KERN_INFO "mychardev: chrdev_read called\n");
return 0;
}
static ssize_t chrdev_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
printk(KERN_INFO "mychardev: chrdev_write called\n");
return size;
}
static int chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "mychardev: chrdev_release called\n");
return 0;
}
static struct file_operations cdev_fops_test = {
.owner = THIS_MODULE,
.open = chrdev_open,
.read = chrdev_read,
.write = chrdev_write,
.release = chrdev_release,
};
// ==================== Platform Driver Functions ====================
// 1. Define matching table
static const struct of_device_id mychardev_of_match[] = {
{ .compatible = "lckfb,mychardev" },
{ }
};
MODULE_DEVICE_TABLE(of, mychardev_of_match);
// 2. probe function (called when device matching succeeds)
static int mychardev_probe(struct platform_device *pdev)
{
int ret;
int major, minor;
struct resource *res;
u32 buffer_len, dev_id;
printk(KERN_INFO "mychardev: probe called!\n");
// ========== Get device tree resources ==========
// Get register address resource
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
printk(KERN_ERR "mychardev: Failed to get register resource!\n");
return -ENODEV;
}
printk(KERN_INFO "mychardev: Register base address=0x%llx, size=0x%llx\n",
res->start, resource_size(res));
// Read custom property
if (of_property_read_u32(pdev->dev.of_node, "buffer-len", &buffer_len)) {
printk(KERN_ERR "mychardev: Failed to read buffer-len!\n");
return -EINVAL;
}
printk(KERN_INFO "mychardev: buffer-len=%u\n", buffer_len);
if (of_property_read_u32(pdev->dev.of_node, "dev-id", &dev_id)) {
printk(KERN_ERR "mychardev: Failed to read dev-id!\n");
return -EINVAL;
}
printk(KERN_INFO "mychardev: dev-id=0x%X\n", dev_id);
// ========== Register character device ==========
// Allocate device number
ret = alloc_chrdev_region(&dev_num, 0, 1, DEV_NAME);
if (ret < 0) {
printk(KERN_ERR "mychardev: alloc_chrdev_region failed\n");
return ret;
}
major = MAJOR(dev_num);
minor = MINOR(dev_num);
printk(KERN_INFO "mychardev: Device number major=%d, minor=%d\n", major, minor);
// Initialize and register cdev
cdev_init(&cdev_test, &cdev_fops_test);
ret = cdev_add(&cdev_test, dev_num, 1);
if (ret < 0) {
printk(KERN_ERR "mychardev: cdev_add failed\n");
unregister_chrdev_region(dev_num, 1);
return ret;
}
// Create device class
class_test = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(class_test)) {
printk(KERN_ERR "mychardev: class_create failed\n");
cdev_del(&cdev_test);
unregister_chrdev_region(dev_num, 1);
return PTR_ERR(class_test);
}
// Create device node
if (device_create(class_test, &pdev->dev, dev_num, NULL, DEV_NAME) == NULL) {
printk(KERN_ERR "mychardev: device_create failed\n");
class_destroy(class_test);
cdev_del(&cdev_test);
unregister_chrdev_region(dev_num, 1);
return -ENOMEM;
}
printk(KERN_INFO "mychardev: Driver loaded successfully!\n");
return 0;
}
// 3. remove function (called when device is removed)
static void mychardev_remove(struct platform_device *pdev)
{
printk(KERN_INFO "mychardev: remove called!\n");
// Release character device resources
device_destroy(class_test, dev_num);
class_destroy(class_test);
cdev_del(&cdev_test);
unregister_chrdev_region(dev_num, 1);
printk(KERN_INFO "mychardev: Driver unloaded successfully!\n");
}
// 4. Define platform_driver
static struct platform_driver mychardev_driver = {
.probe = mychardev_probe,
.remove_new = mychardev_remove,
.driver = {
.name = "mychardev",
.of_match_table = mychardev_of_match,
},
};
// 5. Register driver
module_platform_driver(mychardev_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LCKFB");
MODULE_DESCRIPTION("Platform Char Device Driver");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
Overall code framework:
3. Write Makefile
In the 12_devicetree_platform/ directory, create the Makefile file and write the following code:
export ARCH=arm64
# Cross compiler path
export CROSS_COMPILE=/home/lckfb/TaishanPi-3-Linux/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
# Driver module name
obj-m += platform_chardev.o
# Kernel source directory
KDIR := /home/lckfb/TaishanPi-3-Linux/kernel-6.1
PWD ? = $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CROSS_COMPILE: Still the compiler path prefix from the SDK.KDIR: Still the kernel source directory.- Need to change what follows
obj-m +=toplatform_chardev.o.
4. Compile
In the 12_devicetree_platform/ directory, use:
makeCompile the code and generate .ko file.
4. Testing
1. Load Driver
First, we need to copy the compiled platform_chardev.ko file to the development board (USB drive, TF card, or SSH), and run the load command:
sudo insmod platform_chardev.ko2. View Device Node
ls -l /dev/mychardev3. Test Read/Write
First, set permissions on /dev/mychardev:
sudo chmod 666 /dev/mychardevStart testing:
# Test write
echo "hello" > /dev/mychardev
# Test read
cat /dev/mychardev2
3
4
5
You can see that both
openandwritefunctions have been called normally, printing the information.
4. Unload Driver
sudo rmmod platform_chardevYou can use
sudo lsmodto view currently loaded module information.