From 8e886ac838ef12f6994ed9b13ab87784c4f0bc35 Mon Sep 17 00:00:00 2001 From: Maksim Kiselev Date: Wed, 10 May 2023 11:11:10 +0300 Subject: [PATCH] spi: sun6i: add quirk for in-controller clock divider Previously SPI controllers in Allwinner SoCs has a clock divider inside. However now the clock divider is removed and to set the transfer clock rate it's only needed to set the SPI module clock to the target value and configure a proper work mode. According to the datasheet there are three work modes: | SPI Sample Mode | SDM(bit13) | SDC(bit11) | Run Clock | |-------------------------|------------|------------|-----------| | normal sample | 1 | 0 | <= 24 MHz | | delay half cycle sample | 0 | 0 | <= 40 MHz | | delay one cycle sample | 0 | 1 | >= 80 MHz | Add a quirk for this kind of SPI controllers. Co-developed-by: Icenowy Zheng mclk); - if (mclk_rate < (2 * tfr->speed_hz)) { - clk_set_rate(sspi->mclk, 2 * tfr->speed_hz); - mclk_rate = clk_get_rate(sspi->mclk); - } + if (sspi->cfg->has_clk_ctl) { + unsigned int 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) - * 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_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; - tfr->effective_speed_hz = mclk_rate / (2 * div_cdr2); + /* Ensure that we have a parent clock fast enough */ + if (mclk_rate < (2 * tfr->speed_hz)) { + clk_set_rate(sspi->mclk, 2 * tfr->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) + * 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_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; + tfr->effective_speed_hz = mclk_rate / (2 * div_cdr2); + } else { + div = min(SUN6I_CLK_CTL_CDR1_MASK, order_base_2(div_cdr1)); + reg = SUN6I_CLK_CTL_CDR1(div); + tfr->effective_speed_hz = mclk_rate / (1 << div); + } + + sun6i_spi_write(sspi, SUN6I_CLK_CTL_REG, reg); } else { - div = min(SUN6I_CLK_CTL_CDR1_MASK, order_base_2(div_cdr1)); - reg = SUN6I_CLK_CTL_CDR1(div); - tfr->effective_speed_hz = mclk_rate / (1 << div); + clk_set_rate(sspi->mclk, tfr->speed_hz); + tfr->effective_speed_hz = clk_get_rate(sspi->mclk); + + /* + * Configure work mode. + * + * There are three work modes depending on the controller clock + * frequency: + * - normal sample mode : CLK <= 24MHz SDM=1 SDC=0 + * - delay half-cycle sample mode : CLK <= 40MHz SDM=0 SDC=0 + * - delay one-cycle sample mode : CLK >= 80MHz SDM=0 SDC=1 + */ + reg = sun6i_spi_read(sspi, SUN6I_TFR_CTL_REG); + reg &= ~(SUN6I_TFR_CTL_SDM | SUN6I_TFR_CTL_SDC); + + if (tfr->effective_speed_hz <= 24000000) + reg |= SUN6I_TFR_CTL_SDM; + else if (tfr->effective_speed_hz >= 80000000) + reg |= SUN6I_TFR_CTL_SDC; + + sun6i_spi_write(sspi, SUN6I_TFR_CTL_REG, reg); } - sun6i_spi_write(sspi, SUN6I_CLK_CTL_REG, reg); /* Finally enable the bus - doing so before might raise SCK to HIGH */ reg = sun6i_spi_read(sspi, SUN6I_GBL_CTL_REG); reg |= SUN6I_GBL_CTL_BUS_ENABLE; @@ -701,10 +730,12 @@ static void sun6i_spi_remove(struct platform_device *pdev) static const struct sun6i_spi_cfg sun6i_a31_spi_cfg = { .fifo_depth = SUN6I_FIFO_DEPTH, + .has_clk_ctl = true, }; static const struct sun6i_spi_cfg sun8i_h3_spi_cfg = { .fifo_depth = SUN8I_FIFO_DEPTH, + .has_clk_ctl = true, }; static const struct of_device_id sun6i_spi_match[] = {