基于PL022 SPI 控制器 海思3516系列芯片SPI速率慢问题深入分析与优化( 二 )

spi_write_XXbyte这几个函数,这几个函数内都有这样的代码
spi_enable();ssp_writew(SSP_DR,spi_data);ret = hi_spi_check_timeout();if(ret != 0){printk("spi_send timeout\n");}spi_disable();在前面的代码中设置过CS片选信号由spi使能信号控制,也就是说 , 这段代码每次写一个字节都要拉一次CS信号 , 效率比较低 , 我将 spi_enable 和 spi_disable 提出来后再次进行测试,速度确实有一定提升,但提升幅度仍然不大,只有大概几百ns的水平,优化效果仍然不理想 。
这段代码只剩下两个函数了,ssp_writew 是写寄存器,根本不能优化,只能想办法优化 hi_spi_check_timeout,来看一下这个函数的实现
static int hi_spi_check_timeout(void){unsigned int value =https://www.huyubaike.com/biancheng/0;unsigned int tmp = 0;while (1){ssp_readw(SSP_SR,value);if ((value & SPI_SR_TFE) && (!(value & SPI_SR_BSY))){break;}if (tmp++ > MAX_WAIT){printk("spi transfer wait timeout!\n");return -1;}udelay(1);}return 0;}逻辑非常简单,不停地读取发送FIFO空和SPI忙的标志位,延时1us继续读,直到发送完成且SPI空闲 。看到这个 udelay(1) 你现在的想法肯定和我当时的想法一样:第一次读取发现寄存器没有置位 , 延时1us,第二次读取寄存器置位,退出循环 , 现在的发送间隔是1.6us,去掉这个延时尽快读取寄存器,应该直接能优化到0.6us以内 。
想得美!实际测试去掉这个 udelay(1) 以后优化效果确实挺明显的,延时直接缩短到 1.2us 左右,但是这个延时还是太长了 。
现在再来看这个函数,就剩一个读寄存器了,为了保证可靠传输,读标志位肯定不能去掉 , 这已经最简单了,还能怎么优化呢?我们重新梳理一下:去掉 udelay(1) 后间隔缩短到 1.2us 左右,说明这里循环读了很多次寄存器,不然怎么还有这么长的延时?那要不就加打印看看这里到底循环读取了几次?来来来,竞猜一下这里到底循环了几次?十次以内?百次以内?还是千次以内?答案是一次!没错只有一次!
1次这个答案可以说即在意料之外也在意料之中 。说它在意料之外是因为读写寄存器这种操作是非常快的,一般而言几个cpu时钟就能完成,但这里读一个寄存器却花费了 1.2us 。说它在意料之内是因为这些物理寄存器都是通过硬件置位的,以发送 FIFO 为空标志为例 , 当 SPI 发送完成瞬间它就会被硬件置位,这一点在任何一款单片机上都可以验证,实际操作一下就会发现类似的寄存器是瞬间被置位的 。pl022 应该是非常成熟的 SPI 控制器,我觉得芯片设计人员不会范发送 FIFO 为空后很长时间才设置标志位这种低级错误 。
接下赖重点思考这个问题:为什么 ssp_readw(SSP_SR,value) 这样一个简单的读寄存器操作要 1.2us 之久?(目前我测试 ssp_writew 写一个寄存器大概在 100ns 以内 , ssp_readw 读一个寄存器大概在 1us 左右)这个问题无论是百度还是谷歌我找了很久都没有找到确切答案 , 如果有知道的大佬非常欢迎指导?。。〔话祖沃?,私信发红包 。下面是我个人的推测,虽然是推测,但我觉得这就是正确答案 , 仅供参考:
linux 不同于单片机裸机程序那样可以直接访问寄存器,如果需要读写物理寄存器,在内核态使用 ioremap (在用户态使用 mmap)将一段物理地址映射到内核态(或用户态)的虚拟地址空间 , 再对映射后的地址进行读写 。
来看一下实际读写寄存器的这两个接口,很简单就是直接读写某个地址处的数据(前提是这个地址必须经过映射) 。
#definessp_readw(addr,ret)(ret =(*(volatile unsigned int *)(addr)))#definessp_writew(addr,value)((*(volatile unsigned int *)(addr)) = (value))首先可以明确一点,读写寄存器的操作必然会经过MMU 。对于写寄存器来说,不需要考虑同步、脏数据等问题 , MMU 应该是直接将这个数据写到物理地址了 。对于读寄存器来说,有 volatile 关键字的存在,这里的代码不会去优化,每次读取必须从物理地址进行读取 , 这里可能需要 cache 回写等操作导致导致读取的速度非常慢 。
在 u-boot 下可以直接读写物理寄存器,应该不需要这么久,几个CLK就可以完成吧?这一点我没有验证过,有测试过的朋友欢迎分享 。
总之 , 耗时的地方找到了,想办法优化吧 。我这里有两种优化思路:

  1. 查阅手册可知,SPI的内部收发部分各有一个宽度 16bit、深度为 256 的 FIFO 。我可以一次性写入不超过256字节的数据 , 然后使用 ssp_readw 不停地读取寄存器,直到发送 FIFO 为空 。在50MHz时钟的条件下情况下 ssp_readw 的 1.2us 延时相当于发送了8字节左右,理论上来说如果发送的数据量大于8字节 , 这个 1.2us 延时等于没有 。

    推荐阅读