Linux spi驱动框架分析(二)

news/2024/7/5 19:59:45

系列文章
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设备所示:
    在这里插入图片描述
  2. 显式实例化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。


http://www.niftyadmin.cn/n/3657304.html

相关文章

Linux spi驱动框架分析(三)

系列文章&#xff1a; Linux spi驱动框架分析&#xff08;一&#xff09; Linux spi驱动框架分析&#xff08;二&#xff09; Linux spi驱动框架分析&#xff08;三&#xff09; Linux spi驱动框架分析&#xff08;四&#xff09; spi_master驱动 spi_master驱动负责最底层的…

Visual Studio International Pack对汉字的支持

还在重复发明轮子吗&#xff1f;看看微软在汉字方面的新支持&#xff1a;Simplified Chinese Pin-Yin Conversion Library - 支持获取简体中文字符的常用属性比如拼音&#xff0c;多音字&#xff0c;同音字&#xff0c;笔画数。 Traditional Chinese to Simplified Chinese Con…

Hyper-V: 一台服务器变二百台服务器

微软未来的virtualization技术会带来。。。&#xff1f;来自微软的邮件摘录&#xff1a;Q: My customer has a significant investment of 32-bit servers and wants to know why Hyper-V won’t be developed for Windows Server 2008 32-bit editions.A: Developing Hyper-V …

Linux spi驱动框架分析(四)

系列文章&#xff1a; Linux spi驱动框架分析&#xff08;一&#xff09; Linux spi驱动框架分析&#xff08;二&#xff09; Linux spi驱动框架分析&#xff08;三&#xff09; Linux spi驱动框架分析&#xff08;四&#xff09; spi_master的消息队列机制 SPI数据传输可以有…

Linux 输入子系统分析(一)

Linux 输入子系统分析&#xff08;一&#xff09; Linux 输入子系统分析&#xff08;二&#xff09; 分析一个内核提供的input_handler Linux内核输入子系统 输入设备&#xff08;如按键、键盘、触摸屏、鼠标等&#xff09;是典型的字符设备&#xff0c;其一般的工作机理是底…

文件的上传和下载

在Web编程中,我们常需要把一些本地文件上传到Web服务器上,上传后,用户可以通过浏览器方便地浏览这些文件&#xff0c;应用十分广泛。那么使用C#如何实现文件上传的功能呢?首先,在你的Visual C# web project 中增加一个上传用的Web Form,为了要上传文件,需要在ToolBox中选择HTM…

Linux 输入子系统分析(二)

Linux 输入子系统分析&#xff08;一&#xff09; Linux 输入子系统分析&#xff08;二&#xff09; 分析一个内核提供的input_handler input_dev驱动 input_dev驱动程序的工作主要是&#xff1a;申请一些硬件资源&#xff0c;如注册中断等&#xff0c;申请input_dev并设置&a…

分析一个内核提供的input_handler

Linux 输入子系统分析&#xff08;一&#xff09; Linux 输入子系统分析&#xff08;二&#xff09; 分析一个内核提供的input_handler 对应一些常用的输入设备&#xff0c;如键盘、鼠标等&#xff0c;内核提供的对应的input_handler&#xff0c;用来统一处理这类设备的事件。…