天天看點

Linux spi驅動架構分析(三)spi_master驅動

系列文章:

Linux spi驅動架構分析(一)

Linux spi驅動架構分析(二)

Linux spi驅動架構分析(三)

Linux spi驅動架構分析(四)

spi_master驅動

spi_master驅動負責最底層的資料收發工作,為了完成資料的收發工作,控制器驅動需要完成以下這些功能:

  1. 申請必要的硬體資源,例如中斷,DMA通道,DMA記憶體緩沖區等等;
  2. 配置spi控制器的工作模式和參數,使之可以和相應的裝置進行正确的資料交換工作;
  3. 向spi核心層提供接口,使得上層的裝置驅動可以通過核心層通路控制器驅動;
  4. 配合核心層,完成資料消息隊列的排隊和處理,直到消息隊列變空為止;

控制器的相關資訊一般在裝置樹中描述,驅動程式從裝置樹中擷取硬體資源,用以初始化spi_master,之後調用spi_register_master函數進行注冊。

如下圖描述了一個spi控制器的裝置節點:

Linux spi驅動架構分析(三)spi_master驅動

在裝置樹上描述了spi控制器的各種IO資源,如寄存器位址、中斷号,片選引腳的gpio等。

下面就來分析spi_register_master函數,函數執行流程圖如下所示:

Linux spi驅動架構分析(三)spi_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;
}
           

繼續閱讀