diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index f5b383a9668d..4f5dee101af8 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 @@ -1500,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 13bd525bf790..1fc6aadb2f1b 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 @@ -179,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/fixed.c b/drivers/regulator/fixed.c index 599ad201dca7..0534c06bf981 100644 --- a/drivers/regulator/fixed.c +++ b/drivers/regulator/fixed.c @@ -27,6 +27,10 @@ #include #include #include +#ifdef QTI_FIXED_REGULATOR +#include +#include +#endif #include #include @@ -172,6 +176,24 @@ 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); + + ret = devm_regulator_debug_register(dev, rdev); + if (ret) + dev_err(dev, "failed to register debug regulator, ret=%d\n", + ret); +} +#endif + static int reg_fixed_voltage_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -295,6 +317,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 +341,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 +368,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" 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");