mirror of
git://projects.qi-hardware.com/ben-blinkenlights.git
synced 2024-12-04 11:25:56 +02:00
13f34170de
We inherited the cast from ubb-patgen where the buffer was "const" and thus had to be cast for the the non-const argument of physmem_xlat. We never needed a cast in ubb-la, though. Since physmem_xlat now uses "const" as well, the cast is even doubly superfluous.
337 lines
6.7 KiB
C
337 lines
6.7 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>
|
|
|
|
|
|
#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));
|
|
|
|
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 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;
|
|
}
|
|
|
|
|
|
/* ----- 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)
|
|
{
|
|
struct mmcclk mmc;
|
|
|
|
mmcclk_first(&mmc, 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 frequency_MHz]\n\n"
|
|
" -C output the MMC clock on CLK/TRIG (for debugging)\n"
|
|
" -f freq_MHz select the specified frequency (default; 1 MHz)\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;
|
|
unsigned long trigger = 1, mask = 0;
|
|
int clkout = 0;
|
|
struct mmcclk clk;
|
|
char *end;
|
|
int c, res;
|
|
|
|
while ((c = getopt(argc, argv, "Cf:t:")) != EOF)
|
|
switch (c) {
|
|
case 'C':
|
|
clkout = 1;
|
|
break;
|
|
case 'f':
|
|
freq_mhz = strtod(optarg, &end);
|
|
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);
|
|
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;
|
|
}
|
|
|
|
res = !do_buf(8128, trigger, mask);
|
|
|
|
mmcclk_stop();
|
|
ubb_close(UBB_ALL);
|
|
|
|
return res;
|
|
}
|