Chapter 8: Platform Device Drivers
Table of Contents
- Introduction to Platform Drivers
- Platform Device Model
- Device Tree Basics
- Writing Platform Drivers
- Platform Resources
- Power Management
Introduction to Platform Drivers
What are Platform Devices?
Platform devices are devices that:
- Are not auto-discoverable (unlike PCI, USB)
- Are typically integrated on SoC (System on Chip)
- Require static registration or device tree description
- Examples: GPIO controllers, I2C/SPI controllers, UART, timers
Platform Driver Architecture
Device Tree / Platform Data
↓
┌──────────────────────────┐
│ Platform Bus │
├──────────────────────────┤
│ Platform Device │
│ - Resources (MMIO, IRQ) │
│ - Driver match │
└──────────┬───────────────┘
↓
┌──────────────────────────┐
│ Platform Driver │
│ - probe() │
│ - remove() │
│ - suspend/resume() │
└──────────────────────────┘
Platform Device Model
Basic Structures
#include <linux/platform_device.h>
/*
* struct platform_device - Platform device structure
*/
struct platform_device {
const char *name; /* Device name */
int id; /* Instance ID (-1 for single instance) */
struct device dev; /* Embedded device structure */
u32 num_resources; /* Number of resources */
struct resource *resource; /* Resources (MMIO, IRQ, etc.) */
/* ... more fields ... */
};
/*
* struct platform_driver - Platform driver structure
*/
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
/*
* struct resource - Hardware resource
*/
struct resource {
resource_size_t start; /* Start address */
resource_size_t end; /* End address */
const char *name; /* Resource name */
unsigned long flags; /* Resource type flags */
/* ... more fields ... */
};
/* Resource types */
#define IORESOURCE_IO 0x00000100 /* I/O port */
#define IORESOURCE_MEM 0x00000200 /* Memory region */
#define IORESOURCE_IRQ 0x00000400 /* IRQ line */
#define IORESOURCE_DMA 0x00000800 /* DMA channel */
Device Tree Basics
What is Device Tree?
Device tree describes hardware that exists in the system:
- Text format (
.dts) compiled to binary (.dtb) - Describes SoC components, peripherals, board-specific info
- Loaded by bootloader, passed to kernel
Device Tree Example
/* Example: my-device.dts */
/dts-v1/;
/ {
compatible = "vendor,board";
#address-cells = <1>;
#size-cells = <1>;
/* Device node */
my_device: mydev@10000000 {
compatible = "vendor,my-device-v1";
reg = <0x10000000 0x1000>; /* Register base and size */
interrupts = <0 42 4>; /* IRQ number */
clocks = <&clk_bus>;
clock-names = "bus_clk";
status = "okay";
/* Custom properties */
vendor,max-speed = <100000>;
vendor,num-channels = <4>;
};
};
Device Tree API
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
/*
* Device tree matching
*/
static const struct of_device_id my_device_of_match[] = {
{ .compatible = "vendor,my-device-v1", },
{ .compatible = "vendor,my-device-v2", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_device_of_match);
/*
* Reading properties
*/
/* Read u32 property */
int of_property_read_u32(const struct device_node *np,
const char *propname,
u32 *out_value);
/* Read string property */
int of_property_read_string(const struct device_node *np,
const char *propname,
const char **out_string);
/* Read array */
int of_property_read_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values,
size_t sz);
/* Check if property exists */
bool of_property_read_bool(const struct device_node *np,
const char *propname);
/* Get IRQ from device tree */
int of_irq_get(struct device_node *np, int index);
int platform_get_irq(struct platform_device *pdev, unsigned int num);
Writing Platform Drivers
Complete Platform Driver Example
/*
* my_platform_driver.c - Complete platform driver example
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
/* Device registers */
#define REG_CONTROL 0x00
#define REG_STATUS 0x04
#define REG_DATA 0x08
#define REG_IRQ_MASK 0x0C
/* Device private data */
struct my_device {
struct device *dev;
void __iomem *regs; /* Mapped registers */
int irq;
struct clk *clk;
u32 max_speed;
u32 num_channels;
spinlock_t lock;
};
/*
* Device tree matching table
*/
static const struct of_device_id my_device_of_match[] = {
{
.compatible = "vendor,my-device-v1",
.data = (void *)1, /* Version 1 */
},
{
.compatible = "vendor,my-device-v2",
.data = (void *)2, /* Version 2 */
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_device_of_match);
/*
* Interrupt handler
*/
static irqreturn_t my_device_irq(int irq, void *dev_id)
{
struct my_device *mydev = dev_id;
u32 status;
/* Read interrupt status */
status = readl(mydev->regs + REG_STATUS);
/* Handle interrupt */
dev_dbg(mydev->dev, "IRQ: status=0x%08x\n", status);
/* Clear interrupt */
writel(status, mydev->regs + REG_STATUS);
return IRQ_HANDLED;
}
/*
* Parse device tree properties
*/
static int my_device_parse_dt(struct my_device *mydev)
{
struct device *dev = mydev->dev;
struct device_node *np = dev->of_node;
int ret;
/* Read custom properties */
ret = of_property_read_u32(np, "vendor,max-speed", &mydev->max_speed);
if (ret) {
dev_err(dev, "Failed to read max-speed property\n");
return ret;
}
ret = of_property_read_u32(np, "vendor,num-channels", &mydev->num_channels);
if (ret) {
dev_warn(dev, "num-channels not specified, using default\n");
mydev->num_channels = 1; /* Default */
}
dev_info(dev, "DT: max_speed=%u, num_channels=%u\n",
mydev->max_speed, mydev->num_channels);
return 0;
}
/*
* Probe function - Called when device is matched
*/
static int my_device_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct my_device *mydev;
struct resource *res;
const struct of_device_id *match;
int ret;
dev_info(dev, "Probing device\n");
/* Allocate device structure */
mydev = devm_kzalloc(dev, sizeof(*mydev), GFP_KERNEL);
if (!mydev)
return -ENOMEM;
mydev->dev = dev;
spin_lock_init(&mydev->lock);
/* Get device tree match */
match = of_match_device(my_device_of_match, dev);
if (match) {
unsigned long version = (unsigned long)match->data;
dev_info(dev, "Matched device version %lu\n", version);
}
/* Parse device tree */
if (dev->of_node) {
ret = my_device_parse_dt(mydev);
if (ret)
return ret;
}
/* Get MMIO resource */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "Failed to get MMIO resource\n");
return -ENODEV;
}
dev_info(dev, "MMIO: start=0x%llx, size=0x%llx\n",
(unsigned long long)res->start,
(unsigned long long)resource_size(res));
/* Map registers */
mydev->regs = devm_ioremap_resource(dev, res);
if (IS_ERR(mydev->regs)) {
dev_err(dev, "Failed to map registers\n");
return PTR_ERR(mydev->regs);
}
/* Get IRQ */
mydev->irq = platform_get_irq(pdev, 0);
if (mydev->irq < 0) {
dev_err(dev, "Failed to get IRQ\n");
return mydev->irq;
}
dev_info(dev, "IRQ: %d\n", mydev->irq);
/* Request IRQ */
ret = devm_request_irq(dev, mydev->irq, my_device_irq,
0, dev_name(dev), mydev);
if (ret) {
dev_err(dev, "Failed to request IRQ\n");
return ret;
}
/* Get clock */
mydev->clk = devm_clk_get(dev, "bus_clk");
if (IS_ERR(mydev->clk)) {
dev_err(dev, "Failed to get clock\n");
return PTR_ERR(mydev->clk);
}
/* Enable clock */
ret = clk_prepare_enable(mydev->clk);
if (ret) {
dev_err(dev, "Failed to enable clock\n");
return ret;
}
dev_info(dev, "Clock rate: %lu Hz\n", clk_get_rate(mydev->clk));
/* Initialize hardware */
writel(0, mydev->regs + REG_CONTROL);
writel(0xFFFFFFFF, mydev->regs + REG_STATUS);
writel(0xFFFFFFFF, mydev->regs + REG_IRQ_MASK);
/* Save private data */
platform_set_drvdata(pdev, mydev);
dev_info(dev, "Probe successful\n");
return 0;
}
/*
* Remove function - Called when device is removed
*/
static int my_device_remove(struct platform_device *pdev)
{
struct my_device *mydev = platform_get_drvdata(pdev);
dev_info(mydev->dev, "Removing device\n");
/* Disable interrupts */
writel(0, mydev->regs + REG_IRQ_MASK);
/* Disable clock */
clk_disable_unprepare(mydev->clk);
/* devm_* resources are automatically freed */
dev_info(mydev->dev, "Device removed\n");
return 0;
}
/*
* Platform driver structure
*/
static struct platform_driver my_platform_driver = {
.probe = my_device_probe,
.remove = my_device_remove,
.driver = {
.name = "my-device",
.of_match_table = my_device_of_match,
},
};
/*
* Module registration
*/
module_platform_driver(my_platform_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tutorial Author");
MODULE_DESCRIPTION("Example platform device driver");
MODULE_ALIAS("platform:my-device");
Platform Resources
Accessing Resources
/*
* platform_get_resource - Get resource by type and index
*/
struct resource *platform_get_resource(struct platform_device *pdev,
unsigned int type,
unsigned int num);
/* Get MMIO resource */
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
/* Get IRQ resource */
struct resource *res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
/*
* platform_get_irq - Get IRQ number
*/
int irq = platform_get_irq(pdev, 0);
/*
* Managed resource mapping (automatic cleanup)
*/
/* Map MMIO region */
void __iomem *base = devm_ioremap_resource(dev, res);
/* Map by physical address */
void __iomem *base = devm_ioremap(dev, phys_addr, size);
/* Request memory region and map */
void __iomem *base = devm_ioremap_resource(dev, res);
/*
* Reading/Writing MMIO registers
*/
#include <linux/io.h>
u32 readl(const volatile void __iomem *addr);
void writel(u32 val, volatile void __iomem *addr);
u16 readw(const volatile void __iomem *addr);
void writew(u16 val, volatile void __iomem *addr);
u8 readb(const volatile void __iomem *addr);
void writeb(u8 val, volatile void __iomem *addr);
/* Relaxed versions (no memory barriers) */
u32 readl_relaxed(const volatile void __iomem *addr);
void writel_relaxed(u32 val, volatile void __iomem *addr);
Power Management
Suspend and Resume
/*
* Power management support
*/
#ifdef CONFIG_PM_SLEEP
static int my_device_suspend(struct device *dev)
{
struct my_device *mydev = dev_get_drvdata(dev);
dev_info(dev, "Suspending device\n");
/* Disable interrupts */
writel(0, mydev->regs + REG_IRQ_MASK);
/* Save device state if needed */
/* Disable clock */
clk_disable_unprepare(mydev->clk);
return 0;
}
static int my_device_resume(struct device *dev)
{
struct my_device *mydev = dev_get_drvdata(dev);
int ret;
dev_info(dev, "Resuming device\n");
/* Enable clock */
ret = clk_prepare_enable(mydev->clk);
if (ret)
return ret;
/* Restore device state if needed */
/* Re-enable interrupts */
writel(0xFFFFFFFF, mydev->regs + REG_IRQ_MASK);
return 0;
}
#endif
static const struct dev_pm_ops my_device_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(my_device_suspend, my_device_resume)
};
/* Add to platform_driver */
static struct platform_driver my_platform_driver = {
.driver = {
.pm = &my_device_pm_ops,
/* ... */
},
};
Summary
Platform drivers provide access to SoC-integrated devices using device tree or platform data.
Next Steps
Proceed to 09-block-drivers.md