Root/ubb-patgen/ubb-patgen.c

1/*
2 * ubb-patgen.c - UBB pattern generator
3 *
4 * Written 2013 by Werner Almesberger
5 * Copyright 2013 Werner Almesberger
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 */
12
13#include <stdint.h>
14#include <stdlib.h>
15#include <stdio.h>
16#include <unistd.h>
17#include <ctype.h>
18#include <string.h>
19#include <math.h>
20#include <time.h>
21#include <sched.h>
22#include <assert.h>
23#include <sys/mman.h>
24
25#include <ubb/ubb.h>
26#include <ubb/regs4740.h>
27#include <ubb/mmcclk.h>
28#include <ubb/physmem.h>
29
30
31#define DMA 5
32
33
34/* ----- List available bus clock frequencies ------------------------------ */
35
36
37static int cmp(const void *a, const void *b)
38{
39    const struct mmcclk *ma = a, *mb = b;
40
41    if (ma->bus_clk_hz < mb->bus_clk_hz)
42        return -1;
43    if (ma->bus_clk_hz > mb->bus_clk_hz)
44        return 1;
45    return mb->clkdiv-ma->clkdiv;
46}
47
48
49static struct mmcclk *frequencies(int *n, int all)
50{
51    struct mmcclk mmc;
52    struct mmcclk *clks = malloc(sizeof(struct mmcclk));
53    int n_clks = 1;
54
55    if (!clks) {
56        perror("malloc");
57        exit(1);
58    }
59
60    mmcclk_first(&mmc, 0,
61        MMCCLK_FLAG_WR_ONLY | (all ? MMCCLK_FLAG_ALL : 0));
62    clks[0] = mmc;
63    while (mmcclk_next(&mmc)) {
64        clks = realloc(clks, sizeof(struct mmcclk)*(n_clks+1));
65        if (!clks) {
66            perror("realloc");
67            exit(1);
68        }
69        clks[n_clks] = mmc;
70        n_clks++;
71    }
72
73    qsort(clks, n_clks, sizeof(*clks), cmp);
74    *n = n_clks;
75    return clks;
76}
77
78
79static void print_freq(FILE *file, double f)
80{
81    const char *prefix = "";
82
83    if (f >= 1000000) {
84        f /= 1000000;
85        prefix = "M";
86    } else if (f >= 1000) {
87        f /= 1000;
88        prefix = "k";
89    }
90    fprintf(file, "%g %sHz", f, prefix);
91}
92
93
94static void show_frequencies(int quiet)
95{
96    const struct mmcclk *clks;
97    int n, i;
98    double last = 0;
99
100    clks = frequencies(&n, 0);
101    for (i = 0; i != n; i++) {
102        if (quiet) {
103            if (clks[i].bus_clk_hz != last)
104                printf("%f\n", clks[i].bus_clk_hz);
105            last = clks[i].bus_clk_hz;
106        } else {
107            printf("clkdiv = %u, clkrt = %u, bus_clk = ",
108                clks[i].clkdiv, clks[i].clkrt);
109            print_freq(stdout, clks[i].bus_clk_hz);
110            putchar('\n');
111        }
112    }
113    free((void *) clks);
114}
115
116
117static int select_freq(struct mmcclk *res, int hz, int rel, int quiet, int all)
118{
119    const struct mmcclk *clks, *p, *best = NULL;
120    double d, best_d = 0;
121    int n;
122    double err;
123
124    clks = frequencies(&n, all);
125    for (p = clks; p != clks+n; p++) {
126        if (rel > 0 && p->bus_clk_hz < hz)
127            continue;
128        if (rel < 0 && p->bus_clk_hz > hz)
129            continue;
130        d = fabs(p->bus_clk_hz-hz);
131        if (!best || d < best_d) {
132            best = p;
133            best_d = d;
134        }
135    }
136    if (!best)
137        return 0;
138    *res = *best;
139    free((void *) clks);
140
141    if (quiet)
142        return 1;
143
144    if (res->bus_clk_hz != hz) {
145        fprintf(stderr, "bus clk = ");
146        print_freq(stderr, res->bus_clk_hz);
147        err = (res->bus_clk_hz-hz)/hz;
148        if (err <= -0.0001 || err >= 0.0001)
149            fprintf(stderr, " (%+.2g%%)\n", err*100);
150        else
151            fprintf(stderr, " (%+d ppm)\n", (int) (err*1000000));
152    }
153
154    return 1;
155}
156
157
158/* ----- Pattern parser ---------------------------------------------------- */
159
160
161static void *parse_pattern(const char *s, int *nibbles)
162{
163    uint8_t *buf = physmem_malloc(4095); /* maximum block size */
164    int n = 0;
165    uint8_t v = 0;
166    char *end;
167    unsigned long i;
168
169    memset(buf, 0, 4095);
170    while (*s) {
171        char ch[2] = { *s, 0 };
172
173        v = strtoul(ch, &end, 16);
174        if (*end) {
175            fprintf(stderr, "\"%c\" is not a hex digit\n", *s);
176            exit(1);
177        }
178        if (s[1] == '{') {
179            i = strtoul(s+2, &end, 0);
180            if (!*end) {
181                fprintf(stderr, "unterminated range\n");
182                exit(1);
183            }
184            if (*end != '}' || end == s+2) {
185                fprintf(stderr, "invalid range \"%.*s\"\n",
186                    end-s, s+1);
187                exit(1);
188            }
189            s = end+1;
190        } else {
191            i = 1;
192            s++;
193        }
194        while (i) {
195            if (n == 8192-64-1) {
196                fprintf(stderr, "pattern is too long\n");
197                exit(1);
198            }
199            buf[n >> 1] |= v << 4*(~n & 1);
200            n++;
201            i--;
202        }
203    }
204    /* pad to multiples of 32 bytes */
205    while (n & 63) {
206        buf[n >> 1] |= v << 4*(~n & 1);
207        n++;
208    }
209    *nibbles = n;
210    return buf;
211}
212
213
214static const char *load_pattern(const char *s)
215{
216    static char buf[20000]; /* more than enough :) */
217    FILE *file;
218    char *p = buf;
219    int comment = 0;
220    int c;
221
222    if (!strcmp(s, "-")) {
223        file = stdin;
224    } else {
225        file = fopen(s, "r");
226        if (!file)
227            return s;
228    }
229    while ((c = fgetc(file)) != EOF) {
230        if (comment) {
231            comment = c != '\n';
232            continue;
233        }
234        if (c == '#') {
235            comment = 1;
236            continue;
237        }
238        if (isspace(c))
239            continue;
240        if (buf+sizeof(buf)-1 == p) {
241            fprintf(stderr, "%s: file is too big\n", s);
242            exit(1);
243        }
244        *p++ = c;
245    }
246    if (file != stdin)
247        fclose(file);
248    *p = 0;
249    return buf;
250}
251
252
253/* ----- Real-time mode ---------------------------------------------------- */
254
255
256void realtimize(void)
257{
258    struct sched_param prm;
259
260    prm.sched_priority = sched_get_priority_max(SCHED_FIFO);
261    if (prm.sched_priority < 0) {
262        perror("sched_get_priority_max SCHED_FIFO");
263        exit(1);
264    }
265    if (sched_setscheduler(0, SCHED_FIFO, &prm) < 0) {
266        perror("sched_setscheduler SCHED_FIFO");
267        exit(1);
268    }
269}
270
271
272void unrealtime(void)
273{
274    struct sched_param prm = { .sched_priority = 0 };
275
276    if (sched_setscheduler(0, SCHED_OTHER, &prm) < 0) {
277        perror("sched_setscheduler SCHED_OTHER");
278        exit(1);
279    }
280}
281
282
283/* ----- DMA control ------------------------------------------------------- */
284
285
286static uint32_t old_dmac;
287
288
289static void dma_stop(void)
290{
291    DCS(DMA) =
292        DCS_TT | /* Transfer terminated */
293        DCS_HLT; /* DMA halt */
294    DCS(DMA) = 0; /* reset DMA channel */
295}
296
297
298static void dma_init(void)
299{
300    old_dmac = DMAC;
301
302    DMAC = DMAC_DMAE; /* activate the DMA controller (in case it's off) */
303    dma_stop();
304
305    DCM(DMA) =
306        DCM_SAI | /* source address increment */
307        (DCM_TSZ_32BYTE << DCM_TSZ_SHIFT);
308            /* transfer size is 32 bytes */
309    DRT(DMA) = DRT_MSC_TX; /* MSC transmit-fifo-empty transfer request */
310}
311
312
313static void dma_cleanup(void)
314{
315    DMAC = old_dmac;
316    dma_stop();
317}
318
319
320static void dma_setup(unsigned long buf, int nibbles)
321{
322    assert(!(nibbles & 63));
323
324    DCS(DMA) = DCS_NDES; /* no-descriptor transfer */
325    DSA(DMA) = buf; /* source */
326    DTA(DMA) = REG_PADDR(MSC_TXFIFO); /* MUST set this each time */
327    DTC(DMA) = nibbles >> 6; /* 32 bytes per transfer */
328}
329
330
331static void wait_dma_done(void)
332{
333    while (!(DCS(DMA) & DCS_TT));
334}
335
336
337/* ----- Send pattern using MSC and DMA ------------------------------------ */
338
339
340static void wait_response(void)
341{
342    while (!(MSC_STAT & MSC_STAT_END_CMD_RES));
343}
344
345
346static void wait_fifo_empty(void)
347{
348    while (!(MSC_STAT & MSC_STAT_DATA_FIFO_EMPTY));
349}
350
351
352static void wait_shifted(const struct mmcclk *clk)
353{
354    /* 8 nibbles */
355    double us = 8*1000000.0/clk->bus_clk_hz;
356
357    usleep((int) us+1);
358}
359
360
361static void wait_trigger(const char *trigger, int debounce,
362    const struct timespec *debounce_ns)
363{
364    struct timespec end, now;
365
366    /*
367     * @@@ could also try to use POSIX per-process timers here. May be
368     * slightly cleaner but could increase deviations.
369     */
370    while (*trigger) {
371        while (PIN(UBB_CLK) != *trigger-'0');
372        if (!debounce)
373            goto next;
374again:
375        if (clock_gettime(CLOCK_REALTIME, &end)) {
376            perror("clock_gettime");
377            exit(1);
378        }
379        end.tv_sec += debounce_ns->tv_sec;
380        end.tv_nsec += debounce_ns->tv_nsec;
381        if (end.tv_nsec > 999999999) {
382            end.tv_nsec -= 1000000000;
383            end.tv_sec++;
384        }
385        while (PIN(UBB_CLK) == *trigger-'0') {
386            if (clock_gettime(CLOCK_REALTIME, &now)) {
387                perror("clock_gettime");
388                exit(1);
389            }
390            if (now.tv_sec > end.tv_sec)
391                goto next;
392            if (now.tv_sec == end.tv_sec &&
393                now.tv_nsec >= end.tv_nsec)
394                goto next;
395        }
396        goto again;
397next:
398        trigger++;
399    }
400}
401
402
403static void mmc_buffer(const struct mmcclk *clk,
404    uint8_t first, unsigned long buf, int nibbles, uint32_t mask,
405    const char *trigger, int debounce, const struct timespec *debounce_ns,
406    const struct timespec *wait_ns)
407{
408    /*
409     * If under control of the MMC controller, DATx tri-state until we
410     * actually send data. That's why they have been set up as GPIOs and
411     * we'll only switch them to function when the MMC controller is in a
412     * well-defined state.
413     */
414
415    dma_setup(buf, nibbles);
416
417    MSC_STRPCL = MSC_STRPCRL_START_CLOCK; /* start the bus clock */
418    MSC_RESTO = MSC_RESTO_MASK; /* maximum response time-out */
419    MSC_BLKLEN = MSC_BLKLEN_MASK; /* never reach the end (with CRC) */
420
421    MSC_CMDAT =
422        MSC_CMDAT_BUS_WIDTH_4 << MSC_CMDAT_BUS_WIDTH_SHIFT |
423        MSC_CMDAT_DMA_EN | /* DMA */
424        MSC_CMDAT_WRITE_READ | /* write */
425        MSC_CMDAT_DATA_EN | /* with data transfer */
426        MSC_CMDAT_RESPONSE_FORMAT_R1; /* R1 response */
427
428        MSC_STRPCL = MSC_STRPCRL_START_OP;
429
430    /*
431     * Make sure we've reached the end of the command and then send the
432     * first pattern (eight times, since this is the smallest amount we
433     * can send.
434     */
435
436    wait_response();
437
438    MSC_TXFIFO = first*0x11111111;
439
440    wait_fifo_empty();
441    wait_shifted(clk);
442
443    /*
444     * Since the transfer (of nominally 4095 bytes) is not done yet, the
445     * MMC controller will hold the bus at the last value sent. It's now
446     * safe to switch from GPIO to function.
447     */
448
449    PDFUNS = mask;
450
451    realtimize();
452
453    if (trigger)
454        wait_trigger(trigger, debounce, debounce_ns);
455    if (wait_ns->tv_sec || wait_ns->tv_nsec)
456        if (nanosleep(wait_ns, NULL))
457            perror("nanosleep");
458
459    /*
460     * Send the pattern with DMA. Note that we still have to send the first
461     * pattern, since the static state we begin from may not have been
462     * present long enough.
463     */
464
465    DCS(DMA) =
466        DCS_NDES | /* no descriptor */
467        DCS_CTE; /* enable channel */
468
469    unrealtime();
470
471    wait_dma_done();
472    wait_fifo_empty();
473    wait_shifted(clk);
474
475    /*
476     * We're done. As far as the MMC controller is concerned, the transfer
477     * is still not finished (i.e., we haven't sent 4095 bytes) and will
478     * therefore just hold the bus. We can now return the bus to GPIO.
479     * This form of handover also prevents the MMC controller from sending
480     * a CRC, which may confuse the recipient of the pattern.
481     */
482}
483
484
485static void send_buffer(const struct mmcclk *clk,
486    const uint8_t *buf, int nibbles, uint32_t mask,
487    const char *trigger, int debounce, const struct timespec *debounce_ns,
488    const struct timespec *wait_ns)
489{
490    struct physmem_vec vec;
491    int n;
492
493    if (physmem_flush(buf, nibbles)) {
494        perror("physmem_flush");
495        exit(1);
496    }
497
498    n = physmem_xlat(buf, nibbles >> 1, &vec, 1);
499    if (n < 0) {
500        perror("physmem_xlat_vec");
501        exit(1);
502    }
503    if (n != 1) {
504        fprintf(stderr, "physmem_xlat_vec: expected 1, got %d\n", n);
505        exit(1);
506    }
507    mmc_buffer(clk, buf[0] >> 4, vec.addr, nibbles, mask,
508        trigger, debounce, debounce_ns, wait_ns);
509}
510
511
512static void dma_pattern(const struct mmcclk *clk,
513    const char *pattern, uint32_t mask, const char *trigger,
514    int debounce, const struct timespec *debounce_ns,
515    const struct timespec *wait_ns)
516{
517    const uint8_t *buf;
518    int n;
519
520    if (!*pattern) {
521        fprintf(stderr, "pattern is empty\n");
522        exit(1);
523    }
524    buf = parse_pattern(pattern, &n);
525
526    if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
527        perror("mlockall");
528        exit(1);
529    }
530
531    if (trigger) {
532        PDFUNC = UBB_CLK;
533        IN(UBB_CLK);
534    }
535
536    dma_init();
537
538    /* Initial static state: the first pattern. */
539
540    PDFUNS = UBB_CMD;
541    PDDATC = ~((buf[0] >> 4) << 10) & mask;
542    PDDATS = (buf[0] >> 4) << 10;
543    PDDIRS = mask;
544
545    send_buffer(clk, buf, n, mask,
546        trigger, debounce, debounce_ns, wait_ns);
547
548    /* Final static state: the last pattern. */
549
550    PDDATC = ~((buf[(n >> 1)-1] & 0xf) << 10) & mask;
551    PDDATS = (buf[(n >> 1)-1] & 0xf) << 10;
552
553    PDFUNC = mask;
554
555    dma_cleanup();
556}
557
558
559/* ----- Command-line processing ------------------------------------------- */
560
561
562static int frequency(const char *s, int *hz, int *rel)
563{
564    char *end;
565    double f;
566
567    f = strtod(s, &end);
568
569    if (end == s || f < 0)
570        return 0;
571
572    switch (*end) {
573    case 'M':
574    case 'm':
575        *hz = f*1000000;
576        end++;
577        break;
578    case 'K':
579    case 'k':
580        *hz = f*1000;
581        end++;
582        break;
583    default:
584        *hz = f;
585        break;
586    }
587
588    if ((end[0] == 'H' || end[0] == 'h') &&
589        (end[1] == 'Z' || end[1] == 'z'))
590        end += 2;
591
592    switch (*end) {
593    case '+':
594        *rel = 1;
595        end++;
596        break;
597    case '-':
598        *rel = -1;
599        end++;
600        break;
601    default:
602        *rel = 0;
603        break;
604    }
605
606    return !*end;
607}
608
609
610static int duration(const char *s, double *res, int *rel)
611{
612    char *end;
613    double d;
614
615    d = strtod(s, &end);
616
617    if (end == s || d < 0)
618        return 0;
619
620    switch (*end) {
621    case 'M':
622    case 'm':
623        d /= 1e3;
624        end++;
625        break;
626    case 'U':
627    case 'u':
628        d /= 1e6;
629        end++;
630        break;
631    case 'N':
632    case 'n':
633        d /= 1e9;
634        end++;
635        break;
636    default:
637        break;
638    }
639
640    if (*end == 'S' || *end == 's')
641        end++;
642
643    switch (*end) {
644    case '+':
645        *rel = 1;
646        end++;
647        break;
648    case '-':
649        *rel = -1;
650        end++;
651        break;
652    default:
653        *rel = 0;
654        break;
655    }
656
657    if (*end)
658        return 0;
659
660    *res = d;
661
662    return 1;
663}
664
665
666static int duration_timespec(const char *s, struct timespec *res, int *rel)
667{
668    double d;
669
670    if (!duration(s, &d, rel))
671        return 0;
672    res->tv_sec = d;
673    res->tv_nsec = (d-res->tv_sec)*1e9;
674    return 1;
675}
676
677
678static int interval(const char *s, int *hz, int *rel)
679{
680    double d;
681
682    if (!duration(s, &d, rel))
683        return 0;
684    *hz = 1/d;
685    *rel = -*rel;
686    return 1;
687}
688
689
690static void usage(const char *name)
691{
692        fprintf(stderr,
693"usage: %s\n"
694" %s [-q] (-f|-F) freq_hz|(-i|-I) interval_s\n"
695" %s [-q] [(-f|-F) freq_hz|(-i|-I) interval_s] -c [active_s]\n"
696" %s [-q] [(-f|-F) freq_hz|(-i|-I) interval_s]\n"
697" [-C|-t 0|1... [-d debounce_s]] [-w wait_s] [-m mask] [-p]\n"
698" file|pattern\n\n"
699" -c output bus clock on CLK without sending a pattern\n"
700" -C temporarily output bus clock on CLK (for debugging)\n"
701" -d deb_s trigger debounce time (default: no debouncing)\n"
702" -f freq_hz set bus clock to the specified frequency (default: 1 MHz)\n"
703" -F freq_hz like -f, but also allow \"overclocking\"\n"
704" -i inter_s set bus clock such that one cycle equals the specified "
705  "interval\n"
706" -I inter_s like -i, but also allow \"overclocking\"\n"
707" -m mask use only the DATx lines specified in the mask (default: 0xf)\n"
708" -p force interpretation of argument as pattern (and not file)\n"
709" -q quiet. Don't pretty-print frequencies; don't report clock\n"
710" differences.\n"
711" -t 0|1... start pattern when trigger/CLK has passed through the sequence\n"
712" (default: start pattern immediately)\n"
713" -w wait_s wait between trigger and sending the pattern\n\n"
714" active_s keep running that many seconds after setting the clock\n"
715" (default: exit immediately but leave the clock on)\n"
716" file file containing the pattern\n"
717" pattern send the specified pattern on DAT0 through DAT3\n\n"
718"Frequency: the frequency in hertz, optionally followed by \"M\" or \"k\",\n"
719" optionally followed by \"Hz\", optionally followed by \"+\" or \"-\".\n"
720" \"+\" selects a frequency >= the specified one, \"-\" one <=.\n"
721" Without +/-, the closest available frequency is selected.\n"
722"Duration: the duration in seconds, optionally followed by \"m\", \"u\", or\n"
723" \"n\", optionally followed by \"s\", optionally followed by \"+\" or \"-\"."
724  "\n"
725"Pattern: hex digits corresponding to 1 for DAT0, 2 for DAT1, etc.\n"
726" {n} repeats the preceding digit n times, e.g., 1{3} is equivalent to 111.\n"
727    , name, name, name, name);
728    exit(1);
729}
730
731
732int main(int argc, char **argv)
733{
734    struct mmcclk clk;
735    int all = 0;
736    int bus_hz = 0, clk_only = 0, clkout = 0, bus_rel = 0;
737    const char *pattern = NULL;
738    int quiet = 0, force_pattern = 0;
739    struct timespec active_ns;
740    int active_rel;
741    int keep_clk = 1;
742    uint8_t mask = 0xf;
743    const char *trigger = NULL;
744    struct timespec debounce_ns;
745    int debounce = 0, debounce_rel;
746    struct timespec wait_ns = { 0, 0 };
747    int wait_rel;
748    char *end;
749    int c;
750    unsigned long tmp;
751    const char *p;
752
753    while ((c = getopt(argc, argv, "cCd:f:F:i:I:m:pqt:w:")) != EOF)
754        switch (c) {
755        case 'c':
756            clk_only = 1;
757            break;
758        case 'C':
759            clkout = 1;
760            break;
761        case 'd':
762            if (!duration_timespec(optarg,
763                &debounce_ns, &debounce_rel))
764                usage(*argv);
765            if (debounce_rel < 0)
766                usage(*argv);
767            debounce = 1;
768            break;
769        case 'F':
770            all = 1;
771            /* fall through */
772        case 'f':
773            if (!frequency(optarg, &bus_hz, &bus_rel))
774                usage(*argv);
775            break;
776        case 'I':
777            all = 1;
778            /* fall through */
779        case 'i':
780            if (!interval(optarg, &bus_hz, &bus_rel))
781                usage(*argv);
782            break;
783        case 'm':
784            tmp = strtoul(optarg, &end, 0);
785            if (*end)
786                usage(*argv);
787            if (tmp & ~0xfUL) {
788                fprintf(stderr, "mask is too large\n");
789                exit(1);
790            }
791            mask = tmp;
792            break;
793        case 'p':
794            force_pattern = 1;
795            break;
796        case 'q':
797            quiet = 1;
798            break;
799        case 't':
800            trigger = optarg;
801            if (!*trigger)
802                usage(*argv);
803            for (p = trigger; *p; p++)
804                if (*p != '0' && *p != '1')
805                    usage(*argv);
806            break;
807        case 'w':
808            if (!duration_timespec(optarg,
809                &wait_ns, &wait_rel))
810                usage(*argv);
811            if (wait_rel < 0)
812                usage(*argv);
813            break;
814        default:
815            usage(*argv);
816        }
817
818    if (clkout && clk_only)
819        usage(*argv);
820    if ((clkout || clk_only) && trigger)
821        usage(*argv);
822
823    switch (argc-optind) {
824    case 0:
825        if (clk_only)
826            break;
827        if (clkout || force_pattern || trigger)
828            usage(*argv);
829
830        ubb_open(UBB_ALL);
831        if (bus_hz) {
832            if (!select_freq(&clk, bus_hz, bus_rel, quiet, all)) {
833                fprintf(stderr,
834                    "no suitable frequency found\n");
835                exit(1);
836            }
837            printf("%f\n", clk.bus_clk_hz);
838        } else {
839            show_frequencies(quiet);
840        }
841        return 0;
842    case 1:
843        if (clk_only) {
844            if (force_pattern)
845                usage(*argv);
846            if (!duration_timespec(argv[optind],
847                &active_ns, &active_rel))
848                usage(*argv);
849            if (active_rel < 0)
850                usage(*argv);
851            keep_clk = 0;
852        } else {
853            pattern = argv[optind];
854        }
855        break;
856    default:
857        usage(*argv);
858    }
859
860    if (pattern && !force_pattern)
861        pattern = load_pattern(pattern);
862
863    ubb_open(UBB_ALL);
864
865    PDFUNS = UBB_CMD;
866
867    if (!bus_hz)
868        bus_hz = 1000000;
869
870    if (!select_freq(&clk, bus_hz, bus_rel, quiet, all)) {
871        fprintf(stderr, "no suitable frequency found\n");
872        exit(1);
873    }
874
875    if (clkout || clk_only)
876        PDFUNS = UBB_CLK;
877    mmcclk_start(&clk);
878
879    if (pattern)
880        dma_pattern(&clk, pattern, mask << 10,
881            trigger, debounce, &debounce_ns, &wait_ns);
882
883    if (!keep_clk)
884        if (nanosleep(&active_ns, NULL))
885            perror("nanosleep");
886    if (pattern) {
887        mmcclk_stop();
888        ubb_close(mask << 10 | (trigger ? UBB_CLK : 0));
889    } else if (keep_clk) {
890        ubb_close(UBB_CLK);
891    } else {
892        mmcclk_stop();
893        ubb_close(0);
894    }
895
896    return 0;
897}
898

Archive Download this file

Branches:
master



interactive