Learn Platform Devices and Drivers in Linux from scratch with clear explanations, device tree examples, driver code, testing steps, and real-world insights.
If you are getting into Linux device drivers, especially embedded Linux, you will keep hearing one phrase again and again: Platform Devices and Drivers.
At first, it sounds complicated. Platform bus, platform_device, platform_driver, device tree, probe function, resources, matching. It feels like too many moving parts.
But once you understand the why behind platform devices and drivers, everything starts to click.
This guide is written for beginners, but with expert depth, so it also helps during interviews and real projects.
What Are Platform Devices and Drivers in Linux?
In simple words:
Platform Devices and Drivers are used in Linux to handle on-chip hardware that is not discoverable automatically.
That single sentence explains 80 percent of the concept.
Unlike PCI or USB devices, many embedded peripherals are:
- Built directly into the SoC
- Always present
- Not hot-pluggable
- Not discoverable by scanning a bus
Examples include:
- GPIO controllers
- I2C controllers
- SPI controllers
- UARTs
- Timers
- Watchdogs
- On-chip ADCs
Linux still needs a clean way to represent these hardware blocks. That is where Platform Devices and Drivers come in.
Why Platform Devices and Drivers Exist
To understand platform drivers, first understand what Linux expects from hardware.
Linux follows a simple model:
- A device describes hardware
- A driver knows how to control that hardware
- The kernel matches them and binds them
For PCI or USB, the kernel can discover devices automatically. For embedded hardware, there is no discovery mechanism.
So Linux needed:
- A way to describe fixed hardware
- A bus that does not rely on enumeration
- A matching mechanism that still feels clean
That solution is the platform bus.
What Is the Platform Bus?
The platform bus is a virtual bus inside the Linux kernel.
It is not a real hardware bus like I2C or SPI.
Think of it as a logical meeting place where:
- Platform devices are registered
- Platform drivers are registered
- The kernel matches them by name or device tree
This bus makes sure Linux can treat on-chip hardware the same way it treats external devices.
Platform Device vs Platform Driver
This confusion is very common, especially for beginners.
Let us clear it properly.
Platform Device
A platform device represents the hardware.
It describes things like:
- Base memory address
- IRQ number
- DMA channels
- Clock names
- Device name
The platform device does not contain logic. It only describes what exists.
Platform Driver
A platform driver contains the software logic.
It knows:
- How to initialize the hardware
- How to read and write registers
- How to handle interrupts
- How to expose functionality to user space
The driver does the actual work.
How Platform Devices Were Defined Earlier
Before device tree became common, platform devices were defined in board files.
Example idea:
static struct platform_device uart_device = {
.name = "my_uart",
.id = -1,
.resource = uart_resources,
};
This worked but had problems:
- Board specific code lived inside the kernel
- Kernel had to be recompiled for hardware changes
- Not scalable
This is why Device Tree replaced board files.
Role of Device Tree in Platform Devices and Drivers
Today, Device Tree is the most common way platform devices are created.
The device tree:
- Describes hardware in a hardware-agnostic way
- Is loaded by the bootloader
- Is parsed by the kernel
- Automatically creates platform devices
So now, you usually do not manually register platform devices in C code.
How Device Tree Creates Platform Devices
When the kernel boots:
- Bootloader loads kernel + device tree blob
- Kernel parses the device tree
- Each compatible node becomes a platform device
- Kernel looks for a matching platform driver
That is the entire flow.
Example Device Tree Node
uart0: serial@48020000 {
compatible = "vendor,my-uart";
reg = <0x48020000 0x1000>;
interrupts = <32>;
status = "okay";
};
This node creates a platform device automatically.
The kernel then looks for a platform driver with:
.compatible = "vendor,my-uart"
How Platform Device and Driver Matching Works
Linux supports multiple matching mechanisms, but device tree is the most common.
Matching Using of_match_table
static const struct of_device_id my_uart_of_match[] = {
{ .compatible = "vendor,my-uart" },
{}
};
When the kernel sees a platform device with the same compatible string, it binds the driver.
Basic Structure of a Platform Driver
A minimal platform driver looks like this:
static int my_driver_probe(struct platform_device *pdev)
{
return 0;
}
static int my_driver_remove(struct platform_device *pdev)
{
return 0;
}
static struct platform_driver my_driver = {
.probe = my_driver_probe,
.remove = my_driver_remove,
.driver = {
.name = "my_driver",
.of_match_table = my_driver_of_match,
},
};
This structure is the backbone of Platform Devices and Drivers in Linux.
What Happens in the Probe Function
The probe() function is the heart of a platform driver.
This is where you:
- Get memory resources
- Map registers using
ioremap - Request IRQs
- Initialize hardware
- Register character device or subsystem
If probe fails, the device is not usable.
Getting Resources from Platform Device
Example:
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
This resource came directly from the device tree reg property.
Mapping Registers
void __iomem *base;
base = devm_ioremap_resource(&pdev->dev, res);
This maps physical memory into kernel virtual address space.
Interrupt Handling in Platform Drivers
int irq = platform_get_irq(pdev, 0);
request_irq(irq, my_isr, 0, "my_driver", dev);
Again, IRQ info comes from device tree.
Power Management in Platform Drivers
Platform drivers integrate nicely with Linux power management.
You can implement:
- Suspend
- Resume
This is important for battery-powered embedded devices.
How Platform Drivers Expose Functionality
Platform drivers usually expose functionality using:
- Character devices
- sysfs
- procfs
- Input subsystem
- Network stack
Example for character device:
alloc_chrdev_region(...)
cdev_add(...)
How to Write Platform Devices and Drivers in Linux Step by Step
Here is a clean beginner flow.
Step 1: Identify Hardware
- Base address
- IRQ number
- Clock source
Step 2: Write Device Tree Node
Describe hardware correctly.
Step 3: Write Platform Driver Skeleton
- probe
- remove
- match table
Step 4: Handle Resources
- Memory
- IRQ
- Clocks
Step 5: Initialize Hardware
- Reset
- Configure registers
Step 6: Expose Interface
- Char device or sysfs
Common Mistakes Beginners Make
- Forgetting
of_match_table - Wrong compatible string
- Not handling error paths
- Hardcoding addresses
- Ignoring device tree
Avoid these and your driver becomes clean and reusable.
Platform Devices and Drivers vs Character Drivers
This is a popular interview question.
A platform driver is about how the driver binds to hardware.
A character driver is about how user space interacts.
Many platform drivers internally register a character driver.
They are not mutually exclusive.
Testing and Verifying Platform Drivers
Testing is where most tutorials stop. This one does not.
Check Device Creation
ls /sys/bus/platform/devices
Check Driver Binding
ls /sys/bus/platform/drivers
Check Kernel Logs
dmesg | grep my_driver
Verify Probe Execution
Add logs:
dev_info(&pdev->dev, "Probe successful\n");
Verify Resource Mapping
Check /proc/iomem.
Debugging Tips for Platform Drivers
- Use
dev_dbg() - Enable dynamic debug
- Validate device tree addresses
- Double-check IRQ numbers
- Use
ftraceif needed
Debugging is part of learning. Do not avoid it.
Real-World Use of Platform Devices and Drivers
Almost every embedded Linux system relies on platform drivers:
- Automotive ECUs
- Medical devices
- Industrial controllers
- Consumer electronics
- IoT gateways
If you work in embedded Linux, you will write or debug platform drivers.
Platform Driver Implementation From Scratch (Linux)
What we are building
We will create a basic platform driver that:
- Matches a device tree node
- Gets MMIO memory
- Maps registers
- Logs success
This is the foundation of all real platform drivers (GPIO, UART, I2C, SPI, etc.).
Step 1: Understand the Flow
Before code, understand this flow:
Device Tree
↓
Platform Device created by kernel
↓
Platform Driver registered
↓
Matching via compatible string
↓
probe() called
↓
Hardware initialized
If this flow is clear, platform drivers become easy.
Step 2: Device Tree Node (Hardware Description)
This is mandatory in modern Linux.
Example Device Tree Node
Add this to your .dts file:
my_device@10000000 {
compatible = "nish,my-platform-device";
reg = <0x10000000 0x1000>;
status = "okay";
};
What this means
| Property | Meaning |
|---|---|
| compatible | Used for driver matching |
| reg | Base address + size |
| status | Enable device |
This node automatically creates a platform device.
Step 3: Platform Driver Skeleton (Core Code)
Create a file:
my_platform_driver.c
Include Required Headers
#include
#include
#include
#include
Step 4: Device Tree Match Table
This is how Linux connects device ↔ driver
static const struct of_device_id my_platform_of_match[] = {
{ .compatible = "nish,my-platform-device" },
{ }
};
MODULE_DEVICE_TABLE(of, my_platform_of_match);
Compatible string must exactly match device tree.
Step 5: Private Driver Data Structure
struct my_platform_dev {
void __iomem *base_addr;
};
This stores mapped register base.
Step 6: Probe Function (Most Important Part)
static int my_platform_probe(struct platform_device *pdev)
{
struct resource *res;
struct my_platform_dev *dev;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Memory resource not found\n");
return -ENODEV;
}
dev->base_addr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(dev->base_addr))
return PTR_ERR(dev->base_addr);
platform_set_drvdata(pdev, dev);
dev_info(&pdev->dev, "Platform driver probed successfully\n");
return 0;
}
What happens here (simple words)
| Step | Explanation |
|---|---|
platform_get_resource() | Gets address from DT |
ioremap() | Maps physical → virtual |
platform_set_drvdata() | Saves private data |
probe() | Hardware is now usable |
Step 7: Remove Function
static int my_platform_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "Platform driver removed\n");
return 0;
}
Cleanup is automatic because we used devm_ APIs.
Step 8: Platform Driver Structure
static struct platform_driver my_platform_driver = {
.probe = my_platform_probe,
.remove = my_platform_remove,
.driver = {
.name = "my_platform_driver",
.of_match_table = my_platform_of_match,
},
};
Step 9: Module Init and Exit
module_platform_driver(my_platform_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nish");
MODULE_DESCRIPTION("Platform Driver from Scratch");
This macro handles init and exit automatically.
Step 10: Complete Driver Code (Final)
#include
#include
#include
#include
struct my_platform_dev {
void __iomem *base_addr;
};
static const struct of_device_id my_platform_of_match[] = {
{ .compatible = "nish,my-platform-device" },
{ }
};
MODULE_DEVICE_TABLE(of, my_platform_of_match);
static int my_platform_probe(struct platform_device *pdev)
{
struct resource *res;
struct my_platform_dev *dev;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
dev->base_addr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(dev->base_addr))
return PTR_ERR(dev->base_addr);
platform_set_drvdata(pdev, dev);
dev_info(&pdev->dev, "Platform driver probed\n");
return 0;
}
static int my_platform_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "Platform driver removed\n");
return 0;
}
static struct platform_driver my_platform_driver = {
.probe = my_platform_probe,
.remove = my_platform_remove,
.driver = {
.name = "my_platform_driver",
.of_match_table = my_platform_of_match,
},
};
module_platform_driver(my_platform_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nish");
MODULE_DESCRIPTION("Platform Driver From Scratch");
Step 11: Makefile
obj-m += my_platform_driver.o
Step 12: Build the Driver
make -C /lib/modules/$(uname -r)/build M=$PWD modules
Step 13: Insert and Test
sudo insmod my_platform_driver.ko
dmesg | tail
Expected output:
Platform driver probed
Step 14: Verify Binding
ls /sys/bus/platform/devices
ls /sys/bus/platform/drivers/my_platform_driver
If your device appears → success
Key Interview Points
- Platform drivers are for non-discoverable devices
- Device tree creates platform devices
- Matching happens via
compatible probe()initializes hardwaredevm_APIs simplify cleanup
What You Should Do Next
If you want real mastery, next steps:
- Add character device interface
- Add IRQ handling
- Add sysfs attributes
- Convert to GPIO / LED driver
- Learn power management callbacks
Why Platform Devices and Drivers Matter for Your Career
Understanding Platform Devices and Drivers shows that you:
- Understand Linux internals
- Can work close to hardware
- Can read device tree files
- Can debug kernel issues
This skill separates application developers from embedded engineers.
FAQ: Platform Devices and Drivers in Linux
1. What are platform devices and drivers in Linux?
Platform devices and drivers in Linux are used to manage hardware that is built directly into the system, such as GPIO controllers, UARTs, and timers. These devices cannot be auto-detected, so Linux uses the platform bus and device tree to describe and bind them to drivers.
2. Why are platform drivers needed in embedded Linux?
Platform drivers are needed in embedded Linux because most on-chip peripherals do not support hardware discovery. The platform driver framework allows Linux to cleanly separate hardware description from driver logic using device tree.
3. What is the difference between a platform device and a platform driver?
A platform device represents the hardware information like memory address and interrupts, while a platform driver contains the code that controls the hardware. Linux matches them and calls the driver’s probe function.
4. How does device tree work with platform devices and drivers?
The device tree describes hardware in a structured format. During boot, the Linux kernel parses the device tree and automatically creates platform devices, which are then matched with platform drivers using compatible strings.
5. What is the platform bus in Linux?
The platform bus is a virtual bus inside the Linux kernel that connects platform devices with platform drivers. It is mainly used for fixed hardware present on the SoC.
6. How does Linux match a platform driver to a platform device?
Linux matches a platform driver to a platform device using the compatible string defined in the device tree and the of_match_table in the driver code.
7. What happens inside the probe function of a platform driver?
The probe function is called when the driver matches a device. It initializes hardware, maps registers, requests interrupts, and prepares the device for use.
8. Is a platform driver the same as a character driver?
No. A platform driver defines how the driver binds to hardware, while a character driver defines how user space interacts with the driver. A platform driver can internally register a character driver.
9. Do platform drivers always require a device tree?
In modern Linux systems, platform drivers usually rely on device tree. Older systems used board files, but device tree is now the standard and recommended approach.
10. How can I test if my platform driver is working correctly?
You can test a platform driver by checking kernel logs using dmesg, verifying device creation in /sys/bus/platform, and confirming that the probe function is executed successfully.
11. What are common mistakes when writing platform devices and drivers?
Common mistakes include mismatched compatible strings, incorrect memory addresses in device tree, forgetting the of_match_table, and improper resource handling in the probe function.
12. Where are platform devices and drivers used in real projects?
Platform devices and drivers are widely used in automotive systems, industrial controllers, IoT devices, medical equipment, and consumer electronics running embedded Linux.
Read More : Char Driver Model in Linux
Mr. Raj Kumar is a highly experienced Technical Content Engineer with 7 years of dedicated expertise in the intricate field of embedded systems. At Embedded Prep, Raj is at the forefront of creating and curating high-quality technical content designed to educate and empower aspiring and seasoned professionals in the embedded domain.
Throughout his career, Raj has honed a unique skill set that bridges the gap between deep technical understanding and effective communication. His work encompasses a wide range of educational materials, including in-depth tutorials, practical guides, course modules, and insightful articles focused on embedded hardware and software solutions. He possesses a strong grasp of embedded architectures, microcontrollers, real-time operating systems (RTOS), firmware development, and various communication protocols relevant to the embedded industry.
Raj is adept at collaborating closely with subject matter experts, engineers, and instructional designers to ensure the accuracy, completeness, and pedagogical effectiveness of the content. His meticulous attention to detail and commitment to clarity are instrumental in transforming complex embedded concepts into easily digestible and engaging learning experiences. At Embedded Prep, he plays a crucial role in building a robust knowledge base that helps learners master the complexities of embedded technologies.









