aboutsummaryrefslogtreecommitdiff
path: root/drivers/mfd/qcom-qca639x.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mfd/qcom-qca639x.c')
-rw-r--r--drivers/mfd/qcom-qca639x.c234
1 files changed, 234 insertions, 0 deletions
diff --git a/drivers/mfd/qcom-qca639x.c b/drivers/mfd/qcom-qca639x.c
new file mode 100644
index 000000000000..16ff767a34b0
--- /dev/null
+++ b/drivers/mfd/qcom-qca639x.c
@@ -0,0 +1,234 @@
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/pinctrl/devinfo.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+struct vreg {
+ const char *name;
+ unsigned int load_uA;
+};
+
+struct qca_cfg_data {
+ const struct vreg *vregs;
+ size_t num_vregs;
+};
+
+static const struct vreg qca6390_vregs[] = {
+ /* 2.0 V */
+ { "vddpcie2", 15000 },
+ { "vddrfa3", 400000 },
+
+ /* 0.95 V */
+ { "vddaon", 100000 },
+ { "vddpmu", 1250000 },
+ { "vddrfa1", 200000 },
+
+ /* 1.35 V */
+ { "vddrfa2", 400000 },
+ { "vddpcie1", 35000 },
+
+ /* 1.8 V */
+ { "vddio", 20000 },
+};
+
+static const struct qca_cfg_data qca6390_cfg_data = {
+ .vregs = qca6390_vregs,
+ .num_vregs = ARRAY_SIZE(qca6390_vregs),
+};
+
+static const struct vreg wcn6855_vregs[] = {
+ /* 2.8 V */
+ { "vddasd" }, /* external antenna switch */
+
+ /* 0.95 V */
+ { "vddaon" },
+ { "vddcx" },
+ { "vddmx" },
+
+ /* 1.9 V - 2.1 V */
+ { "vddrfa1" },
+
+ /* 1.35 V */
+ { "vddrfa2" },
+
+ /* 2.2 V, optional */
+ { "vddrfa3" },
+
+ /* 1.8 V */
+ { "vddio" },
+};
+
+static const struct qca_cfg_data wcn6855_cfg_data = {
+ .vregs = wcn6855_vregs,
+ .num_vregs = ARRAY_SIZE(wcn6855_vregs),
+};
+
+struct qca_data {
+ size_t num_vregs;
+ struct device *dev;
+ struct generic_pm_domain pd;
+ struct gpio_desc *xo_clk_gpio;
+ struct gpio_desc *wlan_en_gpio;
+ struct gpio_desc *bt_en_gpio;
+ struct regulator_bulk_data regulators[];
+};
+
+#define domain_to_data(domain) container_of(domain, struct qca_data, pd)
+
+static int qca_power_on(struct generic_pm_domain *domain)
+{
+ struct qca_data *data = domain_to_data(domain);
+ int ret;
+
+ dev_warn(&domain->dev, "DUMMY POWER ON\n");
+
+ ret = regulator_bulk_enable(data->num_vregs, data->regulators);
+ if (ret) {
+ dev_err(data->dev, "Failed to enable regulators");
+ return ret;
+ }
+
+ /* Wait for 1ms before toggling enable pins. */
+ msleep(1);
+
+ if (data->xo_clk_gpio) {
+ gpiod_set_value(data->xo_clk_gpio, 1);
+
+ /*XO CLK must be asserted for some time before WLAN_EN */
+ usleep_range(100, 200);
+ }
+
+ if (data->wlan_en_gpio)
+ gpiod_set_value(data->wlan_en_gpio, 1);
+ if (data->bt_en_gpio)
+ gpiod_set_value(data->bt_en_gpio, 1);
+
+ if (data->xo_clk_gpio) {
+ /* Assert XO CLK ~(2-5)ms before off for valid latch in HW */
+ usleep_range(2000, 5000);
+ gpiod_set_value(data->xo_clk_gpio, 0);
+ }
+
+ /* Wait for all power levels to stabilize */
+ msleep(6);
+
+ return 0;
+}
+
+static int qca_power_off(struct generic_pm_domain *domain)
+{
+ struct qca_data *data = domain_to_data(domain);
+
+ dev_warn(&domain->dev, "DUMMY POWER OFF\n");
+
+ if (data->wlan_en_gpio)
+ gpiod_set_value(data->wlan_en_gpio, 0);
+ if (data->bt_en_gpio)
+ gpiod_set_value(data->bt_en_gpio, 0);
+
+ regulator_bulk_disable(data->num_vregs, data->regulators);
+
+ return 0;
+}
+
+static int qca_probe(struct platform_device *pdev)
+{
+ const struct qca_cfg_data *cfg;
+ struct qca_data *data;
+ struct device *dev = &pdev->dev;
+ int i, ret;
+
+ cfg = device_get_match_data(&pdev->dev);
+ if (!cfg)
+ return -EINVAL;
+
+ if (!dev->pins || IS_ERR_OR_NULL(dev->pins->default_state))
+ return -EINVAL;
+
+ data = devm_kzalloc(dev, struct_size(data, regulators, cfg->num_vregs), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = dev;
+ data->num_vregs = cfg->num_vregs;
+
+ for (i = 0; i < data->num_vregs; i++)
+ data->regulators[i].supply = cfg->vregs[i].name;
+ ret = devm_regulator_bulk_get(dev, data->num_vregs, data->regulators);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < data->num_vregs; i++) {
+ if (!cfg->vregs[i].load_uA)
+ continue;
+
+ ret = regulator_set_load(data->regulators[i].consumer, cfg->vregs[i].load_uA);
+ if (ret)
+ return ret;
+ }
+
+ data->xo_clk_gpio = devm_gpiod_get_optional(&pdev->dev, "xo-clk", GPIOD_OUT_LOW);
+ if (IS_ERR(data->xo_clk_gpio))
+ return PTR_ERR(data->xo_clk_gpio);
+
+ data->wlan_en_gpio = devm_gpiod_get_optional(&pdev->dev, "wlan-en", GPIOD_OUT_LOW);
+ if (IS_ERR(data->wlan_en_gpio))
+ return PTR_ERR(data->wlan_en_gpio);
+
+ data->bt_en_gpio = devm_gpiod_get_optional(&pdev->dev, "bt-en", GPIOD_OUT_LOW);
+ if (IS_ERR(data->bt_en_gpio))
+ return PTR_ERR(data->bt_en_gpio);
+
+ data->pd.name = dev_name(dev);
+ data->pd.power_on = qca_power_on;
+ data->pd.power_off = qca_power_off;
+
+ ret = pm_genpd_init(&data->pd, NULL, true);
+ if (ret < 0)
+ return ret;
+
+ ret = of_genpd_add_provider_simple(dev->of_node, &data->pd);
+ if (ret < 0) {
+ pm_genpd_remove(&data->pd);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, data);
+
+ return 0;
+}
+
+static int qca_remove(struct platform_device *pdev)
+{
+ struct qca_data *data = platform_get_drvdata(pdev);
+
+ pm_genpd_remove(&data->pd);
+
+ return 0;
+}
+
+static const struct of_device_id qca_of_match[] = {
+ { .compatible = "qcom,qca6390", .data = &qca6390_cfg_data },
+ { .compatible = "qcom,wcn6855", .data = &wcn6855_cfg_data },
+ { },
+};
+
+static struct platform_driver qca_driver = {
+ .probe = qca_probe,
+ .remove = qca_remove,
+ .driver = {
+ .name = "qca639x",
+ .of_match_table = qca_of_match,
+ },
+};
+
+module_platform_driver(qca_driver);
+MODULE_LICENSE("GPL v2");