From 8e130374fe23a429f20a6dc55b61d9584fc3019a Mon Sep 17 00:00:00 2001 From: David Collins Date: Fri, 22 Nov 2019 16:10:42 -0800 Subject: [PATCH 1/3] regulator: add QTI fixed voltage regulator driver Add a new QTI fixed voltage regulator driver that is derived from the fixed voltage regulator driver. Use this to provide support for proxy consumer voting and debug features required on Qualcomm Technologies, Inc. boards. Implement this driver as a wrapper around fixed.c with #ifdef directives so that future updates to fixed.c are applied automatically to qti-fixed-regulator.c. Add support to enforce proxy consumer requests defined in device tree for qti-fixed-regulator devices. These can be used to ensure that regulators are not accidentally disabled before all consumers have had a chance to make their own requests. Change-Id: If4cf664b3f6a8214256a4b2de753f7ff27aa864e Signed-off-by: David Collins --- drivers/regulator/Kconfig | 9 +++++++ drivers/regulator/Makefile | 1 + drivers/regulator/fixed.c | 31 +++++++++++++++++++++++++ drivers/regulator/qti-fixed-regulator.c | 5 ++++ 4 files changed, 46 insertions(+) create mode 100644 drivers/regulator/qti-fixed-regulator.c diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index f5b383a9668d..c733985ee79e 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -46,6 +46,15 @@ config REGULATOR_FIXED_VOLTAGE useful for systems which use a combination of software managed regulators and simple non-configurable regulators. +config REGULATOR_QTI_FIXED_VOLTAGE + tristate "QTI fixed voltage regulator support" + help + This driver provides support for fixed voltage regulators which are + controlled via a GPIO. It also supports proxy consumer voting and + debug features utilized on Qualcomm Technologies, Inc. boards. Use + this driver in place of the fixed voltage regulator driver if these + additional features are required. + config REGULATOR_VIRTUAL_CONSUMER tristate "Virtual regulator consumer support" help diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 13bd525bf790..e8290e419c58 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_REGULATOR) += core.o dummy.o fixed-helper.o helpers.o devres.o irq_helpers.o obj-$(CONFIG_OF) += of_regulator.o obj-$(CONFIG_REGULATOR_FIXED_VOLTAGE) += fixed.o +obj-$(CONFIG_REGULATOR_QTI_FIXED_VOLTAGE) += qti-fixed-regulator.o obj-$(CONFIG_REGULATOR_VIRTUAL_CONSUMER) += virtual.o obj-$(CONFIG_REGULATOR_USERSPACE_CONSUMER) += userspace-consumer.o obj-$(CONFIG_REGULATOR_PROXY_CONSUMER) += proxy-consumer.o diff --git a/drivers/regulator/fixed.c b/drivers/regulator/fixed.c index 599ad201dca7..4b6c5e09c04b 100644 --- a/drivers/regulator/fixed.c +++ b/drivers/regulator/fixed.c @@ -27,6 +27,9 @@ #include #include #include +#ifdef QTI_FIXED_REGULATOR +#include +#endif #include #include @@ -172,6 +175,19 @@ static const struct regulator_ops fixed_voltage_domain_ops = { .is_enabled = reg_is_enabled, }; +#ifdef QTI_FIXED_REGULATOR +static void qti_reg_fixed_voltage_init(struct device *dev, + struct regulator_dev *rdev) +{ + int ret; + + ret = devm_regulator_proxy_consumer_register(dev, dev->of_node); + if (ret) + dev_err(dev, "failed to register proxy consumer, ret=%d\n", + ret); +} +#endif + static int reg_fixed_voltage_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -295,6 +311,10 @@ static int reg_fixed_voltage_probe(struct platform_device *pdev) platform_set_drvdata(pdev, drvdata); +#ifdef QTI_FIXED_REGULATOR + qti_reg_fixed_voltage_init(dev, drvdata->dev); +#endif + dev_dbg(&pdev->dev, "%s supplying %duV\n", drvdata->desc.name, drvdata->desc.fixed_uV); @@ -315,6 +335,12 @@ static const struct fixed_dev_type fixed_domain_data = { }; static const struct of_device_id fixed_of_match[] = { +#ifdef QTI_FIXED_REGULATOR + { + .compatible = "qti-regulator-fixed", + .data = &fixed_voltage_data, + }, +#endif { .compatible = "regulator-fixed", .data = &fixed_voltage_data, @@ -336,7 +362,12 @@ MODULE_DEVICE_TABLE(of, fixed_of_match); static struct platform_driver regulator_fixed_voltage_driver = { .probe = reg_fixed_voltage_probe, .driver = { +#ifdef QTI_FIXED_REGULATOR + .name = "qti-reg-fixed-voltage", + .sync_state = regulator_proxy_consumer_sync_state, +#else .name = "reg-fixed-voltage", +#endif .of_match_table = of_match_ptr(fixed_of_match), }, }; diff --git a/drivers/regulator/qti-fixed-regulator.c b/drivers/regulator/qti-fixed-regulator.c new file mode 100644 index 000000000000..7df0625b91ae --- /dev/null +++ b/drivers/regulator/qti-fixed-regulator.c @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */ + +#define QTI_FIXED_REGULATOR +#include "fixed.c" From bb7c4cfc1e7ef5da408591534c71ecf0ffa50d66 Mon Sep 17 00:00:00 2001 From: David Collins Date: Fri, 26 Jun 2020 15:07:10 -0700 Subject: [PATCH 2/3] regulator: qti-fixed-regulator: add support for debugfs control Add support for debugfs control of a regulator's state. This is useful for testing and debugging. Change-Id: If400e15e25f29902261aa5d262baeee81b323dd5 Signed-off-by: David Collins --- drivers/regulator/fixed.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/regulator/fixed.c b/drivers/regulator/fixed.c index 4b6c5e09c04b..0534c06bf981 100644 --- a/drivers/regulator/fixed.c +++ b/drivers/regulator/fixed.c @@ -28,6 +28,7 @@ #include #include #ifdef QTI_FIXED_REGULATOR +#include #include #endif #include @@ -185,6 +186,11 @@ static void qti_reg_fixed_voltage_init(struct device *dev, if (ret) dev_err(dev, "failed to register proxy consumer, ret=%d\n", ret); + + ret = devm_regulator_debug_register(dev, rdev); + if (ret) + dev_err(dev, "failed to register debug regulator, ret=%d\n", + ret); } #endif From f5061a076f6748a4edc1c5f10527e4411acf67e8 Mon Sep 17 00:00:00 2001 From: David Collins Date: Tue, 5 Apr 2022 17:27:51 -0700 Subject: [PATCH 3/3] regulator: add QTI regulator OCP notifier driver Add a qti-ocp-notifier driver which is used to log and notify consumers of regulator over-current (OCP) events. This driver is needed to monitor regulators controlled by RPMh since the application processor is unable to directly receive OCP interrupts for RPMh managed regulators. Change-Id: I1fac154a5c6f70454ed1a013b9013c208d02dcce Signed-off-by: David Collins --- drivers/regulator/Kconfig | 9 + drivers/regulator/Makefile | 1 + drivers/regulator/qti-ocp-notifier.c | 270 +++++++++++++++++++++++++++ 3 files changed, 280 insertions(+) create mode 100644 drivers/regulator/qti-ocp-notifier.c diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index c733985ee79e..4f5dee101af8 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1509,4 +1509,13 @@ config REGULATOR_STUB Consumers can use stub regulator device with proper constraint checking while the real regulator driver is being developed. +config REGULATOR_QTI_OCP_NOTIFIER + tristate "Qualcomm Technologies, Inc. Regulator OCP Notifier Driver" + help + This driver provides support for logging and notifying consumers about + regulator over-current (OCP) events on certain Qualcomm Technologies, + Inc. PMIC devices. This is useful for debugging as well as for + providing a more graceful recovery mechanism than resetting the entire + system. + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index e8290e419c58..1fc6aadb2f1b 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -180,5 +180,6 @@ obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o obj-$(CONFIG_REGULATOR_RPMH) += rpmh-regulator.o obj-$(CONFIG_REGULATOR_STUB) += stub-regulator.o obj-$(CONFIG_REGULATOR_DEBUG_CONTROL) += debug-regulator.o +obj-$(CONFIG_REGULATOR_QTI_OCP_NOTIFIER) += qti-ocp-notifier.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/qti-ocp-notifier.c b/drivers/regulator/qti-ocp-notifier.c new file mode 100644 index 000000000000..b928cd8d70de --- /dev/null +++ b/drivers/regulator/qti-ocp-notifier.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "internal.h" + +#define OCP_LOG_ENTRY_SIZE 2 +#define IPC_LOG_PAGES 3 + +struct ocp_log_entry { + u16 ppid; + u8 mode_at_ocp; +}; + +struct ocp_notifier_dev { + struct device *dev; + void *ipc_log; + struct nvmem_cell *nvmem_cell; + int ocp_log_entry_count; + struct idr regulators; +}; + +static const char *rdev_name(struct regulator_dev *rdev) +{ + if (rdev->constraints && rdev->constraints->name) + return rdev->constraints->name; + else if (rdev->desc->name) + return rdev->desc->name; + else + return ""; +} + +#define BUF_SIZE 16 + +static int ocp_notifier_get_regulator_name(struct ocp_notifier_dev *ocp_dev, + int ppid, const char **name) +{ + struct regulator_dev *rdev; + struct regulator *reg; + char buf[BUF_SIZE]; + int ret; + + rdev = idr_find(&ocp_dev->regulators, ppid); + + if (!rdev) { + buf[0] = '\0'; + scnprintf(buf, BUF_SIZE, "periph-%03x", ppid); + reg = regulator_get_optional(ocp_dev->dev, buf); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + if (ret == -EPROBE_DEFER) + return ret; + /* Ignore case of unspecified supply mapping */ + dev_dbg(ocp_dev->dev, "failed to get %s-supply, ret=%d\n", + buf, ret); + } else { + rdev = reg->rdev; + regulator_put(reg); + ret = idr_alloc(&ocp_dev->regulators, rdev, ppid, + ppid + 1, GFP_KERNEL); + if (ret < 0) + return ret; + else if (ret != ppid) + return -EINVAL; + } + } + + if (rdev) + *name = rdev_name(rdev); + + return 0; +} + +static int ocp_notifier_log_event(struct ocp_notifier_dev *ocp_dev, + struct ocp_log_entry *entry, const char *label) +{ + const char *name = NULL; + int ret; + + if (entry->ppid == 0) + return 0; + + ret = ocp_notifier_get_regulator_name(ocp_dev, entry->ppid, &name); + if (ret) + return ret; + + if (name) { + pr_err("%s name=%s, ppid=0x%03X, mode=%u\n", + label, name, entry->ppid, entry->mode_at_ocp); + ipc_log_string(ocp_dev->ipc_log, "%s name=%s, ppid=0x%03X, mode=%u\n", + label, name, entry->ppid, entry->mode_at_ocp); + } else { + pr_err("%s ppid=0x%03X, mode=%u\n", + label, entry->ppid, entry->mode_at_ocp); + ipc_log_string(ocp_dev->ipc_log, "%s ppid=0x%03X, mode=%u\n", + label, entry->ppid, entry->mode_at_ocp); + } + + return 0; +} + +static int ocp_notifier_read_entry(struct ocp_notifier_dev *ocp_dev, u32 index, + struct ocp_log_entry *entry) +{ + size_t len = 0; + u8 *buf; + int ret, i; + + if (index >= ocp_dev->ocp_log_entry_count) + return -EINVAL; + + buf = nvmem_cell_read(ocp_dev->nvmem_cell, &len); + if (IS_ERR(buf)) { + ret = PTR_ERR(buf); + dev_err(ocp_dev->dev, "failed to read nvmem cell, ret=%d\n", ret); + return ret; + } + + i = index * OCP_LOG_ENTRY_SIZE; + if (i + 1 >= len) { + dev_err(ocp_dev->dev, "invalid OCP log index=%i\n", i); + kfree(buf); + return -EINVAL; + } + + /* + * OCP log entry layout: + * Byte 0: [7:4] - SID + * [2:0] - mode at OCP + * Byte 1: [7:0] - PID + */ + entry->ppid = (((u16)buf[i] << 4) & 0xF00) | buf[i + 1]; + entry->mode_at_ocp = buf[i] & 0x7; + + kfree(buf); + + return 0; +} + +static irqreturn_t ocp_notifier_handler(int irq, void *data) +{ + struct ocp_notifier_dev *ocp_dev = data; + struct ocp_log_entry entry = {0}; + struct regulator_dev *rdev; + int ret; + + ret = ocp_notifier_read_entry(ocp_dev, 0, &entry); + if (ret) + goto done; + + ret = ocp_notifier_log_event(ocp_dev, &entry, + "Regulator OCP during runtime:"); + if (ret) + goto done; + + rdev = idr_find(&ocp_dev->regulators, entry.ppid); + if (!rdev) + goto done; + + regulator_notifier_call_chain(rdev, REGULATOR_EVENT_OVER_CURRENT, NULL); +done: + return IRQ_HANDLED; +} + +static int ocp_notifier_probe(struct platform_device *pdev) +{ + struct ocp_notifier_dev *ocp_dev; + struct ocp_log_entry entry = {0}; + size_t len = 0; + int ret, i, irq; + u8 *buf; + + ocp_dev = devm_kzalloc(&pdev->dev, sizeof(*ocp_dev), GFP_KERNEL); + if (!ocp_dev) + return -ENOMEM; + ocp_dev->dev = &pdev->dev; + + ocp_dev->nvmem_cell = devm_nvmem_cell_get(&pdev->dev, "ocp_log"); + if (IS_ERR(ocp_dev->nvmem_cell)) { + ret = PTR_ERR(ocp_dev->nvmem_cell); + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "failed to get nvmem cell, ret=%d\n", + ret); + return ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "failed to irq, ret=%d\n", irq); + return irq; + } + + ocp_dev->ipc_log = ipc_log_context_create(IPC_LOG_PAGES, + "regulator_ocp", 0); + platform_set_drvdata(pdev, ocp_dev); + + buf = nvmem_cell_read(ocp_dev->nvmem_cell, &len); + if (IS_ERR(buf)) { + ret = PTR_ERR(buf); + dev_err(&pdev->dev, "failed to read nvmem cell, ret=%d\n", ret); + return ret; + } + ocp_dev->ocp_log_entry_count = len / OCP_LOG_ENTRY_SIZE; + kfree(buf); + + idr_init(&ocp_dev->regulators); + + for (i = 0; i < ocp_dev->ocp_log_entry_count; i++) { + ret = ocp_notifier_read_entry(ocp_dev, i, &entry); + if (ret) + return ret; + + ret = ocp_notifier_log_event(ocp_dev, &entry, + "Regulator OCP event before kernel boot:"); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "failed to log entry %d, ret=%d\n", + i, ret); + return ret; + } + } + + return devm_request_threaded_irq(&pdev->dev, irq, NULL, + ocp_notifier_handler, IRQF_ONESHOT, + "regulator-ocp", ocp_dev); +} + +static int ocp_notifier_remove(struct platform_device *pdev) +{ + struct ocp_notifier_dev *ocp_dev = platform_get_drvdata(pdev); + + ipc_log_context_destroy(ocp_dev->ipc_log); + idr_destroy(&ocp_dev->regulators); + + return 0; +} + +static const struct of_device_id ocp_notifier_of_match[] = { + { .compatible = "qcom,regulator-ocp-notifier" }, + {} +}; +MODULE_DEVICE_TABLE(of, ocp_notifier_of_match); + +static struct platform_driver ocp_notifier_driver = { + .driver = { + .name = "qti-ocp-notifier", + .of_match_table = of_match_ptr(ocp_notifier_of_match), + }, + .probe = ocp_notifier_probe, + .remove = ocp_notifier_remove, +}; +module_platform_driver(ocp_notifier_driver); + +MODULE_DESCRIPTION("QTI Regulator OCP Notifier Driver"); +MODULE_LICENSE("GPL v2");