Tag: platform devices and drivers

  • Master How to Write Platform Devices and Drivers (2026)

    Learn how to write Platform Devices and Drivers from scratch using Device Tree, I2C, interrupts, and testing, explained clearly for beginners.

    If you are stepping into Linux device driver development, sooner or later you will hear one phrase again and again: Platform Devices and Drivers.
    They sound complex at first, but once you understand the flow, they are actually one of the cleanest and most structured ways to write Linux drivers for embedded systems.

    In this article, we will go from zero to a working platform driver.
    We will start with the basic idea, then move step by step through implementation, device tree integration, I2C communication, interrupt handling, and finally testing and debugging.

    What Is Platform Devices and Drivers?

    Platform Devices and Drivers are a Linux kernel mechanism used to describe and manage on-board hardware that is not discoverable dynamically.

    Unlike USB or PCI devices, platform devices do not announce themselves.
    They are usually fixed on the board, soldered directly to the SoC or connected via internal buses like I2C or SPI.

    Examples include:

    • GPIO controllers
    • I2C sensors
    • RTC chips
    • Watchdog timers
    • Custom hardware blocks

    Linux needs a way to:

    1. Describe this hardware
    2. Match it with the correct driver
    3. Initialize it safely during boot

    That is exactly what platform devices and drivers do.

    Why Platform Devices and Drivers Matter in Embedded Linux

    If you work with embedded boards, you will almost always use Platform Devices and Drivers because:

    • Hardware is board-specific
    • There is no auto-detection
    • Device Tree is used to describe hardware
    • Drivers must bind cleanly during boot

    This model separates hardware description (Device Tree) from driver logic (kernel module), which makes systems easier to maintain and port.

    High-Level Architecture

    Before writing any code, let’s understand the flow:

    1. You buy an I2C chip (for example, a temperature sensor)
    2. You connect it to your board
    3. You describe it in the Device Tree
    4. Linux creates a platform device
    5. Your platform driver registers itself
    6. Kernel matches device and driver
    7. probe() function runs
    8. Driver initializes hardware
    9. Device becomes usable from user space

    Once you understand this flow, everything else fits naturally.

    From Purchase to Product: Real-World Scenario

    Let’s make this real.

    Imagine you purchased an I2C temperature sensor that:

    • Works over I2C
    • Has an interrupt pin
    • Is connected to your SoC

    Your goal:

    • Write a Linux driver
    • Handle I2C communication
    • Handle interrupts
    • Expose data to user space

    We will build this using Platform Devices and Drivers.

    Step 1: Understand the Hardware

    Before touching the kernel:

    • Read the datasheet
    • Note the I2C address
    • Register map
    • Interrupt behavior
    • Voltage levels

    Example details:

    • I2C address: 0x48
    • Interrupt active low
    • Data register at 0x00

    Never skip this step. Drivers written without understanding hardware always fail.

    Step 2: Device Tree Basics

    In modern Linux, Device Tree describes platform devices.

    The Device Tree tells the kernel:

    • Where the device is
    • How it is connected
    • What driver should bind to it

    Example Device Tree Node

    i2c1 {
        status = "okay";
    
        temp_sensor@48 {
            compatible = "vendor,temp-sensor";
            reg = <0x48>;
            interrupt-parent = <&gpio1>;
            interrupts = <12 IRQ_TYPE_EDGE_FALLING>;
        };
    };
    

    Important properties:

    • compatible → used for driver matching
    • reg → I2C address
    • interrupts → interrupt configuration

    This node becomes a platform device internally.

    Step 3: Platform Driver Structure

    A platform driver is a kernel module that registers itself using platform_driver.

    Basic Skeleton

    static int temp_probe(struct platform_device *pdev)
    {
        return 0;
    }
    
    static int temp_remove(struct platform_device *pdev)
    {
        return 0;
    }
    
    static const struct of_device_id temp_of_match[] = {
        { .compatible = "vendor,temp-sensor" },
        { }
    };
    MODULE_DEVICE_TABLE(of, temp_of_match);
    
    static struct platform_driver temp_driver = {
        .probe = temp_probe,
        .remove = temp_remove,
        .driver = {
            .name = "temp_sensor",
            .of_match_table = temp_of_match,
        },
    };
    
    module_platform_driver(temp_driver);
    

    This is the heart of Platform Devices and Drivers.

    Step 4: Driver and Device Matching

    Matching happens using the compatible string.

    • Device Tree provides compatible = "vendor,temp-sensor"
    • Driver declares the same string
    • Kernel binds them automatically

    If probe() is not called:

    • Check compatible string
    • Check Device Tree compilation
    • Check kernel logs

    Step 5: Accessing Device Tree Data

    Inside probe(), you can read properties from Device Tree.

    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    

    This lets you:

    • Read GPIO numbers
    • Read custom properties
    • Configure behavior per board

    This is why platform drivers are flexible.

    Step 6: Integrating I2C in Platform Drivers

    Many beginners get confused here.

    Yes, the device is on I2C, but the binding still happens via platform devices when using Device Tree.

    You usually:

    • Get the I2C adapter
    • Create an I2C client
    • Communicate using I2C APIs

    Example

    struct i2c_client *client;
    
    client = i2c_new_dummy_device(adapter, 0x48);
    

    Now you can read registers:

    i2c_smbus_read_byte_data(client, 0x00);
    

    This approach keeps hardware description separate from communication logic.

    Step 7: Interrupt Handling

    Interrupts are critical for real devices.

    Get IRQ Number

    int irq = platform_get_irq(pdev, 0);
    

    Register Interrupt Handler

    request_irq(irq, temp_irq_handler,
                IRQF_TRIGGER_FALLING,
                "temp_irq", dev);
    

    Interrupt Handler

    static irqreturn_t temp_irq_handler(int irq, void *dev_id)
    {
        // Read status register
        return IRQ_HANDLED;
    }
    

    Always keep interrupt handlers short and fast.

    Step 8: Exposing Data to User Space

    A driver is useless if user space cannot talk to it.

    Common approaches:

    • sysfs
    • character device
    • IIO subsystem (for sensors)

    Simple sysfs Example

    static ssize_t temp_show(struct device *dev,
                             struct device_attribute *attr,
                             char *buf)
    {
        return sprintf(buf, "25\n");
    }
    
    DEVICE_ATTR_RO(temp);
    

    This creates:

    /sys/devices/.../temp
    

    Step 9: Error Handling and Cleanup

    Good platform drivers always clean up.

    In remove():

    • Free IRQ
    • Unregister I2C client
    • Free memory
    free_irq(irq, dev);
    

    Never assume probe() always succeeds.

    Step 10: Building and Testing

    Compile the Driver

    • Add to kernel tree or as module
    • Enable in menuconfig

    Load Module

    insmod temp_driver.ko
    

    Check Logs

    dmesg | grep temp
    

    Verify Device Tree

    ls /proc/device-tree
    

    Testing is where most learning happens.

    Common Mistakes Beginners Make

    • Wrong compatible string
    • Forgetting to enable I2C bus
    • Interrupt polarity mismatch
    • Blocking code in IRQ handler
    • Ignoring return values

    Every one of these will cost you hours if you ignore basics.

    How to Write Platform Devices and Drivers

    // SPDX-License-Identifier: GPL-2.0
    /*
     * Production-ready Platform Driver example
     * - Device Tree based
     * - I2C sensor
     * - Interrupt handling
     * - sysfs interface
     */
    
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/platform_device.h>
    #include <linux/of.h>
    #include <linux/of_irq.h>
    #include <linux/i2c.h>
    #include <linux/interrupt.h>
    #include <linux/slab.h>
    #include <linux/mutex.h>
    #include <linux/pm.h>
    #include <linux/sysfs.h>
    
    /* ---------- Driver Private Data ---------- */
    struct temp_dev {
    	struct device       *dev;
    	struct i2c_client   *client;
    	int                  irq;
    	int                  temperature;
    	struct mutex         lock;
    };
    
    /* ---------- I2C Read Helper ---------- */
    static int temp_sensor_read(struct temp_dev *tdev)
    {
    	int ret;
    
    	ret = i2c_smbus_read_byte_data(tdev->client, 0x00);
    	if (ret < 0)
    		dev_err(tdev->dev, "I2C read failed\n");
    
    	return ret;
    }
    
    /* ---------- Interrupt Handler ---------- */
    static irqreturn_t temp_irq_handler(int irq, void *data)
    {
    	struct temp_dev *tdev = data;
    	int temp;
    
    	mutex_lock(&tdev->lock);
    
    	temp = temp_sensor_read(tdev);
    	if (temp >= 0)
    		tdev->temperature = temp;
    
    	mutex_unlock(&tdev->lock);
    
    	dev_info(tdev->dev, "Interrupt: temperature=%d\n",
    		 tdev->temperature);
    
    	return IRQ_HANDLED;
    }
    
    /* ---------- sysfs Interface ---------- */
    static ssize_t temperature_show(struct device *dev,
    				struct device_attribute *attr,
    				char *buf)
    {
    	struct temp_dev *tdev = dev_get_drvdata(dev);
    	int temp;
    
    	mutex_lock(&tdev->lock);
    	temp = tdev->temperature;
    	mutex_unlock(&tdev->lock);
    
    	return sysfs_emit(buf, "%d\n", temp);
    }
    
    static DEVICE_ATTR_RO(temperature);
    
    static struct attribute *temp_attrs[] = {
    	&dev_attr_temperature.attr,
    	NULL,
    };
    
    static const struct attribute_group temp_attr_group = {
    	.attrs = temp_attrs,
    };
    
    /* ---------- Probe Function ---------- */
    static int temp_probe(struct platform_device *pdev)
    {
    	struct temp_dev *tdev;
    	struct i2c_adapter *adapter;
    	struct device *dev = &pdev->dev;
    	int ret;
    
    	dev_info(dev, "Probing temperature sensor\n");
    
    	tdev = devm_kzalloc(dev, sizeof(*tdev), GFP_KERNEL);
    	if (!tdev)
    		return -ENOMEM;
    
    	tdev->dev = dev;
    	mutex_init(&tdev->lock);
    	platform_set_drvdata(pdev, tdev);
    
    	/* Get I2C adapter (from DT bus number) */
    	adapter = i2c_get_adapter(1);
    	if (!adapter) {
    		dev_err(dev, "Failed to get I2C adapter\n");
    		return -ENODEV;
    	}
    
    	/* Create I2C client */
    	tdev->client = i2c_new_dummy_device(adapter, 0x48);
    	i2c_put_adapter(adapter);
    
    	if (IS_ERR(tdev->client)) {
    		dev_err(dev, "Failed to create I2C client\n");
    		return PTR_ERR(tdev->client);
    	}
    
    	/* Read initial value */
    	ret = temp_sensor_read(tdev);
    	if (ret >= 0)
    		tdev->temperature = ret;
    
    	/* Get IRQ from Device Tree */
    	tdev->irq = platform_get_irq(pdev, 0);
    	if (tdev->irq < 0) {
    		dev_err(dev, "Failed to get IRQ\n");
    		return tdev->irq;
    	}
    
    	ret = devm_request_irq(dev,
    			       tdev->irq,
    			       temp_irq_handler,
    			       IRQF_TRIGGER_FALLING,
    			       dev_name(dev),
    			       tdev);
    	if (ret) {
    		dev_err(dev, "Failed to request IRQ\n");
    		return ret;
    	}
    
    	/* Create sysfs group */
    	ret = sysfs_create_group(&dev->kobj, &temp_attr_group);
    	if (ret) {
    		dev_err(dev, "Failed to create sysfs group\n");
    		return ret;
    	}
    
    	dev_info(dev, "Temperature sensor driver loaded\n");
    	return 0;
    }
    
    /* ---------- Remove Function ---------- */
    static int temp_remove(struct platform_device *pdev)
    {
    	struct temp_dev *tdev = platform_get_drvdata(pdev);
    
    	sysfs_remove_group(&pdev->dev.kobj, &temp_attr_group);
    
    	if (tdev->client)
    		i2c_unregister_device(tdev->client);
    
    	dev_info(&pdev->dev, "Temperature sensor removed\n");
    	return 0;
    }
    
    /* ---------- Power Management ---------- */
    #ifdef CONFIG_PM_SLEEP
    static int temp_suspend(struct device *dev)
    {
    	dev_info(dev, "Suspending temperature sensor\n");
    	return 0;
    }
    
    static int temp_resume(struct device *dev)
    {
    	dev_info(dev, "Resuming temperature sensor\n");
    	return 0;
    }
    #endif
    
    static SIMPLE_DEV_PM_OPS(temp_pm_ops,
    			 temp_suspend,
    			 temp_resume);
    
    /* ---------- Device Tree Match ---------- */
    static const struct of_device_id temp_of_match[] = {
    	{ .compatible = "demo,temp-sensor" },
    	{ }
    };
    MODULE_DEVICE_TABLE(of, temp_of_match);
    
    /* ---------- Platform Driver ---------- */
    static struct platform_driver temp_driver = {
    	.probe  = temp_probe,
    	.remove = temp_remove,
    	.driver = {
    		.name           = "temp_sensor",
    		.of_match_table = temp_of_match,
    		.pm             = &temp_pm_ops,
    	},
    };
    
    module_platform_driver(temp_driver);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Nishant Singh");
    MODULE_DESCRIPTION("Production-ready Platform Driver with I2C and Interrupt");
    

    Device Tree

    &i2c1 {
    	status = "okay";
    
    	temp_sensor@48 {
    		compatible = "demo,temp-sensor";
    		reg = <0x48>;
    		interrupt-parent = <&gpio1>;
    		interrupts = <12 IRQ_TYPE_EDGE_FALLING>;
    	};
    };
    

    User Space Test

    cat /sys/devices/platform/temp_sensor/temperature
    

    Why Platform Devices and Drivers Scale Well

    Once you master Platform Devices and Drivers, you can:

    • Port drivers across boards
    • Reuse Device Tree files
    • Support multiple variants
    • Keep drivers clean and readable

    This is why the Linux kernel strongly encourages this model.

    FAQs : Platform Devices and Drivers.

    1. What are Platform Devices and Drivers in Linux?

    Platform Devices and Drivers are a Linux kernel mechanism used for non-discoverable hardware. These are devices that are permanently attached to the board, like I2C sensors, GPIO controllers, RTCs, and on-chip peripherals. Since such hardware cannot announce itself, Linux relies on the Device Tree to describe it and platform drivers to manage it.

    2. Why do we need Platform Drivers when using Device Tree?

    Device Tree only describes hardware.
    Platform drivers contain the actual code that talks to the hardware. The kernel uses the compatible string in the Device Tree to match a platform device with the correct platform driver and then calls the driver’s probe() function.

    3. What is the role of the probe() function?

    The probe() function is where everything starts. It is called when the kernel successfully matches a platform device with its driver. In probe(), you:

    • Allocate memory
    • Read Device Tree properties
    • Initialize hardware
    • Register interrupts
    • Create sysfs or character devices

    If probe() fails, the device will not work.

    4. How does Device Tree match a Platform Driver?

    Matching is done using the compatible string.
    The Device Tree node provides a compatible property, and the platform driver provides an of_device_id table with the same string. If they match exactly, the kernel binds them together.

    5. Can Platform Drivers be used for I2C or SPI devices?

    Yes. This is very common.
    Even though the device communicates over I2C or SPI, the binding still happens through Platform Devices and Drivers when using Device Tree. Inside the platform driver, you create or access an I2C or SPI client to communicate with the hardware.

    6. What is the difference between a Platform Driver and an I2C Driver?

    An I2C driver is tied directly to the I2C subsystem and is usually auto-created by the I2C core.
    A Platform Driver is more generic and is often used when:

    • You need board-specific logic
    • The device uses multiple resources (IRQ, GPIO, regulators)
    • You want tighter control over initialization

    Both approaches are valid and used in production.

    7. How are interrupts handled in Platform Drivers?

    Interrupts are described in the Device Tree using the interrupts property.
    Inside the driver, you retrieve the IRQ number using platform_get_irq() and register a handler using request_irq() or devm_request_irq(). The interrupt handler should be fast and should avoid sleeping or heavy processing.

    8. What is devm_* and why should it be used?

    devm_* APIs automatically free resources when the device is removed.
    They reduce memory leaks and simplify error handling. In production drivers, devm_kzalloc(), devm_request_irq(), and similar APIs are strongly recommended.

    9. How do Platform Drivers expose data to user space?

    Common methods include:

    • sysfs attributes
    • character devices
    • standard kernel subsystems like IIO, input, or hwmon

    For simple data like sensor readings, sysfs is often enough. For high-performance or streaming data, character devices or IIO are better choices.

    10. What are the most common reasons probe() is not called?

    The most common reasons are:

    • Mismatch in compatible string
    • Device Tree node not compiled or loaded
    • Driver not enabled in kernel configuration
    • Incorrect Device Tree hierarchy (wrong bus node)

    Checking dmesg usually points you in the right direction.

    11. Are Platform Drivers used in real production kernels?

    Absolutely.
    Most SoC drivers, board-level drivers, and embedded peripherals in the Linux kernel are implemented using Platform Devices and Drivers. This model scales well and is heavily used by vendors and upstream kernel developers.

    12. When should I avoid using Platform Devices and Drivers?

    You should avoid platform drivers if:

    • The hardware is self-discoverable (USB, PCI)
    • The kernel already provides a dedicated subsystem driver
    • You are writing a pure user-space driver

    In those cases, platform drivers would add unnecessary complexity.

    You can also read : Linked List Coding Questions

  • Platform Devices and Drivers on Linux

    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:

    1. Bootloader loads kernel + device tree blob
    2. Kernel parses the device tree
    3. Each compatible node becomes a platform device
    4. 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 ftrace if 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

    PropertyMeaning
    compatibleUsed for driver matching
    regBase address + size
    statusEnable 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 <linux/module.h>
    #include <linux/platform_device.h>
    #include <linux/of.h>
    #include <linux/io.h>
    

    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)

    StepExplanation
    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 <linux/module.h>
    #include <linux/platform_device.h>
    #include <linux/of.h>
    #include <linux/io.h>
    
    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 hardware
    • devm_ APIs simplify cleanup

    What You Should Do Next

    If you want real mastery, next steps:

    1. Add character device interface
    2. Add IRQ handling
    3. Add sysfs attributes
    4. Convert to GPIO / LED driver
    5. 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