diff options
Diffstat (limited to 'sys/arm/ti/twl')
-rw-r--r-- | sys/arm/ti/twl/twl.c | 464 | ||||
-rw-r--r-- | sys/arm/ti/twl/twl.h | 39 | ||||
-rw-r--r-- | sys/arm/ti/twl/twl_clks.c | 675 | ||||
-rw-r--r-- | sys/arm/ti/twl/twl_clks.h | 38 | ||||
-rw-r--r-- | sys/arm/ti/twl/twl_vreg.c | 1053 | ||||
-rw-r--r-- | sys/arm/ti/twl/twl_vreg.h | 36 |
6 files changed, 2305 insertions, 0 deletions
diff --git a/sys/arm/ti/twl/twl.c b/sys/arm/ti/twl/twl.c new file mode 100644 index 0000000..8edaf2f --- /dev/null +++ b/sys/arm/ti/twl/twl.c @@ -0,0 +1,464 @@ +/*- + * 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 and + * Audio CODEC devices. + * + * This code is based on the Linux TWL multifunctional device driver, which is + * copyright (C) 2005-2006 Texas Instruments, Inc. + * + * These chips are typically used as support ICs for the OMAP range of embedded + * ARM processes/SOC from Texas Instruments. They are typically used to control + * on board voltages, however some variants have other features like audio + * codecs, USB OTG transceivers, RTC, PWM, etc. + * + * This driver acts as a bus for more specific companion devices. + * + */ + +#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/mutex.h> +#include <sys/malloc.h> + +#include <machine/bus.h> +#include <machine/cpu.h> +#include <machine/cpufunc.h> +#include <machine/frame.h> +#include <machine/resource.h> +#include <machine/intr.h> + +#include <dev/iicbus/iicbus.h> +#include <dev/iicbus/iiconf.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include "arm/ti/twl/twl.h" + +/* TWL device IDs */ +#define TWL_DEVICE_UNKNOWN 0xffff +#define TWL_DEVICE_4030 0x4030 +#define TWL_DEVICE_6025 0x6025 +#define TWL_DEVICE_6030 0x6030 + +/* Each TWL device typically has more than one I2C address */ +#define TWL_MAX_SUBADDRS 4 + +/* The maxium number of bytes that can be written in one call */ +#define TWL_MAX_IIC_DATA_SIZE 63 + +/* The TWL devices typically use 4 I2C address for the different internal + * register sets, plus one SmartReflex I2C address. + */ +#define TWL_CHIP_ID0 0x48 +#define TWL_CHIP_ID1 0x49 +#define TWL_CHIP_ID2 0x4A +#define TWL_CHIP_ID3 0x4B + +#define TWL_SMARTREFLEX_CHIP_ID 0x12 + +#define TWL_INVALID_CHIP_ID 0xff + +struct twl_softc { + device_t sc_dev; + struct mtx sc_mtx; + unsigned int sc_type; + + uint8_t sc_subaddr_map[TWL_MAX_SUBADDRS]; + + struct intr_config_hook sc_scan_hook; + + device_t sc_vreg; + device_t sc_clks; +}; + +/** + * Macros for driver mutex locking + */ +#define TWL_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define TWL_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define TWL_LOCK_INIT(_sc) \ + mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->sc_dev), \ + "twl", MTX_DEF) +#define TWL_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); +#define TWL_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED); +#define TWL_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED); + + +/** + * twl_is_4030 - returns true if the device is TWL4030 + * twl_is_6025 - returns true if the device is TWL6025 + * twl_is_6030 - returns true if the device is TWL6030 + * @sc: device soft context + * + * Returns a non-zero value if the device matches. + * + * RETURNS: + * Returns a non-zero value if the device matches, otherwise zero. + */ +int +twl_is_4030(device_t dev) +{ + struct twl_softc *sc = device_get_softc(dev); + return (sc->sc_type == TWL_DEVICE_4030); +} + +int +twl_is_6025(device_t dev) +{ + struct twl_softc *sc = device_get_softc(dev); + return (sc->sc_type == TWL_DEVICE_6025); +} + +int +twl_is_6030(device_t dev) +{ + struct twl_softc *sc = device_get_softc(dev); + return (sc->sc_type == TWL_DEVICE_6030); +} + + +/** + * twl_read - read one or more registers from the TWL device + * @sc: device soft context + * @nsub: the sub-module to read from + * @reg: the register offset within the module to read + * @buf: buffer to store the bytes in + * @cnt: the number of bytes to read + * + * Reads one or more registers and stores the result in the suppled buffer. + * + * RETURNS: + * Zero on success or an error code on failure. + */ +int +twl_read(device_t dev, uint8_t nsub, uint8_t reg, uint8_t *buf, uint16_t cnt) +{ + struct twl_softc *sc; + struct iic_msg msg[2]; + uint8_t addr; + int rc; + + sc = device_get_softc(dev); + + TWL_LOCK(sc); + addr = sc->sc_subaddr_map[nsub]; + TWL_UNLOCK(sc); + + if (addr == TWL_INVALID_CHIP_ID) + return (EIO); + + + /* Set the address to read from */ + msg[0].slave = addr; + msg[0].flags = IIC_M_WR | IIC_M_NOSTOP; + msg[0].len = 1; + msg[0].buf = ® + /* Read the data back */ + msg[1].slave = addr; + msg[1].flags = IIC_M_RD; + msg[1].len = cnt; + msg[1].buf = buf; + + rc = iicbus_transfer(dev, msg, 2); + if (rc != 0) { + device_printf(dev, "iicbus read failed (adr:0x%02x, reg:0x%02x)\n", + addr, reg); + return (EIO); + } + + return (0); +} + +/** + * twl_write - writes one or more registers to the TWL device + * @sc: device soft context + * @nsub: the sub-module to read from + * @reg: the register offset within the module to read + * @buf: data to write + * @cnt: the number of bytes to write + * + * Writes one or more registers. + * + * RETURNS: + * Zero on success or a negative error code on failure. + */ +int +twl_write(device_t dev, uint8_t nsub, uint8_t reg, uint8_t *buf, uint16_t cnt) +{ + struct twl_softc *sc; + struct iic_msg msg; + uint8_t addr; + uint8_t tmp_buf[TWL_MAX_IIC_DATA_SIZE + 1]; + int rc; + + if (cnt > TWL_MAX_IIC_DATA_SIZE) + return (ENOMEM); + + /* Set the register address as the first byte */ + tmp_buf[0] = reg; + memcpy(&tmp_buf[1], buf, cnt); + + sc = device_get_softc(dev); + + TWL_LOCK(sc); + addr = sc->sc_subaddr_map[nsub]; + TWL_UNLOCK(sc); + + if (addr == TWL_INVALID_CHIP_ID) + return (EIO); + + + /* Setup the transfer and execute it */ + msg.slave = addr; + msg.flags = IIC_M_WR; + msg.len = cnt + 1; + msg.buf = tmp_buf; + + rc = iicbus_transfer(dev, &msg, 1); + if (rc != 0) { + device_printf(sc->sc_dev, "iicbus write failed (adr:0x%02x, reg:0x%02x)\n", + addr, reg); + return (EIO); + } + + return (0); +} + +/** + * twl_test_present - checks if a device with given address is present + * @sc: device soft context + * @addr: the address of the device to scan for + * + * Sends just the address byte and checks for an ACK. If no ACK then device + * is assumed to not be present. + * + * RETURNS: + * EIO if device is not present, otherwise 0 is returned. + */ +static int +twl_test_present(struct twl_softc *sc, uint8_t addr) +{ + struct iic_msg msg; + uint8_t tmp; + + /* Set the address to read from */ + msg.slave = addr; + msg.flags = IIC_M_RD; + msg.len = 1; + msg.buf = &tmp; + + if (iicbus_transfer(sc->sc_dev, &msg, 1) != 0) + return (EIO); + + return (0); +} + +/** + * twl_scan - scans the i2c bus for sub modules + * @dev: the twl device + * + * TWL devices don't just have one i2c slave address, rather they have up to + * 5 other addresses, each is for separate modules within the device. This + * function scans the bus for 4 possible sub-devices and stores the info + * internally. + * + */ +static void +twl_scan(void *dev) +{ + struct twl_softc *sc; + unsigned i; + uint8_t devs[TWL_MAX_SUBADDRS]; + uint8_t base = TWL_CHIP_ID0; + + sc = device_get_softc((device_t)dev); + + memset(devs, TWL_INVALID_CHIP_ID, TWL_MAX_SUBADDRS); + + /* Try each of the addresses (0x48, 0x49, 0x4a & 0x4b) to determine which + * sub modules we have. + */ + for (i = 0; i < TWL_MAX_SUBADDRS; i++) { + if (twl_test_present(sc, (base + i)) == 0) { + devs[i] = (base + i); + device_printf(sc->sc_dev, "Found (sub)device at 0x%02x\n", (base + i)); + } + } + + TWL_LOCK(sc); + memcpy(sc->sc_subaddr_map, devs, TWL_MAX_SUBADDRS); + TWL_UNLOCK(sc); + + /* Finished with the interrupt hook */ + config_intrhook_disestablish(&sc->sc_scan_hook); +} + +/** + * twl_probe - + * @dev: the twl device + * + * Scans the FDT for a match for the device, possible compatible device + * strings are; "ti,twl6030", "ti,twl6025", "ti,twl4030". + * + * The FDT compat string also determines the type of device (it is currently + * not possible to dynamically determine the device type). + * + */ +static int +twl_probe(device_t dev) +{ + phandle_t node; + const char *compat; + int len, l; + struct twl_softc *sc; + + if ((compat = ofw_bus_get_compat(dev)) == NULL) + return (ENXIO); + + if ((node = ofw_bus_get_node(dev)) == 0) + return (ENXIO); + + /* Get total 'compatible' prop len */ + if ((len = OF_getproplen(node, "compatible")) <= 0) + return (ENXIO); + + sc = device_get_softc(dev); + sc->sc_dev = dev; + sc->sc_type = TWL_DEVICE_UNKNOWN; + + while (len > 0) { + if (strncasecmp(compat, "ti,twl6030", 10) == 0) + sc->sc_type = TWL_DEVICE_6030; + else if (strncasecmp(compat, "ti,twl6025", 10) == 0) + sc->sc_type = TWL_DEVICE_6025; + else if (strncasecmp(compat, "ti,twl4030", 10) == 0) + sc->sc_type = TWL_DEVICE_4030; + + if (sc->sc_type != TWL_DEVICE_UNKNOWN) + break; + + /* Slide to the next sub-string. */ + l = strlen(compat) + 1; + compat += l; + len -= l; + } + + switch (sc->sc_type) { + case TWL_DEVICE_4030: + device_set_desc(dev, "TI TWL4030/TPS659x0 Companion IC"); + break; + case TWL_DEVICE_6025: + device_set_desc(dev, "TI TWL6025 Companion IC"); + break; + case TWL_DEVICE_6030: + device_set_desc(dev, "TI TWL6030 Companion IC"); + break; + case TWL_DEVICE_UNKNOWN: + default: + return (ENXIO); + } + + return (0); +} + +static int +twl_attach(device_t dev) +{ + struct twl_softc *sc; + + sc = device_get_softc(dev); + sc->sc_dev = dev; + + TWL_LOCK_INIT(sc); + + /* We have to wait until interrupts are enabled. I2C read and write + * only works if the interrupts are available. + */ + sc->sc_scan_hook.ich_func = twl_scan; + sc->sc_scan_hook.ich_arg = dev; + + if (config_intrhook_establish(&sc->sc_scan_hook) != 0) + return (ENOMEM); + + /* FIXME: should be in DTS file */ + if ((sc->sc_vreg = device_add_child(dev, "twl_vreg", -1)) == NULL) + device_printf(dev, "could not allocate twl_vreg instance\n"); + if ((sc->sc_clks = device_add_child(dev, "twl_clks", -1)) == NULL) + device_printf(dev, "could not allocate twl_clks instance\n"); + + return (bus_generic_attach(dev)); +} + +static int +twl_detach(device_t dev) +{ + struct twl_softc *sc; + + sc = device_get_softc(dev); + + if (sc->sc_vreg) + device_delete_child(dev, sc->sc_vreg); + if (sc->sc_clks) + device_delete_child(dev, sc->sc_clks); + + + TWL_LOCK_DESTROY(sc); + + return (0); +} + +static device_method_t twl_methods[] = { + DEVMETHOD(device_probe, twl_probe), + DEVMETHOD(device_attach, twl_attach), + DEVMETHOD(device_detach, twl_detach), + + {0, 0}, +}; + +static driver_t twl_driver = { + "twl", + twl_methods, + sizeof(struct twl_softc), +}; +static devclass_t twl_devclass; + +DRIVER_MODULE(twl, iicbus, twl_driver, twl_devclass, 0, 0); +MODULE_VERSION(twl, 1); diff --git a/sys/arm/ti/twl/twl.h b/sys/arm/ti/twl/twl.h new file mode 100644 index 0000000..07f2cfd --- /dev/null +++ b/sys/arm/ti/twl/twl.h @@ -0,0 +1,39 @@ +/*- + * 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. + * + * $FreeBSD$ + */ +#ifndef _TWL_H_ +#define _TWL_H_ + +int twl_read(device_t dev, uint8_t nsub, uint8_t reg, uint8_t *buf, uint16_t cnt); +int twl_write(device_t dev, uint8_t nsub, uint8_t reg, uint8_t *buf, uint16_t cnt); + +int twl_is_4030(device_t dev); +int twl_is_6025(device_t dev); +int twl_is_6030(device_t dev); + +#endif /* _TWL_H_ */ diff --git a/sys/arm/ti/twl/twl_clks.c b/sys/arm/ti/twl/twl_clks.c new file mode 100644 index 0000000..d78a733 --- /dev/null +++ b/sys/arm/ti/twl/twl_clks.c @@ -0,0 +1,675 @@ +/*- + * Copyright (c) 2012 + * Ben Gray <bgray@freebsd.org>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY 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 external clocks, allows for enabling & + * disabling their output. + * + * + * + * 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: + * + * external-clocks = "name1", "state1", + * "name2", "state2", + * etc; + * + * Each override should be a pair, the first entry is the name of the clock + * the second is the state to set, possible strings are either "on" or "off". + * + */ + +#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/frame.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_clks.h" + + +static int twl_clks_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 clk regulator register set + */ +#define TWL_CLKS_GRP 0x00 /* Regulator GRP register */ +#define TWL_CLKS_STATE 0x02 /* TWL6030 only */ + + + +/** + * Support voltage regulators for the different IC's + */ +struct twl_clock { + const char *name; + uint8_t subdev; + uint8_t regbase; +}; + +static const struct twl_clock twl4030_clocks[] = { + { "32kclkout", 0, 0x8e }, + { NULL, 0, 0x00 } +}; + +static const struct twl_clock twl6030_clocks[] = { + { "clk32kg", 0, 0xbc }, + { "clk32kao", 0, 0xb9 }, + { "clk32kaudio", 0, 0xbf }, + { NULL, 0, 0x00 } +}; + +#define TWL_CLKS_MAX_NAMELEN 32 + +struct twl_clk_entry { + LIST_ENTRY(twl_clk_entry) link; + struct sysctl_oid *oid; + char name[TWL_CLKS_MAX_NAMELEN]; + uint8_t sub_dev; /* the sub-device number for the clock */ + uint8_t reg_off; /* register base address of the clock */ +}; + +struct twl_clks_softc { + device_t sc_dev; /* twl_clk device */ + device_t sc_pdev; /* parent device (twl) */ + struct sx sc_sx; /* internal locking */ + struct intr_config_hook sc_init_hook; + LIST_HEAD(twl_clk_list, twl_clk_entry) sc_clks_list; +}; + +/** + * Macros for driver shared locking + */ +#define TWL_CLKS_XLOCK(_sc) sx_xlock(&(_sc)->sc_sx) +#define TWL_CLKS_XUNLOCK(_sc) sx_xunlock(&(_sc)->sc_sx) +#define TWL_CLKS_SLOCK(_sc) sx_slock(&(_sc)->sc_sx) +#define TWL_CLKS_SUNLOCK(_sc) sx_sunlock(&(_sc)->sc_sx) +#define TWL_CLKS_LOCK_INIT(_sc) sx_init(&(_sc)->sc_sx, "twl_clks") +#define TWL_CLKS_LOCK_DESTROY(_sc) sx_destroy(&(_sc)->sc_sx); + +#define TWL_CLKS_ASSERT_LOCKED(_sc) sx_assert(&(_sc)->sc_sx, SA_LOCKED); + +#define TWL_CLKS_LOCK_UPGRADE(_sc) \ + do { \ + while (!sx_try_upgrade(&(_sc)->sc_sx)) \ + pause("twl_clks_ex", (hz / 100)); \ + } while(0) +#define TWL_CLKS_LOCK_DOWNGRADE(_sc) sx_downgrade(&(_sc)->sc_sx); + + + + +/** + * twl_clks_read_1 - read single register from the TWL device + * twl_clks_write_1 - writes 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_clks_read_1(struct twl_clks_softc *sc, struct twl_clk_entry *clk, + uint8_t off, uint8_t *val) +{ + return (twl_read(sc->sc_pdev, clk->sub_dev, clk->reg_off + off, val, 1)); +} + +static inline int +twl_clks_write_1(struct twl_clks_softc *sc, struct twl_clk_entry *clk, + uint8_t off, uint8_t val) +{ + return (twl_write(sc->sc_pdev, clk->sub_dev, clk->reg_off + off, &val, 1)); +} + + +/** + * twl_clks_is_enabled - determines if a clock is enabled + * @dev: TWL CLK device + * @name: the name of the clock + * @enabled: upon return will contain the 'enabled' state + * + * LOCKING: + * Internally the function takes and releases the TWL lock. + * + * RETURNS: + * Zero on success or a negative error code on failure. + */ +int +twl_clks_is_enabled(device_t dev, const char *name, int *enabled) +{ + struct twl_clks_softc *sc = device_get_softc(dev); + struct twl_clk_entry *clk; + int found = 0; + int err; + uint8_t grp, state; + + TWL_CLKS_SLOCK(sc); + + LIST_FOREACH(clk, &sc->sc_clks_list, link) { + if (strcmp(clk->name, name) == 0) { + found = 1; + break; + } + } + + if (!found) { + TWL_CLKS_SUNLOCK(sc); + return (EINVAL); + } + + + if (twl_is_4030(sc->sc_pdev)) { + + err = twl_clks_read_1(sc, clk, TWL_CLKS_GRP, &grp); + if (!err) + *enabled = (grp & TWL4030_P1_GRP); + + } else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev)) { + + TWL_CLKS_LOCK_UPGRADE(sc); + + /* Check the clock is in the application group */ + if (twl_is_6030(sc->sc_pdev)) { + err = twl_clks_read_1(sc, clk, TWL_CLKS_GRP, &grp); + if (err) { + TWL_CLKS_LOCK_DOWNGRADE(sc); + goto done; + } + + if (!(grp & TWL6030_P1_GRP)) { + TWL_CLKS_LOCK_DOWNGRADE(sc); + *enabled = 0; /* disabled */ + goto done; + } + } + + /* Read the application mode state and verify it's ON */ + err = twl_clks_read_1(sc, clk, TWL_CLKS_STATE, &state); + if (!err) + *enabled = ((state & 0x0C) == 0x04); + + TWL_CLKS_LOCK_DOWNGRADE(sc); + + } else { + err = EINVAL; + } + +done: + TWL_CLKS_SUNLOCK(sc); + return (err); +} + + +/** + * twl_clks_set_state - enables/disables a clock output + * @sc: device context + * @clk: the clock entry to enable/disable + * @enable: non-zero the clock is enabled, zero the clock is disabled + * + * LOCKING: + * The TWL CLK lock must be held before this function is called. + * + * RETURNS: + * Zero on success or an error code on failure. + */ +static int +twl_clks_set_state(struct twl_clks_softc *sc, struct twl_clk_entry *clk, + int enable) +{ + int xlocked; + int err; + uint8_t grp; + + TWL_CLKS_ASSERT_LOCKED(sc); + + /* Upgrade the lock to exclusive because about to perform read-mod-write */ + xlocked = sx_xlocked(&sc->sc_sx); + if (!xlocked) + TWL_CLKS_LOCK_UPGRADE(sc); + + err = twl_clks_read_1(sc, clk, TWL_CLKS_GRP, &grp); + if (err) + goto done; + + if (twl_is_4030(sc->sc_pdev)) { + + /* On the TWL4030 we just need to ensure the clock is in the right + * power domain, don't need to turn on explicitly like TWL6030. + */ + if (enable) + grp |= TWL4030_P1_GRP; + else + grp &= ~(TWL4030_P1_GRP | TWL4030_P2_GRP | TWL4030_P3_GRP); + + err = twl_clks_write_1(sc, clk, TWL_CLKS_GRP, grp); + + } else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev)) { + + /* Make sure the clock belongs to at least the APP power group */ + if (twl_is_6030(sc->sc_pdev) && !(grp & TWL6030_P1_GRP)) { + grp |= TWL6030_P1_GRP; + err = twl_clks_write_1(sc, clk, TWL_CLKS_GRP, grp); + if (err) + goto done; + } + + /* 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; + + /* Set the state of the clock */ + if (enable) + err = twl_clks_write_1(sc, clk, TWL_CLKS_STATE, (grp << 5) | 0x01); + else + err = twl_clks_write_1(sc, clk, TWL_CLKS_STATE, (grp << 5)); + + } else { + + err = EINVAL; + } + +done: + if (!xlocked) + TWL_CLKS_LOCK_DOWNGRADE(sc); + + if ((twl_clks_debug > 1) && !err) + device_printf(sc->sc_dev, "%s : %sabled\n", clk->name, + enable ? "en" : "dis"); + + return (err); +} + + +/** + * twl_clks_disable - disables a clock output + * @dev: TWL clk device +* @name: the name of the clock + * + * LOCKING: + * Internally the function takes and releases the TWL lock. + * + * RETURNS: +* Zero on success or an error code on failure. + */ +int +twl_clks_disable(device_t dev, const char *name) +{ + struct twl_clks_softc *sc = device_get_softc(dev); + struct twl_clk_entry *clk; + int err = EINVAL; + + TWL_CLKS_SLOCK(sc); + + LIST_FOREACH(clk, &sc->sc_clks_list, link) { + if (strcmp(clk->name, name) == 0) { + err = twl_clks_set_state(sc, clk, 0); + break; + } + } + + TWL_CLKS_SUNLOCK(sc); + return (err); +} + +/** + * twl_clks_enable - enables a clock output + * @dev: TWL clk device + * @name: the name of the clock + * + * LOCKING: + * Internally the function takes and releases the TWL CLKS lock. + * + * RETURNS: + * Zero on success or an error code on failure. + */ +int +twl_clks_enable(device_t dev, const char *name) +{ + struct twl_clks_softc *sc = device_get_softc(dev); + struct twl_clk_entry *clk; + int err = EINVAL; + + TWL_CLKS_SLOCK(sc); + + LIST_FOREACH(clk, &sc->sc_clks_list, link) { + if (strcmp(clk->name, name) == 0) { + err = twl_clks_set_state(sc, clk, 1); + break; + } + } + + TWL_CLKS_SUNLOCK(sc); + return (err); +} + +/** + * twl_clks_sysctl_clock - reads the state of the clock + * @SYSCTL_HANDLER_ARGS: arguments for the callback + * + * Returns the clock status; disabled is zero and enabled is non-zero. + * + * LOCKING: + * It's expected the TWL lock is held while this function is called. + * + * RETURNS: + * EIO if device is not present, otherwise 0 is returned. + */ +static int +twl_clks_sysctl_clock(SYSCTL_HANDLER_ARGS) +{ + struct twl_clks_softc *sc = (struct twl_clks_softc*)arg1; + int err; + int enabled = 0; + + if ((err = twl_clks_is_enabled(sc->sc_dev, oidp->oid_name, &enabled)) != 0) + return err; + + return sysctl_handle_int(oidp, &enabled, 0, req); +} + +/** + * twl_clks_add_clock - adds single clock 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 clocks registers + * + * Adds a single clock to the device and also a sysctl interface for + * querying it's status. + * + * LOCKING: + * It's expected the exclusive lock is held while this function is called. + * + * RETURNS: + * Pointer to the new clock entry on success, otherwise NULL on failure. + */ +static struct twl_clk_entry* +twl_clks_add_clock(struct twl_clks_softc *sc, const char *name, + uint8_t nsub, uint8_t regbase) +{ + 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_clk_entry *new; + + TWL_CLKS_ASSERT_LOCKED(sc); + + new = malloc(sizeof(struct twl_clk_entry), M_DEVBUF, M_NOWAIT | M_ZERO); + if (new == NULL) + return (NULL); + + + strncpy(new->name, name, TWL_CLKS_MAX_NAMELEN); + new->name[TWL_CLKS_MAX_NAMELEN - 1] = '\0'; + + new->sub_dev = nsub; + new->reg_off = regbase; + + + + /* Add a sysctl entry for the clock */ + new->oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, name, + CTLTYPE_INT | CTLFLAG_RD, sc, 0, + twl_clks_sysctl_clock, "I", "external clock"); + + /* Finally add the regulator to list of supported regulators */ + LIST_INSERT_HEAD(&sc->sc_clks_list, new, link); + + return (new); +} + +/** + * twl_clks_add_clocks - populates the internal list of clocks + * @sc: device soft context + * @chip: the name of the chip used in the hints + * @clks the list of clocks supported by the device + * + * Loops over the list of clocks and adds them to the device context. Also + * scans the FDT to determine if there are any clocks that should be + * enabled/disabled automatically. + * + * LOCKING: + * Internally takes the exclusive lock while adding the clocks to the + * device context. + * + * RETURNS: + * Always returns 0. + */ +static int +twl_clks_add_clocks(struct twl_clks_softc *sc, const struct twl_clock *clks) +{ + int err; + const struct twl_clock *walker; + struct twl_clk_entry *entry; + phandle_t child; + char rnames[256]; + char *name, *state; + int len = 0, prop_len; + int enable; + + + TWL_CLKS_XLOCK(sc); + + /* Add the regulators from the list */ + walker = &clks[0]; + while (walker->name != NULL) { + + /* Add the regulator to the list */ + entry = twl_clks_add_clock(sc, walker->name, walker->subdev, + walker->regbase); + if (entry == NULL) + continue; + + walker++; + } + + /* Check for any FDT settings that need to be applied */ + child = ofw_bus_get_node(sc->sc_pdev); + if (child) { + + prop_len = OF_getprop(child, "external-clocks", rnames, sizeof(rnames)); + while (len < prop_len) { + name = rnames + len; + len += strlen(name) + 1; + if ((len >= prop_len) || (name[0] == '\0')) + break; + + state = rnames + len; + len += strlen(state) + 1; + if (state[0] == '\0') + break; + + enable = !strncmp(state, "on", 2); + + LIST_FOREACH(entry, &sc->sc_clks_list, link) { + if (strcmp(entry->name, name) == 0) { + twl_clks_set_state(sc, entry, enable); + break; + } + } + } + } + + TWL_CLKS_XUNLOCK(sc); + + + if (twl_clks_debug) { + LIST_FOREACH(entry, &sc->sc_clks_list, link) { + err = twl_clks_is_enabled(sc->sc_dev, entry->name, &enable); + if (!err) + device_printf(sc->sc_dev, "%s : %s\n", entry->name, + enable ? "on" : "off"); + } + } + + return (0); +} + +/** + * twl_clks_init - initialises the list of clocks + * @dev: the twl_clks 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 a clock + * based on settings providied in the FDT. + * + * LOCKING: + * May takes the exclusive lock in the function. + */ +static void +twl_clks_init(void *dev) +{ + struct twl_clks_softc *sc; + + sc = device_get_softc((device_t)dev); + + if (twl_is_4030(sc->sc_pdev)) + twl_clks_add_clocks(sc, twl4030_clocks); + else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev)) + twl_clks_add_clocks(sc, twl6030_clocks); + + config_intrhook_disestablish(&sc->sc_init_hook); +} + +static int +twl_clks_probe(device_t dev) +{ + if (twl_is_4030(device_get_parent(dev))) + device_set_desc(dev, "TI TWL4030 PMIC External Clocks"); + else if (twl_is_6025(device_get_parent(dev)) || + twl_is_6030(device_get_parent(dev))) + device_set_desc(dev, "TI TWL6025/TWL6030 PMIC External Clocks"); + else + return (ENXIO); + + return (0); +} + +static int +twl_clks_attach(device_t dev) +{ + struct twl_clks_softc *sc; + + sc = device_get_softc(dev); + sc->sc_dev = dev; + sc->sc_pdev = device_get_parent(dev); + + TWL_CLKS_LOCK_INIT(sc); + + LIST_INIT(&sc->sc_clks_list); + + + sc->sc_init_hook.ich_func = twl_clks_init; + sc->sc_init_hook.ich_arg = dev; + + if (config_intrhook_establish(&sc->sc_init_hook) != 0) + return (ENOMEM); + + return (0); +} + +static int +twl_clks_detach(device_t dev) +{ + struct twl_clks_softc *sc; + struct twl_clk_entry *clk; + struct twl_clk_entry *tmp; + + sc = device_get_softc(dev); + + TWL_CLKS_XLOCK(sc); + + LIST_FOREACH_SAFE(clk, &sc->sc_clks_list, link, tmp) { + LIST_REMOVE(clk, link); + sysctl_remove_oid(clk->oid, 1, 0); + free(clk, M_DEVBUF); + } + + TWL_CLKS_XUNLOCK(sc); + + TWL_CLKS_LOCK_DESTROY(sc); + + return (0); +} + +static device_method_t twl_clks_methods[] = { + DEVMETHOD(device_probe, twl_clks_probe), + DEVMETHOD(device_attach, twl_clks_attach), + DEVMETHOD(device_detach, twl_clks_detach), + + {0, 0}, +}; + +static driver_t twl_clks_driver = { + "twl_clks", + twl_clks_methods, + sizeof(struct twl_clks_softc), +}; + +static devclass_t twl_clks_devclass; + +DRIVER_MODULE(twl_clks, twl, twl_clks_driver, twl_clks_devclass, 0, 0); +MODULE_VERSION(twl_clks, 1); diff --git a/sys/arm/ti/twl/twl_clks.h b/sys/arm/ti/twl/twl_clks.h new file mode 100644 index 0000000..c5d89f3 --- /dev/null +++ b/sys/arm/ti/twl/twl_clks.h @@ -0,0 +1,38 @@ +/*- + * Copyright (c) 2012 + * Ben Gray <bgray@freebsd.org>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY 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. + * + * $FreeBSD$ + */ +#ifndef _TWL_CLKS_H_ +#define _TWL_CLKS_H_ + + +int twl_clks_enable(device_t dev, const char *name); +int twl_clks_disable(device_t dev, const char *name); +int twl_clks_is_enabled(device_t dev, const char *name, int *enabled); + + +#endif /* _TWL_CLKS_H_ */ diff --git a/sys/arm/ti/twl/twl_vreg.c b/sys/arm/ti/twl/twl_vreg.c new file mode 100644 index 0000000..26c2e26 --- /dev/null +++ b/sys/arm/ti/twl/twl_vreg.c @@ -0,0 +1,1053 @@ +/*- + * 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/frame.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 +}; +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 +}; +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); diff --git a/sys/arm/ti/twl/twl_vreg.h b/sys/arm/ti/twl/twl_vreg.h new file mode 100644 index 0000000..dc77dfc --- /dev/null +++ b/sys/arm/ti/twl/twl_vreg.h @@ -0,0 +1,36 @@ +/*- + * 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. + * + * $FreeBSD$ + */ +#ifndef _TWL_VREG_H_ +#define _TWL_VREG_H_ + + +int twl_vreg_get_voltage(device_t dev, const char *name, int *millivolts); +int twl_vreg_set_voltage(device_t dev, const char *name, int millivolts); + +#endif /* _TWL_VREG_H_ */ |