Cover image for Linux Platform Bus

Linux Platform Bus


Timeline

Timeline

2025-11-15

init

This article introduces the concept and role of the platform bus in the Linux kernel, pointing out that it serves as an abstraction layer connecting platform devices and platform drivers, primarily used for devices directly integrated on SoCs or motherboards. The article also summarizes the advantages of the platform bus model in improving code reusability by separating devices and drivers, and discusses in detail core structures such as platform_device and resource, along with their parameter configuration methods.

Linux Driver Notes

Table of ContentsLinks
1. Linux Driver Framework
2. Linux Driver Loading Logic
3. Character Device Basics
4. Concurrency and Race Conditions
5. Advanced Character Device Progression
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hot Plug
11. pinctrl Subsystem
12. GPIO Subsystem
13. Input Subsystem
14. Single Bus
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network Device
23. ADC
24. IIO
25. USB
26. LCD

Platform Bus

Inthe Linux Kernel,the Platform Busis a mechanism for managing and connectingplatform devicesandplatform driverabstraction layer. It acts as a bridge between platform devices and platform drivers, responsible for matching and binding them.

It is mainly used for devices thatare not discovered through standard buses (such as PCI, USB, I²C, SPI), but aredirectly integrated into the SoC (System on Chip) or on the motherboard

Relationship between device, driver, and platform bus
Relationship between device, driver, and platform bus

By using the platform bus model, device drivers and platform devices are separated. This way, we only need to write a generic driver code, and then configure it for different platform devices, which greatly reduces the workload of rewriting code and improves the reusability of driver code.

When we need to port the driver to a different platform, we only need to adapt the hardware-related parts, while the rest can remain unchanged.

Under the platform bus,devices (platform_device) and drivers (platform_driver) are two independent objects

  • Devices come from DT/board-level files/platform code
  • Drivers come from module loading/compilation into the kernel
  • Driver unloading does not mean device disappearance

Register platform device

struct platform_device

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// include/linux/platform_device.h
struct platform_device {
const char *name;// Device name, used to uniquely identify the device
int id;// Device ID, used to distinguish different instances of the same device
bool id_auto;// Indicates whether the device ID is automatically generated
struct device dev;// Represents the struct device corresponding to the platform device, used for basic device management and operations
u64 platform_dma_mask;
struct device_dma_parameters dma_parms;
u32 num_resources;// Number of device resources
struct resource *resource;// Pointer to device resources

const struct platform_device_id *id_entry;// Pointer to the device ID table entry, used for matching devices and drivers
/*
* Driver name to force a match. Do not set directly, because core
* frees it. Use driver_set_override() to set or clear it.
*/
const char *driver_override;// Driver name that forces the device to match a specified driver

/* MFD cell pointer */
struct mfd_cell *mfd_cell;// Pointer to a multi-function device (MFD) cell, used for describing multi-function devices

/* arch specific additions */
struct pdev_archdata archdata;// Used to store architecture-specific device data
};
  • const char *name: Device name, used to uniquely identify the device. A unique name must be provided so that the kernel can correctly identify and manage the device.
  • int id: Device ID, used to distinguish different instances of the same device. This parameter is optional; if ID-based differentiation is not needed, it can be set to -1
  • struct device dev: Represents the struct device corresponding to the platform device, used for basic device management and operations. A valid struct device object must be provided for this parameter,The release method of this structure must be implemented, otherwise a compilation error will occur
  • u32 num_resources: The number of device resources. If the device has resources (such as memory regions, interrupts, etc.), the number of resources needs to be provided.
  • struct resource *resource: A pointer to the device resources. If the device has resources, a pointer to the resource array needs to be provided. This structure will be explained in detail in the next subsection.

struct resource

1
2
3
4
5
6
7
8
9
10
//include/linux/ioport.h
struct resource {
resource_size_t start;/* Start address of the resource */
resource_size_t end;/* End address of the resource */
const char *name;/* Name of the resource */
unsigned long flags;/* Flags of the resource */
unsigned long desc;/* Description information of the resource */
struct resource *parent, *sibling, *child;/* Pointer to child resources */
};

The most important are the first four parameters, and the specific introduction of each parameter is as follows:

  • resource_size_t start: The start address of the resource. It indicates the starting position of the resource or the address of the starting register.
  • resource_size_t end: The end address of the resource. It indicates the ending position of the resource or the address of the ending register.
  • const char *name: The name of the resource. It is a string used to identify and describe the resource.
  • unsigned long flags: The flags of the resource. It contains specific flags used to indicate the attributes or characteristics of the resource. For example, flags can be used to indicate the availability, shareability, cache attributes, etc., of the resource. The specific values and meanings of the flags parameter can be defined and interpreted according to the system and driver requirements, but typically, it is used to represent the attributes, characteristics, or configuration options of the resource. Below are some common flags and their possible meanings
CategoryFlagsDescription
Resource TypeRelated FlagsIORESOURCE_IOResource is I/O port resource
IORESOURCE_MEMResource is memory resource
IORESOURCE_REGResource is register offset
IORESOURCE_IRQResource is interrupt resource
IORESOURCE_DMAResource is DMA (Direct Memory Access) resource
IORESOURCE_BUSBus
Resource Attributes and CharacteristicsRelated FlagsIORESOURCE_PREFETCHResource is side-effect-free prefetchable resource
IORESOURCE_READONLYResource is read-only
IORESOURCE_CACHEABLEResource supports caching
IORESOURCE_RANGELENGTHResource range length
IORESOURCE_SHADOWABLEResource can be replaced by shadow resource
IORESOURCE_SIZEALIGNResource size field alignment
IORESOURCE_STARTALIGNStart address field alignment
IORESOURCE_MEM_64Resource is a 64-bit memory resource
IORESOURCE_WINDOWResource forwarded by bridge
IORESOURCE_MUXEDResource reused by software
IORESOURCE_SYSRAMResource is system RAM (modifier)
Status and ControlRelated flagsIORESOURCE_EXCLUSIVEUser space cannot map this resource
IORESOURCE_DISABLEDResource is currently disabled
IORESOURCE_UNSETAddress not yet assigned to resource
IORESOURCE_AUTOAddress automatically assigned by system
IORESOURCE_BUSYDriver marks resource as busy

Embedded instruct platform_deviceinstruct resourcecan be retrieved viaplatform_get_resource()function. The following isplatform_get_resourceprototype:

1
struct resource *platform_get_resource(struct platform_device *pdev, unsigned int type, unsigned int num);
  • The first parameter is the instance of the platform device itself.
  • The second parameter specifies what kind of resource is needed. For memory, it should beIORESOURCE_MEM
  • The num parameter is an index indicating which resource type is needed. Zero means the first, and so on.

If it is an interrupt resource, you must use:

1
2
3
int platform_get_irq(struct platform_device *dev, unsigned int num);

int platform_get_irq_optional(struct platform_device *dev, unsigned int num)

platform_device_register()

ItemDescription
Function Definitionint platform_device_register(struct platform_device *pdev);
Header File#include <linux/platform_device.h>
Parameter pdevPointer toplatform_devicestructure pointer, describing the platform device to be registered, including device name, resources, device ID, etc.
FunctionRegister the platform device with the kernel, enabling it to participate in device resource allocation and driver matching.
Return ValueSuccess: returns 0;
Failure: returns a negative error code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// include/linux/platform_device.h
#define platform_get_device_id(pdev) ((pdev)->id_entry)

#define dev_is_platform(dev) ((dev)->bus == &platform_bus_type)
#define to_platform_device(x) container_of((x), struct platform_device, dev)

extern int platform_device_register(struct platform_device *);
extern void platform_device_unregister(struct platform_device *);


// drivers/base/platform.c

/**
* platform_device_register - add a platform-level device
* @pdev: platform device we're adding
*/
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
setup_pdev_dma_masks(pdev);
return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);
  • device_initialize(&pdev->dev)Initializepdev->dev.pdev->devisstruct platform_devicea member of the structure, representing the platform device’s correspondingstruct devicestructure. By calling thedevice_initializefunction, perform basic initialization onpdev->dev, such as setting the device’s reference count and device type.
  • setup_pdev_dma_masksSet the architecture-specific data of pdev based on the platform device’s architecture data. The specific implementation of this function may be architecture-dependent, and it is mainly used to perform specific settings for the platform device under different architectures.
  • platform_device_addfunction adds the platform device pdev to the kernel.platform_device_addfunction completes the addition of the platform device, including adding the device to the device hierarchy and adding the device’s resources. It returns an int result indicating the outcome of the device addition.

platform_device_registerThe main function of this function is toplatform_deviceregister the platform device described by the structure into the kernel, including device initialization, adding to the platform bus and device hierarchy, and adding device resources.

Through this function, after the platform device is registered, it can participate in the device resource allocation and driver matching process. The return value of the function can be used to determine whether the device registration is successful.

platform_device_unregister()

ItemDescription
Function Definitionvoid platform_device_unregister(struct platform_device *pdev);
Header File#include <linux/platform_device.h>
Parameter pdevPointer to the platform device to be unregisteredplatform_devicestructure pointer
FunctionUnregister a registered platform device, remove the device from the kernel, and perform resource cleanup.
Return ValueNo return value
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// drivers/base/platform.c

/**
* platform_device_unregister - unregister a platform-level device
* @pdev: platform device we're unregistering
*
* Unregistration is done in 2 steps. First we release all resources
* and remove it from the subsystem, then we drop reference count by
* calling platform_device_put().
*/
void platform_device_unregister(struct platform_device *pdev)
{
platform_device_del(pdev);
platform_device_put(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_unregister);
  • platform_device_delFunction to remove the device from the device list of the platform bus. It removes the device from the device hierarchy and stops device resource allocation and driver matching.
  • platform_device_putfunction, used to decrease the reference count of a device. This function checks the device’s reference count; if the count drops to zero, it releases the device structure and related resources. By decreasing the reference count, it ensures that the device can be freed when no longer in use.

platform_device_unregisterThe function is used to unregister a previously registered platform device, removing it from the kernel. It first callsplatform_device_delfunction to remove the device from the device hierarchy, and then callsplatform_device_putfunction to decrease the device’s reference count, ensuring the device can be freed when no longer in use.

Example

In lower kernel versionsplatform_devicethe release callback function must be implemented, otherwise compilation may fail

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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h> // struct resource

#define MEM_START_ADDR 0xFDD60000
#define MEM_END_ADDR 0xFDD60004
#define IRQ_NUMBER 101

static struct resource my_resources[] = { {
.start = MEM_START_ADDR,
.end = MEM_END_ADDR,
.name = "test_resource1",
.flags = IORESOURCE_MEM, // Marked as memory resource
},
{
.start = IRQ_NUMBER,
.end = IRQ_NUMBER,
.flags = IORESOURCE_IRQ, // Marked as interrupt resource
} };

void my_dev_release(struct device *dev) // Callback function for releasing pdev->dev resources
{
pr_info("my_dev_release is called\n");
}

static struct platform_device my_platform_device = {
.name = "my_platform_device",
.id = -1, // Device ID
.num_resources = ARRAY_SIZE(my_resources),
.resource = my_resources,
.dev.release = my_dev_release,
};

static int __init platform_device_test_init(void)
{
int ret;
ret = platform_device_register(&my_platform_device);
if (ret < 0){
pr_err("platform_device_register fail\n");
return ret;
}
pr_info("platform_device register success\n");


return 0;
}

static void __exit platform_device_test_exit(void)
{
platform_device_unregister(&my_platform_device);
}

module_init(platform_device_test_init);
module_exit(platform_device_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is a test sample for platform_device");

After loading, in the/sys/bus/platform/devicesdirectory, we can see themy_platform_devicedevice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
~ # insmod platform_device_test.ko
[ 10.099551] platform_device_test: loading out-of-tree module taints kernel.
[ 10.115921] platform_device register success
~ # ls /sys/devices/platform/
Fixed MDIO bus.0/ fixedregulator_5v0/ scb/
arm-pmu/ kgdboc/ sd_io_1v8_reg/
cam1_regulator/ leds/ sd_vcc_reg/
cam_dummy_reg/ my_platform_device/ soc/
cpufreq-dt/ phy/ timer/
emmc2bus/ power/ uevent
fixedregulator_3v3/ reg-dummy/ v3dbus/
~ # ls /sys/devices/platform/my_platform_device/
driver_override power uevent
modalias subsystem

Register platform driver

struct platform_driver

1
2
3
4
5
6
7
8
9
10
11
// include/linux/platform_device.h
struct platform_driver {
int (*probe)(struct platform_device *); /* Probe function pointer for platform device */
int (*remove)(struct platform_device *); /* Remove function pointer for platform device */
void (*shutdown)(struct platform_device *);/* Shutdown function pointer for platform device */
int (*suspend)(struct platform_device *, pm_message_t state);/* Suspend function pointer for platform device */
int (*resume)(struct platform_device *);/* Resume function pointer for platform device */
struct device_driver driver;/* Generic data for device driver */
const struct platform_device_id *id_table;/* Association table between platform devices and drivers */
bool prevent_deferred_probe;/* Whether to prevent deferred probing */
};
  • probe: Probe function pointer for platform device. When the system detects a platform device matching this driver, this function is called to initialize and configure the device.

  • remove: Remove function pointer for platform device. When a platform device is removed from the system, this function is called to perform cleanup and resource release operations.

  • shutdown: Shutdown function pointer for platform device. When the system shuts down, this function is called to perform shutdown operations related to the platform device.

  • suspend: Suspend function pointer for platform device. When the system enters a suspend state, this function is called to perform suspend operations related to the platform device.

  • resume: Resume function pointer for platform device. When the system resumes from a suspend state, this function is called to perform resume operations related to the platform device.

  • driver: Contains generic data related to the device driver, which is an instance of typestruct device_driver. It includes information such as the driver’s name, bus type, module owner, and pointer to an array of attribute groups.

  • id_table: Pointer tostruct platform_device_ida pointer to an array of structs, used to match the association between platform devices and drivers. Only upon successful matching can the probe function be entered, but its priority is lower thanplatform_driver.driver.of_match_table

  • prevent_deferred_probe: A boolean value used to determine whether to prevent deferred probing. If set to true, deferred probing will be disabled.

Note: The probe function is not a replacement for the init function. The probe function is called whenever a given device matches a driver, while the init function runs only once when the module is loaded.

You can useMODULE_DEVICE_TABLE(type, name);to export the device ID tableplatform_driver.driver.of_match_tableinto the module’s ELF section, allowing userspace (udev/modprobe) to automatically load the driver.

struct device_driver

struct device_driveristhe Device Model layer)abstraction, which describes theidentity and behavior of the driver itself, for example:

  • driver name, bus type, module information
  • device match table (device tree, ACPI)
  • driver operation callbacks:probe/remove/suspend/resume
  • sysfs attributes and power management

Core idea:It focuses on the matching and lifecycle management between drivers and devices

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
// include/linux/device/driver.h
struct device_driver {
const char *name;// The name of the driver, used to match devices.
struct bus_type *bus;// The bus type the driver belongs to, e.g., PCI, USB, I2C, etc.

struct module *owner;// Pointer to the module (struct module) the driver belongs to, used for module reference counting.
const char *mod_name; /* used for built-in modules */ //Module name; if it is a built-in driver rather than a module, this name is used.

bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ //If true, manual binding/unbinding of devices via sysfs is disabled.
enum probe_type probe_type;// Driver probe method, determining how the driver is automatically bound to devices (e.g., normal probe or deferred probe).

const struct of_device_id *of_match_table;// Used for Device Tree matching
const struct acpi_device_id *acpi_match_table;// Used for ACPI device matching.

int (*probe) (struct device *dev);// Core function, called when a driver matches a device, to initialize the device.
void (*sync_state)(struct device *dev);// Synchronize device state, mainly used internally by the kernel
int (*remove) (struct device *dev);// Called when the device is removed or the driver is unloaded.
void (*shutdown) (struct device *dev);// Called when the system shuts down.
int (*suspend) (struct device *dev, pm_message_t state);// Used for power management, suspend.
int (*resume) (struct device *dev);// Used for power management, resume.
// Used to expose sysfs attributes of drivers and devices, which can be viewed and manipulated via /sys/bus/... or /sys/class/...
const struct attribute_group **groups;
const struct attribute_group **dev_groups;

const struct dev_pm_ops *pm;// Pointer to the structure of power management operations, containing callbacks such as suspend/resume, runtime PM, etc.
void (*coredump) (struct device *dev);// When a device encounters a critical error, it can trigger the driver's coredump callback for debugging.

struct driver_private *p;// Used internally by the kernel to store driver-related private data, such as a list of bound devices.
};

platform_driver_register()

ItemDescription
Function Definitionint platform_driver_register(struct platform_driver *driver);
Header File#include <linux/platform_device.h>
Parameter driverPointer toplatform_drivera pointer to the structure describing the platform driver to be registered, including attributes and callbacks.
FunctionRegisters the platform driver with the kernel, enabling the kernel to match it with specific platform devices and invoke the corresponding callbacks.
Return valueSuccess: returns 0;
Failure: returns a negative error code
1
2
3
4
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
extern int __platform_driver_register(struct platform_driver *,
struct module *);

This macro is used to simplify the registration process of platform drivers. It associates the actual registration function__platform_driver_registerwith the current module (driver). The macro parameter drv is a pointer tostruct platform_drivera structure that describes the properties and callback functions of the platform driver to be registered.THIS_MODULEis a macro used to obtain the pointer of the current module.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// drivers/base/platform.c
/**
* __platform_driver_register - register a driver for platform-level devices
* @drv: platform driver structure
* @owner: owning module/driver
*/
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;// Set the ownership of the platform driver to the current module
drv->driver.bus = &platform_bus_type;// Set the bus type of the platform driver to platform bus
drv->driver.probe = platform_drv_probe;// Set the probe function of the platform driver
drv->driver.remove = platform_drv_remove;// Set the remove function of the platform driver
drv->driver.shutdown = platform_drv_shutdown;// Set the shutdown function of the platform driver

return driver_register(&drv->driver);// Register the platform driver with the kernel
}
EXPORT_SYMBOL_GPL(__platform_driver_register);

Through these operations,__platform_driver_registerthe function associates the platform driver with the kernel, ensuring that the kernel can correctly identify and call the various callback functions of the driver to achieve interaction and management with platform devices. The return value of the function indicates the execution status of the registration process, allowing for error handling when necessary.

  • platform_driver_register(): Registers the driver and places it into the driver list maintained by the kernel, so that whenever a new match is found, its probe() function can be called on demand.
  • platform_driver_probe(): After calling this function, the kernel immediately runs a matching loop to check if there is a platform device name match; if matched, it calls the driver’sprobe(), meaning the device exists; otherwise, the driver is ignored. This method prevents deferred probing because it does not register the driver on the system. Here, the probe function is placed in the__initsection, which is freed when kernel startup completes, thus preventing deferred probing and reducing the driver’s memory footprint. Use this method if you are 100% sure the device exists in the system.

platform_driver_unregister()

ItemDescription
Function Definitionint platform_driver_register(struct platform_driver *driver);
Header File#include <linux/platform_device.h>
Parameter driverPointer toplatform_driverstructure pointer, describing the platform driver to be registered, including attributes and callback functions.
FunctionRegisters the platform driver with the kernel, enabling the kernel to match it with specific platform devices and call the corresponding callback functions.
Return ValueSuccess: returns 0;
Failure: returns a negative error code
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
// include/linux/platform_device.h
extern void platform_driver_unregister(struct platform_driver *);

// drivers/base/platform.c

/**
* platform_driver_unregister - unregister a driver for platform-level devices
* @drv: platform driver structure
*/
void platform_driver_unregister(struct platform_driver *drv)
{
driver_unregister(&drv->driver);
}
EXPORT_SYMBOL_GPL(platform_driver_unregister);

// drivers/base/driver.c

/**
* driver_unregister - remove driver from system.
* @drv: driver.
*
* Again, we pass off most of the work to the bus-level call.
*/
void driver_unregister(struct device_driver *drv)
{
// Check whether the passed device driver pointer and p member are valid
if (!drv || !drv->p) {
WARN(1, "Unexpected driver unregister!\n");
return;
}
driver_remove_groups(drv, drv->groups);// Remove the attribute group associated with the device driver
bus_remove_driver(drv);// Remove the device driver from the bus
}
EXPORT_SYMBOL_GPL(driver_unregister);
  • bus_remove_driverFunction to remove the device driver from the bus. This function performs the following operations:
    • Remove the specified device driver from the bus driver list.
    • Call the remove callback function associated with the device driver (if defined).
    • Release the resources and memory occupied by the device driver.
    • Finally destroy the data structure of the device driver.

By calling thedriver_unregisterfunction, the device driver can be properly unregistered, and necessary cleanup work is performed during the unregistration process. This avoids resource leaks and other issues. After calling this function, avoid continuing to use the pointer of the unregistered device driver, as the driver no longer exists in the kernel

platform_get_resource()

Obtain resource information of the platform device in the driver, and perform subsequent operations and configurations based on this information

ItemDescription
Function Definitionstruct resource *platform_get_resource(struct platform_device *pdev, unsigned int type, unsigned int num);
Header file#include <linux/platform_device.h>
Parameter pdevPointer to the platform device from which to obtain resources (platform_device) structure pointer
Parameter typeResource type, such as:
IORESOURCE_MEM: Memory resource
IORESOURCE_IO: I/O resource
IORESOURCE_IRQ: Interrupt resource
Parameter numResource index number, used to select thenum-th resource of the same type** in the device
FunctionRetrieve resource information of a specified type and index from the platform device’s resource array
Return valueOn success: returns a pointer tostruct resourcepointer;
On failure or resource not found: returnsNULL

helper_macro

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
/* module_platform_driver() - Helper macro for drivers that don't do
* anything special in module init/exit. This eliminates a lot of
* boilerplate. Each module may only use this macro once, and
* calling it replaces module_init() and module_exit()
*/
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)

/* builtin_platform_driver() - Helper macro for builtin drivers that
* don't do anything special in driver init. This eliminates some
* boilerplate. Each driver may only use this macro once, and
* calling it replaces device_initcall(). Note this is meant to be
* a parallel of module_platform_driver() above, but w/o _exit stuff.
*/
#define builtin_platform_driver(__platform_driver) \
builtin_driver(__platform_driver, platform_driver_register)

/* module_platform_driver_probe() - Helper macro for drivers that don't do
* anything special in module init/exit. This eliminates a lot of
* boilerplate. Each module may only use this macro once, and
* calling it replaces module_init() and module_exit()
*/
#define module_platform_driver_probe(__platform_driver, __platform_probe) \
static int __init __platform_driver##_init(void) \
{ \
return platform_driver_probe(&(__platform_driver), \
__platform_probe); \
} \
module_init(__platform_driver##_init); \
static void __exit __platform_driver##_exit(void) \
{ \
platform_driver_unregister(&(__platform_driver)); \
} \
module_exit(__platform_driver##_exit);

/* builtin_platform_driver_probe() - Helper macro for drivers that don't do
* anything special in device init. This eliminates some boilerplate. Each
* driver may only use this macro once, and using it replaces device_initcall.
* This is meant to be a parallel of module_platform_driver_probe above, but
* without the __exit parts.
*/
#define builtin_platform_driver_probe(__platform_driver, __platform_probe) \
static int __init __platform_driver##_init(void) \
{ \
return platform_driver_probe(&(__platform_driver), \
__platform_probe); \
} \
device_initcall(__platform_driver##_init); \

module_platform_driver(driver)

📌 Purpose

Used forloadable kernel module.ko), and the driver structure has been fully defined with.probe,.removeand other members.

🔧 Expanded effect

1
2
3
4
5
6
7
8
9
10
11
static int __init driver_init(void)
{
return platform_driver_register(&driver);
}
module_init(driver_init);

static void __exit driver_exit(void)
{
platform_driver_unregister(&driver);
}
module_exit(driver_exit);

✅ Prerequisites

  • driveris a completestruct platform_drivervariable;
  • Implemented.probe,.removeand other callback functions.
1
2
3
4
5
6
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = { .name = "my-dev", ... },
};
module_platform_drvier(my_driver); // ← Note: the actual spelling is module_platform_driver

builtin_platform_driver(driver)

📌 Purpose

Used fordrivers compiled into the kernel (not as modules), and the driver structureis fully defined

🔧 Expanded effect

1
2
3
4
5
static int __init driver_init(void)
{
return platform_driver_register(&driver);
}
device_initcall(driver_init); // Called during kernel startup

Noexitpart (because built-in drivers are usually not unloaded).

✅ Applicable scenarios

  • Driver statically linked to the kernel (CONFIG_MY_DRIVER=y);
  • no need to support runtime unloading.

module_platform_driver_probe(driver, probe_fn)

📌 Purpose

Used forloadable modules, but you only want to provide a.probefunction, without manually defining the completeplatform_driverstruct (especially when you don’t need.removeand other callbacks).

🔧 Expanded Effect

1
2
3
4
5
6
7
8
9
10
11
static int __init driver_init(void)
{
return platform_driver_probe(&driver, probe_fn);
}
module_init(driver_init);

static void __exit driver_exit(void)
{
platform_driver_unregister(&driver);
}
module_exit(driver_exit);

⚠️ Key Points

  • platform_driver_probe()is alightweight registration method
    • It only supports.probe, and does not support.remove.shutdownetc.;
    • drivercannot be unloaded and then reloaded(because internally it setsdriver->prevent_deferred_probe = true);
    • Applicable toSimple, one-time probedevices (such as certain SoC built-in controllers).

📝 Example

1
2
3
4
5
6
7
8
9
10
11
static int my_probe(struct platform_device *pdev)
{
// Initialize device
return 0;
}

static struct platform_driver my_driver = {
.driver = { .name = "my-simple-dev", },
};

module_platform_driver_probe(my_driver, my_probe);

💡 At this point,my_driverno need to write in.probe = my_probe**, the macro will automatically associate.

builtin_platform_driver_probe(driver, probe_fn)

📌 Usage

module_platform_driver_probeofbuilt-in version(compiled into the kernel, not removable).

🔧 Expansion effect

1
2
3
4
5
static int __init driver_init(void)
{
return platform_driver_probe(&driver, probe_fn);
}
device_initcall(driver_init);

Similarlyno exit path, suitable for simple built-in drivers.

Example

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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>

static int platform_driver_test_probe(struct platform_device *pdev)
{
struct resource *res_mem, *res_irq;
// Method1: Direct Access
if (pdev->num_resources >= 2) {
struct resource *res_mem = &pdev->resource[0];
struct resource *res_irq = &pdev->resource[1];
pr_info("using pdev->resource[i] to get resource\n");
pr_info("Memory Resource: start=0x%llx, end=0x%llx\n", res_mem->start,
res_mem->end);
pr_info("IRQ Resource: number=%lld\n", res_irq->start);
}
// Method 2: Using Platform_get_resource()
res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res_mem == NULL) {
dev_err(&pdev->dev, "Fail to get MEMORY resource\n");
return -ENODEV;
}
res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res_irq == NULL) {
dev_err(&pdev->dev, "Fail to get IRQ resource\n");
return -ENODEV;
}
pr_info("using platform_get_resource() to get resource\n");
pr_info("Memory Resource: start=0x%llx, end=0x%llx\n", res_mem->start, res_mem->end);
pr_info("IRQ Resource: number=%lld\n", res_irq->start);

return 0;
}
static int platform_driver_test_remove(struct platform_device *pdev)
{
pr_info("platform_driver_test_remove is called\n");
return 0;
}

static struct platform_driver my_platform_driver={
.driver = {
.name = "my_platform_device",
.owner = THIS_MODULE,
},
.probe = platform_driver_test_probe,
.remove = platform_driver_test_remove,
};

static int __init platform_driver_test_init(void)
{
int ret;

ret = platform_driver_register(&my_platform_driver);
if (ret < 0) {
pr_err("platform_driver_register failed\n");
return ret;
}

pr_info("platform_driver_register success\n");
return 0;
}

static void __exit platform_driver_test_exit(void)
{
platform_driver_unregister(&my_platform_driver);
}

module_init(platform_driver_test_init);
module_exit(platform_driver_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is a test sample for platform_driver");

Test:

1
2
3
4
5
6
7
8
9
10
11
~ # insmod platform_device_test.ko
[ 13.668535] platform_device_test: loading out-of-tree module taints kernel.
[ 13.679351] platform_device register success
~ # insmod platform_driver_test.ko
[ 20.597568] using pdev->resource[i] to get resource
[ 20.597797] Memory Resource: start=0xfdd60000, end=0xfdd60004
[ 20.597884] IRQ Resource: number=101
[ 20.597958] using platform_get_resource() to get resource
[ 20.598036] Memory Resource: start=0xfdd60000, end=0xfdd60004
[ 20.598309] IRQ Resource: number=101
[ 20.599166] platform_driver_register success

LED Platform Bus Example

platform_device

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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>

/* Multiplex Register */
#define PMU_GRF_BASE 0xFDC20000
#define GPIO0B_IOMUX_OFFSET 0xC
#define GPIO0B_IOMUX (PMU_GRF_BASE + GPIO0B_IOMUX_OFFSET)

/* Data Register and Direction Register */
#define GPIO0_BASE 0xFDD60000
#define GPIO_SWPORT_DDR_L_OFFSET 0x8
#define GPIO_SWPORT_DR_L_OFFSET 0x0
#define GPIO_EXT_PORT_OFFSET 0x70
// Direction Register
#define GPIO0_SWPORT_DDR_L (GPIO0_BASE + GPIO_SWPORT_DDR_L_OFFSET)
// Data Register
#define GPIO0_SWPORT_DR_L (GPIO0_BASE + GPIO_SWPORT_DR_L_OFFSET)
// External Input Register, Read-Only
#define GPIO0_EXT_PORT (GPIO0_BASE + GPIO_EXT_PORT_OFFSET)

#define REG_SIZE 4

struct resource my_resources[] = { {
.start = GPIO0B_IOMUX,
.end = GPIO0B_IOMUX + REG_SIZE - 1,
.name = "GPIO0B_IOMUX",
.flags = IORESOURCE_MEM,
},
{
.start = GPIO0_SWPORT_DDR_L,
.end = GPIO0_SWPORT_DDR_L + REG_SIZE - 1,
.name = "GPIO0_SWPORT_DDR_L",
.flags = IORESOURCE_MEM,
},
{
.start = GPIO0_SWPORT_DR_L,
.end = GPIO0_SWPORT_DR_L + REG_SIZE - 1,
.name = "GPIO0_SWPORT_DR_L",
.flags = IORESOURCE_MEM,
},
{
.start = GPIO0_EXT_PORT,
.end = GPIO0_EXT_PORT + REG_SIZE - 1,
.name = "GPIO0_EXT_PORT",
.flags = IORESOURCE_MEM,
} };

static struct platform_device my_platform_device={
.name = "light_up_led",
.num_resources = ARRAY_SIZE(my_resources),
.resource = my_resources,
};

static int __init platform_device_led_init(void)
{
int ret;
ret = platform_device_register(&my_platform_device);
if(ret < 0){
pr_info("platform_device_register fail\n");
return ret;
}
return 0;
}

static void __exit platform_device_led_exit(void)
{
platform_device_unregister(&my_platform_device);
}

module_init(platform_device_led_init);
module_exit(platform_device_led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is light_up_led example using platform");

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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/ioctl.h>
struct led_drv_data {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
void __iomem *gpio0b_iomux;
void __iomem *gpio0_swport_ddr_l;
void __iomem *gpio0_swport_dr_l;
void __iomem *gpio0_ext_port;
};

int led_open(struct inode *inode, struct file *file)
{
struct cdev *led_cdev = inode->i_cdev;
file->private_data = container_of(led_cdev, struct led_drv_data, cdev);
pr_info("led_open is called\n");
return 0;
}
ssize_t led_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
struct led_drv_data *drv_data = file->private_data;
u32 val;

if (size != sizeof(u32)) {
pr_info("read need 4 bytes\n");
return -ENOMEM;
}

val = readl(drv_data->gpio0_ext_port);
val &= 1 << 15; // gpio0_b7
val = val >> 15;

if (copy_to_user(buf, &val, sizeof(u32)) != 0) {
return -EFAULT;
}
pr_info("led_read is called\n");
return sizeof(u32);
}
ssize_t led_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
struct led_drv_data *drv_data = file->private_data;
u32 val;

if (size != sizeof(u32)) {
pr_err("write need 4 bytes\n");
return -ENOMEM;
}

if (copy_from_user(&val, buf, sizeof(u32)) != 0) {
return -EFAULT;
}
if (val > 0) {
// Configure as GPIO Output
val = readl(drv_data->gpio0_swport_ddr_l);
val |= 0x80008000;
writel(val, drv_data->gpio0_swport_ddr_l);
// Turn On
val = readl(drv_data->gpio0_swport_dr_l);
val |= 0x80008000;
writel(val, drv_data->gpio0_swport_dr_l);
} else if (val == 0) {
// Configure as GPIO Output
val = readl(drv_data->gpio0_swport_ddr_l);
val |= 0x80008000;
writel(val, drv_data->gpio0_swport_ddr_l);
// Turn Off
val = readl(drv_data->gpio0_swport_dr_l);
val |= 0x80000000;
val &= 0xffff7fff;
writel(val, drv_data->gpio0_swport_dr_l);
}
pr_info("led_write is called\n");
return sizeof(u32);
}

#define LED_OPEN _IO('A', 0)
#define LED_CLOSE _IO('A', 1)
#define LED_STATUS _IOR('A', 2, u32)

long led_unlocked_ioctl(struct file *file, unsigned int op, unsigned long arg)
{
struct led_drv_data *drv_data = file->private_data;
int val;
switch (op) {
case LED_OPEN:
// Configure as GPIO Output
val = readl(drv_data->gpio0_swport_ddr_l);
val |= 0x80008000;
writel(val, drv_data->gpio0_swport_ddr_l);
// Open
val = readl(drv_data->gpio0_swport_dr_l);
val |= 0x80008000;
writel(val, drv_data->gpio0_swport_dr_l);
break;
case LED_CLOSE:
// Configure as GPIO output
val = readl(drv_data->gpio0_swport_ddr_l);
val |= 0x80008000;
writel(val, drv_data->gpio0_swport_ddr_l);
// Close
val = readl(drv_data->gpio0_swport_dr_l);
val |= 0x80000000;
val &= 0xffff7fff;
writel(val, drv_data->gpio0_swport_dr_l);
break;
case LED_STATUS:
val = readl(drv_data->gpio0_ext_port);
val &= 1 << 15; // gpio0_b7
val = val >> 15;
if (copy_to_user((void __user *)arg, &val, sizeof(val)))
return -EFAULT;
break;
default:
return -EFAULT;
}
pr_info("led_ioctl is called\n");
return 0;
}

int led_release(struct inode *inode, struct file *file)
{
pr_info("led_release is called\n");
return 0;
}

struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.unlocked_ioctl = led_unlocked_ioctl,
.release = led_release,
};

static int my_platform_driver_probe(struct platform_device *pdev)
{
int ret;
u32 val;
struct led_drv_data *led_drv_dat;
struct resource *res_iomux, *res_ddr_l, *res_dr_l, *res_ext_port;

led_drv_dat = devm_kzalloc(&pdev->dev, sizeof(struct led_drv_data), GFP_KERNEL);

if (led_drv_dat == NULL) {
ret = -ENOMEM;
goto devm_kzalloc_fail;
}

ret = alloc_chrdev_region(&led_drv_dat->dev_num, 0, 1, "lightup_led_chrdev_region");
if (ret < 0) {
ret = -ENOMEM;
goto alloc_chrdev_region_fail;
}
platform_set_drvdata(pdev, led_drv_dat);

cdev_init(&led_drv_dat->cdev, &fops);
led_drv_dat->cdev.owner = THIS_MODULE;
ret = cdev_add(&led_drv_dat->cdev, led_drv_dat->dev_num, 1);
if (ret < 0)
goto cdev_add_fail;

led_drv_dat->class = class_create(THIS_MODULE, "test_led");
if (IS_ERR(led_drv_dat->class)) {
ret = PTR_ERR(led_drv_dat->class);
goto class_create_fail;
}

led_drv_dat->dev =
device_create(led_drv_dat->class, NULL, led_drv_dat->dev_num, NULL, "led0");
if (IS_ERR(led_drv_dat->dev)) {
ret = PTR_ERR(led_drv_dat->dev);
goto device_create_fail;
}

res_iomux = platform_get_resource(pdev, IORESOURCE_MEM, 0);
res_ddr_l = platform_get_resource(pdev, IORESOURCE_MEM, 1);
res_dr_l = platform_get_resource(pdev, IORESOURCE_MEM, 2);
res_ext_port = platform_get_resource(pdev, IORESOURCE_MEM, 3);

if (!res_iomux || !res_ddr_l || !res_dr_l || !res_ext_port) {
ret = -ENODEV;
goto address_fail;
}

led_drv_dat->gpio0b_iomux =
devm_ioremap(&pdev->dev, res_iomux->start, resource_size(res_iomux));
led_drv_dat->gpio0_swport_ddr_l =
devm_ioremap(&pdev->dev, res_ddr_l->start, resource_size(res_ddr_l));
led_drv_dat->gpio0_swport_dr_l =
devm_ioremap(&pdev->dev, res_dr_l->start, resource_size(res_dr_l));
led_drv_dat->gpio0_ext_port =
devm_ioremap(&pdev->dev, res_ext_port->start, resource_size(res_ext_port));

if (!led_drv_dat->gpio0b_iomux || !led_drv_dat->gpio0_swport_ddr_l ||
!led_drv_dat->gpio0_swport_dr_l || !led_drv_dat->gpio0_ext_port) {
ret = -ENOMEM;
goto address_fail;
}

// led_drv_dat->gpio0b_iomux =
// devm_ioremap_resource(&pdev->dev, platform_get_resource(pdev, IORESOURCE_MEM, 0));
// if (IS_ERR(led_drv_dat->gpio0b_iomux)) {
// ret = PTR_ERR(led_drv_dat->gpio0b_iomux);
// goto ioremap_fail;
// }

// led_drv_dat->gpio0_swport_ddr_l =
// devm_ioremap_resource(&pdev->dev, platform_get_resource(pdev, IORESOURCE_MEM, 1));
// if (IS_ERR(led_drv_dat->gpio0_swport_ddr_l)) {
// ret = PTR_ERR(led_drv_dat->gpio0_swport_ddr_l);
// goto ioremap_fail;
// }

// led_drv_dat->gpio0_swport_dr_l =
// devm_ioremap_resource(&pdev->dev, platform_get_resource(pdev, IORESOURCE_MEM, 2));
// if (IS_ERR(led_drv_dat->gpio0_swport_dr_l)) {
// ret = PTR_ERR(led_drv_dat->gpio0_swport_dr_l);
// goto ioremap_fail;
// }

// led_drv_dat->gpio0_ext_port =
// devm_ioremap_resource(&pdev->dev, platform_get_resource(pdev, IORESOURCE_MEM, 3));
// if (!led_drv_dat->gpio0_ext_port) {
// ret = -ENODEV;
// goto ioremap_fail;
// }

// Set pin multiplexing to GPIO
val = readl(led_drv_dat->gpio0b_iomux);
val |= 0x70000000; // write access
val &= 0xFFFF8FFF; // gpio0_b7
writel(val, led_drv_dat->gpio0b_iomux);
pr_info("gpio0b_7 is set as GPIO\n");

return 0;
address_fail:
device_destroy(led_drv_dat->class, led_drv_dat->dev_num);
device_create_fail:
class_destroy(led_drv_dat->class);
class_create_fail:
cdev_del(&led_drv_dat->cdev);
cdev_add_fail:
unregister_chrdev_region(led_drv_dat->dev_num, 1);
alloc_chrdev_region_fail:
devm_kzalloc_fail:
return ret;
}
static int my_platform_driver_remove(struct platform_device *pdev)
{
struct led_drv_data *led_drv_dat = platform_get_drvdata(pdev);
device_destroy(led_drv_dat->class, led_drv_dat->dev_num);
class_destroy(led_drv_dat->class);
cdev_del(&led_drv_dat->cdev);
unregister_chrdev_region(led_drv_dat->dev_num, 1);
return 0;
}

static struct platform_driver my_platform_driver = {
.driver ={
.name = "light_up_led",
.owner = THIS_MODULE,
},
.probe = my_platform_driver_probe,
.remove = my_platform_driver_remove,
};

module_platform_driver(my_platform_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is light up led example for platform bus");

A platform_driver may be used by multiple platform_devices. If written in the module_exit function, resources will not be released when the device is hot-plugged (or removed via sysfs). Therefore, resource cleanup logic should be placed in the remove function.