- Published on
linux驱动学习-资源管理01-MMIO与Regmap
- Authors

- Name
- i Joe
MMIO
MMIO 是把设备寄存器映射到 CPU 的物理地址空间中,使 CPU 可以通过地址访问外设寄存器。比如说下面的dts:
gpio4: gpio@2ae40000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0x2ae40000 0x0 0x200>;
clocks = <&cru PCLK_GPIO4>, <&cru DBCLK_GPIO4>;
gpio-controller;
gpio-ranges = <&pinctrl 0 128 32>;
interrupts = <GIC_SPI 169 IRQ_TYPE_LEVEL_HIGH>;
interrupt-controller;
#gpio-cells = <2>;
#interrupt-cells = <2>;
};
可以很直观的看到它的物理地址reg,但是实际cpu需要的是虚拟地址,所以会先对其进行映射,例如:bank->reg_base = devm_platform_ioremap_resource(pdev, 0);。会建立虚拟地址到物理地址的映射,并返回虚拟地址。
然后使用writel、readl进行读写(包括writeb、writew等省略),其中有个writel_relaxed和readl_relaxed,readl_relaxed/writel_relaxed 仍然是 MMIO 访问接口,只是相比 readl/writel 提供的访问顺序保证更弱。具体的内存屏障和顺序问题后续再深入。
MMIO 映射后通常使用 void **iomem * 保存地址,其中 **iomem 是内核用于标记 I/O 内存指针的注解,表示这个地址对应的是设备寄存器,而不是普通 RAM,访问时应使用 readl/writel 等接口。
DTS 的 reg 最终会成为 platform device 的 IORESOURCE_MEM 资源,driver 获取这段资源后再进行 MMIO 映射。
Regmap
对寄存器的读写每个设备都有自己要读写的寄存器,维护起来很困难,所以就出现了regmap,实际上就是在寄存器读写上套一层统一接口。例如:
static int snvs_rtc_probe(struct platform_device *pdev)
{
struct snvs_rtc_data *data;
int ret;
void __iomem *mmio;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->rtc = devm_rtc_allocate_device(&pdev->dev);
if (IS_ERR(data->rtc))
return PTR_ERR(data->rtc);
data->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "regmap");
if (IS_ERR(data->regmap)) {
dev_warn(&pdev->dev, "snvs rtc: you use old dts file, please update it\n");
mmio = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(mmio))
return PTR_ERR(mmio);
data->regmap = devm_regmap_init_mmio(&pdev->dev, mmio, &snvs_rtc_config);
} else {
data->offset = SNVS_LPREGISTER_OFFSET;
of_property_read_u32(pdev->dev.of_node, "offset", &data->offset);
}
...
}
static u64 rtc_read_lpsrt(struct snvs_rtc_data *data)
{
u32 msb, lsb;
regmap_read(data->regmap, data->offset + SNVS_LPSRTCMR, &msb);
regmap_read(data->regmap, data->offset + SNVS_LPSRTCLR, &lsb);
return (u64)msb << 32 | lsb;
}
这个就是rtc的probe和读。它首先找一找有没已经初始化过的,没有的话,就自己映射 MMIO,再基于这段 MMIO 初始化一个 Regmap。然后直接用这个regmap去读。
但寄存器底层可能通过 MMIO、I2C、SPI 等不同方式访问,它是如何统一这些方式的呢?这就涉及到regmap core,Regmap Core 本身不关心寄存器底层通过什么方式访问。不同底层由 regmap_bus 提供具体读写实现,例如 MMIO、I2C、SPI。因此上层驱动都可以统一使用 regmap_read/write。
如何配置也很简单如下:
reg_bits
val_bits
reg_stride
max_register
readable/writeable/volatile...
所以整体流程如下:
driver
↓
regmap_read/write/update_bits
↓
Regmap Core
↓
regmap_bus
├─ MMIO → readl/writel
├─ SPI → SPI API
└─ I2C → I2C API
↓
硬件寄存器
