How to enable spi and access it in Debian os

Hi everyone,

Thanks to all your information, I was able to the SPI on the LS expansion sending and receiving data using libsoc. However, I’ve noticed that I wasn’t able to change the clock speed. It seems to be stuck at either 1 MHz or 5 MHz (measured with oscilloscope). I am using this code which is based off the libsoc SPI example: http://pastebin.com/K9zRVAGS.

If I try to to set the speed to 1 MHz, 250KHz, or 10 KHz and then read back the speed, it is what is expected. However, the speed of the physical clock pin seems to stay above 1 MHz.

I have tried setting spi-max-frequency in the device tree under spidev’s node but it seemed to have no effect. Is there a minimum clock speed? Am I missing something? Thanks.

I’m seeing a similar issue using the http://learn.linksprite.com/96-board/sliding-rheostat example modified for HiKey. The user program sets the clock at 10kHz but measured 500kHz on the analyzer, which is also the max frequency stated in the DT. Is it possible that the DT ‘locks up’ the speed?

I recently added spidev on the HS connector as well, and there’s one thing I was ignoring before but is happening on the HS connector too that has me curious. The spidev/spi-qup driver doesn’t seem to be operating the CS line on my boards, I’ve been having to do it manually by exporting and writing to it at /sys/class/gpio.

Is there something that needs to be added to msm8916.dtsi or another file in order to tell the SPI driver to control the CS line?

I did notice that in the Qualcomm’s example dtsi file on page 64:

They have:
pinctrl-0 = <&spi0_default &spi0_cs0_active>;
and similar for pinctrl-1, while on the dragonboard we just have:
pinctrl-0 = <&spi3_default>;
or spi5, or what have you for the interface you’re looking at.

Is it as simple as adding &spi3_cs0_active and sleep to pinctrl-0 and pinctrl-1, or is there more to it than that?

Can you also try adding the cs-gpios property to the blsp_spi5 (or whatever it is for the HS) node and see if that works? Something like below, but not sure if the number designation is exact or not.


num-cs = &lt;1&gt;;
cs-gpios = &lt;&amp;gpio18 0 1&gt;;

Thanks for the suggestion. Unfortunately I tried all combinations I could think of with num-cs, cs-gpios, the lines I mentioned earlier, as well as removing the cs gpio from pinconf earlier in the dtsi file (since it’s called out explicitly in pinconf_cs a few lines later, I thought it might be causing a conflict), but none of them were able to get CS to move.

In general, it’d be good to start new threads for new problems. Can you please specify which spi you are trying to use on the system? Or which pins, on which header?

Given your answers, I’ll provide some snippets of DT that should work for you.

I figured it was suitable here since it still has to do with getting spi running on Debian, but I can see your point.

Ultimately I’m using spi3 on the HS header, but my configuration has both of them enabled and I’m using spi5 on the LS header for testing since it’s easier to probe. If I can see how to get CS running on either of them, I should be able to port the changes to the other without much hassle. Thanks.

I changed

                pinmux_cs {
                        function = &quot;gpio&quot;;
                        pins = &quot;gpio18&quot;;
                };

to

                pinmux_cs {
                        function = &quot;blsp_spi5&quot;;
                        pins = &quot;gpio18&quot;;
                };

and now it drives the chipselect.

Unfortunately it created a new problem. The chip select goes high between each byte of data transferred, but the device needs CS to stay low for all 3 bytes that are rw to the device.

I dug into the source of drivers/spi/spi-qup.c and found that the bit MX_CS_MODE is never set, hence the CS goes high between bytes. I added a line of code

      control |= SPI_IO_C_MX_CS_MODE;

At line 554 and now CS stays low for the entire transfer.

	spi@78b7000 {
	/* On High speed expansion */
		label = "HS-SPI1";
		status = "okay";
		cs-gpios = &lt;&amp;msmgpio 10&gt;;

		spidev@0 {
			compatible = "spidev";
			reg = &lt;0&gt;;
			spi-max-frequency = &lt;50000000&gt;;
		};
	};

	spi@78b9000 {
	/* On Low speed expansion */
		label = "LS-SPI0";
		status = "okay";
		cs-gpios = &lt;&amp;msmgpio 18&gt;;

		spidev@0 {
			compatible = "spidev";
			reg = &lt;0&gt;;
			spi-max-frequency = &lt;50000000&gt;;
		};
	};

That should get you working CS-0s on LS and HS. I’ll try to verify a little later.

Thanks you to both ljking and agross! Those look like two different solutions to the same problem (treating the CS line as part of SPI vs a SPI-controlled GPIO). I haven’t tested agross’s solution, but ljking’s worked great for me.

And actually, that “problem” you mentioned of CS going high between each byte is GREAT for my application, and solves another, unrelated problem I’ve been having. For some reason, approximately every 5e11 bits that’s transferred in my system, I get a corruption where a clock cycle doesn’t make it through. I don’t know if the 410c isn’t sending it, the CPLD isn’t reading it, or due to a flea coughing in a nearby state some interference squashes it, but either way when it happens it causes the entire remainder of the 32kB SPI transfer to get shifted by one bit and corrupted. The CS toggling every byte allows the 410c and CPLD to remained sync’d at a much lower level than I could have ever done in user-space, AND it doesn’t slow anything down, the CS toggles in the 70nS gap between bytes that was already there, perfect!

Slight update. I was helping someone else and found a slight error in my DT solution. I verified that the following toggles the CS line correctly:

            spi@78b9000 {
            /* On Low speed expansion */
                    label = "LS-SPI0";
                    cs-gpios = &lt;&amp;msmgpio 18 0&gt;;
                    status = "okay";

                    spidev@0 {
                            compatible = "spidev";
                            reg = &lt;0&gt;;
                            spi-max-frequency = &lt;50000000&gt;;
                    };
            };

The error was leaving off the last cell in the gpios. msmgpio requires both the pin and the polarity.

Hi All,

Thanks to everyone for all the useful information. Are there any plans to have SPI Enabled in an image so users do not have rebuild the kernel to enable it?

Thanks,
Mike

@mvartini Hi Mike,

I think it’s enabled as a module. Can you try ‘modprobe spidev’ or ‘insmod spidev’ and see if something like ‘/dev/spidev0.0’ appears?

Hi @vchong,

Thank you for your reply.

I downloaded the latest Debian SD image from http://builds.96boards.org/releases/dragonboard410c/linaro/debian/latest/ and it does not appear to be working.

/dev/spidev0.0 does not appear after running the two commands above as a root user.

sudo insmod spidev generates the following error message:

insmod: ERROR: could not load module spidev: No such file or directory

Thanks,
Mike

The SPI driver is in the kernel (as a module), but not in the device tree. you will need to rebuild the device tree to get it to work. Additionally if you want hardware chip select to work, you will need to make a small change to the device driver in the kernel. If a proper device driver exists for the device you are attaching to the SPI bus, then you should use it if possible (instead of spidev). This ‘patch’ will enable the generic /dev/spidev0.0 and fix the HW chip select. I have tested this on build #202 this week.


diff --git a/arch/arm64/boot/dts/qcom/apq8016-sbc.dtsi b/arch/arm64/boot/dts/qcom/apq8016-sbc.dtsi
index f6d2bcb6dbd1..874150a412a2 100644
--- a/arch/arm64/boot/dts/qcom/apq8016-sbc.dtsi
+++ b/arch/arm64/boot/dts/qcom/apq8016-sbc.dtsi
@@ -208,8 +208,14 @@
                /* On Low speed expansion */
                        label = &quot;LS-SPI0&quot;;
                        status = &quot;okay&quot;;
+                       spidev@0 {
+                               compatible = &quot;spidev&quot;;
+                               spi-max-frequency = &lt;100000&gt;;
+                               reg = &lt;0&gt;;
+                       };
                };

                leds {
diff --git a/arch/arm64/boot/dts/qcom/msm8916-pins.dtsi b/arch/arm64/boot/dts/qcom/msm8916-pins.dtsi
index 899f2b28a9c9..1c22b4414edf 100644
--- a/arch/arm64/boot/dts/qcom/msm8916-pins.dtsi
+++ b/arch/arm64/boot/dts/qcom/msm8916-pins.dtsi
@@ -208,7 +208,7 @@
                        pins = &quot;gpio16&quot;, &quot;gpio17&quot;, &quot;gpio19&quot;;
                };
                pinmux_cs {
-                       function = &quot;gpio&quot;;
+                       function = &quot;blsp_spi5&quot;;
                        pins = &quot;gpio18&quot;;
                };
                pinconf {
@@ -218,7 +218,7 @@
                };
                pinconf_cs {
                        pins = &quot;gpio18&quot;;
-                       drive-strength = &lt;2&gt;;
+                       drive-strength = &lt;12&gt;;
                        bias-disable;
                        output-high;
                };

diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c
index 68f95acf7971..aed71ef7e3fd 100644
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -580,6 +580,7 @@ static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer)
        else
                control &amp;= ~SPI_IO_C_CLK_IDLE_HIGH;

+       config |= SPI_IO_C_MX_CS_MODE;
        writel_relaxed(control, controller-&gt;base + SPI_IO_CONTROL);

        config = readl_relaxed(controller-&gt;base + SPI_CONFIG);
@@ -928,7 +929,7 @@ static int spi_qup_probe(struct platform_device *pdev)
                        base + QUP_ERROR_FLAGS_EN);

        writel_relaxed(0, base + SPI_CONFIG);
-       writel_relaxed(SPI_IO_C_NO_TRI_STATE, base + SPI_IO_CONTROL);
+       writel_relaxed(SPI_IO_C_NO_TRI_STATE|SPI_IO_C_MX_CS_MODE, base + SPI_IO_CONTROL);

        ret = devm_request_irq(dev, irq, spi_qup_qup_irq,
                               IRQF_TRIGGER_HIGH, pdev-&gt;name, controller);


if you don’t want to use hardware chip select, and would rather use software chip select then you don’t need to patch the drivers/spi/spi-qup.c file, instead, leave pinmux_cs as gpio, and add the line:
cs-gpios = <&msmgpio 18>;
in front of the spidev@0 in the file arch/arm64/boot/dts/qcom/apq8016-sbc.dtsi

Instructions for rebuilding the kernel and devicetree are here: http://builds.96boards.org/releases/dragonboard410c/linaro/debian/latest/ although you will need to use a slightly different git checkout command if you want to rebuild #202.

git checkout -b build-202 0ea6924010269ba065848b2e450673e96bceffd9

Hi @ljking,

Thanks for the reply and detailed information. I am able to rebuild the image as well which is great but I guess I was wondering what the reason for not including a common feature such as SPI in the actual image without needing to rebuild the kernel? Is it a memory issue?

I would think that having SPI enabled by default on the latest image would improve the out of box experience for many users that want to use SPI. Rebuilding the kernel and requiring a separate box to do the fastboot might be difficult for some users… But I could missing something obvious as to why it is not included…

Thanks again for your help,
Mike

Hi @ljking

I also try your patch for a specific SPI mtd device (m25p80), but hardware chip select does not work. I always get failed for reading JEDEC id in probe.

[2.496337] spi_qup 78b9000.spi: IN:block:16, fifo:64, OUT:block:16, fifo:64
[2.512441] m25p80 spi0.0: unrecognized JEDEC id bytes: 00,  0,  0

But it works well by using software chip select. (define cs-gpios in DTS)

I don’t know if hardware chip select issues are fixed now, but there seems some reasons to disable it in spi-qup.c.
https://git.linaro.org/landing-teams/working/qualcomm/kernel.git/commit/drivers/spi/spi-qup.c?h=release/qcomlt-4.4&id=4a8573abe965115bc5b064401fd669b74e985258


In addition, I also encoutner problems in block mode. To dump block for /dev/mtdblock0 will cause crash in DMA.

$ hexdump -C /dev/mtdblock0 -n 64

Call trace:
 [<ffffffc0003cef40>] bam_start_dma+0x1f0/0x350
 [<ffffffc0003cf124>] bam_issue_pending+0x84/0x90
 [<ffffffc00050d334>] spi_qup_transfer_one+0x34c/0x6a8
 [<ffffffc000507a70>] spi_transfer_one_message+0xc0/0x320
 [<ffffffc00050828c>] __spi_pump_messages+0x34c/0x4a0
 [<ffffffc000508614>] __spi_sync+0x214/0x230
 [<ffffffc000508640>] spi_sync+0x10/0x18
 [<ffffffbffc0073d0>] m25p80_read+0x148/0x180 [m25p80]
 [<ffffffc000504124>] spi_nor_read+0x7c/0xd8
 [<ffffffc0004fc9a8>] mtd_read+0x48/0x80
 [<ffffffc000503904>] mtdblock_readsect+0x64/0x190
 [<ffffffc0005029c0>] mtd_blktrans_work+0x130/0x308
 [<ffffffc0000cc8ac>] process_one_work+0x11c/0x378
 [<ffffffc0000ccc2c>] worker_thread+0x124/0x498
 [<ffffffc0000d2924>] kthread+0xdc/0xf0
 [<ffffffc000085dd0>] ret_from_fork+0x10/0x40

If I disable DMA in DTS, it shows OUTPUT_OVER_RUN problem.

$ hexdump -C /dev/mtdblock0 -n 64

 spi_qup 78b9000.spi: OUTPUT_OVER_RUN
 m25p80 spi0.0: SPI transfer failed: -5
 spi_qup 78b9000.spi: INPUT_UNDER_RUN
 spi_master spi0: failed to transfer one message from queue
 blk_update_request: I/O error, dev mtdblock0, sector 0
 spi_qup 78b9000.spi: OUTPUT_OVER_RUN

Finally, I apply @agross ’ patch and it work well.
https://git.kernel.org/cgit/linux/kernel/git/agross/linux.git/commit/drivers/spi/spi-qup.c?h=spi-wip&id=698179c6148035da04bbeb129fd24880827f5d1f

Does that mean DMA should be disabled, if I want to use SPI for block devices?
Thanks!

Hi @daniel-hung

First thing to note is I was only testing the SPI ADC that is on the Linker mezzanine board, it only sends/receives 3 bytes at a time so I definitely didn’t stress the DMA capacity.

When you are using ‘software’ chip selects, a transaction takes a lot longer that when using hardware chip selects. I measured an extra 100 to 500 microSeconds per transaction.

You really need an Oscilloscope to take a look at the SPI bus to verify what you are really getting. It stuns me the number of times I see programmers trying multiple things to get a device to work when it would have been trivial to just look at the signal and see what the problem is.

These two files show Hardware chip select with and without the SPI_IO_C_MX_CS_MODE bit set. As you can see the chip select does not stay low for the entire transaction if the SPI_IO_C_MX_CS_MODE bit is not set. The Yellow trace is clock, the green trace is the data sent from the 410c to the device, and the Magenta trace is the chip select. With the Oscilloscope traces in hand it was trivial to debug the problem.

Hi @daniel-hung

I managed to buy a M25P80 breakout board and hook it up to my 410c. I see the same thing you do, the device is recognized when using SW chip selects and not recognized when using HW chip selects. When SW chip selects are used there are ‘two’ transactions, first the command byte READ_ID is sent to the device, then the data transfer sequence of several bytes to read back the ID information from the chip. The chip select stays low between pair of transactions. When using HW chip selects the Chipselect goes high between the two transactions, this of course resets the state machine in the M25P80 and it doesn’t return a device ID.

This is trivial to debug with an oscilloscope.

I can now see why HW chip selects are causing grief and probably why @agross disabled them. I am surprised that the patches have not been upstreamed into the mainline, it has been a full year since they were completed.,

Hi @ljking

Sorry for late reply because we have a vocation here. Thank you very much for the test & suggestions. Actually, I also check the signals with an oscilloscope, and see the strange behavior of HW chip select. It looks similar with and without the SPI_IO_C_MX_CS_MODE bit set you mentioned. Then, I try to revise your patch (it may be a typo for control & config variables in yours, or I misunderstand something?) and confirm SPI_IO_C_MX_CS_MODE bit is set correctly, but HW chip select still doesn’t work.

diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c
index 68f95acf7971..aed71ef7e3fd 100644
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -580,6 +580,7 @@ static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer)
        else
                control &= ~SPI_IO_C_CLK_IDLE_HIGH;

+       control |= SPI_IO_C_MX_CS_MODE;
        writel_relaxed(control, controller-&gt;base + SPI_IO_CONTROL);

        config = readl_relaxed(controller-&gt;base + SPI_CONFIG);

I still do not know yet why this strange symptom happens, but at least SW chip select can work.