Re: [PATCH v4 5/5] mmc: sdhci: simplify v2/v3+ clock calculation

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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.



[Index of Archives]     [Linux Memonry Technology]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux