From 404ad60f5f389d337c616699bc060a3c0742f243 Mon Sep 17 00:00:00 2001 From: Christian Mauderer Date: Thu, 2 Apr 2020 18:04:17 +0200 Subject: regulator: Import from FreeBSD. Update #3869. --- freebsd/sys/dev/extres/regulator/regulator.c | 1321 ++++++++++++++++++++ freebsd/sys/dev/extres/regulator/regulator.h | 156 +++ freebsd/sys/dev/extres/regulator/regulator_bus.c | 89 ++ freebsd/sys/dev/extres/regulator/regulator_fixed.c | 502 ++++++++ freebsd/sys/dev/extres/regulator/regulator_fixed.h | 44 + freebsd/sys/dev/gpio/gpioregulator.c | 350 ++++++ 6 files changed, 2462 insertions(+) create mode 100644 freebsd/sys/dev/extres/regulator/regulator.c create mode 100644 freebsd/sys/dev/extres/regulator/regulator.h create mode 100644 freebsd/sys/dev/extres/regulator/regulator_bus.c create mode 100644 freebsd/sys/dev/extres/regulator/regulator_fixed.c create mode 100644 freebsd/sys/dev/extres/regulator/regulator_fixed.h create mode 100644 freebsd/sys/dev/gpio/gpioregulator.c diff --git a/freebsd/sys/dev/extres/regulator/regulator.c b/freebsd/sys/dev/extres/regulator/regulator.c new file mode 100644 index 00000000..4ad6e94a --- /dev/null +++ b/freebsd/sys/dev/extres/regulator/regulator.c @@ -0,0 +1,1321 @@ +#include + +/*- + * Copyright 2016 Michal Meloun + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef FDT +#include +#include +#include +#endif +#include + +#include + +SYSCTL_NODE(_hw, OID_AUTO, regulator, CTLFLAG_RD, NULL, "Regulators"); + +MALLOC_DEFINE(M_REGULATOR, "regulator", "Regulator framework"); + +#define DIV_ROUND_UP(n,d) howmany(n, d) + +/* 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_init(struct regnode *regnode); +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); +static void regulator_constraint(void *dummy); +static void regulator_shutdown(void *dummy); + +/* + * Regulator controller methods. + */ +static regnode_method_t regnode_methods[] = { + REGNODEMETHOD(regnode_init, regnode_method_init), + 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(regnode_check_voltage, regnode_method_check_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 */ + + struct sysctl_ctx_list sysctl_ctx; +}; + +/* + * 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)) + +SYSINIT(regulator_constraint, SI_SUB_LAST, SI_ORDER_ANY, regulator_constraint, + NULL); +SYSINIT(regulator_shutdown, SI_SUB_LAST, SI_ORDER_ANY, regulator_shutdown, + NULL); + +static void +regulator_constraint(void *dummy) +{ + struct regnode *entry; + int rv; + + REG_TOPO_SLOCK(); + TAILQ_FOREACH(entry, ®node_list, reglist_link) { + rv = regnode_set_constraint(entry); + if (rv != 0 && bootverbose) + printf("regulator: setting constraint on %s failed (%d)\n", + entry->name, rv); + } + REG_TOPO_UNLOCK(); +} + +/* + * Disable unused regulator + * We run this function at SI_SUB_LAST which mean that every driver that needs + * regulator should have already enable them. + * All the remaining regulators should be those left enabled by the bootloader + * or enable by default by the PMIC. + */ +static void +regulator_shutdown(void *dummy) +{ + struct regnode *entry; + int status, ret; + int disable = 1; + + TUNABLE_INT_FETCH("hw.regulator.disable_unused", &disable); + if (!disable) + return; + REG_TOPO_SLOCK(); + + if (bootverbose) + printf("regulator: shutting down unused regulators\n"); + TAILQ_FOREACH(entry, ®node_list, reglist_link) { + if (!entry->std_param.always_on) { + ret = regnode_status(entry, &status); + if (ret == 0 && status == REGULATOR_STATUS_ENABLED) { + if (bootverbose) + printf("regulator: shutting down %s... ", + entry->name); + ret = regnode_stop(entry, 0); + if (bootverbose) { + /* + * Call out busy in particular, here, + * because it's not unexpected to fail + * shutdown if the regulator is simply + * in-use. + */ + if (ret == EBUSY) + printf("busy\n"); + else if (ret != 0) + printf("error (%d)\n", ret); + else + printf("ok\n"); + } + } + } + } + REG_TOPO_UNLOCK(); +} + +/* + * sysctl handler + */ +static int +regnode_uvolt_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct regnode *regnode = arg1; + int rv, uvolt; + + if (regnode->std_param.min_uvolt == regnode->std_param.max_uvolt) { + uvolt = regnode->std_param.min_uvolt; + } else { + REG_TOPO_SLOCK(); + if ((rv = regnode_get_voltage(regnode, &uvolt)) != 0) { + REG_TOPO_UNLOCK(); + return (rv); + } + REG_TOPO_UNLOCK(); + } + + return sysctl_handle_int(oidp, &uvolt, sizeof(uvolt), req); +} + +/* ---------------------------------------------------------------------------- + * + * Default regulator methods for base class. + * + */ +static int +regnode_method_init(struct regnode *regnode) +{ + + return (0); +} + +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); +} + +int +regnode_method_check_voltage(struct regnode *regnode, int uvolt) +{ + + if ((uvolt > regnode->std_param.max_uvolt) || + (uvolt < regnode->std_param.min_uvolt)) + return (ERANGE); + return (0); +} + +/* ---------------------------------------------------------------------------- + * + * 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; + struct sysctl_oid *regnode_oid; + + 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 + + sysctl_ctx_init(®node->sysctl_ctx); + regnode_oid = SYSCTL_ADD_NODE(®node->sysctl_ctx, + SYSCTL_STATIC_CHILDREN(_hw_regulator), + OID_AUTO, regnode->name, + CTLFLAG_RD, 0, "A regulator node"); + + SYSCTL_ADD_INT(®node->sysctl_ctx, + SYSCTL_CHILDREN(regnode_oid), + OID_AUTO, "min_uvolt", + CTLFLAG_RD, ®node->std_param.min_uvolt, 0, + "Minimal voltage (in uV)"); + SYSCTL_ADD_INT(®node->sysctl_ctx, + SYSCTL_CHILDREN(regnode_oid), + OID_AUTO, "max_uvolt", + CTLFLAG_RD, ®node->std_param.max_uvolt, 0, + "Maximal voltage (in uV)"); + SYSCTL_ADD_INT(®node->sysctl_ctx, + SYSCTL_CHILDREN(regnode_oid), + OID_AUTO, "min_uamp", + CTLFLAG_RD, ®node->std_param.min_uamp, 0, + "Minimal amperage (in uA)"); + SYSCTL_ADD_INT(®node->sysctl_ctx, + SYSCTL_CHILDREN(regnode_oid), + OID_AUTO, "max_uamp", + CTLFLAG_RD, ®node->std_param.max_uamp, 0, + "Maximal amperage (in uA)"); + SYSCTL_ADD_INT(®node->sysctl_ctx, + SYSCTL_CHILDREN(regnode_oid), + OID_AUTO, "ramp_delay", + CTLFLAG_RD, ®node->std_param.ramp_delay, 0, + "Ramp delay (in uV/us)"); + SYSCTL_ADD_INT(®node->sysctl_ctx, + SYSCTL_CHILDREN(regnode_oid), + OID_AUTO, "enable_delay", + CTLFLAG_RD, ®node->std_param.enable_delay, 0, + "Enable delay (in us)"); + SYSCTL_ADD_INT(®node->sysctl_ctx, + SYSCTL_CHILDREN(regnode_oid), + OID_AUTO, "enable_cnt", + CTLFLAG_RD, ®node->enable_cnt, 0, + "The regulator enable counter"); + SYSCTL_ADD_U8(®node->sysctl_ctx, + SYSCTL_CHILDREN(regnode_oid), + OID_AUTO, "boot_on", + CTLFLAG_RD, (uint8_t *) ®node->std_param.boot_on, 0, + "Is enabled on boot"); + SYSCTL_ADD_U8(®node->sysctl_ctx, + SYSCTL_CHILDREN(regnode_oid), + OID_AUTO, "always_on", + CTLFLAG_RD, (uint8_t *)®node->std_param.always_on, 0, + "Is always enabled"); + + SYSCTL_ADD_PROC(®node->sysctl_ctx, + SYSCTL_CHILDREN(regnode_oid), + OID_AUTO, "uvolt", + CTLTYPE_INT | CTLFLAG_RD, + regnode, 0, regnode_uvolt_sysctl, + "I", + "Current voltage (in uV)"); + + 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 && + !regnode->std_param.always_on) { + 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_STOP(regnode, &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 && regnode->parent->enable_cnt == 0) + 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); +} + +int +regnode_set_constraint(struct regnode *regnode) +{ + int status, rv, uvolt; + + if (regnode->std_param.boot_on != true && + regnode->std_param.always_on != true) + return (0); + + rv = regnode_status(regnode, &status); + if (rv != 0) { + if (bootverbose) + printf("Cannot get regulator status for %s\n", + regnode_get_name(regnode)); + return (rv); + } + + if (status == REGULATOR_STATUS_ENABLED) + return (0); + + rv = regnode_get_voltage(regnode, &uvolt); + if (rv != 0) { + if (bootverbose) + printf("Cannot get regulator voltage for %s\n", + regnode_get_name(regnode)); + return (rv); + } + + if (uvolt < regnode->std_param.min_uvolt || + uvolt > regnode->std_param.max_uvolt) { + if (bootverbose) + printf("Regulator %s current voltage %d is not in the" + " acceptable range : %d<->%d\n", + regnode_get_name(regnode), + uvolt, regnode->std_param.min_uvolt, + regnode->std_param.max_uvolt); + return (ERANGE); + } + + rv = regnode_enable(regnode); + if (rv != 0) { + if (bootverbose) + printf("Cannot enable regulator %s\n", + regnode_get_name(regnode)); + return (rv); + } + + return (0); +} + +#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); +} + +int +regulator_check_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_CHECK_VOLTAGE(regnode, 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", + (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 = true; + + if (OF_hasprop(node, "regulator-always-on")) + par->always_on = true; + + 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", + (void **)&def->parent_name); + if (rv <= 0) + def->parent_name = NULL; + } + return (0); +} + +int +regulator_get_by_ofw_property(device_t cdev, phandle_t cnode, char *name, + regulator_t *reg) +{ + phandle_t *cells; + device_t regdev; + int ncells, rv; + intptr_t id; + + *reg = NULL; + + if (cnode <= 0) + 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_multi(cnode, name, sizeof(*cells), + (void **)&cells); + if (ncells <= 0) + return (ENOENT); + + /* Translate xref to device */ + regdev = OF_device_from_xref(cells[0]); + if (regdev == NULL) { + OF_prop_free(cells); + return (ENODEV); + } + + /* Map regulator to number */ + rv = REGDEV_MAP(regdev, cells[0], ncells - 1, cells + 1, &id); + OF_prop_free(cells); + if (rv != 0) + return (rv); + return (regulator_get_by_id(cdev, regdev, id, reg)); +} +#endif + +/* -------------------------------------------------------------------------- + * + * Regulator utility functions. + * + */ + +/* Convert raw selector value to real voltage */ +int +regulator_range_sel8_to_volt(struct regulator_range *ranges, int nranges, + uint8_t sel, int *volt) +{ + struct regulator_range *range; + int i; + + if (nranges == 0) + panic("Voltage regulator have zero ranges\n"); + + for (i = 0; i < nranges ; i++) { + range = ranges + i; + + if (!(sel >= range->min_sel && + sel <= range->max_sel)) + continue; + + sel -= range->min_sel; + + *volt = range->min_uvolt + sel * range->step_uvolt; + return (0); + } + + return (ERANGE); +} + +int +regulator_range_volt_to_sel8(struct regulator_range *ranges, int nranges, + int min_uvolt, int max_uvolt, uint8_t *out_sel) +{ + struct regulator_range *range; + uint8_t sel; + int uvolt; + int rv, i; + + if (nranges == 0) + panic("Voltage regulator have zero ranges\n"); + + for (i = 0; i < nranges; i++) { + range = ranges + i; + uvolt = range->min_uvolt + + (range->max_sel - range->min_sel) * range->step_uvolt; + + if ((min_uvolt > uvolt) || + (max_uvolt < range->min_uvolt)) + continue; + + if (min_uvolt <= range->min_uvolt) + min_uvolt = range->min_uvolt; + + /* if step == 0 -> fixed voltage range. */ + if (range->step_uvolt == 0) + sel = 0; + else + sel = DIV_ROUND_UP(min_uvolt - range->min_uvolt, + range->step_uvolt); + + + sel += range->min_sel; + + break; + } + + if (i >= nranges) + return (ERANGE); + + /* Verify new settings. */ + rv = regulator_range_sel8_to_volt(ranges, nranges, sel, &uvolt); + if (rv != 0) + return (rv); + if ((uvolt < min_uvolt) || (uvolt > max_uvolt)) + return (ERANGE); + + *out_sel = sel; + return (0); +} diff --git a/freebsd/sys/dev/extres/regulator/regulator.h b/freebsd/sys/dev/extres/regulator/regulator.h new file mode 100644 index 00000000..3905aa36 --- /dev/null +++ b/freebsd/sys/dev/extres/regulator/regulator.h @@ -0,0 +1,156 @@ +/*- + * Copyright 2016 Michal Meloun + * 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. + * + * $FreeBSD$ + */ + +#ifndef _DEV_EXTRES_REGULATOR_H_ +#define _DEV_EXTRES_REGULATOR_H_ +#include + +#include +#include +#ifdef FDT +#include +#endif +#include + +SYSCTL_DECL(_hw_regulator); + +#define REGULATOR_FLAGS_STATIC 0x00000001 /* Static strings */ +#define REGULATOR_FLAGS_NOT_DISABLE 0x00000002 /* Cannot be disabled */ + +#define REGULATOR_STATUS_ENABLED 0x00000001 +#define REGULATOR_STATUS_OVERCURRENT 0x00000002 + +typedef struct regulator *regulator_t; + +/* Standard regulator parameters. */ +struct regnode_std_param { + int min_uvolt; /* In uV */ + int max_uvolt; /* In uV */ + int min_uamp; /* In uA */ + int max_uamp; /* In uA */ + int ramp_delay; /* In uV/usec */ + int enable_delay; /* In usec */ + bool boot_on; /* Is enabled on boot */ + bool always_on; /* Must be enabled */ + int enable_active_high; +}; + +/* Initialization parameters. */ +struct regnode_init_def { + char *name; /* Regulator name */ + char *parent_name; /* Name of parent regulator */ + struct regnode_std_param std_param; /* Standard parameters */ + intptr_t id; /* Regulator ID */ + int flags; /* Flags */ +#ifdef FDT + phandle_t ofw_node; /* OFW node of regulator */ +#endif +}; + +struct regulator_range { + int min_uvolt; + int step_uvolt; + uint8_t min_sel; + uint8_t max_sel; +}; + +#define REG_RANGE_INIT(_min_sel, _max_sel, _min_uvolt, _step_uvolt) { \ + .min_sel = _min_sel, \ + .max_sel = _max_sel, \ + .min_uvolt = _min_uvolt, \ + .step_uvolt = _step_uvolt, \ +} + +/* + * Shorthands for constructing method tables. + */ +#define REGNODEMETHOD KOBJMETHOD +#define REGNODEMETHOD_END KOBJMETHOD_END +#define regnode_method_t kobj_method_t +#define regnode_class_t kobj_class_t +DECLARE_CLASS(regnode_class); + +/* Providers interface. */ +struct regnode *regnode_create(device_t pdev, regnode_class_t regnode_class, + struct regnode_init_def *def); +struct regnode *regnode_register(struct regnode *regnode); +const char *regnode_get_name(struct regnode *regnode); +const char *regnode_get_parent_name(struct regnode *regnode); +struct regnode *regnode_get_parent(struct regnode *regnode); +int regnode_get_flags(struct regnode *regnode); +void *regnode_get_softc(struct regnode *regnode); +device_t regnode_get_device(struct regnode *regnode); +struct regnode_std_param *regnode_get_stdparam(struct regnode *regnode); +void regnode_topo_unlock(void); +void regnode_topo_xlock(void); +void regnode_topo_slock(void); + +int regnode_enable(struct regnode *regnode); +int regnode_disable(struct regnode *regnode); +int regnode_stop(struct regnode *regnode, int depth); +int regnode_status(struct regnode *regnode, int *status); +int regnode_get_voltage(struct regnode *regnode, int *uvolt); +int regnode_set_voltage(struct regnode *regnode, int min_uvolt, int max_uvolt); +int regnode_set_constraint(struct regnode *regnode); + +/* Standard method that aren't default */ +int regnode_method_check_voltage(struct regnode *regnode, int uvolt); + +#ifdef FDT +phandle_t regnode_get_ofw_node(struct regnode *regnode); +#endif + +/* Consumers interface. */ +int regulator_get_by_name(device_t cdev, const char *name, + regulator_t *regulator); +int regulator_get_by_id(device_t cdev, device_t pdev, intptr_t id, + regulator_t *regulator); +int regulator_release(regulator_t regulator); +const char *regulator_get_name(regulator_t regulator); +int regulator_enable(regulator_t reg); +int regulator_disable(regulator_t reg); +int regulator_stop(regulator_t reg); +int regulator_status(regulator_t reg, int *status); +int regulator_get_voltage(regulator_t reg, int *uvolt); +int regulator_set_voltage(regulator_t reg, int min_uvolt, int max_uvolt); +int regulator_check_voltage(regulator_t reg, int uvolt); + +#ifdef FDT +int regulator_get_by_ofw_property(device_t dev, phandle_t node, char *name, + regulator_t *reg); +int regulator_parse_ofw_stdparam(device_t dev, phandle_t node, + struct regnode_init_def *def); +#endif + +/* Utility functions */ +int regulator_range_volt_to_sel8(struct regulator_range *ranges, int nranges, + int min_uvolt, int max_uvolt, uint8_t *out_sel); +int regulator_range_sel8_to_volt(struct regulator_range *ranges, int nranges, + uint8_t sel, int *volt); + +#endif /* _DEV_EXTRES_REGULATOR_H_ */ diff --git a/freebsd/sys/dev/extres/regulator/regulator_bus.c b/freebsd/sys/dev/extres/regulator/regulator_bus.c new file mode 100644 index 00000000..b79c2633 --- /dev/null +++ b/freebsd/sys/dev/extres/regulator/regulator_bus.c @@ -0,0 +1,89 @@ +#include + +/*- + * Copyright 2016 Michal Meloun + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include + +#include +#include + +struct ofw_regulator_bus_softc { + struct simplebus_softc simplebus_sc; +}; + +static int +ofw_regulator_bus_probe(device_t dev) +{ + const char *name; + + name = ofw_bus_get_name(dev); + if (name == NULL || strcmp(name, "regulators") != 0) + return (ENXIO); + device_set_desc(dev, "OFW regulators bus"); + + return (0); +} + +static int +ofw_regulator_bus_attach(device_t dev) +{ + phandle_t node, child; + + node = ofw_bus_get_node(dev); + simplebus_init(dev, node); + + for (child = OF_child(node); child > 0; child = OF_peer(child)) { + simplebus_add_device(dev, child, 0, NULL, -1, NULL); + } + + return (bus_generic_attach(dev)); +} + +static device_method_t ofw_regulator_bus_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ofw_regulator_bus_probe), + DEVMETHOD(device_attach, ofw_regulator_bus_attach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_1(ofw_regulator_bus, ofw_regulator_bus_driver, + ofw_regulator_bus_methods, sizeof(struct ofw_regulator_bus_softc), + simplebus_driver); +static devclass_t ofw_regulator_bus_devclass; +EARLY_DRIVER_MODULE(ofw_regulator_bus, simplebus, ofw_regulator_bus_driver, + ofw_regulator_bus_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); +MODULE_VERSION(ofw_regulator_bus, 1); diff --git a/freebsd/sys/dev/extres/regulator/regulator_fixed.c b/freebsd/sys/dev/extres/regulator/regulator_fixed.c new file mode 100644 index 00000000..0bc6d96e --- /dev/null +++ b/freebsd/sys/dev/extres/regulator/regulator_fixed.c @@ -0,0 +1,502 @@ +#include + +/*- + * Copyright 2016 Michal Meloun + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef FDT +#include +#include +#include +#endif +#include +#include + +#include + +MALLOC_DEFINE(M_FIXEDREGULATOR, "fixedregulator", "Fixed regulator"); + +/* GPIO list for shared pins. */ +typedef TAILQ_HEAD(gpio_list, gpio_entry) gpio_list_t; +struct gpio_entry { + TAILQ_ENTRY(gpio_entry) link; + struct gpiobus_pin gpio_pin; + int use_cnt; + int enable_cnt; + bool always_on; +}; +static gpio_list_t gpio_list = TAILQ_HEAD_INITIALIZER(gpio_list); +static struct mtx gpio_list_mtx; +MTX_SYSINIT(gpio_list_lock, &gpio_list_mtx, "Regulator GPIO lock", MTX_DEF); + +struct regnode_fixed_sc { + struct regnode_std_param *param; + bool gpio_open_drain; + struct gpio_entry *gpio_entry; +}; + +static int regnode_fixed_init(struct regnode *regnode); +static int regnode_fixed_enable(struct regnode *regnode, bool enable, + int *udelay); +static int regnode_fixed_status(struct regnode *regnode, int *status); +static int regnode_fixed_stop(struct regnode *regnode, int *udelay); + +static regnode_method_t regnode_fixed_methods[] = { + /* Regulator interface */ + REGNODEMETHOD(regnode_init, regnode_fixed_init), + REGNODEMETHOD(regnode_enable, regnode_fixed_enable), + REGNODEMETHOD(regnode_status, regnode_fixed_status), + REGNODEMETHOD(regnode_stop, regnode_fixed_stop), + REGNODEMETHOD(regnode_check_voltage, regnode_method_check_voltage), + REGNODEMETHOD_END +}; +DEFINE_CLASS_1(regnode_fixed, regnode_fixed_class, regnode_fixed_methods, + sizeof(struct regnode_fixed_sc), regnode_class); + +/* + * GPIO list functions. + * Two or more regulators can share single GPIO pins, so we must track all + * GPIOs in gpio_list. + * The GPIO pin is registerd and reseved for first consumer, all others share + * gpio_entry with it. + */ +static struct gpio_entry * +regnode_get_gpio_entry(struct gpiobus_pin *gpio_pin) +{ + struct gpio_entry *entry, *tmp; + device_t busdev; + int rv; + + busdev = GPIO_GET_BUS(gpio_pin->dev); + if (busdev == NULL) + return (NULL); + entry = malloc(sizeof(struct gpio_entry), M_FIXEDREGULATOR, + M_WAITOK | M_ZERO); + + mtx_lock(&gpio_list_mtx); + + TAILQ_FOREACH(tmp, &gpio_list, link) { + if (tmp->gpio_pin.dev == gpio_pin->dev && + tmp->gpio_pin.pin == gpio_pin->pin) { + tmp->use_cnt++; + mtx_unlock(&gpio_list_mtx); + free(entry, M_FIXEDREGULATOR); + return (tmp); + } + } + + /* Reserve pin. */ + /* XXX Can we call gpiobus_acquire_pin() with gpio_list_mtx held? */ + rv = gpiobus_acquire_pin(busdev, gpio_pin->pin); + if (rv != 0) { + mtx_unlock(&gpio_list_mtx); + free(entry, M_FIXEDREGULATOR); + return (NULL); + } + /* Everything is OK, build new entry and insert it to list. */ + entry->gpio_pin = *gpio_pin; + entry->use_cnt = 1; + TAILQ_INSERT_TAIL(&gpio_list, entry, link); + + mtx_unlock(&gpio_list_mtx); + return (entry); +} + + +/* + * Regulator class implementation. + */ +static int +regnode_fixed_init(struct regnode *regnode) +{ + device_t dev; + struct regnode_fixed_sc *sc; + struct gpiobus_pin *pin; + uint32_t flags; + int rv; + + sc = regnode_get_softc(regnode); + dev = regnode_get_device(regnode); + sc->param = regnode_get_stdparam(regnode); + if (sc->gpio_entry == NULL) + return (0); + pin = &sc->gpio_entry->gpio_pin; + + flags = GPIO_PIN_OUTPUT; + if (sc->gpio_open_drain) + flags |= GPIO_PIN_OPENDRAIN; + if (sc->param->boot_on || sc->param->always_on) { + rv = GPIO_PIN_SET(pin->dev, pin->pin, sc->param->enable_active_high); + if (rv != 0) { + device_printf(dev, "Cannot set GPIO pin: %d\n", + pin->pin); + return (rv); + } + } + + rv = GPIO_PIN_SETFLAGS(pin->dev, pin->pin, flags); + if (rv != 0) { + device_printf(dev, "Cannot configure GPIO pin: %d\n", pin->pin); + return (rv); + } + + return (0); +} + +/* + * Enable/disable regulator. + * Take shared GPIO pins in account + */ +static int +regnode_fixed_enable(struct regnode *regnode, bool enable, int *udelay) +{ + device_t dev; + struct regnode_fixed_sc *sc; + struct gpiobus_pin *pin; + int rv; + + sc = regnode_get_softc(regnode); + dev = regnode_get_device(regnode); + + *udelay = 0; + if (sc->gpio_entry == NULL) + return (0); + pin = &sc->gpio_entry->gpio_pin; + if (enable) { + sc->gpio_entry->enable_cnt++; + if (sc->gpio_entry->enable_cnt > 1) + return (0); + } else { + KASSERT(sc->gpio_entry->enable_cnt > 0, + ("Invalid enable count")); + sc->gpio_entry->enable_cnt--; + if (sc->gpio_entry->enable_cnt >= 1) + return (0); + } + if (sc->gpio_entry->always_on && !enable) + return (0); + if (!sc->param->enable_active_high) + enable = !enable; + rv = GPIO_PIN_SET(pin->dev, pin->pin, enable); + if (rv != 0) { + device_printf(dev, "Cannot set GPIO pin: %d\n", pin->pin); + return (rv); + } + *udelay = sc->param->enable_delay; + return (0); +} + +/* + * Stop (physicaly shutdown) regulator. + * Take shared GPIO pins in account + */ +static int +regnode_fixed_stop(struct regnode *regnode, int *udelay) +{ + device_t dev; + struct regnode_fixed_sc *sc; + struct gpiobus_pin *pin; + int rv; + + sc = regnode_get_softc(regnode); + dev = regnode_get_device(regnode); + + *udelay = 0; + if (sc->gpio_entry == NULL) + return (0); + if (sc->gpio_entry->always_on) + return (0); + pin = &sc->gpio_entry->gpio_pin; + if (sc->gpio_entry->enable_cnt > 0) { + /* Other regulator(s) are enabled. */ + /* XXXX Any diagnostic message? Or error? */ + return (0); + } + rv = GPIO_PIN_SET(pin->dev, pin->pin, + sc->param->enable_active_high ? false: true); + if (rv != 0) { + device_printf(dev, "Cannot set GPIO pin: %d\n", pin->pin); + return (rv); + } + *udelay = sc->param->enable_delay; + return (0); +} + +static int +regnode_fixed_status(struct regnode *regnode, int *status) +{ + struct regnode_fixed_sc *sc; + struct gpiobus_pin *pin; + uint32_t val; + int rv; + + sc = regnode_get_softc(regnode); + + *status = 0; + if (sc->gpio_entry == NULL) { + *status = REGULATOR_STATUS_ENABLED; + return (0); + } + pin = &sc->gpio_entry->gpio_pin; + + rv = GPIO_PIN_GET(pin->dev, pin->pin, &val); + if (rv == 0) { + if (!sc->param->enable_active_high ^ (val != 0)) + *status = REGULATOR_STATUS_ENABLED; + } + return (rv); +} + +int +regnode_fixed_register(device_t dev, struct regnode_fixed_init_def *init_def) +{ + struct regnode *regnode; + struct regnode_fixed_sc *sc; + + regnode = regnode_create(dev, ®node_fixed_class, + &init_def->reg_init_def); + if (regnode == NULL) { + device_printf(dev, "Cannot create regulator.\n"); + return(ENXIO); + } + sc = regnode_get_softc(regnode); + sc->gpio_open_drain = init_def->gpio_open_drain; + if (init_def->gpio_pin != NULL) { + sc->gpio_entry = regnode_get_gpio_entry(init_def->gpio_pin); + if (sc->gpio_entry == NULL) + return(ENXIO); + } + regnode = regnode_register(regnode); + if (regnode == NULL) { + device_printf(dev, "Cannot register regulator.\n"); + return(ENXIO); + } + + if (sc->gpio_entry != NULL) + sc->gpio_entry->always_on |= sc->param->always_on; + + return (0); +} + +/* + * OFW Driver implementation. + */ +#ifdef FDT + +struct regfix_softc +{ + device_t dev; + bool attach_done; + struct regnode_fixed_init_def init_def; + phandle_t gpio_prodxref; + pcell_t *gpio_cells; + int gpio_ncells; + struct gpiobus_pin gpio_pin; +}; + +static struct ofw_compat_data compat_data[] = { + {"regulator-fixed", 1}, + {NULL, 0}, +}; + +static int +regfix_get_gpio(struct regfix_softc * sc) +{ + device_t busdev; + phandle_t node; + + int rv; + + if (sc->gpio_prodxref == 0) + return (0); + + node = ofw_bus_get_node(sc->dev); + + /* Test if controller exist. */ + sc->gpio_pin.dev = OF_device_from_xref(sc->gpio_prodxref); + if (sc->gpio_pin.dev == NULL) + return (ENODEV); + + /* Test if GPIO bus already exist. */ + busdev = GPIO_GET_BUS(sc->gpio_pin.dev); + if (busdev == NULL) + return (ENODEV); + + rv = gpio_map_gpios(sc->gpio_pin.dev, node, + OF_node_from_xref(sc->gpio_prodxref), sc->gpio_ncells, + sc->gpio_cells, &(sc->gpio_pin.pin), &(sc->gpio_pin.flags)); + if (rv != 0) { + device_printf(sc->dev, "Cannot map the gpio property.\n"); + return (ENXIO); + } + sc->init_def.gpio_pin = &sc->gpio_pin; + return (0); +} + +static int +regfix_parse_fdt(struct regfix_softc * sc) +{ + phandle_t node; + int rv; + struct regnode_init_def *init_def; + + node = ofw_bus_get_node(sc->dev); + init_def = &sc->init_def.reg_init_def; + + rv = regulator_parse_ofw_stdparam(sc->dev, node, init_def); + if (rv != 0) { + device_printf(sc->dev, "Cannot parse standard parameters.\n"); + return(rv); + } + + /* Fixed regulator uses 'startup-delay-us' property for enable_delay */ + rv = OF_getencprop(node, "startup-delay-us", + &init_def->std_param.enable_delay, + sizeof(init_def->std_param.enable_delay)); + if (rv <= 0) + init_def->std_param.enable_delay = 0; + /* GPIO pin */ + if (OF_hasprop(node, "gpio-open-drain")) + sc->init_def.gpio_open_drain = true; + + if (!OF_hasprop(node, "gpio")) + return (0); + rv = ofw_bus_parse_xref_list_alloc(node, "gpio", "#gpio-cells", 0, + &sc->gpio_prodxref, &sc->gpio_ncells, &sc->gpio_cells); + if (rv != 0) { + sc->gpio_prodxref = 0; + device_printf(sc->dev, "Malformed gpio property\n"); + return (ENXIO); + } + return (0); +} + +static void +regfix_new_pass(device_t dev) +{ + struct regfix_softc * sc; + int rv; + + sc = device_get_softc(dev); + bus_generic_new_pass(dev); + + if (sc->attach_done) + return; + + /* Try to get and configure GPIO. */ + rv = regfix_get_gpio(sc); + if (rv != 0) + return; + + /* Register regulator. */ + regnode_fixed_register(sc->dev, &sc->init_def); + sc->attach_done = true; +} + +static int +regfix_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) + return (ENXIO); + + device_set_desc(dev, "Fixed Regulator"); + return (BUS_PROBE_DEFAULT); +} + +static int +regfix_detach(device_t dev) +{ + + /* This device is always present. */ + return (EBUSY); +} + +static int +regfix_attach(device_t dev) +{ + struct regfix_softc * sc; + int rv; + + sc = device_get_softc(dev); + sc->dev = dev; + + /* Parse FDT data. */ + rv = regfix_parse_fdt(sc); + if (rv != 0) + return(ENXIO); + + /* Fill reset of init. */ + sc->init_def.reg_init_def.id = 1; + sc->init_def.reg_init_def.flags = REGULATOR_FLAGS_STATIC; + + /* Try to get and configure GPIO. */ + rv = regfix_get_gpio(sc); + if (rv != 0) + return (bus_generic_attach(dev)); + + /* Register regulator. */ + regnode_fixed_register(sc->dev, &sc->init_def); + sc->attach_done = true; + + return (bus_generic_attach(dev)); +} + +static device_method_t regfix_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, regfix_probe), + DEVMETHOD(device_attach, regfix_attach), + DEVMETHOD(device_detach, regfix_detach), + /* Bus interface */ + DEVMETHOD(bus_new_pass, regfix_new_pass), + /* Regdev interface */ + DEVMETHOD(regdev_map, regdev_default_ofw_map), + + DEVMETHOD_END +}; + +static devclass_t regfix_devclass; +DEFINE_CLASS_0(regfix, regfix_driver, regfix_methods, + sizeof(struct regfix_softc)); +EARLY_DRIVER_MODULE(regfix, simplebus, regfix_driver, + regfix_devclass, 0, 0, BUS_PASS_BUS); + +#endif /* FDT */ diff --git a/freebsd/sys/dev/extres/regulator/regulator_fixed.h b/freebsd/sys/dev/extres/regulator/regulator_fixed.h new file mode 100644 index 00000000..5cc07516 --- /dev/null +++ b/freebsd/sys/dev/extres/regulator/regulator_fixed.h @@ -0,0 +1,44 @@ +/*- + * Copyright 2016 Michal Meloun + * 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. + * + * $FreeBSD$ + */ + +#ifndef _DEV_EXTRES_REGULATOR_FIXED_H_ +#define _DEV_EXTRES_REGULATOR_FIXED_H_ + +#include +#include + +struct regnode_fixed_init_def { + struct regnode_init_def reg_init_def; + bool gpio_open_drain; + struct gpiobus_pin *gpio_pin; +}; + +int regnode_fixed_register(device_t dev, + struct regnode_fixed_init_def *init_def); + +#endif /*_DEV_EXTRES_REGULATOR_FIXED_H_*/ diff --git a/freebsd/sys/dev/gpio/gpioregulator.c b/freebsd/sys/dev/gpio/gpioregulator.c new file mode 100644 index 00000000..6d05e52e --- /dev/null +++ b/freebsd/sys/dev/gpio/gpioregulator.c @@ -0,0 +1,350 @@ +#include + +/*- + * Copyright (c) 2016 Jared McNeill + * 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 ``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 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. + * + * $FreeBSD$ + */ + +/* + * GPIO controlled regulators + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include + +struct gpioregulator_state { + int val; + uint32_t mask; +}; + +struct gpioregulator_init_def { + struct regnode_init_def reg_init_def; + struct gpiobus_pin *enable_pin; + int enable_pin_valid; + int startup_delay_us; + int nstates; + struct gpioregulator_state *states; + int npins; + struct gpiobus_pin **pins; +}; + +struct gpioregulator_reg_sc { + struct regnode *regnode; + device_t base_dev; + struct regnode_std_param *param; + struct gpioregulator_init_def *def; +}; + +struct gpioregulator_softc { + device_t dev; + struct gpioregulator_reg_sc *reg_sc; + struct gpioregulator_init_def init_def; +}; + +static int +gpioregulator_regnode_init(struct regnode *regnode) +{ + struct gpioregulator_reg_sc *sc; + int error, n; + + sc = regnode_get_softc(regnode); + + if (sc->def->enable_pin_valid == 1) { + error = gpio_pin_setflags(sc->def->enable_pin, GPIO_PIN_OUTPUT); + if (error != 0) + return (error); + } + + for (n = 0; n < sc->def->npins; n++) { + error = gpio_pin_setflags(sc->def->pins[n], GPIO_PIN_OUTPUT); + if (error != 0) + return (error); + } + + return (0); +} + +static int +gpioregulator_regnode_enable(struct regnode *regnode, bool enable, int *udelay) +{ + struct gpioregulator_reg_sc *sc; + bool active; + int error; + + sc = regnode_get_softc(regnode); + + if (sc->def->enable_pin_valid == 1) { + active = enable; + if (!sc->param->enable_active_high) + active = !active; + error = gpio_pin_set_active(sc->def->enable_pin, active); + if (error != 0) + return (error); + } + + *udelay = sc->def->startup_delay_us; + + return (0); +} + +static int +gpioregulator_regnode_set_voltage(struct regnode *regnode, int min_uvolt, + int max_uvolt, int *udelay) +{ + struct gpioregulator_reg_sc *sc; + const struct gpioregulator_state *state; + int error, n; + + sc = regnode_get_softc(regnode); + state = NULL; + + for (n = 0; n < sc->def->nstates; n++) { + if (sc->def->states[n].val >= min_uvolt && + sc->def->states[n].val <= max_uvolt) { + state = &sc->def->states[n]; + break; + } + } + if (state == NULL) + return (EINVAL); + + for (n = 0; n < sc->def->npins; n++) { + error = gpio_pin_set_active(sc->def->pins[n], + (state->mask >> n) & 1); + if (error != 0) + return (error); + } + + *udelay = sc->def->startup_delay_us; + + return (0); +} + +static int +gpioregulator_regnode_get_voltage(struct regnode *regnode, int *uvolt) +{ + struct gpioregulator_reg_sc *sc; + uint32_t mask; + int error, n; + bool active; + + sc = regnode_get_softc(regnode); + mask = 0; + + for (n = 0; n < sc->def->npins; n++) { + error = gpio_pin_is_active(sc->def->pins[n], &active); + if (error != 0) + return (error); + mask |= (active << n); + } + + for (n = 0; n < sc->def->nstates; n++) { + if (sc->def->states[n].mask == mask) { + *uvolt = sc->def->states[n].val; + return (0); + } + } + + return (EIO); +} + +static regnode_method_t gpioregulator_regnode_methods[] = { + /* Regulator interface */ + REGNODEMETHOD(regnode_init, gpioregulator_regnode_init), + REGNODEMETHOD(regnode_enable, gpioregulator_regnode_enable), + REGNODEMETHOD(regnode_set_voltage, gpioregulator_regnode_set_voltage), + REGNODEMETHOD(regnode_get_voltage, gpioregulator_regnode_get_voltage), + REGNODEMETHOD_END +}; +DEFINE_CLASS_1(gpioregulator_regnode, gpioregulator_regnode_class, + gpioregulator_regnode_methods, sizeof(struct gpioregulator_reg_sc), + regnode_class); + +static int +gpioregulator_parse_fdt(struct gpioregulator_softc *sc) +{ + uint32_t *pstates, mask; + phandle_t node; + ssize_t len; + int error, n; + + node = ofw_bus_get_node(sc->dev); + pstates = NULL; + mask = 0; + + error = regulator_parse_ofw_stdparam(sc->dev, node, + &sc->init_def.reg_init_def); + if (error != 0) + return (error); + + /* "states" property (required) */ + len = OF_getencprop_alloc_multi(node, "states", sizeof(*pstates), + (void **)&pstates); + if (len < 2) { + device_printf(sc->dev, "invalid 'states' property\n"); + error = EINVAL; + goto done; + } + sc->init_def.nstates = len / 2; + sc->init_def.states = malloc(sc->init_def.nstates * + sizeof(*sc->init_def.states), M_DEVBUF, M_WAITOK); + for (n = 0; n < sc->init_def.nstates; n++) { + sc->init_def.states[n].val = pstates[n * 2 + 0]; + sc->init_def.states[n].mask = pstates[n * 2 + 1]; + mask |= sc->init_def.states[n].mask; + } + + /* "startup-delay-us" property (optional) */ + len = OF_getencprop(node, "startup-delay-us", + &sc->init_def.startup_delay_us, + sizeof(sc->init_def.startup_delay_us)); + if (len <= 0) + sc->init_def.startup_delay_us = 0; + + /* "enable-gpio" property (optional) */ + error = gpio_pin_get_by_ofw_property(sc->dev, node, "enable-gpio", + &sc->init_def.enable_pin); + if (error == 0) + sc->init_def.enable_pin_valid = 1; + + /* "gpios" property */ + sc->init_def.npins = 32 - __builtin_clz(mask); + sc->init_def.pins = malloc(sc->init_def.npins * + sizeof(sc->init_def.pins), M_DEVBUF, M_WAITOK); + for (n = 0; n < sc->init_def.npins; n++) { + error = gpio_pin_get_by_ofw_idx(sc->dev, node, n, + &sc->init_def.pins[n]); + if (error != 0) { + device_printf(sc->dev, "cannot get pin %d\n", n); + goto done; + } + } + +done: + if (error != 0) { + for (n = 0; n < sc->init_def.npins; n++) { + if (sc->init_def.pins[n] != NULL) + gpio_pin_release(sc->init_def.pins[n]); + } + + free(sc->init_def.states, M_DEVBUF); + free(sc->init_def.pins, M_DEVBUF); + + } + OF_prop_free(pstates); + + return (error); +} + +static int +gpioregulator_probe(device_t dev) +{ + + if (!ofw_bus_is_compatible(dev, "regulator-gpio")) + return (ENXIO); + + device_set_desc(dev, "GPIO controlled regulator"); + return (BUS_PROBE_GENERIC); +} + +static int +gpioregulator_attach(device_t dev) +{ + struct gpioregulator_softc *sc; + struct regnode *regnode; + phandle_t node; + int error; + + sc = device_get_softc(dev); + sc->dev = dev; + node = ofw_bus_get_node(dev); + + error = gpioregulator_parse_fdt(sc); + if (error != 0) { + device_printf(dev, "cannot parse parameters\n"); + return (ENXIO); + } + sc->init_def.reg_init_def.id = 1; + sc->init_def.reg_init_def.ofw_node = node; + + regnode = regnode_create(dev, &gpioregulator_regnode_class, + &sc->init_def.reg_init_def); + if (regnode == NULL) { + device_printf(dev, "cannot create regulator\n"); + return (ENXIO); + } + + sc->reg_sc = regnode_get_softc(regnode); + sc->reg_sc->regnode = regnode; + sc->reg_sc->base_dev = dev; + sc->reg_sc->param = regnode_get_stdparam(regnode); + sc->reg_sc->def = &sc->init_def; + + regnode_register(regnode); + + return (0); +} + + +static device_method_t gpioregulator_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, gpioregulator_probe), + DEVMETHOD(device_attach, gpioregulator_attach), + + /* Regdev interface */ + DEVMETHOD(regdev_map, regdev_default_ofw_map), + + DEVMETHOD_END +}; + +static driver_t gpioregulator_driver = { + "gpioregulator", + gpioregulator_methods, + sizeof(struct gpioregulator_softc), +}; + +static devclass_t gpioregulator_devclass; + +EARLY_DRIVER_MODULE(gpioregulator, simplebus, gpioregulator_driver, + gpioregulator_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LAST); +MODULE_VERSION(gpioregulator, 1); -- cgit v1.2.3