[Linux] Utilizing Driver ID Information and DTS

1 minute read

U-BOOT Device Driver

As of this writing, the structure of modern U-BOOT is similar to that of the Linux kernel.
To cover various chips, the evolution of drivers
must also proceed in a direction that supports multiple chipsets.

To develop a minimal driver that applies a single interface
across diverse environments (SoCs),
techniques for distinguishing between different SoCs are crucial.

This is not unique to U-BOOT drivers.

From the driver’s perspective, the specific SoC currently in use is meaningless;
it simply needs to parse specific information and call the appropriate functions.

Parsing the Device Tree

The Linux kernel introduced the device tree
to efficiently manage the growing number of devices.
U-BOOT has followed the kernel’s lead.

Commands are executed based strictly on the parsed information from device tree properties.
Below are a few examples.
(The kernel also has APIs that serve similar roles.)

In U-BOOT, there is the DTS parsing API dev_read_TYPE.

...
    #address-cells = <1>;
    #size-cells = <1>;
    ...
    foo@71004000 {
        compatible = "steve,foo";
        regs = <0x71004000 0x4>;
        clock = <&clk_peri PERI_TCT>;
        clock-frequency = "1000000";
    };
uint32_t reg;
uint32_t freq;

reg = dev_read_addr_index(dev, 0);
freq = dev_read_u32(dev, "clock-frequency");

In this case, 0x71004000 is stored in reg, and 1000000 is stored in freq.

Parsing the udevice ID

A mapping table is required to distinguish devices.
In U-BOOT, the udevice_id type is widely used.
In the mapping table, devices are primarily distinguished by compatible and data.

struct udevice_id {
    const char *compatible;
    ulong data;
};
static const struct udevice_id bar_ids[] = {
    { .compatible = "steve,foo", .data = FOO_DATA },
    { }
};

U_BOOT_DRIVER(bar) = {
    .name       = "bar",
    .id     = UCLASS_BAR,
    .of_match   = bar_ids,
    .ops        = &bar_ops,
    .probe      = bar_probe,
    .of_to_plat = bar_ofdata_to_platdata,
};

The compatible property distinguishes the driver,
while the data property can distinguish behaviors within the same driver.

To utilize the data property, you can use dev_get_driver_data(dev)
within the function connected to of_to_plat.

struct foo_dev {
    int data;
};

static int bar_ofdata_to_platdata(udevice dev)
{
    struct foo_dev *foo = dev_get_priv(dev);
    ...
    foo->data = dev_get_driver_data(dev);
}

dev_get_priv is an API that retrieves the private information of the udevice.

  • Getting the driver_data during the driver probe phase is also perfectly fine.

Leave a comment