AOSP Framework & Internals
3 min read

Platform Drivers and Device Trees

Discover how the Linux kernel discovers and configures embedded hardware that cannot be dynamically probed.

On a modern desktop PC, if you plug a graphics card into a PCIe slot, the motherboard can dynamically interrogate the slot, ask the card for its vendor ID, and automatically load the correct driver.

Smartphones do not have PCIe slots for internal components. The touch screen controller, the audio codec, and the camera sensors are physically soldered directly to the motherboard via simple, non-discoverable buses like I2C or SPI. The CPU has absolutely no way to dynamically "ask" what is connected.

To solve this, Android relies heavily on the Platform Bus and Device Trees.

The Platform Bus

The Linux kernel created a virtual bus called the Platform Bus specifically for these hardwired, non-discoverable SoC (System-on-Chip) components.

A Platform Driver is a driver written specifically to bind to hardware sitting on this virtual bus.

// Example: A simplified platform driver registration
static struct platform_driver my_touch_driver = {
    .probe      = my_touch_probe,
    .remove     = my_touch_remove,
    .driver     = {
        .name   = "my_custom_touchscreen",
        .of_match_table = my_touch_dt_ids,
    },
};

module_platform_driver(my_touch_driver);

Probe and Remove

The heart of a platform driver is the probe() function. When the kernel matches a piece of hardware to your driver, it calls probe(). This is where you allocate memory, configure the I2C connection, and register the device with the Android input subsystem.

The Device Tree (DT)

If the hardware cannot dynamically announce its presence, how does the kernel know what is soldered to the board? The bootloader tells it via the Device Tree Blob (DTB).

The Device Tree is a structured text file (compiled into a binary blob) that physically maps out every component on the motherboard.

// Example Device Tree Source (.dts) snippet for a touchscreen
&i2c_bus_1 {
    status = "okay";

    my_custom_touchscreen@38 {
        compatible = "vendor,my_custom_touchscreen";
        reg = <0x38>; // I2C address
        interrupt-parent = <&gpio>;
        interrupts = <45 IRQ_TYPE_EDGE_FALLING>;
    };
};

How it Connects

  1. Parsing: During boot, the Linux kernel reads the Device Tree. It sees the node for my_custom_touchscreen@38.
  2. Matching: The kernel searches its list of registered platform drivers. It looks at the .of_match_table in our C code, sees that our driver is compatible with "vendor,my_custom_touchscreen".
  3. Binding: The kernel binds the hardware to the driver and executes the probe() function, passing in the hardware resources (like the 0x38 I2C address and the GPIO interrupt number 45) automatically.
# Developers can inspect the live Device Tree on an active Android device
adb shell ls -l /sys/firmware/devicetree/base/

This clean separation means that if a manufacturer releases a new phone with the exact same touchscreen but wired to a different I2C address, they do not need to rewrite the C driver. They simply update the Device Tree text file.