ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

Linux SPI总线(2) - SPI通用接口层

2021-08-02 12:58:22  阅读:202  来源: 互联网

标签:struct SPI int transfer 总线 spi master Linux message


1. 简介

        由前面的博客可知,SPI通用接口层说白了就是一个中间层,承上启下,为协议驱动和控制器驱动提供一系列的标准接口API、标准数据结构,所以SPI通用接口层可以说是SPI总线的一个核心层了。

        SPI通用接口层的代码集中在:/drivers/spi/spi.c中。

2. SPI通用接口层初始化

        这一部分向系统注册了一个名为spi的总线类型,同时也为SPI控制器注册了一个名为spi_master的设备类。

struct bus_type spi_bus_type = {
	.name		= "spi",
	.dev_groups	= spi_dev_groups,
	.match		= spi_match_device,
	.uevent		= spi_uevent,
	.pm		= &spi_pm,
};
static struct class spi_master_class = {
	.name		= "spi_master",
	.owner		= THIS_MODULE,
	.dev_release	= spi_master_release,
};

static int __init spi_init(void)
{
	status = bus_register(&spi_bus_type);   // 生成sys/bus/spi
     ······
	status = class_register(&spi_master_class);   //生成sys/class/spi_master
	return 0;
}
postcore_initcall(spi_init);

3. SPI通用接口层对外提供的数据结构和接口

3.1 SPI控制器

        SPI控制器负责按照设定的物理信号格式在主控和spi从设备之间交换数据,SPI控制器关注数据是如何被传输的,而不关心数据的内容,SPI通用接口层用spi_master结构来表示一个spi控制器。

struct spi_master {
	struct device	dev;    //spi控制器对应的device结构
	struct list_head list;  //系统可能有多个控制器,用该链表链接在一个全局链表变量上
	s16 bus_num;            //该控制器对应的spi总线编号,从0开始,通常由板级代码设定
	u16 num_chipselect;     //连接到该spi控制器的片选信号的个数
	u16 dma_alignment;
	u16 mode_bits;          //工作模式,由驱动解释该模式的意义
	u32 min_speed_hz;       //最低工作时钟
	u32 max_speed_hz;       //最高工作时钟
	u16 flags;              //用于设定某些限制条件的标志位
	int (*setup)(struct spi_device *spi);    //回调函数,用于设置某个spi从设备在该控制器上的工作参数
    //回调函数,用于把包含数据信息的mesg结构加入控制器的消息链表中
	int (*transfer)(struct spi_device *spi, struct spi_message *mesg);
	void (*cleanup)(struct spi_device *spi);  //回调函数,当spi_master被释放时,该函数被调用
	struct kthread_worker kworker;      //用于管理数据传输消息队列的工作队列线程
	struct kthread_work pump_messages;  //具体实现数据传输队列的工作队列线程
	struct list_head queue;       //该控制器的消息队列,所有等待传输的消息队列挂在该链表下
	struct spi_message *cur_msg;  //当前正在处理的消息队列
	int (*prepare_transfer_hardware)(struct spi_master *master);  //回调函数,正式发起传送前会被调用,用于准备硬件资源
   //单个消息的原子传送回调函数,队列中的每个消息都会调用一次该回调函数来完成传输工作
	int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg);
	int (*unprepare_transfer_hardware)(struct spi_master *master);  //清理回调函数
	int *cs_gpios;   //片选信号所用到的gpio
}

        spi_master结构通常由控制器驱动填充,然后通过以下通用接口层的API注册到系统中:

int spi_register_master(struct spi_master *master);

3.2 SPI从设备

        SPI通用接口层用spi_device结构来表示一个spi从设备。

struct spi_device {
	struct device dev;            //代表该spi设备的device结构
	struct spi_master *master;    //指向该spi设备所使用的控制器
	u32 max_speed_hz;     //该设备的最大工作时钟频率
	u8 chip_select;       //在控制器中的片选引脚编号索引
	u16 mode;             //在控制器中的片选引脚编号索引
	u8 bits_per_word;     //设备每个单位数据所需要的比特数
	int irq;              //设备使用的irq编号
	char modalias[SPI_NAME_SIZE];    //该设备的名字,用于spi总线和驱动进行配对
	int cs_gpio;//片选信号的gpio编号,通常不用我们自己设置,接口层会根据上面的chip_select字段在spi_master结构中进行查找并赋值
}

        要向系统增加并注册一个SPI从设备,我们还需要另一个数据结构:

struct spi_board_info {
        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_board_info结构大部分字段和spi_device结构相对应,bus_num字段则用来指定所属的控制器编号,通过spi_board_info结构,我们可以有两种方式向系统增加spi设备。第一种方式是在SPI控制器驱动已经被加载后,我们使用通用接口层提供的如下API来完成:

struct spi_device *spi_new_device(struct spi_master *master, struct spi_board_info *chip);

        第二种方式是在板子的初始化代码中,定义一个spi_board_info数组,然后通过以下API注册spi_board_info:

int spi_register_board_info(struct spi_board_info const *info, unsigned n);

        这个API会把每个spi_board_info挂在全局链表变量board_list上,并且遍历已经在系统中注册了的控制器,匹配上相应的控制器并取得它们的spi_master结构指针,最终也会通过spi_new_device函数添加SPI设备。因为spi_register_board_info可以在板子的初始化代码中调用,可能这时控制器驱动尚未加载,此刻无法取得相应的spi_master指针,不过不要担心,控制器驱动被加载时,一定会调用spi_register_master函数来注册spi_master结构,而spi_register_master函数会反过来遍历全局链表board_list上的spi_board_info,然后通过spi_new_device函数添加SPI设备。

3.3 SPI从设备驱动

        SPI通用接口层用spi_driver结构来表示一个spi从设备驱动程序(或者称为“SPI协议驱动程序”)。

struct spi_driver {
        const struct spi_device_id *id_table;
        int (*probe)(struct spi_device *spi);
        int (*remove)(struct spi_device *spi);
        void (*shutdown)(struct spi_device *spi);
        int (*suspend)(struct spi_device *spi, pm_message_t mesg);
        int (*resume)(struct spi_device *spi);
        struct device_driver driver;
};

        id_table字段用于指定该驱动可以驱动的设备名称,总线的匹配函数会把id_table中指定的名字和spi_device结构中的modalias字段进行比较,两者相符即表示匹配成功,然后触发spi_driver的probe回调函数被调用,从而完成驱动程序的初始化工作。

        通用接口层提供如下API来完成spi_driver的注册:

int spi_register_driver(struct spi_driver *sdrv)
{
        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;
        return driver_register(&sdrv->driver);
}

3.4 控制器和从设备之间的数据传输

        spi_message包含了一个的spi_transfer结构序列,一旦控制器接收了一个spi_message,其中的spi_transfer应该按顺序被发送,并且不能被其它spi_message打断,所以我们认为spi_message就是一次SPI数据交换的原子操作。

struct spi_message {
/* 链表字段transfers则用于链接挂在本message下的spi_tranfer结构 */    
        struct list_head        transfers;
        struct spi_device       *spi;
        unsigned                is_dma_mapped:1;
/* complete回调函数则会在该message下的所有spi_transfer都被传输完成时被调用,以便通知协议驱动处理接收到的数据以及准备下一批需要发送的数据 */
        void                    (*complete)(void *context);
        void                    *context;
        unsigned                frame_length;
        unsigned                actual_length;
        int                     status;
/* 链表字段queue用于把该结构挂在代表控制器的spi_master结构的queue字段上,控制器上可以同时被加入多个spi_message进行排队 */        
        struct list_head        queue;
        void                    *state;
};

struct spi_transfer {
        /* tx_buf和rx_buf提供了非dma模式下的数据缓冲区地址 */
        const void      *tx_buf;
        void            *rx_buf;
        unsigned        len;        //需要传输数据的长度
        /* tx_dma和rx_dma提供了dma模式下的缓冲区地址 */
        dma_addr_t      tx_dma;
        dma_addr_t      rx_dma;
        unsigned        cs_change:1;
        u8              tx_nbits;
        u8              rx_nbits;
        u8              bits_per_word;
        u16             delay_usecs;
        u32             speed_hz;
        /* transfer_list链表字段用于把该transfer挂在一个spi_message结构中 */
        struct list_head transfer_list;
};

        通用接口层为我们提供了一系列用于操作和维护spi_message和spi_transfer的API函数:

/* 初始化spi_message结构 */
void spi_message_init(struct spi_message *m);

/* 把一个spi_transfer加入/移除到一个spi_message中(注意,只是加入,未启动传输过程) */
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);
void spi_transfer_del(struct spi_transfer *t);

/* 以上两个API的组合,初始化一个spi_message并添加数个spi_transfer结构 */
void spi_message_init_with_transfers(struct spi_message *m, struct spi_transfer *xfers, unsigned int num_xfers);

/* 分配一个自带数个spi_transfer结构的spi_message */
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);

        利用以上这些API函数接口,SPI设备的协议驱动程序就可以完成与某个SPI从设备的数据交换工作,因为有通用接口层的隔离,控制器驱动对于协议驱动程序来说是透明的,即协议驱动程序只关心具体需要处理和交换的数据,无需关心控制器是如何传送这些数据的。

3.5 总结

        总结一下,协议驱动发送数据的流程大致是这样的:

定义一个spi_message结构;
用spi_message_init函数初始化spi_message;
定义一个或数个spi_transfer结构,初始化并为数据准备缓冲区并赋值给spi_transfer相应的字段(tx_buf,rx_buf等);
通过spi_message_init函数把这些spi_transfer挂在spi_message结构下;
如果使用同步方式,调用spi_sync(),如果使用异步方式,调用spi_async();

        另外,通用接口层也为一些简单的数据传输提供了独立的API来完成上述的组合过程:

/* 同步方式发送数据 */
int spi_write(struct spi_device *spi, const void *buf, size_t len);

/* 同步方式接收数据 */
int spi_read(struct spi_device *spi, void *buf, size_t len);   

/* 同步方式,直接传送数个spi_transfer,接收和发送。 */
int spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsigned int num_xfers);

/* 先写后读 */
int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx);

/* 写8位,然后读8位 */
ssize_t spi_w8r8(struct spi_device *spi, u8 cmd);

/* 写8位,然后读16位 */
ssize_t spi_w8r16(struct spi_device *spi, u8 cmd);

4. SPI数据传输的队列化

        所谓队列化,是指把等待传输的message放入一个等待队列中,发起一个传输操作,其实就是把对应的message按先后顺序放入一个等待队列中,系统会在不断检测队列中是否有等待传输的message,如果有就不停地调度数据传输内核线程,逐个取出队列中的message进行处理,直到队列变空为止。

4.1 队列及工作线程的初始化

        队列化相关的字段和工作线程的初始化工作是在spi控制器注册过程中完成的,即spi_register_master中。

int spi_register_master(struct spi_master *master)
{
    /* If we're using a queued driver, start the queue */
    /* 如果spi_master设置了transfer回调函数字段,表示控制器驱动不准备使用通用接口层提供的队列化框架 */
	if (master->transfer)
		dev_info(dev, "master is unqueued, this is deprecated\n");
	else {
     /* 否则,spi_master_initialize_queue函数就会被调用---> */
		status = spi_master_initialize_queue(master);
		if (status) {
			device_del(&master->dev);
			goto done;
		}
	}
}

static int spi_master_initialize_queue(struct spi_master *master)
{
	int ret;
   /* 把master->transfer回调字段设置为默认的实现函数:spi_queued_transfer */
	master->transfer = spi_queued_transfer;
	if (!master->transfer_one_message)
   /* 如果控制器驱动没有实现transfer_one_message回调,用默认的spi_transfer_one_message函数进行赋值 */
		master->transfer_one_message = spi_transfer_one_message;

	/* Initialize and start queue 初始化队列和创建工作线程---> */
	ret = spi_init_queue(master);
	master->queued = true;
   /* 启动工作线程---> */
	ret = spi_start_queue(master);

	return 0;
}

static int spi_init_queue(struct spi_master *master)
{
	struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };

	INIT_LIST_HEAD(&master->queue);
	spin_lock_init(&master->queue_lock);

	init_kthread_worker(&master->kworker);
	master->kworker_task = kthread_run(kthread_worker_fn,
					   &master->kworker,
					   dev_name(&master->dev));
	init_kthread_work(&master->pump_messages, spi_pump_messages);

	if (master->rt) {
		dev_info(&master->dev,
			"will run message pump with realtime priority\n");
		sched_setscheduler(master->kworker_task, SCHED_FIFO, &param);
	}

	return 0;
}

int kthread_worker_fn(void *worker_ptr)
{
	struct kthread_worker *worker = worker_ptr;
	struct kthread_work *work;

	worker->task = current;
	work = NULL;
   /* 判断当前的 worker 的 work_list 上是否为空,如果非空,那么取出它,设置成为 worker->current_work,即当前的 work */
	if (!list_empty(&worker->work_list)) {
		work = list_first_entry(&worker->work_list,
					struct kthread_work, node);
		list_del_init(&work->node);
	}
	worker->current_work = work;

	if (work) {
		__set_current_state(TASK_RUNNING);
		work->func(work);   //执行取出的 work->func
	} else if (!freezing(current))
 /* 如果 worker 的 work_list 上为空,也就是没有任务,那么就会调用 schedule(),这个 kthread_worker_fn 执行线程进入睡眠 */
		schedule();          
}

static int spi_start_queue(struct spi_master *master)
{
   ······
   /* 唤醒master->kworker线程,即执行spi_pump_messages函数 */
	queue_kthread_work(&master->kworker, &master->pump_messages);
	return 0;
}

4.2 队列化的工作机制

        当协议驱动程序通过spi_async发起一个message请求时,队列化和工作线程被激活,触发一些列的操作,最终完成message的传输操作。

int spi_async(struct spi_device *spi, struct spi_message *message)
{
	struct spi_master *master = spi->master;
	int ret;

	if (master->bus_lock_flag)
		ret = -EBUSY;
	else
		ret = __spi_async(spi, message);

	return ret;
}

static int __spi_async(struct spi_device *spi, struct spi_message *message)
{
	struct spi_master *master = spi->master;

   /* 在spi_master_initialize_queue()中transfer回调已经被设置为默认的实现函数:spi_queued_transfer*/
	return master->transfer(spi, message);
 } 
 
static int spi_queued_transfer(struct spi_device *spi, struct spi_message *msg)
{
	struct spi_master *master = spi->master;

	list_add_tail(&msg->queue, &master->queue);   /* 将spi_message放到spi_master的queue链表中 */
	if (!master->busy)
           /* 唤醒master->kworker线程,即执行spi_pump_messages函数 */
		queue_kthread_work(&master->kworker, &master->pump_messages);

	return 0;
}

static void spi_pump_messages(struct kthread_work *work)
{
	struct spi_master *master = container_of(work, struct spi_master, pump_messages);
	int ret;

   /* 取出master->queue队列中的第一个spi_message */ 
	master->cur_msg = list_first_entry(&master->queue, struct spi_message, queue);
	list_del_init(&master->cur_msg->queue);
	
   /* 调用控制器驱动的prepare_transfer_hardware回调来让控制器驱动准备必要的硬件资源 */ 
	if (!was_busy && master->prepare_transfer_hardware) {
		ret = master->prepare_transfer_hardware(master);
		if (ret) {
			dev_err(&master->dev, "failed to prepare transfer hardware\n");

			return;
		}
	}

   /* 调用控制器驱动的prepare_message对message进行必要的处理 */
	if (master->prepare_message) {
		ret = master->prepare_message(master, master->cur_msg);
		if (ret) {
			dev_err(&master->dev, "failed to prepare message: %d\n", ret);
			master->cur_msg->status = ret;
			spi_finalize_current_message(master);
			return;
		}
		master->cur_msg_prepared = true;
	}

   /* 调用控制器驱动的transfer_one_message回调函数完成该message的传输工作 */
	ret = master->transfer_one_message(master, master->cur_msg);
}   

        如果不想自己实现transfer_one_message的话,可以使用默认的函数,在spi_master_initialize_queue()中会判断控制器驱动中是否提供了transfer_one_message,如果没有提供就会将默认的函数接口spi_transfer_one_message()赋值给transfer_one_message。

static int spi_transfer_one_message(struct spi_master *master, struct spi_message *msg)
{
	struct spi_transfer *xfer;
	bool keep_cs = false;
	int ret = 0;
	int ms = 1;

	spi_set_cs(msg->spi, true);

	list_for_each_entry(xfer, &msg->transfers, transfer_list) {
		trace_spi_transfer_start(msg, xfer);
		INIT_COMPLETION(master->xfer_completion);
            
           /* 调用控制器驱动中的回调函数transfer_one */ 
		ret = master->transfer_one(master, msg->spi, xfer);
		if (ret > 0) {
			ret = 0;
			ms = xfer->len * 8 * 1000 / xfer->speed_hz;
			ms += ms + 100; /* some tolerance */
			ms = wait_for_completion_timeout(&master->xfer_completion, msecs_to_jiffies(ms));
		}

		if (ms == 0) {
			dev_err(&msg->spi->dev, "SPI transfer timed out\n");
			msg->status = -ETIMEDOUT;
		}
		trace_spi_transfer_stop(msg, xfer);
		msg->actual_length += xfer->len;
	}
   /* 通知通用接口层继续处理队列中的下一个message */  
	spi_finalize_current_message(master);

	return ret;
}

        总之,在控制器驱动中,spi_transfer_one_message和transfer_one回调函数要实现其中一个。

标签:struct,SPI,int,transfer,总线,spi,master,Linux,message
来源: https://blog.csdn.net/qq_41076734/article/details/119216696

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有