summaryrefslogtreecommitdiff
path: root/drivers/power/sequencing
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power/sequencing')
-rw-r--r--drivers/power/sequencing/Kconfig21
-rw-r--r--drivers/power/sequencing/Makefile2
-rw-r--r--drivers/power/sequencing/core.c21
-rw-r--r--drivers/power/sequencing/pwrseq-pcie-m2.c486
-rw-r--r--drivers/power/sequencing/pwrseq-qcom-wcn.c143
-rw-r--r--drivers/power/sequencing/pwrseq-thead-gpu.c249
6 files changed, 900 insertions, 22 deletions
diff --git a/drivers/power/sequencing/Kconfig b/drivers/power/sequencing/Kconfig
index ddcc42a98492..1c5f5820f5b7 100644
--- a/drivers/power/sequencing/Kconfig
+++ b/drivers/power/sequencing/Kconfig
@@ -16,7 +16,7 @@ if POWER_SEQUENCING
config POWER_SEQUENCING_QCOM_WCN
tristate "Qualcomm WCN family PMU driver"
default m if ARCH_QCOM
- depends on OF
+ depends on OF || COMPILE_TEST
help
Say Y here to enable the power sequencing driver for Qualcomm
WCN Bluetooth/WLAN chipsets.
@@ -27,4 +27,23 @@ config POWER_SEQUENCING_QCOM_WCN
this driver is needed for correct power control or else we'd risk not
respecting the required delays between enabling Bluetooth and WLAN.
+config POWER_SEQUENCING_TH1520_GPU
+ tristate "T-HEAD TH1520 GPU power sequencing driver"
+ depends on (ARCH_THEAD && AUXILIARY_BUS) || COMPILE_TEST
+ help
+ Say Y here to enable the power sequencing driver for the TH1520 SoC
+ GPU. This driver handles the complex clock and reset sequence
+ required to power on the Imagination BXM GPU on this platform.
+
+config POWER_SEQUENCING_PCIE_M2
+ tristate "PCIe M.2 connector power sequencing driver"
+ depends on OF
+ depends on PCI
+ depends on SERIAL_DEV_BUS
+ select OF_DYNAMIC
+ help
+ Say Y here to enable the power sequencing driver for PCIe M.2
+ connectors. This driver handles the power sequencing for the M.2
+ connectors exposing multiple interfaces like PCIe, SATA, UART, etc...
+
endif
diff --git a/drivers/power/sequencing/Makefile b/drivers/power/sequencing/Makefile
index 2eec2df7912d..0911d4618298 100644
--- a/drivers/power/sequencing/Makefile
+++ b/drivers/power/sequencing/Makefile
@@ -4,3 +4,5 @@ obj-$(CONFIG_POWER_SEQUENCING) += pwrseq-core.o
pwrseq-core-y := core.o
obj-$(CONFIG_POWER_SEQUENCING_QCOM_WCN) += pwrseq-qcom-wcn.o
+obj-$(CONFIG_POWER_SEQUENCING_TH1520_GPU) += pwrseq-thead-gpu.o
+obj-$(CONFIG_POWER_SEQUENCING_PCIE_M2) += pwrseq-pcie-m2.o
diff --git a/drivers/power/sequencing/core.c b/drivers/power/sequencing/core.c
index 0ffc259c6bb6..4dff71be11b6 100644
--- a/drivers/power/sequencing/core.c
+++ b/drivers/power/sequencing/core.c
@@ -90,7 +90,7 @@ static struct pwrseq_unit *pwrseq_unit_new(const struct pwrseq_unit_data *data)
{
struct pwrseq_unit *unit;
- unit = kzalloc(sizeof(*unit), GFP_KERNEL);
+ unit = kzalloc_obj(*unit);
if (!unit)
return NULL;
@@ -138,7 +138,7 @@ static struct pwrseq_unit_dep *pwrseq_unit_dep_new(struct pwrseq_unit *unit)
{
struct pwrseq_unit_dep *dep;
- dep = kzalloc(sizeof(*dep), GFP_KERNEL);
+ dep = kzalloc_obj(*dep);
if (!dep)
return NULL;
@@ -195,7 +195,7 @@ pwrseq_target_new(const struct pwrseq_target_data *data)
{
struct pwrseq_target *target;
- target = kzalloc(sizeof(*target), GFP_KERNEL);
+ target = kzalloc_obj(*target);
if (!target)
return NULL;
@@ -628,7 +628,7 @@ static int pwrseq_match_device(struct device *pwrseq_dev, void *data)
return 0;
ret = pwrseq->match(pwrseq, match_data->dev);
- if (ret <= 0)
+ if (ret == PWRSEQ_NO_MATCH || ret < 0)
return ret;
/* We got the matching device, let's find the right target. */
@@ -651,7 +651,7 @@ static int pwrseq_match_device(struct device *pwrseq_dev, void *data)
match_data->desc->pwrseq = pwrseq_device_get(pwrseq);
- return 1;
+ return PWRSEQ_MATCH_OK;
}
/**
@@ -669,8 +669,7 @@ struct pwrseq_desc *pwrseq_get(struct device *dev, const char *target)
struct pwrseq_match_data match_data;
int ret;
- struct pwrseq_desc *desc __free(kfree) = kzalloc(sizeof(*desc),
- GFP_KERNEL);
+ struct pwrseq_desc *desc __free(kfree) = kzalloc_obj(*desc);
if (!desc)
return ERR_PTR(-ENOMEM);
@@ -684,7 +683,7 @@ struct pwrseq_desc *pwrseq_get(struct device *dev, const char *target)
pwrseq_match_device);
if (ret < 0)
return ERR_PTR(ret);
- if (ret == 0)
+ if (ret == PWRSEQ_NO_MATCH)
/* No device matched. */
return ERR_PTR(-EPROBE_DEFER);
@@ -914,8 +913,10 @@ int pwrseq_power_on(struct pwrseq_desc *desc)
if (target->post_enable) {
ret = target->post_enable(pwrseq);
if (ret) {
- pwrseq_unit_disable(pwrseq, unit);
- desc->powered_on = false;
+ scoped_guard(mutex, &pwrseq->state_lock) {
+ pwrseq_unit_disable(pwrseq, unit);
+ desc->powered_on = false;
+ }
}
}
diff --git a/drivers/power/sequencing/pwrseq-pcie-m2.c b/drivers/power/sequencing/pwrseq-pcie-m2.c
new file mode 100644
index 000000000000..ef69ae268059
--- /dev/null
+++ b/drivers/power/sequencing/pwrseq-pcie-m2.c
@@ -0,0 +1,486 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * Author: Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>
+ */
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/pwrseq/provider.h>
+#include <linux/regulator/consumer.h>
+#include <linux/serdev.h>
+#include <linux/slab.h>
+
+struct pwrseq_pcie_m2_pdata {
+ const struct pwrseq_target_data **targets;
+};
+
+struct pwrseq_pcie_m2_ctx {
+ struct pwrseq_device *pwrseq;
+ struct device_node *of_node;
+ const struct pwrseq_pcie_m2_pdata *pdata;
+ struct regulator_bulk_data *regs;
+ size_t num_vregs;
+ struct notifier_block nb;
+ struct gpio_desc *w_disable1_gpio;
+ struct gpio_desc *w_disable2_gpio;
+ struct serdev_device *serdev;
+ struct of_changeset *ocs;
+ struct device *dev;
+};
+
+static int pwrseq_pcie_m2_vregs_enable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return regulator_bulk_enable(ctx->num_vregs, ctx->regs);
+}
+
+static int pwrseq_pcie_m2_vregs_disable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return regulator_bulk_disable(ctx->num_vregs, ctx->regs);
+}
+
+static const struct pwrseq_unit_data pwrseq_pcie_m2_vregs_unit_data = {
+ .name = "regulators-enable",
+ .enable = pwrseq_pcie_m2_vregs_enable,
+ .disable = pwrseq_pcie_m2_vregs_disable,
+};
+
+static const struct pwrseq_unit_data *pwrseq_pcie_m2_unit_deps[] = {
+ &pwrseq_pcie_m2_vregs_unit_data,
+ NULL
+};
+
+static int pwrseq_pci_m2_e_uart_enable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return gpiod_set_value_cansleep(ctx->w_disable2_gpio, 0);
+}
+
+static int pwrseq_pci_m2_e_uart_disable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return gpiod_set_value_cansleep(ctx->w_disable2_gpio, 1);
+}
+
+static const struct pwrseq_unit_data pwrseq_pcie_m2_e_uart_unit_data = {
+ .name = "uart-enable",
+ .deps = pwrseq_pcie_m2_unit_deps,
+ .enable = pwrseq_pci_m2_e_uart_enable,
+ .disable = pwrseq_pci_m2_e_uart_disable,
+};
+
+static int pwrseq_pci_m2_e_pcie_enable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return gpiod_set_value_cansleep(ctx->w_disable1_gpio, 0);
+}
+
+static int pwrseq_pci_m2_e_pcie_disable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return gpiod_set_value_cansleep(ctx->w_disable1_gpio, 1);
+}
+
+static const struct pwrseq_unit_data pwrseq_pcie_m2_e_pcie_unit_data = {
+ .name = "pcie-enable",
+ .deps = pwrseq_pcie_m2_unit_deps,
+ .enable = pwrseq_pci_m2_e_pcie_enable,
+ .disable = pwrseq_pci_m2_e_pcie_disable,
+};
+
+static const struct pwrseq_unit_data pwrseq_pcie_m2_m_pcie_unit_data = {
+ .name = "pcie-enable",
+ .deps = pwrseq_pcie_m2_unit_deps,
+};
+
+static int pwrseq_pcie_m2_e_pwup_delay(struct pwrseq_device *pwrseq)
+{
+ /*
+ * FIXME: This delay is only required for some Qcom WLAN/BT cards like
+ * WCN7850 and not for all devices. But currently, there is no way to
+ * identify the device model before enumeration.
+ */
+ msleep(50);
+
+ return 0;
+}
+
+static const struct pwrseq_target_data pwrseq_pcie_m2_e_uart_target_data = {
+ .name = "uart",
+ .unit = &pwrseq_pcie_m2_e_uart_unit_data,
+ .post_enable = pwrseq_pcie_m2_e_pwup_delay,
+};
+
+static const struct pwrseq_target_data pwrseq_pcie_m2_e_pcie_target_data = {
+ .name = "pcie",
+ .unit = &pwrseq_pcie_m2_e_pcie_unit_data,
+ .post_enable = pwrseq_pcie_m2_e_pwup_delay,
+};
+
+static const struct pwrseq_target_data pwrseq_pcie_m2_m_pcie_target_data = {
+ .name = "pcie",
+ .unit = &pwrseq_pcie_m2_m_pcie_unit_data,
+};
+
+static const struct pwrseq_target_data *pwrseq_pcie_m2_e_targets[] = {
+ &pwrseq_pcie_m2_e_pcie_target_data,
+ &pwrseq_pcie_m2_e_uart_target_data,
+ NULL
+};
+
+static const struct pwrseq_target_data *pwrseq_pcie_m2_m_targets[] = {
+ &pwrseq_pcie_m2_m_pcie_target_data,
+ NULL
+};
+
+static const struct pwrseq_pcie_m2_pdata pwrseq_pcie_m2_e_of_data = {
+ .targets = pwrseq_pcie_m2_e_targets,
+};
+
+static const struct pwrseq_pcie_m2_pdata pwrseq_pcie_m2_m_of_data = {
+ .targets = pwrseq_pcie_m2_m_targets,
+};
+
+static int pwrseq_pcie_m2_match(struct pwrseq_device *pwrseq,
+ struct device *dev)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+ struct device_node *endpoint __free(device_node) = NULL;
+
+ /*
+ * Traverse the 'remote-endpoint' nodes and check if the remote node's
+ * parent matches the OF node of 'dev'.
+ */
+ for_each_endpoint_of_node(ctx->of_node, endpoint) {
+ struct device_node *remote __free(device_node) =
+ of_graph_get_remote_port_parent(endpoint);
+ if (remote && (remote == dev_of_node(dev)))
+ return PWRSEQ_MATCH_OK;
+ }
+
+ return PWRSEQ_NO_MATCH;
+}
+
+static int pwrseq_m2_pcie_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx,
+ struct device_node *parent)
+{
+ struct device *dev = ctx->dev;
+ struct device_node *np;
+ int ret;
+
+ ctx->ocs = kzalloc_obj(*ctx->ocs);
+ if (!ctx->ocs)
+ return -ENOMEM;
+
+ of_changeset_init(ctx->ocs);
+
+ np = of_changeset_create_node(ctx->ocs, parent, "bluetooth");
+ if (!np) {
+ dev_err(dev, "Failed to create bluetooth node\n");
+ ret = -ENODEV;
+ goto err_destroy_changeset;
+ }
+
+ ret = of_changeset_add_prop_string(ctx->ocs, np, "compatible", "qcom,wcn7850-bt");
+ if (ret) {
+ dev_err(dev, "Failed to add bluetooth compatible: %d\n", ret);
+ goto err_destroy_changeset;
+ }
+
+ ret = of_changeset_apply(ctx->ocs);
+ if (ret) {
+ dev_err(dev, "Failed to apply changeset: %d\n", ret);
+ goto err_destroy_changeset;
+ }
+
+ ret = device_add_of_node(&ctx->serdev->dev, np);
+ if (ret) {
+ dev_err(dev, "Failed to add OF node: %d\n", ret);
+ goto err_revert_changeset;
+ }
+
+ return 0;
+
+err_revert_changeset:
+ of_changeset_revert(ctx->ocs);
+err_destroy_changeset:
+ of_changeset_destroy(ctx->ocs);
+ kfree(ctx->ocs);
+ ctx->ocs = NULL;
+
+ return ret;
+}
+
+static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx)
+{
+ struct serdev_controller *serdev_ctrl;
+ struct device *dev = ctx->dev;
+ int ret;
+
+ struct device_node *serdev_parent __free(device_node) =
+ of_graph_get_remote_node(dev_of_node(ctx->dev), 3, 0);
+ if (!serdev_parent)
+ return 0;
+
+ serdev_ctrl = of_find_serdev_controller_by_node(serdev_parent);
+ if (!serdev_ctrl)
+ return 0;
+
+ /* Bail out if the device was already attached to this controller */
+ if (serdev_ctrl->serdev) {
+ serdev_controller_put(serdev_ctrl);
+ return 0;
+ }
+
+ ctx->serdev = serdev_device_alloc(serdev_ctrl);
+ if (!ctx->serdev) {
+ ret = -ENOMEM;
+ goto err_put_ctrl;
+ }
+
+ ret = pwrseq_m2_pcie_create_bt_node(ctx, serdev_parent);
+ if (ret)
+ goto err_free_serdev;
+
+ ret = serdev_device_add(ctx->serdev);
+ if (ret) {
+ dev_err(dev, "Failed to add serdev for WCN7850: %d\n", ret);
+ goto err_free_dt_node;
+ }
+
+ serdev_controller_put(serdev_ctrl);
+
+ return 0;
+
+err_free_dt_node:
+ device_remove_of_node(&ctx->serdev->dev);
+ of_changeset_revert(ctx->ocs);
+ of_changeset_destroy(ctx->ocs);
+ kfree(ctx->ocs);
+ ctx->ocs = NULL;
+err_free_serdev:
+ serdev_device_put(ctx->serdev);
+ ctx->serdev = NULL;
+err_put_ctrl:
+ serdev_controller_put(serdev_ctrl);
+
+ return ret;
+}
+
+static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx)
+{
+ if (ctx->serdev) {
+ device_remove_of_node(&ctx->serdev->dev);
+ serdev_device_remove(ctx->serdev);
+ ctx->serdev = NULL;
+ }
+
+ if (ctx->ocs) {
+ of_changeset_revert(ctx->ocs);
+ of_changeset_destroy(ctx->ocs);
+ kfree(ctx->ocs);
+ ctx->ocs = NULL;
+ }
+}
+
+static int pwrseq_m2_pcie_notify(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = container_of(nb, struct pwrseq_pcie_m2_ctx, nb);
+ struct pci_dev *pdev = to_pci_dev(data);
+ int ret;
+
+ /*
+ * Check whether the PCI device is associated with this M.2 connector or
+ * not, by comparing the OF node of the PCI device parent and the Port 0
+ * (PCIe) remote node parent OF node.
+ */
+ struct device_node *pci_parent __free(device_node) =
+ of_graph_get_remote_node(dev_of_node(ctx->dev), 0, 0);
+ if (!pci_parent || (pci_parent != pdev->dev.parent->of_node))
+ return NOTIFY_DONE;
+
+ switch (action) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ /* Create serdev device for WCN7850 */
+ if (pdev->vendor == PCI_VENDOR_ID_QCOM && pdev->device == 0x1107) {
+ ret = pwrseq_pcie_m2_create_serdev(ctx);
+ if (ret)
+ return notifier_from_errno(ret);
+ }
+ break;
+ case BUS_NOTIFY_REMOVED_DEVICE:
+ /* Destroy serdev device for WCN7850 */
+ if (pdev->vendor == PCI_VENDOR_ID_QCOM && pdev->device == 0x1107)
+ pwrseq_pcie_m2_remove_serdev(ctx);
+
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static bool pwrseq_pcie_m2_check_remote_node(struct device *dev, u8 port, u8 endpoint,
+ const char *node)
+{
+ struct device_node *remote __free(device_node) =
+ of_graph_get_remote_node(dev_of_node(dev), port, endpoint);
+
+ if (remote && of_node_name_eq(remote, node))
+ return true;
+
+ return false;
+}
+
+/*
+ * If the connector exposes a non-discoverable bus like UART, the respective
+ * protocol device needs to be created manually with the help of the notifier
+ * of the discoverable bus like PCIe.
+ */
+static int pwrseq_pcie_m2_register_notifier(struct pwrseq_pcie_m2_ctx *ctx, struct device *dev)
+{
+ int ret;
+
+ /*
+ * Register a PCI notifier for Key E connector that has PCIe as Port
+ * 0/Endpoint 0 interface and Serial as Port 3/Endpoint 0 interface.
+ */
+ if (pwrseq_pcie_m2_check_remote_node(dev, 3, 0, "serial")) {
+ if (pwrseq_pcie_m2_check_remote_node(dev, 0, 0, "pcie")) {
+ ctx->dev = dev;
+ ctx->nb.notifier_call = pwrseq_m2_pcie_notify;
+ ret = bus_register_notifier(&pci_bus_type, &ctx->nb);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register notifier for serdev\n");
+ }
+ }
+
+ return 0;
+}
+
+static int pwrseq_pcie_m2_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pwrseq_pcie_m2_ctx *ctx;
+ struct pwrseq_config config = {};
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, ctx);
+ ctx->of_node = dev_of_node(dev);
+ ctx->pdata = device_get_match_data(dev);
+ if (!ctx->pdata)
+ return dev_err_probe(dev, -ENODEV,
+ "Failed to obtain platform data\n");
+
+ /*
+ * Currently, of_regulator_bulk_get_all() is the only regulator API that
+ * allows to get all supplies in the devicetree node without manually
+ * specifying them.
+ */
+ ret = of_regulator_bulk_get_all(dev, dev_of_node(dev), &ctx->regs);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "Failed to get all regulators\n");
+
+ ctx->num_vregs = ret;
+
+ ctx->w_disable1_gpio = devm_gpiod_get_optional(dev, "w-disable1", GPIOD_OUT_HIGH);
+ if (IS_ERR(ctx->w_disable1_gpio)) {
+ ret = dev_err_probe(dev, PTR_ERR(ctx->w_disable1_gpio),
+ "Failed to get the W_DISABLE_1# GPIO\n");
+ goto err_free_regulators;
+ }
+
+ ctx->w_disable2_gpio = devm_gpiod_get_optional(dev, "w-disable2", GPIOD_OUT_HIGH);
+ if (IS_ERR(ctx->w_disable2_gpio)) {
+ ret = dev_err_probe(dev, PTR_ERR(ctx->w_disable2_gpio),
+ "Failed to get the W_DISABLE_2# GPIO\n");
+ goto err_free_regulators;
+ }
+
+ config.parent = dev;
+ config.owner = THIS_MODULE;
+ config.drvdata = ctx;
+ config.match = pwrseq_pcie_m2_match;
+ config.targets = ctx->pdata->targets;
+
+ ctx->pwrseq = devm_pwrseq_device_register(dev, &config);
+ if (IS_ERR(ctx->pwrseq)) {
+ ret = dev_err_probe(dev, PTR_ERR(ctx->pwrseq),
+ "Failed to register the power sequencer\n");
+ goto err_free_regulators;
+ }
+
+ /*
+ * Register a notifier for creating protocol devices for
+ * non-discoverable busses like UART.
+ */
+ ret = pwrseq_pcie_m2_register_notifier(ctx, dev);
+ if (ret)
+ goto err_free_regulators;
+
+ return 0;
+
+err_free_regulators:
+ regulator_bulk_free(ctx->num_vregs, ctx->regs);
+
+ return ret;
+}
+
+static void pwrseq_pcie_m2_remove(struct platform_device *pdev)
+{
+ struct pwrseq_pcie_m2_ctx *ctx = platform_get_drvdata(pdev);
+
+ bus_unregister_notifier(&pci_bus_type, &ctx->nb);
+ pwrseq_pcie_m2_remove_serdev(ctx);
+
+ regulator_bulk_free(ctx->num_vregs, ctx->regs);
+}
+
+static const struct of_device_id pwrseq_pcie_m2_of_match[] = {
+ {
+ .compatible = "pcie-m2-m-connector",
+ .data = &pwrseq_pcie_m2_m_of_data,
+ },
+ {
+ .compatible = "pcie-m2-e-connector",
+ .data = &pwrseq_pcie_m2_e_of_data,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pwrseq_pcie_m2_of_match);
+
+static struct platform_driver pwrseq_pcie_m2_driver = {
+ .driver = {
+ .name = "pwrseq-pcie-m2",
+ .of_match_table = pwrseq_pcie_m2_of_match,
+ },
+ .probe = pwrseq_pcie_m2_probe,
+ .remove = pwrseq_pcie_m2_remove,
+};
+module_platform_driver(pwrseq_pcie_m2_driver);
+
+MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>");
+MODULE_DESCRIPTION("Power Sequencing driver for PCIe M.2 connector");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/sequencing/pwrseq-qcom-wcn.c b/drivers/power/sequencing/pwrseq-qcom-wcn.c
index e8f5030f2639..b55b4317e21b 100644
--- a/drivers/power/sequencing/pwrseq-qcom-wcn.c
+++ b/drivers/power/sequencing/pwrseq-qcom-wcn.c
@@ -12,6 +12,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
#include <linux/regulator/consumer.h>
#include <linux/pwrseq/provider.h>
#include <linux/string.h>
@@ -23,6 +24,8 @@ struct pwrseq_qcom_wcn_pdata {
unsigned int pwup_delay_ms;
unsigned int gpio_enable_delay_ms;
const struct pwrseq_target_data **targets;
+ bool has_vddio; /* separate VDD IO regulator */
+ int (*match)(struct pwrseq_device *pwrseq, struct device *dev);
};
struct pwrseq_qcom_wcn_ctx {
@@ -30,6 +33,7 @@ struct pwrseq_qcom_wcn_ctx {
struct device_node *of_node;
const struct pwrseq_qcom_wcn_pdata *pdata;
struct regulator_bulk_data *regs;
+ struct regulator *vddio;
struct gpio_desc *bt_gpio;
struct gpio_desc *wlan_gpio;
struct gpio_desc *xo_clk_gpio;
@@ -52,6 +56,26 @@ static void pwrseq_qcom_wcn_ensure_gpio_delay(struct pwrseq_qcom_wcn_ctx *ctx)
msleep(ctx->pdata->gpio_enable_delay_ms - diff_msecs);
}
+static int pwrseq_qcom_wcn_vddio_enable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return regulator_enable(ctx->vddio);
+}
+
+static int pwrseq_qcom_wcn_vddio_disable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+
+ return regulator_disable(ctx->vddio);
+}
+
+static const struct pwrseq_unit_data pwrseq_qcom_wcn_vddio_unit_data = {
+ .name = "vddio-enable",
+ .enable = pwrseq_qcom_wcn_vddio_enable,
+ .disable = pwrseq_qcom_wcn_vddio_disable,
+};
+
static int pwrseq_qcom_wcn_vregs_enable(struct pwrseq_device *pwrseq)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
@@ -94,6 +118,19 @@ static const struct pwrseq_unit_data pwrseq_qcom_wcn_clk_unit_data = {
.disable = pwrseq_qcom_wcn_clk_disable,
};
+static const struct pwrseq_unit_data *pwrseq_qcom_wcn3990_unit_deps[] = {
+ &pwrseq_qcom_wcn_vddio_unit_data,
+ &pwrseq_qcom_wcn_vregs_unit_data,
+ NULL,
+};
+
+static const struct pwrseq_unit_data pwrseq_qcom_wcn3990_unit_data = {
+ .name = "clock-enable",
+ .deps = pwrseq_qcom_wcn3990_unit_deps,
+ .enable = pwrseq_qcom_wcn_clk_enable,
+ .disable = pwrseq_qcom_wcn_clk_disable,
+};
+
static const struct pwrseq_unit_data *pwrseq_qcom_wcn_unit_deps[] = {
&pwrseq_qcom_wcn_vregs_unit_data,
&pwrseq_qcom_wcn_clk_unit_data,
@@ -155,7 +192,7 @@ static const struct pwrseq_unit_data pwrseq_qcom_wcn_bt_unit_data = {
};
static const struct pwrseq_unit_data pwrseq_qcom_wcn6855_bt_unit_data = {
- .name = "wlan-enable",
+ .name = "bluetooth-enable",
.deps = pwrseq_qcom_wcn6855_unit_deps,
.enable = pwrseq_qcom_wcn_bt_enable,
.disable = pwrseq_qcom_wcn_bt_disable,
@@ -229,6 +266,17 @@ static const struct pwrseq_target_data pwrseq_qcom_wcn_wlan_target_data = {
.post_enable = pwrseq_qcom_wcn_pwup_delay,
};
+/* There are no separate BT and WLAN enablement pins */
+static const struct pwrseq_target_data pwrseq_qcom_wcn3990_bt_target_data = {
+ .name = "bluetooth",
+ .unit = &pwrseq_qcom_wcn3990_unit_data,
+};
+
+static const struct pwrseq_target_data pwrseq_qcom_wcn3990_wlan_target_data = {
+ .name = "wlan",
+ .unit = &pwrseq_qcom_wcn3990_unit_data,
+};
+
static const struct pwrseq_target_data pwrseq_qcom_wcn6855_bt_target_data = {
.name = "bluetooth",
.unit = &pwrseq_qcom_wcn6855_bt_unit_data,
@@ -247,6 +295,12 @@ static const struct pwrseq_target_data *pwrseq_qcom_wcn_targets[] = {
NULL
};
+static const struct pwrseq_target_data *pwrseq_qcom_wcn3990_targets[] = {
+ &pwrseq_qcom_wcn3990_bt_target_data,
+ &pwrseq_qcom_wcn3990_wlan_target_data,
+ NULL
+};
+
static const struct pwrseq_target_data *pwrseq_qcom_wcn6855_targets[] = {
&pwrseq_qcom_wcn6855_bt_target_data,
&pwrseq_qcom_wcn6855_wlan_target_data,
@@ -272,6 +326,26 @@ static const struct pwrseq_qcom_wcn_pdata pwrseq_qca6390_of_data = {
.targets = pwrseq_qcom_wcn_targets,
};
+static const char *const pwrseq_wcn3990_vregs[] = {
+ /* vddio is handled separately */
+ "vddxo",
+ "vddrf",
+ "vddch0",
+ "vddch1",
+};
+
+static int pwrseq_qcom_wcn3990_match(struct pwrseq_device *pwrseq,
+ struct device *dev);
+
+static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn3990_of_data = {
+ .vregs = pwrseq_wcn3990_vregs,
+ .num_vregs = ARRAY_SIZE(pwrseq_wcn3990_vregs),
+ .pwup_delay_ms = 50,
+ .targets = pwrseq_qcom_wcn3990_targets,
+ .has_vddio = true,
+ .match = pwrseq_qcom_wcn3990_match,
+};
+
static const char *const pwrseq_wcn6750_vregs[] = {
"vddaon",
"vddasd",
@@ -328,8 +402,9 @@ static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn7850_of_data = {
.targets = pwrseq_qcom_wcn_targets,
};
-static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq,
- struct device *dev)
+static int pwrseq_qcom_wcn_match_regulator(struct pwrseq_device *pwrseq,
+ struct device *dev,
+ const char *name)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
struct device_node *dev_node = dev->of_node;
@@ -340,13 +415,13 @@ static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq,
* 'vddaon-supply' property and whether it leads us to the right
* device.
*/
- if (!of_property_present(dev_node, "vddaon-supply"))
- return 0;
+ if (!of_property_present(dev_node, name))
+ return PWRSEQ_NO_MATCH;
struct device_node *reg_node __free(device_node) =
- of_parse_phandle(dev_node, "vddaon-supply", 0);
+ of_parse_phandle(dev_node, name, 0);
if (!reg_node)
- return 0;
+ return PWRSEQ_NO_MATCH;
/*
* `reg_node` is the PMU AON regulator, its parent is the `regulators`
@@ -355,9 +430,29 @@ static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq,
*/
if (!reg_node->parent || !reg_node->parent->parent ||
reg_node->parent->parent != ctx->of_node)
- return 0;
+ return PWRSEQ_NO_MATCH;
+
+ return PWRSEQ_MATCH_OK;
+}
+
+static int pwrseq_qcom_wcn_match(struct pwrseq_device *pwrseq,
+ struct device *dev)
+{
+ return pwrseq_qcom_wcn_match_regulator(pwrseq, dev, "vddaon-supply");
+}
+
+static int pwrseq_qcom_wcn3990_match(struct pwrseq_device *pwrseq,
+ struct device *dev)
+{
+ int ret;
+
+ /* BT device */
+ ret = pwrseq_qcom_wcn_match_regulator(pwrseq, dev, "vddio-supply");
+ if (ret == PWRSEQ_MATCH_OK)
+ return ret;
- return 1;
+ /* WiFi device match */
+ return pwrseq_qcom_wcn_match_regulator(pwrseq, dev, "vdd-1.8-xo-supply");
}
static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
@@ -373,7 +468,7 @@ static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
ctx->of_node = dev->of_node;
- ctx->pdata = of_device_get_match_data(dev);
+ ctx->pdata = device_get_match_data(dev);
if (!ctx->pdata)
return dev_err_probe(dev, -ENODEV,
"Failed to obtain platform data\n");
@@ -391,6 +486,12 @@ static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
return dev_err_probe(dev, ret,
"Failed to get all regulators\n");
+ if (ctx->pdata->has_vddio) {
+ ctx->vddio = devm_regulator_get(dev, "vddio");
+ if (IS_ERR(ctx->vddio))
+ return dev_err_probe(dev, PTR_ERR(ctx->vddio), "Failed to get VDDIO\n");
+ }
+
ctx->bt_gpio = devm_gpiod_get_optional(dev, "bt-enable", GPIOD_OUT_LOW);
if (IS_ERR(ctx->bt_gpio))
return dev_err_probe(dev, PTR_ERR(ctx->bt_gpio),
@@ -432,7 +533,7 @@ static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
config.parent = dev;
config.owner = THIS_MODULE;
config.drvdata = ctx;
- config.match = pwrseq_qcom_wcn_match;
+ config.match = ctx->pdata->match ? : pwrseq_qcom_wcn_match;
config.targets = ctx->pdata->targets;
ctx->pwrseq = devm_pwrseq_device_register(dev, &config);
@@ -445,6 +546,26 @@ static int pwrseq_qcom_wcn_probe(struct platform_device *pdev)
static const struct of_device_id pwrseq_qcom_wcn_of_match[] = {
{
+ .compatible = "qcom,wcn3950-pmu",
+ .data = &pwrseq_wcn3990_of_data,
+ },
+ {
+ .compatible = "qcom,wcn3988-pmu",
+ .data = &pwrseq_wcn3990_of_data,
+ },
+ {
+ .compatible = "qcom,wcn3990-pmu",
+ .data = &pwrseq_wcn3990_of_data,
+ },
+ {
+ .compatible = "qcom,wcn3991-pmu",
+ .data = &pwrseq_wcn3990_of_data,
+ },
+ {
+ .compatible = "qcom,wcn3998-pmu",
+ .data = &pwrseq_wcn3990_of_data,
+ },
+ {
.compatible = "qcom,qca6390-pmu",
.data = &pwrseq_qca6390_of_data,
},
diff --git a/drivers/power/sequencing/pwrseq-thead-gpu.c b/drivers/power/sequencing/pwrseq-thead-gpu.c
new file mode 100644
index 000000000000..a45318b4b2c1
--- /dev/null
+++ b/drivers/power/sequencing/pwrseq-thead-gpu.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * T-HEAD TH1520 GPU Power Sequencer Driver
+ *
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
+ * Author: Michal Wilczynski <m.wilczynski@samsung.com>
+ *
+ * This driver implements the power sequence for the Imagination BXM-4-64
+ * GPU on the T-HEAD TH1520 SoC. The sequence requires coordinating resources
+ * from both the sequencer's parent device node (clkgen_reset) and the GPU's
+ * device node (clocks and core reset).
+ *
+ * The `match` function is used to acquire the GPU's resources when the
+ * GPU driver requests the "gpu-power" sequence target.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pwrseq/provider.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/power/thead,th1520-power.h>
+
+struct pwrseq_thead_gpu_ctx {
+ struct pwrseq_device *pwrseq;
+ struct reset_control *clkgen_reset;
+ struct device_node *aon_node;
+
+ /* Consumer resources */
+ struct device_node *consumer_node;
+ struct clk_bulk_data *clks;
+ int num_clks;
+ struct reset_control *gpu_reset;
+};
+
+static int pwrseq_thead_gpu_enable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+ int ret;
+
+ if (!ctx->clks || !ctx->gpu_reset)
+ return -ENODEV;
+
+ ret = clk_bulk_prepare_enable(ctx->num_clks, ctx->clks);
+ if (ret)
+ return ret;
+
+ ret = reset_control_deassert(ctx->clkgen_reset);
+ if (ret)
+ goto err_disable_clks;
+
+ /*
+ * According to the hardware manual, a delay of at least 32 clock
+ * cycles is required between de-asserting the clkgen reset and
+ * de-asserting the GPU reset. Assuming a worst-case scenario with
+ * a very high GPU clock frequency, a delay of 1 microsecond is
+ * sufficient to ensure this requirement is met across all
+ * feasible GPU clock speeds.
+ */
+ udelay(1);
+
+ ret = reset_control_deassert(ctx->gpu_reset);
+ if (ret)
+ goto err_assert_clkgen;
+
+ return 0;
+
+err_assert_clkgen:
+ reset_control_assert(ctx->clkgen_reset);
+err_disable_clks:
+ clk_bulk_disable_unprepare(ctx->num_clks, ctx->clks);
+ return ret;
+}
+
+static int pwrseq_thead_gpu_disable(struct pwrseq_device *pwrseq)
+{
+ struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+ int ret = 0, err;
+
+ if (!ctx->clks || !ctx->gpu_reset)
+ return -ENODEV;
+
+ err = reset_control_assert(ctx->gpu_reset);
+ if (err)
+ ret = err;
+
+ err = reset_control_assert(ctx->clkgen_reset);
+ if (err && !ret)
+ ret = err;
+
+ clk_bulk_disable_unprepare(ctx->num_clks, ctx->clks);
+
+ /* ret stores values of the first error code */
+ return ret;
+}
+
+static const struct pwrseq_unit_data pwrseq_thead_gpu_unit = {
+ .name = "gpu-power-sequence",
+ .enable = pwrseq_thead_gpu_enable,
+ .disable = pwrseq_thead_gpu_disable,
+};
+
+static const struct pwrseq_target_data pwrseq_thead_gpu_target = {
+ .name = "gpu-power",
+ .unit = &pwrseq_thead_gpu_unit,
+};
+
+static const struct pwrseq_target_data *pwrseq_thead_gpu_targets[] = {
+ &pwrseq_thead_gpu_target,
+ NULL
+};
+
+static int pwrseq_thead_gpu_match(struct pwrseq_device *pwrseq,
+ struct device *dev)
+{
+ struct pwrseq_thead_gpu_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
+ static const char *const clk_names[] = { "core", "sys" };
+ struct of_phandle_args pwr_spec;
+ int i, ret;
+
+ /* We only match the specific T-HEAD TH1520 GPU compatible */
+ if (!of_device_is_compatible(dev->of_node, "thead,th1520-gpu"))
+ return PWRSEQ_NO_MATCH;
+
+ ret = of_parse_phandle_with_args(dev->of_node, "power-domains",
+ "#power-domain-cells", 0, &pwr_spec);
+ if (ret)
+ return PWRSEQ_NO_MATCH;
+
+ /* Additionally verify consumer device has AON as power-domain */
+ if (pwr_spec.np != ctx->aon_node || pwr_spec.args[0] != TH1520_GPU_PD) {
+ of_node_put(pwr_spec.np);
+ return PWRSEQ_NO_MATCH;
+ }
+
+ of_node_put(pwr_spec.np);
+
+ /* If a consumer is already bound, only allow a re-match from it */
+ if (ctx->consumer_node)
+ return ctx->consumer_node == dev->of_node ?
+ PWRSEQ_MATCH_OK : PWRSEQ_NO_MATCH;
+
+ ctx->num_clks = ARRAY_SIZE(clk_names);
+ ctx->clks = kzalloc_objs(*ctx->clks, ctx->num_clks);
+ if (!ctx->clks)
+ return -ENOMEM;
+
+ for (i = 0; i < ctx->num_clks; i++)
+ ctx->clks[i].id = clk_names[i];
+
+ ret = clk_bulk_get(dev, ctx->num_clks, ctx->clks);
+ if (ret)
+ goto err_free_clks;
+
+ ctx->gpu_reset = reset_control_get_shared(dev, NULL);
+ if (IS_ERR(ctx->gpu_reset)) {
+ ret = PTR_ERR(ctx->gpu_reset);
+ goto err_put_clks;
+ }
+
+ ctx->consumer_node = of_node_get(dev->of_node);
+
+ return PWRSEQ_MATCH_OK;
+
+err_put_clks:
+ clk_bulk_put(ctx->num_clks, ctx->clks);
+err_free_clks:
+ kfree(ctx->clks);
+ ctx->clks = NULL;
+
+ return ret;
+}
+
+static int pwrseq_thead_gpu_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct device *parent_dev = dev->parent;
+ struct pwrseq_thead_gpu_ctx *ctx;
+ struct pwrseq_config config = {};
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->aon_node = parent_dev->of_node;
+
+ ctx->clkgen_reset =
+ devm_reset_control_get_exclusive(parent_dev, "gpu-clkgen");
+ if (IS_ERR(ctx->clkgen_reset))
+ return dev_err_probe(
+ dev, PTR_ERR(ctx->clkgen_reset),
+ "Failed to get GPU clkgen reset from parent\n");
+
+ config.parent = dev;
+ config.owner = THIS_MODULE;
+ config.drvdata = ctx;
+ config.match = pwrseq_thead_gpu_match;
+ config.targets = pwrseq_thead_gpu_targets;
+
+ ctx->pwrseq = devm_pwrseq_device_register(dev, &config);
+ if (IS_ERR(ctx->pwrseq))
+ return dev_err_probe(dev, PTR_ERR(ctx->pwrseq),
+ "Failed to register power sequencer\n");
+
+ auxiliary_set_drvdata(adev, ctx);
+
+ return 0;
+}
+
+static void pwrseq_thead_gpu_remove(struct auxiliary_device *adev)
+{
+ struct pwrseq_thead_gpu_ctx *ctx = auxiliary_get_drvdata(adev);
+
+ if (ctx->gpu_reset)
+ reset_control_put(ctx->gpu_reset);
+
+ if (ctx->clks) {
+ clk_bulk_put(ctx->num_clks, ctx->clks);
+ kfree(ctx->clks);
+ }
+
+ if (ctx->consumer_node)
+ of_node_put(ctx->consumer_node);
+}
+
+static const struct auxiliary_device_id pwrseq_thead_gpu_id_table[] = {
+ { .name = "th1520_pm_domains.pwrseq-gpu" },
+ {},
+};
+MODULE_DEVICE_TABLE(auxiliary, pwrseq_thead_gpu_id_table);
+
+static struct auxiliary_driver pwrseq_thead_gpu_driver = {
+ .driver = {
+ .name = "pwrseq-thead-gpu",
+ },
+ .probe = pwrseq_thead_gpu_probe,
+ .remove = pwrseq_thead_gpu_remove,
+ .id_table = pwrseq_thead_gpu_id_table,
+};
+module_auxiliary_driver(pwrseq_thead_gpu_driver);
+
+MODULE_AUTHOR("Michal Wilczynski <m.wilczynski@samsung.com>");
+MODULE_DESCRIPTION("T-HEAD TH1520 GPU power sequencer driver");
+MODULE_LICENSE("GPL");