06_spi子系统

总结

和i2c框架图类似 spi控制器也会当作spi设备 挂在spi总线上面

  • SPI核心层
    提供SPI控制器驱动和设备驱动的注册方法、注销方法、SPI通信硬件无关接口

  • SPI主机驱动
    主要包含SPI硬件体系结构中适配器(spi控制器)的控制,用于产生SPI 读写时序
    主要数据结构:spi_master(spi_controller)

  • SPI设备驱动
    通过SPI主机驱动与CPU交换数据
    主要数据结构:spi_device和spi_driver

和iic不一样的地方 因为速度高了很多提供了同步和异步的传输方式
异步的时候不是自己发送数据 把数据给内核线程 线程帮忙发送(前面讲的内核流水线工人)
同时和iic一样有万能驱动 开机自行注册 使用万能驱动 让app操作构造的spi控制器节点 直接操作每一个spi控制器
建议看iic子系统再来理解这个 spi写的有点冲忙
在这里插入图片描述

spi总线的注册

spi总线定义

struct bus_type spi_bus_type = {  //设定了spi设备和驱动匹配规则
	.name		= "spi",
	.dev_groups	= spi_dev_groups,
	.match		= spi_match_device,
	.uevent		= spi_uevent,
};

spi总线的注册
drivers/spi/spi.c 开机自动运行

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

spi控制器注册

设备树节点注册
spi的控制器的设备树节点 用第三个spi控制器举例

ecspi3: ecspi@2010000 {
					#address-cells = <1>;
					#size-cells = <0>;
					compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
					reg = <0x2010000 0x4000>;
					interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
					clocks = <&clks IMX6UL_CLK_ECSPI3>,
						 <&clks IMX6UL_CLK_ECSPI3>;
					clock-names = "ipg", "per";
					dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;
					dma-names = "rx", "tx";
					status = "disabled";
				};

核心层(spi控制器)的主要结构体 spi_master

#define spi_master 	
	struct device	dev;  //继承device结构体,linux设备驱动模型
	...
	struct list_head list; //链表节点,链接spi_controller
	s16			bus_num; //spi控制器编号,通过这个对不同控制器识别
	u16			num_chipselect; //片选信号数量
	...
	struct spi_message		*cur_msg;//注意这个结构体类型,表明正在处理的消息队列
	...
	int			(*setup)(struct spi_device *spi);//初始化spi设备使用
	int			(*transfer)(struct spi_device *spi, //传输spi消息,因为是异步的,其实就是把消息加入到消息队列queue
						struct spi_message *mesg);
	void		(*cleanup)(struct spi_device *spi);
	struct kthread_worker		kworker;//内核流水线工人
	struct task_struct		*kworker_task;//指向一个线程.线程配合工人工作
	struct kthread_work		pump_messages;//工人的具体工作,负责发送queue消息
	struct list_head		queue;  //挂载一系列的spi消息,消息会给工人处理
	struct spi_message		*cur_msg;
	
	...
	int (*transfer_one)(struct spi_controller *ctlr, struct spi_device *spi,struct spi_transfer *transfer);
	int (*prepare_transfer_hardware)(struct spi_controller *ctlr);
	int (*transfer_one_message)(struct spi_controller *ctlr,struct spi_message *mesg);
	void (*set_cs)(struct spi_device *spi, bool enable);
	...
	int			*cs_gpios; //负责记录具体片选信号
}
//初始化完控制器后 需要注册到内核
int spi_register_master(struct spi_master *master)
	spi_register_controller(struct spi_controller *ctlr)
	struct device		*dev = ctlr->dev.parent;//拿到结构体的dev的父指针
	struct boardinfo	*bi;
	int			status = -ENODEV;
	int			id, first_dynamic;
	dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);//给dev设置名字,
	status = device_add(&ctlr->dev);//加入linux设备驱动模型
	if (ctlr->transfer) { //判断transfer的成员变量是否在
	spi_controller_initialize_queue(ctlr);
		ctlr->transfer = spi_queued_transfer;
		ctlr->transfer_one_message = spi_transfer_one_message;
		/* 具体工作&ctlr->pump_messages ,对于这个工作的处理函数spi_pump_messages,被kthread_worker_fn线程进行调用*/
		ret = spi_init_queue(ctlr); //初始化内核线程工人,初始化内核具体工作
			kthread_init_worker(&ctlr->kworker);//初始化一个内核流水线工人
			ctlr->kworker_task = kthread_run(kthread_worker_fn, &ctlr->kworker,"%s", dev_name(&ctlr->dev)); 
										//为流水线工人创造一个线程
			kthread_init_work(&ctlr->pump_messages, spi_pump_messages);//初始化一个具体的工作,这个工作的具体函数就是spi_pump_messages
				spi_pump_messages函数实现
				void spi_pump_messages(struct kthread_work *work)
					struct spi_controller *ctlr = container_of(work, struct spi_controller, pump_messages); //根据pump_messages获取spi_controller
					__spi_pump_messages(ctlr, true);
						list_first_entry(&ctlr->queue, struct spi_message, queue);//通过queue找到要传输的message
					list_del_init(&ctlr->cur_msg->queue);//删除列表上的第一个message结构体,所以queue链接到第二个message结构体了
					if (ctlr->prepare_message) 
						ret = ctlr->prepare_message(ctlr, ctlr->cur_msg);
					ret = ctlr->transfer_one_message(ctlr, ctlr->cur_msg);
		
		ret = spi_start_queue(ctlr);//启动内核具体工作

			kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages); //把内核具体工作交给流水线工人

spi控制器驱动


static struct platform_driver spi_imx_driver = {
	.driver = {
		   .name = DRIVER_NAME,
		   .of_match_table = spi_imx_dt_ids,
		   .pm = IMX_SPI_PM,
	},
	.id_table = spi_imx_devtype,
	.probe = spi_imx_probe,
	.remove = spi_imx_remove,
};
module_platform_driver(spi_imx_driver); //这边的init有点奇怪 但还是调用了module_init
	module_driver(__platform_driver, platform_driver_register, \
			platform_driver_unregister)
				module_init(__driver##_init); \

//获取设备树节点信息,初始化spi时钟,
static int spi_imx_probe(struct platform_device *pdev)
	struct spi_master *master;
	master = spi_alloc_master(&pdev->dev,sizeof(struct spi_imx_data) + sizeof(int) * num_cs);
	spi_imx = spi_master_get_devdata(master);
	ret = of_property_read_u32(np, "fsl,spi-num-chipselects", &num_cs); //找到片选信号的数量
	master->num_chipselect = num_cs;
	ret = spi_bitbang_start(&spi_imx->bitbang);
		ret = spi_register_master(spi_master_get(master));  //把spi主控制器注册到linux系统

spi_device设备

struct spi_device {
	struct device		dev;
	struct spi_controller	*controller;  //表示这个设备在哪个spi控制器
	struct spi_controller	*master;	/* compatibility layer */
	u32			max_speed_hz;  //通讯最大频率
	u8			chip_select;
	u8			bits_per_word; //8位还是16位通讯
	u16			mode;  //spi的四大通讯模式
#define	SPI_CPHA	0x01			/* clock phase */
#define	SPI_CPOL	0x02			/* clock polarity */
#define	SPI_MODE_0	(0|0)			/* (original MicroWire) */
#define	SPI_MODE_1	(0|SPI_CPHA)
#define	SPI_MODE_2	(SPI_CPOL|0)
#define	SPI_MODE_3	(SPI_CPOL|SPI_CPHA)
#define	SPI_CS_HIGH	0x04			/* chipselect active high? */
#define	SPI_LSB_FIRST	0x08			/* per-word bits-on-wire */
#define	SPI_3WIRE	0x10			/* SI/SO signals shared */
#define	SPI_LOOP	0x20			/* loopback mode */
#define	SPI_NO_CS	0x40			/* 1 dev/bus, no chipselect */
#define	SPI_READY	0x80			/* slave pulls low to pause */
#define	SPI_TX_DUAL	0x100			/* transmit with 2 wires */
#define	SPI_TX_QUAD	0x200			/* transmit with 4 wires */
#define	SPI_RX_DUAL	0x400			/* receive with 2 wires */
#define	SPI_RX_QUAD	0x800			/* receive with 4 wires */
...
	char			modalias[SPI_NAME_SIZE]; //记录spi设备名字,因为要和driver配对 	
...
}

spi driver

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);
	struct device_driver	driver;  继承driver
};
probe里面大概做了
	int spi_register_driver(struct spi_driver *sdrv) 注册一个spi驱动
	

同步异步函数分析
在这里插入图片描述

	spi_controller->queue->spi_message 
	通过queue把一系列的 spi_message链接
	spi_controller->queue->spi_message->transfer
	把一系列的transfer给链接起来 一个transfer就是一个传输基本单位 所以也是message管理通讯消息
	
	
spi_sync函数 同步数据传输 sync总会有地方阻塞  async异步和下面的操作类似 但是不会阻塞
spi_sync(struct spi_device *spi, struct spi_message *message)
	ret = __spi_sync(spi, message);
	status = __spi_validate(spi, message);//判断各种参数正常
	message->complete = spi_complete;
	message->context = &done;
	message->spi = spi;
	status = __spi_queued_transfer(spi, message, false);
		if (!ctlr->running) { //判断spi控制器是否被占用
		spin_unlock_irqrestore(&ctlr->queue_lock, flags);
		return -ESHUTDOWN;
		}
	list_add_tail(&msg->queue, &ctlr->queue); //把message消息的queue挂载在控制器的queue的末尾
	kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);//把内核具体工作交给流水线工人


#define spi_register_master(_ctlr)	spi_register_controller(_ctlr)
	int spi_register_controller(struct spi_controller *ctlr)
		struct device *dev = ctlr->dev.parent; //拿到控制器的dev属性的父节点
		dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);//给控制器的device属性设置名字
		status = device_add(&ctlr->dev); //创建/sys/下面的目录
		if(ctlr->transfer_one || ctlr->transfer_one_message)
			spi_controller_initialize_queue(ctlr);
				ctlr->transfer = spi_queued_transfer;
			ret = spi_init_queue(ctlr); //初始化一个内核流水线工人和具体工作
				kthread_init_worker(&ctlr->kworker);  //初始化工人
				ctlr->kworker_task = kthread_run(kthread_worker_fn, &ctlr->kworker, //初始化工人的线程
					 "%s", dev_name(&ctlr->dev));
				kthread_init_work(&ctlr->pump_messages, spi_pump_messages);//初始化具体工作
			spi_start_queue() //启动工人的具体工作
				kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);

万能spi驱动分析

drivers/spi/spidev.c
开机后自动加载spidev驱动模块
设备树增加节点 引用spi3控制器

&ecspi3{  //引用spi3控制器的节点
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_ecspi3>; //初始化spi3控制器的相关引脚
	status = "okay";
	#address-cells = <1>;
	#size-cells = <0>; 

	spidev@0 {  //spi的设备节点
		compatible = "spidev";
		spi-max-frequency = <20000000>;
		reg = <0>;
	};
};

驱动实现 drivers/spi/spidev.c

开机后自动装载
module_init(spidev_init);
int __init spidev_init(void)
	register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);//对哈希表1申请设备号????还有呢
	spidev_class = class_create(THIS_MODULE, "spidev"); 创建class类
	spi_register_driver(&spidev_spi_driver);//在spi总线上新增驱动
		spidev_spi_driver.probe =	spidev_probe

//spi_dev结构体		
struct spidev_data {
	dev_t			devt;//设备号
	spinlock_t		spi_lock;
	struct spi_device	*spi;//spi设备指针
	struct list_head	device_entry;
	struct mutex		buf_lock;
	unsigned		users;
	u8			*tx_buffer;//发送buffer
	u8			*rx_buffer;//接受buffer
	u32			speed_hz;//速率
};	
//看看probe函数	  本质上创建一个字符设备 因为init中已经在哈希表1和哈希表二登记了主次设备号和file_operation结构体 
//下面就不用注册file_operation结构体了 只用新注册一个设备
int spidev_probe(struct spi_device *spi) //这里传进来的值也就是上面设备树节点spidev@0
	struct spidev_data	*spidev;
	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); //给spi_dev分配内存
	minor = find_first_zero_bit(minors, N_SPI_MINORS);//分配次设备号
	spidev->devt = MKDEV(SPIDEV_MAJOR, minor); //合成总设备号
	dev = device_create(spidev_class, &spi->dev, spidev->devt, //在class下面创建设备类
				    spidev, "spidev%d.%d",
				    spi->master->bus_num, spi->chip_select);//spi控制器编号 和spi片选编号 方便从文件名知道这个文件搞啥
	list_add(&spidev->device_entry, &device_list);//以后想找到spidev直接遍历device_list链表就很方便
	spidev->speed_hz = spi->max_speed_hz;//初始化传输速率
	spi_set_drvdata(spi, spidev);//把spidev属性记录到 spi_driver->data下面
			
//file_operation里面其他的函数
.open =		spidev_open,  //差不多为tx rxbuff分配内存
static int spidev_open(struct inode *inode, struct file *filp)
	list_for_each_entry(spidev, &device_list, device_entry) //遍历全局device_list链表,找到spi_dev
		if (spidev->devt == inode->i_rdev)//根据主次设备号 找到了想要的spi_dev
			status = 0;
	if (!spidev->tx_buffer) //看看有没有tx_buff
		spidev->tx_buffer = kmalloc(bufsiz, GFP_KERNEL);
	if (!spidev->rx_buffer)
		spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL);
	filp->private_data = spidev;//把spi_dev放入文件指针的私有数据里面

.read =		spidev_read,
spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
	spidev = filp->private_data; //把spi_dev拿出来
	status = spidev_sync_read(spidev, count);
		struct spi_transfer	t = { //创建一个spi传输结构体
			.rx_buf		= spidev->rx_buffer,
			.len		= len,
			.speed_hz	= spidev->speed_hz,
		};
		struct spi_message	m;

		spi_message_init(&m); //初始化一个spi_message
		spi_message_add_tail(&t, &m);//把spi_transfer加入spi_message中进行管理
		return spidev_sync(spidev, &m);//数据发送
			spi_async(spidev->spi, message);//之前的数据异步发送
		missing = copy_to_user(buf, spidev->rx_buffer, status);//收到数据回给应用层 

.unlocked_ioctl = spidev_ioctl, 也就是ioctrl 这个表示32位的ioctrl
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

实验 使用spi3控制器 进行自行收发

继续对上面的 驱动进行描述 应用层可以使用驱动生成的 spi控制器设备节点 进行操作spi控制器收发
应用层在ioctrl默认选择发送一个 spi_ioc_transfer 结构体

struct spi_ioc_transfer {
	__u64		tx_buf; //spi发送缓冲区
	__u64		rx_buf; //spi接受缓冲区

	__u32		len; //数据长度
	__u32		speed_hz;

	__u16		delay_usecs;
	__u8		bits_per_word;
	__u8		cs_change;
	__u8		tx_nbits;
	__u8		rx_nbits;
	__u16		pad;
	}

驱动中的ioctrl

.unlocked_ioctl = spidev_ioct
	switch (cmd) {
	case SPI_IOC_RD_MODE:
		xxx
	case SPI_IOC_RD_LSB_FIRST::
		xxxx
	default:
		ioc = spidev_get_ioc_message(cmd,  //传过来的arg可能不止一个 spi_ioc_transfer,可能是个结构体数组
				(struct spi_ioc_transfer __user *)arg, &n_ioc);//n_ioc记录传过来的ico个数
			tmp = _IOC_SIZE(cmd);//获得总数据的大小
			*n_ioc = tmp / sizeof(struct spi_ioc_transfer);//得出有多少个ioc结构体传入
		retval = spidev_message(spidev, ioc, n_ioc); //调用一系列spi核心函数 ,构造msg和trans进行数据收发
			struct spi_message	msg;
			struct spi_transfer	*k_xfers;
			spi_message_init(&msg);
			if (copy_from_user(tx_buf, (const u8 __user *)  //拷贝用户空间的数据未接下来发送准备
						(uintptr_t) u_tmp->tx_buf,
			spi_message_add_tail(k_tmp, &msg);
			status = spidev_sync(spidev, &msg);//数据同步发送
			if (__copy_to_user((u8 __user *)
					(uintptr_t) u_tmp->rx_buf, rx_buf, //读到的数据返回用户空间
			xxx...

开始实验
在这里插入图片描述
在这里插入图片描述
设备树节点的修改

iomux节点增加
	pinctrl_ecspi3:ecspi3grp {
					fsl,pins = <
						MX6UL_PAD_UART2_TX_DATA__ECSPI3_SS0     0x1a090
						MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK		0x11090
						MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI			0x11090
						MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO			0x11090
					>;
	};
	引用适配器做出修改 增加节点
&ecspi3{  //spi3控制器支持多组片选引脚
	fsl,spi-num-chipselects = <1>;  //这里修改这个spi3控制器支持多少个片选引脚
	cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>; //每个片选引脚使用到的具体引脚
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_ecspi3>;
	status = "okay";
	#address-cells = <1>;
	#size-cells = <0>; 

	spidev@0 {
		compatible = "spidev";
		spi-max-frequency = <20000000>;
		reg = <0>;  //指定这个spi设备使用哪个片选引脚
	};
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值