@@ -22,6 +22,7 @@
#include <linux/phy/phy.h>
#include <linux/regmap.h>
#include <linux/of.h>
+#include <linux/firmware/intel/keembay.h>
#include <linux/firmware/xlnx-zynqmp.h>
#include "cqhci.h"
@@ -79,6 +80,8 @@ struct sdhci_arasan_soc_ctl_field {
* @baseclkfreq: Where to find corecfg_baseclkfreq
* @clockmultiplier: Where to find corecfg_clockmultiplier
* @support64b: Where to find SUPPORT64B bit
+ * @otap_delay: Where to find otap_delay
+ * @sel_clk_buffer: Where to find clock buffer delay
* @hiword_update: If true, use HIWORD_UPDATE to access the syscon
*
* It's up to the licensee of the Arsan IP block to make these available
@@ -89,6 +92,8 @@ struct sdhci_arasan_soc_ctl_map {
struct sdhci_arasan_soc_ctl_field baseclkfreq;
struct sdhci_arasan_soc_ctl_field clockmultiplier;
struct sdhci_arasan_soc_ctl_field support64b;
+ struct sdhci_arasan_soc_ctl_field otap_delay;
+ struct sdhci_arasan_soc_ctl_field sel_clk_buffer;
bool hiword_update;
};
@@ -139,6 +144,8 @@ struct sdhci_arasan_clk_data {
* @soc_ctl_base: Pointer to regmap for syscon for soc_ctl registers.
* @soc_ctl_map: Map to get offsets into soc_ctl registers.
* @quirks: Arasan deviations from spec.
+ * @aux_regulator: Struct for regulator.
+ * @aux_regulator_en: True if regulator is on; false if not.
*/
struct sdhci_arasan_data {
struct sdhci_host *host;
@@ -153,6 +160,8 @@ struct sdhci_arasan_data {
struct regmap *soc_ctl_base;
const struct sdhci_arasan_soc_ctl_map *soc_ctl_map;
unsigned int quirks;
+ struct regulator *aux_regulator;
+ bool aux_regulator_en;
/* Controller does not have CD wired and will not function normally without */
#define SDHCI_ARASAN_QUIRK_FORCE_CDTEST BIT(0)
@@ -189,6 +198,8 @@ static const struct sdhci_arasan_soc_ctl_map intel_keembay_soc_ctl_map = {
.baseclkfreq = { .reg = 0x0, .width = 8, .shift = 14 },
.clockmultiplier = { .reg = 0x4, .width = 8, .shift = 14 },
.support64b = { .reg = 0x4, .width = 1, .shift = 24 },
+ .otap_delay = { .reg = 0x24, .width = 5, .shift = 23 },
+ .sel_clk_buffer = { .reg = 0x2c, .width = 3, .shift = 25 },
.hiword_update = false,
};
@@ -364,6 +375,132 @@ static int sdhci_arasan_voltage_switch(struct mmc_host *mmc,
return -EINVAL;
}
+/**
+ * sdhci_arasan_keembay_voltage_switch - Voltage switch operation
+ * @mmc: Pointer to mmc_host
+ * @ios: Pointer to IO bus setting
+ *
+ * For Keem Bay hardware, in order to operate the GPIOs line for Clk, Cmd and Data,
+ * it is important to configure the supplied voltage applied to their I/O Rail
+ * and the output of the I²C expander Pin.
+ *
+ * Note that to configure the voltage rail output setting, specific bits in AON_CFG
+ * register must be set. This AON_CFG register is a secure register. Keem Bay regulator
+ * was introduced to encapsulate the Secure Monitor Call Calling Convention (SMCCC) for
+ * voltage rail output change. While to configure the I²C expander pin output,
+ * GPIO regulator modelling is been used to control the pin state.
+ *
+ * Regulator configuration:
+ * mmc->supply.vqmmc - expander pin output voltage
+ * sdhci_arasan->aux_regulator - voltage rail output voltage
+ *
+ * Final Voltage applied on the GPIOs Line are dependent by both supplied voltage
+ * I/O Rail and expander pin output as it is been set after passing through the
+ * voltage sense resistor.
+ *
+ * Return: 0 on success and error value on error
+ */
+static int sdhci_arasan_keembay_voltage_switch(struct mmc_host *mmc,
+ struct mmc_ios *ios)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host);
+ struct keembay_reg_supply supplies[KEEMBAY_REG_NUM_CONSUMERS];
+ u16 ctrl_2, clk;
+ int i, ret;
+
+ /* If no vqmmc supply then we can't change the expander pin output voltage */
+ if (IS_ERR(mmc->supply.vqmmc))
+ return PTR_ERR(mmc->supply.vqmmc);
+
+ /* If no sdvrail supply then we can't change the voltage rail output voltage */
+ if (IS_ERR(sdhci_arasan->aux_regulator))
+ return PTR_ERR(sdhci_arasan->aux_regulator);
+
+ supplies[0].consumer = mmc->supply.vqmmc;
+ supplies[1].consumer = sdhci_arasan->aux_regulator;
+
+ switch (ios->signal_voltage) {
+ case MMC_SIGNAL_VOLTAGE_180:
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+ clk &= ~SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+ if (clk & SDHCI_CLOCK_CARD_EN)
+ return -EAGAIN;
+
+ sdhci_writeb(host, SDHCI_POWER_ON | SDHCI_POWER_180,
+ SDHCI_POWER_CONTROL);
+
+ for (i = 0; i < KEEMBAY_REG_NUM_CONSUMERS; i++) {
+ ret = regulator_set_voltage(supplies[i].consumer,
+ KEEMBAY_IOV_1_8V_uV,
+ KEEMBAY_IOV_1_8V_uV);
+ if (ret)
+ return ret;
+ }
+
+ ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ ctrl_2 |= SDHCI_CTRL_VDD_180;
+ sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+
+ /* Sleep for 5ms to stabilize 1.8V regulator */
+ usleep_range(5000, 5500);
+
+ /* 1.8V regulator output should be stable within 5 ms */
+ ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ if (!(ctrl_2 & SDHCI_CTRL_VDD_180))
+ return -EAGAIN;
+
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+ clk |= SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+ return 0;
+ case MMC_SIGNAL_VOLTAGE_330:
+ for (i = 0; i < KEEMBAY_REG_NUM_CONSUMERS; i++) {
+ ret = regulator_set_voltage(supplies[i].consumer,
+ KEEMBAY_IOV_3_3V_uV,
+ KEEMBAY_IOV_3_3V_uV);
+ if (ret)
+ return ret;
+ }
+
+ /* Set 1.8V Signal Enable in the Host Control2 register to 0 */
+ ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ ctrl_2 &= ~SDHCI_CTRL_VDD_180;
+ sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+
+ /* Sleep for 5ms to stabilize 3.3V regulator */
+ usleep_range(5000, 5500);
+
+ /* 3.3V regulator output should be stable within 5 ms */
+ ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ if (ctrl_2 & SDHCI_CTRL_VDD_180)
+ return -EAGAIN;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int sdhci_arasan_keembay_select_drive_strength(struct mmc_card *card,
+ unsigned int max_dtr, int host_drv,
+ int card_drv, int *drv_type)
+{
+ switch (card->host->ios.signal_voltage) {
+ case MMC_SIGNAL_VOLTAGE_180:
+ *drv_type = MMC_SET_DRIVER_TYPE_C;
+ return 0;
+ case MMC_SIGNAL_VOLTAGE_330:
+ *drv_type = MMC_SET_DRIVER_TYPE_B;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
static const struct sdhci_ops sdhci_arasan_ops = {
.set_clock = sdhci_arasan_set_clock,
.get_max_clock = sdhci_pltfm_clk_get_max_clock,
@@ -968,6 +1105,72 @@ static void sdhci_arasan_update_baseclkfreq(struct sdhci_host *host)
sdhci_arasan_syscon_write(host, &soc_ctl_map->baseclkfreq, mhz);
}
+/**
+ * sdhci_arasan_update_otap_delay - Set otap delay
+ * @host: The sdhci_host
+ * @value: The value to write
+ *
+ * Otap delay can be use to control the txclk tap delay for flopping the final stage flops.
+ *
+ * NOTE:
+ * Many existing devices don't seem to do this and work fine. To keep compatibility for
+ * old hardware where the device tree doesn't provide a register map, this function is a
+ * noop if a soc_ctl_map hasn't been provided for this platform.
+ */
+static void sdhci_arasan_update_otap_delay(struct sdhci_host *host, u32 value)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host);
+ const struct sdhci_arasan_soc_ctl_map *soc_ctl_map;
+ struct device *dev = host->mmc->parent;
+
+ /* Having a map is optional */
+ soc_ctl_map = sdhci_arasan->soc_ctl_map;
+ if (!soc_ctl_map)
+ return;
+
+ /* If we have a map, we expect to have a syscon */
+ if (!sdhci_arasan->soc_ctl_base) {
+ dev_warn(dev, "Have regmap, but no soc-ctl-syscon.\n");
+ return;
+ }
+
+ sdhci_arasan_syscon_write(host, &soc_ctl_map->otap_delay, value);
+}
+
+/**
+ * sdhci_arasan_update_sel_clkbuf - Clock buffer select
+ * @host: The sdhci_host
+ * @value: The value to write
+ *
+ * Clock buffer select is use to delay the clock buffer.
+ *
+ * NOTE:
+ * Many existing devices don't seem to do this and work fine. To keep compatibility for
+ * old hardware where the device tree doesn't provide a register map, this function is a
+ * noop if a soc_ctl_map hasn't been provided for this platform.
+ */
+static void sdhci_arasan_update_sel_clkbuf(struct sdhci_host *host, u32 value)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host);
+ const struct sdhci_arasan_soc_ctl_map *soc_ctl_map;
+ struct device *dev = host->mmc->parent;
+
+ /* Having a map is optional */
+ soc_ctl_map = sdhci_arasan->soc_ctl_map;
+ if (!soc_ctl_map)
+ return;
+
+ /* If we have a map, we expect to have a syscon */
+ if (!sdhci_arasan->soc_ctl_base) {
+ dev_warn(dev, "Have regmap, but no soc-ctl-syscon.\n");
+ return;
+ }
+
+ sdhci_arasan_syscon_write(host, &soc_ctl_map->sel_clk_buffer, value);
+}
+
static void sdhci_arasan_set_clk_delays(struct sdhci_host *host)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
@@ -1256,6 +1459,42 @@ static const struct of_device_id sdhci_arasan_of_match[] = {
};
MODULE_DEVICE_TABLE(of, sdhci_arasan_of_match);
+static int sdhci_arasan_keembay_regulator_setup(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host);
+ struct device *dev = host->mmc->parent;
+
+ sdhci_arasan->aux_regulator = devm_regulator_get_optional(dev, "sdvrail");
+ if (IS_ERR(sdhci_arasan->aux_regulator))
+ return PTR_ERR(sdhci_arasan->aux_regulator);
+
+ return regulator_enable(sdhci_arasan->aux_regulator);
+}
+
+static int sdhci_arasan_keembay_phy_configuration(struct sdhci_host *host)
+{
+ struct device *dev = host->mmc->parent;
+ struct device_node *phys;
+ u32 otap_delay, sel_clk_buffer;
+
+ phys = of_parse_phandle(dev->of_node, "phys", 0);
+ if (!phys) {
+ dev_err(dev, "Can't get phys for sd0\n");
+ return -ENODEV;
+ }
+
+ of_property_read_u32(phys, "intel,keembay-emmc-phy-otap-dly", &otap_delay);
+ of_property_read_u32(phys, "intel,keembay-emmc-phy-sel-clkbuf", &sel_clk_buffer);
+
+ of_node_put(phys);
+
+ sdhci_arasan_update_otap_delay(host, otap_delay);
+ sdhci_arasan_update_sel_clkbuf(host, sel_clk_buffer);
+
+ return 0;
+}
+
/**
* sdhci_arasan_register_sdcardclk - Register the sdcardclk for a PHY to use
*
@@ -1590,6 +1829,24 @@ static int sdhci_arasan_probe(struct platform_device *pdev)
host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
}
+ if (of_device_is_compatible(np, "intel,keembay-sdhci-5.1-sd")) {
+ ret = sdhci_arasan_keembay_regulator_setup(host);
+ if (ret)
+ goto clk_disable_all;
+
+ sdhci_arasan->aux_regulator_en = true;
+
+ ret = sdhci_arasan_keembay_phy_configuration(host);
+ if (ret)
+ goto clk_disable_all;
+
+ host->mmc_host_ops.start_signal_voltage_switch =
+ sdhci_arasan_keembay_voltage_switch;
+
+ host->mmc_host_ops.select_drive_strength =
+ sdhci_arasan_keembay_select_drive_strength;
+ }
+
sdhci_arasan_update_baseclkfreq(host);
ret = sdhci_arasan_register_sdclk(sdhci_arasan, clk_xin, dev);
@@ -1651,6 +1908,9 @@ static int sdhci_arasan_probe(struct platform_device *pdev)
clk_dis_ahb:
clk_disable_unprepare(sdhci_arasan->clk_ahb);
err_pltfm_free:
+ if (sdhci_arasan->aux_regulator_en)
+ regulator_disable(sdhci_arasan->aux_regulator);
+
sdhci_pltfm_free(pdev);
return ret;
}
@@ -1669,6 +1929,9 @@ static int sdhci_arasan_remove(struct platform_device *pdev)
phy_exit(sdhci_arasan->phy);
}
+ if (sdhci_arasan->aux_regulator_en)
+ regulator_disable(sdhci_arasan->aux_regulator);
+
sdhci_arasan_unregister_sdclk(&pdev->dev);
ret = sdhci_pltfm_unregister(pdev);