This is a proposed power sequencer subsystem. This is a
generification of the MMC pwrseq code. The subsystem tries to abstract
the idea of complex power-up/power-down/reset of the devices.
The primary set of devices that promted me to create this patchset is
the Qualcomm BT+WiFi family of chips. They reside on serial+platform
or serial + SDIO interfaces (older generations) or on serial+PCIe (newer
generations). They require a set of external voltage regulators to be
powered on and (some of them) have separate WiFi and Bluetooth enable
GPIOs.
The major drawback for now is the lack of proper PCIe integration
At this moment support for PCIe is hacked up to be able to test the
PCIe part of qca6390. Proper PCIe support would require automatically
powering up the devices before the scan basing on the proper device
structure in the device tree. This two last patches are noted as WIP and
are included into the patchset for the purpose of testing WiFi on newer
chips (like qca6390/qca6391).
Changes since RFC v2:
- Add documentation for the pwrseq code. Document data structures,
macros and exported functions.
- Export of_pwrseq_xlate_onecell()
- Add separate pwrseq_set_drvdata() function to follow the typical API
design
- Remove pwrseq_get_optional()/devm_pwrseq_get_optional()
- Moved code to handle old mmc-pwrseq binding to the MMC patch
- Split of_pwrseq_xlate_onecell() support to a separate patch
Changes since RFC v1:
- Provider pwrseq fallback support
- Implement fallback support in pwrseq_qca.
- Mmove susclk handling to pwrseq_qca.
- Significantly simplify hci_qca.c changes, by dropping all legacy
code. Now hci_qca uses only pwrseq calls to power up/down bluetooth
parts of the chip.
Provide of_pwrseq_xlate_onecell() - a helper easing implementation of
power sequencers using one cell to determine pwrseq instance to return.
Signed-off-by: Dmitry Baryshkov <[email protected]>
---
drivers/power/pwrseq/core.c | 26 ++++++++++++++++++++++++++
include/linux/pwrseq/driver.h | 12 ++++++++++++
2 files changed, 38 insertions(+)
diff --git a/drivers/power/pwrseq/core.c b/drivers/power/pwrseq/core.c
index 0aaba4e79a44..3dffa52f65ee 100644
--- a/drivers/power/pwrseq/core.c
+++ b/drivers/power/pwrseq/core.c
@@ -449,6 +449,32 @@ struct pwrseq_provider *__devm_of_pwrseq_provider_register(struct device *dev,
}
EXPORT_SYMBOL_GPL(__devm_of_pwrseq_provider_register);
+/**
+ * of_pwrseq_xlate_single() - returns the pwrseq instance from pwrseq provider using single index
+ * @data: the pwrseq provider data, struct pwrseq_onecell_data
+ * @args: of_phandle_args containing single integer index
+ *
+ * Intended to be used by pwrseq provider for the common case where
+ * #pwrseq-cells is 1. It will return corresponding pwrseq instance.
+ */
+struct pwrseq *of_pwrseq_xlate_onecell(void *data, struct of_phandle_args *args)
+{
+ struct pwrseq_onecell_data *pwrseq_data = data;
+ unsigned int idx;
+
+ if (args->args_count != 1)
+ return ERR_PTR(-EINVAL);
+
+ idx = args->args[0];
+ if (idx >= pwrseq_data->num) {
+ pr_err("%s: invalid index %u\n", __func__, idx);
+ return ERR_PTR(-EINVAL);
+ }
+
+ return pwrseq_data->pwrseqs[idx];
+}
+EXPORT_SYMBOL_GPL(of_pwrseq_xlate_onecell);
+
static int __init pwrseq_core_init(void)
{
return class_register(&pwrseq_class);
diff --git a/include/linux/pwrseq/driver.h b/include/linux/pwrseq/driver.h
index 0ca1d0311ab6..8d46940981bf 100644
--- a/include/linux/pwrseq/driver.h
+++ b/include/linux/pwrseq/driver.h
@@ -157,4 +157,16 @@ static inline struct pwrseq *of_pwrseq_xlate_single(void *data,
return data;
}
+/**
+ * struct pwrseq_onecell_data - pwrseq data for of_pwrseq_xlate_onecell
+ * @num: amount of instances in @owrseqs
+ * @pwrseqs: array of pwrseq instances
+ */
+struct pwrseq_onecell_data {
+ unsigned int num;
+ struct pwrseq *pwrseqs[];
+};
+
+struct pwrseq *of_pwrseq_xlate_onecell(void *data, struct of_phandle_args *args);
+
#endif /* __LINUX_PWRSEQ_DRIVER_H__ */
--
2.33.0
Add support for power sequencer used in the Qualcomm BT+WiFi SoCs. They
require several external volate regulators and some of them use separate
BT and WiFi enable GPIO pins. This code is mostly extracted from the
hci_qca.c bluetooth driver and ath10k WiFi driver. Instead of having
each of them manage pins, different requirements, regulator types, move
this knowledge to the common power sequencer driver. Currently original
drivers are not stripped from the regulator code, this will be done
later (to keep compatibility with the old device trees).
Signed-off-by: Dmitry Baryshkov <[email protected]>
---
drivers/power/pwrseq/Kconfig | 14 ++
drivers/power/pwrseq/Makefile | 1 +
drivers/power/pwrseq/pwrseq_qca.c | 373 ++++++++++++++++++++++++++++++
3 files changed, 388 insertions(+)
create mode 100644 drivers/power/pwrseq/pwrseq_qca.c
diff --git a/drivers/power/pwrseq/Kconfig b/drivers/power/pwrseq/Kconfig
index 1985f13d9193..7983e37c7855 100644
--- a/drivers/power/pwrseq/Kconfig
+++ b/drivers/power/pwrseq/Kconfig
@@ -22,6 +22,20 @@ config PWRSEQ_EMMC
This driver can also be built as a module. If so, the module
will be called pwrseq_emmc.
+config PWRSEQ_QCA
+ tristate "Power Sequencer for Qualcomm WiFi + BT SoCs"
+ depends on OF
+ help
+ If you say yes to this option, support will be included for Qualcomm
+ WCN399x,QCA639x,QCA67xx families of hybrid WiFi and Bluetooth SoCs.
+ Note, this driver supports only power control for these SoC, you
+ still have to enable individual Bluetooth and WiFi drivers. This
+ driver is only necessary on ARM platforms with these chips. PCIe
+ cards handle power sequencing on their own.
+
+ Say M here if you want to include support for Qualcomm WiFi+BT SoCs
+ as a module. This will build a module called "pwrseq_qca".
+
config PWRSEQ_SD8787
tristate "HW reset support for SD8787 BT + Wifi module"
depends on OF
diff --git a/drivers/power/pwrseq/Makefile b/drivers/power/pwrseq/Makefile
index 6f359d228843..556bf5582d47 100644
--- a/drivers/power/pwrseq/Makefile
+++ b/drivers/power/pwrseq/Makefile
@@ -6,5 +6,6 @@
obj-$(CONFIG_PWRSEQ) += core.o
obj-$(CONFIG_PWRSEQ_EMMC) += pwrseq_emmc.o
+obj-$(CONFIG_PWRSEQ_QCA) += pwrseq_qca.o
obj-$(CONFIG_PWRSEQ_SD8787) += pwrseq_sd8787.o
obj-$(CONFIG_PWRSEQ_SIMPLE) += pwrseq_simple.o
diff --git a/drivers/power/pwrseq/pwrseq_qca.c b/drivers/power/pwrseq/pwrseq_qca.c
new file mode 100644
index 000000000000..c15508cc80d2
--- /dev/null
+++ b/drivers/power/pwrseq/pwrseq_qca.c
@@ -0,0 +1,373 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2021, Linaro Ltd.
+ *
+ * Author: Dmitry Baryshkov <[email protected]>
+ *
+ * Power Sequencer for Qualcomm WiFi + BT SoCs
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/pwrseq/driver.h>
+#include <linux/regulator/consumer.h>
+
+/* susclk rate */
+#define SUSCLK_RATE_32KHZ 32768
+
+/*
+ * Voltage regulator information required for configuring the
+ * QCA WiFi+Bluetooth chipset
+ */
+struct qca_vreg {
+ const char *name;
+ unsigned int load_uA;
+};
+
+struct qca_device_data {
+ bool has_enable_gpios;
+
+ /*
+ * VDDIO has to be enabled before the rest of regulators, so we treat
+ * it separately
+ */
+ struct qca_vreg vddio;
+
+ size_t num_vregs;
+ struct qca_vreg vregs[];
+};
+
+struct pwrseq_qca_common {
+ struct gpio_desc *sw_ctrl;
+ struct clk *susclk;
+
+ /*
+ * Again vddio is separate so that it can be enabled before enabling
+ * other regulators.
+ */
+ struct regulator *vddio;
+ int num_vregs;
+ struct regulator_bulk_data vregs[];
+};
+
+struct pwrseq_qca_one {
+ struct pwrseq_qca_common *common;
+ struct gpio_desc *enable;
+};
+
+#define PWRSEQ_QCA_WIFI 0
+#define PWRSEQ_QCA_BT 1
+
+#define PWRSEQ_QCA_MAX 2
+
+struct pwrseq_qca {
+ struct pwrseq_qca_one pwrseq_qcas[PWRSEQ_QCA_MAX];
+ struct pwrseq_qca_common common;
+};
+
+static int pwrseq_qca_pre_power_on(struct pwrseq *pwrseq)
+{
+ struct pwrseq_qca_one *qca_one = pwrseq_get_drvdata(pwrseq);
+
+ if (qca_one->enable) {
+ gpiod_set_value_cansleep(qca_one->enable, 0);
+ msleep(50);
+ }
+
+ return 0;
+}
+
+static int pwrseq_qca_power_on(struct pwrseq *pwrseq)
+{
+ struct pwrseq_qca_one *qca_one = pwrseq_get_drvdata(pwrseq);
+ int ret;
+
+ if (qca_one->common->vddio) {
+ ret = regulator_enable(qca_one->common->vddio);
+ if (ret)
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(qca_one->common->num_vregs, qca_one->common->vregs);
+ if (ret)
+ goto err_bulk;
+
+ ret = clk_prepare_enable(qca_one->common->susclk);
+ if (ret)
+ goto err_clk;
+
+ if (qca_one->enable) {
+ gpiod_set_value_cansleep(qca_one->enable, 1);
+ msleep(150);
+ }
+
+ if (qca_one->common->sw_ctrl) {
+ bool sw_ctrl_state = gpiod_get_value_cansleep(qca_one->common->sw_ctrl);
+ dev_dbg(&pwrseq->dev, "SW_CTRL is %d", sw_ctrl_state);
+ }
+
+ return 0;
+
+err_clk:
+ regulator_bulk_disable(qca_one->common->num_vregs, qca_one->common->vregs);
+err_bulk:
+ regulator_disable(qca_one->common->vddio);
+
+ return ret;
+}
+
+static void pwrseq_qca_power_off(struct pwrseq *pwrseq)
+{
+ struct pwrseq_qca_one *qca_one = pwrseq_get_drvdata(pwrseq);
+
+ if (qca_one->enable) {
+ gpiod_set_value_cansleep(qca_one->enable, 0);
+ msleep(50);
+ }
+
+ clk_disable_unprepare(qca_one->common->susclk);
+
+ regulator_bulk_disable(qca_one->common->num_vregs, qca_one->common->vregs);
+ regulator_disable(qca_one->common->vddio);
+
+ if (qca_one->common->sw_ctrl) {
+ bool sw_ctrl_state = gpiod_get_value_cansleep(qca_one->common->sw_ctrl);
+ dev_dbg(&pwrseq->dev, "SW_CTRL is %d", sw_ctrl_state);
+ }
+}
+
+static const struct pwrseq_ops pwrseq_qca_ops = {
+ .pre_power_on = pwrseq_qca_pre_power_on,
+ .power_on = pwrseq_qca_power_on,
+ .power_off = pwrseq_qca_power_off,
+};
+
+static int pwrseq_qca_common_init(struct device *dev, struct pwrseq_qca_common *qca_common,
+ const struct qca_device_data *data)
+{
+ int ret, i;
+
+ if (data->vddio.name) {
+ qca_common->vddio = devm_regulator_get(dev, data->vddio.name);
+ if (IS_ERR(qca_common->vddio))
+ return PTR_ERR(qca_common->vddio);
+
+ ret = regulator_set_load(qca_common->vddio, data->vddio.load_uA);
+ if (ret)
+ return ret;
+ }
+
+ qca_common->num_vregs = data->num_vregs;
+
+ for (i = 0; i < qca_common->num_vregs; i++)
+ qca_common->vregs[i].supply = data->vregs[i].name;
+
+ ret = devm_regulator_bulk_get(dev, qca_common->num_vregs, qca_common->vregs);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < qca_common->num_vregs; i++) {
+ if (!data->vregs[i].load_uA)
+ continue;
+
+ ret = regulator_set_load(qca_common->vregs[i].consumer, data->vregs[i].load_uA);
+ if (ret)
+ return ret;
+ }
+
+ qca_common->susclk = devm_clk_get_optional(dev, NULL);
+ if (IS_ERR(qca_common->susclk)) {
+ dev_err(dev, "failed to acquire clk\n");
+ return PTR_ERR(qca_common->susclk);
+ }
+
+ qca_common->sw_ctrl = devm_gpiod_get_optional(dev, "swctrl", GPIOD_IN);
+ if (IS_ERR(qca_common->sw_ctrl)) {
+ return dev_err_probe(dev, PTR_ERR(qca_common->sw_ctrl),
+ "failed to acquire SW_CTRL gpio\n");
+ } else if (!qca_common->sw_ctrl)
+ dev_info(dev, "No SW_CTRL gpio\n");
+
+ return 0;
+}
+
+static void pwrseq_qca_unprepare_susclk(void *data)
+{
+ struct pwrseq_qca_common *qca_common = data;
+
+ clk_disable_unprepare(qca_common->susclk);
+}
+
+static const struct qca_device_data qca_soc_data_default = {
+ .num_vregs = 0,
+ .has_enable_gpios = true,
+};
+
+static int pwrseq_qca_probe(struct platform_device *pdev)
+{
+ struct pwrseq_qca *pwrseq_qca;
+ struct pwrseq *pwrseq;
+ struct pwrseq_provider *provider;
+ struct device *dev = &pdev->dev;
+ struct pwrseq_onecell_data *onecell;
+ const struct qca_device_data *data;
+ int ret, i;
+
+ data = device_get_match_data(dev);
+ if (!data)
+ data = &qca_soc_data_default;
+
+ pwrseq_qca = devm_kzalloc(dev, struct_size(pwrseq_qca, common.vregs, data->num_vregs), GFP_KERNEL);
+ if (!pwrseq_qca)
+ return -ENOMEM;
+
+ onecell = devm_kzalloc(dev, struct_size(onecell, pwrseqs, PWRSEQ_QCA_MAX), GFP_KERNEL);
+ if (!onecell)
+ return -ENOMEM;
+
+ ret = pwrseq_qca_common_init(dev, &pwrseq_qca->common, data);
+ if (ret)
+ return ret;
+
+ if (data->has_enable_gpios) {
+ struct gpio_desc *gpiod;
+
+ gpiod = devm_gpiod_get_optional(dev, "wifi-enable", GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod))
+ return dev_err_probe(dev, PTR_ERR(gpiod), "failed to acquire WIFI enable GPIO\n");
+ else if (!gpiod)
+ dev_warn(dev, "No WiFi enable GPIO declared\n");
+
+ pwrseq_qca->pwrseq_qcas[PWRSEQ_QCA_WIFI].enable = gpiod;
+
+ gpiod = devm_gpiod_get_optional(dev, "bt-enable", GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod))
+ return dev_err_probe(dev, PTR_ERR(gpiod), "failed to acquire BT enable GPIO\n");
+ else if (!gpiod)
+ dev_warn(dev, "No BT enable GPIO declared\n");
+
+ pwrseq_qca->pwrseq_qcas[PWRSEQ_QCA_BT].enable = gpiod;
+ }
+
+ /* If we have no control over device's enablement, make sure that sleep clock is always running */
+ if (!pwrseq_qca->common.vddio ||
+ !pwrseq_qca->common.num_vregs ||
+ !(pwrseq_qca->pwrseq_qcas[PWRSEQ_QCA_BT].enable &&
+ pwrseq_qca->pwrseq_qcas[PWRSEQ_QCA_WIFI].enable)) {
+ ret = clk_set_rate(pwrseq_qca->common.susclk, SUSCLK_RATE_32KHZ);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(pwrseq_qca->common.susclk);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, pwrseq_qca_unprepare_susclk, &pwrseq_qca->common);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < PWRSEQ_QCA_MAX; i++) {
+ pwrseq_qca->pwrseq_qcas[i].common = &pwrseq_qca->common;
+
+ pwrseq = devm_pwrseq_create(dev, &pwrseq_qca_ops);
+ if (IS_ERR(pwrseq))
+ return PTR_ERR(pwrseq);
+
+ pwrseq_set_drvdata(pwrseq, &pwrseq_qca->pwrseq_qcas[i]);
+
+ onecell->pwrseqs[i] = pwrseq;
+ }
+
+ onecell->num = PWRSEQ_QCA_MAX;
+
+ provider = devm_of_pwrseq_provider_register(dev, of_pwrseq_xlate_onecell, onecell);
+
+ return PTR_ERR_OR_ZERO(provider);
+}
+
+static const struct qca_device_data qca_soc_data_qca6390 = {
+ .vddio = { "vddio", 20000 },
+ .vregs = {
+ /* 2.0 V */
+ { "vddpcie2", 15000 },
+ { "vddrfa3", 400000 },
+
+ /* 0.95 V */
+ { "vddaon", 100000 },
+ { "vddpmu", 1250000 },
+ { "vddrfa1", 200000 },
+
+ /* 1.35 V */
+ { "vddrfa2", 400000 },
+ { "vddpcie1", 35000 },
+ },
+ .num_vregs = 7,
+ .has_enable_gpios = true,
+};
+
+/* Shared between wcn3990 and wcn3991 */
+static const struct qca_device_data qca_soc_data_wcn3990 = {
+ .vddio = { "vddio", 15000 },
+ .vregs = {
+ { "vddxo", 80000 },
+ { "vddrf", 300000 },
+ { "vddch0", 450000 },
+ { "vddch1", 450000 },
+ },
+ .num_vregs = 4,
+};
+
+static const struct qca_device_data qca_soc_data_wcn3998 = {
+ .vddio = { "vddio", 10000 },
+ .vregs = {
+ { "vddxo", 80000 },
+ { "vddrf", 300000 },
+ { "vddch0", 450000 },
+ { "vddch1", 450000 },
+ },
+ .num_vregs = 4,
+};
+
+static const struct qca_device_data qca_soc_data_wcn6750 = {
+ .vddio = { "vddio", 5000 },
+ .vregs = {
+ { "vddaon", 26000 },
+ { "vddbtcxmx", 126000 },
+ { "vddrfacmn", 12500 },
+ { "vddrfa0p8", 102000 },
+ { "vddrfa1p7", 302000 },
+ { "vddrfa1p2", 257000 },
+ { "vddrfa2p2", 1700000 },
+ { "vddasd", 200 },
+ },
+ .num_vregs = 8,
+ .has_enable_gpios = true,
+};
+
+static const struct of_device_id pwrseq_qca_of_match[] = {
+ { .compatible = "qcom,qca6174-pwrseq", },
+ { .compatible = "qcom,qca6390-pwrseq", .data = &qca_soc_data_qca6390 },
+ { .compatible = "qcom,qca9377-pwrseq" },
+ { .compatible = "qcom,wcn3990-pwrseq", .data = &qca_soc_data_wcn3990 },
+ { .compatible = "qcom,wcn3991-pwrseq", .data = &qca_soc_data_wcn3990 },
+ { .compatible = "qcom,wcn3998-pwrseq", .data = &qca_soc_data_wcn3998 },
+ { .compatible = "qcom,wcn6750-pwrseq", .data = &qca_soc_data_wcn6750 },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, pwrseq_qca_of_match);
+
+static struct platform_driver pwrseq_qca_driver = {
+ .probe = pwrseq_qca_probe,
+ .driver = {
+ .name = "pwrseq_qca",
+ .of_match_table = pwrseq_qca_of_match,
+ },
+};
+
+module_platform_driver(pwrseq_qca_driver);
+MODULE_LICENSE("GPL v2");
--
2.33.0
Power sequencer support requires changing device tree. To ease migration
to pwrseq, add support for pwrseq 'fallback': let the power sequencer
driver register special handler that if matched will create pwrseq
instance basing on the consumer device tree data.
Signed-off-by: Dmitry Baryshkov <[email protected]>
---
drivers/power/pwrseq/Makefile | 2 +-
drivers/power/pwrseq/core.c | 3 ++
drivers/power/pwrseq/fallback.c | 88 +++++++++++++++++++++++++++++++++
include/linux/pwrseq/fallback.h | 60 ++++++++++++++++++++++
4 files changed, 152 insertions(+), 1 deletion(-)
create mode 100644 drivers/power/pwrseq/fallback.c
create mode 100644 include/linux/pwrseq/fallback.h
diff --git a/drivers/power/pwrseq/Makefile b/drivers/power/pwrseq/Makefile
index 556bf5582d47..949ec848cf00 100644
--- a/drivers/power/pwrseq/Makefile
+++ b/drivers/power/pwrseq/Makefile
@@ -3,7 +3,7 @@
# Makefile for power sequencer drivers.
#
-obj-$(CONFIG_PWRSEQ) += core.o
+obj-$(CONFIG_PWRSEQ) += core.o fallback.o
obj-$(CONFIG_PWRSEQ_EMMC) += pwrseq_emmc.o
obj-$(CONFIG_PWRSEQ_QCA) += pwrseq_qca.o
diff --git a/drivers/power/pwrseq/core.c b/drivers/power/pwrseq/core.c
index 3dffa52f65ee..30380ca6159a 100644
--- a/drivers/power/pwrseq/core.c
+++ b/drivers/power/pwrseq/core.c
@@ -14,6 +14,7 @@
#include <linux/of.h>
#include <linux/pwrseq/consumer.h>
#include <linux/pwrseq/driver.h>
+#include <linux/pwrseq/fallback.h>
#include <linux/slab.h>
#define to_pwrseq(a) (container_of((a), struct pwrseq, dev))
@@ -120,6 +121,8 @@ struct pwrseq * pwrseq_get(struct device *dev, const char *id)
struct pwrseq *pwrseq;
pwrseq = _of_pwrseq_get(dev, id);
+ if (pwrseq == NULL)
+ pwrseq = pwrseq_fallback_get(dev, id);
if (IS_ERR_OR_NULL(pwrseq))
return pwrseq;
diff --git a/drivers/power/pwrseq/fallback.c b/drivers/power/pwrseq/fallback.c
new file mode 100644
index 000000000000..b83bd5795ccb
--- /dev/null
+++ b/drivers/power/pwrseq/fallback.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2021 (c) Linaro Ltd.
+ * Author: Dmitry Baryshkov <[email protected]>
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+#include <linux/pwrseq/fallback.h>
+#include <linux/slab.h>
+
+static DEFINE_MUTEX(pwrseq_fallback_mutex);
+static LIST_HEAD(pwrseq_fallback_list);
+
+/**
+ * __pwrseq_fallback_register - internal helper for pwrseq_fallback_register
+ * @fallback - struct pwrseq_fallback to be registered
+ * @owner: module containing fallback callback
+ *
+ * Internal helper for pwrseq_fallback_register. It should not be called directly.
+ */
+int __pwrseq_fallback_register(struct pwrseq_fallback *fallback, struct module *owner)
+{
+ if (!try_module_get(owner))
+ return -EPROBE_DEFER;
+
+ fallback->owner = owner;
+
+ mutex_lock(&pwrseq_fallback_mutex);
+ list_add_tail(&fallback->list, &pwrseq_fallback_list);
+ mutex_unlock(&pwrseq_fallback_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__pwrseq_fallback_register);
+
+/**
+ * pwrseq_fallback_unregister() - unregister fallback helper
+ * @fallback - struct pwrseq_fallback to unregister
+ *
+ * Unregister pwrseq fallback handler registered by pwrseq_fallback_handler.
+ */
+void pwrseq_fallback_unregister(struct pwrseq_fallback *fallback)
+{
+ mutex_lock(&pwrseq_fallback_mutex);
+ list_del(&fallback->list);
+ mutex_unlock(&pwrseq_fallback_mutex);
+
+ module_put(fallback->owner);
+
+ kfree(fallback);
+}
+EXPORT_SYMBOL_GPL(pwrseq_fallback_unregister);
+
+static bool pwrseq_fallback_match(struct device *dev, struct pwrseq_fallback *fallback)
+{
+ if (of_match_device(fallback->of_match_table, dev) != NULL)
+ return true;
+
+ /* We might add support for other matching options later */
+
+ return false;
+}
+
+struct pwrseq *pwrseq_fallback_get(struct device *dev, const char *id)
+{
+ struct pwrseq_fallback *fallback;
+ struct pwrseq *pwrseq = ERR_PTR(-ENODEV);
+
+ mutex_lock(&pwrseq_fallback_mutex);
+
+ list_for_each_entry(fallback, &pwrseq_fallback_list, list) {
+ if (!pwrseq_fallback_match(dev, fallback))
+ continue;
+
+ pwrseq = fallback->get(dev, id);
+ break;
+ }
+
+ mutex_unlock(&pwrseq_fallback_mutex);
+
+ if (!IS_ERR_OR_NULL(pwrseq))
+ dev_warn(dev, "legacy pwrseq support used for the device\n");
+
+ return pwrseq;
+}
diff --git a/include/linux/pwrseq/fallback.h b/include/linux/pwrseq/fallback.h
new file mode 100644
index 000000000000..14f9aa527692
--- /dev/null
+++ b/include/linux/pwrseq/fallback.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2021 Linaro Ltd.
+ */
+
+#ifndef __LINUX_PWRSEQ_FALLBACK_H__
+#define __LINUX_PWRSEQ_FALLBACK_H__
+
+#include <linux/list.h>
+
+struct pwrseq;
+
+struct device;
+struct module;
+struct of_device_id;
+
+/**
+ * struct pwrseq_fallback - structure providing fallback data/
+ * @list: a list node for the fallback handlers
+ * @owner: module containing fallback callback
+ * @of_match_table: match table for this fallback
+ *
+ * Pwrseq fallback is a mechanism for handling backwards compatibility in the
+ * case device tree was not updated to use proper pwrseq providers.
+ *
+ * In case the pwrseq instance is not registered, core will automatically try
+ * locating and calling fallback getter. If the requesting device matches
+ * against @of_match_table, the @get callback will be called to retrieve pwrseq
+ * instance.
+ *
+ * The driver should fill of_match_table and @get fields only. @list and @owner
+ * will be filled by the core code.
+ */
+struct pwrseq_fallback {
+ struct list_head list;
+ struct module *owner;
+
+ const struct of_device_id *of_match_table;
+
+ struct pwrseq *(*get)(struct device *dev, const char *id);
+};
+
+/* provider interface */
+
+int __pwrseq_fallback_register(struct pwrseq_fallback *fallback, struct module *owner);
+
+/**
+ * pwrseq_fallback_register() - register fallback helper
+ * @fallback - struct pwrseq_fallback to be registered
+ *
+ * Register pwrseq fallback handler to assist pwrseq core.
+ */
+#define pwrseq_fallback_register(fallback) __pwrseq_fallback_register(fallback, THIS_MODULE)
+
+void pwrseq_fallback_unregister(struct pwrseq_fallback *fallback);
+
+/* internal interface to be used by pwrseq core */
+struct pwrseq *pwrseq_fallback_get(struct device *dev, const char *id);
+
+#endif /* __LINUX_PWRSEQ_DRIVER_H__ */
--
2.33.0
While we are waiting for all users of wcn399x-bt to be converted to the
pwrseq declaration in the device tree, provide support for the pwrseq
fallback: if the regulators are declared in the device itself, create
pwrseq instance. This way the hci_qca driver doesn't have to cope with
old and new dts bindings.
Signed-off-by: Dmitry Baryshkov <[email protected]>
---
drivers/power/pwrseq/pwrseq_qca.c | 155 +++++++++++++++++++++++++++++-
1 file changed, 154 insertions(+), 1 deletion(-)
diff --git a/drivers/power/pwrseq/pwrseq_qca.c b/drivers/power/pwrseq/pwrseq_qca.c
index c15508cc80d2..f237cf2f1880 100644
--- a/drivers/power/pwrseq/pwrseq_qca.c
+++ b/drivers/power/pwrseq/pwrseq_qca.c
@@ -11,9 +11,11 @@
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/mod_devicetable.h>
+#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/pwrseq/driver.h>
+#include <linux/pwrseq/fallback.h>
#include <linux/regulator/consumer.h>
/* susclk rate */
@@ -369,5 +371,156 @@ static struct platform_driver pwrseq_qca_driver = {
},
};
-module_platform_driver(pwrseq_qca_driver);
+struct pwrseq_qca_fallback {
+ struct pwrseq_qca_one qca_one;
+ struct pwrseq_qca_common common;
+};
+
+static const struct of_device_id pwrseq_qca_bt_of_match[] = {
+ { .compatible = "qcom,qca6174-bt" },
+ { .compatible = "qcom,qca6390-bt", .data = &qca_soc_data_qca6390},
+ { .compatible = "qcom,qca9377-bt" },
+ { .compatible = "qcom,wcn3990-bt", .data = &qca_soc_data_wcn3990 },
+ { .compatible = "qcom,wcn3991-bt", .data = &qca_soc_data_wcn3990 },
+ { .compatible = "qcom,wcn3998-bt", .data = &qca_soc_data_wcn3998 },
+ { .compatible = "qcom,wcn6750-bt", .data = &qca_soc_data_wcn6750 },
+ { /* sentinel */ },
+};
+
+static const struct qca_device_data qca_soc_data_wifi = {
+ .vregs = {
+ { "vdd-1.8-xo", 80000 },
+ { "vdd-1.3-rfa", 300000 },
+ { "vdd-3.3-ch0", 450000 },
+ { "vdd-3.3-ch1", 450000 },
+ },
+ .num_vregs = 4,
+};
+
+static const struct of_device_id pwrseq_qca_wifi_of_match[] = {
+ { .compatible = "qcom,wcn3990-wifi", .data = &qca_soc_data_wifi },
+ { /* sentinel */ }
+};
+
+static struct pwrseq * pwrseq_qca_fallback_get(struct device *dev)
+{
+ struct pwrseq_qca_fallback *fallback;
+ struct pwrseq *pwrseq;
+ const struct of_device_id *match;
+ const struct qca_device_data *data;
+ struct gpio_desc *gpiod;
+ int ret;
+
+ match = of_match_device(pwrseq_qca_bt_of_match, dev);
+ if (!match)
+ return ERR_PTR(-ENODEV);
+
+ data = match->data;
+ if (!data)
+ data = &qca_soc_data_default;
+
+ fallback = devm_kzalloc(dev, struct_size(fallback, common.vregs, data->num_vregs), GFP_KERNEL);
+ if (!data)
+ return ERR_PTR(-ENOMEM);
+
+ fallback->qca_one.common = &fallback->common;
+
+ ret = pwrseq_qca_common_init(dev, &fallback->common, data);
+ if (ret)
+ return ERR_PTR(ret);
+
+ if (data->has_enable_gpios) {
+ gpiod = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod))
+ return ERR_PTR(dev_err_probe(dev, PTR_ERR(gpiod), "failed to acquire enable GPIO\n"));
+ fallback->qca_one.enable = gpiod;
+ }
+
+ /* If we have no control over device's enablement, make sure that sleep clock is always running */
+ if (!fallback->common.vddio ||
+ !fallback->common.num_vregs ||
+ !fallback->qca_one.enable) {
+ ret = clk_set_rate(fallback->common.susclk, SUSCLK_RATE_32KHZ);
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = clk_prepare_enable(fallback->common.susclk);
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = devm_add_action_or_reset(dev, pwrseq_qca_unprepare_susclk, &fallback->common);
+ if (ret)
+ return ERR_PTR(ret);
+ }
+
+ pwrseq = devm_pwrseq_create(dev, &pwrseq_qca_ops);
+ if (IS_ERR(pwrseq))
+ return pwrseq;
+
+ pwrseq_set_drvdata(pwrseq, &fallback->qca_one);
+
+ return pwrseq;
+}
+
+static struct pwrseq * pwrseq_qca_fallback_get_bt(struct device *dev, const char *id)
+{
+ if (strcmp(id, "bt"))
+ return ERR_PTR(-ENODEV);
+
+ return pwrseq_qca_fallback_get(dev);
+}
+
+static struct pwrseq * pwrseq_qca_fallback_get_wifi(struct device *dev, const char *id)
+{
+ if (strcmp(id, "wifi"))
+ return ERR_PTR(-ENODEV);
+
+ return pwrseq_qca_fallback_get(dev);
+}
+
+static struct pwrseq_fallback pwrseq_qca_fallback_bt = {
+ .get = pwrseq_qca_fallback_get_bt,
+ .of_match_table = pwrseq_qca_bt_of_match,
+};
+
+static struct pwrseq_fallback pwrseq_qca_fallback_wifi = {
+ .get = pwrseq_qca_fallback_get_wifi,
+ .of_match_table = pwrseq_qca_wifi_of_match,
+};
+
+static int __init pwrseq_qca_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&pwrseq_qca_driver);
+ if (ret)
+ return ret;
+
+ ret = pwrseq_fallback_register(&pwrseq_qca_fallback_bt);
+ if (ret)
+ goto err_bt;
+
+ ret = pwrseq_fallback_register(&pwrseq_qca_fallback_wifi);
+ if (ret)
+ goto err_wifi;
+
+ return 0;
+
+err_wifi:
+ pwrseq_fallback_unregister(&pwrseq_qca_fallback_bt);
+err_bt:
+ platform_driver_unregister(&pwrseq_qca_driver);
+
+ return ret;
+}
+module_init(pwrseq_qca_init);
+
+static void __exit pwrseq_qca_exit(void)
+{
+ pwrseq_fallback_unregister(&pwrseq_qca_fallback_wifi);
+ pwrseq_fallback_unregister(&pwrseq_qca_fallback_bt);
+ platform_driver_unregister(&pwrseq_qca_driver);
+}
+module_exit(pwrseq_qca_exit);
+
MODULE_LICENSE("GPL v2");
--
2.33.0
Switch sdm845-db845c device tree to use new power sequencer driver
rather than separate regulators.
Signed-off-by: Dmitry Baryshkov <[email protected]>
---
arch/arm64/boot/dts/qcom/sdm845-db845c.dts | 21 ++++++++++++++-------
arch/arm64/boot/dts/qcom/sdm845.dtsi | 6 ++++++
2 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/arch/arm64/boot/dts/qcom/sdm845-db845c.dts b/arch/arm64/boot/dts/qcom/sdm845-db845c.dts
index 2d5533dd4ec2..a6a34a959a91 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-db845c.dts
+++ b/arch/arm64/boot/dts/qcom/sdm845-db845c.dts
@@ -629,6 +629,16 @@ &qupv3_id_1 {
status = "okay";
};
+&qca_pwrseq {
+ status = "okay";
+
+ vddio-supply = <&vreg_s4a_1p8>;
+
+ vddxo-supply = <&vreg_l7a_1p8>;
+ vddrf-supply = <&vreg_l17a_1p3>;
+ vddch0-supply = <&vreg_l25a_3p3>;
+};
+
&sdhc_2 {
status = "okay";
@@ -916,10 +926,8 @@ &uart6 {
bluetooth {
compatible = "qcom,wcn3990-bt";
- vddio-supply = <&vreg_s4a_1p8>;
- vddxo-supply = <&vreg_l7a_1p8>;
- vddrf-supply = <&vreg_l17a_1p3>;
- vddch0-supply = <&vreg_l25a_3p3>;
+ bt-pwrseq = <&qca_pwrseq 1>;
+
max-speed = <3200000>;
};
};
@@ -1036,9 +1044,8 @@ &wifi {
status = "okay";
vdd-0.8-cx-mx-supply = <&vreg_l5a_0p8>;
- vdd-1.8-xo-supply = <&vreg_l7a_1p8>;
- vdd-1.3-rfa-supply = <&vreg_l17a_1p3>;
- vdd-3.3-ch0-supply = <&vreg_l25a_3p3>;
+
+ wifi-pwrseq = <&qca_pwrseq 0>;
qcom,snoc-host-cap-8bit-quirk;
};
diff --git a/arch/arm64/boot/dts/qcom/sdm845.dtsi b/arch/arm64/boot/dts/qcom/sdm845.dtsi
index 6d7172e6f4c3..99117eaf617f 100644
--- a/arch/arm64/boot/dts/qcom/sdm845.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845.dtsi
@@ -1046,6 +1046,12 @@ psci {
method = "smc";
};
+ qca_pwrseq: qca-pwrseq {
+ compatible = "qcom,wcn3990-pwrseq";
+ #pwrseq-cells = <1>;
+ status = "disabled";
+ };
+
soc: soc@0 {
#address-cells = <2>;
#size-cells = <2>;
--
2.33.0
Port MMC's all pwrseq drivers to new pwrseq subsystem.
Signed-off-by: Dmitry Baryshkov <[email protected]>
---
.../pwrseq}/mmc-pwrseq-emmc.yaml | 6 +-
.../pwrseq}/mmc-pwrseq-sd8787.yaml | 6 +-
.../pwrseq}/mmc-pwrseq-simple.yaml | 6 +-
drivers/mmc/core/Kconfig | 32 ----
drivers/mmc/core/Makefile | 3 -
drivers/mmc/core/pwrseq_emmc.c | 120 -------------
drivers/mmc/core/pwrseq_sd8787.c | 117 -------------
drivers/mmc/core/pwrseq_simple.c | 164 ------------------
drivers/power/pwrseq/Kconfig | 32 ++++
drivers/power/pwrseq/Makefile | 4 +
drivers/power/pwrseq/pwrseq_emmc.c | 121 +++++++++++++
drivers/power/pwrseq/pwrseq_sd8787.c | 108 ++++++++++++
drivers/power/pwrseq/pwrseq_simple.c | 162 +++++++++++++++++
include/linux/pwrseq/driver.h | 4 +-
14 files changed, 444 insertions(+), 441 deletions(-)
rename Documentation/devicetree/bindings/{mmc => power/pwrseq}/mmc-pwrseq-emmc.yaml (91%)
rename Documentation/devicetree/bindings/{mmc => power/pwrseq}/mmc-pwrseq-sd8787.yaml (86%)
rename Documentation/devicetree/bindings/{mmc => power/pwrseq}/mmc-pwrseq-simple.yaml (92%)
delete mode 100644 drivers/mmc/core/pwrseq_emmc.c
delete mode 100644 drivers/mmc/core/pwrseq_sd8787.c
delete mode 100644 drivers/mmc/core/pwrseq_simple.c
create mode 100644 drivers/power/pwrseq/pwrseq_emmc.c
create mode 100644 drivers/power/pwrseq/pwrseq_sd8787.c
create mode 100644 drivers/power/pwrseq/pwrseq_simple.c
diff --git a/Documentation/devicetree/bindings/mmc/mmc-pwrseq-emmc.yaml b/Documentation/devicetree/bindings/power/pwrseq/mmc-pwrseq-emmc.yaml
similarity index 91%
rename from Documentation/devicetree/bindings/mmc/mmc-pwrseq-emmc.yaml
rename to Documentation/devicetree/bindings/power/pwrseq/mmc-pwrseq-emmc.yaml
index 1fc7e620f328..a5e14e4a19b3 100644
--- a/Documentation/devicetree/bindings/mmc/mmc-pwrseq-emmc.yaml
+++ b/Documentation/devicetree/bindings/power/pwrseq/mmc-pwrseq-emmc.yaml
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
%YAML 1.2
---
-$id: http://devicetree.org/schemas/mmc/mmc-pwrseq-emmc.yaml#
+$id: http://devicetree.org/schemas/power/pwrseq/mmc-pwrseq-emmc.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Simple eMMC hardware reset provider binding
@@ -32,6 +32,9 @@ properties:
reset procedure as described in Jedec 4.4 specification, the
gpio line should be defined as GPIO_ACTIVE_LOW.
+ "#pwrseq-cells":
+ const: 0
+
required:
- compatible
- reset-gpios
@@ -43,6 +46,7 @@ examples:
#include <dt-bindings/gpio/gpio.h>
sdhci0_pwrseq {
compatible = "mmc-pwrseq-emmc";
+ #pwrseq-cells = <0>;
reset-gpios = <&gpio1 12 GPIO_ACTIVE_LOW>;
};
...
diff --git a/Documentation/devicetree/bindings/mmc/mmc-pwrseq-sd8787.yaml b/Documentation/devicetree/bindings/power/pwrseq/mmc-pwrseq-sd8787.yaml
similarity index 86%
rename from Documentation/devicetree/bindings/mmc/mmc-pwrseq-sd8787.yaml
rename to Documentation/devicetree/bindings/power/pwrseq/mmc-pwrseq-sd8787.yaml
index 9e2396751030..7876be05573d 100644
--- a/Documentation/devicetree/bindings/mmc/mmc-pwrseq-sd8787.yaml
+++ b/Documentation/devicetree/bindings/power/pwrseq/mmc-pwrseq-sd8787.yaml
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
%YAML 1.2
---
-$id: http://devicetree.org/schemas/mmc/mmc-pwrseq-sd8787.yaml#
+$id: http://devicetree.org/schemas/power/pwrseq/mmc-pwrseq-sd8787.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Marvell SD8787 power sequence provider binding
@@ -25,6 +25,9 @@ properties:
description:
contains a reset GPIO specifier with the default active state
+ "#pwrseq-cells":
+ const: 0
+
required:
- compatible
- powerdown-gpios
@@ -37,6 +40,7 @@ examples:
#include <dt-bindings/gpio/gpio.h>
wifi_pwrseq: wifi_pwrseq {
compatible = "mmc-pwrseq-sd8787";
+ #pwrseq-cells = <0>;
powerdown-gpios = <&twl_gpio 0 GPIO_ACTIVE_LOW>;
reset-gpios = <&twl_gpio 1 GPIO_ACTIVE_LOW>;
};
diff --git a/Documentation/devicetree/bindings/mmc/mmc-pwrseq-simple.yaml b/Documentation/devicetree/bindings/power/pwrseq/mmc-pwrseq-simple.yaml
similarity index 92%
rename from Documentation/devicetree/bindings/mmc/mmc-pwrseq-simple.yaml
rename to Documentation/devicetree/bindings/power/pwrseq/mmc-pwrseq-simple.yaml
index 226fb191913d..3eff40fd347e 100644
--- a/Documentation/devicetree/bindings/mmc/mmc-pwrseq-simple.yaml
+++ b/Documentation/devicetree/bindings/power/pwrseq/mmc-pwrseq-simple.yaml
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
%YAML 1.2
---
-$id: http://devicetree.org/schemas/mmc/mmc-pwrseq-simple.yaml#
+$id: http://devicetree.org/schemas/power/pwrseq/mmc-pwrseq-simple.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Simple MMC power sequence provider binding
@@ -47,6 +47,9 @@ properties:
Delay in us after asserting the reset-gpios (if any)
during power off of the card.
+ "#pwrseq-cells":
+ const: 0
+
required:
- compatible
@@ -57,6 +60,7 @@ examples:
#include <dt-bindings/gpio/gpio.h>
sdhci0_pwrseq {
compatible = "mmc-pwrseq-simple";
+ #pwrseq-cells = <0>;
reset-gpios = <&gpio1 12 GPIO_ACTIVE_LOW>;
clocks = <&clk_32768_ck>;
clock-names = "ext_clock";
diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig
index 6f25c34e4fec..cf7df64ce009 100644
--- a/drivers/mmc/core/Kconfig
+++ b/drivers/mmc/core/Kconfig
@@ -2,38 +2,6 @@
#
# MMC core configuration
#
-config PWRSEQ_EMMC
- tristate "HW reset support for eMMC"
- default y
- depends on OF
- help
- This selects Hardware reset support aka pwrseq-emmc for eMMC
- devices. By default this option is set to y.
-
- This driver can also be built as a module. If so, the module
- will be called pwrseq_emmc.
-
-config PWRSEQ_SD8787
- tristate "HW reset support for SD8787 BT + Wifi module"
- depends on OF && (MWIFIEX || BT_MRVL_SDIO || LIBERTAS_SDIO || WILC1000_SDIO)
- help
- This selects hardware reset support for the SD8787 BT + Wifi
- module. By default this option is set to n.
-
- This driver can also be built as a module. If so, the module
- will be called pwrseq_sd8787.
-
-config PWRSEQ_SIMPLE
- tristate "Simple HW reset support for MMC"
- default y
- depends on OF
- help
- This selects simple hardware reset support aka pwrseq-simple for MMC
- devices. By default this option is set to y.
-
- This driver can also be built as a module. If so, the module
- will be called pwrseq_simple.
-
config MMC_BLOCK
tristate "MMC block device driver"
depends on BLOCK
diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile
index 6a907736cd7a..322eb69bd00e 100644
--- a/drivers/mmc/core/Makefile
+++ b/drivers/mmc/core/Makefile
@@ -10,9 +10,6 @@ mmc_core-y := core.o bus.o host.o \
sdio_cis.o sdio_io.o sdio_irq.o \
slot-gpio.o regulator.o
mmc_core-$(CONFIG_OF) += pwrseq.o
-obj-$(CONFIG_PWRSEQ_SIMPLE) += pwrseq_simple.o
-obj-$(CONFIG_PWRSEQ_SD8787) += pwrseq_sd8787.o
-obj-$(CONFIG_PWRSEQ_EMMC) += pwrseq_emmc.o
mmc_core-$(CONFIG_DEBUG_FS) += debugfs.o
obj-$(CONFIG_MMC_BLOCK) += mmc_block.o
mmc_block-objs := block.o queue.o
diff --git a/drivers/mmc/core/pwrseq_emmc.c b/drivers/mmc/core/pwrseq_emmc.c
deleted file mode 100644
index f6dde9edd7a3..000000000000
--- a/drivers/mmc/core/pwrseq_emmc.c
+++ /dev/null
@@ -1,120 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Copyright (C) 2015, Samsung Electronics Co., Ltd.
- *
- * Author: Marek Szyprowski <[email protected]>
- *
- * Simple eMMC hardware reset provider
- */
-#include <linux/delay.h>
-#include <linux/kernel.h>
-#include <linux/init.h>
-#include <linux/platform_device.h>
-#include <linux/module.h>
-#include <linux/slab.h>
-#include <linux/device.h>
-#include <linux/err.h>
-#include <linux/gpio/consumer.h>
-#include <linux/reboot.h>
-
-#include <linux/mmc/host.h>
-
-#include "pwrseq.h"
-
-struct mmc_pwrseq_emmc {
- struct mmc_pwrseq pwrseq;
- struct notifier_block reset_nb;
- struct gpio_desc *reset_gpio;
-};
-
-#define to_pwrseq_emmc(p) container_of(p, struct mmc_pwrseq_emmc, pwrseq)
-
-static void mmc_pwrseq_emmc_reset(struct mmc_host *host)
-{
- struct mmc_pwrseq_emmc *pwrseq = to_pwrseq_emmc(host->pwrseq);
-
- gpiod_set_value_cansleep(pwrseq->reset_gpio, 1);
- udelay(1);
- gpiod_set_value_cansleep(pwrseq->reset_gpio, 0);
- udelay(200);
-}
-
-static int mmc_pwrseq_emmc_reset_nb(struct notifier_block *this,
- unsigned long mode, void *cmd)
-{
- struct mmc_pwrseq_emmc *pwrseq = container_of(this,
- struct mmc_pwrseq_emmc, reset_nb);
- gpiod_set_value(pwrseq->reset_gpio, 1);
- udelay(1);
- gpiod_set_value(pwrseq->reset_gpio, 0);
- udelay(200);
-
- return NOTIFY_DONE;
-}
-
-static const struct mmc_pwrseq_ops mmc_pwrseq_emmc_ops = {
- .reset = mmc_pwrseq_emmc_reset,
-};
-
-static int mmc_pwrseq_emmc_probe(struct platform_device *pdev)
-{
- struct mmc_pwrseq_emmc *pwrseq;
- struct device *dev = &pdev->dev;
-
- pwrseq = devm_kzalloc(dev, sizeof(*pwrseq), GFP_KERNEL);
- if (!pwrseq)
- return -ENOMEM;
-
- pwrseq->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
- if (IS_ERR(pwrseq->reset_gpio))
- return PTR_ERR(pwrseq->reset_gpio);
-
- if (!gpiod_cansleep(pwrseq->reset_gpio)) {
- /*
- * register reset handler to ensure emmc reset also from
- * emergency_reboot(), priority 255 is the highest priority
- * so it will be executed before any system reboot handler.
- */
- pwrseq->reset_nb.notifier_call = mmc_pwrseq_emmc_reset_nb;
- pwrseq->reset_nb.priority = 255;
- register_restart_handler(&pwrseq->reset_nb);
- } else {
- dev_notice(dev, "EMMC reset pin tied to a sleepy GPIO driver; reset on emergency-reboot disabled\n");
- }
-
- pwrseq->pwrseq.ops = &mmc_pwrseq_emmc_ops;
- pwrseq->pwrseq.dev = dev;
- pwrseq->pwrseq.owner = THIS_MODULE;
- platform_set_drvdata(pdev, pwrseq);
-
- return mmc_pwrseq_register(&pwrseq->pwrseq);
-}
-
-static int mmc_pwrseq_emmc_remove(struct platform_device *pdev)
-{
- struct mmc_pwrseq_emmc *pwrseq = platform_get_drvdata(pdev);
-
- unregister_restart_handler(&pwrseq->reset_nb);
- mmc_pwrseq_unregister(&pwrseq->pwrseq);
-
- return 0;
-}
-
-static const struct of_device_id mmc_pwrseq_emmc_of_match[] = {
- { .compatible = "mmc-pwrseq-emmc",},
- {/* sentinel */},
-};
-
-MODULE_DEVICE_TABLE(of, mmc_pwrseq_emmc_of_match);
-
-static struct platform_driver mmc_pwrseq_emmc_driver = {
- .probe = mmc_pwrseq_emmc_probe,
- .remove = mmc_pwrseq_emmc_remove,
- .driver = {
- .name = "pwrseq_emmc",
- .of_match_table = mmc_pwrseq_emmc_of_match,
- },
-};
-
-module_platform_driver(mmc_pwrseq_emmc_driver);
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/core/pwrseq_sd8787.c b/drivers/mmc/core/pwrseq_sd8787.c
deleted file mode 100644
index 2e120ad83020..000000000000
--- a/drivers/mmc/core/pwrseq_sd8787.c
+++ /dev/null
@@ -1,117 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * pwrseq_sd8787.c - power sequence support for Marvell SD8787 BT + Wifi chip
- *
- * Copyright (C) 2016 Matt Ranostay <[email protected]>
- *
- * Based on the original work pwrseq_simple.c
- * Copyright (C) 2014 Linaro Ltd
- * Author: Ulf Hansson <[email protected]>
- */
-
-#include <linux/delay.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/platform_device.h>
-#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/slab.h>
-#include <linux/device.h>
-#include <linux/err.h>
-#include <linux/gpio/consumer.h>
-
-#include <linux/mmc/host.h>
-
-#include "pwrseq.h"
-
-struct mmc_pwrseq_sd8787 {
- struct mmc_pwrseq pwrseq;
- struct gpio_desc *reset_gpio;
- struct gpio_desc *pwrdn_gpio;
- u32 reset_pwrdwn_delay_ms;
-};
-
-#define to_pwrseq_sd8787(p) container_of(p, struct mmc_pwrseq_sd8787, pwrseq)
-
-static void mmc_pwrseq_sd8787_pre_power_on(struct mmc_host *host)
-{
- struct mmc_pwrseq_sd8787 *pwrseq = to_pwrseq_sd8787(host->pwrseq);
-
- gpiod_set_value_cansleep(pwrseq->reset_gpio, 1);
-
- msleep(pwrseq->reset_pwrdwn_delay_ms);
- gpiod_set_value_cansleep(pwrseq->pwrdn_gpio, 1);
-}
-
-static void mmc_pwrseq_sd8787_power_off(struct mmc_host *host)
-{
- struct mmc_pwrseq_sd8787 *pwrseq = to_pwrseq_sd8787(host->pwrseq);
-
- gpiod_set_value_cansleep(pwrseq->pwrdn_gpio, 0);
- gpiod_set_value_cansleep(pwrseq->reset_gpio, 0);
-}
-
-static const struct mmc_pwrseq_ops mmc_pwrseq_sd8787_ops = {
- .pre_power_on = mmc_pwrseq_sd8787_pre_power_on,
- .power_off = mmc_pwrseq_sd8787_power_off,
-};
-
-static const u32 sd8787_delay_ms = 300;
-static const u32 wilc1000_delay_ms = 5;
-
-static const struct of_device_id mmc_pwrseq_sd8787_of_match[] = {
- { .compatible = "mmc-pwrseq-sd8787", .data = &sd8787_delay_ms },
- { .compatible = "mmc-pwrseq-wilc1000", .data = &wilc1000_delay_ms },
- {/* sentinel */},
-};
-MODULE_DEVICE_TABLE(of, mmc_pwrseq_sd8787_of_match);
-
-static int mmc_pwrseq_sd8787_probe(struct platform_device *pdev)
-{
- struct mmc_pwrseq_sd8787 *pwrseq;
- struct device *dev = &pdev->dev;
- const struct of_device_id *match;
-
- pwrseq = devm_kzalloc(dev, sizeof(*pwrseq), GFP_KERNEL);
- if (!pwrseq)
- return -ENOMEM;
-
- match = of_match_node(mmc_pwrseq_sd8787_of_match, pdev->dev.of_node);
- pwrseq->reset_pwrdwn_delay_ms = *(u32 *)match->data;
-
- pwrseq->pwrdn_gpio = devm_gpiod_get(dev, "powerdown", GPIOD_OUT_LOW);
- if (IS_ERR(pwrseq->pwrdn_gpio))
- return PTR_ERR(pwrseq->pwrdn_gpio);
-
- pwrseq->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
- if (IS_ERR(pwrseq->reset_gpio))
- return PTR_ERR(pwrseq->reset_gpio);
-
- pwrseq->pwrseq.dev = dev;
- pwrseq->pwrseq.ops = &mmc_pwrseq_sd8787_ops;
- pwrseq->pwrseq.owner = THIS_MODULE;
- platform_set_drvdata(pdev, pwrseq);
-
- return mmc_pwrseq_register(&pwrseq->pwrseq);
-}
-
-static int mmc_pwrseq_sd8787_remove(struct platform_device *pdev)
-{
- struct mmc_pwrseq_sd8787 *pwrseq = platform_get_drvdata(pdev);
-
- mmc_pwrseq_unregister(&pwrseq->pwrseq);
-
- return 0;
-}
-
-static struct platform_driver mmc_pwrseq_sd8787_driver = {
- .probe = mmc_pwrseq_sd8787_probe,
- .remove = mmc_pwrseq_sd8787_remove,
- .driver = {
- .name = "pwrseq_sd8787",
- .of_match_table = mmc_pwrseq_sd8787_of_match,
- },
-};
-
-module_platform_driver(mmc_pwrseq_sd8787_driver);
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/core/pwrseq_simple.c b/drivers/mmc/core/pwrseq_simple.c
deleted file mode 100644
index ea4d3670560e..000000000000
--- a/drivers/mmc/core/pwrseq_simple.c
+++ /dev/null
@@ -1,164 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Copyright (C) 2014 Linaro Ltd
- *
- * Author: Ulf Hansson <[email protected]>
- *
- * Simple MMC power sequence management
- */
-#include <linux/clk.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/platform_device.h>
-#include <linux/module.h>
-#include <linux/slab.h>
-#include <linux/device.h>
-#include <linux/err.h>
-#include <linux/gpio/consumer.h>
-#include <linux/delay.h>
-#include <linux/property.h>
-
-#include <linux/mmc/host.h>
-
-#include "pwrseq.h"
-
-struct mmc_pwrseq_simple {
- struct mmc_pwrseq pwrseq;
- bool clk_enabled;
- u32 post_power_on_delay_ms;
- u32 power_off_delay_us;
- struct clk *ext_clk;
- struct gpio_descs *reset_gpios;
-};
-
-#define to_pwrseq_simple(p) container_of(p, struct mmc_pwrseq_simple, pwrseq)
-
-static void mmc_pwrseq_simple_set_gpios_value(struct mmc_pwrseq_simple *pwrseq,
- int value)
-{
- struct gpio_descs *reset_gpios = pwrseq->reset_gpios;
-
- if (!IS_ERR(reset_gpios)) {
- unsigned long *values;
- int nvalues = reset_gpios->ndescs;
-
- values = bitmap_alloc(nvalues, GFP_KERNEL);
- if (!values)
- return;
-
- if (value)
- bitmap_fill(values, nvalues);
- else
- bitmap_zero(values, nvalues);
-
- gpiod_set_array_value_cansleep(nvalues, reset_gpios->desc,
- reset_gpios->info, values);
-
- kfree(values);
- }
-}
-
-static void mmc_pwrseq_simple_pre_power_on(struct mmc_host *host)
-{
- struct mmc_pwrseq_simple *pwrseq = to_pwrseq_simple(host->pwrseq);
-
- if (!IS_ERR(pwrseq->ext_clk) && !pwrseq->clk_enabled) {
- clk_prepare_enable(pwrseq->ext_clk);
- pwrseq->clk_enabled = true;
- }
-
- mmc_pwrseq_simple_set_gpios_value(pwrseq, 1);
-}
-
-static void mmc_pwrseq_simple_post_power_on(struct mmc_host *host)
-{
- struct mmc_pwrseq_simple *pwrseq = to_pwrseq_simple(host->pwrseq);
-
- mmc_pwrseq_simple_set_gpios_value(pwrseq, 0);
-
- if (pwrseq->post_power_on_delay_ms)
- msleep(pwrseq->post_power_on_delay_ms);
-}
-
-static void mmc_pwrseq_simple_power_off(struct mmc_host *host)
-{
- struct mmc_pwrseq_simple *pwrseq = to_pwrseq_simple(host->pwrseq);
-
- mmc_pwrseq_simple_set_gpios_value(pwrseq, 1);
-
- if (pwrseq->power_off_delay_us)
- usleep_range(pwrseq->power_off_delay_us,
- 2 * pwrseq->power_off_delay_us);
-
- if (!IS_ERR(pwrseq->ext_clk) && pwrseq->clk_enabled) {
- clk_disable_unprepare(pwrseq->ext_clk);
- pwrseq->clk_enabled = false;
- }
-}
-
-static const struct mmc_pwrseq_ops mmc_pwrseq_simple_ops = {
- .pre_power_on = mmc_pwrseq_simple_pre_power_on,
- .post_power_on = mmc_pwrseq_simple_post_power_on,
- .power_off = mmc_pwrseq_simple_power_off,
-};
-
-static const struct of_device_id mmc_pwrseq_simple_of_match[] = {
- { .compatible = "mmc-pwrseq-simple",},
- {/* sentinel */},
-};
-MODULE_DEVICE_TABLE(of, mmc_pwrseq_simple_of_match);
-
-static int mmc_pwrseq_simple_probe(struct platform_device *pdev)
-{
- struct mmc_pwrseq_simple *pwrseq;
- struct device *dev = &pdev->dev;
-
- pwrseq = devm_kzalloc(dev, sizeof(*pwrseq), GFP_KERNEL);
- if (!pwrseq)
- return -ENOMEM;
-
- pwrseq->ext_clk = devm_clk_get(dev, "ext_clock");
- if (IS_ERR(pwrseq->ext_clk) && PTR_ERR(pwrseq->ext_clk) != -ENOENT)
- return PTR_ERR(pwrseq->ext_clk);
-
- pwrseq->reset_gpios = devm_gpiod_get_array(dev, "reset",
- GPIOD_OUT_HIGH);
- if (IS_ERR(pwrseq->reset_gpios) &&
- PTR_ERR(pwrseq->reset_gpios) != -ENOENT &&
- PTR_ERR(pwrseq->reset_gpios) != -ENOSYS) {
- return PTR_ERR(pwrseq->reset_gpios);
- }
-
- device_property_read_u32(dev, "post-power-on-delay-ms",
- &pwrseq->post_power_on_delay_ms);
- device_property_read_u32(dev, "power-off-delay-us",
- &pwrseq->power_off_delay_us);
-
- pwrseq->pwrseq.dev = dev;
- pwrseq->pwrseq.ops = &mmc_pwrseq_simple_ops;
- pwrseq->pwrseq.owner = THIS_MODULE;
- platform_set_drvdata(pdev, pwrseq);
-
- return mmc_pwrseq_register(&pwrseq->pwrseq);
-}
-
-static int mmc_pwrseq_simple_remove(struct platform_device *pdev)
-{
- struct mmc_pwrseq_simple *pwrseq = platform_get_drvdata(pdev);
-
- mmc_pwrseq_unregister(&pwrseq->pwrseq);
-
- return 0;
-}
-
-static struct platform_driver mmc_pwrseq_simple_driver = {
- .probe = mmc_pwrseq_simple_probe,
- .remove = mmc_pwrseq_simple_remove,
- .driver = {
- .name = "pwrseq_simple",
- .of_match_table = mmc_pwrseq_simple_of_match,
- },
-};
-
-module_platform_driver(mmc_pwrseq_simple_driver);
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/pwrseq/Kconfig b/drivers/power/pwrseq/Kconfig
index dab8f4d860fe..1985f13d9193 100644
--- a/drivers/power/pwrseq/Kconfig
+++ b/drivers/power/pwrseq/Kconfig
@@ -11,4 +11,36 @@ menuconfig PWRSEQ
if PWRSEQ
+config PWRSEQ_EMMC
+ tristate "HW reset support for eMMC"
+ default y
+ depends on OF
+ help
+ This selects Hardware reset support aka pwrseq-emmc for eMMC
+ devices. By default this option is set to y.
+
+ This driver can also be built as a module. If so, the module
+ will be called pwrseq_emmc.
+
+config PWRSEQ_SD8787
+ tristate "HW reset support for SD8787 BT + Wifi module"
+ depends on OF
+ help
+ This selects hardware reset support for the SD8787 BT + Wifi
+ module. By default this option is set to n.
+
+ This driver can also be built as a module. If so, the module
+ will be called pwrseq_sd8787.
+
+config PWRSEQ_SIMPLE
+ tristate "Simple HW reset support"
+ default y
+ depends on OF
+ help
+ This selects simple hardware reset support aka pwrseq-simple.
+ By default this option is set to y.
+
+ This driver can also be built as a module. If so, the module
+ will be called pwrseq_simple.
+
endif
diff --git a/drivers/power/pwrseq/Makefile b/drivers/power/pwrseq/Makefile
index 108429ff6445..6f359d228843 100644
--- a/drivers/power/pwrseq/Makefile
+++ b/drivers/power/pwrseq/Makefile
@@ -4,3 +4,7 @@
#
obj-$(CONFIG_PWRSEQ) += core.o
+
+obj-$(CONFIG_PWRSEQ_EMMC) += pwrseq_emmc.o
+obj-$(CONFIG_PWRSEQ_SD8787) += pwrseq_sd8787.o
+obj-$(CONFIG_PWRSEQ_SIMPLE) += pwrseq_simple.o
diff --git a/drivers/power/pwrseq/pwrseq_emmc.c b/drivers/power/pwrseq/pwrseq_emmc.c
new file mode 100644
index 000000000000..954bbb44979d
--- /dev/null
+++ b/drivers/power/pwrseq/pwrseq_emmc.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2015, Samsung Electronics Co., Ltd.
+ *
+ * Author: Marek Szyprowski <[email protected]>
+ *
+ * Simple eMMC hardware reset provider
+ */
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/reboot.h>
+#include <linux/pwrseq/driver.h>
+
+struct pwrseq_emmc {
+ struct notifier_block reset_nb;
+ struct gpio_desc *reset_gpio;
+};
+
+static void pwrseq_ereset(struct pwrseq *pwrseq)
+{
+ struct pwrseq_emmc *pwrseq_emmc = pwrseq_get_drvdata(pwrseq);
+
+ gpiod_set_value_cansleep(pwrseq_emmc->reset_gpio, 1);
+ udelay(1);
+ gpiod_set_value_cansleep(pwrseq_emmc->reset_gpio, 0);
+ udelay(200);
+}
+
+static int pwrseq_ereset_nb(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ struct pwrseq_emmc *pwrseq_emmc = container_of(this,
+ struct pwrseq_emmc, reset_nb);
+ gpiod_set_value(pwrseq_emmc->reset_gpio, 1);
+ udelay(1);
+ gpiod_set_value(pwrseq_emmc->reset_gpio, 0);
+ udelay(200);
+
+ return NOTIFY_DONE;
+}
+
+static const struct pwrseq_ops pwrseq_eops = {
+ .reset = pwrseq_ereset,
+};
+
+static int pwrseq_eprobe(struct platform_device *pdev)
+{
+ struct pwrseq_emmc *pwrseq_emmc;
+ struct pwrseq *pwrseq;
+ struct pwrseq_provider *provider;
+ struct device *dev = &pdev->dev;
+
+ pwrseq_emmc = devm_kzalloc(dev, sizeof(*pwrseq_emmc), GFP_KERNEL);
+ if (!pwrseq_emmc)
+ return -ENOMEM;
+
+ pwrseq_emmc->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(pwrseq_emmc->reset_gpio))
+ return PTR_ERR(pwrseq_emmc->reset_gpio);
+
+ if (!gpiod_cansleep(pwrseq_emmc->reset_gpio)) {
+ /*
+ * register reset handler to ensure emmc reset also from
+ * emergency_reboot(), priority 255 is the highest priority
+ * so it will be executed before any system reboot handler.
+ */
+ pwrseq_emmc->reset_nb.notifier_call = pwrseq_ereset_nb;
+ pwrseq_emmc->reset_nb.priority = 255;
+ register_restart_handler(&pwrseq_emmc->reset_nb);
+ } else {
+ dev_notice(dev, "EMMC reset pin tied to a sleepy GPIO driver; reset on emergency-reboot disabled\n");
+ }
+
+ platform_set_drvdata(pdev, pwrseq_emmc);
+
+ pwrseq = devm_pwrseq_create(dev, &pwrseq_eops);
+ if (IS_ERR(pwrseq))
+ return PTR_ERR(pwrseq);
+
+ pwrseq_set_drvdata(pwrseq, pwrseq_emmc);
+
+ provider = devm_of_pwrseq_provider_register(dev, of_pwrseq_xlate_single, pwrseq);
+
+ return PTR_ERR_OR_ZERO(provider);
+}
+
+static int pwrseq_eremove(struct platform_device *pdev)
+{
+ struct pwrseq_emmc *pwrseq_emmc = platform_get_drvdata(pdev);
+
+ unregister_restart_handler(&pwrseq_emmc->reset_nb);
+
+ return 0;
+}
+
+static const struct of_device_id pwrseq_eof_match[] = {
+ { .compatible = "mmc-pwrseq-emmc",},
+ {/* sentinel */},
+};
+
+MODULE_DEVICE_TABLE(of, pwrseq_eof_match);
+
+static struct platform_driver pwrseq_edriver = {
+ .probe = pwrseq_eprobe,
+ .remove = pwrseq_eremove,
+ .driver = {
+ .name = "pwrseq_emmc",
+ .of_match_table = pwrseq_eof_match,
+ },
+};
+
+module_platform_driver(pwrseq_edriver);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/pwrseq/pwrseq_sd8787.c b/drivers/power/pwrseq/pwrseq_sd8787.c
new file mode 100644
index 000000000000..aeb80327f40d
--- /dev/null
+++ b/drivers/power/pwrseq/pwrseq_sd8787.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * pwrseq_sd8787.c - power sequence support for Marvell SD8787 BT + Wifi chip
+ *
+ * Copyright (C) 2016 Matt Ranostay <[email protected]>
+ *
+ * Based on the original work pwrseq_sd8787.c
+ * Copyright (C) 2014 Linaro Ltd
+ * Author: Ulf Hansson <[email protected]>
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+
+#include <linux/pwrseq/driver.h>
+
+struct pwrseq_sd8787 {
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *pwrdn_gpio;
+ u32 reset_pwrdwn_delay_ms;
+};
+
+static int pwrseq_sd8787_pre_power_on(struct pwrseq *pwrseq)
+{
+ struct pwrseq_sd8787 *pwrseq_sd8787 = pwrseq_get_drvdata(pwrseq);
+
+ gpiod_set_value_cansleep(pwrseq_sd8787->reset_gpio, 1);
+
+ msleep(pwrseq_sd8787->reset_pwrdwn_delay_ms);
+ gpiod_set_value_cansleep(pwrseq_sd8787->pwrdn_gpio, 1);
+
+ return 0;
+}
+
+static void pwrseq_sd8787_power_off(struct pwrseq *pwrseq)
+{
+ struct pwrseq_sd8787 *pwrseq_sd8787 = pwrseq_get_drvdata(pwrseq);
+
+ gpiod_set_value_cansleep(pwrseq_sd8787->pwrdn_gpio, 0);
+ gpiod_set_value_cansleep(pwrseq_sd8787->reset_gpio, 0);
+}
+
+static const struct pwrseq_ops pwrseq_sd8787_ops = {
+ .pre_power_on = pwrseq_sd8787_pre_power_on,
+ .power_off = pwrseq_sd8787_power_off,
+};
+
+static const u32 sd8787_delay_ms = 300;
+static const u32 wilc1000_delay_ms = 5;
+
+static const struct of_device_id pwrseq_sd8787_of_match[] = {
+ { .compatible = "mmc-pwrseq-sd8787", .data = &sd8787_delay_ms },
+ { .compatible = "mmc-pwrseq-wilc1000", .data = &wilc1000_delay_ms },
+ {/* sentinel */},
+};
+MODULE_DEVICE_TABLE(of, pwrseq_sd8787_of_match);
+
+static int pwrseq_sd8787_probe(struct platform_device *pdev)
+{
+ struct pwrseq_sd8787 *pwrseq_sd8787;
+ struct pwrseq *pwrseq;
+ struct pwrseq_provider *provider;
+ struct device *dev = &pdev->dev;
+
+ pwrseq_sd8787 = devm_kzalloc(dev, sizeof(*pwrseq_sd8787), GFP_KERNEL);
+ if (!pwrseq_sd8787)
+ return -ENOMEM;
+
+ pwrseq_sd8787->reset_pwrdwn_delay_ms = *(u32 *)of_device_get_match_data(dev);
+
+ pwrseq_sd8787->pwrdn_gpio = devm_gpiod_get(dev, "powerdown", GPIOD_OUT_LOW);
+ if (IS_ERR(pwrseq_sd8787->pwrdn_gpio))
+ return PTR_ERR(pwrseq_sd8787->pwrdn_gpio);
+
+ pwrseq_sd8787->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(pwrseq_sd8787->reset_gpio))
+ return PTR_ERR(pwrseq_sd8787->reset_gpio);
+
+ pwrseq = devm_pwrseq_create(dev, &pwrseq_sd8787_ops);
+ if (IS_ERR(pwrseq))
+ return PTR_ERR(pwrseq);
+
+ pwrseq_set_drvdata(pwrseq, pwrseq_sd8787);
+
+ provider = devm_of_pwrseq_provider_register(dev, of_pwrseq_xlate_single, pwrseq);
+
+ return PTR_ERR_OR_ZERO(provider);
+}
+
+static struct platform_driver pwrseq_sd8787_driver = {
+ .probe = pwrseq_sd8787_probe,
+ .driver = {
+ .name = "pwrseq_sd8787",
+ .of_match_table = pwrseq_sd8787_of_match,
+ },
+};
+
+module_platform_driver(pwrseq_sd8787_driver);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/pwrseq/pwrseq_simple.c b/drivers/power/pwrseq/pwrseq_simple.c
new file mode 100644
index 000000000000..4889fd5a11e0
--- /dev/null
+++ b/drivers/power/pwrseq/pwrseq_simple.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2014 Linaro Ltd
+ *
+ * Author: Ulf Hansson <[email protected]>
+ *
+ * Simple MMC power sequence management
+ */
+#include <linux/clk.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+#include <linux/property.h>
+#include <linux/pwrseq/driver.h>
+
+struct pwrseq_simple {
+ bool clk_enabled;
+ u32 post_power_on_delay_ms;
+ u32 power_off_delay_us;
+ struct clk *ext_clk;
+ struct gpio_descs *reset_gpios;
+};
+
+static int pwrseq_simple_set_gpios_value(struct pwrseq_simple *pwrseq_simple,
+ int value)
+{
+ struct gpio_descs *reset_gpios = pwrseq_simple->reset_gpios;
+ unsigned long *values;
+ int nvalues;
+ int ret;
+
+ if (IS_ERR(reset_gpios))
+ return PTR_ERR(reset_gpios);
+
+ nvalues = reset_gpios->ndescs;
+
+ values = bitmap_alloc(nvalues, GFP_KERNEL);
+ if (!values)
+ return -ENOMEM;
+
+ if (value)
+ bitmap_fill(values, nvalues);
+ else
+ bitmap_zero(values, nvalues);
+
+ ret = gpiod_set_array_value_cansleep(nvalues, reset_gpios->desc,
+ reset_gpios->info, values);
+ kfree(values);
+
+ return ret;
+}
+
+static int pwrseq_simple_pre_power_on(struct pwrseq *pwrseq)
+{
+ struct pwrseq_simple *pwrseq_simple = pwrseq_get_drvdata(pwrseq);
+
+ if (!IS_ERR(pwrseq_simple->ext_clk) && !pwrseq_simple->clk_enabled) {
+ clk_prepare_enable(pwrseq_simple->ext_clk);
+ pwrseq_simple->clk_enabled = true;
+ }
+
+ return pwrseq_simple_set_gpios_value(pwrseq_simple, 1);
+}
+
+static int pwrseq_simple_power_on(struct pwrseq *pwrseq)
+{
+ struct pwrseq_simple *pwrseq_simple = pwrseq_get_drvdata(pwrseq);
+ int ret;
+
+ ret = pwrseq_simple_set_gpios_value(pwrseq_simple, 0);
+ if (ret)
+ return ret;
+
+ if (pwrseq_simple->post_power_on_delay_ms)
+ msleep(pwrseq_simple->post_power_on_delay_ms);
+
+ return 0;
+}
+
+static void pwrseq_simple_power_off(struct pwrseq *pwrseq)
+{
+ struct pwrseq_simple *pwrseq_simple = pwrseq_get_drvdata(pwrseq);
+
+ pwrseq_simple_set_gpios_value(pwrseq_simple, 1);
+
+ if (pwrseq_simple->power_off_delay_us)
+ usleep_range(pwrseq_simple->power_off_delay_us,
+ 2 * pwrseq_simple->power_off_delay_us);
+
+ if (!IS_ERR(pwrseq_simple->ext_clk) && pwrseq_simple->clk_enabled) {
+ clk_disable_unprepare(pwrseq_simple->ext_clk);
+ pwrseq_simple->clk_enabled = false;
+ }
+}
+
+static const struct pwrseq_ops pwrseq_simple_ops = {
+ .pre_power_on = pwrseq_simple_pre_power_on,
+ .power_on = pwrseq_simple_power_on,
+ .power_off = pwrseq_simple_power_off,
+};
+
+static const struct of_device_id pwrseq_simple_of_match[] = {
+ { .compatible = "mmc-pwrseq-simple",}, /* MMC-specific compatible */
+ {/* sentinel */},
+};
+MODULE_DEVICE_TABLE(of, pwrseq_simple_of_match);
+
+static int pwrseq_simple_probe(struct platform_device *pdev)
+{
+ struct pwrseq_simple *pwrseq_simple;
+ struct pwrseq *pwrseq;
+ struct pwrseq_provider *provider;
+ struct device *dev = &pdev->dev;
+
+ pwrseq_simple = devm_kzalloc(dev, sizeof(*pwrseq_simple), GFP_KERNEL);
+ if (!pwrseq_simple)
+ return -ENOMEM;
+
+ pwrseq_simple->ext_clk = devm_clk_get(dev, "ext_clock");
+ if (IS_ERR(pwrseq_simple->ext_clk) && PTR_ERR(pwrseq_simple->ext_clk) != -ENOENT)
+ return PTR_ERR(pwrseq_simple->ext_clk);
+
+ pwrseq_simple->reset_gpios = devm_gpiod_get_array(dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(pwrseq_simple->reset_gpios) &&
+ PTR_ERR(pwrseq_simple->reset_gpios) != -ENOENT &&
+ PTR_ERR(pwrseq_simple->reset_gpios) != -ENOSYS) {
+ return PTR_ERR(pwrseq_simple->reset_gpios);
+ }
+
+ device_property_read_u32(dev, "post-power-on-delay-ms",
+ &pwrseq_simple->post_power_on_delay_ms);
+ device_property_read_u32(dev, "power-off-delay-us",
+ &pwrseq_simple->power_off_delay_us);
+
+ pwrseq = devm_pwrseq_create(dev, &pwrseq_simple_ops);
+ if (IS_ERR(pwrseq))
+ return PTR_ERR(pwrseq);
+
+ pwrseq_set_drvdata(pwrseq, pwrseq_simple);
+
+ provider = devm_of_pwrseq_provider_register(dev, of_pwrseq_xlate_single, pwrseq);
+
+ return PTR_ERR_OR_ZERO(provider);
+}
+
+static struct platform_driver pwrseq_simple_driver = {
+ .probe = pwrseq_simple_probe,
+ .driver = {
+ .name = "pwrseq_simple",
+ .of_match_table = pwrseq_simple_of_match,
+ },
+};
+
+module_platform_driver(pwrseq_simple_driver);
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/pwrseq/driver.h b/include/linux/pwrseq/driver.h
index bdb8a25a8504..0ca1d0311ab6 100644
--- a/include/linux/pwrseq/driver.h
+++ b/include/linux/pwrseq/driver.h
@@ -57,7 +57,7 @@ struct pwrseq *__devm_pwrseq_create(struct device *dev, struct module *owner, co
*
* Return: created instance or the wrapped error code.
*/
-#define pwrseq_create(dev, ops, data) __pwrseq_create((dev), THIS_MODULE, (ops))
+#define pwrseq_create(dev, ops) __pwrseq_create((dev), THIS_MODULE, (ops))
/**
* devm_pwrseq_create() - devres-managed version of pwrseq_create
@@ -71,7 +71,7 @@ struct pwrseq *__devm_pwrseq_create(struct device *dev, struct module *owner, co
*
* Return: created instance or the wrapped error code.
*/
-#define devm_pwrseq_create(dev, ops, data) __devm_pwrseq_create((dev), THIS_MODULE, (ops))
+#define devm_pwrseq_create(dev, ops) __devm_pwrseq_create((dev), THIS_MODULE, (ops))
void pwrseq_destroy(struct pwrseq *pwrseq);
--
2.33.0
Add support for the bluetooth part of the QCA6391 BT+WiFi chip present
on the RB5 board. WiFi is not supported yet, as it requires separate
handling of the PCIe device power.
Signed-off-by: Dmitry Baryshkov <[email protected]>
---
arch/arm64/boot/dts/qcom/qrb5165-rb5.dts | 50 ++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
index 8ac96f8e79d4..326330f528fc 100644
--- a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
+++ b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
@@ -19,6 +19,7 @@ / {
aliases {
serial0 = &uart12;
+ serial1 = &uart6;
sdhc2 = &sdhc_2;
};
@@ -98,6 +99,25 @@ lt9611_3v3: lt9611-3v3 {
regulator-always-on;
};
+ qca_pwrseq: qca-pwrseq {
+ compatible = "qcom,qca6390-pwrseq";
+
+ #pwrseq-cells = <1>;
+
+ vddaon-supply = <&vreg_s6a_0p95>;
+ vddpmu-supply = <&vreg_s2f_0p95>;
+ vddrfa1-supply = <&vreg_s2f_0p95>;
+ vddrfa2-supply = <&vreg_s8c_1p3>;
+ vddrfa3-supply = <&vreg_s5a_1p9>;
+ vddpcie1-supply = <&vreg_s8c_1p3>;
+ vddpcie2-supply = <&vreg_s5a_1p9>;
+ vddio-supply = <&vreg_s4a_1p8>;
+
+ bt-enable-gpios = <&tlmm 21 GPIO_ACTIVE_HIGH>;
+ wifi-enable-gpios = <&tlmm 20 GPIO_ACTIVE_HIGH>;
+ swctrl-gpios = <&tlmm 124 GPIO_ACTIVE_HIGH>;
+ };
+
thermal-zones {
conn-thermal {
polling-delay-passive = <0>;
@@ -804,6 +824,26 @@ lt9611_rst_pin: lt9611-rst-pin {
};
};
+&qup_uart6_default {
+ ctsrx {
+ pins = "gpio16", "gpio19";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ rts {
+ pins = "gpio17";
+ drive-strength = <2>;
+ bias-disable;
+ };
+
+ tx {
+ pins = "gpio18";
+ drive-strength = <2>;
+ bias-pull-up;
+ };
+};
+
&qupv3_id_0 {
status = "okay";
};
@@ -1193,6 +1233,16 @@ sdc2_card_det_n: sd-card-det-n {
};
};
+&uart6 {
+ status = "okay";
+ bluetooth {
+ compatible = "qcom,qca6390-bt";
+ clocks = <&sleep_clk>;
+
+ bt-pwrseq = <&qca_pwrseq 1>;
+ };
+};
+
&uart12 {
status = "okay";
};
--
2.33.0
Signed-off-by: Dmitry Baryshkov <[email protected]>
---
arch/arm64/boot/dts/qcom/qrb5165-rb5.dts | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
index 326330f528fc..0c347cb6f8e0 100644
--- a/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
+++ b/arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
@@ -689,6 +689,7 @@ wifi-therm@1 {
&pcie0 {
status = "okay";
+ bus-pwrseq = <&qca_pwrseq 0>;
};
&pcie0_phy {
--
2.33.0
Dmitry Baryshkov <[email protected]> writes:
> This is a proposed power sequencer subsystem. This is a
> generification of the MMC pwrseq code. The subsystem tries to abstract
> the idea of complex power-up/power-down/reset of the devices.
>
> The primary set of devices that promted me to create this patchset is
> the Qualcomm BT+WiFi family of chips. They reside on serial+platform
> or serial + SDIO interfaces (older generations) or on serial+PCIe (newer
> generations).
Instead of older and newer, it would be more unstandable to mention
specific chips. For example I have no clue what you mean with older
generation.
--
https://patchwork.kernel.org/project/linux-wireless/list/
https://wireless.wiki.kernel.org/en/developers/documentation/submittingpatches
Hi Dmitry,
On 06/10/2021 04:53, Dmitry Baryshkov wrote:
> This is a proposed power sequencer subsystem. This is a
> generification of the MMC pwrseq code. The subsystem tries to abstract
> the idea of complex power-up/power-down/reset of the devices.
>
> The primary set of devices that promted me to create this patchset is
> the Qualcomm BT+WiFi family of chips. They reside on serial+platform
> or serial + SDIO interfaces (older generations) or on serial+PCIe (newer
> generations). They require a set of external voltage regulators to be
> powered on and (some of them) have separate WiFi and Bluetooth enable
> GPIOs.
>
> The major drawback for now is the lack of proper PCIe integration
> At this moment support for PCIe is hacked up to be able to test the
> PCIe part of qca6390. Proper PCIe support would require automatically
> powering up the devices before the scan basing on the proper device
> structure in the device tree. This two last patches are noted as WIP and
> are included into the patchset for the purpose of testing WiFi on newer
> chips (like qca6390/qca6391).
>
> Changes since RFC v2:
> - Add documentation for the pwrseq code. Document data structures,
> macros and exported functions.
> - Export of_pwrseq_xlate_onecell()
> - Add separate pwrseq_set_drvdata() function to follow the typical API
> design
> - Remove pwrseq_get_optional()/devm_pwrseq_get_optional()
> - Moved code to handle old mmc-pwrseq binding to the MMC patch
> - Split of_pwrseq_xlate_onecell() support to a separate patch
>
> Changes since RFC v1:
> - Provider pwrseq fallback support
> - Implement fallback support in pwrseq_qca.
> - Mmove susclk handling to pwrseq_qca.
> - Significantly simplify hci_qca.c changes, by dropping all legacy
> code. Now hci_qca uses only pwrseq calls to power up/down bluetooth
> parts of the chip.
>
>
>
Tested-by: Caleb Connolly <[email protected]>
[Tested on the OnePlus 6]
--
Kind Regards,
Caleb (they/them)
Hi Dmitry,
Do you still plan to refresh this series?
I know there have been multiple attempts to get something similar
landed in the past 10 year or so. Your series didn't seem to get
much pushback from maintainers, might be worth sending a refresh :)
Thanks
Matthias
On Wed, Oct 06, 2021 at 06:53:52AM +0300, Dmitry Baryshkov wrote:
> This is a proposed power sequencer subsystem. This is a
> generification of the MMC pwrseq code. The subsystem tries to abstract
> the idea of complex power-up/power-down/reset of the devices.
>
> The primary set of devices that promted me to create this patchset is
> the Qualcomm BT+WiFi family of chips. They reside on serial+platform
> or serial + SDIO interfaces (older generations) or on serial+PCIe (newer
> generations). They require a set of external voltage regulators to be
> powered on and (some of them) have separate WiFi and Bluetooth enable
> GPIOs.
>
> The major drawback for now is the lack of proper PCIe integration
> At this moment support for PCIe is hacked up to be able to test the
> PCIe part of qca6390. Proper PCIe support would require automatically
> powering up the devices before the scan basing on the proper device
> structure in the device tree. This two last patches are noted as WIP and
> are included into the patchset for the purpose of testing WiFi on newer
> chips (like qca6390/qca6391).
>
> Changes since RFC v2:
> - Add documentation for the pwrseq code. Document data structures,
> macros and exported functions.
> - Export of_pwrseq_xlate_onecell()
> - Add separate pwrseq_set_drvdata() function to follow the typical API
> design
> - Remove pwrseq_get_optional()/devm_pwrseq_get_optional()
> - Moved code to handle old mmc-pwrseq binding to the MMC patch
> - Split of_pwrseq_xlate_onecell() support to a separate patch
>
> Changes since RFC v1:
> - Provider pwrseq fallback support
> - Implement fallback support in pwrseq_qca.
> - Mmove susclk handling to pwrseq_qca.
> - Significantly simplify hci_qca.c changes, by dropping all legacy
> code. Now hci_qca uses only pwrseq calls to power up/down bluetooth
> parts of the chip.
>
>
>
>
> _______________________________________________
> ath10k mailing list
> [email protected]
> http://lists.infradead.org/mailman/listinfo/ath10k
Hi,
sorry for being late to the party.
Am Mittwoch, 19. Oktober 2022, 08:03:22 CET schrieb Dmitry Baryshkov:
> Ho,
>
> On Thu, 13 Oct 2022 at 22:50, Matthias Kaehlcke <[email protected]> wrote:
> > Do you still plan to refresh this series?
> >
> > I know there have been multiple attempts to get something similar
> > landed in the past 10 year or so. Your series didn't seem to get
> > much pushback from maintainers, might be worth sending a refresh :)
>
> Yes, I hope to return to it eventually. I just had no time for it lately.
I just found this thread while searching for power sequencing devices in
Linux. From what I understand this is transforming the existing mmc pwrseq
drivers into generic ones. What is the intention of this new subsystem? What
is it supposed to address?
In my case I have an LTE module attached via USB, but in order to use it I
need to perform several steps:
1. apply power supply
2. Issue a reset pulse(!), the length actually defines whether its a reset or
poweroff/on
3a. wait for a GPIO to toggle
3b. wait a minimum time
4a. device will enumerate on USB
4b. device can be access using UART
This is something required to actually see/detect the device in the first
place, thus it cannot be part of the device driver side.
Is this something pwrseq is supposed to address?
Best regards,
Alexander
> > On Wed, Oct 06, 2021 at 06:53:52AM +0300, Dmitry Baryshkov wrote:
> > > This is a proposed power sequencer subsystem. This is a
> > > generification of the MMC pwrseq code. The subsystem tries to abstract
> > > the idea of complex power-up/power-down/reset of the devices.
> > >
> > > The primary set of devices that promted me to create this patchset is
> > > the Qualcomm BT+WiFi family of chips. They reside on serial+platform
> > > or serial + SDIO interfaces (older generations) or on serial+PCIe (newer
> > > generations). They require a set of external voltage regulators to be
> > > powered on and (some of them) have separate WiFi and Bluetooth enable
> > > GPIOs.
> > >
> > > The major drawback for now is the lack of proper PCIe integration
> > > At this moment support for PCIe is hacked up to be able to test the
> > > PCIe part of qca6390. Proper PCIe support would require automatically
> > > powering up the devices before the scan basing on the proper device
> > > structure in the device tree. This two last patches are noted as WIP and
> > > are included into the patchset for the purpose of testing WiFi on newer
> > > chips (like qca6390/qca6391).
> > >
> > > Changes since RFC v2:
> > > - Add documentation for the pwrseq code. Document data structures,
> > >
> > > macros and exported functions.
> > >
> > > - Export of_pwrseq_xlate_onecell()
> > > - Add separate pwrseq_set_drvdata() function to follow the typical API
> > >
> > > design
> > >
> > > - Remove pwrseq_get_optional()/devm_pwrseq_get_optional()
> > > - Moved code to handle old mmc-pwrseq binding to the MMC patch
> > > - Split of_pwrseq_xlate_onecell() support to a separate patch
> > >
> > > Changes since RFC v1:
> > > - Provider pwrseq fallback support
> > > - Implement fallback support in pwrseq_qca.
> > > - Mmove susclk handling to pwrseq_qca.
> > > - Significantly simplify hci_qca.c changes, by dropping all legacy
> > >
> > > code. Now hci_qca uses only pwrseq calls to power up/down bluetooth
> > > parts of the chip.
> > >
> > > _______________________________________________
> > > ath10k mailing list
> > > [email protected]
> > > http://lists.infradead.org/mailman/listinfo/ath10k
--
TQ-Systems GmbH | M?hlstra?e 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht M?nchen, HRB 105018
Gesch?ftsf?hrer: Detlef Schneider, R?diger Stahl, Stefan Schneider
http://www.tq-group.com/
On Wed, 1 Mar 2023 at 09:17, Alexander Stein
<[email protected]> wrote:
>
> Hi,
>
> sorry for being late to the party.
>
> Am Mittwoch, 19. Oktober 2022, 08:03:22 CET schrieb Dmitry Baryshkov:
> > Ho,
> >
> > On Thu, 13 Oct 2022 at 22:50, Matthias Kaehlcke <[email protected]> wrote:
> > > Do you still plan to refresh this series?
> > >
> > > I know there have been multiple attempts to get something similar
> > > landed in the past 10 year or so. Your series didn't seem to get
> > > much pushback from maintainers, might be worth sending a refresh :)
> >
> > Yes, I hope to return to it eventually. I just had no time for it lately.
>
> I just found this thread while searching for power sequencing devices in
> Linux. From what I understand this is transforming the existing mmc pwrseq
> drivers into generic ones. What is the intention of this new subsystem? What
> is it supposed to address?
> In my case I have an LTE module attached via USB, but in order to use it I
> need to perform several steps:
> 1. apply power supply
> 2. Issue a reset pulse(!), the length actually defines whether its a reset or
> poweroff/on
> 3a. wait for a GPIO to toggle
> 3b. wait a minimum time
> 4a. device will enumerate on USB
> 4b. device can be access using UART
>
> This is something required to actually see/detect the device in the first
> place, thus it cannot be part of the device driver side.
> Is this something pwrseq is supposed to address?
Yes, this is one of those typical use-cases for discoverable buses
that we need to add support for, in one way or the other.
>
> Best regards,
> Alexander
[...]
Kind regards
Uffe