Linux kernel platform device food chain example
Since the device tree is the new way to set up hardware devices on embedded platforms, I hoped that I could avoid the “platform” API for picking which driver is going to take control over what. But it looks like the /arch/arm disaster is here to stay for a while, so I need to at least understand how it works.
So for reference, here’s an example walkthrough of the SPI driver for i.MX51, declared and matched with a hardware device.
The idea is simple: The driver, which is enabled by .config (and hence the Makefile in its directory includes it for compilation) binds itself to a string during its initialization. On the other side, initialization code requests a device matching that string, and also supplies some information along with that. The example tells the story better.
The platform API is documented in the kernel tree’s Documentation/driver-model/platform.txt. There’s also a nice LWN article by Jonathan Corbet.
So let’s assume we have Freescale’s 3-stack board at hand. in arch/arm/mach-mx5/mx51_3stack.c, at the bottom, it says
MACHINE_START(MX51_3DS, "Freescale MX51 3-Stack Board") .fixup = fixup_mxc_board, .map_io = mx5_map_io, .init_irq = mx5_init_irq, .init_machine = mxc_board_init, .timer = &mxc_timer, MACHINE_EN
mxc_board_init() is defined in the same file, which among many other calls goes
mxc_register_device(&mxcspi1_device, &mxcspi1_data);
with the extra info structure mxcspi1_data defined as
static struct mxc_spi_master mxcspi1_data = { .maxchipselect = 4, .spi_version = 23, .chipselect_active = mx51_3ds_gpio_spi_chipselect_active, .chipselect_inactive = mx51_3ds_gpio_spi_chipselect_inactive, };
Now to the declaration of mxcspi1_device: In arch/arm/mach-mx5/devices.c we have
struct platform_device mxcspi1_device = {
.name = "mxc_spi",
.id = 0,
.num_resources = ARRAY_SIZE(mxcspi1_resources),
.resource = mxcspi1_resources,
.dev = {
.dma_mask = &spi_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
},
};
and before that, in the same file there was:
static struct resource mxcspi1_resources[] = { { .start = CSPI1_BASE_ADDR, .end = CSPI1_BASE_ADDR + SZ_4K - 1, .flags = IORESOURCE_MEM, }, { .start = MXC_INT_CSPI1, .end = MXC_INT_CSPI1, .flags = IORESOURCE_IRQ, }, { .start = MXC_DMA_CSPI1_TX, .end = MXC_DMA_CSPI1_TX, .flags = IORESOURCE_DMA, }, };
So that defines the magic driver string and the resources that are allocated to this device.
It’s worth noting that devices.c ends with
postcore_initcall(mxc_init_devices);
which causes a call to mxc_init_devices(), a function that messes up the addresses of the resources for some architectures. Just to add some confusion. Always watch out for those little traps!
Meanwhile, in drivers/spi/mxc_spi.c
static struct platform_driver mxc_spi_driver = {
.driver = {
.name = "mxc_spi",
.owner = THIS_MODULE,
},
.probe = mxc_spi_probe,
.remove = mxc_spi_remove,
.suspend = mxc_spi_suspend,
.resume = mxc_spi_resume,
};
followed by:
static int __init mxc_spi_init(void) { pr_debug("Registering the SPI Controller Driver\n"); return platform_driver_register(&mxc_spi_driver); } static void __exit mxc_spi_exit(void) { pr_debug("Unregistering the SPI Controller Driver\n"); platform_driver_unregister(&mxc_spi_driver); } subsys_initcall(mxc_spi_init); module_exit(mxc_spi_exit);
So this is how the driver tells Linux that it’s responsible for devices marked with the “mxc_spi” string.
As for some interaction with the device data (also in mxc_spi.c), there’s stuff like
mxc_platform_info = (struct mxc_spi_master *)pdev->dev.platform_data;
and
master_drv_data->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
going on with
if (!request_mem_region(master_drv_data->res->start, master_drv_data->res->end - master_drv_data->res->start + 1, pdev->name)) { /* Ayee! */ }
and
if (pdev->dev.dma_mask == NULL) { /* No DMA for you! */ }
and it goes on…