[PATCH 04/12] mci: sdhci: port over some common functions from Linux

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

 



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@xxxxxxxxxxxxxx>
---
 drivers/mci/sdhci.c | 281 ++++++++++++++++++++++++++++++++++++++++++++
 drivers/mci/sdhci.h |  53 +++++++++
 include/mci.h       |   2 +
 3 files changed, 336 insertions(+)

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 aa6dd9824e..872caabde5 100644
--- a/drivers/mci/sdhci.h
+++ b/drivers/mci/sdhci.h
@@ -69,7 +69,9 @@
 #define  SDHCI_BUS_POWER_EN			BIT(0)
 #define SDHCI_CLOCK_CONTROL__TIMEOUT_CONTROL__SOFTWARE_RESET	0x2c
 #define SDHCI_CLOCK_CONTROL					0x2c
+#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)
@@ -137,6 +139,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
@@ -151,6 +174,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)
@@ -189,6 +230,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)
diff --git a/include/mci.h b/include/mci.h
index 5e6805e8dc..df2437f618 100644
--- a/include/mci.h
+++ b/include/mci.h
@@ -365,6 +365,8 @@ enum mci_timing {
 	MMC_TIMING_UHS_SDR104	= 4,
 	MMC_TIMING_UHS_DDR50	= 5,
 	MMC_TIMING_MMC_HS200	= 6,
+	MMC_TIMING_MMC_DDR52	= 7,
+	MMC_TIMING_MMC_HS400	= 8,
 };
 
 struct mci_ios {
-- 
2.29.2


_______________________________________________
barebox mailing list
barebox@xxxxxxxxxxxxxxxxxxx
http://lists.infradead.org/mailman/listinfo/barebox



[Index of Archives]     [Linux Embedded]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux