On Wed, Aug 04, 2021 at 03:40:38PM +0300, Adrian Hunter wrote: > On 25/07/21 12:20 pm, Michał Mirosław wrote: > > For base clock setting, SDHCI V2 differs from V3+ only in allowed divisor > > values. Remove the duplicate version of code and reduce indentation > > levels. We can see now, that 'real_div' can't be zero, so the check is > > removed. While at it, replace divisor search loops with divide-and-clamp > > to make the code even more readable. > > It doesn't seem simpler to me, just different. > > Simpler would mean broken into separate logical functions, getting rid of > the gotos, and above all having the changes broken into separate patches > for easy review. I've extracted part of the function. It does look better I think, but it can be improved. Please take a look. --->8<--- mmc: sdhci: rework clock calculation Rework the code by extracting register value and real clock rate calculations to a separate function. This also removes duplicated calculations of real_div and divisor search loops. diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index cfa314e659bc..9822903841f1 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1845,97 +1845,79 @@ static u16 sdhci_get_preset_value(struct sdhci_host *host) return preset; } -u16 sdhci_calc_clk(struct sdhci_host *host, unsigned int clock, +static u16 sdhci_calc_clk_mode(struct sdhci_host *host, int div, bool prog_mode, unsigned int *actual_clock) { - int div = 0; /* Initialized for compiler warning */ - int real_div = div, clk_mul = 1; + unsigned int clk_mul, real_div; u16 clk = 0; - bool switch_base_clk = false; - - if (host->version >= SDHCI_SPEC_300) { - if (host->preset_enabled) { - u16 pre_val; - - pre_val = sdhci_get_preset_value(host); - div = FIELD_GET(SDHCI_PRESET_SDCLK_FREQ_MASK, pre_val); - if (pre_val & SDHCI_PRESET_CLKGEN_SEL) { - clk = SDHCI_PROG_CLOCK_MODE; - real_div = div + 1; - clk_mul = host->clk_mul; - if (!clk_mul) { - /* The clock frequency is unknown. Assume undivided base. */ - clk_mul = 1; - } - } else { - real_div = max_t(int, 1, div << 1); - } - goto clock_set; - } - - /* - * Check if the Host Controller supports Programmable Clock - * Mode. - */ - if (host->clk_mul) { - for (div = 1; div <= 1024; div++) { - if ((host->max_clk * host->clk_mul / div) - <= clock) - break; - } - if ((host->max_clk * host->clk_mul / div) <= clock) { - /* - * Set Programmable Clock Mode in the Clock - * Control register. - */ - clk = SDHCI_PROG_CLOCK_MODE; - real_div = div; - clk_mul = host->clk_mul; - div--; - } else { - /* - * Divisor can be too small to reach clock - * speed requirement. Then use the base clock. - */ - switch_base_clk = true; - } - } - if (!host->clk_mul || switch_base_clk) { - /* Version 3.00 divisors must be a multiple of 2. */ - if (host->max_clk <= clock) { - div = 1; - if (host->quirks2 & SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN) - div = 2; - } else { - for (div = 2; div < SDHCI_MAX_DIV_SPEC_300; - div += 2) { - if ((host->max_clk / div) <= clock) - break; - } - } - real_div = div; - div >>= 1; - } + if (prog_mode) { + clk = SDHCI_PROG_CLOCK_MODE; + clk_mul = host->max_clk ?: 1; + real_div = div + 1; } else { - /* Version 2.00 divisors must be a power of 2. */ - for (div = 1; div < SDHCI_MAX_DIV_SPEC_200; div *= 2) { - if ((host->max_clk / div) <= clock) - break; - } - real_div = div; - div >>= 1; + clk_mul = 1; + real_div = div * 2 + !div; } -clock_set: - if (real_div) - *actual_clock = (host->max_clk * clk_mul) / real_div; + *actual_clock = (host->max_clk * clk_mul) / real_div; + clk |= (div & SDHCI_DIV_MASK) << SDHCI_DIVIDER_SHIFT; clk |= ((div & SDHCI_DIV_HI_MASK) >> SDHCI_DIV_MASK_LEN) << SDHCI_DIVIDER_HI_SHIFT; return clk; } + +u16 sdhci_calc_clk(struct sdhci_host *host, unsigned int clock, + unsigned int *actual_clock) +{ + unsigned int div; + + if (clock == 0) + return 0; + + if (host->preset_enabled) { + /* Note: Only version 3.00+ can have preset_enabled. */ + + u16 pre_val = sdhci_get_preset_value(host); + bool prog_mode = !!(pre_val & SDHCI_PRESET_CLKGEN_SEL); + div = FIELD_GET(SDHCI_PRESET_SDCLK_FREQ_MASK, pre_val); + + return sdhci_calc_clk_mode(host, div, prog_mode, actual_clock); + } + + if (host->version >= SDHCI_SPEC_300 && host->clk_mul) { + /* Programmable Clock Mode is supported. */ + + div = DIV_ROUND_UP(host->max_clk * host->clk_mul, clock) - 1; + if (div <= SDHCI_MAX_DIV_SPEC_300 / 2) + return sdhci_calc_clk_mode(host, div, true, actual_clock); + + /* + * Divisor is too big for requested clock rate. + * Fall back to the base clock. + */ + } + + div = DIV_ROUND_UP(host->max_clk, clock); + + if (div == 1 && (host->quirks2 & SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN)) + div = 2; + + if (host->version >= SDHCI_SPEC_300) { + /* Version 3.00 divisor must be 1 or a multiple of 2. */ + if (div != 1) + div += div & 1; + div = min(div, SDHCI_MAX_DIV_SPEC_300); + } else { + /* Version 2.00 divisor must be a power of 2. */ + div = roundup_pow_of_two(div); + div = min(div, SDHCI_MAX_DIV_SPEC_200); + } + + return sdhci_calc_clk_mode(host, div / 2, false, actual_clock); +} EXPORT_SYMBOL_GPL(sdhci_calc_clk); void sdhci_enable_clk(struct sdhci_host *host, u16 clk) diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 074dc182b184..a3fa70d91410 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -284,8 +284,8 @@ * End of controller registers. */ -#define SDHCI_MAX_DIV_SPEC_200 256 -#define SDHCI_MAX_DIV_SPEC_300 2046 +#define SDHCI_MAX_DIV_SPEC_200 256u +#define SDHCI_MAX_DIV_SPEC_300 2046u /* * Host SDMA buffer boundary. Valid values from 4K to 512K in powers of 2.