2
votes

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.

1

1 Answers

0
votes

So I am not sure why this is required, but this version of the driver appears to work. I added all the extra ioctls that were in this example. While my FPGA still doesn't like the output (even though it worked fine with 3.14), it looks correct on the logic analyzer and via a protocol analyzer.

#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);

    const std::uint8_t mode = 3;
    const std::uint8_t bits = 8;
    int ioctl_return = 0;

    if (success)
    {
        ioctl_return = ioctl(spi_file_handle, SPI_IOC_WR_MODE, &mode);
        if (ioctl_return != 0)
            success = false;

        ioctl_return = ioctl(spi_file_handle, SPI_IOC_RD_MODE, &mode);
        if (ioctl_return != 0)
            success = false;

        ioctl_return = ioctl(spi_file_handle, SPI_IOC_WR_BITS_PER_WORD, &bits);
        if (ioctl_return != 0)
            success = false;

        ioctl_return = ioctl(spi_file_handle, SPI_IOC_RD_BITS_PER_WORD, &bits);
        if (ioctl_return != 0)
            success = false;

        ioctl_return = ioctl(spi_file_handle, SPI_IOC_WR_MAX_SPEED_HZ, &m_speed_bps);
        if (ioctl_return != 0)
            success = false;

        ioctl_return = ioctl(spi_file_handle, SPI_IOC_RD_MAX_SPEED_HZ, &m_speed_bps);
        if (ioctl_return != 0)
            success = false;
    }

    if (success)
    {
        struct spi_ioc_transfer tr;
        tr.tx_buf = reinterpret_cast<unsigned long>(out_data);
        tr.rx_buf = reinterpret_cast<unsigned long>(in_data);
        tr.len = size_in_bytes;
        tr.delay_usecs = 0;
        tr.speed_hz = 0;
        tr.bits_per_word = 0;

        ioctl_return = ioctl(spi_file_handle, SPI_IOC_MESSAGE(1), &tr);
        success = (ioctl_return != 1);
    }

    close(spi_file_handle);

    return success;
}
}
}
}
}