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