svn commit: r234582 - in projects/armv6/sys/arm: conf ti/omap4 ti/twl

Ben Gray bgray at FreeBSD.org
Sun Apr 22 20:14:34 UTC 2012


Author: bgray
Date: Sun Apr 22 20:14:33 2012
New Revision: 234582
URL: http://svn.freebsd.org/changeset/base/234582

Log:
  Tidied up the TWL code and added support for
  external clock devices (twl_clk device) that
  can be enabled/disabled from the twl pmic.
  
  Also added support for FDT overrides so that
  voltage and clock settings can be set at
  device startup.

Added:
  projects/armv6/sys/arm/ti/twl/twl_clks.c
  projects/armv6/sys/arm/ti/twl/twl_clks.h
Modified:
  projects/armv6/sys/arm/conf/PANDABOARD
  projects/armv6/sys/arm/ti/omap4/files.omap4
  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

Modified: projects/armv6/sys/arm/conf/PANDABOARD
==============================================================================
--- projects/armv6/sys/arm/conf/PANDABOARD	Sun Apr 22 19:00:51 2012	(r234581)
+++ projects/armv6/sys/arm/conf/PANDABOARD	Sun Apr 22 20:14:33 2012	(r234582)
@@ -133,6 +133,7 @@ device		smsc		# SMSC LAN95xx USB Etherne
 device		ti_sdma
 device		twl
 device		twl_vreg
+device		twl_clks
 
 # Flattened Device Tree
 options         FDT

Modified: projects/armv6/sys/arm/ti/omap4/files.omap4
==============================================================================
--- projects/armv6/sys/arm/ti/omap4/files.omap4	Sun Apr 22 19:00:51 2012	(r234581)
+++ projects/armv6/sys/arm/ti/omap4/files.omap4	Sun Apr 22 20:14:33 2012	(r234582)
@@ -15,3 +15,5 @@ arm/ti/omap4/omap4_mp.c				optional	smp
 
 arm/ti/twl/twl.c				optional	twl
 arm/ti/twl/twl_vreg.c				optional	twl twl_vreg
+arm/ti/twl/twl_clks.c				optional	twl twl_clks
+

Modified: projects/armv6/sys/arm/ti/twl/twl.c
==============================================================================
--- projects/armv6/sys/arm/ti/twl/twl.c	Sun Apr 22 19:00:51 2012	(r234581)
+++ projects/armv6/sys/arm/ti/twl/twl.c	Sun Apr 22 20:14:33 2012	(r234582)
@@ -32,7 +32,15 @@ __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
+ * 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.
  *
  */
 
@@ -55,8 +63,12 @@ __FBSDID("$FreeBSD$");
 #include <machine/resource.h>
 #include <machine/intr.h>
 
+#include <dev/iicbus/iicbus.h>
 #include <dev/iicbus/iiconf.h>
-#include "iicbus_if.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"
 
@@ -84,20 +96,17 @@ __FBSDID("$FreeBSD$");
 
 #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;
+	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;
 };
 
 /**
@@ -112,6 +121,40 @@ struct twl_softc {
 #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
@@ -120,13 +163,10 @@ struct twl_softc {
  *	@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.
+ *	Reads one or more registers and stores the result in the suppled buffer.
  *
  *	RETURNS:
- *	Zero on success or a negative error code on failure.
+ *	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)
@@ -139,12 +179,12 @@ twl_read(device_t dev, uint8_t nsub, uin
 	sc = device_get_softc(dev);
 
 	TWL_LOCK(sc);
-
 	addr = sc->sc_subaddr_map[nsub];
-	if (addr == TWL_INVALID_CHIP_ID) {
-		TWL_UNLOCK(sc);
+	TWL_UNLOCK(sc);
+
+	if (addr == TWL_INVALID_CHIP_ID)
 		return (EIO);
-	}
+
 
 	/* Set the address to read from */
 	msg[0].slave = addr;
@@ -156,11 +196,8 @@ twl_read(device_t dev, uint8_t nsub, uin
 	msg[1].flags = IIC_M_RD;
 	msg[1].len = cnt;
 	msg[1].buf = buf;
-	TWL_UNLOCK(sc);
 
 	rc = iicbus_transfer(dev, msg, 2);
-
-
 	if (rc != 0) {
 		device_printf(dev, "iicbus read failed (adr:0x%02x, reg:0x%02x)\n",
 		              addr, reg);
@@ -180,9 +217,6 @@ twl_read(device_t dev, uint8_t nsub, uin
  *
  *	Writes one or more registers.
  *
- *	LOCKING:
- *	Expects the TWL lock to be held.
- *
  *	RETURNS:
  *	Zero on success or a negative error code on failure.
  */
@@ -205,23 +239,20 @@ twl_write(device_t dev, uint8_t nsub, ui
 	sc = device_get_softc(dev);
 
 	TWL_LOCK(sc);
-
 	addr = sc->sc_subaddr_map[nsub];
-	if (addr == TWL_INVALID_CHIP_ID) {
-		TWL_UNLOCK(sc);
+	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;
-	TWL_UNLOCK(sc);
 
 	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);
@@ -236,11 +267,8 @@ twl_write(device_t dev, uint8_t nsub, ui
  *	@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.
+ *	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.
@@ -264,52 +292,111 @@ twl_test_present(struct twl_softc *sc, u
 }
 
 /**
- *	twl_scan - disables IRQ's on the given channel
- *	@ch: the channel to disable IRQ's on
+ *	twl_scan - scans the i2c bus for sub modules
+ *	@dev: the twl device
  *
- *	Disable interupt generation for the given channel.
+ *	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.
  *
- *	RETURNS:
- *	BUS_PROBE_NOWILDCARD
  */
 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(sc->sc_subaddr_map, TWL_INVALID_CHIP_ID, TWL_MAX_SUBADDRS);
+	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) {
-			sc->sc_subaddr_map[i] = (base + i);
+			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);
 }
 
-static void
-twl_identify(driver_t *driver, device_t parent)
-{
-
-        BUS_ADD_CHILD(parent, 0, "twl", 0);
-}
-
+/**
+ *	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)
 {
-	device_set_desc(dev, "TI TWL4030/TWL5030/TWL60x0/TPS659x0 Companion IC");
+	phandle_t node;
+	const char *compat;
+	int len, l;
+	struct twl_softc *sc;
+
+	if ((compat = ofw_bus_get_compat(dev)) == NULL)
+		return (ENXIO);
 
-	return (BUS_PROBE_NOWILDCARD);
+	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
@@ -334,6 +421,8 @@ twl_attach(device_t dev)
 	/* 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));
 }
@@ -342,12 +431,14 @@ 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);
+	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);
 
@@ -355,7 +446,6 @@ twl_detach(device_t dev)
 }
 
 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),

Modified: projects/armv6/sys/arm/ti/twl/twl.h
==============================================================================
--- projects/armv6/sys/arm/ti/twl/twl.h	Sun Apr 22 19:00:51 2012	(r234581)
+++ projects/armv6/sys/arm/ti/twl/twl.h	Sun Apr 22 20:14:33 2012	(r234582)
@@ -30,4 +30,8 @@
 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_ */

Added: projects/armv6/sys/arm/ti/twl/twl_clks.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ projects/armv6/sys/arm/ti/twl/twl_clks.c	Sun Apr 22 20:14:33 2012	(r234582)
@@ -0,0 +1,675 @@
+/*-
+ * Copyright (c) 2012
+ *	Ben Gray <bgray at 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

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


More information about the svn-src-projects mailing list