1239281Sgonzo/*- 2239281Sgonzo * Copyright (c) 2012 3239281Sgonzo * Ben Gray <bgray@freebsd.org>. 4239281Sgonzo * All rights reserved. 5239281Sgonzo * 6239281Sgonzo * Redistribution and use in source and binary forms, with or without 7239281Sgonzo * modification, are permitted provided that the following conditions 8239281Sgonzo * are met: 9239281Sgonzo * 1. Redistributions of source code must retain the above copyright 10239281Sgonzo * notice, this list of conditions and the following disclaimer. 11239281Sgonzo * 2. Redistributions in binary form must reproduce the above copyright 12239281Sgonzo * notice, this list of conditions and the following disclaimer in the 13239281Sgonzo * documentation and/or other materials provided with the distribution. 14239281Sgonzo * 15239281Sgonzo * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16239281Sgonzo * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17239281Sgonzo * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18239281Sgonzo * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 19239281Sgonzo * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20239281Sgonzo * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21239281Sgonzo * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22239281Sgonzo * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23239281Sgonzo * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24239281Sgonzo * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25239281Sgonzo * SUCH DAMAGE. 26239281Sgonzo */ 27239281Sgonzo 28239281Sgonzo#include <sys/cdefs.h> 29239281Sgonzo__FBSDID("$FreeBSD$"); 30239281Sgonzo 31239281Sgonzo/* 32239281Sgonzo * Texas Instruments TWL4030/TWL5030/TWL60x0/TPS659x0 Power Management. 33239281Sgonzo * 34239281Sgonzo * This driver covers the external clocks, allows for enabling & 35239281Sgonzo * disabling their output. 36239281Sgonzo * 37239281Sgonzo * 38239281Sgonzo * 39239281Sgonzo * FLATTENED DEVICE TREE (FDT) 40239281Sgonzo * Startup override settings can be specified in the FDT, if they are they 41239281Sgonzo * should be under the twl parent device and take the following form: 42239281Sgonzo * 43239281Sgonzo * external-clocks = "name1", "state1", 44239281Sgonzo * "name2", "state2", 45239281Sgonzo * etc; 46239281Sgonzo * 47239281Sgonzo * Each override should be a pair, the first entry is the name of the clock 48239281Sgonzo * the second is the state to set, possible strings are either "on" or "off". 49239281Sgonzo * 50239281Sgonzo */ 51239281Sgonzo 52239281Sgonzo#include <sys/param.h> 53239281Sgonzo#include <sys/systm.h> 54239281Sgonzo#include <sys/kernel.h> 55239281Sgonzo#include <sys/lock.h> 56239281Sgonzo#include <sys/module.h> 57239281Sgonzo#include <sys/bus.h> 58239281Sgonzo#include <sys/resource.h> 59239281Sgonzo#include <sys/rman.h> 60239281Sgonzo#include <sys/sysctl.h> 61239281Sgonzo#include <sys/sx.h> 62239281Sgonzo#include <sys/malloc.h> 63239281Sgonzo 64239281Sgonzo#include <machine/bus.h> 65239281Sgonzo#include <machine/cpu.h> 66239281Sgonzo#include <machine/cpufunc.h> 67239281Sgonzo#include <machine/frame.h> 68239281Sgonzo#include <machine/resource.h> 69239281Sgonzo#include <machine/intr.h> 70239281Sgonzo 71239281Sgonzo#include <dev/ofw/openfirm.h> 72239281Sgonzo#include <dev/ofw/ofw_bus.h> 73239281Sgonzo 74239281Sgonzo#include "twl.h" 75239281Sgonzo#include "twl_clks.h" 76239281Sgonzo 77239281Sgonzo 78239281Sgonzostatic int twl_clks_debug = 1; 79239281Sgonzo 80239281Sgonzo 81239281Sgonzo/* 82239281Sgonzo * Power Groups bits for the 4030 and 6030 devices 83239281Sgonzo */ 84239281Sgonzo#define TWL4030_P3_GRP 0x80 /* Peripherals, power group */ 85239281Sgonzo#define TWL4030_P2_GRP 0x40 /* Modem power group */ 86239281Sgonzo#define TWL4030_P1_GRP 0x20 /* Application power group (FreeBSD control) */ 87239281Sgonzo 88239281Sgonzo#define TWL6030_P3_GRP 0x04 /* Modem power group */ 89239281Sgonzo#define TWL6030_P2_GRP 0x02 /* Connectivity power group */ 90239281Sgonzo#define TWL6030_P1_GRP 0x01 /* Application power group (FreeBSD control) */ 91239281Sgonzo 92239281Sgonzo/* 93239281Sgonzo * Register offsets within a clk regulator register set 94239281Sgonzo */ 95239281Sgonzo#define TWL_CLKS_GRP 0x00 /* Regulator GRP register */ 96239281Sgonzo#define TWL_CLKS_STATE 0x02 /* TWL6030 only */ 97239281Sgonzo 98239281Sgonzo 99239281Sgonzo 100239281Sgonzo/** 101239281Sgonzo * Support voltage regulators for the different IC's 102239281Sgonzo */ 103239281Sgonzostruct twl_clock { 104239281Sgonzo const char *name; 105239281Sgonzo uint8_t subdev; 106239281Sgonzo uint8_t regbase; 107239281Sgonzo}; 108239281Sgonzo 109239281Sgonzostatic const struct twl_clock twl4030_clocks[] = { 110239281Sgonzo { "32kclkout", 0, 0x8e }, 111239281Sgonzo { NULL, 0, 0x00 } 112239281Sgonzo}; 113239281Sgonzo 114239281Sgonzostatic const struct twl_clock twl6030_clocks[] = { 115239281Sgonzo { "clk32kg", 0, 0xbc }, 116239281Sgonzo { "clk32kao", 0, 0xb9 }, 117239281Sgonzo { "clk32kaudio", 0, 0xbf }, 118239281Sgonzo { NULL, 0, 0x00 } 119239281Sgonzo}; 120239281Sgonzo 121239281Sgonzo#define TWL_CLKS_MAX_NAMELEN 32 122239281Sgonzo 123239281Sgonzostruct twl_clk_entry { 124239281Sgonzo LIST_ENTRY(twl_clk_entry) link; 125239281Sgonzo struct sysctl_oid *oid; 126239281Sgonzo char name[TWL_CLKS_MAX_NAMELEN]; 127239281Sgonzo uint8_t sub_dev; /* the sub-device number for the clock */ 128239281Sgonzo uint8_t reg_off; /* register base address of the clock */ 129239281Sgonzo}; 130239281Sgonzo 131239281Sgonzostruct twl_clks_softc { 132239281Sgonzo device_t sc_dev; /* twl_clk device */ 133239281Sgonzo device_t sc_pdev; /* parent device (twl) */ 134239281Sgonzo struct sx sc_sx; /* internal locking */ 135239281Sgonzo struct intr_config_hook sc_init_hook; 136239281Sgonzo LIST_HEAD(twl_clk_list, twl_clk_entry) sc_clks_list; 137239281Sgonzo}; 138239281Sgonzo 139239281Sgonzo/** 140239281Sgonzo * Macros for driver shared locking 141239281Sgonzo */ 142239281Sgonzo#define TWL_CLKS_XLOCK(_sc) sx_xlock(&(_sc)->sc_sx) 143239281Sgonzo#define TWL_CLKS_XUNLOCK(_sc) sx_xunlock(&(_sc)->sc_sx) 144239281Sgonzo#define TWL_CLKS_SLOCK(_sc) sx_slock(&(_sc)->sc_sx) 145239281Sgonzo#define TWL_CLKS_SUNLOCK(_sc) sx_sunlock(&(_sc)->sc_sx) 146239281Sgonzo#define TWL_CLKS_LOCK_INIT(_sc) sx_init(&(_sc)->sc_sx, "twl_clks") 147239281Sgonzo#define TWL_CLKS_LOCK_DESTROY(_sc) sx_destroy(&(_sc)->sc_sx); 148239281Sgonzo 149239281Sgonzo#define TWL_CLKS_ASSERT_LOCKED(_sc) sx_assert(&(_sc)->sc_sx, SA_LOCKED); 150239281Sgonzo 151239281Sgonzo#define TWL_CLKS_LOCK_UPGRADE(_sc) \ 152239281Sgonzo do { \ 153239281Sgonzo while (!sx_try_upgrade(&(_sc)->sc_sx)) \ 154239281Sgonzo pause("twl_clks_ex", (hz / 100)); \ 155239281Sgonzo } while(0) 156239281Sgonzo#define TWL_CLKS_LOCK_DOWNGRADE(_sc) sx_downgrade(&(_sc)->sc_sx); 157239281Sgonzo 158239281Sgonzo 159239281Sgonzo 160239281Sgonzo 161239281Sgonzo/** 162239281Sgonzo * twl_clks_read_1 - read single register from the TWL device 163239281Sgonzo * twl_clks_write_1 - writes a single register in the TWL device 164239281Sgonzo * @sc: device context 165239281Sgonzo * @clk: the clock device we're reading from / writing to 166239281Sgonzo * @off: offset within the clock's register set 167239281Sgonzo * @val: the value to write or a pointer to a variable to store the result 168239281Sgonzo * 169239281Sgonzo * RETURNS: 170239281Sgonzo * Zero on success or an error code on failure. 171239281Sgonzo */ 172239281Sgonzostatic inline int 173239281Sgonzotwl_clks_read_1(struct twl_clks_softc *sc, struct twl_clk_entry *clk, 174239281Sgonzo uint8_t off, uint8_t *val) 175239281Sgonzo{ 176239281Sgonzo return (twl_read(sc->sc_pdev, clk->sub_dev, clk->reg_off + off, val, 1)); 177239281Sgonzo} 178239281Sgonzo 179239281Sgonzostatic inline int 180239281Sgonzotwl_clks_write_1(struct twl_clks_softc *sc, struct twl_clk_entry *clk, 181239281Sgonzo uint8_t off, uint8_t val) 182239281Sgonzo{ 183239281Sgonzo return (twl_write(sc->sc_pdev, clk->sub_dev, clk->reg_off + off, &val, 1)); 184239281Sgonzo} 185239281Sgonzo 186239281Sgonzo 187239281Sgonzo/** 188239281Sgonzo * twl_clks_is_enabled - determines if a clock is enabled 189239281Sgonzo * @dev: TWL CLK device 190239281Sgonzo * @name: the name of the clock 191239281Sgonzo * @enabled: upon return will contain the 'enabled' state 192239281Sgonzo * 193239281Sgonzo * LOCKING: 194239281Sgonzo * Internally the function takes and releases the TWL lock. 195239281Sgonzo * 196239281Sgonzo * RETURNS: 197239281Sgonzo * Zero on success or a negative error code on failure. 198239281Sgonzo */ 199239281Sgonzoint 200239281Sgonzotwl_clks_is_enabled(device_t dev, const char *name, int *enabled) 201239281Sgonzo{ 202239281Sgonzo struct twl_clks_softc *sc = device_get_softc(dev); 203239281Sgonzo struct twl_clk_entry *clk; 204239281Sgonzo int found = 0; 205239281Sgonzo int err; 206239281Sgonzo uint8_t grp, state; 207239281Sgonzo 208239281Sgonzo TWL_CLKS_SLOCK(sc); 209239281Sgonzo 210239281Sgonzo LIST_FOREACH(clk, &sc->sc_clks_list, link) { 211239281Sgonzo if (strcmp(clk->name, name) == 0) { 212239281Sgonzo found = 1; 213239281Sgonzo break; 214239281Sgonzo } 215239281Sgonzo } 216239281Sgonzo 217239281Sgonzo if (!found) { 218239281Sgonzo TWL_CLKS_SUNLOCK(sc); 219239281Sgonzo return (EINVAL); 220239281Sgonzo } 221239281Sgonzo 222239281Sgonzo 223239281Sgonzo if (twl_is_4030(sc->sc_pdev)) { 224239281Sgonzo 225239281Sgonzo err = twl_clks_read_1(sc, clk, TWL_CLKS_GRP, &grp); 226239281Sgonzo if (!err) 227239281Sgonzo *enabled = (grp & TWL4030_P1_GRP); 228239281Sgonzo 229239281Sgonzo } else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev)) { 230239281Sgonzo 231239281Sgonzo TWL_CLKS_LOCK_UPGRADE(sc); 232239281Sgonzo 233239281Sgonzo /* Check the clock is in the application group */ 234239281Sgonzo if (twl_is_6030(sc->sc_pdev)) { 235239281Sgonzo err = twl_clks_read_1(sc, clk, TWL_CLKS_GRP, &grp); 236239281Sgonzo if (err) { 237239281Sgonzo TWL_CLKS_LOCK_DOWNGRADE(sc); 238239281Sgonzo goto done; 239239281Sgonzo } 240239281Sgonzo 241239281Sgonzo if (!(grp & TWL6030_P1_GRP)) { 242239281Sgonzo TWL_CLKS_LOCK_DOWNGRADE(sc); 243239281Sgonzo *enabled = 0; /* disabled */ 244239281Sgonzo goto done; 245239281Sgonzo } 246239281Sgonzo } 247239281Sgonzo 248239281Sgonzo /* Read the application mode state and verify it's ON */ 249239281Sgonzo err = twl_clks_read_1(sc, clk, TWL_CLKS_STATE, &state); 250239281Sgonzo if (!err) 251239281Sgonzo *enabled = ((state & 0x0C) == 0x04); 252239281Sgonzo 253239281Sgonzo TWL_CLKS_LOCK_DOWNGRADE(sc); 254239281Sgonzo 255239281Sgonzo } else { 256239281Sgonzo err = EINVAL; 257239281Sgonzo } 258239281Sgonzo 259239281Sgonzodone: 260239281Sgonzo TWL_CLKS_SUNLOCK(sc); 261239281Sgonzo return (err); 262239281Sgonzo} 263239281Sgonzo 264239281Sgonzo 265239281Sgonzo/** 266239281Sgonzo * twl_clks_set_state - enables/disables a clock output 267239281Sgonzo * @sc: device context 268239281Sgonzo * @clk: the clock entry to enable/disable 269239281Sgonzo * @enable: non-zero the clock is enabled, zero the clock is disabled 270239281Sgonzo * 271239281Sgonzo * LOCKING: 272239281Sgonzo * The TWL CLK lock must be held before this function is called. 273239281Sgonzo * 274239281Sgonzo * RETURNS: 275239281Sgonzo * Zero on success or an error code on failure. 276239281Sgonzo */ 277239281Sgonzostatic int 278239281Sgonzotwl_clks_set_state(struct twl_clks_softc *sc, struct twl_clk_entry *clk, 279239281Sgonzo int enable) 280239281Sgonzo{ 281239281Sgonzo int xlocked; 282239281Sgonzo int err; 283239281Sgonzo uint8_t grp; 284239281Sgonzo 285239281Sgonzo TWL_CLKS_ASSERT_LOCKED(sc); 286239281Sgonzo 287239281Sgonzo /* Upgrade the lock to exclusive because about to perform read-mod-write */ 288239281Sgonzo xlocked = sx_xlocked(&sc->sc_sx); 289239281Sgonzo if (!xlocked) 290239281Sgonzo TWL_CLKS_LOCK_UPGRADE(sc); 291239281Sgonzo 292239281Sgonzo err = twl_clks_read_1(sc, clk, TWL_CLKS_GRP, &grp); 293239281Sgonzo if (err) 294239281Sgonzo goto done; 295239281Sgonzo 296239281Sgonzo if (twl_is_4030(sc->sc_pdev)) { 297239281Sgonzo 298239281Sgonzo /* On the TWL4030 we just need to ensure the clock is in the right 299239281Sgonzo * power domain, don't need to turn on explicitly like TWL6030. 300239281Sgonzo */ 301239281Sgonzo if (enable) 302239281Sgonzo grp |= TWL4030_P1_GRP; 303239281Sgonzo else 304239281Sgonzo grp &= ~(TWL4030_P1_GRP | TWL4030_P2_GRP | TWL4030_P3_GRP); 305239281Sgonzo 306239281Sgonzo err = twl_clks_write_1(sc, clk, TWL_CLKS_GRP, grp); 307239281Sgonzo 308239281Sgonzo } else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev)) { 309239281Sgonzo 310239281Sgonzo /* Make sure the clock belongs to at least the APP power group */ 311239281Sgonzo if (twl_is_6030(sc->sc_pdev) && !(grp & TWL6030_P1_GRP)) { 312239281Sgonzo grp |= TWL6030_P1_GRP; 313239281Sgonzo err = twl_clks_write_1(sc, clk, TWL_CLKS_GRP, grp); 314239281Sgonzo if (err) 315239281Sgonzo goto done; 316239281Sgonzo } 317239281Sgonzo 318239281Sgonzo /* On TWL6030 we need to make sure we disable power for all groups */ 319239281Sgonzo if (twl_is_6030(sc->sc_pdev)) 320239281Sgonzo grp = TWL6030_P1_GRP | TWL6030_P2_GRP | TWL6030_P3_GRP; 321239281Sgonzo else 322239281Sgonzo grp = 0x00; 323239281Sgonzo 324239281Sgonzo /* Set the state of the clock */ 325239281Sgonzo if (enable) 326239281Sgonzo err = twl_clks_write_1(sc, clk, TWL_CLKS_STATE, (grp << 5) | 0x01); 327239281Sgonzo else 328239281Sgonzo err = twl_clks_write_1(sc, clk, TWL_CLKS_STATE, (grp << 5)); 329239281Sgonzo 330239281Sgonzo } else { 331239281Sgonzo 332239281Sgonzo err = EINVAL; 333239281Sgonzo } 334239281Sgonzo 335239281Sgonzodone: 336239281Sgonzo if (!xlocked) 337239281Sgonzo TWL_CLKS_LOCK_DOWNGRADE(sc); 338239281Sgonzo 339239281Sgonzo if ((twl_clks_debug > 1) && !err) 340239281Sgonzo device_printf(sc->sc_dev, "%s : %sabled\n", clk->name, 341239281Sgonzo enable ? "en" : "dis"); 342239281Sgonzo 343239281Sgonzo return (err); 344239281Sgonzo} 345239281Sgonzo 346239281Sgonzo 347239281Sgonzo/** 348239281Sgonzo * twl_clks_disable - disables a clock output 349239281Sgonzo * @dev: TWL clk device 350239281Sgonzo* @name: the name of the clock 351239281Sgonzo * 352239281Sgonzo * LOCKING: 353239281Sgonzo * Internally the function takes and releases the TWL lock. 354239281Sgonzo * 355239281Sgonzo * RETURNS: 356239281Sgonzo* Zero on success or an error code on failure. 357239281Sgonzo */ 358239281Sgonzoint 359239281Sgonzotwl_clks_disable(device_t dev, const char *name) 360239281Sgonzo{ 361239281Sgonzo struct twl_clks_softc *sc = device_get_softc(dev); 362239281Sgonzo struct twl_clk_entry *clk; 363239281Sgonzo int err = EINVAL; 364239281Sgonzo 365239281Sgonzo TWL_CLKS_SLOCK(sc); 366239281Sgonzo 367239281Sgonzo LIST_FOREACH(clk, &sc->sc_clks_list, link) { 368239281Sgonzo if (strcmp(clk->name, name) == 0) { 369239281Sgonzo err = twl_clks_set_state(sc, clk, 0); 370239281Sgonzo break; 371239281Sgonzo } 372239281Sgonzo } 373239281Sgonzo 374239281Sgonzo TWL_CLKS_SUNLOCK(sc); 375239281Sgonzo return (err); 376239281Sgonzo} 377239281Sgonzo 378239281Sgonzo/** 379239281Sgonzo * twl_clks_enable - enables a clock output 380239281Sgonzo * @dev: TWL clk device 381239281Sgonzo * @name: the name of the clock 382239281Sgonzo * 383239281Sgonzo * LOCKING: 384239281Sgonzo * Internally the function takes and releases the TWL CLKS lock. 385239281Sgonzo * 386239281Sgonzo * RETURNS: 387239281Sgonzo * Zero on success or an error code on failure. 388239281Sgonzo */ 389239281Sgonzoint 390239281Sgonzotwl_clks_enable(device_t dev, const char *name) 391239281Sgonzo{ 392239281Sgonzo struct twl_clks_softc *sc = device_get_softc(dev); 393239281Sgonzo struct twl_clk_entry *clk; 394239281Sgonzo int err = EINVAL; 395239281Sgonzo 396239281Sgonzo TWL_CLKS_SLOCK(sc); 397239281Sgonzo 398239281Sgonzo LIST_FOREACH(clk, &sc->sc_clks_list, link) { 399239281Sgonzo if (strcmp(clk->name, name) == 0) { 400239281Sgonzo err = twl_clks_set_state(sc, clk, 1); 401239281Sgonzo break; 402239281Sgonzo } 403239281Sgonzo } 404239281Sgonzo 405239281Sgonzo TWL_CLKS_SUNLOCK(sc); 406239281Sgonzo return (err); 407239281Sgonzo} 408239281Sgonzo 409239281Sgonzo/** 410239281Sgonzo * twl_clks_sysctl_clock - reads the state of the clock 411239281Sgonzo * @SYSCTL_HANDLER_ARGS: arguments for the callback 412239281Sgonzo * 413239281Sgonzo * Returns the clock status; disabled is zero and enabled is non-zero. 414239281Sgonzo * 415239281Sgonzo * LOCKING: 416239281Sgonzo * It's expected the TWL lock is held while this function is called. 417239281Sgonzo * 418239281Sgonzo * RETURNS: 419239281Sgonzo * EIO if device is not present, otherwise 0 is returned. 420239281Sgonzo */ 421239281Sgonzostatic int 422239281Sgonzotwl_clks_sysctl_clock(SYSCTL_HANDLER_ARGS) 423239281Sgonzo{ 424239281Sgonzo struct twl_clks_softc *sc = (struct twl_clks_softc*)arg1; 425239281Sgonzo int err; 426239281Sgonzo int enabled = 0; 427239281Sgonzo 428239281Sgonzo if ((err = twl_clks_is_enabled(sc->sc_dev, oidp->oid_name, &enabled)) != 0) 429239281Sgonzo return err; 430239281Sgonzo 431239281Sgonzo return sysctl_handle_int(oidp, &enabled, 0, req); 432239281Sgonzo} 433239281Sgonzo 434239281Sgonzo/** 435239281Sgonzo * twl_clks_add_clock - adds single clock sysctls for the device 436239281Sgonzo * @sc: device soft context 437239281Sgonzo * @name: the name of the regulator 438239281Sgonzo * @nsub: the number of the subdevice 439239281Sgonzo * @regbase: the base address of the clocks registers 440239281Sgonzo * 441239281Sgonzo * Adds a single clock to the device and also a sysctl interface for 442239281Sgonzo * querying it's status. 443239281Sgonzo * 444239281Sgonzo * LOCKING: 445239281Sgonzo * It's expected the exclusive lock is held while this function is called. 446239281Sgonzo * 447239281Sgonzo * RETURNS: 448239281Sgonzo * Pointer to the new clock entry on success, otherwise NULL on failure. 449239281Sgonzo */ 450239281Sgonzostatic struct twl_clk_entry* 451239281Sgonzotwl_clks_add_clock(struct twl_clks_softc *sc, const char *name, 452239281Sgonzo uint8_t nsub, uint8_t regbase) 453239281Sgonzo{ 454239281Sgonzo struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev); 455239281Sgonzo struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev); 456239281Sgonzo struct twl_clk_entry *new; 457239281Sgonzo 458239281Sgonzo TWL_CLKS_ASSERT_LOCKED(sc); 459239281Sgonzo 460239281Sgonzo new = malloc(sizeof(struct twl_clk_entry), M_DEVBUF, M_NOWAIT | M_ZERO); 461239281Sgonzo if (new == NULL) 462239281Sgonzo return (NULL); 463239281Sgonzo 464239281Sgonzo 465239281Sgonzo strncpy(new->name, name, TWL_CLKS_MAX_NAMELEN); 466239281Sgonzo new->name[TWL_CLKS_MAX_NAMELEN - 1] = '\0'; 467239281Sgonzo 468239281Sgonzo new->sub_dev = nsub; 469239281Sgonzo new->reg_off = regbase; 470239281Sgonzo 471239281Sgonzo 472239281Sgonzo 473239281Sgonzo /* Add a sysctl entry for the clock */ 474239281Sgonzo new->oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, name, 475239281Sgonzo CTLTYPE_INT | CTLFLAG_RD, sc, 0, 476239281Sgonzo twl_clks_sysctl_clock, "I", "external clock"); 477239281Sgonzo 478239281Sgonzo /* Finally add the regulator to list of supported regulators */ 479239281Sgonzo LIST_INSERT_HEAD(&sc->sc_clks_list, new, link); 480239281Sgonzo 481239281Sgonzo return (new); 482239281Sgonzo} 483239281Sgonzo 484239281Sgonzo/** 485239281Sgonzo * twl_clks_add_clocks - populates the internal list of clocks 486239281Sgonzo * @sc: device soft context 487239281Sgonzo * @chip: the name of the chip used in the hints 488239281Sgonzo * @clks the list of clocks supported by the device 489239281Sgonzo * 490239281Sgonzo * Loops over the list of clocks and adds them to the device context. Also 491239281Sgonzo * scans the FDT to determine if there are any clocks that should be 492239281Sgonzo * enabled/disabled automatically. 493239281Sgonzo * 494239281Sgonzo * LOCKING: 495239281Sgonzo * Internally takes the exclusive lock while adding the clocks to the 496239281Sgonzo * device context. 497239281Sgonzo * 498239281Sgonzo * RETURNS: 499239281Sgonzo * Always returns 0. 500239281Sgonzo */ 501239281Sgonzostatic int 502239281Sgonzotwl_clks_add_clocks(struct twl_clks_softc *sc, const struct twl_clock *clks) 503239281Sgonzo{ 504239281Sgonzo int err; 505239281Sgonzo const struct twl_clock *walker; 506239281Sgonzo struct twl_clk_entry *entry; 507239281Sgonzo phandle_t child; 508239281Sgonzo char rnames[256]; 509239281Sgonzo char *name, *state; 510239281Sgonzo int len = 0, prop_len; 511239281Sgonzo int enable; 512239281Sgonzo 513239281Sgonzo 514239281Sgonzo TWL_CLKS_XLOCK(sc); 515239281Sgonzo 516239281Sgonzo /* Add the regulators from the list */ 517239281Sgonzo walker = &clks[0]; 518239281Sgonzo while (walker->name != NULL) { 519239281Sgonzo 520239281Sgonzo /* Add the regulator to the list */ 521239281Sgonzo entry = twl_clks_add_clock(sc, walker->name, walker->subdev, 522239281Sgonzo walker->regbase); 523239281Sgonzo if (entry == NULL) 524239281Sgonzo continue; 525239281Sgonzo 526239281Sgonzo walker++; 527239281Sgonzo } 528239281Sgonzo 529239281Sgonzo /* Check for any FDT settings that need to be applied */ 530239281Sgonzo child = ofw_bus_get_node(sc->sc_pdev); 531239281Sgonzo if (child) { 532239281Sgonzo 533239281Sgonzo prop_len = OF_getprop(child, "external-clocks", rnames, sizeof(rnames)); 534239281Sgonzo while (len < prop_len) { 535239281Sgonzo name = rnames + len; 536239281Sgonzo len += strlen(name) + 1; 537239281Sgonzo if ((len >= prop_len) || (name[0] == '\0')) 538239281Sgonzo break; 539239281Sgonzo 540239281Sgonzo state = rnames + len; 541239281Sgonzo len += strlen(state) + 1; 542239281Sgonzo if (state[0] == '\0') 543239281Sgonzo break; 544239281Sgonzo 545239281Sgonzo enable = !strncmp(state, "on", 2); 546239281Sgonzo 547239281Sgonzo LIST_FOREACH(entry, &sc->sc_clks_list, link) { 548239281Sgonzo if (strcmp(entry->name, name) == 0) { 549239281Sgonzo twl_clks_set_state(sc, entry, enable); 550239281Sgonzo break; 551239281Sgonzo } 552239281Sgonzo } 553239281Sgonzo } 554239281Sgonzo } 555239281Sgonzo 556239281Sgonzo TWL_CLKS_XUNLOCK(sc); 557239281Sgonzo 558239281Sgonzo 559239281Sgonzo if (twl_clks_debug) { 560239281Sgonzo LIST_FOREACH(entry, &sc->sc_clks_list, link) { 561239281Sgonzo err = twl_clks_is_enabled(sc->sc_dev, entry->name, &enable); 562239281Sgonzo if (!err) 563239281Sgonzo device_printf(sc->sc_dev, "%s : %s\n", entry->name, 564239281Sgonzo enable ? "on" : "off"); 565239281Sgonzo } 566239281Sgonzo } 567239281Sgonzo 568239281Sgonzo return (0); 569239281Sgonzo} 570239281Sgonzo 571239281Sgonzo/** 572239281Sgonzo * twl_clks_init - initialises the list of clocks 573239281Sgonzo * @dev: the twl_clks device 574239281Sgonzo * 575239281Sgonzo * This function is called as an intrhook once interrupts have been enabled, 576239281Sgonzo * this is done so that the driver has the option to enable/disable a clock 577239281Sgonzo * based on settings providied in the FDT. 578239281Sgonzo * 579239281Sgonzo * LOCKING: 580239281Sgonzo * May takes the exclusive lock in the function. 581239281Sgonzo */ 582239281Sgonzostatic void 583239281Sgonzotwl_clks_init(void *dev) 584239281Sgonzo{ 585239281Sgonzo struct twl_clks_softc *sc; 586239281Sgonzo 587239281Sgonzo sc = device_get_softc((device_t)dev); 588239281Sgonzo 589239281Sgonzo if (twl_is_4030(sc->sc_pdev)) 590239281Sgonzo twl_clks_add_clocks(sc, twl4030_clocks); 591239281Sgonzo else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev)) 592239281Sgonzo twl_clks_add_clocks(sc, twl6030_clocks); 593239281Sgonzo 594239281Sgonzo config_intrhook_disestablish(&sc->sc_init_hook); 595239281Sgonzo} 596239281Sgonzo 597239281Sgonzostatic int 598239281Sgonzotwl_clks_probe(device_t dev) 599239281Sgonzo{ 600239281Sgonzo if (twl_is_4030(device_get_parent(dev))) 601239281Sgonzo device_set_desc(dev, "TI TWL4030 PMIC External Clocks"); 602239281Sgonzo else if (twl_is_6025(device_get_parent(dev)) || 603239281Sgonzo twl_is_6030(device_get_parent(dev))) 604239281Sgonzo device_set_desc(dev, "TI TWL6025/TWL6030 PMIC External Clocks"); 605239281Sgonzo else 606239281Sgonzo return (ENXIO); 607239281Sgonzo 608239281Sgonzo return (0); 609239281Sgonzo} 610239281Sgonzo 611239281Sgonzostatic int 612239281Sgonzotwl_clks_attach(device_t dev) 613239281Sgonzo{ 614239281Sgonzo struct twl_clks_softc *sc; 615239281Sgonzo 616239281Sgonzo sc = device_get_softc(dev); 617239281Sgonzo sc->sc_dev = dev; 618239281Sgonzo sc->sc_pdev = device_get_parent(dev); 619239281Sgonzo 620239281Sgonzo TWL_CLKS_LOCK_INIT(sc); 621239281Sgonzo 622239281Sgonzo LIST_INIT(&sc->sc_clks_list); 623239281Sgonzo 624239281Sgonzo 625239281Sgonzo sc->sc_init_hook.ich_func = twl_clks_init; 626239281Sgonzo sc->sc_init_hook.ich_arg = dev; 627239281Sgonzo 628239281Sgonzo if (config_intrhook_establish(&sc->sc_init_hook) != 0) 629239281Sgonzo return (ENOMEM); 630239281Sgonzo 631239281Sgonzo return (0); 632239281Sgonzo} 633239281Sgonzo 634239281Sgonzostatic int 635239281Sgonzotwl_clks_detach(device_t dev) 636239281Sgonzo{ 637239281Sgonzo struct twl_clks_softc *sc; 638239281Sgonzo struct twl_clk_entry *clk; 639239281Sgonzo struct twl_clk_entry *tmp; 640239281Sgonzo 641239281Sgonzo sc = device_get_softc(dev); 642239281Sgonzo 643239281Sgonzo TWL_CLKS_XLOCK(sc); 644239281Sgonzo 645239281Sgonzo LIST_FOREACH_SAFE(clk, &sc->sc_clks_list, link, tmp) { 646239281Sgonzo LIST_REMOVE(clk, link); 647239281Sgonzo sysctl_remove_oid(clk->oid, 1, 0); 648239281Sgonzo free(clk, M_DEVBUF); 649239281Sgonzo } 650239281Sgonzo 651239281Sgonzo TWL_CLKS_XUNLOCK(sc); 652239281Sgonzo 653239281Sgonzo TWL_CLKS_LOCK_DESTROY(sc); 654239281Sgonzo 655239281Sgonzo return (0); 656239281Sgonzo} 657239281Sgonzo 658239281Sgonzostatic device_method_t twl_clks_methods[] = { 659239281Sgonzo DEVMETHOD(device_probe, twl_clks_probe), 660239281Sgonzo DEVMETHOD(device_attach, twl_clks_attach), 661239281Sgonzo DEVMETHOD(device_detach, twl_clks_detach), 662239281Sgonzo 663239281Sgonzo {0, 0}, 664239281Sgonzo}; 665239281Sgonzo 666239281Sgonzostatic driver_t twl_clks_driver = { 667239281Sgonzo "twl_clks", 668239281Sgonzo twl_clks_methods, 669239281Sgonzo sizeof(struct twl_clks_softc), 670239281Sgonzo}; 671239281Sgonzo 672239281Sgonzostatic devclass_t twl_clks_devclass; 673239281Sgonzo 674239281SgonzoDRIVER_MODULE(twl_clks, twl, twl_clks_driver, twl_clks_devclass, 0, 0); 675239281SgonzoMODULE_VERSION(twl_clks, 1); 676