1. What is platform_driver
platform_driver is the structure used in the Linux kernel to describe a Platform driver.
Its role is:
- Tell the kernel "which devices I can drive" (through matching table)
- Implement device initialization and cleanup logic
- Provide callback functions such as
probe()andremove()
2. platform_driver Structure Details
platform_driver structure is defined in TaishanPi-3-Linux/kernel-6.1/include/linux/platform_device.h:
struct platform_driver {
int (*probe)(struct platform_device *); // Device probe function, called when device and driver match successfully
int (*remove)(struct platform_device *); // Device remove function (old version)
void (*remove_new)(struct platform_device *); // Device remove function (new version)
void (*shutdown)(struct platform_device *); // Called when system shuts down/restarts
int (*suspend)(struct platform_device *, pm_message_t state); // Called when system suspends (hibernates)
int (*resume)(struct platform_device *); // Called when system resumes (wakes up)
struct device_driver driver; // Generic device driver structure
const struct platform_device_id *id_table; // Device ID matching table (for non-device tree matching)
bool prevent_deferred_probe; // Prevent deferred probing
bool driver_managed_dma; // Driver manages DMA itself (true/false)
};2
3
4
5
6
7
8
9
10
11
12
Core parameters:
probe: Initialization function called after device matching succeedsremove/remove_new: Cleanup function called when device is removeddriver.name: Driver namedriver.of_match_table: Device tree matching table
1. Initialization Function
When device and driver match successfully, the kernel automatically calls the probe() function.
Function prototype:
int (*probe)(struct platform_device *pdev);Parameters:
pdev: Pointer to theplatform_devicestructure that matched successfully
Return value:
0: Initialization successful- Negative number: Initialization failed (error code)
Role:
- Get device resources (register addresses, interrupt numbers, etc.)
- Allocate character device number
- Register character device
- Create device node
- Initialize hardware
2. Cleanup Function
When device is removed or driver is unloaded, the kernel automatically calls the remove() or remove_new() function.
Function prototype:
// Old version (returns int)
int (*remove)(struct platform_device *pdev);
// New version (returns void)
void (*remove_new)(struct platform_device *pdev);2
3
4
5
Difference between the two versions:
remove(): Old version, returnsint(but kernel usually ignores return value)remove_new(): New version (kernel 6.x), returnsvoid, more aligned with actual usage- New drivers should use
remove_new()
Role:
- Release resources allocated in
probe() - Unregister character device
- Delete device node
- Release interrupt
- Unmap address
3. System Shutdown Function
shutdown() is called when the system shuts down or restarts, used to safely shut down the device.
Usage scenarios:
- Turn off device power
- Stop DMA transfers
- Save device state
4. Power Management Functions
suspend / resume are used for device power management during system hibernation and wake-up.
Usage scenarios:
- Laptop hibernation/wake-up
- Embedded device low-power mode
5. Device Tree Matching Table
driver.of_match_table is the most important member, used to specify which device tree nodes the driver can match.
Matching table structure:
struct of_device_id {
char name[32]; // Device name
char type[32]; // Device type
char compatible[128]; // Matching compatible string
const void *data; // Optional private data pointer
};2
3
4
5
6
Example:
static const struct of_device_id my_of_match[] = {
{ .compatible = "lckfb,mychardev" }, // Match node with compatible = "lckfb,mychardev"
{ /* Terminator */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);2
3
4
5
6. ID Matching Table
id_table is used for old-style matching without device tree.
Usage scenarios:
- x86 platforms (partial)
- Old kernels (without device tree support)
7. Prevent Deferred Probe
prevent_deferred_probe When set to true, if probe() returns -EPROBE_DEFER, the kernel will not retry with delay.
Usage scenarios:
- Driver does not want deferred probing
- Usually keep default value
false
8. DMA Management Flag
driver_managed_dma indicates whether the driver manages DMA itself.
Usage scenarios:
- Device needs DMA transfer
- Simple character devices usually don't need to care about this field
3. Steps to Write platform_driver
We create the following device tree node
/ {
mychardev@0 {
compatible = "lckfb,mychardev";
reg = <0x12340000 0x1000>; // Register address
interrupts = <56>; // Interrupt number
dev-id = <0xFFA8>; // Custom property
status = "okay";
};
};2
3
4
5
6
7
8
9
1. Define Matching Table
static const struct of_device_id my_of_match[] = {
{ .compatible = "lckfb,mychardev" }, // Must match compatible in device tree
{ /* Terminator */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);2
3
4
5
2. Implement probe Function
static int my_probe(struct platform_device *pdev)
{
struct resource *res;
int irq;
u32 dev_id;
// 1. Get register address
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (! res) {
return -ENODEV;
}
// 2. Get interrupt number
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
return irq;
}
// 3. Read custom property
if (of_property_read_u32(pdev->dev.of_node, "dev-id", &dev_id)) {
return -EINVAL;
}
// 4. Other code:
// - Allocate character device number
// - Register cdev
// - Create device node
// - ioremap registers
// - Register interrupt handler
return 0;
}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
3. Implement remove Function
static int my_remove(struct platform_device *pdev)
{
// Release resources:
// 1. Unregister character device
// 2. Delete device node
// 3. Release interrupt
// 4. iounmap address mapping
return 0;
}2
3
4
5
6
7
8
9
10
4. Define platform_driver
static struct platform_driver my_driver = {
.probe = my_probe, // Called when matching succeeds
.remove = my_remove, // Called when removed
.driver = {
.name = "mychardev", // Driver name
.of_match_table = my_of_match, // Device tree matching table
},
};2
3
4
5
6
7
8
5. Register Driver
Use module_platform_driver() macro to automatically implement module_init() and module_exit():
module_platform_driver(my_driver);This macro will be automatically expanded to the following code:
static int __init my_driver_init(void)
{
return platform_driver_register(&my_driver);
}
static void __exit my_driver_exit(void)
{
platform_driver_unregister(&my_driver);
}
module_init(my_driver_init);
module_exit(my_driver_exit);2
3
4
5
6
7
8
9
10
11
12
Therefore, after using this macro, no other operations are needed.
4. Driver and Device Matching Process
1. Matching Flow
2. compatible Matching Rules
Device tree:
mychardev@0 {
compatible = "lckfb,mychardev";
};2
3
Driver matching table:
static const struct of_device_id my_of_match[] = {
{ .compatible = "lckfb,mychardev" },
{ }
};2
3
4
INFO
compatiblestring must match exactly- Device tree can have multiple
compatiblevalues; driver only needs to match one - After successful matching, kernel calls
probe()function
3. When Multiple compatible Values Exist
Device tree node:
mychardev@0 {
compatible = "lckfb,mychardev-v2", "lckfb,mychardev";
};2
3
Driver matching table:
static const struct of_device_id my_of_match[] = {
{ .compatible = "lckfb,mychardev-v2" }, // Prefer matching v2 first
{ .compatible = "lckfb,mychardev" }, // Compatibility
{ }
};2
3
4
5
The kernel tries to match in order; as long as one matches, it's successful.
5. Complete Driver Framework
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
// 1. Define matching table
static const struct of_device_id my_of_match[] = {
{ .compatible = "lckfb,mychardev" },
{ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
// 2. Implement probe function
static int my_probe(struct platform_device *pdev)
{
printk(KERN_INFO "mychardev: probe!\n");
// Get resources, initialize device...
return 0;
}
// 3. Implement remove function
static int my_remove(struct platform_device *pdev)
{
printk(KERN_INFO "mychardev: remove!\n");
// Release resources...
return 0;
}
// 4. Define platform_driver
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "mychardev",
.of_match_table = my_of_match,
},
};
// 5. Register driver
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LCKFB");
MODULE_DESCRIPTION("Platform Driver Frame");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