static int sun4i_spi_xfer(struct udevice *dev, unsigned int bitlen, const void *dout, void *din, unsigned long flags) { struct udevice *bus = dev->parent; struct sun4i_spi_priv *priv = dev_get_priv(bus); struct dm_spi_slave_platdata *slave_plat = dev_get_parent_platdata(dev); u32 len = bitlen / 8; u32 reg; u8 nbytes; int ret; priv->tx_buf = dout; priv->rx_buf = din; if (bitlen % 8) { debug("%s: non byte-aligned SPI transfer.\n", __func__); return -ENAVAIL; } if (flags & SPI_XFER_BEGIN) sun4i_spi_set_cs(bus, slave_plat->cs, true); reg = readl(&priv->regs->ctl); /* Reset FIFOs */ writel(reg | SUN4I_CTL_RF_RST | SUN4I_CTL_TF_RST, &priv->regs->ctl); while (len) { /* Setup the transfer now... */ nbytes = min(len, (u32)(SUN4I_FIFO_DEPTH - 1)); /* Setup the counters */ writel(SUN4I_BURST_CNT(nbytes), &priv->regs->bc); writel(SUN4I_XMIT_CNT(nbytes), &priv->regs->tc); /* Fill the TX FIFO */ sun4i_spi_fill_fifo(priv, nbytes); /* Start the transfer */ reg = readl(&priv->regs->ctl); writel(reg | SUN4I_CTL_XCH, &priv->regs->ctl); /* Wait transfer to complete */ ret = wait_for_bit_le32(&priv->regs->ctl, SUN4I_CTL_XCH_MASK, false, SUN4I_SPI_TIMEOUT_US, false); if (ret) { printf("ERROR: sun4i_spi: Timeout transferring data\n"); sun4i_spi_set_cs(bus, slave_plat->cs, false); return ret; } /* Drain the RX FIFO */ sun4i_spi_drain_fifo(priv, nbytes); len -= nbytes; } if (flags & SPI_XFER_END) sun4i_spi_set_cs(bus, slave_plat->cs, false); return 0; }
static int sun4i_spi_transfer_one(struct spi_master *master, struct spi_device *spi, struct spi_transfer *tfr) { struct sun4i_spi *sspi = spi_master_get_devdata(master); unsigned int mclk_rate, div, timeout; unsigned int tx_len = 0; int ret = 0; u32 reg; /* We don't support transfer larger than the FIFO */ if (tfr->len > SUN4I_FIFO_DEPTH) return -EINVAL; reinit_completion(&sspi->done); sspi->tx_buf = tfr->tx_buf; sspi->rx_buf = tfr->rx_buf; sspi->len = tfr->len; /* Clear pending interrupts */ sun4i_spi_write(sspi, SUN4I_INT_STA_REG, ~0); reg = sun4i_spi_read(sspi, SUN4I_CTL_REG); /* Reset FIFOs */ sun4i_spi_write(sspi, SUN4I_CTL_REG, reg | SUN4I_CTL_RF_RST | SUN4I_CTL_TF_RST); /* * Setup the transfer control register: Chip Select, * polarities, etc. */ if (spi->mode & SPI_CPOL) reg |= SUN4I_CTL_CPOL; else reg &= ~SUN4I_CTL_CPOL; if (spi->mode & SPI_CPHA) reg |= SUN4I_CTL_CPHA; else reg &= ~SUN4I_CTL_CPHA; if (spi->mode & SPI_LSB_FIRST) reg |= SUN4I_CTL_LMTF; else reg &= ~SUN4I_CTL_LMTF; /* * If it's a TX only transfer, we don't want to fill the RX * FIFO with bogus data */ if (sspi->rx_buf) reg &= ~SUN4I_CTL_DHB; else reg |= SUN4I_CTL_DHB; /* We want to control the chip select manually */ reg |= SUN4I_CTL_CS_MANUAL; sun4i_spi_write(sspi, SUN4I_CTL_REG, reg); /* Ensure that we have a parent clock fast enough */ mclk_rate = clk_get_rate(sspi->mclk); if (mclk_rate < (2 * spi->max_speed_hz)) { clk_set_rate(sspi->mclk, 2 * spi->max_speed_hz); mclk_rate = clk_get_rate(sspi->mclk); } /* * Setup clock divider. * * We have two choices there. Either we can use the clock * divide rate 1, which is calculated thanks to this formula: * SPI_CLK = MOD_CLK / (2 ^ (cdr + 1)) * Or we can use CDR2, which is calculated with the formula: * SPI_CLK = MOD_CLK / (2 * (cdr + 1)) * Wether we use the former or the latter is set through the * DRS bit. * * First try CDR2, and if we can't reach the expected * frequency, fall back to CDR1. */ div = mclk_rate / (2 * spi->max_speed_hz); if (div <= (SUN4I_CLK_CTL_CDR2_MASK + 1)) { if (div > 0) div--; reg = SUN4I_CLK_CTL_CDR2(div) | SUN4I_CLK_CTL_DRS; } else { div = ilog2(mclk_rate) - ilog2(spi->max_speed_hz); reg = SUN4I_CLK_CTL_CDR1(div); } sun4i_spi_write(sspi, SUN4I_CLK_CTL_REG, reg); /* Setup the transfer now... */ if (sspi->tx_buf) tx_len = tfr->len; /* Setup the counters */ sun4i_spi_write(sspi, SUN4I_BURST_CNT_REG, SUN4I_BURST_CNT(tfr->len)); sun4i_spi_write(sspi, SUN4I_XMIT_CNT_REG, SUN4I_XMIT_CNT(tx_len)); /* Fill the TX FIFO */ sun4i_spi_fill_fifo(sspi, SUN4I_FIFO_DEPTH); /* Enable the interrupts */ sun4i_spi_write(sspi, SUN4I_INT_CTL_REG, SUN4I_INT_CTL_TC); /* Start the transfer */ reg = sun4i_spi_read(sspi, SUN4I_CTL_REG); sun4i_spi_write(sspi, SUN4I_CTL_REG, reg | SUN4I_CTL_XCH); timeout = wait_for_completion_timeout(&sspi->done, msecs_to_jiffies(1000)); if (!timeout) { ret = -ETIMEDOUT; goto out; } sun4i_spi_drain_fifo(sspi, SUN4I_FIFO_DEPTH); out: sun4i_spi_write(sspi, SUN4I_INT_CTL_REG, 0); return ret; }