linux下的spi开发与框架源码分析

devtools/2024/11/24 5:07:20/

目录

1 概述

2 spi子系统框架

3 spi硬件原理回顾

4 框架下spi的应用

4.1 在驱动中使用spi

4.1.1 使用框架与流程

4.1.2 示例分析

4.2 在应用使用spi

5 spi硬件驱动开发

6 spi子系统源码分析

6.1 子系统加载

6.2 注册controller过程

6.3数据收发过程

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.xret = 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和slavetarget_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;//支持csctlr->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,或者人为加入的csnum_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时,配置使能dmars->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_isrif (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_deviceif (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;elsectlr->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_messageret = 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工作线程从消息队列中取数据进行收发,最终调用到硬件发送接口。


http://www.ppmy.cn/devtools/136462.html

相关文章

有关django、python版本、sqlite3版本冲突问题

本篇是解析为什么会出现python版本使用旧版本的sqlite3版本的问题&#xff0c;解决办法在下面有备注&#xff0c;如有遗漏之处或错误&#xff0c;望佬们指出&#xff0c;再次感谢不禁~~ 【坑点】更新python版本&#xff0c;并不会让sqlite版本实时更新&#xff0c;依旧是调用首…

微软Visual C++ 2015运行时库:轻松运行C++应用程序的必备工具

微软Visual C 2015运行时库&#xff1a;轻松运行C应用程序的必备工具 【下载地址】MicrosoftVisualC2015Redistributablex64x86安装包下载 本仓库提供Microsoft Visual C 2015 Redistributable x64/x86安装包的下载。该安装包包含了Visual C库的运行时组件&#xff0c;这些组件…

麒麟部署一套mysql集群,使用ansible批量部署可以提高工作效率

一、 服务端和客户端同时配置kylin镜像 配置麒麟的yum源 rm -rf /etc/yum.repos.d/CentOS-Base.repo vim /etc/yum.repos.d/Kylin_aarch64.repo Copy 写入如下yum源 [ks10-adv-os] name = Kylin Linux Advanced Server 10 - Os baseurl = http://update.cs2c.com.cn:8080/…

wpf 事件转命令的方式

1&#xff0c;方式1 <StackPanel Background"Transparent"><StackPanel.InputBindings><KeyBinding Command"{Binding ChangeColorCommand}"CommandParameter"{Binding ElementNamecolorPicker, PathSelectedItem}"Key"{Bi…

40分钟学 Go 语言高并发:sync包详解(下)

sync包详解&#xff08;下&#xff09; 学习目标 知识点掌握程度应用场景WaitGroup使用熟练使用和理解原理并发任务的同步等待Once实现原理理解底层实现和使用场景单例模式、一次性初始化Pool性能优化掌握对象池的使用和调优高并发下的内存优化Cond应用场景了解条件变量的使用…

npm/cnpm的使用

npm 1、安装npm 前往nodejs官网下载安装node 验证是否安装成功node node -v node安装npm也会安装 npm -v 2、使用npm 1. 初始化项目 在一个项目文件夹中运行&#xff1a; npm init 根据提示输入项目信息&#xff08;如项目名称、版本号等&#xff09;。 如果你希望快速初…

Linux下Intel编译器oneAPI安装和链接MKL库编译

参考: https://blog.csdn.net/qq_44263574/article/details/123582481 官网下载: https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-toolkit-download.html?packagesoneapi-toolkit&oneapi-toolkit-oslinux&oneapi-linoffline 填写邮件和国家,…

基于spring boot扶贫助农系统设计与实现

摘 要 扶贫助农系统是一种旨在改善农村贫困地区经济发展和居民生活水平的综合信息化平台。该系统通过整合资源、提供信息服务和优化供应链管理&#xff0c;帮助农民增加收入并提升农业生产效率。系统功能包括农产品在线销售、扶贫资讯等等功能。用户界面友好&#xff0c;操作简…