Commit f4fade12 authored by Rhyland Klein's avatar Rhyland Klein Committed by Mark Brown

spi/tegra114: Correct support for cs_change

The tegra114 driver wasn't currently handling the cs_change
functionality. cs_change is meant to invert the decisions of whether
or not to deactivate CS after each transfer. Without cs_change, after
every transfer (other than the last in the message) the normal behavior
is to leave CS active. For the last transfer, normally CS is
deactivated when the transfer is complete.

With cs_change set on a transfer (other than last one) CS would be
deactivated and the next transfer would need to activate it again. If
cs_change was set on the last tranfer in a message, then CS would be
left active when the message compeleted.

Also, this builds in logic so that if a different device tries to start
a transfer while CS is active from a different device, it will abort the
previous transfer and start a new one for the new device.

This splits tegra_spi_start_transfer_one into 2 functions, the new one
being tegra_spi_setup_transfer_one. The setup function is safe to call
on all transfers, sets up for the transfer, and handles the special case
of the first transfer in a message. In this special case, it needs to
know whether or not it needs to activate CS.

This work was based on the spi-atmel driver.
Signed-off-by: default avatarRhyland Klein <rklein@nvidia.com>
Signed-off-by: default avatarMark Brown <broonie@linaro.org>
parent 4a10c2ac
...@@ -182,6 +182,7 @@ struct tegra_spi_data { ...@@ -182,6 +182,7 @@ struct tegra_spi_data {
u32 cur_speed; u32 cur_speed;
struct spi_device *cur_spi; struct spi_device *cur_spi;
struct spi_device *cs_control;
unsigned cur_pos; unsigned cur_pos;
unsigned cur_len; unsigned cur_len;
unsigned words_per_32bit; unsigned words_per_32bit;
...@@ -676,15 +677,12 @@ static void tegra_spi_deinit_dma_param(struct tegra_spi_data *tspi, ...@@ -676,15 +677,12 @@ static void tegra_spi_deinit_dma_param(struct tegra_spi_data *tspi,
dma_release_channel(dma_chan); dma_release_channel(dma_chan);
} }
static int tegra_spi_start_transfer_one(struct spi_device *spi, static unsigned long tegra_spi_setup_transfer_one(struct spi_device *spi,
struct spi_transfer *t, bool is_first_of_msg, struct spi_transfer *t, bool is_first_of_msg)
bool is_single_xfer)
{ {
struct tegra_spi_data *tspi = spi_master_get_devdata(spi->master); struct tegra_spi_data *tspi = spi_master_get_devdata(spi->master);
u32 speed = t->speed_hz; u32 speed = t->speed_hz;
u8 bits_per_word = t->bits_per_word; u8 bits_per_word = t->bits_per_word;
unsigned total_fifo_words;
int ret;
unsigned long command1; unsigned long command1;
int req_mode; int req_mode;
...@@ -698,7 +696,6 @@ static int tegra_spi_start_transfer_one(struct spi_device *spi, ...@@ -698,7 +696,6 @@ static int tegra_spi_start_transfer_one(struct spi_device *spi,
tspi->cur_rx_pos = 0; tspi->cur_rx_pos = 0;
tspi->cur_tx_pos = 0; tspi->cur_tx_pos = 0;
tspi->curr_xfer = t; tspi->curr_xfer = t;
total_fifo_words = tegra_spi_calculate_curr_xfer_param(spi, tspi, t);
if (is_first_of_msg) { if (is_first_of_msg) {
tegra_spi_clear_status(tspi); tegra_spi_clear_status(tspi);
...@@ -717,7 +714,12 @@ static int tegra_spi_start_transfer_one(struct spi_device *spi, ...@@ -717,7 +714,12 @@ static int tegra_spi_start_transfer_one(struct spi_device *spi,
else if (req_mode == SPI_MODE_3) else if (req_mode == SPI_MODE_3)
command1 |= SPI_CONTROL_MODE_3; command1 |= SPI_CONTROL_MODE_3;
tegra_spi_writel(tspi, command1, SPI_COMMAND1); if (tspi->cs_control) {
if (tspi->cs_control != spi)
tegra_spi_writel(tspi, command1, SPI_COMMAND1);
tspi->cs_control = NULL;
} else
tegra_spi_writel(tspi, command1, SPI_COMMAND1);
command1 |= SPI_CS_SW_HW; command1 |= SPI_CS_SW_HW;
if (spi->mode & SPI_CS_HIGH) if (spi->mode & SPI_CS_HIGH)
...@@ -732,6 +734,18 @@ static int tegra_spi_start_transfer_one(struct spi_device *spi, ...@@ -732,6 +734,18 @@ static int tegra_spi_start_transfer_one(struct spi_device *spi,
command1 |= SPI_BIT_LENGTH(bits_per_word - 1); command1 |= SPI_BIT_LENGTH(bits_per_word - 1);
} }
return command1;
}
static int tegra_spi_start_transfer_one(struct spi_device *spi,
struct spi_transfer *t, unsigned long command1)
{
struct tegra_spi_data *tspi = spi_master_get_devdata(spi->master);
unsigned total_fifo_words;
int ret;
total_fifo_words = tegra_spi_calculate_curr_xfer_param(spi, tspi, t);
if (tspi->is_packed) if (tspi->is_packed)
command1 |= SPI_PACKED; command1 |= SPI_PACKED;
...@@ -803,29 +817,50 @@ static int tegra_spi_setup(struct spi_device *spi) ...@@ -803,29 +817,50 @@ static int tegra_spi_setup(struct spi_device *spi)
return 0; return 0;
} }
static void tegra_spi_transfer_delay(int delay)
{
if (!delay)
return;
if (delay >= 1000)
mdelay(delay / 1000);
udelay(delay % 1000);
}
static int tegra_spi_transfer_one_message(struct spi_master *master, static int tegra_spi_transfer_one_message(struct spi_master *master,
struct spi_message *msg) struct spi_message *msg)
{ {
bool is_first_msg = true; bool is_first_msg = true;
int single_xfer;
struct tegra_spi_data *tspi = spi_master_get_devdata(master); struct tegra_spi_data *tspi = spi_master_get_devdata(master);
struct spi_transfer *xfer; struct spi_transfer *xfer;
struct spi_device *spi = msg->spi; struct spi_device *spi = msg->spi;
int ret; int ret;
bool skip = false;
msg->status = 0; msg->status = 0;
msg->actual_length = 0; msg->actual_length = 0;
single_xfer = list_is_singular(&msg->transfers);
list_for_each_entry(xfer, &msg->transfers, transfer_list) { list_for_each_entry(xfer, &msg->transfers, transfer_list) {
unsigned long cmd1;
INIT_COMPLETION(tspi->xfer_completion); INIT_COMPLETION(tspi->xfer_completion);
ret = tegra_spi_start_transfer_one(spi, xfer,
is_first_msg, single_xfer); cmd1 = tegra_spi_setup_transfer_one(spi, xfer, is_first_msg);
if (!xfer->len) {
ret = 0;
skip = true;
goto complete_xfer;
}
ret = tegra_spi_start_transfer_one(spi, xfer, cmd1);
if (ret < 0) { if (ret < 0) {
dev_err(tspi->dev, dev_err(tspi->dev,
"spi can not start transfer, err %d\n", ret); "spi can not start transfer, err %d\n", ret);
goto exit; goto complete_xfer;
} }
is_first_msg = false; is_first_msg = false;
ret = wait_for_completion_timeout(&tspi->xfer_completion, ret = wait_for_completion_timeout(&tspi->xfer_completion,
SPI_DMA_TIMEOUT); SPI_DMA_TIMEOUT);
...@@ -833,24 +868,40 @@ static int tegra_spi_transfer_one_message(struct spi_master *master, ...@@ -833,24 +868,40 @@ static int tegra_spi_transfer_one_message(struct spi_master *master,
dev_err(tspi->dev, dev_err(tspi->dev,
"spi trasfer timeout, err %d\n", ret); "spi trasfer timeout, err %d\n", ret);
ret = -EIO; ret = -EIO;
goto exit; goto complete_xfer;
} }
if (tspi->tx_status || tspi->rx_status) { if (tspi->tx_status || tspi->rx_status) {
dev_err(tspi->dev, "Error in Transfer\n"); dev_err(tspi->dev, "Error in Transfer\n");
ret = -EIO; ret = -EIO;
goto exit; goto complete_xfer;
} }
msg->actual_length += xfer->len; msg->actual_length += xfer->len;
if (xfer->cs_change && xfer->delay_usecs) {
complete_xfer:
if (ret < 0 || skip) {
tegra_spi_writel(tspi, tspi->def_command1_reg, tegra_spi_writel(tspi, tspi->def_command1_reg,
SPI_COMMAND1); SPI_COMMAND1);
udelay(xfer->delay_usecs); tegra_spi_transfer_delay(xfer->delay_usecs);
goto exit;
} else if (msg->transfers.prev == &xfer->transfer_list) {
/* This is the last transfer in message */
if (xfer->cs_change)
tspi->cs_control = spi;
else {
tegra_spi_writel(tspi, tspi->def_command1_reg,
SPI_COMMAND1);
tegra_spi_transfer_delay(xfer->delay_usecs);
}
} else if (xfer->cs_change) {
tegra_spi_writel(tspi, tspi->def_command1_reg,
SPI_COMMAND1);
tegra_spi_transfer_delay(xfer->delay_usecs);
} }
} }
ret = 0; ret = 0;
exit: exit:
tegra_spi_writel(tspi, tspi->def_command1_reg, SPI_COMMAND1);
msg->status = ret; msg->status = ret;
spi_finalize_current_message(master); spi_finalize_current_message(master);
return ret; return ret;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment