I have a development board which is LS1028ARDB. I need to do some proof of concept regarding the communication between LS1028's FlexSPI with my custom device.
So the connection is as follows:
LS1028ARDB <--- QSPI port (managed by FlexSPI) <----> My device.
The problem is that the development board exposes QSPI port with 1.8V voltage pins where my target device operates on 3.3V. I came up with the idea that I could use a voltage switcher. The problem is that they are mostly slow, which seems to be in my case, so the communication is not reliable - and that's important to me - I do not need fast communication for my tests - I need reliable communication - in general the lower SPI freq the better.
The problem is that I'm not able to reconfigure SPI clock to go below 18Mhz which is pretty fast in the case I use voltage switchers. I manage SPI frequency via spi-max-frequency property in the device tree. I can set the clock to eg. 50Mhz and this change is applied correctly, but any other low-value like 10Mhz, 5Mhz, 1Mhz, and so on results in clock set to around 18Mhz
Below I post some code but bear in mind that all clock setup, clock configuration, and so on are managed by mainlined drivers which I haven't touched. The only changes I made are an additional node in the device tree and a very tiny driver that triggers an operation on SPI. The clock speed I verified in /sys/kernel/debug/clk/fspi_clk/clk_rate.
If I set spi-max-frequency to 50Mhz then /sys/kernel/debug/clk/fspi_clk/clk_rate shows: around 48Mhz
If I set spi-max-frequency to 10/5/1Mhz then /sys/kernel/debug/clk/fspi_clk/clk_rate shows: around 18Mhz.
Mainline fsl-ls-1028a.dtsi fspi node:
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";
};
Mainline fsl-ls-1028a-rdb.dts - I show only the modification I did:
&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>;
};
};
My very simple driver:
#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");
As you can see it triggers spi mem operation. If you don't know what that is - that's fine - trust me, under the hood this call ends up in nxp_fspi_exec_op which is driver for FlexSPI controller.
There are a lot of things happening in the controller driver so there is no use posting all the driver source code so I extracted only relevant parts - I tracked down the call stack and I'm pretty sure that the call stack reaches this part:
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;
}
So as you can see there is a call to clk_set_rate(f->clk, rate) which in fact works because it changes the clock if I set clock spi-max-frequency to 50Mhz. It also works for other values but does not allow to set lower frequency than 18Mhz. Of course all operations end with success, there are no errors on so on.
So I'm trying to wrap my head around it and figure out how to set to a lower value than 18Mhz which in fact I need for my tests.
Actually I have an idea of how to solve it. First - in the current setup - there is no way to go lower than 18Mhz. Why? Let's look at the picture:
As you can see FlexSPI is connected with MUX1 which has 7 inputs. The input selection is set by RCW.
In the current setup, HWA_CGA_M1_CLK_SEL is set to 1. What does the doc say about this value?
0b001 Async mode -- Cluster Group A PLL 1 /1 is clock
So we know that through the multiplexer we select CGA_PLL1:1 which does not divide the PLL. Let's move on. SysClk is equal to 100Mhz. According to the code CGA_PLL1_RAT is set to 15. 100 * 15 -> 1500Mhz.
This we can confirm on Linux looking at the /sys/kernel/debug/clk/clk_summary:
cg-pll1-div1 0 0 0 1500000000 0 0 50000 Y
Ok let's move on. FlexSPi itself has a divider capability so that we can configure its clock. What's the maximum value of the divider? 80.
Simple maths reveals that: 1500 / 80 = 18,75Mhz and this is exactly what the Linux says about the current setup of my FlexSPI clock.
So my solution will be to reconfigure RCW to use: Cluster Group A PLL 1 /4 is clock
Edit: It works as I thought. The only interesting thing is that there is no set_parent function for mux which would change the parent by the software call to clk_set_rate.