我有一块开发板,LS1028ARDB。我需要对 LS1028 的 FlexSPI 与我的自定义设备之间的通信进行一些概念验证。
所以连接如下:
LS1028ARDB <--- QSPI port (managed by FlexSPI) <----> My device.
问题在于开发板暴露了具有 1.8V 电压引脚的 QSPI 端口,而我的目标设备在 3.3V 下运行。我想到了可以使用电压切换器的想法。问题是它们大多很慢,这似乎在我的情况下,所以通信不可靠 - 这对我来说很重要 - 我的测试不需要快速通信 - 我需要可靠的通信 - 一般来说较低的 SPI频率越高越好。
问题是我无法将 SPI 时钟重新配置为低于 18Mhz,这在我使用电压切换器的情况下相当快。我通过设备树中的 spi-max-Frequency 属性管理 SPI 频率。我可以将时钟设置为例如。 50Mhz 并且此更改应用正确,但任何其他低值(如 10Mhz、5Mhz、1Mhz 等)都会导致时钟设置为 18Mhz 左右
下面我发布了一些代码,但请记住,所有时钟设置、时钟配置等都是由我没有接触过的主线驱动程序管理的。我所做的唯一更改是设备树中的一个附加节点和一个触发 SPI 操作的非常小的驱动程序。 我在 /sys/kernel/debug/clk/fspi_clk/clk_rate 中验证的时钟速度。
如果我将 spi-max-Frequency 设置为 50Mhz 那么 /sys/kernel/debug/clk/fspi_clk/clk_rate 显示:大约 48Mhz
如果我将 spi-max-Frequency 设置为 10/5/1Mhz,则 /sys/kernel/debug/clk/fspi_clk/clk_rate 显示:大约 18Mhz。
主线 fsl-ls-1028a.dtsi fspi 节点:
fspi: spi@20c0000 {
compatible = "nxp,lx2160a-fspi";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x0 0x20c0000 0x0 0x10000>,
<0x0 0x20000000 0x0 0x10000000>;
reg-names = "fspi_base", "fspi_mmap";
interrupts = <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&fspi_clk>, <&fspi_clk>;
clock-names = "fspi_en", "fspi";
status = "disabled";
};
主线 fsl-ls-1028a-rdb.dts - 我仅显示我所做的修改:
&fspi {
status = "okay";
pbspimem: pbspimem@0 {
compatible = "pb,pb-spi-mem";
#address-cells = <1>;
#size-cells = <1>;
spi-max-frequency = <5000000>;
spi-rx-bus-width = <1>; /* 1 SPI Rx lines */
spi-tx-bus-width = <1>; /* 1 SPI Tx line */
reg = <0>;
};
};
我非常简单的驱动程序:
#include "linux/types.h"
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/spi/spi-mem.h>
#include <linux/dma-mapping.h>
static int pb_spi_mem_probe(struct spi_mem *spimem)
{
int ret;
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
struct spi_mem_op op = SPI_MEM_OP(
SPI_MEM_OP_CMD(0x80, 1),
SPI_MEM_OP_ADDR(3, 0x000004, 1),
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_IN(4, buf, 1)
);
ret = spi_mem_exec_op(spimem, &op);
if (ret) {
pr_info("Cannot execute op\n");
}
return ret;
}
static int pb_spi_mem_remove(struct spi_mem *spimem)
{
pr_info("%s\n", __func__);
return 0;
}
static const struct spi_device_id spi_nor_dev_ids[] = {
{"pb-spi-mem"},
{ } /* sentinel */
};
MODULE_DEVICE_TABLE(spi, spi_nor_dev_ids);
static const struct of_device_id pb_spi_mem_of_table[] = {
{ .compatible = "pb,pb-spi-mem" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, pb_spi_mem_of_table);
static struct spi_mem_driver pb_spi_mem_driver = {
.spidrv = {
.driver = {
.name = "pb-spi-mem-namer",
.of_match_table = pb_spi_mem_of_table
},
.id_table = spi_nor_dev_ids
},
.probe = pb_spi_mem_probe,
.remove = pb_spi_mem_remove
};
module_spi_mem_driver(pb_spi_mem_driver);
MODULE_LICENSE("GPL");
如您所见,它触发了 spi mem 操作。如果您不知道那是什么 - 没关系 - 相信我,在幕后这个调用最终会在 nxp_fspi_exec_op 中结束,它是 FlexSPI 控制器的驱动程序。
控制器驱动程序中发生了很多事情,因此发布所有驱动程序源代码是没有用的,所以我只提取了相关部分 - 我跟踪了调用堆栈,我很确定调用堆栈到达了这部分:
static void nxp_fspi_select_mem(struct nxp_fspi *f, struct spi_device *spi)
{
unsigned long rate = spi->max_speed_hz;
int ret;
uint64_t size_kb;
[...]
nxp_fspi_clk_disable_unprep(f);
ret = clk_set_rate(f->clk, rate);
if (ret)
return;
ret = nxp_fspi_clk_prep_enable(f);
if (ret)
return;
f->selected = spi->chip_select;
}
static int nxp_fspi_clk_prep_enable(struct nxp_fspi *f)
{
int ret;
if (is_acpi_node(f->dev->fwnode))
return 0;
ret = clk_prepare_enable(f->clk_en);
if (ret)
return ret;
ret = clk_prepare_enable(f->clk);
if (ret) {
clk_disable_unprepare(f->clk_en);
return ret;
}
return 0;
}
正如你所看到的,有一个对 clk_set_rate(f->clk,rate) 的调用,它实际上是有效的,因为如果我将时钟 spi-最大频率设置为 50Mhz,它会改变时钟。它也适用于其他值,但不允许设置低于 18Mhz 的频率。当然所有操作都以成功结束,没有错误等等。
所以我正在尝试解决这个问题,并找出如何设置为低于 18Mhz 的值,这实际上是我测试所需要的。
其实我已经知道如何解决这个问题了。 首先 - 在当前设置中 - 无法低于 18Mhz。 为什么? 我们看图:
如您所见,FlexSPI 与具有 7 个输入的 MUX1 连接。输入选择由 RCW 设置。
在当前设置中,HWA_CGA_M1_CLK_SEL 设置为 1。 医生对这个值有什么看法?
0b001 异步模式——集群 A 组 PLL 1 /1 是时钟
所以我们知道,通过多路复用器,我们选择 CGA_PLL1:1,它不会划分 PLL。 让我们继续。 SysClk 等于 100Mhz。根据代码CGA_PLL1_RAT设置为15。 100 * 15 -> 1500Mhz。
我们可以在 Linux 上查看 /sys/kernel/debug/clk/clk_summary 来确认:
cg-pll1-div1 0 0 0 1500000000 0 0 50000 Y
好吧,让我们继续吧。 FlexSPi本身具有分频器功能,以便我们可以配置其时钟。除法器的最大值是多少? 80.
简单的数学计算表明:1500 / 80 = 18,75Mhz,这正是 Linux 对我的 FlexSPI 时钟当前设置的描述。
所以我的解决方案是重新配置 RCW 以使用:Cluster Group A PLL 1 /4 是时钟