summaryrefslogtreecommitdiff
path: root/drivers/mmc/host/sprd-sdhci-regulator.c
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/mmc/host/sprd-sdhci-regulator.c
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'drivers/mmc/host/sprd-sdhci-regulator.c')
-rw-r--r--drivers/mmc/host/sprd-sdhci-regulator.c278
1 files changed, 278 insertions, 0 deletions
diff --git a/drivers/mmc/host/sprd-sdhci-regulator.c b/drivers/mmc/host/sprd-sdhci-regulator.c
new file mode 100644
index 00000000..d3d89d52
--- /dev/null
+++ b/drivers/mmc/host/sprd-sdhci-regulator.c
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/types.h>
+#include "sprd-sdhci-regulator.h"
+
+#define SPRD_SDHCI_REGULATOR_SUPPLY(id, v) \
+ REGULATOR_SUPPLY(#v, "sprd-sdhci."#id)
+
+#ifndef CONFIG_OF
+#define SPRD_SDHCI_REGULATOR_INIT_DATA(_id, _v, _supply, _min_uV, _max_uV) { \
+ .supply_regulator = _supply, \
+ .constraints = { \
+ .name = #_v#_id, \
+ .min_uV = (_min_uV), \
+ .max_uV = (_max_uV), \
+ .valid_modes_mask = REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY, \
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS |REGULATOR_CHANGE_VOLTAGE |REGULATOR_CHANGE_MODE, \
+ }, \
+ .consumer_supplies = &sprd_consumer_supplies_##_v[_id], \
+ .num_consumer_supplies = 1, \
+ }
+
+#define SPRD_SDHCI_REGULATOR_DESC(_id, _name) { \
+ .name = #_name#_id, \
+ .id = (_id), \
+ .ops = &sprd_sdhci_regulator_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .n_voltages = 4, \
+ .owner = THIS_MODULE, \
+ }
+
+#define SPRD_SDHCI_REGULATOR_DEFINE_INFO(v) \
+ static struct regulator_consumer_supply sprd_consumer_supplies_##v[] = { \
+ SPRD_SDHCI_REGULATOR_SUPPLY(0, v), \
+ SPRD_SDHCI_REGULATOR_SUPPLY(1, v), \
+ SPRD_SDHCI_REGULATOR_SUPPLY(2, v), \
+ SPRD_SDHCI_REGULATOR_SUPPLY(3, v), \
+ }; \
+ static struct regulator_init_data sprd_init_data_##v[] = { \
+ SPRD_SDHCI_REGULATOR_INIT_DATA(0, v, NULL, 1800 * 1000, 3000 * 1000), \
+ SPRD_SDHCI_REGULATOR_INIT_DATA(1, v, NULL, 0, 0), \
+ SPRD_SDHCI_REGULATOR_INIT_DATA(2, v, NULL, 0, 0), \
+ SPRD_SDHCI_REGULATOR_INIT_DATA(3, v, "vddemmccore", 1200 * 1000, 1800 * 1000), \
+ }; \
+ static struct regulator_desc sprd_regulator_desc_##v[] = { \
+ SPRD_SDHCI_REGULATOR_DESC(0, v), \
+ SPRD_SDHCI_REGULATOR_DESC(1, v), \
+ SPRD_SDHCI_REGULATOR_DESC(2, v), \
+ SPRD_SDHCI_REGULATOR_DESC(3, v), \
+ };
+
+#define SPRD_SDHCI_REGULATOR_DECLARE_INFO(id, v) do { \
+ memset(&config_##v, 0, sizeof(struct regulator_config)); \
+ desc_##v = &sprd_regulator_desc_##v[(id)]; \
+ config_##v.init_data = &sprd_init_data_##v[(id)]; \
+ } while(0)
+
+static const unsigned int sprd_sdhci_regulator_voltage_level[4][4] = {
+ {0 * 1000, 0 * 1000, 1800 * 1000, 3000 * 1000}, // vddsd
+ {},
+ {},
+ {1200 * 1000, 1300 * 1000, 1500 * 1000, 1800 * 1000}, // vddemmcio
+};
+#else
+#define SPRD_SDHCI_REGULATOR_INIT_DATA(_id, _v, _np, _supply) do { \
+ (init_data_##_v).constraints.valid_modes_mask |= REGULATOR_MODE_NORMAL | REGULATOR_MODE_STANDBY; \
+ (init_data_##_v).constraints.valid_ops_mask |= REGULATOR_CHANGE_MODE; \
+ (init_data_##_v).consumer_supplies = &sprd_consumer_supplies_##_v[(_id)]; \
+ (init_data_##_v).num_consumer_supplies = 1; \
+ config_##_v.init_data = &init_data_##_v; \
+ config_##_v.of_node = (_np); \
+ desc_##_v->supply_name = (_supply); \
+ } while(0)
+
+#define SPRD_SDHCI_REGULATOR_DESC(_id, _name) { \
+ .name = #_name#_id, \
+ .id = (_id), \
+ .ops = &sprd_sdhci_regulator_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .n_voltages = 4, \
+ .owner = THIS_MODULE, \
+ }
+
+#define SPRD_SDHCI_REGULATOR_DEFINE_INFO(v) \
+ static struct regulator_consumer_supply sprd_consumer_supplies_##v[] = { \
+ SPRD_SDHCI_REGULATOR_SUPPLY(0, v), \
+ SPRD_SDHCI_REGULATOR_SUPPLY(1, v), \
+ SPRD_SDHCI_REGULATOR_SUPPLY(2, v), \
+ SPRD_SDHCI_REGULATOR_SUPPLY(3, v), \
+ }; \
+ static struct regulator_desc sprd_regulator_desc_##v[] = { \
+ SPRD_SDHCI_REGULATOR_DESC(0, v), \
+ SPRD_SDHCI_REGULATOR_DESC(1, v), \
+ SPRD_SDHCI_REGULATOR_DESC(2, v), \
+ SPRD_SDHCI_REGULATOR_DESC(3, v), \
+ };
+
+#define SPRD_SDHCI_REGULATOR_DECLARE_INFO(id, v) do { \
+ memset(&config_##v, 0, sizeof(struct regulator_config)); \
+ desc_##v = &sprd_regulator_desc_##v[(id)]; \
+ } while(0)
+
+static unsigned int sprd_sdhci_regulator_voltage_level[4][4];
+
+static struct of_regulator_match sprd_sdhci_default_regulator_matches[4] = {
+ { .name = "sd", .driver_data = (void *)0 },
+ { .name = "wifi", .driver_data = (void *)0 },
+ { .name = "ap-cp", .driver_data = (void *)0 },
+ { .name = "emmc-signal", .driver_data = (void *)1 },
+};
+#endif
+
+static int sprd_sdhci_regulator_enable(struct regulator_dev *rdev) {
+ int retval;
+ struct regulator *regulator = rdev_get_drvdata(rdev);
+ regulator_notifier_call_chain(rdev, REGULATOR_EVENT_ENABLE, rdev);
+ retval = regulator ? regulator_enable(regulator) : 0;
+ if(!retval)
+ return retval;
+ regulator_notifier_call_chain(rdev, REGULATOR_EVENT_DISABLE, rdev);
+ return retval;
+}
+
+static int sprd_sdhci_regulator_disable(struct regulator_dev *rdev) {
+ struct regulator *regulator = rdev_get_drvdata(rdev);
+ return regulator ? regulator_disable(regulator) : 0;
+}
+
+static int sprd_sdhci_regulator_is_enabled(struct regulator_dev *rdev) {
+ return rdev->use_count > 0;
+}
+
+static int sprd_sdhci_regulator_set_voltage(struct regulator_dev *rdev, int min_uV,
+ int max_uV, unsigned *selector) {
+ int uV;
+ int i;
+ int start;
+ int retval;
+ int id = rdev_get_id(rdev);
+ struct regulator *regulator = rdev_get_drvdata(rdev);
+ if(rdev->supply) {
+ uV = 3000 * 1000;
+ retval = regulator_set_voltage(rdev->supply, uV, uV);
+ if(retval < 0)
+ return retval;
+ }
+ start = sprd_sdhci_regulator_voltage_level[id][0];
+ for(i = 0; i < ARRAY_SIZE(sprd_sdhci_regulator_voltage_level[0]); i++) {
+ if(min_uV <= sprd_sdhci_regulator_voltage_level[id][i] || min_uV <= (sprd_sdhci_regulator_voltage_level[id][i] + start) /2)
+ break;
+ start = sprd_sdhci_regulator_voltage_level[id][i];
+ }
+ if(i == ARRAY_SIZE(sprd_sdhci_regulator_voltage_level[0]))
+ i--;
+ uV = sprd_sdhci_regulator_voltage_level[id][i];
+ if(selector)
+ *selector = i;
+ return regulator_set_voltage(regulator, uV, uV);
+}
+
+static int sprd_sdhci_regulator_get_voltage(struct regulator_dev *rdev) {
+ struct regulator *regulator = rdev_get_drvdata(rdev);
+ return regulator_get_voltage(regulator);
+}
+
+static int sprd_sdhci_regulator_set_mode(struct regulator_dev *rdev, unsigned int mode) {
+ struct regulator *regulator = rdev_get_drvdata(rdev);
+ return regulator_set_mode(regulator, mode);
+}
+
+static int sprd_sdhci_regulator_list_voltage(struct regulator_dev *rdev,
+ unsigned selector) {
+ int id = rdev_get_id(rdev);
+ if(selector >= ARRAY_SIZE(sprd_sdhci_regulator_voltage_level[0]))
+ return -EINVAL;
+ return sprd_sdhci_regulator_voltage_level[id][selector];
+}
+
+static struct regulator_ops sprd_sdhci_regulator_ops = {
+ .is_enabled = sprd_sdhci_regulator_is_enabled,
+ .enable = sprd_sdhci_regulator_enable,
+ .disable = sprd_sdhci_regulator_disable,
+ .set_voltage = sprd_sdhci_regulator_set_voltage,
+ .get_voltage = sprd_sdhci_regulator_get_voltage,
+ .set_mode = sprd_sdhci_regulator_set_mode,
+ .list_voltage = sprd_sdhci_regulator_list_voltage,
+};
+
+struct regulator_dev *sprd_sdhci_regulator_init(struct platform_device *pdev, struct notifier_block *nb, const char *ext_vdd_name) {
+ struct regulator *regulator_vmmc = NULL, *regulator_vqmmc = NULL;
+ struct regulator_dev *rdev_vmmc, *rdev_vqmmc;
+ struct regulator_desc *desc_vmmc, *desc_vqmmc;
+ struct regulator_config config_vmmc, config_vqmmc;
+#ifdef CONFIG_OF
+ struct device_node *regulators;
+ struct device_node *np = pdev->dev.of_node;
+ struct of_regulator_match *regulator_match;
+ struct regulator_init_data init_data_vmmc, init_data_vqmmc;
+ int retval;
+#endif
+ SPRD_SDHCI_REGULATOR_DEFINE_INFO(vmmc);
+ SPRD_SDHCI_REGULATOR_DEFINE_INFO(vqmmc);
+ SPRD_SDHCI_REGULATOR_DECLARE_INFO(pdev->id, vmmc);
+ SPRD_SDHCI_REGULATOR_DECLARE_INFO(pdev->id, vqmmc);
+ if(!ext_vdd_name)
+ return NULL;
+#ifdef CONFIG_OF
+ retval = of_property_read_u32_array(np, "vdd-level", sprd_sdhci_regulator_voltage_level[pdev->id], 4);
+ if(IS_ERR(ERR_PTR(retval)))
+ dev_err(&pdev->dev, "vdd-level failed\n");
+ regulators = of_find_node_by_name(NULL, "sprd-regulators");
+ if(!regulators) {
+ dev_err(&pdev->dev, "regulators failed\n");
+ return NULL;
+ }
+ regulator_match = &sprd_sdhci_default_regulator_matches[pdev->id];
+ retval = of_regulator_match(&pdev->dev, regulators, regulator_match, 1);
+ of_node_put(regulators);
+ if(IS_ERR_OR_NULL(ERR_PTR(retval))) {
+ dev_err(&pdev->dev, "of_regulator_match failed\n");
+ return NULL;
+ }
+ regulator_match->init_data->constraints.name = NULL;
+ init_data_vmmc = init_data_vqmmc = regulator_match->init_data[0];
+ if(regulator_match->driver_data) {
+ SPRD_SDHCI_REGULATOR_INIT_DATA(pdev->id, vmmc, np, regulator_match->name);
+ SPRD_SDHCI_REGULATOR_INIT_DATA(pdev->id, vqmmc, np, regulator_match->name);
+ } else {
+ SPRD_SDHCI_REGULATOR_INIT_DATA(pdev->id, vmmc, np, NULL);
+ SPRD_SDHCI_REGULATOR_INIT_DATA(pdev->id, vqmmc, np, NULL);
+ }
+#endif
+ config_vmmc.dev = config_vqmmc.dev = &pdev->dev;
+ regulator_vqmmc = regulator_get(&pdev->dev, ext_vdd_name);
+ if(IS_ERR_OR_NULL(regulator_vqmmc))
+ return NULL;
+ config_vmmc.driver_data = config_vqmmc.driver_data = regulator_vqmmc;
+ rdev_vmmc = regulator_register(desc_vmmc, &config_vmmc);
+ if(IS_ERR_OR_NULL(rdev_vmmc))
+ goto ERR_EXIT_VMMC;
+ rdev_vqmmc = regulator_register(desc_vqmmc, &config_vqmmc);
+ if(IS_ERR_OR_NULL(rdev_vqmmc))
+ goto ERR_EXIT_VQMMC;
+ regulator_vmmc = regulator_get(&pdev->dev, "vmmc");
+ if(IS_ERR_OR_NULL(regulator_vmmc))
+ goto ERR_EXIT_GET_VMMC;
+ if(regulator_register_notifier(regulator_vmmc, nb) < 0)
+ goto ERR_EXIT_NOTIFIER;
+ regulator_put(regulator_vmmc);
+ return rdev_vmmc;
+ERR_EXIT_NOTIFIER:
+ dev_err(&pdev->dev, "regulator_register_notifier vmmc failed\n");
+ regulator_put(regulator_vmmc);
+ERR_EXIT_GET_VMMC:
+ dev_err(&pdev->dev, "regulator_get vmmc failed\n");
+ regulator_unregister(rdev_vqmmc);
+ERR_EXIT_VQMMC:
+ dev_err(&pdev->dev, "regulator_register vqmmc failed\n");
+ regulator_unregister(rdev_vmmc);
+ERR_EXIT_VMMC:
+ dev_err(&pdev->dev, "regulator_register vmmc failed\n");
+ if(regulator_vqmmc)
+ regulator_put(regulator_vqmmc);
+ return NULL;
+}
+