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