static int sun4i_spi_set_speed(struct udevice *dev, uint speed) { struct sun4i_spi_platdata *plat = dev_get_platdata(dev); struct sun4i_spi_priv *priv = dev_get_priv(dev); unsigned int div; u32 reg; if (speed > plat->max_hz) speed = plat->max_hz; if (speed < SUN4I_SPI_MIN_RATE) speed = SUN4I_SPI_MIN_RATE; /* * 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)) * Whether 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 = SUN4I_SPI_MAX_RATE / (2 * speed); reg = readl(&priv->regs->cctl); if (div <= (SUN4I_CLK_CTL_CDR2_MASK + 1)) { if (div > 0) div--; reg &= ~(SUN4I_CLK_CTL_CDR2_MASK | SUN4I_CLK_CTL_DRS); reg |= SUN4I_CLK_CTL_CDR2(div) | SUN4I_CLK_CTL_DRS; } else { div = __ilog2(SUN4I_SPI_MAX_RATE) - __ilog2(speed); reg &= ~((SUN4I_CLK_CTL_CDR1_MASK << 8) | SUN4I_CLK_CTL_DRS); reg |= SUN4I_CLK_CTL_CDR1(div); } priv->freq = speed; writel(reg, &priv->regs->cctl); 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; }