Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cf03466c authored by David Collins's avatar David Collins
Browse files

regulator: Add proxy consumer driver



Add a proxy consumer driver which can be used to ensure that a
given regulator maintains a certain minimum state during bootup.
Enable state, voltage, and current may be forced to specified
levels.

Change-Id: I0ccc63a41684462684ac737fb2f4129a3e6e4aea
Signed-off-by: default avatarDavid Collins <collinsd@codeaurora.org>
parent f6d207a3
Loading
Loading
Loading
Loading
+32 −0
Original line number Diff line number Diff line
Regulator Proxy Consumer Bindings

Regulator proxy consumers provide a means to use a default regulator state
during bootup only which is removed at the end of boot.  This feature can be
used in situations where a shared regulator can be scaled between several
possible voltages and hardware requires that it be at a high level at the
beginning of boot before the consumer device responsible for requesting the
high level has probed.

Optional properties:
proxy-supply:			phandle of the regulator's own device node.
				This property is required if any of the three
				properties below are specified.
qcom,proxy-consumer-enable:	Boolean indicating that the regulator must be
				kept enabled during boot.
qcom,proxy-consumer-voltage:	List of two integers corresponding the minimum
				and maximum voltage allowed during boot in
				microvolts.
qcom,proxy-consumer-current:	Minimum current in microamps required during
				boot.

Example:

	foo_vreg: regulator@0 {
		regulator-name = "foo";
		regulator-min-microvolt = <1000000>;
		regulator-max-microvolt = <2000000>;
		proxy-supply = <&foo_vreg>;
		qcom,proxy-consumer-voltage = <1500000 2000000>;
		qcom,proxy-consumer-current = <25000>;
		qcom,proxy-consumer-enable;
	};
+10 −0
Original line number Diff line number Diff line
@@ -54,6 +54,16 @@ config REGULATOR_USERSPACE_CONSUMER

	  If unsure, say no.

config REGULATOR_PROXY_CONSUMER
	bool "Boot time regulator proxy consumer support"
	help
	  This driver provides support for boot time regulator proxy requests.
	  It can enforce a specified voltage range, set a minimum current,
	  and/or keep a regulator enabled.  It is needed in circumstances where
	  reducing one or more of these three quantities will cause hardware to
	  stop working if performed before the driver managing the hardware has
	  probed.

config REGULATOR_88PG86X
	tristate "Marvell 88PG86X voltage regulators"
	depends on I2C
+1 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ obj-$(CONFIG_OF) += of_regulator.o
obj-$(CONFIG_REGULATOR_FIXED_VOLTAGE) += fixed.o
obj-$(CONFIG_REGULATOR_VIRTUAL_CONSUMER) += virtual.o
obj-$(CONFIG_REGULATOR_USERSPACE_CONSUMER) += userspace-consumer.o
obj-$(CONFIG_REGULATOR_PROXY_CONSUMER) += proxy-consumer.o

obj-$(CONFIG_REGULATOR_88PG86X) += 88pg86x.o
obj-$(CONFIG_REGULATOR_88PM800) += 88pm800.o
+222 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2013-2014, 2016, The Linux Foundation. All rights reserved.
 */

#define pr_fmt(fmt) "%s: " fmt, __func__

#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/regulator/consumer.h>
#include <linux/regulator/proxy-consumer.h>

struct proxy_consumer {
	struct list_head	list;
	struct regulator	*reg;
	bool			enable;
	int			min_uV;
	int			max_uV;
	u32			current_uA;
};

static DEFINE_MUTEX(proxy_consumer_list_mutex);
static LIST_HEAD(proxy_consumer_list);
static bool proxy_consumers_removed;

/**
 * regulator_proxy_consumer_register() - conditionally register a proxy consumer
 *		 for the specified regulator and set its boot time parameters
 * @reg_dev:		Device pointer of the regulator
 * @reg_node:		Device node pointer of the regulator
 *
 * Returns a struct proxy_consumer pointer corresponding to the regulator on
 * success, ERR_PTR() if an error occurred, or NULL if no proxy consumer is
 * needed for the regulator.  This function calls
 * regulator_get(reg_dev, "proxy") after first checking if any proxy consumer
 * properties are present in the reg_node device node.  After that, the voltage,
 * minimum current, and/or the enable state will be set based upon the device
 * node property values.
 */
struct proxy_consumer *regulator_proxy_consumer_register(struct device *reg_dev,
			struct device_node *reg_node)
{
	struct proxy_consumer *consumer = NULL;
	const char *reg_name = "";
	u32 voltage[2] = {0};
	int rc;

	/* Return immediately if no proxy consumer properties are specified. */
	if (!of_find_property(reg_node, "qcom,proxy-consumer-enable", NULL)
	    && !of_find_property(reg_node, "qcom,proxy-consumer-voltage", NULL)
	    && !of_find_property(reg_node, "qcom,proxy-consumer-current", NULL))
		return NULL;

	mutex_lock(&proxy_consumer_list_mutex);

	/* Do not register new consumers if they cannot be removed later. */
	if (proxy_consumers_removed) {
		rc = -EPERM;
		goto unlock;
	}

	if (dev_name(reg_dev))
		reg_name = dev_name(reg_dev);

	consumer = kzalloc(sizeof(*consumer), GFP_KERNEL);
	if (!consumer) {
		rc = -ENOMEM;
		goto unlock;
	}

	consumer->enable
		= of_property_read_bool(reg_node, "qcom,proxy-consumer-enable");
	of_property_read_u32(reg_node, "qcom,proxy-consumer-current",
				&consumer->current_uA);
	rc = of_property_read_u32_array(reg_node, "qcom,proxy-consumer-voltage",
					voltage, 2);
	if (!rc) {
		consumer->min_uV = voltage[0];
		consumer->max_uV = voltage[1];
	}

	dev_dbg(reg_dev, "proxy consumer request: enable=%d, voltage_range=[%d, %d] uV, min_current=%d uA\n",
		consumer->enable, consumer->min_uV, consumer->max_uV,
		consumer->current_uA);

	consumer->reg = regulator_get(reg_dev, "proxy");
	if (IS_ERR_OR_NULL(consumer->reg)) {
		rc = PTR_ERR(consumer->reg);
		pr_err("regulator_get() failed for %s, rc=%d\n", reg_name, rc);
		goto unlock;
	}

	if (consumer->max_uV > 0 && consumer->min_uV <= consumer->max_uV) {
		rc = regulator_set_voltage(consumer->reg, consumer->min_uV,
						consumer->max_uV);
		if (rc) {
			pr_err("regulator_set_voltage %s failed, rc=%d\n",
				reg_name, rc);
			goto free_regulator;
		}
	}

	if (consumer->current_uA > 0) {
		rc = regulator_set_load(consumer->reg,
						consumer->current_uA);
		if (rc < 0) {
			pr_err("regulator_set_load %s failed, rc=%d\n",
				reg_name, rc);
			goto remove_voltage;
		}
	}

	if (consumer->enable) {
		rc = regulator_enable(consumer->reg);
		if (rc) {
			pr_err("regulator_enable %s failed, rc=%d\n", reg_name,
				rc);
			goto remove_current;
		}
	}

	list_add(&consumer->list, &proxy_consumer_list);
	mutex_unlock(&proxy_consumer_list_mutex);

	return consumer;

remove_current:
	regulator_set_load(consumer->reg, 0);
remove_voltage:
	regulator_set_voltage(consumer->reg, 0, INT_MAX);
free_regulator:
	regulator_put(consumer->reg);
unlock:
	kfree(consumer);
	mutex_unlock(&proxy_consumer_list_mutex);
	return ERR_PTR(rc);
}

/* proxy_consumer_list_mutex must be held by caller. */
static int regulator_proxy_consumer_remove(struct proxy_consumer *consumer)
{
	int rc = 0;

	if (consumer->enable) {
		rc = regulator_disable(consumer->reg);
		if (rc)
			pr_err("regulator_disable failed, rc=%d\n", rc);
	}

	if (consumer->current_uA > 0) {
		rc = regulator_set_load(consumer->reg, 0);
		if (rc < 0)
			pr_err("regulator_set_load failed, rc=%d\n",
				rc);
	}

	if (consumer->max_uV > 0 && consumer->min_uV <= consumer->max_uV) {
		rc = regulator_set_voltage(consumer->reg, 0, INT_MAX);
		if (rc)
			pr_err("regulator_set_voltage failed, rc=%d\n", rc);
	}

	regulator_put(consumer->reg);
	list_del(&consumer->list);
	kfree(consumer);

	return rc;
}

/**
 * regulator_proxy_consumer_unregister() - unregister a proxy consumer and
 *					   remove its boot time requests
 * @consumer:		Pointer to proxy_consumer to be removed
 *
 * Returns 0 on success or errno on failure.  This function removes all requests
 * made by the proxy consumer in regulator_proxy_consumer_register() and then
 * frees the consumer's resources.
 */
int regulator_proxy_consumer_unregister(struct proxy_consumer *consumer)
{
	int rc = 0;

	if (IS_ERR_OR_NULL(consumer))
		return 0;

	mutex_lock(&proxy_consumer_list_mutex);
	if (!proxy_consumers_removed)
		rc = regulator_proxy_consumer_remove(consumer);
	mutex_unlock(&proxy_consumer_list_mutex);

	return rc;
}

/*
 * Remove all proxy requests at late_initcall_sync.  The assumption is that all
 * devices have probed at this point and made their own regulator requests.
 */
static int __init regulator_proxy_consumer_remove_all(void)
{
	struct proxy_consumer *consumer;
	struct proxy_consumer *temp;

	mutex_lock(&proxy_consumer_list_mutex);
	proxy_consumers_removed = true;

	if (!list_empty(&proxy_consumer_list))
		pr_info("removing regulator proxy consumer requests\n");

	list_for_each_entry_safe(consumer, temp, &proxy_consumer_list, list) {
		regulator_proxy_consumer_remove(consumer);
	}
	mutex_unlock(&proxy_consumer_list_mutex);

	return 0;
}
late_initcall_sync(regulator_proxy_consumer_remove_all);
+33 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright (c) 2013, The Linux Foundation. All rights reserved.
 */

#ifndef _LINUX_REGULATOR_PROXY_CONSUMER_H_
#define _LINUX_REGULATOR_PROXY_CONSUMER_H_

#include <linux/device.h>
#include <linux/of.h>

struct proxy_consumer;

#ifdef CONFIG_REGULATOR_PROXY_CONSUMER

struct proxy_consumer *regulator_proxy_consumer_register(struct device *reg_dev,
			struct device_node *reg_node);

int regulator_proxy_consumer_unregister(struct proxy_consumer *consumer);

#else

static inline struct proxy_consumer *regulator_proxy_consumer_register(
			struct device *reg_dev, struct device_node *reg_node)
{ return NULL; }

static inline int regulator_proxy_consumer_unregister(
			struct proxy_consumer *consumer)
{ return 0; }

#endif

#endif