/* * ubb-la.c - UBB logic analyzer * * Written 2013 by Werner Almesberger * Copyright 2013 Werner Almesberger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #define DMA 5 #define KEY_MASK 0x5fc0000 /* * The initial skip is for samples loaded into the FIFO when starting the * command, before waiting for the trigger. A completely filled FIFO would * hold 16 * 32 bits or 128 samples. In reality, we observe 122 samples for * sample rates up to 24 MHz and 123 samples for 42 or 56 MHz. */ #define INITIAL_SKIP 122 /* should be 123 for higher speeds */ /* ----- Enable/disable interrupts ----------------------------------------- */ static uint32_t old_icmr; static void disable_interrupts(void) { old_icmr = ICMR; ICMSR = 0xffffffff; } static void enable_interrupts(void) { ICMCR = ~old_icmr; } /* ----- DMA control ------------------------------------------------------- */ static uint32_t old_dmac; static void dma_stop(void) { DCS(DMA) = DCS_TT | /* Transfer terminated */ DCS_HLT; /* DMA halt */ DCS(DMA) = 0; /* reset DMA channel */ } static void dma_init(void) { old_dmac = DMAC; DMAC = DMAC_DMAE; /* activate the DMA controller (in case it's off) */ dma_stop(); DCM(DMA) = DCM_DAI | /* destination address increment */ (DCM_TSZ_32BYTE << DCM_TSZ_SHIFT); /* transfer size is 32 bytes */ DRT(DMA) = DRT_MSC_RX; /* MSC receive-fifo-full transfer request */ } static void dma_cleanup(void) { DMAC = old_dmac; dma_stop(); } static void dma_setup(unsigned long buf, int nibbles) { assert(!(nibbles & 63)); /* need to reset DSA, DTA, DTC when done. uncertain about DCS */ DCS(DMA) = DCS_NDES; /* no-descriptor transfer */ DSA(DMA) = REG_PADDR(MSC_RXFIFO); /* source */ DTA(DMA) = buf; /* destination */ DTC(DMA) = nibbles >> 6; /* 32 bytes per transfer */ } static void wait_dma_done(void) { while (!(DCS(DMA) & DCS_TT)); } /* ----- MMC control ------------------------------------------------------- */ static int xfer(unsigned long buf, int nibbles, uint32_t trigger, uint32_t mask) { dma_init(); dma_setup(buf, nibbles); MSC_STRPCL = MSC_STRPCRL_START_CLOCK; /* start the bus clock */ MSC_RESTO = MSC_RESTO_MASK; /* maximum response time-out */ MSC_RDTO = MSC_RDTO_MASK; MSC_BLKLEN = nibbles >> 1; MSC_CMDAT = MSC_CMDAT_BUS_WIDTH_4 << MSC_CMDAT_BUS_WIDTH_SHIFT | MSC_CMDAT_DMA_EN | /* DMA */ MSC_CMDAT_DATA_EN | /* with data transfer */ MSC_CMDAT_RESPONSE_FORMAT_NONE; /* no response required */ MSC_STRPCL = MSC_STRPCRL_START_OP; while (MSC_STAT & MSC_STAT_DATA_FIFO_EMPTY); IN(UBB_CMD); disable_interrupts(); while ((PDPIN & mask) != trigger) if ((PDPIN & KEY_MASK) != KEY_MASK) goto quit; DCS(DMA) = DCS_NDES | /* no descriptor */ DCS_CTE; /* enable channel */ enable_interrupts(); wait_dma_done(); //printf("MSC_STAT = %08x\n", MSC_STAT); dma_cleanup(); return 1; quit: enable_interrupts(); dma_cleanup(); return 0; } static void xfers(unsigned long *bufs, int n_bufs, int nibbles) { int i = 0; dma_init(); MSC_STRPCL = MSC_STRPCRL_START_CLOCK; /* start the bus clock */ MSC_RESTO = MSC_RESTO_MASK; /* maximum response time-out */ MSC_RDTO = MSC_RDTO_MASK; MSC_BLKLEN = nibbles >> 1; MSC_CMDAT = MSC_CMDAT_BUS_WIDTH_4 << MSC_CMDAT_BUS_WIDTH_SHIFT | MSC_CMDAT_DMA_EN | /* DMA */ MSC_CMDAT_DATA_EN | /* with data transfer */ MSC_CMDAT_RESPONSE_FORMAT_NONE; /* no response required */ disable_interrupts(); OUT(UBB_CMD); dma_setup(bufs[0], nibbles); while (1) { int j; MSC_STRPCL = MSC_STRPCRL_START_OP; /* * Experimental value for 12 MSa/s: hangs always/usually with * 231 cycles, hangs sometimes with 232, hands never (so far) * with 233. */ for (j = 0; j != 233; j++) asm volatile (""); // while (MSC_STAT & MSC_STAT_DATA_FIFO_EMPTY); // while (!(MSC_STAT & MSC_STAT_END_CMD_RES)); IN(UBB_CMD); DCS(DMA) = DCS_NDES | /* no descriptor */ DCS_CTE; /* enable channel */ /* * @@@ We could enable interrupts while waiting, particularly * at low sample rates, as long as the probability of missing * the end of the DMA transfer is acceptably low. */ wait_dma_done(); if (++i == n_bufs) break; OUT(UBB_CMD); dma_setup(bufs[i], nibbles); while (!(MSC_STAT & MSC_STAT_DATA_TRAN_DONE)); } enable_interrupts(); dma_cleanup(); } static void print_samples(FILE *file, uint8_t *buf, int skip, int nibbles) { uint8_t v, last = 0xff; int i, count = 0; for (i = skip; i != nibbles; i++) { v = (buf[i >> 1] >> (4*(~i & 1))) & 0xf; if (v == last) { count++; } else { switch (count) { case 0: break; case 1: printf("%X", last); break; default: printf("%X{%d}", last, count); break; } last = v; count = 1; } } if (count == 1) printf("%X\n", last); else printf("%X{%d}\n", last, count); } static int do_buf(int nibbles, uint32_t trigger, uint32_t mask) { uint8_t *buf = physmem_malloc(4096); struct physmem_vec vec; int n; if (mlockall(MCL_CURRENT | MCL_FUTURE)) { perror("mlockall"); exit(1); } memset(buf, 0, 4096); physmem_flush(buf, 4096); n = physmem_xlat(buf, nibbles >> 1, &vec, 1); if (n != 1) { fprintf(stderr, "physmem_xlat_vec: expected 1, got %d\n", n); exit(1); } if (!xfer(vec.addr, nibbles, trigger, mask)) return 0; print_samples(stdout, buf, INITIAL_SKIP, nibbles); return 1; } static void do_bufs(int n_bufs, int nibbles) { uint8_t *bufs[n_bufs]; struct physmem_vec vecs[n_bufs]; unsigned long addrs[n_bufs]; int i,n; if (mlockall(MCL_CURRENT | MCL_FUTURE)) { perror("mlockall"); exit(1); } for (i = 0; i != n_bufs; i++) { bufs[i] = physmem_malloc(4096); memset(bufs[i], 0, 4096); physmem_flush(bufs[i], 4096); n = physmem_xlat(bufs[i], nibbles >> 1, vecs+i, 1); if (n != 1) { fprintf(stderr, "physmem_xlat_vec: expected 1, got %d\n", n); exit(1); } addrs[i] = vecs[i].addr; } xfers(addrs, n_bufs, nibbles); for (i = 0; i != n_bufs; i++) print_samples(stdout, bufs[i], 0, nibbles); } /* ----- Command-line processing ------------------------------------------- */ /* * Among equal bus rates, pick the configuration with the fastest MMC clock. * It'll save a few nanoseconds. */ static void frequency(struct mmcclk *clk, int hz, int all) { struct mmcclk mmc; mmcclk_first(&mmc, 0, MMCCLK_FLAG_RD_ONLY | (all ? MMCCLK_FLAG_ALL : 0)); *clk = mmc; while (mmcclk_next(&mmc)) if (fabs(clk->bus_clk_hz-hz) > fabs(mmc.bus_clk_hz-hz) || (fabs(clk->bus_clk_hz-hz) == fabs(mmc.bus_clk_hz-hz) && clk->clkdiv > mmc.clkdiv)) *clk = mmc; } static unsigned long xlat_pins(unsigned long pins) { if (pins & ~0x1fUL) { fprintf(stderr, "invalid trigger set/mask: 0x%lx\n", pins); exit(1); } pins <<= 10; if (pins & (0x10 << 10)) pins = (pins ^ (0x10 << 10)) | UBB_CLK; return pins; } static void usage(const char *name) { fprintf(stderr, "usage: %s [-C] [-t pattern/mask] [(-f|-F) frequency_MHz] [-n N]\n\n" " -C output the MMC clock on CLK/TRIG (for debugging)\n" " -f freq_MHz select the specified frequency (default; 1 MHz)\n" " -F freq_MHz like -f, but also allow \"overclocking\"\n" " -n N capture N buffers worth of samples without waiting for a\n" " trigger\n" " -t pattern/mask start capture at the specified pattern (DAT0 = 1, etc.,\n" " CLK = 16). Default: any change on TRIG.\n" , name); exit(1); } int main(int argc, char **argv) { double freq_mhz = 1; int all = 0; unsigned long trigger = 1, mask = 0; unsigned long multi = 0; int clkout = 0; struct mmcclk clk; char *end; int c, res; while ((c = getopt(argc, argv, "Cf:F:n:t:")) != EOF) switch (c) { case 'C': clkout = 1; break; case 'F': all = 1; /* fall through */ case 'f': freq_mhz = strtod(optarg, &end); if (*end) usage(*argv); break; case 'n': multi = strtoul(optarg, &end, 0); if (*end) usage(*argv); break; case 't': trigger = strtoul(optarg, &end, 0); if (*end != '/') usage(*argv); mask = strtoul(end+1, &end, 0); if (*end) usage(*argv); trigger = xlat_pins(trigger); mask = xlat_pins(mask); break; default: usage(*argv); } if (optind != argc) usage(*argv); ubb_open(UBB_ALL); PDFUNS = UBB_DAT0 | UBB_DAT1 | UBB_DAT2 | UBB_DAT3; if (clkout) PDFUNS = UBB_CLK; OUT(UBB_CMD); CLR(UBB_CMD); PDFUNC = UBB_CMD; frequency(&clk, 1e6*freq_mhz, all); fprintf(stderr, "bus %g MHz controller %g MHz\n", clk.bus_clk_hz/1e6, clk.sys_clk_hz/(clk.clkdiv+1.0)/1e6); mmcclk_start(&clk); if (trigger == 1) { trigger = ~PDPIN & UBB_CLK; mask = UBB_CLK; } if (!multi) { res = !do_buf(8128, trigger, mask); } else { do_bufs(multi, 8128); res = 0; } mmcclk_stop(); ubb_close(UBB_ALL); return res; }