Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,49 @@
# SPDX-License-Identifier: GPL-2.0-only
menuconfig POWER_SEQUENCING
tristate "Power Sequencing support"
help
Say Y here to enable the Power Sequencing subsystem.
This subsystem is designed to control power to devices that share
complex resources and/or require specific power sequences to be run
during power-up.
If unsure, say no.
if POWER_SEQUENCING
config POWER_SEQUENCING_QCOM_WCN
tristate "Qualcomm WCN family PMU driver"
default m if ARCH_QCOM
depends on OF || COMPILE_TEST
help
Say Y here to enable the power sequencing driver for Qualcomm
WCN Bluetooth/WLAN chipsets.
Typically, a package from the Qualcomm WCN family contains the BT
and WLAN modules whose power is controlled by the PMU module. As the
former two share the power-up sequence which is executed by the PMU,
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
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,605 @@
// 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/list.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_pci_dev {
struct serdev_device *serdev;
struct of_changeset *ocs;
struct pci_dev *pdev;
struct list_head list;
};
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 device *dev;
struct list_head pci_devices;
struct mutex list_lock;
};
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 const struct pci_device_id pwrseq_m2_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x1103),
.driver_data = (kernel_ulong_t)"qcom,wcn6855-bt" },
{ PCI_DEVICE(PCI_VENDOR_ID_QCOM, 0x1107),
.driver_data = (kernel_ulong_t)"qcom,wcn7850-bt" },
{ } /* Sentinel */
};
static int pwrseq_pcie_m2_create_bt_node(struct pwrseq_pcie_m2_ctx *ctx,
struct pwrseq_pci_dev *pci_dev,
struct device_node *parent,
struct pci_dev *pdev)
{
const struct pci_device_id *id;
struct device *dev = ctx->dev;
const char *compatible;
struct device_node *np;
int ret;
id = pci_match_id(pwrseq_m2_pci_ids, pdev);
if (WARN_ON_ONCE(!id)) /* Shouldn't happen */
return -ENODEV;
compatible = (const char *)id->driver_data;
pci_dev->ocs = kzalloc_obj(*pci_dev->ocs);
if (!pci_dev->ocs)
return -ENOMEM;
of_changeset_init(pci_dev->ocs);
np = of_changeset_create_node(pci_dev->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(pci_dev->ocs, np, "compatible", compatible);
if (ret) {
dev_err(dev, "Failed to add bluetooth compatible: %d\n", ret);
goto err_destroy_changeset;
}
ret = of_changeset_apply(pci_dev->ocs);
if (ret) {
dev_err(dev, "Failed to apply changeset: %d\n", ret);
goto err_destroy_changeset;
}
ret = device_add_of_node(&pci_dev->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(pci_dev->ocs);
err_destroy_changeset:
of_changeset_destroy(pci_dev->ocs);
kfree(pci_dev->ocs);
pci_dev->ocs = NULL;
return ret;
}
static int pwrseq_pcie_m2_create_serdev_one(struct pwrseq_pcie_m2_ctx *ctx,
struct pci_dev *pdev)
{
struct serdev_controller *serdev_ctrl;
struct device *dev = ctx->dev;
struct pwrseq_pci_dev *pci_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;
}
/* Bail out if the serdev device was already created for the PCI dev */
scoped_guard(mutex, &ctx->list_lock) {
list_for_each_entry(pci_dev, &ctx->pci_devices, list) {
if (pci_dev->pdev == pdev)
return 0;
}
}
pci_dev = kzalloc(sizeof(*pci_dev), GFP_KERNEL);
if (!pci_dev) {
ret = -ENOMEM;
goto err_put_ctrl;
}
pci_dev->serdev = serdev_device_alloc(serdev_ctrl);
if (!pci_dev->serdev) {
ret = -ENOMEM;
goto err_free_pci_dev;
}
ret = pwrseq_pcie_m2_create_bt_node(ctx, pci_dev, serdev_parent, pdev);
if (ret)
goto err_free_serdev;
ret = serdev_device_add(pci_dev->serdev);
if (ret) {
dev_err(dev, "Failed to add serdev for PCI device (%s): %d\n",
pci_name(pdev), ret);
goto err_free_dt_node;
}
serdev_controller_put(serdev_ctrl);
pci_dev->pdev = pci_dev_get(pdev);
mutex_lock(&ctx->list_lock);
list_add_tail(&pci_dev->list, &ctx->pci_devices);
mutex_unlock(&ctx->list_lock);
return 0;
err_free_dt_node:
device_remove_of_node(&pci_dev->serdev->dev);
of_changeset_revert(pci_dev->ocs);
of_changeset_destroy(pci_dev->ocs);
kfree(pci_dev->ocs);
pci_dev->ocs = NULL;
err_free_serdev:
serdev_device_put(pci_dev->serdev);
pci_dev->serdev = NULL;
err_free_pci_dev:
kfree(pci_dev);
err_put_ctrl:
serdev_controller_put(serdev_ctrl);
return ret;
}
static void __pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx,
struct pwrseq_pci_dev *pci_dev)
{
if (pci_dev->serdev) {
device_remove_of_node(&pci_dev->serdev->dev);
serdev_device_remove(pci_dev->serdev);
}
if (pci_dev->ocs) {
of_changeset_revert(pci_dev->ocs);
of_changeset_destroy(pci_dev->ocs);
kfree(pci_dev->ocs);
}
pci_dev_put(pci_dev->pdev);
list_del(&pci_dev->list);
kfree(pci_dev);
}
static void pwrseq_pcie_m2_remove_serdev(struct pwrseq_pcie_m2_ctx *ctx,
struct pci_dev *pdev)
{
struct pwrseq_pci_dev *pci_dev, *tmp;
mutex_lock(&ctx->list_lock);
list_for_each_entry_safe(pci_dev, tmp, &ctx->pci_devices, list) {
if (!pdev || pci_dev->pdev == pdev) {
__pwrseq_pcie_m2_remove_serdev(ctx, pci_dev);
if (pdev)
break;
}
}
mutex_unlock(&ctx->list_lock);
}
static int pwrseq_pcie_m2_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:
if (pci_match_id(pwrseq_m2_pci_ids, pdev)) {
ret = pwrseq_pcie_m2_create_serdev_one(ctx, pdev);
if (ret)
return notifier_from_errno(ret);
}
break;
case BUS_NOTIFY_REMOVED_DEVICE:
if (pci_match_id(pwrseq_m2_pci_ids, pdev))
pwrseq_pcie_m2_remove_serdev(ctx, pdev);
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)
{
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(ctx->dev, 3, 0, "serial") ||
!pwrseq_pcie_m2_check_remote_node(ctx->dev, 0, 0, "pcie"))
return 0;
ctx->nb.notifier_call = pwrseq_pcie_m2_notify;
ret = bus_register_notifier(&pci_bus_type, &ctx->nb);
if (ret)
return dev_err_probe(ctx->dev, ret,
"Failed to register notifier for serdev\n");
return 0;
}
static int pwrseq_pcie_m2_create_serdev(struct pwrseq_pcie_m2_ctx *ctx)
{
struct pci_dev *pdev = NULL;
int ret;
if (!pwrseq_pcie_m2_check_remote_node(ctx->dev, 3, 0, "serial") ||
!pwrseq_pcie_m2_check_remote_node(ctx->dev, 0, 0, "pcie"))
return 0;
struct device_node *pci_parent __free(device_node) =
of_graph_get_remote_node(dev_of_node(ctx->dev), 0, 0);
if (!pci_parent)
return 0;
/* Create serdev for existing PCI devices if required */
for_each_pci_dev(pdev) {
if (!pdev->dev.parent || pci_parent != pdev->dev.parent->of_node)
continue;
if (!pci_match_id(pwrseq_m2_pci_ids, pdev))
continue;
ret = pwrseq_pcie_m2_create_serdev_one(ctx, pdev);
if (ret) {
dev_err_probe(ctx->dev, ret,
"Failed to create serdev for PCI device (%s)\n",
pci_name(pdev));
pci_dev_put(pdev);
goto err_remove_serdev;
}
}
return 0;
err_remove_serdev:
pwrseq_pcie_m2_remove_serdev(ctx, NULL);
return ret;
}
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;
}
mutex_init(&ctx->list_lock);
INIT_LIST_HEAD(&ctx->pci_devices);
ctx->dev = dev;
/* Create serdev for available PCI devices (if required) */
ret = pwrseq_pcie_m2_create_serdev(ctx);
if (ret)
goto err_destroy_mutex;
/*
* Register a notifier for creating protocol devices for
* non-discoverable busses like UART.
*/
ret = pwrseq_pcie_m2_register_notifier(ctx);
if (ret)
goto err_remove_serdev;
return 0;
err_remove_serdev:
pwrseq_pcie_m2_remove_serdev(ctx, NULL);
err_destroy_mutex:
mutex_destroy(&ctx->list_lock);
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, NULL);
mutex_destroy(&ctx->list_lock);
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");
@@ -0,0 +1,598 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2024 Linaro Ltd.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/jiffies.h>
#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>
#include <linux/types.h>
struct pwrseq_qcom_wcn_pdata {
const char *const *vregs;
size_t num_vregs;
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 {
struct pwrseq_device *pwrseq;
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;
struct clk *clk;
unsigned long last_gpio_enable_jf;
};
static void pwrseq_qcom_wcn_ensure_gpio_delay(struct pwrseq_qcom_wcn_ctx *ctx)
{
unsigned long diff_jiffies;
unsigned int diff_msecs;
if (!ctx->pdata->gpio_enable_delay_ms)
return;
diff_jiffies = jiffies - ctx->last_gpio_enable_jf;
diff_msecs = jiffies_to_msecs(diff_jiffies);
if (diff_msecs < ctx->pdata->gpio_enable_delay_ms)
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);
return regulator_bulk_enable(ctx->pdata->num_vregs, ctx->regs);
}
static int pwrseq_qcom_wcn_vregs_disable(struct pwrseq_device *pwrseq)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
return regulator_bulk_disable(ctx->pdata->num_vregs, ctx->regs);
}
static const struct pwrseq_unit_data pwrseq_qcom_wcn_vregs_unit_data = {
.name = "regulators-enable",
.enable = pwrseq_qcom_wcn_vregs_enable,
.disable = pwrseq_qcom_wcn_vregs_disable,
};
static int pwrseq_qcom_wcn_clk_enable(struct pwrseq_device *pwrseq)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
return clk_prepare_enable(ctx->clk);
}
static int pwrseq_qcom_wcn_clk_disable(struct pwrseq_device *pwrseq)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
clk_disable_unprepare(ctx->clk);
return 0;
}
static const struct pwrseq_unit_data pwrseq_qcom_wcn_clk_unit_data = {
.name = "clock-enable",
.enable = pwrseq_qcom_wcn_clk_enable,
.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,
NULL
};
static int pwrseq_qcom_wcn6855_clk_assert(struct pwrseq_device *pwrseq)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
if (!ctx->xo_clk_gpio)
return 0;
msleep(1);
gpiod_set_value_cansleep(ctx->xo_clk_gpio, 1);
usleep_range(100, 200);
return 0;
}
static const struct pwrseq_unit_data pwrseq_qcom_wcn6855_xo_clk_assert = {
.name = "xo-clk-assert",
.enable = pwrseq_qcom_wcn6855_clk_assert,
};
static const struct pwrseq_unit_data *pwrseq_qcom_wcn6855_unit_deps[] = {
&pwrseq_qcom_wcn_vregs_unit_data,
&pwrseq_qcom_wcn_clk_unit_data,
&pwrseq_qcom_wcn6855_xo_clk_assert,
NULL
};
static int pwrseq_qcom_wcn_bt_enable(struct pwrseq_device *pwrseq)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
pwrseq_qcom_wcn_ensure_gpio_delay(ctx);
gpiod_set_value_cansleep(ctx->bt_gpio, 1);
ctx->last_gpio_enable_jf = jiffies;
return 0;
}
static int pwrseq_qcom_wcn_bt_disable(struct pwrseq_device *pwrseq)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
gpiod_set_value_cansleep(ctx->bt_gpio, 0);
return 0;
}
static const struct pwrseq_unit_data pwrseq_qcom_wcn_bt_unit_data = {
.name = "bluetooth-enable",
.deps = pwrseq_qcom_wcn_unit_deps,
.enable = pwrseq_qcom_wcn_bt_enable,
.disable = pwrseq_qcom_wcn_bt_disable,
};
static const struct pwrseq_unit_data pwrseq_qcom_wcn6855_bt_unit_data = {
.name = "bluetooth-enable",
.deps = pwrseq_qcom_wcn6855_unit_deps,
.enable = pwrseq_qcom_wcn_bt_enable,
.disable = pwrseq_qcom_wcn_bt_disable,
};
static int pwrseq_qcom_wcn_wlan_enable(struct pwrseq_device *pwrseq)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
pwrseq_qcom_wcn_ensure_gpio_delay(ctx);
gpiod_set_value_cansleep(ctx->wlan_gpio, 1);
ctx->last_gpio_enable_jf = jiffies;
return 0;
}
static int pwrseq_qcom_wcn_wlan_disable(struct pwrseq_device *pwrseq)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
gpiod_set_value_cansleep(ctx->wlan_gpio, 0);
return 0;
}
static const struct pwrseq_unit_data pwrseq_qcom_wcn_wlan_unit_data = {
.name = "wlan-enable",
.deps = pwrseq_qcom_wcn_unit_deps,
.enable = pwrseq_qcom_wcn_wlan_enable,
.disable = pwrseq_qcom_wcn_wlan_disable,
};
static const struct pwrseq_unit_data pwrseq_qcom_wcn6855_wlan_unit_data = {
.name = "wlan-enable",
.deps = pwrseq_qcom_wcn6855_unit_deps,
.enable = pwrseq_qcom_wcn_wlan_enable,
.disable = pwrseq_qcom_wcn_wlan_disable,
};
static int pwrseq_qcom_wcn_pwup_delay(struct pwrseq_device *pwrseq)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
if (ctx->pdata->pwup_delay_ms)
msleep(ctx->pdata->pwup_delay_ms);
return 0;
}
static int pwrseq_qcom_wcn6855_xo_clk_deassert(struct pwrseq_device *pwrseq)
{
struct pwrseq_qcom_wcn_ctx *ctx = pwrseq_device_get_drvdata(pwrseq);
if (ctx->xo_clk_gpio) {
usleep_range(2000, 5000);
gpiod_set_value_cansleep(ctx->xo_clk_gpio, 0);
}
return pwrseq_qcom_wcn_pwup_delay(pwrseq);
}
static const struct pwrseq_target_data pwrseq_qcom_wcn_bt_target_data = {
.name = "bluetooth",
.unit = &pwrseq_qcom_wcn_bt_unit_data,
.post_enable = pwrseq_qcom_wcn_pwup_delay,
};
static const struct pwrseq_target_data pwrseq_qcom_wcn_wlan_target_data = {
.name = "wlan",
.unit = &pwrseq_qcom_wcn_wlan_unit_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,
.post_enable = pwrseq_qcom_wcn6855_xo_clk_deassert,
};
static const struct pwrseq_target_data pwrseq_qcom_wcn6855_wlan_target_data = {
.name = "wlan",
.unit = &pwrseq_qcom_wcn6855_wlan_unit_data,
.post_enable = pwrseq_qcom_wcn6855_xo_clk_deassert,
};
static const struct pwrseq_target_data *pwrseq_qcom_wcn_targets[] = {
&pwrseq_qcom_wcn_bt_target_data,
&pwrseq_qcom_wcn_wlan_target_data,
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,
NULL
};
static const char *const pwrseq_qca6390_vregs[] = {
"vddio",
"vddaon",
"vddpmu",
"vddrfa0p95",
"vddrfa1p3",
"vddrfa1p9",
"vddpcie1p3",
"vddpcie1p9",
};
static const struct pwrseq_qcom_wcn_pdata pwrseq_qca6390_of_data = {
.vregs = pwrseq_qca6390_vregs,
.num_vregs = ARRAY_SIZE(pwrseq_qca6390_vregs),
.pwup_delay_ms = 60,
.gpio_enable_delay_ms = 100,
.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",
"vddpmu",
"vddrfa0p8",
"vddrfa1p2",
"vddrfa1p7",
"vddrfa2p2",
};
static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn6750_of_data = {
.vregs = pwrseq_wcn6750_vregs,
.num_vregs = ARRAY_SIZE(pwrseq_wcn6750_vregs),
.pwup_delay_ms = 50,
.gpio_enable_delay_ms = 5,
.targets = pwrseq_qcom_wcn_targets,
};
static const char *const pwrseq_wcn6855_vregs[] = {
"vddio",
"vddaon",
"vddpmu",
"vddpmumx",
"vddpmucx",
"vddrfa0p95",
"vddrfa1p3",
"vddrfa1p9",
"vddpcie1p3",
"vddpcie1p9",
};
static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn6855_of_data = {
.vregs = pwrseq_wcn6855_vregs,
.num_vregs = ARRAY_SIZE(pwrseq_wcn6855_vregs),
.pwup_delay_ms = 50,
.gpio_enable_delay_ms = 5,
.targets = pwrseq_qcom_wcn6855_targets,
};
static const char *const pwrseq_wcn7850_vregs[] = {
"vdd",
"vddio",
"vddio1p2",
"vddaon",
"vdddig",
"vddrfa1p2",
"vddrfa1p8",
};
static const struct pwrseq_qcom_wcn_pdata pwrseq_wcn7850_of_data = {
.vregs = pwrseq_wcn7850_vregs,
.num_vregs = ARRAY_SIZE(pwrseq_wcn7850_vregs),
.pwup_delay_ms = 50,
.targets = pwrseq_qcom_wcn_targets,
};
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;
/*
* The PMU supplies power to the Bluetooth and WLAN modules. both
* consume the PMU AON output so check the presence of the
* 'vddaon-supply' property and whether it leads us to the right
* device.
*/
if (!of_property_present(dev_node, name))
return PWRSEQ_NO_MATCH;
struct device_node *reg_node __free(device_node) =
of_parse_phandle(dev_node, name, 0);
if (!reg_node)
return PWRSEQ_NO_MATCH;
/*
* `reg_node` is the PMU AON regulator, its parent is the `regulators`
* node and finally its grandparent is the PMU device node that we're
* looking for.
*/
if (!reg_node->parent || !reg_node->parent->parent ||
reg_node->parent->parent != ctx->of_node)
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;
/* 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)
{
struct device *dev = &pdev->dev;
struct pwrseq_qcom_wcn_ctx *ctx;
struct pwrseq_config config;
int i, ret;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->of_node = dev->of_node;
ctx->pdata = device_get_match_data(dev);
if (!ctx->pdata)
return dev_err_probe(dev, -ENODEV,
"Failed to obtain platform data\n");
ctx->regs = devm_kcalloc(dev, ctx->pdata->num_vregs,
sizeof(*ctx->regs), GFP_KERNEL);
if (!ctx->regs)
return -ENOMEM;
for (i = 0; i < ctx->pdata->num_vregs; i++)
ctx->regs[i].supply = ctx->pdata->vregs[i];
ret = devm_regulator_bulk_get(dev, ctx->pdata->num_vregs, ctx->regs);
if (ret < 0)
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),
"Failed to get the Bluetooth enable GPIO\n");
/*
* FIXME: This should actually be GPIOD_OUT_LOW, but doing so would
* cause the WLAN power to be toggled, resulting in PCIe link down.
* Since the PCIe controller driver is not handling link down currently,
* the device becomes unusable. So we need to keep this workaround until
* the link down handling is implemented in the controller driver.
*/
ctx->wlan_gpio = devm_gpiod_get_optional(dev, "wlan-enable",
GPIOD_ASIS);
if (IS_ERR(ctx->wlan_gpio))
return dev_err_probe(dev, PTR_ERR(ctx->wlan_gpio),
"Failed to get the WLAN enable GPIO\n");
ctx->xo_clk_gpio = devm_gpiod_get_optional(dev, "xo-clk",
GPIOD_OUT_LOW);
if (IS_ERR(ctx->xo_clk_gpio))
return dev_err_probe(dev, PTR_ERR(ctx->xo_clk_gpio),
"Failed to get the XO_CLK GPIO\n");
/*
* Set direction to output but keep the current value in order to not
* disable the WLAN module accidentally if it's already powered on.
*/
gpiod_direction_output(ctx->wlan_gpio,
gpiod_get_value_cansleep(ctx->wlan_gpio));
ctx->clk = devm_clk_get_optional(dev, NULL);
if (IS_ERR(ctx->clk))
return dev_err_probe(dev, PTR_ERR(ctx->clk),
"Failed to get the reference clock\n");
memset(&config, 0, sizeof(config));
config.parent = dev;
config.owner = THIS_MODULE;
config.drvdata = ctx;
config.match = ctx->pdata->match ? : pwrseq_qcom_wcn_match;
config.targets = ctx->pdata->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 the power sequencer\n");
return 0;
}
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,
},
{
.compatible = "qcom,wcn6855-pmu",
.data = &pwrseq_wcn6855_of_data,
},
{
.compatible = "qcom,wcn7850-pmu",
.data = &pwrseq_wcn7850_of_data,
},
{
.compatible = "qcom,wcn6750-pmu",
.data = &pwrseq_wcn6750_of_data,
},
{ }
};
MODULE_DEVICE_TABLE(of, pwrseq_qcom_wcn_of_match);
static struct platform_driver pwrseq_qcom_wcn_driver = {
.driver = {
.name = "pwrseq-qcom_wcn",
.of_match_table = pwrseq_qcom_wcn_of_match,
},
.probe = pwrseq_qcom_wcn_probe,
};
module_platform_driver(pwrseq_qcom_wcn_driver);
MODULE_AUTHOR("Bartosz Golaszewski <bartosz.golaszewski@linaro.org>");
MODULE_DESCRIPTION("Qualcomm WCN PMU power sequencing driver");
MODULE_LICENSE("GPL");
@@ -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");