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种工作模式。先看下 CPOL
和 CPHA
的作用:
1. CPOL=0,串行时钟空闲状态为低电平。
2. CPOL=1,串行时钟空闲状态为高电平,此时可以通过配置时钟相位(CPHA)来选择具体的传输协议。
3. CPHA=0,串行时钟的第1个跳变沿(上升沿或下降沿)采集数据。
4. CPHA=1,串行时钟的第2个跳变沿(上升沿或下降沿)采集数据。
再看由 CPOL
和 CPHA
搭配的模式:
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.c
的 spidev_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 SoC
和 STM32
的 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
后,工作在 24MHz
的 AM335X SPI 控制器
,传送 1KB
的时间大概为 650us
左右,这大概是理论时间 342us 的两倍
。这是合理的,为了传输的可靠性,每两个字节的传送之间,总应该插入一点间隔。
6. FAQ
SPI 常见的问题 时钟、Modex、数据字长、位序(MSB,LSB)
的配置错误,导致通信失败。