linux spi驱动分析 altas200模块
- 准备
- 匹配设备树节点和驱动
- spi控制器驱动分析(source/dev_plat/spi/spi-hisi.c)
- spi设备驱动分析(source/kernel/linux-4.19/drivers/spi/spidev.c)
- spi测试例程
准备
??华为官方使用的源代码包ascend200AI加速模块的SDK,下载地址为:点击跳转 使用的固件和驱动版本为:1.0.9.alpha 压缩包名称为:A200-3000-sdk_20.2.0.zip ??将A200-3000-sdk_20.2.0.zip解压后可见Ascend310-source-minirc.tar.gz这个压缩包里有压缩包ascend200AI加速模块的linux核源代码包、设备树及驱动文件等。
匹配设备树节点和驱动
??这里不做太多赘述,可以自己百度。 ??其spi位于设备树节点source/dtb/hi1910-fpga-spi.dtsi内容如下:
alg_clk: alg_clk { compatible = "fixed-clock"; #clock-cells = <0>; clock-frequency = <200000000>; }; spi_0: spi@130980000{ #address-cells = <1>; #size-cells = <0>; compatible = "hisi-spi"; reg = <0x1 0x30980000 0 0x10000>,<0x1 0x30900000 0 0x1000>; interrupts = <0 322 4>; clocks = <&alg_clk>; clock-names = "spi_clk"; num-cs = <2>; id = <0>; status = "ok"; }; spi_1: spi@130990000 { #address-cells = <1>; #size-cells = <0>; compatible = "hisi-spi"; reg = <0x1 0x30990000 0 0x10000>,<0x1 0x30900000 0 0x1000>; interrupts = <0 323 4>; clocks = <&alg_clk>; clock-names = "spi_clk"; num-cs = <2>; id = <1>; status = "disable"; }; clocks {
clk25m: clk@1 {
compatible = "fixed-clock"; reg=<1>; #clock-cells = <0>; clock-frequency = <25000000>; clock-output-names = "clk25m"; }; };
spi_0和spi_1节点的compatible 属性会和spi控制器的驱动匹配。(位于source/drivers/dev_plat/spi/spi-hisi.c)
STATIC const struct of_device_id hisi_spi_dt_ids[] = {
{
.compatible = "hisi-spi" }, {
/* sentinel */ }};
STATIC struct platform_driver hisi_spi_driver = {
.driver = {
.name = "hisi_spi", .pm = HISI_SPI_PM_OPS, .of_match_table = of_match_ptr(hisi_spi_dt_ids),#ifdef CONFIG_ACPI .acpi_match_table = ACPI_PTR(hisi_spi_acpi_ids),#endif }, .probe = hisi_spi_probe, .remove = hisi_spi_remove,};
内核会先解析设备树节点,然后驱动程序注册的时候of_match_ptr中的of_device_id会和设备树节点进行匹配。
spi控制器驱动分析(source/dev_plat/spi/spi-hisi.c)
spi_0和spi_1的设备树节点都会匹配上spi控制器驱动,然后执行他的probe函数,由于spi_1节点的状态为disable,所以只有spi_0节点会执行一次probe函数
int ret; int irq; u32 id = 0; u32 cs_num = 0; u32 rate = 0; struct resource *res = NULL; struct resource *res_iomux = NULL; struct hisi_spi *hs = NULL; struct spi_master *master = NULL;
初始化变量
ASSERT_RET((pdev != NULL), -EINVAL); dev_info(&pdev->dev, "hi_spi_probe enter..\n"); dev_info(&pdev->dev, "hi:dev name %s, id %d, auto %d, num res %u\n", pdev->name, pdev->id, pdev->id_auto, pdev->num_resources);
ASSERT_RET断言,判断platform_device *pdev结构体是否为空。 dev_info和printk类似,在内核启动时打印消息
ret = hisi_spi_get_dts(pdev, &cs_num, &id, &rate); if (ret != 0) {
dev_err(&pdev->dev, "hisi_spi_get_dts fail\n"); return -EINVAL; }
hisi_spi_get_dts用于获取片选和时钟等信息
STATIC int hisi_spi_get_dts(struct platform_device *pdev, u32 *cs_num, u32 *id, u32 *rate){
int ret;#ifndef CONFIG_ACPI struct clk *clk = NULL;#endif if ((pdev == NULL) || (cs_num == NULL) || (id == NULL) || (rate == NULL)) {
return -EINVAL; }#ifdef CONFIG_ACPI ret = device_property_read_u32(&pdev->dev, "num-cs", cs_num); if (ret < 0) {
dev_err(&pdev->dev, "get num_cs error: %d \n", ret); return -EINVAL; } ret = device_property_read_u32(&pdev->dev, "id", id); if (ret < 0) {
dev_err(&pdev->dev, "get id error: %d \n", ret); return -EINVAL; } ret = device_property_read_u32(&pdev->dev, "spi_clk", rate); if (ret < 0) {
dev_err(&pdev->dev, "get clk error : %d\n", ret); return -EINVAL; }#else ret = of_property_read_u32(pdev->dev.of_node, "num-cs", cs_num); if (ret != 0) {
dev_err(&pdev->dev, "of_property_read_u32 get num-cs fail\n"); return -EINVAL; } ret = of_property_read_u32(pdev->dev.of_node, "id", id); if (ret != 0) {
dev_err(&pdev->dev, "of_property_read_u32 get id fail\n"); return -EINVAL; } clk = clk_get(&pdev->dev, "spi_clk"); if (IS_ERR(clk)) {
return PTR_ERR(clk); } *rate = clk_get_rate(clk); clk_put(clk);#endif return 0;}
这个ACPI好像和电源管理有关,内核中全局搜索CONFIG_ACPI也未找到定义,所里这里先不管他。 这个hisi_spi_get_dts会获取设备书中spi节点信息 片选数量cs_num=2 spi节点id=0 spi时钟速率rate = 200000000 接着获取设备树描述中的mem资源
/* get base addr */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (unlikely(res == NULL)) {
dev_err(&pdev->dev, "invalid resource\n"); return -EINVAL; }
这里要先说一下platform_get_resource这个函数,内核函数定义如下:
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num){
int i; for (i = 0; i < dev->num_resources; i++) {
struct resource *r = &dev->resource[i]; if (type == resource_type(r) && num-- == 0) return r; } return NULL;}
从第一份资源开始匹配,type用来匹配资源类型num–==0表示你想获得的是第多少份资源 spi0设备树节点:
reg = <0x1 0x30980000 0 0x10000>, <0x1 0x30900000 0 0x1000>; interrupts = <0 322 4>;
则spi0的pdev结构体的资源有三个,两个为IORESOURCE_MEM,一个为IORESOURCE_IRQ。这里调用:
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
获取的是<0x1 0x30980000 0 0x10000>他表示spi控制器的内存起始地址为0x30980000 ,大小为0x10000。然后获取irq资源:
irq = platform_get_irq(pdev, 0); if (irq < 0) {
dev_err(&pdev->dev, "platform_get_irq error\n"); return -ENODEV; } dev_info(&pdev->dev, "hi: get irq %d\n", irq);
中断代码中我没有使用过,所以不管他了,接下来是分配spi_master结构体的内存:
master = spi_alloc_master(&pdev->dev, sizeof(*hs)); if (master == NULL) {
dev_err(&pdev->dev, "spi_alloc_master error.\n"); return -ENOMEM; }
跳入spi_alloc_master里面:
static inline struct spi_controller *spi_alloc_master(struct device *host, unsigned int size){
return __spi_alloc_controller(host, size, false);}
struct spi_controller *__spi_alloc_controller(struct device *dev, unsigned int size, bool slave){
struct spi_controller *ctlr; if (!dev) return NULL; ctlr = kzalloc(size + sizeof(*ctlr), GFP_KERNEL); if (!ctlr) return NULL; device_initialize(&ctlr->dev); ctlr->bus_num = -1; ctlr->num_chipselect = 1; ctlr->slave = slave; if (IS_ENABLED(CONFIG_SPI_SLAVE) && slave) ctlr->dev.class = &spi_slave_class; else ctlr->dev.class = &spi_master_class; ctlr->dev.parent = dev; pm_suspend_ignore_children(&ctlr->dev, true); spi_controller_set_devdata(ctlr, &ctlr[1]); return ctlr;}EXPORT_SYMBOL_GPL(__spi_alloc_controller);
重点关注这两行:
ctlr = kzalloc(size + sizeof(*ctlr), GFP_KERNEL);spi_controller_set_devdata