目录
[1 概述](#1 概述)
[2 spi子系统框架](#2 spi子系统框架)
[3 spi硬件原理回顾](#3 spi硬件原理回顾)
[4 框架下spi的应用](#4 框架下spi的应用)
[4.1 在驱动中使用spi](#4.1 在驱动中使用spi)
[4.1.1 使用框架与流程](#4.1.1 使用框架与流程)
[4.1.2 示例分析](#4.1.2 示例分析)
[4.2 在应用使用spi](#4.2 在应用使用spi)
[5 spi硬件驱动开发](#5 spi硬件驱动开发)
[6 spi子系统源码分析](#6 spi子系统源码分析)
[6.1 子系统加载](#6.1 子系统加载)
[6.2 注册controller过程](#6.2 注册controller过程)
[6.4 数据关系](#6.4 数据关系)
1 概述
本书主要描述spi的框架,应用层使用、驱动层对spi子系统的使用、spi子系统框架和增加spi硬件适配驱动的方法以及源码分析,建立在读者具有理解spi基本工作原理和一定的Linux基础。
2 spi子系统框架
利用Linux下的spi子系统进行spi通信,spi在Linux的应用框架如下框图,其中spi子系统主要由spi设备驱动层、spi核心层和spi硬件驱动层组成。
应用可使用设备驱动提供的接口进行spi通信,也可以使用spi子系统生成的/dev/spidevx.x设备进行直接通信,spi子系统向下提供了controller的注册接口,支持多个controller;驱动开发者在注册spi driver同时,spi子系统也为该driver生成匹配的device,在spi bus对两者匹配后回调设备驱动probe进行设备初始化等。
3 spi硬件原理回顾
SPI 是英语Serial Peripheral interface的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。
SPI是全双工且SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps
SPI接口一般使用四条信号线通信:SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)
MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
SCLK:串行时钟信号,由主设备产生。
CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为"片选引脚",也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。
SPI通信的四种模式
SPI的四种模式,简单地讲就是设置SCLK时钟信号线的那种信号为有效信号
4 框架下spi的应用
4.1 在驱动中使用spi
4.1.1 使用框架与流程
使用在dts的spi节点挂载spi设备的方式来利用spi框架进行spi通信,一般流程如下,先定义好struct spi_driver结构体,调用spi框架提供的spi_register_driver接口注册spi_driver,spi子系统中的bus匹配到device后,driver的probe调用,此时即可用spi子系统提供的收发接口进行数据收发。
驱动中应用的框架如下,其中spi硬件驱动向spi子系统注册了spi controller,间接提供上层硬件发送接收接口。
4.1.2 示例分析
以eeprom的驱动为例子,eeprom源码drivers/misc/eeprom/at25.c,在设备树的定义如下:
&spi0 {
status = "okay";
num-cs = <4>;
is-decoded-cs = <0>;
eeprom: eeprom@2 {
compatible = "atmel,at25";
reg = <2>;
spi-max-frequency = <1000000>;
size = <8192>;
address-width = <16>;
pagesize = <32>;
};
};
在驱动里,直接调用module_spi_driver注册spi driver
static struct spi_driver at25_driver = {
.driver = {
.name = "at25",
.of_match_table = at25_of_match,
.dev_groups = sernum_groups,
},
.probe = at25_probe,
.id_table = at25_spi_ids,
};
module_spi_driver(at25_driver);
spi子系统调用probe函数后,进行eeprom设备自己的初始化逻辑。
static int at25_probe(struct spi_device *spi)
{
...
}
此后,即可通过spi提供的接口进行读写
/* Read extra registers as ID or serial number */
static int fm25_aux_read(struct at25_data *at25, u8 *buf, uint8_t command, int len)
{
int status;
struct spi_transfer t[2];
struct spi_message m;
spi_message_init(&m);
memset(t, 0, sizeof(t));
t[0].tx_buf = at25->command;
t[0].len = 1;
spi_message_add_tail(&t[0], &m);
t[1].rx_buf = buf;
t[1].len = len;
spi_message_add_tail(&t[1], &m);
mutex_lock(&at25->lock);
at25->command[0] = command;
status = spi_sync(at25->spi, &m);
...
return status;
}
就该函数的应用来说,收发之前,添加一个message并将消息内容填充到transfer里,transfer被添加在message后,调用同步发送函数spi_sync发送,同时接收返回①。
当然spi子系统也提供了异步发送函数
int spi_async(struct spi_device *spi, struct spi_message *message)
以及封装了上述步骤的读写接口
spi_write(struct spi_device *spi, const void *buf, size_t len)
① 同时收发需要分别配置rx_buf和tx_buf,原理后面源码章节分析
4.2 在应用使用spi
spi子系统在初始化时,也向系统注册了设备,用户可直接打开/dev/spi-x下的设备进行spi通信,参考内核代码tools/spi/spidev_test.c。
int main(int argc, char *argv[])
{
...
fd = open(device, O_RDWR);//打开/dev/spi-x或者/dev/spidevx.x
ret = ioctl(fd, SPI_IOC_WR_MODE32, &mode);//设置模式
ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);//设置位宽
ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);//读位宽
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);//设置最大频率
...
}
static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
{
int ret;
int out_fd;
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = len,
.delay_usecs = delay,
.speed_hz = speed,
.bits_per_word = bits,
};
ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);//读写消息
...
}
5 spi硬件驱动开发
spi bsp层开发,为一个新平台适配spi框架,使上层能用spi子系统接口访问芯片spi这个ip。
如下为注册一个spi controller简化流程,其中初始spi controller是注册一个controller必须的实现,后续spi子系统将检查配置的硬件属性以及回调,上层调用时将回调由controller提供的硬件回调函数,实现对硬件的操作。
以rockchip的spi bsp实现为例子,源码实现在drivers/spi/spi-rockchip.c。
static int rockchip_spi_probe(struct platform_device *pdev)
{...
//根据设备树spi节点声明的spi-slave标签分类为master和slave
target_mode = of_property_read_bool(np, "spi-slave");
if (target_mode)
//spi子系统提供的slave controller内存申请接口,sizeof则是驱动私有数据,spi子系统会为驱动再申请该sizeof大小的内存作为privdata,被dev指向拥有,下同
ctlr = spi_alloc_target(&pdev->dev,sizeof(struct rockchip_spi));
else
//申请master controller内存空间并初始化
ctlr = spi_alloc_host(&pdev->dev,sizeof(struct rockchip_spi));
//上述申请到的内存空间,比dev privdata指向
rs = spi_controller_get_devdata(ctlr);
...//硬件资源初始化(clk irq等)
ret = devm_request_threaded_irq(&pdev->dev, ret, rockchip_spi_isr, NULL,
IRQF_ONESHOT, dev_name(&pdev->dev), ctlr);
ctlr->bus_num = pdev->id;//重设备树spix中读出取出来的x值
ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP | SPI_LSB_FIRST;//spi的模式配置
if (target_mode) {//slave模式
ctlr->mode_bits |= SPI_NO_CS;//没有片选
ctlr->target_abort = rockchip_spi_target_abort;//中断spi
} else {
ctlr->flags = SPI_CONTROLLER_GPIO_SS;//支持cs
ctlr->max_native_cs = ROCKCHIP_SPI_MAX_CS_NUM;//cs最大数量
/*
* rk spi0 has two native cs, spi1..5 one cs only
* if num-cs is missing in the dts, default to 1
*/
if (of_property_read_u32(np, "num-cs", &num_cs))//有些ip支持多个cs,或者人为加入的cs
num_cs = 1;
ctlr->num_chipselect = num_cs;//配置片选数量
ctlr->use_gpio_descriptors = true;//不是ip管控的cs,用的是普通io配置,
}
ctlr->dev.of_node = pdev->dev.of_node;
ctlr->bits_per_word_mask = SPI_BPW_MASK(16) | SPI_BPW_MASK(8) | SPI_BPW_MASK(4);//spi 位宽
ctlr->min_speed_hz = rs->freq / BAUDR_SCKDV_MAX;//最大频率
ctlr->max_speed_hz = min(rs->freq / BAUDR_SCKDV_MIN, MAX_SCLK_OUT);//最小频率
ctlr->setup = rockchip_spi_setup;//spi ip使能,配置,发送接收初始化被调用
ctlr->set_cs = rockchip_spi_set_cs;//设置片选回调
ctlr->transfer_one = rockchip_spi_transfer_one;//收发数据回调
ctlr->max_transfer_size = rockchip_spi_max_transfer_size;//硬件支持的单次收发数据量
ctlr->handle_err = rockchip_spi_handle_err;//错误处理回调
...
ret = devm_spi_register_controller(&pdev->dev, ctlr);//注册spi controller
...
}
static struct platform_driver rockchip_spi_driver = {
.driver = {
.name = DRIVER_NAME,
.pm = &rockchip_spi_pm,
.of_match_table = of_match_ptr(rockchip_spi_dt_match),
},
.probe = rockchip_spi_probe,
.remove_new = rockchip_spi_remove,
};
module_platform_driver(rockchip_spi_driver);
注册一个spi controller的时,spi子系统也为挂在该spi总线下的设备生成device,以匹配驱动层调用注册一个spi driver。
其中比较重要的是硬件收发函数,上层每次调用spi子系统收发接口时,都会最终回调到该硬件接口。
rockchip_spi_transfer_one
=>rockchip_spi_config
spi ip硬件寄存器配置
=>rockchip_spi_prepare_dma
支持dma时,配置使能dma
rs->tx = xfer->tx_buf;//将spi子系统发送缓冲区记录到驱动私有tx中
rs->rx = xfer->rx_buf;//同理
=>rockchip_spi_prepare_irq
spi支持中断时,配置中断接收发送
rs->tx = xfer->tx_buf;//将spi子系统发送缓冲区记录到驱动私有tx中
rs->rx = xfer->rx_buf;//同理
数据的收发反馈在中断里
rockchip_spi_isr
if (rs->tx_left)//发送结束中断,如果还有剩下数据没发送,继续发
rockchip_spi_pio_writer(rs);
=>rockchip_spi_pio_reader(rs);
rs->rx_left = rx_left;
for (; words; words--) {
u32 rxw = readl_relaxed(rs->regs + ROCKCHIP_SPI_RXDR);//驱动接收的数据
...
rs->rx += rs->n_bytes;//将收到的数据放到rx中,即上述发送回调里的rx_buf里
}
6 spi子系统源码分析
spi子系统源码实现在Linux内核drivers/spi下。
6.1 子系统加载
spi子系统的初始化加载在drivers/spi/spi.c核心代码里,注册了一个总线
static int __init spi_init(void)
{
int status;
buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
status = bus_register(&spi_bus_type);
status = class_register(&spi_master_class);
if (IS_ENABLED(CONFIG_SPI_SLAVE)) {
status = class_register(&spi_slave_class);
if (status < 0)
goto err3;
}
if (IS_ENABLED(CONFIG_OF_DYNAMIC))
WARN_ON(of_reconfig_notifier_register(&spi_of_notifier));
...
return status;
}
postcore_initcall(spi_init);
static int spi_probe(struct device *dev)
{
const struct spi_driver *sdrv = to_spi_driver(dev->driver);
struct spi_device *spi = to_spi_device(dev);
...
if (sdrv->probe) {
ret = sdrv->probe(spi);//调用driver的probe,同时传入匹配的spi_device
if (ret)
dev_pm_domain_detach(dev, true);
}
return ret;
}
6.2 注册controller过程
注册controller前,使用spi子系统提供的接口分配controller内容
spi_alloc_slave / spi_alloc_host
=>__spi_alloc_controller
struct spi_controller *ctlr;
size_t ctlr_size = ALIGN(sizeof(*ctlr), dma_get_cache_alignment());
ctlr = kzalloc(size + ctlr_size, GFP_KERNEL);//分配多出的ctlr_size是驱动私有数据
...
ctlr->slave = slave;//true:从机, flase: 主机
if (IS_ENABLED(CONFIG_SPI_SLAVE) && slave)
ctlr->dev.class = &spi_slave_class;
else
ctlr->dev.class = &spi_master_class;
...
spi_controller_set_devdata(ctlr, (void *)ctlr + ctlr_size);
==>dev_set_drvdata //dev和驱动数据关联,dev->priv = data
注册
devm_spi_register_controller(drivers/spi/spi.c)
=>spi_register_controller
==>spi_controller_check_ops
检查回调合法性
==>spi_controller_initialize_queue
这里将初始化一个工作线程,用来提供用户异步发送过程使用
ctlr->transfer = spi_queued_transfer;//初始化transfer回调,后面异步发送调用接口
if (!ctlr->transfer_one_message)//如果用没有设置硬件发送接口,默认配置一个
ctlr->transfer_one_message = spi_transfer_one_message;
ret = spi_init_queue(ctlr);//初始化工作队列
===>spi_init_queue
ctlr->kworker = kthread_create_worker(0, dev_name(&ctlr->dev));//创建工作线程
kthread_init_work(&ctlr->pump_messages, spi_pump_messages);//设置工作线程的处理函数,这里是处理收发数据
|=>spi_pump_messages
|==>__spi_pump_messages
msg = list_first_entry(&ctlr->queue, struct spi_message, queue);//从消息队列中取出消息用于发送
__spi_pump_transfer_message发送数据
kthread_queue_work(ctlr->kworker, &ctlr->pump_messages);//重新启动线程检查是否有消息,没有则休眠
|===>__spi_pump_transfer_message
ret = ctlr->transfer_one_message(ctlr, msg);//调用硬件接口发送消息
==>list_add_tail(&ctlr->list, &spi_controller_list);//新申请的controller加入spi_controller_list链表
==>of_register_spi_devices
spi = spi_alloc_device(ctlr);//申请生成一个struct spi_device,并做赋值初始化
rc = of_spi_parse_dt(ctlr, spi, nc);//取设备信息
===>spi_add_device
====>__spi_add_device
status = spi_setup(spi);//初始化spi硬件控制器
status = device_add(&spi->dev);//添加一个device,该device与后面绑定该设备树的driver匹配
6.3数据收发过程
spi子系统收发数据分出了同步和异步接口,先看同步接口
spi_message_init(&m);
INIT_LIST_HEAD(&m->transfers);//初始链表头
INIT_LIST_HEAD(&m->resources);
static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
{
list_add_tail(&t->transfer_list, &m->transfers);//将tranfer加入message的链表中
}
--------------------------------------
spi_sync
=>__spi_sync
==>__spi_validate
校验和配置硬件信息
==>__spi_transfer_message_noqueue
===>__spi_pump_transfer_message
//最大发送包长度,当前包超长时分包
ret = spi_split_transfers_maxsize(ctlr, msg,spi_max_transfer_size(msg->spi),GFP_KERNEL | GFP_DMA);
ret = ctlr->transfer_one_message(ctlr, msg);//调用硬件回调接口收发数据
wait_for_completion(&ctlr->cur_msg_completion);//等待数据发送完成
------------------------------------------
spi_async
=>__spi_async
=>ctlr->transfer(spi, message);该回调注册controller时赋值
==>spi_queued_transfer
===>__spi_queued_transfer
list_add_tail(&msg->queue, &ctlr->queue);//将上层设置下来的message,放到controller的message队列
kthread_queue_work(ctlr->kworker, &ctlr->pump_messages);//启动工作线程手法数据(初始化过程分析了工作线程)
上层调用了发送接口后,最终调用到了硬件接口rockchip_spi_transfer_one,对于硬件接口如何处理上层发来的transfer中的tx_buf和rx_buf,spi硬件驱动已分析。
6.4 数据关系
下图大致总结了几个数据结构之间的关系,其中最上层struct spi_device下链接这所有的数据,每个device都绑定了各自的struct spi_controller,controller下维护了一个struct spi_message消息队列,该消息队列是由struct spi_transfer组成,用户使用同步发送时,则是使用自己创建的message进行发送,如果用的是异步发送时,spi子系统将把用户的message加入到struct spi_controller维护的message消息队列,并通知其维护work工作线程从消息队列中取数据进行收发,最终调用到硬件发送接口。