1
0
mirror of git://projects.qi-hardware.com/ben-blinkenlights.git synced 2024-11-27 17:05:20 +02:00
ben-blinkenlights/ubb-la/ubb-la.c

475 lines
9.9 KiB
C

/*
* 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 <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include <sys/mman.h>
#include <ubb/ubb.h>
#include <ubb/regs4740.h>
#include <ubb/mmcclk.h>
#include <ubb/physmem.h>
#include "gui.h"
#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,
const struct mmcclk *clk, const struct mmcclk *fast_clk)
{
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) {
MSC_STRPCL = MSC_STRPCRL_START_OP;
MSC_CLKRT = fast_clk->clkrt;
MSCCDR = 0;
while (!(MSC_STAT & MSC_STAT_END_CMD_RES));
IN(UBB_CMD);
MSCCDR = clk->clkdiv;
MSC_CLKRT = clk->clkrt;
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();
}
/* ----- ASCII output ------------------------------------------------------ */
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);
}
/* ----- Capture ----------------------------------------------------------- */
static int do_buf(int nibbles, uint32_t trigger, uint32_t mask,
const struct mmcclk *clk, int use_gui)
{
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;
if (use_gui)
gui(buf, INITIAL_SKIP, nibbles, clk->bus_clk_hz);
else
print_samples(stdout, buf, INITIAL_SKIP, nibbles);
return 1;
}
static void do_bufs(int n_bufs, int nibbles,
const struct mmcclk *clk, const struct mmcclk *fast_clk)
{
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;
}
assert(!fast_clk->clkdiv);
xfers(addrs, n_bufs, nibbles, clk, fast_clk);
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] [-g] [-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"
" -g display the captured waveforms graphically (default:\n"
" print as text to standard output)\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;
int use_gui = 0;
struct mmcclk clk, fast_clk;
char *end;
int c, res;
while ((c = getopt(argc, argv, "Cf:F:gn: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 'g':
use_gui = 1;
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 (use_gui)
gui_init();
if (!multi) {
res = !do_buf(8128, trigger, mask, &clk, use_gui);
} else {
frequency(&fast_clk, 84e6, 1);
do_bufs(multi, 8128, &clk, &fast_clk);
res = 0;
}
mmcclk_stop();
ubb_close(UBB_ALL);
return res;
}