From owner-svn-src-projects@FreeBSD.ORG Sun Apr 22 20:14:34 2012 Return-Path: Delivered-To: svn-src-projects@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 5DF67106564A; Sun, 22 Apr 2012 20:14:34 +0000 (UTC) (envelope-from bgray@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:4f8:fff6::2c]) by mx1.freebsd.org (Postfix) with ESMTP id 481EB8FC16; Sun, 22 Apr 2012 20:14:34 +0000 (UTC) Received: from svn.freebsd.org (localhost [127.0.0.1]) by svn.freebsd.org (8.14.4/8.14.4) with ESMTP id q3MKEYJK054985; Sun, 22 Apr 2012 20:14:34 GMT (envelope-from bgray@svn.freebsd.org) Received: (from bgray@localhost) by svn.freebsd.org (8.14.4/8.14.4/Submit) id q3MKEYX6054978; Sun, 22 Apr 2012 20:14:34 GMT (envelope-from bgray@svn.freebsd.org) Message-Id: <201204222014.q3MKEYX6054978@svn.freebsd.org> From: Ben Gray Date: Sun, 22 Apr 2012 20:14:34 +0000 (UTC) To: src-committers@freebsd.org, svn-src-projects@freebsd.org X-SVN-Group: projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cc: Subject: svn commit: r234582 - in projects/armv6/sys/arm: conf ti/omap4 ti/twl X-BeenThere: svn-src-projects@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: "SVN commit messages for the src " projects" tree" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 22 Apr 2012 20:14:34 -0000 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 #include +#include #include -#include "iicbus_if.h" + +#include +#include +#include #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 . + * 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 +__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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 ***