天天看點

Linux spi驅動架構分析(二)spi corespi裝置與驅動

系列文章:

Linux spi驅動架構分析(一)

Linux spi驅動架構分析(二)

Linux spi驅動架構分析(三)

Linux spi驅動架構分析(四)

spi core

spi核心(dervers/spi/spi.c)中提供了一組不依賴于硬體平台的接口函數,了解其中的主要函數非常關鍵,因為spi_master驅動和spi裝置驅動之間賴于i2c核心作為紐帶。spi核心中提供的主要函數如下。

(1) 為spi_master驅動提供的接口

//配置設定spi_master結構體
struct spi_master *spi_alloc_master(struct device *dev, unsigned size);

//注冊spi_master
int spi_register_master(struct spi_master *master);

//登出spi_master
void spi_unregister_master(struct spi_master *master);
           

(2) 為spi裝置驅動提供的接口

//注冊spi_driver
#define spi_register_driver(driver) \
	__spi_register_driver(THIS_MODULE, driver)

//登出spi_driver
static inline void spi_unregister_driver(struct spi_driver *sdrv);
           

(3) 提供了spi裝置注冊/銷毀的接口

//注冊spi_device
struct spi_device *spi_new_device(struct spi_master *master,
				  struct spi_board_info *chip);
//登出spi_device
void spi_unregister_device(struct spi_device *spi);
           

(4) 提供了一系列用于操作和維護spi_message和spi_transfer的接口

//用于初始化spi_message結構
static inline void spi_message_init(struct spi_message *m);

//把一個spi_transfer加入到一個spi_message中
static inline void
spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);

//移除一個spi_transfer
static inline void spi_transfer_del(struct spi_transfer *t);

//初始化一個spi_message并添加數個spi_transfer
static inline void
spi_message_init_with_transfers(struct spi_message *m,
struct spi_transfer *xfers, unsigned int num_xfers);

//配置設定一個自帶數個spi_transfer結構體的spi_message
static inline struct spi_message *spi_message_alloc(unsigned ntrans, gfp_t flags);

//發起一個spi_message,異步版本
int spi_async(struct spi_device *spi, struct spi_message *message);

//發起一個spi_message,同步版本
int spi_sync(struct spi_device *spi, struct spi_message *message);
           

spi核心建構了spi總線,用于驅動與裝置的比對等。

struct bus_type spi_bus_type = {
	.name		= "spi",
	.dev_groups	= spi_dev_groups,
	.match		= spi_match_device,
	.uevent		= spi_uevent,
};
           

當注冊spi_driver或者增添spi_device時,會調用到spi總線的match函數,進行驅動與裝置的比對。

驅動與裝置的比對

注冊驅動或裝置時進行比對,spi_register_driver:

#define spi_register_driver(driver) \
	__spi_register_driver(THIS_MODULE, driver)


int __spi_register_driver(struct module *owner, struct spi_driver *sdrv)
{
	sdrv->driver.owner = owner;

	//設定總線類型
	sdrv->driver.bus = &spi_bus_type;
	if (sdrv->probe)
		sdrv->driver.probe = spi_drv_probe;
	if (sdrv->remove)
		sdrv->driver.remove = spi_drv_remove;
	if (sdrv->shutdown)
		sdrv->driver.shutdown = spi_drv_shutdown;

	/* 注冊spi_driver
  	   driver_register函數會周遊spi總線的裝置連結清單,調用spi_match_device函數
  	   進行比對比對成功的話,會調用驅動的probe函數
	 */ 
	return driver_register(&sdrv->driver);
}
           

看看具體是怎麼比對的,spi_match_device:

static int spi_match_device(struct device *dev, struct device_driver *drv)
{
	const struct spi_device	*spi = to_spi_device(dev);
	const struct spi_driver	*sdrv = to_spi_driver(drv);

	/* 裝置樹比對*/
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	//id表比對
	if (sdrv->id_table)
		return !!spi_match_id(sdrv->id_table, spi);

	//前面比對都不成功,直接比較驅動的name與裝置的name是否一緻
	return strcmp(spi->modalias, drv->name) == 0;
}

           

id表比對,spi_match_id:

static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
						const struct spi_device *sdev)
{
	while (id->name[0]) {
		if (!strcmp(sdev->modalias, id->name))
			return id;
		id++;
	}
	return NULL;
}
           

spi裝置與驅動

Linux spi裝置執行個體化的幾種方法

  1. 通過裝置樹聲明spi裝置

    在spi控制器的節點裡聲明子節點,每個子節點代表一個spi裝置,如下圖spidev節點代表一個spi裝置所示:

    Linux spi驅動架構分析(二)spi corespi裝置與驅動
  2. 顯式執行個體化spi裝置

    通過spi_new_device()函數,建立并注冊spi_device。

在分析源碼之前,先來了解一下spi硬體上的連接配接,如下圖:

Linux spi驅動架構分析(二)spi corespi裝置與驅動

如圖,一個soc可能含有一個或多個spi控制器,一個spi控制器可以與多個spi裝置連接配接,通過片選引腳選擇不同裝置。片選引腳可以由控制器來控制,需要配置為專用引腳功能,也可以配置為gpio來控制,主流的還是配置為gpio來控制片選。

spi_master結構體中的num_chipselect成員,用來描述該控制器支援的片選數目,而spi_device結構體中的chip_select成員,用來描述片選号(從0開始的)。

spi_master結構體中的cs_gpios成員,指向一個int型數組,儲存着所有片選的gpio号,裝置的片選号即為這個數組的索引。裝置通過片選索引獲得自身的片選gpio号,spi_device結構體中的cs_gpio成員儲存着這片選gpio号。

不管是那種方式執行個體化,最終都是調用spi_new_device()函數進行注冊,這個函數傳入兩個參數,第一個參數表示裝置挂接在哪個spi控制器上,第二個參數用來描述這個spi裝置的資訊(名字、片選引腳等資訊)。

spi_new_device函數的第二個參數是struct spi_board_info *類型的結構體,用于來描述裝置的片選索引、name、工作頻率等資訊,結構體定義如下:

struct spi_board_info {

	//name,用于比對驅動
	char		modalias[SPI_NAME_SIZE];

	const void	*platform_data;
	void		*controller_data;
	int		irq;

	//該裝置的工作頻率
	u32		max_speed_hz;


	//總線号,即該裝置連接配接在哪個控制器上
	u16		bus_num;

	//片選号
	u16		chip_select;

	//該裝置的工作模式
	u16		mode;

};
           

在了解一些資訊後,下面就來分析一下spi_new_device函數,該函數的定義如下:

struct spi_device *spi_new_device(struct spi_master *master,
				  struct spi_board_info *chip)
{
	struct spi_device	*proxy;
	int			status;

	//建立一個struct spi_device,并進行一些初始化
	proxy = spi_alloc_device(master);
	if (!proxy)
		return NULL;

	WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias));

	//通過傳入的struct spi_board_info,初始化建立的struct spi_device
	proxy->chip_select = chip->chip_select;
	proxy->max_speed_hz = chip->max_speed_hz;
	proxy->mode = chip->mode;
	proxy->irq = chip->irq;
	strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));
	proxy->dev.platform_data = (void *) chip->platform_data;
	proxy->controller_data = chip->controller_data;
	proxy->controller_state = NULL;

	//add裝置
	status = spi_add_device(proxy);
	if (status < 0) {
		spi_dev_put(proxy);
		return NULL;
	}

	return proxy;
}
           

spi_add_device:

int spi_add_device(struct spi_device *spi)
{
	static DEFINE_MUTEX(spi_add_lock);
	struct spi_master *master = spi->master;
	struct device *dev = master->dev.parent;
	int status;

	/* 判斷片選索引是否有效 */
	if (spi->chip_select >= master->num_chipselect) {
		dev_err(dev, "cs%d >= max %d\n",
			spi->chip_select,
			master->num_chipselect);
		return -EINVAL;
	}

	/* Set the bus ID string */
	spi_dev_set_name(spi);


	mutex_lock(&spi_add_lock);

	//檢查是否重複注冊裝置(判斷spi_device ->master和spi_device ->chip_select是否一緻)
	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;
	}

	//通過片選索引,獲得片選gpio号
	if (master->cs_gpios)
		spi->cs_gpio = master->cs_gpios[spi->chip_select];

	/* 通過spi_device設定spi控制器,會調用spi_master->setup函數進行設定
   * 之後會調用spi_set_cs函數來選擇裝置
   */
	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_add,函數執行過程中,會進行比對,前面已講過比對相關規則 */
	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;
}
           

Linux spi裝置驅動的子產品加載與解除安裝

spi裝置驅動的子產品加載函數通用的方法是通過spi核心的spi_register_driver()函數添加 spi_driver的工作,而在子產品解除安裝函數中需要做相反的工作:通過spi核心的spi_unregister_driver()函數删除 spi_driver。

繼續閱讀