le_spi_WriteHD() doesn't work right for SPI with 24 bits per word

Hi,
my application runs on a custom board with WP76xx and a Legato system based upon 18.08.

I use spidev1.0, and at startup my application calls le_spi_Configure(spiHandle, mode, bitsPerWord, speed, msb)
with following parameters:
mode = 3
speed = 960000
msb =0
The SPI data is sent with le_spi_WriteHD(spiHandle, buffer, length).
I used an oscilloscope to check the output signals for different possibilities of the ‘bitsPerWord’ parameter and ‘length’ parameter.

4 bits per word
length = 2, results in 2 correct SPI words
length = 8, results in 8 correct SPI words

10 bits per word
length = 2, results in 1 correct SPI words
length = 8, results in 4 SPI words (???)

16 bits per word
length = 2, results in 1 correct SPI word
length = 8, results in 4 correct SPI words

24 bits per word
length = 4, results in 2 SPI words, but, the highest 16 bits of the 2nd word are 0
length = 8, results in 3 SPI words, but, the highest 8 bits of the 3rd word are 0
When the length is 1, 3, 5, 6, 9 or 10,
I always get the error “Transfer failed with error -1 : 22 (Invalid argument) … can’t send spi message”

Apparently, it’s impossible to send SPI words with a length of 24 bits.
It would be logical that the length should be a multiple of ‘3’, but, even that doesn’t work.

Is there something wrong with the spidev kernel implementation ?

Greetings,

annaertd

Hello,
Can we have more details about your program - application? and provide us the result of the “fwupdate query”
command, please.
Regard

Hi,

I’ve created a small SPI test program which runs on my mangOH Green board with a WP76xx controller.
fwupdate query returns:
Firmware Version: SWI9X07Y_02.18.05.00 000000 jenkins 2018/07/19 17:40:21
Bootloader Version: SWI9X07Y_02.18.05.00 000000 jenkins 2018/07/19 17:40:21
Linux Version: 3.18.44 #2 PREEMPT Thu Sep 20 16:20:02 UTC 2018

My mangOH system and testapplication have been built with the most recent Sierra Wireless firmware release package R10.1.1 (Legato 18.06.3).

The testapplication uses spidev1.0 and configures the SPI bus with
le_spi_Configure(spiHandle, 3, 24, 960000, 0);

I try to use API function le_spi_WriteHD( handle, writeDataPtr, writeDataSize) to put 24 bit data-words on the SPI bus.
So, basically you need a pointer to an array of uint8_t, and a datalength as parameters.
I assumed that a 24 bit data-word would need an arraylength which is a multiple of 3 :slight_smile:
Apparently, the API function returns an error in that case.

My testapplication tries to call le_spi_WriteHD() with several lengths.
The data array contains a list of byte values 0x0F.

  • length = 3 ==> error
  • length = 4 ==> LE_OK, but, I get two 24 bit data-words on the bus
  • length = 6 ==> error
  • length = 8 ==> LE_OK, but, I get three 24 bit data-words on the bus
  • length = 9 ==> error

In attachment, you can find:

  • testapplication sources
  • application log-file (spiService.adef contains LE_LOG_LEVEL = DEBUG)
  • screenshots from the oscilloscope

In the screenshots you can see that the SPI bus contains 24 bit data-words.
For an array length of 4, the data-words are 0x0F0F0F and 0x00000F
For an array length of 8, the data-words are 0x0F0F0F, 0x0F0F0F and 0x000F0F

Why does the API function le_spi_WriteHD() only work with arrays of 4 or 8 bytes? (related to uint32_t handling in Legato drivers?)
Why does this fail for arrays of 3 bytes, which is needed to create a 24 bit data-word?

spitest - log.txt (3.5 KB)
test.tar.gz (1.9 KB)

SPI%2024bits%20per%20word%20-%20overview
SPI%2024bits%20per%20word%20-%20detailed%20view
SPI%2024bits%20per%20word%20-%20use%204%20data%20bytes
SPI%2024bits%20per%20word%20-%20use%208%20data%20bytes

Greetings,
annaertd

Hi,

I just want to explain something :

In the case :
length = 4 ==> LE_OK, but, I get two 24 bit data-words on the bus … it’s normal to get 2x24 data words because the number of bytes-arrays is 4, it means one word contains 3 bytes + another word that contains 1 byte and the 2 bytes that remain are at 0.

length = 8 ==> LE_OK, but, I get three 24 bit data-words on the bus … the same principle, you have 8 bytes to send, so you need 2 words (0x0F x3) and the 3rd with (0x0F x1)

** it is always better to use bitsPerWord = 8, 16 or 24

@Vasantha_lax have you an idea why when we use 24 bits per word + [NUM_ARRAY_MEMBERS (write_buffer_tx) = 1, 3, 5, 6, 9 or 10 doesn’t work.

I tracked the issue “Transfer failed with error -1 : 22 (Invalid argument) … can’t send spi message” in le_spiLibrary.c and I found the source of this message :

> le_result_t le_spiLib_WriteHD
> (
>     int fd,                   ///< [in] open file descriptor of SPI port
>     const uint8_t* writeData, ///< [in] command/address being sent to slave
>     size_t writeDataLength    ///< [in] number of bytes in tx message
> )
> {
>     int transferResult;
>     le_result_t result;
> 
>     struct spi_ioc_transfer tr[] =
>     {
>         {
>             .tx_buf = (unsigned long)writeData,
>             .rx_buf = (unsigned long)NULL,
>             .len = writeDataLength,
>             // .delay_usecs = delay_out,
>             .cs_change = 0
>         }
>     };
> 
> 
>     LE_DEBUG("Transferring this message...len: %zu", writeDataLength);
>     for (size_t i = 0; i < writeDataLength; i++)
>     {
>         LE_DEBUG("%.2X ", writeData[i]);
>     }
> 
>     transferResult = ioctl(fd, SPI_IOC_MESSAGE(1), tr);
>     if (transferResult < 1)
>     {
>         LE_ERROR("Transfer failed with error %d : %d (%m)", transferResult, errno);
>         LE_ERROR("can't send spi message");
>         result = LE_FAULT;

I think it’s a problem of the parameter not supported in transferResult.

Hi,
I found the reason why function call ioctl(fd, SPI_IOC_MESSAGE(1), tr) fails when using 24 bits per word, in case the writeDataLength is 1, 3, 5, 6, 9 or 10.

The yocto-2.2 source tree contains a file /kernel/drivers/spi/spi.c with the function below:
static int __spi_validate(struct spi_device *spi, struct spi_message *message)
{

	/*
	 * SPI transfer length should be multiple of SPI word size
	 * where SPI word size should be power-of-two multiple
	 */
	if (xfer->bits_per_word <= 8)
		w_size = 1;
	else if (xfer->bits_per_word <= 16)
		w_size = 2;
	else
		w_size = 4;

	/* No partial transfers accepted */
	if (xfer->len % w_size)
		return -EINVAL;


}

So, this means we can only use buffer lengths of multiples of 4 for the 24 bits SPI mode.

Does anyone know how to fix the last part of this issue?
How can we put 1 24-bit SPI message on the bus if the buffer contains 4 data bytes?
As you can see in the images above, the current SPI implementation always creates 2 messages: it adds an extra 24-bit message where the upper 16 bits are zero.

Greetings,
annaertd

Hi,

I managed to patch my SPI issue :slight_smile:

Due to the check " if (xfer->len % w_size)" in the function __spi_validate() we are obliged to use message lenghts which are a multiple of 4, in case you need an SPI protocol with 24 bits per word.
When you check the file /kernel/drivers/spi/spi_qsd.c, you can see that the message length is used as the number of bytes that needs to be transmitted.

msm_spi_process_transfer(struct msm_spi *dd)
{
...
dd->tx_bytes_remaining = dd->cur_msg_len;
...
}

This number of bytes will be used to write the data into the FIFO.

static void msm_spi_write_word_to_fifo(struct msm_spi *dd)
{

word = 0;
if (dd->write_buf) {
for (i = 0; (i < write_bytes) &&
dd->tx_bytes_remaining; i++) {
dd->tx_bytes_remaining–;
byte = *dd->write_buf++;
word |= (byte << (BITS_PER_BYTE * i));
}
}

}
So, the result of this implementation is, that, if you are obliged to use a buffer of 4 bytes in case of 24 bits per word, the low level code will create a 24 bit word with the first 3 bytes of your buffer.
BUT: after that, it will create another 24 bit word with the 4th byte of your buffer and it will add leading 0’s.
So, it’s impossible to put 1 word with 24bits on the SPI bus.

Therefore, I rebuilt the Yocto-2.2 image without " if (xfer->len % w_size)" !
With this image, I managed to create correct SPI words with 24 bits per word.

My application uses _le_spi_WriteHD(spiHandle, write_buffer_tx, write_buffer_tx_length), and I make sure that write_buffer_tx_length is always a multiple of 3.

Greetings,
annaertd

@JayM2M, you are aware of this limitation ( the failure when using 24 bits per word, in case the writeDataLength is 1, 3, 5, 6, 9 or 10 ) in yocto? does it match to the SPI spec? I didn’t find anything about this case.

@annaertd, you don’t say why you use 24 Bit Per Word? why do not you use 8 Bit Per Word? do you have a specific use case?
If you choose to modify the kernel (a thing to discourage), I prefer to add the 24 BitPerWord case instead of removing something :

            if (xfer->bits_per_word <= 8)
                    w_size = 1;
            else if (xfer->bits_per_word <= 16)
                    w_size = 2;
            else if (xfer->bits_per_word <= 24)
                    w_size = 3;
            else
                    w_size = 4;

            /* No partial transfers accepted */
            if (xfer->len % w_size)
                    return -EINVAL;

Regard

Hi,

our custom board uses an EA DOGM204-A 4x20 character LCD display.
The onboard LCD controller works with SPI messages with 24 bits per word.

Greetings,
annaertd