The Problem of Silent Hardware
Building a desktop computer is forgiving because components communicate their identities dynamically. You plug a graphics card into a PCIe slot, and the motherboard simply asks the card for its vendor ID. Smartphone architectures do not have this luxury. Manufacturers solder mobile components like touchscreens directly to the motherboard via simple buses like I2C or SPI. These embedded buses have no dynamic discovery mechanism built into them. The CPU has absolutely no way to ask what hardware is connected on the other end of the wire.
The Linux kernel solves this silence using the Platform Bus. This virtual bus manages hardwired, non-discoverable components found on a System-on-Chip. It treats these static components as if they were hot-pluggable devices. You write a Platform Driver in C to bind to specific hardware sitting on this virtual bus.
Your driver defines an of_match_table containing the exact identifier strings it supports. It also provides a probe() function to handle initialization.
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);
When a device appears on the bus, the kernel checks if the string matches your driver. A successful match causes the system to call probe(). Your code then allocates memory, configures the electrical connection, and registers the component with the Android input subsystem. This architecture creates a unified lifecycle for all hardware. The kernel can manage a hardwired I2C touchscreen using the exact same power management logic it uses for a USB mouse. Your system now knows how to handle the hardware, but it still needs a way to know the touchscreen exists in the first place.
Mapping the Motherboard
Since the hardware cannot announce its own presence, something else must tell the kernel what is physically soldered to the board. Hardcoding memory addresses and interrupt lines directly into the C driver creates a massive maintenance problem. A manufacturer might release a new phone with the exact same touchscreen wired to a completely different I2C address. You would have to compile a completely new driver just to update that one address.
The Device Tree fixes this hardcoding problem. This structured text file physically maps out every single component on the motherboard. The bootloader compiles this text into a binary blob and passes it to the kernel at boot. You define hardware nodes in a Device Tree Source file to represent the physical layout. Each node describes a specific component, its memory address, and its connection parameters.
&i2c_bus_1 {
status = "okay";
my_custom_touchscreen@38 {
compatible = "vendor,my_custom_touchscreen";
reg = <0x38>;
interrupt-parent = <&gpio>;
interrupts = <45 IRQ_TYPE_EDGE_FALLING>;
};
};
The compatible property is the critical link in this configuration. It holds the exact string your platform driver is actively looking for. A reg property defines the physical I2C address. The interrupts property tells the kernel which GPIO pin triggers when a user touches the screen.
This approach cleanly separates hardware configuration from driver logic. You can use the same compiled C driver across twenty different Android devices without modifying the source code. Board designers simply update the Device Tree text file for each specific hardware layout. You now have the physical hardware mapped to text. The system just needs a way to connect that text mapping to your C driver.
The Probe Matching Sequence
The kernel now has a compiled Device Tree in memory and a list of registered platform drivers. These two sides need a way to find each other automatically during the boot sequence. Static text configurations must become active hardware bindings. The kernel uses a matching mechanism to pair Device Tree nodes with their corresponding drivers.
The following flowchart illustrates the data flow from the bootloader up to the driver initialization. This visualizes how static configurations turn into active drivers in memory. Pay attention to how the string match acts as the exact bridge between the Device Tree and the C driver.
The bootloader hands the binary Device Tree to the kernel. Next, the OS reads every node and searches its driver registry for a matching compatible string. When a match occurs, the system executes the driver initialization.
During boot, the kernel walks through the parsed Device Tree and sees the node for my_custom_touchscreen@38. The initialization process checks the .of_match_table of every registered platform driver. It finds your driver claiming compatibility with "vendor,my_custom_touchscreen". The kernel binds the hardware node to your driver and executes the probe() function. It automatically passes the hardware resources, like the 0x38 I2C address, directly as arguments to the function.
Sometimes you need to verify if the kernel actually saw the hardware node you defined. You can inspect the live Device Tree on an active Android device to solve this debugging problem. The following command lists the parsed device tree nodes directly from the sysfs interface. This shows you exactly what the kernel loaded into memory.
adb shell ls -l /sys/firmware/devicetree/base/
Common Mistake: Engineers often mistake this directory for a static file structure. It actually represents the live memory structure of the kernel.
This successful probe execution is the exact moment an inert piece of silicon becomes an active participant in the Android system. The driver wakes up, configures the hardware, and begins sending input events. Your kernel has successfully discovered non-discoverable hardware. You now have a working touchscreen, but those raw hardware events still need to reach the Android application layer.