]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - sys/arm/ti/twl/twl_clks.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / sys / arm / ti / twl / twl_clks.c
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$");
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
78 static 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  */
103 struct twl_clock {
104         const char      *name;
105         uint8_t         subdev;
106         uint8_t         regbase;
107 };
108
109 static const struct twl_clock twl4030_clocks[] = {
110         { "32kclkout", 0, 0x8e },
111         { NULL, 0, 0x00 } 
112 };
113
114 static 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
123 struct 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
131 struct 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  */
172 static inline int
173 twl_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
179 static inline int
180 twl_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  */
199 int
200 twl_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
259 done:
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  */
277 static int
278 twl_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
335 done:
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  */
358 int
359 twl_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  */
389 int
390 twl_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  */
421 static int
422 twl_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  */
450 static struct twl_clk_entry*
451 twl_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  */
501 static int
502 twl_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  */
582 static void
583 twl_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
597 static int
598 twl_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
611 static int
612 twl_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
634 static int
635 twl_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
658 static 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
666 static driver_t twl_clks_driver = {
667         "twl_clks",
668         twl_clks_methods,
669         sizeof(struct twl_clks_softc),
670 };
671
672 static devclass_t twl_clks_devclass;
673
674 DRIVER_MODULE(twl_clks, twl, twl_clks_driver, twl_clks_devclass, 0, 0);
675 MODULE_VERSION(twl_clks, 1);