系列文章:
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裝置執行個體化的幾種方法
-
通過裝置樹聲明spi裝置
在spi控制器的節點裡聲明子節點,每個子節點代表一個spi裝置,如下圖spidev節點代表一個spi裝置所示:
Linux spi驅動架構分析(二)spi corespi裝置與驅動 -
顯式執行個體化spi裝置
通過spi_new_device()函數,建立并注冊spi_device。
在分析源碼之前,先來了解一下spi硬體上的連接配接,如下圖:
如圖,一個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。