I am running embedded Linux (4.14.16) built with Yocto (Pyro). I am running on a custom board that has an i.MX6DL and the SPI is connected to an FPGA (a Xilinx Artix 7). I am currently writing a class that is part of an abstraction layer, so this code sits on top of the Linux drivers. It isn't a Linux device driver. The SPI works; I can program the FPGA with a shell script, and see the SPI traffic if echo data into /dev/spi1.0 (the FPGA brings out the SPI to a header, which I have an analyzer hooked up to).
The issue is when I use my driver to try to read registers in the FPGA, it isn't sending anything; the SPI transfer doesn't happen.
I dug into the spidev and spi drivers in Linux a little bit, and I am seeing it fail the __spi_validate
call in drivers/spi/spi.c right under the /* check transfer rx_nbits */
comment. What controls those bits? Everything on our board is single data bit per clock, we have no quad-spi.
Here is the code in question:
#include "os/drivers/buses/linuxos/spi_driver.h"
#include <fcntl.h>
#include <sstream>
#include <stdio.h>
#include <unistd.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>
namespace os
{
namespace drivers
{
namespace buses
{
namespace linuxos
{
spi_driver::spi_driver(int bus_id, int cs_index, std::uint32_t speed_bps) :
m_speed_bps(speed_bps)
{
std::stringstream descriptor;
descriptor << "/dev/spidev" << bus_id << '.' << cs_index;
m_device_file_descriptor = descriptor.str();
}
bool spi_driver::transfer(const unsigned char *out_data, unsigned char *in_data, size_t size_in_bytes)
{
int spi_file_handle = open(m_device_file_descriptor.c_str(), O_RDWR);
bool success = (spi_file_handle >= 0);
if (success)
{
printf("spidev opened\n");
struct spi_ioc_transfer transfer_parameters;
transfer_parameters.tx_buf = reinterpret_cast<unsigned long>(out_data);
transfer_parameters.rx_buf = reinterpret_cast<unsigned long>(in_data);
transfer_parameters.len = size_in_bytes;
transfer_parameters.speed_hz = m_speed_bps;
transfer_parameters.bits_per_word = 0;
// transfer_parameters.cs_change = 0;
// transfer_parameters.delay_usecs = 0;
int ioctl_return = ioctl(spi_file_handle, SPI_IOC_MESSAGE(1), &transfer_parameters);
printf("spidev ioctl returned %d\n", ioctl_return);
success = (ioctl_return > 0);
printf("Received data: ");
for (unsigned int i = 0; i < size_in_bytes; i++)
{
printf("%02x ", in_data[i]);
}
printf("\n");
}
close(spi_file_handle);
return success;
}
}
}
}
}
I am not sure if it is relevant (the SPI appears to work...) but here is the SPI part of the device tree.
&ecspi2 {
cs-gpios = <&gpio5 12 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi2 &pinctrl_ecspi2_cs>;
status = "okay";
simulator-fpga {
compatible = "mi,simulator-fpga";
spi-max-frequency = <8000000>;
reg = <0>;
};
};
The mi,simulator-fpga has been added to the generic spidev driver's compatible string, since it prints an error if you use spidev directly, but it is the generic spidev device.
I did try just using the Linux read and write functions, which worked (I saw the traffic on the SPI analyzer), but I need full duplex transfers which can't be done via that method.
Edit: In case anyone is wondering what those printf statements print out, here is what I get from those:
spidev opened
spidev ioctl returned -1
Received data: 00 00 00 00 00 00 00 00
The Received data is the correct length for the desired message being sent. I am not sure why this is getting -1 for a return value, the error thrown in spi.c in the kernel is -22 (EINVAL/Invalid argument) as noted above.