X-Git-Url: https://git.kernelconcepts.de/?a=blobdiff_plain;f=drivers%2Fspi%2Fmxs_spi.c;h=a037c130efa5c09557c4be0a5552129222fc2759;hb=fa7a51cb8272bd6076ea4701fd6bdc65a68703ba;hp=a21b02b7f42a55eba389f870506e52817e5efe8b;hpb=c7065fa824ab30108e1964bf1060eacb4323a85e;p=karo-tx-uboot.git diff --git a/drivers/spi/mxs_spi.c b/drivers/spi/mxs_spi.c index a21b02b7f4..a037c130ef 100644 --- a/drivers/spi/mxs_spi.c +++ b/drivers/spi/mxs_spi.c @@ -31,17 +31,32 @@ #include #include #include +#include #define MXS_SPI_MAX_TIMEOUT 1000000 #define MXS_SPI_PORT_OFFSET 0x2000 #define MXS_SSP_CHIPSELECT_MASK 0x00300000 #define MXS_SSP_CHIPSELECT_SHIFT 20 +#define MXSSSP_SMALL_TRANSFER 512 + +/* + * CONFIG_MXS_SPI_DMA_ENABLE: Experimental mixed PIO/DMA support for MXS SPI + * host. Use with utmost caution! + * + * Enabling this is not yet recommended since this + * still doesn't support transfers to/from unaligned + * addresses. Therefore this driver will not work + * for example with saving environment. This is + * caused by DMA alignment constraints on MXS. + */ + struct mxs_spi_slave { struct spi_slave slave; uint32_t max_khz; uint32_t mode; - struct mx28_ssp_regs *regs; + struct mxs_ssp_regs *regs; + struct mxs_dma_desc *desc; }; static inline struct mxs_spi_slave *to_mxs_slave(struct spi_slave *slave) @@ -67,8 +82,9 @@ struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, { struct mxs_spi_slave *mxs_slave; uint32_t addr; - struct mx28_ssp_regs *ssp_regs; + struct mxs_ssp_regs *ssp_regs; int reg; + struct mxs_dma_desc *desc; if (!spi_cs_is_valid(bus, cs)) { printf("mxs_spi: invalid bus %d / chip select %d\n", bus, cs); @@ -79,13 +95,21 @@ struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, if (!mxs_slave) return NULL; + desc = mxs_dma_desc_alloc(); + if (!desc) + goto err_desc; + + if (mxs_dma_init_channel(bus)) + goto err_init; + addr = MXS_SSP0_BASE + (bus * MXS_SPI_PORT_OFFSET); mxs_slave->slave.bus = bus; mxs_slave->slave.cs = cs; mxs_slave->max_khz = max_hz / 1000; mxs_slave->mode = mode; - mxs_slave->regs = (struct mx28_ssp_regs *)addr; + mxs_slave->regs = (struct mxs_ssp_regs *)addr; + mxs_slave->desc = desc; ssp_regs = mxs_slave->regs; reg = readl(&ssp_regs->hw_ssp_ctrl0); @@ -94,21 +118,28 @@ struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, writel(reg, &ssp_regs->hw_ssp_ctrl0); return &mxs_slave->slave; + +err_init: + mxs_dma_desc_free(desc); +err_desc: + free(mxs_slave); + return NULL; } void spi_free_slave(struct spi_slave *slave) { struct mxs_spi_slave *mxs_slave = to_mxs_slave(slave); + mxs_dma_desc_free(mxs_slave->desc); free(mxs_slave); } int spi_claim_bus(struct spi_slave *slave) { struct mxs_spi_slave *mxs_slave = to_mxs_slave(slave); - struct mx28_ssp_regs *ssp_regs = mxs_slave->regs; + struct mxs_ssp_regs *ssp_regs = mxs_slave->regs; uint32_t reg = 0; - mx28_reset_block(&ssp_regs->hw_ssp_ctrl0_reg); + mxs_reset_block(&ssp_regs->hw_ssp_ctrl0_reg); writel(SSP_CTRL0_BUS_WIDTH_ONE_BIT, &ssp_regs->hw_ssp_ctrl0); @@ -128,59 +159,31 @@ void spi_release_bus(struct spi_slave *slave) { } -static void mxs_spi_start_xfer(struct mx28_ssp_regs *ssp_regs) +static void mxs_spi_start_xfer(struct mxs_ssp_regs *ssp_regs) { writel(SSP_CTRL0_LOCK_CS, &ssp_regs->hw_ssp_ctrl0_set); writel(SSP_CTRL0_IGNORE_CRC, &ssp_regs->hw_ssp_ctrl0_clr); } -static void mxs_spi_end_xfer(struct mx28_ssp_regs *ssp_regs) +static void mxs_spi_end_xfer(struct mxs_ssp_regs *ssp_regs) { writel(SSP_CTRL0_LOCK_CS, &ssp_regs->hw_ssp_ctrl0_clr); writel(SSP_CTRL0_IGNORE_CRC, &ssp_regs->hw_ssp_ctrl0_set); } -int spi_xfer(struct spi_slave *slave, unsigned int bitlen, - const void *dout, void *din, unsigned long flags) +static int mxs_spi_xfer_pio(struct mxs_spi_slave *slave, + char *data, int length, int write, unsigned long flags) { - struct mxs_spi_slave *mxs_slave = to_mxs_slave(slave); - struct mx28_ssp_regs *ssp_regs = mxs_slave->regs; - int len = bitlen / 8; - char dummy; - int write = 0; - char *data = NULL; - - if (bitlen == 0) { - if (flags & SPI_XFER_END) { - din = (void *)&dummy; - len = 1; - } else - return 0; - } - - /* Half-duplex only */ - if (din && dout) - return -EINVAL; - /* No data */ - if (!din && !dout) - return 0; - - if (dout) { - data = (char *)dout; - write = 1; - } else if (din) { - data = (char *)din; - write = 0; - } + struct mxs_ssp_regs *ssp_regs = slave->regs; if (flags & SPI_XFER_BEGIN) mxs_spi_start_xfer(ssp_regs); - while (len--) { + while (length--) { /* We transfer 1 byte */ writel(1, &ssp_regs->hw_ssp_xfer_size); - if ((flags & SPI_XFER_END) && !len) + if ((flags & SPI_XFER_END) && !length) mxs_spi_end_xfer(ssp_regs); if (write) @@ -190,7 +193,7 @@ int spi_xfer(struct spi_slave *slave, unsigned int bitlen, writel(SSP_CTRL0_RUN, &ssp_regs->hw_ssp_ctrl0_set); - if (mx28_wait_mask_set(&ssp_regs->hw_ssp_ctrl0_reg, + if (mxs_wait_mask_set(&ssp_regs->hw_ssp_ctrl0_reg, SSP_CTRL0_RUN, MXS_SPI_MAX_TIMEOUT)) { printf("MXS SPI: Timeout waiting for start\n"); return -ETIMEDOUT; @@ -202,7 +205,7 @@ int spi_xfer(struct spi_slave *slave, unsigned int bitlen, writel(SSP_CTRL0_DATA_XFER, &ssp_regs->hw_ssp_ctrl0_set); if (!write) { - if (mx28_wait_mask_clr(&ssp_regs->hw_ssp_status_reg, + if (mxs_wait_mask_clr(&ssp_regs->hw_ssp_status_reg, SSP_STATUS_FIFO_EMPTY, MXS_SPI_MAX_TIMEOUT)) { printf("MXS SPI: Timeout waiting for data\n"); return -ETIMEDOUT; @@ -212,7 +215,7 @@ int spi_xfer(struct spi_slave *slave, unsigned int bitlen, data++; } - if (mx28_wait_mask_clr(&ssp_regs->hw_ssp_ctrl0_reg, + if (mxs_wait_mask_clr(&ssp_regs->hw_ssp_ctrl0_reg, SSP_CTRL0_RUN, MXS_SPI_MAX_TIMEOUT)) { printf("MXS SPI: Timeout waiting for finish\n"); return -ETIMEDOUT; @@ -221,3 +224,122 @@ int spi_xfer(struct spi_slave *slave, unsigned int bitlen, return 0; } + +static int mxs_spi_xfer_dma(struct mxs_spi_slave *slave, + char *data, int length, int write, unsigned long flags) +{ + struct mxs_dma_desc *desc = slave->desc; + struct mxs_ssp_regs *ssp_regs = slave->regs; + uint32_t ctrl0 = SSP_CTRL0_DATA_XFER; + uint32_t cache_data_count; + int dmach; + + memset(desc, 0, sizeof(struct mxs_dma_desc)); + desc->address = (dma_addr_t)desc; + + if (flags & SPI_XFER_BEGIN) + ctrl0 |= SSP_CTRL0_LOCK_CS; + if (flags & SPI_XFER_END) + ctrl0 |= SSP_CTRL0_IGNORE_CRC; + if (!write) + ctrl0 |= SSP_CTRL0_READ; + + writel(length, &ssp_regs->hw_ssp_xfer_size); + + if (length % ARCH_DMA_MINALIGN) + cache_data_count = roundup(length, ARCH_DMA_MINALIGN); + else + cache_data_count = length; + + if (!write) { + slave->desc->cmd.data = MXS_DMA_DESC_COMMAND_DMA_WRITE; + slave->desc->cmd.address = (dma_addr_t)data; + } else { + slave->desc->cmd.data = MXS_DMA_DESC_COMMAND_DMA_READ; + slave->desc->cmd.address = (dma_addr_t)data; + + /* Flush data to DRAM so DMA can pick them up */ + flush_dcache_range((uint32_t)data, + (uint32_t)(data + cache_data_count)); + } + + slave->desc->cmd.data |= MXS_DMA_DESC_IRQ | MXS_DMA_DESC_DEC_SEM | + (length << MXS_DMA_DESC_BYTES_OFFSET) | + (1 << MXS_DMA_DESC_PIO_WORDS_OFFSET) | + MXS_DMA_DESC_WAIT4END; + + slave->desc->cmd.pio_words[0] = ctrl0; + + dmach = MXS_DMA_CHANNEL_AHB_APBH_SSP0 + slave->slave.bus; + mxs_dma_desc_append(dmach, slave->desc); + if (mxs_dma_go(dmach)) + return -EINVAL; + + /* The data arrived into DRAM, invalidate cache over them */ + if (!write) { + invalidate_dcache_range((uint32_t)data, + (uint32_t)(data + cache_data_count)); + } + + return 0; +} + +int spi_xfer(struct spi_slave *slave, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + struct mxs_spi_slave *mxs_slave = to_mxs_slave(slave); + struct mxs_ssp_regs *ssp_regs = mxs_slave->regs; + int len = bitlen / 8; + char dummy; + int write = 0; + char *data = NULL; + +#ifdef CONFIG_MXS_SPI_DMA_ENABLE + int dma = 1; +#else + int dma = 0; +#endif + + if (bitlen == 0) { + if (flags & SPI_XFER_END) { + din = (void *)&dummy; + len = 1; + } else + return 0; + } + + /* Half-duplex only */ + if (din && dout) + return -EINVAL; + /* No data */ + if (!din && !dout) + return 0; + + if (dout) { + data = (char *)dout; + write = 1; + } else if (din) { + data = (char *)din; + write = 0; + } + + /* + * Check for alignment, if the buffer is aligned, do DMA transfer, + * PIO otherwise. This is a temporary workaround until proper bounce + * buffer is in place. + */ + if (dma) { + if (((uint32_t)data) & (ARCH_DMA_MINALIGN - 1)) + dma = 0; + if (((uint32_t)len) & (ARCH_DMA_MINALIGN - 1)) + dma = 0; + } + + if (!dma || (len < MXSSSP_SMALL_TRANSFER)) { + writel(SSP_CTRL1_DMA_ENABLE, &ssp_regs->hw_ssp_ctrl1_clr); + return mxs_spi_xfer_pio(mxs_slave, data, len, write, flags); + } else { + writel(SSP_CTRL1_DMA_ENABLE, &ssp_regs->hw_ssp_ctrl1_set); + return mxs_spi_xfer_dma(mxs_slave, data, len, write, flags); + } +}