赞
踩
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
SPI 总线
是 Master 设备
和 Slave 从设
通信的接口,是一种高速、全双工
的同步串行通信总线。简单看一下 SPI 总线的拓扑结构:
其中:
SCLK: SPI 总线时钟,4MHz-20MHz;
MOSI: 数据线,从Master传送数据到Slave;
MISO: 数据线,从Slave传送数据到Master;
SSx: Slave片选信号,可能标记成CS更常见。
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 是全双工的,读写可以同时进行。
编写 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, ... }; };
以 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; }
我们可以通过 SPI 子系统,提供的用户空间接口来操控 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);
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()
接口。
到目前为止,我们还没讲述 SPI Master 总线
设备是如何向从设
传送数据的,接下来将对此加以说明。
在注册 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 总线数据传送上下文来传输数据了。
我们以 drivers/spi/spidev.c
的 spidev_write()
为起点,来说明 SPI 总线的数据传输过程。SPI 数据传送发生的上下文
,可能是 当前进程
,也可能是 kthread_worker_fn() 代表的内核线程
,到底在哪里发起,看谁先抢到 spi_controller::queue_lock
锁。先来看发生在 当前进程上下文
的传送过程。
默认情形下,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 */ ...
/* 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; } ... }
/* 应用代码 */ 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; ... }
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; } ... }
内核空间驱动 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);
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);
}
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 的两倍
。这是合理的,为了传输的可靠性,每两个字节的传送之间,总应该插入一点间隔。
SPI 常见的问题 时钟、Modex、数据字长、位序(MSB,LSB)
的配置错误,导致通信失败。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。