| From 60a253423a0d818db6e8a260f878081d27499153 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Mon, 6 Jul 2020 16:34:34 +0200 |
| Subject: spi: spi-sun6i: sun6i_spi_transfer_one(): fix setting of clock rate |
| |
| From: Marc Kleine-Budde <mkl@pengutronix.de> |
| |
| [ Upstream commit ed7815db70d17b1741883f2da8e1d80bc2efe517 ] |
| |
| 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: 3558fe900e8a ("spi: sunxi: Add Allwinner A31 SPI controller driver") |
| Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de> |
| Acked-by: Maxime Ripard <mripard@kernel.org> |
| Link: https://lore.kernel.org/r/20200706143443.9855-2-mkl@pengutronix.de |
| Signed-off-by: Mark Brown <broonie@kernel.org> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| drivers/spi/spi-sun6i.c | 14 ++++++-------- |
| 1 file changed, 6 insertions(+), 8 deletions(-) |
| |
| diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c |
| index 7e7da97982aaf..17068e62e792d 100644 |
| --- a/drivers/spi/spi-sun6i.c |
| +++ b/drivers/spi/spi-sun6i.c |
| @@ -163,7 +163,7 @@ static int sun6i_spi_transfer_one(struct spi_master *master, |
| struct spi_transfer *tfr) |
| { |
| 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 tx_len = 0; |
| int ret = 0; |
| @@ -241,14 +241,12 @@ static int sun6i_spi_transfer_one(struct spi_master *master, |
| * First try CDR2, and if we can't reach the expected |
| * frequency, fall back to CDR1. |
| */ |
| - div = mclk_rate / (2 * tfr->speed_hz); |
| - if (div <= (SUN6I_CLK_CTL_CDR2_MASK + 1)) { |
| - if (div > 0) |
| - div--; |
| - |
| - reg = SUN6I_CLK_CTL_CDR2(div) | SUN6I_CLK_CTL_DRS; |
| + div_cdr1 = DIV_ROUND_UP(mclk_rate, tfr->speed_hz); |
| + div_cdr2 = DIV_ROUND_UP(div_cdr1, 2); |
| + if (div_cdr2 <= (SUN6I_CLK_CTL_CDR2_MASK + 1)) { |
| + reg = SUN6I_CLK_CTL_CDR2(div_cdr2 - 1) | SUN6I_CLK_CTL_DRS; |
| } 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); |
| } |
| |
| -- |
| 2.25.1 |
| |