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