《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》 第 23 章 Linux 芯片级移植及底层驱动

《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年