# Linux SPI subsystem學習筆記 ###### tags: `Linux kernel` 本文以Beaglebone版子為例,linux 4.19.142 * 系統移植請參閱 [beaglebone black wireless開發板系統移植](https://hackmd.io/@amberchung/install-beaglebone) * spidev驅動、spidev_test測試,則可參閱[如何使用linux內核提供SPI設備驅動Spidev](https://hackmd.io/@amberchung/linux-spidev) ## 確定驅動檔案 為了找到內核與SPI相關的程式碼,利用以下步驟 ### 1. 進入kernal資料夾,使用make menuconfig 選擇Device Drivers -> <*>SPI support ![](https://i.imgur.com/nvYD8Ng.png) 找到預設已勾選的選項,輸入'?'紀錄第一行CONFIG_xxxxx ![](https://i.imgur.com/KjkttwW.png) ### 2. 進入drivers\spi目錄,開啟makefile 查找上一步驟所記錄的CONFIG_xxxxx,就可以知道那個檔案會被使用到 ### 3. 重複上面兩步驟,將make menuconfig所有已勾選項目找出 列出如下 ``` # # Makefile for kernel SPI drivers. # # small core, mostly translating board-specific # config declarations into driver model code obj-$(CONFIG_SPI_MASTER) += spi.o obj-$(CONFIG_SPI_MEM) += spi-mem.o obj-$(CONFIG_SPI_SPIDEV) += spidev.o # SPI master controller drivers (bus) obj-$(CONFIG_SPI_BITBANG) += spi-bitbang.o obj-$(CONFIG_SPI_GPIO) += spi-gpio.o obj-$(CONFIG_SPI_OMAP24XX) += spi-omap2-mcspi.o ``` ### 4. 查看build完kernal裡drivers\spi目錄所產生的檔案 ![](https://i.imgur.com/ZFPVMAu.png) 與Makefile定義的一樣 ### 5. 閱讀內核的Documentation\spi\spi-summary 第140行 > There are two types of SPI driver, here called: > >> Controller drivers ... controllers may be built into System-On-Chip > processors, and often support both Master and Slave roles. > These drivers touch hardware registers and may use DMA. > Or they can be PIO bitbangers, needing just GPIO pins. > >> Protocol drivers ... these pass messages through the controller > driver to communicate with a Slave or Master device on the > other side of an SPI link. 由以上步驟以及spi-summary說明,可以推測出SPI是由core(spi.c)、protocol driver(spidev.c)、master controller driver(spi-omap2-mcspi.c)、spi device(由device tree載入)所構成。 ## SPI驅動架構 ```graphviz digraph hierarchy { nodesep=1.0 // increases the separation between nodes node [color=Red,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Blue, style=filled] //All the lines look like this spi_bus->{spi_device spi_driver spi_master} } ``` 比起一般的linux驅動架構,增加了spi_master(spi_controller)結構,一般由BSP提供,驅動工程師則負責針對spi driver進行開發。 --- 而各個區塊主要負責的工作 | 檔案 | struct | 說明 | | ------ | ----------- | ------ | | spi.c | N/A | spi_bus總線,負責管理設備、驅動、控制器 | | spi-omap2-mcspi.c | spi_master | spi控制器,可以想像成am335x晶片有spi0、spi1兩個控制器,藉由spi_master設定spi硬體控制器,進行SPI資料傳輸 | | spidev.c | spi_driver | spi驅動,根據連接硬體裝置的datasheet,撰寫所需傳輸的資料內容,例如flash driver主要功能有flash讀/寫/擦除等操作,皆可以透過ioctrl實現 | | dts | spi_device | 硬體設備相關資訊,定義在device tree裡 | 在撰寫spi驅動時,建議參考內核提供的spidev.c以及Documentation\spi\spidev說明文件 ### spi master controller如何向內核註冊? 內核載入時會自動安裝spi-omap2-mcspi.c的module,註冊名為omap2_mcspi_driver的platform_driver ```c= static const struct of_device_id omap_mcspi_of_match[] = { { .compatible = "ti,omap2-mcspi", .data = &omap2_pdata, }, { .compatible = "ti,omap4-mcspi", .data = &omap4_pdata, }, { }, }; MODULE_DEVICE_TABLE(of, omap_mcspi_of_match); static struct platform_driver omap2_mcspi_driver = { .driver = { .name = "omap2_mcspi", .pm = &omap2_mcspi_pm_ops, .of_match_table = omap_mcspi_of_match, }, .probe = omap2_mcspi_probe, .remove = omap2_mcspi_remove, }; module_platform_driver(omap2_mcspi_driver); ``` 而當設備樹的compatile屬性與omap2_mcspi_driver裡的omap_mcspi_of_match裡compatible定義的名子相同時,就會調用omap2_mcspi_probe函數。 如同[如何使用linux內核提供SPI設備驅動Spidev](https://hackmd.io/@amberchung/linux-spidev)文章,am335x-boneblack-wireless.dts裡定義的 ``` &spi0 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&spi0_pins_default>; spidev0: spi@0 { compatible = "spidev"; reg = <0>; /*CS0*/ spi-max-frequency = <40000000>; }; }; ``` 並在am33xx.dtsi裡找到spi0節點的定義 ``` spi0: spi@48030000 { compatible = "ti,omap4-mcspi"; #address-cells = <1>; #size-cells = <0>; reg = <0x48030000 0x400>; interrupts = <65>; ti,spi-num-cs = <2>; ti,hwmods = "spi0"; dmas = <&edma 16 0 &edma 17 0 &edma 18 0 &edma 19 0>; dma-names = "tx0", "rx0", "tx1", "rx1"; status = "disabled"; }; ``` 發現compatible定義與spi-omap2-mcspi.c裡的omap_mcspi_of_match相同,所以會調用omap2_mcspi_probe :::info **補充**:雖然在spi0節點裡的status是disabled,但在am335x-boneblack-wireless.dts裡將其enabled,所以會重新enabled ::: ```c= static int omap2_mcspi_probe(struct platform_device *pdev) { struct spi_master *master; const struct omap2_mcspi_platform_config *pdata; struct omap2_mcspi *mcspi; struct resource *r; int status = 0, i; u32 regs_offset = 0; struct device_node *node = pdev->dev.of_node; const struct of_device_id *match; master = spi_alloc_master(&pdev->dev, sizeof *mcspi); if (master == NULL) { dev_dbg(&pdev->dev, "master allocation failed\n"); return -ENOMEM; } /* the spi->mode bits understood by this driver: */ master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32); master->setup = omap2_mcspi_setup; master->auto_runtime_pm = true; master->prepare_message = omap2_mcspi_prepare_message; master->can_dma = omap2_mcspi_can_dma; master->transfer_one = omap2_mcspi_transfer_one; master->set_cs = omap2_mcspi_set_cs; master->cleanup = omap2_mcspi_cleanup; master->dev.of_node = node; master->max_speed_hz = OMAP2_MCSPI_MAX_FREQ; master->min_speed_hz = OMAP2_MCSPI_MAX_FREQ >> 15; platform_set_drvdata(pdev, master); mcspi = spi_master_get_devdata(master); mcspi->master = master; /* 解析設備樹裡的資訊 */ match = of_match_device(omap_mcspi_of_match, &pdev->dev); if (match) { u32 num_cs = 1; /* default number of chipselect */ pdata = match->data; of_property_read_u32(node, "ti,spi-num-cs", &num_cs); master->num_chipselect = num_cs; if (of_get_property(node, "ti,pindir-d0-out-d1-in", NULL)) mcspi->pin_dir = MCSPI_PINDIR_D0_OUT_D1_IN; } else { pdata = dev_get_platdata(&pdev->dev); master->num_chipselect = pdata->num_cs; mcspi->pin_dir = pdata->pin_dir; } regs_offset = pdata->regs_offset; r = platform_get_resource(pdev, IORESOURCE_MEM, 0); mcspi->base = devm_ioremap_resource(&pdev->dev, r); if (IS_ERR(mcspi->base)) { status = PTR_ERR(mcspi->base); goto free_master; } mcspi->phys = r->start + regs_offset; mcspi->base += regs_offset; mcspi->dev = &pdev->dev; INIT_LIST_HEAD(&mcspi->ctx.cs); mcspi->dma_channels = devm_kcalloc(&pdev->dev, master->num_chipselect, sizeof(struct omap2_mcspi_dma), GFP_KERNEL); if (mcspi->dma_channels == NULL) { status = -ENOMEM; goto free_master; } for (i = 0; i < master->num_chipselect; i++) { sprintf(mcspi->dma_channels[i].dma_rx_ch_name, "rx%d", i); sprintf(mcspi->dma_channels[i].dma_tx_ch_name, "tx%d", i); } pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_set_autosuspend_delay(&pdev->dev, SPI_AUTOSUSPEND_TIMEOUT); pm_runtime_enable(&pdev->dev); status = omap2_mcspi_master_setup(mcspi); if (status < 0) goto disable_pm; status = devm_spi_register_master(&pdev->dev, master); if (status < 0) goto disable_pm; return status; disable_pm: pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); free_master: spi_master_put(master); return status; } ``` omap2_mcspi_probe主要將設備樹裡定義的硬體資訊匯入到spi_master 第87行**devm_spi_register_master**()會調用到**spi_register_controller**向內核註冊spi_master。 spi controller(spi-omap2-mcspi.c)是由BSP提供,因此只需瞭解struct spi_master結構即可。 ### spi device如何向內核註冊? 在前面spi controller註冊時,會調用到spi_register_controller向內核註冊spi_master spi_register_controller會呼叫of_register_spi_devices,最終會調用spi_add_device。 ```c= /** * spi_add_device - Add spi_device allocated with spi_alloc_device * @spi: spi_device to register * * Companion function to spi_alloc_device. Devices allocated with * spi_alloc_device can be added onto the spi bus with this function. * * Return: 0 on success; negative errno on failure */ int spi_add_device(struct spi_device *spi) { struct spi_controller *ctlr = spi->controller; struct device *dev = ctlr->dev.parent; int status; /* Chipselects are numbered 0..max; validate. */ if (spi->chip_select >= ctlr->num_chipselect) { dev_err(dev, "cs%d >= max %d\n", spi->chip_select, ctlr->num_chipselect); return -EINVAL; } /* Set the bus ID string */ spi_dev_set_name(spi); /* We need to make sure there's no other device with this * chipselect **BEFORE** we call setup(), else we'll trash * its configuration. Lock against concurrent add() calls. */ mutex_lock(&spi_add_lock); status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check); if (status) { dev_err(dev, "chipselect %d already in use\n", spi->chip_select); goto done; } /* Controller may unregister concurrently */ if (IS_ENABLED(CONFIG_SPI_DYNAMIC) && !device_is_registered(&ctlr->dev)) { status = -ENODEV; goto done; } if (ctlr->cs_gpios) spi->cs_gpio = ctlr->cs_gpios[spi->chip_select]; /* Drivers may modify this initial i/o setup, but will * normally rely on the device being setup. Devices * using SPI_CS_HIGH can't coexist well otherwise... */ status = spi_setup(spi); if (status < 0) { dev_err(dev, "can't setup %s, status %d\n", dev_name(&spi->dev), status); goto done; } /* Device may be bound to an active driver when this returns */ status = device_add(&spi->dev); if (status < 0) dev_err(dev, "can't add %s, status %d\n", dev_name(&spi->dev), status); else dev_dbg(dev, "registered child %s\n", dev_name(&spi->dev)); done: mutex_unlock(&spi_add_lock); return status; } ``` 第53行spi_setup會調用到controller的setup函數,設定該設備等mode、max frequency。 第61行spi_add_device則會將device加入內核中,並找尋是否有合適driver進行綁定。 ### spi protocol driver如何向內核註冊? 安裝spidev驅動時會呼叫入口函數spidev_init() ```c= static int __init spidev_init(void) { int status; /* Claim our 256 reserved device numbers. Then register a class * that will key udev/mdev to add/remove /dev nodes. Last, register * the driver which manages those device numbers. */ BUILD_BUG_ON(N_SPI_MINORS > 256); status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops); if (status < 0) return status; spidev_class = class_create(THIS_MODULE, "spidev"); if (IS_ERR(spidev_class)) { unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); return PTR_ERR(spidev_class); } status = spi_register_driver(&spidev_spi_driver); if (status < 0) { class_destroy(spidev_class); unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); } return status; } module_init(spidev_init); ``` 在第20行**spi_register_driver**函式會向內核註冊"*struct spidriver* spidev_spi_driver",而若spidev_spi_driver裡的.of_match_table的compatile或是spidev_spi_driver裡的.name與設備樹定義的相同,則會調用probe函數,因此最終會調用到spidev_probe。 ```c= static int spidev_probe(struct spi_device *spi) { struct spidev_data *spidev; int status; unsigned long minor; /* * spidev should never be referenced in DT without a specific * compatible string, it is a Linux implementation thing * rather than a description of the hardware. */ WARN(spi->dev.of_node && of_device_is_compatible(spi->dev.of_node, "spidev"), "%pOF: buggy DT: spidev listed directly in DT\n", spi->dev.of_node); spidev_probe_acpi(spi); /* Allocate driver data */ spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); if (!spidev) return -ENOMEM; /* Initialize the driver data */ spidev->spi = spi; spin_lock_init(&spidev->spi_lock); mutex_init(&spidev->buf_lock); INIT_LIST_HEAD(&spidev->device_entry); /* If we can allocate a minor number, hook up this device. * Reusing minors is fine so long as udev or mdev is working. */ mutex_lock(&device_list_lock); minor = find_first_zero_bit(minors, N_SPI_MINORS); if (minor < N_SPI_MINORS) { struct device *dev; spidev->devt = MKDEV(SPIDEV_MAJOR, minor); dev = device_create(spidev_class, &spi->dev, spidev->devt, spidev, "spidev%d.%d", spi->master->bus_num, spi->chip_select); status = PTR_ERR_OR_ZERO(dev); } else { dev_dbg(&spi->dev, "no minor number available!\n"); status = -ENODEV; } if (status == 0) { set_bit(minor, minors); list_add(&spidev->device_entry, &device_list); } mutex_unlock(&device_list_lock); spidev->speed_hz = spi->max_speed_hz; if (status == 0) spi_set_drvdata(spi, spidev); else kfree(spidev); return status; } ``` spidev_probe會建立設備節點/dev/spidev*.*,應用層可開啟此設備節點並操作設備 ### 數據收發,spi protocol driver如何調用spi master controller? 透過spi.h提供的spi_write/spi_read/spi_write_then_read函式 * 以spi_write為例: ```c= static inline int spi_write(struct spi_device *spi, const void *buf, size_t len) { struct spi_transfer t = { .tx_buf = buf, .len = len, }; /* 建立spi_transfer結構體 */ return spi_sync_transfer(spi, &t, 1); /* 同步傳輸 */ } static inline int spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsigned int num_xfers) { struct spi_message msg; spi_message_init_with_transfers(&msg, xfers, num_xfers); return spi_sync(spi, &msg); } static inline void spi_message_init_with_transfers(struct spi_message *m, struct spi_transfer *xfers, unsigned int num_xfers) { unsigned int i; spi_message_init(m); /* 在宣告一個spi_message並初始化 */ /* 將多個spi_transfer加入spi_message的鏈表中 */ for (i = 0; i < num_xfers; ++i) spi_message_add_tail(&xfers[i], m); } ``` 第20行的spi_sync:將多個spi_transfer組成的spi_message送出spi同步傳輸,最終會調用spi_master提供的**transfer**相關函數。