/* * Do a dcache operation by virtual address. This is useful for * maintaining coherency in drivers which do DMA transfers and only need to * perform cache maintenance on a particular memory range rather than the * entire cache. */ static void dcache_op_va(void const *addr, size_t len, enum dcache_op op) { unsigned long line, linesize; linesize = dcache_line_bytes(); line = (uintptr_t)addr & ~(linesize - 1); dsb(); while (line < (uintptr_t)addr + len) { switch(op) { case OP_DCCIVAC: dccivac(line); break; case OP_DCCVAC: dccvac(line); break; case OP_DCIVAC: dcivac(line); break; default: break; } line += linesize; } isb(); }
/* * xfer_setup() prepares a transfer. It does sanity checking, alignment, and * sets transfer mode used by this channel (if not set already). * * A few caveats to watch out for: * - The number of bytes which can be transferred may be smaller than the * number of bytes the caller specifies. The number of bytes ready for * a transfer will be returned (unless an error occurs). * * - Only one mode can be used for both RX and TX. The transfer mode of the * SPI channel (spi->xfer_mode) is checked each time this function is called. * If conflicting modes are detected, spi->xfer_mode will be set to * XFER_MODE_NONE and an error will be returned. * * Returns bytes ready for transfer if successful, <0 to indicate error. */ static int xfer_setup(struct tegra_spi_channel *spi, void *buf, unsigned int bytes, enum spi_direction dir) { unsigned int line_size = dcache_line_bytes(); unsigned int align; int ret = -1; if (!bytes) return 0; if (dir == SPI_SEND) spi->out_buf = buf; else if (dir == SPI_RECEIVE) spi->in_buf = buf; /* * Alignment consideratons: * When we enable caching we'll need to clean/invalidate portions of * memory. So we need to be careful about memory alignment. Also, DMA * likes to operate on 4-bytes at a time on the AHB side. So for * example, if we only want to receive 1 byte, 4 bytes will be be * written in memory even if those extra 3 bytes are beyond the length * we want. * * For now we'll use PIO to send/receive unaligned bytes. We may * consider setting aside some space for a kind of bounce buffer to * stay in DMA mode once we have a chance to benchmark the two * approaches. */ if (bytes < line_size) { if (spi->xfer_mode == XFER_MODE_DMA) { spi->xfer_mode = XFER_MODE_NONE; ret = -1; } else { spi->xfer_mode = XFER_MODE_PIO; ret = tegra_spi_pio_prepare(spi, bytes, dir); } goto done; } /* transfer bytes before the aligned boundary */ align = line_size - ((uintptr_t)buf % line_size); if ((align != 0) && (align != line_size)) { if (spi->xfer_mode == XFER_MODE_DMA) { spi->xfer_mode = XFER_MODE_NONE; ret = -1; } else { spi->xfer_mode = XFER_MODE_PIO; ret = tegra_spi_pio_prepare(spi, align, dir); } goto done; } /* do aligned DMA transfer */ align = (((uintptr_t)buf + bytes) % line_size); if (bytes - align > 0) { unsigned int dma_bytes = bytes - align; if (spi->xfer_mode == XFER_MODE_PIO) { spi->xfer_mode = XFER_MODE_NONE; ret = -1; } else { spi->xfer_mode = XFER_MODE_DMA; ret = tegra_spi_dma_prepare(spi, dma_bytes, dir); } goto done; } /* transfer any remaining unaligned bytes */ if (align) { if (spi->xfer_mode == XFER_MODE_DMA) { spi->xfer_mode = XFER_MODE_NONE; ret = -1; } else { spi->xfer_mode = XFER_MODE_PIO; ret = tegra_spi_pio_prepare(spi, align, dir); } goto done; } done: return ret; }