《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》
第 23 章 Linux 芯片级移植及底层驱动
参考:宋宝华 著,机械工业出版社,2015年版
23.1 Linux 的移植概述
23.1.1 Linux 移植的概念
Linux 移植(Porting)是指将 Linux 内核适配到特定硬件平台(SoC)的过程。移植工作主要包括:
Linux 移植的主要工作:
1. Bootloader 移植(U-Boot)
- 初始化 DDR、时钟、串口
- 加载内核镜像和设备树到内存
- 跳转到内核入口
2. 内核移植
- 选择正确的 CPU 架构(arch/arm/)
- 配置内核(make menuconfig)
- 编写/修改板级支持代码
3. 设备树编写
- 描述 SoC 和开发板的硬件配置
- 替代旧的 board 文件(arch/arm/mach-xxx/)
4. 底层驱动移植
- 时钟驱动(Clock Driver)
- GPIO 驱动(GPIO Driver)
- 中断控制器驱动(IRQ Chip Driver)
- Pinctrl 驱动(Pin Control Driver)
5. 外设驱动移植
- UART、SPI、I2C、USB 等总线驱动
- LCD、触摸屏、音频等设备驱动
23.1.2 ARM Linux 的目录结构
Linux 内核 ARM 相关目录:
arch/arm/
├── boot/ ← 启动相关
│ ├── dts/ ← 设备树源文件(.dts/.dtsi)
│ │ ├── imx6ul.dtsi ← i.MX6UL SoC 设备树
│ │ ├── imx6ull.dtsi ← i.MX6ULL SoC 设备树
│ │ └── imx6ull-14x14-evk.dts ← 开发板设备树
│ └── compressed/ ← 内核解压代码
├── configs/ ← 默认配置文件
│ ├── imx_v6_v7_defconfig ← i.MX6/7 默认配置
│ └── vexpress_defconfig ← Vexpress 默认配置
├── include/ ← ARM 头文件
├── kernel/ ← ARM 内核核心代码
│ ├── entry-armv.S ← 异常向量表
│ ├── head.S ← 内核启动代码
│ └── irq.c ← 中断处理
├── lib/ ← ARM 库函数
├── mach-imx/ ← i.MX 系列 SoC 支持
│ ├── clk-imx6ul.c ← i.MX6UL 时钟驱动
│ └── mach-imx6ul.c ← i.MX6UL 机器描述
└── mm/ ← ARM 内存管理
23.1.3 Linux 移植的演进
Linux ARM 移植的历史演进:
Linux 2.6 时代(旧方式):
每款开发板有独立的 board 文件
arch/arm/mach-xxx/board-xxx.c
硬件信息硬编码在 C 代码中
问题:代码重复,维护困难,内核体积大
Linux 3.x 时代(设备树方式):
Linus Torvalds 批评 ARM 代码"一团糟"
引入设备树(Device Tree)描述硬件
一个内核镜像支持多款开发板
硬件信息从 C 代码移到 .dts 文件
Linux 4.0 时代(当前):
设备树全面普及
大量使用 platform 驱动框架
底层驱动(时钟/GPIO/中断/Pinctrl)标准化
23.2 设备树(Device Tree)
23.2.1 设备树的起源与作用
**设备树(Device Tree)**起源于 PowerPC 架构,后被 ARM Linux 采用,用于描述硬件平台的拓扑结构:
设备树的核心思想:
"机制与策略分离":
内核(机制):提供驱动框架,不关心具体硬件配置
设备树(策略):描述具体硬件,不包含驱动代码
设备树的作用:
1. 描述 SoC 内部外设(UART、SPI、I2C、GPIO 等)
2. 描述开发板上的外部设备(LCD、触摸屏、传感器等)
3. 描述设备间的连接关系(哪个 I2C 总线上有哪些设备)
4. 描述硬件资源(寄存器地址、中断号、时钟等)
5. 替代旧的 board 文件,实现"一个内核,多款开发板"
设备树文件类型:
.dts(Device Tree Source):开发板级设备树源文件
.dtsi(Device Tree Source Include):SoC 级设备树,被 .dts 包含
.dtb(Device Tree Blob):编译后的二进制设备树,由 Bootloader 加载
23.2.2 设备树的组成与结构
设备树的基本语法
dts
/*
* 设备树基本语法示例
* 文件:arch/arm/boot/dts/my-board.dts
*/
/dts-v1/; /* 设备树版本声明 */
#include "imx6ull.dtsi" /* 包含 SoC 级设备树 */
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
/* 根节点(必须)*/
/ {
model = "My Custom Board based on i.MX6ULL";
compatible = "myvendor,my-board", "fsl,imx6ull";
/* 内存节点(必须)*/
memory {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 起始地址 0x80000000,大小 512MB */
};
/* 选择节点(指定启动参数等)*/
chosen {
stdout-path = &uart1; /* 控制台串口 */
bootargs = "console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait";
};
/* 时钟节点 */
clocks {
/* 外部 24MHz 晶振 */
osc24m: oscillator {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
clock-output-names = "osc24m";
};
};
/* GPIO 按键 */
gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_keys>;
user-key {
label = "User Key";
gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
linux,code = <KEY_F1>;
debounce-interval = <20>;
};
};
/* LED */
leds {
compatible = "gpio-leds";
heartbeat-led {
label = "heartbeat";
gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
linux,default-trigger = "heartbeat";
};
};
};
/* 覆盖 SoC 设备树中的节点 */
&uart1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
status = "okay";
};
&i2c1 {
clock-frequency = <400000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
/* I2C 设备 */
lm75: temperature-sensor@48 {
compatible = "national,lm75";
reg = <0x48>;
};
};
&usdhc2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_usdhc2>;
bus-width = <8>;
non-removable;
status = "okay";
};
设备树节点的属性
dts
/*
* 设备树属性详解
*/
example-node {
/* ── 基本属性 ──────────────────────────────────────────── */
/* compatible:兼容性字符串,用于驱动匹配(最重要的属性)*/
compatible = "vendor,device-v2", "vendor,device";
/* 格式:"厂商,设备名",可以有多个(从具体到通用)*/
/* reg:寄存器地址和大小 */
reg = <0x02028000 0x4000>;
/* 格式:<地址 大小>,可以有多组 */
/* interrupts:中断配置 */
interrupts = <GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>;
/* 格式:<中断类型 中断号 触发方式> */
/* clocks:时钟引用 */
clocks = <&clks IMX6UL_CLK_UART1>,
<&clks IMX6UL_CLK_UART1_SERIAL>;
clock-names = "ipg", "per";
/* dmas:DMA 通道 */
dmas = <&sdma 25 4 0>, <&sdma 26 4 0>;
dma-names = "rx", "tx";
/* ── 地址相关属性 ──────────────────────────────────────── */
/* #address-cells:子节点 reg 属性中地址占用的 cell 数 */
#address-cells = <1>;
/* #size-cells:子节点 reg 属性中大小占用的 cell 数 */
#size-cells = <1>;
/* ── 状态属性 ──────────────────────────────────────────── */
/* status:设备状态 */
status = "okay"; /* 使能 */
/* status = "disabled"; */ /* 禁用 */
/* ── GPIO 相关属性 ──────────────────────────────────────── */
/* gpios:GPIO 引用 */
reset-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
/* 格式:<&gpio控制器 引脚号 极性> */
/* ── Pinctrl 相关属性 ──────────────────────────────────── */
/* pinctrl-names:引脚控制状态名称 */
pinctrl-names = "default", "sleep";
/* pinctrl-N:对应状态的引脚配置 */
pinctrl-0 = <&pinctrl_uart1>;
pinctrl-1 = <&pinctrl_uart1_sleep>;
};
设备树的层次结构
设备树层次结构示例(i.MX6ULL):
/(根节点)
├── cpus/
│ └── cpu@0(ARM Cortex-A7)
├── memory(DDR 内存)
├── chosen(启动参数)
├── clocks/(外部时钟)
├── soc/(SoC 内部外设)
│ ├── aips1@02000000(AIPS1 总线)
│ │ ├── uart1@02020000(UART1)
│ │ ├── i2c1@021a0000(I2C1)
│ │ ├── spi1@02008000(SPI1)
│ │ └── gpio1@0209c000(GPIO1)
│ ├── aips2@02100000(AIPS2 总线)
│ │ ├── usdhc1@02190000(eMMC)
│ │ └── usdhc2@02194000(SD 卡)
│ └── aips3@02200000(AIPS3 总线)
│ └── usbotg1@02184000(USB OTG)
├── gpio-keys(GPIO 按键)
├── leds(LED)
└── sound(音频)
├── codec(WM8960)
└── cpu(I2S 控制器)
23.2.3 设备树的绑定(Binding)
设备树绑定(Binding)是描述设备树节点属性含义的文档规范 ,存放在 Documentation/devicetree/bindings/ 目录:
设备树绑定文档示例(UART 绑定):
文件:Documentation/devicetree/bindings/serial/fsl-imx-uart.txt
Required properties:
- compatible: Should be "fsl,imx6ul-uart" or "fsl,imx21-uart"
- reg: Address and length of the register set for the device
- interrupts: Should contain uart interrupt
- clocks: phandle to the input clocks
- clock-names: Should be "ipg" and "per"
Optional properties:
- fsl,uart-has-rtscts: Indicate the uart has rts and cts
- fsl,dte-mode: Indicate the uart works in DTE mode
Example:
uart1: serial@02020000 {
compatible = "fsl,imx6ul-uart", "fsl,imx21-uart";
reg = <0x02020000 0x4000>;
interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_UART1>,
<&clks IMX6UL_CLK_UART1_SERIAL>;
clock-names = "ipg", "per";
status = "disabled";
};
编写自定义设备树绑定:
自定义设备绑定文档:
文件:Documentation/devicetree/bindings/myvendor/my-device.txt
My Device Driver
Required properties:
- compatible: Must be "myvendor,my-device"
- reg: Physical base address and size of the device registers
- interrupts: Interrupt specifier for the device interrupt
- clocks: Clock phandle
- clock-names: Must be "clk"
Optional properties:
- reset-gpios: GPIO for hardware reset (active low)
- myvendor,timeout-ms: Timeout in milliseconds (default: 1000)
Example:
my_device: my-device@44E09000 {
compatible = "myvendor,my-device";
reg = <0x44E09000 0x1000>;
interrupts = <0 72 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks MY_CLK>;
clock-names = "clk";
reset-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
myvendor,timeout-ms = <500>;
status = "okay";
};
23.2.4 常用的 OF API
OF(Open Firmware)API 是驱动程序读取设备树信息的接口:
c
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
/* ── 节点查找 ──────────────────────────────────────────────── */
/* 通过 compatible 查找节点 */
struct device_node *np = of_find_compatible_node(NULL, NULL,
"myvendor,my-device");
/* 通过路径查找节点 */
struct device_node *np = of_find_node_by_path("/soc/uart@02020000");
/* 通过名称查找节点 */
struct device_node *np = of_find_node_by_name(NULL, "uart1");
/* 获取父节点 */
struct device_node *parent = of_get_parent(np);
/* 获取子节点 */
struct device_node *child;
for_each_child_of_node(np, child) {
pr_info("子节点:%s\n", child->name);
}
/* ── 属性读取 ──────────────────────────────────────────────── */
/* 读取字符串属性 */
const char *str;
of_property_read_string(np, "compatible", &str);
/* 读取 u32 属性 */
u32 val;
of_property_read_u32(np, "myvendor,timeout-ms", &val);
/* 读取 u32 数组 */
u32 array[4];
of_property_read_u32_array(np, "reg", array, 4);
/* 读取 u64 属性 */
u64 val64;
of_property_read_u64(np, "size", &val64);
/* 检查属性是否存在 */
bool has_prop = of_property_read_bool(np, "fsl,uart-has-rtscts");
/* 读取字符串列表 */
const char *name;
int i = 0;
of_property_for_each_string(np, "clock-names", prop, name) {
pr_info("clock-names[%d] = %s\n", i++, name);
}
/* ── 资源获取 ──────────────────────────────────────────────── */
/* 获取寄存器地址并映射 */
void __iomem *base = of_iomap(np, 0); /* 映射第0个 reg 资源 */
/* 获取中断号 */
int irq = of_irq_get(np, 0); /* 获取第0个中断 */
/* 获取 GPIO */
int gpio = of_get_named_gpio(np, "reset-gpios", 0);
/* 获取时钟 */
struct clk *clk = of_clk_get_by_name(np, "clk");
/* ── 设备匹配 ──────────────────────────────────────────────── */
/* 检查设备是否与 compatible 匹配 */
bool match = of_device_is_compatible(np, "myvendor,my-device");
/* 获取匹配的 of_device_id 条目 */
const struct of_device_id my_of_match[] = {
{ .compatible = "myvendor,my-device-v1", .data = &v1_data },
{ .compatible = "myvendor,my-device-v2", .data = &v2_data },
{}
};
const struct of_device_id *match = of_match_device(my_of_match, dev);
if (match) {
const struct my_data *data = match->data;
}
/* ── 完整的 probe 函数中使用 OF API ──────────────────────── */
static int my_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
u32 timeout;
int gpio, irq;
void __iomem *base;
/* 读取自定义属性 */
if (of_property_read_u32(np, "myvendor,timeout-ms", &timeout))
timeout = 1000; /* 默认值 */
/* 获取 GPIO */
gpio = of_get_named_gpio(np, "reset-gpios", 0);
if (gpio_is_valid(gpio)) {
devm_gpio_request_one(&pdev->dev, gpio,
GPIOF_OUT_INIT_LOW, "reset");
}
/* 获取中断 */
irq = of_irq_get(np, 0);
/* 映射寄存器 */
base = of_iomap(np, 0);
dev_info(&pdev->dev, "timeout=%u, gpio=%d, irq=%d\n",
timeout, gpio, irq);
return 0;
}
23.3 平台设备与驱动
23.3.1 platform_device
platform_device 是 Linux 设备模型中用于描述不能自动发现的片上外设的数据结构:
c
#include <linux/platform_device.h>
/*
* platform_device:平台设备
* 描述一个不能自动发现的硬件设备
*/
struct platform_device {
const char *name; /* 设备名称(用于与驱动匹配)*/
int id; /* 设备 ID(-1 表示唯一)*/
struct device dev; /* 内嵌设备结构体 */
u32 num_resources; /* 资源数量 */
struct resource *resource; /* 资源数组 */
const struct platform_device_id *id_entry; /* 匹配的 ID 条目 */
};
/*
* resource:设备资源
* 描述设备使用的内存、I/O 端口、中断等资源
*/
struct resource {
resource_size_t start; /* 资源起始地址/中断号 */
resource_size_t end; /* 资源结束地址 */
const char *name; /* 资源名称 */
unsigned long flags; /* 资源类型标志 */
};
/* 资源类型标志 */
#define IORESOURCE_MEM 0x00000200 /* 内存资源 */
#define IORESOURCE_IO 0x00000100 /* I/O 端口资源 */
#define IORESOURCE_IRQ 0x00000400 /* 中断资源 */
#define IORESOURCE_DMA 0x00000800 /* DMA 资源 */
静态注册 platform_device(旧方式,已不推荐):
c
/* 定义资源 */
static struct resource my_device_resources[] = {
{
.start = 0x44E09000,
.end = 0x44E09FFF,
.flags = IORESOURCE_MEM,
.name = "registers",
},
{
.start = 72,
.end = 72,
.flags = IORESOURCE_IRQ,
.name = "irq",
},
};
/* 定义平台数据 */
static struct my_platform_data my_pdata = {
.baudrate = 115200,
.timeout = 1000,
};
/* 定义平台设备 */
static struct platform_device my_platform_device = {
.name = "my-device",
.id = -1,
.num_resources = ARRAY_SIZE(my_device_resources),
.resource = my_device_resources,
.dev = {
.platform_data = &my_pdata,
},
};
/* 注册平台设备 */
static int __init my_board_init(void)
{
return platform_device_register(&my_platform_device);
}
arch_initcall(my_board_init);
通过设备树自动创建 platform_device(推荐):
设备树方式:
内核启动时,of_platform_populate() 遍历设备树
为每个有 compatible 属性的节点创建 platform_device
驱动通过 of_match_table 与设备树节点匹配
不需要手动注册 platform_device!
23.3.2 platform_driver
c
/*
* platform_driver:平台驱动
* 与 platform_device 匹配后,执行 probe 函数
*/
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;
};
/* 完整的 platform_driver 实现 */
static int my_probe(struct platform_device *pdev)
{
struct my_priv *priv;
struct resource *res;
int ret;
/* 分配私有数据 */
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* 获取内存资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
/* 获取中断资源 */
priv->irq = platform_get_irq(pdev, 0);
if (priv->irq < 0)
return priv->irq;
/* 获取时钟 */
priv->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(priv->clk))
return PTR_ERR(priv->clk);
clk_prepare_enable(priv->clk);
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev, "设备初始化成功\n");
return 0;
}
static int my_remove(struct platform_device *pdev)
{
struct my_priv *priv = platform_get_drvdata(pdev);
clk_disable_unprepare(priv->clk);
return 0;
}
/* 设备树匹配表 */
static const struct of_device_id my_of_match[] = {
{ .compatible = "myvendor,my-device-v1" },
{ .compatible = "myvendor,my-device-v2" },
{}
};
MODULE_DEVICE_TABLE(of, my_of_match);
/* 平台设备 ID 表(非设备树匹配)*/
static const struct platform_device_id my_id_table[] = {
{ "my-device", 0 },
{}
};
MODULE_DEVICE_TABLE(platform, my_id_table);
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my-device",
.owner = THIS_MODULE,
.of_match_table = my_of_match,
.pm = &my_pm_ops,
},
.id_table = my_id_table,
};
/* 简化的模块注册宏 */
module_platform_driver(my_driver);
23.3.3 platform 设备资源和数据
c
/* ── 获取资源 ──────────────────────────────────────────────── */
/* 获取内存资源(第 N 个)*/
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "获取内存资源失败\n");
return -ENODEV;
}
pr_info("内存资源:0x%08x ~ 0x%08x\n", res->start, res->end);
/* 获取中断资源(第 N 个)*/
int irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "获取中断资源失败\n");
return irq;
}
/* 获取命名资源 */
struct resource *res = platform_get_resource_byname(pdev,
IORESOURCE_MEM,
"registers");
/* ── 获取平台数据 ──────────────────────────────────────────── */
/* 获取 platform_data(旧方式)*/
struct my_platform_data *pdata = dev_get_platdata(&pdev->dev);
if (pdata) {
pr_info("波特率:%u\n", pdata->baudrate);
}
/* 从设备树获取数据(新方式,推荐)*/
struct device_node *np = pdev->dev.of_node;
u32 baudrate;
of_property_read_u32(np, "baudrate", &baudrate);
/* ── 设备树与 platform_data 的兼容处理 ──────────────────── */
static int my_probe(struct platform_device *pdev)
{
struct my_config config = {
.baudrate = 115200, /* 默认值 */
.timeout = 1000,
};
if (pdev->dev.of_node) {
/* 从设备树读取配置 */
of_property_read_u32(pdev->dev.of_node, "baudrate",
&config.baudrate);
of_property_read_u32(pdev->dev.of_node, "timeout-ms",
&config.timeout);
} else if (dev_get_platdata(&pdev->dev)) {
/* 从 platform_data 读取配置 */
struct my_platform_data *pdata = dev_get_platdata(&pdev->dev);
config.baudrate = pdata->baudrate;
config.timeout = pdata->timeout;
}
dev_info(&pdev->dev, "baudrate=%u, timeout=%u\n",
config.baudrate, config.timeout);
return 0;
}
23.4 底层时钟驱动
23.4.1 时钟框架(Common Clock Framework)
Linux 4.0 使用 **CCF(Common Clock Framework)**统一管理系统时钟:
时钟树示例(i.MX6ULL):
外部晶振(24MHz)
↓
PLL(锁相环)
├── ARM PLL(696MHz)→ CPU 时钟
├── System PLL(528MHz)→ AHB/IPG 时钟
│ ├── AHB 时钟(132MHz)
│ └── IPG 时钟(66MHz)→ 各外设时钟
├── USB PLL(480MHz)→ USB 时钟
└── Audio PLL(786MHz)→ 音频时钟
时钟框架的作用:
- 统一管理所有时钟(PLL、分频器、门控)
- 提供统一的 API(clk_get/enable/disable/set_rate)
- 支持时钟树的自动管理(父子关系)
- 支持时钟门控(节能)
23.4.2 时钟驱动实现
c
#include <linux/clk-provider.h>
/*
* 注册固定频率时钟(最简单)
*/
struct clk *clk = clk_register_fixed_rate(dev,
"osc24m", /* 时钟名称 */
NULL, /* 父时钟名称 */
0, /* 标志 */
24000000); /* 频率 24MHz */
/*
* 注册门控时钟(可以开关的时钟)
*/
struct clk *clk = clk_register_gate(dev,
"uart1_clk",
"ipg", /* 父时钟 */
0,
base + CCM_CCGR5, /* 门控寄存器 */
24, /* 位偏移 */
0, /* 标志 */
&lock);
/*
* 注册分频器时钟
*/
struct clk *clk = clk_register_divider(dev,
"ahb_clk",
"system_pll",
0,
base + CCM_CBCDR,
10, /* 位偏移 */
3, /* 位宽(3位 = 最大8分频)*/
0,
&lock);
/*
* 注册 MUX 时钟(多路选择)
*/
static const char *uart_clk_sels[] = { "pll3_80m", "osc" };
struct clk *clk = clk_register_mux(dev,
"uart_clk_sel",
uart_clk_sels,
ARRAY_SIZE(uart_clk_sels),
0,
base + CCM_CSCDR1,
6, /* 位偏移 */
1, /* 位宽 */
0,
&lock);
/*
* 使用 CLK_OF_DECLARE 注册时钟提供者
*/
static void __init imx6ul_clocks_init(struct device_node *np)
{
struct clk **clks;
void __iomem *base;
base = of_iomap(np, 0);
/* 注册所有时钟 */
clks[IMX6UL_CLK_OSC] = clk_register_fixed_rate(NULL, "osc",
NULL, 0, 24000000);
clks[IMX6UL_CLK_UART1] = clk_register_gate(NULL, "uart1",
"ipg", 0,
base + CCM_CCGR5,
24, 0, &imx_ccm_lock);
/* ... 注册更多时钟 ... */
/* 注册时钟提供者 */
of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data);
}
CLK_OF_DECLARE(imx6ul, "fsl,imx6ul-ccm", imx6ul_clocks_init);
23.5 底层 GPIO 驱动
23.5.1 GPIO 子系统架构
GPIO 子系统架构:
用户空间:/sys/class/gpio/
↓
GPIO 核心层(gpiolib)
↓
GPIO 控制器驱动(gpio_chip)
↓
GPIO 硬件(SoC GPIO 控制器)
23.5.2 GPIO 控制器驱动实现
c
#include <linux/gpio/driver.h>
/*
* gpio_chip:GPIO 控制器描述符
* 每个 GPIO 控制器对应一个 gpio_chip
*/
struct gpio_chip {
const char *label; /* 控制器标签 */
struct device *parent; /* 父设备 */
struct module *owner;
/* GPIO 操作函数 */
int (*request)(struct gpio_chip *chip, unsigned offset);
void (*free)(struct gpio_chip *chip, unsigned offset);
int (*get_direction)(struct gpio_chip *chip, unsigned offset);
int (*direction_input)(struct gpio_chip *chip, unsigned offset);
int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
int (*get)(struct gpio_chip *chip, unsigned offset);
void (*set)(struct gpio_chip *chip, unsigned offset, int value);
int (*to_irq)(struct gpio_chip *chip, unsigned offset);
int base; /* GPIO 基础编号 */
u16 ngpio; /* GPIO 数量 */
bool can_sleep; /* 操作是否可能睡眠 */
};
/* GPIO 控制器寄存器 */
#define GPIO_DR 0x00 /* 数据寄存器 */
#define GPIO_GDIR 0x04 /* 方向寄存器(1=输出,0=输入)*/
#define GPIO_PSR 0x08 /* 引脚状态寄存器 */
#define GPIO_ICR1 0x0C /* 中断配置寄存器1 */
#define GPIO_ICR2 0x10 /* 中断配置寄存器2 */
#define GPIO_IMR 0x14 /* 中断屏蔽寄存器 */
#define GPIO_ISR 0x18 /* 中断状态寄存器 */
#define GPIO_EDGE 0x1C /* 边沿选择寄存器 */
struct imx_gpio {
void __iomem *base;
struct gpio_chip chip;
spinlock_t lock;
};
/* 设置 GPIO 方向为输入 */
static int imx_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
{
struct imx_gpio *gpio = gpiochip_get_data(chip);
unsigned long flags;
spin_lock_irqsave(&gpio->lock, flags);
/* 清除方向寄存器对应位(0=输入)*/
writel(readl(gpio->base + GPIO_GDIR) & ~BIT(offset),
gpio->base + GPIO_GDIR);
spin_unlock_irqrestore(&gpio->lock, flags);
return 0;
}
/* 设置 GPIO 方向为输出 */
static int imx_gpio_direction_output(struct gpio_chip *chip,
unsigned offset, int value)
{
struct imx_gpio *gpio = gpiochip_get_data(chip);
unsigned long flags;
spin_lock_irqsave(&gpio->lock, flags);
/* 先设置输出值 */
if (value)
writel(readl(gpio->base + GPIO_DR) | BIT(offset),
gpio->base + GPIO_DR);
else
writel(readl(gpio->base + GPIO_DR) & ~BIT(offset),
gpio->base + GPIO_DR);
/* 设置方向寄存器对应位(1=输出)*/
writel(readl(gpio->base + GPIO_GDIR) | BIT(offset),
gpio->base + GPIO_GDIR);
spin_unlock_irqrestore(&gpio->lock, flags);
return 0;
}
/* 读取 GPIO 值 */
static int imx_gpio_get(struct gpio_chip *chip, unsigned offset)
{
struct imx_gpio *gpio = gpiochip_get_data(chip);
return !!(readl(gpio->base + GPIO_PSR) & BIT(offset));
}
/* 设置 GPIO 值 */
static void imx_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
{
struct imx_gpio *gpio = gpiochip_get_data(chip);
unsigned long flags;
spin_lock_irqsave(&gpio->lock, flags);
if (value)
writel(readl(gpio->base + GPIO_DR) | BIT(offset),
gpio->base + GPIO_DR);
else
writel(readl(gpio->base + GPIO_DR) & ~BIT(offset),
gpio->base + GPIO_DR);
spin_unlock_irqrestore(&gpio->lock, flags);
}
/* probe 函数 */
static int imx_gpio_probe(struct platform_device *pdev)
{
struct imx_gpio *gpio;
struct resource *res;
int ret;
gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
if (!gpio)
return -ENOMEM;
spin_lock_init(&gpio->lock);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
gpio->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(gpio->base))
return PTR_ERR(gpio->base);
/* 初始化 gpio_chip */
gpio->chip.label = dev_name(&pdev->dev);
gpio->chip.parent = &pdev->dev;
gpio->chip.owner = THIS_MODULE;
gpio->chip.direction_input = imx_gpio_direction_input;
gpio->chip.direction_output = imx_gpio_direction_output;
gpio->chip.get = imx_gpio_get;
gpio->chip.set = imx_gpio_set;
gpio->chip.base = -1; /* 动态分配基础编号 */
gpio->chip.ngpio = 32; /* 32个 GPIO */
gpio->chip.of_node = pdev->dev.of_node;
/* 注册 GPIO 控制器 */
ret = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);
if (ret) {
dev_err(&pdev->dev, "注册 GPIO 控制器失败\n");
return ret;
}
dev_info(&pdev->dev, "GPIO 控制器注册成功,基础编号 %d\n",
gpio->chip.base);
return 0;
}
23.6 底层中断控制器驱动
23.6.1 中断控制器框架
Linux 中断控制器框架:
硬件中断
↓
中断控制器(GIC/NVIC/PIC 等)
↓
irq_chip(中断控制器驱动)
↓
irq_domain(中断号映射)
↓
Linux 虚拟中断号(virq)
↓
request_irq() / 中断处理函数
关键概念:
hwirq:硬件中断号(中断控制器内部编号)
virq:Linux 虚拟中断号(内核使用)
irq_domain:hwirq 到 virq 的映射表
irq_chip:中断控制器的操作函数集
23.6.2 中断控制器驱动实现
c
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/irqchip.h>
/*
* irq_chip:中断控制器操作函数集
*/
struct irq_chip my_irq_chip = {
.name = "my-irq-chip",
.irq_mask = my_irq_mask, /* 屏蔽中断 */
.irq_unmask = my_irq_unmask, /* 取消屏蔽 */
.irq_ack = my_irq_ack, /* 确认中断 */
.irq_set_type = my_irq_set_type, /* 设置触发类型 */
.irq_enable = my_irq_enable, /* 使能中断 */
.irq_disable = my_irq_disable, /* 禁用中断 */
};
/* 屏蔽中断 */
static void my_irq_mask(struct irq_data *d)
{
struct my_irq_chip *chip = irq_data_get_irq_chip_data(d);
writel(BIT(d->hwirq), chip->base + IRQ_MASK_REG);
}
/* 取消屏蔽 */
static void my_irq_unmask(struct irq_data *d)
{
struct my_irq_chip *chip = irq_data_get_irq_chip_data(d);
writel(BIT(d->hwirq), chip->base + IRQ_UNMASK_REG);
}
/* irq_domain 操作函数 */
static int my_irq_domain_map(struct irq_domain *d,
unsigned int virq,
irq_hw_number_t hwirq)
{
struct my_irq_chip *chip = d->host_data;
/* 设置中断处理函数和 irq_chip */
irq_set_chip_and_handler(virq, &my_irq_chip, handle_level_irq);
irq_set_chip_data(virq, chip);
irq_set_noprobe(virq);
return 0;
}
static const struct irq_domain_ops my_irq_domain_ops = {
.map = my_irq_domain_map,
.xlate = irq_domain_xlate_onecell,
};
/* 中断控制器 probe 函数 */
static int my_irq_chip_probe(struct platform_device *pdev)
{
struct my_irq_chip *chip;
struct irq_domain *domain;
struct resource *res;
int irq, ret;
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
chip->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(chip->base))
return PTR_ERR(chip->base);
/* 获取父中断(级联中断)*/
irq = platform_get_irq(pdev, 0);
/* 创建 irq_domain(线性映射,32个中断)*/
domain = irq_domain_add_linear(pdev->dev.of_node,
32,
&my_irq_domain_ops,
chip);
if (!domain) {
dev_err(&pdev->dev, "创建 irq_domain 失败\n");
return -ENOMEM;
}
chip->domain = domain;
/* 注册级联中断处理函数 */
irq_set_chained_handler_and_data(irq, my_irq_cascade_handler, chip);
dev_info(&pdev->dev, "中断控制器注册成功\n");
return 0;
}
/* 级联中断处理函数 */
static void my_irq_cascade_handler(struct irq_desc *desc)
{
struct my_irq_chip *chip = irq_desc_get_handler_data(desc);
struct irq_chip *irqchip = irq_desc_get_chip(desc);
u32 pending;
int hwirq;
/* 屏蔽父中断 */
chained_irq_enter(irqchip, desc);
/* 读取待处理的中断 */
pending = readl(chip->base + IRQ_PENDING_REG);
while (pending) {
hwirq = __ffs(pending);
pending &= ~BIT(hwirq);
/* 将硬件中断号转换为虚拟中断号并处理 */
generic_handle_irq(irq_find_mapping(chip->domain, hwirq));
}
/* 取消屏蔽父中断 */
chained_irq_exit(irqchip, desc);
}
23.7 底层 Pinctrl 驱动
23.7.1 Pinctrl 子系统概述
Pinctrl(Pin Control)子系统管理 SoC 引脚的复用功能 和电气特性:
Pinctrl 子系统的作用:
引脚复用(Pin Mux):
每个 SoC 引脚通常有多种功能(GPIO/UART/SPI/I2C 等)
Pinctrl 负责配置引脚使用哪种功能
引脚配置(Pin Config):
上拉/下拉电阻
驱动强度(Drive Strength)
开漏/推挽输出
施密特触发器
速率(Slew Rate)
Pinctrl 与设备树的关系:
设备树中的 pinctrl-0 属性指定引脚配置
Pinctrl 驱动解析配置并写入寄存器
23.7.2 Pinctrl 驱动实现
c
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinmux.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinconf-generic.h>
/*
* i.MX6ULL IOMUXC 寄存器格式:
* 每个引脚有两个寄存器:
* 1. MUX 寄存器:选择引脚功能(ALT0~ALT7)
* 2. PAD 寄存器:配置电气特性(上拉/驱动强度等)
*/
/* PAD 寄存器位定义 */
#define PAD_CTL_SRE_SLOW (0 << 0) /* 慢速率 */
#define PAD_CTL_SRE_FAST (1 << 0) /* 快速率 */
#define PAD_CTL_DSE_DISABLE (0 << 3) /* 禁用驱动 */
#define PAD_CTL_DSE_240ohm (1 << 3) /* 240Ω 驱动 */
#define PAD_CTL_DSE_120ohm (2 << 3) /* 120Ω 驱动 */
#define PAD_CTL_DSE_80ohm (3 << 3) /* 80Ω 驱动 */
#define PAD_CTL_DSE_60ohm (4 << 3) /* 60Ω 驱动 */
#define PAD_CTL_DSE_48ohm (5 << 3) /* 48Ω 驱动 */
#define PAD_CTL_DSE_40ohm (6 << 3) /* 40Ω 驱动 */
#define PAD_CTL_DSE_34ohm (7 << 3) /* 34Ω 驱动 */
#define PAD_CTL_PUS_100K_DOWN (0 << 14) /* 100kΩ 下拉 */
#define PAD_CTL_PUS_47K_UP (1 << 14) /* 47kΩ 上拉 */
#define PAD_CTL_PUS_100K_UP (2 << 14) /* 100kΩ 上拉 */
#define PAD_CTL_PUS_22K_UP (3 << 14) /* 22kΩ 上拉 */
#define PAD_CTL_PUE (1 << 13) /* 上拉/下拉使能 */
#define PAD_CTL_PKE (1 << 12) /* 保持器使能 */
#define PAD_CTL_HYS (1 << 16) /* 施密特触发器 */
/* 设备树中的 Pinctrl 配置 */
/*
&iomuxc {
pinctrl_uart1: uart1grp {
fsl,pins = <
MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1
>;
};
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b1
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b1
>;
};
};
*/
/* Pinctrl 驱动的核心操作 */
struct pinctrl_ops my_pctrl_ops = {
.get_groups_count = my_get_groups_count,
.get_group_name = my_get_group_name,
.get_group_pins = my_get_group_pins,
.dt_node_to_map = my_dt_node_to_map,
.dt_free_map = pinctrl_utils_free_map,
};
struct pinmux_ops my_pmx_ops = {
.get_functions_count = my_get_functions_count,
.get_function_name = my_get_function_name,
.get_function_groups = my_get_function_groups,
.set_mux = my_set_mux,
};
struct pinconf_ops my_pconf_ops = {
.is_generic = true,
.pin_config_get = my_pin_config_get,
.pin_config_set = my_pin_config_set,
};
/* 解析设备树中的引脚配置 */
static int my_dt_node_to_map(struct pinctrl_dev *pctldev,
struct device_node *np,
struct pinctrl_map **map,
unsigned *num_maps)
{
struct my_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);
const struct fsl_pin_reg *pin_reg;
struct pinctrl_map *new_map;
int map_num = 1;
int i, j;
/* 读取 fsl,pins 属性 */
int list_size;
const __be32 *list = of_get_property(np, "fsl,pins", &list_size);
if (!list) {
dev_err(ipctl->dev, "未找到 fsl,pins 属性\n");
return -EINVAL;
}
int pin_count = list_size / (sizeof(u32) * 6); /* 每个引脚6个u32 */
/* 分配 map 数组 */
new_map = kcalloc(map_num + pin_count, sizeof(*new_map), GFP_KERNEL);
if (!new_map)
return -ENOMEM;
/* 解析每个引脚配置 */
for (i = 0; i < pin_count; i++) {
u32 mux_reg = be32_to_cpu(*list++); /* MUX 寄存器偏移 */
u32 conf_reg = be32_to_cpu(*list++); /* PAD 寄存器偏移 */
u32 input_reg = be32_to_cpu(*list++); /* 输入选择寄存器偏移 */
u32 mux_val = be32_to_cpu(*list++); /* MUX 值(ALT功能选择)*/
u32 input_val = be32_to_cpu(*list++); /* 输入选择值 */
u32 conf_val = be32_to_cpu(*list++); /* PAD 配置值 */
/* 写入 MUX 寄存器 */
writel(mux_val, ipctl->base + mux_reg);
/* 写入输入选择寄存器(如果有)*/
if (input_reg)
writel(input_val, ipctl->base + input_reg);
/* 写入 PAD 配置寄存器 */
writel(conf_val, ipctl->base + conf_reg);
}
*map = new_map;
*num_maps = map_num + pin_count;
return 0;
}
/* Pinctrl 驱动 probe 函数 */
static int my_pinctrl_probe(struct platform_device *pdev)
{
struct my_pinctrl *ipctl;
struct resource *res;
struct pinctrl_desc *pctl_desc;
int ret;
ipctl = devm_kzalloc(&pdev->dev, sizeof(*ipctl), GFP_KERNEL);
if (!ipctl)
return -ENOMEM;
ipctl->dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ipctl->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(ipctl->base))
return PTR_ERR(ipctl->base);
/* 初始化 pinctrl_desc */
pctl_desc = devm_kzalloc(&pdev->dev, sizeof(*pctl_desc), GFP_KERNEL);
pctl_desc->name = dev_name(&pdev->dev);
pctl_desc->pins = my_pins;
pctl_desc->npins = ARRAY_SIZE(my_pins);
pctl_desc->pctlops = &my_pctrl_ops;
pctl_desc->pmxops = &my_pmx_ops;
pctl_desc->confops = &my_pconf_ops;
pctl_desc->owner = THIS_MODULE;
/* 注册 Pinctrl 控制器 */
ipctl->pctl = devm_pinctrl_register(&pdev->dev, pctl_desc, ipctl);
if (IS_ERR(ipctl->pctl)) {
dev_err(&pdev->dev, "注册 Pinctrl 控制器失败\n");
return PTR_ERR(ipctl->pctl);
}
platform_set_drvdata(pdev, ipctl);
dev_info(&pdev->dev, "Pinctrl 控制器注册成功\n");
return 0;
}
本章小结
| 章节 | 核心知识点 | 关键 API |
|---|---|---|
| 23.1 Linux移植概述 | 移植工作内容(Bootloader/内核/设备树/底层驱动/外设驱动);ARM Linux目录结构;移植演进历史(board文件→设备树) | 概念理解 |
| 23.2 设备树 | 设备树起源与作用;DTS/DTSI/DTB文件类型;完整DTS语法 (根节点/内存/chosen/时钟/GPIO按键/LED);节点属性详解(compatible/reg/interrupts/clocks/status);设备树绑定文档;完整OF API(节点查找/属性读取/资源获取/设备匹配) | of_property_read_u32()、of_get_named_gpio()、of_irq_get()、of_iomap() |
| 23.3 平台设备与驱动 | platform_device/resource结构体;静态注册(旧方式);设备树自动创建(推荐);完整platform_driver实现(probe/remove/of_match_table/id_table);资源获取API;platform_data与设备树兼容处理 | platform_get_resource()、platform_get_irq()、module_platform_driver() |
| 23.4 底层时钟驱动 | CCF时钟框架;时钟树结构;固定频率/门控/分频/MUX时钟注册;CLK_OF_DECLARE | clk_register_fixed_rate()、clk_register_gate()、clk_register_divider() |
| 23.5 底层GPIO驱动 | GPIO子系统架构;gpio_chip结构体;完整GPIO控制器驱动(direction_input/output/get/set);devm_gpiochip_add_data注册 | gpiochip_get_data()、devm_gpiochip_add_data() |
| 23.6 底层中断控制器驱动 | 中断控制器框架(hwirq/virq/irq_domain/irq_chip);irq_chip操作函数集;irq_domain创建;级联中断处理 | irq_domain_add_linear()、irq_set_chip_and_handler()、generic_handle_irq() |
| 23.7 底层Pinctrl驱动 | Pinctrl子系统(引脚复用+电气配置);PAD寄存器位定义;设备树fsl,pins属性解析;pinctrl_ops/pinmux_ops/pinconf_ops;devm_pinctrl_register注册 | devm_pinctrl_register()、pinctrl_utils_free_map() |
芯片级移植开发要点
1. 设备树编写
从 SoC 厂商提供的 .dtsi 开始
只在 .dts 中覆盖需要修改的节点
使用 &node_label 语法覆盖节点属性
status = "okay" 使能设备
2. OF API 使用
优先使用 devm_ 版本的资源获取函数
of_property_read_u32 读取整数属性
of_get_named_gpio 获取 GPIO
of_irq_get 获取中断号
3. 底层驱动顺序
时钟驱动 → GPIO 驱动 → 中断控制器 → Pinctrl → 其他驱动
使用 CLK_OF_DECLARE/IRQCHIP_DECLARE 早期注册
4. 调试方法
cat /sys/kernel/debug/clk/clk_summary ← 时钟树
cat /sys/kernel/debug/gpio ← GPIO 状态
cat /proc/interrupts ← 中断统计
cat /sys/kernel/debug/pinctrl/ ← Pinctrl 状态
参考文献:宋宝华《Linux设备驱动开发详解:基于最新的Linux 4.0内核》,机械工业出版社,2015年