diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2021-06-07 12:44:03 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2021-06-10 15:16:12 +0200 |
commit | 6fb9b51fcaa302e935a31b7fce269589427b7aa0 (patch) | |
tree | ae15d3813e085fb6af5646e300f214ab4a558043 /drivers/mci/sdhci.c | |
parent | a3900f221692606410d404a9a48ed7b86ab38908 (diff) | |
download | barebox-6fb9b51fcaa302e935a31b7fce269589427b7aa0.tar.gz barebox-6fb9b51fcaa302e935a31b7fce269589427b7aa0.tar.xz |
mci: sdhci: port over some common functions from Linux
This adds some functions useful for SDHCI drivers from Linux:
sdhci_calc_clk()
sdhci_set_clock()
sdhci_enable_clk()
sdhci_read_caps()
sdhci_set_bus_width()
These functions can be used to further unify our different SDHCI
drivers. All the new functions assume the also newly introduced
sdhci_setup_host() has been called before using them.
The functions are moslty the same as their Linux pendants, only
sdhci_calc_clk() takes an addional clock rate argument where Linux
uses host->max_clk. This is not suitable for the upcoming Rockchip
driver which needs to adjust the input clock using clk_set_rate(),
so fixed host->max_clk is not accurate for this driver.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Link: https://lore.barebox.org/20210607104411.23071-5-s.hauer@pengutronix.de
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/mci/sdhci.c')
-rw-r--r-- | drivers/mci/sdhci.c | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/drivers/mci/sdhci.c b/drivers/mci/sdhci.c index dba26b2665..0783f6d420 100644 --- a/drivers/mci/sdhci.c +++ b/drivers/mci/sdhci.c @@ -4,6 +4,7 @@ #include <driver.h> #include <mci.h> #include <io.h> +#include <linux/bitfield.h> #include "sdhci.h" @@ -88,6 +89,27 @@ static void sdhci_tx_pio(struct sdhci *sdhci, struct mci_data *data, sdhci_write32(sdhci, SDHCI_BUFFER, buf[i]); } +void sdhci_set_bus_width(struct sdhci *host, int width) +{ + u8 ctrl; + + BUG_ON(!host->mci); /* Call sdhci_setup_host() before using this */ + + ctrl = sdhci_read8(host, SDHCI_HOST_CONTROL); + if (width == MMC_BUS_WIDTH_8) { + ctrl &= ~SDHCI_CTRL_4BITBUS; + ctrl |= SDHCI_CTRL_8BITBUS; + } else { + if (host->mci->host_caps & MMC_CAP_8_BIT_DATA) + ctrl &= ~SDHCI_CTRL_8BITBUS; + if (width == MMC_BUS_WIDTH_4) + ctrl |= SDHCI_CTRL_4BITBUS; + else + ctrl &= ~SDHCI_CTRL_4BITBUS; + } + sdhci_write8(host, SDHCI_HOST_CONTROL, ctrl); +} + #ifdef __PBL__ /* * Stubs to make timeout logic below work in PBL @@ -149,3 +171,262 @@ int sdhci_reset(struct sdhci *sdhci, u8 mask) val, !(val & mask), 100 * USEC_PER_MSEC); } + +static u16 sdhci_get_preset_value(struct sdhci *host) +{ + u16 preset = 0; + + BUG_ON(!host->mci); /* Call sdhci_setup_host() before using this */ + + switch (host->timing) { + case MMC_TIMING_UHS_SDR12: + preset = sdhci_read16(host, SDHCI_PRESET_FOR_SDR12); + break; + case MMC_TIMING_UHS_SDR25: + preset = sdhci_read16(host, SDHCI_PRESET_FOR_SDR25); + break; + case MMC_TIMING_UHS_SDR50: + preset = sdhci_read16(host, SDHCI_PRESET_FOR_SDR50); + break; + case MMC_TIMING_UHS_SDR104: + case MMC_TIMING_MMC_HS200: + preset = sdhci_read16(host, SDHCI_PRESET_FOR_SDR104); + break; + case MMC_TIMING_UHS_DDR50: + case MMC_TIMING_MMC_DDR52: + preset = sdhci_read16(host, SDHCI_PRESET_FOR_DDR50); + break; + case MMC_TIMING_MMC_HS400: + preset = sdhci_read16(host, SDHCI_PRESET_FOR_HS400); + break; + default: + dev_warn(host->mci->hw_dev, "Invalid UHS-I mode selected\n"); + preset = sdhci_read16(host, SDHCI_PRESET_FOR_SDR12); + break; + } + return preset; +} + +u16 sdhci_calc_clk(struct sdhci *host, unsigned int clock, + unsigned int *actual_clock, unsigned int input_clock) +{ + int div = 0; /* Initialized for compiler warning */ + int real_div = div, clk_mul = 1; + u16 clk = 0; + bool switch_base_clk = false; + + BUG_ON(!host->mci); /* Call sdhci_setup_host() before using this */ + + if (host->version >= SDHCI_SPEC_300) { + if (host->preset_enabled) { + u16 pre_val; + + clk = sdhci_read16(host, SDHCI_CLOCK_CONTROL); + pre_val = sdhci_get_preset_value(host); + div = FIELD_GET(SDHCI_PRESET_SDCLK_FREQ_MASK, pre_val); + if (host->clk_mul && + (pre_val & SDHCI_PRESET_CLKGEN_SEL)) { + clk = SDHCI_PROG_CLOCK_MODE; + real_div = div + 1; + clk_mul = host->clk_mul; + } 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 ((input_clock * host->clk_mul / div) + <= clock) + break; + } + if ((input_clock * 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 (input_clock <= clock) + div = 1; + else { + for (div = 2; div < SDHCI_MAX_DIV_SPEC_300; + div += 2) { + if ((input_clock / div) <= clock) + break; + } + } + real_div = div; + div >>= 1; + if ((host->quirks2 & SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN) + && !div && input_clock <= 25000000) + 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 ((input_clock / div) <= clock) + break; + } + real_div = div; + div >>= 1; + } + +clock_set: + if (real_div) + *actual_clock = (input_clock * 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; +} + +void sdhci_enable_clk(struct sdhci *host, u16 clk) +{ + u64 start; + + BUG_ON(!host->mci); /* Call sdhci_setup_host() before using this */ + + clk |= SDHCI_CLOCK_INT_EN; + sdhci_write16(host, SDHCI_CLOCK_CONTROL, clk); + + start = get_time_ns(); + while (!(sdhci_read16(host, SDHCI_CLOCK_CONTROL) & + SDHCI_CLOCK_INT_STABLE)) { + if (is_timeout(start, 150 * MSECOND)) { + dev_err(host->mci->hw_dev, + "SDHCI clock stable timeout\n"); + return; + } + } + + clk |= SDHCI_CLOCK_CARD_EN; + sdhci_write16(host, SDHCI_CLOCK_CONTROL, clk); +} + +void sdhci_set_clock(struct sdhci *host, unsigned int clock, unsigned int input_clock) +{ + u16 clk; + + BUG_ON(!host->mci); /* Call sdhci_setup_host() before using this */ + + host->mci->clock = 0; + + sdhci_write16(host, SDHCI_CLOCK_CONTROL, 0); + + if (clock == 0) + return; + + clk = sdhci_calc_clk(host, clock, &host->mci->clock, input_clock); + sdhci_enable_clk(host, clk); +} + +void __sdhci_read_caps(struct sdhci *host, const u16 *ver, + const u32 *caps, const u32 *caps1) +{ + u16 v; + u64 dt_caps_mask = 0; + u64 dt_caps = 0; + struct device_node *np = host->mci->hw_dev->device_node; + + BUG_ON(!host->mci); /* Call sdhci_setup_host() before using this */ + + if (host->read_caps) + return; + + host->read_caps = true; + + sdhci_reset(host, SDHCI_RESET_ALL); + + of_property_read_u64(np, "sdhci-caps-mask", &dt_caps_mask); + of_property_read_u64(np, "sdhci-caps", &dt_caps); + + v = ver ? *ver : sdhci_read16(host, SDHCI_HOST_VERSION); + host->version = (v & SDHCI_SPEC_VER_MASK) >> SDHCI_SPEC_VER_SHIFT; + + if (host->quirks & SDHCI_QUIRK_MISSING_CAPS) + return; + + if (caps) { + host->caps = *caps; + } else { + host->caps = sdhci_read32(host, SDHCI_CAPABILITIES); + host->caps &= ~lower_32_bits(dt_caps_mask); + host->caps |= lower_32_bits(dt_caps); + } + + if (host->version < SDHCI_SPEC_300) + return; + + if (caps1) { + host->caps1 = *caps1; + } else { + host->caps1 = sdhci_read32(host, SDHCI_CAPABILITIES_1); + host->caps1 &= ~upper_32_bits(dt_caps_mask); + host->caps1 |= upper_32_bits(dt_caps); + } +} + +int sdhci_setup_host(struct sdhci *host) +{ + struct mci_host *mci = host->mci; + + BUG_ON(!mci); + + sdhci_read_caps(host); + + if (!host->max_clk) { + if (host->version >= SDHCI_SPEC_300) + host->max_clk = FIELD_GET(SDHCI_CLOCK_V3_BASE_MASK, host->caps); + else + host->max_clk = FIELD_GET(SDHCI_CLOCK_BASE_MASK, host->caps); + + host->max_clk *= 1000000; + } + + /* + * In case of Host Controller v3.00, find out whether clock + * multiplier is supported. + */ + host->clk_mul = FIELD_GET(SDHCI_CLOCK_MUL_MASK, host->caps1); + + /* + * In case the value in Clock Multiplier is 0, then programmable + * clock mode is not supported, otherwise the actual clock + * multiplier is one more than the value of Clock Multiplier + * in the Capabilities Register. + */ + if (host->clk_mul) + host->clk_mul += 1; + + if (host->caps & SDHCI_CAN_VDD_180) + mci->voltages |= MMC_VDD_165_195; + if (host->caps & SDHCI_CAN_VDD_300) + mci->voltages |= MMC_VDD_29_30 | MMC_VDD_30_31; + if (host->caps & SDHCI_CAN_VDD_330) + mci->voltages |= MMC_VDD_32_33 | MMC_VDD_33_34; + + if (host->caps & SDHCI_CAN_DO_HISPD) + mci->host_caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED; + + return 0; +} |