twl_clks.c revision 259329
1118611Snjl/*-
2118611Snjl * Copyright (c) 2012
3118611Snjl *	Ben Gray <bgray@freebsd.org>.
4118611Snjl * All rights reserved.
5118611Snjl *
6118611Snjl * Redistribution and use in source and binary forms, with or without
7217365Sjkim * modification, are permitted provided that the following conditions
8281075Sdim * are met:
9118611Snjl * 1. Redistributions of source code must retain the above copyright
10118611Snjl *    notice, this list of conditions and the following disclaimer.
11217365Sjkim * 2. Redistributions in binary form must reproduce the above copyright
12217365Sjkim *    notice, this list of conditions and the following disclaimer in the
13217365Sjkim *    documentation and/or other materials provided with the distribution.
14217365Sjkim *
15217365Sjkim * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16217365Sjkim * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17217365Sjkim * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18217365Sjkim * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
19217365Sjkim * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20217365Sjkim * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21217365Sjkim * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22217365Sjkim * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23217365Sjkim * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24217365Sjkim * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25118611Snjl * SUCH DAMAGE.
26217365Sjkim */
27217365Sjkim
28217365Sjkim#include <sys/cdefs.h>
29118611Snjl__FBSDID("$FreeBSD: stable/10/sys/arm/ti/twl/twl_clks.c 259329 2013-12-13 20:43:11Z ian $");
30217365Sjkim
31217365Sjkim/*
32217365Sjkim * Texas Instruments TWL4030/TWL5030/TWL60x0/TPS659x0 Power Management.
33217365Sjkim *
34217365Sjkim * This driver covers the external clocks, allows for enabling &
35217365Sjkim * disabling their output.
36217365Sjkim *
37217365Sjkim *
38217365Sjkim *
39217365Sjkim * FLATTENED DEVICE TREE (FDT)
40217365Sjkim * Startup override settings can be specified in the FDT, if they are they
41217365Sjkim * should be under the twl parent device and take the following form:
42217365Sjkim *
43118611Snjl *    external-clocks = "name1", "state1",
44151937Sjkim *                      "name2", "state2",
45118611Snjl *                      etc;
46193529Sjkim *
47118611Snjl * Each override should be a pair, the first entry is the name of the clock
48118611Snjl * the second is the state to set, possible strings are either "on" or "off".
49118611Snjl *
50118611Snjl */
51118611Snjl
52151937Sjkim#include <sys/param.h>
53118611Snjl#include <sys/systm.h>
54151937Sjkim#include <sys/kernel.h>
55151937Sjkim#include <sys/lock.h>
56151937Sjkim#include <sys/module.h>
57151937Sjkim#include <sys/bus.h>
58151937Sjkim#include <sys/resource.h>
59151937Sjkim#include <sys/rman.h>
60151937Sjkim#include <sys/sysctl.h>
61151937Sjkim#include <sys/sx.h>
62151937Sjkim#include <sys/malloc.h>
63151937Sjkim
64151937Sjkim#include <machine/bus.h>
65151937Sjkim#include <machine/cpu.h>
66151937Sjkim#include <machine/cpufunc.h>
67151937Sjkim#include <machine/resource.h>
68151937Sjkim#include <machine/intr.h>
69151937Sjkim
70151937Sjkim#include <dev/ofw/openfirm.h>
71151937Sjkim#include <dev/ofw/ofw_bus.h>
72118611Snjl
73118611Snjl#include "twl.h"
74118611Snjl#include "twl_clks.h"
75118611Snjl
76118611Snjl
77118611Snjlstatic int twl_clks_debug = 1;
78118611Snjl
79118611Snjl
80118611Snjl/*
81241973Sjkim * Power Groups bits for the 4030 and 6030 devices
82118611Snjl */
83118611Snjl#define TWL4030_P3_GRP		0x80	/* Peripherals, power group */
84118611Snjl#define TWL4030_P2_GRP		0x40	/* Modem power group */
85118611Snjl#define TWL4030_P1_GRP		0x20	/* Application power group (FreeBSD control) */
86118611Snjl
87118611Snjl#define TWL6030_P3_GRP		0x04	/* Modem power group */
88118611Snjl#define TWL6030_P2_GRP		0x02	/* Connectivity power group */
89118611Snjl#define TWL6030_P1_GRP		0x01	/* Application power group (FreeBSD control) */
90118611Snjl
91118611Snjl/*
92118611Snjl * Register offsets within a clk regulator register set
93118611Snjl */
94118611Snjl#define TWL_CLKS_GRP		0x00	/* Regulator GRP register */
95118611Snjl#define TWL_CLKS_STATE		0x02	/* TWL6030 only */
96118611Snjl
97118611Snjl
98118611Snjl
99118611Snjl/**
100118611Snjl *  Support voltage regulators for the different IC's
101118611Snjl */
102118611Snjlstruct twl_clock {
103118611Snjl	const char	*name;
104118611Snjl	uint8_t		subdev;
105118611Snjl	uint8_t		regbase;
106118611Snjl};
107118611Snjl
108118611Snjlstatic const struct twl_clock twl4030_clocks[] = {
109118611Snjl	{ "32kclkout", 0, 0x8e },
110118611Snjl	{ NULL, 0, 0x00 }
111118611Snjl};
112118611Snjl
113241973Sjkimstatic const struct twl_clock twl6030_clocks[] = {
114118611Snjl	{ "clk32kg",     0, 0xbc },
115118611Snjl	{ "clk32kao",    0, 0xb9 },
116118611Snjl	{ "clk32kaudio", 0, 0xbf },
117118611Snjl	{ NULL, 0, 0x00 }
118118611Snjl};
119118611Snjl
120118611Snjl#define TWL_CLKS_MAX_NAMELEN  32
121118611Snjl
122118611Snjlstruct twl_clk_entry {
123118611Snjl	LIST_ENTRY(twl_clk_entry) link;
124118611Snjl	struct sysctl_oid *oid;
125118611Snjl	char		       name[TWL_CLKS_MAX_NAMELEN];
126118611Snjl	uint8_t            sub_dev;  /* the sub-device number for the clock */
127118611Snjl	uint8_t            reg_off;  /* register base address of the clock */
128118611Snjl};
129118611Snjl
130118611Snjlstruct twl_clks_softc {
131118611Snjl	device_t           sc_dev;   /* twl_clk device */
132118611Snjl	device_t           sc_pdev;  /* parent device (twl) */
133118611Snjl	struct sx          sc_sx;    /* internal locking */
134118611Snjl	struct intr_config_hook sc_init_hook;
135118611Snjl	LIST_HEAD(twl_clk_list, twl_clk_entry) sc_clks_list;
136118611Snjl};
137118611Snjl
138118611Snjl/**
139118611Snjl *	Macros for driver shared locking
140118611Snjl */
141118611Snjl#define TWL_CLKS_XLOCK(_sc)			sx_xlock(&(_sc)->sc_sx)
142118611Snjl#define	TWL_CLKS_XUNLOCK(_sc)		sx_xunlock(&(_sc)->sc_sx)
143118611Snjl#define TWL_CLKS_SLOCK(_sc)			sx_slock(&(_sc)->sc_sx)
144118611Snjl#define	TWL_CLKS_SUNLOCK(_sc)		sx_sunlock(&(_sc)->sc_sx)
145118611Snjl#define TWL_CLKS_LOCK_INIT(_sc)		sx_init(&(_sc)->sc_sx, "twl_clks")
146118611Snjl#define TWL_CLKS_LOCK_DESTROY(_sc)	sx_destroy(&(_sc)->sc_sx);
147118611Snjl
148151937Sjkim#define TWL_CLKS_ASSERT_LOCKED(_sc)	sx_assert(&(_sc)->sc_sx, SA_LOCKED);
149118611Snjl
150118611Snjl#define TWL_CLKS_LOCK_UPGRADE(_sc)               \
151118611Snjl	do {                                         \
152118611Snjl		while (!sx_try_upgrade(&(_sc)->sc_sx))   \
153118611Snjl			pause("twl_clks_ex", (hz / 100));    \
154118611Snjl	} while(0)
155118611Snjl#define TWL_CLKS_LOCK_DOWNGRADE(_sc)	sx_downgrade(&(_sc)->sc_sx);
156118611Snjl
157118611Snjl
158151937Sjkim
159118611Snjl
160118611Snjl/**
161118611Snjl *	twl_clks_read_1 - read single register from the TWL device
162118611Snjl *	twl_clks_write_1 - writes a single register in the TWL device
163118611Snjl *	@sc: device context
164118611Snjl *	@clk: the clock device we're reading from / writing to
165118611Snjl *	@off: offset within the clock's register set
166118611Snjl *	@val: the value to write or a pointer to a variable to store the result
167118611Snjl *
168118611Snjl *	RETURNS:
169118611Snjl *	Zero on success or an error code on failure.
170118611Snjl */
171118611Snjlstatic inline int
172118611Snjltwl_clks_read_1(struct twl_clks_softc *sc, struct twl_clk_entry *clk,
173118611Snjl	uint8_t off, uint8_t *val)
174118611Snjl{
175118611Snjl	return (twl_read(sc->sc_pdev, clk->sub_dev, clk->reg_off + off, val, 1));
176118611Snjl}
177118611Snjl
178118611Snjlstatic inline int
179118611Snjltwl_clks_write_1(struct twl_clks_softc *sc, struct twl_clk_entry *clk,
180118611Snjl	uint8_t off, uint8_t val)
181118611Snjl{
182118611Snjl	return (twl_write(sc->sc_pdev, clk->sub_dev, clk->reg_off + off, &val, 1));
183118611Snjl}
184118611Snjl
185118611Snjl
186118611Snjl/**
187118611Snjl *	twl_clks_is_enabled - determines if a clock is enabled
188118611Snjl *	@dev: TWL CLK device
189118611Snjl *	@name: the name of the clock
190118611Snjl *	@enabled: upon return will contain the 'enabled' state
191118611Snjl *
192118611Snjl *	LOCKING:
193118611Snjl *	Internally the function takes and releases the TWL lock.
194118611Snjl *
195118611Snjl *	RETURNS:
196118611Snjl *	Zero on success or a negative error code on failure.
197118611Snjl */
198118611Snjlint
199118611Snjltwl_clks_is_enabled(device_t dev, const char *name, int *enabled)
200151937Sjkim{
201118611Snjl	struct twl_clks_softc *sc = device_get_softc(dev);
202118611Snjl	struct twl_clk_entry *clk;
203118611Snjl	int found = 0;
204118611Snjl	int err;
205118611Snjl	uint8_t grp, state;
206118611Snjl
207118611Snjl	TWL_CLKS_SLOCK(sc);
208118611Snjl
209118611Snjl	LIST_FOREACH(clk, &sc->sc_clks_list, link) {
210151937Sjkim		if (strcmp(clk->name, name) == 0) {
211118611Snjl			found = 1;
212118611Snjl			break;
213118611Snjl		}
214118611Snjl	}
215118611Snjl
216118611Snjl	if (!found) {
217118611Snjl		TWL_CLKS_SUNLOCK(sc);
218118611Snjl		return (EINVAL);
219118611Snjl	}
220118611Snjl
221118611Snjl
222118611Snjl	if (twl_is_4030(sc->sc_pdev)) {
223118611Snjl
224118611Snjl		err = twl_clks_read_1(sc, clk, TWL_CLKS_GRP, &grp);
225118611Snjl		if (!err)
226118611Snjl			*enabled = (grp & TWL4030_P1_GRP);
227118611Snjl
228118611Snjl	} else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev)) {
229118611Snjl
230118611Snjl		TWL_CLKS_LOCK_UPGRADE(sc);
231151937Sjkim
232151937Sjkim		/* Check the clock is in the application group */
233118611Snjl		if (twl_is_6030(sc->sc_pdev)) {
234118611Snjl			err = twl_clks_read_1(sc, clk, TWL_CLKS_GRP, &grp);
235118611Snjl			if (err) {
236118611Snjl				TWL_CLKS_LOCK_DOWNGRADE(sc);
237118611Snjl				goto done;
238118611Snjl			}
239118611Snjl
240118611Snjl			if (!(grp & TWL6030_P1_GRP)) {
241118611Snjl				TWL_CLKS_LOCK_DOWNGRADE(sc);
242118611Snjl				*enabled = 0; /* disabled */
243118611Snjl				goto done;
244118611Snjl			}
245118611Snjl		}
246118611Snjl
247118611Snjl		/* Read the application mode state and verify it's ON */
248118611Snjl		err = twl_clks_read_1(sc, clk, TWL_CLKS_STATE, &state);
249118611Snjl		if (!err)
250118611Snjl			*enabled = ((state & 0x0C) == 0x04);
251118611Snjl
252118611Snjl		TWL_CLKS_LOCK_DOWNGRADE(sc);
253118611Snjl
254118611Snjl	} else {
255118611Snjl		err = EINVAL;
256118611Snjl	}
257118611Snjl
258118611Snjldone:
259118611Snjl	TWL_CLKS_SUNLOCK(sc);
260250838Sjkim	return (err);
261118611Snjl}
262118611Snjl
263118611Snjl
264118611Snjl/**
265118611Snjl *	twl_clks_set_state - enables/disables a clock output
266118611Snjl *	@sc: device context
267118611Snjl *	@clk: the clock entry to enable/disable
268118611Snjl *	@enable: non-zero the clock is enabled, zero the clock is disabled
269118611Snjl *
270118611Snjl *	LOCKING:
271118611Snjl *	The TWL CLK lock must be held before this function is called.
272118611Snjl *
273118611Snjl *	RETURNS:
274118611Snjl *	Zero on success or an error code on failure.
275118611Snjl */
276118611Snjlstatic int
277118611Snjltwl_clks_set_state(struct twl_clks_softc *sc, struct twl_clk_entry *clk,
278118611Snjl	int enable)
279118611Snjl{
280118611Snjl	int xlocked;
281118611Snjl	int err;
282118611Snjl	uint8_t grp;
283118611Snjl
284118611Snjl	TWL_CLKS_ASSERT_LOCKED(sc);
285118611Snjl
286118611Snjl	/* Upgrade the lock to exclusive because about to perform read-mod-write */
287118611Snjl	xlocked = sx_xlocked(&sc->sc_sx);
288118611Snjl	if (!xlocked)
289118611Snjl		TWL_CLKS_LOCK_UPGRADE(sc);
290118611Snjl
291118611Snjl	err = twl_clks_read_1(sc, clk, TWL_CLKS_GRP, &grp);
292118611Snjl	if (err)
293118611Snjl		goto done;
294118611Snjl
295118611Snjl	if (twl_is_4030(sc->sc_pdev)) {
296118611Snjl
297118611Snjl		/* On the TWL4030 we just need to ensure the clock is in the right
298118611Snjl		 * power domain, don't need to turn on explicitly like TWL6030.
299118611Snjl		 */
300118611Snjl		if (enable)
301118611Snjl			grp |= TWL4030_P1_GRP;
302118611Snjl		else
303118611Snjl			grp &= ~(TWL4030_P1_GRP | TWL4030_P2_GRP | TWL4030_P3_GRP);
304118611Snjl
305118611Snjl		err = twl_clks_write_1(sc, clk, TWL_CLKS_GRP, grp);
306118611Snjl
307118611Snjl	} else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev)) {
308118611Snjl
309118611Snjl		/* Make sure the clock belongs to at least the APP power group */
310118611Snjl		if (twl_is_6030(sc->sc_pdev) && !(grp & TWL6030_P1_GRP)) {
311118611Snjl			grp |= TWL6030_P1_GRP;
312118611Snjl			err = twl_clks_write_1(sc, clk, TWL_CLKS_GRP, grp);
313118611Snjl			if (err)
314118611Snjl				goto done;
315118611Snjl		}
316118611Snjl
317118611Snjl		/* On TWL6030 we need to make sure we disable power for all groups */
318118611Snjl		if (twl_is_6030(sc->sc_pdev))
319118611Snjl			grp = TWL6030_P1_GRP | TWL6030_P2_GRP | TWL6030_P3_GRP;
320118611Snjl		else
321118611Snjl			grp = 0x00;
322118611Snjl
323118611Snjl		/* Set the state of the clock */
324118611Snjl		if (enable)
325118611Snjl			err = twl_clks_write_1(sc, clk, TWL_CLKS_STATE, (grp << 5) | 0x01);
326118611Snjl		else
327118611Snjl			err = twl_clks_write_1(sc, clk, TWL_CLKS_STATE, (grp << 5));
328118611Snjl
329250838Sjkim	} else {
330118611Snjl
331118611Snjl		err = EINVAL;
332118611Snjl	}
333118611Snjl
334118611Snjldone:
335118611Snjl	if (!xlocked)
336118611Snjl		TWL_CLKS_LOCK_DOWNGRADE(sc);
337151937Sjkim
338151937Sjkim	if ((twl_clks_debug > 1) && !err)
339118611Snjl		device_printf(sc->sc_dev, "%s : %sabled\n", clk->name,
340118611Snjl			enable ? "en" : "dis");
341118611Snjl
342118611Snjl	return (err);
343118611Snjl}
344118611Snjl
345118611Snjl
346118611Snjl/**
347118611Snjl *	twl_clks_disable - disables a clock output
348118611Snjl *	@dev: TWL clk device
349118611Snjl*	@name: the name of the clock
350118611Snjl *
351118611Snjl *	LOCKING:
352118611Snjl *	Internally the function takes and releases the TWL lock.
353118611Snjl *
354118611Snjl *	RETURNS:
355118611Snjl*	Zero on success or an error code on failure.
356118611Snjl */
357118611Snjlint
358118611Snjltwl_clks_disable(device_t dev, const char *name)
359118611Snjl{
360118611Snjl	struct twl_clks_softc *sc = device_get_softc(dev);
361118611Snjl	struct twl_clk_entry *clk;
362118611Snjl	int err = EINVAL;
363118611Snjl
364118611Snjl	TWL_CLKS_SLOCK(sc);
365118611Snjl
366118611Snjl	LIST_FOREACH(clk, &sc->sc_clks_list, link) {
367118611Snjl		if (strcmp(clk->name, name) == 0) {
368118611Snjl			err = twl_clks_set_state(sc, clk, 0);
369118611Snjl			break;
370118611Snjl		}
371118611Snjl	}
372118611Snjl
373118611Snjl	TWL_CLKS_SUNLOCK(sc);
374118611Snjl	return (err);
375118611Snjl}
376118611Snjl
377118611Snjl/**
378118611Snjl *	twl_clks_enable - enables a clock output
379118611Snjl *	@dev: TWL clk device
380118611Snjl *	@name: the name of the clock
381118611Snjl *
382118611Snjl *	LOCKING:
383118611Snjl *	Internally the function takes and releases the TWL CLKS lock.
384151937Sjkim *
385151937Sjkim *	RETURNS:
386151937Sjkim *	Zero on success or an error code on failure.
387151937Sjkim */
388118611Snjlint
389118611Snjltwl_clks_enable(device_t dev, const char *name)
390118611Snjl{
391118611Snjl	struct twl_clks_softc *sc = device_get_softc(dev);
392118611Snjl	struct twl_clk_entry *clk;
393151937Sjkim	int err = EINVAL;
394151937Sjkim
395118611Snjl	TWL_CLKS_SLOCK(sc);
396118611Snjl
397118611Snjl	LIST_FOREACH(clk, &sc->sc_clks_list, link) {
398118611Snjl		if (strcmp(clk->name, name) == 0) {
399118611Snjl			err = twl_clks_set_state(sc, clk, 1);
400118611Snjl			break;
401118611Snjl		}
402118611Snjl	}
403118611Snjl
404118611Snjl	TWL_CLKS_SUNLOCK(sc);
405118611Snjl	return (err);
406118611Snjl}
407118611Snjl
408118611Snjl/**
409118611Snjl *	twl_clks_sysctl_clock - reads the state of the clock
410118611Snjl *	@SYSCTL_HANDLER_ARGS: arguments for the callback
411118611Snjl *
412118611Snjl *	Returns the clock status; disabled is zero and enabled is non-zero.
413118611Snjl *
414118611Snjl *	LOCKING:
415118611Snjl *	It's expected the TWL lock is held while this function is called.
416118611Snjl *
417118611Snjl *	RETURNS:
418118611Snjl *	EIO if device is not present, otherwise 0 is returned.
419151937Sjkim */
420151937Sjkimstatic int
421151937Sjkimtwl_clks_sysctl_clock(SYSCTL_HANDLER_ARGS)
422151937Sjkim{
423151937Sjkim	struct twl_clks_softc *sc = (struct twl_clks_softc*)arg1;
424151937Sjkim	int err;
425151937Sjkim	int enabled = 0;
426151937Sjkim
427151937Sjkim	if ((err = twl_clks_is_enabled(sc->sc_dev, oidp->oid_name, &enabled)) != 0)
428151937Sjkim		return err;
429241973Sjkim
430151937Sjkim	return sysctl_handle_int(oidp, &enabled, 0, req);
431151937Sjkim}
432151937Sjkim
433151937Sjkim/**
434151937Sjkim *	twl_clks_add_clock - adds single clock sysctls for the device
435151937Sjkim *	@sc: device soft context
436151937Sjkim *	@name: the name of the regulator
437151937Sjkim *	@nsub: the number of the subdevice
438151937Sjkim *	@regbase: the base address of the clocks registers
439151937Sjkim *
440151937Sjkim *	Adds a single clock to the device and also a sysctl interface for
441151937Sjkim *	querying it's status.
442151937Sjkim *
443151937Sjkim *	LOCKING:
444151937Sjkim *	It's expected the exclusive lock is held while this function is called.
445151937Sjkim *
446151937Sjkim *	RETURNS:
447151937Sjkim *	Pointer to the new clock entry on success, otherwise NULL on failure.
448151937Sjkim */
449151937Sjkimstatic struct twl_clk_entry*
450151937Sjkimtwl_clks_add_clock(struct twl_clks_softc *sc, const char *name,
451151937Sjkim	uint8_t nsub, uint8_t regbase)
452151937Sjkim{
453151937Sjkim	struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev);
454151937Sjkim	struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev);
455151937Sjkim	struct twl_clk_entry *new;
456151937Sjkim
457	TWL_CLKS_ASSERT_LOCKED(sc);
458
459	new = malloc(sizeof(struct twl_clk_entry), M_DEVBUF, M_NOWAIT | M_ZERO);
460	if (new == NULL)
461		return (NULL);
462
463
464	strncpy(new->name, name, TWL_CLKS_MAX_NAMELEN);
465	new->name[TWL_CLKS_MAX_NAMELEN - 1] = '\0';
466
467	new->sub_dev = nsub;
468	new->reg_off = regbase;
469
470
471
472	/* Add a sysctl entry for the clock */
473	new->oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, name,
474	    CTLTYPE_INT | CTLFLAG_RD, sc, 0,
475	    twl_clks_sysctl_clock, "I", "external clock");
476
477	/* Finally add the regulator to list of supported regulators */
478	LIST_INSERT_HEAD(&sc->sc_clks_list, new, link);
479
480	return (new);
481}
482
483/**
484 *	twl_clks_add_clocks - populates the internal list of clocks
485 *	@sc: device soft context
486 *	@chip: the name of the chip used in the hints
487 *	@clks the list of clocks supported by the device
488 *
489 *	Loops over the list of clocks and adds them to the device context. Also
490 *	scans the FDT to determine if there are any clocks that should be
491 *	enabled/disabled automatically.
492 *
493 *	LOCKING:
494 *	Internally takes the exclusive lock while adding the clocks to the
495 *	device context.
496 *
497 *	RETURNS:
498 *	Always returns 0.
499 */
500static int
501twl_clks_add_clocks(struct twl_clks_softc *sc, const struct twl_clock *clks)
502{
503	int err;
504	const struct twl_clock *walker;
505	struct twl_clk_entry *entry;
506	phandle_t child;
507	char rnames[256];
508	char *name, *state;
509	int len = 0, prop_len;
510	int enable;
511
512
513	TWL_CLKS_XLOCK(sc);
514
515	/* Add the regulators from the list */
516	walker = &clks[0];
517	while (walker->name != NULL) {
518
519		/* Add the regulator to the list */
520		entry = twl_clks_add_clock(sc, walker->name, walker->subdev,
521		    walker->regbase);
522		if (entry == NULL)
523			continue;
524
525		walker++;
526	}
527
528	/* Check for any FDT settings that need to be applied */
529	child = ofw_bus_get_node(sc->sc_pdev);
530	if (child) {
531
532		prop_len = OF_getprop(child, "external-clocks", rnames, sizeof(rnames));
533		while (len < prop_len) {
534			name = rnames + len;
535			len += strlen(name) + 1;
536			if ((len >= prop_len) || (name[0] == '\0'))
537				break;
538
539			state = rnames + len;
540			len += strlen(state) + 1;
541			if (state[0] == '\0')
542				break;
543
544			enable = !strncmp(state, "on", 2);
545
546			LIST_FOREACH(entry, &sc->sc_clks_list, link) {
547				if (strcmp(entry->name, name) == 0) {
548					twl_clks_set_state(sc, entry, enable);
549					break;
550				}
551			}
552		}
553	}
554
555	TWL_CLKS_XUNLOCK(sc);
556
557
558	if (twl_clks_debug) {
559		LIST_FOREACH(entry, &sc->sc_clks_list, link) {
560			err = twl_clks_is_enabled(sc->sc_dev, entry->name, &enable);
561			if (!err)
562				device_printf(sc->sc_dev, "%s : %s\n", entry->name,
563					enable ? "on" : "off");
564		}
565	}
566
567	return (0);
568}
569
570/**
571 *	twl_clks_init - initialises the list of clocks
572 *	@dev: the twl_clks device
573 *
574 *	This function is called as an intrhook once interrupts have been enabled,
575 *	this is done so that the driver has the option to enable/disable a clock
576 *	based on settings providied in the FDT.
577 *
578 *	LOCKING:
579 *	May takes the exclusive lock in the function.
580 */
581static void
582twl_clks_init(void *dev)
583{
584	struct twl_clks_softc *sc;
585
586	sc = device_get_softc((device_t)dev);
587
588	if (twl_is_4030(sc->sc_pdev))
589		twl_clks_add_clocks(sc, twl4030_clocks);
590	else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev))
591		twl_clks_add_clocks(sc, twl6030_clocks);
592
593	config_intrhook_disestablish(&sc->sc_init_hook);
594}
595
596static int
597twl_clks_probe(device_t dev)
598{
599	if (twl_is_4030(device_get_parent(dev)))
600		device_set_desc(dev, "TI TWL4030 PMIC External Clocks");
601	else if (twl_is_6025(device_get_parent(dev)) ||
602	         twl_is_6030(device_get_parent(dev)))
603		device_set_desc(dev, "TI TWL6025/TWL6030 PMIC External Clocks");
604	else
605		return (ENXIO);
606
607	return (0);
608}
609
610static int
611twl_clks_attach(device_t dev)
612{
613	struct twl_clks_softc *sc;
614
615	sc = device_get_softc(dev);
616	sc->sc_dev = dev;
617	sc->sc_pdev = device_get_parent(dev);
618
619	TWL_CLKS_LOCK_INIT(sc);
620
621	LIST_INIT(&sc->sc_clks_list);
622
623
624	sc->sc_init_hook.ich_func = twl_clks_init;
625	sc->sc_init_hook.ich_arg = dev;
626
627	if (config_intrhook_establish(&sc->sc_init_hook) != 0)
628		return (ENOMEM);
629
630	return (0);
631}
632
633static int
634twl_clks_detach(device_t dev)
635{
636	struct twl_clks_softc *sc;
637	struct twl_clk_entry *clk;
638	struct twl_clk_entry *tmp;
639
640	sc = device_get_softc(dev);
641
642	TWL_CLKS_XLOCK(sc);
643
644	LIST_FOREACH_SAFE(clk, &sc->sc_clks_list, link, tmp) {
645		LIST_REMOVE(clk, link);
646		sysctl_remove_oid(clk->oid, 1, 0);
647		free(clk, M_DEVBUF);
648	}
649
650	TWL_CLKS_XUNLOCK(sc);
651
652	TWL_CLKS_LOCK_DESTROY(sc);
653
654	return (0);
655}
656
657static device_method_t twl_clks_methods[] = {
658	DEVMETHOD(device_probe,		twl_clks_probe),
659	DEVMETHOD(device_attach,	twl_clks_attach),
660	DEVMETHOD(device_detach,	twl_clks_detach),
661
662	{0, 0},
663};
664
665static driver_t twl_clks_driver = {
666	"twl_clks",
667	twl_clks_methods,
668	sizeof(struct twl_clks_softc),
669};
670
671static devclass_t twl_clks_devclass;
672
673DRIVER_MODULE(twl_clks, twl, twl_clks_driver, twl_clks_devclass, 0, 0);
674MODULE_VERSION(twl_clks, 1);
675