# 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

找到預設已勾選的選項,輸入'?'紀錄第一行CONFIG_xxxxx

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

與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**相關函數。