forked from Imagelibrary/rtems
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:
@@ -43,16 +43,19 @@ struct imxrt_lpspi_bus {
|
|||||||
uint32_t src_clock_hz;
|
uint32_t src_clock_hz;
|
||||||
clock_ip_name_t clock_ip;
|
clock_ip_name_t clock_ip;
|
||||||
|
|
||||||
uint32_t msg_todo;
|
|
||||||
const spi_ioc_transfer *msg;
|
|
||||||
rtems_binary_semaphore sem;
|
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;
|
size_t remaining_rx_size;
|
||||||
uint8_t *rx_buf;
|
uint8_t *rx_buf;
|
||||||
|
|
||||||
|
uint32_t tx_msg_todo;
|
||||||
|
const spi_ioc_transfer *tx_msg;
|
||||||
size_t remaining_tx_size;
|
size_t remaining_tx_size;
|
||||||
const uint8_t *tx_buf;
|
const uint8_t *tx_buf;
|
||||||
|
|
||||||
uint32_t fifo_size;
|
uint32_t fifo_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -146,11 +149,7 @@ static void imxrt_lpspi_config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
tcr |= LPSPI_TCR_PCS(msg->cs);
|
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);
|
tcr |= LPSPI_TCR_FRAMESZ(word_size-1);
|
||||||
|
|
||||||
if (ccr_orig != ccr) {
|
if (ccr_orig != ccr) {
|
||||||
@@ -159,9 +158,13 @@ static void imxrt_lpspi_config(
|
|||||||
regs->CR |= LPSPI_CR_MEN_MASK;
|
regs->CR |= LPSPI_CR_MEN_MASK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (bus->cs_change_on_last_msg) {
|
||||||
/* No CONTC on first write. Otherwise upper 8 bits are not written. */
|
/* No CONTC on first write. Otherwise upper 8 bits are not written. */
|
||||||
regs->TCR = tcr;
|
regs->TCR = tcr;
|
||||||
regs->TCR = tcr | LPSPI_TCR_CONTC_MASK | LPSPI_TCR_CONT_MASK;
|
}
|
||||||
|
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(
|
static inline bool imxrt_lpspi_rx_fifo_not_empty(
|
||||||
@@ -184,14 +187,41 @@ static inline bool imxrt_lpspi_tx_fifo_not_full(
|
|||||||
bus->fifo_size - 2;
|
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(
|
static void imxrt_lpspi_fill_tx_fifo(
|
||||||
struct imxrt_lpspi_bus *bus,
|
struct imxrt_lpspi_bus *bus,
|
||||||
volatile LPSPI_Type *regs
|
volatile LPSPI_Type *regs
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
while(imxrt_lpspi_tx_fifo_not_full(bus, regs)
|
while(imxrt_lpspi_tx_fifo_not_full(bus, regs)
|
||||||
&& bus->remaining_tx_size > 0) {
|
&& (bus->tx_msg_todo > 0 || bus->remaining_tx_size > 0)) {
|
||||||
if (bus->remaining_tx_size == 1) {
|
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);
|
regs->TCR &= ~(LPSPI_TCR_CONT_MASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,29 +233,26 @@ static void imxrt_lpspi_fill_tx_fifo(
|
|||||||
}
|
}
|
||||||
--bus->remaining_tx_size;
|
--bus->remaining_tx_size;
|
||||||
}
|
}
|
||||||
|
if (bus->remaining_tx_size == 0) {
|
||||||
|
--bus->tx_msg_todo;
|
||||||
|
++bus->tx_msg;
|
||||||
|
imxrt_lpspi_next_tx_msg(bus, regs);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void imxrt_lpspi_next_msg(
|
static void imxrt_lpspi_next_rx_msg(
|
||||||
struct imxrt_lpspi_bus *bus,
|
struct imxrt_lpspi_bus *bus,
|
||||||
volatile LPSPI_Type *regs
|
volatile LPSPI_Type *regs
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (bus->msg_todo > 0) {
|
if (bus->rx_msg_todo > 0) {
|
||||||
const spi_ioc_transfer *msg;
|
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->remaining_rx_size = msg->len;
|
||||||
bus->rx_buf = msg->rx_buf;
|
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,9 +261,10 @@ static void imxrt_lpspi_pull_data_from_rx_fifo(
|
|||||||
volatile LPSPI_Type *regs
|
volatile LPSPI_Type *regs
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
while (imxrt_lpspi_rx_fifo_not_empty(regs) && bus->remaining_rx_size > 0) {
|
|
||||||
uint32_t data;
|
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;
|
data = regs->RDR;
|
||||||
if (bus->rx_buf != NULL) {
|
if (bus->rx_buf != NULL) {
|
||||||
*bus->rx_buf = data;
|
*bus->rx_buf = data;
|
||||||
@@ -244,6 +272,12 @@ static void imxrt_lpspi_pull_data_from_rx_fifo(
|
|||||||
}
|
}
|
||||||
--bus->remaining_rx_size;
|
--bus->remaining_rx_size;
|
||||||
}
|
}
|
||||||
|
if (bus->remaining_rx_size == 0) {
|
||||||
|
--bus->rx_msg_todo;
|
||||||
|
++bus->rx_msg;
|
||||||
|
imxrt_lpspi_next_rx_msg(bus, regs);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void imxrt_lpspi_interrupt(void *arg)
|
static void imxrt_lpspi_interrupt(void *arg)
|
||||||
@@ -257,39 +291,22 @@ static void imxrt_lpspi_interrupt(void *arg)
|
|||||||
imxrt_lpspi_pull_data_from_rx_fifo(bus, regs);
|
imxrt_lpspi_pull_data_from_rx_fifo(bus, regs);
|
||||||
imxrt_lpspi_fill_tx_fifo(bus, regs);
|
imxrt_lpspi_fill_tx_fifo(bus, regs);
|
||||||
|
|
||||||
if (bus->remaining_tx_size == 0) {
|
if (bus->tx_msg_todo > 0 || bus->remaining_tx_size > 0) {
|
||||||
if (bus->remaining_rx_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;
|
regs->IER = LPSPI_IER_RDIE_MASK;
|
||||||
} else {
|
} else {
|
||||||
--bus->msg_todo;
|
regs->IER = 0;
|
||||||
++bus->msg;
|
rtems_binary_semaphore_post(&bus->sem);
|
||||||
imxrt_lpspi_next_msg(bus, regs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int imxrt_lpspi_settings_ok(
|
static inline int imxrt_lpspi_settings_ok(
|
||||||
struct imxrt_lpspi_bus *bus,
|
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 */
|
/* most of this is currently just not implemented */
|
||||||
if (msg->cs > 3 ||
|
if (msg->cs > 3 ||
|
||||||
msg->speed_hz > bus->base.max_speed_hz ||
|
msg->speed_hz > bus->base.max_speed_hz ||
|
||||||
@@ -299,6 +316,18 @@ static inline int imxrt_lpspi_settings_ok(
|
|||||||
return -EINVAL;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,17 +337,29 @@ static int imxrt_lpspi_check_messages(
|
|||||||
uint32_t size
|
uint32_t size
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
const spi_ioc_transfer *prev_msg = NULL;
|
||||||
|
|
||||||
while(size > 0) {
|
while(size > 0) {
|
||||||
int rv;
|
int rv;
|
||||||
rv = imxrt_lpspi_settings_ok(bus, msg);
|
rv = imxrt_lpspi_settings_ok(bus, msg, prev_msg);
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prev_msg = msg;
|
||||||
++msg;
|
++msg;
|
||||||
--size;
|
--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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,10 +377,20 @@ static int imxrt_lpspi_transfer(
|
|||||||
rv = imxrt_lpspi_check_messages(bus, msgs, n);
|
rv = imxrt_lpspi_check_messages(bus, msgs, n);
|
||||||
|
|
||||||
if (rv == 0) {
|
if (rv == 0) {
|
||||||
bus->msg_todo = n;
|
bus->tx_msg_todo = n;
|
||||||
bus->msg = &msgs[0];
|
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);
|
rtems_binary_semaphore_wait(&bus->sem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,7 +467,7 @@ static int imxrt_lpspi_setup(spi_bus *base)
|
|||||||
|
|
||||||
bus = (struct imxrt_lpspi_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.
|
* Nothing to do besides checking.
|
||||||
|
|||||||
Reference in New Issue
Block a user