系列文章:
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设备所示:
- 显式实例化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。