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 <[email protected]>
---
 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
[email protected]
http://lists.infradead.org/mailman/listinfo/barebox

Reply via email to