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