twl_clks.c revision 239281
1/*-
2 * Copyright (c) 2012
3 *	Ben Gray <bgray@freebsd.org>.
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/cdefs.h>
29__FBSDID("$FreeBSD: head/sys/arm/ti/twl/twl_clks.c 239281 2012-08-15 06:31:32Z gonzo $");
30
31/*
32 * Texas Instruments TWL4030/TWL5030/TWL60x0/TPS659x0 Power Management.
33 *
34 * This driver covers the external clocks, allows for enabling &
35 * disabling their output.
36 *
37 *
38 *
39 * FLATTENED DEVICE TREE (FDT)
40 * Startup override settings can be specified in the FDT, if they are they
41 * should be under the twl parent device and take the following form:
42 *
43 *    external-clocks = "name1", "state1",
44 *                      "name2", "state2",
45 *                      etc;
46 *
47 * Each override should be a pair, the first entry is the name of the clock
48 * the second is the state to set, possible strings are either "on" or "off".
49 *
50 */
51
52#include <sys/param.h>
53#include <sys/systm.h>
54#include <sys/kernel.h>
55#include <sys/lock.h>
56#include <sys/module.h>
57#include <sys/bus.h>
58#include <sys/resource.h>
59#include <sys/rman.h>
60#include <sys/sysctl.h>
61#include <sys/sx.h>
62#include <sys/malloc.h>
63
64#include <machine/bus.h>
65#include <machine/cpu.h>
66#include <machine/cpufunc.h>
67#include <machine/frame.h>
68#include <machine/resource.h>
69#include <machine/intr.h>
70
71#include <dev/ofw/openfirm.h>
72#include <dev/ofw/ofw_bus.h>
73
74#include "twl.h"
75#include "twl_clks.h"
76
77
78static int twl_clks_debug = 1;
79
80
81/*
82 * Power Groups bits for the 4030 and 6030 devices
83 */
84#define TWL4030_P3_GRP		0x80	/* Peripherals, power group */
85#define TWL4030_P2_GRP		0x40	/* Modem power group */
86#define TWL4030_P1_GRP		0x20	/* Application power group (FreeBSD control) */
87
88#define TWL6030_P3_GRP		0x04	/* Modem power group */
89#define TWL6030_P2_GRP		0x02	/* Connectivity power group */
90#define TWL6030_P1_GRP		0x01	/* Application power group (FreeBSD control) */
91
92/*
93 * Register offsets within a clk regulator register set
94 */
95#define TWL_CLKS_GRP		0x00	/* Regulator GRP register */
96#define TWL_CLKS_STATE		0x02	/* TWL6030 only */
97
98
99
100/**
101 *  Support voltage regulators for the different IC's
102 */
103struct twl_clock {
104	const char	*name;
105	uint8_t		subdev;
106	uint8_t		regbase;
107};
108
109static const struct twl_clock twl4030_clocks[] = {
110	{ "32kclkout", 0, 0x8e },
111	{ NULL, 0, 0x00 }
112};
113
114static const struct twl_clock twl6030_clocks[] = {
115	{ "clk32kg",     0, 0xbc },
116	{ "clk32kao",    0, 0xb9 },
117	{ "clk32kaudio", 0, 0xbf },
118	{ NULL, 0, 0x00 }
119};
120
121#define TWL_CLKS_MAX_NAMELEN  32
122
123struct twl_clk_entry {
124	LIST_ENTRY(twl_clk_entry) link;
125	struct sysctl_oid *oid;
126	char		       name[TWL_CLKS_MAX_NAMELEN];
127	uint8_t            sub_dev;  /* the sub-device number for the clock */
128	uint8_t            reg_off;  /* register base address of the clock */
129};
130
131struct twl_clks_softc {
132	device_t           sc_dev;   /* twl_clk device */
133	device_t           sc_pdev;  /* parent device (twl) */
134	struct sx          sc_sx;    /* internal locking */
135	struct intr_config_hook sc_init_hook;
136	LIST_HEAD(twl_clk_list, twl_clk_entry) sc_clks_list;
137};
138
139/**
140 *	Macros for driver shared locking
141 */
142#define TWL_CLKS_XLOCK(_sc)			sx_xlock(&(_sc)->sc_sx)
143#define	TWL_CLKS_XUNLOCK(_sc)		sx_xunlock(&(_sc)->sc_sx)
144#define TWL_CLKS_SLOCK(_sc)			sx_slock(&(_sc)->sc_sx)
145#define	TWL_CLKS_SUNLOCK(_sc)		sx_sunlock(&(_sc)->sc_sx)
146#define TWL_CLKS_LOCK_INIT(_sc)		sx_init(&(_sc)->sc_sx, "twl_clks")
147#define TWL_CLKS_LOCK_DESTROY(_sc)	sx_destroy(&(_sc)->sc_sx);
148
149#define TWL_CLKS_ASSERT_LOCKED(_sc)	sx_assert(&(_sc)->sc_sx, SA_LOCKED);
150
151#define TWL_CLKS_LOCK_UPGRADE(_sc)               \
152	do {                                         \
153		while (!sx_try_upgrade(&(_sc)->sc_sx))   \
154			pause("twl_clks_ex", (hz / 100));    \
155	} while(0)
156#define TWL_CLKS_LOCK_DOWNGRADE(_sc)	sx_downgrade(&(_sc)->sc_sx);
157
158
159
160
161/**
162 *	twl_clks_read_1 - read single register from the TWL device
163 *	twl_clks_write_1 - writes a single register in the TWL device
164 *	@sc: device context
165 *	@clk: the clock device we're reading from / writing to
166 *	@off: offset within the clock's register set
167 *	@val: the value to write or a pointer to a variable to store the result
168 *
169 *	RETURNS:
170 *	Zero on success or an error code on failure.
171 */
172static inline int
173twl_clks_read_1(struct twl_clks_softc *sc, struct twl_clk_entry *clk,
174	uint8_t off, uint8_t *val)
175{
176	return (twl_read(sc->sc_pdev, clk->sub_dev, clk->reg_off + off, val, 1));
177}
178
179static inline int
180twl_clks_write_1(struct twl_clks_softc *sc, struct twl_clk_entry *clk,
181	uint8_t off, uint8_t val)
182{
183	return (twl_write(sc->sc_pdev, clk->sub_dev, clk->reg_off + off, &val, 1));
184}
185
186
187/**
188 *	twl_clks_is_enabled - determines if a clock is enabled
189 *	@dev: TWL CLK device
190 *	@name: the name of the clock
191 *	@enabled: upon return will contain the 'enabled' state
192 *
193 *	LOCKING:
194 *	Internally the function takes and releases the TWL lock.
195 *
196 *	RETURNS:
197 *	Zero on success or a negative error code on failure.
198 */
199int
200twl_clks_is_enabled(device_t dev, const char *name, int *enabled)
201{
202	struct twl_clks_softc *sc = device_get_softc(dev);
203	struct twl_clk_entry *clk;
204	int found = 0;
205	int err;
206	uint8_t grp, state;
207
208	TWL_CLKS_SLOCK(sc);
209
210	LIST_FOREACH(clk, &sc->sc_clks_list, link) {
211		if (strcmp(clk->name, name) == 0) {
212			found = 1;
213			break;
214		}
215	}
216
217	if (!found) {
218		TWL_CLKS_SUNLOCK(sc);
219		return (EINVAL);
220	}
221
222
223	if (twl_is_4030(sc->sc_pdev)) {
224
225		err = twl_clks_read_1(sc, clk, TWL_CLKS_GRP, &grp);
226		if (!err)
227			*enabled = (grp & TWL4030_P1_GRP);
228
229	} else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev)) {
230
231		TWL_CLKS_LOCK_UPGRADE(sc);
232
233		/* Check the clock is in the application group */
234		if (twl_is_6030(sc->sc_pdev)) {
235			err = twl_clks_read_1(sc, clk, TWL_CLKS_GRP, &grp);
236			if (err) {
237				TWL_CLKS_LOCK_DOWNGRADE(sc);
238				goto done;
239			}
240
241			if (!(grp & TWL6030_P1_GRP)) {
242				TWL_CLKS_LOCK_DOWNGRADE(sc);
243				*enabled = 0; /* disabled */
244				goto done;
245			}
246		}
247
248		/* Read the application mode state and verify it's ON */
249		err = twl_clks_read_1(sc, clk, TWL_CLKS_STATE, &state);
250		if (!err)
251			*enabled = ((state & 0x0C) == 0x04);
252
253		TWL_CLKS_LOCK_DOWNGRADE(sc);
254
255	} else {
256		err = EINVAL;
257	}
258
259done:
260	TWL_CLKS_SUNLOCK(sc);
261	return (err);
262}
263
264
265/**
266 *	twl_clks_set_state - enables/disables a clock output
267 *	@sc: device context
268 *	@clk: the clock entry to enable/disable
269 *	@enable: non-zero the clock is enabled, zero the clock is disabled
270 *
271 *	LOCKING:
272 *	The TWL CLK lock must be held before this function is called.
273 *
274 *	RETURNS:
275 *	Zero on success or an error code on failure.
276 */
277static int
278twl_clks_set_state(struct twl_clks_softc *sc, struct twl_clk_entry *clk,
279	int enable)
280{
281	int xlocked;
282	int err;
283	uint8_t grp;
284
285	TWL_CLKS_ASSERT_LOCKED(sc);
286
287	/* Upgrade the lock to exclusive because about to perform read-mod-write */
288	xlocked = sx_xlocked(&sc->sc_sx);
289	if (!xlocked)
290		TWL_CLKS_LOCK_UPGRADE(sc);
291
292	err = twl_clks_read_1(sc, clk, TWL_CLKS_GRP, &grp);
293	if (err)
294		goto done;
295
296	if (twl_is_4030(sc->sc_pdev)) {
297
298		/* On the TWL4030 we just need to ensure the clock is in the right
299		 * power domain, don't need to turn on explicitly like TWL6030.
300		 */
301		if (enable)
302			grp |= TWL4030_P1_GRP;
303		else
304			grp &= ~(TWL4030_P1_GRP | TWL4030_P2_GRP | TWL4030_P3_GRP);
305
306		err = twl_clks_write_1(sc, clk, TWL_CLKS_GRP, grp);
307
308	} else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev)) {
309
310		/* Make sure the clock belongs to at least the APP power group */
311		if (twl_is_6030(sc->sc_pdev) && !(grp & TWL6030_P1_GRP)) {
312			grp |= TWL6030_P1_GRP;
313			err = twl_clks_write_1(sc, clk, TWL_CLKS_GRP, grp);
314			if (err)
315				goto done;
316		}
317
318		/* On TWL6030 we need to make sure we disable power for all groups */
319		if (twl_is_6030(sc->sc_pdev))
320			grp = TWL6030_P1_GRP | TWL6030_P2_GRP | TWL6030_P3_GRP;
321		else
322			grp = 0x00;
323
324		/* Set the state of the clock */
325		if (enable)
326			err = twl_clks_write_1(sc, clk, TWL_CLKS_STATE, (grp << 5) | 0x01);
327		else
328			err = twl_clks_write_1(sc, clk, TWL_CLKS_STATE, (grp << 5));
329
330	} else {
331
332		err = EINVAL;
333	}
334
335done:
336	if (!xlocked)
337		TWL_CLKS_LOCK_DOWNGRADE(sc);
338
339	if ((twl_clks_debug > 1) && !err)
340		device_printf(sc->sc_dev, "%s : %sabled\n", clk->name,
341			enable ? "en" : "dis");
342
343	return (err);
344}
345
346
347/**
348 *	twl_clks_disable - disables a clock output
349 *	@dev: TWL clk device
350*	@name: the name of the clock
351 *
352 *	LOCKING:
353 *	Internally the function takes and releases the TWL lock.
354 *
355 *	RETURNS:
356*	Zero on success or an error code on failure.
357 */
358int
359twl_clks_disable(device_t dev, const char *name)
360{
361	struct twl_clks_softc *sc = device_get_softc(dev);
362	struct twl_clk_entry *clk;
363	int err = EINVAL;
364
365	TWL_CLKS_SLOCK(sc);
366
367	LIST_FOREACH(clk, &sc->sc_clks_list, link) {
368		if (strcmp(clk->name, name) == 0) {
369			err = twl_clks_set_state(sc, clk, 0);
370			break;
371		}
372	}
373
374	TWL_CLKS_SUNLOCK(sc);
375	return (err);
376}
377
378/**
379 *	twl_clks_enable - enables a clock output
380 *	@dev: TWL clk device
381 *	@name: the name of the clock
382 *
383 *	LOCKING:
384 *	Internally the function takes and releases the TWL CLKS lock.
385 *
386 *	RETURNS:
387 *	Zero on success or an error code on failure.
388 */
389int
390twl_clks_enable(device_t dev, const char *name)
391{
392	struct twl_clks_softc *sc = device_get_softc(dev);
393	struct twl_clk_entry *clk;
394	int err = EINVAL;
395
396	TWL_CLKS_SLOCK(sc);
397
398	LIST_FOREACH(clk, &sc->sc_clks_list, link) {
399		if (strcmp(clk->name, name) == 0) {
400			err = twl_clks_set_state(sc, clk, 1);
401			break;
402		}
403	}
404
405	TWL_CLKS_SUNLOCK(sc);
406	return (err);
407}
408
409/**
410 *	twl_clks_sysctl_clock - reads the state of the clock
411 *	@SYSCTL_HANDLER_ARGS: arguments for the callback
412 *
413 *	Returns the clock status; disabled is zero and enabled is non-zero.
414 *
415 *	LOCKING:
416 *	It's expected the TWL lock is held while this function is called.
417 *
418 *	RETURNS:
419 *	EIO if device is not present, otherwise 0 is returned.
420 */
421static int
422twl_clks_sysctl_clock(SYSCTL_HANDLER_ARGS)
423{
424	struct twl_clks_softc *sc = (struct twl_clks_softc*)arg1;
425	int err;
426	int enabled = 0;
427
428	if ((err = twl_clks_is_enabled(sc->sc_dev, oidp->oid_name, &enabled)) != 0)
429		return err;
430
431	return sysctl_handle_int(oidp, &enabled, 0, req);
432}
433
434/**
435 *	twl_clks_add_clock - adds single clock sysctls for the device
436 *	@sc: device soft context
437 *	@name: the name of the regulator
438 *	@nsub: the number of the subdevice
439 *	@regbase: the base address of the clocks registers
440 *
441 *	Adds a single clock to the device and also a sysctl interface for
442 *	querying it's status.
443 *
444 *	LOCKING:
445 *	It's expected the exclusive lock is held while this function is called.
446 *
447 *	RETURNS:
448 *	Pointer to the new clock entry on success, otherwise NULL on failure.
449 */
450static struct twl_clk_entry*
451twl_clks_add_clock(struct twl_clks_softc *sc, const char *name,
452	uint8_t nsub, uint8_t regbase)
453{
454	struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev);
455	struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev);
456	struct twl_clk_entry *new;
457
458	TWL_CLKS_ASSERT_LOCKED(sc);
459
460	new = malloc(sizeof(struct twl_clk_entry), M_DEVBUF, M_NOWAIT | M_ZERO);
461	if (new == NULL)
462		return (NULL);
463
464
465	strncpy(new->name, name, TWL_CLKS_MAX_NAMELEN);
466	new->name[TWL_CLKS_MAX_NAMELEN - 1] = '\0';
467
468	new->sub_dev = nsub;
469	new->reg_off = regbase;
470
471
472
473	/* Add a sysctl entry for the clock */
474	new->oid = SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, name,
475	    CTLTYPE_INT | CTLFLAG_RD, sc, 0,
476	    twl_clks_sysctl_clock, "I", "external clock");
477
478	/* Finally add the regulator to list of supported regulators */
479	LIST_INSERT_HEAD(&sc->sc_clks_list, new, link);
480
481	return (new);
482}
483
484/**
485 *	twl_clks_add_clocks - populates the internal list of clocks
486 *	@sc: device soft context
487 *	@chip: the name of the chip used in the hints
488 *	@clks the list of clocks supported by the device
489 *
490 *	Loops over the list of clocks and adds them to the device context. Also
491 *	scans the FDT to determine if there are any clocks that should be
492 *	enabled/disabled automatically.
493 *
494 *	LOCKING:
495 *	Internally takes the exclusive lock while adding the clocks to the
496 *	device context.
497 *
498 *	RETURNS:
499 *	Always returns 0.
500 */
501static int
502twl_clks_add_clocks(struct twl_clks_softc *sc, const struct twl_clock *clks)
503{
504	int err;
505	const struct twl_clock *walker;
506	struct twl_clk_entry *entry;
507	phandle_t child;
508	char rnames[256];
509	char *name, *state;
510	int len = 0, prop_len;
511	int enable;
512
513
514	TWL_CLKS_XLOCK(sc);
515
516	/* Add the regulators from the list */
517	walker = &clks[0];
518	while (walker->name != NULL) {
519
520		/* Add the regulator to the list */
521		entry = twl_clks_add_clock(sc, walker->name, walker->subdev,
522		    walker->regbase);
523		if (entry == NULL)
524			continue;
525
526		walker++;
527	}
528
529	/* Check for any FDT settings that need to be applied */
530	child = ofw_bus_get_node(sc->sc_pdev);
531	if (child) {
532
533		prop_len = OF_getprop(child, "external-clocks", rnames, sizeof(rnames));
534		while (len < prop_len) {
535			name = rnames + len;
536			len += strlen(name) + 1;
537			if ((len >= prop_len) || (name[0] == '\0'))
538				break;
539
540			state = rnames + len;
541			len += strlen(state) + 1;
542			if (state[0] == '\0')
543				break;
544
545			enable = !strncmp(state, "on", 2);
546
547			LIST_FOREACH(entry, &sc->sc_clks_list, link) {
548				if (strcmp(entry->name, name) == 0) {
549					twl_clks_set_state(sc, entry, enable);
550					break;
551				}
552			}
553		}
554	}
555
556	TWL_CLKS_XUNLOCK(sc);
557
558
559	if (twl_clks_debug) {
560		LIST_FOREACH(entry, &sc->sc_clks_list, link) {
561			err = twl_clks_is_enabled(sc->sc_dev, entry->name, &enable);
562			if (!err)
563				device_printf(sc->sc_dev, "%s : %s\n", entry->name,
564					enable ? "on" : "off");
565		}
566	}
567
568	return (0);
569}
570
571/**
572 *	twl_clks_init - initialises the list of clocks
573 *	@dev: the twl_clks device
574 *
575 *	This function is called as an intrhook once interrupts have been enabled,
576 *	this is done so that the driver has the option to enable/disable a clock
577 *	based on settings providied in the FDT.
578 *
579 *	LOCKING:
580 *	May takes the exclusive lock in the function.
581 */
582static void
583twl_clks_init(void *dev)
584{
585	struct twl_clks_softc *sc;
586
587	sc = device_get_softc((device_t)dev);
588
589	if (twl_is_4030(sc->sc_pdev))
590		twl_clks_add_clocks(sc, twl4030_clocks);
591	else if (twl_is_6030(sc->sc_pdev) || twl_is_6025(sc->sc_pdev))
592		twl_clks_add_clocks(sc, twl6030_clocks);
593
594	config_intrhook_disestablish(&sc->sc_init_hook);
595}
596
597static int
598twl_clks_probe(device_t dev)
599{
600	if (twl_is_4030(device_get_parent(dev)))
601		device_set_desc(dev, "TI TWL4030 PMIC External Clocks");
602	else if (twl_is_6025(device_get_parent(dev)) ||
603	         twl_is_6030(device_get_parent(dev)))
604		device_set_desc(dev, "TI TWL6025/TWL6030 PMIC External Clocks");
605	else
606		return (ENXIO);
607
608	return (0);
609}
610
611static int
612twl_clks_attach(device_t dev)
613{
614	struct twl_clks_softc *sc;
615
616	sc = device_get_softc(dev);
617	sc->sc_dev = dev;
618	sc->sc_pdev = device_get_parent(dev);
619
620	TWL_CLKS_LOCK_INIT(sc);
621
622	LIST_INIT(&sc->sc_clks_list);
623
624
625	sc->sc_init_hook.ich_func = twl_clks_init;
626	sc->sc_init_hook.ich_arg = dev;
627
628	if (config_intrhook_establish(&sc->sc_init_hook) != 0)
629		return (ENOMEM);
630
631	return (0);
632}
633
634static int
635twl_clks_detach(device_t dev)
636{
637	struct twl_clks_softc *sc;
638	struct twl_clk_entry *clk;
639	struct twl_clk_entry *tmp;
640
641	sc = device_get_softc(dev);
642
643	TWL_CLKS_XLOCK(sc);
644
645	LIST_FOREACH_SAFE(clk, &sc->sc_clks_list, link, tmp) {
646		LIST_REMOVE(clk, link);
647		sysctl_remove_oid(clk->oid, 1, 0);
648		free(clk, M_DEVBUF);
649	}
650
651	TWL_CLKS_XUNLOCK(sc);
652
653	TWL_CLKS_LOCK_DESTROY(sc);
654
655	return (0);
656}
657
658static device_method_t twl_clks_methods[] = {
659	DEVMETHOD(device_probe,		twl_clks_probe),
660	DEVMETHOD(device_attach,	twl_clks_attach),
661	DEVMETHOD(device_detach,	twl_clks_detach),
662
663	{0, 0},
664};
665
666static driver_t twl_clks_driver = {
667	"twl_clks",
668	twl_clks_methods,
669	sizeof(struct twl_clks_softc),
670};
671
672static devclass_t twl_clks_devclass;
673
674DRIVER_MODULE(twl_clks, twl, twl_clks_driver, twl_clks_devclass, 0, 0);
675MODULE_VERSION(twl_clks, 1);
676