a75a785aef
values that were probably entered "for completeness" from a datasheet, and for all I know may be useful/necessary some day.
1055 lines
29 KiB
C
1055 lines
29 KiB
C
/*-
|
|
* Copyright (c) 2011
|
|
* Ben Gray <ben.r.gray@gmail.com>.
|
|
* 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 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 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$");
|
|
|
|
/*
|
|
* Texas Instruments TWL4030/TWL5030/TWL60x0/TPS659x0 Power Management.
|
|
*
|
|
* This driver covers the voltages regulators (LDO), allows for enabling &
|
|
* disabling the voltage output and adjusting the voltage level.
|
|
*
|
|
* Voltage regulators can belong to different power groups, in this driver we
|
|
* put the regulators under our control in the "Application power group".
|
|
*
|
|
*
|
|
* FLATTENED DEVICE TREE (FDT)
|
|
* Startup override settings can be specified in the FDT, if they are they
|
|
* should be under the twl parent device and take the following form:
|
|
*
|
|
* voltage-regulators = "name1", "millivolts1",
|
|
* "name2", "millivolts2";
|
|
*
|
|
* Each override should be a pair, the first entry is the name of the regulator
|
|
* the second is the voltage (in millivolts) to set for the given regulator.
|
|
*
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/module.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/rman.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/sx.h>
|
|
#include <sys/malloc.h>
|
|
|
|
#include <machine/bus.h>
|
|
#include <machine/cpu.h>
|
|
#include <machine/cpufunc.h>
|
|
#include <machine/resource.h>
|
|
#include <machine/intr.h>
|
|
|
|
#include <dev/ofw/openfirm.h>
|
|
#include <dev/ofw/ofw_bus.h>
|
|
|
|
#include "twl.h"
|
|
#include "twl_vreg.h"
|
|
|
|
static int twl_vreg_debug = 1;
|
|
|
|
|
|
/*
|
|
* Power Groups bits for the 4030 and 6030 devices
|
|
*/
|
|
#define TWL4030_P3_GRP 0x80 /* Peripherals, power group */
|
|
#define TWL4030_P2_GRP 0x40 /* Modem power group */
|
|
#define TWL4030_P1_GRP 0x20 /* Application power group (FreeBSD control) */
|
|
|
|
#define TWL6030_P3_GRP 0x04 /* Modem power group */
|
|
#define TWL6030_P2_GRP 0x02 /* Connectivity power group */
|
|
#define TWL6030_P1_GRP 0x01 /* Application power group (FreeBSD control) */
|
|
|
|
/*
|
|
* Register offsets within a LDO regulator register set
|
|
*/
|
|
#define TWL_VREG_GRP 0x00 /* Regulator GRP register */
|
|
#define TWL_VREG_STATE 0x02
|
|
#define TWL_VREG_VSEL 0x03 /* Voltage select register */
|
|
|
|
#define UNDF 0xFFFF
|
|
|
|
static const uint16_t twl6030_voltages[] = {
|
|
0000, 1000, 1100, 1200, 1300, 1400, 1500, 1600,
|
|
1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400,
|
|
2500, 2600, 2700, 2800, 2900, 3000, 3100, 3200,
|
|
3300, UNDF, UNDF, UNDF, UNDF, UNDF, UNDF, 2750
|
|
};
|
|
|
|
static const uint16_t twl4030_vaux1_voltages[] = {
|
|
1500, 1800, 2500, 2800, 3000, 3000, 3000, 3000
|
|
};
|
|
static const uint16_t twl4030_vaux2_voltages[] = {
|
|
1700, 1700, 1900, 1300, 1500, 1800, 2000, 2500,
|
|
2100, 2800, 2200, 2300, 2400, 2400, 2400, 2400
|
|
};
|
|
static const uint16_t twl4030_vaux3_voltages[] = {
|
|
1500, 1800, 2500, 2800, 3000, 3000, 3000, 3000
|
|
};
|
|
static const uint16_t twl4030_vaux4_voltages[] = {
|
|
700, 1000, 1200, 1300, 1500, 1800, 1850, 2500,
|
|
2600, 2800, 2850, 3000, 3150, 3150, 3150, 3150
|
|
};
|
|
static const uint16_t twl4030_vmmc1_voltages[] = {
|
|
1850, 2850, 3000, 3150
|
|
};
|
|
static const uint16_t twl4030_vmmc2_voltages[] = {
|
|
1000, 1000, 1200, 1300, 1500, 1800, 1850, 2500,
|
|
2600, 2800, 2850, 3000, 3150, 3150, 3150, 3150
|
|
};
|
|
static const uint16_t twl4030_vpll1_voltages[] = {
|
|
1000, 1200, 1300, 1800, 2800, 3000, 3000, 3000
|
|
};
|
|
static const uint16_t twl4030_vpll2_voltages[] = {
|
|
700, 1000, 1200, 1300, 1500, 1800, 1850, 2500,
|
|
2600, 2800, 2850, 3000, 3150, 3150, 3150, 3150
|
|
};
|
|
static const uint16_t twl4030_vsim_voltages[] = {
|
|
1000, 1200, 1300, 1800, 2800, 3000, 3000, 3000
|
|
};
|
|
static const uint16_t twl4030_vdac_voltages[] = {
|
|
1200, 1300, 1800, 1800
|
|
};
|
|
#if 0 /* vdd1, vdd2, vdio, not currently used. */
|
|
static const uint16_t twl4030_vdd1_voltages[] = {
|
|
800, 1450
|
|
};
|
|
static const uint16_t twl4030_vdd2_voltages[] = {
|
|
800, 1450, 1500
|
|
};
|
|
static const uint16_t twl4030_vio_voltages[] = {
|
|
1800, 1850
|
|
};
|
|
#endif
|
|
static const uint16_t twl4030_vintana2_voltages[] = {
|
|
2500, 2750
|
|
};
|
|
|
|
/**
|
|
* Support voltage regulators for the different IC's
|
|
*/
|
|
struct twl_regulator {
|
|
const char *name;
|
|
uint8_t subdev;
|
|
uint8_t regbase;
|
|
|
|
uint16_t fixedvoltage;
|
|
|
|
const uint16_t *voltages;
|
|
uint32_t num_voltages;
|
|
};
|
|
|
|
#define TWL_REGULATOR_ADJUSTABLE(name, subdev, reg, voltages) \
|
|
{ name, subdev, reg, 0, voltages, (sizeof(voltages)/sizeof(voltages[0])) }
|
|
#define TWL_REGULATOR_FIXED(name, subdev, reg, voltage) \
|
|
{ name, subdev, reg, voltage, NULL, 0 }
|
|
|
|
static const struct twl_regulator twl4030_regulators[] = {
|
|
TWL_REGULATOR_ADJUSTABLE("vaux1", 0, 0x17, twl4030_vaux1_voltages),
|
|
TWL_REGULATOR_ADJUSTABLE("vaux2", 0, 0x1B, twl4030_vaux2_voltages),
|
|
TWL_REGULATOR_ADJUSTABLE("vaux3", 0, 0x1F, twl4030_vaux3_voltages),
|
|
TWL_REGULATOR_ADJUSTABLE("vaux4", 0, 0x23, twl4030_vaux4_voltages),
|
|
TWL_REGULATOR_ADJUSTABLE("vmmc1", 0, 0x27, twl4030_vmmc1_voltages),
|
|
TWL_REGULATOR_ADJUSTABLE("vmmc2", 0, 0x2B, twl4030_vmmc2_voltages),
|
|
TWL_REGULATOR_ADJUSTABLE("vpll1", 0, 0x2F, twl4030_vpll1_voltages),
|
|
TWL_REGULATOR_ADJUSTABLE("vpll2", 0, 0x33, twl4030_vpll2_voltages),
|
|
TWL_REGULATOR_ADJUSTABLE("vsim", 0, 0x37, twl4030_vsim_voltages),
|
|
TWL_REGULATOR_ADJUSTABLE("vdac", 0, 0x3B, twl4030_vdac_voltages),
|
|
TWL_REGULATOR_ADJUSTABLE("vintana2", 0, 0x43, twl4030_vintana2_voltages),
|
|
TWL_REGULATOR_FIXED("vintana1", 0, 0x3F, 1500),
|
|
TWL_REGULATOR_FIXED("vintdig", 0, 0x47, 1500),
|
|
TWL_REGULATOR_FIXED("vusb1v5", 0, 0x71, 1500),
|
|
TWL_REGULATOR_FIXED("vusb1v8", 0, 0x74, 1800),
|
|
TWL_REGULATOR_FIXED("vusb3v1", 0, 0x77, 3100),
|
|
{ NULL, 0, 0x00, 0, NULL, 0 }
|
|
};
|
|
|
|
static const struct twl_regulator twl6030_regulators[] = {
|
|
TWL_REGULATOR_ADJUSTABLE("vaux1", 0, 0x84, twl6030_voltages),
|
|
TWL_REGULATOR_ADJUSTABLE("vaux2", 0, 0x89, twl6030_voltages),
|
|
TWL_REGULATOR_ADJUSTABLE("vaux3", 0, 0x8C, twl6030_voltages),
|
|
TWL_REGULATOR_ADJUSTABLE("vmmc", 0, 0x98, twl6030_voltages),
|
|
TWL_REGULATOR_ADJUSTABLE("vpp", 0, 0x9C, twl6030_voltages),
|
|
TWL_REGULATOR_ADJUSTABLE("vusim", 0, 0xA4, twl6030_voltages),
|
|
TWL_REGULATOR_FIXED("vmem", 0, 0x64, 1800),
|
|
TWL_REGULATOR_FIXED("vusb", 0, 0xA0, 3300),
|
|
TWL_REGULATOR_FIXED("v1v8", 0, 0x46, 1800),
|
|
TWL_REGULATOR_FIXED("v2v1", 0, 0x4C, 2100),
|
|
TWL_REGULATOR_FIXED("v1v29", 0, 0x40, 1290),
|
|
TWL_REGULATOR_FIXED("vcxio", 0, 0x90, 1800),
|
|
TWL_REGULATOR_FIXED("vdac", 0, 0x94, 1800),
|
|
TWL_REGULATOR_FIXED("vana", 0, 0x80, 2100),
|
|
{ NULL, 0, 0x00, 0, NULL, 0 }
|
|
};
|
|
|
|
#define TWL_VREG_MAX_NAMELEN 32
|
|
|
|
struct twl_regulator_entry {
|
|
LIST_ENTRY(twl_regulator_entry) entries;
|
|
char name[TWL_VREG_MAX_NAMELEN];
|
|
struct sysctl_oid *oid;
|
|
uint8_t sub_dev; /* TWL sub-device group */
|
|
uint8_t reg_off; /* base register offset for the LDO */
|
|
uint16_t fixed_voltage; /* the (milli)voltage if LDO is fixed */
|
|
const uint16_t *supp_voltages; /* pointer to an array of possible voltages */
|
|
uint32_t num_supp_voltages; /* the number of supplied voltages */
|
|
};
|
|
|
|
struct twl_vreg_softc {
|
|
device_t sc_dev;
|
|
device_t sc_pdev;
|
|
struct sx sc_sx;
|
|
|
|
struct intr_config_hook sc_init_hook;
|
|
LIST_HEAD(twl_regulator_list, twl_regulator_entry) sc_vreg_list;
|
|
};
|
|
|
|
|
|
#define TWL_VREG_XLOCK(_sc) sx_xlock(&(_sc)->sc_sx)
|
|
#define TWL_VREG_XUNLOCK(_sc) sx_xunlock(&(_sc)->sc_sx)
|
|
#define TWL_VREG_SLOCK(_sc) sx_slock(&(_sc)->sc_sx)
|
|
#define TWL_VREG_SUNLOCK(_sc) sx_sunlock(&(_sc)->sc_sx)
|
|
#define TWL_VREG_LOCK_INIT(_sc) sx_init(&(_sc)->sc_sx, "twl_vreg")
|
|
#define TWL_VREG_LOCK_DESTROY(_sc) sx_destroy(&(_sc)->sc_sx);
|
|
|
|
#define TWL_VREG_ASSERT_LOCKED(_sc) sx_assert(&(_sc)->sc_sx, SA_LOCKED);
|
|
|
|
#define TWL_VREG_LOCK_UPGRADE(_sc) \
|
|
do { \
|
|
while (!sx_try_upgrade(&(_sc)->sc_sx)) \
|
|
pause("twl_vreg_ex", (hz / 100)); \
|
|
} while(0)
|
|
#define TWL_VREG_LOCK_DOWNGRADE(_sc) sx_downgrade(&(_sc)->sc_sx);
|
|
|
|
|
|
|
|
|
|
/**
|
|
* twl_vreg_read_1 - read single register from the TWL device
|
|
* twl_vreg_write_1 - write a single register in the TWL device
|
|
* @sc: device context
|
|
* @clk: the clock device we're reading from / writing to
|
|
* @off: offset within the clock's register set
|
|
* @val: the value to write or a pointer to a variable to store the result
|
|
*
|
|
* RETURNS:
|
|
* Zero on success or an error code on failure.
|
|
*/
|
|
static inline int
|
|
twl_vreg_read_1(struct twl_vreg_softc *sc, struct twl_regulator_entry *regulator,
|
|
uint8_t off, uint8_t *val)
|
|
{
|
|
return (twl_read(sc->sc_pdev, regulator->sub_dev,
|
|
regulator->reg_off + off, val, 1));
|
|
}
|
|
|
|
static inline int
|
|
twl_vreg_write_1(struct twl_vreg_softc *sc, struct twl_regulator_entry *regulator,
|
|
uint8_t off, uint8_t val)
|
|
{
|
|
return (twl_write(sc->sc_pdev, regulator->sub_dev,
|
|
regulator->reg_off + off, &val, 1));
|
|
}
|
|
|
|
/**
|
|
* twl_millivolt_to_vsel - gets the vsel bit value to write into the register
|
|
* for a desired voltage and regulator
|
|
* @sc: the device soft context
|
|
* @regulator: pointer to the regulator device
|
|
* @millivolts: the millivolts to find the bit value for
|
|
* @vsel: upon return will contain the corresponding register value
|
|
*
|
|
* Accepts a (milli)voltage value and tries to find the closest match to the
|
|
* actual supported voltages for the given regulator. If a match is found
|
|
* within 100mv of the target, @vsel is written with the match and 0 is
|
|
* returned. If no voltage match is found the function returns an non-zero
|
|
* value.
|
|
*
|
|
* RETURNS:
|
|
* Zero on success or an error code on failure.
|
|
*/
|
|
static int
|
|
twl_vreg_millivolt_to_vsel(struct twl_vreg_softc *sc,
|
|
struct twl_regulator_entry *regulator, int millivolts, uint8_t *vsel)
|
|
{
|
|
int delta, smallest_delta;
|
|
unsigned i, closest_idx;
|
|
|
|
TWL_VREG_ASSERT_LOCKED(sc);
|
|
|
|
if (regulator->supp_voltages == NULL)
|
|
return (EINVAL);
|
|
|
|
/* Loop over the support voltages and try and find the closest match */
|
|
closest_idx = 0;
|
|
smallest_delta = 0x7fffffff;
|
|
for (i = 0; i < regulator->num_supp_voltages; i++) {
|
|
|
|
/* Ignore undefined values */
|
|
if (regulator->supp_voltages[i] == UNDF)
|
|
continue;
|
|
|
|
/* Calculate the difference */
|
|
delta = millivolts - (int)regulator->supp_voltages[i];
|
|
if (abs(delta) < smallest_delta) {
|
|
smallest_delta = abs(delta);
|
|
closest_idx = i;
|
|
}
|
|
}
|
|
|
|
/* Check we got a voltage that was within 100mv of the actual target, this
|
|
* is just a value I picked out of thin air.
|
|
*/
|
|
if ((smallest_delta > 100) && (closest_idx < 0x100))
|
|
return (EINVAL);
|
|
|
|
*vsel = closest_idx;
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* twl_vreg_is_regulator_enabled - returns the enabled status of the regulator
|
|
* @sc: the device soft context
|
|
* @regulator: pointer to the regulator device
|
|
* @enabled: stores the enabled status, zero disabled, non-zero enabled
|
|
*
|
|
* LOCKING:
|
|
* On entry expects the TWL VREG lock to be held. Will upgrade the lock to
|
|
* exclusive if not already but, if so, it will be downgraded again before
|
|
* returning.
|
|
*
|
|
* RETURNS:
|
|
* Zero on success or an error code on failure.
|
|
*/
|
|
static int
|
|
twl_vreg_is_regulator_enabled(struct twl_vreg_softc *sc,
|
|
struct twl_regulator_entry *regulator, int *enabled)
|
|
{
|
|
int err;
|
|
uint8_t grp;
|
|
uint8_t state;
|
|
int xlocked;
|
|
|
|
if (enabled == NULL)
|
|
return (EINVAL);
|
|
|
|
TWL_VREG_ASSERT_LOCKED(sc);
|
|
|
|
xlocked = sx_xlocked(&sc->sc_sx);
|
|
if (!xlocked)
|
|
TWL_VREG_LOCK_UPGRADE(sc);
|
|
|
|
/* The status reading is different for the different devices */
|
|
if (twl_is_4030(sc->sc_pdev)) {
|
|
|
|
err = twl_vreg_read_1(sc, regulator, TWL_VREG_GRP, &state);
|
|
if (err)
|
|
goto done;
|
|
|
|
*enabled = (state & TWL4030_P1_GRP);
|
|
|
|
} else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev)) {
|
|
|
|
/* Check the regulator is in the application group */
|
|
if (twl_is_6030(sc->sc_pdev)) {
|
|
err = twl_vreg_read_1(sc, regulator, TWL_VREG_GRP, &grp);
|
|
if (err)
|
|
goto done;
|
|
|
|
if (!(grp & TWL6030_P1_GRP)) {
|
|
*enabled = 0; /* disabled */
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* Read the application mode state and verify it's ON */
|
|
err = twl_vreg_read_1(sc, regulator, TWL_VREG_STATE, &state);
|
|
if (err)
|
|
goto done;
|
|
|
|
*enabled = ((state & 0x0C) == 0x04);
|
|
|
|
} else {
|
|
err = EINVAL;
|
|
}
|
|
|
|
done:
|
|
if (!xlocked)
|
|
TWL_VREG_LOCK_DOWNGRADE(sc);
|
|
|
|
return (err);
|
|
}
|
|
|
|
/**
|
|
* twl_vreg_disable_regulator - disables a voltage regulator
|
|
* @sc: the device soft context
|
|
* @regulator: pointer to the regulator device
|
|
*
|
|
* Disables the regulator which will stop the output drivers.
|
|
*
|
|
* LOCKING:
|
|
* On entry expects the TWL VREG lock to be held. Will upgrade the lock to
|
|
* exclusive if not already but, if so, it will be downgraded again before
|
|
* returning.
|
|
*
|
|
* RETURNS:
|
|
* Zero on success or a positive error code on failure.
|
|
*/
|
|
static int
|
|
twl_vreg_disable_regulator(struct twl_vreg_softc *sc,
|
|
struct twl_regulator_entry *regulator)
|
|
{
|
|
int err = 0;
|
|
uint8_t grp;
|
|
int xlocked;
|
|
|
|
TWL_VREG_ASSERT_LOCKED(sc);
|
|
|
|
xlocked = sx_xlocked(&sc->sc_sx);
|
|
if (!xlocked)
|
|
TWL_VREG_LOCK_UPGRADE(sc);
|
|
|
|
if (twl_is_4030(sc->sc_pdev)) {
|
|
|
|
/* Read the regulator CFG_GRP register */
|
|
err = twl_vreg_read_1(sc, regulator, TWL_VREG_GRP, &grp);
|
|
if (err)
|
|
goto done;
|
|
|
|
/* On the TWL4030 we just need to remove the regulator from all the
|
|
* power groups.
|
|
*/
|
|
grp &= ~(TWL4030_P1_GRP | TWL4030_P2_GRP | TWL4030_P3_GRP);
|
|
err = twl_vreg_write_1(sc, regulator, TWL_VREG_GRP, grp);
|
|
|
|
} else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev)) {
|
|
|
|
/* On TWL6030 we need to make sure we disable power for all groups */
|
|
if (twl_is_6030(sc->sc_pdev))
|
|
grp = TWL6030_P1_GRP | TWL6030_P2_GRP | TWL6030_P3_GRP;
|
|
else
|
|
grp = 0x00;
|
|
|
|
/* Write the resource state to "OFF" */
|
|
err = twl_vreg_write_1(sc, regulator, TWL_VREG_STATE, (grp << 5));
|
|
}
|
|
|
|
done:
|
|
if (!xlocked)
|
|
TWL_VREG_LOCK_DOWNGRADE(sc);
|
|
|
|
return (err);
|
|
}
|
|
|
|
/**
|
|
* twl_vreg_enable_regulator - enables the voltage regulator
|
|
* @sc: the device soft context
|
|
* @regulator: pointer to the regulator device
|
|
*
|
|
* Enables the regulator which will enable the voltage out at the currently
|
|
* set voltage. Set the voltage before calling this function to avoid
|
|
* driving the voltage too high/low by mistake.
|
|
*
|
|
* LOCKING:
|
|
* On entry expects the TWL VREG lock to be held. Will upgrade the lock to
|
|
* exclusive if not already but, if so, it will be downgraded again before
|
|
* returning.
|
|
*
|
|
* RETURNS:
|
|
* Zero on success or a positive error code on failure.
|
|
*/
|
|
static int
|
|
twl_vreg_enable_regulator(struct twl_vreg_softc *sc,
|
|
struct twl_regulator_entry *regulator)
|
|
{
|
|
int err;
|
|
uint8_t grp;
|
|
int xlocked;
|
|
|
|
TWL_VREG_ASSERT_LOCKED(sc);
|
|
|
|
xlocked = sx_xlocked(&sc->sc_sx);
|
|
if (!xlocked)
|
|
TWL_VREG_LOCK_UPGRADE(sc);
|
|
|
|
|
|
err = twl_vreg_read_1(sc, regulator, TWL_VREG_GRP, &grp);
|
|
if (err)
|
|
goto done;
|
|
|
|
/* Enable the regulator by ensuring it's in the application power group
|
|
* and is in the "on" state.
|
|
*/
|
|
if (twl_is_4030(sc->sc_pdev)) {
|
|
|
|
/* On the TWL4030 we just need to ensure the regulator is in the right
|
|
* power domain, don't need to turn on explicitly like TWL6030.
|
|
*/
|
|
grp |= TWL4030_P1_GRP;
|
|
err = twl_vreg_write_1(sc, regulator, TWL_VREG_GRP, grp);
|
|
|
|
} else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev)) {
|
|
|
|
if (twl_is_6030(sc->sc_pdev) && !(grp & TWL6030_P1_GRP)) {
|
|
grp |= TWL6030_P1_GRP;
|
|
err = twl_vreg_write_1(sc, regulator, TWL_VREG_GRP, grp);
|
|
if (err)
|
|
goto done;
|
|
}
|
|
|
|
/* Write the resource state to "ON" */
|
|
err = twl_vreg_write_1(sc, regulator, TWL_VREG_STATE, (grp << 5) | 0x01);
|
|
}
|
|
|
|
done:
|
|
if (!xlocked)
|
|
TWL_VREG_LOCK_DOWNGRADE(sc);
|
|
|
|
return (err);
|
|
}
|
|
|
|
/**
|
|
* twl_vreg_write_regulator_voltage - sets the voltage level on a regulator
|
|
* @sc: the device soft context
|
|
* @regulator: pointer to the regulator structure
|
|
* @millivolts: the voltage to set
|
|
*
|
|
* Sets the voltage output on a given regulator, if the regulator is not
|
|
* enabled, it will be enabled.
|
|
*
|
|
* LOCKING:
|
|
* On entry expects the TWL VREG lock to be held, may upgrade the lock to
|
|
* exclusive but if so it will be downgraded once again before returning.
|
|
*
|
|
* RETURNS:
|
|
* Zero on success or an error code on failure.
|
|
*/
|
|
static int
|
|
twl_vreg_write_regulator_voltage(struct twl_vreg_softc *sc,
|
|
struct twl_regulator_entry *regulator, int millivolts)
|
|
{
|
|
int err;
|
|
uint8_t vsel;
|
|
int xlocked;
|
|
|
|
TWL_VREG_ASSERT_LOCKED(sc);
|
|
|
|
/* If millivolts is zero then we simply disable the output */
|
|
if (millivolts == 0)
|
|
return (twl_vreg_disable_regulator(sc, regulator));
|
|
|
|
/* If the regulator has a fixed voltage then check the setting matches
|
|
* and simply enable.
|
|
*/
|
|
if (regulator->supp_voltages == NULL || regulator->num_supp_voltages == 0) {
|
|
if (millivolts != regulator->fixed_voltage)
|
|
return (EINVAL);
|
|
|
|
return (twl_vreg_enable_regulator(sc, regulator));
|
|
}
|
|
|
|
/* Get the VSEL value for the given voltage */
|
|
err = twl_vreg_millivolt_to_vsel(sc, regulator, millivolts, &vsel);
|
|
if (err)
|
|
return (err);
|
|
|
|
|
|
/* Need to upgrade because writing the voltage and enabling should be atomic */
|
|
xlocked = sx_xlocked(&sc->sc_sx);
|
|
if (!xlocked)
|
|
TWL_VREG_LOCK_UPGRADE(sc);
|
|
|
|
|
|
/* Set voltage and enable (atomically) */
|
|
err = twl_vreg_write_1(sc, regulator, TWL_VREG_VSEL, (vsel & 0x1f));
|
|
if (!err) {
|
|
err = twl_vreg_enable_regulator(sc, regulator);
|
|
}
|
|
|
|
if (!xlocked)
|
|
TWL_VREG_LOCK_DOWNGRADE(sc);
|
|
|
|
if ((twl_vreg_debug > 1) && !err)
|
|
device_printf(sc->sc_dev, "%s : setting voltage to %dmV (vsel: 0x%x)\n",
|
|
regulator->name, millivolts, vsel);
|
|
|
|
return (err);
|
|
}
|
|
|
|
/**
|
|
* twl_vreg_read_regulator_voltage - reads the voltage on a given regulator
|
|
* @sc: the device soft context
|
|
* @regulator: pointer to the regulator structure
|
|
* @millivolts: upon return will contain the voltage on the regulator
|
|
*
|
|
* LOCKING:
|
|
* On entry expects the TWL VREG lock to be held. It will upgrade the lock to
|
|
* exclusive if not already, but if so, it will be downgraded again before
|
|
* returning.
|
|
*
|
|
* RETURNS:
|
|
* Zero on success, or otherwise an error code.
|
|
*/
|
|
static int
|
|
twl_vreg_read_regulator_voltage(struct twl_vreg_softc *sc,
|
|
struct twl_regulator_entry *regulator, int *millivolts)
|
|
{
|
|
int err;
|
|
int en = 0;
|
|
int xlocked;
|
|
uint8_t vsel;
|
|
|
|
TWL_VREG_ASSERT_LOCKED(sc);
|
|
|
|
/* Need to upgrade the lock because checking enabled state and voltage
|
|
* should be atomic.
|
|
*/
|
|
xlocked = sx_xlocked(&sc->sc_sx);
|
|
if (!xlocked)
|
|
TWL_VREG_LOCK_UPGRADE(sc);
|
|
|
|
|
|
/* Check if the regulator is currently enabled */
|
|
err = twl_vreg_is_regulator_enabled(sc, regulator, &en);
|
|
if (err)
|
|
goto done;
|
|
|
|
*millivolts = 0;
|
|
if (!en)
|
|
goto done;
|
|
|
|
|
|
/* Not all voltages are adjustable */
|
|
if (regulator->supp_voltages == NULL || !regulator->num_supp_voltages) {
|
|
*millivolts = regulator->fixed_voltage;
|
|
goto done;
|
|
}
|
|
|
|
/* For variable voltages read the voltage register */
|
|
err = twl_vreg_read_1(sc, regulator, TWL_VREG_VSEL, &vsel);
|
|
if (err)
|
|
goto done;
|
|
|
|
vsel &= (regulator->num_supp_voltages - 1);
|
|
if (regulator->supp_voltages[vsel] == UNDF) {
|
|
err = EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
*millivolts = regulator->supp_voltages[vsel];
|
|
|
|
done:
|
|
if (!xlocked)
|
|
TWL_VREG_LOCK_DOWNGRADE(sc);
|
|
|
|
if ((twl_vreg_debug > 1) && !err)
|
|
device_printf(sc->sc_dev, "%s : reading voltage is %dmV (vsel: 0x%x)\n",
|
|
regulator->name, *millivolts, vsel);
|
|
|
|
return (err);
|
|
}
|
|
|
|
/**
|
|
* twl_vreg_get_voltage - public interface to read the voltage on a regulator
|
|
* @dev: TWL VREG device
|
|
* @name: the name of the regulator to read the voltage of
|
|
* @millivolts: pointer to an integer that upon return will contain the mV
|
|
*
|
|
* If the regulator is disabled the function will set the @millivolts to zero.
|
|
*
|
|
* LOCKING:
|
|
* Internally the function takes and releases the TWL VREG lock.
|
|
*
|
|
* RETURNS:
|
|
* Zero on success or a negative error code on failure.
|
|
*/
|
|
int
|
|
twl_vreg_get_voltage(device_t dev, const char *name, int *millivolts)
|
|
{
|
|
struct twl_vreg_softc *sc;
|
|
struct twl_regulator_entry *regulator;
|
|
int err = EINVAL;
|
|
|
|
if (millivolts == NULL)
|
|
return (EINVAL);
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
TWL_VREG_SLOCK(sc);
|
|
|
|
LIST_FOREACH(regulator, &sc->sc_vreg_list, entries) {
|
|
if (strcmp(regulator->name, name) == 0) {
|
|
err = twl_vreg_read_regulator_voltage(sc, regulator, millivolts);
|
|
break;
|
|
}
|
|
}
|
|
|
|
TWL_VREG_SUNLOCK(sc);
|
|
|
|
return (err);
|
|
}
|
|
|
|
/**
|
|
* twl_vreg_set_voltage - public interface to write the voltage on a regulator
|
|
* @dev: TWL VREG device
|
|
* @name: the name of the regulator to read the voltage of
|
|
* @millivolts: the voltage to set in millivolts
|
|
*
|
|
* Sets the output voltage on a given regulator. If the regulator is a fixed
|
|
* voltage reg then the @millivolts value should match the fixed voltage. If
|
|
* a variable regulator then the @millivolt value must fit within the max/min
|
|
* range of the given regulator.
|
|
*
|
|
* LOCKING:
|
|
* Internally the function takes and releases the TWL VREG lock.
|
|
*
|
|
* RETURNS:
|
|
* Zero on success or a negative error code on failure.
|
|
*/
|
|
int
|
|
twl_vreg_set_voltage(device_t dev, const char *name, int millivolts)
|
|
{
|
|
struct twl_vreg_softc *sc;
|
|
struct twl_regulator_entry *regulator;
|
|
int err = EINVAL;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
TWL_VREG_SLOCK(sc);
|
|
|
|
LIST_FOREACH(regulator, &sc->sc_vreg_list, entries) {
|
|
if (strcmp(regulator->name, name) == 0) {
|
|
err = twl_vreg_write_regulator_voltage(sc, regulator, millivolts);
|
|
break;
|
|
}
|
|
}
|
|
|
|
TWL_VREG_SUNLOCK(sc);
|
|
|
|
return (err);
|
|
}
|
|
|
|
/**
|
|
* twl_sysctl_voltage - reads or writes the voltage for a regulator
|
|
* @SYSCTL_HANDLER_ARGS: arguments for the callback
|
|
*
|
|
* Callback for the sysctl entry for the regulator, simply used to return
|
|
* the voltage on a particular regulator.
|
|
*
|
|
* LOCKING:
|
|
* Takes the TWL_VREG shared lock internally.
|
|
*
|
|
* RETURNS:
|
|
* Zero on success or an error code on failure.
|
|
*/
|
|
static int
|
|
twl_vreg_sysctl_voltage(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct twl_vreg_softc *sc = (struct twl_vreg_softc*)arg1;
|
|
struct twl_regulator_entry *regulator;
|
|
int voltage;
|
|
int found = 0;
|
|
|
|
TWL_VREG_SLOCK(sc);
|
|
|
|
/* Find the regulator with the matching name */
|
|
LIST_FOREACH(regulator, &sc->sc_vreg_list, entries) {
|
|
if (strcmp(regulator->name, oidp->oid_name) == 0) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Sanity check that we found the regulator */
|
|
if (!found) {
|
|
TWL_VREG_SUNLOCK(sc);
|
|
return (EINVAL);
|
|
}
|
|
|
|
twl_vreg_read_regulator_voltage(sc, regulator, &voltage);
|
|
|
|
TWL_VREG_SUNLOCK(sc);
|
|
|
|
return sysctl_handle_int(oidp, &voltage, 0, req);
|
|
}
|
|
|
|
/**
|
|
* twl_add_regulator - adds single voltage regulator sysctls for the device
|
|
* @sc: device soft context
|
|
* @name: the name of the regulator
|
|
* @nsub: the number of the subdevice
|
|
* @regbase: the base address of the voltage regulator registers
|
|
* @fixed_voltage: if a fixed voltage regulator this defines it's voltage
|
|
* @voltages: if a variable voltage regulator, an array of possible voltages
|
|
* @num_voltages: the number of entries @voltages
|
|
*
|
|
* Adds a voltage regulator to the device and also a sysctl interface for the
|
|
* regulator.
|
|
*
|
|
* LOCKING:
|
|
* The TWL_VEG exclusive lock must be held while this function is called.
|
|
*
|
|
* RETURNS:
|
|
* Pointer to the new regulator entry on success, otherwise on failure NULL.
|
|
*/
|
|
static struct twl_regulator_entry*
|
|
twl_vreg_add_regulator(struct twl_vreg_softc *sc, const char *name,
|
|
uint8_t nsub, uint8_t regbase, uint16_t fixed_voltage,
|
|
const uint16_t *voltages, uint32_t num_voltages)
|
|
{
|
|
struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev);
|
|
struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev);
|
|
struct twl_regulator_entry *new;
|
|
|
|
new = malloc(sizeof(struct twl_regulator_entry), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
if (new == NULL)
|
|
return (NULL);
|
|
|
|
|
|
strncpy(new->name, name, TWL_VREG_MAX_NAMELEN);
|
|
new->name[TWL_VREG_MAX_NAMELEN - 1] = '\0';
|
|
|
|
new->sub_dev = nsub;
|
|
new->reg_off = regbase;
|
|
|
|
new->fixed_voltage = fixed_voltage;
|
|
|
|
new->supp_voltages = voltages;
|
|
new->num_supp_voltages = num_voltages;
|
|
|
|
|
|
/* Add a sysctl entry for the voltage */
|
|
new->oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, name,
|
|
CTLTYPE_INT | CTLFLAG_RD, sc, 0,
|
|
twl_vreg_sysctl_voltage, "I", "voltage regulator");
|
|
|
|
/* Finally add the regulator to list of supported regulators */
|
|
LIST_INSERT_HEAD(&sc->sc_vreg_list, new, entries);
|
|
|
|
return (new);
|
|
}
|
|
|
|
/**
|
|
* twl_vreg_add_regulators - adds any voltage regulators to the device
|
|
* @sc: device soft context
|
|
* @chip: the name of the chip used in the hints
|
|
* @regulators: the list of possible voltage regulators
|
|
*
|
|
* Loops over the list of regulators and matches up with the FDT values,
|
|
* adjusting the actual voltage based on the supplied values.
|
|
*
|
|
* LOCKING:
|
|
* The TWL_VEG exclusive lock must be held while this function is called.
|
|
*
|
|
* RETURNS:
|
|
* Always returns 0.
|
|
*/
|
|
static int
|
|
twl_vreg_add_regulators(struct twl_vreg_softc *sc,
|
|
const struct twl_regulator *regulators)
|
|
{
|
|
int err;
|
|
int millivolts;
|
|
const struct twl_regulator *walker;
|
|
struct twl_regulator_entry *entry;
|
|
phandle_t child;
|
|
char rnames[256];
|
|
char *name, *voltage;
|
|
int len = 0, prop_len;
|
|
|
|
|
|
/* Add the regulators from the list */
|
|
walker = ®ulators[0];
|
|
while (walker->name != NULL) {
|
|
|
|
/* Add the regulator to the list */
|
|
entry = twl_vreg_add_regulator(sc, walker->name, walker->subdev,
|
|
walker->regbase, walker->fixedvoltage,
|
|
walker->voltages, walker->num_voltages);
|
|
if (entry == NULL)
|
|
continue;
|
|
|
|
walker++;
|
|
}
|
|
|
|
|
|
/* Check if the FDT is telling us to set any voltages */
|
|
child = ofw_bus_get_node(sc->sc_pdev);
|
|
if (child) {
|
|
|
|
prop_len = OF_getprop(child, "voltage-regulators", rnames, sizeof(rnames));
|
|
while (len < prop_len) {
|
|
name = rnames + len;
|
|
len += strlen(name) + 1;
|
|
if ((len >= prop_len) || (name[0] == '\0'))
|
|
break;
|
|
|
|
voltage = rnames + len;
|
|
len += strlen(voltage) + 1;
|
|
if (voltage[0] == '\0')
|
|
break;
|
|
|
|
millivolts = strtoul(voltage, NULL, 0);
|
|
|
|
LIST_FOREACH(entry, &sc->sc_vreg_list, entries) {
|
|
if (strcmp(entry->name, name) == 0) {
|
|
twl_vreg_write_regulator_voltage(sc, entry, millivolts);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (twl_vreg_debug) {
|
|
LIST_FOREACH(entry, &sc->sc_vreg_list, entries) {
|
|
err = twl_vreg_read_regulator_voltage(sc, entry, &millivolts);
|
|
if (!err)
|
|
device_printf(sc->sc_dev, "%s : %d mV\n", entry->name, millivolts);
|
|
}
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* twl_vreg_init - initialises the list of regulators
|
|
* @dev: the twl_vreg device
|
|
*
|
|
* This function is called as an intrhook once interrupts have been enabled,
|
|
* this is done so that the driver has the option to enable/disable or set
|
|
* the voltage level based on settings providied in the FDT.
|
|
*
|
|
* LOCKING:
|
|
* Takes the exclusive lock in the function.
|
|
*/
|
|
static void
|
|
twl_vreg_init(void *dev)
|
|
{
|
|
struct twl_vreg_softc *sc;
|
|
|
|
sc = device_get_softc((device_t)dev);
|
|
|
|
TWL_VREG_XLOCK(sc);
|
|
|
|
if (twl_is_4030(sc->sc_pdev))
|
|
twl_vreg_add_regulators(sc, twl4030_regulators);
|
|
else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev))
|
|
twl_vreg_add_regulators(sc, twl6030_regulators);
|
|
|
|
TWL_VREG_XUNLOCK(sc);
|
|
|
|
config_intrhook_disestablish(&sc->sc_init_hook);
|
|
}
|
|
|
|
static int
|
|
twl_vreg_probe(device_t dev)
|
|
{
|
|
if (twl_is_4030(device_get_parent(dev)))
|
|
device_set_desc(dev, "TI TWL4030 PMIC Voltage Regulators");
|
|
else if (twl_is_6025(device_get_parent(dev)) ||
|
|
twl_is_6030(device_get_parent(dev)))
|
|
device_set_desc(dev, "TI TWL6025/TWL6030 PMIC Voltage Regulators");
|
|
else
|
|
return (ENXIO);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
twl_vreg_attach(device_t dev)
|
|
{
|
|
struct twl_vreg_softc *sc;
|
|
|
|
sc = device_get_softc(dev);
|
|
sc->sc_dev = dev;
|
|
sc->sc_pdev = device_get_parent(dev);
|
|
|
|
TWL_VREG_LOCK_INIT(sc);
|
|
|
|
LIST_INIT(&sc->sc_vreg_list);
|
|
|
|
/* We have to wait until interrupts are enabled. I2C read and write
|
|
* only works if the interrupts are available.
|
|
*/
|
|
sc->sc_init_hook.ich_func = twl_vreg_init;
|
|
sc->sc_init_hook.ich_arg = dev;
|
|
|
|
if (config_intrhook_establish(&sc->sc_init_hook) != 0)
|
|
return (ENOMEM);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
twl_vreg_detach(device_t dev)
|
|
{
|
|
struct twl_vreg_softc *sc;
|
|
struct twl_regulator_entry *regulator;
|
|
struct twl_regulator_entry *tmp;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
/* Take the lock and free all the added regulators */
|
|
TWL_VREG_XLOCK(sc);
|
|
|
|
LIST_FOREACH_SAFE(regulator, &sc->sc_vreg_list, entries, tmp) {
|
|
LIST_REMOVE(regulator, entries);
|
|
sysctl_remove_oid(regulator->oid, 1, 0);
|
|
free(regulator, M_DEVBUF);
|
|
}
|
|
|
|
TWL_VREG_XUNLOCK(sc);
|
|
|
|
TWL_VREG_LOCK_DESTROY(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static device_method_t twl_vreg_methods[] = {
|
|
DEVMETHOD(device_probe, twl_vreg_probe),
|
|
DEVMETHOD(device_attach, twl_vreg_attach),
|
|
DEVMETHOD(device_detach, twl_vreg_detach),
|
|
|
|
{0, 0},
|
|
};
|
|
|
|
static driver_t twl_vreg_driver = {
|
|
"twl_vreg",
|
|
twl_vreg_methods,
|
|
sizeof(struct twl_vreg_softc),
|
|
};
|
|
|
|
static devclass_t twl_vreg_devclass;
|
|
|
|
DRIVER_MODULE(twl_vreg, twl, twl_vreg_driver, twl_vreg_devclass, 0, 0);
|
|
MODULE_VERSION(twl_vreg, 1);
|