Try   HackMD

Linux SPI subsystem學習筆記

tags: Linux kernel

本文以Beaglebone版子為例,linux 4.19.142

確定驅動檔案

為了找到內核與SPI相關的程式碼,利用以下步驟

1. 進入kernal資料夾,使用make menuconfig

選擇Device Drivers
-> <*>SPI support

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

找到預設已勾選的選項,輸入'?'紀錄第一行CONFIG_xxxxx
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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目錄所產生的檔案

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

與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驅動架構







hierarchy



spi_bus

spi_bus



spi_device

spi_device



spi_bus->spi_device





spi_driver

spi_driver



spi_bus->spi_driver





spi_master

spi_master



spi_bus->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

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文章,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

補充:雖然在spi0節點裡的status是disabled,但在am335x-boneblack-wireless.dts裡將其enabled,所以會重新enabled

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。

/** * 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()

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。

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為例:
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相關函數。