mirror of
https://github.com/torvalds/linux.git
synced 2026-07-30 19:21:28 +02:00
Merge "regulator: add QTI regulator OCP notifier driver"
This commit is contained in:
commit
386aa76ed8
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@
|
|||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/regulator/of_regulator.h>
|
||||
#ifdef QTI_FIXED_REGULATOR
|
||||
#include <linux/regulator/debug-regulator.h>
|
||||
#include <linux/regulator/proxy-consumer.h>
|
||||
#endif
|
||||
#include <linux/regulator/machine.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
|
|
@ -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),
|
||||
},
|
||||
};
|
||||
|
|
|
|||
5
drivers/regulator/qti-fixed-regulator.c
Normal file
5
drivers/regulator/qti-fixed-regulator.c
Normal file
|
|
@ -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"
|
||||
270
drivers/regulator/qti-ocp-notifier.c
Normal file
270
drivers/regulator/qti-ocp-notifier.c
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved. */
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/ipc_logging.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/nvmem-consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
|
||||
#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");
|
||||
Loading…
Reference in New Issue
Block a user