Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 21 Feb 2012 06:10:36 +0000 (UTC)
From:      Oleksandr Tymoshenko <gonzo@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-projects@freebsd.org
Subject:   svn commit: r231962 - in projects/armv6/sys/arm: conf ti/omap4 ti/twl
Message-ID:  <201202210610.q1L6Aa5C085746@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: gonzo
Date: Tue Feb 21 06:10:35 2012
New Revision: 231962
URL: http://svn.freebsd.org/changeset/base/231962

Log:
  Add TWL companion devices support by Ben R. Gray slightly modified
  by me:
  
  - style(9) cleanup
  - make clear distinction between bus driver and actual companion device
  - FDT-ize and get rid of references to hints
  - Hardcode TWL6030  support. Actual run-time chip detection: TBD
  - Make sysctls R/O. Unless there's something really pressing -
      do not let userland tinker with voltages.
  
  Submitted by:	Ben R. Gray

Added:
  projects/armv6/sys/arm/ti/twl/
  projects/armv6/sys/arm/ti/twl/twl.c
  projects/armv6/sys/arm/ti/twl/twl.h
  projects/armv6/sys/arm/ti/twl/twl_vreg.c
  projects/armv6/sys/arm/ti/twl/twl_vreg.h
Modified:
  projects/armv6/sys/arm/conf/PANDABOARD
  projects/armv6/sys/arm/ti/omap4/files.omap4

Modified: projects/armv6/sys/arm/conf/PANDABOARD
==============================================================================
--- projects/armv6/sys/arm/conf/PANDABOARD	Tue Feb 21 04:12:28 2012	(r231961)
+++ projects/armv6/sys/arm/conf/PANDABOARD	Tue Feb 21 06:10:35 2012	(r231962)
@@ -130,6 +130,8 @@ device		smsc		# SMSC LAN95xx USB Etherne
 
 # OMAP-specific devices
 device		omap_dma
+device		twl
+device		twl_vreg
 
 # Flattened Device Tree
 options         FDT

Modified: projects/armv6/sys/arm/ti/omap4/files.omap4
==============================================================================
--- projects/armv6/sys/arm/ti/omap4/files.omap4	Tue Feb 21 04:12:28 2012	(r231961)
+++ projects/armv6/sys/arm/ti/omap4/files.omap4	Tue Feb 21 06:10:35 2012	(r231962)
@@ -28,4 +28,7 @@ arm/ti/omap_mmc.c				optional	mmc
 arm/ti/omap4/omap4_prcm_clks.c			standard
 arm/ti/omap4/omap4_scm_padconf.c		standard
 
+arm/ti/twl/twl.c				optional	twl
+arm/ti/twl/twl_vreg.c				optional	twl twl_vreg
+
 dev/uart/uart_dev_ns8250.c			optional	uart

Added: projects/armv6/sys/arm/ti/twl/twl.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ projects/armv6/sys/arm/ti/twl/twl.c	Tue Feb 21 06:10:35 2012	(r231962)
@@ -0,0 +1,380 @@
+/*-
+ * 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 driver acts as a bus for mor 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/iiconf.h>
+#include "iicbus_if.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
+
+/**
+ *	Structure that stores the driver context.
+ *
+ *	This structure is allocated during driver attach.
+ */
+struct twl_softc {
+	device_t		sc_dev;
+	struct mtx		sc_mtx;
+
+	uint8_t			sc_subaddr_map[TWL_MAX_SUBADDRS];
+
+	struct intr_config_hook	sc_scan_hook;
+
+	device_t		sc_vreg;
+};
+
+/**
+ *	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_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 registers and stores the result in the suppled buffer.
+ *
+ *	LOCKING:
+ *	Expects the TWL lock to be held.
+ *
+ *	RETURNS:
+ *	Zero on success or a negative 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];
+	if (addr == TWL_INVALID_CHIP_ID) {
+		TWL_UNLOCK(sc);
+		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(sc->sc_dev, msg, 2);
+
+	TWL_UNLOCK(sc);
+
+	if (rc != 0) {
+		device_printf(sc->sc_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.
+ *
+ *	LOCKING:
+ *	Expects the TWL lock to be held.
+ *
+ *	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];
+	if (addr == TWL_INVALID_CHIP_ID) {
+		TWL_UNLOCK(sc);
+		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(sc->sc_dev, &msg, 1);
+
+	TWL_UNLOCK(sc);
+
+	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, otherwise device is present.
+ *
+ *	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_test_present(struct twl_softc *sc, uint8_t addr)
+{
+	struct iic_msg msg;
+	uint8_t tmp;
+
+	TWL_ASSERT_LOCKED(sc);
+
+	/* 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 - disables IRQ's on the given channel
+ *	@ch: the channel to disable IRQ's on
+ *
+ *	Disable interupt generation for the given channel.
+ *
+ *	RETURNS:
+ *	BUS_PROBE_NOWILDCARD
+ */
+static void
+twl_scan(void *dev)
+{
+	struct twl_softc *sc;
+	unsigned i;
+	uint8_t base = TWL_CHIP_ID0;
+
+	sc = device_get_softc((device_t)dev);
+
+	TWL_LOCK(sc);
+
+	memset(sc->sc_subaddr_map, 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) {
+			sc->sc_subaddr_map[i] = (base + i);
+			device_printf(sc->sc_dev, "Found (sub)device at 0x%02x\n", (base + i));
+		}
+	}
+
+	TWL_UNLOCK(sc);
+
+	/* Finished with the interrupt hook */
+	config_intrhook_disestablish(&sc->sc_scan_hook);
+}
+
+static void
+twl_identify(driver_t *driver, device_t parent)
+{
+
+        BUS_ADD_CHILD(parent, 0, "twl", 0);
+}
+
+static int
+twl_probe(device_t dev)
+{
+	device_set_desc(dev, "TI TWL4030/TWL5030/TWL60x0/TPS659x0 Companion IC");
+
+	return (BUS_PROBE_NOWILDCARD);
+}
+
+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");
+
+	return (bus_generic_attach(dev));
+}
+
+static int
+twl_detach(device_t dev)
+{
+	struct twl_softc *sc;
+	int rv;
+
+	sc = device_get_softc(dev);
+
+	if (sc->sc_vreg && (rv = device_delete_child(dev, sc->sc_vreg)) != 0)
+		return (rv);
+
+	TWL_LOCK_DESTROY(sc);
+
+	return (0);
+}
+
+static device_method_t twl_methods[] = {
+	DEVMETHOD(device_identify,	twl_identify),
+	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);

Added: projects/armv6/sys/arm/ti/twl/twl.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ projects/armv6/sys/arm/ti/twl/twl.h	Tue Feb 21 06:10:35 2012	(r231962)
@@ -0,0 +1,33 @@
+/*-
+ * 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.
+ */
+#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);
+
+#endif /* _TWL_H_ */

Added: projects/armv6/sys/arm/ti/twl/twl_vreg.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ projects/armv6/sys/arm/ti/twl/twl_vreg.c	Tue Feb 21 06:10:35 2012	(r231962)
@@ -0,0 +1,1057 @@
+/*-
+ * 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.
+ *
+ * Currently this driver focuses on the voltage regulator side of things,
+ * however in future it might be wise to split up this driver so that there is
+ * one base driver used for communication and other child drivers that
+ * manipulate the various modules on the chip.
+ *
+ * Voltage Regulators
+ * ------------------
+ * - Voltage regulators can belong to different power groups, in this driver we
+ *   put the regulators under our control in the "Application power group".
+ *
+ */
+
+#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/iiconf.h>
+
+#include "twl.h"
+#include "twl_vreg.h"
+
+/* The register sets are divided up into groups with the following base address
+ * and I2C addresses.
+ */
+
+/*
+ * 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
+
+/**
+ *  Linked list of voltage regulators support by the device.
+ */
+struct twl_regulator_entry {
+	LIST_ENTRY(twl_regulator_entry) entries;
+
+	char			name[TWL_VREG_MAX_NAMELEN];
+	struct sysctl_oid	*oid;
+
+	uint8_t			sub_dev;
+	uint8_t			reg_off;
+
+	uint16_t		fixed_voltage;
+
+	const uint16_t		*supp_voltages;
+	uint32_t		num_supp_voltages;
+};
+
+/**
+ *	Structure that stores the driver context.
+ *
+ *	This structure is allocated during driver attach.
+ */
+struct twl_vreg_softc {
+	device_t			sc_dev;
+	device_t			sc_pdev;
+
+	struct mtx			sc_mtx;
+
+	struct intr_config_hook sc_init_hook;
+
+	LIST_HEAD(twl_regulator_list, twl_regulator_entry) sc_vreg_list;
+};
+
+/**
+ *	Macros for driver mutex locking
+ */
+#define TWL_VREG_LOCK(_sc)		mtx_lock(&(_sc)->sc_mtx)
+#define	TWL_VREG_UNLOCK(_sc)		mtx_unlock(&(_sc)->sc_mtx)
+#define TWL_VREG_LOCK_INIT(_sc) \
+	mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->sc_dev), \
+	         "twl_vreg", MTX_DEF)
+#define TWL_VREG_LOCK_DESTROY(_sc)	mtx_destroy(&_sc->sc_mtx);
+#define TWL_VREG_ASSERT_LOCKED(_sc)	mtx_assert(&_sc->sc_mtx, MA_OWNED);
+#define TWL_VREG_ASSERT_UNLOCKED(_sc)	mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
+
+static int debug;
+
+/**
+ *	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.
+ *
+ *	LOCKING:
+ *	None.
+ *
+ *	RETURNS:
+ *	Returns a non-zero value if the device matches, otherwise zero.
+ */
+static int
+twl_is_4030(device_t dev)
+{
+
+	return (0);
+}
+
+static int
+twl_is_6025(device_t dev)
+{
+
+	return (0);
+}
+
+static int
+twl_is_6030(device_t dev)
+{
+
+	return (1);
+}
+
+/**
+ *	twl_vreg_read_1 - 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 registers and stores the result in the suppled buffer.
+ *
+ *	LOCKING:
+ *	Expects the TWL lock to be held.
+ *
+ *	RETURNS:
+ *	Zero on success or a negative 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));
+}
+
+/**
+ *	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.
+ *
+ *	LOCKING:
+ *	Expects the TWL lock to be held.
+ *
+ *	RETURNS:
+ *	Zero on success or a negative error code on failure.
+ */
+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
+ *	@sc: the device soft context
+ *	@regulator: pointer to the regulator device
+ *	@millivolts: the millivolts to find the bit value for
+ *	@bitval: upond return will contain the value to write
+ *	@diff: upon return will contain the difference between the requested
+ *	       voltage value and the actual voltage (in millivolts)
+ *
+ *  Accepts a voltage value and tries to find the closest match to the actual
+ *	supported voltages.  If a match is found within 100mv of the target, the
+ *	function returns the VSEL value.  If no voltage match is found the function
+ *	returns UNDEF.
+ *
+ *	LOCKING:
+ *	It's expected the TWL lock is held while this function is called.
+ *
+ *	RETURNS:
+ *	A value to load into the VSEL register if matching voltage found, if not
+ *	EINVAL is returned.
+ */
+static int
+twl_vreg_millivolt_to_vsel(struct twl_vreg_softc *sc,
+    struct twl_regulator_entry *regulator, int millivolts)
+{
+	int delta, smallest_delta;
+	unsigned i, closest_idx;
+
+	TWL_VREG_ASSERT_LOCKED(sc);
+
+	/* First check that variable voltage is supported */
+	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 150mv of the actual target, this
+	 * is just a value I picked out of thin air.
+	 */
+	if (smallest_delta > 100)
+		return (EINVAL);
+
+	return (closest_idx);
+}
+
+/**
+ *	twl_regulator_is_enabled - returns the enabled status of the regulator
+ *	@sc: the device soft context
+ *	@regulator: pointer to the regulator device
+ *
+ *
+ *
+ *	LOCKING:
+ *	It's expected the TWL lock is held while this function is called.
+ *
+ *	RETURNS:
+ *	Zero if disabled, positive non-zero value if enabled, on failure a negative
+ *	error code.
+ */
+static int
+twl_vreg_is_regulator_enabled(struct twl_vreg_softc *sc,
+    struct twl_regulator_entry *regulator)
+{
+	int err;
+	uint8_t grp;
+	uint8_t state;
+
+	TWL_VREG_ASSERT_LOCKED(sc);
+
+	/* The status reading is different for the different devices */
+	if (twl_is_4030(sc->sc_dev)) {
+
+		err = twl_vreg_read_1(sc, regulator, TWL_VREG_GRP, &state);
+		if (err)
+			return (err);
+
+		return (state & TWL4030_P1_GRP);
+
+	} else if (twl_is_6030(sc->sc_dev) || twl_is_6025(sc->sc_dev)) {
+
+		/* Check the regulator is in the application group */
+		if (twl_is_6030(sc->sc_dev)) {
+			err = twl_vreg_read_1(sc, regulator, TWL_VREG_GRP, &grp);
+			if (err)
+				return (err);
+
+			if (!(grp & TWL6030_P1_GRP))
+				return (0);
+		}
+
+		/* Read the application mode state and verify it's ON */
+		err = twl_vreg_read_1(sc, regulator, TWL_VREG_STATE, &state);
+		if (err)
+			return (err);
+
+		return ((state & 0x0C) == 0x04);
+	}
+
+	return (EINVAL);
+}
+
+/**
+ *	twl_disable_regulator - disables the voltage regulator
+ *	@sc: the device soft context
+ *	@regulator: pointer to the regulator device
+ *
+ *  Disables the regulator which will stop the out drivers.
+ *
+ *	LOCKING:
+ *	It's expected the TWL lock is held while this function is called.
+ *
+ *	RETURNS:
+ *	Zero on success, or otherwise a negative error code.
+ */
+static int
+twl_vreg_disable_regulator(struct twl_vreg_softc *sc,
+    struct twl_regulator_entry *regulator)
+{
+	int err = 0;
+	uint8_t grp;
+
+	TWL_VREG_ASSERT_LOCKED(sc);
+
+	if (twl_is_4030(sc->sc_dev)) {
+
+		/* Read the regulator CFG_GRP register */
+		err = twl_vreg_read_1(sc, regulator, TWL_VREG_GRP, &grp);
+		if (err)
+			return (err);
+
+		/* 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_dev) || twl_is_6025(sc->sc_dev)) {
+
+		/* On TWL6030 we need to make sure we disable power for all groups */
+		if (twl_is_6030(sc->sc_dev))
+			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));
+	}
+
+	return (err);
+}
+
+/**
+ *	twl_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.
+ *
+ *	LOCKING:
+ *	It's expected the TWL lock is held while this function is called.
+ *
+ *	RETURNS:
+ *	Zero on success, or otherwise a negative error code.
+ */
+static int
+twl_vreg_enable_regulator(struct twl_vreg_softc *sc,
+    struct twl_regulator_entry *regulator)
+{
+	int err;
+	uint8_t grp;
+
+	TWL_VREG_ASSERT_LOCKED(sc);
+
+	/* Read the regulator CFG_GRP register */
+	err = twl_vreg_read_1(sc, regulator, TWL_VREG_GRP, &grp);
+	if (err)
+		return (err);
+
+	/* Enable the regulator by ensuring it's in the application power group
+	 * and is in the "on" state.
+	 */
+	if (twl_is_4030(sc->sc_dev)) {
+
+		/* 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_dev) || twl_is_6025(sc->sc_dev)) {
+
+		if (twl_is_6030(sc->sc_dev) && !(grp & TWL6030_P1_GRP)) {
+			grp |= TWL6030_P1_GRP;
+			err = twl_vreg_write_1(sc, regulator, TWL_VREG_GRP, grp);
+			if (err)

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201202210610.q1L6Aa5C085746>