kernel SPI 框架
SPI驱动从整体上遵循spi bus - drv -dev模型,核心层为spi.c,这与平台总线设备驱动模型及其相似,都遵循内核的bus_type框架,只是在构造spi device时会稍微有些复杂。
根据上面描述的spi总线模型,spi驱动核心的结构体有spi_device 和 spi_driver,其中,spi_device结构体中会含有spi控制器的信息,也就是spi_master结构体。
一 从spi master驱动到spi device的构建
在处理挂载在spi总线上的设备驱动之前,需要完成对spi总线的驱动,即spi Master驱动。spi Master驱动符合platform bus-dev-drv模型,其中,dev部分为设备树中spi设备节点,drv部分注册为一个platform_driver,内核在扫描设备树时,会将spi节点自动识别为一个平台设备,构造device_node结构体,最终构造platform_device结构体,并将该结构体注册进platform bus 中,如果platform bus中有相同的compatible属性的platform driver,则调用驱动程序的probe函数,相关代码如下:
// platform driver中相关结构体 (spi master driver)
static const struct of_device_id rockchip_spi_dt_match[] = {
{ .compatible = "rockchip,rk3399-spi", },
... ...
{ },
};
static struct platform_driver rockchip_spi_driver = {
.driver = {
.name = DRIVER_NAME,
.pm = &rockchip_spi_pm,
.of_match_table = of_match_ptr(rockchip_spi_dt_match),
},
.probe = rockchip_spi_probe,
.remove = rockchip_spi_remove,
};
module_platform_driver(rockchip_spi_driver);
// dts中的spi节点
spi0: spi@ff1c0000 {
compatible = "rockchip,rk3399-spi", "rockchip,rk3066-spi";
reg = <0x0 0xff1c0000 0x0 0x1000>;
clocks = <&cru SCLK_SPI0>, <&cru PCLK_SPI0>;
clock-names = "spiclk", "apb_pclk";
interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH 0>;
pinctrl-names = "default";
pinctrl-0 = <&spi0_clk &spi0_tx &spi0_rx &spi0_cs0>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
// 匹配过程
platform_match
of_driver_match_device
of_match_device
of_match_node
__of_device_is_compatible
of_compat_cmp
spi master 驱动需要完成两个任务,一是完成spi master结构体中定义的所有函数,即spi 总线驱动的相关设置和收发的底层函数,二是完成对spi子设备节点的解析,构造spi_device结构体,并向spi bus注册这个结构体。
挂载在spi或iic等平台总线下的设备,会被识别成哪种设备在spi/iic controller驱动的probe函数中决定。
static int rockchip_spi_probe(struct platform_device *pdev)
{
/* 分配spi_master */
master = spi_alloc_master(&pdev->dev, sizeof(struct rockchip_spi));
... .../* 设置 spi_master */
/* 注册spi_master */
ret = devm_spi_register_master(&pdev->dev, master);
}
根据设备树信息构造spi_device是在devm_spi_register_master里面实现的
在spi_device构建完成之后,会寻找spi bus中的driver,同样还是根据compatible属性,如果找到同名的compatible属性,则调用spi driver中的probe函数。
rockchip_spi_probe
devm_spi_register_master
spi_register_master
of_register_spi_devices
of_register_spi_device
spi_alloc_device
spi_add_device
spi_setup
device_add
bus_add_device /* add device */
bus_probe_device /* probe device */
device_initial_probe
__device_attach
bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
__device_attach_driver
driver_probe_device
really_probe
if (dev->bus->probe) ret = dev->bus->probe(dev);
二 spi_driver驱动注册到字符设备注册
spi_driver:
static const struct of_device_id rockchip_spi_test_dt_match[] = {
{ .compatible = "rockchip,spi_test_bus0_cs0", },
... ...
{},
};
static struct spi_driver spi_rockchip_test_driver = {
.driver = {
.name = "spi_test",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(rockchip_spi_test_dt_match),
},
.probe = rockchip_spi_test_probe,
.remove = rockchip_spi_test_remove,
};
static int __init spi_rockchip_test_init(void)
{
int ret = 0;
misc_register(&spi_test_misc);
ret = spi_register_driver(&spi_rockchip_test_driver);
return ret;
}
在spi driver的probe函数中,一般会注册一个spi字符设备,并实现spi设备的read/write/ioctrl等函数,以便于User调用,操作SPI设备,在spi driver中不能直接调用spi master里面的某些函数来进行spi设备的操作,而是使用spi.h中提供的接口来操作spi,如spi_setup,spi_read,spi_write。
需要注意的是,
从dts中创建的platform/spi device 并不含有任何的硬件信息,只含有:
1 platform device的dev.device_node,含有device_node信息
2 platform device的resource数组,对应占用的相关资源信息(reg,interrupts属性),有中断、内存、IO资源,驱动程序可以通过pdev->dev.of_node来获取其他信息,如硬件信息
三 SPI设备的枚举过程
内核首先会对dts文件进行识别,构造不同的device_node结构体,内核会自动将spi controller节点识别为平台设备,构造platform_device结构体,向平台总线注册这个结构体。
在注册spi master驱动时,会搜索平台总线中匹配的设备,如果有匹配的设备,则执行驱动的probe函数,在驱动程序中,会实现spi的收发,设置等底层函数,构造,设置spi_master结构体,并识别该spi设备节点下的子节点,构造spi_device结构体,并将spi_master赋值到spi_device中,并向spi核心层注册spi_device,同时搜索匹配的spi_driver,如果匹配,则调用spi_driver的probe函数,在该函数中,会注册字符设备驱动,实现该spi设备的枚举,并提供字符设备的open/read/write/ioctrl等函数。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!