summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2021-06-07 12:44:03 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2021-06-10 15:16:12 +0200
commit6fb9b51fcaa302e935a31b7fce269589427b7aa0 (patch)
treeae15d3813e085fb6af5646e300f214ab4a558043 /drivers
parenta3900f221692606410d404a9a48ed7b86ab38908 (diff)
downloadbarebox-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')
-rw-r--r--drivers/mci/sdhci.c281
-rw-r--r--drivers/mci/sdhci.h52
2 files changed, 333 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;
+}
diff --git a/drivers/mci/sdhci.h b/drivers/mci/sdhci.h
index 1256e3c276..3e505c5b15 100644
--- a/drivers/mci/sdhci.h
+++ b/drivers/mci/sdhci.h
@@ -79,6 +79,7 @@
#define SDHCI_DIVIDER_SHIFT 8
#define SDHCI_DIVIDER_HI_SHIFT 6
#define SDHCI_DIV_MASK 0xFF
+#define SDHCI_DIV_HI_MASK 0x300
#define SDHCI_DIV_MASK_LEN 8
#define SDHCI_FREQ_SEL(x) (((x) & 0xff) << 8)
#define SDHCI_DIV_HI_MASK 0x300
@@ -147,6 +148,27 @@
#define SDHCI_CAN_DO_ADMA3 0x08000000
#define SDHCI_SUPPORT_HS400 0x80000000 /* Non-standard */
+#define SDHCI_PRESET_FOR_SDR12 0x66
+#define SDHCI_PRESET_FOR_SDR25 0x68
+#define SDHCI_PRESET_FOR_SDR50 0x6A
+#define SDHCI_PRESET_FOR_SDR104 0x6C
+#define SDHCI_PRESET_FOR_DDR50 0x6E
+#define SDHCI_PRESET_FOR_HS400 0x74 /* Non-standard */
+#define SDHCI_PRESET_CLKGEN_SEL BIT(10)
+#define SDHCI_PRESET_SDCLK_FREQ_MASK GENMASK(9, 0)
+
+#define SDHCI_HOST_VERSION 0xFE
+#define SDHCI_VENDOR_VER_MASK 0xFF00
+#define SDHCI_VENDOR_VER_SHIFT 8
+#define SDHCI_SPEC_VER_MASK 0x00FF
+#define SDHCI_SPEC_VER_SHIFT 0
+#define SDHCI_SPEC_100 0
+#define SDHCI_SPEC_200 1
+#define SDHCI_SPEC_300 2
+#define SDHCI_SPEC_400 3
+#define SDHCI_SPEC_410 4
+#define SDHCI_SPEC_420 5
+
#define SDHCI_CLOCK_MUL_SHIFT 16
#define SDHCI_MMC_BOOT 0xC4
@@ -161,6 +183,24 @@ struct sdhci {
void (*write32)(struct sdhci *host, int reg, u32 val);
void (*write16)(struct sdhci *host, int reg, u16 val);
void (*write8)(struct sdhci *host, int reg, u8 val);
+
+ int max_clk; /* Max possible freq (Hz) */
+ int clk_mul; /* Clock Muliplier value */
+
+ unsigned int version; /* SDHCI spec. version */
+
+ enum mci_timing timing;
+ bool preset_enabled; /* Preset is enabled */
+
+ unsigned int quirks;
+#define SDHCI_QUIRK_MISSING_CAPS BIT(27)
+ unsigned int quirks2;
+#define SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN BIT(15)
+ u32 caps; /* CAPABILITY_0 */
+ u32 caps1; /* CAPABILITY_1 */
+ bool read_caps; /* Capability flags have been read */
+
+ struct mci_host *mci;
};
static inline u32 sdhci_read32(struct sdhci *host, int reg)
@@ -199,6 +239,18 @@ void sdhci_set_cmd_xfer_mode(struct sdhci *host, struct mci_cmd *cmd,
u32 *xfer);
int sdhci_transfer_data(struct sdhci *sdhci, struct mci_data *data);
int sdhci_reset(struct sdhci *sdhci, u8 mask);
+u16 sdhci_calc_clk(struct sdhci *host, unsigned int clock,
+ unsigned int *actual_clock, unsigned int input_clock);
+void sdhci_set_clock(struct sdhci *host, unsigned int clock, unsigned int input_clock);
+void sdhci_enable_clk(struct sdhci *host, u16 clk);
+int sdhci_setup_host(struct sdhci *host);
+void __sdhci_read_caps(struct sdhci *host, const u16 *ver,
+ const u32 *caps, const u32 *caps1);
+static inline void sdhci_read_caps(struct sdhci *host)
+{
+ __sdhci_read_caps(host, NULL, NULL, NULL);
+}
+void sdhci_set_bus_width(struct sdhci *host, int width);
#define sdhci_read8_poll_timeout(sdhci, reg, val, cond, timeout_us) \
read_poll_timeout(sdhci_read8, val, cond, timeout_us, sdhci, reg)