Commit 43969aa5 authored by Marc Kleine-Budde's avatar Marc Kleine-Budde Committed by Greg Kroah-Hartman

spi: spi-sun6i: sun6i_spi_transfer_one(): fix setting of clock rate

[ Upstream commit ed7815db ]

A SPI transfer defines the _maximum_ speed of the SPI transfer. However the
driver doesn't take into account that the clock divider is always rounded down
(due to integer arithmetics). This results in a too high clock rate for the SPI
transfer.

E.g.: with a mclk_rate of 24 MHz and a SPI transfer speed of 10 MHz, the
original code calculates a reg of "0", which results in a effective divider of
"2" and a 12 MHz clock for the SPI transfer.

This patch fixes the issue by using DIV_ROUND_UP() instead of a plain
integer division.

While there simplify the divider calculation for the CDR1 case, use
order_base_2() instead of two ilog2() calculations.

Fixes: 3558fe90 ("spi: sunxi: Add Allwinner A31 SPI controller driver")
Signed-off-by: default avatarMarc Kleine-Budde <mkl@pengutronix.de>
Acked-by: default avatarMaxime Ripard <mripard@kernel.org>
Link: https://lore.kernel.org/r/20200706143443.9855-2-mkl@pengutronix.deSigned-off-by: default avatarMark Brown <broonie@kernel.org>
Signed-off-by: default avatarSasha Levin <sashal@kernel.org>
parent a34d3075
...@@ -202,7 +202,7 @@ static int sun6i_spi_transfer_one(struct spi_master *master, ...@@ -202,7 +202,7 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
struct spi_transfer *tfr) struct spi_transfer *tfr)
{ {
struct sun6i_spi *sspi = spi_master_get_devdata(master); struct sun6i_spi *sspi = spi_master_get_devdata(master);
unsigned int mclk_rate, div, timeout; unsigned int mclk_rate, div, div_cdr1, div_cdr2, timeout;
unsigned int start, end, tx_time; unsigned int start, end, tx_time;
unsigned int trig_level; unsigned int trig_level;
unsigned int tx_len = 0; unsigned int tx_len = 0;
...@@ -291,14 +291,12 @@ static int sun6i_spi_transfer_one(struct spi_master *master, ...@@ -291,14 +291,12 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
* First try CDR2, and if we can't reach the expected * First try CDR2, and if we can't reach the expected
* frequency, fall back to CDR1. * frequency, fall back to CDR1.
*/ */
div = mclk_rate / (2 * tfr->speed_hz); div_cdr1 = DIV_ROUND_UP(mclk_rate, tfr->speed_hz);
if (div <= (SUN6I_CLK_CTL_CDR2_MASK + 1)) { div_cdr2 = DIV_ROUND_UP(div_cdr1, 2);
if (div > 0) if (div_cdr2 <= (SUN6I_CLK_CTL_CDR2_MASK + 1)) {
div--; reg = SUN6I_CLK_CTL_CDR2(div_cdr2 - 1) | SUN6I_CLK_CTL_DRS;
reg = SUN6I_CLK_CTL_CDR2(div) | SUN6I_CLK_CTL_DRS;
} else { } else {
div = ilog2(mclk_rate) - ilog2(tfr->speed_hz); div = min(SUN6I_CLK_CTL_CDR1_MASK, order_base_2(div_cdr1));
reg = SUN6I_CLK_CTL_CDR1(div); reg = SUN6I_CLK_CTL_CDR1(div);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment