xref: /freebsd/sys/arm/ti/twl/twl_clks.c (revision 643ac419fafba89f5adda0e0ea75b538727453fb)
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);
650