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