diff options
Diffstat (limited to 'drivers/power/sequencing')
| -rw-r--r-- | drivers/power/sequencing/Kconfig | 21 | ||||
| -rw-r--r-- | drivers/power/sequencing/Makefile | 2 | ||||
| -rw-r--r-- | drivers/power/sequencing/core.c | 21 | ||||
| -rw-r--r-- | drivers/power/sequencing/pwrseq-pcie-m2.c | 486 | ||||
| -rw-r--r-- | drivers/power/sequencing/pwrseq-qcom-wcn.c | 143 | ||||
| -rw-r--r-- | drivers/power/sequencing/pwrseq-thead-gpu.c | 249 |
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"); |
