系列文章:
Linux spi驅動架構分析(一)
Linux spi驅動架構分析(二)
Linux spi驅動架構分析(三)
Linux spi驅動架構分析(四)
spi_master驅動
spi_master驅動負責最底層的資料收發工作,為了完成資料的收發工作,控制器驅動需要完成以下這些功能:
- 申請必要的硬體資源,例如中斷,DMA通道,DMA記憶體緩沖區等等;
- 配置spi控制器的工作模式和參數,使之可以和相應的裝置進行正确的資料交換工作;
- 向spi核心層提供接口,使得上層的裝置驅動可以通過核心層通路控制器驅動;
- 配合核心層,完成資料消息隊列的排隊和處理,直到消息隊列變空為止;
控制器的相關資訊一般在裝置樹中描述,驅動程式從裝置樹中擷取硬體資源,用以初始化spi_master,之後調用spi_register_master函數進行注冊。
如下圖描述了一個spi控制器的裝置節點:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN2XjlGcjAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL0EkeNFTSU5kMRpHW3BjMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL2UzMzITMzUTM0AzMwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
在裝置樹上描述了spi控制器的各種IO資源,如寄存器位址、中斷号,片選引腳的gpio等。
下面就來分析spi_register_master函數,函數執行流程圖如下所示:
源碼分析
spi_register_master函數定義如下:
int spi_register_master(struct spi_master *master)
{
static atomic_t dyn_bus_id = ATOMIC_INIT((1<<15) - 1);
struct device *dev = master->dev.parent;
struct boardinfo *bi;
int status = -ENODEV;
int dynamic = 0;
if (!dev)
return -ENODEV;
//從裝置樹擷取資訊,用以初始化spi_master裡的num_chipselect、cs_gpios成員
status = of_spi_register_master(master);
if (status)
return status;
if (master->num_chipselect == 0)
return -EINVAL;
//設定總線号
if ((master->bus_num < 0) && master->dev.of_node)
master->bus_num = of_alias_get_id(master->dev.of_node, "spi");
/* convention: dynamically assigned bus IDs count down from the max */
if (master->bus_num < 0) {
/* FIXME switch to an IDR based scheme, something like
* I2C now uses, so we can't run out of "dynamic" IDs
*/
master->bus_num = atomic_dec_return(&dyn_bus_id);
dynamic = 1;
}
//初始化各種鎖及連結清單等
INIT_LIST_HEAD(&master->queue);
spin_lock_init(&master->queue_lock);
spin_lock_init(&master->bus_lock_spinlock);
mutex_init(&master->bus_lock_mutex);
mutex_init(&master->io_mutex);
master->bus_lock_flag = 0;
init_completion(&master->xfer_completion);
......
//如果設定了transfer成員,則不采用消息隊列機制,不贊成這麼做
if (master->transfer)
dev_info(dev, "master is unqueued, this is deprecated\n");
else {
//初始化消息隊列相關
status = spi_master_initialize_queue(master);
if (status) {
device_del(&master->dev);
goto done;
}
}
/* add statistics */
spin_lock_init(&master->statistics.lock);
mutex_lock(&board_lock);
//所有的spi_master都會連結在全局連結清單spi_master_list裡
list_add_tail(&master->list, &spi_master_list);
/* 對于以前不支援裝置樹的核心,平台相關的spi裝置資訊是儲存在struct spi_board_info結構體裡
* 然後調用spi_register_board_info函數統一增添到全局連結清單board_list裡,等待注冊spi控制器
* 後,再從board_list連結清單裡取出這些資訊,spi_new_device進核心
*/
list_for_each_entry(bi, &board_list, list)
spi_match_master_to_boardinfo(master, &bi->board_info);
mutex_unlock(&board_lock);
/* 注冊在裝置樹裡描述的spi裝置 */
of_register_spi_devices(master);
......
}
看看在裝置樹上擷取什麼資訊來初始化spi_master裡的num_chipselect、cs_gpios成員,of_spi_register_master:
static int of_spi_register_master(struct spi_master *master)
{
int nb, i, *cs;
struct device_node *np = master->dev.of_node;
if (!np)
return 0;
//通過cs-gpios屬性,擷取gpio的個數,即片選數
nb = of_gpio_named_count(np, "cs-gpios");
master->num_chipselect = max_t(int, nb, master->num_chipselect);
/* Return error only for an incorrectly formed cs-gpios property */
......
//配置設定int型數組,長度為片選數,用于儲存片選gpio号
cs = devm_kzalloc(&master->dev,
sizeof(int) * master->num_chipselect,
GFP_KERNEL);
master->cs_gpios = cs;
if (!master->cs_gpios)
return -ENOMEM;
for (i = 0; i < master->num_chipselect; i++)
cs[i] = -ENOENT;
//擷取片選gpio号并設定cs_gpios
for (i = 0; i < nb; i++)
{
cs[i] = of_get_named_gpio(np, "cs-gpios", i);
}
return 0;
}
前面提到過,執行個體化spi裝置方式之一是在spi控制器的節點下描述裝置節點,這些裝置節點會在注冊spi驅動時,進行解析并注冊進核心,調用of_register_spi_devices函數來處理這些裝置節點:
static void of_register_spi_devices(struct spi_master *master)
{
struct spi_device *spi;
struct device_node *nc;
if (!master->dev.of_node)
return;
//周遊子節點調用of_register_spi_device函數進行處理
for_each_available_child_of_node(master->dev.of_node, nc) {
if (of_node_test_and_set_flag(nc, OF_POPULATED))
continue;
spi = of_register_spi_device(master, nc);
if (IS_ERR(spi)) {
dev_warn(&master->dev, "Failed to create SPI device for %s\n",
nc->full_name);
of_node_clear_flag(nc, OF_POPULATED);
}
}
}
of_register_spi_device定義如下:
static struct spi_device *
of_register_spi_device(struct spi_master *master, struct device_node *nc)
{
struct spi_device *spi;
int rc;
u32 value;
/* 建立一個spi_device */
spi = spi_alloc_device(master);
......
/* 讀取節點中"compatible"屬性,并作為name */
rc = of_modalias_node(nc, spi->modalias,
sizeof(spi->modalias));
......
/* 讀取節點中"reg屬性,并作為片選索引 */
rc = of_property_read_u32(nc, "reg", &value);
......
spi->chip_select = value;
/* 可在裝置節點描述"spi-cpha"等相關屬性來說明裝置的模式 */
if (of_find_property(nc, "spi-cpha", NULL))
spi->mode |= SPI_CPHA;
if (of_find_property(nc, "spi-cpol", NULL))
spi->mode |= SPI_CPOL;
if (of_find_property(nc, "spi-cs-high", NULL))
spi->mode |= SPI_CS_HIGH;
if (of_find_property(nc, "spi-3wire", NULL))
spi->mode |= SPI_3WIRE;
if (of_find_property(nc, "spi-lsb-first", NULL))
spi->mode |= SPI_LSB_FIRST;
/* Device DUAL/QUAD mode */
if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) {
switch (value) {
case 1:
break;
case 2:
spi->mode |= SPI_TX_DUAL;
break;
case 4:
spi->mode |= SPI_TX_QUAD;
break;
default:
dev_warn(&master->dev,
"spi-tx-bus-width %d not supported\n",
value);
break;
}
}
if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) {
switch (value) {
case 1:
break;
case 2:
spi->mode |= SPI_RX_DUAL;
break;
case 4:
spi->mode |= SPI_RX_QUAD;
break;
default:
dev_warn(&master->dev,
"spi-rx-bus-width %d not supported\n",
value);
break;
}
}
/* Device speed */
rc = of_property_read_u32(nc, "spi-max-frequency", &value);
if (rc) {
dev_err(&master->dev, "%s has no valid 'spi-max-frequency' property (%d)\n",
nc->full_name, rc);
goto err_out;
}
spi->max_speed_hz = value;
/* Store a pointer to the node in the device structure */
of_node_get(nc);
spi->dev.of_node = nc;
/* 注冊新的裝置 */
rc = spi_add_device(spi);
if (rc) {
dev_err(&master->dev, "spi_device register error %s\n",
nc->full_name);
goto err_of_node_put;
}
return spi;
err_of_node_put:
of_node_put(nc);
err_out:
spi_dev_put(spi);
return ERR_PTR(rc);
}
of_modalias_node有個細節要注意:
int of_modalias_node(struct device_node *node, char *modalias, int len)
{
const char *compatible, *p;
int cplen;
/* 如,對于“compatible"屬性為"invensense,icm20608"的裝置節點,最終spi_device的modalias成員
* 為"icm20608"而不是"invensense,icm20608"
*/
compatible = of_get_property(node, "compatible", &cplen);
if (!compatible || strlen(compatible) > cplen)
return -ENODEV;
p = strchr(compatible, ',');
strlcpy(modalias, p ? p + 1 : compatible, len);
return 0;
}