2021-08-29 13:14:07

by Dmitry Baryshkov

[permalink] [raw]
Subject: [RFC v2 00/13] create power sequencing subsystem

This is the second RFC on the 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.

To ease migration to pwrseq and to provide compatibility with older
device trees, while keeping drivers simple, this iteration of RFC
introduces pwrseq fallback support: pwrseq driver can register fallback
providers. If another device driver requests pwrseq instance and none
was declared, the pwrseq fallback code would go through the list of
fallback providers and if the match is found, driver would return a
crafted pwrseq instance. For now this mechanism is limited to the OF
device matching, but it can be extended further to use any combination
of device IDs.

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.

This patchset being an RFC tries to demonstrate the approach, design and
usage of the pwrseq subsystem. Following issues are present in the RFC
at this moment but will be fixed later if the overall approach would be
viewed as acceptable:

- No documentation
While the code tries to be self-documenting proper documentation
would be required.

- Minimal device tree bindings changes
There are no proper updates for the DT bindings (thus neither Rob
Herring nor devicetree are included in the To/Cc lists). The dt
schema changes would be a part of v1.

- 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 bus scan depending on the proper
device structure in the device tree.

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.



2021-08-29 13:14:07

by Dmitry Baryshkov

[permalink] [raw]
Subject: [RFC v2 04/13] pwrseq: add support for QCA BT+WiFi power sequencer

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 | 371 ++++++++++++++++++++++++++++++
3 files changed, 386 insertions(+)
create mode 100644 drivers/power/pwrseq/pwrseq_qca.c

diff --git a/drivers/power/pwrseq/Kconfig b/drivers/power/pwrseq/Kconfig
index 36339a456b03..990c3164144e 100644
--- a/drivers/power/pwrseq/Kconfig
+++ b/drivers/power/pwrseq/Kconfig
@@ -19,6 +19,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..7aa5f2d94039
--- /dev/null
+++ b/drivers/power/pwrseq/pwrseq_qca.c
@@ -0,0 +1,371 @@
+// 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_data(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_data(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_data(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, &pwrseq_qca->pwrseq_qcas[i]);
+ if (IS_ERR(pwrseq))
+ return PTR_ERR(pwrseq);
+
+ 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

2021-08-29 13:14:09

by Dmitry Baryshkov

[permalink] [raw]
Subject: [RFC v2 06/13] pwrseq: pwrseq_qca: implement fallback support

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 | 148 +++++++++++++++++++++++++++++-
1 file changed, 147 insertions(+), 1 deletion(-)

diff --git a/drivers/power/pwrseq/pwrseq_qca.c b/drivers/power/pwrseq/pwrseq_qca.c
index 7aa5f2d94039..d01f1ef4626b 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 */
@@ -367,5 +369,149 @@ 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;
+ 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);
+ }
+
+ return devm_pwrseq_create(dev, &pwrseq_qca_ops, &fallback->qca_one);
+}
+
+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

2021-08-29 13:14:15

by Dmitry Baryshkov

[permalink] [raw]
Subject: [RFC v2 09/13] arm64: dts: qcom: sdm845-db845c: switch bt+wifi to qca power sequencer

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 0a86fe71a66d..78e889b2c8dd 100644
--- a/arch/arm64/boot/dts/qcom/sdm845.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845.dtsi
@@ -1051,6 +1051,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

2021-08-29 13:14:17

by Dmitry Baryshkov

[permalink] [raw]
Subject: [RFC v2 12/13] WIP: PCI: qcom: use pwrseq to power up bus devices

Use bus-pwrseq device tree node to power up the devices on the bus. This
is to be rewritten with the proper code parsing the device tree and
powering up individual devices.

Signed-off-by: Dmitry Baryshkov <[email protected]>
---
drivers/pci/controller/dwc/pcie-qcom.c | 13 +++++++++++++
1 file changed, 13 insertions(+)

diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index 8a7a300163e5..a60d41fbcd6f 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -23,6 +23,7 @@
#include <linux/pm_runtime.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
+#include <linux/pwrseq/consumer.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/slab.h>
@@ -1467,6 +1468,7 @@ static int qcom_pcie_probe(struct platform_device *pdev)
struct pcie_port *pp;
struct dw_pcie *pci;
struct qcom_pcie *pcie;
+ struct pwrseq *pwrseq;
int ret;

pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
@@ -1520,6 +1522,17 @@ static int qcom_pcie_probe(struct platform_device *pdev)

pp->ops = &qcom_pcie_dw_ops;

+ pwrseq = devm_pwrseq_get_optional(dev, "bus");
+ if (IS_ERR(pwrseq)) {
+ ret = PTR_ERR(pwrseq);
+ goto err_pm_runtime_put;
+ }
+ if (pwrseq) {
+ ret = pwrseq_full_power_on(pwrseq);
+ if (ret)
+ goto err_pm_runtime_put;
+ }
+
ret = phy_init(pcie->phy);
if (ret) {
pm_runtime_disable(&pdev->dev);
--
2.33.0

2021-08-29 13:14:36

by Dmitry Baryshkov

[permalink] [raw]
Subject: [RFC v2 13/13] WIP: arm64: dts: qcom: qrb5165-rb5: add bus-pwrseq property to pcie0

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

2021-08-29 13:14:45

by Dmitry Baryshkov

[permalink] [raw]
Subject: [RFC v2 10/13] arm64: dts: qcom: qrb5165-rb5: add bluetooth support

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

2021-08-29 13:15:14

by Dmitry Baryshkov

[permalink] [raw]
Subject: [RFC v2 05/13] pwrseq: add fallback support

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 | 75 +++++++++++++++++++++++++++++++++
include/linux/pwrseq/fallback.h | 36 ++++++++++++++++
4 files changed, 115 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 2e4e9d123e60..a43977a0eed8 100644
--- a/drivers/power/pwrseq/core.c
+++ b/drivers/power/pwrseq/core.c
@@ -15,6 +15,7 @@
#include <linux/pm_runtime.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))
@@ -108,6 +109,8 @@ struct pwrseq * __pwrseq_get(struct device *dev, const char *id, bool optional)
struct device_link *link;

pwrseq = _of_pwrseq_get(dev, id);
+ if (pwrseq == NULL)
+ pwrseq = pwrseq_fallback_get(dev, id);
if (pwrseq == NULL)
return optional ? NULL : ERR_PTR(-ENODEV);
else if (IS_ERR(pwrseq))
diff --git a/drivers/power/pwrseq/fallback.c b/drivers/power/pwrseq/fallback.c
new file mode 100644
index 000000000000..6ecf24dd8f29
--- /dev/null
+++ b/drivers/power/pwrseq/fallback.c
@@ -0,0 +1,75 @@
+// 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);
+
+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);
+
+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..616049df179f
--- /dev/null
+++ b/include/linux/pwrseq/fallback.h
@@ -0,0 +1,36 @@
+/* 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 {
+ 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);
+#define pwrseq_fallback_register(fallback) __pwrseq_fallback_register(fallback, THIS_MODULE)
+
+void pwrseq_fallback_unregister(struct pwrseq_fallback *fallback);
+
+/* internal interface */
+struct pwrseq *pwrseq_fallback_get(struct device *dev, const char *id);
+
+#endif /* __LINUX_PWRSEQ_DRIVER_H__ */
--
2.33.0

2021-08-29 13:15:16

by Dmitry Baryshkov

[permalink] [raw]
Subject: [RFC v2 03/13] mmc: core: switch to new pwrseq subsystem

Drop old MMC pwrseq code and use new pwrseq subsystem instead.
Individual drivers are already ported to new subsystem.

Signed-off-by: Dmitry Baryshkov <[email protected]>
---
drivers/mmc/core/Makefile | 1 -
drivers/mmc/core/core.c | 9 ++-
drivers/mmc/core/host.c | 8 ++-
drivers/mmc/core/mmc.c | 3 +-
drivers/mmc/core/pwrseq.c | 117 --------------------------------------
drivers/mmc/core/pwrseq.h | 58 -------------------
include/linux/mmc/host.h | 4 +-
7 files changed, 12 insertions(+), 188 deletions(-)
delete mode 100644 drivers/mmc/core/pwrseq.c
delete mode 100644 drivers/mmc/core/pwrseq.h

diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile
index 322eb69bd00e..a504d873cf8e 100644
--- a/drivers/mmc/core/Makefile
+++ b/drivers/mmc/core/Makefile
@@ -9,7 +9,6 @@ mmc_core-y := core.o bus.o host.o \
sdio.o sdio_ops.o sdio_bus.o \
sdio_cis.o sdio_io.o sdio_irq.o \
slot-gpio.o regulator.o
-mmc_core-$(CONFIG_OF) += pwrseq.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/core.c b/drivers/mmc/core/core.c
index 95fedcf56e4a..c468af900a45 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -41,7 +41,6 @@
#include "bus.h"
#include "host.h"
#include "sdio_bus.h"
-#include "pwrseq.h"

#include "mmc_ops.h"
#include "sd_ops.h"
@@ -1321,7 +1320,7 @@ void mmc_power_up(struct mmc_host *host, u32 ocr)
if (host->ios.power_mode == MMC_POWER_ON)
return;

- mmc_pwrseq_pre_power_on(host);
+ pwrseq_pre_power_on(host->pwrseq);

host->ios.vdd = fls(ocr) - 1;
host->ios.power_mode = MMC_POWER_UP;
@@ -1336,7 +1335,7 @@ void mmc_power_up(struct mmc_host *host, u32 ocr)
*/
mmc_delay(host->ios.power_delay_ms);

- mmc_pwrseq_post_power_on(host);
+ pwrseq_power_on(host->pwrseq);

host->ios.clock = host->f_init;

@@ -1355,7 +1354,7 @@ void mmc_power_off(struct mmc_host *host)
if (host->ios.power_mode == MMC_POWER_OFF)
return;

- mmc_pwrseq_power_off(host);
+ pwrseq_power_off(host->pwrseq);

host->ios.clock = 0;
host->ios.vdd = 0;
@@ -1985,7 +1984,7 @@ EXPORT_SYMBOL(mmc_set_blocklen);

static void mmc_hw_reset_for_init(struct mmc_host *host)
{
- mmc_pwrseq_reset(host);
+ pwrseq_reset(host->pwrseq);

if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->hw_reset)
return;
diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
index 0475d96047c4..214b9cfda723 100644
--- a/drivers/mmc/core/host.c
+++ b/drivers/mmc/core/host.c
@@ -28,7 +28,6 @@
#include "crypto.h"
#include "host.h"
#include "slot-gpio.h"
-#include "pwrseq.h"
#include "sdio_ops.h"

#define cls_dev_to_mmc_host(d) container_of(d, struct mmc_host, class_dev)
@@ -413,7 +412,11 @@ int mmc_of_parse(struct mmc_host *host)
device_property_read_u32(dev, "post-power-on-delay-ms",
&host->ios.power_delay_ms);

- return mmc_pwrseq_alloc(host);
+ host->pwrseq = devm_pwrseq_get_optional(dev, "mmc");
+ if (IS_ERR(host->pwrseq))
+ return PTR_ERR(host->pwrseq);
+
+ return 0;
}

EXPORT_SYMBOL(mmc_of_parse);
@@ -632,7 +635,6 @@ EXPORT_SYMBOL(mmc_remove_host);
*/
void mmc_free_host(struct mmc_host *host)
{
- mmc_pwrseq_free(host);
put_device(&host->class_dev);
}

diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 838726b68ff3..59d0d26bb5c0 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -24,7 +24,6 @@
#include "mmc_ops.h"
#include "quirks.h"
#include "sd_ops.h"
-#include "pwrseq.h"

#define DEFAULT_CMD6_TIMEOUT_MS 500
#define MIN_CACHE_EN_TIMEOUT_MS 1600
@@ -2220,7 +2219,7 @@ static int _mmc_hw_reset(struct mmc_host *host)
} else {
/* Do a brute force power cycle */
mmc_power_cycle(host, card->ocr);
- mmc_pwrseq_reset(host);
+ pwrseq_reset(host->pwrseq);
}
return mmc_init_card(host, card->ocr, card);
}
diff --git a/drivers/mmc/core/pwrseq.c b/drivers/mmc/core/pwrseq.c
deleted file mode 100644
index ef675f364bf0..000000000000
--- a/drivers/mmc/core/pwrseq.c
+++ /dev/null
@@ -1,117 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Copyright (C) 2014 Linaro Ltd
- *
- * Author: Ulf Hansson <[email protected]>
- *
- * MMC power sequence management
- */
-#include <linux/kernel.h>
-#include <linux/err.h>
-#include <linux/module.h>
-#include <linux/of.h>
-
-#include <linux/mmc/host.h>
-
-#include "pwrseq.h"
-
-static DEFINE_MUTEX(pwrseq_list_mutex);
-static LIST_HEAD(pwrseq_list);
-
-int mmc_pwrseq_alloc(struct mmc_host *host)
-{
- struct device_node *np;
- struct mmc_pwrseq *p;
-
- np = of_parse_phandle(host->parent->of_node, "mmc-pwrseq", 0);
- if (!np)
- return 0;
-
- mutex_lock(&pwrseq_list_mutex);
- list_for_each_entry(p, &pwrseq_list, pwrseq_node) {
- if (p->dev->of_node == np) {
- if (!try_module_get(p->owner))
- dev_err(host->parent,
- "increasing module refcount failed\n");
- else
- host->pwrseq = p;
-
- break;
- }
- }
-
- of_node_put(np);
- mutex_unlock(&pwrseq_list_mutex);
-
- if (!host->pwrseq)
- return -EPROBE_DEFER;
-
- dev_info(host->parent, "allocated mmc-pwrseq\n");
-
- return 0;
-}
-
-void mmc_pwrseq_pre_power_on(struct mmc_host *host)
-{
- struct mmc_pwrseq *pwrseq = host->pwrseq;
-
- if (pwrseq && pwrseq->ops->pre_power_on)
- pwrseq->ops->pre_power_on(host);
-}
-
-void mmc_pwrseq_post_power_on(struct mmc_host *host)
-{
- struct mmc_pwrseq *pwrseq = host->pwrseq;
-
- if (pwrseq && pwrseq->ops->post_power_on)
- pwrseq->ops->post_power_on(host);
-}
-
-void mmc_pwrseq_power_off(struct mmc_host *host)
-{
- struct mmc_pwrseq *pwrseq = host->pwrseq;
-
- if (pwrseq && pwrseq->ops->power_off)
- pwrseq->ops->power_off(host);
-}
-
-void mmc_pwrseq_reset(struct mmc_host *host)
-{
- struct mmc_pwrseq *pwrseq = host->pwrseq;
-
- if (pwrseq && pwrseq->ops->reset)
- pwrseq->ops->reset(host);
-}
-
-void mmc_pwrseq_free(struct mmc_host *host)
-{
- struct mmc_pwrseq *pwrseq = host->pwrseq;
-
- if (pwrseq) {
- module_put(pwrseq->owner);
- host->pwrseq = NULL;
- }
-}
-
-int mmc_pwrseq_register(struct mmc_pwrseq *pwrseq)
-{
- if (!pwrseq || !pwrseq->ops || !pwrseq->dev)
- return -EINVAL;
-
- mutex_lock(&pwrseq_list_mutex);
- list_add(&pwrseq->pwrseq_node, &pwrseq_list);
- mutex_unlock(&pwrseq_list_mutex);
-
- return 0;
-}
-EXPORT_SYMBOL_GPL(mmc_pwrseq_register);
-
-void mmc_pwrseq_unregister(struct mmc_pwrseq *pwrseq)
-{
- if (pwrseq) {
- mutex_lock(&pwrseq_list_mutex);
- list_del(&pwrseq->pwrseq_node);
- mutex_unlock(&pwrseq_list_mutex);
- }
-}
-EXPORT_SYMBOL_GPL(mmc_pwrseq_unregister);
diff --git a/drivers/mmc/core/pwrseq.h b/drivers/mmc/core/pwrseq.h
deleted file mode 100644
index f3bb103db9ad..000000000000
--- a/drivers/mmc/core/pwrseq.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * Copyright (C) 2014 Linaro Ltd
- *
- * Author: Ulf Hansson <[email protected]>
- */
-#ifndef _MMC_CORE_PWRSEQ_H
-#define _MMC_CORE_PWRSEQ_H
-
-#include <linux/types.h>
-
-struct mmc_host;
-struct device;
-struct module;
-
-struct mmc_pwrseq_ops {
- void (*pre_power_on)(struct mmc_host *host);
- void (*post_power_on)(struct mmc_host *host);
- void (*power_off)(struct mmc_host *host);
- void (*reset)(struct mmc_host *host);
-};
-
-struct mmc_pwrseq {
- const struct mmc_pwrseq_ops *ops;
- struct device *dev;
- struct list_head pwrseq_node;
- struct module *owner;
-};
-
-#ifdef CONFIG_OF
-
-int mmc_pwrseq_register(struct mmc_pwrseq *pwrseq);
-void mmc_pwrseq_unregister(struct mmc_pwrseq *pwrseq);
-
-int mmc_pwrseq_alloc(struct mmc_host *host);
-void mmc_pwrseq_pre_power_on(struct mmc_host *host);
-void mmc_pwrseq_post_power_on(struct mmc_host *host);
-void mmc_pwrseq_power_off(struct mmc_host *host);
-void mmc_pwrseq_reset(struct mmc_host *host);
-void mmc_pwrseq_free(struct mmc_host *host);
-
-#else
-
-static inline int mmc_pwrseq_register(struct mmc_pwrseq *pwrseq)
-{
- return -ENOSYS;
-}
-static inline void mmc_pwrseq_unregister(struct mmc_pwrseq *pwrseq) {}
-static inline int mmc_pwrseq_alloc(struct mmc_host *host) { return 0; }
-static inline void mmc_pwrseq_pre_power_on(struct mmc_host *host) {}
-static inline void mmc_pwrseq_post_power_on(struct mmc_host *host) {}
-static inline void mmc_pwrseq_power_off(struct mmc_host *host) {}
-static inline void mmc_pwrseq_reset(struct mmc_host *host) {}
-static inline void mmc_pwrseq_free(struct mmc_host *host) {}
-
-#endif
-
-#endif
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 0abd47e9ef9b..1673e37f6028 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -16,6 +16,7 @@
#include <linux/mmc/pm.h>
#include <linux/dma-direction.h>
#include <linux/keyslot-manager.h>
+#include <linux/pwrseq/consumer.h>

struct mmc_ios {
unsigned int clock; /* clock rate */
@@ -278,7 +279,6 @@ struct mmc_context_info {
};

struct regulator;
-struct mmc_pwrseq;

struct mmc_supply {
struct regulator *vmmc; /* Card power supply */
@@ -294,7 +294,7 @@ struct mmc_host {
struct device class_dev;
int index;
const struct mmc_host_ops *ops;
- struct mmc_pwrseq *pwrseq;
+ struct pwrseq *pwrseq;
unsigned int f_min;
unsigned int f_max;
unsigned int f_init;
--
2.33.0

2021-08-29 13:15:19

by Dmitry Baryshkov

[permalink] [raw]
Subject: [RFC v2 08/13] ath10k: add support for pwrseq sequencing

Power sequencing for Qualcomm WiFi+BT chipsets are being reworked to use
pwrseq rather than individually handling all the regulators. Add support
for pwrseq to ath10k SNOC driver.

Signed-off-by: Dmitry Baryshkov <[email protected]>
---
drivers/net/wireless/ath/ath10k/snoc.c | 45 +++++++++++++-------------
drivers/net/wireless/ath/ath10k/snoc.h | 4 +--
2 files changed, 25 insertions(+), 24 deletions(-)

diff --git a/drivers/net/wireless/ath/ath10k/snoc.c b/drivers/net/wireless/ath/ath10k/snoc.c
index ea00fbb15601..8578c56982df 100644
--- a/drivers/net/wireless/ath/ath10k/snoc.c
+++ b/drivers/net/wireless/ath/ath10k/snoc.c
@@ -14,6 +14,7 @@
#include <linux/regulator/consumer.h>
#include <linux/of_address.h>
#include <linux/iommu.h>
+#include <linux/pwrseq/consumer.h>

#include "ce.h"
#include "coredump.h"
@@ -41,14 +42,6 @@ static char *const ce_name[] = {
"WLAN_CE_11",
};

-static const char * const ath10k_regulators[] = {
- "vdd-0.8-cx-mx",
- "vdd-1.8-xo",
- "vdd-1.3-rfa",
- "vdd-3.3-ch0",
- "vdd-3.3-ch1",
-};
-
static const char * const ath10k_clocks[] = {
"cxo_ref_clk_pin", "qdss",
};
@@ -1010,10 +1003,14 @@ static int ath10k_hw_power_on(struct ath10k *ar)

ath10k_dbg(ar, ATH10K_DBG_SNOC, "soc power on\n");

- ret = regulator_bulk_enable(ar_snoc->num_vregs, ar_snoc->vregs);
+ ret = pwrseq_full_power_on(ar_snoc->pwrseq);
if (ret)
return ret;

+ ret = regulator_enable(ar_snoc->vreg_cx_mx);
+ if (ret)
+ goto vreg_pwrseq_off;
+
ret = clk_bulk_prepare_enable(ar_snoc->num_clks, ar_snoc->clks);
if (ret)
goto vreg_off;
@@ -1021,11 +1018,14 @@ static int ath10k_hw_power_on(struct ath10k *ar)
return ret;

vreg_off:
- regulator_bulk_disable(ar_snoc->num_vregs, ar_snoc->vregs);
+ regulator_disable(ar_snoc->vreg_cx_mx);
+vreg_pwrseq_off:
+ pwrseq_power_off(ar_snoc->pwrseq);
+
return ret;
}

-static int ath10k_hw_power_off(struct ath10k *ar)
+static void ath10k_hw_power_off(struct ath10k *ar)
{
struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);

@@ -1033,7 +1033,9 @@ static int ath10k_hw_power_off(struct ath10k *ar)

clk_bulk_disable_unprepare(ar_snoc->num_clks, ar_snoc->clks);

- return regulator_bulk_disable(ar_snoc->num_vregs, ar_snoc->vregs);
+ regulator_disable(ar_snoc->vreg_cx_mx);
+
+ pwrseq_power_off(ar_snoc->pwrseq);
}

static void ath10k_snoc_wlan_disable(struct ath10k *ar)
@@ -1691,20 +1693,19 @@ static int ath10k_snoc_probe(struct platform_device *pdev)
goto err_release_resource;
}

- ar_snoc->num_vregs = ARRAY_SIZE(ath10k_regulators);
- ar_snoc->vregs = devm_kcalloc(&pdev->dev, ar_snoc->num_vregs,
- sizeof(*ar_snoc->vregs), GFP_KERNEL);
- if (!ar_snoc->vregs) {
- ret = -ENOMEM;
+ ar_snoc->pwrseq = devm_pwrseq_get(&pdev->dev, "wifi");
+ if (IS_ERR(ar_snoc->pwrseq)) {
+ ret = PTR_ERR(ar_snoc->pwrseq);
+ if (ret != -EPROBE_DEFER)
+ ath10k_warn(ar, "failed to acquire pwrseq: %d\n", ret);
goto err_free_irq;
}
- for (i = 0; i < ar_snoc->num_vregs; i++)
- ar_snoc->vregs[i].supply = ath10k_regulators[i];

- ret = devm_regulator_bulk_get(&pdev->dev, ar_snoc->num_vregs,
- ar_snoc->vregs);
- if (ret < 0)
+ ar_snoc->vreg_cx_mx = devm_regulator_get(&pdev->dev, "vdd-0.8-cx-mx");
+ if (IS_ERR(ar_snoc->vreg_cx_mx)) {
+ ret = PTR_ERR(ar_snoc->vreg_cx_mx);
goto err_free_irq;
+ }

ar_snoc->num_clks = ARRAY_SIZE(ath10k_clocks);
ar_snoc->clks = devm_kcalloc(&pdev->dev, ar_snoc->num_clks,
diff --git a/drivers/net/wireless/ath/ath10k/snoc.h b/drivers/net/wireless/ath/ath10k/snoc.h
index 5095d1893681..5188d6f6f850 100644
--- a/drivers/net/wireless/ath/ath10k/snoc.h
+++ b/drivers/net/wireless/ath/ath10k/snoc.h
@@ -70,10 +70,10 @@ struct ath10k_snoc {
struct ath10k_snoc_ce_irq ce_irqs[CE_COUNT_MAX];
struct ath10k_ce ce;
struct timer_list rx_post_retry;
- struct regulator_bulk_data *vregs;
- size_t num_vregs;
+ struct regulator *vreg_cx_mx;
struct clk_bulk_data *clks;
size_t num_clks;
+ struct pwrseq *pwrseq;
struct ath10k_qmi *qmi;
unsigned long flags;
bool xo_cal_supported;
--
2.33.0

2021-09-14 01:28:34

by Steev Klimaszewski

[permalink] [raw]
Subject: Re: [RFC v2 00/13] create power sequencing subsystem


On 8/29/21 8:12 AM, Dmitry Baryshkov wrote:
> This is the second RFC on the 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.
>
> To ease migration to pwrseq and to provide compatibility with older
> device trees, while keeping drivers simple, this iteration of RFC
> introduces pwrseq fallback support: pwrseq driver can register fallback
> providers. If another device driver requests pwrseq instance and none
> was declared, the pwrseq fallback code would go through the list of
> fallback providers and if the match is found, driver would return a
> crafted pwrseq instance. For now this mechanism is limited to the OF
> device matching, but it can be extended further to use any combination
> of device IDs.
>
> 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.
>
> This patchset being an RFC tries to demonstrate the approach, design and
> usage of the pwrseq subsystem. Following issues are present in the RFC
> at this moment but will be fixed later if the overall approach would be
> viewed as acceptable:
>
> - No documentation
> While the code tries to be self-documenting proper documentation
> would be required.
>
> - Minimal device tree bindings changes
> There are no proper updates for the DT bindings (thus neither Rob
> Herring nor devicetree are included in the To/Cc lists). The dt
> schema changes would be a part of v1.
>
> - 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 bus scan depending on the proper
> device structure in the device tree.
>
> 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.
>
I tested this here, on the Lenovo Yoga C630, after creating a patch to
do basically the same thing as the db845c does.  One thing I noticed, if
PWRSEQ=y and the rest are =m, there is a build error.  I suppose once
the full set is posted and not RFC, I can send the patch for that. 

One question I have, if you don't mind, in patch 11, you add a second
channel to qca power sequencer.  I've added that here, but in the c630's
dts, "vreg_l23a_3p3: ldo23" is empty, so I added the same numbers in for
the regulator, and I'm wondering how to test that it's actually working
correctly?

-- steev

2021-10-06 03:50:31

by Dmitry Baryshkov

[permalink] [raw]
Subject: Re: [RFC v2 00/13] create power sequencing subsystem

Hi Steev,

On Tue, 14 Sept 2021 at 02:39, Steev Klimaszewski <[email protected]> wrote:
>
>
> On 8/29/21 8:12 AM, Dmitry Baryshkov wrote:
> > This is the second RFC on the 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.
> >
> > To ease migration to pwrseq and to provide compatibility with older
> > device trees, while keeping drivers simple, this iteration of RFC
> > introduces pwrseq fallback support: pwrseq driver can register fallback
> > providers. If another device driver requests pwrseq instance and none
> > was declared, the pwrseq fallback code would go through the list of
> > fallback providers and if the match is found, driver would return a
> > crafted pwrseq instance. For now this mechanism is limited to the OF
> > device matching, but it can be extended further to use any combination
> > of device IDs.
> >
> > 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.
> >
> > This patchset being an RFC tries to demonstrate the approach, design and
> > usage of the pwrseq subsystem. Following issues are present in the RFC
> > at this moment but will be fixed later if the overall approach would be
> > viewed as acceptable:
> >
> > - No documentation
> > While the code tries to be self-documenting proper documentation
> > would be required.
> >
> > - Minimal device tree bindings changes
> > There are no proper updates for the DT bindings (thus neither Rob
> > Herring nor devicetree are included in the To/Cc lists). The dt
> > schema changes would be a part of v1.
> >
> > - 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 bus scan depending on the proper
> > device structure in the device tree.
> >
> > 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.
> >
> I tested this here, on the Lenovo Yoga C630, after creating a patch to
> do basically the same thing as the db845c does. One thing I noticed, if
> PWRSEQ=y and the rest are =m, there is a build error. I suppose once
> the full set is posted and not RFC, I can send the patch for that.

Please excuse me for the delay in the response. I was carried away by
other duties. Yes, could you please provide a fixup patch.
I'm going to send v1 now, containing mostly cosmetical and
documentation changes. I'll include your patch in v2.

> One question I have, if you don't mind, in patch 11, you add a second
> channel to qca power sequencer. I've added that here, but in the c630's
> dts, "vreg_l23a_3p3: ldo23" is empty, so I added the same numbers in for
> the regulator, and I'm wondering how to test that it's actually working
> correctly?

That's a good question. I have not looked in the details in the ath10k
documentation. I'll try finding it.
Maybe Kalle Valo can answer your question. Could you please duplicate
your question on the ath10k mailing list?

--
With best wishes
Dmitry