Linux: SPI 驱动

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. SPI 总线驱动

2.1 SPI 总线拓扑

SPI 总线Master 设备Slave 从设 通信的接口,是一种高速、全双工的同步串行通信总线。简单看一下 SPI 总线的拓扑结构:
在这里插入图片描述
其中:

SCLK: SPI 总线时钟,4MHz-20MHz;
MOSI: 数据线,从Master传送数据到Slave;
MISO: 数据线,从Slave传送数据到Master;
SSx: Slave片选信号,可能标记成CS更常见。

2.2 SPI 总线工作模式

SPI 有4种工作模式,通过串行时钟极性(CPOL)相位(CPHA)的搭配来得到4种工作模式。先看下 CPOLCPHA 的作用:

1. CPOL=0,串行时钟空闲状态为低电平。
2. CPOL=1,串行时钟空闲状态为高电平,此时可以通过配置时钟相位(CPHA)来选择具体的传输协议。
3. CPHA=0,串行时钟的第1个跳变沿(上升沿或下降沿)采集数据。
4. CPHA=1,串行时钟的第2个跳变沿(上升沿或下降沿)采集数据。

再看由 CPOLCPHA 搭配的模式:

SCL空闲时电平	采样	CPHA	Mode
低电平	上升沿	CPOL = 0, CPHA = 0	Mode0
低电平	下降沿	CPOL = 0, CPHA = 1	Mode1
高电平	下降沿	CPOL = 1, CPHA = 0	Mode2
高电平	上升沿	CPOL = 1, CPHA = 1	Mode3

通过配置 SPI Master控制器,可以配置 SPI 的工作模式,而从设的模式一般是固定的,可以参考从设的数据手册。
SPI 的读写不同于 I2C,不需要显式标记,因为 SPI 是全双工的,读写可以同时进行。

2.3 SPI 总线驱动编写

编写 SPI 总线驱动相关的内核接口:

extern struct spi_controller *__spi_alloc_controller(struct device *host,
						unsigned int size, bool slave);
extern int spi_register_controller(struct spi_controller *ctlr);

看一个 SPI 总线驱动框架示例:

spi@01c68000 {
	compatible = "allwinner,sun8i-h3-spi";
	...

	spi@0 {
		compatible = "nanopi,spidev";
		...
	};
};
/* drivers/spi/sun6i-spi.c */

static int sun6i_spi_probe(struct platform_device *pdev)
{
	struct spi_master *master;

	/* 创建SPI总线控制器对象 */
	master = spi_alloc_master(&pdev->dev, sizeof(struct sun6i_spi));
	
	...

	/* SPI控制器中断处理: 数据传输完成、传输FIFO等的处理 */
	ret = devm_request_irq(&pdev->dev, irq, sun6i_spi_handler,
			       0, "sun6i-spi", sspi);

	...
	
	master->max_speed_hz = 100 * 1000 * 1000; /* 支持的最小时钟 */
	master->min_speed_hz = 3 * 1000; /* 支持的最小时钟 */
	master->set_cs = sun6i_spi_set_cs; /* 片选接口 */
	master->transfer_one = sun6i_spi_transfer_one; /* 设置SPI总线数据传输接口 */
	...
	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST; /* 设置工作模式 */
	...
	
	/* 注册SPI总线控制器对象到系统 */
	ret = devm_spi_register_master(&pdev->dev, master);

	return 0;
}

static const struct of_device_id sun6i_spi_match[] = {
	...
	{ .compatible = "allwinner,sun8i-h3-spi",  .data = (void *)SUN8I_FIFO_DEPTH },
	{}
};

static struct platform_driver sun6i_spi_driver = {
	.probe	= sun6i_spi_probe,
	...
	.driver	= {
		...
		.of_match_table	= sun6i_spi_match,
		...
	};
};

3. SPI 从设驱动

ads7846 触摸输入设备驱动为例来分析。

spi@01c68000 {
	compatible = "allwinner,sun8i-h3-spi";
	...

	/* SPI 从设 ads7846,挂接在 SPI 总线 spi@01c68000 上 */
	pitft-ts@1 {
		compatible = "ti,ads7846";
		...
		spi-max-frequency = <2000000>;
		interrupt-parent = <&pio>;
		interrupts = <6 9 IRQ_TYPE_EDGE_FALLING>;   /* PG9 / EINT9 */
		...
	};
};
static const struct of_device_id ads7846_dt_ids[] = {
	...
	{ .compatible = "ti,ads7846",	.data = (void *) 7846 },
	...
	{ }
};

/* SPI 从设驱动入口 */
static int ads7846_probe(struct spi_device *spi)
{
	struct input_dev *input_dev;
	
	...
	
	spi->bits_per_word = 8;
	spi->mode = SPI_MODE_0;
	err = spi_setup(spi); /* 设置 SPI 工作模式和时钟频率 */

	/* 创建输入设备对象 */
	input_dev = input_allocate_device();
	...

	/* 配置输入设备 */
	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
	input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
	input_set_abs_params(input_dev, ABS_X,
			pdata->x_min ? : 0,
			pdata->x_max ? : MAX_12BIT,
			0, 0);
	...

	/* 上电 */
	ts->reg = regulator_get(&spi->dev, "vcc");
	err = regulator_enable(ts->reg);

	/* 注册输入中断处理接口:上报按键事件 */
	err = request_threaded_irq(spi->irq, ads7846_hard_irq, ads7846_irq,
				   irq_flags, spi->dev.driver->name, ts);
	
	...
	/* 注册输入设备 */
	err = input_register_device(input_dev);

	...
	return 0;
}

static struct spi_driver ads7846_driver = {
	.driver = {
		.name	= "ads7846",
		...
		.of_match_table = of_match_ptr(ads7846_dt_ids),
	},
	.probe		= ads7846_probe,
	...
};

一个 SPI 输入设备的驱动框架已经出来了,现在还剩一点需要说明:系统是何时创建 spi_device 来触发驱动的 ads7846_probe() 接口的?答案是 SPI 控制器驱动对象注册的时候:

#define devm_spi_register_master(_dev, _ctlr) \
	devm_spi_register_controller(_dev, _ctlr)

int devm_spi_register_controller(struct device *dev,
				 struct spi_controller *ctlr)
{
	int ret;

	...

	ret = spi_register_controller(ctlr);
	...

	return ret;
}

int spi_register_controller(struct spi_controller *ctlr)
{
	...
	
	dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);
	status = device_add(&ctlr->dev);

	...

	list_add_tail(&ctlr->list, &spi_controller_list);
	list_for_each_entry(bi, &board_list, list)
		spi_match_controller_to_boardinfo(ctlr, &bi->board_info); /* 旧的 spi_register_board_info() 方式创建 spi_device */

	of_register_spi_devices(ctlr); /* DTS 方式创建 spi_device */
	acpi_register_spi_devices(ctlr); /* ACPI 方式创建 spi_device */
done:
	return status;
}

/* 旧的 spi_register_board_info() 方式创建 spi_device */
static void spi_match_controller_to_boardinfo(struct spi_controller *ctlr,
					      struct spi_board_info *bi)
{
	struct spi_device *dev;

	dev = spi_new_device(ctlr, bi);
	...
}

struct spi_device *spi_new_device(struct spi_controller *ctlr,
				  struct spi_board_info *chip)
{
	struct spi_device	*proxy;

	proxy = spi_alloc_device(ctlr); /* 创建 spi_device 对象 */
	...

	status = spi_add_device(proxy); /* 注册 spi_device 到 driver core,触发驱动 probe 接口 */
	...

	return proxy;
}

/* DTS 方式创建 spi_device */
static void of_register_spi_devices(struct spi_controller *ctlr)
{
	struct spi_device *spi;
	
	/*
	 * 扫描 DTS 中 SPI 总线上挂接的从设节点,为它们创建 spi_device。 
	 * 如前面 DTS 代码片段中的 "ti,ads7846" 。
	*/
	for_each_available_child_of_node(ctlr->dev.of_node, nc) {
		...
		spi = of_register_spi_device(ctlr, nc);
		...
	}
}

static struct spi_device *
of_register_spi_device(struct spi_controller *ctlr, struct device_node *nc)
{
	struct spi_device *spi;
	int rc;
	
	spi = spi_alloc_device(ctlr); /* 创建 spi_device 对象 */
	...

	rc = of_spi_parse_dt(ctlr, spi, nc); /* 解析 SPI 从设 DTS 配置 */

	rc = spi_add_device(spi); /* 注册 spi_device 到 driver core,触发驱动 probe 接口 */

	return spi;
}

4. SPI 用户空间接口

我们可以通过 SPI 子系统,提供的用户空间接口来操控 SPI 从设。

4.1 创建 SPI 总线用户空间字符设备节点

spi@01c68000 {
	compatible = "allwinner,sun8i-h3-spi";
	...

	/* SPI 总线用户空间设备 DTS (/dev/spidev0.0, ...) */
	spi@0 {
		compatible = "nanopi,spidev";
		...
	};
};
/* drivers/spi/spidev.c */

static const struct of_device_id spidev_dt_ids[] = {
	...
	{ .compatible = "nanopi,spidev" },
	...
	{},
};
MODULE_DEVICE_TABLE(of, spidev_dt_ids);

static int spidev_probe(struct spi_device *spi)
{
	struct spidev_data	*spidev;

	...
	minor = find_first_zero_bit(minors, N_SPI_MINORS);
	if (minor < N_SPI_MINORS) 
		struct device *dev;
	
		spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
		/* 创建 SPI 总线的字符设备节点:供用户空间访问 */
		dev = device_create(spidev_class, &spi->dev, spidev->devt,
				    spidev, "spidev%d.%d",
				    spi->master->bus_num, spi->chip_select);
	} else {
		...
	}

	...
}

static struct spi_driver spidev_spi_driver = {
	.driver = {
		.name =		"spidev",
		.of_match_table = of_match_ptr(spidev_dt_ids),
		...
	},
	.probe =	spidev_probe,
	...
};

static int __init spidev_init(void)
{
	int status;
	
	/* SPI 总线字符设备 */
	status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
	...

	spidev_class = class_create(THIS_MODULE, "spidev");
	...

	status = spi_register_driver(&spidev_spi_driver);
	...

	return status;
}
module_init(spidev_init);

4.2 操作 SPI 总线用户字符设备节点

int fd, mode = SPI_MODE_0;

fd = open("/dev/spidev1.0", O_RDWR); /* 打开 SPI 从设备 */
ret = ioctl(fd, SPI_IOC_WR_MODE, &mode); /* 设置工作模式 */
ret = ioctl(fd_spi, SPI_IOC_WR_BITS_PER_WORD, &bits); /* 设置SPI的数据位 */
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); /* 设置速度 */
ret = ioctl(fd, SPI_IOC_MESSAGE(n), &tr); /* 数据发送 */
...
close(fd);

更多细节参考 drivers/spi/spidev.c 中的 spidev_ioctl() 接口。

5. SPI 总线数据传送

到目前为止,我们还没讲述 SPI Master 总线设备是如何向从设传送数据的,接下来将对此加以说明。

5.1 建立 SPI 总线数据传送上下文

在注册 SPI 总线设备驱动对象时,SPI 驱动核心部分,为 SPI 总线对象建立了用于数据传输的 work 线程、初始化传输数据队列、以及传输状态标记 等上下文:

/* drivers/spi/spi.c */

int spi_register_controller(struct spi_controller *ctlr)
{
	...
	
	dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);
	
	...
	
	/* If we're using a queued driver, start the queue */
	if (ctlr->transfer)
		dev_info(dev, "controller is unqueued, this is deprecated\n");
	else {
		/*
		 * 建立 SPI 总线传输上下文:
		 * . 数据传输 work 内核线程
		 * . 传输数据队列
		 * . 传输状态初始化
		 */
		status = spi_controller_initialize_queue(ctlr); /* 建立 SPI 总线传输上下文 */
		...
	}
	...

	list_add_tail(&ctlr->list, &spi_controller_list);
}

static int spi_controller_initialize_queue(struct spi_controller *ctlr)
{
	int ret;
	
	ctlr->transfer = spi_queued_transfer;
	if (!ctlr->transfer_one_message)
		ctlr->transfer_one_message = spi_transfer_one_message;

	/* Initialize and start queue */
	ret = spi_init_queue(ctlr); /* 创建 SPI 总线数据传送 work 内核线程 和 work */
	...

	ctlr->queued = true;
	ret = spi_start_queue(ctlr); /* 添加 SPI 数据传送 work 到 worker,标记 SPI 传送队列已启动 */
	...

	return 0;
	
	...
}

/* 创建 SPI 总线数据传送 work 内核线程 和 work */
static int spi_init_queue(struct spi_controller *ctlr)
{
	ctlr->running = false;
	ctlr->busy = false;
 
 	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);

	...
	
	return 0;
}

/* 添加 SPI 数据传送 work 到 worker,标记 SPI 传送队列已启动 */
static int spi_start_queue(struct spi_controller *ctlr)
{
	...

	ctlr->running = true;
	ctlr->cur_msg = NULL;
 	...

	kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages); /* 添加数据传送 work 到内核线程的 worker 的 work 列表 */

	return 0;
}

到此,SPI 总线数据传送的上下文已经建立,传送内核线程也已经启动。接下来,就是看如何利用建立的 SPI 总线数据传送上下文来传输数据了。

5.2 SPI 总线数据传输过程

我们以 drivers/spi/spidev.cspidev_write() 为起点,来说明 SPI 总线的数据传输过程。SPI 数据传送发生的上下文,可能是 当前进程,也可能是 kthread_worker_fn() 代表的内核线程,到底在哪里发起,看谁先抢到 spi_controller::queue_lock 锁。先来看发生在 当前进程上下文 的传送过程。

5.2.1 SPI 数据传送速率

5.2.1.1 默认传输速率

默认情形下,SPI 从设的传输速率与配置的最大速率相同:

&spi0 {
	spidev0: spi@0 {
		compatible = "nanopi,spidev";
		reg = <0>;
		status = "okay";
		
		spi-max-frequency = <10000000>;
	};
	...
};
/* drivers/spi/spi.c */
spi_register_controller()
	of_register_spi_devices(ctlr)
		of_register_spi_device(ctlr, nc)
			struct spi_device *spi;
			
			spi = spi_alloc_device(ctlr);
			...
			rc = of_spi_parse_dt(ctlr, spi, nc);
				...
				rc = of_property_read_u32(nc, "spi-max-frequency", &value);
				spi->max_speed_hz = value; /* 设置 SPI 从设的最大频率 */
			...
			rc = spi_add_device(spi);
				status = spi_setup(spi); /* SPI 从设速率等的初始化 */
				...
				status = device_add(&spi->dev); /* 触发 SPI 从设备 probe */
				...
			...

/* drivers/spi/spidev.c */
spidev_probe()
	struct spidev_data *spidev;

	...
	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
	
	...
	mutex_lock(&device_list_lock);
	...
	list_add(&spidev->device_entry, &device_list);
	...
	mutex_unlock(&device_list_lock);

	spidev->speed_hz = spi->max_speed_hz; /* SPI 从设默认速率为配置的 max_speed_hz */
	...
5.2.1.2 传输前设定速率
/* drivers/spi/spidev.c */
spidev_ioctl()
	...
	switch (cmd) {
	...
	case SPI_IOC_WR_MAX_SPEED_HZ:
		retval = get_user(tmp, (__u32 __user *)arg);
		if (retval == 0) {
			u32 save = spi->max_speed_hz;
			
			spi->max_speed_hz = tmp;
   			retval = spi_setup(spi); /* 设定 SPI 传输速率 */
   			if (retval >= 0)
   				spidev->speed_hz = tmp;
   			else
				dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
			spi->max_speed_hz = save;
		}
	...
	}
5.2.1.3 传输时指定速率
/* 应用代码 */
struct spi_ioc_transfer tr = {
	.tx_buf = (unsigned long)wbuf,
	.rx_buf = (unsigned long)rbuf,
	.len = len,
	.speed_hz = speed, /* 指定传输速率 */
	.delay_usecs = 0,
 };
 ioctl(fd, SPI_IOC_MESSAGE(1), &tr);

/* 内核空间 */
spidev_ioctl()
	switch (cmd) {
	...
	default:
		ioc = spidev_get_ioc_message(cmd,
			(struct spi_ioc_transfer __user *)arg, &n_ioc);
		retval = spidev_message(spidev, ioc, n_ioc); /* SPI 总线驱动会对比设定的速率 和 此次传输要求的速率,不同则设置为此次传输要求的速率 */
		kfree(ioc);
		break;
	...
	}

5.2.2 由当前进程直接发起 SPI 数据传送

5.2.2.1 用户空间发起的 SPI 数据传送
int fd = open("/dev/spidev1.0", O_RDWR);
...
write(fd, data, data_len)
	...
	spidev_write()
/* drivers/spi/spidev.c */

static ssize_t
spidev_write(struct file *filp, const char __user *buf,
	size_t count, loff_t *f_pos)
{
	struct spidev_data *spidev;
	ssize_t   status = 0;
	unsigned long  missing;

	...

	spidev = filp->private_data;

	...
	missing = copy_from_user(spidev->tx_buffer, buf, count);
	if (missing == 0)
		status = spidev_sync_write(spidev, count);
	else
		status = -EFAULT;
	...
	
	return status;
}

static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len)
{
	struct spi_transfer t = {
		.tx_buf  = spidev->tx_buffer,
		.len  = len,
		.speed_hz = spidev->speed_hz,
	};
	struct spi_message m;

	spi_message_init(&m);
		memset(m, 0, sizeof *m);
		spi_message_init_no_memset(m);
	spi_message_add_tail(&t, &m);
		list_add_tail(&t->transfer_list, &m->transfers);
	return spidev_sync(spidev, &m);
		status = spi_sync(spi, message);
			__spi_sync(spi, message)
		if (status == 0)
			status = message->actual_length;
		
		return status;
}

static int __spi_sync(struct spi_device *spi, struct spi_message *message)
{
	DECLARE_COMPLETION_ONSTACK(done);
	...

	/*
	 * 设置传输完成回调 spi_comlete(): 
	 * spi_comlete() -> complete(&done) 
	 * spi_comlete() 唤醒 在函数末尾处等待传输完成的代码。
	 */
	message->complete = spi_complete;
	message->context = &done;
	message->spi = spi;

	if (ctlr->transfer == spi_queued_transfer) {
		...
		/* 添加数据传输对象 @message 到 SPI 总线对象的传输队列 */
		status = __spi_queued_transfer(spi, message, false);
		...
	} else {
		...
	}

	if (status == 0) {
		if (ctlr->transfer == spi_queued_transfer) {
			...
			__spi_pump_messages(ctlr, false); /* 发起 SPI 数据传送 */
		}
		
		wait_for_completion(&done); /* 等待传输完成 */
		status = message->status; /* 返回传输结果 */
	}
	message->context = NULL;
	return status;
}

/* 添加数据传输对象 @msg 到 SPI 总线对象的传输队列 */
static int __spi_queued_transfer(struct spi_device *spi,
		struct spi_message *msg,
		bool need_pump)
{
	struct spi_controller *ctlr = spi->controller;
	...
	
	...
	msg->actual_length = 0;
	msg->status = -EINPROGRESS;

	list_add_tail(&msg->queue, &ctlr->queue); /* 添加传输数据到队列 */
	...

	return 0;
}

/* 发起 SPI 数据传送 */
static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread)
{
	bool was_busy = false;
	
	/* Lock queue */
	/* 不管是 内核线程 发起传送,还是 当前进程 发起传送,先占数据队列锁,谁先占锁谁发起本次传送 */
	spin_lock_irqsave(&ctlr->queue_lock, flags);

	/* Make sure we are not already running a message */
	if (ctlr->cur_msg) { /* 有数据正在传送,立即返回 */
		spin_unlock_irqrestore(&ctlr->queue_lock, flags);
		return;
	}

	...
	/* Extract head of queue */
	/* 取出 SPI 总线对象传输队列头部数据 */
	ctlr->cur_msg =
		list_first_entry(&ctlr->queue, struct spi_message, queue);
	
	list_del_init(&ctlr->cur_msg->queue); /* 将数据 从 SPI 总线对象传输队列移除 */
	if (ctlr->busy)
		was_busy = true;
	else
		ctlr->busy = true; /* 标记正在传输过程中: 忙碌状态 */
	/*
	 * 并不会在整个数据传送期间占据队列锁,而是在取出队首数据后,让 ctlr->cur_msg 
	 * 指向该数据。请求发起数据传送时,需检查 ctlr->cur_msg ,以确认当前没有其它数
	 * 据在传送。这正是前面占住数据队列锁后立马执行的操作:
	 * spin_lock_irqsave(&ctlr->queue_lock, flags);
	 * if (ctlr->cur_msg) {
	 * 	spin_unlock_irqrestore(&ctlr->queue_lock, flags);
	 *	return;
	 * }
	 * 数据传送完成后,会将 ctlr->cur_msg 置为 /NULL ,参看
	 * spi_finalize_current_message() 的逻辑 。
	 */
	spin_unlock_irqrestore(&ctlr->queue_lock, flags);
	
	...

	/* DMA 传输准备工作 */
	ret = spi_map_msg(ctlr, ctlr->cur_msg);

	/* 传输数据 */
	ret = ctlr->transfer_one_message(ctlr, ctlr->cur_msg); /* spi_transfer_one_message() */
	...
}

static int spi_transfer_one_message(struct spi_controller *ctlr,
					struct spi_message *msg)
{
	...

	/* 逐条传送 数据队列 spi_message::transfers 中的 每条数据 (spi_transfer) */
	list_for_each_entry(xfer, &msg->transfers, transfe*r_list) {
		...
		if (xfer->tx_buf || xfer->rx_buf) { /* 有 发送 或 接收 数据的请求 */
			/* 调用 SPI 总线 具体硬件实现 的 数据传送接口 传送数据 */
			ret = ctlr->transfer_one(ctlr, msg->spi, xfer); /* sun6i_spi_transfer_one(), ... */
			...
		}
		...
	}

	...
	/* 数据传送完成后清理工作: DMA 传输清理,唤醒等待进程 等等 */
	spi_finalize_current_message(ctlr);

	return ret;
}

/* 数据传送完成后清理工作: DMA 传输清理,唤醒等待进程 等等 */
void spi_finalize_current_message(struct spi_controller *ctlr)
{
	...
	spi_unmap_msg(ctlr, mesg); /* DMA 传输清理工作 */

	ctlr->cur_msg = NULL; /* 标记当前没有数据在传送 */
	ctlr->cur_msg_prepared = false;
	kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);

	mesg->state = NULL;
	if (mesg->complete)
		mesg->complete(mesg->context); /* spi_complete() */
			complete(arg);
}

上面描述了 SPI 总线数据传输的公共实现部分,具体硬件相关的传输逻辑,我们以 AllWinner H3 的 SPI 总线为例,来加以说明。

/* drivers/spi/spi-sun6i.c */

static int sun6i_spi_transfer_one(struct spi_master *master,
		struct spi_device *spi,
		struct spi_transfer *tfr)
{
	...

	reinit_completion(&sspi->done);
	sspi->tx_buf = tfr->tx_buf;
	sspi->rx_buf = tfr->rx_buf;
	sspi->len = tfr->len;
	use_dma = master->can_dma ? master->can_dma(master, spi, tfr) : false; /* sun6i_spi_can_dma() */

	/* Clear pending interrupts */
	sun6i_spi_write(sspi, SUN6I_INT_STA_REG, ~0);

	/* Reset FIFO */
	sun6i_spi_write(sspi, SUN6I_FIFO_CTL_REG,
		SUN6I_FIFO_CTL_RF_RST | SUN6I_FIFO_CTL_TF_RST);

	...

	/* 设置传输时钟 */
	...

	if (!use_dma) { /* 非 DMA 传输方式 */
		/* Fill the TX FIFO */
		sun6i_spi_fill_fifo(sspi);
	} else { /* DMA 传输方式 */
		ret = sun6i_spi_prepare_dma(sspi, tfr);
	}

	/* Enable the interrupts */
	/* 使能传输完成中断 */
	...

	/* Start the transfer */
	/* 启动传输 */

	/*
	 * 等待传输完成:
	 * 传输完成会产生中断,在中断处理中,调用 complete(&sspi->done) 唤醒此处等待的代码。
	 * 详见 sun6i_spi_handler() .
	 */
	tx_time = max(tfr->len * 8 * 2 / (tfr->speed_hz / 1000), 100U); /* 超时时间设置 */
 	...
 	timeout = wait_for_completion_timeout(&sspi->done,
				msecs_to_jiffies(tx_time));
	...
	if (!timeout) { /* 传输超时 */
		...
		ret = -ETIMEDOUT;
	}

	...

	return ret;
}

/* 数据传输完成触发中断 */
static irqreturn_t sun6i_spi_handler(int irq, void *dev_id)
{
	struct sun6i_spi *sspi = dev_id;
	u32 status = sun6i_spi_read(sspi, SUN6I_INT_STA_REG); /* 读取中断状态 */

	/* Transfer complete */
	if (status & SUN6I_INT_CTL_TC) { /* 数据传送完成 */
		sun6i_spi_write(sspi, SUN6I_INT_STA_REG, SUN6I_INT_CTL_TC);
		sun6i_spi_drain_fifo(sspi);
		complete(&sspi->done); /* 唤醒等待传送完成的代码 */
		return IRQ_HANDLED;
	}

	...
}
5.2.2.1 内核驱动发起的 SPI 数据传送

内核空间驱动 SPI 数据传送,和用户流程接近,仍然是类似如下代码片段发起:

struct spi_transfer xfer;
struct spi_message msg;

xfer.tx_buf = /* 传输缓冲地址 */;
xfer.len = /* 传送数据长度 */;

spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);

status = spi_sync(spi, &msg);

5.2.3 由内核线程间接发起 SPI 数据传送

spi_register_controller()
	spi_controller_initialize_queue()
		spi_init_queue(ctlr)
			/* 创建内核 work 线程 */
			kthread_init_worker(&ctlr->kworker);
				ctlr->kworker_task = kthread_run(kthread_worker_fn, &ctlr->kworker, ...);
				kthread_init_work(&ctlr->pump_messages, spi_pump_messages);
		spi_start_queue()
			/* 添加数据传送 work 到内核线程的 worker 的 work 列表 */
			kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);

内核 SPI 数据传送 work 线程,会查询是否有数据传送的 work 请求,有则发起 SPI 数据传送工作,前提是抢到 spi_controller::queue_lock 锁(见前面对 __spi_pump_messages() 的分析):

/* kernel/kthread.c */

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

	worker->task = current;

repeat:
	set_current_state(TASK_INTERRUPTIBLE); /* mb paired w/ kthread_stop */

	...
	work = NULL;
	spin_lock_irq(&worker->lock);
	if (!list_empty(&worker->work_list)) { /* 工作队列有 work 要做 */
		/* 从工作队列取出一个 work */
		work = list_first_entry(&worker->work_list,
				struct kthread_work, node);
		list_del_init(&work->node);
	}
	worker->current_work = work;
	spin_unlock_irq(&worker->lock);
	
	if (work) { /* 调度 work 执行: 发起 SPI 数据传送 */
		__set_current_state(TASK_RUNNING);
		work->func(work); /* spi_pump_messages(), ... */
	} else if (!freezing(current)) /* 工作队列当前没有工作要做 & 也没有对内核线程的冻结请求 */
		schedule(); /* 调度出去 */

	try_to_freeze(); /* 有对进程的冻结请求,尝试冻结内核线程 */
	cond_resched(); /* 冻结苏醒后尝试重新调度 */
	goto repeat; /* 循环执行 */
}

看到了吧,内核线程会调度执行 spi_pump_messages() 来发起 SPI 数据传送:

/* drivers/spi/spi.c */

static void spi_pump_messages(struct kthread_work *work)
{
	struct spi_controller *ctlr =
		container_of(work, struct spi_controller, pump_messages);

	__spi_pump_messages(ctlr, true);
}

5.3 SPI 传输性能

SPI 在一些场景下会遭遇性能问题。譬如,在不使用 DMA 的情形下,从 SPI 从设读取 1KB 数据时,对比 TI AM335X SoCSTM32 的 SPI 控制器,从测试结果来看,速度上是 STM32 更为优胜
从示波器抓取的 AM335X 传送数据波形图上看出,每两个字节的读取,中间会有大概 10us 的间隔,1KB 会因此引入 1023 * 10us = 10 ms 的开销。我们假定 SPI 控制器工作在 24MHz ,那传送 1KB 的数据大概只需要 1024*8/24000000 = 342us, 这相对于传送字节间间隔时间引入的 10ms 开销,几乎可以忽略不记
必须明确的一点是,测试过程中对 SPI 从设发起的 1KB 读操作,是一次性提交给 SPI 控制器的,所以这个字节间的传送间隔并不是程序引入的。不清楚 AM335X 字节间的传送间隔,是不是为了提高 SPI 传输可靠性而引入的。
解决上述场景的问题,可以通过启用 AM335X SPI 传送的 DMA 来解决。启用 DMA 后,工作在 24MHzAM335X SPI 控制器,传送 1KB 的时间大概为 650us 左右,这大概是理论时间 342us 的两倍。这是合理的,为了传输的可靠性,每两个字节的传送之间,总应该插入一点间隔。

6. FAQ

SPI 常见的问题 时钟、Modex、数据字长、位序(MSB,LSB) 的配置错误,导致通信失败。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Linux 内核提供了 SPI 总线驱动框架,用于支持 SPI 设备的驱动开发。SPI 总线驱动框架提供了一组 SPI 控制器驱动,用于管理硬件 SPI 控制器,并提供了一组 SPI 设备驱动,用于管理与 SPI 总线连接的设备。 在 Linux 内核中,SPI 总线驱动框架的核心文件是 `drivers/spi/spi.c`,它定义了 `spi_master` 结构体和一组操作函数,用于管理 SPI 总线和 SPI 设备。`spi_master` 结构体包含了指向 SPI 控制器驱动SPI 设备驱动的指针,以及一些与 SPI 总线相关的参数,如传输时钟频率、传输模式等。 SPI 控制器驱动需要实现一组操作函数,包括初始化、传输数据等。SPI 设备驱动需要实现一组操作函数,包括初始化、传输数据等。在初始化 SPI 设备驱动时,需要向 SPI 控制器驱动注册一个 `spi_device` 结构体,该结构体包含了 SPI 设备的相关信息,如设备名称、传输模式等。 以下是一个简单的 SPI 设备驱动的示例代码: ``` #include <linux/module.h> #include <linux/spi/spi.h> static int spi_example_probe(struct spi_device *spi) { // 初始化 SPI 设备 printk(KERN_INFO "spi_example_probe: %s\n", spi->modalias); return 0; } static int spi_example_remove(struct spi_device *spi) { // 卸载 SPI 设备 printk(KERN_INFO "spi_example_remove: %s\n", spi->modalias); return 0; } static struct spi_device_id spi_example_id[] = { {"spi_example", 0}, {}, }; MODULE_DEVICE_TABLE(spi, spi_example_id); static struct spi_driver spi_example_driver = { .driver = { .name = "spi_example", .owner = THIS_MODULE, }, .probe = spi_example_probe, .remove = spi_example_remove, .id_table = spi_example_id, }; static int __init spi_example_init(void) { // 注册 SPI 设备驱动 int ret = spi_register_driver(&spi_example_driver); if (ret < 0) { printk(KERN_ERR "Failed to register SPI device driver\n"); } return ret; } static void __exit spi_example_exit(void) { // 注销 SPI 设备驱动 spi_unregister_driver(&spi_example_driver); } module_init(spi_example_init); module_exit(spi_example_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("SPI example driver"); ``` 在上面的代码中,`spi_example_probe` 和 `spi_example_remove` 函数分别用于初始化和卸载 SPI 设备。`spi_example_id` 定义了 SPI 设备驱动支持的设备 ID,`spi_example_driver` 定义了 SPI 设备驱动的相关信息,包括设备名称、初始化函数、卸载函数等。`spi_example_init` 和 `spi_example_exit` 函数分别用于注册和注销 SPI 设备驱动。 需要注意的是,上述代码仅为示例代码,实际开发中需要根据具体的硬件和应用场景进行修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值