summaryrefslogtreecommitdiffstats
path: root/sys/arm/ti/twl
diff options
context:
space:
mode:
Diffstat (limited to 'sys/arm/ti/twl')
-rw-r--r--sys/arm/ti/twl/twl.c464
-rw-r--r--sys/arm/ti/twl/twl.h39
-rw-r--r--sys/arm/ti/twl/twl_clks.c675
-rw-r--r--sys/arm/ti/twl/twl_clks.h38
-rw-r--r--sys/arm/ti/twl/twl_vreg.c1053
-rw-r--r--sys/arm/ti/twl/twl_vreg.h36
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 = &reg;
+ /* 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 = &regulators[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_ */
OpenPOWER on IntegriCloud