bsps/imxrt: Improve SPI driver

It wasn't possible to keep the CS line low between multiple message
descriptors in one transfer. This patch reworks the driver so that it is
possible.

Update #4180
This commit is contained in:
Christian Mauderer
2021-09-01 15:54:33 +02:00
parent 25b0fddad9
commit e495633887

View File

@@ -43,16 +43,19 @@ struct imxrt_lpspi_bus {
uint32_t src_clock_hz;
clock_ip_name_t clock_ip;
uint32_t msg_todo;
const spi_ioc_transfer *msg;
rtems_binary_semaphore sem;
uint32_t tcr;
bool cs_change_on_last_msg;
uint32_t rx_msg_todo;
const spi_ioc_transfer *rx_msg;
size_t remaining_rx_size;
uint8_t *rx_buf;
uint32_t tx_msg_todo;
const spi_ioc_transfer *tx_msg;
size_t remaining_tx_size;
const uint8_t *tx_buf;
uint32_t fifo_size;
};
@@ -146,11 +149,7 @@ static void imxrt_lpspi_config(
}
tcr |= LPSPI_TCR_PCS(msg->cs);
if (!msg->cs_change) {
tcr |= LPSPI_TCR_CONT_MASK;
}
tcr |= LPSPI_TCR_CONT_MASK;
tcr |= LPSPI_TCR_FRAMESZ(word_size-1);
if (ccr_orig != ccr) {
@@ -159,9 +158,13 @@ static void imxrt_lpspi_config(
regs->CR |= LPSPI_CR_MEN_MASK;
}
/* No CONTC on first write. Otherwise upper 8 bits are not written. */
regs->TCR = tcr;
regs->TCR = tcr | LPSPI_TCR_CONTC_MASK | LPSPI_TCR_CONT_MASK;
if (bus->cs_change_on_last_msg) {
/* No CONTC on first write. Otherwise upper 8 bits are not written. */
regs->TCR = tcr;
}
regs->TCR = tcr | LPSPI_TCR_CONTC_MASK;
bus->cs_change_on_last_msg = msg->cs_change;
}
static inline bool imxrt_lpspi_rx_fifo_not_empty(
@@ -184,48 +187,72 @@ static inline bool imxrt_lpspi_tx_fifo_not_full(
bus->fifo_size - 2;
}
static void imxrt_lpspi_next_tx_msg(
struct imxrt_lpspi_bus *bus,
volatile LPSPI_Type *regs
)
{
if (bus->tx_msg_todo > 0) {
const spi_ioc_transfer *msg;
msg = bus->tx_msg;
imxrt_lpspi_config(bus, regs, msg);
bus->remaining_tx_size = msg->len;
bus->tx_buf = msg->tx_buf;
}
}
static void imxrt_lpspi_fill_tx_fifo(
struct imxrt_lpspi_bus *bus,
volatile LPSPI_Type *regs
)
{
while(imxrt_lpspi_tx_fifo_not_full(bus, regs)
&& bus->remaining_tx_size > 0) {
if (bus->remaining_tx_size == 1) {
regs->TCR &= ~(LPSPI_TCR_CONT_MASK);
}
&& (bus->tx_msg_todo > 0 || bus->remaining_tx_size > 0)) {
if (bus->remaining_tx_size > 0) {
if (bus->remaining_tx_size == 1 && bus->tx_msg->cs_change) {
/*
* Necessary for getting the last data out of the Rx FIFO. See "i.MX
* RT1050 Processor Reference Manual Rev. 4" Chapter 47.3.2.2 "Receive
* FIFO and Data Match":
*
* "During a continuous transfer, if the transmit FIFO is empty, then
* the receive data is only written to the receive FIFO after the
* transmit FIFO is written or after the Transmit Command Register (TCR)
* is written to end the frame."
*/
regs->TCR &= ~(LPSPI_TCR_CONT_MASK);
}
if (bus->tx_buf != NULL) {
regs->TDR = bus->tx_buf[0];
++bus->tx_buf;
} else {
regs->TDR = 0;
if (bus->tx_buf != NULL) {
regs->TDR = bus->tx_buf[0];
++bus->tx_buf;
} else {
regs->TDR = 0;
}
--bus->remaining_tx_size;
}
if (bus->remaining_tx_size == 0) {
--bus->tx_msg_todo;
++bus->tx_msg;
imxrt_lpspi_next_tx_msg(bus, regs);
}
--bus->remaining_tx_size;
}
}
static void imxrt_lpspi_next_msg(
static void imxrt_lpspi_next_rx_msg(
struct imxrt_lpspi_bus *bus,
volatile LPSPI_Type *regs
)
{
if (bus->msg_todo > 0) {
if (bus->rx_msg_todo > 0) {
const spi_ioc_transfer *msg;
msg = bus->msg;
msg = bus->rx_msg;
imxrt_lpspi_config(bus, regs, msg);
bus->remaining_tx_size = msg->len;
bus->remaining_rx_size = msg->len;
bus->rx_buf = msg->rx_buf;
bus->tx_buf = msg->tx_buf;
imxrt_lpspi_fill_tx_fifo(bus, regs);
regs->IER = LPSPI_IER_TDIE_MASK;
} else {
regs->IER = 0;
rtems_binary_semaphore_post(&bus->sem);
}
}
@@ -234,15 +261,22 @@ static void imxrt_lpspi_pull_data_from_rx_fifo(
volatile LPSPI_Type *regs
)
{
while (imxrt_lpspi_rx_fifo_not_empty(regs) && bus->remaining_rx_size > 0) {
uint32_t data;
data = regs->RDR;
if (bus->rx_buf != NULL) {
*bus->rx_buf = data;
++bus->rx_buf;
uint32_t data;
while (imxrt_lpspi_rx_fifo_not_empty(regs)
&& (bus->rx_msg_todo > 0 || bus->remaining_rx_size > 0)) {
if (bus->remaining_rx_size > 0) {
data = regs->RDR;
if (bus->rx_buf != NULL) {
*bus->rx_buf = data;
++bus->rx_buf;
}
--bus->remaining_rx_size;
}
if (bus->remaining_rx_size == 0) {
--bus->rx_msg_todo;
++bus->rx_msg;
imxrt_lpspi_next_rx_msg(bus, regs);
}
--bus->remaining_rx_size;
}
}
@@ -257,39 +291,22 @@ static void imxrt_lpspi_interrupt(void *arg)
imxrt_lpspi_pull_data_from_rx_fifo(bus, regs);
imxrt_lpspi_fill_tx_fifo(bus, regs);
if (bus->remaining_tx_size == 0) {
if (bus->remaining_rx_size > 0) {
regs->IER = LPSPI_IER_RDIE_MASK;
} else {
--bus->msg_todo;
++bus->msg;
imxrt_lpspi_next_msg(bus, regs);
}
if (bus->tx_msg_todo > 0 || bus->remaining_tx_size > 0) {
regs->IER = LPSPI_IER_TDIE_MASK;
} else if (bus->rx_msg_todo > 0 || bus->remaining_rx_size > 0) {
regs->IER = LPSPI_IER_RDIE_MASK;
} else {
regs->IER = 0;
rtems_binary_semaphore_post(&bus->sem);
}
}
static inline int imxrt_lpspi_settings_ok(
struct imxrt_lpspi_bus *bus,
const spi_ioc_transfer *msg
const spi_ioc_transfer *msg,
const spi_ioc_transfer *prev_msg
)
{
if (msg->cs_change == 0) {
/*
* This one most likely would need a bigger workaround if it is necessary.
* See "i.MX RT1050 Processor Reference Manual Rev. 4" Chapter 47.3.2.2
* "Receive FIFO and Data Match":
*
* "During a continuous transfer, if the transmit FIFO is empty, then the
* receive data is only written to the receive FIFO after the transmit FIFO
* is written or after the Transmit Command Register (TCR) is written to end
* the frame."
*
* It might is possible to extend the driver so that it can work with an
* empty read buffer.
*/
return -EINVAL;
}
/* most of this is currently just not implemented */
if (msg->cs > 3 ||
msg->speed_hz > bus->base.max_speed_hz ||
@@ -299,6 +316,18 @@ static inline int imxrt_lpspi_settings_ok(
return -EINVAL;
}
if (prev_msg != NULL && !prev_msg->cs_change) {
/*
* A lot of settings have to be the same in this case because the upper 8
* bit of TCR can't be changed if it is a continuous transfer.
*/
if (prev_msg->cs != msg->cs ||
prev_msg->speed_hz != msg->speed_hz ||
prev_msg->mode != msg->mode) {
return -EINVAL;
}
}
return 0;
}
@@ -308,17 +337,29 @@ static int imxrt_lpspi_check_messages(
uint32_t size
)
{
const spi_ioc_transfer *prev_msg = NULL;
while(size > 0) {
int rv;
rv = imxrt_lpspi_settings_ok(bus, msg);
rv = imxrt_lpspi_settings_ok(bus, msg, prev_msg);
if (rv != 0) {
return rv;
}
prev_msg = msg;
++msg;
--size;
}
/*
* Check whether cs_change is set on last message. Can't work without it
* because the last received data is only put into the FIFO if it is the end
* of a transfer or if another TX byte is put into the FIFO.
*/
if (!prev_msg->cs_change) {
return -EINVAL;
}
return 0;
}
@@ -336,10 +377,20 @@ static int imxrt_lpspi_transfer(
rv = imxrt_lpspi_check_messages(bus, msgs, n);
if (rv == 0) {
bus->msg_todo = n;
bus->msg = &msgs[0];
bus->tx_msg_todo = n;
bus->tx_msg = &msgs[0];
bus->rx_msg_todo = n;
bus->rx_msg = &msgs[0];
bus->cs_change_on_last_msg = true;
imxrt_lpspi_next_msg(bus, bus->regs);
imxrt_lpspi_next_rx_msg(bus, bus->regs);
imxrt_lpspi_next_tx_msg(bus, bus->regs);
/*
* Enable the transmit FIFO empty interrupt which will cause an interrupt
* instantly because there is no data in the transmit FIFO. The interrupt
* will then fill the FIFO. So nothing else to do here.
*/
bus->regs->IER = LPSPI_IER_TDIE_MASK;
rtems_binary_semaphore_wait(&bus->sem);
}
@@ -416,7 +467,7 @@ static int imxrt_lpspi_setup(spi_bus *base)
bus = (struct imxrt_lpspi_bus *) base;
rv = imxrt_lpspi_settings_ok(bus, &msg);
rv = imxrt_lpspi_settings_ok(bus, &msg, NULL);
/*
* Nothing to do besides checking.