tegra124: strict error detection and reporting for SPI

This re-factors the SPI driver to be more pedantic about spotting
errors and reporting them.

BUG=none
BRANCH=none
TEST=tested on nyan

Change-Id: Ice51ff67b15c677826a201f2e35afe9707708b03
Signed-off-by: David Hendricks <dhendrix@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/173681
Reviewed-by: Gabe Black <gabeblack@chromium.org>
Commit-Queue: Gabe Black <gabeblack@chromium.org>
Tested-by: Gabe Black <gabeblack@chromium.org>
This commit is contained in:
David Hendricks 2013-10-17 21:35:39 -07:00 committed by chrome-internal-fetch
commit c056fa954e

View file

@ -250,7 +250,7 @@ void spi_cs_deactivate(struct spi_slave *slave)
write32(val, &regs->command1);
}
static void print_fifo_status(struct tegra_spi_channel *spi)
static void dump_fifo_status(struct tegra_spi_channel *spi)
{
u32 status = read32(&spi->regs->fifo_status);
@ -316,69 +316,121 @@ static void dump_dma_regs(struct apb_dma_channel *dma)
read32(&dma->regs->word_transfer));
}
static void dump_regs(struct tegra_spi_channel *spi,
struct apb_dma_channel *dma)
{
if (dma)
dump_dma_regs(dma);
if (spi) {
dump_spi_regs(spi);
dump_fifo_status(spi);
}
}
static int fifo_error(struct tegra_spi_channel *spi)
{
return read32(&spi->regs->fifo_status) & SPI_FIFO_STATUS_ERR ? 1 : 0;
}
static inline unsigned int spi_byte_count(struct tegra_spi_channel *spi)
{
/* FIXME: Make this take total packet size into account */
return read32(&spi->regs->trans_status) &
(SPI_STATUS_BLOCK_COUNT << SPI_STATUS_BLOCK_COUNT_SHIFT);
}
static int tegra_spi_fifo_receive(struct tegra_spi_channel *spi,
u8 *din, unsigned int in_bytes)
{
unsigned int remaining;
unsigned int received = 0, remaining = in_bytes;
printk(BIOS_SPEW, "%s: Receiving %d bytes\n", __func__, in_bytes);
setbits_le32(&spi->regs->command1, SPI_CMD1_RX_EN);
while (in_bytes) {
remaining = MIN(in_bytes, SPI_MAX_TRANSFER_BYTES_FIFO);
in_bytes -= remaining;
while (remaining) {
unsigned int from_fifo;
from_fifo = MIN(in_bytes, SPI_MAX_TRANSFER_BYTES_FIFO);
remaining -= from_fifo;
/* BLOCK_SIZE in SPI_DMA_BLK register applies to both DMA and
* PIO transfers */
write32(remaining - 1, &spi->regs->dma_blk);
write32(from_fifo - 1, &spi->regs->dma_blk);
setbits_le32(&spi->regs->trans_status, SPI_STATUS_RDY);
setbits_le32(&spi->regs->command1, SPI_CMD1_GO);
while ((read32(&spi->regs->trans_status) &
SPI_STATUS_BLOCK_COUNT) != remaining)
;
/* FIXME: delay loops should be "thread" friendly */
while (spi_byte_count(spi) != from_fifo) {
if (fifo_error(spi))
goto done;
}
while (remaining) {
received += from_fifo;
while (from_fifo) {
*din = read8(&spi->regs->rx_fifo);
din++;
remaining--;
from_fifo--;
}
}
done:
clrbits_le32(&spi->regs->command1, SPI_CMD1_RX_EN);
if ((received != in_bytes) || fifo_error(spi)) {
printk(BIOS_ERR, "%s: ERROR: Received %u bytes, expected %u\n",
__func__, received, in_bytes);
dump_regs(spi, NULL);
return -1;
}
return in_bytes;
}
static int tegra_spi_fifo_send(struct tegra_spi_channel *spi,
const u8 *dout, unsigned int out_bytes)
{
unsigned int remaining;
unsigned int sent = 0, remaining = out_bytes;
printk(BIOS_SPEW, "%s: Sending %d bytes\n", __func__, out_bytes);
setbits_le32(&spi->regs->command1, SPI_CMD1_TX_EN);
while (out_bytes) {
remaining = MIN(out_bytes, SPI_MAX_TRANSFER_BYTES_FIFO);
out_bytes -= remaining;
while (remaining) {
unsigned int to_fifo, tmp;
to_fifo = MIN(out_bytes, SPI_MAX_TRANSFER_BYTES_FIFO);
/* BLOCK_SIZE in SPI_DMA_BLK register applies to both DMA and
* PIO transfers */
write32(remaining - 1, &spi->regs->dma_blk);
write32(to_fifo - 1, &spi->regs->dma_blk);
while (remaining) {
tmp = to_fifo;
while (tmp) {
write32(*dout, &spi->regs->tx_fifo);
dout++;
remaining--;
tmp--;
}
setbits_le32(&spi->regs->trans_status, SPI_STATUS_RDY);
setbits_le32(&spi->regs->command1, SPI_CMD1_GO);
/* FIXME: delay loops should be "thread" friendly */
while (!(read32(&spi->regs->fifo_status) &
SPI_FIFO_STATUS_TX_FIFO_EMPTY))
;
SPI_FIFO_STATUS_TX_FIFO_EMPTY)) {
if (fifo_error(spi))
goto done;
}
remaining -= to_fifo;
sent += to_fifo;
}
done:
clrbits_le32(&spi->regs->command1, SPI_CMD1_TX_EN);
if ((sent != out_bytes) || fifo_error(spi)) {
printk(BIOS_ERR, "%s: ERROR: Sent %u bytes, expected "
"to send %u\n", __func__, sent, out_bytes);
dump_regs(spi, NULL);
return -1;
}
return out_bytes;
}
@ -450,8 +502,7 @@ static int tegra_spi_dma_receive(struct tegra_spi_channel *spi,
dma_start(dma);
/* FIXME: delay loops should be "thread" friendly */
while ((read32(&spi->regs->trans_status) &
SPI_STATUS_BLOCK_COUNT) != in_bytes)
while (spi_byte_count(spi) != in_bytes)
;
clrbits_le32(&spi->regs->command1, SPI_CMD1_RX_EN);
@ -461,8 +512,12 @@ static int tegra_spi_dma_receive(struct tegra_spi_channel *spi,
dma_stop(dma);
dma_release(dma);
if (read32(&spi->regs->fifo_status) & SPI_FIFO_STATUS_ERR)
dump_dma_regs(dma);
if ((spi_byte_count(spi) != in_bytes) || fifo_error(spi)) {
printk(BIOS_ERR, "%s: ERROR: Received %u bytes, expected %u\n",
__func__, spi_byte_count(spi), in_bytes);
dump_regs(spi, dma);
return -1;
}
return in_bytes;
}
@ -508,15 +563,18 @@ static int tegra_spi_dma_send(struct tegra_spi_channel *spi,
dma_busy(dma))
;
dma_stop(dma);
while ((read32(&spi->regs->trans_status) &
SPI_STATUS_BLOCK_COUNT) != out_bytes)
while (spi_byte_count(spi) != out_bytes)
;
clrbits_le32(&spi->regs->command1, SPI_CMD1_TX_EN);
dma_release(dma);
if (read32(&spi->regs->fifo_status) & SPI_FIFO_STATUS_ERR)
dump_dma_regs(dma);
if ((spi_byte_count(spi) != out_bytes) || fifo_error(spi)) {
printk(BIOS_ERR, "%s: ERROR: Sent %u bytes, expected %u\n",
__func__, spi_byte_count(spi), out_bytes);
dump_regs(spi, dma);
return -1;
}
return out_bytes;
}
@ -540,7 +598,8 @@ int spi_xfer(struct spi_slave *slave, const void *dout, unsigned int bitsout,
/*
* DMA operates on 4 bytes at a time, so to avoid accessing memory
* outside the specified buffers we'll only use DMA for 4-byte aligned
* transactions accesses and transfer remaining bytes using FIFO access.
* transactions accesses and transfer remaining bytes manually using
* the Rx/Tx FIFOs.
*/
while (out_bytes > 0) {
@ -551,20 +610,23 @@ int spi_xfer(struct spi_slave *slave, const void *dout, unsigned int bitsout,
dma_out -= fifo_out;
if (dma_out) {
tegra_spi_dma_send(spi, out_buf, dma_out);
ret = tegra_spi_dma_send(spi, out_buf, dma_out);
if (ret != dma_out) {
ret = -1;
goto spi_xfer_exit;
}
out_buf += dma_out;
out_bytes -= dma_out;
}
if (fifo_out) {
tegra_spi_fifo_send(spi, out_buf, fifo_out);
ret = tegra_spi_fifo_send(spi, out_buf, fifo_out);
if (ret != fifo_out) {
ret = -1;
goto spi_xfer_exit;
}
out_buf += fifo_out;
out_bytes -= fifo_out;
}
if (read32(&spi->regs->fifo_status) & SPI_FIFO_STATUS_ERR) {
ret = -1;
goto spi_xfer_exit;
}
}
while (in_bytes > 0) {
@ -575,28 +637,30 @@ int spi_xfer(struct spi_slave *slave, const void *dout, unsigned int bitsout,
dma_in -= fifo_in;
if (dma_in) {
tegra_spi_dma_receive(spi, in_buf, dma_in);
ret = tegra_spi_dma_receive(spi, in_buf, dma_in);
if (ret != dma_in) {
ret = -1;
goto spi_xfer_exit;
}
in_buf += dma_in;
in_bytes -= dma_in;
}
if (fifo_in) {
tegra_spi_fifo_receive(spi, in_buf, fifo_in);
ret = tegra_spi_fifo_receive(spi, in_buf, fifo_in);
if (ret != fifo_in) {
ret = -1;
goto spi_xfer_exit;
}
in_buf += fifo_in;
in_bytes -= fifo_in;
}
if (read32(&spi->regs->fifo_status) & SPI_FIFO_STATUS_ERR) {
ret = -1;
break;
}
}
ret = 0;
spi_xfer_exit:
if (ret < 0) {
dump_spi_regs(spi);
print_fifo_status(spi);
if (ret < 0)
clear_fifo_status(spi);
}
return ret;
}