878ca242eb
support frameworks(i.e. clk/reset/phy/tsensors/fuses...). The framework is still far from perfect and probably doesn't have stable interface yet, but we want to start testing it on more real boards and different architectures.
985 lines
21 KiB
C
985 lines
21 KiB
C
/*-
|
|
* Copyright 2016 Michal Meloun <mmel@FreeBSD.org>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include "opt_platform.h"
|
|
#include <sys/param.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/kobj.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/limits.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/sx.h>
|
|
|
|
#ifdef FDT
|
|
#include <dev/fdt/fdt_common.h>
|
|
#include <dev/ofw/ofw_bus.h>
|
|
#include <dev/ofw/ofw_bus_subr.h>
|
|
#endif
|
|
#include <dev/extres/regulator/regulator.h>
|
|
|
|
#include "regdev_if.h"
|
|
|
|
MALLOC_DEFINE(M_REGULATOR, "regulator", "Regulator framework");
|
|
|
|
/* Forward declarations. */
|
|
struct regulator;
|
|
struct regnode;
|
|
|
|
typedef TAILQ_HEAD(regnode_list, regnode) regnode_list_t;
|
|
typedef TAILQ_HEAD(regulator_list, regulator) regulator_list_t;
|
|
|
|
/* Default regulator methods. */
|
|
static int regnode_method_enable(struct regnode *regnode, bool enable,
|
|
int *udelay);
|
|
static int regnode_method_status(struct regnode *regnode, int *status);
|
|
static int regnode_method_set_voltage(struct regnode *regnode, int min_uvolt,
|
|
int max_uvolt, int *udelay);
|
|
static int regnode_method_get_voltage(struct regnode *regnode, int *uvolt);
|
|
|
|
/*
|
|
* Regulator controller methods.
|
|
*/
|
|
static regnode_method_t regnode_methods[] = {
|
|
REGNODEMETHOD(regnode_enable, regnode_method_enable),
|
|
REGNODEMETHOD(regnode_status, regnode_method_status),
|
|
REGNODEMETHOD(regnode_set_voltage, regnode_method_set_voltage),
|
|
REGNODEMETHOD(regnode_get_voltage, regnode_method_get_voltage),
|
|
|
|
REGNODEMETHOD_END
|
|
};
|
|
DEFINE_CLASS_0(regnode, regnode_class, regnode_methods, 0);
|
|
|
|
/*
|
|
* Regulator node - basic element for modelling SOC and bard power supply
|
|
* chains. Its contains producer data.
|
|
*/
|
|
struct regnode {
|
|
KOBJ_FIELDS;
|
|
|
|
TAILQ_ENTRY(regnode) reglist_link; /* Global list entry */
|
|
regulator_list_t consumers_list; /* Consumers list */
|
|
|
|
/* Cache for already resolved names */
|
|
struct regnode *parent; /* Resolved parent */
|
|
|
|
/* Details of this device. */
|
|
const char *name; /* Globally unique name */
|
|
const char *parent_name; /* Parent name */
|
|
|
|
device_t pdev; /* Producer device_t */
|
|
void *softc; /* Producer softc */
|
|
intptr_t id; /* Per producer unique id */
|
|
#ifdef FDT
|
|
phandle_t ofw_node; /* OFW node of regulator */
|
|
#endif
|
|
int flags; /* REGULATOR_FLAGS_ */
|
|
struct sx lock; /* Lock for this regulator */
|
|
int ref_cnt; /* Reference counter */
|
|
int enable_cnt; /* Enabled counter */
|
|
|
|
struct regnode_std_param std_param; /* Standard parameters */
|
|
};
|
|
|
|
/*
|
|
* Per consumer data, information about how a consumer is using a regulator
|
|
* node.
|
|
* A pointer to this structure is used as a handle in the consumer interface.
|
|
*/
|
|
struct regulator {
|
|
device_t cdev; /* Consumer device */
|
|
struct regnode *regnode;
|
|
TAILQ_ENTRY(regulator) link; /* Consumers list entry */
|
|
|
|
int enable_cnt;
|
|
int min_uvolt; /* Requested uvolt range */
|
|
int max_uvolt;
|
|
};
|
|
|
|
/*
|
|
* Regulator names must be system wide unique.
|
|
*/
|
|
static regnode_list_t regnode_list = TAILQ_HEAD_INITIALIZER(regnode_list);
|
|
|
|
static struct sx regnode_topo_lock;
|
|
SX_SYSINIT(regulator_topology, ®node_topo_lock, "Regulator topology lock");
|
|
|
|
#define REG_TOPO_SLOCK() sx_slock(®node_topo_lock)
|
|
#define REG_TOPO_XLOCK() sx_xlock(®node_topo_lock)
|
|
#define REG_TOPO_UNLOCK() sx_unlock(®node_topo_lock)
|
|
#define REG_TOPO_ASSERT() sx_assert(®node_topo_lock, SA_LOCKED)
|
|
#define REG_TOPO_XASSERT() sx_assert(®node_topo_lock, SA_XLOCKED)
|
|
|
|
#define REGNODE_SLOCK(_sc) sx_slock(&((_sc)->lock))
|
|
#define REGNODE_XLOCK(_sc) sx_xlock(&((_sc)->lock))
|
|
#define REGNODE_UNLOCK(_sc) sx_unlock(&((_sc)->lock))
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
*
|
|
* Default regulator methods for base class.
|
|
*
|
|
*/
|
|
static int
|
|
regnode_method_enable(struct regnode *regnode, bool enable, int *udelay)
|
|
{
|
|
|
|
if (!enable)
|
|
return (ENXIO);
|
|
|
|
*udelay = 0;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
regnode_method_status(struct regnode *regnode, int *status)
|
|
{
|
|
*status = REGULATOR_STATUS_ENABLED;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
regnode_method_set_voltage(struct regnode *regnode, int min_uvolt, int max_uvolt,
|
|
int *udelay)
|
|
{
|
|
|
|
if ((min_uvolt > regnode->std_param.max_uvolt) ||
|
|
(max_uvolt < regnode->std_param.min_uvolt))
|
|
return (ERANGE);
|
|
*udelay = 0;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
regnode_method_get_voltage(struct regnode *regnode, int *uvolt)
|
|
{
|
|
|
|
return (regnode->std_param.min_uvolt +
|
|
(regnode->std_param.max_uvolt - regnode->std_param.min_uvolt) / 2);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
*
|
|
* Internal functions.
|
|
*
|
|
*/
|
|
|
|
static struct regnode *
|
|
regnode_find_by_name(const char *name)
|
|
{
|
|
struct regnode *entry;
|
|
|
|
REG_TOPO_ASSERT();
|
|
|
|
TAILQ_FOREACH(entry, ®node_list, reglist_link) {
|
|
if (strcmp(entry->name, name) == 0)
|
|
return (entry);
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
static struct regnode *
|
|
regnode_find_by_id(device_t dev, intptr_t id)
|
|
{
|
|
struct regnode *entry;
|
|
|
|
REG_TOPO_ASSERT();
|
|
|
|
TAILQ_FOREACH(entry, ®node_list, reglist_link) {
|
|
if ((entry->pdev == dev) && (entry->id == id))
|
|
return (entry);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Create and initialize regulator object, but do not register it.
|
|
*/
|
|
struct regnode *
|
|
regnode_create(device_t pdev, regnode_class_t regnode_class,
|
|
struct regnode_init_def *def)
|
|
{
|
|
struct regnode *regnode;
|
|
|
|
KASSERT(def->name != NULL, ("regulator name is NULL"));
|
|
KASSERT(def->name[0] != '\0', ("regulator name is empty"));
|
|
|
|
REG_TOPO_SLOCK();
|
|
if (regnode_find_by_name(def->name) != NULL)
|
|
panic("Duplicated regulator registration: %s\n", def->name);
|
|
REG_TOPO_UNLOCK();
|
|
|
|
/* Create object and initialize it. */
|
|
regnode = malloc(sizeof(struct regnode), M_REGULATOR,
|
|
M_WAITOK | M_ZERO);
|
|
kobj_init((kobj_t)regnode, (kobj_class_t)regnode_class);
|
|
sx_init(®node->lock, "Regulator node lock");
|
|
|
|
/* Allocate softc if required. */
|
|
if (regnode_class->size > 0) {
|
|
regnode->softc = malloc(regnode_class->size, M_REGULATOR,
|
|
M_WAITOK | M_ZERO);
|
|
}
|
|
|
|
|
|
/* Copy all strings unless they're flagged as static. */
|
|
if (def->flags & REGULATOR_FLAGS_STATIC) {
|
|
regnode->name = def->name;
|
|
regnode->parent_name = def->parent_name;
|
|
} else {
|
|
regnode->name = strdup(def->name, M_REGULATOR);
|
|
if (def->parent_name != NULL)
|
|
regnode->parent_name = strdup(def->parent_name,
|
|
M_REGULATOR);
|
|
}
|
|
|
|
/* Rest of init. */
|
|
TAILQ_INIT(®node->consumers_list);
|
|
regnode->id = def->id;
|
|
regnode->pdev = pdev;
|
|
regnode->flags = def->flags;
|
|
regnode->parent = NULL;
|
|
regnode->std_param = def->std_param;
|
|
#ifdef FDT
|
|
regnode->ofw_node = def->ofw_node;
|
|
#endif
|
|
|
|
return (regnode);
|
|
}
|
|
|
|
/* Register regulator object. */
|
|
struct regnode *
|
|
regnode_register(struct regnode *regnode)
|
|
{
|
|
int rv;
|
|
|
|
#ifdef FDT
|
|
if (regnode->ofw_node <= 0)
|
|
regnode->ofw_node = ofw_bus_get_node(regnode->pdev);
|
|
if (regnode->ofw_node <= 0)
|
|
return (NULL);
|
|
#endif
|
|
|
|
rv = REGNODE_INIT(regnode);
|
|
if (rv != 0) {
|
|
printf("REGNODE_INIT failed: %d\n", rv);
|
|
return (NULL);
|
|
}
|
|
|
|
REG_TOPO_XLOCK();
|
|
TAILQ_INSERT_TAIL(®node_list, regnode, reglist_link);
|
|
REG_TOPO_UNLOCK();
|
|
#ifdef FDT
|
|
OF_device_register_xref(OF_xref_from_node(regnode->ofw_node),
|
|
regnode->pdev);
|
|
#endif
|
|
return (regnode);
|
|
}
|
|
|
|
static int
|
|
regnode_resolve_parent(struct regnode *regnode)
|
|
{
|
|
|
|
/* All ready resolved or no parent? */
|
|
if ((regnode->parent != NULL) ||
|
|
(regnode->parent_name == NULL))
|
|
return (0);
|
|
|
|
regnode->parent = regnode_find_by_name(regnode->parent_name);
|
|
if (regnode->parent == NULL)
|
|
return (ENODEV);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
regnode_delay(int usec)
|
|
{
|
|
int ticks;
|
|
|
|
if (usec == 0)
|
|
return;
|
|
ticks = (usec * hz + 999999) / 1000000;
|
|
|
|
if (cold || ticks < 2)
|
|
DELAY(usec);
|
|
else
|
|
pause("REGULATOR", ticks);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------
|
|
*
|
|
* Regulator providers interface
|
|
*
|
|
*/
|
|
|
|
const char *
|
|
regnode_get_name(struct regnode *regnode)
|
|
{
|
|
|
|
return (regnode->name);
|
|
}
|
|
|
|
const char *
|
|
regnode_get_parent_name(struct regnode *regnode)
|
|
{
|
|
|
|
return (regnode->parent_name);
|
|
}
|
|
|
|
int
|
|
regnode_get_flags(struct regnode *regnode)
|
|
{
|
|
|
|
return (regnode->flags);
|
|
}
|
|
|
|
void *
|
|
regnode_get_softc(struct regnode *regnode)
|
|
{
|
|
|
|
return (regnode->softc);
|
|
}
|
|
|
|
device_t
|
|
regnode_get_device(struct regnode *regnode)
|
|
{
|
|
|
|
return (regnode->pdev);
|
|
}
|
|
|
|
struct regnode_std_param *regnode_get_stdparam(struct regnode *regnode)
|
|
{
|
|
|
|
return (®node->std_param);
|
|
}
|
|
|
|
void regnode_topo_unlock(void)
|
|
{
|
|
|
|
REG_TOPO_UNLOCK();
|
|
}
|
|
|
|
void regnode_topo_xlock(void)
|
|
{
|
|
|
|
REG_TOPO_XLOCK();
|
|
}
|
|
|
|
void regnode_topo_slock(void)
|
|
{
|
|
|
|
REG_TOPO_SLOCK();
|
|
}
|
|
|
|
|
|
/* --------------------------------------------------------------------------
|
|
*
|
|
* Real consumers executive
|
|
*
|
|
*/
|
|
struct regnode *
|
|
regnode_get_parent(struct regnode *regnode)
|
|
{
|
|
int rv;
|
|
|
|
REG_TOPO_ASSERT();
|
|
|
|
rv = regnode_resolve_parent(regnode);
|
|
if (rv != 0)
|
|
return (NULL);
|
|
|
|
return (regnode->parent);
|
|
}
|
|
|
|
/*
|
|
* Enable regulator.
|
|
*/
|
|
int
|
|
regnode_enable(struct regnode *regnode)
|
|
{
|
|
int udelay;
|
|
int rv;
|
|
|
|
REG_TOPO_ASSERT();
|
|
|
|
/* Enable regulator for each node in chain, starting from source. */
|
|
rv = regnode_resolve_parent(regnode);
|
|
if (rv != 0)
|
|
return (rv);
|
|
if (regnode->parent != NULL) {
|
|
rv = regnode_enable(regnode->parent);
|
|
if (rv != 0)
|
|
return (rv);
|
|
}
|
|
|
|
/* Handle this node. */
|
|
REGNODE_XLOCK(regnode);
|
|
if (regnode->enable_cnt == 0) {
|
|
rv = REGNODE_ENABLE(regnode, true, &udelay);
|
|
if (rv != 0) {
|
|
REGNODE_UNLOCK(regnode);
|
|
return (rv);
|
|
}
|
|
regnode_delay(udelay);
|
|
}
|
|
regnode->enable_cnt++;
|
|
REGNODE_UNLOCK(regnode);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Disable regulator.
|
|
*/
|
|
int
|
|
regnode_disable(struct regnode *regnode)
|
|
{
|
|
int udelay;
|
|
int rv;
|
|
|
|
REG_TOPO_ASSERT();
|
|
rv = 0;
|
|
|
|
REGNODE_XLOCK(regnode);
|
|
/* Disable regulator for each node in chain, starting from consumer. */
|
|
if ((regnode->enable_cnt == 1) &&
|
|
((regnode->flags & REGULATOR_FLAGS_NOT_DISABLE) == 0)) {
|
|
rv = REGNODE_ENABLE(regnode, false, &udelay);
|
|
if (rv != 0) {
|
|
REGNODE_UNLOCK(regnode);
|
|
return (rv);
|
|
}
|
|
regnode_delay(udelay);
|
|
}
|
|
regnode->enable_cnt--;
|
|
REGNODE_UNLOCK(regnode);
|
|
|
|
rv = regnode_resolve_parent(regnode);
|
|
if (rv != 0)
|
|
return (rv);
|
|
if (regnode->parent != NULL)
|
|
rv = regnode_disable(regnode->parent);
|
|
return (rv);
|
|
}
|
|
|
|
/*
|
|
* Stop regulator.
|
|
*/
|
|
int
|
|
regnode_stop(struct regnode *regnode, int depth)
|
|
{
|
|
int udelay;
|
|
int rv;
|
|
|
|
REG_TOPO_ASSERT();
|
|
rv = 0;
|
|
|
|
REGNODE_XLOCK(regnode);
|
|
/* The first node must not be enabled. */
|
|
if ((regnode->enable_cnt != 0) && (depth == 0)) {
|
|
REGNODE_UNLOCK(regnode);
|
|
return (EBUSY);
|
|
}
|
|
/* Disable regulator for each node in chain, starting from consumer */
|
|
if ((regnode->enable_cnt == 0) &&
|
|
((regnode->flags & REGULATOR_FLAGS_NOT_DISABLE) == 0)) {
|
|
rv = REGNODE_ENABLE(regnode, false, &udelay);
|
|
if (rv != 0) {
|
|
REGNODE_UNLOCK(regnode);
|
|
return (rv);
|
|
}
|
|
regnode_delay(udelay);
|
|
}
|
|
REGNODE_UNLOCK(regnode);
|
|
|
|
rv = regnode_resolve_parent(regnode);
|
|
if (rv != 0)
|
|
return (rv);
|
|
if (regnode->parent != NULL)
|
|
rv = regnode_stop(regnode->parent, depth + 1);
|
|
return (rv);
|
|
}
|
|
|
|
/*
|
|
* Get regulator status. (REGULATOR_STATUS_*)
|
|
*/
|
|
int
|
|
regnode_status(struct regnode *regnode, int *status)
|
|
{
|
|
int rv;
|
|
|
|
REG_TOPO_ASSERT();
|
|
|
|
REGNODE_XLOCK(regnode);
|
|
rv = REGNODE_STATUS(regnode, status);
|
|
REGNODE_UNLOCK(regnode);
|
|
return (rv);
|
|
}
|
|
|
|
/*
|
|
* Get actual regulator voltage.
|
|
*/
|
|
int
|
|
regnode_get_voltage(struct regnode *regnode, int *uvolt)
|
|
{
|
|
int rv;
|
|
|
|
REG_TOPO_ASSERT();
|
|
|
|
REGNODE_XLOCK(regnode);
|
|
rv = REGNODE_GET_VOLTAGE(regnode, uvolt);
|
|
REGNODE_UNLOCK(regnode);
|
|
|
|
/* Pass call into parent, if regulator is in bypass mode. */
|
|
if (rv == ENOENT) {
|
|
rv = regnode_resolve_parent(regnode);
|
|
if (rv != 0)
|
|
return (rv);
|
|
if (regnode->parent != NULL)
|
|
rv = regnode_get_voltage(regnode->parent, uvolt);
|
|
|
|
}
|
|
return (rv);
|
|
}
|
|
|
|
/*
|
|
* Set regulator voltage.
|
|
*/
|
|
int
|
|
regnode_set_voltage(struct regnode *regnode, int min_uvolt, int max_uvolt)
|
|
{
|
|
int udelay;
|
|
int rv;
|
|
|
|
REG_TOPO_ASSERT();
|
|
|
|
REGNODE_XLOCK(regnode);
|
|
|
|
rv = REGNODE_SET_VOLTAGE(regnode, min_uvolt, max_uvolt, &udelay);
|
|
if (rv == 0)
|
|
regnode_delay(udelay);
|
|
REGNODE_UNLOCK(regnode);
|
|
return (rv);
|
|
}
|
|
|
|
/*
|
|
* Consumer variant of regnode_set_voltage().
|
|
*/
|
|
static int
|
|
regnode_set_voltage_checked(struct regnode *regnode, struct regulator *reg,
|
|
int min_uvolt, int max_uvolt)
|
|
{
|
|
int udelay;
|
|
int all_max_uvolt;
|
|
int all_min_uvolt;
|
|
struct regulator *tmp;
|
|
int rv;
|
|
|
|
REG_TOPO_ASSERT();
|
|
|
|
REGNODE_XLOCK(regnode);
|
|
/* Return error if requested range is outside of regulator range. */
|
|
if ((min_uvolt > regnode->std_param.max_uvolt) ||
|
|
(max_uvolt < regnode->std_param.min_uvolt)) {
|
|
REGNODE_UNLOCK(regnode);
|
|
return (ERANGE);
|
|
}
|
|
|
|
/* Get actual voltage range for all consumers. */
|
|
all_min_uvolt = regnode->std_param.min_uvolt;
|
|
all_max_uvolt = regnode->std_param.max_uvolt;
|
|
TAILQ_FOREACH(tmp, ®node->consumers_list, link) {
|
|
/* Don't take requestor in account. */
|
|
if (tmp == reg)
|
|
continue;
|
|
if (all_min_uvolt < tmp->min_uvolt)
|
|
all_min_uvolt = tmp->min_uvolt;
|
|
if (all_max_uvolt > tmp->max_uvolt)
|
|
all_max_uvolt = tmp->max_uvolt;
|
|
}
|
|
|
|
/* Test if request fits to actual contract. */
|
|
if ((min_uvolt > all_max_uvolt) ||
|
|
(max_uvolt < all_min_uvolt)) {
|
|
REGNODE_UNLOCK(regnode);
|
|
return (ERANGE);
|
|
}
|
|
|
|
/* Adjust new range.*/
|
|
if (min_uvolt < all_min_uvolt)
|
|
min_uvolt = all_min_uvolt;
|
|
if (max_uvolt > all_max_uvolt)
|
|
max_uvolt = all_max_uvolt;
|
|
|
|
rv = REGNODE_SET_VOLTAGE(regnode, min_uvolt, max_uvolt, &udelay);
|
|
regnode_delay(udelay);
|
|
REGNODE_UNLOCK(regnode);
|
|
return (rv);
|
|
}
|
|
|
|
#ifdef FDT
|
|
phandle_t
|
|
regnode_get_ofw_node(struct regnode *regnode)
|
|
{
|
|
|
|
return (regnode->ofw_node);
|
|
}
|
|
#endif
|
|
|
|
/* --------------------------------------------------------------------------
|
|
*
|
|
* Regulator consumers interface.
|
|
*
|
|
*/
|
|
/* Helper function for regulator_get*() */
|
|
static regulator_t
|
|
regulator_create(struct regnode *regnode, device_t cdev)
|
|
{
|
|
struct regulator *reg;
|
|
|
|
REG_TOPO_ASSERT();
|
|
|
|
reg = malloc(sizeof(struct regulator), M_REGULATOR,
|
|
M_WAITOK | M_ZERO);
|
|
reg->cdev = cdev;
|
|
reg->regnode = regnode;
|
|
reg->enable_cnt = 0;
|
|
|
|
REGNODE_XLOCK(regnode);
|
|
regnode->ref_cnt++;
|
|
TAILQ_INSERT_TAIL(®node->consumers_list, reg, link);
|
|
reg ->min_uvolt = regnode->std_param.min_uvolt;
|
|
reg ->max_uvolt = regnode->std_param.max_uvolt;
|
|
REGNODE_UNLOCK(regnode);
|
|
|
|
return (reg);
|
|
}
|
|
|
|
int
|
|
regulator_enable(regulator_t reg)
|
|
{
|
|
int rv;
|
|
struct regnode *regnode;
|
|
|
|
regnode = reg->regnode;
|
|
KASSERT(regnode->ref_cnt > 0,
|
|
("Attempt to access unreferenced regulator: %s\n", regnode->name));
|
|
REG_TOPO_SLOCK();
|
|
rv = regnode_enable(regnode);
|
|
if (rv == 0)
|
|
reg->enable_cnt++;
|
|
REG_TOPO_UNLOCK();
|
|
return (rv);
|
|
}
|
|
|
|
int
|
|
regulator_disable(regulator_t reg)
|
|
{
|
|
int rv;
|
|
struct regnode *regnode;
|
|
|
|
regnode = reg->regnode;
|
|
KASSERT(regnode->ref_cnt > 0,
|
|
("Attempt to access unreferenced regulator: %s\n", regnode->name));
|
|
KASSERT(reg->enable_cnt > 0,
|
|
("Attempt to disable already disabled regulator: %s\n",
|
|
regnode->name));
|
|
REG_TOPO_SLOCK();
|
|
rv = regnode_disable(regnode);
|
|
if (rv == 0)
|
|
reg->enable_cnt--;
|
|
REG_TOPO_UNLOCK();
|
|
return (rv);
|
|
}
|
|
|
|
int
|
|
regulator_stop(regulator_t reg)
|
|
{
|
|
int rv;
|
|
struct regnode *regnode;
|
|
|
|
regnode = reg->regnode;
|
|
KASSERT(regnode->ref_cnt > 0,
|
|
("Attempt to access unreferenced regulator: %s\n", regnode->name));
|
|
KASSERT(reg->enable_cnt == 0,
|
|
("Attempt to stop already enabled regulator: %s\n", regnode->name));
|
|
|
|
REG_TOPO_SLOCK();
|
|
rv = regnode_stop(regnode, 0);
|
|
REG_TOPO_UNLOCK();
|
|
return (rv);
|
|
}
|
|
|
|
int
|
|
regulator_status(regulator_t reg, int *status)
|
|
{
|
|
int rv;
|
|
struct regnode *regnode;
|
|
|
|
regnode = reg->regnode;
|
|
KASSERT(regnode->ref_cnt > 0,
|
|
("Attempt to access unreferenced regulator: %s\n", regnode->name));
|
|
|
|
REG_TOPO_SLOCK();
|
|
rv = regnode_status(regnode, status);
|
|
REG_TOPO_UNLOCK();
|
|
return (rv);
|
|
}
|
|
|
|
int
|
|
regulator_get_voltage(regulator_t reg, int *uvolt)
|
|
{
|
|
int rv;
|
|
struct regnode *regnode;
|
|
|
|
regnode = reg->regnode;
|
|
KASSERT(regnode->ref_cnt > 0,
|
|
("Attempt to access unreferenced regulator: %s\n", regnode->name));
|
|
|
|
REG_TOPO_SLOCK();
|
|
rv = regnode_get_voltage(regnode, uvolt);
|
|
REG_TOPO_UNLOCK();
|
|
return (rv);
|
|
}
|
|
|
|
int
|
|
regulator_set_voltage(regulator_t reg, int min_uvolt, int max_uvolt)
|
|
{
|
|
struct regnode *regnode;
|
|
int rv;
|
|
|
|
regnode = reg->regnode;
|
|
KASSERT(regnode->ref_cnt > 0,
|
|
("Attempt to access unreferenced regulator: %s\n", regnode->name));
|
|
|
|
REG_TOPO_SLOCK();
|
|
|
|
rv = regnode_set_voltage_checked(regnode, reg, min_uvolt, max_uvolt);
|
|
if (rv == 0) {
|
|
reg->min_uvolt = min_uvolt;
|
|
reg->max_uvolt = max_uvolt;
|
|
}
|
|
REG_TOPO_UNLOCK();
|
|
return (rv);
|
|
}
|
|
|
|
const char *
|
|
regulator_get_name(regulator_t reg)
|
|
{
|
|
struct regnode *regnode;
|
|
|
|
regnode = reg->regnode;
|
|
KASSERT(regnode->ref_cnt > 0,
|
|
("Attempt to access unreferenced regulator: %s\n", regnode->name));
|
|
return (regnode->name);
|
|
}
|
|
|
|
int
|
|
regulator_get_by_name(device_t cdev, const char *name, regulator_t *reg)
|
|
{
|
|
struct regnode *regnode;
|
|
|
|
REG_TOPO_SLOCK();
|
|
regnode = regnode_find_by_name(name);
|
|
if (regnode == NULL) {
|
|
REG_TOPO_UNLOCK();
|
|
return (ENODEV);
|
|
}
|
|
*reg = regulator_create(regnode, cdev);
|
|
REG_TOPO_UNLOCK();
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
regulator_get_by_id(device_t cdev, device_t pdev, intptr_t id, regulator_t *reg)
|
|
{
|
|
struct regnode *regnode;
|
|
|
|
REG_TOPO_SLOCK();
|
|
|
|
regnode = regnode_find_by_id(pdev, id);
|
|
if (regnode == NULL) {
|
|
REG_TOPO_UNLOCK();
|
|
return (ENODEV);
|
|
}
|
|
*reg = regulator_create(regnode, cdev);
|
|
REG_TOPO_UNLOCK();
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
regulator_release(regulator_t reg)
|
|
{
|
|
struct regnode *regnode;
|
|
|
|
regnode = reg->regnode;
|
|
KASSERT(regnode->ref_cnt > 0,
|
|
("Attempt to access unreferenced regulator: %s\n", regnode->name));
|
|
REG_TOPO_SLOCK();
|
|
while (reg->enable_cnt > 0) {
|
|
regnode_disable(regnode);
|
|
reg->enable_cnt--;
|
|
}
|
|
REGNODE_XLOCK(regnode);
|
|
TAILQ_REMOVE(®node->consumers_list, reg, link);
|
|
regnode->ref_cnt--;
|
|
REGNODE_UNLOCK(regnode);
|
|
REG_TOPO_UNLOCK();
|
|
|
|
free(reg, M_REGULATOR);
|
|
return (0);
|
|
}
|
|
|
|
#ifdef FDT
|
|
/* Default DT mapper. */
|
|
int
|
|
regdev_default_ofw_map(device_t dev, phandle_t xref, int ncells,
|
|
pcell_t *cells, intptr_t *id)
|
|
{
|
|
if (ncells == 0)
|
|
*id = 1;
|
|
else if (ncells == 1)
|
|
*id = cells[0];
|
|
else
|
|
return (ERANGE);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
regulator_parse_ofw_stdparam(device_t pdev, phandle_t node,
|
|
struct regnode_init_def *def)
|
|
{
|
|
phandle_t supply_xref;
|
|
struct regnode_std_param *par;
|
|
int rv;
|
|
|
|
par = &def->std_param;
|
|
rv = OF_getprop_alloc(node, "regulator-name", 1,
|
|
(void **)&def->name);
|
|
if (rv <= 0) {
|
|
device_printf(pdev, "%s: Missing regulator name\n",
|
|
__func__);
|
|
return (ENXIO);
|
|
}
|
|
|
|
rv = OF_getencprop(node, "regulator-min-microvolt", &par->min_uvolt,
|
|
sizeof(par->min_uvolt));
|
|
if (rv <= 0)
|
|
par->min_uvolt = 0;
|
|
|
|
rv = OF_getencprop(node, "regulator-max-microvolt", &par->max_uvolt,
|
|
sizeof(par->max_uvolt));
|
|
if (rv <= 0)
|
|
par->max_uvolt = 0;
|
|
|
|
rv = OF_getencprop(node, "regulator-min-microamp", &par->min_uamp,
|
|
sizeof(par->min_uamp));
|
|
if (rv <= 0)
|
|
par->min_uamp = 0;
|
|
|
|
rv = OF_getencprop(node, "regulator-max-microamp", &par->max_uamp,
|
|
sizeof(par->max_uamp));
|
|
if (rv <= 0)
|
|
par->max_uamp = 0;
|
|
|
|
rv = OF_getencprop(node, "regulator-ramp-delay", &par->ramp_delay,
|
|
sizeof(par->ramp_delay));
|
|
if (rv <= 0)
|
|
par->ramp_delay = 0;
|
|
|
|
rv = OF_getencprop(node, "regulator-enable-ramp-delay",
|
|
&par->enable_delay, sizeof(par->enable_delay));
|
|
if (rv <= 0)
|
|
par->enable_delay = 0;
|
|
|
|
if (OF_hasprop(node, "regulator-boot-on"))
|
|
par->boot_on = 1;
|
|
|
|
if (OF_hasprop(node, "regulator-always-on"))
|
|
par->always_on = 1;
|
|
|
|
if (OF_hasprop(node, "enable-active-high"))
|
|
par->enable_active_high = 1;
|
|
|
|
rv = OF_getencprop(node, "vin-supply", &supply_xref,
|
|
sizeof(supply_xref));
|
|
if (rv >= 0) {
|
|
rv = OF_getprop_alloc(supply_xref, "regulator-name", 1,
|
|
(void **)&def->parent_name);
|
|
if (rv <= 0)
|
|
def->parent_name = NULL;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
regulator_get_by_ofw_property(device_t cdev, char *name, regulator_t *reg)
|
|
{
|
|
phandle_t cnode, *cells;
|
|
device_t regdev;
|
|
int ncells, rv;
|
|
intptr_t id;
|
|
|
|
*reg = NULL;
|
|
|
|
cnode = ofw_bus_get_node(cdev);
|
|
if (cnode <= 0) {
|
|
device_printf(cdev, "%s called on not ofw based device\n",
|
|
__func__);
|
|
return (ENXIO);
|
|
}
|
|
|
|
cells = NULL;
|
|
ncells = OF_getencprop_alloc(cnode, name, sizeof(*cells),
|
|
(void **)&cells);
|
|
if (ncells <= 0)
|
|
return (ENXIO);
|
|
|
|
/* Translate xref to device */
|
|
regdev = OF_device_from_xref(cells[0]);
|
|
if (regdev == NULL) {
|
|
free(cells, M_OFWPROP);
|
|
return (ENODEV);
|
|
}
|
|
|
|
/* Map regulator to number */
|
|
rv = REGDEV_MAP(regdev, cells[0], ncells - 1, cells + 1, &id);
|
|
free(cells, M_OFWPROP);
|
|
if (rv != 0)
|
|
return (rv);
|
|
return (regulator_get_by_id(cdev, regdev, id, reg));
|
|
}
|
|
#endif
|