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